summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/dockerfiles/Dockerfile.32-bit20
-rw-r--r--.github/dockerfiles/Dockerfile.64-bit22
-rw-r--r--.github/dockerfiles/Dockerfile.cross-compile56
-rw-r--r--.github/dockerfiles/Dockerfile.debian-base43
-rw-r--r--.github/dockerfiles/Dockerfile.documentation23
-rw-r--r--.github/dockerfiles/Dockerfile.ubuntu-base13
-rwxr-xr-x.github/scripts/base-tag20
-rw-r--r--.github/workflows/main.yaml217
-rw-r--r--.github/workflows/update-base.yaml41
-rw-r--r--.gitignore15
-rw-r--r--.travis.yml81
-rw-r--r--CONTRIBUTING.md12
-rw-r--r--HOWTO/DEPRECATE.md79
-rw-r--r--HOWTO/INSTALL-ANDROID.md157
-rw-r--r--HOWTO/INSTALL-WIN32-OLD.md898
-rw-r--r--HOWTO/INSTALL-WIN32.md828
-rw-r--r--HOWTO/INSTALL.md15
-rw-r--r--HOWTO/OTP-PATCH-APPLY.md8
-rw-r--r--HOWTO/SYSTEMTAP.md2
-rw-r--r--HOWTO/TESTING.md93
-rw-r--r--Makefile.in68
-rw-r--r--OTP_VERSION2
-rw-r--r--bootstrap/bin/no_dot_erlang.bootbin6624 -> 6759 bytes
-rw-r--r--bootstrap/bin/start.bootbin6624 -> 6759 bytes
-rw-r--r--bootstrap/bin/start_clean.bootbin6624 -> 6759 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_a.beambin3332 -> 3076 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_asm.beambin11052 -> 10932 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_block.beambin3444 -> 3828 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_call_types.beambin0 -> 13424 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_clean.beambin3516 -> 3688 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_dict.beambin4644 -> 4492 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_digraph.beambin0 -> 3508 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_disasm.beambin20868 -> 20680 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_except.beambin4252 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_flatten.beambin1928 -> 1928 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_jump.beambin10100 -> 10128 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beambin28700 -> 27952 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_listing.beambin1580 -> 1604 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_opcodes.beambin7548 -> 7648 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_peep.beambin3616 -> 3572 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa.beambin12252 -> 14868 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_bool.beambin0 -> 22656 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_bsm.beambin18020 -> 17688 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_codegen.beambin37932 -> 38544 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_dead.beambin12424 -> 12300 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_funs.beambin2556 -> 2588 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_lint.beambin7528 -> 8144 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_opt.beambin40080 -> 48192 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_pp.beambin5500 -> 6112 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beambin46768 -> 46916 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_recv.beambin4412 -> 4228 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_share.beambin5372 -> 5444 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_type.beambin28480 -> 33988 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_trim.beambin8676 -> 9060 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_types.beambin0 -> 15496 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_utils.beambin3548 -> 3664 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_validator.beambin50996 -> 48344 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_z.beambin3604 -> 3800 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl.beambin28236 -> 28084 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_clauses.beambin2808 -> 2696 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_inline.beambin34712 -> 33016 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_sets.beambin2728 -> 2412 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_trees.beambin20408 -> 20528 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/compile.beambin41604 -> 41896 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/compiler.app12
-rw-r--r--bootstrap/lib/compiler/ebin/core_lib.beambin3720 -> 3500 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_lint.beambin12472 -> 12420 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_parse.beambin63064 -> 61100 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_pp.beambin11452 -> 11452 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_scan.beambin6248 -> 6116 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/erl_bifs.beambin2080 -> 2096 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/rec_env.beambin4468 -> 4256 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_alias.beambin5556 -> 5452 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_bsm.beambin1672 -> 1580 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_fold.beambin45252 -> 38204 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_fold_lists.beambin4020 -> 3996 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_inline.beambin3908 -> 3428 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_prepare.beambin0 -> 1712 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_pre_attributes.beambin2468 -> 2232 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_core.beambin50688 -> 60276 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_kernel.beambin50920 -> 41184 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_kernel_pp.beambin12052 -> 10964 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application.beambin3852 -> 3840 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application_controller.beambin30032 -> 31464 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application_master.beambin6108 -> 6048 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application_starter.beambin1180 -> 1172 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/auth.beambin6148 -> 6260 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/code.beambin12920 -> 15652 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/code_server.beambin22636 -> 22424 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log.beambin29556 -> 28940 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log_1.beambin22348 -> 21864 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log_server.beambin6136 -> 6004 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log_sup.beambin556 -> 552 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/dist_ac.beambin23356 -> 22936 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/dist_util.beambin12188 -> 16500 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_boot_server.beambin5572 -> 5580 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_compile_server.beambin5052 -> 4904 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_ddll.beambin2744 -> 2684 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_distribution.beambin1872 -> 2004 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_epmd.beambin7040 -> 7368 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_reply.beambin888 -> 860 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_signal_handler.beambin1100 -> 1096 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erpc.beambin0 -> 7924 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/error_handler.beambin1576 -> 1576 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/error_logger.beambin6144 -> 6180 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erts_debug.beambin9228 -> 8996 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file.beambin13348 -> 13564 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file_io_server.beambin15292 -> 15184 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file_server.beambin4912 -> 4876 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_sctp.beambin3212 -> 3224 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_tcp.beambin2096 -> 2604 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_tcp_socket.beambin0 -> 26212 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_udp.beambin1636 -> 1644 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global.beambin28700 -> 28056 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global_group.beambin15708 -> 15712 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global_search.beambin2920 -> 2924 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/group.beambin14196 -> 13984 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/group_history.beambin5496 -> 5656 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/heart.beambin5140 -> 5136 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/hipe_unified_loader.beambin12340 -> 11912 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet.beambin23648 -> 23896 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_sctp.beambin1440 -> 1436 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_tcp.beambin3016 -> 3016 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_tcp_dist.beambin864 -> 936 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_udp.beambin2104 -> 2096 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_config.beambin7236 -> 7180 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_db.beambin25360 -> 26096 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_dns.beambin18568 -> 17184 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_gethost_native.beambin9704 -> 9628 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_hosts.beambin1896 -> 1600 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_parse.beambin13424 -> 13092 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_res.beambin13200 -> 12920 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_sctp.beambin2148 -> 2136 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_tcp.beambin2784 -> 2784 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_tcp_dist.beambin7476 -> 7956 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_udp.beambin2188 -> 2176 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.app12
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.beambin3764 -> 3844 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel_config.beambin2604 -> 2620 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel_refc.beambin2288 -> 2224 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/local_tcp.beambin2180 -> 2128 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/local_udp.beambin1388 -> 1332 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger.beambin15084 -> 14896 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_backend.beambin2544 -> 2420 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_config.beambin3232 -> 3840 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_disk_log_h.beambin3348 -> 3276 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_filters.beambin1748 -> 1720 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_formatter.beambin9060 -> 8868 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_h_common.beambin7696 -> 7444 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_handler_watcher.beambin1344 -> 1328 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_olp.beambin8308 -> 7968 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_proxy.beambin2908 -> 2804 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_server.beambin11352 -> 11244 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_simple_h.beambin4212 -> 4272 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_std_h.beambin9604 -> 9572 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_sup.beambin636 -> 632 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net.beambin3276 -> 3412 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net_adm.beambin2824 -> 2816 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net_kernel.beambin25096 -> 27656 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/os.beambin5112 -> 5000 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/pg.beambin0 -> 7960 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/pg2.beambin7568 -> 7444 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/ram_file.beambin6008 -> 5928 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io.beambin1660 -> 1652 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_compressed.beambin2336 -> 2372 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_deflate.beambin2592 -> 2584 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_delayed.beambin5224 -> 5248 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_inflate.beambin4140 -> 4136 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_list.beambin2460 -> 2568 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_raw.beambin396 -> 396 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/rpc.beambin7704 -> 13252 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/seq_trace.beambin1600 -> 1588 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/socket.beambin0 -> 12784 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/standard_error.beambin3724 -> 3696 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user.beambin10980 -> 10896 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user_drv.beambin10948 -> 10996 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user_sup.beambin1704 -> 1700 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/wrap_log_reader.beambin2940 -> 2920 bytes
-rw-r--r--bootstrap/lib/kernel/include/dist.hrl14
-rw-r--r--bootstrap/lib/kernel/include/dist_util.hrl6
-rw-r--r--bootstrap/lib/kernel/include/eep48.hrl14
-rw-r--r--bootstrap/lib/kernel/include/logger.hrl2
-rw-r--r--bootstrap/lib/stdlib/ebin/array.beambin11568 -> 11472 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/base64.beambin6496 -> 6084 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/beam_lib.beambin18852 -> 18576 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/binary.beambin2844 -> 2796 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/c.beambin16988 -> 17988 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/calendar.beambin8108 -> 8104 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets.beambin45120 -> 44704 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_server.beambin6444 -> 6364 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_sup.beambin544 -> 540 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_utils.beambin25696 -> 25292 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_v9.beambin45356 -> 44104 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dict.beambin8836 -> 8632 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/digraph.beambin7500 -> 7612 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/digraph_utils.beambin6736 -> 6300 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/edlin.beambin10472 -> 10380 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/edlin_expand.beambin3748 -> 4220 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/epp.beambin28328 -> 27668 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_abstract_code.beambin968 -> 960 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_anno.beambin3484 -> 3456 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_bits.beambin2348 -> 2348 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_compile.beambin6808 -> 6692 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_error.beambin8252 -> 8844 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_eval.beambin35024 -> 34504 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_expand_records.beambin19344 -> 18588 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_internal.beambin6732 -> 6756 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_lint.beambin86552 -> 85132 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_parse.beambin95840 -> 93880 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_posix_msg.beambin5168 -> 5192 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_pp.beambin27196 -> 26816 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_scan.beambin25692 -> 25524 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_tar.beambin31540 -> 29824 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/error_logger_file_h.beambin3944 -> 3916 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/error_logger_tty_h.beambin4776 -> 4760 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/escript.beambin15692 -> 15148 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/ets.beambin21608 -> 20952 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/eval_bits.beambin7660 -> 7380 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/file_sorter.beambin27268 -> 26832 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/filelib.beambin10364 -> 11252 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/filename.beambin14772 -> 14592 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gb_sets.beambin7712 -> 7560 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gb_trees.beambin5084 -> 5088 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen.beambin4896 -> 6004 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_event.beambin13012 -> 14888 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_fsm.beambin12424 -> 14040 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_server.beambin15328 -> 18060 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_statem.beambin22144 -> 24252 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io.beambin5792 -> 5776 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib.beambin13636 -> 13248 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_format.beambin14868 -> 14648 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_fread.beambin6508 -> 6344 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_pretty.beambin21136 -> 20792 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/lists.beambin29272 -> 28736 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/log_mf_h.beambin2352 -> 2364 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/maps.beambin3188 -> 3192 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/math.beambin1288 -> 1288 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/ms_transform.beambin18396 -> 17912 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/orddict.beambin2900 -> 2888 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/ordsets.beambin1848 -> 1844 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/otp_internal.beambin8256 -> 8976 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/pool.beambin3596 -> 3572 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/proc_lib.beambin12624 -> 14608 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/proplists.beambin4596 -> 4336 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/qlc.beambin65048 -> 63240 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/qlc_pt.beambin70588 -> 67364 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/queue.beambin5908 -> 5908 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/rand.beambin29048 -> 28616 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/random.beambin1768 -> 1816 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/re.beambin12388 -> 12052 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/sets.beambin6152 -> 6132 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell.beambin28396 -> 28276 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell_default.beambin4064 -> 4700 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell_docs.beambin0 -> 17148 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/slave.beambin4740 -> 4744 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/sofs.beambin35284 -> 34668 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/stdlib.app7
-rw-r--r--bootstrap/lib/stdlib/ebin/string.beambin35740 -> 33924 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/supervisor.beambin21588 -> 23384 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/supervisor_bridge.beambin2332 -> 5324 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/sys.beambin9116 -> 9148 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/timer.beambin5276 -> 5248 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/unicode.beambin14076 -> 12860 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/unicode_util.beambin200320 -> 200984 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/uri_string.beambin26440 -> 26548 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/win32reg.beambin5120 -> 5060 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/zip.beambin24208 -> 23640 bytes
-rw-r--r--bootstrap/lib/stdlib/include/erl_bits.hrl16
-rw-r--r--configure.src16
-rw-r--r--erts/.gitignore1
-rw-r--r--erts/Makefile13
-rw-r--r--erts/aclocal.m4169
-rwxr-xr-xerts/autoconf/configure.vxworks142
-rw-r--r--erts/autoconf/vxworks/sed.general132
-rw-r--r--erts/autoconf/vxworks/sed.vxworks_cpu3247
-rw-r--r--erts/autoconf/vxworks/sed.vxworks_ppc3258
-rw-r--r--erts/autoconf/vxworks/sed.vxworks_ppc60353
-rw-r--r--erts/autoconf/vxworks/sed.vxworks_ppc603_nolongcall53
-rw-r--r--erts/autoconf/vxworks/sed.vxworks_ppc86052
-rw-r--r--erts/autoconf/vxworks/sed.vxworks_simlinux66
-rw-r--r--erts/autoconf/vxworks/sed.vxworks_simso70
-rw-r--r--erts/autoconf/vxworks/sed.vxworks_sparc40
-rw-r--r--erts/configure.in156
-rw-r--r--erts/doc/src/.gitignore3
-rw-r--r--erts/doc/src/Makefile143
-rw-r--r--erts/doc/src/absform.xml303
-rw-r--r--erts/doc/src/alt_disco.xml27
-rw-r--r--erts/doc/src/alt_dist.xml128
-rw-r--r--erts/doc/src/atomics.xml6
-rw-r--r--erts/doc/src/communication.xml4
-rw-r--r--erts/doc/src/counters.xml37
-rw-r--r--erts/doc/src/crash_dump.xml36
-rw-r--r--erts/doc/src/driver.xml4
-rw-r--r--erts/doc/src/driver_entry.xml136
-rw-r--r--erts/doc/src/epmd_cmd.xml (renamed from erts/doc/src/epmd.xml)45
-rw-r--r--erts/doc/src/erl_cmd.xml (renamed from erts/doc/src/erl.xml)457
-rw-r--r--erts/doc/src/erl_dist_protocol.xml828
-rw-r--r--erts/doc/src/erl_driver.xml834
-rw-r--r--erts/doc/src/erl_ext_dist.xml237
-rw-r--r--erts/doc/src/erl_ext_fig.gifbin3834 -> 3840 bytes
-rw-r--r--erts/doc/src/erl_nif.xml905
-rw-r--r--erts/doc/src/erl_prim_loader.xml24
-rw-r--r--erts/doc/src/erl_tracer.xml359
-rw-r--r--erts/doc/src/erlang.xml2919
-rw-r--r--erts/doc/src/erlc_cmd.xml (renamed from erts/doc/src/erlc.xml)23
-rw-r--r--erts/doc/src/erlsrv_cmd.xml (renamed from erts/doc/src/erlsrv.xml)23
-rw-r--r--erts/doc/src/erts_alloc.xml153
-rw-r--r--erts/doc/src/escript_cmd.xml (renamed from erts/doc/src/escript.xml)37
-rw-r--r--erts/doc/src/inet_cfg.xml39
-rw-r--r--erts/doc/src/init.xml63
-rw-r--r--erts/doc/src/introduction.xml2
-rw-r--r--erts/doc/src/match_spec.xml12
-rw-r--r--erts/doc/src/notes.xml2686
-rw-r--r--erts/doc/src/part.xml (renamed from erts/doc/src/part.xml.src)1
-rw-r--r--erts/doc/src/persistent_term.xml34
-rw-r--r--erts/doc/src/ref_man.xml (renamed from erts/doc/src/ref_man.xml.src)19
-rw-r--r--erts/doc/src/run_erl_cmd.xml (renamed from erts/doc/src/run_erl.xml)17
-rw-r--r--erts/doc/src/specs.xml (renamed from erts/doc/src/specs.xml.src)1
-rw-r--r--erts/doc/src/start_cmd.xml (renamed from erts/doc/src/start.xml)13
-rw-r--r--erts/doc/src/start_erl_cmd.xml (renamed from erts/doc/src/start_erl.xml)13
-rw-r--r--erts/doc/src/time_correction.xml336
-rw-r--r--erts/doc/src/werl_cmd.xml (renamed from erts/doc/src/werl.xml)11
-rw-r--r--erts/doc/src/zlib.xml122
-rw-r--r--erts/emulator/Makefile.in65
-rwxr-xr-xerts/emulator/asan/asan_logs_to_html453
-rw-r--r--erts/emulator/asan/suppress18
-rw-r--r--erts/emulator/beam/arith_instrs.tab18
-rw-r--r--erts/emulator/beam/atom.names21
-rw-r--r--erts/emulator/beam/beam_bif_load.c102
-rw-r--r--erts/emulator/beam/beam_bp.c395
-rw-r--r--erts/emulator/beam/beam_bp.h16
-rw-r--r--erts/emulator/beam/beam_debug.c37
-rw-r--r--erts/emulator/beam/beam_emu.c612
-rw-r--r--erts/emulator/beam/beam_load.c475
-rw-r--r--erts/emulator/beam/beam_load.h2
-rw-r--r--erts/emulator/beam/bif.c686
-rw-r--r--erts/emulator/beam/bif.tab40
-rw-r--r--erts/emulator/beam/bif_instrs.tab159
-rw-r--r--erts/emulator/beam/big.c18
-rw-r--r--erts/emulator/beam/big.h2
-rw-r--r--erts/emulator/beam/binary.c12
-rw-r--r--erts/emulator/beam/break.c9
-rw-r--r--erts/emulator/beam/bs_instrs.tab15
-rw-r--r--erts/emulator/beam/code_ix.c74
-rw-r--r--erts/emulator/beam/code_ix.h9
-rw-r--r--erts/emulator/beam/dist.c2049
-rw-r--r--erts/emulator/beam/dist.h168
-rw-r--r--erts/emulator/beam/erl_alloc.c56
-rw-r--r--erts/emulator/beam/erl_alloc.h80
-rw-r--r--erts/emulator/beam/erl_alloc.types31
-rw-r--r--erts/emulator/beam/erl_alloc_util.c1306
-rw-r--r--erts/emulator/beam/erl_alloc_util.h100
-rw-r--r--erts/emulator/beam/erl_async.c47
-rw-r--r--erts/emulator/beam/erl_bif_binary.c2
-rw-r--r--erts/emulator/beam/erl_bif_chksum.c10
-rw-r--r--erts/emulator/beam/erl_bif_guard.c2
-rw-r--r--erts/emulator/beam/erl_bif_info.c601
-rw-r--r--erts/emulator/beam/erl_bif_lists.c35
-rw-r--r--erts/emulator/beam/erl_bif_persistent.c590
-rw-r--r--erts/emulator/beam/erl_bif_port.c33
-rw-r--r--erts/emulator/beam/erl_bif_re.c2
-rw-r--r--erts/emulator/beam/erl_bif_trace.c218
-rw-r--r--erts/emulator/beam/erl_binary.h2
-rw-r--r--erts/emulator/beam/erl_bits.c24
-rw-r--r--erts/emulator/beam/erl_bits.h4
-rw-r--r--erts/emulator/beam/erl_cpu_topology.c13
-rw-r--r--erts/emulator/beam/erl_cpu_topology.h5
-rw-r--r--erts/emulator/beam/erl_db.c812
-rw-r--r--erts/emulator/beam/erl_db.h19
-rw-r--r--erts/emulator/beam/erl_db_catree.c43
-rw-r--r--erts/emulator/beam/erl_db_hash.c819
-rw-r--r--erts/emulator/beam/erl_db_hash.h12
-rw-r--r--erts/emulator/beam/erl_db_tree.c250
-rw-r--r--erts/emulator/beam/erl_db_tree_util.h7
-rw-r--r--erts/emulator/beam/erl_db_util.c169
-rw-r--r--erts/emulator/beam/erl_db_util.h63
-rw-r--r--erts/emulator/beam/erl_flxctr.c80
-rw-r--r--erts/emulator/beam/erl_flxctr.h18
-rw-r--r--erts/emulator/beam/erl_fun.c145
-rw-r--r--erts/emulator/beam/erl_fun.h1
-rw-r--r--erts/emulator/beam/erl_gc.c78
-rw-r--r--erts/emulator/beam/erl_global_literals.c69
-rw-r--r--erts/emulator/beam/erl_global_literals.h (renamed from lib/erl_interface/src/legacy/erl_error.h)26
-rw-r--r--erts/emulator/beam/erl_hl_timer.c4
-rw-r--r--erts/emulator/beam/erl_init.c27
-rw-r--r--erts/emulator/beam/erl_io_queue.c2
-rw-r--r--erts/emulator/beam/erl_lock_check.c3
-rw-r--r--erts/emulator/beam/erl_lock_count.h11
-rw-r--r--erts/emulator/beam/erl_lock_flags.h4
-rw-r--r--erts/emulator/beam/erl_map.c17
-rw-r--r--erts/emulator/beam/erl_message.c29
-rw-r--r--erts/emulator/beam/erl_message.h1
-rw-r--r--erts/emulator/beam/erl_monitor_link.c305
-rw-r--r--erts/emulator/beam/erl_monitor_link.h281
-rw-r--r--erts/emulator/beam/erl_nfunc_sched.c89
-rw-r--r--erts/emulator/beam/erl_nfunc_sched.h144
-rw-r--r--erts/emulator/beam/erl_nif.c779
-rw-r--r--erts/emulator/beam/erl_node_tables.c48
-rw-r--r--erts/emulator/beam/erl_node_tables.h39
-rw-r--r--erts/emulator/beam/erl_port.h13
-rw-r--r--erts/emulator/beam/erl_port_task.c3
-rw-r--r--erts/emulator/beam/erl_proc_sig_queue.c1368
-rw-r--r--erts/emulator/beam/erl_proc_sig_queue.h147
-rw-r--r--erts/emulator/beam/erl_process.c1192
-rw-r--r--erts/emulator/beam/erl_process.h113
-rw-r--r--erts/emulator/beam/erl_process_dict.c2
-rw-r--r--erts/emulator/beam/erl_process_dump.c7
-rw-r--r--erts/emulator/beam/erl_ptab.c2
-rw-r--r--erts/emulator/beam/erl_sched_spec_pre_alloc.c2
-rw-r--r--erts/emulator/beam/erl_sched_spec_pre_alloc.h6
-rw-r--r--erts/emulator/beam/erl_sys_driver.h2
-rw-r--r--erts/emulator/beam/erl_thr_progress.c32
-rw-r--r--erts/emulator/beam/erl_thr_progress.h3
-rw-r--r--erts/emulator/beam/erl_threads.h27
-rw-r--r--erts/emulator/beam/erl_trace.c39
-rw-r--r--erts/emulator/beam/erl_trace.h11
-rw-r--r--erts/emulator/beam/erl_utils.h47
-rw-r--r--erts/emulator/beam/erlang_dtrace.d39
-rw-r--r--erts/emulator/beam/erlang_lttng.h44
-rw-r--r--erts/emulator/beam/error.h16
-rw-r--r--erts/emulator/beam/export.c66
-rw-r--r--erts/emulator/beam/export.h83
-rw-r--r--erts/emulator/beam/external.c2184
-rw-r--r--erts/emulator/beam/external.h33
-rw-r--r--erts/emulator/beam/global.h25
-rw-r--r--erts/emulator/beam/hash.c107
-rw-r--r--erts/emulator/beam/hash.h66
-rw-r--r--erts/emulator/beam/index.c45
-rw-r--r--erts/emulator/beam/instrs.tab259
-rw-r--r--erts/emulator/beam/io.c217
-rw-r--r--erts/emulator/beam/macros.tab134
-rw-r--r--erts/emulator/beam/msg_instrs.tab5
-rw-r--r--erts/emulator/beam/ops.tab387
-rw-r--r--erts/emulator/beam/packet_parser.c7
-rw-r--r--erts/emulator/beam/packet_parser.h6
-rw-r--r--erts/emulator/beam/register.c118
-rw-r--r--erts/emulator/beam/register.h1
-rw-r--r--erts/emulator/beam/sys.h55
-rw-r--r--erts/emulator/beam/time.c7
-rw-r--r--erts/emulator/beam/trace_instrs.tab22
-rw-r--r--erts/emulator/beam/utils.c937
-rw-r--r--erts/emulator/drivers/common/inet_drv.c657
-rw-r--r--erts/emulator/drivers/unix/ttsl_drv.c12
-rw-r--r--erts/emulator/drivers/win32/registry_drv.c6
-rw-r--r--erts/emulator/drivers/win32/ttsl_drv.c4
-rw-r--r--erts/emulator/drivers/win32/win_con.c66
-rw-r--r--erts/emulator/hipe/hipe_amd64_asm.m42
-rw-r--r--erts/emulator/hipe/hipe_amd64_glue.S54
-rw-r--r--erts/emulator/hipe/hipe_arm_asm.m42
-rw-r--r--erts/emulator/hipe/hipe_arm_glue.S10
-rw-r--r--erts/emulator/hipe/hipe_bif2.c1
-rw-r--r--erts/emulator/hipe/hipe_bif_list.m42
-rw-r--r--erts/emulator/hipe/hipe_debug.c1
-rw-r--r--erts/emulator/hipe/hipe_instrs.tab9
-rw-r--r--erts/emulator/hipe/hipe_mode_switch.c8
-rw-r--r--erts/emulator/hipe/hipe_ppc_asm.m42
-rw-r--r--erts/emulator/hipe/hipe_ppc_glue.S40
-rw-r--r--erts/emulator/hipe/hipe_sparc_asm.m42
-rw-r--r--erts/emulator/hipe/hipe_x86_glue.S46
-rw-r--r--erts/emulator/internal_doc/AutomaticYieldingOfCCode.md62
-rw-r--r--erts/emulator/internal_doc/NewLinking.tla194
-rw-r--r--erts/emulator/internal_doc/beam_makeops.md24
-rw-r--r--erts/emulator/nifs/common/prim_file_nif.c64
-rw-r--r--erts/emulator/nifs/common/prim_file_nif.h15
-rw-r--r--erts/emulator/nifs/common/prim_net_nif.c107
-rw-r--r--erts/emulator/nifs/common/prim_socket_nif.c (renamed from erts/emulator/nifs/common/socket_nif.c)5893
-rw-r--r--erts/emulator/nifs/common/socket_dbg.c68
-rw-r--r--erts/emulator/nifs/common/socket_dbg.h16
-rw-r--r--erts/emulator/nifs/common/socket_int.h6
-rw-r--r--erts/emulator/nifs/common/socket_util.c149
-rw-r--r--erts/emulator/nifs/common/socket_util.h20
-rw-r--r--erts/emulator/nifs/unix/unix_prim_file.c153
-rw-r--r--erts/emulator/nifs/win32/win_prim_file.c233
-rw-r--r--erts/emulator/pcre/LICENCE6
-rw-r--r--erts/emulator/pcre/README.pcre_update.md5
-rw-r--r--erts/emulator/pcre/local_config.h2
-rw-r--r--erts/emulator/pcre/pcre-8.43.tar.bz2bin1576584 -> 0 bytes
-rw-r--r--erts/emulator/pcre/pcre-8.44.tar.bz2bin0 -> 1577611 bytes
-rw-r--r--erts/emulator/pcre/pcre.h4
-rw-r--r--erts/emulator/pcre/pcre_compile.c36
-rw-r--r--erts/emulator/pcre/pcre_jit_compile.c6
-rw-r--r--erts/emulator/sys/common/erl_check_io.c57
-rw-r--r--erts/emulator/sys/common/erl_check_io.h6
-rw-r--r--erts/emulator/sys/common/erl_mmap.c13
-rw-r--r--erts/emulator/sys/common/erl_mmap.h13
-rw-r--r--erts/emulator/sys/common/erl_osenv.h28
-rw-r--r--erts/emulator/sys/common/erl_poll.c45
-rw-r--r--erts/emulator/sys/common/erl_poll.h11
-rw-r--r--erts/emulator/sys/common/erl_sys_common_misc.c12
-rw-r--r--erts/emulator/sys/unix/erl_child_setup.c34
-rw-r--r--erts/emulator/sys/unix/sys.c20
-rw-r--r--erts/emulator/sys/unix/sys_drivers.c1
-rw-r--r--erts/emulator/sys/unix/sys_uds.c2
-rw-r--r--erts/emulator/sys/win32/erl_win_sys.h4
-rw-r--r--erts/emulator/sys/win32/sys.c15
-rw-r--r--erts/emulator/sys/win32/sys_env.c1
-rw-r--r--erts/emulator/test/Makefile28
-rw-r--r--erts/emulator/test/a_SUITE.erl24
-rw-r--r--erts/emulator/test/alloc_SUITE.erl180
-rw-r--r--erts/emulator/test/async_ports_SUITE_data/cport.c55
-rw-r--r--erts/emulator/test/beam_SUITE.erl3
-rw-r--r--erts/emulator/test/bif_SUITE.erl125
-rw-r--r--erts/emulator/test/binary_SUITE.erl128
-rw-r--r--erts/emulator/test/bs_bincomp_SUITE.erl14
-rw-r--r--erts/emulator/test/bs_construct_SUITE.erl32
-rw-r--r--erts/emulator/test/call_trace_SUITE.erl72
-rw-r--r--erts/emulator/test/code_SUITE_data/call_purged_fun_tester.erl3
-rw-r--r--erts/emulator/test/counters_SUITE.erl4
-rw-r--r--erts/emulator/test/decode_packet_SUITE.erl74
-rw-r--r--erts/emulator/test/dirty_bif_SUITE.erl4
-rw-r--r--erts/emulator/test/distribution_SUITE.erl304
-rw-r--r--erts/emulator/test/driver_SUITE.erl79
-rw-r--r--erts/emulator/test/driver_SUITE_data/Makefile.src14
-rw-r--r--erts/emulator/test/driver_SUITE_data/lots_of_fds_used_wrapper.c61
-rw-r--r--erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c4
-rw-r--r--erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c4
-rw-r--r--erts/emulator/test/emulator.spec1
-rw-r--r--erts/emulator/test/emulator_bench.spec1
-rw-r--r--erts/emulator/test/erl_drv_thread_SUITE_data/rwlock.c2
-rw-r--r--erts/emulator/test/erl_link_SUITE.erl165
-rw-r--r--erts/emulator/test/erts_debug_SUITE.erl5
-rw-r--r--erts/emulator/test/erts_test_destructor.erl41
-rw-r--r--erts/emulator/test/estone_SUITE_data/estone_cat.c8
-rw-r--r--erts/emulator/test/exception_SUITE.erl150
-rw-r--r--erts/emulator/test/hash_SUITE.erl658
-rw-r--r--erts/emulator/test/hash_property_test_SUITE.erl103
-rw-r--r--erts/emulator/test/hibernate_SUITE.erl9
-rw-r--r--erts/emulator/test/hipe_SUITE.erl47
-rw-r--r--erts/emulator/test/hipe_SUITE_data/trycatch_1.erl5
-rw-r--r--erts/emulator/test/lcnt_SUITE.erl4
-rw-r--r--erts/emulator/test/list_bif_SUITE.erl68
-rw-r--r--erts/emulator/test/lttng_SUITE.erl70
-rw-r--r--erts/emulator/test/map_SUITE.erl16
-rw-r--r--erts/emulator/test/match_spec_SUITE.erl3
-rw-r--r--erts/emulator/test/mtx_SUITE_data/Makefile.src4
-rw-r--r--erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c1
-rw-r--r--erts/emulator/test/nif_SUITE.erl177
-rw-r--r--erts/emulator/test/nif_SUITE_data/Makefile.src7
-rw-r--r--erts/emulator/test/nif_SUITE_data/nif_SUITE.c59
-rw-r--r--erts/emulator/test/nif_SUITE_data/nif_api_2_0/erl_nif.h4
-rw-r--r--erts/emulator/test/nif_SUITE_data/nif_mod.c7
-rw-r--r--erts/emulator/test/nif_SUITE_data/nif_mod.erl14
-rw-r--r--erts/emulator/test/node_container_SUITE.erl16
-rw-r--r--erts/emulator/test/nofrag_SUITE.erl5
-rw-r--r--erts/emulator/test/num_bif_SUITE.erl45
-rw-r--r--erts/emulator/test/os_signal_SUITE.erl9
-rw-r--r--erts/emulator/test/persistent_term_SUITE.erl137
-rw-r--r--erts/emulator/test/persistent_term_SUITE_data/Makefile.src8
-rw-r--r--erts/emulator/test/persistent_term_SUITE_data/erts_test_destructor.c83
-rw-r--r--erts/emulator/test/port_SUITE.erl8
-rw-r--r--erts/emulator/test/port_bif_SUITE.erl12
-rw-r--r--erts/emulator/test/port_bif_SUITE_data/port_test.c3
-rw-r--r--erts/emulator/test/process_SUITE.erl1044
-rw-r--r--erts/emulator/test/property_test/phash2_properties.erl63
-rw-r--r--erts/emulator/test/receive_SUITE.erl2
-rw-r--r--erts/emulator/test/scheduler_SUITE.erl191
-rw-r--r--erts/emulator/test/send_term_SUITE.erl24
-rw-r--r--erts/emulator/test/small_SUITE.erl12
-rw-r--r--erts/emulator/test/statistics_SUITE.erl65
-rw-r--r--erts/emulator/test/system_info_SUITE.erl61
-rw-r--r--erts/emulator/test/timer_bif_SUITE.erl32
-rw-r--r--erts/emulator/test/trace_SUITE.erl51
-rw-r--r--erts/emulator/test/trace_SUITE_data/Makefile.src3
-rw-r--r--erts/emulator/test/trace_SUITE_data/slow_drv.c108
-rw-r--r--erts/emulator/test/trace_call_time_SUITE.erl115
-rw-r--r--erts/emulator/test/trace_local_SUITE.erl9
-rw-r--r--erts/emulator/test/z_SUITE.erl36
-rwxr-xr-xerts/emulator/utils/beam_makeops293
-rwxr-xr-xerts/emulator/utils/find_cross_ycf55
-rwxr-xr-xerts/emulator/utils/make_alloc_types3
-rwxr-xr-xerts/emulator/utils/make_driver_tab1
-rwxr-xr-xerts/emulator/utils/make_preload10
-rwxr-xr-xerts/emulator/utils/make_tables78
-rw-r--r--erts/emulator/valgrind/suppress.standard45
-rw-r--r--erts/epmd/Makefile2
-rw-r--r--erts/epmd/epmd.mk2
-rw-r--r--erts/epmd/src/Makefile.in4
-rw-r--r--erts/epmd/src/epmd.c51
-rw-r--r--erts/epmd/src/epmd.h1
-rw-r--r--erts/epmd/src/epmd_int.h34
-rw-r--r--erts/epmd/src/epmd_srv.c169
-rw-r--r--erts/epmd/test/Makefile2
-rw-r--r--erts/etc/common/Makefile.in53
-rw-r--r--erts/etc/common/erlc.c18
-rw-r--r--erts/etc/common/erlexec.c279
-rw-r--r--erts/etc/common/heart.c2
-rw-r--r--erts/etc/common/inet_gethost.c3
-rw-r--r--erts/etc/unix/Install.src1
-rw-r--r--erts/etc/unix/cerl.src37
-rw-r--r--erts/etc/unix/etp-commands.in179
-rw-r--r--erts/etc/unix/run_erl.c14
-rw-r--r--erts/etc/unix/to_erl.c24
-rw-r--r--erts/etc/win32/Install.c2
-rw-r--r--erts/etc/win32/beam.rc9
-rw-r--r--erts/etc/win32/cygwin_tools/reg_query.sh24
-rwxr-xr-xerts/etc/win32/cygwin_tools/w32_path.sh59
-rw-r--r--erts/etc/win32/erl.c27
-rw-r--r--erts/etc/win32/erl_log.c2
-rw-r--r--erts/etc/win32/erlsrv/erlsrv_interactive.c2
-rw-r--r--erts/etc/win32/manifest.xml17
-rw-r--r--erts/etc/win32/msys_tools/reg_query.sh2
-rwxr-xr-xerts/etc/win32/msys_tools/w32_path.sh59
-rw-r--r--erts/etc/win32/nsis/Makefile60
-rwxr-xr-xerts/etc/win32/nsis/dll_version_helper.sh19
-rw-r--r--erts/etc/win32/nsis/erlang.nsi386
-rw-r--r--erts/etc/win32/nsis/erlang20.nsi28
-rwxr-xr-xerts/etc/win32/nsis/find_redist.sh50
-rw-r--r--erts/etc/win32/resource.h7
-rw-r--r--erts/etc/win32/win_erlexec.c13
-rw-r--r--erts/etc/win32/wsl_tools/SetupWSLcross.bat71
-rwxr-xr-xerts/etc/win32/wsl_tools/erl45
-rwxr-xr-xerts/etc/win32/wsl_tools/erlc60
-rwxr-xr-xerts/etc/win32/wsl_tools/javac.sh53
-rwxr-xr-x[-rw-r--r--]erts/etc/win32/wsl_tools/make_bootstrap_ini.sh (renamed from lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.first)33
-rwxr-xr-x[-rw-r--r--]erts/etc/win32/wsl_tools/make_local_ini.sh (renamed from erts/doc/Makefile)45
-rwxr-xr-xerts/etc/win32/wsl_tools/reg_query.sh19
-rwxr-xr-x[-rw-r--r--]erts/etc/win32/wsl_tools/vc/ar.sh (renamed from lib/erl_interface/test/erl_ext_SUITE_data/Makefile.first)36
-rwxr-xr-xerts/etc/win32/wsl_tools/vc/cc.sh382
-rwxr-xr-xerts/etc/win32/wsl_tools/vc/coffix.c161
-rwxr-xr-xerts/etc/win32/wsl_tools/vc/emu_cc.sh100
-rwxr-xr-xerts/etc/win32/wsl_tools/vc/ld.sh214
-rwxr-xr-xerts/etc/win32/wsl_tools/vc/mc.sh96
-rwxr-xr-xerts/etc/win32/wsl_tools/vc/rc.sh94
-rwxr-xr-xerts/etc/win32/wsl_tools/w32_path.sh119
-rw-r--r--erts/include/internal/erl_misc_utils.h1
-rw-r--r--erts/include/internal/erl_printf_format.h4
-rw-r--r--erts/include/internal/ethread.h88
-rw-r--r--erts/include/internal/ethread_header_config.h.in20
-rw-r--r--erts/include/internal/gcc/ethr_membar.h47
-rw-r--r--erts/include/internal/gcc/ethread.h38
-rw-r--r--erts/lib_src/Makefile.in26
-rw-r--r--erts/lib_src/common/erl_misc_utils.c292
-rw-r--r--erts/lib_src/common/erl_printf.c5
-rw-r--r--erts/lib_src/common/erl_printf_format.c5
-rw-r--r--erts/lib_src/common/ethr_aux.c121
-rw-r--r--erts/lib_src/common/ethr_mutex.c41
-rw-r--r--erts/lib_src/pthread/ethr_event.c2
-rw-r--r--erts/lib_src/pthread/ethread.c61
-rw-r--r--erts/lib_src/win/ethread.c33
-rw-r--r--erts/lib_src/yielding_c_fun/.gitignore16
-rw-r--r--erts/lib_src/yielding_c_fun/GNUmakefile212
-rw-r--r--erts/lib_src/yielding_c_fun/Makefile54
-rw-r--r--erts/lib_src/yielding_c_fun/README.md610
-rw-r--r--erts/lib_src/yielding_c_fun/TODO4
-rwxr-xr-xerts/lib_src/yielding_c_fun/bin/yielding_c_fun42
-rw-r--r--erts/lib_src/yielding_c_fun/doc/thread_tutorial.md222
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/.gitignore11
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/.misc/clang_blacklist.txt10
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/GIT_VERSION73
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/LICENSE201
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/Makefile121
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/README.md69
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/chained_hash_set.h307
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/simple_c_gc.c310
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/simple_c_gc.h57
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/sorted_list_set.h370
-rw-r--r--erts/lib_src/yielding_c_fun/lib/simple_c_gc/test.c53
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/GIT_VERSION468
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/LICENSE24
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/Makefile106
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/README.md138
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/re.c470
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/re.h54
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/exrex.py307
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/regex_test.py77
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/regex_test_neg.py82
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test1.c141
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test2.c2097
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_print.c21
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_rand.c28
-rw-r--r--erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_rand_neg.c29
-rw-r--r--erts/lib_src/yielding_c_fun/main_target.mk41
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/auto_yield.c124
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/auto_yield.c.out173
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/const_defenition.c54
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/const_defenition.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/consume_reds.c85
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/consume_reds.c.out11
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/control_statements.c289
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/control_statements.c.out45
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state.c129
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state.c.out11
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state_alt_syntax.c131
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state_alt_syntax.c.out11
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/debug_ptr_to_stack.c54
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/debug_ptr_to_stack.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/declarations.c91
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/declarations.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/declarations_in_for_loops.c83
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/declarations_in_for_loops.c.out211
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/destroy_while_yielded.c92
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/destroy_while_yielded.c.out33
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/in_code_var_declaration.c54
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/in_code_var_declaration.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/multi_scope_yield.c93
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/multi_scope_yield.c.out9
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/nested_call_consume_reds.c91
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/nested_call_consume_reds.c.out11
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/nested_loop_yield.c104
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/nested_loop_yield.c.out74
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/.gitignore25
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/LICENSE191
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/Makefile34
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/README.md23
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/Makefile83
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/.gitignore3
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/.travis.yml2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/GIT_VERSION265
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/LICENSE24
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/Makefile11
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/README.md97
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256.c224
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256.h4
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256_orginal.c210
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/test.c238
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha256_nif.c173
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/rebar.config12
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/src/sha256_nif.app.src14
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/src/sha256_nif.erl27
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/test/basic_SUITE.erl32
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/simple_fun_call.c92
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/simple_fun_call.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/simple_yield.c58
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/simple_yield.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/simple_yield_no_reds.c61
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/simple_yield_no_reds.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/simple_yielding_fun_call.c111
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/simple_yielding_fun_call.c.out18
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/stack_array.c135
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/stack_array.c.out4
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/static_inline_function.c71
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/static_inline_function.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_code.c (renamed from lib/erl_interface/src/legacy/erl_internal.h)68
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_main.c71
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_main.c.out302
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/test_only_output_yielding_funs.c60
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/test_only_output_yielding_funs.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/test_trap.c42
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/thread_example.c88
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/thread_example.c.out47
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/void_param.c79
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/void_param.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/void_ret_fun.c85
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/void_ret_fun.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/ycf_stack_alloc.c126
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/ycf_stack_alloc.c.out15
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/yield_with_struct.c67
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/yield_with_struct.c.out2
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/yielding_fun_in_control.c68
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/yielding_fun_in_control.c.out10
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/yielding_mutual_recursion.c104
-rw-r--r--erts/lib_src/yielding_c_fun/test/examples/yielding_mutual_recursion.c.out302
-rwxr-xr-xerts/lib_src/yielding_c_fun/test/test.sh176
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_helpers.h (renamed from lib/erl_interface/src/legacy/portability.h)34
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_lexer.c483
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_lists.h316
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_main.c330
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_node.c915
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_node.h455
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_parser.c1498
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_parser.h (renamed from lib/erl_interface/src/legacy/erl_connect.h)18
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_printers.c493
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_printers.h (renamed from lib/erl_interface/src/legacy/erl_fix_alloc.h)23
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_string.c111
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_string.h89
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_symbol.c166
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_symbol.h110
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_utils.c106
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_utils.h (renamed from lib/erl_interface/src/legacy/erl_marshal.h)34
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_yield_fun.c1625
-rw-r--r--erts/lib_src/yielding_c_fun/ycf_yield_fun.h116
-rw-r--r--erts/preloaded/ebin/atomics.beambin3312 -> 3312 bytes
-rw-r--r--erts/preloaded/ebin/counters.beambin3112 -> 3108 bytes
-rw-r--r--erts/preloaded/ebin/erl_init.beambin2304 -> 2316 bytes
-rw-r--r--erts/preloaded/ebin/erl_prim_loader.beambin52732 -> 52296 bytes
-rw-r--r--erts/preloaded/ebin/erl_tracer.beambin2224 -> 2224 bytes
-rw-r--r--erts/preloaded/ebin/erlang.beambin100436 -> 108992 bytes
-rw-r--r--erts/preloaded/ebin/erts_code_purger.beambin10996 -> 10900 bytes
-rw-r--r--erts/preloaded/ebin/erts_dirty_process_signal_handler.beambin2784 -> 2760 bytes
-rw-r--r--erts/preloaded/ebin/erts_internal.beambin20984 -> 22896 bytes
-rw-r--r--erts/preloaded/ebin/erts_literal_area_collector.beambin3272 -> 3260 bytes
-rw-r--r--erts/preloaded/ebin/init.beambin50224 -> 50832 bytes
-rw-r--r--erts/preloaded/ebin/persistent_term.beambin1860 -> 1860 bytes
-rw-r--r--erts/preloaded/ebin/prim_buffer.beambin3612 -> 3612 bytes
-rw-r--r--erts/preloaded/ebin/prim_eval.beambin1540 -> 1540 bytes
-rw-r--r--erts/preloaded/ebin/prim_file.beambin28012 -> 28736 bytes
-rw-r--r--erts/preloaded/ebin/prim_inet.beambin81472 -> 81244 bytes
-rw-r--r--erts/preloaded/ebin/prim_net.beambin5140 -> 4900 bytes
-rw-r--r--erts/preloaded/ebin/prim_socket.beambin0 -> 31272 bytes
-rw-r--r--erts/preloaded/ebin/prim_zip.beambin22452 -> 22300 bytes
-rw-r--r--erts/preloaded/ebin/socket.beambin79016 -> 0 bytes
-rw-r--r--erts/preloaded/ebin/socket_registry.beambin5672 -> 5588 bytes
-rw-r--r--erts/preloaded/ebin/zlib.beambin19728 -> 19676 bytes
-rw-r--r--erts/preloaded/src/Makefile4
-rw-r--r--erts/preloaded/src/erl_init.erl4
-rw-r--r--erts/preloaded/src/erl_prim_loader.erl2
-rw-r--r--erts/preloaded/src/erlang.erl704
-rw-r--r--erts/preloaded/src/erts.app.src4
-rw-r--r--erts/preloaded/src/erts_internal.erl98
-rw-r--r--erts/preloaded/src/init.erl37
-rw-r--r--erts/preloaded/src/prim_file.erl38
-rw-r--r--erts/preloaded/src/prim_net.erl17
-rw-r--r--erts/preloaded/src/prim_socket.erl1378
-rw-r--r--erts/preloaded/src/socket.erl4145
-rw-r--r--erts/preloaded/src/zlib.erl2
-rw-r--r--erts/test/erl_print_SUITE.erl41
-rw-r--r--erts/test/erlexec_SUITE.erl51
-rw-r--r--erts/test/erlexec_SUITE_data/erlexec_tests.c2
-rw-r--r--erts/test/ethread_SUITE_data/ethread_tests.c36
-rw-r--r--erts/test/nt_SUITE_data/nt_info.c7
-rw-r--r--erts/test/otp_SUITE.erl27
-rw-r--r--erts/test/upgrade_SUITE.erl11
-rw-r--r--erts/test/z_SUITE.erl99
-rw-r--r--erts/vsn.mk2
-rw-r--r--lib/asn1/Makefile9
-rw-r--r--lib/asn1/c_src/Makefile2
-rw-r--r--lib/asn1/doc/src/Makefile79
-rw-r--r--lib/asn1/doc/src/asn1_getting_started.xml56
-rw-r--r--lib/asn1/doc/src/asn1_spec.xmlsrc12
-rw-r--r--lib/asn1/doc/src/asn1ct.xml34
-rw-r--r--lib/asn1/doc/src/notes.xml52
-rw-r--r--lib/asn1/doc/users_guide/Makefile3
-rw-r--r--lib/asn1/src/asn1ct.erl8
-rw-r--r--lib/asn1/src/asn1ct_tok.erl55
-rw-r--r--lib/asn1/src/asn1rt.erl34
-rw-r--r--lib/asn1/test/asn1_SUITE.erl395
-rw-r--r--lib/asn1/test/asn1_SUITE_data/PartialDecChoExtension.asn12
-rw-r--r--lib/asn1/test/asn1_SUITE_data/PartialDecChoExtension.asn1config2
-rw-r--r--lib/asn1/test/asn1_SUITE_data/ValueTest.asn2
-rw-r--r--lib/asn1/test/asn1_test_lib.erl7
-rw-r--r--lib/asn1/test/testMegaco.erl4
-rw-r--r--lib/asn1/test/testValueTest.erl2
-rw-r--r--lib/asn1/test/test_selective_decode.erl8
-rw-r--r--lib/asn1/vsn.mk2
-rw-r--r--lib/common_test/Makefile4
-rw-r--r--lib/common_test/doc/src/Makefile92
-rw-r--r--lib/common_test/doc/src/basics_chapter.xml4
-rw-r--r--lib/common_test/doc/src/common_test_app.xml556
-rw-r--r--lib/common_test/doc/src/config_file_chapter.xml32
-rw-r--r--lib/common_test/doc/src/cover_chapter.xml14
-rw-r--r--lib/common_test/doc/src/ct.xml303
-rw-r--r--lib/common_test/doc/src/ct_cover.xml8
-rw-r--r--lib/common_test/doc/src/ct_ftp.xml42
-rw-r--r--lib/common_test/doc/src/ct_hooks.xml157
-rw-r--r--lib/common_test/doc/src/ct_hooks_chapter.xml122
-rw-r--r--lib/common_test/doc/src/ct_master.xml14
-rw-r--r--lib/common_test/doc/src/ct_master_chapter.xml22
-rw-r--r--lib/common_test/doc/src/ct_netconfc.xml100
-rw-r--r--lib/common_test/doc/src/ct_property_test.xml14
-rw-r--r--lib/common_test/doc/src/ct_property_test_chapter.xml6
-rw-r--r--lib/common_test/doc/src/ct_rpc.xml4
-rw-r--r--lib/common_test/doc/src/ct_run_cmd.xml (renamed from lib/common_test/doc/src/ct_run.xml)13
-rw-r--r--lib/common_test/doc/src/ct_slave.xml6
-rw-r--r--lib/common_test/doc/src/ct_snmp.xml109
-rw-r--r--lib/common_test/doc/src/ct_ssh.xml201
-rw-r--r--lib/common_test/doc/src/ct_suite.xml619
-rw-r--r--lib/common_test/doc/src/ct_telnet.xml98
-rw-r--r--lib/common_test/doc/src/dependencies_chapter.xml14
-rw-r--r--lib/common_test/doc/src/event_handler_chapter.xml24
-rw-r--r--lib/common_test/doc/src/getting_started_chapter.xml28
-rw-r--r--lib/common_test/doc/src/install_chapter.xml12
-rw-r--r--lib/common_test/doc/src/introduction.xml2
-rw-r--r--lib/common_test/doc/src/notes.xml68
-rw-r--r--lib/common_test/doc/src/ref_man.xml3
-rw-r--r--lib/common_test/doc/src/run_test_chapter.xml144
-rw-r--r--lib/common_test/doc/src/specs.xml1
-rw-r--r--lib/common_test/doc/src/test_structure_chapter.xml8
-rw-r--r--lib/common_test/doc/src/unix_telnet.xml16
-rw-r--r--lib/common_test/doc/src/write_test_chapter.xml124
-rw-r--r--lib/common_test/src/Makefile11
-rw-r--r--lib/common_test/src/common_test.app.src3
-rw-r--r--lib/common_test/src/ct_config.erl9
-rw-r--r--lib/common_test/src/ct_framework.erl2
-rw-r--r--lib/common_test/src/ct_property_test.erl45
-rw-r--r--lib/common_test/src/ct_suite.erl130
-rw-r--r--lib/common_test/src/cth_log_redirect.erl12
-rw-r--r--lib/common_test/src/test_server.erl115
-rw-r--r--lib/common_test/src/test_server_ctrl.erl2
-rw-r--r--lib/common_test/src/test_server_node.erl33
-rw-r--r--lib/common_test/test/ct_hooks_SUITE.erl138
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl33
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_formatter_SUITE.erl124
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_unexpect_SUITE.erl129
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ecdsa_key5
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ecdsa_key.pub1
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ed25519_key7
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ed25519_key.pub1
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_rsa_key27
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_rsa_key.pub1
-rw-r--r--lib/common_test/test/ct_repeat_testrun_SUITE.erl6
-rw-r--r--lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl2
-rw-r--r--lib/common_test/test_server/conf_vars.in1
-rw-r--r--lib/common_test/test_server/configure.in8
-rw-r--r--lib/common_test/test_server/ts_autoconf_win32.erl14
-rw-r--r--lib/common_test/test_server/ts_run.erl19
-rw-r--r--lib/common_test/vsn.mk2
-rw-r--r--lib/compiler/Makefile3
-rw-r--r--lib/compiler/doc/src/Makefile83
-rw-r--r--lib/compiler/doc/src/compile.xml91
-rw-r--r--lib/compiler/doc/src/notes.xml334
-rw-r--r--lib/compiler/scripts/.gitignore6
-rw-r--r--lib/compiler/scripts/smoke-build/mix.lock9
-rw-r--r--lib/compiler/scripts/smoke-mix.exs8
-rw-r--r--lib/compiler/src/Makefile12
-rw-r--r--lib/compiler/src/beam_a.erl10
-rw-r--r--lib/compiler/src/beam_asm.erl23
-rw-r--r--lib/compiler/src/beam_block.erl39
-rw-r--r--lib/compiler/src/beam_call_types.erl992
-rw-r--r--lib/compiler/src/beam_clean.erl52
-rw-r--r--lib/compiler/src/beam_dict.erl49
-rw-r--r--lib/compiler/src/beam_digraph.erl308
-rw-r--r--lib/compiler/src/beam_disasm.erl9
-rw-r--r--lib/compiler/src/beam_except.erl256
-rw-r--r--lib/compiler/src/beam_jump.erl277
-rw-r--r--lib/compiler/src/beam_kernel_to_ssa.erl801
-rw-r--r--lib/compiler/src/beam_peep.erl6
-rw-r--r--lib/compiler/src/beam_ssa.erl365
-rw-r--r--lib/compiler/src/beam_ssa.hrl12
-rw-r--r--lib/compiler/src/beam_ssa_bool.erl1703
-rw-r--r--lib/compiler/src/beam_ssa_bsm.erl83
-rw-r--r--lib/compiler/src/beam_ssa_codegen.erl443
-rw-r--r--lib/compiler/src/beam_ssa_dead.erl105
-rw-r--r--lib/compiler/src/beam_ssa_funs.erl18
-rw-r--r--lib/compiler/src/beam_ssa_lint.erl295
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl1692
-rw-r--r--lib/compiler/src/beam_ssa_opt.hrl24
-rw-r--r--lib/compiler/src/beam_ssa_pp.erl86
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl947
-rw-r--r--lib/compiler/src/beam_ssa_recv.erl18
-rw-r--r--lib/compiler/src/beam_ssa_share.erl52
-rw-r--r--lib/compiler/src/beam_ssa_type.erl3377
-rw-r--r--lib/compiler/src/beam_trim.erl130
-rw-r--r--lib/compiler/src/beam_types.erl1127
-rw-r--r--lib/compiler/src/beam_types.hrl154
-rw-r--r--lib/compiler/src/beam_utils.erl10
-rw-r--r--lib/compiler/src/beam_validator.erl3308
-rw-r--r--lib/compiler/src/beam_z.erl29
-rw-r--r--lib/compiler/src/cerl_inline.erl72
-rw-r--r--lib/compiler/src/cerl_sets.erl69
-rw-r--r--lib/compiler/src/cerl_trees.erl11
-rw-r--r--lib/compiler/src/compile.erl214
-rw-r--r--lib/compiler/src/compiler.app.src8
-rw-r--r--lib/compiler/src/core_lib.erl109
-rw-r--r--lib/compiler/src/core_lint.erl64
-rw-r--r--lib/compiler/src/core_pp.erl12
-rw-r--r--lib/compiler/src/erl_bifs.erl15
-rwxr-xr-xlib/compiler/src/genop.tab12
-rw-r--r--lib/compiler/src/sys_core_alias.erl33
-rw-r--r--lib/compiler/src/sys_core_bsm.erl7
-rw-r--r--lib/compiler/src/sys_core_fold.erl796
-rw-r--r--lib/compiler/src/sys_core_fold_lists.erl36
-rw-r--r--lib/compiler/src/sys_core_inline.erl20
-rw-r--r--lib/compiler/src/sys_core_prepare.erl130
-rw-r--r--lib/compiler/src/v3_core.erl1610
-rw-r--r--lib/compiler/src/v3_kernel.erl1730
-rw-r--r--lib/compiler/src/v3_kernel.hrl24
-rw-r--r--lib/compiler/src/v3_kernel_pp.erl89
-rw-r--r--lib/compiler/test/Makefile57
-rw-r--r--lib/compiler/test/andor_SUITE.erl36
-rw-r--r--lib/compiler/test/apply_SUITE.erl53
-rw-r--r--lib/compiler/test/beam_block_SUITE.erl15
-rw-r--r--lib/compiler/test/beam_except_SUITE.erl15
-rw-r--r--lib/compiler/test/beam_jump_SUITE.erl117
-rw-r--r--lib/compiler/test/beam_ssa_SUITE.erl523
-rw-r--r--lib/compiler/test/beam_type_SUITE.erl98
-rw-r--r--lib/compiler/test/beam_types_SUITE.erl166
-rw-r--r--lib/compiler/test/beam_validator_SUITE.erl279
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/branch_to_try_handler.S48
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/call_last.S21
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/call_without_stack.S21
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/freg_state.S59
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/freg_uninit.S2
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S77
-rw-r--r--lib/compiler/test/bif_SUITE.erl1
-rw-r--r--lib/compiler/test/bs_bincomp_SUITE.erl51
-rw-r--r--lib/compiler/test/bs_construct_SUITE.erl107
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl606
-rw-r--r--lib/compiler/test/bs_size_expr_SUITE.erl296
-rw-r--r--lib/compiler/test/bs_utf_SUITE.erl17
-rw-r--r--lib/compiler/test/compilation_SUITE.erl9
-rw-r--r--lib/compiler/test/compilation_SUITE_data/infinite_loop.erl22
-rw-r--r--lib/compiler/test/compile_SUITE.erl98
-rw-r--r--lib/compiler/test/compile_SUITE_data/debug_info.erl2
-rw-r--r--lib/compiler/test/core_SUITE.erl62
-rw-r--r--lib/compiler/test/core_SUITE_data/bs_shadowed_size_var.core10
-rw-r--r--lib/compiler/test/core_SUITE_data/receive_tests.core1761
-rw-r--r--lib/compiler/test/core_alias_SUITE.erl60
-rw-r--r--lib/compiler/test/core_fold_SUITE.erl22
-rw-r--r--lib/compiler/test/error_SUITE.erl3
-rw-r--r--lib/compiler/test/float_SUITE.erl38
-rw-r--r--lib/compiler/test/fun_SUITE.erl7
-rw-r--r--lib/compiler/test/guard_SUITE.erl550
-rw-r--r--lib/compiler/test/lfe_andor_SUITE.core2
-rw-r--r--lib/compiler/test/lfe_guard_SUITE.core2
-rw-r--r--lib/compiler/test/map_SUITE.erl127
-rw-r--r--lib/compiler/test/match_SUITE.erl83
-rw-r--r--lib/compiler/test/misc_SUITE.erl44
-rw-r--r--lib/compiler/test/property_test/beam_types_prop.erl315
-rw-r--r--lib/compiler/test/property_test/compile_prop.erl59
-rw-r--r--lib/compiler/test/random_code_SUITE.erl57
-rw-r--r--lib/compiler/test/receive_SUITE.erl174
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_19.erl15
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_20.erl16
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_21.erl16
-rw-r--r--lib/compiler/test/record_SUITE.erl36
-rw-r--r--lib/compiler/test/test_lib.erl43
-rw-r--r--lib/compiler/test/trycatch_SUITE.erl112
-rw-r--r--lib/compiler/test/warnings_SUITE.erl9
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/Makefile1
-rw-r--r--lib/crypto/c_src/Makefile.in27
-rw-r--r--lib/crypto/c_src/algorithms.c704
-rw-r--r--lib/crypto/c_src/api_ng.c417
-rw-r--r--lib/crypto/c_src/api_ng.h2
-rw-r--r--lib/crypto/c_src/atoms.c20
-rw-r--r--lib/crypto/c_src/atoms.h10
-rw-r--r--lib/crypto/c_src/cipher.c82
-rw-r--r--lib/crypto/c_src/cipher.h6
-rw-r--r--lib/crypto/c_src/crypto.c23
-rw-r--r--lib/crypto/c_src/crypto_callback.c47
-rw-r--r--lib/crypto/c_src/crypto_callback.h4
-rw-r--r--lib/crypto/c_src/dh.c6
-rw-r--r--lib/crypto/c_src/engine.c49
-rw-r--r--lib/crypto/c_src/evp.c31
-rw-r--r--lib/crypto/c_src/fips.c2
-rw-r--r--lib/crypto/c_src/fips.h2
-rw-r--r--lib/crypto/c_src/hmac.c19
-rw-r--r--lib/crypto/c_src/hmac.h2
-rw-r--r--lib/crypto/c_src/info.c2
-rw-r--r--lib/crypto/c_src/mac.c4
-rw-r--r--lib/crypto/c_src/openssl_config.h18
-rw-r--r--lib/crypto/c_src/otp_test_engine.c8
-rw-r--r--lib/crypto/c_src/pkey.c29
-rw-r--r--lib/crypto/c_src/srp.c5
-rw-r--r--lib/crypto/configure.in127
-rw-r--r--lib/crypto/doc/src/Makefile79
-rw-r--r--lib/crypto/doc/src/algorithm_details.xml62
-rw-r--r--lib/crypto/doc/src/crypto.xml546
-rw-r--r--lib/crypto/doc/src/crypto_app.xml12
-rw-r--r--lib/crypto/doc/src/engine_keys.xml10
-rw-r--r--lib/crypto/doc/src/insidecover.xml26
-rw-r--r--lib/crypto/doc/src/new_api.xml107
-rw-r--r--lib/crypto/doc/src/notes.xml305
-rw-r--r--lib/crypto/doc/src/ref_man.xml2
-rw-r--r--lib/crypto/doc/src/release_notes.xml2
-rw-r--r--lib/crypto/doc/src/usersguide.xml2
-rw-r--r--lib/crypto/src/Makefile2
-rw-r--r--lib/crypto/src/crypto.erl464
-rw-r--r--lib/crypto/test/Makefile50
-rw-r--r--lib/crypto/test/crypto_SUITE.erl839
-rw-r--r--lib/crypto/test/crypto_bench_SUITE.erl28
-rw-r--r--lib/crypto/test/crypto_property_test_SUITE.erl12
-rw-r--r--lib/crypto/test/engine_SUITE.erl62
-rw-r--r--lib/crypto/test/property_test/crypto_ng_api.erl126
-rw-r--r--lib/crypto/test/property_test/crypto_prop_generators.erl4
-rw-r--r--lib/crypto/test/property_test/crypto_prop_generators.hrl1
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/debugger/Makefile4
-rw-r--r--lib/debugger/doc/src/Makefile84
-rw-r--r--lib/debugger/doc/src/attach.jpg (renamed from lib/debugger/doc/src/images/attach.jpg)bin56341 -> 56341 bytes
-rw-r--r--lib/debugger/doc/src/cond_break_dialog.jpg (renamed from lib/debugger/doc/src/images/cond_break_dialog.jpg)bin21770 -> 21770 bytes
-rw-r--r--lib/debugger/doc/src/debugger_chapter.xml68
-rw-r--r--lib/debugger/doc/src/function_break_dialog.jpg (renamed from lib/debugger/doc/src/images/function_break_dialog.jpg)bin13532 -> 13532 bytes
-rw-r--r--lib/debugger/doc/src/i.xml16
-rw-r--r--lib/debugger/doc/src/int.xml6
-rw-r--r--lib/debugger/doc/src/interpret.jpg (renamed from lib/debugger/doc/src/images/interpret.jpg)bin28924 -> 28924 bytes
-rw-r--r--lib/debugger/doc/src/introduction.xml2
-rw-r--r--lib/debugger/doc/src/line_break_dialog.jpg (renamed from lib/debugger/doc/src/images/line_break_dialog.jpg)bin14414 -> 14414 bytes
-rw-r--r--lib/debugger/doc/src/monitor.jpg (renamed from lib/debugger/doc/src/images/monitor.jpg)bin40742 -> 40742 bytes
-rw-r--r--lib/debugger/doc/src/notes.xml37
-rw-r--r--lib/debugger/doc/src/view.jpg (renamed from lib/debugger/doc/src/images/view.jpg)bin34504 -> 34504 bytes
-rw-r--r--lib/debugger/src/dbg_ieval.erl32
-rw-r--r--lib/debugger/src/dbg_iload.erl6
-rw-r--r--lib/debugger/src/dbg_wx_win.erl5
-rw-r--r--lib/debugger/test/Makefile1
-rw-r--r--lib/debugger/test/bs_size_expr_SUITE.erl273
-rw-r--r--lib/debugger/test/exception_SUITE.erl81
-rw-r--r--lib/debugger/test/int_eval_SUITE_data/stacktrace.erl13
-rw-r--r--lib/debugger/test/line_number_SUITE.erl4
-rw-r--r--lib/debugger/test/map_SUITE.erl52
-rw-r--r--lib/debugger/vsn.mk2
-rw-r--r--lib/dialyzer/Makefile3
-rw-r--r--lib/dialyzer/doc/src/Makefile76
-rw-r--r--lib/dialyzer/doc/src/dialyzer.xml26
-rw-r--r--lib/dialyzer/doc/src/dialyzer_chapter.xml10
-rw-r--r--lib/dialyzer/doc/src/notes.xml64
-rw-r--r--lib/dialyzer/doc/src/ref_man.xml3
-rw-r--r--lib/dialyzer/doc/src/typer_cmd.xml (renamed from lib/dialyzer/doc/src/typer.xml)14
-rw-r--r--lib/dialyzer/src/Makefile1
-rw-r--r--lib/dialyzer/src/dialyzer.app.src1
-rw-r--r--lib/dialyzer/src/dialyzer_cl.erl107
-rw-r--r--lib/dialyzer/src/dialyzer_cl_parse.erl16
-rw-r--r--lib/dialyzer/src/dialyzer_clean_core.erl225
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl76
-rw-r--r--lib/dialyzer/src/dialyzer_dep.erl90
-rw-r--r--lib/dialyzer/src/dialyzer_gui_wx.erl4
-rw-r--r--lib/dialyzer/src/dialyzer_plt.erl2
-rw-r--r--lib/dialyzer/src/dialyzer_typesig.erl43
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl4
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/maps_remove4
-rw-r--r--lib/dialyzer/test/map_SUITE_data/src/maps_remove.erl23
-rw-r--r--lib/dialyzer/test/plt_SUITE.erl28
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/no_match4
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/stacktrace4
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/stacktrace.erl12
-rw-r--r--lib/dialyzer/vsn.mk2
-rw-r--r--lib/diameter/Makefile5
-rw-r--r--lib/diameter/doc/src/Makefile116
-rw-r--r--lib/diameter/doc/src/diameter.xml12
-rw-r--r--lib/diameter/doc/src/diameter_app.xml4
-rw-r--r--lib/diameter/doc/src/diameter_codec.xml6
-rw-r--r--lib/diameter/doc/src/diameter_dict.xml6
-rw-r--r--lib/diameter/doc/src/diameter_intro.xml26
-rw-r--r--lib/diameter/doc/src/diameter_make.xml6
-rw-r--r--lib/diameter/doc/src/diameter_sctp.xml10
-rw-r--r--lib/diameter/doc/src/diameter_soc.xml12
-rw-r--r--lib/diameter/doc/src/diameter_tcp.xml22
-rw-r--r--lib/diameter/doc/src/diameter_transport.xml8
-rw-r--r--lib/diameter/doc/src/diameterc_cmd.xml (renamed from lib/diameter/doc/src/diameterc.xml)2
-rw-r--r--lib/diameter/doc/src/files.mk4
-rw-r--r--lib/diameter/doc/src/notes.xml2
-rw-r--r--lib/diameter/doc/src/ref_man.xml2
-rw-r--r--lib/diameter/doc/src/seealso.ent166
-rw-r--r--lib/diameter/src/Makefile8
-rw-r--r--lib/edoc/Makefile9
-rw-r--r--lib/edoc/doc/overview.edoc12
-rw-r--r--lib/edoc/doc/src/Makefile97
-rw-r--r--lib/edoc/doc/src/notes.xml26
-rw-r--r--lib/edoc/src/edoc.app.src2
-rw-r--r--lib/edoc/src/edoc.erl7
-rw-r--r--lib/edoc/src/edoc_data.erl53
-rw-r--r--lib/edoc/src/edoc_extract.erl2
-rw-r--r--lib/edoc/src/edoc_layout.erl49
-rw-r--r--lib/edoc/src/edoc_lib.erl140
-rw-r--r--lib/edoc/src/edoc_specs.erl30
-rw-r--r--lib/edoc/vsn.mk2
-rw-r--r--lib/eldap/Makefile3
-rw-r--r--lib/eldap/doc/src/Makefile76
-rw-r--r--lib/eldap/doc/src/eldap.xml50
-rw-r--r--lib/eldap/doc/src/notes.xml15
-rw-r--r--lib/eldap/include/eldap.hrl1
-rw-r--r--lib/eldap/src/eldap.erl10
-rw-r--r--lib/eldap/test/eldap_basic_SUITE.erl113
-rw-r--r--lib/eldap/test/make_certs.erl3
-rw-r--r--lib/eldap/vsn.mk2
-rw-r--r--lib/erl_docgen/Makefile2
-rw-r--r--lib/erl_docgen/doc/src/Makefile78
-rw-r--r--lib/erl_docgen/doc/src/block_tags.xml68
-rw-r--r--lib/erl_docgen/doc/src/doc-build.xml8
-rw-r--r--lib/erl_docgen/doc/src/doc_storage.xml136
-rw-r--r--lib/erl_docgen/doc/src/inline_tags.xml201
-rw-r--r--lib/erl_docgen/doc/src/notes.xml92
-rw-r--r--lib/erl_docgen/doc/src/overview.xml20
-rw-r--r--lib/erl_docgen/doc/src/part.xml1
-rw-r--r--lib/erl_docgen/doc/src/refman_dtds.xml126
-rw-r--r--lib/erl_docgen/doc/src/user_guide_dtds.xml22
-rw-r--r--lib/erl_docgen/priv/bin/chunk.escript30
-rw-r--r--lib/erl_docgen/priv/bin/specs_gen.escript8
-rwxr-xr-xlib/erl_docgen/priv/bin/validate_links.escript360
-rw-r--r--lib/erl_docgen/priv/css/Makefile9
-rw-r--r--lib/erl_docgen/priv/css/otp_doc.css73
-rw-r--r--lib/erl_docgen/priv/dtd/Makefile7
-rw-r--r--lib/erl_docgen/priv/dtd/bookinsidecover.dtd37
-rw-r--r--lib/erl_docgen/priv/dtd/cites.dtd36
-rw-r--r--lib/erl_docgen/priv/dtd/common.dtd59
-rw-r--r--lib/erl_docgen/priv/dtd/common.refs.dtd11
-rw-r--r--lib/erl_docgen/priv/dtd/erlref.dtd2
-rw-r--r--lib/erl_docgen/priv/dtd/fascicules.dtd36
-rw-r--r--lib/erl_docgen/priv/dtd/report.dtd141
-rw-r--r--lib/erl_docgen/priv/dtd/terms.dtd37
-rw-r--r--lib/erl_docgen/priv/images/Makefile9
-rw-r--r--lib/erl_docgen/priv/js/flipmenu/Makefile10
-rw-r--r--lib/erl_docgen/priv/xsl/db_html.xsl434
-rw-r--r--lib/erl_docgen/priv/xsl/db_man.xsl7
-rw-r--r--lib/erl_docgen/priv/xsl/db_pdf.xsl9
-rw-r--r--lib/erl_docgen/src/Makefile3
-rw-r--r--lib/erl_docgen/src/docgen_edoc_xml_cb.erl172
-rw-r--r--lib/erl_docgen/src/docgen_otp_specs.erl8
-rw-r--r--lib/erl_docgen/src/docgen_xmerl_xml_cb.erl6
-rw-r--r--lib/erl_docgen/src/docgen_xml_to_chunk.erl852
-rw-r--r--lib/erl_docgen/src/erl_docgen.app.src3
-rw-r--r--lib/erl_docgen/vsn.mk2
-rw-r--r--lib/erl_interface/Makefile2
-rw-r--r--lib/erl_interface/configure.in8
-rw-r--r--lib/erl_interface/doc/src/Makefile79
-rw-r--r--lib/erl_interface/doc/src/ei.xml502
-rw-r--r--lib/erl_interface/doc/src/ei_connect.xml362
-rw-r--r--lib/erl_interface/doc/src/ei_global.xml (renamed from lib/erl_interface/doc/src/erl_global.xml)46
-rw-r--r--lib/erl_interface/doc/src/ei_users_guide.xml466
-rw-r--r--lib/erl_interface/doc/src/erl_call_cmd.xml (renamed from lib/erl_interface/doc/src/erl_call.xml)47
-rw-r--r--lib/erl_interface/doc/src/erl_connect.xml662
-rw-r--r--lib/erl_interface/doc/src/erl_error.xml145
-rw-r--r--lib/erl_interface/doc/src/erl_eterm.xml776
-rw-r--r--lib/erl_interface/doc/src/erl_format.xml138
-rw-r--r--lib/erl_interface/doc/src/erl_interface.xml637
-rw-r--r--lib/erl_interface/doc/src/erl_malloc.xml212
-rw-r--r--lib/erl_interface/doc/src/erl_marshal.xml276
-rw-r--r--lib/erl_interface/doc/src/notes.xml318
-rw-r--r--lib/erl_interface/doc/src/ref_man.xml18
-rw-r--r--lib/erl_interface/doc/src/ref_man_ei.xml53
-rw-r--r--lib/erl_interface/doc/src/ref_man_erl_interface.xml60
-rw-r--r--lib/erl_interface/doc/src/registry.xml9
-rw-r--r--lib/erl_interface/include/ei.h122
-rw-r--r--lib/erl_interface/include/ei_global.h (renamed from lib/erl_interface/src/legacy/erl_global.h)15
-rw-r--r--lib/erl_interface/include/erl_interface.h473
-rw-r--r--lib/erl_interface/src/Makefile.in202
-rw-r--r--lib/erl_interface/src/README4
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c1212
-rw-r--r--lib/erl_interface/src/connect/ei_connect_int.h58
-rw-r--r--lib/erl_interface/src/connect/ei_resolve.c259
-rw-r--r--lib/erl_interface/src/connect/eirecv.c48
-rw-r--r--lib/erl_interface/src/connect/send.c44
-rw-r--r--lib/erl_interface/src/connect/send_exit.c50
-rw-r--r--lib/erl_interface/src/connect/send_reg.c47
-rw-r--r--lib/erl_interface/src/decode/decode_big.c7
-rw-r--r--lib/erl_interface/src/decode/decode_iodata.c166
-rw-r--r--lib/erl_interface/src/decode/decode_ref.c3
-rw-r--r--lib/erl_interface/src/encode/encode_pid.c11
-rw-r--r--lib/erl_interface/src/encode/encode_port.c11
-rw-r--r--lib/erl_interface/src/encode/encode_ref.c10
-rw-r--r--lib/erl_interface/src/encode/encode_trace.c18
-rw-r--r--lib/erl_interface/src/epmd/ei_epmd.h8
-rw-r--r--lib/erl_interface/src/epmd/epmd_port.c22
-rw-r--r--lib/erl_interface/src/epmd/epmd_publish.c38
-rw-r--r--lib/erl_interface/src/epmd/epmd_unpublish.c10
-rw-r--r--lib/erl_interface/src/global/global_names.c (renamed from lib/erl_interface/src/legacy/global_names.c)46
-rw-r--r--lib/erl_interface/src/global/global_register.c (renamed from lib/erl_interface/src/legacy/global_register.c)70
-rw-r--r--lib/erl_interface/src/global/global_unregister.c (renamed from lib/erl_interface/src/legacy/global_unregister.c)62
-rw-r--r--lib/erl_interface/src/global/global_whereis.c (renamed from lib/erl_interface/src/legacy/global_whereis.c)63
-rw-r--r--lib/erl_interface/src/legacy/decode_term.c144
-rw-r--r--lib/erl_interface/src/legacy/encode_term.c54
-rw-r--r--lib/erl_interface/src/legacy/erl_connect.c467
-rw-r--r--lib/erl_interface/src/legacy/erl_error.c181
-rw-r--r--lib/erl_interface/src/legacy/erl_eterm.c1413
-rw-r--r--lib/erl_interface/src/legacy/erl_eterm.h64
-rw-r--r--lib/erl_interface/src/legacy/erl_fix_alloc.c198
-rw-r--r--lib/erl_interface/src/legacy/erl_format.c742
-rw-r--r--lib/erl_interface/src/legacy/erl_format.h23
-rw-r--r--lib/erl_interface/src/legacy/erl_malloc.c252
-rw-r--r--lib/erl_interface/src/legacy/erl_malloc.h27
-rw-r--r--lib/erl_interface/src/legacy/erl_marshal.c2267
-rw-r--r--lib/erl_interface/src/legacy/erl_resolve.c107
-rw-r--r--lib/erl_interface/src/legacy/erl_timeout.c163
-rw-r--r--lib/erl_interface/src/legacy/erl_timeout.h75
-rw-r--r--lib/erl_interface/src/misc/ei_cmp_nc.c117
-rw-r--r--lib/erl_interface/src/misc/ei_decode_term.c5
-rw-r--r--lib/erl_interface/src/misc/ei_format.c4
-rw-r--r--lib/erl_interface/src/misc/ei_locking.c25
-rw-r--r--lib/erl_interface/src/misc/ei_locking.h11
-rw-r--r--lib/erl_interface/src/misc/ei_portio.c26
-rw-r--r--lib/erl_interface/src/misc/ei_portio.h10
-rw-r--r--lib/erl_interface/src/misc/ei_printterm.c73
-rw-r--r--lib/erl_interface/src/misc/ei_pthreads.c25
-rw-r--r--lib/erl_interface/src/misc/ei_x_encode.c4
-rw-r--r--lib/erl_interface/src/misc/eidef.h5
-rw-r--r--lib/erl_interface/src/misc/get_type.c6
-rw-r--r--lib/erl_interface/src/misc/show_msg.c6
-rw-r--r--lib/erl_interface/src/not_used/ei_send.c105
-rw-r--r--lib/erl_interface/src/not_used/ei_send_reg.c108
-rw-r--r--lib/erl_interface/src/not_used/send_link.c104
-rw-r--r--lib/erl_interface/src/not_used/whereis.c71
-rw-r--r--lib/erl_interface/src/prog/ei_fake_prog.c10
-rw-r--r--lib/erl_interface/src/prog/erl_call.c280
-rw-r--r--lib/erl_interface/src/prog/erl_fake_prog.c251
-rw-r--r--lib/erl_interface/src/prog/erl_start.c143
-rw-r--r--lib/erl_interface/src/registry/reg_dump.c22
-rw-r--r--lib/erl_interface/src/registry/reg_get.c4
-rw-r--r--lib/erl_interface/src/registry/reg_restore.c20
-rw-r--r--lib/erl_interface/src/registry/reg_set.c4
-rw-r--r--lib/erl_interface/test/Makefile15
-rw-r--r--lib/erl_interface/test/all_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/all_SUITE_data/ei_runner.c2
-rw-r--r--lib/erl_interface/test/all_SUITE_data/init_tc.erl4
-rw-r--r--lib/erl_interface/test/all_SUITE_data/my_ussi.c198
-rw-r--r--lib/erl_interface/test/all_SUITE_data/my_ussi.h (renamed from lib/erl_interface/src/legacy/erl_config.h)19
-rw-r--r--lib/erl_interface/test/all_SUITE_data/runner.c459
-rw-r--r--lib/erl_interface/test/all_SUITE_data/runner.h51
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE.erl72
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src1
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c28
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c67
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE.erl174
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src6
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c129
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE_data/einode.c29
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE.erl201
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c273
-rw-r--r--lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c44
-rw-r--r--lib/erl_interface/test/ei_encode_SUITE.erl84
-rw-r--r--lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c12
-rw-r--r--lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c5
-rw-r--r--lib/erl_interface/test/ei_global_SUITE.erl (renamed from lib/erl_interface/test/erl_global_SUITE.erl)93
-rw-r--r--lib/erl_interface/test/ei_global_SUITE_data/Makefile.first (renamed from lib/erl_interface/test/erl_global_SUITE_data/Makefile.first)4
-rw-r--r--lib/erl_interface/test/ei_global_SUITE_data/Makefile.src (renamed from lib/erl_interface/test/erl_global_SUITE_data/Makefile.src)19
-rw-r--r--lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c240
-rw-r--r--lib/erl_interface/test/ei_print_SUITE.erl68
-rw-r--r--lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c71
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE.erl311
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src3
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c173
-rw-r--r--lib/erl_interface/test/erl_call_SUITE.erl210
-rw-r--r--lib/erl_interface/test/erl_connect_SUITE.erl131
-rw-r--r--lib/erl_interface/test/erl_connect_SUITE_data/Makefile.first22
-rw-r--r--lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src41
-rw-r--r--lib/erl_interface/test/erl_connect_SUITE_data/erl_connect_test.c203
-rw-r--r--lib/erl_interface/test/erl_eterm_SUITE.erl1084
-rw-r--r--lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src50
-rw-r--r--lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c173
-rw-r--r--lib/erl_interface/test/erl_eterm_SUITE_data/eterm_test.c1604
-rw-r--r--lib/erl_interface/test/erl_eterm_SUITE_data/print_term.c131
-rw-r--r--lib/erl_interface/test/erl_ext_SUITE.erl70
-rw-r--r--lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src41
-rw-r--r--lib/erl_interface/test/erl_ext_SUITE_data/ext_test.c510
-rw-r--r--lib/erl_interface/test/erl_format_SUITE.erl135
-rw-r--r--lib/erl_interface/test/erl_format_SUITE_data/Makefile.first22
-rw-r--r--lib/erl_interface/test/erl_format_SUITE_data/Makefile.src43
-rw-r--r--lib/erl_interface/test/erl_format_SUITE_data/format_test.c133
-rw-r--r--lib/erl_interface/test/erl_global_SUITE_data/erl_global_test.c264
-rw-r--r--lib/erl_interface/test/erl_match_SUITE.erl280
-rw-r--r--lib/erl_interface/test/erl_match_SUITE_data/Makefile.first22
-rw-r--r--lib/erl_interface/test/erl_match_SUITE_data/Makefile.src42
-rw-r--r--lib/erl_interface/test/erl_match_SUITE_data/match_test.c114
-rw-r--r--lib/erl_interface/test/port_call_SUITE_data/Makefile.src3
-rw-r--r--lib/erl_interface/test/port_call_SUITE_data/port_call_drv.c2
-rw-r--r--lib/erl_interface/vsn.mk2
-rw-r--r--lib/et/Makefile3
-rw-r--r--lib/et/doc/src/Makefile80
-rw-r--r--lib/et/doc/src/et_collector.xml24
-rw-r--r--lib/et/doc/src/et_tutorial.xmlsrc4
-rw-r--r--lib/et/doc/src/files.mk2
-rw-r--r--lib/et/src/et_collector.erl2
-rw-r--r--lib/eunit/Makefile10
-rw-r--r--lib/eunit/doc/overview.edoc20
-rw-r--r--lib/eunit/doc/src/Makefile127
-rw-r--r--lib/eunit/doc/src/notes.xml44
-rw-r--r--lib/eunit/include/eunit.hrl10
-rw-r--r--lib/eunit/src/Makefile3
-rw-r--r--lib/eunit/src/eunit.erl2
-rw-r--r--lib/eunit/src/eunit_lib.erl44
-rw-r--r--lib/eunit/src/eunit_proc.erl22
-rw-r--r--lib/eunit/src/eunit_tests.erl7
-rw-r--r--lib/eunit/src/eunit_tty.erl53
-rw-r--r--lib/eunit/vsn.mk2
-rw-r--r--lib/ftp/Makefile40
-rw-r--r--lib/ftp/doc/src/Makefile102
-rw-r--r--lib/ftp/doc/src/ftp.xml24
-rw-r--r--lib/ftp/doc/src/notes.xml34
-rw-r--r--lib/ftp/src/ftp.erl2329
-rw-r--r--lib/ftp/test/Makefile2
-rw-r--r--lib/ftp/test/erl_make_certs.erl9
-rw-r--r--lib/ftp/test/ftp_SUITE.erl696
-rw-r--r--lib/ftp/test/ftp_bench.spec1
-rw-r--r--lib/ftp/vsn.mk2
-rw-r--r--lib/hipe/Makefile3
-rw-r--r--lib/hipe/cerl/cerl_closurean.erl4
-rw-r--r--lib/hipe/cerl/erl_bif_types.erl8
-rw-r--r--lib/hipe/cerl/erl_types.erl22
-rw-r--r--lib/hipe/doc/src/HiPE_app.xml (renamed from lib/hipe/doc/src/hipe_app.xml)16
-rw-r--r--lib/hipe/doc/src/Makefile78
-rw-r--r--lib/hipe/doc/src/notes.xml58
-rw-r--r--lib/hipe/doc/src/ref_man.xml2
-rw-r--r--lib/hipe/icode/hipe_beam_to_icode.erl59
-rw-r--r--lib/hipe/icode/hipe_icode_primops.erl10
-rw-r--r--lib/hipe/main/hipe.erl4
-rw-r--r--lib/hipe/test/basic_SUITE_data/basic_bugs_hipe.erl26
-rw-r--r--lib/hipe/vsn.mk2
-rw-r--r--lib/inets/Makefile40
-rw-r--r--lib/inets/doc/src/Makefile100
-rw-r--r--lib/inets/doc/src/http_client.xml2
-rw-r--r--lib/inets/doc/src/http_server.xml242
-rw-r--r--lib/inets/doc/src/http_uri.xml15
-rw-r--r--lib/inets/doc/src/httpc.xml44
-rw-r--r--lib/inets/doc/src/httpd.xml160
-rw-r--r--lib/inets/doc/src/httpd_custom_api.xml2
-rw-r--r--lib/inets/doc/src/httpd_socket.xml2
-rw-r--r--lib/inets/doc/src/httpd_util.xml10
-rw-r--r--lib/inets/doc/src/inets.xml8
-rw-r--r--lib/inets/doc/src/introduction.xml2
-rw-r--r--lib/inets/doc/src/mod_alias.xml14
-rw-r--r--lib/inets/doc/src/mod_auth.xml4
-rw-r--r--lib/inets/doc/src/mod_esi.xml15
-rw-r--r--lib/inets/doc/src/mod_security.xml19
-rw-r--r--lib/inets/doc/src/notes.xml265
-rw-r--r--lib/inets/doc/src/notes_history.xml2
-rw-r--r--lib/inets/doc/src/part.xml2
-rw-r--r--lib/inets/src/http_client/httpc.erl2
-rw-r--r--lib/inets/src/http_client/httpc_handler.erl44
-rw-r--r--lib/inets/src/http_client/httpc_response.erl10
-rw-r--r--lib/inets/src/http_lib/http_request.erl6
-rw-r--r--lib/inets/src/http_lib/http_transport.erl6
-rw-r--r--lib/inets/src/http_lib/http_uri.erl8
-rw-r--r--lib/inets/src/http_server/Makefile2
-rw-r--r--lib/inets/src/http_server/httpd.erl16
-rw-r--r--lib/inets/src/http_server/httpd_conf.erl496
-rw-r--r--lib/inets/src/http_server/httpd_example.erl13
-rw-r--r--lib/inets/src/http_server/httpd_instance_sup.erl45
-rw-r--r--lib/inets/src/http_server/httpd_manager.erl21
-rw-r--r--lib/inets/src/http_server/httpd_request_handler.erl45
-rw-r--r--lib/inets/src/http_server/httpd_sup.erl55
-rw-r--r--lib/inets/src/http_server/httpd_util.erl24
-rw-r--r--lib/inets/src/http_server/mod_actions.erl19
-rw-r--r--lib/inets/src/http_server/mod_alias.erl81
-rw-r--r--lib/inets/src/http_server/mod_auth.erl93
-rw-r--r--lib/inets/src/http_server/mod_browser.erl251
-rw-r--r--lib/inets/src/http_server/mod_dir.erl69
-rw-r--r--lib/inets/src/http_server/mod_esi.erl72
-rw-r--r--lib/inets/src/http_server/mod_head.erl4
-rw-r--r--lib/inets/src/http_server/mod_htaccess.erl1071
-rw-r--r--lib/inets/src/inets_app/inets.app.src2
-rw-r--r--lib/inets/test/Makefile4
-rw-r--r--lib/inets/test/httpc_SUITE.erl16
-rw-r--r--lib/inets/test/httpd_SUITE.erl411
-rw-r--r--lib/inets/test/httpd_SUITE_data/cgi_echo.c2
-rw-r--r--lib/inets/test/httpd_bench_SUITE.erl3
-rw-r--r--lib/inets/test/httpd_mod_SUITE.erl2
-rw-r--r--lib/inets/test/httpd_test_lib.erl8
-rw-r--r--lib/inets/test/inets_SUITE.erl90
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/jinterface/Makefile1
-rw-r--r--lib/jinterface/doc/src/Makefile88
-rw-r--r--lib/jinterface/doc/src/jinterface_users_guide.xml62
-rw-r--r--lib/jinterface/doc/src/notes.xml58
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java302
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java13
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java11
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/Links.java132
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile7
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java76
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java45
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java12
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java6
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java6
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java7
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java14
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java128
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java52
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java50
-rw-r--r--lib/jinterface/test/jinterface_SUITE.erl94
-rw-r--r--lib/jinterface/test/jinterface_SUITE_data/MboxLinkUnlink.java86
-rw-r--r--lib/jinterface/vsn.mk2
-rw-r--r--lib/kernel/Makefile4
-rw-r--r--lib/kernel/doc/src/Makefile118
-rw-r--r--lib/kernel/doc/src/app.xml14
-rw-r--r--lib/kernel/doc/src/application.xml80
-rw-r--r--lib/kernel/doc/src/auth.xml18
-rw-r--r--lib/kernel/doc/src/code.xml112
-rw-r--r--lib/kernel/doc/src/config.xml10
-rw-r--r--lib/kernel/doc/src/disk_log.xml95
-rw-r--r--lib/kernel/doc/src/eep48_chapter.xml191
-rw-r--r--lib/kernel/doc/src/erl_boot_server.xml8
-rw-r--r--lib/kernel/doc/src/erl_ddll.xml216
-rw-r--r--lib/kernel/doc/src/erl_epmd.xml35
-rw-r--r--lib/kernel/doc/src/erl_prim_loader_stub.xml4
-rw-r--r--lib/kernel/doc/src/erlang_stub.xml4
-rw-r--r--lib/kernel/doc/src/erpc.xml695
-rw-r--r--lib/kernel/doc/src/error_logger.xml114
-rw-r--r--lib/kernel/doc/src/file.xml263
-rw-r--r--lib/kernel/doc/src/gen_sctp.xml140
-rw-r--r--lib/kernel/doc/src/gen_tcp.xml60
-rw-r--r--lib/kernel/doc/src/gen_udp.xml41
-rw-r--r--lib/kernel/doc/src/global.xml24
-rw-r--r--lib/kernel/doc/src/global_group.xml18
-rw-r--r--lib/kernel/doc/src/heart.xml4
-rw-r--r--lib/kernel/doc/src/inet.xml194
-rw-r--r--lib/kernel/doc/src/inet_res.xml93
-rw-r--r--lib/kernel/doc/src/init_stub.xml2
-rw-r--r--lib/kernel/doc/src/kernel_app.xml164
-rw-r--r--lib/kernel/doc/src/logger.xml367
-rw-r--r--lib/kernel/doc/src/logger_chapter.xml462
-rw-r--r--lib/kernel/doc/src/logger_cookbook.xml58
-rw-r--r--lib/kernel/doc/src/logger_disk_log_h.xml38
-rw-r--r--lib/kernel/doc/src/logger_filters.xml42
-rw-r--r--lib/kernel/doc/src/logger_formatter.xml98
-rw-r--r--lib/kernel/doc/src/logger_std_h.xml42
-rw-r--r--lib/kernel/doc/src/net.xml7
-rw-r--r--lib/kernel/doc/src/net_adm.xml8
-rw-r--r--lib/kernel/doc/src/net_kernel.xml30
-rw-r--r--lib/kernel/doc/src/notes.xml750
-rw-r--r--lib/kernel/doc/src/os.xml126
-rw-r--r--lib/kernel/doc/src/part.xml2
-rw-r--r--lib/kernel/doc/src/pg.xml195
-rw-r--r--lib/kernel/doc/src/pg2.xml20
-rw-r--r--lib/kernel/doc/src/ref_man.xml3
-rw-r--r--lib/kernel/doc/src/rpc.xml197
-rw-r--r--lib/kernel/doc/src/seq_trace.xml153
-rw-r--r--lib/kernel/doc/src/socket.xml (renamed from erts/doc/src/socket.xml)417
-rw-r--r--lib/kernel/doc/src/socket_usage.xml (renamed from erts/doc/src/socket_usage.xml)85
-rw-r--r--lib/kernel/doc/src/specs.xml3
-rw-r--r--lib/kernel/doc/src/wrap_log_reader.xml6
-rw-r--r--lib/kernel/doc/src/zlib_stub.xml2
-rw-r--r--lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl7
-rw-r--r--lib/kernel/include/dist.hrl14
-rw-r--r--lib/kernel/include/dist_util.hrl6
-rw-r--r--lib/kernel/include/eep48.hrl14
-rw-r--r--lib/kernel/include/logger.hrl2
-rw-r--r--lib/kernel/src/Makefile7
-rw-r--r--lib/kernel/src/application_controller.erl163
-rw-r--r--lib/kernel/src/application_master.erl6
-rw-r--r--lib/kernel/src/auth.erl8
-rw-r--r--lib/kernel/src/code.erl160
-rw-r--r--lib/kernel/src/code_server.erl33
-rw-r--r--lib/kernel/src/disk_log.erl4
-rw-r--r--lib/kernel/src/disk_log_server.erl6
-rw-r--r--lib/kernel/src/dist_util.erl565
-rw-r--r--lib/kernel/src/erl_distribution.erl46
-rw-r--r--lib/kernel/src/erl_epmd.erl146
-rw-r--r--lib/kernel/src/erpc.erl626
-rw-r--r--lib/kernel/src/error_handler.erl9
-rw-r--r--lib/kernel/src/error_logger.erl13
-rw-r--r--lib/kernel/src/erts_debug.erl64
-rw-r--r--lib/kernel/src/file.erl91
-rw-r--r--lib/kernel/src/file_io_server.erl8
-rw-r--r--lib/kernel/src/gen_tcp.erl74
-rw-r--r--lib/kernel/src/gen_tcp_socket.erl2135
-rw-r--r--lib/kernel/src/global.erl2
-rw-r--r--lib/kernel/src/global_group.erl20
-rw-r--r--lib/kernel/src/group.erl10
-rw-r--r--lib/kernel/src/group_history.erl30
-rw-r--r--lib/kernel/src/inet.erl45
-rw-r--r--lib/kernel/src/inet6_tcp_dist.erl16
-rw-r--r--lib/kernel/src/inet6_udp.erl6
-rw-r--r--lib/kernel/src/inet_db.erl593
-rw-r--r--lib/kernel/src/inet_dns.erl2
-rw-r--r--lib/kernel/src/inet_hosts.erl26
-rw-r--r--lib/kernel/src/inet_res.erl208
-rw-r--r--lib/kernel/src/inet_res.hrl46
-rw-r--r--lib/kernel/src/inet_tcp_dist.erl96
-rw-r--r--lib/kernel/src/inet_udp.erl6
-rw-r--r--lib/kernel/src/kernel.app.src10
-rw-r--r--lib/kernel/src/kernel.appup.src21
-rw-r--r--lib/kernel/src/kernel.erl19
-rw-r--r--lib/kernel/src/logger.erl19
-rw-r--r--lib/kernel/src/logger_backend.erl3
-rw-r--r--lib/kernel/src/logger_config.erl150
-rw-r--r--lib/kernel/src/logger_formatter.erl2
-rw-r--r--lib/kernel/src/logger_h_common.erl24
-rw-r--r--lib/kernel/src/logger_handler_watcher.erl29
-rw-r--r--lib/kernel/src/logger_internal.hrl17
-rw-r--r--lib/kernel/src/logger_olp.erl97
-rw-r--r--lib/kernel/src/logger_olp.hrl19
-rw-r--r--lib/kernel/src/logger_proxy.erl18
-rw-r--r--lib/kernel/src/logger_server.erl40
-rw-r--r--lib/kernel/src/logger_simple_h.erl15
-rw-r--r--lib/kernel/src/logger_std_h.erl23
-rw-r--r--lib/kernel/src/net.erl59
-rw-r--r--lib/kernel/src/net_kernel.erl492
-rw-r--r--lib/kernel/src/os.erl6
-rw-r--r--lib/kernel/src/pg.erl539
-rw-r--r--lib/kernel/src/pg2.erl2
-rw-r--r--lib/kernel/src/raw_file_io_compressed.erl6
-rw-r--r--lib/kernel/src/raw_file_io_delayed.erl6
-rw-r--r--lib/kernel/src/raw_file_io_list.erl7
-rw-r--r--lib/kernel/src/rpc.erl765
-rw-r--r--lib/kernel/src/seq_trace.erl16
-rw-r--r--lib/kernel/src/socket.erl2582
-rw-r--r--lib/kernel/src/user.erl51
-rw-r--r--lib/kernel/src/user_drv.erl25
-rw-r--r--lib/kernel/test/Makefile28
-rw-r--r--lib/kernel/test/application_SUITE.erl235
-rw-r--r--lib/kernel/test/ch_sup.erl8
-rw-r--r--lib/kernel/test/code_SUITE.erl55
-rw-r--r--lib/kernel/test/disk_log_SUITE.erl41
-rw-r--r--lib/kernel/test/erl_boot_server_SUITE.erl69
-rw-r--r--lib/kernel/test/erl_distribution_SUITE.erl489
-rw-r--r--lib/kernel/test/erl_distribution_wb_SUITE.erl269
-rw-r--r--lib/kernel/test/erpc_SUITE.erl1282
-rw-r--r--lib/kernel/test/esock_misc/esock_iow_client.erl (renamed from erts/emulator/test/esock_misc/esock_iow_client.erl)0
-rw-r--r--lib/kernel/test/esock_misc/esock_iow_lib.erl (renamed from erts/emulator/test/esock_misc/esock_iow_lib.erl)0
-rw-r--r--lib/kernel/test/esock_misc/esock_iow_server.erl (renamed from erts/emulator/test/esock_misc/esock_iow_server.erl)0
-rw-r--r--lib/kernel/test/esock_misc/socket_client.erl (renamed from erts/emulator/test/esock_misc/socket_client.erl)0
-rw-r--r--lib/kernel/test/esock_misc/socket_lib.erl (renamed from erts/emulator/test/esock_misc/socket_lib.erl)0
-rw-r--r--lib/kernel/test/esock_misc/socket_server.erl (renamed from erts/emulator/test/esock_misc/socket_server.erl)0
-rw-r--r--lib/kernel/test/esock_ttest/.gitignore (renamed from erts/emulator/test/esock_ttest/.gitignore)0
-rwxr-xr-xlib/kernel/test/esock_ttest/esock-ttest (renamed from erts/emulator/test/esock_ttest/esock-ttest)0
-rwxr-xr-xlib/kernel/test/esock_ttest/esock-ttest-client (renamed from erts/emulator/test/esock_ttest/esock-ttest-client)0
-rwxr-xr-xlib/kernel/test/esock_ttest/esock-ttest-server-gen (renamed from erts/emulator/test/esock_ttest/esock-ttest-server-gen)0
-rwxr-xr-xlib/kernel/test/esock_ttest/esock-ttest-server-sock (renamed from erts/emulator/test/esock_ttest/esock-ttest-server-sock)0
-rw-r--r--lib/kernel/test/file_SUITE.erl286
-rw-r--r--lib/kernel/test/file_name_SUITE.erl4
-rw-r--r--lib/kernel/test/gen_sctp_SUITE.erl678
-rw-r--r--lib/kernel/test/gen_tcp_api_SUITE.erl831
-rw-r--r--lib/kernel/test/gen_tcp_echo_SUITE.erl28
-rw-r--r--lib/kernel/test/gen_tcp_misc_SUITE.erl3320
-rw-r--r--lib/kernel/test/gen_udp_SUITE.erl358
-rw-r--r--lib/kernel/test/global_SUITE.erl48
-rw-r--r--lib/kernel/test/inet_SUITE.erl494
-rw-r--r--lib/kernel/test/inet_res_SUITE.erl442
-rw-r--r--lib/kernel/test/init_SUITE.erl27
-rw-r--r--lib/kernel/test/interactive_shell_SUITE.erl243
-rw-r--r--lib/kernel/test/kernel_SUITE.erl3
-rw-r--r--lib/kernel/test/kernel_test_lib.erl1762
-rw-r--r--lib/kernel/test/kernel_test_lib.hrl55
-rw-r--r--lib/kernel/test/logger_SUITE.erl50
-rw-r--r--lib/kernel/test/logger_formatter_SUITE.erl5
-rw-r--r--lib/kernel/test/logger_legacy_SUITE.erl6
-rw-r--r--lib/kernel/test/logger_std_h_SUITE.erl34
-rw-r--r--lib/kernel/test/net_SUITE.erl66
-rw-r--r--lib/kernel/test/os_SUITE.erl10
-rw-r--r--lib/kernel/test/os_SUITE_data/my_echo.c3
-rw-r--r--lib/kernel/test/os_SUITE_data/my_fds.c4
-rw-r--r--lib/kernel/test/pg_SUITE.erl793
-rw-r--r--lib/kernel/test/prim_file_SUITE.erl59
-rw-r--r--lib/kernel/test/rpc_SUITE.erl499
-rw-r--r--lib/kernel/test/sendfile_SUITE.erl39
-rw-r--r--lib/kernel/test/seq_trace_SUITE.erl551
-rw-r--r--lib/kernel/test/socket_SUITE.erl (renamed from erts/emulator/test/socket_SUITE.erl)3810
-rw-r--r--lib/kernel/test/socket_test_evaluator.erl (renamed from erts/emulator/test/socket_test_evaluator.erl)0
-rw-r--r--lib/kernel/test/socket_test_evaluator.hrl (renamed from erts/emulator/test/socket_test_evaluator.hrl)0
-rw-r--r--lib/kernel/test/socket_test_lib.erl (renamed from erts/emulator/test/socket_test_lib.erl)21
-rw-r--r--lib/kernel/test/socket_test_logger.erl (renamed from erts/emulator/test/socket_test_logger.erl)0
-rw-r--r--lib/kernel/test/socket_test_ttest.hrl (renamed from erts/emulator/test/socket_test_ttest.hrl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_client.hrl (renamed from erts/emulator/test/socket_test_ttest_client.hrl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_lib.erl (renamed from erts/emulator/test/socket_test_ttest_lib.erl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_client.erl (renamed from erts/emulator/test/socket_test_ttest_tcp_client.erl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_client_gen.erl (renamed from erts/emulator/test/socket_test_ttest_tcp_client_gen.erl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_client_socket.erl (renamed from erts/emulator/test/socket_test_ttest_tcp_client_socket.erl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_gen.erl (renamed from erts/emulator/test/socket_test_ttest_tcp_gen.erl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_server.erl (renamed from erts/emulator/test/socket_test_ttest_tcp_server.erl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_server_gen.erl (renamed from erts/emulator/test/socket_test_ttest_tcp_server_gen.erl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_server_socket.erl (renamed from erts/emulator/test/socket_test_ttest_tcp_server_socket.erl)0
-rw-r--r--lib/kernel/test/socket_test_ttest_tcp_socket.erl (renamed from erts/emulator/test/socket_test_ttest_tcp_socket.erl)0
-rw-r--r--lib/kernel/vsn.mk2
-rw-r--r--lib/megaco/Makefile42
-rw-r--r--lib/megaco/configure.in7
-rw-r--r--lib/megaco/doc/src/Makefile122
-rw-r--r--lib/megaco/doc/src/definitions/cite.defs26
-rw-r--r--lib/megaco/doc/src/definitions/cite.defs.xml70
-rw-r--r--lib/megaco/doc/src/definitions/term.defs211
-rw-r--r--lib/megaco/doc/src/definitions/term.defs.xml1518
-rw-r--r--lib/megaco/doc/src/files.mk2
-rw-r--r--lib/megaco/doc/src/megaco.xml131
-rw-r--r--lib/megaco/doc/src/megaco_debug.xml8
-rw-r--r--lib/megaco/doc/src/megaco_encode.xml53
-rw-r--r--lib/megaco/doc/src/megaco_encoder.xml2
-rw-r--r--lib/megaco/doc/src/megaco_intro.xml27
-rw-r--r--lib/megaco/doc/src/megaco_mib.xml4
-rw-r--r--lib/megaco/doc/src/megaco_run.xml47
-rw-r--r--lib/megaco/doc/src/megaco_transport.xml22
-rw-r--r--lib/megaco/doc/src/megaco_transport_mechanisms.xml6
-rw-r--r--lib/megaco/doc/src/megaco_user.xml66
-rw-r--r--lib/megaco/doc/src/notes.xml162
-rw-r--r--lib/megaco/doc/src/notes_history.xml244
-rw-r--r--lib/megaco/src/app/megaco.erl3
-rw-r--r--lib/megaco/src/binary/Makefile5
-rw-r--r--lib/megaco/src/binary/depend.mk16
-rw-r--r--lib/megaco/src/binary/megaco_ber_encoder.erl97
-rw-r--r--lib/megaco/src/binary/megaco_binary_encoder.erl183
-rw-r--r--lib/megaco/src/binary/megaco_binary_name_resolver_prev3a.erl7
-rw-r--r--lib/megaco/src/binary/megaco_binary_name_resolver_prev3b.erl7
-rw-r--r--lib/megaco/src/binary/megaco_binary_name_resolver_prev3c.erl7
-rw-r--r--lib/megaco/src/binary/megaco_binary_transformer_prev3a.erl8
-rw-r--r--lib/megaco/src/binary/megaco_binary_transformer_prev3b.erl8
-rw-r--r--lib/megaco/src/binary/megaco_binary_transformer_prev3c.erl8
-rw-r--r--lib/megaco/src/binary/megaco_per_encoder.erl54
-rw-r--r--lib/megaco/src/binary/modules.mk63
-rw-r--r--lib/megaco/src/engine/megaco_config.erl13
-rw-r--r--lib/megaco/src/engine/megaco_erl_dist_encoder.erl27
-rw-r--r--lib/megaco/src/text/Makefile8
-rw-r--r--lib/megaco/src/text/depend.mk11
-rw-r--r--lib/megaco/src/text/megaco_compact_text_encoder.erl167
-rw-r--r--lib/megaco/src/text/megaco_compact_text_encoder_prev3a.erl7
-rw-r--r--lib/megaco/src/text/megaco_compact_text_encoder_prev3b.erl7
-rw-r--r--lib/megaco/src/text/megaco_compact_text_encoder_prev3c.erl7
-rw-r--r--lib/megaco/src/text/megaco_pretty_text_encoder.erl170
-rw-r--r--lib/megaco/src/text/megaco_pretty_text_encoder_prev3a.erl9
-rw-r--r--lib/megaco/src/text/megaco_pretty_text_encoder_prev3b.erl9
-rw-r--r--lib/megaco/src/text/megaco_pretty_text_encoder_prev3c.erl9
-rw-r--r--lib/megaco/src/text/megaco_text_gen_v3.hrl14
-rw-r--r--lib/megaco/src/text/megaco_text_mini_parser.yrl8
-rw-r--r--lib/megaco/src/text/megaco_text_parser_prev3a.hrl8
-rw-r--r--lib/megaco/src/text/megaco_text_parser_prev3a.yrl7
-rw-r--r--lib/megaco/src/text/megaco_text_parser_prev3b.hrl8
-rw-r--r--lib/megaco/src/text/megaco_text_parser_prev3b.yrl7
-rw-r--r--lib/megaco/src/text/megaco_text_parser_prev3c.hrl8
-rw-r--r--lib/megaco/src/text/megaco_text_parser_prev3c.yrl7
-rw-r--r--lib/megaco/src/text/megaco_text_parser_v2.yrl6
-rw-r--r--lib/megaco/src/text/megaco_text_parser_v3.hrl32
-rw-r--r--lib/megaco/src/text/megaco_text_parser_v3.yrl29
-rw-r--r--lib/megaco/src/text/modules.mk55
-rw-r--r--lib/megaco/test/Makefile27
-rw-r--r--lib/megaco/test/megaco_codec_mini_SUITE.erl401
-rw-r--r--lib/megaco/test/megaco_codec_v2_SUITE.erl1776
-rw-r--r--lib/megaco/test/megaco_codec_v3_SUITE.erl1921
-rw-r--r--lib/megaco/test/megaco_config_SUITE.erl289
-rw-r--r--lib/megaco/test/megaco_mess_SUITE.erl39
-rw-r--r--lib/megaco/test/megaco_segment_SUITE.erl336
-rw-r--r--lib/megaco/test/megaco_test_lib.erl1006
-rw-r--r--lib/megaco/test/megaco_test_lib.hrl2
-rw-r--r--lib/megaco/test/megaco_test_megaco_generator.erl9
-rw-r--r--lib/megaco/test/megaco_test_mgc.erl44
-rw-r--r--lib/megaco/test/megaco_test_msg_v3_lib.erl33
-rw-r--r--lib/megaco/test/megaco_trans_SUITE.erl66
-rw-r--r--lib/megaco/test/megaco_udp_SUITE.erl239
-rw-r--r--lib/megaco/test/modules.mk27
-rw-r--r--lib/megaco/vsn.mk2
-rw-r--r--lib/mnesia/Makefile1
-rw-r--r--lib/mnesia/doc/misc/Makefile2
-rw-r--r--lib/mnesia/doc/specs/.gitignore1
-rw-r--r--lib/mnesia/doc/src/Makefile84
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap1.xml53
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap2.xmlsrc36
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap3.xmlsrc50
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap4.xmlsrc222
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap5.xmlsrc120
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap7.xmlsrc100
-rw-r--r--lib/mnesia/doc/src/Mnesia_overview.xml158
-rw-r--r--lib/mnesia/doc/src/mnesia.xml404
-rw-r--r--lib/mnesia/doc/src/mnesia_frag_hash.xml2
-rw-r--r--lib/mnesia/doc/src/mnesia_registry.xml4
-rw-r--r--lib/mnesia/doc/src/notes.xml144
-rw-r--r--lib/mnesia/doc/src/specs.xml6
-rw-r--r--lib/mnesia/info2
-rw-r--r--lib/mnesia/src/Makefile1
-rw-r--r--lib/mnesia/src/mnesia.app.src48
-rw-r--r--lib/mnesia/src/mnesia.erl157
-rw-r--r--lib/mnesia/src/mnesia.hrl2
-rw-r--r--lib/mnesia/src/mnesia_controller.erl15
-rw-r--r--lib/mnesia/src/mnesia_dumper.erl5
-rw-r--r--lib/mnesia/src/mnesia_event.erl8
-rw-r--r--lib/mnesia/src/mnesia_kernel_sup.erl1
-rw-r--r--lib/mnesia/src/mnesia_lib.erl56
-rw-r--r--lib/mnesia/src/mnesia_loader.erl56
-rw-r--r--lib/mnesia/src/mnesia_monitor.erl8
-rw-r--r--lib/mnesia/src/mnesia_recover.erl8
-rw-r--r--lib/mnesia/src/mnesia_rpc.erl119
-rw-r--r--lib/mnesia/src/mnesia_schema.erl2
-rw-r--r--lib/mnesia/src/mnesia_sp.erl13
-rw-r--r--lib/mnesia/src/mnesia_sup.erl2
-rw-r--r--lib/mnesia/src/mnesia_text.erl4
-rw-r--r--lib/mnesia/src/mnesia_tm.erl6
-rw-r--r--lib/mnesia/test/mnesia_isolation_test.erl7
-rw-r--r--lib/mnesia/test/mnesia_trans_access_test.erl102
-rw-r--r--lib/mnesia/vsn.mk2
-rw-r--r--lib/observer/Makefile2
-rw-r--r--lib/observer/doc/src/Makefile88
-rw-r--r--lib/observer/doc/src/cdv_cmd.xml (renamed from lib/observer/doc/src/cdv.xml)2
-rw-r--r--lib/observer/doc/src/crashdump.xml2
-rw-r--r--lib/observer/doc/src/crashdump_ug.xml38
-rw-r--r--lib/observer/doc/src/etop.xml4
-rw-r--r--lib/observer/doc/src/etop_ug.xml8
-rw-r--r--lib/observer/doc/src/notes.xml30
-rw-r--r--lib/observer/doc/src/observer.xml4
-rw-r--r--lib/observer/doc/src/observer_ug.xml24
-rw-r--r--lib/observer/doc/src/ref_man.xml2
-rw-r--r--lib/observer/doc/src/ttb.xml37
-rw-r--r--lib/observer/doc/src/ttb_ug.xml76
-rw-r--r--lib/observer/src/observer.app.src4
-rw-r--r--lib/observer/src/observer_alloc_wx.erl30
-rw-r--r--lib/observer/src/observer_app_wx.erl2
-rw-r--r--lib/observer/src/observer_perf_wx.erl8
-rw-r--r--lib/observer/test/crashdump_helper.erl25
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE.erl30
-rw-r--r--lib/observer/test/ttb_SUITE.erl30
-rw-r--r--lib/observer/test/ttb_helper.erl5
-rw-r--r--lib/observer/vsn.mk2
-rw-r--r--lib/odbc/Makefile8
-rw-r--r--lib/odbc/c_src/Makefile.in4
-rw-r--r--lib/odbc/c_src/odbcserver.c99
-rw-r--r--lib/odbc/c_src/odbcserver.h4
-rw-r--r--lib/odbc/configure.in19
-rw-r--r--lib/odbc/doc/src/Makefile76
-rw-r--r--lib/odbc/doc/src/databases.xml8
-rw-r--r--lib/odbc/doc/src/error_handling.xml4
-rw-r--r--lib/odbc/doc/src/notes.xml79
-rw-r--r--lib/odbc/doc/src/odbc.xml12
-rw-r--r--lib/odbc/test/README2
-rw-r--r--lib/odbc/test/postgres.erl6
-rw-r--r--lib/odbc/vsn.mk2
-rw-r--r--lib/os_mon/Makefile1
-rw-r--r--lib/os_mon/c_src/memsup.c79
-rw-r--r--lib/os_mon/c_src/memsup.h2
-rw-r--r--lib/os_mon/c_src/nteventlog/elog_main.c10
-rw-r--r--lib/os_mon/doc/src/Makefile71
-rw-r--r--lib/os_mon/doc/src/cpu_sup.xml4
-rw-r--r--lib/os_mon/doc/src/disksup.xml16
-rw-r--r--lib/os_mon/doc/src/memsup.xml113
-rw-r--r--lib/os_mon/doc/src/notes.xml108
-rw-r--r--lib/os_mon/doc/src/nteventlog.xml15
-rw-r--r--lib/os_mon/doc/src/os_mon_app.xml24
-rw-r--r--lib/os_mon/doc/src/os_sup.xml18
-rw-r--r--lib/os_mon/include/memsup.hrl2
-rw-r--r--lib/os_mon/src/Makefile2
-rw-r--r--lib/os_mon/src/cpu_sup.erl19
-rw-r--r--lib/os_mon/src/disksup.erl26
-rw-r--r--lib/os_mon/src/memsup.erl325
-rw-r--r--lib/os_mon/src/nteventlog.erl27
-rw-r--r--lib/os_mon/src/os_mon.app.src2
-rw-r--r--lib/os_mon/src/os_mon_mib.erl27
-rw-r--r--lib/os_mon/src/os_mon_sysinfo.erl19
-rw-r--r--lib/os_mon/src/os_sup.erl65
-rw-r--r--lib/os_mon/test/cpu_sup_SUITE.erl76
-rw-r--r--lib/os_mon/test/memsup_SUITE.erl90
-rw-r--r--lib/os_mon/vsn.mk2
-rw-r--r--lib/parsetools/Makefile1
-rw-r--r--lib/parsetools/doc/src/Makefile71
-rw-r--r--lib/parsetools/doc/src/leex.xml27
-rw-r--r--lib/parsetools/doc/src/notes.xml16
-rw-r--r--lib/parsetools/doc/src/ref_man.xml6
-rw-r--r--lib/parsetools/src/yecc.erl30
-rw-r--r--lib/parsetools/vsn.mk2
-rw-r--r--lib/public_key/Makefile3
-rw-r--r--lib/public_key/asn1/OCSP-2013-88.asn1149
-rw-r--r--lib/public_key/asn1/OTP-PKIX.asn118
-rw-r--r--lib/public_key/asn1/OTP-PUB-KEY.set.asn1
-rw-r--r--lib/public_key/asn1/PKCS-1.asn1335
-rw-r--r--lib/public_key/doc/src/Makefile105
-rw-r--r--lib/public_key/doc/src/notes.xml139
-rw-r--r--lib/public_key/doc/src/public_key.xml128
-rw-r--r--lib/public_key/doc/src/public_key_app.xml2
-rw-r--r--lib/public_key/doc/src/public_key_records.xml29
-rw-r--r--lib/public_key/doc/src/using_public_key.xml4
-rw-r--r--lib/public_key/src/Makefile3
-rw-r--r--lib/public_key/src/pubkey_cert.erl175
-rw-r--r--lib/public_key/src/pubkey_cert_records.erl3
-rw-r--r--lib/public_key/src/pubkey_ocsp.erl345
-rw-r--r--lib/public_key/src/pubkey_pbe.erl44
-rw-r--r--lib/public_key/src/pubkey_pem.erl35
-rw-r--r--lib/public_key/src/public_key.app.src1
-rw-r--r--lib/public_key/src/public_key.erl225
-rw-r--r--lib/public_key/test/erl_make_certs.erl6
-rw-r--r--lib/public_key/test/pbe_SUITE.erl38
-rw-r--r--lib/public_key/test/pkits_SUITE.erl161
-rw-r--r--lib/public_key/test/pubkey_ocsp_SUITE.erl376
-rw-r--r--lib/public_key/test/pubkey_ssh_SUITE.erl35
-rw-r--r--lib/public_key/test/public_key_SUITE.erl289
-rw-r--r--lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem29
-rw-r--r--lib/public_key/vsn.mk2
-rw-r--r--lib/reltool/Makefile3
-rw-r--r--lib/reltool/doc/src/Makefile77
-rw-r--r--lib/reltool/doc/src/files.mk3
-rw-r--r--lib/reltool/doc/src/reltool_examples.xml8
-rw-r--r--lib/reltool/test/reltool_test_lib.erl11
-rw-r--r--lib/runtime_tools/Makefile2
-rw-r--r--lib/runtime_tools/doc/src/LTTng.xml61
-rw-r--r--lib/runtime_tools/doc/src/Makefile77
-rw-r--r--lib/runtime_tools/doc/src/dbg.xml158
-rw-r--r--lib/runtime_tools/doc/src/dyntrace.xml34
-rw-r--r--lib/runtime_tools/doc/src/erts_alloc_config.xml42
-rw-r--r--lib/runtime_tools/doc/src/msacc.xml44
-rw-r--r--lib/runtime_tools/doc/src/notes.xml78
-rw-r--r--lib/runtime_tools/doc/src/scheduler.xml42
-rw-r--r--lib/runtime_tools/doc/src/system_information.xml12
-rw-r--r--lib/runtime_tools/examples/function-calls.d46
-rw-r--r--lib/runtime_tools/examples/function-calls.systemtap46
-rw-r--r--lib/runtime_tools/src/appmon_info.erl6
-rw-r--r--lib/runtime_tools/src/erts_alloc_config.erl25
-rw-r--r--lib/runtime_tools/src/observer_backend.erl23
-rw-r--r--lib/runtime_tools/src/runtime_tools.app.src4
-rw-r--r--lib/runtime_tools/test/erts_alloc_config_SUITE.erl16
-rw-r--r--lib/runtime_tools/vsn.mk2
-rw-r--r--lib/sasl/Makefile3
-rw-r--r--lib/sasl/doc/specs/.gitignore1
-rw-r--r--lib/sasl/doc/src/Makefile76
-rw-r--r--lib/sasl/doc/src/alarm_handler.xml10
-rw-r--r--lib/sasl/doc/src/appup.xml58
-rw-r--r--lib/sasl/doc/src/error_logging.xml64
-rw-r--r--lib/sasl/doc/src/notes.xml96
-rw-r--r--lib/sasl/doc/src/rb.xml22
-rw-r--r--lib/sasl/doc/src/rel.xml10
-rw-r--r--lib/sasl/doc/src/release_handler.xml161
-rw-r--r--lib/sasl/doc/src/relup.xml18
-rw-r--r--lib/sasl/doc/src/sasl_app.xml40
-rw-r--r--lib/sasl/doc/src/sasl_intro.xml4
-rw-r--r--lib/sasl/doc/src/script.xml14
-rw-r--r--lib/sasl/doc/src/specs.xml4
-rw-r--r--lib/sasl/doc/src/systools.xml75
-rw-r--r--lib/sasl/src/release_handler_1.erl19
-rw-r--r--lib/sasl/src/sasl.appup.src13
-rw-r--r--lib/sasl/src/systools.erl24
-rw-r--r--lib/sasl/src/systools_lib.erl6
-rw-r--r--lib/sasl/src/systools_make.erl155
-rw-r--r--lib/sasl/src/systools_relup.erl14
-rw-r--r--lib/sasl/test/installer.erl2
-rw-r--r--lib/sasl/test/release_handler_SUITE.erl21
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.0/src/m.erl1
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.1/src/m.erl1
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/many_mods-2.0/src/m.erl1
-rw-r--r--lib/sasl/test/sasl_SUITE.erl4
-rw-r--r--lib/sasl/test/sasl_report_SUITE.erl42
-rw-r--r--lib/sasl/test/systools_SUITE.erl249
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/ebin/db.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/src/db1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/ebin/db.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/ebin/db.appup14
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/src/db1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/ebin/fe.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe3.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/gh-1.0/ebin/gh.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_replace_app/lib/gh-1.0/src/gh1.erl2
-rw-r--r--lib/sasl/vsn.mk2
-rw-r--r--lib/snmp/Makefile50
-rw-r--r--lib/snmp/configure.in7
-rw-r--r--lib/snmp/doc/src/Makefile126
-rw-r--r--lib/snmp/doc/src/files.mk16
-rw-r--r--lib/snmp/doc/src/notes.xml446
-rw-r--r--lib/snmp/doc/src/notes_history.xml328
-rw-r--r--lib/snmp/doc/src/snmp.xml8
-rw-r--r--lib/snmp/doc/src/snmp_agent_config_files.xml106
-rw-r--r--lib/snmp/doc/src/snmp_agent_funct_descr.xml24
-rw-r--r--lib/snmp/doc/src/snmp_agent_netif.xml18
-rw-r--r--lib/snmp/doc/src/snmp_app.xml125
-rw-r--r--lib/snmp/doc/src/snmp_audit_trail_log.xml4
-rw-r--r--lib/snmp/doc/src/snmp_config.xml139
-rw-r--r--lib/snmp/doc/src/snmp_def_instr_functions.xml4
-rw-r--r--lib/snmp/doc/src/snmp_index.xml2
-rw-r--r--lib/snmp/doc/src/snmp_instr_functions.xml2
-rw-r--r--lib/snmp/doc/src/snmp_manager_config_files.xml14
-rw-r--r--lib/snmp/doc/src/snmp_manager_funct_descr.xml22
-rw-r--r--lib/snmp/doc/src/snmp_manager_netif.xml22
-rw-r--r--lib/snmp/doc/src/snmp_mib_compiler.xml2
-rw-r--r--lib/snmp/doc/src/snmp_pdus.xml1
-rw-r--r--lib/snmp/doc/src/snmp_target_mib.xml4
-rw-r--r--lib/snmp/doc/src/snmp_view_based_acm_mib.xml2
-rw-r--r--lib/snmp/doc/src/snmpa.xml68
-rw-r--r--lib/snmp/doc/src/snmpa_conf.xml76
-rw-r--r--lib/snmp/doc/src/snmpa_discovery_handler.xml10
-rw-r--r--lib/snmp/doc/src/snmpa_error.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_error_io.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_error_logger.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_mib_data.xml49
-rw-r--r--lib/snmp/doc/src/snmpa_mib_storage.xml39
-rw-r--r--lib/snmp/doc/src/snmpa_mpd.xml20
-rw-r--r--lib/snmp/doc/src/snmpa_network_interface.xml18
-rw-r--r--lib/snmp/doc/src/snmpa_network_interface_filter.xml16
-rw-r--r--lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml8
-rw-r--r--lib/snmp/doc/src/snmpa_notification_filter.xml2
-rw-r--r--lib/snmp/doc/src/snmpa_supervisor.xml4
-rw-r--r--lib/snmp/doc/src/snmpc_cmd.xml10
-rw-r--r--lib/snmp/doc/src/snmpm.xml66
-rw-r--r--lib/snmp/doc/src/snmpm_conf.xml32
-rw-r--r--lib/snmp/doc/src/snmpm_mpd.xml6
-rw-r--r--lib/snmp/doc/src/snmpm_network_interface.xml38
-rw-r--r--lib/snmp/doc/src/snmpm_network_interface_filter.xml12
-rw-r--r--lib/snmp/doc/src/snmpm_user.xml26
-rw-r--r--lib/snmp/examples/ex2/snmp_ex2_manager.erl8
-rw-r--r--lib/snmp/src/agent/snmp_framework_mib.erl250
-rw-r--r--lib/snmp/src/agent/snmpa.erl18
-rw-r--r--lib/snmp/src/agent/snmpa_agent.erl121
-rw-r--r--lib/snmp/src/agent/snmpa_mib.erl216
-rw-r--r--lib/snmp/src/agent/snmpa_mpd.erl23
-rw-r--r--lib/snmp/src/agent/snmpa_net_if.erl884
-rw-r--r--lib/snmp/src/agent/snmpa_trap.erl75
-rw-r--r--lib/snmp/src/agent/snmpa_usm.erl61
-rw-r--r--lib/snmp/src/app/snmp.erl101
-rw-r--r--lib/snmp/src/compile/snmpc_misc.erl5
-rw-r--r--lib/snmp/src/manager/snmpm.erl78
-rw-r--r--lib/snmp/src/manager/snmpm_conf.erl2
-rw-r--r--lib/snmp/src/manager/snmpm_config.erl12
-rw-r--r--lib/snmp/src/manager/snmpm_net_if.erl48
-rw-r--r--lib/snmp/src/manager/snmpm_server.erl271
-rw-r--r--lib/snmp/src/manager/snmpm_usm.erl36
-rw-r--r--lib/snmp/src/misc/snmp_conf.erl181
-rw-r--r--lib/snmp/src/misc/snmp_config.erl298
-rw-r--r--lib/snmp/src/misc/snmp_usm.erl31
-rw-r--r--lib/snmp/src/misc/snmp_verbosity.erl4
-rw-r--r--lib/snmp/test/modules.mk1
-rw-r--r--lib/snmp/test/snmp_agent_SUITE.erl1201
-rw-r--r--lib/snmp/test/snmp_agent_mibs_SUITE.erl450
-rw-r--r--lib/snmp/test/snmp_agent_test_lib.erl191
-rw-r--r--lib/snmp/test/snmp_conf_SUITE.erl179
-rw-r--r--lib/snmp/test/snmp_manager_SUITE.erl1224
-rw-r--r--lib/snmp/test/snmp_manager_config_SUITE.erl82
-rw-r--r--lib/snmp/test/snmp_manager_user.erl197
-rw-r--r--lib/snmp/test/snmp_manager_user_SUITE.erl33
-rw-r--r--lib/snmp/test/snmp_otp16649_user.erl93
-rw-r--r--lib/snmp/test/snmp_test_global_sys_monitor.erl84
-rw-r--r--lib/snmp/test/snmp_test_lib.erl1090
-rw-r--r--lib/snmp/test/snmp_test_lib.hrl5
-rw-r--r--lib/snmp/test/snmp_test_manager.erl8
-rw-r--r--lib/snmp/test/snmp_test_mgr.erl101
-rw-r--r--lib/snmp/test/snmp_test_mgr_misc.erl199
-rw-r--r--lib/snmp/test/snmp_to_snmpnet_SUITE.erl47
-rw-r--r--lib/snmp/vsn.mk2
-rw-r--r--lib/ssh/Makefile3
-rw-r--r--lib/ssh/doc/src/Makefile94
-rw-r--r--lib/ssh/doc/src/SSH_app.xml (renamed from lib/ssh/doc/src/ssh_app.xml)231
-rw-r--r--lib/ssh/doc/src/configurations.xml341
-rw-r--r--lib/ssh/doc/src/configure_algos.xml69
-rw-r--r--lib/ssh/doc/src/hardening.xml275
-rw-r--r--lib/ssh/doc/src/introduction.xml26
-rw-r--r--lib/ssh/doc/src/notes.xml582
-rw-r--r--lib/ssh/doc/src/ref_man.xml4
-rw-r--r--lib/ssh/doc/src/specs.xml3
-rw-r--r--lib/ssh/doc/src/ssh.xml437
-rw-r--r--lib/ssh/doc/src/ssh_agent.xml136
-rw-r--r--lib/ssh/doc/src/ssh_client_channel.xml108
-rw-r--r--lib/ssh/doc/src/ssh_client_key_api.xml129
-rw-r--r--lib/ssh/doc/src/ssh_connection.xml82
-rw-r--r--lib/ssh/doc/src/ssh_file.xml198
-rw-r--r--lib/ssh/doc/src/ssh_protocol.xml30
-rw-r--r--lib/ssh/doc/src/ssh_server_channel.xml39
-rw-r--r--lib/ssh/doc/src/ssh_server_key_api.xml22
-rw-r--r--lib/ssh/doc/src/ssh_sftp.xml70
-rw-r--r--lib/ssh/doc/src/ssh_sftpd.xml4
-rw-r--r--lib/ssh/doc/src/terminology.xml12
-rw-r--r--lib/ssh/doc/src/usersguide.xml2
-rw-r--r--lib/ssh/doc/src/using_ssh.xml44
-rw-r--r--lib/ssh/src/Makefile13
-rw-r--r--lib/ssh/src/ssh.app.src8
-rw-r--r--lib/ssh/src/ssh.erl185
-rw-r--r--lib/ssh/src/ssh.hrl27
-rw-r--r--lib/ssh/src/ssh_acceptor.erl61
-rw-r--r--lib/ssh/src/ssh_agent.erl210
-rw-r--r--lib/ssh/src/ssh_agent.hrl106
-rw-r--r--lib/ssh/src/ssh_auth.erl470
-rw-r--r--lib/ssh/src/ssh_channel_sup.erl90
-rw-r--r--lib/ssh/src/ssh_cli.erl354
-rw-r--r--lib/ssh/src/ssh_client_channel.erl12
-rw-r--r--lib/ssh/src/ssh_client_key_api.erl88
-rw-r--r--lib/ssh/src/ssh_connect.hrl1
-rw-r--r--lib/ssh/src/ssh_connection.erl400
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl516
-rw-r--r--lib/ssh/src/ssh_controller.erl125
-rw-r--r--lib/ssh/src/ssh_dbg.erl201
-rw-r--r--lib/ssh/src/ssh_file.erl1025
-rw-r--r--lib/ssh/src/ssh_info.erl160
-rw-r--r--lib/ssh/src/ssh_message.erl227
-rw-r--r--lib/ssh/src/ssh_options.erl268
-rw-r--r--lib/ssh/src/ssh_server_key_api.erl21
-rw-r--r--lib/ssh/src/ssh_sftp.erl12
-rw-r--r--lib/ssh/src/ssh_sftpd.erl87
-rw-r--r--lib/ssh/src/ssh_shell.erl84
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl48
-rw-r--r--lib/ssh/src/ssh_system_sup.erl27
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_acceptor.erl116
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl (renamed from lib/ssh/src/ssh_server_channel_sup.erl)44
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_client.erl84
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_srv.erl75
-rw-r--r--lib/ssh/src/ssh_transport.erl456
-rw-r--r--lib/ssh/src/ssh_xfer.erl32
-rw-r--r--lib/ssh/src/sshc_sup.erl67
-rw-r--r--lib/ssh/src/sshd_sup.erl2
-rw-r--r--lib/ssh/test/.gitignore2
-rw-r--r--lib/ssh/test/Makefile56
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server.erl13
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa21
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa2565
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa3846
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa5217
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed255197
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed44810
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa38
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_encode_decode.erl2
-rw-r--r--lib/ssh/test/ssh.cover20
-rw-r--r--lib/ssh/test/ssh_agent_SUITE.erl180
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/authorized_keys1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/id_rsa27
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key27
-rw-r--r--lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_agent_mock_server.erl158
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE.erl166
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa21
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa38
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_all.cover1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl1288
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_dsa21
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_rsa38
-rw-r--r--lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_bench_SUITE.erl30
-rw-r--r--lib/ssh/test/ssh_chan_behaviours_SUITE.erl21
-rw-r--r--lib/ssh/test/ssh_compat_SUITE.erl310
-rwxr-xr-xlib/ssh/test/ssh_compat_SUITE_data/build_scripts/create_all2
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl443
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE.erl185
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_dsa12
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa2565
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa3846
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa5217
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ed255197
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ed44810
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_ed448.pub1
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_rsa27
-rw-r--r--lib/ssh/test/ssh_dbg_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_echo_server.erl1
-rw-r--r--lib/ssh/test/ssh_engine_SUITE.erl36
-rw-r--r--lib/ssh/test/ssh_eqc_event_handler.erl12
-rw-r--r--lib/ssh/test/ssh_key_cb.erl8
-rw-r--r--lib/ssh/test/ssh_key_cb_engine_keys.erl7
-rw-r--r--lib/ssh/test/ssh_key_cb_options.erl7
-rw-r--r--lib/ssh/test/ssh_limited.cover22
-rw-r--r--lib/ssh/test/ssh_options_SUITE.erl508
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_dsa21
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa2565
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa3846
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa5217
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ed255197
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ed44810
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_rsa38
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_property_test_SUITE.erl29
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE.erl67
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_dsa21
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa2565
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa3846
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa5217
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ed255197
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ed44810
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_rsa38
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE.erl423
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa21
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa9
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed255197
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed44815
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key21
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key9
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key15
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa12
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa5
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key12
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key5
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa15
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa8
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa30
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key12
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key5
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key27
-rw-r--r--lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE.erl430
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa12
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa2565
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa3846
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa5217
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed255197
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed44810
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa27
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key10
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub1
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key16
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE.erl209
-rw-r--r--lib/ssh/test/ssh_sftpd_SUITE.erl88
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl104
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/authorized_keys4
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_dsa21
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_dsa.pub1
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ecdsa2565
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ecdsa256.pub1
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ed255197
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ed25519.pub1
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa38
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ecdsa_key2565
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ecdsa_key256.pub1
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_sup_SUITE.erl131
-rw-r--r--lib/ssh/test/ssh_test_lib.erl653
-rw-r--r--lib/ssh/test/ssh_test_lib.hrl12
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl309
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key5
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub1
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key7
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub1
-rw-r--r--lib/ssh/test/ssh_trpt_test_lib.erl10
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE.erl22
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--lib/ssl/Makefile2
-rw-r--r--lib/ssl/doc/src/Makefile86
-rw-r--r--lib/ssl/doc/src/cite.defs112
-rw-r--r--lib/ssl/doc/src/notes.xml674
-rw-r--r--lib/ssl/doc/src/ssl.xml550
-rw-r--r--lib/ssl/doc/src/ssl_app.xml56
-rw-r--r--lib/ssl/doc/src/ssl_crl_cache.xml10
-rw-r--r--lib/ssl/doc/src/ssl_crl_cache_api.xml66
-rw-r--r--lib/ssl/doc/src/ssl_distribution.xml30
-rw-r--r--lib/ssl/doc/src/ssl_introduction.xml2
-rw-r--r--lib/ssl/doc/src/ssl_protocol.xml56
-rw-r--r--lib/ssl/doc/src/ssl_session_cache_api.xml66
-rw-r--r--lib/ssl/doc/src/standards_compliance.xml204
-rw-r--r--lib/ssl/doc/src/usersguide.xml4
-rw-r--r--lib/ssl/doc/src/using_ssl.xml328
-rw-r--r--lib/ssl/prebuild.skip1
-rw-r--r--lib/ssl/src/Makefile31
-rw-r--r--lib/ssl/src/dtls_connection.erl1125
-rw-r--r--lib/ssl/src/dtls_connection_sup.erl4
-rw-r--r--lib/ssl/src/dtls_gen_connection.erl685
-rw-r--r--lib/ssl/src/dtls_handshake.erl49
-rw-r--r--lib/ssl/src/dtls_listener_sup.erl32
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl54
-rw-r--r--lib/ssl/src/dtls_record.erl25
-rw-r--r--lib/ssl/src/dtls_server_session_cache_sup.erl63
-rw-r--r--lib/ssl/src/dtls_server_sup.erl75
-rw-r--r--lib/ssl/src/dtls_socket.erl121
-rw-r--r--lib/ssl/src/dtls_sup.erl24
-rw-r--r--lib/ssl/src/inet6_tls_dist.erl13
-rw-r--r--lib/ssl/src/inet_tls_dist.erl39
-rw-r--r--lib/ssl/src/ssl.app.src18
-rw-r--r--lib/ssl/src/ssl.appup.src2
-rw-r--r--lib/ssl/src/ssl.erl1233
-rw-r--r--lib/ssl/src/ssl_alert.erl2
-rw-r--r--lib/ssl/src/ssl_certificate.erl346
-rw-r--r--lib/ssl/src/ssl_cipher.erl251
-rw-r--r--lib/ssl/src/ssl_cipher.hrl12
-rw-r--r--lib/ssl/src/ssl_cipher_format.erl376
-rw-r--r--lib/ssl/src/ssl_client_session_cache_db.erl (renamed from lib/ssl/src/ssl_session_cache.erl)47
-rw-r--r--lib/ssl/src/ssl_config.erl145
-rw-r--r--lib/ssl/src/ssl_connection.erl3117
-rw-r--r--lib/ssl/src/ssl_connection.hrl11
-rw-r--r--lib/ssl/src/ssl_crl.erl68
-rw-r--r--lib/ssl/src/ssl_dist_connection_sup.erl30
-rw-r--r--lib/ssl/src/ssl_dist_sup.erl8
-rw-r--r--lib/ssl/src/ssl_gen_statem.erl2055
-rw-r--r--lib/ssl/src/ssl_handshake.erl710
-rw-r--r--lib/ssl/src/ssl_handshake.hrl63
-rw-r--r--lib/ssl/src/ssl_internal.hrl50
-rw-r--r--lib/ssl/src/ssl_listen_tracker_sup.erl2
-rw-r--r--lib/ssl/src/ssl_logger.erl11
-rw-r--r--lib/ssl/src/ssl_manager.erl237
-rw-r--r--lib/ssl/src/ssl_record.erl73
-rw-r--r--lib/ssl/src/ssl_record.hrl3
-rw-r--r--lib/ssl/src/ssl_server_session_cache.erl259
-rw-r--r--lib/ssl/src/ssl_server_session_cache_db.erl80
-rw-r--r--lib/ssl/src/ssl_server_session_cache_sup.erl65
-rw-r--r--lib/ssl/src/ssl_session.erl71
-rw-r--r--lib/ssl/src/ssl_session_cache_api.erl2
-rw-r--r--lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl90
-rw-r--r--lib/ssl/src/ssl_v3.erl200
-rw-r--r--lib/ssl/src/tls_client_ticket_store.erl118
-rw-r--r--lib/ssl/src/tls_connection.erl1479
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl474
-rw-r--r--lib/ssl/src/tls_connection_sup.erl8
-rw-r--r--lib/ssl/src/tls_dist_server_sup.erl89
-rw-r--r--lib/ssl/src/tls_dist_sup.erl75
-rw-r--r--lib/ssl/src/tls_dtls_connection.erl1687
-rw-r--r--lib/ssl/src/tls_gen_connection.erl778
-rw-r--r--lib/ssl/src/tls_handshake.erl105
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl1299
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl8
-rw-r--r--lib/ssl/src/tls_record.erl252
-rw-r--r--lib/ssl/src/tls_record_1_3.erl119
-rw-r--r--lib/ssl/src/tls_sender.erl43
-rw-r--r--lib/ssl/src/tls_server_session_ticket.erl81
-rw-r--r--lib/ssl/src/tls_server_session_ticket_sup.erl26
-rw-r--r--lib/ssl/src/tls_server_sup.erl28
-rw-r--r--lib/ssl/src/tls_socket.erl96
-rw-r--r--lib/ssl/src/tls_sup.erl6
-rw-r--r--lib/ssl/src/tls_v1.erl179
-rw-r--r--lib/ssl/test/Makefile10
-rw-r--r--lib/ssl/test/dtls_api_SUITE.erl177
-rw-r--r--lib/ssl/test/inet_crypto_dist.erl147
-rw-r--r--lib/ssl/test/make_certs.erl8
-rw-r--r--lib/ssl/test/openssl_ECC_SUITE.erl (renamed from lib/ssl/test/ssl_ECC_openssl_SUITE.erl)51
-rw-r--r--lib/ssl/test/openssl_alpn_SUITE.erl549
-rw-r--r--lib/ssl/test/openssl_cipher_suite_SUITE.erl364
-rw-r--r--lib/ssl/test/openssl_client_cert_SUITE.erl151
-rw-r--r--lib/ssl/test/openssl_key_update_SUITE.erl15
-rw-r--r--lib/ssl/test/openssl_mfl_SUITE.erl263
-rw-r--r--lib/ssl/test/openssl_npn_SUITE.erl421
-rw-r--r--lib/ssl/test/openssl_ocsp_SUITE.erl321
-rw-r--r--lib/ssl/test/openssl_reject_SUITE.erl102
-rw-r--r--lib/ssl/test/openssl_renegotiate_SUITE.erl183
-rw-r--r--lib/ssl/test/openssl_server_cert_SUITE.erl141
-rw-r--r--lib/ssl/test/openssl_session_SUITE.erl168
-rw-r--r--lib/ssl/test/openssl_session_ticket_SUITE.erl539
-rw-r--r--lib/ssl/test/openssl_sni_SUITE.erl65
-rw-r--r--lib/ssl/test/openssl_tls_1_3_version_SUITE.erl56
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_chain.erl443
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_cipher_format.erl188
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_handshake.erl21
-rw-r--r--lib/ssl/test/ssl_ECC.erl23
-rw-r--r--lib/ssl/test/ssl_ECC_SUITE.erl61
-rw-r--r--lib/ssl/test/ssl_alert_SUITE.erl18
-rw-r--r--lib/ssl/test/ssl_alpn_SUITE.erl186
-rw-r--r--lib/ssl/test/ssl_api_SUITE.erl1213
-rw-r--r--lib/ssl/test/ssl_app_env_SUITE.erl97
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl388
-rw-r--r--lib/ssl/test/ssl_bench_SUITE.erl292
-rw-r--r--lib/ssl/test/ssl_bench_test_lib.erl10
-rw-r--r--lib/ssl/test/ssl_cert_SUITE.erl381
-rw-r--r--lib/ssl/test/ssl_cert_tests.erl153
-rw-r--r--lib/ssl/test/ssl_cipher_SUITE.erl25
-rw-r--r--lib/ssl/test/ssl_cipher_suite_SUITE.erl295
-rw-r--r--lib/ssl/test/ssl_crl_SUITE.erl119
-rw-r--r--lib/ssl/test/ssl_dist_SUITE.erl859
-rw-r--r--lib/ssl/test/ssl_dist_bench_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_dist_test_lib.erl4
-rw-r--r--lib/ssl/test/ssl_engine_SUITE.erl29
-rw-r--r--lib/ssl/test/ssl_eqc_SUITE.erl79
-rw-r--r--lib/ssl/test/ssl_handshake_SUITE.erl69
-rw-r--r--lib/ssl/test/ssl_key_update_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_mfl_SUITE.erl251
-rw-r--r--lib/ssl/test/ssl_npn_SUITE.erl149
-rw-r--r--lib/ssl/test/ssl_npn_hello_SUITE.erl20
-rw-r--r--lib/ssl/test/ssl_packet_SUITE.erl255
-rw-r--r--lib/ssl/test/ssl_payload_SUITE.erl123
-rw-r--r--lib/ssl/test/ssl_pem_cache_SUITE.erl28
-rw-r--r--lib/ssl/test/ssl_renegotiate_SUITE.erl71
-rw-r--r--lib/ssl/test/ssl_rfc_5869_SUITE.erl28
-rw-r--r--lib/ssl/test/ssl_session_SUITE.erl206
-rw-r--r--lib/ssl/test/ssl_session_cache_SUITE.erl176
-rw-r--r--lib/ssl/test/ssl_session_cache_api_SUITE.erl105
-rw-r--r--lib/ssl/test/ssl_session_ticket_SUITE.erl786
-rw-r--r--lib/ssl/test/ssl_sni_SUITE.erl237
-rw-r--r--lib/ssl/test/ssl_socket_SUITE.erl51
-rw-r--r--lib/ssl/test/ssl_test_lib.erl1879
-rw-r--r--lib/ssl/test/ssl_upgrade_SUITE.erl80
-rw-r--r--lib/ssl/test/tls_1_3_record_SUITE.erl102
-rw-r--r--lib/ssl/test/tls_1_3_version_SUITE.erl135
-rw-r--r--lib/ssl/test/tls_api_SUITE.erl309
-rw-r--r--lib/ssl/test/x509_test.erl2
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/Makefile4
-rw-r--r--lib/stdlib/doc/src/Makefile74
-rw-r--r--lib/stdlib/doc/src/array.xml172
-rw-r--r--lib/stdlib/doc/src/assert_hrl.xml4
-rw-r--r--lib/stdlib/doc/src/beam_lib.xml26
-rw-r--r--lib/stdlib/doc/src/binary.xml46
-rw-r--r--lib/stdlib/doc/src/c.xml88
-rw-r--r--lib/stdlib/doc/src/calendar.xml14
-rw-r--r--lib/stdlib/doc/src/dets.xml190
-rw-r--r--lib/stdlib/doc/src/dict.xml16
-rw-r--r--lib/stdlib/doc/src/digraph.xml78
-rw-r--r--lib/stdlib/doc/src/digraph_utils.xml80
-rw-r--r--lib/stdlib/doc/src/epp.xml24
-rw-r--r--lib/stdlib/doc/src/erl_anno.xml38
-rw-r--r--lib/stdlib/doc/src/erl_eval.xml44
-rw-r--r--lib/stdlib/doc/src/erl_expand_records.xml2
-rw-r--r--lib/stdlib/doc/src/erl_id_trans.xml10
-rw-r--r--lib/stdlib/doc/src/erl_lint.xml20
-rw-r--r--lib/stdlib/doc/src/erl_parse.xml71
-rw-r--r--lib/stdlib/doc/src/erl_pp.xml26
-rw-r--r--lib/stdlib/doc/src/erl_scan.xml18
-rw-r--r--lib/stdlib/doc/src/erl_tar.xml113
-rw-r--r--lib/stdlib/doc/src/ets.xml541
-rw-r--r--lib/stdlib/doc/src/file_sorter.xml2
-rw-r--r--lib/stdlib/doc/src/filelib.xml52
-rw-r--r--lib/stdlib/doc/src/filename.xml44
-rw-r--r--lib/stdlib/doc/src/gb_sets.xml52
-rw-r--r--lib/stdlib/doc/src/gb_trees.xml12
-rw-r--r--lib/stdlib/doc/src/gen_event.xml310
-rw-r--r--lib/stdlib/doc/src/gen_fsm.xml4
-rw-r--r--lib/stdlib/doc/src/gen_server.xml323
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml933
-rw-r--r--lib/stdlib/doc/src/io.xml74
-rw-r--r--lib/stdlib/doc/src/io_lib.xml30
-rw-r--r--lib/stdlib/doc/src/io_protocol.xml65
-rw-r--r--lib/stdlib/doc/src/lists.xml32
-rw-r--r--lib/stdlib/doc/src/log_mf_h.xml10
-rw-r--r--lib/stdlib/doc/src/maps.xml12
-rw-r--r--lib/stdlib/doc/src/math.xml4
-rw-r--r--lib/stdlib/doc/src/ms_transform.xml48
-rw-r--r--lib/stdlib/doc/src/notes.xml529
-rw-r--r--lib/stdlib/doc/src/orddict.xml16
-rw-r--r--lib/stdlib/doc/src/ordsets.xml8
-rw-r--r--lib/stdlib/doc/src/part.xml1
-rw-r--r--lib/stdlib/doc/src/pool.xml10
-rw-r--r--lib/stdlib/doc/src/proc_lib.xml166
-rw-r--r--lib/stdlib/doc/src/proplists.xml64
-rw-r--r--lib/stdlib/doc/src/qlc.xml218
-rw-r--r--lib/stdlib/doc/src/queue.xml33
-rw-r--r--lib/stdlib/doc/src/rand.xml38
-rw-r--r--lib/stdlib/doc/src/random.xml30
-rw-r--r--lib/stdlib/doc/src/re.xml172
-rw-r--r--lib/stdlib/doc/src/ref_man.xml1
-rw-r--r--lib/stdlib/doc/src/sets.xml8
-rw-r--r--lib/stdlib/doc/src/shell.xml35
-rw-r--r--lib/stdlib/doc/src/shell_default.xml2
-rw-r--r--lib/stdlib/doc/src/shell_docs.xml166
-rw-r--r--lib/stdlib/doc/src/slave.xml6
-rw-r--r--lib/stdlib/doc/src/sofs.xml324
-rw-r--r--lib/stdlib/doc/src/specs.xml1
-rw-r--r--lib/stdlib/doc/src/stdlib_app.xml8
-rw-r--r--lib/stdlib/doc/src/string.xml173
-rw-r--r--lib/stdlib/doc/src/supervisor.xml109
-rw-r--r--lib/stdlib/doc/src/supervisor_bridge.xml29
-rw-r--r--lib/stdlib/doc/src/sys.xml127
-rw-r--r--lib/stdlib/doc/src/timer.xml49
-rw-r--r--lib/stdlib/doc/src/unicode.xml40
-rw-r--r--lib/stdlib/doc/src/unicode_usage.xml164
-rw-r--r--lib/stdlib/doc/src/uri_string.xml91
-rw-r--r--lib/stdlib/doc/src/uri_string_usage.xml370
-rw-r--r--lib/stdlib/doc/src/win32reg.xml14
-rw-r--r--lib/stdlib/doc/src/zip.xml68
-rw-r--r--lib/stdlib/examples/erl_id_trans.erl8
-rw-r--r--lib/stdlib/include/erl_bits.hrl16
-rwxr-xr-xlib/stdlib/scripts/update_deprecations359
-rw-r--r--lib/stdlib/src/Makefile11
-rw-r--r--lib/stdlib/src/beam_lib.erl13
-rw-r--r--lib/stdlib/src/c.erl212
-rw-r--r--lib/stdlib/src/calendar.erl3
-rw-r--r--lib/stdlib/src/digraph.erl4
-rw-r--r--lib/stdlib/src/edlin.erl5
-rw-r--r--lib/stdlib/src/edlin_expand.erl78
-rw-r--r--lib/stdlib/src/epp.erl2
-rw-r--r--lib/stdlib/src/erl_error.erl219
-rw-r--r--lib/stdlib/src/erl_eval.erl26
-rw-r--r--lib/stdlib/src/erl_expand_records.erl18
-rw-r--r--lib/stdlib/src/erl_internal.erl16
-rw-r--r--lib/stdlib/src/erl_lint.erl481
-rw-r--r--lib/stdlib/src/erl_parse.yrl33
-rw-r--r--lib/stdlib/src/erl_pp.erl84
-rw-r--r--lib/stdlib/src/erl_scan.erl160
-rw-r--r--lib/stdlib/src/erl_tar.erl85
-rw-r--r--lib/stdlib/src/escript.erl38
-rw-r--r--lib/stdlib/src/ets.erl4
-rw-r--r--lib/stdlib/src/eval_bits.erl46
-rw-r--r--lib/stdlib/src/filelib.erl145
-rw-r--r--lib/stdlib/src/filename.erl4
-rw-r--r--lib/stdlib/src/gen.erl97
-rw-r--r--lib/stdlib/src/gen_event.erl256
-rw-r--r--lib/stdlib/src/gen_fsm.erl343
-rw-r--r--lib/stdlib/src/gen_server.erl348
-rw-r--r--lib/stdlib/src/gen_statem.erl450
-rw-r--r--lib/stdlib/src/io.erl12
-rw-r--r--lib/stdlib/src/io_lib.erl29
-rw-r--r--lib/stdlib/src/io_lib_pretty.erl3
-rw-r--r--lib/stdlib/src/lists.erl8
-rw-r--r--lib/stdlib/src/maps.erl3
-rw-r--r--lib/stdlib/src/otp_internal.erl1070
-rw-r--r--lib/stdlib/src/otp_internal.hrl36
-rw-r--r--lib/stdlib/src/proc_lib.erl411
-rw-r--r--lib/stdlib/src/proplists.erl20
-rw-r--r--lib/stdlib/src/qlc.erl4
-rw-r--r--lib/stdlib/src/qlc_pt.erl28
-rw-r--r--lib/stdlib/src/queue.erl2
-rw-r--r--lib/stdlib/src/random.erl2
-rw-r--r--lib/stdlib/src/shell.erl61
-rw-r--r--lib/stdlib/src/shell_default.erl21
-rw-r--r--lib/stdlib/src/shell_docs.erl1018
-rw-r--r--lib/stdlib/src/stdlib.app.src3
-rw-r--r--lib/stdlib/src/stdlib.appup.src15
-rw-r--r--lib/stdlib/src/supervisor.erl269
-rw-r--r--lib/stdlib/src/supervisor_bridge.erl176
-rw-r--r--lib/stdlib/src/sys.erl5
-rw-r--r--lib/stdlib/src/uri_string.erl155
-rw-r--r--lib/stdlib/src/zip.erl148
-rw-r--r--lib/stdlib/test/Makefile6
-rw-r--r--lib/stdlib/test/beam_lib_SUITE.erl28
-rw-r--r--lib/stdlib/test/dict_test_lib.erl5
-rw-r--r--lib/stdlib/test/digraph_SUITE.erl51
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE.erl32
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/ExpandTestCaps.erl (renamed from lib/stdlib/test/ExpandTestCaps.erl)0
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/ExpandTestCaps1.erl (renamed from lib/stdlib/test/ExpandTestCaps1.erl)0
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/expand_test.erl (renamed from lib/stdlib/test/expand_test.erl)0
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/expand_test1.erl (renamed from lib/stdlib/test/expand_test1.erl)0
-rw-r--r--lib/stdlib/test/edlin_expand_SUITE_data/unicode_expand.erl (renamed from lib/stdlib/test/unicode_expand.erl)0
-rw-r--r--lib/stdlib/test/epp_SUITE.erl66
-rw-r--r--lib/stdlib/test/erl_eval_SUITE.erl73
-rw-r--r--lib/stdlib/test/erl_expand_records_SUITE.erl6
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl226
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl36
-rw-r--r--lib/stdlib/test/erl_scan_SUITE.erl111
-rw-r--r--lib/stdlib/test/ets_SUITE.erl800
-rw-r--r--lib/stdlib/test/ets_SUITE_data/visualize_throughput.html135
-rw-r--r--lib/stdlib/test/filelib_SUITE.erl172
-rw-r--r--lib/stdlib/test/filename_SUITE.erl7
-rw-r--r--lib/stdlib/test/gen_event_SUITE.erl302
-rw-r--r--lib/stdlib/test/gen_fsm_SUITE.erl239
-rw-r--r--lib/stdlib/test/gen_server_SUITE.erl344
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl619
-rw-r--r--lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl18
-rw-r--r--lib/stdlib/test/io_proto_SUITE.erl20
-rw-r--r--lib/stdlib/test/lists_SUITE.erl2
-rw-r--r--lib/stdlib/test/maps_SUITE.erl33
-rw-r--r--lib/stdlib/test/proc_lib_SUITE.erl372
-rw-r--r--lib/stdlib/test/property_test/shell_docs_prop.erl135
-rw-r--r--lib/stdlib/test/property_test/uri_string_recompose.erl9
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput23
-rw-r--r--lib/stdlib/test/shell_SUITE.erl51
-rw-r--r--lib/stdlib/test/shell_docs_SUITE.erl237
-rw-r--r--lib/stdlib/test/stdlib_SUITE.erl4
-rw-r--r--lib/stdlib/test/string_SUITE.erl20
-rw-r--r--lib/stdlib/test/supervisor_SUITE.erl323
-rw-r--r--lib/stdlib/test/supervisor_bridge_SUITE.erl206
-rw-r--r--lib/stdlib/test/tar_SUITE.erl28
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt78
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt6
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt34
-rw-r--r--lib/stdlib/test/uri_string_SUITE.erl182
-rw-r--r--lib/stdlib/test/win32reg_SUITE.erl38
-rw-r--r--lib/stdlib/test/zip_SUITE.erl203
-rw-r--r--lib/stdlib/test/zip_SUITE_data/zip-latin1.zipbin0 -> 115 bytes
-rw-r--r--lib/stdlib/uc_spec/CaseFolding.txt13
-rw-r--r--lib/stdlib/uc_spec/CompositionExclusions.txt6
-rw-r--r--lib/stdlib/uc_spec/GraphemeBreakProperty.txt39
-rw-r--r--lib/stdlib/uc_spec/PropList.txt77
-rw-r--r--lib/stdlib/uc_spec/README-UPDATE.txt10
-rw-r--r--lib/stdlib/uc_spec/SpecialCasing.txt6
-rw-r--r--lib/stdlib/uc_spec/UnicodeData.txt565
-rw-r--r--lib/stdlib/uc_spec/emoji-data.txt305
-rw-r--r--lib/stdlib/uc_spec/gen_unicode_mod.escript2
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--lib/syntax_tools/Makefile2
-rw-r--r--lib/syntax_tools/doc/src/Makefile101
-rw-r--r--lib/syntax_tools/doc/src/notes.xml83
-rw-r--r--lib/syntax_tools/src/epp_dodger.erl67
-rw-r--r--lib/syntax_tools/src/erl_prettypr.erl88
-rw-r--r--lib/syntax_tools/src/erl_syntax.erl101
-rw-r--r--lib/syntax_tools/src/erl_syntax_lib.erl5
-rw-r--r--lib/syntax_tools/src/erl_tidy.erl54
-rw-r--r--lib/syntax_tools/src/igor.erl2
-rw-r--r--lib/syntax_tools/src/prettypr.erl5
-rw-r--r--lib/syntax_tools/test/merl_SUITE.erl7
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE.erl34
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE_data/epp_dodger_clever.erl12
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_test.erl19
-rw-r--r--lib/syntax_tools/vsn.mk2
-rw-r--r--lib/tftp/Makefile34
-rw-r--r--lib/tftp/doc/src/Makefile100
-rw-r--r--lib/tftp/doc/src/getting_started.xml14
-rw-r--r--lib/tftp/doc/src/tftp.xml31
-rw-r--r--lib/tftp/test/Makefile2
-rw-r--r--lib/tftp/test/tftp_bench.spec1
-rw-r--r--lib/tools/Makefile3
-rw-r--r--lib/tools/c_src/Makefile.in1
-rw-r--r--lib/tools/doc/src/Makefile77
-rw-r--r--lib/tools/doc/src/cover.xml7
-rw-r--r--lib/tools/doc/src/cover_chapter.xml12
-rw-r--r--lib/tools/doc/src/cprof.xml14
-rw-r--r--lib/tools/doc/src/cprof_chapter.xml2
-rw-r--r--lib/tools/doc/src/erlang_mode.xml2
-rw-r--r--lib/tools/doc/src/erlang_mode_chapter.xml4
-rw-r--r--lib/tools/doc/src/fprof.xml10
-rw-r--r--lib/tools/doc/src/fprof_chapter.xml10
-rw-r--r--lib/tools/doc/src/instrument.xml22
-rw-r--r--lib/tools/doc/src/lcnt.xml26
-rw-r--r--lib/tools/doc/src/lcnt_chapter.xml4
-rw-r--r--lib/tools/doc/src/make.xml2
-rw-r--r--lib/tools/doc/src/notes.xml122
-rw-r--r--lib/tools/doc/src/xref.xml131
-rw-r--r--lib/tools/doc/src/xref_chapter.xml24
-rw-r--r--lib/tools/emacs/Makefile16
-rw-r--r--lib/tools/emacs/erlang-eunit.el2
-rw-r--r--lib/tools/emacs/erlang-test.el70
-rw-r--r--lib/tools/emacs/erlang.el69
-rw-r--r--lib/tools/emacs/erldoc.el4
-rw-r--r--lib/tools/emacs/internal_doc/emacs.sgml2
-rw-r--r--lib/tools/src/cover.erl24
-rw-r--r--lib/tools/src/eprof.erl4
-rw-r--r--lib/tools/src/instrument.erl7
-rw-r--r--lib/tools/src/tools.app.src2
-rw-r--r--lib/tools/src/xref_base.erl19
-rw-r--r--lib/tools/src/xref_reader.erl24
-rw-r--r--lib/tools/src/xref_utils.erl2
-rw-r--r--lib/tools/test/cover_SUITE.erl42
-rw-r--r--lib/tools/test/cprof_SUITE.erl12
-rw-r--r--lib/tools/test/emacs_SUITE.erl4
-rw-r--r--lib/tools/test/eprof_SUITE.erl13
-rw-r--r--lib/tools/test/fprof_SUITE.erl16
-rw-r--r--lib/tools/test/instrument_SUITE.erl37
-rw-r--r--lib/tools/test/prof_bench_SUITE.erl2
-rw-r--r--lib/tools/test/xref_SUITE.erl105
-rw-r--r--lib/tools/vsn.mk2
-rw-r--r--lib/wx/Makefile2
-rw-r--r--lib/wx/api_gen/wx_extra/wxTaskBarIcon.c_src46
-rw-r--r--lib/wx/api_gen/wx_extra/wxTaskBarIcon.erl45
-rw-r--r--lib/wx/api_gen/wx_gen.erl4
-rw-r--r--lib/wx/api_gen/wx_gen_erl.erl65
-rw-r--r--lib/wx/api_gen/wxapi.conf2
-rw-r--r--lib/wx/c_src/egl_impl.cpp6
-rw-r--r--lib/wx/c_src/gen/wxe_derived_dest.h9
-rw-r--r--lib/wx/c_src/gen/wxe_funcs.cpp10
-rw-r--r--lib/wx/c_src/wxe_callback_impl.cpp20
-rw-r--r--lib/wx/c_src/wxe_main.cpp15
-rw-r--r--lib/wx/c_src/wxe_ps_init.c16
-rw-r--r--lib/wx/configure.in74
-rw-r--r--lib/wx/doc/src/Makefile95
-rw-r--r--lib/wx/doc/src/notes.xml60
-rw-r--r--lib/wx/examples/simple/menu.erl3
-rw-r--r--lib/wx/src/gen/wxCalendarCtrl.erl5
-rw-r--r--lib/wx/src/gen/wxClientDC.erl2
-rw-r--r--lib/wx/src/gen/wxCursor.erl3
-rw-r--r--lib/wx/src/gen/wxDC.erl4
-rw-r--r--lib/wx/src/gen/wxGraphicsRenderer.erl3
-rw-r--r--lib/wx/src/gen/wxGridCellEditor.erl3
-rw-r--r--lib/wx/src/gen/wxIdleEvent.erl2
-rw-r--r--lib/wx/src/gen/wxMDIClientWindow.erl3
-rw-r--r--lib/wx/src/gen/wxPaintDC.erl2
-rw-r--r--lib/wx/src/gen/wxPostScriptDC.erl3
-rw-r--r--lib/wx/src/gen/wxTaskBarIcon.erl24
-rw-r--r--lib/wx/src/gen/wxWindowDC.erl2
-rw-r--r--lib/wx/src/wx_object.erl32
-rw-r--r--lib/wx/test/wx_basic_SUITE.erl11
-rw-r--r--lib/wx/vsn.mk2
-rw-r--r--lib/xmerl/Makefile8
-rw-r--r--lib/xmerl/doc/src/Makefile118
-rw-r--r--lib/xmerl/doc/src/notes.xml39
-rw-r--r--lib/xmerl/doc/src/xmerl_sax_parser.xml23
-rw-r--r--lib/xmerl/doc/src/xmerl_ug.xmlsrc8
-rw-r--r--lib/xmerl/src/xmerl_sax_old_dom.erl157
-rw-r--r--lib/xmerl/src/xmerl_sax_parser_base.erlsrc7
-rw-r--r--lib/xmerl/test/xmerl_SUITE.erl65
-rw-r--r--lib/xmerl/test/xmerl_sax_std_SUITE.erl4
-rw-r--r--lib/xmerl/test/xmerl_sax_stream_SUITE.erl2
-rw-r--r--lib/xmerl/vsn.mk2
-rw-r--r--make/app_targets.mk66
-rw-r--r--make/configure.in52
-rw-r--r--make/doc.mk215
-rwxr-xr-xmake/emd2exml.in24
-rw-r--r--make/otp.mk.in35
-rw-r--r--make/otp_patch_solve_forward_merge_version2
-rw-r--r--make/otp_release_targets.mk23
-rw-r--r--make/otp_subdir.mk4
-rw-r--r--make/otp_version_tickets6
-rw-r--r--make/otp_version_tickets_in_merge4
-rw-r--r--make/prebuild.skip1
-rw-r--r--make/run_make.mk4
-rw-r--r--make/target.mk3
-rwxr-xr-xmake/test_target_script.sh254
-rwxr-xr-xotp_build384
-rw-r--r--otp_versions.table44
-rw-r--r--prebuild.delete2
-rwxr-xr-xscripts/build-otp3
-rwxr-xr-xscripts/build-otp-tar701
-rwxr-xr-xscripts/bundle-otp15
-rwxr-xr-xscripts/diffable111
-rwxr-xr-xscripts/otp_html_check540
-rwxr-xr-xscripts/pre-push94
-rwxr-xr-xscripts/run-dialyzer28
-rwxr-xr-xscripts/run-smoke-tests25
-rw-r--r--system/COPYRIGHT6
-rw-r--r--system/doc/definitions/cite.defs26
-rw-r--r--system/doc/definitions/cite.defs.xml70
-rw-r--r--system/doc/definitions/term.defs211
-rw-r--r--system/doc/definitions/term.defs.xml1525
-rw-r--r--system/doc/design_principles/Makefile13
-rw-r--r--system/doc/design_principles/applications.xml34
-rw-r--r--system/doc/design_principles/appup_cookbook.xml26
-rw-r--r--system/doc/design_principles/des_princ.xml21
-rw-r--r--system/doc/design_principles/distributed_applications.xml6
-rw-r--r--system/doc/design_principles/events.xml9
-rw-r--r--system/doc/design_principles/gen_server_concepts.xml6
-rw-r--r--system/doc/design_principles/release_handling.xml35
-rw-r--r--system/doc/design_principles/release_structure.xml25
-rw-r--r--system/doc/design_principles/spec_proc.xml12
-rw-r--r--system/doc/design_principles/statem.xml551
-rw-r--r--system/doc/design_principles/sup_princ.xml39
-rw-r--r--system/doc/efficiency_guide/Makefile14
-rw-r--r--system/doc/efficiency_guide/advanced.xml18
-rw-r--r--system/doc/efficiency_guide/binaryhandling.xml12
-rw-r--r--system/doc/efficiency_guide/commoncaveats.xml12
-rw-r--r--system/doc/efficiency_guide/drivers.xml18
-rw-r--r--system/doc/efficiency_guide/listhandling.xml12
-rw-r--r--system/doc/efficiency_guide/myths.xml8
-rw-r--r--system/doc/efficiency_guide/processes.xml6
-rw-r--r--system/doc/efficiency_guide/profiling.xml56
-rw-r--r--system/doc/embedded/Makefile16
-rw-r--r--system/doc/general_info/DEPRECATIONS214
-rw-r--r--system/doc/general_info/Makefile30
-rw-r--r--system/doc/general_info/deprecations.xml98
-rw-r--r--system/doc/general_info/deprecations_12.inc6
-rw-r--r--system/doc/general_info/deprecations_18.inc17
-rw-r--r--system/doc/general_info/deprecations_19.inc4
-rw-r--r--system/doc/general_info/deprecations_22.inc38
-rw-r--r--system/doc/general_info/deprecations_23.inc70
-rw-r--r--system/doc/general_info/deprecations_head.inc (renamed from lib/erl_interface/doc/src/part_erl_interface.xml)26
-rw-r--r--system/doc/general_info/scheduled_for_removal_23.inc39
-rw-r--r--system/doc/general_info/scheduled_for_removal_24.inc52
-rw-r--r--system/doc/general_info/scheduled_for_removal_25.inc7
-rw-r--r--system/doc/general_info/scheduled_for_removal_head.inc (renamed from system/doc/general_info/scheduled_for_removal.xml)32
-rw-r--r--system/doc/general_info/xmlfiles.mk5
-rw-r--r--system/doc/getting_started/Makefile14
-rw-r--r--system/doc/getting_started/conc_prog.xml4
-rw-r--r--system/doc/getting_started/intro.xml8
-rw-r--r--system/doc/getting_started/robustness.xml10
-rw-r--r--system/doc/getting_started/seq_prog.xml30
-rw-r--r--system/doc/installation_guide/Makefile13
-rw-r--r--system/doc/oam/Makefile14
-rw-r--r--system/doc/programming_examples/Makefile16
-rw-r--r--system/doc/programming_examples/bit_syntax.xml42
-rw-r--r--system/doc/programming_examples/funs.xmlsrc6
-rw-r--r--system/doc/programming_examples/list_comprehensions.xml4
-rw-r--r--system/doc/programming_examples/records.xml4
-rw-r--r--system/doc/reference_manual/Makefile30
-rw-r--r--system/doc/reference_manual/character_set.xml4
-rw-r--r--system/doc/reference_manual/code_loading.xml14
-rw-r--r--system/doc/reference_manual/data_types.xml59
-rw-r--r--system/doc/reference_manual/distributed.xml30
-rw-r--r--system/doc/reference_manual/errors.xml33
-rw-r--r--system/doc/reference_manual/expressions.xml124
-rw-r--r--system/doc/reference_manual/functions.xml4
-rw-r--r--system/doc/reference_manual/introduction.xml22
-rw-r--r--system/doc/reference_manual/macros.xml6
-rw-r--r--system/doc/reference_manual/modules.xml30
-rw-r--r--system/doc/reference_manual/patterns.xml4
-rw-r--r--system/doc/reference_manual/ports.xml12
-rw-r--r--system/doc/reference_manual/processes.xml270
-rw-r--r--system/doc/reference_manual/records.xml6
-rw-r--r--system/doc/reference_manual/typespec.xml25
-rw-r--r--system/doc/system_architecture_intro/Makefile16
-rw-r--r--system/doc/system_architecture_intro/sys_arch_intro.xml2
-rw-r--r--system/doc/system_principles/Makefile15
-rw-r--r--system/doc/system_principles/create_target.xmlsrc18
-rw-r--r--system/doc/system_principles/error_logging.xml14
-rw-r--r--system/doc/system_principles/misc.xml6
-rw-r--r--system/doc/system_principles/system_principles.xml42
-rw-r--r--system/doc/system_principles/upgrade.xml4
-rw-r--r--system/doc/system_principles/versions.xml22
-rw-r--r--system/doc/top/Makefile42
-rw-r--r--system/doc/top/print.html2
-rw-r--r--system/doc/top/templates/index.html.src2
-rw-r--r--system/doc/tutorial/Makefile12
-rw-r--r--system/doc/tutorial/c_port.xmlsrc2
-rw-r--r--system/doc/tutorial/c_portdriver.xmlsrc2
-rw-r--r--system/doc/tutorial/cnode.xmlsrc26
-rw-r--r--system/doc/tutorial/distribution.xml2
-rw-r--r--system/doc/tutorial/ei.c75
-rw-r--r--system/doc/tutorial/erl_comm.c59
-rw-r--r--system/doc/tutorial/erl_interface.xmlsrc89
-rw-r--r--system/doc/tutorial/nif.xmlsrc2
-rw-r--r--system/doc/tutorial/overview.xml75
-rw-r--r--xcomp/erl-xcomp-arm-android.conf52
-rw-r--r--xcomp/erl-xcomp-arm64-android.conf (renamed from xcomp/erl-xcomp-vxworks_ppc32.conf)63
2820 files changed, 198679 insertions, 101086 deletions
diff --git a/.github/dockerfiles/Dockerfile.32-bit b/.github/dockerfiles/Dockerfile.32-bit
new file mode 100644
index 0000000000..8d955a46e4
--- /dev/null
+++ b/.github/dockerfiles/Dockerfile.32-bit
@@ -0,0 +1,20 @@
+FROM docker.pkg.github.com/erlang/otp/i386-debian-base
+
+ARG MAKEFLAGS=-j4
+ENV MAKEFLAGS=$MAKEFLAGS \
+ ERLC_USE_SERVER=yes \
+ ERL_TOP=/buildroot/otp \
+ PATH=/buildroot/otp/bin:$PATH
+
+ARG ARCHIVE=./otp.tar.gz
+COPY $ARCHIVE /buildroot/otp.tar.gz
+RUN cd /buildroot && tar -xzf ./otp.tar.gz
+
+WORKDIR /buildroot/otp/
+
+RUN ./configure --prefix=/otp && make && make install && \
+ make install-docs DOC_TARGETS=chunks
+
+RUN TESTSUITE_ROOT=/tests ./otp_build tests
+
+ENTRYPOINT ["bash","-c"]
diff --git a/.github/dockerfiles/Dockerfile.64-bit b/.github/dockerfiles/Dockerfile.64-bit
new file mode 100644
index 0000000000..c651cbc618
--- /dev/null
+++ b/.github/dockerfiles/Dockerfile.64-bit
@@ -0,0 +1,22 @@
+FROM docker.pkg.github.com/erlang/otp/ubuntu-base
+
+## We do a SSA lint check here
+ENV ERL_COMPILER_OPTIONS=ssalint
+
+ARG MAKEFLAGS=-j4
+ENV MAKEFLAGS=$MAKEFLAGS \
+ ERLC_USE_SERVER=yes \
+ ERL_TOP=/buildroot/otp \
+ PATH=/buildroot/otp/bin:$PATH
+
+ARG ARCHIVE=./otp.tar.gz
+COPY $ARCHIVE /buildroot/otp.tar.gz
+RUN cd /buildroot && tar -xzf ./otp.tar.gz
+
+WORKDIR /buildroot/otp/
+
+RUN ./configure --prefix=/otp && make && make install
+
+RUN TESTSUITE_ROOT=/tests ./otp_build tests
+
+ENTRYPOINT ["bash","-c"]
diff --git a/.github/dockerfiles/Dockerfile.cross-compile b/.github/dockerfiles/Dockerfile.cross-compile
new file mode 100644
index 0000000000..98f7f0e576
--- /dev/null
+++ b/.github/dockerfiles/Dockerfile.cross-compile
@@ -0,0 +1,56 @@
+##
+## This docker file will build Erlang on 32-bit to 64-bit x86
+##
+FROM docker.pkg.github.com/erlang/otp/i386-debian-base as build
+
+ARG MAKEFLAGS=-j4
+ENV MAKEFLAGS=$MAKEFLAGS \
+ ERLC_USE_SERVER=yes \
+ ERL_TOP=/buildroot/otp
+
+ARG ARCHIVE=./otp.tar.gz
+COPY $ARCHIVE /buildroot/otp.tar.gz
+RUN cd /buildroot && tar xzf ./otp.tar.gz
+
+WORKDIR /buildroot/otp/
+
+## Build the bootstrap system
+RUN ./configure && make && make install
+
+## Build pre-build tar ball
+RUN scripts/build-otp-tar -o /buildroot/otp_clean_src.tar.gz /buildroot/otp_src.tar.gz \
+ -b /buildroot/otp/ /buildroot/otp.tar.gz
+
+## Prepare for a new build using pre-built tar ball
+RUN cd .. && rm -rf otp && tar -xzf ./otp_src.tar.gz
+
+ENV HOST=$HOST_TRIP \
+ CC=$HOST_TRIP-gcc \
+ CPP=$HOST_TRIP-cpp \
+ CXX=$HOST_TRIP-g++ \
+ LD=$CC \
+ DED_LDFLAGS="-shared -Wl,-Bsymbolic" \
+ RANLIB=$HOST_TRIP-ranlib \
+ AR=$HOST_TRIP-ar \
+ erl_xcomp_sysroot=/buildroot/sysroot
+
+## Build the cross system
+RUN ./configure --prefix=/otp/ --host=$HOST --build=`erts/autoconf/config.guess` && \
+ make && make install
+
+## Build the cross tests
+RUN ./otp_build tests
+RUN cd release/tests/test_server && \
+ erl -sname test@docker -noshell \
+ -eval "ts:install([{cross,\"yes\"},{crossflags,[{\"host\",\"$HOST\"}]},{crossroot,\"/$ERL_TOP\"}])." \
+ -s ts compile_testcases -s init stop
+
+FROM debian as install
+
+# Install the released application
+COPY --from=build /otp /otp
+COPY --from=build /buildroot/otp/release/tests /tests
+
+ENV PATH=/otp/bin:$PATH
+
+ENTRYPOINT ["bash","-c"]
diff --git a/.github/dockerfiles/Dockerfile.debian-base b/.github/dockerfiles/Dockerfile.debian-base
new file mode 100644
index 0000000000..416edd97c9
--- /dev/null
+++ b/.github/dockerfiles/Dockerfile.debian-base
@@ -0,0 +1,43 @@
+##
+## This docker file will build a base image for building Erlang/OTP
+##
+ARG BASE=debian
+FROM $BASE
+## Need to have a second arg here as the first does not expose the $BASE in the script below
+ARG BASE=debian
+
+ARG HOST_TRIP=x86_64-linux-gnu
+ENV HOST_TRIP=$HOST_TRIP
+
+ENV INSTALL_LIBS="zlib1g-dev libncurses5-dev libssh-dev unixodbc-dev libgmp3-dev libwxbase3.0-dev libwxgtk3.0-dev libwxgtk-webview3.0-gtk3-dev libsctp-dev lksctp-tools"
+
+## See https://wiki.debian.org/Multiarch/HOWTO for details on how to install things
+##
+## 1. Install build-essential to get access to dpkg-architecture
+## 2. Use dpkg-architecture to figure out what we are runnon on
+## 3. If the HOST_TRIP does not equal BUILD_TRIP we should cross compile
+RUN apt-get update && apt-get -y upgrade && apt-get install -y build-essential && \
+ BUILD_TRIP=`dpkg-architecture -t${HOST_TRIP} -qDEB_BUILD_MULTIARCH` && \
+ BUILD_ARCH=`dpkg-architecture -t${HOST_TRIP} -qDEB_BUILD_ARCH` && \
+ if [ "$HOST_TRIP" != "$BUILD_TRIP" ]; then \
+ HOST_ARCH=`dpkg-architecture -t${HOST_TRIP} -qDEB_HOST_ARCH` && \
+ dpkg --add-architecture $HOST_ARCH && \
+ sed -i "s:deb http:deb [arch=$BUILD_ARCH,$HOST_ARCH] http:g" /etc/apt/sources.list; \
+ fi && \
+ apt-get update && \
+ apt-get install -y build-essential m4 autoconf fop xsltproc default-jdk libxml2-utils \
+ $INSTALL_LIBS && \
+ if [ "$HOST_TRIP" != "$BUILD_TRIP" ]; then \
+ apt-get install -y \
+ crossbuild-essential-$HOST_ARCH \
+ $(for LIB in $INSTALL_LIBS; do echo "$LIB:$HOST_ARCH"; done) && \
+ for dir in `find / -type d -name $HOST_TRIP`; do \
+ echo -n "$dir: /buildroot/sysroot"; \
+ echo `dirname $dir`; \
+ mkdir -p /buildroot/sysroot$dir; \
+ cp -r `dirname $dir`/* `dirname /buildroot/sysroot$dir`; \
+ cp -r $dir/* `dirname /buildroot/sysroot$dir`; \
+ done; \
+ fi && \
+ update-alternatives --set wx-config /usr/lib/${BUILD_TRIP}/wx/config/gtk3-unicode-3.0 && \
+ rm -rf /var/lib/apt/lists/*
diff --git a/.github/dockerfiles/Dockerfile.documentation b/.github/dockerfiles/Dockerfile.documentation
new file mode 100644
index 0000000000..b0c2eb8015
--- /dev/null
+++ b/.github/dockerfiles/Dockerfile.documentation
@@ -0,0 +1,23 @@
+FROM docker.pkg.github.com/erlang/otp/ubuntu-base
+
+ARG MAKEFLAGS=-j4
+ENV MAKEFLAGS=$MAKEFLAGS \
+ ERLC_USE_SERVER=yes \
+ ERL_TOP=/buildroot/otp \
+ PATH=/buildroot/otp/bin:$PATH
+
+ARG ARCHIVE=./otp.tar.gz
+COPY $ARCHIVE /buildroot/otp.tar.gz
+RUN cd /buildroot && tar -xzf ./otp.tar.gz
+
+WORKDIR /buildroot/otp/
+
+ENV RELEASE_ROOT=/otp
+
+RUN ./configure --prefix=/otp && make && make release
+
+RUN ./configure && make && make release
+
+RUN make docs release_docs
+
+ENTRYPOINT ["bash","-c"]
diff --git a/.github/dockerfiles/Dockerfile.ubuntu-base b/.github/dockerfiles/Dockerfile.ubuntu-base
new file mode 100644
index 0000000000..55983f296a
--- /dev/null
+++ b/.github/dockerfiles/Dockerfile.ubuntu-base
@@ -0,0 +1,13 @@
+##
+## This docker file will build a base image for building Erlang/OTP
+##
+FROM ubuntu
+
+ENV INSTALL_LIBS="zlib1g-dev libncurses5-dev libssh-dev unixodbc-dev libgmp3-dev libwxbase3.0-dev libwxgtk3.0-gtk3-dev libsctp-dev lksctp-tools"
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update && \
+ apt-get -y upgrade && \
+ apt-get install -y build-essential m4 autoconf fop xsltproc \
+ default-jdk libxml2-utils $INSTALL_LIBS
diff --git a/.github/scripts/base-tag b/.github/scripts/base-tag
new file mode 100755
index 0000000000..6683793762
--- /dev/null
+++ b/.github/scripts/base-tag
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+set -x
+
+case "$1" in
+ *i386-debian-base)
+ BASE="i386/debian"
+ BASE_TYPE=debian-base
+ ;;
+ *debian-base)
+ BASE="debian"
+ BASE_TYPE=debian-base
+ ;;
+ *ubuntu-base)
+ BASE="ubuntu"
+ BASE_TYPE=ubuntu-base
+ ;;
+esac
+echo "::set-output name=BASE::${BASE}"
+echo "::set-output name=BASE_TYPE::${BASE_TYPE}"
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
new file mode 100644
index 0000000000..988bf3b07e
--- /dev/null
+++ b/.github/workflows/main.yaml
@@ -0,0 +1,217 @@
+##
+## This workflow handles testing of pull requests and pushes.
+## It also publishes some packages to any new Erlang/OTP release
+##
+## To speed this up it would be nice if one could share docker
+## images inbetween different jobs, but at the moment this is
+## not possible so we need to rebuild all of Erlang/OTP multiple
+## times.
+##
+## Also once the windows runner supports WSL we should implement
+## support for building Erlang/OTP here.
+##
+## When ghcr.io support using the GITHUB_TOKEN we should migrate
+## over to use it instead as that should allow us to use the
+## built-in caching mechanisms of docker/build-push-action@v2.
+## However as things are now we use docker directly to make things
+## work.
+##
+
+name: Build and check Erlang/OTP
+
+on:
+ push:
+ pull_request:
+
+jobs:
+
+ pack:
+ name: Pack the Erlang/OTP tar.gz
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Commit autoconf files
+ ## We first commit the autoconf generate so that they
+ ## are kept in the pre-built achive
+ run: |
+ ./otp_build autoconf
+ find . -name aclocal.m4 | xargs git add -f
+ find . -name configure | xargs git add -f
+ find . -name config.h.in | xargs git add -f
+ find . -name config.guess | xargs git add -f
+ find . -name config.sub | xargs git add -f
+ find . -name install-sh | xargs git add -f
+ git config --global user.email "you@example.com"
+ git config --global user.name "Your Name"
+ git commit --no-verify -m 'Add generated configure files'
+ - name: Archive git repository
+ run: git archive --prefix otp/ -o otp_src.tar.gz HEAD
+ - name: Upload source tar archive
+ uses: actions/upload-artifact@v2
+ with:
+ name: otp_git_archive
+ path: otp_src.tar.gz
+
+ build:
+ name: Build Erlang/OTP
+ runs-on: ubuntu-latest
+ needs: pack
+
+ strategy:
+ matrix:
+ type: [64-bit,32-bit,cross-compile,documentation]
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Download source archive
+ uses: actions/download-artifact@v2
+ with:
+ name: otp_git_archive
+ - name: Docker login
+ uses: docker/login-action@v1
+ with:
+ registry: docker.pkg.github.com
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Calculate BASE image
+ id: base
+ run: |
+ BASE_TAG=$(grep "^FROM" .github/dockerfiles/Dockerfile.${{ matrix.type }} | head -1 | awk '{print $2}')
+ echo "::set-output name=BASE_TAG::${BASE_TAG}"
+ .github/scripts/base-tag "${BASE_TAG}"
+ - name: Pull BASE image
+ run: docker pull ${{ steps.base.outputs.BASE_TAG }}
+ - name: Build BASE image
+ run: |
+ docker build --pull --tag ${{ steps.base.outputs.BASE_TAG }} \
+ --cache-from ${{ steps.base.outputs.BASE_TAG }} \
+ --file .github/dockerfiles/Dockerfile.${{ steps.base.outputs.BASE_TYPE }} \
+ --build-arg BASE=${{ steps.base.outputs.BASE }} .
+ - name: Build ${{ matrix.type }} image
+ run: |
+ docker build --tag otp --file .github/dockerfiles/Dockerfile.${{ matrix.type }} \
+ --build-arg ARCHIVE=otp_src.tar.gz .
+
+ ## Smoke build tests
+ - if: matrix.type == '32-bit' || matrix.type == '64-bit' || matrix.type == 'cross-compile'
+ name: Run smoke test
+ run: docker run -v $PWD/scripts:/scripts otp "cd /tests && /scripts/run-smoke-tests"
+
+ ## Documentation checks
+ - if: matrix.type == 'documentation'
+ name: Run xmllimt
+ run: docker run otp "make xmllint"
+ - if: matrix.type == 'documentation'
+ name: Run html link check
+ run: docker run -v $PWD/scripts:/scripts otp "/scripts/otp_html_check /otp doc/index.html"
+ - if: matrix.type == 'documentation'
+ name: Release docs to publish
+ run: |
+ docker run -v $PWD/:/github otp "make release_docs DOC_TARGETS='man html pdf' RELEASE_ROOT=/github/docs"
+ sudo chown -R `whoami` docs
+ cd docs
+ tar czf ../otp_doc_man.tar.gz man
+ rm -rf man
+ tar czf ../otp_doc_html.tar.gz *
+ - if: matrix.type == 'documentation'
+ name: Upload html documentation archive
+ uses: actions/upload-artifact@v2
+ with:
+ name: otp_doc_html
+ path: otp_doc_html.tar.gz
+ - if: matrix.type == 'documentation'
+ name: Upload man documentation archive
+ uses: actions/upload-artifact@v2
+ with:
+ name: otp_doc_man
+ path: otp_doc_man.tar.gz
+
+ ## Run dialyzer
+ - if: matrix.type == '64-bit'
+ name: Run dialyzer
+ run: docker run -v $PWD/scripts:/scripts otp "/scripts/run-dialyzer"
+
+ ## Build pre-built tar archives
+ - if: matrix.type == '32-bit'
+ name: Build pre-built tar archives
+ run: |
+ docker run -v $PWD:/github otp \
+ "scripts/build-otp-tar -o /github/otp_clean_src.tar.gz /github/otp_src.tar.gz -b /buildroot/otp/ /buildroot/otp.tar.gz"
+ - if: matrix.type == '32-bit'
+ name: Upload pre-built tar archive
+ uses: actions/upload-artifact@v2
+ with:
+ name: otp_prebuilt
+ path: otp_src.tar.gz
+
+ ## If this is a tag that has been pushed we do some release work
+ release:
+ name: Release Erlang/OTP
+ runs-on: ubuntu-latest
+ needs: build
+ if: startsWith(github.ref, 'refs/tags/') && github.repository == 'erlang/otp'
+ steps:
+ ## This step outputs the tag name and whether the tag is a release or patch
+ ## (all releases have only two version identifiers, while patches have three
+ ## or more)
+ - name: Get Tag Name
+ id: tag
+ run: |
+ TAG=${GITHUB_REF#refs/tags/}
+ VSN=${TAG#OTP-}
+ IS_RELEASE=`$(echo $TAG | grep -E '^OTP-[0-9]+\.[0-9]+$' > /dev/null) \
+ && echo "true" || echo "false"`
+ echo "::set-output name=tag::${TAG}"
+ echo "::set-output name=vsn::${VSN}"
+ echo "::set-output name=release::${IS_RELEASE}"
+
+ - uses: actions/checkout@v2
+
+ ## Publish the pre-built archive and docs
+ - name: Download source archive
+ uses: actions/download-artifact@v2
+ with:
+ name: otp_prebuilt
+ - name: Download html docs
+ uses: actions/download-artifact@v2
+ with:
+ name: otp_doc_html
+ - name: Download man docs
+ uses: actions/download-artifact@v2
+ with:
+ name: otp_doc_man
+
+ ## We add the correct version name into the file names
+ ## and create the hash files for all assets
+ - name: Create pre-build and doc archives
+ run: |
+ mkdir artifacts
+ tar -xzf otp_src.tar.gz
+ mv otp otp_src_${{ steps.tag.outputs.vsn }}
+ tar -czf artifacts/otp_src_${{ steps.tag.outputs.vsn }}.tar.gz otp_src_${{ steps.tag.outputs.vsn }}
+ mv otp_doc_man.tar.gz artifacts/otp_doc_man_${{ steps.tag.outputs.vsn }}.tar.gz
+ mv otp_doc_html.tar.gz artifacts/otp_doc_html_${{ steps.tag.outputs.vsn }}.tar.gz
+
+ - name: Build OTP Bundle
+ if: steps.tag.outputs.release == 'true'
+ run: |
+ scripts/bundle-otp ${{ steps.tag.outputs.tag }}
+
+ ## Create hash files
+ - name: Create pre-build and doc archives
+ run: |
+ shopt -s nullglob
+ cd artifacts
+ md5sum {*.tar.gz,*.txt} > MD5.txt
+ sha256sum {*.tar.gz,*.txt} > SHA256.txt
+
+ - name: Upload pre-built and doc tar archives
+ uses: softprops/action-gh-release@v1
+ with:
+ name: OTP ${{ steps.tag.outputs.vsn }}
+ files: |
+ artifacts/*.tar.gz
+ artifacts/*.txt
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/update-base.yaml b/.github/workflows/update-base.yaml
new file mode 100644
index 0000000000..6cf2eafac2
--- /dev/null
+++ b/.github/workflows/update-base.yaml
@@ -0,0 +1,41 @@
+name: Update docker base image
+
+## Update the base image every day
+on:
+ schedule:
+ ## In UTC
+ - cron: '0 0 * * *'
+
+## Build base images to be used by other github workflows
+jobs:
+
+ build:
+ name: Update base Erlang/OTP build images
+ if: github.repository == 'erlang/otp'
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ type: [debian-base,ubuntu-base,i386-debian-base]
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Docker login
+ uses: docker/login-action@v1
+ with:
+ registry: docker.pkg.github.com
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Calculate BASE image
+ id: base
+ run: |
+ echo "::set-output name=BASE_TAG::docker.pkg.github.com/erlang/otp/${{ matrix.type }}"
+ .github/scripts/base-tag "${{ matrix.type }}"
+ - name: Build base image
+ run: |
+ docker build --pull --tag ${{ steps.base.outputs.BASE_TAG }} \
+ --cache-from ${{ steps.base.outputs.BASE_TAG }} \
+ --file .github/dockerfiles/Dockerfile.${{ steps.base.outputs.BASE_TYPE }} \
+ --build-arg BASE=${{ steps.base.outputs.BASE }} .
+ - name: Push base image
+ run: docker push ${{ steps.base.outputs.BASE_TAG }}
diff --git a/.gitignore b/.gitignore
index b14ec86df6..171ddfb9af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,10 +32,13 @@ autom4te.cache
# Do not use too creative wildcards.
# Those might ignore files that should not be ignored.
+aarch64-unknown-linux-android
+arm-unknown-linux-androideabi
armv7l-unknown-linux-gnueabihf
i686-pc-linux-gnu
x86_64-unknown-linux-gnu
i386-apple-darwin[0-9]*.[0-9]*.[0-9]*
+arm-apple-darwin[0-9]*.[0-9]*.[0-9]*
x86_64-apple-darwin[0-9]*.[0-9]*.[0-9]*
x86_64-unknown-freebsd[0-9]*.[0-9]*
sparc-sun-solaris[0-9]*.[0-9]*
@@ -54,8 +57,10 @@ a.out.dSYM/
# Windows
*.pdb
+local.static.config.cache
tcltk85_win32_bin.tar.gz
erts/autoconf/win32.config.cache
+erts/autoconf/win64.config.cache
erts/emulator/obj/
erts/emulator/pcre/obj/
erts/emulator/pcre/win32/
@@ -66,6 +71,9 @@ erts/epmd/src/win32/
erts/etc/common/Install.ini
erts/etc/common/win32/
erts/etc/win32/cygwin_tools/vc/coffix.exe
+erts/etc/win32/wsl_tools/vc/coffix.exe
+erts/etc/win32/nsis/helper.*
+erts/etc/win32/nsis/hello.*
erts/include/internal/win32/
erts/include/win32/
erts/lib/internal/win32/
@@ -178,6 +186,8 @@ JAVADOC-GENERATED
/lib/*/doc/pdf/*.pdf
/lib/*/doc/src/*.fo
/lib/*/doc/xml/*.xml
+/lib/*/doc/xml/*.ent
+/lib/*/doc/chunks/*.chunk
/lib/config.log
/lib/config.status
@@ -245,11 +255,14 @@ JAVADOC-GENERATED
/lib/compiler/src/core_parse.erl
/lib/compiler/test/*_no_opt_SUITE.erl
+/lib/compiler/test/*_no_copt_SUITE.erl
/lib/compiler/test/*_no_ssa_opt_SUITE.erl
/lib/compiler/test/*_post_opt_SUITE.erl
/lib/compiler/test/*_inline_SUITE.erl
/lib/compiler/test/*_r21_SUITE.erl
/lib/compiler/test/*_no_module_opt_SUITE.erl
+/lib/compiler/test/*_no_type_opt_SUITE.erl
+/lib/compiler/test/*_dialyzer_SUITE.erl
# crypto
/lib/crypto/test/crypto_SUITE_data/*.rsp
@@ -274,6 +287,8 @@ JAVADOC-GENERATED
/erts/doc/pdf/*.pdf
/erts/doc/src/*.fo
/erts/doc/xml/*.xml
+/erts/doc/xml/figures/*.png
+/erts/doc/chunks/*.chunk
/erts/doc/man[0-9]/*.[0-9]
/erts/doc/CONF_INFO
diff --git a/.travis.yml b/.travis.yml
index 5a87bd3fd1..5cdef141c0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,32 +23,24 @@ addons:
matrix:
include:
- # Dialyzer is first as it takes the longest to run
- - env: Linux64Dialyzer
- addons:
- apt:
- packages:
- # Don't build with wx, java or xslt to get faster build
- - autoconf
- - libncurses-dev
- - build-essential
- - libssl-dev
- script:
- - ./scripts/build-otp
- - ./scripts/run-dialyzer
-
- - env: Linux32
- services:
- - docker
- script:
- - ./scripts/build-docker-otp 32 sh -c "scripts/build-otp release && ./otp_build tests && scripts/run-smoke-tests && bin/dialyzer --build_plt --apps erts kernel stdlib"
-
- - env: Linux64SmokeTest
+ # Doc build second as it also takes a long time to run
+ - env: Linux64Docbuild
script:
- - ERL_COMPILER_OPTIONS=ssalint ./scripts/build-otp
- - ERL_COMPILER_OPTIONS=ssalint ./otp_build tests
- - ./scripts/run-smoke-tests
-
+ - ./scripts/build-otp docs
+ deploy:
+ provider: pages
+ repo: erlang/cd
+ target-branch: master
+ skip-cleanup: true
+ keep-history: false
+ verbose: true
+ github-token: $ERLANG_CD_GITHUB_TOKEN
+ on:
+ # We only deploy on pushes to branches
+ all_branches: true
+ tags: false
+ condition: $TRAVIS_PULL_REQUEST = "false"
+ repo: erlang/otp
- env: Linux-ppc64le-SmokeTest
os: linux-ppc64le
script:
@@ -70,45 +62,6 @@ matrix:
- xsltproc
- libxml2-utils
- - env: Linux64Docbuild
- script:
- - ./scripts/build-otp docs
- deploy:
- provider: pages
- repo: erlang/cd
- target-branch: master
- skip-cleanup: true
- keep-history: false
- verbose: true
- github-token: $ERLANG_CD_GITHUB_TOKEN
- on:
- # We only deploy on pushes to branches
- all_branches: true
- tags: false
- condition: $TRAVIS_PULL_REQUEST = "false"
- repo: erlang/otp
- # This stage publishes a otp bundle that contains multiple
- # Erlang/OTP source repositories
- - stage: deploy
- env: Deploy
- if: tag =~ ^OTP-[0-9]+\.[0-9]+$
- script:
- - ./scripts/bundle-otp
- deploy:
- provider: releases
- skip_cleanup: true
- api_key:
- secure: vW5PN6zng5H5+TCvwfwpGZsABrdCWYcFwDm3KXq+plsecBmTayu/0jgNso5Z97FbzDGVTLHWchvywEYQWnmrEByyOrqH73v1LN6JEfN99VpSrdFr15IzhblcyU1R9ugYc3WEoYjX0Q1uGelDSWRuuQOPbzy8mZf3D4rSGonyraP7jPTdHhs5P3ZWk6OMFz+tCdF4XohXqbhXIBOeH/EKg0svX2u5IcV01/YOL8LHWz6G7+gqBryEXx1+ngjQXQmMQwd7Yg3WOKE4XV9gX8ixZsbpUPZXAQKF+VOYdEgeiIr1hI0tBQUYX7FYEzYH5MCxqng5RdaPTOAm1oQroyGkIcWSXzDwN4AhJ7xqa/0NRdEaBPdQzPBCc+pVUDkxBR1ytXjBQqdQMnI6184TDiU5XBnj3kmieLkkKPKQNoPve/Y8Q8zutw4GNc7gixGcQCjtAFUbrT73QVRrezQH0qIdt23rivvf2R7CCOWSmgzowrswmtHdgeEVbodUIBPTNp7qzlUk9gDp6vW0XrOC4qEFI+VaY5PsEOXrrxZmI3gGGJgsbfzRvzvvupQcLNERniJ67r/uumbForpL0x1c65scKuMWwcn1wqt2OLbDoIIuM31Ph2HX/09TTqECU7CTvqLT5MnbZHXGjY9c3ch+sY3tSfaEX6aazl/Dqx28c7boCEw=
- file:
- - ${TRAVIS_TAG}.0-bundle.txt
- - ${TRAVIS_TAG}.0-bundle.tar.gz
- on:
- # We only deploy on pushes to tags that match the regexp
- tags: true
- condition: $TRAVIS_TAG =~ ^OTP-[0-9]+\.[0-9]+$
- repo: erlang/otp
-
-
before_script:
- set -e
- export ERL_TOP=$PWD
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8814e506f9..068ae0507e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -30,7 +30,7 @@ By making a contribution to this project, I certify that:
Erlang/otp is licensed under the
Apache License 2.0
-As stated in: LICENSE.txt
+As stated in: [LICENSE.txt](LICENSE.txt)
http://developercertificate.org/
@@ -95,6 +95,9 @@ a discussion on the mailing list.
* Make sure existing test cases don't fail. It is not necessary to run all tests (that would take many hours),
but you should at least run the tests for the application you have changed.
See [Running tests](https://github.com/erlang/otp/wiki/Running-tests).
+* Make sure the documentation builds and is according to the dtd. eg. `make xmllint` or `cd lib/stdlib/ && make xmllint`
+* Make sure no new dialyzer warnings have been added. eg. `make dialyzer` or `cd lib/stdlib/ && make dialyzer`
+* Make sure that travis passes, if you go to https://travis-ci.org/$YOUR_GITHUB_USER/otp/ you can enable travis builds for you otp fork.
Make sure that your branch contains clean commits:
@@ -106,8 +109,11 @@ Make sure that your branch contains clean commits:
* Don't merge `maint` or `master` into your branch. Use `git rebase` if you need to resolve merge
conflicts or include the latest changes.
-* To make it possible to use the powerful `git bisect` command, make sure that each commit can be
-compiled and that it works.
+* Each commit should represent a logical change, such as a feature added or bug fixed, and also include relevant changes to documentation and tests.
+
+* Each commit should compile separately and pass the most relevant test cases. This makes it possible to use the powerful `git bisect` command.
+
+* Changes to multiple applications should be made in separate commits to facilitate code reviews, unless special circumstances motivates a single commit, such as not breaking the ability to build cleanly.
* Check for unnecessary whitespace before committing with `git diff --check`.
However, do not fix preexisting whitespace errors in otherwise untouched source lines.
diff --git a/HOWTO/DEPRECATE.md b/HOWTO/DEPRECATE.md
new file mode 100644
index 0000000000..a33b555e89
--- /dev/null
+++ b/HOWTO/DEPRECATE.md
@@ -0,0 +1,79 @@
+# Deprecate
+
+This HOWTO shows how to deprecate functionality from Erlang/OTP.
+
+When adding a new *deprecation* or *removal* warning you need to add an attribute to
+the module in question and update [$ERL_TOP/system/doc/general_info/DEPRECATIONS][1]
+with information about when the deprecation was added.
+
+After changing or adding an attribute, and updating the [`DEPRECATIONS`][1],
+you need to update the internal state by running:
+
+ $ ./otp_build update_deprecations
+
+This will update the documentation and the central list of deprecated and removed
+interfaces ([`otp_internal.erl`][2]).
+
+## Attribute format
+
+To mark a function/type as deprecated or removed, point out its name and arity
+together with a suggestion on what the user should do instead:
+
+ -deprecated([{now,0,
+ "see the \"Time and Time Correction in Erlang\" "
+ "chapter of the ERTS User's Guide for more information"}]).
+
+ -deprecated([{cmac, 3, "use crypto:mac/4 instead"},
+ {cmac, 4, "use crypto:macN/5 instead"}]).
+
+ -removed([{md5_mac,2,"use crypto:hmac/3 instead"},
+ {md5_mac_96,2,"use crypto:hmac/4 instead"}]).
+
+ -deprecated_type([{gadget,1,"use widget/1 instead"}]).
+
+ -removed_type([{column,0,"use erl_anno:column() instead"},
+ {line,0,"use erl_anno:line() instead"},
+ {location,0,"use erl_anno:location() instead"}]).
+
+Wildcards can be used to match all names and/or arities:
+
+ -removed([{rsa_sign,'_',"use crypto:sign/4 instead"},
+ {rsa_verify,'_',"use crypto:verify/5 instead"}]).
+
+ -deprecated([{next_iv, '_',"see the 'New and Old API' chapter of the CRYPTO User's guide"}]).
+
+ -deprecated([{'_','_',"use the 'rand' module instead"}]).
+
+ -deprecated_type([{grunka,'_',"use frobnitz/1,2 instead"}]).
+
+You can also use the `Name/Arity` shorthand for all of these variants, e.g.
+`-deprecated([f/1])`, which will result in a generic description to the effect of
+"see the documentation for details."
+
+Note that it is not possible to warn about a module that no longer exists.
+This is to prevent later namespace clashes from raising warnings, like the ones
+we had when the long-removed `net` module reappeared in the socket NIF.
+"Removed" modules should instead have their contents replaced by
+`-removed()` attributes until there's no longer any point in warning
+about their use.
+
+## The DEPRECATIONS file
+
+The [$ERL_TOP/system/doc/general_info/DEPRECATIONS][1] file contains additional
+information about each deprecated function, namely in what release it was deprecated
+and optionally in what release it will be removed. The information in this file will
+be used to generate the [Deprecations](http://erlang.org/doc/general_info/deprecations.html)
+and [Scheduled for Removal](http://erlang.org/doc/general_info/scheduled_for_removal.html)
+pages in the documentation.
+
+Here is how the entry for `erlang:now/0` that was deprecated in OTP 18 looks like:
+
+ erlang:now/0 since=18
+
+Here is an example of a function that was deprecated in OTP 23 and is scheduled for removal in OTP 25:
+
+ filename:safe_relative_path/1 since=23 remove=25
+
+
+ [1]: ../system/doc/general_info/DEPRECATIONS
+ [2]: ../lib/stdlib/src/otp_internal.erl
diff --git a/HOWTO/INSTALL-ANDROID.md b/HOWTO/INSTALL-ANDROID.md
index 31698d4ce3..7d5af0b0ec 100644
--- a/HOWTO/INSTALL-ANDROID.md
+++ b/HOWTO/INSTALL-ANDROID.md
@@ -4,48 +4,163 @@ Cross Compiling Erlang/OTP - ANDROID
Introduction
------------
-This document describes how to cross compile Erlang OTP to Android/Rasberry Pi platforms.
+This document describes how to cross compile Erlang/OTP to Android/Rasberry Pi platforms.
+
### Download and Install Android NDK ###
-https://developer.android.com/tools/sdk/ndk/index.html
+https://developer.android.com/ndk
+
### Define System Variables ###
-export NDK_ROOT=/usr/local/android
-export NDK_PLAT=android-9
-export PATH=$NDK_ROOT/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/bin:$PATH
+ $ export NDK_ROOT=/path/to/android-ndk
+ $ export PATH=$NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
+ $ # export PATH=$NDK_ROOT/toolchains/lvvm/prebuilt/darwin-x86_64/bin:$PATH
+
+
+### Configure Erlang/OTP ###
+
+If you are building Erlang/OTP from git, you will need to run this
+to generate the configure scripts.
+
+ $ ./otp_build autoconf
+
+
+Use the following commands when compiling a 64-bit version.
+
+ $ export NDK_ABI_PLAT=android21 # When targeting Android 5.0 Lollipop
+
+
+ $ # Either without OpenSSL support:
+ $
+ $ ./otp_build configure \
+ --xcomp-conf=./xcomp/erl-xcomp-arm64-android.conf \
+ --without-ssl
+
+
+ $ # Or with OpenSSL linked statically:
+ $
+ $ cd /path/to/OpenSSL/source/dir/built/for/android-arm64
+ $ # First follow the NOTES.ANDROID build instructions from OpenSSL
+ $
+ $ # Then to avoid the full installation of this cross-compiled build,
+ $ # manually create a 'lib' directory at the root of the OpenSSL directory
+ $ # (at the same level as 'include') and link 'libcrypto.a' inside it.
+ $
+ $ mkdir lib
+ $ ln -s ../libcrypto.a lib/libcrypto.a
+ $ cd - # Return to the Erlang/OTP directory
+ $
+ $ # This previous step is needed for the OpenSSL static linking to work as
+ $ # the --with-ssl option expects a path with both the 'lib' and 'include'
+ $ # directories. Otherwise the Erlang/OTP build will fallback to dynamic
+ $ # linking if it doesn't find 'libcrypto.a' in its expected location.
+ $ ./otp_build configure \
+ --xcomp-conf=./xcomp/erl-xcomp-arm64-android.conf \
+ --with-ssl=/path/to/OpenSSL/source/dir/built/for/android-arm64 \
+ --disable-dynamic-ssl-lib
+
+
+Use the following commands instead when compiling a 32-bit version.
-### Configure OTP ###
+ $ export NDK_ABI_PLAT=androideabi16 # When targeting Android 4.1 Jelly Bean
-./otp_build configure \
- --xcomp-conf=./xcomp/erl-xcomp-arm-android.conf \
- --without-ssl
-### Compile OTP ###
+ $ # Either without OpenSSL support:
+ $
+ $ ./otp_build configure \
+ --xcomp-conf=./xcomp/erl-xcomp-arm-android.conf \
+ --without-ssl
+
+
+ $ # Or with OpenSSL linked statically:
+ $
+ $ cd /path/to/OpenSSL/source/dir/built/for/android-arm
+ $ # First follow the NOTES.ANDROID build instructions from OpenSSL
+ $
+ $ # Then to avoid the full installation of this cross-compiled build,
+ $ # manually create a 'lib' directory at the root of the OpenSSL directory
+ $ # (at the same level as 'include') and link 'libcrypto.a' inside it.
+ $
+ $ mkdir lib
+ $ ln -s ../libcrypto.a lib/libcrypto.a
+ $ cd - # Return to the Erlang/OTP directory
+ $
+ $ # This previous step is needed for the OpenSSL static linking to work as
+ $ # the --with-ssl option expects a path with both the 'lib' and 'include'
+ $ # directories. Otherwise the Erlang/OTP build will fallback to dynamic
+ $ # linking if it doesn't find 'libcrypto.a' in its expected location.
+ $ ./otp_build configure \
+ --xcomp-conf=./xcomp/erl-xcomp-arm-android.conf \
+ --with-ssl=/path/to/OpenSSL/source/dir/built/for/android-arm \
+ --disable-dynamic-ssl-lib
+
+
+### Compile Erlang/OTP ###
+
+ $ make noboot [-j4]
+ or
+ $ make [-j4]
-make noboot [-j4]
### Make Release ###
-./otp_build release -a /usr/local/otp_R16B03_arm
+ $ make RELEASE_ROOT=/path/to/release/erlang_23.0_arm release
+
+
+### Target Deployment for Rasberry Pi ###
+
+Make a tarball out of /path/to/release/erlang_23.0_arm and copy it to target
+device. Extract it and install.
-### Target Deployment ###
+ $ ./Install /usr/local/erlang_23.0_arm
-Make a tarball out of /usr/local/otp_R16B03_arm and copy it to target device
-(e.g. Raspberry Pi). Extract it and install
-./Install /usr/local/otp_R16B03_arm
+### Target Deployment for Android testing ###
-Android SDK (adb tool) is used to deploy OTP/Erlang to target device for
-evaluation purpose only.
+The adb tool from the Android SDK can be used to deploy Erlang/OTP to a target
+Android device, for testing purpose mainly, as the /data/local/tmp path used
+for installation below is executable only from the adb shell command, but not
+from other local applications due to Android sandbox security model.
+
+ $ cd /path/to/release/erlang_23.0_arm
+ $ # For testing purpose, configure the Erlang/OTP scripts to use the target
+ $ # installation path in /data/local/tmp which is executable from adb shell
+ $ ./Install -cross -minimal /data/local/tmp/erlang_23.0
+
+To properly integrate into an Android application, the installation would have
+to target /data/data/[your/app/package/name]/files/[erlang/dir/once/unpacked]
+as shown in https://github.com/JeromeDeBretagne/erlanglauncher as an example.
+
+WARNING: adb has issues with symlinks (and java.util.zip too). There is only
+one symlink for epmd in recent Erlang/OTP releases (20 to master-based 23) so
+it has to be removed before using adb push, and then recreated manually on the
+target device itself if needed (or epmd can simply be duplicated instead).
+
+ $ # Make sure that the epmd symlink is not present before adb push
+ $ rm bin/epmd
+ $ cp erts-X.Y.Z/bin/epmd bin/epmd
+ $ cd ..
+ $ # The release can now be deployed in the pre-configured target directory
+ $ adb push erlang_23.0_arm /data/local/tmp/erlang_23.0
+
+Start an interactive shell onto the target Android device, and launch erl.
+
+ $ adb shell
+ :/ $ /data/local/tmp/erlang_23.0/bin/erl
+ Eshel VX.Y.Z (abort with ^G)
+ 1> q().
+ ok
+ 2> :/ $ # Erlang/OTP is running on Android, congratulations! :-)
-adb push /usr/local/otp_R16B03_arm /mnt/sdcard/otp_R16B03_arm
-adb shell
### Known Issues ###
- * native inet:gethostbyname/1 return {error, nxdomain} on Raspberry PI. Use dns resolver to by-pass the issue (see http://www.erlang.org/doc/apps/erts/inet_cfg.html)
+ * native inet:gethostbyname/1 return {error, nxdomain} on Raspberry PI.
+ Use dns resolver to by-pass the issue (see
+ http://www.erlang.org/doc/apps/erts/inet_cfg.html)
+
### References ###
diff --git a/HOWTO/INSTALL-WIN32-OLD.md b/HOWTO/INSTALL-WIN32-OLD.md
new file mode 100644
index 0000000000..00dc502a94
--- /dev/null
+++ b/HOWTO/INSTALL-WIN32-OLD.md
@@ -0,0 +1,898 @@
+How to Build Erlang/OTP on Windows
+==================================
+
+Introduction
+------------
+
+This HOWTO describes how to build from Cygwin, MSYS or MSYS2 environments,
+we currently use WSL to build and test Erlang/OTP so the instructions
+herein is no longer tested/used by the OTP team.
+
+Using Cygwin, MSYS or MSYS2 as a build environment will be removed in
+the future.
+
+This section describes how to build the Erlang emulator and the OTP
+libraries on Windows. Note that the Windows binary releases are still
+a preferred alternative if one does not have Microsoft’s development
+tools and/or don’t want to install Cygwin, MSYS or MSYS2.
+
+The instructions apply to versions of Windows supporting the Cygwin
+emulated gnuish environment or the MSYS or MSYS2 ditto. We’ve built on
+the following platforms: Windows 2012, Windows 7, Windows 8 and Windows 10.
+It’s probably possible to build on older platforms too, but you might
+not be able to install the appropriate Microsoft SDK, Visual Studio or
+OpenSSL, in which case you will need to go back to earlier compilers etc.
+
+The procedure described uses either Cygwin, MSYS or MSYS2 as a build
+environment. You run the bash shell in Cygwin/MSYS/MSYS2 and use the gnu
+make/configure/autoconf etc to do the build. The emulator C-source code
+is, however, mostly compiled with Microsoft Visual C++™, producing a
+native Windows binary. This is the same procedure as we use to build the
+pre-built binaries. Why we use VC++ and not gcc is explained further in
+the FAQ section.
+
+If you are not familiar with Cygwin, MSYS, MSYS2 or a Unix environment,
+you’ll probably need to read up a bit on how that works. There are plenty of
+documentation about this online.
+
+These instructions apply for both 32-bit and 64-bit Windows. Note that even
+if you build a 64-bit version of Erlang, most of the directories and files
+involved are still named win32. Some occurances of the name win64 are
+however present. The installation file for a 64-bit Windows version of
+Erlang, for example, is `otp_win64_%OTP-REL%.exe`.
+
+If you feel comfortable with the environment and build
+system, and have all the necessary tools, you have a great opportunity
+to make the Erlang/OTP distribution for Windows better. Please submit
+any suggestions to our [JIRA] [2] and patches to our [git project] [3] to let
+them find their way into the next version of Erlang. If making changes
+to the build system (like makefiles etc) please bear in mind that the
+same makefiles are used on Unix/VxWorks, so that your changes
+don't break other platforms. That of course goes for C-code too; system
+specific code resides in the `$ERL_TOP/erts/emulator/sys/win32` and
+`$ERL_TOP/erts/etc/win32` directories mostly. The
+`$ERL_TOP/erts/emulator/beam` directory is for common code.
+
+We've used this build procedure for a couple of
+releases, and it has worked fine for us. Still, there might be all
+sorts of troubles on different machines and with different
+setups. We'll try to give hints wherever we've encountered difficulties,
+but please share your experiences by using the [erlang-questions] [1]
+mailing list. We cannot, of course, help everyone with all
+their issues, so please try to solve such issues and submit
+solutions/workarounds.
+
+Lets go then! We’ll start with a short version of the setup procedure,
+followed by some FAQ, and then we’ll go into more details of the setup.
+
+
+Short Version
+-------------
+
+In the following sections, we've described as much as we could about the
+installation of the tools needed. Once the tools are installed, building
+is quite easy. We have also tried to make these instructions understandable
+for people with limited Unix experience. Cygwin/MSYS/MSYS2 is a whole new
+environment to some Windows users, why careful explanation of environment
+variables etc seemed to be in place.
+
+This is the short story though, for the experienced and impatient:
+
+ * Get and install complete Cygwin (latest), complete MinGW with MSYS or
+ complete MSYS2
+
+ * Install Visual Studio 12.0 (2013)
+
+ * Install Microsofts Windows SDK 8.1
+
+ * Get and install Sun's JDK 1.6.0 or later
+
+ * Get and install NSIS 2.01 or later (up to 2.46 tried and working)
+
+ * Get, build and install OpenSSL 0.9.8r or later (up to 1.0.2d
+ tried & working) with static libs.
+
+ * Get the Erlang source distribution (from
+ <http://www.erlang.org/download.html>) and unpack with
+ Cygwin's/MSYS's/MSYS2's `tar`.
+
+ * Set `ERL_TOP` to where you unpacked the source distribution
+
+ * `$ cd $ERL_TOP`
+
+ * Modify PATH and other environment variables so that all these tools
+ are runnable from a bash shell. Still standing in `$ERL_TOP`, issue
+ the following commands (for 32-bit Windows, remove the x64 from the
+ first row and change `otp_win64_%OTP-REL%` to `otp_win32_%OTP-REL%` on
+ the last row):
+
+ $ eval `./otp_build env_win32 x64`
+ $ ./otp_build autoconf
+ $ ./otp_build configure
+ $ ./otp_build boot -a
+ $ ./otp_build release -a
+ $ ./otp_build installer_win32
+ $ release/win32/otp_win64_%OTP-REL% /S
+
+ Voila! `Start->Programs->Erlang OTP %OTP-REL%->Erlang` starts the Erlang
+ Windows shell.
+
+
+Frequently Asked Questions
+--------------------------
+
+* Q: So, now I can build Erlang using GCC on Windows?
+
+ A: No, unfortunately not. You'll need Microsoft's Visual C++
+ still. A Bourne-shell script (cc.sh) wraps the Visual C++ compiler
+ and runs it from within the Cygwin environment. All other tools
+ needed to build Erlang are free-ware/open source, but not the C
+ compiler. The Windows SDK is however enough to build Erlang, you
+ do not need to buy Visual C++, just download the SDK (SDK version
+ 8.1 == Visual studio 2013).
+
+* Q: Why haven't you got rid of VC++ then, you \*\*\*\*\*\*?
+
+ A: Well, partly because it's a good compiler - really! Actually it's
+ been possible in late R11-releases to build using mingw instead of
+ visual C++ (you might see the remnants of that in some scripts and
+ directories). Unfortunately the development of the SMP version for
+ Windows broke the mingw build and we chose to focus on the VC++ build
+ as the performance has been much better in the VC++ versions. The
+ mingw build will possibly be back, but as long as VC++ gives better
+ performance, the commercial build will be a VC++ one.
+
+* Q: OK, you need VC++, but now you've started to demand a quite recent
+ (and expensive) version of Visual Studio. Why?
+
+ A: Well, it's not expensive, it's free (as in free beer). Just
+ download and install the latest Windows SDK from Microsoft and all
+ the tools you need are there. The included debugger (WinDbg) is
+ also quite usable. That's what I used when porting Erlang to 64bit
+ Windows. Another reason to use later Microsoft compilers is
+ DLL compatibility. DLL's using a new version of the standard
+ library might not load if the VM is compiled with an old VC++
+ version. So we should aim to use the latest freely available SDK
+ and compiler.
+
+* Q: Can/will I build a Cygwin binary with the procedure you describe?
+
+ A: No, the result will be a pure Windows binary, and as far as I know,
+ it's not possible to make a Cygwin binary yet. That is of course
+ something desirable, but there are still some problems with the
+ dynamic linking (dynamic Erlang driver loading) as well as the TCP/IP
+ emulation in Cygwin, which, I'm sure of, will improve, but still has
+ some problems. Fixing those problems might be easy or might be hard.
+ I suggest you try yourself and share your experience. No one would be
+ happier if a simple `./configure && make` would produce a fully fledged
+ Cygwin binary.
+
+* Q: Hah, I saw you, you used GCC even though you said you didn't!
+
+ A: OK, I admit, one of the files is compiled using Cygwin's or
+ MinGW's GCC and the resulting object code is then converted to MS
+ VC++ compatible coff using a small C hack. It's because that
+ particular file, `beam_emu.c` benefits immensely from being able
+ to use the GCC labels-as-values extension, which boosts emulator
+ performance by up to 50%. That does unfortunately not (yet) mean
+ that all of OTP could be compiled using GCC. That particular
+ source code does not do anything system specific and actually is
+ adopted to the fact that GCC is used to compile it on Windows.
+
+* Q: So now there's a MS VC++ project file somewhere and I can build OTP
+ using the nifty VC++ GUI?
+
+ A: No, never. The hassle of keeping the project files up to date and
+ do all the steps that constitute an OTP build from within the VC++ GUI
+ is simply not worth it, maybe even impossible. A VC++ project
+ file for Erlang/OTP will never happen.
+
+* Q: So how does it all work then?
+
+ A: Cygwin, MSYS or MSYS2 is the environment, which closely resembles the
+ environment found on any Unix machine. It's almost like you had a
+ virtual Unix machine inside Windows. Configure, given certain
+ parameters, then creates makefiles that are used by the
+ environment's gnu-make to built the system. Most of the actual
+ compilers etc are not, however, Cygwin/MSYS/MSYS2 tools, so we've written
+ a couple of wrappers (Bourne-shell scripts), which reside in
+ `$ERL_TOP/etc/win32/cygwin_tools` and
+ `$ERL_TOP/etc/win32/msys_tools`. They all do conversion of
+ parameters and switches common in the Unix environment to fit the
+ native Windows tools. Most notable is of course the paths, which
+ in Cygwin/MSYS/MSYS2 are Unix-like paths with "forward slashes" (/) and
+ no drive letters. The Cygwin specific command `cygpath` is used
+ for most of the path conversions in a Cygwin environment. Other
+ tools are used (when needed) in the corresponding MSYS and MSYS2
+ environment. Luckily most compilers accept forward slashes instead
+ of backslashes as path separators, but one still have to get the drive
+ letters etc right, though. The wrapper scripts are not general in
+ the sense that, for example, cc.sh would understand and translate
+ every possible gcc option and pass correct options to
+ cl.exe. The principle is that the scripts are powerful enough to
+ allow building of Erlang/OTP, no more, no less. They might need
+ extensions to cope with changes during the development of Erlang, and
+ that's one of the reasons we made them into shell-scripts and not
+ Perl-scripts. We believe they are easier to understand and change
+ that way.
+
+ In `$ERL_TOP`, there is a script called `otp_build`. That script handles
+ the hassle of giving all the right parameters to `configure`/`make` and
+ also helps you set up the correct environment variables to work with
+ the Erlang source under Cygwin/MSYS/MSYS2.
+
+* Q: You use and need Cygwin, but then you haven't taken the time to
+ port Erlang to the Cygwin environment but instead focus on your
+ commercial release, is that really ethical?
+
+ A: No, not really, but see this as a step in the right direction.
+
+* Q: Can I build something that looks exactly as the commercial release?
+
+ A: Yes, we use the exact same build procedure.
+
+* Q: Which version of Cygwin/MSYS/MSYS2 and other tools do you use then?
+
+ A: For Cygwin, MSYS and MSYS2 alike, we try to use the latest releases
+ available when building. What versions you use shouldn't really
+ matter. We try to include workarounds for the bugs we've found in
+ different Cygwin/MSYS/MSYS2 releases. Please help us add workarounds
+ for new Cygwin/MSYS/MSYS2-related bugs as soon as you encounter
+ them. Also please do submit bug reports to the appropriate Cygwin, MSYS
+ and/or MSYS2 developers. The GCC we used for %OTP-REL% was version
+ 4.8.1 (MinGW 32bit) and 4.8.5 (MSYS2 64bit). We used VC++ 12.0
+ (i.e. Visual studio 2013), Sun's JDK 1.6.0\_45 (32bit) and Sun's
+ JDK 1.7.0\_1 (64bit), NSIS 2.46, and Win32 OpenSSL 1.0.2d. Please
+ read the next section for details on what you need.
+
+* Q: Can you help me setup X in Cygwin/MSYS/MSYS2?
+
+ A: No, unfortunately we haven't got time to help with Cygwin/MSYS/MSYS2
+ related user problems, please read related websites, newsgroups and
+ mailing lists.
+
+
+Tools you Need and Their Environment
+------------------------------------
+
+You need some tools to be able to build Erlang/OTP on Windows. Most
+notably you'll need Cygwin, MSYS or MSYS2, Visual Studio and Microsofts
+Windows SDK, but you might also want a Java compiler, the NSIS install
+system and OpenSSL. Well, here's some information about the different
+tools:
+
+* Cygwin, the very latest is usually best. Get all the development
+ tools and of course all the basic ditto. Make sure to get jar and
+ also make sure *not* to install a Cygwin'ish Java, since the Cygwin
+ jar command is used but Sun's Java compiler and virtual machine.
+
+ If you are going to build a 64bit Windows version, you should make
+ sure to get MinGW's 64bit gcc installed with Cygwin. It's in one of
+ the development packages.
+
+ URL: <http://www.cygwin.com>
+
+ Get the installer from the website and use it to install
+ Cygwin. Be sure to have fair privileges. If you're on an NT domain you
+ should consider running `mkpasswd -d` and `mkgroup -d` after the
+ installation to get the user databases correct. See their respective
+ manual pages.
+
+ When you start your first bash shell, you will get an awful prompt. You
+ might also have a `PATH` environment variable that contains backslashes
+ and such. Edit `$HOME/.profile` and `$HOME/.bashrc` to set fair prompts
+ and a correct PATH. Also do an `export SHELL` in `.profile`. For some
+ non-obvious reason the environment variable `$SHELL` is not exported in
+ bash. Also note that `.profile` is run at login time and `.bashrc` when
+ sub shells are created. You'll need to explicitly source `.bashrc` from
+ `.profile` if you want the commands there to be run at login time (like
+ setting up aliases, shell functions and the like). You can for example
+ do like this at the end of `.profile`:
+
+ ENV=$HOME/.bashrc
+ export ENV
+ . $ENV
+
+ You might also want to setup X-windows (XFree86). That might be as easy
+ as running startx from the command prompt and it might be much harder.
+ Use Google to find help.
+
+ If you don't use X-windows, you might want to setup the Windows
+ console window by selecting properties in the console system menu
+ (upper left corner of the window, the Cygwin icon in the title
+ bar). Especially setting a larger screen buffer size (lines) is useful
+ as it gets you a scrollbar so you can see whatever error messages
+ that might appear.
+
+ There are a few other shells available, but in all examples below we assume
+ that you use bash.
+
+* Alternatively you download MinGW and MSYS. You'll find the latest
+ installer at:
+
+ URL: <http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/>
+
+ Make sure to install the basic dev tools, but avoid the MinGW autoconf and
+ install the msys one instead.
+
+ To be able to build the 64bit VM, you will also need the 64bit
+ MinGW compiler from:
+
+ URL: <http://sourceforge.net/projects/mingw-w64/files/latest/download?source=files>
+
+ We've tried up to 1.0, but the latest version should do. Make sure you
+ download the `mingw-w64-bin_i686-mingw_<something>.zip`, not a linux
+ version. You unzip the package on top of your MinGW installation
+ (`c:\MinGW`) and that's it.
+
+* A third alternative is to download and install MSYS2 from:
+
+ URL: <https://msys2.github.io/>
+
+ When you've followed the instructions there, you also need to install
+ these packages: autoconf, make, perl, and tar. You do so by running
+ the following in the msys console:
+
+ pacman -S msys/autoconf msys/make msys/perl msys/tar
+
+ You also need a gcc. If you installed the 64 bit MSYS2 you run:
+
+ mingw64/mingw-w64-x86_64-gcc
+
+ And for 32 bit MSYS2:
+
+ pacman -S mingw32/mingw-w64-i686-gcc
+ pacman -S mingw-w64-i686-editrights
+
+* Visual Studio 2013 (Visual Studio 12.0). Download and run the web
+ installer from:
+
+ https://www.visualstudio.com/
+
+* Microsofts Windows SDK version 8.1 (corresponding to VC++ 12.0 and
+ Visual Studio 2013). You'll find it here:
+
+ URL: <https://msdn.microsoft.com/en-us/windows/desktop/bg162891.aspx>
+
+* To help setup the environment, there is a bat file,
+ `%PROGRAMFILES%\Mirosoft Visual Studio 12.0\VC\vcvarsall.bat`,
+ that set's the appropriate
+ environment for a Windows command prompt. This is not appropriate
+ for bash, so you'll need to convert it to bash-style environments
+ by editing your `.bash_profile`. In my case, where the SDK is
+ installed in the default directory and `%PROGRAMFILES%` is
+ `C:\Program Files`, the commands for setting up a 32bit build
+ environment (on a 64bit or 32bit machine) look like this (in Cygwin):
+
+ # Some common paths
+ C_DRV=/cygdrive/c
+ PRG_FLS=$C_DRV/Program\ Files
+
+ # nsis
+ NSIS_BIN=$PRG_FLS/NSIS
+ # java
+ JAVA_BIN=$PROGRAMFILES/Java/jdk1.7.0_02/bin
+
+ ##
+ ## MS SDK
+ ##
+
+ CYGWIN=nowinsymlinks
+
+ VISUAL_STUDIO_ROOT=$PRG_FLS/Microsoft\ Visual\ Studio\ 12.0
+ WIN_VISUAL_STUDIO_ROOT="C:\\Program Files\\Microsoft Visual Studio 12.0"
+ SDK=$PRG_FLS/Windows\ Kits/8.1
+ WIN_SDK="C:\\Program Files\\Windows Kits\\8.1"
+
+ PATH="$NSIS_BIN:\
+ $VISUAL_STUDIO_ROOT/VC/bin:\
+ $VISUAL_STUDIO_ROOT/VC/vcpackages:\
+ $VISUAL_STUDIO_ROOT/Common7/IDE:\
+ $VISUAL_STUDIO_ROOT/Common7/Tools:\
+ $SDK/bin/x86
+ /usr/local/bin:/usr/bin:/bin:\
+ /cygdrive/c/WINDOWS/system32:/cygdrive/c/WINDOWS:\
+ /cygdrive/c/WINDOWS/system32/Wbem:\
+ $JAVA_BIN"
+
+ LIBPATH="$WIN_VISUAL_STUDIO_ROOT\\VC\\lib"
+
+ LIB="$WIN_VISUAL_STUDIO_ROOT\\VC\\lib\\;$WIN_SDK\\lib\\winv6.3\\um\\x86"
+
+ INCLUDE="$WIN_VISUAL_STUDIO_ROOT\\VC\\include\\;$WIN_SDK\\include\\shared\\;\
+ $WIN_SDK\\include\\um;$WIN_SDK\\include\\winrt\\;$WIN_SDK\\include\\um\\gl"
+
+ export CYGWIN PATH LIBPATH LIB INCLUDE
+
+ If you're using MinGW's MSYS instead, you need to change the `C_DRV` setting,
+ which would read:
+
+ C_DRV=/c
+
+ and you also need to change the PATH environment variable to:
+
+ MINGW_BIN=/c/MinGW/bin
+
+
+ PATH="$NSIS_BIN:\
+ $VISUAL_STUDIO_ROOT/VC/bin:\
+ $VISUAL_STUDIO_ROOT/VC/vcpackages:\
+ $VISUAL_STUDIO_ROOT/Common7/IDE:\
+ $VISUAL_STUDIO_ROOT/Common7/Tools:\
+ $SDK/bin/x86:/usr/local/bin:\
+ $MINGW_BIN:\
+ /bin:/c/Windows/system32:/c/Windows:\
+ /c/Windows/System32/Wbem:\
+ $JAVA_BIN"
+
+ For MSYS2 you use the same `C_DRV` and PATH as for MSYS, only update the `MINGW_BIN`:
+
+ MINGW_BIN=/mingw32/bin
+
+
+ If you are building a 64 bit version of Erlang, you should set up
+ PATHs etc a little differently. We have two templates to make things
+ work in both Cygwin and MSYS but needs editing to work with MSYS2 (see the
+ comments in the script).
+ The following one is for 32 bits:
+
+ make_winpath()
+ {
+ P=$1
+ if [ "$IN_CYGWIN" = "true" ]; then
+ cygpath -d "$P"
+ else
+ (cd "$P" && /bin/cmd //C "for %i in (".") do @echo %~fsi")
+ fi
+ }
+
+ make_upath()
+ {
+ P=$1
+ if [ "$IN_CYGWIN" = "true" ]; then
+ cygpath "$P"
+ else
+ echo "$P" | /bin/sed 's,^\([a-zA-Z]\):\\,/\L\1/,;s,\\,/,g'
+ fi
+ }
+
+ # Some common paths
+ if [ -x /usr/bin/msys-?.0.dll ]; then
+ # Without this the path conversion won't work
+ COMSPEC='C:\Windows\System32\cmd.exe'
+ MSYSTEM=MINGW32 # Comment out this line if in MSYS2
+ export MSYSTEM COMSPEC
+ # For MSYS2: Change /mingw/bin to the msys bin dir on the line below
+ PATH=/usr/local/bin:/mingw/bin:/bin:/c/Windows/system32:\
+ /c/Windows:/c/Windows/System32/Wbem
+ C_DRV=/c
+ IN_CYGWIN=false
+ else
+ PATH=/ldisk/overrides:/usr/local/bin:/usr/bin:/bin:\
+ /usr/X11R6/bin:/cygdrive/c/windows/system32:\
+ /cygdrive/c/windows:/cygdrive/c/windows/system32/Wbem
+ C_DRV=/cygdrive/c
+ IN_CYGWIN=true
+ fi
+
+ obe_otp_gcc_vsn_map="
+ .*=>default
+ "
+ obe_otp_64_gcc_vsn_map="
+ .*=>default
+ "
+ # Program Files
+ PRG_FLS=$C_DRV/Program\ Files
+
+ # Visual Studio
+ VISUAL_STUDIO_ROOT=$PRG_FLS/Microsoft\ Visual\ Studio\ 12.0
+ WIN_VISUAL_STUDIO_ROOT="C:\\Program Files\\Microsoft Visual Studio 12.0"
+
+ # SDK
+ SDK=$PRG_FLS/Windows\ Kits/8.1
+ WIN_SDK="C:\\Program Files\\Windows Kits\\8.1"
+
+ # NSIS
+ NSIS_BIN=$PROGRAMFILES/NSIS
+
+ # Java
+ JAVA_BIN=$PROGRAMFILES/Java/jdk1.7.0_02/bin
+
+ ## The PATH variable should be Cygwin'ish
+ VCPATH=
+ $VISUAL_STUDIO_ROOT/VC/bin:\
+ $VISUAL_STUDIO_ROOT/VC/vcpackages:\
+ $VISUAL_STUDIO_ROOT/Common7/IDE:\
+ $VISUAL_STUDIO_ROOT/Common7/Tools:\
+ $SDK/bin/x86
+
+ ## Microsoft SDK libs
+ LIBPATH=$WIN_VISUAL_STUDIO_ROOT\\VC\\lib
+
+ LIB=$WIN_VISUAL_STUDIO_ROOT\\VC\\lib\\;$WIN_KITS\\lib\\winv6.3\\um\\x86
+
+ INCLUDE=$WIN_VISUAL_STUDIO_ROOT\\VC\\include\\;\
+ $WIN_KITS\\include\\shared\\;$WIN_KITS\\include\\um;\
+ $WIN_KITS\\include\\winrt\\;$WIN_KITS\\include\\um\\gl
+
+ # Put nsis, c compiler and java in path
+ export PATH=$VCPATH:$PATH:$JAVA_BIN:$NSIS_BIN
+
+ # Make sure LIB and INCLUDE is available for others
+ export LIBPATH LIB INCLUDE
+
+
+
+ The first part of the 64 bit template is identical to the 32 bit one,
+ but there are some environment variable differences:
+
+ # Program Files
+ PRG_FLS64=$C_DRV/Program\ Files
+ PRG_FLS32=$C_DRV/Program\ Files\ \(x86\)
+
+ # Visual Studio
+ VISUAL_STUDIO_ROOT=$PRG_FLS32/Microsoft\ Visual\ Studio\ 12.0
+ WIN_VISUAL_STUDIO_ROOT="C:\\Program Files (x86)\\Microsoft Visual Studio 12.0"
+
+ # SDK
+ SDK=$PRG_FLS32/Windows\ Kits/8.1
+ WIN_SDK="C:\\Program Files (x86)\\Windows Kits\\8.1"
+
+ # NSIS
+ NSIS_BIN=$PROGRAMFILES/NSIS
+ # Java
+ JAVA_BIN=$PROGRAMFILES/Java/jdk1.7.0_02/bin
+
+ ## The PATH variable should be Cygwin'ish
+ VCPATH=
+ $VISUAL_STUDIO_ROOT/VC/bin/amd64:\
+ $VISUAL_STUDIO_ROOT/VC/vcpackages:\
+ $VISUAL_STUDIO_ROOT/Common7/IDE:\
+ $VISUAL_STUDIO_ROOT/Common7/Tools:\
+ $SDK/bin/x86
+
+ ## Microsoft SDK libs
+ LIBPATH=$WIN_VISUAL_STUDIO_ROOT\\VC\\lib\\amd64
+
+ LIB=$WIN_VISUAL_STUDIO_ROOT\\VC\\lib\\amd64\\;\
+ $WIN_KITS\\lib\\winv6.3\\um\\x64
+
+ INCLUDE=$WIN_VISUAL_STUDIO_ROOT\\VC\\include\\;\
+ $WIN_KITS\\include\\shared\\;$WIN_KITS\\include\\um;\
+ $WIN_KITS\\include\\winrt\\;$WIN_KITS\\include\\um\\gl
+
+ # Put nsis, c compiler and java in path
+ export PATH=$VCPATH:$PATH:$JAVA_BIN:$NSIS_BIN
+
+ # Make sure LIB and INCLUDE is available for others
+ export LIBPATH LIB INCLUDE
+
+
+ Make sure to set the PATH so that NSIS and Microsoft SDK is found
+ before the MSYS/Cygwin tools and that Java is last in the PATH.
+
+ Make a simple hello world and try to compile it with the `cl`
+ command from within bash. If that does not work, your environment
+ needs fixing. Remember, there should be
+ no backslashes in your path environment variable in Cygwin bash,
+ but LIB and INCLUDE should contain Windows style paths with
+ semicolon, drive letters and backslashes.
+
+* Sun's Java JDK 1.6.0 or later. Our Java code (jinterface, ic) is
+ written for JDK 1.6.0. Get it for Windows and install it, the JRE is
+ not enough. If you don't care about Java, you can skip this step. The
+ result will be that jinterface is not built.
+
+ URL: <http://java.sun.com>
+
+ Add javac *LAST* to your path environment in bash, in my case this means:
+
+ `PATH="$PATH:/cygdrive/c/Program Files/Java/jdk1.7.0_02/bin"`
+
+ No `CLASSPATH` or anything is needed. Type `javac` in the bash prompt
+ and you should get a list of available Java options. Make sure, e.g by
+ typing `type java`, that you use the Java you installed. Note however that
+ Cygwin's/MinGW's/MSYS2's `jar.exe` is used. That's why the JDK bin-directory should be
+ added last in the `PATH`.
+
+* Nullsoft NSIS installer system. You need this to build the self
+ installing package. It's a free open source installer that's much
+ nicer to use than the commercial Wise and Install shield
+ installers. This is the installer we use for commercial releases as
+ well.
+
+ URL: <http://nsis.sourceforge.net/download>
+
+ Install the lot, especially the modern user interface components, as
+ it's definitely needed. Put `makensis` in your path, in my case:
+
+ PATH=/cygdrive/c/Program\ Files/NSIS:$PATH
+
+ Type makensis at the bash prompt and you should get a list of options
+ if everything is OK.
+
+* OpenSSL. This is if you want the SSL and crypto applications to
+ compile (and run). There are prebuilt binaries, which you can just
+ download and install, available here:
+
+ URL: <http://openssl.org/community/binaries.html>
+
+ We would recommend using 1.0.2d.
+
+* Building with wxWidgets. Download wxWidgets-3.0.3 or higher.
+
+ Install or unpack it to the pgm folder:
+ Cygwin:
+ `DRIVE:/PATH/cygwin/opt/local/pgm`
+ MSYS:
+ `DRIVE:/PATH/MinGW/msys/1.0/opt/local/pgm`
+ MSYS2:
+ `DRIVE:/PATH/msys<32/64>/opt/local/pgm`
+
+ If the `wxUSE_POSTSCRIPT` isn't enabled in `<path\to\pgm>\wxMSW-3.0.3\include\wx\msw\setup.h`,
+ enable it.
+
+ build: From a command prompt with the VC tools available (See the
+ instructions for OpenSSL build above for help on starting the
+ proper command prompt in RELEASE mode):
+
+ C:\...\> cd <path\to\pgm>\wxMSW-3.0.3\build\msw
+ C:\...\> nmake BUILD=release SHARED=0 DIR_SUFFIX_CPU= -f makefile.vc
+
+ Or - if building a 64bit version:
+
+ C:\...\> cd <path\to\pgm>\wxMSW-3.0.3\build\msw
+ C:\...\> nmake TARGET_CPU=amd64 BUILD=release SHARED=0 DIR_SUFFIX_CPU= -f makefile.vc
+
+* Get the Erlang source distribution (from <http://www.erlang.org/download.html>).
+ The same as for Unix platforms. Preferably use tar from within Cygwin, MSYS or MSYS2 to
+ unpack the source tar.gz (`tar zxf otp_src_%OTP-REL%.tar.gz`).
+
+ Set the environment `ERL_TOP` to point to the root directory of the
+ source distribution. Let's say I stood in `$HOME/src` and unpacked
+ `otp_src_%OTP-REL%.tar.gz`, I then add the following to `.profile`:
+
+ ERL_TOP=$HOME/src/otp_src_%OTP-REL%
+ export $ERL_TOP
+
+
+The Shell Environment
+---------------------
+
+So, if you have followed the instructions above, when you start a bash
+shell, you should have an INCLUDE environment with a Windows style
+path, a LIB environment variable also in Windows style, and finally a
+PATH that let's you reach cl, makensis, javac etc from the
+command prompt (use `which cl` etc to verify from bash).
+
+You should also have an `ERL_TOP` environment variable that is *Cygwin
+style*, and points to a directory containing, among other files, the
+script `otp_build`.
+
+A final massage of the environment is needed, and that is done by
+the script `$ERL_TOP/otp_build`. Start bash and do the following, note
+the "back-ticks" (\`), can be quite hard to get on some keyboards, but
+pressing the back-tick key followed by the space bar might do it...
+
+ $ cd $ERL_TOP
+ $ eval `./otp_build env_win32`
+
+If you're unable to produce back-ticks on your keyboard, you can use
+the ksh variant:
+
+ $ cd $ERL_TOP
+ $ eval $(./otp_build env_win32)
+
+If you are building a 64 bit version, you supply `otp_build` with an architecture parameter:
+
+ $ cd $ERL_TOP
+ $ eval `./otp_build env_win32 x64`
+
+
+This should do the final touch to the environment and building should
+be easy after this. You could run `./otp_build env_win32` without
+`eval` just to see what it does, and to see that the environment it
+sets seems OK. The path is cleaned of spaces if possible (using DOS
+style short names instead), the variables `OVERRIDE_TARGET`, `CC`, `CXX`,
+`AR` and `RANLIB` are set to their respective wrappers and the directories
+`$ERL_TOP/erts/etc/win32/<cygwin/msys>_tools/vc` and
+`$ERL_TOP/erts/etc/win32/<cygwin/msys>_tool` are added first in the PATH.
+
+Now you can check which erlc you have by writing `type erlc` in your shell.
+It should reside in `$ERL_TOP/erts/etc/win32/cygwin_tools`
+or `$ERL_TOP/erts/etc/win32/msys_tools`.
+
+
+Building and Installing
+-----------------------
+
+Building is easiest using the `otp_build` script:
+
+ $ ./otp_build autoconf # Ignore the warning blob about versions of autoconf
+ $ ./otp_build configure <optional configure options>
+ $ ./otp_build boot -a
+ $ ./otp_build release -a <installation directory>
+ $ ./otp_build installer_win32 <installation directory> # optional
+
+Now you will have a file called `otp_win32_%OTP-REL%.exe` or `otp_win64_%OTP-REL%.exe`
+in the `<installation directory>`, i.e. `$ERL_TOP/release/win32`.
+
+Lets get into more detail:
+
+1. `$ ./otp_build autoconf` - This step rebuilds the configure scripts
+ to work correctly in your environment. In an ideal world, this
+ would not be needed, but alas, we have encountered several
+ incompatibilities between our distributed configure scripts (generated
+ on a Linux platform) and the Cygwin/MSYS/MSYS2 environment over the
+ years. Running autoconf in Cygwin/MSYS/MSYS2 ensures that the configure
+ scripts are generated in a compatible way and that they will work well
+ in the next step.
+
+2. `$ ./otp_build configure` - This runs the newly generated configure
+ scripts with options making configure behave nicely. The target machine
+ type is plainly `win32`, so a lot of the configure-scripts recognize
+ this awkward target name and behave accordingly. The CC variable also
+ makes the compiler be `cc.sh`, which wraps MSVC++, so all configure
+ tests regarding the C compiler gets to run the right compiler. A lot of
+ the tests are not needed on Windows, but we thought it best to run the
+ whole configure anyway.
+
+3. `$ ./otp_build boot -a` - This uses the bootstrap directory (shipped
+ with the source, `$ERL_TOP/bootstrap`) to build a complete OTP
+ system. When this is done you can run erl from within the source tree;
+ just type `$ERL_TOP/bin/erl` and you whould have the prompt.
+
+4. `$ ./otp_build release -a` - Builds a commercial release tree from the
+ source tree. The default is to put it in `$ERL_TOP/release/win32`. You can
+ give any directory as parameter (Cygwin style), but it doesn't really
+ matter if you're going to build a self extracting installer too.
+
+5. `$ ./otp_build installer_win32` - Creates the self extracting installer executable.
+ The executable `otp_win32_%OTP-REL%.exe` or `otp_win64_%OTP-REL%.exe` will be placed
+ in the top directory of the release created in the previous step. If
+ no release directory is specified, the release is expected to have
+ been built to `$ERL_TOP/release/win32`, which also will be the place
+ where the installer executable will be placed. If you specified some
+ other directory for the release (i.e. `./otp_build release -a
+ /tmp/erl_release`), you're expected to give the same parameter here,
+ (i.e. `./otp_build installer_win32 /tmp/erl_release`). You need to have
+ a full NSIS installation and `makensis.exe` in your path for this to
+ work. Once you have created the installer, you can run it to
+ install Erlang/OTP in the regular way, just run the executable and
+ follow the steps in the installation wizard. To get all default settings
+ in the installation without any questions asked, you run the executable
+ with the parameter `/S` (capital S) like in:
+
+ $ cd $ERL_TOP
+ $ release/win32/otp_win32_%OTP-REL% /S
+ ...
+
+ or
+
+ $ cd $ERL_TOP
+ $ release/win32/otp_win64_%OTP-REL% /S
+ ...
+
+
+ and after a while Erlang/OTP-%OTP-REL% will have been installed in
+ `C:\Program Files\erl%ERTS-VSN%\`, with shortcuts in the menu etc.
+
+
+Development
+-----------
+
+Once the system is built, you might want to change it. Having a test
+release in some nice directory might be useful, but you can also run
+Erlang from within the source tree. The target `local_setup`, makes
+the program `$ERL_TOP/bin/erl.exe` usable and it also uses all the OTP
+libraries in the source tree.
+
+If you hack the emulator, you can build the emulator executable
+by standing in `$ERL_TOP/erts/emulator` and do a simple
+
+ $ make opt
+
+Note that you need to have run ``(cd $ERL_TOP && eval `./otp_build env_win32`)``
+in the particular shell before building anything on Windows. After
+doing a make opt you can test your result by running `$ERL_TOP/bin/erl`.
+If you want to copy the result to a release directory (say
+`/tmp/erl_release`), you do this (still in `$ERL_TOP/erts/emulator`)
+
+ $ make TESTROOT=/tmp/erl_release release
+
+That will copy the emulator executables.
+
+To make a debug build of the emulator, you need to recompile both
+`beam.dll` (the actual runtime system) and `erlexec.dll`. Do like this
+
+ $ cd $ERL_TOP
+ $ rm bin/win32/erlexec.dll
+ $ cd erts/emulator
+ $ make debug
+ $ cd ../etc
+ $ make debug
+
+and sometimes
+
+ $ cd $ERL_TOP
+ $ make local_setup
+
+So now when you run `$ERL_TOP/erl.exe`, you should have a debug compiled
+emulator, which you will see if you do a:
+
+ 1> erlang:system_info(system_version).
+
+in the erlang shell. If the returned string contains `[debug]`, you
+got a debug compiled emulator.
+
+To hack the erlang libraries, you simply do a `make opt` in the
+specific "applications" directory, like:
+
+ $ cd $ERL_TOP/lib/stdlib
+ $ make opt
+
+or even in the source directory...
+
+ $ cd $ERL_TOP/lib/stdlib/src
+ $ make opt
+
+Note that you're expected to have a fresh Erlang in your path when
+doing this, preferably the plain %OTP-REL% you have built in the previous
+steps. You could also add `$ERL_TOP/bootstrap/bin` to your `PATH` before
+rebuilding specific libraries. That would give you a good enough
+Erlang system to compile any OTP erlang code. Setting up the path
+correctly is a little bit tricky. You still need to have
+`$ERL_TOP/erts/etc/win32/cygwin_tools/vc` and
+`$ERL_TOP/erts/etc/win32/cygwin_tools` *before* the actual emulator
+in the path. A typical setting of the path for using the bootstrap
+compiler would be:
+
+ $ export PATH=$ERL_TOP/erts/etc/win32/cygwin_tools/vc\
+ :$ERL_TOP/erts/etc/win32/cygwin_tools:$ERL_TOP/bootstrap/bin:$PATH
+
+That should make it possible to rebuild any library without hassle...
+
+If you want to copy a library (an application) newly built, to a
+release area, you do like with the emulator:
+
+ $ cd $ERL_TOP/lib/stdlib
+ $ make TESTROOT=/tmp/erlang_release release
+
+Remember that:
+
+* Windows specific C-code goes in the `$ERL_TOP/erts/emulator/sys/win32`,
+ `$ERL_TOP/erts/emulator/drivers/win32` or `$ERL_TOP/erts/etc/win32`.
+
+* Windows specific erlang code should be used conditionally and the
+ host OS tested in *runtime*, the exactly same beam files should be
+ distributed for every platform! So write code like:
+
+ case os:type() of
+ {win32,_} ->
+ do_windows_specific();
+ Other ->
+ do_fallback_or_exit()
+ end,
+
+That's basically all you need to get going.
+
+
+Using GIT
+---------
+
+You might want to check out versions of the source code from GitHUB. That is possible directly in Cygwin, but not in MSYS. There is a project MsysGIT:
+
+URL:<http://code.google.com/p/msysgit/>
+
+that makes a nice Git port. The msys prompt you get from MsysGIT is
+however not compatible with the full version from MinGW, so you will
+need to check out files using MsysGIT's command prompt and then switch
+to a common MSYS command prompt for building. Also all test suites
+cannot be built as MsysGIT/MSYS does not handle symbolic links.
+
+
+ [1]: http://www.erlang.org/static/doc/mailinglist.html
+ [2]: http://bugs.erlang.org
+ [3]: https://github.com/erlang/otp
+
+ [?TOC]: true
diff --git a/HOWTO/INSTALL-WIN32.md b/HOWTO/INSTALL-WIN32.md
index 98c608060d..1ccc8656ae 100644
--- a/HOWTO/INSTALL-WIN32.md
+++ b/HOWTO/INSTALL-WIN32.md
@@ -4,40 +4,31 @@ How to Build Erlang/OTP on Windows
Introduction
------------
-This section describes how to build the Erlang emulator and the OTP
-libraries on Windows. Note that the Windows binary releases are still
-a preferred alternative if one does not have Microsoft’s development
-tools and/or don’t want to install Cygwin, MSYS or MSYS2.
-
-The instructions apply to versions of Windows supporting the Cygwin
-emulated gnuish environment or the MSYS or MSYS2 ditto. We’ve built on
-the following platforms: Windows 2012, Windows 7, Windows 8 and Windows 10.
-It’s probably possible to build on older platforms too, but you might
-not be able to install the appropriate Microsoft SDK, Visual Studio or
-OpenSSL, in which case you will need to go back to earlier compilers etc.
-
-The procedure described uses either Cygwin, MSYS or MSYS2 as a build
-environment. You run the bash shell in Cygwin/MSYS/MSYS2 and use the gnu
-make/configure/autoconf etc to do the build. The emulator C-source code
-is, however, mostly compiled with Microsoft Visual C++™, producing a
-native Windows binary. This is the same procedure as we use to build the
-pre-built binaries. Why we use VC++ and not gcc is explained further in
-the FAQ section.
-
-If you are not familiar with Cygwin, MSYS, MSYS2 or a Unix environment,
-you’ll probably need to read up a bit on how that works. There are plenty of
-documentation about this online.
-
-These instructions apply for both 32-bit and 64-bit Windows. Note that even
-if you build a 64-bit version of Erlang, most of the directories and files
-involved are still named win32. Some occurances of the name win64 are
-however present. The installation file for a 64-bit Windows version of
-Erlang, for example, is `otp_win64_%OTP-REL%.exe`.
+This section describes how to build the Erlang emulator and the OTP
+libraries on Windows. Note that the Windows binary releases are still
+a preferred alternative if one does not have Microsoft’s development
+tools and/or don’t want to install WSL.
+
+The instructions apply to Windows 10 (v.1809 and later) supporting the
+WSL.1 (Windows Subsystem for Linux v.1) and using Ubuntu 18.04 release.
+
+The procedure described uses WSL as a build environment. You run the
+bash shell in WSL and use the gnu make/configure/autoconf etc to do
+the build. The emulator C-source code is, however, mostly compiled
+with Microsoft Visual C++™, producing a native Windows binary. This is
+the same procedure as we use to build the pre-built binaries. Why we
+use VC++ and not gcc is explained further in the FAQ section.
+
+These instructions apply for both 32-bit and 64-bit Windows. Note that
+even if you build a 64-bit version of Erlang, most of the directories
+and files involved are still named win32. Some occurrences of the name
+win64 are however present. The installation file for a 64-bit Windows
+version of Erlang, for example, is `otp_win64_%OTP-REL%.exe`.
If you feel comfortable with the environment and build
system, and have all the necessary tools, you have a great opportunity
to make the Erlang/OTP distribution for Windows better. Please submit
-any suggestions to our [JIRA] [2] and patches to our [git project] [3] to let
+any suggestions to our [JIRA] [1] and patches to our [git project] [2] to let
them find their way into the next version of Erlang. If making changes
to the build system (like makefiles etc) please bear in mind that the
same makefiles are used on Unix/VxWorks, so that your changes
@@ -46,56 +37,44 @@ specific code resides in the `$ERL_TOP/erts/emulator/sys/win32` and
`$ERL_TOP/erts/etc/win32` directories mostly. The
`$ERL_TOP/erts/emulator/beam` directory is for common code.
-We've used this build procedure for a couple of
-releases, and it has worked fine for us. Still, there might be all
-sorts of troubles on different machines and with different
-setups. We'll try to give hints wherever we've encountered difficulties,
-but please share your experiences by using the [erlang-questions] [1]
-mailing list. We cannot, of course, help everyone with all
-their issues, so please try to solve such issues and submit
-solutions/workarounds.
-
-Lets go then! We’ll start with a short version of the setup procedure,
-followed by some FAQ, and then we’ll go into more details of the setup.
-
Short Version
-------------
-In the following sections, we've described as much as we could about the
-installation of the tools needed. Once the tools are installed, building
-is quite easy. We have also tried to make these instructions understandable
-for people with limited Unix experience. Cygwin/MSYS/MSYS2 is a whole new
-environment to some Windows users, why careful explanation of environment
-variables etc seemed to be in place.
+In the following sections, we've described as much as we could about
+the installation of the tools needed. Once the tools are installed,
+building is quite easy. We have also tried to make these instructions
+understandable for people with limited Unix experience. WSL is a whole
+new environment to some Windows users, why careful explanation of
+environment variables etc seemed to be in place.
This is the short story though, for the experienced and impatient:
- * Get and install complete Cygwin (latest), complete MinGW with MSYS or
- complete MSYS2
+ * Get and install complete WSL environment
- * Install Visual Studio 12.0 (2013)
+ * Install Visual Studio 2019
- * Install Microsofts Windows SDK 8.1
+ * Get and install windows JDK-8
- * Get and install Sun's JDK 1.6.0 or later
+ * Get and install windows NSIS 3.05 or later (3.05 tried and working)
- * Get and install NSIS 2.01 or later (up to 2.46 tried and working)
+ * Get, build and install OpenSSL v1.1.1d or later (up to 1.1.1d
+ tried & working) with static libs.
- * Get, build and install OpenSSL 0.9.8r or later (up to 1.0.2d
+ * Get, build and install wxWidgets-3.1.3 or later (up to 3.1.3
tried & working) with static libs.
* Get the Erlang source distribution (from
- <http://www.erlang.org/download.html>) and unpack with
- Cygwin's/MSYS's/MSYS2's `tar`.
+ <http://www.erlang.org/download.html>) and unpack with `tar`
+ to the windows disk for example to: /mnt/c/src/
- * Set `ERL_TOP` to where you unpacked the source distribution
+ * Install mingw-gcc, make and autoconf: `sudo apt install gcc-mingw-w64 make autoconf`
- * `$ cd $ERL_TOP`
+ * `$ cd UNPACK_DIR`
* Modify PATH and other environment variables so that all these tools
are runnable from a bash shell. Still standing in `$ERL_TOP`, issue
- the following commands (for 32-bit Windows, remove the x64 from the
+ the following commands (for 32-bit Windows, remove the x64 from the
first row and change `otp_win64_%OTP-REL%` to `otp_win32_%OTP-REL%` on
the last row):
@@ -111,576 +90,110 @@ This is the short story though, for the experienced and impatient:
Windows shell.
-Frequently Asked Questions
---------------------------
-
-* Q: So, now I can build Erlang using GCC on Windows?
-
- A: No, unfortunately not. You'll need Microsoft's Visual C++
- still. A Bourne-shell script (cc.sh) wraps the Visual C++ compiler
- and runs it from within the Cygwin environment. All other tools
- needed to build Erlang are free-ware/open source, but not the C
- compiler. The Windows SDK is however enough to build Erlang, you
- do not need to buy Visual C++, just download the SDK (SDK version
- 8.1 == Visual studio 2013).
-
-* Q: Why haven't you got rid of VC++ then, you \*\*\*\*\*\*?
-
- A: Well, partly because it's a good compiler - really! Actually it's
- been possible in late R11-releases to build using mingw instead of
- visual C++ (you might see the remnants of that in some scripts and
- directories). Unfortunately the development of the SMP version for
- Windows broke the mingw build and we chose to focus on the VC++ build
- as the performance has been much better in the VC++ versions. The
- mingw build will possibly be back, but as long as VC++ gives better
- performance, the commercial build will be a VC++ one.
-
-* Q: OK, you need VC++, but now you've started to demand a quite recent
- (and expensive) version of Visual Studio. Why?
-
- A: Well, it's not expensive, it's free (as in free beer). Just
- download and install the latest Windows SDK from Microsoft and all
- the tools you need are there. The included debugger (WinDbg) is
- also quite usable. That's what I used when porting Erlang to 64bit
- Windows. Another reason to use later Microsoft compilers is
- DLL compatibility. DLL's using a new version of the standard
- library might not load if the VM is compiled with an old VC++
- version. So we should aim to use the latest freely available SDK
- and compiler.
-
-* Q: Can/will I build a Cygwin binary with the procedure you describe?
-
- A: No, the result will be a pure Windows binary, and as far as I know,
- it's not possible to make a Cygwin binary yet. That is of course
- something desirable, but there are still some problems with the
- dynamic linking (dynamic Erlang driver loading) as well as the TCP/IP
- emulation in Cygwin, which, I'm sure of, will improve, but still has
- some problems. Fixing those problems might be easy or might be hard.
- I suggest you try yourself and share your experience. No one would be
- happier if a simple `./configure && make` would produce a fully fledged
- Cygwin binary.
-
-* Q: Hah, I saw you, you used GCC even though you said you didn't!
-
- A: OK, I admit, one of the files is compiled using Cygwin's or
- MinGW's GCC and the resulting object code is then converted to MS
- VC++ compatible coff using a small C hack. It's because that
- particular file, `beam_emu.c` benefits immensely from being able
- to use the GCC labels-as-values extension, which boosts emulator
- performance by up to 50%. That does unfortunately not (yet) mean
- that all of OTP could be compiled using GCC. That particular
- source code does not do anything system specific and actually is
- adopted to the fact that GCC is used to compile it on Windows.
-
-* Q: So now there's a MS VC++ project file somewhere and I can build OTP
- using the nifty VC++ GUI?
-
- A: No, never. The hassle of keeping the project files up to date and
- do all the steps that constitute an OTP build from within the VC++ GUI
- is simply not worth it, maybe even impossible. A VC++ project
- file for Erlang/OTP will never happen.
-
-* Q: So how does it all work then?
-
- A: Cygwin, MSYS or MSYS2 is the environment, which closely resembles the
- environment found on any Unix machine. It's almost like you had a
- virtual Unix machine inside Windows. Configure, given certain
- parameters, then creates makefiles that are used by the
- environment's gnu-make to built the system. Most of the actual
- compilers etc are not, however, Cygwin/MSYS/MSYS2 tools, so we've written
- a couple of wrappers (Bourne-shell scripts), which reside in
- `$ERL_TOP/etc/win32/cygwin_tools` and
- `$ERL_TOP/etc/win32/msys_tools`. They all do conversion of
- parameters and switches common in the Unix environment to fit the
- native Windows tools. Most notable is of course the paths, which
- in Cygwin/MSYS/MSYS2 are Unix-like paths with "forward slashes" (/) and
- no drive letters. The Cygwin specific command `cygpath` is used
- for most of the path conversions in a Cygwin environment. Other
- tools are used (when needed) in the corresponding MSYS and MSYS2
- environment. Luckily most compilers accept forward slashes instead
- of backslashes as path separators, but one still have to get the drive
- letters etc right, though. The wrapper scripts are not general in
- the sense that, for example, cc.sh would understand and translate
- every possible gcc option and pass correct options to
- cl.exe. The principle is that the scripts are powerful enough to
- allow building of Erlang/OTP, no more, no less. They might need
- extensions to cope with changes during the development of Erlang, and
- that's one of the reasons we made them into shell-scripts and not
- Perl-scripts. We believe they are easier to understand and change
- that way.
-
- In `$ERL_TOP`, there is a script called `otp_build`. That script handles
- the hassle of giving all the right parameters to `configure`/`make` and
- also helps you set up the correct environment variables to work with
- the Erlang source under Cygwin/MSYS/MSYS2.
-
-* Q: You use and need Cygwin, but then you haven't taken the time to
- port Erlang to the Cygwin environment but instead focus on your
- commercial release, is that really ethical?
-
- A: No, not really, but see this as a step in the right direction.
-
-* Q: Can I build something that looks exactly as the commercial release?
-
- A: Yes, we use the exact same build procedure.
-
-* Q: Which version of Cygwin/MSYS/MSYS2 and other tools do you use then?
-
- A: For Cygwin, MSYS and MSYS2 alike, we try to use the latest releases
- available when building. What versions you use shouldn't really
- matter. We try to include workarounds for the bugs we've found in
- different Cygwin/MSYS/MSYS2 releases. Please help us add workarounds
- for new Cygwin/MSYS/MSYS2-related bugs as soon as you encounter
- them. Also please do submit bug reports to the appropriate Cygwin, MSYS
- and/or MSYS2 developers. The GCC we used for %OTP-REL% was version
- 4.8.1 (MinGW 32bit) and 4.8.5 (MSYS2 64bit). We used VC++ 12.0
- (i.e. Visual studio 2013), Sun's JDK 1.6.0\_45 (32bit) and Sun's
- JDK 1.7.0\_1 (64bit), NSIS 2.46, and Win32 OpenSSL 1.0.2d. Please
- read the next section for details on what you need.
-
-* Q: Can you help me setup X in Cygwin/MSYS/MSYS2?
-
- A: No, unfortunately we haven't got time to help with Cygwin/MSYS/MSYS2
- related user problems, please read related websites, newsgroups and
- mailing lists.
-
-
Tools you Need and Their Environment
------------------------------------
You need some tools to be able to build Erlang/OTP on Windows. Most
-notably you'll need Cygwin, MSYS or MSYS2, Visual Studio and Microsofts
-Windows SDK, but you might also want a Java compiler, the NSIS install
-system and OpenSSL. Well, here's some information about the different
-tools:
-
-* Cygwin, the very latest is usually best. Get all the development
- tools and of course all the basic ditto. Make sure to get jar and
- also make sure *not* to install a Cygwin'ish Java, since the Cygwin
- jar command is used but Sun's Java compiler and virtual machine.
-
- If you are going to build a 64bit Windows version, you should make
- sure to get MinGW's 64bit gcc installed with Cygwin. It's in one of
- the development packages.
-
- URL: <http://www.cygwin.com>
-
- Get the installer from the website and use it to install
- Cygwin. Be sure to have fair privileges. If you're on an NT domain you
- should consider running `mkpasswd -d` and `mkgroup -d` after the
- installation to get the user databases correct. See their respective
- manual pages.
-
- When you start your first bash shell, you will get an awful prompt. You
- might also have a `PATH` environment variable that contains backslashes
- and such. Edit `$HOME/.profile` and `$HOME/.bashrc` to set fair prompts
- and a correct PATH. Also do an `export SHELL` in `.profile`. For some
- non-obvious reason the environment variable `$SHELL` is not exported in
- bash. Also note that `.profile` is run at login time and `.bashrc` when
- sub shells are created. You'll need to explicitly source `.bashrc` from
- `.profile` if you want the commands there to be run at login time (like
- setting up aliases, shell functions and the like). You can for example
- do like this at the end of `.profile`:
-
- ENV=$HOME/.bashrc
- export ENV
- . $ENV
-
- You might also want to setup X-windows (XFree86). That might be as easy
- as running startx from the command prompt and it might be much harder.
- Use Google to find help.
-
- If you don't use X-windows, you might want to setup the Windows
- console window by selecting properties in the console system menu
- (upper left corner of the window, the Cygwin icon in the title
- bar). Especially setting a larger screen buffer size (lines) is useful
- as it gets you a scrollbar so you can see whatever error messages
- that might appear.
-
- There are a few other shells available, but in all examples below we assume
- that you use bash.
-
-* Alternatively you download MinGW and MSYS. You'll find the latest
- installer at:
-
- URL: <http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/>
-
- Make sure to install the basic dev tools, but avoid the MinGW autoconf and
- install the msys one instead.
-
- To be able to build the 64bit VM, you will also need the 64bit
- MinGW compiler from:
-
- URL: <http://sourceforge.net/projects/mingw-w64/files/latest/download?source=files>
-
- We've tried up to 1.0, but the latest version should do. Make sure you
- download the `mingw-w64-bin_i686-mingw_<something>.zip`, not a linux
- version. You unzip the package on top of your MinGW installation
- (`c:\MinGW`) and that's it.
-
-* A third alternative is to download and install MSYS2 from:
+notably you'll need WSL (with ubuntu), Visual Studio and
+Microsofts Windows SDK, but you might also want a Java compiler, the
+NSIS install system, OpenSSL and wxWidgets. Well, here's some information about
+the different tools:
- URL: <https://msys2.github.io/>
+* WSL: Install WSL and Ubuntu in Windows 10
+ <https://docs.microsoft.com/en-us/windows/wsl/install-win10>
- When you've followed the instructions there, you also need to install
- these packages: autoconf, make, perl, and tar. You do so by running
- the following in the msys console:
+ We have used and tested with WSL-1, WSL-2 was not available and may
+ not be prefered when building Erlang/OTP since access to the windows
+ disk is (currently) slower WSL-2.
- pacman -S msys/autoconf msys/make msys/perl msys/tar
+* Visual Studio 2019
+ Download and run the installer from:
+ <http://visualstudio.microsoft.com/downloads>
+ Install C++ and SDK packages to the default installation directory.
- You also need a gcc. If you installed the 64 bit MSYS2 you run:
-
- mingw64/mingw-w64-x86_64-gcc
-
- And for 32 bit MSYS2:
-
- pacman -S mingw32/mingw-w64-i686-gcc
- pacman -S mingw-w64-i686-editrights
-
-* Visual Studio 2013 (Visual Studio 12.0). Download and run the web
- installer from:
-
- https://www.visualstudio.com/
-
-* Microsofts Windows SDK version 8.1 (corresponding to VC++ 12.0 and
- Visual Studio 2013). You'll find it here:
-
- URL: <https://msdn.microsoft.com/en-us/windows/desktop/bg162891.aspx>
-
-* To help setup the environment, there is a bat file,
- `%PROGRAMFILES%\Mirosoft Visual Studio 12.0\VC\vcvarsall.bat`,
- that set's the appropriate
- environment for a Windows command prompt. This is not appropriate
- for bash, so you'll need to convert it to bash-style environments
- by editing your `.bash_profile`. In my case, where the SDK is
- installed in the default directory and `%PROGRAMFILES%` is
- `C:\Program Files`, the commands for setting up a 32bit build
- environment (on a 64bit or 32bit machine) look like this (in Cygwin):
-
- # Some common paths
- C_DRV=/cygdrive/c
- PRG_FLS=$C_DRV/Program\ Files
-
- # nsis
- NSIS_BIN=$PRG_FLS/NSIS
- # java
- JAVA_BIN=$PROGRAMFILES/Java/jdk1.7.0_02/bin
-
- ##
- ## MS SDK
- ##
-
- CYGWIN=nowinsymlinks
-
- VISUAL_STUDIO_ROOT=$PRG_FLS/Microsoft\ Visual\ Studio\ 12.0
- WIN_VISUAL_STUDIO_ROOT="C:\\Program Files\\Microsoft Visual Studio 12.0"
- SDK=$PRG_FLS/Windows\ Kits/8.1
- WIN_SDK="C:\\Program Files\\Windows Kits\\8.1"
-
- PATH="$NSIS_BIN:\
- $VISUAL_STUDIO_ROOT/VC/bin:\
- $VISUAL_STUDIO_ROOT/VC/vcpackages:\
- $VISUAL_STUDIO_ROOT/Common7/IDE:\
- $VISUAL_STUDIO_ROOT/Common7/Tools:\
- $SDK/bin/x86
- /usr/local/bin:/usr/bin:/bin:\
- /cygdrive/c/WINDOWS/system32:/cygdrive/c/WINDOWS:\
- /cygdrive/c/WINDOWS/system32/Wbem:\
- $JAVA_BIN"
-
- LIBPATH="$WIN_VISUAL_STUDIO_ROOT\\VC\\lib"
-
- LIB="$WIN_VISUAL_STUDIO_ROOT\\VC\\lib\\;$WIN_SDK\\lib\\winv6.3\\um\\x86"
-
- INCLUDE="$WIN_VISUAL_STUDIO_ROOT\\VC\\include\\;$WIN_SDK\\include\\shared\\;\
- $WIN_SDK\\include\\um;$WIN_SDK\\include\\winrt\\;$WIN_SDK\\include\\um\\gl"
-
- export CYGWIN PATH LIBPATH LIB INCLUDE
-
- If you're using MinGW's MSYS instead, you need to change the `C_DRV` setting,
- which would read:
-
- C_DRV=/c
-
- and you also need to change the PATH environment variable to:
-
- MINGW_BIN=/c/MinGW/bin
-
-
- PATH="$NSIS_BIN:\
- $VISUAL_STUDIO_ROOT/VC/bin:\
- $VISUAL_STUDIO_ROOT/VC/vcpackages:\
- $VISUAL_STUDIO_ROOT/Common7/IDE:\
- $VISUAL_STUDIO_ROOT/Common7/Tools:\
- $SDK/bin/x86:/usr/local/bin:\
- $MINGW_BIN:\
- /bin:/c/Windows/system32:/c/Windows:\
- /c/Windows/System32/Wbem:\
- $JAVA_BIN"
-
- For MSYS2 you use the same `C_DRV` and PATH as for MSYS, only update the `MINGW_BIN`:
-
- MINGW_BIN=/mingw32/bin
-
-
- If you are building a 64 bit version of Erlang, you should set up
- PATHs etc a little differently. We have two templates to make things
- work in both Cygwin and MSYS but needs editing to work with MSYS2 (see the
- comments in the script).
- The following one is for 32 bits:
-
- make_winpath()
- {
- P=$1
- if [ "$IN_CYGWIN" = "true" ]; then
- cygpath -d "$P"
- else
- (cd "$P" && /bin/cmd //C "for %i in (".") do @echo %~fsi")
- fi
- }
-
- make_upath()
- {
- P=$1
- if [ "$IN_CYGWIN" = "true" ]; then
- cygpath "$P"
- else
- echo "$P" | /bin/sed 's,^\([a-zA-Z]\):\\,/\L\1/,;s,\\,/,g'
- fi
- }
-
- # Some common paths
- if [ -x /usr/bin/msys-?.0.dll ]; then
- # Without this the path conversion won't work
- COMSPEC='C:\Windows\System32\cmd.exe'
- MSYSTEM=MINGW32 # Comment out this line if in MSYS2
- export MSYSTEM COMSPEC
- # For MSYS2: Change /mingw/bin to the msys bin dir on the line below
- PATH=/usr/local/bin:/mingw/bin:/bin:/c/Windows/system32:\
- /c/Windows:/c/Windows/System32/Wbem
- C_DRV=/c
- IN_CYGWIN=false
- else
- PATH=/ldisk/overrides:/usr/local/bin:/usr/bin:/bin:\
- /usr/X11R6/bin:/cygdrive/c/windows/system32:\
- /cygdrive/c/windows:/cygdrive/c/windows/system32/Wbem
- C_DRV=/cygdrive/c
- IN_CYGWIN=true
- fi
-
- obe_otp_gcc_vsn_map="
- .*=>default
- "
- obe_otp_64_gcc_vsn_map="
- .*=>default
- "
- # Program Files
- PRG_FLS=$C_DRV/Program\ Files
-
- # Visual Studio
- VISUAL_STUDIO_ROOT=$PRG_FLS/Microsoft\ Visual\ Studio\ 12.0
- WIN_VISUAL_STUDIO_ROOT="C:\\Program Files\\Microsoft Visual Studio 12.0"
-
- # SDK
- SDK=$PRG_FLS/Windows\ Kits/8.1
- WIN_SDK="C:\\Program Files\\Windows Kits\\8.1"
-
- # NSIS
- NSIS_BIN=$PROGRAMFILES/NSIS
-
- # Java
- JAVA_BIN=$PROGRAMFILES/Java/jdk1.7.0_02/bin
-
- ## The PATH variable should be Cygwin'ish
- VCPATH=
- $VISUAL_STUDIO_ROOT/VC/bin:\
- $VISUAL_STUDIO_ROOT/VC/vcpackages:\
- $VISUAL_STUDIO_ROOT/Common7/IDE:\
- $VISUAL_STUDIO_ROOT/Common7/Tools:\
- $SDK/bin/x86
-
- ## Microsoft SDK libs
- LIBPATH=$WIN_VISUAL_STUDIO_ROOT\\VC\\lib
-
- LIB=$WIN_VISUAL_STUDIO_ROOT\\VC\\lib\\;$WIN_KITS\\lib\\winv6.3\\um\\x86
-
- INCLUDE=$WIN_VISUAL_STUDIO_ROOT\\VC\\include\\;\
- $WIN_KITS\\include\\shared\\;$WIN_KITS\\include\\um;\
- $WIN_KITS\\include\\winrt\\;$WIN_KITS\\include\\um\\gl
-
- # Put nsis, c compiler and java in path
- export PATH=$VCPATH:$PATH:$JAVA_BIN:$NSIS_BIN
-
- # Make sure LIB and INCLUDE is available for others
- export LIBPATH LIB INCLUDE
-
-
-
- The first part of the 64 bit template is identical to the 32 bit one,
- but there are some environment variable differences:
-
- # Program Files
- PRG_FLS64=$C_DRV/Program\ Files
- PRG_FLS32=$C_DRV/Program\ Files\ \(x86\)
-
- # Visual Studio
- VISUAL_STUDIO_ROOT=$PRG_FLS32/Microsoft\ Visual\ Studio\ 12.0
- WIN_VISUAL_STUDIO_ROOT="C:\\Program Files (x86)\\Microsoft Visual Studio 12.0"
-
- # SDK
- SDK=$PRG_FLS32/Windows\ Kits/8.1
- WIN_SDK="C:\\Program Files (x86)\\Windows Kits\\8.1"
-
- # NSIS
- NSIS_BIN=$PROGRAMFILES/NSIS
- # Java
- JAVA_BIN=$PROGRAMFILES/Java/jdk1.7.0_02/bin
-
- ## The PATH variable should be Cygwin'ish
- VCPATH=
- $VISUAL_STUDIO_ROOT/VC/bin/amd64:\
- $VISUAL_STUDIO_ROOT/VC/vcpackages:\
- $VISUAL_STUDIO_ROOT/Common7/IDE:\
- $VISUAL_STUDIO_ROOT/Common7/Tools:\
- $SDK/bin/x86
-
- ## Microsoft SDK libs
- LIBPATH=$WIN_VISUAL_STUDIO_ROOT\\VC\\lib\\amd64
-
- LIB=$WIN_VISUAL_STUDIO_ROOT\\VC\\lib\\amd64\\;\
- $WIN_KITS\\lib\\winv6.3\\um\\x64
-
- INCLUDE=$WIN_VISUAL_STUDIO_ROOT\\VC\\include\\;\
- $WIN_KITS\\include\\shared\\;$WIN_KITS\\include\\um;\
- $WIN_KITS\\include\\winrt\\;$WIN_KITS\\include\\um\\gl
-
- # Put nsis, c compiler and java in path
- export PATH=$VCPATH:$PATH:$JAVA_BIN:$NSIS_BIN
-
- # Make sure LIB and INCLUDE is available for others
- export LIBPATH LIB INCLUDE
-
-
- Make sure to set the PATH so that NSIS and Microsoft SDK is found
- before the MSYS/Cygwin tools and that Java is last in the PATH.
-
- Make a simple hello world and try to compile it with the `cl`
- command from within bash. If that does not work, your environment
- needs fixing. Remember, there should be
- no backslashes in your path environment variable in Cygwin bash,
- but LIB and INCLUDE should contain Windows style paths with
- semicolon, drive letters and backslashes.
-
-* Sun's Java JDK 1.6.0 or later. Our Java code (jinterface, ic) is
- written for JDK 1.6.0. Get it for Windows and install it, the JRE is
- not enough. If you don't care about Java, you can skip this step. The
+* Java JDK 8 or later (optional)
+ If you don't care about Java, you can skip this step. The
result will be that jinterface is not built.
- URL: <http://java.sun.com>
+ Our Java code (jinterface, ic) is tested on windows with JDK 8.
+ Get it for Windows and install it, the JRE is not enough.
+
+ URL: <http://www.oracle.com/java/technologies/javase-downloads.html>
- Add javac *LAST* to your path environment in bash, in my case this means:
+ Add javac to your path environment, in my case this means:
- `PATH="$PATH:/cygdrive/c/Program Files/Java/jdk1.7.0_02/bin"`
+ `PATH="/mnt/c/Program\ Files/Java/jdk1.8.0_241/bin:$PATH`
- No `CLASSPATH` or anything is needed. Type `javac` in the bash prompt
- and you should get a list of available Java options. Make sure, e.g by
- typing `type java`, that you use the Java you installed. Note however that
- Cygwin's/MinGW's/MSYS2's `jar.exe` is used. That's why the JDK bin-directory should be
- added last in the `PATH`.
+ No `CLASSPATH` or anything is needed. Type `javac.exe` in the bash prompt
+ and you should get a list of available Java options.
-* Nullsoft NSIS installer system. You need this to build the self
- installing package. It's a free open source installer that's much
- nicer to use than the commercial Wise and Install shield
- installers. This is the installer we use for commercial releases as
- well.
+* Nullsoft NSIS installer system (optional)
+ You need this to build the self installing package.
+ Download and run the installer from:
URL: <http://nsis.sourceforge.net/download>
- Install the lot, especially the modern user interface components, as
- it's definitely needed. Put `makensis` in your path, in my case:
+ Add 'makensis.exe' to your path environment:
- PATH=/cygdrive/c/Program\ Files/NSIS:$PATH
+ `PATH="/mnt/c/Program\ Files/NSIS/Bin:$PATH`
- Type makensis at the bash prompt and you should get a list of options
- if everything is OK.
+ Type `which makensis.exe` in the bash prompt and you should get the
+ path to the program.
-* OpenSSL. This is if you want the SSL and crypto applications to
- compile (and run). There are prebuilt binaries, which you can just
- download and install, available here:
+* OpenSSL (optional)
+ You need this to build crypto, ssh and ssl libs.
- URL: <http://openssl.org/community/binaries.html>
+ We recommend v1.1.1d or later.
+ There are prebuilt avaiable binaries, which you can just
+ download and install, available here:
+ URL: <http://wiki.openssl.org/index.php/Binaries>
- We would recommend using 1.0.2d.
+ Install into `C:/OpenSSL-Win64` (or `C:/OpenSSL-Win32`)
-* Building with wxWidgets. Download wxWidgets-3.0.3 or higher.
+* wxWidgets (optional)
+ You need this to build wx and use gui's in debugger and observer.
- Install or unpack it to the pgm folder:
- Cygwin:
- `DRIVE:/PATH/cygwin/opt/local/pgm`
- MSYS:
- `DRIVE:/PATH/MinGW/msys/1.0/opt/local/pgm`
- MSYS2:
- `DRIVE:/PATH/msys<32/64>/opt/local/pgm`
+ We recommend v3.1.3 or later.
+ Unpack into `c:/opt/local64/pgm/wxWidgets-3.1.3`
- If the `wxUSE_POSTSCRIPT` isn't enabled in `<path\to\pgm>\wxMSW-3.0.3\include\wx\msw\setup.h`,
+ If the `wxUSE_POSTSCRIPT` isn't enabled in `c:/opt/local64/pgm/wxWidgets-3.1.3/include/wx/msw/setup.h`,
enable it.
- build: From a command prompt with the VC tools available (See the
- instructions for OpenSSL build above for help on starting the
- proper command prompt in RELEASE mode):
-
- C:\...\> cd <path\to\pgm>\wxMSW-3.0.3\build\msw
- C:\...\> nmake BUILD=release SHARED=0 DIR_SUFFIX_CPU= -f makefile.vc
-
- Or - if building a 64bit version:
+ Build with:
- C:\...\> cd <path\to\pgm>\wxMSW-3.0.3\build\msw
+ C:\...\> cd c:\opt\local64\pgm\wxWidgets-3.1.3\build\msw
C:\...\> nmake TARGET_CPU=amd64 BUILD=release SHARED=0 DIR_SUFFIX_CPU= -f makefile.vc
-
+
+ Remove the `TARGET_CPU=amd64` for 32bit build.
+
* Get the Erlang source distribution (from <http://www.erlang.org/download.html>).
- The same as for Unix platforms. Preferably use tar from within Cygwin, MSYS or MSYS2 to
- unpack the source tar.gz (`tar zxf otp_src_%OTP-REL%.tar.gz`).
+ The same as for Unix platforms. Preferably use tar to
+ unpack the source tar.gz (`tar zxf otp_src_%OTP-REL%.tar.gz`) to somewhere
+ on the windows disk, `/mnt/c/path/to/otp_src`
+
+ NOTE: It is important that source on the windows disk.
Set the environment `ERL_TOP` to point to the root directory of the
- source distribution. Let's say I stood in `$HOME/src` and unpacked
+ source distribution. Let's say I stood in `/mnt/c/src` and unpacked
`otp_src_%OTP-REL%.tar.gz`, I then add the following to `.profile`:
- ERL_TOP=$HOME/src/otp_src_%OTP-REL%
- export $ERL_TOP
+ ERL_TOP=/mnt/c/src/otp_src_%OTP-REL%
+ export ERL_TOP
+
The Shell Environment
---------------------
-So, if you have followed the instructions above, when you start a bash
-shell, you should have an INCLUDE environment with a Windows style
-path, a LIB environment variable also in Windows style, and finally a
-PATH that let's you reach cl, makensis, javac etc from the
-command prompt (use `which cl` etc to verify from bash).
+The path variable should now contain the windows paths to javac.exe and makensis.exe.
-You should also have an `ERL_TOP` environment variable that is *Cygwin
-style*, and points to a directory containing, among other files, the
-script `otp_build`.
-
-A final massage of the environment is needed, and that is done by
-the script `$ERL_TOP/otp_build`. Start bash and do the following, note
-the "back-ticks" (\`), can be quite hard to get on some keyboards, but
-pressing the back-tick key followed by the space bar might do it...
-
- $ cd $ERL_TOP
- $ eval `./otp_build env_win32`
+Setup the environment with:
-If you're unable to produce back-ticks on your keyboard, you can use
-the ksh variant:
-
- $ cd $ERL_TOP
- $ eval $(./otp_build env_win32)
-
-If you are building a 64 bit version, you supply `otp_build` with an architecture parameter:
-
- $ cd $ERL_TOP
+ $ export PATH
+ $ cd /mnt/c/path/to/otp_src/
$ eval `./otp_build env_win32 x64`
-
+
+This should setup the additional environment variables.
This should do the final touch to the environment and building should
be easy after this. You could run `./otp_build env_win32` without
@@ -688,18 +201,24 @@ be easy after this. You could run `./otp_build env_win32` without
sets seems OK. The path is cleaned of spaces if possible (using DOS
style short names instead), the variables `OVERRIDE_TARGET`, `CC`, `CXX`,
`AR` and `RANLIB` are set to their respective wrappers and the directories
-`$ERL_TOP/erts/etc/win32/<cygwin/msys>_tools/vc` and
-`$ERL_TOP/erts/etc/win32/<cygwin/msys>_tool` are added first in the PATH.
+`$ERL_TOP/erts/etc/win32/wsl_tools/vc` and
+`$ERL_TOP/erts/etc/win32/wsl_tools` are added first in the PATH.
+
+Now you can check which erlc you have by writing `type erlc` in your shell.
+It should reside in `$ERL_TOP/erts/etc/win32/wsl_tools`.
+
+And running `cl.exe` should print the Microsoft compiler usage message.
-Now you can check which erlc you have by writing `type erlc` in your shell.
-It should reside in `$ERL_TOP/erts/etc/win32/cygwin_tools`
-or `$ERL_TOP/erts/etc/win32/msys_tools`.
+The needed compiler environment variables are setup inside `otp_build`
+via `erts/etc/win32/wsl_tools/SetupWSLcross.bat`. It contains some
+hardcoded paths, if your installation path is different it can be added
+to that file.
Building and Installing
-----------------------
-Building is easiest using the `otp_build` script:
+Building is easiest using the `otp_build` script:
$ ./otp_build autoconf # Ignore the warning blob about versions of autoconf
$ ./otp_build configure <optional configure options>
@@ -707,7 +226,7 @@ Building is easiest using the `otp_build` script:
$ ./otp_build release -a <installation directory>
$ ./otp_build installer_win32 <installation directory> # optional
-Now you will have a file called `otp_win32_%OTP-REL%.exe` or `otp_win64_%OTP-REL%.exe`
+Now you will have a file called `otp_win32_%OTP-REL%.exe` or `otp_win64_%OTP-REL%.exe`
in the `<installation directory>`, i.e. `$ERL_TOP/release/win32`.
Lets get into more detail:
@@ -716,8 +235,8 @@ Lets get into more detail:
to work correctly in your environment. In an ideal world, this
would not be needed, but alas, we have encountered several
incompatibilities between our distributed configure scripts (generated
- on a Linux platform) and the Cygwin/MSYS/MSYS2 environment over the
- years. Running autoconf in Cygwin/MSYS/MSYS2 ensures that the configure
+ on a Linux platform) and the Cygwin/MSYS/MSYS2/WSL environment over the
+ years. Running autoconf in WSL ensures that the configure
scripts are generated in a compatible way and that they will work well
in the next step.
@@ -732,15 +251,15 @@ Lets get into more detail:
3. `$ ./otp_build boot -a` - This uses the bootstrap directory (shipped
with the source, `$ERL_TOP/bootstrap`) to build a complete OTP
- system. When this is done you can run erl from within the source tree;
- just type `$ERL_TOP/bin/erl` and you whould have the prompt.
+ system. When this is done you can run erl from within the source tree;
+ just type `$ERL_TOP/bin/erl` and you should have the prompt.
4. `$ ./otp_build release -a` - Builds a commercial release tree from the
source tree. The default is to put it in `$ERL_TOP/release/win32`. You can
- give any directory as parameter (Cygwin style), but it doesn't really
- matter if you're going to build a self extracting installer too.
+ give any directory as parameter, but it doesn't really
+ matter if you're going to build a self extracting installer too.
-5. `$ ./otp_build installer_win32` - Creates the self extracting installer executable.
+5. `$ ./otp_build installer_win32` - Creates the self extracting installer executable.
The executable `otp_win32_%OTP-REL%.exe` or `otp_win64_%OTP-REL%.exe` will be placed
in the top directory of the release created in the previous step. If
no release directory is specified, the release is expected to have
@@ -765,7 +284,7 @@ Lets get into more detail:
$ cd $ERL_TOP
$ release/win32/otp_win64_%OTP-REL% /S
...
-
+
and after a while Erlang/OTP-%OTP-REL% will have been installed in
`C:\Program Files\erl%ERTS-VSN%\`, with shortcuts in the menu etc.
@@ -835,13 +354,13 @@ steps. You could also add `$ERL_TOP/bootstrap/bin` to your `PATH` before
rebuilding specific libraries. That would give you a good enough
Erlang system to compile any OTP erlang code. Setting up the path
correctly is a little bit tricky. You still need to have
-`$ERL_TOP/erts/etc/win32/cygwin_tools/vc` and
-`$ERL_TOP/erts/etc/win32/cygwin_tools` *before* the actual emulator
+`$ERL_TOP/erts/etc/win32/wsl_tools/vc` and
+`$ERL_TOP/erts/etc/win32/wsl_tools` *before* the actual emulator
in the path. A typical setting of the path for using the bootstrap
compiler would be:
- $ export PATH=$ERL_TOP/erts/etc/win32/cygwin_tools/vc\
- :$ERL_TOP/erts/etc/win32/cygwin_tools:$ERL_TOP/bootstrap/bin:$PATH
+ $ export PATH=$ERL_TOP/erts/etc/win32/wsl_tools/vc\
+ :$ERL_TOP/erts/etc/win32/wsl_tools:$ERL_TOP/bootstrap/bin:$PATH
That should make it possible to rebuild any library without hassle...
@@ -870,22 +389,93 @@ Remember that:
That's basically all you need to get going.
-Using GIT
----------
-You might want to check out versions of the source code from GitHUB. That is possible directly in Cygwin, but not in MSYS. There is a project MsysGIT:
+Frequently Asked Questions
+--------------------------
+
+* Q: So, now I can build Erlang using GCC on Windows?
+
+ A: No, unfortunately not. You'll need Microsoft's Visual C++
+ still. A Bourne-shell script (cc.sh) wraps the Visual C++ compiler
+ and runs it from within the WSL environment. All other tools
+ needed to build Erlang are free-ware/open source, but not the C
+ compiler.
+
+* Q: Why haven't you got rid of VC++ then, you \*\*\*\*\*\*?
+
+ A: Well, partly because it's a good compiler - really! Actually it's
+ been possible in late R11-releases to build using mingw instead of
+ visual C++ (you might see the remnants of that in some scripts and
+ directories). Unfortunately the development of the SMP version for
+ Windows broke the mingw build and we chose to focus on the VC++ build
+ as the performance has been much better in the VC++ versions. The
+ mingw build will possibly be back, but as long as VC++ gives better
+ performance, the commercial build will be a VC++ one.
+
+* Q: Hah, I saw you, you used GCC even though you said you didn't!
+
+ A: OK, I admit, one of the files is compiled using
+ MinGW's GCC and the resulting object code is then converted to MS
+ VC++ compatible coff using a small C hack. It's because that
+ particular file, `beam_emu.c` benefits immensely from being able
+ to use the GCC labels-as-values extension, which boosts emulator
+ performance by up to 50%. That does unfortunately not (yet) mean
+ that all of OTP could be compiled using GCC. That particular
+ source code does not do anything system specific and actually is
+ adopted to the fact that GCC is used to compile it on Windows.
+
+* Q: So now there's a MS VC++ project file somewhere and I can build OTP
+ using the nifty VC++ GUI?
+
+ A: No, never. The hassle of keeping the project files up to date and
+ do all the steps that constitute an OTP build from within the VC++ GUI
+ is simply not worth it, maybe even impossible. A VC++ project
+ file for Erlang/OTP will never happen.
+
+* Q: So how does it all work then?
+
+ A: WSL/Ubuntu is the environment, it's almost like you had a
+ virtual Unix machine inside Windows. Configure, given certain
+ parameters, then creates makefiles that are used by the
+ environment's gnu-make to built the system. Most of the actual
+ compilers etc are not, however, WSL tools, so we've written
+ a couple of wrappers (Bourne-shell scripts), which reside in
+ `$ERL_TOP/etc/win32/wsl_tools`. They all do conversion of
+ parameters and switches common in the Unix environment to fit the
+ native Windows tools. Most notable is of course the paths, which
+ in WSL are Unix-like paths with "forward slashes" (/) and
+ no drive letters. The WSL specific command `wslpath` is used
+ for most of the path conversions in a WSL environment.
+ Luckily most compilers accept forward slashes instead
+ of backslashes as path separators, but one still have to get the drive
+ letters etc right, though. The wrapper scripts are not general in
+ the sense that, for example, cc.sh would understand and translate
+ every possible gcc option and pass correct options to
+ cl.exe. The principle is that the scripts are powerful enough to
+ allow building of Erlang/OTP, no more, no less. They might need
+ extensions to cope with changes during the development of Erlang, and
+ that's one of the reasons we made them into shell-scripts and not
+ Perl-scripts. We believe they are easier to understand and change
+ that way.
+
+ In `$ERL_TOP`, there is a script called `otp_build`. That script handles
+ the hassle of giving all the right parameters to `configure`/`make` and
+ also helps you set up the correct environment variables to work with
+ the Erlang source under WSL.
+
+* Q: Can I build something that looks exactly as the commercial release?
+
+ A: Yes, we use the exact same build procedure.
-URL:<http://code.google.com/p/msysgit/>
+* Q: Which version of WSL and other tools do you use then?
-that makes a nice Git port. The msys prompt you get from MsysGIT is
-however not compatible with the full version from MinGW, so you will
-need to check out files using MsysGIT's command prompt and then switch
-to a common MSYS command prompt for building. Also all test suites
-cannot be built as MsysGIT/MSYS does not handle symbolic links.
+ A: We use WSL 1 with Ubuntu 18.04.
+ The GCC we used for %OTP-REL% was version 7.3-win32.
+ We used Visual studio 2019, Sun's JDK 1.8.0\_241,
+ NSIS 3.05, Win32 OpenSSL 1.1.1d and wxWidgets-3.1.3.
- [1]: http://www.erlang.org/static/doc/mailinglist.html
- [2]: http://bugs.erlang.org
- [3]: https://github.com/erlang/otp
+ [1]: http://bugs.erlang.org
+ [2]: https://github.com/erlang/otp
[?TOC]: true
diff --git a/HOWTO/INSTALL.md b/HOWTO/INSTALL.md
index 674454bc8e..fab973e5e4 100644
--- a/HOWTO/INSTALL.md
+++ b/HOWTO/INSTALL.md
@@ -217,6 +217,12 @@ Build the documentation.
$ make docs
+It is possible to limit which types of documentation is build by passing the `DOC_TARGETS`
+environment variable to `make docs`. The currently available types are: `html`, `pdf`, `man` and
+`chunks`. Example:
+
+ $ make docs DOC_TARGETS=chunks
+
#### Build Issues ####
We have sometimes experienced problems with Oracle's `java` running out of
@@ -247,6 +253,8 @@ or using the `release_docs` target.
$ make release_docs RELEASE_ROOT=<release dir>
+It is possible to limit which types of documentation is released using the same `DOC_TARGETS`
+environment variable as when building documentation.
### Accessing the Documentation ###
@@ -261,6 +269,8 @@ After installation you can access the documentation by
* Browsing the html pages by loading the page `/usr/local/lib/erlang/doc/erlang/index.html`
or `<BaseDir>/lib/erlang/doc/erlang/index.html` if the prefix option has been used.
+* Read the embedded documentation by using the built-in shell functions `h/1,2,3` or
+ `ht/1,2,3`.
### How to Install the Pre-formatted Documentation ###
@@ -353,6 +363,7 @@ Some of the available `configure` options are:
`(g)cc`
* `--enable-m32-build` - Build 32-bit binaries using the `-m32` flag to
`(g)cc`
+* `--{enable,disable}-pie` - Build position independent executable binaries.
* `--with-assumed-cache-line-size=SIZE` - Set assumed cache-line size in
bytes. Default is 64. Valid values are powers of two between and
including 16 and 8192. The runtime system use this value in order to
@@ -596,7 +607,7 @@ using the similar steps just described.
$ (cd $ERL_TOP/erts/emulator && make $TYPE)
-where `$TYPE` is `opt`, `gcov`, `gprof`, `debug`, `valgrind`, or `lcnt`.
+where `$TYPE` is `opt`, `gcov`, `gprof`, `debug`, `valgrind`, `asan` or `lcnt`.
These different beam types are useful for debugging and profiling
purposes.
@@ -789,7 +800,7 @@ Use `hipe:help_options/0` to print out the available options.
[html documentation]: http://www.erlang.org/download/otp_doc_html_%OTP-VSN%.tar.gz
[man pages]: http://www.erlang.org/download/otp_doc_man_%OTP-VSN%.tar.gz
[the released source tar ball]: http://www.erlang.org/download/otp_src_%OTP-VSN%.tar.gz
- [System Principles]: ../system_principles/system_principles
+ [System Principles]: system/system_principles:system_principles
[native build]: #How-to-Build-and-Install-ErlangOTP
[cross build]: INSTALL-CROSS.md
[Required Utilities]: #Required-Utilities
diff --git a/HOWTO/OTP-PATCH-APPLY.md b/HOWTO/OTP-PATCH-APPLY.md
index 2aa31629ef..1fdd13f9c4 100644
--- a/HOWTO/OTP-PATCH-APPLY.md
+++ b/HOWTO/OTP-PATCH-APPLY.md
@@ -137,8 +137,8 @@ dependencies among applications actually loaded.
Please take a look at the reference of [sanity_check()][] for more
information.
-[application resource file]: kernel:app
-[runtime_dependencies]: kernel:app#runtime_dependencies
+[application resource file]: seefile/kernel:app
+[runtime_dependencies]: seefile/kernel:app#runtime_dependencies
[building and installing Erlang/OTP]: INSTALL.md
-[version handling]: ../system_principles/versions
-[sanity_check()]: runtime_tools:system_information#sanity_check-0
+[version handling]: system/system_principles:versions
+[sanity_check()]: seemfa/runtime_tools:system_information#sanity_check/0
diff --git a/HOWTO/SYSTEMTAP.md b/HOWTO/SYSTEMTAP.md
index ce9c0b2f0c..76deac97e5 100644
--- a/HOWTO/SYSTEMTAP.md
+++ b/HOWTO/SYSTEMTAP.md
@@ -5,7 +5,7 @@ Introduction
------------
SystemTap is DTrace for Linux. In fact Erlang's SystemTap support
-is build using SystemTap's DTrace compatibility's layer. For an
+is built using SystemTap's DTrace compatibility's layer. For an
introduction to Erlang DTrace support read [$ERL_TOP/HOWTO/DTRACE.md][].
Requisites
diff --git a/HOWTO/TESTING.md b/HOWTO/TESTING.md
index ad59319efa..7a7f6982f2 100644
--- a/HOWTO/TESTING.md
+++ b/HOWTO/TESTING.md
@@ -130,6 +130,52 @@ i.e.
Running [ct_run][] from the command line still requires you to do the
`ts:install()` step above.
+### Convenience for running tests without the release and configuration steps
+
+It can be convenient to run tests with a single command. This way, one
+do not need to worry about missing to run `make release_tests` after
+changing a test suite. The `make test` command can be used for this
+purpose. The `make test` command works when the current directory
+contains a directory called test and in the root directory of the
+source code tree.
+
+*(Waring)* Some test cases do not run correctly or cannot be run at
+all through the `make test` command (typically test cases that require
+test specific C code to be compiled) because `make test` runs tests
+directly by invoking the `ct_run` command instead of using the `ts`
+wrapper. One has to follow the procedure described above to run test
+cases that do not work with `make test`.
+
+Below are some examples that illustrate how `make test` can be
+used:
+
+ # ERL_TOP needs to be set correctly
+ cd /path/to/otp
+ export ERL_TOP=`pwd`
+
+ # Build Erlang/OTP
+ #
+ # Note that make test will only compile test code except when
+ # make test is executed from $ERL_TOP.
+ ./otp_build setup -a
+
+ # Run a test case (The ARGS variable is passed to ct_run)
+ (cd $ERL_TOP/erts/emulator && make ARGS="-suite binary_SUITE -case deep_bitstr_lists" test)
+
+ # Run a test suite
+ (cd $ERL_TOP/lib/stdlib && make ARGS="-suite ets_SUITE" test)
+
+ # Run all test suites for an application
+ (cd $ERL_TOP/lib/asn1 && make test)
+
+ # Run all tests
+ #
+ # When executed from $ERL_TOP, "make test" will first release and
+ # configure all tests and then attempt to run all tests with `ts:run`.
+ # This will take several hours.
+ (cd $ERL_TOP && make test)
+
+
Examining the results
---------------------
@@ -139,6 +185,52 @@ examine the results so far for the currently executing test suite (in R14B02 and
later you want to open the `release/tests/test_server/all_runs.html` file to
get to the currently running test)
+
+Run tests with Address Sanitizer
+--------------------------------
+
+First build emulator with `asan` build target.
+See [$ERL_TOP/HOWTO/INSTALL.md][].
+
+Set environment variable `ASAN_LOG_DIR` to the directory
+where the error logs will be generated.
+
+ export ASAN_LOG_DIR=$TESTROOT/test_server/asan_logs
+ mkdir $ASAN_LOG_DIR
+
+Set environment variable `TS_RUN_EMU` to `asan`.
+
+ export TS_RUN_EMU=asan
+
+Then run the tests you want with `ts:run` as described above. Either
+inspect the log files directly or use the script at
+`$ERL_TOP/erts/emulator/asan/asan_logs_to_html` to read all log files
+in `$ASAN_LOG_DIR` and distill them into one html page
+`asan_summary.html`. Repeated reports from the same memory leak will
+for example be ignored by the script and make it easier to analyze.
+
+
+Run tests with Valgrind
+-----------------------
+
+First make sure [valgrind][] is installed, then build OTP from source
+and build the emulator with `valgrind` build target. See
+[$ERL_TOP/HOWTO/INSTALL.md][].
+
+Set environment variable `VALGRIND_LOG_DIR` to the directory
+where the valgrind error logs will be generated.
+
+ export VALGRIND_LOG_DIR=$TESTROOT/test_server/vg_logs
+ mkdir $VALGRIND_LOG_DIR
+
+Set environment variable `TS_RUN_EMU` to `valgrind`.
+
+ export TS_RUN_EMU=valgrind
+
+Then run the tests you want with `ts:run` as described above and
+inspect the log file(s) in `$VALGRIND_LOG_DIR`.
+
+
[ct_run]: http://www.erlang.org/doc/man/ct_run.html
[ct hook]: http://www.erlang.org/doc/apps/common_test/ct_hooks_chapter.html
[$ERL_TOP/HOWTO/INSTALL.md]: INSTALL.md
@@ -146,5 +238,6 @@ get to the currently running test)
[common_test]: http://www.erlang.org/doc/man/ct.html
[data_dir]: http://www.erlang.org/doc/apps/common_test/write_test_chapter.html#data_priv_dir
[configuring the tests]: #configuring-the-test-environment
+ [valgrind]: https://valgrind.org
[?TOC]: true
diff --git a/Makefile.in b/Makefile.in
index 622f5a4a01..16bc7b1a3a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -338,7 +338,7 @@ endif
else
# Cross compiling
-all: cross_check_erl depend build_erl_interface \
+all: cross_check_erl depend erl_interface \
emulator libs start_scripts check_dev_rt_dep
endif
@@ -378,28 +378,22 @@ endif
# the system is delivered in open source, the primary
# bootstrap is not included, it requires a pre built emulator...
ifeq ($(OTP_TINY_BUILD),true)
-all_bootstraps: build_erl_interface emulator bootstrap_setup \
+all_bootstraps: erl_interface emulator bootstrap_setup \
tiny_secondary_bootstrap_build tiny_secondary_bootstrap_copy
else
-all_bootstraps: build_erl_interface emulator \
+all_bootstraps: erl_interface emulator \
bootstrap_setup \
secondary_bootstrap_build secondary_bootstrap_copy \
tertiary_bootstrap_build tertiary_bootstrap_copy
endif
-.PHONY: build_erl_interface
-
-build_erl_interface:
- $(make_verbose)cd lib/erl_interface && \
- ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) $(TYPE)
#
# Use these targets when you want to use the erl and erlc
# binaries in your PATH instead of those created from the
# pre-compiled Erlang modules under bootstrap/.
#
noboot:
- $(MAKE) USE_PGO=false BOOT_PREFIX= build_erl_interface emulator libs local_setup
+ $(MAKE) USE_PGO=false BOOT_PREFIX= erl_interface emulator libs local_setup
noboot_install:
$(MAKE) USE_PGO=false BOOT_PREFIX= install
@@ -461,6 +455,8 @@ else
PATH=$(BOOT_PREFIX)"$${PATH}" ERL_TOP=$(ERL_TOP) \
$(MAKE) -C system/doc $@
endif
+ $(DOCGEN)/priv/bin/validate_links.escript $(ERL_TOP) make/$(TARGET)/mod2app.xml \
+ lib/*/doc/xml/*.xml erts/doc/xml/*.xml system/doc/xml/*/*.xml
mod2app: $(ERL_TOP)/make/$(TARGET)/mod2app.xml
@@ -477,13 +473,14 @@ BOOTSTRAP_COMPILER = $(BOOTSTRAP_TOP)/primary_compiler
# otp.mk is only used to figure out if we are doing PGO or not
include $(ERL_TOP)/make/$(TARGET)/otp.mk
-.PHONY: emulator libs kernel stdlib compiler hipe syntax_tools preloaded
+.PHONY: emulator libs kernel preloaded
ifeq ($(USE_PGO), true)
PROFILE=use
PROFILE_EMU_DEPS=emulator_profile_generate bootstrap_setup
emulator_profile_generate:
- $(make_verbose)cd erts && ERL_TOP=$(ERL_TOP) $(MAKE) NO_START_SCRIPTS=true $(TYPE) FLAVOR=$(FLAVOR) PROFILE=generate
+ $(make_verbose)cd erts && PATH=$(BOOT_PREFIX)"$${PATH}" ERL_TOP=$(ERL_TOP) \
+ $(MAKE) NO_START_SCRIPTS=true $(TYPE) FLAVOR=$(FLAVOR) PROFILE=generate
else
PROFILE=
PROFILE_EMU_DEPS=
@@ -497,27 +494,36 @@ libs:
ifeq ($(OTP_SMALL_BUILD),true)
$(make_verbose)cd lib && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) $(TYPE)
+ $(MAKE) $(TYPE) ERL_COMPILE_WARNINGS_AS_ERRORS=yes
+
else
ifeq ($(OTP_TINY_BUILD),true)
$(make_verbose)cd lib && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) opt TINY_BUILD=true
+ $(MAKE) opt TINY_BUILD=true ERL_COMPILE_WARNINGS_AS_ERRORS=yes
else
$(make_verbose)cd lib && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) $(TYPE) BUILD_ALL=true
+ $(MAKE) $(TYPE) BUILD_ALL=true ERL_COMPILE_WARNINGS_AS_ERRORS=yes
$(V_at)test -f $(ERL_TOP)/make/otp_built || echo "OTP built" > $(ERL_TOP)/make/otp_built
endif
endif
APPS=$(patsubst $(ERL_TOP)/lib/%/doc,%,$(wildcard $(ERL_TOP)/lib/*/doc))
+.PHONY: $(APPS)
+
$(APPS):
$(make_verbose)cd lib/$@ && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
$(MAKE) $(TYPE) BUILD_ALL=true
+##
+## Generate `otp_internal` from the -deprecated() attributes in source.
+##
+deprecations: all
+ $(ERL_TOP)/lib/stdlib/scripts/update_deprecations update $(ERL_TOP)
+
preloaded:
$(make_verbose)cd erts/preloaded/src && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
@@ -525,9 +531,9 @@ preloaded:
dep depend:
$(make_verbose)
- $(V_at)test X"$$ERTS_SKIP_DEPEND" = X"true" || (cd erts/emulator && ERL_TOP=$(ERL_TOP) $(MAKE) generate)
- $(V_at)test X"$$ERTS_SKIP_DEPEND" = X"true" || (cd erts/emulator && ERL_TOP=$(ERL_TOP) $(MAKE) depend)
- $(V_at)test X"$$ERTS_SKIP_DEPEND" = X"true" || (cd erts/lib_src && ERL_TOP=$(ERL_TOP) $(MAKE) depend)
+ $(V_at)test X"$$ERTS_SKIP_DEPEND" = X"true" || (cd erts/emulator && PATH=$(BOOT_PREFIX)"$${PATH}" ERL_TOP=$(ERL_TOP) $(MAKE) generate)
+ $(V_at)test X"$$ERTS_SKIP_DEPEND" = X"true" || (cd erts/emulator && PATH=$(BOOT_PREFIX)"$${PATH}" ERL_TOP=$(ERL_TOP) $(MAKE) depend)
+ $(V_at)test X"$$ERTS_SKIP_DEPEND" = X"true" || (cd erts/lib_src && PATH=$(BOOT_PREFIX)"$${PATH}" ERL_TOP=$(ERL_TOP) $(MAKE) depend)
# Creates "erl" and "erlc" in bootstrap/bin which uses the precompiled
# libraries in the bootstrap directory
@@ -547,7 +553,8 @@ bootstrap_setup: check_recreate_primary_bootstrap bootstrap_setup_target
$(BOOTSTRAP_ROOT)/bootstrap/bin/erlc.exe \
$(BOOTSTRAP_ROOT)/bootstrap/bin/escript.exe \
$(BOOTSTRAP_ROOT)/bootstrap/bin/erl.ini \
- $(BOOTSTRAP_ROOT)/bootstrap/bin/beam.dll
+ $(BOOTSTRAP_ROOT)/bootstrap/bin/beam.dll \
+ $(BOOTSTRAP_ROOT)/bootstrap/bin/yielding_c_fun.exe
make_bootstrap_ini.sh $(BOOTSTRAP_ROOT)/bootstrap \
$(ERL_TOP)/bin/$(TARGET)
@cp $(ERL_TOP)/bin/$(TARGET)/erlc.exe \
@@ -556,8 +563,10 @@ bootstrap_setup: check_recreate_primary_bootstrap bootstrap_setup_target
$(BOOTSTRAP_ROOT)/bootstrap/bin/erl.exe
@cp $(ERL_TOP)/bin/$(TARGET)/escript.exe \
$(BOOTSTRAP_ROOT)/bootstrap/bin/escript.exe
+ @cp $(ERL_TOP)/erts/lib_src/yielding_c_fun/bin/$(TARGET)/yielding_c_fun.exe \
+ $(BOOTSTRAP_ROOT)/bootstrap/bin/yielding_c_fun.exe
else
-bootstrap_setup: check_recreate_primary_bootstrap bootstrap_setup_target $(BOOTSTRAP_ROOT)/bootstrap/bin/erl $(BOOTSTRAP_ROOT)/bootstrap/bin/erlc $(BOOTSTRAP_ROOT)/bootstrap/bin/escript
+bootstrap_setup: check_recreate_primary_bootstrap bootstrap_setup_target $(BOOTSTRAP_ROOT)/bootstrap/bin/erl $(BOOTSTRAP_ROOT)/bootstrap/bin/erlc $(BOOTSTRAP_ROOT)/bootstrap/bin/escript $(BOOTSTRAP_ROOT)/bootstrap/bin/yielding_c_fun
$(BOOTSTRAP_ROOT)/bootstrap/bin/erl: $(ERL_TOP)/erts/etc/unix/erl.src.src $(BOOTSTRAP_ROOT)/bootstrap/target
@rm -f $(BOOTSTRAP_ROOT)/bootstrap/bin/erl
@@ -577,6 +586,11 @@ $(BOOTSTRAP_ROOT)/bootstrap/bin/escript: $(ERL_TOP)/bin/$(TARGET)/escript $(BOOT
@rm -f $(BOOTSTRAP_ROOT)/bootstrap/bin/escript
@cp $(ERL_TOP)/bin/$(TARGET)/escript $(BOOTSTRAP_ROOT)/bootstrap/bin/escript
@chmod 755 $(BOOTSTRAP_ROOT)/bootstrap/bin/escript
+
+$(BOOTSTRAP_ROOT)/bootstrap/bin/yielding_c_fun: $(ERL_TOP)/bin/$(TARGET)/escript $(BOOTSTRAP_ROOT)/bootstrap/target
+ @rm -f $(BOOTSTRAP_ROOT)/bootstrap/bin/yielding_c_fun
+ @cp $(ERL_TOP)/erts/lib_src/yielding_c_fun/bin/$(TARGET)/yielding_c_fun $(BOOTSTRAP_ROOT)/bootstrap/bin/yielding_c_fun
+ @chmod 755 $(BOOTSTRAP_ROOT)/bootstrap/bin/yielding_c_fun
endif
bootstrap_setup_target:
@@ -587,12 +601,12 @@ bootstrap_setup_target:
tiny_secondary_bootstrap_build:
$(make_verbose)cd lib && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) opt SECONDARY_BOOTSTRAP=true TINY_BUILD=true
+ $(MAKE) opt SECONDARY_BOOTSTRAP=true TINY_BUILD=true ERL_COMPILE_WARNINGS_AS_ERRORS=yes
secondary_bootstrap_build:
$(make_verbose)cd lib && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) opt SECONDARY_BOOTSTRAP=true
+ $(MAKE) opt SECONDARY_BOOTSTRAP=true ERL_COMPILE_WARNINGS_AS_ERRORS=yes
tiny_secondary_bootstrap_copy:
$(V_at)if test ! -d $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools ; then mkdir $(BOOTSTRAP_ROOT)/bootstrap/lib/parsetools ; fi
@@ -712,7 +726,7 @@ secondary_bootstrap_copy:
tertiary_bootstrap_build:
$(make_verbose)cd lib && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
- $(MAKE) opt TERTIARY_BOOTSTRAP=true
+ $(MAKE) opt TERTIARY_BOOTSTRAP=true ERL_COMPILE_WARNINGS_AS_ERRORS=yes
tertiary_bootstrap_copy:
$(make_verbose)
@@ -1206,3 +1220,11 @@ bootstrap_clean:
|| $(MAKE) BOOTSTRAP_ROOT=$(BOOTSTRAP_ROOT) bootstrap_root_clean
# ----------------------------------------------------------------------
+
+.PHONY: test dialyzer
+
+test: all release release_tests
+ $(ERL_TOP)/make/test_target_script.sh $(ERL_TOP)
+
+dialyzer: all
+ $(ERL_TOP)/scripts/run-dialyzer
diff --git a/OTP_VERSION b/OTP_VERSION
index e5a7961ef5..a9a57c8226 100644
--- a/OTP_VERSION
+++ b/OTP_VERSION
@@ -1 +1 @@
-22.3.4
+23.3.1
diff --git a/bootstrap/bin/no_dot_erlang.boot b/bootstrap/bin/no_dot_erlang.boot
index 303852b9d8..1434a96636 100644
--- a/bootstrap/bin/no_dot_erlang.boot
+++ b/bootstrap/bin/no_dot_erlang.boot
Binary files differ
diff --git a/bootstrap/bin/start.boot b/bootstrap/bin/start.boot
index 303852b9d8..1434a96636 100644
--- a/bootstrap/bin/start.boot
+++ b/bootstrap/bin/start.boot
Binary files differ
diff --git a/bootstrap/bin/start_clean.boot b/bootstrap/bin/start_clean.boot
index 303852b9d8..1434a96636 100644
--- a/bootstrap/bin/start_clean.boot
+++ b/bootstrap/bin/start_clean.boot
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_a.beam b/bootstrap/lib/compiler/ebin/beam_a.beam
index 69f3c8e451..37c074fe11 100644
--- a/bootstrap/lib/compiler/ebin/beam_a.beam
+++ b/bootstrap/lib/compiler/ebin/beam_a.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_asm.beam b/bootstrap/lib/compiler/ebin/beam_asm.beam
index ef06c4b69b..047f014022 100644
--- a/bootstrap/lib/compiler/ebin/beam_asm.beam
+++ b/bootstrap/lib/compiler/ebin/beam_asm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_block.beam b/bootstrap/lib/compiler/ebin/beam_block.beam
index 1db56cb5f1..1443b24523 100644
--- a/bootstrap/lib/compiler/ebin/beam_block.beam
+++ b/bootstrap/lib/compiler/ebin/beam_block.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_call_types.beam b/bootstrap/lib/compiler/ebin/beam_call_types.beam
new file mode 100644
index 0000000000..d0ba1a1494
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_call_types.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_clean.beam b/bootstrap/lib/compiler/ebin/beam_clean.beam
index 4d567eca88..e91269bcc2 100644
--- a/bootstrap/lib/compiler/ebin/beam_clean.beam
+++ b/bootstrap/lib/compiler/ebin/beam_clean.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_dict.beam b/bootstrap/lib/compiler/ebin/beam_dict.beam
index b9317a55ee..96a973cf91 100644
--- a/bootstrap/lib/compiler/ebin/beam_dict.beam
+++ b/bootstrap/lib/compiler/ebin/beam_dict.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_digraph.beam b/bootstrap/lib/compiler/ebin/beam_digraph.beam
new file mode 100644
index 0000000000..1619595155
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_digraph.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_disasm.beam b/bootstrap/lib/compiler/ebin/beam_disasm.beam
index eb2a13d620..505014fbf5 100644
--- a/bootstrap/lib/compiler/ebin/beam_disasm.beam
+++ b/bootstrap/lib/compiler/ebin/beam_disasm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_except.beam b/bootstrap/lib/compiler/ebin/beam_except.beam
deleted file mode 100644
index adf220948f..0000000000
--- a/bootstrap/lib/compiler/ebin/beam_except.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_flatten.beam b/bootstrap/lib/compiler/ebin/beam_flatten.beam
index d7780861c2..9c16ffa8e7 100644
--- a/bootstrap/lib/compiler/ebin/beam_flatten.beam
+++ b/bootstrap/lib/compiler/ebin/beam_flatten.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_jump.beam b/bootstrap/lib/compiler/ebin/beam_jump.beam
index 97012b22ba..07f1395408 100644
--- a/bootstrap/lib/compiler/ebin/beam_jump.beam
+++ b/bootstrap/lib/compiler/ebin/beam_jump.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beam b/bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beam
index 962bb86586..44868beab7 100644
--- a/bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beam
+++ b/bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_listing.beam b/bootstrap/lib/compiler/ebin/beam_listing.beam
index 097ba5e7ff..cb196fb100 100644
--- a/bootstrap/lib/compiler/ebin/beam_listing.beam
+++ b/bootstrap/lib/compiler/ebin/beam_listing.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_opcodes.beam b/bootstrap/lib/compiler/ebin/beam_opcodes.beam
index 52a23f7f6c..601c6881a8 100644
--- a/bootstrap/lib/compiler/ebin/beam_opcodes.beam
+++ b/bootstrap/lib/compiler/ebin/beam_opcodes.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_peep.beam b/bootstrap/lib/compiler/ebin/beam_peep.beam
index afd470a3b7..57dc52d6fe 100644
--- a/bootstrap/lib/compiler/ebin/beam_peep.beam
+++ b/bootstrap/lib/compiler/ebin/beam_peep.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa.beam b/bootstrap/lib/compiler/ebin/beam_ssa.beam
index 0327230a58..ed118c16d6 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam b/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam
new file mode 100644
index 0000000000..157a77e524
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_bool.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam b/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam
index 21947889c9..507f510009 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam b/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
index cb06630012..23bb5192a4 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam b/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
index 782073698e..cb4ac5122c 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_funs.beam b/bootstrap/lib/compiler/ebin/beam_ssa_funs.beam
index 6370e1eb78..ab4cb275a5 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_funs.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_funs.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam b/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam
index e0a43057fc..08344d0398 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam b/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam
index b8588725b4..9c69449771 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_opt.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam b/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam
index f76f15aa70..4d8d3e5d6c 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_pp.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam b/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
index 7b68634bb3..f97a03c473 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam b/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
index 10bce0a580..4a6997cf07 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_share.beam b/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
index d975cebf48..a3d7a060ba 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_type.beam b/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
index 0403230604..514e61b466 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_trim.beam b/bootstrap/lib/compiler/ebin/beam_trim.beam
index 00f3224106..08c1cb1db5 100644
--- a/bootstrap/lib/compiler/ebin/beam_trim.beam
+++ b/bootstrap/lib/compiler/ebin/beam_trim.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_types.beam b/bootstrap/lib/compiler/ebin/beam_types.beam
new file mode 100644
index 0000000000..9f409ae6e0
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_types.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_utils.beam b/bootstrap/lib/compiler/ebin/beam_utils.beam
index 996525efc9..7ac14df230 100644
--- a/bootstrap/lib/compiler/ebin/beam_utils.beam
+++ b/bootstrap/lib/compiler/ebin/beam_utils.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_validator.beam b/bootstrap/lib/compiler/ebin/beam_validator.beam
index c333ff927a..48dc4f8096 100644
--- a/bootstrap/lib/compiler/ebin/beam_validator.beam
+++ b/bootstrap/lib/compiler/ebin/beam_validator.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_z.beam b/bootstrap/lib/compiler/ebin/beam_z.beam
index 21ca7bcf46..3dea70729c 100644
--- a/bootstrap/lib/compiler/ebin/beam_z.beam
+++ b/bootstrap/lib/compiler/ebin/beam_z.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl.beam b/bootstrap/lib/compiler/ebin/cerl.beam
index 948c4759b0..480da51872 100644
--- a/bootstrap/lib/compiler/ebin/cerl.beam
+++ b/bootstrap/lib/compiler/ebin/cerl.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl_clauses.beam b/bootstrap/lib/compiler/ebin/cerl_clauses.beam
index a20a4ca43b..977fb2eee6 100644
--- a/bootstrap/lib/compiler/ebin/cerl_clauses.beam
+++ b/bootstrap/lib/compiler/ebin/cerl_clauses.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl_inline.beam b/bootstrap/lib/compiler/ebin/cerl_inline.beam
index 7bf92a0dd7..f764bf0270 100644
--- a/bootstrap/lib/compiler/ebin/cerl_inline.beam
+++ b/bootstrap/lib/compiler/ebin/cerl_inline.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl_sets.beam b/bootstrap/lib/compiler/ebin/cerl_sets.beam
index bda70130a6..c12a16f10d 100644
--- a/bootstrap/lib/compiler/ebin/cerl_sets.beam
+++ b/bootstrap/lib/compiler/ebin/cerl_sets.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl_trees.beam b/bootstrap/lib/compiler/ebin/cerl_trees.beam
index 831eddc405..ef29f6fdfc 100644
--- a/bootstrap/lib/compiler/ebin/cerl_trees.beam
+++ b/bootstrap/lib/compiler/ebin/cerl_trees.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/compile.beam b/bootstrap/lib/compiler/ebin/compile.beam
index 540672bb22..27a8a497b6 100644
--- a/bootstrap/lib/compiler/ebin/compile.beam
+++ b/bootstrap/lib/compiler/ebin/compile.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/compiler.app b/bootstrap/lib/compiler/ebin/compiler.app
index 47e14790df..a85ab33c24 100644
--- a/bootstrap/lib/compiler/ebin/compiler.app
+++ b/bootstrap/lib/compiler/ebin/compiler.app
@@ -1,7 +1,7 @@
% This is an -*- erlang -*- file.
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,15 +19,16 @@
{application, compiler,
[{description, "ERTS CXC 138 10"},
- {vsn, "7.5.2"},
+ {vsn, "7.6.6"},
{modules, [
beam_a,
beam_asm,
beam_block,
+ beam_call_types,
beam_clean,
beam_dict,
+ beam_digraph,
beam_disasm,
- beam_except,
beam_flatten,
beam_jump,
beam_kernel_to_ssa,
@@ -35,6 +36,7 @@
beam_opcodes,
beam_peep,
beam_ssa,
+ beam_ssa_bool,
beam_ssa_bsm,
beam_ssa_codegen,
beam_ssa_dead,
@@ -47,6 +49,7 @@
beam_ssa_share,
beam_ssa_type,
beam_trim,
+ beam_types,
beam_utils,
beam_validator,
beam_z,
@@ -68,6 +71,7 @@
sys_core_fold,
sys_core_fold_lists,
sys_core_inline,
+ sys_core_prepare,
sys_pre_attributes,
v3_core,
v3_kernel,
@@ -76,5 +80,5 @@
{registered, []},
{applications, [kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["stdlib-2.5","kernel-4.0","hipe-3.12","erts-9.0",
+ {runtime_dependencies, ["stdlib-3.13","kernel-7.0","hipe-3.12","erts-11.0",
"crypto-3.6"]}]}.
diff --git a/bootstrap/lib/compiler/ebin/core_lib.beam b/bootstrap/lib/compiler/ebin/core_lib.beam
index 19b0c3dfcb..c73352bda3 100644
--- a/bootstrap/lib/compiler/ebin/core_lib.beam
+++ b/bootstrap/lib/compiler/ebin/core_lib.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/core_lint.beam b/bootstrap/lib/compiler/ebin/core_lint.beam
index ecad4f33e0..d4a5ad17d6 100644
--- a/bootstrap/lib/compiler/ebin/core_lint.beam
+++ b/bootstrap/lib/compiler/ebin/core_lint.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/core_parse.beam b/bootstrap/lib/compiler/ebin/core_parse.beam
index f14357cf02..6a60b958b4 100644
--- a/bootstrap/lib/compiler/ebin/core_parse.beam
+++ b/bootstrap/lib/compiler/ebin/core_parse.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/core_pp.beam b/bootstrap/lib/compiler/ebin/core_pp.beam
index a2b3167e10..db0e52f459 100644
--- a/bootstrap/lib/compiler/ebin/core_pp.beam
+++ b/bootstrap/lib/compiler/ebin/core_pp.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/core_scan.beam b/bootstrap/lib/compiler/ebin/core_scan.beam
index 8e44464cd7..f964b39cf7 100644
--- a/bootstrap/lib/compiler/ebin/core_scan.beam
+++ b/bootstrap/lib/compiler/ebin/core_scan.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/erl_bifs.beam b/bootstrap/lib/compiler/ebin/erl_bifs.beam
index e626e91f4f..4d1b788bd9 100644
--- a/bootstrap/lib/compiler/ebin/erl_bifs.beam
+++ b/bootstrap/lib/compiler/ebin/erl_bifs.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/rec_env.beam b/bootstrap/lib/compiler/ebin/rec_env.beam
index 6d0c5baa04..74f85b4f52 100644
--- a/bootstrap/lib/compiler/ebin/rec_env.beam
+++ b/bootstrap/lib/compiler/ebin/rec_env.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_alias.beam b/bootstrap/lib/compiler/ebin/sys_core_alias.beam
index b769b6be41..7f88d7bb9b 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_alias.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_alias.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_bsm.beam b/bootstrap/lib/compiler/ebin/sys_core_bsm.beam
index 6f4204e0a1..ddd3849ef2 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_bsm.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_bsm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_fold.beam b/bootstrap/lib/compiler/ebin/sys_core_fold.beam
index ecd52b5eb0..51d2940fd4 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_fold.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_fold.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_fold_lists.beam b/bootstrap/lib/compiler/ebin/sys_core_fold_lists.beam
index f4aac9ac13..7a6d5fb047 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_fold_lists.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_fold_lists.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_inline.beam b/bootstrap/lib/compiler/ebin/sys_core_inline.beam
index 5647c8fcb5..0c0e121687 100644
--- a/bootstrap/lib/compiler/ebin/sys_core_inline.beam
+++ b/bootstrap/lib/compiler/ebin/sys_core_inline.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_prepare.beam b/bootstrap/lib/compiler/ebin/sys_core_prepare.beam
new file mode 100644
index 0000000000..4f69160a38
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/sys_core_prepare.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_pre_attributes.beam b/bootstrap/lib/compiler/ebin/sys_pre_attributes.beam
index 002b4f2914..dafcb2cf3b 100644
--- a/bootstrap/lib/compiler/ebin/sys_pre_attributes.beam
+++ b/bootstrap/lib/compiler/ebin/sys_pre_attributes.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/v3_core.beam b/bootstrap/lib/compiler/ebin/v3_core.beam
index 3cbbdfd88b..3d0c23d772 100644
--- a/bootstrap/lib/compiler/ebin/v3_core.beam
+++ b/bootstrap/lib/compiler/ebin/v3_core.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/v3_kernel.beam b/bootstrap/lib/compiler/ebin/v3_kernel.beam
index 4be5b827e4..a0fe4a3775 100644
--- a/bootstrap/lib/compiler/ebin/v3_kernel.beam
+++ b/bootstrap/lib/compiler/ebin/v3_kernel.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam b/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam
index 5177b65198..3eda809d20 100644
--- a/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam
+++ b/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/application.beam b/bootstrap/lib/kernel/ebin/application.beam
index 84c5b7636e..5e90a7734c 100644
--- a/bootstrap/lib/kernel/ebin/application.beam
+++ b/bootstrap/lib/kernel/ebin/application.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/application_controller.beam b/bootstrap/lib/kernel/ebin/application_controller.beam
index b5d6545ca7..618dad4942 100644
--- a/bootstrap/lib/kernel/ebin/application_controller.beam
+++ b/bootstrap/lib/kernel/ebin/application_controller.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/application_master.beam b/bootstrap/lib/kernel/ebin/application_master.beam
index 77bb9544e2..c090f903b0 100644
--- a/bootstrap/lib/kernel/ebin/application_master.beam
+++ b/bootstrap/lib/kernel/ebin/application_master.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/application_starter.beam b/bootstrap/lib/kernel/ebin/application_starter.beam
index 0daab463ab..c703c9ee0f 100644
--- a/bootstrap/lib/kernel/ebin/application_starter.beam
+++ b/bootstrap/lib/kernel/ebin/application_starter.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/auth.beam b/bootstrap/lib/kernel/ebin/auth.beam
index c77178bf7c..c4e00d2f20 100644
--- a/bootstrap/lib/kernel/ebin/auth.beam
+++ b/bootstrap/lib/kernel/ebin/auth.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/code.beam b/bootstrap/lib/kernel/ebin/code.beam
index 204b15fcf2..eafa1d5aa3 100644
--- a/bootstrap/lib/kernel/ebin/code.beam
+++ b/bootstrap/lib/kernel/ebin/code.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/code_server.beam b/bootstrap/lib/kernel/ebin/code_server.beam
index 3211f91ac6..98cdcde9bf 100644
--- a/bootstrap/lib/kernel/ebin/code_server.beam
+++ b/bootstrap/lib/kernel/ebin/code_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/disk_log.beam b/bootstrap/lib/kernel/ebin/disk_log.beam
index 25142ee575..fd3a3309b6 100644
--- a/bootstrap/lib/kernel/ebin/disk_log.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/disk_log_1.beam b/bootstrap/lib/kernel/ebin/disk_log_1.beam
index 0a781001dc..543620a65f 100644
--- a/bootstrap/lib/kernel/ebin/disk_log_1.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log_1.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/disk_log_server.beam b/bootstrap/lib/kernel/ebin/disk_log_server.beam
index 3b961dcefa..18b975fbaf 100644
--- a/bootstrap/lib/kernel/ebin/disk_log_server.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/disk_log_sup.beam b/bootstrap/lib/kernel/ebin/disk_log_sup.beam
index 67099f7212..570af6d495 100644
--- a/bootstrap/lib/kernel/ebin/disk_log_sup.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log_sup.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/dist_ac.beam b/bootstrap/lib/kernel/ebin/dist_ac.beam
index e82fc7dc75..d1f54037e5 100644
--- a/bootstrap/lib/kernel/ebin/dist_ac.beam
+++ b/bootstrap/lib/kernel/ebin/dist_ac.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/dist_util.beam b/bootstrap/lib/kernel/ebin/dist_util.beam
index a3d7abe5b6..2a9c561e94 100644
--- a/bootstrap/lib/kernel/ebin/dist_util.beam
+++ b/bootstrap/lib/kernel/ebin/dist_util.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_boot_server.beam b/bootstrap/lib/kernel/ebin/erl_boot_server.beam
index 801e3d8b88..60382dd4ec 100644
--- a/bootstrap/lib/kernel/ebin/erl_boot_server.beam
+++ b/bootstrap/lib/kernel/ebin/erl_boot_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_compile_server.beam b/bootstrap/lib/kernel/ebin/erl_compile_server.beam
index 28d79c2efb..d11096d521 100644
--- a/bootstrap/lib/kernel/ebin/erl_compile_server.beam
+++ b/bootstrap/lib/kernel/ebin/erl_compile_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_ddll.beam b/bootstrap/lib/kernel/ebin/erl_ddll.beam
index c04bc43872..d1d3bc0d3b 100644
--- a/bootstrap/lib/kernel/ebin/erl_ddll.beam
+++ b/bootstrap/lib/kernel/ebin/erl_ddll.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_distribution.beam b/bootstrap/lib/kernel/ebin/erl_distribution.beam
index 4490f9c81f..4c499ee49b 100644
--- a/bootstrap/lib/kernel/ebin/erl_distribution.beam
+++ b/bootstrap/lib/kernel/ebin/erl_distribution.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_epmd.beam b/bootstrap/lib/kernel/ebin/erl_epmd.beam
index 57dd1b8735..0a9b03732e 100644
--- a/bootstrap/lib/kernel/ebin/erl_epmd.beam
+++ b/bootstrap/lib/kernel/ebin/erl_epmd.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_reply.beam b/bootstrap/lib/kernel/ebin/erl_reply.beam
index 7658a35642..823ba8cf2c 100644
--- a/bootstrap/lib/kernel/ebin/erl_reply.beam
+++ b/bootstrap/lib/kernel/ebin/erl_reply.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_signal_handler.beam b/bootstrap/lib/kernel/ebin/erl_signal_handler.beam
index da03769e2e..f15afbdc68 100644
--- a/bootstrap/lib/kernel/ebin/erl_signal_handler.beam
+++ b/bootstrap/lib/kernel/ebin/erl_signal_handler.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erpc.beam b/bootstrap/lib/kernel/ebin/erpc.beam
new file mode 100644
index 0000000000..0f87cabc51
--- /dev/null
+++ b/bootstrap/lib/kernel/ebin/erpc.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/error_handler.beam b/bootstrap/lib/kernel/ebin/error_handler.beam
index 19739b0043..7e06588ea2 100644
--- a/bootstrap/lib/kernel/ebin/error_handler.beam
+++ b/bootstrap/lib/kernel/ebin/error_handler.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/error_logger.beam b/bootstrap/lib/kernel/ebin/error_logger.beam
index 0af2d1513e..e7e5d58a44 100644
--- a/bootstrap/lib/kernel/ebin/error_logger.beam
+++ b/bootstrap/lib/kernel/ebin/error_logger.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erts_debug.beam b/bootstrap/lib/kernel/ebin/erts_debug.beam
index e0b759eee3..6d4eaa9be5 100644
--- a/bootstrap/lib/kernel/ebin/erts_debug.beam
+++ b/bootstrap/lib/kernel/ebin/erts_debug.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/file.beam b/bootstrap/lib/kernel/ebin/file.beam
index a7a82a1f12..18006a9cc4 100644
--- a/bootstrap/lib/kernel/ebin/file.beam
+++ b/bootstrap/lib/kernel/ebin/file.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/file_io_server.beam b/bootstrap/lib/kernel/ebin/file_io_server.beam
index b8f1272be4..dc6175e504 100644
--- a/bootstrap/lib/kernel/ebin/file_io_server.beam
+++ b/bootstrap/lib/kernel/ebin/file_io_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/file_server.beam b/bootstrap/lib/kernel/ebin/file_server.beam
index cc2face79c..8701a0ef65 100644
--- a/bootstrap/lib/kernel/ebin/file_server.beam
+++ b/bootstrap/lib/kernel/ebin/file_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_sctp.beam b/bootstrap/lib/kernel/ebin/gen_sctp.beam
index 9fc5800f0c..4ee051d780 100644
--- a/bootstrap/lib/kernel/ebin/gen_sctp.beam
+++ b/bootstrap/lib/kernel/ebin/gen_sctp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_tcp.beam b/bootstrap/lib/kernel/ebin/gen_tcp.beam
index 736b28c68f..6d4d0a2fa6 100644
--- a/bootstrap/lib/kernel/ebin/gen_tcp.beam
+++ b/bootstrap/lib/kernel/ebin/gen_tcp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam b/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam
new file mode 100644
index 0000000000..3cbec1359e
--- /dev/null
+++ b/bootstrap/lib/kernel/ebin/gen_tcp_socket.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/gen_udp.beam b/bootstrap/lib/kernel/ebin/gen_udp.beam
index 0d6541ee53..d0555b4dc3 100644
--- a/bootstrap/lib/kernel/ebin/gen_udp.beam
+++ b/bootstrap/lib/kernel/ebin/gen_udp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/global.beam b/bootstrap/lib/kernel/ebin/global.beam
index aadb5bf080..12a33d895b 100644
--- a/bootstrap/lib/kernel/ebin/global.beam
+++ b/bootstrap/lib/kernel/ebin/global.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/global_group.beam b/bootstrap/lib/kernel/ebin/global_group.beam
index 83885cdeb5..e0c1cd7f6f 100644
--- a/bootstrap/lib/kernel/ebin/global_group.beam
+++ b/bootstrap/lib/kernel/ebin/global_group.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/global_search.beam b/bootstrap/lib/kernel/ebin/global_search.beam
index a94d9da454..6c5a9d207c 100644
--- a/bootstrap/lib/kernel/ebin/global_search.beam
+++ b/bootstrap/lib/kernel/ebin/global_search.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/group.beam b/bootstrap/lib/kernel/ebin/group.beam
index dcec55421c..3cb9f92447 100644
--- a/bootstrap/lib/kernel/ebin/group.beam
+++ b/bootstrap/lib/kernel/ebin/group.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/group_history.beam b/bootstrap/lib/kernel/ebin/group_history.beam
index b9df7cfc14..e07fdffc5a 100644
--- a/bootstrap/lib/kernel/ebin/group_history.beam
+++ b/bootstrap/lib/kernel/ebin/group_history.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/heart.beam b/bootstrap/lib/kernel/ebin/heart.beam
index 1b63142893..39798eca70 100644
--- a/bootstrap/lib/kernel/ebin/heart.beam
+++ b/bootstrap/lib/kernel/ebin/heart.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam b/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
index 860bb73d0e..1c2262a7a5 100644
--- a/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
+++ b/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet.beam b/bootstrap/lib/kernel/ebin/inet.beam
index 45d7b10040..37130e7a9a 100644
--- a/bootstrap/lib/kernel/ebin/inet.beam
+++ b/bootstrap/lib/kernel/ebin/inet.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet6_sctp.beam b/bootstrap/lib/kernel/ebin/inet6_sctp.beam
index a6220a9d69..d1f974115f 100644
--- a/bootstrap/lib/kernel/ebin/inet6_sctp.beam
+++ b/bootstrap/lib/kernel/ebin/inet6_sctp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet6_tcp.beam b/bootstrap/lib/kernel/ebin/inet6_tcp.beam
index 7cacde3399..a48597421f 100644
--- a/bootstrap/lib/kernel/ebin/inet6_tcp.beam
+++ b/bootstrap/lib/kernel/ebin/inet6_tcp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam b/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam
index d1ca0b4f0d..424f9cd8ac 100644
--- a/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam
+++ b/bootstrap/lib/kernel/ebin/inet6_tcp_dist.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet6_udp.beam b/bootstrap/lib/kernel/ebin/inet6_udp.beam
index ac4be100e8..0bfdaa353b 100644
--- a/bootstrap/lib/kernel/ebin/inet6_udp.beam
+++ b/bootstrap/lib/kernel/ebin/inet6_udp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_config.beam b/bootstrap/lib/kernel/ebin/inet_config.beam
index c44b389b75..a75aabf04f 100644
--- a/bootstrap/lib/kernel/ebin/inet_config.beam
+++ b/bootstrap/lib/kernel/ebin/inet_config.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_db.beam b/bootstrap/lib/kernel/ebin/inet_db.beam
index a835fa1a74..35df8327d7 100644
--- a/bootstrap/lib/kernel/ebin/inet_db.beam
+++ b/bootstrap/lib/kernel/ebin/inet_db.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_dns.beam b/bootstrap/lib/kernel/ebin/inet_dns.beam
index cf68913d1b..ecabcd9569 100644
--- a/bootstrap/lib/kernel/ebin/inet_dns.beam
+++ b/bootstrap/lib/kernel/ebin/inet_dns.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_gethost_native.beam b/bootstrap/lib/kernel/ebin/inet_gethost_native.beam
index 8166eca5de..ecfb340eae 100644
--- a/bootstrap/lib/kernel/ebin/inet_gethost_native.beam
+++ b/bootstrap/lib/kernel/ebin/inet_gethost_native.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_hosts.beam b/bootstrap/lib/kernel/ebin/inet_hosts.beam
index 6797eecb29..728351bb73 100644
--- a/bootstrap/lib/kernel/ebin/inet_hosts.beam
+++ b/bootstrap/lib/kernel/ebin/inet_hosts.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_parse.beam b/bootstrap/lib/kernel/ebin/inet_parse.beam
index e8935134b5..9557186443 100644
--- a/bootstrap/lib/kernel/ebin/inet_parse.beam
+++ b/bootstrap/lib/kernel/ebin/inet_parse.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_res.beam b/bootstrap/lib/kernel/ebin/inet_res.beam
index e3b4753885..fe972672aa 100644
--- a/bootstrap/lib/kernel/ebin/inet_res.beam
+++ b/bootstrap/lib/kernel/ebin/inet_res.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_sctp.beam b/bootstrap/lib/kernel/ebin/inet_sctp.beam
index 0e659bd738..9157154021 100644
--- a/bootstrap/lib/kernel/ebin/inet_sctp.beam
+++ b/bootstrap/lib/kernel/ebin/inet_sctp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_tcp.beam b/bootstrap/lib/kernel/ebin/inet_tcp.beam
index c273d47012..1d61c1e5ed 100644
--- a/bootstrap/lib/kernel/ebin/inet_tcp.beam
+++ b/bootstrap/lib/kernel/ebin/inet_tcp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam b/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam
index d09aa250c8..496fc213f0 100644
--- a/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam
+++ b/bootstrap/lib/kernel/ebin/inet_tcp_dist.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_udp.beam b/bootstrap/lib/kernel/ebin/inet_udp.beam
index 5853bde54f..5ca6c7adc2 100644
--- a/bootstrap/lib/kernel/ebin/inet_udp.beam
+++ b/bootstrap/lib/kernel/ebin/inet_udp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/kernel.app b/bootstrap/lib/kernel/ebin/kernel.app
index 045fdcd33b..ac5d0dbc49 100644
--- a/bootstrap/lib/kernel/ebin/kernel.app
+++ b/bootstrap/lib/kernel/ebin/kernel.app
@@ -22,7 +22,7 @@
{application, kernel,
[
{description, "ERTS CXC 138 10"},
- {vsn, "6.5.1"},
+ {vsn, "7.2"},
{modules, [application,
application_controller,
application_master,
@@ -36,6 +36,7 @@
erl_distribution,
erl_reply,
erl_signal_handler,
+ erpc,
error_handler,
error_logger,
file,
@@ -95,6 +96,7 @@
gen_tcp,
gen_udp,
gen_sctp,
+ gen_tcp_socket,
inet,
inet_db,
inet_dns,
@@ -103,6 +105,7 @@
inet_tcp,
inet_udp,
inet_sctp,
+ pg,
pg2,
raw_file_io,
raw_file_io_compressed,
@@ -112,6 +115,7 @@
raw_file_io_list,
raw_file_io_raw,
seq_trace,
+ socket,
standard_error,
wrap_log_reader]},
{registered, [application_controller,
@@ -143,12 +147,14 @@
ddll_server,
erl_epmd,
inet_db,
+ pg,
pg2]},
{applications, []},
{env, [{logger_level, notice},
- {logger_sasl_compatible, false}
+ {logger_sasl_compatible, false},
+ {shell_docs_ansi,auto}
]},
{mod, {kernel, []}},
- {runtime_dependencies, ["erts-10.6", "stdlib-3.5", "sasl-3.0"]}
+ {runtime_dependencies, ["erts-11.0", "stdlib-3.13", "sasl-3.0"]}
]
}.
diff --git a/bootstrap/lib/kernel/ebin/kernel.beam b/bootstrap/lib/kernel/ebin/kernel.beam
index 5b93fe805b..65c566fe59 100644
--- a/bootstrap/lib/kernel/ebin/kernel.beam
+++ b/bootstrap/lib/kernel/ebin/kernel.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/kernel_config.beam b/bootstrap/lib/kernel/ebin/kernel_config.beam
index e749cc6c96..e310d47355 100644
--- a/bootstrap/lib/kernel/ebin/kernel_config.beam
+++ b/bootstrap/lib/kernel/ebin/kernel_config.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/kernel_refc.beam b/bootstrap/lib/kernel/ebin/kernel_refc.beam
index 04306c6e2c..0da8237b7f 100644
--- a/bootstrap/lib/kernel/ebin/kernel_refc.beam
+++ b/bootstrap/lib/kernel/ebin/kernel_refc.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/local_tcp.beam b/bootstrap/lib/kernel/ebin/local_tcp.beam
index 2958d19524..e25175b07b 100644
--- a/bootstrap/lib/kernel/ebin/local_tcp.beam
+++ b/bootstrap/lib/kernel/ebin/local_tcp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/local_udp.beam b/bootstrap/lib/kernel/ebin/local_udp.beam
index 33ed76e5cd..13584f7024 100644
--- a/bootstrap/lib/kernel/ebin/local_udp.beam
+++ b/bootstrap/lib/kernel/ebin/local_udp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger.beam b/bootstrap/lib/kernel/ebin/logger.beam
index 8cd5613a2d..8844841373 100644
--- a/bootstrap/lib/kernel/ebin/logger.beam
+++ b/bootstrap/lib/kernel/ebin/logger.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_backend.beam b/bootstrap/lib/kernel/ebin/logger_backend.beam
index 40a3c4d80a..f735d40960 100644
--- a/bootstrap/lib/kernel/ebin/logger_backend.beam
+++ b/bootstrap/lib/kernel/ebin/logger_backend.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_config.beam b/bootstrap/lib/kernel/ebin/logger_config.beam
index b000b83ce8..e8f35ea43e 100644
--- a/bootstrap/lib/kernel/ebin/logger_config.beam
+++ b/bootstrap/lib/kernel/ebin/logger_config.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_disk_log_h.beam b/bootstrap/lib/kernel/ebin/logger_disk_log_h.beam
index dd0ca5f204..97161d656f 100644
--- a/bootstrap/lib/kernel/ebin/logger_disk_log_h.beam
+++ b/bootstrap/lib/kernel/ebin/logger_disk_log_h.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_filters.beam b/bootstrap/lib/kernel/ebin/logger_filters.beam
index 2d8035278d..ac28d8c9d0 100644
--- a/bootstrap/lib/kernel/ebin/logger_filters.beam
+++ b/bootstrap/lib/kernel/ebin/logger_filters.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_formatter.beam b/bootstrap/lib/kernel/ebin/logger_formatter.beam
index e3e40460f0..eb3ddf6ab9 100644
--- a/bootstrap/lib/kernel/ebin/logger_formatter.beam
+++ b/bootstrap/lib/kernel/ebin/logger_formatter.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_h_common.beam b/bootstrap/lib/kernel/ebin/logger_h_common.beam
index 9e8c25b6b9..d565915fed 100644
--- a/bootstrap/lib/kernel/ebin/logger_h_common.beam
+++ b/bootstrap/lib/kernel/ebin/logger_h_common.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_handler_watcher.beam b/bootstrap/lib/kernel/ebin/logger_handler_watcher.beam
index fb20fd1818..4fa97161a3 100644
--- a/bootstrap/lib/kernel/ebin/logger_handler_watcher.beam
+++ b/bootstrap/lib/kernel/ebin/logger_handler_watcher.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_olp.beam b/bootstrap/lib/kernel/ebin/logger_olp.beam
index 9a91035515..bbeff97faf 100644
--- a/bootstrap/lib/kernel/ebin/logger_olp.beam
+++ b/bootstrap/lib/kernel/ebin/logger_olp.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_proxy.beam b/bootstrap/lib/kernel/ebin/logger_proxy.beam
index f5680de1e3..24222341cb 100644
--- a/bootstrap/lib/kernel/ebin/logger_proxy.beam
+++ b/bootstrap/lib/kernel/ebin/logger_proxy.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_server.beam b/bootstrap/lib/kernel/ebin/logger_server.beam
index dcff9beac7..f85bbe4234 100644
--- a/bootstrap/lib/kernel/ebin/logger_server.beam
+++ b/bootstrap/lib/kernel/ebin/logger_server.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_simple_h.beam b/bootstrap/lib/kernel/ebin/logger_simple_h.beam
index 583c77b290..a624bdd45f 100644
--- a/bootstrap/lib/kernel/ebin/logger_simple_h.beam
+++ b/bootstrap/lib/kernel/ebin/logger_simple_h.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_std_h.beam b/bootstrap/lib/kernel/ebin/logger_std_h.beam
index 679e2573c2..b9644e5e2b 100644
--- a/bootstrap/lib/kernel/ebin/logger_std_h.beam
+++ b/bootstrap/lib/kernel/ebin/logger_std_h.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/logger_sup.beam b/bootstrap/lib/kernel/ebin/logger_sup.beam
index 60192ef4a1..d866563a79 100644
--- a/bootstrap/lib/kernel/ebin/logger_sup.beam
+++ b/bootstrap/lib/kernel/ebin/logger_sup.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/net.beam b/bootstrap/lib/kernel/ebin/net.beam
index 434c08737f..e14ae8eb5c 100644
--- a/bootstrap/lib/kernel/ebin/net.beam
+++ b/bootstrap/lib/kernel/ebin/net.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/net_adm.beam b/bootstrap/lib/kernel/ebin/net_adm.beam
index 5810577c6a..ef65337bda 100644
--- a/bootstrap/lib/kernel/ebin/net_adm.beam
+++ b/bootstrap/lib/kernel/ebin/net_adm.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/net_kernel.beam b/bootstrap/lib/kernel/ebin/net_kernel.beam
index 845a10c94f..e71934422f 100644
--- a/bootstrap/lib/kernel/ebin/net_kernel.beam
+++ b/bootstrap/lib/kernel/ebin/net_kernel.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/os.beam b/bootstrap/lib/kernel/ebin/os.beam
index f05007041b..261456162d 100644
--- a/bootstrap/lib/kernel/ebin/os.beam
+++ b/bootstrap/lib/kernel/ebin/os.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/pg.beam b/bootstrap/lib/kernel/ebin/pg.beam
new file mode 100644
index 0000000000..9375c68a46
--- /dev/null
+++ b/bootstrap/lib/kernel/ebin/pg.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/pg2.beam b/bootstrap/lib/kernel/ebin/pg2.beam
index d293ea18f3..e2e587a71d 100644
--- a/bootstrap/lib/kernel/ebin/pg2.beam
+++ b/bootstrap/lib/kernel/ebin/pg2.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/ram_file.beam b/bootstrap/lib/kernel/ebin/ram_file.beam
index 02c4c6454a..cd32cce3c1 100644
--- a/bootstrap/lib/kernel/ebin/ram_file.beam
+++ b/bootstrap/lib/kernel/ebin/ram_file.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io.beam b/bootstrap/lib/kernel/ebin/raw_file_io.beam
index 16dc314ba5..2617ebe6af 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_compressed.beam b/bootstrap/lib/kernel/ebin/raw_file_io_compressed.beam
index 4f42134f4a..2861389eff 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_compressed.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_compressed.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam b/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam
index 11b8f4a6db..f3504c5c9f 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_deflate.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_delayed.beam b/bootstrap/lib/kernel/ebin/raw_file_io_delayed.beam
index ec7492f62a..82c49b4d8e 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_delayed.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_delayed.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_inflate.beam b/bootstrap/lib/kernel/ebin/raw_file_io_inflate.beam
index 7c47f2cf10..46f8033560 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_inflate.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_inflate.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_list.beam b/bootstrap/lib/kernel/ebin/raw_file_io_list.beam
index b544fd394e..5522898428 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_list.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_list.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/raw_file_io_raw.beam b/bootstrap/lib/kernel/ebin/raw_file_io_raw.beam
index b47a67055d..ef11ec329c 100644
--- a/bootstrap/lib/kernel/ebin/raw_file_io_raw.beam
+++ b/bootstrap/lib/kernel/ebin/raw_file_io_raw.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/rpc.beam b/bootstrap/lib/kernel/ebin/rpc.beam
index b53525e2ca..00740253d0 100644
--- a/bootstrap/lib/kernel/ebin/rpc.beam
+++ b/bootstrap/lib/kernel/ebin/rpc.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/seq_trace.beam b/bootstrap/lib/kernel/ebin/seq_trace.beam
index f81a39ddc0..3781fee0d9 100644
--- a/bootstrap/lib/kernel/ebin/seq_trace.beam
+++ b/bootstrap/lib/kernel/ebin/seq_trace.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/socket.beam b/bootstrap/lib/kernel/ebin/socket.beam
new file mode 100644
index 0000000000..051885341c
--- /dev/null
+++ b/bootstrap/lib/kernel/ebin/socket.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/standard_error.beam b/bootstrap/lib/kernel/ebin/standard_error.beam
index 18582d22f2..c6f4a0232d 100644
--- a/bootstrap/lib/kernel/ebin/standard_error.beam
+++ b/bootstrap/lib/kernel/ebin/standard_error.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/user.beam b/bootstrap/lib/kernel/ebin/user.beam
index 8214d9d6aa..24c2045e56 100644
--- a/bootstrap/lib/kernel/ebin/user.beam
+++ b/bootstrap/lib/kernel/ebin/user.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/user_drv.beam b/bootstrap/lib/kernel/ebin/user_drv.beam
index 43df5afa36..8a598eff84 100644
--- a/bootstrap/lib/kernel/ebin/user_drv.beam
+++ b/bootstrap/lib/kernel/ebin/user_drv.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/user_sup.beam b/bootstrap/lib/kernel/ebin/user_sup.beam
index c82fca70da..05c7f80e5d 100644
--- a/bootstrap/lib/kernel/ebin/user_sup.beam
+++ b/bootstrap/lib/kernel/ebin/user_sup.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/wrap_log_reader.beam b/bootstrap/lib/kernel/ebin/wrap_log_reader.beam
index ceb8dd9a78..9ac6df5c7a 100644
--- a/bootstrap/lib/kernel/ebin/wrap_log_reader.beam
+++ b/bootstrap/lib/kernel/ebin/wrap_log_reader.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/include/dist.hrl b/bootstrap/lib/kernel/include/dist.hrl
index f06fc328d7..c59bd78523 100644
--- a/bootstrap/lib/kernel/include/dist.hrl
+++ b/bootstrap/lib/kernel/include/dist.hrl
@@ -44,7 +44,19 @@
-define(DFLAG_BIG_SEQTRACE_LABELS, 16#100000).
%% -define(DFLAG_NO_MAGIC, 16#200000). %% Used internally only
-define(DFLAG_EXIT_PAYLOAD, 16#400000).
--define(DFLAG_FRAGMENTS, 16#800000).
+-define(DFLAG_FRAGMENTS, 16#00800000).
+-define(DFLAG_HANDSHAKE_23, 16#01000000).
+-define(DFLAG_RESERVED, 16#fe000000).
+-define(DFLAG_SPAWN, 16#100000000).
+-define(DFLAG_NAME_ME, 16#200000000).
%% Also update dflag2str() in ../src/dist_util.erl
%% when adding flags...
+
+
+-define(ERL_DIST_VER_5, 5). % OTP-22 or (much) older
+-define(ERL_DIST_VER_6, 6). % OTP-23 (or maybe newer?)
+
+-define(ERL_DIST_VER_LOW, ?ERL_DIST_VER_5).
+-define(ERL_DIST_VER_HIGH, ?ERL_DIST_VER_6).
+
diff --git a/bootstrap/lib/kernel/include/dist_util.hrl b/bootstrap/lib/kernel/include/dist_util.hrl
index 56f775f060..1eebb56030 100644
--- a/bootstrap/lib/kernel/include/dist_util.hrl
+++ b/bootstrap/lib/kernel/include/dist_util.hrl
@@ -84,7 +84,11 @@
f_handshake_complete, %% Notify handshake complete
add_flags, %% dflags to add
reject_flags, %% dflags not to use (not all can be rejected)
- require_flags %% dflags that are required
+ require_flags, %% dflags that are required
+
+ %% New in kernel-@master@ (OTP-23.0)
+ this_creation,
+ other_creation
}).
diff --git a/bootstrap/lib/kernel/include/eep48.hrl b/bootstrap/lib/kernel/include/eep48.hrl
new file mode 100644
index 0000000000..2ce9a1430a
--- /dev/null
+++ b/bootstrap/lib/kernel/include/eep48.hrl
@@ -0,0 +1,14 @@
+-define(NATIVE_FORMAT,<<"application/erlang+html">>).
+-define(CURR_DOC_VERSION, {1,0,0}).
+-record(docs_v1, {anno,
+ beam_language = erlang,
+ format = ?NATIVE_FORMAT,
+ module_doc,
+ metadata = #{ otp_doc_vsn => ?CURR_DOC_VERSION },
+ docs}).
+
+-record(docs_v1_entry, {kind_name_arity,
+ anno,
+ signature,
+ doc,
+ metadata}).
diff --git a/bootstrap/lib/kernel/include/logger.hrl b/bootstrap/lib/kernel/include/logger.hrl
index b09977e0f2..bd17f7efc4 100644
--- a/bootstrap/lib/kernel/include/logger.hrl
+++ b/bootstrap/lib/kernel/include/logger.hrl
@@ -46,7 +46,7 @@
-define(DO_LOG(Level,Args),
case logger:allow(Level,?MODULE) of
true ->
- apply(logger,macro_log,[?LOCATION,Level|Args]);
+ erlang:apply(logger,macro_log,[?LOCATION,Level|Args]);
false ->
ok
end).
diff --git a/bootstrap/lib/stdlib/ebin/array.beam b/bootstrap/lib/stdlib/ebin/array.beam
index 9e783452f5..d8819f13e6 100644
--- a/bootstrap/lib/stdlib/ebin/array.beam
+++ b/bootstrap/lib/stdlib/ebin/array.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/base64.beam b/bootstrap/lib/stdlib/ebin/base64.beam
index 5872970da7..3893851b25 100644
--- a/bootstrap/lib/stdlib/ebin/base64.beam
+++ b/bootstrap/lib/stdlib/ebin/base64.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/beam_lib.beam b/bootstrap/lib/stdlib/ebin/beam_lib.beam
index a96ccb6f8c..cb3b35e6ba 100644
--- a/bootstrap/lib/stdlib/ebin/beam_lib.beam
+++ b/bootstrap/lib/stdlib/ebin/beam_lib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/binary.beam b/bootstrap/lib/stdlib/ebin/binary.beam
index fc1bbe163a..451d95746b 100644
--- a/bootstrap/lib/stdlib/ebin/binary.beam
+++ b/bootstrap/lib/stdlib/ebin/binary.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/c.beam b/bootstrap/lib/stdlib/ebin/c.beam
index 17e061b1d0..6407a36fda 100644
--- a/bootstrap/lib/stdlib/ebin/c.beam
+++ b/bootstrap/lib/stdlib/ebin/c.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/calendar.beam b/bootstrap/lib/stdlib/ebin/calendar.beam
index 66c6b69246..3515420af0 100644
--- a/bootstrap/lib/stdlib/ebin/calendar.beam
+++ b/bootstrap/lib/stdlib/ebin/calendar.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets.beam b/bootstrap/lib/stdlib/ebin/dets.beam
index 2eadde21ff..20db2e7d82 100644
--- a/bootstrap/lib/stdlib/ebin/dets.beam
+++ b/bootstrap/lib/stdlib/ebin/dets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_server.beam b/bootstrap/lib/stdlib/ebin/dets_server.beam
index 8cd247e3be..f704dfb58e 100644
--- a/bootstrap/lib/stdlib/ebin/dets_server.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_server.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_sup.beam b/bootstrap/lib/stdlib/ebin/dets_sup.beam
index 0b9fb6379f..c14efa6c28 100644
--- a/bootstrap/lib/stdlib/ebin/dets_sup.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_sup.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_utils.beam b/bootstrap/lib/stdlib/ebin/dets_utils.beam
index e38eb384a1..1d3481448e 100644
--- a/bootstrap/lib/stdlib/ebin/dets_utils.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_utils.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_v9.beam b/bootstrap/lib/stdlib/ebin/dets_v9.beam
index f73509ae3f..69822567ef 100644
--- a/bootstrap/lib/stdlib/ebin/dets_v9.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_v9.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dict.beam b/bootstrap/lib/stdlib/ebin/dict.beam
index dc390024a4..6b0b4f7327 100644
--- a/bootstrap/lib/stdlib/ebin/dict.beam
+++ b/bootstrap/lib/stdlib/ebin/dict.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/digraph.beam b/bootstrap/lib/stdlib/ebin/digraph.beam
index 60abe020fd..f9a7d68504 100644
--- a/bootstrap/lib/stdlib/ebin/digraph.beam
+++ b/bootstrap/lib/stdlib/ebin/digraph.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/digraph_utils.beam b/bootstrap/lib/stdlib/ebin/digraph_utils.beam
index fabe782d90..0c0cc94b09 100644
--- a/bootstrap/lib/stdlib/ebin/digraph_utils.beam
+++ b/bootstrap/lib/stdlib/ebin/digraph_utils.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/edlin.beam b/bootstrap/lib/stdlib/ebin/edlin.beam
index b6a7ecc9a5..cbc8a9d3d0 100644
--- a/bootstrap/lib/stdlib/ebin/edlin.beam
+++ b/bootstrap/lib/stdlib/ebin/edlin.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/edlin_expand.beam b/bootstrap/lib/stdlib/ebin/edlin_expand.beam
index 865066c3ac..32833b51da 100644
--- a/bootstrap/lib/stdlib/ebin/edlin_expand.beam
+++ b/bootstrap/lib/stdlib/ebin/edlin_expand.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/epp.beam b/bootstrap/lib/stdlib/ebin/epp.beam
index e3d8c21edf..43050c392e 100644
--- a/bootstrap/lib/stdlib/ebin/epp.beam
+++ b/bootstrap/lib/stdlib/ebin/epp.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_abstract_code.beam b/bootstrap/lib/stdlib/ebin/erl_abstract_code.beam
index 973ad9fb80..0759d1a464 100644
--- a/bootstrap/lib/stdlib/ebin/erl_abstract_code.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_abstract_code.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_anno.beam b/bootstrap/lib/stdlib/ebin/erl_anno.beam
index a40fcb3f92..f422046b72 100644
--- a/bootstrap/lib/stdlib/ebin/erl_anno.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_anno.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_bits.beam b/bootstrap/lib/stdlib/ebin/erl_bits.beam
index 1beb0d5ebb..8714ea5f00 100644
--- a/bootstrap/lib/stdlib/ebin/erl_bits.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_bits.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_compile.beam b/bootstrap/lib/stdlib/ebin/erl_compile.beam
index 5dd5691b3b..7c6f7f924d 100644
--- a/bootstrap/lib/stdlib/ebin/erl_compile.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_compile.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_error.beam b/bootstrap/lib/stdlib/ebin/erl_error.beam
index 5d1b91aeef..fadaecea89 100644
--- a/bootstrap/lib/stdlib/ebin/erl_error.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_error.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_eval.beam b/bootstrap/lib/stdlib/ebin/erl_eval.beam
index 5ef1521371..b62581e20a 100644
--- a/bootstrap/lib/stdlib/ebin/erl_eval.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_eval.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_expand_records.beam b/bootstrap/lib/stdlib/ebin/erl_expand_records.beam
index e527f98776..c196ae05cf 100644
--- a/bootstrap/lib/stdlib/ebin/erl_expand_records.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_expand_records.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_internal.beam b/bootstrap/lib/stdlib/ebin/erl_internal.beam
index 9015ad8936..e48f3a1b3e 100644
--- a/bootstrap/lib/stdlib/ebin/erl_internal.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_internal.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_lint.beam b/bootstrap/lib/stdlib/ebin/erl_lint.beam
index 3004cbf251..11c5f1c1cf 100644
--- a/bootstrap/lib/stdlib/ebin/erl_lint.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_lint.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_parse.beam b/bootstrap/lib/stdlib/ebin/erl_parse.beam
index f713498929..489c7e65b6 100644
--- a/bootstrap/lib/stdlib/ebin/erl_parse.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_parse.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_posix_msg.beam b/bootstrap/lib/stdlib/ebin/erl_posix_msg.beam
index 5f6c03bcae..1b3edde848 100644
--- a/bootstrap/lib/stdlib/ebin/erl_posix_msg.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_posix_msg.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_pp.beam b/bootstrap/lib/stdlib/ebin/erl_pp.beam
index 31bddf899c..6cfad9980b 100644
--- a/bootstrap/lib/stdlib/ebin/erl_pp.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_pp.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_scan.beam b/bootstrap/lib/stdlib/ebin/erl_scan.beam
index 18538620b7..1e3b49e695 100644
--- a/bootstrap/lib/stdlib/ebin/erl_scan.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_scan.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_tar.beam b/bootstrap/lib/stdlib/ebin/erl_tar.beam
index 3945350214..8fecbca62a 100644
--- a/bootstrap/lib/stdlib/ebin/erl_tar.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_tar.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam b/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam
index 64de26f7a4..a3efb7f04b 100644
--- a/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam
+++ b/bootstrap/lib/stdlib/ebin/error_logger_file_h.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam b/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam
index e7ee41cc2e..d6d5ad85ab 100644
--- a/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam
+++ b/bootstrap/lib/stdlib/ebin/error_logger_tty_h.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/escript.beam b/bootstrap/lib/stdlib/ebin/escript.beam
index b1c6f58180..3e535cdd7d 100644
--- a/bootstrap/lib/stdlib/ebin/escript.beam
+++ b/bootstrap/lib/stdlib/ebin/escript.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/ets.beam b/bootstrap/lib/stdlib/ebin/ets.beam
index 350316dfcc..f80809f26e 100644
--- a/bootstrap/lib/stdlib/ebin/ets.beam
+++ b/bootstrap/lib/stdlib/ebin/ets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/eval_bits.beam b/bootstrap/lib/stdlib/ebin/eval_bits.beam
index 08f86f86ff..92a7e8d1de 100644
--- a/bootstrap/lib/stdlib/ebin/eval_bits.beam
+++ b/bootstrap/lib/stdlib/ebin/eval_bits.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/file_sorter.beam b/bootstrap/lib/stdlib/ebin/file_sorter.beam
index 0a42a40d7e..672a43d820 100644
--- a/bootstrap/lib/stdlib/ebin/file_sorter.beam
+++ b/bootstrap/lib/stdlib/ebin/file_sorter.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/filelib.beam b/bootstrap/lib/stdlib/ebin/filelib.beam
index fd719cd85d..06d958a89b 100644
--- a/bootstrap/lib/stdlib/ebin/filelib.beam
+++ b/bootstrap/lib/stdlib/ebin/filelib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/filename.beam b/bootstrap/lib/stdlib/ebin/filename.beam
index d353a11f3e..68fa20fdb9 100644
--- a/bootstrap/lib/stdlib/ebin/filename.beam
+++ b/bootstrap/lib/stdlib/ebin/filename.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gb_sets.beam b/bootstrap/lib/stdlib/ebin/gb_sets.beam
index 8d1e2c11e0..298f49f6bc 100644
--- a/bootstrap/lib/stdlib/ebin/gb_sets.beam
+++ b/bootstrap/lib/stdlib/ebin/gb_sets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gb_trees.beam b/bootstrap/lib/stdlib/ebin/gb_trees.beam
index 0aae509cb6..d5b69b24e1 100644
--- a/bootstrap/lib/stdlib/ebin/gb_trees.beam
+++ b/bootstrap/lib/stdlib/ebin/gb_trees.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen.beam b/bootstrap/lib/stdlib/ebin/gen.beam
index 85a9c25618..574d24bdbf 100644
--- a/bootstrap/lib/stdlib/ebin/gen.beam
+++ b/bootstrap/lib/stdlib/ebin/gen.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_event.beam b/bootstrap/lib/stdlib/ebin/gen_event.beam
index f3376d2ee2..f3c5564736 100644
--- a/bootstrap/lib/stdlib/ebin/gen_event.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_event.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_fsm.beam b/bootstrap/lib/stdlib/ebin/gen_fsm.beam
index 5c4a0aa3d5..5d7a91dc91 100644
--- a/bootstrap/lib/stdlib/ebin/gen_fsm.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_fsm.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_server.beam b/bootstrap/lib/stdlib/ebin/gen_server.beam
index 06c3d6de19..55b21213b9 100644
--- a/bootstrap/lib/stdlib/ebin/gen_server.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_server.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/gen_statem.beam b/bootstrap/lib/stdlib/ebin/gen_statem.beam
index e34bbe1efd..60dc5eab0d 100644
--- a/bootstrap/lib/stdlib/ebin/gen_statem.beam
+++ b/bootstrap/lib/stdlib/ebin/gen_statem.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io.beam b/bootstrap/lib/stdlib/ebin/io.beam
index 631863b587..60fbdadc21 100644
--- a/bootstrap/lib/stdlib/ebin/io.beam
+++ b/bootstrap/lib/stdlib/ebin/io.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib.beam b/bootstrap/lib/stdlib/ebin/io_lib.beam
index 45692978e4..94380578cc 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib_format.beam b/bootstrap/lib/stdlib/ebin/io_lib_format.beam
index 66047b5070..9925476691 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib_format.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib_format.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib_fread.beam b/bootstrap/lib/stdlib/ebin/io_lib_fread.beam
index 22690ce685..e180b1fcb3 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib_fread.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib_fread.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam b/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam
index b0f4177d71..7c821ff6c1 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/lists.beam b/bootstrap/lib/stdlib/ebin/lists.beam
index 5a330bece0..722d493bf6 100644
--- a/bootstrap/lib/stdlib/ebin/lists.beam
+++ b/bootstrap/lib/stdlib/ebin/lists.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/log_mf_h.beam b/bootstrap/lib/stdlib/ebin/log_mf_h.beam
index b812621c0a..0304375c3e 100644
--- a/bootstrap/lib/stdlib/ebin/log_mf_h.beam
+++ b/bootstrap/lib/stdlib/ebin/log_mf_h.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/maps.beam b/bootstrap/lib/stdlib/ebin/maps.beam
index 193a06241d..731e2418ea 100644
--- a/bootstrap/lib/stdlib/ebin/maps.beam
+++ b/bootstrap/lib/stdlib/ebin/maps.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/math.beam b/bootstrap/lib/stdlib/ebin/math.beam
index 7e61673b35..e388b2c304 100644
--- a/bootstrap/lib/stdlib/ebin/math.beam
+++ b/bootstrap/lib/stdlib/ebin/math.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/ms_transform.beam b/bootstrap/lib/stdlib/ebin/ms_transform.beam
index c424e2f614..615deb46cf 100644
--- a/bootstrap/lib/stdlib/ebin/ms_transform.beam
+++ b/bootstrap/lib/stdlib/ebin/ms_transform.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/orddict.beam b/bootstrap/lib/stdlib/ebin/orddict.beam
index d6f267ad8f..1e7a73ec2a 100644
--- a/bootstrap/lib/stdlib/ebin/orddict.beam
+++ b/bootstrap/lib/stdlib/ebin/orddict.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/ordsets.beam b/bootstrap/lib/stdlib/ebin/ordsets.beam
index 44f9e5d8b5..4f2465c9bb 100644
--- a/bootstrap/lib/stdlib/ebin/ordsets.beam
+++ b/bootstrap/lib/stdlib/ebin/ordsets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/otp_internal.beam b/bootstrap/lib/stdlib/ebin/otp_internal.beam
index 26d495af72..90850f2828 100644
--- a/bootstrap/lib/stdlib/ebin/otp_internal.beam
+++ b/bootstrap/lib/stdlib/ebin/otp_internal.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/pool.beam b/bootstrap/lib/stdlib/ebin/pool.beam
index e8e339dfa0..1891f85ecb 100644
--- a/bootstrap/lib/stdlib/ebin/pool.beam
+++ b/bootstrap/lib/stdlib/ebin/pool.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/proc_lib.beam b/bootstrap/lib/stdlib/ebin/proc_lib.beam
index b51898901d..ab699fba83 100644
--- a/bootstrap/lib/stdlib/ebin/proc_lib.beam
+++ b/bootstrap/lib/stdlib/ebin/proc_lib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/proplists.beam b/bootstrap/lib/stdlib/ebin/proplists.beam
index a8ddeb2d57..a0acbdb212 100644
--- a/bootstrap/lib/stdlib/ebin/proplists.beam
+++ b/bootstrap/lib/stdlib/ebin/proplists.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/qlc.beam b/bootstrap/lib/stdlib/ebin/qlc.beam
index cca174e3cd..8e608d07a3 100644
--- a/bootstrap/lib/stdlib/ebin/qlc.beam
+++ b/bootstrap/lib/stdlib/ebin/qlc.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/qlc_pt.beam b/bootstrap/lib/stdlib/ebin/qlc_pt.beam
index 4d86fdfec2..0b69f638fb 100644
--- a/bootstrap/lib/stdlib/ebin/qlc_pt.beam
+++ b/bootstrap/lib/stdlib/ebin/qlc_pt.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/queue.beam b/bootstrap/lib/stdlib/ebin/queue.beam
index 94728e30cc..a29c6917fe 100644
--- a/bootstrap/lib/stdlib/ebin/queue.beam
+++ b/bootstrap/lib/stdlib/ebin/queue.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/rand.beam b/bootstrap/lib/stdlib/ebin/rand.beam
index 7f1e6140bf..392e99b528 100644
--- a/bootstrap/lib/stdlib/ebin/rand.beam
+++ b/bootstrap/lib/stdlib/ebin/rand.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/random.beam b/bootstrap/lib/stdlib/ebin/random.beam
index a4bc2b6128..c7ade26078 100644
--- a/bootstrap/lib/stdlib/ebin/random.beam
+++ b/bootstrap/lib/stdlib/ebin/random.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/re.beam b/bootstrap/lib/stdlib/ebin/re.beam
index e16327b906..0de43ec600 100644
--- a/bootstrap/lib/stdlib/ebin/re.beam
+++ b/bootstrap/lib/stdlib/ebin/re.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/sets.beam b/bootstrap/lib/stdlib/ebin/sets.beam
index e3609ea527..3dcb065519 100644
--- a/bootstrap/lib/stdlib/ebin/sets.beam
+++ b/bootstrap/lib/stdlib/ebin/sets.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/shell.beam b/bootstrap/lib/stdlib/ebin/shell.beam
index 319bb9aebf..3135e8ca2e 100644
--- a/bootstrap/lib/stdlib/ebin/shell.beam
+++ b/bootstrap/lib/stdlib/ebin/shell.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/shell_default.beam b/bootstrap/lib/stdlib/ebin/shell_default.beam
index afd0c65921..d4f079dc45 100644
--- a/bootstrap/lib/stdlib/ebin/shell_default.beam
+++ b/bootstrap/lib/stdlib/ebin/shell_default.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/shell_docs.beam b/bootstrap/lib/stdlib/ebin/shell_docs.beam
new file mode 100644
index 0000000000..d539010453
--- /dev/null
+++ b/bootstrap/lib/stdlib/ebin/shell_docs.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/slave.beam b/bootstrap/lib/stdlib/ebin/slave.beam
index 259d300155..af6a3a4a5b 100644
--- a/bootstrap/lib/stdlib/ebin/slave.beam
+++ b/bootstrap/lib/stdlib/ebin/slave.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/sofs.beam b/bootstrap/lib/stdlib/ebin/sofs.beam
index 3a90a07add..b593b134fc 100644
--- a/bootstrap/lib/stdlib/ebin/sofs.beam
+++ b/bootstrap/lib/stdlib/ebin/sofs.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/stdlib.app b/bootstrap/lib/stdlib/ebin/stdlib.app
index 6a64b18669..11754bf301 100644
--- a/bootstrap/lib/stdlib/ebin/stdlib.app
+++ b/bootstrap/lib/stdlib/ebin/stdlib.app
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
%%
{application, stdlib,
[{description, "ERTS CXC 138 10"},
- {vsn, "3.11.2"},
+ {vsn, "3.14"},
{modules, [array,
base64,
beam_lib,
@@ -92,6 +92,7 @@
sets,
shell,
shell_default,
+ shell_docs,
slave,
sofs,
string,
@@ -108,6 +109,6 @@
dets]},
{applications, [kernel]},
{env, []},
- {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-10.6.2","crypto-3.3",
+ {runtime_dependencies, ["sasl-3.0","kernel-7.0","erts-11.0","crypto-3.3",
"compiler-5.0"]}
]}.
diff --git a/bootstrap/lib/stdlib/ebin/string.beam b/bootstrap/lib/stdlib/ebin/string.beam
index ee19fb41a4..63a26170d1 100644
--- a/bootstrap/lib/stdlib/ebin/string.beam
+++ b/bootstrap/lib/stdlib/ebin/string.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/supervisor.beam b/bootstrap/lib/stdlib/ebin/supervisor.beam
index 1816fe6c41..263f3efc13 100644
--- a/bootstrap/lib/stdlib/ebin/supervisor.beam
+++ b/bootstrap/lib/stdlib/ebin/supervisor.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam b/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam
index 761907b40c..2cdc7c5142 100644
--- a/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam
+++ b/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/sys.beam b/bootstrap/lib/stdlib/ebin/sys.beam
index a3f46b5983..9d86998ce1 100644
--- a/bootstrap/lib/stdlib/ebin/sys.beam
+++ b/bootstrap/lib/stdlib/ebin/sys.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/timer.beam b/bootstrap/lib/stdlib/ebin/timer.beam
index 56a1c99010..bfd4057f29 100644
--- a/bootstrap/lib/stdlib/ebin/timer.beam
+++ b/bootstrap/lib/stdlib/ebin/timer.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/unicode.beam b/bootstrap/lib/stdlib/ebin/unicode.beam
index d61ed589aa..024dfce787 100644
--- a/bootstrap/lib/stdlib/ebin/unicode.beam
+++ b/bootstrap/lib/stdlib/ebin/unicode.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/unicode_util.beam b/bootstrap/lib/stdlib/ebin/unicode_util.beam
index c0523f2e52..5d6e4c977e 100644
--- a/bootstrap/lib/stdlib/ebin/unicode_util.beam
+++ b/bootstrap/lib/stdlib/ebin/unicode_util.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/uri_string.beam b/bootstrap/lib/stdlib/ebin/uri_string.beam
index 2b2b4882b8..b482af410d 100644
--- a/bootstrap/lib/stdlib/ebin/uri_string.beam
+++ b/bootstrap/lib/stdlib/ebin/uri_string.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/win32reg.beam b/bootstrap/lib/stdlib/ebin/win32reg.beam
index 136b65f9fb..e12757251b 100644
--- a/bootstrap/lib/stdlib/ebin/win32reg.beam
+++ b/bootstrap/lib/stdlib/ebin/win32reg.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/zip.beam b/bootstrap/lib/stdlib/ebin/zip.beam
index 75c2289f47..b68b8136ad 100644
--- a/bootstrap/lib/stdlib/ebin/zip.beam
+++ b/bootstrap/lib/stdlib/ebin/zip.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/include/erl_bits.hrl b/bootstrap/lib/stdlib/include/erl_bits.hrl
index 2a54587a17..7810527832 100644
--- a/bootstrap/lib/stdlib/include/erl_bits.hrl
+++ b/bootstrap/lib/stdlib/include/erl_bits.hrl
@@ -31,19 +31,3 @@
sign :: bt_sign() | 'undefined',
endian :: bt_endian() | 'undefined'
}).
-
--record(bitdefault, {
- integer, %% default type for integer
- float, %% default type for float
- binary %% default type for binary
- }).
-
-%%% (From config.hrl in the bitsyntax branch.)
--define(SYS_ENDIAN, big).
--define(SIZEOF_CHAR, 1).
--define(SIZEOF_DOUBLE, 8).
--define(SIZEOF_FLOAT, 4).
--define(SIZEOF_INT, 4).
--define(SIZEOF_LONG, 4).
--define(SIZEOF_LONG_LONG, 8).
--define(SIZEOF_SHORT, 2).
diff --git a/configure.src b/configure.src
index 4b748f2545..ff39a58aa9 100644
--- a/configure.src
+++ b/configure.src
@@ -37,6 +37,8 @@ unset CDPATH
default_cflags="-g -O2"
+pie_cflags=
+pie_ldflags=
mXY_build=
static_cache=
@@ -125,6 +127,14 @@ while test $# != 0; do
if test "$mXY_build" = "-m32"; then
mXY_build=
fi;;
+ --enable-pie)
+ pie_cflags="-fPIE"
+ pie_ldflags="-pie"
+ ;;
+ --disable-pie)
+ pie_cflags="-fno-PIE"
+ pie_ldflags="-no-pie"
+ ;;
CFLAGS=* | LDFLAGS=*)
flgs_var=`expr "$1" : '\([^=]*\)=.*'`
flgs_val=`expr "$1" : '[^=]*=\(.*\)'`
@@ -263,7 +273,7 @@ case "$help" in
exit 0;;
esac
-if test "$mXY_build" = ""; then
+if test "$mXY_build" = "" && test "$pie_cflags" = ""; then
if test "$CFLAGS" != ""; then
config_arguments="$config_arguments CFLAGS='$CFLAGS'"
unset CFLAGS
@@ -277,9 +287,9 @@ else
if test "$CFLAGS" = ""; then
CFLAGS=$default_cflags
fi
- config_arguments="$config_arguments CFLAGS='$mXY_build $CFLAGS'"
+ config_arguments="$config_arguments CFLAGS='$mXY_build $pie_cflags $CFLAGS'"
unset CFLAGS
- config_arguments="$config_arguments LDFLAGS='$mXY_build $LDFLAGS'"
+ config_arguments="$config_arguments LDFLAGS='$mXY_build $pie_ldflags $LDFLAGS'"
unset LDFLAGS
case $mXY_build in
-m32)
diff --git a/erts/.gitignore b/erts/.gitignore
index e515dc8811..6631fc883e 100644
--- a/erts/.gitignore
+++ b/erts/.gitignore
@@ -21,3 +21,4 @@
/emulator/test/*_no_opt_SUITE.erl
/emulator/pcre/pcre_exec_loop_break_cases.inc
+/emulator/beam/erl_db_insert_list.ycf.h
diff --git a/erts/Makefile b/erts/Makefile
index 9c07f024ec..4827789c96 100644
--- a/erts/Makefile
+++ b/erts/Makefile
@@ -79,6 +79,7 @@ local_setup:
cp $(ERL_TOP)/bin/$(TARGET)/ct_run.exe $(ERL_TOP)/bin/ct_run.exe; \
cp $(ERL_TOP)/bin/$(TARGET)/erlc.exe $(ERL_TOP)/bin/erlc.exe; \
cp $(ERL_TOP)/bin/$(TARGET)/erl.exe $(ERL_TOP)/bin/erl.exe; \
+ cp $(ERL_TOP)/bin/$(TARGET)/erl_call.exe $(ERL_TOP)/bin/erl_call.exe; \
cp $(ERL_TOP)/bin/$(TARGET)/werl.exe $(ERL_TOP)/bin/werl.exe; \
cp $(ERL_TOP)/bin/$(TARGET)/escript.exe $(ERL_TOP)/bin/escript.exe; \
chmod 755 $(ERL_TOP)/bin/erl.exe $(ERL_TOP)/bin/erlc.exe \
@@ -94,6 +95,7 @@ local_setup:
-e "s;%TARGET%;$(TARGET);" \
-e "s;%VSN%;$(VSN);" \
$(ERL_TOP)/erts/etc/unix/cerl.src > $(ERL_TOP)/bin/cerl; \
+ cp $(ERL_TOP)/bin/$(TARGET)/erl_call $(ERL_TOP)/bin/erl_call; \
cp $(ERL_TOP)/bin/$(TARGET)/dialyzer $(ERL_TOP)/bin/dialyzer; \
cp $(ERL_TOP)/bin/$(TARGET)/typer $(ERL_TOP)/bin/typer; \
cp $(ERL_TOP)/bin/$(TARGET)/ct_run $(ERL_TOP)/bin/ct_run; \
@@ -106,6 +108,15 @@ local_setup:
$(ERL_TOP)/bin/start_sasl.script \
$(ERL_TOP)/bin/start_clean.script \
$(ERL_TOP)/bin/no_dot_erlang.script
+# On Android, the shell is found in /system/bin/sh instead of /bin/sh
+ @case "$(TARGET)" in \
+ *-android*) \
+ sed -i'' -e "s;/bin/sh;/system/bin/sh;" \
+ $(ERL_TOP)/bin/erl; \
+ sed -i'' -e "s;/bin/sh;/system/bin/sh;" \
+ $(ERL_TOP)/bin/cerl; \
+ ;; \
+ esac
# ----------------------------------------------------------------------
# These are "convenience targets", provided as shortcuts for developers
@@ -153,3 +164,5 @@ release_docs:
.PHONY: xmllint
xmllint:
$(MAKE) -C doc/src $@
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/erts/aclocal.m4 b/erts/aclocal.m4
index 085fe76918..fae4254ae3 100644
--- a/erts/aclocal.m4
+++ b/erts/aclocal.m4
@@ -116,8 +116,8 @@ dnl
dnl LM_WINDOWS_ENVIRONMENT
dnl
dnl
-dnl Tries to determine thw windows build environment, i.e.
-dnl MIXED_CYGWIN_VC or MIXED_MSYS_VC
+dnl Tries to determine the windows build environment, i.e.
+dnl MIXED_VC or MIXED_MINGW
dnl
AC_DEFUN(LM_WINDOWS_ENVIRONMENT,
@@ -127,36 +127,39 @@ if test "X$windows_environment_" != "Xchecked"; then
windows_environment_=checked
MIXED_CYGWIN=no
MIXED_MSYS=no
+MIXED_VSL=no
-AC_MSG_CHECKING(for mixed cygwin or msys and native VC++ environment)
+dnl MIXED_VC is Microsoft Visual C++ used as standard compiler
+MIXED_VC=no
+dnl MIXED_MINGW is mingw(32|64) used as standard compiler
+MIXED_MINGW=no
+
+AC_MSG_CHECKING(for mixed mingw-gcc and native VC++ environment)
if test "X$host" = "Xwin32" -a "x$GCC" != "xyes"; then
if test -x /usr/bin/msys-?.0.dll; then
CFLAGS="$CFLAGS -O2"
MIXED_MSYS=yes
AC_MSG_RESULT([MSYS and VC])
- MIXED_MSYS_VC=yes
- CPPFLAGS="$CPPFLAGS -DERTS_MIXED_MSYS_VC"
+ MIXED_VC=yes
+ CPPFLAGS="$CPPFLAGS -DERTS_MIXED_VC"
elif test -x /usr/bin/cygpath; then
CFLAGS="$CFLAGS -O2"
MIXED_CYGWIN=yes
AC_MSG_RESULT([Cygwin and VC])
- MIXED_CYGWIN_VC=yes
- CPPFLAGS="$CPPFLAGS -DERTS_MIXED_CYGWIN_VC"
- else
+ MIXED_VC=yes
+ CPPFLAGS="$CPPFLAGS -DERTS_MIXED_VC"
+ elif test -x /bin/wslpath; then
+ CFLAGS="$CFLAGS -O2"
+ MIXED_WSL=yes
+ AC_MSG_RESULT([WSL and VC])
+ MIXED_VC=yes
+ CPPFLAGS="$CPPFLAGS -DERTS_MIXED_VC"
+ else
AC_MSG_RESULT([undeterminable])
- AC_MSG_ERROR(Seems to be mixed windows but not with cygwin, cannot handle this!)
+ AC_MSG_ERROR(Seems to be mixed windows but not within any known env, cannot handle this!)
fi
else
AC_MSG_RESULT([no])
- MIXED_CYGWIN_VC=no
- MIXED_MSYS_VC=no
-fi
-AC_SUBST(MIXED_CYGWIN_VC)
-AC_SUBST(MIXED_MSYS_VC)
-
-MIXED_VC=no
-if test "x$MIXED_MSYS_VC" = "xyes" -o "x$MIXED_CYGWIN_VC" = "xyes" ; then
- MIXED_VC=yes
fi
AC_SUBST(MIXED_VC)
@@ -166,44 +169,58 @@ if test "x$MIXED_MSYS" != "xyes"; then
if test "X$host" = "Xwin32" -a "x$GCC" = x"yes"; then
if test -x /usr/bin/cygpath; then
CFLAGS="$CFLAGS -O2"
- MIXED_CYGWIN=yes
AC_MSG_RESULT([yes])
- MIXED_CYGWIN_MINGW=yes
- CPPFLAGS="$CPPFLAGS -DERTS_MIXED_CYGWIN_MINGW"
+ MIXED_MINGW=yes
+ CPPFLAGS="$CPPFLAGS -DERTS_MIXED_MINGW"
else
AC_MSG_RESULT([undeterminable])
AC_MSG_ERROR(Seems to be mixed windows but not with cygwin, cannot handle this!)
fi
else
AC_MSG_RESULT([no])
- MIXED_CYGWIN_MINGW=no
fi
else
- MIXED_CYGWIN_MINGW=no
-fi
-AC_SUBST(MIXED_CYGWIN_MINGW)
+ AC_MSG_CHECKING(for mixed MSYS and native MinGW environment)
+ if test "x$GCC" = x"yes"; then
+ if test -x /usr/bin/msys-=.0.dll; then
+ CFLAGS="$CFLAGS -O2"
+ AC_MSG_RESULT([yes])
+ MIXED_MINGW=yes
+ CPPFLAGS="$CPPFLAGS -DERTS_MIXED_MINGW"
+ else
+ AC_MSG_RESULT([undeterminable])
+ AC_MSG_ERROR(Seems to be mixed windows but not with msys, cannot handle this!)
+ fi
+ else
+ AC_MSG_RESULT([no])
+ fi
+fi
+AC_SUBST(MIXED_MINGW)
AC_MSG_CHECKING(if we mix cygwin with any native compiler)
if test "X$MIXED_CYGWIN" = "Xyes"; then
- AC_MSG_RESULT([yes])
+ AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
fi
-AC_SUBST(MIXED_CYGWIN)
-
AC_MSG_CHECKING(if we mix msys with another native compiler)
if test "X$MIXED_MSYS" = "Xyes" ; then
- AC_MSG_RESULT([yes])
+ AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
fi
-AC_SUBST(MIXED_MSYS)
+AC_MSG_CHECKING(if we mix WSL with another native compiler)
+if test "X$MIXED_WSL" = "Xyes" ; then
+ AC_MSG_RESULT([yes])
+else
+ AC_MSG_RESULT([no])
+fi
fi
-])
-
+])
+
dnl ----------------------------------------------------------------------
dnl
dnl LM_FIND_EMU_CC
@@ -226,6 +243,7 @@ AC_TRY_COMPILE([],[
#endif
__label__ lbl1;
__label__ lbl2;
+ extern int magic(void);
int x = magic();
static void *jtab[2];
@@ -271,6 +289,7 @@ if test "$ac_cv_prog_emu_cc" != no; then
#endif
__label__ lbl1;
__label__ lbl2;
+ extern int magic(void);
int x = magic();
static void *jtab[2];
@@ -1355,28 +1374,55 @@ AC_DEFUN(ETHR_CHK_GCC_ATOMIC_OPS,
ETHR_CHK_GCC_ATOMIC_OP__(__atomic_compare_exchange_n)
ethr_have_gcc_native_atomics=no
- ethr_arm_dbm_instr_val=0
+ ethr_arm_dbm_sy_instr_val=0
+ ethr_arm_dbm_st_instr_val=0
+ ethr_arm_dbm_ld_instr_val=0
case "$GCC-$host_cpu" in
- yes-arm*)
- AC_CACHE_CHECK([for ARM DMB instruction], ethr_cv_arm_dbm_instr,
+ yes-arm*|yes-aarch*)
+ AC_CACHE_CHECK([for ARM 'dmb sy' instruction], ethr_cv_arm_dbm_sy_instr,
[
- ethr_cv_arm_dbm_instr=no
+ ethr_cv_arm_dbm_sy_instr=no
AC_TRY_LINK([],
[
__asm__ __volatile__("dmb sy" : : : "memory");
- __asm__ __volatile__("dmb st" : : : "memory");
],
- [ethr_cv_arm_dbm_instr=yes])
+ [ethr_cv_arm_dbm_sy_instr=yes])
])
- if test $ethr_cv_arm_dbm_instr = yes; then
- ethr_arm_dbm_instr_val=1
+ if test $ethr_cv_arm_dbm_sy_instr = yes; then
+ ethr_arm_dbm_sy_instr_val=1
test $ethr_cv_64bit___atomic_compare_exchange_n = yes &&
ethr_have_gcc_native_atomics=yes
+ fi
+ AC_CACHE_CHECK([for ARM 'dmb st' instruction], ethr_cv_arm_dbm_st_instr,
+ [
+ ethr_cv_arm_dbm_st_instr=no
+ AC_TRY_LINK([],
+ [
+ __asm__ __volatile__("dmb st" : : : "memory");
+ ],
+ [ethr_cv_arm_dbm_st_instr=yes])
+ ])
+ if test $ethr_cv_arm_dbm_st_instr = yes; then
+ ethr_arm_dbm_st_instr_val=1
+ fi
+ AC_CACHE_CHECK([for ARM 'dmb ld' instruction], ethr_cv_arm_dbm_ld_instr,
+ [
+ ethr_cv_arm_dbm_ld_instr=no
+ AC_TRY_LINK([],
+ [
+ __asm__ __volatile__("dmb ld" : : : "memory");
+ ],
+ [ethr_cv_arm_dbm_ld_instr=yes])
+ ])
+ if test $ethr_cv_arm_dbm_ld_instr = yes; then
+ ethr_arm_dbm_ld_instr_val=1
fi;;
*)
;;
esac
- AC_DEFINE_UNQUOTED([ETHR_HAVE_GCC_ASM_ARM_DMB_INSTRUCTION], [$ethr_arm_dbm_instr_val], [Define as a boolean indicating whether you have a gcc compatible compiler capable of generating the ARM DMB instruction, and are compiling for an ARM processor with ARM DMB instruction support, or not])
+ AC_DEFINE_UNQUOTED([ETHR_HAVE_GCC_ASM_ARM_DMB_INSTRUCTION], [$ethr_arm_dbm_sy_instr_val], [Define as a boolean indicating whether you have a gcc compatible compiler capable of generating the ARM 'dmb sy' instruction, and are compiling for an ARM processor with ARM DMB instruction support, or not])
+ AC_DEFINE_UNQUOTED([ETHR_HAVE_GCC_ASM_ARM_DMB_ST_INSTRUCTION], [$ethr_arm_dbm_st_instr_val], [Define as a boolean indicating whether you have a gcc compatible compiler capable of generating the ARM 'dmb st' instruction, and are compiling for an ARM processor with ARM DMB instruction support, or not])
+ AC_DEFINE_UNQUOTED([ETHR_HAVE_GCC_ASM_ARM_DMB_LD_INSTRUCTION], [$ethr_arm_dbm_ld_instr_val], [Define as a boolean indicating whether you have a gcc compatible compiler capable of generating the ARM 'dmb ld' instruction, and are compiling for an ARM processor with ARM DMB instruction support, or not])
test $ethr_cv_32bit___sync_val_compare_and_swap = yes &&
ethr_have_gcc_native_atomics=yes
test $ethr_cv_64bit___sync_val_compare_and_swap = yes &&
@@ -1493,6 +1539,33 @@ AC_ARG_WITH(with_sparc_memory_order,
AS_HELP_STRING([--with-sparc-memory-order=TSO|PSO|RMO],
[specify sparc memory order (defaults to RMO)]))
+AC_ARG_ENABLE(ppc-lwsync-instruction,
+AS_HELP_STRING([--enable-ppc-lwsync-instruction], [enable use of powerpc lwsync instruction])
+AS_HELP_STRING([--disable-ppc-lwsync-instruction], [disable use of powerpc lwsync instruction]),
+[ case "$enableval" in
+ no) enable_lwsync=no ;;
+ *) enable_lwsync=yes ;;
+ esac ],
+[
+ AC_CHECK_SIZEOF(void *)
+ case $host_cpu-$ac_cv_sizeof_void_p in
+ macppc-8|powerpc-8|ppc-8|powerpc64-8|ppc64-8|powerpc64le-8|ppc64le-8|"Power Macintosh"-8)
+ enable_lwsync=yes;;
+ *)
+ enable_lwsync=undefined;;
+ esac ])
+
+case $enable_lwsync in
+ no)
+ AC_DEFINE(ETHR_PPC_HAVE_NO_LWSYNC, [1], [Define if you do not have the powerpc lwsync instruction])
+ ;;
+ yes)
+ AC_DEFINE(ETHR_PPC_HAVE_LWSYNC, [1], [Define if you have the powerpc lwsync instruction])
+ ;;
+ *)
+ ;;
+esac
+
LM_CHECK_THR_LIB
ERL_INTERNAL_LIBS
@@ -2804,7 +2877,10 @@ AC_DEFUN([LM_HARDWARE_ARCH], [
ppc) ARCH=ppc;;
ppc64) ARCH=ppc64;;
ppc64le) ARCH=ppc64le;;
+ powerpc64) ARCH=ppc64;;
+ powerpc64le) ARCH=ppc64le;;
"Power Macintosh") ARCH=ppc;;
+ arm64) ARCH=arm64;;
armv5b) ARCH=arm;;
armv5teb) ARCH=arm;;
armv5tel) ARCH=arm;;
@@ -2813,6 +2889,9 @@ AC_DEFUN([LM_HARDWARE_ARCH], [
armv6hl) ARCH=arm;;
armv7l) ARCH=arm;;
armv7hl) ARCH=arm;;
+ armv8*) ARCH=arm;;
+ aarch64) ARCH=arm64;;
+ aarch*) ARCH=arm;;
tile) ARCH=tile;;
e2k) ARCH=e2k;;
*) ARCH=noarch;;
@@ -2850,8 +2929,8 @@ AC_DEFUN([LM_HARDWARE_ARCH], [
ARCH=ppc64
;;
arm-8)
- AC_MSG_RESULT(yes: adjusting ARCH=arm to ARCH=noarch)
- ARCH=noarch
+ AC_MSG_RESULT(yes: adjusting ARCH=arm to ARCH=arm64)
+ ARCH=arm64
;;
*)
AC_MSG_RESULT(no: ARCH is $ARCH)
@@ -2943,6 +3022,8 @@ if test "x$GCC" = xyes; then
DED_STATIC_CFLAGS="$DED_CFLAGS"
DED_CFLAGS="$DED_CFLAGS -fPIC"
+ # Remove -fPIE and -fno-PIE
+ DED_CFLAGS=`echo $DED_CFLAGS | sed 's/-f\(no-\)\?PIE//g'`
fi
DED_EXT=so
@@ -2993,7 +3074,7 @@ case $host_os in
DED_LDFLAGS="-64 $DED_LDFLAGS"
fi
;;
- aix4*)
+ aix*|os400*)
DED_LDFLAGS="-G -bnoentry -bexpall"
;;
freebsd2*)
diff --git a/erts/autoconf/configure.vxworks b/erts/autoconf/configure.vxworks
deleted file mode 100755
index c44339e64d..0000000000
--- a/erts/autoconf/configure.vxworks
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/bin/sh
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2020. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author:
-# Patrik Winroth
-#
-
-
-# vxworks_ppc860 vxworks_ppc603 vxworks_ppc603_longcall vxworks_cpu32 vxworks_sparc
-# vxworks_ppc750 vxworks_simso
-
-case $# in
-1) host=$1 ;;
-*) echo "usage: configure.vxworks host-configuration"; exit 1 ;;
-esac
-
-case $1 in
-vxworks_cpu32) ;;
-vxworks_ppc750) ;;
-vxworks_ppc860) ;;
-vxworks_ppc603) ;;
-vxworks_ppc603_nolongcall) ;;
-vxworks_sparc) ;;
-vxworks_simso) ;;
-vxworks_simlinux) ;;
-vxworks_ppc32) ;;
-*) echo "usage: configure.vxworks TARGET";
- echo "where TARGET is one of vxworks_cpu32, vxworks_ppc750, vxworks_ppc860, vxworks_ppc603, vxworks_ppc603_nolongcall, vxworks_sparc, vxworks_simso, vxworks_simlinux, vxworks_ppc32"; exit 1;;
-esac
-
-if [ "x$ERL_TOP" = x ]; then
- echo "You need to set ERL_TOP!"
- exit 1
-fi
-
-
-target=$host
-
-# Find out the HOST and WIND_BASE environment
-HOST_TYPE=${HOST_TYPE:=sun4-solaris2}
-case $1 in
-vxworks_ppc750) VXTOP=Tornado2.2 ;;
-vxworks_simso) VXTOP=WindRiver ;;
-vxworks_simlinux) VXTOP=WindRiver ;;
-vxworks_ppc32) VXTOP=WindRiver ;;
-*) VXTOP=wind ;;
-esac
-
-WIND_BASE=${WIND_BASE:=`ypmatch tornado passwd | awk -F: '{print $6}'`/$VXTOP}
-
-# These are created by autoconf.
-MKDIRS="${ERL_TOP}/lib/os_mon/priv/bin/$target
- ${ERL_TOP}/lib/os_mon/priv/obj/$target
- ${ERL_TOP}/lib/asn1/priv/lib/$target
- ${ERL_TOP}/lib/asn1/priv/obj/$target
- ${ERL_TOP}/lib/erl_interface/obj/$target
- ${ERL_TOP}/lib/erl_interface/obj.debug/$target
- ${ERL_TOP}/lib/erl_interface/bin/$target
- ${ERL_TOP}/lib/runtime_tools/priv/lib/$target
- ${ERL_TOP}/lib/runtime_tools/priv/obj/$target
- ${ERL_TOP}/erts/obj/$target
- ${ERL_TOP}/erts/obj.debug/$target
- ${ERL_TOP}/bin/$target"
-
-for dir in $MKDIRS; do
- test ! -d "$dir" && mkdir -p "$dir"
-done
-
-#
-# Create Makefiles for vxWorks.
-#
-my_root=${ERL_TOP}/erts/emulator
-emu_test=$my_root/test
-beam=$my_root/beam
-erts_lib_src=${ERL_TOP}/erts/lib_src
-erts_incl=${ERL_TOP}/erts/include
-erts_incl_intrnl=${ERL_TOP}/erts/include/internal
-etcdir=${ERL_TOP}/erts/etc/common
-erlint_incl_dir=${ERL_TOP}/lib/erl_interface/include
-erlint_dir=${ERL_TOP}/lib/erl_interface/src
-epmd_dir=${ERL_TOP}/erts/epmd/src
-os_mon_dir=${ERL_TOP}/lib/os_mon/c_src
-internal_tools_dir=${ERL_TOP}
-libdir=${ERL_TOP}/lib
-tsdir=$libdir/test_server/src
-zlibdir=${ERL_TOP}/erts/emulator/zlib
-runtime_tools_dir=${ERL_TOP}/lib/runtime_tools/c_src
-tools_dir=${ERL_TOP}/lib/tools/c_src
-
-CONFIG_FILES="${ERL_TOP}/erts/emulator/$host/Makefile
- $erts_lib_src/$host/Makefile
- $erts_incl/$host/erl_int_sizes_config.h
- $erts_incl_intrnl/$host/ethread.mk
- $erts_incl_intrnl/$host/ethread_header_config.h
- $etcdir/$host/Makefile
- $erlint_incl_dir/$host/ei_config.h
- $erlint_dir/$host/Makefile
- $erlint_dir/$host/eidefs.mk
- $epmd_dir/$host/Makefile
- $internal_tools_dir/make/$host/otp.mk
- $internal_tools_dir/make/$host/otp_ded.mk
- $os_mon_dir/$host/Makefile
- $zlibdir/$host/Makefile
- $runtime_tools_dir/$host/Makefile
- $tools_dir/$host/Makefile"
-
-for file in $CONFIG_FILES; do
- new_name=`echo $file|sed "s%/$host/%/$target/%"`
- dir=`echo $new_name|sed 's%/[^/][^/]*$%%'`
- if test "$dir" != "$new_name" && test "$dir" != .; then
- test ! -d "$dir" && mkdir "$dir"
- fi
-
- sole_name=`echo $file|sed "s%.*$target/%%"`
- in_file=`echo $dir|sed "s%/[^/][^/]*$%/$sole_name.in%"`
- echo "creating $new_name"
- sed -f vxworks/sed.$target -f vxworks/sed.general \
- -e "s,@HOST_TYPE@,$HOST_TYPE,g" \
- -e "s,@WIND_BASE@,$WIND_BASE,g" \
- -e "s,@TARGET@,$target,g" \
- -e "s,@ENV_CFLAGS@,$CFLAGS,g" \
- $in_file > $new_name
-done
-
-
diff --git a/erts/autoconf/vxworks/sed.general b/erts/autoconf/vxworks/sed.general
deleted file mode 100644
index 0ed5a7318f..0000000000
--- a/erts/autoconf/vxworks/sed.general
+++ /dev/null
@@ -1,132 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2020. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author: Patrik Winroth
-#
-# This sed program file is intended to be used when creating Makefiles
-# for vxworks from the generic Makefile.in that is found in a number
-# of directories (see configure.vxworks).
-#
-# This is the general part that is common for all architectures.
-#
-
-# Size of data types.
-s|^#undef SIZEOF_CHAR|#define SIZEOF_CHAR 1|
-s|^#undef SIZEOF_SHORT|#define SIZEOF_SHORT 2|
-s|^#undef SIZEOF_INT|#define SIZEOF_INT 4|
-s|^#undef SIZEOF_LONG_LONG|#define SIZEOF_LONG_LONG 8|
-s|^#undef SIZEOF_LONG$|#define SIZEOF_LONG 4|
-s|^#undef SIZEOF_VOID_P$|#define SIZEOF_VOID_P 4|
-
-# General stuff.
-s|@erts_rootdir@|/clearcase/otp/erts|
-
-s|@LIBOBJS@|$(OBJDIR)/elib_malloc.o|
-s|@DLOAD_LIB@||
-s|@LDFLAGS@||
-# FIXME: A bit strange to clear out remaining DED_*
-s|@DED_LDFLAGS@||
-s|@DED_CFLAGS@||
-s|@DED_EMU_THR_DEFS@||
-s|@DED_THR_DEFS@||
-s|@DED_SYS_INCLUDE@||
-s|@WERRORFLAGS@||
-s|@DED_STATIC_CFLAGS@||
-s|@STATIC_CFLAGS@||
-s|@GCCLIB@|libgcc.a|
-s|@DEFS@||
-s|@DEXPORT@||
-s|@DCFLAGS@||
-s|@THR_DEFS@||
-s|@THR_LIBS@||
-s|@THR_LIB_NAME@||
-s|@THR_X_LIBS@||
-s|@ETHR_X_LIBS@||
-s|@ETHR_LIBS@||
-s|@ETHR_LIB_NAME@||
-s|@ETHR_DEFS@||
-s|@ETHR_THR_LIB_BASE@||
-s|@ETHR_THR_LIB_BASE_DIR@||
-s|@SYSTEMD_DAEMON_LIBS@||
-s|@EMU_THR_DEFS@||
-s|@EMU_THR_LIBS@||
-s|@EMU_THR_LIB_NAME@|ethread|
-s|@ERTS_ENABLE_KERNEL_POLL@|no|
-s|@ERTS_INTERNAL_X_LIBS@||
-s|@cc_root@|/clearcase/otp/|
-# Define VxWorks even though cross-compiling.
-s|@CROSS_COMPILING|yes|
-
-s|@HCFLAGS@|-DVXWORKS|
-s|@HCLIBS@||
-s|@ENABLE_ALLOC_TYPE_VARS@||
-s|@TERMCAP_LIB@||
-s|@ERTS_BUILD_SMP_EMU@|no|
-s|@HAVE_VALGRIND@|no|
-s|@EXEEXT@||
-s|@WITH_SCTP@||
-
-# HiPE
-s|@HIPE_ENABLED@||
-
-# m4
-s|@OPSYS@|noopsys|
-
-# Conditional inclusion of applications
-s|@HIPE_APP@||
-s|@SSL_APP@|ssl|
-s|@CRYPTO_APP@|crypto|
-s|@SSH_APP@|ssh|
-
-# The target tools prefix, prepended to all cc,ld,as etc commands
-s|@TTPREFIX@|GCC_EXEC_PREFIX=@WIND_BASE@/host/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/host/@HOST_TYPE@/bin/|
-
-# Install programs etc
-s|@PERL@|perl|
-s|@INSTALL_PROGRAM@|${INSTALL}|
-s|@INSTALL_SCRIPT@|${INSTALL}|
-s|@INSTALL_DATA@|${INSTALL} -m 644|
-s|@INSTALL_DIR@|$(INSTALL) -d|
-s|@MKDIR@|/bin/mkdir|
-s|@ERLANG_OSTYPE@|vxworks|
-s|@vxworks_reclaim@|reclaim.h|
-s|@os_mon_programs@||
-s|@erlexec@|erl.exec|
-s|@EMU_LIBOBJS@||
-
-# General CFLAGS
-s|@GENERAL_CFLAGS@|@ENV_CFLAGS@ -DHAVE_LOCALTIME_R -DHAVE_GMTIME_R -DENABLE_ELIB_MALLOC -DELIB_HEAP_USER -DELIB_SORTED_BLOCKS -DWORDS_BIGENDIAN -DELIB_DONT_INITIALIZE -DSIZEOF_CHAR=1 -DSIZEOF_SHORT=2 -DSIZEOF_INT=4 -DSIZEOF_LONG=4 -DSIZEOF_LONG_LONG=8 -DSIZEOF_VOID_P=4 -DERTS_USE_PORT_TASKS=1|g
-s|@WFLAGS@||
-
-# Thread flags for eidefs.mk (erl_interface)
-s|@EI_THREADS@|false|
-
-# Make java code compile although we don't test it on VxWorks (no license)
-s|@JAVAC@|javac|
-
-# What is this anyway?
-# Disable it and see what breaks.
-#s|@ded_soname@||
-
-# Only variable substituted directly
-s|$(LDFLAGS)|-r -d|
-s|@LIBRT@||
-# XXX What is EFFLAGS? Not used in the emulator Makefile.in anyway.
-s|$(EFLAGS)|-DENABLE_ELIB_MALLOC -DELIB_HEAP_USER -DELIB_SORTED_BLOCKS|
-
diff --git a/erts/autoconf/vxworks/sed.vxworks_cpu32 b/erts/autoconf/vxworks/sed.vxworks_cpu32
deleted file mode 100644
index 26e4f4c7ad..0000000000
--- a/erts/autoconf/vxworks/sed.vxworks_cpu32
+++ /dev/null
@@ -1,47 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2018. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author: Patrik Winroth
-#
-# This sed program file is intended to be used when creating Makefiles for vxworks
-# from the generic Makefile.in that is found in a number of directories (see configure.vxworks)
-#
-#
-#
-s|@host@|vxworks_cpu32|
-s|@system_type@|vxworks_cpu32|
-s|@CC@|@TTPREFIX@cc68k|
-s|@HCC@|gcc|
-s|@GCC@|yes|
-s|@LD@|@TTPREFIX@ld68k|
-s|@LIBS@||
-s|@DED_LD@|@TTPREFIX@ld68k|
-s|@DED_CFLAGS@|@CFLAGS@|
-s|@DEBUG_FLAGS@|-g|
-s|@GCCLIB_PATH@|@WIND_BASE@/host/@HOST_TYPE@/lib/gcc-lib/m68k-wrs-vxworks/cygnus-2.7.2-960126/m68000/msoft-float/libgcc.a|
-s|@RANLIB@|@TTPREFIX@ranlib68k|
-s|@AR@|@TTPREFIX@ar68k|
-s|@STRIP@|@TTPREFIX@strip68k|
-s|@SYMPREFIX@|_|
-s|@GCCLIBFLAGS@|-L@WIND_BASE@/host/@HOST_TYPE@/lib/gcc-lib/m68k-wrs-vxworks/cygnus-2.7.2-960126/m68000/msoft-float -lgcc|
-
-s|@LIB_CFLAGS@|@CFLAGS@|
-
-s|@CFLAGS@|@GENERAL_CFLAGS@ -DCPU=CPU32 -mnobitfield -DWANT_NONBLOCKING -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/target/h -fno-builtin -nostdinc -fvolatile -msoft-float|
-#s|@LIB_CFLAGS@|@GENERAL_CFLAGS@ -DCPU=CPU32 -mnobitfield -DWANT_NONBLOCKING -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/target/h -fno-builtin -nostdinc -fvolatile -msoft-float|
diff --git a/erts/autoconf/vxworks/sed.vxworks_ppc32 b/erts/autoconf/vxworks/sed.vxworks_ppc32
deleted file mode 100644
index 44697aadc2..0000000000
--- a/erts/autoconf/vxworks/sed.vxworks_ppc32
+++ /dev/null
@@ -1,58 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2006-2018. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author: Peter Andersson
-#
-# This sed program file is intended to be used when creating Makefiles for vxworks
-# from the generic Makefile.in that is found in a number of directories (see configure.vxworks)
-#
-#
-#
-# Install programs etc
-s|@INSTALL@|/usr/bin/install -c|
-
-# other
-s|@host@|vxworks_ppc32|
-s|@system_type@|vxworks_ppc32|
-s|@ARCH@|ppc32|
-s|@CC@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ccppc -mlongcall|
-s|@HCC@|gcc|
-s|@GCC@|yes|
-s|@LD@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ldppc|
-s|@STRIP@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/workbench-2.3/@HOST_TYPE@/bin/stripppc|
-s|@SYMPREFIX@||
-s|@LIBS@||
-s|@GCCLIBFLAGS@|-L@WIND_BASE@/vxworks-6.3/target/lib/ppc/PPC32/common -lgcc|
-s|@DED_LD@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ldppc|
-s|@DED_CFLAGS@|@CFLAGS@|
-s|@DEBUG_CFLAGS@|@CFLAGS@|
-# generate dwarf debug code on PPC ..
-s|@DEBUG_FLAGS@|-gdwarf|
-# remove -g option
-s|TYPE_FLAGS = -g |TYPE_FLAGS = |
-s|@GCCLIB_PATH@|@WIND_BASE@/vxworks-6.3/target/lib/ppc/PPC32/common/libgcc.a|
-s|@RANLIB@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ranlibppc|
-s|@AR@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/arppc|
-# -Dasm(X)= is for beam
-
-s|@LIB_CFLAGS@|@CFLAGS@|
-
-s|@CFLAGS@|@GENERAL_CFLAGS@ -DCPU=PPC32 -DTOOL_FAMILY=gnu -DTOOL=gnu -DWANT_NONBLOCKING -DHAVE_SENS -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/vxworks-6.3/target/h -I@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc/powerpc-wrs-vxworks/3.4.4/include -I@WIND_BASE@/vxworks-6.3/target/h/wrn/coreip -mstrict-align -fvolatile -fno-builtin |
-
-#s|@LIB_CFLAGS@|@GENERAL_CFLAGS@ -DCPU=PPC32 -DTOOL_FAMILY=gnu -DTOOL=gnu -DWANT_NONBLOCKING -DHAVE_SENS -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/vxworks-6.3/target/h/wrn/coreip -I@WIND_BASE@/vxworks-6.3/target/h -mstrict-align -fvolatile -fno-builtin |
diff --git a/erts/autoconf/vxworks/sed.vxworks_ppc603 b/erts/autoconf/vxworks/sed.vxworks_ppc603
deleted file mode 100644
index 4fdfd51273..0000000000
--- a/erts/autoconf/vxworks/sed.vxworks_ppc603
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2000-2018. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author: Patrik Winroth
-#
-# This sed program file is intended to be used when creating Makefiles for vxworks
-# from the generic Makefile.in that is found in a number of directories (see configure.vxworks)
-#
-#
-#
-s|@host@|vxworks_ppc603|
-s|@system_type@|vxworks_ppc603|
-s|@ARCH@|ppc603|
-s|@CC@|@TTPREFIX@ccppc -mlongcall|
-s|@HCC@|gcc|
-s|@GCC@|yes|
-s|@LD@|@TTPREFIX@ldppc|
-s|@STRIP@|@TTPREFIX@stripppc|
-s|@SYMPREFIX@||
-s|@LIBS@||
-s|@GCCLIBFLAGS@|-L@WIND_BASE@/host/@HOST_TYPE@/lib/gcc-lib/powerpc-wrs-vxworks/cygnus-2.7.2-960126 -lgcc|
-s|@DED_LD@|@TTPREFIX@ldppc|
-s|@DED_CFLAGS@|@CFLAGS@|
-s|@DEBUG_CFLAGS@|@CFLAGS@|
-# generate dwarf debug code on PPC ..
-s|@DEBUG_FLAGS@|-gdwarf|
-# remove -g option
-s|TYPE_FLAGS = -g |TYPE_FLAGS = |
-s|@GCCLIB_PATH@|@WIND_BASE@/host/@HOST_TYPE@/lib/gcc-lib/powerpc-wrs-vxworks/cygnus-2.7.2-960126/libgcc.a|
-s|@RANLIB@|@TTPREFIX@ranlibppc|
-s|@AR@|@TTPREFIX@arppc|
-# -Dasm(X)= is for beam
-
-s|@LIB_CFLAGS@|@CFLAGS@|
-
-s|@CFLAGS@|@GENERAL_CFLAGS@ -DCPU=PPC603 -DWANT_NONBLOCKING -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/target/h -mstrict-align -fvolatile -fno-builtin -fno-for-scope -D_GNU_TOOL|
-#s|@LIB_CFLAGS@|@GENERAL_CFLAGS@ -DCPU=PPC603 -DWANT_NONBLOCKING -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/target/h -mstrict-align -fvolatile -fno-builtin -fno-for-scope -D_GNU_TOOL|
diff --git a/erts/autoconf/vxworks/sed.vxworks_ppc603_nolongcall b/erts/autoconf/vxworks/sed.vxworks_ppc603_nolongcall
deleted file mode 100644
index d86876e90e..0000000000
--- a/erts/autoconf/vxworks/sed.vxworks_ppc603_nolongcall
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2018. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author: Patrik Winroth
-#
-# This sed program file is intended to be used when creating Makefiles for vxworks
-# from the generic Makefile.in that is found in a number of directories (see configure.vxworks)
-#
-#
-#
-s|@host@|vxworks_ppc603|
-s|@system_type@|vxworks_ppc603|
-s|@ARCH@|ppc603|
-s|@CC@|@TTPREFIX@ccppc|
-s|@HCC@|gcc|
-s|@GCC@|yes|
-s|@LD@|@TTPREFIX@ldppc|
-s|@STRIP@|@TTPREFIX@stripppc|
-s|@SYMPREFIX@||
-s|@LIBS@||
-s|@GCCLIBFLAGS@|-L@WIND_BASE@/host/@HOST_TYPE@/lib/gcc-lib/powerpc-wrs-vxworks/cygnus-2.7.2-960126 -lgcc|
-s|@DED_LD@|@TTPREFIX@ldppc|
-s|@DED_CFLAGS@|@CFLAGS@|
-# generate dwarf debug code on PPC ..
-s|@DEBUG_FLAGS@|-gdwarf|
-# remove -g option
-s|TYPE_FLAGS = -g |TYPE_FLAGS = |
-s|@GCCLIB_PATH@|@WIND_BASE@/host/@HOST_TYPE@/lib/gcc-lib/powerpc-wrs-vxworks/cygnus-2.7.2-960126/libgcc.a|
-s|@RANLIB@|@TTPREFIX@ranlibppc|
-s|@AR@|@TTPREFIX@arppc|
-# -Dasm(X)= is for beam
-
-s|@LIB_CFLAGS@|@CFLAGS@|
-
-s|@CFLAGS@|@GENERAL_CFLAGS@ -DCPU=PPC603 -DWANT_NONBLOCKING -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/target/h -mstrict-align -fvolatile -fno-builtin -fno-for-scope -D_GNU_TOOL|
-#s|@LIB_CFLAGS@|@GENERAL_CFLAGS@ -DCPU=PPC603 -DWANT_NONBLOCKING -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/target/h -mstrict-align -fvolatile -fno-builtin -fno-for-scope -D_GNU_TOOL|
-
diff --git a/erts/autoconf/vxworks/sed.vxworks_ppc860 b/erts/autoconf/vxworks/sed.vxworks_ppc860
deleted file mode 100644
index a5c4c2d5c3..0000000000
--- a/erts/autoconf/vxworks/sed.vxworks_ppc860
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2018. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author: Patrik Winroth
-#
-# This sed program file is intended to be used when creating Makefiles for vxworks
-# from the generic Makefile.in that is found in a number of directories (see configure.vxworks)
-#
-#
-#
-s|@host@|vxworks_ppc860|
-s|@system_type@|vxworks_ppc860|
-s|@ARCH@|ppc860|
-s|@CC@|@TTPREFIX@ccppc -mlongcall|
-s|@HCC@|gcc|
-s|@GCC@|yes|
-s|@LD@|@TTPREFIX@ldppc|
-s|@STRIP@|@TTPREFIX@stripppc|
-s|@SYMPREFIX@||
-s|@LIBS@||
-s|@GCCLIBFLAGS@|-L@WIND_BASE@/host/@HOST_TYPE@/lib/gcc-lib/powerpc-wrs-vxworks/cygnus-2.7.2-960126/soft-float -lgcc|
-s|@DED_LD@|@TTPREFIX@ldppc|
-s|@DED_CFLAGS@|@CFLAGS@|
-s|@DEBUG_CFLAGS@|@CFLAGS@|
-# generate dwarf debug code on PPC ..
-s|@DEBUG_FLAGS@|-gdwarf|
-# remove -g option (go for dwarf)
-s|TYPE_FLAGS = -g |TYPE_FLAGS = |
-s|@GCCLIB_PATH@|@WIND_BASE@/host/@HOST_TYPE@/lib/gcc-lib/powerpc-wrs-vxworks/cygnus-2.7.2-960126/soft-float/libgcc.a|
-s|@RANLIB@|@TTPREFIX@ranlibppc|
-s|@AR@|@TTPREFIX@arppc|
-
-s|@LIB_CFLAGS@|@CFLAGS@|
-
-s|@CFLAGS@|@GENERAL_CFLAGS@ -DCPU=PPC860 -DWANT_NONBLOCKING -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/target/h -mcpu=860 -fvolatile -fno-builtin -fno-for-scope -msoft-float -D_GNU_TOOL -nostdinc|
-#s|@LIB_CFLAGS@|@GENERAL_CFLAGS@ -DCPU=PPC860 -DWANT_NONBLOCKING -DHAVE_MEMMOVE -DVXWORKS -I@WIND_BASE@/target/h -mcpu=powerpc -fvolatile -fno-builtin -fno-for-scope -msoft-float -D_GNU_TOOL -nostdinc|
diff --git a/erts/autoconf/vxworks/sed.vxworks_simlinux b/erts/autoconf/vxworks/sed.vxworks_simlinux
deleted file mode 100644
index 1a2bbd6236..0000000000
--- a/erts/autoconf/vxworks/sed.vxworks_simlinux
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2008-2018. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author: Peter Andersson
-#
-# This sed program file is intended to be used when creating Makefiles for vxworks
-# from the generic Makefile.in that is found in a number of directories (see configure.vxworks)
-#
-#
-#
-#
-# Install programs etc
-s|@INSTALL@|/usr/bin/install -c|
-
-# other
-s|@host@|vxworks_simlinux|
-s|@system_type@|vxworks_simlinux|
-s|@ARCH@|simlinux|
-
-s|@CC@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ccpentium|
-
-s|@HCC@|gcc|
-s|@GCC@|yes|
-
-s|@LD@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ldpentium|
-
-#s|@STRIP@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/strip|
-s|@STRIP@||
-
-s|@SYMPREFIX@||
-s|@LIBS@||
-s|@GCCLIBFLAGS@|-L@WIND_BASE@/vxworks-6.3/target/lib/simlinux/SIMLINUX/common -lgcc|
-
-s|@DED_LD@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ldpentium|
-
-s|@DED_CFLAGS@|@CFLAGS@|
-s|@DEBUG_CFLAGS@|@CFLAGS@|
-# remove -g option
-s|TYPE_FLAGS = -g |TYPE_FLAGS = |
-s|@GCCLIB_PATH@|@WIND_BASE@/vxworks-6.3/target/lib/simlinux/SIMLINUX/common/libgcc.a|
-
-s|@RANLIB@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ranlibpentium|
-
-s|@AR@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/arpentium|
-
-# -Dasm(X)= is for beam
-s|@LIB_CFLAGS@|@CFLAGS@|
-
-s|@CFLAGS@|@GENERAL_CFLAGS@ -DCPU=SIMLINUX -DTOOL_FAMILY=gnu -DTOOL=gnu -DWANT_NONBLOCKING -DHAVE_SENS -DHAVE_MEMMOVE -DVXWORKS -DDEBUG -I@WIND_BASE@/vxworks-6.3/target/h -I@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc/i586-wrs-vxworks/3.4.4/include -I@WIND_BASE@/vxworks-6.3/target/h/wrn/coreip -fvolatile -fno-builtin |
-#s|@LIB_CFLAGS@|@GENERAL_CFLAGS@ -DCPU=SIMLINUX -DTOOL_FAMILY=gnu -DTOOL=gnu -DWANT_NONBLOCKING -DHAVE_SENS -DHAVE_MEMMOVE -DVXWORKS -DDEBUG -I@WIND_BASE@/vxworks-6.3/target/h -I@WIND_BASE@/vxworks-6.3/target/h/wrn/coreip -mstrict-align -fvolatile -fno-builtin |
diff --git a/erts/autoconf/vxworks/sed.vxworks_simso b/erts/autoconf/vxworks/sed.vxworks_simso
deleted file mode 100644
index 8d15e87a1b..0000000000
--- a/erts/autoconf/vxworks/sed.vxworks_simso
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2005-2018. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author: Peter Andersson
-#
-# This sed program file is intended to be used when creating Makefiles for vxworks
-# from the generic Makefile.in that is found in a number of directories (see configure.vxworks)
-#
-#
-#
-# Install programs etc
-s|@INSTALL@|/usr/ucb/install -c|
-
-# other
-s|@host@|vxworks_simso|
-s|@system_type@|vxworks_simso|
-s|@ARCH@|simso|
-
-# Tornado2.2: s|@CC@|@TTPREFIX@ccsimso|
-s|@CC@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ccsparc|
-
-s|@HCC@|gcc|
-s|@GCC@|yes|
-
-# Tornado2.2: s|@LD@|@TTPREFIX@ldsimso|
-s|@LD@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ldsparc|
-
-# Tornado2.2: s|@STRIP@|@TTPREFIX@stripsimso|
-s|@STRIP@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/stripsparc|
-
-s|@SYMPREFIX@||
-s|@LIBS@||
-s|@GCCLIBFLAGS@|-L@WIND_BASE@/vxworks-6.3/target/lib/simso/SIMSPARCSOLARIS/common -lgcc|
-
-# Tornado2.2: s|@DED_LD@|@TTPREFIX@ldsimso|
-s|@DED_LD@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ldsparc|
-
-s|@DED_CFLAGS@|@CFLAGS@|
-s|@DEBUG_CFLAGS@|@CFLAGS@|
-# remove -g option
-s|TYPE_FLAGS = -g |TYPE_FLAGS = |
-s|@GCCLIB_PATH@|@WIND_BASE@/vxworks-6.3/target/lib/simso/SIMSPARCSOLARIS/common/libgcc.a|
-
-# Tornado2.2: s|@RANLIB@|@TTPREFIX@ranlibsimso|
-s|@RANLIB@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/ranlibsparc|
-
-# Tornado2.2: s|@AR@|arsimso|
-s|@AR@|GCC_EXEC_PREFIX=@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc-lib/ @WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/bin/arsparc|
-
-# -Dasm(X)= is for beam
-s|@LIB_CFLAGS@|@CFLAGS@|
-
-s|@CFLAGS@|@GENERAL_CFLAGS@ -DCPU=SIMSPARCSOLARIS -DTOOL_FAMILY=gnu -DTOOL=gnu -DWANT_NONBLOCKING -DHAVE_SENS -DHAVE_MEMMOVE -DVXWORKS -DDEBUG -I@WIND_BASE@/vxworks-6.3/target/h -I@WIND_BASE@/gnu/3.4.4-vxworks-6.3/@HOST_TYPE@/lib/gcc/sparc-wrs-vxworks/3.4.4/include -I@WIND_BASE@/vxworks-6.3/target/h/wrn/coreip -fvolatile -fno-builtin |
-#s|@LIB_CFLAGS@|@GENERAL_CFLAGS@ -DCPU=SIMSPARCSOLARIS -DTOOL_FAMILY=gnu -DTOOL=gnu -DWANT_NONBLOCKING -DHAVE_SENS -DHAVE_MEMMOVE -DVXWORKS -DDEBUG -I@WIND_BASE@/vxworks-6.3/target/h -I@WIND_BASE@/vxworks-6.3/target/h/wrn/coreip -mstrict-align -fvolatile -fno-builtin |
diff --git a/erts/autoconf/vxworks/sed.vxworks_sparc b/erts/autoconf/vxworks/sed.vxworks_sparc
deleted file mode 100644
index 118b01d16d..0000000000
--- a/erts/autoconf/vxworks/sed.vxworks_sparc
+++ /dev/null
@@ -1,40 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2018. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-# Author: Patrik Winroth
-#
-# This sed program file is intended to be used when creating Makefiles for vxworks
-# from the generic Makefile.in that is found in a number of directories (see configure.vxworks)
-#
-
-# ccsparc -O2 doesn't work when compiling "rundir"/gc.c - signal 11 is generated when trying
-# therefore it is compiled with -O1 instead, which works - get a new ccsparc !
-s/\$(COMPILE\.emu) -o \$@ -c gc\.c/$(CC) @CFLAGS@ @DEFS@ -O1 $(BEAM_MODE) -I$(SYSDIR) -I$(EMUDIR) -I. $(CPPFLAGS) -c -o $@ -c gc.c/
-s/@host@/vxworks_sparc/
-s/@system_type@/vxworks_sparc/
-s/@CC@/\/home\/gandalf\/bsproj\/tools\/vw-gnu\/solaris.sparc\/bin\/ccsparc/
-s/@HCC@/gcc/
-s/@GCC@/yes/
-s/@LD@/\/home\/gandalf\/bsproj\/tools\/vw-gnu\/solaris.sparc\/bin\/ldsparc/
-s/@DEBUG_FLAGS@/-g/
-s/@GCCLIB_PATH@/\/home\/gandalf\/bsproj\/tools\/vw-gnu\/solaris.sparc\/lib\/gcc-lib\/sparc-wrs-vxworks\/cygnus-2.2.3.1\/libgcc.a/
-s/@RANLIB@/ranlibsparc/
-s/@AR@/arsparc/
-s/@CFLAGS@/-I\/home\/gandalf\/bsproj\/BS.2\/UOS\/vw\/5.2\/h -DWANT_NONBLOCKING -DHAVE_MEMMOVE -DCPU=SPARC -DVXWORKS -fno-builtin -nostdinc/
-
diff --git a/erts/configure.in b/erts/configure.in
index caa1ce568b..1b54c39ddc 100644
--- a/erts/configure.in
+++ b/erts/configure.in
@@ -447,6 +447,12 @@ dnl ---------------------------------------------------------------------
dnl NOTE: CPPFLAGS will be included in CFLAGS at the end
case $host_os in
linux*) CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE";;
+ aix*|os400*)
+ # * _ALL_SOURCE: Required to get the winsize structure for TIOCSWINSZ.
+ # * _LINUX_SOURCE_COMPAT: Not required, but makes some libc functions
+ # behave closer to glibc assumptions.
+ CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE -D_LINUX_SOURCE_COMPAT"
+ ;;
win32)
# The ethread library requires _WIN32_WINNT of at least 0x0403.
# -D_WIN32_WINNT=* from CPPFLAGS is saved in ETHR_DEFS.
@@ -577,6 +583,17 @@ else
WERRORFLAGS=""
fi
+AC_MSG_CHECKING([C99 support])
+
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[
+#if __STDC_VERSION__ < 199901L
+ #error "Not C99"
+#endif])],
+ [AC_MSG_RESULT([yes])],
+ [AC_MSG_RESULT([no])
+ CFLAGS="-std=gnu99 $CFLAGS"
+ DEBUG_CFLAGS="-std=gnu99 $DEBUG_CFLAGS"])
+
AC_MSG_CHECKING([CFLAGS for -O switch])
case "$CFLAGS" in
*-O*) AC_MSG_RESULT([yes]) ;;
@@ -621,12 +638,12 @@ if test "X$PROFILE_INSTR_GENERATE" = "Xtrue"; then
saved_CFLAGS=$CFLAGS;
CFLAGS="-fprofile-instr-generate -Werror $saved_CFLAGS"
AC_RUN_IFELSE([AC_LANG_PROGRAM([],[])],
- [AC_CHECK_PROGS([LLVM_PROFDATA], [llvm-profdata])
+ [AC_PATH_PROG([LLVM_PROFDATA], [llvm-profdata],[],[$PATH:/Library/Developer/CommandLineTools/usr/bin])
AC_CHECK_PROGS([XCRUN], [xcrun])
if test "X$XCRUN" != "X" -a "X$LLVM_PROFDATA" = "X"; then
- AC_MSG_CHECKING([for $XCRUN llvm-profdata])
- if $XCRUN llvm-profdata --help 2>& AS_MESSAGE_LOG_FD >& AS_MESSAGE_LOG_FD; then
- LLVM_PROFDATA="$XCRUN llvm-profdata"
+ AC_MSG_CHECKING([for $XCRUN $LLVM_PROFDATA])
+ if $XCRUN $LLVM_PROFDATA --help 2>& AS_MESSAGE_LOG_FD >& AS_MESSAGE_LOG_FD; then
+ LLVM_PROFDATA="$XCRUN $LLVM_PROFDATA"
AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
@@ -634,7 +651,7 @@ if test "X$PROFILE_INSTR_GENERATE" = "Xtrue"; then
fi
AC_SUBST(LLVM_PROFDATA)
if test "X$LLVM_PROFDATA" != "X"; then
- CFLAGS="-fprofile-instr-use=default.profdata -Werror $saved_CFLAGS";
+ CFLAGS="-fprofile-instr-use=default.profdata $saved_CFLAGS";
$LLVM_PROFDATA merge -output=default.profdata *.profraw;
AC_MSG_CHECKING([whether $CC accepts -fprofile-instr-use=default.profdata -Werror])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[return 0;])],
@@ -733,7 +750,7 @@ dnl First remove common_tests skip file.
dnl Adjust LDFLAGS to allow 64bit linkage on DARWIN
case $ARCH-$OPSYS in
- amd64-darwin*)
+ amd64-darwin*|arm64-darwin*)
AC_MSG_NOTICE([Adjusting LDFLAGS to cope with 64bit Darwin])
case $LDFLAGS in
*-m64*)
@@ -918,13 +935,10 @@ dnl
HCC='$(CC)' AC_SUBST(HCC)
HCFLAGS="" AC_SUBST(HCFLAGS)
HCFLAGS="$HCFLAGS -I${ERL_TOP}/erts/$host"
-vxworks_reclaim="" AC_SUBST(vxworks_reclaim)
dnl We want to use $(CC) as linker for the emulator regardless of
dnl what the user say. This might not be the right way to do it, but
dnl for now that is the way we do it.
-USER_LD=$LD
-USER_LDFLAGS="$LDFLAGS"
LD='$(CC)'
LD_MAY_BE_WEAK=no
AC_SUBST(LD)
@@ -934,7 +948,7 @@ dnl AC_CYGWIN is deprecated
AC_EXEEXT
AC_OBJEXT
-dnl This is the os flavour, should be unix, ose, vxworks or win32
+dnl This is the os flavour, should be unix or win32
case $host in
win32)
ERLANG_OSTYPE=win32 ;;
@@ -957,7 +971,7 @@ AC_SUBST(ERLANG_OSTYPE)
AC_MSG_CHECKING(for extra flags needed to export symbols)
DEXPORT=""
case $host_os in
- aix4*)
+ aix*|os400*)
DEXPORT=-Wl,-bexpall,-brtl
;;
bsdi*)
@@ -1375,6 +1389,19 @@ fi
AC_SUBST(USE_ESOCK)
+dnl *** ESOCK_USE_RCVSNDTIMEO ***
+
+AC_ARG_ENABLE(esock_use_rcvsndtimeo,
+AS_HELP_STRING([--enable-esock-rcvsndtimeo], [enable use of the option(s) rcvtimeo and sndtimeo])
+AS_HELP_STRING([--disable-esock-rcvsndtimeo], [disable use of the option(s) rcvtimeo and sndtimeo (default)]))
+
+if test "x$enable_esock_rcvsndtimeo" = "xyes"; then
+ AC_DEFINE(ESOCK_USE_RCVSNDTIMEO, [1], [Use SO_[RCV|SND]TMIEO])
+fi
+
+
+dnl *** ESOCK_COUNTER_SIZE ***
+
AC_ARG_WITH(esock-counter-size,
AS_HELP_STRING([--with-esock-counter-size=SZ],
[Size of the esock counters, in number of bits: 16 | 24 | 32 | 48 | 64; default is 64]),
@@ -1408,6 +1435,49 @@ dnl ESOCK_COUNTER_SIZE=$with_esock_counter_size
dnl We don't actually (currently) use this in erlang
dnl AC_SUBST(ESOCK_COUNTER_SIZE)
+dnl Checks for the net nif
+AC_CHECK_FUNC([if_nametoindex],
+ [AC_DEFINE(HAVE_IF_NAMETOINDEX, [1],
+ [Define as 1 if function exists])]
+ [])
+AC_CHECK_FUNC([if_indextoname],
+ [AC_DEFINE(HAVE_IF_INDEXTONAME, [1],
+ [Define as 1 if function exists])],
+ [])
+AC_CHECK_FUNC([if_nameindex],
+ [AC_DEFINE(HAVE_IF_NAMEINDEX, [1],
+ [Define as 1 if function exists])],
+ [])
+AC_CHECK_FUNC([if_freenameindex],
+ [AC_DEFINE(HAVE_IF_FREENAMEINDEX, [1],
+ [Define as 1 if function exists])]
+ [])
+AC_CHECK_FUNC([gethostname],
+ [AC_DEFINE(HAVE_GETHOSTNAME, [1],
+ [Define as 1 if function exists])]
+ [])
+
+
+dnl *** ESOCK_USE_SOCKET_REGISTRY ***
+dnl At this time we *enable* use of the socket regstry by default
+dnl since we want to be able to have as much debug info as possible.
+
+AC_ARG_ENABLE(esock_use_socket_registry,
+AS_HELP_STRING([--enable-esock-socket-registry], [enable use of the socket registry by default (default)])
+AS_HELP_STRING([--disable-esock-socket-registry], [disable use of the socket registry by default]))
+
+if test "x$enable_esock_socket_registry" = "xyes"; then
+ AC_DEFINE(ESOCK_USE_SOCKET_REGISTRY, [1], [Use socket registry by default])
+else
+ if test "x$disable_esock_socket_registry" = "xyes"; then
+ AC_DEFINE(ESOCK_USE_SOCKET_REGISTRY, [0], [Don't use socket registry by default])
+ else
+ # TEMPORARY default (enabled)
+ AC_DEFINE(ESOCK_USE_SOCKET_REGISTRY, [1], [Use socket registry by default])
+ fi
+fi
+
+
dnl
dnl This test kindly borrowed from Tcl
@@ -1464,7 +1534,7 @@ if test "$have_gethostbyname_r" = yes; then
AC_DEFINE(HAVE_GETHOSTBYNAME_R, GHBN_R_SOLARIS,
[Define to flavour of gethostbyname_r])
;;
- aix4*)
+ aix*|os400*)
# AIX version also needs "struct hostent_data" defn
AC_TRY_COMPILE([#include <netdb.h>],
[struct hostent_data hd;],
@@ -1583,21 +1653,15 @@ AC_CHECK_HEADERS(fcntl.h limits.h unistd.h syslog.h dlfcn.h ieeefp.h \
AC_CHECK_MEMBERS([struct ifreq.ifr_hwaddr], [], [],
[#ifdef __WIN32__
#else
- #ifdef VXWORKS
- #else
#include <net/if.h>
#endif
- #endif
])
AC_CHECK_MEMBERS([struct ifreq.ifr_enaddr], [], [],
[#ifdef __WIN32__
#else
- #ifdef VXWORKS
- #else
#include <net/if.h>
#endif
- #endif
])
dnl ----------------------------------------------------------------------
@@ -1713,6 +1777,12 @@ if test x"$ac_cv_header_netinet_sctp_h" = x"yes"; then
AS_IF([test "x$enable_sctp" = "xlib"],
AC_CHECK_LIB(sctp, sctp_bindx))
AC_CHECK_FUNCS([sctp_bindx sctp_peeloff sctp_getladdrs sctp_freeladdrs sctp_getpaddrs sctp_freepaddrs])
+ AC_CHECK_MEMBERS([struct sctp_accoc_value.assoc_id], [], [],
+ [#if HAVE_SYS_SOCKET_H
+ #include <sys/socket.h>
+ #endif
+ #include <netinet/sctp.h>
+ ])
AC_CHECK_DECLS([SCTP_UNORDERED, SCTP_ADDR_OVER, SCTP_ABORT,
SCTP_EOF, SCTP_SENDALL, SCTP_ADDR_CONFIRMED,
SCTP_DELAYED_ACK_TIME,
@@ -1833,8 +1903,9 @@ fi
if test "x$ac_compiler_gnu" = "xyes"; then
AC_MSG_CHECKING([if we should add -fno-tree-copyrename to CFLAGS for computed gotos to work properly])
+## tree-copyrename was broken in gcc 4.3 and then removed in gcc 6
AC_TRY_COMPILE([],[
- #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
+ #if (__GNUC__ > 4 && __GNUC__ < 6) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
;
#else
#error old and ok
@@ -2080,14 +2151,33 @@ AC_CHECK_FUNCS([getipnodebyname getipnodebyaddr gethostbyname2])
AC_CHECK_FUNCS([ieee_handler fpsetmask finite isnan isinf res_gethostbyname dlopen \
pread pwrite memmove strerror strerror_r strncasecmp \
- gethrtime localtime_r gmtime_r inet_pton mprotect \
+ gethrtime localtime_r gmtime_r mprotect madvise posix_madvise \
mmap mremap memcpy mallopt sbrk _sbrk __sbrk brk _brk __brk \
flockfile fstat strlcpy strlcat setsid posix2time time2posix \
- setlocale nl_langinfo poll mlockall ppoll])
+ setlocale nl_langinfo poll mlockall ppoll vsyslog])
+
+## We have a special check for inet_pton as AC_CHECK_FUCNS does not work
+## on windows 32-bit as there a macro is used to rename the symbol...
+AC_MSG_CHECKING([for inet_pton])
+AC_TRY_LINK([
+#ifdef WIN32
+#include <ws2tcpip.h>
+#else
+#include <arpa/inet.h>
+#endif
+],[inet_pton(2,"",(void*)0)], have_inet_pton=yes, have_inet_pton=no)
+
+if test $have_inet_pton = yes; then
+ AC_DEFINE(HAVE_INET_PTON,[1],
+ [Define to 1 if you have the `inet_pton' function.])
+ AC_MSG_RESULT(yes)
+else
+ AC_MSG_RESULT(no)
+fi
AC_MSG_CHECKING([for isfinite])
AC_TRY_LINK([#include <math.h>],
- [isfinite(0);], have_isfinite=yes, have_isfinite=no),
+ [isfinite(0);], have_isfinite=yes, have_isfinite=no)
if test $have_isfinite = yes; then
AC_DEFINE(HAVE_ISFINITE,[1],
@@ -2191,6 +2281,7 @@ AC_CACHE_CHECK(
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
+ #include <stdio.h>
]],
[[printf("%d", in6addr_any.s6_addr[16]);]]
)],
@@ -2214,6 +2305,7 @@ AC_CACHE_CHECK(
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
+ #include <stdio.h>
]],
[[printf("%d", in6addr_loopback.s6_addr[16]);]]
)],
@@ -3478,6 +3570,26 @@ AH_BOTTOM([
#endif
])
+
+dnl ----------------------------------------------------------------------
+dnl Check for GCC diagnostic ignored "-Waddress-of-packed-member"
+dnl ----------------------------------------------------------------------
+saved_CFLAGS="$CFLAGS"
+CFLAGS="-Werror $CFLAGS"
+AC_TRY_COMPILE([],
+ [_Pragma("GCC diagnostic push")
+ _Pragma("GCC diagnostic ignored \"-Waddress-of-packed-member\"")
+ _Pragma("GCC diagnostic pop")
+ ],
+ AC_DEFINE(HAVE_GCC_DIAG_IGNORE_WADDRESS_OF_PACKED_MEMBER,[1],
+ [define if compiler support _Pragma('GCC diagnostic ignored '-Waddress-of-packed-member'')]))
+CFLAGS="$saved_CFLAGS"
+
+
+dnl ----------------------------------------------------------------------
+dnl Enable any -Werror flags
+dnl ----------------------------------------------------------------------
+
if test "x$GCC" = xyes; then
CFLAGS="$WERRORFLAGS $CFLAGS"
fi
diff --git a/erts/doc/src/.gitignore b/erts/doc/src/.gitignore
new file mode 100644
index 0000000000..abe9a7d858
--- /dev/null
+++ b/erts/doc/src/.gitignore
@@ -0,0 +1,3 @@
+ref_man.xml
+specs.xml
+part.xml \ No newline at end of file
diff --git a/erts/doc/src/Makefile b/erts/doc/src/Makefile
index bb96293947..41ade2bba7 100644
--- a/erts/doc/src/Makefile
+++ b/erts/doc/src/Makefile
@@ -37,29 +37,15 @@ RELSYSDIR = $(RELEASE_PATH)/$(APPLICATION)-$(VSN)
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
-XML_REF1_FILES = epmd.xml \
- erl.xml \
- erlc.xml \
- escript.xml \
- werl.xml \
- erlsrv.xml \
- start_erl.xml \
- run_erl.xml \
- start.xml
-
-ifeq ($(USE_ESOCK), yes)
-XML_REF3_ESOCK_EFILES = socket.xml
-XML_CHAPTER_ESOCK_EFILES = socket_usage.xml
-ESOCK_USE_SOCKET_XML=<xi:include href="socket.xml"\/>
-ESOCK_USE_SOCKET_SPECS_XML=<xi:include href="../specs/specs_socket.xml"/>
-ESOCK_USE_SOCKET_USAGE_XML=<xi:include href="socket_usage.xml"/>
-else
-XML_REF3_ESOCK_EFILES =
-XML_CHAPTER_ESOCK_EFILES =
-ESOCK_USE_SOCKET_XML =
-ESOCK_USE_SOCKET_SPECS_XML =
-ESOCK_USE_SOCKET_USAGE_XML =
-endif
+XML_REF1_FILES = epmd_cmd.xml \
+ erl_cmd.xml \
+ erlc_cmd.xml \
+ escript_cmd.xml \
+ werl_cmd.xml \
+ erlsrv_cmd.xml \
+ start_erl_cmd.xml \
+ run_erl_cmd.xml \
+ start_cmd.xml
XML_REF3_EFILES = \
erl_prim_loader.xml \
@@ -69,16 +55,18 @@ XML_REF3_EFILES = \
persistent_term.xml \
atomics.xml \
counters.xml \
- zlib.xml \
- $(XML_REF3_ESOCK_EFILES)
+ zlib.xml
-XML_REF3_FILES = \
- $(XML_REF3_EFILES) \
+XML_REF3_CREF = \
driver_entry.xml \
erl_nif.xml \
erl_driver.xml \
erts_alloc.xml
+XML_REF3_FILES = \
+ $(XML_REF3_EFILES) \
+ $(XML_REF3_CREF)
+
XML_PART_FILES = \
part.xml internal.xml
@@ -96,7 +84,6 @@ XML_INTERNAL_FILES = \
SuperCarrier.xml \
CountingInstructions.xml
-
XML_CHAPTER_FILES = \
introduction.xml \
tty.xml \
@@ -107,7 +94,6 @@ XML_CHAPTER_FILES = \
driver.xml \
absform.xml \
inet_cfg.xml \
- $(XML_CHAPTER_ESOCK_EFILES) \
erl_ext_dist.xml \
erl_dist_protocol.xml \
communication.xml \
@@ -118,32 +104,24 @@ TOPDOCDIR=../../../doc
BOOK_FILES = book.xml
-GIF_FILES = \
+IMAGE_FILES = \
erl_ext_fig.gif
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF1_FILES) $(XML_APPLICATION_FILES)
+HTML_EXTRA_FILES = $(ERL_TOP)/erts/example/time_compat.erl \
+ $(ERL_TOP)/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl
+
XML_GEN_FILES = $(XML_INTERNAL_FILES:%=$(XMLDIR)/%)
-# ----------------------------------------------------
+NO_CHUNKS = $(XML_REF3_CREF) erl_tracer.xml
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
+# ----------------------------------------------------
-INFO_FILE = ../../info
INFO_FILE_SRC = ../../info.src
-MAN1_FILES = $(XML_REF1_FILES:%.xml=$(MAN1DIR)/%.1)
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_EFILES:%.xml=$(SPECDIR)/specs_%.xml)
-
TOP_SPECS_FILE = specs.xml
XML_FIGURE_DIR = $(XMLDIR)/figures
@@ -153,101 +131,38 @@ PNG_FILES = $(notdir $(INTERNAL_DOC_PNG_FILES))
XMLDIR_PNG_FILES = $(PNG_FILES:%=$(XML_FIGURE_DIR)/%)
# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-KERNEL_SRC=$(ERL_TOP)/lib/kernel/src
-KERNEL_INCLUDE=$(ERL_TOP)/lib/kernel/include
-SPECS_FLAGS = -I$(KERNEL_SRC) -I$(KERNEL_INCLUDE)
-
-# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-_create_dirs := $(shell mkdir -p $(XML_FIGURE_DIR))
+include $(ERL_TOP)/make/doc.mk
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
+_create_dirs := $(shell mkdir -p $(XML_FIGURE_DIR))
$(XML_FIGURE_DIR)/%.png: ../../emulator/internal_doc/figures/%.png
$(INSTALL_DATA) $< $@
-docs: part ref_man specs figures man pdf html $(INFO_FILE)
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN1_FILES) $(MAN3_FILES)
-
-ref_man: ref_man.xml
-part: part.xml
-specs: specs.xml
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
+html: figures
$(INFO_FILE): $(INFO_FILE_SRC) $(ERL_TOP)/make/$(TARGET)/otp.mk
sed -e 's;%RELEASE%;$(SYSTEM_VSN);' $(INFO_FILE_SRC) > $(INFO_FILE)
figures: $(XMLDIR_PNG_FILES)
-debug opt:
-
-ldocs: xmllint local_docs
-
-clean:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN1DIR)/*
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECDIR)/*
- rm -f errs core *~
-
-$(SPECDIR)/specs_%.xml:
+## This rule generate dummy specs for all XML_REF3_CREF's
+$(XML_REF3_CREF:%.xml=$(SPECDIR)/specs_%.xml): $(@:%.xml=%.xml)
$(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) \
-o$(dir $@) -module $(patsubst $(SPECDIR)/specs_%.xml,%,$@)
$(XMLDIR)/%.xml: ../../emulator/internal_doc/%.md $(ERL_TOP)/make/emd2exml
$(ERL_TOP)/make/emd2exml $< $@
-ref_man.xml: ref_man.xml.src
- ($(PERL) -p -e 's?%ESOCK_USE_SOCKET_XML%?$(ESOCK_USE_SOCKET_XML)?' \
- $<) > $@
-
-part.xml: part.xml.src
- ($(PERL) -p -e 's?%ESOCK_USE_SOCKET_USAGE_XML%?$(ESOCK_USE_SOCKET_USAGE_XML)?' \
- $<) > $@
-
-specs.xml: specs.xml.src
- ($(PERL) -p -e 's?%ESOCK_USE_SOCKET_SPECS_XML%?$(ESOCK_USE_SOCKET_SPECS_XML)?' \
- $<) > $@
-
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
+release_html_spec: release_figures
+
+release_figures:
$(INSTALL_DIR) "$(RELSYSDIR)/doc/html/figures"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
$(INSTALL_DATA) $(XMLDIR)/figures/* \
"$(RELSYSDIR)/doc/html/figures"
- $(INSTALL_DATA) $(ERL_TOP)/erts/example/time_compat.erl \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(ERL_TOP)/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DATA) $(MAN1_FILES) "$(RELEASE_PATH)/man/man1"
-
-release_spec:
diff --git a/erts/doc/src/absform.xml b/erts/doc/src/absform.xml
index fd4cdac03f..be37e41bfc 100644
--- a/erts/doc/src/absform.xml
+++ b/erts/doc/src/absform.xml
@@ -35,37 +35,39 @@
<p>This section describes the standard representation of parse trees for Erlang
programs as Erlang terms. This representation is known as the <em>abstract
format</em>. Functions dealing with such parse trees are
- <seealso marker="compiler:compile#forms/1">
- <c>compile:forms/1,2</c></seealso> and functions in the following
+ <seemfa marker="compiler:compile#forms/1">
+ <c>compile:forms/1,2</c></seemfa> and functions in the following
modules:</p>
<list type="bulleted">
- <item><seealso marker="stdlib:epp">
- <c>epp(3)</c></seealso></item>
- <item><seealso marker="stdlib:erl_eval">
- <c>erl_eval(3)</c></seealso></item>
- <item><seealso marker="stdlib:erl_lint">
- <c>erl_lint(3)</c></seealso></item>
- <item><seealso marker="stdlib:erl_parse">
- <c>erl_parse(3)</c></seealso></item>
- <item><seealso marker="stdlib:erl_pp">
- <c>erl_pp(3)</c></seealso></item>
- <item><seealso marker="stdlib:io">
- <c>io(3)</c></seealso></item>
+ <item><seeerl marker="stdlib:epp">
+ <c>epp(3)</c></seeerl></item>
+ <item><seeerl marker="stdlib:erl_eval">
+ <c>erl_eval(3)</c></seeerl></item>
+ <item><seeerl marker="stdlib:erl_lint">
+ <c>erl_lint(3)</c></seeerl></item>
+ <item><seeerl marker="stdlib:erl_parse">
+ <c>erl_parse(3)</c></seeerl></item>
+ <item><seeerl marker="stdlib:erl_pp">
+ <c>erl_pp(3)</c></seeerl></item>
+ <item><seeerl marker="stdlib:io">
+ <c>io(3)</c></seeerl></item>
</list>
<p>The functions are also used as input and output for parse transforms, see
- the <seealso marker="compiler:compile"><c>compile(3)</c></seealso>
+ the <seeerl marker="compiler:compile"><c>compile(3)</c></seeerl>
module.</p>
<p>We use the function <c>Rep</c> to denote the mapping from an Erlang source
construct <c>C</c> to its abstract format representation <c>R</c>, and write
<c>R = Rep(C)</c>.</p>
- <p>The word <c>LINE</c> in this section represents an integer, and denotes the
- number of the line in the source file where the construction occurred.
- Several instances of <c>LINE</c> in the same construction can denote
- different lines.</p>
+ <p>The word <c>ANNO</c> in this section represents an annotation,
+ and denotes among other things the number of the line in the
+ source file where the construction occurred. See <seeerl
+ marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl> for details.
+ Several instances of <c>ANNO</c> in the same construction can denote
+ different annotations.</p>
<p>As operators are not terms in their own right, when operators are
mentioned below, the representation of an operator is to be taken to
@@ -86,27 +88,27 @@
<item>
<p>If F is an attribute <c>-export([Fun_1/A_1, ..., Fun_k/A_k])</c>,
then Rep(F) =
- <c>{attribute,LINE,export,[{Fun_1,A_1}, ..., {Fun_k,A_k}]}</c>.</p>
+ <c>{attribute,ANNO,export,[{Fun_1,A_1}, ..., {Fun_k,A_k}]}</c>.</p>
</item>
<item>
<p>If F is an attribute <c>-import(Mod,[Fun_1/A_1, ..., Fun_k/A_k])</c>,
then Rep(F) =
- <c>{attribute,LINE,import,{Mod,[{Fun_1,A_1}, ...,
+ <c>{attribute,ANNO,import,{Mod,[{Fun_1,A_1}, ...,
{Fun_k,A_k}]}}</c>.</p>
</item>
<item>
<p>If F is an attribute <c>-module(Mod)</c>, then
- Rep(F) = <c>{attribute,LINE,module,Mod}</c>.</p>
+ Rep(F) = <c>{attribute,ANNO,module,Mod}</c>.</p>
</item>
<item>
<p>If F is an attribute <c>-file(File,Line)</c>, then
- Rep(F) = <c>{attribute,LINE,file,{File,Line}}</c>.</p>
+ Rep(F) = <c>{attribute,ANNO,file,{File,Line}}</c>.</p>
</item>
<item>
<p>If F is a function declaration <c>Name Fc_1 ; ... ; Name Fc_k</c>,
where each <c>Fc_i</c> is a function clause with a pattern sequence of
the same length <c>Arity</c>, then Rep(F) =
- <c>{function,LINE,Name,Arity,[Rep(Fc_1), ...,Rep(Fc_k)]}</c>.</p>
+ <c>{function,ANNO,Name,Arity,[Rep(Fc_1), ...,Rep(Fc_k)]}</c>.</p>
</item>
<item>
<p>If F is a function specification <c>-Spec Name Ft_1; ...; Ft_k</c>,
@@ -114,7 +116,7 @@
<c>callback</c>, and each <c>Ft_i</c> is a possibly constrained
function type with an argument sequence of the same length
<c>Arity</c>, then Rep(F) =
- <c>{attribute,Line,Spec,{{Name,Arity},[Rep(Ft_1), ...,
+ <c>{attribute,ANNO,Spec,{{Name,Arity},[Rep(Ft_1), ...,
Rep(Ft_k)]}}</c>.</p>
</item>
<item>
@@ -122,13 +124,13 @@
<c>-spec Mod:Name Ft_1; ...; Ft_k</c>, where each <c>Ft_i</c> is a
possibly constrained function type with an argument sequence of the
same length <c>Arity</c>, then Rep(F) =
- <c>{attribute,Line,spec,{{Mod,Name,Arity},[Rep(Ft_1), ...,
+ <c>{attribute,ANNO,spec,{{Mod,Name,Arity},[Rep(Ft_1), ...,
Rep(Ft_k)]}}</c>.</p>
</item>
<item>
<p>If F is a record declaration <c>-record(Name,{V_1, ..., V_k})</c>,
where each <c>V_i</c> is a record field, then Rep(F) =
- <c>{attribute,LINE,record,{Name,[Rep(V_1), ..., Rep(V_k)]}}</c>.
+ <c>{attribute,ANNO,record,{Name,[Rep(V_1), ..., Rep(V_k)]}}</c>.
For Rep(V), see below.</p>
</item>
<item>
@@ -136,12 +138,12 @@
where <c>Type</c> is either the atom <c>type</c> or the atom
<c>opaque</c>, each <c>V_i</c> is a type variable, and <c>T</c> is a type,
then Rep(F) =
- <c>{attribute,LINE,Type,{Name,Rep(T),[Rep(V_1), ...,
+ <c>{attribute,ANNO,Type,{Name,Rep(T),[Rep(V_1), ...,
Rep(V_k)]}}</c>.</p>
</item>
<item>
<p>If F is a wild attribute <c>-A(T)</c>, then
- Rep(F) = <c>{attribute,LINE,A,T}</c>.</p>
+ Rep(F) = <c>{attribute,ANNO,A,T}</c>.</p>
</item>
</list>
@@ -154,20 +156,20 @@
<list type="bulleted">
<item>
<p>If V is <c>A</c>, then
- Rep(V) = <c>{record_field,LINE,Rep(A)}</c>.</p>
+ Rep(V) = <c>{record_field,ANNO,Rep(A)}</c>.</p>
</item>
<item>
<p>If V is <c>A = E</c>, where <c>E</c> is an expression, then
- Rep(V) = <c>{record_field,LINE,Rep(A),Rep(E)}</c>.</p>
+ Rep(V) = <c>{record_field,ANNO,Rep(A),Rep(E)}</c>.</p>
</item>
<item>
<p>If V is <c>A :: T</c>, where <c>T</c> is a type, then Rep(V) =
- <c>{typed_record_field,{record_field,LINE,Rep(A)},Rep(T)}</c>.</p>
+ <c>{typed_record_field,{record_field,ANNO,Rep(A)},Rep(T)}</c>.</p>
</item>
<item>
<p>If V is <c>A = E :: T</c>, where
<c>E</c> is an expression and <c>T</c> is a type, then Rep(V) =
- <c>{typed_record_field,{record_field,LINE,Rep(A),Rep(E)},Rep(T)}</c>.
+ <c>{typed_record_field,{record_field,ANNO,Rep(A),Rep(E)},Rep(T)}</c>.
</p>
</item>
</list>
@@ -177,8 +179,8 @@
<title>Representation of Parse Errors and End-of-File</title>
<p>In addition to the representations of forms, the list that represents
a module declaration (as returned by functions in
- <seealso marker="stdlib:epp"><c>epp(3)</c></seealso> and
- <seealso marker="stdlib:erl_parse"><c>erl_parse(3)</c></seealso>)
+ <seeerl marker="stdlib:epp"><c>epp(3)</c></seeerl> and
+ <seeerl marker="stdlib:erl_parse"><c>erl_parse(3)</c></seeerl>)
can contain the following:</p>
<list type="bulleted">
@@ -190,8 +192,11 @@
<item>
<p><c>{eof,LOCATION}</c>, denoting an end-of-stream
encountered before a complete form had been parsed.
- The word <c>LOCATION</c> represents an integer, and denotes the
- number of the last line in the source file.
+ The word <c>LOCATION</c> represents a location, and denotes the
+ number of the last line, and possibly the number of the last
+ column on that line, in the source file. See <seeerl
+ marker="stdlib:erl_anno">erl_anno(3)</seeerl> for
+ details.
</p>
</item>
</list>
@@ -205,22 +210,22 @@
<list type="bulleted">
<item>
- <p>If L is an atom literal, then Rep(L) = <c>{atom,LINE,L}</c>.</p>
+ <p>If L is an atom literal, then Rep(L) = <c>{atom,ANNO,L}</c>.</p>
</item>
<item>
- <p>If L is a character literal, then Rep(L) = <c>{char,LINE,L}</c>.</p>
+ <p>If L is a character literal, then Rep(L) = <c>{char,ANNO,L}</c>.</p>
</item>
<item>
- <p>If L is a float literal, then Rep(L) = <c>{float,LINE,L}</c>.</p>
+ <p>If L is a float literal, then Rep(L) = <c>{float,ANNO,L}</c>.</p>
</item>
<item>
<p>If L is an integer literal, then
- Rep(L) = <c>{integer,LINE,L}</c>.</p>
+ Rep(L) = <c>{integer,ANNO,L}</c>.</p>
</item>
<item>
<p>If L is a string literal consisting of the characters
<c>C_1</c>, ..., <c>C_k</c>, then
- Rep(L) = <c>{string,LINE,[C_1, ..., C_k]}</c>.</p>
+ Rep(L) = <c>{string,ANNO,[C_1, ..., C_k]}</c>.</p>
</item>
</list>
@@ -245,42 +250,42 @@
<c>&lt;&lt;P_1:Size_1/TSL_1, ..., P_k:Size_k/TSL_k>></c>, where each
<c>Size_i</c> is an expression that can be evaluated to an integer,
and each <c>TSL_i</c> is a type specificer list, then Rep(P) =
- <c>{bin,LINE,[{bin_element,LINE,Rep(P_1),Rep(Size_1),Rep(TSL_1)},
- ..., {bin_element,LINE,Rep(P_k),Rep(Size_k),Rep(TSL_k)}]}</c>.
+ <c>{bin,ANNO,[{bin_element,ANNO,Rep(P_1),Rep(Size_1),Rep(TSL_1)},
+ ..., {bin_element,ANNO,Rep(P_k),Rep(Size_k),Rep(TSL_k)}]}</c>.
For Rep(TSL), see below.
An omitted <c>Size_i</c> is represented by <c>default</c>.
An omitted <c>TSL_i</c> is represented by <c>default</c>.</p>
</item>
<item>
<p>If P is a compound pattern <c>P_1 = P_2</c>, then Rep(P) =
- <c>{match,LINE,Rep(P_1),Rep(P_2)}</c>.</p>
+ <c>{match,ANNO,Rep(P_1),Rep(P_2)}</c>.</p>
</item>
<item>
<p>If P is a cons pattern <c>[P_h | P_t]</c>, then Rep(P) =
- <c>{cons,LINE,Rep(P_h),Rep(P_t)}</c>.</p>
+ <c>{cons,ANNO,Rep(P_h),Rep(P_t)}</c>.</p>
</item>
<item>
<p>If P is a map pattern <c>#{A_1, ..., A_k}</c>, where each
<c>A_i</c> is an association <c>P_i_1 := P_i_2</c>, then Rep(P) =
- <c>{map,LINE,[Rep(A_1), ..., Rep(A_k)]}</c>.
+ <c>{map,ANNO,[Rep(A_1), ..., Rep(A_k)]}</c>.
For Rep(A), see below.</p>
</item>
<item>
<p>If P is a nil pattern <c>[]</c>, then Rep(P) =
- <c>{nil,LINE}</c>.</p>
+ <c>{nil,ANNO}</c>.</p>
</item>
<item>
<p>If P is an operator pattern <c>P_1 Op P_2</c>, where <c>Op</c> is a
binary operator (this is either an occurrence of <c>++</c> applied to
a literal string or character list, or an occurrence of an expression
that can be evaluated to a number at compile time), then Rep(P) =
- <c>{op,LINE,Op,Rep(P_1),Rep(P_2)}</c>.</p>
+ <c>{op,ANNO,Op,Rep(P_1),Rep(P_2)}</c>.</p>
</item>
<item>
<p>If P is an operator pattern <c>Op P_0</c>, where <c>Op</c> is a
unary operator (this is an occurrence of an expression that can be
evaluated to a number at compile time), then Rep(P) =
- <c>{op,LINE,Op,Rep(P_0)}</c>.</p>
+ <c>{op,ANNO,Op,Rep(P_0)}</c>.</p>
</item>
<item>
<p>If P is a parenthesized pattern <c>( P_0 )</c>, then Rep(P) =
@@ -290,24 +295,24 @@
<item>
<p>If P is a record field index pattern <c>#Name.Field</c>,
where <c>Field</c> is an atom, then Rep(P) =
- <c>{record_index,LINE,Name,Rep(Field)}</c>.</p>
+ <c>{record_index,ANNO,Name,Rep(Field)}</c>.</p>
</item>
<item>
<p>If P is a record pattern <c>#Name{Field_1=P_1, ..., Field_k=P_k}</c>,
where each <c>Field_i</c> is an atom or <c>_</c>, then Rep(P) =
- <c>{record,LINE,Name,[{record_field,LINE,Rep(Field_1),Rep(P_1)}, ...,
- {record_field,LINE,Rep(Field_k),Rep(P_k)}]}</c>.</p>
+ <c>{record,ANNO,Name,[{record_field,ANNO,Rep(Field_1),Rep(P_1)}, ...,
+ {record_field,ANNO,Rep(Field_k),Rep(P_k)}]}</c>.</p>
</item>
<item>
<p>If P is a tuple pattern <c>{P_1, ..., P_k}</c>, then Rep(P) =
- <c>{tuple,LINE,[Rep(P_1), ..., Rep(P_k)]}</c>.</p>
+ <c>{tuple,ANNO,[Rep(P_1), ..., Rep(P_k)]}</c>.</p>
</item>
<item>
<p>If P is a universal pattern <c>_</c>, then Rep(P) =
- <c>{var,LINE,'_'}</c>.</p></item>
+ <c>{var,ANNO,'_'}</c>.</p></item>
<item>
<p>If P is a variable pattern <c>V</c>, then Rep(P) =
- <c>{var,LINE,A}</c>, where A is an atom with a printname consisting
+ <c>{var,ANNO,A}</c>, where A is an atom with a printname consisting
of the same characters as <c>V</c>.</p>
</item>
</list>
@@ -331,7 +336,7 @@
<p>If E is a bitstring comprehension
<c>&lt;&lt;E_0 || Q_1, ..., Q_k>></c>,
where each <c>Q_i</c> is a qualifier, then Rep(E) =
- <c>{bc,LINE,Rep(E_0),[Rep(Q_1), ..., Rep(Q_k)]}</c>.
+ <c>{bc,ANNO,Rep(E_0),[Rep(Q_1), ..., Rep(Q_k)]}</c>.
For Rep(Q), see below.</p>
</item>
<item>
@@ -339,8 +344,8 @@
<c>&lt;&lt;E_1:Size_1/TSL_1, ..., E_k:Size_k/TSL_k>></c>,
where each <c>Size_i</c> is an expression and each
<c>TSL_i</c> is a type specificer list, then Rep(E) =
- <c>{bin,LINE,[{bin_element,LINE,Rep(E_1),Rep(Size_1),Rep(TSL_1)},
- ..., {bin_element,LINE,Rep(E_k),Rep(Size_k),Rep(TSL_k)}]}</c>.
+ <c>{bin,ANNO,[{bin_element,ANNO,Rep(E_1),Rep(Size_1),Rep(TSL_1)},
+ ..., {bin_element,ANNO,Rep(E_k),Rep(Size_k),Rep(TSL_k)}]}</c>.
For Rep(TSL), see below.
An omitted <c>Size_i</c> is represented by <c>default</c>.
An omitted <c>TSL_i</c> is represented by <c>default</c>.</p>
@@ -348,94 +353,94 @@
<item>
<p>If E is a block expression <c>begin B end</c>,
where <c>B</c> is a body, then Rep(E) =
- <c>{block,LINE,Rep(B)}</c>.</p>
+ <c>{block,ANNO,Rep(B)}</c>.</p>
</item>
<item>
<p>If E is a case expression <c>case E_0 of Cc_1 ; ... ; Cc_k end</c>,
where <c>E_0</c> is an expression and each <c>Cc_i</c> is a
case clause, then Rep(E) =
- <c>{'case',LINE,Rep(E_0),[Rep(Cc_1), ..., Rep(Cc_k)]}</c>.</p>
+ <c>{'case',ANNO,Rep(E_0),[Rep(Cc_1), ..., Rep(Cc_k)]}</c>.</p>
</item>
<item>
<p>If E is a catch expression <c>catch E_0</c>, then Rep(E) =
- <c>{'catch',LINE,Rep(E_0)}</c>.</p>
+ <c>{'catch',ANNO,Rep(E_0)}</c>.</p>
</item>
<item>
<p>If E is a cons skeleton <c>[E_h | E_t]</c>, then Rep(E) =
- <c>{cons,LINE,Rep(E_h),Rep(E_t)}</c>.</p>
+ <c>{cons,ANNO,Rep(E_h),Rep(E_t)}</c>.</p>
</item>
<item>
<p>If E is a fun expression <c>fun Name/Arity</c>, then Rep(E) =
- <c>{'fun',LINE,{function,Name,Arity}}</c>.</p>
+ <c>{'fun',ANNO,{function,Name,Arity}}</c>.</p>
</item>
<item>
<p>If E is a fun expression <c>fun Module:Name/Arity</c>, then Rep(E) =
- <c>{'fun',LINE,{function,Rep(Module),Rep(Name),Rep(Arity)}}</c>.
+ <c>{'fun',ANNO,{function,Rep(Module),Rep(Name),Rep(Arity)}}</c>.
(Before Erlang/OTP R15: Rep(E) =
- <c>{'fun',LINE,{function,Module,Name,Arity}}</c>.)</p>
+ <c>{'fun',ANNO,{function,Module,Name,Arity}}</c>.)</p>
</item>
<item>
<p>If E is a fun expression <c>fun Fc_1 ; ... ; Fc_k end</c>,
where each <c>Fc_i</c> is a function clause, then Rep(E) =
- <c>{'fun',LINE,{clauses,[Rep(Fc_1), ..., Rep(Fc_k)]}}</c>.</p>
+ <c>{'fun',ANNO,{clauses,[Rep(Fc_1), ..., Rep(Fc_k)]}}</c>.</p>
</item>
<item>
<p>If E is a fun expression <c>fun Name Fc_1 ; ... ; Name Fc_k end</c>,
where <c>Name</c> is a variable and each
<c>Fc_i</c> is a function clause, then Rep(E) =
- <c>{named_fun,LINE,Name,[Rep(Fc_1), ..., Rep(Fc_k)]}</c>.</p>
+ <c>{named_fun,ANNO,Name,[Rep(Fc_1), ..., Rep(Fc_k)]}</c>.</p>
</item>
<item>
<p>If E is a function call <c>E_0(E_1, ..., E_k)</c>, then Rep(E) =
- <c>{call,LINE,Rep(E_0),[Rep(E_1), ..., Rep(E_k)]}</c>.</p>
+ <c>{call,ANNO,Rep(E_0),[Rep(E_1), ..., Rep(E_k)]}</c>.</p>
</item>
<item>
<p>If E is a function call <c>E_m:E_0(E_1, ..., E_k)</c>, then Rep(E) =
- <c>{call,LINE,{remote,LINE,Rep(E_m),Rep(E_0)},[Rep(E_1), ...,
+ <c>{call,ANNO,{remote,ANNO,Rep(E_m),Rep(E_0)},[Rep(E_1), ...,
Rep(E_k)]}</c>.</p>
</item>
<item>
<p>If E is an if expression <c>if Ic_1 ; ... ; Ic_k end</c>,
where each <c>Ic_i</c> is an if clause, then Rep(E) =
- <c>{'if',LINE,[Rep(Ic_1), ..., Rep(Ic_k)]}</c>.</p>
+ <c>{'if',ANNO,[Rep(Ic_1), ..., Rep(Ic_k)]}</c>.</p>
</item>
<item>
<p>If E is a list comprehension <c>[E_0 || Q_1, ..., Q_k]</c>,
where each <c>Q_i</c> is a qualifier, then Rep(E) =
- <c>{lc,LINE,Rep(E_0),[Rep(Q_1), ..., Rep(Q_k)]}</c>.
+ <c>{lc,ANNO,Rep(E_0),[Rep(Q_1), ..., Rep(Q_k)]}</c>.
For Rep(Q), see below.</p>
</item>
<item>
<p>If E is a map creation <c>#{A_1, ..., A_k}</c>,
where each <c>A_i</c> is an association <c>E_i_1 => E_i_2</c>,
- then Rep(E) = <c>{map,LINE,[Rep(A_1), ..., Rep(A_k)]}</c>.
+ then Rep(E) = <c>{map,ANNO,[Rep(A_1), ..., Rep(A_k)]}</c>.
For Rep(A), see below.</p>
</item>
<item>
<p>If E is a map update <c>E_0#{A_1, ..., A_k}</c>,
where each <c>A_i</c> is an association <c>E_i_1 => E_i_2</c>
or <c>E_i_1 := E_i_2</c>, then Rep(E) =
- <c>{map,LINE,Rep(E_0),[Rep(A_1), ..., Rep(A_k)]}</c>.
+ <c>{map,ANNO,Rep(E_0),[Rep(A_1), ..., Rep(A_k)]}</c>.
For Rep(A), see below.</p>
</item>
<item>
<p>If E is a match operator expression <c>P = E_0</c>,
where <c>P</c> is a pattern, then Rep(E) =
- <c>{match,LINE,Rep(P),Rep(E_0)}</c>.</p>
+ <c>{match,ANNO,Rep(P),Rep(E_0)}</c>.</p>
</item>
<item>
- <p>If E is nil, <c>[]</c>, then Rep(E) = <c>{nil,LINE}</c>.</p>
+ <p>If E is nil, <c>[]</c>, then Rep(E) = <c>{nil,ANNO}</c>.</p>
</item>
<item>
<p>If E is an operator expression <c>E_1 Op E_2</c>,
where <c>Op</c> is a binary operator other than match operator
<c>=</c>, then Rep(E) =
- <c>{op,LINE,Op,Rep(E_1),Rep(E_2)}</c>.</p>
+ <c>{op,ANNO,Op,Rep(E_1),Rep(E_2)}</c>.</p>
</item>
<item>
<p>If E is an operator expression <c>Op E_0</c>,
where <c>Op</c> is a unary operator, then Rep(E) =
- <c>{op,LINE,Op,Rep(E_0)}</c>.</p>
+ <c>{op,ANNO,Op,Rep(E_0)}</c>.</p>
</item>
<item>
<p>If E is a parenthesized expression <c>( E_0 )</c>, then Rep(E) =
@@ -445,68 +450,68 @@
<item>
<p>If E is a receive expression <c>receive Cc_1 ; ... ; Cc_k end</c>,
where each <c>Cc_i</c> is a case clause, then Rep(E) =
- <c>{'receive',LINE,[Rep(Cc_1), ..., Rep(Cc_k)]}</c>.</p>
+ <c>{'receive',ANNO,[Rep(Cc_1), ..., Rep(Cc_k)]}</c>.</p>
</item>
<item>
<p>If E is a receive expression
<c>receive Cc_1 ; ... ; Cc_k after E_0 -> B_t end</c>,
where each <c>Cc_i</c> is a case clause, <c>E_0</c> is an expression,
and <c>B_t</c> is a body, then Rep(E) =
- <c>{'receive',LINE,[Rep(Cc_1), ...,
+ <c>{'receive',ANNO,[Rep(Cc_1), ...,
Rep(Cc_k)],Rep(E_0),Rep(B_t)}</c>.</p>
</item>
<item>
<p>If E is a record creation
<c>#Name{Field_1=E_1, ..., Field_k=E_k}</c>,
where each <c>Field_i</c> is an atom or <c>_</c>, then Rep(E) =
- <c>{record,LINE,Name,[{record_field,LINE,Rep(Field_1),Rep(E_1)},
- ..., {record_field,LINE,Rep(Field_k),Rep(E_k)}]}</c>.</p>
+ <c>{record,ANNO,Name,[{record_field,ANNO,Rep(Field_1),Rep(E_1)},
+ ..., {record_field,ANNO,Rep(Field_k),Rep(E_k)}]}</c>.</p>
</item>
<item>
<p>If E is a record field access <c>E_0#Name.Field</c>,
where <c>Field</c> is an atom, then Rep(E) =
- <c>{record_field,LINE,Rep(E_0),Name,Rep(Field)}</c>.</p>
+ <c>{record_field,ANNO,Rep(E_0),Name,Rep(Field)}</c>.</p>
</item>
<item>
<p>If E is a record field index <c>#Name.Field</c>,
where <c>Field</c> is an atom, then Rep(E) =
- <c>{record_index,LINE,Name,Rep(Field)}</c>.</p></item>
+ <c>{record_index,ANNO,Name,Rep(Field)}</c>.</p></item>
<item>
<p>If E is a record update
<c>E_0#Name{Field_1=E_1, ..., Field_k=E_k}</c>,
where each <c>Field_i</c> is an atom, then Rep(E) =
- <c>{record,LINE,Rep(E_0),Name,[{record_field,LINE,Rep(Field_1),Rep(E_1)},
- ..., {record_field,LINE,Rep(Field_k),Rep(E_k)}]}</c>.</p>
+ <c>{record,ANNO,Rep(E_0),Name,[{record_field,ANNO,Rep(Field_1),Rep(E_1)},
+ ..., {record_field,ANNO,Rep(Field_k),Rep(E_k)}]}</c>.</p>
</item>
<item>
<p>If E is a tuple skeleton <c>{E_1, ..., E_k}</c>, then Rep(E) =
- <c>{tuple,LINE,[Rep(E_1), ..., Rep(E_k)]}</c>.</p>
+ <c>{tuple,ANNO,[Rep(E_1), ..., Rep(E_k)]}</c>.</p>
</item>
<item>
<p>If E is a try expression <c>try B catch Tc_1 ; ... ; Tc_k end</c>,
where <c>B</c> is a body and each <c>Tc_i</c> is a catch clause,
then Rep(E) =
- <c>{'try',LINE,Rep(B),[],[Rep(Tc_1), ..., Rep(Tc_k)],[]}</c>.</p>
+ <c>{'try',ANNO,Rep(B),[],[Rep(Tc_1), ..., Rep(Tc_k)],[]}</c>.</p>
</item>
<item>
<p>If E is a try expression
<c>try B of Cc_1 ; ... ; Cc_k catch Tc_1 ; ... ; Tc_n end</c>,
where <c>B</c> is a body, each <c>Cc_i</c> is a case clause, and
each <c>Tc_j</c> is a catch clause, then Rep(E) =
- <c>{'try',LINE,Rep(B),[Rep(Cc_1), ..., Rep(Cc_k)],[Rep(Tc_1), ...,
+ <c>{'try',ANNO,Rep(B),[Rep(Cc_1), ..., Rep(Cc_k)],[Rep(Tc_1), ...,
Rep(Tc_n)],[]}</c>.</p>
</item>
<item>
<p>If E is a try expression <c>try B after A end</c>,
where <c>B</c> and <c>A</c> are bodies, then Rep(E) =
- <c>{'try',LINE,Rep(B),[],[],Rep(A)}</c>.</p>
+ <c>{'try',ANNO,Rep(B),[],[],Rep(A)}</c>.</p>
</item>
<item>
<p>If E is a try expression
<c>try B of Cc_1 ; ... ; Cc_k after A end</c>,
where <c>B</c> and <c>A</c> are a bodies,
and each <c>Cc_i</c> is a case clause, then Rep(E) =
- <c>{'try',LINE,Rep(B),[Rep(Cc_1), ...,
+ <c>{'try',ANNO,Rep(B),[Rep(Cc_1), ...,
Rep(Cc_k)],[],Rep(A)}</c>.</p>
</item>
<item>
@@ -514,7 +519,7 @@
<c>try B catch Tc_1 ; ... ; Tc_k after A end</c>,
where <c>B</c> and <c>A</c> are bodies,
and each <c>Tc_i</c> is a catch clause, then Rep(E) =
- <c>{'try',LINE,Rep(B),[],[Rep(Tc_1), ...,
+ <c>{'try',ANNO,Rep(B),[],[Rep(Tc_1), ...,
Rep(Tc_k)],Rep(A)}</c>.</p>
</item>
<item>
@@ -523,11 +528,11 @@
end</c>, where <c>B</c> and <c>A</c> are a bodies,
each <c>Cc_i</c> is a case clause,
and each <c>Tc_j</c> is a catch clause, then Rep(E) =
- <c>{'try',LINE,Rep(B),[Rep(Cc_1), ..., Rep(Cc_k)],[Rep(Tc_1), ...,
+ <c>{'try',ANNO,Rep(B),[Rep(Cc_1), ..., Rep(Cc_k)],[Rep(Tc_1), ...,
Rep(Tc_n)],Rep(A)}</c>.</p>
</item>
<item>
- <p>If E is a variable <c>V</c>, then Rep(E) = <c>{var,LINE,A}</c>,
+ <p>If E is a variable <c>V</c>, then Rep(E) = <c>{var,ANNO,A}</c>,
where <c>A</c> is an atom with a printname consisting of the same
characters as <c>V</c>.</p>
</item>
@@ -545,12 +550,12 @@
<item>
<p>If Q is a generator <c>P &lt;- E</c>, where <c>P</c> is
a pattern and <c>E</c> is an expression, then Rep(Q) =
- <c>{generate,LINE,Rep(P),Rep(E)}</c>.</p>
+ <c>{generate,ANNO,Rep(P),Rep(E)}</c>.</p>
</item>
<item>
<p>If Q is a bitstring generator <c>P &lt;= E</c>, where <c>P</c> is
a pattern and <c>E</c> is an expression, then Rep(Q) =
- <c>{b_generate,LINE,Rep(P),Rep(E)}</c>.</p>
+ <c>{b_generate,ANNO,Rep(P),Rep(E)}</c>.</p>
</item>
</list>
</section>
@@ -581,11 +586,11 @@
<list type="bulleted">
<item>
<p>If A is an association <c>K => V</c>,
- then Rep(A) = <c>{map_field_assoc,LINE,Rep(K),Rep(V)}</c>.</p>
+ then Rep(A) = <c>{map_field_assoc,ANNO,Rep(K),Rep(V)}</c>.</p>
</item>
<item>
<p>If A is an association <c>K := V</c>,
- then Rep(A) = <c>{map_field_exact,LINE,Rep(K),Rep(V)}</c>.</p>
+ then Rep(A) = <c>{map_field_exact,ANNO,Rep(K),Rep(V)}</c>.</p>
</item>
</list>
</section>
@@ -602,18 +607,18 @@
<item>
<p>If C is a case clause <c>P -> B</c>,
where <c>P</c> is a pattern and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,[Rep(P)],[],Rep(B)}</c>.</p>
+ Rep(C) = <c>{clause,ANNO,[Rep(P)],[],Rep(B)}</c>.</p>
</item>
<item>
<p>If C is a case clause <c>P when Gs -> B</c>,
where <c>P</c> is a pattern,
<c>Gs</c> is a guard sequence, and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,[Rep(P)],Rep(Gs),Rep(B)}</c>.</p>
+ Rep(C) = <c>{clause,ANNO,[Rep(P)],Rep(Gs),Rep(B)}</c>.</p>
</item>
<item>
<p>If C is a catch clause <c>P -> B</c>,
where <c>P</c> is a pattern and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,[Rep({throw,P,_})],[],Rep(B)}</c>,
+ Rep(C) = <c>{clause,ANNO,[Rep({throw,P,_})],[],Rep(B)}</c>,
that is, a catch clause with an explicit exception class
<c>throw</c> and with or without an explicit stacktrace
variable <c>_</c> cannot be distinguished from a catch clause
@@ -624,7 +629,7 @@
<p>If C is a catch clause <c>X : P -> B</c>,
where <c>X</c> is an atomic literal or a variable pattern,
<c>P</c> is a pattern, and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,[Rep({X,P,_})],[],Rep(B)}</c>,
+ Rep(C) = <c>{clause,ANNO,[Rep({X,P,_})],[],Rep(B)}</c>,
that is, a catch clause with an explicit exception class and
with an explicit stacktrace variable <c>_</c> cannot be
distinguished from a catch clause with an explicit exception
@@ -635,13 +640,13 @@
where <c>X</c> is an atomic literal or a variable pattern,
<c>P</c> is a pattern, <c>S</c> is a variable, and <c>B</c>
is a body, then
- Rep(C) = <c>{clause,LINE,[Rep({X,P,S})],[],Rep(B)}</c>.</p>
+ Rep(C) = <c>{clause,ANNO,[Rep({X,P,S})],[],Rep(B)}</c>.</p>
</item>
<item>
<p>If C is a catch clause <c>P when Gs -> B</c>,
where <c>P</c> is a pattern, <c>Gs</c> is a guard sequence,
and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,[Rep({throw,P,_})],Rep(Gs),Rep(B)}</c>,
+ Rep(C) = <c>{clause,ANNO,[Rep({throw,P,_})],Rep(Gs),Rep(B)}</c>,
that is, a catch clause with an explicit exception class
<c>throw</c> and with or without an explicit stacktrace
variable <c>_</c> cannot be distinguished from a catch clause
@@ -653,7 +658,7 @@
where <c>X</c> is an atomic literal or a variable pattern,
<c>P</c> is a pattern, <c>Gs</c> is a guard sequence,
and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,[Rep({X,P,_})],Rep(Gs),Rep(B)}</c>,
+ Rep(C) = <c>{clause,ANNO,[Rep({X,P,_})],Rep(Gs),Rep(B)}</c>,
that is, a catch clause with an explicit exception class and
with an explicit stacktrace variable <c>_</c> cannot be
distinguished from a catch clause with an explicit exception
@@ -664,23 +669,23 @@
where <c>X</c> is an atomic literal or a variable pattern,
<c>P</c> is a pattern, <c>Gs</c> is a guard sequence,
<c>S</c> is a variable, and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,[Rep({X,P,S})],Rep(Gs),Rep(B)}</c>.</p>
+ Rep(C) = <c>{clause,ANNO,[Rep({X,P,S})],Rep(Gs),Rep(B)}</c>.</p>
</item>
<item>
<p>If C is a function clause <c>( Ps ) -> B</c>,
where <c>Ps</c> is a pattern sequence and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,Rep(Ps),[],Rep(B)}</c>.</p>
+ Rep(C) = <c>{clause,ANNO,Rep(Ps),[],Rep(B)}</c>.</p>
</item>
<item>
<p>If C is a function clause <c>( Ps ) when Gs -> B</c>,
where <c>Ps</c> is a pattern sequence,
<c>Gs</c> is a guard sequence and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,Rep(Ps),Rep(Gs),Rep(B)}</c>.</p>
+ Rep(C) = <c>{clause,ANNO,Rep(Ps),Rep(Gs),Rep(B)}</c>.</p>
</item>
<item>
<p>If C is an if clause <c>Gs -> B</c>,
where <c>Gs</c> is a guard sequence and <c>B</c> is a body, then
- Rep(C) = <c>{clause,LINE,[],Rep(Gs),Rep(B)}</c>.</p>
+ Rep(C) = <c>{clause,ANNO,[],Rep(Gs),Rep(B)}</c>.</p>
</item>
</list>
</section>
@@ -706,54 +711,54 @@
<c>&lt;&lt;Gt_1:Size_1/TSL_1, ..., Gt_k:Size_k/TSL_k>></c>,
where each <c>Size_i</c> is a guard test and each
<c>TSL_i</c> is a type specificer list, then Rep(Gt) =
- <c>{bin,LINE,[{bin_element,LINE,Rep(Gt_1),Rep(Size_1),Rep(TSL_1)},
- ..., {bin_element,LINE,Rep(Gt_k),Rep(Size_k),Rep(TSL_k)}]}</c>.
+ <c>{bin,ANNO,[{bin_element,ANNO,Rep(Gt_1),Rep(Size_1),Rep(TSL_1)},
+ ..., {bin_element,ANNO,Rep(Gt_k),Rep(Size_k),Rep(TSL_k)}]}</c>.
For Rep(TSL), see above.
An omitted <c>Size_i</c> is represented by <c>default</c>.
An omitted <c>TSL_i</c> is represented by <c>default</c>.</p>
</item>
<item>
<p>If Gt is a cons skeleton <c>[Gt_h | Gt_t]</c>, then Rep(Gt) =
- <c>{cons,LINE,Rep(Gt_h),Rep(Gt_t)}</c>.</p>
+ <c>{cons,ANNO,Rep(Gt_h),Rep(Gt_t)}</c>.</p>
</item>
<item>
<p>If Gt is a function call <c>A(Gt_1, ..., Gt_k)</c>,
where <c>A</c> is an atom, then Rep(Gt) =
- <c>{call,LINE,Rep(A),[Rep(Gt_1), ..., Rep(Gt_k)]}</c>.</p>
+ <c>{call,ANNO,Rep(A),[Rep(Gt_1), ..., Rep(Gt_k)]}</c>.</p>
</item>
<item>
<p>If Gt is a function call <c>A_m:A(Gt_1, ..., Gt_k)</c>,
where <c>A_m</c> is the atom <c>erlang</c> and <c>A</c> is
an atom or an operator, then Rep(Gt) =
- <c>{call,LINE,{remote,LINE,Rep(A_m),Rep(A)},[Rep(Gt_1), ...,
+ <c>{call,ANNO,{remote,ANNO,Rep(A_m),Rep(A)},[Rep(Gt_1), ...,
Rep(Gt_k)]}</c>.</p>
</item>
<item>
<p>If Gt is a map creation <c>#{A_1, ..., A_k}</c>,
where each <c>A_i</c> is an association <c>Gt_i_1 => Gt_i_2</c>,
- then Rep(Gt) = <c>{map,LINE,[Rep(A_1), ..., Rep(A_k)]}</c>.
+ then Rep(Gt) = <c>{map,ANNO,[Rep(A_1), ..., Rep(A_k)]}</c>.
For Rep(A), see above.</p>
</item>
<item>
<p>If Gt is a map update <c>Gt_0#{A_1, ..., A_k}</c>,
where each <c>A_i</c> is an association <c>Gt_i_1 => Gt_i_2</c>
or <c>Gt_i_1 := Gt_i_2</c>, then Rep(Gt) =
- <c>{map,LINE,Rep(Gt_0),[Rep(A_1), ..., Rep(A_k)]}</c>.
+ <c>{map,ANNO,Rep(Gt_0),[Rep(A_1), ..., Rep(A_k)]}</c>.
For Rep(A), see above.</p>
</item>
<item>
- <p>If Gt is nil, <c>[]</c>, then Rep(Gt) = <c>{nil,LINE}</c>.</p>
+ <p>If Gt is nil, <c>[]</c>, then Rep(Gt) = <c>{nil,ANNO}</c>.</p>
</item>
<item>
<p>If Gt is an operator guard test <c>Gt_1 Op Gt_2</c>,
where <c>Op</c> is a binary operator other than match
operator <c>=</c>, then Rep(Gt) =
- <c>{op,LINE,Op,Rep(Gt_1),Rep(Gt_2)}</c>.</p>
+ <c>{op,ANNO,Op,Rep(Gt_1),Rep(Gt_2)}</c>.</p>
</item>
<item>
<p>If Gt is an operator guard test <c>Op Gt_0</c>,
where <c>Op</c> is a unary operator, then Rep(Gt) =
- <c>{op,LINE,Op,Rep(Gt_0)}</c>.</p>
+ <c>{op,ANNO,Op,Rep(Gt_0)}</c>.</p>
</item>
<item>
<p>If Gt is a parenthesized guard test <c>( Gt_0 )</c>, then Rep(Gt) =
@@ -764,26 +769,26 @@
<p>If Gt is a record creation
<c>#Name{Field_1=Gt_1, ..., Field_k=Gt_k}</c>,
where each <c>Field_i</c> is an atom or <c>_</c>, then Rep(Gt) =
- <c>{record,LINE,Name,[{record_field,LINE,Rep(Field_1),Rep(Gt_1)},
- ..., {record_field,LINE,Rep(Field_k),Rep(Gt_k)}]}</c>.</p>
+ <c>{record,ANNO,Name,[{record_field,ANNO,Rep(Field_1),Rep(Gt_1)},
+ ..., {record_field,ANNO,Rep(Field_k),Rep(Gt_k)}]}</c>.</p>
</item>
<item>
<p>If Gt is a record field access <c>Gt_0#Name.Field</c>,
where <c>Field</c> is an atom, then Rep(Gt) =
- <c>{record_field,LINE,Rep(Gt_0),Name,Rep(Field)}</c>.</p>
+ <c>{record_field,ANNO,Rep(Gt_0),Name,Rep(Field)}</c>.</p>
</item>
<item>
<p>If Gt is a record field index <c>#Name.Field</c>,
where <c>Field</c> is an atom, then Rep(Gt) =
- <c>{record_index,LINE,Name,Rep(Field)}</c>.</p>
+ <c>{record_index,ANNO,Name,Rep(Field)}</c>.</p>
</item>
<item>
<p>If Gt is a tuple skeleton <c>{Gt_1, ..., Gt_k}</c>, then Rep(Gt) =
- <c>{tuple,LINE,[Rep(Gt_1), ..., Rep(Gt_k)]}</c>.</p>
+ <c>{tuple,ANNO,[Rep(Gt_1), ..., Rep(Gt_k)]}</c>.</p>
</item>
<item>
<p>If Gt is a variable pattern <c>V</c>, then Rep(Gt) =
- <c>{var,LINE,A}</c>, where A is an atom with
+ <c>{var,ANNO,A}</c>, where A is an atom with
a printname consisting of the same characters as <c>V</c>.</p>
</item>
</list>
@@ -798,7 +803,7 @@
<item>
<p>If T is an annotated type <c>A :: T_0</c>,
where <c>A</c> is a variable, then Rep(T) =
- <c>{ann_type,LINE,[Rep(A),Rep(T_0)]}</c>.</p>
+ <c>{ann_type,ANNO,[Rep(A),Rep(T_0)]}</c>.</p>
</item>
<item>
<p>If T is an atom, a character, or an integer literal L,
@@ -807,21 +812,21 @@
<item>
<p>If T is a bitstring type <c>&lt;&lt;_:M,_:_*N>></c>,
where <c>M</c> and <c>N</c> are singleton integer types, then Rep(T) =
- <c>{type,LINE,binary,[Rep(M),Rep(N)]}</c>.</p>
+ <c>{type,ANNO,binary,[Rep(M),Rep(N)]}</c>.</p>
</item>
<item>
<p>If T is the empty list type <c>[]</c>, then Rep(T) =
- <c>{type,Line,nil,[]}</c>, that is, the empty list type
+ <c>{type,ANNO,nil,[]}</c>, that is, the empty list type
<c>[]</c> cannot be distinguished from the predefined type
<c>nil()</c>.</p>
</item>
<item>
<p>If T is a fun type <c>fun()</c>, then Rep(T) =
- <c>{type,LINE,'fun',[]}</c>.</p>
+ <c>{type,ANNO,'fun',[]}</c>.</p>
</item>
<item>
<p>If T is a fun type <c>fun((...) -> T_0)</c>, then Rep(T) =
- <c>{type,LINE,'fun',[{type,LINE,any},Rep(T_0)]}</c>.</p>
+ <c>{type,ANNO,'fun',[{type,ANNO,any},Rep(T_0)]}</c>.</p>
</item>
<item>
<p>If T is a fun type <c>fun(Ft)</c>, where
@@ -831,16 +836,16 @@
<item>
<p>If T is an integer range type <c>L .. H</c>,
where <c>L</c> and <c>H</c> are singleton integer types, then Rep(T) =
- <c>{type,LINE,range,[Rep(L),Rep(H)]}</c>.</p>
+ <c>{type,ANNO,range,[Rep(L),Rep(H)]}</c>.</p>
</item>
<item>
<p>If T is a map type <c>map()</c>, then Rep(T) =
- <c>{type,LINE,map,any}</c>.</p>
+ <c>{type,ANNO,map,any}</c>.</p>
</item>
<item>
<p>If T is a map type <c>#{A_1, ..., A_k}</c>, where each
<c>A_i</c> is an association type, then Rep(T) =
- <c>{type,LINE,map,[Rep(A_1), ..., Rep(A_k)]}</c>.
+ <c>{type,ANNO,map,[Rep(A_1), ..., Rep(A_k)]}</c>.
For Rep(A), see below.</p>
</item>
<item>
@@ -848,13 +853,13 @@
where <c>Op</c> is a binary operator (this is an occurrence of
an expression that can be evaluated to an integer at compile
time), then Rep(T) =
- <c>{op,LINE,Op,Rep(T_1),Rep(T_2)}</c>.</p>
+ <c>{op,ANNO,Op,Rep(T_1),Rep(T_2)}</c>.</p>
</item>
<item>
<p>If T is an operator type <c>Op T_0</c>, where <c>Op</c> is a
unary operator (this is an occurrence of an expression that can
be evaluated to an integer at compile time), then Rep(T) =
- <c>{op,LINE,Op,Rep(T_0)}</c>.</p>
+ <c>{op,ANNO,Op,Rep(T_0)}</c>.</p>
</item>
<item>
<p>If T is <c>( T_0 )</c>, then Rep(T) = <c>Rep(T_0)</c>, that is,
@@ -862,40 +867,40 @@
</item>
<item>
<p>If T is a predefined (or built-in) type <c>N(T_1, ..., T_k)</c>,
- then Rep(T) = <c>{type,LINE,N,[Rep(T_1), ..., Rep(T_k)]}</c>.</p>
+ then Rep(T) = <c>{type,ANNO,N,[Rep(T_1), ..., Rep(T_k)]}</c>.</p>
</item>
<item>
<p>If T is a record type <c>#Name{F_1, ..., F_k}</c>,
where each <c>F_i</c> is a record field type, then Rep(T) =
- <c>{type,LINE,record,[Rep(Name),Rep(F_1), ..., Rep(F_k)]}</c>.
+ <c>{type,ANNO,record,[Rep(Name),Rep(F_1), ..., Rep(F_k)]}</c>.
For Rep(F), see below.</p>
</item>
<item>
<p>If T is a remote type <c>M:N(T_1, ..., T_k)</c>, then Rep(T) =
- <c>{remote_type,LINE,[Rep(M),Rep(N),[Rep(T_1), ...,
+ <c>{remote_type,ANNO,[Rep(M),Rep(N),[Rep(T_1), ...,
Rep(T_k)]]}</c>.</p>
</item>
<item>
<p>If T is a tuple type <c>tuple()</c>, then Rep(T) =
- <c>{type,LINE,tuple,any}</c>.</p>
+ <c>{type,ANNO,tuple,any}</c>.</p>
</item>
<item>
<p>If T is a tuple type <c>{T_1, ..., T_k}</c>, then Rep(T) =
- <c>{type,LINE,tuple,[Rep(T_1), ..., Rep(T_k)]}</c>.</p>
+ <c>{type,ANNO,tuple,[Rep(T_1), ..., Rep(T_k)]}</c>.</p>
</item>
<item>
<p>If T is a type union <c>T_1 | ... | T_k</c>, then Rep(T) =
- <c>{type,LINE,union,[Rep(T_1), ..., Rep(T_k)]}</c>.</p>
+ <c>{type,ANNO,union,[Rep(T_1), ..., Rep(T_k)]}</c>.</p>
</item>
<item>
<p>If T is a type variable <c>V</c>, then Rep(T) =
- <c>{var,LINE,A}</c>, where <c>A</c> is an atom with a printname
+ <c>{var,ANNO,A}</c>, where <c>A</c> is an atom with a printname
consisting of the same characters as <c>V</c>. A type variable
is any variable except underscore (<c>_</c>).</p>
</item>
<item>
<p>If T is a user-defined type <c>N(T_1, ..., T_k)</c>, then Rep(T) =
- <c>{user_type,LINE,N,[Rep(T_1), ..., Rep(T_k)]}</c>.</p>
+ <c>{user_type,ANNO,N,[Rep(T_1), ..., Rep(T_k)]}</c>.</p>
</item>
</list>
@@ -908,13 +913,13 @@
<p>If Ft is a constrained function type <c>Ft_1 when Fc</c>,
where <c>Ft_1</c> is a function type and
<c>Fc</c> is a function constraint, then Rep(T) =
- <c>{type,LINE,bounded_fun,[Rep(Ft_1),Rep(Fc)]}</c>.
+ <c>{type,ANNO,bounded_fun,[Rep(Ft_1),Rep(Fc)]}</c>.
For Rep(Fc), see below.</p>
</item>
<item>
<p>If Ft is a function type <c>(T_1, ..., T_n) -> T_0</c>,
where each <c>T_i</c> is a type, then Rep(Ft) =
- <c>{type,LINE,'fun',[{type,LINE,product,[Rep(T_1), ...,
+ <c>{type,ANNO,'fun',[{type,ANNO,product,[Rep(T_1), ...,
Rep(T_n)]},Rep(T_0)]}</c>.</p>
</item>
</list>
@@ -930,7 +935,7 @@
<item>If C is a constraint <c>V :: T</c>,
where <c>V</c> is a type variable
and <c>T</c> is a type, then Rep(C) =
- <c>{type,LINE,constraint,[{atom,LINE,is_subtype},[Rep(V),Rep(T)]]}</c>.
+ <c>{type,ANNO,constraint,[{atom,ANNO,is_subtype},[Rep(V),Rep(T)]]}</c>.
</item>
</list>
</section>
@@ -941,12 +946,12 @@
<item>
<p>If A is an association type <c>K => V</c>,
where <c>K</c> and <c>V</c> are types, then Rep(A) =
- <c>{type,LINE,map_field_assoc,[Rep(K),Rep(V)]}</c>.</p>
+ <c>{type,ANNO,map_field_assoc,[Rep(K),Rep(V)]}</c>.</p>
</item>
<item>
<p>If A is an association type <c>K := V</c>,
where <c>K</c> and <c>V</c> are types, then Rep(A) =
- <c>{type,LINE,map_field_exact,[Rep(K),Rep(V)]}</c>.</p>
+ <c>{type,ANNO,map_field_exact,[Rep(K),Rep(V)]}</c>.</p>
</item>
</list>
</section>
@@ -956,7 +961,7 @@
<list type="bulleted">
<item>If F is a record field type <c>Name :: Type</c>,
where <c>Type</c> is a type, then Rep(F) =
- <c>{type,LINE,field_type,[Rep(Name),Rep(Type)]}</c>.
+ <c>{type,ANNO,field_type,[Rep(Name),Rep(Type)]}</c>.
</item>
</list>
</section>
diff --git a/erts/doc/src/alt_disco.xml b/erts/doc/src/alt_disco.xml
index 590aa5a76e..94bd7a2efd 100644
--- a/erts/doc/src/alt_disco.xml
+++ b/erts/doc/src/alt_disco.xml
@@ -48,7 +48,7 @@
<section>
<title>Introduction</title>
<p>To implement your own node discovery module you have to write your own
- EPMD module. The <seealso marker="kernel:erl_epmd">EPMD module</seealso> is
+ EPMD module. The <seeerl marker="kernel:erl_epmd">EPMD module</seeerl> is
responsible for providing the location of another node. The distribution
modules (<c>inet_tcp_dist</c>/<c>inet_tls_dist</c>) call the EPMD module to
get the IP address and port of the other node. The EPMD module that is part
@@ -60,34 +60,39 @@
<section>
<title>Discovery module</title>
<p>The discovery module needs to implement the same API as the regular
- <seealso marker="kernel:erl_epmd">EPMD module</seealso>. However, instead of
+ <seeerl marker="kernel:erl_epmd">EPMD module</seeerl>. However, instead of
communicating with EPMD you can connect to any service to find out
connection details of other nodes. A discovery module is enabled
- by setting <seealso marker="erts:erl">-epmd_module</seealso>
+ by setting <seecom marker="erts:erl">-epmd_module</seecom>
when starting erlang. The discovery module must implement the following
callbacks:</p>
<taglist>
- <tag><seealso marker="kernel:erl_epmd#start_link/0">start_link/0</seealso></tag>
+ <tag><seemfa marker="kernel:erl_epmd#start_link/0">start_link/0</seemfa></tag>
<item>Start any processes needed by the discovery module.</item>
- <tag><seealso marker="kernel:erl_epmd#names/1">names/1</seealso></tag>
+ <tag><seemfa marker="kernel:erl_epmd#names/1">names/1</seemfa></tag>
<item>Return node names held by the registrar for the given host.</item>
- <tag><seealso marker="kernel:erl_epmd#register_node/2">register_node/2</seealso></tag>
+ <tag><seemfa marker="kernel:erl_epmd#register_node/2">register_node/2</seemfa></tag>
<item>Register the given node name with the registrar.</item>
- <tag><seealso marker="kernel:erl_epmd#port_please/3">port_please/3</seealso></tag>
+ <tag><seemfa marker="kernel:erl_epmd#port_please/3">port_please/3</seemfa></tag>
<item>Return the distribution port used by the given node.</item>
</taglist>
<p>The discovery module may implement the following callback:</p>
<taglist>
- <tag><seealso marker="kernel:erl_epmd#address_please/3">address_please/3</seealso></tag>
+ <tag><seemfa marker="kernel:erl_epmd#address_please/3">address_please/3</seemfa></tag>
<item><p>Return the address of the given node.
- If not implemented, <seealso marker="kernel:erl_epmd#address_please/3">
- <c>erl_epmd:address_please/3</c></seealso> will be used instead.</p>
+ If not implemented, <seemfa marker="kernel:erl_epmd#address_please/3">
+ <c>erl_epmd:address_please/3</c></seemfa> will be used instead.</p>
<p>This callback may also return the port of the given node. In that case
- <seealso marker="kernel:erl_epmd#port_please/3">port_please/3</seealso>
+ <seemfa marker="kernel:erl_epmd#port_please/3">port_please/3</seemfa>
may be omitted.</p></item>
+ <tag><seemfa marker="kernel:erl_epmd#listen_port_please/2">listen_port_please/2</seemfa></tag>
+ <item><p>Return the port the local node should listen to.
+ If not implemented, <seemfa marker="kernel:erl_epmd#listen_port_please/2">
+ <c>erl_epmd:listen_port_please/2</c></seemfa> will be used instead.</p>
+ </item>
</taglist>
</section>
</chapter>
diff --git a/erts/doc/src/alt_dist.xml b/erts/doc/src/alt_dist.xml
index 352bb638a2..09424cedb2 100644
--- a/erts/doc/src/alt_dist.xml
+++ b/erts/doc/src/alt_dist.xml
@@ -65,8 +65,8 @@
to do you perhaps do not need to implement a driver at all
and can then skip the driver related sections below.
The <c>gen_tcp_dist</c> example described in the
- <seealso marker="#distribution_module">Distribution
- Module</seealso> section utilize distribution controller
+ <seeguide marker="#distribution_module">Distribution
+ Module</seeguide> section utilize distribution controller
processes and can be worth having a look at if you want to
use distribution controller processes.
</p></note>
@@ -204,18 +204,19 @@
The following functions are mandatory:
</p>
<taglist>
- <tag><marker id="listen"/><c>listen(Name) -></c><br/>&nbsp;&nbsp;<c>{ok, {Listen, Address, Creation}} | {error, Error} </c></tag>
+ <tag><marker id="listen"/><c>listen(Name) -></c><br/>&nbsp;&nbsp;<c>{ok, {Listen, Address, Creation}} | {error, Error} </c><br/><c>listen(Name,Host) -></c><br/>&nbsp;&nbsp;<c>{ok, {Listen, Address, Creation}} | {error, Error} </c></tag>
<item>
<p>
- <c>listen/1</c> is called once in order to listen for incoming
+ <c>listen/2</c> is called once in order to listen for incoming
connection requests. The call is made when the distribution is brought
up. The argument <c>Name</c> is the part of the node name before
the <c>@</c> sign in the full node name. It can be either an atom or a
- string.
+ string. The argument <c>Host</c> is the part of the node name after
+ the <c>@</c> sign in the full node name. It is always a string.
</p>
<p>
The return value consists of a <c>Listen</c> handle (which is
- later passed to the <seealso marker="#accept"><c>accept/1</c></seealso>
+ later passed to the <seeguide marker="#accept"><c>accept/1</c></seeguide>
callback), <c>Address</c> which is a <c>#net_address{}</c> record
with information about the address for the node (the
<c>#net_address{}</c> record is defined in
@@ -223,14 +224,27 @@
(currently) is an integer <c>1</c>, <c>2</c>, or <c>3</c>.
</p>
<p>
- If <seealso marker="erts:epmd"><c>epmd</c></seealso> is to be used
- for node discovery, you typically want to use the (unfortunately
- undocumented) <c>erl_epmd</c> module (part of the <c>kernel</c>
+ If <seecom marker="erts:epmd"><c>epmd</c></seecom> is to be used
+ for node discovery, you typically want to use the
+ <c>erl_epmd</c> module (part of the <c>kernel</c>
application) in order to register the listen port with <c>epmd</c>
and retrieve <c>Creation</c> to use.
</p>
</item>
+ <tag><marker id="address"/><c>address() -></c><br/>&nbsp;&nbsp;<c>Address</c></tag>
+ <item>
+ <p><c>address/0</c> is called in order to get the <c>Address</c> part of the
+ <seeguide marker="#listen"><c>listen/2</c></seeguide> function without creating
+ a listen socket. All fields except <c>address</c> have to be set in the returned
+ record</p>
+ <p>Example:</p>
+ <code type="erl">address() ->
+ {ok, Host} = inet:gethostname(),
+ #net_address{ host = Host, protocol = tcp, family = inet6 }.
+ </code>
+ </item>
+
<tag><marker id="accept"/><c>accept(Listen) -></c><br/>&nbsp;&nbsp;<c>AcceptorPid</c></tag>
<item>
<p>
@@ -241,7 +255,7 @@
<p>
The <c>Listen</c> argument will be the same as the <c>Listen</c> handle
part of the return value of the
- <seealso marker="#listen"><c>listen/1</c></seealso> callback above.
+ <seeguide marker="#listen"><c>listen/1</c></seeguide> callback above.
<c>accept/1</c> is called only once when the distribution protocol is
started.
</p>
@@ -269,7 +283,7 @@
The request was accepted and <c>SupervisorPid</c> is the
process identifier of the connection supervisor process
(which is created in the
- <seealso marker="#accept_connection"><c>accept_connection/5</c></seealso>
+ <seeguide marker="#accept_connection"><c>accept_connection/5</c></seeguide>
callback).
</p>
</item>
@@ -302,7 +316,7 @@
<item>
<p>
Process identifier of the process created by the
- <seealso marker="#accept"><c>accept/1</c></seealso>
+ <seeguide marker="#accept"><c>accept/1</c></seeguide>
callback.
</p>
</item>
@@ -340,7 +354,7 @@
<p>
The created process should provide callbacks and other
information needed for the handshake in a
- <seealso marker="#hs_data_record"><c>#hs_data{}</c></seealso>
+ <seeguide marker="#hs_data_record"><c>#hs_data{}</c></seeguide>
record and call <c>dist_util:handshake_other_started(HsData)</c>
with this record.
</p>
@@ -417,7 +431,7 @@
<p>
The created process should provide callbacks and other
information needed for the handshake in a
- <seealso marker="#hs_data_record"><c>#hs_data{}</c></seealso>
+ <seeguide marker="#hs_data_record"><c>#hs_data{}</c></seeguide>
record and call <c>dist_util:handshake_we_started(HsData)</c>
with this record.
</p>
@@ -434,7 +448,7 @@
<item><p>
Called in order to close the <c>Listen</c> handle
that originally was passed from the
- <seealso marker="#listen"><c>listen/1</c></seealso> callback.
+ <seeguide marker="#listen"><c>listen/1</c></seeguide> callback.
</p></item>
<tag><marker id="select"/><c>select(NodeName) -></c><br/>&nbsp;&nbsp;<c>boolean()</c></tag>
@@ -457,7 +471,7 @@
<p>
The argument <c>Listen</c> is the handle originally passed
from the
- <seealso marker="#listen"><c>listen/1</c></seealso> callback.
+ <seeguide marker="#listen"><c>listen/1</c></seeguide> callback.
The argument <c>Opts</c> is a list of options to set on future
connections.
</p>
@@ -468,7 +482,7 @@
<p>
The argument <c>Listen</c> is the handle originally passed
from the
- <seealso marker="#listen"><c>listen/1</c></seealso> callback.
+ <seeguide marker="#listen"><c>listen/1</c></seeguide> callback.
The argument <c>Opts</c> is a list of options to read for future
connections.
</p>
@@ -498,8 +512,8 @@
<p>
Process identifier of the <c>Kernel</c> process. That is,
the process that called either
- <seealso marker="#setup"><c>setup/5</c></seealso> or
- <seealso marker="#accept_connection"><c>accept_connection/5</c></seealso>.
+ <seeguide marker="#setup"><c>setup/5</c></seeguide> or
+ <seeguide marker="#accept_connection"><c>accept_connection/5</c></seeguide>.
</p>
</item>
@@ -508,7 +522,7 @@
<p>Name of the other node. This field is only
mandatory when this node initiates the connection.
That is, when connection is set up via
- <seealso marker="#setup"><c>setup/5</c></seealso>.
+ <seeguide marker="#setup"><c>setup/5</c></seeguide>.
</p>
</item>
@@ -540,7 +554,7 @@
mandatory when the remote node initiated the
connection. That is, when the connection is set
up via
- <seealso marker="#accept_connection"><c>accept_connection/5</c></seealso>.
+ <seeguide marker="#accept_connection"><c>accept_connection/5</c></seeguide>.
</p>
</item>
@@ -700,7 +714,7 @@
<item>
<p>
The request <c>Type</c> as passed to
- <seealso marker="#setup"><c>setup/5</c></seealso>.
+ <seeguide marker="#setup"><c>setup/5</c></seeguide>.
This is only mandatory when the connection has
been initiated by this node. That is, the connection
is set up via <c>setup/5</c>.
@@ -750,10 +764,10 @@
calling the following BIFs:
</p>
<list>
- <item><p><seealso marker="erts:erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data/1</c></seealso></p></item>
- <item><p><seealso marker="erts:erlang#dist_ctrl_get_data_notification/1"><c>erlang:dist_ctrl_get_data_notification/1</c></seealso></p></item>
- <item><p><seealso marker="erts:erlang#dist_ctrl_input_handler/2"><c>erlang:dist_ctrl_input_handler/2</c></seealso></p></item>
- <item><p><seealso marker="erts:erlang#dist_ctrl_put_data/2"><c>erlang:dist_ctrl_put_data/2</c></seealso></p></item>
+ <item><p><seemfa marker="erts:erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data/1</c></seemfa></p></item>
+ <item><p><seemfa marker="erts:erlang#dist_ctrl_get_data_notification/1"><c>erlang:dist_ctrl_get_data_notification/1</c></seemfa></p></item>
+ <item><p><seemfa marker="erts:erlang#dist_ctrl_input_handler/2"><c>erlang:dist_ctrl_input_handler/2</c></seemfa></p></item>
+ <item><p><seemfa marker="erts:erlang#dist_ctrl_put_data/2"><c>erlang:dist_ctrl_put_data/2</c></seemfa></p></item>
</list>
<p>
This function is called when the handshake has
@@ -767,7 +781,7 @@
<tag><marker id="hs_data_add_flags"/><c>add_flags</c></tag>
<item>
<p>
- <seealso marker="erl_dist_protocol#dflags">Distribution flags</seealso>
+ <seeguide marker="erl_dist_protocol#dflags">Distribution flags</seeguide>
to add to the connection. Currently all (non obsolete) flags will
automatically be enabled.
</p>
@@ -779,25 +793,27 @@
<tag><marker id="hs_data_reject_flags"/><c>reject_flags</c></tag>
<item>
<p>
- <seealso marker="erl_dist_protocol#dflags">Distribution flags</seealso>
+ <seeguide marker="erl_dist_protocol#dflags">Distribution flags</seeguide>
to reject. Currently the following distribution flags can be rejected:
</p>
<taglist>
<tag><c>DFLAG_DIST_HDR_ATOM_CACHE</c></tag>
<item>Do not use atom cache over this connection.</item>
+ <tag><c>DFLAG_FRAGMENTS</c></tag>
+ <item>Split large distribution messages into multiple fragments.</item>
</taglist>
- <p>Use function <c>dist_util:strict_order_flags/0</c> to get all flags
- for features that require strict order delivery.</p>
<p>
This flag field is optional.
</p>
+ <p>See also <seeguide marker="#distribution_data_delivery">
+ Distribution Data Delivery</seeguide></p>
</item>
<tag><marker id="hs_data_require_flags"/><c>require_flags</c></tag>
<item>
<p>
- Require these <seealso marker="erl_dist_protocol#dflags">distribution
- flags</seealso> to be used. The connection will be aborted during the
+ Require these <seeguide marker="erl_dist_protocol#dflags">distribution
+ flags</seeguide> to be used. The connection will be aborted during the
handshake if the other end does not use them.
</p>
<p>
@@ -822,10 +838,10 @@
The data delivery order can be relaxed by disabling
features that require strict ordering. This is done by
passing the
- <seealso marker="erl_dist_protocol#dflags">distribution flags</seealso>
+ <seeguide marker="erl_dist_protocol#dflags">distribution flags</seeguide>
returned by <c>dist_util:strict_order_flags/0</c> in the
- <seealso marker="alt_dist#hs_data_reject_flags"><c>reject_flags</c></seealso>
- field of the <seealso marker="#hs_data_record"><c>#hs_data{}</c></seealso>
+ <seeguide marker="alt_dist#hs_data_reject_flags"><c>reject_flags</c></seeguide>
+ field of the <seeguide marker="#hs_data_record"><c>#hs_data{}</c></seeguide>
record used when setting up the connection. When relaxed
ordering is used, only the order of signals with the same
sender/receiver pair has to be preserved.
@@ -862,8 +878,8 @@
been made to the documentation of the driver presented here,
but more can be done and is planned for the future.
The reader is encouraged to read the
- <seealso marker="erl_driver"><c>erl_driver</c></seealso> and
- <seealso marker="driver_entry"><c>driver_entry</c></seealso>
+ <seecref marker="erl_driver"><c>erl_driver</c></seecref> and
+ <seecref marker="driver_entry"><c>driver_entry</c></seecref>
documentation also.</p>
</note>
@@ -885,7 +901,7 @@
<p>The driver data types and the functions available to the driver
writer are defined in header file <c><![CDATA[erl_driver.h]]></c>
seated in Erlang's include directory. See the
- <seealso marker="erts:erl_driver">erl_driver</seealso> documentation
+ <seecref marker="erts:erl_driver">erl_driver</seecref> documentation
for details of which functions are available.</p>
<p>When writing a driver to make a communications protocol available
@@ -971,8 +987,8 @@
</item>
<item>
<p>When an Erlang process calls
- <seealso marker="erlang#port_control/3">
- <c>erlang:port_control/3</c></seealso>,
+ <seemfa marker="erlang#port_control/3">
+ <c>erlang:port_control/3</c></seemfa>,
which is a synchronous interface to drivers. The control interface
is used to set driver options, change states of ports, and so on.
This interface is used a lot in the example.</p>
@@ -1102,15 +1118,15 @@
<p>As from ERTS 5.5.3 the driver interface was extended with
version control and the possibility to pass capability information.
Capability flags are present on line 48. As from ERTS 5.7.4 flag
- <seealso marker="driver_entry#driver_flags">
- <c>ERL_DRV_FLAG_SOFT_BUSY</c></seealso> is required for drivers that
+ <seecref marker="driver_entry#driver_flags">
+ <c>ERL_DRV_FLAG_SOFT_BUSY</c></seecref> is required for drivers that
are to be used by the distribution. The soft busy flag implies that the
driver can handle calls to the <c>output</c> and <c>outputv</c>
callbacks although it has marked itself as busy. This has always been a
requirement on drivers used by the distribution, but no capability
information has been available about this previously. For more
- information. see <seealso marker="erl_driver#set_busy_port">
- <c>erl_driver:set_busy_port()</c></seealso>).</p>
+ information. see <seecref marker="erl_driver#set_busy_port">
+ <c>erl_driver:set_busy_port()</c></seecref>).</p>
<p>This driver was written before the runtime system had SMP support.
The driver will still function in the runtime system with SMP support,
@@ -1119,8 +1135,8 @@
rewriting the code so that each instance of the driver safely can
execute in parallel. When instances safely can execute in parallel, it
is safe to enable instance-specific locking on the driver. This is done
- by passing <seealso marker="driver_entry#driver_flags">
- <c>ERL_DRV_FLAG_USE_PORT_LOCKING</c></seealso> as a driver flag. This
+ by passing <seecref marker="driver_entry#driver_flags">
+ <c>ERL_DRV_FLAG_USE_PORT_LOCKING</c></seecref> as a driver flag. This
is left as an exercise for the reader.</p>
<p>Thus, the defined callbacks are as follows:</p>
@@ -1158,8 +1174,8 @@
</item>
<tag><c>uds_control</c></tag>
<item>
- <p>The <seealso marker="erlang#port_control/3">
- <c>erlang:port_control/3</c></seealso> callback, which is
+ <p>The <seemfa marker="erlang#port_control/3">
+ <c>erlang:port_control/3</c></seemfa> callback, which is
used a lot in this implementation.</p>
</item>
</taglist>
@@ -1354,8 +1370,8 @@
if this value has changed. (The Erlang
<c>net_kernel</c> <c>ticker</c> uses this value by calling the
driver to fetch it, which is done through the
- <seealso marker="erlang#port_control/3">
- <c>erlang:port_control/3</c></seealso> routine.)</p>
+ <seemfa marker="erlang#port_control/3">
+ <c>erlang:port_control/3</c></seemfa> routine.)</p>
</item>
<tag><c>received</c></tag>
<item>
@@ -1822,8 +1838,8 @@
<p>The driver implements a control interface, which is a
synchronous interface called when Erlang calls
- <seealso marker="erlang#port_control/3">
- <c>erlang:port_control/3</c></seealso>. Only this interface
+ <seemfa marker="erlang#port_control/3">
+ <c>erlang:port_control/3</c></seemfa>. Only this interface
can control the driver when it is in <c>data</c> mode. It can
be called with the following opcodes:</p>
@@ -1864,11 +1880,11 @@
ticker to send dummy data when no other traffic is present.</p>
<p><em>Note:</em> It is important that the interface for
sending ticks is not blocking. This implementation uses
- <seealso marker="erlang#port_control/3">
- <c>erlang:port_control/3</c></seealso>, which does not block the
+ <seemfa marker="erlang#port_control/3">
+ <c>erlang:port_control/3</c></seemfa>, which does not block the
caller. If <c>erlang:port_command</c> is used, use
- <seealso marker="erlang#port_command/3">
- <c>erlang:port_command/3</c></seealso> and pass <c>[force]</c> as
+ <seemfa marker="erlang#port_command/3">
+ <c>erlang:port_command/3</c></seemfa> and pass <c>[force]</c> as
option list; otherwise the caller can be blocked indefinitely
on a busy port and prevent the system from taking down a
connection that is not functioning.</p>
diff --git a/erts/doc/src/atomics.xml b/erts/doc/src/atomics.xml
index 455973f011..9439643a14 100644
--- a/erts/doc/src/atomics.xml
+++ b/erts/doc/src/atomics.xml
@@ -63,7 +63,7 @@
<datatype>
<name name="atomics_ref"/>
<desc><p>Identifies an atomic array returned from
- <seealso marker="#new/2"><c>new/2</c></seealso>.</p>
+ <seemfa marker="#new/2"><c>new/2</c></seemfa>.</p>
</desc>
</datatype>
</datatypes>
@@ -73,7 +73,9 @@
<name name="new" arity="2" since="OTP 21.2"/>
<fsummary>Create atomic array</fsummary>
<desc>
- <p>Create a new atomic array of <c><anno>Arity</anno></c> atomics.</p>
+ <p>Create a new array of <c><anno>Arity</anno></c>
+ number of atomics. All atomics in the array are initially set
+ to zero.</p>
<p>Argument <c><anno>Opts</anno></c> is a list of the following possible
options:</p>
<taglist>
diff --git a/erts/doc/src/communication.xml b/erts/doc/src/communication.xml
index be6f88689e..95efd9a3b6 100644
--- a/erts/doc/src/communication.xml
+++ b/erts/doc/src/communication.xml
@@ -64,8 +64,8 @@
a synchronous communication operation consists of two asynchronous
signals; one request signal and one reply signal. An example of
such a synchronous communication is a call to
- <seealso marker="erlang#process_info/2">
- <c>erlang:process_info/2</c></seealso>
+ <seemfa marker="erlang#process_info/2">
+ <c>erlang:process_info/2</c></seemfa>
when the first argument is not <c>self()</c>. The caller sends
an asynchronous signal requesting information, and then
waits for the reply signal containing the requested information. When
diff --git a/erts/doc/src/counters.xml b/erts/doc/src/counters.xml
index 36816bd68d..fde4d75642 100644
--- a/erts/doc/src/counters.xml
+++ b/erts/doc/src/counters.xml
@@ -38,8 +38,7 @@
<item>
<p>Counters wrap around at overflow and underflow operations.</p>
</item>
- <item><p>Counters are initialized to zero and can then only be written to
- by adding or subtracting.</p>
+ <item><p>Counters are initialized to zero.</p>
</item>
<item>
<p>Write operations guarantee atomicity. No intermediate results can be
@@ -51,7 +50,7 @@
performance with nice consistent semantics while
<c>write_concurrency</c> counters offers even better concurrent
write performance at the expense of some potential read
- inconsistencies. See <seealso marker="#new/2"><c>new/2</c></seealso>.</p>
+ inconsistencies. See <seemfa marker="#new/2"><c>new/2</c></seemfa>.</p>
</item>
<item>
<p>Indexes into counter arrays are one-based. A counter array of
@@ -64,7 +63,7 @@
<datatype>
<name name="counters_ref"/>
<desc><p>Identifies a counter array returned from
- <seealso marker="#new/2"><c>new/2</c></seealso>.</p>
+ <seemfa marker="#new/2"><c>new/2</c></seemfa>.</p>
</desc>
</datatype>
</datatypes>
@@ -74,19 +73,21 @@
<name name="new" arity="2" since="OTP 21.2"/>
<fsummary>Create counter array</fsummary>
<desc>
- <p>Create a new counter array of <c><anno>Size</anno></c> counters.</p>
+ <p>Create a new counter array of <c><anno>Size</anno></c>
+ counters. All counters in the array are initially set to zero.</p>
<p>Argument <c><anno>Opts</anno></c> is a list of the following possible
options:</p>
<taglist>
<tag><c>atomics</c> (Default)</tag>
<item><p>Counters will be sequentially consistent. If write
- operation A is done sequentially before write operation B, then a concurrent reader
- may see none of them, only A, or both A and B. It cannot see only B.</p>
+ operation A is done sequentially before write operation B,
+ then a concurrent reader may see the result of none of them,
+ only A, or both A and B. It cannot see the result of only B.</p>
</item>
<tag><c>write_concurrency</c></tag>
<item><p>This is an optimization to achieve very efficient concurrent
- <seealso marker="#add/3"><c>add</c></seealso> and <seealso
- marker="#sub/3"><c>sub</c></seealso> operations at the expense of potential read
+ <seemfa marker="#add/3"><c>add</c></seemfa> and <seemfa
+ marker="#sub/3"><c>sub</c></seemfa> operations at the expense of potential read
inconsistency and memory consumption per counter.</p>
<p>Read operations may see sequentially inconsistent results with
regard to concurrent write operations. Even if write operation A is done
@@ -95,10 +96,10 @@
guaranteed to see all writes done sequentially before the read. No writes
are ever lost, but will eventually all be seen.</p>
<p>The typical use case for <c>write_concurrency</c> is when
- concurrent calls to <seealso marker="#add/3"><c>add</c></seealso> and
- <seealso marker="#sub/3"><c>sub</c></seealso> toward the same counters
- are very frequent, while calls to <seealso marker="#get/2"><c>get</c>
- </seealso> and <seealso marker="#put/3"><c>put</c></seealso> are much
+ concurrent calls to <seemfa marker="#add/3"><c>add</c></seemfa> and
+ <seemfa marker="#sub/3"><c>sub</c></seemfa> toward the same counters
+ are very frequent, while calls to <seemfa marker="#get/2"><c>get</c>
+ </seemfa> and <seemfa marker="#put/3"><c>put</c></seemfa> are much
less frequent. The lack of absolute read consistency must also be
acceptable.</p>
</item>
@@ -143,11 +144,11 @@
<note>
<p>Despite its name, the <c>write_concurrency</c> optimization does not
improve <c>put</c>. A call to <c>put</c> is a relatively heavy
- operation compared to the very lightweight and scalable <seealso
- marker="#add/3"><c>add</c></seealso> and <seealso marker="#sub/3">
- <c>sub</c></seealso>. The cost for a <c>put</c> with
- <c>write_concurrency</c> is like a <seealso marker="#get/2"><c>get</c>
- </seealso> plus a <c>put</c> without <c>write_concurrency</c>.</p>
+ operation compared to the very lightweight and scalable <seemfa
+ marker="#add/3"><c>add</c></seemfa> and <seemfa marker="#sub/3">
+ <c>sub</c></seemfa>. The cost for a <c>put</c> with
+ <c>write_concurrency</c> is like a <seemfa marker="#get/2"><c>get</c>
+ </seemfa> plus a <c>put</c> without <c>write_concurrency</c>.</p>
</note>
</desc>
</func>
diff --git a/erts/doc/src/crash_dump.xml b/erts/doc/src/crash_dump.xml
index 1fe27afbfa..cf45e69642 100644
--- a/erts/doc/src/crash_dump.xml
+++ b/erts/doc/src/crash_dump.xml
@@ -38,8 +38,8 @@
<note>
<p>The Erlang crash dump had a major facelift in Erlang/OTP R9C. The
information in this section is therefore not directly applicable for
- older dumps. However, if you use <seealso marker="observer:crashdump_viewer">
- <c>crashdump_viewer(3)</c></seealso> on older dumps,
+ older dumps. However, if you use <seeerl marker="observer:crashdump_viewer">
+ <c>crashdump_viewer(3)</c></seeerl> on older dumps,
the crash dumps are translated into a format similar to this.</p>
</note>
@@ -115,7 +115,7 @@ Slogan: &lt;reason&gt;</pre>
&lt;T&gt; is most often <c><![CDATA[heap]]></c>,
<c><![CDATA[old_heap]]></c>, <c><![CDATA[heap_frag]]></c>, or
<c><![CDATA[binary]]></c>. For more information on allocators, see
- <seealso marker="erts_alloc"><c>erts_alloc(3)</c></seealso>.</p>
+ <seecref marker="erts_alloc"><c>erts_alloc(3)</c></seecref>.</p>
</item>
<tag><em>&lt;A&gt;: Cannot reallocate &lt;N&gt; bytes of memory (of
type "&lt;T&gt;")</em></tag>
@@ -253,7 +253,7 @@ Slogan: &lt;reason&gt;</pre>
followed by the <em>State</em>, <em>Internal State</em>,
<em>Program Counter</em>, and <em>CP</em> of that same process.
The entries are described in section
- <seealso marker="#processes">Process Information</seealso>.</p>
+ <seeguide marker="#processes">Process Information</seeguide>.</p>
<p>Notice that this is a snapshot of what the entries are exactly when
the crash dump is starting to be generated. Therefore they are most
likely different (and more telling) than the entries for the same
@@ -264,8 +264,8 @@ Slogan: &lt;reason&gt;</pre>
<tag><em>Current Process Limited Stack Trace</em></tag>
<item>
<p>This entry is shown only if there is a current process. It is
- similar to <seealso marker="#proc_data">
- <em>=proc_stack</em></seealso>, except that only the function frames
+ similar to <seeguide marker="#proc_data">
+ <em>=proc_stack</em></seeguide>, except that only the function frames
are shown (that is, the stack variables are omitted).
Also, only the top and bottom part of the stack are shown. If the
stack is small (&lt; 512 slots), the entire stack is shown. Otherwise
@@ -290,8 +290,8 @@ Slogan: &lt;reason&gt;</pre>
<title>Memory Information</title>
<p>Under the tag <em>=memory</em> is shown information similar
to what can be obtained on a living node with
- <seealso marker="erts:erlang#memory/0">
- <c>erlang:memory()</c></seealso>.</p>
+ <seemfa marker="erts:erlang#memory/0">
+ <c>erlang:memory()</c></seemfa>.</p>
</section>
<section>
@@ -307,8 +307,8 @@ Slogan: &lt;reason&gt;</pre>
<title>Allocated Areas</title>
<p>Under the tag <em>=allocated_areas</em> is shown information
similar to what can be obtained on a living node with
- <seealso marker="erts:erlang#system_info_allocated_areas">
- <c>erlang:system_info(allocated_areas)</c></seealso>.</p>
+ <seeerl marker="erts:erlang#system_info_allocated_areas">
+ <c>erlang:system_info(allocated_areas)</c></seeerl>.</p>
</section>
<section>
@@ -317,10 +317,10 @@ Slogan: &lt;reason&gt;</pre>
<p>Under the tag <em>=allocator:&lt;A&gt;</em> is shown
various information about allocator &lt;A&gt;. The information
is similar to what can be obtained on a living node with
- <seealso marker="erts:erlang#system_info_allocator_tuple">
- <c>erlang:system_info({allocator, &lt;A&gt;})</c></seealso>.
+ <seeerl marker="erts:erlang#system_info_allocator_tuple">
+ <c>erlang:system_info({allocator, &lt;A&gt;})</c></seeerl>.
For more information, see also
- <seealso marker="erts_alloc"><c>erts_alloc(3)</c></seealso>.</p>
+ <seecref marker="erts_alloc"><c>erts_alloc(3)</c></seecref>.</p>
</section>
<section>
@@ -439,8 +439,8 @@ Slogan: &lt;reason&gt;</pre>
<item>
<p>The total memory used by this process, in bytes.
This includes call stack, heap, and internal structures.
- Same as <seealso marker="erlang#process_info-2">
- <c>erlang:process_info(Pid,memory)</c></seealso>.</p>
+ Same as <seemfa marker="erlang#process_info/2">
+ <c>erlang:process_info(Pid,memory)</c></seemfa>.</p>
</item>
<tag><em>Program counter</em></tag>
<item>
@@ -468,7 +468,7 @@ Slogan: &lt;reason&gt;</pre>
this process.</p>
</item>
</taglist>
- <p>See also section <seealso marker="#proc_data">Process Data</seealso>.</p>
+ <p>See also section <seeguide marker="#proc_data">Process Data</seeguide>.</p>
</section>
<section>
@@ -522,8 +522,8 @@ Slogan: &lt;reason&gt;</pre>
<tag><em>Fixed</em></tag>
<item>
<p>If the table is fixed using
- <seealso marker="stdlib:ets#safe_fixtable/2">
- <c>ets:safe_fixtable/2</c></seealso> or some internal mechanism.</p>
+ <seemfa marker="stdlib:ets#safe_fixtable/2">
+ <c>ets:safe_fixtable/2</c></seemfa> or some internal mechanism.</p>
</item>
<tag><em>Objects</em></tag>
<item>
diff --git a/erts/doc/src/driver.xml b/erts/doc/src/driver.xml
index 2c8fa2c547..897fb34ceb 100644
--- a/erts/doc/src/driver.xml
+++ b/erts/doc/src/driver.xml
@@ -34,8 +34,8 @@
valid, as it explains important concepts, but this was
written for an older driver interface so the examples do not
work anymore. The reader is encouraged to read the
- <seealso marker="erl_driver"><c>erl_driver</c></seealso> and
- <seealso marker="driver_entry"><c>driver_entry</c></seealso>
+ <seecref marker="erl_driver"><c>erl_driver</c></seecref> and
+ <seecref marker="driver_entry"><c>driver_entry</c></seecref>
documentation also.</p>
</note>
diff --git a/erts/doc/src/driver_entry.xml b/erts/doc/src/driver_entry.xml
index fd7d6223f6..cb6548d3f0 100644
--- a/erts/doc/src/driver_entry.xml
+++ b/erts/doc/src/driver_entry.xml
@@ -56,7 +56,7 @@
</item>
<item>
<p>A driver callback doing
- <seealso marker="erl_driver#lengthy_work">lengthy work</seealso>
+ <seecref marker="erl_driver#lengthy_work">lengthy work</seecref>
before returning degrades responsiveness of the VM, and can cause
miscellaneous strange behaviors. Such strange behaviors
include, but are not limited to, extreme memory usage, and bad
@@ -69,19 +69,19 @@
<p>As from ERTS 5.9 (Erlang/OTP R15B) the driver interface
has been changed with larger types for the callbacks
- <seealso marker="#output"><c>output</c></seealso>,
- <seealso marker="#control"><c>control</c></seealso>, and
- <seealso marker="#call"><c>call</c></seealso>.
- See driver <seealso marker="erl_driver#version_management">
- version management</seealso> in
- <seealso marker="erl_driver"><c>erl_driver</c></seealso>.</p>
+ <seecref marker="#output"><c>output</c></seecref>,
+ <seecref marker="#control"><c>control</c></seecref>, and
+ <seecref marker="#call"><c>call</c></seecref>.
+ See driver <seecref marker="erl_driver#version_management">
+ version management</seecref> in
+ <seecref marker="erl_driver"><c>erl_driver</c></seecref>.</p>
<note>
<p>Old drivers (compiled with an <c>erl_driver.h</c> from an
ERTS version earlier than 5.9) must be updated and have
to use the extended interface (with
- <seealso marker="erl_driver#version_management">version management
- </seealso>).</p>
+ <seecref marker="erl_driver#version_management">version management
+ </seecref>).</p>
</note>
<p>The <c>driver_entry</c> structure is a C struct that all Erlang
@@ -90,7 +90,7 @@
the driver.</p>
<p><marker id="emulator"></marker>
- The <seealso marker="erl_driver"><c>erl_driver</c></seealso> driver
+ The <seecref marker="erl_driver"><c>erl_driver</c></seecref> driver
API functions need a port handle
that identifies the driver instance (and the port in the
emulator). This is only passed to the <c>start</c> function, but
@@ -211,8 +211,8 @@ typedef struct erl_drv_entry {
<tag><marker id="init"/><c>int (*init)(void)</c></tag>
<item>
<p>Called directly after the driver has been loaded by
- <seealso marker="kernel:erl_ddll#load_driver/2">
- <c>erl_ddll:load_driver/2</c></seealso> (actually when the driver is
+ <seemfa marker="kernel:erl_ddll#load_driver/2">
+ <c>erl_ddll:load_driver/2</c></seemfa> (actually when the driver is
added to the driver list). The driver is to return <c>0</c>, or, if
the driver cannot initialize, <c>-1</c>.</p>
</item>
@@ -220,8 +220,8 @@ typedef struct erl_drv_entry {
<c>ErlDrvData (*start)(ErlDrvPort port, char* command)</c></tag>
<item>
<p>Called when the driver is instantiated, when
- <seealso marker="erlang#open_port/2">
- <c>erlang:open_port/2</c></seealso> is called.
+ <seemfa marker="erlang#open_port/2">
+ <c>erlang:open_port/2</c></seemfa> is called.
The driver is to return a number &gt;= 0 or a pointer, or, if the
driver cannot be started, one of three error codes:</p>
<taglist>
@@ -237,8 +237,8 @@ typedef struct erl_drv_entry {
<tag><marker id="stop"/><c>void (*stop)(ErlDrvData drv_data)</c></tag>
<item>
<p>Called when the port is closed, with
- <seealso marker="erlang#port_close/1">
- <c>erlang:port_close/1</c></seealso> or <c>Port ! {self(), close}</c>.
+ <seemfa marker="erlang#port_close/1">
+ <c>erlang:port_close/1</c></seemfa> or <c>Port ! {self(), close}</c>.
Notice that terminating the port owner process also closes the
port. If <c>drv_data</c> is a pointer to memory allocated in
<c>start</c>, then <c>stop</c> is the place to deallocate that
@@ -253,10 +253,10 @@ typedef struct erl_drv_entry {
the port with <c>Port ! {self(), {command, Data}}</c> or with
<c>erlang:port_command/2</c>. Depending on how the port was
opened, it is to be either a list of integers <c>0...255</c> or a
- binary. See <seealso marker="erlang#open_port/2">
- <c>erlang:open_port/2</c></seealso> and
- <seealso marker="erlang#port_command/2">
- <c>erlang:port_command/2</c></seealso>.</p>
+ binary. See <seemfa marker="erlang#open_port/2">
+ <c>erlang:open_port/2</c></seemfa> and
+ <seemfa marker="erlang#port_command/2">
+ <c>erlang:port_command/2</c></seemfa>.</p>
</item>
<tag><marker id="ready_input"/>
<c>void (*ready_input)(ErlDrvData drv_data, ErlDrvEvent event)</c>
@@ -288,8 +288,8 @@ typedef struct erl_drv_entry {
<tag><marker id="driver_name"/><c>char *driver_name</c></tag>
<item>
<p>The driver name. It must correspond to the atom used in
- <seealso marker="erlang#open_port/2">
- <c>erlang:open_port/2</c></seealso>, and the name of the driver
+ <seemfa marker="erlang#open_port/2">
+ <c>erlang:open_port/2</c></seemfa>, and the name of the driver
library file (without the extension).</p>
</item>
<tag><marker id="finish"/><c>void (*finish)(void)</c></tag>
@@ -297,8 +297,8 @@ typedef struct erl_drv_entry {
<p>Called by the <c>erl_ddll</c> driver when the
driver is unloaded. (It is only called in dynamic drivers.)</p>
<p>The driver is only unloaded as a result of calling
- <seealso marker="kernel:erl_ddll#unload_driver/1">
- <c>erl_ddll:unload_driver/1</c></seealso>,
+ <seemfa marker="kernel:erl_ddll#unload_driver/1">
+ <c>erl_ddll:unload_driver/1</c></seemfa>,
or when the emulator halts.</p>
</item>
<tag><c>void *handle</c></tag>
@@ -312,8 +312,8 @@ typedef struct erl_drv_entry {
char *buf, ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen)</c></tag>
<item>
<p>A special routine invoked with
- <seealso marker="erlang#port_control/3">
- <c>erlang:port_control/3</c></seealso>.
+ <seemfa marker="erlang#port_control/3">
+ <c>erlang:port_control/3</c></seemfa>.
It works a little like an "ioctl" for
Erlang drivers. The data specified to <c>port_control/3</c>
arrives in <c>buf</c> and <c>len</c>. The driver can send
@@ -328,28 +328,28 @@ typedef struct erl_drv_entry {
<c>*rbuf</c> points to a default buffer of <c>rlen</c> bytes, which
can be used to return data. Data is returned differently depending on
the port control flags (those that are set with
- <seealso marker="erl_driver#set_port_control_flags">
- <c>erl_driver:set_port_control_flags</c></seealso>).</p>
+ <seecref marker="erl_driver#set_port_control_flags">
+ <c>erl_driver:set_port_control_flags</c></seecref>).</p>
<p>If the flag is set to <c>PORT_CONTROL_FLAG_BINARY</c>,
a binary is returned. Small binaries can be returned by writing
the raw data into the default buffer. A binary can also be
returned by setting <c>*rbuf</c> to point to a binary allocated with
- <seealso marker="erl_driver#driver_alloc_binary">
- <c>erl_driver:driver_alloc_binary</c></seealso>.
+ <seecref marker="erl_driver#driver_alloc_binary">
+ <c>erl_driver:driver_alloc_binary</c></seecref>.
This binary is freed automatically after <c>control</c> has returned.
The driver can retain the binary for <em>read only</em> access with
- <seealso marker="erl_driver#driver_binary_inc_refc">
- <c>erl_driver:driver_binary_inc_refc</c></seealso> to be freed later
- with <seealso marker="erl_driver#driver_free_binary">
- <c>erl_driver:driver_free_binary</c></seealso>.
+ <seecref marker="erl_driver#driver_binary_inc_refc">
+ <c>erl_driver:driver_binary_inc_refc</c></seecref> to be freed later
+ with <seecref marker="erl_driver#driver_free_binary">
+ <c>erl_driver:driver_free_binary</c></seecref>.
It is never allowed to change the binary after <c>control</c> has
returned. If <c>*rbuf</c> is set to <c>NULL</c>, an empty list is
returned.</p>
<p>If the flag is set to <c>0</c>, data is returned as a
list of integers. Either use the default buffer or set
<c>*rbuf</c> to point to a larger buffer allocated with
- <seealso marker="erl_driver#driver_alloc">
- <c>erl_driver:driver_alloc</c></seealso>. The
+ <seecref marker="erl_driver#driver_alloc">
+ <c>erl_driver:driver_alloc</c></seecref>. The
buffer is freed automatically after <c>control</c> has returned.</p>
<p>Using binaries is faster if more than a few bytes are returned.</p>
<p>The return value is the number of bytes returned in <c>*rbuf</c>.</p>
@@ -359,8 +359,8 @@ typedef struct erl_drv_entry {
<item>
<p>Called any time after the driver's timer reaches <c>0</c>.
The timer is activated with
- <seealso marker="erl_driver#driver_set_timer">
- <c>erl_driver:driver_set_timer</c></seealso>. No priorities or
+ <seecref marker="erl_driver#driver_set_timer">
+ <c>erl_driver:driver_set_timer</c></seecref>. No priorities or
ordering exist among drivers, so if several drivers time out at
the same time, anyone of them is called first.</p>
</item>
@@ -372,14 +372,14 @@ typedef struct erl_drv_entry {
instead. This function is faster than <c>output</c>, as
it takes an <c>ErlIOVec</c> directly, which requires no
copying of the data. The port is to be in binary mode, see
- <seealso marker="erlang#open_port/2">
- <c>erlang:open_port/2</c></seealso>.</p>
+ <seemfa marker="erlang#open_port/2">
+ <c>erlang:open_port/2</c></seemfa>.</p>
<p><c>ErlIOVec</c> contains both a <c>SysIOVec</c>,
suitable for <c>writev</c>, and one or more binaries. If
these binaries are to be retained when the driver returns
from <c>outputv</c>, they can be queued (using, for example,
- <seealso marker="erl_driver#driver_enq_bin">
- <c>erl_driver:driver_enq_bin</c></seealso>)
+ <seecref marker="erl_driver#driver_enq_bin">
+ <c>erl_driver:driver_enq_bin</c></seecref>)
or, if they are kept in a static or global
variable, the reference counter can be incremented.</p>
</item>
@@ -389,8 +389,8 @@ typedef struct erl_drv_entry {
<item>
<p>Called after an asynchronous call has completed.
The asynchronous call is started with
- <seealso marker="erl_driver#driver_async">
- <c>erl_driver:driver_async</c></seealso>.
+ <seecref marker="erl_driver#driver_async">
+ <c>erl_driver:driver_async</c></seecref>.
This function is called from the Erlang emulator thread, as
opposed to the asynchronous function, which is called in
some thread (if multi-threading is enabled).</p>
@@ -405,8 +405,8 @@ typedef struct erl_drv_entry {
unsigned int command, char *buf, ErlDrvSizeT len, char **rbuf,
ErlDrvSizeT rlen, unsigned int *flags)</c></tag>
<item>
- <p>Called from <seealso marker="erlang#port_call/3">
- <c>erlang:port_call/3</c></seealso>.
+ <p>Called from <seemfa marker="erlang#port_call/3">
+ <c>erlang:port_call/3</c></seemfa>.
It works a lot like the <c>control</c> callback, but uses the
external term format for input and output.</p>
<p><c>command</c> is an integer, obtained from the call from
@@ -420,8 +420,8 @@ typedef struct erl_drv_entry {
term and returned by <c>erlang:port_call/3</c> to the
caller. If more space than <c>rlen</c> bytes is needed to
return data, <c>*rbuf</c> can be set to memory allocated with
- <seealso marker="erl_driver#driver_alloc">
- <c>erl_driver:driver_alloc</c></seealso>.
+ <seecref marker="erl_driver#driver_alloc">
+ <c>erl_driver:driver_alloc</c></seecref>.
This memory is freed automatically after <c>call</c> has returned.</p>
<p>The return value is the number of bytes returned in
<c>*rbuf</c>. If <c>ERL_DRV_ERROR_GENERAL</c> is returned
@@ -467,17 +467,17 @@ typedef struct erl_drv_entry {
all ports executing this driver instead of driver-level
locking when the driver is run in a runtime
system with SMP support. For more information, see
- <seealso marker="erl_driver#smp_support">
- <c>erl_driver</c></seealso>.</p>
+ <seecref marker="erl_driver#smp_support">
+ <c>erl_driver</c></seecref>.</p>
</item>
<tag><c>ERL_DRV_FLAG_SOFT_BUSY</c></tag>
<item>
<p>Marks that driver instances can handle being called
- in the <seealso marker="#output"><c>output</c></seealso> and/or
- <seealso marker="#outputv"><c>outputv</c></seealso> callbacks
+ in the <seecref marker="#output"><c>output</c></seecref> and/or
+ <seecref marker="#outputv"><c>outputv</c></seecref> callbacks
although a driver instance has marked itself as busy (see
- <seealso marker="erl_driver#set_busy_port">
- <c>erl_driver:set_busy_port</c></seealso>).
+ <seecref marker="erl_driver#set_busy_port">
+ <c>erl_driver:set_busy_port</c></seecref>).
As from ERTS 5.7.4 this flag is required for drivers used
by the Erlang distribution (the behavior has always been
required by drivers used by the distribution).</p>
@@ -486,15 +486,15 @@ typedef struct erl_drv_entry {
<item>
<p>Disables busy port message queue functionality. For
more information, see
- <seealso marker="erl_driver#erl_drv_busy_msgq_limits">
- <c>erl_driver:erl_drv_busy_msgq_limits</c></seealso>.</p>
+ <seecref marker="erl_driver#erl_drv_busy_msgq_limits">
+ <c>erl_driver:erl_drv_busy_msgq_limits</c></seecref>.</p>
</item>
<tag><c>ERL_DRV_FLAG_USE_INIT_ACK</c></tag>
<item>
<p>When this flag is specified, the linked-in driver must manually
acknowledge that the port has been successfully started using
- <seealso marker="erl_driver#erl_drv_init_ack">
- <c>erl_driver:erl_drv_init_ack()</c></seealso>.
+ <seecref marker="erl_driver#erl_drv_init_ack">
+ <c>erl_driver:erl_drv_init_ack()</c></seecref>.
This allows the implementor to make the
<c>erlang:open_port</c> exit with <c>badarg</c> after some
initial asynchronous initialization has been done.</p>
@@ -514,13 +514,13 @@ typedef struct erl_drv_entry {
<p>Called when a monitored process exits. The
<c>drv_data</c> is the data associated with the port for which
the process is monitored (using
- <seealso marker="erl_driver#driver_monitor_process">
- <c>erl_driver:driver_monitor_process</c></seealso>)
+ <seecref marker="erl_driver#driver_monitor_process">
+ <c>erl_driver:driver_monitor_process</c></seecref>)
and the <c>monitor</c> corresponds to the <c>ErlDrvMonitor</c>
structure filled
in when creating the monitor. The driver interface function
- <seealso marker="erl_driver#driver_get_monitored_process">
- <c>erl_driver:driver_get_monitored_process</c></seealso>
+ <seecref marker="erl_driver#driver_get_monitored_process">
+ <c>erl_driver:driver_get_monitored_process</c></seecref>
can be used to retrieve the process ID of the exiting process as
an <c>ErlDrvTermData</c>.</p>
</item>
@@ -528,8 +528,8 @@ typedef struct erl_drv_entry {
<c>void (*stop_select)(ErlDrvEvent event, void* reserved)</c></tag>
<item>
<p>Called on behalf of
- <seealso marker="erl_driver#driver_select">
- <c>erl_driver:driver_select</c></seealso>
+ <seecref marker="erl_driver#driver_select">
+ <c>erl_driver:driver_select</c></seecref>
when it is safe to close an event object.</p>
<p>A typical implementation on Unix is to do
<c>close((int)event)</c>.</p>
@@ -544,7 +544,7 @@ typedef struct erl_drv_entry {
the case that <c>stop_select</c> is called directly by
<c>erl_driver:driver_select</c>.</p>
<p>It is not allowed to call any functions in the
- <seealso marker="erl_driver">driver API</seealso> from
+ <seecref marker="erl_driver">driver API</seecref> from
<c>stop_select</c>. This strict limitation is because the
volatile context that <c>stop_select</c> can be called.</p>
</item>
@@ -553,9 +553,9 @@ typedef struct erl_drv_entry {
<section>
<title>See Also</title>
- <p><seealso marker="erl_driver"><c>erl_driver(3)</c></seealso>,
- <seealso marker="erlang"><c>erlang(3)</c></seealso>,
- <seealso marker="kernel:erl_ddll"><c>erl_ddll(3)</c></seealso></p>
+ <p><seecref marker="erl_driver"><c>erl_driver(3)</c></seecref>,
+ <seeerl marker="erlang"><c>erlang(3)</c></seeerl>,
+ <seeerl marker="kernel:erl_ddll"><c>erl_ddll(3)</c></seeerl></p>
</section>
</cref>
diff --git a/erts/doc/src/epmd.xml b/erts/doc/src/epmd_cmd.xml
index af2574b262..7af0033c14 100644
--- a/erts/doc/src/epmd.xml
+++ b/erts/doc/src/epmd_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,11 +19,11 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>epmd</title>
- <prepared>Claes Wikstr&ouml;m</prepared>
+ <prepared>Claes Wikstr&ouml;m</prepared>
<responsible></responsible>
<docno>1</docno>
<approved></approved>
@@ -82,28 +82,28 @@
<tag>Starting the port mapper daemon</tag>
<item>
<p>The daemon is started automatically by command
- <seealso marker="erl"><c>erl(1)</c></seealso>
+ <seecom marker="erl"><c>erl(1)</c></seecom>
if the node is to be distributed and no running
instance is present. If automatically launched
environment variables must be used to change the behavior
of the daemon; see section
- <seealso marker="#environment_variables">Environment
- Variables</seealso>.</p>
+ <seecom marker="#environment_variables">Environment
+ Variables</seecom>.</p>
<p>If argument <c>-daemon</c> is not specified,
<c><![CDATA[epmd]]></c> runs as a normal program with the
controlling terminal of the shell in which it is
started. Normally, it is to be run as a daemon.</p>
<p>Regular startup options are described in section
- <seealso marker="#daemon_flags">Regular Options</seealso>.</p>
+ <seecom marker="#daemon_flags">Regular Options</seecom>.</p>
<p>The <c>DbgExtra</c> options are described in section
- <seealso marker="#debug_flags">DbgExtra Options</seealso>.</p>
+ <seecom marker="#debug_flags">DbgExtra Options</seecom>.</p>
</item>
<tag>Communicating with a running port mapper daemon</tag>
<item>
<p>Communicating with the running <c>epmd</c> daemon by the
<c>epmd</c> program is done primarily for debugging purposes.</p>
- <p>The different queries are described in section <seealso
- marker="#interactive_flags">Interactive options</seealso>.</p>
+ <p>The different queries are described in section <seecom
+ marker="#interactive_flags">Interactive options</seecom>.</p>
</item>
</taglist>
</description>
@@ -113,7 +113,7 @@
<title>Regular Options</title>
<p>These options are available when starting the name server. The name
server is normally started automatically by command
- <seealso marker="erl"><c>erl(1)</c></seealso> (if not already available),
+ <seecom marker="erl"><c>erl(1)</c></seecom> (if not already available),
but it can also be started at system startup.</p>
<taglist>
@@ -123,15 +123,15 @@
comma-separated list of IP addresses and on the loopback address
(which is implicitly added to the list if it has not been
specified). This can also be set using environment variable
- <c><![CDATA[ERL_EPMD_ADDRESS]]></c>; see section <seealso
- marker="#environment_variables">Environment Variables</seealso>.</p>
+ <c><![CDATA[ERL_EPMD_ADDRESS]]></c>; see section <seecom
+ marker="#environment_variables">Environment Variables</seecom>.</p>
</item>
<tag><c><![CDATA[-port No]]></c></tag>
<item>
<p>Lets this instance of <c>epmd</c> listen to another TCP port than
default 4369. This can also be set using environment variable
- <c><![CDATA[ERL_EPMD_PORT]]></c>; see section <seealso
- marker="#environment_variables">Environment Variables</seealso>.</p>
+ <c><![CDATA[ERL_EPMD_PORT]]></c>; see section <seecom
+ marker="#environment_variables">Environment Variables</seecom>.</p>
</item>
<tag><c><![CDATA[-d | -debug]]></c></tag>
<item>
@@ -161,8 +161,8 @@
</item>
<item>
<p>Command <c>epmd -stop</c> (and the corresponding messages to
- <c>epmd</c>, as can be specified using <seealso
- marker="erl_interface:ei"><c>erl_interface:ei(3)</c></seealso>) is
+ <c>epmd</c>, as can be specified using <seecref
+ marker="erl_interface:ei"><c>erl_interface:ei(3)</c></seecref>) is
normally always ignored. This because it can cause a strange
situation where two nodes of the same name can be alive at the
same time. A node unregisters itself by only closing the
@@ -224,8 +224,8 @@
<item>
<p>Contacts the <c>epmd</c> listening on the specified TCP port
number (default 4369). This can also be set using environment
- variable <c><![CDATA[ERL_EPMD_PORT]]></c>; see section <seealso
- marker="#environment_variables">Environment Variables</seealso>.</p>
+ variable <c><![CDATA[ERL_EPMD_PORT]]></c>; see section <seecom
+ marker="#environment_variables">Environment Variables</seecom>.</p>
</item>
<tag><c><![CDATA[-names]]></c></tag>
<item>
@@ -295,9 +295,8 @@
<title>Logging</title>
<p>On some operating systems <em>syslog</em> will be used for
error reporting when <c>epmd</c> runs as a daemon. To enable
- the error logging, you must edit the
- <path unix="" windows="">/etc/syslog.conf</path> file and add an
- entry:</p>
+ the error logging, you must edit the /etc/syslog.conf file and
+ add an entry:</p>
<code type="none"><![CDATA[
!epmd
@@ -334,5 +333,3 @@
<p>To restrict access further, firewall software must be used.</p>
</section>
</comref>
-
-
diff --git a/erts/doc/src/erl.xml b/erts/doc/src/erl_cmd.xml
index 340196502f..372aee0380 100644
--- a/erts/doc/src/erl.xml
+++ b/erts/doc/src/erl_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -49,7 +49,7 @@
<p>As from ERTS 5.9 (Erlang/OTP R15B) the runtime system does by
default <em>not</em> bind schedulers to logical processors.
For more information, see system flag
- <seealso marker="#+sbt"><c>+sbt</c></seealso>.</p>
+ <seecom marker="#+sbt"><c>+sbt</c></seecom>.</p>
</note>
</description>
@@ -65,17 +65,17 @@
<item>
<p>Any argument starting with character <c><![CDATA[+]]></c> is
interpreted as an
- <seealso marker="#emu_flags">emulator flag</seealso>.</p>
+ <seecom marker="#emu_flags">emulator flag</seecom>.</p>
<p>As indicated by the name, emulator flags control
the behavior of the emulator.</p>
</item>
<item>
<p>Any argument starting with character <c><![CDATA[-]]></c>
(hyphen) is interpreted as a
- <seealso marker="#init_flags">flag</seealso>, which is to
+ <seecom marker="#init_flags">flag</seecom>, which is to
be passed to the Erlang part of the runtime system, more
specifically to the <c><![CDATA[init]]></c> system process, see
- <seealso marker="init"><c>init(3)</c></seealso>.</p>
+ <seeerl marker="init"><c>init(3)</c></seeerl>.</p>
<p>The <c><![CDATA[init]]></c> process itself interprets some of
these flags, the <em>init flags</em>. It also stores any
remaining flags, the <em>user flags</em>. The latter can be
@@ -143,9 +143,9 @@
<p>Sets the application configuration parameter <c><![CDATA[Par]]></c>
to the value <c><![CDATA[Val]]></c> for the application
<c><![CDATA[Application]]></c>; see
- <seealso marker="kernel:app"><c>app(4)</c></seealso> and
- <seealso marker="kernel:application">
- <c>application(3)</c></seealso>.</p>
+ <seefile marker="kernel:app"><c>app(4)</c></seefile> and
+ <seeerl marker="kernel:application">
+ <c>application(3)</c></seeerl>.</p>
</item>
<tag><marker id="args_file"/><c><![CDATA[-args_file FileName]]></c></tag>
<item>
@@ -179,9 +179,9 @@
<item>
<p>Specifies the name of the boot file, <c><![CDATA[File.boot]]></c>,
which is used to start the system; see
- <seealso marker="init"><c>init(3)</c></seealso>. Unless
+ <seeerl marker="init"><c>init(3)</c></seeerl>. Unless
<c><![CDATA[File]]></c> contains an absolute path, the system searches
- for <c><![CDATA[File.boot]]></c> in the current and
+ for <c><![CDATA[File.boot]]></c> in the current and
<c><![CDATA[$ROOT/bin]]></c> directories.</p>
<p>Defaults to <c><![CDATA[$ROOT/bin/start.boot]]></c>.</p>
</item>
@@ -191,37 +191,37 @@
other than <c><![CDATA[$ROOT]]></c>, this variable is expanded to
<c><![CDATA[Dir]]></c>. Used when applications are installed in
another directory than <c><![CDATA[$ROOT/lib]]></c>; see
- <seealso marker="sasl:systools#make_script/1">
- <c>systools:make_script/1,2</c></seealso> in SASL.</p>
+ <seemfa marker="sasl:systools#make_script/1">
+ <c>systools:make_script/1,2</c></seemfa> in SASL.</p>
</item>
<tag><c><![CDATA[-code_path_cache]]></c></tag>
<item>
<p>Enables the code path cache of the code server; see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-compile Mod1 Mod2 ...]]></c></tag>
<item>
<p>Compiles the specified modules and then terminates (with
non-zero exit code if the compilation of some file did not
succeed). Implies <c><![CDATA[-noinput]]></c>.</p>
- <p>Not recommended; use <seealso marker="erlc"><c>erlc</c></seealso>
+ <p>Not recommended; use <seecom marker="erlc"><c>erlc</c></seecom>
instead.</p>
</item>
- <tag><c><![CDATA[-config Config]]></c></tag>
+ <tag><c><![CDATA[-config Config [Config]]]></c></tag>
<item>
- <p>Specifies the name of a configuration file,
+ <p>Specifies the name of one or more configuration files,
<c><![CDATA[Config.config]]></c>, which is used to configure
applications; see
- <seealso marker="kernel:app"><c>app(4)</c></seealso> and
- <seealso marker="kernel:application">
- <c>application(3)</c></seealso>.</p>
+ <seefile marker="kernel:app"><c>app(4)</c></seefile> and
+ <seeerl marker="kernel:application">
+ <c>application(3)</c></seeerl>.</p>
</item>
<tag><marker id="connect_all"/><c><![CDATA[-connect_all false]]></c></tag>
<item>
<p>If this flag is present, <c><![CDATA[global]]></c> does not maintain
a fully connected network of distributed Erlang nodes, and then
global name registration cannot be used; see
- <seealso marker="kernel:global"><c>global(3)</c></seealso>.</p>
+ <seeerl marker="kernel:global"><c>global(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-cookie Cookie]]></c></tag>
<item>
@@ -260,13 +260,19 @@
<tag><c><![CDATA[-epmd_module Module]]></c> (init flag)</tag>
<item>
<p>Configures the module responsible to communicate to
- <seealso marker="epmd">epmd</seealso>. Defaults to <c>erl_epmd</c>.</p>
+ <seecom marker="epmd">epmd</seecom>. Defaults to <c>erl_epmd</c>.</p>
+ </item>
+ <tag><c><![CDATA[-erl_epmd_port Port]]></c> (init flag)</tag>
+ <item>
+ <p>Configures the port used by erl_epmd to listen for connection and connect to
+ other nodes. See <seeerl marker="kernel:erl_epmd">erl_epmd</seeerl> for more details.
+ Defaults to <c>0</c>.</p>
</item>
<tag><c><![CDATA[-eval Expr]]></c> (init flag)</tag>
<item>
<p>Makes <c><![CDATA[init]]></c> evaluate the expression
<c><![CDATA[Expr]]></c>; see
- <seealso marker="init"><c>init(3)</c></seealso>.</p>
+ <seeerl marker="init"><c>init(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-extra]]></c> (init flag)</tag>
<item>
@@ -277,10 +283,10 @@
<tag><c><![CDATA[-heart]]></c></tag>
<item>
<p>Starts heartbeat monitoring of the Erlang runtime system;
- see <seealso marker="kernel:heart">
- <c>heart(3)</c></seealso>.</p>
+ see <seeerl marker="kernel:heart">
+ <c>heart(3)</c></seeerl>.</p>
</item>
- <tag><c><![CDATA[-hidden]]></c></tag>
+ <tag><marker id="hidden"/><c><![CDATA[-hidden]]></c></tag>
<item>
<p>Starts the Erlang runtime system as a hidden node, if it is
run as a distributed node. Hidden nodes always establish
@@ -289,14 +295,14 @@
any of the connected nodes, that is, none of the connected
nodes are part of the result from <c><![CDATA[nodes/0]]></c> on the
other node. See also hidden global groups;
- <seealso marker="kernel:global_group">
- <c>global_group(3)</c></seealso>.</p>
+ <seeerl marker="kernel:global_group">
+ <c>global_group(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-hosts Hosts]]></c></tag>
<item>
<p>Specifies the IP addresses for the hosts on which Erlang boot servers
- are running, see <seealso marker="kernel:erl_boot_server">
- <c>erl_boot_server(3)</c></seealso>. This flag
+ are running, see <seeerl marker="kernel:erl_boot_server">
+ <c>erl_boot_server(3)</c></seeerl>. This flag
is mandatory if flag <c><![CDATA[-loader inet]]></c> is present.</p>
<p>The IP addresses must be specified in the standard form (four
decimal numbers separated by periods, for example,
@@ -329,7 +335,7 @@
<item>
<p>Specifies the method used by <c><![CDATA[erl_prim_loader]]></c> to
load Erlang modules into the system; see
- <seealso marker="erl_prim_loader"><c>erl_prim_loader(3)</c></seealso>.
+ <seeerl marker="erl_prim_loader"><c>erl_prim_loader(3)</c></seeerl>.
Two <c><![CDATA[Loader]]></c> methods are supported:</p>
<list type="bulleted">
<item>
@@ -350,7 +356,7 @@
<item>
<p>Makes the Erlang runtime system invoke <c><![CDATA[make:all()]]></c>
in the current working directory and then terminate; see
- <seealso marker="tools:make"><c>make(3)</c></seealso>. Implies
+ <seeerl marker="tools:make"><c>make(3)</c></seeerl>. Implies
<c><![CDATA[-noinput]]></c>.</p>
</item>
<tag><c><![CDATA[-man Module]]></c></tag>
@@ -365,25 +371,50 @@
the default. In <c><![CDATA[embedded]]></c> mode modules are not auto
loaded. The latter is recommended when the boot script preloads all
modules, as conventionally happens in OTP releases. See
- <seealso marker="kernel:code"><c>code(3)</c></seealso></p>.
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</item>
- <tag><c><![CDATA[-name Name]]></c></tag>
+ <tag><marker id="name"/><c><![CDATA[-name Name]]></c></tag>
<item>
<p>Makes the Erlang runtime system into a distributed node.
This flag invokes all network servers necessary for a node to
- become distributed; see <seealso marker="kernel:net_kernel">
- <c>net_kernel(3)</c></seealso>. It is also ensured that
+ become distributed; see <seeerl marker="kernel:net_kernel">
+ <c>net_kernel(3)</c></seeerl>. It is also ensured that
<c><![CDATA[epmd]]></c> runs on the current host before Erlang is
- started; see <seealso marker="epmd"><c>epmd(1)</c></seealso>.and the
- <seealso marker="#start_epmd"><c>-start_epmd</c></seealso> option.</p>
+ started; see <seecom marker="epmd"><c>epmd(1)</c></seecom>.and the
+ <seecom marker="#start_epmd"><c>-start_epmd</c></seecom> option.</p>
<p>The node name will be <c><![CDATA[Name@Host]]></c>, where
<c><![CDATA[Host]]></c> is the fully qualified host name of the
current host. For short names, use flag <c><![CDATA[-sname]]></c>
instead.</p>
+ <p>If <c>Name</c> is set to <em><c>undefined</c></em> the node will
+ be started in a special mode optimized to be the temporary client
+ of another node. When enabled the node will request a
+ <seeguide marker="erl_dist_protocol#DFLAG_NAME_ME">dynamic
+ node name</seeguide> from the first node it connects to. In addition
+ these distribution settings will be set:
+ </p>
+ <pre><seeapp marker="kernel:kernel_app#dist_listen">-dist_listen false</seeapp> <seecom marker="#hidden">-hidden</seecom> <seeapp marker="kernel:kernel_app#dist_auto_connect">-dist_auto_connect never</seeapp></pre>
+ <p>Because <c>-dist_auto_connect</c> is set to <c>never</c>, the system will have
+ to manually call <seemfa marker="kernel:net_kernel#connect_node/1">
+ <c>net_kernel:connect_node/1</c></seemfa> in order to start the distribution.
+ If the distribution channel is closed, when a node uses a dynamic
+ node name, the node will stop the distribution and a new call to
+ <seemfa marker="kernel:net_kernel#connect_node/1">
+ <c>net_kernel:connect_node/1</c></seemfa> has to be made. Note that the node
+ name may change if the distribution is dropped and then set up again.
+ </p>
+ <note>
+ <p>
+ The <em>dynamic node name</em> feature is supported from
+ OTP 23. Both the temporary client node and the first
+ connected peer node (supplying the dynamic node name) must
+ be at least OTP 23 for it to work.
+ </p>
+ </note>
<warning>
<p>
Starting a distributed node without also specifying
- <seealso marker="#proto_dist"><c>-proto_dist inet_tls</c></seealso>
+ <seecom marker="#proto_dist"><c>-proto_dist inet_tls</c></seecom>
will expose the node to attacks that may give the attacker
complete access to the node and in extension the cluster.
When using un-secure distributed nodes, make sure that the
@@ -406,7 +437,7 @@
<item>
<p>Disables the sticky directory facility of the Erlang code
server; see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-oldshell]]></c></tag>
<item>
@@ -416,26 +447,26 @@
<tag><c><![CDATA[-pa Dir1 Dir2 ...]]></c></tag>
<item>
<p>Adds the specified directories to the beginning of the code
- path, similar to <seealso marker="kernel:code#add_pathsa/1">
- <c><![CDATA[code:add_pathsa/1]]></c></seealso>. Note that the
+ path, similar to <seemfa marker="kernel:code#add_pathsa/1">
+ <c><![CDATA[code:add_pathsa/1]]></c></seemfa>. Note that the
order of the given directories will be reversed in the
resulting path.</p>
<p>As an alternative to <c>-pa</c>, if several directories are
to be prepended to the code path and the directories have a
common parent directory, that parent directory can be
specified in environment variable <c>ERL_LIBS</c>; see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-pz Dir1 Dir2 ...]]></c></tag>
<item>
<p>Adds the specified directories to the end of the code path,
similar to <c><![CDATA[code:add_pathsz/1]]></c>; see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-path Dir1 Dir2 ...]]></c></tag>
<item>
<p>Replaces the path specified in the boot script; see
- <seealso marker="sasl:script"><c>script(4)</c></seealso>.</p>
+ <seefile marker="sasl:script"><c>script(4)</c></seefile>.</p>
</item>
<tag><c><![CDATA[-proto_dist Proto]]></c></tag>
<item>
@@ -446,8 +477,8 @@
<item>TCP over IPv4 (the default)</item>
<tag><c>inet_tls</c></tag>
<item>Distribution over TLS/SSL, See the
- <seealso marker="ssl:ssl_distribution">
- Using SSL for Erlang Distribution</seealso> User's Guide
+ <seeguide marker="ssl:ssl_distribution">
+ Using SSL for Erlang Distribution</seeguide> User&apos;s Guide
for details on how to setup a secure distributed node.
</item>
<tag><c>inet6_tcp</c></tag>
@@ -457,19 +488,23 @@
<pre>
% <input>erl -name test@ipv6node.example.com -proto_dist inet6_tcp</input></pre>
</item>
- <tag><c><![CDATA[-remsh Node]]></c></tag>
+ <tag><marker id="remsh"/><c><![CDATA[-remsh Node]]></c></tag>
<item>
- <p>Starts Erlang with a remote shell connected to
- <c><![CDATA[Node]]></c>. Requires either <c><![CDATA[-name]]></c>
- or <c><![CDATA[-sname]]></c> to be given. If <c><![CDATA[Node]]></c>
- does not contain a hostname, one is automatically taken from
- <c><![CDATA[-name]]></c> or <c><![CDATA[-sname]]></c></p>
+ <p>Starts Erlang with a remote shell connected to <c><![CDATA[Node]]></c>.</p>
+ <p>If no <c><![CDATA[-name]]></c> or <c><![CDATA[-sname]]></c> is given
+ the node will be started using <c>-sname undefined</c>.
+ If <c><![CDATA[Node]]></c> does not contain a hostname, one is automatically
+ taken from <c><![CDATA[-name]]></c> or <c><![CDATA[-sname]]></c>
+ </p>
+ <note><p>Before OTP-23 the user <em>needed</em> to supply a valid <c>-sname</c> or
+ <c>-name</c> for <c>-remsh</c> to work. This is still the case if the target
+ node is not running OTP-23 or later.</p></note>
</item>
<tag><c><![CDATA[-rsh Program]]></c></tag>
<item>
<p>Specifies an alternative to <c><![CDATA[ssh]]></c> for starting a
slave node on a remote host; see
- <seealso marker="stdlib:slave"><c>slave(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:slave"><c>slave(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-run Mod [Func [Arg1, Arg2, ...]]]]></c> (init
flag)</tag>
@@ -479,8 +514,8 @@
If no arguments are provided, the function is assumed to be of
arity 0. Otherwise it is assumed to be of arity 1, taking the list
<c><![CDATA[[Arg1,Arg2,...]]]></c> as argument. All arguments are
- passed as strings. See <seealso marker="init">
- <c>init(3)</c></seealso>.</p>
+ passed as strings. See <seeerl marker="init">
+ <c>init(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-s Mod [Func [Arg1, Arg2, ...]]]]></c> (init flag)</tag>
<item>
@@ -489,14 +524,14 @@
If no arguments are provided, the function is assumed to be of
arity 0. Otherwise it is assumed to be of arity 1, taking the list
<c><![CDATA[[Arg1,Arg2,...]]]></c> as argument. All arguments are
- passed as atoms. See <seealso marker="init">
- <c>init(3)</c></seealso>.</p>
+ passed as atoms. See <seeerl marker="init">
+ <c>init(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[-setcookie Cookie]]></c></tag>
<item>
<p>Sets the magic cookie of the node to <c><![CDATA[Cookie]]></c>; see
- <seealso marker="erlang#set_cookie/2">
- <c>erlang:set_cookie/2</c></seealso>.</p>
+ <seemfa marker="erlang#set_cookie/2">
+ <c>erlang:set_cookie/2</c></seemfa>.</p>
</item>
<tag><c><![CDATA[-shutdown_time Time]]></c></tag>
<item>
@@ -508,9 +543,9 @@
<tag><c><![CDATA[-sname Name]]></c></tag>
<item>
<p>Makes the Erlang runtime system into a distributed node, similar to
- <c><![CDATA[-name]]></c>, but the host name portion of the node
- name <c><![CDATA[Name@Host]]></c> will be the short name, not fully
- qualified.</p>
+ <seecom marker="#name"><c><![CDATA[-name]]></c></seecom>, but the host
+ name portion of the node name <c><![CDATA[Name@Host]]></c> will be the
+ short name, not fully qualified.</p>
<p>This is sometimes the only way to run distributed Erlang if
the Domain Name System (DNS) is not running. No communication can
exist between nodes running with flag <c><![CDATA[-sname]]></c>
@@ -519,7 +554,7 @@
<warning>
<p>
Starting a distributed node without also specifying
- <seealso marker="#proto_dist"><c>-proto_dist inet_tls</c></seealso>
+ <seecom marker="#proto_dist"><c>-proto_dist inet_tls</c></seecom>
will expose the node to attacks that may give the attacker
complete access to the node and in extension the cluster.
When using un-secure distributed nodes, make sure that the
@@ -531,7 +566,7 @@
<item>
<p>Specifies whether Erlang should start
- <seealso marker="epmd">epmd</seealso> on startup. By default
+ <seecom marker="epmd">epmd</seecom> on startup. By default
this is <c>true</c>, but if you prefer to start epmd
manually, set this to <c>false</c>.</p>
@@ -567,16 +602,16 @@
be large. The default size is enough for drivers
delivered with Erlang/OTP, but might not be large
enough for other dynamically linked-in drivers that use the
- <seealso marker="erl_driver#driver_async">
- <c>driver_async()</c></seealso> functionality.
+ <seecref marker="erl_driver#driver_async">
+ <c>driver_async()</c></seecref> functionality.
Notice that the value passed is only a suggestion,
and it can even be ignored on some platforms.</p>
</item>
<tag><marker id="async_thread_pool_size"/><c><![CDATA[+A size]]></c></tag>
<item>
<p>Sets the number of threads in async thread pool. Valid range
- is 0-1024. The async thread pool is used by linked-in drivers to
- handle work that may take a very long time. Since OTP-21 there are
+ is 1-1024. The async thread pool is used by linked-in drivers to
+ handle work that may take a very long time. Since OTP 21 there are
very few linked-in drivers in the default Erlang/OTP distribution
that uses the async thread pool. Most of them have been migrated to
dirty IO schedulers. Defaults to 1.</p>
@@ -601,8 +636,8 @@
<tag><marker id="+c"/><c><![CDATA[+c true | false]]></c></tag>
<item>
<p>Enables or disables
- <seealso marker="time_correction#Time_Correction">time
- correction</seealso>:</p>
+ <seeguide marker="time_correction#Time_Correction">time
+ correction</seeguide>:</p>
<taglist>
<tag><c>true</c></tag>
<item>Enables time correction. This is the default if
@@ -616,18 +651,18 @@
<tag><marker id="+C_"/><c><![CDATA[+C no_time_warp | single_time_warp |
multi_time_warp]]></c></tag>
<item>
- <p>Sets <seealso marker="time_correction#Time_Warp_Modes">time warp
- mode</seealso>:</p>
+ <p>Sets <seeguide marker="time_correction#Time_Warp_Modes">time warp
+ mode</seeguide>:</p>
<taglist>
<tag><c>no_time_warp</c></tag>
- <item><seealso marker="time_correction#No_Time_Warp_Mode">
- No time warp mode</seealso> (the default)</item>
+ <item><seeguide marker="time_correction#No_Time_Warp_Mode">
+ No time warp mode</seeguide> (the default)</item>
<tag><c>single_time_warp</c></tag>
- <item><seealso marker="time_correction#Single_Time_Warp_Mode">
- Single time warp mode</seealso></item>
+ <item><seeguide marker="time_correction#Single_Time_Warp_Mode">
+ Single time warp mode</seeguide></item>
<tag><c>multi_time_warp</c></tag>
- <item><seealso marker="time_correction#Multi_Time_Warp_Mode">
- Multi-time warp mode</seealso></item>
+ <item><seeguide marker="time_correction#Multi_Time_Warp_Mode">
+ Multi-time warp mode</seeguide></item>
</taglist>
</item>
<tag><c><![CDATA[+d]]></c></tag>
@@ -638,12 +673,12 @@
of process heaps is destroyed by the crash dump generation.</p>
<p>Option <c>+d</c> instructs the emulator to produce only a
core dump and no crash dump if an internal error is detected.</p>
- <p>Calling <seealso marker="erlang#halt/1">
- <c>erlang:halt/1</c></seealso> with a string argument still
+ <p>Calling <seemfa marker="erlang#halt/1">
+ <c>erlang:halt/1</c></seemfa> with a string argument still
produces a crash dump. On Unix systems, sending an emulator process
a <c>SIGUSR1</c> signal also forces a crash dump.</p>
</item>
- <tag><marker id="+dcg"/><c><![CDATA[+rg DecentralizedCounterGroupsLimit]]></c></tag>
+ <tag><marker id="+dcg"/><c><![CDATA[+dcg DecentralizedCounterGroupsLimit]]></c></tag>
<item>
<p>Limits the number of decentralized counter groups used by
decentralized counters optimized for update operations in the
@@ -658,8 +693,8 @@
read operations. Each group consumes 64 bytes in each
counter.</p>
<p>Notice that a runtime system using decentralized
- counter groups benefits from <seealso marker="#+sbt">binding
- schedulers to logical processors</seealso>, as the groups are
+ counter groups benefits from <seecom marker="#+sbt">binding
+ schedulers to logical processors</seecom>, as the groups are
distributed better between schedulers with this option.</p>
<p>This option only affects decentralized counters used for
the counters that are keeping track of the memory consumption
@@ -669,7 +704,7 @@
<tag><marker id="+e"/><c><![CDATA[+e Number]]></c></tag>
<item>
<p>Sets the maximum number of ETS tables. This limit is
- <seealso marker="stdlib:ets#max_ets_tables">partially obsolete</seealso>.
+ <seeerl marker="stdlib:ets#max_ets_tables">partially obsolete</seeerl>.
</p>
</item>
<tag><c><![CDATA[+ec]]></c></tag>
@@ -684,20 +719,20 @@
using the ISO Latin-1 encoding, disallowing Unicode characters with
code points &gt; 255.</p>
<p>For more information about Unicode filenames, see section
- <seealso marker="stdlib:unicode_usage#unicode_file_names">Unicode
- Filenames</seealso> in the STDLIB User's Guide. Notice that
+ <seeguide marker="stdlib:unicode_usage#unicode_file_names">Unicode
+ Filenames</seeguide> in the STDLIB User&apos;s Guide. Notice that
this value also applies to command-line parameters and environment
- variables (see section <seealso
+ variables (see section <seeguide
marker="stdlib:unicode_usage#unicode_in_environment_and_parameters">
- Unicode in Environment and Parameters</seealso> in the STDLIB
- User's Guide).</p>
+ Unicode in Environment and Parameters</seeguide> in the STDLIB
+ User&apos;s Guide).</p>
</item>
<tag><c><![CDATA[+fnu[{w|i|e}]]]></c></tag>
<item>
<p>The virtual machine works with filenames as if they are encoded
using UTF-8 (or some other system-specific Unicode encoding). This is
the default on operating systems that enforce Unicode encoding, that
- is, Windows and MacOS X.</p>
+ is, Windows MacOS X and Android.</p>
<p>The <c>+fnu</c> switch can be followed by <c>w</c>, <c>i</c>, or
<c>e</c> to control how wrongly encoded filenames are to be
reported:</p>
@@ -716,38 +751,38 @@
wrongly encoded filename (or directory name) is encountered.</p>
</item>
</list>
- <p>Notice that <seealso marker="kernel:file#read_link/1">
- <c>file:read_link/1</c></seealso> always returns an error if the link
+ <p>Notice that <seemfa marker="kernel:file#read_link/1">
+ <c>file:read_link/1</c></seemfa> always returns an error if the link
points to an invalid filename.</p>
<p>For more information about Unicode filenames, see section
- <seealso marker="stdlib:unicode_usage#unicode_file_names">Unicode
- Filenames</seealso> in the STDLIB User's Guide. Notice that
+ <seeguide marker="stdlib:unicode_usage#unicode_file_names">Unicode
+ Filenames</seeguide> in the STDLIB User&apos;s Guide. Notice that
this value also applies to command-line parameters and environment
- variables (see section <seealso
+ variables (see section <seeguide
marker="stdlib:unicode_usage#unicode_in_environment_and_parameters">
- Unicode in Environment and Parameters</seealso> in the STDLIB
- User's Guide).</p>
+ Unicode in Environment and Parameters</seeguide> in the STDLIB
+ User&apos;s Guide).</p>
</item>
<tag><c><![CDATA[+fna[{w|i|e}]]]></c></tag>
<item>
<p>Selection between <c>+fnl</c> and <c>+fnu</c> is done based
on the current locale settings in the OS. This means that if you
have set your terminal for UTF-8 encoding, the filesystem is
- expected to use the same encoding for filenames. This is
- default on all operating systems, except MacOS X and Windows.</p>
+ expected to use the same encoding for filenames. This is the default
+ on all operating systems, except Android, MacOS X and Windows.</p>
<p>The <c>+fna</c> switch can be followed by <c>w</c>, <c>i</c>, or
<c>e</c>. This has effect if the locale settings cause the behavior
of <c>+fnu</c> to be selected; see the description of <c>+fnu</c>
above. If the locale settings cause the behavior of <c>+fnl</c> to be
selected, then <c>w</c>, <c>i</c>, or <c>e</c> have no effect.</p>
<p>For more information about Unicode filenames, see section
- <seealso marker="stdlib:unicode_usage#unicode_file_names">Unicode
- Filenames</seealso> in the STDLIB User's Guide. Notice that
+ <seeguide marker="stdlib:unicode_usage#unicode_file_names">Unicode
+ Filenames</seeguide> in the STDLIB User&apos;s Guide. Notice that
this value also applies to command-line parameters and environment
- variables (see section <seealso
+ variables (see section <seeguide
marker="stdlib:unicode_usage#unicode_in_environment_and_parameters">
- Unicode in Environment and Parameters</seealso> in the STDLIB
- User's Guide).</p>
+ Unicode in Environment and Parameters</seeguide> in the STDLIB
+ User&apos;s Guide).</p>
</item>
<tag><c><![CDATA[+hms Size]]></c></tag>
<item>
@@ -764,23 +799,23 @@
<p>Sets the default maximum heap size of processes to the size
<c><![CDATA[Size]]></c>. Defaults to <c>0</c>, which means that no
maximum heap size is used. For more information, see
- <seealso marker="erlang#process_flag_max_heap_size">
- <c>process_flag(max_heap_size, MaxHeapSize)</c></seealso>.</p>
+ <seeerl marker="erlang#process_flag_max_heap_size">
+ <c>process_flag(max_heap_size, MaxHeapSize)</c></seeerl>.</p>
</item>
<tag><marker id="+hmaxel"/><c><![CDATA[+hmaxel true|false]]></c></tag>
<item>
<p>Sets whether to send an error logger message or not for processes
reaching the maximum heap size. Defaults to <c>true</c>.
For more information, see
- <seealso marker="erlang#process_flag_max_heap_size">
- <c>process_flag(max_heap_size, MaxHeapSize)</c></seealso>.</p>
+ <seeerl marker="erlang#process_flag_max_heap_size">
+ <c>process_flag(max_heap_size, MaxHeapSize)</c></seeerl>.</p>
</item>
<tag><marker id="+hmaxk"/><c><![CDATA[+hmaxk true|false]]></c></tag>
<item>
<p>Sets whether to kill processes reaching the maximum heap size or not.
Default to <c>true</c>. For more information, see
- <seealso marker="erlang#process_flag_max_heap_size">
- <c>process_flag(max_heap_size, MaxHeapSize)</c></seealso>.</p>
+ <seeerl marker="erlang#process_flag_max_heap_size">
+ <c>process_flag(max_heap_size, MaxHeapSize)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[+hpds Size]]></c></tag>
<item>
@@ -789,11 +824,11 @@
</item>
<tag><marker id="+hmqd"/><c>+hmqd off_heap|on_heap</c></tag>
<item>
- <p>Sets the default value for process flag <c>message_queue_data</c>.
+ <p>Sets the default value of the <c>message_queue_data</c> process flag.
Defaults to <c>on_heap</c>. If <c>+hmqd</c> is not
- passed, <c>on_heap</c> will be the default. For more information, see
- <seealso marker="erlang#process_flag_message_queue_data">
- <c>process_flag(message_queue_data, MQD)</c></seealso>.</p>
+ passed, <c>on_heap</c> will be the default. For more information, see
+ <seeerl marker="erlang#process_flag_message_queue_data">
+ <c>process_flag(message_queue_data, MQD)</c></seeerl>.</p>
</item>
<tag><marker id="+IOp"/><c>+IOp PollSets</c></tag>
<item>
@@ -810,13 +845,13 @@
The maximum number of poll threads allowed is 1024. The default is 1.
</p>
<p>A good way to check if more IO poll threads are needed is to use
- <seealso marker="runtime_tools:msacc">microstate accounting</seealso>
+ <seeerl marker="runtime_tools:msacc">microstate accounting</seeerl>
and see what the load of the IO poll thread is. If it is high it could
be a good idea to add more threads.</p>
</item>
<tag><marker id="+IOPp"/><c>+IOPp PollSetsPercentage</c></tag>
<item>
- <p>Similar to <seealso marker="#+IOp"><c>+IOp</c></seealso> but uses
+ <p>Similar to <seecom marker="#+IOp"><c>+IOp</c></seecom> but uses
percentages to set the number of IO pollsets to create, based on the
number of poll threads configured. If both <c>+IOPp</c> and <c>+IOp</c>
are used, <c>+IOPp</c> is ignored.
@@ -824,17 +859,12 @@
</item>
<tag><marker id="+IOPt"/><c>+IOPt PollThreadsPercentage</c></tag>
<item>
- <p>Similar to <seealso marker="#+IOt"><c>+IOt</c></seealso> but uses
+ <p>Similar to <seecom marker="#+IOt"><c>+IOt</c></seecom> but uses
percentages to set the number of IO poll threads to create, based on
the number of schedulers configured. If both <c>+IOPt</c> and
<c>+IOt</c> are used, <c>+IOPt</c> is ignored.
</p>
</item>
- <tag><c><![CDATA[+l]]></c></tag>
- <item>
- <p>Enables autoload tracing, displaying information while loading
- code.</p>
- </item>
<tag><c><![CDATA[+L]]></c></tag>
<item>
<p>Prevents loading information about source filenames and line
@@ -844,7 +874,7 @@
<tag><marker id="erts_alloc"/><c><![CDATA[+MFlag Value]]></c></tag>
<item>
<p>Memory allocator-specific flags. For more information, see
- <seealso marker="erts_alloc"><c>erts_alloc(3)</c></seealso>.</p>
+ <seecref marker="erts_alloc"><c>erts_alloc(3)</c></seecref>.</p>
</item>
<tag><marker id="+pc"/><marker id="printable_character_range"/>
<c><![CDATA[+pc Range]]></c></tag>
@@ -852,7 +882,7 @@
<p>Sets the range of characters that the system considers printable in
heuristic detection of strings. This typically affects the shell,
debugger, and <c>io:format</c> functions (when <c>~tp</c> is used in
- the format string).</p>
+ the format string).</p>
<p>Two values are supported for <c>Range</c>:</p>
<taglist>
<tag><c>latin1</c></tag>
@@ -867,8 +897,8 @@
string syntax. This can give unexpected results if, for
example, your font does not cover all Unicode characters.</item>
</taglist>
- <p>See also <seealso marker="stdlib:io#printable_range/0">
- <c>io:printable_range/0</c></seealso> in STDLIB.</p>
+ <p>See also <seemfa marker="stdlib:io#printable_range/0">
+ <c>io:printable_range/0</c></seemfa> in STDLIB.</p>
</item>
<tag><marker id="+P"/><marker id="max_processes"/><c><![CDATA[+P Number]]></c></tag>
<item>
@@ -880,7 +910,7 @@
but not always, chooses a value that is a power of 2. This might,
however, be changed in the future. The actual value chosen can be
checked by calling
- <seealso marker="erlang#system_info_process_limit">erlang:system_info(process_limit)</seealso>.</p>
+ <seeerl marker="erlang#system_info_process_limit">erlang:system_info(process_limit)</seeerl>.</p>
<p>The default value is <c>262144</c></p>
</item>
<tag><marker id="+Q"/><marker id="max_ports"/><c><![CDATA[+Q Number]]></c></tag>
@@ -893,7 +923,7 @@
but not always, chooses a value that is a power of 2. This might,
however, be changed in the future. The actual value chosen can be
checked by calling
- <seealso marker="erlang#system_info_port_limit">erlang:system_info(port_limit)</seealso>.</p>
+ <seeerl marker="erlang#system_info_port_limit">erlang:system_info(port_limit)</seeerl>.</p>
<p>The default value used is normally <c>65536</c>. However, if
the runtime system is able to determine maximum amount of file
descriptors that it is allowed to open and this value is larger
@@ -940,25 +970,30 @@
for write operations. Each reader group consumes 64 byte
in each read/write lock.</p>
<p>Notice that a runtime system using shared reader groups benefits from
- <seealso marker="#+sbt">binding schedulers to logical
- processors</seealso>, as the reader groups are distributed better
+ <seecom marker="#+sbt">binding schedulers to logical
+ processors</seecom>, as the reader groups are distributed better
between schedulers.</p>
</item>
<tag><marker id="+S"/>
<c><![CDATA[+S Schedulers:SchedulerOnline]]></c></tag>
<item>
<p>Sets the number of scheduler threads to create and scheduler threads
- to set online. The maximum for both
- values is 1024. If the Erlang runtime system is able to determine the
- number of logical processors configured and logical processors
- available, <c>Schedulers</c> defaults to logical processors
- configured, and <c>SchedulersOnline</c> defaults to logical processors
- available; otherwise the default values are 1. <c>Schedulers</c> can
- be omitted if <c>:SchedulerOnline</c> is not and conversely. The
- number of schedulers online can be changed at runtime through
- <seealso marker="erlang#system_flag_schedulers_online">
+ to set online. The maximum for both values is 1024. If the Erlang
+ runtime system is able to determine the number of logical processors
+ configured and logical processors available, <c>Schedulers</c>
+ defaults to logical processors configured, and
+ <c>SchedulersOnline</c> defaults to logical processors available;
+ otherwise the default values are 1. If the emulator detects that it
+ is subject to a <seeerl marker="erlang#system_info_cpu_quota">CPU
+ quota</seeerl>, the default value for <c>SchedulersOnline</c> will
+ be limited accordingly.</p>
+ <p>
+ <c>Schedulers</c> can be omitted if <c>:SchedulerOnline</c> is not
+ and conversely. The number of schedulers online can be changed at
+ runtime through
+ <seeerl marker="erlang#system_flag_schedulers_online">
<c>erlang:system_flag(schedulers_online,
- SchedulersOnline)</c></seealso>.</p>
+ SchedulersOnline)</c></seeerl>.</p>
<p>If <c>Schedulers</c> or <c>SchedulersOnline</c> is specified as a
negative number, the value is subtracted from the default number of
logical processors configured or logical processors available,
@@ -970,7 +1005,7 @@
<tag><marker id="+SP"/><c><![CDATA[+SP
SchedulersPercentage:SchedulersOnlinePercentage]]></c></tag>
<item>
- <p>Similar to <seealso marker="#+S"><c>+S</c></seealso> but uses
+ <p>Similar to <seecom marker="#+S"><c>+S</c></seecom> but uses
percentages to set the number of scheduler threads to create, based
on logical processors configured, and scheduler threads to set online,
based on logical processors available.
@@ -981,10 +1016,10 @@
<c>SchedulersPercentage</c> can be omitted if
<c>:SchedulersOnlinePercentage</c> is not and conversely. The number
of schedulers online can be changed at runtime through
- <seealso marker="erlang#system_flag_schedulers_online">
+ <seeerl marker="erlang#system_flag_schedulers_online">
<c>erlang:system_flag(schedulers_online,
- SchedulersOnline)</c></seealso>.</p>
- <p>This option interacts with <seealso marker="#+S"><c>+S</c></seealso>
+ SchedulersOnline)</c></seeerl>.</p>
+ <p>This option interacts with <seecom marker="#+S"><c>+S</c></seecom>
settings. For example, on a system with 8 logical cores configured
and 8 logical cores available, the combination of the options
<c>+S 4:4 +SP 50:25</c> (in either order) results in 2 scheduler
@@ -1003,17 +1038,17 @@
<item>The number of dirty CPU scheduler threads online cannot exceed
the number of normal scheduler threads online.</item>
</list>
- <p>For details, see the <seealso marker="#+S"><c>+S</c></seealso> and
- <seealso marker="#+SP"><c>+SP</c></seealso>. By default, the number
+ <p>For details, see the <seecom marker="#+S"><c>+S</c></seecom> and
+ <seecom marker="#+SP"><c>+SP</c></seecom>. By default, the number
of dirty CPU scheduler threads created equals the number of normal
scheduler threads created, and the number of dirty CPU scheduler
threads online equals the number of normal scheduler threads online.
<c>DirtyCPUSchedulers</c> can be omitted if
<c>:DirtyCPUSchedulersOnline</c> is not and conversely. The number of
dirty CPU schedulers online can be changed at runtime through
- <seealso marker="erlang#system_flag_dirty_cpu_schedulers_online">
+ <seeerl marker="erlang#system_flag_dirty_cpu_schedulers_online">
<c>erlang:system_flag(dirty_cpu_schedulers_online,
- DirtyCPUSchedulersOnline)</c></seealso>.</p>
+ DirtyCPUSchedulersOnline)</c></seeerl>.</p>
<p>The amount of dirty CPU schedulers is limited by the amount of
normal schedulers in order to limit the effect on processes
executing on ordinary schedulers. If the amount of dirty CPU
@@ -1022,14 +1057,14 @@
<p>Typical users of the dirty CPU schedulers are large garbage collections,
json protocol encode/decoders written as nifs and matrix manipulation
libraries.</p>
- <p>You can use <seealso marker="runtime_tools:msacc">msacc(3)</seealso>
+ <p>You can use <seeerl marker="runtime_tools:msacc">msacc(3)</seeerl>
in order to see the current load of the dirty CPU schedulers threads
and adjust the number used accordingly.</p>
</item>
<tag><marker id="+SDPcpu"/><c><![CDATA[+SDPcpu
DirtyCPUSchedulersPercentage:DirtyCPUSchedulersOnlinePercentage]]></c></tag>
<item>
- <p>Similar to <seealso marker="#+SDcpu"><c>+SDcpu</c></seealso> but
+ <p>Similar to <seecom marker="#+SDcpu"><c>+SDcpu</c></seecom> but
uses percentages to set the number of dirty CPU scheduler threads to
create and the number of dirty CPU scheduler threads to set online.
Specified values must be
@@ -1040,11 +1075,11 @@
be omitted if <c>:DirtyCPUSchedulersOnlinePercentage</c> is not and
conversely. The number of dirty CPU schedulers online can be changed
at runtime through
- <seealso marker="erlang#system_flag_dirty_cpu_schedulers_online">
+ <seeerl marker="erlang#system_flag_dirty_cpu_schedulers_online">
<c>erlang:system_flag(dirty_cpu_schedulers_online,
- DirtyCPUSchedulersOnline)</c></seealso>.</p>
- <p>This option interacts with <seealso
- marker="#+SDcpu"><c>+SDcpu</c></seealso> settings. For example, on a
+ DirtyCPUSchedulersOnline)</c></seeerl>.</p>
+ <p>This option interacts with <seecom
+ marker="#+SDcpu"><c>+SDcpu</c></seecom> settings. For example, on a
system with 8 logical cores configured and 8 logical cores available,
the combination of the options <c>+SDcpu 4:4 +SDPcpu 50:25</c> (in
either order) results in 2 dirty CPU scheduler threads (50% of 4) and
@@ -1056,13 +1091,13 @@
Valid range is 1-1024. By
default, the number of dirty I/O scheduler threads created is 10.</p>
<p>The amount of dirty IO schedulers is not limited by the amount of
- normal schedulers <seealso marker="#+SDcpu">like the amount of
- dirty CPU schedulers</seealso>. This since only I/O bound work is
+ normal schedulers <seecom marker="#+SDcpu">like the amount of
+ dirty CPU schedulers</seecom>. This since only I/O bound work is
expected to execute on dirty I/O schedulers. If the user should schedule CPU
bound jobs on dirty I/O schedulers, these jobs might starve ordinary
jobs executing on ordinary schedulers.</p>
<p>Typical users of the dirty IO schedulers are reading and writing to files.</p>
- <p>You can use <seealso marker="runtime_tools:msacc">msacc(3)</seealso>
+ <p>You can use <seeerl marker="runtime_tools:msacc">msacc(3)</seeerl>
in order to see the current load of the dirty IO schedulers threads
and adjust the number used accordingly.</p>
</item>
@@ -1074,7 +1109,7 @@
<item>
<p>Sets scheduler bind type.</p>
<p>Schedulers can also be bound using flag
- <seealso marker="#+stbt"><c>+stbt</c></seealso>. The only
+ <seecom marker="#+stbt"><c>+stbt</c></seecom>. The only
difference between these two flags is how the following errors
are handled:</p>
<list type="bulleted">
@@ -1082,7 +1117,7 @@
platform.</item>
<item>No available CPU topology. That is, the runtime system was
not able to detect the CPU topology automatically, and no
- <seealso marker="#+sct">user-defined CPU topology</seealso>
+ <seecom marker="#+sct">user-defined CPU topology</seecom>
was set.</item>
</list>
<p>If any of these errors occur when <c>+sbt</c> has been passed,
@@ -1104,7 +1139,7 @@
</item>
<tag><c>ts</c></tag>
<item><c>thread_spread</c> - Thread refers to hardware threads
- (such as Intel's hyper-threads). Schedulers with low scheduler
+ (such as Intel&apos;s hyper-threads). Schedulers with low scheduler
identifiers, are bound to the first hardware thread of
each core, then schedulers with higher scheduler identifiers
are bound to the second hardware thread of each core,and so on.
@@ -1149,7 +1184,7 @@
is processed and <c>BindType</c> is any other type than
<c>u</c>, the runtime system fails to start. CPU
topology can be defined using flag
- <seealso marker="#+sct"><c>+sct</c></seealso>. Notice
+ <seecom marker="#+sct"><c>+sct</c></seecom>. Notice
that flag <c>+sct</c> can have to be passed before flag
<c>+sbt</c> on the command line (if no CPU topology
has been automatically detected).</p>
@@ -1180,8 +1215,8 @@
an error is reported, it is reported to the
<c>error_logger</c>. If you want to verify that the
schedulers have bound as requested, call
- <seealso marker="erlang#system_info_scheduler_bindings">
- <c>erlang:system_info(scheduler_bindings)</c></seealso>.</p>
+ <seeerl marker="erlang#system_info_scheduler_bindings">
+ <c>erlang:system_info(scheduler_bindings)</c></seeerl>.</p>
</note>
</item>
<tag><marker id="+sbwt"/>
@@ -1198,7 +1233,7 @@
<tag><marker id="+sbwtdcpu"/>
<c>+sbwtdcpu none|very_short|short|medium|long|very_long</c></tag>
<item>
- <p>As <seealso marker="#+sbwt"><c>+sbwt</c></seealso> but affects
+ <p>As <seecom marker="#+sbwt"><c>+sbwt</c></seecom> but affects
dirty CPU schedulers. Defaults to <c>short</c>.</p>
<note>
<p>This flag can be removed or changed at any time
@@ -1208,7 +1243,7 @@
<tag><marker id="+sbwtdio"/>
<c>+sbwtdio none|very_short|short|medium|long|very_long</c></tag>
<item>
- <p>As <seealso marker="#+sbwt"><c>+sbwt</c></seealso> but affects
+ <p>As <seecom marker="#+sbwt"><c>+sbwt</c></seecom> but affects
dirty IO schedulers. Defaults to <c>short</c>.</p>
<note>
<p>This flag can be removed or changed at any time
@@ -1227,7 +1262,7 @@
the frequency with which schedulers run out of work is
not taken into account by the load balancing logic.</p>
<p><c>+scl false</c> is similar to
- <seealso marker="#+sub"><c>+sub true</c></seealso>, but
+ <seecom marker="#+sub"><c>+sub true</c></seecom>, but
<c>+sub true</c> also balances scheduler utilization
between schedulers.</p>
</item>
@@ -1257,8 +1292,8 @@
<p>Sets a user-defined CPU topology. The user-defined
CPU topology overrides any automatically detected
CPU topology. The CPU topology is used when
- <seealso marker="#+sbt">binding schedulers to logical
- processors</seealso>.</p>
+ <seecom marker="#+sbt">binding schedulers to logical
+ processors</seecom>.</p>
<p>Uppercase letters signify real identifiers and lowercase
letters signify fake identifiers only used for description
of the topology. Identifiers passed as real identifiers can
@@ -1270,7 +1305,7 @@
node identifiers can be omitted. If omitted, the thread ID
defaults to <c>t0</c>, the core ID defaults to <c>c0</c>,
the processor ID defaults to <c>p0</c>, and the node ID is
- left undefined. Either each logical processor must
+ left undefined. Either each logical processor must
belong to only one NUMA node, or no logical
processors must belong to any NUMA nodes.</p>
<p>Both increasing and decreasing <c><![CDATA[<IdRange>]]></c>s
@@ -1358,8 +1393,8 @@
how the real CPU topology looks like is likely to
decrease the performance of the runtime system.</p>
<p>For more information, see
- <seealso marker="erlang#system_info_cpu_topology">
- <c>erlang:system_info(cpu_topology)</c></seealso>.</p>
+ <seeerl marker="erlang#system_info_cpu_topology">
+ <c>erlang:system_info(cpu_topology)</c></seeerl>.</p>
</item>
<tag><marker id="+sfwi"/><c>+sfwi Interval</c></tag>
<item>
@@ -1371,8 +1406,8 @@
<note>
<p>This feature has been introduced as a temporary workaround
for long-executing native code, and native code that does not
- bump reductions properly in OTP. When these bugs have be fixed,
- this flag will be removed.</p>
+ bump reductions properly in OTP. When these bugs have been
+ fixed, this flag will be removed.</p>
</note>
</item>
<tag><marker id="+spp"/><c>+spp Bool</c></tag>
@@ -1383,13 +1418,13 @@
virtual machine tries to perform port tasks immediately,
improving latency at the expense of parallelism. Default to
<c>false</c>. The default used can be inspected in runtime by
- calling <seealso marker="erlang#system_info_port_parallelism">
- <c>erlang:system_info(port_parallelism)</c></seealso>.
+ calling <seeerl marker="erlang#system_info_port_parallelism">
+ <c>erlang:system_info(port_parallelism)</c></seeerl>.
The default can be overridden on port creation by passing option
- <seealso marker="erlang#open_port_parallelism">
- <c>parallelism</c></seealso> to
- <seealso marker="erlang#open_port/2">
- <c>erlang:open_port/2</c></seealso></p>.
+ <seeerl marker="erlang#open_port_parallelism">
+ <c>parallelism</c></seeerl> to
+ <seemfa marker="erlang#open_port/2">
+ <c>erlang:open_port/2</c></seemfa>.</p>
</item>
<tag><marker id="sched_thread_stack_size"/>
<c><![CDATA[+sss size]]></c></tag>
@@ -1415,15 +1450,15 @@
<tag><marker id="+stbt"/><c>+stbt BindType</c></tag>
<item>
<p>Tries to set the scheduler bind type. The same as flag
- <seealso marker="#+sbt"><c>+sbt</c></seealso> except
+ <seecom marker="#+sbt"><c>+sbt</c></seecom> except
how some errors are handled. For more information, see
- <seealso marker="#+sbt"><c>+sbt</c></seealso>.</p>
+ <seecom marker="#+sbt"><c>+sbt</c></seecom>.</p>
</item>
<tag><marker id="+sub"/><c>+sub true|false</c></tag>
<item>
<p>Enables or disables
- <seealso marker="erts:erlang#statistics_scheduler_wall_time">
- scheduler utilization</seealso> balancing of load. By default
+ <seeerl marker="erts:erlang#statistics_scheduler_wall_time">
+ scheduler utilization</seeerl> balancing of load. By default
scheduler utilization balancing is disabled and instead scheduler
compaction of load is enabled, which strives for a load
distribution that causes as many scheduler threads as possible
@@ -1434,8 +1469,8 @@
<p><c>+sub true</c> is only supported on systems where the runtime
system detects and uses a monotonically increasing high-resolution
clock. On other systems, the runtime system fails to start.</p>
- <p><c>+sub true</c> implies <seealso marker="#+scl">
- <c>+scl false</c></seealso>. The difference between
+ <p><c>+sub true</c> implies <seecom marker="#+scl">
+ <c>+scl false</c></seecom>. The difference between
<c>+sub true</c> and <c>+scl false</c> is that <c>+scl false</c>
does not try to balance the scheduler utilization.</p>
</item>
@@ -1483,7 +1518,7 @@
<tag><marker id="+swtdcpu"/>
<c>+swtdcpu very_low|low|medium|high|very_high</c></tag>
<item>
- <p>As <seealso marker="#+swt"><c>+swt</c></seealso> but
+ <p>As <seecom marker="#+swt"><c>+swt</c></seecom> but
affects dirty CPU schedulers. Defaults to <c>medium</c>.</p>
<note>
<p>This flag can be removed or changed at any time
@@ -1493,7 +1528,7 @@
<tag><marker id="+swtdio"/>
<c>+swtdio very_low|low|medium|high|very_high</c></tag>
<item>
- <p>As <seealso marker="#+swt"><c>+swt</c></seealso> but affects
+ <p>As <seecom marker="#+swt"><c>+swt</c></seecom> but affects
dirty IO schedulers. Defaults to <c>medium</c>.</p>
<note>
<p>This flag can be removed or changed at any time
@@ -1557,8 +1592,8 @@
information reports (<c><![CDATA[+W i]]></c>). Defaults to warnings.
The current mapping can be retrieved using
<c><![CDATA[error_logger:warning_map/0]]></c>. For more information,
- see <seealso marker="kernel:error_logger#warning_map/0">
- <c>error_logger:warning_map/0</c></seealso> in Kernel.</p>
+ see <seemfa marker="kernel:error_logger#warning_map/0">
+ <c>error_logger:warning_map/0</c></seemfa> in Kernel.</p>
</item>
<tag><c><![CDATA[+zFlag Value]]></c></tag>
<item>
@@ -1567,8 +1602,7 @@
<tag><marker id="+zdbbl"/><c>+zdbbl size</c></tag>
<item>
<p>Sets the distribution buffer busy limit
- (<seealso marker="erlang#system_info_dist_buf_busy_limit">
- <c>dist_buf_busy_limit</c></seealso>)
+ (<seeerl marker="erlang#system_info_dist_buf_busy_limit"><c>dist_buf_busy_limit</c></seeerl>)
in kilobytes. Valid range is 1-2097151. Defaults to 1024.</p>
<p>A larger buffer limit allows processes to buffer
more outgoing messages over the distribution. When the
@@ -1581,8 +1615,7 @@
<tag><marker id="+zdntgc"/><c>+zdntgc time</c></tag>
<item>
<p>Sets the delayed node table garbage collection time
- (<seealso marker="erlang#system_info_delayed_node_table_gc">
- <c>delayed_node_table_gc</c></seealso>)
+ (<seeerl marker="erlang#system_info_delayed_node_table_gc"><c>delayed_node_table_gc</c></seeerl>)
in seconds. Valid values are either <c>infinity</c> or
an integer in the range 0-100000000. Defaults to 60.</p>
<p>Node table entries that are not referred linger
@@ -1638,7 +1671,7 @@
passed to <c>erl</c> and <c>ERL_CRASH_DUMP_SECONDS</c> is not set.
</item>
</taglist>
- <p>See also <seealso marker="kernel:heart"><c>heart(3)</c></seealso>.</p>
+ <p>See also <seeerl marker="kernel:heart"><c>heart(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[ERL_CRASH_DUMP_BYTES]]></c></tag>
<item>
@@ -1674,19 +1707,19 @@
<item>
<p>Contains a list of additional library directories that the code
server searches for applications and adds to the code path; see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</item>
<tag><c><![CDATA[ERL_EPMD_ADDRESS]]></c></tag>
<item>
<p>Can be set to a comma-separated list of IP addresses, in which case
- the <seealso marker="epmd"><c>epmd</c></seealso> daemon listens only
+ the <seecom marker="epmd"><c>epmd</c></seecom> daemon listens only
on the specified address(es) and on the loopback address (which is
implicitly added to the list if it has not been specified).</p>
</item>
<tag><c><![CDATA[ERL_EPMD_PORT]]></c></tag>
<item>
<p>Can contain the port number to use when communicating with
- <seealso marker="epmd"><c>epmd</c></seealso>. The default port works
+ <seecom marker="epmd"><c>epmd</c></seecom>. The default port works
fine in most cases. A different port can be specified
to allow nodes of independent clusters to co-exist on the same host.
All nodes in a cluster must use the same <c>epmd</c> port number.</p>
@@ -1722,7 +1755,7 @@
<tag>The <c>.erlang</c> startup file</tag>
<item>
<p>When Erlang/OTP is started, the system searches for a file named
- <c>.erlang</c> in the user's home directory.</p>
+ <c>.erlang</c> in the user&apos;s home directory.</p>
<p>If an <c>.erlang</c> file is found, it is assumed to contain valid
Erlang expressions. These expressions are evaluated as if they were
input to the shell.</p>
@@ -1752,25 +1785,25 @@ code:load_abs("..../user_default"). ]]></code>
of <c>user_default</c> is defined, the Erlang/OTP environment can be
customized. More powerful changes can be made by supplying
command-line arguments in the startup script <c>erl</c>. For more
- information, see <seealso marker="init"><c>init(3)</c></seealso>.</p>
+ information, see <seeerl marker="init"><c>init(3)</c></seeerl>.</p>
</item>
</taglist>
</section>
<section>
<title>See Also</title>
- <p><seealso marker="epmd"><c>epmd(1)</c></seealso>,
- <seealso marker="erl_prim_loader"><c>erl_prim_loader(3)</c></seealso>,
- <seealso marker="erts_alloc"><c>erts_alloc(3)</c></seealso>,
- <seealso marker="init"><c>init(3)</c></seealso>,
- <seealso marker="kernel:application">
- <c>application(3)</c></seealso>,
- <seealso marker="kernel:auth"><c>auth(3)</c></seealso>,
- <seealso marker="kernel:code"><c>code(3)</c></seealso>,
- <seealso marker="kernel:erl_boot_server">
- <c>erl_boot_server(3)</c></seealso>,
- <seealso marker="kernel:heart"><c>heart(3)</c></seealso>,
- <seealso marker="kernel:net_kernel"><c>net_kernel(3)</c></seealso>,
- <seealso marker="tools:make"><c>make(3)</c></seealso></p>
+ <p><seecom marker="epmd"><c>epmd(1)</c></seecom>,
+ <seeerl marker="erl_prim_loader"><c>erl_prim_loader(3)</c></seeerl>,
+ <seecref marker="erts_alloc"><c>erts_alloc(3)</c></seecref>,
+ <seeerl marker="init"><c>init(3)</c></seeerl>,
+ <seeerl marker="kernel:application">
+ <c>application(3)</c></seeerl>,
+ <seeerl marker="kernel:auth"><c>auth(3)</c></seeerl>,
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>,
+ <seeerl marker="kernel:erl_boot_server">
+ <c>erl_boot_server(3)</c></seeerl>,
+ <seeerl marker="kernel:heart"><c>heart(3)</c></seeerl>,
+ <seeerl marker="kernel:net_kernel"><c>net_kernel(3)</c></seeerl>,
+ <seeerl marker="tools:make"><c>make(3)</c></seeerl></p>
</section>
</comref>
diff --git a/erts/doc/src/erl_dist_protocol.xml b/erts/doc/src/erl_dist_protocol.xml
index 0d50c31b38..ae3de0a31c 100644
--- a/erts/doc/src/erl_dist_protocol.xml
+++ b/erts/doc/src/erl_dist_protocol.xml
@@ -47,8 +47,8 @@
<p>Handshake, interchange node name, and authenticate (2)</p>
</item>
<item>
- <p>Authentication (done by <seealso marker="kernel:net_kernel">
- <c>net_kernel(3)</c></seealso>) (3)</p>
+ <p>Authentication (done by <seeerl marker="kernel:net_kernel">
+ <c>net_kernel(3)</c></seeerl>) (3)</p>
</item>
<item>
<p>Connected (4)</p>
@@ -75,8 +75,8 @@
The Erlang Distribution protocol is not by itself secure and does not
aim to be so. In order to get secure distribution the distributed nodes
should be configured to use distribution over tls.
- See the <seealso marker="ssl:ssl_distribution">
- Using SSL for Erlang Distribution</seealso> User's Guide
+ See the <seeguide marker="ssl:ssl_distribution">
+ Using SSL for Erlang Distribution</seeguide> User's Guide
for details on how to setup a secure distributed node.
</p>
</warning>
@@ -109,7 +109,8 @@
<title>Register a Node in EPMD</title>
<p>When a distributed node is started it registers itself in the EPMD.
The message <c>ALIVE2_REQ</c> described below is sent from the node to
- the EPMD. The response from the EPMD is <c>ALIVE2_RESP</c>.</p>
+ the EPMD. The response from the EPMD is <c>ALIVE2_X_RESP</c> (or
+ <c>ALIVE2_RESP</c>).</p>
<table align="left">
<row>
@@ -154,13 +155,13 @@
</item>
<tag><c>HighestVersion</c></tag>
<item>
- <p>The highest distribution version that this node can handle.
- The value in Erlang/OTP R6B and later is 5.</p>
+ <p>The highest distribution protocol version this node can handle.
+ The value in OTP 23 and later is 6. Older nodes only support version 5.</p>
</item>
<tag><c>LowestVersion</c></tag>
<item>
<p>The lowest distribution version that this node can handle.
- The value in Erlang/OTP R6B and later is 5.</p>
+ Should be 5 to support connections to nodes older than OTP 23.</p>
</item>
<tag><c>Nlen</c></tag>
<item>
@@ -184,7 +185,24 @@
node is a distributed node. When the connection is closed,
the node is automatically unregistered from the EPMD.</p>
- <p>The response message <c>ALIVE2_RESP</c> is as follows:</p>
+ <p>The response message is either <c>ALIVE2_X_RESP</c> or
+ <c>ALIVE2_RESP</c> depending on distribution version. If both the node
+ and EPMD support distribution version 6 then the response is
+ <c>ALIVE2_X_RESP</c> otherwise it is the older <c>ALIVE2_RESP</c>:</p>
+
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">1</cell>
+ <cell align="center">4</cell>
+ </row>
+ <row>
+ <cell align="center"><c>118</c></cell>
+ <cell align="center"><c>Result</c></cell>
+ <cell align="center"><c>Creation</c></cell>
+ </row>
+ <tcaption>ALIVE2_X_RESP (118) with 32 bit creation</tcaption>
+ </table>
<table align="left">
<row>
@@ -197,7 +215,7 @@
<cell align="center"><c>Result</c></cell>
<cell align="center"><c>Creation</c></cell>
</row>
- <tcaption>ALIVE2_RESP (121)</tcaption>
+ <tcaption>ALIVE2_RESP (121) with 16-bit creation</tcaption>
</table>
<p>Result = 0 -> ok, result &gt; 0 -> error.</p>
@@ -283,8 +301,8 @@
<section>
<title>Get All Registered Names from EPMD</title>
<p>This request is used through the Erlang function
- <seealso marker="kernel:net_adm#names/1,2">
- <c>net_adm:names/1,2</c></seealso>. A TCP connection is opened
+ <seemfa marker="kernel:net_adm#names/1">
+ <c>net_adm:names/1,2</c></seemfa>. A TCP connection is opened
to the EPMD and this request is sent.</p>
<table align="left">
@@ -412,9 +430,6 @@ io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
<p>where n = <c>Length</c> - 1.</p>
- <p>The current implementation of Erlang does not care if the connection
- to the EPMD is broken.</p>
-
<p>The response for a <c>STOP_REQ</c> is as follows:</p>
<table align="left">
@@ -531,8 +546,14 @@ io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
<section>
<marker id="distribution_handshake"/>
<title>Distribution Handshake</title>
- <p>This section describes the distribution handshake protocol introduced
- in Erlang/OTP R6. The handshake has remained almost the same since then.</p>
+ <p>
+ This section describes the distribution handshake protocol used between
+ nodes to establishing a connection. The protocol was introduced in
+ Erlang/OTP R6 and has remained unchanged until OTP 23. The changes made in
+ OTP 23 were designed to be compatible with the older protocol
+ version. That is an old node can still connect toward a new node and vice
+ versa.
+ </p>
<section>
<title>General</title>
@@ -580,7 +601,7 @@ io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
<p>Every message in the handshake starts with a 16-bit big-endian integer,
which contains the message length (not counting the two initial bytes).
In Erlang this corresponds to option <c>{packet, 2}</c> in
- <seealso marker="kernel:gen_tcp"><c>gen_tcp(3)</c></seealso>.
+ <seeerl marker="kernel:gen_tcp"><c>gen_tcp(3)</c></seeerl>.
Notice that after the handshake, the distribution switches to 4 byte
packet headers.</p>
</section>
@@ -599,24 +620,91 @@ io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
<tag>2) <c>send_name</c>/<c>receive_name</c></tag>
<item>
<p><c>A</c> sends an initial identification to <c>B</c>, which
- receives the message. The message looks as follows (every "square"
- is one byte and the packet header is removed):</p>
- <pre>
-+---+--------+--------+-----+-----+-----+-----+-----+-----+-...-+-----+
-|'n'|Version0|Version1|Flag0|Flag1|Flag2|Flag3|Name0|Name1| ... |NameN|
-+---+--------+--------+-----+-----+-----+-----+-----+-----+-... +-----+</pre>
- <p>'n' is the message tag. 'Version0' and 'Version1' is the
- distribution version selected by <c>A</c>, based on information
- from the EPMD. (16-bit big-endian) 'Flag0' ... 'Flag3' are
- capability flags, the capabilities are defined in
- <c>$ERL_TOP/lib/kernel/include/dist.hrl</c>. (32-bit big-endian)
- 'Name0' ... 'NameN' is the full node name of <c>A</c>, as a string
- of bytes (the packet length denotes how long it is).</p>
+ receives the message. The message can have two different formats
+ which looks as follows (the packet headers are removed):
+ </p>
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">2</cell>
+ <cell align="center">4</cell>
+ <cell align="center">Nlen</cell>
+ </row>
+ <row>
+ <cell align="center"><c>'n'</c></cell>
+ <cell align="center"><c>Version=5</c></cell>
+ <cell align="center"><c>Flags</c></cell>
+ <cell align="center"><c>Name</c></cell>
+ </row>
+ <tcaption>Old send_name ('n') for protocol version 5</tcaption>
+ </table>
+
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">8</cell>
+ <cell align="center">4</cell>
+ <cell align="center">2</cell>
+ <cell align="center">Nlen</cell>
+ </row>
+ <row>
+ <cell align="center"><c>'N'</c></cell>
+ <cell align="center"><c>Flags</c></cell>
+ <cell align="center"><c>Creation</c></cell>
+ <cell align="center"><c>Nlen</c></cell>
+ <cell align="center"><c>Name</c></cell>
+ </row>
+ <tcaption>New send_name ('N') for protocol version 6</tcaption>
+ </table>
+
+ <p>
+ The old <c>send_name</c> format is sent from nodes only supporting version 5
+ or to nodes that might only support version 5. The <c>Version</c> is
+ a 16-bit big endian integer and <em>must</em> always have the value 5, even
+ if node <c>A</c> supports version 6. <c>Flags</c> are the
+ <seeguide marker="#dflags">capability flags</seeguide>
+ of node <c>A</c> in 32-bit big endian. The flag bit
+ <seeguide marker="#DFLAG_HANDSHAKE_23"><c>DFLAG_HANDSHAKE_23</c></seeguide>
+ should be set if node <c>A</c> supports version 6.
+ <c>Name</c> is the full node name of <c>A</c>, as a string of bytes
+ (the packet length denotes how long it is).
+ </p>
+ <p>
+ The new <c>send_name</c> is only sent from nodes supporting version 6 to
+ nodes known to support version 6. <c>Flags</c> are the
+ <seeguide marker="#dflags">capability flags</seeguide> of node
+ <c>A</c> in 64-bit big endian. The flag bit
+ <seeguide marker="#DFLAG_HANDSHAKE_23"><c>DFLAG_HANDSHAKE_23</c></seeguide>
+ must always be set. <c>Creation</c> is the node incarnation
+ identifier used by node <c>A</c> to create its pids, ports and
+ references. <c>Name</c> is the full node name of <c>A</c>, as a
+ string of bytes. <c>Nlen</c> is the byte length of the node name in
+ 16-bit big endian. Any extra data after the node <c>Name</c> must be
+ accepted and ignored.
+ </p>
</item>
<tag>3) <c>recv_status</c>/<c>send_status</c></tag>
<item>
- <p><c>B</c> sends a status message to <c>A</c>, which indicates if the
- connection is allowed. The following status codes are defined:</p>
+ <p>
+ <c>B</c> sends a status message to <c>A</c>, which indicates if the
+ connection is allowed.
+ </p>
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">Slen</cell>
+ </row>
+ <row>
+ <cell align="center"><c>'s'</c></cell>
+ <cell align="center"><c>Status</c></cell>
+ </row>
+ <tcaption>The format of the status message</tcaption>
+ </table>
+ <p>
+ 's' is the message tag. <c>Status</c> is the status code as a
+ string (not null terminated). The following status codes are
+ defined:
+ </p>
<taglist>
<tag><c>ok</c></tag>
<item>
@@ -647,15 +735,37 @@ io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
breakdown of a previous node with this name has not yet reached
node <c>B</c>. See step 3B below.</p>
</item>
+ <tag><c>named:</c></tag>
+ <item><p>
+ The handshake willl continue, but <c>A</c> requested a dynamic
+ node name by setting flag <seeguide marker="#DFLAG_NAME_ME">
+ <c>DFLAG_NAME_ME</c></seeguide>. The dynamic node name of
+ <c>A</c> is supplied at the end of the status message from
+ <c>B</c>.</p>
+ </item>
</taglist>
- <p>The format of the status message is as follows:</p>
- <pre>
-+---+-------+-------+-...-+-------+
-|'s'|Status0|Status1| ... |StatusN|
-+---+-------+-------+-...-+-------+</pre>
- <p>'s' is the message tag. 'Status0' ... 'StatusN' is the status as a
- string (not terminated).</p>
- </item>
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">Slen=6</cell>
+ <cell align="center">2</cell>
+ <cell align="center">Nlen</cell>
+ </row>
+ <row>
+ <cell align="center"><c>'s'</c></cell>
+ <cell align="center"><c>Status='named:'</c></cell>
+ <cell align="center"><c>Nlen</c></cell>
+ <cell align="center"><c>Name</c></cell>
+ </row>
+ <tcaption>The format of the 'named:' status message</tcaption>
+ </table>
+ <p>
+ <c>Name</c> is the full dynamic node name of <c>A</c>, as a
+ string of bytes. <c>Nlen</c> is the byte length of the node name in
+ 16-bit big endian. Any extra data after the node <c>Name</c> must be
+ accepted and ignored.
+ </p>
+ </item>
<tag>3B) <c>send_status</c>/<c>recv_status</c></tag>
<item>
<p>If status was <c>alive</c>, node <c>A</c> answers with another
@@ -670,39 +780,136 @@ io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
handshake continues with <c>B</c> sending <c>A</c> another message,
the challenge. The challenge contains the same type of information
as the "name" message initially sent from <c>A</c> to <c>B</c>, plus
- a 32-bit challenge:</p>
- <pre>
-+---+--------+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-...-+-----+
-|'n'|Version0|Version1|Flag0|Flag1|Flag2|Flag3|Chal0|Chal1|Chal2|Chal3|Name0|Name1| ... |NameN|
-+---+--------+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-... +-----+</pre>
- <p>'Chal0' ... 'Chal3' is the challenge as a 32-bit big-endian integer
- and the other fields are <c>B</c>'s version, flags, and full node
- name.</p>
+ a 32-bit challenge. The challenge message can have two different
+ formats:
+ </p>
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">2</cell>
+ <cell align="center">4</cell>
+ <cell align="center">4</cell>
+ <cell align="center">Nlen</cell>
+ </row>
+ <row>
+ <cell align="center"><c>'n'</c></cell>
+ <cell align="center"><c>Version=5</c></cell>
+ <cell align="center"><c>Flags</c></cell>
+ <cell align="center"><c>Challenge</c></cell>
+ <cell align="center"><c>Name</c></cell>
+ </row>
+ <tcaption>The old challenge message format (version 5)</tcaption>
+ </table>
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">8</cell>
+ <cell align="center">4</cell>
+ <cell align="center">4</cell>
+ <cell align="center">2</cell>
+ <cell align="center">Nlen</cell>
+ </row>
+ <row>
+ <cell align="center"><c>'N'</c></cell>
+ <cell align="center"><c>Flags</c></cell>
+ <cell align="center"><c>Challenge</c></cell>
+ <cell align="center"><c>Creation</c></cell>
+ <cell align="center"><c>Nlen</c></cell>
+ <cell align="center"><c>Name</c></cell>
+ </row>
+ <tcaption>The new challenge message format (version 6)</tcaption>
+ </table>
+ <p>
+ The old challenge message is sent from old <c>B</c> nodes
+ (supporting only version 5) or if node <c>A</c> had not capability flag
+ <seeguide marker="#DFLAG_HANDSHAKE_23"><c>DFLAG_HANDSHAKE_23</c></seeguide>
+ set. The <c>Version</c> is a 16-bit big endian integer and
+ <c>must</c> always have the value 5.
+ </p>
+ <p>
+ The new challenge message is sent from new <c>B</c> nodes if node
+ <c>A</c> had capability flag <seeguide marker="#DFLAG_HANDSHAKE_23">
+ <c>DFLAG_HANDSHAKE_23</c></seeguide> set. Any extra data after the
+ node <c>Name</c> must be accepted and ignored.
+ </p>
+ <p>
+ <c>Challenge</c> is a 32-bit big-endian integer. The other fields
+ are node <c>B</c>'s flags, creation and full node name, similar to
+ the <c>send_name</c> message.
+ </p>
</item>
+
+ <tag>4B) <c>send_complement</c>/<c>recv_complement</c></tag>
+ <item>
+ <p>
+ The complement message, from <c>A</c> to <c>B</c>, is only sent if
+ node <c>A</c> initially sent an old name message and received back a
+ new challenge message from node <c>B</c>. It contains complementary
+ information missing in the initial old name message from node <c>A</c>.
+ </p>
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">4</cell>
+ <cell align="center">4</cell>
+ </row>
+ <row>
+ <cell align="center"><c>'c'</c></cell>
+ <cell align="center"><c>FlagsHigh</c></cell>
+ <cell align="center"><c>Creation</c></cell>
+ </row>
+ <tcaption>The complement message</tcaption>
+ </table>
+ <p>
+ <c>FlagsHigh</c> are the high capability flags (bit 33-64) of node
+ <c>A</c> as a 32-bit big endian integer. <c>Creation</c> is the
+ incarnation identifier of node <c>A</c>.
+ </p>
+ </item>
+
<tag>5) <c>send_challenge_reply</c>/<c>recv_challenge_reply</c></tag>
<item>
<p>Now <c>A</c> has generated a digest and its own challenge. Those
are sent together in a package to <c>B</c>:</p>
- <pre>
-+---+-----+-----+-----+-----+-----+-----+-----+-----+-...-+------+
-|'r'|Chal0|Chal1|Chal2|Chal3|Dige0|Dige1|Dige2|Dige3| ... |Dige15|
-+---+-----+-----+-----+-----+-----+-----+-----+-----+-...-+------+</pre>
- <p>'r' is the tag. 'Chal0' ... 'Chal3' is <c>A</c>'s challenge for
- <c>B</c> to handle. 'Dige0' ... 'Dige15' is the digest that <c>A</c>
- constructed from the challenge <c>B</c> sent in the previous
- step.</p>
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">4</cell>
+ <cell align="center">16</cell>
+ </row>
+ <row>
+ <cell align="center"><c>'r'</c></cell>
+ <cell align="center"><c>Challenge</c></cell>
+ <cell align="center"><c>Digest</c></cell>
+ </row>
+ <tcaption>The challenge_reply message</tcaption>
+ </table>
+ <p>
+ <c>Challenge</c> is <c>A</c>'s challenge for <c>B</c> to
+ handle. <c>Digest</c> is the MD5 digest that <c>A</c> constructed
+ from the challenge <c>B</c> sent in the previous step.
+ </p>
</item>
<tag>6) <c>recv_challenge_ack</c>/<c>send_challenge_ack</c></tag>
<item>
<p><c>B</c> checks that the digest received from <c>A</c> is correct
and generates a digest from the challenge received from <c>A</c>.
The digest is then sent to <c>A</c>. The message is as follows:</p>
- <pre>
-+---+-----+-----+-----+-----+-...-+------+
-|'a'|Dige0|Dige1|Dige2|Dige3| ... |Dige15|
-+---+-----+-----+-----+-----+-...-+------+</pre>
- <p>'a' is the tag. 'Dige0' ... 'Dige15' is the digest calculated by
- <c>B</c> for <c>A</c>'s challenge.</p>
+ <table align="left">
+ <row>
+ <cell align="center">1</cell>
+ <cell align="center">16</cell>
+ </row>
+ <row>
+ <cell align="center"><c>'a'</c></cell>
+ <cell align="center"><c>Digest</c></cell>
+ </row>
+ <tcaption>The challenge_ack message</tcaption>
+ </table>
+ <p>
+ <c>Digest</c> is the digest calculated by <c>B</c> for <c>A</c>'s
+ challenge.
+ </p>
</item>
<tag>7) check</tag>
<item>
@@ -728,10 +935,15 @@ recv_status
(if status was 'alive'
send_status - - - - - - - - - - - - - - - - - -&gt;
recv_status)
- ChB = gen_challenge()
- (ChB)
+
+ (ChB) ChB = gen_challenge()
&lt;---------------------------------------------- send_challenge
recv_challenge
+
+(if old send_name and new recv_challenge
+ send_complement - - - - - - - - - - - - - - - -&gt;
+ recv_complement)
+
ChA = gen_challenge(),
OCA = out_cookie(B),
DiA = gen_digest(ChB, OCA)
@@ -759,7 +971,11 @@ DiB == gen_digest(ChA, ICA)?
<section>
<marker id="dflags"/>
<title>Distribution Flags</title>
- <p>The following capability flags are defined:</p>
+ <p>Early in the distribution handshake the two participating nodes
+ exchange capability flags. This is done in order to determine how the
+ communication between the two nodes should be performed. The intersection
+ of the capabilities presented by the two nodes defines the capabilities
+ that will be used. The following capability flags are defined:</p>
<taglist>
<tag><c>-define(DFLAG_PUBLISHED,16#1).</c></tag>
<item>
@@ -793,7 +1009,8 @@ DiB == gen_digest(ChA, ICA)?
</item>
<tag><c>-define(DFLAG_NEW_FUN_TAGS,16#80).</c></tag>
<item>
- <p>The node understand new fun tags.</p>
+ <p>The node understands the <seeguide marker="erl_ext_dist#NEW_FUN_EXT">
+ <c>NEW_FUN_EXT</c></seeguide> tag.</p>
</item>
<tag><c>-define(DFLAG_EXTENDED_PIDS_PORTS,16#100).</c></tag>
<item>
@@ -802,13 +1019,18 @@ DiB == gen_digest(ChA, ICA)?
</item>
<tag><c>-define(DFLAG_EXPORT_PTR_TAG,16#200).</c></tag>
<item>
+ <p>The node understands the <seeguide marker="erl_ext_dist#EXPORT_EXT">
+ <c>EXPORT_EXT</c></seeguide> tag.</p>
</item>
<tag><c>-define(DFLAG_BIT_BINARIES,16#400).</c></tag>
<item>
+ <p>The node understands the <seeguide marker="erl_ext_dist#BIT_BINARY_EXT">
+ <c>BIT_BINARY_EXT</c></seeguide> tag.</p>
</item>
<tag><c>-define(DFLAG_NEW_FLOATS,16#800).</c></tag>
<item>
- <p>The node understands new float format.</p>
+ <p>The node understands the <seeguide marker="erl_ext_dist#NEW_FLOAT_EXT">
+ <c>NEW_FLOAT_EXT</c></seeguide> tag.</p>
</item>
<tag><c>-define(DFLAG_UNICODE_IO,16#1000).</c></tag>
<item>
@@ -817,27 +1039,37 @@ DiB == gen_digest(ChA, ICA)?
<item>
<p>The node implements atom cache in distribution header.</p>
</item>
- <tag><c>-define(DFLAG_SMALL_ATOM_TAGS, 16#4000).</c></tag>
+ <tag><marker id="DFLAG_SMALL_ATOM_TAGS"/><c>-define(DFLAG_SMALL_ATOM_TAGS, 16#4000).</c></tag>
<item>
- <p>The node understand the <c>SMALL_ATOM_EXT</c> tag.</p>
+ <p>The node understands the <seeguide marker="erl_ext_dist#SMALL_ATOM_EXT">
+ <c>SMALL_ATOM_EXT</c></seeguide> tag.</p>
</item>
- <tag><c>-define(DFLAG_UTF8_ATOMS, 16#10000).</c></tag>
+ <tag><marker id="DFLAG_UTF8_ATOMS"/><c>-define(DFLAG_UTF8_ATOMS, 16#10000).</c></tag>
<item>
- <p>The node understand UTF-8 encoded atoms.</p>
+ <p>The node understands UTF-8 atoms encoded with
+ <seeguide marker="erl_ext_dist#ATOM_UTF8_EXT">
+ <c>ATOM_UTF8_EXT</c></seeguide> and
+ <seeguide marker="erl_ext_dist#SMALL_ATOM_UTF8_EXT">
+ <c>SMALL ATOM_UTF8_EXT</c></seeguide>.</p>
</item>
<tag><c>-define(DFLAG_MAP_TAG, 16#20000).</c></tag>
<item>
- <p>The node understand the map tag.</p>
+ <p>The node understands the map tag
+ <seeguide marker="erl_ext_dist#MAP_EXT"><c>MAP_EXT</c></seeguide>.</p>
</item>
- <tag><c>-define(DFLAG_BIG_CREATION, 16#40000).</c></tag>
+ <tag><marker id="DFLAG_BIG_CREATION"/><c>-define(DFLAG_BIG_CREATION, 16#40000).</c></tag>
<item>
- <p>The node understand big node creation.</p>
+ <p>The node understands big node creation tags
+ <seeguide marker="erl_ext_dist#NEW_PID_EXT"><c>NEW_PID_EXT</c></seeguide>,
+ <seeguide marker="erl_ext_dist#NEW_PORT_EXT"><c>NEW_PORT_EXT</c></seeguide> and
+ <seeguide marker="erl_ext_dist#NEWER_REFERENCE_EXT"><c>NEWER_REFERENCE_EXT</c></seeguide>.
+ </p>
</item>
<tag><c>-define(DFLAG_SEND_SENDER, 16#80000).</c></tag>
<item>
<p>
Use the <c>SEND_SENDER</c>
- <seealso marker="#control_message">control message</seealso>
+ <seeguide marker="#control_message">control message</seeguide>
instead of the <c>SEND</c> control message and use the
<c>SEND_SENDER_TT</c> control message instead
of the <c>SEND_TT</c> control message.
@@ -852,14 +1084,41 @@ DiB == gen_digest(ChA, ICA)?
<p>Use the <c>PAYLOAD_EXIT</c>, <c>PAYLOAD_EXIT_TT</c>,
<c>PAYLOAD_EXIT2</c>, <c>PAYLOAD_EXIT2_TT</c>
and <c>PAYLOAD_MONITOR_P_EXIT</c>
- <seealso marker="#control_message">control message</seealso>s
+ <seeguide marker="#control_message">control message</seeguide>s
instead of the non-PAYLOAD variants.</p>
</item>
- <tag><c>-define(DFLAG_FRAGMENTS, 16#800000).</c></tag>
+ <tag><marker id="DFLAG_FRAGMENTS"/><c>-define(DFLAG_FRAGMENTS, 16#800000).</c></tag>
<item>
- <p>Use <seealso marker="erl_ext_dist#fragments">fragmented</seealso>
+ <p>Use <seeguide marker="erl_ext_dist#fragments">fragmented</seeguide>
distribution messages to send large messages.</p>
</item>
+ <tag><marker id="DFLAG_HANDSHAKE_23"/><c>-define(DFLAG_HANDSHAKE_23, 16#1000000).</c></tag>
+ <item>
+ <p>The node supports the new connection setup handshake (version 6)
+ introduced in OTP 23.</p>
+ </item>
+ <tag><marker id="DFLAG_UNLINK_ID"/><c>-define(DFLAG_UNLINK_ID, 16#2000000).</c></tag>
+ <item>
+ <p>Use the <seeguide marker="#new_link_protocol">new link protocol</seeguide>.</p>
+ <note><p>This flag will become mandatory in OTP 26.</p></note>
+ <p>Unless both nodes have set the <c>DFLAG_UNLINK_ID</c> flag, the
+ <seeguide marker="#old_link_protocol">old link protocol</seeguide>
+ will be used as a fallback.</p>
+ </item>
+ <tag><marker id="DFLAG_SPAWN"/><c>-define(DFLAG_SPAWN, (1 bsl 32)).</c></tag>
+ <item>
+ <p>Set if the <seeguide marker="#SPAWN_REQUEST"><c>SPAWN_REQUEST</c></seeguide>,
+ <seeguide marker="#SPAWN_REQUEST_TT"><c>SPAWN_REQUEST_TT</c></seeguide>,
+ <seeguide marker="#SPAWN_REPLY"><c>SPAWN_REPLY</c></seeguide>,
+ <seeguide marker="#SPAWN_REPLY_TT"><c>SPAWN_REPLY_TT</c></seeguide>
+ control messages are supported.</p>
+ </item>
+ <tag><marker id="DFLAG_NAME_ME"/><c>-define(DFLAG_NAME_ME, (1 bsl 33)).</c></tag>
+ <item><p>
+ Dynamic node name. This is not a capability but rather used as a
+ request from the connecting node to receive its node name from the
+ accepting node as part of the handshake.</p>
+ </item>
</taglist>
<p>
There is also function <c>dist_util:strict_order_flags/0</c>
@@ -874,8 +1133,8 @@ DiB == gen_digest(ChA, ICA)?
<title>Protocol between Connected Nodes</title>
<p>Since ERTS 5.7.2 (OTP R13B) the runtime system passes a distribution flag
in the handshake stage that enables the use of a
- <seealso marker="erl_ext_dist#distribution_header">distribution header
- </seealso> on all messages passed. Messages passed between nodes have in
+ <seeguide marker="erl_ext_dist#distribution_header">distribution header
+ </seeguide> on all messages passed. Messages passed between nodes have in
this case the following format:</p>
<table align="left">
@@ -903,9 +1162,9 @@ DiB == gen_digest(ChA, ICA)?
<tag><c>DistributionHeader</c></tag>
<item>
<p>
- <seealso marker="erl_ext_dist#distribution_header">Distribution header
+ <seeguide marker="erl_ext_dist#distribution_header">Distribution header
describing the atom cache and fragmented distribution messages.
- </seealso>
+ </seeguide>
</p>
</item>
<tag><c>ControlMessage</c></tag>
@@ -920,9 +1179,9 @@ DiB == gen_digest(ChA, ICA)?
</item>
</taglist>
- <p>Notice that <seealso marker="erl_ext_dist#overall_format">the version
+ <p>Notice that <seeguide marker="erl_ext_dist#overall_format">the version
number is omitted from the terms that follow a distribution header
- </seealso>.</p>
+ </seeguide>.</p>
<p>Nodes with an ERTS version earlier than 5.7.2 (OTP R13B) does not pass the
distribution flag that enables the distribution header. Messages passed
@@ -971,9 +1230,12 @@ DiB == gen_digest(ChA, ICA)?
which distributed operation it encodes:</p>
<taglist>
- <tag><c>LINK</c></tag>
+ <tag><marker id="LINK"/><c>LINK</c></tag>
<item>
<p><c>{1, FromPid, ToPid}</c></p>
+ <p>This signal is sent by <c>FromPid</c> in order
+ to create a link between <c>FromPid</c> and
+ <c>ToPid</c>.</p>
</item>
<tag><c>SEND</c></tag>
<item>
@@ -986,9 +1248,18 @@ DiB == gen_digest(ChA, ICA)?
<p><c>{3, FromPid, ToPid, Reason}</c></p>
<p>This signal is sent when a link has been broken</p>
</item>
- <tag><c>UNLINK</c></tag>
+ <tag><marker id="UNLINK"/><c>UNLINK</c> (deprecated)</tag>
<item>
<p><c>{4, FromPid, ToPid}</c></p>
+ <p>This signal is sent by <c>FromPid</c> in order to remove
+ a link between <c>FromPid</c> and <c>ToPid</c>, when using the
+ <seeguide marker="#old_link_protocol">old link
+ protocol</seeguide>.</p>
+ <warning><p>This signal has been deprecated and will not
+ be supported in OTP 26. For more information see the
+ documentation of the
+ <seeguide marker="#new_link_protocol">new link protocol</seeguide>.
+ </p></warning>
</item>
<tag><c>NODE_LINK</c></tag>
<item>
@@ -1009,12 +1280,6 @@ DiB == gen_digest(ChA, ICA)?
<p><c>{8, FromPid, ToPid, Reason}</c></p>
<p>This signal is sent by a call to the erlang:exit/2 bif</p>
</item>
- </taglist>
- </section>
-
- <section>
- <title>New Ctrlmessages for distrvsn = 1 (Erlang/OTP R4)</title>
- <taglist>
<tag><c>SEND_TT</c></tag>
<item>
<p><c>{12, Unused, ToPid, TraceToken}</c></p>
@@ -1035,24 +1300,6 @@ DiB == gen_digest(ChA, ICA)?
<item>
<p><c>{18, FromPid, ToPid, TraceToken, Reason}</c></p>
</item>
- </taglist>
- </section>
-
- <section>
- <title>New Ctrlmessages for distrvsn = 2</title>
- <p><c>distrvsn</c> 2 was never used.</p>
- </section>
-
- <section>
- <title>New Ctrlmessages for distrvsn = 3 (Erlang/OTP R5C)</title>
- <p>None, but the version number was increased anyway.</p>
- </section>
-
- <section>
- <title>New Ctrlmessages for distrvsn = 4 (Erlang/OTP R6)</title>
- <p>These are only recognized by Erlang nodes, not by hidden nodes.</p>
-
- <taglist>
<tag><c>MONITOR_P</c></tag>
<item>
<p><c>{19, FromPid, ToProc, Ref}</c>, where
@@ -1074,9 +1321,8 @@ DiB == gen_digest(ChA, ICA)?
<c>Reason</c> = exit reason for the monitored process</p>
</item>
</taglist>
- </section>
- <section>
+ <section>
<title>New Ctrlmessages for Erlang/OTP 21</title>
<taglist>
<tag><c>SEND_SENDER</c></tag>
@@ -1086,7 +1332,7 @@ DiB == gen_digest(ChA, ICA)?
<p>
This control message replaces the <c>SEND</c> control
message and will be sent when the distribution flag
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_SEND_SENDER</c></seealso>
+ <seeguide marker="erl_dist_protocol#dflags"><c>DFLAG_SEND_SENDER</c></seeguide>
has been negotiated in the connection setup handshake.
</p>
<note><p>
@@ -1105,7 +1351,7 @@ DiB == gen_digest(ChA, ICA)?
<p>
This control message replaces the <c>SEND_TT</c> control
message and will be sent when the distribution flag
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_SEND_SENDER</c></seealso>
+ <seeguide marker="erl_dist_protocol#dflags"><c>DFLAG_SEND_SENDER</c></seeguide>
has been negotiated in the connection setup handshake.
</p>
<note><p>
@@ -1137,7 +1383,7 @@ DiB == gen_digest(ChA, ICA)?
<p>
This control message replaces the <c>EXIT</c> control
message and will be sent when the distribution flag
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ <seeguide marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seeguide>
has been negotiated in the connection setup handshake.
</p>
</item>
@@ -1148,7 +1394,7 @@ DiB == gen_digest(ChA, ICA)?
<p>
This control message replaces the <c>EXIT_TT</c> control
message and will be sent when the distribution flag
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ <seeguide marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seeguide>
has been negotiated in the connection setup handshake.
</p>
</item>
@@ -1159,7 +1405,7 @@ DiB == gen_digest(ChA, ICA)?
<p>
This control message replaces the <c>EXIT2</c> control
message and will be sent when the distribution flag
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ <seeguide marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seeguide>
has been negotiated in the connection setup handshake.
</p>
</item>
@@ -1170,7 +1416,7 @@ DiB == gen_digest(ChA, ICA)?
<p>
This control message replaces the <c>EXIT2_TT</c> control
message and will be sent when the distribution flag
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ <seeguide marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seeguide>
has been negotiated in the connection setup handshake.
</p>
</item>
@@ -1181,11 +1427,353 @@ DiB == gen_digest(ChA, ICA)?
<p>
This control message replaces the <c>MONITOR_P_EXIT</c> control
message and will be sent when the distribution flag
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ <seeguide marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seeguide>
has been negotiated in the connection setup handshake.
</p>
</item>
+
</taglist>
</section>
+ <section>
+ <title>New Ctrlmessages for Erlang/OTP 23</title>
+ <taglist>
+ <tag><marker id="SPAWN_REQUEST"/><c>SPAWN_REQUEST</c></tag>
+ <item>
+ <p><c>{29, ReqId, From, GroupLeader, {Module, Function, Arity}, OptList}</c></p>
+ <p>Followed by <c>ArgList</c>.</p>
+ <p>This signal is sent by the
+ <seemfa marker="erlang#spawn_request/5"><c>spawn_request()</c></seemfa> BIF.</p>
+ <taglist>
+ <tag><c>ReqId :: reference()</c></tag>
+ <item><p>Request identifier. Also used as monitor
+ reference in case the <c>monitor</c> option has been
+ passed.</p></item>
+ <tag><c>From :: pid()</c></tag>
+ <item><p>Process identifier of the process making the
+ request. That is, the parent process to be.</p></item>
+ <tag><c>GroupLeader :: pid()</c></tag>
+ <item><p>Process identifier of the group leader of the
+ newly created process.</p></item>
+ <tag><c>{Module :: atom(), Function :: atom(), Arity :: integer() >= 0}</c></tag>
+ <item><p>Entry point for the the new process.</p></item>
+ <tag><c>OptList :: [term()]</c></tag>
+ <item><p>A proper list of spawn options to use when spawning.</p></item>
+ <tag><c>ArgList :: [term()]</c></tag>
+ <item><p>A proper list of arguments to use in the call to the entry point.</p></item>
+ </taglist>
+ <p>
+ Only supported when the
+ <seeguide marker="erl_dist_protocol#DFLAG_SPAWN"><c>DFLAG_SPAWN</c></seeguide>
+ <seeguide marker="erl_dist_protocol#dflags">distribution flag</seeguide>
+ has been passed.
+ </p>
+ </item>
+ <tag><marker id="SPAWN_REQUEST_TT"/><c>SPAWN_REQUEST_TT</c></tag>
+ <item>
+ <p><c>{30, ReqId, From, GroupLeader, {Module, Function, Arity}, OptList, Token}</c></p>
+ <p>Followed by <c>ArgList</c>.</p>
+ <p>Same as <seeguide marker="#SPAWN_REQUEST"><c>SPAWN_REQUEST</c></seeguide>, but also
+ with a sequential trace <c>Token</c>.
+ </p>
+ <p>
+ Only supported when the
+ <seeguide marker="erl_dist_protocol#DFLAG_SPAWN"><c>DFLAG_SPAWN</c></seeguide>
+ <seeguide marker="erl_dist_protocol#dflags">distribution flag</seeguide>
+ has been passed.
+ </p>
+ </item>
+ <tag><marker id="SPAWN_REPLY"/><c>SPAWN_REPLY</c></tag>
+ <item>
+ <p><c>{31, ReqId, To, Flags, Result}</c></p>
+ <p>This signal is sent as a reply to a process previously sending
+ a <seeguide marker="#SPAWN_REQUEST"><c>SPAWN_REQUEST</c></seeguide> signal.</p>
+ <taglist>
+ <tag><c>ReqId :: reference()</c></tag>
+ <item><p>Request identifier. Also used as monitor
+ reference in case the <c>monitor</c> option has been
+ passed.</p></item>
+ <tag><c>To :: pid()</c></tag>
+ <item><p>Process identifier of the process making the
+ spawn request.</p></item>
+ <tag><c>Flags :: integer() >= 0</c></tag>
+ <item><p>A bit flag field of bit flags bitwise or:ed together. Currently the
+ following flags are defined:</p>
+ <taglist>
+ <tag><c>1</c></tag>
+ <item><p>A link between <c>To</c> and <c>Result</c> was set up on
+ the node where <c>Result</c> resides.</p></item>
+ <tag><c>2</c></tag>
+ <item><p>A monitor from <c>To</c> to <c>Result</c> was set up on
+ the node where <c>Result</c> resides.</p></item>
+ </taglist>
+ </item>
+ <tag><c>Result :: pid() | atom()</c></tag>
+ <item><p>Result of the operation. If <c>Result</c> is a process
+ identifier, the operation succeeded and the process identifier
+ is the identifier of the newly created process. If <c>Result</c>
+ is an atom, the operation failed and the atom identifies failure
+ reason.</p></item>
+ </taglist>
+ <p>
+ Only supported when the
+ <seeguide marker="erl_dist_protocol#DFLAG_SPAWN"><c>DFLAG_SPAWN</c></seeguide>
+ <seeguide marker="erl_dist_protocol#dflags">distribution flag</seeguide>
+ has been passed.
+ </p>
+ </item>
+ <tag><marker id="SPAWN_REPLY_TT"/><c>SPAWN_REPLY_TT</c></tag>
+ <item>
+ <p><c>{32, ReqId, To, Flags, Result, Token}</c></p>
+ <p>Same as <seeguide marker="#SPAWN_REPLY"><c>SPAWN_REPLY</c></seeguide>, but also
+ with a sequential trace <c>Token</c>.</p>
+ <p>
+ Only supported when the
+ <seeguide marker="erl_dist_protocol#DFLAG_SPAWN"><c>DFLAG_SPAWN</c></seeguide>
+ <seeguide marker="erl_dist_protocol#dflags">distribution flag</seeguide>
+ has been passed.
+ </p>
+ </item>
+ <tag><marker id="UNLINK_ID"/><c>UNLINK_ID</c></tag>
+ <item>
+ <p><c>{35, Id, FromPid, ToPid}</c></p>
+ <p>This signal is sent by <c>FromPid</c> in order to remove a
+ link between <c>FromPid</c> and <c>ToPid</c>. This unlink signal
+ replaces the <seeguide marker="#UNLINK"><c>UNLINK</c></seeguide>
+ signal. Besides process identifiers of the sender and receiver
+ the <c>UNLINK_ID</c> signal also contains an integer identifier
+ <c>Id</c>. Valid range of <c>Id</c> is <c>[1, (1 bsl 64) - 1]</c>.
+ <c>Id</c> is to be passed back to the sender by the receiver in an
+ <seeguide marker="#UNLINK_ID_ACK"><c>UNLINK_ID_ACK</c></seeguide>
+ signal. <c>Id</c> must uniquely identify the <c>UNLINK_ID</c> signal
+ among all not yet acknowledged <c>UNLINK_ID</c> signals from
+ <c>FromPid</c> to <c>ToPid</c>.</p>
+ <p>
+ This signal is only passed when the
+ <seeguide marker="#new_link_protocol">new link protocol</seeguide>
+ has been negotiated using the
+ <seeguide marker="erl_dist_protocol#DFLAG_UNLINK_ID"><c>DFLAG_UNLINK_ID</c></seeguide>
+ <seeguide marker="erl_dist_protocol#dflags">distribution flag</seeguide>.
+ </p>
+ </item>
+ <tag><marker id="UNLINK_ID_ACK"/><c>UNLINK_ID_ACK</c></tag>
+ <item>
+ <p><c>{36, Id, FromPid, ToPid}</c></p>
+ <p>An unlink acknowledgement signal. This signal is sent as an
+ acknowledgement of the reception of an
+ <seeguide marker="#UNLINK_ID"><c>UNLINK_ID</c></seeguide>
+ signal. The <c>Id</c> element should be the same <c>Id</c>
+ as present in the <c>UNLINK_ID</c> signal. <c>FromPid</c>
+ identifies the sender of the <c>UNLINK_ID_ACK</c> signal and
+ <c>ToPid</c> identifies the sender of the <c>UNLINK_ID</c>
+ signal.</p>
+ <p>
+ This signal is only passed when the
+ <seeguide marker="#new_link_protocol">new link protocol</seeguide>
+ has been negotiated using the
+ <seeguide marker="erl_dist_protocol#DFLAG_UNLINK_ID"><c>DFLAG_UNLINK_ID</c></seeguide>
+ <seeguide marker="erl_dist_protocol#dflags">distribution flag</seeguide>.
+ </p>
+ </item>
+ </taglist>
+ </section>
+ <section>
+ <marker id="link_protocol"/>
+ <title>Link Protocol</title>
+
+ <section>
+ <marker id="new_link_protocol"/>
+ <title>New Link Protocol</title>
+
+ <p>
+ The new link protocol will be used when both nodes flag that
+ they understand it using the
+ <seeguide marker="#DFLAG_UNLINK_ID"><c>DFLAG_UNLINK_ID</c></seeguide>
+ <seeguide marker="#dflags">distribution flag</seeguide>. If
+ one of the nodes does not understand the new link protocol, the
+ <seeguide marker="#old_link_protocol">old link protocol</seeguide>
+ will be used as a fallback.
+ </p>
+
+ <p>
+ The new link protocol introduces two new signals,
+ <seeguide marker="#UNLINK_ID"><c>UNLINK_ID</c></seeguide> and
+ <seeguide marker="#UNLINK_ID"><c>UNLINK_ID_ACK</c></seeguide>,
+ which replace the old
+ <seeguide marker="#UNLINK"><c>UNLINK</c></seeguide>
+ signal. The old <seeguide marker="#LINK"><c>LINK</c></seeguide>
+ signal is still sent in order to set up a link, but handled
+ differently upon reception.
+ </p>
+
+ <p>
+ In order to set up a link, a <c>LINK</c> signal is sent, from
+ the process initiating the operation, to the process that it
+ wants to link to. In order to remove a link, an
+ <c>UNLINK_ID</c> signal is sent, from the process initiating
+ the operation, to the linked process. The receiver of an
+ <c>UNLINK_ID</c> signal responds with an <c>UNLINK_ID_ACK</c>
+ signal. Upon reception of an <c>UNLINK_ID</c> signal, the
+ corresponding <c>UNLINK_ID_ACK</c> signal <em>must</em> be
+ sent before any other signals are sent to the sender of the
+ <c>UNLINK_ID</c> signal. Together with
+ <seeguide marker="erts:communication#passing-of-signals">the
+ signal ordering guarantees</seeguide> of Erlang this makes it
+ possible for the sender of the <c>UNLINK_ID</c> signal to know
+ the order of other signals which is essential for the protocol.
+ The <c>UNLINK_ID_ACK</c> signal should contain the same
+ <c>Id</c> as the <c>Id</c> contained in the <c>UNLINK_ID</c>
+ signal being acknowledged.
+ </p>
+
+ <p>
+ Processes also need to maintain process local information about
+ links. The state of this process local information is changed
+ when the signals above are sent and received. This process
+ local information also determines if a signal should be sent
+ when a process calls
+ <seemfa marker="erlang#link/1"><c>link/1</c></seemfa> or
+ <seemfa marker="erlang#unlink/1"><c>unlink/1</c></seemfa>.
+ A <c>LINK</c> signal is only sent if there does not currently
+ exist an active link between the processes according to the
+ process local information and an <c>UNLINK_ID</c> signal is
+ only sent if there currently exists an active link between the
+ processes according to the process local information.
+ </p>
+
+ <p>
+ The process local information about a link contains:
+ </p>
+ <taglist>
+ <tag>Pid</tag>
+ <item>
+ Process identifier of the linked process.
+ </item>
+ <tag>Active Flag</tag>
+ <item>
+ If set, the link is active and the process will react on
+ <seeguide marker="system/reference_manual:processes#receiving_exit_signals">incoming
+ exit signals</seeguide> issued due to the link. If not set,
+ the link is inactive and incoming exit signals, issued due
+ to the link, will be ignored. That is, the processes are
+ considered as <em>not</em> linked.
+ </item>
+ <tag>Unlink Id</tag>
+ <item>
+ Identifier of an outstanding unlink operation. That is,
+ an unlink operation that has not yet been acknowledged.
+ This information is only used when the active flag is not
+ set.
+ </item>
+ </taglist>
+
+ <p>
+ A process is only considered linked to another process
+ if it has process local information about the link
+ containing the process identifier of the other process and
+ with the active flag set.
+ </p>
+
+ <p>
+ The process local information about a link is updated as
+ follows:
+ </p>
+ <taglist>
+ <tag>A <c>LINK</c> signal is sent</tag>
+ <item>
+ Link information is created if not already existing. The
+ active flag is set, and unlink id is cleared. That is,
+ if we had an outstanding unlink operation we will ignore
+ the result of that operation and enable the link.
+ </item>
+ <tag>A <c>LINK</c> signal is received</tag>
+ <item>
+ If no link information already exists, it is created, the
+ active flag is set and unlink id is cleared. If the link
+ information already exists, the signal is silently ignored,
+ regardless of whether the active flag is set or not.
+ That is, if we have an outstanding unlink operation we will
+ <em>not</em> activate the link. In this scenario, the sender
+ of the <c>LINK</c> signal has not yet sent an
+ <c>UNLINK_ID_ACK</c> signal corresponding to our
+ <c>UNLINK_ID</c> signal which means that it will receive
+ our <c>UNLINK_ID</c> signal after it sent its
+ <c>LINK</c> signal. This in turn means that both processes
+ in the end will agree that there is no link between them.
+ </item>
+ <tag>An <c>UNLINK_ID</c> signal is sent</tag>
+ <item>
+ Link information already exists and the active flag is set
+ (otherwise the signal would not be sent). The active flag
+ is unset, and the unlink id of the signal is saved in the
+ link information.
+ </item>
+ <tag>An <c>UNLINK_ID</c> signal is received</tag>
+ <item>
+ If the active flag is set, information about the link
+ is removed. If the active flag is not set (that is, we have
+ an outstanding unlink operation), the information about the
+ link is left unchanged.
+ </item>
+ <tag>An <c>UNLINK_ID_ACK</c> signal is sent</tag>
+ <item>
+ This is done when an <c>UNLINK_ID</c> signal is received and
+ causes no further changes of the link information.
+ </item>
+ <tag>An <c>UNLINK_ID_ACK</c> signal is received</tag>
+ <item>
+ If information about the link exists, the active flag is not
+ set, and the unlink id in the link information equals the
+ <c>Id</c> in the signal, the link information is removed;
+ otherwise, the signal is ignored.
+ </item>
+ </taglist>
+
+ <p>
+ When a process receives an exit signal due to a link, the
+ process will first react to the exit signal if the link
+ is active and then remove the process local information about
+ the link.
+ </p>
+ <p>
+ In case the connection is lost between two nodes, exit signals
+ with exit reason <c>noconnection</c> are sent to all processes
+ with links over the connection. This will cause all process
+ local information about links over the connection to be
+ removed.
+ </p>
+ <p>
+ Exactly the same link protocol is also used internally on an
+ Erlang node. The signals however have different formats since
+ they do not have to be sent over the wire.
+ </p>
+ </section>
+
+ <section>
+ <marker id="old_link_protocol"/>
+ <title>Old Link Protocol</title>
+
+ <p>
+ The old link protocol utilize two signals
+ <seeguide marker="#LINK"><c>LINK</c></seeguide>, and
+ <seeguide marker="#UNLINK"><c>UNLINK</c></seeguide>. The
+ <c>LINK</c> signal informs the other process that a link
+ should be set up, and the <c>UNLINK</c> signal informs the
+ other process that a link should be removed. This protocol
+ is however a bit too naive. If both processes operate on the
+ link simultaneously, the link may end up in an inconsistent
+ state where one process thinks it is linked while the other
+ thinks it is not linked.
+ </p>
+ <p>
+ This protocol is deprecated and support for it will be removed
+ in OTP 26. Until then, it will be used as fallback when
+ communicating with old nodes that do not understand the
+ <seeguide marker="#new_link_protocol">new link
+ protocol</seeguide>.
+ </p>
+ </section>
+
+ </section>
+ </section>
</chapter>
diff --git a/erts/doc/src/erl_driver.xml b/erts/doc/src/erl_driver.xml
index 209541f7bc..febfc27c99 100644
--- a/erts/doc/src/erl_driver.xml
+++ b/erts/doc/src/erl_driver.xml
@@ -60,7 +60,7 @@
</item>
<item>
<p>A driver callback doing
- <seealso marker="#lengthy_work">lengthy work</seealso> before
+ <seecref marker="#lengthy_work">lengthy work</seecref> before
returning degrades responsiveness of the VM and can cause
miscellaneous strange behaviors. Such strange behaviors
include, but are not limited to, extreme memory usage and bad
@@ -72,19 +72,19 @@
</warning>
<p>As from ERTS 5.5.3 the driver interface has been extended
- (see <seealso marker="driver_entry#extended_marker">
- <c>extended marker</c></seealso>). The extended interface introduces
- <seealso marker="#version_management">version management</seealso>,
+ (see <seecref marker="driver_entry#extended_marker">
+ <c>extended marker</c></seecref>). The extended interface introduces
+ <seecref marker="#version_management">version management</seecref>,
the possibility to pass capability flags (see
- <seealso marker="driver_entry#driver_flags">
- <c>driver_flags</c></seealso>) to the runtime system at driver
+ <seecref marker="driver_entry#driver_flags">
+ <c>driver_flags</c></seecref>) to the runtime system at driver
initialization, and some new driver API functions.</p>
<note>
<p>As from ERTS 5.9 old drivers must be recompiled
and use the extended interface. They must also be adjusted to the
- <seealso marker="#rewrites_for_64_bits">
- 64-bit capable driver interface</seealso>.</p>
+ <seecref marker="#rewrites_for_64_bits">
+ 64-bit capable driver interface</seecref>.</p>
</note>
<p>The driver calls back to the emulator, using the API
@@ -97,8 +97,8 @@
the <c>port</c> handle as an argument. This identifies the driver
instance. Notice that this port handle must be stored by the driver,
it is not given when the driver is called from the emulator (see
- <seealso marker="driver_entry#emulator">
- <c>driver_entry</c></seealso>).</p>
+ <seecref marker="driver_entry#emulator">
+ <c>driver_entry</c></seecref>).</p>
<p>Some of the functions take a parameter of type
<c>ErlDrvBinary</c>, a driver binary. It is to be both
@@ -121,8 +121,8 @@
at the same time. Only one thread at a time will call
driver callbacks corresponding to the same port, though.
To enable port level locking, set the <c>ERL_DRV_FLAG_USE_PORT_LOCKING</c>
- <seealso marker="driver_entry#driver_flags">driver flag</seealso> in
- the <seealso marker="driver_entry"><c>driver_entry</c></seealso>
+ <seecref marker="driver_entry#driver_flags">driver flag</seecref> in
+ the <seecref marker="driver_entry"><c>driver_entry</c></seecref>
used by the driver. When port level locking is used,
the driver writer is responsible for synchronizing all accesses
to data shared by the ports (driver instances).</p>
@@ -180,7 +180,7 @@
</warning>
<p><marker id="lengthy_work"/>
- As mentioned in the <seealso marker="#WARNING">warning</seealso> text at
+ As mentioned in the <seecref marker="#WARNING">warning</seecref> text at
the beginning of this section, it is of vital importance that a driver
callback returns relatively fast. It is difficult to give an exact
maximum amount of time that a driver callback is allowed to work, but
@@ -189,9 +189,9 @@
If you have full control over the code to execute in the driver
callback, the best approach is to divide the work into multiple chunks of
work, and trigger multiple calls to the
- <seealso marker="driver_entry#timeout">time-out callback</seealso> using
- zero time-outs. Function <seealso marker="#erl_drv_consume_timeslice">
- <c>erl_drv_consume_timeslice</c></seealso> can be useful to
+ <seecref marker="driver_entry#timeout">time-out callback</seecref> using
+ zero time-outs. Function <seecref marker="#erl_drv_consume_timeslice">
+ <c>erl_drv_consume_timeslice</c></seecref> can be useful to
determine when to trigger such time-out callback calls. However, sometimes
it cannot be implemented this way, for example when calling
third-party libraries. In this case, you typically want to dispatch the
@@ -209,8 +209,8 @@
<tag>Timer functions</tag>
<item>
<p>Control the timer that a driver can use. The timer has the
- emulator call the <seealso marker="driver_entry#timeout">
- <c>timeout</c></seealso> entry function after a specified time.
+ emulator call the <seecref marker="driver_entry#timeout">
+ <c>timeout</c></seecref> entry function after a specified time.
Only one timer is available for each driver instance.</p>
</item>
<tag>Queue handling</tag>
@@ -224,14 +224,14 @@
closing.</p>
<p>The queue can be manipulated from any threads if
a port data lock is used. For more information, see
- <seealso marker="#ErlDrvPDL"><c>ErlDrvPDL</c></seealso>.</p>
+ <seecref marker="#ErlDrvPDL"><c>ErlDrvPDL</c></seecref>.</p>
</item>
<tag>Output functions</tag>
<item>
<p>With these functions, the driver sends data back to the emulator.
The data is received as messages by the port owner process, see
- <seealso marker="erlang#open_port/2">
- <c>erlang:open_port/2</c></seealso>. The vector function and the
+ <seemfa marker="erlang#open_port/2">
+ <c>erlang:open_port/2</c></seemfa>. The vector function and the
function taking a driver binary are faster, as they avoid
copying the data buffer. There is also a fast way of sending
terms from the driver, without going through the binary term
@@ -257,14 +257,14 @@
more or less the basic functionality needed for multi-threaded
programming:</p>
<list>
- <item><seealso marker="#ErlDrvTid">Threads</seealso></item>
- <item><seealso marker="#ErlDrvMutex">Mutexes</seealso></item>
- <item><seealso marker="#ErlDrvCond">
- Condition variables</seealso></item>
- <item><seealso marker="#ErlDrvRWLock">
- Read/write locks</seealso></item>
- <item><seealso marker="#ErlDrvTSDKey">
- Thread-specific data</seealso></item>
+ <item><seecref marker="#ErlDrvTid">Threads</seecref></item>
+ <item><seecref marker="#ErlDrvMutex">Mutexes</seecref></item>
+ <item><seecref marker="#ErlDrvCond">
+ Condition variables</seecref></item>
+ <item><seecref marker="#ErlDrvRWLock">
+ Read/write locks</seecref></item>
+ <item><seecref marker="#ErlDrvTSDKey">
+ Thread-specific data</seecref></item>
</list>
<p>The Erlang driver thread API can be used in conjunction with
the POSIX thread API on UN-ices and with the Windows native thread
@@ -290,13 +290,13 @@
<p>In order for the Erlang driver thread API to function, thread
support must be enabled in the runtime system. An Erlang driver
can check if thread support is enabled by use of
- <seealso marker="#driver_system_info">
- <c>driver_system_info</c></seealso>.
+ <seecref marker="#driver_system_info">
+ <c>driver_system_info</c></seecref>.
Notice that some functions in the Erlang driver API are thread-safe
only when the runtime system has SMP support, also this
information can be retrieved through
- <seealso marker="#driver_system_info">
- <c>driver_system_info</c></seealso>.
+ <seecref marker="#driver_system_info">
+ <c>driver_system_info</c></seecref>.
Also notice that many functions in the Erlang driver API are
<em>not</em> thread-safe, regardless of whether SMP support is
enabled or not. If a function is not documented as thread-safe, it
@@ -331,9 +331,9 @@
<tag><marker id="version_management"/>Version management</tag>
<item>
<p>Version management is enabled for drivers that have set the
- <seealso marker="driver_entry#extended_marker">
- <c>extended_marker</c></seealso> field of their
- <seealso marker="driver_entry"><c>driver_entry</c></seealso>
+ <seecref marker="driver_entry#extended_marker">
+ <c>extended_marker</c></seecref> field of their
+ <seecref marker="driver_entry"><c>driver_entry</c></seecref>
to <c>ERL_DRV_EXTENDED_MARKER</c>. <c>erl_driver.h</c> defines:</p>
<list type="bulleted">
<item>
@@ -364,9 +364,9 @@
<p>The emulator refuses to load a driver that does not use
the extended driver interface, to allow for 64-bit capable drivers,
as incompatible type changes for the callbacks
- <seealso marker="driver_entry#output"><c>output</c></seealso>,
- <seealso marker="driver_entry#control"><c>control</c></seealso>, and
- <seealso marker="driver_entry#call"><c>call</c></seealso>
+ <seecref marker="driver_entry#output"><c>output</c></seecref>,
+ <seecref marker="driver_entry#control"><c>control</c></seecref>, and
+ <seecref marker="driver_entry#call"><c>call</c></seecref>
were introduced in Erlang/OTP R15B. A driver written
with the old types would compile with warnings and when
called return garbage sizes to the emulator, causing it
@@ -388,16 +388,16 @@
<item>
<p>Support for time measurement in drivers:</p>
<list type="bulleted">
- <item><seealso marker="#ErlDrvTime">
- <c>ErlDrvTime</c></seealso></item>
- <item><seealso marker="#ErlDrvTimeUnit">
- <c>ErlDrvTimeUnit</c></seealso></item>
- <item><seealso marker="#erl_drv_monotonic_time">
- <c>erl_drv_monotonic_time</c></seealso></item>
- <item><seealso marker="#erl_drv_time_offset">
- <c>erl_drv_time_offset</c></seealso></item>
- <item><seealso marker="#erl_drv_convert_time_unit">
- <c>erl_drv_convert_time_unit</c></seealso></item>
+ <item><seecref marker="#ErlDrvTime">
+ <c>ErlDrvTime</c></seecref></item>
+ <item><seecref marker="#ErlDrvTimeUnit">
+ <c>ErlDrvTimeUnit</c></seecref></item>
+ <item><seecref marker="#erl_drv_monotonic_time">
+ <c>erl_drv_monotonic_time</c></seecref></item>
+ <item><seecref marker="#erl_drv_time_offset">
+ <c>erl_drv_time_offset</c></seecref></item>
+ <item><seecref marker="#erl_drv_convert_time_unit">
+ <c>erl_drv_convert_time_unit</c></seecref></item>
</list>
</item>
</taglist>
@@ -407,8 +407,8 @@
<marker id="rewrites_for_64_bits"/>
<title>Rewrites for 64-Bit Driver Interface</title>
<p>ERTS 5.9 introduced two new integer types,
- <seealso marker="#ErlDrvSizeT"><c>ErlDrvSizeT</c></seealso> and
- <seealso marker="#ErlDrvSSizeT"><c>ErlDrvSSizeT</c></seealso>,
+ <seecref marker="#ErlDrvSizeT"><c>ErlDrvSizeT</c></seecref> and
+ <seecref marker="#ErlDrvSSizeT"><c>ErlDrvSSizeT</c></seecref>,
which can hold 64-bit sizes if necessary.</p>
<p>To not update a driver and only recompile, it probably works
@@ -430,10 +430,10 @@
<tag>Return types for driver callbacks</tag>
<item>
<p>Rewrite driver callback
- <seealso marker="driver_entry#control"><c>control</c></seealso>
+ <seecref marker="driver_entry#control"><c>control</c></seecref>
to use return type <c>ErlDrvSSizeT</c> instead of <c>int</c>.</p>
<p>Rewrite driver callback
- <seealso marker="driver_entry#call"><c>call</c></seealso>
+ <seecref marker="driver_entry#call"><c>call</c></seecref>
to use return type <c>ErlDrvSSizeT</c> instead of <c>int</c>.</p>
<note>
<p>These changes are essential not to crash the emulator
@@ -447,15 +447,15 @@
<tag>Arguments to driver callbacks</tag>
<item>
<p>Driver callback
- <seealso marker="driver_entry#output"><c>output</c></seealso>
+ <seecref marker="driver_entry#output"><c>output</c></seecref>
now gets <c>ErlDrvSizeT</c> as 3rd argument instead
of previously <c>int</c>.</p>
<p>Driver callback
- <seealso marker="driver_entry#control"><c>control</c></seealso>
+ <seecref marker="driver_entry#control"><c>control</c></seecref>
now gets <c>ErlDrvSizeT</c> as 4th and 6th arguments instead
of previously <c>int</c>.</p>
<p>Driver callback
- <seealso marker="driver_entry#call"><c>call</c></seealso>
+ <seecref marker="driver_entry#call"><c>call</c></seecref>
now gets <c>ErlDrvSizeT</c> as 4th and 6th arguments instead
of previously <c>int</c>.</p>
<p>Sane compiler's calling conventions probably make these changes
@@ -474,7 +474,7 @@
<tag>Larger <c>size</c> field in <c>ErlIOVec</c></tag>
<item>
<p>The <c>size</c> field in
- <seealso marker="#ErlIOVec"><c>ErlIOVec</c></seealso>
+ <seecref marker="#ErlIOVec"><c>ErlIOVec</c></seecref>
has been changed to <c>ErlDrvSizeT</c> from <c>int</c>.
Check all code that use that field.</p>
<p>Automatic type-casting probably makes these changes necessary only
@@ -493,59 +493,59 @@
Automatic type-casting probably makes these changes necessary only
for a driver that encounters sizes &gt; 32 bits.</p>
<taglist>
- <tag><seealso marker="#driver_output">
- <c>driver_output</c></seealso></tag>
+ <tag><seecref marker="#driver_output">
+ <c>driver_output</c></seecref></tag>
<item>3rd argument</item>
- <tag><seealso marker="#driver_output2">
- <c>driver_output2</c></seealso></tag>
+ <tag><seecref marker="#driver_output2">
+ <c>driver_output2</c></seecref></tag>
<item>3rd and 5th arguments</item>
- <tag><seealso marker="#driver_output_binary">
- <c>driver_output_binary</c></seealso></tag>
+ <tag><seecref marker="#driver_output_binary">
+ <c>driver_output_binary</c></seecref></tag>
<item>3rd, 5th, and 6th arguments</item>
- <tag><seealso marker="#driver_outputv">
- <c>driver_outputv</c></seealso></tag>
+ <tag><seecref marker="#driver_outputv">
+ <c>driver_outputv</c></seecref></tag>
<item>3rd and 5th arguments</item>
- <tag><seealso marker="#driver_vec_to_buf">
- <c>driver_vec_to_buf</c></seealso></tag>
+ <tag><seecref marker="#driver_vec_to_buf">
+ <c>driver_vec_to_buf</c></seecref></tag>
<item>3rd argument and return value</item>
- <tag><seealso marker="#driver_alloc">
- <c>driver_alloc</c></seealso></tag>
+ <tag><seecref marker="#driver_alloc">
+ <c>driver_alloc</c></seecref></tag>
<item>1st argument</item>
- <tag><seealso marker="#driver_realloc">
- <c>driver_realloc</c></seealso></tag>
+ <tag><seecref marker="#driver_realloc">
+ <c>driver_realloc</c></seecref></tag>
<item>2nd argument</item>
- <tag><seealso marker="#driver_alloc_binary">
- <c>driver_alloc_binary</c></seealso></tag>
+ <tag><seecref marker="#driver_alloc_binary">
+ <c>driver_alloc_binary</c></seecref></tag>
<item>1st argument</item>
- <tag><seealso marker="#driver_realloc_binary">
- <c>driver_realloc_binary</c></seealso></tag>
+ <tag><seecref marker="#driver_realloc_binary">
+ <c>driver_realloc_binary</c></seecref></tag>
<item>2nd argument</item>
- <tag><seealso marker="#driver_enq">
- <c>driver_enq</c></seealso></tag>
+ <tag><seecref marker="#driver_enq">
+ <c>driver_enq</c></seecref></tag>
<item>3rd argument</item>
- <tag><seealso marker="#driver_pushq">
- <c>driver_pushq</c></seealso></tag>
+ <tag><seecref marker="#driver_pushq">
+ <c>driver_pushq</c></seecref></tag>
<item>3rd argument</item>
- <tag><seealso marker="#driver_deq">
- <c>driver_deq</c></seealso></tag>
+ <tag><seecref marker="#driver_deq">
+ <c>driver_deq</c></seecref></tag>
<item>2nd argument and return value</item>
- <tag><seealso marker="#driver_sizeq">
- <c>driver_sizeq</c></seealso></tag>
+ <tag><seecref marker="#driver_sizeq">
+ <c>driver_sizeq</c></seecref></tag>
<item>Return value</item>
- <tag><seealso marker="#driver_enq_bin">
- <c>driver_enq_bin</c></seealso></tag>
+ <tag><seecref marker="#driver_enq_bin">
+ <c>driver_enq_bin</c></seecref></tag>
<item>3rd and 4th arguments</item>
- <tag><seealso marker="#driver_pushq_bin">
- <c>driver_pushq_bin</c></seealso></tag>
+ <tag><seecref marker="#driver_pushq_bin">
+ <c>driver_pushq_bin</c></seecref></tag>
<item>3rd and 4th arguments</item>
- <tag><seealso marker="#driver_enqv">
- <c>driver_enqv</c></seealso></tag>
+ <tag><seecref marker="#driver_enqv">
+ <c>driver_enqv</c></seecref></tag>
<item>3rd argument</item>
- <tag><seealso marker="#driver_pushqv">
- <c>driver_pushqv</c></seealso></tag>
+ <tag><seecref marker="#driver_pushqv">
+ <c>driver_pushqv</c></seecref></tag>
<item>3rd argument</item>
- <tag><seealso marker="#driver_peekqv">
- <c>driver_peekqv</c></seealso></tag>
+ <tag><seecref marker="#driver_peekqv">
+ <c>driver_peekqv</c></seecref></tag>
<item>Return value</item>
</taglist>
<note>
@@ -586,44 +586,44 @@ typedef struct ErlDrvSysInfo {
} ErlDrvSysInfo;</code>
<p>The <c>ErlDrvSysInfo</c> structure is used for storage of
information about the Erlang runtime system.
- <seealso marker="#driver_system_info">
- <c>driver_system_info</c></seealso>
+ <seecref marker="#driver_system_info">
+ <c>driver_system_info</c></seecref>
writes the system information when passed a reference to
a <c>ErlDrvSysInfo</c> structure. The fields in the structure
are as follows:</p>
<taglist>
<tag><c>driver_major_version</c></tag>
<item>
- <p>The value of <seealso marker="#version_management">
- <c>ERL_DRV_EXTENDED_MAJOR_VERSION</c></seealso>
+ <p>The value of <seecref marker="#version_management">
+ <c>ERL_DRV_EXTENDED_MAJOR_VERSION</c></seecref>
when the runtime system was compiled. This value is the same
- as the value of <seealso marker="#version_management">
- <c>ERL_DRV_EXTENDED_MAJOR_VERSION</c></seealso>
+ as the value of <seecref marker="#version_management">
+ <c>ERL_DRV_EXTENDED_MAJOR_VERSION</c></seecref>
used when compiling the driver; otherwise the runtime system
would have refused to load the driver.</p>
</item>
<tag><c>driver_minor_version</c></tag>
<item>
- <p>The value of <seealso marker="#version_management">
- <c>ERL_DRV_EXTENDED_MINOR_VERSION</c></seealso>
+ <p>The value of <seecref marker="#version_management">
+ <c>ERL_DRV_EXTENDED_MINOR_VERSION</c></seecref>
when the runtime system was compiled. This value can differ
- from the value of <seealso marker="#version_management">
- <c>ERL_DRV_EXTENDED_MINOR_VERSION</c></seealso>
+ from the value of <seecref marker="#version_management">
+ <c>ERL_DRV_EXTENDED_MINOR_VERSION</c></seecref>
used when compiling the driver.</p>
</item>
<tag><c>erts_version</c></tag>
<item>
<p>A string containing the version number of the runtime system
(the same as returned by
- <seealso marker="erlang#system_info_version">
- <c>erlang:system_info(version)</c></seealso>).</p>
+ <seeerl marker="erlang#system_info_version">
+ <c>erlang:system_info(version)</c></seeerl>).</p>
</item>
<tag><c>otp_release</c></tag>
<item>
<p>A string containing the OTP release number
(the same as returned by
- <seealso marker="erlang#system_info_otp_release">
- <c>erlang:system_info(otp_release)</c></seealso>).</p>
+ <seeerl marker="erlang#system_info_otp_release">
+ <c>erlang:system_info(otp_release)</c></seeerl>).</p>
</item>
<tag><c>thread_support</c></tag>
<item>
@@ -638,17 +638,17 @@ typedef struct ErlDrvSysInfo {
<tag><c>async_threads</c></tag>
<item>
<p>The number of async threads in the async thread pool used by
- <seealso marker="#driver_async"><c>driver_async</c></seealso>
+ <seecref marker="#driver_async"><c>driver_async</c></seecref>
(the same as returned by
- <seealso marker="erlang#system_info_thread_pool_size">
- <c>erlang:system_info(thread_pool_size)</c></seealso>).</p>
+ <seeerl marker="erlang#system_info_thread_pool_size">
+ <c>erlang:system_info(thread_pool_size)</c></seeerl>).</p>
</item>
<tag><c>scheduler_threads</c></tag>
<item>
<p>The number of scheduler threads used by the runtime system
(the same as returned by
- <seealso marker="erlang#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso>).</p>
+ <seeerl marker="erlang#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl>).</p>
</item>
<tag><c>nif_major_version</c></tag>
<item>
@@ -686,12 +686,12 @@ typedef struct ErlDrvBinary {
<p>The <c>refc</c> field has been removed. The reference count of
an <c>ErlDrvBinary</c> is now stored elsewhere. The
reference count of an <c>ErlDrvBinary</c> can be accessed through
- <seealso marker="#driver_binary_get_refc">
- <c>driver_binary_get_refc</c></seealso>,
- <seealso marker="#driver_binary_inc_refc">
- <c>driver_binary_inc_refc</c></seealso>, and
- <seealso marker="#driver_binary_dec_refc">
- <c>driver_binary_dec_refc</c></seealso>.</p>
+ <seecref marker="#driver_binary_get_refc">
+ <c>driver_binary_get_refc</c></seecref>,
+ <seecref marker="#driver_binary_inc_refc">
+ <c>driver_binary_inc_refc</c></seecref>, and
+ <seecref marker="#driver_binary_dec_refc">
+ <c>driver_binary_dec_refc</c></seecref>.</p>
</note>
<p>Some driver calls, such as <c>driver_enq_binary</c>,
increment the driver reference count, and others, such as
@@ -707,13 +707,13 @@ typedef struct ErlDrvBinary {
in the emulator, the ref-count will not go to zero.)</p>
<p>Driver binaries are used in the <c>driver_output2</c> and
<c>driver_outputv</c> calls, and in the queue. Also the
- driver callback <seealso marker="driver_entry#outputv">
- <c>outputv</c></seealso> uses driver binaries.</p>
+ driver callback <seecref marker="driver_entry#outputv">
+ <c>outputv</c></seecref> uses driver binaries.</p>
<p>If the driver for some reason wants to keep a
driver binary around, for example in a static variable, the
reference count is to be incremented, and the binary can later
- be freed in the <seealso marker="driver_entry#stop">
- <c>stop</c></seealso> callback, with <c>driver_free_binary</c>.</p>
+ be freed in the <seecref marker="driver_entry#stop">
+ <c>stop</c></seecref> callback, with <c>driver_free_binary</c>.</p>
<p>Notice that as a driver binary is shared by the driver and
the emulator. A binary received from the emulator or sent to
the emulator must not be changed by the driver.</p>
@@ -745,7 +745,7 @@ typedef struct ErlIOVec {
<p>The I/O vector used by the emulator and drivers is a list
of binaries, with a <c>SysIOVec</c> pointing to the buffers
of the binaries. It is used in <c>driver_outputv</c> and the
- <seealso marker="driver_entry#outputv"><c>outputv</c></seealso>
+ <seecref marker="driver_entry#outputv"><c>outputv</c></seecref>
driver callback. Also, the driver queue is an
<c>ErlIOVec</c>.</p>
</item>
@@ -757,8 +757,8 @@ typedef struct ErlIOVec {
using the supplied compare function (that is, it behaves like
a struct).</p>
<p>The driver writer is to provide the memory for storing the
- monitor when calling <seealso marker="#driver_monitor_process">
- <c>driver_monitor_process</c></seealso>. The
+ monitor when calling <seecref marker="#driver_monitor_process">
+ <c>driver_monitor_process</c></seecref>. The
address of the data is not stored outside of the driver, so
<c>ErlDrvMonitor</c> can be used as any other data, it
can be copied, moved in memory, forgotten, and so on.</p>
@@ -790,17 +790,17 @@ typedef struct ErlIOVec {
<p>Normally a driver instance has no port data lock. If
the driver instance wants to use a port data lock, it must
create the port data lock by calling
- <seealso marker="#driver_pdl_create">
- <c>driver_pdl_create</c></seealso>.</p>
+ <seecref marker="#driver_pdl_create">
+ <c>driver_pdl_create</c></seecref>.</p>
<note>
<p>Once the port data lock has been created, every
access to data associated with the port data lock must be done
while the port data lock is locked. The port data lock is
locked and unlocked by
- <seealso marker="#driver_pdl_lock">
- <c>driver_pdl_lock</c></seealso>, and
- <seealso marker="#driver_pdl_unlock">
- <c>driver_pdl_unlock</c></seealso>, respectively.</p>
+ <seecref marker="#driver_pdl_lock">
+ <c>driver_pdl_lock</c></seecref>, and
+ <seecref marker="#driver_pdl_unlock">
+ <c>driver_pdl_unlock</c></seecref>, respectively.</p>
</note>
<p>A port data lock is reference counted, and when the reference
count reaches zero, it is destroyed. The emulator at
@@ -813,34 +813,34 @@ typedef struct ErlIOVec {
the reference count does not reach zero before the last use
of the lock by the driver has been made. The reference count
can be read, incremented, and decremented by
- <seealso marker="#driver_pdl_get_refc">
- <c>driver_pdl_get_refc</c></seealso>,
- <seealso marker="#driver_pdl_inc_refc">
- <c>driver_pdl_inc_refc</c></seealso>, and
- <seealso marker="#driver_pdl_dec_refc">
- <c>driver_pdl_dec_refc</c></seealso>, respectively.</p>
+ <seecref marker="#driver_pdl_get_refc">
+ <c>driver_pdl_get_refc</c></seecref>,
+ <seecref marker="#driver_pdl_inc_refc">
+ <c>driver_pdl_inc_refc</c></seecref>, and
+ <seecref marker="#driver_pdl_dec_refc">
+ <c>driver_pdl_dec_refc</c></seecref>, respectively.</p>
</item>
<tag><marker id="ErlDrvTid"/><c>ErlDrvTid</c></tag>
<item>
<p>Thread identifier.</p>
- <p>See also <seealso marker="#erl_drv_thread_create">
- <c>erl_drv_thread_create</c></seealso>,
- <seealso marker="#erl_drv_thread_exit">
- <c>erl_drv_thread_exit</c></seealso>,
- <seealso marker="#erl_drv_thread_join">
- <c>erl_drv_thread_join</c></seealso>,
- <seealso marker="#erl_drv_thread_self">
- <c>erl_drv_thread_self</c></seealso>, and
- <seealso marker="#erl_drv_equal_tids">
- <c>erl_drv_equal_tids</c></seealso>.</p>
+ <p>See also <seecref marker="#erl_drv_thread_create">
+ <c>erl_drv_thread_create</c></seecref>,
+ <seecref marker="#erl_drv_thread_exit">
+ <c>erl_drv_thread_exit</c></seecref>,
+ <seecref marker="#erl_drv_thread_join">
+ <c>erl_drv_thread_join</c></seecref>,
+ <seecref marker="#erl_drv_thread_self">
+ <c>erl_drv_thread_self</c></seecref>, and
+ <seecref marker="#erl_drv_equal_tids">
+ <c>erl_drv_equal_tids</c></seecref>.</p>
</item>
<tag><marker id="ErlDrvThreadOpts"/><c>ErlDrvThreadOpts</c></tag>
<item>
<code type="none">
int suggested_stack_size;</code>
<p>Thread options structure passed to
- <seealso marker="#erl_drv_thread_create">
- <c>erl_drv_thread_create</c></seealso>.
+ <seecref marker="#erl_drv_thread_create">
+ <c>erl_drv_thread_create</c></seecref>.
The following field exists:</p>
<taglist>
<tag><c>suggested_stack_size</c></tag>
@@ -848,43 +848,43 @@ int suggested_stack_size;</code>
A value &lt; 0 means default size.
</item>
</taglist>
- <p>See also <seealso marker="#erl_drv_thread_opts_create">
- <c>erl_drv_thread_opts_create</c></seealso>,
- <seealso marker="#erl_drv_thread_opts_destroy">
- <c>erl_drv_thread_opts_destroy</c></seealso>, and
- <seealso marker="#erl_drv_thread_create">
- <c>erl_drv_thread_create</c></seealso>.</p>
+ <p>See also <seecref marker="#erl_drv_thread_opts_create">
+ <c>erl_drv_thread_opts_create</c></seecref>,
+ <seecref marker="#erl_drv_thread_opts_destroy">
+ <c>erl_drv_thread_opts_destroy</c></seecref>, and
+ <seecref marker="#erl_drv_thread_create">
+ <c>erl_drv_thread_create</c></seecref>.</p>
</item>
<tag><marker id="ErlDrvMutex"/><c>ErlDrvMutex</c></tag>
<item>
<p>Mutual exclusion lock. Used for synchronizing access to shared data.
Only one thread at a time can lock a mutex.</p>
- <p>See also <seealso marker="#erl_drv_mutex_create">
- <c>erl_drv_mutex_create</c></seealso>,
- <seealso marker="#erl_drv_mutex_destroy">
- <c>erl_drv_mutex_destroy</c></seealso>,
- <seealso marker="#erl_drv_mutex_lock">
- <c>erl_drv_mutex_lock</c></seealso>,
- <seealso marker="#erl_drv_mutex_trylock">
- <c>erl_drv_mutex_trylock</c></seealso>, and
- <seealso marker="#erl_drv_mutex_unlock">
- <c>erl_drv_mutex_unlock</c></seealso>.</p>
+ <p>See also <seecref marker="#erl_drv_mutex_create">
+ <c>erl_drv_mutex_create</c></seecref>,
+ <seecref marker="#erl_drv_mutex_destroy">
+ <c>erl_drv_mutex_destroy</c></seecref>,
+ <seecref marker="#erl_drv_mutex_lock">
+ <c>erl_drv_mutex_lock</c></seecref>,
+ <seecref marker="#erl_drv_mutex_trylock">
+ <c>erl_drv_mutex_trylock</c></seecref>, and
+ <seecref marker="#erl_drv_mutex_unlock">
+ <c>erl_drv_mutex_unlock</c></seecref>.</p>
</item>
<tag><marker id="ErlDrvCond"/><c>ErlDrvCond</c></tag>
<item>
<p>Condition variable. Used when threads must wait for a specific
condition to appear before continuing execution. Condition variables
must be used with associated mutexes.</p>
- <p>See also <seealso marker="#erl_drv_cond_create">
- <c>erl_drv_cond_create</c></seealso>,
- <seealso marker="#erl_drv_cond_destroy">
- <c>erl_drv_cond_destroy</c></seealso>,
- <seealso marker="#erl_drv_cond_signal">
- <c>erl_drv_cond_signal</c></seealso>,
- <seealso marker="#erl_drv_cond_broadcast">
- <c>erl_drv_cond_broadcast</c></seealso>, and
- <seealso marker="#erl_drv_cond_wait">
- <c>erl_drv_cond_wait</c></seealso>.</p>
+ <p>See also <seecref marker="#erl_drv_cond_create">
+ <c>erl_drv_cond_create</c></seecref>,
+ <seecref marker="#erl_drv_cond_destroy">
+ <c>erl_drv_cond_destroy</c></seecref>,
+ <seecref marker="#erl_drv_cond_signal">
+ <c>erl_drv_cond_signal</c></seecref>,
+ <seecref marker="#erl_drv_cond_broadcast">
+ <c>erl_drv_cond_broadcast</c></seecref>, and
+ <seecref marker="#erl_drv_cond_wait">
+ <c>erl_drv_cond_wait</c></seecref>.</p>
</item>
<tag><marker id="ErlDrvRWLock"/><c>ErlDrvRWLock</c></tag>
<item>
@@ -892,34 +892,34 @@ int suggested_stack_size;</code>
while only allowing one thread to write the same data. Multiple
threads can read lock an rwlock at the same time, while only
one thread can read/write lock an rwlock at a time.</p>
- <p>See also <seealso marker="#erl_drv_rwlock_create">
- <c>erl_drv_rwlock_create</c></seealso>,
- <seealso marker="#erl_drv_rwlock_destroy">
- <c>erl_drv_rwlock_destroy</c></seealso>,
- <seealso marker="#erl_drv_rwlock_rlock">
- <c>erl_drv_rwlock_rlock</c></seealso>,
- <seealso marker="#erl_drv_rwlock_tryrlock">
- <c>erl_drv_rwlock_tryrlock</c></seealso>,
- <seealso marker="#erl_drv_rwlock_runlock">
- <c>erl_drv_rwlock_runlock</c></seealso>,
- <seealso marker="#erl_drv_rwlock_rwlock">
- <c>erl_drv_rwlock_rwlock</c></seealso>,
- <seealso marker="#erl_drv_rwlock_tryrwlock">
- <c>erl_drv_rwlock_tryrwlock</c></seealso>, and
- <seealso marker="#erl_drv_rwlock_rwunlock">
- <c>erl_drv_rwlock_rwunlock</c></seealso>.</p>
+ <p>See also <seecref marker="#erl_drv_rwlock_create">
+ <c>erl_drv_rwlock_create</c></seecref>,
+ <seecref marker="#erl_drv_rwlock_destroy">
+ <c>erl_drv_rwlock_destroy</c></seecref>,
+ <seecref marker="#erl_drv_rwlock_rlock">
+ <c>erl_drv_rwlock_rlock</c></seecref>,
+ <seecref marker="#erl_drv_rwlock_tryrlock">
+ <c>erl_drv_rwlock_tryrlock</c></seecref>,
+ <seecref marker="#erl_drv_rwlock_runlock">
+ <c>erl_drv_rwlock_runlock</c></seecref>,
+ <seecref marker="#erl_drv_rwlock_rwlock">
+ <c>erl_drv_rwlock_rwlock</c></seecref>,
+ <seecref marker="#erl_drv_rwlock_tryrwlock">
+ <c>erl_drv_rwlock_tryrwlock</c></seecref>, and
+ <seecref marker="#erl_drv_rwlock_rwunlock">
+ <c>erl_drv_rwlock_rwunlock</c></seecref>.</p>
</item>
<tag><marker id="ErlDrvTSDKey"/><c>ErlDrvTSDKey</c></tag>
<item>
<p>Key that thread-specific data can be associated with.</p>
- <p>See also <seealso marker="#erl_drv_tsd_key_create">
- <c>erl_drv_tsd_key_create</c></seealso>,
- <seealso marker="#erl_drv_tsd_key_destroy">
- <c>erl_drv_tsd_key_destroy</c></seealso>,
- <seealso marker="#erl_drv_tsd_set">
- <c>erl_drv_tsd_set</c></seealso>, and
- <seealso marker="#erl_drv_tsd_get">
- <c>erl_drv_tsd_get</c></seealso>.</p>
+ <p>See also <seecref marker="#erl_drv_tsd_key_create">
+ <c>erl_drv_tsd_key_create</c></seecref>,
+ <seecref marker="#erl_drv_tsd_key_destroy">
+ <c>erl_drv_tsd_key_destroy</c></seecref>,
+ <seecref marker="#erl_drv_tsd_set">
+ <c>erl_drv_tsd_set</c></seecref>, and
+ <seecref marker="#erl_drv_tsd_get">
+ <c>erl_drv_tsd_get</c></seecref>.</p>
</item>
<tag><marker id="ErlDrvTime"/><c>ErlDrvTime</c></tag>
<item>
@@ -950,7 +950,7 @@ int suggested_stack_size;</code>
<desc>
<marker id="add_driver_entry"></marker>
<p>Adds a driver entry to the list of drivers known by Erlang.
- The <seealso marker="driver_entry#init"><c>init</c></seealso>
+ The <seecref marker="driver_entry#init"><c>init</c></seecref>
function of parameter <c>de</c> is called.</p>
<note>
<p>To use this function for adding drivers residing in
@@ -959,8 +959,8 @@ int suggested_stack_size;</code>
loaded module (that is, <c>.so</c> file) as a normal
dynamically loaded driver (loaded with the <c>erl_ddll</c>
interface), the caller is to call
- <seealso marker="#driver_lock_driver">
- <c>driver_lock_driver</c></seealso> before
+ <seecref marker="#driver_lock_driver">
+ <c>driver_lock_driver</c></seecref> before
adding driver entries.</p>
<p><em>Use of this function is generally deprecated.</em></p>
</note>
@@ -978,7 +978,7 @@ int suggested_stack_size;</code>
memory, in which case <c>NULL</c> is returned. (This is most
often a wrapper for <c>malloc</c>).</p>
<p>Memory allocated must be explicitly freed with a corresponding
- call to <seealso marker="#driver_free"><c>driver_free</c></seealso>
+ call to <seecref marker="#driver_free"><c>driver_free</c></seecref>
(unless otherwise stated).</p>
<p>This function is thread-safe.</p>
</desc>
@@ -995,8 +995,8 @@ int suggested_stack_size;</code>
or <c>NULL</c> on failure (out of memory). When a driver binary has
been sent to the emulator, it must not be changed. Every
allocated binary is to be freed by a corresponding call to
- <seealso marker="#driver_free_binary">
- <c>driver_free_binary</c></seealso> (unless otherwise stated).</p>
+ <seecref marker="#driver_free_binary">
+ <c>driver_free_binary</c></seecref> (unless otherwise stated).</p>
<p>Notice that a driver binary has an internal reference counter.
This means that calling <c>driver_free_binary</c>, it may not
actually dispose of it. If it is sent to the emulator, it can
@@ -1020,13 +1020,13 @@ int suggested_stack_size;</code>
time-consuming, blocking operations without blocking the
emulator.</p>
<p>The async thread pool size can be set with command-line argument
- <seealso marker="erl#async_thread_pool_size"><c>+A</c></seealso>
- in <seealso marker="erl"><c>erl(1)</c></seealso>.
+ <seecom marker="erl#async_thread_pool_size"><c>+A</c></seecom>
+ in <seecom marker="erl"><c>erl(1)</c></seecom>.
If an async thread pool is unavailable, the call is made
synchronously in the thread calling <c>driver_async</c>. The
current number of async threads in the async thread pool can be
- retrieved through <seealso marker="#driver_system_info">
- <c>driver_system_info</c></seealso>.</p>
+ retrieved through <seecref marker="#driver_system_info">
+ <c>driver_system_info</c></seecref>.</p>
<p>If a thread pool is available, a thread is used.
If argument <c>key</c> is <c>NULL</c>, the threads from the
pool are used in a round-robin way, each call to
@@ -1050,8 +1050,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
can be used to signal that the async operation completed.
The data is to be freed in <c>async_free</c>.</p>
<p>When the async operation is done,
- <seealso marker="driver_entry#ready_async">
- <c>ready_async</c></seealso> driver
+ <seecref marker="driver_entry#ready_async">
+ <c>ready_async</c></seecref> driver
entry function is called. If <c>ready_async</c> is <c>NULL</c> in
the driver entry, the <c>async_free</c> function is called
instead.</p>
@@ -1069,8 +1069,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<c>driver_async</c> functionality. A suggested stack size
for threads in the async-thread pool can be configured
through command-line argument
- <seealso marker="erl#async_thread_stack_size"><c>+a</c></seealso>
- in <seealso marker="erl"><c>erl(1)</c></seealso>.</p>
+ <seecom marker="erl#async_thread_stack_size"><c>+a</c></seecom>
+ in <seecom marker="erl"><c>erl(1)</c></seecom>.</p>
</note>
</desc>
</func>
@@ -1081,8 +1081,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<fsummary>Calculate an async key from an ErlDrvPort.</fsummary>
<desc>
<marker id="driver_async_port_key"></marker>
- <p>Calculates a key for later use in <seealso
- marker="#driver_async"><c>driver_async</c></seealso>. The keys are
+ <p>Calculates a key for later use in <seecref
+ marker="#driver_async"><c>driver_async</c></seecref>. The keys are
evenly distributed so that a fair mapping between port IDs
and async thread IDs is achieved.</p>
<note>
@@ -1106,8 +1106,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<p>This function is thread-safe.</p>
<note>
<p>The reference count of driver binary is normally to be decremented
- by calling <seealso marker="#driver_free_binary">
- <c>driver_free_binary</c></seealso>.</p>
+ by calling <seecref marker="#driver_free_binary">
+ <c>driver_free_binary</c></seecref>.</p>
<p><c>driver_binary_dec_refc</c> does <em>not</em> free
the binary if the reference count reaches zero. <em>Only</em>
use <c>driver_binary_dec_refc</c> when you are sure
@@ -1147,35 +1147,35 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<marker id="driver_caller"></marker>
<p>Returns the process ID of the process that
made the current call to the driver. The process ID can be used with
- <seealso marker="#driver_send_term"><c>driver_send_term</c></seealso>
+ <seecref marker="#driver_send_term"><c>driver_send_term</c></seecref>
to send back data to the caller.
<c>driver_caller</c> only returns valid data
when currently executing in one of the following driver callbacks:</p>
<taglist>
- <tag><seealso marker="driver_entry#start">
- <c>start</c></seealso></tag>
- <item>Called from <seealso marker="erlang#open_port/2">
- <c>erlang:open_port/2</c></seealso>.</item>
- <tag><seealso marker="driver_entry#output">
- <c>output</c></seealso></tag>
- <item>Called from <seealso marker="erlang#send/2">
- <c>erlang:send/2</c></seealso> and
- <seealso marker="erlang#port_command/2">
- <c>erlang:port_command/2</c></seealso>.</item>
- <tag><seealso marker="driver_entry#outputv">
- <c>outputv</c></seealso></tag>
- <item>Called from <seealso marker="erlang#send/2">
- <c>erlang:send/2</c></seealso> and
- <seealso marker="erlang#port_command/2">
- <c>erlang:port_command/2</c></seealso>.</item>
- <tag><seealso marker="driver_entry#control">
- <c>control</c></seealso></tag>
- <item>Called from <seealso marker="erlang#port_control/3">
- <c>erlang:port_control/3</c></seealso>.</item>
- <tag><seealso marker="driver_entry#call">
- <c>call</c></seealso></tag>
- <item>Called from <seealso marker="erlang#port_call/3">
- <c>erlang:port_call/3</c></seealso>.</item>
+ <tag><seecref marker="driver_entry#start">
+ <c>start</c></seecref></tag>
+ <item>Called from <seemfa marker="erlang#open_port/2">
+ <c>erlang:open_port/2</c></seemfa>.</item>
+ <tag><seecref marker="driver_entry#output">
+ <c>output</c></seecref></tag>
+ <item>Called from <seemfa marker="erlang#send/2">
+ <c>erlang:send/2</c></seemfa> and
+ <seemfa marker="erlang#port_command/2">
+ <c>erlang:port_command/2</c></seemfa>.</item>
+ <tag><seecref marker="driver_entry#outputv">
+ <c>outputv</c></seecref></tag>
+ <item>Called from <seemfa marker="erlang#send/2">
+ <c>erlang:send/2</c></seemfa> and
+ <seemfa marker="erlang#port_command/2">
+ <c>erlang:port_command/2</c></seemfa>.</item>
+ <tag><seecref marker="driver_entry#control">
+ <c>control</c></seecref></tag>
+ <item>Called from <seemfa marker="erlang#port_control/3">
+ <c>erlang:port_control/3</c></seemfa>.</item>
+ <tag><seecref marker="driver_entry#call">
+ <c>call</c></seecref></tag>
+ <item>Called from <seemfa marker="erlang#port_call/3">
+ <c>erlang:port_call/3</c></seemfa>.</item>
</taglist>
<p>Notice that this function is <em>not</em> thread-safe, not
even when the emulator with SMP support is used.</p>
@@ -1189,8 +1189,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<desc>
<marker id="driver_cancel_timer"></marker>
<p>Cancels a timer set with
- <seealso marker="#driver_set_timer">
- <c>driver_set_timer</c></seealso>.</p>
+ <seecref marker="#driver_set_timer">
+ <c>driver_set_timer</c></seecref>.</p>
<p>The return value is <c>0</c>.</p>
</desc>
</func>
@@ -1242,26 +1242,26 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<tag><c>name</c></tag>
<item>The port name of the new port. You usually want to
use the same port name as the driver name
- (<seealso marker="driver_entry#driver_name">
- <c>driver_name</c></seealso> field of the
- <seealso marker="driver_entry"><c>driver_entry</c></seealso>).
+ (<seecref marker="driver_entry#driver_name">
+ <c>driver_name</c></seecref> field of the
+ <seecref marker="driver_entry"><c>driver_entry</c></seecref>).
</item>
<tag><c>drv_data</c></tag>
<item>The driver-defined handle that is passed in later
calls to driver callbacks. Notice that the
- <seealso marker="driver_entry#start">driver start
- callback</seealso> is not called for this new driver instance.
+ <seecref marker="driver_entry#start">driver start
+ callback</seecref> is not called for this new driver instance.
The driver-defined handle is normally created in the
- <seealso marker="driver_entry#start">driver start callback</seealso>
+ <seecref marker="driver_entry#start">driver start callback</seecref>
when a port is created through
- <seealso marker="erlang#open_port/2">
- <c>erlang:open_port/2</c></seealso>.
+ <seemfa marker="erlang#open_port/2">
+ <c>erlang:open_port/2</c></seemfa>.
</item>
</taglist>
<p>The caller of <c>driver_create_port</c> is allowed to
manipulate the newly created port when <c>driver_create_port</c>
has returned. When
- <seealso marker="#smp_support">port level locking</seealso>
+ <seecref marker="#smp_support">port level locking</seecref>
is used, the creating port is only allowed to
manipulate the newly created port until the current driver
callback, which was called by the emulator, returns.</p>
@@ -1292,7 +1292,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<p>Returns the number of bytes remaining in the queue on success,
otherwise <c>-1</c>.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
</desc>
@@ -1318,7 +1318,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
the queue is empty.</p>
<p>The return value is <c>0</c>.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
</desc>
@@ -1335,10 +1335,10 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
queue. The data in <c>bin</c> at <c>offset</c> with length
<c>len</c> is placed at the end of the queue. This function
is most often faster than
- <seealso marker="#driver_enq"><c>driver_enq</c></seealso>,
+ <seecref marker="#driver_enq"><c>driver_enq</c></seecref>,
because no data must be copied.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
<p>The return value is <c>0</c>.</p>
@@ -1354,11 +1354,11 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<p>Enqueues the data in <c>ev</c>, skipping the
first <c>skip</c> bytes of it, at the end of the driver
queue. It is faster than
- <seealso marker="#driver_enq"><c>driver_enq</c></seealso>,
+ <seecref marker="#driver_enq"><c>driver_enq</c></seecref>,
because no data must be copied.</p>
<p>The return value is <c>0</c>.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
</desc>
@@ -1387,7 +1387,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
when the driver cannot possibly keep open, for example,
buffer allocation gets out of memory. For normal errors
it is more appropriate to send error codes with
- <seealso marker="#driver_output"><c>driver_output</c></seealso>.</p>
+ <seecref marker="#driver_output"><c>driver_output</c></seecref>.</p>
<p>The return value is <c>0</c>.</p>
</desc>
</func>
@@ -1428,8 +1428,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<desc>
<marker id="driver_free_binary"></marker>
<p>Frees a driver binary <c>bin</c>, allocated previously with
- <seealso marker="#driver_alloc_binary">
- <c>driver_alloc_binary</c></seealso>. As binaries
+ <seecref marker="#driver_alloc_binary">
+ <c>driver_alloc_binary</c></seecref>. As binaries
in Erlang are reference counted, the binary can still be around.</p>
<p>This function is thread-safe.</p>
</desc>
@@ -1444,8 +1444,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<marker id="driver_get_monitored_process"></marker>
<p>Returns the process ID associated with a living
monitor. It can be used in the
- <seealso marker="driver_entry#process_exit">
- <c>process_exit</c></seealso> callback to
+ <seecref marker="driver_entry#process_exit">
+ <c>process_exit</c></seecref> callback to
get the process identification for the exiting process.</p>
<p>Returns <c>driver_term_nil</c> if the monitor no longer exists.</p>
</desc>
@@ -1459,14 +1459,14 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<marker id="driver_get_now"></marker>
<warning>
<p><em>This function is deprecated. Do not use it.</em> Use
- <seealso marker="#erl_drv_monotonic_time">
- <c>erl_drv_monotonic_time</c></seealso> (perhaps in combination with
- <seealso marker="#erl_drv_time_offset">
- <c>erl_drv_time_offset</c></seealso>) instead.</p>
+ <seecref marker="#erl_drv_monotonic_time">
+ <c>erl_drv_monotonic_time</c></seecref> (perhaps in combination with
+ <seecref marker="#erl_drv_time_offset">
+ <c>erl_drv_time_offset</c></seecref>) instead.</p>
</warning>
<p>Reads a time stamp into the memory pointed to by
parameter <c>now</c>. For information about specific fields, see
- <seealso marker="#ErlDrvNowData"><c>ErlDrvNowData</c></seealso>.</p>
+ <seecref marker="#ErlDrvNowData"><c>ErlDrvNowData</c></seecref>.</p>
<p>The return value is <c>0</c>, unless the <c>now</c> pointer is
invalid, in which case it is &lt; <c>0</c>.</p>
</desc>
@@ -1507,10 +1507,10 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<desc>
<marker id="driver_mk_port"></marker>
<p>Converts a port handle to the Erlang term format, usable in
- <seealso marker="#erl_drv_output_term">
- <c>erl_drv_output_term</c></seealso> and
- <seealso marker="#erl_drv_send_term">
- <c>erl_drv_send_term</c></seealso>.</p>
+ <seecref marker="#erl_drv_output_term">
+ <c>erl_drv_output_term</c></seecref> and
+ <seecref marker="#erl_drv_send_term">
+ <c>erl_drv_send_term</c></seecref>.</p>
<p>Notice that this function is <em>not</em> thread-safe, not
even when the emulator with SMP support is used.</p>
</desc>
@@ -1524,15 +1524,15 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<marker id="driver_monitor_process"></marker>
<p>Starts monitoring a process from a driver. When a process is
monitored, a process exit results in a call to the provided
- <seealso marker="driver_entry#process_exit">
- <c>process_exit</c></seealso> callback
- in the <seealso marker="driver_entry"><c>ErlDrvEntry</c></seealso>
+ <seecref marker="driver_entry#process_exit">
+ <c>process_exit</c></seecref> callback
+ in the <seecref marker="driver_entry"><c>ErlDrvEntry</c></seecref>
structure. The <c>ErlDrvMonitor</c> structure is filled in, for later
removal or compare.</p>
<p>Parameter <c>process</c> is to be the return value of an
- earlier call to <seealso marker="#driver_caller">
- <c>driver_caller</c></seealso> or
- <seealso marker="#driver_connected"><c>driver_connected</c></seealso>
+ earlier call to <seecref marker="#driver_caller">
+ <c>driver_caller</c></seecref> or
+ <seecref marker="#driver_connected"><c>driver_connected</c></seecref>
call.</p>
<p>Returns <c>0</c> on success, &lt; 0 if no callback is
provided, and &gt; 0 if the process is no longer alive.</p>
@@ -1569,13 +1569,13 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<p>Sends data to a port owner process from a
driver binary. It has a header buffer (<c>hbuf</c>
and <c>hlen</c>) just like
- <seealso marker="#driver_output2"><c>driver_output2</c></seealso>.
+ <seecref marker="#driver_output2"><c>driver_output2</c></seecref>.
Parameter <c>hbuf</c> can be <c>NULL</c>.</p>
<p>Parameter <c>offset</c> is an offset into the binary and
<c>len</c> is the number of bytes to send.</p>
<p>Driver binaries are created with
- <seealso marker="#driver_alloc_binary">
- <c>driver_alloc_binary</c></seealso>.</p>
+ <seecref marker="#driver_alloc_binary">
+ <c>driver_alloc_binary</c></seecref>.</p>
<p>The data in the header is sent as a list and the binary as
an Erlang binary in the tail of the list.</p>
<p>For example, if <c>hlen</c> is <c>2</c>, the port owner process
@@ -1596,12 +1596,12 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<marker id="driver_output_term"></marker>
<warning>
<p><em>This function is deprecated.</em>
- Use <seealso marker="#erl_drv_send_term">
- <c>erl_drv_output_term</c></seealso>instead.</p>
+ Use <seecref marker="#erl_drv_send_term">
+ <c>erl_drv_output_term</c></seecref>instead.</p>
</warning>
<p>Parameters <c>term</c> and <c>n</c> work as in
- <seealso marker="#erl_drv_output_term">
- <c>erl_drv_output_term</c></seealso>.</p>
+ <seecref marker="#erl_drv_output_term">
+ <c>erl_drv_output_term</c></seecref>.</p>
<p>Notice that this function is <em>not</em> thread-safe, not
even when the emulator with SMP support is used.</p>
</desc>
@@ -1632,18 +1632,18 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<marker id="driver_outputv"></marker>
<p>Sends data from an I/O vector, <c>ev</c>, to
the port owner process. It has a header buffer (<c>hbuf</c>
- and <c>hlen</c>), just like <seealso marker="#driver_output2">
- <c>driver_output2</c></seealso>.</p>
+ and <c>hlen</c>), just like <seecref marker="#driver_output2">
+ <c>driver_output2</c></seecref>.</p>
<p>Parameter <c>skip</c> is a number of bytes to skip of
the <c>ev</c> vector from the head.</p>
<p>You get vectors of <c>ErlIOVec</c> type from the driver
queue (see below), and the
- <seealso marker="driver_entry#outputv"><c>outputv</c></seealso>
+ <seecref marker="driver_entry#outputv"><c>outputv</c></seecref>
driver entry function. You can also make them yourself, if you want to
send several <c>ErlDrvBinary</c> buffers at once. Often
it is faster to use
- <seealso marker="#driver_output"><c>driver_output</c></seealso> or
- <seealso marker="#driver_output_binary"></seealso>.</p>
+ <seecref marker="#driver_output"><c>driver_output</c></seecref> or
+ <seecref marker="#driver_output_binary"></seecref>.</p>
<p>For example, if <c>hlen</c> is <c>2</c> and <c>ev</c> points to an
array of three binaries, the port owner process receives
<c><![CDATA[[H1, H2, <<B1>>, <<B2>> | <<B3>>]]]></c>.</p>
@@ -1744,11 +1744,11 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
elements in <c>vlen</c>. This is one of two ways to get data
out of the queue.</p>
<p>Nothing is removed from the queue by this function, that must be done
- with <seealso marker="#driver_deq"><c>driver_deq</c></seealso>.</p>
+ with <seecref marker="#driver_deq"><c>driver_deq</c></seecref>.</p>
<p>The returned array is suitable to use with the Unix system
call <c>writev</c>.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
</desc>
@@ -1766,9 +1766,9 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<p>If <c>ev</c> is <c>NULL</c>, all ones that is <c>-1</c> type cast to
<c>ErlDrvSizeT</c> are returned.</p>
<p>Nothing is removed from the queue by this function, that must be done
- with <seealso marker="#driver_deq"><c>driver_deq</c></seealso>.</p>
+ with <seecref marker="#driver_deq"><c>driver_deq</c></seecref>.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
</desc>
@@ -1785,7 +1785,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
at the beginning of the queue.</p>
<p>The return value is <c>0</c>.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
</desc>
@@ -1801,10 +1801,10 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<p>Puts data in the binary <c>bin</c>, at
<c>offset</c> with length <c>len</c> at the head of the
driver queue. It is most often faster than
- <seealso marker="#driver_pushq"><c>driver_pushq</c></seealso>,
+ <seecref marker="#driver_pushq"><c>driver_pushq</c></seecref>,
because no data must be copied.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
<p>The return value is <c>0</c>.</p>
@@ -1820,11 +1820,11 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<p>Puts the data in <c>ev</c>, skipping the first
<c>skip</c> bytes of it, at the head of the driver queue.
It is faster than
- <seealso marker="#driver_pushq"><c>driver_pushq</c></seealso>,
+ <seecref marker="#driver_pushq"><c>driver_pushq</c></seecref>,
because no data must be copied.</p>
<p>The return value is <c>0</c>.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
</desc>
@@ -1895,10 +1895,10 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<c>ERL_DRV_READ</c>, <c>ERL_DRV_WRITE</c>, and <c>ERL_DRV_USE</c>.
The first two specify whether to wait for read events and/or write
events. A fired read event calls
- <seealso marker="driver_entry#ready_input">
- <c>ready_input</c></seealso> and a fired write event calls
- <seealso marker="driver_entry#ready_output">
- <c>ready_output</c></seealso>.</p>
+ <seecref marker="driver_entry#ready_input">
+ <c>ready_input</c></seecref> and a fired write event calls
+ <seecref marker="driver_entry#ready_output">
+ <c>ready_output</c></seecref>.</p>
<note>
<p>Some OS (Windows) do not differentiate between read and write
events. The callback for a fired event then only depends on the
@@ -1912,7 +1912,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
internally. To safely close an event object, call
<c>driver_select</c> with <c>ERL_DRV_USE</c> and <c>on==0</c>, which
clears all events and then either calls
- <seealso marker="driver_entry#stop_select"><c>stop_select</c></seealso>
+ <seecref marker="driver_entry#stop_select"><c>stop_select</c></seecref>
or schedules it to be called when it is safe to close the event
object. <c>ERL_DRV_USE</c> is to be set together with the first event
for an event object. It is harmless to set <c>ERL_DRV_USE</c>
@@ -1940,8 +1940,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<marker id="driver_send_term"></marker>
<warning>
<p><em>This function is deprecated.</em>
- Use <seealso marker="#erl_drv_send_term">
- <c>erl_drv_send_term</c></seealso> instead.</p>
+ Use <seecref marker="#erl_drv_send_term">
+ <c>erl_drv_send_term</c></seecref> instead.</p>
</warning>
<note>
<p>The parameters of this function
@@ -1950,8 +1950,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
function not to fail when it should.</p>
</note>
<p>Parameters <c>term</c> and <c>n</c> work as in
- <seealso marker="#erl_drv_output_term">
- <c>erl_drv_output_term</c></seealso>.</p>
+ <seecref marker="#erl_drv_output_term">
+ <c>erl_drv_output_term</c></seecref>.</p>
<p>This function is only thread-safe when the emulator with SMP
support is used.</p>
</desc>
@@ -1967,8 +1967,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
down and call the driver when it is timed out. Parameter
<c>time</c> is the time in milliseconds before the timer expires.</p>
<p>When the timer reaches <c>0</c> and expires, the driver entry
- function <seealso marker="driver_entry#timeout">
- <c>timeout</c></seealso> is called.</p>
+ function <seecref marker="driver_entry#timeout">
+ <c>timeout</c></seecref> is called.</p>
<p>Notice that only one timer exists on each driver instance;
setting a new timer replaces an older one.</p>
<p>Return value is <c>0</c>, unless the <c>timeout</c>
@@ -1984,7 +1984,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<marker id="driver_sizeq"></marker>
<p>Returns the number of bytes currently in the driver queue.</p>
<p>This function can be called from any thread if a
- <seealso marker="#ErlDrvPDL">port data lock</seealso>
+ <seecref marker="#ErlDrvPDL">port data lock</seecref>
associated with the <c>port</c> is locked by the calling
thread during the call.</p>
</desc>
@@ -1997,13 +1997,13 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<desc>
<marker id="driver_system_info"></marker>
<p>Writes information about the Erlang runtime system into the
- <seealso marker="#ErlDrvSysInfo"><c>ErlDrvSysInfo</c></seealso>
+ <seecref marker="#ErlDrvSysInfo"><c>ErlDrvSysInfo</c></seecref>
structure referred to by the first argument. The second
argument is to be the size of the
- <seealso marker="#ErlDrvSysInfo"><c>ErlDrvSysInfo</c></seealso>
+ <seecref marker="#ErlDrvSysInfo"><c>ErlDrvSysInfo</c></seecref>
structure, that is, <c>sizeof(ErlDrvSysInfo)</c>.</p>
<p>For information about specific fields, see
- <seealso marker="#ErlDrvSysInfo"><c>ErlDrvSysInfo</c></seealso>.</p>
+ <seecref marker="#ErlDrvSysInfo"><c>ErlDrvSysInfo</c></seecref>.</p>
</desc>
</func>
@@ -2018,7 +2018,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<c>buf</c>, of the size <c>len</c>.</p>
<p>If the data is to be sent from the driver to the port owner
process, it is faster to use
- <seealso marker="#driver_outputv"><c>driver_outputv</c></seealso>.</p>
+ <seecref marker="#driver_outputv"><c>driver_outputv</c></seecref>.</p>
<p>The return value is the space left in the buffer, that is, if
<c>ev</c> contains less than <c>len</c> bytes it is the
difference, and if <c>ev</c> contains <c>len</c> bytes or more,
@@ -2046,7 +2046,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<c>Port ! {Owner, {command, Data}}</c> or
<c>port_command/[2,3]</c>. Notice that these limits
only concerns command data that have not yet reached the
- port. The <seealso marker="#set_busy_port">busy port</seealso>
+ port. The <seecref marker="#set_busy_port">busy port</seecref>
feature can be used for data that has reached the port.</p>
<p>Valid limits are values in the range
<c>[ERL_DRV_BUSY_MSGQ_LIM_MIN, ERL_DRV_BUSY_MSGQ_LIM_MAX]</c>.
@@ -2065,8 +2065,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
read. Values are in bytes.</p>
<p>The busy message queue feature can be disabled either
by setting the <c>ERL_DRV_FLAG_NO_BUSY_MSGQ</c>
- <seealso marker="driver_entry#driver_flags">driver flag</seealso>
- in the <seealso marker="driver_entry"><c>driver_entry</c></seealso>
+ <seecref marker="driver_entry#driver_flags">driver flag</seecref>
+ in the <seecref marker="driver_entry"><c>driver_entry</c></seecref>
used by the driver, or by calling this function with
<c>ERL_DRV_BUSY_MSGQ_DISABLED</c> as a limit (either low or
high). When this feature has been disabled, it cannot be
@@ -2078,7 +2078,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
busy. Suspended processes are resumed when neither the
port or the port message queue is busy.</p>
<p>For information about busy port functionality, see
- <seealso marker="#set_busy_port"><c>set_busy_port</c></seealso>.</p>
+ <seecref marker="#set_busy_port"><c>set_busy_port</c></seecref>.</p>
</desc>
</func>
@@ -2120,8 +2120,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<desc>
<marker id="erl_drv_cond_destroy"></marker>
<p>Destroys a condition variable previously created by
- <seealso marker="#erl_drv_cond_create">
- <c>erl_drv_cond_create</c></seealso>.</p>
+ <seecref marker="#erl_drv_cond_create">
+ <c>erl_drv_cond_create</c></seecref>.</p>
<p><c>cnd</c> is a pointer to a condition variable to destroy.</p>
<p>This function is thread-safe.</p>
</desc>
@@ -2222,7 +2222,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
misbehaviors of the VM because of a port monopolizing a scheduler
thread. It can be used when dividing lengthy work into some repeated
driver callback calls, without the need to use threads.</p>
- <p>See also the important <seealso marker="#WARNING">warning</seealso>
+ <p>See also the important <seecref marker="#WARNING">warning</seecref>
text at the beginning of this manual page.</p>
</desc>
</func>
@@ -2246,10 +2246,10 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</taglist>
<p>Returns <c>ERL_DRV_TIME_ERROR</c> if called with an invalid
time unit argument.</p>
- <p>See also <seealso marker="#ErlDrvTime">
- <c>ErlDrvTime</c></seealso> and
- <seealso marker="#ErlDrvTimeUnit">
- <c>ErlDrvTimeUnit</c></seealso>.</p>
+ <p>See also <seecref marker="#ErlDrvTime">
+ <c>ErlDrvTime</c></seecref> and
+ <seecref marker="#ErlDrvTimeUnit">
+ <c>ErlDrvTimeUnit</c></seecref>.</p>
</desc>
</func>
@@ -2305,7 +2305,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<c>*value_size</c> has been set to the buffer size needed.</p>
<warning>
<p>This function reads the emulated environment used by
- <seealso marker="kernel:os#getenv/1"><c>os:getenv/1</c></seealso> and not
+ <seemfa marker="kernel:os#getenv/1"><c>os:getenv/1</c></seemfa> and not
the environment used by libc's <c>getenv(3)</c> or similar. Drivers
that <em>require</em> that these are in sync will need to do so
themselves, but keep in mind that they are segregated for a reason;
@@ -2330,16 +2330,16 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</item>
<tag><c>res</c></tag>
<item>The result of the port initialization. Can be the same
- values as the return value of <seealso marker="driver_entry#start">
- <c>start</c></seealso>, that is, any of the error codes or the
+ values as the return value of <seecref marker="driver_entry#start">
+ <c>start</c></seecref>, that is, any of the error codes or the
<c>ErlDrvData</c> that is to be used for this port.
</item>
</taglist>
<p>When this function is called the initiating <c>erlang:open_port</c>
- call is returned as if the <seealso marker="driver_entry#start">
- <c>start</c></seealso> function had just been called. It can only be
- used when flag <seealso marker="driver_entry#driver_flags">
- <c>ERL_DRV_FLAG_USE_INIT_ACK</c></seealso>
+ call is returned as if the <seecref marker="driver_entry#start">
+ <c>start</c></seecref> function had just been called. It can only be
+ used when flag <seecref marker="driver_entry#driver_flags">
+ <c>ERL_DRV_FLAG_USE_INIT_ACK</c></seecref>
has been set on the linked-in driver.</p>
</desc>
</func>
@@ -2351,16 +2351,16 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<fsummary>Get Erlang monotonic time.</fsummary>
<desc>
<marker id="erl_drv_monotonic_time"></marker>
- <p>Returns <seealso marker="time_correction#Erlang_Monotonic_Time">
- Erlang monotonic time</seealso>. Notice that negative values are
+ <p>Returns <seeguide marker="time_correction#Erlang_Monotonic_Time">
+ Erlang monotonic time</seeguide>. Notice that negative values are
not uncommon.</p>
<p><c>time_unit</c> is time unit of returned value.</p>
<p>Returns <c>ERL_DRV_TIME_ERROR</c> if called with an invalid
time unit argument, or if called from a thread that is not a
scheduler thread.</p>
- <p>See also <seealso marker="#ErlDrvTime"><c>ErlDrvTime</c></seealso>
- and <seealso marker="#ErlDrvTimeUnit">
- <c>ErlDrvTimeUnit</c></seealso>.</p>
+ <p>See also <seecref marker="#ErlDrvTime"><c>ErlDrvTime</c></seecref>
+ and <seecref marker="#ErlDrvTimeUnit">
+ <c>ErlDrvTimeUnit</c></seecref>.</p>
</desc>
</func>
@@ -2402,8 +2402,8 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
<desc>
<marker id="erl_drv_mutex_destroy"></marker>
<p>Destroys a mutex previously created by
- <seealso marker="#erl_drv_mutex_create">
- <c>erl_drv_mutex_create</c></seealso>.
+ <seecref marker="#erl_drv_mutex_create">
+ <c>erl_drv_mutex_create</c></seecref>.
The mutex must be in an unlocked state before it is destroyed.</p>
<p><c>mtx</c> is a pointer to a mutex to destroy.</p>
<p>This function is thread-safe.</p>
@@ -2485,15 +2485,15 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
format to the port owner process. This is a fast way to
deliver term data from a driver. It needs no binary
conversion, so the port owner process receives data as
- normal Erlang terms. The <seealso marker="#erl_drv_send_term">
- <c>erl_drv_send_term</c></seealso>
+ normal Erlang terms. The <seecref marker="#erl_drv_send_term">
+ <c>erl_drv_send_term</c></seecref>
functions can be used for sending to any process
on the local node.</p>
<note>
<p>Parameter <c>port</c> is <em>not</em>
an ordinary port handle, but a port handle converted using
- <seealso marker="#driver_mk_port">
- <c>driver_mk_port</c></seealso>.</p>
+ <seecref marker="#driver_mk_port">
+ <c>driver_mk_port</c></seecref>.</p>
</note>
<p>Parameter <c>term</c> points to an array of
<c>ErlDrvTermData</c> with <c>n</c> elements. This array
@@ -2602,15 +2602,15 @@ ErlDrvTermData spec[] = {
erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0])); ]]></code>
<p>The <c>ERL_DRV_EXT2TERM</c> term type is used for passing a
term encoded with the
- <seealso marker="erl_ext_dist">external format</seealso>,
+ <seeguide marker="erl_ext_dist">external format</seeguide>,
that is, a term that has been encoded by
- <seealso marker="erlang#term_to_binary/2">
- <c>erlang:term_to_binary</c></seealso>,
- <seealso marker="erl_interface:ei"><c>erl_interface:ei(3)</c></seealso>,
+ <seemfa marker="erlang#term_to_binary/2">
+ <c>erlang:term_to_binary</c></seemfa>,
+ <seecref marker="erl_interface:ei"><c>erl_interface:ei(3)</c></seecref>,
and so on.
For example, if <c>binp</c> is a pointer to an <c>ErlDrvBinary</c>
that contains term <c>{17, 4711}</c> encoded with the
- <seealso marker="erl_ext_dist">external format</seealso>,
+ <seeguide marker="erl_ext_dist">external format</seeguide>,
and you want to wrap it in a two-tuple with the tag <c>my_tag</c>,
that is, <c>{my_tag, {17, 4711}}</c>, you can do as follows:</p>
<code type="none"><![CDATA[
@@ -2637,8 +2637,8 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<p>If you want to pass a binary and do not already have the content
of the binary in an <c>ErlDrvBinary</c>, you can benefit from using
<c>ERL_DRV_BUF2BINARY</c> instead of creating an <c>ErlDrvBinary</c>
- through <seealso marker="#driver_alloc_binary">
- <c>driver_alloc_binary</c></seealso> and then pass the binary through
+ through <seecref marker="#driver_alloc_binary">
+ <c>driver_alloc_binary</c></seecref> and then pass the binary through
<c>ERL_DRV_BINARY</c>. The runtime system often allocates
binaries smarter if <c>ERL_DRV_BUF2BINARY</c> is used.
However, if the content of the binary to pass already resides in
@@ -2672,7 +2672,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</note>
<warning>
<p>This function modifies the emulated environment used by
- <seealso marker="kernel:os#putenv/2"><c>os:putenv/2</c></seealso> and not
+ <seemfa marker="kernel:os#putenv/2"><c>os:putenv/2</c></seemfa> and not
the environment used by libc's <c>putenv(3)</c> or similar. Drivers
that <em>require</em> that these are in sync will need to do so
themselves, but keep in mind that they are segregated for a reason;
@@ -2692,7 +2692,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<p>Creates an rwlock and returns a pointer to it.</p>
<p><c>name</c> is a string identifying the created rwlock.
It is used to identify the rwlock in debug functionality (see note
- about the <seealso marker="#lock_checker">lock checker</seealso>).</p>
+ about the <seecref marker="#lock_checker">lock checker</seecref>).</p>
<p>Returns <c>NULL</c> on failure. The driver creating the rwlock
is responsible for destroying it before the driver is unloaded.</p>
<p>This function is thread-safe.</p>
@@ -2706,8 +2706,8 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<desc>
<marker id="erl_drv_rwlock_destroy"></marker>
<p>Destroys an rwlock previously created by
- <seealso marker="#erl_drv_rwlock_create">
- <c>erl_drv_rwlock_create</c></seealso>.
+ <seecref marker="#erl_drv_rwlock_create">
+ <c>erl_drv_rwlock_create</c></seecref>.
The rwlock must be in an unlocked state before it is destroyed.</p>
<p><c>rwlck</c> is a pointer to an rwlock to destroy.</p>
<p>This function is thread-safe.</p>
@@ -2847,12 +2847,12 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<note>
<p>Parameter <c>port</c> is <em>not</em> an ordinary port handle, but
a port handle converted using
- <seealso marker="#driver_mk_port">
- <c>driver_mk_port</c></seealso>.</p>
+ <seecref marker="#driver_mk_port">
+ <c>driver_mk_port</c></seecref>.</p>
</note>
<p>Parameters <c>port</c>, <c>term</c>, and <c>n</c> work as in
- <seealso marker="#erl_drv_output_term">
- <c>erl_drv_output_term</c></seealso>.</p>
+ <seecref marker="#erl_drv_output_term">
+ <c>erl_drv_output_term</c></seecref>.</p>
<p>This function is only thread-safe when the emulator with SMP
support is used.</p>
</desc>
@@ -2865,8 +2865,8 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<desc>
<marker id="erl_drv_set_os_pid"></marker>
<p>Sets the <c>os_pid</c> seen when doing
- <seealso marker="erlang#port_info/2">
- <c>erlang:port_info/2</c></seealso> on this port.</p>
+ <seemfa marker="erlang#port_info/2">
+ <c>erlang:port_info/2</c></seemfa> on this port.</p>
<p><c>port</c> is the port handle of the port (driver instance) to set
the pid on. <c>pid</c>is the pid to set.</p>
</desc>
@@ -2902,27 +2902,27 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
identifier of the newly created thread is available in
<c>*tid</c>. <c>opts</c> can be either a <c>NULL</c> pointer, or a
pointer to an
- <seealso marker="#ErlDrvThreadOpts"><c>ErlDrvThreadOpts</c></seealso>
+ <seecref marker="#ErlDrvThreadOpts"><c>ErlDrvThreadOpts</c></seecref>
structure. If <c>opts</c> is a <c>NULL</c> pointer, default options
are used, otherwise the passed options are used.</p>
<warning>
<p>You are not allowed to allocate the
- <seealso marker="#ErlDrvThreadOpts">
- <c>ErlDrvThreadOpts</c></seealso> structure by yourself.
+ <seecref marker="#ErlDrvThreadOpts">
+ <c>ErlDrvThreadOpts</c></seecref> structure by yourself.
It must be allocated and initialized by
- <seealso marker="#erl_drv_thread_opts_create">
- <c>erl_drv_thread_opts_create</c></seealso>.</p>
+ <seecref marker="#erl_drv_thread_opts_create">
+ <c>erl_drv_thread_opts_create</c></seecref>.</p>
</warning>
<p>The created thread terminates either when <c>func</c> returns or if
- <seealso marker="#erl_drv_thread_exit">
- <c>erl_drv_thread_exit</c></seealso>
+ <seecref marker="#erl_drv_thread_exit">
+ <c>erl_drv_thread_exit</c></seecref>
is called by the thread. The exit value of the thread is either
returned from <c>func</c> or passed as argument to
- <seealso marker="#erl_drv_thread_exit">
- <c>erl_drv_thread_exit</c></seealso>.
+ <seecref marker="#erl_drv_thread_exit">
+ <c>erl_drv_thread_exit</c></seecref>.
The driver creating the thread is responsible for joining the
- thread, through <seealso marker="#erl_drv_thread_join">
- <c>erl_drv_thread_join</c></seealso>,
+ thread, through <seecref marker="#erl_drv_thread_join">
+ <c>erl_drv_thread_join</c></seecref>,
before the driver is unloaded. "Detached" threads cannot be created,
that is, threads that do not need to be joined.</p>
<warning>
@@ -2945,11 +2945,11 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
argument. <c>exit_value</c> is a pointer to an exit value or
<c>NULL</c>.</p>
<p>You are only allowed to terminate threads created with
- <seealso marker="#erl_drv_thread_create">
- <c>erl_drv_thread_create</c></seealso>.</p>
+ <seecref marker="#erl_drv_thread_create">
+ <c>erl_drv_thread_create</c></seecref>.</p>
<p>The exit value can later be retrieved by another thread through
- <seealso marker="#erl_drv_thread_join">
- <c>erl_drv_thread_join</c></seealso>.</p>
+ <seecref marker="#erl_drv_thread_join">
+ <c>erl_drv_thread_join</c></seecref>.</p>
<p>This function is thread-safe.</p>
</desc>
</func>
@@ -3003,16 +3003,16 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
functionality.</p>
<p>Returns <c>NULL</c> on failure. A thread option
structure is used for passing options to
- <seealso marker="#erl_drv_thread_create">
- <c>erl_drv_thread_create</c></seealso>.
+ <seecref marker="#erl_drv_thread_create">
+ <c>erl_drv_thread_create</c></seecref>.
If the structure is not modified before it is passed to
- <seealso marker="#erl_drv_thread_create">
- <c>erl_drv_thread_create</c></seealso>,
+ <seecref marker="#erl_drv_thread_create">
+ <c>erl_drv_thread_create</c></seecref>,
the default values are used.</p>
<warning>
<p>You are not allowed to allocate the
- <seealso marker="#ErlDrvThreadOpts">
- <c>ErlDrvThreadOpts</c></seealso>
+ <seecref marker="#ErlDrvThreadOpts">
+ <c>ErlDrvThreadOpts</c></seecref>
structure by yourself. It must be allocated and initialized by
<c>erl_drv_thread_opts_create</c>.</p>
</warning>
@@ -3028,8 +3028,8 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<desc>
<marker id="erl_drv_thread_opts_destroy"></marker>
<p>Destroys thread options previously created by
- <seealso marker="#erl_drv_thread_opts_create">
- <c>erl_drv_thread_opts_create</c></seealso>.</p>
+ <seecref marker="#erl_drv_thread_opts_create">
+ <c>erl_drv_thread_opts_create</c></seecref>.</p>
<p><c>opts</c> is a pointer to thread options to destroy.</p>
<p>This function is thread-safe.</p>
</desc>
@@ -3053,19 +3053,19 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<desc>
<marker id="erl_drv_time_offset"></marker>
<p>Returns the current time offset between
- <seealso marker="time_correction#Erlang_Monotonic_Time">
- Erlang monotonic time</seealso> and
- <seealso marker="time_correction#Erlang_System_Time">
- Erlang system time</seealso>
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">
+ Erlang monotonic time</seeguide> and
+ <seeguide marker="time_correction#Erlang_System_Time">
+ Erlang system time</seeguide>
converted into the <c>time_unit</c> passed as argument.</p>
<p><c>time_unit</c> is time unit of returned value.</p>
<p>Returns <c>ERL_DRV_TIME_ERROR</c> if called with an invalid
time unit argument, or if called from a thread that is not a
scheduler thread.</p>
- <p>See also <seealso marker="#ErlDrvTime">
- <c>ErlDrvTime</c></seealso> and
- <seealso marker="#ErlDrvTimeUnit">
- <c>ErlDrvTimeUnit</c></seealso>.</p>
+ <p>See also <seecref marker="#ErlDrvTime">
+ <c>ErlDrvTime</c></seecref> and
+ <seecref marker="#ErlDrvTimeUnit">
+ <c>ErlDrvTimeUnit</c></seecref>.</p>
</desc>
</func>
@@ -3108,11 +3108,11 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<desc>
<marker id="erl_drv_tsd_key_destroy"></marker>
<p>Destroys a thread-specific data key previously created by
- <seealso marker="#erl_drv_tsd_key_create">
- <c>erl_drv_tsd_key_create</c></seealso>.
+ <seecref marker="#erl_drv_tsd_key_create">
+ <c>erl_drv_tsd_key_create</c></seecref>.
All thread-specific data using this key in all threads
- must be cleared (see <seealso marker="#erl_drv_tsd_set">
- <c>erl_drv_tsd_set</c></seealso>)
+ must be cleared (see <seecref marker="#erl_drv_tsd_set">
+ <c>erl_drv_tsd_set</c></seecref>)
before the call to <c>erl_drv_tsd_key_destroy</c>.</p>
<p><c>key</c> is a thread-specific data key to destroy.</p>
<warning>
@@ -3172,8 +3172,8 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<desc>
<marker id="remove_driver_entry"></marker>
<p>Removes a driver entry <c>de</c> previously added with
- <seealso marker="#add_driver_entry">
- <c>add_driver_entry</c></seealso>.</p>
+ <seecref marker="#add_driver_entry">
+ <c>add_driver_entry</c></seecref>.</p>
<p>Driver entries added by the <c>erl_ddll</c> Erlang interface
cannot be removed by using this interface.</p>
</desc>
@@ -3188,8 +3188,8 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<p>Sets and unsets the busy state of the port. If
<c>on</c> is non-zero, the port is set to busy. If it is zero,
the port is set to not busy. You typically want to combine
- this feature with the <seealso marker="#erl_drv_busy_msgq_limits">
- busy port message queue</seealso> functionality.</p>
+ this feature with the <seecref marker="#erl_drv_busy_msgq_limits">
+ busy port message queue</seecref> functionality.</p>
<p>Processes sending command data to the port are suspended
if either the port or the port message queue
is busy. Suspended processes are resumed when neither the
@@ -3197,16 +3197,16 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
is in this context data passed to the port using either
<c>Port ! {Owner, {command, Data}}</c> or
<c>port_command/[2,3]</c>.</p>
- <p>If the <seealso marker="driver_entry#driver_flags">
- <![CDATA[ERL_DRV_FLAG_SOFT_BUSY]]></seealso> has been set in the
- <seealso marker="driver_entry"><c>driver_entry</c></seealso>,
+ <p>If the <seecref marker="driver_entry#driver_flags">
+ <![CDATA[ERL_DRV_FLAG_SOFT_BUSY]]></seecref> has been set in the
+ <seecref marker="driver_entry"><c>driver_entry</c></seecref>,
data can be forced into the driver through
- <seealso marker="erlang#port_command/3">
- <c>erlang:port_command(Port, Data, [force])</c></seealso>
+ <seemfa marker="erlang#port_command/3">
+ <c>erlang:port_command(Port, Data, [force])</c></seemfa>
even if the driver has signaled that it is busy.</p>
<p>For information about busy port message queue functionality, see
- <seealso marker="#erl_drv_busy_msgq_limits">
- <c>erl_drv_busy_msgq_limits</c></seealso>.</p>
+ <seecref marker="#erl_drv_busy_msgq_limits">
+ <c>erl_drv_busy_msgq_limits</c></seecref>.</p>
</desc>
</func>
@@ -3216,12 +3216,12 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<fsummary>Set flags on how to handle control entry function.</fsummary>
<desc>
<marker id="set_port_control_flags"></marker>
- <p>Sets flags for how the <seealso marker="driver_entry#control">
- <c>control</c></seealso> driver entry
+ <p>Sets flags for how the <seecref marker="driver_entry#control">
+ <c>control</c></seecref> driver entry
function will return data to the port owner process.
(The <c>control</c> function is called from
- <seealso marker="erlang#port_control/3">
- <c>erlang:port_control/3</c></seealso>.)</p>
+ <seemfa marker="erlang#port_control/3">
+ <c>erlang:port_control/3</c></seemfa>.)</p>
<p>Currently there are only two meaningful values for
<c>flags</c>: <c>0</c> means that data is returned in a list,
and <c>PORT_CONTROL_FLAG_BINARY</c> means data is returned as
@@ -3232,10 +3232,10 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
<section>
<title>See Also</title>
- <p><seealso marker="driver_entry"><c>driver_entry(3)</c></seealso>,
- <seealso marker="erlang"><c>erlang(3)</c></seealso>,
- <seealso marker="kernel:erl_ddll"><c>erl_ddll(3)</c></seealso>,
- section <seealso marker="alt_dist">How to Implement an Alternative
- Carrier for the Erlang Distribution</seealso> in the User's Guide</p>
+ <p><seecref marker="driver_entry"><c>driver_entry(3)</c></seecref>,
+ <seeerl marker="erlang"><c>erlang(3)</c></seeerl>,
+ <seeerl marker="kernel:erl_ddll"><c>erl_ddll(3)</c></seeerl>,
+ section <seeguide marker="alt_dist">How to Implement an Alternative
+ Carrier for the Erlang Distribution</seeguide> in the User's Guide</p>
</section>
</cref>
diff --git a/erts/doc/src/erl_ext_dist.xml b/erts/doc/src/erl_ext_dist.xml
index 322c04b740..b4357df4cd 100644
--- a/erts/doc/src/erl_ext_dist.xml
+++ b/erts/doc/src/erl_ext_dist.xml
@@ -46,12 +46,12 @@
of the different terms is application-specific.
</p>
<p>
- In Erlang the BIF <seealso marker="erts:erlang#term_to_binary/1">
- <c>erlang:term_to_binary/1,2</c></seealso> is used to convert a
+ In Erlang the BIF <seemfa marker="erts:erlang#term_to_binary/1">
+ <c>erlang:term_to_binary/1,2</c></seemfa> is used to convert a
term into the external format.
To convert binary data encoding to a term, the BIF
- <seealso marker="erts:erlang#binary_to_term/1">
- <c>erlang:binary_to_term/1</c></seealso> is used.
+ <seemfa marker="erts:erlang#binary_to_term/1">
+ <c>erlang:binary_to_term/1</c></seemfa> is used.
</p>
<p>
The distribution does this implicitly when sending messages across
@@ -76,10 +76,10 @@
<note>
<p>
When messages are
- <seealso marker="erl_dist_protocol#connected_nodes">passed between
- connected nodes</seealso> and a
- <seealso marker="#distribution_header">distribution
- header</seealso> is used, the first byte containing the version
+ <seeguide marker="erl_dist_protocol#connected_nodes">passed between
+ connected nodes</seeguide> and a
+ <seeguide marker="#distribution_header">distribution
+ header</seeguide> is used, the first byte containing the version
number (131) is omitted from the terms that follow the distribution
header. This is because the version number is implied by the version
number in the distribution header.
@@ -121,10 +121,10 @@
<note>
<p>As from ERTS 9.0 (OTP 20), atoms may contain any Unicode
characters and are always encoded using the UTF-8 external formats
- <seealso marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seealso>
- or <seealso marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seealso>.
- The old Latin-1 formats <seealso marker="#ATOM_EXT"><c>ATOM_EXT</c></seealso>
- and <seealso marker="#SMALL_ATOM_EXT"><c>SMALL_ATOM_EXT</c></seealso>
+ <seeguide marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seeguide>
+ or <seeguide marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seeguide>.
+ The old Latin-1 formats <seeguide marker="#ATOM_EXT"><c>ATOM_EXT</c></seeguide>
+ and <seeguide marker="#SMALL_ATOM_EXT"><c>SMALL_ATOM_EXT</c></seeguide>
are deprecated and are only kept for backward
compatibility when decoding terms encoded by older nodes.</p>
<p>Support for UTF-8 encoded atoms in the external format has been
@@ -142,19 +142,19 @@
<p>
The distribution header is sent by the erlang distribution to
carry metadata about the coming
- <seealso marker="erl_dist_protocol#control_message">control message</seealso>
+ <seeguide marker="erl_dist_protocol#control_message">control message</seeguide>
and potential payload. It is primarily used to handle the atom cache
in the Erlang distribution. Since OTP-22 it is also used to fragment
large distribution messages into multiple smaller fragments.
For more information about how the distribution uses the distribution header,
see the documentation of the
- <seealso marker="erl_dist_protocol#connected_nodes">protocol between
- connected nodes</seealso> in the
- <seealso marker="erl_dist_protocol">distribution protocol</seealso>
+ <seeguide marker="erl_dist_protocol#connected_nodes">protocol between
+ connected nodes</seeguide> in the
+ <seeguide marker="erl_dist_protocol">distribution protocol</seeguide>
documentation.
</p>
<p>
- Any <seealso marker="#ATOM_CACHE_REF">ATOM_CACHE_REF</seealso>
+ Any <seeguide marker="#ATOM_CACHE_REF">ATOM_CACHE_REF</seeguide>
entries with corresponding <c>AtomCacheReferenceIndex</c> in terms
encoded on the external format following a distribution header refer
to the atom cache references made in the distribution header. The range
@@ -217,8 +217,13 @@
256, that is, an atom cache can contain 2048 entries.
</p>
<p>
- After flag fields for atom cache references, another half byte flag
- field is located with the following format:
+ Another half byte flag field is located along with flag fields for
+ atom cache references. When <c>NumberOfAtomCacheRefs</c> is even,
+ this half byte is the least significant half byte of the byte that
+ follows the atom cache references. When <c>NumberOfAtomCacheRefs</c>
+ is odd, this half byte is the most significant half byte of the last
+ byte of the atom cache references (on the wire, it will appear before
+ the last cache reference). It has the following format:
</p>
<table align="left">
<row>
@@ -264,11 +269,11 @@
consists of. Length is a 2 byte big-endian integer
if flag <c>LongAtoms</c> has been set, otherwise a 1 byte
integer. When distribution flag
- <seealso marker="erl_dist_protocol#dflags">
- <c>DFLAG_UTF8_ATOMS</c></seealso>
+ <seeguide marker="erl_dist_protocol#DFLAG_UTF8_ATOMS">
+ <c>DFLAG_UTF8_ATOMS</c></seeguide>
has been exchanged between both nodes in the
- <seealso marker="erl_dist_protocol#distribution_handshake">
- distribution handshake</seealso>,
+ <seeguide marker="erl_dist_protocol#distribution_handshake">
+ distribution handshake</seeguide>,
characters in <c>AtomText</c> are encoded in UTF-8, otherwise
in Latin-1. The following <c>CachedAtomRef</c>s with the same
<c>SegmentIndex</c> and <c>InternalSegmentIndex</c> as this
@@ -278,7 +283,7 @@
</p>
<p>
For more information on encoding of atoms, see the
- <seealso marker="#utf8_atoms">note on UTF-8 encoded atoms</seealso>
+ <seeguide marker="#utf8_atoms">note on UTF-8 encoded atoms</seeguide>
in the beginning of this section.
</p>
<p>
@@ -309,15 +314,15 @@
quite large. Since OTP-22 it is possible to split large messages
into smaller fragments in order to allow smaller messages to be interleaved
between larges messages. It is only the <c>message</c> part of each
- <seealso marker="erl_dist_protocol#connected_nodes">distributed message</seealso>
+ <seeguide marker="erl_dist_protocol#connected_nodes">distributed message</seeguide>
that may be split using fragmentation. Therefore it is recommended to use the
- <seealso marker="erl_dist_protocol#new-ctrlmessages-for-erlang-otp-22">
- PAYLOAD control messages</seealso> introduced in OTP-22.
+ <seeguide marker="erl_dist_protocol#new-ctrlmessages-for-erlang-otp-22">
+ PAYLOAD control messages</seeguide> introduced in OTP-22.
</p>
<p>Fragmented distribution messages are only used if the receiving node
signals that it supports them via the
- <seealso marker="erl_dist_protocol#dflags">DFLAG_FRAGMENTS</seealso> distribution
- flag.</p>
+ <seeguide marker="erl_dist_protocol#DFLAG_FRAGMENTS">DFLAG_FRAGMENTS</seeguide>
+ distribution flag.</p>
<p>A process must complete the sending of a fragmented message before it
can start sending any other message on the same distribution channel.</p>
@@ -517,7 +522,7 @@
<tcaption>ATOM_CACHE_REF</tcaption></table>
<p>
Refers to the atom with <c>AtomCacheReferenceIndex</c> in the
- <seealso marker="#distribution_header">distribution header</seealso>.
+ <seeguide marker="#distribution_header">distribution header</seeguide>.
</p>
</section>
@@ -580,7 +585,7 @@
<p>
This term is used in minor version 0 of the external format;
it has been superseded by
- <seealso marker="#NEW_FLOAT_EXT"><c>NEW_FLOAT_EXT</c></seealso>.
+ <seeguide marker="#NEW_FLOAT_EXT"><c>NEW_FLOAT_EXT</c></seeguide>.
</p>
</section>
@@ -602,7 +607,7 @@
</row>
<tcaption>PORT_EXT</tcaption></table>
<p>
- Same as <seealso marker="#NEW_PORT_EXT"><c>NEW_PORT_EXT</c></seealso>
+ Same as <seeguide marker="#NEW_PORT_EXT"><c>NEW_PORT_EXT</c></seeguide>
except the <c>Creation</c> field is only one byte and only two
bits are significant, the rest are to be 0.
</p>
@@ -627,21 +632,24 @@
<tcaption>NEW_PORT_EXT</tcaption></table>
<p>
Encodes a port identifier (obtained from
- <seealso marker="erlang#open_port/2"><c>erlang:open_port/2</c></seealso>).
+ <seemfa marker="erlang#open_port/2"><c>erlang:open_port/2</c></seemfa>).
<c>Node</c> is an encoded atom, that is,
- <seealso marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seealso>,
- <seealso marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seealso>
- or <seealso marker="#ATOM_CACHE_REF"><c>ATOM_CACHE_REF</c></seealso>.
+ <seeguide marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seeguide>,
+ <seeguide marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seeguide>
+ or <seeguide marker="#ATOM_CACHE_REF"><c>ATOM_CACHE_REF</c></seeguide>.
<c>ID</c> is a 32-bit big endian unsigned integer. Only 28 bits are
significant; the rest are to be 0. The <c>Creation</c> works just like in
- <seealso marker="#NEW_PID_EXT"><c>NEW_PID_EXT</c></seealso>.
+ <seeguide marker="#NEW_PID_EXT"><c>NEW_PID_EXT</c></seeguide>.
Port operations are not allowed across node boundaries.
</p>
- <p>Introduced in OTP 19, but only to be decoded and echoed back. Not
- encoded for local ports. Planned to supersede <seealso marker="#PORT_EXT">
- <c>PORT_EXT</c></seealso> in OTP 23 when
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_BIG_CREATON</c></seealso>
- becomes mandatory.
+ <p><c>NEW_PORT_EXT</c> was introduced in OTP 19, but only to be decoded
+ and echoed back. Not encoded for local ports.
+ </p>
+ <p>In OTP 23 distribution flag
+ <seeguide marker="erl_dist_protocol#DFLAG_BIG_CREATION"><c>DFLAG_BIG_CREATION</c></seeguide>
+ became mandatory. All ports are now
+ encoded using <c>NEW_PORT_EXT</c>, even external ports received as <seeguide
+ marker="#PORT_EXT"><c>PORT_EXT</c></seeguide> from older nodes.
</p>
</section>
@@ -665,7 +673,7 @@
</row>
<tcaption>PID_EXT</tcaption></table>
<p>
- Same as <seealso marker="#NEW_PID_EXT"><c>NEW_PID_EXT</c></seealso>
+ Same as <seeguide marker="#NEW_PID_EXT"><c>NEW_PID_EXT</c></seeguide>
except the <c>Creation</c> field is only one byte and only two
bits are significant, the rest are to be 0.
</p>
@@ -696,10 +704,10 @@
<taglist>
<tag><c>Node</c></tag>
<item><p>The name of the originating node, encoded using
- <seealso marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seealso>,
- <seealso marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seealso>
- or <seealso
- marker="#ATOM_CACHE_REF"><c>ATOM_CACHE_REF</c></seealso>.</p>
+ <seeguide marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seeguide>,
+ <seeguide marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seeguide>
+ or <seeguide
+ marker="#ATOM_CACHE_REF"><c>ATOM_CACHE_REF</c></seeguide>.</p>
</item>
<tag><c>ID</c></tag>
<item><p>A 32-bit big endian unsigned integer. Only 15 bits are
@@ -715,15 +723,18 @@
values. This makes it possible to separate identifiers from old
(crashed) nodes from a new one. The value zero should be avoided for
normal operations as it is used as a wild card for debug purpose
- (like a pid returned by <seealso marker="erts:erlang#list_to_pid/1">
- erlang:list_to_pid/1</seealso>).</p>
+ (like a pid returned by <seemfa marker="erts:erlang#list_to_pid/1">
+ erlang:list_to_pid/1</seemfa>).</p>
</item>
</taglist>
- <p>Introduced in OTP 19, but only to be decoded and echoed back. Not
- encoded for local processes. Planned to supersede <seealso marker="#PID_EXT">
- <c>PID_EXT</c></seealso> in OTP 23 when
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_BIG_CREATON</c></seealso>
- becomes mandatory.
+ <p><c>NEW_PID_EXT</c> was introduced in OTP 19, but only to be decoded
+ and echoed back. Not encoded for local processes.
+ </p>
+ <p>In OTP 23 distribution flag
+ <seeguide marker="erl_dist_protocol#DFLAG_BIG_CREATION"><c>DFLAG_BIG_CREATION</c></seeguide>
+ became mandatory. All pids are now encoded using <c>NEW_PID_EXT</c>,
+ even external pids received as
+ <seeguide marker="#PID_EXT"><c>PID_EXT</c></seeguide> from older nodes.
</p>
</section>
@@ -766,7 +777,7 @@
<tcaption>LARGE_TUPLE_EXT</tcaption></table>
<p>
Same as
- <seealso marker="#SMALL_TUPLE_EXT"><c>SMALL_TUPLE_EXT</c></seealso>
+ <seeguide marker="#SMALL_TUPLE_EXT"><c>SMALL_TUPLE_EXT</c></seeguide>
except that <c>Arity</c> is an
unsigned 4 byte integer in big-endian format.
</p>
@@ -837,7 +848,7 @@
As field <c>Length</c> is an unsigned 2 byte integer
(big-endian), implementations must ensure that lists longer than
65535 elements are encoded as
- <seealso marker="#LIST_EXT"><c>LIST_EXT</c></seealso>.
+ <seeguide marker="#LIST_EXT"><c>LIST_EXT</c></seeguide>.
</p>
</section>
@@ -861,7 +872,7 @@
<p>
<c>Length</c> is the number of elements that follows in section
<c>Elements</c>. <c>Tail</c> is the final tail of the list; it is
- <seealso marker="#NIL_EXT"><c>NIL_EXT</c></seealso>
+ <seeguide marker="#NIL_EXT"><c>NIL_EXT</c></seeguide>
for a proper list, but can be any type if the list is
improper (for example, <c>[a|b]</c>).
</p>
@@ -884,10 +895,10 @@
<tcaption>BINARY_EXT</tcaption></table>
<p>
Binaries are generated with bit syntax expression or with
- <seealso marker="erts:erlang#list_to_binary/1">
- <c>erlang:list_to_binary/1</c></seealso>,
- <seealso marker="erts:erlang#term_to_binary/1">
- <c>erlang:term_to_binary/1</c></seealso>,
+ <seemfa marker="erts:erlang#list_to_binary/1">
+ <c>erlang:list_to_binary/1</c></seemfa>,
+ <seemfa marker="erts:erlang#term_to_binary/1">
+ <c>erlang:term_to_binary/1</c></seemfa>,
or as input from binary ports.
The <c>Len</c> length field is an unsigned 4 byte integer
(big-endian).
@@ -940,8 +951,8 @@
</row>
<tcaption>LARGE_BIG_EXT</tcaption></table>
<p>
- Same as <seealso marker="#SMALL_BIG_EXT">
- <c>SMALL_BIG_EXT</c></seealso>
+ Same as <seeguide marker="#SMALL_BIG_EXT">
+ <c>SMALL_BIG_EXT</c></seeguide>
except that the length field is an unsigned 4 byte integer.
</p>
</section>
@@ -964,8 +975,8 @@
</row>
<tcaption>REFERENCE_EXT</tcaption></table>
<p>
- The same as <seealso marker="#NEW_REFERENCE_EXT">
- <c>NEW_REFERENCE_EXT</c></seealso> except <c>ID</c> is only one word
+ The same as <seeguide marker="#NEW_REFERENCE_EXT">
+ <c>NEW_REFERENCE_EXT</c></seeguide> except <c>ID</c> is only one word
(<c>Len</c> = 1).
</p>
</section>
@@ -990,8 +1001,8 @@
</row>
<tcaption>NEW_REFERENCE_EXT</tcaption></table>
<p>
- The same as <seealso marker="#NEWER_REFERENCE_EXT">
- <c>NEWER_REFERENCE_EXT</c></seealso> <em>except</em>:
+ The same as <seeguide marker="#NEWER_REFERENCE_EXT">
+ <c>NEWER_REFERENCE_EXT</c></seeguide> <em>except</em>:
</p>
<taglist>
<tag><c>ID</c></tag>
@@ -1025,14 +1036,14 @@
<tcaption>NEWER_REFERENCE_EXT</tcaption></table>
<p>
Encodes a reference term generated with
- <seealso marker="erts:erlang#make_ref/0">erlang:make_ref/0</seealso>.
+ <seemfa marker="erts:erlang#make_ref/0">erlang:make_ref/0</seemfa>.
</p>
<taglist>
<tag><c>Node</c></tag>
<item><p>The name of the originating node, encoded using
- <seealso marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seealso>,
- <seealso marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seealso>
- or <seealso marker="#ATOM_CACHE_REF"><c>ATOM_CACHE_REF</c></seealso>.</p>
+ <seeguide marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seeguide>,
+ <seeguide marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seeguide>
+ or <seeguide marker="#ATOM_CACHE_REF"><c>ATOM_CACHE_REF</c></seeguide>.</p>
</item>
<tag><c>Len</c></tag>
<item><p>A 16-bit big endian unsigned integer not larger than 3.</p>
@@ -1044,14 +1055,18 @@
</item>
<tag><c>Creation</c></tag>
<item><p>Works just like in
- <seealso marker="#NEW_PID_EXT"><c>NEW_PID_EXT</c></seealso>.</p>
+ <seeguide marker="#NEW_PID_EXT"><c>NEW_PID_EXT</c></seeguide>.</p>
</item>
</taglist>
- <p>Introduced in OTP 19, but only to be decoded and echoed back. Not
- encoded for local references. Planned to supersede <seealso marker="#NEW_REFERENCE_EXT">
- <c>NEW_REFERENCE_EXT</c></seealso> in OTP 23 when
- <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_BIG_CREATON</c></seealso>
- becomes mandatory.
+ <p><c>NEWER_REFERENCE_EXT</c> was introduced in OTP 19, but only to be decoded
+ and echoed back. Not encoded for local references.
+ </p>
+ <p>In OTP 23 distribution flag
+ <seeguide marker="erl_dist_protocol#DFLAG_BIG_CREATION"><c>DFLAG_BIG_CREATION</c></seeguide>
+ became mandatory. All references are now encoded using
+ <c>NEWER_REFERENCE_EXT</c>, even external references received as
+ <seeguide marker="#NEW_REFERENCE_EXT"><c>NEW_REFERENCE_EXT</c></seeguide>
+ from older nodes.
</p>
</section>
@@ -1082,35 +1097,35 @@
<tag><c>Pid</c></tag>
<item>
<p>A process identifier as in
- <seealso marker="#PID_EXT"><c>PID_EXT</c></seealso>.
+ <seeguide marker="#PID_EXT"><c>PID_EXT</c></seeguide>.
Represents the process in which the fun was created.
</p>
</item>
<tag><c>Module</c></tag>
<item>
<p>Encoded as an atom, using
- <seealso marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seealso>,
- <seealso marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seealso>,
- or <seealso marker="#ATOM_CACHE_REF">
- <c>ATOM_CACHE_REF</c></seealso>.
+ <seeguide marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seeguide>,
+ <seeguide marker="#SMALL_ATOM_UTF8_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seeguide>,
+ or <seeguide marker="#ATOM_CACHE_REF">
+ <c>ATOM_CACHE_REF</c></seeguide>.
This is the module that the fun is implemented in.
</p>
</item>
<tag><c>Index</c></tag>
<item>
<p>An integer encoded using
- <seealso marker="#SMALL_INTEGER_EXT">
- <c>SMALL_INTEGER_EXT</c></seealso>
- or <seealso marker="#INTEGER_EXT"><c>INTEGER_EXT</c></seealso>.
+ <seeguide marker="#SMALL_INTEGER_EXT">
+ <c>SMALL_INTEGER_EXT</c></seeguide>
+ or <seeguide marker="#INTEGER_EXT"><c>INTEGER_EXT</c></seeguide>.
It is typically a small index into the module's fun table.
</p>
</item>
<tag><c>Uniq</c></tag>
<item>
<p>An integer encoded using
- <seealso marker="#SMALL_INTEGER_EXT">
- <c>SMALL_INTEGER_EXT</c></seealso> or
- <seealso marker="#INTEGER_EXT"><c>INTEGER_EXT</c></seealso>.
+ <seeguide marker="#SMALL_INTEGER_EXT">
+ <c>SMALL_INTEGER_EXT</c></seeguide> or
+ <seeguide marker="#INTEGER_EXT"><c>INTEGER_EXT</c></seeguide>.
<c>Uniq</c> is the hash value of the parse for the fun.
</p>
</item>
@@ -1184,35 +1199,35 @@
<tag><c>Module</c></tag>
<item>
<p>Encoded as an atom, using
- <seealso marker="#ATOM_EXT"><c>ATOM_UTF8_EXT</c></seealso>,
- <seealso marker="#SMALL_ATOM_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seealso>,
- or <seealso marker="#ATOM_CACHE_REF">
- <c>ATOM_CACHE_REF</c></seealso>.
+ <seeguide marker="#ATOM_EXT"><c>ATOM_UTF8_EXT</c></seeguide>,
+ <seeguide marker="#SMALL_ATOM_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seeguide>,
+ or <seeguide marker="#ATOM_CACHE_REF">
+ <c>ATOM_CACHE_REF</c></seeguide>.
Is the module that the fun is implemented in.
</p>
</item>
<tag><c>OldIndex</c></tag>
<item>
<p>An integer encoded using
- <seealso marker="#SMALL_INTEGER_EXT">
- <c>SMALL_INTEGER_EXT</c></seealso> or
- <seealso marker="#INTEGER_EXT"><c>INTEGER_EXT</c></seealso>.
+ <seeguide marker="#SMALL_INTEGER_EXT">
+ <c>SMALL_INTEGER_EXT</c></seeguide> or
+ <seeguide marker="#INTEGER_EXT"><c>INTEGER_EXT</c></seeguide>.
Is typically a small index into the module's fun table.
</p>
</item>
<tag><c>OldUniq</c></tag>
<item>
<p>An integer encoded using
- <seealso marker="#SMALL_INTEGER_EXT">
- <c>SMALL_INTEGER_EXT</c></seealso> or
- <seealso marker="#INTEGER_EXT"><c>INTEGER_EXT</c></seealso>.
+ <seeguide marker="#SMALL_INTEGER_EXT">
+ <c>SMALL_INTEGER_EXT</c></seeguide> or
+ <seeguide marker="#INTEGER_EXT"><c>INTEGER_EXT</c></seeguide>.
<c>Uniq</c> is the hash value of the parse tree for the fun.
</p>
</item>
<tag><c>Pid</c></tag>
<item>
<p>A process identifier as in
- <seealso marker="#PID_EXT"><c>PID_EXT</c></seealso>.
+ <seeguide marker="#PID_EXT"><c>PID_EXT</c></seeguide>.
Represents the process in which the fun was created.
</p>
</item>
@@ -1247,14 +1262,14 @@
</p>
<p>
<c>Module</c> and <c>Function</c> are atoms
- (encoded using <seealso marker="#ATOM_EXT"><c>ATOM_UTF8_EXT</c></seealso>,
- <seealso marker="#SMALL_ATOM_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seealso>, or
- <seealso marker="#ATOM_CACHE_REF"><c>ATOM_CACHE_REF</c></seealso>).
+ (encoded using <seeguide marker="#ATOM_EXT"><c>ATOM_UTF8_EXT</c></seeguide>,
+ <seeguide marker="#SMALL_ATOM_EXT"><c>SMALL_ATOM_UTF8_EXT</c></seeguide>, or
+ <seeguide marker="#ATOM_CACHE_REF"><c>ATOM_CACHE_REF</c></seeguide>).
</p>
<p>
<c>Arity</c> is an integer encoded using
- <seealso marker="#SMALL_INTEGER_EXT">
- <c>SMALL_INTEGER_EXT</c></seealso>.
+ <seeguide marker="#SMALL_INTEGER_EXT">
+ <c>SMALL_INTEGER_EXT</c></seeguide>.
</p>
</section>
@@ -1329,7 +1344,7 @@
</p>
<p>
For more information on encoding of atoms, see the
- <seealso marker="#utf8_atoms">note on UTF-8 encoded atoms</seealso>
+ <seeguide marker="#utf8_atoms">note on UTF-8 encoded atoms</seeguide>
in the beginning of this section.
</p>
</section>
@@ -1353,11 +1368,11 @@
An atom is stored with a 1 byte unsigned length,
followed by <c>Len</c> bytes containing the <c>AtomName</c> encoded
in UTF-8. Longer atoms encoded in UTF-8 can be represented using
- <seealso marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seealso>.
+ <seeguide marker="#ATOM_UTF8_EXT"><c>ATOM_UTF8_EXT</c></seeguide>.
</p>
<p>
For more information on encoding of atoms, see the
- <seealso marker="#utf8_atoms">note on UTF-8 encoded atoms</seealso>
+ <seeguide marker="#utf8_atoms">note on UTF-8 encoded atoms</seeguide>
in the beginning of this section.
</p>
</section>
@@ -1408,10 +1423,10 @@
<p>
<c>SMALL_ATOM_EXT</c> was introduced in ERTS 5.7.2 and
require an exchange of distribution flag
- <seealso marker="erl_dist_protocol#dflags">
- <c>DFLAG_SMALL_ATOM_TAGS</c></seealso> in the
- <seealso marker="erl_dist_protocol#distribution_handshake">
- distribution handshake</seealso>.
+ <seeguide marker="erl_dist_protocol#DFLAG_SMALL_ATOM_TAGS">
+ <c>DFLAG_SMALL_ATOM_TAGS</c></seeguide> in the
+ <seeguide marker="erl_dist_protocol#distribution_handshake">
+ distribution handshake</seeguide>.
</p>
</note>
</section>
diff --git a/erts/doc/src/erl_ext_fig.gif b/erts/doc/src/erl_ext_fig.gif
index 14d6bbc871..40dd17bd5e 100644
--- a/erts/doc/src/erl_ext_fig.gif
+++ b/erts/doc/src/erl_ext_fig.gif
Binary files differ
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml
index ac520b3c24..57bff58db4 100644
--- a/erts/doc/src/erl_nif.xml
+++ b/erts/doc/src/erl_nif.xml
@@ -39,8 +39,8 @@
of an Erlang module. The native implemented functions (NIFs) are
called like any other functions without any difference to the
caller. A NIF library is built as a dynamically linked library file
- and loaded in runtime by calling <seealso marker="erlang#load_nif-2">
- <c>erlang:load_nif/2</c></seealso>.</p>
+ and loaded in runtime by calling <seemfa marker="erlang#load_nif/2">
+ <c>erlang:load_nif/2</c></seemfa>.</p>
<warning>
<marker id="WARNING"/>
<p><em>Use this functionality with extreme care.</em></p>
@@ -61,8 +61,8 @@
call to the native function.</p>
</item>
<item>
- <p>A native function doing <seealso marker="#lengthy_work">lengthy
- work</seealso> before returning degrades responsiveness of the VM,
+ <p>A native function doing <seecref marker="#lengthy_work">lengthy
+ work</seecref> before returning degrades responsiveness of the VM,
and can cause miscellaneous strange behaviors. Such strange
behaviors include, but are not limited to, extreme memory usage,
and bad load balancing between schedulers. Strange behaviors that
@@ -120,10 +120,10 @@ $> erl
"Hello world!"</code>
<p>
- In the example above the <seealso marker="doc/reference_manual:code_loading#on_load">
- <em><c>on_load</c></em></seealso> directive is used get function <c>init</c> called
+ In the example above the <seeguide marker="system/reference_manual:code_loading#on_load">
+ <em><c>on_load</c></em></seeguide> directive is used get function <c>init</c> called
automatically when the module is loaded. Function <c>init</c> in turn
- calls <seealso marker="erlang#load_nif-2"><c>erlang:load_nif/2</c></seealso>
+ calls <seemfa marker="erlang#load_nif/2"><c>erlang:load_nif/2</c></seemfa>
which loads the NIF library and replaces the <c>hello</c> function with its
native implementation in C. Once loaded, a NIF library is persistent. It
will not be unloaded until the module code version that it belongs to is
@@ -131,8 +131,8 @@ $> erl
<p>
Each NIF must have an implementation in Erlang to be invoked if the
function is called before the NIF library is successfully loaded. A
- typical such stub implementation is to call <seealso marker="erlang#nif_error-1">
- <c>erlang:nif_error</c></seealso> which will raise an exception. The
+ typical such stub implementation is to call <seemfa marker="erlang#nif_error/1">
+ <c>erlang:nif_error</c></seemfa> which will raise an exception. The
Erlang function can also be used as a fallback implementation if the NIF
library lacks implementation for some OS or hardware architecture for example.</p>
<note>
@@ -140,6 +140,21 @@ $> erl
However, unused local stub functions will be optimized
away by the compiler, causing loading of the NIF library to fail.</p>
</note>
+ <warning>
+ <p>
+ There is a known limitation for Erlang fallback functions of NIFs. Avoid
+ functions involved in traversal of binaries by matching and
+ recursion. If a NIF is loaded over such function, binary arguments to
+ the NIF may get corrupted and cause VM crash or other misbehavior.
+ </p>
+ <p>Example of such bad fallback function:</p>
+ <code type="none">
+skip_until(Byte, &lt;&lt;Byte, Rest/binary&gt;&gt;) ->
+ Rest;
+skip_until(Byte, &lt;&lt;_, Rest/binary&gt;&gt;) ->
+ skip_until(Byte, Rest).
+</code>
+ </warning>
</section>
<section>
@@ -153,7 +168,7 @@ $> erl
<item>
<p>Any Erlang terms can be passed to a NIF as function arguments and
be returned as function return values. The terms are of C-type
- <seealso marker="#ERL_NIF_TERM"><c>ERL_NIF_TERM</c></seealso> and can
+ <seecref marker="#ERL_NIF_TERM"><c>ERL_NIF_TERM</c></seecref> and can
only be read or written using API functions. Most functions to read
the content of a term are prefixed <c>enif_get_</c> and usually return
<c>true</c> (or <c>false</c>) if the term is of the expected type (or
@@ -163,7 +178,7 @@ $> erl
to query terms, like <c>enif_is_atom</c>, <c>enif_is_identical</c>,
and <c>enif_compare</c>.</p>
<p>All terms of type <c>ERL_NIF_TERM</c> belong to an environment of
- type <seealso marker="#ErlNifEnv"><c>ErlNifEnv</c></seealso>. The
+ type <seecref marker="#ErlNifEnv"><c>ErlNifEnv</c></seecref>. The
lifetime of a term is controlled by the lifetime of its environment
object. All API functions that read or write terms has the
environment that the term belongs to as the first function
@@ -172,27 +187,27 @@ $> erl
<tag>Binaries</tag>
<item>
<p>Terms of type binary are accessed with the help of struct type
- <seealso marker="#ErlNifBinary"><c>ErlNifBinary</c></seealso>,
+ <seecref marker="#ErlNifBinary"><c>ErlNifBinary</c></seecref>,
which contains a pointer (<c>data</c>) to the raw binary data and the
length (<c>size</c>) of the data in bytes. Both <c>data</c> and
<c>size</c> are read-only and are only to be written using calls to
API functions. Instances of <c>ErlNifBinary</c> are, however, always
allocated by the user (usually as local variables).</p>
<p>The raw data pointed to by <c>data</c> is only mutable after a call
- to <seealso marker="#enif_alloc_binary">
- <c>enif_alloc_binary</c></seealso> or
- <seealso marker="#enif_realloc_binary">
- <c>enif_realloc_binary</c></seealso>. All other functions that
+ to <seecref marker="#enif_alloc_binary">
+ <c>enif_alloc_binary</c></seecref> or
+ <seecref marker="#enif_realloc_binary">
+ <c>enif_realloc_binary</c></seecref>. All other functions that
operate on a binary leave the data as read-only.
A mutable binary must in the end either be freed with
- <seealso marker="#enif_release_binary">
- <c>enif_release_binary</c></seealso>
+ <seecref marker="#enif_release_binary">
+ <c>enif_release_binary</c></seecref>
or made read-only by transferring it to an Erlang term with
- <seealso marker="#enif_make_binary"><c>enif_make_binary</c></seealso>.
+ <seecref marker="#enif_make_binary"><c>enif_make_binary</c></seecref>.
However, it does not have to occur in the same NIF call. Read-only
binaries do not have to be released.</p>
- <p><seealso marker="#enif_make_new_binary">
- <c>enif_make_new_binary</c></seealso> can be used as a shortcut to
+ <p><seecref marker="#enif_make_new_binary">
+ <c>enif_make_new_binary</c></seecref> can be used as a shortcut to
allocate and return a binary in the same NIF call.</p>
<p>Binaries are sequences of whole bytes. Bitstrings with an arbitrary
bit length have no support yet.</p>
@@ -202,28 +217,28 @@ $> erl
<p>The use of resource objects is a safe way to return pointers to
native data structures from a NIF. A resource object is
only a block of memory allocated with
- <seealso marker="#enif_alloc_resource">
- <c>enif_alloc_resource</c></seealso>.
+ <seecref marker="#enif_alloc_resource">
+ <c>enif_alloc_resource</c></seecref>.
A handle ("safe pointer") to this memory block can then be returned
to Erlang by the use of
- <seealso marker="#enif_make_resource">
- <c>enif_make_resource</c></seealso>.
+ <seecref marker="#enif_make_resource">
+ <c>enif_make_resource</c></seecref>.
The term returned by <c>enif_make_resource</c> is opaque in nature.
It can be stored and passed between processes, but
the only real end usage is to pass it back as an argument to a NIF.
- The NIF can then call <seealso marker="#enif_get_resource">
- <c>enif_get_resource</c></seealso> and get back a pointer to the
+ The NIF can then call <seecref marker="#enif_get_resource">
+ <c>enif_get_resource</c></seecref> and get back a pointer to the
memory block, which is guaranteed to still be valid. A resource
object is not deallocated until the last handle term
is garbage collected by the VM and the resource is released with
- <seealso marker="#enif_release_resource">
- <c>enif_release_resource</c></seealso>
+ <seecref marker="#enif_release_resource">
+ <c>enif_release_resource</c></seecref>
(not necessarily in that order).</p>
<p>All resource objects are created as instances of some <em>resource
type</em>. This makes resources from different modules to be
distinguishable. A resource type is created by calling
- <seealso marker="#enif_open_resource_type">
- <c>enif_open_resource_type</c></seealso> when a library is loaded.
+ <seecref marker="#enif_open_resource_type">
+ <c>enif_open_resource_type</c></seecref> when a library is loaded.
Objects of that resource type can then later be allocated and
<c>enif_get_resource</c> verifies that the resource is of the
expected type. A resource type can have a user-supplied destructor
@@ -258,8 +273,8 @@ return term;</code>
the term.</p>
<p>Another use of resource objects is to create binary terms with
user-defined memory management.
- <seealso marker="#enif_make_resource_binary">
- <c>enif_make_resource_binary</c></seealso>
+ <seecref marker="#enif_make_resource_binary">
+ <c>enif_make_resource_binary</c></seecref>
creates a binary term that is connected to a resource object. The
destructor of the resource is called when the binary is garbage
collected, at which time the binary data can be released. An example
@@ -287,15 +302,15 @@ return term;</code>
unintentionally shared static data between module instances, each Erlang
module version can keep its own private data. This private data can be
set when the NIF library is loaded and later retrieved by calling
- <seealso marker="#enif_priv_data"><c>enif_priv_data</c></seealso>.</p>
+ <seecref marker="#enif_priv_data"><c>enif_priv_data</c></seecref>.</p>
</item>
<tag>Threads and concurrency</tag>
<item>
<p>A NIF is thread-safe without any explicit synchronization as
long as it acts as a pure function and only reads the supplied
arguments. When you write to a shared state either through
- static variables or <seealso marker="#enif_priv_data">
- <c>enif_priv_data</c></seealso>, you need to supply your own explicit
+ static variables or <seecref marker="#enif_priv_data">
+ <c>enif_priv_data</c></seecref>, you need to supply your own explicit
synchronization. This includes terms in process independent
environments that are shared between threads. Resource objects also
require synchronization if you treat them as mutable.</p>
@@ -335,16 +350,16 @@ return term;</code>
<item>
<p>Support for time measurement in NIF libraries:</p>
<list type="bulleted">
- <item><seealso marker="#ErlNifTime">
- <c>ErlNifTime</c></seealso></item>
- <item><seealso marker="#ErlNifTimeUnit">
- <c>ErlNifTimeUnit</c></seealso></item>
- <item><seealso marker="#enif_monotonic_time">
- <c>enif_monotonic_time()</c></seealso></item>
- <item><seealso marker="#enif_time_offset">
- <c>enif_time_offset()</c></seealso></item>
- <item><seealso marker="#enif_convert_time_unit">
- <c>enif_convert_time_unit()</c></seealso></item>
+ <item><seecref marker="#ErlNifTime">
+ <c>ErlNifTime</c></seecref></item>
+ <item><seecref marker="#ErlNifTimeUnit">
+ <c>ErlNifTimeUnit</c></seecref></item>
+ <item><seecref marker="#enif_monotonic_time">
+ <c>enif_monotonic_time()</c></seecref></item>
+ <item><seecref marker="#enif_time_offset">
+ <c>enif_time_offset()</c></seecref></item>
+ <item><seecref marker="#enif_convert_time_unit">
+ <c>enif_convert_time_unit()</c></seecref></item>
</list>
</item>
<tag><marker id="enif_ioq"/>I/O Queues</tag>
@@ -354,28 +369,28 @@ return term;</code>
The I/O Queue is not thread safe, so some other synchronization
mechanism has to be used.</p>
<list type="bulleted">
- <item><seealso marker="#SysIOVec">
- <c>SysIOVec</c></seealso></item>
- <item><seealso marker="#ErlNifIOVec">
- <c>ErlNifIOVec</c></seealso></item>
- <item><seealso marker="#enif_ioq_create">
- <c>enif_ioq_create()</c></seealso></item>
- <item><seealso marker="#enif_ioq_destroy">
- <c>enif_ioq_destroy()</c></seealso></item>
- <item><seealso marker="#enif_ioq_enq_binary">
- <c>enif_ioq_enq_binary()</c></seealso></item>
- <item><seealso marker="#enif_ioq_enqv">
- <c>enif_ioq_enqv()</c></seealso></item>
- <item><seealso marker="#enif_ioq_deq">
- <c>enif_ioq_deq()</c></seealso></item>
- <item><seealso marker="#enif_ioq_peek">
- <c>enif_ioq_peek()</c></seealso></item>
- <item><seealso marker="#enif_ioq_peek_head">
- <c>enif_ioq_peek_head()</c></seealso></item>
- <item><seealso marker="#enif_inspect_iovec">
- <c>enif_inspect_iovec()</c></seealso></item>
- <item><seealso marker="#enif_free_iovec">
- <c>enif_free_iovec()</c></seealso></item>
+ <item><seecref marker="#SysIOVec">
+ <c>SysIOVec</c></seecref></item>
+ <item><seecref marker="#ErlNifIOVec">
+ <c>ErlNifIOVec</c></seecref></item>
+ <item><seecref marker="#enif_ioq_create">
+ <c>enif_ioq_create()</c></seecref></item>
+ <item><seecref marker="#enif_ioq_destroy">
+ <c>enif_ioq_destroy()</c></seecref></item>
+ <item><seecref marker="#enif_ioq_enq_binary">
+ <c>enif_ioq_enq_binary()</c></seecref></item>
+ <item><seecref marker="#enif_ioq_enqv">
+ <c>enif_ioq_enqv()</c></seecref></item>
+ <item><seecref marker="#enif_ioq_deq">
+ <c>enif_ioq_deq()</c></seecref></item>
+ <item><seecref marker="#enif_ioq_peek">
+ <c>enif_ioq_peek()</c></seecref></item>
+ <item><seecref marker="#enif_ioq_peek_head">
+ <c>enif_ioq_peek_head()</c></seecref></item>
+ <item><seecref marker="#enif_inspect_iovec">
+ <c>enif_inspect_iovec()</c></seecref></item>
+ <item><seecref marker="#enif_free_iovec">
+ <c>enif_free_iovec()</c></seecref></item>
</list>
<p>Typical usage when writing to a file descriptor looks like this:</p>
<code type="none"><![CDATA[
@@ -426,7 +441,7 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
</item>
<tag><marker id="lengthy_work"/>Long-running NIFs</tag>
<item>
- <p>As mentioned in the <seealso marker="#WARNING">warning</seealso> text
+ <p>As mentioned in the <seecref marker="#WARNING">warning</seecref> text
at the beginning of this manual page, it is of <em>vital
importance</em> that a native function returns relatively fast. It is
difficult to give an exact maximum amount of time that a native
@@ -437,8 +452,8 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
divide the work into multiple chunks of work and call the native
function multiple times. This is, however, not always possible, for
example when calling third-party libraries.</p>
- <p>The <seealso marker="#enif_consume_timeslice">
- <c>enif_consume_timeslice()</c></seealso> function can be used to
+ <p>The <seecref marker="#enif_consume_timeslice">
+ <c>enif_consume_timeslice()</c></seecref> function can be used to
inform the runtime system about the length of the NIF call.
It is typically always to be used unless the NIF executes very
fast.</p>
@@ -457,8 +472,8 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
</item>
<item>
<p>Call a NIF that first performs a chunk of the work, then
- invokes the <seealso marker="#enif_schedule_nif">
- <c>enif_schedule_nif</c></seealso> function to schedule
+ invokes the <seecref marker="#enif_schedule_nif">
+ <c>enif_schedule_nif</c></seecref> function to schedule
another NIF call to perform the next chunk. The final call
scheduled in this manner can then return the overall
result.</p>
@@ -475,8 +490,8 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
<p>This is accomplished by dispatching the work to another thread
managed by the NIF library, return from the NIF, and wait for
the result. The thread can send the result back to the Erlang
- process using <seealso marker="#enif_send">
- <c>enif_send</c></seealso>.
+ process using <seecref marker="#enif_send">
+ <c>enif_send</c></seecref>.
Information about thread primitives is provided below.</p>
</item>
<tag><marker id="dirty_nifs"/>Dirty NIF</tag>
@@ -489,8 +504,8 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
SMP support does <em>not</em> support dirty schedulers even
when the dirty scheduler support is explicitly enabled. To
check at runtime for the presence of dirty scheduler threads,
- code can use the <seealso marker="#enif_system_info">
- <c>enif_system_info()</c></seealso> API function.</p>
+ code can use the <seecref marker="#enif_system_info">
+ <c>enif_system_info()</c></seecref> API function.</p>
</note>
<p>A NIF that cannot be split and cannot execute in a millisecond
or less is called a "dirty NIF", as it performs work that the
@@ -517,12 +532,12 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
<list type="bulleted">
<item>
<p>Set the appropriate flags value for the dirty NIF in its
- <seealso marker="#ErlNifFunc"> <c>ErlNifFunc</c></seealso>
+ <seecref marker="#ErlNifFunc"> <c>ErlNifFunc</c></seecref>
entry.</p>
</item>
<item>
- <p>Call <seealso marker="#enif_schedule_nif">
- <c>enif_schedule_nif</c></seealso>, pass to it a pointer
+ <p>Call <seecref marker="#enif_schedule_nif">
+ <c>enif_schedule_nif</c></seecref>, pass to it a pointer
to the dirty NIF to be executed, and indicate with argument
<c>flags</c> whether it expects the operation to be CPU-bound
or I/O-bound.</p>
@@ -533,24 +548,24 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
that it executes on the correct type of dirty scheduler at all
times. For more information see the documentation of the
<c>erl(1)</c> command line arguments
- <seealso marker="erl#+SDcpu"><c>+SDcpu</c></seealso>,
- and <seealso marker="erl#+SDio"><c>+SDio</c></seealso>.</p>
+ <seecom marker="erl#+SDcpu"><c>+SDcpu</c></seecom>,
+ and <seecom marker="erl#+SDio"><c>+SDio</c></seecom>.</p>
<p>While a process executes a dirty NIF, some operations that
communicate with it can take a very long time to complete.
Suspend or garbage collection of a process executing a dirty
NIF cannot be done until the dirty NIF has returned. Thus, other
processes waiting for such operations to complete might
have to wait for a very long time. Blocking multi-scheduling, that
- is, calling <seealso marker="erlang#system_flag_multi_scheduling">
- <c>erlang:system_flag(multi_scheduling, block)</c></seealso>, can
+ is, calling <seeerl marker="erlang#system_flag_multi_scheduling">
+ <c>erlang:system_flag(multi_scheduling, block)</c></seeerl>, can
also take a very long time to complete. This is because all ongoing
dirty operations on all dirty schedulers must complete before
the block operation can complete.</p>
<p>Many operations communicating with a process executing a
dirty NIF can, however, complete while it executes the
dirty NIF. For example, retrieving information about it through
- <seealso marker="erlang#process_info/1">
- <c>process_info</c></seealso>, setting its group leader,
+ <seemfa marker="erlang#process_info/1">
+ <c>process_info</c></seemfa>, setting its group leader,
register/unregister its name, and so on.</p>
<p>Termination of a process executing a dirty NIF can only be
completed up to a certain point while it executes the dirty NIF.
@@ -560,11 +575,11 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
can safely continue execution, allocate heap memory, and so on,
but it is of course better to stop executing as soon as possible.
The NIF can check whether a current process is alive using
- <seealso marker="#enif_is_current_process_alive">
- <c>enif_is_current_process_alive</c></seealso>. Communication
- using <seealso marker="#enif_send"><c>enif_send</c></seealso> and
- <seealso marker="#enif_port_command">
- <c>enif_port_command</c></seealso> is also dropped when the
+ <seecref marker="#enif_is_current_process_alive">
+ <c>enif_is_current_process_alive</c></seecref>. Communication
+ using <seecref marker="#enif_send"><c>enif_send</c></seecref> and
+ <seecref marker="#enif_port_command">
+ <c>enif_port_command</c></seecref> is also dropped when the
sending process is not alive. Deallocation of certain internal
resources, such as process heap and process control block, is
delayed until the dirty NIF has completed.</p>
@@ -609,8 +624,8 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
calls. <c>enif_priv_data</c> returns this pointer.
<c>*priv_data</c> is initialized to <c>NULL</c> when <c>load</c> is
called.</p>
- <p><c>load_info</c> is the second argument to <seealso
- marker="erlang#load_nif-2"><c>erlang:load_nif/2</c></seealso>.</p>
+ <p><c>load_info</c> is the second argument to <seemfa
+ marker="erlang#load_nif/2"><c>erlang:load_nif/2</c></seemfa>.</p>
<p>The library fails to load if <c>load</c> returns
anything other than <c>0</c>. <c>load</c> can be <c>NULL</c> if
initialization is not needed.</p>
@@ -648,7 +663,7 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
This is an opaque type and values of it can only by used either as
arguments to API functions or as return values from NIFs. All
<c>ERL_NIF_TERM</c>s belong to an environment
- (<seealso marker="#ErlNifEnv"><c>ErlNifEnv</c></seealso>).
+ (<seecref marker="#ErlNifEnv"><c>ErlNifEnv</c></seecref>).
A term cannot be destructed individually, it is valid until its
environment is destructed.</p>
</item>
@@ -660,7 +675,7 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
it can only be passed on to API functions. Three types of environments
exist:</p>
<taglist>
- <tag>Process bound environment</tag>
+ <tag><marker id="proc_bound_env"/>Process bound environment</tag>
<item>
<p>Passed as the first argument to all NIFs. All function arguments
passed to a NIF belong to that environment. The return value from
@@ -671,36 +686,36 @@ int writeiovec(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *tail,
returns. It is thus useless and dangerous to store pointers to
process bound environments between NIF calls.</p>
</item>
- <tag>Callback environment</tag>
+ <tag><marker id="callback_env"/>Callback environment</tag>
<item>
<p>Passed as the first argument to all the non-NIF callback functions
- (<seealso marker="#load"><c>load</c></seealso>,
- <seealso marker="#upgrade"><c>upgrade</c></seealso>,
- <seealso marker="#unload"><c>unload</c></seealso>,
- <seealso marker="#ErlNifResourceDtor"><c>dtor</c></seealso>,
- <seealso marker="#ErlNifResourceDown"><c>down</c></seealso> and
- <seealso marker="#ErlNifResourceStop"><c>stop</c></seealso>).
+ (<seecref marker="#load"><c>load</c></seecref>,
+ <seecref marker="#upgrade"><c>upgrade</c></seecref>,
+ <seecref marker="#unload"><c>unload</c></seecref>,
+ <seecref marker="#ErlNifResourceDtor"><c>dtor</c></seecref>,
+ <seecref marker="#ErlNifResourceDown"><c>down</c></seecref> and
+ <seecref marker="#ErlNifResourceStop"><c>stop</c></seecref>).
Works like a process bound environment but with a temporary
pseudo process that "terminates" when the callback has
returned. Terms may be created in this environment but they will
only be accessible during the callback.</p>
</item>
- <tag>Process independent environment</tag>
+ <tag><marker id="proc_indep_env"/>Process independent environment</tag>
<item>
- <p>Created by calling <seealso marker="#enif_alloc_env">
- <c>enif_alloc_env</c></seealso>. This environment can be
+ <p>Created by calling <seecref marker="#enif_alloc_env">
+ <c>enif_alloc_env</c></seecref>. This environment can be
used to store terms between NIF calls and to send terms with
- <seealso marker="#enif_send"><c>enif_send</c></seealso>. A
+ <seecref marker="#enif_send"><c>enif_send</c></seecref>. A
process independent environment with all its terms is valid until
you explicitly invalidate it with
- <seealso marker="#enif_free_env"><c>enif_free_env</c></seealso>
+ <seecref marker="#enif_free_env"><c>enif_free_env</c></seecref>
or <c>enif_send</c>.</p>
</item>
</taglist>
<p>All contained terms of a list/tuple/map must belong to the same
environment as the list/tuple/map itself. Terms can be copied between
environments with
- <seealso marker="#enif_make_copy"><c>enif_make_copy</c></seealso>.</p>
+ <seecref marker="#enif_make_copy"><c>enif_make_copy</c></seecref>.</p>
</item>
<tag><marker id="ErlNifFunc"/><c>ErlNifFunc</c></tag>
<item>
@@ -734,7 +749,7 @@ typedef struct {
<p>Is <c>0</c> for a regular NIF (and so its value can be omitted
for statically initialized <c>ErlNifFunc</c> instances).</p>
<p><c>flags</c> can be used to indicate that the NIF is a
- <seealso marker="#dirty_nifs">dirty NIF</seealso> that is to be
+ <seecref marker="#dirty_nifs">dirty NIF</seecref> that is to be
executed on a dirty scheduler thread.</p>
<p>If the dirty NIF is expected to be CPU-bound, its <c>flags</c>
field is to be set to <c>ERL_NIF_DIRTY_JOB_CPU_BOUND</c> or
@@ -763,8 +778,8 @@ typedef struct {
<tag><marker id="ErlNifBinaryToTerm"/><c>ErlNifBinaryToTerm</c></tag>
<item>
<p>An enumeration of the options that can be specified to
- <seealso marker="#enif_binary_to_term">
- <c>enif_binary_to_term</c></seealso>.
+ <seecref marker="#enif_binary_to_term">
+ <c>enif_binary_to_term</c></seecref>.
For default behavior, use value <c>0</c>.</p>
<p>When receiving data from untrusted sources, use option
<c>ERL_NIF_BIN2TERM_SAFE</c>.</p>
@@ -773,19 +788,19 @@ typedef struct {
<item>
<p>This is an opaque data type that identifies a monitor.</p>
<p>The nif writer is to provide the memory for storing the
- monitor when calling <seealso marker="#enif_monitor_process">
- <c>enif_monitor_process</c></seealso>. The
+ monitor when calling <seecref marker="#enif_monitor_process">
+ <c>enif_monitor_process</c></seecref>. The
address of the data is not stored by the runtime system, so
<c>ErlNifMonitor</c> can be used as any other data, it
can be copied, moved in memory, forgotten, and so on.
- To compare two monitors, <seealso marker="#enif_compare_monitors">
- <c>enif_compare_monitors</c></seealso> must be used.</p>
+ To compare two monitors, <seecref marker="#enif_compare_monitors">
+ <c>enif_compare_monitors</c></seecref> must be used.</p>
</item>
<tag><marker id="ErlNifPid"/><c>ErlNifPid</c></tag>
<item>
<p>A process identifier (pid). In contrast to pid terms (instances of
<c>ERL_NIF_TERM</c>), <c>ErlNifPid</c>s are self-contained and not
- bound to any <seealso marker="#ErlNifEnv">environment</seealso>.
+ bound to any <seecref marker="#ErlNifEnv">environment</seecref>.
<c>ErlNifPid</c> is an opaque type. It can be copied, moved
in memory, forgotten, and so on.</p>
</item>
@@ -793,7 +808,7 @@ typedef struct {
<item>
<p>A port identifier. In contrast to port ID terms (instances of
<c>ERL_NIF_TERM</c>), <c>ErlNifPort</c>s are self-contained and not
- bound to any <seealso marker="#ErlNifEnv">environment</seealso>.
+ bound to any <seecref marker="#ErlNifEnv">environment</seecref>.
<c>ErlNifPort</c> is an opaque type. It can be copied, moved
in memory, forgotten, and so on.</p>
</item>
@@ -812,8 +827,8 @@ typedef struct {
ErlNifResourceStop* stop;
ErlNifResourceDown* down;
} ErlNifResourceTypeInit;</code>
- <p>Initialization structure read by <seealso marker="#enif_open_resource_type_x">
- enif_open_resource_type_x</seealso>.</p>
+ <p>Initialization structure read by <seecref marker="#enif_open_resource_type_x">
+ enif_open_resource_type_x</seecref>.</p>
</item>
<tag><marker id="ErlNifResourceDtor"/><c>ErlNifResourceDtor</c></tag>
<item>
@@ -830,8 +845,8 @@ typedef void ErlNifResourceDtor(ErlNifEnv* caller_env, void* obj);</code>
<code type="none">
typedef void ErlNifResourceDown(ErlNifEnv* caller_env, void* obj, ErlNifPid* pid, ErlNifMonitor* mon);</code>
<p>The function prototype of a resource down function,
- called on the behalf of <seealso marker="#enif_monitor_process">
- enif_monitor_process</seealso>. <c>obj</c> is the resource, <c>pid</c>
+ called on the behalf of <seecref marker="#enif_monitor_process">
+ enif_monitor_process</seecref>. <c>obj</c> is the resource, <c>pid</c>
is the identity of the monitored process that is exiting, and <c>mon</c>
is the identity of the monitor.
</p>
@@ -841,8 +856,8 @@ typedef void ErlNifResourceDown(ErlNifEnv* caller_env, void* obj, ErlNifPid* pid
<code type="none">
typedef void ErlNifResourceStop(ErlNifEnv* caller_env, void* obj, ErlNifEvent event, int is_direct_call);</code>
<p>The function prototype of a resource stop function,
- called on the behalf of <seealso marker="#enif_select">
- enif_select</seealso>. <c>obj</c> is the resource, <c>event</c> is OS event,
+ called on the behalf of <seecref marker="#enif_select">
+ enif_select</seecref>. <c>obj</c> is the resource, <c>event</c> is OS event,
<c>is_direct_call</c> is true if the call is made directly from <c>enif_select</c>
or false if it is a scheduled call (potentially from another thread).</p>
</item>
@@ -858,11 +873,11 @@ typedef enum {
</item>
<tag><marker id="ErlNifSysInfo"/><c>ErlNifSysInfo</c></tag>
<item>
- <p>Used by <seealso marker="#enif_system_info">
- <c>enif_system_info</c></seealso> to return information about the
+ <p>Used by <seecref marker="#enif_system_info">
+ <c>enif_system_info</c></seecref> to return information about the
runtime system. Contains the same content as
- <seealso marker="erl_driver#ErlDrvSysInfo">
- <c>ErlDrvSysInfo</c></seealso>.</p>
+ <seecref marker="erl_driver#ErlDrvSysInfo">
+ <c>ErlDrvSysInfo</c></seecref>.</p>
</item>
<tag><marker id="ErlNifSInt64"/><c>ErlNifSInt64</c></tag>
<item>
@@ -893,8 +908,8 @@ typedef enum {
<tag><marker id="ErlNifUniqueInteger"/><c>ErlNifUniqueInteger</c></tag>
<item>
<p>An enumeration of the properties that can be requested from
- <seealso marker="#enif_make_unique_integer">
- <c>enif_make_unique_integer</c></seealso>.
+ <seecref marker="#enif_make_unique_integer">
+ <c>enif_make_unique_integer</c></seecref>.
For default properties, use value <c>0</c>.</p>
<taglist>
<tag><c>ERL_NIF_UNIQUE_POSITIVE</c></tag>
@@ -903,9 +918,9 @@ typedef enum {
</item>
<tag><c>ERL_NIF_UNIQUE_MONOTONIC</c></tag>
<item>
- <p>Return only <seealso
+ <p>Return only <seeguide
marker="time_correction#Strictly_Monotonically_Increasing">
- strictly monotonically increasing</seealso> integer corresponding
+ strictly monotonically increasing</seeguide> integer corresponding
to creation time.</p>
</item>
</taglist>
@@ -913,7 +928,7 @@ typedef enum {
<tag><marker id="ErlNifHash"/><c>ErlNifHash</c></tag>
<item>
<p>An enumeration of the supported hash types that can be generated
- using <seealso marker="#enif_hash"><c>enif_hash</c></seealso>.
+ using <seecref marker="#enif_hash"><c>enif_hash</c></seecref>.
</p>
<taglist>
<tag><c>ERL_NIF_INTERNAL_HASH</c></tag>
@@ -928,7 +943,7 @@ typedef enum {
same Erlang term regardless of machine architecture and ERTS version.</p>
<p><em>It ignores salt values</em> and generates hashes within <c>0..2^27-1</c>.</p>
<p>Slower than <c>ERL_NIF_INTERNAL_HASH.</c>
- It corresponds to <seealso marker="erlang#phash2-1"><c>erlang:phash2/1</c></seealso>.
+ It corresponds to <seemfa marker="erlang#phash2/1"><c>erlang:phash2/1</c></seemfa>.
</p>
</item>
</taglist>
@@ -938,7 +953,7 @@ typedef enum {
<p>A system I/O vector, as used by <c>writev</c> on
Unix and <c>WSASend</c> on Win32. It is used in
<c>ErlNifIOVec</c> and by
- <seealso marker="#enif_ioq_peek"><c>enif_ioq_peek</c></seealso>.</p>
+ <seecref marker="#enif_ioq_peek"><c>enif_ioq_peek</c></seecref>.</p>
</item>
<tag><marker id="ErlNifIOVec"/><c>ErlNifIOVec</c></tag>
<item>
@@ -950,10 +965,10 @@ typedef struct {
} ErlNifIOVec;</code>
<p>An I/O vector containing <c>iovcnt</c> <c>SysIOVec</c>s
pointing to the data. It is used by
- <seealso marker="#enif_inspect_iovec">
- <c>enif_inspect_iovec</c></seealso> and
- <seealso marker="#enif_ioq_enqv">
- <c>enif_ioq_enqv</c></seealso>.</p>
+ <seecref marker="#enif_inspect_iovec">
+ <c>enif_inspect_iovec</c></seecref> and
+ <seecref marker="#enif_ioq_enqv">
+ <c>enif_ioq_enqv</c></seecref>.</p>
</item>
<tag><marker id="ErlNifIOQueueOpts"/><c>ErlNifIOQueueOpts</c></tag>
<item>
@@ -987,15 +1002,15 @@ typedef struct {
<p>Allocates a new binary of size <c>size</c> bytes.
Initializes the structure pointed to by <c>bin</c> to
refer to the allocated binary. The binary must either be released by
- <seealso marker="#enif_release_binary">
- <c>enif_release_binary</c></seealso>
+ <seecref marker="#enif_release_binary">
+ <c>enif_release_binary</c></seecref>
or ownership transferred to an Erlang term with
- <seealso marker="#enif_make_binary"><c>enif_make_binary</c></seealso>.
+ <seecref marker="#enif_make_binary"><c>enif_make_binary</c></seecref>.
An allocated (and owned) <c>ErlNifBinary</c> can be kept between NIF
calls.</p>
<p>If you do not need to reallocate or keep the data alive across NIF
- calls, consider using <seealso marker="#enif_make_new_binary">
- <c>enif_make_new_binary</c></seealso> instead as it will allocate
+ calls, consider using <seecref marker="#enif_make_new_binary">
+ <c>enif_make_new_binary</c></seecref> instead as it will allocate
small binaries on the process heap when possible.</p>
<p>Returns <c>true</c> on success, or <c>false</c> if allocation
fails.</p>
@@ -1006,12 +1021,14 @@ typedef struct {
<name since="OTP R14B"><ret>ErlNifEnv *</ret><nametext>enif_alloc_env()</nametext></name>
<fsummary>Create a new environment.</fsummary>
<desc>
- <p>Allocates a new process independent environment. The environment can
+ <p>
+ Allocates a new <seecref marker="#proc_indep_env">process
+ independent environment</seecref>. The environment can
be used to hold terms that are not bound to any process. Such terms
can later be copied to a process environment with
- <seealso marker="#enif_make_copy"><c>enif_make_copy</c></seealso> or
- be sent to a process as a message with <seealso marker="#enif_send">
- <c>enif_send</c></seealso>.</p>
+ <seecref marker="#enif_make_copy"><c>enif_make_copy</c></seecref> or
+ be sent to a process as a message with <seecref marker="#enif_send">
+ <c>enif_send</c></seecref>.</p>
<p>Returns pointer to the new environment.</p>
</desc>
</func>
@@ -1036,18 +1053,18 @@ typedef struct {
<c>data</c>, which must be encoded according to the Erlang external
term format. No more than <c>size</c> bytes are read from <c>data</c>.
Argument <c>opts</c> corresponds to the second argument to
- <seealso marker="erlang#binary_to_term-2">
- <c>erlang:binary_to_term/2</c></seealso> and must be either <c>0</c>
+ <seemfa marker="erlang#binary_to_term/2">
+ <c>erlang:binary_to_term/2</c></seemfa> and must be either <c>0</c>
or <c>ERL_NIF_BIN2TERM_SAFE</c>.</p>
<p>On success, stores the resulting term at <c>*term</c> and returns
the number of bytes read. Returns <c>0</c> if decoding fails or if
<c>opts</c> is invalid.</p>
- <p>See also <seealso marker="#ErlNifBinaryToTerm">
- <c>ErlNifBinaryToTerm</c></seealso>,
- <seealso marker="erlang#binary_to_term-2">
- <c>erlang:binary_to_term/2</c></seealso>, and
- <seealso marker="#enif_term_to_binary">
- <c>enif_term_to_binary</c></seealso>.</p>
+ <p>See also <seecref marker="#ErlNifBinaryToTerm">
+ <c>ErlNifBinaryToTerm</c></seecref>,
+ <seemfa marker="erlang#binary_to_term/2">
+ <c>erlang:binary_to_term/2</c></seemfa>, and
+ <seecref marker="#enif_term_to_binary">
+ <c>enif_term_to_binary</c></seecref>.</p>
</desc>
</func>
@@ -1058,7 +1075,7 @@ typedef struct {
<desc>
<p>Frees all terms in an environment and clears it for reuse.
The environment must have been allocated with
- <seealso marker="#enif_alloc_env"><c>enif_alloc_env</c></seealso>.</p>
+ <seecref marker="#enif_alloc_env"><c>enif_alloc_env</c></seecref>.</p>
</desc>
</func>
@@ -1083,7 +1100,7 @@ typedef struct {
<fsummary>Compare two monitors.</fsummary>
<desc>
<marker id="enif_compare_monitors"></marker>
- <p>Compares two <seealso marker="#ErlNifMonitor"><c>ErlNifMonitor</c></seealso>s.
+ <p>Compares two <seecref marker="#ErlNifMonitor"><c>ErlNifMonitor</c></seecref>s.
Can also be used to imply some artificial order on monitors,
for whatever reason.</p>
<p>Returns <c>0</c> if <c>monitor1</c> and <c>monitor2</c> are equal,
@@ -1098,8 +1115,8 @@ typedef struct {
</nametext></name>
<fsummary>Compare two pids.</fsummary>
<desc>
- <p>Compares two <seealso marker="#ErlNifPid"><c>ErlNifPid</c>
- </seealso>s according to term order.</p>
+ <p>Compares two <seecref marker="#ErlNifPid"><c>ErlNifPid</c>
+ </seecref>s according to term order.</p>
<p>Returns <c>0</c> if <c>pid1</c> and <c>pid2</c> are equal,
&lt; <c>0</c> if <c>pid1</c> &lt; <c>pid2</c>, and
&gt; <c>0</c> if <c>pid1</c> &gt; <c>pid2</c>.</p>
@@ -1111,8 +1128,8 @@ typedef struct {
<nametext>enif_cond_broadcast(ErlNifCond *cnd)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_cond_broadcast">
- <c>erl_drv_cond_broadcast</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_cond_broadcast">
+ <c>erl_drv_cond_broadcast</c></seecref>.</p>
</desc>
</func>
@@ -1121,8 +1138,8 @@ typedef struct {
<nametext>enif_cond_create(char *name)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_cond_create">
- <c>erl_drv_cond_create</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_cond_create">
+ <c>erl_drv_cond_create</c></seecref>.</p>
</desc>
</func>
@@ -1131,8 +1148,8 @@ typedef struct {
<nametext>enif_cond_destroy(ErlNifCond *cnd)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_cond_destroy">
- <c>erl_drv_cond_destroy</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_cond_destroy">
+ <c>erl_drv_cond_destroy</c></seecref>.</p>
</desc>
</func>
@@ -1141,8 +1158,8 @@ typedef struct {
<nametext>enif_cond_name(ErlNifCond* cnd)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_cond_name">
- <c>erl_drv_cond_name</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_cond_name">
+ <c>erl_drv_cond_name</c></seecref>.</p>
</desc>
</func>
@@ -1151,8 +1168,8 @@ typedef struct {
<nametext>enif_cond_signal(ErlNifCond *cnd)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_cond_signal">
- <c>erl_drv_cond_signal</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_cond_signal">
+ <c>erl_drv_cond_signal</c></seecref>.</p>
</desc>
</func>
@@ -1162,8 +1179,8 @@ typedef struct {
</name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_cond_wait">
- <c>erl_drv_cond_wait</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_cond_wait">
+ <c>erl_drv_cond_wait</c></seecref>.</p>
</desc>
</func>
@@ -1194,10 +1211,10 @@ typedef struct {
<p>This function is provided to better support co-operative scheduling,
improve system responsiveness, and make it easier to prevent
misbehaviors of the VM because of a NIF monopolizing a scheduler
- thread. It can be used to divide <seealso marker="#lengthy_work">
- length work</seealso> into a number of repeated NIF calls without the
+ thread. It can be used to divide <seecref marker="#lengthy_work">
+ length work</seecref> into a number of repeated NIF calls without the
need to create threads.</p>
- <p>See also the <seealso marker="#WARNING">warning</seealso> text at
+ <p>See also the <seecref marker="#WARNING">warning</seecref> text at
the beginning of this manual page.</p>
</desc>
</func>
@@ -1221,9 +1238,9 @@ typedef struct {
</taglist>
<p>Returns <c>ERL_NIF_TIME_ERROR</c> if called with an invalid
time unit argument.</p>
- <p>See also <seealso marker="#ErlNifTime"><c>ErlNifTime</c></seealso>
+ <p>See also <seecref marker="#ErlNifTime"><c>ErlNifTime</c></seecref>
and
- <seealso marker="#ErlNifTimeUnit"><c>ErlNifTimeUnit</c></seealso>.</p>
+ <seecref marker="#ErlNifTimeUnit"><c>ErlNifTimeUnit</c></seecref>.</p>
</desc>
</func>
@@ -1233,13 +1250,13 @@ typedef struct {
<fsummary></fsummary>
<desc>
<p>Returns the CPU time in the same format as
- <seealso marker="erlang#timestamp-0">
- <c>erlang:timestamp()</c></seealso>.
+ <seemfa marker="erlang#timestamp/0">
+ <c>erlang:timestamp()</c></seemfa>.
The CPU time is the time the current logical CPU has spent executing
since some arbitrary point in the past. If the OS does not support
fetching this value, <c>enif_cpu_time</c> invokes
- <seealso marker="#enif_make_badarg">
- <c>enif_make_badarg</c></seealso>.</p>
+ <seecref marker="#enif_make_badarg">
+ <c>enif_make_badarg</c></seecref>.</p>
</desc>
</func>
@@ -1249,12 +1266,16 @@ typedef struct {
<fsummary>Cancel a process monitor.</fsummary>
<desc>
<marker id="enif_demonitor_process"></marker>
- <p>Cancels a monitor created earlier with <seealso marker="#enif_monitor_process">
- <c>enif_monitor_process</c></seealso>. Argument <c>obj</c> is a pointer
+ <p>Cancels a monitor created earlier with <seecref marker="#enif_monitor_process">
+ <c>enif_monitor_process</c></seecref>. Argument <c>obj</c> is a pointer
to the resource holding the monitor and <c>*mon</c> identifies the
monitor.</p>
- <p>Argument <c>caller_env</c> is the environment of the calling process
- or callback. Must only be NULL if calling from a custom thread.</p>
+ <p>
+ Argument <c>caller_env</c> is the environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by ERTS.
+ </p>
<p>Returns <c>0</c> if the monitor was successfully identified and removed.
Returns a non-zero value if the monitor could not be identified, which means
it was either</p>
@@ -1276,8 +1297,8 @@ typedef struct {
</name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_equal_tids">
- <c>erl_drv_equal_tids</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_equal_tids">
+ <c>erl_drv_equal_tids</c></seecref>.</p>
</desc>
</func>
@@ -1287,7 +1308,7 @@ typedef struct {
<desc>
<p>Similar to <c>fprintf</c> but this format string also accepts
<c>"%T"</c>, which formats Erlang terms of type
- <seealso marker="#ERL_NIF_TERM"><c>ERL_NIF_TERM</c></seealso>.</p>
+ <seecref marker="#ERL_NIF_TERM"><c>ERL_NIF_TERM</c></seecref>.</p>
<p>This function is primarily intended for debugging purpose. It is not
recommended to print very large terms with <c>%T</c>. The function may
change <c>errno</c>, even if successful.</p>
@@ -1299,7 +1320,7 @@ typedef struct {
<fsummary>Free dynamic memory.</fsummary>
<desc>
<p>Frees memory allocated by
- <seealso marker="#enif_alloc"><c>enif_alloc</c></seealso>.</p>
+ <seecref marker="#enif_alloc"><c>enif_alloc</c></seecref>.</p>
</desc>
</func>
@@ -1309,7 +1330,7 @@ typedef struct {
<fsummary>Free an environment allocated with enif_alloc_env.</fsummary>
<desc>
<p>Frees an environment allocated with
- <seealso marker="#enif_alloc_env"><c>enif_alloc_env</c></seealso>.
+ <seecref marker="#enif_alloc_env"><c>enif_alloc_env</c></seecref>.
All terms created in the environment are freed as well.</p>
</desc>
</func>
@@ -1320,11 +1341,11 @@ typedef struct {
<fsummary>Free an ErlIOVec</fsummary>
<desc>
<p>Frees an io vector returned from
- <seealso marker="#enif_inspect_iovec">
- <c>enif_inspect_iovec</c></seealso>.
+ <seecref marker="#enif_inspect_iovec">
+ <c>enif_inspect_iovec</c></seecref>.
This is needed only if a <c>NULL</c> environment is passed to
- <seealso marker="#enif_inspect_iovec">
- <c>enif_inspect_iovec</c></seealso>.</p>
+ <seecref marker="#enif_inspect_iovec">
+ <c>enif_inspect_iovec</c></seecref>.</p>
<code type="none"><![CDATA[
ErlNifIOVec *iovec = NULL;
size_t max_elements = 128;
@@ -1348,7 +1369,7 @@ enif_free_iovec(iovec);]]></code>
<p>Writes a <c>NULL</c>-terminated string in the buffer pointed to by
<c>buf</c> of size <c>size</c>, consisting of the string
representation of the atom <c>term</c> with encoding
- <seealso marker="#ErlNifCharEncoding">encode</seealso>.</p>
+ <seecref marker="#ErlNifCharEncoding">encode</seecref>.</p>
<p>Returns the number of bytes written (including terminating
<c>NULL</c> character) or <c>0</c> if <c>term</c> is not an atom with
maximum length of <c>size-1</c>.</p>
@@ -1412,8 +1433,8 @@ enif_free_iovec(iovec);]]></code>
<c>true</c>. Otherwise returns <c>false</c>. No check is done to see
if the process is alive.</p>
<note><p><c>enif_get_local_pid</c> will return false if argument
- <c>term</c> is the atom <seealso marker="#enif_make_pid">
- <c>undefined</c></seealso>.</p></note>
+ <c>term</c> is the atom <seecref marker="#enif_make_pid">
+ <c>undefined</c></seecref>.</p></note>
</desc>
</func>
@@ -1513,7 +1534,7 @@ enif_free_iovec(iovec);]]></code>
<p>Writes a <c>NULL</c>-terminated string in the buffer pointed to by
<c>buf</c> with size <c>size</c>, consisting of the characters
in the string <c>list</c>. The characters are written using encoding
- <seealso marker="#ErlNifCharEncoding">encode</seealso>.</p>
+ <seecref marker="#ErlNifCharEncoding">encode</seecref>.</p>
<p>Returns one of the following:</p>
<list type="bulleted">
<item>The number of bytes written (including terminating <c>NULL</c>
@@ -1586,8 +1607,8 @@ enif_free_iovec(iovec);]]></code>
size_t *value_size)</nametext></name>
<fsummary>Get the value of an environment variable.</fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_getenv">
- <c>erl_drv_getenv</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_getenv">
+ <c>erl_drv_getenv</c></seecref>.</p>
</desc>
</func>
@@ -1600,15 +1621,15 @@ enif_free_iovec(iovec);]]></code>
environment <c>env</c>. If <c>reason</c> is a <c>NULL</c> pointer,
ignore it. Otherwise, if a pending exception associated with
<c>env</c> exists, set <c>*reason</c> to the value of the exception
- term. For example, if <seealso marker="#enif_make_badarg">
- <c>enif_make_badarg</c></seealso> is called to set a pending
+ term. For example, if <seecref marker="#enif_make_badarg">
+ <c>enif_make_badarg</c></seecref> is called to set a pending
<c>badarg</c> exception, a later call to
<c>enif_has_pending_exception(env, &amp;reason)</c> sets
<c>*reason</c> to the atom <c>badarg</c>, then return <c>true</c>.</p>
- <p>See also <seealso marker="#enif_make_badarg">
- <c>enif_make_badarg</c></seealso> and
- <seealso marker="#enif_raise_exception">
- <c>enif_raise_exception</c></seealso>.</p>
+ <p>See also <seecref marker="#enif_make_badarg">
+ <c>enif_make_badarg</c></seecref> and
+ <seecref marker="#enif_raise_exception">
+ <c>enif_raise_exception</c></seecref>.</p>
</desc>
</func>
@@ -1620,7 +1641,7 @@ enif_free_iovec(iovec);]]></code>
<fsummary>Hash terms.</fsummary>
<desc>
<p>Hashes <c>term</c> according to the specified
- <seealso marker="#ErlNifHash"><c>ErlNifHash</c></seealso> <c>type</c>.</p>
+ <seecref marker="#ErlNifHash"><c>ErlNifHash</c></seecref> <c>type</c>.</p>
<p>Ranges of taken salt (if any) and returned value depend on the hash type.</p>
</desc>
</func>
@@ -1664,8 +1685,8 @@ enif_free_iovec(iovec);]]></code>
<c>max_elements</c> on some platforms.
</p>
<p>To create a list of binaries from an arbitrary iolist, use
- <seealso marker="erts:erlang#iolist_to_iovec/1">
- <c>erlang:iolist_to_iovec/1</c></seealso>.</p>
+ <seemfa marker="erts:erlang#iolist_to_iovec/1">
+ <c>erlang:iolist_to_iovec/1</c></seemfa>.</p>
<p>When calling this function, <c>iovec</c> should contain a pointer to
<c>NULL</c> or a ErlNifIOVec structure that should be used if
possible. e.g.
@@ -1684,8 +1705,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
call returns, it is possible to call this function with a
<c>NULL</c> environment. If no environment is given the <c>iovec</c>
owns the data in the vector and it has to be explicitly freed using
- <seealso marker="#enif_free_iovec"><c>enif_free_iovec</c>
- </seealso>.</p>
+ <seecref marker="#enif_free_iovec"><c>enif_free_iovec</c>
+ </seecref>.</p>
<p>Returns <c>true</c> on success, or <c>false</c> if <c>iovec_term</c>
not an iovec.</p>
</desc>
@@ -1756,7 +1777,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<p>Get the I/O queue as a pointer to an array of <c>SysIOVec</c>s.
It also returns the number of elements in <c>iovlen</c>.</p>
<p>Nothing is removed from the queue by this function, that must be done
- with <seealso marker="#enif_ioq_deq"><c>enif_ioq_deq</c></seealso>.</p>
+ with <seecref marker="#enif_ioq_deq"><c>enif_ioq_deq</c></seecref>.</p>
<p>The returned array is suitable to use with the Unix system
call <c>writev</c>.</p>
</desc>
@@ -1771,7 +1792,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<p>If <c>size</c> is not <c>NULL</c>, the size of the head is placed
there.</p>
<p>Nothing is removed from the queue by this function, that must be done
- with <seealso marker="#enif_ioq_deq"><c>enif_ioq_deq</c></seealso>.</p>
+ with <seecref marker="#enif_ioq_deq"><c>enif_ioq_deq</c></seecref>.</p>
<p>Returns <c>true</c> on success, or <c>false</c> if the queue is
empty.</p>
</desc>
@@ -1901,8 +1922,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<fsummary>Determine if pid is undefined.</fsummary>
<desc>
<p>Returns <c>true</c> if <c>pid</c> has been set as undefined by
- <seealso marker="#enif_set_pid_undefined"><c>enif_set_pid_undefined</c>
- </seealso>.</p>
+ <seecref marker="#enif_set_pid_undefined"><c>enif_set_pid_undefined</c>
+ </seecref>.</p>
</desc>
</func>
@@ -1967,11 +1988,11 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<fsummary>Add a reference to a resource object.</fsummary>
<desc>
<p>Adds a reference to resource object <c>obj</c> obtained from
- <seealso marker="#enif_alloc_resource">
- <c>enif_alloc_resource</c></seealso>. Each call to
+ <seecref marker="#enif_alloc_resource">
+ <c>enif_alloc_resource</c></seecref>. Each call to
<c>enif_keep_resource</c> for an object must be balanced by a call to
- <seealso marker="#enif_release_resource">
- <c>enif_release_resource</c></seealso>
+ <seecref marker="#enif_release_resource">
+ <c>enif_release_resource</c></seecref>
before the object is destructed.</p>
</desc>
</func>
@@ -1985,8 +2006,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<p>Creates an atom term from the <c>NULL</c>-terminated C-string
<c>name</c> with ISO Latin-1 encoding. If the length of <c>name</c>
exceeds the maximum length allowed for an atom (255 characters),
- <c>enif_make_atom</c> invokes <seealso marker="#enif_make_badarg">
- <c>enif_make_badarg</c></seealso>.</p>
+ <c>enif_make_atom</c> invokes <seecref marker="#enif_make_badarg">
+ <c>enif_make_badarg</c></seecref>.</p>
</desc>
</func>
@@ -1999,8 +2020,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<c>len</c>. <c>NULL</c> characters are treated as any other
characters. If <c>len</c> exceeds the maximum length
allowed for an atom (255 characters), <c>enif_make_atom</c> invokes
- <seealso marker="#enif_make_badarg">
- <c>enif_make_badarg</c></seealso>.</p>
+ <seecref marker="#enif_make_badarg">
+ <c>enif_make_badarg</c></seecref>.</p>
</desc>
</func>
@@ -2016,13 +2037,13 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
NIF attempts to return a non-exception term instead.</p>
<p>The return value from <c>enif_make_badarg</c> can be used only as
the return value from the NIF that invoked it (directly or indirectly)
- or be passed to <seealso marker="#enif_is_exception">
- <c>enif_is_exception</c></seealso>, but not to any other NIF API
+ or be passed to <seecref marker="#enif_is_exception">
+ <c>enif_is_exception</c></seecref>, but not to any other NIF API
function.</p>
- <p>See also <seealso marker="#enif_has_pending_exception">
- <c>enif_has_pending_exception</c></seealso> and
- <seealso marker="#enif_raise_exception">
- <c>enif_raise_exception</c></seealso>.</p>
+ <p>See also <seecref marker="#enif_has_pending_exception">
+ <c>enif_has_pending_exception</c></seecref> and
+ <seecref marker="#enif_raise_exception">
+ <c>enif_raise_exception</c></seecref>.</p>
<note>
<p>Before ERTS 7.0 (Erlang/OTP 18), the return value
from <c>enif_make_badarg</c> had to be returned from the NIF. This
@@ -2063,8 +2084,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<desc>
<p>Creates a floating-point term from a <c>double</c>. If argument
<c>double</c> is not finite or is NaN, <c>enif_make_double</c>
- invokes <seealso marker="#enif_make_badarg">
- <c>enif_make_badarg</c></seealso>.</p>
+ invokes <seecref marker="#enif_make_badarg">
+ <c>enif_make_badarg</c></seecref>.</p>
</desc>
</func>
@@ -2076,7 +2097,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<desc>
<p>Tries to create the term of an already existing atom from
the <c>NULL</c>-terminated C-string <c>name</c> with encoding
- <seealso marker="#ErlNifCharEncoding">encode</seealso>.</p>
+ <seecref marker="#ErlNifCharEncoding">encode</seecref>.</p>
<p>If the atom already exists, this function stores the term in
<c>*atom</c> and returns <c>true</c>, otherwise <c>false</c>.
Also returns <c>false</c> if the length of <c>name</c> exceeds the
@@ -2092,7 +2113,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<desc>
<p>Tries to create the term of an already existing atom from the
string <c>name</c> with length <c>len</c> and encoding
- <seealso marker="#ErlNifCharEncoding">encode</seealso>. <c>NULL</c>
+ <seecref marker="#ErlNifCharEncoding">encode</seecref>. <c>NULL</c>
characters are treated as any other characters.</p>
<p>If the atom already exists, this function stores the term in
<c>*atom</c> and returns <c>true</c>, otherwise <c>false</c>.
@@ -2258,8 +2279,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<fsummary>Make monitor term from the given monitor identifier.</fsummary>
<desc>
<p>Creates a term identifying the given monitor received from
- <seealso marker="#enif_monitor_process"><c>enif_monitor_process</c>
- </seealso>.</p>
+ <seecref marker="#enif_monitor_process"><c>enif_monitor_process</c>
+ </seecref>.</p>
<p>This function is primarily intended for debugging purpose.</p>
</desc>
</func>
@@ -2272,7 +2293,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<p>Allocates a binary of size <c>size</c> bytes and creates an owning
term. The binary data is mutable until the calling NIF returns.
This is a quick way to create a new binary without having to use
- <seealso marker="#ErlNifBinary"><c>ErlNifBinary</c></seealso>.
+ <seecref marker="#ErlNifBinary"><c>ErlNifBinary</c></seecref>.
The drawbacks are that the binary cannot be kept between NIF calls
and it cannot be reallocated.</p>
<p>Returns a pointer to the raw binary data and sets
@@ -2295,8 +2316,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</name>
<fsummary>Make a pid term.</fsummary>
<desc>
- <p>Makes a pid term or the atom <seealso marker="#enif_set_pid_undefined">
- <c>undefined</c></seealso> from <c>*pid</c>.</p>
+ <p>Makes a pid term or the atom <seecref marker="#enif_set_pid_undefined">
+ <c>undefined</c></seecref> from <c>*pid</c>.</p>
</desc>
</func>
@@ -2305,8 +2326,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<nametext>enif_make_ref(ErlNifEnv* env)</nametext></name>
<fsummary>Create a reference.</fsummary>
<desc>
- <p>Creates a reference like <seealso marker="erlang#make_ref-0">
- <c>erlang:make_ref/0</c></seealso>.</p>
+ <p>Creates a reference like <seemfa marker="erlang#make_ref/0">
+ <c>erlang:make_ref/0</c></seemfa>.</p>
</desc>
</func>
@@ -2317,16 +2338,16 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<fsummary>Create an opaque handle to a resource object.</fsummary>
<desc>
<p>Creates an opaque handle to a memory-managed resource object
- obtained by <seealso marker="#enif_alloc_resource">
- <c>enif_alloc_resource</c></seealso>. No ownership transfer is done,
+ obtained by <seecref marker="#enif_alloc_resource">
+ <c>enif_alloc_resource</c></seecref>. No ownership transfer is done,
as the resource object still needs to be released by
- <seealso marker="#enif_release_resource">
- <c>enif_release_resource</c></seealso>. However, notice that the call
+ <seecref marker="#enif_release_resource">
+ <c>enif_release_resource</c></seecref>. However, notice that the call
to <c>enif_release_resource</c> can occur immediately after obtaining
the term from <c>enif_make_resource</c>, in which case the resource
object is deallocated when the term is garbage collected. For more
- details, see the <seealso marker="#enif_resource_example">example of
- creating and returning a resource object</seealso> in the User's
+ details, see the <seecref marker="#enif_resource_example">example of
+ creating and returning a resource object</seecref> in the User's
Guide.</p>
<note>
<p>Since ERTS 9.0 (OTP-20.0), resource terms have a defined behavior
@@ -2336,14 +2357,14 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<item>
<p>Two resource terms will compare equal if and only if they
would yield the same resource object pointer when passed to
- <seealso marker="#enif_get_resource"><c>enif_get_resource</c></seealso>.</p>
+ <seecref marker="#enif_get_resource"><c>enif_get_resource</c></seecref>.</p>
</item>
<item>
<p>A resource term can be serialized with <c>term_to_binary</c> and later
be fully recreated if the resource object is still alive when
<c>binary_to_term</c> is called. A <em>stale</em> resource term will be
returned from <c>binary_to_term</c> if the resource object has
- been deallocated. <seealso marker="#enif_get_resource"><c>enif_get_resource</c></seealso>
+ been deallocated. <seecref marker="#enif_get_resource"><c>enif_get_resource</c></seecref>
will return false for stale resource terms.</p>
<p>The same principles of serialization apply when passing
resource terms in messages to remote nodes and back again. A
@@ -2365,8 +2386,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<fsummary>Create a custom binary term.</fsummary>
<desc>
<p>Creates a binary term that is memory-managed by a resource object
- <c>obj</c> obtained by <seealso marker="#enif_alloc_resource">
- <c>enif_alloc_resource</c></seealso>. The returned binary term
+ <c>obj</c> obtained by <seecref marker="#enif_alloc_resource">
+ <c>enif_alloc_resource</c></seecref>. The returned binary term
consists of <c>size</c> bytes pointed to by <c>data</c>. This raw
binary data must be kept readable and unchanged until the destructor
of the resource is called. The binary data can be stored external to
@@ -2376,11 +2397,11 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
destructor is not called until the last binary is garbage collected.
This can be useful to return different parts of a larger binary
buffer.</p>
- <p>As with <seealso marker="#enif_make_resource">
- <c>enif_make_resource</c></seealso>, no ownership transfer is done.
+ <p>As with <seecref marker="#enif_make_resource">
+ <c>enif_make_resource</c></seecref>, no ownership transfer is done.
The resource still needs to be released with
- <seealso marker="#enif_release_resource">
- <c>enif_release_resource</c></seealso>.</p>
+ <seecref marker="#enif_release_resource">
+ <c>enif_release_resource</c></seecref>.</p>
</desc>
</func>
@@ -2407,7 +2428,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<desc>
<p>Creates a list containing the characters of the
<c>NULL</c>-terminated string <c>string</c> with encoding
- <seealso marker="#ErlNifCharEncoding">encoding</seealso>.</p>
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>.</p>
</desc>
</func>
@@ -2419,7 +2440,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<desc>
<p>Creates a list containing the characters of the string <c>string</c>
with length <c>len</c> and encoding
- <seealso marker="#ErlNifCharEncoding">encoding</seealso>.
+ <seecref marker="#ErlNifCharEncoding">encoding</seecref>.
<c>NULL</c> characters are treated as any other characters.</p>
</desc>
</func>
@@ -2522,15 +2543,15 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
<fsummary></fsummary>
<desc>
<p>Returns a unique integer with the same properties as specified by
- <seealso marker="erlang#unique_integer-1">
- <c>erlang:unique_integer/1</c></seealso>.</p>
+ <seemfa marker="erlang#unique_integer/1">
+ <c>erlang:unique_integer/1</c></seemfa>.</p>
<p><c>env</c> is the environment to create the integer in.</p>
<p><c>ERL_NIF_UNIQUE_POSITIVE</c> and <c>ERL_NIF_UNIQUE_MONOTONIC</c>
can be passed as the second argument to change the properties of the
integer returned. They can be combined by OR:ing the two values
together.</p>
- <p>See also <seealso marker="#ErlNifUniqueInteger">
- <c>ErlNifUniqueInteger</c></seealso>.</p>
+ <p>See also <seecref marker="#ErlNifUniqueInteger">
+ <c>ErlNifUniqueInteger</c></seecref>.</p>
</desc>
</func>
@@ -2548,8 +2569,8 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
map.</p>
<p>A map iterator is only useful during the lifetime of environment
<c>env</c> that the <c>map</c> belongs to. The iterator must be
- destroyed by calling <seealso marker="#enif_map_iterator_destroy">
- <c>enif_map_iterator_destroy</c></seealso>:</p>
+ destroyed by calling <seecref marker="#enif_map_iterator_destroy">
+ <c>enif_map_iterator_destroy</c></seecref>:</p>
<code type="none">
ERL_NIF_TERM key, value;
ErlNifMapIterator iter;
@@ -2575,8 +2596,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<fsummary>Destroy a map iterator.</fsummary>
<desc>
<p>Destroys a map iterator created by
- <seealso marker="#enif_map_iterator_create">
- <c>enif_map_iterator_create</c></seealso>.</p>
+ <seecref marker="#enif_map_iterator_create">
+ <c>enif_map_iterator_create</c></seecref>.</p>
</desc>
</func>
@@ -2645,26 +2666,30 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<marker id="enif_monitor_process"></marker>
<p>Starts monitoring a process from a resource. When a process is
monitored, a process exit results in a call to the provided
- <seealso marker="#ErlNifResourceDown">
- <c>down</c></seealso> callback associated with the resource type.</p>
+ <seecref marker="#ErlNifResourceDown">
+ <c>down</c></seecref> callback associated with the resource type.</p>
<p>Argument <c>obj</c> is pointer to the resource to hold the monitor and
<c>*target_pid</c> identifies the local process to be monitored.</p>
<p>If <c>mon</c> is not <c>NULL</c>, a successful call stores the
identity of the monitor in the
- <seealso marker="#ErlNifMonitor"><c>ErlNifMonitor</c></seealso>
+ <seecref marker="#ErlNifMonitor"><c>ErlNifMonitor</c></seecref>
struct pointed to by <c>mon</c>. This identifier is used to refer to the
monitor for later removal with
- <seealso marker="#enif_demonitor_process"><c>enif_demonitor_process</c></seealso>
+ <seecref marker="#enif_demonitor_process"><c>enif_demonitor_process</c></seecref>
or compare with
- <seealso marker="#enif_compare_monitors"><c>enif_compare_monitors</c></seealso>.
+ <seecref marker="#enif_compare_monitors"><c>enif_compare_monitors</c></seecref>.
A monitor is automatically removed when it triggers or when
the resource is deallocated.</p>
- <p>Argument <c>caller_env</c> is the environment of the calling process
- or callback. Must only be NULL if calling from a custom thread.</p>
+ <p>
+ Argument <c>caller_env</c> is the environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by ERTS.
+ </p>
<p>Returns <c>0</c> on success, &lt; 0 if no <c>down</c> callback is
provided, and &gt; 0 if the process is no longer alive or if
- <c>target_pid</c> is <seealso marker="#enif_set_pid_undefined">
- undefined</seealso>.</p>
+ <c>target_pid</c> is <seecref marker="#enif_set_pid_undefined">
+ undefined</seecref>.</p>
<p>This function is only thread-safe when the emulator with SMP support
is used. It can only be used in a non-SMP emulator from a NIF-calling
thread.</p>
@@ -2679,15 +2704,15 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<desc>
<marker id="enif_monotonic_time"></marker>
<p>Returns the current
- <seealso marker="time_correction#Erlang_Monotonic_Time">
- Erlang monotonic time</seealso>. Notice that it is not uncommon with
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">
+ Erlang monotonic time</seeguide>. Notice that it is not uncommon with
negative values.</p>
<p><c>time_unit</c> is the time unit of the returned value.</p>
<p>Returns <c>ERL_NIF_TIME_ERROR</c> if called with an invalid time
unit argument, or if called from a thread that is not a scheduler
thread.</p>
- <p>See also <seealso marker="#ErlNifTime"><c>ErlNifTime</c></seealso>
- and <seealso marker="#ErlNifTimeUnit"><c>ErlNifTimeUnit</c></seealso>.
+ <p>See also <seecref marker="#ErlNifTime"><c>ErlNifTime</c></seecref>
+ and <seecref marker="#ErlNifTimeUnit"><c>ErlNifTimeUnit</c></seecref>.
</p>
</desc>
</func>
@@ -2697,8 +2722,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_mutex_create(char *name)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_mutex_create">
- <c>erl_drv_mutex_create</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_mutex_create">
+ <c>erl_drv_mutex_create</c></seecref>.</p>
</desc>
</func>
@@ -2707,8 +2732,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_mutex_destroy(ErlNifMutex *mtx)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_mutex_destroy">
- <c>erl_drv_mutex_destroy</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_mutex_destroy">
+ <c>erl_drv_mutex_destroy</c></seecref>.</p>
</desc>
</func>
@@ -2717,8 +2742,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_mutex_lock(ErlNifMutex *mtx)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_mutex_lock">
- <c>erl_drv_mutex_lock</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_mutex_lock">
+ <c>erl_drv_mutex_lock</c></seecref>.</p>
</desc>
</func>
@@ -2727,8 +2752,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_mutex_name(ErlNifMutex* mtx)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_mutex_name">
- <c>erl_drv_mutex_name</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_mutex_name">
+ <c>erl_drv_mutex_name</c></seecref>.</p>
</desc>
</func>
@@ -2737,8 +2762,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_mutex_trylock(ErlNifMutex *mtx)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_mutex_trylock">
- <c>erl_drv_mutex_trylock</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_mutex_trylock">
+ <c>erl_drv_mutex_trylock</c></seecref>.</p>
</desc>
</func>
@@ -2747,8 +2772,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_mutex_unlock(ErlNifMutex *mtx)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_mutex_unlock">
- <c>erl_drv_mutex_unlock</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_mutex_unlock">
+ <c>erl_drv_mutex_unlock</c></seecref>.</p>
</desc>
</func>
@@ -2757,8 +2782,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_now_time(ErlNifEnv *env)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Returns an <seealso marker="erlang#now-0">
- <c>erlang:now()</c></seealso> time stamp.</p>
+ <p>Returns an <seemfa marker="erlang#now/0">
+ <c>erlang:now()</c></seemfa> time stamp.</p>
<p><em>This function is deprecated.</em></p>
</desc>
</func>
@@ -2773,7 +2798,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<desc>
<p>Creates or takes over a resource type identified by the string
<c>name</c> and gives it the destructor function pointed to by
- <seealso marker="#ErlNifResourceDtor"><c>dtor</c></seealso>.
+ <seecref marker="#ErlNifResourceDtor"><c>dtor</c></seecref>.
Argument <c>flags</c> can have the following values:</p>
<taglist>
<tag><c>ERL_NIF_RT_CREATE</c></tag>
@@ -2795,10 +2820,10 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
It is allowed to set <c>tried</c> to <c>NULL</c>.</p>
<p>Notice that <c>enif_open_resource_type</c> is only allowed to be
called in the two callbacks
- <seealso marker="#load"><c>load</c></seealso> and
- <seealso marker="#upgrade"><c>upgrade</c></seealso>.</p>
- <p>See also <seealso marker="#enif_open_resource_type_x">
- <c>enif_open_resource_type_x</c></seealso>.</p>
+ <seecref marker="#load"><c>load</c></seecref> and
+ <seecref marker="#upgrade"><c>upgrade</c></seecref>.</p>
+ <p>See also <seecref marker="#enif_open_resource_type_x">
+ <c>enif_open_resource_type_x</c></seecref>.</p>
</desc>
</func>
@@ -2810,12 +2835,12 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</name>
<fsummary>Create or takeover a resource type.</fsummary>
<desc>
- <p>Same as <seealso marker="#enif_open_resource_type"><c>enif_open_resource_type</c></seealso>
+ <p>Same as <seecref marker="#enif_open_resource_type"><c>enif_open_resource_type</c></seecref>
except it accepts additional callback functions for resource types that are
- used together with <seealso marker="#enif_select"><c>enif_select</c></seealso>
- and <seealso marker="#enif_monitor_process"><c>enif_monitor_process</c></seealso>.</p>
+ used together with <seecref marker="#enif_select"><c>enif_select</c></seecref>
+ and <seecref marker="#enif_monitor_process"><c>enif_monitor_process</c></seecref>.</p>
<p>Argument <c>init</c> is a pointer to an
- <seealso marker="#ErlNifResourceTypeInit"><c>ErlNifResourceTypeInit</c></seealso>
+ <seecref marker="#ErlNifResourceTypeInit"><c>ErlNifResourceTypeInit</c></seecref>
structure that contains the function pointers for destructor, down and stop callbacks
for the resource type.</p>
</desc>
@@ -2827,8 +2852,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</name>
<fsummary>Send a port_command to to_port.</fsummary>
<desc>
- <p>Works as <seealso marker="erlang#port_command-2">
- <c>erlang:port_command/2</c></seealso>,
+ <p>Works as <seemfa marker="erlang#port_command/2">
+ <c>erlang:port_command/2</c></seemfa>,
except that it is always completely asynchronous.</p>
<taglist>
<tag><c>env</c></tag>
@@ -2839,12 +2864,12 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
port on the local node.</item>
<tag><c>msg_env</c></tag>
<item>The environment of the message term. Can be a process independent
- environment allocated with <seealso marker="#enif_alloc_env">
- <c>enif_alloc_env</c></seealso> or <c>NULL</c>.</item>
+ environment allocated with <seecref marker="#enif_alloc_env">
+ <c>enif_alloc_env</c></seecref> or <c>NULL</c>.</item>
<tag><c>msg</c></tag>
<item>The message term to send. The same limitations apply as on the
- payload to <seealso marker="erlang#port_command-2">
- <c>erlang:port_command/2</c></seealso>.</item>
+ payload to <seemfa marker="erlang#port_command/2">
+ <c>erlang:port_command/2</c></seemfa>.</item>
</taglist>
<p>Using a <c>msg_env</c> of <c>NULL</c> is an optimization, which
groups together calls to <c>enif_alloc_env</c>, <c>enif_make_copy</c>,
@@ -2859,8 +2884,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
alive.</item>
<item><c>msg</c> is invalid.</item>
</list>
- <p>See also <seealso marker="#enif_get_local_port">
- <c>enif_get_local_port</c></seealso>.</p>
+ <p>See also <seecref marker="#enif_get_local_port">
+ <c>enif_get_local_port</c></seecref>.</p>
</desc>
</func>
@@ -2870,8 +2895,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<fsummary>Get the private data of a NIF library.</fsummary>
<desc>
<p>Returns the pointer to the private data that was set by
- <seealso marker="#load"><c>load</c></seealso> or
- <seealso marker="#upgrade"><c>upgrade</c></seealso>.</p>
+ <seecref marker="#load"><c>load</c></seecref> or
+ <seecref marker="#upgrade"><c>upgrade</c></seecref>.</p>
</desc>
</func>
@@ -2888,13 +2913,13 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
to return a non-exception term instead.</p>
<p>The return value from <c>enif_raise_exception</c> can only be used
as the return value from the NIF that invoked it (directly or
- indirectly) or be passed to <seealso marker="#enif_is_exception">
- <c>enif_is_exception</c></seealso>, but not to any other NIF API
+ indirectly) or be passed to <seecref marker="#enif_is_exception">
+ <c>enif_is_exception</c></seecref>, but not to any other NIF API
function.</p>
- <p>See also <seealso marker="#enif_has_pending_exception">
- <c>enif_has_pending_exception</c></seealso> and
- <seealso marker="#enif_make_badarg">
- <c>enif_make_badarg</c></seealso>.</p>
+ <p>See also <seecref marker="#enif_has_pending_exception">
+ <c>enif_has_pending_exception</c></seecref> and
+ <seecref marker="#enif_make_badarg">
+ <c>enif_make_badarg</c></seecref>.</p>
</desc>
</func>
@@ -2904,7 +2929,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<fsummary>Reallocate dynamic memory.</fsummary>
<desc>
<p>Reallocates memory allocated by
- <seealso marker="#enif_alloc"><c>enif_alloc</c></seealso> to
+ <seecref marker="#enif_alloc"><c>enif_alloc</c></seecref> to
<c>size</c> bytes.</p>
<p>Returns <c>NULL</c> if the reallocation fails.</p>
<p>The returned pointer is suitably aligned for any built-in type that
@@ -2932,8 +2957,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<fsummary>Release a binary.</fsummary>
<desc>
<p>Releases a binary obtained from
- <seealso marker="#enif_alloc_binary">
- <c>enif_alloc_binary</c></seealso>.</p>
+ <seecref marker="#enif_alloc_binary">
+ <c>enif_alloc_binary</c></seecref>.</p>
</desc>
</func>
@@ -2943,15 +2968,15 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<fsummary>Release a resource object.</fsummary>
<desc>
<p>Removes a reference to resource object <c>obj</c> obtained from
- <seealso marker="#enif_alloc_resource">
- <c>enif_alloc_resource</c></seealso>.
+ <seecref marker="#enif_alloc_resource">
+ <c>enif_alloc_resource</c></seecref>.
The resource object is destructed when the last reference is removed.
Each call to <c>enif_release_resource</c> must correspond to a
previous call to <c>enif_alloc_resource</c> or
- <seealso marker="#enif_keep_resource">
- <c>enif_keep_resource</c></seealso>.
- References made by <seealso marker="#enif_make_resource">
- <c>enif_make_resource</c></seealso>
+ <seecref marker="#enif_keep_resource">
+ <c>enif_keep_resource</c></seecref>.
+ References made by <seecref marker="#enif_make_resource">
+ <c>enif_make_resource</c></seecref>
can only be removed by the garbage collector.</p>
<p>There are no guarantees exactly when the destructor of an
unreferenced resource is called. It could be called directly by
@@ -2965,8 +2990,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_rwlock_create(char *name)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_rwlock_create">
- <c>erl_drv_rwlock_create</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_rwlock_create">
+ <c>erl_drv_rwlock_create</c></seecref>.</p>
</desc>
</func>
@@ -2975,8 +3000,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_rwlock_destroy(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_rwlock_destroy">
- <c>erl_drv_rwlock_destroy</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_rwlock_destroy">
+ <c>erl_drv_rwlock_destroy</c></seecref>.</p>
</desc>
</func>
@@ -2985,8 +3010,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_rwlock_name(ErlNifRWLock* rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_rwlock_name">
- <c>erl_drv_rwlock_name</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_rwlock_name">
+ <c>erl_drv_rwlock_name</c></seecref>.</p>
</desc>
</func>
@@ -2995,8 +3020,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_rwlock_rlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_rwlock_rlock">
- <c>erl_drv_rwlock_rlock</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_rwlock_rlock">
+ <c>erl_drv_rwlock_rlock</c></seecref>.</p>
</desc>
</func>
@@ -3005,8 +3030,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_rwlock_runlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_rwlock_runlock">
- <c>erl_drv_rwlock_runlock</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_rwlock_runlock">
+ <c>erl_drv_rwlock_runlock</c></seecref>.</p>
</desc>
</func>
@@ -3015,8 +3040,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_rwlock_rwlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_rwlock_rwlock">
- <c>erl_drv_rwlock_rwlock</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_rwlock_rwlock">
+ <c>erl_drv_rwlock_rwlock</c></seecref>.</p>
</desc>
</func>
@@ -3025,8 +3050,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_rwlock_rwunlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_rwlock_rwunlock">
- <c>erl_drv_rwlock_rwunlock</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_rwlock_rwunlock">
+ <c>erl_drv_rwlock_rwunlock</c></seecref>.</p>
</desc>
</func>
@@ -3035,8 +3060,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_rwlock_tryrlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_rwlock_tryrlock">
- <c>erl_drv_rwlock_tryrlock</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_rwlock_tryrlock">
+ <c>erl_drv_rwlock_tryrlock</c></seecref>.</p>
</desc>
</func>
@@ -3045,8 +3070,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<nametext>enif_rwlock_tryrwlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_rwlock_tryrwlock">
- <c>erl_drv_rwlock_tryrwlock</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_rwlock_tryrwlock">
+ <c>erl_drv_rwlock_tryrwlock</c></seecref>.</p>
</desc>
</func>
@@ -3059,8 +3084,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<desc>
<p>Schedules NIF <c>fp</c> to execute. This function allows an
application to break up long-running work into multiple regular NIF
- calls or to schedule a <seealso marker="#dirty_nifs">
- dirty NIF</seealso> to execute on a dirty scheduler thread.</p>
+ calls or to schedule a <seecref marker="#dirty_nifs">
+ dirty NIF</seecref> to execute on a dirty scheduler thread.</p>
<taglist>
<tag><c>fun_name</c></tag>
<item>
@@ -3119,20 +3144,20 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<p><c>ready_input</c> or <c>ready_output</c> indicates if the event object
is ready for reading or writing.</p>
<note><p>For complete control over the message format use the newer functions
- <seealso marker="#enif_select_read"><c>enif_select_read</c></seealso> or
- <seealso marker="#enif_select_write"><c>enif_select_write</c></seealso>
+ <seecref marker="#enif_select_read"><c>enif_select_read</c></seecref> or
+ <seecref marker="#enif_select_write"><c>enif_select_write</c></seecref>
introduced in erts-11.0 (OTP-22.0).</p>
</note>
<p>Argument <c>pid</c> may be <c>NULL</c> to indicate the calling
- process. It must not be set as <seealso marker="#enif_set_pid_undefined">
- undefined</seealso>.</p>
+ process. It must not be set as <seecref marker="#enif_set_pid_undefined">
+ undefined</seecref>.</p>
<p>Argument <c>obj</c> is a resource object obtained from
- <seealso marker="#enif_alloc_resource"><c>enif_alloc_resource</c></seealso>.
+ <seecref marker="#enif_alloc_resource"><c>enif_alloc_resource</c></seecref>.
The purpose of the resource objects is as a container of the event object
to manage its state and lifetime. A handle to the resource is received
in the notification message as <c>Obj</c>.</p>
<p>Argument <c>ref</c> must be either a reference obtained from
- <seealso marker="erlang#make_ref-0"><c>erlang:make_ref/0</c></seealso>
+ <seemfa marker="erlang#make_ref/0"><c>erlang:make_ref/0</c></seemfa>
or the atom <c>undefined</c>. It will be passed as <c>Ref</c> in the notifications.
If a selective <c>receive</c> statement is used to wait for the notification
then a reference created just before the <c>receive</c> will exploit a runtime
@@ -3150,7 +3175,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
notification may already have been sent.</p>
<p>Use <c>ERL_NIF_SELECT_STOP</c> as <c>mode</c> in order to safely
close an event object that has been passed to <c>enif_select</c>. The
- <seealso marker="#ErlNifResourceStop"><c>stop</c></seealso> callback
+ <seecref marker="#ErlNifResourceStop"><c>stop</c></seecref> callback
of the resource <c>obj</c> will be called when it is safe to close
the event object. This safe way of closing event objects must be used
even if all notifications have been received (or cancelled) and no
@@ -3169,7 +3194,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
one resource. A resource will not be destructed until all its contained relations
have been dissolved.</p>
<note>
- <p>Use <seealso marker="#enif_monitor_process"><c>enif_monitor_process</c></seealso>
+ <p>Use <seecref marker="#enif_monitor_process"><c>enif_monitor_process</c></seecref>
together with <c>enif_select</c> to detect failing Erlang
processes and prevent them from causing permanent leakage of resources
and their contained OS event objects.</p>
@@ -3233,24 +3258,24 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</name>
<fsummary>Manage subscription on IO event.</fsummary>
<desc>
- <p>These are variants of <seealso marker="#enif_select">enif_select</seealso>
+ <p>These are variants of <seecref marker="#enif_select">enif_select</seecref>
where you can supply your own message term <c>msg</c> that will be sent to
the process instead of the predefined tuple <c>{select,_,_,_}.</c></p>
<p>Argument <c>msg_env</c> must either be <c>NULL</c> or the environment of
- <c>msg</c> allocated with <seealso marker="#enif_alloc_env">
- <c>enif_alloc_env</c></seealso>. If argument <c>msg_env</c> is
+ <c>msg</c> allocated with <seecref marker="#enif_alloc_env">
+ <c>enif_alloc_env</c></seecref>. If argument <c>msg_env</c> is
<c>NULL</c> the term <c>msg</c> will be copied, otherwise both
<c>msg</c> and <c>msg_env</c> will be invalidated by a successful call
to <c>enif_select_read</c> or <c>enif_select_write</c>. The environment
- is then to either be freed with <seealso marker="#enif_free_env">
- <c>enif_free_env</c></seealso> or cleared for reuse with
- <seealso marker="#enif_clear_env"><c>enif_clear_env</c></seealso>. An
+ is then to either be freed with <seecref marker="#enif_free_env">
+ <c>enif_free_env</c></seecref> or cleared for reuse with
+ <seecref marker="#enif_clear_env"><c>enif_clear_env</c></seecref>. An
unsuccessful call will leave <c>msg</c> and <c>msg_env</c> still valid.</p>
<p>Apart from the message format <c>enif_select_read</c> and
- <c>enif_select_write</c> behaves exactly the same as <seealso
- marker="#enif_select">enif_select</seealso> with argument <c>mode</c> as
+ <c>enif_select_write</c> behaves exactly the same as <seecref
+ marker="#enif_select">enif_select</seecref> with argument <c>mode</c> as
either <c>ERL_NIF_SELECT_READ</c> or <c>ERL_NIF_SELECT_WRITE</c>. To
- cancel or close events use <seealso marker="#enif_select">enif_select</seealso>.</p>
+ cancel or close events use <seecref marker="#enif_select">enif_select</seecref>.</p>
</desc>
</func>
@@ -3260,10 +3285,10 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</name>
<fsummary>Get the pid of the calling process.</fsummary>
<desc>
- <p>Initializes the <seealso marker="#ErlNifPid"><c>ErlNifPid</c></seealso>
+ <p>Initializes the <seecref marker="#ErlNifPid"><c>ErlNifPid</c></seecref>
variable at <c>*pid</c> to represent the calling process.</p>
<p>Returns <c>pid</c> if successful, or NULL if <c>caller_env</c> is not
- a <seealso marker="#ErlNifEnv">process bound environment</seealso>.</p>
+ a <seecref marker="#proc_bound_env">process bound environment</seecref>.</p>
</desc>
</func>
@@ -3275,15 +3300,19 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<p>Sends a message to a process.</p>
<taglist>
<tag><c>caller_env</c></tag>
- <item>The environment of the calling process or callback. Must be <c>NULL</c>
- only if calling from a custom thread not spawned by ERTS.</item>
+ <item>
+ The environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by ERTS.
+ </item>
<tag><c>*to_pid</c></tag>
<item>The pid of the receiving process. The pid is to refer to a
process on the local node.</item>
<tag><c>msg_env</c></tag>
<item>The environment of the message term. Must be a
process independent environment allocated with
- <seealso marker="#enif_alloc_env"><c>enif_alloc_env</c></seealso>
+ <seecref marker="#enif_alloc_env"><c>enif_alloc_env</c></seecref>
or NULL.</item>
<tag><c>msg</c></tag>
<item>The message term to send.</item>
@@ -3298,9 +3327,9 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<p>The message environment <c>msg_env</c> with all its terms (including
<c>msg</c>) is invalidated by a successful call to <c>enif_send</c>.
The environment is to either be freed with
- <seealso marker="#enif_free_env">
- <c>enif_free_env</c></seealso> or cleared for reuse with
- <seealso marker="#enif_clear_env"><c>enif_clear_env</c></seealso>. An
+ <seecref marker="#enif_free_env">
+ <c>enif_free_env</c></seecref> or cleared for reuse with
+ <seecref marker="#enif_clear_env"><c>enif_clear_env</c></seecref>. An
unsuccessful call will leave <c>msg</c> and <c>msg_env</c> still valid.</p>
<p>If <c>msg_env</c> is set to <c>NULL</c>, the <c>msg</c> term is
copied and the original term and its environment is still valid after
@@ -3320,9 +3349,9 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<nametext>enif_set_pid_undefined(ErlNifPid* pid)</nametext></name>
<fsummary>Set pid as undefined.</fsummary>
<desc>
- <p>Sets an <seealso marker="#ErlNifPid"><c>ErlNifPid</c></seealso>
- variable as undefined. See <seealso marker="#enif_is_pid_undefined">
- <c>enif_is_pid_undefined</c></seealso>.</p>
+ <p>Sets an <seecref marker="#ErlNifPid"><c>ErlNifPid</c></seecref>
+ variable as undefined. See <seecref marker="#enif_is_pid_undefined">
+ <c>enif_is_pid_undefined</c></seecref>.</p>
</desc>
</func>
@@ -3332,8 +3361,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<fsummary>Get the byte size of a resource object.</fsummary>
<desc>
<p>Gets the byte size of resource object <c>obj</c> obtained by
- <seealso marker="#enif_alloc_resource">
- <c>enif_alloc_resource</c></seealso>.</p>
+ <seecref marker="#enif_alloc_resource">
+ <c>enif_alloc_resource</c></seecref>.</p>
</desc>
</func>
@@ -3344,7 +3373,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<desc>
<p>Similar to <c>snprintf</c> but this format string also accepts
<c>"%T"</c>, which formats Erlang terms of type
- <seealso marker="#ERL_NIF_TERM"><c>ERL_NIF_TERM</c></seealso>.</p>
+ <seecref marker="#ERL_NIF_TERM"><c>ERL_NIF_TERM</c></seecref>.</p>
<p>This function is primarily intended for debugging purpose. It is not
recommended to print very large terms with <c>%T</c>. The function may
change <c>errno</c>, even if successful.</p>
@@ -3356,8 +3385,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
*sys_info_ptr, size_t size)</nametext></name>
<fsummary>Get information about the Erlang runtime system.</fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#driver_system_info">
- <c>driver_system_info</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#driver_system_info">
+ <c>driver_system_info</c></seecref>.</p>
</desc>
</func>
@@ -3366,15 +3395,15 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
ERL_NIF_TERM term, ErlNifBinary *bin)</nametext></name>
<fsummary>Convert a term to the external format.</fsummary>
<desc>
- <p>Allocates a new binary with <seealso marker="#enif_alloc_binary">
- <c>enif_alloc_binary</c></seealso> and stores the result of encoding
+ <p>Allocates a new binary with <seecref marker="#enif_alloc_binary">
+ <c>enif_alloc_binary</c></seecref> and stores the result of encoding
<c>term</c> according to the Erlang external term format.</p>
<p>Returns <c>true</c> on success, or <c>false</c> if the allocation
fails.</p>
- <p>See also <seealso marker="erlang#term_to_binary-1">
- <c>erlang:term_to_binary/1</c></seealso> and
- <seealso marker="#enif_binary_to_term">
- <c>enif_binary_to_term</c></seealso>.</p>
+ <p>See also <seemfa marker="erlang#term_to_binary/1">
+ <c>erlang:term_to_binary/1</c></seemfa> and
+ <seecref marker="#enif_binary_to_term">
+ <c>enif_binary_to_term</c></seecref>.</p>
</desc>
</func>
@@ -3386,10 +3415,10 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<desc>
<p>Determines the type of the given term. The term must be an ordinary
Erlang term and not one of the special terms returned by
- <seealso marker="#enif_raise_exception">
- <c>enif_raise_exception</c></seealso>,
- <seealso marker="#enif_schedule_nif">
- <c>enif_schedule_nif</c></seealso>, or similar.</p>
+ <seecref marker="#enif_raise_exception">
+ <c>enif_raise_exception</c></seecref>,
+ <seecref marker="#enif_schedule_nif">
+ <c>enif_schedule_nif</c></seecref>, or similar.</p>
<p>The following types are defined at the moment:</p>
<taglist>
<tag><c>ERL_NIF_TERM_TYPE_ATOM</c></tag>
@@ -3427,8 +3456,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
*opts)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_thread_create">
- <c>erl_drv_thread_create</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_thread_create">
+ <c>erl_drv_thread_create</c></seecref>.</p>
</desc>
</func>
@@ -3437,8 +3466,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<nametext>enif_thread_exit(void *resp)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_thread_exit">
- <c>erl_drv_thread_exit</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_thread_exit">
+ <c>erl_drv_thread_exit</c></seecref>.</p>
</desc>
</func>
@@ -3447,8 +3476,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<nametext>enif_thread_join(ErlNifTid, void **respp)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_thread_join">
- <c>erl_drv_thread_join</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_thread_join">
+ <c>erl_drv_thread_join</c></seecref>.</p>
</desc>
</func>
@@ -3457,8 +3486,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<nametext>enif_thread_name(ErlNifTid tid)</nametext></name>
<fsummary>Thread name</fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_thread_name">
- <c>erl_drv_thread_name</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_thread_name">
+ <c>erl_drv_thread_name</c></seecref>.</p>
</desc>
</func>
@@ -3467,8 +3496,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<nametext>enif_thread_opts_create(char *name)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_thread_opts_create">
- <c>erl_drv_thread_opts_create</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_thread_opts_create">
+ <c>erl_drv_thread_opts_create</c></seecref>.</p>
</desc>
</func>
@@ -3478,8 +3507,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_thread_opts_destroy">
- <c>erl_drv_thread_opts_destroy</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_thread_opts_destroy">
+ <c>erl_drv_thread_opts_destroy</c></seecref>.</p>
</desc>
</func>
@@ -3488,8 +3517,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<nametext>enif_thread_self(void)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_thread_self">
- <c>erl_drv_thread_self</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_thread_self">
+ <c>erl_drv_thread_self</c></seecref>.</p>
</desc>
</func>
@@ -3522,18 +3551,18 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<desc>
<marker id="enif_time_offset"></marker>
<p>Returns the current time offset between
- <seealso marker="time_correction#Erlang_Monotonic_Time">
- Erlang monotonic time</seealso> and
- <seealso marker="time_correction#Erlang_System_Time">
- Erlang system time</seealso>
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">
+ Erlang monotonic time</seeguide> and
+ <seeguide marker="time_correction#Erlang_System_Time">
+ Erlang system time</seeguide>
converted into the <c>time_unit</c> passed as argument.</p>
<p><c>time_unit</c> is the time unit of the returned value.</p>
<p>Returns <c>ERL_NIF_TIME_ERROR</c> if called with an invalid
time unit argument or if called from a thread that is not a
scheduler thread.</p>
- <p>See also <seealso marker="#ErlNifTime"><c>ErlNifTime</c></seealso>
+ <p>See also <seecref marker="#ErlNifTime"><c>ErlNifTime</c></seecref>
and
- <seealso marker="#ErlNifTimeUnit"><c>ErlNifTimeUnit</c></seealso>.</p>
+ <seecref marker="#ErlNifTimeUnit"><c>ErlNifTimeUnit</c></seecref>.</p>
</desc>
</func>
@@ -3542,8 +3571,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<nametext>enif_tsd_get(ErlNifTSDKey key)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_tsd_get">
- <c>erl_drv_tsd_get</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_tsd_get">
+ <c>erl_drv_tsd_get</c></seecref>.</p>
</desc>
</func>
@@ -3553,8 +3582,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_tsd_key_create">
- <c>erl_drv_tsd_key_create</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_tsd_key_create">
+ <c>erl_drv_tsd_key_create</c></seecref>.</p>
</desc>
</func>
@@ -3563,8 +3592,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<nametext>enif_tsd_key_destroy(ErlNifTSDKey key)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_tsd_key_destroy">
- <c>erl_drv_tsd_key_destroy</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_tsd_key_destroy">
+ <c>erl_drv_tsd_key_destroy</c></seecref>.</p>
</desc>
</func>
@@ -3573,8 +3602,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<nametext>enif_tsd_set(ErlNifTSDKey key, void *data)</nametext></name>
<fsummary></fsummary>
<desc>
- <p>Same as <seealso marker="erl_driver#erl_drv_tsd_set">
- <c>erl_drv_tsd_set</c></seealso>.</p>
+ <p>Same as <seecref marker="erl_driver#erl_drv_tsd_set">
+ <c>erl_drv_tsd_set</c></seecref>.</p>
</desc>
</func>
@@ -3584,7 +3613,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</nametext></name>
<fsummary>Format strings and Erlang terms.</fsummary>
<desc>
- <p>Equivalent to <seealso marker="#enif_fprintf"><c>enif_fprintf</c></seealso>
+ <p>Equivalent to <seecref marker="#enif_fprintf"><c>enif_fprintf</c></seecref>
except that its called with a <c>va_list</c> instead of a variable number of
arguments.</p>
</desc>
@@ -3596,7 +3625,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</nametext></name>
<fsummary>Format strings and Erlang terms.</fsummary>
<desc>
- <p>Equivalent to <seealso marker="#enif_snprintf"><c>enif_snprintf</c></seealso>
+ <p>Equivalent to <seecref marker="#enif_snprintf"><c>enif_snprintf</c></seecref>
except that its called with a <c>va_list</c> instead of a variable number of
arguments.</p>
</desc>
@@ -3604,56 +3633,62 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<func>
<name since="OTP 20.0"><ret>int</ret>
- <nametext>enif_whereis_pid(ErlNifEnv *env,
+ <nametext>enif_whereis_pid(ErlNifEnv *caller_env,
ERL_NIF_TERM name, ErlNifPid *pid)</nametext></name>
<fsummary>Looks up a process by its registered name.</fsummary>
<desc>
<p>Looks up a process by its registered name.</p>
<taglist>
- <tag><c>env</c></tag>
- <item>The environment of the calling process. Must be <c>NULL</c>
- only if calling from a created thread.</item>
+ <tag><c>caller_env</c></tag>
+ <item>The environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by
+ ERTS.</item>
<tag><c>name</c></tag>
<item>The name of a registered process, as an atom.</item>
<tag><c>*pid</c></tag>
- <item>The <seealso marker="#ErlNifPid"><c>ErlNifPid</c></seealso>
+ <item>The <seecref marker="#ErlNifPid"><c>ErlNifPid</c></seecref>
in which the resolved process id is stored.</item>
</taglist>
<p>On success, sets <c>*pid</c> to the local process registered with
<c>name</c> and returns <c>true</c>. If <c>name</c> is not a
registered process, or is not an atom, <c>false</c> is returned and
<c>*pid</c> is unchanged.</p>
- <p>Works as <seealso marker="erlang#whereis-1">
- <c>erlang:whereis/1</c></seealso>, but restricted to processes. See
- <seealso marker="#enif_whereis_port"><c>enif_whereis_port</c></seealso>
+ <p>Works as <seemfa marker="erlang#whereis/1">
+ <c>erlang:whereis/1</c></seemfa>, but restricted to processes. See
+ <seecref marker="#enif_whereis_port"><c>enif_whereis_port</c></seecref>
to resolve registered ports.</p>
</desc>
</func>
<func>
<name since="OTP 20.0"><ret>int</ret>
- <nametext>enif_whereis_port(ErlNifEnv *env,
+ <nametext>enif_whereis_port(ErlNifEnv *caller_env,
ERL_NIF_TERM name, ErlNifPort *port)</nametext></name>
<fsummary>Looks up a port by its registered name.</fsummary>
<desc>
<p>Looks up a port by its registered name.</p>
<taglist>
- <tag><c>env</c></tag>
- <item>The environment of the calling process. Must be <c>NULL</c>
- only if calling from a created thread.</item>
+ <tag><c>caller_env</c></tag>
+ <item>The environment of the calling thread
+ (<seecref marker="#proc_bound_env">process bound</seecref> or
+ <seecref marker="#callback_env">callback</seecref> environment) or
+ <c>NULL</c> if calling from a custom thread not spawned by
+ ERTS.</item>
<tag><c>name</c></tag>
<item>The name of a registered port, as an atom.</item>
<tag><c>*port</c></tag>
- <item>The <seealso marker="#ErlNifPort"><c>ErlNifPort</c></seealso>
+ <item>The <seecref marker="#ErlNifPort"><c>ErlNifPort</c></seecref>
in which the resolved port id is stored.</item>
</taglist>
<p>On success, sets <c>*port</c> to the port registered with
<c>name</c> and returns <c>true</c>. If <c>name</c> is not a
registered port, or is not an atom, <c>false</c> is returned and
<c>*port</c> is unchanged.</p>
- <p>Works as <seealso marker="erlang#whereis-1">
- <c>erlang:whereis/1</c></seealso>, but restricted to ports. See
- <seealso marker="#enif_whereis_pid"><c>enif_whereis_pid</c></seealso>
+ <p>Works as <seemfa marker="erlang#whereis/1">
+ <c>erlang:whereis/1</c></seemfa>, but restricted to ports. See
+ <seecref marker="#enif_whereis_pid"><c>enif_whereis_pid</c></seecref>
to resolve registered processes.</p>
</desc>
</func>
@@ -3662,7 +3697,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<section>
<title>See Also</title>
- <p><seealso marker="erlang#load_nif-2">
- <c>erlang:load_nif/2</c></seealso></p>
+ <p><seemfa marker="erlang#load_nif/2">
+ <c>erlang:load_nif/2</c></seemfa></p>
</section>
</cref>
diff --git a/erts/doc/src/erl_prim_loader.xml b/erts/doc/src/erl_prim_loader.xml
index 95bc13d99a..e9f7d6745c 100644
--- a/erts/doc/src/erl_prim_loader.xml
+++ b/erts/doc/src/erl_prim_loader.xml
@@ -60,7 +60,7 @@
for example,
<c>$OTPROOT/lib/</c><c>mnesia-4.4.7.ez/mnesia-4.4.7/ebin/</c><c>mnesia.beam</c>.
For information about archive files, see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</desc>
</func>
@@ -69,7 +69,7 @@
<fsummary>Get the path set in the loader.</fsummary>
<desc>
<p>Gets the path set in the loader. The path is
- set by the <seealso marker="init"><c>init(3)</c></seealso>
+ set by the <seeerl marker="init"><c>init(3)</c></seeerl>
process according to information found in the start script.</p>
</desc>
</func>
@@ -87,7 +87,7 @@
for example,
<c>$OTPROOT/lib/</c><c>mnesia-4.4.7.ez/mnesia-4.4.7/ebin</c>.
For information about archive files, see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</desc>
</func>
@@ -104,12 +104,12 @@
<code type="none">
-include_lib("kernel/include/file.hrl").</code>
<p>For more information about the record <c>file_info</c>, see
- <seealso marker="kernel:file"><c>file(3)</c></seealso>.</p>
+ <seeerl marker="kernel:file"><c>file(3)</c></seeerl>.</p>
<p><c><anno>Filename</anno></c> can also be a file in an archive,
for example,
<c>$OTPROOT/lib/</c><c>mnesia-4.4.7.ez/mnesia-4.4.7/ebin/</c><c>mnesia</c>.
For information about archive files, see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</desc>
</func>
@@ -118,7 +118,7 @@
<fsummary>Get information about a link or file.</fsummary>
<desc>
<p>Works like
- <seealso marker="#read_file_info/1"><c>read_file_info/1</c></seealso>
+ <seemfa marker="#read_file_info/1"><c>read_file_info/1</c></seemfa>
except that if <c><anno>Filename</anno></c> is a symbolic link,
information about the link is returned in the <c>file_info</c>
record and the <c>type</c> field of the record is set to
@@ -135,7 +135,7 @@
<fsummary>Set the path of the loader.</fsummary>
<desc>
<p>Sets the path of the loader if
- <seealso marker="init"><c>init(3)</c></seealso>
+ <seeerl marker="init"><c>init(3)</c></seeerl>
interprets a <c>path</c> command in the start script.</p>
</desc>
</func>
@@ -166,8 +166,8 @@
<p>Specifies which other Erlang nodes the <c>inet</c> loader
can use. This flag is mandatory if flag <c>-loader inet</c>
is present. On each host, there must be on Erlang node
- with the <seealso marker="kernel:erl_boot_server">
- <c>erl_boot_server(3)</c></seealso>,
+ with the <seeerl marker="kernel:erl_boot_server">
+ <c>erl_boot_server(3)</c></seeerl>,
which handles the load requests.
<c>Hosts</c> is a list of IP addresses (hostnames
are not acceptable).</p>
@@ -182,9 +182,9 @@
<section>
<title>See Also</title>
- <p><seealso marker="init"><c>init(3)</c></seealso>,
- <seealso marker="kernel:erl_boot_server">
- <c>erl_boot_server(3)</c></seealso></p>
+ <p><seeerl marker="init"><c>init(3)</c></seeerl>,
+ <seeerl marker="kernel:erl_boot_server">
+ <c>erl_boot_server(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/erts/doc/src/erl_tracer.xml b/erts/doc/src/erl_tracer.xml
index 8cdcbbf6da..a2cccf9d40 100644
--- a/erts/doc/src/erl_tracer.xml
+++ b/erts/doc/src/erl_tracer.xml
@@ -43,7 +43,7 @@
<note>
<p>All functions in this behavior must be implemented as NIFs.
This limitation can be removed in a future releases.
- An <seealso marker="#example">example tracer module NIF</seealso>
+ An <seeerl marker="#example">example tracer module NIF</seeerl>
implementation is provided at the end of this page.</p>
</note>
@@ -85,7 +85,7 @@
<desc>
<p>The different trace tags that the tracer is called with.
Each trace tag is described in detail in
- <seealso marker="#Module:trace/5"><c>Module:trace/5</c></seealso>.</p>
+ <seemfa marker="#Module:trace/5"><c>Module:trace/5</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -107,8 +107,8 @@
the trace event. What the additional data is depends on which
<c>TraceTag</c> has been triggered. The <c>extra</c> trace data
corresponds to the fifth element in the trace tuples described in
- <seealso marker="erlang#trace_3_trace_messages">
- erlang:trace/3</seealso>.</item>
+ <seeerl marker="erlang#trace_3_trace_messages">
+ erlang:trace/3</seeerl>.</item>
<tag><c>match_spec_result</c></tag>
<item>If set the tracer has been requested to include the output
of a match specification that was run.</item>
@@ -121,8 +121,8 @@
<name name="tracer_state"/>
<desc>
<p>The state specified when calling
- <seealso marker="erlang#trace-3">
- <c>erlang:trace(PidPortSpec,true,[{tracer,Module,TracerState}])</c></seealso>.
+ <seemfa marker="erlang#trace/3">
+ <c>erlang:trace(PidPortSpec,true,[{tracer,Module,TracerState}])</c></seemfa>.
The tracer state is an immutable value that is passed to
<c>erl_tracer</c> callbacks and is to
contain all the data that is needed to generate the trace event.</p>
@@ -130,78 +130,79 @@
</datatype>
</datatypes>
- <section>
- <title>Callback Functions</title>
- <p>The following functions are to be exported from an <c>erl_tracer</c>
- callback module:</p>
-
- <taglist>
- <tag><seealso marker="#Module:enabled/3">
- <c>Module:enabled/3</c></seealso></tag>
- <item>Mandatory</item>
- <tag><seealso marker="#Module:trace/5">
- <c>Module:trace/5</c></seealso></tag>
- <item>Mandatory</item>
- <tag><seealso marker="#Module:enabled_call/3">
- <c>Module:enabled_call/3</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:trace_call/5">
- <c>Module:trace_call/5</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:enabled_garbage_collection/3">
- <c>Module:enabled_garbage_collection/3</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:trace_garbage_collection/5">
- <c>Module:trace_garbage_collection/5</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:enabled_ports/3">
- <c>Module:enabled_ports/3</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:trace_ports/5">
- <c>Module:trace_ports/5</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:enabled_procs/3">
- <c>Module:enabled_procs/3</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:trace_procs/5">
- <c>Module:trace_procs/5</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:enabled_receive/3">
- <c>Module:enabled_receive/3</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:trace_receive/5">
- <c>Module:trace_receive/5</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:enabled_running_ports/3">
- <c>Module:enabled_running_ports/3</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:trace_running_ports/5">
- <c>Module:trace_running_ports/5</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:enabled_running_procs/3">
- <c>Module:enabled_running_procs/3</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:trace_running_procs/5">
- <c>Module:trace_running_procs/5</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:enabled_send/3">
- <c>Module:enabled_send/3</c></seealso></tag>
- <item>Optional</item>
- <tag><seealso marker="#Module:trace_send/5">
- <c>Module:trace_send/5</c></seealso></tag>
- <item>Optional</item>
- </taglist>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>The following functions are to be exported from an <c>erl_tracer</c>
+ callback module:</p>
+
+ <taglist>
+ <tag><seemfa marker="#Module:enabled/3">
+ <c>Module:enabled/3</c></seemfa></tag>
+ <item>Mandatory</item>
+ <tag><seemfa marker="#Module:trace/5">
+ <c>Module:trace/5</c></seemfa></tag>
+ <item>Mandatory</item>
+ <tag><seemfa marker="#Module:enabled_call/3">
+ <c>Module:enabled_call/3</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:trace_call/5">
+ <c>Module:trace_call/5</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:enabled_garbage_collection/3">
+ <c>Module:enabled_garbage_collection/3</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:trace_garbage_collection/5">
+ <c>Module:trace_garbage_collection/5</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:enabled_ports/3">
+ <c>Module:enabled_ports/3</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:trace_ports/5">
+ <c>Module:trace_ports/5</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:enabled_procs/3">
+ <c>Module:enabled_procs/3</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:trace_procs/5">
+ <c>Module:trace_procs/5</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:enabled_receive/3">
+ <c>Module:enabled_receive/3</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:trace_receive/5">
+ <c>Module:trace_receive/5</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:enabled_running_ports/3">
+ <c>Module:enabled_running_ports/3</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:trace_running_ports/5">
+ <c>Module:trace_running_ports/5</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:enabled_running_procs/3">
+ <c>Module:enabled_running_procs/3</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:trace_running_procs/5">
+ <c>Module:trace_running_procs/5</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:enabled_send/3">
+ <c>Module:enabled_send/3</c></seemfa></tag>
+ <item>Optional</item>
+ <tag><seemfa marker="#Module:trace_send/5">
+ <c>Module:trace_send/5</c></seemfa></tag>
+ <item>Optional</item>
+ </taglist>
+ </fsdescription>
<func>
<name since="OTP 19.0">Module:enabled(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag">
- trace_tag()</seealso> | trace_status</v>
+ <v>TraceTag = <seetype marker="#trace_tag">
+ trace_tag()</seetype> | trace_status</v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>Result = trace | discard | remove</v>
</type>
<desc>
@@ -227,18 +228,18 @@
<name since="OTP 19.0">Module:enabled_call(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_call">
- trace_tag_call()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_call">
+ trace_tag_call()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>Result = trace | discard | remove</v>
</type>
<desc>
<p>This callback is called whenever a tracepoint with trace flag
- <seealso marker="erlang#trace-3"><c>call | return_to</c></seealso>
+ <seemfa marker="erlang#trace/3"><c>call | return_to</c></seemfa>
is triggered.</p>
<p>If <c>enabled_call/3</c> is undefined,
- <seealso marker="#Module:enabled/3"><c>Module:enabled/3</c></seealso>
+ <seemfa marker="#Module:enabled/3"><c>Module:enabled/3</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -247,18 +248,18 @@
<name since="OTP 19.0">Module:enabled_garbage_collection(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_gc">
- trace_tag_gc()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_gc">
+ trace_tag_gc()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>Result = trace | discard | remove</v>
</type>
<desc>
<p>This callback is called whenever a tracepoint with trace flag
- <seealso marker="erlang#trace-3"><c>garbage_collection</c></seealso>
+ <seemfa marker="erlang#trace/3"><c>garbage_collection</c></seemfa>
is triggered.</p>
<p>If <c>enabled_garbage_collection/3</c> is undefined,
- <seealso marker="#Module:enabled/3"><c>Module:enabled/3</c></seealso>
+ <seemfa marker="#Module:enabled/3"><c>Module:enabled/3</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -267,18 +268,18 @@
<name since="OTP 19.0">Module:enabled_ports(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_ports">
- trace_tag_ports()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_ports">
+ trace_tag_ports()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>Result = trace | discard | remove</v>
</type>
<desc>
<p>This callback is called whenever a tracepoint with trace flag
- <seealso marker="erlang#trace-3"><c>ports</c></seealso>
+ <seemfa marker="erlang#trace/3"><c>ports</c></seemfa>
is triggered.</p>
<p>If <c>enabled_ports/3</c> is undefined,
- <seealso marker="#Module:enabled/3"><c>Module:enabled/3</c></seealso>
+ <seemfa marker="#Module:enabled/3"><c>Module:enabled/3</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -287,18 +288,18 @@
<name since="OTP 19.0">Module:enabled_procs(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_procs">
- trace_tag_procs()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_procs">
+ trace_tag_procs()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>Result = trace | discard | remove</v>
</type>
<desc>
<p>This callback is called whenever a tracepoint with trace flag
- <seealso marker="erlang#trace-3"><c>procs</c></seealso>
+ <seemfa marker="erlang#trace/3"><c>procs</c></seemfa>
is triggered.</p>
<p>If <c>enabled_procs/3</c> is undefined,
- <seealso marker="#Module:enabled/3"><c>Module:enabled/3</c></seealso>
+ <seemfa marker="#Module:enabled/3"><c>Module:enabled/3</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -308,18 +309,18 @@
</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_receive">
- trace_tag_receive()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_receive">
+ trace_tag_receive()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>Result = trace | discard | remove</v>
</type>
<desc>
<p>This callback is called whenever a tracepoint with trace flag
- <seealso marker="erlang#trace-3"><c>'receive'</c></seealso>
+ <seemfa marker="erlang#trace/3"><c>'receive'</c></seemfa>
is triggered.</p>
<p>If <c>enabled_receive/3</c> is undefined,
- <seealso marker="#Module:enabled/3"><c>Module:enabled/3</c></seealso>
+ <seemfa marker="#Module:enabled/3"><c>Module:enabled/3</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -329,18 +330,18 @@
Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_running_ports">
- trace_tag_running_ports()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_running_ports">
+ trace_tag_running_ports()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>Result = trace | discard | remove</v>
</type>
<desc>
<p>This callback is called whenever a tracepoint with trace flag
- <seealso marker="erlang#trace-3"><c>running_ports</c></seealso>
+ <seemfa marker="erlang#trace/3"><c>running_ports</c></seemfa>
is triggered.</p>
<p>If <c>enabled_running_ports/3</c> is undefined,
- <seealso marker="#Module:enabled/3"><c>Module:enabled/3</c></seealso>
+ <seemfa marker="#Module:enabled/3"><c>Module:enabled/3</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -350,19 +351,19 @@
Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_running_procs">
- trace_tag_running_procs()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_running_procs">
+ trace_tag_running_procs()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>Result = trace | discard | remove</v>
</type>
<desc>
<p>This callback is called whenever a tracepoint with trace flag
- <seealso marker="erlang#trace-3">
- <c>running_procs | running</c></seealso>
+ <seemfa marker="erlang#trace/3">
+ <c>running_procs | running</c></seemfa>
is triggered.</p>
<p>If <c>enabled_running_procs/3</c> is undefined,
- <seealso marker="#Module:enabled/3"><c>Module:enabled/3</c></seealso>
+ <seemfa marker="#Module:enabled/3"><c>Module:enabled/3</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -371,18 +372,18 @@
<name since="OTP 19.0">Module:enabled_send(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_send">
- trace_tag_send()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_send">
+ trace_tag_send()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>Result = trace | discard | remove</v>
</type>
<desc>
<p>This callback is called whenever a tracepoint with trace flag
- <seealso marker="erlang#trace-3"><c>send</c></seealso>
+ <seemfa marker="erlang#trace/3"><c>send</c></seemfa>
is triggered.</p>
<p>If <c>enabled_send/3</c> is undefined,
- <seealso marker="#Module:enabled/3"><c>Module:enabled/3</c></seealso>
+ <seemfa marker="#Module:enabled/3"><c>Module:enabled/3</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -392,39 +393,39 @@
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag">
- trace_tag()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag">
+ trace_tag()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>TraceTerm = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
<p>This callback is called when a tracepoint is triggered and the
- <seealso marker="#Module:enabled/3"><c>Module:enabled/3</c></seealso>
+ <seemfa marker="#Module:enabled/3"><c>Module:enabled/3</c></seemfa>
callback returned <c>trace</c>. In it any side effects needed by
the tracer are to be done. The tracepoint payload is located in
the <c>TraceTerm</c>. The content of the <c>TraceTerm</c>
depends on which <c>TraceTag</c> is triggered.
<c>TraceTerm</c> corresponds to the
fourth element in the trace tuples described in
- <seealso marker="erlang#trace_3_trace_messages">
- <c>erlang:trace/3</c></seealso>.</p>
+ <seeerl marker="erlang#trace_3_trace_messages">
+ <c>erlang:trace/3</c></seeerl>.</p>
<p>If the trace tuple has five elements, the fifth element
will be sent as the <c>extra</c> value in the <c>Opts</c> maps.</p>
</desc>
</func>
<func>
- <name name="trace" since="OTP 19.0">Module:trace(seq_trace, TracerState, Label,
+ <name since="OTP 19.0">Module:trace(seq_trace, TracerState, Label,
SeqTraceInfo, Opts) -> Result</name>
<fsummary>Check if a sequence trace event is to be generated.</fsummary>
<type>
<v>TracerState = term()</v>
<v>Label = term()</v>
<v>SeqTraceInfo = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
@@ -433,8 +434,8 @@
the <c>Label</c> associated with the <c>seq_trace</c> event is
specified.</p>
<p>For more information on what <c>Label</c> and <c>SeqTraceInfo</c>
- can be, see <seealso marker="kernel:seq_trace">
- <c>seq_trace(3)</c></seealso>.</p>
+ can be, see <seeerl marker="kernel:seq_trace">
+ <c>seq_trace(3)</c></seeerl>.</p>
</desc>
</func>
@@ -443,21 +444,21 @@
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_call">
- trace_tag_call()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_call">
+ trace_tag_call()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>TraceTerm = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
<p>This callback is called when a tracepoint is triggered and the
- <seealso marker="#Module:enabled_call/3">
- <c>Module:enabled_call/3</c></seealso>
+ <seemfa marker="#Module:enabled_call/3">
+ <c>Module:enabled_call/3</c></seemfa>
callback returned <c>trace</c>.</p>
<p>If <c>trace_call/5</c> is undefined,
- <seealso marker="#Module:trace/5"><c>Module:trace/5</c></seealso>
+ <seemfa marker="#Module:trace/5"><c>Module:trace/5</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -467,21 +468,21 @@
TraceTerm, Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_gc">
- trace_tag_gc()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_gc">
+ trace_tag_gc()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>TraceTerm = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
<p>This callback is called when a tracepoint is triggered and the
- <seealso marker="#Module:enabled_garbage_collection/3">
- <c>Module:enabled_garbage_collection/3</c></seealso>
+ <seemfa marker="#Module:enabled_garbage_collection/3">
+ <c>Module:enabled_garbage_collection/3</c></seemfa>
callback returned <c>trace</c>.</p>
<p>If <c>trace_garbage_collection/5</c> is undefined,
- <seealso marker="#Module:trace/5"><c>Module:trace/5</c></seealso>
+ <seemfa marker="#Module:trace/5"><c>Module:trace/5</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -491,21 +492,21 @@
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_ports">
- trace_tag()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_ports">
+ trace_tag()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>TraceTerm = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
<p>This callback is called when a tracepoint is triggered and the
- <seealso marker="#Module:enabled_ports/3">
- <c>Module:enabled_ports/3</c></seealso>
+ <seemfa marker="#Module:enabled_ports/3">
+ <c>Module:enabled_ports/3</c></seemfa>
callback returned <c>trace</c>.</p>
<p>If <c>trace_ports/5</c> is undefined,
- <seealso marker="#Module:trace/5"><c>Module:trace/5</c></seealso>
+ <seemfa marker="#Module:trace/5"><c>Module:trace/5</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -515,21 +516,21 @@
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_procs">
- trace_tag()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_procs">
+ trace_tag()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>TraceTerm = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
<p>This callback is called when a tracepoint is triggered and the
- <seealso marker="#Module:enabled_procs/3">
- <c>Module:enabled_procs/3</c></seealso>
+ <seemfa marker="#Module:enabled_procs/3">
+ <c>Module:enabled_procs/3</c></seemfa>
callback returned <c>trace</c>.</p>
<p>If <c>trace_procs/5</c> is undefined,
- <seealso marker="#Module:trace/5"><c>Module:trace/5</c></seealso>
+ <seemfa marker="#Module:trace/5"><c>Module:trace/5</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -539,21 +540,21 @@
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_receive">
- trace_tag_receive()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_receive">
+ trace_tag_receive()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>TraceTerm = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
<p>This callback is called when a tracepoint is triggered and the
- <seealso marker="#Module:enabled_receive/3">
- <c>Module:enabled_receive/3</c></seealso>
+ <seemfa marker="#Module:enabled_receive/3">
+ <c>Module:enabled_receive/3</c></seemfa>
callback returned <c>trace</c>.</p>
<p>If <c>trace_receive/5</c> is undefined,
- <seealso marker="#Module:trace/5"><c>Module:trace/5</c></seealso>
+ <seemfa marker="#Module:trace/5"><c>Module:trace/5</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -563,21 +564,21 @@
TraceTerm, Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_running_ports">
- trace_tag_running_ports()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_running_ports">
+ trace_tag_running_ports()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>TraceTerm = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
<p>This callback is called when a tracepoint is triggered and the
- <seealso marker="#Module:enabled_running_ports/3">
- <c>Module:enabled_running_ports/3</c></seealso>
+ <seemfa marker="#Module:enabled_running_ports/3">
+ <c>Module:enabled_running_ports/3</c></seemfa>
callback returned <c>trace</c>.</p>
<p>If <c>trace_running_ports/5</c> is undefined,
- <seealso marker="#Module:trace/5"><c>Module:trace/5</c></seealso>
+ <seemfa marker="#Module:trace/5"><c>Module:trace/5</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -587,21 +588,21 @@
TraceTerm, Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_running_procs">
- trace_tag_running_procs()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_running_procs">
+ trace_tag_running_procs()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>TraceTerm = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
<p>This callback is called when a tracepoint is triggered and the
- <seealso marker="#Module:enabled_running_procs/3">
- <c>Module:enabled_running_procs/3</c></seealso>
+ <seemfa marker="#Module:enabled_running_procs/3">
+ <c>Module:enabled_running_procs/3</c></seemfa>
callback returned <c>trace</c>.</p>
<p>If <c>trace_running_procs/5</c> is undefined,
- <seealso marker="#Module:trace/5"><c>Module:trace/5</c></seealso>
+ <seemfa marker="#Module:trace/5"><c>Module:trace/5</c></seemfa>
is called instead.</p>
</desc>
</func>
@@ -611,21 +612,21 @@
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
- <v>TraceTag = <seealso marker="#type-trace_tag_send">
- trace_tag_send()</seealso></v>
+ <v>TraceTag = <seetype marker="#trace_tag_send">
+ trace_tag_send()</seetype></v>
<v>TracerState = term()</v>
- <v>Tracee = <seealso marker="#type-tracee">tracee()</seealso></v>
+ <v>Tracee = <seetype marker="#tracee">tracee()</seetype></v>
<v>TraceTerm = term()</v>
- <v>Opts = <seealso marker="#type-trace_opts">trace_opts()</seealso></v>
+ <v>Opts = <seetype marker="#trace_opts">trace_opts()</seetype></v>
<v>Result = ok</v>
</type>
<desc>
<p>This callback is called when a tracepoint is triggered and the
- <seealso marker="#Module:enabled_send/3">
- <c>Module:enabled_send/3</c></seealso>
+ <seemfa marker="#Module:enabled_send/3">
+ <c>Module:enabled_send/3</c></seemfa>
callback returned <c>trace</c>.</p>
<p>If <c>trace_send/5</c> is undefined,
- <seealso marker="#Module:trace/5"><c>Module:trace/5</c></seealso>
+ <seemfa marker="#Module:trace/5"><c>Module:trace/5</c></seemfa>
is called instead.</p>
</desc>
</func>
diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml
index 613d382396..dcefdafcaf 100644
--- a/erts/doc/src/erlang.xml
+++ b/erts/doc/src/erlang.xml
@@ -60,25 +60,32 @@
</desc>
</datatype>
<datatype>
+ <name name="ext_iovec"/>
+ <desc>
+ <p>A term of type <seetype marker="#iovec"><c>iovec()</c></seetype>,
+ structured according to the Erlang external term format.</p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="iovec"/>
<desc>
<p>A list of binaries. This datatype is useful to use
- together with <seealso marker="erl_nif#enif_inspect_iovec">
- <c>enif_inspect_iovec</c></seealso>.</p>
+ together with <seecref marker="erl_nif#enif_inspect_iovec">
+ <c>enif_inspect_iovec</c></seecref>.</p>
</desc>
</datatype>
<datatype>
<name name="message_queue_data"></name>
<desc>
- <p>See <seealso marker="#process_flag_message_queue_data">
- <c>process_flag(message_queue_data, MQD)</c></seealso>.</p>
+ <p>See <seeerl marker="#process_flag_message_queue_data">
+ <c>process_flag(message_queue_data, MQD)</c></seeerl>.</p>
</desc>
</datatype>
<datatype>
<name name="timestamp"></name>
<desc>
- <p>See <seealso marker="#timestamp/0">
- <c>erlang:timestamp/0</c></seealso>.</p>
+ <p>See <seemfa marker="#timestamp/0">
+ <c>erlang:timestamp/0</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -105,12 +112,12 @@
<tag><c>microsecond</c></tag>
<item>
<p>Symbolic representation of the time unit
- represented by the integer <c>1000000</c>.</p>
+ represented by the integer <c>1000_000</c>.</p>
</item>
<tag><c>nanosecond</c></tag>
<item>
<p>Symbolic representation of the time unit
- represented by the integer <c>1000000000</c>.</p>
+ represented by the integer <c>1000_000_000</c>.</p>
</item>
<tag><c>native</c></tag>
<item>
@@ -126,8 +133,8 @@
instance.</p>
<p>One can get an approximation of the <c>native</c>
time unit by calling
- <seealso marker="#convert_time_unit/3">
- <c>erlang:convert_time_unit(1, second, native)</c></seealso>.
+ <seemfa marker="#convert_time_unit/3">
+ <c>erlang:convert_time_unit(1, second, native)</c></seemfa>.
The result equals the number
of whole <c>native</c> time units per second. If
the number of <c>native</c> time units per second does not
@@ -136,13 +143,13 @@
<p>The value of the <c>native</c> time unit gives
you more or less no information about the
quality of time values. It sets a limit for the
- <seealso marker="time_correction#Time_Resolution">
- resolution</seealso> and for the
- <seealso marker="time_correction#Time_Precision">
- precision</seealso> of time values,
+ <seeguide marker="time_correction#Time_Resolution">
+ resolution</seeguide> and for the
+ <seeguide marker="time_correction#Time_Precision">
+ precision</seeguide> of time values,
but it gives no information about the
- <seealso marker="time_correction#Time_Accuracy">
- accuracy</seealso> of time values. The resolution of
+ <seeguide marker="time_correction#Time_Accuracy">
+ accuracy</seeguide> of time values. The resolution of
the <c>native</c> time unit and the resolution of time
values can differ significantly.</p>
</note>
@@ -154,38 +161,38 @@
<p>The <c>perf_counter</c> time unit behaves much in the same way
as the <c>native</c> time unit. That is, it can differ between
runtime restarts. To get values of this type, call
- <seealso marker="kernel:os#perf_counter/0">
- <c>os:perf_counter/0</c></seealso>.</p>
+ <seemfa marker="kernel:os#perf_counter/0">
+ <c>os:perf_counter/0</c></seemfa>.</p>
</item>
- <tag><seealso marker="#type_deprecated_time_unit"><c>deprecated_time_unit()</c></seealso></tag>
+ <tag><seeerl marker="#type_deprecated_time_unit"><c>deprecated_time_unit()</c></seeerl></tag>
<item><p>
Deprecated symbolic representations kept for backwards-compatibility.
</p></item>
</taglist>
<p>The <c>time_unit/0</c> type can be extended.
To convert time values between time units, use
- <seealso marker="#convert_time_unit/3">
- <c>erlang:convert_time_unit/3</c></seealso>.</p>
+ <seemfa marker="#convert_time_unit/3">
+ <c>erlang:convert_time_unit/3</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
<name name="deprecated_time_unit"></name>
<desc><marker id="type_deprecated_time_unit"/>
- <p>The <seealso marker="#type_time_unit"><c>time_unit()</c></seealso>
+ <p>The <seeerl marker="#type_time_unit"><c>time_unit()</c></seeerl>
type also consist of the following <em>deprecated</em> symbolic
time units:</p>
<taglist>
<tag><c>seconds</c></tag>
- <item><p>Same as <seealso marker="#type_time_unit"><c>second</c></seealso>.</p></item>
+ <item><p>Same as <seeerl marker="#type_time_unit"><c>second</c></seeerl>.</p></item>
<tag><c>milli_seconds</c></tag>
- <item><p>Same as <seealso marker="#type_time_unit"><c>millisecond</c></seealso>.</p></item>
+ <item><p>Same as <seeerl marker="#type_time_unit"><c>millisecond</c></seeerl>.</p></item>
<tag><c>micro_seconds</c></tag>
- <item><p>Same as <seealso marker="#type_time_unit"><c>microsecond</c></seealso>.</p></item>
+ <item><p>Same as <seeerl marker="#type_time_unit"><c>microsecond</c></seeerl>.</p></item>
<tag><c>nano_seconds</c></tag>
- <item><p>Same as <seealso marker="#type_time_unit"><c>nanosecond</c></seealso>.</p></item>
+ <item><p>Same as <seeerl marker="#type_time_unit"><c>nanosecond</c></seeerl>.</p></item>
</taglist>
</desc>
</datatype>
@@ -201,10 +208,41 @@
<name name="nif_resource"></name>
<desc>
<p>An opaque handle identifing a
- <seealso marker="erl_nif#resource_objects">NIF resource object
- </seealso>.</p>
+ <seecref marker="erl_nif#resource_objects">NIF resource object
+ </seecref>.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="spawn_opt_option"></name>
+ <desc>
+ <p>Options for <seemfa marker="#spawn_opt/4"><c>spawn_opt()</c></seemfa>.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="priority_level"></name>
+ <desc>
+ <p>Process priority level. For more info see
+ <seeerl marker="#process_flag_priority"><c>process_flag(priority,
+ Level)</c></seeerl> </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="max_heap_size"></name>
+ <desc>
+ <p>Process max heap size configuration. For more info see
+ <seeerl marker="#process_flag_max_heap_size"><c>process_flag(max_heap_size,
+ MaxHeapSize)</c></seeerl> </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="message_queue_data"></name>
+ <desc>
+ <p>Process message queue data configuration. For more information, see
+ <seeerl marker="#process_flag_message_queue_data"><c>process_flag(message_queue_data,
+ MQD)</c></seeerl> </p>
</desc>
</datatype>
+
</datatypes>
@@ -324,11 +362,11 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
the call is better written as
<c><anno>Module</anno>:<anno>Function</anno>(Arg1, Arg2, ...,
ArgN)</c>.</p>
- <p>Failure: <seealso marker="kernel:error_handler#undefined_function/3">
- <c>error_handler:undefined_function/3</c></seealso> is called
+ <p>Failure: <seemfa marker="kernel:error_handler#undefined_function/3">
+ <c>error_handler:undefined_function/3</c></seemfa> is called
if the applied function is not exported. The error handler
can be redefined (see
- <seealso marker="#process_flag/2"><c>process_flag/2</c></seealso>).
+ <seemfa marker="#process_flag/2"><c>process_flag/2</c></seemfa>).
If <c>error_handler</c> is undefined, or if the user has
redefined the default <c>error_handler</c> so the replacement
module is undefined, an error with reason <c>undef</c>
@@ -337,6 +375,16 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
+ <name name="atom_to_binary" arity="1" since="OTP 23.0"/>
+ <fsummary>Return the binary representation of an atom.</fsummary>
+ <desc>
+ <p>
+ The same as <seemfa marker="#atom_to_binary/2"><c>atom_to_binary</c>
+ </seemfa><c>(<anno>Atom</anno>, utf8)</c>.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="atom_to_binary" arity="2" since=""/>
<fsummary>Return the binary representation of an atom.</fsummary>
<desc>
@@ -393,7 +441,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
2> binary_part(Bin,{0,2}).
&lt;&lt;1,2&gt;&gt;</code>
<p>For details about the <c><anno>PosLen</anno></c> semantics, see
- <seealso marker="stdlib:binary"><c>binary(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:binary"><c>binary(3)</c></seeerl>.</p>
<p>Allowed in guard tests.</p>
</desc>
</func>
@@ -409,14 +457,21 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
+ <name name="binary_to_atom" arity="1" since="OTP 23.0"/>
+ <fsummary>Convert from text representation to an atom.</fsummary>
+ <desc>
+ <p>
+ The same as <seemfa marker="#binary_to_atom/2"><c>binary_to_atom</c>
+ </seemfa><c>(<anno>Binary</anno>, utf8)</c>.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="binary_to_atom" arity="2" since=""/>
<fsummary>Convert from text representation to an atom.</fsummary>
<desc>
<p>Returns the atom whose text representation is
- <c><anno>Binary</anno></c>.
- If <c><anno>Encoding</anno></c> is <c>latin1</c>, no
- translation of bytes in the binary is done.
- If <c><anno>Encoding</anno></c>
+ <c><anno>Binary</anno></c>. If <c><anno>Encoding</anno></c>
is <c>utf8</c> or <c>unicode</c>, the binary must contain
valid UTF-8 sequences.</p>
<note>
@@ -424,8 +479,8 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
is capable of encoding any Unicode character. Earlier versions would
fail if the binary contained Unicode characters &gt; 255.
For more information about Unicode support in atoms, see the
- <seealso marker="erl_ext_dist#utf8_atoms">note on UTF-8
- encoded atoms</seealso>
+ <seeguide marker="erl_ext_dist#utf8_atoms">note on UTF-8
+ encoded atoms</seeguide>
in section "External Term Format" in the User's Guide.</p>
</note>
<p>Examples:</p>
@@ -438,11 +493,22 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
+ <name name="binary_to_existing_atom" arity="1" since="OTP 23.0"/>
+ <fsummary>Convert from text representation to an atom.</fsummary>
+ <desc>
+ <p>
+ The same as <seemfa marker="#binary_to_existing_atom/2">
+ <c>binary_to_existing_atom</c></seemfa>
+ <c>(<anno>Binary</anno>, utf8)</c>.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="binary_to_existing_atom" arity="2" since=""/>
<fsummary>Convert from text representation to an atom.</fsummary>
<desc>
<p>As
- <seealso marker="#binary_to_atom/2"><c>binary_to_atom/2</c></seealso>,
+ <seemfa marker="#binary_to_atom/2"><c>binary_to_atom/2</c></seemfa>,
but the atom must exist.</p>
<p>Failure: <c>badarg</c> if the atom does not exist.</p>
<note>
@@ -524,8 +590,8 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
<note>
<p><em>The one-based indexing for binaries used by
this function is deprecated.</em> New code is to use
- <seealso marker="stdlib:binary#bin_to_list/3">
- <c>binary:bin_to_list/3</c></seealso>
+ <seemfa marker="stdlib:binary#bin_to_list/3">
+ <c>binary:bin_to_list/3</c></seemfa>
in STDLIB instead. All functions in module
<c>binary</c> consistently use zero-based indexing.</p>
</note>
@@ -538,8 +604,8 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
<desc>
<p>Returns an Erlang term that is the result of decoding
binary object <c><anno>Binary</anno></c>, which must be encoded
- according to the <seealso marker="erts:erl_ext_dist">
- Erlang external term format</seealso>.</p>
+ according to the <seeguide marker="erts:erl_ext_dist">
+ Erlang external term format</seeguide>.</p>
<pre>
> <input>Bin = term_to_binary(hello).</input>
&lt;&lt;131,100,0,5,104,101,108,108,111>>
@@ -552,9 +618,9 @@ hello
of Service attacks.</p>
</warning>
<p>See also
- <seealso marker="#term_to_binary/1"><c>term_to_binary/1</c></seealso>
- and <seealso marker="#binary_to_term/2">
- <c>binary_to_term/2</c></seealso>.</p>
+ <seemfa marker="#term_to_binary/1"><c>term_to_binary/1</c></seemfa>
+ and <seemfa marker="#binary_to_term/2">
+ <c>binary_to_term/2</c></seemfa>.</p>
</desc>
</func>
@@ -604,11 +670,11 @@ hello
<p>Failure: <c>badarg</c> if <c>safe</c> is specified and unsafe
data is decoded.</p>
<p>See also
- <seealso marker="#term_to_binary/1"><c>term_to_binary/1</c></seealso>,
- <seealso marker="#binary_to_term/1">
- <c>binary_to_term/1</c></seealso>, and
- <seealso marker="#list_to_existing_atom/1">
- <c>list_to_existing_atom/1</c></seealso>.</p>
+ <seemfa marker="#term_to_binary/1"><c>term_to_binary/1</c></seemfa>,
+ <seemfa marker="#binary_to_term/1">
+ <c>binary_to_term/1</c></seemfa>, and
+ <seemfa marker="#list_to_existing_atom/1">
+ <c>list_to_existing_atom/1</c></seemfa>.</p>
</desc>
</func>
@@ -678,8 +744,8 @@ hello
<fsummary>Cancel a timer.</fsummary>
<desc>
<p>Cancels a timer. The same as calling
- <seealso marker="#cancel_timer/2">
- <c>erlang:cancel_timer(TimerRef, [])</c></seealso>.</p>
+ <seemfa marker="#cancel_timer/2">
+ <c>erlang:cancel_timer(TimerRef, [])</c></seemfa>.</p>
</desc>
</func>
@@ -688,9 +754,9 @@ hello
<fsummary>Cancel a timer.</fsummary>
<desc>
<p>Cancels a timer that has been created by
- <seealso marker="#start_timer/4">
- <c>erlang:start_timer</c></seealso> or
- <seealso marker="#send_after/4"><c>erlang:send_after</c></seealso>.
+ <seemfa marker="#start_timer/4">
+ <c>erlang:start_timer</c></seemfa> or
+ <seemfa marker="#send_after/4"><c>erlang:send_after</c></seemfa>.
<c><anno>TimerRef</anno></c> identifies the timer, and
was returned by the BIF that created the timer.</p>
<p><c><anno>Option</anno></c>s:</p>
@@ -757,11 +823,11 @@ hello
process blocks until the operation has been performed.</p>
</note>
<p>See also
- <seealso marker="#send_after/4"><c>erlang:send_after/4</c></seealso>,
- <seealso marker="#start_timer/4">
- <c>erlang:start_timer/4</c></seealso>, and
- <seealso marker="#read_timer/2">
- <c>erlang:read_timer/2</c></seealso>.</p>
+ <seemfa marker="#send_after/4"><c>erlang:send_after/4</c></seemfa>,
+ <seemfa marker="#start_timer/4">
+ <c>erlang:start_timer/4</c></seemfa>, and
+ <seemfa marker="#read_timer/2">
+ <c>erlang:read_timer/2</c></seemfa>.</p>
</desc>
</func>
@@ -784,8 +850,8 @@ hello
<desc>
<p>Returns <c>true</c> if <c><anno>Module</anno></c> has old code,
otherwise <c>false</c>.</p>
- <p>See also <seealso marker="kernel:code">
- <c>code(3)</c></seealso>.</p>
+ <p>See also <seeerl marker="kernel:code">
+ <c>code(3)</c></seeerl>.</p>
</desc>
</func>
@@ -794,9 +860,9 @@ hello
<fsummary>Check if a process executes old code for a module.</fsummary>
<desc>
<p>The same as
- <seealso marker="#check_process_code/3">
+ <seemfa marker="#check_process_code/3">
<c>check_process_code(<anno>Pid</anno>, <anno>Module</anno>, [])</c>
- </seealso>.</p>
+ </seemfa>.</p>
</desc>
</func>
@@ -880,13 +946,13 @@ hello
after the purge). Literals will be taken care of (copied)
at a later stage. This behavior can as of ERTS version
8.1 be enabled when
- <seealso marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring">building OTP</seealso>,
+ <seeguide marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring">building OTP</seeguide>,
and will automatically be enabled if dirty scheduler
support is enabled.
</p>
</note>
- <p>See also <seealso marker="kernel:code">
- <c>code(3)</c></seealso>.</p>
+ <p>See also <seeerl marker="kernel:code">
+ <c>code(3)</c></seeerl>.</p>
<p>Failures:</p>
<taglist>
<tag><c>badarg</c></tag>
@@ -1045,7 +1111,9 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
as atoms. Others are returned as strings. Strings of
unrecognized header fields are formatted with only
capital letters first and after hyphen characters, for
- example, <c>"Sec-Websocket-Key"</c>.</p>
+ example, <c>"Sec-Websocket-Key"</c>. Header field names
+ are also returned in <c><anno>UnmodifiedField</anno></c>
+ as strings, without any conversion or formatting.</p>
<p>The protocol type <c>http</c> is only to be used for
the first line when an <c><anno>HttpRequest</anno></c> or an
<c><anno>HttpResponse</anno></c> is expected.
@@ -1112,7 +1180,7 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
otherwise <c>true</c>.</p>
<warning>
<p>This BIF is intended for the code server (see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>)
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>)
and is not to be used elsewhere.</p>
</warning>
<p>Failure: <c>badarg</c> if there already is an old version of
@@ -1126,7 +1194,7 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
<desc>
<p>If <c><anno>MonitorRef</anno></c> is a reference that the
calling process obtained by calling
- <seealso marker="#monitor/2"><c>monitor/2</c></seealso>,
+ <seemfa marker="#monitor/2"><c>monitor/2</c></seemfa>,
this monitoring is turned off. If the monitoring is already
turned off, nothing happens.</p>
<p>Once <c>demonitor(<anno>MonitorRef</anno>)</c> has returned, it is
@@ -1139,8 +1207,8 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
the call. It is therefore usually advisable
to remove such a <c>'DOWN'</c> message from the message queue
after monitoring has been stopped.
- <seealso marker="#demonitor/2">
- <c>demonitor(<anno>MonitorRef</anno>, [flush])</c></seealso>
+ <seemfa marker="#demonitor/2">
+ <c>demonitor(<anno>MonitorRef</anno>, [flush])</c></seemfa>
can be used instead of <c>demonitor(<anno>MonitorRef</anno>)</c>
if this cleanup is wanted.</p>
<note>
@@ -1169,8 +1237,8 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
<p>The returned value is <c>true</c> unless <c>info</c> is part
of <c><anno>OptionList</anno></c>.</p>
<p><c>demonitor(<anno>MonitorRef</anno>, [])</c> is equivalent to
- <seealso marker="#demonitor/1">
- <c>demonitor(<anno>MonitorRef</anno>)</c></seealso>.</p>
+ <seemfa marker="#demonitor/1">
+ <c>demonitor(<anno>MonitorRef</anno>)</c></seemfa>.</p>
<p><c><anno>Option</anno></c>s:</p>
<taglist>
<tag><c>flush</c></tag>
@@ -1224,7 +1292,7 @@ end</code>
</item>
<tag><c>badarg</c></tag>
<item>The same failure as for
- <seealso marker="#demonitor/1"><c>demonitor/1</c></seealso>.
+ <seemfa marker="#demonitor/1"><c>demonitor/1</c></seemfa>.
</item>
</taglist>
</desc>
@@ -1267,14 +1335,15 @@ end</code>
is available, the atom <c>none</c> is returned. One
can request to be informed by a message when more
data is available by calling
- <seealso marker="erlang#dist_ctrl_get_data_notification/1"><c>erlang:dist_ctrl_get_data_notification(DHandle)</c></seealso>.
+ <seemfa marker="erlang#dist_ctrl_get_data_notification/1"><c>erlang:dist_ctrl_get_data_notification(DHandle)</c></seemfa>.
</p>
<p>The returned value when there are data available depends
- on the value of the <c>get_size</c> option configured on the
- distribution channel identified by <c><anno>DHandle</anno></c>.
- For more information see the documentation of the <c>get_size</c>
+ on the value of the <c>get_size</c>
+ option configured on the distribution channel identified
+ by <c><anno>DHandle</anno></c>. For more information see
+ the documentation of the <c>get_size</c>
option for the
- <seealso marker="#dist_ctrl_set_opt/3"><c>erlang:dist_ctrl_set_opt/3</c></seealso>
+ <seemfa marker="#dist_ctrl_set_opt/3"><c>erlang:dist_ctrl_set_opt/3</c></seemfa>
function.</p>
<note><p>
Only the process registered as distribution
@@ -1287,11 +1356,11 @@ end</code>
distribution carrier using processes as distribution
controllers. <c><anno>DHandle</anno></c> is retrived
via the callback
- <seealso marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seealso>.
+ <seeguide marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seeguide>.
More information can be found in the documentation of
- <seealso marker="erts:alt_dist#distribution_module">ERTS
+ <seeguide marker="erts:alt_dist#distribution_module">ERTS
User's Guide ➜ How to implement an Alternative Carrier
- for the Erlang Distribution ➜ Distribution Module</seealso>.
+ for the Erlang Distribution ➜ Distribution Module</seeguide>.
</p>
</desc>
</func>
@@ -1303,7 +1372,7 @@ end</code>
<p>Returns the value of the <c>get_size</c> option on the distribution channel
identified by <c><anno>DHandle</anno></c>. For more information see the
documentation of the <c>get_size</c> option for the
- <seealso marker="#dist_ctrl_set_opt/3"><c>erlang:dist_ctrl_set_opt/3</c></seealso>
+ <seemfa marker="#dist_ctrl_set_opt/3"><c>erlang:dist_ctrl_set_opt/3</c></seemfa>
function.</p>
<note><p>
Only the process registered as distribution
@@ -1316,11 +1385,11 @@ end</code>
distribution carrier using processes as distribution
controllers. <c><anno>DHandle</anno></c> is retrived
via the callback
- <seealso marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seealso>.
+ <seeguide marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seeguide>.
More information can be found in the documentation of
- <seealso marker="erts:alt_dist#distribution_module">ERTS
+ <seeguide marker="erts:alt_dist#distribution_module">ERTS
User's Guide ➜ How to implement an Alternative Carrier
- for the Erlang Distribution ➜ Distribution Module</seealso>.
+ for the Erlang Distribution ➜ Distribution Module</seeguide>.
</p>
</desc>
</func>
@@ -1332,7 +1401,7 @@ end</code>
<p>
Request notification when more data is available to
fetch using
- <seealso marker="erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data(DHandle)</c></seealso>
+ <seemfa marker="erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data(DHandle)</c></seemfa>
for the distribution channel identified by
<c><anno>DHandle</anno></c>. When more data is present,
the caller will be sent the message <c>dist_data</c>.
@@ -1352,11 +1421,11 @@ end</code>
distribution carrier using processes as distribution
controllers. <c><anno>DHandle</anno></c> is retrived
via the callback
- <seealso marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seealso>.
+ <seeguide marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seeguide>.
More information can be found in the documentation of
- <seealso marker="erts:alt_dist#distribution_module">ERTS
+ <seeguide marker="erts:alt_dist#distribution_module">ERTS
User's Guide ➜ How to implement an Alternative Carrier
- for the Erlang Distribution ➜ Distribution Module</seealso>.
+ for the Erlang Distribution ➜ Distribution Module</seeguide>.
</p>
</desc>
</func>
@@ -1370,7 +1439,7 @@ end</code>
distribution channel identified by <c><anno>DHandle</anno></c>.
Once this function has been called, <c><anno>InputHandler</anno></c>
is the only process allowed to call
- <seealso marker="erlang#dist_ctrl_put_data/2"><c>erlang:dist_ctrl_put_data(DHandle, Data)</c></seealso>
+ <seemfa marker="erlang#dist_ctrl_put_data/2"><c>erlang:dist_ctrl_put_data(DHandle, Data)</c></seemfa>
with the <c><anno>DHandle</anno></c> identifing this distribution
channel.
</p>
@@ -1385,11 +1454,11 @@ end</code>
distribution carrier using processes as distribution
controllers. <c><anno>DHandle</anno></c> is retrived
via the callback
- <seealso marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seealso>.
+ <seeguide marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seeguide>.
More information can be found in the documentation of
- <seealso marker="erts:alt_dist#distribution_module">ERTS
+ <seeguide marker="erts:alt_dist#distribution_module">ERTS
User's Guide ➜ How to implement an Alternative Carrier
- for the Erlang Distribution ➜ Distribution Module</seealso>.
+ for the Erlang Distribution ➜ Distribution Module</seeguide>.
</p>
</desc>
</func>
@@ -1408,7 +1477,7 @@ end</code>
<c><anno>DHandle</anno></c> is allowed to call this
function unless an alternate input handler process
has been registered using
- <seealso marker="erlang#dist_ctrl_input_handler/2"><c>erlang:dist_ctrl_input_handler(DHandle, InputHandler)</c></seealso>.
+ <seemfa marker="erlang#dist_ctrl_input_handler/2"><c>erlang:dist_ctrl_input_handler(DHandle, InputHandler)</c></seemfa>.
If an alternate input handler has been registered, only
the registered input handler process is allowed to call
this function.
@@ -1418,11 +1487,11 @@ end</code>
distribution carrier using processes as distribution
controllers. <c><anno>DHandle</anno></c> is retrived
via the callback
- <seealso marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seealso>.
+ <seeguide marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seeguide>.
More information can be found in the documentation of
- <seealso marker="erts:alt_dist#distribution_module">ERTS
+ <seeguide marker="erts:alt_dist#distribution_module">ERTS
User's Guide ➜ How to implement an Alternative Carrier
- for the Erlang Distribution ➜ Distribution Module</seealso>.
+ for the Erlang Distribution ➜ Distribution Module</seeguide>.
</p>
</desc>
</func>
@@ -1434,7 +1503,7 @@ end</code>
<p>Sets the value of the <c>get_size</c> option on the distribution channel
identified by <c><anno>DHandle</anno></c>. This option controls the return
value of calls to
- <seealso marker="#dist_ctrl_get_data/1">erlang:dist_ctrl_get_data(<anno>DHandle</anno>)</seealso>
+ <seemfa marker="#dist_ctrl_get_data/1">erlang:dist_ctrl_get_data(<anno>DHandle</anno>)</seemfa>
where <c><anno>DHandle</anno></c> equals <c><anno>DHandle</anno></c> used
when setting this option.
When the <c>get_size</c> option is:</p>
@@ -1467,11 +1536,11 @@ end</code>
distribution carrier using processes as distribution
controllers. <c><anno>DHandle</anno></c> is retrived
via the callback
- <seealso marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seealso>.
+ <seeguide marker="erts:alt_dist#hs_data_f_handshake_complete"><c>f_handshake_complete</c></seeguide>.
More information can be found in the documentation of
- <seealso marker="erts:alt_dist#distribution_module">ERTS
+ <seeguide marker="erts:alt_dist#distribution_module">ERTS
User's Guide ➜ How to implement an Alternative Carrier
- for the Erlang Distribution ➜ Distribution Module</seealso>.
+ for the Erlang Distribution ➜ Distribution Module</seeguide>.
</p>
</desc>
</func>
@@ -1599,8 +1668,8 @@ b</pre>
</item>
<item><p><c>From</c> is the process identifier of the process
that sent the exit signal. See also
- <seealso marker="#process_flag/2">
- <c>process_flag/2</c></seealso>.</p>
+ <seemfa marker="#process_flag/2">
+ <c>process_flag/2</c></seemfa>.</p>
</item>
</list>
<p>If <c><anno>Reason</anno></c> is the atom <c>normal</c>,
@@ -1649,8 +1718,8 @@ erlang:external_size(<anno>Term</anno>, [])</code>
true</pre>
<p>Option <c>{minor_version, <anno>Version</anno>}</c> specifies how
floats are encoded. For a detailed description, see
- <seealso marker="#term_to_binary/2">
- <c>term_to_binary/2</c></seealso>.</p>
+ <seemfa marker="#term_to_binary/2">
+ <c>term_to_binary/2</c></seemfa>.</p>
</desc>
</func>
@@ -1667,7 +1736,7 @@ true</pre>
<note>
<p>If used on the top level in a guard, it tests whether the
argument is a floating point number; for clarity, use
- <seealso marker="#is_float/1"><c>is_float/1</c></seealso>
+ <seemfa marker="#is_float/1"><c>is_float/1</c></seemfa>
instead.</p>
<p>When <c>float/1</c> is used in an expression in a guard,
such as '<c>float(A) == 4.0</c>', it converts a number as
@@ -1693,8 +1762,8 @@ true</pre>
<p>Returns a binary corresponding to the text
representation of <c><anno>Float</anno></c> using fixed decimal
point formatting. <c><anno>Options</anno></c> behaves in the same
- way as <seealso marker="#float_to_list/2">
- <c>float_to_list/2</c></seealso>. Examples:</p>
+ way as <seemfa marker="#float_to_list/2">
+ <c>float_to_list/2</c></seemfa>. Examples:</p>
<pre>
> <input>float_to_binary(7.12, [{decimals, 4}]).</input>
&lt;&lt;"7.1200">>
@@ -1735,8 +1804,8 @@ true</pre>
digits of precision.</p>
</item>
<item><p>If <c>Options</c> is <c>[]</c>, the function behaves as
- <seealso marker="#float_to_list/1">
- <c>float_to_list/1</c></seealso>.</p>
+ <seemfa marker="#float_to_list/1">
+ <c>float_to_list/1</c></seemfa>.</p>
</item>
</list>
<p>Examples:</p>
@@ -1839,7 +1908,7 @@ true</pre>
<p>It might point to the <c>init</c> process if the
<c>Fun</c> was statically allocated when module was
loaded (this optimisation is performed for local
- functions that do not capture the enviornment).</p>
+ functions that do not capture the environment).</p>
</item>
<tag><c>{index, Index}</c></tag>
<item>
@@ -1883,7 +1952,7 @@ true</pre>
<c>uniq</c>, and <c>pid</c>. For an external fun, the value
of any of these items is always the atom <c>undefined</c>.</p>
<p>See
- <seealso marker="#fun_info/1"><c>erlang:fun_info/1</c></seealso>.</p>
+ <seemfa marker="#fun_info/1"><c>erlang:fun_info/1</c></seemfa>.</p>
</desc>
</func>
@@ -1933,8 +2002,8 @@ true</pre>
<fsummary>Garbage collect a process.</fsummary>
<desc>
<p>The same as
- <seealso marker="#garbage_collect/2">
- <c>garbage_collect(<anno>Pid</anno>, [])</c></seealso>.</p>
+ <seemfa marker="#garbage_collect/2">
+ <c>garbage_collect(<anno>Pid</anno>, [])</c></seemfa>.</p>
</desc>
</func>
@@ -1964,8 +2033,8 @@ true</pre>
<p>If <c><anno>Pid</anno></c> equals <c>self()</c>, and
no <c>async</c> option has been passed, the garbage
collection is performed at once, that is, the same as calling
- <seealso marker="#garbage_collect/0">
- <c>garbage_collect/0</c></seealso>.
+ <seemfa marker="#garbage_collect/0">
+ <c>garbage_collect/0</c></seemfa>.
Otherwise a request for garbage collection
is sent to the process identified by <c><anno>Pid</anno></c>,
and will be handled when appropriate. If no <c>async</c>
@@ -1987,8 +2056,8 @@ true</pre>
</item>
</taglist>
<p>Notice that the same caveats apply as for
- <seealso marker="#garbage_collect/0">
- <c>garbage_collect/0</c></seealso>.</p>
+ <seemfa marker="#garbage_collect/0">
+ <c>garbage_collect/0</c></seemfa>.</p>
<p>Failures:</p>
<taglist>
<tag><c>badarg</c></tag>
@@ -2082,8 +2151,9 @@ true</pre>
<fsummary>Get the call stack back-trace of the last exception.</fsummary>
<type name="stack_item"/>
<desc>
- <warning><p><c>erlang:get_stacktrace/0</c> is deprecated and will stop working
- in a future release.</p></warning>
+ <warning><p><c>erlang:get_stacktrace/0</c> is deprecated and
+ will be removed in OTP 24. Starting from OTP 23,
+ <c>erlang:get_stacktrace/0</c> returns <c>[]</c>.</p></warning>
<p>Instead of using <c>erlang:get_stacktrace/0</c> to retrieve
the call stack back-trace, use the following syntax:</p>
<pre>
@@ -2092,53 +2162,6 @@ catch
Class:Reason:Stacktrace ->
{Class,Reason,Stacktrace}
end</pre>
- <p><c>erlang:get_stacktrace/0</c> retrieves the call stack back-trace
- (<em>stacktrace</em>) for an exception that has just been
- caught in the calling process as a list of
- <c>{<anno>Module</anno>,<anno>Function</anno>,<anno>Arity</anno>,<anno>Location</anno>}</c>
- tuples. Field <c><anno>Arity</anno></c> in the first tuple can
- be the argument list of that function call instead of an arity
- integer, depending on the exception.</p>
- <p>If there has not been any exceptions in a process, the
- stacktrace is <c>[]</c>. After a code change for the process,
- the stacktrace can also be reset to <c>[]</c>.</p>
- <p>The stacktrace is the same data as operator <c>catch</c>
- returns, for example:</p>
- <pre>
-{'EXIT',{badarg,Stacktrace}} = catch abs(x)</pre>
- <p><c><anno>Location</anno></c> is a (possibly empty) list
- of two-tuples that
- can indicate the location in the source code of the function.
- The first element is an atom describing the type of
- information in the second element. The following
- items can occur:</p>
- <taglist>
- <tag><c>file</c></tag>
- <item>The second element of the tuple is a string (list of
- characters) representing the filename of the source file
- of the function.
- </item>
- <tag><c>line</c></tag>
- <item>The second element of the tuple is the line number
- (an integer &gt; 0) in the source file
- where the exception occurred or the function was called.
- </item>
- </taglist>
- <warning><p>Developers should rely on stacktrace entries only for
- debugging purposes.</p>
- <p>The VM performs tail call optimization, which
- does not add new entries to the stacktrace, and also limits stacktraces
- to a certain depth. Furthermore, compiler options, optimizations and
- future changes may add or remove stacktrace entries, causing any code
- that expects the stacktrace to be in a certain order or contain specific
- items to fail.</p>
- <p>The only exception to this rule is <c>error:undef</c> which
- guarantees to include the <anno>Module</anno>, <anno>Function</anno> and <anno>Arity</anno>
- of the attempted function as the first stacktrace entry.</p>
- </warning>
- <p>See also
- <seealso marker="#error/1"><c>error/1</c></seealso> and
- <seealso marker="#error/2"><c>error/2</c></seealso>.</p>
</desc>
</func>
@@ -2171,9 +2194,9 @@ end</pre>
assumes the group leader of their processes is
their application master.</p>
<p>See also
- <seealso marker="#group_leader/0"><c>group_leader/0</c></seealso>
- and <seealso marker="doc/design_principles:applications#stopping">OTP
- design principles</seealso> related to starting and stopping
+ <seemfa marker="#group_leader/0"><c>group_leader/0</c></seemfa>
+ and <seeguide marker="system/design_principles:applications#stopping">OTP
+ design principles</seeguide> related to starting and stopping
applications.</p>
</desc>
</func>
@@ -2184,7 +2207,7 @@ end</pre>
the calling environment.</fsummary>
<desc>
<p>The same as
- <seealso marker="#halt/2"><c>halt(0, [])</c></seealso>. Example:</p>
+ <seemfa marker="#halt/2"><c>halt(0, [])</c></seemfa>. Example:</p>
<pre>
> <input>halt().</input>
os_prompt%</pre>
@@ -2195,8 +2218,8 @@ os_prompt%</pre>
<name name="halt" arity="1" since=""/>
<fsummary>Halt the Erlang runtime system.</fsummary>
<desc>
- <p>The same as <seealso marker="#halt/2">
- <c>halt(<anno>Status</anno>, [])</c></seealso>. Example:</p>
+ <p>The same as <seemfa marker="#halt/2">
+ <c>halt(<anno>Status</anno>, [])</c></seemfa>. Example:</p>
<pre>
> <input>halt(17).</input>
os_prompt% <input>echo $?</input>
@@ -2296,8 +2319,8 @@ os_prompt%</pre>
hibernation. One effect of this is that processes started
using <c>proc_lib</c> (also indirectly, such as
<c>gen_server</c> processes), are to use
- <seealso marker="stdlib:proc_lib#hibernate/3">
- <c>proc_lib:hibernate/3</c></seealso>
+ <seemfa marker="stdlib:proc_lib#hibernate/3">
+ <c>proc_lib:hibernate/3</c></seemfa>
instead, to ensure that the exception handler continues to work
when the process wakes up.</p>
</desc>
@@ -2673,28 +2696,85 @@ false</code>
<func>
<name name="link" arity="1" since=""/>
- <fsummary>Create a link to another process (or port).</fsummary>
- <desc>
- <p>Creates a link between the calling process and another
- process (or port) <c><anno>PidOrPort</anno></c>, if there is
- not such a link
- already. If a process attempts to create a link to itself,
- nothing is done. Returns <c>true</c>.</p>
- <p>If <c><anno>PidOrPort</anno></c> does not exist, the behavior
- of the BIF
- depends on if the calling process is trapping exits or not (see
- <seealso marker="#process_flag/2">
- <c>process_flag/2</c></seealso>):</p>
- <list type="bulleted">
- <item><p>If the calling process is not trapping exits, and
- checking <c><anno>PidOrPort</anno></c> is cheap
- (that is, if <c><anno>PidOrPort</anno></c>
- is local), <c>link/1</c> fails with reason <c>noproc</c>.</p></item>
- <item><p>Otherwise, if the calling process is trapping exits,
- and/or <c><anno>PidOrPort</anno></c> is remote, <c>link/1</c>
- returns <c>true</c>, but an exit signal with reason <c>noproc</c>
- is sent to the calling process.</p></item>
- </list>
+ <fsummary>Create a link to another process or port.</fsummary>
+ <desc>
+
+ <p>
+ Sets up and activates a link between the calling process and
+ another process or a port identified by
+ <c><anno>PidOrPort</anno></c>. We will from here on call the
+ identified process or port linkee. If the linkee is a port, it
+ must reside on the same node as the caller.
+ </p>
+
+ <p>
+ If one of the participants of a link terminates, it will
+ <seeguide marker="system/reference_manual:processes#sending_exit_signals">send
+ an exit signal</seeguide> to the other participant. The exit
+ signal will contain the
+ <seeguide marker="system/reference_manual:processes#link_exit_signal_reason">exit
+ reason</seeguide> of the terminated participant. Other cases when
+ exit signals are triggered due to a link are when no linkee exist
+ (<c>noproc</c> exit reason) and when the connection between linked
+ processes on different nodes is lost or cannot be established
+ (<c>noconnection</c> exit reason).
+ </p>
+
+ <p>
+ An existing link can be removed by calling
+ <seemfa marker="#unlink/1"><c>unlink/1</c></seemfa>.
+ For more information on links and exit signals due to links, see
+ the <i>Processes</i> chapter in the <i>Erlang Reference Manual</i>:
+ </p>
+ <list>
+ <item>
+ <seeguide marker="system/reference_manual:processes#links">Links</seeguide>
+ </item>
+ <item>
+ <seeguide marker="system/reference_manual:processes#sending_exit_signals">Sending
+ Exit Signals</seeguide>
+ </item>
+ <item>
+ <seeguide marker="system/reference_manual:processes#receiving_exit_signals">Receiving
+ Exit Signals</seeguide>
+ </item>
+ </list>
+
+ <p>
+ For historical reasons, <c>link/1</c> has a strange
+ semi-synchronous behavior when it is "cheap" to check if the
+ linkee exists or not, and the caller does not
+ <seeerl marker="erlang#process_flag_trap_exit">trap exits</seeerl>.
+ If the above is true and the linkee does not exist, <c>link/1</c>
+ will raise a <c>noproc</c> error <em>exception</em>. The expected
+ behavior would instead have been that <c>link/1</c> returned
+ <c>true</c>, and the caller later was sent an exit signal
+ with <c>noproc</c> exit reason, but this is unfortunately not the
+ case. The <c>noproc</c>
+ <seeguide marker="system/reference_manual:errors#exceptions">
+ exception</seeguide> is not to be confused with an
+ <seeguide marker="system/reference_manual:processes#sending_exit_signals">exit
+ signal</seeguide> with exit reason <c>noproc</c>. Currently it is "cheap"
+ to check if the linkee exists when it is supposed to reside on
+ the same node as the calling process.
+ </p>
+
+ <p>
+ The link setup and activation is performed asynchronously. If the
+ link already exists, or if the caller attempts to create a link
+ to itself, nothing is done. A detailed description of the
+ <seeguide marker="erts:erl_dist_protocol#link_protocol">link
+ protocol</seeguide> can be found in the <i>Distribution Protocol</i>
+ chapter of the <i>ERTS User's Guide</i>.
+ </p>
+
+ <p>Failure:</p>
+ <list>
+ <item><c>badarg</c> if <c><anno>PidOrPort</anno></c> does not identify
+ a process or a node local port.</item>
+ <item><c>noproc</c> linkee does not exist and it is "cheap" to check
+ if it exists as described above.</item>
+ </list>
</desc>
</func>
@@ -2708,8 +2788,8 @@ false</code>
any Unicode character. Earlier versions allowed only ISO-latin-1
characters as the implementation did not allow Unicode characters
above 255. For more information on Unicode support in atoms, see
- <seealso marker="erl_ext_dist#utf8_atoms">note on UTF-8
- encoded atoms</seealso>
+ <seeguide marker="erl_ext_dist#utf8_atoms">note on UTF-8
+ encoded atoms</seeguide>
in section "External Term Format" in the User's Guide.</p>
<p>Example:</p>
<pre>
@@ -2915,7 +2995,7 @@ false</code>
</taglist>
<warning>
<p>This BIF is intended for the code server (see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>)
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>)
and is not to be used elsewhere.</p>
</warning>
</desc>
@@ -2934,7 +3014,7 @@ false</code>
done. If the name is the same, but the contents differ, the
old library may be loaded instead. For information on how to
implement a NIF library, see
- <seealso marker="erl_nif"><c>erl_nif(3)</c></seealso>.</p>
+ <seecref marker="erl_nif"><c>erl_nif(3)</c></seecref>.</p>
<p><c><anno>LoadInfo</anno></c> can be any term. It is passed on to
the library as part of the initialization. A good practice is
to include a module version number to support future code
@@ -2980,8 +3060,8 @@ false</code>
<desc>
<p>Returns a list of all loaded Erlang modules (current and
old code), including preloaded modules.</p>
- <p>See also <seealso marker="kernel:code">
- <c>code(3)</c></seealso>.</p>
+ <p>See also <seeerl marker="kernel:code">
+ <c>code(3)</c></seeerl>.</p>
</desc>
</func>
@@ -3048,8 +3128,8 @@ false</code>
<fsummary>Return a unique reference.</fsummary>
<desc>
<p>Returns a
- <seealso marker="doc/efficiency_guide:advanced#unique_references">
- unique reference</seealso>. The reference is unique among
+ <seeguide marker="system/efficiency_guide:advanced#unique_references">
+ unique reference</seeguide>. The reference is unique among
connected nodes.</p>
<warning>
<p>Known issue: When a node is restarted multiple
@@ -3128,9 +3208,9 @@ false</code>
<fsummary>Test that a match specification works.</fsummary>
<desc>
<p>Tests a match specification used in calls to
- <seealso marker="stdlib:ets#select/2"><c>ets:select/2</c></seealso>
- and <seealso marker="#trace_pattern/3">
- <c>erlang:trace_pattern/3</c></seealso>.
+ <seemfa marker="stdlib:ets#select/2"><c>ets:select/2</c></seemfa>
+ and <seemfa marker="#trace_pattern/3">
+ <c>erlang:trace_pattern/3</c></seemfa>.
The function tests both a match specification for "syntactic"
correctness and runs the match specification against the object. If
the match specification contains errors, the tuple <c>{error,
@@ -3157,7 +3237,7 @@ false</code>
<p>This is a useful debugging and test tool, especially when writing
complicated match specifications.</p>
<p>See also
- <seealso marker="stdlib:ets#test_ms/2"><c>ets:test_ms/2</c></seealso>.</p>
+ <seemfa marker="stdlib:ets#test_ms/2"><c>ets:test_ms/2</c></seemfa>.</p>
</desc>
</func>
@@ -3253,8 +3333,8 @@ false</code>
<p>The total amount of memory currently allocated for
the emulator that is not directly related to any Erlang
process. Memory presented as <c>processes</c> is not
- included in this memory. <seealso marker="tools:instrument">
- <c>instrument(3)</c></seealso> can be used to
+ included in this memory. <seeerl marker="tools:instrument">
+ <c>instrument(3)</c></seeerl> can be used to
get a more detailed breakdown of what memory is part
of this type.</p>
</item>
@@ -3304,9 +3384,9 @@ false</code>
when the emulator is run with instrumentation.</p>
<p>For information on how to run the emulator with
instrumentation, see
- <seealso marker="tools:instrument">
- <c>instrument(3)</c></seealso>
- and/or <seealso marker="erl"><c>erl(1)</c></seealso>.</p>
+ <seeerl marker="tools:instrument">
+ <c>instrument(3)</c></seeerl>
+ and/or <seecom marker="erl"><c>erl(1)</c></seecom>.</p>
</item>
</taglist>
<note>
@@ -3354,11 +3434,11 @@ RealSystem = system + MissedSystem</code>
</note>
<note>
<p>As from ERTS 5.6.4, <c>erlang:memory/0</c> requires that
- all <seealso marker="erts:erts_alloc"><c>erts_alloc(3)</c></seealso>
+ all <seecref marker="erts:erts_alloc"><c>erts_alloc(3)</c></seecref>
allocators are enabled (default behavior).</p>
</note>
<p>Failure: <c>notsup</c> if an
- <seealso marker="erts:erts_alloc"><c>erts_alloc(3)</c></seealso>
+ <seecref marker="erts:erts_alloc"><c>erts_alloc(3)</c></seecref>
allocator has been disabled.</p>
</desc>
</func>
@@ -3376,7 +3456,7 @@ RealSystem = system + MissedSystem</code>
<note>
<p>As from ERTS 5.6.4,
<c>erlang:memory/1</c> requires that
- all <seealso marker="erts_alloc"><c>erts_alloc(3)</c></seealso>
+ all <seecref marker="erts_alloc"><c>erts_alloc(3)</c></seecref>
allocators are enabled (default behavior).</p>
</note>
<p>Failures:</p>
@@ -3385,7 +3465,7 @@ RealSystem = system + MissedSystem</code>
<item>
If <c><anno>Type</anno></c> is not one of the memory types
listed in the description of
- <seealso marker="#memory/0"><c>erlang:memory/0</c></seealso>.
+ <seemfa marker="#memory/0"><c>erlang:memory/0</c></seemfa>.
</item>
<tag><c>badarg</c></tag>
<item>
@@ -3394,12 +3474,12 @@ RealSystem = system + MissedSystem</code>
</item>
<tag><c>notsup</c></tag>
<item>
- If an <seealso marker="erts_alloc"><c>erts_alloc(3)</c></seealso>
+ If an <seecref marker="erts_alloc"><c>erts_alloc(3)</c></seecref>
allocator has been disabled.
</item>
</taglist>
<p>See also
- <seealso marker="#memory/0"><c>erlang:memory/0</c></seealso>.</p>
+ <seemfa marker="#memory/0"><c>erlang:memory/0</c></seemfa>.</p>
</desc>
</func>
@@ -3422,7 +3502,7 @@ RealSystem = system + MissedSystem</code>
the module.</p>
<warning>
<p>This BIF is intended for the code server (see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>)
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>)
and is not to be used elsewhere.</p>
</warning>
</desc>
@@ -3458,7 +3538,7 @@ RealSystem = system + MissedSystem</code>
or port terminates, does not exist at the moment of creation,
or if the connection to it is lost. If the connection to it is lost,
we do not know if it still exists. The monitoring is also turned off
- when <seealso marker="#demonitor/1">demonitor/1</seealso> is
+ when <seemfa marker="#demonitor/1">demonitor/1</seemfa> is
called.</p>
<p>A <c>process</c> or <c>port</c> monitor by name
@@ -3529,12 +3609,12 @@ RealSystem = system + MissedSystem</code>
<marker id="monitor_time_offset"/><c>time_offset</c></tag>
<item>
<p>Monitors changes in
- <seealso marker="#time_offset/0"><c>time offset</c></seealso>
+ <seemfa marker="#time_offset/0"><c>time offset</c></seemfa>
between
- <seealso marker="time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso> and
- <seealso marker="time_correction#Erlang_System_Time">Erlang
- system time</seealso>. One valid <c><anno>Item</anno></c>
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide> and
+ <seeguide marker="time_correction#Erlang_System_Time">Erlang
+ system time</seeguide>. One valid <c><anno>Item</anno></c>
exists in combination with the
<c>time_offset <anno>Type</anno></c>, namely the atom
<c>clock_service</c>. Notice that the atom <c>clock_service</c> is
@@ -3545,20 +3625,20 @@ RealSystem = system + MissedSystem</code>
<p>The monitor is triggered when the time offset is changed.
This either if the time offset value is changed, or if the
offset is changed from preliminary to final during
- <seealso marker="#system_flag_time_offset">finalization
- of the time offset</seealso> when the
- <seealso marker="time_correction#Single_Time_Warp_Mode">single
- time warp mode</seealso> is used. When a change from preliminary
+ <seeerl marker="#system_flag_time_offset">finalization
+ of the time offset</seeerl> when the
+ <seeguide marker="time_correction#Single_Time_Warp_Mode">single
+ time warp mode</seeguide> is used. When a change from preliminary
to final time offset is made, the monitor is triggered once
regardless of whether the time offset value was changed
or not.</p>
<p>If the runtime system is in
- <seealso marker="time_correction#Multi_Time_Warp_Mode">multi
- time warp mode</seealso>, the time offset is changed when
+ <seeguide marker="time_correction#Multi_Time_Warp_Mode">multi
+ time warp mode</seeguide>, the time offset is changed when
the runtime system detects that the
- <seealso marker="time_correction#OS_System_Time">OS system
- time</seealso> has changed. The runtime system does, however,
+ <seeguide marker="time_correction#OS_System_Time">OS system
+ time</seeguide> has changed. The runtime system does, however,
not detect this immediately when it occurs. A task checking
the time offset is scheduled to execute at least once a minute,
so under normal operation this is to be detected within a
@@ -3579,8 +3659,8 @@ RealSystem = system + MissedSystem</code>
<p>When the <c>'CHANGE'</c> message has been received you are
guaranteed not to retrieve the old time offset when calling
- <seealso marker="#time_offset/0">
- <c>erlang:time_offset()</c></seealso>.
+ <seemfa marker="#time_offset/0">
+ <c>erlang:time_offset()</c></seemfa>.
Notice that you can observe the change of the time offset
when calling <c>erlang:time_offset()</c> before you
get the <c>'CHANGE'</c> message.</p>
@@ -3631,7 +3711,7 @@ RealSystem = system + MissedSystem</code>
<fsummary>Monitor the status of a node.</fsummary>
<desc>
<p>Behaves as
- <seealso marker="#monitor_node/2"><c>monitor_node/2</c></seealso>
+ <seemfa marker="#monitor_node/2"><c>monitor_node/2</c></seemfa>
except that it allows an
extra option to be specified, namely <c>allow_passive_connect</c>.
This option allows the BIF to wait the normal network connection
@@ -3657,17 +3737,17 @@ RealSystem = system + MissedSystem</code>
<fsummary>Current Erlang monotonic time.</fsummary>
<desc>
<p>Returns the current
- <seealso marker="time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso> in <c>native</c>
- <seealso marker="#type_time_unit">time unit</seealso>. This
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide> in <c>native</c>
+ <seeerl marker="#type_time_unit">time unit</seeerl>. This
is a monotonically increasing time since some unspecified point in
time.</p>
<note>
<p>This is a
- <seealso marker="time_correction#Monotonically_Increasing">
- monotonically increasing</seealso> time, but <em>not</em> a
- <seealso marker="time_correction#Strictly_Monotonically_Increasing">
- strictly monotonically increasing</seealso>
+ <seeguide marker="time_correction#Monotonically_Increasing">
+ monotonically increasing</seeguide> time, but <em>not</em> a
+ <seeguide marker="time_correction#Strictly_Monotonically_Increasing">
+ strictly monotonically increasing</seeguide>
time. That is, consecutive calls to
<c>erlang:monotonic_time/0</c> can produce the same result.</p>
<p>Different runtime system instances will use different unspecified
@@ -3680,8 +3760,8 @@ RealSystem = system + MissedSystem</code>
positive value), or the runtime system start (time at start is
zero). The monotonic time at runtime system start can be
retrieved by calling
- <seealso marker="#system_info_start_time">
- <c>erlang:system_info(start_time)</c></seealso>.</p>
+ <seeerl marker="#system_info_start_time">
+ <c>erlang:system_info(start_time)</c></seeerl>.</p>
</note>
</desc>
</func>
@@ -3691,14 +3771,14 @@ RealSystem = system + MissedSystem</code>
<fsummary>Current Erlang monotonic time.</fsummary>
<desc>
<p>Returns the current
- <seealso marker="time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso> converted
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide> converted
into the <c><anno>Unit</anno></c> passed as argument.</p>
<p>Same as calling
- <seealso marker="#convert_time_unit/3">
- <c>erlang:convert_time_unit</c></seealso><c>(</c><seealso
+ <seemfa marker="#convert_time_unit/3">
+ <c>erlang:convert_time_unit</c></seemfa><c>(</c><seemfa
marker="#monotonic_time/0">
- <c>erlang:monotonic_time()</c></seealso><c>,
+ <c>erlang:monotonic_time()</c></seemfa><c>,
native, <anno>Unit</anno>)</c>,
however optimized for commonly used <c><anno>Unit</anno></c>s.</p>
</desc>
@@ -3709,7 +3789,7 @@ RealSystem = system + MissedSystem</code>
<fsummary>Stop execution with a specified reason.</fsummary>
<desc>
<p>Works exactly like
- <seealso marker="#error/1"><c>error/1</c></seealso>, but
+ <seemfa marker="#error/1"><c>error/1</c></seemfa>, but
Dialyzer thinks that this BIF will return an arbitrary
term. When used in a stub function for a NIF to generate an
exception when the NIF library is not loaded, Dialyzer
@@ -3722,7 +3802,7 @@ RealSystem = system + MissedSystem</code>
<fsummary>Stop execution with a specified reason.</fsummary>
<desc>
<p>Works exactly like
- <seealso marker="#error/2"><c>error/2</c></seealso>, but
+ <seemfa marker="#error/2"><c>error/2</c></seemfa>, but
Dialyzer thinks that this BIF will return an arbitrary
term. When used in a stub function for a NIF to generate an
exception when the NIF library is not loaded, Dialyzer
@@ -3795,8 +3875,8 @@ RealSystem = system + MissedSystem</code>
The set of known nodes is garbage collected. Notice that
this garbage collection can be delayed. For more
information, see
- <seealso marker="erlang#system_info_delayed_node_table_gc">
- <c>erlang:system_info(delayed_node_table_gc)</c></seealso>.</p>
+ <seeerl marker="erlang#system_info_delayed_node_table_gc">
+ <c>erlang:system_info(delayed_node_table_gc)</c></seeerl>.</p>
</item>
</taglist>
<p>Some equalities: <c>[node()] = nodes(this)</c>,
@@ -3813,10 +3893,10 @@ RealSystem = system + MissedSystem</code>
<warning>
<p><em>This function is deprecated. Do not use it.</em></p>
<p>For more information, see section
- <seealso marker="time_correction">Time and Time Correction</seealso>
+ <seeguide marker="time_correction">Time and Time Correction</seeguide>
in the User's Guide. Specifically, section
- <seealso marker="time_correction#Dos_and_Donts">
- Dos and Dont's</seealso> describes what to use instead of
+ <seeguide marker="time_correction#Dos_and_Donts">
+ Dos and Dont's</seeguide> describes what to use instead of
<c>erlang:now/0</c>.</p>
</warning>
<p>Returns the tuple <c>{MegaSecs, Secs, MicroSecs}</c>, which is
@@ -3848,11 +3928,11 @@ RealSystem = system + MissedSystem</code>
translation or to force, for example UTF-8, supply the executable
and/or arguments as a binary in the correct
encoding. For details, see the module
- <seealso marker="kernel:file"><c>file(3)</c></seealso>, the
- function <seealso marker="kernel:file#native_name_encoding/0">
- <c>file:native_name_encoding/0</c></seealso> in Kernel, and
- the <seealso marker="stdlib:unicode_usage">
- <c>Using Unicode in Erlang</c></seealso> User's Guide.</p>
+ <seeerl marker="kernel:file"><c>file(3)</c></seeerl>, the
+ function <seemfa marker="kernel:file#native_name_encoding/0">
+ <c>file:native_name_encoding/0</c></seemfa> in Kernel, and
+ the <seeguide marker="stdlib:unicode_usage">
+ <c>Using Unicode in Erlang</c></seeguide> User's Guide.</p>
<note>
<p>The characters in the name (if specified as a list) can
only be &gt; 255 if the Erlang virtual machine is started
@@ -3902,8 +3982,8 @@ RealSystem = system + MissedSystem</code>
program, it is executed directly. <c>PATH</c> (or
equivalent) is not searched. To find a program
in <c>PATH</c> to execute, use
- <seealso marker="kernel:os#find_executable/1">
- <c>os:find_executable/1</c></seealso>.</p>
+ <seemfa marker="kernel:os#find_executable/1">
+ <c>os:find_executable/1</c></seemfa>.</p>
<p>Only if a shell script or <c>.bat</c> file is
executed, the appropriate command interpreter is
invoked implicitly, but there is still no
@@ -3972,8 +4052,8 @@ RealSystem = system + MissedSystem</code>
<item>
<p>
Types:<br/>
- &nbsp;&nbsp;<c><anno>Name</anno> = </c><seealso marker="kernel:os#type-env_var_name"><c>os:env_var_name()</c></seealso><br/>
- &nbsp;&nbsp;<c><anno>Val</anno> = </c><seealso marker="kernel:os#type-env_var_value"><c>os:env_var_value()</c></seealso><c> | false</c><br/>
+ &nbsp;&nbsp;<c><anno>Name</anno> = </c><seetype marker="kernel:os#env_var_name"><c>os:env_var_name()</c></seetype><br/>
+ &nbsp;&nbsp;<c><anno>Val</anno> = </c><seetype marker="kernel:os#env_var_value"><c>os:env_var_value()</c></seetype><c> | false</c><br/>
&nbsp;&nbsp;<c>Env = [{<anno>Name</anno>, <anno>Val</anno>}]</c>
</p>
<p>Only valid for <c>{spawn, <anno>Command</anno>}</c>, and
@@ -3989,7 +4069,7 @@ RealSystem = system + MissedSystem</code>
<c><anno>Val</anno></c> must be strings. The one
exception is <c><anno>Val</anno></c> being the atom
<c>false</c> (in analogy with
- <seealso marker="kernel:os#getenv/1"><c>os:getenv/1</c></seealso>,
+ <seemfa marker="kernel:os#getenv/1"><c>os:getenv/1</c></seemfa>,
which removes the environment variable.
</p>
<p>
@@ -4009,8 +4089,8 @@ RealSystem = system + MissedSystem</code>
they are supplied to the executable. Most notably this
means that file wildcard expansion does not occur.
To expand wildcards for the arguments, use
- <seealso marker="stdlib:filelib#wildcard/1">
- <c>filelib:wildcard/1</c></seealso>.
+ <seemfa marker="stdlib:filelib#wildcard/1">
+ <c>filelib:wildcard/1</c></seemfa>.
Notice that even if
the program is a Unix shell script, meaning that the
shell ultimately is invoked, wildcard expansion
@@ -4118,7 +4198,7 @@ RealSystem = system + MissedSystem</code>
perform port tasks immediately, improving latency at the
expense of parallelism. The default can be set at system startup
by passing command-line argument
- <seealso marker="erl#+spp"><c>+spp</c></seealso> to
+ <seecom marker="erl#+spp"><c>+spp</c></seecom> to
<c>erl(1)</c>.</p>
</item>
<tag><c>{busy_limits_port, {Low, High} | disabled}</c></tag>
@@ -4183,7 +4263,7 @@ RealSystem = system + MissedSystem</code>
and the <c>fd</c> driver do not disable this feature
and do not adjust these limits by themselves.</p>
<p>For more information see the documentation
- <seealso marker="erl_driver#erl_drv_busy_msgq_limits"><c>erl_drv_busy_msgq_limits()</c></seealso>.
+ <seecref marker="erl_driver#erl_drv_busy_msgq_limits"><c>erl_drv_busy_msgq_limits()</c></seecref>.
</p>
</item>
</taglist>
@@ -4232,10 +4312,10 @@ RealSystem = system + MissedSystem</code>
the owning process using signals of the form
<c>{'EXIT', Port, PosixCode}</c>. For the possible values of
<c>PosixCode</c>, see
- <seealso marker="kernel:file"><c>file(3)</c></seealso>.</p>
+ <seeerl marker="kernel:file"><c>file(3)</c></seeerl>.</p>
<p>The maximum number of ports that can be open at the same
time can be configured by passing command-line flag
- <seealso marker="erl#max_ports"><c>+Q</c></seealso> to
+ <seecom marker="erl#max_ports"><c>+Q</c></seecom> to
<c>erl(1)</c>.</p>
</desc>
</func>
@@ -4451,8 +4531,8 @@ RealSystem = system + MissedSystem</code>
call fails with a <c>notsup</c> exception if the
driver of the port does not support this. For more
information, see driver flag
- <seealso marker="driver_entry#driver_flags">
- <c>![CDATA[ERL_DRV_FLAG_SOFT_BUSY]]</c></seealso>.
+ <seecref marker="driver_entry#driver_flags">
+ <c>ERL_DRV_FLAG_SOFT_BUSY</c></seecref>.
</item>
<tag><c>nosuspend</c></tag>
<item>The calling process is not suspended if the port is
@@ -4635,7 +4715,7 @@ RealSystem = system + MissedSystem</code>
<item><c>output</c></item>
</list>
<p>For more information about the different <c>Item</c>s, see
- <seealso marker="#port_info/2"><c>port_info/2</c></seealso>.</p>
+ <seemfa marker="#port_info/2"><c>port_info/2</c></seemfa>.</p>
<p>Failure: <c>badarg</c> if <c>Port</c> is not a local port
identifier, or an atom.</p>
</desc>
@@ -4782,7 +4862,7 @@ RealSystem = system + MissedSystem</code>
<fsummary>Information about the name of a port.</fsummary>
<desc>
<p><c><anno>Name</anno></c> is the command name set by
- <seealso marker="#open_port/2"><c>open_port/2</c></seealso>.</p>
+ <seemfa marker="#open_port/2"><c>open_port/2</c></seemfa>.</p>
<p>If the port identified by <c><anno>Port</anno></c> is not open,
<c>undefined</c> is returned. If the port is closed and the
calling process was previously linked to the port, the exit
@@ -4799,8 +4879,8 @@ RealSystem = system + MissedSystem</code>
<desc>
<p><c><anno>OsPid</anno></c> is the process identifier (or equivalent)
of an OS process created with
- <seealso marker="#open_port/2"><c>open_port({spawn | spawn_executable,
- Command}, Options)</c></seealso>. If the port is not the result of
+ <seemfa marker="#open_port/2"><c>open_port({spawn | spawn_executable,
+ Command}, Options)</c></seemfa>. If the port is not the result of
spawning an OS process, the value is <c>undefined</c>.</p>
<p>If the port identified by <c><anno>Port</anno></c> is not open,
<c>undefined</c> is returned. If the port is closed and the
@@ -4818,8 +4898,8 @@ RealSystem = system + MissedSystem</code>
<desc>
<p><c><anno>Bytes</anno></c> is the total number of bytes written
to the port from Erlang processes using
- <seealso marker="#port_command/2"><c>port_command/2</c></seealso>,
- <seealso marker="#port_command/3"><c>port_command/3</c></seealso>,
+ <seemfa marker="#port_command/2"><c>port_command/2</c></seemfa>,
+ <seemfa marker="#port_command/3"><c>port_command/3</c></seemfa>,
or <c><anno>Port</anno> ! {Owner, {command, Data}</c>.</p>
<p>If the port identified by <c><anno>Port</anno></c> is not open,
<c>undefined</c> is returned. If the port is closed and the
@@ -4837,8 +4917,8 @@ RealSystem = system + MissedSystem</code>
<desc>
<p><c><anno>Boolean</anno></c> corresponds to the port parallelism
hint used by this port. For more information, see option
- <seealso marker="#open_port_parallelism"><c>parallelism</c></seealso>
- of <seealso marker="#open_port/2"><c>open_port/2</c></seealso>.</p>
+ <seeerl marker="#open_port_parallelism"><c>parallelism</c></seeerl>
+ of <seemfa marker="#open_port/2"><c>open_port/2</c></seemfa>.</p>
</desc>
</func>
@@ -4921,7 +5001,8 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_flag" arity="2" clause_i="1" since=""/>
+ <name name="process_flag" arity="2" clause_i="1"
+ anchor="process_flag_trap_exit" since=""/>
<fsummary>Set process flag trap_exit for the calling process.</fsummary>
<desc>
<p>When <c>trap_exit</c> is set to <c>true</c>, exit signals
@@ -4933,7 +5014,7 @@ RealSystem = system + MissedSystem</code>
linked processes. Application processes are normally
not to trap exits.</p>
<p>Returns the old value of the flag.</p>
- <p>See also <seealso marker="#exit/2"><c>exit/2</c></seealso>.</p>
+ <p>See also <seemfa marker="#exit/2"><c>exit/2</c></seemfa>.</p>
</desc>
</func>
@@ -4989,20 +5070,20 @@ RealSystem = system + MissedSystem</code>
<item>
<p>The maximum size in words of the process. If set to zero, the
heap size limit is disabled. <c>badarg</c> is be thrown if the
- value is smaller than <seealso marker="#process_flag_min_heap_size">
- <c>min_heap_size</c></seealso>. The size check is only done when
+ value is smaller than <seeerl marker="#process_flag_min_heap_size">
+ <c>min_heap_size</c></seeerl>. The size check is only done when
a garbage collection is triggered.</p>
<p><c>size</c> is the entire heap of the process when garbage collection
is triggered. This includes all generational heaps, the process stack,
- any <seealso marker="#process_flag_message_queue_data">
- messages that are considered to be part of the heap</seealso>, and any
+ any <seeerl marker="#process_flag_message_queue_data">
+ messages that are considered to be part of the heap</seeerl>, and any
extra memory that the garbage collector needs during collection.</p>
<p><c>size</c> is the same as can be retrieved using
- <seealso marker="#process_info_total_heap_size">
- <c>erlang:process_info(Pid, total_heap_size)</c></seealso>,
+ <seeerl marker="#process_info_total_heap_size">
+ <c>erlang:process_info(Pid, total_heap_size)</c></seeerl>,
or by adding <c>heap_block_size</c>, <c>old_heap_block_size</c>
- and <c>mbuf_size</c> from <seealso marker="#process_info_garbage_collection_info">
- <c>erlang:process_info(Pid, garbage_collection_info)</c></seealso>.</p>
+ and <c>mbuf_size</c> from <seeerl marker="#process_info_garbage_collection_info">
+ <c>erlang:process_info(Pid, garbage_collection_info)</c></seeerl>.</p>
</item>
<tag><c>kill</c></tag>
<item>
@@ -5016,24 +5097,24 @@ RealSystem = system + MissedSystem</code>
<p>If <c>kill</c> is not defined in the map,
the system default will be used. The default system default
is <c>true</c>. It can be changed by either option
- <seealso marker="erl#+hmaxk">+hmaxk</seealso> in <c>erl(1)</c>,
- or <seealso marker="#system_flag_max_heap_size">
- <c>erlang:system_flag(max_heap_size, MaxHeapSize)</c></seealso>.</p>
+ <seecom marker="erl#+hmaxk">+hmaxk</seecom> in <c>erl(1)</c>,
+ or <seeerl marker="#system_flag_max_heap_size">
+ <c>erlang:system_flag(max_heap_size, MaxHeapSize)</c></seeerl>.</p>
</item>
<tag><c>error_logger</c></tag>
<item>
<p>When set to <c>true</c>, the runtime system logs an
- error event via <seealso marker="kernel:logger">
- <c>logger</c></seealso>,
+ error event via <seeerl marker="kernel:logger">
+ <c>logger</c></seeerl>,
containing details about the process when the maximum
heap size is reached. One log event is sent
each time the limit is reached.</p>
<p>If <c>error_logger</c> is not defined in the map, the system
default is used. The default system default is <c>true</c>.
It can be changed by either the option
- <seealso marker="erl#+hmaxel">+hmaxel</seealso> int <c>erl(1)</c>,
- or <seealso marker="#system_flag_max_heap_size">
- <c>erlang:system_flag(max_heap_size, MaxHeapSize)</c></seealso>.</p>
+ <seecom marker="erl#+hmaxel">+hmaxel</seecom> int <c>erl(1)</c>,
+ or <seeerl marker="#system_flag_max_heap_size">
+ <c>erlang:system_flag(max_heap_size, MaxHeapSize)</c></seeerl>.</p>
</item>
</taglist>
<p>The heap size of a process is quite hard to predict, especially the
@@ -5054,37 +5135,37 @@ RealSystem = system + MissedSystem</code>
</fsummary>
<type name="message_queue_data"/>
<desc>
- <p>This flag determines how messages in the message queue
+ <p>Determines how messages in the message queue
are stored, as follows:</p>
<taglist>
<tag><c>off_heap</c></tag>
<item>
<p><em>All</em> messages in the message queue will be stored
- outside of the process heap. This implies that <em>no</em>
+ outside the process heap. This implies that <em>no</em>
messages in the message queue will be part of a garbage
collection of the process.</p>
</item>
<tag><c>on_heap</c></tag>
<item>
<p>All messages in the message queue will eventually be
- placed on heap. They can however temporarily be stored
- off heap. This is how messages always have been stored
+ placed on the process heap. They can, however, be temporarily
+ stored off the heap. This is how messages have always been stored
up until ERTS 8.0.</p>
</item>
</taglist>
- <p>The default <c>message_queue_data</c> process flag is determined
- by command-line argument <seealso marker="erl#+hmqd">
- <c>+hmqd</c></seealso> in <c>erl(1)</c>.</p>
- <p>If the process potentially can get many messages in its queue,
- you are advised to set the flag to <c>off_heap</c>. This
- because a garbage collection with many messages placed on
- the heap can become extremely expensive and the process can
- consume large amounts of memory. Performance of the
- actual message passing is however generally better when not
- using flag <c>off_heap</c>.</p>
- <p>When changing this flag messages will be moved. This work
- has been initiated but not completed when this function
- call returns.</p>
+ <p>The default value of the <c>message_queue_data</c> process flag is
+ determined by the command-line argument <seecom marker="erl#+hmqd">
+ <c>+hmqd</c></seecom> in <c>erl(1)</c>.</p>
+ <p>If the process may potentially accumulate a large number of messages
+ in its queue it is recommended to set the flag value to <c>off_heap</c>.
+ This is due to the fact that the garbage collection of a process that
+ has a large number of messages stored on the heap can become extremely
+ expensive and the process can consume large amounts of memory.
+ The performance of the actual message passing is, however, generally
+ better when the flag value is <c>on_heap</c>.</p>
+ <p>Changing the flag value causes any existing messages to be moved.
+ The move operation is initiated, but not necessarily completed,
+ by the time the function returns.</p>
<p>Returns the old value of the flag.</p>
</desc>
</func>
@@ -5233,7 +5314,7 @@ RealSystem = system + MissedSystem</code>
<desc>
<p>Sets certain flags for the process <c><anno>Pid</anno></c>,
in the same manner as
- <seealso marker="#process_flag/2"><c>process_flag/2</c></seealso>.
+ <seemfa marker="#process_flag/2"><c>process_flag/2</c></seemfa>.
Returns the old value of the flag. The valid values for
<c><anno>Flag</anno></c> are only a subset of those allowed in
<c>process_flag/2</c>, namely <c>save_calls</c>.</p>
@@ -5282,11 +5363,11 @@ RealSystem = system + MissedSystem</code>
also an <c><anno>InfoTuple</anno></c> with item <c>registered_name</c>
is included.</p>
<p>For information about specific <c><anno>InfoTuple</anno></c>s, see
- <seealso marker="#process_info/2"><c>process_info/2</c></seealso>.</p>
+ <seemfa marker="#process_info/2"><c>process_info/2</c></seemfa>.</p>
<warning>
<p>This BIF is intended for <em>debugging only</em>. For
- all other purposes, use <seealso marker="#process_info/2">
- <c>process_info/2</c></seealso>.</p>
+ all other purposes, use <seemfa marker="#process_info/2">
+ <c>process_info/2</c></seemfa>.</p>
</warning>
<p>Failure: <c>badarg</c> if <c><anno>Pid</anno></c> is not a
local process.</p>
@@ -5352,9 +5433,10 @@ RealSystem = system + MissedSystem</code>
removed without prior notice. In the current implementation
<c><anno>BinInfo</anno></c> is a list of tuples. The tuples
contain; <c>BinaryId</c>, <c>BinarySize</c>, <c>BinaryRefcCount</c>.</p>
- <p>The message queue is on the heap depending on the
- process flag <seealso marker="#process_flag_message_queue_data">
- <c>message_queue_data</c></seealso>.</p>
+ <p>Depending on the value of the
+ <seeerl marker="#process_flag_message_queue_data">
+ <c>message_queue_data</c></seeerl> process flag the message queue
+ may be stored on the heap.</p>
</item>
<tag><c>{catchlevel, <anno>CatchLevel</anno>}</c></tag>
<item>
@@ -5383,12 +5465,13 @@ RealSystem = system + MissedSystem</code>
</item>
<tag><c>{current_stacktrace, <anno>Stack</anno>}</c></tag>
<item>
- <p>Returns the current call stack back-trace (<em>stacktrace</em>)
- of the process. The stack has the same format as returned by
- <seealso marker="#get_stacktrace/0">
- <c>erlang:get_stacktrace/0</c></seealso>. The depth of the
- stacktrace is truncated according to the <c>backtrace_depth</c>
- system flag setting.</p>
+ <p>Returns the current call stack back-trace
+ (<em>stacktrace</em>) of the process. The stack has the
+ same format as in the <c>catch</c> part of a
+ <c>try</c>. See <seeguide
+ marker="system/reference_manual:errors#stacktrace">The call-stack back trace (stacktrace)</seeguide>.
+ The depth of the stacktrace is truncated according to the
+ <c>backtrace_depth</c> system flag setting.</p>
</item>
<tag><c>{dictionary, <anno>Dictionary</anno>}</c></tag>
<item>
@@ -5415,8 +5498,8 @@ RealSystem = system + MissedSystem</code>
detailed information about garbage collection for this process.
The content of <c><anno>GCInfo</anno></c> can be changed without
prior notice. For details about the meaning of each item, see
- <seealso marker="#gc_minor_start"><c>gc_minor_start</c></seealso>
- in <seealso marker="#trace/3"><c>erlang:trace/3</c></seealso>.</p>
+ <seeerl marker="#gc_minor_start"><c>gc_minor_start</c></seeerl>
+ in <seemfa marker="#trace/3"><c>erlang:trace/3</c></seemfa>.</p>
</item>
<tag><c>{group_leader, <anno>GroupLeader</anno>}</c></tag>
<item>
@@ -5448,8 +5531,8 @@ RealSystem = system + MissedSystem</code>
<tag><c>{last_calls, false|Calls}</c></tag>
<item>
<p>The value is <c>false</c> if call saving is not active
- for the process (see <seealso marker="#process_flag/3">
- <c>process_flag/3</c></seealso>).
+ for the process (see <seemfa marker="#process_flag/3">
+ <c>process_flag/3</c></seemfa>).
If call saving is active, a list is returned, in which
the last element is the most recent called.</p>
</item>
@@ -5507,19 +5590,19 @@ RealSystem = system + MissedSystem</code>
</item>
<tag><c>{message_queue_data, <anno>MQD</anno>}</c></tag>
<item>
- <p>Returns the current state of process flag
- <c>message_queue_data</c>. <c><anno>MQD</anno></c> is either
+ <p><c><anno>MQD</anno></c> is the current value of the
+ <c>message_queue_data</c> process flag, which can be either
<c>off_heap</c> or <c>on_heap</c>. For more
information, see the documentation of
- <seealso marker="#process_flag_message_queue_data">
- <c>process_flag(message_queue_data, MQD)</c></seealso>.</p>
+ <seeerl marker="#process_flag_message_queue_data">
+ <c>process_flag(message_queue_data, MQD)</c></seeerl>.</p>
</item>
<tag><c>{priority, <anno>Level</anno>}</c></tag>
<item>
<p><c><anno>Level</anno></c> is the current priority level for
the process. For more information on priorities, see
- <seealso marker="#process_flag_priority">
- <c>process_flag(priority, Level)</c></seealso>.</p>
+ <seeerl marker="#process_flag_priority">
+ <c>process_flag(priority, Level)</c></seeerl>.</p>
</item>
<tag><c>{reductions, <anno>Number</anno>}</c></tag>
<item>
@@ -5567,10 +5650,10 @@ RealSystem = system + MissedSystem</code>
<c><anno>Suspendee</anno></c> is the process identifier of a
process that has been, or is to be,
suspended by the process identified by <c><anno>Pid</anno></c>
- through the BIF <seealso marker="#suspend_process/2">
- <c>erlang:suspend_process/2</c></seealso> or
- <seealso marker="#suspend_process/1">
- <c>erlang:suspend_process/1</c></seealso>.</p>
+ through the BIF <seemfa marker="#suspend_process/2">
+ <c>erlang:suspend_process/2</c></seemfa> or
+ <seemfa marker="#suspend_process/1">
+ <c>erlang:suspend_process/1</c></seemfa>.</p>
<p><c><anno>ActiveSuspendCount</anno></c> is the number of
times <c><anno>Suspendee</anno></c> has been suspended by
<c><anno>Pid</anno></c>.
@@ -5653,12 +5736,12 @@ RealSystem = system + MissedSystem</code>
<desc>
<p>Removes old code for <c><anno>Module</anno></c>.
Before this BIF is used,
- <seealso marker="#check_process_code/2">
- <c>check_process_code/2</c></seealso>is to be called to check
+ <seemfa marker="#check_process_code/2">
+ <c>check_process_code/2</c></seemfa> is to be called to check
that no processes execute old code in the module.</p>
<warning>
<p>This BIF is intended for the code server (see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>)
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>)
and is not to be used elsewhere.</p>
</warning>
<note>
@@ -5708,9 +5791,16 @@ RealSystem = system + MissedSystem</code>
<c>erlang:raise(<anno>Class</anno>, <anno>Reason</anno>,
<anno>Stacktrace</anno>)</c> is equivalent to
<c>erlang:<anno>Class</anno>(<anno>Reason</anno>)</c>.</p>
- <p><c><anno>Reason</anno></c> is any term.
- <c><anno>Stacktrace</anno></c> is a list as
- returned from <c>get_stacktrace()</c>, that is, a list of
+ <p><c><anno>Reason</anno></c> can be any term.</p>
+ <p><c><anno>Stacktrace</anno></c> is a list as provided in a try-catch
+ clause.</p>
+ <pre>
+try
+ ...
+catch Class:Reason:Stacktrace ->
+ ...
+end</pre>
+ <p>That is, a list of
four-tuples <c>{Module, Function, Arity | Args,
Location}</c>, where <c>Module</c> and <c>Function</c>
are atoms, and the third element is an integer arity or an
@@ -5738,8 +5828,8 @@ RealSystem = system + MissedSystem</code>
<fsummary>Read the state of a timer.</fsummary>
<desc>
<p>Reads the state of a timer. The same as calling
- <seealso marker="#read_timer/2"><c>erlang:read_timer(TimerRef,
- [])</c></seealso>.</p>
+ <seemfa marker="#read_timer/2"><c>erlang:read_timer(TimerRef,
+ [])</c></seemfa>.</p>
</desc>
</func>
@@ -5748,8 +5838,8 @@ RealSystem = system + MissedSystem</code>
<fsummary>Read the state of a timer.</fsummary>
<desc>
<p>Reads the state of a timer that has been created by either
- <seealso marker="#start_timer/4"><c>erlang:start_timer</c></seealso>
- or <seealso marker="#send_after/4"><c>erlang:send_after</c></seealso>.
+ <seemfa marker="#start_timer/4"><c>erlang:start_timer</c></seemfa>
+ or <seemfa marker="#send_after/4"><c>erlang:send_after</c></seemfa>.
<c><anno>TimerRef</anno></c> identifies the timer, and
was returned by the BIF that created the timer.</p>
<p><c><anno>Option</anno>s</c>:</p>
@@ -5791,11 +5881,11 @@ RealSystem = system + MissedSystem</code>
process is blocked until the operation has been performed.</p>
</note>
<p>See also
- <seealso marker="#send_after/4"><c>erlang:send_after/4</c></seealso>,
- <seealso marker="#start_timer/4">
- <c>erlang:start_timer/4</c></seealso>, and
- <seealso marker="#cancel_timer/2">
- <c>erlang:cancel_timer/2</c></seealso>.</p>
+ <seemfa marker="#send_after/4"><c>erlang:send_after/4</c></seemfa>,
+ <seemfa marker="#start_timer/4">
+ <c>erlang:start_timer/4</c></seemfa>, and
+ <seemfa marker="#cancel_timer/2">
+ <c>erlang:cancel_timer/2</c></seemfa>.</p>
</desc>
</func>
@@ -5846,7 +5936,7 @@ true</pre>
<fsummary>All registered names.</fsummary>
<desc>
<p>Returns a list of names that have been registered using
- <seealso marker="#register/2"><c>register/2</c></seealso>, for
+ <seemfa marker="#register/2"><c>register/2</c></seemfa>, for
example:</p>
<pre>
> <input>registered().</input>
@@ -5861,10 +5951,10 @@ true</pre>
<p>Decreases the suspend count on the process identified by
<c><anno>Suspendee</anno></c>. <c><anno>Suspendee</anno></c>
is previously to have been suspended through
- <seealso marker="#suspend_process/2">
- <c>erlang:suspend_process/2</c></seealso> or
- <seealso marker="#suspend_process/1">
- <c>erlang:suspend_process/1</c></seealso>
+ <seemfa marker="#suspend_process/2">
+ <c>erlang:suspend_process/2</c></seemfa> or
+ <seemfa marker="#suspend_process/1">
+ <c>erlang:suspend_process/1</c></seemfa>
by the process calling
<c>erlang:resume_process(<anno>Suspendee</anno>)</c>. When the
suspend count on <c><anno>Suspendee</anno></c> reaches zero,
@@ -5927,8 +6017,8 @@ true</pre>
<type name="dst"/>
<desc>
<p>Sends a message and returns <c><anno>Msg</anno></c>. This
- is the same as using the <seealso marker="doc/reference_manual:expressions#send">
- send operator</seealso>:
+ is the same as using the <seeguide marker="system/reference_manual:expressions#send">
+ send operator</seeguide>:
<c><anno>Dest</anno> ! <anno>Msg</anno></c>.</p>
<p><c><anno>Dest</anno></c> can be a remote or local process identifier,
a (local) port, a locally registered name, or a tuple
@@ -5949,10 +6039,10 @@ true</pre>
<p>Either sends a message and returns <c>ok</c>, or does not send
the message but returns something else (see below).
Otherwise the same as
- <seealso marker="#send/2"><c>erlang:send/2</c></seealso>.
+ <seemfa marker="#send/2"><c>erlang:send/2</c></seemfa>.
For more detailed explanation and warnings, see
- <seealso marker="#send_nosuspend/2">
- <c>erlang:send_nosuspend/2,3</c></seealso>.</p>
+ <seemfa marker="#send_nosuspend/2">
+ <c>erlang:send_nosuspend/2,3</c></seemfa>.</p>
<p>Options:</p>
<taglist>
<tag><c>nosuspend</c></tag>
@@ -5978,9 +6068,9 @@ true</pre>
<fsummary>Start a timer.</fsummary>
<desc>
<p>Starts a timer. The same as calling
- <seealso marker="#send_after/4">
+ <seemfa marker="#send_after/4">
<c>erlang:send_after(<anno>Time</anno>, <anno>Dest</anno>,
- <anno>Msg</anno>, [])</c></seealso>.</p>
+ <anno>Msg</anno>, [])</c></seemfa>.</p>
</desc>
</func>
@@ -5992,8 +6082,8 @@ true</pre>
<c><anno>Msg</anno></c> is sent to the process
identified by <c><anno>Dest</anno></c>. Apart from
the format of the time-out message, this function works exactly as
- <seealso marker="#start_timer/4">
- <c>erlang:start_timer/4</c></seealso>.</p>
+ <seemfa marker="#start_timer/4">
+ <c>erlang:start_timer/4</c></seemfa>.</p>
</desc>
</func>
@@ -6003,8 +6093,8 @@ true</pre>
<type name="dst"/>
<desc>
<p>The same as
- <seealso marker="#send/3"><c>erlang:send(<anno>Dest</anno>,
- <anno>Msg</anno>, [nosuspend])</c></seealso>,
+ <seemfa marker="#send/3"><c>erlang:send(<anno>Dest</anno>,
+ <anno>Msg</anno>, [nosuspend])</c></seemfa>,
but returns <c>true</c> if
the message was sent and <c>false</c> if the message was not
sent because the sender would have had to be suspended.</p>
@@ -6053,12 +6143,12 @@ true</pre>
<type name="dst"/>
<desc>
<p>The same as
- <seealso marker="#send/3"><c>erlang:send(<anno>Dest</anno>,
- <anno>Msg</anno>, [nosuspend | <anno>Options</anno>])</c></seealso>,
+ <seemfa marker="#send/3"><c>erlang:send(<anno>Dest</anno>,
+ <anno>Msg</anno>, [nosuspend | <anno>Options</anno>])</c></seemfa>,
but with a Boolean return value.</p>
<p>This function behaves like
- <seealso marker="#send_nosuspend/2">
- <c>erlang:send_nosuspend/2</c></seealso>,
+ <seemfa marker="#send_nosuspend/2">
+ <c>erlang:send_nosuspend/2</c></seemfa>,
but takes a third parameter, a list of options.
The only option is <c>noconnect</c>, which
makes the function return <c>false</c> if
@@ -6089,8 +6179,8 @@ true</pre>
local node, the function
also sets the cookie of all other unknown nodes to
<c><anno>Cookie</anno></c> (see section
- <seealso marker="doc/reference_manual:distributed">
- Distributed Erlang</seealso>
+ <seeguide marker="system/reference_manual:distributed">
+ Distributed Erlang</seeguide>
in the Erlang Reference Manual in System Documentation).</p>
<p>Failure: <c>function_clause</c> if the local node is not
alive.</p>
@@ -6131,9 +6221,9 @@ true</pre>
number of bytes is rounded <em>down</em>.</p>
<p>Allowed in guard tests.</p>
<p>See also
- <seealso marker="#tuple_size/1"><c>tuple_size/1</c></seealso>,
- <seealso marker="#byte_size/1"><c>byte_size/1</c></seealso>, and
- <seealso marker="#bit_size/1"><c>bit_size/1</c></seealso>.</p>
+ <seemfa marker="#tuple_size/1"><c>tuple_size/1</c></seemfa>,
+ <seemfa marker="#byte_size/1"><c>byte_size/1</c></seemfa>, and
+ <seemfa marker="#bit_size/1"><c>bit_size/1</c></seemfa>.</p>
</desc>
</func>
@@ -6144,7 +6234,7 @@ true</pre>
<p>Returns the process identifier of a new process started by the
application of <c><anno>Fun</anno></c> to the empty list
<c>[]</c>. Otherwise
- works like <seealso marker="#spawn/3"><c>spawn/3</c></seealso>.</p>
+ works like <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>.</p>
</desc>
</func>
@@ -6158,7 +6248,7 @@ true</pre>
empty list <c>[]</c> on <c><anno>Node</anno></c>. If
<c><anno>Node</anno></c> does not exist, a useless pid is
returned. Otherwise works like
- <seealso marker="#spawn/3"><c>spawn/3</c></seealso>.</p>
+ <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>.</p>
</desc>
</func>
@@ -6176,7 +6266,7 @@ true</pre>
does not exist (where <c>Arity</c> is the length of
<c><anno>Args</anno></c>). The error handler
can be redefined (see
- <seealso marker="#process_flag/2"><c>process_flag/2</c></seealso>).
+ <seemfa marker="#process_flag/2"><c>process_flag/2</c></seemfa>).
If <c>error_handler</c> is undefined, or the user has
redefined the default <c>error_handler</c> and its replacement is
undefined, a failure with reason <c>undef</c> occurs.</p>
@@ -6198,7 +6288,7 @@ true</pre>
to <c><anno>Args</anno></c> on <c><anno>Node</anno></c>. If
<c><anno>Node</anno></c> does not exist, a useless pid is returned.
Otherwise works like
- <seealso marker="#spawn/3"><c>spawn/3</c></seealso>.</p>
+ <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>.</p>
</desc>
</func>
@@ -6212,7 +6302,7 @@ true</pre>
<c>[]</c>. A link is created between
the calling process and the new process, atomically.
Otherwise works like
- <seealso marker="#spawn/3"><c>spawn/3</c></seealso>.</p>
+ <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>.</p>
</desc>
</func>
@@ -6228,8 +6318,8 @@ true</pre>
atomically. If <c><anno>Node</anno></c> does not exist,
a useless pid is returned and an exit signal with
reason <c>noconnection</c> is sent to the calling
- process. Otherwise works like <seealso marker="#spawn/3">
- <c>spawn/3</c></seealso>.</p>
+ process. Otherwise works like <seemfa marker="#spawn/3">
+ <c>spawn/3</c></seemfa>.</p>
</desc>
</func>
@@ -6243,7 +6333,7 @@ true</pre>
to <c><anno>Args</anno></c>. A link is created
between the calling process and the new process, atomically.
Otherwise works like
- <seealso marker="#spawn/3"><c>spawn/3</c></seealso>.</p>
+ <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>.</p>
</desc>
</func>
@@ -6260,8 +6350,8 @@ true</pre>
process, atomically. If <c><anno>Node</anno></c> does
not exist, a useless pid is returned and an exit signal with
reason <c>noconnection</c> is sent to the calling
- process. Otherwise works like <seealso marker="#spawn/3">
- <c>spawn/3</c></seealso>.</p>
+ process. Otherwise works like <seemfa marker="#spawn/3">
+ <c>spawn/3</c></seemfa>.</p>
</desc>
</func>
@@ -6275,7 +6365,24 @@ true</pre>
<c>[]</c>,
and a reference for a monitor created to the new process.
Otherwise works like
- <seealso marker="#spawn/3"><c>spawn/3</c></seealso>.</p>
+ <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_monitor" arity="2" since="OTP 23.0"/>
+ <fsummary>Create and monitor a new process with a fun as entry point.
+ </fsummary>
+ <desc>
+ <p>Returns the process identifier of a new process, started by
+ the application of <c><anno>Fun</anno></c> to the empty list
+ <c>[]</c> on the node <c><anno>Node</anno></c>,
+ and a reference for a monitor created to the new process.
+ Otherwise works like
+ <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>.</p>
+ <p>If the node identified by <c><anno>Node</anno></c> does not
+ support distributed <c>spawn_monitor()</c>, the call will fail
+ with a <c>notsup</c> exception.</p>
</desc>
</func>
@@ -6289,7 +6396,24 @@ true</pre>
to <c><anno>Args</anno></c>. The process is
monitored at the same time. Returns the process identifier
and a reference for the monitor. Otherwise works like
- <seealso marker="#spawn/3"><c>spawn/3</c></seealso>.</p>
+ <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_monitor" arity="4" since="OTP 23.0"/>
+ <fsummary>Create and monitor a new process with a function as entry point.
+ </fsummary>
+ <desc>
+ <p>A new process is started by the application
+ of <c><anno>Module</anno>:<anno>Function</anno></c>
+ to <c><anno>Args</anno></c> on the node <c><anno>Node</anno></c>.
+ The process is monitored at the same time. Returns the process
+ identifier and a reference for the monitor. Otherwise works like
+ <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>.</p>
+ <p>If the node identified by <c><anno>Node</anno></c> does not
+ support distributed <c>spawn_monitor()</c>, the call will fail
+ with a <c>notsup</c> exception.</p>
</desc>
</func>
@@ -6304,7 +6428,7 @@ true</pre>
<p>Returns the process identifier (pid) of a new process
started by the application of <c><anno>Fun</anno></c>
to the empty list <c>[]</c>. Otherwise works like
- <seealso marker="#spawn_opt/4"><c>spawn_opt/4</c></seealso>.</p>
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/4</c></seemfa>.</p>
<p>If option <c>monitor</c> is specified, the newly created
process is monitored, and both the pid and reference for
the monitor are returned.</p>
@@ -6315,17 +6439,22 @@ true</pre>
<name name="spawn_opt" arity="3" since=""/>
<fsummary>Create a new process with a fun as entry point on a specified
node.</fsummary>
- <type name="priority_level"/>
- <type name="max_heap_size"/>
- <type name="message_queue_data"/>
- <type name="spawn_opt_option"/>
<desc>
<p>Returns the process identifier (pid) of a new process started
by the application of <c><anno>Fun</anno></c> to the
empty list <c>[]</c> on <c><anno>Node</anno></c>. If
<c><anno>Node</anno></c> does not exist, a useless pid is
returned. Otherwise works like
- <seealso marker="#spawn_opt/4"><c>spawn_opt/4</c></seealso>.</p>
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/4</c></seemfa>.</p>
+ <p>
+ Valid options depends on what options are
+ supported by the node identified by
+ <c><anno>Node</anno></c>. A description of
+ valid <c>Option</c>s for the local node
+ of current OTP version can be found in the
+ documentation of
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/4</c></seemfa>.
+ </p>
</desc>
</func>
@@ -6338,7 +6467,7 @@ true</pre>
<type name="spawn_opt_option"/>
<desc>
<p>Works as
- <seealso marker="#spawn/3"><c>spawn/3</c></seealso>, except that an
+ <seemfa marker="#spawn/3"><c>spawn/3</c></seemfa>, except that an
extra option list is specified when creating the process.</p>
<p>If option <c>monitor</c> is specified, the newly created
process is monitored, and both the pid and reference for
@@ -6348,25 +6477,25 @@ true</pre>
<tag><c>link</c></tag>
<item>
<p>Sets a link to the parent process (like
- <seealso marker="#spawn_link/3"><c>spawn_link/3</c></seealso>
+ <seemfa marker="#spawn_link/3"><c>spawn_link/3</c></seemfa>
does).</p>
</item>
<tag><c>monitor</c></tag>
<item>
<p>Monitors the new process (like
- <seealso marker="#monitor/2"><c>monitor/2</c></seealso> does).</p>
+ <seemfa marker="#monitor/2"><c>monitor/2</c></seemfa> does).</p>
</item>
<tag><c>{priority, <anno>Level</anno>}</c></tag>
<item>
<p>Sets the priority of the new process. Equivalent to
- executing <seealso marker="#process_flag_priority">
- <c>process_flag(priority, <anno>Level</anno>)</c></seealso>
+ executing <seeerl marker="#process_flag_priority">
+ <c>process_flag(priority, <anno>Level</anno>)</c></seeerl>
in the start function of the new process,
except that the priority is set before the process is
selected for execution for the first time. For more
information on priorities, see
- <seealso marker="#process_flag_priority">
- <c>process_flag(priority, <anno>Level</anno>)</c></seealso>.</p>
+ <seeerl marker="#process_flag_priority">
+ <c>process_flag(priority, <anno>Level</anno>)</c></seeerl>.</p>
</item>
<tag><c>{fullsweep_after, <anno>Number</anno>}</c></tag>
<item>
@@ -6403,8 +6532,8 @@ true</pre>
and no virtual memory, you might want to preserve memory
by setting <c><anno>Number</anno></c> to zero.
(The value can be set globally, see
- <seealso marker="#system_flag/2">
- <c>erlang:system_flag/2</c></seealso>.)
+ <seemfa marker="#system_flag/2">
+ <c>erlang:system_flag/2</c></seemfa>.)
</item>
</list>
</item>
@@ -6442,24 +6571,24 @@ true</pre>
<item>
<p>Sets the <c>max_heap_size</c> process flag. The default
<c>max_heap_size</c> is determined by command-line argument
- <seealso marker="erl#+hmax"><c>+hmax</c></seealso>
+ <seecom marker="erl#+hmax"><c>+hmax</c></seecom>
in <c>erl(1)</c>. For more information, see the
- documentation of <seealso marker="#process_flag_max_heap_size">
- <c>process_flag(max_heap_size, <anno>Size</anno>)</c></seealso>.
+ documentation of <seeerl marker="#process_flag_max_heap_size">
+ <c>process_flag(max_heap_size, <anno>Size</anno>)</c></seeerl>.
</p>
</item>
<tag><c>{message_queue_data, <anno>MQD</anno>}</c></tag>
<item>
- <p>Sets the state of the <c>message_queue_data</c> process
- flag. <c><anno>MQD</anno></c> is to be either <c>off_heap</c>
- or <c>on_heap</c>. The default
+ <p>Sets the value of the <c>message_queue_data</c> process
+ flag. <c><anno>MQD</anno></c> can be either <c>off_heap</c>
+ or <c>on_heap</c>. The default value of the
<c>message_queue_data</c> process flag is determined by
- command-line argument <seealso marker="erl#+hmqd">
- <c>+hmqd</c></seealso> in <c>erl(1)</c>.
+ the command-line argument <seecom marker="erl#+hmqd">
+ <c>+hmqd</c></seecom> in <c>erl(1)</c>.
For more information, see the documentation of
- <seealso marker="#process_flag_message_queue_data">
+ <seeerl marker="#process_flag_message_queue_data">
<c>process_flag(message_queue_data,
- <anno>MQD</anno>)</c></seealso>.</p>
+ <anno>MQD</anno>)</c></seeerl>.</p>
</item>
</taglist>
</desc>
@@ -6469,10 +6598,6 @@ true</pre>
<name name="spawn_opt" arity="5" since=""/>
<fsummary>Create a new process with a function as entry point on a
specified node.</fsummary>
- <type name="priority_level"/>
- <type name="max_heap_size"/>
- <type name="message_queue_data"/>
- <type name="spawn_opt_option"/>
<desc>
<p>Returns the process identifier (pid) of a new process started
by the application
@@ -6480,15 +6605,415 @@ true</pre>
<c><anno>Args</anno></c> on <c><anno>Node</anno></c>. If
<c><anno>Node</anno></c> does not exist, a useless pid is returned.
Otherwise works like
- <seealso marker="#spawn_opt/4"><c>spawn_opt/4</c></seealso>.</p>
- <note>
- <p>Option <c>monitor</c> is not supported by
- <c>spawn_opt/5</c>.</p>
- </note>
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/4</c></seemfa>.</p>
+ <p>
+ Valid options depends on what options are
+ supported by the node identified by
+ <c><anno>Node</anno></c>. A description of
+ valid <c>Option</c>s for the local node
+ of current OTP version can be found in the
+ documentation of
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/4</c></seemfa>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_request" arity="1" since="OTP 23.0"/>
+ <fsummary>Asynchronously send a request to spawn a new process.</fsummary>
+ <desc>
+ <p>
+ The same as the call
+ <seemfa marker="#spawn_request/3"><c>spawn_request(node(),<anno>Fun</anno>,[])</c></seemfa>.
+ That is, a spawn request on the local node with no options.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_request" arity="2" clause_i="1" since="OTP 23.0"/>
+ <fsummary>Asynchronously send a request to spawn a new process.</fsummary>
+ <desc>
+ <p>
+ The same as the call
+ <seemfa marker="#spawn_request/3"><c>spawn_request(node(),<anno>Fun</anno>,<anno>Options</anno>)</c></seemfa>.
+ That is, a spawn request on the local node.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_request" arity="2" clause_i="2" since="OTP 23.0"/>
+ <fsummary>Asynchronously send a request to spawn a new process.</fsummary>
+ <desc>
+ <p>
+ The same as the call
+ <seemfa marker="#spawn_request/3"><c>spawn_request(<anno>Node</anno>,<anno>Fun</anno>,[])</c></seemfa>.
+ That is, a spawn request with no options.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_request" arity="3" clause_i="1" since="OTP 23.0"/>
+ <fsummary>Asynchronously send a request to spawn a new process.</fsummary>
+ <desc>
+ <p>
+ The same as
+ <seemfa marker="#spawn_request/5"><c>spawn_request(<anno>Node</anno>,erlang,apply,[<anno>Fun</anno>,[]],<anno>Options</anno>)</c></seemfa>.
+ That is, a spawn request using the fun <c><anno>Fun</anno></c> of
+ arity zero as entry point.
+ </p>
+ <p>This function will fail with a <c>badarg</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Fun</anno></c> is not a fun of arity zero.</p></item>
+ <item><p><c><anno>Options</anno></c> is not a proper list
+ of terms.</p></item>
+ </list>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_request" arity="3" clause_i="2" since="OTP 23.0"/>
+ <fsummary>Asynchronously send a request to spawn a new process.</fsummary>
+ <desc>
+ <p>
+ The same as the call
+ <seemfa marker="#spawn_request/5"><c>spawn_request(node(),<anno>Module</anno>,<anno>Function</anno>,<anno>Args</anno>,[])</c></seemfa>.
+ That is, a spawn request on the local node with no options.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_request" arity="4" clause_i="1" since="OTP 23.0"/>
+ <fsummary>Asynchronously send a request to spawn a new process.</fsummary>
+ <desc>
+ <p>
+ The same as the call
+ <seemfa marker="#spawn_request/5"><c>spawn_request(<anno>Node</anno>,<anno>Module</anno>,<anno>Function</anno>,<anno>Args</anno>,[])</c></seemfa>.
+ That is, a spawn request with no options.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_request" arity="4" clause_i="2" since="OTP 23.0"/>
+ <fsummary>Asynchronously send a request to spawn a new process.</fsummary>
+ <desc>
+ <p>
+ The same as the call
+ <seemfa marker="#spawn_request/5"><c>spawn_request(node(),<anno>Module</anno>,<anno>Function</anno>,<anno>Args</anno>,<anno>Options</anno>)</c></seemfa>.
+ That is, a spawn request on the local node.
+ </p>
</desc>
</func>
<func>
+ <name name="spawn_request" arity="5" since="OTP 23.0"/>
+ <fsummary>Asynchronously send a request to spawn a new process.</fsummary>
+ <desc>
+ <p>
+ Asynchronously send a spawn request. Returns a request
+ identifier <c><anno>ReqId</anno></c>.
+ </p>
+
+ <marker id="spawn_request_success_message"/>
+ <p>
+ If the spawn operation succeeds, a new process is
+ created on the node identified by <c><anno>Node</anno></c>.
+ When a spawn operation succeeds, the caller will by
+ default be sent a message on the form
+ <c>{<anno>ReplyTag</anno>, <anno>ReqId</anno>, ok, Pid}</c>
+ where <c>Pid</c> is the process identifier of the
+ newly created process. Such a message is referred to as a
+ <i>success message</i> below in the text.
+ <c><anno>ReplyTag</anno></c> is by default the atom
+ <c>spawn_reply</c> unless modified by
+ the <c>{reply_tag, <anno>ReplyTag</anno>}</c> option. The
+ new process is started by the application of
+ <c><anno>Module</anno>:<anno>Function</anno></c>
+ to <c><anno>Args</anno></c>.
+ </p>
+
+ <marker id="spawn_request_error_message"/>
+ <p>The spawn operation fails either if creation of a new process
+ failed or if the spawn operation was interrupted by a connection
+ failure. When a spawn operation fails, the caller will by default
+ be sent a message on the form
+ <c>{<anno>ReplyTag</anno>, <anno>ReqId</anno>, error, Reason}</c>
+ where <c>Reason</c> is the error reason. Such a message is
+ referred to as an <i>error message</i> below in the text.
+ Currently the following spawn error <c>Reason</c>s are defined,
+ but other reasons can appear at any time without prior notice:</p>
+ <taglist>
+ <tag><c>badopt</c></tag>
+ <item>
+ <p>
+ An invalid <c><anno>Option</anno></c> was passed as
+ argument. Note that different runtime systems may
+ support different options.
+ </p>
+ </item>
+ <tag><c>notsup</c></tag>
+ <item>
+ <p>
+ The node identified by <c><anno>Node</anno></c> does
+ not support spawn operations issued by
+ <c>spawn_request()</c>.
+ </p>
+ </item>
+ <tag><c>noconnection</c></tag>
+ <item>
+ <p>
+ Failure to set up a connection to the node
+ identified by <c><anno>Node</anno></c> or
+ the connection to that node was lost during
+ the spawn operation. In the case the
+ connection was lost, a process may or may
+ not have been created.
+ </p>
+ </item>
+ <tag><c>system_limit</c></tag>
+ <item>
+ <p>
+ Could not create a new process due to that some
+ system limit was reached. Typically the process table
+ was full.
+ </p>
+ </item>
+ </taglist>
+ <p>Valid <c><anno>Option</anno></c>s:</p>
+ <taglist>
+ <tag><c>monitor</c></tag>
+ <item>
+ <p>
+ In the absence of spawn operation failures, atomically
+ sets up a monitor to the newly created process. That
+ is, as if the calling process had called
+ <seemfa marker="#monitor/2"><c>monitor(process, Pid)</c></seemfa>
+ where <c>Pid</c> is the process identifier of the
+ newly created process. The <c><anno>ReqId</anno></c>
+ returned by <c>spawn_request()</c> is also used as
+ monitor reference as if it was returned from
+ <c>monitor(process, Pid)</c>.
+ </p>
+ <p>
+ The monitor will not be activated for the calling
+ process until the spawn operation has succeeded.
+ The monitor can not be
+ <seemfa marker="#demonitor/1">demonitored</seemfa>
+ before the operation has succeeded. A <c>'DOWN'</c>
+ message for the corresponding monitor is guaranteed
+ not to be delivered before a
+ <seeerl marker="#spawn_request_success_message"><i>success
+ message</i></seeerl> that corresponds to the spawn
+ operation. If the spawn operation fails, no
+ <c>'DOWN'</c> message will be delivered.
+ </p>
+ <p>
+ If the connection between the nodes involved in
+ the spawn operation is lost during the spawn
+ operation, the spawn operation will fail with an
+ error reason of <c>noconnection</c>. A new process
+ may or may not have been created.
+ </p>
+ </item>
+ <tag><c>link</c></tag>
+ <item>
+ <p>
+ In absence of spawn operation failures, atomically
+ sets up a link between the calling process and the
+ newly created process. That is, as if the calling
+ process had called
+ <seemfa marker="#link/1"><c>link(Pid)</c></seemfa>
+ where <c>Pid</c> is the process identifier of the
+ newly created process.
+ </p>
+ <p>
+ The link will not be activated for the calling
+ process until the spawn operation has succeeded.
+ The link can not be removed before the operation
+ has succeeded. An exit signal due to the link is
+ guaranteed not to be delivered before a
+ <seeerl marker="#spawn_request_success_message"><i>success
+ message</i></seeerl> that corresponds to the spawn
+ operation. If the spawn operation fails, no
+ exit signal due to the link will be delivered to
+ the caller of <c>spawn_request()</c>.
+ </p>
+ <p>
+ If the connection between the nodes involved in
+ the spawn operation is lost during the spawn
+ operation, the spawn operation will fail with
+ an error reason of <c>noconnection</c>. A new
+ process may or may not have been created. If it
+ has been created, it will be delivered an exit
+ signal with an exit reason of <c>noconnection</c>.
+ </p>
+ </item>
+ <tag><c>{reply, <anno>Reply</anno>}</c></tag>
+ <item>
+ <p>Valid <c>Reply</c> values:</p>
+ <taglist>
+ <tag><c>yes</c></tag>
+ <item><p>
+ A spawn reply message will be sent to the caller
+ regardless of whether the operation succeeds or
+ not. If the call to <c>spawn_request()</c> returns
+ without raising an exception and the <c>reply</c>
+ option is set to <c>yes</c>, the caller is
+ guaranteed to be delivered either a
+ <seeerl marker="#spawn_request_success_message"><i>success
+ message</i></seeerl> or an
+ <seeerl marker="#spawn_request_error_message"><i>error
+ message</i></seeerl>. The <c>reply</c> option is by
+ default set to <c>yes</c>.
+ </p></item>
+ <tag><c>no</c></tag>
+ <item><p>
+ No spawn reply message will be sent to the caller
+ when the spawn operation completes. This regardless of
+ whether the operation succeeds or not.
+ </p></item>
+ <tag><c>error_only</c></tag>
+ <item><p>
+ No spawn reply message will be sent to the caller
+ if the spawn operation succeeds, but an
+ <seeerl marker="#spawn_request_error_message"><i>error
+ message</i></seeerl> will be sent to the caller
+ if the operation fails.
+ </p></item>
+ <tag><c>success_only</c></tag>
+ <item><p>
+ No spawn reply message will be sent to the caller
+ if the spawn operation fails, but a
+ <seeerl marker="#spawn_request_success_message"><i>success
+ message</i></seeerl> will be sent to the caller
+ if the operation succeeds.
+ </p></item>
+ </taglist>
+ </item>
+ <tag><c>{reply_tag, <anno>ReplyTag</anno>}</c></tag>
+ <item>
+ <p>
+ Sets the reply tag to <c><anno>ReplyTag</anno></c> in
+ the reply message. That is, in the
+ <seeerl marker="#spawn_request_success_message"><i>success</i></seeerl>
+ or <seeerl marker="#spawn_request_error_message"><i>error</i></seeerl>
+ message that is sent to the caller due to the
+ spawn operation. The default reply tag is the atom
+ <c>spawn_reply</c>.
+ </p>
+ </item>
+ <tag><c><anno>OtherOption</anno></c></tag>
+ <item>
+ <p>
+ Other valid options depends on what options are
+ supported by the node identified by
+ <c><anno>Node</anno></c>. A description of other
+ valid <c><anno>Option</anno></c>s for the local node
+ of current OTP version can be found in the
+ documentation of
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/4</c></seemfa>.
+ </p>
+ </item>
+ </taglist>
+ <p>This function will fail with a <c>badarg</c> exception if:
+ </p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Args</anno></c> is not a proper list
+ of terms.</p></item>
+ <item><p><c><anno>Options</anno></c> is not a proper list
+ of terms.</p></item>
+ </list>
+ <p>
+ Note that not all individual <c><anno>Option</anno></c>s
+ are checked when the spawn request is sent. Some
+ <c><anno>Option</anno></c>s can only be checked on
+ reception of the request. Therefore an invalid option
+ does <em>not</em> cause a <c>badarg</c> exception, but
+ will cause the spawn operation to fail with an error
+ reason of <c>badopt</c>.
+ </p>
+ <p>
+ A spawn request can be abandoned by calling
+ <seemfa marker="#spawn_request_abandon/1"><c>spawn_request_abandon/1</c></seemfa>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="spawn_request_abandon" arity="1" since="OTP 23.0"/>
+ <fsummary>Abandon a previously issued spawn request.</fsummary>
+ <desc>
+ <p>
+ Abandon a previously issued spawn request. <c><anno>ReqId</anno></c>
+ corresponds to a request identifier previously returned by
+ <seemfa marker="#spawn_request/5"><c>spawn_request()</c></seemfa>
+ in a call from current process. That is, only the process that
+ has made the request can abandon the request.
+ </p>
+ <p>
+ A spawn request can only be successfully abandoned until the
+ spawn request has completed. When a spawn request has been
+ successfully abandoned, the caller will not be effected by
+ future direct effects of the spawn request itself.
+ For example, it will not receive a spawn reply message. The
+ request is however not withdrawn, so a new process may or may
+ not be created due to the request. If a new process is created
+ after the spawn request was abandoned, no monitors nor links
+ will be set up to the caller of <c>spawn_request_abandon/1</c>
+ due to the spawn request. If the spawn request included the
+ <c>link</c> option, the process created due to this request
+ will be sent an exit signal from its parent with the exit
+ reason <c>abandoned</c> when it is detected that the
+ spawn operation has succeeded.
+ </p>
+ <note><p>
+ A process created due to a spawn request that has been
+ abandoned may communicate with its parent as any other
+ process. It is <em>only</em> the direct effects on the
+ parent of the actual spawn request, that will be canceled
+ by abandoning a spawn request.
+ </p></note>
+ <p>Return values:</p>
+ <taglist>
+ <tag><c>true</c></tag>
+ <item><p>
+ The spawn request was successfully abandoned.
+ </p></item>
+ <tag><c>false</c></tag>
+ <item><p>
+ No spawn request was abandoned. The <c><anno>ReqId</anno></c>
+ request identifier did not correspond to an outstanding
+ spawn request issued by the calling process. The reason for
+ this is either:</p>
+ <list>
+ <item><p>
+ <c><anno>ReqId</anno></c> corresponds to a spawn request
+ previoulsy made by the calling process. The spawn operation
+ has completed and a spawn reply has already been delivered
+ to the calling process unless the spawn reply was disabled
+ in the request.
+ </p></item>
+ <item><p>
+ <c><anno>ReqId</anno></c> does not correspond to a spawn
+ request that has been made by the calling process.
+ </p></item>
+ </list>
+ </item>
+ </taglist>
+ <p>This function fail with a <c>badarg</c> exception if
+ <c><anno>ReqId</anno></c> is not a reference.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="split_binary" arity="2" since=""/>
<fsummary>Split a binary into two.</fsummary>
<type_desc variable="Pos">0..byte_size(Bin)</type_desc>
@@ -6517,9 +7042,9 @@ true</pre>
<fsummary>Start a timer.</fsummary>
<desc>
<p>Starts a timer. The same as calling
- <seealso marker="#start_timer/4">
+ <seemfa marker="#start_timer/4">
<c>erlang:start_timer(<anno>Time</anno>,
- <anno>Dest</anno>, <anno>Msg</anno>, [])</c></seealso>.</p>
+ <anno>Dest</anno>, <anno>Msg</anno>, [])</c></seemfa>.</p>
</desc>
</func>
@@ -6537,8 +7062,8 @@ true</pre>
<p>This is the default. It means the
<c><anno>Time</anno></c> value is interpreted
as a time in milliseconds <em>relative</em> current
- <seealso marker="time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso>.</p>
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide>.</p>
</item>
<tag><c>{abs, true}</c></tag>
<item>
@@ -6550,10 +7075,10 @@ true</pre>
<p>More <c><anno>Option</anno></c>s can be added in the future.</p>
<p>The absolute point in time, the timer is set to expire on,
must be in the interval
- <c>[</c><seealso marker="#system_info_start_time">
- <c>erlang:system_info(start_time)</c></seealso><c>,
- </c><seealso marker="#system_info_end_time">
- <c>erlang:system_info(end_time)</c></seealso><c>]</c>.
+ <c>[</c><seeerl marker="#system_info_start_time">
+ <c>erlang:system_info(start_time)</c></seeerl><c>,
+ </c><seeerl marker="#system_info_end_time">
+ <c>erlang:system_info(end_time)</c></seeerl><c>]</c>.
If a relative time is specified, the <c><anno>Time</anno></c>
value is not allowed to be negative.</p>
<p>If <c><anno>Dest</anno></c> is a <c>pid()</c>, it must
@@ -6571,11 +7096,11 @@ true</pre>
timers are not automatically canceled when
<c><anno>Dest</anno></c> is an <c>atom()</c>.</p>
<p>See also
- <seealso marker="#send_after/4"><c>erlang:send_after/4</c></seealso>,
- <seealso marker="#cancel_timer/2">
- <c>erlang:cancel_timer/2</c></seealso>, and
- <seealso marker="#read_timer/2">
- <c>erlang:read_timer/2</c></seealso>.</p>
+ <seemfa marker="#send_after/4"><c>erlang:send_after/4</c></seemfa>,
+ <seemfa marker="#cancel_timer/2">
+ <c>erlang:cancel_timer/2</c></seemfa>, and
+ <seemfa marker="#read_timer/2">
+ <c>erlang:read_timer/2</c></seemfa>.</p>
<p>Failure: <c>badarg</c> if the arguments do not satisfy
the requirements specified here.</p>
</desc>
@@ -6587,8 +7112,8 @@ true</pre>
<fsummary>Information about active processes and ports.</fsummary>
<desc>
<p>Returns the same as
- <seealso marker="#statistics_active_tasks_all">
- <c>statistics(active_tasks_all)</c></seealso>
+ <seeerl marker="#statistics_active_tasks_all">
+ <c>statistics(active_tasks_all)</c></seeerl>
with the exception that no information about the dirty
IO run queue and its associated schedulers is part of
the result. That is, only tasks that are expected to be
@@ -6625,16 +7150,16 @@ true</pre>
queues can migrate to other normal run queues. This has
to be taken into account when evaluating the result.</p></note>
<p>See also
- <seealso marker="#statistics_total_active_tasks">
- <c>statistics(total_active_tasks)</c></seealso>,
- <seealso marker="#statistics_run_queue_lengths">
- <c>statistics(run_queue_lengths)</c></seealso>,
- <seealso marker="#statistics_run_queue_lengths_all">
- <c>statistics(run_queue_lengths_all)</c></seealso>,
- <seealso marker="#statistics_total_run_queue_lengths">
- <c>statistics(total_run_queue_lengths)</c></seealso>, and
- <seealso marker="#statistics_total_run_queue_lengths_all">
- <c>statistics(total_run_queue_lengths_all)</c></seealso>.</p>
+ <seeerl marker="#statistics_total_active_tasks">
+ <c>statistics(total_active_tasks)</c></seeerl>,
+ <seeerl marker="#statistics_run_queue_lengths">
+ <c>statistics(run_queue_lengths)</c></seeerl>,
+ <seeerl marker="#statistics_run_queue_lengths_all">
+ <c>statistics(run_queue_lengths_all)</c></seeerl>,
+ <seeerl marker="#statistics_total_run_queue_lengths">
+ <c>statistics(total_run_queue_lengths)</c></seeerl>, and
+ <seeerl marker="#statistics_total_run_queue_lengths_all">
+ <c>statistics(total_run_queue_lengths_all)</c></seeerl>.</p>
</desc>
</func>
@@ -6656,8 +7181,8 @@ true</pre>
<note>
<p><c>statistics(exact_reductions)</c> is
a more expensive operation than
- <seealso marker="#statistics_reductions">
- statistics(reductions)</seealso>.</p>
+ <seeerl marker="#statistics_reductions">
+ statistics(reductions)</seeerl>.</p>
</note>
</desc>
</func>
@@ -6696,8 +7221,8 @@ true</pre>
is enabled. Microstate accounting is meant to be a profiling tool
to help finding performance bottlenecks.
To <c>start</c>/<c>stop</c>/<c>reset</c> microstate accounting, use
- system flag <seealso marker="#system_flag_microstate_accounting">
- <c>microstate_accounting</c></seealso>.</p>
+ system flag <seeerl marker="#system_flag_microstate_accounting">
+ <c>microstate_accounting</c></seeerl>.</p>
<p><c>statistics(microstate_accounting)</c> returns a list of maps
representing some of the OS threads within ERTS. Each map
contains <c>type</c> and <c>id</c> fields that can be used to
@@ -6717,8 +7242,8 @@ true</pre>
id => 1,
type => scheduler}|...]</pre>
<p>The time unit is the same as returned by
- <seealso marker="kernel:os#perf_counter/0">
- <c>os:perf_counter/0</c></seealso>.
+ <seemfa marker="kernel:os#perf_counter/0">
+ <c>os:perf_counter/0</c></seemfa>.
So, to convert it to milliseconds, you can do something like this:</p>
<pre>
lists:map(
@@ -6735,23 +7260,23 @@ lists:map(
<taglist>
<tag><c>scheduler</c></tag>
<item>The main execution threads that do most of the work. See
- <seealso marker="erts:erl#+S">erl +S</seealso> for more details.</item>
+ <seecom marker="erts:erl#+S">erl +S</seecom> for more details.</item>
<tag><c>dirty_cpu_scheduler</c></tag>
<item>The threads for long running cpu intensive work. See
- <seealso marker="erts:erl#+SDcpu">erl +SDcpu</seealso> for more details.</item>
+ <seecom marker="erts:erl#+SDcpu">erl +SDcpu</seecom> for more details.</item>
<tag><c>dirty_io_scheduler</c></tag>
<item>The threads for long running I/O work. See
- <seealso marker="erts:erl#+SDio">erl +SDio</seealso> for more details.</item>
+ <seecom marker="erts:erl#+SDio">erl +SDio</seecom> for more details.</item>
<tag><c>async</c></tag>
<item>Async threads are used by various linked-in drivers (mainly the
file drivers) do offload non-CPU intensive work. See
- <seealso marker="erts:erl#async_thread_pool_size">erl +A</seealso> for more details.</item>
+ <seecom marker="erts:erl#async_thread_pool_size">erl +A</seecom> for more details.</item>
<tag><c>aux</c></tag>
<item>Takes care of any work that is not
specifically assigned to a scheduler.</item>
<tag><c>poll</c></tag>
<item>Does the IO polling for the emulator. See
- <seealso marker="erts:erl#+IOt">erl +IOt</seealso> for more details.</item>
+ <seecom marker="erts:erl#+IOt">erl +IOt</seecom> for more details.</item>
</taglist>
<p>The following <c><anno>MSAcc_Thread_State</anno></c>s are available.
All states are exclusive, meaning that a thread cannot be in two
@@ -6791,8 +7316,8 @@ lists:map(
<tag><c>busy_wait</c></tag>
<item>Time spent busy waiting. This is also the state where a
scheduler no longer reports that it is active when using
- <seealso marker="#statistics_scheduler_wall_time">
- <c>statistics(scheduler_wall_time)</c></seealso>. So, if you add
+ <seeerl marker="#statistics_scheduler_wall_time">
+ <c>statistics(scheduler_wall_time)</c></seeerl>. So, if you add
all other states but this and sleep, and then divide that by all
time in the thread, you should get something very similar to the
<c>scheduler_wall_time</c> fraction. Without extra states this
@@ -6814,11 +7339,11 @@ lists:map(
part of the <c>other</c> state.</item>
</taglist>
<p>The utility module
- <seealso marker="runtime_tools:msacc"><c>msacc(3)</c></seealso>
+ <seeerl marker="runtime_tools:msacc"><c>msacc(3)</c></seeerl>
can be used to more easily analyse these statistics.</p>
<p>Returns <c>undefined</c> if system flag
- <seealso marker="#system_flag_microstate_accounting">
- <c>microstate_accounting</c></seealso> is turned off.</p>
+ <seeerl marker="#system_flag_microstate_accounting">
+ <c>microstate_accounting</c></seeerl> is turned off.</p>
<p>The list of thread information is unsorted and can appear in
different order between calls.</p>
<note>
@@ -6841,8 +7366,8 @@ lists:map(
this value does not include reductions performed in current
time slices of currently scheduled processes. If an
exact value is wanted, use
- <seealso marker="#statistics_exact_reductions">
- <c>statistics(exact_reductions)</c></seealso>.</p>
+ <seeerl marker="#statistics_exact_reductions">
+ <c>statistics(exact_reductions)</c></seeerl>.</p>
</note>
</desc>
</func>
@@ -6852,14 +7377,13 @@ lists:map(
anchor="statistics_run_queue" since=""/>
<fsummary>Information about the run-queues.</fsummary>
<desc>
- <p>Returns the total length of all normal run-queues. That is, the number
- of processes and ports that are ready to run on all available
- normal run-queues. Dirty run queues are not part of the
- result. The information is gathered atomically. That
+ <p>Returns the total length of all normal and dirty CPU
+ run queues. That is, queued work that is expected
+ to be CPU bound. The information is gathered atomically. That
is, the result is a consistent snapshot of the state, but
this operation is much more expensive compared to
- <seealso marker="#statistics_total_run_queue_lengths">
- <c>statistics(total_run_queue_lengths)</c></seealso>,
+ <seeerl marker="#statistics_total_run_queue_lengths">
+ <c>statistics(total_run_queue_lengths)</c></seeerl>,
especially when a large amount of schedulers is used.</p>
</desc>
</func>
@@ -6870,8 +7394,8 @@ lists:map(
<fsummary>Information about the run-queue lengths.</fsummary>
<desc>
<p>Returns the same as
- <seealso marker="#statistics_run_queue_lengths_all">
- <c>statistics(run_queue_lengths_all)</c></seealso>
+ <seeerl marker="#statistics_run_queue_lengths_all">
+ <c>statistics(run_queue_lengths_all)</c></seeerl>
with the exception that no information about the dirty
IO run queue is part of the result. That is, only
run queues with work that is expected to be CPU bound
@@ -6905,20 +7429,20 @@ lists:map(
queues can migrate to other normal run queues. This has
to be taken into account when evaluating the result.</p></note>
<p>See also
- <seealso marker="#statistics_run_queue_lengths">
- <c>statistics(run_queue_lengths)</c></seealso>,
- <seealso marker="#statistics_total_run_queue_lengths_all">
- <c>statistics(total_run_queue_lengths_all)</c></seealso>,
- <seealso marker="#statistics_total_run_queue_lengths">
- <c>statistics(total_run_queue_lengths)</c></seealso>,
- <seealso marker="#statistics_active_tasks">
- <c>statistics(active_tasks)</c></seealso>,
- <seealso marker="#statistics_active_tasks_all">
- <c>statistics(active_tasks_all)</c></seealso>, and
- <seealso marker="#statistics_total_active_tasks">
- <c>statistics(total_active_tasks)</c></seealso>,
- <seealso marker="#statistics_total_active_tasks_all">
- <c>statistics(total_active_tasks_all)</c></seealso>.</p>
+ <seeerl marker="#statistics_run_queue_lengths">
+ <c>statistics(run_queue_lengths)</c></seeerl>,
+ <seeerl marker="#statistics_total_run_queue_lengths_all">
+ <c>statistics(total_run_queue_lengths_all)</c></seeerl>,
+ <seeerl marker="#statistics_total_run_queue_lengths">
+ <c>statistics(total_run_queue_lengths)</c></seeerl>,
+ <seeerl marker="#statistics_active_tasks">
+ <c>statistics(active_tasks)</c></seeerl>,
+ <seeerl marker="#statistics_active_tasks_all">
+ <c>statistics(active_tasks_all)</c></seeerl>, and
+ <seeerl marker="#statistics_total_active_tasks">
+ <c>statistics(total_active_tasks)</c></seeerl>,
+ <seeerl marker="#statistics_total_active_tasks_all">
+ <c>statistics(total_active_tasks_all)</c></seeerl>.</p>
</desc>
</func>
@@ -6952,8 +7476,8 @@ lists:map(
<c><anno>ActiveTime</anno></c> is
the duration the scheduler has been busy, and
<c><anno>TotalTime</anno></c> is the total time duration since
- <seealso marker="#system_flag_scheduler_wall_time">
- <c>scheduler_wall_time</c></seealso>
+ <seeerl marker="#system_flag_scheduler_wall_time">
+ <c>scheduler_wall_time</c></seeerl>
activation for the specific scheduler. Note that
activation time can differ significantly between
schedulers. Currently dirty schedulers are activated
@@ -6978,28 +7502,28 @@ lists:map(
<p>Notice that a scheduler can also be busy even if the
OS has scheduled out the scheduler thread.</p>
<p>Returns <c>undefined</c> if system flag
- <seealso marker="#system_flag_scheduler_wall_time">
- <c>scheduler_wall_time</c></seealso> is turned off.</p>
+ <seeerl marker="#system_flag_scheduler_wall_time">
+ <c>scheduler_wall_time</c></seeerl> is turned off.</p>
<p>The list of scheduler information is unsorted and can
appear in different order between calls.</p>
<p>As of ERTS version 9.0, also dirty CPU schedulers will
be included in the result. That is, all scheduler threads
that are expected to handle CPU bound work. If you also
want information about dirty I/O schedulers, use
- <seealso marker="#statistics_scheduler_wall_time_all">
- <c>statistics(scheduler_wall_time_all)</c></seealso>
+ <seeerl marker="#statistics_scheduler_wall_time_all">
+ <c>statistics(scheduler_wall_time_all)</c></seeerl>
instead.</p>
<p>Normal schedulers will have scheduler identifiers in
the range <c>1 =&lt; <anno>SchedulerId</anno> =&lt;
- </c><seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso>.
+ </c><seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl>.
Dirty CPU schedulers will have scheduler identifiers in
the range <c>erlang:system_info(schedulers) &lt;
<anno>SchedulerId</anno> =&lt; erlang:system_info(schedulers)
+
- </c><seealso marker="#system_info_dirty_cpu_schedulers">
- <c>erlang:system_info(dirty_cpu_schedulers)</c></seealso>.
+ </c><seeerl marker="#system_info_dirty_cpu_schedulers">
+ <c>erlang:system_info(dirty_cpu_schedulers)</c></seeerl>.
</p>
<note><p>The different types of schedulers handle
specific types of jobs. Every job is assigned to a specific
@@ -7070,21 +7594,21 @@ ok
<fsummary>Information about each schedulers work time.</fsummary>
<desc>
<p>The same as
- <seealso marker="#statistics_scheduler_wall_time"><c>statistics(scheduler_wall_time)</c></seealso>,
+ <seeerl marker="#statistics_scheduler_wall_time"><c>statistics(scheduler_wall_time)</c></seeerl>,
except that it also include information about all dirty I/O
schedulers.</p>
<p>Dirty IO schedulers will have scheduler identifiers in
the range
- <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso><c>
+ <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl><c>
+
- </c><seealso marker="#system_info_dirty_cpu_schedulers">
- <c>erlang:system_info(dirty_cpu_schedulers)</c></seealso><c> &lt;
+ </c><seeerl marker="#system_info_dirty_cpu_schedulers">
+ <c>erlang:system_info(dirty_cpu_schedulers)</c></seeerl><c> &lt;
<anno>SchedulerId</anno> =&lt; erlang:system_info(schedulers)
+ erlang:system_info(dirty_cpu_schedulers)
+
- </c><seealso marker="#system_info_dirty_io_schedulers">
- <c>erlang:system_info(dirty_io_schedulers)</c></seealso>.</p>
+ </c><seeerl marker="#system_info_dirty_io_schedulers">
+ <c>erlang:system_info(dirty_io_schedulers)</c></seeerl>.</p>
<note><p>Note that work executing on dirty I/O schedulers
are expected to mainly wait for I/O. That is, when you
get high scheduler utilization on dirty I/O schedulers,
@@ -7098,7 +7622,7 @@ ok
<fsummary>Information about active processes and ports.</fsummary>
<desc>
<p>The same as calling
- <c>lists:sum(</c><seealso marker="#statistics_active_tasks"><c>statistics(active_tasks)</c></seealso><c>)</c>,
+ <c>lists:sum(</c><seeerl marker="#statistics_active_tasks"><c>statistics(active_tasks)</c></seeerl><c>)</c>,
but more efficient.</p>
</desc>
</func>
@@ -7109,7 +7633,7 @@ ok
<fsummary>Information about active processes and ports.</fsummary>
<desc>
<p>The same as calling
- <c>lists:sum(</c><seealso marker="#statistics_active_tasks_all"><c>statistics(active_tasks_all)</c></seealso><c>)</c>,
+ <c>lists:sum(</c><seeerl marker="#statistics_active_tasks_all"><c>statistics(active_tasks_all)</c></seeerl><c>)</c>,
but more efficient.</p>
</desc>
</func>
@@ -7120,7 +7644,7 @@ ok
<fsummary>Information about the run-queue lengths.</fsummary>
<desc>
<p>The same as calling
- <c>lists:sum(</c><seealso marker="#statistics_run_queue_lengths"><c>statistics(run_queue_lengths)</c></seealso><c>)</c>,
+ <c>lists:sum(</c><seeerl marker="#statistics_run_queue_lengths"><c>statistics(run_queue_lengths)</c></seeerl><c>)</c>,
but more efficient.</p>
</desc>
</func>
@@ -7131,7 +7655,7 @@ ok
<fsummary>Information about the run-queue lengths.</fsummary>
<desc>
<p>The same as calling
- <c>lists:sum(</c><seealso marker="#statistics_run_queue_lengths_all"><c>statistics(run_queue_lengths_all)</c></seealso><c>)</c>,
+ <c>lists:sum(</c><seeerl marker="#statistics_run_queue_lengths_all"><c>statistics(run_queue_lengths_all)</c></seeerl><c>)</c>,
but more efficient.</p>
</desc>
</func>
@@ -7153,9 +7677,9 @@ ok
<desc>
<p>Suspends the process identified by
<c><anno>Suspendee</anno></c>. The same as calling
- <seealso marker="#suspend_process/2">
+ <seemfa marker="#suspend_process/2">
<c>erlang:suspend_process(<anno>Suspendee</anno>,
- [])</c></seealso>.</p>
+ [])</c></seemfa>.</p>
<warning>
<p>This BIF is intended for debugging only.</p>
</warning>
@@ -7176,8 +7700,8 @@ ok
process does not leave the suspended state until its suspend
count reaches zero. The suspend count of
<c><anno>Suspendee</anno></c> is decreased when
- <seealso marker="#resume_process/1">
- <c>erlang:resume_process(<anno>Suspendee</anno>)</c></seealso>
+ <seemfa marker="#resume_process/1">
+ <c>erlang:resume_process(<anno>Suspendee</anno>)</c></seemfa>
is called by the same process that called
<c>erlang:suspend_process(<anno>Suspendee</anno>)</c>.
All increased suspend
@@ -7302,7 +7826,7 @@ ok
has been suspended
more times by the calling process than can be represented by the
currently used internal data structures. The system limit is
- &gt; 2,000,000,000 suspends and will never be lower.
+ greater than 2,000,000,000 suspends and will never be lower.
</item>
</taglist>
</desc>
@@ -7334,7 +7858,7 @@ ok
<p>
<em>This argument is deprecated.</em>
Instead of using this argument, use command-line argument
- <seealso marker="erts:erl#+sct"><c>+sct</c></seealso> in
+ <seecom marker="erts:erl#+sct"><c>+sct</c></seecom> in
<c>erl(1)</c>.</p>
<p>When this argument is removed, a final CPU topology
to use is determined at emulator boot time.</p>
@@ -7355,15 +7879,15 @@ ok
to rebind according to the new CPU topology.</p>
<p>The user-defined CPU topology can also be set by passing
command-line argument
- <seealso marker="erts:erl#+sct"><c>+sct</c></seealso> to
+ <seecom marker="erts:erl#+sct"><c>+sct</c></seecom> to
<c>erl(1)</c>.</p>
<p>For information on type <c><anno>CpuTopology</anno></c>
and more, see
- <seealso marker="#system_info_cpu_topology">
- <c>erlang:system_info(cpu_topology)</c></seealso>
+ <seeerl marker="#system_info_cpu_topology">
+ <c>erlang:system_info(cpu_topology)</c></seeerl>
as well as command-line flags
- <seealso marker="erts:erl#+sct"><c>+sct</c></seealso> and
- <seealso marker="erts:erl#+sbt"><c>+sbt</c></seealso> in
+ <seecom marker="erts:erl#+sct"><c>+sct</c></seecom> and
+ <seecom marker="erts:erl#+sbt"><c>+sbt</c></seecom> in
<c>erl(1)</c>.</p>
</desc>
</func>
@@ -7390,10 +7914,10 @@ ok
online increases proportionally to increases in the number of
schedulers online.</p>
<p>For more information, see
- <seealso marker="#system_info_dirty_cpu_schedulers">
- <c>erlang:system_info(dirty_cpu_schedulers)</c></seealso> and
- <seealso marker="#system_info_dirty_cpu_schedulers_online">
- <c>erlang:system_info(dirty_cpu_schedulers_online)</c></seealso>.</p>
+ <seeerl marker="#system_info_dirty_cpu_schedulers">
+ <c>erlang:system_info(dirty_cpu_schedulers)</c></seeerl> and
+ <seeerl marker="#system_info_dirty_cpu_schedulers_online">
+ <c>erlang:system_info(dirty_cpu_schedulers_online)</c></seeerl>.</p>
</desc>
</func>
@@ -7402,13 +7926,13 @@ ok
<fsummary>Set system flag for erts_alloc.</fsummary>
<desc>
<p>Sets system flags for
- <seealso marker="erts:erts_alloc"><c>erts_alloc(3)</c></seealso>.
+ <seecref marker="erts:erts_alloc"><c>erts_alloc(3)</c></seecref>.
<c><anno>Alloc</anno></c> is the allocator to affect, for example
<c>binary_alloc</c>. <c><anno>F</anno></c> is the flag to change and
<c><anno>V</anno></c> is the new value.</p>
<p>Only a subset of all <c>erts_alloc</c> flags can be changed
at run time. This subset is currently only the flag
- <seealso marker="erts:erts_alloc#M_sbct"><c>sbct</c></seealso>.</p>
+ <seecref marker="erts:erts_alloc#M_sbct"><c>sbct</c></seecref>.</p>
<p>Returns <c>ok</c> if the flag was set or <c>notsup</c> if not
supported by <c>erts_alloc</c>.</p>
</desc>
@@ -7442,8 +7966,8 @@ ok
Turns on/off microstate accounting measurements. When passing reset,
all counters are reset to 0.</p>
<p>For more information see
- <seealso marker="#statistics_microstate_accounting">
- <c>statistics(microstate_accounting)</c></seealso>.</p>
+ <seeerl marker="#statistics_microstate_accounting">
+ <c>statistics(microstate_accounting)</c></seeerl>.</p>
</desc>
</func>
@@ -7456,8 +7980,8 @@ ok
only processes spawned after the change of
<c>min_heap_size</c> has been made. <c>min_heap_size</c>
can be set for individual processes by using
- <seealso marker="#spawn_opt/4"><c>spawn_opt/4</c></seealso> or
- <seealso marker="#process_flag/2"><c>process_flag/2</c></seealso>.</p>
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/4</c></seemfa> or
+ <seemfa marker="#process_flag/2"><c>process_flag/2</c></seemfa>.</p>
<p>Returns the old value of the flag.</p>
</desc>
</func>
@@ -7473,8 +7997,8 @@ ok
<c>min_bin_vheap_size</c> has been made.
<c>min_bin_vheap_size</c> can be set for individual
processes by using
- <seealso marker="#spawn_opt/4"><c>spawn_opt/2,3,4</c></seealso> or
- <seealso marker="#process_flag/2"><c>process_flag/2</c></seealso>.</p>
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/2,3,4</c></seemfa> or
+ <seemfa marker="#process_flag/2"><c>process_flag/2</c></seemfa>.</p>
<p>Returns the old value of the flag.</p>
</desc>
</func>
@@ -7490,9 +8014,9 @@ ok
The size is specified in words. The new <c>max_heap_size</c>
effects only processes spawned efter the change has been made.
<c>max_heap_size</c> can be set for individual processes using
- <seealso marker="#spawn_opt/4"><c>spawn_opt/2,3,4</c></seealso> or
- <seealso marker="#process_flag_max_heap_size">
- <c>process_flag/2</c></seealso>.</p>
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/2,3,4</c></seemfa> or
+ <seeerl marker="#process_flag_max_heap_size">
+ <c>process_flag/2</c></seeerl>.</p>
<p>Returns the old value of the flag.</p>
</desc>
</func>
@@ -7533,8 +8057,8 @@ ok
describes the state just after the call to
<c>erlang:system_flag(multi_scheduling, <anno>BlockState</anno>)</c>
has been made. For information about the return values, see
- <seealso marker="#system_info_multi_scheduling">
- <c>erlang:system_info(multi_scheduling)</c></seealso>.</p>
+ <seeerl marker="#system_info_multi_scheduling">
+ <c>erlang:system_info(multi_scheduling)</c></seeerl>.</p>
<note><p>Blocking of multi-scheduling and normal multi-scheduling
is normally not needed. If you feel that you need to use these
features, consider it a few more times again. Blocking
@@ -7542,14 +8066,14 @@ ok
most likely a <em>very inefficient</em> way to solve the problem.</p>
</note>
<p>See also
- <seealso marker="#system_info_multi_scheduling">
- <c>erlang:system_info(multi_scheduling)</c></seealso>,
- <seealso marker="#system_info_normal_multi_scheduling_blockers">
- <c>erlang:system_info(normal_multi_scheduling_blockers)</c></seealso>,
- <seealso marker="#system_info_multi_scheduling_blockers">
- <c>erlang:system_info(multi_scheduling_blockers)</c></seealso>, and
- <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso>.</p>
+ <seeerl marker="#system_info_multi_scheduling">
+ <c>erlang:system_info(multi_scheduling)</c></seeerl>,
+ <seeerl marker="#system_info_normal_multi_scheduling_blockers">
+ <c>erlang:system_info(normal_multi_scheduling_blockers)</c></seeerl>,
+ <seeerl marker="#system_info_multi_scheduling_blockers">
+ <c>erlang:system_info(multi_scheduling_blockers)</c></seeerl>, and
+ <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl>.</p>
</desc>
</func>
@@ -7563,7 +8087,7 @@ ok
<p>
<em>This argument is deprecated.</em>
Instead of using this argument, use command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt</c></seealso> in
+ <seecom marker="erts:erl#+sbt"><c>+sbt</c></seecom> in
<c>erl(1)</c>. When this argument is removed, a final scheduler bind
type to use is determined at emulator boot time.</p>
</warning>
@@ -7577,8 +8101,8 @@ ok
logical processor identifiers. If an error is reported,
an error event is logged. To verify that the
schedulers have bound as requested, call
- <seealso marker="#system_info_scheduler_bindings">
- <c>erlang:system_info(scheduler_bindings)</c></seealso>.</p>
+ <seeerl marker="#system_info_scheduler_bindings">
+ <c>erlang:system_info(scheduler_bindings)</c></seeerl>.</p>
</note>
<p>Schedulers can be bound on newer Linux,
Solaris, FreeBSD, and Windows systems, but more systems will be
@@ -7587,8 +8111,8 @@ ok
the CPU topology must be known. If the runtime system fails
to detect the CPU topology automatically, it can be defined.
For more information on how to define the CPU topology, see
- command-line flag <seealso marker="erts:erl#+sct">
- <c>+sct</c></seealso> in <c>erl(1)</c>.</p>
+ command-line flag <seecom marker="erts:erl#+sct">
+ <c>+sct</c></seecom> in <c>erl(1)</c>.</p>
<p>The runtime system does by default <em>not</em> bind schedulers
to logical processors.</p>
<note><p>If the Erlang runtime system is the only OS
@@ -7606,47 +8130,47 @@ ok
<taglist>
<tag><c>unbound</c></tag>
<item>Same as command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt u</c></seealso> in
+ <seecom marker="erts:erl#+sbt"><c>+sbt u</c></seecom> in
<c>erl(1)</c>.
</item>
<tag><c>no_spread</c></tag>
<item>Same as command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt ns</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt ns</c></seecom>
in <c>erl(1)</c>.
</item>
<tag><c>thread_spread</c></tag>
<item>Same as command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt ts</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt ts</c></seecom>
in <c>erl(1)</c>.
</item>
<tag><c>processor_spread</c></tag>
<item>Same as command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt ps</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt ps</c></seecom>
in <c>erl(1)</c>.
</item>
<tag><c>spread</c></tag>
<item>Same as command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt s</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt s</c></seecom>
in <c>erl(1)</c>.
</item>
<tag><c>no_node_thread_spread</c></tag>
<item>Same as command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt nnts</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt nnts</c></seecom>
in <c>erl(1)</c>.
</item>
<tag><c>no_node_processor_spread</c></tag>
<item>Same as command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt nnps</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt nnps</c></seecom>
in <c>erl(1)</c>.
</item>
<tag><c>thread_no_node_processor_spread</c></tag>
<item>Same as command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt tnnps</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt tnnps</c></seecom>
in <c>erl(1)</c>.
</item>
<tag><c>default_bind</c></tag>
<item>Same as command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt db</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt db</c></seecom>
in <c>erl(1)</c>.
</item>
</taglist>
@@ -7666,16 +8190,16 @@ ok
</item>
</taglist>
<p>The scheduler bind type can also be set by passing command-line
- argument <seealso marker="erts:erl#+sbt">
- <c>+sbt</c></seealso> to <c>erl(1)</c>.</p>
+ argument <seecom marker="erts:erl#+sbt">
+ <c>+sbt</c></seecom> to <c>erl(1)</c>.</p>
<p>For more information, see
- <seealso marker="#system_info_scheduler_bind_type">
- <c>erlang:system_info(scheduler_bind_type)</c></seealso>,
- <seealso marker="#system_info_scheduler_bindings">
- <c>erlang:system_info(scheduler_bindings)</c></seealso>,
+ <seeerl marker="#system_info_scheduler_bind_type">
+ <c>erlang:system_info(scheduler_bind_type)</c></seeerl>,
+ <seeerl marker="#system_info_scheduler_bindings">
+ <c>erlang:system_info(scheduler_bindings)</c></seeerl>,
as well as command-line flags
- <seealso marker="erts:erl#+sbt"><c>+sbt</c></seealso>
- and <seealso marker="erts:erl#+sct"><c>+sct</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt</c></seecom>
+ and <seecom marker="erts:erl#+sct"><c>+sct</c></seecom>
in <c>erl(1)</c>.</p>
</desc>
</func>
@@ -7688,8 +8212,8 @@ ok
<p>
Turns on or off scheduler wall time measurements.</p>
<p>For more information, see
- <seealso marker="#statistics_scheduler_wall_time">
- <c>statistics(scheduler_wall_time)</c></seealso>.</p>
+ <seeerl marker="#statistics_scheduler_wall_time">
+ <c>statistics(scheduler_wall_time)</c></seeerl>.</p>
</desc>
</func>
@@ -7704,8 +8228,8 @@ ok
erlang:system_info(schedulers)]]></c>.</p>
<p>Returns the old value of the flag.</p>
<p>If the emulator was built with support for
- <seealso marker="#system_flag_dirty_cpu_schedulers_online">
- dirty schedulers</seealso>,
+ <seeerl marker="#system_flag_dirty_cpu_schedulers_online">
+ dirty schedulers</seeerl>,
changing the number of schedulers online can also change the
number of dirty CPU schedulers online. For example, if 12
schedulers and 6 dirty CPU schedulers are online, and
@@ -7715,10 +8239,10 @@ ok
Similarly, the number of dirty CPU schedulers online increases
proportionally to increases in the number of schedulers online.</p>
<p>For more information, see
- <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso> and
- <seealso marker="#system_info_schedulers_online">
- <c>erlang:system_info(schedulers_online)</c></seealso>.</p>
+ <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl> and
+ <seeerl marker="#system_info_schedulers_online">
+ <c>erlang:system_info(schedulers_online)</c></seeerl>.</p>
</desc>
</func>
@@ -7746,7 +8270,7 @@ Metadata = #{ pid => pid(),
<p>The default is the process named <c>logger</c>.</p>
<p>Returns the old value of the flag.</p>
<note><p>This function is designed to be used by the
- KERNEL <seealso marker="kernel:logger"><c>logger</c></seealso>.
+ KERNEL <seeerl marker="kernel:logger"><c>logger</c></seeerl>.
Be careful if you change it to something else as
log messages may be lost. If you want to intercept
emulator log messages, do it by adding a specialized handler
@@ -7761,7 +8285,7 @@ Metadata = #{ pid => pid(),
<p>Sets the value of the node trace control word to
<c><anno>TCW</anno></c>, which is to be an unsigned integer.
For more information, see function
- <seealso marker="erts:match_spec#set_tcw"><c>set_tcw</c></seealso>
+ <seeguide marker="erts:match_spec#set_tcw"><c>set_tcw</c></seeguide>
in section "Match Specifications in Erlang" in the
User's Guide.</p>
<p>Returns the old value of the flag.</p>
@@ -7774,9 +8298,9 @@ Metadata = #{ pid => pid(),
<fsummary>Finalize the time offset.</fsummary>
<desc>
<p>
- Finalizes the <seealso marker="#time_offset/0">time offset</seealso>
- when <seealso marker="time_correction#Single_Time_Warp_Mode">single
- time warp mode</seealso> is used. If another time warp mode
+ Finalizes the <seemfa marker="#time_offset/0">time offset</seemfa>
+ when <seeguide marker="time_correction#Single_Time_Warp_Mode">single
+ time warp mode</seeguide> is used. If another time warp mode
is used, the time offset state is left unchanged.</p>
<p>Returns the old state identifier, that is:</p>
<list>
@@ -7786,145 +8310,145 @@ Metadata = #{ pid => pid(),
<item><p>If <c>final</c> is returned, the time offset was
already in the final state. This either because another
<c>erlang:system_flag(time_offset, finalize)</c> call or
- because <seealso marker="time_correction#No_Time_Warp_Mode">no
- time warp mode</seealso> is used.</p>
+ because <seeguide marker="time_correction#No_Time_Warp_Mode">no
+ time warp mode</seeguide> is used.</p>
</item>
<item><p>If <c>volatile</c> is returned, the time offset
cannot be finalized because
- <seealso marker="time_correction#Multi_Time_Warp_Mode">multi-time
- warp mode</seealso> is used.</p>
+ <seeguide marker="time_correction#Multi_Time_Warp_Mode">multi-time
+ warp mode</seeguide> is used.</p>
</item>
</list>
</desc>
</func>
<func>
- <name name="system_info" arity="1" clause_i="76" since=""/>
+ <name name="system_info" arity="1" clause_i="77" since=""/>
<fsummary>System info overview.</fsummary>
<desc>
<p>Returns information about the current system.
The documentation of this function is broken into the following
sections in order to make it easier to navigate.</p>
<taglist>
- <tag><seealso marker="#system_info_allocator">
- <c>Memory Allocation</c></seealso></tag>
+ <tag><seeerl marker="#system_info_allocator">
+ <c>Memory Allocation</c></seeerl></tag>
<item>
<p>
- <seealso marker="#system_info_allocated_areas"><c>allocated_areas</c></seealso>,
- <seealso marker="#system_info_allocator"><c>allocator</c></seealso>,
- <seealso marker="#system_info_alloc_util_allocators"><c>alloc_util_allocators</c></seealso>,
- <seealso marker="#system_info_allocator_sizes"><c>allocator_sizes</c></seealso>,
- <seealso marker="#system_info_elib_malloc"><c>elib_malloc</c></seealso>
+ <seeerl marker="#system_info_allocated_areas"><c>allocated_areas</c></seeerl>,
+ <seeerl marker="#system_info_allocator"><c>allocator</c></seeerl>,
+ <seeerl marker="#system_info_alloc_util_allocators"><c>alloc_util_allocators</c></seeerl>,
+ <seeerl marker="#system_info_allocator_sizes"><c>allocator_sizes</c></seeerl>,
+ <seeerl marker="#system_info_elib_malloc"><c>elib_malloc</c></seeerl>
</p>
</item>
- <tag><seealso marker="#system_info_cpu_topology">
- <c>CPU Topology</c></seealso></tag>
+ <tag><seeerl marker="#system_info_cpu_topology">
+ <c>CPU Topology</c></seeerl></tag>
<item>
<p>
- <seealso marker="#system_info_cpu_topology"><c>cpu_topology</c></seealso>,
- <seealso marker="#system_info_logical_processors"><c>logical_processors</c></seealso>,
- <seealso marker="#system_info_update_cpu_info"><c>update_cpu_info</c></seealso>
+ <seeerl marker="#system_info_cpu_topology"><c>cpu_topology</c></seeerl>,
+ <seeerl marker="#system_info_logical_processors"><c>logical_processors</c></seeerl>,
+ <seeerl marker="#system_info_update_cpu_info"><c>update_cpu_info</c></seeerl>
</p>
</item>
- <tag><seealso marker="#system_info_process">
- <c>Process Information</c></seealso></tag>
+ <tag><seeerl marker="#system_info_process">
+ <c>Process Information</c></seeerl></tag>
<item>
<p>
- <seealso marker="#system_info_fullsweep_after"><c>fullsweep_after</c></seealso>,
- <seealso marker="#system_info_garbage_collection"><c>garbage_collection</c></seealso>,
- <seealso marker="#system_info_heap_sizes"><c>heap_sizes</c></seealso>,
- <seealso marker="#system_info_heap_type"><c>heap_type</c></seealso>,
- <seealso marker="#system_info_max_heap_size"><c>max_heap_size</c></seealso>,
- <seealso marker="#system_info_message_queue_data"><c>message_queue_data</c></seealso>,
- <seealso marker="#system_info_min_heap_size"><c>min_heap_size</c></seealso>,
- <seealso marker="#system_info_min_bin_vheap_size"><c>min_bin_vheap_size</c></seealso>,
- <seealso marker="#system_info_procs"><c>procs</c></seealso>
+ <seeerl marker="#system_info_fullsweep_after"><c>fullsweep_after</c></seeerl>,
+ <seeerl marker="#system_info_garbage_collection"><c>garbage_collection</c></seeerl>,
+ <seeerl marker="#system_info_heap_sizes"><c>heap_sizes</c></seeerl>,
+ <seeerl marker="#system_info_heap_type"><c>heap_type</c></seeerl>,
+ <seeerl marker="#system_info_max_heap_size"><c>max_heap_size</c></seeerl>,
+ <seeerl marker="#system_info_message_queue_data"><c>message_queue_data</c></seeerl>,
+ <seeerl marker="#system_info_min_heap_size"><c>min_heap_size</c></seeerl>,
+ <seeerl marker="#system_info_min_bin_vheap_size"><c>min_bin_vheap_size</c></seeerl>,
+ <seeerl marker="#system_info_procs"><c>procs</c></seeerl>
</p>
</item>
- <tag><seealso marker="#system_info_limits">
- <c>System Limits</c></seealso></tag>
+ <tag><seeerl marker="#system_info_limits">
+ <c>System Limits</c></seeerl></tag>
<item>
<p>
- <seealso marker="#system_info_atom_count"><c>atom_count</c></seealso>,
- <seealso marker="#system_info_atom_limit"><c>atom_limit</c></seealso>,
- <seealso marker="#system_info_ets_count"><c>ets_count</c></seealso>,
- <seealso marker="#system_info_ets_limit"><c>ets_limit</c></seealso>,
- <seealso marker="#system_info_port_count"><c>port_count</c></seealso>,
- <seealso marker="#system_info_port_limit"><c>port_limit</c></seealso>,
- <seealso marker="#system_info_process_count"><c>process_count</c></seealso>,
- <seealso marker="#system_info_process_limit"><c>process_limit</c></seealso>
+ <seeerl marker="#system_info_atom_count"><c>atom_count</c></seeerl>,
+ <seeerl marker="#system_info_atom_limit"><c>atom_limit</c></seeerl>,
+ <seeerl marker="#system_info_ets_count"><c>ets_count</c></seeerl>,
+ <seeerl marker="#system_info_ets_limit"><c>ets_limit</c></seeerl>,
+ <seeerl marker="#system_info_port_count"><c>port_count</c></seeerl>,
+ <seeerl marker="#system_info_port_limit"><c>port_limit</c></seeerl>,
+ <seeerl marker="#system_info_process_count"><c>process_count</c></seeerl>,
+ <seeerl marker="#system_info_process_limit"><c>process_limit</c></seeerl>
</p>
</item>
- <tag><seealso marker="#system_info_time">
- <c>System Time</c></seealso></tag>
+ <tag><seeerl marker="#system_info_time">
+ <c>System Time</c></seeerl></tag>
<item>
<p>
- <seealso marker="#system_info_end_time"><c>end_time</c></seealso>,
- <seealso marker="#system_info_os_monotonic_time_source"><c>os_monotonic_time_source</c></seealso>,
- <seealso marker="#system_info_os_system_time_source"><c>os_system_time_source</c></seealso>,
- <seealso marker="#system_info_start_time"><c>start_time</c></seealso>,
- <seealso marker="#system_info_time_correction"><c>time_correction</c></seealso>,
- <seealso marker="#system_info_time_offset"><c>time_offset</c></seealso>,
- <seealso marker="#system_info_time_warp_mode"><c>time_warp_mode</c></seealso>,
- <seealso marker="#system_info_tolerant_timeofday"><c>tolerant_timeofday</c></seealso>
+ <seeerl marker="#system_info_end_time"><c>end_time</c></seeerl>,
+ <seeerl marker="#system_info_os_monotonic_time_source"><c>os_monotonic_time_source</c></seeerl>,
+ <seeerl marker="#system_info_os_system_time_source"><c>os_system_time_source</c></seeerl>,
+ <seeerl marker="#system_info_start_time"><c>start_time</c></seeerl>,
+ <seeerl marker="#system_info_time_correction"><c>time_correction</c></seeerl>,
+ <seeerl marker="#system_info_time_offset"><c>time_offset</c></seeerl>,
+ <seeerl marker="#system_info_time_warp_mode"><c>time_warp_mode</c></seeerl>,
+ <seeerl marker="#system_info_tolerant_timeofday"><c>tolerant_timeofday</c></seeerl>
</p>
</item>
- <tag><seealso marker="#system_info_scheduler">
- <c>Scheduler Information</c></seealso></tag>
+ <tag><seeerl marker="#system_info_scheduler">
+ <c>Scheduler Information</c></seeerl></tag>
<item>
<p>
- <seealso marker="#system_info_dirty_cpu_schedulers"><c>dirty_cpu_schedulers</c></seealso>,
- <seealso marker="#system_info_dirty_cpu_schedulers_online"><c>dirty_cpu_schedulers_online</c></seealso>,
- <seealso marker="#system_info_dirty_io_schedulers"><c>dirty_io_schedulers</c></seealso>,
- <seealso marker="#system_info_multi_scheduling"><c>multi_scheduling</c></seealso>,
- <seealso marker="#system_info_multi_scheduling_blockers"><c>multi_scheduling_blockers</c></seealso>,
- <seealso marker="#system_info_normal_multi_scheduling_blockers"><c>normal_multi_scheduling_blockers</c></seealso>,
- <seealso marker="#system_info_scheduler_bind_type"><c>scheduler_bind_type</c></seealso>,
- <seealso marker="#system_info_scheduler_bindings"><c>scheduler_bindings</c></seealso>,
- <seealso marker="#system_info_scheduler_id"><c>scheduler_id</c></seealso>,
- <seealso marker="#system_info_schedulers"><c>schedulers</c></seealso>,
- <seealso marker="#system_info_smp_support"><c>smp_support</c></seealso>,
- <seealso marker="#system_info_threads"><c>threads</c></seealso>,
- <seealso marker="#system_info_thread_pool_size"><c>thread_pool_size</c></seealso>
+ <seeerl marker="#system_info_dirty_cpu_schedulers"><c>dirty_cpu_schedulers</c></seeerl>,
+ <seeerl marker="#system_info_dirty_cpu_schedulers_online"><c>dirty_cpu_schedulers_online</c></seeerl>,
+ <seeerl marker="#system_info_dirty_io_schedulers"><c>dirty_io_schedulers</c></seeerl>,
+ <seeerl marker="#system_info_multi_scheduling"><c>multi_scheduling</c></seeerl>,
+ <seeerl marker="#system_info_multi_scheduling_blockers"><c>multi_scheduling_blockers</c></seeerl>,
+ <seeerl marker="#system_info_normal_multi_scheduling_blockers"><c>normal_multi_scheduling_blockers</c></seeerl>,
+ <seeerl marker="#system_info_scheduler_bind_type"><c>scheduler_bind_type</c></seeerl>,
+ <seeerl marker="#system_info_scheduler_bindings"><c>scheduler_bindings</c></seeerl>,
+ <seeerl marker="#system_info_scheduler_id"><c>scheduler_id</c></seeerl>,
+ <seeerl marker="#system_info_schedulers"><c>schedulers</c></seeerl>,
+ <seeerl marker="#system_info_smp_support"><c>smp_support</c></seeerl>,
+ <seeerl marker="#system_info_threads"><c>threads</c></seeerl>,
+ <seeerl marker="#system_info_thread_pool_size"><c>thread_pool_size</c></seeerl>
</p>
</item>
- <tag><seealso marker="#system_info_dist">
- <c>Distribution Information</c></seealso></tag>
+ <tag><seeerl marker="#system_info_dist">
+ <c>Distribution Information</c></seeerl></tag>
<item>
<p>
- <seealso marker="#system_info_creation"><c>creation</c></seealso>,
- <seealso marker="#system_info_delayed_node_table_gc"><c>delayed_node_table_gc</c></seealso>,
- <seealso marker="#system_info_dist"><c>dist</c></seealso>,
- <seealso marker="#system_info_dist_buf_busy_limit"><c>dist_buf_busy_limit</c></seealso>,
- <seealso marker="#system_info_dist_ctrl"><c>dist_ctrl</c></seealso>
+ <seeerl marker="#system_info_creation"><c>creation</c></seeerl>,
+ <seeerl marker="#system_info_delayed_node_table_gc"><c>delayed_node_table_gc</c></seeerl>,
+ <seeerl marker="#system_info_dist"><c>dist</c></seeerl>,
+ <seeerl marker="#system_info_dist_buf_busy_limit"><c>dist_buf_busy_limit</c></seeerl>,
+ <seeerl marker="#system_info_dist_ctrl"><c>dist_ctrl</c></seeerl>
</p>
</item>
- <tag><seealso marker="#system_info_misc">
- <c>System Information</c></seealso></tag>
+ <tag><seeerl marker="#system_info_misc">
+ <c>System Information</c></seeerl></tag>
<item>
<p>
- <seealso marker="#system_info_build_type"><c>build_type</c></seealso>,
- <seealso marker="#system_info_c_compiler_used"><c>c_compiler_used</c></seealso>,
- <seealso marker="#system_info_check_io"><c>check_io</c></seealso>,
- <seealso marker="#system_info_compat_rel"><c>compat_rel</c></seealso>,
- <seealso marker="#system_info_debug_compiled"><c>debug_compiled</c></seealso>,
- <seealso marker="#system_info_driver_version"><c>driver_version</c></seealso>,
- <seealso marker="#system_info_dynamic_trace"><c>dynamic_trace</c></seealso>,
- <seealso marker="#system_info_dynamic_trace_probes"><c>dynamic_trace_probes</c></seealso>,
- <seealso marker="#system_info_info"><c>info</c></seealso>,
- <seealso marker="#system_info_kernel_poll"><c>kernel_poll</c></seealso>,
- <seealso marker="#system_info_loaded"><c>loaded</c></seealso>,
- <seealso marker="#system_info_machine"><c>machine</c></seealso>,
- <seealso marker="#system_info_modified_timing_level"><c>modified_timing_level</c></seealso>,
- <seealso marker="#system_info_nif_version"><c>nif_version</c></seealso>,
- <seealso marker="#system_info_otp_release"><c>otp_release</c></seealso>,
- <seealso marker="#system_info_port_parallelism"><c>port_parallelism</c></seealso>,
- <seealso marker="#system_info_system_architecture"><c>system_architecture</c></seealso>,
- <seealso marker="#system_info_system_logger"><c>system_logger</c></seealso>,
- <seealso marker="#system_info_system_version"><c>system_version</c></seealso>,
- <seealso marker="#system_info_trace_control_word"><c>trace_control_word</c></seealso>,
- <seealso marker="#system_info_version"><c>version</c></seealso>,
- <seealso marker="#system_info_wordsize"><c>wordsize</c></seealso>
+ <seeerl marker="#system_info_build_type"><c>build_type</c></seeerl>,
+ <seeerl marker="#system_info_c_compiler_used"><c>c_compiler_used</c></seeerl>,
+ <seeerl marker="#system_info_check_io"><c>check_io</c></seeerl>,
+ <seeerl marker="#system_info_compat_rel"><c>compat_rel</c></seeerl>,
+ <seeerl marker="#system_info_debug_compiled"><c>debug_compiled</c></seeerl>,
+ <seeerl marker="#system_info_driver_version"><c>driver_version</c></seeerl>,
+ <seeerl marker="#system_info_dynamic_trace"><c>dynamic_trace</c></seeerl>,
+ <seeerl marker="#system_info_dynamic_trace_probes"><c>dynamic_trace_probes</c></seeerl>,
+ <seeerl marker="#system_info_info"><c>info</c></seeerl>,
+ <seeerl marker="#system_info_kernel_poll"><c>kernel_poll</c></seeerl>,
+ <seeerl marker="#system_info_loaded"><c>loaded</c></seeerl>,
+ <seeerl marker="#system_info_machine"><c>machine</c></seeerl>,
+ <seeerl marker="#system_info_modified_timing_level"><c>modified_timing_level</c></seeerl>,
+ <seeerl marker="#system_info_nif_version"><c>nif_version</c></seeerl>,
+ <seeerl marker="#system_info_otp_release"><c>otp_release</c></seeerl>,
+ <seeerl marker="#system_info_port_parallelism"><c>port_parallelism</c></seeerl>,
+ <seeerl marker="#system_info_system_architecture"><c>system_architecture</c></seeerl>,
+ <seeerl marker="#system_info_system_logger"><c>system_logger</c></seeerl>,
+ <seeerl marker="#system_info_system_version"><c>system_version</c></seeerl>,
+ <seeerl marker="#system_info_trace_control_word"><c>trace_control_word</c></seeerl>,
+ <seeerl marker="#system_info_version"><c>version</c></seeerl>,
+ <seeerl marker="#system_info_wordsize"><c>wordsize</c></seeerl>
</p>
</item>
</taglist>
@@ -7972,8 +8496,8 @@ Metadata = #{ pid => pid(),
Some values are part of other values, and some memory
areas are not part of the result. For information about
the total amount of memory allocated by the emulator, see
- <seealso marker="#memory/0">
- <c>erlang:memory/0,1</c></seealso>.</p>
+ <seemfa marker="#memory/0">
+ <c>erlang:memory/0,1</c></seemfa>.</p>
</item>
<tag><marker id="system_info_allocator"/>
<c>allocator</c></tag>
@@ -8007,8 +8531,8 @@ Metadata = #{ pid => pid(),
</item>
</list>
<p>See also "System Flags Effecting erts_alloc" in
- <seealso marker="erts:erts_alloc#flags">
- <c>erts_alloc(3)</c></seealso>.</p>
+ <seecref marker="erts:erts_alloc#flags">
+ <c>erts_alloc(3)</c></seecref>.</p>
</item>
<tag><marker id="system_info_allocator_tuple"></marker>
<c>{allocator, <anno>Alloc</anno>}</c></tag>
@@ -8029,7 +8553,7 @@ Metadata = #{ pid => pid(),
as it can be of interest for others it has been
briefly documented.</p>
<p>The recognized allocators are listed in
- <seealso marker="erts:erts_alloc"><c>erts_alloc(3)</c></seealso>.
+ <seecref marker="erts:erts_alloc"><c>erts_alloc(3)</c></seecref>.
Information about super carriers can be obtained from
ERTS 8.0 with <c>{allocator, erts_mmap}</c> or from
ERTS 5.10.4; the returned list when calling with
@@ -8063,8 +8587,8 @@ Metadata = #{ pid => pid(),
<p>Returns a list of the names of all allocators using
the ERTS internal <c>alloc_util</c> framework
as atoms. For more information, see section
- <seealso marker="erts:erts_alloc#alloc_util">The
- alloc_util framework</seealso>
+ <seecref marker="erts:erts_alloc#alloc_util">The
+ alloc_util framework</seecref>
in <c>erts_alloc(3)</c>.</p>
</item>
<tag><marker id="system_info_allocator_sizes"/>
@@ -8073,9 +8597,9 @@ Metadata = #{ pid => pid(),
<p>Returns various size information for the specified
allocator. The information returned is a subset of the
information returned by
- <seealso marker="#system_info_allocator_tuple">
+ <seeerl marker="#system_info_allocator_tuple">
<c>erlang:system_info({allocator,
- <anno>Alloc</anno>})</c></seealso>.</p>
+ <anno>Alloc</anno>})</c></seeerl>.</p>
</item>
<tag><marker id="system_info_elib_malloc"/>
<c>elib_malloc</c></tag>
@@ -8130,11 +8654,11 @@ Metadata = #{ pid => pid(),
<p>Returns the <c><anno>CpuTopology</anno></c> currently used by
the emulator. The CPU topology is used when binding schedulers
to logical processors. The CPU topology used is the
- <seealso marker="erlang#system_info_cpu_topology_defined">
- user-defined CPU topology</seealso>,
+ <seeerl marker="erlang#system_info_cpu_topology_defined">
+ user-defined CPU topology</seeerl>,
if such exists, otherwise the
- <seealso marker="erlang#system_info_cpu_topology_detected">
- automatically detected CPU topology</seealso>,
+ <seeerl marker="erlang#system_info_cpu_topology_detected">
+ automatically detected CPU topology</seeerl>,
if such exists. If no CPU topology
exists, <c>undefined</c> is returned.</p>
<p><c>node</c> refers to Non-Uniform Memory Access (NUMA)
@@ -8163,10 +8687,10 @@ Metadata = #{ pid => pid(),
<marker id="system_info_cpu_topology_defined"></marker>
<p>Returns the user-defined <c><anno>CpuTopology</anno></c>.
For more information, see command-line flag
- <seealso marker="erts:erl#+sct"><c>+sct</c></seealso> in
+ <seecom marker="erts:erl#+sct"><c>+sct</c></seecom> in
<c>erl(1)</c> and argument
- <seealso marker="#system_info_cpu_topology">
- <c>cpu_topology</c></seealso>.</p>
+ <seeerl marker="#system_info_cpu_topology">
+ <c>cpu_topology</c></seeerl>.</p>
</item>
<tag><c>{cpu_topology, detected}</c></tag>
<item>
@@ -8178,15 +8702,15 @@ Metadata = #{ pid => pid(),
On Windows system with more than 32 logical processors,
the CPU topology is not detected.</p>
<p>For more information, see argument
- <seealso marker="#system_info_cpu_topology">
- <c>cpu_topology</c></seealso>.</p>
+ <seeerl marker="#system_info_cpu_topology">
+ <c>cpu_topology</c></seeerl>.</p>
</item>
<tag><c>{cpu_topology, used}</c></tag>
<item>
<p>Returns <c><anno>CpuTopology</anno></c> used by the emulator.
For more information, see argument
- <seealso marker="#system_info_cpu_topology">
- <c>cpu_topology</c></seealso>.</p>
+ <seeerl marker="#system_info_cpu_topology">
+ <c>cpu_topology</c></seeerl>.</p>
</item>
<tag><marker id="system_info_logical_processors"/>
<c>logical_processors</c></tag>
@@ -8204,8 +8728,8 @@ Metadata = #{ pid => pid(),
integer, or the atom <c>unknown</c> if the emulator
cannot detect the available logical processors. The number
of available logical processors is less than or equal to
- the number of <seealso marker="#system_info_logical_processors_online">
- logical processors online</seealso>.</p>
+ the number of <seeerl marker="#system_info_logical_processors_online">
+ logical processors online</seeerl>.</p>
</item>
<tag><marker id="system_info_logical_processors_online"/>
<c>logical_processors_online</c></tag>
@@ -8215,29 +8739,39 @@ Metadata = #{ pid => pid(),
or the atom <c>unknown</c> if the emulator cannot
detect logical processors online. The number of logical
processors online is less than or equal to the number of
- <seealso marker="#system_info_logical_processors">logical processors
- configured</seealso>.</p>
+ <seeerl marker="#system_info_logical_processors">logical processors
+ configured</seeerl>.</p>
+ </item>
+ <tag><marker id="system_info_cpu_quota"/>
+ <c>cpu_quota</c></tag>
+ <item>
+ <p>Returns the detected CPU quota the emulator is limited by. The
+ return value is an integer saying how many processors' worth of
+ runtime we get (between 1 and the number of logical processors),
+ or the atom <c>unknown</c> if the emulator cannot detect a
+ quota.</p>
</item>
<tag><marker id="system_info_update_cpu_info"/>
<c>update_cpu_info</c></tag>
<item>
<p>The runtime system rereads the CPU information available
and updates its internally stored information about the
- <seealso marker="#system_info_cpu_topology_detected">detected
- CPU topology</seealso> and the number of logical processors
- <seealso marker="#system_info_logical_processors">configured</seealso>,
- <seealso marker="#system_info_logical_processors_online">online</seealso>,
- and <seealso marker="#system_info_logical_processors_available">
- available</seealso>.</p>
+ <seeerl marker="#system_info_cpu_topology_detected">detected
+ CPU topology</seeerl> and the number of logical processors
+ <seeerl marker="#system_info_logical_processors">configured</seeerl>,
+ <seeerl marker="#system_info_logical_processors_online">online</seeerl>,
+ <seeerl marker="#system_info_logical_processors_available">available</seeerl>,
+ and <seeerl marker="#system_info_cpu_quota">cpu
+ quota</seeerl>.</p>
<p>If the CPU information has changed since the last time
it was read, the atom <c>changed</c> is returned, otherwise
the atom <c>unchanged</c>. If the CPU information has changed,
you probably want to
- <seealso marker="#system_flag_schedulers_online">adjust the
- number of schedulers online</seealso>. You typically want
+ <seeerl marker="#system_flag_schedulers_online">adjust the
+ number of schedulers online</seeerl>. You typically want
to have as many schedulers online as
- <seealso marker="#system_info_logical_processors_available">logical
- processors available</seealso>.</p>
+ <seeerl marker="#system_info_logical_processors_available">logical
+ processors available</seeerl>.</p>
</item>
</taglist>
</desc>
@@ -8277,9 +8811,9 @@ Metadata = #{ pid => pid(),
<c>spawn</c> or <c>spawn_link</c> uses these
garbage collection settings. The default settings can be
changed by using
- <seealso marker="#system_flag/2">
- <c>erlang:system_flag/2</c></seealso>.
- <seealso marker="#spawn_opt/4"><c>spawn_opt/2,3,4</c></seealso>
+ <seemfa marker="#system_flag/2">
+ <c>erlang:system_flag/2</c></seemfa>.
+ <seemfa marker="#spawn_opt/4"><c>spawn_opt/2,3,4</c></seemfa>
can spawn a process that does not use the default
settings.</p>
</item>
@@ -8312,27 +8846,26 @@ Metadata = #{ pid => pid(),
where <c><anno>MaxHeapSize</anno></c> is the current
system-wide maximum heap size settings for spawned processes.
This setting can be set using the command-line flags
- <seealso marker="erl#+hmax"><c>+hmax</c></seealso>,
- <seealso marker="erl#+hmaxk"><c>+hmaxk</c></seealso> and
- <seealso marker="erl#+hmaxel"><c>+hmaxel</c></seealso> in
+ <seecom marker="erl#+hmax"><c>+hmax</c></seecom>,
+ <seecom marker="erl#+hmaxk"><c>+hmaxk</c></seecom> and
+ <seecom marker="erl#+hmaxel"><c>+hmaxel</c></seecom> in
<c>erl(1)</c>. It can also be changed at runtime using
- <seealso marker="#system_flag_max_heap_size">
- <c>erlang:system_flag(max_heap_size, MaxHeapSize)</c></seealso>.
+ <seeerl marker="#system_flag_max_heap_size">
+ <c>erlang:system_flag(max_heap_size, MaxHeapSize)</c></seeerl>.
For more details about the <c>max_heap_size</c> process flag,
- see <seealso marker="#process_flag_max_heap_size">
- <c>process_flag(max_heap_size, MaxHeapSize)</c></seealso>.</p>
+ see <seeerl marker="#process_flag_max_heap_size">
+ <c>process_flag(max_heap_size, MaxHeapSize)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_message_queue_data"/>
<c>message_queue_data</c></tag>
<item>
<p>Returns the default value of the <c>message_queue_data</c>
- process flag, which is either <c>off_heap</c> or <c>on_heap</c>.
- This default is set by command-line argument
- <seealso marker="erl#+hmqd"><c>+hmqd</c></seealso> in
- <c>erl(1)</c>. For more information on the
- <c>message_queue_data</c> process flag, see documentation of
- <seealso marker="#process_flag_message_queue_data">
- <c>process_flag(message_queue_data, MQD)</c></seealso>.</p>
+ process flag, which can be either <c>off_heap</c> or <c>on_heap</c>.
+ The default value is set by the command-line argument
+ <seecom marker="erl#+hmqd"><c>+hmqd</c></seecom> in
+ <c>erl(1)</c>. For more information, see the documentation of
+ <seeerl marker="#process_flag_message_queue_data">
+ <c>process_flag(message_queue_data, MQD)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_min_heap_size"/>
<c>min_heap_size</c></tag>
@@ -8354,8 +8887,8 @@ Metadata = #{ pid => pid(),
<item>
<p>Returns a binary containing a string of process and port
information formatted as in Erlang crash dumps. For more
- information, see section <seealso marker="erts:crash_dump">
- How to interpret the Erlang crash dumps</seealso>
+ information, see section <seeguide marker="erts:crash_dump">
+ How to interpret the Erlang crash dumps</seeguide>
in the User's Guide.</p>
</item>
</taglist>
@@ -8389,7 +8922,7 @@ Metadata = #{ pid => pid(),
<p>Returns the maximum number of atoms allowed.
This limit can be increased at startup by passing
command-line flag
- <seealso marker="erts:erl#+t"><c>+t</c></seealso> to
+ <seecom marker="erts:erl#+t"><c>+t</c></seecom> to
<c>erl(1)</c>.
</p>
</item>
@@ -8403,7 +8936,7 @@ Metadata = #{ pid => pid(),
<c>ets_limit</c></tag>
<item>
<p>Returns the limit for number of ETS tables. This limit is
- <seealso marker="stdlib:ets#max_ets_tables">partially obsolete</seealso>
+ <seeerl marker="stdlib:ets#max_ets_tables">partially obsolete</seeerl>
and number of tables are only limited by available memory.</p>
</item>
<tag><marker id="system_info_port_count"/><c>port_count</c></tag>
@@ -8419,7 +8952,7 @@ Metadata = #{ pid => pid(),
<p>Returns the maximum number of simultaneously existing
ports at the local node as an integer. This limit can be
configured at startup by using command-line flag
- <seealso marker="erl#+Q"><c>+Q</c></seealso> in <c>erl(1)</c>.</p>
+ <seecom marker="erl#+Q"><c>+Q</c></seecom> in <c>erl(1)</c>.</p>
</item>
<tag><marker id="system_info_process_count"/>
<c>process_count</c></tag>
@@ -8435,7 +8968,7 @@ Metadata = #{ pid => pid(),
<p>Returns the maximum number of simultaneously existing
processes at the local node. The value is given as an
integer. This limit can be configured at startup by using
- command-line flag <seealso marker="erl#+P"><c>+P</c></seealso>
+ command-line flag <seecom marker="erl#+P"><c>+P</c></seecom>
in <c>erl(1)</c>.</p>
</item>
</taglist>
@@ -8460,20 +8993,20 @@ Metadata = #{ pid => pid(),
<taglist>
<tag><marker id="system_info_end_time"/><c>end_time</c></tag>
<item>
- <p>The last <seealso marker="#monotonic_time/0">Erlang monotonic
- time</seealso> in <c>native</c>
- <seealso marker="#type_time_unit">time unit</seealso> that
+ <p>The last <seemfa marker="#monotonic_time/0">Erlang monotonic
+ time</seemfa> in <c>native</c>
+ <seeerl marker="#type_time_unit">time unit</seeerl> that
can be represented internally in the current Erlang runtime system
instance. The time between the
- <seealso marker="#system_info_start_time">start time</seealso> and
+ <seeerl marker="#system_info_start_time">start time</seeerl> and
the end time is at least a quarter of a millennium.</p>
</item>
<tag><marker id="system_info_os_monotonic_time_source"/>
<c>os_monotonic_time_source</c></tag>
<item>
<p>Returns a list containing information about the source of
- <seealso marker="erts:time_correction#OS_Monotonic_Time">OS
- monotonic time</seealso> that is used by the runtime system.</p>
+ <seeguide marker="erts:time_correction#OS_Monotonic_Time">OS
+ monotonic time</seeguide> that is used by the runtime system.</p>
<p>If <c>[]</c> is returned, no OS monotonic time is
available. The list contains two-tuples with <c>Key</c>s
as first element, and <c>Value</c>s as second element. The
@@ -8494,8 +9027,8 @@ Metadata = #{ pid => pid(),
</item>
<tag><c>{resolution, OsMonotonicTimeResolution}</c></tag>
<item><p>Highest possible
- <seealso marker="time_correction#Time_Resolution">
- resolution</seealso>
+ <seeguide marker="time_correction#Time_Resolution">
+ resolution</seeguide>
of current OS monotonic time source as parts per
second. If no resolution information can be retrieved
from the OS, <c>OsMonotonicTimeResolution</c> is
@@ -8504,10 +9037,10 @@ Metadata = #{ pid => pid(),
resolution can be lower than
<c>OsMonotonicTimeResolution</c>. Notice that
the resolution does not say anything about the
- <seealso marker="time_correction#Time_Accuracy">
- accuracy</seealso> or whether the
- <seealso marker="time_correction#Time_Precision">
- precision</seealso> aligns with the resolution. You do,
+ <seeguide marker="time_correction#Time_Accuracy">
+ accuracy</seeguide> or whether the
+ <seeguide marker="time_correction#Time_Precision">
+ precision</seeguide> aligns with the resolution. You do,
however, know that the precision is not better than
<c>OsMonotonicTimeResolution</c>.</p>
</item>
@@ -8529,7 +9062,7 @@ Metadata = #{ pid => pid(),
<tag><c>{time, OsMonotonicTime}</c></tag>
<item><p><c>OsMonotonicTime</c> equals current OS
monotonic time in <c>native</c>
- <seealso marker="#type_time_unit">time unit</seealso>.</p>
+ <seeerl marker="#type_time_unit">time unit</seeerl>.</p>
</item>
</taglist>
</item>
@@ -8537,8 +9070,8 @@ Metadata = #{ pid => pid(),
<c>os_system_time_source</c></tag>
<item>
<p>Returns a list containing information about the source of
- <seealso marker="erts:time_correction#OS_System_Time">OS
- system time</seealso> that is used by the runtime system.</p>
+ <seeguide marker="erts:time_correction#OS_System_Time">OS
+ system time</seeguide> that is used by the runtime system.</p>
<p>The list contains two-tuples with <c>Key</c>s
as first element, and <c>Value</c>s as second element. The
order of these tuples is undefined. The following
@@ -8556,8 +9089,8 @@ Metadata = #{ pid => pid(),
</item>
<tag><c>{resolution, OsSystemTimeResolution}</c></tag>
<item><p>Highest possible
- <seealso marker="time_correction#Time_Resolution">
- resolution</seealso>
+ <seeguide marker="time_correction#Time_Resolution">
+ resolution</seeguide>
of current OS system time source as parts per
second. If no resolution information can be retrieved
from the OS, <c>OsSystemTimeResolution</c> is
@@ -8566,10 +9099,10 @@ Metadata = #{ pid => pid(),
resolution can be lower than
<c>OsSystemTimeResolution</c>. Notice that
the resolution does not say anything about the
- <seealso marker="time_correction#Time_Accuracy">
- accuracy</seealso> or whether the
- <seealso marker="time_correction#Time_Precision">
- precision</seealso> do align with the resolution. You do,
+ <seeguide marker="time_correction#Time_Accuracy">
+ accuracy</seeguide> or whether the
+ <seeguide marker="time_correction#Time_Precision">
+ precision</seeguide> do align with the resolution. You do,
however, know that the precision is not better than
<c>OsSystemTimeResolution</c>.</p>
</item>
@@ -8583,25 +9116,25 @@ Metadata = #{ pid => pid(),
<tag><c>{time, OsSystemTime}</c></tag>
<item><p><c>OsSystemTime</c> equals current OS
system time in <c>native</c>
- <seealso marker="#type_time_unit">time unit</seealso>.</p>
+ <seeerl marker="#type_time_unit">time unit</seeerl>.</p>
</item>
</taglist>
</item>
<tag><marker id="system_info_start_time"/><c>start_time</c></tag>
<item>
- <p>The <seealso marker="#monotonic_time/0">Erlang monotonic
- time</seealso> in <c>native</c>
- <seealso marker="#type_time_unit">time unit</seealso> at the
+ <p>The <seemfa marker="#monotonic_time/0">Erlang monotonic
+ time</seemfa> in <c>native</c>
+ <seeerl marker="#type_time_unit">time unit</seeerl> at the
time when current Erlang runtime system instance started.</p>
- <p>See also <seealso marker="#system_info_end_time">
- <c>erlang:system_info(end_time)</c></seealso>.</p>
+ <p>See also <seeerl marker="#system_info_end_time">
+ <c>erlang:system_info(end_time)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_time_correction"/>
<c>time_correction</c></tag>
<item>
<p>Returns a boolean value indicating whether
- <seealso marker="time_correction#Time_Correction">
- time correction</seealso> is enabled or not.</p>
+ <seeguide marker="time_correction#Time_Correction">
+ time correction</seeguide> is enabled or not.</p>
</item>
<tag><marker id="system_info_time_offset"/>
<c>time_offset</c></tag>
@@ -8613,24 +9146,24 @@ Metadata = #{ pid => pid(),
<p>The time offset is preliminary, and will be changed
and finalized later. The preliminary time offset
is used during the preliminary phase of the
- <seealso marker="time_correction#Single_Time_Warp_Mode">
- single time warp mode</seealso>.</p>
+ <seeguide marker="time_correction#Single_Time_Warp_Mode">
+ single time warp mode</seeguide>.</p>
</item>
<tag><c>final</c></tag>
<item>
<p>The time offset is final. This either because
- <seealso marker="time_correction#No_Time_Warp_Mode">
- no time warp mode</seealso> is used, or because the time
+ <seeguide marker="time_correction#No_Time_Warp_Mode">
+ no time warp mode</seeguide> is used, or because the time
offset have been finalized when
- <seealso marker="time_correction#Single_Time_Warp_Mode">
- single time warp mode</seealso> is used.</p>
+ <seeguide marker="time_correction#Single_Time_Warp_Mode">
+ single time warp mode</seeguide> is used.</p>
</item>
<tag><c>volatile</c></tag>
<item>
<p>The time offset is volatile. That is, it can
change at any time. This is because
- <seealso marker="time_correction#Multi_Time_Warp_Mode">
- multi-time warp mode</seealso> is used.</p>
+ <seeguide marker="time_correction#Multi_Time_Warp_Mode">
+ multi-time warp mode</seeguide> is used.</p>
</item>
</taglist>
</item>
@@ -8638,20 +9171,20 @@ Metadata = #{ pid => pid(),
<c>time_warp_mode</c></tag>
<item>
<p>Returns a value identifying the
- <seealso marker="time_correction#Time_Warp_Modes">
- time warp mode</seealso> that is used:</p>
+ <seeguide marker="time_correction#Time_Warp_Modes">
+ time warp mode</seeguide> that is used:</p>
<taglist>
<tag><c>no_time_warp</c></tag>
- <item>The <seealso marker="time_correction#No_Time_Warp_Mode">
- no time warp mode</seealso> is used.
+ <item>The <seeguide marker="time_correction#No_Time_Warp_Mode">
+ no time warp mode</seeguide> is used.
</item>
<tag><c>single_time_warp</c></tag>
- <item>The <seealso marker="time_correction#Single_Time_Warp_Mode">
- single time warp mode</seealso> is used.
+ <item>The <seeguide marker="time_correction#Single_Time_Warp_Mode">
+ single time warp mode</seeguide> is used.
</item>
<tag><c>multi_time_warp</c></tag>
- <item>The <seealso marker="time_correction#Multi_Time_Warp_Mode">
- multi-time warp mode</seealso> is used.
+ <item>The <seeguide marker="time_correction#Multi_Time_Warp_Mode">
+ multi-time warp mode</seeguide> is used.
</item>
</taglist>
</item>
@@ -8661,10 +9194,10 @@ Metadata = #{ pid => pid(),
<p>Returns whether a pre ERTS 7.0 backwards compatible
compensation for sudden changes of system time is <c>enabled</c>
or <c>disabled</c>. Such compensation is <c>enabled</c> when the
- <seealso marker="#system_info_time_offset">time offset</seealso>
+ <seeerl marker="#system_info_time_offset">time offset</seeerl>
is <c>final</c>, and
- <seealso marker="#system_info_time_correction">
- time correction</seealso> is enabled.</p>
+ <seeerl marker="#system_info_time_correction">
+ time correction</seeerl> is enabled.</p>
</item>
</taglist>
</desc>
@@ -8705,24 +9238,24 @@ Metadata = #{ pid => pid(),
can be changed at any time. The number of dirty CPU
schedulers can be set at startup by passing
command-line flag
- <seealso marker="erts:erl#+SDcpu"><c>+SDcpu</c></seealso> or
- <seealso marker="erts:erl#+SDPcpu"><c>+SDPcpu</c></seealso> in
+ <seecom marker="erts:erl#+SDcpu"><c>+SDcpu</c></seecom> or
+ <seecom marker="erts:erl#+SDPcpu"><c>+SDPcpu</c></seecom> in
<c>erl(1)</c>.</p>
<p>See also
- <seealso marker="#system_flag_dirty_cpu_schedulers_online">
+ <seeerl marker="#system_flag_dirty_cpu_schedulers_online">
<c>erlang:system_flag(dirty_cpu_schedulers_online,
- DirtyCPUSchedulersOnline)</c></seealso>,
- <seealso marker="#system_info_dirty_cpu_schedulers_online">
- <c>erlang:system_info(dirty_cpu_schedulers_online)</c></seealso>,
- <seealso marker="#system_info_dirty_io_schedulers">
- <c>erlang:system_info(dirty_io_schedulers)</c></seealso>,
- <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso>,
- <seealso marker="#system_info_schedulers_online">
- <c>erlang:system_info(schedulers_online)</c></seealso>, and
- <seealso marker="#system_flag_schedulers_online">
+ DirtyCPUSchedulersOnline)</c></seeerl>,
+ <seeerl marker="#system_info_dirty_cpu_schedulers_online">
+ <c>erlang:system_info(dirty_cpu_schedulers_online)</c></seeerl>,
+ <seeerl marker="#system_info_dirty_io_schedulers">
+ <c>erlang:system_info(dirty_io_schedulers)</c></seeerl>,
+ <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl>,
+ <seeerl marker="#system_info_schedulers_online">
+ <c>erlang:system_info(schedulers_online)</c></seeerl>, and
+ <seeerl marker="#system_flag_schedulers_online">
<c>erlang:system_flag(schedulers_online,
- SchedulersOnline)</c></seealso>.</p>
+ SchedulersOnline)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_dirty_cpu_schedulers_online"/>
<c>dirty_cpu_schedulers_online</c></tag>
@@ -8735,18 +9268,18 @@ Metadata = #{ pid => pid(),
<c>erlang:system_info(schedulers_online)</c>.</p>
<p>The number of dirty CPU schedulers online can be set at
startup by passing command-line flag
- <seealso marker="erts:erl#+SDcpu"><c>+SDcpu</c></seealso> in
+ <seecom marker="erts:erl#+SDcpu"><c>+SDcpu</c></seecom> in
<c>erl(1)</c>.</p>
<p>For more information, see
- <seealso marker="#system_info_dirty_cpu_schedulers">
- <c>erlang:system_info(dirty_cpu_schedulers)</c></seealso>,
- <seealso marker="#system_info_dirty_io_schedulers">
- <c>erlang:system_info(dirty_io_schedulers)</c></seealso>,
- <seealso marker="#system_info_schedulers_online">
- <c>erlang:system_info(schedulers_online)</c></seealso>, and
- <seealso marker="#system_flag_dirty_cpu_schedulers_online">
+ <seeerl marker="#system_info_dirty_cpu_schedulers">
+ <c>erlang:system_info(dirty_cpu_schedulers)</c></seeerl>,
+ <seeerl marker="#system_info_dirty_io_schedulers">
+ <c>erlang:system_info(dirty_io_schedulers)</c></seeerl>,
+ <seeerl marker="#system_info_schedulers_online">
+ <c>erlang:system_info(schedulers_online)</c></seeerl>, and
+ <seeerl marker="#system_flag_dirty_cpu_schedulers_online">
<c>erlang:system_flag(dirty_cpu_schedulers_online,
- DirtyCPUSchedulersOnline)</c></seealso>.</p>
+ DirtyCPUSchedulersOnline)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_dirty_io_schedulers"/>
<c>dirty_io_schedulers</c></tag>
@@ -8756,16 +9289,16 @@ Metadata = #{ pid => pid(),
such as NIFs and linked-in driver code, which cannot be
managed cleanly by the normal emulator schedulers.</p>
<p>This value can be set at startup by passing command-line
- argument <seealso marker="erts:erl#+SDio"><c>+SDio</c></seealso>
+ argument <seecom marker="erts:erl#+SDio"><c>+SDio</c></seecom>
in <c>erl(1)</c>.</p>
<p>For more information, see
- <seealso marker="#system_info_dirty_cpu_schedulers">
- <c>erlang:system_info(dirty_cpu_schedulers)</c></seealso>,
- <seealso marker="#system_info_dirty_cpu_schedulers_online">
- <c>erlang:system_info(dirty_cpu_schedulers_online)</c></seealso>,
- and <seealso marker="#system_flag_dirty_cpu_schedulers_online">
+ <seeerl marker="#system_info_dirty_cpu_schedulers">
+ <c>erlang:system_info(dirty_cpu_schedulers)</c></seeerl>,
+ <seeerl marker="#system_info_dirty_cpu_schedulers_online">
+ <c>erlang:system_info(dirty_cpu_schedulers_online)</c></seeerl>,
+ and <seeerl marker="#system_flag_dirty_cpu_schedulers_online">
<c>erlang:system_flag(dirty_cpu_schedulers_online,
- DirtyCPUSchedulersOnline)</c></seealso>.</p>
+ DirtyCPUSchedulersOnline)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_multi_scheduling"/>
<c>multi_scheduling</c></tag>
@@ -8800,14 +9333,14 @@ Metadata = #{ pid => pid(),
</item>
</taglist>
<p>See also
- <seealso marker="#system_flag_multi_scheduling">
- <c>erlang:system_flag(multi_scheduling, BlockState)</c></seealso>,
- <seealso marker="#system_info_multi_scheduling_blockers">
- <c>erlang:system_info(multi_scheduling_blockers)</c></seealso>,
- <seealso marker="#system_info_normal_multi_scheduling_blockers">
- <c>erlang:system_info(normal_multi_scheduling_blockers)</c></seealso>,
- and <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso>.</p>
+ <seeerl marker="#system_flag_multi_scheduling">
+ <c>erlang:system_flag(multi_scheduling, BlockState)</c></seeerl>,
+ <seeerl marker="#system_info_multi_scheduling_blockers">
+ <c>erlang:system_info(multi_scheduling_blockers)</c></seeerl>,
+ <seeerl marker="#system_info_normal_multi_scheduling_blockers">
+ <c>erlang:system_info(normal_multi_scheduling_blockers)</c></seeerl>,
+ and <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_multi_scheduling_blockers"/>
<c>multi_scheduling_blockers</c></tag>
@@ -8820,14 +9353,14 @@ Metadata = #{ pid => pid(),
only once in the list, even if the corresponding
process has blocked multiple times.</p>
<p>See also
- <seealso marker="#system_flag_multi_scheduling">
- <c>erlang:system_flag(multi_scheduling, BlockState)</c></seealso>,
- <seealso marker="#system_info_multi_scheduling">
- <c>erlang:system_info(multi_scheduling)</c></seealso>,
- <seealso marker="#system_info_normal_multi_scheduling_blockers">
- <c>erlang:system_info(normal_multi_scheduling_blockers)</c></seealso>,
- and <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso>.</p>
+ <seeerl marker="#system_flag_multi_scheduling">
+ <c>erlang:system_flag(multi_scheduling, BlockState)</c></seeerl>,
+ <seeerl marker="#system_info_multi_scheduling">
+ <c>erlang:system_info(multi_scheduling)</c></seeerl>,
+ <seeerl marker="#system_info_normal_multi_scheduling_blockers">
+ <c>erlang:system_info(normal_multi_scheduling_blockers)</c></seeerl>,
+ and <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_normal_multi_scheduling_blockers"/>
<c>normal_multi_scheduling_blockers</c></tag>
@@ -8840,14 +9373,14 @@ Metadata = #{ pid => pid(),
A <c><anno>Pid</anno></c> occurs only once in the list, even if
the corresponding process has blocked multiple times.</p>
<p>See also
- <seealso marker="#system_flag_multi_scheduling">
- <c>erlang:system_flag(multi_scheduling, BlockState)</c></seealso>,
- <seealso marker="#system_info_multi_scheduling">
- <c>erlang:system_info(multi_scheduling)</c></seealso>,
- <seealso marker="#system_info_multi_scheduling_blockers">
- <c>erlang:system_info(multi_scheduling_blockers)</c></seealso>,
- and <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso>.</p>
+ <seeerl marker="#system_flag_multi_scheduling">
+ <c>erlang:system_flag(multi_scheduling, BlockState)</c></seeerl>,
+ <seeerl marker="#system_info_multi_scheduling">
+ <c>erlang:system_info(multi_scheduling)</c></seeerl>,
+ <seeerl marker="#system_info_multi_scheduling_blockers">
+ <c>erlang:system_info(multi_scheduling_blockers)</c></seeerl>,
+ and <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_scheduler_bind_type"/>
<c>scheduler_bind_type</c></tag>
@@ -8857,13 +9390,13 @@ Metadata = #{ pid => pid(),
<p>Notice that although a user has requested
schedulers to be bound, they can silently have failed
to bind. To inspect the scheduler bindings, call
- <seealso marker="#system_info_scheduler_bindings">
- <c>erlang:system_info(scheduler_bindings)</c></seealso>.</p>
+ <seeerl marker="#system_info_scheduler_bindings">
+ <c>erlang:system_info(scheduler_bindings)</c></seeerl>.</p>
<p>For more information, see command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt</c></seecom>
in <c>erl(1)</c> and
- <seealso marker="#system_info_scheduler_bindings">
- <c>erlang:system_info(scheduler_bindings)</c></seealso>.</p>
+ <seeerl marker="#system_info_scheduler_bindings">
+ <c>erlang:system_info(scheduler_bindings)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_scheduler_bindings"/>
<c>scheduler_bindings</c></tag>
@@ -8871,8 +9404,8 @@ Metadata = #{ pid => pid(),
<p>Returns information about the currently used scheduler
bindings.</p>
<p>A tuple of a size equal to
- <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso>
+ <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl>
is returned. The tuple elements are integers
or the atom <c>unbound</c>. Logical processor identifiers
are represented as integers. The <c>N</c>th
@@ -8886,10 +9419,10 @@ Metadata = #{ pid => pid(),
<p>Notice that only schedulers online can be bound to logical
processors.</p>
<p>For more information, see command-line argument
- <seealso marker="erts:erl#+sbt"><c>+sbt</c></seealso>
+ <seecom marker="erts:erl#+sbt"><c>+sbt</c></seecom>
in <c>erl(1)</c> and
- <seealso marker="#system_info_schedulers_online">
- <c>erlang:system_info(schedulers_online)</c></seealso>.</p>
+ <seeerl marker="#system_info_schedulers_online">
+ <c>erlang:system_info(schedulers_online)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_scheduler_id"/>
<c>scheduler_id</c></tag>
@@ -8900,8 +9433,8 @@ Metadata = #{ pid => pid(),
where <c><![CDATA[1 <= SchedulerId <=
erlang:system_info(schedulers)]]></c>.</p>
<p>See also
- <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso>.</p>
+ <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_schedulers"/>
<c>schedulers</c></tag>
@@ -8915,21 +9448,21 @@ Metadata = #{ pid => pid(),
However, the number of schedulers online can
be changed at any time.</p>
<p>See also
- <seealso marker="#system_flag_schedulers_online">
+ <seeerl marker="#system_flag_schedulers_online">
<c>erlang:system_flag(schedulers_online,
- SchedulersOnline)</c></seealso>,
- <seealso marker="#system_info_schedulers_online">
- <c>erlang:system_info(schedulers_online)</c></seealso>,
- <seealso marker="#system_info_scheduler_id">
- <c>erlang:system_info(scheduler_id)</c></seealso>,
- <seealso marker="#system_flag_multi_scheduling">
- <c>erlang:system_flag(multi_scheduling, BlockState)</c></seealso>,
- <seealso marker="#system_info_multi_scheduling">
- <c>erlang:system_info(multi_scheduling)</c></seealso>,
- <seealso marker="#system_info_normal_multi_scheduling_blockers">
- <c>erlang:system_info(normal_multi_scheduling_blockers)</c></seealso>
- and <seealso marker="#system_info_multi_scheduling_blockers">
- <c>erlang:system_info(multi_scheduling_blockers)</c></seealso>.
+ SchedulersOnline)</c></seeerl>,
+ <seeerl marker="#system_info_schedulers_online">
+ <c>erlang:system_info(schedulers_online)</c></seeerl>,
+ <seeerl marker="#system_info_scheduler_id">
+ <c>erlang:system_info(scheduler_id)</c></seeerl>,
+ <seeerl marker="#system_flag_multi_scheduling">
+ <c>erlang:system_flag(multi_scheduling, BlockState)</c></seeerl>,
+ <seeerl marker="#system_info_multi_scheduling">
+ <c>erlang:system_info(multi_scheduling)</c></seeerl>,
+ <seeerl marker="#system_info_normal_multi_scheduling_blockers">
+ <c>erlang:system_info(normal_multi_scheduling_blockers)</c></seeerl>
+ and <seeerl marker="#system_info_multi_scheduling_blockers">
+ <c>erlang:system_info(multi_scheduling_blockers)</c></seeerl>.
</p>
</item>
<tag><marker id="system_info_schedulers_online"/>
@@ -8940,11 +9473,11 @@ Metadata = #{ pid => pid(),
<c><![CDATA[1 <= SchedulerId <=
erlang:system_info(schedulers_online)]]></c>.</p>
<p>For more information, see
- <seealso marker="#system_info_schedulers">
- <c>erlang:system_info(schedulers)</c></seealso> and
- <seealso marker="#system_flag_schedulers_online">
+ <seeerl marker="#system_info_schedulers">
+ <c>erlang:system_info(schedulers)</c></seeerl> and
+ <seeerl marker="#system_flag_schedulers_online">
<c>erlang:system_flag(schedulers_online,
- SchedulersOnline)</c></seealso>.</p>
+ SchedulersOnline)</c></seeerl>.</p>
</item>
<tag><marker id="system_info_smp_support"/>
<c>smp_support</c></tag>
@@ -8962,8 +9495,8 @@ Metadata = #{ pid => pid(),
<marker id="system_info_thread_pool_size"></marker>
<p>Returns the number of async threads in the async thread
pool used for asynchronous driver calls
- (<seealso marker="erts:erl_driver#driver_async">
- <c>erl_driver:driver_async()</c></seealso>).
+ (<seecref marker="erts:erl_driver#driver_async">
+ <c>erl_driver:driver_async()</c></seecref>).
The value is given as an integer.</p>
</item>
</taglist>
@@ -9002,7 +9535,7 @@ Metadata = #{ pid => pid(),
<p>Returns the amount of time in seconds garbage collection
of an entry in a node table is delayed. This limit can be set
on startup by passing command-line flag
- <seealso marker="erts:erl#+zdntgc"><c>+zdntgc</c></seealso>
+ <seecom marker="erts:erl#+zdntgc"><c>+zdntgc</c></seecom>
to <c>erl(1)</c>. For more information, see the documentation of
the command-line flag.</p>
</item>
@@ -9011,8 +9544,8 @@ Metadata = #{ pid => pid(),
<item>
<p>Returns a binary containing a string of distribution
information formatted as in Erlang crash dumps. For more
- information, see section <seealso marker="erts:crash_dump">
- How to interpret the Erlang crash dumps</seealso>
+ information, see section <seeguide marker="erts:crash_dump">
+ How to interpret the Erlang crash dumps</seeguide>
in the User's Guide.</p>
</item>
<tag><marker id="system_info_dist_buf_busy_limit"/>
@@ -9021,7 +9554,7 @@ Metadata = #{ pid => pid(),
<p>Returns the value of the distribution buffer busy limit
in bytes. This limit can be set at startup by passing
command-line flag
- <seealso marker="erts:erl#+zdbbl"><c>+zdbbl</c></seealso>
+ <seecom marker="erts:erl#+zdbbl"><c>+zdbbl</c></seecom>
to <c>erl(1)</c>.</p>
</item>
<tag><marker id="system_info_dist_ctrl"/>
@@ -9120,6 +9653,7 @@ Metadata = #{ pid => pid(),
<name name="system_info" arity="1" clause_i="75" since=""/> <!-- version -->
<name name="system_info" arity="1" clause_i="76" since=""/> <!-- wordsize -->
<!-- <name name="system_info" arity="1" clause_i="77"/> overview -->
+ <!-- When adding any entry, make sure to update the overview clause_i -->
<fsummary>Information about the system.</fsummary>
<desc>
<marker id="system_info_misc_tags"/>
@@ -9162,7 +9696,7 @@ Metadata = #{ pid => pid(),
Erlang/OTP release that the current emulator has been
set to be backward compatible with. The compatibility
mode can be configured at startup by using command-line flag
- <seealso marker="erts:erl#compat_rel"><c>+R</c></seealso> in
+ <seecom marker="erts:erl#compat_rel"><c>+R</c></seecom> in
<c>erl(1)</c>.</p>
</item>
<tag><marker id="system_info_debug_compiled"/>
@@ -9176,8 +9710,8 @@ Metadata = #{ pid => pid(),
<item>
<p>Returns a string containing the Erlang driver version
used by the runtime system. It has the form
- <seealso marker="erts:erl_driver#version_management">
- "&lt;major ver&gt;.&lt;minor ver&gt;"</seealso>.</p>
+ <seecref marker="erts:erl_driver#version_management">
+ "&lt;major ver&gt;.&lt;minor ver&gt;"</seecref>.</p>
</item>
<tag><marker id="system_info_dynamic_trace"/>
<c>dynamic_trace</c></tag>
@@ -9189,8 +9723,8 @@ Metadata = #{ pid => pid(),
The other return values indicate a custom configuration
(for example, <c>./configure --with-dynamic-trace=dtrace</c>).
For more information about dynamic tracing, see
- <seealso marker="runtime_tools:dyntrace">
- <c>dyntrace(3)</c></seealso> manual page and the
+ <seeerl marker="runtime_tools:dyntrace">
+ <c>dyntrace(3)</c></seeerl> manual page and the
<c>README.dtrace</c>/<c>README.systemtap</c> files in the
Erlang source code top directory.</p>
</item>
@@ -9210,8 +9744,8 @@ Metadata = #{ pid => pid(),
<p>Returns a binary containing a string of miscellaneous
system information formatted as in Erlang crash dumps.
For more information, see section
- <seealso marker="erts:crash_dump">
- How to interpret the Erlang crash dumps</seealso>
+ <seeguide marker="erts:crash_dump">
+ How to interpret the Erlang crash dumps</seeguide>
in the User's Guide.</p>
</item>
<tag><marker id="system_info_kernel_poll"/>
@@ -9226,8 +9760,8 @@ Metadata = #{ pid => pid(),
<p>Returns a binary containing a string of loaded module
information formatted as in Erlang crash dumps. For more
information, see section
- <seealso marker="erts:crash_dump">How to interpret the Erlang
- crash dumps</seealso> in the User's Guide.</p>
+ <seeguide marker="erts:crash_dump">How to interpret the Erlang
+ crash dumps</seeguide> in the User's Guide.</p>
</item>
<tag><marker id="system_info_machine"/>
<c>machine</c></tag>
@@ -9241,7 +9775,7 @@ Metadata = #{ pid => pid(),
modified timing is enabled, otherwise <c>undefined</c>.
For more information about modified timing, see
command-line flag
- <seealso marker="erts:erl#+T"><c>+T</c></seealso>
+ <seecom marker="erts:erl#+T"><c>+T</c></seecom>
in <c>erl(1)</c></p>
</item>
<tag><marker id="system_info_nif_version"/>
@@ -9264,15 +9798,15 @@ Metadata = #{ pid => pid(),
version. This is because the exact OTP version in the general case
is difficult to determine. For more information, see the
description of versions in
- <seealso marker="doc/system_principles:versions">
- System principles</seealso> in System Documentation.</p>
+ <seeguide marker="system/system_principles:versions">
+ System principles</seeguide> in System Documentation.</p>
</item>
<tag><marker id="system_info_port_parallelism"/>
<c>port_parallelism</c></tag>
<item>
<p>Returns the default port parallelism scheduling hint used.
For more information, see command-line argument
- <seealso marker="erl#+spp"><c>+spp</c></seealso>
+ <seecom marker="erl#+spp"><c>+spp</c></seecom>
in <c>erl(1)</c>.</p>
</item>
<tag><marker id="system_info_system_architecture"/>
@@ -9285,7 +9819,7 @@ Metadata = #{ pid => pid(),
<c>system_logger</c></tag>
<item>
<p>Returns the current <c>system_logger</c> as set by
- <seealso marker="#system_flag/2"><c>erlang:system_flag(system_logger, _)</c></seealso>.</p>
+ <seemfa marker="#system_flag/2"><c>erlang:system_flag(system_logger, _)</c></seemfa>.</p>
</item>
<tag><marker id="system_info_system_version"/>
<c>system_version</c></tag>
@@ -9298,8 +9832,8 @@ Metadata = #{ pid => pid(),
<item>
<p>Returns the value of the node trace control word. For
more information, see function <c>get_tcw</c> in section
- <seealso marker="erts:match_spec#get_tcw">
- Match Specifications in Erlang</seealso> in the User's Guide.</p>
+ <seeguide marker="erts:match_spec#get_tcw">
+ Match Specifications in Erlang</seeguide> in the User's Guide.</p>
</item>
<tag><marker id="system_info_version"/>
<c>version</c></tag>
@@ -9339,8 +9873,8 @@ Metadata = #{ pid => pid(),
<type name="system_monitor_option"/>
<desc>
<p>Returns the current system monitoring settings set by
- <seealso marker="#system_monitor/2">
- <c>erlang:system_monitor/2</c></seealso>
+ <seemfa marker="#system_monitor/2">
+ <c>erlang:system_monitor/2</c></seemfa>
as <c>{<anno>MonitorPid</anno>, <anno>Options</anno>}</c>,
or <c>undefined</c> if no settings exist. The order of the
options can be different from the one that was set.</p>
@@ -9356,12 +9890,12 @@ Metadata = #{ pid => pid(),
system performance monitoring settings are cleared.</p>
<p>Calling the function with <c>{<anno>MonitorPid</anno>,
<anno>Options</anno>}</c> as argument is the same as calling
- <seealso marker="#system_monitor/2">
+ <seemfa marker="#system_monitor/2">
<c>erlang:system_monitor(<anno>MonitorPid</anno>,
- <anno>Options</anno>)</c></seealso>.</p>
+ <anno>Options</anno>)</c></seemfa>.</p>
<p>Returns the previous system monitor settings just like
- <seealso marker="#system_monitor/0">
- <c>erlang:system_monitor/0</c></seealso>.</p>
+ <seemfa marker="#system_monitor/0">
+ <c>erlang:system_monitor/0</c></seemfa>.</p>
</desc>
</func>
@@ -9390,8 +9924,8 @@ Metadata = #{ pid => pid(),
<c>stack_size</c>, <c>mbuf_size</c>, <c>old_heap_size</c>,
and <c>old_heap_block_size</c>. These tuples are
explained in the description of trace message
- <seealso marker="#gc_minor_start"><c>gc_minor_start</c></seealso>
- (see <seealso marker="#trace/3"><c>erlang:trace/3</c></seealso>).
+ <seeerl marker="#gc_minor_start"><c>gc_minor_start</c></seeerl>
+ (see <seemfa marker="#trace/3"><c>erlang:trace/3</c></seemfa>).
New tuples can be added, and the order of the tuples in
the <c>Info</c> list can be changed at any time without
prior notice.</p>
@@ -9452,8 +9986,8 @@ Metadata = #{ pid => pid(),
all memory blocks allocated for all heap generations after
a garbage collection is equal to or higher than <c>Size</c>.</p>
<p>When a process is killed by
- <seealso marker="#process_flag_max_heap_size">
- <c>max_heap_size</c></seealso>, it is killed before the
+ <seeerl marker="#process_flag_max_heap_size">
+ <c>max_heap_size</c></seeerl>, it is killed before the
garbage collection is complete and thus no large heap message
is sent.</p>
</item>
@@ -9477,8 +10011,8 @@ Metadata = #{ pid => pid(),
</item>
</taglist>
<p>Returns the previous system monitor settings just like
- <seealso marker="#system_monitor/0">
- <c>erlang:system_monitor/0</c></seealso>.</p>
+ <seemfa marker="#system_monitor/0">
+ <c>erlang:system_monitor/0</c></seemfa>.</p>
<p>The arguments to <c>system_monitor/2</c> specifies how all
system monitoring on the node should be done, not how it should be
changed. This means only one process at a time
@@ -9515,8 +10049,8 @@ Metadata = #{ pid => pid(),
<type name="system_profile_option"/>
<desc>
<p>Returns the current system profiling settings set by
- <seealso marker="#system_profile/2">
- <c>erlang:system_profile/2</c></seealso>
+ <seemfa marker="#system_profile/2">
+ <c>erlang:system_profile/2</c></seemfa>
as <c>{<anno>ProfilerPid</anno>, <anno>Options</anno>}</c>,
or <c>undefined</c> if there
are no settings. The order of the options can be different
@@ -9545,8 +10079,8 @@ Metadata = #{ pid => pid(),
<tag><c>monotonic_timestamp</c></tag>
<item>
<p>Time stamps in profile messages use
- <seealso marker="time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso>. The time stamp (Ts) has the same
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide>. The time stamp (Ts) has the same
format and value as produced by
<c>erlang:monotonic_time(nanosecond)</c>.</p>
</item>
@@ -9573,8 +10107,8 @@ Metadata = #{ pid => pid(),
<tag><c>strict_monotonic_timestamp</c></tag>
<item>
<p>Time stamps in profile messages consist of
- <seealso marker="time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso> and a monotonically increasing
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide> and a monotonically increasing
integer. The time stamp (Ts) has the same format and value
as produced by <c>{erlang:monotonic_time(nanosecond),
erlang:unique_integer([monotonic])}</c>.</p>
@@ -9586,7 +10120,7 @@ Metadata = #{ pid => pid(),
<c>erlang:now()</c>. This is also the default if no
time stamp flag is specified. If <c>cpu_timestamp</c> has
been enabled through
- <seealso marker="#trace/3"><c>erlang:trace/3</c></seealso>,
+ <seemfa marker="#trace/3"><c>erlang:trace/3</c></seemfa>,
this also effects the time stamp produced in profiling messages
when flag <c>timestamp</c> is enabled.</p>
</item>
@@ -9603,19 +10137,19 @@ Metadata = #{ pid => pid(),
<fsummary>Current Erlang system time.</fsummary>
<desc>
<p>Returns current
- <seealso marker="time_correction#Erlang_System_Time">
- Erlang system time</seealso> in <c>native</c>
- <seealso marker="#type_time_unit">time unit</seealso>.</p>
+ <seeguide marker="time_correction#Erlang_System_Time">
+ Erlang system time</seeguide> in <c>native</c>
+ <seeerl marker="#type_time_unit">time unit</seeerl>.</p>
<p>Calling <c>erlang:system_time()</c> is equivalent to
- <seealso marker="#monotonic_time/0">
- <c>erlang:monotonic_time()</c></seealso><c> +
- </c><seealso marker="#time_offset/0">
- <c>erlang:time_offset()</c></seealso>.</p>
+ <seemfa marker="#monotonic_time/0">
+ <c>erlang:monotonic_time()</c></seemfa><c> +
+ </c><seemfa marker="#time_offset/0">
+ <c>erlang:time_offset()</c></seemfa>.</p>
<note>
<p>This time is <em>not</em> a monotonically increasing time
in the general case. For more information, see the documentation of
- <seealso marker="time_correction#Time_Warp_Modes">
- time warp modes</seealso> in the User's Guide.</p>
+ <seeguide marker="time_correction#Time_Warp_Modes">
+ time warp modes</seeguide> in the User's Guide.</p>
</note>
</desc>
</func>
@@ -9625,19 +10159,19 @@ Metadata = #{ pid => pid(),
<fsummary>Current Erlang system time.</fsummary>
<desc>
<p>Returns current
- <seealso marker="time_correction#Erlang_System_Time">
- Erlang system time</seealso>
+ <seeguide marker="time_correction#Erlang_System_Time">
+ Erlang system time</seeguide>
converted into the <c><anno>Unit</anno></c> passed as argument.</p>
<p>Calling <c>erlang:system_time(<anno>Unit</anno>)</c> is equivalent
- to <seealso marker="#convert_time_unit/3">
- <c>erlang:convert_time_unit</c></seealso><c>(</c><seealso
- marker="#system_time/0"><c>erlang:system_time()</c></seealso><c>,
+ to <seemfa marker="#convert_time_unit/3">
+ <c>erlang:convert_time_unit</c></seemfa><c>(</c><seemfa
+ marker="#system_time/0"><c>erlang:system_time()</c></seemfa><c>,
native, <anno>Unit</anno>)</c>.</p>
<note>
<p>This time is <em>not</em> a monotonically increasing time
in the general case. For more information, see the documentation of
- <seealso marker="time_correction#Time_Warp_Modes">
- time warp modes</seealso> in the User's Guide.</p>
+ <seeguide marker="time_correction#Time_Warp_Modes">
+ time warp modes</seeguide> in the User's Guide.</p>
</note>
</desc>
</func>
@@ -9649,8 +10183,8 @@ Metadata = #{ pid => pid(),
<desc>
<p>Returns a binary data object that is the result of encoding
<c><anno>Term</anno></c> according to the
- <seealso marker="erts:erl_ext_dist">Erlang external
- term format.</seealso></p>
+ <seeguide marker="erts:erl_ext_dist">Erlang external
+ term format.</seeguide></p>
<p>This can be used for various purposes, for example,
writing a term to a file in an efficient way, or sending an
Erlang term to some type of communications channel not
@@ -9661,8 +10195,8 @@ Metadata = #{ pid => pid(),
> <input>hello = binary_to_term(Bin).</input>
hello
</pre>
- <p>See also <seealso marker="#binary_to_term/1">
- <c>binary_to_term/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#binary_to_term/1">
+ <c>binary_to_term/1</c></seemfa>.</p>
<note>
<p>There is no guarantee that this function will return
the same encoded representation for the same term.</p>
@@ -9672,7 +10206,7 @@ hello
<func>
<name name="term_to_binary" arity="2" since=""/>
- <fsummary>Encode a term to en Erlang external term format binary.
+ <fsummary>Encode a term to an Erlang external term format binary.
</fsummary>
<desc>
<p>Returns a binary data object that is the result of encoding
@@ -9730,8 +10264,61 @@ hello
systems as of R16B can decode this representation.</p>
</item>
</taglist>
- <p>See also <seealso marker="#binary_to_term/1">
- <c>binary_to_term/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#binary_to_term/1">
+ <c>binary_to_term/1</c></seemfa>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="term_to_iovec" arity="1" since="OTP 23.0"/>
+ <fsummary>Encode a term to an Erlang external term format as iovec().
+ </fsummary>
+ <desc>
+ <p>Returns the encoding of <c><anno>Term</anno></c> according to
+ the Erlang external term format as
+ <seetype marker="#ext_iovec"><c>ext_iovec()</c></seetype>.</p>
+
+ <p>This function produce the same encoding as
+ <seemfa marker="#term_to_binary/1"><c>term_to_binary/1</c></seemfa>,
+ but with another return type. The call
+ <c>iolist_to_binary(term_to_iovec(Term))</c> will produce
+ exactly the same result as the call <c>term_to_binary(Term)</c>.</p>
+
+ <p><c>term_to_iovec()</c> is a pure optimization of the functionality
+ <c>term_to_binary()</c> provide. <c>term_to_iovec()</c> can for example
+ refer directly to off heap binaries instead of copying the binary
+ data into the result.</p>
+
+ <p>See also
+ <seemfa marker="#term_to_binary/1"><c>term_to_binary/1</c></seemfa>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="term_to_iovec" arity="2" since="OTP 23.0"/>
+ <fsummary>Encode a term to en Erlang external term format as iovec().
+ </fsummary>
+ <desc>
+ <p>Returns the encoding of <c><anno>Term</anno></c> according to
+ the Erlang external term format as
+ <seetype marker="#ext_iovec"><c>ext_iovec()</c></seetype>.</p>
+
+ <p>This function produce the same encoding as
+ <seemfa marker="#term_to_binary/2"><c>term_to_binary/2</c></seemfa>,
+ but with another return type. The call
+ <c>iolist_to_binary(term_to_iovec(Term, Opts))</c> will produce
+ exactly the same result as <c>term_to_binary(Term, Opts)</c>.</p>
+
+ <p>Currently recognised options are all options recognised by
+ <seemfa marker="#term_to_binary/2"><c>term_to_binary/2</c></seemfa>.</p>
+
+ <p><c>term_to_iovec()</c> is a pure optimization of the functionality
+ <c>term_to_binary()</c> provide. <c>term_to_iovec()</c> can for example
+ refer directly to off heap binaries instead of copying the binary
+ data into the result.</p>
+
+ <p>See also
+ <seemfa marker="#term_to_binary/2"><c>term_to_binary/2</c></seemfa>.</p>
</desc>
</func>
@@ -9767,25 +10354,25 @@ hello
<fsummary>Current time offset.</fsummary>
<desc>
<p>Returns the current time offset between
- <seealso marker="time_correction#Erlang_Monotonic_Time">
- Erlang monotonic time</seealso> and
- <seealso marker="time_correction#Erlang_System_Time">
- Erlang system time</seealso> in
- <c>native</c> <seealso marker="#type_time_unit">time unit</seealso>.
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">
+ Erlang monotonic time</seeguide> and
+ <seeguide marker="time_correction#Erlang_System_Time">
+ Erlang system time</seeguide> in
+ <c>native</c> <seeerl marker="#type_time_unit">time unit</seeerl>.
Current time offset added to an Erlang monotonic time gives
corresponding Erlang system time.</p>
<p>The time offset may or may not change during operation depending
- on the <seealso marker="time_correction#Time_Warp_Modes">time
- warp mode</seealso> used.</p>
+ on the <seeguide marker="time_correction#Time_Warp_Modes">time
+ warp mode</seeguide> used.</p>
<note>
<p>A change in time offset can be observed at slightly
different points in time by different processes.</p>
<p>If the runtime system is in
- <seealso marker="time_correction#Multi_Time_Warp_Mode">multi-time
- warp mode</seealso>, the time offset is changed when
+ <seeguide marker="time_correction#Multi_Time_Warp_Mode">multi-time
+ warp mode</seeguide>, the time offset is changed when
the runtime system detects that the
- <seealso marker="time_correction#OS_System_Time">OS system
- time</seealso> has changed. The runtime system will, however,
+ <seeguide marker="time_correction#OS_System_Time">OS system
+ time</seeguide> has changed. The runtime system will, however,
not detect this immediately when it occurs. A task checking
the time offset is scheduled to execute at least once a minute;
so, under normal operation this is to be detected within a
@@ -9799,15 +10386,15 @@ hello
<fsummary>Current time offset.</fsummary>
<desc>
<p>Returns the current time offset between
- <seealso marker="time_correction#Erlang_Monotonic_Time">
- Erlang monotonic time</seealso> and
- <seealso marker="time_correction#Erlang_System_Time">
- Erlang system time</seealso>
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">
+ Erlang monotonic time</seeguide> and
+ <seeguide marker="time_correction#Erlang_System_Time">
+ Erlang system time</seeguide>
converted into the <c><anno>Unit</anno></c> passed as argument.</p>
<p>Same as calling
- <seealso marker="#convert_time_unit/3">
- <c>erlang:convert_time_unit</c></seealso><c>(</c><seealso marker="#time_offset/0">
- <c>erlang:time_offset()</c></seealso><c>, native,
+ <seemfa marker="#convert_time_unit/3">
+ <c>erlang:convert_time_unit</c></seemfa><c>(</c><seemfa marker="#time_offset/0">
+ <c>erlang:time_offset()</c></seemfa><c>, native,
<anno>Unit</anno>)</c>
however optimized for commonly used <c><anno>Unit</anno></c>s.</p>
</desc>
@@ -9819,26 +10406,26 @@ hello
<type name="timestamp"/>
<desc>
<p>Returns current
- <seealso marker="time_correction#Erlang_System_Time">
- Erlang system time</seealso>
+ <seeguide marker="time_correction#Erlang_System_Time">
+ Erlang system time</seeguide>
on the format <c>{MegaSecs, Secs, MicroSecs}</c>. This format is
- the same as <seealso marker="kernel:os#timestamp/0">
- <c>os:timestamp/0</c></seealso>
- and the deprecated <seealso marker="#now/0">
- <c>erlang:now/0</c></seealso>
+ the same as <seemfa marker="kernel:os#timestamp/0">
+ <c>os:timestamp/0</c></seemfa>
+ and the deprecated <seemfa marker="#now/0">
+ <c>erlang:now/0</c></seemfa>
use. The reason for the existence of <c>erlang:timestamp()</c> is
purely to simplify use for existing code that assumes this time stamp
format. Current Erlang system time can more efficiently be retrieved
in the time unit of your choice using
- <seealso marker="#system_time/1">
- <c>erlang:system_time/1</c></seealso>.</p>
+ <seemfa marker="#system_time/1">
+ <c>erlang:system_time/1</c></seemfa>.</p>
<p>The <c>erlang:timestamp()</c> BIF is equivalent to:</p>
<code type="none">
timestamp() ->
ErlangSystemTime = erlang:system_time(microsecond),
- MegaSecs = ErlangSystemTime div 1000000000000,
- Secs = ErlangSystemTime div 1000000 - MegaSecs*1000000,
- MicroSecs = ErlangSystemTime rem 1000000,
+ MegaSecs = ErlangSystemTime div 1000_000_000_000,
+ Secs = ErlangSystemTime div 1000_000 - MegaSecs*1000_000,
+ MicroSecs = ErlangSystemTime rem 1000_000,
{MegaSecs, Secs, MicroSecs}.</code>
<p>It, however, uses a native implementation that does
not build garbage on the heap and with slightly better
@@ -9846,8 +10433,8 @@ timestamp() ->
<note>
<p>This time is <em>not</em> a monotonically increasing time
in the general case. For more information, see the documentation of
- <seealso marker="time_correction#Time_Warp_Modes">
- time warp modes</seealso> in the User's Guide.</p>
+ <seeguide marker="time_correction#Time_Warp_Modes">
+ time warp modes</seeguide> in the User's Guide.</p>
</note>
</desc>
</func>
@@ -9914,8 +10501,8 @@ timestamp() ->
</taglist>
<p><c><anno>FlagList</anno></c> can contain any number of the
following flags (the "message tags" refers to the list of
- <seealso marker="#trace_3_trace_messages">
- <c>trace messages</c></seealso>):</p>
+ <seeerl marker="#trace_3_trace_messages">
+ <c>trace messages</c></seeerl>):</p>
<taglist>
<tag><c>all</c></tag>
<item>
@@ -9927,28 +10514,28 @@ timestamp() ->
<item>
<p>Traces sending of messages.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_send">
- <c>send</c></seealso> and
- <seealso marker="#trace_3_trace_messages_send_to_non_existing_process">
- <c>send_to_non_existing_process</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_send">
+ <c>send</c></seeerl> and
+ <seeerl marker="#trace_3_trace_messages_send_to_non_existing_process">
+ <c>send_to_non_existing_process</c></seeerl>.</p>
</item>
<tag><c>'receive'</c></tag>
<item>
<p>Traces receiving of messages.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_receive">
- <c>'receive'</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_receive">
+ <c>'receive'</c></seeerl>.</p>
</item>
<tag><c>call</c></tag>
<item>
<p>Traces certain function calls. Specify which function
- calls to trace by calling <seealso marker="#trace_pattern/3">
- <c>erlang:trace_pattern/3</c></seealso>.</p>
+ calls to trace by calling <seemfa marker="#trace_pattern/3">
+ <c>erlang:trace_pattern/3</c></seemfa>.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_call">
- <c>call</c></seealso> and
- <seealso marker="#trace_3_trace_messages_return_from">
- <c>return_from</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_call">
+ <c>call</c></seeerl> and
+ <seeerl marker="#trace_3_trace_messages_return_from">
+ <c>return_from</c></seeerl>.</p>
</item>
<tag><c>silent</c></tag>
<item>
@@ -9967,20 +10554,20 @@ timestamp() ->
a high degree of control of which functions with which
arguments that trigger the trace.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_call">
- <c>call</c></seealso>,
- <seealso marker="#trace_3_trace_messages_return_from">
- <c>return_from</c></seealso>, and
- <seealso marker="#trace_3_trace_messages_return_to">
- <c>return_to</c></seealso>. Or rather, the absence of.</p>
+ <seeerl marker="#trace_3_trace_messages_call">
+ <c>call</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_return_from">
+ <c>return_from</c></seeerl>, and
+ <seeerl marker="#trace_3_trace_messages_return_to">
+ <c>return_to</c></seeerl>. Or rather, the absence of.</p>
</item>
<tag><c>return_to</c></tag>
<item>
<p>Used with the <c>call</c> trace flag.
Traces the return from a traced function back to
its caller. Only works for functions traced with
- option <c>local</c> to <seealso marker="#trace_pattern/3">
- <c>erlang:trace_pattern/3</c></seealso>.</p>
+ option <c>local</c> to <seemfa marker="#trace_pattern/3">
+ <c>erlang:trace_pattern/3</c></seemfa>.</p>
<p>The semantics is that a trace message is sent when a
call traced function returns, that is, when a
chain of tail recursive calls ends. Only one trace
@@ -9994,68 +10581,68 @@ timestamp() ->
functions, use the <c>{return_trace}</c> match
specification action instead.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_return_to">
- <c>return_to</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_return_to">
+ <c>return_to</c></seeerl>.</p>
</item>
<tag><c>procs</c></tag>
<item>
<p>Traces process-related events.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_spawn">
- <c>spawn</c></seealso>,
- <seealso marker="#trace_3_trace_messages_spawned">
- <c>spawned</c></seealso>,
- <seealso marker="#trace_3_trace_messages_exit">
- <c>exit</c></seealso>,
- <seealso marker="#trace_3_trace_messages_register">
- <c>register</c></seealso>,
- <seealso marker="#trace_3_trace_messages_unregister">
- <c>unregister</c></seealso>,
- <seealso marker="#trace_3_trace_messages_link">
- <c>link</c></seealso>,
- <seealso marker="#trace_3_trace_messages_unlink">
- <c>unlink</c></seealso>,
- <seealso marker="#trace_3_trace_messages_getting_linked">
- <c>getting_linked</c></seealso>, and
- <seealso marker="#trace_3_trace_messages_getting_unlinked">
- <c>getting_unlinked</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_spawn">
+ <c>spawn</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_spawned">
+ <c>spawned</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_exit">
+ <c>exit</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_register">
+ <c>register</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_unregister">
+ <c>unregister</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_link">
+ <c>link</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_unlink">
+ <c>unlink</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_getting_linked">
+ <c>getting_linked</c></seeerl>, and
+ <seeerl marker="#trace_3_trace_messages_getting_unlinked">
+ <c>getting_unlinked</c></seeerl>.</p>
</item>
<tag><c>ports</c></tag>
<item>
<p>Traces port-related events.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_open">
- <c>open</c></seealso>,
- <seealso marker="#trace_3_trace_messages_closed">
- <c>closed</c></seealso>,
- <seealso marker="#trace_3_trace_messages_register">
- <c>register</c></seealso>,
- <seealso marker="#trace_3_trace_messages_unregister">
- <c>unregister</c></seealso>,
- <seealso marker="#trace_3_trace_messages_getting_linked">
- <c>getting_linked</c></seealso>, and
- <seealso marker="#trace_3_trace_messages_getting_unlinked">
- <c>getting_unlinked</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_open">
+ <c>open</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_closed">
+ <c>closed</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_register">
+ <c>register</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_unregister">
+ <c>unregister</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_getting_linked">
+ <c>getting_linked</c></seeerl>, and
+ <seeerl marker="#trace_3_trace_messages_getting_unlinked">
+ <c>getting_unlinked</c></seeerl>.</p>
</item>
<tag><c>running</c></tag>
<item>
<p>Traces scheduling of processes.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_in_proc">
- <c>in</c></seealso> and
- <seealso marker="#trace_3_trace_messages_out_proc">
- <c>out</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_in_proc">
+ <c>in</c></seeerl> and
+ <seeerl marker="#trace_3_trace_messages_out_proc">
+ <c>out</c></seeerl>.</p>
</item>
<tag><c>exiting</c></tag>
<item>
<p>Traces scheduling of exiting processes.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_in_exiting_proc">
- <c>in_exiting</c></seealso>,
- <seealso marker="#trace_3_trace_messages_out_exiting_proc">
- <c>out_exiting</c></seealso>, and
- <seealso marker="#trace_3_trace_messages_out_exited_proc">
- <c>out_exited</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_in_exiting_proc">
+ <c>in_exiting</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_out_exiting_proc">
+ <c>out_exiting</c></seeerl>, and
+ <seeerl marker="#trace_3_trace_messages_out_exited_proc">
+ <c>out_exited</c></seeerl>.</p>
</item>
<tag><c>running_procs</c></tag>
<item>
@@ -10064,30 +10651,30 @@ timestamp() ->
process executes within the context of a port without
being scheduled out itself.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_in_proc">
- <c>in</c></seealso> and
- <seealso marker="#trace_3_trace_messages_out_proc">
- <c>out</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_in_proc">
+ <c>in</c></seeerl> and
+ <seeerl marker="#trace_3_trace_messages_out_proc">
+ <c>out</c></seeerl>.</p>
</item>
<tag><c>running_ports</c></tag>
<item>
<p>Traces scheduling of ports.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_in_port">
- <c>in</c></seealso> and
- <seealso marker="#trace_3_trace_messages_out_port">
- <c>out</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_in_port">
+ <c>in</c></seeerl> and
+ <seeerl marker="#trace_3_trace_messages_out_port">
+ <c>out</c></seeerl>.</p>
</item>
<tag><c>garbage_collection</c></tag>
<item>
<p>Traces garbage collections of processes.</p>
<p>Message tags:
- <seealso marker="#trace_3_trace_messages_gc_minor_start">
- <c>gc_minor_start</c></seealso>,
- <seealso marker="#trace_3_trace_messages_gc_max_heap_size">
- <c>gc_max_heap_size</c></seealso>, and
- <seealso marker="#trace_3_trace_messages_gc_minor_end">
- <c>gc_minor_end</c></seealso>.</p>
+ <seeerl marker="#trace_3_trace_messages_gc_minor_start">
+ <c>gc_minor_start</c></seeerl>,
+ <seeerl marker="#trace_3_trace_messages_gc_max_heap_size">
+ <c>gc_max_heap_size</c></seeerl>, and
+ <seeerl marker="#trace_3_trace_messages_gc_minor_end">
+ <c>gc_minor_end</c></seeerl>.</p>
</item>
<tag><c>timestamp</c></tag>
<item>
@@ -10112,24 +10699,24 @@ timestamp() ->
<tag><c>monotonic_timestamp</c></tag>
<item>
<p>Includes an
- <seealso marker="time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso> time stamp in all trace messages. The
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide> time stamp in all trace messages. The
time stamp (Ts) has the same format and value as produced by
- <seealso marker="#monotonic_time-1">
- <c>erlang:monotonic_time(nanosecond)</c></seealso>.
+ <seemfa marker="#monotonic_time/1">
+ <c>erlang:monotonic_time(nanosecond)</c></seemfa>.
This flag overrides flag <c>cpu_timestamp</c>.</p>
</item>
<tag><c>strict_monotonic_timestamp</c></tag>
<item>
<p>Includes an time stamp consisting of
- <seealso marker="time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso> and a monotonically increasing
+ <seeguide marker="time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide> and a monotonically increasing
integer in all trace messages. The time stamp (Ts) has the
same format and value as produced by <c>{</c>
- <seealso marker="#monotonic_time-1">
- <c>erlang:monotonic_time(nanosecond)</c></seealso><c>,</c>
- <seealso marker="#unique_integer-1">
- <c>erlang:unique_integer([monotonic])</c></seealso><c>}</c>.
+ <seemfa marker="#monotonic_time/1">
+ <c>erlang:monotonic_time(nanosecond)</c></seemfa><c>,</c>
+ <seemfa marker="#unique_integer/1">
+ <c>erlang:unique_integer([monotonic])</c></seemfa><c>}</c>.
This flag overrides flag <c>cpu_timestamp</c>.</p>
</item>
<tag><c>arity</c></tag>
@@ -10172,7 +10759,7 @@ timestamp() ->
instead of sending a trace message. The tracer module
can then ignore or change the trace message. For more details
on how to write a tracer module, see
- <seealso marker="erts:erl_tracer"><c>erl_tracer(3)</c></seealso>.</p>
+ <seeerl marker="erts:erl_tracer"><c>erl_tracer(3)</c></seeerl>.</p>
</item>
</taglist>
<p>If no <c>tracer</c> is specified, the calling process
@@ -10471,8 +11058,8 @@ timestamp() ->
<c>{trace, Pid, gc_max_heap_size, Info}</c>
</tag>
<item>
- <p>Sent when the <seealso marker="#process_flag_max_heap_size">
- <c>max_heap_size</c></seealso>
+ <p>Sent when the <seeerl marker="#process_flag_max_heap_size">
+ <c>max_heap_size</c></seeerl>
is reached during garbage collection. <c>Info</c> contains the
same kind of list as in message <c>gc_start</c>,
but the sizes reflect the sizes that triggered
@@ -10533,10 +11120,10 @@ timestamp() ->
<fsummary>Notification when trace has been delivered.</fsummary>
<desc>
<p>The delivery of trace messages (generated by
- <seealso marker="#trace/3"><c>erlang:trace/3</c></seealso>,
- <seealso marker="kernel:seq_trace"><c>seq_trace(3)</c></seealso>,
- or <seealso marker="#system_profile/2">
- <c>erlang:system_profile/2</c></seealso>)
+ <seemfa marker="#trace/3"><c>erlang:trace/3</c></seemfa>,
+ <seeerl marker="kernel:seq_trace"><c>seq_trace(3)</c></seeerl>,
+ or <seemfa marker="#system_profile/2">
+ <c>erlang:system_profile/2</c></seemfa>)
is dislocated on the time-line
compared to other events in the system. If you know that
<c><anno>Tracee</anno></c> has passed some specific point
@@ -10565,8 +11152,8 @@ timestamp() ->
<c>erlang:trace_delivered(<anno>Tracee</anno>)</c> resides on.
The special <c><anno>Tracee</anno></c> atom <c>all</c>
denotes all processes that currently are traced in the node.</p>
- <p>When used together with a <seealso marker="erts:erl_tracer">
- Tracer Module</seealso>, any message sent in the trace callback
+ <p>When used together with a <seeerl marker="erts:erl_tracer">
+ Tracer Module</seeerl>, any message sent in the trace callback
is guaranteed to have reached its recipient before the
<c>trace_delivered</c> message is sent.</p>
<p>Example: Process <c>A</c> is <c><anno>Tracee</anno></c>,
@@ -10677,8 +11264,8 @@ timestamp() ->
<p>Returns the call count value for this function or
<c>true</c> for the pseudo function <c>on_load</c> if call
count tracing is active. Otherwise <c>false</c> is returned.</p>
- <p>See also <seealso marker="#trace_pattern/3">
- <c>erlang:trace_pattern/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#trace_pattern/3">
+ <c>erlang:trace_pattern/3</c></seemfa>.</p>
</item>
<tag><c>call_time</c></tag>
<item>
@@ -10689,8 +11276,8 @@ timestamp() ->
is a list of each process that executed the function
and its specific counters.</p>
<p>See also
- <seealso marker="#trace_pattern/3">
- <c>erlang:trace_pattern/3</c></seealso>.</p>
+ <seemfa marker="#trace_pattern/3">
+ <c>erlang:trace_pattern/3</c></seemfa>.</p>
</item>
<tag><c>all</c></tag>
<item>
@@ -10731,8 +11318,8 @@ timestamp() ->
<type name="match_variable"/>
<desc>
<p>The same as
- <seealso marker="#trace_pattern/3">
- <c>erlang:trace_pattern(Event, MatchSpec, [])</c></seealso>,
+ <seemfa marker="#trace_pattern/3">
+ <c>erlang:trace_pattern(Event, MatchSpec, [])</c></seemfa>,
retained for backward compatibility.</p>
</desc>
</func>
@@ -10748,7 +11335,7 @@ timestamp() ->
<desc>
<p>Sets trace pattern for <em>message sending</em>.
Must be combined with
- <seealso marker="#trace/3"><c>erlang:trace/3</c></seealso>
+ <seemfa marker="#trace/3"><c>erlang:trace/3</c></seemfa>
to set the <c>send</c> trace flag for one or more processes.
By default all messages sent from <c>send</c> traced processes
are traced. To limit
@@ -10766,8 +11353,8 @@ timestamp() ->
process can be accessed with the guard function
<c>self/0</c>. An empty list is the same as <c>true</c>.
For more information, see section
- <seealso marker="erts:match_spec">
- Match Specifications in Erlang</seealso> in the User's Guide.</p>
+ <seeguide marker="erts:match_spec">
+ Match Specifications in Erlang</seeguide> in the User's Guide.</p>
</item>
<tag><c>true</c></tag>
<item>
@@ -10805,6 +11392,22 @@ timestamp() ->
<p>A match specification for <c>send</c> trace can use
all guard and body functions except <c>caller</c>.</p>
</note>
+ <p>Fails by raising an error exception with an error reason of:</p>
+ <taglist>
+ <tag><c>badarg</c></tag>
+ <item><p>
+ If an argument is invalid.
+ </p></item>
+ <tag><c>system_limit</c></tag>
+ <item><p>
+ If a match specification passed as argument has excessive
+ nesting which causes scheduler stack exhaustion for the
+ scheduler that the calling process is executing on.
+ <seecom marker="erts:erl#sched_thread_stack_size">Scheduler
+ stack size</seecom> can be configured when starting the
+ runtime system.
+ </p></item>
+ </taglist>
</desc>
</func>
@@ -10819,7 +11422,7 @@ timestamp() ->
<desc>
<p>Sets trace pattern for <em>message receiving</em>.
Must be combined with
- <seealso marker="#trace/3"><c>erlang:trace/3</c></seealso>
+ <seemfa marker="#trace/3"><c>erlang:trace/3</c></seemfa>
to set the <c>'receive'</c> trace flag for one or more processes.
By default all messages received by <c>'receive'</c> traced
processes are traced. To limit
@@ -10839,8 +11442,8 @@ timestamp() ->
message term. The pid of the receiving process can be
accessed with the guard function <c>self/0</c>. An empty
list is the same as <c>true</c>. For more information, see
- section <seealso marker="erts:match_spec">
- Match Specifications in Erlang</seealso> in the User's Guide.</p>
+ section <seeguide marker="erts:match_spec">
+ Match Specifications in Erlang</seeguide> in the User's Guide.</p>
</item>
<tag><c>true</c></tag>
<item>
@@ -10877,6 +11480,22 @@ timestamp() ->
<c>enable_trace</c>, <c>disable_trace</c>, <c>trace</c>,
<c>silent</c>, and <c>process_dump</c>.</p>
</note>
+ <p>Fails by raising an error exception with an error reason of:</p>
+ <taglist>
+ <tag><c>badarg</c></tag>
+ <item><p>
+ If an argument is invalid.
+ </p></item>
+ <tag><c>system_limit</c></tag>
+ <item><p>
+ If a match specification passed as argument has excessive
+ nesting which causes scheduler stack exhaustion for the
+ scheduler that the calling process is executing on.
+ <seecom marker="erts:erl#sched_thread_stack_size">Scheduler
+ stack size</seecom> can be configured when starting the
+ runtime system.
+ </p></item>
+ </taglist>
</desc>
</func>
@@ -10893,7 +11512,7 @@ timestamp() ->
<desc>
<p>Enables or disables <em>call tracing</em> for one or more functions.
Must be combined with
- <seealso marker="#trace/3"><c>erlang:trace/3</c></seealso>
+ <seemfa marker="#trace/3"><c>erlang:trace/3</c></seemfa>
to set the <c>call</c> trace flag
for one or more processes.</p>
<p>Conceptually, call tracing works as follows. Inside
@@ -10903,7 +11522,7 @@ timestamp() ->
Otherwise, nothing happens.</p>
<p>To add or remove one or more processes to the set of traced
processes, use
- <seealso marker="#trace/3"><c>erlang:trace/3</c></seealso>.</p>
+ <seemfa marker="#trace/3"><c>erlang:trace/3</c></seemfa>.</p>
<p>To add or remove functions to the set of traced
functions, use <c>erlang:trace_pattern/3</c>.</p>
<p>The BIF <c>erlang:trace_pattern/3</c> can also add match
@@ -10957,8 +11576,8 @@ timestamp() ->
<item>
<p>A list of match specifications. An empty list is
equivalent to <c>true</c>. For a description of match
- specifications, see section <seealso marker="erts:match_spec">
- Match Specifications in Erlang</seealso> in the User's Guide.</p>
+ specifications, see section <seeguide marker="erts:match_spec">
+ Match Specifications in Erlang</seeguide> in the User's Guide.</p>
</item>
<tag><c>restart</c></tag>
<item>
@@ -11024,8 +11643,8 @@ timestamp() ->
Paused and running counters can be restarted from zero with
<c><anno>MatchSpec</anno> == restart</c>.</p>
<p>To read the counter value, use
- <seealso marker="#trace_info/2">
- <c>erlang:trace_info/2</c></seealso>.</p>
+ <seemfa marker="#trace_info/2">
+ <c>erlang:trace_info/2</c></seemfa>.</p>
</item>
<tag><c>call_time</c></tag>
<item>
@@ -11043,8 +11662,8 @@ timestamp() ->
Paused and running counters can be restarted from zero with
<c><anno>MatchSpec</anno> == restart</c>.</p>
<p>To read the counter value, use
- <seealso marker="#trace_info/2">
- <c>erlang:trace_info/2</c></seealso>.</p>
+ <seemfa marker="#trace_info/2">
+ <c>erlang:trace_info/2</c></seemfa>.</p>
</item>
</taglist>
<p>The options <c>global</c> and <c>local</c> are mutually
@@ -11064,10 +11683,26 @@ timestamp() ->
If a function has a match specification, it can be replaced
with a new one. To change an existing match specification,
use the BIF
- <seealso marker="#trace_info/2"><c>erlang:trace_info/2</c></seealso>
+ <seemfa marker="#trace_info/2"><c>erlang:trace_info/2</c></seemfa>
to retrieve the existing match specification.</p>
<p>Returns the number of functions matching
argument <c><anno>MFA</anno></c>. This is zero if none matched.</p>
+ <p>Fails by raising an error exception with an error reason of:</p>
+ <taglist>
+ <tag><c>badarg</c></tag>
+ <item><p>
+ If an argument is invalid.
+ </p></item>
+ <tag><c>system_limit</c></tag>
+ <item><p>
+ If a match specification passed as argument has excessive
+ nesting which causes scheduler stack exhaustion for the
+ scheduler that the calling process is executing on.
+ <seecom marker="erts:erl#sched_thread_stack_size">Scheduler
+ stack size</seecom> can be configured when starting the
+ runtime system.
+ </p></item>
+ </taglist>
</desc>
</func>
@@ -11115,11 +11750,11 @@ timestamp() ->
<fsummary>Get a unique integer value.</fsummary>
<desc>
<p>Generates and returns an
- <seealso marker="doc/efficiency_guide:advanced#unique_integers">
- integer unique on current runtime system instance</seealso>.
+ <seeguide marker="system/efficiency_guide:advanced#unique_integers">
+ integer unique on current runtime system instance</seeguide>.
The same as calling
- <seealso marker="#unique_integer/1">
- <c>erlang:unique_integer([])</c></seealso>.</p>
+ <seemfa marker="#unique_integer/1">
+ <c>erlang:unique_integer([])</c></seemfa>.</p>
</desc>
</func>
@@ -11128,9 +11763,9 @@ timestamp() ->
<fsummary>Get a unique integer value.</fsummary>
<desc>
<p>Generates and returns an
- <seealso marker="doc/efficiency_guide:advanced#unique_integers">
+ <seeguide marker="system/efficiency_guide:advanced#unique_integers">
integer unique on current runtime system
- instance</seealso>. The integer is unique in the
+ instance</seeguide>. The integer is unique in the
sense that this BIF, using the same set of
modifiers, does not return the same integer more
than once on the current runtime system instance.
@@ -11155,9 +11790,9 @@ timestamp() ->
</item>
<tag>monotonic</tag>
<item>
- <p>Returns <seealso
+ <p>Returns <seeguide
marker="time_correction#Strictly_Monotonically_Increasing">
- strictly monotonically increasing</seealso> integers
+ strictly monotonically increasing</seeguide> integers
corresponding to creation time. That is, the integer
returned is always larger than previously
returned integers on the current runtime system
@@ -11245,27 +11880,50 @@ timestamp() ->
<name name="unlink" arity="1" since=""/>
<fsummary>Remove a link to another process or port.</fsummary>
<desc>
- <p>Removes the link, if there is one, between the calling
- process and the process or port referred to by
- <c><anno>Id</anno></c>.</p>
- <p>Returns <c>true</c> and does not fail, even if there is no
- link to <c><anno>Id</anno></c>, or if <c><anno>Id</anno></c>
- does not exist.</p>
- <p>Once <c>unlink(<anno>Id</anno>)</c> has returned,
- it is guaranteed that
- the link between the caller and the entity referred to by
- <c><anno>Id</anno></c> has no effect on the caller
- in the future (unless
- the link is setup again). If the caller is trapping exits, an
- <c>{'EXIT', <anno>Id</anno>, _}</c> message from the link
- can have been placed in the caller's message queue before
- the call.</p>
- <p>Notice that the <c>{'EXIT', <anno>Id</anno>, _}</c>
- message can be the
- result of the link, but can also be the result of <c>Id</c>
- calling <c>exit/2</c>. Therefore, it <em>can</em> be
- appropriate to clean up the message queue when trapping exits
- after the call to <c>unlink(<anno>Id</anno>)</c>, as follows:</p>
+ <p>
+ Removes a link between the calling process and another process
+ or a port identified by <c><anno>Id</anno></c>. We will from
+ here on call the identified process or port unlinkee.
+ </p>
+
+ <p>
+ A link can be set up using the
+ <seemfa marker="#link/1"><c>link/1</c></seemfa> BIF. For more
+ information on links and exit signals due to links, see the
+ <i>Processes</i> chapter in the <i>Erlang Reference Manual</i>:
+ </p>
+ <list>
+ <item>
+ <seeguide marker="system/reference_manual:processes#links">Links</seeguide>
+ </item>
+ <item>
+ <seeguide marker="system/reference_manual:processes#sending_exit_signals">Sending
+ Exit Signals</seeguide>
+ </item>
+ <item>
+ <seeguide marker="system/reference_manual:processes#receiving_exit_signals">Receiving
+ Exit Signals</seeguide>
+ </item>
+ </list>
+
+ <p>
+ Once <c>unlink(<anno>Id</anno>)</c> has returned, it is
+ guaranteed that the link between the caller and the unlinkee
+ has no effect on the caller in the future (unless the link is
+ setup again). Note that if the caller is
+ <seeerl marker="erts:erlang#process_flag_trap_exit">trapping
+ exits</seeerl>, an <c>{'EXIT', <anno>Id</anno>, ExitReason}</c>
+ message due to the link may have been placed in the message
+ queue of the caller before the <c>unlink(<anno>Id</anno>)</c>
+ call completed. Also note that the <c>{'EXIT', <anno>Id</anno>,
+ ExitReason}</c> message may be the result of the link, but
+ may also be the result of the unlikee sending the caller an
+ exit signal by calling the
+ <seemfa marker="#exit/2"><c>exit/2</c></seemfa> BIF.
+ Therefore, it may or may not be appropriate to clean up
+ the message queue after a call to <c>unlink(<anno>Id</anno>)</c>
+ as follows, when trapping exits:
+ </p>
<code type="none">
unlink(Id),
receive
@@ -11274,16 +11932,19 @@ receive
after 0 ->
true
end</code>
- <note>
- <p>Before Erlang/OTP R11B (ERTS 5.5) <c>unlink/1</c>
- behaved completely asynchronously, that is, the link was active
- until the "unlink signal" reached the linked entity. This
- had an undesirable effect, as you could never know when
- you were guaranteed <em>not</em> to be effected by the link.</p>
- <p>The current behavior can be viewed as two combined operations:
- asynchronously send an "unlink signal" to the linked entity
- and ignore any future results of the link.</p>
- </note>
+
+ <p>
+ The link removal is performed asynchronously. If such a link
+ does not exist, nothing is done. A detailed description of the
+ <seeguide marker="erts:erl_dist_protocol#link_protocol">link
+ protocol</seeguide> can be found in the <i>Distribution Protocol</i>
+ chapter of the <i>ERTS User's Guide</i>.
+ </p>
+
+ <p>
+ Failure: <c>badarg</c> if <c>Id</c> does not identify a process
+ or a node local port.
+ </p>
</desc>
</func>
@@ -11323,7 +11984,7 @@ true</pre>
<desc>
<p>Voluntarily lets other processes (if any) get a chance to
execute. Using this function is similar to
- <c>receive after 1 -> ok end</c>, except that <c>yield()</c>
+ <c>receive after 1 -> true end</c>, except that <c>yield()</c>
is faster.</p>
<warning>
<p>There is seldom or never any need to use this BIF
diff --git a/erts/doc/src/erlc.xml b/erts/doc/src/erlc_cmd.xml
index 64f7451070..25c6ebf23d 100644
--- a/erts/doc/src/erlc.xml
+++ b/erts/doc/src/erlc_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -106,7 +106,7 @@
must be quoted. Terms containing spaces
must be quoted on all platforms.</p>
</item>
- <tag><c>-W&lt;Error&gt;</c></tag>
+ <tag><c>-WError</c></tag>
<item>
<p>Makes all warnings into errors.</p>
</item>
@@ -141,12 +141,12 @@
<tag><c>-no-server</c></tag>
<item>
<p>Do not use the
- <seealso marker="#compile_server">compile server</seealso>.</p>
+ <seecom marker="#compile_server">compile server</seecom>.</p>
</item>
<tag><c>-server</c></tag>
<item>
<p>Use the
- <seealso marker="#compile_server">compile server</seealso>.</p>
+ <seecom marker="#compile_server">compile server</seecom>.</p>
</item>
<tag><c>-M</c></tag>
<item>
@@ -363,14 +363,14 @@ erlc +export_all file.erl</pre>
specified in environment variable <c>PATH</c>.</item>
<tag><c>ERLC_USE_SERVER</c></tag>
<item>Allowed values are <c>yes</c> or <c>true</c> to use the
- <seealso marker="#compile_server">compile
- server</seealso>, and <c>no</c> or <c>false</c> to not use the
+ <seecom marker="#compile_server">compile
+ server</seecom>, and <c>no</c> or <c>false</c> to not use the
compile server. If other values are given, <c>erlc</c> will
print a warning message and continue.
</item>
<tag><c>ERLC_SERVER_ID</c></tag>
<item>Tells <c>erlc</c> to identify the
- <seealso marker="#compile_server">compile server</seealso> by the given
+ <seecom marker="#compile_server">compile server</seecom> by the given
name, allowing a single user to run multiple unrelated builds in
parallel without them affecting each other, which can be useful for
shared build machines and the like. The name must be alpha&shy;numeric,
@@ -381,10 +381,9 @@ erlc +export_all file.erl</pre>
<section>
<title>See Also</title>
- <p><seealso marker="erl"><c>erl(1)</c></seealso>,
- <seealso marker="compiler:compile"><c>compile(3)</c></seealso>,
- <seealso marker="parsetools:yecc"><c>yecc(3)</c></seealso>,
- <seealso marker="snmp:snmp"><c>snmp(3)</c></seealso></p>
+ <p><seecom marker="erl"><c>erl(1)</c></seecom>,
+ <seeerl marker="compiler:compile"><c>compile(3)</c></seeerl>,
+ <seeerl marker="parsetools:yecc"><c>yecc(3)</c></seeerl>,
+ <seeerl marker="snmp:snmp"><c>snmp(3)</c></seeerl></p>
</section>
</comref>
-
diff --git a/erts/doc/src/erlsrv.xml b/erts/doc/src/erlsrv_cmd.xml
index 6c08b25220..20efd94f81 100644
--- a/erts/doc/src/erlsrv.xml
+++ b/erts/doc/src/erlsrv_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -43,7 +43,7 @@
Windows services applet in a manner similar to other services.</p>
<p>Notice that <c>erlsrv</c> is not a general service utility for Windows,
- but designed for embedded Erlang systems.</p>
+ but designed for embedded Erlang systems.</p>
<p><c>erlsrv</c> also provides a command-line interface for registering,
changing, starting, and stopping services.</p>
@@ -302,7 +302,7 @@
automatically sorted. Any number of <c><![CDATA[-env]]></c>
options can be specified in one command. Default is to use the
system environment block unmodified (except for two additions,
- see section <seealso marker="#002">Environment</seealso>
+ see section <seecom marker="#002">Environment</seecom>
below).</p>
</item>
<tag><c>-w[orkdir] [&lt;directory&gt;]</c></tag>
@@ -471,10 +471,10 @@
<code type="none"><![CDATA[
#include <windows.h>
-/*
+/*
** A Console control handler that ignores the log off events,
** and lets the default handler take care of other events.
-*/
+*/
BOOL WINAPI service_aware_handler(DWORD ctrl){
if(ctrl == CTRL_LOGOFF_EVENT)
return TRUE;
@@ -485,8 +485,8 @@ BOOL WINAPI service_aware_handler(DWORD ctrl){
void initialize_handler(void){
char buffer[2];
- /*
- * We assume we are running as a service if this
+ /*
+ * We assume we are running as a service if this
* environment variable is defined.
*/
if(GetEnvironmentVariable("ERLSRV_SERVICE_NAME",buffer,
@@ -519,15 +519,14 @@ void initialize_handler(void){
<c><![CDATA[os:find_executable/1]]></c> Erlang function.</p>
<p>For release handling to work, use <c><![CDATA[start_erl]]></c> as the
- Erlang machine. As stated <seealso marker="#001">above</seealso>,
+ Erlang machine. As stated <seecom marker="#001">above</seecom>,
the service name is significant.</p>
</section>
<section>
<title>See Also</title>
- <p><seealso marker="start_erl"><c>start_erl(1)</c></seealso>,
- <seealso marker="sasl:release_handler">
- <c>release_handler(3)</c></seealso></p>
+ <p><seecom marker="start_erl"><c>start_erl(1)</c></seecom>,
+ <seeerl marker="sasl:release_handler">
+ <c>release_handler(3)</c></seeerl></p>
</section>
</comref>
-
diff --git a/erts/doc/src/erts_alloc.xml b/erts/doc/src/erts_alloc.xml
index 962bc9a244..5fa940e1cc 100644
--- a/erts/doc/src/erts_alloc.xml
+++ b/erts/doc/src/erts_alloc.xml
@@ -67,7 +67,7 @@
<item>A fast allocator used for some frequently used
fixed size data types.</item>
<tag><c>exec_alloc</c></tag>
- <item>Allocator used by the <seealso marker="hipe:HiPE_app"><c>HiPE</c></seealso>
+ <item>Allocator used by the <seeapp marker="hipe:HiPE_app"><c>HiPE</c></seeapp>
application for native executable code.</item>
<tag><c>std_alloc</c></tag>
<item>Allocator used for most memory blocks not allocated through any of
@@ -90,7 +90,7 @@
enabled and cannot be disabled. <c>exec_alloc</c> is only available if it
is needed and cannot be disabled. <c>mseg_alloc</c> is always enabled if it is
available and an allocator that uses it is enabled. All other
- allocators can be <seealso marker="#M_e">enabled or disabled</seealso>.
+ allocators can be <seecref marker="#M_e">enabled or disabled</seecref>.
By default all allocators are enabled.
When an allocator is disabled, <c>sys_alloc</c> is used instead of
the disabled allocator.</p>
@@ -125,7 +125,7 @@
</item>
<item>
<p>Blocks that are larger than the value of the singleblock carrier
- threshold (<seealso marker="#M_sbct"><c>sbct</c></seealso>) parameter
+ threshold (<seecref marker="#M_sbct"><c>sbct</c></seecref>) parameter
are placed in singleblock carriers.</p>
</item>
<item>
@@ -136,7 +136,7 @@
<p>Normally an allocator creates a "main multiblock
carrier". Main multiblock carriers are never deallocated. The
size of the main multiblock carrier is determined by the value of
- parameter <seealso marker="#M_mmbcs"><c>mmbcs</c></seealso>.</p>
+ parameter <seecref marker="#M_mmbcs"><c>mmbcs</c></seecref>.</p>
<p><marker id="mseg_mbc_sizes"></marker>Sizes of multiblock carriers
allocated through <c>mseg_alloc</c> are decided based on the
@@ -144,11 +144,11 @@
<list type="bulleted">
<item>The values of the largest multiblock carrier size
- (<seealso marker="#M_lmbcs"><c>lmbcs</c></seealso>)</item>
+ (<seecref marker="#M_lmbcs"><c>lmbcs</c></seecref>)</item>
<item>The smallest multiblock carrier size
- (<seealso marker="#M_smbcs"><c>smbcs</c></seealso>)</item>
+ (<seecref marker="#M_smbcs"><c>smbcs</c></seecref>)</item>
<item>The multiblock carrier growth stages
- (<seealso marker="#M_mbcgs"><c>mbcgs</c></seealso>)</item>
+ (<seecref marker="#M_mbcgs"><c>mbcgs</c></seecref>)</item>
</list>
<p>If <c>nc</c> is the current number of multiblock carriers (the main
@@ -167,7 +167,7 @@
<p>Sizes of carriers allocated through <c>sys_alloc</c> are
decided based on the value of the <c>sys_alloc</c> carrier size
- (<seealso marker="#Muycs"><c>ycs</c></seealso>) parameter. The size of
+ (<seecref marker="#Muycs"><c>ycs</c></seecref>) parameter. The size of
a carrier is the least number of multiples of the value of
parameter <c>ycs</c> satisfying the request.</p>
@@ -177,7 +177,7 @@
<p><marker id="strategy"></marker>The memory allocation strategy
used for multiblock carriers by an allocator can be
- configured using parameter <seealso marker="#M_as"><c>as</c></seealso>.
+ configured using parameter <seecref marker="#M_as"><c>as</c></seecref>.
The following strategies are available:</p>
<taglist>
@@ -261,7 +261,7 @@
search depth is small (by default 3), this implementation
has a time complexity that is constant. The maximum block
search depth can be configured using parameter
- <seealso marker="#M_mbsd"><c>mbsd</c></seealso>.</p>
+ <seecref marker="#M_mbsd"><c>mbsd</c></seecref>.</p>
</item>
<tag>A fit</tag>
<item>
@@ -303,7 +303,7 @@
where <c><![CDATA[<S>]]></c> is a letter identifying a subsystem,
<c><![CDATA[<P>]]></c> is a parameter, and <c><![CDATA[<V>]]></c> is the
value to use. The flags can be passed to the Erlang emulator
- (<seealso marker="erl"><c>erl(1)</c></seealso>) as command-line
+ (<seecom marker="erl"><c>erl(1)</c></seecom>) as command-line
arguments.</p>
<p>System flags effecting specific allocators have an uppercase
@@ -345,14 +345,14 @@
</item>
<tag><marker id="MMsco"/><c><![CDATA[+MMsco true|false]]></c></tag>
<item>
- <p>Sets <seealso marker="#MMscs">super carrier</seealso> only flag.
+ <p>Sets <seecref marker="#MMscs">super carrier</seecref> only flag.
Defaults to <c>true</c>. When a super carrier is used and this
flag is <c>true</c>, <c>mseg_alloc</c> only creates carriers in
the super carrier. Notice that the <c>alloc_util</c> framework can
create <c>sys_alloc</c> carriers, so if you want all carriers to
be created in the super carrier, you therefore want to disable use
of <c>sys_alloc</c> carriers by also passing
- <seealso marker="#Musac"><c>+Musac false</c></seealso>. When
+ <seecref marker="#Musac"><c>+Musac false</c></seecref>. When
the flag is <c>false</c>, <c>mseg_alloc</c> tries to create carriers
outside of the super carrier when the super carrier is full.</p>
<note>
@@ -362,7 +362,7 @@
</item>
<tag><marker id="MMscrfsd"/><c><![CDATA[+MMscrfsd <amount>]]></c></tag>
<item>
- <p>Sets <seealso marker="#MMscs">super carrier</seealso> reserved
+ <p>Sets <seecref marker="#MMscs">super carrier</seecref> reserved
free segment descriptors. Defaults to <c>65536</c>.
This parameter determines the amount of memory to reserve for
free segment descriptors used by the super carrier. If the system
@@ -371,12 +371,12 @@
so you want to ensure that this never happens. The maximum amount
of free segment descriptors used can be retrieved from the
<c>erts_mmap</c> tuple part of the result from calling
- <seealso marker="erts:erlang#system_info_allocator_tuple">
- <c>erlang:system_info({allocator, mseg_alloc})</c></seealso>.</p>
+ <seeerl marker="erts:erlang#system_info_allocator_tuple">
+ <c>erlang:system_info({allocator, mseg_alloc})</c></seeerl>.</p>
</item>
<tag><marker id="MMscrpm"/><c><![CDATA[+MMscrpm true|false]]></c></tag>
<item>
- <p>Sets <seealso marker="#MMscs">super carrier</seealso> reserve
+ <p>Sets <seecref marker="#MMscs">super carrier</seecref> reserve
physical memory flag. Defaults to <c>true</c>. When this flag is
<c>true</c>, physical memory is reserved for the whole super
carrier at once when it is created. The reservation is after that
@@ -402,7 +402,7 @@
<c>mseg_alloc</c> always tries to create new carriers in the super
carrier if it exists. Notice that the <c>alloc_util</c> framework
can create <c>sys_alloc</c> carriers. For more information, see
- <seealso marker="#MMsco"><c>+MMsco</c></seealso>.</p>
+ <seecref marker="#MMsco"><c>+MMsco</c></seecref>.</p>
</item>
<tag><marker id="MMmcs"/><c><![CDATA[+MMmcs <amount>]]></c></tag>
<item>
@@ -464,9 +464,9 @@
<p>If <c>u</c> is used as subsystem identifier (that is,
<c><![CDATA[<S> = u]]></c>), all allocators based on
<c>alloc_util</c> are effected. If <c>B</c>, <c>D</c>, <c>E</c>,
- <c>F</c>, <c>H</c>, <c>L</c>, <c>R</c>, <c>S</c>, or <c>T</c> is used
- as subsystem identifier, only the specific allocator identifier is
- effected.</p>
+ <c>F</c>, <c>H</c>, <c>I</c>, <c>L</c>, <c>R</c>, <c>S</c>, <c>T</c>,
+ <c>X</c> is used as subsystem identifier, only the specific allocator
+ identifier is effected.</p>
<taglist>
<tag><marker id="M_acul"/><c><![CDATA[+M<S>acul <utilization>|de]]></c>
@@ -491,12 +491,12 @@
carrier can be fetched, it creates a new empty carrier. When an
abandoned carrier has been fetched, it will function as an ordinary
carrier. This feature has special requirements on the
- <seealso marker="#M_as">allocation strategy</seealso> used. Only
+ <seecref marker="#M_as">allocation strategy</seecref> used. Only
the strategies <c>aoff</c>, <c>aoffcbf</c>, <c>aoffcaobf</c>,
<c>ageffcaoff</c>m, <c>ageffcbf</c> and <c>ageffcaobf</c>
support abandoned carriers.</p>
<p>This feature also requires
- <seealso marker="#M_t">multiple thread specific instances</seealso>
+ <seecref marker="#M_t">multiple thread specific instances</seecref>
to be enabled. When enabling this feature, multiple thread-specific
instances are enabled if not already enabled, and the
<c>aoffcbf</c> strategy is enabled if the current strategy does not
@@ -513,7 +513,7 @@
free block in a carrier must be at least <c>bytes</c> large, for the
carrier to be abandoned. The default is zero but can be changed
in the future.</p>
- <p>See also <seealso marker="#M_acul"><c>acul</c></seealso>.</p>
+ <p>See also <seecref marker="#M_acul"><c>acul</c></seecref>.</p>
</item>
<tag><marker id="M_acnl"/><c><![CDATA[+M<S>acnl <amount>]]></c>
@@ -523,7 +523,7 @@
is a positive integer representing max number of abandoned carriers per
allocator instance. Defaults to 1000 which will practically disable
the limit, but this can be changed in the future.</p>
- <p>See also <seealso marker="#M_acul"><c>acul</c></seealso>.</p>
+ <p>See also <seecref marker="#M_acul"><c>acul</c></seecref>.</p>
</item>
<tag><marker id="M_as"/>
@@ -547,7 +547,7 @@
<item><c>af</c> (a fit)</item>
</list>
<p>See the description of allocation strategies in section
- <seealso marker="#strategy">The alloc_util Framework</seealso>.</p>
+ <seecref marker="#strategy">The alloc_util Framework</seecref>.</p>
</item>
<tag><marker id="M_asbcst"/><c><![CDATA[+M<S>asbcst <size>]]></c></tag>
<item>
@@ -556,13 +556,13 @@
<c>mseg_alloc</c> singleblock carrier is shrunk, the carrier
is left unchanged if the amount of unused memory is less
than this threshold, otherwise the carrier is shrunk.
- See also <seealso marker="#M_rsbcst"><c>rsbcst</c></seealso>.</p>
+ See also <seecref marker="#M_rsbcst"><c>rsbcst</c></seecref>.</p>
</item>
<tag><marker id="M_atags"/><c><![CDATA[+M<S>atags true|false]]></c></tag>
<item>
<p>Adds a small tag to each allocated block that contains basic
information about what it is and who allocated it. Use the
- <seealso marker="tools:instrument"><c>instrument</c></seealso>
+ <seeerl marker="tools:instrument"><c>instrument</c></seeerl>
module to inspect this information.</p>
<p>The runtime overhead is one word per allocation when enabled. This
@@ -572,6 +572,57 @@
<c>driver_alloc</c>, and <c>false</c> for the other allocator
types.</p>
</item>
+ <tag><marker id="M_cp"/><c><![CDATA[+M<S>cg B|D|E|F|H||L|R|S|@|:]]></c></tag>
+ <item>
+ <p>
+ Set carrier pool to use for the allocator. Memory carriers will
+ only migrate between allocator instances that use the same carrier
+ pool. The following carrier pool names exist:
+ </p>
+ <taglist>
+ <tag><c>B</c></tag>
+ <item>Carrier pool associated with <c>binary_alloc</c>.</item>
+ <tag><c>D</c></tag>
+ <item>Carrier pool associated with <c>std_alloc</c>.</item>
+ <tag><c>E</c></tag>
+ <item>Carrier pool associated with <c>ets_alloc</c>.</item>
+ <tag><c>F</c></tag>
+ <item>Carrier pool associated with <c>fix_alloc</c>.</item>
+ <tag><c>H</c></tag>
+ <item>Carrier pool associated with <c>eheap_alloc</c>.</item>
+ <tag><c>L</c></tag>
+ <item>Carrier pool associated with <c>ll_alloc</c>.</item>
+ <tag><c>R</c></tag>
+ <item>Carrier pool associated with <c>driver_alloc</c>.</item>
+ <tag><c>S</c></tag>
+ <item>Carrier pool associated with <c>sl_alloc</c>.</item>
+ <tag><c>@</c></tag>
+ <item>Carrier pool associated with the system as a whole.</item>
+ </taglist>
+ <p>
+ Besides passing carrier pool name as value to the parameter,
+ you can also pass <c>:</c>. By passing <c>:</c> instead of carrier
+ pool name, the allocator will use the carrier pool associated
+ with itself. By passing the command line argument "<c>+Mucg :</c>",
+ all allocators that have an associated carrier pool will use the
+ carrier pool associated with themselves.
+ </p>
+ <p>
+ The association between carrier pool and allocator is very loose.
+ The associations are more or less only there to get names for the
+ amount of carrier pools needed and names of carrier pools that
+ can be easily identified by the <c>:</c> value.
+ </p>
+ <p>
+ This flag is only valid for allocators that have an associated
+ carrier pool. Besides that, there are no restrictions on carrier
+ pools to use for an allocator.
+ </p>
+ <p>
+ Currently the <c>@</c> carrier pool is used by default, but
+ this will most likely change.
+ </p>
+ </item>
<tag><marker id="M_e"/><c><![CDATA[+M<S>e true|false]]></c></tag>
<item>
<p>Enables allocator <c><![CDATA[<S>]]></c>.</p>
@@ -581,8 +632,8 @@
<p>Largest (<c>mseg_alloc</c>) multiblock carrier size (in kilobytes).
See the description on how sizes for <c>mseg_alloc</c> multiblock
carriers are decided in section
- <seealso marker="#mseg_mbc_sizes">
- The alloc_util Framework</seealso>. On
+ <seecref marker="#mseg_mbc_sizes">
+ The alloc_util Framework</seecref>. On
32-bit Unix style OS this limit cannot be set &gt; 64 MB.</p>
</item>
<tag><marker id="M_mbcgs"/><c><![CDATA[+M<S>mbcgs <ratio>]]></c></tag>
@@ -590,8 +641,8 @@
<p>(<c>mseg_alloc</c>) multiblock carrier growth stages.
See the description on how sizes for <c>mseg_alloc</c> multiblock
carriers are decided in section
- <seealso marker="#mseg_mbc_sizes">
- The alloc_util Framework</seealso>.</p>
+ <seecref marker="#mseg_mbc_sizes">
+ The alloc_util Framework</seecref>.</p>
</item>
<tag><marker id="M_mbsd"/><c><![CDATA[+M<S>mbsd <depth>]]></c></tag>
<item>
@@ -646,7 +697,7 @@
<p>Relative singleblock carrier move threshold (in percent). When
a block located in a singleblock carrier is shrunk to
a size smaller than the value of parameter
- <seealso marker="#M_sbct"><c>sbct</c></seealso>,
+ <seecref marker="#M_sbct"><c>sbct</c></seecref>,
the block is left unchanged in the singleblock carrier if
the ratio of unused memory is less than this threshold,
otherwise it is moved into a multiblock carrier.</p>
@@ -658,7 +709,7 @@
singleblock carrier is shrunk, the carrier is left
unchanged if the ratio of unused memory is less than this
threshold, otherwise the carrier is shrunk.
- See also <seealso marker="#M_asbcst"><c>asbcst</c></seealso>.</p>
+ See also <seecref marker="#M_asbcst"><c>asbcst</c></seecref>.</p>
</item>
<tag><marker id="M_sbct"/><c><![CDATA[+M<S>sbct <size>]]></c></tag>
<item>
@@ -673,8 +724,8 @@
<p>Smallest (<c>mseg_alloc</c>) multiblock carrier size (in
kilobytes). See the description on how sizes for <c>mseg_alloc</c>
multiblock carriers are decided in section
- <seealso marker="#mseg_mbc_sizes">
- The alloc_util Framework</seealso>.</p>
+ <seecref marker="#mseg_mbc_sizes">
+ The alloc_util Framework</seecref>.</p>
</item>
<tag><marker id="M_t"/><c><![CDATA[+M<S>t true|false]]></c></tag>
<item>
@@ -741,7 +792,7 @@
<item>
<p>Adds a small tag to each allocated block that contains basic
information about what it is and who allocated it. See
- <seealso marker="#M_atags"><c>+M&lt;S&gt;atags</c></seealso> for a
+ <seecref marker="#M_atags"><c>+M&lt;S&gt;atags</c></seecref> for a
more complete description.</p>
</item>
<tag><marker id="Mit"/><c>+Mit X</c></tag>
@@ -780,8 +831,8 @@
<item>
<p>Disables features that cannot be enabled while creating an
allocator configuration with
- <seealso marker="runtime_tools:erts_alloc_config">
- <c>erts_alloc_config(3)</c></seealso>.</p>
+ <seeerl marker="runtime_tools:erts_alloc_config">
+ <c>erts_alloc_config(3)</c></seeerl>.</p>
<note>
<p>This option is to be used only while running
<c>erts_alloc_config(3)</c>, <em>not</em> when
@@ -811,10 +862,10 @@
<p>Only some default values have been presented here. For information
about the currently used settings and the current status of the
allocators, see
- <seealso marker="erts:erlang#system_info_allocator">
- <c>erlang:system_info(allocator)</c></seealso> and
- <seealso marker="erts:erlang#system_info_allocator_tuple">
- <c>erlang:system_info({allocator, Alloc})</c></seealso>.</p>
+ <seeerl marker="erts:erlang#system_info_allocator">
+ <c>erlang:system_info(allocator)</c></seeerl> and
+ <seeerl marker="erts:erlang#system_info_allocator_tuple">
+ <c>erlang:system_info({allocator, Alloc})</c></seeerl>.</p>
<note>
<p>Most of these flags are highly implementation-dependent and
@@ -823,8 +874,8 @@
have been passed to it (it can even ignore them).</p>
</note>
- <p>The <seealso marker="runtime_tools:erts_alloc_config">
- <c>erts_alloc_config(3)</c></seealso>
+ <p>The <seeerl marker="runtime_tools:erts_alloc_config">
+ <c>erts_alloc_config(3)</c></seeerl>
tool can be used to aid creation of an
<c>erts_alloc</c> configuration that is suitable for a limited
number of runtime scenarios.</p>
@@ -832,12 +883,12 @@
<section>
<title>See Also</title>
- <p><seealso marker="erl"><c>erl(1)</c></seealso>,
- <seealso marker="erlang"><c>erlang(3)</c></seealso>,
- <seealso marker="runtime_tools:erts_alloc_config">
- <c>erts_alloc_config(3)</c></seealso>,
- <seealso marker="tools:instrument">
- <c>instrument(3)</c></seealso></p>
+ <p><seecom marker="erl"><c>erl(1)</c></seecom>,
+ <seeerl marker="erlang"><c>erlang(3)</c></seeerl>,
+ <seeerl marker="runtime_tools:erts_alloc_config">
+ <c>erts_alloc_config(3)</c></seeerl>,
+ <seeerl marker="tools:instrument">
+ <c>instrument(3)</c></seeerl></p>
</section>
</cref>
diff --git a/erts/doc/src/escript.xml b/erts/doc/src/escript_cmd.xml
index be1664b39f..b234b6752c 100644
--- a/erts/doc/src/escript.xml
+++ b/erts/doc/src/escript_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -42,7 +42,7 @@
the top <c>bin</c> directory of the standalone system and given
<c>.escript</c> as file extension. Further the (built-in)
<c>escript</c> program should be copied to the same directory and
- given the scripts original name (without the <c>.escript</c>
+ given the script's original name (without the <c>.escript</c>
extension). This will enable use of the bundled Erlang runtime
system.</p>
@@ -101,7 +101,7 @@ usage: factorial integer</pre>
a normal Erlang module. The first line is intended to be the
interpreter line, which invokes <c>escript</c>.</p>
<p>However, if you invoke the <c>escript</c> as follows,
- the contents of the first line does not matter, but it
+ the contents of the first line do not matter, but it
cannot contain Erlang code as it will be ignored:</p>
<pre>
$ <input>escript factorial 5</input></pre>
@@ -110,8 +110,8 @@ $ <input>escript factorial 5</input></pre>
enter the major mode for editing Erlang source files. If the
directive is present, it must be located on the second
line.</p>
- <p>If a comment selecting the <seealso
- marker="stdlib:epp#encoding">encoding</seealso> exists, it can be
+ <p>If a comment selecting the <seeerl
+ marker="stdlib:epp#encoding">encoding</seeerl> exists, it can be
located on the second line.</p>
<note>
<p>The encoding specified by the above mentioned comment
@@ -122,8 +122,8 @@ io:setopts([{encoding, unicode}])</code>
<p>The default encoding of the I/O-server for <c>standard_io</c>
is <c>latin1</c>, as the script runs in a non-interactive terminal
(see section
- <seealso marker="stdlib:unicode_usage#unicode_options_summary">
- Summary of Options</seealso>) in the STDLIB User's Guide.</p>
+ <seeguide marker="stdlib:unicode_usage#unicode_options_summary">
+ Summary of Options</seeguide>) in the STDLIB User's Guide.</p>
</note>
<p>On the third line (or second line depending on the presence
of the Emacs directive), arguments can be specified to
@@ -157,22 +157,22 @@ io:setopts([{encoding, unicode}])</code>
halt(1).</pre>
<p>
To retrieve the pathname of the script, call
- <seealso marker="#script_name-0">
+ <seecom marker="#script_name-0">
<c>escript:script_name()</c>
- </seealso>
+ </seecom>
from your script
(the pathname is usually, but not always, absolute).</p>
<p>If the file contains source code (as in the example above),
it is processed by the
- <seealso marker="stdlib:epp"><c>epp</c></seealso> preprocessor.
+ <seeerl marker="stdlib:epp"><c>epp</c></seeerl> preprocessor.
This means that you, for example, can use predefined macros
(such as <c>?MODULE</c>) and include directives like
the <c>-include_lib</c> directive. For example, use</p>
<pre>
-include_lib("kernel/include/file.hrl").</pre>
<p>to include the record definitions for the records used by function
- <seealso marker="kernel:file#read_link_info/1">
- <c>file:read_link_info/1</c></seealso>. You can also select
+ <seemfa marker="kernel:file#read_link_info/1">
+ <c>file:read_link_info/1</c></seemfa>. You can also select
encoding by including an encoding comment here, but if
a valid encoding comment exists on the second line, it takes
precedence.</p>
@@ -218,7 +218,7 @@ halt(1).</pre>
as one of the emulator flags. <c>Module</c> must be the
name of a module that has an exported <c>main/1</c>
function. For more information about archives and code loading, see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
<p>It is often very convenient to have a header in
the escript, especially on Unix platforms. However, the header
is optional, so you directly can "execute"
@@ -253,14 +253,14 @@ factorial 5 = 120</pre>
&nbsp;&nbsp;&nbsp;| {archive, ZipArchive}
&nbsp;&nbsp;&nbsp;| {archive, ZipFiles, ZipOptions}</v>
<v>SourceCode = BeamCode = file:filename() | binary()</v>
- <v>ZipArchive = <seealso marker="stdlib:zip#type-filename">
- zip:filename()</seealso> | binary()</v>
+ <v>ZipArchive = <seetype marker="stdlib:zip#filename">
+ zip:filename()</seetype> | binary()</v>
<v>ZipFiles = [ZipFile]</v>
<v>ZipFile = file:filename()
&nbsp;&nbsp;&nbsp;| {file:filename(), binary()}
&nbsp;&nbsp;&nbsp;| {file:filename(), binary(), file:file_info()}</v>
- <v>ZipOptions = [<seealso marker="stdlib:zip#type-create_option">
- zip:create_option()</seealso>]</v>
+ <v>ZipOptions = [<seetype marker="stdlib:zip#create_option">
+ zip:create_option()</seetype>]</v>
</type>
<desc>
<p>
@@ -376,7 +376,7 @@ ok
<p>
Parses an escript and extracts its sections.
This is the reverse of
- <seealso marker="#create-2"><c>create/2</c></seealso>.
+ <seecom marker="#create-2"><c>create/2</c></seecom>.
</p>
<p>All sections are returned even if they do not exist in the
escript. If a particular section happens to have the same
@@ -448,4 +448,3 @@ ok
</taglist>
</section>
</comref>
-
diff --git a/erts/doc/src/inet_cfg.xml b/erts/doc/src/inet_cfg.xml
index 0cfcc7905d..7934a6df86 100644
--- a/erts/doc/src/inet_cfg.xml
+++ b/erts/doc/src/inet_cfg.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2016</year>
+ <year>2004</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -68,7 +68,7 @@
<c>/etc/host.conf</c> and <c>/etc/nsswitch.conf</c>) in these modes,
except for <c>/etc/resolv.conf</c> and <c>/etc/hosts</c> that is read and
monitored for changes on Unix platforms for the internal DNS client
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>.</p>
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>.</p>
<p>If Erlang is started in long name distributed mode, it needs to
get the domain name from somewhere and reads system <c>inet</c>
@@ -138,7 +138,7 @@
<p><c><![CDATA[File = string()]]></c></p>
<p>Specify a system file that Erlang is to read resolver
configuration from for the internal DNS client
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>,
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>,
and monitor for changes, even if it does not exist.
The path must be absolute.</p>
<p>This can override the configuration parameters
@@ -200,7 +200,7 @@
<p><c><![CDATA[Port = integer()]]></c></p>
<p>Add address (and port, if other than default) of the primary
nameserver to use for
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>.
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>.
</p>
</item>
<tag><c><![CDATA[{alt_nameserver, IP [,Port]}.]]></c></tag>
@@ -209,14 +209,14 @@
<p><c><![CDATA[Port = integer()]]></c></p>
<p>Add address (and port, if other than default) of the secondary
nameserver for
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>.
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>.
</p>
</item>
<tag><c><![CDATA[{search, Domains}.]]></c></tag>
<item>
<p><c><![CDATA[Domains = [string()]]]></c></p>
<p>Add search domains for
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>.
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>.
</p>
</item>
<tag><c><![CDATA[{lookup, Methods}.]]></c></tag>
@@ -229,7 +229,7 @@
<item><c><![CDATA[file]]></c> (use host data retrieved from system
configuration files and/or the user configuration file)</item>
<item><c><![CDATA[dns]]></c> (use the Erlang DNS client
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>
for nameserver queries)</item>
</list>
<p>The lookup method <c><![CDATA[string]]></c> tries to
@@ -249,7 +249,7 @@
<item>
<p><c><![CDATA[Time = integer()]]></c></p>
<p>Set how often (in milliseconds) the resolver cache for
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>
is refreshed (that is, expired DNS records are deleted).
Defaults to 1 hour.</p>
</item>
@@ -258,28 +258,39 @@
<p><c><![CDATA[Time = integer()]]></c></p>
<p>Set the time to wait until retry (in milliseconds) for DNS queries
made by
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>.
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>.
Defaults to 2 seconds.</p>
</item>
<tag><c><![CDATA[{retry, N}.]]></c></tag>
<item>
<p><c><![CDATA[N = integer()]]></c></p>
<p>Set the number of DNS queries
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>
will try before giving up. Defaults to 3.</p>
</item>
+
+ <tag><c><![CDATA[{servfail_retry_timeout, Time}.]]></c></tag>
+ <item>
+ <p><c><![CDATA[Time = non_neg_integer()]]></c></p>
+ <p>After all name servers have been tried, there is a timeout
+ before the name servers are tried again. This is to prevent
+ the server from answering the query with what's in the servfail cache,
+ <seeerl marker="kernel:inet_res#servfail_retry_timeout"><c>inet_res(3)</c></seeerl>.
+ Defaults to 1500 milli seconds .</p>
+ </item>
+
<tag><c><![CDATA[{inet6, Bool}.]]></c></tag>
<item>
<p><c><![CDATA[Bool = true | false]]></c></p>
<p>Tells the DNS client
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>
to look up IPv6 addresses. Defaults to <c>false</c>.</p>
</item>
<tag><c><![CDATA[{usevc, Bool}.]]></c></tag>
<item>
<p><c><![CDATA[Bool = true | false]]></c></p>
<p>Tells the DNS client
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>
to use TCP (Virtual Circuit) instead of UDP. Defaults to
<c>false</c>.</p>
</item>
@@ -287,7 +298,7 @@
<item>
<p><c><![CDATA[Version = false | 0]]></c></p>
<p>Sets the EDNS version that
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>
will use. The only allowed version is zero. Defaults to <c>false</c>,
which means not to use EDNS.</p>
</item>
@@ -295,7 +306,7 @@
<item>
<p><c><![CDATA[N = integer()]]></c></p>
<p>Sets the allowed UDP payload size
- <seealso marker="kernel:inet_res"><c>inet_res(3)</c></seealso>
+ <seeerl marker="kernel:inet_res"><c>inet_res(3)</c></seeerl>
will advertise in EDNS queries. Also sets the limit
when the DNS query will be deemed too large for UDP
forcing a TCP query instead; this is not entirely
diff --git a/erts/doc/src/init.xml b/erts/doc/src/init.xml
index 5970627e11..1b22cbe478 100644
--- a/erts/doc/src/init.xml
+++ b/erts/doc/src/init.xml
@@ -38,16 +38,25 @@
<c>boot(BootArgs)</c>, where <c>BootArgs</c> is a list of
command-line arguments supplied to the Erlang runtime system from
the local operating system; see
- <seealso marker="erts:erl"><c>erl(1)</c></seealso>.</p>
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom>.</p>
<p><c>init</c> reads the boot script, which contains instructions on
how to initiate the system. For more information about boot scripts, see
- <seealso marker="sasl:script"><c>script(4)</c></seealso>.</p>
+ <seefile marker="sasl:script"><c>script(4)</c></seefile>.</p>
<p><c>init</c> also contains functions to restart, reboot, and stop
the system.</p>
</description>
+ <datatypes>
+ <datatype>
+ <name name="mode"/>
+ <desc>
+ <p>Code loading mode.</p>
+ </desc>
+ </datatype>
+ </datatypes>
+
<funcs>
<func>
<name name="boot" arity="1" since=""/>
@@ -57,14 +66,14 @@
when the emulator is started and coordinates system startup.</p>
<p><c><anno>BootArgs</anno></c> are all command-line arguments except
the emulator flags, that is, flags and plain arguments; see
- <seealso marker="erts:erl"><c>erl(1)</c></seealso>.</p>
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom>.</p>
<p><c>init</c> interprets some of the flags, see section
- <seealso marker="#flags">Command-Line Flags</seealso> below.
+ <seeerl marker="#flags">Command-Line Flags</seeerl> below.
The remaining flags ("user flags") and plain arguments are
passed to the <c>init</c> loop and can be retrieved by calling
- <seealso marker="#get_arguments/0"><c>get_arguments/0</c></seealso>
- and <seealso marker="#get_plain_arguments/0">
- <c>get_plain_arguments/0</c></seealso>, respectively.</p>
+ <seemfa marker="#get_arguments/0"><c>get_arguments/0</c></seemfa>
+ and <seemfa marker="#get_plain_arguments/0">
+ <c>get_plain_arguments/0</c></seemfa>, respectively.</p>
</desc>
</func>
@@ -116,7 +125,7 @@
<fsummary>Get all command-line user flags.</fsummary>
<desc>
<p>Returns all command-line flags and the system-defined flags, see
- <seealso marker="#get_argument/1"><c>get_argument/1</c></seealso>.</p>
+ <seemfa marker="#get_argument/1"><c>get_argument/1</c></seemfa>.</p>
</desc>
</func>
@@ -154,7 +163,7 @@
terminates. If command-line flag <c>-heart</c> was specified,
the <c>heart</c> program tries to reboot the system. For more
information, see
- <seealso marker="kernel:heart"><c>heart(3)</c></seealso>.</p>
+ <seeerl marker="kernel:heart"><c>heart(3)</c></seeerl>.</p>
<p>To limit the shutdown time, the time <c>init</c> is allowed
to spend taking down applications, command-line flag
<c>-shutdown_time</c> is to be used.</p>
@@ -165,12 +174,24 @@
<name name="restart" arity="0" since=""/>
<fsummary>Restart the running Erlang node.</fsummary>
<desc>
+ <p>The same as
+ <seemfa marker="#restart/1"><c>restart([])</c></seemfa>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="restart" arity="1" since="OTP 23.0"/>
+ <fsummary>Restart the running Erlang node.</fsummary>
+ <desc>
<p>The system is restarted <em>inside</em> the running Erlang
node, which means that the emulator is not restarted. All
applications are taken down smoothly, all code is unloaded,
and all ports are closed before the system is booted again in
- the same way as initially started. The same <c>BootArgs</c>
- are used again.</p>
+ the same way as initially started.</p>
+ <p>The same <c>BootArgs</c> are used when restarting the
+ system unless the <c>mode</c> option is given, allowing the
+ code loading mode to be set to either <c>embedded</c> or
+ <c>interactive</c>. All other <c>BootArgs</c> remain the same.</p>
<p>To limit the shutdown time, the time <c>init</c> is allowed
to spend taking down applications, command-line flag
<c>-shutdown_time</c> is to be used.</p>
@@ -193,7 +214,7 @@
<fsummary>Take down an Erlang node smoothly.</fsummary>
<desc>
<p>The same as
- <seealso marker="#stop/1"><c>stop(0)</c></seealso>.</p>
+ <seemfa marker="#stop/1"><c>stop(0)</c></seemfa>.</p>
</desc>
</func>
@@ -207,7 +228,7 @@
command-line flag <c>-heart</c> was specified, the <c>heart</c>
program is terminated before the Erlang node terminates.
For more information, see
- <seealso marker="kernel:heart"><c>heart(3)</c></seealso>.</p>
+ <seeerl marker="kernel:heart"><c>heart(3)</c></seeerl>.</p>
<p>To limit the shutdown time, the time <c>init</c> is allowed
to spend taking down applications, command-line flag
<c>-shutdown_time</c> is to be used.</p>
@@ -233,8 +254,8 @@
<item>
<p>Everything following <c>--</c> up to the next flag is
considered plain arguments and can be retrieved using
- <seealso marker="#get_plain_arguments/0">
- <c>get_plain_arguments/0</c></seealso>.</p>
+ <seemfa marker="#get_plain_arguments/0">
+ <c>get_plain_arguments/0</c></seemfa>.</p>
</item>
<tag><c>-code_path_choice Choice</c></tag>
<item>
@@ -254,9 +275,9 @@
useful when you want to elaborate with code loading from
archives without editing the <c>boot script</c>. For more
information about interpretation of boot scripts, see
- <seealso marker="sasl:script"><c>script(4)</c></seealso>.
+ <seefile marker="sasl:script"><c>script(4)</c></seefile>.
The flag has also a similar effect on how the code server works; see
- <seealso marker="kernel:code"><c>code(3)</c></seealso>.</p>
+ <seeerl marker="kernel:code"><c>code(3)</c></seeerl>.</p>
</item>
<tag><c>-epmd_module Module</c></tag>
<item>
@@ -286,8 +307,8 @@ BF</pre>
<item>
<p>Everything following <c>-extra</c> is considered plain
arguments and can be retrieved using
- <seealso marker="#get_plain_arguments/0">
- <c>get_plain_arguments/0</c></seealso>.</p>
+ <seemfa marker="#get_plain_arguments/0">
+ <c>get_plain_arguments/0</c></seemfa>.</p>
</item>
<tag><c>-run Mod [Func [Arg1, Arg2, ...]]</c></tag>
<item>
@@ -360,8 +381,8 @@ error</pre>
<section>
<title>See Also</title>
- <p><seealso marker="erl_prim_loader"><c>erl_prim_loader(3)</c></seealso>,
- <seealso marker="kernel:heart"><c>heart(3)</c></seealso></p>
+ <p><seeerl marker="erl_prim_loader"><c>erl_prim_loader(3)</c></seeerl>,
+ <seeerl marker="kernel:heart"><c>heart(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/erts/doc/src/introduction.xml b/erts/doc/src/introduction.xml
index 790e24f9f3..9d57823d12 100644
--- a/erts/doc/src/introduction.xml
+++ b/erts/doc/src/introduction.xml
@@ -42,7 +42,7 @@
<c><![CDATA[ERTS]]></c> itself.</p>
<p>For information on how to communicate with Erlang/OTP components
from earlier releases, see the documentation of system flag
- <seealso marker="erl#compat_rel"><c>+R</c></seealso> in <c>erl(1)</c>.
+ <seecom marker="erl#compat_rel"><c>+R</c></seecom> in <c>erl(1)</c>.
</p>
</note>
</section>
diff --git a/erts/doc/src/match_spec.xml b/erts/doc/src/match_spec.xml
index 48e502739a..16fe2c8a40 100644
--- a/erts/doc/src/match_spec.xml
+++ b/erts/doc/src/match_spec.xml
@@ -35,9 +35,9 @@
<p>A "match specification" (<c>match_spec</c>) is an Erlang term describing a
small "program" that tries to match something. It can be used
to either control tracing with
- <seealso marker="erlang#trace_pattern/3">erlang:trace_pattern/3</seealso>
+ <seemfa marker="erlang#trace_pattern/3">erlang:trace_pattern/3</seemfa>
or to search for objects in an ETS table with for example
- <seealso marker="stdlib:ets#select/2">ets:select/2</seealso>.
+ <seemfa marker="stdlib:ets#select/2">ets:select/2</seemfa>.
The match specification in many ways works like a small function in Erlang,
but is interpreted/compiled by the Erlang runtime system to something much more
efficient than calling an Erlang function. The match specification is also
@@ -144,7 +144,7 @@
</list>
<p>A match specification used in
- <seealso marker="stdlib:ets"><c>ets(3)</c></seealso>
+ <seeerl marker="stdlib:ets"><c>ets(3)</c></seeerl>
can be described in the following <em>informal</em> grammar:</p>
<list type="bulleted">
@@ -411,7 +411,7 @@
enable list takes precedence. If no tracer is specified, the same
tracer as the process executing the match specification is used (not the meta tracer).
If that process doesn't have tracer either, then trace flags are ignored.</p>
- <p>When using a <seealso marker="erl_tracer">tracer module</seealso>,
+ <p>When using a <seeerl marker="erl_tracer">tracer module</seeerl>,
the module must be loaded before the match specification is
executed. If it is not loaded, the match fails.</p>
<p>With three parameters to this function, the first is
@@ -562,7 +562,7 @@
<list type="bulleted">
<item>
<p>The variable <c><![CDATA['$_']]></c> expands to the whole
- <seealso marker="#match_target">match target</seealso> term.
+ <seeguide marker="#match_target">match target</seeguide> term.
</p>
</item>
<item>
@@ -867,7 +867,7 @@
['$_']}]
]]></code>
- <p>Function <seealso marker="stdlib:ets#test_ms/2"><c>ets:test_ms/2></c></seealso>
+ <p>Function <seemfa marker="stdlib:ets#test_ms/2"><c>ets:test_ms/2></c></seemfa>
can be useful for testing complicated ETS matches.</p>
</section>
</chapter>
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml
index c731cc7afa..14a3c2b58d 100644
--- a/erts/doc/src/notes.xml
+++ b/erts/doc/src/notes.xml
@@ -31,6 +31,1603 @@
</header>
<p>This document describes the changes made to the ERTS application.</p>
+<section><title>Erts 11.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix memory leak of about 6 words when
+ <c>erlang:process_flag/3</c> is called with a pid of an
+ already dead process. Bug exists since OTP-21.0.</p>
+ <p>
+ Own Id: OTP-17081 Aux Id: PR-2930 </p>
+ </item>
+ <item>
+ <p>
+ Fixed small memory leak in <c>erl_drv_send_term</c> and
+ <c>erl_drv_output_term</c> when failing due to the term
+ being invalid.</p>
+ <p>
+ Own Id: OTP-17089 Aux Id: PR-2934 </p>
+ </item>
+ <item>
+ <p>
+ The DTrace/SystemTap <c>process_heap_grow</c> probe is
+ now called with valid the heap and stack pointers for the
+ process in question.</p>
+ <p>
+ Own Id: OTP-17096 Aux Id: PR-2932 </p>
+ </item>
+ <item>
+ <p>Fixed a performance issue in memory allocation for
+ Linux kernels that didn't support <c>MADV_FREE</c>.</p>
+ <p>
+ Own Id: OTP-17124</p>
+ </item>
+ <item>
+ <p>
+ A <seeguide
+ marker="erts:erl_dist_protocol#new_link_protocol">new
+ link protocol</seeguide> has been introduced which
+ prevents links from ending up in an inconsistent state
+ where one participant considers itself linked while the
+ other doesn't. This bug has always existed in the
+ distributed case, but has since OTP 21 also existed in
+ the node local case since the distributed link protocol
+ then was adopted also for node local links. The bug
+ could, however, only trigger if both participants
+ operated on the link simultaneously.</p>
+ <p>
+ Own Id: OTP-17127</p>
+ </item>
+ <item>
+ <p>
+ Fix memory leak when receiving sigchld from port program
+ to already dead port.</p>
+ <p>
+ Own Id: OTP-17163</p>
+ </item>
+ <item>
+ <p>
+ Fix bug where complex seq_trace tokens (that is lists,
+ tuples, maps etc) could becomes corrupted by the GC. The
+ bug was introduced in OTP-21.</p>
+ <p>
+ Own Id: OTP-17209 Aux Id: PR-3039 </p>
+ </item>
+ <item>
+ <p>
+ Fixed WSLPATH environment variable addition to PATH on
+ windows, the last character was lost.</p>
+ <p>
+ Own Id: OTP-17229</p>
+ </item>
+ <item>
+ <p>
+ Fixed a bug in the timer implementation which could cause
+ timers that were set to more than 37.25 hours in the
+ future to be delayed. This could occur if there were
+ multiple timers scheduled to be triggered very close in
+ time, but still at different times, and the scheduler
+ thread handling the timers was not able to handle them
+ quickly enough. Delayed timers were in this case
+ triggered when another unrelated timer was triggered.</p>
+ <p>
+ Own Id: OTP-17253</p>
+ </item>
+ <item>
+ <p>
+ Fixed small memory leak in <c>erlang:trace/3</c> if
+ option <c>{tracer,_}</c> is included and the option list
+ is invalid or the call races with a concurrent trace or
+ code change operation.</p>
+ <p>
+ Own Id: OTP-17265 Aux Id: PR-4596 </p>
+ </item>
+ <item>
+ <p>
+ Fix configure check for <c>inet_pton</c> on 32-bit
+ windows. The failure of this check would cause epmd to be
+ built without ipv6 support.</p>
+ <p>
+ Own Id: OTP-17283</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Various address sanitizer support.</p>
+ <p>
+ Own Id: OTP-16959 Aux Id: PR-2965 </p>
+ </item>
+ <item>
+ <p>The emulator will now honor <c>cgroup2</c> CPU
+ quotas.</p>
+ <p>
+ Own Id: OTP-17002</p>
+ </item>
+ <item>
+ <p>
+ Improved memory barrier usage on ARMv8 hardware, and
+ specifically on Apple silicon.</p>
+ <p>
+ Own Id: OTP-17195 Aux Id: PR-4505, PR-4538 </p>
+ </item>
+ <item>
+ <p>
+ Improved memory barrier usage on 64-bit POWER hardware.</p>
+ <p>
+ Own Id: OTP-17200 Aux Id: PR-4510 </p>
+ </item>
+ <item>
+ <p>
+ Fix a file descriptor leak when using sendfile and the
+ remote side closes the connection. This bug has been
+ present since OTP-21.0.</p>
+ <p>
+ Own Id: OTP-17244</p>
+ </item>
+ <item>
+ <p>
+ Refinement of the documentation of the
+ <c>message_queue_data</c> process flag.</p>
+ <p>
+ Own Id: OTP-17252 Aux Id: PR-4568 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug that could cause some work scheduled for
+ execution on scheduler threads to be delayed until other
+ similar work appeared. Beside delaying various cleanup of
+ internal data structures also the following could be
+ delayed:</p> <list> <item>Termination of a distribution
+ controller process</item> <item>Disabling of the
+ distribution on a node</item> <item>Gathering of memory
+ allocator information using the <c>instrument</c>
+ module</item> <item>Enabling, disabling, and gathering of
+ <c>msacc</c> information</item> <item>Delivery of
+ <c>'CHANGE'</c> messages when time offset is
+ monitored</item> <item>A call to
+ <c>erlang:cancel_timer()</c></item> <item>A call to
+ <c>erlang:read_timer()</c></item> <item>A call to
+ <c>erlang:statistics(io | garbage_collection |
+ scheduler_wall_time)</c></item> <item>A call to
+ <c>ets:all()</c></item> <item>A call to
+ <c>erlang:memory()</c></item> <item>A call to
+ <c>erlang:system_info({allocator | allocator_sizes,
+ _})</c></item> <item>A call to
+ <c>erlang:trace_delivered()</c></item> </list> <p>The bug
+ existed on runtime systems running on all types of
+ hardware except for x86/x86_64.</p>
+ <p>
+ Own Id: OTP-17185</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.7</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Make windows installer remove write access rights for non
+ admin users when installing to a non default directory.
+ Reduces the risk for DLL sideloading, but the user should
+ always be aware of the access rights for the
+ installation.</p>
+ <p>
+ Own Id: OTP-17097</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The <c>suspend_process()</c> and <c>resume_process()</c>
+ BIFs did not check their arguments properly which could
+ cause an emulator crash.</p>
+ <p>
+ Own Id: OTP-17080</p>
+ </item>
+ <item>
+ <p>
+ The runtime system would get into an infinite loop if the
+ runtime system was started with more than 1023 file
+ descriptors already open.</p>
+ <p>
+ Own Id: OTP-17088 Aux Id: ERIERL-580 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix emulator crash when sending small bit-strings over
+ Erlang distribution while the connection is being setup.</p>
+ <p>
+ The fault was introduced in OTP-23.0</p>
+ <p>
+ Own Id: OTP-17083 Aux Id: ERIERL-572 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bug which could cause VM crash when a NIF is loaded
+ at the same time as the Erlang implementation of the NIF
+ is called. Bug exists since OTP 23.0.</p>
+ <p>
+ Own Id: OTP-16859</p>
+ </item>
+ <item>
+ <p>
+ Fixed <c>enif_make_map_*</c> functions in debug build
+ when given environment from <c>enif_alloc_env</c>.</p>
+ <p>
+ Own Id: OTP-16863 Aux Id: ERL-1352 </p>
+ </item>
+ <item>
+ <p>
+ Fixed broken configuration option <c>--disable-pie</c>.</p>
+ <p>
+ Own Id: OTP-16864</p>
+ </item>
+ <item>
+ <p>
+ Fixed rare distribution bug in race between received
+ signal (link/monitor/spawn_request/spawn_reply) and
+ disconnection. Symptom: VM crash. Since: OTP 21.0.</p>
+ <p>
+ Own Id: OTP-16869 Aux Id: ERL-1337 </p>
+ </item>
+ <item>
+ <p>Fixed a performance issue when extremely many items
+ were stored in the process dictionary. (Fixing this bug
+ also eliminates a compiler warning emitted by the latest
+ version of Clang.)</p>
+ <p>
+ Own Id: OTP-16888</p>
+ </item>
+ <item>
+ <p>
+ Remove <c>-ftree-copyrename</c> from flags passed to
+ compiler when building erts. The flag is not used by
+ modern gcc's and is not supported by clang.</p>
+ <p>
+ Own Id: OTP-16894</p>
+ </item>
+ <item>
+ <p>Modules using complicated nested binary comprehensions
+ could fail to load.</p>
+ <p>
+ Own Id: OTP-16899</p>
+ </item>
+ <item>
+ <p>Fixed a race in <c>file:read_file/1</c> were an
+ incomplete file could be returned if another OS process
+ swapped the file out while reading.</p>
+ <p>
+ Own Id: OTP-16948 Aux Id: PR-2792 </p>
+ </item>
+ <item>
+ <p>The call <c>list_to_integer("10", true)</c> would
+ return <c>4</c> instead of raising an exception. Certain
+ other atoms would also be interpreted as a number
+ base.</p>
+ <p>
+ Own Id: OTP-17030</p>
+ </item>
+ <item>
+ <p>
+ On macOS 11 (Big Sur), erl would not start if the maximum
+ number of file descriptors were unlimited (<c>ulimit -n
+ unlimited</c>).</p>
+ <p>
+ Own Id: OTP-17055 Aux Id: ERL-1417 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add manifest to all executables and dynamic libraries.</p>
+ <p>
+ Own Id: OTP-17067 Aux Id: PR-2907 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a crash when exceptions were thrown during call
+ time tracing.</p>
+ <p>
+ Own Id: OTP-16994</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bugs causing issues when enabling the ERTS internal
+ allocators on a system built with the undocumented and
+ unsupported <c>SMALL_MEMORY</c> feature.</p>
+ <p>
+ Own Id: OTP-16939</p>
+ </item>
+ <item>
+ <p>
+ The inet driver used to use 16 as maximum elements in an
+ I/O vector passed to <c>writev()</c> (and
+ <c>WSASend()</c> on Windows). When the data to send
+ contained lots of elements, this caused a performance
+ degradation since repeated calls to <c>writev()</c> had
+ to be made to a much larger extent. The inet driver now
+ looks up actual maximum amount of elements that can be
+ used on the system, instead of just assuming 16. On most
+ systems this will result in a maximum amount of I/O
+ vector elements of 1024.</p>
+ <p>
+ As of OTP 23.0 the term encoding of signals to send over
+ the distribution are encoded into I/O vectors of buffers
+ instead of into a single buffer. Reference counted
+ binaries are referred to directly from the I/O vector
+ instead of being copied into the single buffer. That is,
+ Erlang signals containing huge amounts of reference
+ counted binaries was effected by this performance
+ degradation.</p>
+ <p>
+ Own Id: OTP-16955 Aux Id: ERL-1343, OTP-15618 </p>
+ </item>
+ <item>
+ <p>
+ In the distributed case, a faulty <c>reply</c> option in
+ a call to the <c>spawn_request()</c> BIF erroneously
+ caused a <c>badarg</c> exception instead of a
+ <c>badopt</c> error message reply.</p>
+ <p>
+ Own Id: OTP-16991 Aux Id: OTP-15251 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ inet:setopts([{active,once}]) wakes up IO polling thread
+ unnecessarily, leading to lock contention and visibly
+ higher CPU utilization.</p>
+ <p>
+ Own Id: OTP-16847 Aux Id: ERL-1301 </p>
+ </item>
+ <item>
+ <p>
+ Two bugs in the ERTS internal thread wakeup functionality
+ have been fixed. These bugs mainly hit when all threads
+ in the system tried to go to sleep. When the bugs were
+ triggered, certain operations were delayed until a thread
+ woke up due to some other reason. Most important
+ operations effected were code loading, persistent term
+ updates, and memory deallocation.</p>
+ <p>
+ Own Id: OTP-16870</p>
+ </item>
+ <item>
+ <p>
+ Fixed bug in <c>ets:select_replace/2</c> on
+ <c>compressed</c> tables that could produce faulty
+ results or VM crash. Bug exists since OTP 20.</p>
+ <p>
+ Own Id: OTP-16874 Aux Id: ERL-1356, PR-2763 </p>
+ </item>
+ <item>
+ <p>When compiling Erlang/OTP on macOS using Xcode 12, the
+ performance of the BEAM interpreter would be
+ degraded.</p>
+ <p>
+ Own Id: OTP-16892</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ As of OTP 22, the allocator specific memory carrier pools
+ were replaced by a node global carrier pool. This
+ unfortunately caused substantial memory fragmentation in
+ some cases due to long lived data being spread into
+ carriers used by allocators mainly handling short lived
+ data.</p>
+ <p>
+ A new command line argument <c>+M&lt;S&gt;cp</c> has been
+ introduced with which one can enable the old behavior as
+ well as configuring other behaviors for the carrier
+ pools. In order to configure the old behavior, with
+ allocator specific carrier pools for all allocators, pass
+ <c>+Mucp :</c> (including the colon character) as a
+ command line argument to <c>erl</c> when starting the
+ Erlang system.</p>
+ <p>
+ The default configuration for carrier pools will be
+ changed to <c>+Mucp :</c> some time in the future, but
+ not in this patch.</p>
+ <p>
+ Own Id: OTP-16856</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Update the documentation of the abstract format to use
+ <c>ANNO</c> instead of <c>LINE</c>.</p>
+ <p>
+ Own Id: OTP-16625 Aux Id: PR-2609 </p>
+ </item>
+ <item>
+ <p>The emulator will no longer revert to the default
+ number of schedulers when running under a CPU quota lower
+ than 1 CPU.</p>
+ <p>
+ Own Id: OTP-16710 Aux Id: ERL-1280 </p>
+ </item>
+ <item>
+ <p>Fixed a problem with crash dumps. When a process that
+ contained reference to literals internally created by the
+ runtime system (such as the tuple returned by
+ <c>os:type/0</c>), the literal would not be included in
+ the crash dump and the crashdump viewer would complain
+ about the heap being incomplete.</p>
+ <p>
+ Own Id: OTP-16713</p>
+ </item>
+ <item>
+ <p>
+ Fix configure detection of PGO for clang.</p>
+ <p>
+ Own Id: OTP-16738</p>
+ </item>
+ <item>
+ <p>
+ The to_erl program has been fixed to correctly interpret
+ newline as only newline and not newline+return.</p>
+ <p>
+ This bug would cause the terminal to behave strangely
+ when using lines longer than the terminal size.</p>
+ <p>
+ Own Id: OTP-16741</p>
+ </item>
+ <item>
+ <p>
+ A race condition when changing process priority by
+ calling <c>process_flag(priority, Prio)</c> could cause
+ elevation of priority for a system task to be ignored.
+ This bug hit if the system task was scheduled on the
+ process calling <c>process_flag()</c> at the same time as
+ the priority was changed. The bug is quite harmless and
+ should hit very seldom if ever.</p>
+ <p>
+ Own Id: OTP-16770</p>
+ </item>
+ <item>
+ <p>
+ Adjust <c>/bin/sh</c> to <c>/system/bin/sh</c> in scripts
+ when installing on Android.</p>
+ <p>
+ Own Id: OTP-16833 Aux Id: PR-2729 </p>
+ </item>
+ <item>
+ <p>In rare circumstances, when loading a BEAM file
+ generated by an alternative code generator (not the
+ Erlang compiler in OTP) or from handwritten or patched
+ BEAM code, the loader could do an unsafe
+ optimization.</p>
+ <p>
+ Own Id: OTP-16850 Aux Id: ERL-1344 </p>
+ </item>
+ <item>
+ <p>
+ A memory and file descriptor leak in socket has been
+ fixed. (When a newly opened socket that had not entered
+ the fd into the VM's poll set (neither received, sent,
+ accepted nor connected) was abandoned without closing
+ (process died), after assigning a different controlling
+ process, then a memory block and the file descriptor
+ could be leaked.)</p>
+ <p>
+ Own Id: OTP-16857</p>
+ </item>
+ <item>
+ <p>
+ The documentation of <c>statistics(run_queue)</c>
+ erroneously stated that it returns the total length of
+ all normal run queues when it is the total length of all
+ normal and dirty CPU run queues that is returned. The
+ documentation has been updated to reflect the actual
+ behavior.</p>
+ <p>
+ Own Id: OTP-16866 Aux Id: ERL-1355 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Changes in order to build on the Haiku operating system.</p>
+ <p>
+ Thanks to Calvin Buckley</p>
+ <p>
+ Own Id: OTP-16707 Aux Id: PR-2638 </p>
+ </item>
+ <item>
+ <p>
+ When building the inet driver on Windows, there where
+ many compiler warnings regarding type casting (used when
+ calling the debug macro). This has now been resolved.</p>
+ <p>
+ Own Id: OTP-16715</p>
+ </item>
+ <item>
+ <p>
+ Make (use of) the socket registry optional (still enabled
+ by default). Its now possible to build OTP with the
+ socket registry turned off, turn it off by setting an
+ environment variable and controlling in runtime (via
+ function calls and arguments when creating sockets).</p>
+ <p>
+ Own Id: OTP-16763</p>
+ </item>
+ <item>
+ <p>
+ Change default filename encoding on android to UTF-8.</p>
+ <p>
+ Own Id: OTP-16821 Aux Id: PR-2733 </p>
+ </item>
+ <item>
+ <p>
+ Clarification of the format of the atom cache header used
+ by the distribution.</p>
+ <p>
+ Own Id: OTP-16848 Aux Id: PR-2737 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.0.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The ERTS internal I/O poll implementation could get into
+ an inconsistent state causing input events to be ignored.</p>
+ <p>
+ Own Id: OTP-16780 Aux Id: PR-2701 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.0.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bug in <c>erlang:load_nif/2</c> that could cause it
+ to throw <c>badarg</c> exception if a concurrent code
+ change operation was in progress. Bug existed since OTP
+ 23.0.</p>
+ <p>
+ Own Id: OTP-16704 Aux Id: ERL-1273, PR-16704 </p>
+ </item>
+ <item>
+ <p>
+ Minor fix of debug compiled VM.</p>
+ <p>
+ Own Id: OTP-16717</p>
+ </item>
+ <item>
+ <p>
+ An unintentional reuse of an already used emulator
+ internal event object could cause a wakeup signal to a
+ thread to be lost. In worst case this could cause the
+ runtime system to hang. This hang was however quite rare.</p>
+ <p>
+ Own Id: OTP-16766 Aux Id: ERL-1304 </p>
+ </item>
+ <item>
+ <p>
+ NIF threads and driver threads on non-Linux systems
+ leaked internal resources when terminating. On Windows
+ these resources were one event per thread. On most other
+ systems one mutex and one condition variable per thread.
+ On these other systems that also lacked
+ <c>pthread_cond_timedwait()</c> also a pipe with its file
+ descriptors was leaked.</p>
+ <p>
+ Own Id: OTP-16772 Aux Id: ERL-1304 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bug when sending an export fun (eg
+ <c>lists:reverse/1</c>) on a not yet established
+ connection. It could cause VM crash. Bug exists since OTP
+ 23.0.</p>
+ <p>
+ Own Id: OTP-16693 Aux Id: ERL-1254, PR-2640 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.0.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The functionality utilized by BIFs for temporary
+ disabling of garbage collection while yielding could
+ cause system task queues to become inconsistent on a
+ process executing such a BIF. Process system tasks are
+ for example utilized when purging code, garbage
+ collecting literal data, and when issuing an ordinary
+ garbage collection from another process.</p>
+ <p>
+ The bug does not trigger frequently. Multiple code purges
+ in direct sequence makes it more likely that this bug is
+ triggered. In the cases observed, this has resulted in a
+ hanging code purge operation.</p>
+ <p>
+ Own Id: OTP-16639 Aux Id: ERL-1236 </p>
+ </item>
+ <item>
+ <p>
+ SCTP and UDP recv/2,3 hangs indefinitely if socket is
+ closed while recv is called (socket in passive mode).</p>
+ <p>
+ Own Id: OTP-16654 Aux Id: ERL-1242 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 11.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>BIFs now behave like ordinary functions with regard to
+ tracing, allowing <c>call_count</c> tracing and fixing a
+ few bugs where return trace messages were lost when BIFs
+ tail-called themselves or other functions
+ ("trapping").</p>
+ <p>
+ Own Id: OTP-14734 Aux Id: ERL-496 </p>
+ </item>
+ <item>
+ <p>
+ Fix various compiler warnings on 64-bit Windows.</p>
+ <p>
+ Own Id: OTP-15800</p>
+ </item>
+ <item>
+ <p><c>erlang:fun_info(fun foo/1, name/1)</c> used to
+ return a function name based on the name of the function
+ that <c>fun foo/1</c> was used in. The name returned is
+ now <c>-fun.foo/1-</c>.</p>
+ <p>
+ Own Id: OTP-15837</p>
+ </item>
+ <item>
+ <p><c>file:allocate/3</c> will now update the file size
+ on all platforms.</p>
+ <p>
+ Own Id: OTP-16155 Aux Id: PR-2408 </p>
+ </item>
+ <item>
+ <p>
+ <c>erlang:decode_packet</c> with type set to <c>httph</c>
+ no longer accepts http headers that have whitespaces in
+ between the header name and the colon. That is:</p>
+ <p>
+ <c> Content-Type : text/html </c></p>
+ <p>
+ is no longer allowed. This has been changed to conform
+ with RFC 7230 and thus protect against http desync
+ attacks.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16168 Aux Id: ERL-1053 </p>
+ </item>
+ <item>
+ <p>
+ Fix the quoting rules in <c>erl -args_file</c>,
+ <c>ERL_FLAGS</c>, <c>ERL_AFLAGS</c> and <c>ERL_ZFLAGS</c>
+ to work as unix sh quoting.</p>
+ <p>
+ This bug fix can make previous configuration options to
+ <c>erl</c> passed through <c>ERL_FLAGS</c>,
+ <c>ERL_AFLAGS</c>, <c>ERL_ZFLAGS</c> or <c>-args_file</c>
+ not be interpreted in the same way as before the fix.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16244 Aux Id: ERL-1051 </p>
+ </item>
+ <item>
+ <p>
+ Fix the Erlang distribution to handle the scenario when a
+ node connects that can handle message fragmentation but
+ can not handle the atom cache. This bug only affects
+ users that have implemented a custom distribution
+ carrier. It has been present since OTP-21.</p>
+ <p>
+ The <c>DFLAG_FRAGMENT</c> distribution flag was added to
+ the set of flags that can be rejected by a distribution
+ implementation.</p>
+ <p>
+ Own Id: OTP-16284</p>
+ </item>
+ <item>
+ <p>
+ Compiling a match specification with excessive nesting
+ caused the runtime system to crash due to scheduler stack
+ exhaustion. Instead of crashing the runtime system,
+ effected functions will now raise a <c>system_limit</c>
+ error exception in this situation.</p>
+ <p>
+ Own Id: OTP-16431 Aux Id: ERL-592 </p>
+ </item>
+ <item>
+ <p>Fixed a bug that prevented Erlang from being started
+ on Windows if it were installed on certain paths.</p>
+ <p>
+ Own Id: OTP-16478 Aux Id: ERL-1115 </p>
+ </item>
+ <item>
+ <p>Fix bug on Windows causing bad performance when
+ standard input is closed, especially if the VM is only
+ assigned one CPU core. Could be provoked for example by
+ starting erl or escript via function <c>os:cmd/1</c>.
+ Could be avoided with command line option
+ <c>-noinput</c>.</p> <p>The bad performance was caused by
+ an io thread spinning indefinitely.</p>
+ <p>
+ Own Id: OTP-16521 Aux Id: ERL-716 </p>
+ </item>
+ <item>
+ <p>Fixed a bug on Unix platforms that would cause
+ <c>file:read_file_info/1</c> to return incorrect results
+ if the emulator's effective user or group id differed
+ from its actual ones.</p>
+ <p>
+ Own Id: OTP-16571</p>
+ </item>
+ <item>
+ <p>
+ socket: Compile problems on Android when PACKET_FASTROUTE
+ and PACKET_USER are both defined and has the same value.
+ Use of PACKET_FASTROUTE has been removed as it may be
+ unused and also only for none user-land.</p>
+ <p>
+ Own Id: OTP-16576 Aux Id: ERL-1208 </p>
+ </item>
+ <item>
+ <p>
+ Fixed bug in erl_crash.dump generation that could cause a
+ SEGV core dump if a recently cancelled timer was found.</p>
+ <p>
+ Own Id: OTP-16596 Aux Id: ERL-1105, PR-2606 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Improved concurrency of <c>erlang:load_nif/2</c> as it
+ does no longer block other schedulers from executing
+ during initial load of a NIF library.</p>
+ <p>
+ Own Id: OTP-10278</p>
+ </item>
+ <item>
+ <p>EEP-52 has been implemented.</p>
+ <p>In binary matching, the size of the segment to be
+ matched is now allowed to be a guard expression, and
+ similarly in map matching the keys can now be guard
+ expressions. See the Erlang Reference Manual and
+ Programming Examples for more details.</p>
+ <p>Language compilers or code generators that generate
+ Core Erlang code may need to be updated to be compatible
+ with the compiler in OTP 23. For more details, see the
+ section Backwards Compatibility in <url
+ href="http://erlang.org/eeps/eep-0052.html">EEP
+ 52</url>.</p>
+ <p>
+ Own Id: OTP-14708</p>
+ </item>
+ <item>
+ <p>Internally in BEAM, handling of continuation pointers
+ has been simplified. This change is not user-visible,
+ except when examing a process stack in the crashdump
+ viewer. The continuation pointer for a function will now
+ be stored below the y(0) for that function.</p>
+ <p>
+ Own Id: OTP-15077</p>
+ </item>
+ <item>
+ <p><c>seq_trace</c> tokens are now propagated to spawned
+ processes.</p>
+ <p>
+ Own Id: OTP-15232 Aux Id: ERL-700 </p>
+ </item>
+ <item>
+ <p>Improvements of distributed spawn operations. These
+ include both scalability and performance improvements as
+ well as new functionality.</p> <p>New functionality:</p>
+ <list> <item><p>A distributed <seemfa
+ marker="erts:erlang#spawn_monitor/4"><c>spawn_monitor()</c></seemfa>
+ BIF.</p></item> <item><p>Support for <c>monitor</c>
+ option in the distributed <seemfa
+ marker="erts:erlang#spawn_opt/5"><c>spawn_opt()</c></seemfa>
+ BIF.</p></item> <item><p>New <seemfa
+ marker="erts:erlang#spawn_request/5"><c>spawn_request()</c></seemfa>
+ BIFs for asynchronous spawn of processes.
+ <c>spawn_request()</c> supports all options that
+ <c>spawn_opt()</c> support plus a few more.</p></item>
+ </list>
+ <p>
+ Own Id: OTP-15251</p>
+ </item>
+ <item>
+ <p>
+ Make <c>ets:insert/2</c> and <c>ets:insert_new/2</c>
+ yield scheduler execution on long lists of records to
+ insert.</p>
+ <p>
+ Own Id: OTP-15517 Aux Id: ERL-560 </p>
+ </item>
+ <item>
+ <p>
+ Increased size of node incarnation numbers (aka
+ "creation"), from 2 bits to 32 bits. This will reduce the
+ risk of pids/ports/refs, from different node incarnation
+ with the same name, being mixed up.</p>
+ <p>
+ Own Id: OTP-15603</p>
+ </item>
+ <item>
+ <p>The runtime system can now encode Erlang terms to the
+ Erlang external term format as I/O vectors. The main
+ benefit of this is that reference counted binaries can be
+ referred to directly instead of copied into a new
+ binary.</p> <p>The default Erlang distribution over TCP
+ will always utilize this. Alternate distribution
+ implementations utilizing a port as distribution
+ controller will utilize this if the driver implements the
+ <seecref
+ marker="erts:driver_entry#outputv"><c>outputv</c></seecref>
+ callback. Alternate Erlang distribution implementations
+ utilizing a process as distribution controller will
+ utilize this if I/O vectors are utilized by the
+ functionality that processes the data returned from
+ <seemfa
+ marker="erts:erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data()</c></seemfa>.</p>
+ <p>The return type for data returned by <seemfa
+ marker="erts:erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data()</c></seemfa>
+ has been changed from <c>iodata()</c> to <c>iovec()</c>.
+ Note that <c>iovec()</c> data is valid <c>iodata()</c> so
+ old implementations using
+ <c>erlang:dist_ctrl_get_data()</c> do not need to be
+ changed, but may benefit from being changed depending on
+ usage scenario.</p> <p>The new BIFs <seemfa
+ marker="erts:erlang#term_to_iovec/1"><c>term_to_iovec/1</c></seemfa>
+ and <seemfa
+ marker="erts:erlang#term_to_iovec/2"><c>term_to_iovec/2</c></seemfa>
+ have been introduced. These work exactly as
+ <c>term_to_binary()</c> with the corresponding arity
+ except the return type.</p>
+ <p>
+ Own Id: OTP-15618</p>
+ </item>
+ <item>
+ <p>Improved ETS scalability of concurrent calls that
+ change the size of a table, like <c>ets:insert/2</c> and
+ <c>ets:delete/2</c>.</p> <p>This performance feature was
+ implemented for <c>ordered_set</c> in OTP 22.0 and does
+ now apply for all ETS table types.</p> <p>The improved
+ scalability may come at the cost of longer latency of
+ <c>ets:info(T,size)</c> and <c>ets:info(T,memory)</c>. A
+ new table option <c>decentralized_counters</c> has
+ therefore been added. It is default <c>true</c> for
+ <c>ordered_set</c> with <c>write_concurrency</c> enabled
+ and default <c>false</c> for all other table types.</p>
+ <p>
+ Own Id: OTP-15744 Aux Id: OTP-15623, PR-2229 </p>
+ </item>
+ <item>
+ <p>Directories can now be opened by <c>file:open/2</c>
+ when passing the <c>directory</c> option.</p>
+ <p>
+ Own Id: OTP-15835 Aux Id: PR-2212 </p>
+ </item>
+ <item>
+ <p>
+ Add Hygon Dhyana as known processor to enable support for
+ atomic operations.</p>
+ <p>
+ Own Id: OTP-15840</p>
+ </item>
+ <item>
+ <p>
+ Make <c>erlang:phash2</c> functions consume reductions
+ proportional to the size of the input term and yield
+ scheduler when reductions are depleted.</p>
+ <p>
+ Own Id: OTP-15842 Aux Id: PR-2182 </p>
+ </item>
+ <item>
+ <p>
+ Fix various build issues when compiling Erlang/OTP to the
+ IBM AIX platform.</p>
+ <p>
+ Own Id: OTP-15866 Aux Id: PR-2110 </p>
+ </item>
+ <item>
+ <p>
+ Add configure options <c>--enable-pie</c> and
+ <c>--disable-pie</c> to control the build of position
+ independent executables.</p>
+ <p>
+ Own Id: OTP-15868</p>
+ </item>
+ <item>
+ <p><c>file:read_file_info/2</c> can now be used on opened
+ files and directories.</p>
+ <p>
+ Own Id: OTP-15956 Aux Id: PR-2231 </p>
+ </item>
+ <item>
+ <p>
+ Add arity-1 versions of <c>atom_to_binary</c>,
+ <c>binary_to_atom</c> and <c>binary_to_existing_atom</c>,
+ all with <c>utf8</c> as default encoding.</p>
+ <p>
+ Own Id: OTP-15995 Aux Id: PR-2358 </p>
+ </item>
+ <item>
+ <p>
+ Optimized the erts internal hash table implementation for
+ faster lookups. The internal hash is used for things
+ like; the process registry, executing erlang:apply/2,
+ executing M:func(test), and more.</p>
+ <p>
+ Own Id: OTP-16014 Aux Id: PR-2345 </p>
+ </item>
+ <item>
+ <p>CPU quotas are now taken into account when deciding
+ the default number of online schedulers, improving
+ performance in container environments where quotas are
+ applied, such as <c>docker</c> with the <c>--cpus</c>
+ flag.</p>
+ <p>
+ Own Id: OTP-16105 Aux Id: ERL-927 </p>
+ </item>
+ <item>
+ <p>
+ The <c>-config</c> option to <c>erl</c> now can take
+ multiple config files without repeating the
+ <c>-config</c> option. Example:</p>
+ <p>
+ erl -config sys local</p>
+ <p>
+ Own Id: OTP-16148 Aux Id: PR-2373 </p>
+ </item>
+ <item>
+ <p>
+ Removed the <c>scheduler_poll</c> and <c>async I/O</c>
+ dtrace and LTTng trace probes.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16215</p>
+ </item>
+ <item>
+ <p>
+ Optimized <c>persistent_term:put/2</c> and <c>erase/1</c>
+ to consume less CPU in many cases.</p>
+ <p>
+ Own Id: OTP-16237 Aux Id: PR-2389 </p>
+ </item>
+ <item>
+ <p>The possibility to run Erlang distribution without
+ relying on EPMD has been extended. To achieve this a
+ couple of new options to the inet distribution has been
+ added.</p> <taglist> <tag>-dist_listen false</tag>
+ <item>Setup the distribution channel, but do not listen
+ for incoming connection. This is useful when you want to
+ use the current node to interact with another node on the
+ same machine without it joining the entire
+ cluster.</item> <tag>-erl_epmd_port Port</tag>
+ <item>Configure a default port that the built-in EPMD
+ client should return. This allows the local node to know
+ the port to connect to for any other node in the
+ cluster.</item> </taglist> <p>The <c>erl_epmd</c>
+ callback API has also been extended to allow returning
+ <c>-1</c> as the creation which means that a random
+ creation will be created by the node.</p>
+ <p>In addition a new callback function called
+ <c>listen_port_please</c> has been added that allows the
+ callback to return which listen port the distribution
+ should use. This can be used instead of
+ <c>inet_dist_listen_min/max</c> if the listen port is to
+ be fetched from an external service.</p>
+ <p>
+ Own Id: OTP-16250</p>
+ </item>
+ <item>
+ <p>
+ On systems without <c>closefrom()</c>, such as Linux,
+ iterating over all possible file descriptors and calling
+ <c>close()</c> for each is inefficient. This is markedly
+ so when the maximum number of file descriptors has been
+ tuned to a large number.</p>
+ <p>
+ Instead, in erl_child_setup, walk the open descriptors
+ under <c>/dev/fd</c> and close only those which are open.</p>
+ <p>
+ This optimization affects the CPU usage of starting a new
+ Erlang instance.</p>
+ <p>
+ Own Id: OTP-16270</p>
+ </item>
+ <item>
+ <p>
+ Optimized <c>maps:merge/2</c> for trivial cases of an
+ empty map(s) or same map.</p>
+ <p>
+ Own Id: OTP-16283 Aux Id: PR-2441 </p>
+ </item>
+ <item>
+ <p>
+ The new experimental <c>socket</c> module has been moved
+ to the Kernel application.</p>
+ <p>
+ Own Id: OTP-16312</p>
+ </item>
+ <item>
+ <p>Improved the presentation of allocations and carriers
+ in the <c>instrument</c> module.</p>
+ <p>
+ Own Id: OTP-16327</p>
+ </item>
+ <item>
+ <p>
+ As announced in OTP 22.0, the previously existing limited
+ support for VxWorks has now been removed.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16329 Aux Id: OTP-15621 </p>
+ </item>
+ <item>
+ <p> The return value when using the <c>httph</c> and
+ <c>httph_bin</c> option to <c>erlang:decode_packet/3</c>
+ and <c>inet:setopts/2</c> has been changed to also
+ include the original header unmodified. See <seemfa
+ marker="erlang#decode_packet/3"><c>erlang:decode_packet/3</c></seemfa>.
+ Example: </p> <pre> >
+ erlang:decode_packet(httph_bin,&lt;&lt;"HELLO:
+ hi\r\n\r\n"&gt;&gt;,[]).
+ {ok,{http_header,0,&lt;&lt;"Hello"&gt;&gt;,&lt;&lt;"HELLO"&gt;&gt;,&lt;&lt;"hi"&gt;&gt;},&lt;&lt;"\r\n"&gt;&gt;}
+ </pre>
+ <p>
+ Own Id: OTP-16347 Aux Id: PR-2466 </p>
+ </item>
+ <item>
+ <p>
+ Ensure <c>net_kernel:monitor_nodes/1</c> sends
+ <c>nodedown</c> messages of a failed connection before
+ <c>nodeup</c> messages of a reestablished connection
+ toward the same node.</p>
+ <p>
+ Own Id: OTP-16362</p>
+ </item>
+ <item>
+ <p>
+ Update of <seeerl
+ marker="kernel:seq_trace#whatis">sequential
+ tracing</seeerl> to also support other information
+ transfers than message passing.</p>
+ <p>
+ Own Id: OTP-16370 Aux Id: OTP-15251, OTP-15232 </p>
+ </item>
+ <item>
+ <p>
+ socket: It is now possible to create a socket from an
+ already existing file descriptor.</p>
+ <p>
+ Own Id: OTP-16398 Aux Id: ERL-1154 </p>
+ </item>
+ <item>
+ <p>
+ socket: The socket:supports/1 function now also report if
+ netns is supported or not.</p>
+ <p>
+ Own Id: OTP-16432</p>
+ </item>
+ <item>
+ <p><c>=:=</c> has been optimized to return <c>false</c>
+ immediately when comparing two maps of different
+ sizes.</p>
+ <p>
+ Own Id: OTP-16454</p>
+ </item>
+ <item>
+ <p>
+ Changed the behaviour of passing the <c>erl</c> command
+ line argument <seecom
+ marker="erts:erl#async_thread_pool_size"><c>+A
+ 0</c></seecom> to silently imply <c>+A 1</c>. That is, it
+ will no longer be possible to completely disable the
+ async thread pool. Disabling of the async thread pool has
+ since OTP 21 had no benefits; only lots of drawbacks.</p>
+ <p>
+ Own Id: OTP-16482</p>
+ </item>
+ <item>
+ <p>The deprecated <c>erlang:get_stacktrace/0</c> BIF now
+ returns an empty list instead of a stacktrace. To
+ retrieve the stacktrace, use the extended try/catch
+ syntax that was introduced in OTP 21.
+ <c>erlang:get_stacktrace/0</c> is scheduled for removal
+ in OTP 24.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16484</p>
+ </item>
+ <item>
+ <p>
+ <seemfa
+ marker="erts:init#restart/1"><c>init:restart/1</c></seemfa>
+ has been introduced. <c>init:restart/1</c> can be
+ utilized for changing the code loading mode during a
+ restart.</p>
+ <p>
+ Own Id: OTP-16492 Aux Id: PR-2461 </p>
+ </item>
+ <item>
+ <p>
+ Improve configure for the net nif, which should increase
+ portability.</p>
+ <p>
+ Own Id: OTP-16530 Aux Id: OTP-16464 </p>
+ </item>
+ <item>
+ <p>
+ socket: Socket counters and socket global counters are
+ now represented as maps (instead of property lists).</p>
+ <p>
+ Own Id: OTP-16535</p>
+ </item>
+ <item>
+ <p>Reduced the resource usage of <c>erlc</c> in parallel
+ builds (e.g. <c>make -j128</c>).</p>
+ <p>
+ Own Id: OTP-16543 Aux Id: ERL-1186 </p>
+ </item>
+ <item>
+ <p>
+ The experimental socket module has gotten restrictions
+ removed so now the 'seqpacket' socket type should work
+ for any communication domain (protocol family) where the
+ OS supports it, typically the Unix Domain.</p>
+ <p>
+ Own Id: OTP-16550 Aux Id: ERIERL-476 </p>
+ </item>
+ <item>
+ <p>Updated the internal <c>pcre</c> library to
+ <c>8.44</c>.</p>
+ <p>
+ Own Id: OTP-16557</p>
+ </item>
+ <item>
+ <p>There is now cost in terms of reductions when copying
+ binary data using the binary syntax.</p>
+ <p>
+ Own Id: OTP-16601 Aux Id: OTP-16577 </p>
+ </item>
+ <item>
+ <p>
+ The executable <c>erl_call</c> is now part of the
+ <c>erts</c> distribution in addition to
+ <c>erl_interface</c>.</p>
+ <p>
+ Own Id: OTP-16602</p>
+ </item>
+ <item>
+ <p>
+ Fix a buffer overflow bug that caused EPMD to consume
+ 100% CPU after many nodes had been connected on the same
+ time on NetBSD.</p>
+ <p>
+ Own Id: OTP-16615</p>
+ </item>
+ <item>
+ <p>
+ <c>erl -remsh</c> now uses the dynamic node names feature
+ by default. See the <seecom
+ marker="erts:erl">erl</seecom> documentation for details.</p>
+ <p>
+ Own Id: OTP-16616</p>
+ </item>
+ <item>
+ <p>
+ socket: By default the socket options rcvtimeo and
+ sndtimeo are now disabled. To enable these, OTP now has
+ to be built with the configure option
+ --enable-esock-rcvsndtimeo</p>
+ <p>
+ Own Id: OTP-16620</p>
+ </item>
+ <item>
+ <p>
+ The environment variable $HOME does no longer have to be
+ set before Erlang can be started.</p>
+ <p>
+ Own Id: OTP-16635 Aux Id: ERL-476 PR-2390 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug that could cause some work scheduled for
+ execution on scheduler threads to be delayed until other
+ similar work appeared. Beside delaying various cleanup of
+ internal data structures also the following could be
+ delayed:</p> <list> <item>Termination of a distribution
+ controller process</item> <item>Disabling of the
+ distribution on a node</item> <item>Gathering of memory
+ allocator information using the <c>instrument</c>
+ module</item> <item>Enabling, disabling, and gathering of
+ <c>msacc</c> information</item> <item>Delivery of
+ <c>'CHANGE'</c> messages when time offset is
+ monitored</item> <item>A call to
+ <c>erlang:cancel_timer()</c></item> <item>A call to
+ <c>erlang:read_timer()</c></item> <item>A call to
+ <c>erlang:statistics(io | garbage_collection |
+ scheduler_wall_time)</c></item> <item>A call to
+ <c>ets:all()</c></item> <item>A call to
+ <c>erlang:memory()</c></item> <item>A call to
+ <c>erlang:system_info({allocator | allocator_sizes,
+ _})</c></item> <item>A call to
+ <c>erlang:trace_delivered()</c></item> </list> <p>The bug
+ existed on runtime systems running on all types of
+ hardware except for x86/x86_64.</p>
+ <p>
+ Own Id: OTP-17185</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The <c>suspend_process()</c> and <c>resume_process()</c>
+ BIFs did not check their arguments properly which could
+ cause an emulator crash.</p>
+ <p>
+ Own Id: OTP-17080</p>
+ </item>
+ <item>
+ <p>
+ The runtime system would get into an infinite loop if the
+ runtime system was started with more than 1023 file
+ descriptors already open.</p>
+ <p>
+ Own Id: OTP-17088 Aux Id: ERIERL-580 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed rare distribution bug in race between received
+ signal (link/monitor/spawn_request/spawn_reply) and
+ disconnection. Symptom: VM crash. Since: OTP 21.0.</p>
+ <p>
+ Own Id: OTP-16869 Aux Id: ERL-1337 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bugs causing issues when enabling the ERTS internal
+ allocators on a system built with the undocumented and
+ unsupported <c>SMALL_MEMORY</c> feature.</p>
+ <p>
+ Own Id: OTP-16939</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ inet:setopts([{active,once}]) wakes up IO polling thread
+ unnecessarily, leading to lock contention and visibly
+ higher CPU utilization.</p>
+ <p>
+ Own Id: OTP-16847 Aux Id: ERL-1301 </p>
+ </item>
+ <item>
+ <p>
+ The documentation of <c>statistics(run_queue)</c>
+ erroneously stated that it returns the total length of
+ all normal run queues when it is the total length of all
+ normal and dirty CPU run queues that is returned. The
+ documentation has been updated to reflect the actual
+ behavior.</p>
+ <p>
+ Own Id: OTP-16866 Aux Id: ERL-1355 </p>
+ </item>
+ <item>
+ <p>
+ Two bugs in the ERTS internal thread wakeup functionality
+ have been fixed. These bugs mainly hit when all threads
+ in the system tried to go to sleep. When the bugs were
+ triggered, certain operations were delayed until a thread
+ woke up due to some other reason. Most important
+ operations effected were code loading, persistent term
+ updates, and memory deallocation.</p>
+ <p>
+ Own Id: OTP-16870</p>
+ </item>
+ <item>
+ <p>
+ Fixed bug in <c>ets:select_replace/2</c> on
+ <c>compressed</c> tables that could produce faulty
+ results or VM crash. Bug exists since OTP 20.</p>
+ <p>
+ Own Id: OTP-16874 Aux Id: ERL-1356, PR-2763 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ As of OTP 22, the allocator specific memory carrier pools
+ were replaced by a node global carrier pool. This
+ unfortunately caused substantial memory fragmentation in
+ some cases due to long lived data being spread into
+ carriers used by allocators mainly handling short lived
+ data.</p>
+ <p>
+ A new command line argument <c>+M&lt;S&gt;cp</c> has been
+ introduced with which one can enable the old behavior as
+ well as configuring other behaviors for the carrier
+ pools. In order to configure the old behavior, with
+ allocator specific carrier pools for all allocators, pass
+ <c>+Mucp :</c> (including the colon character) as a
+ command line argument to <c>erl</c> when starting the
+ Erlang system.</p>
+ <p>
+ The default configuration for carrier pools will be
+ changed to <c>+Mucp :</c> some time in the future, but
+ not in this patch.</p>
+ <p>
+ Own Id: OTP-16856</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The ERTS internal I/O poll implementation could get into
+ an inconsistent state causing input events to be ignored.</p>
+ <p>
+ Own Id: OTP-16780 Aux Id: PR-2701 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ An unintentional reuse of an already used emulator
+ internal event object could cause a wakeup signal to a
+ thread to be lost. In worst case this could cause the
+ runtime system to hang. This hang was however quite rare.</p>
+ <p>
+ Own Id: OTP-16766 Aux Id: ERL-1304 </p>
+ </item>
+ <item>
+ <p>
+ NIF threads and driver threads on non-Linux systems
+ leaked internal resources when terminating. On Windows
+ these resources were one event per thread. On most other
+ systems one mutex and one condition variable per thread.
+ On these other systems that also lacked
+ <c>pthread_cond_timedwait()</c> also a pipe with its file
+ descriptors was leaked.</p>
+ <p>
+ Own Id: OTP-16772 Aux Id: ERL-1304 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.7.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bug in erl_crash.dump generation that could cause a
+ SEGV core dump if a recently cancelled timer was found.</p>
+ <p>
+ Own Id: OTP-16596 Aux Id: ERL-1105, PR-2606 </p>
+ </item>
+ <item>
+ <p>
+ The functionality utilized by BIFs for temporary
+ disabling of garbage collection while yielding could
+ cause system task queues to become inconsistent on a
+ process executing such a BIF. Process system tasks are
+ for example utilized when purging code, garbage
+ collecting literal data, and when issuing an ordinary
+ garbage collection from another process.</p>
+ <p>
+ The bug does not trigger frequently. Multiple code purges
+ in direct sequence makes it more likely that this bug is
+ triggered. In the cases observed, this has resulted in a
+ hanging code purge operation.</p>
+ <p>
+ Own Id: OTP-16639 Aux Id: ERL-1236 </p>
+ </item>
+ <item>
+ <p>
+ SCTP and UDP recv/2,3 hangs indefinitely if socket is
+ closed while recv is called (socket in passive mode).</p>
+ <p>
+ Own Id: OTP-16654 Aux Id: ERL-1242 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 10.7.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -78,8 +1675,8 @@
<list>
<item>
<p>
- <seealso marker="stdlib:re#run/3">re:run(Subject, RE,
- [unicode])</seealso> returned <c>nomatch</c> instead of
+ <seemfa marker="stdlib:re#run/3">re:run(Subject, RE,
+ [unicode])</seemfa> returned <c>nomatch</c> instead of
failing with a <c>badarg</c> error exception when
<c>Subject</c> contained illegal utf8 and <c>RE</c> was
passed as a binary. This has been corrected along with
@@ -185,8 +1782,8 @@
<item>
<p>
Corrected the valid range of the <c>erl</c> command line
- argument <seealso marker="erts:erl#+SDio"><c>+SDio
- &lt;NumberOfDirtyIoSchedulers&gt;</c></seealso> from
+ argument <seecom marker="erts:erl#+SDio"><c>+SDio
+ &lt;NumberOfDirtyIoSchedulers&gt;</c></seecom> from
<c>0..1024</c> to <c>1..1024</c>. <c>+SDio 0</c> was
erroneously allowed which just caused the VM to crash on
the first dirty I/O job scheduled.</p>
@@ -208,7 +1805,7 @@
<list>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, not all
+ For <seeerl marker="kernel:socket#">socket</seeerl>, not all
send and receive flags are supported on all platforms. In
order to (at least) simplify testing, the
socket:supports/0,1,2,3 functions has been extended with
@@ -228,8 +1825,8 @@
<p>
The options <c>busy_limits_port</c> and
<c>busy_limits_msgq</c> have been added to the BIF
- <seealso
- marker="erts:erlang#open_port/2"><c>erlang:open_port/2</c></seealso>.
+ <seemfa
+ marker="erts:erlang#open_port/2"><c>erlang:open_port/2</c></seemfa>.
The <c>busy_limits_port</c> option can be used for
controlling the busy state of a port executing the
<c>spawn_driver</c> or the <c>fd_driver</c>. The
@@ -353,9 +1950,9 @@
</item>
<item>
<p>
- A process calling <seealso
+ A process calling <seeerl
marker="erts:erlang#system_flag_multi_scheduling"><c>erlang:system_flag(multi_scheduling,
- block)</c></seealso> could end up blocked waiting for the
+ block)</c></seeerl> could end up blocked waiting for the
operation to complete indefinitely.</p>
<p>
Own Id: OTP-16379</p>
@@ -409,10 +2006,10 @@
<list>
<item>
<p>
- The functions <seealso
- marker="stdlib:unicode#characters_to_list/2"><c>unicode:characters_to_list()</c></seealso>
- and <seealso
- marker="stdlib:unicode#characters_to_binary/3"><c>unicode:characters_to_binary()</c></seealso>
+ The functions <seemfa
+ marker="stdlib:unicode#characters_to_list/2"><c>unicode:characters_to_list()</c></seemfa>
+ and <seemfa
+ marker="stdlib:unicode#characters_to_binary/3"><c>unicode:characters_to_binary()</c></seemfa>
raised a <c>badarg</c> exception instead of returning an
error tuple when passed very large invalid code points as
input.</p>
@@ -427,7 +2024,7 @@
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, invalid
+ For <seeerl marker="kernel:socket#">socket</seeerl>, invalid
encoding of send and receive flags caused badarg and send
failure.</p>
<p>
@@ -442,7 +2039,7 @@
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, unable to
+ For <seeerl marker="kernel:socket#">socket</seeerl>, unable to
properly decode the timestamp control message header on
FreeBSD. We incorrectly used the SO_TIMESTAMP flag for
the timestamp control message header type. It should have
@@ -453,7 +2050,7 @@
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, when
+ For <seeerl marker="kernel:socket#">socket</seeerl>, when
setting the ip option 'recvtos' to true, thereby
indicating that we want to receive the TOS control
message header, we don't actually get TOS but RECVTOS on
@@ -470,7 +2067,7 @@
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, wrong
+ For <seeerl marker="kernel:socket#">socket</seeerl>, wrong
type for protocol caused segmentation fault if protocol
was provided as {raw, integer()}.</p>
<p>
@@ -478,7 +2075,7 @@
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, when
+ For <seeerl marker="kernel:socket#">socket</seeerl>, when
setting the ip option 'recvttl' to true, thereby
indicating that we want to receive the TTL control
message header, we don't actually get TTL but RECVTTL on
@@ -489,7 +2086,7 @@
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, IPv6
+ For <seeerl marker="kernel:socket#">socket</seeerl>, IPv6
control message headers was incorrectly decoded with
level ip instead of ipv6.</p>
<p>
@@ -535,7 +2132,7 @@
</item>
<item>
<p>
- The <seealso marker="socket#">socket</seealso> socket
+ The <seeerl marker="kernel:socket#">socket</seeerl> socket
option 'peek_off' has been disabled. If peek_off was set
and then socket:recv/3 was called with the peek flag, the
call could hang.</p>
@@ -551,7 +2148,7 @@
</item>
<item>
<p>
- For <seealso marker="kernel:net#">net</seealso>, a couple
+ For <seeerl marker="kernel:net#">net</seeerl>, a couple
of NI macros was deprecated in new versions of glibc, so
these are no longer used (IDN_ALLOW_UNASSIGNED and
IDN_USE_STD3_ASCII_RULES).</p>
@@ -576,16 +2173,16 @@
<item>
<p>
Fixed a bug causing actual nodedown reason reported by
- <seealso
+ <seemfa
marker="kernel:net_kernel#monitor_nodes/2"><c>net_kernel:monitor_nodes(true,
- [nodedown_reason])</c></seealso> to be lost and replaced
+ [nodedown_reason])</c></seemfa> to be lost and replaced
by the reason <c>killed</c>.</p>
<p>
Own Id: OTP-16216</p>
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, the
+ For <seeerl marker="kernel:socket#">socket</seeerl>, the
timestamp creation used when printing warning messages
and debug printouts did not work. The used buffer was too
small.</p>
@@ -631,7 +2228,7 @@
<list>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, not all
+ For <seeerl marker="kernel:socket#">socket</seeerl>, not all
send and receive flags are supported on all platforms. In
order to (at least) simplify testing, the
socket:supports/0,1,2,3 functions has been extended with
@@ -642,7 +2239,7 @@
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, add
+ For <seeerl marker="kernel:socket#">socket</seeerl>, add
support for IPv6 socket options tclass and recvtclass.
Both has been added, but the use of them are platform
dependent. Call socket:supports(options, ipv6, Opt) to be
@@ -653,7 +2250,7 @@
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, the TCP
+ For <seeerl marker="kernel:socket#">socket</seeerl>, the TCP
socket option cork was not supported even though the
supports function reported it as such.</p>
<p>
@@ -686,7 +2283,7 @@
</item>
<item>
<p>
- For <seealso marker="socket#">socket</seealso>, added
+ For <seeerl marker="kernel:socket#">socket</seeerl>, added
support for the socket option extended_err. Andreas
Schultz.</p>
<p>
@@ -953,8 +2550,8 @@
to version 8.43. See <url
href="http://pcre.org/original/changelog.txt">http://pcre.org/original/changelog.txt</url>
for information about changes made to PCRE. This library
- implements major parts of the <seealso
- marker="stdlib:re"><c>re</c></seealso> regular
+ implements major parts of the <seeerl
+ marker="stdlib:re"><c>re</c></seeerl> regular
expressions module.</p>
<p>
Own Id: OTP-15889</p>
@@ -1452,12 +3049,12 @@
Own Id: OTP-14831</p>
</item>
<item>
- <p>Added NIF functions <seealso
- marker="erl_nif#enif_set_pid_undefined"><c>enif_set_pid_undefined</c></seealso>,
- <seealso
- marker="erl_nif#enif_is_pid_undefined"><c>enif_is_pid_undefined</c></seealso>
- and <seealso
- marker="erl_nif#enif_compare_pids"><c>enif_compare_pids</c></seealso>.</p>
+ <p>Added NIF functions <seecref
+ marker="erl_nif#enif_set_pid_undefined"><c>enif_set_pid_undefined</c></seecref>,
+ <seecref
+ marker="erl_nif#enif_is_pid_undefined"><c>enif_is_pid_undefined</c></seecref>
+ and <seecref
+ marker="erl_nif#enif_compare_pids"><c>enif_compare_pids</c></seecref>.</p>
<p>
Own Id: OTP-15011 Aux Id: PR-2147 </p>
</item>
@@ -1685,13 +3282,13 @@
</item>
<item>
<p>
- A new <seealso
+ A new <seemfa
marker="erlang#dist_ctrl_set_opt/3"><c>erlang:dist_ctrl_set_opt(DHandle,
- get_size, Value)</c></seealso> option has been added.
+ get_size, Value)</c></seemfa> option has been added.
This option makes it possible to configure the
distribution channel identified by <c>DHandle</c> so that
- <seealso
- marker="erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data(DHandle)</c></seealso>
+ <seemfa
+ marker="erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data(DHandle)</c></seemfa>
also returns the size of the data to pass over the
channel.</p>
<p>
@@ -1792,6 +3389,237 @@
</section>
+<section><title>Erts 10.3.5.16</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug that could cause some work scheduled for
+ execution on scheduler threads to be delayed until other
+ similar work appeared. Beside delaying various cleanup of
+ internal data structures also the following could be
+ delayed:</p> <list> <item>Termination of a distribution
+ controller process</item> <item>Disabling of the
+ distribution on a node</item> <item>Gathering of memory
+ allocator information using the <c>instrument</c>
+ module</item> <item>Enabling, disabling, and gathering of
+ <c>msacc</c> information</item> <item>Delivery of
+ <c>'CHANGE'</c> messages when time offset is
+ monitored</item> <item>A call to
+ <c>erlang:cancel_timer()</c></item> <item>A call to
+ <c>erlang:read_timer()</c></item> <item>A call to
+ <c>erlang:statistics(io | garbage_collection |
+ scheduler_wall_time)</c></item> <item>A call to
+ <c>ets:all()</c></item> <item>A call to
+ <c>erlang:memory()</c></item> <item>A call to
+ <c>erlang:system_info({allocator | allocator_sizes,
+ _})</c></item> <item>A call to
+ <c>erlang:trace_delivered()</c></item> </list> <p>The bug
+ existed on runtime systems running on all types of
+ hardware except for x86/x86_64.</p>
+ <p>
+ Own Id: OTP-17185</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.3.5.15</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed rare distribution bug in race between received
+ signal (link/monitor/spawn_request/spawn_reply) and
+ disconnection. Symptom: VM crash. Since: OTP 21.0.</p>
+ <p>
+ Own Id: OTP-16869 Aux Id: ERL-1337 </p>
+ </item>
+ <item>
+ <p>
+ The <c>suspend_process()</c> and <c>resume_process()</c>
+ BIFs did not check their arguments properly which could
+ cause an emulator crash.</p>
+ <p>
+ Own Id: OTP-17080</p>
+ </item>
+ <item>
+ <p>
+ The runtime system would get into an infinite loop if the
+ runtime system was started with more than 1023 file
+ descriptors already open.</p>
+ <p>
+ Own Id: OTP-17088 Aux Id: ERIERL-580 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.3.5.14</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The ERTS internal I/O poll implementation could get into
+ an inconsistent state causing input events to be ignored.</p>
+ <p>
+ Own Id: OTP-16780 Aux Id: PR-2701 </p>
+ </item>
+ <item>
+ <p>
+ The documentation of <c>statistics(run_queue)</c>
+ erroneously stated that it returns the total length of
+ all normal run queues when it is the total length of all
+ normal and dirty CPU run queues that is returned. The
+ documentation has been updated to reflect the actual
+ behavior.</p>
+ <p>
+ Own Id: OTP-16866 Aux Id: ERL-1355 </p>
+ </item>
+ <item>
+ <p>
+ Two bugs in the ERTS internal thread wakeup functionality
+ have been fixed. These bugs mainly hit when all threads
+ in the system tried to go to sleep. When the bugs were
+ triggered, certain operations were delayed until a thread
+ woke up due to some other reason. Most important
+ operations effected were code loading, persistent term
+ updates, and memory deallocation.</p>
+ <p>
+ Own Id: OTP-16870</p>
+ </item>
+ <item>
+ <p>
+ Fixed bug in <c>ets:select_replace/2</c> on
+ <c>compressed</c> tables that could produce faulty
+ results or VM crash. Bug exists since OTP 20.</p>
+ <p>
+ Own Id: OTP-16874 Aux Id: ERL-1356, PR-2763 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.3.5.13</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ An unintentional reuse of an already used emulator
+ internal event object could cause a wakeup signal to a
+ thread to be lost. In worst case this could cause the
+ runtime system to hang. This hang was however quite rare.</p>
+ <p>
+ Own Id: OTP-16766 Aux Id: ERL-1304 </p>
+ </item>
+ <item>
+ <p>
+ NIF threads and driver threads on non-Linux systems
+ leaked internal resources when terminating. On Windows
+ these resources were one event per thread. On most other
+ systems one mutex and one condition variable per thread.
+ On these other systems that also lacked
+ <c>pthread_cond_timedwait()</c> also a pipe with its file
+ descriptors was leaked.</p>
+ <p>
+ Own Id: OTP-16772 Aux Id: ERL-1304 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.3.5.12</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The functionality utilized by BIFs for temporary
+ disabling of garbage collection while yielding could
+ cause system task queues to become inconsistent on a
+ process executing such a BIF. Process system tasks are
+ for example utilized when purging code, garbage
+ collecting literal data, and when issuing an ordinary
+ garbage collection from another process.</p>
+ <p>
+ The bug does not trigger frequently. Multiple code purges
+ in direct sequence makes it more likely that this bug is
+ triggered. In the cases observed, this has resulted in a
+ hanging code purge operation.</p>
+ <p>
+ Own Id: OTP-16639 Aux Id: ERL-1236 </p>
+ </item>
+ <item>
+ <p>
+ A literal area could prematurely be released before all
+ uses of it had been removed. This occurred either when a
+ terminating process had a complex exit reason referring
+ to a literal that concurrently was removed, or when a
+ terminating process continued executing a dirty NIF
+ accessing a literal (via the heap) that concurrently was
+ removed.</p>
+ <p>
+ Own Id: OTP-16640 Aux Id: OTP-16193 </p>
+ </item>
+ <item>
+ <p>
+ The VM could potentially crash when checking process code
+ of a process that terminated while executing a dirty NIF.
+ The checking of process code is part of a code purge
+ operation.</p>
+ <p>
+ Own Id: OTP-16641</p>
+ </item>
+ <item>
+ <p>
+ System tasks of <c>low</c> priority were not interleaved
+ with <c>normal</c> priority system tasks as they should.
+ This could potentially delay garbage collection of
+ another process longer than intended if the garbage
+ collection was requested from a <c>low</c> priority
+ process.</p>
+ <p>
+ Own Id: OTP-16642</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.3.5.11</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ <seemfa marker="stdlib:re#run/3">re:run(Subject, RE,
+ [unicode])</seemfa> returned <c>nomatch</c> instead of
+ failing with a <c>badarg</c> error exception when
+ <c>Subject</c> contained illegal utf8 and <c>RE</c> was
+ passed as a binary. This has been corrected along with
+ corrections of reduction counting in <c>re:run()</c>
+ error cases.</p>
+ <p>
+ Own Id: OTP-16553</p>
+ </item>
+ <item>
+ <p>Fixed a bug that could cause the emulator to crash
+ when purging modules or persistent terms.</p>
+ <p>
+ Own Id: OTP-16555 Aux Id: ERL-1188 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 10.3.5.10</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1818,8 +3646,8 @@
<item>
<p>
Corrected the valid range of the <c>erl</c> command line
- argument <seealso marker="erts:erl#+SDio"><c>+SDio
- &lt;NumberOfDirtyIoSchedulers&gt;</c></seealso> from
+ argument <seecom marker="erts:erl#+SDio"><c>+SDio
+ &lt;NumberOfDirtyIoSchedulers&gt;</c></seecom> from
<c>0..1024</c> to <c>1..1024</c>. <c>+SDio 0</c> was
erroneously allowed which just caused the VM to crash on
the first dirty I/O job scheduled.</p>
@@ -1875,9 +3703,9 @@
</item>
<item>
<p>
- A process calling <seealso
+ A process calling <seeerl
marker="erts:erlang#system_flag_multi_scheduling"><c>erlang:system_flag(multi_scheduling,
- block)</c></seealso> could end up blocked waiting for the
+ block)</c></seeerl> could end up blocked waiting for the
operation to complete indefinitely.</p>
<p>
Own Id: OTP-16379</p>
@@ -2239,11 +4067,11 @@
while it was executing dirty, the receiving process could
later end up in a suspended state indefinitely. This bug
was introduced in ERTS version 10.0 (OTP 21.0).</p>
- <p>Suspend/resume signals are sent from <seealso
- marker="erts:erlang#suspend_process/1"><c>erlang:suspend_process()</c></seealso>/<seealso
- marker="erts:erlang#resume_process/1"><c>erlang:resume_process()</c></seealso>.
- The <seealso
- marker="runtime_tools:dbg"><c>dbg</c></seealso> trace
+ <p>Suspend/resume signals are sent from <seemfa
+ marker="erts:erlang#suspend_process/1"><c>erlang:suspend_process()</c></seemfa>/<seemfa
+ marker="erts:erlang#resume_process/1"><c>erlang:resume_process()</c></seemfa>.
+ The <seeerl
+ marker="runtime_tools:dbg"><c>dbg</c></seeerl> trace
tool utilize this functionality and could thus trigger
this bug.</p>
<p>
@@ -2941,8 +4769,8 @@
to version 8.42. See <url
href="http://pcre.org/original/changelog.txt">http://pcre.org/original/changelog.txt</url>
for information about changes made to PCRE. This library
- implements major parts of the <seealso
- marker="stdlib:re"><c>re</c></seealso> regular
+ implements major parts of the <seeerl
+ marker="stdlib:re"><c>re</c></seeerl> regular
expressions module.</p>
<p>
Own Id: OTP-15217</p>
@@ -3245,18 +5073,18 @@
Own Id: OTP-14537 Aux Id: PR1529 </p>
</item>
<item>
- <p> File operations used to accept <seealso
- marker="kernel:file#type-name_all">filenames</seealso>
+ <p> File operations used to accept <seetype
+ marker="kernel:file#name_all">filenames</seetype>
containing null characters (integer value zero). This
caused the name to be truncated and in some cases
arguments to primitive operations to be mixed up.
Filenames containing null characters inside the filename
are now <em>rejected</em> and will cause primitive file
operations to fail. </p> <p> Also environment variable
- operations used to accept <seealso
- marker="kernel:os#type-env_var_name">names</seealso> and
- <seealso
- marker="kernel:os#type-env_var_value">values</seealso> of
+ operations used to accept <seetype
+ marker="kernel:os#env_var_name">names</seetype> and
+ <seetype
+ marker="kernel:os#env_var_value">values</seetype> of
environment variables containing null characters (integer
value zero). This caused operations to silently produce
erroneous results. Environment variable names and values
@@ -3267,12 +5095,12 @@
character in environment variable names causing various
problems. <c>$=</c> characters in environment variable
names are now also <em>rejected</em>. </p> <p>Also
- <seealso
- marker="kernel:os#cmd/1"><c>os:cmd/1</c></seealso> now
- reject null characters inside its <seealso
- marker="kernel:os#type-os_command">command</seealso>.
- </p> <p><seealso
- marker="erts:erlang#open_port/2"><c>erlang:open_port/2</c></seealso>
+ <seemfa
+ marker="kernel:os#cmd/1"><c>os:cmd/1</c></seemfa> now
+ reject null characters inside its <seetype
+ marker="kernel:os#os_command">command</seetype>.
+ </p> <p><seemfa
+ marker="erts:erlang#open_port/2"><c>erlang:open_port/2</c></seemfa>
will also reject null characters inside the port name
from now on.</p>
<p>
@@ -3454,8 +5282,8 @@
<item>
<p>
On OSs with per thread CPU time support, change
- <c>cpu_timestamp</c> in <seealso
- marker="erlang#trace/3">erlang:trace/3</seealso> to use
+ <c>cpu_timestamp</c> in <seemfa
+ marker="erlang#trace/3">erlang:trace/3</seemfa> to use
it instead of per process CPU time. This makes this
option useable on such OSs when running multiple
schedulers.</p>
@@ -3536,10 +5364,10 @@
</item>
<item>
<p>A new logging API is added to Erlang/OTP, see the
- <seealso
- marker="kernel:logger"><c>logger(3)</c></seealso> manual
- page, and section <seealso
- marker="kernel:logger_chapter">Logging</seealso> in the
+ <seeerl
+ marker="kernel:logger"><c>logger(3)</c></seeerl> manual
+ page, and section <seeguide
+ marker="kernel:logger_chapter">Logging</seeguide> in the
Kernel User's Guide.</p>
<p>Calls to <c>error_logger</c> are automatically
redirected to the new API, and legacy error logger event
@@ -3627,8 +5455,8 @@
still possible to disable kernel-poll, but it has to be
done at compile time through the configure option
<c>--disable-kernel-poll</c>.</p> <p>The new <c>erl</c>
- options <seealso marker="erl#+IOt"><c>+IOt</c></seealso>
- and <seealso marker="erl#+IOp"><c>+IOp</c></seealso> can
+ options <seecom marker="erl#+IOt"><c>+IOt</c></seecom>
+ and <seecom marker="erl#+IOp"><c>+IOp</c></seecom> can
be used to change how many IO poll threads and poll sets
that erts should use. See their respective documentation
for more details.</p>
@@ -3680,10 +5508,10 @@
This mainly consists of support for usage of distribution
controller processes (previously only ports could be used
as distribution controllers). For more information see
- <seealso marker="erts:alt_dist#distribution_module">ERTS
+ <seeguide marker="erts:alt_dist#distribution_module">ERTS
User's Guide ➜ How to implement an Alternative Carrier
for the Erlang Distribution ➜ Distribution
- Module</seealso>.</p>
+ Module</seeguide>.</p>
<p>
Own Id: OTP-14459</p>
</item>
@@ -3982,11 +5810,11 @@
thresholds for dirty schedulers, and changing these
settings for normal schedulers will no longer affect
dirty schedulers. </p> <p>Refer to the documentation for
- details. The new switches are <seealso
- marker="erl#+sbwtdcpu">+sbwtdcpu</seealso>, <seealso
- marker="erl#+sbwtdio">+sbwtdio</seealso>, <seealso
- marker="erl#+swtdcpu">+swtdcpu</seealso>, and <seealso
- marker="erl#+swtdio">+swtdio</seealso>.</p> <p>The
+ details. The new switches are <seecom
+ marker="erl#+sbwtdcpu">+sbwtdcpu</seecom>, <seecom
+ marker="erl#+sbwtdio">+sbwtdio</seecom>, <seecom
+ marker="erl#+swtdcpu">+swtdcpu</seecom>, and <seecom
+ marker="erl#+swtdio">+swtdio</seecom>.</p> <p>The
default busy wait threshold for dirty scheduler threads
has also been lowered to <c>short</c>.</p>
<p>
@@ -4016,8 +5844,8 @@
Own Id: OTP-14961</p>
</item>
<item>
- <p>The process suspend functionality used by the <seealso
- marker="erlang#suspend_process/2">erlang:suspend_process/2</seealso>
+ <p>The process suspend functionality used by the <seemfa
+ marker="erlang#suspend_process/2">erlang:suspend_process/2</seemfa>
BIF has been reimplemented using the newly introduced
true asynchronous signaling between processes. This
mainly to reduce memory usage in the process control
@@ -4146,11 +5974,11 @@
<p>The callback module passed as <c>-epmd_module</c> to
erl has been expanded to be able to do name and port
resolving.</p> <p>Documentation has also been added in
- the <seealso
- marker="kernel:erl_epmd"><c>erl_epmd</c></seealso>
- reference manual and ERTS User's Guide <seealso
+ the <seeerl
+ marker="kernel:erl_epmd"><c>erl_epmd</c></seeerl>
+ reference manual and ERTS User's Guide <seeguide
marker="erts:alt_disco">How to Implement an Alternative
- Node Discovery for Erlang Distribution</seealso>.</p>
+ Node Discovery for Erlang Distribution</seeguide>.</p>
<p>
Own Id: OTP-15086 Aux Id: PR-1694 </p>
</item>
@@ -4209,8 +6037,8 @@
<item>
<p>
Corrected the valid range of the <c>erl</c> command line
- argument <seealso marker="erts:erl#+SDio"><c>+SDio
- &lt;NumberOfDirtyIoSchedulers&gt;</c></seealso> from
+ argument <seecom marker="erts:erl#+SDio"><c>+SDio
+ &lt;NumberOfDirtyIoSchedulers&gt;</c></seecom> from
<c>0..1024</c> to <c>1..1024</c>. <c>+SDio 0</c> was
erroneously allowed which just caused the VM to crash on
the first dirty I/O job scheduled.</p>
@@ -4228,9 +6056,9 @@
<list>
<item>
<p>
- A process calling <seealso
+ A process calling <seeerl
marker="erts:erlang#system_flag_multi_scheduling"><c>erlang:system_flag(multi_scheduling,
- block)</c></seealso> could end up blocked waiting for the
+ block)</c></seeerl> could end up blocked waiting for the
operation to complete indefinitely.</p>
<p>
Own Id: OTP-16379</p>
@@ -5123,8 +6951,8 @@
to version 8.41. See <url
href="http://pcre.org/original/changelog.txt">http://pcre.org/original/changelog.txt</url>
for information about changes made to PCRE. This library
- implements major parts of the <seealso
- marker="stdlib:re"><c>re</c></seealso> regular
+ implements major parts of the <seeerl
+ marker="stdlib:re"><c>re</c></seeerl> regular
expressions module.</p>
<p>
Own Id: OTP-14574</p>
@@ -5228,22 +7056,22 @@
<item>
<p>Add new nif API functions for managing an I/O Queue.
The added functions are:</p> <list type="bulleted">
- <item><seealso marker="erl_nif#enif_ioq_create">
- <c>enif_ioq_create()</c></seealso></item> <item><seealso
+ <item><seecref marker="erl_nif#enif_ioq_create">
+ <c>enif_ioq_create()</c></seecref></item> <item><seecref
marker="erl_nif#enif_ioq_destroy">
- <c>enif_ioq_destroy()</c></seealso></item> <item><seealso
+ <c>enif_ioq_destroy()</c></seecref></item> <item><seecref
marker="erl_nif#enif_ioq_enq_binary">
- <c>enif_ioq_enq_binary()</c></seealso></item>
- <item><seealso marker="erl_nif#enif_ioq_enqv">
- <c>enif_ioq_enqv()</c></seealso></item> <item><seealso
+ <c>enif_ioq_enq_binary()</c></seecref></item>
+ <item><seecref marker="erl_nif#enif_ioq_enqv">
+ <c>enif_ioq_enqv()</c></seecref></item> <item><seecref
marker="erl_nif#enif_ioq_deq">
- <c>enif_ioq_deq()</c></seealso></item> <item><seealso
+ <c>enif_ioq_deq()</c></seecref></item> <item><seecref
marker="erl_nif#enif_ioq_peek">
- <c>enif_ioq_peek()</c></seealso></item> <item><seealso
+ <c>enif_ioq_peek()</c></seecref></item> <item><seecref
marker="erl_nif#enif_inspect_iovec">
- <c>enif_inspect_iovec()</c></seealso></item>
- <item><seealso marker="erl_nif#enif_free_iovec">
- <c>enif_free_iovec()</c></seealso></item> </list>
+ <c>enif_inspect_iovec()</c></seecref></item>
+ <item><seecref marker="erl_nif#enif_free_iovec">
+ <c>enif_free_iovec()</c></seecref></item> </list>
<p>
Own Id: OTP-14598</p>
</item>
@@ -5618,9 +7446,9 @@
Own Id: OTP-13827</p>
</item>
<item>
- <p>Replaced usage of deprecated symbolic <seealso
- marker="erts:erlang#type-time_unit"><c>time
- unit</c></seealso> representations.</p>
+ <p>Replaced usage of deprecated symbolic <seetype
+ marker="erts:erlang#time_unit"><c>time
+ unit</c></seetype> representations.</p>
<p>
Own Id: OTP-13831 Aux Id: OTP-13735 </p>
</item>
@@ -5648,8 +7476,8 @@
case when a <c>fun</c> is received by a process after the
purge.</p>
<p>
- For more information see the documentation of <seealso
- marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>.</p>
+ For more information see the documentation of <seemfa
+ marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seemfa>.</p>
<p>
*** POTENTIAL INCOMPATIBILITY ***</p>
<p>
@@ -5739,18 +7567,18 @@
time to complete are now performed on dirty schedulers if
enabled.</p>
<p>
- <seealso
- marker="erts:erlang#statistics/1"><c>erlang:statistics/1</c></seealso>
+ <seemfa
+ marker="erts:erlang#statistics/1"><c>erlang:statistics/1</c></seemfa>
with arguments inspecting scheduler and run queue states
have been changed due to the dirty scheduler support.
Code using this functionality may have to be rewritten
taking these incompatibilities into consideration.
- Examples of such uses are calls to <seealso
- marker="erts:erlang#statistics_scheduler_wall_time"><c>erlang:statistics(scheduler_wall_time)</c></seealso>,
- <seealso
- marker="erts:erlang#statistics_total_run_queue_lengths"><c>statistics(total_run_queue_lengths)</c></seealso>,
- <seealso
- marker="erts:erlang#statistics_total_active_tasks"><c>statistics(total_active_tasks)</c></seealso>,
+ Examples of such uses are calls to <seeerl
+ marker="erts:erlang#statistics_scheduler_wall_time"><c>erlang:statistics(scheduler_wall_time)</c></seeerl>,
+ <seeerl
+ marker="erts:erlang#statistics_total_run_queue_lengths"><c>statistics(total_run_queue_lengths)</c></seeerl>,
+ <seeerl
+ marker="erts:erlang#statistics_total_active_tasks"><c>statistics(total_active_tasks)</c></seeerl>,
etc.</p>
<p>
*** POTENTIAL INCOMPATIBILITY ***</p>
@@ -5831,7 +7659,7 @@
<p>
Upgraded the OTP internal PCRE library from version 8.33
to version 8.40. This library is used for implementation
- of the <seealso marker="stdlib:re"><c>re</c></seealso>
+ of the <seeerl marker="stdlib:re"><c>re</c></seeerl>
regular expressions module.</p>
<p>
Besides various bug fixes, the new version allows for
@@ -5839,10 +7667,10 @@
feature, the stack size of normal scheduler threads is
now by default set to 128 kilo words on all platforms.
The stack size of normal scheduler threads can be set
- upon system start by passing the <seealso
- marker="erts:erl#sched_thread_stack_size"><c>+sss</c></seealso>
- command line argument to the <seealso
- marker="erts:erl"><c>erl</c></seealso> command.</p>
+ upon system start by passing the <seecom
+ marker="erts:erl#sched_thread_stack_size"><c>+sss</c></seecom>
+ command line argument to the <seecom
+ marker="erts:erl"><c>erl</c></seecom> command.</p>
<p>
See <url
href="http://pcre.org/original/changelog.txt">http://pcre.org/original/changelog.txt</url>
@@ -5865,8 +7693,8 @@
by <c>term_to_binary()</c> using latin1 encoding. Note
that all atoms will by default be encoded using utf8 in a
future Erlang/OTP release. For more information see the
- documentation of <seealso
- marker="erts:erlang#term_to_binary/2"><c>erlang:term_to_binary/2</c></seealso>.</p>
+ documentation of <seemfa
+ marker="erts:erlang#term_to_binary/2"><c>erlang:term_to_binary/2</c></seemfa>.</p>
<p>
Own Id: OTP-14337</p>
</item>
@@ -5907,14 +7735,14 @@
Own Id: OTP-14357 Aux Id: PR-1326 </p>
</item>
<item>
- <p>Added the following <seealso
- marker="erl"><c>erl</c></seealso> command line arguments
+ <p>Added the following <seecom
+ marker="erl"><c>erl</c></seecom> command line arguments
with which you can set suggested stack for dirty
- schedulers:</p> <taglist> <tag><seealso
- marker="erl#dcpu_sched_thread_stack_size"><c>+sssdcpu</c></seealso></tag>
+ schedulers:</p> <taglist> <tag><seecom
+ marker="erl#dcpu_sched_thread_stack_size"><c>+sssdcpu</c></seecom></tag>
<item><p>for dirty CPU schedulers</p></item>
- <tag><seealso
- marker="erl#dio_sched_thread_stack_size"><c>+sssdio</c></seealso></tag>
+ <tag><seecom
+ marker="erl#dio_sched_thread_stack_size"><c>+sssdio</c></seecom></tag>
<item><p>for dirty IO schedulers</p></item> </taglist>
<p>The default suggested stack size for dirty schedulers
is 40 kilo words.</p>
@@ -5990,10 +7818,10 @@
</item>
<item>
<p>
- Introduced the new functions <seealso
- marker="erl_nif#enif_whereis_pid"><c>enif_whereis_pid()</c></seealso>
- and <seealso
- marker="erl_nif#enif_whereis_port"><c>enif_whereis_port()</c></seealso>.</p>
+ Introduced the new functions <seecref
+ marker="erl_nif#enif_whereis_pid"><c>enif_whereis_pid()</c></seecref>
+ and <seecref
+ marker="erl_nif#enif_whereis_port"><c>enif_whereis_port()</c></seecref>.</p>
<p>
Own Id: OTP-14453 Aux Id: PR-1400 </p>
</item>
@@ -6383,9 +8211,9 @@
</item>
<item>
<p>
- A process calling <seealso
+ A process calling <seeerl
marker="erlang#system_flag_multi_scheduling"><c>erlang:system_flag(multi_scheduling,
- block)</c></seealso> could end up hanging forever in the
+ block)</c></seeerl> could end up hanging forever in the
call.</p>
<p>
Own Id: OTP-14121</p>
@@ -7027,8 +8855,8 @@
</item>
<item>
<p>Added the following symbolic time unit representations
- to the <seealso
- marker="erlang#type-time_unit"><c>erlang:time_unit()</c></seealso>
+ to the <seetype
+ marker="erlang#time_unit"><c>erlang:time_unit()</c></seetype>
type:</p> <list> <item><c>second</c></item>
<item><c>millisecond</c></item>
<item><c>microsecond</c></item>
@@ -7059,8 +8887,8 @@
new purge strategy will as of ERTS version 8.1 be
enabled. This new strategy is not fully backwards
compatible with the strategy used by default. For more
- information see the documentation of <seealso
- marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>.</p>
+ information see the documentation of <seemfa
+ marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seemfa>.</p>
<p>
Own Id: OTP-13808 Aux Id: OTP-13833 </p>
</item>
@@ -7088,8 +8916,8 @@
the runtime system is built with support for dirty
schedulers.</p>
<p>
- For more information see the documentation of <seealso
- marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>.</p>
+ For more information see the documentation of <seemfa
+ marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seemfa>.</p>
<p>
Own Id: OTP-13833</p>
</item>
@@ -7372,10 +9200,10 @@
<list>
<item>
<p>
- The tracing support has been extended to allow a <seealso
- marker="erl_tracer">tracer module</seealso> to be the
+ The tracing support has been extended to allow a <seeerl
+ marker="erl_tracer">tracer module</seeerl> to be the
trace event handler instead of a process or port. The
- <seealso marker="erl_tracer">tracer module</seealso>
+ <seeerl marker="erl_tracer">tracer module</seeerl>
makes it possible for trace tools to filter or manipulate
trace event data without the trace event first having to
be copied from the traced process or port.</p>
@@ -7399,17 +9227,17 @@
<p>
This feature introduces tracepoints for schedulers,
drivers, memory carriers, memory and async thread pool.</p>
- <p> For a list of all tracepoints, see <seealso
+ <p> For a list of all tracepoints, see <seeguide
marker="runtime_tools:LTTng">Runtime Tools User's
- Guide</seealso> .</p>
+ Guide</seeguide> .</p>
<p>
Own Id: OTP-10282</p>
</item>
<item>
<p>
Make it possible to monitor/demonitor ports using the
- <seealso
- marker="erlang#monitor/2">erlang:monitor/2</seealso> API.
+ <seemfa
+ marker="erlang#monitor/2">erlang:monitor/2</seemfa> API.
The process and port information functions have also been
updated to include information about monitors from
processes to ports.</p>
@@ -7435,10 +9263,10 @@
added to runtime_tools that can assist in gathering and
interpreting the data from Microstate accounting.</p>
<p>
- For more information see <seealso
+ For more information see <seeerl
marker="erts:erlang#statistics_microstate_accounting">erlang:statistics(microstate_accounting,
- _)</seealso> and the <seealso
- marker="runtime_tools:msacc">msacc</seealso> module in
+ _)</seeerl> and the <seeerl
+ marker="runtime_tools:msacc">msacc</seeerl> module in
runtime_tools.</p>
<p>
Own Id: OTP-12345</p>
@@ -7474,8 +9302,8 @@
</item>
<item>
<p>
- Added <seealso
- marker="kernel:os#perf_counter/1">os:perf_counter/1</seealso>.</p>
+ Added <seemfa
+ marker="kernel:os#perf_counter/1">os:perf_counter/1</seemfa>.</p>
<p>
The perf_counter is a very very cheap and high resolution
timer that can be used to timestamp system events. It
@@ -7592,9 +9420,9 @@
</item>
<item>
<p>Improved dirty scheduler implementation. For more
- information see the <seealso
+ information see the <seecref
marker="erl_nif#dirty_nifs">NIF
- documentation</seealso>.</p> <note><list> <item><p>The
+ documentation</seecref>.</p> <note><list> <item><p>The
dirty scheduler support is still
<em>experimental</em>.</p></item> <item><p>The support
for determining whether dirty NIF support exist or not at
@@ -7602,8 +9430,8 @@
<c>ERL_NIF_DIRTY_SCHEDULER_SUPPORT</c> has been
removed.</p></item> <item><p>The
<c>enif_is_on_dirty_scheduler()</c> function has been
- removed. Use <seealso
- marker="erl_nif#enif_thread_type"><c>enif_thread_type()</c></seealso>
+ removed. Use <seecref
+ marker="erl_nif#enif_thread_type"><c>enif_thread_type()</c></seecref>
instead.</p></item> </list></note>
<p>
Own Id: OTP-13123</p>
@@ -7618,8 +9446,8 @@
<p>
Added max_heap_size process flag. max_heap_size allows
the user to limit the maximum heap used by a process. See
- <seealso
- marker="erlang#process_flag/2">erlang:process_flag</seealso>
+ <seemfa
+ marker="erlang#process_flag/2">erlang:process_flag</seemfa>
for more details.</p>
<p>
Own Id: OTP-13174</p>
@@ -7671,9 +9499,9 @@
outside of the heap, and by this prevent this data from
being part of garbage collections.</p>
<p>
- For more information see the documentation of <seealso
+ For more information see the documentation of <seeerl
marker="erlang#process_flag_message_queue_data"><c>process_flag(message_queue_data,
- MQD)</c></seealso>.</p>
+ MQD)</c></seeerl>.</p>
<p>
Own Id: OTP-13366 Aux Id: OTP-13047 </p>
</item>
@@ -7697,22 +9525,22 @@
</item>
<item>
<p>Add the following NIF API functions:</p>
- <list> <item><seealso
- marker="erl_nif#enif_cpu_time"><c>enif_cpu_time</c></seealso></item>
- <item><seealso
- marker="erl_nif#enif_now_time"><c>enif_now_time</c></seealso></item>
- <item><seealso
- marker="erl_nif#enif_make_unique_integer"><c>enif_make_unique_integer</c></seealso></item>
- <item><seealso
- marker="erl_nif#enif_is_process_alive"><c>enif_is_process_alive</c></seealso></item>
- <item><seealso
- marker="erl_nif#enif_is_port_alive"><c>enif_is_port_alive</c></seealso></item>
- <item><seealso
- marker="erl_nif#enif_term_to_binary"><c>enif_term_to_binary</c></seealso></item>
- <item><seealso
- marker="erl_nif#enif_binary_to_term"><c>enif_binary_to_term</c></seealso></item>
- <item><seealso
- marker="erl_nif#enif_port_command"><c>enif_port_command</c></seealso></item>
+ <list> <item><seecref
+ marker="erl_nif#enif_cpu_time"><c>enif_cpu_time</c></seecref></item>
+ <item><seecref
+ marker="erl_nif#enif_now_time"><c>enif_now_time</c></seecref></item>
+ <item><seecref
+ marker="erl_nif#enif_make_unique_integer"><c>enif_make_unique_integer</c></seecref></item>
+ <item><seecref
+ marker="erl_nif#enif_is_process_alive"><c>enif_is_process_alive</c></seecref></item>
+ <item><seecref
+ marker="erl_nif#enif_is_port_alive"><c>enif_is_port_alive</c></seecref></item>
+ <item><seecref
+ marker="erl_nif#enif_term_to_binary"><c>enif_term_to_binary</c></seecref></item>
+ <item><seecref
+ marker="erl_nif#enif_binary_to_term"><c>enif_binary_to_term</c></seecref></item>
+ <item><seecref
+ marker="erl_nif#enif_port_command"><c>enif_port_command</c></seecref></item>
</list>
<p>
For details of what each function does, see the erl_nif
@@ -7762,8 +9590,8 @@
ports. Ports can be traced on using the 'ports', 'send'
and 'receive' trace flags.</p>
<p>
- The first argument of <seealso
- marker="erts:erlang#trace/3">erlang:trace/3</seealso> has
+ The first argument of <seemfa
+ marker="erts:erlang#trace/3">erlang:trace/3</seemfa> has
been extended so that <c>'all'</c>, <c>'existing'</c> and
<c>'new'</c> now include both processes and ports. New
<c>Tracee</c> variants, <c>'all_processes'</c>,
@@ -7795,8 +9623,8 @@
</item>
<item>
<p>
- Add the <seealso
- marker="erts:erlang#match_spec_test/3">erlang:match_spec_test/3</seealso>
+ Add the <seemfa
+ marker="erts:erlang#match_spec_test/3">erlang:match_spec_test/3</seemfa>
function. The functions allows the testing of match
specifications for both tracing and ets tables. It can be
used to test that a match specification does the expected
@@ -7852,9 +9680,9 @@
configure option <c>--with-dynamic-trace=lttng</c>.</p>
<p>The dynamic trace module <c>dyntrace</c> is now
capable to be used as a LTTng sink for Erlang tracing.
- For a list of all tracepoints, see <seealso
+ For a list of all tracepoints, see <seeguide
marker="runtime_tools:LTTng">Runtime Tools User's
- Guide</seealso> .</p>
+ Guide</seeguide> .</p>
<p>This feature also introduces an incompatible change in
trace tags. The trace tags <c>gc_start</c> and
<c>gc_end</c> has been split into <c>gc_minor_start</c>,
@@ -8234,13 +10062,13 @@
</item>
<item>
<p>
- <seealso
- marker="kernel:gen_tcp#accept/2"><c>gen_tcp:accept/2</c></seealso>
- was not <seealso
+ <seemfa
+ marker="kernel:gen_tcp#accept/2"><c>gen_tcp:accept/2</c></seemfa>
+ was not <seeguide
marker="erts:time_correction#Time_Warp_Safe_Code">time
- warp safe</seealso>. This since it used the same time as
- returned by <seealso
- marker="erts:erlang#now/0"><c>erlang:now/0</c></seealso>
+ warp safe</seeguide>. This since it used the same time as
+ returned by <seemfa
+ marker="erts:erlang#now/0"><c>erlang:now/0</c></seemfa>
when calculating timeout. This has now been fixed.</p>
<p>
Own Id: OTP-13254 Aux Id: OTP-11997, OTP-13222 </p>
@@ -8304,14 +10132,14 @@
<p>Introduced new statistics functionality in order to
more efficiently retrieve information about run able and
active processes and ports. For more information see:</p>
- <list> <item><seealso
- marker="erlang#statistics_total_run_queue_lengths"><c>statistics(total_run_queue_lengths)</c></seealso></item>
- <item><seealso
- marker="erlang#statistics_run_queue_lengths"><c>statistics(run_queue_lengths)</c></seealso></item>
- <item><seealso
- marker="erlang#statistics_total_active_tasks"><c>statistics(total_active_tasks)</c></seealso></item>
- <item><seealso
- marker="erlang#statistics_active_tasks"><c>statistics(active_tasks)</c></seealso></item>
+ <list> <item><seeerl
+ marker="erlang#statistics_total_run_queue_lengths"><c>statistics(total_run_queue_lengths)</c></seeerl></item>
+ <item><seeerl
+ marker="erlang#statistics_run_queue_lengths"><c>statistics(run_queue_lengths)</c></seeerl></item>
+ <item><seeerl
+ marker="erlang#statistics_total_active_tasks"><c>statistics(total_active_tasks)</c></seeerl></item>
+ <item><seeerl
+ marker="erlang#statistics_active_tasks"><c>statistics(active_tasks)</c></seeerl></item>
</list>
<p>
Own Id: OTP-13201</p>
@@ -8371,14 +10199,14 @@
<p>Introduced new statistics functionality in order to
more efficiently retrieve information about run able and
active processes and ports. For more information see:</p>
- <list> <item><seealso
- marker="erlang#statistics_total_run_queue_lengths"><c>statistics(total_run_queue_lengths)</c></seealso></item>
- <item><seealso
- marker="erlang#statistics_run_queue_lengths"><c>statistics(run_queue_lengths)</c></seealso></item>
- <item><seealso
- marker="erlang#statistics_total_active_tasks"><c>statistics(total_active_tasks)</c></seealso></item>
- <item><seealso
- marker="erlang#statistics_active_tasks"><c>statistics(active_tasks)</c></seealso></item>
+ <list> <item><seeerl
+ marker="erlang#statistics_total_run_queue_lengths"><c>statistics(total_run_queue_lengths)</c></seeerl></item>
+ <item><seeerl
+ marker="erlang#statistics_run_queue_lengths"><c>statistics(run_queue_lengths)</c></seeerl></item>
+ <item><seeerl
+ marker="erlang#statistics_total_active_tasks"><c>statistics(total_active_tasks)</c></seeerl></item>
+ <item><seeerl
+ marker="erlang#statistics_active_tasks"><c>statistics(active_tasks)</c></seeerl></item>
</list>
<p>
Own Id: OTP-13201</p>
@@ -8616,16 +10444,16 @@
MacOS X to <c>gettimeofday()</c> in order to improve
performance. The system can be configured during build to
use the previously used higher resolution clock source by
- passing the switch <seealso
- marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring"><c>--with-clock-resolution=high</c></seealso>
+ passing the switch <seeguide
+ marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring"><c>--with-clock-resolution=high</c></seeguide>
when configuring the build.</p>
<p>
Own Id: OTP-12945 Aux Id: OTP-12892 </p>
</item>
<item>
<p>
- Added the <c>configure</c> option <seealso
- marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring"><c>--disable-saved-compile-time</c></seealso>
+ Added the <c>configure</c> option <seeguide
+ marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring"><c>--disable-saved-compile-time</c></seeguide>
which disables saving of compile date and time in the
emulator binary.</p>
<p>
@@ -8944,8 +10772,8 @@
</item>
<item>
<p>
- The documentation of the driver callback <seealso
- marker="driver_entry#start"><c>start()</c></seealso>
+ The documentation of the driver callback <seecref
+ marker="driver_entry#start"><c>start()</c></seecref>
erroneously stated that a return value of
<c>ERL_DRV_ERROR_ERRNO</c> caused the error value to be
passed via <c>erl_errno</c> when it should have been
@@ -8978,20 +10806,20 @@
</item>
<item>
<p>The time functionality of Erlang has been extended.
- This both includes a <seealso
+ This both includes a <seeguide
marker="time_correction#The_New_Time_API">new
- API</seealso> for time, as well as <seealso
+ API</seeguide> for time, as well as <seeguide
marker="time_correction#Time_Warp_Modes">time warp
- modes</seealso> which alters the behavior of the system
+ modes</seeguide> which alters the behavior of the system
when system time changes. <em>You are strongly encouraged
to use the new API</em> instead of the old API based on
- <seealso
- marker="erlang#now/0"><c>erlang:now/0</c></seealso>.
+ <seemfa
+ marker="erlang#now/0"><c>erlang:now/0</c></seemfa>.
<c>erlang:now/0</c> has been deprecated since it is and
forever will be a scalability bottleneck. For more
- information see the <seealso
+ information see the <seeguide
marker="time_correction">Time and Time
- Correction</seealso> chapter of the ERTS User's
+ Correction</seeguide> chapter of the ERTS User's
Guide.</p>
<p>Besides the API changes and time warp modes a lot of
scalability and performance improvements regarding time
@@ -9124,9 +10952,9 @@
are available (using a <c>gcc</c> of at least version
4.1) an improvement can be seen.</p>
<p>
- For more information see the "<seealso
- marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring_Atomic-Memory-Operations-and-the-VM">Atomic
- Memory Operations and the VM</seealso>" section of
+ For more information see the "<seeguide
+ marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring_Atomic-Memory-Operations-and-the-VM">Atomic
+ Memory Operations and the VM</seeguide>" section of
<c>$ERL_TOP/HOWTO/INSTALL.md</c>.</p>
<p>
Own Id: OTP-12383</p>
@@ -9217,8 +11045,8 @@
</item>
<item>
<p>
- Scalability improvement for <seealso
- marker="erlang#make_ref/0">erlang:make_ref/0</seealso>,
+ Scalability improvement for <seemfa
+ marker="erlang#make_ref/0">erlang:make_ref/0</seemfa>,
and other functionality that create references. Each
scheduler now manage its own set of references. By this
no communication at all is needed when creating
@@ -9237,10 +11065,10 @@
monotonicity as a property.</p>
<p>
If you need to create data with a strict monotonicity
- property use <seealso
- marker="erlang#unique_integer/1">erlang:unique_integer([monotonic])</seealso>.
- Do <em>not</em> use the deprecated <seealso
- marker="erlang#now/0">erlang:now()</seealso>.</p>
+ property use <seemfa
+ marker="erlang#unique_integer/1">erlang:unique_integer([monotonic])</seemfa>.
+ Do <em>not</em> use the deprecated <seemfa
+ marker="erlang#now/0">erlang:now()</seemfa>.</p>
<p>
*** POTENTIAL INCOMPATIBILITY ***</p>
<p>
@@ -9285,14 +11113,14 @@
The API of BIF timers has also been extended. Timeout
values are for example no longer limited to 32-bit
integers. For more information see the documentation of
- <seealso
- marker="erlang#start_timer/4"><c>erlang:start_timer/4</c></seealso>,
- <seealso
- marker="erlang#send_after/4"><c>erlang:send_after/4</c></seealso>,
- <seealso
- marker="erlang#cancel_timer/2"><c>erlang:cancel_timer/2</c></seealso>,
- and <seealso
- marker="erlang#read_timer/2"><c>erlang:read_timer/2</c></seealso>.</p>
+ <seemfa
+ marker="erlang#start_timer/4"><c>erlang:start_timer/4</c></seemfa>,
+ <seemfa
+ marker="erlang#send_after/4"><c>erlang:send_after/4</c></seemfa>,
+ <seemfa
+ marker="erlang#cancel_timer/2"><c>erlang:cancel_timer/2</c></seemfa>,
+ and <seemfa
+ marker="erlang#read_timer/2"><c>erlang:read_timer/2</c></seemfa>.</p>
<p>
Characteristics impact: Calls to the synchronous versions
of <c>erlang:cancel_timer()</c>, and
@@ -9413,7 +11241,7 @@
oscillation of entries in and out of the tables. The
oscillation caused unnecessary lock contention on the
table locks. The delay length can be set by passing the
- <seealso marker="erl#+zdntgc"><c>+zdntgc</c></seealso>
+ <seecom marker="erl#+zdntgc"><c>+zdntgc</c></seecom>
command line argument.</p>
<p>
Characteristics impact: The tables can grow to very large
@@ -9425,8 +11253,8 @@
Own Id: OTP-12802</p>
</item>
<item>
- <p>Improved implementation of <seealso
- marker="erlang#statistics/1"><c>erlang:statistics</c></seealso><c>(io)</c>
+ <p>Improved implementation of <seemfa
+ marker="erlang#statistics/1"><c>erlang:statistics</c></seemfa><c>(io)</c>
in order to reduce contention between schedulers.</p>
<p>Characteristics impact: The actual call to
<c>erlang:statistics(io)</c> takes longer time to
@@ -9987,8 +11815,8 @@
double word atomics when implemented by
<c>libatomic_ops</c> (for example, implemented for ARM).</p>
<p>
- The <seealso
- marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring_Atomic-Memory-Operations-and-the-VM"><c>$ERL_TOP/HOWTO/INSTALL.md</c></seealso>
+ The <seeguide
+ marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP_Configuring_Atomic-Memory-Operations-and-the-VM"><c>$ERL_TOP/HOWTO/INSTALL.md</c></seeguide>
document now also more clearly describes when you want to
build together with a <c>libatomic_ops</c> installation.</p>
<p>
@@ -10059,18 +11887,18 @@
<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
+ either calling</p> <list> <item> <seemfa
marker="erlang#port_command/3"><c>port_command(Port,
- Data, [nosuspend | Options])</c></seealso>, </item>
- <item> <seealso
+ Data, [nosuspend | Options])</c></seemfa>, </item>
+ <item> <seemfa
marker="erlang#send/3"><c>erlang:send(Port, {PortOwner,
- {command, Data}}, [nosuspend | Options])</c></seealso>,
- </item> <item> <seealso
+ {command, Data}}, [nosuspend | Options])</c></seemfa>,
+ </item> <item> <seemfa
marker="erlang#send_nosuspend/2"><c>erlang:send_nosuspend(Port,
- {PortOwner, {command, Data}})</c></seealso>, or </item>
- <item> <seealso
+ {PortOwner, {command, Data}})</c></seemfa>, or </item>
+ <item> <seemfa
marker="erlang#send_nosuspend/3"><c>erlang:send_nosuspend(Port,
- {PortOwner, {command, Data}}, Options)</c></seealso>.
+ {PortOwner, {command, Data}}, Options)</c></seemfa>.
</item> </list>
<p>Thanks Vasily Demidenok for reporting the issue, and
Sergey Kudryashov for providing a testcase.</p>
@@ -10098,8 +11926,8 @@
</item>
<item>
<p>
- The documentation erroneously listed the <seealso
- marker="erl#+swct"><c>+swct</c></seealso> command line
+ The documentation erroneously listed the <seecom
+ marker="erl#+swct"><c>+swct</c></seecom> command line
argument under <c>+sws</c>.</p>
<p>
Own Id: OTP-12102 Aux Id: OTP-10994 </p>
@@ -10108,8 +11936,8 @@
<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>.
+ <c>runnable_ports</c> using <seemfa
+ marker="erlang#system_profile/2"><c>erlang:system_profile/2</c></seemfa>.
This bug was introduced in ERTS version 5.10.</p>
<p>
Own Id: OTP-12105 Aux Id: OTP-10336 </p>
@@ -10174,8 +12002,8 @@
</item>
<item>
<p>
- Introduced <seealso
- marker="erl_nif#enif_schedule_nif"><c>enif_schedule_nif()</c></seealso>
+ Introduced <seecref
+ marker="erl_nif#enif_schedule_nif"><c>enif_schedule_nif()</c></seecref>
to the NIF API.</p>
<p>
The <c>enif_schedule_nif()</c> function allows a
@@ -10194,8 +12022,8 @@
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>
+ See the <seecref marker="erl_nif">NIF
+ documentation</seecref> for more information.</p>
<p>
Thanks to Steve Vinoski.</p>
<p>
@@ -10610,8 +12438,8 @@
since there still exist unresolved issues with floating
point exceptions on Linux.</p>
<p>
- For more information see <seealso
- marker="doc/installation_guide:INSTALL"><c>$ERL_TOP/HOWTO/INSTALL.md</c></seealso>.</p>
+ For more information see <seeguide
+ marker="system/installation_guide:INSTALL"><c>$ERL_TOP/HOWTO/INSTALL.md</c></seeguide>.</p>
<p>
*** POTENTIAL INCOMPATIBILITY ***</p>
<p>
@@ -10806,7 +12634,7 @@
<p>
A new optional scheduler utilization balancing mechanism
has been introduced. For more information see the
- <seealso marker="erl#+sub"><c>+sub</c></seealso> command
+ <seecom marker="erl#+sub"><c>+sub</c></seecom> command
line argument.</p>
<p>
Characteristics impact: None, when not enabled. When
@@ -10838,10 +12666,10 @@
and yield without having to set up the heap in a state
that can be garbage collected.</p>
<p>
- The <seealso
- marker="erts:erlang#garbage_collect/2"><c>garbage_collect/2</c></seealso>,
- and <seealso
- marker="erts:erlang#check_process_code/3"><c>check_process_code/3</c></seealso>
+ The <seemfa
+ marker="erts:erlang#garbage_collect/2"><c>garbage_collect/2</c></seemfa>,
+ and <seemfa
+ marker="erts:erlang#check_process_code/3"><c>check_process_code/3</c></seemfa>
BIFs have been introduced. Both taking an option list as
last argument. Using these, one can issue asynchronous
requests.</p>
@@ -10910,11 +12738,11 @@
<p>
Migration of memory carriers has been enabled by default
on all ERTS internal memory allocators based on the
- <seealso
- marker="erts_alloc#alloc_util"><c>alloc_util</c></seealso>
- framework except for <c>temp_alloc</c>. That is, <seealso
+ <seecref
+ marker="erts_alloc#alloc_util"><c>alloc_util</c></seecref>
+ framework except for <c>temp_alloc</c>. That is, <seecref
marker="erts_alloc#M_acul"><c>+M&lt;S&gt;acul
- de</c></seealso> is default for these allocators. Note
+ de</c></seecref> is default for these allocators. Note
that this also implies changed allocation strategies for
all of these allocators. They will all now use the
"address order first fit carrier best fit" strategy.</p>
@@ -10960,8 +12788,8 @@
</taglist>
<p>
For information on how to use Maps please see Map Expressions in the
- <seealso marker="doc/reference_manual:expressions#map_expressions">
- Reference Manual</seealso>.</p>
+ <seeguide marker="system/reference_manual:expressions#map_expressions">
+ Reference Manual</seeguide>.</p>
<p>
The current implementation is without the following
features:</p>
@@ -10997,10 +12825,10 @@
<p>
Dirty schedulers can currently only be used by NIFs on a
system with SMP support. More information can be found in
- the <seealso
- marker="erl_nif#dirty_nifs"><c>erl_nif(3)</c></seealso>
- documentation, the <seealso
- marker="erl"><c>erl(1)</c></seealso> documentation, and
+ the <seecref
+ marker="erl_nif#dirty_nifs"><c>erl_nif(3)</c></seecref>
+ documentation, the <seecom
+ marker="erl"><c>erl(1)</c></seecom> documentation, and
in the git commit comment of commit
'c1c03ae4ee50e58b7669ea88ec4d29c6b2b67c7b'.</p>
<p>
@@ -11051,8 +12879,8 @@
<p>
Introduced the <c>configure</c> option
<c>--with-assumed-cache-line-size=SIZE</c>. For more
- information see <seealso
- marker="doc/installation_guide:INSTALL"><c>$ERL_TOP/HOWTO/INSTALL.md</c></seealso>.</p>
+ information see <seeguide
+ marker="system/installation_guide:INSTALL"><c>$ERL_TOP/HOWTO/INSTALL.md</c></seeguide>.</p>
<p>
Own Id: OTP-11742</p>
</item>
@@ -11085,13 +12913,13 @@
<p>
Introduced functionality for allowing old drivers and NIF
libraries to be loaded during a transition period. For
- more information see <seealso
+ more information see <seecref
marker="erts:erl_driver#version_management">the version
management section in the <c>erl_driver(3)</c>
- documentation</seealso> and <seealso
+ documentation</seecref> and <seecref
marker="erts:erl_nif#version_management">the version
management section in the <c>erl_nif(3)</c>
- documentation</seealso>.</p>
+ documentation</seecref>.</p>
<p>
Own Id: OTP-11799</p>
</item>
@@ -11242,10 +13070,10 @@
<p>
Threads other than schedulers threads could make thread
unsafe accesses when support for migration of memory
- carriers had been enabled, i.e., when the <seealso
- marker="erts_alloc#M_acul"><c>+M&lt;S&gt;acul</c></seealso>
- command line flag had been passed to <seealso
- marker="erl"><c>erl</c></seealso>. This could cause
+ carriers had been enabled, i.e., when the <seecref
+ marker="erts_alloc#M_acul"><c>+M&lt;S&gt;acul</c></seecref>
+ command line flag had been passed to <seecom
+ marker="erl"><c>erl</c></seecom>. This could cause
corruption of the VMs internal state.</p>
<p>
This bug was introduced in erts-5.10.2 when the support
@@ -11262,14 +13090,14 @@
</item>
<item>
<p>
- Under rare circumstances a process calling <seealso
- marker="kernel:inet#close/1"><c>inet:close/1</c></seealso>,
- <seealso
- marker="kernel:gen_tcp#close/1"><c>gen_tcp:close/1</c></seealso>,
- <seealso
- marker="kernel:gen_udp#close/1"><c>gen_udp:close/1</c></seealso>,
- or <seealso
- marker="kernel:gen_sctp#close/1"><c>gen_sctp:close/1</c></seealso>
+ Under rare circumstances a process calling <seemfa
+ marker="kernel:inet#close/1"><c>inet:close/1</c></seemfa>,
+ <seemfa
+ marker="kernel:gen_tcp#close/1"><c>gen_tcp:close/1</c></seemfa>,
+ <seemfa
+ marker="kernel:gen_udp#close/1"><c>gen_udp:close/1</c></seemfa>,
+ or <seemfa
+ marker="kernel:gen_sctp#close/1"><c>gen_sctp:close/1</c></seemfa>
could hang in the call indefinitely.</p>
<p>
Own Id: OTP-11491</p>
@@ -11319,23 +13147,23 @@
should be able to use.</p>
<p>
By default the super carrier is disabled. It is enabled
- by passing the <seealso
+ by passing the <seecref
marker="erts:erts_alloc#MMscs"><c>+MMscs &lt;size in
- MB&gt;</c></seealso> command line argument. For more
- information see the documentation of the <seealso
- marker="erts:erts_alloc#MMsco"><c>+MMsco</c></seealso>,
- <seealso
- marker="erts:erts_alloc#MMscrfsd"><c>+MMscrfsd</c></seealso>,
- <seealso
- marker="erts:erts_alloc#MMscrpm"><c>+MMscrpm</c></seealso>,
- <seealso
- marker="erts:erts_alloc#MMscs"><c>+MMscs</c></seealso>,
- <seealso
- marker="erts:erts_alloc#Musac"><c>+MMusac</c></seealso>,
- and, <seealso
- marker="erts:erts_alloc#Mlpm"><c>+Mlpm</c></seealso>
- command line arguments in the <seealso
- marker="erts:erts_alloc"><c>erts_alloc(3)</c></seealso>
+ MB&gt;</c></seecref> command line argument. For more
+ information see the documentation of the <seecref
+ marker="erts:erts_alloc#MMsco"><c>+MMsco</c></seecref>,
+ <seecref
+ marker="erts:erts_alloc#MMscrfsd"><c>+MMscrfsd</c></seecref>,
+ <seecref
+ marker="erts:erts_alloc#MMscrpm"><c>+MMscrpm</c></seecref>,
+ <seecref
+ marker="erts:erts_alloc#MMscs"><c>+MMscs</c></seecref>,
+ <seecref
+ marker="erts:erts_alloc#Musac"><c>+MMusac</c></seecref>,
+ and, <seecref
+ marker="erts:erts_alloc#Mlpm"><c>+Mlpm</c></seecref>
+ command line arguments in the <seecref
+ marker="erts:erts_alloc"><c>erts_alloc(3)</c></seecref>
documentation.</p>
<p>
Since it is disabled by default there should be no impact
@@ -11343,9 +13171,9 @@
<p>
This change has been marked as a potential
incompatibility since the returned list when calling
- <seealso
+ <seeerl
marker="erts:erlang#system_info_allocator_tuple"><c>erlang:system_info({allocator,
- mseg_alloc})</c></seealso> now also include an
+ mseg_alloc})</c></seeerl> now also include an
<c>{erts_mmap, _}</c> tuple as one element in the list.</p>
<p>
*** POTENTIAL INCOMPATIBILITY ***</p>
@@ -11376,22 +13204,22 @@
signal issued due to this link will be received by the
processes before the BIF returns, or fail with an
exception due to the port not being open. </p><p> The
- synchronous port BIFs are: </p> <list> <item><seealso
- marker="erlang#port_close/1"><c>port_close/1</c></seealso></item>
- <item><seealso
- marker="erlang#port_command/2"><c>port_command/2</c></seealso></item>
- <item><seealso
- marker="erlang#port_command/3"><c>port_command/3</c></seealso></item>
- <item><seealso
- marker="erlang#port_connect/2"><c>port_connect/2</c></seealso></item>
- <item><seealso
- marker="erlang#port_control/3"><c>port_control/3</c></seealso></item>
- <item><seealso
- marker="erlang#port_call/3"><c>erlang:port_call/3</c></seealso></item>
- <item><seealso
- marker="erlang#port_info/1"><c>erlang:port_info/1</c></seealso></item>
- <item><seealso
- marker="erlang#port_info/2"><c>erlang:port_info/2</c></seealso></item>
+ synchronous port BIFs are: </p> <list> <item><seemfa
+ marker="erlang#port_close/1"><c>port_close/1</c></seemfa></item>
+ <item><seemfa
+ marker="erlang#port_command/2"><c>port_command/2</c></seemfa></item>
+ <item><seemfa
+ marker="erlang#port_command/3"><c>port_command/3</c></seemfa></item>
+ <item><seemfa
+ marker="erlang#port_connect/2"><c>port_connect/2</c></seemfa></item>
+ <item><seemfa
+ marker="erlang#port_control/3"><c>port_control/3</c></seemfa></item>
+ <item><seemfa
+ marker="erlang#port_call/3"><c>erlang:port_call/3</c></seemfa></item>
+ <item><seemfa
+ marker="erlang#port_info/1"><c>erlang:port_info/1</c></seemfa></item>
+ <item><seemfa
+ marker="erlang#port_info/2"><c>erlang:port_info/2</c></seemfa></item>
</list> <p> Note that some ports under certain
circumstances unlink themselves from the calling process
before exiting, i.e. even though the process linked
@@ -11857,18 +13685,18 @@
performance loss.</item> <item>When enabled on the
<c>fix_alloc</c> allocator, a different strategy for
management of fix blocks will be used.</item> <item>The
- information returned from <seealso
+ information returned from <seeerl
marker="erlang#system_info_allocator_tuple"><c>erlang:system_info({allocator,
- A})</c></seealso>, and <seealso
+ A})</c></seeerl>, and <seeerl
marker="erlang#system_info_allocator_sizes"><c>erlang:system_info({allocator_sizes,
- A})</c></seealso> will be slightly different when this
+ A})</c></seeerl> will be slightly different when this
feature has been enabled. An <c>mbcs_pool</c> tuple will
be present giving information about abandoned carriers,
and in the <c>fix_alloc</c> case no <c>fix_types</c>
tuple will be present. </item></list>
<p>For more information, see the documentation of the
- <seealso
- marker="erts_alloc#M_acul"><c>+M&lt;S&gt;acul</c></seealso>
+ <seecref
+ marker="erts_alloc#M_acul"><c>+M&lt;S&gt;acul</c></seecref>
command line argument.</p>
<p>
Own Id: OTP-10279</p>
@@ -11935,10 +13763,10 @@
incompatibility.</item> </list>
<p>Due to the above mensioned potential incompatibility,
it will still be possible to enable the old algorithm for
- some time. The command line argument <seealso
- marker="erl#+P"><c>+P legacy</c></seealso> will enable
- the old algorithm on the process table, and <seealso
- marker="erl#+Q"><c>+Q legacy</c></seealso> will do the
+ some time. The command line argument <seecom
+ marker="erl#+P"><c>+P legacy</c></seecom> will enable
+ the old algorithm on the process table, and <seecom
+ marker="erl#+Q"><c>+Q legacy</c></seecom> will do the
same for the port table. These command line arguments are
however deprecated as of their introduction and have been
scheduled for removal in OTP-R18.</p>
@@ -11997,8 +13825,8 @@
issues like this for some time.</p>
<p>In order to alleviate problems with scheduling which
might occur when executing misbehaving native code, the
- command line argument <seealso
- marker="erl#+sfwi">+sfwi</seealso> has been
+ command line argument <seecom
+ marker="erl#+sfwi">+sfwi</seecom> has been
introduced.</p>
<p>By default this feature is disabled and you are
advised not to enable it if you do not encounter problems
@@ -12051,8 +13879,8 @@
<list>
<item>
<p>
- The BIF <seealso
- marker="erlang#is_process_alive/1"><c>is_process_alive/1</c></seealso>
+ The BIF <seemfa
+ marker="erlang#is_process_alive/1"><c>is_process_alive/1</c></seemfa>
could prematurely return <c>false</c> while the process
being inspected was terminating. This bug was introduced
in ERTS-5.10.</p>
@@ -12068,10 +13896,10 @@
</item>
<item>
<p>
- The <seealso
- marker="erl#+sws"><c>+sws&lt;value&gt;</c></seealso> and
- <seealso
- marker="erl#+swt"><c>+swt&lt;value&gt;</c></seealso>
+ The <seecom
+ marker="erl#+sws"><c>+sws&lt;value&gt;</c></seecom> and
+ <seecom
+ marker="erl#+swt"><c>+swt&lt;value&gt;</c></seecom>
system flags failed if no white space were passed between
the parameter and value parts of the flags. Upon failure,
the runtime system refused to start.</p>
@@ -12088,10 +13916,10 @@
<p>
Scheduler threads will now by default be less eager
requesting wakeup due to certain cleanup operations. This
- can also be controlled using the <seealso
- marker="erl#+swct"><c>+swct</c></seealso> command line
- argument of <seealso
- marker="erl"><c>erl(1)</c></seealso>.</p>
+ can also be controlled using the <seecom
+ marker="erl#+swct"><c>+swct</c></seecom> command line
+ argument of <seecom
+ marker="erl"><c>erl(1)</c></seecom>.</p>
<p>
Own Id: OTP-10994</p>
</item>
@@ -12308,10 +14136,10 @@
Own Id: OTP-10348 Aux Id: kunagi-316 [227] </p>
</item>
<item>
- <p>The driver API function <seealso
- marker="erl_driver#erl_drv_consume_timeslice"><c>erl_drv_consume_timeslice()</c></seealso>,
- and the NIF API function <seealso
- marker="erl_nif#enif_consume_timeslice"><c>enif_consume_timeslice()</c></seealso>
+ <p>The driver API function <seecref
+ marker="erl_driver#erl_drv_consume_timeslice"><c>erl_drv_consume_timeslice()</c></seecref>,
+ and the NIF API function <seecref
+ marker="erl_nif#enif_consume_timeslice"><c>enif_consume_timeslice()</c></seecref>
have been introduced.</p>
<p>These functions are provided in order to better
support co-operative scheduling, improve system
@@ -12627,8 +14455,8 @@
for a much larger maximum amount of ports allowed as a
default. The previous default of 1024 has been raised to
65536. Maximum amount of ports can be set using the
- <seealso marker="erts:erl#+Q">+Q</seealso> command line
- flag of <seealso marker="erts:erl">erl(1)</seealso>. The
+ <seecom marker="erts:erl#+Q">+Q</seecom> command line
+ flag of <seecom marker="erts:erl">erl(1)</seecom>. The
previously used environment variable <c>ERL_MAX_PORTS</c>
has been deprecated and scheduled for removal in
OTP-R17.</item> <item>Major rewrite of scheduling of port
@@ -12647,8 +14475,8 @@
port lock as well as truly asynchronous communication
with ports.</item> <item>Optimized lookup of port handles
from drivers.</item> <item>Optimized driver lookup when
- creating ports.</item> <item>Preemptable <seealso
- marker="erts:erlang#ports-0">erlang:ports/0</seealso>
+ creating ports.</item> <item>Preemptable <seemfa
+ marker="erts:erlang#ports/0">erlang:ports/0</seemfa>
BIF.</item> <item>Improving responsiveness by bumping
reductions for a process calling a driver callback
directly.</item> </list>
@@ -12667,10 +14495,10 @@
<em>false assumptions</em> about signal delivery order to
fail even though they previously succeeded. For more
information about signal ordering guarantees, see the
- chapter on <seealso
- marker="erts:communication">communication</seealso> in
+ chapter on <seeguide
+ marker="erts:communication">communication</seeguide> in
the ERTS user's guide. The <c>+n</c> command line flag of
- <seealso marker="erts:erl">erl(1)</seealso> can be
+ <seecom marker="erts:erl">erl(1)</seecom> can be
helpful when trying to find signaling order bugs in
Erlang code that have been exposed by these
changes.</item> <tag>Latency of signals sent from
@@ -12689,17 +14517,17 @@
but improves the overall performance of the system since
it reduce lock contention between schedulers. The default
behavior of only scheduling delivery of these signals on
- conflict can be changed by passing the <seealso
- marker="erts:erl#+spp">+spp</seealso> command line flag
- to <seealso marker="erts:erl">erl(1)</seealso>. The
+ conflict can be changed by passing the <seecom
+ marker="erts:erl#+spp">+spp</seecom> command line flag
+ to <seecom marker="erts:erl">erl(1)</seecom>. The
behavior can also be changed on port basis using the
- <seealso
- marker="erts:erlang#open_port_parallelism">parallelism</seealso>
- option of the <seealso
- marker="erts:erlang#open_port-2">open_port/2</seealso>
+ <seeerl
+ marker="erts:erlang#open_port_parallelism">parallelism</seeerl>
+ option of the <seemfa
+ marker="erts:erlang#open_port/2">open_port/2</seemfa>
BIF.</item> <tag>Execution time of the
- <c>erlang:ports/0</c> BIF</tag> <item>Since <seealso
- marker="erts:erlang#ports-0">erlang:ports/0</seealso> now
+ <c>erlang:ports/0</c> BIF</tag> <item>Since <seemfa
+ marker="erts:erlang#ports/0">erlang:ports/0</seemfa> now
can be preempted, the responsiveness of the system as a
whole has been improved. A call to <c>erlang:ports/0</c>
may, however, take a much longer time to complete than
@@ -12714,19 +14542,19 @@
<p><em>Potential incompatibilities</em>:</p> <list>
<item><c>driver_send_term()</c> has been deprecated and
has been scheduled for removal in OTP-R17. Replace usage
- of <c>driver_send_term()</c> with usage of <seealso
- marker="erts:erl_driver#erl_drv_send_term">erl_drv_send_term()</seealso>.</item>
+ of <c>driver_send_term()</c> with usage of <seecref
+ marker="erts:erl_driver#erl_drv_send_term">erl_drv_send_term()</seecref>.</item>
<item><c>driver_output_term()</c> has been deprecated and
has been scheduled for removal in OTP-R17. Replace usage
- of <c>driver_output_term()</c> with usage of <seealso
- marker="erts:erl_driver#erl_drv_output_term">erl_drv_output_term()</seealso>.</item>
- <item>The new function <seealso
- marker="erts:erl_driver#erl_drv_busy_msgq_limits">erl_drv_busy_msgq_limits()</seealso>
+ of <c>driver_output_term()</c> with usage of <seecref
+ marker="erts:erl_driver#erl_drv_output_term">erl_drv_output_term()</seecref>.</item>
+ <item>The new function <seecref
+ marker="erts:erl_driver#erl_drv_busy_msgq_limits">erl_drv_busy_msgq_limits()</seecref>
has been added in order to able to control management of
port queues.</item> </list>
- <p>The <seealso
+ <p>The <seecref
marker="erts:erl_driver#version_management">driver API
- version</seealso> has been bumped to 2.1 from 2.0 due to
+ version</seecref> has been bumped to 2.1 from 2.0 due to
the above changes in the driver API.</p>
<p>
*** POTENTIAL INCOMPATIBILITY ***</p>
@@ -12770,9 +14598,9 @@
<p>
The previous default of a maximum of 32768 simultaneous
processes has been raised to 262144. This value can be
- changed using the the <seealso
- marker="erl#+P">+P</seealso> command line flag of
- <seealso marker="erl">erl(1)</seealso>. Note that the
+ changed using the the <seecom
+ marker="erl#+P">+P</seecom> command line flag of
+ <seecom marker="erl">erl(1)</seecom>. Note that the
value passed now is considered as a hint, and that actual
value chosen in most cases will be a power of two.</p>
<p>
@@ -12794,14 +14622,14 @@
Timing in the system will due to this also change.</p>
<p>
The previous strategy can still be enabled by passing the
- <seealso marker="erl#+sws">+sws legacy</seealso> command
+ <seecom marker="erl#+sws">+sws legacy</seecom> command
line flag to <c>erl</c>.</p>
<p>
Own Id: OTP-10661 Aux Id: OTP-10033 </p>
</item>
<item>
<p>
- The <seealso marker="erl#+stbt">+stbt</seealso> command line
+ The <seecom marker="erl#+stbt">+stbt</seecom> command line
argument of <c>erl</c> was added. This argument can be
used for trying to set scheduler bind type. Upon failure
unbound schedulers will be used.</p>
@@ -12870,8 +14698,8 @@
pool by user implemented drivers.</p>
<p>
The amount of async threads can be controlled by using
- the <seealso
- marker="erl#async_thread_pool_size"><c>+A</c></seealso>
+ the <seecom
+ marker="erl#async_thread_pool_size"><c>+A</c></seecom>
command line argument of <c>erl(1)</c>. When running some
offline tools you <em>might</em> want to disable async
threads, but you are advised <em>not</em> to in the
@@ -13108,9 +14936,9 @@
</item>
<item>
<p>
- Clearer warnings about the dangers of misuse of <seealso
- marker="erl_nif#WARNING">native functions</seealso> and
- <seealso marker="erl_driver#WARNING">drivers</seealso>
+ Clearer warnings about the dangers of misuse of <seecref
+ marker="erl_nif#WARNING">native functions</seecref> and
+ <seecref marker="erl_driver#WARNING">drivers</seecref>
have been added to the documentation.</p>
<p>
Own Id: OTP-10557</p>
@@ -13503,8 +15331,8 @@
disabled at compile time (<c>--disable-threads</c> had
been passed to <c>configure</c>), and the <c>+A</c>
command line argument of <c>erl</c> was passed when
- starting the runtime system, <seealso
- marker="erl_driver#driver_system_info">driver_system_info()</seealso>
+ starting the runtime system, <seecref
+ marker="erl_driver#driver_system_info">driver_system_info()</seecref>
erroneously claimed that the runtime system had async
threads even though it had not.</p>
<p>
@@ -13525,7 +15353,7 @@
<p>
A proposal for a new scheduler wakeup strategy has been
implemented. For more information see the documentation
- of the <seealso marker="erl#+sws">+sws</seealso> command
+ of the <seecom marker="erl#+sws">+sws</seecom> command
line argument of <c>erl</c>.</p>
<p>
Own Id: OTP-10033 Aux Id: Seq12025 </p>
@@ -13534,8 +15362,8 @@
<p>
A switch for configuration of busy wait length for
scheduler threads has been added. For more information
- see the documentation of the <seealso
- marker="erl#+sbwt">+sbwt</seealso> command line argument
+ see the documentation of the <seecom
+ marker="erl#+sbwt">+sbwt</seecom> command line argument
of <c>erl</c>.</p>
<p>
Own Id: OTP-10044 Aux Id: Seq11976 </p>
@@ -14224,13 +16052,13 @@
aware drivers. Most importantly the return types for
ErlDrvEntry callbacks 'call' and 'control' has ben
enlarged which require drivers to be changed or they will
- cause emulator crashes. See <seealso
+ cause emulator crashes. See <seecref
marker="erl_driver#rewrites_for_64_bits"> Rewrites for
- 64-bit driver interface </seealso> in the driver manual.
+ 64-bit driver interface </seecref> in the driver manual.
</p>
- <p>Due to this driver <seealso
+ <p>Due to this driver <seecref
marker="erl_driver#version_management">version
- management</seealso> is now mandatory. A driver that is
+ management</seecref> is now mandatory. A driver that is
not written with version management or a driver that was
compiled with the wrong major version will be not be
loaded by the emulator.</p>
@@ -15240,8 +17068,8 @@
</item>
<item>
<p>
- The <c>configure</c> command line argument <seealso
- marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--enable-ethread-pre-pentium4-compatibility</seealso>
+ The <c>configure</c> command line argument <seeguide
+ marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--enable-ethread-pre-pentium4-compatibility</seeguide>
had no effect. This option is now also automatically
enabled if required on the build machine.</p>
<p>
@@ -15268,10 +17096,10 @@
</item>
<item>
<p>
- A bug in <seealso
- marker="erl_driver#erl_drv_tsd_get">erl_drv_tsd_get()</seealso>
- and <seealso
- marker="erl_nif#enif_tsd_get">enif_tsd_get()</seealso>
+ A bug in <seecref
+ marker="erl_driver#erl_drv_tsd_get">erl_drv_tsd_get()</seecref>
+ and <seecref
+ marker="erl_nif#enif_tsd_get">enif_tsd_get()</seecref>
could cause an emulator crash. These functions are
currently not used in OTP. That is, the crash only occur
on systems with user implemented NIF libraries, or
@@ -15427,8 +17255,8 @@
<p>
The distribution buffer busy limit can now be configured
at system startup. For more information see the
- documentation of the <c>erl</c> <seealso
- marker="erl#+zdbbl">+zdbbl</seealso> command line flag.
+ documentation of the <c>erl</c> <seecom
+ marker="erl#+zdbbl">+zdbbl</seecom> command line flag.
(Thanks to Scott Lystig Fritchie)</p>
<p>
Own Id: OTP-8912</p>
@@ -15769,15 +17597,15 @@
lines. The reader optimized rwlock implementation is used
by miscellaneous rwlocks in the runtime system that are
known to be read-locked frequently, and can be enabled on
- ETS tables by passing the <seealso
+ ETS tables by passing the <seeerl
marker="stdlib:ets#new_2_read_concurrency">{read_concurrency,
- true}</seealso> option upon table creation. See the
- documentation of <seealso
- marker="stdlib:ets#new/2">ets:new/2</seealso> for more
+ true}</seeerl> option upon table creation. See the
+ documentation of <seemfa
+ marker="stdlib:ets#new/2">ets:new/2</seemfa> for more
information. The reader optimized rwlock implementation
can be fine tuned when starting the runtime system. For
- more information, see the documentation of the <seealso
- marker="erts:erl#+rg">+rg</seealso> command line argument
+ more information, see the documentation of the <seecom
+ marker="erts:erl#+rg">+rg</seecom> command line argument
of <c>erl</c>.</p>
<p>
There is also a new implementation of rwlocks that is not
@@ -15819,8 +17647,8 @@
utilize optimized native atomic operations on more
platforms than before. If <c>configure</c> warns about no
atomic implementation available, try using the
- <c>libatomic_ops</c> library. Use the <seealso
- marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--with-libatomic_ops=PATH</seealso>
+ <c>libatomic_ops</c> library. Use the <seeguide
+ marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--with-libatomic_ops=PATH</seeguide>
<c>configure</c> command line argument when specifying
where the <c>libatomic_ops</c> installation is located.
The <c>libatomic_ops</c> library can be downloaded from:
@@ -15837,8 +17665,8 @@
library will now use instructions that first appeared on
the pentium 4 processor. If you want the runtime system
to be compatible with older processors (back to 486) you
- need to pass the <seealso
- marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--enable-ethread-pre-pentium4-compatibility</seealso>
+ need to pass the <seeguide
+ marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--enable-ethread-pre-pentium4-compatibility</seeguide>
<c>configure</c> command line argument when configuring
the system.</p>
<p>
@@ -15930,8 +17758,8 @@
Calling <c>erlang:system_info/1</c> with the new argument
<c>update_cpu_info</c> will make the runtime system
reread and update the internally stored CPU information.
- For more information see the documentation of <seealso
- marker="erlang#system_info_update_cpu_info">erlang:system_info(update_cpu_info)</seealso>.</p>
+ For more information see the documentation of <seeerl
+ marker="erlang#system_info_update_cpu_info">erlang:system_info(update_cpu_info)</seeerl>.</p>
<p>
The CPU topology is now automatically detected on Windows
systems with less than 33 logical processors. The runtime
@@ -15966,8 +17794,8 @@
<item>
<p>
The scheduler wakeup threshold is now possible to adjust
- at system boot. For more information see the <seealso
- marker="erl#+swt">+swt</seealso> command line argument of
+ at system boot. For more information see the <seecom
+ marker="erl#+swt">+swt</seecom> command line argument of
<c>erl</c>.</p>
<p>
Own Id: OTP-8811</p>
@@ -16023,8 +17851,8 @@
<list>
<item>
<p>
- Driver threads, such as async threads, using <seealso
- marker="erl_driver#ErlDrvPDL">port data locks</seealso>
+ Driver threads, such as async threads, using <seecref
+ marker="erl_driver#ErlDrvPDL">port data locks</seecref>
peeked at the port status field without proper locking
when looking up the driver queue.</p>
<p>
@@ -16176,9 +18004,9 @@
</item>
<item>
<p>
- A user defined CPU topology set via a call to <seealso
+ A user defined CPU topology set via a call to <seeerl
marker="erlang#system_flag_cpu_topology">erlang:system_flag(cpu_topology,
- CPUTopology)</seealso> was not properly verified, and
+ CPUTopology)</seeerl> was not properly verified, and
could in worst case cause an emulator crash. The emulator
crash could only occur when a user defined CPU topology
already existed and was redefined.</p>
@@ -16266,8 +18094,8 @@
with user defined memory management
(<c>enif_make_resource_binary</c>) </item></list> <p>And
some incompatible changes made to the API. For more
- information see the warning text in <seealso
- marker="erl_nif">erl_nif(3)</seealso>.</p>
+ information see the warning text in <seecref
+ marker="erl_nif">erl_nif(3)</seecref>.</p>
<p>
*** POTENTIAL INCOMPATIBILITY ***</p>
<p>
@@ -16415,11 +18243,11 @@
for example another Erlang runtime system) also bind
threads to logical processors, there might be a
performance penalty instead. If this is the case you, are
- are advised to unbind the schedulers using the <seealso
- marker="erl#+sbt">+sbtu</seealso> command line argument,
- or by invoking <seealso
+ are advised to unbind the schedulers using the <seecom
+ marker="erl#+sbt">+sbtu</seecom> command line argument,
+ or by invoking <seeerl
marker="erlang#system_flag_scheduler_bind_type">erlang:system_flag(scheduler_bind_type,
- unbound)</seealso>.</p>
+ unbound)</seeerl>.</p>
<p>
Own Id: OTP-8666</p>
</item>
@@ -16531,8 +18359,8 @@
<list>
<item>
<p>
- Driver threads, such as async threads, using <seealso
- marker="erl_driver#ErlDrvPDL">port data locks</seealso>
+ Driver threads, such as async threads, using <seecref
+ marker="erl_driver#ErlDrvPDL">port data locks</seecref>
peeked at the port status field without proper locking
when looking up the driver queue.</p>
<p>
@@ -17131,8 +18959,8 @@
types <c>ErlDrvSInt64</c> and <c>ErlDrvUInt64</c> have
been introduced.</p>
<p>
- For more information see the <seealso
- marker="erl_driver">erl_driver(3)</seealso>
+ For more information see the <seecref
+ marker="erl_driver">erl_driver(3)</seecref>
documentation.</p>
<p>
Own Id: OTP-8205</p>
@@ -17188,12 +19016,12 @@
</item>
<item>
<p>
- The <seealso
- marker="erlang#port_command/3">erlang:port_command/3</seealso>
+ The <seemfa
+ marker="erlang#port_command/3">erlang:port_command/3</seemfa>
BIF has been added. <c>erlang:port_command/3</c> is
currently not auto imported, but it is planned to be auto
imported in OTP R14. For more information see the
- <seealso marker="erlang">erlang(3)</seealso>
+ <seeerl marker="erlang">erlang(3)</seeerl>
documentation.</p>
<p>
Own Id: OTP-8225</p>
diff --git a/erts/doc/src/part.xml.src b/erts/doc/src/part.xml
index 9b20beffad..5c5e1bedb4 100644
--- a/erts/doc/src/part.xml.src
+++ b/erts/doc/src/part.xml
@@ -42,7 +42,6 @@
<xi:include href="tty.xml"/>
<xi:include href="driver.xml"/>
<xi:include href="inet_cfg.xml"/>
- %ESOCK_USE_SOCKET_USAGE_XML%
<xi:include href="erl_ext_dist.xml"/>
<xi:include href="erl_dist_protocol.xml"/>
</part>
diff --git a/erts/doc/src/persistent_term.xml b/erts/doc/src/persistent_term.xml
index 75e2597e56..46cb313ff1 100644
--- a/erts/doc/src/persistent_term.xml
+++ b/erts/doc/src/persistent_term.xml
@@ -32,8 +32,8 @@
<module since="OTP 21.2">persistent_term</module>
<modulesummary>Persistent terms.</modulesummary>
<description>
- <p>This module is similar to <seealso
- marker="stdlib:ets"><c>ets</c></seealso> in that it provides a
+ <p>This module is similar to <seeerl
+ marker="stdlib:ets"><c>ets</c></seeerl> in that it provides a
storage for Erlang terms that can be accessed in constant time,
but with the difference that <c>persistent_term</c> has been
highly optimized for reading terms at the expense of writing and
@@ -49,21 +49,21 @@
make sure to fully understand the consequence to system
performance when updating or deleting persistent terms.</p></warning>
- <p>Term lookup (using <seealso
- marker="#get/1"><c>get/1</c></seealso>), is done in constant time
+ <p>Term lookup (using <seemfa
+ marker="#get/1"><c>get/1</c></seemfa>), is done in constant time
and without taking any locks, and the term is <strong>not</strong>
copied to the heap (as is the case with terms stored in ETS
tables).</p>
- <p>Storing or updating a term (using <seealso
- marker="#put/2"><c>put/2</c></seealso>) is proportional to the
+ <p>Storing or updating a term (using <seemfa
+ marker="#put/2"><c>put/2</c></seemfa>) is proportional to the
number of already created persistent terms because the hash table
holding the keys will be copied. In addition, the term itself will
be copied.</p>
- <p>When a (complex) term is deleted (using <seealso
- marker="#erase/1"><c>erase/1</c></seealso>) or replaced by another
- (using <seealso marker="#put/2"><c>put/2</c></seealso>), a global
+ <p>When a (complex) term is deleted (using <seemfa
+ marker="#erase/1"><c>erase/1</c></seemfa>) or replaced by another
+ (using <seemfa marker="#put/2"><c>put/2</c></seemfa>), a global
garbage collection is initiated. It works like this:</p>
<list>
@@ -105,12 +105,12 @@
<section>
<title>Storing Huge Persistent Terms</title>
<p>The current implementation of persistent terms uses the literal
- <seealso marker="erts_alloc">allocator</seealso> also used for
+ <seecref marker="erts_alloc">allocator</seecref> also used for
literals (constant terms) in BEAM code. By default, 1 GB of
virtual address space is reserved for literals in BEAM code and
persistent terms. The amount of virtual address space reserved for
- literals can be changed by using the <seealso
- marker="erts_alloc#MIscs"><c>+MIscs option</c></seealso> when
+ literals can be changed by using the <seecref
+ marker="erts_alloc#MIscs"><c>+MIscs option</c></seecref> when
starting the emulator.</p>
<p>Here is an example how the reserved virtual address space for literals
@@ -134,7 +134,7 @@
<p>Updating a persistent term with the same value as it already
has is specially optimized to do nothing quickly; thus, there is
no need compare the old and new values and avoid calling
- <seealso marker="#put/2"><c>put/2</c></seealso> if the values
+ <seemfa marker="#put/2"><c>put/2</c></seemfa> if the values
are equal.</p>
<p>When atoms or other terms that fit in one machine word are
@@ -210,8 +210,8 @@
persistent term associated with the key.</p>
<p>If there existed a previous persistent term associated with
key <c><anno>Key</anno></c>, a global GC has been initiated
- when <c>erase/1</c> returns. See <seealso
- marker="#description">Description</seealso>.</p>
+ when <c>erase/1</c> returns. See <seeerl
+ marker="#description">Description</seeerl>.</p>
</desc>
</func>
@@ -285,8 +285,8 @@
quickly.</p>
<p>If there existed a previous persistent term associated with
key <c><anno>Key</anno></c>, a global GC has been initiated
- when <c>put/2</c> returns. See <seealso
- marker="#description">Description</seealso>.</p>
+ when <c>put/2</c> returns. See <seeerl
+ marker="#description">Description</seeerl>.</p>
</desc>
</func>
</funcs>
diff --git a/erts/doc/src/ref_man.xml.src b/erts/doc/src/ref_man.xml
index 7dd003763c..f78d6549a8 100644
--- a/erts/doc/src/ref_man.xml.src
+++ b/erts/doc/src/ref_man.xml
@@ -34,24 +34,23 @@
<xi:include href="atomics.xml"/>
<xi:include href="counters.xml"/>
<xi:include href="driver_entry.xml"/>
- <xi:include href="epmd.xml"/>
- <xi:include href="erl.xml"/>
+ <xi:include href="epmd_cmd.xml"/>
+ <xi:include href="erl_cmd.xml"/>
<xi:include href="erlang.xml"/>
- <xi:include href="erlc.xml"/>
+ <xi:include href="erlc_cmd.xml"/>
<xi:include href="erl_driver.xml"/>
<xi:include href="erl_nif.xml"/>
<xi:include href="erl_prim_loader.xml"/>
- <xi:include href="erlsrv.xml"/>
+ <xi:include href="erlsrv_cmd.xml"/>
<xi:include href="erl_tracer.xml"/>
<xi:include href="erts_alloc.xml"/>
- <xi:include href="escript.xml"/>
+ <xi:include href="escript_cmd.xml"/>
<xi:include href="init.xml"/>
<xi:include href="persistent_term.xml"/>
- <xi:include href="run_erl.xml"/>
- %ESOCK_USE_SOCKET_XML%
- <xi:include href="start.xml"/>
- <xi:include href="start_erl.xml"/>
- <xi:include href="werl.xml"/>
+ <xi:include href="run_erl_cmd.xml"/>
+ <xi:include href="start_cmd.xml"/>
+ <xi:include href="start_erl_cmd.xml"/>
+ <xi:include href="werl_cmd.xml"/>
<xi:include href="zlib.xml"/>
</application>
diff --git a/erts/doc/src/run_erl.xml b/erts/doc/src/run_erl_cmd.xml
index fa36457489..9d642fa418 100644
--- a/erts/doc/src/run_erl.xml
+++ b/erts/doc/src/run_erl_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>run_erl</title>
@@ -42,8 +42,8 @@
it possible to monitor and debug an embedded system remotely.</p>
<p>For more information about the use, see the
- <seealso marker="doc/embedded:embedded_solaris">
- Embedded System User's Guide</seealso> in System Documentation.</p>
+ <seeguide marker="system/embedded:embedded_solaris">
+ Embedded System User's Guide</seeguide> in System Documentation.</p>
</description>
<funcs>
@@ -84,8 +84,8 @@
<p>Up to five log files at maximum 100 KB each with the content
of the standard streams from and to the command. (Both the
number of logs and sizes can be changed by environment
- variables, see section <seealso
- marker="#environment_variables">Environment Variables</seealso>
+ variables, see section <seecom
+ marker="#environment_variables">Environment Variables</seecom>
below.)</p>
<p>When the logs are full, <c><![CDATA[run_erl]]></c> deletes
and reuses the oldest log file.</p>
@@ -209,8 +209,7 @@
<section>
<title>See Also</title>
- <p><seealso marker="start"><c>start(1)</c></seealso>,
- <seealso marker="start_erl"><c>start_erl(1)</c></seealso></p>
+ <p><seecom marker="start"><c>start(1)</c></seecom>,
+ <seecom marker="start_erl"><c>start_erl(1)</c></seecom></p>
</section>
</comref>
-
diff --git a/erts/doc/src/specs.xml.src b/erts/doc/src/specs.xml
index 54224c15f5..0b943e6295 100644
--- a/erts/doc/src/specs.xml.src
+++ b/erts/doc/src/specs.xml
@@ -5,7 +5,6 @@
<xi:include href="../specs/specs_erl_tracer.xml"/>
<xi:include href="../specs/specs_init.xml"/>
<xi:include href="../specs/specs_persistent_term.xml"/>
- %ESOCK_USE_SOCKET_SPECS_XML%
<xi:include href="../specs/specs_zlib.xml"/>
<xi:include href="../specs/specs_atomics.xml"/>
<xi:include href="../specs/specs_counters.xml"/>
diff --git a/erts/doc/src/start.xml b/erts/doc/src/start_cmd.xml
index 6eac47fe94..6e83b71fde 100644
--- a/erts/doc/src/start.xml
+++ b/erts/doc/src/start_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>start</title>
@@ -39,8 +39,8 @@
how to start up the Erlang system in embedded mode on Unix.</p>
<p>For more information about the use, see the
- <seealso marker="doc/embedded:embedded_solaris">
- Embedded System User's Guide</seealso> in System Documentation.</p>
+ <seeguide marker="system/embedded:embedded_solaris">
+ Embedded System User's Guide</seeguide> in System Documentation.</p>
</description>
<funcs>
@@ -66,8 +66,7 @@
<section>
<title>See Also</title>
- <p><seealso marker="run_erl"><c>run_erl(1)</c></seealso>,
- <seealso marker="start_erl"><c>start_erl(1)</c></seealso></p>
+ <p><seecom marker="run_erl"><c>run_erl(1)</c></seecom>,
+ <seecom marker="start_erl"><c>start_erl(1)</c></seecom></p>
</section>
</comref>
-
diff --git a/erts/doc/src/start_erl.xml b/erts/doc/src/start_erl_cmd.xml
index 4887d4606e..b869a8919e 100644
--- a/erts/doc/src/start_erl.xml
+++ b/erts/doc/src/start_erl_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>start_erl</title>
@@ -89,7 +89,7 @@
only option <c>-rootdir</c> is specified, the directory is
assumed to be &lt;Erlang root&gt;\\releases.</p>
</item>
- <tag><c>-rootdir &lt;Erlang root directory&gt;</c></tag>
+ <tag><c>-rootdir &lt;Erlang root directory&gt;</c></tag>
<item>
<p>Mandatory if <c>-reldir</c> is not specified and no
<c><![CDATA[RELDIR]]></c> exists in the environment. This
@@ -161,9 +161,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="erlsrv"><c>erlsrv(1)</c></seealso>,
- <seealso marker="sasl:release_handler">
- <c>release_handler(3)</c></seealso></p>
+ <p><seecom marker="erlsrv"><c>erlsrv(1)</c></seecom>,
+ <seeerl marker="sasl:release_handler">
+ <c>release_handler(3)</c></seeerl></p>
</section>
</comref>
-
diff --git a/erts/doc/src/time_correction.xml b/erts/doc/src/time_correction.xml
index a9f06d8a7d..bf57d69d95 100644
--- a/erts/doc/src/time_correction.xml
+++ b/erts/doc/src/time_correction.xml
@@ -37,25 +37,25 @@
<title>New Extended Time Functionality</title>
<note><p>As from Erlang/OTP 18 (ERTS 7.0) the time functionality
has been extended. This includes a
- <seealso marker="#The_New_Time_API">new API</seealso>
+ <seeguide marker="#The_New_Time_API">new API</seeguide>
for time and
- <seealso marker="#Time_Warp_Modes">time warp
- modes</seealso> that change the system behavior when
+ <seeguide marker="#Time_Warp_Modes">time warp
+ modes</seeguide> that change the system behavior when
system time changes.</p>
- <p>The <seealso marker="#No_Time_Warp_Mode">default
- time warp mode</seealso> has the same behavior as before, and the
+ <p>The <seeguide marker="#No_Time_Warp_Mode">default
+ time warp mode</seeguide> has the same behavior as before, and the
old API still works. Thus, you are not required to change
anything unless you want to. However, <em>you are strongly
encouraged to use the new API</em> instead of the old API based
- on <seealso marker="erlang#now/0"><c>erlang:now/0</c></seealso>.
+ on <seemfa marker="erlang#now/0"><c>erlang:now/0</c></seemfa>.
<c>erlang:now/0</c> is deprecated, as it is and
will be a scalability bottleneck.</p>
<p>By using the new API, you
automatically get scalability and performance improvements. This
also enables you to use the
- <seealso marker="#Multi_Time_Warp_Mode">multi-time warp mode</seealso>
+ <seeguide marker="#Multi_Time_Warp_Mode">multi-time warp mode</seeguide>
that improves accuracy and precision of time measurements.</p>
</note>
</section>
@@ -94,7 +94,7 @@
<section>
<title>UTC</title>
<p>Coordinated Universal Time. UTC almost aligns with
- <seealso marker="#UT1">UT1</seealso>. However, UTC uses the
+ <seeguide marker="#UT1">UT1</seeguide>. However, UTC uses the
SI definition of a second, which has not exactly the same length
as the second used by UT1. This means that UTC slowly drifts from
UT1. To keep UTC relatively in sync with UT1, leap seconds
@@ -108,7 +108,7 @@
<p>Time since
<url href="http://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap03.html#tag_21_03_00_17">
Epoch</url>.
- Epoch is defined to be 00:00:00 <seealso marker="#UTC">UTC</seealso>,
+ Epoch is defined to be 00:00:00 <seeguide marker="#UTC">UTC</seeguide>,
1970-01-01.
<url href="http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap04.html#tag_04_14">
A day in POSIX time</url>
@@ -137,7 +137,7 @@
<p>The shortest time interval that can be distinguished
repeatedly and reliably when reading time values. Precision
is limited by the
- <seealso marker="#Time_Resolution">resolution</seealso>, but
+ <seeguide marker="#Time_Resolution">resolution</seeguide>, but
resolution and precision can differ significantly.</p>
</section>
@@ -159,19 +159,19 @@
<section>
<title>OS System Time</title>
<p>The operating systems view of
- <seealso marker="#POSIX_Time">POSIX time</seealso>. To
+ <seeguide marker="#POSIX_Time">POSIX time</seeguide>. To
retrieve it, call
- <seealso marker="kernel:os#system_time/0">
- <c>os:system_time()</c></seealso>.
+ <seemfa marker="kernel:os#system_time/0">
+ <c>os:system_time()</c></seemfa>.
This may or may not be an accurate view of POSIX time. This time
may typically be adjusted both backwards and forwards without
- limitation. That is, <seealso marker="#Time_Warp">time warps</seealso>
+ limitation. That is, <seeguide marker="#Time_Warp">time warps</seeguide>
may be observed.</p>
<p>To get information about the Erlang runtime
system's source of OS system time, call
- <seealso marker="erlang#system_info_os_system_time_source">
- <c>erlang:system_info(os_system_time_source)</c></seealso>.</p>
+ <seeerl marker="erlang#system_info_os_system_time_source">
+ <c>erlang:system_info(os_system_time_source)</c></seeerl>.</p>
</section>
<marker id="OS_Monotonic_Time"/>
@@ -183,33 +183,33 @@
uncommon that OS monotonic time stops if the system is
suspended. This time typically increases since some
unspecified point in time that is not connected to
- <seealso marker="#OS_System_Time">OS system time</seealso>.
+ <seeguide marker="#OS_System_Time">OS system time</seeguide>.
This type of time is not necessarily provided by all OSs.</p>
<p>To get information about the Erlang
runtime system's source of OS monotonic time, call
- <seealso marker="erlang#system_info_os_monotonic_time_source">
- <c>erlang:system_info(os_monotonic_time_source)</c></seealso>.</p>
+ <seeerl marker="erlang#system_info_os_monotonic_time_source">
+ <c>erlang:system_info(os_monotonic_time_source)</c></seeerl>.</p>
</section>
<marker id="Erlang_System_Time"/>
<section>
<title>Erlang System Time</title>
<p>The Erlang runtime systems view of
- <seealso marker="#POSIX_Time">POSIX time</seealso>. To
+ <seeguide marker="#POSIX_Time">POSIX time</seeguide>. To
retrieve it, call
- <seealso marker="erlang#system_time/0">
- <c>erlang:system_time()</c></seealso>.</p>
+ <seemfa marker="erlang#system_time/0">
+ <c>erlang:system_time()</c></seemfa>.</p>
<p>This time may or may not be an accurate view of POSIX time,
and may
- or may not align with <seealso marker="#OS_System_Time">OS system
- time</seealso>. The runtime system works towards aligning the two
+ or may not align with <seeguide marker="#OS_System_Time">OS system
+ time</seeguide>. The runtime system works towards aligning the two
system times. Depending on the
- <seealso marker="#Time_Warp_Modes">time warp mode</seealso> used,
+ <seeguide marker="#Time_Warp_Modes">time warp mode</seeguide> used,
this can be achieved by letting Erlang
- system time perform a <seealso marker="#Time_Warp">time
- warp</seealso>.</p>
+ system time perform a <seeguide marker="#Time_Warp">time
+ warp</seeguide>.</p>
</section>
<marker id="Erlang_Monotonic_Time"/>
@@ -218,22 +218,22 @@
<p>A monotonically increasing time provided by the
Erlang runtime system. Erlang monotonic time increases since
some unspecified point in time. To retrieve it, call
- <seealso marker="erlang#monotonic_time/0">
- <c>erlang:monotonic_time()</c></seealso>.</p>
+ <seemfa marker="erlang#monotonic_time/0">
+ <c>erlang:monotonic_time()</c></seemfa>.</p>
- <p>The <seealso marker="#Time_Accuracy">accuracy</seealso> and
- <seealso marker="#Time_Precision">precision</seealso> of Erlang
+ <p>The <seeguide marker="#Time_Accuracy">accuracy</seeguide> and
+ <seeguide marker="#Time_Precision">precision</seeguide> of Erlang
monotonic time heavily depends on the following:</p>
<list type="bulleted">
<item>Accuracy and precision of
- <seealso marker="#OS_Monotonic_Time">OS monotonic time</seealso>
+ <seeguide marker="#OS_Monotonic_Time">OS monotonic time</seeguide>
</item>
<item>Accuracy and precision of
- <seealso marker="#OS_System_Time">OS system time</seealso>
+ <seeguide marker="#OS_System_Time">OS system time</seeguide>
</item>
- <item><seealso marker="#Time_Warp_Modes">
- time warp mode</seealso> used
+ <item><seeguide marker="#Time_Warp_Modes">
+ time warp mode</seeguide> used
</item>
</list>
@@ -247,16 +247,16 @@
everything that has anything to do with time. All timers,
regardless of it is a <c>receive ... after</c> timer, BIF timer,
or a timer in the
- <seealso marker="stdlib:timer"><c>timer(3)</c></seealso>
+ <seeerl marker="stdlib:timer"><c>timer(3)</c></seeerl>
module, are triggered relative Erlang monotonic time. Even
- <seealso marker="#Erlang_System_Time">Erlang system
- time</seealso> is based on Erlang monotonic time.
+ <seeguide marker="#Erlang_System_Time">Erlang system
+ time</seeguide> is based on Erlang monotonic time.
By adding current Erlang monotonic time with current time
offset, you get current Erlang system time.</p>
<p>To retrieve the current time offset, call
- <seealso marker="erlang#time_offset/0">
- <c>erlang:time_offset/0</c></seealso>.</p>
+ <seemfa marker="erlang#time_offset/0">
+ <c>erlang:time_offset/0</c></seemfa>.</p>
</section>
</section>
@@ -320,14 +320,14 @@
<title>Time Correction</title>
<p>If time correction is enabled, the Erlang runtime system
makes use of both
- <seealso marker="#OS_System_Time">OS system time</seealso>
- and <seealso marker="#OS_Monotonic_Time">OS monotonic time</seealso>,
+ <seeguide marker="#OS_System_Time">OS system time</seeguide>
+ and <seeguide marker="#OS_Monotonic_Time">OS monotonic time</seeguide>,
to adjust the frequency of the Erlang
monotonic clock. Time correction ensures that
- <seealso marker="#Erlang_Monotonic_Time">Erlang monotonic time</seealso>
+ <seeguide marker="#Erlang_Monotonic_Time">Erlang monotonic time</seeguide>
does not warp and that the frequency is relatively accurate.
The type of frequency adjustments depends on the time warp mode used.
- Section <seealso marker="#Time_Warp_Modes">Time Warp Modes</seealso>
+ Section <seeguide marker="#Time_Warp_Modes">Time Warp Modes</seeguide>
provides more details.</p>
<p>By default time correction is enabled if support for
@@ -336,15 +336,15 @@
implementation in the Erlang runtime system using
OS monotonic time. To check if your system has support
for OS monotonic time, call
- <seealso marker="erlang#system_info_os_monotonic_time_source">
- <c>erlang:system_info(os_monotonic_time_source)</c></seealso>.
+ <seeerl marker="erlang#system_info_os_monotonic_time_source">
+ <c>erlang:system_info(os_monotonic_time_source)</c></seeerl>.
To check if time correction is enabled on your system, call
- <seealso marker="erlang#system_info_time_correction">
- <c>erlang:system_info(time_correction)</c></seealso>.</p>
+ <seeerl marker="erlang#system_info_time_correction">
+ <c>erlang:system_info(time_correction)</c></seeerl>.</p>
<p>To enable or disable time correction, pass command-line argument
- <seealso marker="erl#+c"><c>+c [true|false]</c></seealso> to
- <seealso marker="erl"><c>erl(1)</c></seealso>.</p>
+ <seecom marker="erl#+c"><c>+c [true|false]</c></seecom> to
+ <seecom marker="erl"><c>erl(1)</c></seecom>.</p>
<p>If time correction is disabled, Erlang monotonic time
can warp forwards or stop, or even freeze for extended
@@ -362,10 +362,10 @@
<marker id="Time_Warp_Safe_Code"/>
<title>Time Warp Safe Code</title>
<p>Time warp safe code can handle
- a <seealso marker="#Time_Warp">time warp</seealso> of
- <seealso marker="#Erlang_System_Time">Erlang system time</seealso>.</p>
+ a <seeguide marker="#Time_Warp">time warp</seeguide> of
+ <seeguide marker="#Erlang_System_Time">Erlang system time</seeguide>.</p>
- <p><seealso marker="erlang#now/0"><c>erlang:now/0</c></seealso>
+ <p><seemfa marker="erlang#now/0"><c>erlang:now/0</c></seemfa>
behaves bad when Erlang system time warps. When Erlang
system time does a time warp backwards, the values returned
from <c>erlang:now/0</c> freeze (if you disregard the
@@ -381,25 +381,25 @@
and scalability perspective. So you really want to replace
the use of it with other functionality. For examples
of how to replace the use of <c>erlang:now/0</c>, see section
- <seealso marker="#Dos_and_Donts">How to Work with the New
- API</seealso>.</p>
+ <seeguide marker="#Dos_and_Donts">How to Work with the New
+ API</seeguide>.</p>
</section>
<section>
<title>Time Warp Modes</title>
<marker id="Time_Warp_Modes"/>
- <p>Current <seealso marker="#Erlang_System_Time">Erlang system
- time</seealso> is determined by adding the current
- <seealso marker="erlang#monotonic_time/0">Erlang monotonic time</seealso>
+ <p>Current <seeguide marker="#Erlang_System_Time">Erlang system
+ time</seeguide> is determined by adding the current
+ <seemfa marker="erlang#monotonic_time/0">Erlang monotonic time</seemfa>
with current
- <seealso marker="erlang#time_offset/0">time offset</seealso>. The
+ <seemfa marker="erlang#time_offset/0">time offset</seemfa>. The
time offset is managed differently depending on which time
warp mode you use.</p>
<p>To set the time warp mode, pass command-line argument
- <seealso marker="erl#+C_"><c>+C
- [no_time_warp|single_time_warp|multi_time_warp]</c></seealso> to
- <seealso marker="erl"><c>erl(1)</c></seealso>.</p>
+ <seecom marker="erl#+C_"><c>+C
+ [no_time_warp|single_time_warp|multi_time_warp]</c></seecom> to
+ <seecom marker="erl"><c>erl(1)</c></seecom>.</p>
<marker id="No_Time_Warp_Mode"/>
<section>
@@ -410,8 +410,8 @@
default <em>only</em> because this is how the runtime system
behaved until ERTS 7.0.
Ensure that your Erlang code that can execute during a time
- warp is <seealso marker="#Time_Warp_Safe_Code">time warp
- safe</seealso> before enabling other modes.</p>
+ warp is <seeguide marker="#Time_Warp_Safe_Code">time warp
+ safe</seeguide> before enabling other modes.</p>
<p>As the time offset is not allowed to change, time
correction must adjust the frequency of the Erlang
@@ -440,13 +440,13 @@
has no power supply, not even a battery, when it is
shut off. The system clock on such a system is typically
way off when the system boots. If
- <seealso marker="#No_Time_Warp_Mode">no time warp mode</seealso>
+ <seeguide marker="#No_Time_Warp_Mode">no time warp mode</seeguide>
is used, and the Erlang runtime system is started before
OS system time has been corrected, Erlang system time
can be wrong for a long time, centuries or even longer.</p>
<p>If you need to use Erlang code that is not
- <seealso marker="#Time_Warp_Safe_Code">time warp safe</seealso>,
+ <seeguide marker="#Time_Warp_Safe_Code">time warp safe</seeguide>,
and you need to start the Erlang runtime system before OS
system time has been corrected, you may want to use the single
time warp mode.</p>
@@ -454,8 +454,8 @@
<note><p>There are limitations to when you can
execute time warp unsafe code using this mode. If it is possible
to use time warp safe code only, it is <em>much</em> better
- to use the <seealso marker="#Multi_Time_Warp_Mode">multi-time
- warp mode</seealso> instead.</p></note>
+ to use the <seeguide marker="#Multi_Time_Warp_Mode">multi-time
+ warp mode</seeguide> instead.</p></note>
<p>Using the single time warp mode, the time offset is
handled in two phases:</p>
@@ -478,16 +478,16 @@
<p>If time correction is disabled, changes in OS system
time affects the monotonic clock the same way as
- when the <seealso marker="#No_Time_Warp_Mode">no time warp
- mode</seealso> is used.</p>
+ when the <seeguide marker="#No_Time_Warp_Mode">no time warp
+ mode</seeguide> is used.</p>
</item>
<tag>Final Phase</tag>
<item>
<p>This phase begins when the user finalizes the time
offset by calling
- <seealso marker="erlang#system_flag_time_offset">
- <c>erlang:system_flag(time_offset, finalize)</c></seealso>.
+ <seeerl marker="erlang#system_flag_time_offset">
+ <c>erlang:system_flag(time_offset, finalize)</c></seeerl>.
The finalization can only be performed once.</p>
<p>During finalization, the time offset is adjusted and
@@ -500,8 +500,8 @@
correction from now on also makes adjustments
to align Erlang system time with OS system
time. When the system is in the final phase, it behaves
- exactly as in <seealso marker="#No_Time_Warp_Mode">no
- time warp mode</seealso>.</p>
+ exactly as in <seeguide marker="#No_Time_Warp_Mode">no
+ time warp mode</seeguide>.</p>
</item>
</taglist>
@@ -542,8 +542,8 @@
<warning><p>To use this mode, ensure that
all Erlang code that will execute in both phases is
- <seealso marker="#Time_Warp_Safe_Code">time warp
- safe</seealso>.</p>
+ <seeguide marker="#Time_Warp_Safe_Code">time warp
+ safe</seeguide>.</p>
<p>Code executing only in the final phase does not have
to be able to cope with the time warp.</p></warning>
@@ -578,8 +578,8 @@
<warning><p>To use this mode, ensure that all
Erlang code that will execute on the runtime system is
- <seealso marker="#Time_Warp_Safe_Code">time warp
- safe</seealso>.</p></warning>
+ <seeguide marker="#Time_Warp_Safe_Code">time warp
+ safe</seeguide>.</p></warning>
</section>
</section>
@@ -587,7 +587,7 @@
<title>New Time API</title>
<marker id="The_New_Time_API"/>
<p>The old time API is based on
- <seealso marker="erlang#now/0"><c>erlang:now/0</c></seealso>.
+ <seemfa marker="erlang#now/0"><c>erlang:now/0</c></seemfa>.
<c>erlang:now/0</c> was intended to be used for many unrelated
things. This tied these unrelated operations together and
caused issues with performance, scalability, accuracy, and
@@ -599,8 +599,8 @@
remains "as is", but <em>you are strongly discouraged from using
it</em>. Many use cases of <c>erlang:now/0</c>
prevents you from using the new
- <seealso marker="#Multi_Time_Warp_Mode">multi-time warp
- mode</seealso>, which is an important part of this
+ <seeguide marker="#Multi_Time_Warp_Mode">multi-time warp
+ mode</seeguide>, which is an important part of this
new time functionality improvement.</p>
<p>Some of the new BIFs on some systems, perhaps surprisingly,
@@ -610,54 +610,54 @@
<p>The new API consists of the following new BIFs:</p>
<list>
- <item><p><seealso marker="erlang#convert_time_unit/3">
- <c>erlang:convert_time_unit/3</c></seealso></p></item>
- <item><p><seealso marker="erlang#monotonic_time/0">
- <c>erlang:monotonic_time/0</c></seealso></p></item>
- <item><p><seealso marker="erlang#monotonic_time/1">
- <c>erlang:monotonic_time/1</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_time/0">
- <c>erlang:system_time/0</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_time/1">
- <c>erlang:system_time/1</c></seealso></p></item>
- <item><p><seealso marker="erlang#time_offset/0">
- <c>erlang:time_offset/0</c></seealso></p></item>
- <item><p><seealso marker="erlang#time_offset/1">
- <c>erlang:time_offset/1</c></seealso></p></item>
- <item><p><seealso marker="erlang#timestamp/0">
- <c>erlang:timestamp/0</c></seealso></p></item>
- <item><p><seealso marker="erlang#unique_integer/0">
- <c>erlang:unique_integer/0</c></seealso></p></item>
- <item><p><seealso marker="erlang#unique_integer/1">
- <c>erlang:unique_integer/1</c></seealso></p></item>
- <item><p><seealso marker="kernel:os#system_time/0">
- <c>os:system_time/0</c></seealso></p></item>
- <item><p><seealso marker="kernel:os#system_time/1">
- <c>os:system_time/1</c></seealso></p></item>
+ <item><p><seemfa marker="erlang#convert_time_unit/3">
+ <c>erlang:convert_time_unit/3</c></seemfa></p></item>
+ <item><p><seemfa marker="erlang#monotonic_time/0">
+ <c>erlang:monotonic_time/0</c></seemfa></p></item>
+ <item><p><seemfa marker="erlang#monotonic_time/1">
+ <c>erlang:monotonic_time/1</c></seemfa></p></item>
+ <item><p><seemfa marker="erlang#system_time/0">
+ <c>erlang:system_time/0</c></seemfa></p></item>
+ <item><p><seemfa marker="erlang#system_time/1">
+ <c>erlang:system_time/1</c></seemfa></p></item>
+ <item><p><seemfa marker="erlang#time_offset/0">
+ <c>erlang:time_offset/0</c></seemfa></p></item>
+ <item><p><seemfa marker="erlang#time_offset/1">
+ <c>erlang:time_offset/1</c></seemfa></p></item>
+ <item><p><seemfa marker="erlang#timestamp/0">
+ <c>erlang:timestamp/0</c></seemfa></p></item>
+ <item><p><seemfa marker="erlang#unique_integer/0">
+ <c>erlang:unique_integer/0</c></seemfa></p></item>
+ <item><p><seemfa marker="erlang#unique_integer/1">
+ <c>erlang:unique_integer/1</c></seemfa></p></item>
+ <item><p><seemfa marker="kernel:os#system_time/0">
+ <c>os:system_time/0</c></seemfa></p></item>
+ <item><p><seemfa marker="kernel:os#system_time/1">
+ <c>os:system_time/1</c></seemfa></p></item>
</list>
<p>The new API also consists of extensions of the following existing BIFs:
</p>
<list>
- <item><p><seealso marker="erlang#monitor/2">
- <c>erlang:monitor(time_offset, clock_service)</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_flag_time_offset">
- <c>erlang:system_flag(time_offset, finalize)</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_info_os_monotonic_time_source">
- <c>erlang:system_info(os_monotonic_time_source)</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_info_os_system_time_source">
- <c>erlang:system_info(os_system_time_source)</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_info_time_offset">
- <c>erlang:system_info(time_offset)</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_info_time_warp_mode">
- <c>erlang:system_info(time_warp_mode)</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_info_time_correction">
- <c>erlang:system_info(time_correction)</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_info_start_time">
- <c>erlang:system_info(start_time)</c></seealso></p></item>
- <item><p><seealso marker="erlang#system_info_end_time">
- <c>erlang:system_info(end_time)</c></seealso></p></item>
+ <item><p><seemfa marker="erlang#monitor/2">
+ <c>erlang:monitor(time_offset, clock_service)</c></seemfa></p></item>
+ <item><p><seeerl marker="erlang#system_flag_time_offset">
+ <c>erlang:system_flag(time_offset, finalize)</c></seeerl></p></item>
+ <item><p><seeerl marker="erlang#system_info_os_monotonic_time_source">
+ <c>erlang:system_info(os_monotonic_time_source)</c></seeerl></p></item>
+ <item><p><seeerl marker="erlang#system_info_os_system_time_source">
+ <c>erlang:system_info(os_system_time_source)</c></seeerl></p></item>
+ <item><p><seeerl marker="erlang#system_info_time_offset">
+ <c>erlang:system_info(time_offset)</c></seeerl></p></item>
+ <item><p><seeerl marker="erlang#system_info_time_warp_mode">
+ <c>erlang:system_info(time_warp_mode)</c></seeerl></p></item>
+ <item><p><seeerl marker="erlang#system_info_time_correction">
+ <c>erlang:system_info(time_correction)</c></seeerl></p></item>
+ <item><p><seeerl marker="erlang#system_info_start_time">
+ <c>erlang:system_info(start_time)</c></seeerl></p></item>
+ <item><p><seeerl marker="erlang#system_info_end_time">
+ <c>erlang:system_info(end_time)</c></seeerl></p></item>
</list>
<marker id="The_New_Erlang_Monotonic_Time"/>
@@ -682,8 +682,8 @@
wrong at some point in time. Separate adjustments
of the two times are only performed in the time warp
modes, and only fully separated in the
- <seealso marker="#Multi_Time_Warp_Mode">multi-time
- warp mode</seealso>. All other modes than the
+ <seeguide marker="#Multi_Time_Warp_Mode">multi-time
+ warp mode</seeguide>. All other modes than the
multi-time warp mode are for backward
compatibility reasons. When using these modes, the
accuracy of Erlang monotonic time suffer, as
@@ -710,8 +710,8 @@
happened. The change in Erlang system time occurs when the
current time offset is changed. We have therefore
introduced the possibility to monitor the time offset using
- <seealso marker="erlang#monitor/2">
- <c>erlang:monitor(time_offset, clock_service)</c></seealso>.
+ <seemfa marker="erlang#monitor/2">
+ <c>erlang:monitor(time_offset, clock_service)</c></seemfa>.
A process monitoring the time
offset is sent a message on the following format
when the time offset is changed:</p>
@@ -727,8 +727,8 @@
produces unique and strictly monotonically increasing
values. To detach this functionality from
time measurements, we have introduced
- <seealso marker="erlang#unique_integer/1">
- <c>erlang:unique_integer()</c></seealso>.</p>
+ <seemfa marker="erlang#unique_integer/1">
+ <c>erlang:unique_integer()</c></seemfa>.</p>
</section>
<marker id="Dos_and_Donts"/>
@@ -749,14 +749,14 @@
<do>
<p>
Use
- <seealso marker="erlang#system_time/1">
- <c>erlang:system_time/1</c></seealso>
+ <seemfa marker="erlang#system_time/1">
+ <c>erlang:system_time/1</c></seemfa>
to retrieve the current Erlang system time on the
- <seealso marker="erlang#type_time_unit">time unit</seealso>
+ <seeerl marker="erlang#type_time_unit">time unit</seeerl>
of your choice.</p>
<p>If you want the same format as returned by <c>erlang:now/0</c>,
- use <seealso marker="erlang#timestamp/0">
- <c>erlang:timestamp/0</c></seealso>.
+ use <seemfa marker="erlang#timestamp/0">
+ <c>erlang:timestamp/0</c></seemfa>.
</p>
</do>
</section>
@@ -768,26 +768,26 @@
<p>
Take time stamps with <c>erlang:now/0</c> and calculate
the difference in time with
- <seealso marker="stdlib:timer#now_diff/2">
- <c>timer:now_diff/2</c></seealso>.
+ <seemfa marker="stdlib:timer#now_diff/2">
+ <c>timer:now_diff/2</c></seemfa>.
</p>
</dont>
<do>
<p>
Take time stamps with
- <seealso marker="erlang#monotonic_time/0">
- <c>erlang:monotonic_time/0</c></seealso>
+ <seemfa marker="erlang#monotonic_time/0">
+ <c>erlang:monotonic_time/0</c></seemfa>
and calculate the time difference using ordinary subtraction.
The result is in <c>native</c>
- <seealso marker="erlang#type_time_unit">time unit</seealso>.
+ <seeerl marker="erlang#type_time_unit">time unit</seeerl>.
If you want to convert the
result to another time unit, you can use
- <seealso marker="erlang#convert_time_unit/3">
- <c>erlang:convert_time_unit/3</c></seealso>.
+ <seemfa marker="erlang#convert_time_unit/3">
+ <c>erlang:convert_time_unit/3</c></seemfa>.
</p>
<p>An easier way to do this is to use
- <seealso marker="erlang#monotonic_time/1">
- <c>erlang:monotonic_time/1</c></seealso>
+ <seemfa marker="erlang#monotonic_time/1">
+ <c>erlang:monotonic_time/1</c></seemfa>
with the desired time unit. However, you can then lose accuracy
and precision.
</p>
@@ -807,8 +807,8 @@
<p>
Determine the order of events by saving the integer
returned by
- <seealso marker="erlang#unique_integer/1">
- <c>erlang:unique_integer([monotonic])</c></seealso>
+ <seemfa marker="erlang#unique_integer/1">
+ <c>erlang:unique_integer([monotonic])</c></seemfa>
when the event occurs. These integers are strictly
monotonically ordered on current runtime system instance
corresponding to creation time.
@@ -828,9 +828,9 @@
<do>
<p>
Determine the order of events by saving a tuple containing
- <seealso marker="erlang#monotonic_time/0">monotonic time</seealso>
- and a <seealso marker="erlang#unique_integer/1">strictly
- monotonically increasing integer</seealso> as follows:</p>
+ <seemfa marker="erlang#monotonic_time/0">monotonic time</seemfa>
+ and a <seemfa marker="erlang#unique_integer/1">strictly
+ monotonically increasing integer</seemfa> as follows:</p>
<code type="none">
Time = erlang:monotonic_time(),
@@ -848,8 +848,8 @@ EventTag = {Time, UMI}</code>
<p>If you are interested in Erlang system time at the
time when the event occurred, you can also save the time
offset before or after saving the events using
- <seealso marker="erlang#time_offset/0">
- <c>erlang:time_offset/0</c></seealso>.
+ <seemfa marker="erlang#time_offset/0">
+ <c>erlang:time_offset/0</c></seemfa>.
Erlang monotonic time added with the time
offset corresponds to Erlang system time.</p>
@@ -873,12 +873,12 @@ EventTag = {Time, UMI}</code>
<do>
<p>
Use the value returned from
- <seealso marker="erlang#unique_integer/0">
- <c>erlang:unique_integer/0</c></seealso>
+ <seemfa marker="erlang#unique_integer/0">
+ <c>erlang:unique_integer/0</c></seemfa>
to create a name unique on the current runtime system
instance. If you only want positive integers, you can use
- <seealso marker="erlang#unique_integer/1">
- <c>erlang:unique_integer([positive])</c></seealso>.
+ <seemfa marker="erlang#unique_integer/1">
+ <c>erlang:unique_integer([positive])</c></seemfa>.
</p>
</do>
</section>
@@ -894,12 +894,12 @@ EventTag = {Time, UMI}</code>
<do>
<p>
Seed random number generation using a combination of
- <seealso marker="erlang#monotonic_time/0">
- <c>erlang:monotonic_time()</c></seealso>,
- <seealso marker="erlang#time_offset/0">
- <c>erlang:time_offset()</c></seealso>,
- <seealso marker="erlang#unique_integer/0">
- <c>erlang:unique_integer()</c></seealso>,
+ <seemfa marker="erlang#monotonic_time/0">
+ <c>erlang:monotonic_time()</c></seemfa>,
+ <seemfa marker="erlang#time_offset/0">
+ <c>erlang:time_offset()</c></seemfa>,
+ <seemfa marker="erlang#unique_integer/0">
+ <c>erlang:unique_integer()</c></seemfa>,
and other functionality.
</p>
</do>
@@ -927,20 +927,20 @@ EventTag = {Time, UMI}</code>
<list type="bulleted">
<item>
- <seealso marker="erlang#system_info_start_time">
- <c>erlang:system_info(start_time)</c></seealso>
+ <seeerl marker="erlang#system_info_start_time">
+ <c>erlang:system_info(start_time)</c></seeerl>
</item>
<item>
- <seealso marker="erlang#system_info_end_time">
- <c>erlang:system_info(end_time)</c></seealso>
+ <seeerl marker="erlang#system_info_end_time">
+ <c>erlang:system_info(end_time)</c></seeerl>
</item>
<item>
- <seealso marker="erlang#system_info_os_monotonic_time_source">
- <c>erlang:system_info(os_monotonic_time_source)</c></seealso>
+ <seeerl marker="erlang#system_info_os_monotonic_time_source">
+ <c>erlang:system_info(os_monotonic_time_source)</c></seeerl>
</item>
<item>
- <seealso marker="erlang#system_info_os_system_time_source">
- <c>erlang:system_info(os_system_time_source)</c></seealso>
+ <seeerl marker="erlang#system_info_os_system_time_source">
+ <c>erlang:system_info(os_system_time_source)</c></seeerl>
</item>
</list>
diff --git a/erts/doc/src/werl.xml b/erts/doc/src/werl_cmd.xml
index 792fe204e8..5bf63b47fa 100644
--- a/erts/doc/src/werl.xml
+++ b/erts/doc/src/werl_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>werl</title>
@@ -43,7 +43,7 @@
<p>This starts Erlang in its own window, with fully
functioning command-line editing and scrollbars. All flags
except <c><![CDATA[-oldshell]]></c> work as they do for
- <seealso marker="erl"><c>erl(1)</c></seealso>.</p>
+ <seecom marker="erl"><c>erl(1)</c></seecom>.</p>
<list type="bulleted">
<item>
@@ -62,7 +62,7 @@
<p>In cases where you want to redirect standard input and/or
standard output or use Erlang in a pipeline, <c>werl</c> is
not suitable, and the <c>erl</c> program is to be used instead.</p>
-
+
<p>The <c>werl</c> window is in many ways modeled after the <c>xterm</c>
window present on other platforms, as the <c>xterm</c> model
fits well with line-oriented command-based interaction. This
@@ -102,7 +102,7 @@
use <c>Ctrl-P</c>.</p>
</item>
</list>
-
+
<p>A drop-down box in the toolbar contains the command
history. Selecting a command in the drop-down box inserts the command
at the prompt, as if you used the keyboard to retrieve the
@@ -115,4 +115,3 @@
</list>
</description>
</comref>
-
diff --git a/erts/doc/src/zlib.xml b/erts/doc/src/zlib.xml
index f3e29c8322..7b471bb95c 100644
--- a/erts/doc/src/zlib.xml
+++ b/erts/doc/src/zlib.xml
@@ -67,14 +67,14 @@ list_to_binary([Compressed|Last])</pre>
</item>
<tag><c>not_initialized</c></tag>
<item>The stream hasn't been initialized, eg. if
- <seealso marker="#inflateInit/1"><c>inflateInit/1</c></seealso> wasn't
+ <seemfa marker="#inflateInit/1"><c>inflateInit/1</c></seemfa> wasn't
called prior to a call to
- <seealso marker="#inflate/2"><c>inflate/2</c></seealso>.
+ <seemfa marker="#inflate/2"><c>inflate/2</c></seemfa>.
</item>
<tag><c>not_on_controlling_process</c></tag>
<item>The stream was used by a process that doesn't control it. Use
- <seealso marker="#set_controlling_process/2">
- <c>set_controlling_process/2</c></seealso> if you need to transfer
+ <seemfa marker="#set_controlling_process/2">
+ <c>set_controlling_process/2</c></seemfa> if you need to transfer
a stream to a different process.</item>
<tag><c>data_error</c></tag>
<item>The data contains errors.
@@ -82,7 +82,7 @@ list_to_binary([Compressed|Last])</pre>
<tag><c>stream_error</c></tag>
<item>Inconsistent stream state.</item>
<tag><c>{need_dictionary,Adler32}</c></tag>
- <item>See <seealso marker="#inflate/2"><c>inflate/2</c></seealso>.
+ <item>See <seemfa marker="#inflate/2"><c>inflate/2</c></seemfa>.
</item>
</taglist>
</description>
@@ -91,7 +91,7 @@ list_to_binary([Compressed|Last])</pre>
<datatype>
<name name="zstream"/>
<desc>
- <p>A zlib stream, see <seealso marker="#open/0"><c>open/0</c></seealso>.
+ <p>A zlib stream, see <seemfa marker="#open/0"><c>open/0</c></seemfa>.
</p>
</desc>
</datatype>
@@ -126,8 +126,8 @@ list_to_binary([Compressed|Last])</pre>
<p>Calculates the Adler-32 checksum for <c><anno>Data</anno></c>.</p>
<warning>
<p>This function is deprecated and will be removed in a future
- release. Use <seealso marker="erts:erlang#adler32/1">
- <c>erlang:adler32/1</c></seealso> instead.</p>
+ release. Use <seemfa marker="erts:erlang#adler32/1">
+ <c>erlang:adler32/1</c></seemfa> instead.</p>
</warning>
</desc>
</func>
@@ -146,8 +146,8 @@ Crc = lists:foldl(fun(Data,Crc0) ->
end, zlib:adler32(Z,&lt;&lt; &gt;&gt;), Datas)</pre>
<warning>
<p>This function is deprecated and will be removed in a future
- release. Use <seealso marker="erts:erlang#adler32/2">
- <c>erlang:adler32/2</c></seealso> instead.</p>
+ release. Use <seemfa marker="erts:erlang#adler32/2">
+ <c>erlang:adler32/2</c></seemfa> instead.</p>
</warning>
</desc>
</func>
@@ -165,8 +165,8 @@ Crc = lists:foldl(fun(Data,Crc0) ->
<c><anno>Adler2</anno></c>, and <c><anno>Size2</anno></c>.</p>
<warning>
<p>This function is deprecated and will be removed in a future
- release. Use <seealso marker="erts:erlang#adler32_combine/3">
- <c>erlang:adler32_combine/3</c></seealso> instead.</p>
+ release. Use <seemfa marker="erts:erlang#adler32_combine/3">
+ <c>erlang:adler32_combine/3</c></seemfa> instead.</p>
</warning>
</desc>
</func>
@@ -194,8 +194,8 @@ Crc = lists:foldl(fun(Data,Crc0) ->
<p>Gets the current calculated CRC checksum.</p>
<warning>
<p>This function is deprecated and will be removed in a future
- release. Use <seealso marker="erts:erlang#crc32/1">
- <c>erlang:crc32/1</c></seealso> on the uncompressed data
+ release. Use <seemfa marker="erts:erlang#crc32/1">
+ <c>erlang:crc32/1</c></seemfa> on the uncompressed data
instead.</p>
</warning>
</desc>
@@ -208,8 +208,8 @@ Crc = lists:foldl(fun(Data,Crc0) ->
<p>Calculates the CRC checksum for <c><anno>Data</anno></c>.</p>
<warning>
<p>This function is deprecated and will be removed in a future
- release. Use <seealso marker="erts:erlang#crc32/1">
- <c>erlang:crc32/1</c></seealso> instead.</p>
+ release. Use <seemfa marker="erts:erlang#crc32/1">
+ <c>erlang:crc32/1</c></seemfa> instead.</p>
</warning>
</desc>
</func>
@@ -228,8 +228,8 @@ Crc = lists:foldl(fun(Data,Crc0) ->
end, zlib:crc32(Z,&lt;&lt; &gt;&gt;), Datas)</pre>
<warning>
<p>This function is deprecated and will be removed in a future
- release. Use <seealso marker="erts:erlang#crc32/2">
- <c>erlang:crc32/2</c></seealso> instead.</p>
+ release. Use <seemfa marker="erts:erlang#crc32/2">
+ <c>erlang:crc32/2</c></seemfa> instead.</p>
</warning>
</desc>
</func>
@@ -247,8 +247,8 @@ Crc = lists:foldl(fun(Data,Crc0) ->
<c><anno>CRC2</anno></c>, and <c><anno>Size2</anno></c>.</p>
<warning>
<p>This function is deprecated and will be removed in a future
- release. Use <seealso marker="erts:erlang#crc32_combine/3">
- <c>erlang:crc32_combine/3</c></seealso> instead.</p>
+ release. Use <seemfa marker="erts:erlang#crc32_combine/3">
+ <c>erlang:crc32_combine/3</c></seemfa> instead.</p>
</warning>
</desc>
</func>
@@ -284,10 +284,10 @@ Crc = lists:foldl(fun(Data,Crc0) ->
pending input is processed, pending output is flushed, and
<c>deflate/3</c> returns. Afterwards the only possible operations
on the stream are
- <seealso marker="#deflateReset/1"><c>deflateReset/1</c></seealso> or
- <seealso marker="#deflateEnd/1"><c>deflateEnd/1</c></seealso>.</p>
+ <seemfa marker="#deflateReset/1"><c>deflateReset/1</c></seemfa> or
+ <seemfa marker="#deflateEnd/1"><c>deflateEnd/1</c></seemfa>.</p>
<p><c><anno>Flush</anno></c> can be set to <c>finish</c> immediately
- after <seealso marker="#deflateInit/1"><c>deflateInit</c></seealso>
+ after <seemfa marker="#deflateInit/1"><c>deflateInit</c></seemfa>
if all compression is to be done in one step.</p>
<p>Example:</p>
<pre>
@@ -305,7 +305,7 @@ list_to_binary([B1,B2])</pre>
<desc>
<p>Ends the deflate session and cleans all data used. Notice that this
function throws a <c>data_error</c> exception if the last call to
- <seealso marker="#deflate/3"><c>deflate/3</c></seealso>
+ <seemfa marker="#deflate/3"><c>deflate/3</c></seemfa>
was not called with <c>Flush</c> set to <c>finish</c>.</p>
</desc>
</func>
@@ -358,8 +358,8 @@ list_to_binary([B1,B2])</pre>
<p>The base two logarithm of the window size (the size of the
history buffer). It is to be in the range 8 through 15. Larger
values result in better compression at the expense of memory
- usage. Defaults to 15 if <seealso marker="#deflateInit/2">
- <c>deflateInit/2</c></seealso> is used. A negative
+ usage. Defaults to 15 if <seemfa marker="#deflateInit/2">
+ <c>deflateInit/2</c></seemfa> is used. A negative
<c><anno>WindowBits</anno></c> value suppresses the zlib header
(and checksum) from the stream. Notice that the zlib source
mentions this only as a undocumented feature.</p>
@@ -416,7 +416,7 @@ list_to_binary([B1,B2])</pre>
<p>Dynamically updates the compression level and compression
strategy. The interpretation of <c><anno>Level</anno></c> and
<c><anno>Strategy</anno></c> is as in
- <seealso marker="#deflateInit/6"><c>deflateInit/6</c></seealso>.
+ <seemfa marker="#deflateInit/6"><c>deflateInit/6</c></seemfa>.
This can be
used to switch between compression and straight copy of the
input data, or to switch to a different kind of input data
@@ -424,7 +424,7 @@ list_to_binary([B1,B2])</pre>
changed, the input available so far is compressed with the
old level (and can be flushed); the new level takes
effect only at the next call of
- <seealso marker="#deflate/3"><c>deflate/3</c></seealso>.</p>
+ <seemfa marker="#deflate/3"><c>deflate/3</c></seemfa>.</p>
<p>Before the call of <c>deflateParams</c>, the stream state must be
set as for a call of <c>deflate/3</c>, as the currently available
input may have to be compressed and flushed.</p>
@@ -436,9 +436,9 @@ list_to_binary([B1,B2])</pre>
<fsummary>Reset the deflate session.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#deflateEnd/1"><c>deflateEnd/1</c></seealso>
+ <seemfa marker="#deflateEnd/1"><c>deflateEnd/1</c></seemfa>
followed by
- <seealso marker="#deflateInit/1"><c>deflateInit/1,2,6</c></seealso>,
+ <seemfa marker="#deflateInit/1"><c>deflateInit/1,2,6</c></seemfa>,
but does not free and reallocate all the internal compression state.
The stream keeps the same compression level and any other
attributes.</p>
@@ -452,13 +452,13 @@ list_to_binary([B1,B2])</pre>
<p>Initializes the compression dictionary from the specified byte
sequence without producing any compressed output.</p>
<p>This function must be called immediately after
- <seealso marker="#deflateInit/1"><c>deflateInit/1,2,6</c></seealso> or
- <seealso marker="#deflateReset/1"><c>deflateReset/1</c></seealso>,
+ <seemfa marker="#deflateInit/1"><c>deflateInit/1,2,6</c></seemfa> or
+ <seemfa marker="#deflateReset/1"><c>deflateReset/1</c></seemfa>,
before any call of
- <seealso marker="#deflate/3"><c>deflate/3</c></seealso>.</p>
+ <seemfa marker="#deflate/3"><c>deflate/3</c></seemfa>.</p>
<p>The compressor and decompressor must use the same dictionary (see
- <seealso marker="#inflateSetDictionary/2">
- <c>inflateSetDictionary/2</c></seealso>).</p>
+ <seemfa marker="#inflateSetDictionary/2">
+ <c>inflateSetDictionary/2</c></seemfa>).</p>
<p>The Adler checksum of the dictionary is returned.</p>
</desc>
</func>
@@ -496,7 +496,7 @@ list_to_binary([B1,B2])</pre>
<fsummary>Decompress data.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#inflate/3"><c>inflate(Z, Data, [])</c></seealso>
+ <seemfa marker="#inflate/3"><c>inflate(Z, Data, [])</c></seemfa>
</p>
</desc>
</func>
@@ -512,8 +512,8 @@ list_to_binary([B1,B2])</pre>
function should throw an exception when a preset dictionary is
required for decompression. When set to false, a
<c>need_dictionary</c> tuple will be returned instead. See
- <seealso marker="#inflateSetDictionary/2">
- <c>inflateSetDictionary/2</c></seealso> for details.</p>
+ <seemfa marker="#inflateSetDictionary/2">
+ <c>inflateSetDictionary/2</c></seemfa> for details.</p>
<warning>
<p>This option defaults to <c>true</c> for backwards compatibility
but we intend to remove the exception behavior in a future
@@ -529,11 +529,11 @@ list_to_binary([B1,B2])</pre>
<desc>
<warning>
<p>This function is deprecated and will be removed in a future
- release. Use <seealso marker="#safeInflate/2"><c>safeInflate/2</c>
- </seealso> instead.</p>
+ release. Use <seemfa marker="#safeInflate/2"><c>safeInflate/2</c>
+ </seemfa> instead.</p>
</warning>
<p>Reads the next chunk of uncompressed data, initialized by
- <seealso marker="#inflateChunk/2"><c>inflateChunk/2</c></seealso>.</p>
+ <seemfa marker="#inflateChunk/2"><c>inflateChunk/2</c></seemfa>.</p>
<p>This function is to be repeatedly called, while it returns
<c>{more, Decompressed}</c>.</p>
</desc>
@@ -545,25 +545,25 @@ list_to_binary([B1,B2])</pre>
<desc>
<warning>
<p>This function is deprecated and will be removed in a future
- release. Use <seealso marker="#safeInflate/2"><c>safeInflate/2</c>
- </seealso> instead.</p>
+ release. Use <seemfa marker="#safeInflate/2"><c>safeInflate/2</c>
+ </seemfa> instead.</p>
</warning>
- <p>Like <seealso marker="#inflate/2"><c>inflate/2</c></seealso>,
+ <p>Like <seemfa marker="#inflate/2"><c>inflate/2</c></seemfa>,
but decompresses no more data than will fit in the buffer configured
- through <seealso marker="#setBufSize/2"><c>setBufSize/2</c>
- </seealso>. Is is useful when decompressing a stream with a high
+ through <seemfa marker="#setBufSize/2"><c>setBufSize/2</c>
+ </seemfa>. Is is useful when decompressing a stream with a high
compression ratio, such that a small amount of compressed input can
expand up to 1000 times.</p>
<p>This function returns <c>{more, Decompressed}</c>, when there is
more output available, and
- <seealso marker="#inflateChunk/1"><c>inflateChunk/1</c></seealso>
+ <seemfa marker="#inflateChunk/1"><c>inflateChunk/1</c></seemfa>
is to be used to read it.</p>
<p>This function can introduce some output latency (reading input
without producing any output).</p>
<p>An exception will be thrown if a preset dictionary is required for
further decompression. See
- <seealso marker="#inflateSetDictionary/2">
- <c>inflateSetDictionary/2</c></seealso> for details.</p>
+ <seemfa marker="#inflateSetDictionary/2">
+ <c>inflateSetDictionary/2</c></seemfa> for details.</p>
<p>Example:</p>
<pre>
walk(Compressed, Handler) ->
@@ -600,8 +600,8 @@ loop(Z, Handler, Uncompressed) ->
<desc>
<p>Returns the decompression dictionary currently in use
by the stream. This function must be called between
- <seealso marker="#inflateInit/1"><c>inflateInit/1,2</c></seealso>
- and <seealso marker="#inflateEnd/1"><c>inflateEnd</c></seealso>.</p>
+ <seemfa marker="#inflateInit/1"><c>inflateInit/1,2</c></seemfa>
+ and <seemfa marker="#inflateEnd/1"><c>inflateEnd</c></seemfa>.</p>
<p>Only supported if ERTS was compiled with zlib >= 1.2.8.</p>
</desc>
</func>
@@ -622,10 +622,10 @@ loop(Z, Handler, Uncompressed) ->
<p><c><anno>WindowBits</anno></c> is the base two logarithm
of the maximum window size (the size of the history buffer).
It is to be in the range 8 through 15. Default to 15 if
- <seealso marker="#inflateInit/1"><c>inflateInit/1</c></seealso>
+ <seemfa marker="#inflateInit/1"><c>inflateInit/1</c></seemfa>
is used.</p>
<p>If a compressed stream with a larger window size is specified as
- input, <seealso marker="#inflate/2"><c>inflate/2</c></seealso>
+ input, <seemfa marker="#inflate/2"><c>inflate/2</c></seemfa>
throws the <c>data_error</c> exception.</p>
<p>A negative <c><anno>WindowBits</anno></c> value makes zlib
ignore the zlib header (and checksum) from the stream. Notice that
@@ -638,9 +638,9 @@ loop(Z, Handler, Uncompressed) ->
<fsummary>>Reset the inflate session.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#inflateEnd/1"><c>inflateEnd/1</c></seealso>
+ <seemfa marker="#inflateEnd/1"><c>inflateEnd/1</c></seemfa>
followed by
- <seealso marker="#inflateInit/1"><c>inflateInit/1</c></seealso>,
+ <seemfa marker="#inflateInit/1"><c>inflateInit/1</c></seemfa>,
but does not free and reallocate all the internal decompression state.
The stream will keep attributes that could have been set by
<c>inflateInit/1,2</c>.</p>
@@ -654,15 +654,15 @@ loop(Z, Handler, Uncompressed) ->
<p>Initializes the decompression dictionary from the specified
uncompressed byte sequence. This function must be called as a
response to an inflate operation (eg.
- <seealso marker="#safeInflate/2"><c>safeInflate/2</c></seealso>)
+ <seemfa marker="#safeInflate/2"><c>safeInflate/2</c></seemfa>)
returning <c>{need_dictionary,Adler,Output}</c> or in the case of
deprecated functions, throwing an
<c>{'EXIT',{{need_dictionary,Adler},_StackTrace}}</c> exception.</p>
<p>The dictionary chosen by the compressor can be determined from the
Adler value returned or thrown by the call to the inflate function.
The compressor and decompressor must use the same dictionary (See
- <seealso marker="#deflateSetDictionary/2">
- <c>deflateSetDictionary/2</c></seealso>).</p>
+ <seemfa marker="#deflateSetDictionary/2">
+ <c>deflateSetDictionary/2</c></seemfa>).</p>
<p>After setting the dictionary the inflate operation should be
retried without new input.</p>
<p>Example:</p>
@@ -699,7 +699,7 @@ new_unpack(Z, Compressed, Dict) ->
<name name="safeInflate" arity="2" since="OTP 20.1"/>
<fsummary>Decompress data with limited output size.</fsummary>
<desc>
- <p>Like <seealso marker="#inflate/2"><c>inflate/2</c></seealso>,
+ <p>Like <seemfa marker="#inflate/2"><c>inflate/2</c></seemfa>,
but returns once it has expanded beyond a small
implementation-defined threshold. It's useful when decompressing
untrusted input which could have been maliciously crafted to expand
@@ -713,8 +713,8 @@ new_unpack(Z, Compressed, Dict) ->
input without producing any output).</p>
<p>If a preset dictionary is required for further decompression, this
function returns a <c>need_dictionary</c> tuple. See
- <seealso marker="#inflateSetDictionary/2">
- <c>inflateSetDictionary/2</c></seealso>) for details.</p>
+ <seemfa marker="#inflateSetDictionary/2">
+ <c>inflateSetDictionary/2</c></seemfa>) for details.</p>
<p>Example:</p>
<pre>
walk(Compressed, Handler) ->
diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in
index fb56eadf39..98dd6ea669 100644
--- a/erts/emulator/Makefile.in
+++ b/erts/emulator/Makefile.in
@@ -32,6 +32,7 @@ USE_VM_PROBES=@USE_VM_PROBES@
FPE=@FPE@
LIBS = @LIBS@
Z_LIB=@Z_LIB@
+CROSS_COMPILING = @CROSS_COMPILING@
NO_INLINE_FUNCTIONS=false
OPCODE_TABLES = $(ERL_TOP)/lib/compiler/src/genop.tab \
beam/ops.tab \
@@ -136,6 +137,14 @@ TYPE_FLAGS = $(DEBUG_CFLAGS) -DVALGRIND -DNO_JUMP_TABLE
ENABLE_ALLOC_TYPE_VARS += valgrind
else
+ifeq ($(TYPE),asan)
+PURIFY =
+TYPEMARKER = .asan
+TYPE_FLAGS = $(DEBUG_CFLAGS) -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer -DADDRESS_SANITIZER
+LDFLAGS += -fsanitize=address
+ENABLE_ALLOC_TYPE_VARS += asan
+else
+
ifeq ($(TYPE),gprof)
PURIFY =
TYPEMARKER = .gprof
@@ -180,6 +189,7 @@ endif
endif
endif
endif
+endif
LIBS += $(TYPE_LIBS)
@@ -269,15 +279,16 @@ STRIP = strip
PERL = @PERL@
MKDIR = @MKDIR@
-USING_MINGW=@MIXED_CYGWIN_MINGW@
-MIXED_MSYS=@MIXED_MSYS@
+USING_MINGW=@MIXED_MINGW@
ifeq ($(TARGET),win32)
LIB_PREFIX=
LIB_SUFFIX=.lib
+EXE_SUFFIX=.exe
else
LIB_PREFIX=lib
LIB_SUFFIX=.a
+EXE_SUFFIX=
endif
ifeq (@EMU_LOCK_CHECKING@,yes)
@@ -308,9 +319,6 @@ ifeq ($(TARGET), win32)
GEN_OPT_FLGS = $(OPT_LEVEL)
UNROLL_FLG =
RC=rc.sh
-ifeq ($(MIXED_MSYS), yes)
-MAKE_PRELOAD_EXTRA = -msys
-endif
ifeq ($(USING_MINGW), yes)
RES_EXT = @OBJEXT@
MAKE_PRELOAD_EXTRA += " -windres"
@@ -599,7 +607,6 @@ endif
$(TTF_DIR)/erl_bif_table.c \
$(TTF_DIR)/erl_bif_table.h \
-$(TTF_DIR)/erl_bif_wrap.c \
$(TTF_DIR)/erl_bif_list.h \
$(TTF_DIR)/erl_atom_table.c \
$(TTF_DIR)/erl_atom_table.h \
@@ -626,7 +633,35 @@ $(TTF_DIR)/driver_tab.c: Makefile.in utils/make_driver_tab
$(gen_verbose)LANG=C $(PERL) utils/make_driver_tab -o $@ -nifs $(NIF_OBJS) $(STATIC_NIF_LIBS) -drivers $(DRV_OBJS) $(STATIC_DRIVER_LIBS)
GENERATE += $(TTF_DIR)/driver_tab.c
+DB_INSERT_LIST_YCF_FLAGS=-yield \
+ -static_aux_funs \
+ -only_yielding_funs \
+ -f ets_insert_2_list_check \
+ -f ets_insert_new_2_list_has_member \
+ -f ets_insert_2_list_from_p_heap \
+ -f ets_insert_2_list_destroy_copied_dbterms \
+ -f ets_insert_2_list_copy_term_list \
+ -f ets_insert_new_2_dbterm_list_has_member \
+ -f ets_insert_2_list_insert_db_term_list \
+ -f ets_insert_2_list \
+ -fnoauto ets_insert_2_list_lock_tbl
+
+ifeq ($(CROSS_COMPILING),no)
+$(TARGET)/erl_db_insert_list.ycf.h: beam/erl_db.c $(ERTS_LIB)
+ $(gen_verbose)$(ERL_TOP)/erts/lib_src/yielding_c_fun/bin/$(TARGET)/yielding_c_fun$(EXE_SUFFIX) \
+ $(DB_INSERT_LIST_YCF_FLAGS) \
+ -output_file_name $@ $<
+else
+## We cannot build a 'yielding_c_fun' that can be executed since we are
+## cross compiling. A working bootstrap system of valid version *should*
+## be in the path. Find 'yielding_c_fun' in the bootstrap system...
+$(TARGET)/erl_db_insert_list.ycf.h: beam/erl_db.c
+ $(gen_verbose)CROSS_YCF=`utils/find_cross_ycf` && \
+ "$$CROSS_YCF" $(DB_INSERT_LIST_YCF_FLAGS) \
+ -output_file_name $@ $<
+endif
+GENERATE += $(TARGET)/erl_db_insert_list.ycf.h
# Preloaded code.
#
@@ -636,7 +671,7 @@ GENERATE += $(TTF_DIR)/driver_tab.c
ifeq ($(USE_ESOCK), yes)
ESOCK_PRELOAD_BEAM = \
$(ERL_TOP)/erts/preloaded/ebin/socket_registry.beam \
- $(ERL_TOP)/erts/preloaded/ebin/socket.beam \
+ $(ERL_TOP)/erts/preloaded/ebin/prim_socket.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_net.beam
else
ESOCK_PRELOAD_BEAM =
@@ -849,7 +884,7 @@ ifeq ($(USE_ESOCK), yes)
# WE ARE USING ESOCK
ESOCK_NIF_OBJS = \
- $(OBJDIR)/socket_nif.o \
+ $(OBJDIR)/prim_socket_nif.o \
$(OBJDIR)/prim_net_nif.o
ifneq ($(TARGET), win32)
@@ -886,7 +921,6 @@ RUN_OBJS += \
$(OBJDIR)/erl_bif_persistent.o \
$(OBJDIR)/erl_bif_atomics.o $(OBJDIR)/erl_bif_counters.o \
$(OBJDIR)/erl_bif_trace.o $(OBJDIR)/erl_bif_unique.o \
- $(OBJDIR)/erl_bif_wrap.o $(OBJDIR)/erl_nfunc_sched.o \
$(OBJDIR)/erl_guard_bifs.o $(OBJDIR)/erl_dirty_bif_wrap.o \
$(OBJDIR)/erl_trace.o $(OBJDIR)/copy.o \
$(OBJDIR)/utils.o $(OBJDIR)/bif.o \
@@ -922,7 +956,9 @@ RUN_OBJS += \
$(OBJDIR)/erl_ptab.o $(OBJDIR)/erl_map.o \
$(OBJDIR)/erl_msacc.o $(OBJDIR)/erl_lock_flags.o \
$(OBJDIR)/erl_io_queue.o $(OBJDIR)/erl_db_catree.o \
- $(ESOCK_RUN_OBJS) $(OBJDIR)/erl_flxctr.o
+ $(ESOCK_RUN_OBJS) $(OBJDIR)/erl_flxctr.o \
+ $(OBJDIR)/erl_nfunc_sched.o \
+ $(OBJDIR)/erl_global_literals.o
LTTNG_OBJS = $(OBJDIR)/erlang_lttng.o
@@ -1196,8 +1232,7 @@ ifeq ($(TARGET), win32)
# These are *currently* only needed for non-win32,
# since the nif-functions for socket and net are basically
# stubbed with badarg in the win32 case.
-NIF_SOCKET_UTILS_SRC=$(filter-out nifs/common/socket_nif.c, $(wildcard nifs/common/socket_*.c))
-NIF_COMMON_SRC=$(filter-out $(NIF_SOCKET_UTILS_SRC), $(wildcard nifs/common/*.c))
+NIF_COMMON_SRC=$(filter-out $(wildcard nifs/common/socket_*.c), $(wildcard nifs/common/*.c))
else
NIF_COMMON_SRC=$(wildcard nifs/common/*.c)
endif
@@ -1218,7 +1253,7 @@ DEP_FLAGS=-MM $(subst -O2,,$(CFLAGS)) $(INCLUDES) -I../etc/win32 \
-Idrivers/common -Idrivers/$(ERLANG_OSTYPE) \
-Inifs/common -Inifs/$(ERLANG_OSTYPE)
-# ifeq (@MIXED_CYGWIN_VC@,yes)
+# ifeq (@MIXED_VC@,yes)
# VC++ used for compiling. If __GNUC__ is defined we will include
# other headers then when compiling which will result in faulty
# dependencies.
@@ -1285,6 +1320,7 @@ endif
$(V_at)cd $(ERTS_LIB_DIR) && $(MAKE) depend
endif
+ifneq ($(ERTS_SKIP_DEPEND),true)
ifneq ($(MAKECMDGOALS),clean)
ifneq ($(MAKECMDGOALS),generate)
ifndef VOID_EMULATOR
@@ -1292,3 +1328,6 @@ ifndef VOID_EMULATOR
endif
endif
endif
+endif
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/erts/emulator/asan/asan_logs_to_html b/erts/emulator/asan/asan_logs_to_html
new file mode 100755
index 0000000000..14c9b7fcde
--- /dev/null
+++ b/erts/emulator/asan/asan_logs_to_html
@@ -0,0 +1,453 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+%% Parse address sanitizer log files generated from test runs with
+%% with environment variables ASAN_LOG_DIR and TS_RUN_EMU=asan set.
+
+%% Repeated leak reports are ignored and additional leaks of same type
+%% as seen before are identified as such.
+
+-mode(compile).
+
+main([]) ->
+ help();
+main(["--help"]) ->
+ help();
+main([OutDir]) ->
+ case os:getenv("ASAN_LOG_DIR") of
+ false ->
+ io:format(standard_error,
+ "\nMissing asan log directory argument and environment\n"
+ "variable ASAN_LOG_DIR is not set.\n\n",[]),
+ help();
+ InDir ->
+ run(OutDir, InDir)
+ end;
+main([OutDir, InDir]) ->
+ run(OutDir, InDir).
+
+
+help() ->
+ io:format("\nSyntax: asan_log_to_html OutDir [InDir]\n"
+ "\nParses all address-sanetizer log files in InDir\n"
+ "and generates a summary file OutDir/asan_summary.html.\n"
+ "Environment variable ASAN_LOG_DIR is used if InDir\n"
+ "is not specified\n\n", []).
+
+-record(logacc, {srcfile, % full path of current log file
+ did_output = false, % output contribution from srcfile
+ obuf = [], % output buffer
+ app = none, % current application
+ app_err = 0, % nr of reports from application
+ tc_err = 0, % nr of reports from srcfile (test case)
+ app_stat_bytes = 0, % total leaked bytes from app
+ app_stat_blocks = 0, % total leaked blocks from app
+ app_stat_errors = 0}). % total errors from app
+
+run(OutDir, InDir) ->
+ StartTime = erlang:monotonic_time(millisecond),
+
+ {ok, InFilesUS} = file:list_dir(InDir),
+ InFiles = lists:sort(InFilesUS),
+
+ OutFile = filename:join(OutDir, "asan_summary.html"),
+ {ok, Out} = file:open(OutFile, [write]),
+
+ ok = file:write(Out, <<"<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head><title>Address Sanitizer</title>\n">>),
+ ok = file:write(Out, style_block()),
+ ok = file:write(Out, <<"</head><body>\n"
+ "<h1>Address Sanitizer</h1>\n">>),
+
+ {_, _, LogAcc2} =
+ lists:foldl(fun(File, {LM, RegEx, LogAcc}) ->
+ analyze_log_file(Out, filename:join(InDir,File),
+ {LM, RegEx, LogAcc})
+ end,
+ {#{}, none, #logacc{}},
+ InFiles),
+
+ LogAcc3 = app_end(Out, LogAcc2),
+ try_delete_srcfile(LogAcc3),
+
+ Time = calendar:system_time_to_rfc3339(erlang:system_time(second),
+ [{time_designator, 32}]),
+ %%{_, _, ThisFile} = code:get_object_code(?MODULE),
+ ThisFile = escript:script_name(),
+ User = string:trim(os:cmd("whoami")),
+ {ok, Host} = inet:gethostname(),
+ Seconds = (erlang:monotonic_time(millisecond) - StartTime) / 1000,
+ ok = io:format(Out, "\n<hr><p><small>This page was generated ~s\n"
+ " by <tt>~s</tt>\n"
+ " run by ~s@~s in ~.1f seconds.</small></p>\n",
+ [Time, ThisFile, User, Host, Seconds]),
+
+ ok = file:write(Out, script_block()),
+ ok = file:write(Out, <<"</body>\n</html>\n">>),
+ ok = file:close(Out),
+ io:format("Generated file ~s\n", [OutFile]),
+ ok.
+
+analyze_log_file(Out, SrcFile, {LeakMap0, RegEx0, LogAcc0}=Acc) ->
+
+ #logacc{app=PrevApp} = LogAcc0,
+
+ case filelib:is_regular(SrcFile) of
+ false ->
+ Acc;
+ true ->
+ FileName = filename:basename(SrcFile),
+ %%io:format("analyze ~s\n", [FileName]),
+
+ %% Is it a new application?
+ LogAcc2 = case string:lexemes(FileName, "-") of
+ [_Exe, PrevApp | _] ->
+ try_delete_srcfile(LogAcc0),
+ LogAcc0#logacc{srcfile=SrcFile,
+ tc_err=0,
+ did_output=false};
+ [_Exe, NewApp | _] ->
+ LogAcc1 = app_end(Out, LogAcc0),
+ try_delete_srcfile(LogAcc1),
+ LogAcc1#logacc{srcfile=SrcFile,
+ obuf=[],
+ app=NewApp,
+ app_err=0,
+ tc_err=0,
+ did_output=false,
+ app_stat_bytes=0,
+ app_stat_blocks=0,
+ app_stat_errors=0}
+ end,
+
+ case LogAcc2#logacc.app_err of
+ truncated ->
+ {LeakMap0, RegEx0, LogAcc2};
+ _ ->
+ {ok, Bin} = file:read_file(SrcFile),
+ match_loop(Out, Bin, RegEx0, LogAcc2, 0, [], LeakMap0)
+ end
+ end.
+
+-define(APP_ERR_LIMIT, 200).
+
+match_loop(Out, _, RegEx, #logacc{app_err=AppErr}=LogAcc0, _, _, LM)
+ when AppErr >= ?APP_ERR_LIMIT ->
+
+ Txt = [io_format("<h2>WARNING!!! Log truncated for application ~p,"
+ " more than ~p errors found.</h2>\n",
+ [LogAcc0#logacc.app, ?APP_ERR_LIMIT])],
+ LogAcc1 = log_error(Out, LogAcc0, Txt),
+ {LM, RegEx, LogAcc1#logacc{app_err=truncated}};
+
+match_loop(Out, Bin, RegEx0, LogAcc0, PrevEnd, Unmatched0, LM0) ->
+ {Match, RegEx1} =
+ run_regex(Bin, RegEx0,
+ %% LeakReport
+ "(?:(Direct|Indirect) leak of ([0-9]+) byte\\(s\\) "
+ "in ([0-9]+) object\\(s\\) allocated from:\n"
+ "((?:[ \t]*#[0-9]+.+\n)+))" % Call stack
+ "|"
+ %% ErrorReport
+ "(?:(==ERROR: AddressSanitizer:.*\n"
+ "(?:.*\n)+?)" % any lines (non-greedy)
+ "^(?:==|--))" % stop at line begining with == or --
+ "|"
+ %% Skipped
+ "(?:^[=-]+$)" % skip lines consisting only of = or -
+ "|"
+ "Objects leaked above:\n" % if LSAN_OPTIONS="report_objects=1"
+ "(?:0x.+\n)+"
+ "|"
+ "^\n", % empty lines
+ [multiline],
+ [{offset, PrevEnd}, {capture, all, index}]),
+
+
+ BP = fun(PartIx) -> binary:part(Bin, PartIx) end,
+
+ case Match of
+ [ErrorReport, {-1,0}, {-1,0}, {-1,0}, {-1,0}, Captured] ->
+ {Start,MatchLen} = ErrorReport,
+ Txt = [io_format("<pre~s>\n", [style(error)]),
+ file_write(BP(Captured)),
+ io_format("</pre>\n", [])],
+ LogAcc1 = log_error(Out, LogAcc0, Txt),
+ Unmatched1 = [BP({PrevEnd, Start-PrevEnd}) | Unmatched0],
+ End = Start + MatchLen,
+ match_loop(Out, Bin, RegEx1, app_stats(LogAcc1,0,0,1),
+ End, Unmatched1, LM0);
+
+ [LeakReport, TypeIx, BytesIx, BlocksIx, StackIx | _] ->
+ {Start, MatchLen} = LeakReport,
+ Bytes = binary_to_integer(BP(BytesIx)),
+ Blocks = binary_to_integer(BP(BlocksIx)),
+ End = Start + MatchLen,
+ Unmatched1 = [BP({PrevEnd, Start-PrevEnd})|Unmatched0],
+ TypeBin = BP(TypeIx),
+
+ %% We indentify a leak by its type (direct or indirect)
+ %% and its full call stack.
+ Key = {TypeBin, BP(StackIx)},
+ {LogAcc2, LM2} =
+ case lookup_leak(LM0, Key) of
+ undefined ->
+ %% A new leak
+ LM1 = insert_leak(LM0, Key, Bytes, Blocks),
+ Txt = [io_format("<pre~s>\n", [style(new, TypeBin)]),
+ file_write(BP(LeakReport)),
+ io_format("</pre>\n", [])],
+ LogAcc1 = log_error(Out, LogAcc0, Txt),
+ {app_stats(LogAcc1,Bytes,Blocks,0), LM1};
+
+ {Bytes, Blocks} ->
+ %% Exact same leak(s) repeated, ignore
+ {LogAcc0, LM0};
+
+ {OldBytes, OldBlocks} ->
+ %% More leaked bytes/blocks of same type&stack as before
+ LM1 = insert_leak(LM0, Key, Bytes, Blocks),
+ ByteDiff = Bytes - OldBytes,
+ BlockDiff = Blocks - OldBlocks,
+ Txt = [io_format("<pre~s>\n", [style(more, TypeBin)]),
+ io_format("More ~s leak of ~w(~w) byte(s) "
+ "in ~w(~w) object(s) allocated from:\n",
+ [TypeBin, ByteDiff, Bytes, BlockDiff, Blocks]),
+ file_write(BP(StackIx)),
+ io_format("</pre>\n", [])],
+ LogAcc1 = log_error(Out, LogAcc0, Txt),
+ {app_stats(LogAcc1, ByteDiff, BlockDiff, 0), LM1}
+ end,
+ match_loop(Out, Bin, RegEx1, LogAcc2, End, Unmatched1, LM2);
+
+ [SkipLine] ->
+ {Start, MatchLen} = SkipLine,
+ %%nomatch = binary:match(BP(SkipLine), <<"\n">>), % Assert single line
+ End = Start + MatchLen,
+ Unmatched1 = [BP({PrevEnd, Start-PrevEnd})|Unmatched0],
+ match_loop(Out, Bin, RegEx1, LogAcc0, End, Unmatched1, LM0);
+
+ nomatch ->
+ Unmatched1 = [BP({PrevEnd, byte_size(Bin)-PrevEnd}) | Unmatched0],
+
+ LogAcc1 =
+ case iolist_size(Unmatched1) > 500 of
+ true ->
+ Txt = [io_format("<h2>WARNING!!! May be unmatched error reports"
+ " in file ~s:</h2>\n<pre>~s</pre>",
+ [LogAcc0#logacc.srcfile, Unmatched1])],
+ log_error(Out, LogAcc0, Txt);
+ false ->
+ LogAcc0
+ end,
+ {LM0, RegEx1, LogAcc1}
+ end.
+
+lookup_leak(LeakMap, Key) ->
+ maps:get(Key, LeakMap, undefined).
+
+insert_leak(LeakMap, Key, Bytes, Blocks) ->
+ LeakMap#{Key => {Bytes, Blocks}}.
+
+log_error(_Out, #logacc{app_err=AppErr, tc_err=TcErr}=LogAcc, Txt0) ->
+ {DidTc, Txt1} =
+ case TcErr of
+ 0 ->
+ %% First error in test case, print test case header
+ SrcFile = LogAcc#logacc.srcfile,
+ TcFile = filename:basename(SrcFile),
+ Hdr = case string:lexemes(TcFile, "-") of
+ [_Exe, App, _Rest] ->
+ io_format("<h3>Before first test case of ~s</h3>\n",
+ [App]);
+ [_Exe, _App, "tc", Num, Mod, Rest] ->
+ [Func | _] = string:lexemes(Rest, "."),
+ io_format("<h3>Test case #~s ~s:~s</h3>\n",
+ [Num, Mod, Func]);
+ _ ->
+ io_format("<h3>Strange log file name '~s'</h3>\n",
+ [SrcFile])
+ end,
+ {true, [Hdr | Txt0]};
+ _ ->
+ {false, Txt0}
+ end,
+ LogAcc#logacc{app_err=AppErr+1, tc_err=TcErr+1,
+ obuf = [Txt1 | LogAcc#logacc.obuf],
+ did_output = (LogAcc#logacc.did_output or DidTc)}.
+
+app_stats(#logacc{}=LogAcc, Bytes, Blocks, Errors) ->
+ LogAcc#logacc{app_stat_bytes = LogAcc#logacc.app_stat_bytes + Bytes,
+ app_stat_blocks = LogAcc#logacc.app_stat_blocks + Blocks,
+ app_stat_errors = LogAcc#logacc.app_stat_errors + Errors}.
+
+
+app_end(Out, LogAcc) ->
+ case LogAcc of
+ #logacc{app=none} ->
+ LogAcc;
+ #logacc{app_err=0} ->
+ ok = io:format(Out, "<button class=\"app_ok\" disabled>~s</button>\n",
+ [LogAcc#logacc.app]),
+ LogAcc#logacc{did_output = true};
+ #logacc{} ->
+ %% Print red clickable app button with stats
+ %% and all the buffered logs.
+ ok = io:format(Out, "<button type=\"button\" class=\"app_err\">~s"
+ "<span class=\"stats\">Leaks: ~p bytes in ~p blocks</span>",
+ [LogAcc#logacc.app,
+ LogAcc#logacc.app_stat_bytes,
+ LogAcc#logacc.app_stat_blocks]),
+ case LogAcc#logacc.app_stat_errors of
+ 0 -> ignore;
+ _ ->
+ ok = io:format(Out, "<span class=\"stats\">Errors: ~p</span>",
+ [LogAcc#logacc.app_stat_errors])
+ end,
+ ok = io:format(Out, "</button>\n"
+ "<div class=\"content\">", []),
+
+ flush_obuf(Out, LogAcc#logacc.obuf),
+
+ ok = io:format(Out, "<button type=\"button\" "
+ "class=\"app_err_end\">"
+ "end of ~s</button>\n", [LogAcc#logacc.app]),
+ ok = io:format(Out, "</div>", []),
+ LogAcc
+ end.
+
+flush_obuf(Out, Obuf) ->
+ flush_obuf_rev(Out, lists:reverse(Obuf)).
+
+flush_obuf_rev(_Out, []) -> ok;
+flush_obuf_rev(Out, [Txt | T]) ->
+ [OutFun(Out) || OutFun <- Txt],
+ flush_obuf_rev(Out, T).
+
+io_format(Frmt, List) ->
+ fun(Out) -> io:format(Out, Frmt, List) end.
+
+file_write(Bin) ->
+ fun(Out) -> file:write(Out, Bin) end.
+
+style(error) ->
+ " style=\"background-color:Tomato;\"".
+
+style(new, <<"Direct">>) ->
+ " style=\"background-color:orange;\"";
+style(new, <<"Indirect">>) ->
+ "";
+style(more, _) ->
+ " style=\"background-color:yellow;\"".
+
+
+run_regex(Bin, none, RegExString, CompileOpts, RunOpts) ->
+ {ok, RegEx} = re:compile(RegExString, CompileOpts),
+ run_regex(Bin, RegEx, none, none, RunOpts);
+run_regex(Bin, RegEx, _, _, RunOpts) ->
+ case re:run(Bin, RegEx, RunOpts) of
+ nomatch ->
+ {nomatch, RegEx};
+ {match, Match} ->
+ {Match, RegEx}
+ end.
+
+try_delete_srcfile(LogAcc) ->
+ case LogAcc of
+ #logacc{srcfile=undefined} ->
+ ignore;
+ #logacc{did_output=false} ->
+ %% This file did not contribute any output.
+ %% Optimize future script invokations by removing it.
+ delete_file(LogAcc#logacc.srcfile);
+ _ ->
+ keep
+ end.
+
+delete_file(File) ->
+ case filelib:is_regular(File) of
+ true ->
+ io:format("Delete file ~p\n", [File]),
+ Dir = filename:dirname(File),
+ Name = filename:basename(File),
+ Trash = filename:join([Dir, "DELETED", Name]),
+ ok = filelib:ensure_dir(Trash),
+ ok = file:rename(File, Trash);
+ false ->
+ ignore
+ end.
+
+style_block() ->
+ <<"<style>
+
+.app_err, .app_err_end, .app_ok {
+ color: white;
+ padding: 10px;
+ /*border: none;*/
+ text-align: left;
+ /*outline: none;*/
+ font-size: 15px;
+}
+
+.app_err {
+ width: 100%;
+ background-color: #D11;
+ cursor: pointer;
+}
+.app_err:hover {
+ background-color: #F11;
+}
+.app_err_end {
+ background-color: #D11;
+ cursor: pointer;
+}
+.app_err_end:hover {
+ background-color: #F11;
+}
+
+.app_ok {
+ width: 100%;
+ background-color: #292;
+}
+
+.stats {
+ font-style: italic;
+ margin-left: 50px;
+}
+
+.content {
+ padding: 0 18px;
+ display: none;
+ overflow: hidden;
+ background-color: #f1f1f1;
+}
+</style>
+">>.
+
+script_block() ->
+ <<"<script>
+var app_err = document.getElementsByClassName(\"app_err\");
+var i;
+
+for (i = 0; i < app_err.length; i++) {
+ app_err[i].addEventListener(\"click\", function() {
+ var content = this.nextElementSibling;
+ if (content.style.display === \"block\") {
+ content.style.display = \"none\";
+ } else {
+ content.style.display = \"block\";
+ }
+ });
+}
+
+var app_err_end = document.getElementsByClassName(\"app_err_end\");
+for (i = 0; i < app_err_end.length; i++) {
+ app_err_end[i].addEventListener(\"click\", function() {
+ var content = this.parentElement;
+ content.style.display = \"none\";
+ });
+}
+
+</script>
+">>.
diff --git a/erts/emulator/asan/suppress b/erts/emulator/asan/suppress
new file mode 100644
index 0000000000..5625938f37
--- /dev/null
+++ b/erts/emulator/asan/suppress
@@ -0,0 +1,18 @@
+leak:erts_alloc_permanent_cache_aligned
+
+# Harmless leak of ErtsThrPrgrData from async threads in exiting emulator
+leak:erts_thr_progress_register_unmanaged_thread
+
+# Block passed to sigaltstack()
+leak:sys_thread_init_signal_stack
+
+#Copied from valgrind/suppress.standard:
+#Crypto internal... loading gives expected errors when curves are tried.
+#But including <openssl/err.h> and removing them triggers compiler errors on Windows
+#fun:valid_curve
+#fun:init_curves
+leak:init_curve_types
+#fun:init_algorithms_types
+#fun:initialize
+#fun:load
+#fun:erts_load_nif
diff --git a/erts/emulator/beam/arith_instrs.tab b/erts/emulator/beam/arith_instrs.tab
index 7882d83b9c..406c1bd21b 100644
--- a/erts/emulator/beam/arith_instrs.tab
+++ b/erts/emulator/beam/arith_instrs.tab
@@ -217,6 +217,11 @@ i_int_div(Fail, Op1, Op2, Dst) {
$BIF_ERROR_ARITY_2($Fail, BIF_intdiv_2, op1, op2);
} else if (ERTS_LIKELY(is_both_small(op1, op2))) {
Sint ires = signed_val(op1) / signed_val(op2);
+
+ /* We could skip this check if it weren't for the fact that dividing
+ * MIN_SMALL by -1 causes an overflow, and we have nothing to gain from
+ * fixed-point optimizing this instruction since there's no
+ * __builtin_div_overflow. */
if (ERTS_LIKELY(IS_SSMALL(ires))) {
$Dst = make_small(ires);
$NEXT0();
@@ -241,7 +246,14 @@ rem.execute(Fail, Dst) {
c_p->freason = BADARITH;
$BIF_ERROR_ARITY_2($Fail, BIF_rem_2, RemOp1, RemOp2);
} else if (ERTS_LIKELY(is_both_small(RemOp1, RemOp2))) {
- $Dst = make_small(signed_val(RemOp1) % signed_val(RemOp2));
+ Sint lhs_untagged, rhs_untagged, untagged_result;
+
+ /* See plus.execute */
+ lhs_untagged = (RemOp1 & ~_TAG_IMMED1_MASK);
+ rhs_untagged = (RemOp2 & ~_TAG_IMMED1_MASK);
+ untagged_result = lhs_untagged % rhs_untagged;
+
+ $Dst = untagged_result | _TAG_IMMED1_SMALL;
$NEXT0();
} else {
$OUTLINED_ARITH_2($Fail, int_rem, BIF_rem_2, RemOp1, RemOp2, $Dst);
@@ -447,10 +459,10 @@ shift.execute(Fail, Dst) {
reg[1] = Op2;
SWAPOUT;
if (IsOpCode(I[0], i_bsl_ssjd)) {
- I = handle_error(c_p, I, reg, &bif_export[BIF_bsl_2]->info.mfa);
+ I = handle_error(c_p, I, reg, &bif_trap_export[BIF_bsl_2].info.mfa);
} else {
ASSERT(IsOpCode(I[0], i_bsr_ssjd));
- I = handle_error(c_p, I, reg, &bif_export[BIF_bsr_2]->info.mfa);
+ I = handle_error(c_p, I, reg, &bif_trap_export[BIF_bsr_2].info.mfa);
}
goto post_error_handling;
}
diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names
index 93960862b0..31acd46260 100644
--- a/erts/emulator/beam/atom.names
+++ b/erts/emulator/beam/atom.names
@@ -68,6 +68,7 @@ atom ErtsSecretAtom='3RT$'
atom DOWN='DOWN'
atom UP='UP'
atom EXIT='EXIT'
+atom abandoned
atom abort
atom abs_path
atom absoluteURI
@@ -116,6 +117,7 @@ atom awaiting_load
atom awaiting_unload
atom backtrace backtrace_depth
atom badarg badarith badarity badfile badfun badkey badmap badmatch badsig
+atom badopt
atom bag
atom band
atom big
@@ -195,6 +197,7 @@ atom current_location
atom current_stacktrace
atom data
atom debug_flags
+atom decentralized_counters
atom decimals
atom default
atom delay_trap
@@ -216,6 +219,7 @@ atom dist_cmd
atom dist_ctrl_put_data
atom dist_ctrlr
atom dist_data
+atom dist_spawn_init
atom Div='/'
atom div
atom dmonitor_node
@@ -246,6 +250,7 @@ atom erlang
atom erl_signal_server
atom error_handler
atom error_logger
+atom error_only
atom erts_code_purger
atom erts_debug
atom erts_dflags
@@ -293,6 +298,7 @@ atom gc_minor_start
atom Ge='>='
atom generational
atom get_all_trap
+atom get_iovec
atom get_internal_state_blocked
atom get_seq_token
atom get_size
@@ -345,6 +351,8 @@ atom is_constant
atom is_seq_trace
atom iterator
atom io
+atom iodata
+atom iovec
atom keypos
atom kill
atom killed
@@ -408,6 +416,7 @@ atom min_bin_vheap_size
atom minor
atom minor_version
atom Minus='-'
+atom MinusMinus='--'
atom module
atom module_info
atom monitored_by
@@ -453,6 +462,7 @@ atom noeol
atom noproc
atom normal
atom nosuspend
+atom no_fail
atom no_float
atom no_integer
atom no_network
@@ -498,6 +508,7 @@ atom packet
atom packet_size
atom parallelism
atom Plus='+'
+atom PlusPlus='++'
atom pause
atom pending
atom pending_driver
@@ -550,10 +561,13 @@ atom register
atom registered_name
atom reload
atom rem
+atom reply
+atom reply_tag
atom report_errors
atom reset
atom reset_seq_trace
atom restart
+atom resume
atom return_from
atom return_to
atom return_trace
@@ -612,6 +626,10 @@ atom silent
atom size
atom spawn_executable
atom spawn_driver
+atom spawn_init
+atom spawn_reply
+atom spawn_request
+atom spawn_request_yield
atom spawned
atom ssl_tls
atom stack_size
@@ -622,6 +640,7 @@ atom stop
atom stream
atom strict_monotonic
atom strict_monotonic_timestamp
+atom success_only
atom sunrm
atom suspend
atom suspended
@@ -689,4 +708,4 @@ atom xor
atom x86
atom yes
atom yield
-atom nifs
+atom nifs \ No newline at end of file
diff --git a/erts/emulator/beam/beam_bif_load.c b/erts/emulator/beam/beam_bif_load.c
index 0c95566678..d551863978 100644
--- a/erts/emulator/beam/beam_bif_load.c
+++ b/erts/emulator/beam/beam_bif_load.c
@@ -144,7 +144,7 @@ BIF_RETTYPE code_make_stub_module_3(BIF_ALIST_3)
BIF_ERROR(BIF_P, BADARG);
if (!erts_try_seize_code_write_permission(BIF_P)) {
- ERTS_BIF_YIELD3(bif_export[BIF_code_make_stub_module_3],
+ ERTS_BIF_YIELD3(&bif_trap_export[BIF_code_make_stub_module_3],
BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
}
@@ -279,7 +279,7 @@ finish_loading_1(BIF_ALIST_1)
int do_commit = 0;
if (!erts_try_seize_code_write_permission(BIF_P)) {
- ERTS_BIF_YIELD1(bif_export[BIF_finish_loading_1], BIF_P, BIF_ARG_1);
+ ERTS_BIF_YIELD1(&bif_trap_export[BIF_finish_loading_1], BIF_P, BIF_ARG_1);
}
/*
@@ -643,7 +643,7 @@ BIF_RETTYPE delete_module_1(BIF_ALIST_1)
}
if (!erts_try_seize_code_write_permission(BIF_P)) {
- ERTS_BIF_YIELD1(bif_export[BIF_delete_module_1], BIF_P, BIF_ARG_1);
+ ERTS_BIF_YIELD1(&bif_trap_export[BIF_delete_module_1], BIF_P, BIF_ARG_1);
}
{
@@ -770,7 +770,7 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2)
}
if (!erts_try_seize_code_write_permission(BIF_P)) {
- ERTS_BIF_YIELD2(bif_export[BIF_finish_after_on_load_2],
+ ERTS_BIF_YIELD2(&bif_trap_export[BIF_finish_after_on_load_2],
BIF_P, BIF_ARG_1, BIF_ARG_2);
}
@@ -821,21 +821,25 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2)
*/
num_exps = export_list_size(code_ix);
for (i = 0; i < num_exps; i++) {
- Export *ep = export_list(i,code_ix);
- if (ep == NULL || ep->info.mfa.module != BIF_ARG_1) {
- continue;
- }
- if (ep->beam[1] != 0) {
- ep->addressv[code_ix] = (void *) ep->beam[1];
- ep->beam[1] = 0;
- } else {
- if (ep->addressv[code_ix] == ep->beam &&
- BeamIsOpCode(ep->beam[0], op_apply_bif)) {
- continue;
- }
- ep->addressv[code_ix] = ep->beam;
- ep->beam[0] = BeamOpCodeAddr(op_call_error_handler);
- }
+ Export *ep = export_list(i, code_ix);
+
+ if (ep == NULL || ep->info.mfa.module != BIF_ARG_1) {
+ continue;
+ }
+
+ DBG_CHECK_EXPORT(ep, code_ix);
+
+ if (ep->trampoline.not_loaded.deferred != 0) {
+ ep->addressv[code_ix] = (void*)ep->trampoline.not_loaded.deferred;
+ ep->trampoline.not_loaded.deferred = 0;
+ } else {
+ if (ep->bif_number != -1) {
+ continue;
+ }
+
+ ep->addressv[code_ix] = ep->trampoline.raw;
+ ep->trampoline.op = BeamOpCodeAddr(op_call_error_handler);
+ }
}
modp->curr.code_hdr->on_load_function_ptr = NULL;
@@ -858,10 +862,11 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2)
if (ep == NULL || ep->info.mfa.module != BIF_ARG_1) {
continue;
}
- if (BeamIsOpCode(ep->beam[0], op_apply_bif)) {
+ if (ep->bif_number != -1) {
continue;
}
- ep->beam[1] = 0;
+
+ ep->trampoline.not_loaded.deferred = 0;
}
}
erts_release_code_write_permission();
@@ -1116,16 +1121,15 @@ check_process_code(Process* rp, Module* modp, int *redsp, int fcalls)
mod_size = modp->old.code_length;
/*
- * Check if current instruction or continuation pointer points into module.
+ * Check if the instruction pointer points into module.
*/
- if (ErtsInArea(rp->i, mod_start, mod_size)
- || ErtsInArea(rp->cp, mod_start, mod_size)) {
+ if (ErtsInArea(rp->i, mod_start, mod_size)) {
return am_true;
}
-
+
*redsp += 1;
- if (erts_check_nif_export_in_area(rp, mod_start, mod_size))
+ if (erts_check_nfunc_in_area(rp, mod_start, mod_size))
return am_true;
*redsp += (STACK_START(rp) - rp->stop) / 32;
@@ -1664,7 +1668,7 @@ BIF_RETTYPE erts_internal_release_literal_area_switch_0(BIF_ALIST_0)
ASSERT(old_area);
ERTS_VBUMP_ALL_REDS(BIF_P);
- BIF_TRAP0(bif_export[BIF_erts_internal_release_literal_area_switch_0],
+ BIF_TRAP0(&bif_trap_export[BIF_erts_internal_release_literal_area_switch_0],
BIF_P);
}
@@ -2026,7 +2030,7 @@ BIF_RETTYPE erts_internal_purge_module_2(BIF_ALIST_2)
BIF_ERROR(BIF_P, BADARG);
if (!erts_try_seize_code_write_permission(BIF_P)) {
- ERTS_BIF_YIELD2(bif_export[BIF_erts_internal_purge_module_2],
+ ERTS_BIF_YIELD2(&bif_trap_export[BIF_erts_internal_purge_module_2],
BIF_P, BIF_ARG_1, BIF_ARG_2);
}
@@ -2050,23 +2054,22 @@ BIF_RETTYPE erts_internal_purge_module_2(BIF_ALIST_2)
ERTS_BIF_PREP_RET(ret, am_false);
}
else {
- /*
- * Unload any NIF library
- */
- if (modp->old.nif != NULL
- || IF_HIPE(hipe_purge_need_blocking(modp))) {
- /* ToDo: Do unload nif without blocking */
+ if (IF_HIPE(hipe_purge_need_blocking(modp))) {
erts_rwunlock_old_code(code_ix);
erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
erts_thr_progress_block();
is_blocking = 1;
erts_rwlock_old_code(code_ix);
- if (modp->old.nif) {
- erts_unload_nif(modp->old.nif);
- modp->old.nif = NULL;
- }
}
+ /*
+ * Unload any NIF library
+ */
+ if (modp->old.nif) {
+ erts_unload_nif(modp->old.nif);
+ modp->old.nif = NULL;
+ }
+
/*
* Remove the old code.
*/
@@ -2198,25 +2201,28 @@ delete_code(Module* modp)
for (i = 0; i < num_exps; i++) {
Export *ep = export_list(i, code_ix);
if (ep != NULL && (ep->info.mfa.module == module)) {
- if (ep->addressv[code_ix] == ep->beam) {
- if (BeamIsOpCode(ep->beam[0], op_apply_bif)) {
- continue;
- }
- else if (BeamIsOpCode(ep->beam[0], op_i_generic_breakpoint)) {
+ if (ep->addressv[code_ix] == ep->trampoline.raw) {
+ if (BeamIsOpCode(ep->trampoline.op, op_i_generic_breakpoint)) {
ERTS_LC_ASSERT(erts_thr_progress_is_blocking());
ASSERT(modp->curr.num_traced_exports > 0);
DBG_TRACE_MFA_P(&ep->info.mfa,
"export trace cleared, code_ix=%d", code_ix);
- erts_clear_export_break(modp, &ep->info);
+ erts_clear_export_break(modp, ep);
}
else {
- ASSERT(BeamIsOpCode(ep->beam[0], op_call_error_handler) ||
+ ASSERT(BeamIsOpCode(ep->trampoline.op, op_call_error_handler) ||
!erts_initialized);
}
}
- ep->addressv[code_ix] = ep->beam;
- ep->beam[0] = BeamOpCodeAddr(op_call_error_handler);
- ep->beam[1] = 0;
+
+ if (ep->bif_number != -1 && ep->is_bif_traced) {
+ /* Code unloading kills both global and local call tracing. */
+ ep->is_bif_traced = 0;
+ }
+
+ ep->addressv[code_ix] = ep->trampoline.raw;
+ ep->trampoline.op = BeamOpCodeAddr(op_call_error_handler);
+ ep->trampoline.not_loaded.deferred = 0;
DBG_TRACE_MFA_P(&ep->info.mfa,
"export invalidation, code_ix=%d", code_ix);
}
diff --git a/erts/emulator/beam/beam_bp.c b/erts/emulator/beam/beam_bp.c
index 60fd20b9df..612166c024 100644
--- a/erts/emulator/beam/beam_bp.c
+++ b/erts/emulator/beam/beam_bp.c
@@ -207,9 +207,6 @@ erts_bp_match_functions(BpFunctions* f, ErtsCodeMFA *mfa, int specified)
if (erts_is_function_native(ci)) {
continue;
}
- if (is_nil(ci->mfa.module)) { /* Ignore BIF stub */
- continue;
- }
switch (specified) {
case 3:
if (ci->mfa.arity != mfa->arity)
@@ -244,8 +241,10 @@ erts_bp_match_export(BpFunctions* f, ErtsCodeMFA *mfa, int specified)
f->matching = (BpFunction *) Alloc(num_exps*sizeof(BpFunction));
ne = 0;
for (i = 0; i < num_exps; i++) {
- Export* ep = export_list(i, code_ix);
- BeamInstr* pc;
+ BeamInstr *func;
+ Export* ep;
+
+ ep = export_list(i, code_ix);
switch (specified) {
case 3:
@@ -263,19 +262,20 @@ erts_bp_match_export(BpFunctions* f, ErtsCodeMFA *mfa, int specified)
ASSERT(0);
}
- pc = ep->beam;
- if (ep->addressv[code_ix] == pc) {
- if (BeamIsOpCode(*pc, op_apply_bif) ||
- BeamIsOpCode(*pc, op_call_error_handler)) {
- continue;
- }
- ASSERT(BeamIsOpCode(*pc, op_i_generic_breakpoint));
- } else if (erts_is_function_native(erts_code_to_codeinfo(ep->addressv[code_ix]))) {
- continue;
- }
+ func = ep->addressv[code_ix];
+
+ if (func == ep->trampoline.raw) {
+ if (BeamIsOpCode(*func, op_call_error_handler)) {
+ continue;
+ }
+ ASSERT(BeamIsOpCode(*func, op_i_generic_breakpoint));
+ } else if (erts_is_function_native(erts_code_to_codeinfo(func))) {
+ continue;
+ }
f->matching[ne].ci = &ep->info;
f->matching[ne].mod = erts_get_module(ep->info.mfa.module, code_ix);
+
ne++;
}
@@ -305,18 +305,6 @@ erts_consolidate_bp_data(BpFunctions* f, int local)
}
}
-void
-erts_consolidate_bif_bp_data(void)
-{
- int i;
-
- ERTS_LC_ASSERT(erts_has_code_write_permission());
- for (i = 0; i < BIF_SIZE; i++) {
- Export *ep = bif_export[i];
- consolidate_bp_data(0, &ep->info, 0);
- }
-}
-
static void
consolidate_bp_data(Module* modp, ErtsCodeInfo *ci, int local)
{
@@ -495,7 +483,7 @@ erts_set_mtrace_break(BpFunctions* f, Binary *match_spec, ErtsTracer tracer)
}
void
-erts_set_call_trace_bif(ErtsCodeInfo *ci, Binary *match_spec, int local)
+erts_set_export_trace(ErtsCodeInfo *ci, Binary *match_spec, int local)
{
Uint flags = local ? ERTS_BPF_LOCAL_TRACE : ERTS_BPF_GLOBAL_TRACE;
@@ -503,25 +491,6 @@ erts_set_call_trace_bif(ErtsCodeInfo *ci, Binary *match_spec, int local)
}
void
-erts_set_mtrace_bif(ErtsCodeInfo *ci, Binary *match_spec, ErtsTracer tracer)
-{
- set_function_break(ci, match_spec, ERTS_BPF_META_TRACE, 0, tracer);
-}
-
-void
-erts_set_time_trace_bif(ErtsCodeInfo *ci, enum erts_break_op count_op)
-{
- set_function_break(ci, NULL,
- ERTS_BPF_TIME_TRACE|ERTS_BPF_TIME_TRACE_ACTIVE,
- count_op, erts_tracer_nil);
-}
-
-void
-erts_clear_time_trace_bif(ErtsCodeInfo *ci) {
- clear_function_break(ci, ERTS_BPF_TIME_TRACE|ERTS_BPF_TIME_TRACE_ACTIVE);
-}
-
-void
erts_set_debug_break(BpFunctions* f) {
set_break(f, NULL, ERTS_BPF_DEBUG, 0, erts_tracer_nil);
}
@@ -547,7 +516,7 @@ erts_clear_trace_break(BpFunctions* f)
}
void
-erts_clear_call_trace_bif(ErtsCodeInfo *ci, int local)
+erts_clear_export_trace(ErtsCodeInfo *ci, int local)
{
GenericBp* g = ci->u.gen_bp;
@@ -566,12 +535,6 @@ erts_clear_mtrace_break(BpFunctions* f)
}
void
-erts_clear_mtrace_bif(ErtsCodeInfo *ci)
-{
- clear_function_break(ci, ERTS_BPF_META_TRACE);
-}
-
-void
erts_clear_debug_break(BpFunctions* f)
{
ERTS_LC_ASSERT(erts_thr_progress_is_blocking());
@@ -630,58 +593,56 @@ erts_clear_module_break(Module *modp) {
}
void
-erts_clear_export_break(Module* modp, ErtsCodeInfo *ci)
+erts_clear_export_break(Module* modp, Export *ep)
{
+ ErtsCodeInfo *ci;
+
ERTS_LC_ASSERT(erts_thr_progress_is_blocking());
+ ci = &ep->info;
+
+ ASSERT(erts_codeinfo_to_code(ci) == ep->trampoline.raw);
+
+ ASSERT(BeamIsOpCode(ep->trampoline.op, op_i_generic_breakpoint));
+ ep->trampoline.op = 0;
+
clear_function_break(ci, ERTS_BPF_ALL);
erts_commit_staged_bp();
- *erts_codeinfo_to_code(ci) = (BeamInstr) 0;
+
consolidate_bp_data(modp, ci, 0);
ASSERT(ci->u.gen_bp == NULL);
}
/*
- * If c_p->cp is a trace return instruction, we set cp
- * to be the place where we again start to execute code.
+ * If the topmost continuation pointer on the stack is a trace return
+ * instruction, we modify it to be the place where we again start to
+ * execute code.
*
- * cp is used by match spec {caller} to get the calling
- * function, and if we don't do this fixup it will be
- * 'undefined'. This has the odd side effect of {caller}
- * not really being which function is the caller, but
- * rather which function we are about to return to.
+ * This continuation pointer is used by match spec {caller} to get the
+ * calling function, and if we don't do this fixup it will be
+ * 'undefined'. This has the odd side effect of {caller} not really
+ * being the function which is the caller, but rather the function
+ * which we are about to return to.
*/
static void fixup_cp_before_trace(Process *c_p, int *return_to_trace)
{
- Eterm *cpp, *E = c_p->stop;
- BeamInstr w = *c_p->cp;
- if (BeamIsOpCode(w, op_return_trace)) {
- cpp = &E[2];
- } else if (BeamIsOpCode(w, op_i_return_to_trace)) {
- *return_to_trace = 1;
- cpp = &E[0];
- } else if (BeamIsOpCode(w, op_i_return_time_trace)) {
- cpp = &E[0];
- } else {
- cpp = NULL;
- }
- if (cpp) {
- for (;;) {
- BeamInstr w = *cp_val(*cpp);
- if (BeamIsOpCode(w, op_return_trace)) {
- cpp += 3;
- } else if (BeamIsOpCode(w, op_i_return_to_trace)) {
- *return_to_trace = 1;
- cpp += 1;
- } else if (BeamIsOpCode(w, op_i_return_time_trace)) {
- cpp += 2;
- } else {
- break;
- }
+ Eterm *cpp = c_p->stop;
+
+ for (;;) {
+ BeamInstr w = *cp_val(*cpp);
+ if (BeamIsOpCode(w, op_return_trace)) {
+ cpp += 3;
+ } else if (BeamIsOpCode(w, op_i_return_to_trace)) {
+ *return_to_trace = 1;
+ cpp += 1;
+ } else if (BeamIsOpCode(w, op_i_return_time_trace)) {
+ cpp += 2;
+ } else {
+ break;
}
- c_p->cp = (BeamInstr *) cp_val(*cpp);
- ASSERT(is_CP(*cpp));
}
+ c_p->stop[0] = (Eterm) cp_val(*cpp);
+ ASSERT(is_CP(*cpp));
}
BeamInstr
@@ -742,13 +703,14 @@ erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *info, Eterm* reg)
}
if (bp_flags & ERTS_BPF_TIME_TRACE_ACTIVE) {
- Eterm w;
+ BeamInstr w;
+ Eterm* E;
ErtsCodeInfo* prev_info = erts_trace_time_call(c_p, info, bp->time);
- w = (BeamInstr) *c_p->cp;
+ E = c_p->stop;
+ w = *(BeamInstr*) E[0];
if (! (BeamIsOpCode(w, op_i_return_time_trace) ||
BeamIsOpCode(w, op_return_trace) ||
BeamIsOpCode(w, op_i_return_to_trace)) ) {
- Eterm* E = c_p->stop;
ASSERT(c_p->htop <= E && E <= c_p->hend);
if (E - 2 < c_p->htop) {
(void) erts_garbage_collect(c_p, 2, reg, info->mfa.arity);
@@ -759,9 +721,8 @@ erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *info, Eterm* reg)
ASSERT(c_p->htop <= E && E <= c_p->hend);
E -= 2;
- E[0] = prev_info ? make_cp(erts_codeinfo_to_code(prev_info)) : NIL;
- E[1] = make_cp(c_p->cp); /* original return address */
- c_p->cp = beam_return_time_trace;
+ E[1] = prev_info ? make_cp(erts_codeinfo_to_code(prev_info)) : NIL;
+ E[0] = (Eterm) beam_return_time_trace;
c_p->stop = E;
}
}
@@ -773,237 +734,24 @@ erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *info, Eterm* reg)
}
}
-/*
- * Entry point called by the trace wrap functions in erl_bif_wrap.c
- *
- * The trace wrap functions are themselves called through the export
- * entries instead of the original BIF functions.
- */
-Eterm
-erts_bif_trace(int bif_index, Process* p, Eterm* args, BeamInstr* I)
-{
- Eterm result;
- Eterm (*func)(Process*, Eterm*, BeamInstr*);
- Export* ep = bif_export[bif_index];
- Uint32 flags = 0, flags_meta = 0;
- ErtsTracer meta_tracer = erts_tracer_nil;
- int applying = (I == ep->beam); /* Yup, the apply code for a bif
- * is actually in the
- * export entry */
- BeamInstr *cp = p->cp;
- GenericBp* g;
- GenericBpData* bp = NULL;
- Uint bp_flags = 0;
- int return_to_trace = 0;
-
- ERTS_CHK_HAVE_ONLY_MAIN_PROC_LOCK(p);
-
- g = ep->info.u.gen_bp;
- if (g) {
- bp = &g->data[erts_active_bp_ix()];
- bp_flags = bp->flags;
- }
-
- /*
- * Make continuation pointer OK, it is not during direct BIF calls,
- * but it is correct during apply of bif.
- */
- if (!applying) {
- p->cp = I;
- } else {
- fixup_cp_before_trace(p, &return_to_trace);
- }
- if (bp_flags & (ERTS_BPF_LOCAL_TRACE|ERTS_BPF_GLOBAL_TRACE) &&
- IS_TRACED_FL(p, F_TRACE_CALLS)) {
- int local = !!(bp_flags & ERTS_BPF_LOCAL_TRACE);
- flags = erts_call_trace(p, &ep->info, bp->local_ms, args,
- local, &ERTS_TRACER(p));
- }
- if (bp_flags & ERTS_BPF_META_TRACE) {
- ErtsTracer old_tracer;
-
- meta_tracer = erts_atomic_read_nob(&bp->meta_tracer->tracer);
- old_tracer = meta_tracer;
- flags_meta = erts_call_trace(p, &ep->info, bp->meta_ms, args,
- 0, &meta_tracer);
-
- if (!ERTS_TRACER_COMPARE(old_tracer, meta_tracer)) {
- ErtsTracer new_tracer = erts_tracer_nil;
- erts_tracer_update(&new_tracer, meta_tracer);
- if (old_tracer == erts_atomic_cmpxchg_acqb(
- &bp->meta_tracer->tracer,
- (erts_aint_t)new_tracer,
- (erts_aint_t)old_tracer)) {
- ERTS_TRACER_CLEAR(&old_tracer);
- } else {
- ERTS_TRACER_CLEAR(&new_tracer);
- }
- }
- }
- if (bp_flags & ERTS_BPF_TIME_TRACE_ACTIVE &&
- IS_TRACED_FL(p, F_TRACE_CALLS)) {
- erts_trace_time_call(p, &ep->info, bp->time);
- }
-
- /* Restore original continuation pointer (if changed). */
- p->cp = cp;
-
- func = bif_table[bif_index].f;
-
- result = func(p, args, I);
-
- if (erts_nif_export_check_save_trace(p, result,
- applying, ep,
- cp, flags,
- flags_meta, I,
- meta_tracer)) {
- /*
- * erts_bif_trace_epilogue() will be called
- * later when appropriate via the NIF export
- * scheduling functionality...
- */
- return result;
- }
-
- return erts_bif_trace_epilogue(p, result, applying, ep, cp,
- flags, flags_meta, I,
- meta_tracer);
-}
-
-Eterm
-erts_bif_trace_epilogue(Process *p, Eterm result, int applying,
- Export* ep, BeamInstr *cp, Uint32 flags,
- Uint32 flags_meta, BeamInstr* I,
- ErtsTracer meta_tracer)
-{
- if (applying && (flags & MATCH_SET_RETURN_TO_TRACE)) {
- BeamInstr i_return_trace = beam_return_trace[0];
- BeamInstr i_return_to_trace = beam_return_to_trace[0];
- BeamInstr i_return_time_trace = beam_return_time_trace[0];
- Eterm *cpp;
- /* Maybe advance cp to skip trace stack frames */
- for (cpp = p->stop; ; cp = cp_val(*cpp++)) {
- if (*cp == i_return_trace) {
- /* Skip stack frame variables */
- while (is_not_CP(*cpp)) cpp++;
- cpp += 2; /* Skip return_trace parameters */
- } else if (*cp == i_return_time_trace) {
- /* Skip stack frame variables */
- while (is_not_CP(*cpp)) cpp++;
- cpp += 1; /* Skip return_time_trace parameters */
- } else if (*cp == i_return_to_trace) {
- /* A return_to trace message is going to be generated
- * by normal means, so we do not have to.
- */
- cp = NULL;
- break;
- } else break;
- }
- }
-
- /* Try to get these in the order
- * they usually appear in normal code... */
- if (is_non_value(result)) {
- Uint reason = p->freason;
- if (reason != TRAP) {
- Eterm class;
- Eterm value = p->fvalue;
- /* Expand error value like in handle_error() */
- if (reason & EXF_ARGLIST) {
- Eterm *tp;
- ASSERT(is_tuple(value));
- tp = tuple_val(value);
- value = tp[1];
- }
- if ((reason & EXF_THROWN) && (p->catches <= 0)) {
- Eterm *hp = HAlloc(p, 3);
- value = TUPLE2(hp, am_nocatch, value);
- reason = EXC_ERROR;
- }
- /* Note: expand_error_value() could theoretically
- * allocate on the heap, but not for any error
- * returned by a BIF, and it would do no harm,
- * just be annoying.
- */
- value = expand_error_value(p, reason, value);
- class = exception_tag[GET_EXC_CLASS(reason)];
-
- if (flags_meta & MATCH_SET_EXCEPTION_TRACE) {
- erts_trace_exception(p, &ep->info.mfa, class, value,
- &meta_tracer);
- }
- if (flags & MATCH_SET_EXCEPTION_TRACE) {
- erts_trace_exception(p, &ep->info.mfa, class, value,
- &ERTS_TRACER(p));
- }
- if ((flags & MATCH_SET_RETURN_TO_TRACE) && p->catches > 0) {
- /* can only happen if(local)*/
- Eterm *ptr = p->stop;
- ASSERT(is_CP(*ptr));
- ASSERT(ptr <= STACK_START(p));
- /* Search the nearest stack frame for a catch */
- while (++ptr < STACK_START(p)) {
- if (is_CP(*ptr)) break;
- if (is_catch(*ptr)) {
- if (applying) {
- /* Apply of BIF, cp is in calling function */
- if (cp) erts_trace_return_to(p, cp);
- } else {
- /* Direct bif call, I points into
- * calling function */
- erts_trace_return_to(p, I);
- }
- }
- }
- }
- if ((flags_meta|flags) & MATCH_SET_EXCEPTION_TRACE) {
- erts_proc_lock(p, ERTS_PROC_LOCKS_ALL_MINOR);
- ERTS_TRACE_FLAGS(p) |= F_EXCEPTION_TRACE;
- erts_proc_unlock(p, ERTS_PROC_LOCKS_ALL_MINOR);
- }
- }
- } else {
- if (flags_meta & MATCH_SET_RX_TRACE) {
- erts_trace_return(p, &ep->info.mfa, result, &meta_tracer);
- }
- /* MATCH_SET_RETURN_TO_TRACE cannot occur if(meta) */
- if (flags & MATCH_SET_RX_TRACE) {
- erts_trace_return(p, &ep->info.mfa, result, &ERTS_TRACER(p));
- }
- if (flags & MATCH_SET_RETURN_TO_TRACE &&
- IS_TRACED_FL(p, F_TRACE_RETURN_TO)) {
- /* can only happen if(local)*/
- if (applying) {
- /* Apply of BIF, cp is in calling function */
- if (cp) erts_trace_return_to(p, cp);
- } else {
- /* Direct bif call, I points into calling function */
- erts_trace_return_to(p, I);
- }
- }
- }
- ERTS_CHK_HAVE_ONLY_MAIN_PROC_LOCK(p);
- return result;
-}
-
static ErtsTracer
do_call_trace(Process* c_p, ErtsCodeInfo* info, Eterm* reg,
int local, Binary* ms, ErtsTracer tracer)
{
int return_to_trace = 0;
- BeamInstr *cp_save = c_p->cp;
Uint32 flags;
Uint need = 0;
+ Eterm cp_save;
Eterm* E = c_p->stop;
- fixup_cp_before_trace(c_p, &return_to_trace);
+ cp_save = E[0];
+ fixup_cp_before_trace(c_p, &return_to_trace);
ERTS_UNREQ_PROC_MAIN_LOCK(c_p);
flags = erts_call_trace(c_p, info, ms, reg, local, &tracer);
ERTS_REQ_PROC_MAIN_LOCK(c_p);
- /* restore cp after potential fixup */
- c_p->cp = cp_save;
+ E[0] = cp_save;
ASSERT(!ERTS_PROC_IS_EXITING(c_p));
if ((flags & MATCH_SET_RETURN_TO_TRACE) && !return_to_trace) {
@@ -1023,28 +771,23 @@ do_call_trace(Process* c_p, ErtsCodeInfo* info, Eterm* reg,
if (flags & MATCH_SET_RETURN_TO_TRACE && !return_to_trace) {
E -= 1;
ASSERT(c_p->htop <= E && E <= c_p->hend);
- E[0] = make_cp(c_p->cp);
- c_p->cp = beam_return_to_trace;
+ E[0] = (Eterm) beam_return_to_trace;
+ c_p->stop = E;
}
- if (flags & MATCH_SET_RX_TRACE)
- {
+ if (flags & MATCH_SET_RX_TRACE) {
E -= 3;
c_p->stop = E;
ASSERT(c_p->htop <= E && E <= c_p->hend);
ASSERT(is_CP((Eterm) (UWord) (&info->mfa.module)));
ASSERT(IS_TRACER_VALID(tracer));
- E[2] = make_cp(c_p->cp);
- E[1] = copy_object(tracer, c_p);
- E[0] = make_cp(&info->mfa.module);
- /* We ARE at the beginning of an instruction,
- the funcinfo is above i. */
- c_p->cp = (flags & MATCH_SET_EXCEPTION_TRACE) ?
- beam_exception_trace : beam_return_trace;
+ E[2] = copy_object(tracer, c_p);
+ E[1] = make_cp(&info->mfa.module);
+ E[0] = (Eterm) ((flags & MATCH_SET_EXCEPTION_TRACE) ?
+ beam_exception_trace : beam_return_trace);
erts_proc_lock(c_p, ERTS_PROC_LOCKS_ALL_MINOR);
ERTS_TRACE_FLAGS(c_p) |= F_EXCEPTION_TRACE;
erts_proc_unlock(c_p, ERTS_PROC_LOCKS_ALL_MINOR);
- } else
- c_p->stop = E;
+ }
return tracer;
}
diff --git a/erts/emulator/beam/beam_bp.h b/erts/emulator/beam/beam_bp.h
index c45d7c4011..5d2f54613f 100644
--- a/erts/emulator/beam/beam_bp.h
+++ b/erts/emulator/beam/beam_bp.h
@@ -119,20 +119,16 @@ void erts_bp_free_matched_functions(BpFunctions* f);
void erts_install_breakpoints(BpFunctions* f);
void erts_uninstall_breakpoints(BpFunctions* f);
void erts_consolidate_bp_data(BpFunctions* f, int local);
-void erts_consolidate_bif_bp_data(void);
void erts_set_trace_break(BpFunctions *f, Binary *match_spec);
void erts_clear_trace_break(BpFunctions *f);
-void erts_set_call_trace_bif(ErtsCodeInfo *ci, Binary *match_spec, int local);
-void erts_clear_call_trace_bif(ErtsCodeInfo *ci, int local);
+void erts_set_export_trace(ErtsCodeInfo *ci, Binary *match_spec, int local);
+void erts_clear_export_trace(ErtsCodeInfo *ci, int local);
void erts_set_mtrace_break(BpFunctions *f, Binary *match_spec,
ErtsTracer tracer);
void erts_clear_mtrace_break(BpFunctions *f);
-void erts_set_mtrace_bif(ErtsCodeInfo *ci, Binary *match_spec,
- ErtsTracer tracer);
-void erts_clear_mtrace_bif(ErtsCodeInfo *ci);
void erts_set_debug_break(BpFunctions *f);
void erts_clear_debug_break(BpFunctions *f);
@@ -142,7 +138,7 @@ void erts_clear_count_break(BpFunctions *f);
void erts_clear_all_breaks(BpFunctions* f);
int erts_clear_module_break(Module *modp);
-void erts_clear_export_break(Module *modp, ErtsCodeInfo* ci);
+void erts_clear_export_break(Module *modp, Export *ep);
BeamInstr erts_generic_breakpoint(Process* c_p, ErtsCodeInfo *ci, Eterm* reg);
BeamInstr erts_trace_break(Process *p, ErtsCodeInfo *ci, Eterm *args,
@@ -151,8 +147,6 @@ BeamInstr erts_trace_break(Process *p, ErtsCodeInfo *ci, Eterm *args,
int erts_is_trace_break(ErtsCodeInfo *ci, Binary **match_spec_ret, int local);
int erts_is_mtrace_break(ErtsCodeInfo *ci, Binary **match_spec_ret,
ErtsTracer *tracer_ret);
-int erts_is_mtrace_bif(ErtsCodeInfo *ci, Binary **match_spec_ret,
- ErtsTracer *tracer_ret);
int erts_is_native_break(ErtsCodeInfo *ci);
int erts_is_count_break(ErtsCodeInfo *ci, Uint *count_ret);
int erts_is_time_break(Process *p, ErtsCodeInfo *ci, Eterm *call_time);
@@ -163,10 +157,6 @@ void erts_schedule_time_break(Process *p, Uint out);
void erts_set_time_break(BpFunctions *f, enum erts_break_op);
void erts_clear_time_break(BpFunctions *f);
-int erts_is_time_trace_bif(Process *p, ErtsCodeInfo *ci, Eterm *call_time);
-void erts_set_time_trace_bif(ErtsCodeInfo *ci, enum erts_break_op);
-void erts_clear_time_trace_bif(ErtsCodeInfo *ci);
-
ErtsCodeInfo *erts_find_local_func(ErtsCodeMFA *mfa);
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
diff --git a/erts/emulator/beam/beam_debug.c b/erts/emulator/beam/beam_debug.c
index d24491f88f..2c49d52fc9 100644
--- a/erts/emulator/beam/beam_debug.c
+++ b/erts/emulator/beam/beam_debug.c
@@ -47,7 +47,6 @@
#else
# define HEXF "%08bpX"
#endif
-#define TermWords(t) (((t) / (sizeof(BeamInstr)/sizeof(Eterm))) + !!((t) % (sizeof(BeamInstr)/sizeof(Eterm))))
void dbg_bt(Process* p, Eterm* sp);
void dbg_where(BeamInstr* addr, Eterm x0, Eterm* reg);
@@ -158,7 +157,7 @@ erts_debug_breakpoint_2(BIF_ALIST_2)
}
if (!erts_try_seize_code_write_permission(BIF_P)) {
- ERTS_BIF_YIELD2(bif_export[BIF_erts_debug_breakpoint_2],
+ ERTS_BIF_YIELD2(&bif_trap_export[BIF_erts_debug_breakpoint_2],
BIF_P, BIF_ARG_1, BIF_ARG_2);
}
erts_proc_unlock(p, ERTS_PROC_LOCK_MAIN);
@@ -332,7 +331,7 @@ erts_debug_disassemble_1(BIF_ALIST_1)
"unknown " HEXF "\n", instr);
code_ptr++;
}
- if (i == op_call_nif) {
+ if (i == op_call_nif_WWW) {
/*
* The rest of the code will not be executed. Don't disassemble any
* more code in this function.
@@ -534,7 +533,7 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
}
break;
case 'n': /* Nil */
- erts_print(to, to_arg, "[]");
+ erts_print(to, to_arg, "`[]`");
break;
case 'S': /* Register */
{
@@ -560,7 +559,7 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
case 'i': /* Tagged integer */
case 'c': /* Tagged constant */
case 'q': /* Tagged literal */
- erts_print(to, to_arg, "%T", (Eterm) *ap);
+ erts_print(to, to_arg, "`%T`", (Eterm) *ap);
ap++;
break;
case 'A':
@@ -585,7 +584,7 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
if (*sign == 'W') {
ErlFunEntry* fe = (ErlFunEntry *) *ap;
ErtsCodeMFA* cmfa = find_function_from_pc(fe->address);
- erts_print(to, to_arg, "%T:%T/%bpu", cmfa->module,
+ erts_print(to, to_arg, "fun(`%T`:`%T`/%bpu)", cmfa->module,
cmfa->function, cmfa->arity);
} else {
erts_print(to, to_arg, "%d", *ap);
@@ -628,8 +627,8 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
if (!cmfa || erts_codemfa_to_code(cmfa) != target) {
erts_print(to, to_arg, "f(" HEXF ")", target);
} else {
- erts_print(to, to_arg, "%T:%T/%bpu", cmfa->module,
- cmfa->function, cmfa->arity);
+ erts_print(to, to_arg, "loc(`%T`:`%T`/%bpu)",
+ cmfa->module, cmfa->function, cmfa->arity);
}
ap++;
}
@@ -654,9 +653,10 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
break;
case 'e': /* Export entry */
{
- Export* ex = (Export *) *ap;
- erts_print(to, to_arg,
- "%T:%T/%bpu", (Eterm) ex->info.mfa.module,
+ Export* ex = (Export *) *ap;
+ erts_print(to, to_arg,
+ "exp(`%T`:`%T`/%bpu)",
+ (Eterm) ex->info.mfa.module,
(Eterm) ex->info.mfa.function,
ex->info.mfa.arity);
ap++;
@@ -710,7 +710,7 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
Sint32* jump_tab = (Sint32 *)(ap + n);
while (ix--) {
- erts_print(to, to_arg, "%T ", (Eterm) ap[0]);
+ erts_print(to, to_arg, "`%T` ", (Eterm) ap[0]);
ap++;
size++;
}
@@ -737,7 +737,7 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
size++;
}
/* print sentinel */
- erts_print(to, to_arg, "{%T} ", ap[0], ap[1]);
+ erts_print(to, to_arg, "{`%T`} ", ap[0], ap[1]);
ap++;
size++;
ix = n;
@@ -813,7 +813,7 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
erts_print(to, to_arg, " y(%d)", loader_y_reg_index(ap[0]) - CP_SIZE);
break;
default:
- erts_print(to, to_arg, " %T", (Eterm) ap[0]);
+ erts_print(to, to_arg, " `%T`", (Eterm) ap[0]);
break;
}
ap++, size++, n--;
@@ -834,7 +834,7 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
erts_print(to, to_arg, " y(%d)", loader_y_reg_index(ap[0]) - CP_SIZE);
break;
default:
- erts_print(to, to_arg, " %T", (Eterm) ap[0]);
+ erts_print(to, to_arg, " `%T`", (Eterm) ap[0]);
break;
}
ap++, size++, n--;
@@ -857,7 +857,7 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
erts_print(to, to_arg, " y(%d)", loader_y_reg_index(ap[0]) - CP_SIZE);
break;
default:
- erts_print(to, to_arg, " %T", (Eterm) ap[0]);
+ erts_print(to, to_arg, " `%T`", (Eterm) ap[0]);
break;
}
}
@@ -881,11 +881,12 @@ static void print_bif_name(fmtfn_t to, void* to_arg, BifFunction bif)
}
}
if (i == BIF_SIZE) {
- erts_print(to, to_arg, "b(%d)", (Uint) bif);
+ erts_print(to, to_arg, "bif(%d)", (Uint) bif);
} else {
+ Eterm module = bif_table[i].module;
Eterm name = bif_table[i].name;
unsigned arity = bif_table[i].arity;
- erts_print(to, to_arg, "%T/%u", name, arity);
+ erts_print(to, to_arg, "bif(`%T`:`%T`/%bpu)", module, name, arity);
}
}
diff --git a/erts/emulator/beam/beam_emu.c b/erts/emulator/beam/beam_emu.c
index ea2be00e06..41a307271f 100644
--- a/erts/emulator/beam/beam_emu.c
+++ b/erts/emulator/beam/beam_emu.c
@@ -109,14 +109,9 @@ do { \
# define CHECK_ARGS(T)
#endif
-#define CHECK_ALIGNED(Dst) ASSERT((((Uint)&Dst) & (sizeof(Uint)-1)) == 0)
-
-#define GET_BIF_MODULE(p) (p->info.mfa.module)
-#define GET_BIF_FUNCTION(p) (p->info.mfa.function)
-#define GET_BIF_ARITY(p) (p->info.mfa.arity)
-#define GET_BIF_ADDRESS(p) ((BifFunction) (p->beam[1]))
-#define TermWords(t) (((t) / (sizeof(BeamInstr)/sizeof(Eterm))) + !!((t) % (sizeof(BeamInstr)/sizeof(Eterm))))
-
+#define GET_EXPORT_MODULE(p) ((p)->info.mfa.module)
+#define GET_EXPORT_FUNCTION(p) ((p)->info.mfa.function)
+#define GET_EXPORT_ARITY(p) ((p)->info.mfa.arity)
/*
* We reuse some of fields in the save area in the process structure.
@@ -141,10 +136,6 @@ do { \
BeamCodeAddr(IP) < (BeamInstr)LabelAddr(end_emulator_loop))
#endif /* NO_JUMP_TABLE */
-#define SET_CP(p, ip) \
- ASSERT(VALID_INSTR(*(ip))); \
- (p)->cp = (ip)
-
#define SET_I(ip) \
ASSERT(VALID_INSTR(* (Eterm *)(ip))); \
I = (ip)
@@ -165,7 +156,7 @@ BeamInstr beam_continue_exit[1];
/* NOTE These should be the only variables containing trace instructions.
-** Sometimes tests are form the instruction value, and sometimes
+** Sometimes tests are for the instruction value, and sometimes
** for the referring variable (one of these), and rouge references
** will most likely cause chaos.
*/
@@ -254,72 +245,6 @@ void** beam_ops;
#define Q(N) (N*sizeof(Eterm *))
#define l(N) (freg[N].fd)
-/*
- * Check that we haven't used the reductions and jump to function pointed to by
- * the I register. If we are out of reductions, do a context switch.
- */
-
-#define DispatchMacro() \
- do { \
- BeamInstr dis_next; \
- dis_next = *I; \
- CHECK_ARGS(I); \
- if (FCALLS > 0 || FCALLS > neg_o_reds) { \
- FCALLS--; \
- Goto(dis_next); \
- } else { \
- goto context_switch; \
- } \
- } while (0) \
-
-#define DispatchMacroFun() \
- do { \
- BeamInstr dis_next; \
- dis_next = *I; \
- CHECK_ARGS(I); \
- if (FCALLS > 0 || FCALLS > neg_o_reds) { \
- FCALLS--; \
- Goto(dis_next); \
- } else { \
- goto context_switch_fun; \
- } \
- } while (0)
-
-#define DispatchMacrox() \
- do { \
- if (FCALLS > 0) { \
- BeamInstr dis_next; \
- SET_I(((Export *) Arg(0))->addressv[erts_active_code_ix()]); \
- dis_next = *I; \
- FCALLS--; \
- CHECK_ARGS(I); \
- Goto(dis_next); \
- } else if (ERTS_PROC_GET_SAVED_CALLS_BUF(c_p) \
- && FCALLS > neg_o_reds) { \
- goto save_calls1; \
- } else { \
- SET_I(((Export *) Arg(0))->addressv[erts_active_code_ix()]); \
- CHECK_ARGS(I); \
- goto context_switch; \
- } \
- } while (0)
-
-#ifdef DEBUG
-/*
- * To simplify breakpoint setting, put the code in one place only and jump to it.
- */
-# define Dispatch() goto do_dispatch
-# define Dispatchx() goto do_dispatchx
-# define Dispatchfun() goto do_dispatchfun
-#else
-/*
- * Inline for speed.
- */
-# define Dispatch() DispatchMacro()
-# define Dispatchx() DispatchMacrox()
-# define Dispatchfun() DispatchMacroFun()
-#endif
-
#define Arg(N) I[(N)+1]
#define GetSource(raw, dst) \
@@ -352,19 +277,6 @@ do { \
} \
} while(0)
-#define DispatchReturn \
-do { \
- if (FCALLS > 0 || FCALLS > neg_o_reds) { \
- FCALLS--; \
- Goto(*I); \
- } \
- else { \
- c_p->current = NULL; \
- c_p->arity = 1; \
- goto context_switch3; \
- } \
-} while (0)
-
#ifdef DEBUG
/* Better static type testing by the C compiler */
# define BEAM_IS_TUPLE(Src) is_tuple(Src)
@@ -521,10 +433,10 @@ init_emulator(void)
} \
} while(0)
-#define DTRACE_RETURN_FROM_PC(p) \
+#define DTRACE_RETURN_FROM_PC(p, i) \
do { \
ErtsCodeMFA* cmfa; \
- if (DTRACE_ENABLED(function_return) && (cmfa = find_function_from_pc((p)->cp))) { \
+ if (DTRACE_ENABLED(function_return) && (cmfa = find_function_from_pc(i))) { \
DTRACE_RETURN((p), cmfa); \
} \
} while(0)
@@ -534,7 +446,7 @@ init_emulator(void)
#define DTRACE_GLOBAL_CALL(p, mfa) do {} while (0)
#define DTRACE_GLOBAL_CALL_FROM_EXPORT(p, e) do {} while (0)
#define DTRACE_RETURN(p, mfa) do {} while (0)
-#define DTRACE_RETURN_FROM_PC(p) do {} while (0)
+#define DTRACE_RETURN_FROM_PC(p, i) do {} while (0)
#define DTRACE_BIF_ENTRY(p, mfa) do {} while (0)
#define DTRACE_BIF_RETURN(p, mfa) do {} while (0)
#define DTRACE_NIF_ENTRY(p, mfa) do {} while (0)
@@ -772,27 +684,9 @@ void process_main(Eterm * x_reg_array, FloatDef* f_reg_array)
#endif
#include "beam_hot.h"
-
-#ifdef DEBUG
- /*
- * Set a breakpoint here to get control just after a call instruction.
- * I points to the first instruction in the called function.
- *
- * In gdb, use 'call dis(I-5, 1)' to show the name of the function.
- */
- do_dispatch:
- DispatchMacro();
-
- do_dispatchx:
- DispatchMacrox();
-
- do_dispatchfun:
- DispatchMacroFun();
-
-#endif
-
/*
- * Jumped to from the Dispatch() macro when the reductions are used up.
+ * The labels are jumped to from the $DISPATCH() macros when the reductions
+ * are used up.
*
* Since the I register points just beyond the FuncBegin instruction, we
* can get the module, function, and arity for the function being
@@ -986,18 +880,46 @@ void process_main(Eterm * x_reg_array, FloatDef* f_reg_array)
}
#endif
return; /* Never executed */
+}
- save_calls1:
- {
- BeamInstr dis_next;
+/*
+ * Enter all BIFs into the export table.
+ *
+ * Note that they will all call the error_handler until their modules have been
+ * loaded, which may prevent the system from booting if BIFs from non-preloaded
+ * modules are apply/3'd while loading code. Ordinary BIF calls will work fine
+ * however since they won't go through export entries.
+ */
+static void install_bifs(void) {
+ int i;
+
+ for (i = 0; i < BIF_SIZE; i++) {
+ BifEntry *entry;
+ Export *ep;
+ int j;
- save_calls(c_p, (Export *) Arg(0));
+ entry = &bif_table[i];
- SET_I(((Export *) Arg(0))->addressv[erts_active_code_ix()]);
+ ep = erts_export_put(entry->module, entry->name, entry->arity);
- dis_next = *I;
- FCALLS--;
- Goto(dis_next);
+ ep->info.op = BeamOpCodeAddr(op_i_func_info_IaaI);
+ ep->info.mfa.module = entry->module;
+ ep->info.mfa.function = entry->name;
+ ep->info.mfa.arity = entry->arity;
+ ep->bif_number = i;
+
+ memset(&ep->trampoline, 0, sizeof(ep->trampoline));
+ ep->trampoline.op = BeamOpCodeAddr(op_call_error_handler);
+
+ for (j = 0; j < ERTS_NUM_CODE_IX; j++) {
+ ep->addressv[j] = ep->trampoline.raw;
+ }
+
+ /* Set up a hidden export entry so we can trap to this BIF without
+ * it being seen when tracing. */
+ erts_init_trap_export(&bif_trap_export[i],
+ entry->module, entry->name, entry->arity,
+ entry->f);
}
}
@@ -1008,43 +930,30 @@ void process_main(Eterm * x_reg_array, FloatDef* f_reg_array)
static void
init_emulator_finish(void)
{
- int i;
- Export* ep;
-
#if defined(ARCH_64) && defined(CODE_MODEL_SMALL)
- for (i = 0; i < NUMBER_OF_OPCODES; i++) {
- BeamInstr instr = BeamOpCodeAddr(i);
- if (instr >= (1ull << 32)) {
- erts_exit(ERTS_ERROR_EXIT,
- "This run-time was supposed be compiled with all code below 2Gb,\n"
- "but the instruction '%s' is located at %016lx.\n",
- opc[i].name, instr);
- }
- }
+ int i;
+
+ for (i = 0; i < NUMBER_OF_OPCODES; i++) {
+ BeamInstr instr = BeamOpCodeAddr(i);
+ if (instr >= (1ull << 32)) {
+ erts_exit(ERTS_ERROR_EXIT,
+ "This run-time was supposed be compiled with all code below 2Gb,\n"
+ "but the instruction '%s' is located at %016lx.\n",
+ opc[i].name, instr);
+ }
+ }
#endif
- beam_apply[0] = BeamOpCodeAddr(op_i_apply);
- beam_apply[1] = BeamOpCodeAddr(op_normal_exit);
- beam_exit[0] = BeamOpCodeAddr(op_error_action_code);
- beam_continue_exit[0] = BeamOpCodeAddr(op_continue_exit);
- beam_return_to_trace[0] = BeamOpCodeAddr(op_i_return_to_trace);
- beam_return_trace[0] = BeamOpCodeAddr(op_return_trace);
- beam_exception_trace[0] = BeamOpCodeAddr(op_return_trace); /* UGLY */
- beam_return_time_trace[0] = BeamOpCodeAddr(op_i_return_time_trace);
+ beam_apply[0] = BeamOpCodeAddr(op_i_apply);
+ beam_apply[1] = BeamOpCodeAddr(op_normal_exit);
+ beam_exit[0] = BeamOpCodeAddr(op_error_action_code);
+ beam_continue_exit[0] = BeamOpCodeAddr(op_continue_exit);
+ beam_return_to_trace[0] = BeamOpCodeAddr(op_i_return_to_trace);
+ beam_return_trace[0] = BeamOpCodeAddr(op_return_trace);
+ beam_exception_trace[0] = BeamOpCodeAddr(op_return_trace); /* UGLY */
+ beam_return_time_trace[0] = BeamOpCodeAddr(op_i_return_time_trace);
- /*
- * Enter all BIFs into the export table.
- */
- for (i = 0; i < BIF_SIZE; i++) {
- ep = erts_export_put(bif_table[i].module,
- bif_table[i].name,
- bif_table[i].arity);
- bif_export[i] = ep;
- ep->beam[0] = BeamOpCodeAddr(op_apply_bif);
- ep->beam[1] = (BeamInstr) bif_table[i].f;
- /* XXX: set func info for bifs */
- ep->info.op = BeamOpCodeAddr(op_i_func_info_IaaI);
- }
+ install_bifs();
}
/*
@@ -1257,10 +1166,10 @@ void erts_dirty_process_main(ErtsSchedulerData *esdp)
* I[2]: Pointer to erl_module_nif
* I[3]: Function pointer to dirty NIF
*
- * This layout is determined by the NifExport struct
+ * This layout is determined by the ErtsNativeFunc struct
*/
- ERTS_MSACC_SET_STATE_CACHED_M_X(ERTS_MSACC_STATE_NIF);
+ ERTS_MSACC_SET_STATE_CACHED_X(ERTS_MSACC_STATE_NIF);
codemfa = erts_code_to_codemfa(I);
@@ -1271,11 +1180,11 @@ void erts_dirty_process_main(ErtsSchedulerData *esdp)
ERTS_UNREQ_PROC_MAIN_LOCK(c_p);
ASSERT(!ERTS_PROC_IS_EXITING(c_p));
- if (BeamIsOpCode(*I, op_apply_bif)) {
+ if (BeamIsOpCode(*I, op_call_bif_W)) {
exiting = erts_call_dirty_bif(esdp, c_p, I, reg);
}
else {
- ASSERT(BeamIsOpCode(*I, op_call_nif));
+ ASSERT(BeamIsOpCode(*I, op_call_nif_WWW));
exiting = erts_call_dirty_nif(esdp, c_p, I, reg);
}
@@ -1284,7 +1193,7 @@ void erts_dirty_process_main(ErtsSchedulerData *esdp)
PROCESS_MAIN_CHK_LOCKS(c_p);
ERTS_REQ_PROC_MAIN_LOCK(c_p);
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
- ERTS_MSACC_SET_STATE_CACHED_M_X(ERTS_MSACC_STATE_EMULATOR);
+ ERTS_MSACC_SET_STATE_CACHED_X(ERTS_MSACC_STATE_EMULATOR);
if (exiting)
goto do_dirty_schedule;
ASSERT(!ERTS_PROC_IS_EXITING(c_p));
@@ -1303,7 +1212,7 @@ ubif2mfa(void* uf)
int i;
for (i = 0; erts_u_bifs[i].bif; i++) {
if (erts_u_bifs[i].bif == uf)
- return &bif_export[erts_u_bifs[i].exp_ix]->info.mfa;
+ return &bif_trap_export[erts_u_bifs[i].exp_ix].info.mfa;
}
erts_exit(ERTS_ERROR_EXIT, "bad u bif: %p\n", uf);
return NULL;
@@ -1344,6 +1253,33 @@ Eterm error_atom[NUMBER_EXIT_CODES] = {
am_badkey, /* 19 */
};
+/* Returns the return address at E[0] in printable form, skipping tracing in
+ * the same manner as gather_stacktrace.
+ *
+ * This is needed to generate correct stacktraces when throwing errors from
+ * instructions that return like an ordinary function, such as call_nif. */
+BeamInstr *erts_printable_return_address(Process* p, Eterm *E) {
+ Eterm *ptr = E;
+
+ ASSERT(is_CP(*ptr));
+
+ while (ptr < STACK_START(p)) {
+ BeamInstr *cp = cp_val(*ptr);
+
+ if (cp == beam_exception_trace || cp == beam_return_trace) {
+ ptr += 3;
+ } else if (cp == beam_return_time_trace) {
+ ptr += 2;
+ } else if (cp == beam_return_to_trace) {
+ ptr += 1;
+ } else {
+ return cp;
+ }
+ }
+
+ ERTS_ASSERT(!"No continuation pointer on stack");
+}
+
/*
* To fully understand the error handling, one must keep in mind that
* when an exception is thrown, the search for a handler can jump back
@@ -1373,14 +1309,14 @@ handle_error(Process* c_p, BeamInstr* pc, Eterm* reg, ErtsCodeMFA *bif_mfa)
ASSERT(c_p->freason != TRAP); /* Should have been handled earlier. */
- if (c_p->freason & EXF_RESTORE_NIF)
- erts_nif_export_restore_error(c_p, &pc, reg, &bif_mfa);
+ if (c_p->freason & EXF_RESTORE_NFUNC)
+ erts_nfunc_restore_error(c_p, &pc, reg, &bif_mfa);
#ifdef DEBUG
if (bif_mfa) {
- /* Verify that bif_mfa does not point into our nif export */
- NifExport *nep = ERTS_PROC_GET_NIF_TRAP_EXPORT(c_p);
- ASSERT(!nep || !ErtsInArea(bif_mfa, (char *)nep, sizeof(NifExport)));
+ /* Verify that bif_mfa does not point into our native function wrapper */
+ ErtsNativeFunc *nep = ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(c_p);
+ ASSERT(!nep || !ErtsInArea(bif_mfa, (char *)nep, sizeof(ErtsNativeFunc)));
}
#endif
@@ -1443,7 +1379,7 @@ handle_error(Process* c_p, BeamInstr* pc, Eterm* reg, ErtsCodeMFA *bif_mfa)
reg[2] = Value;
reg[3] = c_p->ftrace;
if ((new_pc = next_catch(c_p, reg))) {
- c_p->cp = 0; /* To avoid keeping stale references. */
+ c_p->stop[0] = NIL; /* To avoid keeping stale references. */
ERTS_RECV_MARK_CLEAR(c_p); /* No longer safe to use this position */
return new_pc;
}
@@ -1481,35 +1417,6 @@ next_catch(Process* c_p, Eterm *reg) {
return NULL;
}
- /*
- * Better safe than sorry here. In debug builds, produce a core
- * dump if the top of the stack doesn't point to a continuation
- * pointer. In other builds, ignore a non-CP at the top of stack.
- */
- ASSERT(is_CP(*ptr));
- if ((is_not_CP(*ptr) || (*cp_val(*ptr) != i_return_trace &&
- *cp_val(*ptr) != i_return_to_trace &&
- *cp_val(*ptr) != i_return_time_trace ))
- && c_p->cp) {
- /* Can not follow cp here - code may be unloaded */
- BeamInstr *cpp = c_p->cp;
- if (cpp == beam_exception_trace) {
- ErtsCodeMFA *mfa = (ErtsCodeMFA*)cp_val(ptr[0]);
- erts_trace_exception(c_p, mfa,
- reg[1], reg[2],
- ERTS_TRACER_FROM_ETERM(ptr+1));
- /* Skip return_trace parameters */
- ptr += 2;
- } else if (cpp == beam_return_trace) {
- /* Skip return_trace parameters */
- ptr += 2;
- } else if (cpp == beam_return_time_trace) {
- /* Skip return_trace parameters */
- ptr += 1;
- } else if (cpp == beam_return_to_trace) {
- have_return_to_trace = !0; /* Record next cp */
- }
- }
while (ptr < STACK_START(c_p)) {
if (is_catch(*ptr)) {
if (active_catches) goto found_catch;
@@ -1518,32 +1425,22 @@ next_catch(Process* c_p, Eterm *reg) {
else if (is_CP(*ptr)) {
prev = ptr;
if (*cp_val(*prev) == i_return_trace) {
- /* Skip stack frame variables */
- while (++ptr, ptr < STACK_START(c_p) && is_not_CP(*ptr)) {
- if (is_catch(*ptr) && active_catches) goto found_catch;
- }
if (cp_val(*prev) == beam_exception_trace) {
- ErtsCodeMFA *mfa = (ErtsCodeMFA*)cp_val(ptr[0]);
+ ErtsCodeMFA *mfa = (ErtsCodeMFA*)cp_val(ptr[1]);
erts_trace_exception(c_p, mfa,
reg[1], reg[2],
- ERTS_TRACER_FROM_ETERM(ptr+1));
+ ERTS_TRACER_FROM_ETERM(ptr+2));
}
- /* Skip return_trace parameters */
- ptr += 2;
+ /* Skip MFA, tracer, and CP. */
+ ptr += 3;
} else if (*cp_val(*prev) == i_return_to_trace) {
- /* Skip stack frame variables */
- while (++ptr, ptr < STACK_START(c_p) && is_not_CP(*ptr)) {
- if (is_catch(*ptr) && active_catches) goto found_catch;
- }
have_return_to_trace = !0; /* Record next cp */
return_to_trace_ptr = NULL;
- } else if (*cp_val(*prev) == i_return_time_trace) {
- /* Skip stack frame variables */
- while (++ptr, ptr < STACK_START(c_p) && is_not_CP(*ptr)) {
- if (is_catch(*ptr) && active_catches) goto found_catch;
- }
- /* Skip return_trace parameters */
+ /* Skip CP. */
ptr += 1;
+ } else if (*cp_val(*prev) == i_return_time_trace) {
+ /* Skip prev_info and CP. */
+ ptr += 2;
} else {
if (have_return_to_trace) {
/* Record this cp as possible return_to trace cp */
@@ -1583,6 +1480,8 @@ terminate_proc(Process* c_p, Eterm Value)
if (GET_EXC_CLASS(c_p->freason) == EXTAG_ERROR) {
Value = add_stacktrace(c_p, Value, c_p->ftrace);
}
+ c_p->ftrace = NIL;
+
/* EXF_LOG is a primary exception flag */
if (c_p->freason & EXF_LOG) {
int alive = erts_is_alive;
@@ -1664,6 +1563,54 @@ expand_error_value(Process* c_p, Uint freason, Eterm Value) {
return Value;
}
+
+static void
+gather_stacktrace(Process* p, struct StackTrace* s, int depth)
+{
+ BeamInstr *prev;
+ Eterm *ptr;
+
+ if (depth == 0) {
+ return;
+ }
+
+ prev = s->depth ? s->trace[s->depth - 1] : s->pc;
+ ptr = p->stop;
+
+ /*
+ * Traverse the stack backwards and add all unique continuation
+ * pointers to the buffer, up to the maximum stack trace size.
+ *
+ * Skip trace stack frames.
+ */
+
+ ASSERT(ptr >= STACK_TOP(p) && ptr <= STACK_START(p));
+
+ while (ptr < STACK_START(p) && depth > 0) {
+ if (is_CP(*ptr)) {
+ BeamInstr *cp = cp_val(*ptr);
+
+ if (cp == beam_exception_trace || cp == beam_return_trace) {
+ ptr += 3;
+ } else if (cp == beam_return_time_trace) {
+ ptr += 2;
+ } else if (cp == beam_return_to_trace) {
+ ptr += 1;
+ } else {
+ if (cp != prev) {
+ /* Record non-duplicates only */
+ prev = cp;
+ s->trace[s->depth++] = cp - 1;
+ depth--;
+ }
+ ptr++;
+ }
+ } else {
+ ptr++;
+ }
+ }
+}
+
/*
* Quick-saving the stack trace in an internal form on the heap. Note
* that c_p->ftrace will point to a cons cell which holds the given args
@@ -1750,11 +1697,6 @@ save_stacktrace(Process* c_p, BeamInstr* pc, Eterm* reg,
s->trace[s->depth++] = pc;
depth--;
}
- /* Save second stack entry if CP is valid and different from pc */
- if (depth > 0 && c_p->cp != 0 && c_p->cp != pc) {
- s->trace[s->depth++] = c_p->cp - 1;
- depth--;
- }
s->pc = NULL;
args = make_arglist(c_p, reg, bif_mfa->arity); /* Overwrite CAR(c_p->ftrace) */
} else {
@@ -1762,9 +1704,9 @@ save_stacktrace(Process* c_p, BeamInstr* pc, Eterm* reg,
non_bif_stacktrace:
s->current = c_p->current;
- /*
+ /*
* For a function_clause error, the arguments are in the beam
- * registers, c_p->cp is valid, and c_p->current is set.
+ * registers and c_p->current is set.
*/
if ( (GET_EXC_INDEX(s->freason)) ==
(GET_EXC_INDEX(EXC_FUNCTION_CLAUSE)) ) {
@@ -1772,18 +1714,8 @@ save_stacktrace(Process* c_p, BeamInstr* pc, Eterm* reg,
ASSERT(s->current);
a = s->current->arity;
args = make_arglist(c_p, reg, a); /* Overwrite CAR(c_p->ftrace) */
- /* Save first stack entry */
- ASSERT(c_p->cp);
- if (depth > 0) {
- s->trace[s->depth++] = c_p->cp - 1;
- depth--;
- }
s->pc = NULL; /* Ignore pc */
} else {
- if (depth > 0 && c_p->cp != 0 && c_p->cp != pc) {
- s->trace[s->depth++] = c_p->cp - 1;
- depth--;
- }
s->pc = pc;
}
}
@@ -1796,80 +1728,13 @@ save_stacktrace(Process* c_p, BeamInstr* pc, Eterm* reg,
}
/* Save the actual stack trace */
- erts_save_stacktrace(c_p, s, depth);
+ gather_stacktrace(c_p, s, depth);
}
void
erts_save_stacktrace(Process* p, struct StackTrace* s, int depth)
{
- if (depth > 0) {
- Eterm *ptr;
- BeamInstr *prev = s->depth ? s->trace[s->depth-1] : NULL;
- BeamInstr i_return_trace = beam_return_trace[0];
- BeamInstr i_return_to_trace = beam_return_to_trace[0];
-
- /*
- * Traverse the stack backwards and add all unique continuation
- * pointers to the buffer, up to the maximum stack trace size.
- *
- * Skip trace stack frames.
- */
- ptr = p->stop;
- if (ptr < STACK_START(p) &&
- (is_not_CP(*ptr)|| (*cp_val(*ptr) != i_return_trace &&
- *cp_val(*ptr) != i_return_to_trace)) &&
- p->cp) {
- /* Cannot follow cp here - code may be unloaded */
- BeamInstr *cpp = p->cp;
- int trace_cp;
- if (cpp == beam_exception_trace || cpp == beam_return_trace) {
- /* Skip return_trace parameters */
- ptr += 2;
- trace_cp = 1;
- } else if (cpp == beam_return_to_trace) {
- /* Skip return_to_trace parameters */
- ptr += 1;
- trace_cp = 1;
- }
- else {
- trace_cp = 0;
- }
- if (trace_cp && s->pc == cpp) {
- /*
- * If process 'cp' points to a return/exception trace
- * instruction and 'cp' has been saved as 'pc' in
- * stacktrace, we need to update 'pc' in stacktrace
- * with the actual 'cp' located on the top of the
- * stack; otherwise, we will lose the top stackframe
- * when building the stack trace.
- */
- ASSERT(is_CP(p->stop[0]));
- s->pc = cp_val(p->stop[0]);
- }
- }
- while (ptr < STACK_START(p) && depth > 0) {
- if (is_CP(*ptr)) {
- if (*cp_val(*ptr) == i_return_trace) {
- /* Skip stack frame variables */
- do ++ptr; while (is_not_CP(*ptr));
- /* Skip return_trace parameters */
- ptr += 2;
- } else if (*cp_val(*ptr) == i_return_to_trace) {
- /* Skip stack frame variables */
- do ++ptr; while (is_not_CP(*ptr));
- } else {
- BeamInstr *cp = cp_val(*ptr);
- if (cp != prev) {
- /* Record non-duplicates only */
- prev = cp;
- s->trace[s->depth++] = cp - 1;
- depth--;
- }
- ptr++;
- }
- } else ptr++;
- }
- }
+ gather_stacktrace(p, s, depth);
}
/*
@@ -2128,95 +1993,66 @@ apply_bif_error_adjustment(Process *p, Export *ep,
Eterm *reg, Uint arity,
BeamInstr *I, Uint stack_offset)
{
+ int apply_only;
+ Uint need;
+
+ need = stack_offset /* bytes */ / sizeof(Eterm);
+ apply_only = stack_offset == 0;
+
/*
* I is only set when the apply is a tail call, i.e.,
* from the instructions i_apply_only, i_apply_last_P,
* and apply_last_IP.
*/
- if (I
- && BeamIsOpCode(ep->beam[0], op_apply_bif)
- && (ep == bif_export[BIF_error_1]
- || ep == bif_export[BIF_error_2]
- || ep == bif_export[BIF_exit_1]
- || ep == bif_export[BIF_throw_1])) {
- /*
- * We are about to tail apply one of the BIFs
- * erlang:error/1, erlang:error/2, erlang:exit/1,
- * or erlang:throw/1. Error handling of these BIFs is
- * special!
- *
- * We need 'p->cp' to point into the calling
- * function when handling the error after the BIF has
- * been applied. This in order to get the topmost
- * stackframe correct. Without the following adjustment,
- * 'p->cp' will point into the function that called
- * current function when handling the error. We add a
- * dummy stackframe in order to achieve this.
- *
- * Note that these BIFs unconditionally will cause
- * an exception to be raised. That is, our modifications
- * of 'p->cp' as well as the stack will be corrected by
- * the error handling code.
- *
- * If we find an exception/return-to trace continuation
- * pointer as the topmost continuation pointer, we do not
- * need to do anything since the information already will
- * be available for generation of the stacktrace.
- */
- int apply_only = stack_offset == 0;
- BeamInstr *cpp;
+ if (!(I && (ep->bif_number == BIF_error_1 ||
+ ep->bif_number == BIF_error_2 ||
+ ep->bif_number == BIF_exit_1 ||
+ ep->bif_number == BIF_throw_1))) {
+ return;
+ }
- if (apply_only) {
- ASSERT(p->cp != NULL);
- cpp = p->cp;
- }
- else {
- ASSERT(is_CP(p->stop[0]));
- cpp = cp_val(p->stop[0]);
- }
+ /*
+ * We are about to tail apply one of the BIFs erlang:error/1,
+ * erlang:error/2, erlang:exit/1, or erlang:throw/1. Error handling of
+ * these BIFs is special!
+ *
+ * We need the topmost continuation pointer to point into the calling
+ * function when handling the error after the BIF has been applied. This in
+ * order to get the topmost stackframe correct.
+ *
+ * Note that these BIFs will unconditionally cause an exception to be
+ * raised. That is, our modifications of the stack will be corrected by the
+ * error handling code.
+ */
+ if (need == 0) {
+ need = 1; /* i_apply_only */
+ }
- if (cpp != beam_exception_trace
- && cpp != beam_return_trace
- && cpp != beam_return_to_trace) {
- Uint need = stack_offset /* bytes */ / sizeof(Eterm);
- if (need == 0)
- need = 1; /* i_apply_only */
- if (p->stop - p->htop < need)
- erts_garbage_collect(p, (int) need, reg, arity+1);
- p->stop -= need;
-
- if (apply_only) {
- /*
- * Called from the i_apply_only instruction.
- *
- * 'p->cp' contains continuation pointer pointing
- * into the function that called current function.
- * We push that continuation pointer onto the stack,
- * and set 'p->cp' to point into current function.
- */
+ if (p->stop - p->htop < need) {
+ erts_garbage_collect(p, (int) need, reg, arity+1);
+ }
- p->stop[0] = make_cp(p->cp);
- p->cp = I;
- }
- else {
- /*
- * Called from an i_apply_last_p, or apply_last_IP,
- * instruction.
- *
- * Calling instruction will after we return read
- * a continuation pointer from the stack and write
- * it to 'p->cp', and then remove the topmost
- * stackframe of size 'stack_offset'.
- *
- * We have sized the dummy-stackframe so that it
- * will be removed by the instruction we currently
- * are executing, and leave the stackframe that
- * normally would have been removed intact.
- *
- */
- p->stop[0] = make_cp(I);
- }
- }
+ if (apply_only) {
+ /*
+ * Called from the i_apply_only instruction.
+ *
+ * Push the continuation pointer for the current function to the stack.
+ */
+ p->stop -= need;
+ p->stop[0] = make_cp(I);
+ } else {
+ /*
+ * Called from an i_apply_last_* instruction.
+ *
+ * The calling instruction will deallocate a stack frame of size
+ * 'stack_offset'.
+ *
+ * Push the continuation pointer for the current function to the stack,
+ * and then add a dummy stackframe for the i_apply_last* instruction
+ * to discard.
+ */
+ p->stop[0] = make_cp(I);
+ p->stop -= need;
}
}
@@ -2437,10 +2273,10 @@ erts_hibernate(Process* c_p, Eterm* reg)
c_p->arg_reg[0] = module;
c_p->arg_reg[1] = function;
c_p->arg_reg[2] = args;
- c_p->stop = STACK_START(c_p);
+ c_p->stop = c_p->hend - 1; /* Keep first continuation pointer */
+ ASSERT(c_p->stop[0] == make_cp(beam_apply+1));
c_p->catches = 0;
c_p->i = beam_apply;
- c_p->cp = (BeamInstr *) beam_apply+1;
/*
* If there are no waiting messages, garbage collect and
@@ -2460,7 +2296,7 @@ erts_hibernate(Process* c_p, Eterm* reg)
ASSERT(!ERTS_PROC_IS_EXITING(c_p));
}
erts_proc_unlock(c_p, ERTS_PROC_LOCK_MSGQ|ERTS_PROC_LOCK_STATUS);
- c_p->current = &bif_export[BIF_hibernate_3]->info.mfa;
+ c_p->current = &bif_trap_export[BIF_hibernate_3].info.mfa;
c_p->flags |= F_HIBERNATE_SCHED; /* Needed also when woken! */
return 1;
}
@@ -3268,10 +3104,10 @@ erts_is_builtin(Eterm Mod, Eterm Name, int arity)
e.info.mfa.arity = arity;
if ((ep = export_get(&e)) == NULL) {
- return 0;
+ return 0;
}
- return ep->addressv[erts_active_code_ix()] == ep->beam &&
- BeamIsOpCode(ep->beam[0], op_apply_bif);
+
+ return ep->bif_number != -1;
}
diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c
index 4bfdb3b927..7d81ef268a 100644
--- a/erts/emulator/beam/beam_load.c
+++ b/erts/emulator/beam/beam_load.c
@@ -141,7 +141,7 @@ typedef struct {
* eventually patch with a pointer into
* the export entry.
*/
- BifFunction bf; /* Pointer to BIF function if BIF;
+ Export *bif; /* Pointer to export entry if BIF;
* NULL otherwise.
*/
} ImportEntry;
@@ -315,6 +315,7 @@ typedef struct LoaderState {
* (or 0 if there is no on_load function)
*/
int otp_20_or_higher; /* Compiled with OTP 20 or higher */
+ unsigned max_opcode; /* Highest opcode used in module */
/*
* Atom table.
@@ -844,17 +845,23 @@ erts_finish_loading(Binary* magic, Process* c_p,
if (ep == NULL || ep->info.mfa.module != module) {
continue;
}
- if (ep->addressv[code_ix] == ep->beam) {
- if (BeamIsOpCode(ep->beam[0], op_apply_bif)) {
- continue;
- } else if (BeamIsOpCode(ep->beam[0], op_i_generic_breakpoint)) {
+
+ DBG_CHECK_EXPORT(ep, code_ix);
+
+ if (ep->addressv[code_ix] == ep->trampoline.raw) {
+ if (BeamIsOpCode(ep->trampoline.op, op_i_generic_breakpoint)) {
ERTS_LC_ASSERT(erts_thr_progress_is_blocking());
ASSERT(mod_tab_p->curr.num_traced_exports > 0);
- erts_clear_export_break(mod_tab_p, &ep->info);
- ep->addressv[code_ix] = (BeamInstr *) ep->beam[1];
- ep->beam[1] = 0;
+
+ erts_clear_export_break(mod_tab_p, ep);
+
+ ep->addressv[code_ix] =
+ (BeamInstr*)ep->trampoline.breakpoint.address;
+ ep->trampoline.breakpoint.address = 0;
+
+ ASSERT(ep->addressv[code_ix] != ep->trampoline.raw);
}
- ASSERT(ep->beam[1] == 0);
+ ASSERT(ep->trampoline.breakpoint.address == 0);
}
}
ASSERT(mod_tab_p->curr.num_breakpoints == 0);
@@ -1470,15 +1477,14 @@ load_import_table(LoaderState* stp)
}
stp->import[i].arity = arity;
stp->import[i].patches = 0;
- stp->import[i].bf = NULL;
+ stp->import[i].bif = NULL;
/*
- * If the export entry refers to a BIF, get the pointer to
- * the BIF function.
+ * If the export entry refers to a BIF, save a pointer to the BIF entry.
*/
if ((e = erts_active_export_entry(mod, func, arity)) != NULL) {
- if (BeamIsOpCode(e->beam[0], op_apply_bif)) {
- stp->import[i].bf = (BifFunction) e->beam[1];
+ if (e->bif_number != -1) {
+ stp->import[i].bif = e;
if (func == am_load_nif && mod == am_erlang && arity == 2) {
stp->may_load_nif = 1;
}
@@ -1529,33 +1535,6 @@ read_export_table(LoaderState* stp)
LoadError2(stp, "export table entry %u: label %u not resolved", i, n);
}
stp->export[i].address = address = stp->codev + value;
-
- /*
- * Find out if there is a BIF with the same name.
- */
-
- if (!is_bif(stp->module, func, arity)) {
- continue;
- }
-
- /*
- * This is a stub for a BIF.
- *
- * It should not be exported, and the information in its
- * func_info instruction should be invalidated so that it
- * can be filtered out by module_info(functions) and by
- * any other functions that walk through all local functions.
- */
-
- if (stp->labels[n].num_patches > 0) {
- LoadError3(stp, "there are local calls to the stub for "
- "the BIF %T:%T/%d",
- stp->module, func, arity);
- }
- stp->export[i].address = NULL;
- address[-1] = 0;
- address[-2] = NIL;
- address[-3] = NIL;
}
return 1;
@@ -1563,31 +1542,33 @@ read_export_table(LoaderState* stp)
return 0;
}
-
static int
is_bif(Eterm mod, Eterm func, unsigned arity)
{
- Export* e = erts_active_export_entry(mod, func, arity);
- if (e == NULL) {
- return 0;
- }
- if (! BeamIsOpCode(e->beam[0], op_apply_bif)) {
- return 0;
- }
- if (mod == am_erlang && func == am_apply && arity == 3) {
- /*
- * erlang:apply/3 is a special case -- it is implemented
- * as an instruction and it is OK to redefine it.
- */
- return 0;
+ Export *e = erts_active_export_entry(mod, func, arity);
+
+ if (e != NULL) {
+ return e->bif_number != -1;
}
- return 1;
+
+ return 0;
}
static int
read_lambda_table(LoaderState* stp)
{
unsigned int i;
+ unsigned int otp_22_or_lower;
+
+ /*
+ * Determine whether this module was compiled with OTP 22 or lower
+ * by looking at the max opcode number. The compiler in OTP 23 will
+ * always set the max opcode to the opcode for `swap` (whether
+ * actually used or not) so that a module compiled for OTP 23
+ * cannot be loaded in earlier versions.
+ */
+
+ otp_22_or_lower = stp->max_opcode < genop_swap_2;
GetInt(stp, 4, stp->num_lambdas);
if (stp->num_lambdas > stp->lambdas_allocated) {
@@ -1619,6 +1600,29 @@ read_lambda_table(LoaderState* stp)
GetInt(stp, 4, Index);
GetInt(stp, 4, stp->lambdas[i].num_free);
GetInt(stp, 4, OldUniq);
+
+ /*
+ * Fun entries are now keyed by the explicit ("new") index in
+ * the fun entry. That allows multiple make_fun2 instructions
+ * to share the same fun entry (when the `fun F/A` syntax is
+ * used). Before OTP 23, fun entries were keyed by the old
+ * index, which is the order of the entries in the fun
+ * chunk. Each make_fun2 needed to refer to its own fun entry.
+ *
+ * Modules compiled before OTP 23 can safely be loaded if the
+ * old index and the new index are equal. That is true for all
+ * modules compiled with OTP R15 and later.
+ */
+ if (otp_22_or_lower && i != Index) {
+ /*
+ * Compiled with a compiler before OTP R15B. The new indices
+ * are not reliable, so it is not safe to load this module.
+ */
+ LoadError2(stp, "please re-compile this module with an "
+ ERLANG_OTP_RELEASE " compiler "
+ "(old-style fun with indices: %d/%d)",
+ i, Index);
+ }
fe = erts_put_fun_entry2(stp->module, OldUniq, i, stp->mod_md5,
Index, arity-stp->lambdas[i].num_free);
stp->lambdas[i].fe = fe;
@@ -1839,7 +1843,6 @@ read_code_header(LoaderState* stp)
{
unsigned head_size;
unsigned version;
- unsigned opcode_max;
int i;
/*
@@ -1871,8 +1874,8 @@ read_code_header(LoaderState* stp)
/*
* Verify the number of the highest opcode used.
*/
- GetInt(stp, 4, opcode_max);
- if (opcode_max > MAX_GENERIC_OPCODE) {
+ GetInt(stp, 4, stp->max_opcode);
+ if (stp->max_opcode > MAX_GENERIC_OPCODE) {
LoadError2(stp,
"This BEAM file was compiled for a later version"
" of the run-time system than " ERLANG_OTP_RELEASE ".\n"
@@ -1880,7 +1883,7 @@ read_code_header(LoaderState* stp)
ERLANG_OTP_RELEASE " compiler.\n"
" (Use of opcode %d; this emulator supports "
"only up to %d.)",
- opcode_max, MAX_GENERIC_OPCODE);
+ stp->max_opcode, MAX_GENERIC_OPCODE);
}
GetInt(stp, 4, stp->num_labels);
@@ -1919,8 +1922,6 @@ read_code_header(LoaderState* stp)
code = stp->codev = (BeamInstr*) &stp->hdr->functions; \
} \
} while (0)
-
-#define TermWords(t) (((t) / (sizeof(BeamInstr)/sizeof(Eterm))) + !!((t) % (sizeof(BeamInstr)/sizeof(Eterm))))
static void init_label(Label* lp)
{
@@ -2500,10 +2501,14 @@ load_code(LoaderState* stp)
if (i >= stp->num_imports) {
LoadError1(stp, "invalid import table index %d", i);
}
- if (stp->import[i].bf == NULL) {
+ if (stp->import[i].bif == NULL) {
LoadError1(stp, "not a BIF: import table index %d", i);
}
- code[ci++] = (BeamInstr) stp->import[i].bf;
+ {
+ int bif_index = stp->import[i].bif->bif_number;
+ BifEntry *bif_entry = &bif_table[bif_index];
+ code[ci++] = (BeamInstr) bif_entry->f;
+ }
break;
case 'P': /* Byte offset into tuple or stack */
case 'Q': /* Like 'P', but packable */
@@ -2711,36 +2716,30 @@ load_code(LoaderState* stp)
num_trailing_f = 0;
}
#endif
+ CodeNeed(1);
switch (tmp_op->a[arg].type) {
case TAG_i:
- CodeNeed(1);
code[ci++] = make_small(tmp_op->a[arg].val);
break;
case TAG_u:
case TAG_a:
case TAG_v:
- CodeNeed(1);
code[ci++] = tmp_op->a[arg].val;
break;
case TAG_f:
- CodeNeed(1);
register_label_patch(stp, tmp_op->a[arg].val, ci, -last_instr_start);
ci++;
break;
case TAG_x:
- CodeNeed(1);
code[ci++] = make_loader_x_reg(tmp_op->a[arg].val);
break;
case TAG_y:
- CodeNeed(1);
code[ci++] = make_loader_y_reg(tmp_op->a[arg].val);
break;
case TAG_n:
- CodeNeed(1);
code[ci++] = NIL;
break;
case TAG_q:
- CodeNeed(1);
new_literal_patch(stp, ci);
code[ci++] = tmp_op->a[arg].val;
break;
@@ -2811,18 +2810,43 @@ load_code(LoaderState* stp)
switch (stp->specific_op) {
case op_i_func_info_IaaI:
{
+ int padding_required;
Sint offset;
+
if (function_number >= stp->num_functions) {
LoadError1(stp, "too many functions in module (header said %u)",
stp->num_functions);
}
- if (stp->may_load_nif) {
+ /* Native function calls may be larger than their stubs, so
+ * we'll need to make sure any potentially-native function stub
+ * is padded with enough room.
+ *
+ * Note that the padding is applied for the previous function,
+ * not the current one, so we check whether the old F/A is
+ * a BIF. */
+ padding_required = last_func_start && (stp->may_load_nif ||
+ is_bif(stp->module, stp->function, stp->arity));
+
+ /*
+ * Save context for error messages.
+ */
+ stp->function = code[ci-2];
+ stp->arity = code[ci-1];
+
+ /*
+ * Save current offset of into the line instruction array.
+ */
+ if (stp->func_line) {
+ stp->func_line[function_number] = stp->current_li;
+ }
+
+ if (padding_required) {
const int finfo_ix = ci - FUNC_INFO_SZ;
- if (finfo_ix - last_func_start < BEAM_NIF_MIN_FUNC_SZ && last_func_start) {
+ if (finfo_ix - last_func_start < BEAM_NATIVE_MIN_FUNC_SZ) {
/* Must make room for call_nif op */
- int pad = BEAM_NIF_MIN_FUNC_SZ - (finfo_ix - last_func_start);
- ASSERT(pad > 0 && pad < BEAM_NIF_MIN_FUNC_SZ);
+ int pad = BEAM_NATIVE_MIN_FUNC_SZ - (finfo_ix - last_func_start);
+ ASSERT(pad > 0 && pad < BEAM_NATIVE_MIN_FUNC_SZ);
CodeNeed(pad);
sys_memmove(&code[finfo_ix+pad], &code[finfo_ix],
FUNC_INFO_SZ*sizeof(BeamInstr));
@@ -2833,20 +2857,6 @@ load_code(LoaderState* stp)
}
last_func_start = ci;
- /*
- * Save current offset of into the line instruction array.
- */
-
- if (stp->func_line) {
- stp->func_line[function_number] = stp->current_li;
- }
-
- /*
- * Save context for error messages.
- */
- stp->function = code[ci-2];
- stp->arity = code[ci-1];
-
/* When this assert is triggered, it is normally a sign that
the size of the ops.tab i_func_info instruction is not
the same as FUNC_INFO_SZ */
@@ -2876,7 +2886,6 @@ load_code(LoaderState* stp)
case op_i_bs_match_string_yfWW:
new_string_patch(stp, ci-1);
break;
-
case op_catch_yf:
/* code[ci-3] &&lb_catch_yf
* code[ci-2] y-register offset in E
@@ -2977,6 +2986,7 @@ load_code(LoaderState* stp)
#define succ3(St, X, Y) ((X).type == (Y).type && (X).val + 3 == (Y).val)
#define succ4(St, X, Y) ((X).type == (Y).type && (X).val + 4 == (Y).val)
+#define offset(St, X, Y, Offset) ((X).type == (Y).type && (X).val + Offset == (Y).val)
#ifdef NO_FPE_SIGNALS
#define no_fpe_signals(St) 1
@@ -3131,27 +3141,6 @@ mixed_types(LoaderState* stp, GenOpArg Size, GenOpArg* Rest)
return 0;
}
-static int
-is_killed_apply(LoaderState* stp, GenOpArg Reg, GenOpArg Live)
-{
- return Reg.type == TAG_x && Live.type == TAG_u &&
- Live.val+2 <= Reg.val;
-}
-
-static int
-is_killed(LoaderState* stp, GenOpArg Reg, GenOpArg Live)
-{
- return Reg.type == TAG_x && Live.type == TAG_u &&
- Live.val <= Reg.val;
-}
-
-static int
-is_killed_by_call_fun(LoaderState* stp, GenOpArg Reg, GenOpArg Live)
-{
- return Reg.type == TAG_x && Live.type == TAG_u &&
- Live.val+1 <= Reg.val;
-}
-
/*
* Test whether register Reg is killed by make_fun instruction that
* creates the fun given by index idx.
@@ -3171,14 +3160,23 @@ is_killed_by_make_fun(LoaderState* stp, GenOpArg Reg, GenOpArg idx)
}
}
-/*
- * Test whether register Reg is killed by the send instruction that follows.
- */
-
+/* Test whether Bif is "heavy" and should always go through its export entry */
static int
-is_killed_by_send(LoaderState* stp, GenOpArg Reg)
+is_heavy_bif(LoaderState* stp, GenOpArg Bif)
{
- return Reg.type == TAG_x && 2 <= Reg.val;
+ Export *ep;
+
+ if (Bif.type != TAG_u || Bif.val >= stp->num_imports) {
+ return 0;
+ }
+
+ ep = stp->import[Bif.val].bif;
+
+ if (ep) {
+ return bif_table[ep->bif_number].kind == BIF_KIND_HEAVY;
+ }
+
+ return 0;
}
/*
@@ -3324,8 +3322,8 @@ gen_get_integer2(LoaderState* stp, GenOpArg Fail, GenOpArg Ms, GenOpArg Live,
}
goto generic;
}
- } else {
- GENOP_NAME_ARITY(op, i_bs_get_integer, 6);
+ } else if (Size.type == TAG_x || Size.type == TAG_y) {
+ GENOP_NAME_ARITY(op, i_bs_get_integer, 6);
op->a[0] = Ms;
op->a[1] = Fail;
op->a[2] = Live;
@@ -3335,6 +3333,9 @@ gen_get_integer2(LoaderState* stp, GenOpArg Fail, GenOpArg Ms, GenOpArg Live,
op->a[5] = Dst;
op->next = NULL;
return op;
+ } else {
+ /* Invalid literal size. */
+ goto error;
}
op->next = NULL;
return op;
@@ -3391,7 +3392,7 @@ gen_get_binary2(LoaderState* stp, GenOpArg Fail, GenOpArg Ms, GenOpArg Live,
op->a[4] = Flags;
op->a[5] = Dst;
}
- } else {
+ } else if (Size.type == TAG_x || Size.type == TAG_y) {
GENOP_NAME_ARITY(op, i_bs_get_binary2, 6);
op->a[0] = Ms;
op->a[1] = Fail;
@@ -3400,6 +3401,9 @@ gen_get_binary2(LoaderState* stp, GenOpArg Fail, GenOpArg Ms, GenOpArg Live,
op->a[4].type = TAG_u;
op->a[4].val = (Unit.val << 3) | Flags.val;
op->a[5] = Dst;
+ } else {
+ /* Invalid literal size. */
+ goto error;
}
op->next = NULL;
return op;
@@ -3636,12 +3640,20 @@ gen_skip_bits2(LoaderState* stp, GenOpArg Fail, GenOpArg Ms,
goto error;
}
}
- } else {
+ } else if (Size.type == TAG_x || Size.type == TAG_y) {
GENOP_NAME_ARITY(op, i_bs_skip_bits2, 4);
op->a[0] = Ms;
op->a[1] = Size;
op->a[2] = Fail;
op->a[3] = Unit;
+ } else {
+ /*
+ * Invalid literal size. Can only happen if compiler
+ * optimizations are selectively disabled. For example,
+ * at the time of writing, [no_copt, no_type_opt] will allow
+ * skip instructions with invalid sizes to slip through.
+ */
+ goto error;
}
op->next = NULL;
return op;
@@ -5208,27 +5220,52 @@ final_touch(LoaderState* stp, struct erl_module_instance* inst_p)
*/
for (i = 0; i < stp->num_exps; i++) {
- Export* ep;
- BeamInstr* address = stp->export[i].address;
+ Export* ep;
+ BeamInstr* address = stp->export[i].address;
- if (address == NULL) {
- /* Skip stub for a BIF */
- continue;
- }
- ep = erts_export_put(stp->module, stp->export[i].function,
- stp->export[i].arity);
- if (on_load) {
- /*
- * on_load: Don't make any of the exported functions
- * callable yet. Keep any function in the current
- * code callable.
- */
- ep->beam[1] = (BeamInstr) address;
- }
- else
+ ep = erts_export_put(stp->module,
+ stp->export[i].function,
+ stp->export[i].arity);
+
+ /* Fill in BIF stubs with a proper call to said BIF. */
+ if (ep->bif_number != -1) {
+ erts_write_bif_wrapper(ep, address);
+ }
+
+ if (on_load) {
+ /*
+ * on_load: Don't make any of the exported functions
+ * callable yet. Keep any function in the current
+ * code callable.
+ */
+ ep->trampoline.not_loaded.deferred = (BeamInstr) address;
+ } else {
ep->addressv[erts_staging_code_ix()] = address;
+ }
}
+#ifdef DEBUG
+ /* Ensure that we've loaded stubs for all BIFs in this module. */
+ for (i = 0; i < BIF_SIZE; i++) {
+ BifEntry *entry = &bif_table[i];
+
+ if (stp->module == entry->module) {
+ Export *ep = erts_export_put(entry->module,
+ entry->name,
+ entry->arity);
+ BeamInstr *addr = ep->addressv[erts_staging_code_ix()];
+
+ if (!ErtsInArea(addr, stp->codev, stp->ci * sizeof(BeamInstr))) {
+ erts_exit(ERTS_ABORT_EXIT,
+ "Module %T doesn't export BIF %T/%i\n",
+ entry->module,
+ entry->name,
+ entry->arity);
+ }
+ }
+ }
+#endif
+
/*
* Import functions and patch all callers.
*/
@@ -5333,6 +5370,16 @@ transform_engine(LoaderState* st)
if (((1 << instr->a[ap].type) & mask) == 0)
goto restart;
break;
+#if defined(TOP_is_type_next_arg)
+ case TOP_is_type_next_arg:
+ mask = *pc++;
+ ASSERT(ap < instr->arity);
+ ASSERT(instr->a[ap].type < BEAM_NUM_TAGS);
+ if (((1 << instr->a[ap].type) & mask) == 0)
+ goto restart;
+ ap++;
+ break;
+#endif
case TOP_pred:
i = *pc++;
switch (i) {
@@ -5362,6 +5409,18 @@ transform_engine(LoaderState* st)
if (*pc++ != instr->a[ap].val)
goto restart;
break;
+#if defined(TOP_is_type_eq_next_arg)
+ case TOP_is_type_eq_next_arg:
+ mask = *pc++;
+ ASSERT(ap < instr->arity);
+ ASSERT(instr->a[ap].type < BEAM_NUM_TAGS);
+ if (((1 << instr->a[ap].type) & mask) == 0)
+ goto restart;
+ if (*pc++ != instr->a[ap].val)
+ goto restart;
+ ap++;
+ break;
+#endif
case TOP_is_same_var:
ASSERT(ap < instr->arity);
i = *pc++;
@@ -5400,15 +5459,16 @@ transform_engine(LoaderState* st)
i = instr->a[ap].val;
ASSERT(i < st->num_imports);
- if (i >= st->num_imports || st->import[i].bf == NULL)
- goto restart;
- if (bif_number != -1 &&
- bif_export[bif_number]->beam[1] != (BeamInstr) st->import[i].bf) {
+ if (i >= st->num_imports || st->import[i].bif == NULL)
goto restart;
- }
+ if (bif_number != -1) {
+ Export *bif = st->import[i].bif;
+ if (bif->bif_number != bif_number) {
+ goto restart;
+ }
+ }
}
break;
-
#endif
#if defined(TOP_is_not_bif)
case TOP_is_not_bif:
@@ -5438,7 +5498,7 @@ transform_engine(LoaderState* st)
* they are special.
*/
if (i < st->num_imports) {
- if (st->import[i].bf != NULL ||
+ if (st->import[i].bif != NULL ||
(st->import[i].module == am_erlang &&
st->import[i].function == am_apply &&
(st->import[i].arity == 2 || st->import[i].arity == 3))) {
@@ -5478,7 +5538,42 @@ transform_engine(LoaderState* st)
var[i].val = instr->a[ap].val;
ap++;
break;
-
+#if defined(TOP_is_type_set_var_next_arg)
+ case TOP_is_type_set_var_next_arg:
+ mask = pc[0];
+ i = pc[1];
+ ASSERT(i < TE_MAX_VARS);
+ ASSERT(ap < instr->arity);
+ ASSERT(instr->a[ap].type < BEAM_NUM_TAGS);
+ if (((1 << instr->a[ap].type) & mask) == 0)
+ goto restart;
+ ASSERT(i < TE_MAX_VARS);
+ var[i] = instr->a[ap];
+ ap++;
+ pc += 2;
+ break;
+#endif
+#if defined(TOP_is_type_eq_set_var_next_arg)
+ case TOP_is_type_eq_set_var_next_arg:
+ {
+ Eterm val;
+ mask = pc[0];
+ val = pc[1];
+ i = pc[2];
+ ASSERT(i < TE_MAX_VARS);
+ ASSERT(ap < instr->arity);
+ ASSERT(instr->a[ap].type < BEAM_NUM_TAGS);
+ if (((1 << instr->a[ap].type) & mask) == 0)
+ goto restart;
+ if (val != instr->a[ap].val)
+ goto restart;
+ ASSERT(i < TE_MAX_VARS);
+ var[i] = instr->a[ap];
+ ap++;
+ pc += 3;
+ }
+ break;
+#endif
#if defined(TOP_rest_args)
case TOP_rest_args:
{
@@ -5494,19 +5589,27 @@ transform_engine(LoaderState* st)
case TOP_commit:
instr = instr->next; /* The next_instr was optimized away. */
keep = instr;
- st->genop = instr;
-#ifdef DEBUG
- instr = 0;
-#endif
break;
+#if defined(TOP_commit_new_instr)
+ case TOP_commit_new_instr:
+ /*
+ * Reuse the last instruction on the left side instead of
+ * allocating a new instruction. Note that this is not
+ * safe if TOP_rest_args has been executed; therefore,
+ * this combined instruction is never used when that is
+ * the case.
+ */
+ ASSERT(instr->a == instr->def_args);
+ keep = instr;
+ instr->op = op = *pc++;
+ instr->arity = gen_opc[op].arity;
+ ap = 0;
+ break;
+#endif
#if defined(TOP_keep)
case TOP_keep:
/* Keep the current instruction unchanged. */
keep = instr;
- st->genop = instr;
-#ifdef DEBUG
- instr = 0;
-#endif
break;
#endif
#if defined(TOP_call_end)
@@ -5535,11 +5638,12 @@ transform_engine(LoaderState* st)
keep = instr->next; /* The next_instr was optimized away. */
*lastp = keep;
- st->genop = new_instr;
+ instr = new_instr;
}
/* FALLTHROUGH */
#endif
case TOP_end:
+ st->genop = instr;
while (first != keep) {
GenOp* next = first->next;
FREE_GENOP(st, first);
@@ -5550,28 +5654,28 @@ transform_engine(LoaderState* st)
/*
* Note that the instructions are generated in reverse order.
*/
- NEW_GENOP(st, instr);
- instr->next = st->genop;
- st->genop = instr;
- instr->op = op = *pc++;
- instr->arity = gen_opc[op].arity;
- ap = 0;
- break;
+ {
+ GenOp* new_instr;
+ NEW_GENOP(st, new_instr);
+ new_instr->next = instr;
+ instr = new_instr;
+ instr->op = op = *pc++;
+ instr->arity = gen_opc[op].arity;
+ ap = 0;
+ }
+ break;
#ifdef TOP_rename
case TOP_rename:
instr->op = op = *pc++;
instr->arity = gen_opc[op].arity;
return TE_OK;
#endif
- case TOP_store_type:
- i = *pc++;
- instr->a[ap].type = i;
- instr->a[ap].val = 0;
- break;
- case TOP_store_val:
- i = *pc++;
- instr->a[ap].val = i;
- break;
+ case TOP_store_val_next_arg:
+ instr->a[ap].type = pc[0];
+ instr->a[ap].val = pc[1];
+ ap++;
+ pc += 2;
+ break;
case TOP_store_var_next_arg:
i = *pc++;
ASSERT(i < TE_MAX_VARS);
@@ -5599,6 +5703,23 @@ transform_engine(LoaderState* st)
break;
case TOP_fail:
return TE_FAIL;
+#if defined(TOP_skip_unless)
+ case TOP_skip_unless:
+ /*
+ * Note that the caller of transform_engine() guarantees that
+ * there is always a second instruction available.
+ */
+ ASSERT(instr);
+ if (instr->next->op != pc[0]) {
+ /* The second instruction is wrong. Skip ahead. */
+ pc += pc[1] + 2;
+ ASSERT(*pc < NUM_TOPS); /* Valid instruction? */
+ } else {
+ /* Correct second instruction. */
+ pc += 2;
+ }
+ break;
+#endif
default:
ASSERT(0);
}
@@ -6283,12 +6404,12 @@ exported_from_module(Process* p, /* Process whose heap to use. */
if (ep->info.mfa.module == mod) {
Eterm tuple;
-
- if (ep->addressv[code_ix] == ep->beam &&
- BeamIsOpCode(ep->beam[0], op_call_error_handler)) {
- /* There is a call to the function, but it does not exist. */
- continue;
- }
+
+ if (ep->addressv[code_ix] == ep->trampoline.raw &&
+ BeamIsOpCode(ep->trampoline.op, op_call_error_handler)) {
+ /* There is a call to the function, but it does not exist. */
+ continue;
+ }
if (hp == hend) {
int need = 10 * 5;
diff --git a/erts/emulator/beam/beam_load.h b/erts/emulator/beam/beam_load.h
index 156c3c45e2..e7127c5b08 100644
--- a/erts/emulator/beam/beam_load.h
+++ b/erts/emulator/beam/beam_load.h
@@ -106,7 +106,7 @@ typedef struct beam_code_header {
}BeamCodeHeader;
-# define BEAM_NIF_MIN_FUNC_SZ 4
+# define BEAM_NATIVE_MIN_FUNC_SZ 4
void erts_release_literal_area(struct ErtsLiteralArea_* literal_area);
int erts_is_module_native(BeamCodeHeader* code);
diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c
index c373fab934..4e71600fa8 100644
--- a/erts/emulator/beam/bif.c
+++ b/erts/emulator/beam/bif.c
@@ -77,6 +77,8 @@ BIF_RETTYPE spawn_3(BIF_ALIST_3)
Eterm pid;
so.flags = erts_default_spo_flags;
+ so.opts = NIL;
+ so.tag = am_spawn_reply;
pid = erl_create_process(BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3, &so);
if (is_non_value(pid)) {
BIF_ERROR(BIF_P, so.error_code);
@@ -104,8 +106,7 @@ BIF_RETTYPE link_1(BIF_ALIST_1)
if (is_internal_pid(BIF_ARG_1)) {
int created;
- ErtsLinkData *ldp;
- ErtsLink *lnk;
+ ErtsLink *lnk, *rlnk;
if (BIF_P->common.id == BIF_ARG_1)
BIF_RET(am_true);
@@ -113,56 +114,61 @@ BIF_RETTYPE link_1(BIF_ALIST_1)
if (!erts_proc_lookup(BIF_ARG_1))
goto res_no_proc;
- lnk = erts_link_tree_lookup_create(&ERTS_P_LINKS(BIF_P),
- &created,
- ERTS_LNK_TYPE_PROC,
- BIF_P->common.id,
- BIF_ARG_1);
- if (!created)
- BIF_RET(am_true);
+ lnk = erts_link_internal_tree_lookup_create(&ERTS_P_LINKS(BIF_P),
+ &created,
+ ERTS_LNK_TYPE_PROC,
+ BIF_ARG_1);
+ if (!created) {
+ ErtsILink *ilnk = (ErtsILink *) lnk;
+ if (!ilnk->unlinking)
+ BIF_RET(am_true);
+ ilnk->unlinking = 0;
+ }
- ldp = erts_link_to_data(lnk);
-
+ rlnk = erts_link_internal_create(ERTS_LNK_TYPE_PROC, BIF_P->common.id);
- if (erts_proc_sig_send_link(BIF_P, BIF_ARG_1, &ldp->b))
+ if (erts_proc_sig_send_link(BIF_P, BIF_ARG_1, rlnk))
BIF_RET(am_true);
erts_link_tree_delete(&ERTS_P_LINKS(BIF_P), lnk);
- erts_link_release_both(ldp);
+ erts_link_internal_release(lnk);
+ erts_link_internal_release(rlnk);
goto res_no_proc;
}
if (is_internal_port(BIF_ARG_1)) {
int created;
- ErtsLinkData *ldp;
- ErtsLink *lnk;
+ ErtsLink *lnk, *rlnk;
Eterm ref;
Eterm *refp;
Port *prt = erts_port_lookup(BIF_ARG_1,
(erts_port_synchronous_ops
? ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP
: ERTS_PORT_SFLGS_INVALID_LOOKUP));
- if (!prt) {
+ if (!prt)
goto res_no_proc;
- }
- lnk = erts_link_tree_lookup_create(&ERTS_P_LINKS(BIF_P),
- &created,
- ERTS_LNK_TYPE_PORT,
- BIF_P->common.id,
- BIF_ARG_1);
- if (!created)
- BIF_RET(am_true);
+ lnk = erts_link_internal_tree_lookup_create(&ERTS_P_LINKS(BIF_P),
+ &created,
+ ERTS_LNK_TYPE_PORT,
+ BIF_ARG_1);
+ if (!created) {
+ ErtsILink *ilnk = (ErtsILink *) lnk;
+ if (!ilnk->unlinking)
+ BIF_RET(am_true);
+ ilnk->unlinking = 0;
+ }
- ldp = erts_link_to_data(lnk);
+ rlnk = erts_link_internal_create(ERTS_LNK_TYPE_PROC, BIF_P->common.id);
refp = erts_port_synchronous_ops ? &ref : NULL;
- switch (erts_port_link(BIF_P, prt, &ldp->b, refp)) {
- case ERTS_PORT_OP_DROPPED:
+ switch (erts_port_link(BIF_P, prt, rlnk, refp)) {
case ERTS_PORT_OP_BADARG:
+ erts_link_internal_release(rlnk);
erts_link_tree_delete(&ERTS_P_LINKS(BIF_P), lnk);
- erts_link_release_both(ldp);
+ erts_link_internal_release(lnk);
goto res_no_proc;
+ case ERTS_PORT_OP_DROPPED:
case ERTS_PORT_OP_SCHEDULED:
if (refp) {
ASSERT(is_internal_ordinary_ref(ref));
@@ -179,10 +185,10 @@ BIF_RETTYPE link_1(BIF_ALIST_1)
}
if (is_external_pid(BIF_ARG_1)) {
- ErtsLinkData *ldp;
- int created;
+ ErtsELink *elnk, *relnk, *pelnk;
+ int created, replace;
DistEntry *dep;
- ErtsLink *lnk;
+ ErtsLink *lnk, *rlnk;
int code;
ErtsDSigSendContext ctx;
@@ -190,16 +196,39 @@ BIF_RETTYPE link_1(BIF_ALIST_1)
if (dep == erts_this_dist_entry)
goto res_no_proc;
- lnk = erts_link_tree_lookup_create(&ERTS_P_LINKS(BIF_P),
- &created,
- ERTS_LNK_TYPE_DIST_PROC,
- BIF_P->common.id,
- BIF_ARG_1);
+ lnk = erts_link_external_tree_lookup_create(&ERTS_P_LINKS(BIF_P),
+ &created,
+ ERTS_LNK_TYPE_DIST_PROC,
+ BIF_P->common.id,
+ BIF_ARG_1);
- if (!created)
- BIF_RET(am_true); /* Already present... */
+ elnk = erts_link_to_elink(lnk);
- ldp = erts_link_to_data(lnk);
+ if (created) {
+ pelnk = NULL;
+ relnk = NULL;
+ rlnk = NULL;
+ }
+ else {
+ if (!elnk->unlinking)
+ BIF_RET(am_true); /* Already present... */
+ /*
+ * We need to replace the link if the connection has changed.
+ * Prepare a link...
+ */
+ pelnk = (ErtsELink *) erts_link_external_create(ERTS_LNK_TYPE_DIST_PROC,
+ BIF_P->common.id,
+ BIF_ARG_1);
+ ASSERT(eq(pelnk->ld.proc.other.item, BIF_ARG_1));
+ ASSERT(pelnk->ld.dist.other.item == BIF_P->common.id);
+ /* Release pelnk if not used as replacement... */
+ relnk = pelnk;
+ rlnk = &pelnk->ld.proc;
+ }
+ replace = 0;
+
+ ASSERT(eq(elnk->ld.proc.other.item, BIF_ARG_1));
+ ASSERT(elnk->ld.dist.other.item == BIF_P->common.id);
code = erts_dsig_prepare(&ctx, dep, BIF_P,
ERTS_PROC_LOCK_MAIN,
@@ -207,31 +236,75 @@ BIF_RETTYPE link_1(BIF_ALIST_1)
switch (code) {
case ERTS_DSIG_PREP_NOT_ALIVE:
case ERTS_DSIG_PREP_NOT_CONNECTED:
- erts_link_set_dead_dist(&ldp->b, dep->sysname);
- erts_proc_sig_send_link_exit(NULL, BIF_ARG_1, &ldp->b,
+ if (created || elnk->unlinking) {
+ if (elnk->unlinking) {
+ /*
+ * Currently unlinking an old link from an old connection; replace
+ * old link with the prepared one...
+ */
+ relnk = NULL;
+ rlnk = lnk;
+ elnk = pelnk;
+ replace = !0;
+ }
+ erts_link_set_dead_dist(&elnk->ld.dist, dep->sysname);
+ }
+ erts_proc_sig_send_link_exit(NULL, THE_NON_VALUE, &elnk->ld.dist,
am_noconnection, NIL);
- BIF_RET(am_true);
+ break;
case ERTS_DSIG_PREP_PENDING:
case ERTS_DSIG_PREP_CONNECTED: {
/*
- * We have (pending) connection.
+ * We have a connection (or a pending connection).
* Setup link and enqueue link signal.
*/
- int inserted = erts_link_dist_insert(&ldp->b, dep->mld);
- ASSERT(inserted); (void)inserted;
+ if (created
+ || (elnk->unlinking
+ && elnk->dist->connection_id != ctx.connection_id)) {
+ int inserted;
+ if (!created) {
+ /*
+ * Currently unlinking an old link from an old connection; replace
+ * old link with the prepared one...
+ */
+ rlnk = lnk;
+ if (erts_link_dist_delete(&elnk->ld.dist))
+ relnk = elnk;
+ else
+ relnk = NULL;
+ elnk = pelnk;
+ replace = !0;
+ }
+ inserted = erts_link_dist_insert(&elnk->ld.dist, dep->mld);
+ ASSERT(inserted); (void)inserted;
+ }
+
erts_de_runlock(dep);
code = erts_dsig_send_link(&ctx, BIF_P->common.id, BIF_ARG_1);
if (code == ERTS_DSIG_SEND_YIELD)
ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
ASSERT(code == ERTS_DSIG_SEND_OK);
- BIF_RET(am_true);
break;
}
default:
ERTS_ASSERT(! "Invalid dsig prepare result");
}
+
+ if (replace) {
+ ASSERT(pelnk);
+ erts_link_tree_replace(&ERTS_P_LINKS(BIF_P), rlnk, &pelnk->ld.proc);
+ }
+
+ if (relnk)
+ erts_link_release_both(&relnk->ld);
+ else if (rlnk)
+ erts_link_release(rlnk);
+
+ elnk->unlinking = 0;
+
+ BIF_RET(am_true);
}
BIF_ERROR(BIF_P, BADARG);
@@ -309,6 +382,15 @@ demonitor(Process *c_p, Eterm ref, Eterm *multip)
int deleted;
ErtsDSigSendContext ctx;
+ if (mon->flags & ERTS_ML_FLG_SPAWN_PENDING) {
+ /*
+ * Not allowed to remove this until spawn
+ * operation has succeeded; restore monitor...
+ */
+ erts_monitor_tree_insert(&ERTS_P_MONITORS(c_p), mon);
+ return am_false;
+ }
+
ASSERT(is_external_pid(to) || is_node_name_atom(to));
if (is_external_pid(to))
@@ -701,8 +783,11 @@ BIF_RETTYPE spawn_link_3(BIF_ALIST_3)
{
ErlSpawnOpts so;
Eterm pid;
+ Eterm tmp_heap[2];
so.flags = erts_default_spo_flags|SPO_LINK;
+ so.opts = CONS(&tmp_heap[0], am_link, NIL);
+ so.tag = am_spawn_reply;
pid = erl_create_process(BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3, &so);
if (is_non_value(pid)) {
BIF_ERROR(BIF_P, so.error_code);
@@ -716,132 +801,39 @@ BIF_RETTYPE spawn_link_3(BIF_ALIST_3)
/**********************************************************************/
-BIF_RETTYPE spawn_opt_1(BIF_ALIST_1)
+BIF_RETTYPE spawn_opt_4(BIF_ALIST_4)
{
ErlSpawnOpts so;
Eterm pid;
- Eterm* tp;
- Eterm ap;
- Eterm arg;
Eterm res;
+ int opts_error;
/*
- * Check that the first argument is a tuple of four elements.
- */
- if (is_not_tuple(BIF_ARG_1)) {
- error:
- BIF_ERROR(BIF_P, BADARG);
- }
- tp = tuple_val(BIF_ARG_1);
- if (*tp != make_arityval(4))
- goto error;
-
- /*
- * Store default values for options.
+ * Fail order:
+ * - Bad types
+ * - Bad options
*/
- so.flags = erts_default_spo_flags|SPO_USE_ARGS;
- so.min_heap_size = H_MIN_SIZE;
- so.min_vheap_size = BIN_VH_MIN_SIZE;
- so.max_heap_size = H_MAX_SIZE;
- so.max_heap_flags = H_MAX_FLAGS;
- so.priority = PRIORITY_NORMAL;
- so.max_gen_gcs = (Uint16) erts_atomic32_read_nob(&erts_max_gen_gcs);
- so.scheduler = 0;
-
- /*
- * Walk through the option list.
- */
- ap = tp[4];
- while (is_list(ap)) {
- arg = CAR(list_val(ap));
- if (arg == am_link) {
- so.flags |= SPO_LINK;
- } else if (arg == am_monitor) {
- so.flags |= SPO_MONITOR;
- } else if (is_tuple(arg)) {
- Eterm* tp2 = tuple_val(arg);
- Eterm val;
- if (*tp2 != make_arityval(2))
- goto error;
- arg = tp2[1];
- val = tp2[2];
- if (arg == am_priority) {
- if (val == am_max)
- so.priority = PRIORITY_MAX;
- else if (val == am_high)
- so.priority = PRIORITY_HIGH;
- else if (val == am_normal)
- so.priority = PRIORITY_NORMAL;
- else if (val == am_low)
- so.priority = PRIORITY_LOW;
- else
- goto error;
- } else if (arg == am_message_queue_data) {
- switch (val) {
- case am_on_heap:
- so.flags &= ~SPO_OFF_HEAP_MSGQ;
- so.flags |= SPO_ON_HEAP_MSGQ;
- break;
- case am_off_heap:
- so.flags &= ~SPO_ON_HEAP_MSGQ;
- so.flags |= SPO_OFF_HEAP_MSGQ;
- break;
- default:
- goto error;
- }
- } else if (arg == am_min_heap_size && is_small(val)) {
- Sint min_heap_size = signed_val(val);
- if (min_heap_size < 0) {
- goto error;
- } else if (min_heap_size < H_MIN_SIZE) {
- so.min_heap_size = H_MIN_SIZE;
- } else {
- so.min_heap_size = erts_next_heap_size(min_heap_size, 0);
- }
- } else if (arg == am_max_heap_size) {
- if (!erts_max_heap_size(val, &so.max_heap_size, &so.max_heap_flags))
- goto error;
- } else if (arg == am_min_bin_vheap_size && is_small(val)) {
- Sint min_vheap_size = signed_val(val);
- if (min_vheap_size < 0) {
- goto error;
- } else if (min_vheap_size < BIN_VH_MIN_SIZE) {
- so.min_vheap_size = BIN_VH_MIN_SIZE;
- } else {
- so.min_vheap_size = erts_next_heap_size(min_vheap_size, 0);
- }
- } else if (arg == am_fullsweep_after && is_small(val)) {
- Sint max_gen_gcs = signed_val(val);
- if (max_gen_gcs < 0) {
- goto error;
- } else {
- so.max_gen_gcs = max_gen_gcs;
- }
- } else if (arg == am_scheduler && is_small(val)) {
- Sint scheduler = signed_val(val);
- if (scheduler < 0 || erts_no_schedulers < scheduler)
- goto error;
- so.scheduler = (int) scheduler;
- } else {
- goto error;
- }
- } else {
- goto error;
- }
- ap = CDR(list_val(ap));
- }
- if (is_not_nil(ap)) {
- goto error;
- }
-
- if (so.max_heap_size != 0 && so.max_heap_size < so.min_heap_size) {
- goto error;
+ opts_error = erts_parse_spawn_opts(&so, BIF_ARG_4, NULL, 0);
+ if (opts_error) {
+ Sint arity;
+ if (is_not_atom(BIF_ARG_1) || is_not_atom(BIF_ARG_2))
+ BIF_ERROR(BIF_P, BADARG);
+ arity = erts_list_length(BIF_ARG_3);
+ if (arity < 0)
+ BIF_ERROR(BIF_P, BADARG);
+ if (arity > MAX_SMALL)
+ BIF_ERROR(BIF_P, SYSTEM_LIMIT);
+ if (opts_error > 0)
+ BIF_ERROR(BIF_P, BADARG);
+ BIF_ERROR(BIF_P, BADARG);
}
-
+
/*
* Spawn the process.
*/
- pid = erl_create_process(BIF_P, tp[1], tp[2], tp[3], &so);
+ so.opts = BIF_ARG_4;
+ so.tag = am_spawn_reply;
+ pid = erl_create_process(BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3, &so);
if (is_non_value(pid)) {
BIF_ERROR(BIF_P, so.error_code);
} else if (so.flags & SPO_MONITOR) {
@@ -859,6 +851,130 @@ BIF_RETTYPE spawn_opt_1(BIF_ALIST_1)
}
}
+/**********************************************************************/
+
+BIF_RETTYPE erts_internal_spawn_request_4(BIF_ALIST_4)
+{
+ ErlSpawnOpts so;
+ Eterm tmp_heap_mfna[4];
+ Eterm tmp_heap_alist[4 + 2];
+ Sint arity;
+ int opts_error;
+ Eterm tag, tmp, error;
+
+ if (!is_atom(BIF_ARG_1))
+ goto badarg;
+ if (!is_atom(BIF_ARG_2))
+ goto badarg;
+ arity = erts_list_length(BIF_ARG_3);
+ if (arity < 0)
+ goto badarg;
+
+ /*
+ * Fail order:
+ * - Bad types
+ * - Bad options
+ */
+ opts_error = erts_parse_spawn_opts(&so, BIF_ARG_4, &tag, !0);
+ if (arity > MAX_SMALL)
+ goto system_limit;
+ if (opts_error) {
+ if (opts_error > 0)
+ goto badarg;
+ goto badopt;
+ }
+
+ /* Make argument list for erts_internal:spawn_init/1 */
+ tmp = TUPLE3(&tmp_heap_alist[0], BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
+ tmp = CONS(&tmp_heap_alist[4], tmp, NIL);
+
+ so.mfa = TUPLE3(&tmp_heap_mfna[0], BIF_ARG_1, BIF_ARG_2, make_small(arity));
+ so.flags |= SPO_ASYNC;
+ so.mref = THE_NON_VALUE;
+ so.tag = tag;
+ so.opts = BIF_ARG_4;
+
+ /*
+ * Spawn the process.
+ */
+ tmp = erl_create_process(BIF_P, am_erts_internal, am_spawn_init, tmp, &so);
+ if (is_non_value(tmp)) {
+ switch (so.error_code) {
+ case SYSTEM_LIMIT:
+ goto system_limit;
+ case BADARG:
+ default:
+ ERTS_INTERNAL_ERROR("Unexpected error from erl_create_process()");
+ BIF_ERROR(BIF_P, EXC_INTERNAL_ERROR);
+ }
+ }
+
+ ASSERT(is_internal_pid(tmp));
+
+ if (ERTS_USE_MODIFIED_TIMING()) {
+ BIF_TRAP2(erts_delay_trap, BIF_P, so.mref, ERTS_MODIFIED_TIMING_DELAY);
+ }
+ else {
+ BIF_RET(so.mref);
+ }
+
+badarg:
+ BIF_RET(am_badarg);
+system_limit:
+ error = am_system_limit;
+ goto send_error;
+badopt:
+ error = am_badopt;
+ /* fall through... */
+send_error: {
+ Eterm ref = erts_make_ref(BIF_P);
+ if (!(so.flags & SPO_NO_EMSG))
+ erts_send_local_spawn_reply(BIF_P, ERTS_PROC_LOCK_MAIN, NULL,
+ tag, ref, error, am_undefined);
+ BIF_RET(ref);
+ }
+
+}
+
+BIF_RETTYPE spawn_request_abandon_1(BIF_ALIST_1)
+{
+ ErtsMonitor *omon;
+
+ if (is_not_internal_ref(BIF_ARG_1)) {
+ if (is_not_ref(BIF_ARG_1))
+ BIF_ERROR(BIF_P, BADARG);
+ /* Not an outstanding spawn_request of this process... */
+ BIF_RET(am_false);
+ }
+
+ omon = erts_monitor_tree_lookup(ERTS_P_MONITORS(BIF_P), BIF_ARG_1);
+ if (!omon
+ || ((omon->flags & (ERTS_ML_FLG_SPAWN_PENDING
+ | ERTS_ML_FLG_SPAWN_ABANDONED))
+ != ERTS_ML_FLG_SPAWN_PENDING)) {
+ /* Not an outstanding spawn_request of this process... */
+ BIF_RET(am_false);
+ }
+
+ ASSERT(erts_monitor_is_origin(omon));
+
+ if (omon->flags & ERTS_ML_FLG_SPAWN_LINK) {
+ /* Leave it for reply... */
+ omon->flags |= ERTS_ML_FLG_SPAWN_ABANDONED;
+ }
+ else {
+ /* We don't need it anymore; remove it... */
+ ErtsMonitorData *mdp;
+ erts_monitor_tree_delete(&ERTS_P_MONITORS(BIF_P), omon);
+ mdp = erts_monitor_to_data(omon);
+ if (erts_monitor_dist_delete(&mdp->target))
+ erts_monitor_release_both(mdp);
+ else
+ erts_monitor_release(omon);
+ }
+ BIF_RET(am_true);
+}
+
/**********************************************************************/
/* remove a link from a process */
@@ -870,38 +986,52 @@ BIF_RETTYPE unlink_1(BIF_ALIST_1)
}
if (is_internal_pid(BIF_ARG_1)) {
- ErtsLink *lnk = erts_link_tree_lookup(ERTS_P_LINKS(BIF_P), BIF_ARG_1);
- if (lnk) {
- erts_link_tree_delete(&ERTS_P_LINKS(BIF_P), lnk);
- erts_proc_sig_send_unlink(BIF_P, lnk);
+ ErtsILink *ilnk;
+ ilnk = (ErtsILink *) erts_link_tree_lookup(ERTS_P_LINKS(BIF_P),
+ BIF_ARG_1);
+ if (ilnk && !ilnk->unlinking) {
+ Uint64 id = erts_proc_sig_send_unlink(BIF_P,
+ BIF_P->common.id,
+ &ilnk->link);
+ if (id)
+ ilnk->unlinking = id;
+ else {
+ erts_link_tree_delete(&ERTS_P_LINKS(BIF_P), &ilnk->link);
+ erts_link_internal_release(&ilnk->link);
+ }
}
BIF_RET(am_true);
}
if (is_internal_port(BIF_ARG_1)) {
- ErtsLink *lnk = erts_link_tree_lookup(ERTS_P_LINKS(BIF_P), BIF_ARG_1);
+ ErtsILink *ilnk;
+ ilnk = (ErtsILink *) erts_link_tree_lookup(ERTS_P_LINKS(BIF_P),
+ BIF_ARG_1);
- if (lnk) {
+ if (ilnk && !ilnk->unlinking) {
Eterm ref;
Eterm *refp = erts_port_synchronous_ops ? &ref : NULL;
ErtsPortOpResult res = ERTS_PORT_OP_DROPPED;
Port *prt;
- erts_link_tree_delete(&ERTS_P_LINKS(BIF_P), lnk);
-
/* Send unlink signal */
prt = erts_port_lookup(BIF_ARG_1, ERTS_PORT_SFLGS_DEAD);
- if (prt) {
+ if (!prt) {
+ erts_link_tree_delete(&ERTS_P_LINKS(BIF_P), &ilnk->link);
+ erts_link_internal_release(&ilnk->link);
+ }
+ else {
+ ErtsSigUnlinkOp *sulnk;
+
+ sulnk = erts_proc_sig_make_unlink_op(BIF_P, BIF_P->common.id);
+ ilnk->unlinking = sulnk->id;
#ifdef DEBUG
ref = NIL;
#endif
- res = erts_port_unlink(BIF_P, prt, lnk, refp);
-
+ res = erts_port_unlink(BIF_P, prt, sulnk, refp);
}
- if (res == ERTS_PORT_OP_DROPPED)
- erts_link_release(lnk);
- else if (refp && res == ERTS_PORT_OP_SCHEDULED) {
+ if (refp && res == ERTS_PORT_OP_SCHEDULED) {
ASSERT(is_internal_ordinary_ref(ref));
BIF_TRAP3(await_port_send_result_trap, BIF_P, ref, am_true, am_true);
}
@@ -911,9 +1041,10 @@ BIF_RETTYPE unlink_1(BIF_ALIST_1)
}
if (is_external_pid(BIF_ARG_1)) {
- ErtsLink *lnk, *dlnk;
- ErtsLinkData *ldp;
+ ErtsLink *lnk;
+ ErtsELink *elnk;
DistEntry *dep;
+ Uint64 unlink_id;
int code;
ErtsDSigSendContext ctx;
@@ -925,13 +1056,13 @@ BIF_RETTYPE unlink_1(BIF_ALIST_1)
if (!lnk)
BIF_RET(am_true);
- erts_link_tree_delete(&ERTS_P_LINKS(BIF_P), lnk);
- dlnk = erts_link_to_other(lnk, &ldp);
+ elnk = erts_link_to_elink(lnk);
- if (erts_link_dist_delete(dlnk))
- erts_link_release_both(ldp);
- else
- erts_link_release(lnk);
+ if (elnk->unlinking)
+ BIF_RET(am_true);
+
+ unlink_id = erts_proc_sig_new_unlink_id(BIF_P);
+ elnk->unlinking = unlink_id;
code = erts_dsig_prepare(&ctx, dep, BIF_P, ERTS_PROC_LOCK_MAIN,
ERTS_DSP_NO_LOCK, 0, 1, 0);
@@ -941,9 +1072,16 @@ BIF_RETTYPE unlink_1(BIF_ALIST_1)
BIF_RET(am_true);
case ERTS_DSIG_PREP_PENDING:
case ERTS_DSIG_PREP_CONNECTED:
- code = erts_dsig_send_unlink(&ctx, BIF_P->common.id, BIF_ARG_1);
- if (code == ERTS_DSIG_SEND_YIELD)
- ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
+ /*
+ * Do not send unlink signal on another connection than
+ * the one which the link was set up on.
+ */
+ if (elnk->dist->connection_id == ctx.connection_id) {
+ code = erts_dsig_send_unlink(&ctx, BIF_P->common.id, BIF_ARG_1,
+ unlink_id);
+ if (code == ERTS_DSIG_SEND_YIELD)
+ ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
+ }
break;
default:
ASSERT(! "Invalid dsig prepare result");
@@ -990,8 +1128,7 @@ BIF_RETTYPE hibernate_3(BIF_ALIST_3)
BIF_RETTYPE get_stacktrace_0(BIF_ALIST_0)
{
- Eterm t = build_stacktrace(BIF_P, BIF_P->ftrace);
- BIF_RET(t);
+ BIF_RET(NIL);
}
/**********************************************************************/
@@ -1735,8 +1872,10 @@ BIF_RETTYPE erts_internal_process_flag_3(BIF_ALIST_3)
exec_process_flag_3,
(void *) pf3a);
- if (is_non_value(res))
+ if (is_non_value(res)) {
+ erts_free(ERTS_ALC_T_PF3_ARGS, pf3a);
BIF_RET(am_badarg);
+ }
return res;
}
@@ -1816,7 +1955,7 @@ ebif_bang_2(BIF_ALIST_2)
#define SEND_INTERNAL_ERROR (-6)
#define SEND_AWAIT_RESULT (-7)
#define SEND_YIELD_CONTINUE (-8)
-#define SEND_SYSTEM_LIMIT (-9)
+#define SEND_SYSTEM_LIMIT (-9)
static Sint remote_send(Process *p, DistEntry *dep,
@@ -1915,7 +2054,7 @@ do_send(Process *p, Eterm to, Eterm msg, Eterm return_term, Eterm *refp,
erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
erts_dsprintf(dsbufp,
"Discarding message %T from %T to %T in an old "
- "incarnation (%d) of this node (%d)\n",
+ "incarnation (%u) of this node (%u)\n",
msg,
p->common.id,
to,
@@ -1959,7 +2098,7 @@ do_send(Process *p, Eterm to, Eterm msg, Eterm return_term, Eterm *refp,
erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
erts_dsprintf(dsbufp,
"Discarding message %T from %T to %T in an old "
- "incarnation (%d) of this node (%d)\n",
+ "incarnation (%u) of this node (%u)\n",
msg,
p->common.id,
to,
@@ -1987,7 +2126,7 @@ do_send(Process *p, Eterm to, Eterm msg, Eterm return_term, Eterm *refp,
trace_send(p, portid, msg);
if (have_seqtrace(SEQ_TRACE_TOKEN(p))) {
- seq_trace_update_send(p);
+ seq_trace_update_serial(p);
seq_trace_output(SEQ_TRACE_TOKEN(p), msg,
SEQ_TRACE_SEND, portid, p);
}
@@ -2160,7 +2299,7 @@ BIF_RETTYPE send_3(BIF_ALIST_3)
break;
case SEND_YIELD:
if (suspend) {
- ERTS_BIF_PREP_YIELD3(retval, bif_export[BIF_send_3], p, to, msg, opts);
+ ERTS_BIF_PREP_YIELD3(retval, &bif_trap_export[BIF_send_3], p, to, msg, opts);
} else {
ERTS_BIF_PREP_RET(retval, am_nosuspend);
}
@@ -2277,7 +2416,7 @@ Eterm erl_send(Process *p, Eterm to, Eterm msg)
ERTS_BIF_PREP_RET(retval, msg);
break;
case SEND_YIELD:
- ERTS_BIF_PREP_YIELD2(retval, bif_export[BIF_send_2], p, to, msg);
+ ERTS_BIF_PREP_YIELD2(retval, &bif_trap_export[BIF_send_2], p, to, msg);
break;
case SEND_YIELD_RETURN:
yield_return:
@@ -2314,27 +2453,6 @@ done:
}
/**********************************************************************/
-/*
- * apply/3 is implemented as an instruction and as erlang code in the
- * erlang module.
- *
- * There is only one reason that apply/3 is included in the BIF table:
- * The error handling code in the beam emulator passes the pointer to
- * this function to the error handling code if the apply instruction
- * fails. The error handling use the function pointer to lookup
- * erlang:apply/3 in the BIF table.
- *
- * This function will never be called. (It could be if init did something
- * like this: apply(erlang, apply, [M, F, A]). Not recommended.)
- */
-
-BIF_RETTYPE apply_3(BIF_ALIST_3)
-{
- BIF_ERROR(BIF_P, BADARG);
-}
-
-
-/**********************************************************************/
/* integer to float */
@@ -2584,7 +2702,7 @@ BIF_RETTYPE iolist_size_1(BIF_ALIST_1)
} else {
ERTS_BIF_ERROR_TRAPPED1(BIF_P,
BADARG,
- bif_export[BIF_iolist_size_1],
+ &bif_trap_export[BIF_iolist_size_1],
input_list);
}
@@ -2604,7 +2722,7 @@ BIF_RETTYPE iolist_size_1(BIF_ALIST_1)
ESTACK_SAVE(s, &context->stack);
erts_set_gc_state(BIF_P, 0);
BUMP_ALL_REDS(BIF_P);
- BIF_TRAP1(bif_export[BIF_iolist_size_1], BIF_P, state_mref);
+ BIF_TRAP1(&bif_trap_export[BIF_iolist_size_1], BIF_P, state_mref);
}
/**********************************************************************/
@@ -3082,13 +3200,15 @@ BIF_RETTYPE list_to_integer_2(BIF_ALIST_2)
int base;
i = erts_list_length(BIF_ARG_1);
- if (i < 0)
- BIF_ERROR(BIF_P, BADARG);
-
+ if (i < 0 || is_not_small(BIF_ARG_2)) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
base = signed_val(BIF_ARG_2);
- if (base < 2 || base > 36)
- BIF_ERROR(BIF_P, BADARG);
+ if (base < 2 || base > 36) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
if (erts_list_to_integer(BIF_P, BIF_ARG_1, base,
&res, &dummy) != LTI_ALL_INTEGER) {
@@ -3942,7 +4062,7 @@ BIF_RETTYPE halt_2(BIF_ALIST_2)
("System halted by BIF halt(%T, %T)\n", BIF_ARG_1, BIF_ARG_2));
if (flush) {
erts_halt(pos_int_code);
- ERTS_BIF_YIELD2(bif_export[BIF_halt_2], BIF_P, am_undefined, am_undefined);
+ ERTS_BIF_YIELD2(&bif_trap_export[BIF_halt_2], BIF_P, am_undefined, am_undefined);
}
else {
erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
@@ -4531,7 +4651,7 @@ BIF_RETTYPE system_flag_2(BIF_ALIST_2)
BIF_RET(am_enabled);
case ERTS_SCHDLR_SSPND_YIELD_RESTART:
ERTS_VBUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_system_flag_2],
+ BIF_TRAP2(&bif_trap_export[BIF_system_flag_2],
BIF_P, BIF_ARG_1, BIF_ARG_2);
case ERTS_SCHDLR_SSPND_YIELD_DONE:
ERTS_BIF_YIELD_RETURN_X(BIF_P, am_enabled,
@@ -4556,7 +4676,7 @@ BIF_RETTYPE system_flag_2(BIF_ALIST_2)
BIF_RET(make_small(old_no));
case ERTS_SCHDLR_SSPND_YIELD_RESTART:
ERTS_VBUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_system_flag_2],
+ BIF_TRAP2(&bif_trap_export[BIF_system_flag_2],
BIF_P, BIF_ARG_1, BIF_ARG_2);
case ERTS_SCHDLR_SSPND_YIELD_DONE:
ERTS_BIF_YIELD_RETURN_X(BIF_P, make_small(old_no),
@@ -4720,7 +4840,7 @@ BIF_RETTYPE system_flag_2(BIF_ALIST_2)
BIF_RET(make_small(old_no));
case ERTS_SCHDLR_SSPND_YIELD_RESTART:
ERTS_VBUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_system_flag_2],
+ BIF_TRAP2(&bif_trap_export[BIF_system_flag_2],
BIF_P, BIF_ARG_1, BIF_ARG_2);
case ERTS_SCHDLR_SSPND_YIELD_DONE:
ERTS_BIF_YIELD_RETURN_X(BIF_P, make_small(old_no),
@@ -4870,9 +4990,13 @@ BIF_RETTYPE phash_2(BIF_ALIST_2)
BIF_RETTYPE phash2_1(BIF_ALIST_1)
{
Uint32 hash;
-
- hash = make_hash2(BIF_ARG_1);
- BIF_RET(make_small(hash & ((1L << 27) - 1)));
+ Eterm trap_state = THE_NON_VALUE;
+ hash = trapping_make_hash2(BIF_ARG_1, &trap_state, BIF_P);
+ if (trap_state == THE_NON_VALUE) {
+ BIF_RET(make_small(hash & ((1L << 27) - 1)));
+ } else {
+ BIF_TRAP1(&bif_trap_export[BIF_phash2_1], BIF_P, trap_state);
+ }
}
BIF_RETTYPE phash2_2(BIF_ALIST_2)
@@ -4880,6 +5004,7 @@ BIF_RETTYPE phash2_2(BIF_ALIST_2)
Uint32 hash;
Uint32 final_hash;
Uint32 range;
+ Eterm trap_state = THE_NON_VALUE;
/* Check for special case 2^32 */
if (term_equals_2pow32(BIF_ARG_2)) {
@@ -4891,7 +5016,10 @@ BIF_RETTYPE phash2_2(BIF_ALIST_2)
}
range = (Uint32) u;
}
- hash = make_hash2(BIF_ARG_1);
+ hash = trapping_make_hash2(BIF_ARG_1, &trap_state, BIF_P);
+ if (trap_state != THE_NON_VALUE) {
+ BIF_TRAP2(&bif_trap_export[BIF_phash2_2], BIF_P, trap_state, BIF_ARG_2);
+ }
if (range) {
final_hash = hash % range; /* [0..range-1] */
} else {
@@ -4967,15 +5095,32 @@ void erts_init_trap_export(Export* ep, Eterm m, Eterm f, Uint a,
Eterm (*bif)(BIF_ALIST))
{
int i;
+
sys_memset((void *) ep, 0, sizeof(Export));
+
for (i=0; i<ERTS_NUM_CODE_IX; i++) {
- ep->addressv[i] = ep->beam;
+ ep->addressv[i] = ep->trampoline.raw;
}
+
+ ep->bif_number = -1;
+
+ ep->info.op = op_i_func_info_IaaI;
ep->info.mfa.module = m;
ep->info.mfa.function = f;
ep->info.mfa.arity = a;
- ep->beam[0] = BeamOpCodeAddr(op_apply_bif);
- ep->beam[1] = (BeamInstr) bif;
+
+ ep->trampoline.op = BeamOpCodeAddr(op_call_bif_W);
+ ep->trampoline.raw[1] = (BeamInstr)bif;
+}
+
+/*
+ * Writes a BIF call wrapper to the given address.
+ */
+void erts_write_bif_wrapper(Export *export, BeamInstr *address) {
+ BifEntry *entry = &bif_table[export->bif_number];
+
+ address[0] = BeamOpCodeAddr(op_call_bif_W);
+ address[1] = (BeamInstr)entry->f;
}
void erts_init_bif(void)
@@ -5027,7 +5172,7 @@ void erts_init_bif(void)
}
/*
- * Scheduling of BIFs via NifExport...
+ * Scheduling of BIFs via ErtsNativeFunc...
*/
#define ERTS_WANT_NFUNC_SCHED_INTERNALS__
#include "erl_nfunc_sched.h"
@@ -5042,8 +5187,8 @@ schedule(Process *c_p, Process *dirty_shadow_proc,
int argc, Eterm *argv)
{
ERTS_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(c_p));
- (void) erts_nif_export_schedule(c_p, dirty_shadow_proc,
- mfa, pc, BeamOpCodeAddr(op_apply_bif),
+ (void) erts_nfunc_schedule(c_p, dirty_shadow_proc,
+ mfa, pc, BeamOpCodeAddr(op_call_bif_W),
dfunc, ifunc,
module, function,
argc, argv);
@@ -5052,23 +5197,23 @@ schedule(Process *c_p, Process *dirty_shadow_proc,
static BIF_RETTYPE dirty_bif_result(BIF_ALIST_1)
{
- NifExport *nep = (NifExport *) ERTS_PROC_GET_NIF_TRAP_EXPORT(BIF_P);
- erts_nif_export_restore(BIF_P, nep, BIF_ARG_1);
+ ErtsNativeFunc *nep = (ErtsNativeFunc *) ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(BIF_P);
+ erts_nfunc_restore(BIF_P, nep, BIF_ARG_1);
BIF_RET(BIF_ARG_1);
}
static BIF_RETTYPE dirty_bif_trap(BIF_ALIST)
{
- NifExport *nep = (NifExport *) ERTS_PROC_GET_NIF_TRAP_EXPORT(BIF_P);
+ ErtsNativeFunc *nep = (ErtsNativeFunc *) ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(BIF_P);
/*
* Arity and argument registers already set
* correct by call to dirty_bif_trap()...
*/
- ASSERT(BIF_P->arity == nep->exp.info.mfa.arity);
+ ASSERT(BIF_P->arity == nep->trampoline.info.mfa.arity);
- erts_nif_export_restore(BIF_P, nep, THE_NON_VALUE);
+ erts_nfunc_restore(BIF_P, nep, THE_NON_VALUE);
BIF_P->i = (BeamInstr *) nep->func;
BIF_P->freason = TRAP;
@@ -5083,8 +5228,8 @@ static BIF_RETTYPE dirty_bif_exception(BIF_ALIST_2)
freason = signed_val(BIF_ARG_1);
- /* Restore orig info for error and clear nif export in handle_error() */
- freason |= EXF_RESTORE_NIF;
+ /* Restore orig info for error and clear nif wrapper in handle_error() */
+ freason |= EXF_RESTORE_NFUNC;
BIF_P->fvalue = BIF_ARG_2;
@@ -5122,6 +5267,7 @@ erts_schedule_bif(Process *proc,
if (!ERTS_PROC_IS_EXITING(c_p)) {
Export *exp;
BifFunction dbif, ibif;
+ BeamInstr call_instr;
BeamInstr *pc;
/*
@@ -5156,29 +5302,41 @@ erts_schedule_bif(Process *proc,
if (i == NULL) {
ERTS_INTERNAL_ERROR("Missing instruction pointer");
}
+
+ if (BeamIsOpCode(*i, op_i_generic_breakpoint)) {
+ ErtsCodeInfo *ci;
+ GenericBp *bp;
+
+ ci = erts_code_to_codeinfo(i);
+ bp = ci->u.gen_bp;
+
+ call_instr = bp->orig_instr;
+ } else {
+ call_instr = *i;
+ }
+
#ifdef HIPE
- else if (proc->flags & F_HIPE_MODE) {
+ if (proc->flags & F_HIPE_MODE) {
/* Pointer to bif export in i */
exp = (Export *) i;
- pc = c_p->cp;
+ pc = cp_val(c_p->stop[0]);
mfa = &exp->info.mfa;
- }
+ } else /* !! This is part of the if clause below !! */
#endif
- else if (BeamIsOpCode(*i, op_call_bif_e)) {
- /* Pointer to bif export in i+1 */
- exp = (Export *) i[1];
+ if (BeamIsOpCode(call_instr, op_call_light_bif_be)) {
+ /* Pointer to bif export in i+2 */
+ exp = (Export *) i[2];
pc = i;
mfa = &exp->info.mfa;
}
- else if (BeamIsOpCode(*i, op_call_bif_only_e)) {
- /* Pointer to bif export in i+1 */
- exp = (Export *) i[1];
+ else if (BeamIsOpCode(call_instr, op_call_light_bif_only_be)) {
+ /* Pointer to bif export in i+2 */
+ exp = (Export *) i[2];
pc = i;
mfa = &exp->info.mfa;
}
- else if (BeamIsOpCode(*i, op_apply_bif)) {
- /* Pointer to bif in i+1, and mfa in i-3 */
- pc = c_p->cp;
+ else if (BeamIsOpCode(call_instr, op_call_bif_W)) {
+ pc = cp_val(c_p->stop[0]);
mfa = erts_code_to_codemfa(i);
}
else {
@@ -5206,7 +5364,7 @@ erts_schedule_bif(Process *proc,
static BIF_RETTYPE
call_bif(Process *c_p, Eterm *reg, BeamInstr *I)
{
- NifExport *nep = ERTS_I_BEAM_OP_TO_NIF_EXPORT(I);
+ ErtsNativeFunc *nep = ERTS_I_BEAM_OP_TO_NFUNC(I);
ErtsBifFunc bif = (ErtsBifFunc) nep->func;
BIF_RETTYPE ret;
@@ -5219,12 +5377,12 @@ call_bif(Process *c_p, Eterm *reg, BeamInstr *I)
ret = (*bif)(c_p, reg, I);
if (is_value(ret))
- erts_nif_export_restore(c_p, nep, ret);
+ erts_nfunc_restore(c_p, nep, ret);
else if (c_p->freason != TRAP)
- c_p->freason |= EXF_RESTORE_NIF; /* restore in handle_error() */
+ c_p->freason |= EXF_RESTORE_NFUNC; /* restore in handle_error() */
else if (nep->func == ERTS_SCHED_BIF_TRAP_MARKER) {
/* BIF did an ordinary trap... */
- erts_nif_export_restore(c_p, nep, ret);
+ erts_nfunc_restore(c_p, nep, ret);
}
/* else:
* BIF rescheduled itself using erts_schedule_bif().
@@ -5241,7 +5399,7 @@ erts_call_dirty_bif(ErtsSchedulerData *esdp, Process *c_p, BeamInstr *I, Eterm *
int exiting;
Process *dirty_shadow_proc;
ErtsBifFunc bf;
- NifExport *nep;
+ ErtsNativeFunc *nep;
#ifdef DEBUG
Eterm *c_p_htop;
erts_aint32_t state;
@@ -5254,8 +5412,8 @@ erts_call_dirty_bif(ErtsSchedulerData *esdp, Process *c_p, BeamInstr *I, Eterm *
#endif
- nep = ERTS_I_BEAM_OP_TO_NIF_EXPORT(I);
- ASSERT(nep == ERTS_PROC_GET_NIF_TRAP_EXPORT(c_p));
+ nep = ERTS_I_BEAM_OP_TO_NFUNC(I);
+ ASSERT(nep == ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(c_p));
nep->func = ERTS_SCHED_BIF_TRAP_MARKER;
@@ -5269,7 +5427,6 @@ erts_call_dirty_bif(ErtsSchedulerData *esdp, Process *c_p, BeamInstr *I, Eterm *
dirty_shadow_proc->freason = c_p->freason;
dirty_shadow_proc->fvalue = c_p->fvalue;
dirty_shadow_proc->ftrace = c_p->ftrace;
- dirty_shadow_proc->cp = c_p->cp;
dirty_shadow_proc->i = c_p->i;
#ifdef DEBUG
@@ -5316,7 +5473,6 @@ erts_call_dirty_bif(ErtsSchedulerData *esdp, Process *c_p, BeamInstr *I, Eterm *
c_p->freason = dirty_shadow_proc->freason;
c_p->fvalue = dirty_shadow_proc->fvalue;
c_p->ftrace = dirty_shadow_proc->ftrace;
- c_p->cp = dirty_shadow_proc->cp;
c_p->i = dirty_shadow_proc->i;
c_p->arity = dirty_shadow_proc->arity;
}
diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab
index 8d16ffd857..5fb8441a15 100644
--- a/erts/emulator/beam/bif.tab
+++ b/erts/emulator/beam/bif.tab
@@ -25,13 +25,14 @@
#
# <bif-decl> ::= "bif" <bif> <C-name>* |
# "ubif" <bif> <C-name>* |
-# "gcbif" <bif> <C-name>*
+# "hbif" <bif> <C-name>*
# <bif> ::= <module> ":" <name> "/" <arity>
#
-# ubif: Use for operators and guard BIFs that never build anything
-# on the heap (such as tuple_size/1) and operators.
+# ubif: Use for operators and guard BIFs.
#
-# gcbif: Use for guard BIFs that may build on the heap (such as abs/1).
+# hbif: Use for BIFs that perform garbage collection or need up-to-date
+# information on where they were called from. These must be called
+# through the export entry.
#
# bif: Use for all other BIFs.
#
@@ -45,7 +46,6 @@ ubif erlang:abs/1
bif erlang:adler32/1
bif erlang:adler32/2
bif erlang:adler32_combine/3
-bif erlang:apply/3
bif erlang:atom_to_list/1
bif erlang:binary_to_list/1
bif erlang:binary_to_list/3
@@ -60,7 +60,7 @@ bif erlang:display_string/1
bif erlang:display_nl/0
ubif erlang:element/2
bif erlang:erase/0
-bif erlang:erase/1
+hbif erlang:erase/1
bif erlang:exit/1
bif erlang:exit/2
bif erlang:exit_signal/2
@@ -70,7 +70,7 @@ ubif erlang:float/1
bif erlang:float_to_list/1
bif erlang:float_to_list/2
bif erlang:fun_info/2
-bif erts_internal:garbage_collect/1
+hbif erts_internal:garbage_collect/1
bif erlang:get/0
bif erlang:get/1
bif erlang:get_keys/1
@@ -127,10 +127,10 @@ bif erlang:ports/0
bif erlang:pre_loaded/0
bif erlang:process_flag/2
bif erts_internal:process_flag/3
-bif erlang:process_info/1
-bif erlang:process_info/2
+hbif erlang:process_info/1
+hbif erlang:process_info/2
bif erlang:processes/0
-bif erlang:put/2
+hbif erlang:put/2
bif erlang:register/2
bif erlang:registered/0
ubif erlang:round/1
@@ -143,6 +143,8 @@ bif erlang:split_binary/2
bif erlang:statistics/1
bif erlang:term_to_binary/1
bif erlang:term_to_binary/2
+bif erlang:term_to_iovec/1
+bif erlang:term_to_iovec/2
bif erlang:throw/1
bif erlang:time/0
ubif erlang:tl/1
@@ -153,7 +155,7 @@ bif erlang:universaltime_to_localtime/1
bif erlang:unlink/1
bif erlang:unregister/1
bif erlang:whereis/1
-bif erlang:spawn_opt/1
+bif erlang:spawn_opt/4
bif erlang:setnode/2
bif erlang:dist_get_stat/1
bif erlang:dist_ctrl_input_handler/2
@@ -174,7 +176,7 @@ bif erts_internal:port_connect/2
bif erts_internal:request_system_task/3
bif erts_internal:request_system_task/4
-bif erts_internal:check_process_code/1
+hbif erts_internal:check_process_code/1
bif erts_internal:map_to_tuple_keys/1
bif erts_internal:term_type/1
@@ -193,10 +195,14 @@ bif erts_internal:scheduler_wall_time/1
bif erts_internal:dirty_process_handle_signals/1
-bif erts_internal:create_dist_channel/4
+bif erts_internal:create_dist_channel/3
bif erts_internal:ets_super_user/1
+bif erts_internal:spawn_request/4
+bif erts_internal:dist_spawn_request/4
+bif erlang:spawn_request_abandon/1
+
# inet_db support
bif erlang:port_set_data/2
bif erlang:port_get_data/1
@@ -466,7 +472,7 @@ bif code:is_module_native/1
# New Bifs in R9C.
#
-bif erlang:hibernate/3
+hbif erlang:hibernate/3
bif error_logger:warning_map/0
#
@@ -758,3 +764,9 @@ bif erts_internal:ets_raw_next/2
bif erts_internal:abort_pending_connection/2
+
+#
+# New in 23
+#
+
+bif erts_internal:get_creation/0
diff --git a/erts/emulator/beam/bif_instrs.tab b/erts/emulator/beam/bif_instrs.tab
index bde3b8a490..7601b3d346 100644
--- a/erts/emulator/beam/bif_instrs.tab
+++ b/erts/emulator/beam/bif_instrs.tab
@@ -212,26 +212,32 @@ i_length.execute(Fail, Live, Dst) {
// Call a BIF, store the result in x(0) and transfer control to the
// next instruction.
//
-call_bif(Exp) {
+call_light_bif(Bif, Exp) {
+ Export *export;
ErtsBifFunc bf;
+
Eterm result;
ErlHeapFragment *live_hf_end;
- Export *export = (Export*) $Exp;
+
+ bf = (ErtsBifFunc) $Bif;
+ export = (Export*) $Exp;
if (!((FCALLS - 1) > 0 || (FCALLS-1) > neg_o_reds)) {
/*
* If we have run out of reductions, do a context
* switch before calling the BIF.
*/
- c_p->arity = GET_BIF_ARITY(export);
+ c_p->arity = GET_EXPORT_ARITY(export);
c_p->current = &export->info.mfa;
goto context_switch3;
}
- ERTS_MSACC_SET_BIF_STATE_CACHED_X(GET_BIF_MODULE(export),
- GET_BIF_ADDRESS(export));
+ if (ERTS_UNLIKELY(export->is_bif_traced)) {
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
+ $DISPATCH_EXPORT(export);
+ }
- bf = GET_BIF_ADDRESS(export);
+ ERTS_MSACC_SET_BIF_STATE_CACHED_X(GET_EXPORT_MODULE(export), bf);
PRE_BIF_SWAPOUT(c_p);
ERTS_DBG_CHK_REDS(c_p, FCALLS);
@@ -243,21 +249,26 @@ call_bif(Exp) {
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
live_hf_end = c_p->mbuf;
ERTS_CHK_MBUF_SZ(c_p);
+
result = (*bf)(c_p, reg, I);
+
+ /* Only heavy BIFs may GC. */
+ ASSERT(E == c_p->stop);
+
ERTS_CHK_MBUF_SZ(c_p);
ASSERT(!ERTS_PROC_IS_EXITING(c_p) || is_non_value(result));
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
ERTS_HOLE_CHECK(c_p);
ERTS_REQ_PROC_MAIN_LOCK(c_p);
if (ERTS_IS_GC_DESIRED(c_p)) {
- Uint arity = GET_BIF_ARITY(export);
+ Uint arity = GET_EXPORT_ARITY(export);
result = erts_gc_after_bif_call_lhf(c_p, live_hf_end, result,
reg, arity);
E = c_p->stop;
}
- PROCESS_MAIN_CHK_LOCKS(c_p);
HTOP = HEAP_TOP(c_p);
FCALLS = c_p->fcalls;
+ PROCESS_MAIN_CHK_LOCKS(c_p);
ERTS_DBG_CHK_REDS(c_p, FCALLS);
/*
@@ -280,10 +291,9 @@ call_bif(Exp) {
* erlang code or by nif_bif.epilogue() when the BIF
* is done).
*/
- SET_CP(c_p, $NEXT_INSTRUCTION);
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
SET_I(c_p->i);
- SWAPIN;
- Dispatch();
+ $DISPATCH();
}
/*
@@ -297,29 +307,38 @@ call_bif(Exp) {
//
// Call a BIF tail-recursively, storing the result in x(0) and doing
-// a return to the continuation poiner (c_p->cp).
+// a return to the continuation poiner.
//
-
-call_bif_only(Exp) {
+call_light_bif_only(Bif, Exp) {
+ ErlHeapFragment *live_hf_end;
ErtsBifFunc bf;
+ Export *export;
Eterm result;
- ErlHeapFragment *live_hf_end;
- Export *export = (Export*) $Exp;
+
+ bf = (ErtsBifFunc) $Bif;
+ export = (Export*) $Exp;
if (!((FCALLS - 1) > 0 || (FCALLS-1) > neg_o_reds)) {
/*
* If we have run out of reductions, do a context
* switch before calling the BIF.
*/
- c_p->arity = GET_BIF_ARITY(export);
+ c_p->arity = GET_EXPORT_ARITY(export);
c_p->current = &export->info.mfa;
goto context_switch3;
}
- ERTS_MSACC_SET_BIF_STATE_CACHED_X(GET_BIF_MODULE(export),
- GET_BIF_ADDRESS(export));
+ if (ERTS_UNLIKELY(export->is_bif_traced)) {
+ /* Set up a dummy stack frame so we can perform a normal call. Loader
+ * transformations ensure that the next instruction after this is
+ * 'deallocate_return 0'. */
+ $AH(0, 0, GET_EXPORT_ARITY(export));
+
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
+ $DISPATCH_EXPORT(export);
+ }
- bf = GET_BIF_ADDRESS(export);
+ ERTS_MSACC_SET_BIF_STATE_CACHED_X(GET_EXPORT_MODULE(export), bf);
PRE_BIF_SWAPOUT(c_p);
ERTS_DBG_CHK_REDS(c_p, FCALLS);
@@ -331,21 +350,26 @@ call_bif_only(Exp) {
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
live_hf_end = c_p->mbuf;
ERTS_CHK_MBUF_SZ(c_p);
+
result = (*bf)(c_p, reg, I);
+
+ /* Only heavy BIFs may GC. */
+ ASSERT(E == c_p->stop);
+
ERTS_CHK_MBUF_SZ(c_p);
ASSERT(!ERTS_PROC_IS_EXITING(c_p) || is_non_value(result));
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
ERTS_HOLE_CHECK(c_p);
ERTS_REQ_PROC_MAIN_LOCK(c_p);
if (ERTS_IS_GC_DESIRED(c_p)) {
- Uint arity = GET_BIF_ARITY(export);
+ Uint arity = GET_EXPORT_ARITY(export);
result = erts_gc_after_bif_call_lhf(c_p, live_hf_end, result,
reg, arity);
E = c_p->stop;
}
- PROCESS_MAIN_CHK_LOCKS(c_p);
HTOP = HEAP_TOP(c_p);
FCALLS = c_p->fcalls;
+ PROCESS_MAIN_CHK_LOCKS(c_p);
ERTS_DBG_CHK_REDS(c_p, FCALLS);
/*
@@ -367,11 +391,10 @@ call_bif_only(Exp) {
} else if (c_p->freason == TRAP) {
/*
* Dispatch to a trap. When the trap is done, a jump
- * to the continuation pointer (c_p->cp) will be done.
+ * to the continuation pointer on the stack will be done.
*/
SET_I(c_p->i);
- SWAPIN;
- Dispatch();
+ $DISPATCH();
}
/*
@@ -413,17 +436,26 @@ send() {
r(0) = result;
CHECK_TERM(r(0));
} else if (c_p->freason == TRAP) {
- SET_CP(c_p, $NEXT_INSTRUCTION);
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
SET_I(c_p->i);
SWAPIN;
- Dispatch();
+ $DISPATCH();
} else {
goto find_func_info;
}
}
+call_nif_early() {
+ HEAVY_SWAPOUT;
+ I = erts_call_nif_early(c_p, erts_code_to_codeinfo(I));
+ HEAVY_SWAPIN;
+ ASSERT(VALID_INSTR(*I));
+ Goto(*I);
+ //| -no_next
+}
+
call_nif := nif_bif.call_nif.epilogue;
-apply_bif := nif_bif.apply_bif.epilogue;
+call_bif := nif_bif.call_bif.epilogue;
nif_bif.head() {
Eterm nif_bif_result;
@@ -433,7 +465,7 @@ nif_bif.head() {
ErtsCodeMFA *codemfa;
}
-nif_bif.call_nif() {
+nif_bif.call_nif(Func, NifMod, DirtyFunc) {
/*
* call_nif is always first instruction in function:
*
@@ -443,11 +475,14 @@ nif_bif.call_nif() {
* I[0]: &&call_nif
* I[1]: Function pointer to NIF function
* I[2]: Pointer to erl_module_nif
- * I[3]: Function pointer to dirty NIF
+ * I[3]: Function pointer to dirty NIF. This is not used in this
+ * instruction, but dirty schedulers look at it.
*
- * This layout is determined by the NifExport struct
+ * This layout is determined by the ErtsNativeFunc struct
*/
+ (void)$DirtyFunc;
+
ERTS_MSACC_SET_STATE_CACHED_M_X(ERTS_MSACC_STATE_NIF);
codemfa = erts_code_to_codemfa(I);
@@ -465,12 +500,12 @@ nif_bif.call_nif() {
ASSERT(!ERTS_PROC_IS_EXITING(c_p));
{
typedef Eterm NifF(struct enif_environment_t*, int argc, Eterm argv[]);
- NifF* fp = vbf = (NifF*) I[1];
+ NifF* fp = vbf = (NifF*) $Func;
struct enif_environment_t env;
ASSERT(c_p->scheduler_data);
live_hf_end = c_p->mbuf;
ERTS_CHK_MBUF_SZ(c_p);
- erts_pre_nif(&env, c_p, (struct erl_module_nif*)I[2], NULL);
+ erts_pre_nif(&env, c_p, (struct erl_module_nif*)$NifMod, NULL);
ASSERT((c_p->scheduler_data)->current_nif == NULL);
(c_p->scheduler_data)->current_nif = &env;
@@ -495,15 +530,15 @@ nif_bif.call_nif() {
DTRACE_NIF_RETURN(c_p, codemfa);
}
-nif_bif.apply_bif() {
+nif_bif.call_bif(Func) {
/*
- * At this point, I points to the code[0] in the export entry for
- * the BIF:
+ * At this point, I points to the code[0] in the native function wrapper
+ * for the BIF:
*
* code[-3]: Module
* code[-2]: Function
* code[-1]: Arity
- * code[0]: &&apply_bif
+ * code[0]: &&call_bif
* code[1]: Function pointer to BIF function
*/
@@ -515,21 +550,19 @@ nif_bif.apply_bif() {
codemfa = erts_code_to_codemfa(I);
- ERTS_MSACC_SET_BIF_STATE_CACHED_X(codemfa->module, (BifFunction)Arg(0));
-
+ ERTS_MSACC_SET_BIF_STATE_CACHED_X(codemfa->module, (BifFunction)$Func);
/* In case we apply process_info/1,2 or load_nif/1 */
c_p->current = codemfa;
$SET_CP_I_ABS(I); /* In case we apply check_process_code/2. */
c_p->arity = 0; /* To allow garbage collection on ourselves
- * (check_process_code/2).
- */
+ * (check_process_code/2, put/2, etc). */
DTRACE_BIF_ENTRY(c_p, codemfa);
SWAPOUT;
ERTS_DBG_CHK_REDS(c_p, FCALLS - 1);
c_p->fcalls = FCALLS - 1;
- vbf = (BifFunction) Arg(0);
+ vbf = (BifFunction)$Func;
PROCESS_MAIN_CHK_LOCKS(c_p);
bif_nif_arity = codemfa->arity;
ASSERT(bif_nif_arity <= 4);
@@ -557,6 +590,7 @@ nif_bif.apply_bif() {
}
nif_bif.epilogue() {
+ //| -no_next
ERTS_REQ_PROC_MAIN_LOCK(c_p);
ERTS_HOLE_CHECK(c_p);
if (ERTS_IS_GC_DESIRED(c_p)) {
@@ -570,8 +604,7 @@ nif_bif.epilogue() {
if (ERTS_LIKELY(is_value(nif_bif_result))) {
r(0) = nif_bif_result;
CHECK_TERM(r(0));
- SET_I(c_p->cp);
- c_p->cp = 0;
+ $RETURN();
Goto(*I);
} else if (c_p->freason == TRAP) {
SET_I(c_p->i);
@@ -579,8 +612,42 @@ nif_bif.epilogue() {
c_p->flags &= ~F_HIBERNATE_SCHED;
goto do_schedule;
}
- Dispatch();
+ $DISPATCH();
+ }
+ {
+ BeamInstr *cp = erts_printable_return_address(c_p, E);
+ ASSERT(VALID_INSTR(*cp));
+ I = handle_error(c_p, cp, reg, c_p->current);
}
- I = handle_error(c_p, c_p->cp, reg, c_p->current);
goto post_error_handling;
}
+
+i_load_nif() {
+ //| -no_next
+ if (erts_try_seize_code_write_permission(c_p)) {
+ Eterm result;
+
+ PRE_BIF_SWAPOUT(c_p);
+ result = erts_load_nif(c_p, I, r(0), r(1));
+ erts_release_code_write_permission();
+ ERTS_REQ_PROC_MAIN_LOCK(c_p);
+ SWAPIN;
+
+ if (ERTS_LIKELY(is_value(result))) {
+ r(0) = result;
+ $NEXT0();
+ } else {
+ static ErtsCodeMFA mfa = {am_erlang, am_load_nif, 2};
+ c_p->freason = BADARG;
+ I = handle_error(c_p, I, reg, &mfa);
+ goto post_error_handling;
+ }
+ } else {
+ /* Yield and try again */
+ $SET_CP_I_ABS(I);
+ SWAPOUT;
+ c_p->current = NULL;
+ c_p->arity = 2;
+ goto context_switch3;
+ }
+}
diff --git a/erts/emulator/beam/big.c b/erts/emulator/beam/big.c
index e5cb3ae84c..e2ae090569 100644
--- a/erts/emulator/beam/big.c
+++ b/erts/emulator/beam/big.c
@@ -2176,6 +2176,24 @@ term_to_Uint64(Eterm term, Uint64 *up)
#endif
}
+int
+term_to_Uint32(Eterm term, Uint32 *up)
+{
+#if ERTS_SIZEOF_ETERM == 4
+ return term_to_Uint(term,up);
+#else
+ if (is_small(term)) {
+ Sint i = signed_val(term);
+ if (i >= 0) {
+ *up = (Uint32) i;
+ return 1;
+ }
+ }
+ *up = BADARG;
+ return 0;
+#endif
+}
+
int term_to_Sint(Eterm term, Sint *sp)
{
diff --git a/erts/emulator/beam/big.h b/erts/emulator/beam/big.h
index 346ce6d284..b60ca1760b 100644
--- a/erts/emulator/beam/big.h
+++ b/erts/emulator/beam/big.h
@@ -168,6 +168,8 @@ Eterm erts_uint64_array_to_big(Uint **, int, int, Uint64 *);
int term_to_Uint64(Eterm, Uint64*);
int term_to_Sint64(Eterm, Sint64*);
#endif
+int term_to_Uint32(Eterm, Uint32*);
+
Uint32 big_to_uint32(Eterm b);
int term_equals_2pow32(Eterm);
diff --git a/erts/emulator/beam/binary.c b/erts/emulator/beam/binary.c
index b6027b3c59..436eafbff2 100644
--- a/erts/emulator/beam/binary.c
+++ b/erts/emulator/beam/binary.c
@@ -567,7 +567,7 @@ BIF_RETTYPE binary_to_list_1(BIF_ALIST_1)
if (size < L2B_B2L_MIN_EXEC_REDS*ERTS_B2L_BYTES_PER_REDUCTION) {
if (reds_left <= L2B_B2L_RESCHED_REDS) {
/* Yield and do it with full context reds... */
- ERTS_BIF_YIELD1(bif_export[BIF_binary_to_list_1],
+ ERTS_BIF_YIELD1(&bif_trap_export[BIF_binary_to_list_1],
BIF_P, BIF_ARG_1);
}
/* Allow a bit more reductions... */
@@ -621,7 +621,7 @@ BIF_RETTYPE binary_to_list_3(BIF_ALIST_3)
if (size < L2B_B2L_MIN_EXEC_REDS*ERTS_B2L_BYTES_PER_REDUCTION) {
if (reds_left <= L2B_B2L_RESCHED_REDS) {
/* Yield and do it with full context reds... */
- ERTS_BIF_YIELD3(bif_export[BIF_binary_to_list_3],
+ ERTS_BIF_YIELD3(&bif_trap_export[BIF_binary_to_list_3],
BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
}
/* Allow a bit more reductions... */
@@ -668,7 +668,7 @@ BIF_RETTYPE bitstring_to_list_1(BIF_ALIST_1)
if (size < L2B_B2L_MIN_EXEC_REDS*ERTS_B2L_BYTES_PER_REDUCTION) {
if (reds_left <= L2B_B2L_RESCHED_REDS) {
/* Yield and do it with full context reds... */
- ERTS_BIF_YIELD1(bif_export[BIF_bitstring_to_list_1],
+ ERTS_BIF_YIELD1(&bif_trap_export[BIF_bitstring_to_list_1],
BIF_P, BIF_ARG_1);
}
/* Allow a bit more reductions... */
@@ -1041,7 +1041,7 @@ HIPE_WRAPPER_BIF_DISABLE_GC(list_to_binary, 1)
BIF_RETTYPE list_to_binary_1(BIF_ALIST_1)
{
- return erts_list_to_binary_bif(BIF_P, BIF_ARG_1, bif_export[BIF_list_to_binary_1]);
+ return erts_list_to_binary_bif(BIF_P, BIF_ARG_1, &bif_trap_export[BIF_list_to_binary_1]);
}
HIPE_WRAPPER_BIF_DISABLE_GC(iolist_to_binary, 1)
@@ -1054,7 +1054,7 @@ BIF_RETTYPE iolist_to_binary_1(BIF_ALIST_1)
}
BIF_ERROR(BIF_P, BADARG);
}
- return erts_list_to_binary_bif(BIF_P, BIF_ARG_1, bif_export[BIF_iolist_to_binary_1]);
+ return erts_list_to_binary_bif(BIF_P, BIF_ARG_1, &bif_trap_export[BIF_iolist_to_binary_1]);
}
static int bitstr_list_len(ErtsIOListState *);
@@ -1081,7 +1081,7 @@ BIF_RETTYPE list_to_bitstring_1(BIF_ALIST_1)
else {
ErtsL2BState state = ERTS_L2B_STATE_INITER(BIF_P,
BIF_ARG_1,
- bif_export[BIF_list_to_bitstring_1],
+ &bif_trap_export[BIF_list_to_bitstring_1],
bitstr_list_len,
list_to_bitstr_buf_yielding);
int orig_reds_left = ERTS_BIF_REDS_LEFT(BIF_P);
diff --git a/erts/emulator/beam/break.c b/erts/emulator/beam/break.c
index c8449a222b..045b1b86ff 100644
--- a/erts/emulator/beam/break.c
+++ b/erts/emulator/beam/break.c
@@ -563,6 +563,8 @@ dump_attributes(fmtfn_t to, void *to_arg, byte* ptr, int size)
void
do_break(void)
{
+ const char *helpstring = "BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo\n"
+ " (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution\n";
int i;
#ifdef __WIN32__
char *mode; /* enough for storing "window" */
@@ -577,9 +579,7 @@ do_break(void)
ASSERT(erts_thr_progress_is_blocking());
- erts_printf("\n"
- "BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo\n"
- " (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution\n");
+ erts_printf("\n%s", helpstring);
while (1) {
if ((i = sys_get_key(0)) <= 0)
@@ -664,7 +664,8 @@ do_break(void)
case '\n':
continue;
default:
- erts_printf("Eh?\n\n");
+ erts_printf("Invalid option '%c'. Please enter one of the following:\n%s",
+ i, helpstring);
}
}
diff --git a/erts/emulator/beam/bs_instrs.tab b/erts/emulator/beam/bs_instrs.tab
index fc3da7d016..df45dd54c6 100644
--- a/erts/emulator/beam/bs_instrs.tab
+++ b/erts/emulator/beam/bs_instrs.tab
@@ -294,10 +294,13 @@ i_new_bs_put_binary(Fail, Sz, Flags, Src) {
Eterm sz = $Sz;
Sint _size;
$BS_GET_UNCHECKED_FIELD_SIZE(sz, (($Flags) >> 3), $BADARG($Fail), _size);
- if (!erts_new_bs_put_binary(ERL_BITS_ARGS_2(($Src), _size))) {
+ c_p->fcalls = FCALLS;
+ if (!erts_new_bs_put_binary(c_p, $Src, _size)) {
$BADARG($Fail);
}
+ FCALLS = c_p->fcalls;
}
+
i_new_bs_put_binary_all := i_new_bs_put_binary_all.fetch.execute;
i_new_bs_put_binary_all.head() {
@@ -309,15 +312,19 @@ i_new_bs_put_binary_all.fetch(Src) {
}
i_new_bs_put_binary_all.execute(Fail, Unit) {
- if (!erts_new_bs_put_binary_all(ERL_BITS_ARGS_2(src, ($Unit)))) {
+ c_p->fcalls = FCALLS;
+ if (!erts_new_bs_put_binary_all(c_p, src, ($Unit))) {
$BADARG($Fail);
}
+ FCALLS = c_p->fcalls;
}
i_new_bs_put_binary_imm(Fail, Sz, Src) {
- if (!erts_new_bs_put_binary(ERL_BITS_ARGS_2(($Src), ($Sz)))) {
+ c_p->fcalls = FCALLS;
+ if (!erts_new_bs_put_binary(c_p, ($Src), ($Sz))) {
$BADARG($Fail);
}
+ FCALLS = c_p->fcalls;
}
i_new_bs_put_float(Fail, Sz, Flags, Src) {
@@ -707,11 +714,13 @@ i_bs_append(Fail, ExtraHeap, Live, Unit, Size, Dst) {
i_bs_private_append(Fail, Unit, Size, Src, Dst) {
Eterm res;
+ c_p->fcalls = FCALLS;
res = erts_bs_private_append(c_p, $Src, $Size, $Unit);
if (is_non_value(res)) {
/* c_p->freason is already set (to BADARG or SYSTEM_LIMIT). */
$FAIL_HEAD_OR_BODY($Fail);
}
+ FCALLS = c_p->fcalls;
$Dst = res;
}
diff --git a/erts/emulator/beam/code_ix.c b/erts/emulator/beam/code_ix.c
index 50352b4084..eab9d51b5e 100644
--- a/erts/emulator/beam/code_ix.c
+++ b/erts/emulator/beam/code_ix.c
@@ -37,9 +37,12 @@
erts_atomic32_t the_active_code_index;
erts_atomic32_t the_staging_code_index;
+static int code_writing_seized = 0;
static Process* code_writing_process = NULL;
struct code_write_queue_item {
Process *p;
+ void (*aux_func)(void *);
+ void *aux_arg;
struct code_write_queue_item* next;
};
static struct code_write_queue_item* code_write_queue = NULL;
@@ -108,19 +111,37 @@ void erts_abort_staging_code_ix(void)
CIX_TRACE("abort");
}
+static int try_seize_cwp(Process* c_p,
+ void (*aux_func)(void *),
+ void *aux_arg);
/*
* Calller _must_ yield if we return 0
*/
int erts_try_seize_code_write_permission(Process* c_p)
{
+ ASSERT(c_p != NULL);
+ return try_seize_cwp(c_p, NULL, NULL);
+}
+
+int erts_try_seize_code_write_permission_aux(void (*aux_func)(void *),
+ void *aux_arg)
+{
+ ASSERT(aux_func != NULL);
+ return try_seize_cwp(NULL, aux_func, aux_arg);
+}
+
+static int try_seize_cwp(Process* c_p,
+ void (*aux_func)(void *),
+ void *aux_arg)
+{
int success;
ASSERT(!erts_thr_progress_is_blocking()); /* to avoid deadlock */
- ASSERT(c_p != NULL);
erts_mtx_lock(&code_write_permission_mtx);
- success = (code_writing_process == NULL);
+ success = !code_writing_seized;
if (success) {
+ code_writing_seized = 1;
code_writing_process = c_p;
#ifdef ERTS_ENABLE_LOCK_CHECK
erts_tsd_set(has_code_write_permission, (void *) 1);
@@ -128,13 +149,22 @@ int erts_try_seize_code_write_permission(Process* c_p)
}
else { /* Already locked */
struct code_write_queue_item* qitem;
- ASSERT(code_writing_process != c_p);
- qitem = erts_alloc(ERTS_ALC_T_CODE_IX_LOCK_Q, sizeof(*qitem));
- qitem->p = c_p;
- erts_proc_inc_refc(c_p);
- qitem->next = code_write_queue;
- code_write_queue = qitem;
- erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL);
+ qitem = erts_alloc(ERTS_ALC_T_CODE_IX_LOCK_Q, sizeof(*qitem));
+ if (c_p) {
+ ASSERT(code_writing_process != c_p);
+ qitem->p = c_p;
+ qitem->aux_func = NULL;
+ qitem->aux_arg = NULL;
+ erts_proc_inc_refc(c_p);
+ erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL);
+ }
+ else {
+ qitem->p = NULL;
+ qitem->aux_func = aux_func;
+ qitem->aux_arg = aux_arg;
+ }
+ qitem->next = code_write_queue;
+ code_write_queue = qitem;
}
erts_mtx_unlock(&code_write_permission_mtx);
return success;
@@ -146,15 +176,25 @@ void erts_release_code_write_permission(void)
ERTS_LC_ASSERT(erts_has_code_write_permission());
while (code_write_queue != NULL) { /* unleash the entire herd */
struct code_write_queue_item* qitem = code_write_queue;
- erts_proc_lock(qitem->p, ERTS_PROC_LOCK_STATUS);
- if (!ERTS_PROC_IS_EXITING(qitem->p)) {
- erts_resume(qitem->p, ERTS_PROC_LOCK_STATUS);
- }
- erts_proc_unlock(qitem->p, ERTS_PROC_LOCK_STATUS);
- code_write_queue = qitem->next;
- erts_proc_dec_refc(qitem->p);
+ if (qitem->p) {
+ erts_proc_lock(qitem->p, ERTS_PROC_LOCK_STATUS);
+ if (!ERTS_PROC_IS_EXITING(qitem->p)) {
+ erts_resume(qitem->p, ERTS_PROC_LOCK_STATUS);
+ }
+ erts_proc_unlock(qitem->p, ERTS_PROC_LOCK_STATUS);
+ erts_proc_dec_refc(qitem->p);
+ }
+ else { /* aux work*/
+ ErtsSchedulerData *esdp = erts_get_scheduler_data();
+ ASSERT(esdp && esdp->type == ERTS_SCHED_NORMAL);
+ erts_schedule_misc_aux_work((int) esdp->no,
+ qitem->aux_func,
+ qitem->aux_arg);
+ }
+ code_write_queue = qitem->next;
erts_free(ERTS_ALC_T_CODE_IX_LOCK_Q, qitem);
}
+ code_writing_seized = 0;
code_writing_process = NULL;
#ifdef ERTS_ENABLE_LOCK_CHECK
erts_tsd_set(has_code_write_permission, (void *) 0);
@@ -165,6 +205,6 @@ void erts_release_code_write_permission(void)
#ifdef ERTS_ENABLE_LOCK_CHECK
int erts_has_code_write_permission(void)
{
- return (code_writing_process != NULL) && erts_tsd_get(has_code_write_permission);
+ return code_writing_seized && erts_tsd_get(has_code_write_permission);
}
#endif
diff --git a/erts/emulator/beam/code_ix.h b/erts/emulator/beam/code_ix.h
index 42976d2301..64a9c95979 100644
--- a/erts/emulator/beam/code_ix.h
+++ b/erts/emulator/beam/code_ix.h
@@ -133,6 +133,15 @@ ErtsCodeIndex erts_staging_code_ix(void);
*/
int erts_try_seize_code_write_permission(struct process* c_p);
+/* Try seize exclusive code write permission for aux work.
+ * System thread progress must not be blocked.
+ * On success return true.
+ * On failure return false and aux work func(arg) will be scheduled when
+ * permission is released. .
+ */
+int erts_try_seize_code_write_permission_aux(void (*func)(void *),
+ void *arg);
+
/* Release code write permission.
* Will resume any suspended waiters.
*/
diff --git a/erts/emulator/beam/dist.c b/erts/emulator/beam/dist.c
index f1cd97c3bf..c79c0834f6 100644
--- a/erts/emulator/beam/dist.c
+++ b/erts/emulator/beam/dist.c
@@ -46,6 +46,7 @@
#include "erl_thr_progress.h"
#include "dtrace-wrapper.h"
#include "erl_proc_sig_queue.h"
+#include "erl_global_literals.h"
#define DIST_CTL_DEFAULT_SIZE 64
@@ -149,6 +150,16 @@ static char *erts_dop_to_string(enum dop dop) {
return "PAYLOAD_EXIT2_TT";
if (dop == DOP_PAYLOAD_MONITOR_P_EXIT)
return "PAYLOAD_MONITOR_P_EXIT";
+ if (dop == DOP_SPAWN_REQUEST)
+ return "SPAWN_REQUEST";
+ if (dop == DOP_SPAWN_REQUEST_TT)
+ return "SPAWN_REQUEST_TT";
+ if (dop == DOP_SPAWN_REPLY)
+ return "SPAWN_REPLY";
+ if (dop == DOP_UNLINK_ID)
+ return "UNLINK_ID";
+ if (dop == DOP_UNLINK_ID_ACK)
+ return "UNLINK_ID_ACK";
ASSERT(0);
return "UNKNOWN";
}
@@ -168,6 +179,9 @@ static char *erts_dop_to_string(enum dop dop) {
int erts_is_alive; /* System must be blocked on change */
int erts_dist_buf_busy_limit;
+int erts_dflags_test_remove_hopefull_flags;
+
+Export spawn_request_yield_export;
/* distribution trap functions */
Export* dmonitor_node_trap = NULL;
@@ -262,6 +276,12 @@ static int monitor_connection_down(ErtsMonitor *mon, void *unused, Sint reds)
return ERTS_MON_LNK_FIRE_REDS;
}
+static int dist_pend_spawn_exit_connection_down(ErtsMonitor *mon, void *unused, Sint reds)
+{
+ erts_monitor_release(mon);
+ return 1;
+}
+
static int link_connection_down(ErtsLink *lnk, void *vdist, Sint reds)
{
erts_proc_sig_send_link_exit(NULL, THE_NON_VALUE, lnk,
@@ -273,12 +293,14 @@ typedef enum {
ERTS_CML_CLEANUP_STATE_LINKS,
ERTS_CML_CLEANUP_STATE_MONITORS,
ERTS_CML_CLEANUP_STATE_ONAME_MONITORS,
+ ERTS_CML_CLEANUP_STATE_PEND_SPAWN_EXIT_MONITORS,
ERTS_CML_CLEANUP_STATE_SEQUENCES,
ERTS_CML_CLEANUP_STATE_NODE_MONITORS
} ErtsConMonLnkSeqCleanupState;
typedef struct {
ErtsConMonLnkSeqCleanupState state;
+ DistEntry* dep;
ErtsMonLnkDist *dist;
DistSeqNode *seq;
void *yield_state;
@@ -327,6 +349,16 @@ con_monitor_link_seq_cleanup(void *vcmlcp)
if (reds <= 0)
break;
+ ASSERT(!cmlcp->yield_state);
+ cmlcp->state = ERTS_CML_CLEANUP_STATE_PEND_SPAWN_EXIT_MONITORS;
+ case ERTS_CML_CLEANUP_STATE_PEND_SPAWN_EXIT_MONITORS:
+ reds = erts_monitor_tree_foreach_delete_yielding(&dist->dist_pend_spawn_exit,
+ dist_pend_spawn_exit_connection_down,
+ NULL, &cmlcp->yield_state,
+ reds);
+ if (reds <= 0)
+ break;
+
cmlcp->dist = NULL;
erts_mon_link_dist_dec_refc(dist);
@@ -343,11 +375,30 @@ con_monitor_link_seq_cleanup(void *vcmlcp)
cmlcp->state = ERTS_CML_CLEANUP_STATE_NODE_MONITORS;
case ERTS_CML_CLEANUP_STATE_NODE_MONITORS:
if (cmlcp->trigger_node_monitors) {
+ Process* waiter;
send_nodes_mon_msgs(NULL,
am_nodedown,
cmlcp->nodename,
cmlcp->visability,
cmlcp->reason);
+ erts_de_rwlock(cmlcp->dep);
+ ASSERT(cmlcp->dep->state == ERTS_DE_STATE_IDLE ||
+ cmlcp->dep->state == ERTS_DE_STATE_PENDING);
+ ASSERT(cmlcp->dep->pending_nodedown);
+ waiter = cmlcp->dep->suspended_nodeup;
+ cmlcp->dep->suspended_nodeup = NULL;
+ cmlcp->dep->pending_nodedown = 0;
+ erts_de_rwunlock(cmlcp->dep);
+ erts_deref_dist_entry(cmlcp->dep);
+
+ if (waiter) {
+ erts_proc_lock(waiter, ERTS_PROC_LOCK_STATUS);
+ if (!ERTS_PROC_IS_EXITING(waiter)) {
+ erts_resume(waiter, ERTS_PROC_LOCK_STATUS);
+ }
+ erts_proc_unlock(waiter, ERTS_PROC_LOCK_STATUS);
+ erts_proc_dec_refc(waiter);
+ }
}
erts_cleanup_offheap(&cmlcp->oh);
erts_free(ERTS_ALC_T_CML_CLEANUP, vcmlcp);
@@ -364,7 +415,8 @@ con_monitor_link_seq_cleanup(void *vcmlcp)
}
static void
-schedule_con_monitor_link_seq_cleanup(ErtsMonLnkDist *dist,
+schedule_con_monitor_link_seq_cleanup(DistEntry* dep,
+ ErtsMonLnkDist *dist,
DistSeqNode *seq,
Eterm nodename,
Eterm visability,
@@ -404,7 +456,16 @@ schedule_con_monitor_link_seq_cleanup(ErtsMonLnkDist *dist,
cmlcp->seq = seq;
- cmlcp->trigger_node_monitors = is_value(nodename);
+ if (is_value(nodename)) {
+ ASSERT(dep);
+ cmlcp->trigger_node_monitors = 1;
+ cmlcp->dep = dep;
+ erts_ref_dist_entry(dep);
+ }
+ else {
+ cmlcp->trigger_node_monitors = 0;
+ cmlcp->dep = NULL;
+ }
cmlcp->nodename = nodename;
cmlcp->visability = visability;
if (rsz == 0)
@@ -422,6 +483,255 @@ schedule_con_monitor_link_seq_cleanup(ErtsMonLnkDist *dist,
}
}
+static void
+dist_pend_spawn_exit_save_child_result(Eterm result, Eterm ref, ErtsMonLnkDist *dist)
+{
+ ErtsMonitorData *new_mdp = NULL;
+ Process *proc = NULL;
+ int done = 0;
+
+ while (1) {
+ erts_mtx_lock(&dist->mtx);
+
+ if (!dist->alive)
+ done = !0;
+ else {
+ ErtsMonitor *mon;
+ mon = erts_monitor_tree_lookup(dist->dist_pend_spawn_exit, ref);
+ if (!mon) {
+ if (new_mdp) {
+ /*
+ * Save info so parent can get child pid when handling
+ * links during termination
+ */
+ erts_monitor_tree_insert(&dist->dist_pend_spawn_exit,
+ &new_mdp->target);
+ done = !0;
+ new_mdp = NULL;
+ }
+ }
+ else {
+ ErtsMonitorDataExtended *mdep;
+ /*
+ * The terminating parent is waiting for this signal.
+ * Store childs pid and resume parent if it has
+ * suspended (i.e, mon->other.ptr != NULL)...
+ */
+ proc = mon->other.ptr;
+ mdep = (ErtsMonitorDataExtended *) erts_monitor_to_data(mon);
+ if (is_atom(result))
+ mdep->md.origin.other.item = result;
+ else {
+ Eterm *hp;
+ ErlOffHeap oh;
+#ifdef DEBUG
+ int i;
+ for (i = 0; i < EXTERNAL_THING_HEAD_SIZE + 1; i++) {
+ ASSERT(is_non_value(mdep->heap[i]));
+ }
+#endif
+ hp = &(mdep)->heap[0];
+ ERTS_INIT_OFF_HEAP(&oh);
+ oh.first = mdep->uptr.ohhp;
+ mdep->md.origin.other.item
+ = copy_struct(result,
+ EXTERNAL_THING_HEAD_SIZE + 1,
+ &hp, &oh);
+ mdep->uptr.ohhp = oh.first;
+ }
+ done = !0;
+ }
+ }
+
+ erts_mtx_unlock(&dist->mtx);
+
+ if (done)
+ break;
+
+ /*
+ * No monitor previously saved by parent; create one
+ * and store child pid...
+ */
+ new_mdp = erts_monitor_create(ERTS_MON_TYPE_DIST_PROC, ref,
+ am_undefined, result, NIL);
+ ASSERT(new_mdp->target.other.item == am_undefined);
+ new_mdp->target.other.ptr = NULL;
+
+ ((ErtsMonitorDataExtended *) new_mdp)->dist = dist;
+ erts_mon_link_dist_inc_refc(dist);
+ erts_monitor_release(&new_mdp->origin);
+ }
+
+ if (proc)
+ erts_resume(proc, 0);
+
+ if (new_mdp)
+ erts_monitor_release(&new_mdp->target);
+
+}
+
+int
+erts_dist_pend_spawn_exit_delete(ErtsMonitor *mon)
+{
+ ErtsMonitorDataExtended *mdep;
+ int delete;
+ ErtsMonLnkDist *dist;
+ Uint16 flags;
+
+ mdep = (ErtsMonitorDataExtended *) erts_monitor_to_data(mon);
+ dist = mdep->dist;
+
+ erts_mtx_lock(&dist->mtx);
+
+ flags = mon->flags;
+ delete = !!dist->alive & !!(flags & ERTS_ML_FLG_IN_TABLE);
+
+ if (delete)
+ erts_monitor_tree_delete(&dist->dist_pend_spawn_exit, mon);
+
+ erts_mtx_unlock(&dist->mtx);
+
+ return delete;
+}
+
+int
+erts_dist_pend_spawn_exit_parent_setup(ErtsMonitor *mon)
+{
+ /*
+ * Return:
+ * 0 -> connection is closing
+ * !0 -> moved target part of monitor to dist_pend_spawn_exit
+ */
+ ErtsMonitorData *mdp;
+ ErtsMonLnkDist *dist;
+ int res;
+
+ ASSERT(erts_monitor_is_origin(mon));
+
+ mdp = (ErtsMonitorData *) erts_monitor_to_data(mon);
+
+ if (!erts_monitor_dist_delete(&mdp->target))
+ return 0;
+
+ dist = ((ErtsMonitorDataExtended *) mdp)->dist;
+
+ while (1) {
+ ErtsMonitor *tmp_mon;
+
+ erts_mtx_lock(&dist->mtx);
+ mdp->target.other.ptr = NULL;
+
+ if (!dist->alive) {
+ res = 0;
+ tmp_mon = NULL;
+ }
+ else {
+ res = !0;
+ tmp_mon = erts_monitor_tree_lookup(dist->dist_pend_spawn_exit,
+ mdp->ref);
+ if (!tmp_mon)
+ erts_monitor_tree_insert(&dist->dist_pend_spawn_exit,
+ &mdp->target);
+ else
+ erts_monitor_tree_delete(&dist->dist_pend_spawn_exit, tmp_mon);
+ }
+
+ erts_mtx_unlock(&dist->mtx);
+
+ if (!tmp_mon) {
+ if (!res)
+ erts_monitor_release(&mdp->target);
+ return res;
+ }
+ else {
+ /*
+ * Child had responded; copy its pid then store
+ * original target end in 'dist_pend_spawn_exit'
+ */
+ ErtsMonitorData *tmp_mdp = erts_monitor_to_data(tmp_mon);
+
+ if (is_atom(tmp_mdp->origin.other.item)) {
+ erts_monitor_release(tmp_mon);
+ erts_monitor_release(&mdp->target);
+ return 0; /* Spawn failed; drop it... */
+ }
+
+ /*
+ * If mdp->origin.other.item != am_pending, the other
+ * end is behaving badly by sending multiple
+ * responses...
+ */
+ if (mdp->origin.other.item == am_pending) {
+ ErtsMonitorDataExtended *mdep = (ErtsMonitorDataExtended *) mdp;
+ ErlOffHeap oh;
+ Eterm *hp;
+#ifdef DEBUG
+ int i;
+
+ ASSERT(is_external_pid(tmp_mdp->origin.other.item));
+ for (i = 0; i < EXTERNAL_THING_HEAD_SIZE + 1; i++) {
+ ASSERT(is_non_value(mdep->heap[i]));
+ }
+#endif
+ hp = &(mdep)->heap[0];
+ ERTS_INIT_OFF_HEAP(&oh);
+ oh.first = mdep->uptr.ohhp;
+ mdep->md.origin.other.item
+ = copy_struct(tmp_mdp->origin.other.item,
+ EXTERNAL_THING_HEAD_SIZE + 1,
+ &hp, &oh);
+ mdep->uptr.ohhp = oh.first;
+ }
+ erts_monitor_release(tmp_mon);
+ }
+ }
+}
+
+int
+erts_dist_pend_spawn_exit_parent_wait(Process *c_p,
+ ErtsProcLocks locks,
+ ErtsMonitor *mon)
+{
+ /*
+ * return value of
+ * > 0 --> Child pid can now be found in monitor
+ * 0 --> Connection not alive; drop it
+ * < 0 --> Setup completed; later need to wait for child pid
+ */
+ ErtsMonitorData *mdp;
+ ErtsMonLnkDist *dist;
+ int res;
+
+ ASSERT(erts_monitor_is_origin(mon));
+
+ mdp = (ErtsMonitorData *) erts_monitor_to_data(mon);
+ dist = ((ErtsMonitorDataExtended *) mdp)->dist;
+
+ erts_mtx_lock(&dist->mtx);
+
+ if (!dist->alive) {
+ res = 0;
+ }
+ else {
+ ASSERT(&mdp->target ==
+ erts_monitor_tree_lookup(dist->dist_pend_spawn_exit, mdp->ref));
+ if (mdp->origin.other.item != am_pending) {
+ erts_monitor_tree_delete(&dist->dist_pend_spawn_exit, &mdp->target);
+ res = 1;
+ }
+ else {
+ /* We need to suspend wait and wait for the result... */
+ mdp->target.other.ptr = (void *) c_p;
+ erts_suspend(c_p, locks, NULL);
+ res = -1;
+ }
+ }
+
+ erts_mtx_unlock(&dist->mtx);
+
+ return res;
+}
+
/*
** A full node name consists of a "n@h"
**
@@ -644,7 +954,7 @@ int erts_do_net_exits(DistEntry *dep, Eterm reason)
ErtsAtomCache *cache;
ErtsProcList *suspendees;
ErtsDistOutputBuf *obuf;
- Uint32 flags;
+ Uint64 flags;
erts_atomic_set_mb(&dep->dist_cmd_scheduled, 1);
erts_de_rwlock(dep);
@@ -674,7 +984,7 @@ int erts_do_net_exits(DistEntry *dep, Eterm reason)
dep->sequences = NULL;
nodename = dep->sysname;
- flags = dep->flags;
+ flags = dep->dflags;
erts_atomic_set_nob(&dep->input_handler, (erts_aint_t) NIL);
cache = dep->cache;
@@ -693,10 +1003,11 @@ int erts_do_net_exits(DistEntry *dep, Eterm reason)
dep->send = NULL;
erts_set_dist_entry_not_connected(dep);
-
+ dep->pending_nodedown = 1;
erts_de_rwunlock(dep);
- schedule_con_monitor_link_seq_cleanup(mld,
+ schedule_con_monitor_link_seq_cleanup(dep,
+ mld,
sequences,
nodename,
(flags & DFLAG_PUBLISHED
@@ -711,8 +1022,6 @@ int erts_do_net_exits(DistEntry *dep, Eterm reason)
delete_cache(cache);
free_de_out_queues(dep, obuf);
- if (dep->transcode_ctx)
- transcode_free_ctx(dep);
}
dec_no_nodes();
@@ -726,13 +1035,7 @@ trap_function(Eterm func, int arity)
return erts_export_put(am_erlang, func, arity);
}
-/*
- * Sync with dist_util.erl:
- *
- * -record(erts_dflags,
- * {default, mandatory, addable, rejectable, strict_order}).
- */
-static Eterm erts_dflags_record;
+static BIF_RETTYPE spawn_request_yield_3(BIF_ALIST_3);
void init_dist(void)
{
@@ -759,61 +1062,118 @@ void init_dist(void)
dist_ctrl_put_data_trap = erts_export_put(am_erts_internal,
am_dist_ctrl_put_data,
2);
+ erts_init_trap_export(&spawn_request_yield_export,
+ am_erts_internal, am_spawn_request_yield,
+ 3, spawn_request_yield_3);
{
- Eterm* hp = erts_alloc(ERTS_ALC_T_LITERAL, (1+6)*sizeof(Eterm));
- erts_dflags_record = TUPLE6(hp, am_erts_dflags,
- make_small(DFLAG_DIST_DEFAULT),
- make_small(DFLAG_DIST_MANDATORY),
- make_small(DFLAG_DIST_ADDABLE),
- make_small(DFLAG_DIST_REJECTABLE),
- make_small(DFLAG_DIST_STRICT_ORDER));
- erts_set_literal_tag(&erts_dflags_record, hp, (1+6));
+ Eterm *hp_start, *hp, **hpp = NULL, tuple;
+ Uint sz = 0, *szp = &sz;
+ while (1) {
+ /*
+ * Sync with dist_util.erl:
+ *
+ * -record(erts_dflags,
+ * {default, mandatory, addable, rejectable, strict_order}).
+ */
+ tuple = erts_bld_tuple(hpp, szp, 6,
+ am_erts_dflags,
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_DEFAULT),
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_MANDATORY),
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_ADDABLE),
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_REJECTABLE),
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_STRICT_ORDER));
+ if (hpp) {
+ ASSERT(is_value(tuple));
+ ASSERT(hp == hp_start + sz);
+ erts_register_global_literal(ERTS_LIT_DFLAGS_RECORD, tuple);
+ break;
+ }
+ hp = hp_start = erts_alloc_global_literal(ERTS_LIT_DFLAGS_RECORD, sz);
+ hpp = &hp;
+ szp = NULL;
+ }
}
+ ERTS_CT_ASSERT(sizeof(ErtsDistOutputBuf) % sizeof(void*) == 0);
+ ERTS_CT_ASSERT(sizeof(SysIOVec) % sizeof(void*) == 0);
+ ERTS_CT_ASSERT(sizeof(ErlIOVec) % sizeof(void*) == 0);
}
-#define ErtsDistOutputBuf2Binary(OB) OB->bin
-
static ERTS_INLINE ErtsDistOutputBuf *
-alloc_dist_obuf(Uint size, Uint headers)
+alloc_dist_obufs(byte **extp, TTBEncodeContext *ctx,
+ Uint data_size, Uint fragments, Uint vlen)
{
- Uint obuf_size = sizeof(ErtsDistOutputBuf)*(headers);
+ int ix;
ErtsDistOutputBuf *obuf;
+ char *ptr;
+ Uint iov_sz, obsz;
Binary *bin;
- byte *extp;
- int i;
+ Uint fragment_size;
+
+ obsz = sizeof(ErtsDistOutputBuf)*fragments;
+
+ iov_sz = erts_ttb_iov_size(0, vlen, fragments);
+
+ bin = erts_bin_drv_alloc(obsz + iov_sz + data_size);
+ ctx->result_bin = bin;
+ ptr = (char *) &bin->orig_bytes[0];
+
+ obuf = (ErtsDistOutputBuf *) ptr;
+ ptr += obsz;
+
+ if (fragments > 1)
+ fragment_size = ERTS_DIST_FRAGMENT_SIZE;
+ else
+ fragment_size = ~((Uint) 0);
- bin = erts_bin_drv_alloc(obuf_size + size);
- erts_refc_add(&bin->intern.refc, headers - 1, 1);
+ erts_ttb_iov_init(ctx, 0, ptr, vlen, fragments, fragment_size);
+ ptr += iov_sz;
- obuf = (ErtsDistOutputBuf *)&bin->orig_bytes[0];
- extp = (byte *)&bin->orig_bytes[obuf_size];
+ erts_refc_add(&bin->intern.refc, fragments - 1, 1);
- for (i = 0; i < headers; i++) {
- obuf[i].bin = bin;
- obuf[i].extp = extp;
+ for (ix = 0; ix < fragments; ix++) {
+ obuf[ix].bin = bin;
+ obuf[ix].eiov = &ctx->fragment_eiovs[ix];
#ifdef DEBUG
- obuf[i].dbg_pattern = ERTS_DIST_OUTPUT_BUF_DBG_PATTERN;
- obuf[i].ext_startp = extp;
- obuf[i].alloc_endp = &extp[size];
- ASSERT(bin == ErtsDistOutputBuf2Binary(obuf));
+ obuf[ix].dbg_pattern = ERTS_DIST_OUTPUT_BUF_DBG_PATTERN;
#endif
}
+ *extp = (byte *) ptr;
return obuf;
}
static ERTS_INLINE void
-free_dist_obuf(ErtsDistOutputBuf *obuf)
+free_dist_obuf(ErtsDistOutputBuf *obuf, int free_binv)
{
- Binary *bin = ErtsDistOutputBuf2Binary(obuf);
ASSERT(obuf->dbg_pattern == ERTS_DIST_OUTPUT_BUF_DBG_PATTERN);
- erts_bin_release(bin);
+
+ if (free_binv) {
+ int i;
+ int vlen = obuf->eiov->vsize;
+ ErlDrvBinary **binv = obuf->eiov->binv;
+ for (i = 0; i < vlen; i++) {
+ if (binv[i])
+ driver_free_binary(binv[i]);
+ }
+ }
+ erts_bin_release(obuf->bin);
}
static ERTS_INLINE Sint
size_obuf(ErtsDistOutputBuf *obuf)
{
- return sizeof(ErtsDistOutputBuf) + (obuf->ext_endp - obuf->ext_start)
- + (obuf->hdr_endp - obuf->hdrp);
+ Sint vlen = obuf->eiov->vsize;
+ Sint sz;
+#ifdef DEBUG
+ Sint i;
+ for (i = 0, sz = 0; i < vlen; i++)
+ sz += obuf->eiov->iov[i].iov_len;
+ ASSERT(sz == obuf->eiov->size);
+#endif
+ sz = sizeof(ErtsDistOutputBuf) + sizeof(ErlIOVec);
+ sz += obuf->eiov->size;
+ sz += sizeof(SysIOVec)*vlen;
+ sz += sizeof(ErlDrvBinary*)*vlen;
+ return sz;
}
static ErtsDistOutputBuf* clear_de_out_queues(DistEntry* dep)
@@ -853,7 +1213,7 @@ static void free_de_out_queues(DistEntry* dep, ErtsDistOutputBuf *obuf)
fobuf = obuf;
obuf = obuf->next;
obufsize += size_obuf(fobuf);
- free_dist_obuf(fobuf);
+ free_dist_obuf(fobuf, !0);
}
if (obufsize) {
@@ -880,7 +1240,7 @@ int erts_dsend_context_dtor(Binary* ctx_bin)
if (ctx->phase >= ERTS_DSIG_SEND_PHASE_ALLOC && ctx->obuf) {
int i;
for (i = 0; i < ctx->fragments; i++)
- free_dist_obuf(&ctx->obuf[i]);
+ free_dist_obuf(&ctx->obuf[i], !0);
}
if (ctx->deref_dep)
erts_deref_dist_entry(ctx->dep);
@@ -932,9 +1292,56 @@ erts_dsig_send_link(ErtsDSigSendContext *ctx, Eterm local, Eterm remote)
}
int
-erts_dsig_send_unlink(ErtsDSigSendContext *ctx, Eterm local, Eterm remote)
+erts_dsig_send_unlink(ErtsDSigSendContext *ctx, Eterm local, Eterm remote, Uint64 id)
{
- Eterm ctl = TUPLE3(&ctx->ctl_heap[0], make_small(DOP_UNLINK), local, remote);
+ Eterm big_heap[ERTS_MAX_UINT64_HEAP_SIZE];
+ Eterm unlink_id;
+ Eterm ctl;
+ if (ctx->dflags & DFLAG_UNLINK_ID) {
+ if (IS_USMALL(0, id))
+ unlink_id = make_small(id);
+ else {
+ Eterm *hp = &big_heap[0];
+ unlink_id = erts_uint64_to_big(id, &hp);
+ }
+ ctl = TUPLE4(&ctx->ctl_heap[0], make_small(DOP_UNLINK_ID),
+ unlink_id, local, remote);
+ }
+ else {
+ /*
+ * A node that isn't capable of talking the new link protocol.
+ *
+ * Send an old unlink op, and send ourselves an unlink-ack. We may
+ * end up in an inconsistent state as we could before the new link
+ * protocol was introduced...
+ */
+ erts_proc_sig_send_dist_unlink_ack(ctx->c_p, ctx->dep, ctx->connection_id,
+ remote, local, id);
+ ctl = TUPLE3(&ctx->ctl_heap[0], make_small(DOP_UNLINK), local, remote);
+ }
+ return dsig_send_ctl(ctx, ctl);
+}
+
+int
+erts_dsig_send_unlink_ack(ErtsDSigSendContext *ctx, Eterm local, Eterm remote, Uint64 id)
+{
+ Eterm big_heap[ERTS_MAX_UINT64_HEAP_SIZE];
+ Eterm unlink_id;
+ Eterm ctl;
+
+ if (!(ctx->dflags & DFLAG_UNLINK_ID)) {
+ /* Receiving node does not understand it, so drop it... */
+ return ERTS_DSIG_SEND_OK;
+ }
+
+ if (IS_USMALL(0, id))
+ unlink_id = make_small(id);
+ else {
+ Eterm *hp = &big_heap[0];
+ unlink_id = erts_uint64_to_big(id, &hp);
+ }
+ ctl = TUPLE4(&ctx->ctl_heap[0], make_small(DOP_UNLINK_ID_ACK),
+ unlink_id, local, remote);
return dsig_send_ctl(ctx, ctl);
}
@@ -947,14 +1354,14 @@ erts_dsig_send_m_exit(ErtsDSigSendContext *ctx, Eterm watcher, Eterm watched,
{
Eterm ctl, msg;
- if (~ctx->flags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
+ if (~ctx->dflags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
/*
* Receiver does not support DOP_MONITOR_P_EXIT (see dsig_send_monitor)
*/
return ERTS_DSIG_SEND_OK;
}
- if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD) {
+ if (ctx->dep->dflags & DFLAG_EXIT_PAYLOAD) {
ctl = TUPLE4(&ctx->ctl_heap[0], make_small(DOP_PAYLOAD_MONITOR_P_EXIT),
watched, watcher, ref);
msg = reason;
@@ -976,7 +1383,7 @@ erts_dsig_send_monitor(ErtsDSigSendContext *ctx, Eterm watcher, Eterm watched,
{
Eterm ctl;
- if (~ctx->flags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
+ if (~ctx->dflags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
/*
* Receiver does not support DOP_MONITOR_P.
* Just avoid sending it and by doing that reduce this monitor
@@ -1002,7 +1409,7 @@ erts_dsig_send_demonitor(ErtsDSigSendContext *ctx, Eterm watcher,
{
Eterm ctl;
- if (~ctx->flags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
+ if (~ctx->dflags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
/*
* Receiver does not support DOP_DEMONITOR_P (see dsig_send_monitor)
*/
@@ -1019,7 +1426,7 @@ erts_dsig_send_demonitor(ErtsDSigSendContext *ctx, Eterm watcher,
static int can_send_seqtrace_token(ErtsDSigSendContext* ctx, Eterm token) {
Eterm label;
- if (ctx->flags & DFLAG_BIG_SEQTRACE_LABELS) {
+ if (ctx->dflags & DFLAG_BIG_SEQTRACE_LABELS) {
/* The other end is capable of handling arbitrary seq_trace labels. */
return 1;
}
@@ -1052,7 +1459,7 @@ erts_dsig_send_msg(ErtsDSigSendContext* ctx, Eterm remote, Eterm message)
#endif
if (have_seqtrace(SEQ_TRACE_TOKEN(sender))) {
- seq_trace_update_send(sender);
+ seq_trace_update_serial(sender);
token = SEQ_TRACE_TOKEN(sender);
seq_trace_output(token, message, SEQ_TRACE_SEND, remote, sender);
}
@@ -1080,7 +1487,7 @@ erts_dsig_send_msg(ErtsDSigSendContext* ctx, Eterm remote, Eterm message)
send_token = (token != NIL && can_send_seqtrace_token(ctx, token));
- if (ctx->flags & DFLAG_SEND_SENDER) {
+ if (ctx->dflags & DFLAG_SEND_SENDER) {
dist_op = make_small(send_token ?
DOP_SEND_SENDER_TT :
DOP_SEND_SENDER);
@@ -1127,7 +1534,7 @@ erts_dsig_send_reg_msg(ErtsDSigSendContext* ctx, Eterm remote_name,
#endif
if (have_seqtrace(SEQ_TRACE_TOKEN(sender))) {
- seq_trace_update_send(sender);
+ seq_trace_update_serial(sender);
token = SEQ_TRACE_TOKEN(sender);
seq_trace_output(token, message, SEQ_TRACE_SEND, full_to, sender);
}
@@ -1182,20 +1589,20 @@ erts_dsig_send_exit_tt(ErtsDSigSendContext *ctx, Eterm local, Eterm remote,
DTRACE_CHARBUF(reason_str, 128);
#endif
- if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD)
+ if (ctx->dep->dflags & DFLAG_EXIT_PAYLOAD)
msg = reason;
if (have_seqtrace(token)) {
- seq_trace_update_send(ctx->c_p);
+ seq_trace_update_serial(ctx->c_p);
seq_trace_output_exit(token, reason, SEQ_TRACE_SEND, remote, local);
- if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD) {
+ if (ctx->dep->dflags & DFLAG_EXIT_PAYLOAD) {
ctl = TUPLE4(&ctx->ctl_heap[0],
make_small(DOP_PAYLOAD_EXIT_TT), local, remote, token);
} else
ctl = TUPLE5(&ctx->ctl_heap[0],
make_small(DOP_EXIT_TT), local, remote, token, reason);
} else {
- if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD)
+ if (ctx->dep->dflags & DFLAG_EXIT_PAYLOAD)
ctl = TUPLE3(&ctx->ctl_heap[0], make_small(DOP_PAYLOAD_EXIT), local, remote);
else
ctl = TUPLE4(&ctx->ctl_heap[0], make_small(DOP_EXIT), local, remote, reason);
@@ -1226,9 +1633,9 @@ erts_dsig_send_exit_tt(ErtsDSigSendContext *ctx, Eterm local, Eterm remote,
int
erts_dsig_send_exit(ErtsDSigSendContext *ctx, Eterm local, Eterm remote, Eterm reason)
{
- Eterm ctl, msg = ctx->dep->flags & DFLAG_EXIT_PAYLOAD ? reason : THE_NON_VALUE;
+ Eterm ctl, msg = ctx->dep->dflags & DFLAG_EXIT_PAYLOAD ? reason : THE_NON_VALUE;
- if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD) {
+ if (ctx->dep->dflags & DFLAG_EXIT_PAYLOAD) {
ctl = TUPLE3(&ctx->ctl_heap[0], make_small(DOP_PAYLOAD_EXIT), local, remote);
msg = reason;
} else {
@@ -1243,7 +1650,7 @@ erts_dsig_send_exit2(ErtsDSigSendContext *ctx, Eterm local, Eterm remote, Eterm
{
Eterm ctl, msg;
- if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD) {
+ if (ctx->dep->dflags & DFLAG_EXIT_PAYLOAD) {
ctl = TUPLE3(&ctx->ctl_heap[0],
make_small(DOP_PAYLOAD_EXIT2), local, remote);
msg = reason;
@@ -1268,6 +1675,65 @@ erts_dsig_send_group_leader(ErtsDSigSendContext *ctx, Eterm leader, Eterm remote
return dsig_send_ctl(ctx, ctl);
}
+static int
+dsig_send_spawn_request(ErtsDSigSendContext *ctx, Eterm ref, Eterm from,
+ Eterm gl, Eterm mfa, Eterm alist, Eterm opts)
+{
+ Process *sender = ctx->c_p;
+ if (!have_seqtrace(SEQ_TRACE_TOKEN(sender))) {
+ ctx->ctl = TUPLE6(&ctx->ctl_heap[0], make_small(DOP_SPAWN_REQUEST),
+ ref, from, gl, mfa, opts);
+ }
+ else {
+ Eterm tmp_heap[8];
+ Eterm node = ctx->dep ? ctx->dep->sysname : ctx->node;
+ Eterm msg;
+ Eterm token;
+ /*
+ * Present this as two messages for the sequence tracing.
+ * All data except the argument list in the first message
+ * and then the argument list as second message (which
+ * willl become an actual message). For more info see
+ * handling of seq-trace token in erl_create_process().
+ */
+
+ seq_trace_update_serial(sender);
+ token = SEQ_TRACE_TOKEN(sender);
+ msg = TUPLE6(&tmp_heap[0], am_spawn_request,
+ ref, from, gl, mfa, opts);
+ seq_trace_output(token, msg, SEQ_TRACE_SEND, node, sender);
+
+ seq_trace_update_serial(sender);
+ token = SEQ_TRACE_TOKEN(sender);
+ seq_trace_output(token, alist, SEQ_TRACE_SEND, node, sender);
+
+ ctx->ctl = TUPLE7(&ctx->ctl_heap[0], make_small(DOP_SPAWN_REQUEST_TT),
+ ref, from, gl, mfa, opts, token);
+ }
+ ctx->msg = alist;
+ return erts_dsig_send(ctx);
+}
+
+int
+erts_dsig_send_spawn_reply(ErtsDSigSendContext *ctx,
+ Eterm ref,
+ Eterm to,
+ Eterm flags,
+ Eterm result,
+ Eterm token)
+{
+ if (!have_seqtrace(token)) {
+ ctx->ctl = TUPLE5(&ctx->ctl_heap[0], make_small(DOP_SPAWN_REPLY),
+ ref, to, flags, result);
+ }
+ else {
+ ctx->ctl = TUPLE6(&ctx->ctl_heap[0], make_small(DOP_SPAWN_REPLY_TT),
+ ref, to, flags, result, token);
+ }
+ ctx->msg = THE_NON_VALUE;
+ return erts_dsig_send(ctx);
+}
+
#define ERTS_RBT_PREFIX dist_seq
#define ERTS_RBT_T DistSeqNode
#define ERTS_RBT_KEY_T Uint
@@ -1597,25 +2063,31 @@ int erts_net_message(Port *prt,
/* old incarnation of node; reply noproc... */
}
else if (is_internal_pid(to)) {
- ErtsLinkData *ldp = erts_link_create(ERTS_LNK_TYPE_DIST_PROC,
- from, to);
- ASSERT(ldp->a.other.item == to);
- ASSERT(eq(ldp->b.other.item, from));
- code = erts_link_dist_insert(&ldp->a, dep->mld);
- ASSERT(code);
-
- if (erts_proc_sig_send_link(NULL, to, &ldp->b))
- break; /* done */
+ ErtsLinkData *ldp = erts_link_external_create(ERTS_LNK_TYPE_DIST_PROC,
+ to, from);
+ ASSERT(ldp->dist.other.item == to);
+ ASSERT(eq(ldp->proc.other.item, from));
+
+ code = erts_link_dist_insert(&ldp->dist, ede.mld);
+ if (erts_proc_sig_send_link(NULL, to, &ldp->proc)) {
+ if (!code) {
+ /* Race: connection already down => send link exit */
+ erts_proc_sig_send_link_exit(NULL, THE_NON_VALUE, &ldp->dist,
+ am_noconnection, NIL);
+ }
+ break; /* Done */
+ }
/* Failed to send signal; cleanup and reply noproc... */
-
- code = erts_link_dist_delete(&ldp->a);
- ASSERT(code);
+ if (code) {
+ code = erts_link_dist_delete(&ldp->dist);
+ ASSERT(code);
+ }
erts_link_release_both(ldp);
}
code = erts_dsig_prepare(&ctx, dep, NULL, 0, ERTS_DSP_NO_LOCK, 1, 1, 0);
- if (code == ERTS_DSIG_PREP_CONNECTED) {
+ if (code == ERTS_DSIG_PREP_CONNECTED && ctx.connection_id == conn_id) {
code = erts_dsig_send_exit(&ctx, to, from, am_noproc);
ASSERT(code == ERTS_DSIG_SEND_OK);
}
@@ -1623,12 +2095,29 @@ int erts_net_message(Port *prt,
break;
}
- case DOP_UNLINK: {
- if (tuple_arity != 3) {
+ case DOP_UNLINK_ID: {
+ Eterm *element;
+ Uint64 id;
+ if (tuple_arity != 4)
goto invalid_message;
- }
- from = tuple[2];
- to = tuple[3];
+
+ element = &tuple[2];
+ if (!term_to_Uint64(*(element++), &id))
+ goto invalid_message;
+
+ if (id == 0)
+ goto invalid_message;
+
+ if (0) {
+ case DOP_UNLINK:
+ if (tuple_arity != 3)
+ goto invalid_message;
+ element = &tuple[2];
+ id = 0;
+ }
+
+ from = *(element++);
+ to = *element;
if (is_not_external_pid(from))
goto invalid_message;
if (dep != external_pid_dist_entry(from))
@@ -1641,10 +2130,37 @@ int erts_net_message(Port *prt,
if (is_not_internal_pid(to))
goto invalid_message;
- erts_proc_sig_send_dist_unlink(dep, from, to);
+ erts_proc_sig_send_dist_unlink(dep, conn_id, from, to, id);
break;
}
+ case DOP_UNLINK_ID_ACK: {
+ Uint64 id;
+ if (tuple_arity != 4)
+ goto invalid_message;
+
+ if (!term_to_Uint64(tuple[2], &id))
+ goto invalid_message;
+
+ from = tuple[3];
+ to = tuple[4];
+ if (is_not_external_pid(from))
+ goto invalid_message;
+ if (dep != external_pid_dist_entry(from))
+ goto invalid_message;
+
+ if (is_external_pid(to)
+ && erts_this_dist_entry == external_pid_dist_entry(from))
+ break;
+
+ if (is_not_internal_pid(to))
+ goto invalid_message;
+
+ erts_proc_sig_send_dist_unlink_ack(NULL, dep, conn_id,
+ from, to, id);
+ break;
+ }
+
case DOP_MONITOR_P: {
/* A remote process wants to monitor us, we get:
{DOP_MONITOR_P, Remote pid, local pid or name, ref} */
@@ -1690,8 +2206,11 @@ int erts_net_message(Port *prt,
mdp = erts_monitor_create(ERTS_MON_TYPE_DIST_PROC,
ref, watcher, pid, name);
- code = erts_monitor_dist_insert(&mdp->origin, dep->mld);
- ASSERT(code); (void)code;
+ if (!erts_monitor_dist_insert(&mdp->origin, ede.mld)) {
+ /* Race: connection down => do nothing */
+ erts_monitor_release_both(mdp);
+ break;
+ }
if (erts_proc_sig_send_monitor(&mdp->target, pid))
break; /* done */
@@ -1705,7 +2224,7 @@ int erts_net_message(Port *prt,
}
code = erts_dsig_prepare(&ctx, dep, NULL, 0, ERTS_DSP_NO_LOCK, 1, 1, 0);
- if (code == ERTS_DSIG_PREP_CONNECTED) {
+ if (code == ERTS_DSIG_PREP_CONNECTED && ctx.connection_id == conn_id) {
code = erts_dsig_send_m_exit(&ctx, watcher, watched, ref, am_noproc);
ASSERT(code == ERTS_DSIG_SEND_OK);
}
@@ -1741,16 +2260,17 @@ int erts_net_message(Port *prt,
;
}
else if (is_atom(watched)) {
- ErtsMonLnkDist *mld = dep->mld;
ErtsMonitor *mon;
- erts_mtx_lock(&mld->mtx);
-
- mon = erts_monitor_tree_lookup(mld->orig_name_monitors, ref);
- if (mon)
- erts_monitor_tree_delete(&mld->orig_name_monitors, mon);
-
- erts_mtx_unlock(&mld->mtx);
+ erts_mtx_lock(&ede.mld->mtx);
+ if (ede.mld->alive) {
+ mon = erts_monitor_tree_lookup(ede.mld->orig_name_monitors, ref);
+ if (mon)
+ erts_monitor_tree_delete(&ede.mld->orig_name_monitors, mon);
+ }
+ else
+ mon = NULL;
+ erts_mtx_unlock(&ede.mld->mtx);
if (mon)
erts_proc_sig_send_demonitor(mon);
@@ -1832,7 +2352,7 @@ int erts_net_message(Port *prt,
* the atom '' (empty cookie).
*/
ASSERT((type == DOP_SEND_SENDER || type == DOP_SEND_SENDER_TT)
- ? (is_pid(tuple[2]) && (dep->flags & DFLAG_SEND_SENDER))
+ ? (is_pid(tuple[2]) && (dep->dflags & DFLAG_SEND_SENDER))
: tuple[2] == am_Empty);
#ifdef ERTS_DIST_MSG_DBG
@@ -2054,6 +2574,253 @@ int erts_net_message(Port *prt,
(void) erts_proc_sig_send_group_leader(NULL, to, from, NIL);
break;
+ case DOP_SPAWN_REQUEST_TT: {
+ Eterm tmp_heap[2];
+ ErlSpawnOpts so;
+ int code, opts_error;
+ Eterm pid, error, ref, from, gl, mfa, opts, token, args;
+ /* {DOP_SPAWN_REQUEST_TT, Ref, From, GL, MFA, Opts, Token} */
+
+ if (tuple_arity != 7)
+ goto invalid_message;
+
+ token = tuple[7];
+
+ if (0) {
+
+ case DOP_SPAWN_REQUEST:
+ /* {DOP_SPAWN_REQUEST, Ref, From, GL, MFA, Opts} */
+ if (tuple_arity != 6)
+ goto invalid_message;
+
+ token = NIL;
+ }
+
+ ref = tuple[2];
+ from = tuple[3];
+ gl = tuple[4];
+ mfa = tuple[5];
+ opts = tuple[6];
+
+ if (is_not_external_ref(ref))
+ goto invalid_message;
+ if (is_not_external_pid(from))
+ goto invalid_message;
+ if (is_not_pid(gl))
+ goto invalid_message;
+ if (is_not_tuple_arity(mfa, 3))
+ goto invalid_message;
+ else {
+ Eterm *tp = tuple_val(tuple[5]);
+ if (is_not_atom(tp[1]))
+ goto invalid_message;
+ if (is_not_atom(tp[2]))
+ goto invalid_message;
+ if (is_not_small(tp[3]))
+ goto invalid_message;
+ }
+
+ opts_error = erts_parse_spawn_opts(&so, opts, NULL, 0);
+ if (opts_error) {
+ ErtsDSigSendContext ctx;
+ if (opts_error > 1)
+ goto invalid_message;
+ error = am_badopt;
+ dist_spawn_error:
+ if (ede_hfrag) {
+ erts_free_dist_ext_copy(erts_get_dist_ext(ede_hfrag));
+ free_message_buffer(ede_hfrag);
+ }
+ if (have_seqtrace(token)) {
+ /*
+ * See erl_create_process() for why we do this
+ * serial trickery...
+ */
+ Eterm tmp_heap[7];
+ Eterm seq_msg;
+ Eterm serial;
+ Uint serial_num;
+ /* Receiver spawn_request... */
+ serial = SEQ_TRACE_T_SERIAL(token);
+ serial_num = unsigned_val(serial);
+ serial_num--;
+ serial = make_small(serial_num);
+ SEQ_TRACE_T_SERIAL(token) = serial;
+ seq_msg = TUPLE6(&tmp_heap[0], am_spawn_request,
+ ref, from, gl, mfa, opts);
+ seq_trace_output(token, seq_msg, SEQ_TRACE_RECEIVE,
+ erts_this_dist_entry->sysname, NULL);
+ SEQ_TRACE_T_LASTCNT(token) = serial;
+ /* Send spawn_reply... */
+ erts_seq_trace_update_node_token(token);
+ seq_msg = TUPLE4(&tmp_heap[0],
+ am_spawn_reply, ref, am_error, error);
+ seq_trace_output(token, seq_msg, SEQ_TRACE_SEND, from, NULL);
+ }
+ code = erts_dsig_prepare(&ctx, dep, NULL, 0,
+ ERTS_DSP_NO_LOCK, 1, 1, 0);
+ if (code == ERTS_DSIG_PREP_CONNECTED
+ && ctx.connection_id == conn_id) {
+ code = erts_dsig_send_spawn_reply(&ctx,
+ tuple[2],
+ tuple[3],
+ make_small(0),
+ error,
+ token);
+ ASSERT(code == ERTS_DSIG_SEND_OK);
+ }
+ break;
+ }
+
+ so.mref = ref;
+ so.tag = am_spawn_reply;
+ so.parent_id = from;
+ so.group_leader = gl;
+ so.mfa = mfa;
+ so.dist_entry = dep;
+ so.mld = ede.mld;
+ so.edep = edep;
+ so.ede_hfrag = ede_hfrag;
+ so.token = token;
+ so.opts = opts;
+
+ args = CONS(&tmp_heap[0], mfa, NIL);
+ pid = erl_create_process(NULL,
+ am_erts_internal,
+ am_dist_spawn_init,
+ args,
+ &so);
+ if (is_non_value(pid)) {
+ if (so.error_code == SYSTEM_LIMIT)
+ error = am_system_limit;
+ else
+ goto invalid_message; /* should not happen */
+ goto dist_spawn_error;
+ }
+
+ break;
+ }
+
+ case DOP_SPAWN_REPLY_TT: {
+ ErtsLinkData *ldp;
+ ErtsLink *lnk;
+ int monitor;
+ int link_inserted;
+ Eterm ref, result, flags_term, parent, token;
+ Uint flags;
+
+ /* {DOP_SPAWN_REPLY_TT, Ref, To, Flags, From, Token} */
+ if (tuple_arity != 6)
+ goto invalid_message;
+
+ token = tuple[6];
+
+ if (0) {
+ case DOP_SPAWN_REPLY:
+
+ /* {DOP_SPAWN_REPLY, Ref, To, Flags, From} */
+ if (tuple_arity != 5)
+ goto invalid_message;
+
+ token = NIL;
+ }
+
+ ldp = NULL;
+ lnk = NULL;
+ link_inserted = 0;
+ monitor = 0;
+
+ ref = tuple[2];
+ parent = tuple[3];
+ flags_term = tuple[4];
+ result = tuple[5];
+
+ if (is_not_internal_pid(parent)) {
+ if (is_external_pid(parent)) {
+ DistEntry *dep = external_pid_dist_entry(parent);
+ if (dep == erts_this_dist_entry)
+ break; /* Old incarnation of this node... */
+ }
+ goto invalid_message;
+ }
+
+ if (is_not_internal_ref(ref)) {
+ if (is_external_ref(ref)) {
+ DistEntry *dep = external_ref_dist_entry(ref);
+ if (dep == erts_this_dist_entry)
+ break; /* Old incarnation of this node... */
+ }
+ goto invalid_message;
+ }
+
+ if (is_not_small(flags_term))
+ goto invalid_message;
+
+ flags = unsigned_val(flags_term);
+ if (flags >= (1 << 27))
+ goto invalid_message;
+
+ if (is_not_external_pid(result) && is_not_atom(result))
+ goto invalid_message;
+
+ if (is_external_pid(result)) {
+
+ monitor = !!(flags & ERTS_DIST_SPAWN_FLAG_MONITOR);
+
+ if (flags & ERTS_DIST_SPAWN_FLAG_LINK) {
+ /* Successful spawn-link... */
+ ldp = erts_link_external_create(ERTS_LNK_TYPE_DIST_PROC,
+ parent, result);
+ ASSERT(ldp->dist.other.item == parent);
+ ASSERT(eq(ldp->proc.other.item, result));
+ link_inserted = erts_link_dist_insert(&ldp->dist, ede.mld);
+ lnk = &ldp->proc;
+ }
+ }
+
+ if (!erts_proc_sig_send_dist_spawn_reply(dep->sysname, ref,
+ parent, lnk, result,
+ token)) {
+ ErtsDSigSendContext ctx;
+ int code;
+
+ if (monitor) {
+ code = erts_dsig_prepare(&ctx, dep, NULL, 0,
+ ERTS_DSP_NO_LOCK, 1, 1, 0);
+ if (code == ERTS_DSIG_PREP_CONNECTED
+ && ctx.connection_id == conn_id) {
+ code = erts_dsig_send_demonitor(&ctx, parent,
+ result, ref);
+ ASSERT(code == ERTS_DSIG_SEND_OK);
+ }
+ }
+
+ if (lnk) {
+ if (link_inserted) {
+ code = erts_link_dist_delete(&ldp->dist);
+ ASSERT(code);
+ }
+ erts_link_release_both(ldp);
+ }
+
+ if (flags & ERTS_DIST_SPAWN_FLAG_LINK) {
+ /*
+ * Save info so the terminating parent can send us
+ * an exit signal with the correct exit reason...
+ */
+ dist_pend_spawn_exit_save_child_result(result,
+ ref,
+ dep->mld);
+ }
+ }
+ else if (lnk && !link_inserted) {
+ erts_proc_sig_send_link_exit(NULL, THE_NON_VALUE, &ldp->dist,
+ am_noconnection, NIL);
+ }
+
+ break;
+ }
+
default:
goto invalid_message;
}
@@ -2201,7 +2968,7 @@ retry:
ctx->connection_id = dep->connection_id;
ctx->no_suspend = no_suspend;
ctx->no_trap = no_trap;
- ctx->flags = dep->flags;
+ ctx->dflags = dep->dflags;
ctx->return_term = am_true;
ctx->phase = ERTS_DSIG_SEND_PHASE_INIT;
ctx->from = proc ? proc->common.id : am_undefined;
@@ -2255,8 +3022,6 @@ erts_dsig_send(ErtsDSigSendContext *ctx)
while (1) {
switch (ctx->phase) {
case ERTS_DSIG_SEND_PHASE_INIT:
- ctx->flags = ctx->flags;
- ctx->c_p = ctx->c_p;
if (!ctx->c_p) {
ctx->no_trap = 1;
@@ -2270,13 +3035,11 @@ erts_dsig_send(ErtsDSigSendContext *ctx)
if (!erts_is_alive)
return ERTS_DSIG_SEND_OK;
- if (ctx->flags & DFLAG_DIST_HDR_ATOM_CACHE) {
+ if (ctx->dflags & DFLAG_DIST_HDR_ATOM_CACHE) {
ctx->acmp = erts_get_atom_cache_map(ctx->c_p);
- ctx->max_finalize_prepend = 0;
}
else {
ctx->acmp = NULL;
- ctx->max_finalize_prepend = 3;
}
#ifdef ERTS_DIST_MSG_DBG
@@ -2287,176 +3050,212 @@ erts_dsig_send(ErtsDSigSendContext *ctx)
erts_fprintf(dbg_file, " MSG: %.160T\n", ctx->msg);
#endif
- ctx->data_size = ctx->max_finalize_prepend;
+ ctx->data_size = 0;
erts_reset_atom_cache_map(ctx->acmp);
- switch (erts_encode_dist_ext_size(ctx->ctl, ctx->flags,
- ctx->acmp, &ctx->data_size)) {
- case ERTS_EXT_SZ_OK:
- break;
- case ERTS_EXT_SZ_SYSTEM_LIMIT:
- retval = ERTS_DSIG_SEND_TOO_LRG;
- goto done;
- case ERTS_EXT_SZ_YIELD:
- ERTS_INTERNAL_ERROR("Unexpected yield result");
- break;
+ ERTS_INIT_TTBSizeContext(&ctx->u.sc, ctx->dflags);
+
+ while (1) {
+ ErtsExtSzRes sz_res;
+ Sint reds = CONTEXT_REDS;
+ sz_res = erts_encode_dist_ext_size(ctx->ctl,
+ ctx->acmp,
+ &ctx->u.sc,
+ &ctx->data_size,
+ &reds,
+ &ctx->vlen,
+ &ctx->fragments);
+ ctx->reds -= CONTEXT_REDS - reds;
+ if (sz_res == ERTS_EXT_SZ_OK)
+ break;
+ if (sz_res == ERTS_EXT_SZ_SYSTEM_LIMIT) {
+ retval = ERTS_DSIG_SEND_TOO_LRG;
+ goto done;
+ }
}
+
if (is_non_value(ctx->msg)) {
ctx->phase = ERTS_DSIG_SEND_PHASE_ALLOC;
break;
}
- ctx->u.sc.wstack.wstart = NULL;
- ctx->u.sc.flags = ctx->flags;
- ctx->u.sc.level = 0;
ctx->phase = ERTS_DSIG_SEND_PHASE_MSG_SIZE;
case ERTS_DSIG_SEND_PHASE_MSG_SIZE: {
- ErtsExtSzRes sz_res;
- sz_res = (!ctx->no_trap
- ? erts_encode_dist_ext_size_ctx(ctx->msg,
- ctx,
- &ctx->data_size)
- : erts_encode_dist_ext_size(ctx->msg,
- ctx->flags,
- ctx->acmp,
- &ctx->data_size));
- switch (sz_res) {
- case ERTS_EXT_SZ_OK:
- break;
- case ERTS_EXT_SZ_SYSTEM_LIMIT:
- retval = ERTS_DSIG_SEND_TOO_LRG;
- goto done;
- case ERTS_EXT_SZ_YIELD:
- if (ctx->no_trap)
- ERTS_INTERNAL_ERROR("Unexpected yield result");
+ Sint reds, *redsp;
+ if (!ctx->no_trap)
+ redsp = &ctx->reds;
+ else {
+ reds = CONTEXT_REDS;
+ redsp = &reds;
+ }
+ while (1) {
+ ErtsExtSzRes sz_res;
+ sz_res = erts_encode_dist_ext_size(ctx->msg,
+ ctx->acmp,
+ &ctx->u.sc,
+ &ctx->data_size,
+ redsp,
+ &ctx->vlen,
+ &ctx->fragments);
+ if (ctx->no_trap) {
+ ctx->reds -= CONTEXT_REDS - reds;
+ if (sz_res == ERTS_EXT_SZ_YIELD) {
+ reds = CONTEXT_REDS;
+ continue;
+ }
+ }
+ if (sz_res == ERTS_EXT_SZ_OK)
+ break;
+ if (sz_res == ERTS_EXT_SZ_SYSTEM_LIMIT) {
+ retval = ERTS_DSIG_SEND_TOO_LRG;
+ goto done;
+ }
+ ASSERT(sz_res == ERTS_EXT_SZ_YIELD);
retval = ERTS_DSIG_SEND_CONTINUE;
goto done;
}
ctx->phase = ERTS_DSIG_SEND_PHASE_ALLOC;
}
- case ERTS_DSIG_SEND_PHASE_ALLOC:
- erts_finalize_atom_cache_map(ctx->acmp, ctx->flags);
-
- if (ctx->flags & DFLAG_FRAGMENTS && is_value(ctx->msg) && is_not_immed(ctx->msg)) {
- /* Calculate the max number of fragments that are needed */
- ASSERT(is_pid(ctx->from) &&
- "from has to be a pid because it is used as sequence id");
- ctx->fragments = ctx->data_size / ERTS_DIST_FRAGMENT_SIZE + 1;
- } else
- ctx->fragments = 1;
-
- ctx->dhdr_ext_size = erts_encode_ext_dist_header_size(ctx->acmp, ctx->fragments);
-
- ctx->obuf = alloc_dist_obuf(
- ctx->dhdr_ext_size + ctx->data_size +
- (ctx->fragments-1) * ERTS_DIST_FRAGMENT_HEADER_SIZE,
- ctx->fragments);
- ctx->obuf->ext_start = &ctx->obuf->extp[0];
- ctx->obuf->ext_endp = &ctx->obuf->extp[0] + ctx->max_finalize_prepend
- + ctx->dhdr_ext_size;
-
+ case ERTS_DSIG_SEND_PHASE_ALLOC: {
+
+ erts_finalize_atom_cache_map(ctx->acmp, ctx->dflags);
+
+ ERTS_INIT_TTBEncodeContext(&ctx->u.ec, ctx->dflags);
+ ctx->dhdr_ext_size = erts_encode_ext_dist_header_size(&ctx->u.ec,
+ ctx->acmp,
+ ctx->fragments);
+ ctx->obuf = alloc_dist_obufs(&ctx->extp,
+ &ctx->u.ec,
+ ctx->dhdr_ext_size + ctx->data_size
+ + ((ctx->fragments - 1)
+ * ERTS_DIST_FRAGMENT_HEADER_SIZE),
+ ctx->fragments,
+ ctx->vlen);
+ ctx->alloced_fragments = ctx->fragments;
/* Encode internal version of dist header */
- ctx->obuf->extp = erts_encode_ext_dist_header_setup(
- ctx->obuf->ext_endp, ctx->acmp, ctx->fragments, ctx->from);
- /* Encode control message */
- erts_encode_dist_ext(ctx->ctl, &ctx->obuf->ext_endp, ctx->flags, ctx->acmp, NULL, NULL);
-
- ctx->obuf->hdrp = NULL;
- ctx->obuf->hdr_endp = NULL;
+ ctx->dhdrp = ctx->extp;
+ ctx->extp += ctx->dhdr_ext_size;
+ ctx->dhdrp = erts_encode_ext_dist_header_setup(&ctx->u.ec,
+ ctx->extp,
+ ctx->acmp,
+ ctx->fragments,
+ ctx->from);
+ ctx->dhdr_ext_size = ctx->extp - ctx->dhdrp;
+ while (1) {
+ Sint reds = CONTEXT_REDS;
+ /* Encode control message */
+ int res = erts_encode_dist_ext(ctx->ctl, &ctx->extp,
+ ctx->dflags, ctx->acmp,
+ &ctx->u.ec, &ctx->fragments,
+ &reds);
+ ctx->reds -= CONTEXT_REDS - reds;
+ if (res == 0)
+ break;
+ }
if (is_non_value(ctx->msg)) {
- ctx->obuf->msg_start = NULL;
ctx->phase = ERTS_DSIG_SEND_PHASE_FIN;
break;
}
- ctx->u.ec.flags = ctx->flags;
- ctx->u.ec.hopefull_flags = 0;
- ctx->u.ec.level = 0;
- ctx->u.ec.wstack.wstart = NULL;
- ctx->obuf->msg_start = ctx->obuf->ext_endp;
ctx->phase = ERTS_DSIG_SEND_PHASE_MSG_ENCODE;
- case ERTS_DSIG_SEND_PHASE_MSG_ENCODE:
- if (!ctx->no_trap) {
- if (erts_encode_dist_ext(ctx->msg, &ctx->obuf->ext_endp, ctx->flags,
- ctx->acmp, &ctx->u.ec, &ctx->reds)) {
+ }
+ case ERTS_DSIG_SEND_PHASE_MSG_ENCODE: {
+ Sint reds, *redsp;
+ if (!ctx->no_trap)
+ redsp = &ctx->reds;
+ else {
+ reds = CONTEXT_REDS;
+ redsp = &reds;
+ }
+ while (1) {
+ int res = erts_encode_dist_ext(ctx->msg, &ctx->extp,
+ ctx->dflags, ctx->acmp,
+ &ctx->u.ec,
+ &ctx->fragments,
+ redsp);
+ if (!ctx->no_trap) {
+ if (res == 0)
+ break;
retval = ERTS_DSIG_SEND_CONTINUE;
goto done;
}
- } else {
- erts_encode_dist_ext(ctx->msg, &ctx->obuf->ext_endp, ctx->flags,
- ctx->acmp, NULL, NULL);
+ else {
+ ctx->reds -= CONTEXT_REDS - reds;
+ if (res == 0)
+ break;
+ reds = CONTEXT_REDS;
+ }
}
ctx->phase = ERTS_DSIG_SEND_PHASE_FIN;
+ }
case ERTS_DSIG_SEND_PHASE_FIN: {
-
- ASSERT(ctx->obuf->extp < ctx->obuf->ext_endp);
- ASSERT(ctx->obuf->ext_startp <= ctx->obuf->extp - ctx->max_finalize_prepend);
- ASSERT(ctx->obuf->ext_endp <= (byte*)ctx->obuf->ext_startp + ctx->data_size + ctx->dhdr_ext_size);
-
- ctx->data_size = ctx->obuf->ext_endp - ctx->obuf->extp;
-
- ctx->obuf->hopefull_flags = ctx->u.ec.hopefull_flags;
-
- if (ctx->fragments > 1) {
- int fin_fragments;
- int i;
- byte *msg = ctx->obuf->msg_start,
- *msg_end = ctx->obuf->ext_endp,
- *hdrp = msg_end;
-
- ASSERT((ctx->obuf->hopefull_flags & ctx->flags) == ctx->obuf->hopefull_flags);
- ASSERT(get_int64(ctx->obuf->extp + 1 + 1 + 8) == ctx->fragments);
-
- /* Now that encoding is done we know how large the term will
- be so we adjust the number of fragments to send. Note that
- this can mean that only 1 fragment is sent. */
- fin_fragments = (ctx->obuf->ext_endp - ctx->obuf->msg_start + ERTS_DIST_FRAGMENT_SIZE-1) /
- ERTS_DIST_FRAGMENT_SIZE - 1;
-
+ Uint fid = ctx->fragments;
+ ErtsDistOutputBuf *obuf = ctx->obuf;
+ ErlIOVec *eiov;
+ Sint fix;
+
+ ASSERT(fid >= 1);
+ ASSERT(ctx->alloced_fragments >= ctx->fragments);
+
+ eiov = obuf[0].eiov;
+ ASSERT(eiov);
+ ASSERT(eiov->vsize >= 3);
+ ASSERT(!eiov->iov[0].iov_base);
+ ASSERT(!eiov->iov[0].iov_len);
+ ASSERT(!eiov->binv[0]);
+ ASSERT(!eiov->iov[1].iov_base);
+ ASSERT(!eiov->iov[1].iov_len);
+ ASSERT(!eiov->binv[1]);
+
+ if (ctx->alloced_fragments > 1) {
+ ASSERT(get_int64(ctx->dhdrp + 1 + 1 + 8) == ctx->alloced_fragments);
/* Update the frag_id in the DIST_FRAG_HEADER */
- put_int64(fin_fragments+1, ctx->obuf->extp + 1 + 1 + 8);
-
- if (fin_fragments > 0)
- msg += ERTS_DIST_FRAGMENT_SIZE;
- else
- msg = msg_end;
- ctx->obuf->next = &ctx->obuf[1];
- ctx->obuf->ext_endp = msg;
-
- /* Loop through all fragments, updating the output buffers
- to be correct and also writing the DIST_FRAG_CONT header. */
- for (i = 1; i < fin_fragments + 1; i++) {
- ctx->obuf[i].hopefull_flags = 0;
- ctx->obuf[i].extp = msg;
- ctx->obuf[i].ext_start = msg;
- if (msg + ERTS_DIST_FRAGMENT_SIZE > msg_end)
- ctx->obuf[i].ext_endp = msg_end;
- else {
- msg += ERTS_DIST_FRAGMENT_SIZE;
- ctx->obuf[i].ext_endp = msg;
- }
- ASSERT(ctx->obuf[i].ext_endp > ctx->obuf[i].extp);
- ctx->obuf[i].hdrp = erts_encode_ext_dist_header_fragment(
- &hdrp, fin_fragments - i + 1, ctx->from);
- ctx->obuf[i].hdr_endp = hdrp;
- ctx->obuf[i].next = &ctx->obuf[i+1];
- }
- /* If the initial fragment calculation was incorrect we free the
- remaining output buffers. */
- for (; i < ctx->fragments; i++) {
- free_dist_obuf(&ctx->obuf[i]);
- }
- if (!ctx->no_trap && !ctx->no_suspend)
- ctx->reds -= ctx->fragments;
- ctx->fragments = fin_fragments + 1;
+ put_int64(ctx->fragments, ctx->dhdrp + 1 + 1 + 8);
+ }
+
+ eiov->size += ctx->dhdr_ext_size;
+ eiov->iov[1].iov_base = ctx->dhdrp;
+ eiov->iov[1].iov_len = ctx->dhdr_ext_size;
+ erts_refc_inc(&obuf[0].bin->intern.refc, 2);
+ eiov->binv[1] = Binary2ErlDrvBinary(obuf[0].bin);
+ obuf[0].next = &obuf[1];
+
+ for (fix = 1; fix < ctx->fragments; fix++) {
+ byte *hdr = erts_encode_ext_dist_header_fragment(&ctx->extp,
+ --fid, ctx->from);
+ Uint sz = ctx->extp - hdr;
+
+ eiov = obuf[fix].eiov;
+ ASSERT(eiov);
+ ASSERT(eiov->vsize >= 3);
+ ASSERT(!eiov->iov[0].iov_base);
+ ASSERT(!eiov->iov[0].iov_len);
+ ASSERT(!eiov->binv[0]);
+ ASSERT(!eiov->iov[1].iov_base);
+ ASSERT(!eiov->iov[1].iov_len);
+ ASSERT(!eiov->binv[1]);
+
+ eiov->size += sz;
+ eiov->iov[1].iov_base = hdr;
+ eiov->iov[1].iov_len = sz;
+ erts_refc_inc(&obuf[fix].bin->intern.refc, 2);
+ eiov->binv[1] = Binary2ErlDrvBinary(obuf[fix].bin);
+ obuf[fix].next = &obuf[fix+1];
+ }
+ obuf[fix-1].next = NULL;
+ ASSERT(fid == 1);
+ /* If the initial fragment calculation was incorrect we free the
+ remaining output buffers. */
+ for (; fix < ctx->alloced_fragments; fix++) {
+ free_dist_obuf(&ctx->obuf[fix], 0);
}
ctx->phase = ERTS_DSIG_SEND_PHASE_SEND;
- if (ctx->reds <= 0) {
+ if (ctx->reds <= 0 && !ctx->no_trap) {
retval = ERTS_DSIG_SEND_CONTINUE;
goto done;
}
@@ -2478,7 +3277,7 @@ erts_dsig_send(ErtsDSigSendContext *ctx)
/* Not the same connection as when we started; drop message... */
erts_de_runlock(dep);
for (i = 0; i < ctx->fragments; i++)
- free_dist_obuf(&ctx->obuf[i]);
+ free_dist_obuf(&ctx->obuf[i], !0);
ctx->fragments = 0;
}
else {
@@ -2538,13 +3337,9 @@ erts_dsig_send(ErtsDSigSendContext *ctx)
erts_mtx_lock(&dep->qlock);
}
- if (fragments > 1) {
- if (!ctx->obuf->hdrp) {
- ASSERT(get_int64(ctx->obuf->extp + 10) == ctx->fragments);
- } else {
- ASSERT(get_int64(ctx->obuf->hdrp + 10) == ctx->fragments);
- }
- }
+ ASSERT(fragments < 2
+ || (get_int64(ctx->obuf->eiov->iov[1].iov_base + 10)
+ == ctx->fragments));
if (fragments) {
ctx->obuf[fragments-1].next = NULL;
@@ -2666,8 +3461,19 @@ dist_port_command(Port *prt, ErtsDistOutputBuf *obuf)
bufp = NULL;
}
else {
- size = obuf->ext_endp - obuf->extp;
- bufp = (char*) obuf->extp;
+ char *ptr;
+ Sint i, vlen = obuf->eiov->vsize;
+ SysIOVec *iov = obuf->eiov->iov;
+ size = obuf->eiov->size;
+ bufp = ptr = erts_alloc(ERTS_ALC_T_TMP, size);
+ for (i = 0; i < vlen; i++) {
+ Uint len = iov[i].iov_len;
+ if (len) {
+ sys_memcpy((void *) ptr, (void *) iov[i].iov_base, len);
+ ptr += len;
+ }
+ }
+ ASSERT(size == ptr - bufp);
}
#ifdef USE_VM_PROBES
@@ -2689,6 +3495,7 @@ dist_port_command(Port *prt, ErtsDistOutputBuf *obuf)
fpe_was_unmasked = erts_block_fpe();
(*prt->drv_ptr->output)((ErlDrvData) prt->drv_data, bufp, size);
erts_unblock_fpe(fpe_was_unmasked);
+ erts_free(ERTS_ALC_T_TMP, bufp);
return size;
}
@@ -2696,59 +3503,53 @@ static Uint
dist_port_commandv(Port *prt, ErtsDistOutputBuf *obuf)
{
int fpe_was_unmasked;
- ErlDrvSizeT size = 0;
- SysIOVec iov[3];
- ErlDrvBinary* bv[3];
- ErlIOVec eiov;
+ SysIOVec iov[1];
+ Uint size;
+ ErlDrvBinary* bv[1];
+ ErlIOVec eiov, *eiovp;
ERTS_CHK_NO_PROC_LOCKS;
ERTS_LC_ASSERT(erts_lc_is_port_locked(prt));
- iov[0].iov_base = NULL;
- iov[0].iov_len = 0;
- bv[0] = NULL;
-
- if (!obuf) {
- size = 0;
+ if (obuf)
+ eiovp = obuf->eiov;
+ else {
+ iov[0].iov_base = NULL;
+ iov[0].iov_len = 0;
+ bv[0] = NULL;
+ eiov.size = 0;
eiov.vsize = 1;
+ eiov.iov = iov;
+ eiov.binv = bv;
+ eiovp = &eiov;
}
- else {
- int i = 1;
- eiov.vsize = 2;
-
- if (obuf->hdrp) {
- eiov.vsize = 3;
- iov[i].iov_base = obuf->hdrp;
- iov[i].iov_len = obuf->hdr_endp - obuf->hdrp;
- size += iov[i].iov_len;
- bv[i] = Binary2ErlDrvBinary(ErtsDistOutputBuf2Binary(obuf));
-#ifdef ERTS_RAW_DIST_MSG_DBG
- erts_fprintf(dbg_file, "SEND: ");
- bw(iov[i].iov_base, iov[i].iov_len);
-#endif
- i++;
- }
+#ifdef DEBUG
+ {
+ Uint sz;
+ Sint i;
+ for (i = 0, sz = 0; i < eiovp->vsize; i++)
+ sz += eiovp->iov[i].iov_len;
+ ASSERT(sz == eiovp->size);
+ }
+#endif
- iov[i].iov_base = obuf->extp;
- iov[i].iov_len = obuf->ext_endp - obuf->extp;
#ifdef ERTS_RAW_DIST_MSG_DBG
- erts_fprintf(dbg_file, "SEND: ");
- bw(iov[i].iov_base, iov[i].iov_len);
-#endif
- size += iov[i].iov_len;
- bv[i] = Binary2ErlDrvBinary(ErtsDistOutputBuf2Binary(obuf));
+ {
+ Sint i;
+ erts_fprintf(dbg_file, "SEND: ");
+ for (i = 0; i < eiovp->vsize; i++) {
+ if (eiovp->iov[i].iov_len)
+ bw(eiovp->iov[i].iov_base, eiovp->iov[i].iov_len);
+ }
}
+#endif
- eiov.size = size;
- eiov.iov = iov;
- eiov.binv = bv;
-
- if (size > (Uint) INT_MAX)
+ if (eiovp->size > (Uint) INT_MAX)
erts_exit(ERTS_DUMP_EXIT,
"Absurdly large distribution output data buffer "
"(%beu bytes) passed.\n",
- size);
+ eiovp->size);
ASSERT(prt->drv_ptr->outputv);
@@ -2768,9 +3569,14 @@ dist_port_commandv(Port *prt, ErtsDistOutputBuf *obuf)
#endif
prt->caller = NIL;
fpe_was_unmasked = erts_block_fpe();
- (*prt->drv_ptr->outputv)((ErlDrvData) prt->drv_data, &eiov);
+ (*prt->drv_ptr->outputv)((ErlDrvData) prt->drv_data, eiovp);
erts_unblock_fpe(fpe_was_unmasked);
+ size = (Uint) eiovp->size;
+ /* Remove header used by driver... */
+ eiovp->size -= eiovp->iov[0].iov_len;
+ eiovp->iov[0].iov_base = NULL;
+ eiovp->iov[0].iov_len = 0;
return size;
}
@@ -2796,7 +3602,7 @@ erts_dist_command(Port *prt, int initial_reds)
{
Sint reds = initial_reds - ERTS_PORT_REDS_DIST_CMD_START;
enum dist_entry_state state;
- Uint32 flags;
+ Uint64 flags;
Sint qsize, obufsize = 0;
ErtsDistOutputQueue oq, foq;
DistEntry *dep = (DistEntry*) erts_prtsd_get(prt, ERTS_PRTSD_DIST_ENTRY);
@@ -2809,7 +3615,7 @@ erts_dist_command(Port *prt, int initial_reds)
erts_atomic_set_mb(&dep->dist_cmd_scheduled, 0);
erts_de_rlock(dep);
- flags = dep->flags;
+ flags = dep->dflags;
state = dep->state;
send = dep->send;
erts_de_runlock(dep);
@@ -2871,14 +3677,14 @@ erts_dist_command(Port *prt, int initial_reds)
do {
Uint size;
ErtsDistOutputBuf *fob;
+ obufsize += size_obuf(foq.first);
size = (*send)(prt, foq.first);
erts_atomic64_inc_nob(&dep->out);
esdp->io.out += (Uint64) size;
reds -= ERTS_PORT_REDS_DIST_CMD_DATA(size);
fob = foq.first;
- obufsize += size_obuf(fob);
foq.first = foq.first->next;
- free_dist_obuf(fob);
+ free_dist_obuf(fob, !0);
sched_flags = erts_atomic32_read_nob(&prt->sched.flags);
preempt = reds < 0 || (sched_flags & ERTS_PTS_FLG_EXIT);
if (sched_flags & ERTS_PTS_FLG_BUSY_PORT)
@@ -2902,7 +3708,7 @@ erts_dist_command(Port *prt, int initial_reds)
reds = erts_encode_ext_dist_header_finalize(ob, dep, flags, reds);
obufsize -= size_obuf(ob);
if (reds < 0)
- break;
+ break; /* finalize needs to be restarted... */
last_finalized = ob;
ob = ob->next;
} while (ob);
@@ -2938,24 +3744,23 @@ erts_dist_command(Port *prt, int initial_reds)
int preempt = 0;
while (oq.first && !preempt) {
ErtsDistOutputBuf *fob;
- Uint size;
+ Uint size, obsz;
obufsize += size_obuf(oq.first);
reds = erts_encode_ext_dist_header_finalize(oq.first, dep, flags, reds);
- obufsize -= size_obuf(oq.first);
- if (reds < 0) {
+ obsz = size_obuf(oq.first);
+ obufsize -= obsz;
+ if (reds < 0) { /* finalize needs to be restarted... */
preempt = 1;
break;
}
- ASSERT(oq.first->bin->orig_bytes <= (char*)oq.first->extp
- && oq.first->extp <= oq.first->ext_endp);
size = (*send)(prt, oq.first);
erts_atomic64_inc_nob(&dep->out);
esdp->io.out += (Uint64) size;
reds -= ERTS_PORT_REDS_DIST_CMD_DATA(size);
fob = oq.first;
- obufsize += size_obuf(fob);
+ obufsize += obsz;
oq.first = oq.first->next;
- free_dist_obuf(fob);
+ free_dist_obuf(fob, !0);
sched_flags = erts_atomic32_read_nob(&prt->sched.flags);
preempt = reds <= 0 || (sched_flags & ERTS_PTS_FLG_EXIT);
if ((sched_flags & ERTS_PTS_FLG_BUSY_PORT) && oq.first && !preempt)
@@ -3008,7 +3813,6 @@ erts_dist_command(Port *prt, int initial_reds)
done:
if (obufsize != 0) {
- ASSERT(obufsize > 0);
erts_mtx_lock(&dep->qlock);
#ifdef DEBUG
qsize = (Sint) erts_atomic_add_read_nob(&dep->qsize,
@@ -3060,7 +3864,7 @@ erts_dist_command(Port *prt, int initial_reds)
ErtsDistOutputBuf *fob = oq.first;
oq.first = oq.first->next;
obufsize += size_obuf(fob);
- free_dist_obuf(fob);
+ free_dist_obuf(fob, !0);
}
foq.first = NULL;
@@ -3374,13 +4178,17 @@ dist_ctrl_get_data_1(BIF_ALIST_1)
{
DistEntry *dep = ERTS_PROC_GET_DIST_ENTRY(BIF_P);
const Sint initial_reds = ERTS_BIF_REDS_LEFT(BIF_P);
- Sint reds = initial_reds, obufsize = 0;
+ Sint reds = initial_reds, obufsize = 0, ix, vlen;
ErtsDistOutputBuf *obuf;
Eterm *hp, res;
- ProcBin *pb;
erts_aint_t qsize;
Uint32 conn_id, get_size;
- Uint hsz = 0, bin_sz;
+ Uint hsz = 0, data_sz;
+ SysIOVec *iov;
+ ErlDrvBinary **binv;
+#ifdef DEBUG
+ Eterm *hendp;
+#endif
if (!dep)
BIF_ERROR(BIF_P, EXC_NOTSUP);
@@ -3412,7 +4220,6 @@ dist_ctrl_get_data_1(BIF_ALIST_1)
{
if (!dep->tmp_out_queue.first) {
ASSERT(!dep->tmp_out_queue.last);
- ASSERT(!dep->transcode_ctx);
qsize = erts_atomic_read_acqb(&dep->qsize);
if (qsize > 0) {
erts_mtx_lock(&dep->qlock);
@@ -3433,13 +4240,13 @@ dist_ctrl_get_data_1(BIF_ALIST_1)
obuf = dep->tmp_out_queue.first;
obufsize += size_obuf(obuf);
- reds = erts_encode_ext_dist_header_finalize(obuf, dep, dep->flags, reds);
+ reds = erts_encode_ext_dist_header_finalize(obuf, dep, dep->dflags, reds);
obufsize -= size_obuf(obuf);
- if (reds < 0) {
+ if (reds < 0) { /* finalize needs to be restarted... */
erts_de_runlock(dep);
if (obufsize)
erts_atomic_add_nob(&dep->qsize, (erts_aint_t) -obufsize);
- ERTS_BIF_YIELD1(bif_export[BIF_dist_ctrl_get_data_1],
+ ERTS_BIF_YIELD1(&bif_trap_export[BIF_dist_ctrl_get_data_1],
BIF_P, BIF_ARG_1);
}
@@ -3452,53 +4259,70 @@ dist_ctrl_get_data_1(BIF_ALIST_1)
erts_de_runlock(dep);
- bin_sz = obuf->ext_endp - obuf->extp + obuf->hdr_endp - obuf->hdrp;
+ vlen = obuf->eiov->vsize;
+ data_sz = obuf->eiov->size;
+ iov = obuf->eiov->iov;
+ binv = obuf->eiov->binv;
+
+#ifdef DEBUG
+ {
+ Uint dbg_sz;
+ for (ix = 0, dbg_sz = 0; ix < vlen; ix++)
+ dbg_sz += iov[ix].iov_len;
+ ASSERT(data_sz == dbg_sz);
+ }
+#endif
+
+ ASSERT(vlen >= 1);
+ ASSERT(iov[0].iov_len == 0);
+ ASSERT(!binv[0]);
+
+ hsz = 2 /* cons */ + PROC_BIN_SIZE;
+ hsz *= vlen - 1;
get_size = dep->opts & ERTS_DIST_CTRL_OPT_GET_SIZE;
if (get_size) {
hsz += 3; /* 2 tuple */
- if (!IS_USMALL(0, bin_sz))
+ if (!IS_USMALL(0, data_sz))
hsz += BIG_UINT_HEAP_SIZE;
}
- if (!obuf->hdrp) {
- hp = HAlloc(BIF_P, PROC_BIN_SIZE + hsz);
- pb = (ProcBin *) (char *) hp;
- pb->thing_word = HEADER_PROC_BIN;
- pb->size = obuf->ext_endp - obuf->extp;
- pb->next = MSO(BIF_P).first;
- MSO(BIF_P).first = (struct erl_off_heap_header*) pb;
- pb->val = ErtsDistOutputBuf2Binary(obuf);
- pb->bytes = (byte*) obuf->extp;
- pb->flags = 0;
- res = make_binary(pb);
- hp += PROC_BIN_SIZE;
- } else {
- hp = HAlloc(BIF_P, PROC_BIN_SIZE * 2 + 4 + hsz);
- pb = (ProcBin *) (char *) hp;
- pb->thing_word = HEADER_PROC_BIN;
- pb->size = obuf->ext_endp - obuf->extp;
- pb->next = MSO(BIF_P).first;
- MSO(BIF_P).first = (struct erl_off_heap_header*) pb;
- pb->val = ErtsDistOutputBuf2Binary(obuf);
- pb->bytes = (byte*) obuf->extp;
- pb->flags = 0;
- hp += PROC_BIN_SIZE;
+ hp = HAlloc(BIF_P, hsz);
+#ifdef DEBUG
+ hendp = hp + hsz;
+#endif
- res = CONS(hp, make_binary(pb), NIL);
- hp += 2;
+ res = NIL;
+
+ for (ix = vlen - 1; ix > 0; ix--) {
+ Binary *bin;
+ ProcBin *pb;
+ Eterm bin_term;
+
+ ASSERT(binv[ix]);
+ /*
+ * We intentionally avoid using sub binaries
+ * since the GC might convert those to heap
+ * binaries and by this ruin the nice preparation
+ * for usage of this data as I/O vector in
+ * nifs/drivers.
+ */
+
+ bin = ErlDrvBinary2Binary(binv[ix]);
pb = (ProcBin *) (char *) hp;
+ hp += PROC_BIN_SIZE;
pb->thing_word = HEADER_PROC_BIN;
- pb->size = obuf->hdr_endp - obuf->hdrp;
+ pb->size = (Uint) iov[ix].iov_len;
pb->next = MSO(BIF_P).first;
MSO(BIF_P).first = (struct erl_off_heap_header*) pb;
- pb->val = ErtsDistOutputBuf2Binary(obuf);
- erts_refc_inc(&pb->val->intern.refc, 1);
- pb->bytes = (byte*) obuf->hdrp;
+ pb->val = bin;
+ pb->bytes = (byte*) iov[ix].iov_base;
pb->flags = 0;
- hp += PROC_BIN_SIZE;
- res = CONS(hp, make_binary(pb), res);
+ OH_OVERHEAD(&MSO(BIF_P), pb->size / sizeof(Eterm));
+ bin_term = make_binary(pb);
+
+ res = CONS(hp, bin_term, res);
hp += 2;
}
@@ -3522,15 +4346,20 @@ dist_ctrl_get_data_1(BIF_ALIST_1)
if (get_size) {
Eterm sz_term;
- if (IS_USMALL(0, bin_sz))
- sz_term = make_small(bin_sz);
+ if (IS_USMALL(0, data_sz))
+ sz_term = make_small(data_sz);
else {
- sz_term = uint_to_big(bin_sz, hp);
+ sz_term = uint_to_big(data_sz, hp);
hp += BIG_UINT_HEAP_SIZE;
}
res = TUPLE2(hp, sz_term, res);
+ hp += 3;
}
+ ASSERT(hendp == hp);
+
+ free_dist_obuf(obuf, 0);
+
BIF_RET2(res, (initial_reds - reds));
}
@@ -3635,6 +4464,7 @@ static int doit_print_link_info(ErtsLink *lnk, void *vptdp, Sint reds)
{
struct print_to_data *ptdp = vptdp;
ErtsLink *lnk2 = erts_link_to_other(lnk, NULL);
+ ASSERT(lnk->flags & ERTS_ML_FLG_EXTENDED);
erts_print(ptdp->to, ptdp->arg, "Remote link: %T %T\n",
lnk2->other.item, lnk->other.item);
return 1;
@@ -3764,13 +4594,11 @@ int distribution_info(fmtfn_t to, void *arg) /* Called by break handler */
BIF_RETTYPE setnode_2(BIF_ALIST_2)
{
Process *net_kernel = NULL;
- Uint creation;
+ Uint32 creation;
int success;
/* valid creation ? */
- if(!term_to_Uint(BIF_ARG_2, &creation))
- goto error;
- if(creation > 3)
+ if(!term_to_Uint32(BIF_ARG_2, &creation))
goto error;
/* valid node name ? */
@@ -3810,6 +4638,7 @@ BIF_RETTYPE setnode_2(BIF_ALIST_2)
if (success) {
inc_no_nodes();
erts_set_this_node(BIF_ARG_1, (Uint32) creation);
+ erts_this_dist_entry->creation = creation;
erts_is_alive = 1;
send_nodes_mon_msgs(NULL, am_nodeup, BIF_ARG_1, am_visible, NIL);
erts_proc_lock(net_kernel, ERTS_PROC_LOCKS_ALL);
@@ -3850,7 +4679,9 @@ BIF_RETTYPE setnode_2(BIF_ALIST_2)
typedef struct {
DistEntry *dep;
- Uint flags;
+ int de_locked;
+ Uint64 dflags;
+ Uint32 creation;
Uint version;
Eterm setup_pid;
Process *net_kernel;
@@ -3858,24 +4689,26 @@ typedef struct {
static int
setup_connection_epiloge_rwunlock(Process *c_p, DistEntry *dep,
- Eterm ctrlr, Uint flags,
- Uint version, Eterm setup_pid,
+ Eterm ctrlr, Uint64 flags,
+ Uint32 creation, Eterm setup_pid,
Process *net_kernel);
static Eterm
setup_connection_distctrl(Process *c_p, void *arg,
int *redsp, ErlHeapFragment **bpp);
-BIF_RETTYPE erts_internal_create_dist_channel_4(BIF_ALIST_4)
+BIF_RETTYPE erts_internal_create_dist_channel_3(BIF_ALIST_3)
{
BIF_RETTYPE ret;
- Uint flags;
+ Uint64 flags;
Uint version;
+ Uint32 creation;
Eterm *hp, res_tag = THE_NON_VALUE, res = THE_NON_VALUE;
DistEntry *dep = NULL;
int de_locked = 0;
Port *pp = NULL;
int true_nk;
+ Eterm *tpl;
Process *net_kernel = erts_whereis_process(BIF_P, ERTS_PROC_LOCK_MAIN,
am_net_kernel,
ERTS_PROC_LOCK_STATUS,
@@ -3901,19 +4734,27 @@ BIF_RETTYPE erts_internal_create_dist_channel_4(BIF_ALIST_4)
if (!is_internal_port(BIF_ARG_2) && !is_internal_pid(BIF_ARG_2))
goto badarg;
+ if (!is_tuple_arity(BIF_ARG_3, 3))
+ goto badarg;
+
+ tpl = tuple_val(BIF_ARG_3);
+
/* Dist flags... */
- if (!is_small(BIF_ARG_3))
+ if (!term_to_Uint64(tpl[1], &flags))
goto badarg;
- flags = unsigned_val(BIF_ARG_3);
/* Version... */
- if (!is_small(BIF_ARG_4))
+ if (!is_small(tpl[2]))
goto badarg;
- version = unsigned_val(BIF_ARG_4);
+ version = unsigned_val(tpl[2]);
if (version == 0)
goto badarg;
+ /* Creation... */
+ if (!term_to_Uint32(tpl[3], &creation))
+ goto badarg;
+
if (~flags & DFLAG_DIST_MANDATORY) {
erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
erts_dsprintf(dsbufp, "%T", BIF_P->common.id);
@@ -3944,11 +4785,18 @@ BIF_RETTYPE erts_internal_create_dist_channel_4(BIF_ALIST_4)
goto system_limit; /* Should never happen!!! */
if (is_internal_pid(BIF_ARG_2)) {
+ erts_de_rwlock(dep);
+ de_locked = 1;
+ if (dep->pending_nodedown)
+ goto suspend;
+
if (BIF_P->common.id == BIF_ARG_2) {
ErtsSetupConnDistCtrl scdc;
scdc.dep = dep;
- scdc.flags = flags;
+ scdc.de_locked = 1;
+ scdc.dflags = flags;
+ scdc.creation = creation;
scdc.version = version;
scdc.setup_pid = BIF_P->common.id;
scdc.net_kernel = net_kernel;
@@ -3956,6 +4804,7 @@ BIF_RETTYPE erts_internal_create_dist_channel_4(BIF_ALIST_4)
res = setup_connection_distctrl(BIF_P, &scdc, NULL, NULL);
/* Dec of refc on net_kernel by setup_connection_distctrl() */
net_kernel = NULL;
+ de_locked = 0;
BUMP_REDS(BIF_P, 5);
dep = NULL;
@@ -3968,11 +4817,16 @@ BIF_RETTYPE erts_internal_create_dist_channel_4(BIF_ALIST_4)
else {
ErtsSetupConnDistCtrl *scdcp;
+ erts_de_rwunlock(dep);
+ de_locked = 0;
+
scdcp = erts_alloc(ERTS_ALC_T_SETUP_CONN_ARG,
sizeof(ErtsSetupConnDistCtrl));
scdcp->dep = dep;
- scdcp->flags = flags;
+ scdcp->de_locked = 0;
+ scdcp->dflags = flags;
+ scdcp->creation = creation;
scdcp->version = version;
scdcp->setup_pid = BIF_P->common.id;
scdcp->net_kernel = net_kernel;
@@ -4021,6 +4875,9 @@ BIF_RETTYPE erts_internal_create_dist_channel_4(BIF_ALIST_4)
|| is_not_nil(dep->cid))
goto badarg;
+ if(dep->pending_nodedown)
+ goto suspend;
+
erts_atomic32_read_bor_nob(&pp->state, ERTS_PORT_SFLG_DISTRIBUTION);
erts_prtsd_set(pp, ERTS_PRTSD_DIST_ENTRY, dep);
@@ -4044,7 +4901,7 @@ BIF_RETTYPE erts_internal_create_dist_channel_4(BIF_ALIST_4)
conn_id = dep->connection_id;
set_res = setup_connection_epiloge_rwunlock(BIF_P, dep, BIF_ARG_2, flags,
- version, BIF_P->common.id,
+ creation, BIF_P->common.id,
net_kernel);
/* Dec of refc on net_kernel by setup_connection_epiloge_rwunlock() */
net_kernel = NULL;
@@ -4084,6 +4941,17 @@ BIF_RETTYPE erts_internal_create_dist_channel_4(BIF_ALIST_4)
return ret;
+ suspend:
+ ASSERT(de_locked);
+ ASSERT(!dep->suspended_nodeup);
+ dep->suspended_nodeup = BIF_P;
+ erts_proc_inc_refc(BIF_P);
+ erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL);
+ ERTS_BIF_PREP_YIELD3(ret,
+ &bif_trap_export[BIF_erts_internal_create_dist_channel_3],
+ BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
+ goto done;
+
badarg:
ERTS_BIF_PREP_RET(ret, am_badarg);
goto done;
@@ -4095,8 +4963,8 @@ BIF_RETTYPE erts_internal_create_dist_channel_4(BIF_ALIST_4)
static int
setup_connection_epiloge_rwunlock(Process *c_p, DistEntry *dep,
- Eterm ctrlr, Uint flags,
- Uint version, Eterm setup_pid,
+ Eterm ctrlr, Uint64 flags,
+ Uint32 creation, Eterm setup_pid,
Process *net_kernel)
{
Eterm notify_proc = NIL;
@@ -4125,8 +4993,7 @@ setup_connection_epiloge_rwunlock(Process *c_p, DistEntry *dep,
if (!success)
return 0;
- dep->version = version;
- dep->creation = 0;
+ dep->creation = creation;
ASSERT(is_internal_port(ctrlr) || is_internal_pid(ctrlr));
ASSERT(dep->state == ERTS_DE_STATE_PENDING);
@@ -4175,7 +5042,6 @@ setup_connection_distctrl(Process *c_p, void *arg, int *redsp, ErlHeapFragment *
{
ErtsSetupConnDistCtrl *scdcp = (ErtsSetupConnDistCtrl *) arg;
DistEntry *dep = scdcp->dep;
- int dep_locked = 0;
Eterm *hp;
Uint32 conn_id;
int dec_net_kernel_on_error = !0;
@@ -4186,8 +5052,10 @@ setup_connection_distctrl(Process *c_p, void *arg, int *redsp, ErlHeapFragment *
if (ERTS_PROC_IS_EXITING(c_p))
goto badarg;
- erts_de_rwlock(dep);
- dep_locked = !0;
+ if (!scdcp->de_locked) {
+ erts_de_rwlock(dep);
+ scdcp->de_locked = !0;
+ }
if (dep->state != ERTS_DE_STATE_PENDING)
goto badarg;
@@ -4211,7 +5079,7 @@ setup_connection_distctrl(Process *c_p, void *arg, int *redsp, ErlHeapFragment *
*redsp = 5;
if (!setup_connection_epiloge_rwunlock(c_p, dep, c_p->common.id,
- scdcp->flags, scdcp->version,
+ scdcp->dflags, scdcp->creation,
scdcp->setup_pid,
scdcp->net_kernel)) {
erts_proc_lock(c_p, ERTS_PROC_LOCKS_ALL_MINOR);
@@ -4241,7 +5109,7 @@ badarg:
if (bpp) /* not called directly */
erts_free(ERTS_ALC_T_SETUP_CONN_ARG, arg);
- if (dep_locked)
+ if (scdcp->de_locked)
erts_de_rwunlock(dep);
erts_deref_dist_entry(dep);
@@ -4252,7 +5120,46 @@ badarg:
BIF_RETTYPE erts_internal_get_dflags_0(BIF_ALIST_0)
{
- return erts_dflags_record;
+ if (erts_dflags_test_remove_hopefull_flags) {
+ /* For internal emulator tests only! */
+ Eterm *hp, **hpp = NULL;
+ Uint sz = 0, *szp = &sz;
+ Eterm res;
+ while (1) {
+ res = erts_bld_tuple(hpp, szp, 6,
+ am_erts_dflags,
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_DEFAULT & ~DFLAG_DIST_HOPEFULLY),
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_MANDATORY & ~DFLAG_DIST_HOPEFULLY),
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_ADDABLE & ~DFLAG_DIST_HOPEFULLY),
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_REJECTABLE & ~DFLAG_DIST_HOPEFULLY),
+ erts_bld_uint64(hpp, szp, DFLAG_DIST_STRICT_ORDER & ~DFLAG_DIST_HOPEFULLY));
+ if (hpp) {
+ ASSERT(is_value(res));
+ return res;
+ }
+ hp = HAlloc(BIF_P, sz);
+ hpp = &hp;
+ szp = NULL;
+ }
+ }
+ return erts_get_global_literal(ERTS_LIT_DFLAGS_RECORD);
+}
+
+BIF_RETTYPE erts_internal_get_creation_0(BIF_ALIST_0)
+{
+ Eterm *hp;
+ Uint hsz = 0;
+ Uint32 cr = erts_this_dist_entry->creation;
+ Eterm ret;
+
+ if (cr == 0)
+ ret = am_undefined;
+ else {
+ erts_bld_uint(NULL, &hsz, cr);
+ hp = HAlloc(BIF_P, hsz);
+ ret = erts_bld_uint(&hp, NULL, cr);
+ }
+ return ret;
}
BIF_RETTYPE erts_internal_new_connection_1(BIF_ALIST_1)
@@ -4262,7 +5169,7 @@ BIF_RETTYPE erts_internal_new_connection_1(BIF_ALIST_1)
Eterm* hp;
Eterm dhandle;
- if (is_not_atom(BIF_ARG_1)) {
+ if (!is_node_name_atom(BIF_ARG_1)) {
BIF_ERROR(BIF_P, BADARG);
}
dep = erts_find_or_insert_dist_entry(BIF_ARG_1);
@@ -4331,7 +5238,7 @@ Sint erts_abort_pending_connection_rwunlock(DistEntry* dep,
erts_de_rwunlock(dep);
schedule_con_monitor_link_seq_cleanup(
- mld, NULL, THE_NON_VALUE,
+ NULL, mld, NULL, THE_NON_VALUE,
THE_NON_VALUE, THE_NON_VALUE);
if (resume_procs) {
@@ -4421,6 +5328,404 @@ int erts_auto_connect(DistEntry* dep, Process *proc, ErtsProcLocks proc_locks)
return 1;
}
+static BIF_RETTYPE spawn_request_yield_3(BIF_ALIST_3)
+{
+ Binary* bin = erts_magic_ref2bin(BIF_ARG_1);
+ ErtsDSigSendContext *ctx = (ErtsDSigSendContext*) ERTS_MAGIC_BIN_DATA(bin);
+ Sint initial_reds = (Sint) (ERTS_BIF_REDS_LEFT(BIF_P) * TERM_TO_BINARY_LOOP_FACTOR);
+ int code;
+
+ ASSERT(ERTS_MAGIC_BIN_DESTRUCTOR(bin) == erts_dsend_context_dtor);
+
+ ctx->reds = initial_reds;
+ code = erts_dsig_send(ctx);
+
+ switch (code) {
+ case ERTS_DSIG_SEND_OK:
+ erts_set_gc_state(BIF_P, 1);
+ BIF_RET(BIF_ARG_2);
+
+ case ERTS_DSIG_SEND_YIELD:
+ erts_set_gc_state(BIF_P, 1);
+ ERTS_BIF_YIELD_RETURN(BIF_P, BIF_ARG_2);
+
+ case ERTS_DSIG_SEND_CONTINUE: {
+ BUMP_ALL_REDS(BIF_P);
+ BIF_TRAP3(&spawn_request_yield_export, BIF_P,
+ BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
+ }
+
+ case ERTS_DSIG_SEND_TOO_LRG: {
+ ErtsMonitor *mon;
+ ErtsMonitorData *mdp;
+ Eterm ref;
+
+ erts_set_gc_state(BIF_P, 1);
+
+ if (is_internal_ordinary_ref(BIF_ARG_2))
+ ref = BIF_ARG_2;
+ else {
+ Eterm *tp;
+ ASSERT(is_tuple_arity(BIF_ARG_2, 2));
+ tp = tuple_val(BIF_ARG_2);
+ ref = tp[1];
+ ASSERT(is_internal_ordinary_ref(ref));
+ }
+
+ mon = erts_monitor_tree_lookup(ERTS_P_MONITORS(BIF_P), ref);
+ ASSERT(mon);
+ erts_monitor_tree_delete(&ERTS_P_MONITORS(BIF_P), mon);
+ mdp = erts_monitor_to_data(mon);
+ if (erts_monitor_dist_delete(&mdp->target))
+ erts_monitor_release_both(mdp);
+ else
+ erts_monitor_release(mon);
+
+ erts_send_local_spawn_reply(BIF_P, ERTS_PROC_LOCK_MAIN, NULL,
+ BIF_ARG_3, ref, am_system_limit,
+ am_undefined);
+ BIF_RET(BIF_ARG_2);
+ }
+
+ default:
+ ERTS_INTERNAL_ERROR("Invalid spawn request result");
+ BIF_ERROR(BIF_P, EXC_INTERNAL_ERROR);
+ }
+}
+
+BIF_RETTYPE erts_internal_dist_spawn_request_4(BIF_ALIST_4)
+{
+ BIF_RETTYPE ret_val;
+ Eterm node = BIF_ARG_1;
+ Eterm mfa = BIF_ARG_2;
+ Eterm opts = BIF_ARG_3;
+ Eterm tag = am_spawn_reply;
+ Eterm mod, func, alist, new_opts, error, ref,
+ ok_result;
+ Uint nargs, nopts, rm_opts, rebuild_opts;
+ DistEntry *dep = NULL;
+ Eterm list;
+ ErtsDSigSendContext ctx;
+ int code, link = 0, monitor = 0, success_message, error_message;
+
+ ok_result = THE_NON_VALUE;
+ success_message = error_message = !0;
+
+ if (!is_atom(node))
+ goto badarg;
+ dep = erts_find_or_insert_dist_entry(node);
+ if (dep == erts_this_dist_entry)
+ goto badarg;
+ if (!is_tuple_arity(mfa, 3))
+ goto badarg;
+ else {
+ Eterm *tp = tuple_val(mfa);
+ mod = tp[1];
+ func = tp[2];
+ alist = tp[3];
+ if (!is_atom(mod))
+ goto badarg;
+ if (!is_atom(func))
+ goto badarg;
+ nargs = 0;
+ list = alist;
+ while (is_list(list)) {
+ Eterm *cp = list_val(list);
+ list = CDR(cp);
+ nargs++;
+ }
+ if (!is_nil(list))
+ goto badarg;
+ }
+
+ new_opts = list = opts;
+ nopts = 0;
+ rm_opts = 0;
+ rebuild_opts = 0;
+
+ while (is_list(list)) {
+ Eterm *cp = list_val(list);
+ Eterm car = CAR(cp);
+ list = CDR(cp);
+ nopts++;
+ switch (car) {
+ case am_link:
+ link = !0;
+ break;
+ case am_monitor:
+ monitor = !0;
+ break;
+ default:
+ if (is_tuple_arity(car, 2)) {
+ Eterm *tp = tuple_val(car);
+ switch (tp[1]) {
+
+ case am_reply_tag:
+ tag = tp[2];
+
+ if (0) {
+ case am_reply:
+ switch (tp[2]) {
+ case am_error_only:
+ success_message = 0;
+ error_message = !0;
+ break;
+ case am_success_only:
+ success_message = !0;
+ error_message = 0;
+ break;
+ case am_no:
+ success_message = 0;
+ error_message = 0;
+ break;
+ case am_yes:
+ success_message = !0;
+ error_message = !0;
+ break;
+ default:
+ if (BIF_ARG_4 != am_spawn_request)
+ goto badarg;
+ ok_result = ref = erts_make_ref(BIF_P);
+ goto badopt;
+ }
+ }
+
+ if (BIF_ARG_4 != am_spawn_request)
+ goto badarg;
+
+ rm_opts++;
+ new_opts = list;
+ rebuild_opts = nopts - rm_opts;
+ break;
+
+ default:
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if (!is_nil(list))
+ goto badarg;
+
+ if (rm_opts) {
+ /*
+ * If no 'rebuild_opts', all options to drop were in
+ * the head of the 'opts' list. 'new_opts' now contain
+ * the tail of original option list without the dropped
+ * options.
+ */
+ if (rebuild_opts) {
+#ifdef DEBUG
+ Eterm reusable_tail, *hp_start;
+#endif
+ Uint rm_cnt;
+ Eterm *hp, *prev_cp;
+ /*
+ * Remove 'reply_tag' option in option list.
+ * This options are mixed with other options.
+ *
+ * We build the list backwards and reuse tail
+ * without options to remove, if such exist.
+ */
+
+ hp = HAlloc(BIF_P, 2*rebuild_opts);
+
+#ifdef DEBUG
+ hp_start = hp;
+ reusable_tail = new_opts;
+#endif
+
+ hp += 2*(rebuild_opts - 1);
+ new_opts = make_list(hp);
+ prev_cp = NULL;
+ list = opts;
+ rm_cnt = 0;
+
+ while (is_list(list)) {
+ Eterm *cp = list_val(list);
+ Eterm car = CAR(cp);
+ list = CDR(cp);
+ if (is_tuple_arity(car, 2)) {
+ Eterm *tp = tuple_val(car);
+ if (am_reply_tag == tp[1]
+ || am_reply == tp[1]) {
+ rm_cnt++;
+ /* skip option */
+ if (rm_cnt == rm_opts) {
+ ASSERT(prev_cp);
+ ASSERT(list == reusable_tail);
+ CDR(prev_cp) = list;
+ break; /* last option to skip */
+ }
+ continue;
+ }
+ }
+#ifdef DEBUG
+ rebuild_opts--;
+#endif
+ CAR(hp) = car;
+ prev_cp = hp;
+ hp -= 2;
+ CDR(prev_cp) = make_list(hp);
+ }
+ ASSERT(hp == hp_start - 2);
+ ASSERT(rebuild_opts == 0);
+ }
+
+ opts = new_opts;
+ }
+
+ /* Arguments checked; do it... */
+
+ ref = erts_make_ref(BIF_P);
+ if (BIF_ARG_4 == am_spawn_request)
+ ok_result = ref;
+ else {
+ Eterm *hp = HAlloc(BIF_P, 3);
+ ASSERT(BIF_ARG_4 == am_spawn_opt);
+ ok_result = TUPLE2(hp, ref, monitor ? am_true : am_false);
+ }
+
+ code = erts_dsig_prepare(&ctx, dep,
+ BIF_P, ERTS_PROC_LOCK_MAIN,
+ ERTS_DSP_RLOCK, 0, 0, 1);
+ switch (code) {
+ case ERTS_DSIG_PREP_NOT_ALIVE:
+ case ERTS_DSIG_PREP_NOT_CONNECTED:
+ goto noconnection;
+
+ case ERTS_DSIG_PREP_CONNECTED:
+ if (!(dep->dflags & DFLAG_SPAWN)) {
+ erts_de_runlock(dep);
+ goto notsup;
+ }
+ /* Fall through... */
+ case ERTS_DSIG_PREP_PENDING: {
+ int inserted;
+ ErtsMonitorData *mdp;
+ Eterm nargs_term, mfna, *hp;
+
+ if (IS_USMALL(0, nargs)) {
+ hp = HAlloc(BIF_P, 4);
+ nargs_term = make_small(nargs);
+ }
+ else {
+ hp = HAlloc(BIF_P, 4+BIG_UINT_HEAP_SIZE);
+ nargs_term = uint_to_big(nargs, hp);
+ hp += BIG_UINT_HEAP_SIZE;
+ }
+
+ mfna = TUPLE3(hp, mod, func, nargs_term);
+
+ mdp = erts_monitor_create(ERTS_MON_TYPE_DIST_PROC, ref,
+ BIF_P->common.id, am_pending,
+ tag);
+ if (monitor)
+ mdp->origin.flags |= ERTS_ML_FLG_SPAWN_MONITOR;
+ if (link)
+ mdp->origin.flags |= ERTS_ML_FLG_SPAWN_LINK;
+ if (!success_message)
+ mdp->origin.flags |= ERTS_ML_FLG_SPAWN_NO_SMSG;
+ if (!error_message)
+ mdp->origin.flags |= ERTS_ML_FLG_SPAWN_NO_EMSG;
+
+ erts_monitor_tree_insert(&ERTS_P_MONITORS(BIF_P),
+ &mdp->origin);
+ inserted = erts_monitor_dist_insert(&mdp->target, dep->mld);
+ ASSERT(inserted); (void)inserted;
+
+ erts_de_runlock(dep);
+
+ ctx.reds = (Sint) (ERTS_BIF_REDS_LEFT(BIF_P) * TERM_TO_BINARY_LOOP_FACTOR);
+
+ code = dsig_send_spawn_request(&ctx, ref, BIF_P->common.id,
+ BIF_P->group_leader, mfna,
+ alist, opts);
+ switch (code) {
+ case ERTS_DSIG_SEND_OK:
+ ERTS_BIF_PREP_RET(ret_val, ok_result);
+ break;
+
+ case ERTS_DSIG_SEND_YIELD:
+ ERTS_BIF_PREP_YIELD_RETURN(ret_val, BIF_P, ok_result);
+ break;
+
+ case ERTS_DSIG_SEND_CONTINUE: {
+ Eterm ctx_term;
+ /* Keep dist entry alive over trap... */
+ ctx.deref_dep = 1;
+ dep = NULL;
+
+ erts_set_gc_state(BIF_P, 0);
+ ctx_term = erts_dsend_export_trap_context(BIF_P, &ctx);
+ BUMP_ALL_REDS(BIF_P);
+ ERTS_BIF_PREP_TRAP3(ret_val, &spawn_request_yield_export,
+ BIF_P, ctx_term, ok_result, tag);
+ break;
+ }
+
+ case ERTS_DSIG_SEND_TOO_LRG: {
+ ErtsMonitor *mon;
+ ErtsMonitorData *mdp;
+
+ mon = erts_monitor_tree_lookup(ERTS_P_MONITORS(BIF_P), ref);
+ ASSERT(mon);
+ erts_monitor_tree_delete(&ERTS_P_MONITORS(BIF_P), mon);
+ mdp = erts_monitor_to_data(mon);
+ if (erts_monitor_dist_delete(&mdp->target))
+ erts_monitor_release_both(mdp);
+ else
+ erts_monitor_release(mon);
+
+ goto system_limit;
+ }
+
+ default:
+ ERTS_INTERNAL_ERROR("Invalid spawn request result");
+ ERTS_BIF_PREP_RET(ret_val, am_internal_error);
+ }
+ break;
+ }
+ default:
+ ERTS_INTERNAL_ERROR("Invalid dsig prepare result");
+ ERTS_BIF_PREP_RET(ret_val, am_internal_error);
+ break;
+ }
+
+do_return:
+
+ if (dep)
+ erts_deref_dist_entry(dep);
+
+ return ret_val;
+
+badarg:
+ ERTS_BIF_PREP_RET(ret_val, am_badarg);
+ goto do_return;
+
+system_limit:
+ error = am_system_limit;
+ goto send_error;
+noconnection:
+ error = am_noconnection;
+ goto send_error;
+notsup:
+ error = am_notsup;
+ goto send_error;
+badopt:
+ error = am_badopt;
+ /* fall through... */
+send_error:
+ ASSERT(is_value(ok_result));
+ if (error_message)
+ erts_send_local_spawn_reply(BIF_P, ERTS_PROC_LOCK_MAIN, NULL,
+ tag, ref, error, am_undefined);
+ ERTS_BIF_PREP_RET(ret_val, ok_result);
+ goto do_return;
+}
+
+
/**********************************************************************/
/* node(Object) -> Node */
@@ -4756,7 +6061,7 @@ BIF_RETTYPE monitor_node_2(BIF_ALIST_2)
BIF_RETTYPE net_kernel_dflag_unicode_io_1(BIF_ALIST_1)
{
DistEntry *de;
- Uint32 f;
+ Uint64 f;
if (is_not_pid(BIF_ARG_1)) {
BIF_ERROR(BIF_P,BADARG);
}
@@ -4766,7 +6071,7 @@ BIF_RETTYPE net_kernel_dflag_unicode_io_1(BIF_ALIST_1)
BIF_RET(am_true);
}
erts_de_rlock(de);
- f = de->flags;
+ f = de->dflags;
erts_de_runlock(de);
BIF_RET(((f & DFLAG_UNICODE_IO) ? am_true : am_false));
}
diff --git a/erts/emulator/beam/dist.h b/erts/emulator/beam/dist.h
index 129244c023..0730fb3662 100644
--- a/erts/emulator/beam/dist.h
+++ b/erts/emulator/beam/dist.h
@@ -25,46 +25,61 @@
#include "erl_node_tables.h"
#include "zlib.h"
-#define DFLAG_PUBLISHED 0x01
-#define DFLAG_ATOM_CACHE 0x02
-#define DFLAG_EXTENDED_REFERENCES 0x04
-#define DFLAG_DIST_MONITOR 0x08
-#define DFLAG_FUN_TAGS 0x10
-#define DFLAG_DIST_MONITOR_NAME 0x20
-#define DFLAG_HIDDEN_ATOM_CACHE 0x40
-#define DFLAG_NEW_FUN_TAGS 0x80
-#define DFLAG_EXTENDED_PIDS_PORTS 0x100
-#define DFLAG_EXPORT_PTR_TAG 0x200
-#define DFLAG_BIT_BINARIES 0x400
-#define DFLAG_NEW_FLOATS 0x800
-#define DFLAG_UNICODE_IO 0x1000
-#define DFLAG_DIST_HDR_ATOM_CACHE 0x2000
-#define DFLAG_SMALL_ATOM_TAGS 0x4000
-#define DFLAG_INTERNAL_TAGS 0x8000 /* used by ETS 'compressed' option */
-#define DFLAG_UTF8_ATOMS 0x10000
-#define DFLAG_MAP_TAG 0x20000
-#define DFLAG_BIG_CREATION 0x40000
-#define DFLAG_SEND_SENDER 0x80000
-#define DFLAG_BIG_SEQTRACE_LABELS 0x100000
-#define DFLAG_NO_MAGIC 0x200000 /* internal for pending connection */
-#define DFLAG_EXIT_PAYLOAD 0x400000
-#define DFLAG_FRAGMENTS 0x800000
+#define DFLAG_PUBLISHED ((Uint64)0x01)
+#define DFLAG_ATOM_CACHE ((Uint64)0x02)
+#define DFLAG_EXTENDED_REFERENCES ((Uint64)0x04)
+#define DFLAG_DIST_MONITOR ((Uint64)0x08)
+#define DFLAG_FUN_TAGS ((Uint64)0x10)
+#define DFLAG_DIST_MONITOR_NAME ((Uint64)0x20)
+#define DFLAG_HIDDEN_ATOM_CACHE ((Uint64)0x40)
+#define DFLAG_NEW_FUN_TAGS ((Uint64)0x80)
+#define DFLAG_EXTENDED_PIDS_PORTS ((Uint64)0x100)
+#define DFLAG_EXPORT_PTR_TAG ((Uint64)0x200)
+#define DFLAG_BIT_BINARIES ((Uint64)0x400)
+#define DFLAG_NEW_FLOATS ((Uint64)0x800)
+#define DFLAG_UNICODE_IO ((Uint64)0x1000)
+#define DFLAG_DIST_HDR_ATOM_CACHE ((Uint64)0x2000)
+#define DFLAG_SMALL_ATOM_TAGS ((Uint64)0x4000)
+#define DFLAG_ETS_COMPRESSED ((Uint64)0x8000) /* internal */
+#define DFLAG_UTF8_ATOMS ((Uint64)0x10000)
+#define DFLAG_MAP_TAG ((Uint64)0x20000)
+#define DFLAG_BIG_CREATION ((Uint64)0x40000)
+#define DFLAG_SEND_SENDER ((Uint64)0x80000)
+#define DFLAG_BIG_SEQTRACE_LABELS ((Uint64)0x100000)
+#define DFLAG_PENDING_CONNECT ((Uint64)0x200000) /* internal */
+#define DFLAG_EXIT_PAYLOAD ((Uint64)0x400000)
+#define DFLAG_FRAGMENTS ((Uint64)0x800000)
+#define DFLAG_HANDSHAKE_23 ((Uint64)0x1000000)
+#define DFLAG_UNLINK_ID ((Uint64)0x2000000)
+#define DFLAG_RESERVED ((Uint64)0xfc000000)
+/*
+ * As the old handshake only support 32 flag bits, we reserve the remaining
+ * bits in the lower 32 for changes in the handshake protocol or potentially
+ * new capabilities that we also want to backport to OTP-22 or older.
+ */
+#define DFLAG_SPAWN (((Uint64)0x1) << 32)
+#define DFLAG_NAME_ME (((Uint64)0x2) << 32)
+#define DFLAG_NAME_ME (((Uint64)0x2) << 32)
+
/* Mandatory flags for distribution */
#define DFLAG_DIST_MANDATORY (DFLAG_EXTENDED_REFERENCES \
| DFLAG_EXTENDED_PIDS_PORTS \
| DFLAG_UTF8_ATOMS \
- | DFLAG_NEW_FUN_TAGS)
+ | DFLAG_NEW_FUN_TAGS \
+ | DFLAG_BIG_CREATION)
/*
* Additional optimistic flags when encoding toward pending connection.
- * If remote node (erl_interface) does not supporting these then we may need
+ * If remote node (erl_interface) does not support these then we may need
* to transcode messages enqueued before connection setup was finished.
*/
#define DFLAG_DIST_HOPEFULLY (DFLAG_EXPORT_PTR_TAG \
| DFLAG_BIT_BINARIES \
| DFLAG_DIST_MONITOR \
- | DFLAG_DIST_MONITOR_NAME)
+ | DFLAG_DIST_MONITOR_NAME \
+ | DFLAG_SPAWN \
+ | DFLAG_UNLINK_ID)
/* Our preferred set of flags. Used for connection setup handshake */
#define DFLAG_DIST_DEFAULT (DFLAG_DIST_MANDATORY | DFLAG_DIST_HOPEFULLY \
@@ -75,11 +90,13 @@
| DFLAG_SMALL_ATOM_TAGS \
| DFLAG_UTF8_ATOMS \
| DFLAG_MAP_TAG \
- | DFLAG_BIG_CREATION \
| DFLAG_SEND_SENDER \
| DFLAG_BIG_SEQTRACE_LABELS \
| DFLAG_EXIT_PAYLOAD \
- | DFLAG_FRAGMENTS)
+ | DFLAG_FRAGMENTS \
+ | DFLAG_HANDSHAKE_23 \
+ | DFLAG_SPAWN \
+ | DFLAG_UNLINK_ID)
/* Flags addable by local distr implementations */
#define DFLAG_DIST_ADDABLE DFLAG_DIST_DEFAULT
@@ -87,6 +104,7 @@
/* Flags rejectable by local distr implementation */
#define DFLAG_DIST_REJECTABLE (DFLAG_DIST_HDR_ATOM_CACHE \
| DFLAG_HIDDEN_ATOM_CACHE \
+ | DFLAG_FRAGMENTS \
| DFLAG_ATOM_CACHE)
/* Flags for all features needing strict order delivery */
@@ -130,9 +148,19 @@ enum dop {
DOP_PAYLOAD_EXIT_TT = 25,
DOP_PAYLOAD_EXIT2 = 26,
DOP_PAYLOAD_EXIT2_TT = 27,
- DOP_PAYLOAD_MONITOR_P_EXIT = 28
+ DOP_PAYLOAD_MONITOR_P_EXIT = 28,
+
+ DOP_SPAWN_REQUEST = 29,
+ DOP_SPAWN_REQUEST_TT = 30,
+ DOP_SPAWN_REPLY = 31,
+ DOP_SPAWN_REPLY_TT = 32,
+ DOP_UNLINK_ID = 35,
+ DOP_UNLINK_ID_ACK = 36
};
+#define ERTS_DIST_SPAWN_FLAG_LINK (1 << 0)
+#define ERTS_DIST_SPAWN_FLAG_MONITOR (1 << 1)
+
/* distribution trap functions */
extern Export* dmonitor_node_trap;
@@ -144,7 +172,7 @@ typedef enum {
/* Must be larger or equal to 16 */
#ifdef DEBUG
-#define ERTS_DIST_FRAGMENT_SIZE 16
+#define ERTS_DIST_FRAGMENT_SIZE 1024
#else
/* This should be made configurable */
#define ERTS_DIST_FRAGMENT_SIZE (64 * 1024)
@@ -175,6 +203,9 @@ extern int erts_is_alive;
/* dist_ctrl_{g,s}et_option/2 */
#define ERTS_DIST_CTRL_OPT_GET_SIZE ((Uint32) (1 << 0))
+/* for emulator internal testing... */
+extern int erts_dflags_test_remove_hopefull_flags;
+
#ifdef DEBUG
#define ERTS_DBG_CHK_NO_DIST_LNK(D, R, L) \
erts_dbg_chk_no_dist_proc_link((D), (R), (L))
@@ -193,23 +224,75 @@ extern int erts_is_alive;
typedef enum { TTBSize, TTBEncode, TTBCompress } TTBState;
typedef struct TTBSizeContext_ {
- Uint flags;
+ Uint64 dflags;
int level;
+ Sint vlen;
+ int iovec;
+ Uint fragment_size;
+ Uint last_result;
+ Uint extra_size;
Uint result;
Eterm obj;
ErtsWStack wstack;
} TTBSizeContext;
+#define ERTS_INIT_TTBSizeContext(Ctx, Flags) \
+ do { \
+ (Ctx)->wstack.wstart = NULL; \
+ (Ctx)->dflags = (Flags); \
+ (Ctx)->level = 0; \
+ (Ctx)->vlen = -1; \
+ (Ctx)->fragment_size = ~((Uint) 0); \
+ (Ctx)->extra_size = 0; \
+ (Ctx)->last_result = 0; \
+ } while (0)
+
typedef struct TTBEncodeContext_ {
- Uint flags;
- Uint hopefull_flags;
+ Uint64 dflags;
+ Uint64 hopefull_flags;
+ byte *hopefull_flagsp;
int level;
byte* ep;
Eterm obj;
ErtsWStack wstack;
Binary *result_bin;
+ byte *cptr;
+ Sint vlen;
+ Uint size;
+ byte *payload_ixp;
+ byte *hopefull_ixp;
+ SysIOVec* iov;
+ ErlDrvBinary** binv;
+ Eterm *termv;
+ int iovec;
+ Uint fragment_size;
+ Sint frag_ix;
+ ErlIOVec *fragment_eiovs;
+#ifdef DEBUG
+ int debug_fragments;
+ int debug_vlen;
+#endif
} TTBEncodeContext;
+#define ERTS_INIT_TTBEncodeContext(Ctx, Flags) \
+ do { \
+ (Ctx)->wstack.wstart = NULL; \
+ (Ctx)->dflags = (Flags); \
+ (Ctx)->level = 0; \
+ (Ctx)->vlen = 0; \
+ (Ctx)->size = 0; \
+ (Ctx)->termv = NULL; \
+ (Ctx)->iov = NULL; \
+ (Ctx)->binv = NULL; \
+ (Ctx)->fragment_size = ~((Uint) 0); \
+ if ((Flags) & DFLAG_PENDING_CONNECT) { \
+ (Ctx)->hopefull_flags = 0; \
+ (Ctx)->hopefull_flagsp = NULL; \
+ (Ctx)->hopefull_ixp = NULL; \
+ (Ctx)->payload_ixp = NULL; \
+ } \
+ } while (0)
+
typedef struct {
Uint real_size;
Uint dest_len;
@@ -246,7 +329,7 @@ typedef struct erts_dsig_send_context {
Eterm ctl;
Eterm msg;
Eterm from;
- Eterm ctl_heap[6];
+ Eterm ctl_heap[8]; /* 7-tuple (SPAWN_REQUEST_TT) */
Eterm return_term;
DistEntry *dep;
@@ -258,12 +341,13 @@ typedef struct erts_dsig_send_context {
enum erts_dsig_send_phase phase;
Sint reds;
- Uint32 max_finalize_prepend;
Uint data_size, dhdr_ext_size;
+ byte *dhdrp, *extp;
ErtsAtomCacheMap *acmp;
ErtsDistOutputBuf *obuf;
- Uint fragments;
- Uint32 flags;
+ Uint alloced_fragments, fragments;
+ Sint vlen;
+ Uint64 dflags;
Process *c_p;
union {
TTBSizeContext sc;
@@ -298,13 +382,15 @@ extern int erts_dsig_send_msg(ErtsDSigSendContext*, Eterm, Eterm);
extern int erts_dsig_send_reg_msg(ErtsDSigSendContext*, Eterm, Eterm, Eterm);
extern int erts_dsig_send_link(ErtsDSigSendContext *, Eterm, Eterm);
extern int erts_dsig_send_exit_tt(ErtsDSigSendContext *, Eterm, Eterm, Eterm, Eterm);
-extern int erts_dsig_send_unlink(ErtsDSigSendContext *, Eterm, Eterm);
+extern int erts_dsig_send_unlink(ErtsDSigSendContext *, Eterm, Eterm, Uint64);
+extern int erts_dsig_send_unlink_ack(ErtsDSigSendContext *, Eterm, Eterm, Uint64);
extern int erts_dsig_send_group_leader(ErtsDSigSendContext *, Eterm, Eterm);
extern int erts_dsig_send_exit(ErtsDSigSendContext *, Eterm, Eterm, Eterm);
extern int erts_dsig_send_exit2(ErtsDSigSendContext *, Eterm, Eterm, Eterm);
extern int erts_dsig_send_demonitor(ErtsDSigSendContext *, Eterm, Eterm, Eterm);
extern int erts_dsig_send_monitor(ErtsDSigSendContext *, Eterm, Eterm, Eterm);
extern int erts_dsig_send_m_exit(ErtsDSigSendContext *, Eterm, Eterm, Eterm, Eterm);
+extern int erts_dsig_send_spawn_reply(ErtsDSigSendContext *, Eterm, Eterm, Eterm, Eterm, Eterm);
extern int erts_dsig_send(ErtsDSigSendContext *dsdp);
extern int erts_dsend_context_dtor(Binary*);
@@ -333,4 +419,8 @@ extern int erts_dsig_prepare(ErtsDSigSendContext *,
void erts_dist_print_procs_suspended_on_de(fmtfn_t to, void *to_arg);
int erts_auto_connect(DistEntry* dep, Process *proc, ErtsProcLocks proc_locks);
+
+Uint erts_ttb_iov_size(int use_termv, Sint vlen, Uint fragments);
+void erts_ttb_iov_init(TTBEncodeContext *ctx, int use_termv, char *ptr,
+ Sint vlen, Uint fragments, Uint fragments_size);
#endif
diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c
index 40608aae86..cda8855150 100644
--- a/erts/emulator/beam/erl_alloc.c
+++ b/erts/emulator/beam/erl_alloc.c
@@ -66,7 +66,7 @@
#define ERTS_ALC_DEFAULT_MAX_THR_PREF ERTS_MAX_NO_OF_SCHEDULERS
-#if defined(SMALL_MEMORY) || defined(PURIFY) || defined(VALGRIND)
+#if defined(SMALL_MEMORY) || defined(PURIFY) || defined(VALGRIND) || defined(ADDRESS_SANITIZER)
#define AU_ALLOC_DEFAULT_ENABLE(X) 0
#else
#define AU_ALLOC_DEFAULT_ENABLE(X) (X)
@@ -228,6 +228,7 @@ set_default_sl_alloc_opts(struct au_init *ip)
ip->astrat = ERTS_ALC_S_GOODFIT;
ip->init.util.name_prefix = "sl_";
ip->init.util.alloc_no = ERTS_ALC_A_SHORT_LIVED;
+ ip->init.util.cp = ERTS_ALC_COMMON_CPOOL_IX;
#ifndef SMALL_MEMORY
ip->init.util.mmbcs = 128*1024; /* Main carrier size */
#else
@@ -247,6 +248,7 @@ set_default_std_alloc_opts(struct au_init *ip)
ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.util.name_prefix = "std_";
ip->init.util.alloc_no = ERTS_ALC_A_STANDARD;
+ ip->init.util.cp = ERTS_ALC_COMMON_CPOOL_IX;
#ifndef SMALL_MEMORY
ip->init.util.mmbcs = 128*1024; /* Main carrier size */
#else
@@ -269,6 +271,7 @@ set_default_ll_alloc_opts(struct au_init *ip)
ip->init.util.sbct = ~((UWord) 0);
ip->init.util.name_prefix = "ll_";
ip->init.util.alloc_no = ERTS_ALC_A_LONG_LIVED;
+ ip->init.util.cp = ERTS_ALC_COMMON_CPOOL_IX;
#ifndef SMALL_MEMORY
ip->init.util.mmbcs = 2*1024*1024; /* Main carrier size */
#else
@@ -286,7 +289,11 @@ static void
set_default_literal_alloc_opts(struct au_init *ip)
{
SET_DEFAULT_ALLOC_OPTS(ip);
+#ifdef ADDRESS_SANITIZER
+ ip->enable = 0;
+#else
ip->enable = 1;
+#endif
ip->thr_spec = 0;
ip->disable_allowed = 0;
ip->thr_spec_allowed = 0;
@@ -392,6 +399,7 @@ set_default_eheap_alloc_opts(struct au_init *ip)
ip->astrat = ERTS_ALC_S_GOODFIT;
ip->init.util.name_prefix = "eheap_";
ip->init.util.alloc_no = ERTS_ALC_A_EHEAP;
+ ip->init.util.cp = ERTS_ALC_COMMON_CPOOL_IX;
#ifndef SMALL_MEMORY
ip->init.util.mmbcs = 512*1024; /* Main carrier size */
#else
@@ -411,6 +419,7 @@ set_default_binary_alloc_opts(struct au_init *ip)
ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.util.name_prefix = "binary_";
ip->init.util.alloc_no = ERTS_ALC_A_BINARY;
+ ip->init.util.cp = ERTS_ALC_COMMON_CPOOL_IX;
#ifndef SMALL_MEMORY
ip->init.util.mmbcs = 128*1024; /* Main carrier size */
#else
@@ -430,6 +439,7 @@ set_default_ets_alloc_opts(struct au_init *ip)
ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.util.name_prefix = "ets_";
ip->init.util.alloc_no = ERTS_ALC_A_ETS;
+ ip->init.util.cp = ERTS_ALC_COMMON_CPOOL_IX;
#ifndef SMALL_MEMORY
ip->init.util.mmbcs = 128*1024; /* Main carrier size */
#else
@@ -448,6 +458,7 @@ set_default_driver_alloc_opts(struct au_init *ip)
ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.util.name_prefix = "driver_";
ip->init.util.alloc_no = ERTS_ALC_A_DRIVER;
+ ip->init.util.cp = ERTS_ALC_COMMON_CPOOL_IX;
#ifndef SMALL_MEMORY
ip->init.util.mmbcs = 128*1024; /* Main carrier size */
#else
@@ -470,6 +481,7 @@ set_default_fix_alloc_opts(struct au_init *ip,
ip->init.util.name_prefix = "fix_";
ip->init.util.fix_type_size = fix_type_sizes;
ip->init.util.alloc_no = ERTS_ALC_A_FIXED_SIZE;
+ ip->init.util.cp = ERTS_ALC_COMMON_CPOOL_IX;
#ifndef SMALL_MEMORY
ip->init.util.mmbcs = 128*1024; /* Main carrier size */
#else
@@ -490,6 +502,7 @@ set_default_test_alloc_opts(struct au_init *ip)
ip->init.aoff.blk_order = FF_BF;
ip->init.util.name_prefix = "test_";
ip->init.util.alloc_no = ERTS_ALC_A_TEST;
+ ip->init.util.cp = ERTS_ALC_A_TEST;
ip->init.util.mmbcs = 0; /* Main carrier size */
ip->init.util.ts = ERTS_ALC_MTA_TEST;
ip->init.util.acul = ERTS_ALC_DEFAULT_ACUL;
@@ -638,7 +651,7 @@ erts_alloc_init(int *argc, char **argv, ErtsAllocInitOpts *eaiop)
fix_type_sizes[ERTS_ALC_FIX_TYPE_IX(ERTS_ALC_T_MONITOR)]
= sizeof(ErtsMonitorDataHeap);
fix_type_sizes[ERTS_ALC_FIX_TYPE_IX(ERTS_ALC_T_LINK)]
- = sizeof(ErtsLinkData);
+ = sizeof(ErtsILink);
fix_type_sizes[ERTS_ALC_FIX_TYPE_IX(ERTS_ALC_T_DRV_SEL_D_STATE)]
= sizeof(ErtsDrvSelectDataState);
fix_type_sizes[ERTS_ALC_FIX_TYPE_IX(ERTS_ALC_T_NIF_SEL_D_STATE)]
@@ -653,8 +666,6 @@ erts_alloc_init(int *argc, char **argv, ErtsAllocInitOpts *eaiop)
= erts_timer_type_size(ERTS_ALC_T_HL_PTIMER);
fix_type_sizes[ERTS_ALC_FIX_TYPE_IX(ERTS_ALC_T_BIF_TIMER)]
= erts_timer_type_size(ERTS_ALC_T_BIF_TIMER);
- fix_type_sizes[ERTS_ALC_FIX_TYPE_IX(ERTS_ALC_T_NIF_EXP_TRACE)]
- = sizeof(NifExportTrace);
fix_type_sizes[ERTS_ALC_FIX_TYPE_IX(ERTS_ALC_T_MREF_NSCHED_ENT)]
= sizeof(ErtsNSchedMagicRefTableEntry);
fix_type_sizes[ERTS_ALC_FIX_TYPE_IX(ERTS_ALC_T_MINDIRECTION)]
@@ -1424,6 +1435,39 @@ handle_au_arg(struct au_init *auip,
else
goto bad_switch;
break;
+ case 'c': {
+ if (has_prefix("cp", sub_param)) {
+ char *param, *param_end, *value;
+ int cp;
+ if (!auip->carrier_migration_allowed && !u_switch)
+ goto bad_switch;
+ param = argv[*ip]+1;
+ param_end = sub_param + 2;
+ value = get_value(param_end, argv, ip);
+ if (value[0] == '\0' || value[1] != '\0')
+ bad_value(param, param_end, value);
+ switch (value[0]) {
+ case 'B': cp = ERTS_ALC_A_BINARY; break;
+ case 'D': cp = ERTS_ALC_A_STANDARD; break;
+ case 'E': cp = ERTS_ALC_A_ETS; break;
+ case 'F': cp = ERTS_ALC_A_FIXED_SIZE; break;
+ case 'H': cp = ERTS_ALC_A_EHEAP; break;
+ case 'L': cp = ERTS_ALC_A_LONG_LIVED; break;
+ case 'R': cp = ERTS_ALC_A_DRIVER; break;
+ case 'S': cp = ERTS_ALC_A_SHORT_LIVED; break;
+ case '@': cp = ERTS_ALC_COMMON_CPOOL_IX; break;
+ case ':': cp = auip->init.util.alloc_no; break;
+ default: cp = -1;
+ bad_value(param, param_end, value);
+ break;
+ }
+ if (auip->carrier_migration_allowed)
+ auip->init.util.cp = cp;
+ }
+ else
+ goto bad_switch;
+ break;
+ }
case 'e': {
int e = get_bool_value(sub_param + 1, argv, ip);
if (!auip->disable_allowed && !e) {
@@ -2392,10 +2436,6 @@ erts_memory(fmtfn_t *print_to_p, void *print_to_arg, void *proc, Eterm earg)
&size.processes_used,
fi,
ERTS_ALC_T_BIF_TIMER);
- add_fix_values(&size.processes,
- &size.processes_used,
- fi,
- ERTS_ALC_T_NIF_EXP_TRACE);
}
if (want.atom || want.atom_used) {
diff --git a/erts/emulator/beam/erl_alloc.h b/erts/emulator/beam/erl_alloc.h
index c13cf3f5b0..831e7ab0a7 100644
--- a/erts/emulator/beam/erl_alloc.h
+++ b/erts/emulator/beam/erl_alloc.h
@@ -358,24 +358,11 @@ erts_alloc_get_verify_unused_temp_alloc(Allctr_t **allctr);
#define ERTS_ALC_CACHE_LINE_ALIGN_SIZE(SZ) \
(((((SZ) - 1) / ERTS_CACHE_LINE_SIZE) + 1) * ERTS_CACHE_LINE_SIZE)
+#if !defined(VALGRIND) && !defined(ADDRESS_SANITIZER)
+
#define ERTS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \
ERTS_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT, (void) 0, (void) 0, (void) 0)
-#define ERTS_TS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \
-ERTS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT)
-
-#define ERTS_TS_PALLOC_IMPL(NAME, TYPE, PASZ) \
-static erts_spinlock_t NAME##_lck; \
-ERTS_PRE_ALLOC_IMPL(NAME, TYPE, PASZ, \
- erts_spinlock_init(&NAME##_lck, #NAME "_alloc_lock", NIL, \
- ERTS_LOCK_FLAGS_CATEGORY_ALLOCATOR),\
- erts_spin_lock(&NAME##_lck), \
- erts_spin_unlock(&NAME##_lck))
-
-
-#define ERTS_PALLOC_IMPL(NAME, TYPE, PASZ) \
- ERTS_TS_PALLOC_IMPL(NAME, TYPE, PASZ)
-
#define ERTS_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT, ILCK, LCK, ULCK) \
ERTS_PRE_ALLOC_IMPL(NAME##_pre, TYPE, PASZ, ILCK, LCK, ULCK) \
@@ -606,6 +593,69 @@ NAME##_free(TYPE *p) \
(char *) p); \
}
+#else /* !defined(VALGRIND) && !defined(ADDRESS_SANITIZER) */
+
+/*
+ * For VALGRIND and ADDRESS_SANITIZER we short circuit all preallocation
+ * with dummy wrappers around malloc and free.
+ */
+
+#define ERTS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \
+ ERTS_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT, (void) 0, (void) 0, (void) 0)
+
+#define ERTS_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT, ILCK, LCK, ULCK) \
+static void init_##NAME##_alloc(void) \
+{ \
+} \
+static ERTS_INLINE TYPE* NAME##_alloc(void) \
+{ \
+ return malloc(sizeof(TYPE)); \
+} \
+static ERTS_INLINE void NAME##_free(TYPE *p) \
+{ \
+ free((void *) p); \
+}
+
+#define ERTS_SCHED_PREF_PALLOC_IMPL(NAME, TYPE, PASZ) \
+ ERTS_SCHED_PREF_PRE_ALLOC_IMPL(NAME, TYPE, PASZ)
+
+#define ERTS_SCHED_PREF_AUX(NAME, TYPE, PASZ) \
+ERTS_SCHED_PREF_PRE_ALLOC_IMPL(NAME##_pre, TYPE, PASZ)
+
+#define ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \
+ ERTS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT)
+
+#define ERTS_THR_PREF_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \
+void erts_##NAME##_pre_alloc_init_thread(void) \
+{ \
+} \
+static void init_##NAME##_alloc(int nthreads) \
+{ \
+} \
+static ERTS_INLINE TYPE* NAME##_alloc(void) \
+{ \
+ return malloc(sizeof(TYPE)); \
+} \
+static ERTS_INLINE void NAME##_free(TYPE *p) \
+{ \
+ free(p); \
+}
+
+#define ERTS_SCHED_PREF_PRE_ALLOC_IMPL(NAME, TYPE, PASZ) \
+static void init_##NAME##_alloc(void) \
+{ \
+} \
+static TYPE* NAME##_alloc(void) \
+{ \
+ return (TYPE *) malloc(sizeof(TYPE)); \
+} \
+static int NAME##_free(TYPE *p) \
+{ \
+ free(p); \
+ return 1; \
+}
+
+#endif /* VALGRIND || ADDRESS_SANITIZER */
#ifdef DEBUG
#define ERTS_ALC_DBG_BLK_SZ(PTR) (*(((UWord *) (PTR)) - 2))
diff --git a/erts/emulator/beam/erl_alloc.types b/erts/emulator/beam/erl_alloc.types
index 7fee650674..7adab91440 100644
--- a/erts/emulator/beam/erl_alloc.types
+++ b/erts/emulator/beam/erl_alloc.types
@@ -195,6 +195,8 @@ type DB_DMC_ERR_INFO ETS ETS db_dmc_error_info
type DB_TERM ETS ETS db_term
type DB_PROC_CLEANUP SHORT_LIVED ETS db_proc_cleanup_state
type ETS_ALL_REQ SHORT_LIVED ETS ets_all_request
+type ETS_CTRS ETS ETS ets_decentralized_ctrs
+type ETS_I_LST_TRAP SHORT_LIVED ETS ets_insert_list_bif_trap_state
type LOGGER_DSBUF TEMPORARY SYSTEM logger_dsbuf
type TMP_DSBUF TEMPORARY SYSTEM tmp_dsbuf
type INFO_DSBUF SYSTEM SYSTEM info_dsbuf
@@ -279,6 +281,7 @@ type SETUP_CONN_ARG SHORT_LIVED PROCESSES setup_connection_argument
type LIST_TRAP SHORT_LIVED PROCESSES list_bif_trap_state
type CONT_EXIT_TRAP SHORT_LIVED PROCESSES continue_exit_trap_state
type SEQ_YIELD_STATE SHORT_LIVED SYSTEM dist_seq_yield_state
+type PHASH2_TRAP SHORT_LIVED PROCESSES phash2_trap_state
type ENVIRONMENT SYSTEM SYSTEM environment
@@ -286,6 +289,8 @@ type PERSISTENT_TERM LONG_LIVED CODE persisten_term
type PERSISTENT_LOCK_Q SHORT_LIVED SYSTEM persistent_lock_q
type PERSISTENT_TERM_TMP SHORT_LIVED SYSTEM persistent_term_tmp_table
+type T2B_VEC SHORT_LIVED PROCESSES term_to_binary_vector
+
#
# Types used for special emulators
#
@@ -331,8 +336,7 @@ type DB_HEIR_DATA STANDARD ETS db_heir_data
type DB_MS_PSDO_PROC LONG_LIVED ETS db_match_pseudo_proc
type SCHDLR_DATA LONG_LIVED SYSTEM scheduler_data
-type NIF_TRAP_EXPORT STANDARD PROCESSES nif_trap_export_entry
-type NIF_EXP_TRACE FIXED_SIZE PROCESSES nif_export_trace
+type NFUNC_TRAP_WRAPPER STANDARD PROCESSES nfunc_trap_wrapper
type EXPORT LONG_LIVED CODE export_entry
type MONITOR FIXED_SIZE PROCESSES monitor
type MONITOR_SUSPEND STANDARD PROCESSES monitor_suspend
@@ -350,17 +354,18 @@ type COUNTERS STANDARD SYSTEM erl_bif_counters
# Types used by system specific code
#
-type TEMP_TERM TEMPORARY SYSTEM temp_term
-type SHORT_LIVED_TERM SHORT_LIVED SYSTEM short_lived_term
-type DRV_TAB LONG_LIVED SYSTEM drv_tab
-type DRV_EV_STATE LONG_LIVED SYSTEM driver_event_state
-type DRV_SEL_D_STATE FIXED_SIZE SYSTEM driver_select_data_state
-type NIF_SEL_D_STATE FIXED_SIZE SYSTEM enif_select_data_state
-type POLLSET LONG_LIVED SYSTEM pollset
-type POLLSET_UPDREQ SHORT_LIVED SYSTEM pollset_update_req
-type POLL_FDS LONG_LIVED SYSTEM poll_fds
-type FD_STATUS LONG_LIVED SYSTEM fd_status
-type SELECT_FDS LONG_LIVED SYSTEM select_fds
+type TEMP_TERM TEMPORARY SYSTEM temp_term
+type SHORT_LIVED_TERM SHORT_LIVED SYSTEM short_lived_term
+type DRV_TAB LONG_LIVED SYSTEM drv_tab
+type DRV_EV_STATE LONG_LIVED SYSTEM driver_event_state
+type DRV_SEL_D_STATE FIXED_SIZE SYSTEM driver_select_data_state
+type NIF_SEL_D_STATE FIXED_SIZE SYSTEM enif_select_data_state
+type POLLSET LONG_LIVED SYSTEM pollset
+type POLLSET_UPDREQ SHORT_LIVED SYSTEM pollset_update_req
+type POLL_FDS LONG_LIVED SYSTEM poll_fds
+type BLOCK_PTHR_DATA LONG_LIVED SYSTEM block_poll_thread_data
+type FD_STATUS LONG_LIVED SYSTEM fd_status
+type SELECT_FDS LONG_LIVED SYSTEM select_fds
+if unix
diff --git a/erts/emulator/beam/erl_alloc_util.c b/erts/emulator/beam/erl_alloc_util.c
index beb26acc3b..aac0c264de 100644
--- a/erts/emulator/beam/erl_alloc_util.c
+++ b/erts/emulator/beam/erl_alloc_util.c
@@ -481,244 +481,265 @@ static void check_blk_carrier(Allctr_t *, Block_t *);
/* Statistics updating ... */
#ifdef DEBUG
-#define DEBUG_CHECK_CARRIER_NO_SZ(AP) \
- ASSERT(((AP)->sbcs.curr.norm.mseg.no \
- && (AP)->sbcs.curr.norm.mseg.size) \
- || (!(AP)->sbcs.curr.norm.mseg.no \
- && !(AP)->sbcs.curr.norm.mseg.size)); \
- ASSERT(((AP)->sbcs.curr.norm.sys_alloc.no \
- && (AP)->sbcs.curr.norm.sys_alloc.size) \
- || (!(AP)->sbcs.curr.norm.sys_alloc.no \
- && !(AP)->sbcs.curr.norm.sys_alloc.size)); \
- ASSERT(((AP)->mbcs.curr.norm.mseg.no \
- && (AP)->mbcs.curr.norm.mseg.size) \
- || (!(AP)->mbcs.curr.norm.mseg.no \
- && !(AP)->mbcs.curr.norm.mseg.size)); \
- ASSERT(((AP)->mbcs.curr.norm.sys_alloc.no \
- && (AP)->mbcs.curr.norm.sys_alloc.size) \
- || (!(AP)->mbcs.curr.norm.sys_alloc.no \
- && !(AP)->mbcs.curr.norm.sys_alloc.size));
+
+#define DEBUG_CHECK_CARRIER_NO_SZ_1(CSTATS) \
+ do { \
+ int ix__; \
+ for (ix__ = ERTS_CRR_ALLOC_MIN; ix__ <= ERTS_CRR_ALLOC_MAX; ix__++) { \
+ UWord no__ = (CSTATS)->carriers[ix__].no; \
+ UWord size__= (CSTATS)->carriers[ix__].size; \
+ ASSERT((no__ > 0 && size__ > 0) || (no__ == 0 && size__ == 0)); \
+ } \
+ } while (0)
+
+#define DEBUG_CHECK_CARRIER_NO_SZ(AP) \
+ do { \
+ DEBUG_CHECK_CARRIER_NO_SZ_1(&(AP)->sbcs); \
+ DEBUG_CHECK_CARRIER_NO_SZ_1(&(AP)->mbcs); \
+ } while (0)
#else
#define DEBUG_CHECK_CARRIER_NO_SZ(AP)
#endif
-#define STAT_SBC_ALLOC(AP, BSZ) \
- (AP)->sbcs.blocks.curr.size += (BSZ); \
- if ((AP)->sbcs.blocks.max.size < (AP)->sbcs.blocks.curr.size) \
- (AP)->sbcs.blocks.max.size = (AP)->sbcs.blocks.curr.size; \
- if ((AP)->sbcs.max.no < ((AP)->sbcs.curr.norm.mseg.no \
- + (AP)->sbcs.curr.norm.sys_alloc.no)) \
- (AP)->sbcs.max.no = ((AP)->sbcs.curr.norm.mseg.no \
- + (AP)->sbcs.curr.norm.sys_alloc.no); \
- if ((AP)->sbcs.max.size < ((AP)->sbcs.curr.norm.mseg.size \
- + (AP)->sbcs.curr.norm.sys_alloc.size)) \
- (AP)->sbcs.max.size = ((AP)->sbcs.curr.norm.mseg.size \
- + (AP)->sbcs.curr.norm.sys_alloc.size)
-
-#define STAT_MSEG_SBC_ALLOC(AP, CSZ, BSZ) \
-do { \
- (AP)->sbcs.curr.norm.mseg.no++; \
- (AP)->sbcs.curr.norm.mseg.size += (CSZ); \
- STAT_SBC_ALLOC((AP), (BSZ)); \
- DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
-} while (0)
-
-#define STAT_SYS_ALLOC_SBC_ALLOC(AP, CSZ, BSZ) \
-do { \
- (AP)->sbcs.curr.norm.sys_alloc.no++; \
- (AP)->sbcs.curr.norm.sys_alloc.size += (CSZ); \
- STAT_SBC_ALLOC((AP), (BSZ)); \
- DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
-} while (0)
-
-
-#define STAT_SBC_FREE(AP, BSZ) \
- ASSERT((AP)->sbcs.blocks.curr.size >= (BSZ)); \
- (AP)->sbcs.blocks.curr.size -= (BSZ)
-
-#define STAT_MSEG_SBC_FREE(AP, CSZ, BSZ) \
-do { \
- ASSERT((AP)->sbcs.curr.norm.mseg.no > 0); \
- (AP)->sbcs.curr.norm.mseg.no--; \
- ASSERT((AP)->sbcs.curr.norm.mseg.size >= (CSZ)); \
- (AP)->sbcs.curr.norm.mseg.size -= (CSZ); \
- STAT_SBC_FREE((AP), (BSZ)); \
- DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
-} while (0)
-
-#define STAT_SYS_ALLOC_SBC_FREE(AP, CSZ, BSZ) \
-do { \
- ASSERT((AP)->sbcs.curr.norm.sys_alloc.no > 0); \
- (AP)->sbcs.curr.norm.sys_alloc.no--; \
- ASSERT((AP)->sbcs.curr.norm.sys_alloc.size >= (CSZ)); \
- (AP)->sbcs.curr.norm.sys_alloc.size -= (CSZ); \
- STAT_SBC_FREE((AP), (BSZ)); \
- DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
-} while (0)
-
-#define STAT_MBC_ALLOC(AP) \
- if ((AP)->mbcs.max.no < ((AP)->mbcs.curr.norm.mseg.no \
- + (AP)->mbcs.curr.norm.sys_alloc.no)) \
- (AP)->mbcs.max.no = ((AP)->mbcs.curr.norm.mseg.no \
- + (AP)->mbcs.curr.norm.sys_alloc.no); \
- if ((AP)->mbcs.max.size < ((AP)->mbcs.curr.norm.mseg.size \
- + (AP)->mbcs.curr.norm.sys_alloc.size)) \
- (AP)->mbcs.max.size = ((AP)->mbcs.curr.norm.mseg.size \
- + (AP)->mbcs.curr.norm.sys_alloc.size)
-
-
-#define STAT_MSEG_MBC_ALLOC(AP, CSZ) \
-do { \
- (AP)->mbcs.curr.norm.mseg.no++; \
- (AP)->mbcs.curr.norm.mseg.size += (CSZ); \
- STAT_MBC_ALLOC((AP)); \
- DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
-} while (0)
-
-#define STAT_SYS_ALLOC_MBC_ALLOC(AP, CSZ) \
-do { \
- (AP)->mbcs.curr.norm.sys_alloc.no++; \
- (AP)->mbcs.curr.norm.sys_alloc.size += (CSZ); \
- STAT_MBC_ALLOC((AP)); \
- DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
-} while (0)
-
-#define STAT_MBC_CPOOL_FETCH(AP, CRR) \
-do { \
- UWord csz__ = CARRIER_SZ((CRR)); \
- if (IS_MSEG_CARRIER((CRR))) \
- STAT_MSEG_MBC_ALLOC((AP), csz__); \
- else \
- STAT_SYS_ALLOC_MBC_ALLOC((AP), csz__); \
- set_new_allctr_abandon_limit(AP); \
- (AP)->mbcs.blocks.curr.no += (CRR)->cpool.blocks[(AP)->alloc_no]; \
- if ((AP)->mbcs.blocks.max.no < (AP)->mbcs.blocks.curr.no) \
- (AP)->mbcs.blocks.max.no = (AP)->mbcs.blocks.curr.no; \
- (AP)->mbcs.blocks.curr.size += \
- (CRR)->cpool.blocks_size[(AP)->alloc_no]; \
- if ((AP)->mbcs.blocks.max.size < (AP)->mbcs.blocks.curr.size) \
- (AP)->mbcs.blocks.max.size = (AP)->mbcs.blocks.curr.size; \
-} while (0)
-
-#define STAT_MSEG_MBC_FREE(AP, CSZ) \
-do { \
- ASSERT((AP)->mbcs.curr.norm.mseg.no > 0); \
- (AP)->mbcs.curr.norm.mseg.no--; \
- ASSERT((AP)->mbcs.curr.norm.mseg.size >= (CSZ)); \
- (AP)->mbcs.curr.norm.mseg.size -= (CSZ); \
- DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
-} while (0)
-
-#define STAT_SYS_ALLOC_MBC_FREE(AP, CSZ) \
-do { \
- ASSERT((AP)->mbcs.curr.norm.sys_alloc.no > 0); \
- (AP)->mbcs.curr.norm.sys_alloc.no--; \
- ASSERT((AP)->mbcs.curr.norm.sys_alloc.size >= (CSZ)); \
- (AP)->mbcs.curr.norm.sys_alloc.size -= (CSZ); \
- DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
-} while (0)
+/* Carrier statistics */
+
+#define STAT_CRR_UPDATED_1(CSTATS) \
+ do { \
+ UWord no_sum__, size_sum__; \
+ int i__; \
+ no_sum__ = size_sum__ = 0; \
+ for (i__ = ERTS_CRR_ALLOC_MIN; i__ <= ERTS_CRR_ALLOC_MAX; i__++) { \
+ ASSERT(ERTS_UWORD_MAX - no_sum__ > (CSTATS)->carriers[i__].no); \
+ ASSERT(ERTS_UWORD_MAX - size_sum__ > (CSTATS)->carriers[i__].size); \
+ no_sum__ += (CSTATS)->carriers[i__].no; \
+ size_sum__ += (CSTATS)->carriers[i__].size; \
+ } \
+ if ((CSTATS)->max.no < no_sum__) { \
+ (CSTATS)->max.no = no_sum__; \
+ } \
+ if ((CSTATS)->max.size < size_sum__) { \
+ (CSTATS)->max.size = size_sum__; \
+ } \
+ } while (0)
+
+#define STAT_CRR_ALLOC_1(TYPE, CSTATS, SIZE) \
+ do { \
+ ASSERT(ERTS_UWORD_MAX - (CSTATS)->carriers[(TYPE)].no > 1); \
+ ASSERT(ERTS_UWORD_MAX - (CSTATS)->carriers[(TYPE)].size > (SIZE)); \
+ (CSTATS)->carriers[(TYPE)].no += 1; \
+ (CSTATS)->carriers[(TYPE)].size += (SIZE); \
+ STAT_CRR_UPDATED_1(CSTATS); \
+ } while (0)
+
+#define STAT_CRR_FREE_1(TYPE, CSTATS, SIZE) \
+ do { \
+ ASSERT((CSTATS)->carriers[(TYPE)].no >= 1); \
+ ASSERT((CSTATS)->carriers[(TYPE)].size >= (SIZE)); \
+ (CSTATS)->carriers[(TYPE)].no -= 1; \
+ (CSTATS)->carriers[(TYPE)].size -= (SIZE); \
+ STAT_CRR_UPDATED_1(CSTATS); \
+ } while (0)
+
+#define STAT_MSEG_SBC_ALLOC(AP, CSZ, BSZ) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->sbcs; \
+ STAT_CRR_ALLOC_1(ERTS_CRR_ALLOC_MSEG, cstats__, CSZ); \
+ STAT_BLK_ALLOC_1(cstats__, (AP)->alloc_no, 1, (BSZ)); \
+ DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
+ } while (0)
+
+#define STAT_SYS_ALLOC_SBC_ALLOC(AP, CSZ, BSZ) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->sbcs; \
+ STAT_CRR_ALLOC_1(ERTS_CRR_ALLOC_SYS, cstats__, CSZ); \
+ STAT_BLK_ALLOC_1(cstats__, (AP)->alloc_no, 1, (BSZ)); \
+ DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
+ } while (0)
+
+#define STAT_MSEG_SBC_FREE(AP, CSZ, BSZ) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->sbcs; \
+ STAT_CRR_FREE_1(ERTS_CRR_ALLOC_MSEG, cstats__, CSZ); \
+ STAT_BLK_FREE_1(cstats__, (AP)->alloc_no, 1, (BSZ)); \
+ DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
+ } while (0)
+
+#define STAT_SYS_ALLOC_SBC_FREE(AP, CSZ, BSZ) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->sbcs; \
+ STAT_CRR_FREE_1(ERTS_CRR_ALLOC_SYS, cstats__, CSZ); \
+ STAT_BLK_FREE_1(cstats__, (AP)->alloc_no, 1, (BSZ)); \
+ DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
+ } while (0)
+
+#define STAT_MSEG_MBC_ALLOC(AP, CSZ) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->mbcs; \
+ STAT_CRR_ALLOC_1(ERTS_CRR_ALLOC_MSEG, cstats__, CSZ); \
+ DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
+ } while (0)
+
+#define STAT_SYS_ALLOC_MBC_ALLOC(AP, CSZ) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->mbcs; \
+ STAT_CRR_ALLOC_1(ERTS_CRR_ALLOC_SYS, cstats__, CSZ); \
+ DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
+ } while (0)
+
+#define STAT_MSEG_MBC_FREE(AP, CSZ) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->mbcs; \
+ STAT_CRR_FREE_1(ERTS_CRR_ALLOC_MSEG, cstats__, CSZ); \
+ DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
+ } while (0)
+
+#define STAT_SYS_ALLOC_MBC_FREE(AP, CSZ) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->mbcs; \
+ STAT_CRR_FREE_1(ERTS_CRR_ALLOC_SYS, cstats__, CSZ); \
+ DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
+ } while (0)
+
+#define STAT_MBC_FREE(AP, CRR) \
+ do { \
+ UWord csz__ = CARRIER_SZ((CRR)); \
+ if (IS_MSEG_CARRIER((CRR))) { \
+ STAT_MSEG_MBC_FREE((AP), csz__); \
+ } else { \
+ STAT_SYS_ALLOC_MBC_FREE((AP), csz__); \
+ } \
+ set_new_allctr_abandon_limit(AP); \
+ } while (0)
+
+/* Block statistics */
+
+#define STAT_BLK_UPDATED_1(BSTATS) \
+ do { \
+ if ((BSTATS)->max.no < (BSTATS)->curr.no) { \
+ (BSTATS)->max.no = (BSTATS)->curr.no; \
+ } \
+ if ((BSTATS)->max.size < (BSTATS)->curr.size) { \
+ (BSTATS)->max.size = (BSTATS)->curr.size; \
+ } \
+ } while (0)
+
+#define STAT_BLK_ALLOC_1(CSTATS, ALLOC_NO, COUNT, SIZE) \
+ do { \
+ BlockStats_t *bstats__ = \
+ &(CSTATS)->blocks[(ALLOC_NO) - ERTS_ALC_A_MIN]; \
+ ASSERT(ERTS_UWORD_MAX - bstats__->curr.no > (COUNT)); \
+ ASSERT(ERTS_UWORD_MAX - bstats__->curr.size > (SIZE)); \
+ bstats__->curr.no += (COUNT); \
+ bstats__->curr.size += (SIZE); \
+ STAT_BLK_UPDATED_1(bstats__); \
+ } while (0)
+
+#define STAT_BLK_FREE_1(CSTATS, ALLOC_NO, COUNT, SIZE) \
+ do { \
+ BlockStats_t *bstats__ = \
+ &(CSTATS)->blocks[(ALLOC_NO) - ERTS_ALC_A_MIN]; \
+ ASSERT(bstats__->curr.no >= (COUNT)); \
+ ASSERT(bstats__->curr.size >= (SIZE)); \
+ bstats__->curr.no -= (COUNT); \
+ bstats__->curr.size -= (SIZE); \
+ STAT_BLK_UPDATED_1(bstats__); \
+ } while (0)
+
+#define STAT_MBC_CPOOL_FETCH(AP, CRR) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->mbcs; \
+ UWord csz__ = CARRIER_SZ((CRR)); \
+ int alloc_no__; \
+ if (IS_MSEG_CARRIER((CRR))) { \
+ STAT_MSEG_MBC_ALLOC((AP), csz__); \
+ } else { \
+ STAT_SYS_ALLOC_MBC_ALLOC((AP), csz__); \
+ } \
+ set_new_allctr_abandon_limit(AP); \
+ for (alloc_no__ = ERTS_ALC_A_MIN; \
+ alloc_no__ <= ERTS_ALC_A_MAX; \
+ alloc_no__++) { \
+ int ix__ = alloc_no__ - ERTS_ALC_A_MIN; \
+ UWord count = (CRR)->cpool.blocks[ix__]; \
+ UWord size = (CRR)->cpool.blocks_size[ix__]; \
+ STAT_BLK_ALLOC_1(cstats__, alloc_no__, count, size); \
+ } \
+ } while (0)
+
+#define STAT_MBC_CPOOL_ABANDON(AP, CRR) \
+ do { \
+ CarriersStats_t *cstats__ = &(AP)->mbcs; \
+ int alloc_no__; \
+ STAT_MBC_FREE(AP, CRR); \
+ for (alloc_no__ = ERTS_ALC_A_MIN; \
+ alloc_no__ <= ERTS_ALC_A_MAX; \
+ alloc_no__++) { \
+ int ix__ = alloc_no__ - ERTS_ALC_A_MIN; \
+ UWord count = (CRR)->cpool.blocks[ix__]; \
+ UWord size = (CRR)->cpool.blocks_size[ix__]; \
+ STAT_BLK_FREE_1(cstats__, alloc_no__, count, size); \
+ } \
+ } while (0)
-#define STAT_MBC_FREE(AP, CRR) \
-do { \
- UWord csz__ = CARRIER_SZ((CRR)); \
- if (IS_MSEG_CARRIER((CRR))) { \
- STAT_MSEG_MBC_FREE((AP), csz__); \
- } else { \
- STAT_SYS_ALLOC_MBC_FREE((AP), csz__); \
- } \
- set_new_allctr_abandon_limit(AP); \
-} while (0)
-
-#define STAT_MBC_ABANDON(AP, CRR) \
-do { \
- STAT_MBC_FREE(AP, CRR); \
- ERTS_ALC_CPOOL_ASSERT((AP)->mbcs.blocks.curr.no \
- >= (CRR)->cpool.blocks[(AP)->alloc_no]); \
- (AP)->mbcs.blocks.curr.no -= (CRR)->cpool.blocks[(AP)->alloc_no]; \
- ERTS_ALC_CPOOL_ASSERT((AP)->mbcs.blocks.curr.size \
- >= (CRR)->cpool.blocks_size[(AP)->alloc_no]); \
- (AP)->mbcs.blocks.curr.size -= (CRR)->cpool.blocks_size[(AP)->alloc_no]; \
-} while (0)
-
-#define STAT_MBC_BLK_ALLOC_CRR(AP, CRR, BSZ) \
-do { \
- (CRR)->cpool.blocks[(AP)->alloc_no]++; \
- (CRR)->cpool.blocks_size[(AP)->alloc_no] += (BSZ); \
- (CRR)->cpool.total_blocks_size += (BSZ); \
-} while (0)
-
-#define STAT_MBC_BLK_ALLOC(AP, CRR, BSZ, FLGS) \
-do { \
- CarriersStats_t *cstats__ = &(AP)->mbcs; \
- cstats__->blocks.curr.no++; \
- if (cstats__->blocks.max.no < cstats__->blocks.curr.no) \
- cstats__->blocks.max.no = cstats__->blocks.curr.no; \
- cstats__->blocks.curr.size += (BSZ); \
- if (cstats__->blocks.max.size < cstats__->blocks.curr.size) \
- cstats__->blocks.max.size = cstats__->blocks.curr.size; \
- STAT_MBC_BLK_ALLOC_CRR((AP), (CRR), (BSZ)); \
-} while (0)
-
-static ERTS_INLINE int
+static ERTS_INLINE void
stat_cpool_mbc_blk_free(Allctr_t *allctr,
- ErtsAlcType_t type,
- Carrier_t *crr,
- Carrier_t **busy_pcrr_pp,
- UWord blksz)
+ int alloc_no,
+ Carrier_t *crr,
+ Carrier_t **busy_pcrr_pp,
+ UWord blksz)
{
- Allctr_t *orig_allctr;
- int alloc_no;
-
- alloc_no = ERTS_ALC_T2A(type);
-
- ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks[alloc_no] > 0);
- crr->cpool.blocks[alloc_no]--;
- ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks_size[alloc_no] >= blksz);
- crr->cpool.blocks_size[alloc_no] -= blksz;
- ERTS_ALC_CPOOL_ASSERT(crr->cpool.total_blocks_size >= blksz);
- crr->cpool.total_blocks_size -= blksz;
-
- if (allctr->alloc_no == alloc_no && (!busy_pcrr_pp || !*busy_pcrr_pp)) {
+ if (!busy_pcrr_pp || !*busy_pcrr_pp) {
/* This is a local block, so we should not update the pool
* statistics. */
- return 0;
- }
-
- /* This is either a foreign block that's been fetched from the pool, or any
- * block that's in the pool. The carrier's owner keeps the statistics for
- * both pooled and foreign blocks. */
-
- orig_allctr = crr->cpool.orig_allctr;
+ CarriersStats_t *cstats__ = &allctr->mbcs;
+ STAT_BLK_FREE_1(cstats__, alloc_no, 1, blksz);
+ } else {
+ Allctr_t *orig_allctr = crr->cpool.orig_allctr;
+ int ix = alloc_no - ERTS_ALC_A_MIN;
- ERTS_ALC_CPOOL_ASSERT(alloc_no != allctr->alloc_no ||
- (crr == *busy_pcrr_pp && allctr == orig_allctr));
+ ERTS_ALC_CPOOL_ASSERT(crr == *busy_pcrr_pp && allctr == orig_allctr);
#ifdef ERTS_ALC_CPOOL_DEBUG
- ERTS_ALC_CPOOL_ASSERT(
- erts_atomic_dec_read_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no]) >= 0);
- ERTS_ALC_CPOOL_ASSERT(
- erts_atomic_add_read_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no],
- -((erts_aint_t) blksz)) >= 0);
+ ERTS_ALC_CPOOL_ASSERT(
+ erts_atomic_dec_read_nob(
+ &orig_allctr->cpool.stat.no_blocks[ix]) >= 0);
+ ERTS_ALC_CPOOL_ASSERT(
+ erts_atomic_add_read_nob(&orig_allctr->cpool.stat.blocks_size[ix],
+ -((erts_aint_t) blksz)) >= 0);
#else
- erts_atomic_dec_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no]);
- erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no],
- -((erts_aint_t) blksz));
+ erts_atomic_dec_nob(&orig_allctr->cpool.stat.no_blocks[ix]);
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size[ix],
+ -((erts_aint_t) blksz));
#endif
-
- return 1;
+ }
}
-#define STAT_MBC_BLK_FREE(AP, TYPE, CRR, BPCRRPP, BSZ, FLGS) \
-do { \
- if (!stat_cpool_mbc_blk_free((AP), (TYPE), (CRR), (BPCRRPP), (BSZ))) { \
- CarriersStats_t *cstats__ = &(AP)->mbcs; \
- ASSERT(cstats__->blocks.curr.no > 0); \
- cstats__->blocks.curr.no--; \
- ASSERT(cstats__->blocks.curr.size >= (BSZ)); \
- cstats__->blocks.curr.size -= (BSZ); \
- } \
-} while (0)
+#define STAT_MBC_BLK_ALLOC(AP, CRR, BSZ) \
+ do { \
+ int alloc_no__ = (AP)->alloc_no; \
+ int ix__ = alloc_no__ - ERTS_ALC_A_MIN; \
+ ASSERT(ERTS_UWORD_MAX - (CRR)->cpool.blocks[ix__] > 1); \
+ ASSERT(ERTS_UWORD_MAX - (CRR)->cpool.blocks_size[ix__] > (BSZ)); \
+ ASSERT(ERTS_UWORD_MAX - (CRR)->cpool.total_blocks_size > (BSZ)); \
+ (CRR)->cpool.blocks[ix__] += 1; \
+ (CRR)->cpool.blocks_size[ix__] += (BSZ); \
+ (CRR)->cpool.total_blocks_size += (BSZ); \
+ STAT_BLK_ALLOC_1(&(AP)->mbcs, alloc_no__, 1, (BSZ)); \
+ } while (0)
+
+#define STAT_MBC_BLK_FREE(AP, TYPE, CRR, BPCRRPP, BSZ) \
+ do { \
+ int alloc_no__ = ERTS_ALC_T2A(TYPE); \
+ int ix__ = (alloc_no__) - ERTS_ALC_A_MIN; \
+ ASSERT((CRR)->cpool.blocks[ix__] >= 1); \
+ ASSERT((CRR)->cpool.blocks_size[ix__] >= (BSZ)); \
+ ASSERT((CRR)->cpool.total_blocks_size >= (BSZ)); \
+ (CRR)->cpool.blocks[ix__] -= 1; \
+ (CRR)->cpool.blocks_size[ix__] -= (BSZ); \
+ (CRR)->cpool.total_blocks_size -= (BSZ); \
+ stat_cpool_mbc_blk_free((AP), alloc_no__, (CRR), (BPCRRPP), (BSZ)); \
+ } while (0)
/* Debug stuff... */
#ifdef DEBUG
@@ -1211,9 +1232,14 @@ static Uint
get_next_mbc_size(Allctr_t *allctr)
{
Uint size;
- int cs = (allctr->mbcs.curr.norm.mseg.no
- + allctr->mbcs.curr.norm.sys_alloc.no
- - (allctr->main_carrier ? 1 : 0));
+ int cs;
+ int i;
+
+ cs = 0;
+ for (i = ERTS_CRR_ALLOC_MIN; i <= ERTS_CRR_ALLOC_MAX; i++) {
+ cs += allctr->mbcs.carriers[i].no;
+ }
+ cs -= (allctr->main_carrier ? 1 : 0);
ASSERT(cs >= 0);
ASSERT(allctr->largest_mbc_size >= allctr->smallest_mbc_size);
@@ -2285,8 +2311,14 @@ check_abandon_carrier(Allctr_t *allctr, Block_t *fblk, Carrier_t **busy_pcrr_pp)
if (allctr->cpool.disable_abandon)
return;
- if (allctr->mbcs.blocks.curr.size > allctr->cpool.abandon_limit)
- return;
+ {
+ int ix = allctr->alloc_no - ERTS_ALC_A_MIN;
+
+ /* We only consider the current allocation type; . */
+ if (allctr->mbcs.blocks[ix].curr.size > allctr->cpool.abandon_limit) {
+ return;
+ }
+ }
ncrr_in_pool = erts_atomic_read_nob(&allctr->cpool.stat.no_carriers);
if (ncrr_in_pool >= allctr->cpool.in_pool_limit)
@@ -2515,7 +2547,7 @@ mbc_alloc_finalize(Allctr_t *allctr,
}
ERTS_ALC_CPOOL_ALLOC_OP(allctr);
- STAT_MBC_BLK_ALLOC(allctr, crr, blk_sz, alcu_flgs);
+ STAT_MBC_BLK_ALLOC(allctr, crr, blk_sz);
ASSERT(IS_ALLOCED_BLK(blk));
ASSERT(blk_sz == MBC_BLK_SZ(blk));
@@ -2734,7 +2766,7 @@ mbc_free(Allctr_t *allctr, ErtsAlcType_t type, void *p, Carrier_t **busy_pcrr_pp
ERTS_ALC_CPOOL_FREE_OP(allctr);
- STAT_MBC_BLK_FREE(allctr, type, crr, busy_pcrr_pp, blk_sz, alcu_flgs);
+ STAT_MBC_BLK_FREE(allctr, type, crr, busy_pcrr_pp, blk_sz);
is_first_blk = IS_MBC_FIRST_ABLK(allctr, blk);
is_last_blk = IS_LAST_BLK(blk);
@@ -2934,8 +2966,8 @@ mbc_realloc(Allctr_t *allctr, ErtsAlcType_t type, void *p, Uint size,
crr = ABLK_TO_MBC(blk);
ERTS_ALC_CPOOL_REALLOC_OP(allctr);
- STAT_MBC_BLK_FREE(allctr, type, crr, NULL, old_blk_sz, alcu_flgs);
- STAT_MBC_BLK_ALLOC(allctr, crr, blk_sz, alcu_flgs);
+ STAT_MBC_BLK_FREE(allctr, type, crr, NULL, old_blk_sz);
+ STAT_MBC_BLK_ALLOC(allctr, crr, blk_sz);
ASSERT(MBC_BLK_SZ(blk) >= allctr->min_block_size);
@@ -3038,8 +3070,8 @@ mbc_realloc(Allctr_t *allctr, ErtsAlcType_t type, void *p, Uint size,
}
ERTS_ALC_CPOOL_REALLOC_OP(allctr);
- STAT_MBC_BLK_FREE(allctr, type, crr, NULL, old_blk_sz, alcu_flgs);
- STAT_MBC_BLK_ALLOC(allctr, crr, blk_sz, alcu_flgs);
+ STAT_MBC_BLK_FREE(allctr, type, crr, NULL, old_blk_sz);
+ STAT_MBC_BLK_ALLOC(allctr, crr, blk_sz);
ASSERT(IS_ALLOCED_BLK(blk));
ASSERT(blk_sz == MBC_BLK_SZ(blk));
@@ -3186,7 +3218,7 @@ mbc_realloc(Allctr_t *allctr, ErtsAlcType_t type, void *p, Uint size,
0);
ERTS_ALC_CPOOL_FREE_OP(allctr);
- STAT_MBC_BLK_FREE(allctr, type, crr, NULL, old_blk_sz, alcu_flgs);
+ STAT_MBC_BLK_FREE(allctr, type, crr, NULL, old_blk_sz);
return new_p;
}
@@ -3216,18 +3248,7 @@ typedef union {
char align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(ErtsAlcCPoolData_t))];
} ErtsAlcCrrPool_t;
-#if ERTS_ALC_A_INVALID != 0
-# error "Carrier pool implementation assumes ERTS_ALC_A_INVALID == 0"
-#endif
-#if ERTS_ALC_A_MIN <= ERTS_ALC_A_INVALID
-# error "Carrier pool implementation assumes ERTS_ALC_A_MIN > ERTS_ALC_A_INVALID"
-#endif
-
-/* The pools are only allowed to be manipulated by managed threads except in
- * the alloc_SUITE:cpool test, where only test_carrier_pool is used. */
-
-static ErtsAlcCrrPool_t firstfit_carrier_pool;
-static ErtsAlcCrrPool_t test_carrier_pool;
+static ErtsAlcCrrPool_t firstfit_carrier_pools[ERTS_ALC_NO_CPOOLS] erts_align_attribute(ERTS_CACHE_LINE_SIZE);
#define ERTS_ALC_CPOOL_MAX_BACKOFF (1 << 8)
@@ -3357,27 +3378,17 @@ cpool_insert(Allctr_t *allctr, Carrier_t *crr)
erts_aint_t val;
ErtsAlcCPoolData_t *sentinel = allctr->cpool.sentinel;
Allctr_t *orig_allctr = crr->cpool.orig_allctr;
+ int ix;
ERTS_ALC_CPOOL_ASSERT(allctr->alloc_no == ERTS_ALC_A_TEST /* testcase */
|| erts_thr_progress_is_managed_thread());
- {
- int alloc_no = allctr->alloc_no;
-
- ERTS_ALC_CPOOL_ASSERT(
- erts_atomic_read_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no]) >= 0 &&
- crr->cpool.blocks_size[alloc_no] >= 0);
-
- ERTS_ALC_CPOOL_ASSERT(
- erts_atomic_read_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no]) >= 0 &&
- crr->cpool.blocks[alloc_no] >= 0);
-
- /* We only modify the counter for our current type since the others are
- * conceptually still in the pool. */
- erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no],
- ((erts_aint_t) crr->cpool.blocks_size[alloc_no]));
- erts_atomic_add_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no],
- ((erts_aint_t) crr->cpool.blocks[alloc_no]));
+ /* Add the carrier's block statistics to the pool. */
+ for (ix = 0; ix < ERTS_ALC_A_COUNT; ix++) {
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size[ix],
+ ((erts_aint_t) crr->cpool.blocks_size[ix]));
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.no_blocks[ix],
+ ((erts_aint_t) crr->cpool.blocks[ix]));
}
erts_atomic_add_nob(&orig_allctr->cpool.stat.carriers_size,
@@ -3454,11 +3465,15 @@ cpool_delete(Allctr_t *allctr, Allctr_t *prev_allctr, Carrier_t *crr)
#ifdef ERTS_ALC_CPOOL_DEBUG
ErtsAlcCPoolData_t *sentinel = allctr->cpool.sentinel;
#endif
+ Allctr_t *orig_allctr = crr->cpool.orig_allctr;
+ int ix;
ERTS_ALC_CPOOL_ASSERT(allctr->alloc_no == ERTS_ALC_A_TEST /* testcase */
|| erts_thr_progress_is_managed_thread());
ERTS_ALC_CPOOL_ASSERT(sentinel != &crr->cpool);
+ ERTS_ALC_CPOOL_ASSERT(orig_allctr == prev_allctr);
+
/* Set mod marker on next ptr of our predecessor */
val = (erts_aint_t) &crr->cpool;
@@ -3531,30 +3546,31 @@ cpool_delete(Allctr_t *allctr, Allctr_t *prev_allctr, Carrier_t *crr)
crr->cpool.thr_prgr = erts_thr_progress_later(NULL);
- {
- Allctr_t *orig_allctr = crr->cpool.orig_allctr;
- int alloc_no = allctr->alloc_no;
-
- ERTS_ALC_CPOOL_ASSERT(orig_allctr == prev_allctr);
-
- ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks_size[alloc_no] <=
- erts_atomic_read_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no]));
-
- ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks[alloc_no] <=
- erts_atomic_read_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no]));
+ /* Subtract the carrier's block statistics from the pool. */
+ for (ix = 0; ix < ERTS_ALC_A_COUNT; ix++) {
+#ifdef ERTS_ALC_CPOOL_DEBUG
+ SWord new_blk_sz, new_blk_no;
- /* We only modify the counters for our current type since the others
- * were, conceptually, never taken out of the pool. */
- erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no],
- -((erts_aint_t) crr->cpool.blocks_size[alloc_no]));
- erts_atomic_add_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no],
- -((erts_aint_t) crr->cpool.blocks[alloc_no]));
+ new_blk_sz =
+ erts_atomic_add_read_nob(&orig_allctr->cpool.stat.blocks_size[ix],
+ -((erts_aint_t)crr->cpool.blocks_size[ix]));
+ new_blk_no =
+ erts_atomic_add_read_nob(&orig_allctr->cpool.stat.no_blocks[ix],
+ -((erts_aint_t)crr->cpool.blocks[ix]));
- erts_atomic_add_nob(&orig_allctr->cpool.stat.carriers_size,
- -((erts_aint_t) CARRIER_SZ(crr)));
- erts_atomic_dec_wb(&orig_allctr->cpool.stat.no_carriers);
+ ERTS_ALC_CPOOL_ASSERT(new_blk_sz >= 0);
+ ERTS_ALC_CPOOL_ASSERT(new_blk_no >= 0);
+#else
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size[ix],
+ -((erts_aint_t) crr->cpool.blocks_size[ix]));
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.no_blocks[ix],
+ -((erts_aint_t) crr->cpool.blocks[ix]));
+#endif
}
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.carriers_size,
+ -((erts_aint_t) CARRIER_SZ(crr)));
+ erts_atomic_dec_wb(&orig_allctr->cpool.stat.no_carriers);
}
static Carrier_t *
@@ -3937,9 +3953,12 @@ allctr_abandon_limit(Allctr_t *allctr)
{
UWord limit;
UWord csz;
+ int i;
- csz = allctr->mbcs.curr.norm.mseg.size;
- csz += allctr->mbcs.curr.norm.sys_alloc.size;
+ csz = 0;
+ for (i = ERTS_CRR_ALLOC_MIN; i <= ERTS_CRR_ALLOC_MAX; i++) {
+ csz += allctr->mbcs.carriers[i].size;
+ }
limit = csz*allctr->cpool.util_limit;
if (limit > csz)
@@ -3961,7 +3980,7 @@ abandon_carrier(Allctr_t *allctr, Carrier_t *crr)
{
erts_aint_t iallctr;
- STAT_MBC_ABANDON(allctr, crr);
+ STAT_MBC_CPOOL_ABANDON(allctr, crr);
unlink_carrier(&allctr->mbc_list, crr);
allctr->remove_mbc(allctr, crr);
@@ -4023,9 +4042,12 @@ static void
cpool_read_stat(Allctr_t *allctr, int alloc_no,
UWord *nocp, UWord *cszp, UWord *nobp, UWord *bszp)
{
+ int block_ix;
int i;
UWord noc = 0, csz = 0, nob = 0, bsz = 0;
+ block_ix = alloc_no - ERTS_ALC_A_MIN;
+
/*
* We try to get consistent values, but after
* ERTS_ALC_CPOOL_MAX_FAILED_STAT_READS failed
@@ -4041,10 +4063,10 @@ cpool_read_stat(Allctr_t *allctr, int alloc_no,
? erts_atomic_read_nob(&allctr->cpool.stat.carriers_size)
: 0);
tnob = (UWord) (nobp
- ? erts_atomic_read_nob(&allctr->cpool.stat.no_blocks[alloc_no])
+ ? erts_atomic_read_nob(&allctr->cpool.stat.no_blocks[block_ix])
: 0);
tbsz = (UWord) (bszp
- ? erts_atomic_read_nob(&allctr->cpool.stat.blocks_size[alloc_no])
+ ? erts_atomic_read_nob(&allctr->cpool.stat.blocks_size[block_ix])
: 0);
if (tnoc == noc && tcsz == csz && tnob == nob && tbsz == bsz)
break;
@@ -4192,12 +4214,12 @@ create_carrier(Allctr_t *allctr, Uint umem_sz, UWord flags)
if (erts_mseg_no(&allctr->mseg_opt) >= max_mseg_carriers)
goto try_sys_alloc;
if (flags & CFLG_SBC) {
- if (allctr->sbcs.curr.norm.mseg.no >= allctr->max_mseg_sbcs)
+ if (allctr->sbcs.carriers[ERTS_CRR_ALLOC_MSEG].no >= allctr->max_mseg_sbcs)
goto try_sys_alloc;
}
#if !ERTS_SUPER_ALIGNED_MSEG_ONLY
else {
- if (allctr->mbcs.curr.norm.mseg.no >= allctr->max_mseg_mbcs)
+ if (allctr->mbcs.carriers[ERTS_CRR_ALLOC_MSEG].no >= allctr->max_mseg_mbcs)
goto try_sys_alloc;
}
#endif
@@ -4604,6 +4626,7 @@ static struct {
Eterm acul;
Eterm acnl;
Eterm acfml;
+ Eterm cp;
#if HAVE_ERTS_MSEG
Eterm mmc;
@@ -4638,10 +4661,10 @@ static struct {
Eterm mseg_alloc_carriers;
#endif
Eterm carriers;
- Eterm blocks_size;
- Eterm blocks;
- Eterm foreign_blocks;
+ Eterm blocks;
+ Eterm count;
+ Eterm size;
Eterm calls;
Eterm sys_alloc;
@@ -4652,12 +4675,18 @@ static struct {
Eterm mseg_dealloc;
Eterm mseg_realloc;
#endif
+
+ Eterm At_sign;
+
#ifdef DEBUG
Eterm end_of_atoms;
#endif
} am;
+static char *allocator_char_str[ERTS_ALC_A_MAX + 1];
+static Eterm allocator_char_atom[ERTS_ALC_A_MAX + 1];
static Eterm alloc_type_atoms[ERTS_ALC_N_MAX + 1];
+static Eterm alloc_num_atoms[ERTS_ALC_A_MAX + 1];
static ERTS_INLINE void atom_init(Eterm *atom, char *name)
{
@@ -4707,6 +4736,7 @@ init_atoms(Allctr_t *allctr)
AM_INIT(acul);
AM_INIT(acnl);
AM_INIT(acfml);
+ AM_INIT(cp);
#if HAVE_ERTS_MSEG
AM_INIT(mmc);
@@ -4741,9 +4771,9 @@ init_atoms(Allctr_t *allctr)
AM_INIT(mseg_alloc_carriers);
#endif
AM_INIT(carriers);
- AM_INIT(blocks_size);
AM_INIT(blocks);
- AM_INIT(foreign_blocks);
+ AM_INIT(count);
+ AM_INIT(size);
AM_INIT(calls);
AM_INIT(sys_alloc);
@@ -4755,18 +4785,33 @@ init_atoms(Allctr_t *allctr)
AM_INIT(mseg_realloc);
#endif
+ am.At_sign = am_atom_put("@", 1);
+
#ifdef DEBUG
for (atom = (Eterm *) &am; atom < &am.end_of_atoms; atom++) {
ASSERT(*atom != THE_NON_VALUE);
}
#endif
+ for (ix = ERTS_ALC_A_MIN; ix <= ERTS_ALC_A_MAX; ix++) {
+ char *cp_str = allocator_char_str[ix];
+ Eterm cp_atom = am_atom_put(cp_str, sys_strlen(cp_str));
+ allocator_char_atom[ix] = cp_atom;
+ }
+
for (ix = ERTS_ALC_N_MIN; ix <= ERTS_ALC_N_MAX; ix++) {
const char *name = ERTS_ALC_N2TD(ix);
size_t len = sys_strlen(name);
alloc_type_atoms[ix] = am_atom_put(name, len);
}
+
+ for (ix = ERTS_ALC_A_MIN; ix <= ERTS_ALC_A_MAX; ix++) {
+ const char *name = ERTS_ALC_A2AD(ix);
+ size_t len = sys_strlen(name);
+
+ alloc_num_atoms[ix] = am_atom_put(name, len);
+ }
}
if (allctr && !allctr->atoms_initialized) {
@@ -4939,39 +4984,81 @@ sz_info_carriers(Allctr_t *allctr,
Uint *szp)
{
Eterm res = THE_NON_VALUE;
- UWord curr_size = cs->curr.norm.mseg.size + cs->curr.norm.sys_alloc.size;
+ UWord curr_size;
+ int i;
+
+ curr_size = 0;
+ for (i = ERTS_CRR_ALLOC_MIN; i <= ERTS_CRR_ALLOC_MAX; i++) {
+ curr_size += cs->carriers[i].size;
+ }
if (print_to_p) {
- fmtfn_t to = *print_to_p;
- void *arg = print_to_arg;
- erts_print(to,
- arg,
- "%sblocks size: %bpu %bpu %bpu\n",
- prefix,
- cs->blocks.curr.size,
- cs->blocks.max.size,
- cs->blocks.max_ever.size);
- erts_print(to,
- arg,
- "%scarriers size: %bpu %bpu %bpu\n",
- prefix,
- curr_size,
- cs->max.size,
- cs->max_ever.size);
+ fmtfn_t to = *print_to_p;
+ void *arg = print_to_arg;
+ int alloc_no;
+
+ for (alloc_no = ERTS_ALC_A_MIN;
+ alloc_no <= ERTS_ALC_A_MAX;
+ alloc_no++) {
+ int ix = alloc_no - ERTS_ALC_A_MIN;
+
+ erts_print(to,
+ arg,
+ "%sblocks[%s] size: %bpu %bpu %bpu\n",
+ prefix,
+ erts_alc_a2ad[alloc_no],
+ cs->blocks[ix].curr.size,
+ cs->blocks[ix].max.size,
+ cs->blocks[ix].max_ever.size);
+ }
+
+ erts_print(to,
+ arg,
+ "%scarriers size: %bpu %bpu %bpu\n",
+ prefix,
+ curr_size,
+ cs->max.size,
+ cs->max_ever.size);
}
if (hpp || szp) {
- res = NIL;
- add_4tup(hpp, szp, &res,
- am.carriers_size,
- bld_unstable_uint(hpp, szp, curr_size),
- bld_unstable_uint(hpp, szp, cs->max.size),
- bld_unstable_uint(hpp, szp, cs->max_ever.size));
- add_4tup(hpp, szp, &res,
- am.blocks_size,
- bld_unstable_uint(hpp, szp, cs->blocks.curr.size),
- bld_unstable_uint(hpp, szp, cs->blocks.max.size),
- bld_unstable_uint(hpp, szp, cs->blocks.max_ever.size));
+ Eterm blocks;
+ int alloc_no;
+
+ res = NIL;
+
+ add_4tup(hpp, szp, &res,
+ am.carriers_size,
+ bld_unstable_uint(hpp, szp, curr_size),
+ bld_unstable_uint(hpp, szp, cs->max.size),
+ bld_unstable_uint(hpp, szp, cs->max_ever.size));
+
+ blocks = NIL;
+ for (alloc_no = ERTS_ALC_A_MIN;
+ alloc_no <= ERTS_ALC_A_MAX;
+ alloc_no++) {
+ int ix = alloc_no - ERTS_ALC_A_MIN;
+ UWord curr, max, max_ever;
+ Eterm info = NIL;
+
+ curr = cs->blocks[ix].curr.size;
+ max = cs->blocks[ix].max.size;
+ max_ever = cs->blocks[ix].max_ever.size;
+
+ if (curr == 0 && max == 0 && max_ever == 0) {
+ continue;
+ }
+
+ add_4tup(hpp, szp, &info,
+ am.size,
+ bld_unstable_uint(hpp, szp, curr),
+ bld_unstable_uint(hpp, szp, max),
+ bld_unstable_uint(hpp, szp, max_ever));
+
+ add_2tup(hpp, szp, &blocks, alloc_num_atoms[alloc_no], info);
+ }
+
+ add_2tup(hpp, szp, &res, am.blocks, blocks);
}
return res;
@@ -4988,33 +5075,43 @@ info_cpool(Allctr_t *allctr,
Uint *szp)
{
Eterm res = THE_NON_VALUE;
- UWord noc, csz, nob, bsz;
+ UWord noc, csz;
- noc = csz = nob = bsz = ~0;
+ noc = csz = ~0;
if (print_to_p || hpp) {
- if (sz_only)
- cpool_read_stat(allctr, allctr->alloc_no, NULL, &csz, NULL, &bsz);
- else
- cpool_read_stat(allctr, allctr->alloc_no, &noc, &csz, &nob, &bsz);
+ cpool_read_stat(allctr, allctr->alloc_no, &noc, &csz, NULL, NULL);
}
if (print_to_p) {
- fmtfn_t to = *print_to_p;
- void *arg = print_to_arg;
- if (!sz_only)
- erts_print(to, arg, "%sblocks: %bpu\n", prefix, nob);
- erts_print(to, arg, "%sblocks size: %bpu\n", prefix, bsz);
- if (!sz_only)
- erts_print(to, arg, "%scarriers: %bpu\n", prefix, noc);
- erts_print(to, arg, "%scarriers size: %bpu\n", prefix, csz);
+ fmtfn_t to = *print_to_p;
+ void *arg = print_to_arg;
+ int alloc_no;
+
+ for (alloc_no = ERTS_ALC_A_MIN;
+ alloc_no <= ERTS_ALC_A_MAX;
+ alloc_no++) {
+ UWord nob, bsz;
+
+ nob = bsz = ~0;
+ cpool_read_stat(allctr, alloc_no, NULL, NULL, &nob, &bsz);
+
+ if (!sz_only)
+ erts_print(to, arg, "%sblocks[%s] count: %bpu\n",
+ prefix, erts_alc_a2ad[alloc_no], nob);
+ erts_print(to, arg, "%sblocks[%s] size: %bpu\n",
+ prefix, erts_alc_a2ad[alloc_no], bsz);
+ }
+
+ if (!sz_only)
+ erts_print(to, arg, "%scarriers: %bpu\n", prefix, noc);
+ erts_print(to, arg, "%scarriers size: %bpu\n", prefix, csz);
}
if (hpp || szp) {
- Eterm foreign_blocks;
- int i;
+ Eterm blocks;
+ int alloc_no;
- foreign_blocks = NIL;
- res = NIL;
+ res = NIL;
if (!sz_only) {
add_3tup(hpp, szp, &res, am.fail_pooled,
@@ -5072,49 +5169,36 @@ info_cpool(Allctr_t *allctr,
bld_unstable_uint(hpp, szp, noc));
}
- add_2tup(hpp, szp, &res,
- am.blocks_size,
- bld_unstable_uint(hpp, szp, bsz));
+ blocks = NIL;
+ for (alloc_no = ERTS_ALC_A_MIN;
+ alloc_no <= ERTS_ALC_A_MAX;
+ alloc_no++) {
+ UWord nob, bsz;
+ Eterm info;
- if (!sz_only) {
- add_2tup(hpp, szp, &res,
- am.blocks,
- bld_unstable_uint(hpp, szp, nob));
- }
-
- for (i = ERTS_ALC_A_MIN; i <= ERTS_ALC_A_MAX; i++) {
- const char *name_str;
- Eterm name, info;
-
- if (i == allctr->alloc_no) {
- continue;
- }
-
- cpool_read_stat(allctr, i, NULL, NULL, &nob, &bsz);
+ nob = bsz = ~0;
+ cpool_read_stat(allctr, alloc_no, NULL, NULL, &nob, &bsz);
if (bsz == 0 && (nob == 0 || sz_only)) {
continue;
}
- name_str = ERTS_ALC_A2AD(i);
info = NIL;
add_2tup(hpp, szp, &info,
- am.blocks_size,
+ am.size,
bld_unstable_uint(hpp, szp, bsz));
if (!sz_only) {
add_2tup(hpp, szp, &info,
- am.blocks,
+ am.count,
bld_unstable_uint(hpp, szp, nob));
}
- name = am_atom_put(name_str, sys_strlen(name_str));
-
- add_2tup(hpp, szp, &foreign_blocks, name, info);
+ add_2tup(hpp, szp, &blocks, alloc_num_atoms[alloc_no], info);
}
- add_2tup(hpp, szp, &res, am.foreign_blocks, foreign_blocks);
+ add_2tup(hpp, szp, &res, am.blocks, blocks);
}
return res;
@@ -5132,27 +5216,41 @@ info_carriers(Allctr_t *allctr,
{
Eterm res = THE_NON_VALUE;
UWord curr_no, curr_size;
-
- curr_no = cs->curr.norm.mseg.no + cs->curr.norm.sys_alloc.no;
- curr_size = cs->curr.norm.mseg.size + cs->curr.norm.sys_alloc.size;
+ int i;
+
+ curr_no = curr_size = 0;
+ for (i = ERTS_CRR_ALLOC_MIN; i <= ERTS_CRR_ALLOC_MAX; i++) {
+ curr_no += cs->carriers[i].no;
+ curr_size += cs->carriers[i].size;
+ }
if (print_to_p) {
- fmtfn_t to = *print_to_p;
- void *arg = print_to_arg;
- erts_print(to,
- arg,
- "%sblocks: %bpu %bpu %bpu\n",
- prefix,
- cs->blocks.curr.no,
- cs->blocks.max.no,
- cs->blocks.max_ever.no);
- erts_print(to,
- arg,
- "%sblocks size: %bpu %bpu %bpu\n",
- prefix,
- cs->blocks.curr.size,
- cs->blocks.max.size,
- cs->blocks.max_ever.size);
+ fmtfn_t to = *print_to_p;
+ void *arg = print_to_arg;
+ int alloc_no;
+
+ for (alloc_no = ERTS_ALC_A_MIN;
+ alloc_no <= ERTS_ALC_A_MAX;
+ alloc_no++) {
+ int ix = alloc_no - ERTS_ALC_A_MIN;
+ erts_print(to,
+ arg,
+ "%sblocks[%s] count: %bpu %bpu %bpu\n",
+ prefix,
+ erts_alc_a2ad[alloc_no],
+ cs->blocks[ix].curr.no,
+ cs->blocks[ix].max.no,
+ cs->blocks[ix].max_ever.no);
+ erts_print(to,
+ arg,
+ "%sblocks[%s] size: %bpu %bpu %bpu\n",
+ prefix,
+ erts_alc_a2ad[alloc_no],
+ cs->blocks[ix].curr.size,
+ cs->blocks[ix].max.size,
+ cs->blocks[ix].max_ever.size);
+ }
+
erts_print(to,
arg,
"%scarriers: %bpu %bpu %bpu\n",
@@ -5165,13 +5263,13 @@ info_carriers(Allctr_t *allctr,
arg,
"%smseg carriers: %bpu\n",
prefix,
- cs->curr.norm.mseg.no);
+ cs->carriers[ERTS_CRR_ALLOC_MSEG].no);
#endif
erts_print(to,
arg,
"%ssys_alloc carriers: %bpu\n",
prefix,
- cs->curr.norm.sys_alloc.no);
+ cs->carriers[ERTS_CRR_ALLOC_SYS].no);
erts_print(to,
arg,
"%scarriers size: %bpu %bpu %bpu\n",
@@ -5184,24 +5282,27 @@ info_carriers(Allctr_t *allctr,
arg,
"%smseg carriers size: %bpu\n",
prefix,
- cs->curr.norm.mseg.size);
+ cs->carriers[ERTS_CRR_ALLOC_MSEG].size);
#endif
erts_print(to,
arg,
"%ssys_alloc carriers size: %bpu\n",
prefix,
- cs->curr.norm.sys_alloc.size);
+ cs->carriers[ERTS_CRR_ALLOC_SYS].size);
}
if (hpp || szp) {
+ Eterm blocks;
+ int alloc_no;
+
res = NIL;
add_2tup(hpp, szp, &res,
am.sys_alloc_carriers_size,
- bld_unstable_uint(hpp, szp, cs->curr.norm.sys_alloc.size));
+ bld_unstable_uint(hpp, szp, cs->carriers[ERTS_CRR_ALLOC_SYS].size));
#if HAVE_ERTS_MSEG
add_2tup(hpp, szp, &res,
am.mseg_alloc_carriers_size,
- bld_unstable_uint(hpp, szp, cs->curr.norm.mseg.size));
+ bld_unstable_uint(hpp, szp, cs->carriers[ERTS_CRR_ALLOC_MSEG].size));
#endif
add_4tup(hpp, szp, &res,
am.carriers_size,
@@ -5210,27 +5311,57 @@ info_carriers(Allctr_t *allctr,
bld_unstable_uint(hpp, szp, cs->max_ever.size));
add_2tup(hpp, szp, &res,
am.sys_alloc_carriers,
- bld_unstable_uint(hpp, szp, cs->curr.norm.sys_alloc.no));
+ bld_unstable_uint(hpp, szp, cs->carriers[ERTS_CRR_ALLOC_SYS].no));
#if HAVE_ERTS_MSEG
add_2tup(hpp, szp, &res,
am.mseg_alloc_carriers,
- bld_unstable_uint(hpp, szp, cs->curr.norm.mseg.no));
+ bld_unstable_uint(hpp, szp, cs->carriers[ERTS_CRR_ALLOC_MSEG].no));
#endif
add_4tup(hpp, szp, &res,
am.carriers,
bld_unstable_uint(hpp, szp, curr_no),
bld_unstable_uint(hpp, szp, cs->max.no),
bld_unstable_uint(hpp, szp, cs->max_ever.no));
- add_4tup(hpp, szp, &res,
- am.blocks_size,
- bld_unstable_uint(hpp, szp, cs->blocks.curr.size),
- bld_unstable_uint(hpp, szp, cs->blocks.max.size),
- bld_unstable_uint(hpp, szp, cs->blocks.max_ever.size));
- add_4tup(hpp, szp, &res,
- am.blocks,
- bld_unstable_uint(hpp, szp, cs->blocks.curr.no),
- bld_unstable_uint(hpp, szp, cs->blocks.max.no),
- bld_unstable_uint(hpp, szp, cs->blocks.max_ever.no));
+
+ blocks = NIL;
+ for (alloc_no = ERTS_ALC_A_MIN;
+ alloc_no <= ERTS_ALC_A_MAX;
+ alloc_no++) {
+ int ix = alloc_no - ERTS_ALC_A_MIN;
+ UWord curr_size, max_size, max_ever_size;
+ UWord curr_no, max_no, max_ever_no;
+ Eterm info;
+
+ curr_size = cs->blocks[ix].curr.size;
+ max_size = cs->blocks[ix].max.size;
+ max_ever_size = cs->blocks[ix].max_ever.size;
+
+ curr_no = cs->blocks[ix].curr.no;
+ max_no = cs->blocks[ix].max.no;
+ max_ever_no = cs->blocks[ix].max_ever.no;
+
+ if (max_ever_no == 0) {
+ continue;
+ }
+
+ info = NIL;
+
+ add_4tup(hpp, szp, &info,
+ am.size,
+ bld_unstable_uint(hpp, szp, curr_size),
+ bld_unstable_uint(hpp, szp, max_size),
+ bld_unstable_uint(hpp, szp, max_ever_size));
+
+ add_4tup(hpp, szp, &info,
+ am.count,
+ bld_unstable_uint(hpp, szp, curr_no),
+ bld_unstable_uint(hpp, szp, max_no),
+ bld_unstable_uint(hpp, szp, max_ever_no));
+
+ add_2tup(hpp, szp, &blocks, alloc_num_atoms[alloc_no], info);
+ }
+
+ add_2tup(hpp, szp, &res, am.blocks, blocks);
}
return res;
@@ -5363,6 +5494,8 @@ info_options(Allctr_t *allctr,
{
Eterm res = THE_NON_VALUE;
UWord acul, acnl, acfml;
+ char *cp_str;
+ Eterm cp_atom;
if (!allctr) {
if (print_to_p)
@@ -5377,6 +5510,19 @@ info_options(Allctr_t *allctr,
acul = allctr->cpool.util_limit;
acnl = allctr->cpool.in_pool_limit;
acfml = allctr->cpool.fblk_min_limit;
+ ASSERT(allctr->cpool.carrier_pool <= ERTS_ALC_A_MAX);
+ if (allctr->cpool.carrier_pool < ERTS_ALC_A_MIN) {
+ cp_str = "undefined";
+ cp_atom = am_undefined;
+ }
+ else if (allctr->cpool.carrier_pool == ERTS_ALC_COMMON_CPOOL_IX) {
+ cp_str = "@";
+ cp_atom = am.At_sign;
+ }
+ else {
+ cp_str = allocator_char_str[allctr->cpool.carrier_pool];
+ cp_atom = allocator_char_atom[allctr->cpool.carrier_pool];
+ }
if (print_to_p) {
char topt[21]; /* Enough for any 64-bit integer */
@@ -5384,6 +5530,10 @@ info_options(Allctr_t *allctr,
erts_snprintf(&topt[0], sizeof(topt), "%d", allctr->t);
else
erts_snprintf(&topt[0], sizeof(topt), "false");
+ /*
+ * Do not use '%T' in the format string here. You'll
+ * likely get into lock order violations...
+ */
erts_print(*print_to_p,
print_to_arg,
"option e: true\n"
@@ -5405,7 +5555,10 @@ info_options(Allctr_t *allctr,
"option lmbcs: %beu\n"
"option smbcs: %beu\n"
"option mbcgs: %beu\n"
- "option acul: %bpu\n",
+ "option acul: %bpu\n"
+ "option acnl: %bpu\n"
+ "option acfml: %bpu\n"
+ "option cp: %s\n",
topt,
allctr->ramv ? "true" : "false",
allctr->atags ? "true" : "false",
@@ -5424,13 +5577,17 @@ info_options(Allctr_t *allctr,
allctr->largest_mbc_size,
allctr->smallest_mbc_size,
allctr->mbc_growth_stages,
- acul);
+ acul,
+ acnl,
+ acfml,
+ cp_str);
}
res = (*allctr->info_options)(allctr, "option ", print_to_p, print_to_arg,
hpp, szp);
if (hpp || szp) {
+ add_2tup(hpp, szp, &res, am.cp, cp_atom);
add_2tup(hpp, szp, &res,
am.acfml,
bld_uint(hpp, szp, acfml));
@@ -5490,23 +5647,50 @@ info_options(Allctr_t *allctr,
static ERTS_INLINE void
update_max_ever_values(CarriersStats_t *cs)
{
- if (cs->max_ever.no < cs->max.no)
- cs->max_ever.no = cs->max.no;
- if (cs->max_ever.size < cs->max.size)
- cs->max_ever.size = cs->max.size;
- if (cs->blocks.max_ever.no < cs->blocks.max.no)
- cs->blocks.max_ever.no = cs->blocks.max.no;
- if (cs->blocks.max_ever.size < cs->blocks.max.size)
- cs->blocks.max_ever.size = cs->blocks.max.size;
+ int ix;
+
+ for (ix = 0; ix < ERTS_ALC_A_COUNT; ix++) {
+ BlockStats_t *bstats = &cs->blocks[ix];
+
+ if (bstats->max_ever.no < bstats->max.no) {
+ bstats->max_ever.no = bstats->max.no;
+ }
+
+ if (bstats->max_ever.size < bstats->max.size) {
+ bstats->max_ever.size = bstats->max.size;
+ }
+ }
+
+ if (cs->max_ever.no < cs->max.no) {
+ cs->max_ever.no = cs->max.no;
+ }
+
+ if (cs->max_ever.size < cs->max.size) {
+ cs->max_ever.size = cs->max.size;
+ }
}
static ERTS_INLINE void
reset_max_values(CarriersStats_t *cs)
{
- cs->max.no = cs->curr.norm.mseg.no + cs->curr.norm.sys_alloc.no;
- cs->max.size = cs->curr.norm.mseg.size + cs->curr.norm.sys_alloc.size;
- cs->blocks.max.no = cs->blocks.curr.no;
- cs->blocks.max.size = cs->blocks.curr.size;
+ UWord curr_no, curr_size;
+ int ix;
+
+ for (ix = 0; ix < ERTS_ALC_A_COUNT; ix++) {
+ BlockStats_t *bstats = &cs->blocks[ix];
+
+ bstats->max.no = bstats->curr.no;
+ bstats->max.size = bstats->curr.size;
+ }
+
+ curr_no = curr_size = 0;
+ for (ix = ERTS_CRR_ALLOC_MIN; ix <= ERTS_CRR_ALLOC_MAX; ix++) {
+ curr_no += cs->carriers[ix].no;
+ curr_size += cs->carriers[ix].size;
+ }
+
+ cs->max.no = curr_no;
+ cs->max.size = curr_size;
}
@@ -5614,11 +5798,6 @@ erts_alcu_sz_info(Allctr_t *allctr,
ERTS_ALCU_DBG_CHK_THR_ACCESS(allctr);
- /* Update sbc values not continuously updated */
- allctr->sbcs.blocks.curr.no
- = allctr->sbcs.curr.norm.mseg.no + allctr->sbcs.curr.norm.sys_alloc.no;
- allctr->sbcs.blocks.max.no = allctr->sbcs.max.no;
-
update_max_ever_values(&allctr->mbcs);
update_max_ever_values(&allctr->sbcs);
@@ -5690,11 +5869,6 @@ erts_alcu_info(Allctr_t *allctr,
ERTS_ALCU_DBG_CHK_THR_ACCESS(allctr);
- /* Update sbc values not continuously updated */
- allctr->sbcs.blocks.curr.no
- = allctr->sbcs.curr.norm.mseg.no + allctr->sbcs.curr.norm.sys_alloc.no;
- allctr->sbcs.blocks.max.no = allctr->sbcs.max.no;
-
update_max_ever_values(&allctr->mbcs);
update_max_ever_values(&allctr->sbcs);
@@ -5753,61 +5927,66 @@ erts_alcu_info(Allctr_t *allctr,
void
erts_alcu_foreign_size(Allctr_t *allctr, ErtsAlcType_t alloc_no, AllctrSize_t *size)
{
+ int ix;
+
+ sys_memset(size, 0, sizeof(*size));
+
+ if (allctr->thread_safe)
+ erts_mtx_lock(&allctr->mutex);
+
+ for (ix = ERTS_CRR_ALLOC_MIN; ix <= ERTS_CRR_ALLOC_MAX; ix++) {
+ size->carriers += allctr->mbcs.carriers[ix].size;
+ size->carriers += allctr->sbcs.carriers[ix].size;
+ }
+
+ ix = alloc_no - ERTS_ALC_A_MIN;
+ size->blocks += allctr->mbcs.blocks[ix].curr.size;
+ size->blocks += allctr->sbcs.blocks[ix].curr.size;
+
if (ERTS_ALC_IS_CPOOL_ENABLED(allctr)) {
UWord csz, bsz;
+
+ csz = bsz = 0;
cpool_read_stat(allctr, alloc_no, NULL, &csz, NULL, &bsz);
- size->carriers = csz;
- size->blocks = bsz;
- } else {
- size->carriers = 0;
- size->blocks = 0;
+
+ size->carriers += csz;
+ size->blocks += bsz;
}
+
+ if (allctr->thread_safe)
+ erts_mtx_unlock(&allctr->mutex);
}
void
erts_alcu_current_size(Allctr_t *allctr, AllctrSize_t *size, ErtsAlcUFixInfo_t *fi, int fisz)
{
-
- if (allctr->thread_safe)
- erts_mtx_lock(&allctr->mutex);
-
- size->carriers = allctr->mbcs.curr.norm.mseg.size;
- size->carriers += allctr->mbcs.curr.norm.sys_alloc.size;
- size->carriers += allctr->sbcs.curr.norm.mseg.size;
- size->carriers += allctr->sbcs.curr.norm.sys_alloc.size;
-
- size->blocks = allctr->mbcs.blocks.curr.size;
- size->blocks += allctr->sbcs.blocks.curr.size;
-
- if (ERTS_ALC_IS_CPOOL_ENABLED(allctr)) {
- UWord csz, bsz;
- cpool_read_stat(allctr, allctr->alloc_no, NULL, &csz, NULL, &bsz);
- size->blocks += bsz;
- size->carriers += csz;
- }
+ erts_alcu_foreign_size(allctr, allctr->alloc_no, size);
if (fi) {
- int ix;
- for (ix = 0; ix < fisz; ix++) {
- if (allctr->fix) {
- if (ERTS_ALC_IS_CPOOL_ENABLED(allctr)) {
- fi[ix].allocated += (allctr->fix[ix].type_size
- * allctr->fix[ix].u.cpool.allocated);
- fi[ix].used += (allctr->fix[ix].type_size
- * allctr->fix[ix].u.cpool.used);
- }
- else {
- fi[ix].allocated += (allctr->fix[ix].type_size
- * allctr->fix[ix].u.nocpool.allocated);
- fi[ix].used += (allctr->fix[ix].type_size
- * allctr->fix[ix].u.nocpool.used);
- }
- }
- }
- }
+ int ix;
+
+ if (allctr->thread_safe)
+ erts_mtx_lock(&allctr->mutex);
+
+ for (ix = 0; ix < fisz; ix++) {
+ if (allctr->fix) {
+ if (ERTS_ALC_IS_CPOOL_ENABLED(allctr)) {
+ fi[ix].allocated += (allctr->fix[ix].type_size
+ * allctr->fix[ix].u.cpool.allocated);
+ fi[ix].used += (allctr->fix[ix].type_size
+ * allctr->fix[ix].u.cpool.used);
+ } else {
+ fi[ix].allocated += (allctr->fix[ix].type_size
+ * allctr->fix[ix].u.nocpool.allocated);
+ fi[ix].used += (allctr->fix[ix].type_size
+ * allctr->fix[ix].u.nocpool.used);
+ }
+ }
+ }
- if (allctr->thread_safe)
- erts_mtx_unlock(&allctr->mutex);
+ if (allctr->thread_safe)
+ erts_mtx_unlock(&allctr->mutex);
+ }
}
/* ----------------------------------------------------------------------- */
@@ -6660,13 +6839,15 @@ erts_alcu_start(Allctr_t *allctr, AllctrInit_t *init)
allctr->cpool.dc_list.last = NULL;
allctr->cpool.abandon_limit = 0;
allctr->cpool.disable_abandon = 0;
- for (i = ERTS_ALC_A_MIN; i <= ERTS_ALC_A_MAX; i++) {
+
+ for (i = 0; i < ERTS_ALC_A_COUNT; i++) {
erts_atomic_init_nob(&allctr->cpool.stat.blocks_size[i], 0);
erts_atomic_init_nob(&allctr->cpool.stat.no_blocks[i], 0);
}
+
erts_atomic_init_nob(&allctr->cpool.stat.carriers_size, 0);
erts_atomic_init_nob(&allctr->cpool.stat.no_carriers, 0);
- if (!init->ts && init->acul && init->acnl) {
+ if (!init->ts && init->acul && init->acnl && init->cp >= 0) {
ASSERT(allctr->add_mbc);
ASSERT(allctr->remove_mbc);
ASSERT(allctr->largest_fblk_in_mbc);
@@ -6676,9 +6857,10 @@ erts_alcu_start(Allctr_t *allctr, AllctrInit_t *init)
allctr->cpool.util_limit = init->acul;
allctr->cpool.in_pool_limit = init->acnl;
allctr->cpool.fblk_min_limit = init->acfml;
+ allctr->cpool.carrier_pool = init->cp;
if (allctr->alloc_strat == ERTS_ALC_S_FIRSTFIT) {
- allctr->cpool.sentinel = &firstfit_carrier_pool.sentinel;
+ allctr->cpool.sentinel = &firstfit_carrier_pools[init->cp].sentinel;
}
else if (allctr->alloc_no != ERTS_ALC_A_TEST) {
ERTS_INTERNAL_ERROR("Impossible carrier migration config.");
@@ -6688,12 +6870,13 @@ erts_alcu_start(Allctr_t *allctr, AllctrInit_t *init)
allctr->cpool.util_limit = 0;
allctr->cpool.in_pool_limit = 0;
allctr->cpool.fblk_min_limit = 0;
+ allctr->cpool.carrier_pool = -1;
}
/* The invasive tests don't really care whether the pool is enabled or not,
* so we need to set this unconditionally for this allocator type. */
if (allctr->alloc_no == ERTS_ALC_A_TEST) {
- allctr->cpool.sentinel = &test_carrier_pool.sentinel;
+ allctr->cpool.sentinel = &firstfit_carrier_pools[ERTS_ALC_TEST_CPOOL_IX].sentinel;
}
allctr->sbc_threshold = adjust_sbct(allctr, init->sbct);
@@ -6858,13 +7041,15 @@ erts_alcu_stop(Allctr_t *allctr)
void
erts_alcu_init(AlcUInit_t *init)
{
+ int i;
ErtsAlcCPoolData_t *sentinel;
- sentinel = &firstfit_carrier_pool.sentinel;
- erts_atomic_init_nob(&sentinel->next, (erts_aint_t) sentinel);
- erts_atomic_init_nob(&sentinel->prev, (erts_aint_t) sentinel);
-
- sentinel = &test_carrier_pool.sentinel;
+ for (i = ERTS_ALC_A_MIN; i <= ERTS_ALC_A_MAX; i++) {
+ sentinel = &firstfit_carrier_pools[i].sentinel;
+ erts_atomic_init_nob(&sentinel->next, (erts_aint_t) sentinel);
+ erts_atomic_init_nob(&sentinel->prev, (erts_aint_t) sentinel);
+ }
+ sentinel = &firstfit_carrier_pools[ERTS_ALC_A_INVALID].sentinel;
erts_atomic_init_nob(&sentinel->next, (erts_aint_t) sentinel);
erts_atomic_init_nob(&sentinel->prev, (erts_aint_t) sentinel);
@@ -6884,6 +7069,30 @@ erts_alcu_init(AlcUInit_t *init)
carrier_alignment = sizeof(Unit_t);
#endif
+#ifdef DEBUG
+ for (i = ERTS_ALC_A_MIN; i <= ERTS_ALC_A_MAX; i++)
+ allocator_char_str[i] = NULL;
+#endif
+ allocator_char_str[ERTS_ALC_A_SYSTEM] = "Y";
+ allocator_char_str[ERTS_ALC_A_TEMPORARY] = "T";
+ allocator_char_str[ERTS_ALC_A_SHORT_LIVED] = "S";
+ allocator_char_str[ERTS_ALC_A_STANDARD] = "D";
+ allocator_char_str[ERTS_ALC_A_LONG_LIVED] = "L";
+ allocator_char_str[ERTS_ALC_A_EHEAP] = "H";
+ allocator_char_str[ERTS_ALC_A_ETS] = "E";
+ allocator_char_str[ERTS_ALC_A_FIXED_SIZE] = "F";
+ allocator_char_str[ERTS_ALC_A_LITERAL] = "I";
+#ifdef ERTS_ALC_A_EXEC
+ allocator_char_str[ERTS_ALC_A_EXEC] = "X";
+#endif
+ allocator_char_str[ERTS_ALC_A_BINARY] = "B";
+ allocator_char_str[ERTS_ALC_A_DRIVER] = "R";
+ allocator_char_str[ERTS_ALC_A_TEST] = "Z";
+#ifdef DEBUG
+ for (i = ERTS_ALC_A_MIN; i <= ERTS_ALC_A_MAX; i++)
+ ASSERT(allocator_char_str[i]);
+#endif
+
erts_mtx_init(&init_atoms_mtx, "alcu_init_atoms", NIL,
ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_ALLOCATOR);
@@ -7696,14 +7905,15 @@ typedef struct chist_node__ {
UWord carrier_size;
UWord unscanned_size;
- UWord allocated_size;
- /* BLOCKSCAN_BAILOUT_THRESHOLD guarantees we won't overflow this or the
- * counters in the free block histogram. */
- int allocated_count;
int flags;
- int histogram[1];
+ /* A mirror of the block counters in the carrier's ErtsAlcCPoolData_t. */
+ UWord alloc_count[ERTS_ALC_A_COUNT];
+ UWord alloc_size[ERTS_ALC_A_COUNT];
+
+ /* BLOCKSCAN_BAILOUT_THRESHOLD guarantees we won't overflow. */
+ int free_histogram[1];
} chist_node_t;
typedef struct {
@@ -7712,7 +7922,7 @@ typedef struct {
ErtsIRefStorage iref;
Process *process;
- Eterm allocator_desc;
+ int allocator_number;
chist_node_t *info_list;
UWord info_count;
@@ -7737,7 +7947,7 @@ static int gather_cinfo_scan(Allctr_t *allocator,
state = (gather_cinfo_t*)user_data;
node = calloc(1, sizeof(chist_node_t) +
(state->hist_slot_count - 1) *
- sizeof(node->histogram[0]));
+ sizeof(node->free_histogram[0]));
blocks_scanned = 1;
/* ERTS_CRR_ALCTR_FLG_BUSY is ignored since we've set it ourselves and it
@@ -7747,16 +7957,20 @@ static int gather_cinfo_scan(Allctr_t *allocator,
node->carrier_size = CARRIER_SZ(carrier);
if (IS_SB_CARRIER(carrier)) {
- UWord block_size;
+ int ix = allocator->alloc_no - ERTS_ALC_A_MIN;
- block = SBC2BLK(allocator, carrier);
- block_size = SBC_BLK_SZ(block);
-
- node->allocated_size = block_size;
- node->allocated_count = 1;
+ node->alloc_count[ix] = 1;
+ node->alloc_size[ix] = node->carrier_size;
} else {
UWord scanned_bytes = MBC_HEADER_SIZE(allocator);
+ sys_memcpy(&node->alloc_count[0],
+ &carrier->cpool.blocks[0],
+ sizeof(UWord) * ERTS_ALC_A_COUNT);
+ sys_memcpy(&node->alloc_size[0],
+ &carrier->cpool.blocks_size[0],
+ sizeof(UWord) * ERTS_ALC_A_COUNT);
+
block = MBC_TO_FIRST_BLK(allocator, carrier);
while (1) {
@@ -7764,10 +7978,7 @@ static int gather_cinfo_scan(Allctr_t *allocator,
scanned_bytes += block_size;
- if (IS_ALLOCED_BLK(block)) {
- node->allocated_size += block_size;
- node->allocated_count++;
- } else {
+ if (IS_FREE_BLK(block)) {
UWord size_interval;
int hist_slot;
@@ -7776,7 +7987,7 @@ static int gather_cinfo_scan(Allctr_t *allocator,
hist_slot = MIN(size_interval, state->hist_slot_count - 1);
- node->histogram[hist_slot]++;
+ node->free_histogram[hist_slot]++;
}
if (blocks_scanned >= BLOCKSCAN_BAILOUT_THRESHOLD) {
@@ -7801,46 +8012,89 @@ static int gather_cinfo_scan(Allctr_t *allocator,
static void gather_cinfo_append_result(gather_cinfo_t *state,
chist_node_t *info)
{
- Eterm carrier_size, unscanned_size, allocated_size;
- Eterm histogram_tuple, carrier_tuple;
+ Eterm carrier_tuple, block_list, histogram_tuple;
+ Eterm carrier_size, unscanned_size;
Uint term_size;
Eterm *hp;
int ix;
ASSERT(state->building_result);
+ term_size = 0;
+
+ /* Free block histogram. */
+ term_size += 1 + state->hist_slot_count;
+
+ /* Per-type block list. */
+ for (ix = ERTS_ALC_A_MIN; ix <= ERTS_ALC_A_MAX; ix++) {
+ UWord count = info->alloc_count[ix - ERTS_ALC_A_MIN];
+ UWord size = info->alloc_size[ix - ERTS_ALC_A_MIN];
+
+ if (count > 0) {
+ /* We have at least one block of this type, so we'll need a cons
+ * cell and a 3-tuple; {Type, Count, Size}. */
+ term_size += 2 + 4;
+ term_size += IS_USMALL(0, count) ? 0 : BIG_UINT_HEAP_SIZE;
+ term_size += IS_USMALL(0, size) ? 0 : BIG_UINT_HEAP_SIZE;
+ }
+ }
- term_size = 11 + state->hist_slot_count;
+ /* Carrier tuple and its fields. */
+ term_size += 7;
term_size += IS_USMALL(0, info->carrier_size) ? 0 : BIG_UINT_HEAP_SIZE;
term_size += IS_USMALL(0, info->unscanned_size) ? 0 : BIG_UINT_HEAP_SIZE;
- term_size += IS_USMALL(0, info->allocated_size) ? 0 : BIG_UINT_HEAP_SIZE;
+ /* ... and a finally a cons cell to keep the result in. */
+ term_size += 2;
+
+ /* * * */
hp = erts_produce_heap(&state->msg_factory, term_size, 0);
- hp[0] = make_arityval(state->hist_slot_count);
+ block_list = NIL;
+ for (ix = ERTS_ALC_A_MIN; ix <= ERTS_ALC_A_MAX; ix++) {
+ UWord count = info->alloc_count[ix - ERTS_ALC_A_MIN];
+ UWord size = info->alloc_size[ix - ERTS_ALC_A_MIN];
- for (ix = 0; ix < state->hist_slot_count; ix++) {
- hp[1 + ix] = make_small(info->histogram[ix]);
+ if (count > 0) {
+ Eterm block_count, block_size;
+ Eterm alloc_tuple;
+
+ block_count = bld_unstable_uint(&hp, NULL, count);
+ block_size = bld_unstable_uint(&hp, NULL, size);
+
+ hp[0] = make_arityval(3);
+ hp[1] = alloc_num_atoms[ix];
+ hp[2] = block_count;
+ hp[3] = block_size;
+
+ alloc_tuple = make_tuple(hp);
+ hp += 4;
+
+ block_list = CONS(hp, alloc_tuple, block_list);
+ hp += 2;
+ }
}
+ hp[0] = make_arityval(state->hist_slot_count);
+ for (ix = 0; ix < state->hist_slot_count; ix++) {
+ hp[1 + ix] = make_small(info->free_histogram[ix]);
+ }
histogram_tuple = make_tuple(hp);
hp += 1 + state->hist_slot_count;
carrier_size = bld_unstable_uint(&hp, NULL, info->carrier_size);
unscanned_size = bld_unstable_uint(&hp, NULL, info->unscanned_size);
- allocated_size = bld_unstable_uint(&hp, NULL, info->allocated_size);
- hp[0] = make_arityval(7);
- hp[1] = state->allocator_desc;
- hp[2] = carrier_size;
- hp[3] = unscanned_size;
- hp[4] = allocated_size;
- hp[5] = make_small(info->allocated_count);
- hp[6] = (info->flags & ERTS_CRR_ALCTR_FLG_IN_POOL) ? am_true : am_false;
- hp[7] = histogram_tuple;
+ hp[0] = make_arityval(6);
+ hp[1] = alloc_num_atoms[state->allocator_number];
+ hp[2] = (info->flags & ERTS_CRR_ALCTR_FLG_IN_POOL) ? am_true : am_false;
+ hp[3] = carrier_size;
+ hp[4] = unscanned_size;
+ hp[5] = block_list;
+ hp[6] = histogram_tuple;
carrier_tuple = make_tuple(hp);
- hp += 8;
+ hp += 7;
state->result_list = CONS(hp, carrier_tuple, state->result_list);
@@ -7936,8 +8190,6 @@ int erts_alcu_gather_carrier_info(struct process *p, int allocator_num,
{
gather_cinfo_t *gather_state;
blockscan_t *scanner;
-
- const char *allocator_desc;
Allctr_t *allocator;
ASSERT(is_internal_ref(ref));
@@ -7948,7 +8200,7 @@ int erts_alcu_gather_carrier_info(struct process *p, int allocator_num,
return 0;
}
- allocator_desc = ERTS_ALC_A2AD(allocator_num);
+ ensure_atoms_initialized(allocator);
/* Plain calloc is intentional. */
gather_state = (gather_cinfo_t*)calloc(1, sizeof(gather_cinfo_t));
@@ -7959,9 +8211,7 @@ int erts_alcu_gather_carrier_info(struct process *p, int allocator_num,
scanner->finish = gather_cinfo_finish;
scanner->user_data = gather_state;
- gather_state->allocator_desc = erts_atom_put((byte *)allocator_desc,
- sys_strlen(allocator_desc),
- ERTS_ATOM_ENC_LATIN1, 1);
+ gather_state->allocator_number = allocator_num;
erts_iref_storage_save(&gather_state->iref, ref);
gather_state->hist_slot_start = hist_start * 2;
gather_state->hist_slot_count = hist_width;
@@ -8069,14 +8319,22 @@ void
erts_alcu_verify_unused(Allctr_t *allctr)
{
UWord no;
+ int ix;
- no = allctr->sbcs.curr.norm.mseg.no;
- no += allctr->sbcs.curr.norm.sys_alloc.no;
- no += allctr->mbcs.blocks.curr.no;
+ no = 0;
+ for (ix = ERTS_CRR_ALLOC_MIN; ix <= ERTS_CRR_ALLOC_MAX; ix++) {
+ no += allctr->sbcs.carriers[ix].no;
+ }
+
+ ix = allctr->alloc_no - ERTS_ALC_A_MIN;
+ no += allctr->mbcs.blocks[ix].curr.no;
if (no) {
- UWord sz = allctr->sbcs.blocks.curr.size;
- sz += allctr->mbcs.blocks.curr.size;
+ UWord sz = 0;
+
+ sz += allctr->sbcs.blocks[ix].curr.size;
+ sz += allctr->mbcs.blocks[ix].curr.size;
+
erts_exit(ERTS_ABORT_EXIT,
"%salloc() used when expected to be unused!\n"
"Total amount of blocks allocated: %bpu\n"
diff --git a/erts/emulator/beam/erl_alloc_util.h b/erts/emulator/beam/erl_alloc_util.h
index 6ba4c04046..a8f832afe8 100644
--- a/erts/emulator/beam/erl_alloc_util.h
+++ b/erts/emulator/beam/erl_alloc_util.h
@@ -53,6 +53,7 @@ typedef struct {
int tpref;
int ramv;
int atags;
+ int cp;
UWord sbct;
UWord asbcst;
UWord rsbcst;
@@ -111,6 +112,7 @@ typedef struct {
0, /* (bool) tpref: thread preferred */\
0, /* (bool) ramv: realloc always moves */\
0, /* (bool) atags: tagged allocations */\
+ -1, /* (ix) cp: carrier pool */\
512*1024, /* (bytes) sbct: sbc threshold */\
2*1024*2024, /* (amount) asbcst: abs sbc shrink threshold */\
20, /* (%) rsbcst: rel sbc shrink threshold */\
@@ -149,10 +151,12 @@ typedef struct {
0, /* (bool) tpref: thread preferred */\
0, /* (bool) ramv: realloc always moves */\
0, /* (bool) atags: tagged allocations */\
+ -1, /* (ix) cp: carrier pool */\
64*1024, /* (bytes) sbct: sbc threshold */\
2*1024*2024, /* (amount) asbcst: abs sbc shrink threshold */\
20, /* (%) rsbcst: rel sbc shrink threshold */\
80, /* (%) rsbcmt: rel sbc move threshold */\
+ 50, /* (%) rmbcmt: rel mbc move threshold */\
128*1024, /* (bytes) mmbcs: main multiblock carrier size */\
256, /* (amount) mmsbc: max mseg sbcs */\
~((UWord) 0), /* (amount) mmmbc: max mseg mbcs */ \
@@ -443,6 +447,24 @@ struct AOFF_RBTree_t_ {
} u;
};
+#if ERTS_ALC_A_INVALID != 0
+# error "Carrier pool implementation assumes ERTS_ALC_A_INVALID == 0"
+#endif
+#if ERTS_ALC_A_MIN <= ERTS_ALC_A_INVALID
+# error "Carrier pool implementation assumes ERTS_ALC_A_MIN > ERTS_ALC_A_INVALID"
+#endif
+
+/* The pools are only allowed to be manipulated by managed threads except in
+ * the alloc_SUITE:cpool test, where only ERTS_ALC_TEST_CPOOL_IX pool is used. */
+
+#define ERTS_ALC_TEST_CPOOL_IX ERTS_ALC_A_INVALID
+/*
+ * System is not an alloc_util allocator, so we use its slot for
+ * the common cpool...
+ */
+#define ERTS_ALC_COMMON_CPOOL_IX ERTS_ALC_A_SYSTEM
+#define ERTS_ALC_NO_CPOOLS (ERTS_ALC_A_MAX+1)
+
void aoff_add_pooled_mbc(Allctr_t*, Carrier_t*);
void aoff_remove_pooled_mbc(Allctr_t*, Carrier_t*);
Carrier_t* aoff_lookup_pooled_mbc(Allctr_t*, Uint size);
@@ -456,8 +478,8 @@ typedef struct {
ErtsThrPrgrVal thr_prgr;
erts_atomic_t max_size;
UWord abandon_limit;
- UWord blocks[ERTS_ALC_A_MAX + 1];
- UWord blocks_size[ERTS_ALC_A_MAX + 1];
+ UWord blocks[ERTS_ALC_A_COUNT];
+ UWord blocks_size[ERTS_ALC_A_COUNT];
UWord total_blocks_size;
enum {
ERTS_MBC_IS_HOME,
@@ -492,30 +514,57 @@ typedef struct {
} StatValues_t;
typedef struct {
- union {
- struct {
- StatValues_t mseg;
- StatValues_t sys_alloc;
- } norm;
- } curr;
- StatValues_t max;
- StatValues_t max_ever;
- struct {
- StatValues_t curr;
- StatValues_t max;
- StatValues_t max_ever;
- } blocks;
+ StatValues_t curr;
+ StatValues_t max;
+ StatValues_t max_ever;
+} BlockStats_t;
+
+enum {
+ ERTS_CRR_ALLOC_MIN = 0,
+
+ ERTS_CRR_ALLOC_MSEG = ERTS_CRR_ALLOC_MIN,
+ ERTS_CRR_ALLOC_SYS = 1,
+
+ ERTS_CRR_ALLOC_MAX,
+ ERTS_CRR_ALLOC_COUNT = ERTS_CRR_ALLOC_MAX + 1
+};
+
+typedef struct {
+ StatValues_t carriers[ERTS_CRR_ALLOC_COUNT];
+
+ StatValues_t max;
+ StatValues_t max_ever;
+
+ BlockStats_t blocks[ERTS_ALC_A_COUNT];
} CarriersStats_t;
#ifdef USE_LTTNG_VM_TRACEPOINTS
-#define LTTNG_CARRIER_STATS_TO_LTTNG_STATS(CSP, LSP) \
- do { \
- (LSP)->carriers.size = (CSP)->curr.norm.mseg.size \
- + (CSP)->curr.norm.sys_alloc.size; \
- (LSP)->carriers.no = (CSP)->curr.norm.mseg.no \
- + (CSP)->curr.norm.sys_alloc.no; \
- (LSP)->blocks.size = (CSP)->blocks.curr.size; \
- (LSP)->blocks.no = (CSP)->blocks.curr.no; \
+#define LTTNG_CARRIER_STATS_TO_LTTNG_STATS(CSP, LSP) \
+ do { \
+ UWord no_sum__, size_sum__; \
+ int alloc_no__, i__; \
+ /* Carrier counters */ \
+ no_sum__ = size_sum__ = 0; \
+ for (i__ = ERTS_CRR_ALLOC_MIN; i__ <= ERTS_CRR_ALLOC_MAX; i__++) { \
+ StatValues_t *curr__ = &((CSP)->carriers[i__]); \
+ no_sum__ += curr__->no; \
+ size_sum__ += curr__->size; \
+ } \
+ (LSP)->carriers.size = size_sum__; \
+ (LSP)->carriers.no = no_sum__; \
+ /* Block counters */ \
+ no_sum__ = size_sum__ = 0; \
+ for (alloc_no__ = ERTS_ALC_A_MIN; \
+ alloc_no__ <= ERTS_ALC_A_MAX; \
+ alloc_no__++) { \
+ StatValues_t *curr__; \
+ i__ = alloc_no__ - ERTS_ALC_A_MIN; \
+ curr__ = &((CSP)->blocks[i__].curr); \
+ no_sum__ += curr__->no; \
+ size_sum__ += curr__->size; \
+ } \
+ (LSP)->blocks.size = size_sum__; \
+ (LSP)->blocks.no = no_sum__; \
} while (0)
#endif
@@ -653,9 +702,10 @@ struct Allctr_t_ {
UWord util_limit; /* acul */
UWord in_pool_limit; /* acnl */
UWord fblk_min_limit; /* acmfl */
+ int carrier_pool; /* cp */
struct {
- erts_atomic_t blocks_size[ERTS_ALC_A_MAX + 1];
- erts_atomic_t no_blocks[ERTS_ALC_A_MAX + 1];
+ erts_atomic_t blocks_size[ERTS_ALC_A_COUNT];
+ erts_atomic_t no_blocks[ERTS_ALC_A_COUNT];
erts_atomic_t carriers_size;
erts_atomic_t no_carriers;
CallCounter_t fail_pooled;
diff --git a/erts/emulator/beam/erl_async.c b/erts/emulator/beam/erl_async.c
index 44655ad5df..ec116cb9f5 100644
--- a/erts/emulator/beam/erl_async.c
+++ b/erts/emulator/beam/erl_async.c
@@ -155,7 +155,7 @@ erts_init_async(void)
if (erts_async_max_threads > 0) {
ErtsThrQInit_t qinit = ERTS_THR_Q_INIT_DEFAULT;
erts_thr_opts_t thr_opts = ERTS_THR_OPTS_DEFAULT_INITER;
- char *ptr, thr_name[16];
+ char *ptr, thr_name[32];
size_t tot_size = 0;
int i;
@@ -209,7 +209,7 @@ erts_init_async(void)
for (i = 0; i < erts_async_max_threads; i++) {
ErtsAsyncQ *aq = async_q(i);
- erts_snprintf(thr_opts.name, 16, "async_%d", i+1);
+ erts_snprintf(thr_opts.name, sizeof(thr_name), "async_%d", i+1);
erts_thr_create(&aq->thr_id, async_main, (void*) aq, &thr_opts);
}
@@ -254,25 +254,6 @@ static ERTS_INLINE void async_add(ErtsAsync *a, ErtsAsyncQ* q)
#endif
erts_thr_q_enqueue(&q->thr_q, a);
-#ifdef USE_LTTNG_VM_TRACEPOINTS
- if (LTTNG_ENABLED(aio_pool_put)) {
- lttng_decl_portbuf(port_str);
- lttng_portid_to_str(a->port, port_str);
- LTTNG2(aio_pool_put, port_str, -1);
- }
-#endif
-#ifdef USE_VM_PROBES
- if (DTRACE_ENABLED(aio_pool_add)) {
- DTRACE_CHARBUF(port_str, 16);
-
- erts_snprintf(port_str, sizeof(DTRACE_CHARBUF_NAME(port_str)),
- "%T", a->port);
- /* DTRACE TODO: Get the queue length from erts_thr_q_enqueue() ? */
- len = -1;
- DTRACE2(aio_pool_add, port_str, len);
- }
- gcc_optimizer_hack++;
-#endif
}
static ERTS_INLINE ErtsAsync *async_get(ErtsThrQ_t *q,
@@ -293,31 +274,13 @@ static ERTS_INLINE ErtsAsync *async_get(ErtsThrQ_t *q,
erts_thr_q_get_finalize_dequeue_data(q, &a->q.fin_deq);
if (saved_fin_deq)
erts_thr_q_append_finalize_dequeue_data(&a->q.fin_deq, &fin_deq);
-#ifdef USE_LTTNG_VM_TRACEPOINTS
- if (LTTNG_ENABLED(aio_pool_get)) {
- lttng_decl_portbuf(port_str);
- int length = erts_thr_q_length_dirty(q);
- lttng_portid_to_str(a->port, port_str);
- LTTNG2(aio_pool_get, port_str, length);
- }
-#endif
-#ifdef USE_VM_PROBES
- if (DTRACE_ENABLED(aio_pool_get)) {
- DTRACE_CHARBUF(port_str, 16);
-
- erts_snprintf(port_str, sizeof(DTRACE_CHARBUF_NAME(port_str)),
- "%T", a->port);
- /* DTRACE TODO: Get the length from erts_thr_q_dequeue() ? */
- len = -1;
- DTRACE2(aio_pool_get, port_str, len);
- }
-#endif
return a;
}
if (ERTS_THR_Q_DIRTY != erts_thr_q_clean(q)) {
ErtsThrQFinDeQ_t tmp_fin_deq;
+ erts_tse_use(tse);
erts_tse_reset(tse);
chk_fin_deq:
@@ -362,6 +325,7 @@ static ERTS_INLINE ErtsAsync *async_get(ErtsThrQ_t *q,
break;
}
+ erts_tse_return(tse);
}
}
}
@@ -429,9 +393,10 @@ static erts_tse_t *async_thread_init(ErtsAsyncQ *aq)
ErtsThrQInit_t qinit = ERTS_THR_Q_INIT_DEFAULT;
erts_tse_t *tse = erts_tse_fetch();
ERTS_DECLARE_DUMMY(Uint no);
-
ErtsThrPrgrCallbacks callbacks;
+ erts_tse_return(tse);
+
callbacks.arg = (void *) tse;
callbacks.wakeup = async_wakeup;
callbacks.prepare_wait = NULL;
diff --git a/erts/emulator/beam/erl_bif_binary.c b/erts/emulator/beam/erl_bif_binary.c
index 7c2980a101..98b38f156a 100644
--- a/erts/emulator/beam/erl_bif_binary.c
+++ b/erts/emulator/beam/erl_bif_binary.c
@@ -2413,7 +2413,7 @@ HIPE_WRAPPER_BIF_DISABLE_GC(binary_list_to_bin, 1)
BIF_RETTYPE binary_list_to_bin_1(BIF_ALIST_1)
{
- return erts_list_to_binary_bif(BIF_P, BIF_ARG_1, bif_export[BIF_binary_list_to_bin_1]);
+ return erts_list_to_binary_bif(BIF_P, BIF_ARG_1, &bif_trap_export[BIF_binary_list_to_bin_1]);
}
typedef struct {
diff --git a/erts/emulator/beam/erl_bif_chksum.c b/erts/emulator/beam/erl_bif_chksum.c
index cce8472ccb..4cab9bec1d 100644
--- a/erts/emulator/beam/erl_bif_chksum.c
+++ b/erts/emulator/beam/erl_bif_chksum.c
@@ -327,7 +327,7 @@ crc32_1(BIF_ALIST_1)
res_sum = erts_make_integer(chksum,BIF_P);
if (rest != NIL) {
BUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_crc32_2], BIF_P, res_sum, rest);
+ BIF_TRAP2(&bif_trap_export[BIF_crc32_2], BIF_P, res_sum, rest);
}
BIF_RET(res_sum);
}
@@ -354,7 +354,7 @@ crc32_2(BIF_ALIST_2)
res_sum = erts_make_integer(chksum,BIF_P);
if (rest != NIL) {
BUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_crc32_2], BIF_P, res_sum, rest);
+ BIF_TRAP2(&bif_trap_export[BIF_crc32_2], BIF_P, res_sum, rest);
}
BIF_RET(res_sum);
}
@@ -407,7 +407,7 @@ adler32_1(BIF_ALIST_1)
res_sum = erts_make_integer(chksum,BIF_P);
if (rest != NIL) {
BUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_adler32_2], BIF_P, res_sum, rest);
+ BIF_TRAP2(&bif_trap_export[BIF_adler32_2], BIF_P, res_sum, rest);
}
BIF_RET(res_sum);
}
@@ -434,7 +434,7 @@ adler32_2(BIF_ALIST_2)
res_sum = erts_make_integer(chksum,BIF_P);
if (rest != NIL) {
BUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_adler32_2], BIF_P, res_sum, rest);
+ BIF_TRAP2(&bif_trap_export[BIF_adler32_2], BIF_P, res_sum, rest);
}
BIF_RET(res_sum);
}
@@ -575,7 +575,7 @@ md5_update_2(BIF_ALIST_2)
bin = new_binary(BIF_P, (byte *) &context, sizeof(MD5_CTX));
if (rest != NIL) {
BUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_md5_update_2], BIF_P, bin, rest);
+ BIF_TRAP2(&bif_trap_export[BIF_md5_update_2], BIF_P, bin, rest);
}
BUMP_REDS(BIF_P,res);
BIF_RET(bin);
diff --git a/erts/emulator/beam/erl_bif_guard.c b/erts/emulator/beam/erl_bif_guard.c
index ab3b825f5c..7526888106 100644
--- a/erts/emulator/beam/erl_bif_guard.c
+++ b/erts/emulator/beam/erl_bif_guard.c
@@ -239,7 +239,7 @@ static BIF_RETTYPE erlang_length_trap(BIF_ALIST_3)
* Signal an error. The original argument was tucked away in BIF_ARG_3.
*/
ERTS_BIF_ERROR_TRAPPED1(BIF_P, BIF_P->freason,
- bif_export[BIF_length_1], BIF_ARG_3);
+ &bif_trap_export[BIF_length_1], BIF_ARG_3);
}
}
}
diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c
index ee80432e87..1086fdd057 100644
--- a/erts/emulator/beam/erl_bif_info.c
+++ b/erts/emulator/beam/erl_bif_info.c
@@ -55,14 +55,18 @@
#ifdef HIPE
#include "hipe_arch.h"
#endif
+#include "erl_global_literals.h"
#ifdef ERTS_ENABLE_LOCK_COUNT
#include "erl_lock_count.h"
#endif
#ifdef VALGRIND
-#include <valgrind/valgrind.h>
-#include <valgrind/memcheck.h>
+# include <valgrind/valgrind.h>
+# include <valgrind/memcheck.h>
+#endif
+#ifdef ADDRESS_SANITIZER
+# include <sanitizer/lsan_interface.h>
#endif
static Export* alloc_info_trap = NULL;
@@ -126,6 +130,9 @@ static char erts_system_version[] = ("Erlang/OTP " ERLANG_OTP_RELEASE
#ifdef VALGRIND
" [valgrind-compiled]"
#endif
+#ifdef ADDRESS_SANITIZER
+ " [address-sanitizer]"
+#endif
#ifdef ERTS_FRMPTR
" [frame-pointer]"
#endif
@@ -151,15 +158,12 @@ static char erts_system_version[] = ("Erlang/OTP " ERLANG_OTP_RELEASE
# define PERFMON_GETPCR _IOR('P', 2, unsigned long long)
#endif
-/* Cached, pre-built {OsType,OsFlavor} and {Major,Minor,Build} tuples */
-static Eterm os_type_tuple;
-static Eterm os_version_tuple;
-
static Eterm
current_function(Process* p, ErtsHeapFactory *hfact, Process* rp,
int full_info, Uint reserve_size, int flags);
-static Eterm current_stacktrace(ErtsHeapFactory *hfact, Process* rp,
- Uint reserve_size);
+static Eterm
+current_stacktrace(Process* p, ErtsHeapFactory *hfact, Process* rp,
+ Uint reserve_size, int flags);
Eterm
erts_bld_bin_list(Uint **hpp, Uint *szp, ErlOffHeap* oh, Eterm tail)
@@ -353,6 +357,7 @@ make_monitor_list(Process *p, int tree, ErtsMonitor *root, Eterm tail)
-record(erl_link, {
type, % process | port | dist_process
pid, % Process or port
+ state, % linked | unlinking
id % (address)
}).
*/
@@ -361,13 +366,18 @@ static int calc_lnk_size(ErtsLink *lnk, void *vpsz, Sint reds)
{
Uint *psz = vpsz;
Uint sz = 0;
- ErtsLinkData *ldp = erts_link_to_data(lnk);
+ UWord addr;
+
+ if (lnk->type == ERTS_LNK_TYPE_DIST_PROC)
+ addr = (UWord) erts_link_to_elink(lnk);
+ else
+ addr = (UWord) lnk;
- (void) erts_bld_uword(NULL, &sz, (UWord) ldp);
+ (void) erts_bld_uword(NULL, &sz, (UWord) addr);
*psz += sz;
*psz += is_immed(lnk->other.item) ? 0 : size_object(lnk->other.item);
- *psz += 7; /* CONS + 4-tuple */
+ *psz += 8; /* CONS + 5-tuple */
return 1;
}
@@ -381,10 +391,23 @@ typedef struct {
static int make_one_lnk_element(ErtsLink *lnk, void * vpllc, Sint reds)
{
LnkListContext *pllc = vpllc;
- Eterm tup, t, pid, id;
- ErtsLinkData *ldp = erts_link_to_data(lnk);
+ Eterm tup, t, pid, id, state;
+ UWord addr;
+ ERTS_DECL_AM(linked);
+ ERTS_DECL_AM(unlinking);
+
+ if (lnk->type == ERTS_LNK_TYPE_DIST_PROC) {
+ ErtsELink *elnk = erts_link_to_elink(lnk);
+ state = elnk->unlinking ? AM_unlinking : AM_linked;
+ addr = (UWord) elnk;
+ }
+ else {
+ ErtsILink *ilnk = (ErtsILink *) lnk;
+ state = ilnk->unlinking ? AM_unlinking : AM_linked;
+ addr = (UWord) ilnk;
+ }
- id = erts_bld_uword(&pllc->hp, NULL, (UWord) ldp);
+ id = erts_bld_uword(&pllc->hp, NULL, (UWord) addr);
if (is_immed(lnk->other.item))
pid = lnk->other.item;
@@ -411,8 +434,8 @@ static int make_one_lnk_element(ErtsLink *lnk, void * vpllc, Sint reds)
break;
}
- tup = TUPLE4(pllc->hp, pllc->tag, t, pid, id);
- pllc->hp += 5;
+ tup = TUPLE5(pllc->hp, pllc->tag, t, pid, state, id);
+ pllc->hp += 6;
pllc->res = CONS(pllc->hp, tup, pllc->res);
pllc->hp += 2;
return 1;
@@ -527,6 +550,15 @@ do { \
static int collect_one_link(ErtsLink *lnk, void *vmicp, Sint reds)
{
MonitorInfoCollection *micp = vmicp;
+ if (lnk->type != ERTS_LNK_TYPE_DIST_PROC) {
+ if (((ErtsILink *) lnk)->unlinking)
+ return 1;
+ }
+ else {
+ ErtsELink *elnk = erts_link_to_elink(lnk);
+ if (elnk->unlinking)
+ return 1;
+ }
EXTEND_MONITOR_INFOS(micp);
micp->mi[micp->mi_i].entity.term = lnk->other.item;
micp->sz += 2 + NC_HEAP_SIZE(lnk->other.item);
@@ -548,6 +580,8 @@ static int collect_one_origin_monitor(ErtsMonitor *mon, void *vmicp, Sint reds)
case ERTS_MON_TYPE_PORT:
case ERTS_MON_TYPE_DIST_PROC:
case ERTS_MON_TYPE_TIME_OFFSET:
+ if (mon->flags & ERTS_ML_FLG_SPAWN_PENDING)
+ break; /* Not an active monitor... */
if (!(mon->flags & ERTS_ML_FLG_NAME)) {
micp->mi[micp->mi_i].named = 0;
micp->mi[micp->mi_i].entity.term = mon->other.item;
@@ -1249,9 +1283,9 @@ exited:
yield:
if (pi2)
- ERTS_BIF_PREP_YIELD2(ret, bif_export[BIF_process_info_2], c_p, pid, opt);
+ ERTS_BIF_PREP_YIELD2(ret, &bif_trap_export[BIF_process_info_2], c_p, pid, opt);
else
- ERTS_BIF_PREP_YIELD1(ret, bif_export[BIF_process_info_1], c_p, pid);
+ ERTS_BIF_PREP_YIELD1(ret, &bif_trap_export[BIF_process_info_1], c_p, pid);
goto done;
send_signal: {
@@ -1384,7 +1418,7 @@ process_info_aux(Process *c_p,
break;
case ERTS_PI_IX_CURRENT_STACKTRACE:
- res = current_stacktrace(hfact, rp, reserve_size);
+ res = current_stacktrace(c_p, hfact, rp, reserve_size, flags);
break;
case ERTS_PI_IX_INITIAL_CALL:
@@ -2022,19 +2056,23 @@ current_function(Process *c_p, ErtsHeapFactory *hfact, Process* rp,
}
if (c_p == rp && !(flags & ERTS_PI_FLAG_REQUEST_FOR_OTHER)) {
- FunctionInfo fi2;
+ BeamInstr* return_address;
+ FunctionInfo caller_fi;
- /*
- * The current function is erlang:process_info/{1,2},
- * which is not the answer that the application want.
- * We will use the function pointed into by rp->cp
- * instead if it can be looked up.
- */
- erts_lookup_function_info(&fi2, rp->cp, full_info);
- if (fi2.mfa) {
- fi = fi2;
- rp->current = fi2.mfa;
- }
+ /*
+ * The current function is erlang:process_info/{1,2}, and we've
+ * historically returned the *calling* function in that case. We
+ * therefore use the continuation pointer stored at the top of the
+ * stack instead, which is safe since process_info is a "heavy" BIF
+ * that is only called through its export entry.
+ */
+ return_address = erts_printable_return_address(rp, STACK_TOP(rp));
+
+ erts_lookup_function_info(&caller_fi, return_address, full_info);
+ if (caller_fi.mfa) {
+ fi = caller_fi;
+ rp->current = caller_fi.mfa;
+ }
}
/*
@@ -2055,8 +2093,8 @@ current_function(Process *c_p, ErtsHeapFactory *hfact, Process* rp,
}
static Eterm
-current_stacktrace(ErtsHeapFactory *hfact, Process* rp,
- Uint reserve_size)
+current_stacktrace(Process *p, ErtsHeapFactory *hfact, Process* rp,
+ Uint reserve_size, int flags)
{
Uint sz;
struct StackTrace* s;
@@ -2073,13 +2111,14 @@ current_stacktrace(ErtsHeapFactory *hfact, Process* rp,
sz = offsetof(struct StackTrace, trace) + sizeof(BeamInstr *)*depth;
s = (struct StackTrace *) erts_alloc(ERTS_ALC_T_TMP, sz);
s->depth = 0;
- if (depth > 0 && rp->i) {
- s->trace[s->depth++] = rp->i;
- depth--;
- }
- if (depth > 0 && rp->cp != 0) {
- s->trace[s->depth++] = rp->cp - 1;
- depth--;
+ s->pc = NULL;
+
+ /* We skip current pc when requesting our own stack trace since it will
+ * inevitably point to process_info/1,2 */
+ if ((p != rp || (flags & ERTS_PI_FLAG_REQUEST_FOR_OTHER)) &&
+ depth > 0 && rp->i) {
+ s->trace[s->depth++] = rp->i;
+ depth--;
}
erts_save_stacktrace(rp, s, depth);
@@ -2115,6 +2154,28 @@ current_stacktrace(ErtsHeapFactory *hfact, Process* rp,
return res;
}
+#if defined(VALGRIND) || defined(ADDRESS_SANITIZER)
+static int iolist_to_tmp_buf(Eterm iolist, char** bufp)
+{
+ ErlDrvSizeT buf_size = 1024; /* Try with 1KB first */
+ char *buf = erts_alloc(ERTS_ALC_T_TMP, buf_size);
+ ErlDrvSizeT r = erts_iolist_to_buf(iolist, (char*) buf, buf_size - 1);
+ if (ERTS_IOLIST_TO_BUF_FAILED(r)) {
+ erts_free(ERTS_ALC_T_TMP, (void *) buf);
+ if (erts_iolist_size(iolist, &buf_size)) {
+ return 0;
+ }
+ buf_size++;
+ buf = erts_alloc(ERTS_ALC_T_TMP, buf_size);
+ r = erts_iolist_to_buf(iolist, (char*) buf, buf_size - 1);
+ ASSERT(r == buf_size - 1);
+ }
+ buf[buf_size - 1 - r] = '\0';
+ *bufp = buf;
+ return 1;
+}
+#endif
+
/*
* This function takes care of calls to erlang:system_info/1 when the argument
* is a tuple.
@@ -2177,59 +2238,45 @@ info_1_tuple(Process* BIF_P, /* Pointer to current process. */
goto badarg;
ERTS_BIF_PREP_TRAP1(ret, erts_format_cpu_topology_trap, BIF_P, res);
return ret;
-#if defined(PURIFY) || defined(VALGRIND)
- } else if (ERTS_IS_ATOM_STR("error_checker", sel)
-#if defined(PURIFY)
- || sel == am_purify
-#elif defined(VALGRIND)
- || ERTS_IS_ATOM_STR("valgrind", sel)
+ } else if (ERTS_IS_ATOM_STR("memory_checker", sel)) {
+ if (arity == 2 && ERTS_IS_ATOM_STR("test_leak", *tp)) {
+#if defined(VALGRIND) || defined(ADDRESS_SANITIZER)
+ erts_alloc(ERTS_ALC_T_HEAP , 100);
#endif
- ) {
- if (*tp == am_memory) {
-#if defined(PURIFY)
- BIF_RET(erts_make_integer(purify_new_leaks(), BIF_P));
-#elif defined(VALGRIND)
-# ifdef VALGRIND_DO_ADDED_LEAK_CHECK
+ BIF_RET(am_ok);
+ }
+ else if (arity == 2 && ERTS_IS_ATOM_STR("test_overflow", *tp)) {
+ static int test[2];
+ BIF_RET(make_small(test[2]));
+ }
+#if defined(VALGRIND) || defined(ADDRESS_SANITIZER)
+ if (arity == 2 && *tp == am_running) {
+# if defined(VALGRIND)
+ if (RUNNING_ON_VALGRIND)
+ BIF_RET(ERTS_MAKE_AM("valgrind"));
+# elif defined(ADDRESS_SANITIZER)
+ BIF_RET(ERTS_MAKE_AM("asan"));
+# endif
+ }
+ else if (arity == 2 && ERTS_IS_ATOM_STR("check_leaks", *tp)) {
+# if defined(VALGRIND)
+# ifdef VALGRIND_DO_ADDED_LEAK_CHECK
VALGRIND_DO_ADDED_LEAK_CHECK;
-# else
+# else
VALGRIND_DO_LEAK_CHECK;
+# endif
+ BIF_RET(am_ok);
+# elif defined(ADDRESS_SANITIZER)
+ __lsan_do_recoverable_leak_check();
+ BIF_RET(am_ok);
# endif
- BIF_RET(make_small(0));
-#endif
- } else if (*tp == am_fd) {
-#if defined(PURIFY)
- BIF_RET(erts_make_integer(purify_new_fds_inuse(), BIF_P));
-#elif defined(VALGRIND)
- /* Not present in valgrind... */
- BIF_RET(make_small(0));
-#endif
- } else if (*tp == am_running) {
-#if defined(PURIFY)
- BIF_RET(purify_is_running() ? am_true : am_false);
-#elif defined(VALGRIND)
- BIF_RET(RUNNING_ON_VALGRIND ? am_true : am_false);
-#endif
- } else if (is_list(*tp)) {
-#if defined(PURIFY)
-# define ERTS_ERROR_CHECKER_PRINTF purify_printf
-#elif defined(VALGRIND)
-# define ERTS_ERROR_CHECKER_PRINTF VALGRIND_PRINTF
-#endif
- ErlDrvSizeT buf_size = 8*1024; /* Try with 8KB first */
- char *buf = erts_alloc(ERTS_ALC_T_TMP, buf_size);
- ErlDrvSizeT r = erts_iolist_to_buf(*tp, (char*) buf, buf_size - 1);
- if (ERTS_IOLIST_TO_BUF_FAILED(r)) {
- erts_free(ERTS_ALC_T_TMP, (void *) buf);
- if (erts_iolist_size(*tp, &buf_size)) {
- goto badarg;
- }
- buf_size++;
- buf = erts_alloc(ERTS_ALC_T_TMP, buf_size);
- r = erts_iolist_to_buf(*tp, (char*) buf, buf_size - 1);
- ASSERT(r == buf_size - 1);
- }
- buf[buf_size - 1 - r] = '\0';
- ERTS_ERROR_CHECKER_PRINTF("%s\n", buf);
+ }
+# if defined(VALGRIND)
+ if (arity == 3 && tp[0] == am_print && is_list(tp[1])) {
+ char* buf;
+ if (!iolist_to_tmp_buf(tp[1], &buf))
+ goto badarg;
+ VALGRIND_PRINTF("%s\n", buf);
erts_free(ERTS_ALC_T_TMP, (void *) buf);
BIF_RET(am_true);
#undef ERTS_ERROR_CHECKER_PRINTF
@@ -2249,6 +2296,30 @@ info_1_tuple(Process* BIF_P, /* Pointer to current process. */
} else if (*tp == am_running) {
BIF_RET(quantify_is_running() ? am_true : am_false);
}
+# endif
+# if defined(ADDRESS_SANITIZER)
+ if (arity == 3 && ERTS_IS_ATOM_STR("log",tp[0]) && is_list(tp[1])) {
+ static char *active_log = NULL;
+ static int active_log_len;
+ Eterm ret = NIL;
+ char* buf;
+ if (!iolist_to_tmp_buf(tp[1], &buf))
+ goto badarg;
+ erts_rwmtx_rwlock(&erts_dist_table_rwmtx); /* random lock abuse */
+ __sanitizer_set_report_path(buf);
+ if (active_log) {
+ Eterm *hp = HAlloc(BIF_P, 2 * active_log_len);
+ ret = erts_bld_string_n(&hp, 0, active_log, active_log_len);
+ erts_free(ERTS_ALC_T_DEBUG, active_log);
+ }
+ active_log_len = sys_strlen(buf);
+ active_log = erts_alloc(ERTS_ALC_T_DEBUG, active_log_len + 1);
+ sys_memcpy(active_log, buf, active_log_len + 1);
+ erts_rwmtx_rwunlock(&erts_dist_table_rwmtx);
+ erts_free(ERTS_ALC_T_TMP, (void *) buf);
+ BIF_RET(ret);
+ }
+# endif
#endif
#if defined(__GNUC__) && defined(HAVE_SOLARIS_SPARC_PERFMON)
} else if (ERTS_IS_ATOM_STR("ultrasparc_set_pcr", sel)) {
@@ -2454,6 +2525,9 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
#elif defined(VALGRIND)
ERTS_DECL_AM(valgrind);
BIF_RET(AM_valgrind);
+#elif defined(ADDRESS_SANITIZER)
+ ERTS_DECL_AM(asan);
+ BIF_RET(AM_asan);
#elif defined(GPROF)
ERTS_DECL_AM(gprof);
BIF_RET(AM_gprof);
@@ -2657,7 +2731,7 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
NIL));
}
else if (BIF_ARG_1 == am_os_type) {
- BIF_RET(os_type_tuple);
+ BIF_RET(erts_get_global_literal(ERTS_LIT_OS_TYPE));
}
else if (BIF_ARG_1 == am_allocator) {
BIF_RET(erts_allocator_options((void *) BIF_P));
@@ -2677,7 +2751,7 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
BIF_RET(am_false);
}
else if (BIF_ARG_1 == am_os_version) {
- BIF_RET(os_version_tuple);
+ BIF_RET(erts_get_global_literal(ERTS_LIT_OS_VERSION));
}
else if (BIF_ARG_1 == am_version) {
int n = sys_strlen(ERLANG_VERSION);
@@ -2814,7 +2888,10 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
} else if (BIF_ARG_1 == am_threads) {
return am_true;
} else if (BIF_ARG_1 == am_creation) {
- return make_small(erts_this_node->creation);
+ Uint hsz = 0;
+ erts_bld_uint(NULL, &hsz, erts_this_node->creation);
+ hp = hsz ? HAlloc(BIF_P, hsz) : NULL;
+ BIF_RET(erts_bld_uint(&hp, NULL, erts_this_node->creation));
} else if (BIF_ARG_1 == am_break_ignored) {
extern int ignore_break;
if (ignore_break)
@@ -2825,7 +2902,7 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
/* Arguments that are unusual follow ... */
else if (ERTS_IS_ATOM_STR("logical_processors", BIF_ARG_1)) {
int no;
- erts_get_logical_processors(&no, NULL, NULL);
+ erts_get_logical_processors(&no, NULL, NULL, NULL);
if (no > 0)
BIF_RET(make_small((Uint) no));
else {
@@ -2835,7 +2912,7 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
}
else if (ERTS_IS_ATOM_STR("logical_processors_online", BIF_ARG_1)) {
int no;
- erts_get_logical_processors(NULL, &no, NULL);
+ erts_get_logical_processors(NULL, &no, NULL, NULL);
if (no > 0)
BIF_RET(make_small((Uint) no));
else {
@@ -2845,7 +2922,17 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
}
else if (ERTS_IS_ATOM_STR("logical_processors_available", BIF_ARG_1)) {
int no;
- erts_get_logical_processors(NULL, NULL, &no);
+ erts_get_logical_processors(NULL, NULL, &no, NULL);
+ if (no > 0)
+ BIF_RET(make_small((Uint) no));
+ else {
+ DECL_AM(unknown);
+ BIF_RET(AM_unknown);
+ }
+ }
+ else if (ERTS_IS_ATOM_STR("cpu_quota", BIF_ARG_1)) {
+ int no;
+ erts_get_logical_processors(NULL, NULL, NULL, &no);
if (no > 0)
BIF_RET(make_small((Uint) no));
else {
@@ -3061,6 +3148,14 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
} else if (ERTS_IS_ATOM_STR("ethread_info", BIF_ARG_1)) {
BIF_RET(erts_get_ethread_info(BIF_P));
}
+ else if (ERTS_IS_ATOM_STR("ethread_used_tse", BIF_ARG_1)) {
+ Uint64 no = (Uint64) ethr_no_used_tse();
+ Uint hsz = 0;
+ erts_bld_uint64(NULL, &hsz, no);
+ hp = hsz ? HAlloc(BIF_P, hsz) : NULL;
+ res = erts_bld_uint64(&hp, NULL, no);
+ BIF_RET(res);
+ }
else if (ERTS_IS_ATOM_STR("emu_args", BIF_ARG_1)) {
BIF_RET(erts_get_emu_args(BIF_P));
}
@@ -4043,7 +4138,16 @@ BIF_RETTYPE erts_debug_get_internal_state_1(BIF_ALIST_1)
BIF_RET(am_notsup);
#endif
}
-
+ else if (ERTS_IS_ATOM_STR("flxctr_memory_usage", BIF_ARG_1)) {
+ Sint mem = erts_flxctr_debug_memory_usage();
+ if (mem == -1) {
+ BIF_RET(am_notsup);
+ } else {
+ Uint hsz = BIG_UWORD_HEAP_SIZE((UWord)mem);
+ Eterm *hp = HAlloc(BIF_P, hsz);
+ BIF_RET(uword_to_big((UWord)mem, hp));
+ }
+ }
}
else if (is_tuple(BIF_ARG_1)) {
Eterm* tp = tuple_val(BIF_ARG_1);
@@ -4256,9 +4360,9 @@ BIF_RETTYPE erts_debug_get_internal_state_1(BIF_ALIST_1)
}
}
else if (ERTS_IS_ATOM_STR("term_to_binary_tuple_fallbacks", tp[1])) {
- Uint dflags = (TERM_TO_BINARY_DFLAGS
- & ~DFLAG_EXPORT_PTR_TAG
- & ~DFLAG_BIT_BINARIES);
+ Uint64 dflags = (TERM_TO_BINARY_DFLAGS
+ & ~DFLAG_EXPORT_PTR_TAG
+ & ~DFLAG_BIT_BINARIES);
Eterm res = erts_term_to_binary(BIF_P, tp[2], 0, dflags);
if (is_value(res))
BIF_RET(res);
@@ -4376,6 +4480,9 @@ BIF_RETTYPE erts_debug_get_internal_state_1(BIF_ALIST_1)
break;
BIF_RET(res);
}
+ else if (ERTS_IS_ATOM_STR("term_to_binary", tp[1])) {
+ return erts_debug_term_to_binary(BIF_P, tp[2], tp[3]);
+ }
break;
}
default:
@@ -4431,6 +4538,28 @@ static void broken_halt_test(Eterm bif_arg_2)
erts_exit(ERTS_DUMP_EXIT, "%T", bif_arg_2);
}
+static void
+test_multizero_timeout_in_timeout3(void *vproc)
+{
+ Process *proc = (Process *) vproc;
+ ErtsMessage *mp = erts_alloc_message(0, NULL);
+ ERTS_DECL_AM(multizero_timeout_in_timeout_done);
+ erts_queue_message(proc, 0, mp, AM_multizero_timeout_in_timeout_done, am_system);
+ erts_proc_dec_refc(proc);
+}
+
+static void
+test_multizero_timeout_in_timeout2(void *vproc)
+{
+ erts_start_timer_callback(0, test_multizero_timeout_in_timeout3, vproc);
+}
+
+static void
+test_multizero_timeout_in_timeout(void *vproc)
+{
+ erts_start_timer_callback(0, test_multizero_timeout_in_timeout2, vproc);
+}
+
BIF_RETTYPE erts_debug_set_internal_state_2(BIF_ALIST_2)
{
/*
@@ -4629,7 +4758,7 @@ BIF_RETTYPE erts_debug_set_internal_state_2(BIF_ALIST_2)
if (!flag && BIF_ARG_2 != am_false) {
erts_atomic_set_nob(&hipe_test_reschedule_flag, 1);
erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL);
- ERTS_BIF_YIELD2(bif_export[BIF_erts_debug_set_internal_state_2],
+ ERTS_BIF_YIELD2(&bif_trap_export[BIF_erts_debug_set_internal_state_2],
BIF_P, BIF_ARG_1, BIF_ARG_2);
}
erts_atomic_set_nob(&hipe_test_reschedule_flag, !flag);
@@ -4675,9 +4804,14 @@ BIF_RETTYPE erts_debug_set_internal_state_2(BIF_ALIST_2)
flag = ERTS_DEBUG_WAIT_COMPLETED_TIMER_CANCELLATIONS;
else if (ERTS_IS_ATOM_STR("aux_work", BIF_ARG_2))
flag = ERTS_DEBUG_WAIT_COMPLETED_AUX_WORK;
+ else if (ERTS_IS_ATOM_STR("thread_progress", BIF_ARG_2))
+ flag = ERTS_DEBUG_WAIT_COMPLETED_THREAD_PROGRESS;
- if (flag && erts_debug_wait_completed(BIF_P, flag)) {
- ERTS_BIF_YIELD_RETURN(BIF_P, am_ok);
+ if (flag) {
+ if (erts_debug_wait_completed(BIF_P, flag))
+ ERTS_BIF_YIELD_RETURN(BIF_P, am_ok);
+ else
+ BIF_ERROR(BIF_P, SYSTEM_LIMIT);
}
}
else if (ERTS_IS_ATOM_STR("broken_halt", BIF_ARG_1)) {
@@ -4756,7 +4890,6 @@ BIF_RETTYPE erts_debug_set_internal_state_2(BIF_ALIST_2)
else if (ERTS_IS_ATOM_STR("ets_debug_random_split_join", BIF_ARG_1)) {
if (is_tuple(BIF_ARG_2)) {
Eterm* tpl = tuple_val(BIF_ARG_2);
-
if (erts_ets_debug_random_split_join(tpl[1], tpl[2] == am_true))
BIF_RET(am_ok);
}
@@ -4771,11 +4904,254 @@ BIF_RETTYPE erts_debug_set_internal_state_2(BIF_ALIST_2)
BIF_P->mbuf_sz += sz;
BIF_RET(copy);
}
+ else if (ERTS_IS_ATOM_STR("remove_hopefull_dflags", BIF_ARG_1)) {
+ int old_val, new_val;
+
+ switch (BIF_ARG_2) {
+ case am_true: new_val = !0; break;
+ case am_false: new_val = 0; break;
+ default: BIF_ERROR(BIF_P, BADARG); break;
+ }
+
+ erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
+ erts_thr_progress_block();
+
+ old_val = erts_dflags_test_remove_hopefull_flags;
+ erts_dflags_test_remove_hopefull_flags = new_val;
+
+ erts_thr_progress_unblock();
+ erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
+
+ BIF_RET(old_val ? am_true : am_false);
+ }
+ else if (ERTS_IS_ATOM_STR("code_write_permission", BIF_ARG_1)) {
+ /*
+ * Warning: This is a unsafe way of seizing the "lock"
+ * as there is no automatic unlock if caller terminates.
+ */
+ switch(BIF_ARG_2) {
+ case am_true:
+ if (!erts_try_seize_code_write_permission(BIF_P)) {
+ ERTS_BIF_YIELD2(&bif_trap_export[BIF_erts_debug_set_internal_state_2],
+ BIF_P, BIF_ARG_1, BIF_ARG_2);
+ }
+ BIF_RET(am_true);
+ case am_false:
+ erts_release_code_write_permission();
+ BIF_RET(am_true);
+ }
+ }
+ else if (ERTS_IS_ATOM_STR("multizero_timeout_in_timeout", BIF_ARG_1)) {
+ Sint64 timeout;
+ if (term_to_Sint64(BIF_ARG_2, &timeout)) {
+ if (timeout < 0)
+ timeout = 0;
+ erts_proc_inc_refc(BIF_P);
+ erts_start_timer_callback((ErtsMonotonicTime) timeout,
+ test_multizero_timeout_in_timeout,
+ (void *) BIF_P);
+ BIF_RET(am_ok);
+ }
+ }
}
BIF_ERROR(BIF_P, BADARG);
}
+Eterm
+erts_get_ethread_info(Process *c_p)
+{
+ Uint sz, *szp;
+ Eterm res, *hp, **hpp, *end_hp = NULL;
+
+ sz = 0;
+ szp = &sz;
+ hpp = NULL;
+
+ while (1) {
+ Eterm tup, list, name;
+#if defined(ETHR_NATIVE_ATOMIC32_IMPL) \
+ || defined(ETHR_NATIVE_ATOMIC64_IMPL) \
+ || defined(ETHR_NATIVE_DW_ATOMIC_IMPL)
+ char buf[1024];
+ int i;
+ char **str;
+#endif
+
+ res = NIL;
+
+#ifdef ETHR_X86_MEMBAR_H__
+
+ tup = erts_bld_tuple(hpp, szp, 2,
+ erts_bld_string(hpp, szp, "sse2"),
+#ifdef ETHR_X86_RUNTIME_CONF_HAVE_SSE2__
+ erts_bld_string(hpp, szp,
+ (ETHR_X86_RUNTIME_CONF_HAVE_SSE2__
+ ? "yes" : "no"))
+#else
+ erts_bld_string(hpp, szp, "yes")
+#endif
+ );
+ res = erts_bld_cons(hpp, szp, tup, res);
+
+ tup = erts_bld_tuple(hpp, szp, 2,
+ erts_bld_string(hpp, szp,
+ "x86"
+#ifdef ARCH_64
+ "_64"
+#endif
+ " OOO"),
+ erts_bld_string(hpp, szp,
+#ifdef ETHR_X86_OUT_OF_ORDER
+ "yes"
+#else
+ "no"
+#endif
+ ));
+
+ res = erts_bld_cons(hpp, szp, tup, res);
+#endif
+
+#ifdef ETHR_SPARC_V9_MEMBAR_H__
+
+ tup = erts_bld_tuple(hpp, szp, 2,
+ erts_bld_string(hpp, szp, "Sparc V9"),
+ erts_bld_string(hpp, szp,
+#if defined(ETHR_SPARC_TSO)
+ "TSO"
+#elif defined(ETHR_SPARC_PSO)
+ "PSO"
+#elif defined(ETHR_SPARC_RMO)
+ "RMO"
+#else
+ "undefined"
+#endif
+ ));
+
+ res = erts_bld_cons(hpp, szp, tup, res);
+
+#endif
+
+#ifdef ETHR_PPC_MEMBAR_H__
+
+ tup = erts_bld_tuple(hpp, szp, 2,
+ erts_bld_string(hpp, szp, "lwsync"),
+ erts_bld_string(hpp, szp,
+#if defined(ETHR_PPC_HAVE_LWSYNC)
+ "yes"
+#elif defined(ETHR_PPC_HAVE_NO_LWSYNC)
+ "no"
+#elif defined(ETHR_PPC_RUNTIME_CONF_HAVE_LWSYNC__)
+ ETHR_PPC_RUNTIME_CONF_HAVE_LWSYNC__ ? "yes" : "no"
+#else
+ "undefined"
+#endif
+ ));
+
+ res = erts_bld_cons(hpp, szp, tup, res);
+
+#endif
+
+ tup = erts_bld_tuple(hpp, szp, 2,
+ erts_bld_string(hpp, szp, "Native rw-spinlocks"),
+#ifdef ETHR_NATIVE_RWSPINLOCK_IMPL
+ erts_bld_string(hpp, szp, ETHR_NATIVE_RWSPINLOCK_IMPL)
+#else
+ erts_bld_string(hpp, szp, "no")
+#endif
+ );
+ res = erts_bld_cons(hpp, szp, tup, res);
+
+ tup = erts_bld_tuple(hpp, szp, 2,
+ erts_bld_string(hpp, szp, "Native spinlocks"),
+#ifdef ETHR_NATIVE_SPINLOCK_IMPL
+ erts_bld_string(hpp, szp, ETHR_NATIVE_SPINLOCK_IMPL)
+#else
+ erts_bld_string(hpp, szp, "no")
+#endif
+ );
+ res = erts_bld_cons(hpp, szp, tup, res);
+
+
+ list = NIL;
+#ifdef ETHR_NATIVE_DW_ATOMIC_IMPL
+ if (ethr_have_native_dw_atomic()) {
+ name = erts_bld_string(hpp, szp, ETHR_NATIVE_DW_ATOMIC_IMPL);
+ str = ethr_native_dw_atomic_ops();
+ for (i = 0; str[i]; i++) {
+ erts_snprintf(buf, sizeof(buf), "ethr_native_dw_atomic_%s()", str[i]);
+ list = erts_bld_cons(hpp, szp,
+ erts_bld_string(hpp, szp, buf),
+ list);
+ }
+ str = ethr_native_su_dw_atomic_ops();
+ for (i = 0; str[i]; i++) {
+ erts_snprintf(buf, sizeof(buf), "ethr_native_su_dw_atomic_%s()", str[i]);
+ list = erts_bld_cons(hpp, szp,
+ erts_bld_string(hpp, szp, buf),
+ list);
+ }
+ }
+ else
+#endif
+ name = erts_bld_string(hpp, szp, "no");
+
+ tup = erts_bld_tuple(hpp, szp, 3,
+ erts_bld_string(hpp, szp, "Double word native atomics"),
+ name,
+ list);
+ res = erts_bld_cons(hpp, szp, tup, res);
+
+ list = NIL;
+#ifdef ETHR_NATIVE_ATOMIC64_IMPL
+ name = erts_bld_string(hpp, szp, ETHR_NATIVE_ATOMIC64_IMPL);
+ str = ethr_native_atomic64_ops();
+ for (i = 0; str[i]; i++) {
+ erts_snprintf(buf, sizeof(buf), "ethr_native_atomic64_%s()", str[i]);
+ list = erts_bld_cons(hpp, szp,
+ erts_bld_string(hpp, szp, buf),
+ list);
+ }
+#else
+ name = erts_bld_string(hpp, szp, "no");
+#endif
+ tup = erts_bld_tuple(hpp, szp, 3,
+ erts_bld_string(hpp, szp, "64-bit native atomics"),
+ name,
+ list);
+ res = erts_bld_cons(hpp, szp, tup, res);
+
+ list = NIL;
+#ifdef ETHR_NATIVE_ATOMIC32_IMPL
+ name = erts_bld_string(hpp, szp, ETHR_NATIVE_ATOMIC32_IMPL);
+ str = ethr_native_atomic32_ops();
+ for (i = 0; str[i]; i++) {
+ erts_snprintf(buf, sizeof(buf), "ethr_native_atomic32_%s()", str[i]);
+ list = erts_bld_cons(hpp, szp,
+ erts_bld_string(hpp, szp, buf),
+ list);
+ }
+#else
+ name = erts_bld_string(hpp, szp, "no");
+#endif
+ tup = erts_bld_tuple(hpp, szp, 3,
+ erts_bld_string(hpp, szp, "32-bit native atomics"),
+ name,
+ list);
+ res = erts_bld_cons(hpp, szp, tup, res);
+
+ if (hpp) {
+ HRelease(c_p, end_hp, *hpp)
+ return res;
+ }
+
+ hp = HAlloc(c_p, sz);
+ end_hp = hp + sz;
+ hpp = &hp;
+ szp = NULL;
+ }
+}
+
static BIF_RETTYPE
gather_histograms_helper(Process * c_p, Eterm arg_tuple,
int gather(Process *, int, int, int, UWord, Eterm))
@@ -5218,21 +5594,22 @@ static void os_info_init(void)
int major, minor, build;
char* buf = erts_alloc(ERTS_ALC_T_TMP, 1024); /* More than enough */
Eterm* hp;
+ Eterm tuple;
os_flavor(buf, 1024);
flav = erts_atom_put((byte *) buf, sys_strlen(buf), ERTS_ATOM_ENC_LATIN1, 1);
erts_free(ERTS_ALC_T_TMP, (void *) buf);
- hp = erts_alloc(ERTS_ALC_T_LITERAL, (3+4)*sizeof(Eterm));
- os_type_tuple = TUPLE2(hp, type, flav);
- erts_set_literal_tag(&os_type_tuple, hp, 3);
+ hp = erts_alloc_global_literal(ERTS_LIT_OS_TYPE, 3);
+ tuple = TUPLE2(hp, type, flav);
+ erts_register_global_literal(ERTS_LIT_OS_TYPE, tuple);
- hp += 3;
+ hp = erts_alloc_global_literal(ERTS_LIT_OS_VERSION, 4);
os_version(&major, &minor, &build);
- os_version_tuple = TUPLE3(hp,
- make_small(major),
- make_small(minor),
- make_small(build));
- erts_set_literal_tag(&os_version_tuple, hp, 4);
+ tuple = TUPLE3(hp,
+ make_small(major),
+ make_small(minor),
+ make_small(build));
+ erts_register_global_literal(ERTS_LIT_OS_VERSION, tuple);
}
void
diff --git a/erts/emulator/beam/erl_bif_lists.c b/erts/emulator/beam/erl_bif_lists.c
index e315e768d4..7cb9b4900b 100644
--- a/erts/emulator/beam/erl_bif_lists.c
+++ b/erts/emulator/beam/erl_bif_lists.c
@@ -32,8 +32,7 @@
#include "bif.h"
#include "erl_binary.h"
-
-static Eterm keyfind(int Bif, Process* p, Eterm Key, Eterm Pos, Eterm List);
+static Eterm keyfind(Export* Bif, Process* p, Eterm Key, Eterm Pos, Eterm List);
/* erlang:'++'/2
*
@@ -308,12 +307,12 @@ static Eterm append(Export *bif_entry, BIF_ALIST_2) {
Eterm
ebif_plusplus_2(BIF_ALIST_2)
{
- return append(bif_export[BIF_ebif_plusplus_2], BIF_CALL_ARGS);
+ return append(&bif_trap_export[BIF_ebif_plusplus_2], BIF_CALL_ARGS);
}
BIF_RETTYPE append_2(BIF_ALIST_2)
{
- return append(bif_export[BIF_append_2], BIF_CALL_ARGS);
+ return append(&bif_trap_export[BIF_append_2], BIF_CALL_ARGS);
}
/* erlang:'--'/2
@@ -954,10 +953,8 @@ static int subtract_continue(Process *p, ErtsSubtractContext *context) {
case SUBTRACT_STAGE_SET_FINISH: {
return subtract_set_finish(p, context);
}
-
- default:
- ERTS_ASSERT(!"unreachable");
}
+ ERTS_INTERNAL_ERROR("unreachable");
}
static int subtract_start(Process *p, Eterm lhs, Eterm rhs,
@@ -1039,11 +1036,11 @@ static Eterm subtract(Export *bif_entry, BIF_ALIST_2) {
}
BIF_RETTYPE ebif_minusminus_2(BIF_ALIST_2) {
- return subtract(bif_export[BIF_ebif_minusminus_2], BIF_CALL_ARGS);
+ return subtract(&bif_trap_export[BIF_ebif_minusminus_2], BIF_CALL_ARGS);
}
BIF_RETTYPE subtract_2(BIF_ALIST_2) {
- return subtract(bif_export[BIF_subtract_2], BIF_CALL_ARGS);
+ return subtract(&bif_trap_export[BIF_subtract_2], BIF_CALL_ARGS);
}
@@ -1068,7 +1065,7 @@ BIF_RETTYPE lists_member_2(BIF_ALIST_2)
while (is_list(list)) {
if (--max_iter < 0) {
BUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_lists_member_2], BIF_P, term, list);
+ BIF_TRAP2(&bif_trap_export[BIF_lists_member_2], BIF_P, term, list);
}
item = CAR(list_val(list));
if ((item == term) || (non_immed_key && eq(item, term))) {
@@ -1130,7 +1127,7 @@ static BIF_RETTYPE lists_reverse_alloc(Process *c_p,
}
ASSERT(is_list(tail) && cells_left == 0);
- BIF_TRAP2(bif_export[BIF_lists_reverse_2], c_p, list, tail);
+ BIF_TRAP2(&bif_trap_export[BIF_lists_reverse_2], c_p, list, tail);
}
static BIF_RETTYPE lists_reverse_onheap(Process *c_p,
@@ -1179,7 +1176,7 @@ static BIF_RETTYPE lists_reverse_onheap(Process *c_p,
}
BUMP_ALL_REDS(c_p);
- BIF_TRAP2(bif_export[BIF_lists_reverse_2], c_p, list, tail);
+ BIF_TRAP2(&bif_trap_export[BIF_lists_reverse_2], c_p, list, tail);
}
BIF_ERROR(c_p, BADARG);
@@ -1209,7 +1206,7 @@ lists_keymember_3(BIF_ALIST_3)
{
Eterm res;
- res = keyfind(BIF_lists_keymember_3, BIF_P,
+ res = keyfind(&bif_trap_export[BIF_lists_keymember_3], BIF_P,
BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
if (is_value(res) && is_tuple(res)) {
return am_true;
@@ -1223,7 +1220,7 @@ lists_keysearch_3(BIF_ALIST_3)
{
Eterm res;
- res = keyfind(BIF_lists_keysearch_3, BIF_P,
+ res = keyfind(&bif_trap_export[BIF_lists_keysearch_3], BIF_P,
BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
if (is_non_value(res) || is_not_tuple(res)) {
return res;
@@ -1236,12 +1233,12 @@ lists_keysearch_3(BIF_ALIST_3)
BIF_RETTYPE
lists_keyfind_3(BIF_ALIST_3)
{
- return keyfind(BIF_lists_keyfind_3, BIF_P,
+ return keyfind(&bif_trap_export[BIF_lists_keyfind_3], BIF_P,
BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
}
static Eterm
-keyfind(int Bif, Process* p, Eterm Key, Eterm Pos, Eterm List)
+keyfind(Export *Bif, Process* p, Eterm Key, Eterm Pos, Eterm List)
{
int max_iter = 10 * CONTEXT_REDS;
Sint pos;
@@ -1257,7 +1254,7 @@ keyfind(int Bif, Process* p, Eterm Key, Eterm Pos, Eterm List)
while (is_list(List)) {
if (--max_iter < 0) {
BUMP_ALL_REDS(p);
- BIF_TRAP3(bif_export[Bif], p, Key, Pos, List);
+ BIF_TRAP3(Bif, p, Key, Pos, List);
}
term = CAR(list_val(List));
List = CDR(list_val(List));
@@ -1282,7 +1279,7 @@ keyfind(int Bif, Process* p, Eterm Key, Eterm Pos, Eterm List)
while (is_list(List)) {
if (--max_iter < 0) {
BUMP_ALL_REDS(p);
- BIF_TRAP3(bif_export[Bif], p, Key, Pos, List);
+ BIF_TRAP3(Bif, p, Key, Pos, List);
}
term = CAR(list_val(List));
List = CDR(list_val(List));
@@ -1300,7 +1297,7 @@ keyfind(int Bif, Process* p, Eterm Key, Eterm Pos, Eterm List)
while (is_list(List)) {
if (--max_iter < 0) {
BUMP_ALL_REDS(p);
- BIF_TRAP3(bif_export[Bif], p, Key, Pos, List);
+ BIF_TRAP3(Bif, p, Key, Pos, List);
}
term = CAR(list_val(List));
List = CDR(list_val(List));
diff --git a/erts/emulator/beam/erl_bif_persistent.c b/erts/emulator/beam/erl_bif_persistent.c
index 0523c8888e..91cc03fe57 100644
--- a/erts/emulator/beam/erl_bif_persistent.c
+++ b/erts/emulator/beam/erl_bif_persistent.c
@@ -45,23 +45,45 @@
#define MUST_SHRINK(t) (((Uint)200) * t->num_entries <= LOAD_FACTOR * t->allocated && \
t->allocated > INITIAL_SIZE)
+
+typedef struct delete_op {
+ enum { DELETE_OP_TUPLE, DELETE_OP_TABLE } type;
+ struct delete_op* next;
+ ErtsThrPrgrLaterOp thr_prog_op;
+ int is_scheduled;
+} DeleteOp;
+
typedef struct hash_table {
Uint allocated;
Uint num_entries;
Uint mask;
Uint first_to_delete;
Uint num_to_delete;
- erts_atomic_t refc;
- struct hash_table* delete_next;
- ErtsThrPrgrLaterOp thr_prog_op;
- Eterm term[1];
+ DeleteOp delete_op;
+ erts_atomic_t term[1];
} HashTable;
+static ERTS_INLINE Eterm get_bucket(HashTable* tab, Uint idx)
+{
+ return (Eterm) erts_atomic_read_nob(&tab->term[idx]);
+}
+
+static ERTS_INLINE void set_bucket(HashTable* tab, Uint idx, Eterm term)
+{
+ erts_atomic_set_nob(&tab->term[idx], (erts_aint_t)term);
+}
+
+static ERTS_INLINE Uint sizeof_HashTable(Uint sz)
+{
+ return offsetof(HashTable, term) + (sz * sizeof(erts_atomic_t));
+}
+
typedef struct trap_data {
HashTable* table;
Uint idx;
Uint remaining;
Uint memory; /* Used by info/0 to count used memory */
+ int got_update_permission;
} TrapData;
typedef enum {
@@ -89,8 +111,7 @@ typedef struct {
} ErtsPersistentTermCpyTableCtx;
typedef enum {
- PUT2_TRAP_LOCATION_NEW_KEY,
- PUT2_TRAP_LOCATION_REPLACE_VALUE
+ PUT2_TRAP_LOCATION_NEW_KEY
} ErtsPersistentTermPut2TrapLocation;
typedef struct {
@@ -117,6 +138,7 @@ typedef struct {
Uint entry_index;
Eterm old_term;
HashTable* tmp_table;
+ int must_shrink;
ErtsPersistentTermCpyTableCtx cpy_ctx;
} ErtsPersistentTermErase1Context;
@@ -126,20 +148,21 @@ typedef struct {
static HashTable* create_initial_table(void);
static Uint lookup(HashTable* hash_table, Eterm key);
+static int is_erasable(HashTable* hash_table, Uint idx);
static HashTable* copy_table(ErtsPersistentTermCpyTableCtx* ctx);
static int try_seize_update_permission(Process* c_p);
static void release_update_permission(int release_updater);
static void table_updater(void* table);
-static void table_deleter(void* hash_table);
-static void dec_table_refc(Process* c_p, HashTable* old_table);
-static void delete_table(Process* c_p, HashTable* table);
+static void scheduled_deleter(void* delete_op);
+static void delete_table(HashTable* table);
+static void delete_tuple(Eterm term);
static void mark_for_deletion(HashTable* hash_table, Uint entry_index);
static ErtsLiteralArea* term_to_area(Eterm tuple);
static void suspend_updater(Process* c_p);
static Eterm do_get_all(Process* c_p, TrapData* trap_data, Eterm res);
static Eterm do_info(Process* c_p, TrapData* trap_data);
-static void append_to_delete_queue(HashTable* table);
-static HashTable* next_to_delete(void);
+static void append_to_delete_queue(DeleteOp*);
+static DeleteOp* list_to_delete(DeleteOp*);
static Eterm alloc_trap_data(Process* c_p);
static int cleanup_trap_data(Binary *bp);
@@ -174,13 +197,16 @@ static Process* updater_process = NULL;
/* Protected by update_table_permission_mtx */
static ErtsThrPrgrLaterOp thr_prog_op;
+static Uint fast_update_index;
+static Eterm fast_update_term = THE_NON_VALUE;
+
/*
* Queue of hash tables to be deleted.
*/
static erts_mtx_t delete_queue_mtx;
-static HashTable* delete_queue_head = NULL;
-static HashTable** delete_queue_tail = &delete_queue_head;
+static DeleteOp* delete_queue_head = NULL;
+static DeleteOp** delete_queue_tail = &delete_queue_head;
/*
* The following variables are only used during crash dumping. They
@@ -284,7 +310,7 @@ BIF_RETTYPE persistent_term_put_2(BIF_ALIST_2)
long iterations_until_trap;
long max_iterations;
#define PUT_TRAP_CODE \
- BIF_TRAP2(bif_export[BIF_persistent_term_put_2], BIF_P, state_mref, BIF_ARG_2)
+ BIF_TRAP2(&bif_trap_export[BIF_persistent_term_put_2], BIF_P, state_mref, BIF_ARG_2)
#define TRAPPING_COPY_TABLE_PUT(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME) \
TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME, PUT_TRAP_CODE)
@@ -306,12 +332,8 @@ BIF_RETTYPE persistent_term_put_2(BIF_ALIST_2)
ctx = ERTS_MAGIC_BIN_DATA(state_bin);
ASSERT(BIF_P->flags & F_DISABLE_GC);
erts_set_gc_state(BIF_P, 1);
- switch (ctx->trap_location) {
- case PUT2_TRAP_LOCATION_NEW_KEY:
- goto L_PUT2_TRAP_LOCATION_NEW_KEY;
- case PUT2_TRAP_LOCATION_REPLACE_VALUE:
- goto L_PUT2_TRAP_LOCATION_REPLACE_VALUE;
- }
+ ASSERT(ctx->trap_location == PUT2_TRAP_LOCATION_NEW_KEY);
+ goto L_PUT2_TRAP_LOCATION_NEW_KEY;
} else {
/* Save state in magic bin in case trapping is necessary */
Eterm* hp;
@@ -329,7 +351,7 @@ BIF_RETTYPE persistent_term_put_2(BIF_ALIST_2)
if (!try_seize_update_permission(BIF_P)) {
- ERTS_BIF_YIELD2(bif_export[BIF_persistent_term_put_2],
+ ERTS_BIF_YIELD2(&bif_trap_export[BIF_persistent_term_put_2],
BIF_P, BIF_ARG_1, BIF_ARG_2);
}
ctx->hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
@@ -344,20 +366,19 @@ BIF_RETTYPE persistent_term_put_2(BIF_ALIST_2)
ctx->heap[2] = ctx->term;
ctx->tuple = make_tuple(ctx->heap);
- if (is_nil(ctx->hash_table->term[ctx->entry_index])) {
- Uint new_size = ctx->hash_table->allocated;
+ if (is_nil(get_bucket(ctx->hash_table, ctx->entry_index))) {
if (MUST_GROW(ctx->hash_table)) {
- new_size *= 2;
+ Uint new_size = ctx->hash_table->allocated * 2;
+ TRAPPING_COPY_TABLE_PUT(ctx->hash_table,
+ ctx->hash_table,
+ new_size,
+ ERTS_PERSISTENT_TERM_CPY_NO_REHASH,
+ PUT2_TRAP_LOCATION_NEW_KEY);
+ ctx->entry_index = lookup(ctx->hash_table, ctx->key);
}
- TRAPPING_COPY_TABLE_PUT(ctx->hash_table,
- ctx->hash_table,
- new_size,
- ERTS_PERSISTENT_TERM_CPY_NO_REHASH,
- PUT2_TRAP_LOCATION_NEW_KEY);
- ctx->entry_index = lookup(ctx->hash_table, ctx->key);
ctx->hash_table->num_entries++;
} else {
- Eterm tuple = ctx->hash_table->term[ctx->entry_index];
+ Eterm tuple = get_bucket(ctx->hash_table, ctx->entry_index);
Eterm old_term;
ASSERT(is_tuple_arity(tuple, 2));
@@ -366,14 +387,6 @@ BIF_RETTYPE persistent_term_put_2(BIF_ALIST_2)
/* Same value. No need to update anything. */
release_update_permission(0);
BIF_RET(am_ok);
- } else {
- /* Mark the old term for deletion. */
- mark_for_deletion(ctx->hash_table, ctx->entry_index);
- TRAPPING_COPY_TABLE_PUT(ctx->hash_table,
- ctx->hash_table,
- ctx->hash_table->allocated,
- ERTS_PERSISTENT_TERM_CPY_NO_REHASH,
- PUT2_TRAP_LOCATION_REPLACE_VALUE);
}
}
@@ -402,8 +415,21 @@ BIF_RETTYPE persistent_term_put_2(BIF_ALIST_2)
literal_area->off_heap = code_off_heap.first;
DESTROY_SHCOPY(info);
erts_set_literal_tag(&ctx->tuple, literal_area->start, term_size);
- ctx->hash_table->term[ctx->entry_index] = ctx->tuple;
+ if (ctx->hash_table == (HashTable *) erts_atomic_read_nob(&the_hash_table)) {
+ /* Schedule fast update in active hash table */
+ fast_update_index = ctx->entry_index;
+ fast_update_term = ctx->tuple;
+ }
+ else {
+ /* Do update in copied table */
+ set_bucket(ctx->hash_table, ctx->entry_index, ctx->tuple);
+ }
+
+ /*
+ * Now wait thread progress before making update visible to guarantee
+ * consistent view of table&term without memory barrier in every get/1.
+ */
erts_schedule_thr_prgr_later_op(table_updater, ctx->hash_table, &thr_prog_op);
suspend_updater(BIF_P);
}
@@ -419,23 +445,25 @@ BIF_RETTYPE persistent_term_get_0(BIF_ALIST_0)
Eterm magic_ref;
Binary* mbp;
+ /* Prevent concurrent updates to get a consistent view */
+ if (!try_seize_update_permission(BIF_P)) {
+ ERTS_BIF_YIELD0(&bif_trap_export[BIF_persistent_term_get_0], BIF_P);
+ }
+
magic_ref = alloc_trap_data(BIF_P);
mbp = erts_magic_ref2bin(magic_ref);
trap_data = ERTS_MAGIC_BIN_DATA(mbp);
trap_data->table = hash_table;
trap_data->idx = 0;
trap_data->remaining = hash_table->num_entries;
+ trap_data->got_update_permission = 1;
res = do_get_all(BIF_P, trap_data, res);
if (trap_data->remaining == 0) {
+ release_update_permission(0);
+ trap_data->got_update_permission = 0;
BUMP_REDS(BIF_P, hash_table->num_entries);
- trap_data->table = NULL; /* Prevent refc decrement */
BIF_RET(res);
} else {
- /*
- * Increment the ref counter to prevent an update operation (by put/2
- * or erase/1) to delete this hash table.
- */
- erts_atomic_inc_nob(&hash_table->refc);
BUMP_ALL_REDS(BIF_P);
BIF_TRAP2(&persistent_term_get_all_export, BIF_P, magic_ref, res);
}
@@ -449,7 +477,7 @@ BIF_RETTYPE persistent_term_get_1(BIF_ALIST_1)
Eterm term;
entry_index = lookup(hash_table, key);
- term = hash_table->term[entry_index];
+ term = get_bucket(hash_table, entry_index);
if (is_boxed(term)) {
ASSERT(is_tuple_arity(term, 2));
BIF_RET(tuple_val(term)[2]);
@@ -466,7 +494,7 @@ BIF_RETTYPE persistent_term_get_2(BIF_ALIST_2)
Eterm term;
entry_index = lookup(hash_table, key);
- term = hash_table->term[entry_index];
+ term = get_bucket(hash_table, entry_index);
if (is_boxed(term)) {
ASSERT(is_tuple_arity(term, 2));
result = tuple_val(term)[2];
@@ -507,7 +535,7 @@ BIF_RETTYPE persistent_term_erase_1(BIF_ALIST_1)
ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(BIF_P);
#endif
#define ERASE_TRAP_CODE \
- BIF_TRAP1(bif_export[BIF_persistent_term_erase_1], BIF_P, state_mref);
+ BIF_TRAP1(&bif_trap_export[BIF_persistent_term_erase_1], BIF_P, state_mref);
#define TRAPPING_COPY_TABLE_ERASE(TABLE_DEST, OLD_TABLE, NEW_SIZE, REHASH, LOC_NAME) \
TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, REHASH, LOC_NAME, ERASE_TRAP_CODE)
if (is_internal_magic_ref(BIF_ARG_1) &&
@@ -542,58 +570,75 @@ BIF_RETTYPE persistent_term_erase_1(BIF_ALIST_1)
ctx->tmp_table = NULL;
}
if (!try_seize_update_permission(BIF_P)) {
- ERTS_BIF_YIELD1(bif_export[BIF_persistent_term_erase_1],
+ ERTS_BIF_YIELD1(&bif_trap_export[BIF_persistent_term_erase_1],
BIF_P, BIF_ARG_1);
}
ctx->key = BIF_ARG_1;
ctx->old_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
ctx->entry_index = lookup(ctx->old_table, ctx->key);
- ctx->old_term = ctx->old_table->term[ctx->entry_index];
+ ctx->old_term = get_bucket(ctx->old_table, ctx->entry_index);
if (is_boxed(ctx->old_term)) {
- Uint new_size;
- /*
- * Since we don't use any delete markers, we must rehash
- * the table when deleting terms to ensure that all terms
- * can still be reached if there are hash collisions.
- * We can't rehash in place and it would not be safe to modify
- * the old table yet, so we will first need a new
- * temporary table copy of the same size as the old one.
- */
+ ctx->must_shrink = MUST_SHRINK(ctx->old_table);
+ if (!ctx->must_shrink && is_erasable(ctx->old_table, ctx->entry_index)) {
+ /*
+ * Fast erase in active hash table.
+ * We schedule with thread progress even here (see put/2).
+ * It's not needed for read consistenty of the NIL word, BUT it's
+ * needed to guarantee sequential read consistenty of multiple
+ * updates. As we do thread progress between all updates, there is
+ * no risk seeing them out of order.
+ */
+ fast_update_index = ctx->entry_index;
+ fast_update_term = NIL;
+ ctx->old_table->num_entries--;
+ erts_schedule_thr_prgr_later_op(table_updater, ctx->old_table, &thr_prog_op);
+ }
+ else {
+ Uint new_size;
+ /*
+ * Since we don't use any delete markers, we must rehash the table
+ * to ensure that all terms can still be reached if there are
+ * hash collisions.
+ * We can't rehash in place and it would not be safe to modify
+ * the old table yet, so we will first need a new
+ * temporary table copy of the same size as the old one.
+ */
- ASSERT(is_tuple_arity(ctx->old_term, 2));
- TRAPPING_COPY_TABLE_ERASE(ctx->tmp_table,
- ctx->old_table,
- ctx->old_table->allocated,
- ERTS_PERSISTENT_TERM_CPY_TEMP,
- ERASE1_TRAP_LOCATION_TMP_COPY);
+ ASSERT(is_tuple_arity(ctx->old_term, 2));
+ TRAPPING_COPY_TABLE_ERASE(ctx->tmp_table,
+ ctx->old_table,
+ ctx->old_table->allocated,
+ ERTS_PERSISTENT_TERM_CPY_TEMP,
+ ERASE1_TRAP_LOCATION_TMP_COPY);
- /*
- * Delete the term from the temporary table. Then copy the
- * temporary table to a new table, rehashing the entries
- * while copying.
- */
+ /*
+ * Delete the term from the temporary table. Then copy the
+ * temporary table to a new table, rehashing the entries
+ * while copying.
+ */
- ctx->tmp_table->term[ctx->entry_index] = NIL;
- ctx->tmp_table->num_entries--;
- new_size = ctx->tmp_table->allocated;
- if (MUST_SHRINK(ctx->tmp_table)) {
- new_size /= 2;
- }
- TRAPPING_COPY_TABLE_ERASE(ctx->new_table,
- ctx->tmp_table,
- new_size,
- ERTS_PERSISTENT_TERM_CPY_REHASH,
- ERASE1_TRAP_LOCATION_FINAL_COPY);
- erts_free(ERTS_ALC_T_PERSISTENT_TERM_TMP, ctx->tmp_table);
- /*
- * IMPORTANT: Memory management depends on that ctx->tmp_table
- * is set to NULL on the line below
- */
- ctx->tmp_table = NULL;
+ set_bucket(ctx->tmp_table, ctx->entry_index, NIL);
+ ctx->tmp_table->num_entries--;
+ new_size = ctx->tmp_table->allocated;
+ if (ctx->must_shrink) {
+ new_size /= 2;
+ }
+ TRAPPING_COPY_TABLE_ERASE(ctx->new_table,
+ ctx->tmp_table,
+ new_size,
+ ERTS_PERSISTENT_TERM_CPY_REHASH,
+ ERASE1_TRAP_LOCATION_FINAL_COPY);
+ erts_free(ERTS_ALC_T_PERSISTENT_TERM_TMP, ctx->tmp_table);
+ /*
+ * IMPORTANT: Memory management depends on that ctx->tmp_table
+ * is set to NULL on the line below
+ */
+ ctx->tmp_table = NULL;
- mark_for_deletion(ctx->old_table, ctx->entry_index);
- erts_schedule_thr_prgr_later_op(table_updater, ctx->new_table, &thr_prog_op);
+ mark_for_deletion(ctx->old_table, ctx->entry_index);
+ erts_schedule_thr_prgr_later_op(table_updater, ctx->new_table, &thr_prog_op);
+ }
suspend_updater(BIF_P);
BUMP_REDS(BIF_P, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED);
ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
@@ -614,7 +659,7 @@ BIF_RETTYPE erts_internal_erase_persistent_terms_0(BIF_ALIST_0)
HashTable* new_table;
if (!try_seize_update_permission(BIF_P)) {
- ERTS_BIF_YIELD0(bif_export[BIF_erts_internal_erase_persistent_terms_0],
+ ERTS_BIF_YIELD0(&bif_trap_export[BIF_erts_internal_erase_persistent_terms_0],
BIF_P);
}
old_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
@@ -634,6 +679,11 @@ BIF_RETTYPE persistent_term_info_0(BIF_ALIST_0)
Eterm magic_ref;
Binary* mbp;
+ /* Prevent concurrent updates to get a consistent view */
+ if (!try_seize_update_permission(BIF_P)) {
+ ERTS_BIF_YIELD0(&bif_trap_export[BIF_persistent_term_info_0], BIF_P);
+ }
+
magic_ref = alloc_trap_data(BIF_P);
mbp = erts_magic_ref2bin(magic_ref);
trap_data = ERTS_MAGIC_BIN_DATA(mbp);
@@ -641,29 +691,19 @@ BIF_RETTYPE persistent_term_info_0(BIF_ALIST_0)
trap_data->idx = 0;
trap_data->remaining = hash_table->num_entries;
trap_data->memory = 0;
+ trap_data->got_update_permission = 0;
res = do_info(BIF_P, trap_data);
if (trap_data->remaining == 0) {
+ release_update_permission(0);
+ trap_data->got_update_permission = 0;
BUMP_REDS(BIF_P, hash_table->num_entries);
- trap_data->table = NULL; /* Prevent refc decrement */
BIF_RET(res);
} else {
- /*
- * Increment the ref counter to prevent an update operation (by put/2
- * or erase/1) to delete this hash table.
- */
- erts_atomic_inc_nob(&hash_table->refc);
BUMP_ALL_REDS(BIF_P);
BIF_TRAP2(&persistent_term_info_export, BIF_P, magic_ref, res);
}
}
-Uint
-erts_persistent_term_count(void)
-{
- HashTable* hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
- return hash_table->num_entries;
-}
-
void
erts_init_persistent_dumping(void)
{
@@ -677,15 +717,15 @@ erts_init_persistent_dumping(void)
*/
erts_persistent_areas = (ErtsLiteralArea **) hash_table->term;
- erts_num_persistent_areas = hash_table->num_entries;
area_p = erts_persistent_areas;
for (i = 0; i < hash_table->allocated; i++) {
- Eterm term = hash_table->term[i];
+ Eterm term = get_bucket(hash_table, i);
if (is_boxed(term)) {
*area_p++ = term_to_area(term);
}
}
+ erts_num_persistent_areas = area_p - erts_persistent_areas;
}
/*
@@ -699,16 +739,14 @@ create_initial_table(void)
int i;
hash_table = (HashTable *) erts_alloc(ERTS_ALC_T_PERSISTENT_TERM,
- sizeof(HashTable)+sizeof(Eterm) *
- (INITIAL_SIZE-1));
+ sizeof_HashTable(INITIAL_SIZE));
hash_table->allocated = INITIAL_SIZE;
hash_table->num_entries = 0;
hash_table->mask = INITIAL_SIZE-1;
hash_table->first_to_delete = 0;
hash_table->num_to_delete = 0;
- erts_atomic_init_nob(&hash_table->refc, (erts_aint_t)1);
for (i = 0; i < INITIAL_SIZE; i++) {
- hash_table->term[i] = NIL;
+ erts_atomic_init_nob(&hash_table->term[i], NIL);
}
return hash_table;
}
@@ -731,12 +769,8 @@ persistent_term_get_all_trap(BIF_ALIST_2)
BUMP_ALL_REDS(BIF_P);
BIF_TRAP2(&persistent_term_get_all_export, BIF_P, BIF_ARG_1, res);
} else {
- /*
- * Decrement ref count (and possibly delete the hash table
- * and associated literal area).
- */
- dec_table_refc(BIF_P, trap_data->table);
- trap_data->table = NULL; /* Prevent refc decrement */
+ release_update_permission(0);
+ trap_data->got_update_permission = 0;
BUMP_REDS(BIF_P, bump_reds);
BIF_RET(res);
}
@@ -774,7 +808,9 @@ do_get_all(Process* c_p, TrapData* trap_data, Eterm res)
i = 0;
heap_size = (2 + 3) * remaining;
while (remaining != 0) {
- Eterm term = hash_table->term[idx];
+ Eterm term;
+ ASSERT(idx < hash_table->allocated);
+ term = get_bucket(hash_table, idx);
if (is_tuple(term)) {
Uint key_size;
Eterm* tup_val;
@@ -829,12 +865,8 @@ persistent_term_info_trap(BIF_ALIST_1)
BUMP_ALL_REDS(BIF_P);
BIF_TRAP1(&persistent_term_info_export, BIF_P, BIF_ARG_1);
} else {
- /*
- * Decrement ref count (and possibly delete the hash table
- * and associated literal area).
- */
- dec_table_refc(BIF_P, trap_data->table);
- trap_data->table = NULL; /* Prevent refc decrement */
+ release_update_permission(0);
+ trap_data->got_update_permission = 0;
BUMP_REDS(BIF_P, bump_reds);
ASSERT(is_map(res));
BIF_RET(res);
@@ -861,9 +893,9 @@ do_info(Process* c_p, TrapData* trap_data)
remaining = trap_data->remaining < max_iter ? trap_data->remaining : max_iter;
trap_data->remaining -= remaining;
while (remaining != 0) {
- if (is_boxed(hash_table->term[idx])) {
+ if (is_boxed(get_bucket(hash_table, idx))) {
ErtsLiteralArea* area;
- area = term_to_area(hash_table->term[idx]);
+ area = term_to_area(get_bucket(hash_table, idx));
trap_data->memory += sizeof(ErtsLiteralArea) +
sizeof(Eterm) * (area->end - area->start - 1);
remaining--;
@@ -911,13 +943,8 @@ cleanup_trap_data(Binary *bp)
{
TrapData* trap_data = ERTS_MAGIC_BIN_DATA(bp);
- if (trap_data->table) {
- /*
- * The process has been killed and is now exiting.
- * Decrement the reference counter for the table.
- */
- dec_table_refc(NULL, trap_data->table);
- }
+ if (trap_data->got_update_permission)
+ release_update_permission(0);
return 1;
}
@@ -925,17 +952,26 @@ static Uint
lookup(HashTable* hash_table, Eterm key)
{
Uint mask = hash_table->mask;
- Eterm* table = hash_table->term;
Uint32 idx = make_internal_hash(key, 0);
Eterm term;
- do {
+ while (1) {
+ term = get_bucket(hash_table, idx & mask);
+ if (is_nil(term) || EQ(key, (tuple_val(term))[1]))
+ break;
idx++;
- term = table[idx & mask];
- } while (is_boxed(term) && !EQ(key, (tuple_val(term))[1]));
+ }
return idx & mask;
}
+static int
+is_erasable(HashTable* hash_table, Uint idx)
+{
+ /* It's ok to erase [idx] if it's not a stepping stone to [idx+1] */
+ return get_bucket(hash_table, (idx + 1) & hash_table->mask) == NIL;
+}
+
+
static HashTable*
copy_table(ErtsPersistentTermCpyTableCtx* ctx)
{
@@ -956,8 +992,7 @@ copy_table(ErtsPersistentTermCpyTableCtx* ctx)
alloc_type = ERTS_ALC_T_PERSISTENT_TERM;
}
ctx->new_table = (HashTable *) erts_alloc(alloc_type,
- sizeof(HashTable) +
- sizeof(Eterm) * (ctx->new_size-1));
+ sizeof_HashTable(ctx->new_size));
if (ctx->old_table->allocated == ctx->new_size &&
(ctx->copy_type == ERTS_PERSISTENT_TERM_CPY_NO_REHASH ||
ctx->copy_type == ERTS_PERSISTENT_TERM_CPY_TEMP)) {
@@ -970,7 +1005,8 @@ copy_table(ErtsPersistentTermCpyTableCtx* ctx)
i < MIN(ctx->iterations_done + ctx->max_iterations,
ctx->new_size);
i++) {
- ctx->new_table->term[i] = ctx->old_table->term[i];
+ erts_atomic_init_nob(&ctx->new_table->term[i],
+ erts_atomic_read_nob(&ctx->old_table->term[i]));
}
ctx->total_iterations_done = (i - ctx->iterations_done);
if (i < ctx->new_size) {
@@ -993,7 +1029,7 @@ copy_table(ErtsPersistentTermCpyTableCtx* ctx)
i < MIN(ctx->iterations_done + ctx->max_iterations,
ctx->new_size);
i++) {
- ctx->new_table->term[i] = NIL;
+ erts_atomic_init_nob(&ctx->new_table->term[i], (erts_aint_t)NIL);
}
ctx->total_iterations_done = (i - ctx->iterations_done);
ctx->max_iterations -= ctx->total_iterations_done;
@@ -1008,11 +1044,12 @@ copy_table(ErtsPersistentTermCpyTableCtx* ctx)
i < MIN(ctx->iterations_done + ctx->max_iterations,
old_size);
i++) {
- if (is_tuple(ctx->old_table->term[i])) {
- Eterm key = tuple_val(ctx->old_table->term[i])[1];
+ Eterm term = get_bucket(ctx->old_table, i);
+ if (is_tuple(term)) {
+ Eterm key = tuple_val(term)[1];
Uint entry_index = lookup(ctx->new_table, key);
- ASSERT(is_nil(ctx->new_table->term[entry_index]));
- ctx->new_table->term[entry_index] = ctx->old_table->term[i];
+ ASSERT(is_nil(get_bucket(ctx->new_table, entry_index)));
+ set_bucket(ctx->new_table, entry_index, term);
}
}
ctx->total_iterations_done += (i - ctx->iterations_done);
@@ -1025,7 +1062,6 @@ copy_table(ErtsPersistentTermCpyTableCtx* ctx)
}
ctx->new_table->first_to_delete = 0;
ctx->new_table->num_to_delete = 0;
- erts_atomic_init_nob(&ctx->new_table->refc, (erts_aint_t)1);
{
HashTable* new_table = ctx->new_table;
/*
@@ -1052,47 +1088,118 @@ term_to_area(Eterm tuple)
offsetof(ErtsLiteralArea, start));
}
+typedef struct {
+ Eterm term;
+ ErtsLiteralArea* area;
+ DeleteOp delete_op;
+} OldLiteral;
+
+static OldLiteral* alloc_old_literal(void)
+{
+ return erts_alloc(ERTS_ALC_T_RELEASE_LAREA, sizeof(OldLiteral));
+}
+
+static void free_old_literal(OldLiteral* olp)
+{
+ erts_free(ERTS_ALC_T_RELEASE_LAREA, olp);
+}
+
static void
table_updater(void* data)
{
HashTable* old_table;
HashTable* new_table;
+ UWord cleanup_bytes;
old_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
new_table = (HashTable *) data;
- ASSERT(new_table->num_to_delete == 0);
- erts_atomic_set_nob(&the_hash_table, (erts_aint_t)new_table);
- append_to_delete_queue(old_table);
- erts_schedule_thr_prgr_later_op(table_deleter,
- old_table,
- &old_table->thr_prog_op);
- release_update_permission(1);
-}
-
-static void
-table_deleter(void* data)
-{
- HashTable* old_table = (HashTable *) data;
+ if (new_table == old_table) {
+ Eterm old_term = get_bucket(old_table, fast_update_index);
+ ASSERT(is_value(fast_update_term));
+ ASSERT(fast_update_index < old_table->allocated);
+ set_bucket(old_table, fast_update_index, fast_update_term);
+#ifdef DEBUG
+ fast_update_term = THE_NON_VALUE;
+#endif
- dec_table_refc(NULL, old_table);
+ if (is_not_nil(old_term)) {
+ OldLiteral *olp = alloc_old_literal();
+ ASSERT(is_tuple_arity(old_term,2));
+ olp->term = old_term;
+ olp->area = term_to_area(old_term);
+ olp->delete_op.type = DELETE_OP_TUPLE;
+ olp->delete_op.is_scheduled = 1;
+ append_to_delete_queue(&olp->delete_op);
+ cleanup_bytes = (ERTS_LITERAL_AREA_SIZE(olp->area)
+ + sizeof(OldLiteral));
+ erts_schedule_thr_prgr_later_cleanup_op(scheduled_deleter,
+ &olp->delete_op,
+ &olp->delete_op.thr_prog_op,
+ cleanup_bytes);
+ }
+ }
+ else {
+ ASSERT(is_non_value(fast_update_term));
+ ASSERT(new_table->num_to_delete == 0);
+ erts_atomic_set_nob(&the_hash_table, (erts_aint_t)new_table);
+ old_table->delete_op.type = DELETE_OP_TABLE;
+ old_table->delete_op.is_scheduled = 1;
+ append_to_delete_queue(&old_table->delete_op);
+ cleanup_bytes = sizeof_HashTable(old_table->allocated);
+ if (old_table->num_to_delete <= 1) {
+ if (old_table->num_to_delete == 1) {
+ ErtsLiteralArea* area;
+ area = term_to_area(get_bucket(old_table,
+ old_table->first_to_delete));
+ cleanup_bytes += ERTS_LITERAL_AREA_SIZE(area);
+ }
+ erts_schedule_thr_prgr_later_cleanup_op(scheduled_deleter,
+ &old_table->delete_op,
+ &old_table->delete_op.thr_prog_op,
+ cleanup_bytes);
+ }
+ else {
+ /* Only at init:restart(). Don't bother with total cleanup size. */
+ ASSERT(old_table->num_to_delete == old_table->allocated);
+ erts_schedule_thr_prgr_later_op(scheduled_deleter,
+ &old_table->delete_op,
+ &old_table->delete_op.thr_prog_op);
+ }
+ }
+ release_update_permission(1);
}
static void
-dec_table_refc(Process* c_p, HashTable* old_table)
+scheduled_deleter(void* data)
{
- erts_aint_t refc = erts_atomic_dec_read_nob(&old_table->refc);
-
- if (refc == 0) {
- HashTable* to_delete;
-
- while ((to_delete = next_to_delete()) != NULL) {
- delete_table(c_p, to_delete);
+ DeleteOp* dop = (DeleteOp*)data;
+
+ dop = list_to_delete(dop);
+
+ while (dop) {
+ DeleteOp* next = dop->next;
+ ASSERT(!dop->is_scheduled);
+ switch (dop->type) {
+ case DELETE_OP_TUPLE: {
+ OldLiteral* olp = ErtsContainerStruct(dop, OldLiteral, delete_op);
+ delete_tuple(olp->term);
+ free_old_literal(olp);
+ break;
+ }
+ case DELETE_OP_TABLE: {
+ HashTable* table = ErtsContainerStruct(dop, HashTable, delete_op);
+ delete_table(table);
+ break;
+ }
+ default:
+ ASSERT(!!"Invalid DeleteOp");
}
+ dop = next;
}
}
static void
-delete_table(Process* c_p, HashTable* table)
+delete_table(HashTable* table)
{
Uint idx = table->first_to_delete;
Uint n = table->num_to_delete;
@@ -1106,25 +1213,32 @@ delete_table(Process* c_p, HashTable* table)
#ifdef DEBUG
if (n == 1) {
- ASSERT(is_tuple_arity(table->term[idx], 2));
+ ASSERT(is_tuple_arity(get_bucket(table, idx), 2));
}
#endif
while (n > 0) {
- Eterm term = table->term[idx];
-
- if (is_tuple_arity(term, 2)) {
- if (is_immed(tuple_val(term)[2])) {
- erts_release_literal_area(term_to_area(term));
- } else {
- erts_queue_release_literals(c_p, term_to_area(term));
- }
- }
+ delete_tuple(get_bucket(table, idx));
idx++, n--;
}
erts_free(ERTS_ALC_T_PERSISTENT_TERM, table);
}
+static void
+delete_tuple(Eterm term)
+{
+ if (is_tuple_arity(term, 2)) {
+ if (is_immed(tuple_val(term)[2])) {
+ erts_release_literal_area(term_to_area(term));
+ } else {
+ erts_queue_release_literals(NULL, term_to_area(term));
+ }
+ }
+ else {
+ ASSERT(is_nil(term));
+ }
+}
+
/*
* Caller *must* yield if this function returns 0.
*/
@@ -1199,42 +1313,46 @@ suspend_updater(Process* c_p)
}
static void
-append_to_delete_queue(HashTable* table)
+append_to_delete_queue(DeleteOp* dop)
{
erts_mtx_lock(&delete_queue_mtx);
- table->delete_next = NULL;
- *delete_queue_tail = table;
- delete_queue_tail = &table->delete_next;
+ dop->next = NULL;
+ *delete_queue_tail = dop;
+ delete_queue_tail = &dop->next;
erts_mtx_unlock(&delete_queue_mtx);
}
-static HashTable*
-next_to_delete(void)
+static DeleteOp*
+list_to_delete(DeleteOp* scheduled_dop)
{
- HashTable* table;
+ DeleteOp* dop;
+ DeleteOp* dop_list;
erts_mtx_lock(&delete_queue_mtx);
- table = delete_queue_head;
- if (table) {
- if (erts_atomic_read_nob(&table->refc)) {
- /*
- * This hash table is still referenced. Hash tables
- * must be deleted in order, so we return a NULL
- * pointer.
- */
- table = NULL;
- } else {
- /*
- * Remove the first hash table from the queue.
- */
- delete_queue_head = table->delete_next;
- if (delete_queue_head == NULL) {
- delete_queue_tail = &delete_queue_head;
- }
- }
+ ASSERT(delete_queue_head && delete_queue_head->is_scheduled);
+ ASSERT(scheduled_dop->is_scheduled);
+ scheduled_dop->is_scheduled = 0;
+
+ if (scheduled_dop == delete_queue_head) {
+ dop = delete_queue_head;
+ while (dop->next && !dop->next->is_scheduled)
+ dop = dop->next;
+
+ /*
+ * Remove list of ripe delete ops.
+ */
+ dop_list = delete_queue_head;
+ delete_queue_head = dop->next;
+ dop->next = NULL;
+ if (delete_queue_head == NULL)
+ delete_queue_tail = &delete_queue_head;
+ }
+ else {
+ dop_list = NULL;
}
erts_mtx_unlock(&delete_queue_mtx);
- return table;
+
+ return dop_list;
}
/*
@@ -1269,25 +1387,53 @@ erts_debug_save_accessed_literal_area(ErtsLiteralArea *lap)
accessed_literal_areas[accessed_no_literal_areas++] = lap;
}
-static void debug_foreach_off_heap(HashTable *tbl, void (*func)(ErlOffHeap *, void *), void *arg)
+static void debug_area_off_heap(ErtsLiteralArea* lap,
+ void (*func)(ErlOffHeap *, void *),
+ void *arg)
+{
+ ErlOffHeap oh;
+ if (!erts_debug_have_accessed_literal_area(lap)) {
+ ERTS_INIT_OFF_HEAP(&oh);
+ oh.first = lap->off_heap;
+ (*func)(&oh, arg);
+ erts_debug_save_accessed_literal_area(lap);
+ }
+}
+
+static void debug_table_foreach_off_heap(HashTable *tbl,
+ void (*func)(ErlOffHeap *, void *),
+ void *arg)
{
int i;
for (i = 0; i < tbl->allocated; i++) {
- Eterm term = tbl->term[i];
+ Eterm term = get_bucket(tbl, i);
if (is_tuple_arity(term, 2)) {
- ErtsLiteralArea *lap = term_to_area(term);
- ErlOffHeap oh;
- if (!erts_debug_have_accessed_literal_area(lap)) {
- ERTS_INIT_OFF_HEAP(&oh);
- oh.first = lap->off_heap;
- (*func)(&oh, arg);
- erts_debug_save_accessed_literal_area(lap);
- }
+ debug_area_off_heap(term_to_area(term), func, arg);
}
}
}
+static void debug_delete_op_foreach_off_heap(DeleteOp *dop,
+ void (*func)(ErlOffHeap *, void *),
+ void *arg)
+{
+ switch (dop->type) {
+ case DELETE_OP_TABLE: {
+ HashTable* table = ErtsContainerStruct(dop, HashTable, delete_op);
+ debug_table_foreach_off_heap(table, func, arg);
+ break;
+ }
+ case DELETE_OP_TUPLE: {
+ OldLiteral* olp = ErtsContainerStruct(dop, OldLiteral, delete_op);
+ debug_area_off_heap(olp->area, func, arg);
+ break;
+ }
+ default:
+ ASSERT(!!"Invalid DeleteOp");
+ }
+}
+
struct debug_la_oh {
void (*func)(ErlOffHeap *, void *);
void *arg;
@@ -1299,13 +1445,16 @@ static void debug_handle_table(void *vfap,
{
struct debug_la_oh *fap = vfap;
HashTable *tbl = vtbl;
- debug_foreach_off_heap(tbl, fap->func, fap->arg);
+ debug_table_foreach_off_heap(tbl, fap->func, fap->arg);
}
+
void
-erts_debug_foreach_persistent_term_off_heap(void (*func)(ErlOffHeap *, void *), void *arg)
+erts_debug_foreach_persistent_term_off_heap(void (*func)(ErlOffHeap *, void *),
+ void *arg)
{
HashTable *tbl;
+ DeleteOp *dop;
struct debug_la_oh fa;
accessed_no_literal_areas = 0;
accessed_literal_areas_size = 10;
@@ -1314,19 +1463,16 @@ erts_debug_foreach_persistent_term_off_heap(void (*func)(ErlOffHeap *, void *),
* accessed_literal_areas_size));
tbl = (HashTable *) erts_atomic_read_nob(&the_hash_table);
- debug_foreach_off_heap(tbl, func, arg);
+ debug_table_foreach_off_heap(tbl, func, arg);
erts_mtx_lock(&delete_queue_mtx);
- for (tbl = delete_queue_head; tbl; tbl = tbl->delete_next)
- debug_foreach_off_heap(tbl, func, arg);
+ for (dop = delete_queue_head; dop; dop = dop->next)
+ debug_delete_op_foreach_off_heap(dop, func, arg);
erts_mtx_unlock(&delete_queue_mtx);
fa.func = func;
fa.arg = arg;
erts_debug_later_op_foreach(table_updater,
debug_handle_table,
(void *) &fa);
- erts_debug_later_op_foreach(table_deleter,
- debug_handle_table,
- (void *) &fa);
erts_debug_foreach_release_literal_area_off_heap(func, arg);
erts_free(ERTS_ALC_T_TMP, accessed_literal_areas);
diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c
index 3375962e56..604fc25b6e 100644
--- a/erts/emulator/beam/erl_bif_port.c
+++ b/erts/emulator/beam/erl_bif_port.c
@@ -44,6 +44,7 @@
#include "erl_bif_unique.h"
#include "dtrace-wrapper.h"
#include "erl_proc_sig_queue.h"
+#include "erl_osenv.h"
static Port *open_port(Process* p, Eterm name, Eterm settings, int *err_typep, int *err_nump);
static int merge_global_environment(erts_osenv_t *env, Eterm key_value_pairs);
@@ -59,8 +60,7 @@ BIF_RETTYPE erts_internal_open_port_2(BIF_ALIST_2)
Eterm res;
char *str;
int err_type, err_num;
- ErtsLinkData *ldp;
- ErtsLink *lnk;
+ ErtsLink *proc_lnk, *port_lnk;
port = open_port(BIF_P, BIF_ARG_1, BIF_ARG_2, &err_type, &err_num);
if (!port) {
@@ -82,15 +82,15 @@ BIF_RETTYPE erts_internal_open_port_2(BIF_ALIST_2)
BIF_RET(res);
}
- ldp = erts_link_create(ERTS_LNK_TYPE_PORT, BIF_P->common.id, port->common.id);
- ASSERT(ldp->a.other.item == port->common.id);
- ASSERT(ldp->b.other.item == BIF_P->common.id);
+ proc_lnk = erts_link_internal_create(ERTS_LNK_TYPE_PORT, port->common.id);
+ port_lnk = erts_link_internal_create(ERTS_LNK_TYPE_PORT, BIF_P->common.id);
/*
* This link should not already be present, but can potentially
* due to id wrapping...
*/
- lnk = erts_link_tree_lookup_insert(&ERTS_P_LINKS(BIF_P), &ldp->a);
- erts_link_tree_insert(&ERTS_P_LINKS(port), &ldp->b);
+ if (!!erts_link_tree_lookup_insert(&ERTS_P_LINKS(BIF_P), proc_lnk))
+ erts_link_internal_release(proc_lnk);
+ erts_link_tree_insert(&ERTS_P_LINKS(port), port_lnk);
if (port->drv_ptr->flags & ERL_DRV_FLAG_USE_INIT_ACK) {
@@ -120,9 +120,6 @@ BIF_RETTYPE erts_internal_open_port_2(BIF_ALIST_2)
erts_port_release(port);
- if (lnk)
- erts_link_release(lnk);
-
return ret;
}
@@ -210,7 +207,7 @@ BIF_RETTYPE erts_internal_port_command_3(BIF_ALIST_3)
ERTS_BIF_PREP_RET(res, am_false);
else {
erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, prt);
- ERTS_BIF_PREP_YIELD3(res, bif_export[BIF_erts_internal_port_command_3],
+ ERTS_BIF_PREP_YIELD3(res, &bif_trap_export[BIF_erts_internal_port_command_3],
BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
}
break;
@@ -1288,22 +1285,25 @@ static int http_request_erl(void* arg, const http_atom_t* meth,
}
static int
-http_header_erl(void* arg, const http_atom_t* name, const char* name_ptr,
- int name_len, const char* value_ptr, int value_len)
+http_header_erl(void* arg, const http_atom_t* name,
+ const char* name_ptr, int name_len,
+ const char* oname_ptr, int oname_len,
+ const char* value_ptr, int value_len)
{
struct packet_callback_args* pca = (struct packet_callback_args*) arg;
- Eterm bit_term, name_term, val_term;
+ Eterm bit_term, name_term, oname_term, val_term;
Uint sz = 6;
Eterm* hp;
#ifdef DEBUG
Eterm* hend;
#endif
- /* {http_header,Bit,Name,IValue,Value} */
+ /* {http_header,Bit,Name,Oname,Value} */
if (name == NULL) {
http_bld_string(pca, NULL, &sz, name_ptr, name_len);
}
+ http_bld_string(pca, NULL, &sz, oname_ptr, oname_len);
http_bld_string(pca, NULL, &sz, value_ptr, value_len);
hp = HAlloc(pca->p, sz);
@@ -1320,8 +1320,9 @@ http_header_erl(void* arg, const http_atom_t* name, const char* name_ptr,
name_term = http_bld_string(pca, &hp,NULL,name_ptr,name_len);
}
+ oname_term = http_bld_string(pca, &hp, NULL, oname_ptr, oname_len);
val_term = http_bld_string(pca, &hp, NULL, value_ptr, value_len);
- pca->res = TUPLE5(hp, am_http_header, bit_term, name_term, am_undefined, val_term);
+ pca->res = TUPLE5(hp, am_http_header, bit_term, name_term, oname_term, val_term);
ASSERT(hp+6==hend);
return 1;
}
diff --git a/erts/emulator/beam/erl_bif_re.c b/erts/emulator/beam/erl_bif_re.c
index 568534cab2..0428b4e348 100644
--- a/erts/emulator/beam/erl_bif_re.c
+++ b/erts/emulator/beam/erl_bif_re.c
@@ -1502,7 +1502,7 @@ re_inspect_2(BIF_ALIST_2)
tp = tuple_val(BIF_ARG_1);
if (tp[1] != am_re_pattern || is_not_small(tp[2]) ||
is_not_small(tp[3]) || is_not_small(tp[4]) ||
- is_not_binary(tp[5])) {
+ is_not_binary(tp[5]) || binary_size(tp[5]) < 4) {
goto error;
}
if (BIF_ARG_2 != am_namelist) {
diff --git a/erts/emulator/beam/erl_bif_trace.c b/erts/emulator/beam/erl_bif_trace.c
index 8b3ad3a8a7..b688e82e70 100644
--- a/erts/emulator/beam/erl_bif_trace.c
+++ b/erts/emulator/beam/erl_bif_trace.c
@@ -80,9 +80,6 @@ static Eterm trace_info_func(Process* p, Eterm pid_spec, Eterm key);
static Eterm trace_info_on_load(Process* p, Eterm key);
static Eterm trace_info_event(Process* p, Eterm event, Eterm key);
-
-static void reset_bif_trace(void);
-static void setup_bif_trace(void);
static void install_exp_breakpoints(BpFunctions* f);
static void uninstall_exp_breakpoints(BpFunctions* f);
static void clean_export_entries(BpFunctions* f);
@@ -131,9 +128,10 @@ trace_pattern(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist)
struct trace_pattern_flags flags = erts_trace_pattern_flags_off;
int is_global;
ErtsTracer meta_tracer = erts_tracer_nil;
+ Uint freason = BADARG;
if (!erts_try_seize_code_write_permission(p)) {
- ERTS_BIF_YIELD3(bif_export[BIF_erts_internal_trace_pattern_3], p, MFA, Pattern, flaglist);
+ ERTS_BIF_YIELD3(&bif_trap_export[BIF_erts_internal_trace_pattern_3], p, MFA, Pattern, flaglist);
}
finish_bp.current = -1;
@@ -155,7 +153,7 @@ trace_pattern(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist)
match_prog_set = NULL;
on = ERTS_BREAK_PAUSE;
} else {
- match_prog_set = erts_match_set_compile(p, Pattern, MFA);
+ match_prog_set = erts_match_set_compile(p, Pattern, MFA, &freason);
if (match_prog_set) {
MatchSetRef(match_prog_set);
on = 1;
@@ -358,7 +356,7 @@ trace_pattern(Process* p, Eterm MFA, Eterm Pattern, Eterm flaglist)
return make_small(matches);
}
else {
- BIF_ERROR(p, BADARG);
+ BIF_ERROR(p, freason);
}
}
@@ -467,19 +465,25 @@ erts_trace_flags(Eterm List,
cpu_timestamp = !0;
#endif
} else if (is_tuple(item)) {
+ ERTS_TRACER_CLEAR(&tracer);
tracer = erts_term_to_tracer(am_tracer, item);
if (tracer == THE_NON_VALUE)
goto error;
} else goto error;
list = CDR(list_val(list));
}
- if (is_not_nil(list)) goto error;
+ if (is_not_nil(list)) {
+ goto error;
+ }
- if (pMask && mask) *pMask = mask;
- if (pTracer && !ERTS_TRACER_IS_NIL(tracer)) *pTracer = tracer;
- if (pCpuTimestamp && cpu_timestamp) *pCpuTimestamp = cpu_timestamp;
+ if (mask) *pMask = mask;
+ if (!ERTS_TRACER_IS_NIL(tracer)) *pTracer = tracer;
+ if (cpu_timestamp) *pCpuTimestamp = cpu_timestamp;
return !0;
+
error:
+ if (tracer != THE_NON_VALUE)
+ ERTS_TRACER_CLEAR(&tracer);
return 0;
}
@@ -543,7 +547,8 @@ Eterm erts_internal_trace_3(BIF_ALIST_3)
}
if (!erts_try_seize_code_write_permission(BIF_P)) {
- ERTS_BIF_YIELD3(bif_export[BIF_erts_internal_trace_3],
+ ERTS_TRACER_CLEAR(&tracer);
+ ERTS_BIF_YIELD3(&bif_trap_export[BIF_erts_internal_trace_3],
BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
}
@@ -793,7 +798,7 @@ Eterm trace_info_2(BIF_ALIST_2)
Eterm res;
if (!erts_try_seize_code_write_permission(p)) {
- ERTS_BIF_YIELD2(bif_export[BIF_trace_info_2], p, What, Key);
+ ERTS_BIF_YIELD2(&bif_trap_export[BIF_trace_info_2], p, What, Key);
}
if (What == am_on_load) {
@@ -1047,14 +1052,13 @@ static int function_is_traced(Process *p,
e.info.mfa.function = mfa[1];
e.info.mfa.arity = mfa[2];
if ((ep = export_get(&e)) != NULL) {
- pc = ep->beam;
+ pc = ep->trampoline.raw;
if (ep->addressv[erts_active_code_ix()] == pc &&
! BeamIsOpCode(*pc, op_call_error_handler)) {
int r = 0;
- ASSERT(BeamIsOpCode(*pc, op_apply_bif) ||
- BeamIsOpCode(*pc, op_i_generic_breakpoint));
+ ASSERT(BeamIsOpCode(*pc, op_i_generic_breakpoint));
if (erts_is_trace_break(&ep->info, ms, 0)) {
return FUNC_TRACE_GLOBAL_TRACE;
@@ -1426,18 +1430,21 @@ erts_set_trace_pattern(Process*p, ErtsCodeMFA *mfa, int specified,
int n;
BpFunction* fp;
- /*
- * First work on normal functions (not real BIFs).
- */
-
erts_bp_match_export(&finish_bp.e, mfa, specified);
fp = finish_bp.e.matching;
n = finish_bp.e.matched;
for (i = 0; i < n; i++) {
ErtsCodeInfo *ci = fp[i].ci;
- BeamInstr* pc = erts_codeinfo_to_code(ci);
- Export* ep = ErtsContainerStruct(ci, Export, info);
+ BeamInstr* pc;
+ Export* ep;
+
+ pc = erts_codeinfo_to_code(ci);
+ ep = ErtsContainerStruct(ci, Export, info);
+
+ if (ep->bif_number != -1) {
+ ep->is_bif_traced = !!on;
+ }
if (on && !flags.breakpoint) {
/* Turn on global call tracing */
@@ -1446,12 +1453,12 @@ erts_set_trace_pattern(Process*p, ErtsCodeMFA *mfa, int specified,
#ifdef DEBUG
ep->info.op = BeamOpCodeAddr(op_i_func_info_IaaI);
#endif
- ep->beam[0] = BeamOpCodeAddr(op_trace_jump_W);
- ep->beam[1] = (BeamInstr) ep->addressv[code_ix];
+ ep->trampoline.op = BeamOpCodeAddr(op_trace_jump_W);
+ ep->trampoline.trace.address = (BeamInstr) ep->addressv[code_ix];
}
- erts_set_call_trace_bif(ci, match_prog_set, 0);
+ erts_set_export_trace(ci, match_prog_set, 0);
if (ep->addressv[code_ix] != pc) {
- ep->beam[0] = BeamOpCodeAddr(op_i_generic_breakpoint);
+ ep->trampoline.op = BeamOpCodeAddr(op_i_generic_breakpoint);
}
} else if (!on && flags.breakpoint) {
/* Turn off breakpoint tracing -- nothing to do here. */
@@ -1460,91 +1467,14 @@ erts_set_trace_pattern(Process*p, ErtsCodeMFA *mfa, int specified,
* Turn off global tracing, either explicitly or implicitly
* before turning on breakpoint tracing.
*/
- erts_clear_call_trace_bif(ci, 0);
- if (BeamIsOpCode(ep->beam[0], op_i_generic_breakpoint)) {
- ep->beam[0] = BeamOpCodeAddr(op_trace_jump_W);
+ erts_clear_export_trace(ci, 0);
+ if (BeamIsOpCode(ep->trampoline.op, op_i_generic_breakpoint)) {
+ ep->trampoline.op = BeamOpCodeAddr(op_trace_jump_W);
}
}
}
/*
- ** OK, now for the bif's
- */
- for (i = 0; i < BIF_SIZE; ++i) {
- Export *ep = bif_export[i];
-
- if (!ExportIsBuiltIn(ep)) {
- continue;
- }
-
- if (bif_table[i].f == bif_table[i].traced) {
- /* Trace wrapper same as regular function - untraceable */
- continue;
- }
-
- switch (specified) {
- case 3:
- if (mfa->arity != ep->info.mfa.arity)
- continue;
- case 2:
- if (mfa->function != ep->info.mfa.function)
- continue;
- case 1:
- if (mfa->module != ep->info.mfa.module)
- continue;
- case 0:
- break;
- default:
- ASSERT(0);
- }
-
- if (! flags.breakpoint) { /* Export entry call trace */
- if (on) {
- erts_clear_call_trace_bif(&ep->info, 1);
- erts_clear_mtrace_bif(&ep->info);
- erts_set_call_trace_bif(&ep->info, match_prog_set, 0);
- } else { /* off */
- erts_clear_call_trace_bif(&ep->info, 0);
- }
- matches++;
- } else { /* Breakpoint call trace */
- int m = 0;
-
- if (on) {
- if (flags.local) {
- erts_clear_call_trace_bif(&ep->info, 0);
- erts_set_call_trace_bif(&ep->info, match_prog_set, 1);
- m = 1;
- }
- if (flags.meta) {
- erts_set_mtrace_bif(&ep->info, meta_match_prog_set,
- meta_tracer);
- m = 1;
- }
- if (flags.call_time) {
- erts_set_time_trace_bif(&ep->info, on);
- /* I don't want to remove any other tracers */
- m = 1;
- }
- } else { /* off */
- if (flags.local) {
- erts_clear_call_trace_bif(&ep->info, 1);
- m = 1;
- }
- if (flags.meta) {
- erts_clear_mtrace_bif(&ep->info);
- m = 1;
- }
- if (flags.call_time) {
- erts_clear_time_trace_bif(&ep->info);
- m = 1;
- }
- }
- matches += m;
- }
- }
-
- /*
** So, now for breakpoint tracing
*/
erts_bp_match_functions(&finish_bp.f, mfa, specified);
@@ -1670,7 +1600,6 @@ erts_finish_breakpointing(void)
install_exp_breakpoints(&finish_bp.e);
}
}
- setup_bif_trace();
return 1;
case 1:
/*
@@ -1699,7 +1628,6 @@ erts_finish_breakpointing(void)
uninstall_exp_breakpoints(&finish_bp.e);
}
}
- reset_bif_trace();
return 1;
case 3:
/*
@@ -1710,7 +1638,6 @@ erts_finish_breakpointing(void)
* updated). If any breakpoints have been totally disabled,
* deallocate the GenericBp structs for them.
*/
- erts_consolidate_bif_bp_data();
clean_export_entries(&finish_bp.e);
erts_consolidate_bp_data(&finish_bp.e, 0);
erts_consolidate_bp_data(&finish_bp.f, 1);
@@ -1736,7 +1663,7 @@ install_exp_breakpoints(BpFunctions* f)
for (i = 0; i < ne; i++) {
Export* ep = ErtsContainerStruct(fp[i].ci, Export, info);
- ep->addressv[code_ix] = ep->beam;
+ ep->addressv[code_ix] = ep->trampoline.raw;
}
}
@@ -1751,11 +1678,12 @@ uninstall_exp_breakpoints(BpFunctions* f)
for (i = 0; i < ne; i++) {
Export* ep = ErtsContainerStruct(fp[i].ci, Export, info);
- if (ep->addressv[code_ix] != ep->beam) {
- continue;
- }
- ASSERT(BeamIsOpCode(ep->beam[0], op_trace_jump_W));
- ep->addressv[code_ix] = (BeamInstr *) ep->beam[1];
+ if (ep->addressv[code_ix] != ep->trampoline.raw) {
+ continue;
+ }
+
+ ASSERT(BeamIsOpCode(ep->trampoline.op, op_trace_jump_W));
+ ep->addressv[code_ix] = (BeamInstr *) ep->trampoline.trace.address;
}
}
@@ -1770,48 +1698,14 @@ clean_export_entries(BpFunctions* f)
for (i = 0; i < ne; i++) {
Export* ep = ErtsContainerStruct(fp[i].ci, Export, info);
- if (ep->addressv[code_ix] == ep->beam) {
- continue;
- }
- if (BeamIsOpCode(ep->beam[0], op_trace_jump_W)) {
- ep->beam[0] = (BeamInstr) 0;
- ep->beam[1] = (BeamInstr) 0;
- }
- }
-}
-
-static void
-setup_bif_trace(void)
-{
- int i;
-
- for (i = 0; i < BIF_SIZE; ++i) {
- Export *ep = bif_export[i];
- GenericBp* g = ep->info.u.gen_bp;
- if (g) {
- if (ExportIsBuiltIn(ep)) {
- ASSERT(ep->beam[1]);
- ep->beam[1] = (BeamInstr) bif_table[i].traced;
- }
- }
- }
-}
+ if (ep->addressv[code_ix] == ep->trampoline.raw) {
+ continue;
+ }
-static void
-reset_bif_trace(void)
-{
- int i;
- ErtsBpIndex active = erts_active_bp_ix();
-
- for (i = 0; i < BIF_SIZE; ++i) {
- Export *ep = bif_export[i];
- GenericBp* g = ep->info.u.gen_bp;
- if (g && g->data[active].flags == 0) {
- if (ExportIsBuiltIn(ep)) {
- ASSERT(ep->beam[1]);
- ep->beam[1] = (BeamInstr) bif_table[i].f;
- }
- }
+ if (BeamIsOpCode(ep->trampoline.op, op_trace_jump_W)) {
+ ep->trampoline.op = (BeamInstr) 0;
+ ep->trampoline.trace.address = (BeamInstr) 0;
+ }
}
}
@@ -1954,10 +1848,13 @@ new_seq_trace_token(Process* p, int ensure_new_heap)
make_small(p->seq_trace_lastcnt));
}
else if (ensure_new_heap) {
+ Eterm *mature = p->abandoned_heap ? p->abandoned_heap : p->heap;
+ Uint mature_size = p->high_water - mature;
Eterm* tpl = tuple_val(SEQ_TRACE_TOKEN(p));
ASSERT(arityval(tpl[0]) == 5);
if (ErtsInArea(tpl, OLD_HEAP(p),
- (OLD_HEND(p) - OLD_HEAP(p))*sizeof(Eterm))) {
+ (OLD_HEND(p) - OLD_HEAP(p))*sizeof(Eterm)) ||
+ ErtsInArea(tpl, mature, mature_size*sizeof(Eterm))) {
hp = HAlloc(p, 6);
sys_memcpy(hp, tpl, 6*sizeof(Eterm));
SEQ_TRACE_TOKEN(p) = make_tuple(hp);
@@ -1976,8 +1873,9 @@ BIF_RETTYPE erl_seq_trace_info(Process *p, Eterm item)
}
if (have_no_seqtrace(SEQ_TRACE_TOKEN(p))) {
- if ((item == am_send) || (item == am_receive) ||
- (item == am_print) || (item == am_timestamp)
+ if ((item == am_send) || (item == am_spawn) ||
+ (item == am_receive) || (item == am_print)
+ || (item == am_timestamp)
|| (item == am_monotonic_timestamp)
|| (item == am_strict_monotonic_timestamp)) {
hp = HAlloc(p,3);
@@ -2041,7 +1939,7 @@ BIF_RETTYPE seq_trace_print_1(BIF_ALIST_1)
if (have_no_seqtrace(SEQ_TRACE_TOKEN(BIF_P))) {
BIF_RET(am_false);
}
- seq_trace_update_send(BIF_P);
+ seq_trace_update_serial(BIF_P);
seq_trace_output(SEQ_TRACE_TOKEN(BIF_P), BIF_ARG_1,
SEQ_TRACE_PRINT, NIL, BIF_P);
BIF_RET(am_true);
@@ -2062,7 +1960,7 @@ BIF_RETTYPE seq_trace_print_2(BIF_ALIST_2)
}
if (!EQ(BIF_ARG_1, SEQ_TRACE_TOKEN_LABEL(BIF_P)))
BIF_RET(am_false);
- seq_trace_update_send(BIF_P);
+ seq_trace_update_serial(BIF_P);
seq_trace_output(SEQ_TRACE_TOKEN(BIF_P), BIF_ARG_2,
SEQ_TRACE_PRINT, NIL, BIF_P);
BIF_RET(am_true);
diff --git a/erts/emulator/beam/erl_binary.h b/erts/emulator/beam/erl_binary.h
index f3e3890e94..20344bbdcd 100644
--- a/erts/emulator/beam/erl_binary.h
+++ b/erts/emulator/beam/erl_binary.h
@@ -369,7 +369,7 @@ erts_free_aligned_binary_bytes(byte* buf)
** These extra bytes where earlier (< R13B04) added by an alignment-bug
** in this code. Do we dare remove this in some major release (R14?) maybe?
*/
-#if defined(DEBUG) || defined(VALGRIND)
+#if defined(DEBUG) || defined(VALGRIND) || defined(ADDRESS_SANITIZER)
# define CHICKEN_PAD 0
#else
# define CHICKEN_PAD (sizeof(void*) - 1)
diff --git a/erts/emulator/beam/erl_bits.c b/erts/emulator/beam/erl_bits.c
index 7084642f7c..79c5781929 100644
--- a/erts/emulator/beam/erl_bits.c
+++ b/erts/emulator/beam/erl_bits.c
@@ -41,6 +41,18 @@
#define BIT_IS_MACHINE_ENDIAN(x) (((x)&BSF_LITTLE) == BIT_ENDIAN_MACHINE)
/*
+ * Here is how many bits we can copy in each reduction.
+ *
+ * At the time of writing of this comment, CONTEXT_REDS was 4000 and
+ * BITS_PER_REDUCTION was 1 KiB (8192 bits). The time for copying an
+ * unaligned 4000 KiB binary on my computer (which has a 4,2 GHz Intel
+ * i7 CPU) was about 5 ms. The time was approximately 4 times lower if
+ * the source and destinations binaries were aligned.
+ */
+
+#define BITS_PER_REDUCTION (8*1024)
+
+/*
* MAKE_MASK(n) constructs a mask with n bits.
* Example: MAKE_MASK(3) returns the binary number 00000111.
*/
@@ -977,14 +989,14 @@ erts_bs_put_utf16(ERL_BITS_PROTO_2(Eterm arg, Uint flags))
erts_bin_offset += num_bits;
return 1;
}
-
int
-erts_new_bs_put_binary(ERL_BITS_PROTO_2(Eterm arg, Uint num_bits))
+erts_new_bs_put_binary(Process *c_p, Eterm arg, Uint num_bits)
{
byte *bptr;
Uint bitoffs;
Uint bitsize;
+ ERL_BITS_DEFINE_STATEP(c_p);
if (!is_binary(arg)) {
return 0;
@@ -995,16 +1007,18 @@ erts_new_bs_put_binary(ERL_BITS_PROTO_2(Eterm arg, Uint num_bits))
}
copy_binary_to_buffer(erts_current_bin, erts_bin_offset, bptr, bitoffs, num_bits);
erts_bin_offset += num_bits;
+ BUMP_REDS(c_p, num_bits / BITS_PER_REDUCTION);
return 1;
}
int
-erts_new_bs_put_binary_all(ERL_BITS_PROTO_2(Eterm arg, Uint unit))
+erts_new_bs_put_binary_all(Process *c_p, Eterm arg, Uint unit)
{
byte *bptr;
Uint bitoffs;
Uint bitsize;
Uint num_bits;
+ ERL_BITS_DEFINE_STATEP(c_p);
/*
* This type test is not needed if the code was compiled with
@@ -1029,6 +1043,7 @@ erts_new_bs_put_binary_all(ERL_BITS_PROTO_2(Eterm arg, Uint unit))
}
copy_binary_to_buffer(erts_current_bin, erts_bin_offset, bptr, bitoffs, num_bits);
erts_bin_offset += num_bits;
+ BUMP_REDS(c_p, num_bits / BITS_PER_REDUCTION);
return 1;
}
@@ -1352,6 +1367,7 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term,
binp = erts_bin_realloc(binp, new_size);
pb->val = binp;
pb->bytes = (byte *) binp->orig_bytes;
+ BUMP_REDS(c_p, pb->size / BITS_PER_REDUCTION);
}
erts_current_bin = pb->bytes;
@@ -1473,6 +1489,7 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term,
* Now copy the data into the binary.
*/
copy_binary_to_buffer(erts_current_bin, 0, src_bytes, bitoffs, erts_bin_offset);
+ BUMP_REDS(c_p, erts_bin_offset / BITS_PER_REDUCTION);
return make_binary(sb);
}
@@ -1537,6 +1554,7 @@ erts_bs_private_append(Process* p, Eterm bin, Eterm build_size_term, Uint unit)
if (binp->orig_size < pb->size) {
Uint new_size = 2*pb->size;
+ BUMP_REDS(p, pb->size / BITS_PER_REDUCTION);
if (pb->flags & PB_IS_WRITABLE) {
/*
* This is the normal case - the binary is writable.
diff --git a/erts/emulator/beam/erl_bits.h b/erts/emulator/beam/erl_bits.h
index eced966a7f..d9262bea00 100644
--- a/erts/emulator/beam/erl_bits.h
+++ b/erts/emulator/beam/erl_bits.h
@@ -164,8 +164,8 @@ Eterm erts_bs_get_binary_all_2(Process *p, ErlBinMatchBuffer* mb);
int erts_new_bs_put_integer(ERL_BITS_PROTO_3(Eterm Integer, Uint num_bits, unsigned flags));
int erts_bs_put_utf8(ERL_BITS_PROTO_1(Eterm Integer));
int erts_bs_put_utf16(ERL_BITS_PROTO_2(Eterm Integer, Uint flags));
-int erts_new_bs_put_binary(ERL_BITS_PROTO_2(Eterm Bin, Uint num_bits));
-int erts_new_bs_put_binary_all(ERL_BITS_PROTO_2(Eterm Bin, Uint unit));
+int erts_new_bs_put_binary(Process *c_p, Eterm Bin, Uint num_bits);
+int erts_new_bs_put_binary_all(Process *c_p, Eterm Bin, Uint unit);
int erts_new_bs_put_float(Process *c_p, Eterm Float, Uint num_bits, int flags);
void erts_new_bs_put_string(ERL_BITS_PROTO_2(byte* iptr, Uint num_bytes));
diff --git a/erts/emulator/beam/erl_cpu_topology.c b/erts/emulator/beam/erl_cpu_topology.c
index 7561cc4703..91a99077f8 100644
--- a/erts/emulator/beam/erl_cpu_topology.c
+++ b/erts/emulator/beam/erl_cpu_topology.c
@@ -1632,7 +1632,7 @@ erts_get_cpu_topology_term(Process *c_p, Eterm which)
}
static void
-get_logical_processors(int *conf, int *onln, int *avail)
+get_logical_processors(int *conf, int *onln, int *avail, int *quota)
{
if (conf)
*conf = erts_get_cpu_configured(cpuinfo);
@@ -1640,13 +1640,15 @@ get_logical_processors(int *conf, int *onln, int *avail)
*onln = erts_get_cpu_online(cpuinfo);
if (avail)
*avail = erts_get_cpu_available(cpuinfo);
+ if (quota)
+ *quota = erts_get_cpu_quota(cpuinfo);
}
void
-erts_get_logical_processors(int *conf, int *onln, int *avail)
+erts_get_logical_processors(int *conf, int *onln, int *avail, int *quota)
{
erts_rwmtx_rlock(&cpuinfo_rwmtx);
- get_logical_processors(conf, onln, avail);
+ get_logical_processors(conf, onln, avail, quota);
erts_rwmtx_runlock(&cpuinfo_rwmtx);
}
@@ -1655,14 +1657,15 @@ erts_pre_early_init_cpu_topology(int *max_dcg_p,
int *max_rg_p,
int *conf_p,
int *onln_p,
- int *avail_p)
+ int *avail_p,
+ int *quota_p)
{
cpu_groups_maps = NULL;
no_cpu_groups_callbacks = 0;
*max_rg_p = ERTS_MAX_READER_GROUPS;
*max_dcg_p = ERTS_MAX_FLXCTR_GROUPS;
cpuinfo = erts_cpu_info_create();
- get_logical_processors(conf_p, onln_p, avail_p);
+ get_logical_processors(conf_p, onln_p, avail_p, quota_p);
}
void
diff --git a/erts/emulator/beam/erl_cpu_topology.h b/erts/emulator/beam/erl_cpu_topology.h
index 8ff0fce3de..62bd0e73b6 100644
--- a/erts/emulator/beam/erl_cpu_topology.h
+++ b/erts/emulator/beam/erl_cpu_topology.h
@@ -32,7 +32,8 @@ erts_pre_early_init_cpu_topology(int *max_dcg_p,
int *max_rg_p,
int *conf_p,
int *onln_p,
- int *avail_p);
+ int *avail_p,
+ int *quota_p);
void
erts_early_init_cpu_topology(int no_schedulers,
int *max_main_threads_p,
@@ -81,7 +82,7 @@ Eterm erts_set_cpu_topology(Process *c_p, Eterm term);
Eterm erts_get_cpu_topology_term(Process *c_p, Eterm which);
int erts_update_cpu_info(void);
-void erts_get_logical_processors(int *conf, int *onln, int *avail);
+void erts_get_logical_processors(int *conf, int *onln, int *avail, int *quota);
int erts_sched_bind_atthrcreate_prepare(void);
int erts_sched_bind_atthrcreate_child(int unbind);
diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c
index 841eef8475..495d846a6a 100644
--- a/erts/emulator/beam/erl_db.c
+++ b/erts/emulator/beam/erl_db.c
@@ -51,6 +51,15 @@ erts_atomic_t erts_ets_misc_mem_size;
** Utility macros
*/
+#if defined(DEBUG)
+# define DBG_RANDOM_REDS(REDS, SEED) \
+ ((REDS) * 0.1 * erts_sched_local_random_float(SEED))
+#else
+# define DBG_RANDOM_REDS(REDS, SEED) (REDS)
+#endif
+
+
+
#define DB_BIF_GET_TABLE(TB, WHAT, KIND, BIF_IX) \
DB_GET_TABLE(TB, BIF_ARG_1, WHAT, KIND, BIF_IX, NULL, BIF_P)
@@ -75,7 +84,7 @@ static BIF_RETTYPE db_bif_fail(Process* p, Uint freason,
{
if (freason == TRAP) {
if (!bif_exp)
- bif_exp = bif_export[bif_ix];
+ bif_exp = &bif_trap_export[bif_ix];
p->arity = bif_exp->info.mfa.arity;
p->i = (BeamInstr*) bif_exp->addressv[erts_active_code_ix()];
}
@@ -353,7 +362,9 @@ struct meta_name_tab_entry* meta_name_tab_bucket(Eterm name,
typedef enum {
LCK_READ=1, /* read only access */
LCK_WRITE=2, /* exclusive table write access */
- LCK_WRITE_REC=3 /* record write access */
+ LCK_WRITE_REC=3, /* record write access */
+ NOLCK_ACCESS=4 /* Used to access the table structure
+ without acquiring the table lock */
} db_lock_kind_t;
extern DbTableMethod db_hash;
@@ -409,9 +420,11 @@ static void
free_dbtable(void *vtb)
{
DbTable *tb = (DbTable *) vtb;
+ erts_flxctr_add(&tb->common.counters,
+ ERTS_DB_TABLE_MEM_COUNTER_ID,
+ -((Sint)erts_flxctr_nr_of_allocated_bytes(&tb->common.counters)));
ASSERT(erts_flxctr_is_snapshot_ongoing(&tb->common.counters) ||
- sizeof(DbTable) == erts_flxctr_read_approx(&tb->common.counters,
- ERTS_DB_TABLE_MEM_COUNTER_ID));
+ sizeof(DbTable) == DB_GET_APPROX_MEM_CONSUMED(tb));
ASSERT(is_immed(tb->common.heir_data));
@@ -423,7 +436,7 @@ free_dbtable(void *vtb)
if (tb->common.btid)
erts_bin_release(tb->common.btid);
- erts_flxctr_destroy(&tb->common.counters, ERTS_ALC_T_DB_TABLE);
+ erts_flxctr_destroy(&tb->common.counters, ERTS_ALC_T_ETS_CTRS);
erts_free(ERTS_ALC_T_DB_TABLE, tb);
}
@@ -619,7 +632,7 @@ static ERTS_INLINE void db_lock(DbTable* tb, db_lock_kind_t kind)
if (kind == LCK_WRITE) {
erts_rwmtx_rwlock(&tb->common.rwlock);
tb->common.is_thread_safe = 1;
- } else {
+ } else if (kind != NOLCK_ACCESS) {
erts_rwmtx_rlock(&tb->common.rwlock);
ASSERT(!tb->common.is_thread_safe);
}
@@ -631,6 +644,8 @@ static ERTS_INLINE void db_lock(DbTable* tb, db_lock_kind_t kind)
case LCK_WRITE_REC:
erts_rwmtx_rwlock(&tb->common.rwlock);
break;
+ case NOLCK_ACCESS:
+ return;
default:
erts_rwmtx_rlock(&tb->common.rwlock);
}
@@ -640,7 +655,7 @@ static ERTS_INLINE void db_lock(DbTable* tb, db_lock_kind_t kind)
static ERTS_INLINE void db_unlock(DbTable* tb, db_lock_kind_t kind)
{
- if (DB_LOCK_FREE(tb))
+ if (DB_LOCK_FREE(tb) || kind == NOLCK_ACCESS)
return;
if (tb->common.type & DB_FINE_LOCKED) {
if (kind == LCK_WRITE) {
@@ -671,7 +686,10 @@ static ERTS_INLINE int db_is_exclusive(DbTable* tb, db_lock_kind_t kind)
if (DB_LOCK_FREE(tb))
return 1;
- return kind != LCK_READ && tb->common.is_thread_safe;
+ return
+ kind != LCK_READ &&
+ kind != NOLCK_ACCESS &&
+ tb->common.is_thread_safe;
}
static DbTable* handle_lacking_permission(Process* p, DbTable* tb,
@@ -679,11 +697,27 @@ static DbTable* handle_lacking_permission(Process* p, DbTable* tb,
Uint* freason_p)
{
if (tb->common.status & DB_BUSY) {
+ void* continuation_state;
if (!db_is_exclusive(tb, kind)) {
db_unlock(tb, kind);
db_lock(tb, LCK_WRITE);
}
- delete_all_objects_continue(p, tb);
+ continuation_state = (void*)erts_atomic_read_nob(&tb->common.continuation_state);
+ if (continuation_state != NULL) {
+ const long iterations_per_red = 10;
+ const long reds = iterations_per_red * ERTS_BIF_REDS_LEFT(p);
+ long nr_of_reductions = DBG_RANDOM_REDS(reds, (Uint)freason_p);
+ const long init_reds = nr_of_reductions;
+ tb->common.continuation(&nr_of_reductions,
+ &continuation_state,
+ NULL);
+ if (continuation_state == NULL) {
+ erts_atomic_set_relb(&tb->common.continuation_state, (Sint)NULL);
+ }
+ BUMP_REDS(p, (init_reds - nr_of_reductions) / iterations_per_red);
+ } else {
+ delete_all_objects_continue(p, tb);
+ }
db_unlock(tb, LCK_WRITE);
tb = NULL;
*freason_p = TRAP;
@@ -722,9 +756,9 @@ DbTable* db_get_table_aux(Process *p,
if (!meta_already_locked)
erts_rwmtx_rlock(mtl);
else {
- ERTS_LC_ASSERT(erts_lc_rwmtx_is_rlocked(mtl)
- || erts_lc_rwmtx_is_rwlocked(mtl)
- || META_DB_LOCK_FREE());
+ ERTS_LC_ASSERT(META_DB_LOCK_FREE()
+ || erts_lc_rwmtx_is_rlocked(mtl)
+ || erts_lc_rwmtx_is_rwlocked(mtl));
}
tb = NULL;
if (bucket->pu.tb != NULL) {
@@ -752,15 +786,28 @@ DbTable* db_get_table_aux(Process *p,
if (tb) {
db_lock(tb, kind);
#ifdef ETS_DBG_FORCE_TRAP
- if (erts_atomic_read_nob(&tb->common.dbg_force_trap) &&
- erts_atomic_add_read_nob(&tb->common.dbg_force_trap, 2) & 2) {
- db_unlock(tb, kind);
- tb = NULL;
- *freason_p = TRAP;
+ if (erts_atomic_read_nob(&tb->common.dbg_force_trap)) {
+ Uint32 rand = erts_sched_local_random((Uint)&p);
+ if ( !(rand & 7) ) {
+ /* About 7 of 8 threads that are on the line above
+ will get here */
+ if (erts_atomic_add_read_nob(&tb->common.dbg_force_trap, 2) & 2) {
+ db_unlock(tb, kind);
+ tb = NULL;
+ *freason_p = TRAP;
+ return tb;
+ }
+ }
+
+
}
- else
#endif
- if (ERTS_UNLIKELY(!(tb->common.status & what)))
+ if (ERTS_UNLIKELY(what != DB_READ_TBL_STRUCT
+ /* IMPORTANT: the above check is
+ necessary as the status field might
+ be in an intermediate state when
+ kind==NOLCK_ACCESS */ &&
+ !(tb->common.status & what)))
tb = handle_lacking_permission(p, tb, kind, freason_p);
}
else
@@ -779,6 +826,17 @@ DbTable* db_get_table(Process *p,
return db_get_table_aux(p, id, what, kind, 0, freason_p);
}
+static BIF_RETTYPE db_get_table_or_fail_return(DbTable **tb, /* out */
+ Eterm table_id,
+ Uint32 what,
+ db_lock_kind_t kind,
+ Uint bif_ix,
+ Process* p)
+{
+ DB_GET_TABLE(*tb, table_id, what, kind, bif_ix, NULL, p);
+ return THE_NON_VALUE;
+}
+
static int insert_named_tab(Eterm name_atom, DbTable* tb, int have_lock)
{
int ret = 0;
@@ -861,7 +919,7 @@ static int remove_named_tab(DbTable *tb, int have_lock)
db_lock(tb, LCK_WRITE);
}
- ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(rwlock) || META_DB_LOCK_FREE());
+ ERTS_LC_ASSERT(META_DB_LOCK_FREE() || erts_lc_rwmtx_is_rwlocked(rwlock));
if (bucket->pu.tb == NULL) {
goto done;
@@ -1382,6 +1440,506 @@ BIF_RETTYPE ets_update_counter_4(BIF_ALIST_4)
return do_update_counter(BIF_P, tb, BIF_ARG_2, BIF_ARG_3, BIF_ARG_4);
}
+typedef enum {
+ ETS_INSERT_2_LIST_PROCESS_LOCAL,
+ ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK,
+ ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK_DESTROY,
+ ETS_INSERT_2_LIST_GLOBAL
+} ets_insert_2_list_status;
+
+typedef struct {
+ ets_insert_2_list_status status;
+ BIF_RETTYPE destroy_return_value;
+ DbTable* tb;
+ void* continuation_state;
+ Binary* continuation_res_bin;
+} ets_insert_2_list_info;
+
+
+static ERTS_INLINE BIF_RETTYPE
+ets_cret_to_return_value(Process* p, int cret)
+{
+ switch (cret) {
+ case DB_ERROR_NONE_FALSE:
+ BIF_RET(am_false);
+ case DB_ERROR_NONE:
+ BIF_RET(am_true);
+ case DB_ERROR_SYSRES:
+ BIF_ERROR(p, SYSTEM_LIMIT);
+ default:
+ BIF_ERROR(p, BADARG);
+ }
+}
+
+/*
+ * > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
+ * > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
+ *
+ * Start of code section that Yielding C Fun (YCF) transforms
+ *
+ * The functions within #idef YCF_FUNCTIONS below are not called directly.
+ * YCF generates yieldable versions of these functions before "erl_db.c" is
+ * compiled. These generated functions are placed in the file
+ * "erl_db_insert_list.ycf.h" which is included below. The generation of
+ * "erl_db_insert_list.ycf.h" is defined in
+ * "$ERL_TOP/erts/emulator/Makefile.in". See
+ * "$ERL_TOP/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md"
+ * for more information about YCF.
+ *
+ * > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
+ * > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
+ */
+
+/*
+ * The LOCAL_VARIABLE macro is a trick to create a local variable that does not
+ * get renamed by YCF.
+ * Such variables will not retain their values over yields. Beware!
+ *
+ * I use this as a workaround for a limitation/bug in YCF. It does not do
+ * proper variable name substitution in expressions passed as argument to
+ * YCF_CONSUME_REDS(Expr).
+ */
+#define LOCAL_VARIABLE(TYPE, NAME) TYPE NAME
+
+#ifdef YCF_FUNCTIONS
+static long ets_insert_2_list_check(int keypos, Eterm list)
+{
+ Eterm lst = THE_NON_VALUE;
+ long i = 0;
+ for (lst = list; is_list(lst); lst = CDR(list_val(lst))) {
+ i++;
+ if (is_not_tuple(CAR(list_val(lst))) ||
+ (arityval(*tuple_val(CAR(list_val(lst)))) < keypos)) {
+ return -1;
+ }
+ }
+ if (lst != NIL) {
+ return -1;
+ }
+ return i;
+}
+
+static int ets_insert_new_2_list_has_member(DbTable* tb, Eterm list)
+{
+ Eterm lst;
+ Eterm lookup_ret;
+ DbTableMethod* meth = tb->common.meth;
+ for (lst = list; is_list(lst); lst = CDR(list_val(lst))) {
+ meth->db_member(tb,
+ TERM_GETKEY(tb,CAR(list_val(lst))),
+ &lookup_ret);
+ if (lookup_ret != am_false) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int ets_insert_2_list_from_p_heap(DbTable* tb, Eterm list)
+{
+ Eterm lst;
+ DbTableMethod* meth = tb->common.meth;
+ int cret = DB_ERROR_NONE;
+ for (lst = list; is_list(lst); lst = CDR(list_val(lst))) {
+ LOCAL_VARIABLE(SWord, consumed_reds);
+ consumed_reds = 1;
+ cret = meth->db_put(tb, CAR(list_val(lst)), 0, &consumed_reds);
+ if (cret != DB_ERROR_NONE)
+ return cret;
+ YCF_CONSUME_REDS(consumed_reds);
+ }
+ return DB_ERROR_NONE;
+}
+#endif /* YCF_FUNCTIONS */
+
+/* This function is called both as is, and as YCF transformed. */
+static void ets_insert_2_list_destroy_copied_dbterms(DbTableMethod* meth,
+ int compressed,
+ void* db_term_list)
+{
+ void* lst = db_term_list;
+ void* term = NULL;
+ while (lst != NULL) {
+ term = meth->db_dbterm_list_remove_first(&lst);
+ meth->db_free_dbterm(compressed, term);
+ }
+}
+
+#ifdef YCF_FUNCTIONS
+static void* ets_insert_2_list_copy_term_list(DbTableMethod* meth,
+ int compress,
+ int keypos,
+ Eterm list)
+{
+ void* db_term_list = NULL;
+ void *term;
+ Eterm lst;
+ for (lst = list; is_list(lst); lst = CDR(list_val(lst))) {
+ term = meth->db_eterm_to_dbterm(compress,
+ keypos,
+ CAR(list_val(lst)));
+ if (db_term_list != NULL) {
+ db_term_list =
+ meth->db_dbterm_list_prepend(db_term_list,
+ term);
+ } else {
+ db_term_list = term;
+ }
+ }
+
+ return db_term_list;
+
+ /* The following code will be executed if the calling process is
+ killed in the middle of the for loop above*/
+ YCF_SPECIAL_CODE_START(ON_DESTROY_STATE); {
+ ets_insert_2_list_destroy_copied_dbterms(meth,
+ compress,
+ db_term_list);
+ } YCF_SPECIAL_CODE_END();
+}
+
+static int ets_insert_new_2_dbterm_list_has_member(DbTable* tb, void* db_term_list)
+{
+ Eterm lookup_ret;
+ DbTableMethod* meth = tb->common.meth;
+ void* lst = db_term_list;
+ void* term = NULL;
+ Eterm key;
+ while (lst != NULL) {
+ term = meth->db_dbterm_list_remove_first(&lst);
+ key = meth->db_get_dbterm_key(tb, term);
+ meth->db_member(tb, key, &lookup_ret);
+ if (lookup_ret != am_false) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void ets_insert_2_list_insert_db_term_list(DbTable* tb,
+ void* list)
+{
+ void* lst = list;
+ void* term = NULL;
+ DbTableMethod* meth = tb->common.meth;
+ do {
+ LOCAL_VARIABLE(SWord, consumed_reds);
+ consumed_reds = 1;
+ term = meth->db_dbterm_list_remove_first(&lst);
+ meth->db_put_dbterm(tb, term, 0, &consumed_reds);
+ YCF_CONSUME_REDS(consumed_reds);
+ } while (lst != NULL);
+ return;
+}
+
+static void ets_insert_2_list_lock_tbl(Eterm table_id,
+ Process* p,
+ Uint bif_ix,
+ ets_insert_2_list_status on_success_status)
+{
+ BIF_RETTYPE fail_ret;
+ DbTable* tb;
+ ets_insert_2_list_info *ctx;
+ do {
+ fail_ret = db_get_table_or_fail_return(&tb,
+ table_id,
+ DB_WRITE,
+ LCK_WRITE,
+ bif_ix,
+ p);
+ ctx = YCF_GET_EXTRA_CONTEXT();
+ if (tb == NULL) {
+ if (p->freason == TRAP) {
+ ctx->status = ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK;
+ } else {
+ ctx->status = ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK_DESTROY;
+ ctx->destroy_return_value = fail_ret;
+ }
+ YCF_YIELD();
+ } else {
+ ctx->status = on_success_status;
+ ASSERT(DB_LOCK_FREE(tb) || erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock));
+ ASSERT(!(tb->common.status & DB_DELETE));
+ }
+ } while (tb == NULL);
+}
+#endif /* YCF_FUNCTIONS */
+
+static ERTS_INLINE int can_insert_without_yield(Uint32 tb_type,
+ long list_len,
+ long reds_left)
+{
+ if (tb_type & DB_BAG) {
+ /* Bag inserts can be really bad and we don't know how much searching
+ * for duplicates we will do */
+ return 0;
+ }
+ else {
+ return list_len <= reds_left;
+ }
+}
+
+#ifdef YCF_FUNCTIONS
+static BIF_RETTYPE ets_insert_2_list(Process* p,
+ Eterm table_id,
+ DbTable *tb,
+ Eterm list,
+ int is_insert_new)
+{
+ int cret = DB_ERROR_NONE;
+ void* db_term_list = NULL; /* OBS: memory managements depends on that
+ db_term_list is initialized to NULL */
+ DbTableMethod* meth = tb->common.meth;
+ int compressed = tb->common.compress;
+ int keypos = tb->common.keypos;
+ Uint32 tb_type = tb->common.type;
+ Uint bif_ix = (is_insert_new ? BIF_ets_insert_new_2 : BIF_ets_insert_2);
+ long list_len;
+ /* tb should not be accessed after this point unless the table
+ lock is held as the table can get deleted while the function is
+ yielding */
+ list_len = ets_insert_2_list_check(keypos, list);
+ if (list_len < 0) {
+ return ets_cret_to_return_value(p, DB_ERROR_BADITEM);
+ }
+ if (can_insert_without_yield(tb_type, list_len, YCF_NR_OF_REDS_LEFT())) {
+ long reds_boost;
+ /* There is enough reductions left to do the inserts directly
+ from the heap without yielding */
+ ets_insert_2_list_lock_tbl(table_id, p, bif_ix, ETS_INSERT_2_LIST_PROCESS_LOCAL);
+ /* Ensure that we will not yield while inserting from heap */
+ reds_boost = YCF_MAX_NR_OF_REDS - YCF_NR_OF_REDS_LEFT();
+ YCF_SET_NR_OF_REDS_LEFT(YCF_MAX_NR_OF_REDS);
+ if (is_insert_new) {
+ if (ets_insert_new_2_list_has_member(tb, list)) {
+ cret = DB_ERROR_NONE_FALSE;
+ } else {
+ cret = ets_insert_2_list_from_p_heap(tb, list);
+ }
+ } else {
+ cret = ets_insert_2_list_from_p_heap(tb, list);
+ }
+ db_unlock(tb, LCK_WRITE);
+ YCF_SET_NR_OF_REDS_LEFT(YCF_NR_OF_REDS_LEFT() - reds_boost);
+ return ets_cret_to_return_value(p, cret);
+ }
+ /* Copy term list from heap so that other processes can help */
+ db_term_list =
+ ets_insert_2_list_copy_term_list(meth, compressed, keypos, list);
+ /* Lock table */
+ ets_insert_2_list_lock_tbl(table_id, p, bif_ix, ETS_INSERT_2_LIST_GLOBAL);
+ /* The operation must complete after this point */
+ if (is_insert_new) {
+ if (ets_insert_new_2_dbterm_list_has_member(tb, db_term_list)) {
+ ets_insert_2_list_destroy_copied_dbterms(meth,
+ compressed,
+ db_term_list);
+ cret = DB_ERROR_NONE_FALSE;
+ } else {
+ ets_insert_2_list_insert_db_term_list(tb, db_term_list);
+ }
+ } else {
+ ets_insert_2_list_insert_db_term_list(tb, db_term_list);
+ }
+ if (tb->common.continuation != NULL) {
+ /* Uninstall the continuation from the table struct */
+ tb->common.continuation = NULL;
+ if (is_insert_new) {
+ int* result_ptr =
+ ERTS_MAGIC_BIN_DATA(tb->common.continuation_res_bin);
+ *result_ptr = cret;
+ erts_bin_release(tb->common.continuation_res_bin);
+ }
+ tb->common.status |= tb->common.type & (DB_PRIVATE|DB_PROTECTED|DB_PUBLIC);
+ tb->common.status &= ~DB_BUSY;
+ erts_atomic_set_relb(&tb->common.continuation_state, (Sint)NULL);
+ }
+
+ return ets_cret_to_return_value(NULL, cret);
+
+ /* The following code will be executed if the initiating process
+ is killed before an ets_insert_2_list_lock_tbl call has
+ succeeded */
+ YCF_SPECIAL_CODE_START(ON_DESTROY_STATE); {
+ ets_insert_2_list_destroy_copied_dbterms(meth,
+ compressed,
+ db_term_list);
+ } YCF_SPECIAL_CODE_END();
+}
+#endif /* YCF_FUNCTIONS */
+
+/*
+ * < < < < < < < < < < < < < < < < < < < < < < < < < < < < < <
+ * < < < < < < < < < < < < < < < < < < < < < < < < < < < < < <
+ *
+ * End of code section that Yielding C Fun (YCF) transforms
+ *
+ * < < < < < < < < < < < < < < < < < < < < < < < < < < < < < <
+ * < < < < < < < < < < < < < < < < < < < < < < < < < < < < < <
+ */
+#include "erl_db_insert_list.ycf.h"
+
+static void* ets_insert_2_yield_alloc(size_t size, void* ctx)
+{
+ (void)ctx;
+ return erts_alloc(ERTS_ALC_T_ETS_I_LST_TRAP, size);
+}
+
+static void ets_insert_2_yield_free(void* data, void* ctx)
+{
+ (void)ctx;
+ erts_free(ERTS_ALC_T_ETS_I_LST_TRAP, data);
+}
+
+static int ets_insert_2_list_yield_dtor(Binary* bin)
+{
+ ets_insert_2_list_info* ctx = ERTS_MAGIC_BIN_DATA(bin);
+ if (ctx->status != ETS_INSERT_2_LIST_GLOBAL &&
+ ctx->continuation_state != NULL) {
+ /* The operation has not been committed to the table and has
+ not completed*/
+ ets_insert_2_list_ycf_gen_destroy(ctx->continuation_state);
+ }
+ return 1;
+}
+
+static void ets_insert_2_list_continuation(long *reds_ptr,
+ void** state,
+ void* extra_context)
+{
+ ets_insert_2_list_ycf_gen_continue(reds_ptr, state, extra_context);
+}
+
+static int db_insert_new_2_res_bin_dtor(Binary *context_bin)
+{
+ (void)context_bin;
+ return 1;
+}
+
+#define ITERATIONS_PER_RED 8
+
+static BIF_RETTYPE ets_insert_2_list_driver(Process* p,
+ Eterm tid,
+ Eterm list,
+ int is_insert_new) {
+ const long reds = ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(p);
+ long nr_of_reductions = DBG_RANDOM_REDS(reds, (Uint)&p);
+ const long init_reds = nr_of_reductions;
+ ets_insert_2_list_info* ctx = NULL;
+ ets_insert_2_list_info ictx;
+ BIF_RETTYPE ret = THE_NON_VALUE;
+ Eterm state_mref = list;
+ Uint bix = (is_insert_new ? BIF_ets_insert_new_2 : BIF_ets_insert_2);
+ if (is_internal_magic_ref(state_mref)) {
+ Binary* state_bin = erts_magic_ref2bin(state_mref);
+ if (ERTS_MAGIC_BIN_DESTRUCTOR(state_bin) != ets_insert_2_list_yield_dtor) {
+ BIF_ERROR(p, BADARG);
+ }
+ /* Continue a trapped call */
+ erts_set_gc_state(p, 1);
+ ctx = ERTS_MAGIC_BIN_DATA(state_bin);
+ if (ctx->status == ETS_INSERT_2_LIST_GLOBAL) {
+ /* An operation that can be helped by other operations is
+ handled here */
+ Uint freason__;
+ int cret = DB_ERROR_NONE;
+ DbTable* tb;
+ /* First check if another process has completed the
+ operation without acquiring the lock */
+ if (NULL == (tb = db_get_table(p, tid, DB_READ_TBL_STRUCT, NOLCK_ACCESS, &freason__))) {
+ if (freason__ == TRAP){
+ erts_set_gc_state(p, 0);
+ return db_bif_fail(p, freason__, bix, NULL);
+ }
+ }
+ if (tb != NULL &&
+ (void*)erts_atomic_read_acqb(&tb->common.continuation_state) ==
+ ctx->continuation_state) {
+ /* The lock has to be taken to complete the operation */
+ if (NULL == (tb = db_get_table(p, tid, DB_WRITE, LCK_WRITE, &freason__))) {
+ if (freason__ == TRAP){
+ erts_set_gc_state(p, 0);
+ return db_bif_fail(p, freason__, bix, NULL);
+ }
+ }
+ /* Must be done since the db_get_table call did not trap */
+ if (tb != NULL) {
+ db_unlock(tb, LCK_WRITE);
+ }
+ }
+ if (is_insert_new) {
+ int* res = ERTS_MAGIC_BIN_DATA(ctx->continuation_res_bin);
+ cret = *res;
+ }
+ return ets_cret_to_return_value(NULL, cret);
+ } else {
+ ret = ets_insert_2_list_ycf_gen_continue(&nr_of_reductions,
+ &ctx->continuation_state,
+ ctx);
+ }
+ } else {
+ /* Start call */
+ ictx.continuation_state = NULL;
+ ictx.status = ETS_INSERT_2_LIST_PROCESS_LOCAL;
+ ictx.tb = NULL;
+ ctx = &ictx;
+ DB_GET_TABLE(ctx->tb, tid, DB_READ_TBL_STRUCT, NOLCK_ACCESS, bix, NULL, p);
+ ret = ets_insert_2_list_ycf_gen_yielding(&nr_of_reductions,
+ &ctx->continuation_state,
+ ctx,
+ ets_insert_2_yield_alloc,
+ ets_insert_2_yield_free,
+ NULL,
+ 0,
+ NULL,
+ p,
+ tid,
+ ctx->tb,
+ list,
+ is_insert_new);
+ if (ctx->continuation_state != NULL) {
+ Binary* state_bin = erts_create_magic_binary(sizeof(ets_insert_2_list_info),
+ ets_insert_2_list_yield_dtor);
+ Eterm* hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE);
+ state_mref = erts_mk_magic_ref(&hp, &MSO(p), state_bin);
+ ctx = ERTS_MAGIC_BIN_DATA(state_bin);
+ *ctx = ictx;
+ }
+ }
+ BUMP_REDS(p, (init_reds - nr_of_reductions) / ITERATIONS_PER_RED);
+ if (ctx->status == ETS_INSERT_2_LIST_GLOBAL &&
+ ctx->continuation_state != NULL &&
+ ctx->tb->common.continuation == NULL) {
+ /* Install the continuation in the table structure so other
+ threads can help */
+ if (is_insert_new) {
+ Binary* bin =
+ erts_create_magic_binary(sizeof(int),
+ db_insert_new_2_res_bin_dtor);
+ Eterm* hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE);
+ erts_mk_magic_ref(&hp, &MSO(p), bin);
+ erts_refc_inctest(&bin->intern.refc, 2);
+ ctx->tb->common.continuation_res_bin = bin;
+ ctx->continuation_res_bin = bin;
+ }
+ ctx->tb->common.continuation = ets_insert_2_list_continuation;
+ ctx->tb->common.status &= ~(DB_PRIVATE|DB_PROTECTED|DB_PUBLIC);
+ ctx->tb->common.status |= DB_BUSY;
+ erts_atomic_set_relb(&ctx->tb->common.continuation_state,
+ (Sint)ctx->continuation_state);
+ }
+ if (ctx->status == ETS_INSERT_2_LIST_FAILED_TO_GET_LOCK_DESTROY) {
+ return ctx->destroy_return_value;
+ }
+ if (ctx->status == ETS_INSERT_2_LIST_GLOBAL) {
+ db_unlock(ctx->tb, LCK_WRITE);
+ }
+ if (ctx->continuation_state != NULL) {
+ erts_set_gc_state(p, 0);
+ BIF_TRAP2(&bif_trap_export[bix], p, tid, state_mref);
+ }
+ return ret;
+}
/*
** The put BIF
@@ -1390,59 +1948,42 @@ BIF_RETTYPE ets_insert_2(BIF_ALIST_2)
{
DbTable* tb;
int cret = DB_ERROR_NONE;
- Eterm lst;
+ Eterm insert_term;
DbTableMethod* meth;
- db_lock_kind_t kind;
-
+ SWord consumed_reds = 0;
CHECK_TABLES();
-
- /* Write lock table if more than one object to keep atomicity */
- kind = ((is_list(BIF_ARG_2) && CDR(list_val(BIF_ARG_2)) != NIL)
- ? LCK_WRITE : LCK_WRITE_REC);
-
- DB_BIF_GET_TABLE(tb, DB_WRITE, kind, BIF_ets_insert_2);
-
if (BIF_ARG_2 == NIL) {
- db_unlock(tb, kind);
+ /* Check that the table exists */
+ DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_insert_2);
+ db_unlock(tb, LCK_WRITE_REC);
BIF_RET(am_true);
- }
- meth = tb->common.meth;
- if (is_list(BIF_ARG_2)) {
- for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) {
- if (is_not_tuple(CAR(list_val(lst))) ||
- (arityval(*tuple_val(CAR(list_val(lst)))) < tb->common.keypos)) {
- goto badarg;
- }
- }
- if (lst != NIL) {
- goto badarg;
- }
- for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) {
- cret = meth->db_put(tb, CAR(list_val(lst)), 0);
- if (cret != DB_ERROR_NONE)
- break;
- }
+ } if ((is_list(BIF_ARG_2) && CDR(list_val(BIF_ARG_2)) != NIL) ||
+ is_internal_magic_ref(BIF_ARG_2)) {
+ /* Handle list case */
+ return ets_insert_2_list_driver(BIF_P,
+ BIF_ARG_1,
+ BIF_ARG_2,
+ 0);
+ } else if (is_list(BIF_ARG_2)) {
+ insert_term = CAR(list_val(BIF_ARG_2));
} else {
- if (is_not_tuple(BIF_ARG_2) ||
- (arityval(*tuple_val(BIF_ARG_2)) < tb->common.keypos)) {
- goto badarg;
- }
- cret = meth->db_put(tb, BIF_ARG_2, 0);
+ insert_term = BIF_ARG_2;
}
- db_unlock(tb, kind);
-
- switch (cret) {
- case DB_ERROR_NONE:
- BIF_RET(am_true);
- case DB_ERROR_SYSRES:
- BIF_ERROR(BIF_P, SYSTEM_LIMIT);
- default:
- BIF_ERROR(BIF_P, BADARG);
+ DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_insert_2);
+
+ meth = tb->common.meth;
+ if (is_not_tuple(insert_term) ||
+ (arityval(*tuple_val(insert_term)) < tb->common.keypos)) {
+ db_unlock(tb, LCK_WRITE_REC);
+ BIF_ERROR(BIF_P, BADARG);
}
- badarg:
- db_unlock(tb, kind);
- BIF_ERROR(BIF_P, BADARG);
+ cret = meth->db_put(tb, insert_term, 0, &consumed_reds);
+
+ db_unlock(tb, LCK_WRITE_REC);
+
+ BUMP_REDS(BIF_P, consumed_reds / ITERATIONS_PER_RED);
+ return ets_cret_to_return_value(BIF_P, cret);
}
@@ -1456,69 +1997,40 @@ BIF_RETTYPE ets_insert_new_2(BIF_ALIST_2)
Eterm ret = am_true;
Eterm obj;
db_lock_kind_t kind;
-
+ SWord consumed_reds = 0;
CHECK_TABLES();
- if (is_list(BIF_ARG_2)) {
- if (CDR(list_val(BIF_ARG_2)) != NIL) {
- Eterm lst;
- Eterm lookup_ret;
- DbTableMethod* meth;
-
- /* More than one object, use LCK_WRITE to keep atomicity */
- kind = LCK_WRITE;
- DB_BIF_GET_TABLE(tb, DB_WRITE, kind, BIF_ets_insert_new_2);
-
- meth = tb->common.meth;
- for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) {
- if (is_not_tuple(CAR(list_val(lst)))
- || (arityval(*tuple_val(CAR(list_val(lst))))
- < tb->common.keypos)) {
- goto badarg;
- }
- }
- if (lst != NIL) {
- goto badarg;
- }
- for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) {
- cret = meth->db_member(tb, TERM_GETKEY(tb,CAR(list_val(lst))),
- &lookup_ret);
- if ((cret != DB_ERROR_NONE) || (lookup_ret != am_false)) {
- ret = am_false;
- goto done;
- }
- }
-
- for (lst = BIF_ARG_2; is_list(lst); lst = CDR(list_val(lst))) {
- cret = meth->db_put(tb,CAR(list_val(lst)), 0);
- if (cret != DB_ERROR_NONE)
- break;
- }
- goto done;
- }
- obj = CAR(list_val(BIF_ARG_2));
- }
- else {
- obj = BIF_ARG_2;
+ if (BIF_ARG_2 == NIL) {
+ /* Check that the table exists */
+ DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_insert_2);
+ db_unlock(tb, LCK_WRITE_REC);
+ BIF_RET(am_true);
+ } if ((is_list(BIF_ARG_2) && CDR(list_val(BIF_ARG_2)) != NIL) ||
+ is_internal_magic_ref(BIF_ARG_2)) {
+ /* Handle list case */
+ return ets_insert_2_list_driver(BIF_P, BIF_ARG_1, BIF_ARG_2, 1);
+ } else if (is_list(BIF_ARG_2)) {
+ obj = CAR(list_val(BIF_ARG_2));
+ } else {
+ obj = BIF_ARG_2;
}
- /* Only one object (or NIL)
- */
+
+ /* Only one object */
kind = LCK_WRITE_REC;
DB_BIF_GET_TABLE(tb, DB_WRITE, kind, BIF_ets_insert_new_2);
- if (BIF_ARG_2 == NIL) {
- db_unlock(tb, kind);
- BIF_RET(am_true);
- }
if (is_not_tuple(obj)
|| (arityval(*tuple_val(obj)) < tb->common.keypos)) {
- goto badarg;
+ db_unlock(tb, kind);
+ BIF_ERROR(BIF_P, BADARG);
}
cret = tb->common.meth->db_put(tb, obj,
- 1); /* key_clash_fail */
+ 1, /* key_clash_fail */
+ &consumed_reds);
-done:
db_unlock(tb, kind);
+
+ BUMP_REDS(BIF_P, consumed_reds / ITERATIONS_PER_RED);
switch (cret) {
case DB_ERROR_NONE:
BIF_RET(ret);
@@ -1529,9 +2041,6 @@ done:
default:
BIF_ERROR(BIF_P, BADARG);
}
- badarg:
- db_unlock(tb, kind);
- BIF_ERROR(BIF_P, BADARG);
}
/*
@@ -1643,6 +2152,8 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2)
Sint keypos;
int is_named, is_compressed;
int is_fine_locked, frequent_read;
+ int is_decentralized_counters;
+ int is_decentralized_counters_option;
int cret;
DbTableMethod* meth;
@@ -1658,6 +2169,8 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2)
is_named = 0;
is_fine_locked = 0;
frequent_read = 0;
+ is_decentralized_counters = 0;
+ is_decentralized_counters_option = -1;
heir = am_none;
heir_data = (UWord) am_undefined;
is_compressed = erts_ets_always_compress;
@@ -1674,6 +2187,7 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2)
status &= ~(DB_SET | DB_BAG | DB_ORDERED_SET | DB_CA_ORDERED_SET);
}
else if (val == am_ordered_set) {
+ is_decentralized_counters = 1;
status |= DB_ORDERED_SET;
status &= ~(DB_SET | DB_BAG | DB_DUPLICATE_BAG | DB_CA_ORDERED_SET);
}
@@ -1699,12 +2213,18 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2)
} else if (tp[2] == am_false) {
frequent_read = 0;
} else break;
-
}
else if (tp[1] == am_heir && tp[2] == am_none) {
heir = am_none;
heir_data = am_undefined;
}
+ else if (tp[1] == am_decentralized_counters) {
+ if (tp[2] == am_true) {
+ is_decentralized_counters_option = 1;
+ } else if (tp[2] == am_false) {
+ is_decentralized_counters_option = 0;
+ } else break;
+ }
else break;
}
else if (arityval(tp[0]) == 3 && tp[1] == am_heir
@@ -1738,6 +2258,9 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2)
if (is_not_nil(list)) { /* bad opt or not a well formed list */
BIF_ERROR(BIF_P, BADARG);
}
+ if (-1 != is_decentralized_counters_option) {
+ is_decentralized_counters = is_decentralized_counters_option;
+ }
if (IS_TREE_TABLE(status) && is_fine_locked && !(status & DB_PRIVATE)) {
meth = &db_catree;
status |= DB_CA_ORDERED_SET;
@@ -1762,27 +2285,30 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2)
/* we create table outside any table lock
* and take the unusal cost of destroy table if it
- * fails to find a slot
+ * fails to find a slot
*/
{
DbTable init_tb;
- erts_flxctr_init(&init_tb.common.counters, 0, 2, ERTS_ALC_T_DB_TABLE);
+ erts_flxctr_init(&init_tb.common.counters, 0, 2, ERTS_ALC_T_ETS_CTRS);
tb = (DbTable*) erts_db_alloc(ERTS_ALC_T_DB_TABLE,
&init_tb, sizeof(DbTable));
erts_flxctr_init(&tb->common.counters,
- status & DB_CA_ORDERED_SET,
+ (status & DB_FINE_LOCKED) && is_decentralized_counters,
2,
- ERTS_ALC_T_DB_TABLE);
+ ERTS_ALC_T_ETS_CTRS);
erts_flxctr_add(&tb->common.counters,
ERTS_DB_TABLE_MEM_COUNTER_ID,
- DB_GET_APPROX_MEM_CONSUMED(&init_tb));
+ DB_GET_APPROX_MEM_CONSUMED(&init_tb) +
+ erts_flxctr_nr_of_allocated_bytes(&tb->common.counters));
}
tb->common.meth = meth;
tb->common.the_name = BIF_ARG_1;
- tb->common.status = status;
+ tb->common.status = status;
tb->common.type = status;
/* Note, 'type' is *read only* from now on... */
+ tb->common.continuation = NULL;
+ erts_atomic_set_nob(&tb->common.continuation_state, (Sint)NULL);
erts_refc_init(&tb->common.fix_count, 0);
db_init_lock(tb, status & (DB_FINE_LOCKED|DB_FREQ_READ));
tb->common.keypos = keypos;
@@ -1819,7 +2345,7 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2)
table_dec_refc(tb, 0);
BIF_ERROR(BIF_P, BADARG);
}
-
+
BIF_P->flags |= F_USING_DB; /* So we can remove tb if p dies */
#ifdef HARDDEBUG
@@ -2194,7 +2720,7 @@ BIF_RETTYPE ets_internal_delete_all_2(BIF_ALIST_2)
tb->common.status |= DB_BUSY;
db_unlock(tb, LCK_WRITE);
BUMP_ALL_REDS(BIF_P);
- BIF_TRAP2(bif_export[BIF_ets_internal_delete_all_2], BIF_P,
+ BIF_TRAP2(&bif_trap_export[BIF_ets_internal_delete_all_2], BIF_P,
BIF_ARG_1, nitems_holder);
}
else {
@@ -2225,7 +2751,7 @@ static void delete_all_objects_continue(Process* p, DbTable* tb)
SWord initial_reds = ERTS_BIF_REDS_LEFT(p);
SWord reds = initial_reds;
- ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock) || DB_LOCK_FREE(tb));
+ ERTS_LC_ASSERT(DB_LOCK_FREE(tb) || erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock));
if ((tb->common.status & (DB_DELETE|DB_BUSY)) != DB_BUSY)
return;
@@ -3310,6 +3836,7 @@ BIF_RETTYPE ets_info_1(BIF_ALIST_1)
am_node, am_size, am_name, am_heir, am_owner, am_memory, am_compressed,
am_write_concurrency,
am_read_concurrency,
+ am_decentralized_counters,
am_id};
Eterm results[sizeof(fields)/sizeof(Eterm)];
DbTable* tb;
@@ -3358,7 +3885,7 @@ BIF_RETTYPE ets_info_1(BIF_ALIST_1)
BIF_RET(am_undefined);
}
if (rp == ERTS_PROC_LOCK_BUSY) {
- ERTS_BIF_YIELD1(bif_export[BIF_ets_info_1], BIF_P, BIF_ARG_1);
+ ERTS_BIF_YIELD1(&bif_trap_export[BIF_ets_info_1], BIF_P, BIF_ARG_1);
}
if ((tb = db_get_table(BIF_P, BIF_ARG_1, DB_INFO, LCK_READ)) == NULL
|| tb->common.owner != owner) {
@@ -3373,16 +3900,16 @@ BIF_RETTYPE ets_info_1(BIF_ALIST_1)
if (!is_ctrs_read_result_set) {
ErtsFlxCtrSnapshotResult res =
- erts_flxctr_snapshot(&tb->common.counters, ERTS_ALC_T_DB_TABLE, BIF_P);
+ erts_flxctr_snapshot(&tb->common.counters, ERTS_ALC_T_ETS_CTRS, BIF_P);
if (ERTS_FLXCTR_GET_RESULT_AFTER_TRAP == res.type) {
Eterm tuple;
db_unlock(tb, LCK_READ);
hp = HAlloc(BIF_P, 3);
tuple = TUPLE2(hp, res.trap_resume_state, table);
- BIF_TRAP1(bif_export[BIF_ets_info_1], BIF_P, tuple);
+ BIF_TRAP1(&bif_trap_export[BIF_ets_info_1], BIF_P, tuple);
} else if (res.type == ERTS_FLXCTR_TRY_AGAIN_AFTER_TRAP) {
db_unlock(tb, LCK_READ);
- BIF_TRAP1(bif_export[BIF_ets_info_1], BIF_P, table);
+ BIF_TRAP1(&bif_trap_export[BIF_ets_info_1], BIF_P, table);
} else {
size = res.result[ERTS_DB_TABLE_NITEMS_COUNTER_ID];
memory = res.result[ERTS_DB_TABLE_MEM_COUNTER_ID];
@@ -3450,13 +3977,13 @@ BIF_RETTYPE ets_info_2(BIF_ALIST_2)
}
if (BIF_ARG_2 == am_size || BIF_ARG_2 == am_memory) {
ErtsFlxCtrSnapshotResult res =
- erts_flxctr_snapshot(&tb->common.counters, ERTS_ALC_T_DB_TABLE, BIF_P);
+ erts_flxctr_snapshot(&tb->common.counters, ERTS_ALC_T_ETS_CTRS, BIF_P);
if (ERTS_FLXCTR_GET_RESULT_AFTER_TRAP == res.type) {
db_unlock(tb, LCK_READ);
- BIF_TRAP2(bif_export[BIF_ets_info_2], BIF_P, res.trap_resume_state, BIF_ARG_2);
+ BIF_TRAP2(&bif_trap_export[BIF_ets_info_2], BIF_P, res.trap_resume_state, BIF_ARG_2);
} else if (res.type == ERTS_FLXCTR_TRY_AGAIN_AFTER_TRAP) {
db_unlock(tb, LCK_READ);
- BIF_TRAP2(bif_export[BIF_ets_info_2], BIF_P, BIF_ARG_1, BIF_ARG_2);
+ BIF_TRAP2(&bif_trap_export[BIF_ets_info_2], BIF_P, BIF_ARG_1, BIF_ARG_2);
} else if (BIF_ARG_2 == am_size) {
ret = erts_make_integer(res.result[ERTS_DB_TABLE_NITEMS_COUNTER_ID], BIF_P);
} else { /* BIF_ARG_2 == am_memory */
@@ -3486,10 +4013,11 @@ BIF_RETTYPE ets_is_compiled_ms_1(BIF_ALIST_1)
BIF_RETTYPE ets_match_spec_compile_1(BIF_ALIST_1)
{
- Binary *mp = db_match_set_compile(BIF_P, BIF_ARG_1, DCOMP_TABLE);
+ Uint freason;
+ Binary *mp = db_match_set_compile(BIF_P, BIF_ARG_1, DCOMP_TABLE, &freason);
Eterm *hp;
if (mp == NULL) {
- BIF_ERROR(BIF_P, BADARG);
+ BIF_ERROR(BIF_P, freason);
}
hp = HAlloc(BIF_P, ERTS_MAGIC_REF_THING_SIZE);
@@ -3522,7 +4050,7 @@ BIF_RETTYPE ets_match_spec_run_r_3(BIF_ALIST_3)
for (lst = BIF_ARG_1; is_list(lst); lst = CDR(list_val(lst))) {
if (++i > CONTEXT_REDS) {
BUMP_ALL_REDS(BIF_P);
- BIF_TRAP3(bif_export[BIF_ets_match_spec_run_r_3],
+ BIF_TRAP3(&bif_trap_export[BIF_ets_match_spec_run_r_3],
BIF_P,lst,BIF_ARG_2,ret);
}
res = db_prog_match(BIF_P, BIF_P,
@@ -4189,7 +4717,7 @@ static SWord free_fixations_locked(Process* p, DbTable *tb)
{
struct free_fixations_ctx ctx;
- ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock) || DB_LOCK_FREE(tb));
+ ERTS_LC_ASSERT(DB_LOCK_FREE(tb) || erts_lc_rwmtx_is_rwlocked(&tb->common.rwlock));
ctx.p = p;
ctx.tb = tb;
@@ -4360,7 +4888,7 @@ static Eterm table_info(Process* p, DbTable* tb, Eterm What)
} else if (What == am_heir) {
ret = tb->common.heir;
} else if (What == am_protection) {
- if (tb->common.status & DB_PRIVATE)
+ if (tb->common.status & DB_PRIVATE)
ret = am_private;
else if (tb->common.status & DB_PROTECTED)
ret = am_protected;
@@ -4382,6 +4910,8 @@ static Eterm table_info(Process* p, DbTable* tb, Eterm What)
ret = tb->common.compress ? am_true : am_false;
} else if (What == am_id) {
ret = make_tid(p, tb);
+ } else if (What == am_decentralized_counters) {
+ ret = tb->common.counters.is_decentralized ? am_true : am_false;
}
/*
diff --git a/erts/emulator/beam/erl_db.h b/erts/emulator/beam/erl_db.h
index 7dd289626b..139e3311b7 100644
--- a/erts/emulator/beam/erl_db.h
+++ b/erts/emulator/beam/erl_db.h
@@ -159,13 +159,15 @@ extern erts_aint_t erts_ets_dbg_force_trap;
*/
#define ERTS_DB_ALC_MEM_UPDATE_(TAB, FREE_SZ, ALLOC_SZ) \
-do { \
- erts_aint_t sz__ = (((erts_aint_t) (ALLOC_SZ)) \
- - ((erts_aint_t) (FREE_SZ))); \
- ASSERT((TAB)); \
- erts_flxctr_add(&(TAB)->common.counters, \
- ERTS_DB_TABLE_MEM_COUNTER_ID, \
- sz__); \
+do { \
+ if ((TAB) != NULL) { \
+ erts_aint_t sz__ = (((erts_aint_t) (ALLOC_SZ)) \
+ - ((erts_aint_t) (FREE_SZ))); \
+ ASSERT((TAB)); \
+ erts_flxctr_add(&(TAB)->common.counters, \
+ ERTS_DB_TABLE_MEM_COUNTER_ID, \
+ sz__); \
+ } \
} while (0)
#define ERTS_ETS_MISC_MEM_ADD(SZ) \
@@ -310,7 +312,8 @@ erts_db_free(ErtsAlcType_t type, DbTable *tab, void *ptr, Uint size)
ASSERT(ptr != 0);
ASSERT(size == ERTS_ALC_DBG_BLK_SZ(ptr));
ERTS_DB_ALC_MEM_UPDATE_(tab, size, 0);
- ASSERT(((void *) tab) != ptr ||
+ ASSERT(tab == NULL ||
+ ((void *) tab) != ptr ||
tab->common.counters.is_decentralized ||
0 == erts_flxctr_read_centralized(&tab->common.counters,
ERTS_DB_TABLE_MEM_COUNTER_ID));
diff --git a/erts/emulator/beam/erl_db_catree.c b/erts/emulator/beam/erl_db_catree.c
index e7dc3ad8af..2e09c241dd 100644
--- a/erts/emulator/beam/erl_db_catree.c
+++ b/erts/emulator/beam/erl_db_catree.c
@@ -104,7 +104,8 @@ static int db_last_catree(Process *p, DbTable *tbl,
static int db_prev_catree(Process *p, DbTable *tbl,
Eterm key,
Eterm *ret);
-static int db_put_catree(DbTable *tbl, Eterm obj, int key_clash_fail);
+static int db_put_catree(DbTable *tbl, Eterm obj, int key_clash_fail,
+ SWord *consumed_reds_p);
static int db_get_catree(Process *p, DbTable *tbl,
Eterm key, Eterm *ret);
static int db_member_catree(DbTable *tbl, Eterm key, Eterm *ret);
@@ -160,6 +161,10 @@ db_lookup_dbterm_catree(Process *, DbTable *, Eterm key, Eterm obj,
DbUpdateHandle*);
static void db_finalize_dbterm_catree(int cret, DbUpdateHandle *);
static int db_get_binary_info_catree(Process*, DbTable*, Eterm key, Eterm *ret);
+static int db_put_dbterm_catree(DbTable* tbl,
+ void* obj,
+ int key_clash_fail,
+ SWord *consumed_reds_p);
static void split_catree(DbTableCATree *tb,
DbTableCATreeNode* ERTS_RESTRICT base,
@@ -213,6 +218,12 @@ DbTableMethod db_catree =
db_foreach_offheap_catree,
db_lookup_dbterm_catree,
db_finalize_dbterm_catree,
+ db_eterm_to_dbterm_tree_common,
+ db_dbterm_list_prepend_tree_common,
+ db_dbterm_list_remove_first_tree_common,
+ db_put_dbterm_catree,
+ db_free_dbterm_tree_common,
+ db_get_dbterm_key_tree_common,
db_get_binary_info_catree,
db_first_catree, /* raw_first same as first */
db_next_catree /* raw_next same as next */
@@ -1367,7 +1378,7 @@ static void split_catree(DbTableCATree *tb,
}
}
-/* @brief Free the entire catree and its sub-trees.
+/** @brief Free the entire catree and its sub-trees.
*
* @param reds Reductions to spend.
* @return Reductions left. Negative value if not done.
@@ -1464,7 +1475,7 @@ static SWord db_free_table_continue_catree(DbTable *tbl, SWord reds)
return reds;
}
-/* @brief Free all objects of a base node, but keep the base node.
+/** @brief Free all objects of a base node, but keep the base node.
*
* @param reds Reductions to spend.
* @return Reductions left. Negative value if not done.
@@ -1632,7 +1643,27 @@ static int db_prev_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
return result;
}
-static int db_put_catree(DbTable *tbl, Eterm obj, int key_clash_fail)
+static int db_put_dbterm_catree(DbTable* tbl,
+ void* obj,
+ int key_clash_fail,
+ SWord *consumed_reds_p)
+{
+ TreeDbTerm *value_to_insert = obj;
+ DbTableCATree *tb = &tbl->catree;
+ Eterm key = GETKEY(tb, value_to_insert->dbterm.tpl);
+ FindBaseNode fbn;
+ DbTableCATreeNode* node = find_wlock_valid_base_node(tb, key, &fbn);
+ int result = db_put_dbterm_tree_common(&tb->common,
+ &node->u.base.root,
+ value_to_insert,
+ key_clash_fail,
+ NULL);
+ wunlock_adapt_base_node(tb, node, fbn.parent, fbn.current_level);
+ return result;
+}
+
+static int db_put_catree(DbTable *tbl, Eterm obj, int key_clash_fail,
+ SWord *consumed_reds_p)
{
DbTableCATree *tb = &tbl->catree;
Eterm key = GETKEY(&tb->common, tuple_val(obj));
@@ -1776,7 +1807,7 @@ TreeDbTerm** catree_find_prev_root(CATreeRootIterator *iter, Eterm* keyp)
return catree_find_nextprev_root(iter, 0, keyp);
}
-/* @brief Find root of tree where object with smallest key of all larger than
+/** @brief Find root of tree where object with smallest key of all larger than
* partially bound key may reside. Can be used as a starting point for
* a reverse iteration with pb_key.
*
@@ -1829,7 +1860,7 @@ TreeDbTerm** catree_find_next_from_pb_key_root(Eterm pb_key,
}
}
-/* @brief Find root of tree where object with largest key of all smaller than
+/** @brief Find root of tree where object with largest key of all smaller than
* partially bound key may reside. Can be used as a starting point for
* a forward iteration with pb_key.
*
diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c
index 50648a760c..73cb58547b 100644
--- a/erts/emulator/beam/erl_db_hash.c
+++ b/erts/emulator/beam/erl_db_hash.c
@@ -85,19 +85,58 @@
#include "erl_db_hash.h"
-#define ADD_NITEMS(DB, TO_ADD) \
- erts_flxctr_add(&(DB)->common.counters, ERTS_DB_TABLE_NITEMS_COUNTER_ID, TO_ADD)
-#define INC_NITEMS(DB) \
- erts_flxctr_inc_read_centralized(&(DB)->common.counters, ERTS_DB_TABLE_NITEMS_COUNTER_ID)
-#define DEC_NITEMS(DB) \
- erts_flxctr_dec_read_centralized(&(DB)->common.counters, ERTS_DB_TABLE_NITEMS_COUNTER_ID)
+#define IS_DECENTRALIZED_CTRS(DB) ((DB)->common.counters.is_decentralized)
+
+#define NITEMS_ESTIMATE_FROM_LCK_CTR(LCK_CTR_P) \
+ (LCK_CTR_P->nitems <= 0 ? 1: LCK_CTR_P->nitems)
+
+#define NITEMS_ESTIMATE(DB, LCK_CTR, HASH) \
+ (IS_DECENTRALIZED_CTRS(DB) ? \
+ (DB_HASH_LOCK_CNT * \
+ (LCK_CTR != NULL ? \
+ NITEMS_ESTIMATE_FROM_LCK_CTR(LCK_CTR) : \
+ NITEMS_ESTIMATE_FROM_LCK_CTR(GET_LOCK_AND_CTR(DB, HASH)))) : \
+ erts_flxctr_read_centralized(&(DB)->common.counters, \
+ ERTS_DB_TABLE_NITEMS_COUNTER_ID))
+
+#define ADD_NITEMS(DB, LCK_CTR, HASH, TO_ADD) \
+ do { \
+ if (IS_DECENTRALIZED_CTRS(DB)) { \
+ if (LCK_CTR != NULL) { \
+ LCK_CTR->nitems += TO_ADD; \
+ } else { \
+ GET_LOCK_AND_CTR(DB,HASH)->nitems += TO_ADD; \
+ } \
+ } \
+ erts_flxctr_add(&(DB)->common.counters, ERTS_DB_TABLE_NITEMS_COUNTER_ID, TO_ADD); \
+ } while(0)
+#define INC_NITEMS(DB, LCK_CTR, HASH) \
+ do { \
+ if (IS_DECENTRALIZED_CTRS(DB)) { \
+ if (LCK_CTR != NULL) { \
+ LCK_CTR->nitems++; \
+ } else { \
+ GET_LOCK_AND_CTR(DB,HASH)->nitems++; \
+ } \
+ } \
+ erts_flxctr_inc(&(DB)->common.counters, ERTS_DB_TABLE_NITEMS_COUNTER_ID); \
+ } while(0)
+#define DEC_NITEMS(DB, LCK_CTR, HASH) \
+ do { \
+ if (IS_DECENTRALIZED_CTRS(DB)) { \
+ if (LCK_CTR != NULL) { \
+ LCK_CTR->nitems--; \
+ } else { \
+ GET_LOCK_AND_CTR(DB,HASH)->nitems--; \
+ } \
+ } \
+ erts_flxctr_dec(&(DB)->common.counters, ERTS_DB_TABLE_NITEMS_COUNTER_ID); \
+ } while(0)
#define RESET_NITEMS(DB) \
erts_flxctr_reset(&(DB)->common.counters, ERTS_DB_TABLE_NITEMS_COUNTER_ID)
-/*
- * The following symbols can be manipulated to "tune" the linear hash array
- */
+
#define GROW_LIMIT(NACTIVE) ((NACTIVE)*1)
-#define SHRINK_LIMIT(NACTIVE) ((NACTIVE) / 2)
+#define SHRINK_LIMIT(TB) erts_atomic_read_nob(&(TB)->shrink_limit)
/*
** We want the first mandatory segment to be small (to reduce minimal footprint)
@@ -129,14 +168,16 @@
: ((struct segment**) erts_atomic_read_nob(&(tb)->segtab)))
#endif
#define NACTIVE(tb) ((int)erts_atomic_read_nob(&(tb)->nactive))
-#define NITEMS(tb) \
- ((Sint)erts_flxctr_read_centralized(&(tb)->common.counters, \
- ERTS_DB_TABLE_NITEMS_COUNTER_ID))
#define SLOT_IX_TO_SEG_IX(i) (((i)+(EXT_SEGSZ-FIRST_SEGSZ)) >> EXT_SEGSZ_EXP)
#define BUCKET(tb, i) SEGTAB(tb)[SLOT_IX_TO_SEG_IX(i)]->buckets[(i) & EXT_SEGSZ_MASK]
+#ifdef DEBUG
+# define DBG_BUCKET_INACTIVE ((HashDbTerm*)0xdead5107)
+#endif
+
+
/*
* When deleting a table, the number of records to delete.
* Approximate number, because we must delete entire buckets.
@@ -224,7 +265,8 @@ static ERTS_INLINE int is_pseudo_deleted(HashDbTerm* p)
make_internal_hash(term, 0)) & MAX_HASH_MASK)
# define DB_HASH_LOCK_MASK (DB_HASH_LOCK_CNT-1)
-# define GET_LOCK(tb,hval) (&(tb)->locks->lck_vec[(hval) & DB_HASH_LOCK_MASK].lck)
+# define GET_LOCK(tb,hval) (&(tb)->locks->lck_vec[(hval) & DB_HASH_LOCK_MASK].lck_ctr.lck)
+# define GET_LOCK_AND_CTR(tb,hval) (&(tb)->locks->lck_vec[(hval) & DB_HASH_LOCK_MASK].lck_ctr)
# define GET_LOCK_MAYBE(tb,hval) ((tb)->common.is_thread_safe ? NULL : GET_LOCK(tb,hval))
/* Fine grained read lock */
@@ -252,6 +294,20 @@ static ERTS_INLINE erts_rwmtx_t* WLOCK_HASH(DbTableHash* tb, HashValue hval)
}
}
+/* Fine grained write lock */
+static ERTS_INLINE
+DbTableHashLockAndCounter* WLOCK_HASH_GET_LCK_AND_CTR(DbTableHash* tb, HashValue hval)
+{
+ if (tb->common.is_thread_safe) {
+ return NULL;
+ } else {
+ DbTableHashLockAndCounter* lck_ctr = GET_LOCK_AND_CTR(tb,hval);
+ ASSERT(tb->common.type & DB_FINE_LOCKED);
+ erts_rwmtx_rwlock(&lck_ctr->lck);
+ return lck_ctr;
+ }
+}
+
static ERTS_INLINE void RUNLOCK_HASH(erts_rwmtx_t* lck)
{
if (lck != NULL) {
@@ -266,6 +322,13 @@ static ERTS_INLINE void WUNLOCK_HASH(erts_rwmtx_t* lck)
}
}
+static ERTS_INLINE void WUNLOCK_HASH_LCK_CTR(DbTableHashLockAndCounter* lck_ctr)
+{
+ if (lck_ctr != NULL) {
+ erts_rwmtx_rwunlock(&lck_ctr->lck);
+ }
+}
+
#ifdef ERTS_ENABLE_LOCK_CHECK
# define IFN_EXCL(tb,cmd) (((tb)->common.is_thread_safe) || (cmd))
@@ -377,7 +440,7 @@ typedef int (*extra_match_validator_t)(int keypos, Eterm match, Eterm guard, Ete
*/
static struct ext_segtab* alloc_ext_segtab(DbTableHash* tb, unsigned seg_ix);
static void alloc_seg(DbTableHash *tb);
-static int free_seg(DbTableHash *tb, int free_records);
+static int free_seg(DbTableHash *tb);
static HashDbTerm* next_live(DbTableHash *tb, Uint *iptr, erts_rwmtx_t** lck_ptr,
HashDbTerm *list);
static HashDbTerm* search_list(DbTableHash* tb, Eterm key,
@@ -468,18 +531,24 @@ db_lookup_dbterm_hash(Process *p, DbTable *tbl, Eterm key, Eterm obj,
DbUpdateHandle* handle);
static void
db_finalize_dbterm_hash(int cret, DbUpdateHandle* handle);
+static void* db_eterm_to_dbterm_hash(int compress, int keypos, Eterm obj);
+static void* db_dbterm_list_prepend_hash(void* list, void* db_term);
+static void* db_dbterm_list_remove_first_hash(void** list);
+static int db_put_dbterm_hash(DbTable* tb,
+ void* obj,
+ int key_clash_fail,
+ SWord *consumed_reds_p);
+static void db_free_dbterm_hash(int compressed, void* obj);
+static Eterm db_get_dbterm_key_hash(DbTable* tb, void* db_term);
static int
db_get_binary_info_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret);
static int db_raw_first_hash(Process* p, DbTable *tbl, Eterm *ret);
static int db_raw_next_hash(Process* p, DbTable *tbl, Eterm key, Eterm *ret);
-static ERTS_INLINE void try_shrink(DbTableHash* tb)
+static ERTS_INLINE void try_shrink(DbTableHash* tb, Sint nitems)
{
- int nactive = NACTIVE(tb);
- int nitems = NITEMS(tb);
- if (nactive > FIRST_SEGSZ && nitems < SHRINK_LIMIT(nactive)
- && !IS_FIXED(tb)) {
+ if (nitems < SHRINK_LIMIT(tb) && !IS_FIXED(tb)) {
shrink(tb, nitems);
}
}
@@ -512,28 +581,55 @@ static ERTS_INLINE int has_key(DbTableHash* tb, HashDbTerm* b,
}
}
-static ERTS_INLINE HashDbTerm* new_dbterm(DbTableHash* tb, Eterm obj)
+static ERTS_INLINE HashDbTerm* new_dbterm_hash(DbTableCommon* tb, Eterm obj)
{
HashDbTerm* p;
- if (tb->common.compress) {
- p = db_store_term_comp(&tb->common, NULL, offsetof(HashDbTerm,dbterm), obj);
+ if (tb->compress) {
+ p = db_store_term_comp(tb, tb->keypos, NULL, offsetof(HashDbTerm,dbterm), obj);
}
else {
- p = db_store_term(&tb->common, NULL, offsetof(HashDbTerm,dbterm), obj);
+ p = db_store_term(tb, NULL, offsetof(HashDbTerm,dbterm), obj);
}
return p;
}
+/*
+ * This function only differ from new_dbterm_hash in that it does not
+ * adjust the memory size of a given table.
+ */
+static ERTS_INLINE HashDbTerm* new_dbterm_hash_no_tab(int compress, int keypos, Eterm obj)
+{
+ HashDbTerm* p;
+ if (compress) {
+ p = db_store_term_comp(NULL, keypos, NULL, offsetof(HashDbTerm,dbterm), obj);
+ } else {
+ p = db_store_term(NULL, NULL, offsetof(HashDbTerm,dbterm), obj);
+ }
+ return p;
+}
+
+static ERTS_INLINE HashDbTerm* new_dbterm(DbTableHash* tb, Eterm obj)
+{
+ return new_dbterm_hash(&tb->common, obj);
+}
+
static ERTS_INLINE HashDbTerm* replace_dbterm(DbTableHash* tb, HashDbTerm* old,
Eterm obj)
{
HashDbTerm* ret;
ASSERT(old != NULL);
if (tb->common.compress) {
- ret = db_store_term_comp(&tb->common, &(old->dbterm), offsetof(HashDbTerm,dbterm), obj);
+ ret = db_store_term_comp(&tb->common,
+ tb->common.keypos,
+ &(old->dbterm),
+ offsetof(HashDbTerm,dbterm),
+ obj);
}
else {
- ret = db_store_term(&tb->common, &(old->dbterm), offsetof(HashDbTerm,dbterm), obj);
+ ret = db_store_term(&tb->common,
+ &(old->dbterm),
+ offsetof(HashDbTerm,dbterm),
+ obj);
}
return ret;
}
@@ -575,6 +671,12 @@ DbTableMethod db_hash =
db_foreach_offheap_hash,
db_lookup_dbterm_hash,
db_finalize_dbterm_hash,
+ db_eterm_to_dbterm_hash,
+ db_dbterm_list_prepend_hash,
+ db_dbterm_list_remove_first_hash,
+ db_put_dbterm_hash,
+ db_free_dbterm_hash,
+ db_get_dbterm_key_hash,
db_get_binary_info_hash,
db_raw_first_hash,
db_raw_next_hash
@@ -693,6 +795,7 @@ int db_create_hash(Process *p, DbTable *tbl)
erts_atomic_init_nob(&tb->szm, FIRST_SEGSZ_MASK);
erts_atomic_init_nob(&tb->nactive, FIRST_SEGSZ);
+ erts_atomic_init_nob(&tb->shrink_limit, 0);
erts_atomic_init_nob(&tb->fixdel, (erts_aint_t)NULL);
erts_atomic_init_nob(&tb->segtab, (erts_aint_t)NULL);
SET_SEGTAB(tb, tb->first_segtab);
@@ -715,8 +818,9 @@ int db_create_hash(Process *p, DbTable *tbl)
(DbTable *) tb,
sizeof(DbTableHashFineLocks));
for (i=0; i<DB_HASH_LOCK_CNT; ++i) {
- erts_rwmtx_init_opt(&tb->locks->lck_vec[i].lck, &rwmtx_opt,
+ erts_rwmtx_init_opt(&tb->locks->lck_vec[i].lck_ctr.lck, &rwmtx_opt,
"db_hash_slot", tb->common.the_name, ERTS_LOCK_FLAGS_CATEGORY_DB);
+ tb->locks->lck_vec[i].lck_ctr.nitems = 0;
}
/* This important property is needed to guarantee the two buckets
* involved in a grow/shrink operation it protected by the same lock:
@@ -779,7 +883,7 @@ static int db_next_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
b = next_live(tb, &ix, &lck, b->next);
if (tb->common.status & (DB_BAG | DB_DUPLICATE_BAG)) {
while (b != 0) {
- if (!has_key(tb, b, key, hval) && !is_pseudo_deleted(b)) {
+ if (!has_key(tb, b, key, hval)) {
break;
}
b = next_live(tb, &ix, &lck, b->next);
@@ -789,13 +893,58 @@ static int db_next_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
*ret = am_EOT;
}
else {
+ ASSERT(!is_pseudo_deleted(b));
*ret = db_copy_key(p, tbl, &b->dbterm);
RUNLOCK_HASH(lck);
}
return DB_ERROR_NONE;
}
-int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail)
+struct tmp_uncomp_term {
+ Eterm term;
+ ErlOffHeap oh;
+ Eterm heap[1];
+};
+
+#define sizeof_tmp_uncomp_term(SZ) \
+ (offsetof(struct tmp_uncomp_term, heap) + (SZ)*sizeof(Eterm))
+
+static ERTS_INLINE void free_tmp_uncomp_term(DbTableCommon* tb,
+ struct tmp_uncomp_term* tmp)
+{
+ if (tmp) {
+ ASSERT(tb->compress);
+ erts_cleanup_offheap(&tmp->oh);
+ erts_free(ERTS_ALC_T_TMP, tmp);
+ }
+}
+
+static ERTS_INLINE int db_terms_eq(DbTableCommon* tb, DbTerm* a, DbTerm* b,
+ struct tmp_uncomp_term** a_tmp_p)
+{
+ if (!tb->compress) {
+ return EQ(make_tuple(a->tpl), make_tuple(b->tpl));
+ }
+ else {
+ struct tmp_uncomp_term* a_tmp = *a_tmp_p;
+ if (!a_tmp) {
+ Eterm* hp;
+ a_tmp = erts_alloc(ERTS_ALC_T_TMP, sizeof_tmp_uncomp_term(a->size));
+
+ a_tmp->oh.first = NULL;
+ hp = a_tmp->heap;
+ a_tmp->term = db_copy_from_comp(tb, a, &hp, &a_tmp->oh);
+ *a_tmp_p = a_tmp;
+ }
+
+ return db_eq_comp(tb, a_tmp->term, b);
+ }
+}
+
+static int db_put_dbterm_hash(DbTable* tbl,
+ void* ob,
+ int key_clash_fail,
+ SWord *consumed_reds_p)
{
DbTableHash *tb = &tbl->hash;
HashValue hval;
@@ -804,13 +953,130 @@ int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail)
HashDbTerm** bp;
HashDbTerm* b;
HashDbTerm* q;
- erts_rwmtx_t* lck;
+ DbTableHashLockAndCounter* lck_ctr;
int nitems;
int ret = DB_ERROR_NONE;
+ HashDbTerm *value_to_insert = ob;
+ Uint size_to_insert = db_term_size(tbl, value_to_insert, offsetof(HashDbTerm, dbterm));
+ ERTS_DB_ALC_MEM_UPDATE_(tbl, 0, size_to_insert);
+ key = GETKEY(tb, value_to_insert->dbterm.tpl);
+ hval = MAKE_HASH(key);
+ value_to_insert->hvalue = hval;
+ lck_ctr = WLOCK_HASH_GET_LCK_AND_CTR(tb, hval);
+ ix = hash_to_ix(tb, hval);
+ bp = &BUCKET(tb, ix);
+ b = *bp;
+
+ for (;;) {
+ if (b == NULL) {
+ goto Lnew;
+ }
+ if (has_key(tb,b,key,hval)) {
+ break;
+ }
+ bp = &b->next;
+ b = b->next;
+ }
+ /* Key found
+ */
+ if (tb->common.status & DB_SET) {
+ HashDbTerm* bnext = b->next;
+ if (is_pseudo_deleted(b)) {
+ INC_NITEMS(tb, lck_ctr, hval);
+ b->pseudo_deleted = 0;
+ }
+ else if (key_clash_fail) {
+ ret = DB_ERROR_BADKEY;
+ goto Ldone;
+ }
+ value_to_insert->pseudo_deleted = b->pseudo_deleted;
+ free_term(tb, b);
+ q = value_to_insert;
+ q->next = bnext;
+ ASSERT(q->hvalue == hval);
+ *bp = q;
+ goto Ldone;
+ }
+ else if (key_clash_fail) { /* && (DB_BAG || DB_DUPLICATE_BAG) */
+ q = b;
+ do {
+ if (!is_pseudo_deleted(q)) {
+ ret = DB_ERROR_BADKEY;
+ goto Ldone;
+ }
+ q = q->next;
+ }while (q != NULL && has_key(tb,q,key,hval));
+ }
+ else if (tb->common.status & DB_BAG) {
+ struct tmp_uncomp_term* tmp = NULL;
+ HashDbTerm** qp = bp;
+ q = b;
+ do {
+ if (db_terms_eq(&tb->common,
+ &value_to_insert->dbterm,
+ &q->dbterm,
+ &tmp)) {
+ if (is_pseudo_deleted(q)) {
+ INC_NITEMS(tb, lck_ctr, hval);
+ q->pseudo_deleted = 0;
+ ASSERT(q->hvalue == hval);
+ if (q != b) { /* must move to preserve key insertion order */
+ *qp = q->next;
+ q->next = b;
+ *bp = q;
+ }
+ }
+ free_term(tb, value_to_insert);
+ free_tmp_uncomp_term(&tb->common, tmp);
+ goto Ldone;
+ }
+ qp = &q->next;
+ q = *qp;
+ (*consumed_reds_p)++;
+ }while (q != NULL && has_key(tb,q,key,hval));
+ free_tmp_uncomp_term(&tb->common, tmp);
+ }
+ /*else DB_DUPLICATE_BAG */
+
+Lnew:
+ q = value_to_insert;
+ q->hvalue = hval;
+ q->pseudo_deleted = 0;
+ q->next = b;
+ *bp = q;
+ INC_NITEMS(tb, lck_ctr, hval);
+ nitems = NITEMS_ESTIMATE(tb, lck_ctr, hval);
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
+ {
+ int nactive = NACTIVE(tb);
+ if (nitems > GROW_LIMIT(nactive) && !IS_FIXED(tb)) {
+ grow(tb, nitems);
+ }
+ }
+ return DB_ERROR_NONE;
+
+Ldone:
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
+ return ret;
+}
+
+int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail,
+ SWord *consumed_reds_p)
+{
+ DbTableHash *tb = &tbl->hash;
+ HashValue hval;
+ int ix;
+ Eterm key;
+ HashDbTerm** bp;
+ HashDbTerm* b;
+ HashDbTerm* q;
+ DbTableHashLockAndCounter* lck_ctr;
+ Sint nitems;
+ int ret = DB_ERROR_NONE;
key = GETKEY(tb, tuple_val(obj));
hval = MAKE_HASH(key);
- lck = WLOCK_HASH(tb, hval);
+ lck_ctr = WLOCK_HASH_GET_LCK_AND_CTR(tb, hval);
ix = hash_to_ix(tb, hval);
bp = &BUCKET(tb, ix);
b = *bp;
@@ -830,7 +1096,7 @@ int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail)
if (tb->common.status & DB_SET) {
HashDbTerm* bnext = b->next;
if (is_pseudo_deleted(b)) {
- INC_NITEMS(tb);
+ INC_NITEMS(tb, lck_ctr, hval);
b->pseudo_deleted = 0;
}
else if (key_clash_fail) {
@@ -859,7 +1125,7 @@ int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail)
do {
if (db_eq(&tb->common,obj,&q->dbterm)) {
if (is_pseudo_deleted(q)) {
- INC_NITEMS(tb);
+ INC_NITEMS(tb, lck_ctr, hval);
q->pseudo_deleted = 0;
ASSERT(q->hvalue == hval);
if (q != b) { /* must move to preserve key insertion order */
@@ -871,8 +1137,10 @@ int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail)
goto Ldone;
}
qp = &q->next;
- q = *qp;
- }while (q != NULL && has_key(tb,q,key,hval));
+ q = *qp;
+ (*consumed_reds_p)++;
+ }while (q != NULL && has_key(tb,q,key,hval));
+
}
/*else DB_DUPLICATE_BAG */
@@ -882,10 +1150,11 @@ Lnew:
q->pseudo_deleted = 0;
q->next = b;
*bp = q;
- nitems = INC_NITEMS(tb);
- WUNLOCK_HASH(lck);
+ INC_NITEMS(tb, lck_ctr, hval);
+ nitems = NITEMS_ESTIMATE(tb, lck_ctr, hval);
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
{
- int nactive = NACTIVE(tb);
+ int nactive = NACTIVE(tb);
if (nitems > GROW_LIMIT(nactive) && !IS_FIXED(tb)) {
grow(tb, nitems);
}
@@ -893,7 +1162,7 @@ Lnew:
return DB_ERROR_NONE;
Ldone:
- WUNLOCK_HASH(lck);
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
return ret;
}
@@ -1047,11 +1316,11 @@ int db_erase_hash(DbTable *tbl, Eterm key, Eterm *ret)
HashDbTerm** bp;
HashDbTerm* b;
HashDbTerm* free_us = NULL;
- erts_rwmtx_t* lck;
+ DbTableHashLockAndCounter* lck_ctr;
int nitems_diff = 0;
-
+ Sint nitems;
hval = MAKE_HASH(key);
- lck = WLOCK_HASH(tb,hval);
+ lck_ctr = WLOCK_HASH_GET_LCK_AND_CTR(tb,hval);
ix = hash_to_ix(tb, hval);
bp = &BUCKET(tb, ix);
b = *bp;
@@ -1078,10 +1347,13 @@ int db_erase_hash(DbTable *tbl, Eterm key, Eterm *ret)
bp = &b->next;
b = b->next;
}
- WUNLOCK_HASH(lck);
if (nitems_diff) {
- ADD_NITEMS(tb, nitems_diff);
- try_shrink(tb);
+ ADD_NITEMS(tb, lck_ctr, hval, nitems_diff);
+ nitems = NITEMS_ESTIMATE(tb, lck_ctr, hval);
+ }
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
+ if (nitems_diff) {
+ try_shrink(tb, nitems);
}
free_term_list(tb, free_us);
*ret = am_true;
@@ -1099,14 +1371,15 @@ static int db_erase_object_hash(DbTable *tbl, Eterm object, Eterm *ret)
HashDbTerm** bp;
HashDbTerm* b;
HashDbTerm* free_us = NULL;
- erts_rwmtx_t* lck;
+ DbTableHashLockAndCounter* lck_ctr;
int nitems_diff = 0;
+ Sint nitems;
int nkeys = 0;
Eterm key;
key = GETKEY(tb, tuple_val(object));
hval = MAKE_HASH(key);
- lck = WLOCK_HASH(tb,hval);
+ lck_ctr = WLOCK_HASH_GET_LCK_AND_CTR(tb,hval);
ix = hash_to_ix(tb, hval);
bp = &BUCKET(tb, ix);
b = *bp;
@@ -1139,10 +1412,13 @@ static int db_erase_object_hash(DbTable *tbl, Eterm object, Eterm *ret)
bp = &b->next;
b = b->next;
}
- WUNLOCK_HASH(lck);
if (nitems_diff) {
- ADD_NITEMS(tb, nitems_diff);
- try_shrink(tb);
+ ADD_NITEMS(tb, lck_ctr, hval, nitems_diff);
+ nitems = NITEMS_ESTIMATE(tb, lck_ctr, hval);
+ }
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
+ if (nitems_diff) {
+ try_shrink(tb, nitems);
}
free_term_list(tb, free_us);
*ret = am_true;
@@ -1322,12 +1598,18 @@ static int match_traverse(traverse_context_t* ctx,
for(;;) {
if (*current_ptr != NULL) {
if (!is_pseudo_deleted(*current_ptr)) {
- match_res = db_match_dbterm(&tb->common, ctx->p, mpi.mp,
- &(*current_ptr)->dbterm, hpp, 2);
+ DbTerm* obj = &(*current_ptr)->dbterm;
+ if (tb->common.compress)
+ obj = db_alloc_tmp_uncompressed(&tb->common, obj);
+ match_res = db_match_dbterm_uncompressed(&tb->common, ctx->p, mpi.mp,
+ obj, hpp, 2);
saved_current = *current_ptr;
if (ctx->on_match_res(ctx, slot_ix, &current_ptr, match_res)) {
++got;
}
+ if (tb->common.compress)
+ db_free_tmp_uncompressed(obj);
+
--iterations_left;
if (*current_ptr != saved_current) {
/* Don't advance to next, the callback did it already */
@@ -1441,12 +1723,18 @@ static int match_traverse_continue(traverse_context_t* ctx,
for(;;) {
if (*current_ptr != NULL) {
if (!is_pseudo_deleted(*current_ptr)) {
- match_res = db_match_dbterm(&tb->common, ctx->p, *mpp,
- &(*current_ptr)->dbterm, hpp, 2);
+ DbTerm* obj = &(*current_ptr)->dbterm;
+ if (tb->common.compress)
+ obj = db_alloc_tmp_uncompressed(&tb->common, obj);
+ match_res = db_match_dbterm_uncompressed(&tb->common, ctx->p, *mpp,
+ obj, hpp, 2);
saved_current = *current_ptr;
if (ctx->on_match_res(ctx, slot_ix, &current_ptr, match_res)) {
++got;
}
+ if (tb->common.compress)
+ db_free_tmp_uncompressed(obj);
+
--iterations_left;
if (*current_ptr != saved_current) {
/* Don't advance to next, the callback did it already */
@@ -2029,9 +2317,11 @@ static int select_delete_on_match_res(traverse_context_t* ctx_base, Sint slot_ix
HashDbTerm** current_ptr = *current_ptr_ptr;
select_delete_context_t* ctx = (select_delete_context_t*) ctx_base;
HashDbTerm* del;
+ DbTableHashLockAndCounter* lck_ctr;
+ Uint32 hval;
if (match_res != am_true)
return 0;
-
+ hval = (*current_ptr)->hvalue;
if (NFIXED(ctx->base.tb) > ctx->fixated_by_me) { /* fixated by others? */
if (slot_ix != ctx->last_pseudo_delete) {
if (!add_fixed_deletion(ctx->base.tb, slot_ix, ctx->fixated_by_me))
@@ -2047,23 +2337,58 @@ static int select_delete_on_match_res(traverse_context_t* ctx_base, Sint slot_ix
del->next = ctx->free_us;
ctx->free_us = del;
}
- DEC_NITEMS(ctx->base.tb);
+ lck_ctr = GET_LOCK_AND_CTR(ctx->base.tb,slot_ix);
+ DEC_NITEMS(ctx->base.tb, lck_ctr, hval);
return 1;
}
+/* This function is only safe to call while the table lock is held in
+ write mode */
+static Sint get_nitems_from_locks_or_counter(DbTableHash* tb)
+{
+ if (IS_DECENTRALIZED_CTRS(tb)) {
+ int i;
+ Sint total = 0;
+ for (i=0; i < DB_HASH_LOCK_CNT; ++i) {
+ total += tb->locks->lck_vec[i].lck_ctr.nitems;
+ }
+ return total;
+ } else {
+ return erts_flxctr_read_centralized(&tb->common.counters,
+ ERTS_DB_TABLE_NITEMS_COUNTER_ID);
+ }
+}
+
static int select_delete_on_loop_ended(traverse_context_t* ctx_base,
Sint slot_ix, Sint got,
Sint iterations_left, Binary** mpp,
Eterm* ret)
{
select_delete_context_t* ctx = (select_delete_context_t*) ctx_base;
- free_term_list(ctx->base.tb, ctx->free_us);
+ DbTableHash* tb = ctx->base.tb;
+ free_term_list(tb, ctx->free_us);
ctx->free_us = NULL;
ASSERT(iterations_left <= MAX_SELECT_DELETE_ITERATIONS);
BUMP_REDS(ctx->base.p, MAX_SELECT_DELETE_ITERATIONS - iterations_left);
if (got) {
- try_shrink(ctx->base.tb);
+ Sint nitems;
+ if (IS_DECENTRALIZED_CTRS(tb)) {
+ /* Get a random hash value so we can get an nitems
+ estimate from a random lock */
+ HashValue hval =
+ (HashValue)&ctx +
+ (HashValue)iterations_left +
+ (HashValue)erts_get_scheduler_data()->reductions;
+ erts_rwmtx_t* lck = RLOCK_HASH(tb, hval);
+ DbTableHashLockAndCounter* lck_ctr = GET_LOCK_AND_CTR(tb, hval);
+ nitems = NITEMS_ESTIMATE(tb, lck_ctr, hval);
+ RUNLOCK_HASH(lck);
+ } else {
+ nitems = erts_flxctr_read_centralized(&tb->common.counters,
+ ERTS_DB_TABLE_NITEMS_COUNTER_ID);
+ }
+ try_shrink(tb, nitems);
}
*ret = erts_make_integer(got, ctx->base.p);
return DB_ERROR_NONE;
@@ -2294,9 +2619,10 @@ static int db_take_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
HashDbTerm **bp, *b;
HashDbTerm *free_us = NULL;
HashValue hval = MAKE_HASH(key);
- erts_rwmtx_t *lck = WLOCK_HASH(tb, hval);
+ DbTableHashLockAndCounter *lck_ctr = WLOCK_HASH_GET_LCK_AND_CTR(tb, hval);
int ix = hash_to_ix(tb, hval);
int nitems_diff = 0;
+ Sint nitems;
*ret = NIL;
for (bp = &BUCKET(tb, ix), b = *bp; b; bp = &b->next, b = b->next) {
@@ -2322,10 +2648,13 @@ static int db_take_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
break;
}
}
- WUNLOCK_HASH(lck);
if (nitems_diff) {
- ADD_NITEMS(tb, nitems_diff);
- try_shrink(tb);
+ ADD_NITEMS(tb, lck_ctr, hval, nitems_diff);
+ nitems = NITEMS_ESTIMATE(tb, lck_ctr, hval);
+ }
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
+ if (nitems_diff) {
+ try_shrink(tb, nitems);
}
free_term_list(tb, free_us);
return DB_ERROR_NONE;
@@ -2449,7 +2778,7 @@ static void db_print_hash(fmtfn_t to, void *to_arg, int show, DbTable *tbl)
static int db_free_empty_table_hash(DbTable *tbl)
{
- ASSERT(NITEMS(tbl) == 0);
+ ASSERT(get_nitems_from_locks_or_counter(&tbl->hash) == 0);
while (db_free_table_continue_hash(tbl, ERTS_SWORD_MAX) < 0)
;
return 0;
@@ -2474,7 +2803,7 @@ static SWord db_free_table_continue_hash(DbTable *tbl, SWord reds)
erts_atomic_set_relb(&tb->fixdel, (erts_aint_t)NULL);
while(tb->nslots != 0) {
- reds -= EXT_SEGSZ/64 + free_seg(tb, 1);
+ reds -= EXT_SEGSZ/64 + free_seg(tb);
/*
* If we have done enough work, get out here.
@@ -2492,8 +2821,11 @@ static SWord db_free_table_continue_hash(DbTable *tbl, SWord reds)
(void*)tb->locks, sizeof(DbTableHashFineLocks));
tb->locks = NULL;
}
- ASSERT(sizeof(DbTable) == erts_flxctr_read_approx(&tb->common.counters,
- ERTS_DB_TABLE_MEM_COUNTER_ID));
+ ASSERT(erts_flxctr_is_snapshot_ongoing(&tb->common.counters) ||
+ ((sizeof(DbTable) +
+ erts_flxctr_nr_of_allocated_bytes(&tb->common.counters)) ==
+ erts_flxctr_read_approx(&tb->common.counters,
+ ERTS_DB_TABLE_MEM_COUNTER_ID)));
return reds; /* Done */
}
@@ -2519,6 +2851,7 @@ static int analyze_pattern(DbTableHash *tb, Eterm pattern,
HashValue hval = NIL;
int num_heads = 0;
int i;
+ Uint freason;
mpi->lists = mpi->dlists;
mpi->num_lists = 0;
@@ -2634,12 +2967,17 @@ static int analyze_pattern(DbTableHash *tb, Eterm pattern,
* match specs that happen to specify non existent keys etc.
*/
if ((mpi->mp = db_match_compile(matches, guards, bodies,
- num_heads, DCOMP_TABLE, NULL))
+ num_heads, DCOMP_TABLE, NULL,
+ &freason))
== NULL) {
if (buff != sbuff) {
erts_free(ERTS_ALC_T_DB_TMP, buff);
}
- return DB_ERROR_BADPARAM;
+ switch (freason) {
+ case BADARG: return DB_ERROR_BADPARAM;
+ case SYSTEM_LIMIT: return DB_ERROR_SYSRES;
+ default: ASSERT(0); return DB_ERROR_UNSPEC;
+ }
}
if (buff != sbuff) {
erts_free(ERTS_ALC_T_DB_TMP, buff);
@@ -2672,6 +3010,63 @@ static struct ext_segtab* alloc_ext_segtab(DbTableHash* tb, unsigned seg_ix)
return est;
}
+static void calc_shrink_limit(DbTableHash* tb)
+{
+ erts_aint_t shrink_limit;
+ int sample_size_is_enough = 1;
+
+ if (IS_DECENTRALIZED_CTRS(tb)) {
+ /*
+ Cochran’s Sample Size Formula indicates that we will get
+ good estimates if we have 100 buckets or more per lock (see
+ calculations below)
+ */
+ /* square of z-score 95% confidence */
+ /* const double z2 = 1.96*1.96; */
+ /* Estimated propotion used buckets */
+ /* const double p = 0.5; */
+ /* margin of error */
+ /* const double moe = 0.1; */
+ /* const double moe2 = moe*moe; */
+ /* Cochran’s Sample Size Formula x=96.040 */
+ /* const double x = (z2 * p * (1-p)) / moe2; */
+ /* Modification for smaller populations */
+ /* for(int n = 10; n < 1000; n = n + 100){ */
+ /* const double d = n*x / (x + n - 1) + 1; */
+ /* printf("Cochran_formula=%f size=%d mod_with_size=%f\n", x, n, d); */
+ /* } */
+ const int needed_slots = 100 * DB_HASH_LOCK_CNT;
+ if (tb->nslots < needed_slots) {
+ sample_size_is_enough = 0;
+ }
+ }
+
+ if (sample_size_is_enough && tb->nslots >= (FIRST_SEGSZ + 2*EXT_SEGSZ)) {
+ /*
+ * Start shrink when the sample size is big enough for
+ * decentralized counters if decentralized counters are used
+ * and when we can remove one extra segment and still remain
+ * below 50% load.
+ */
+ shrink_limit = (tb->nslots - EXT_SEGSZ) / 2;
+ }
+ else {
+ /*
+ * But don't shrink below two segments.
+ * Why? In order to have chance of getting rid of the last extra segment,
+ * and rehash it into the first small segment, we either have to start
+ * early and do speculative joining of buckets or we have to join a lot
+ * of buckets during each delete-op.
+ *
+ * Instead keep segment #2 once allocated. I also think it's a good bet
+ * a shrinking large table will grow large again.
+ */
+ shrink_limit = 0;
+ }
+ erts_atomic_set_nob(&tb->shrink_limit, shrink_limit);
+}
+
+
/* Extend table with one new segment
*/
static void alloc_seg(DbTableHash *tb)
@@ -2690,8 +3085,17 @@ static void alloc_seg(DbTableHash *tb)
segtab[seg_ix] = (struct segment*) erts_db_alloc(ERTS_ALC_T_DB_SEG,
(DbTable *) tb,
SIZEOF_SEGMENT(EXT_SEGSZ));
- sys_memset(segtab[seg_ix], 0, SIZEOF_SEGMENT(EXT_SEGSZ));
+#ifdef DEBUG
+ {
+ int i;
+ for (i = 0; i < EXT_SEGSZ; i++) {
+ segtab[seg_ix]->buckets[i] = DBG_BUCKET_INACTIVE;
+ }
+ }
+#endif
tb->nslots += EXT_SEGSZ;
+
+ calc_shrink_limit(tb);
}
static void dealloc_ext_segtab(void* lop_data)
@@ -2701,10 +3105,19 @@ static void dealloc_ext_segtab(void* lop_data)
erts_free(ERTS_ALC_T_DB_SEG, est);
}
-/* Shrink table by freeing the top segment
+struct dealloc_seg_ops {
+ struct segment* segp;
+ Uint seg_sz;
+
+ struct ext_segtab* est;
+};
+
+/* Shrink table by removing the top segment
** free_records: 1=free any records in segment, 0=assume segment is empty
+** ds_ops: (out) Instructions for dealloc_seg().
*/
-static int free_seg(DbTableHash *tb, int free_records)
+static int remove_seg(DbTableHash *tb, int free_records,
+ struct dealloc_seg_ops *ds_ops)
{
const int seg_ix = SLOT_IX_TO_SEG_IX(tb->nslots) - 1;
struct segment** const segtab = SEGTAB(tb);
@@ -2712,24 +3125,47 @@ static int free_seg(DbTableHash *tb, int free_records)
Uint seg_sz;
int nrecords = 0;
+ ERTS_LC_ASSERT(IS_TAB_WLOCKED(tb) || tb->common.status & DB_DELETE
+ || erts_atomic_read_nob(&tb->is_resizing));
+
ASSERT(segp != NULL);
-#ifndef DEBUG
- if (free_records)
-#endif
- {
- int i = (seg_ix == 0) ? FIRST_SEGSZ : EXT_SEGSZ;
- while (i--) {
- HashDbTerm* p = segp->buckets[i];
+ if (free_records) {
+ int ix, n;
+ if (seg_ix == 0) {
+ /* First segment (always fully active) */
+ n = FIRST_SEGSZ;
+ ix = FIRST_SEGSZ-1;
+ }
+ else if (NACTIVE(tb) < tb->nslots) {
+ /* Last extended segment partially active */
+ n = (NACTIVE(tb) - FIRST_SEGSZ) & EXT_SEGSZ_MASK;
+ ix = (NACTIVE(tb)-1) & EXT_SEGSZ_MASK;
+ }
+ else {
+ /* Full extended segment */
+ n = EXT_SEGSZ;
+ ix = EXT_SEGSZ - 1;
+ }
+ for ( ; n > 0; n--, ix--) {
+ HashDbTerm* p = segp->buckets[ix & EXT_SEGSZ_MASK];
while(p != 0) {
HashDbTerm* nxt = p->next;
- ASSERT(free_records); /* segment not empty as assumed? */
free_term(tb, p);
p = nxt;
++nrecords;
}
}
}
-
+#ifdef DEBUG
+ else {
+ int ix = (seg_ix == 0) ? FIRST_SEGSZ-1 : EXT_SEGSZ-1;
+ for ( ; ix >= 0; ix--) {
+ ASSERT(segp->buckets[ix] == DBG_BUCKET_INACTIVE);
+ }
+ }
+#endif
+
+ ds_ops->est = NULL;
if (seg_ix >= NSEG_1) {
struct ext_segtab* est = ErtsContainerStruct_(segtab,struct ext_segtab,segtab);
@@ -2738,35 +3174,64 @@ static int free_seg(DbTableHash *tb, int free_records)
SET_SEGTAB(tb, est->prev_segtab);
tb->nsegs = est->prev_nsegs;
- if (!tb->common.is_thread_safe) {
- /*
- * Table is doing a graceful shrink operation and we must avoid
- * deallocating this segtab while it may still be read by other
- * threads. Schedule deallocation with thread progress to make
- * sure no lingering threads are still hanging in BUCKET macro
- * with an old segtab pointer.
- */
- erts_schedule_db_free(&tb->common, dealloc_ext_segtab,
- est, &est->lop,
- SIZEOF_EXT_SEGTAB(est->nsegs));
- }
- else
- erts_db_free(ERTS_ALC_T_DB_SEG, (DbTable*)tb, est,
- SIZEOF_EXT_SEGTAB(est->nsegs));
+ ds_ops->est = est;
}
}
+
seg_sz = (seg_ix == 0) ? FIRST_SEGSZ : EXT_SEGSZ;
- erts_db_free(ERTS_ALC_T_DB_SEG, (DbTable *)tb, segp, SIZEOF_SEGMENT(seg_sz));
+ tb->nslots -= seg_sz;
+ ASSERT(tb->nslots >= 0);
+
+ ds_ops->segp = segp;
+ ds_ops->seg_sz = seg_sz;
#ifdef DEBUG
if (seg_ix < tb->nsegs)
SEGTAB(tb)[seg_ix] = NULL;
#endif
- tb->nslots -= seg_sz;
- ASSERT(tb->nslots >= 0);
+ calc_shrink_limit(tb);
return nrecords;
}
+/*
+ * Deallocate segment removed by remove_seg()
+ */
+static void dealloc_seg(DbTableHash *tb, struct dealloc_seg_ops* ds_ops)
+{
+ struct ext_segtab* est = ds_ops->est;
+
+ if (est) {
+ if (!tb->common.is_thread_safe) {
+ /*
+ * Table is doing a graceful shrink operation and we must avoid
+ * deallocating this segtab while it may still be read by other
+ * threads. Schedule deallocation with thread progress to make
+ * sure no lingering threads are still hanging in BUCKET macro
+ * with an old segtab pointer.
+ */
+ erts_schedule_db_free(&tb->common, dealloc_ext_segtab,
+ est, &est->lop,
+ SIZEOF_EXT_SEGTAB(est->nsegs));
+ }
+ else
+ erts_db_free(ERTS_ALC_T_DB_SEG, (DbTable*)tb, est,
+ SIZEOF_EXT_SEGTAB(est->nsegs));
+ }
+
+ erts_db_free(ERTS_ALC_T_DB_SEG, (DbTable *)tb,
+ ds_ops->segp, SIZEOF_SEGMENT(ds_ops->seg_sz));
+}
+
+/* Remove and deallocate top segment and all its contained objects */
+static int free_seg(DbTableHash *tb)
+{
+ struct dealloc_seg_ops ds_ops;
+ int reds;
+
+ reds = remove_seg(tb, 1, &ds_ops);
+ dealloc_seg(tb, &ds_ops);
+ return reds;
+}
/*
** Copy terms from ptr1 until ptr2
@@ -2811,9 +3276,11 @@ static Eterm build_term_list(Process* p, HashDbTerm* ptr1, HashDbTerm* ptr2,
static ERTS_INLINE int
begin_resizing(DbTableHash* tb)
{
- if (DB_USING_FINE_LOCKING(tb))
- return !erts_atomic_xchg_acqb(&tb->is_resizing, 1);
- else
+ if (DB_USING_FINE_LOCKING(tb)) {
+ return
+ !erts_atomic_read_acqb(&tb->is_resizing) &&
+ !erts_atomic_xchg_acqb(&tb->is_resizing, 1);
+ } else
ERTS_LC_ASSERT(IS_TAB_WLOCKED(tb));
return 1;
}
@@ -2888,6 +3355,7 @@ static void grow(DbTableHash* tb, int nitems)
pnext = &BUCKET(tb, from_ix);
p = *pnext;
to_pnext = &BUCKET(tb, to_ix);
+ ASSERT(*to_pnext == DBG_BUCKET_INACTIVE);
while (p != NULL) {
if (is_pseudo_deleted(p)) { /* rare but possible with fine locking */
*pnext = p->next;
@@ -2924,19 +3392,21 @@ abort:
*/
static void shrink(DbTableHash* tb, int nitems)
{
- HashDbTerm** src_bp;
- HashDbTerm** dst_bp;
+ struct dealloc_seg_ops ds_ops;
+ HashDbTerm* src;
+ HashDbTerm* tail;
HashDbTerm** bp;
erts_rwmtx_t* lck;
int src_ix, dst_ix, low_szm;
int nactive;
int loop_limit = 5;
+ ds_ops.segp = NULL;
do {
if (!begin_resizing(tb))
return; /* already in progress */
nactive = NACTIVE(tb);
- if (!(nactive > FIRST_SEGSZ && nitems < SHRINK_LIMIT(nactive))) {
+ if (!(nitems < SHRINK_LIMIT(tb))) {
goto abort; /* already done (race) */
}
src_ix = nactive - 1;
@@ -2953,41 +3423,49 @@ static void shrink(DbTableHash* tb, int nitems)
goto abort;
}
- src_bp = &BUCKET(tb, src_ix);
- dst_bp = &BUCKET(tb, dst_ix);
- bp = src_bp;
-
- /*
- * We join lists by appending "dst" at the end of "src"
- * as we must step through "src" anyway to purge pseudo deleted.
- */
- while(*bp != NULL) {
- if (is_pseudo_deleted(*bp)) {
- HashDbTerm* deleted = *bp;
- *bp = deleted->next;
- free_term(tb, deleted);
- } else {
- bp = &(*bp)->next;
- }
- }
- *bp = *dst_bp;
- *dst_bp = *src_bp;
- *src_bp = NULL;
-
+ src = BUCKET(tb, src_ix);
+#ifdef DEBUG
+ BUCKET(tb, src_ix) = DBG_BUCKET_INACTIVE;
+#endif
nactive = src_ix;
erts_atomic_set_nob(&tb->nactive, nactive);
if (dst_ix == 0) {
erts_atomic_set_relb(&tb->szm, low_szm);
}
- WUNLOCK_HASH(lck);
-
if (tb->nslots - src_ix >= EXT_SEGSZ) {
- free_seg(tb, 0);
+ remove_seg(tb, 0, &ds_ops);
}
done_resizing(tb);
- } while (--loop_limit
- && nactive > FIRST_SEGSZ && nitems < SHRINK_LIMIT(nactive));
+ if (src) {
+ /*
+ * We join buckets by appending "dst" list at the end of "src" list
+ * as we must step through "src" anyway to purge pseudo deleted.
+ */
+ bp = &BUCKET(tb, dst_ix);
+ tail = *bp;
+ *bp = src;
+
+ while(*bp != NULL) {
+ if (is_pseudo_deleted(*bp)) {
+ HashDbTerm* deleted = *bp;
+ *bp = deleted->next;
+ free_term(tb, deleted);
+ } else {
+ bp = &(*bp)->next;
+ }
+ }
+ *bp = tail;
+ }
+
+ WUNLOCK_HASH(lck);
+
+ if (ds_ops.segp) {
+ dealloc_seg(tb, &ds_ops);
+ ds_ops.segp = NULL;
+ }
+
+ } while (--loop_limit && nitems < SHRINK_LIMIT(tb));
return;
abort:
@@ -3047,13 +3525,13 @@ db_lookup_dbterm_hash(Process *p, DbTable *tbl, Eterm key, Eterm obj,
DbTableHash *tb = &tbl->hash;
HashValue hval;
HashDbTerm **bp, *b;
- erts_rwmtx_t* lck;
+ DbTableHashLockAndCounter* lck_ctr;
int flags = 0;
ASSERT(tb->common.status & DB_SET);
hval = MAKE_HASH(key);
- lck = WLOCK_HASH(tb, hval);
+ lck_ctr = WLOCK_HASH_GET_LCK_AND_CTR(tb, hval);
bp = &BUCKET(tb, hash_to_ix(tb, hval));
b = *bp;
@@ -3072,7 +3550,7 @@ db_lookup_dbterm_hash(Process *p, DbTable *tbl, Eterm key, Eterm obj,
}
if (obj == THE_NON_VALUE) {
- WUNLOCK_HASH(lck);
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
return 0;
}
@@ -3105,7 +3583,7 @@ db_lookup_dbterm_hash(Process *p, DbTable *tbl, Eterm key, Eterm obj,
ASSERT(q->hvalue == hval);
q->pseudo_deleted = 0;
*bp = b = q;
- INC_NITEMS(tb);
+ INC_NITEMS(tb, lck_ctr, hval);
}
HRelease(p, hend, htop);
@@ -3118,7 +3596,7 @@ Ldone:
handle->dbterm = &b->dbterm;
handle->flags = flags;
handle->new_size = b->dbterm.size;
- handle->u.hash.lck = lck;
+ handle->u.hash.lck_ctr = lck_ctr;
return 1;
}
@@ -3131,10 +3609,12 @@ db_finalize_dbterm_hash(int cret, DbUpdateHandle* handle)
DbTableHash *tb = &tbl->hash;
HashDbTerm **bp = (HashDbTerm **) handle->bp;
HashDbTerm *b = *bp;
- erts_rwmtx_t* lck = handle->u.hash.lck;
+ Uint32 hval = b->hvalue;
+ DbTableHashLockAndCounter* lck_ctr = handle->u.hash.lck_ctr;
HashDbTerm* free_me = NULL;
+ Sint nitems;
- ERTS_LC_ASSERT(IS_HASH_WLOCKED(tb, lck)); /* locked by db_lookup_dbterm_hash */
+ ERTS_LC_ASSERT(IS_HASH_WLOCKED(tb, &lck_ctr->lck)); /* locked by db_lookup_dbterm_hash */
ASSERT((&b->dbterm == handle->dbterm) == !(tb->common.compress && handle->flags & DB_MUST_RESIZE));
@@ -3146,11 +3626,11 @@ db_finalize_dbterm_hash(int cret, DbUpdateHandle* handle)
*bp = b->next;
free_me = b;
}
-
- WUNLOCK_HASH(lck);
if (!(handle->flags & DB_INC_TRY_GROW))
- DEC_NITEMS(tb);
- try_shrink(tb);
+ DEC_NITEMS(tb, lck_ctr, hval);
+ nitems = NITEMS_ESTIMATE(tb, lck_ctr, hval);
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
+ try_shrink(tb, nitems);
} else {
if (handle->flags & DB_MUST_RESIZE) {
ASSERT(cret == DB_ERROR_NONE);
@@ -3159,16 +3639,18 @@ db_finalize_dbterm_hash(int cret, DbUpdateHandle* handle)
}
if (handle->flags & DB_INC_TRY_GROW) {
int nactive;
- int nitems = INC_NITEMS(tb);
+ int nitems;
ASSERT(cret == DB_ERROR_NONE);
- WUNLOCK_HASH(lck);
+ INC_NITEMS(tb, lck_ctr, hval);
+ nitems = NITEMS_ESTIMATE(tb, lck_ctr, hval);
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
nactive = NACTIVE(tb);
if (nitems > GROW_LIMIT(nactive) && !IS_FIXED(tb)) {
grow(tb, nitems);
}
} else {
- WUNLOCK_HASH(lck);
+ WUNLOCK_HASH_LCK_CTR(lck_ctr);
}
}
@@ -3187,9 +3669,7 @@ static SWord db_delete_all_objects_hash(Process* p,
Eterm* nitems_holder_wb)
{
if (nitems_holder_wb != NULL) {
- Uint nr_of_items =
- erts_flxctr_read_centralized(&tbl->common.counters,
- ERTS_DB_TABLE_NITEMS_COUNTER_ID);
+ Uint nr_of_items = get_nitems_from_locks_or_counter(&tbl->hash);
*nitems_holder_wb = erts_make_integer(nr_of_items, p);
}
if (IS_FIXED(tbl)) {
@@ -3397,6 +3877,49 @@ static int db_raw_next_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
return DB_ERROR_NONE;
}
+static void* db_eterm_to_dbterm_hash(int compress, int keypos, Eterm obj)
+{
+ HashDbTerm* term = new_dbterm_hash_no_tab(compress, keypos, obj);
+ term->next = NULL;
+ return term;
+}
+
+static void* db_dbterm_list_prepend_hash(void* list, void* db_term)
+{
+ HashDbTerm* l = list;
+ HashDbTerm* t = db_term;
+ t->next = l;
+ return t;
+}
+
+static void* db_dbterm_list_remove_first_hash(void** list)
+{
+ if (*list == NULL) {
+ return NULL;
+ } else {
+ HashDbTerm* t = (*list);
+ HashDbTerm* l = t->next;
+ *list = l;
+ return t;
+ }
+}
+
+/*
+ * Frees a HashDbTerm without updating the memory footprint of the
+ * table.
+ */
+static void db_free_dbterm_hash(int compressed, void* obj)
+{
+ HashDbTerm* p = obj;
+ db_free_term_no_tab(compressed, p, offsetof(HashDbTerm, dbterm));
+}
+
+static Eterm db_get_dbterm_key_hash(DbTable* tb, void* db_term)
+{
+ HashDbTerm *value_to_insert = db_term;
+ return GETKEY(tb, value_to_insert->dbterm.tpl);
+}
+
/* For testing only */
Eterm erts_ets_hash_sizeof_ext_segtab(void)
{
@@ -3418,7 +3941,7 @@ void erts_lcnt_enable_db_hash_lock_count(DbTableHash *tb, int enable) {
}
for(i = 0; i < DB_HASH_LOCK_CNT; i++) {
- erts_lcnt_ref_t *ref = &tb->locks->lck_vec[i].lck.lcnt;
+ erts_lcnt_ref_t *ref = &tb->locks->lck_vec[i].lck_ctr.lck.lcnt;
if(enable) {
erts_lcnt_install_new_lock_info(ref, "db_hash_slot", tb->common.the_name,
diff --git a/erts/emulator/beam/erl_db_hash.h b/erts/emulator/beam/erl_db_hash.h
index 0a794289a8..830dc77114 100644
--- a/erts/emulator/beam/erl_db_hash.h
+++ b/erts/emulator/beam/erl_db_hash.h
@@ -53,9 +53,14 @@ typedef struct hash_db_term {
#define DB_HASH_LOCK_CNT 64
#endif
+typedef struct DbTableHashLockAndCounter {
+ Sint nitems;
+ erts_rwmtx_t lck;
+} DbTableHashLockAndCounter;
+
typedef struct db_table_hash_fine_locks {
union {
- erts_rwmtx_t lck;
+ DbTableHashLockAndCounter lck_ctr;
byte _cache_line_alignment[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(erts_rwmtx_t))];
}lck_vec[DB_HASH_LOCK_CNT];
} DbTableHashFineLocks;
@@ -63,9 +68,10 @@ typedef struct db_table_hash_fine_locks {
typedef struct db_table_hash {
DbTableCommon common;
- /* SMP: szm and nactive are write-protected by is_resizing or table write lock */
+ /* szm, nactive, shrink_limit are write-protected by is_resizing or table write lock */
erts_atomic_t szm; /* current size mask. */
erts_atomic_t nactive; /* Number of "active" slots */
+ erts_atomic_t shrink_limit; /* Shrink table when fewer objects than this */
erts_atomic_t segtab; /* The segment table (struct segment**) */
struct segment* first_segtab[1];
@@ -93,7 +99,7 @@ Uint db_kept_items_hash(DbTableHash *tb);
int db_create_hash(Process *p,
DbTable *tbl /* [in out] */);
-int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail);
+int db_put_hash(DbTable *tbl, Eterm obj, int key_clash_fail, SWord* consumed_reds_p);
int db_get_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret);
diff --git a/erts/emulator/beam/erl_db_tree.c b/erts/emulator/beam/erl_db_tree.c
index 32ec6e60a8..90e37c4b4e 100644
--- a/erts/emulator/beam/erl_db_tree.c
+++ b/erts/emulator/beam/erl_db_tree.c
@@ -142,20 +142,33 @@ static ERTS_INLINE TreeDbTerm* new_dbterm(DbTableCommon *tb, Eterm obj)
{
TreeDbTerm* p;
if (tb->compress) {
- p = db_store_term_comp(tb, NULL, offsetof(TreeDbTerm,dbterm), obj);
+ p = db_store_term_comp(tb, tb->keypos, NULL, offsetof(TreeDbTerm,dbterm), obj);
}
else {
p = db_store_term(tb, NULL, offsetof(TreeDbTerm,dbterm), obj);
}
return p;
}
+
+static ERTS_INLINE TreeDbTerm* new_dbterm_no_tab(int compress, int keypos, Eterm obj)
+{
+ TreeDbTerm* p;
+ if (compress) {
+ p = db_store_term_comp(NULL, keypos, NULL, offsetof(TreeDbTerm,dbterm), obj);
+ }
+ else {
+ p = db_store_term(NULL, NULL, offsetof(TreeDbTerm,dbterm), obj);
+ }
+ return p;
+}
+
static ERTS_INLINE TreeDbTerm* replace_dbterm(DbTableCommon *tb, TreeDbTerm* old,
Eterm obj)
{
TreeDbTerm* p;
ASSERT(old != NULL);
if (tb->compress) {
- p = db_store_term_comp(tb, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj);
+ p = db_store_term_comp(tb, tb->keypos, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj);
}
else {
p = db_store_term(tb, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj);
@@ -396,7 +409,7 @@ static int db_last_tree(Process *p, DbTable *tbl,
static int db_prev_tree(Process *p, DbTable *tbl,
Eterm key,
Eterm *ret);
-static int db_put_tree(DbTable *tbl, Eterm obj, int key_clash_fail);
+static int db_put_tree(DbTable *tbl, Eterm obj, int key_clash_fail, SWord *consumed_reds_p);
static int db_get_tree(Process *p, DbTable *tbl,
Eterm key, Eterm *ret);
static int db_member_tree(DbTable *tbl, Eterm key, Eterm *ret);
@@ -458,9 +471,11 @@ db_lookup_dbterm_tree(Process *, DbTable *, Eterm key, Eterm obj,
DbUpdateHandle*);
static void
db_finalize_dbterm_tree(int cret, DbUpdateHandle *);
-
static int db_get_binary_info_tree(Process*, DbTable*, Eterm key, Eterm *ret);
-
+static int db_put_dbterm_tree(DbTable* tbl, /* [in out] */
+ void* obj,
+ int key_clash_fail,
+ SWord *consumed_reds_p);
/*
** Static variables
@@ -503,6 +518,12 @@ DbTableMethod db_tree =
db_foreach_offheap_tree,
db_lookup_dbterm_tree,
db_finalize_dbterm_tree,
+ db_eterm_to_dbterm_tree_common,
+ db_dbterm_list_prepend_tree_common,
+ db_dbterm_list_remove_first_tree_common,
+ db_put_dbterm_tree,
+ db_free_dbterm_tree_common,
+ db_get_dbterm_key_tree_common,
db_get_binary_info_tree,
db_first_tree, /* raw_first same as first */
db_next_tree /* raw_next same as next */
@@ -660,6 +681,139 @@ static ERTS_INLINE int cmp_key_eq(DbTableCommon* tb, Eterm key, TreeDbTerm* obj)
return is_same(key, obj_key) || CMP(key, obj_key) == 0;
}
+/*
+ * This function differ to db_put_tree_common in that it inserts a TreeDbTerm
+ * instead of an Eterm.
+ */
+int db_put_dbterm_tree_common(DbTableCommon *tb,
+ TreeDbTerm **root,
+ TreeDbTerm *value_to_insert,
+ int key_clash_fail,
+ DbTableTree *stack_container)
+{
+ /* Non recursive insertion in AVL tree, building our own stack */
+ TreeDbTerm **tstack[STACK_NEED];
+ int tpos = 0;
+ int dstack[STACK_NEED+1];
+ int dpos = 0;
+ int state = 0;
+ TreeDbTerm **this = root;
+ Sint c;
+ Eterm key;
+ int dir;
+ TreeDbTerm *p1, *p2, *p;
+ Uint size_to_insert = db_term_size((DbTable*)tb, value_to_insert, offsetof(TreeDbTerm, dbterm));
+ ERTS_DB_ALC_MEM_UPDATE_((DbTable*)tb, 0, size_to_insert);
+ key = GETKEY(tb, value_to_insert->dbterm.tpl);
+
+ reset_static_stack(stack_container);
+
+ dstack[dpos++] = DIR_END;
+ for (;;)
+ if (!*this) { /* Found our place */
+ state = 1;
+ INC_NITEMS(((DbTable*)tb));
+ *this = value_to_insert;
+ (*this)->balance = 0;
+ (*this)->left = (*this)->right = NULL;
+ break;
+ } else if ((c = cmp_key(tb, key, *this)) < 0) {
+ /* go lefts */
+ dstack[dpos++] = DIR_LEFT;
+ tstack[tpos++] = this;
+ this = &((*this)->left);
+ } else if (c > 0) { /* go right */
+ dstack[dpos++] = DIR_RIGHT;
+ tstack[tpos++] = this;
+ this = &((*this)->right);
+ } else if (!key_clash_fail) { /* Equal key and this is a set, replace. */
+ value_to_insert->balance = (*this)->balance;
+ value_to_insert->left = (*this)->left;
+ value_to_insert->right = (*this)->right;
+ free_term((DbTable*)tb, *this);
+ *this = value_to_insert;
+ break;
+ } else {
+ return DB_ERROR_BADKEY; /* key already exists */
+ }
+
+ while (state && ( dir = dstack[--dpos] ) != DIR_END) {
+ this = tstack[--tpos];
+ p = *this;
+ if (dir == DIR_LEFT) {
+ switch (p->balance) {
+ case 1:
+ p->balance = 0;
+ state = 0;
+ break;
+ case 0:
+ p->balance = -1;
+ break;
+ case -1: /* The icky case */
+ p1 = p->left;
+ if (p1->balance == -1) { /* Single LL rotation */
+ p->left = p1->right;
+ p1->right = p;
+ p->balance = 0;
+ (*this) = p1;
+ } else { /* Double RR rotation */
+ p2 = p1->right;
+ p1->right = p2->left;
+ p2->left = p1;
+ p->left = p2->right;
+ p2->right = p;
+ p->balance = (p2->balance == -1) ? +1 : 0;
+ p1->balance = (p2->balance == 1) ? -1 : 0;
+ (*this) = p2;
+ }
+ (*this)->balance = 0;
+ state = 0;
+ break;
+ }
+ } else { /* dir == DIR_RIGHT */
+ switch (p->balance) {
+ case -1:
+ p->balance = 0;
+ state = 0;
+ break;
+ case 0:
+ p->balance = 1;
+ break;
+ case 1:
+ p1 = p->right;
+ if (p1->balance == 1) { /* Single RR rotation */
+ p->right = p1->left;
+ p1->left = p;
+ p->balance = 0;
+ (*this) = p1;
+ } else { /* Double RL rotation */
+ p2 = p1->left;
+ p1->left = p2->right;
+ p2->right = p1;
+ p->right = p2->left;
+ p2->left = p;
+ p->balance = (p2->balance == 1) ? -1 : 0;
+ p1->balance = (p2->balance == -1) ? 1 : 0;
+ (*this) = p2;
+ }
+ (*this)->balance = 0;
+ state = 0;
+ break;
+ }
+ }
+ }
+ return DB_ERROR_NONE;
+}
+
+static int db_put_dbterm_tree(DbTable* tbl, /* [in out] */
+ void* obj,
+ int key_clash_fail, /* DB_ERROR_BADKEY if key exists */
+ SWord *consumed_reds_p)
+{
+ DbTableTree *tb = &tbl->tree;
+ return db_put_dbterm_tree_common(&tb->common, &tb->root, obj, key_clash_fail, tb);
+}
+
int db_put_tree_common(DbTableCommon *tb, TreeDbTerm **root, Eterm obj,
int key_clash_fail, DbTableTree *stack_container)
{
@@ -772,7 +926,8 @@ int db_put_tree_common(DbTableCommon *tb, TreeDbTerm **root, Eterm obj,
return DB_ERROR_NONE;
}
-static int db_put_tree(DbTable *tbl, Eterm obj, int key_clash_fail)
+static int db_put_tree(DbTable *tbl, Eterm obj, int key_clash_fail,
+ SWord *consumed_reds_p)
{
DbTableTree *tb = &tbl->tree;
return db_put_tree_common(&tb->common, &tb->root, obj, key_clash_fail, tb);
@@ -1175,7 +1330,7 @@ int db_select_continue_tree_common(Process *p,
sc.accum,
tptr[7],
make_small(sc.got));
- RET_TO_BIF(bif_trap1(bif_export[BIF_ets_select_1], p, continuation),
+ RET_TO_BIF(bif_trap1(&bif_trap_export[BIF_ets_select_1], p, continuation),
DB_ERROR_NONE);
#undef RET_TO_BIF
@@ -1320,7 +1475,7 @@ int db_select_tree_common(Process *p, DbTable *tb,
make_small(sc.got));
/* Don't free mpi.mp, so don't use macro */
- *ret = bif_trap1(bif_export[BIF_ets_select_1], p, continuation);
+ *ret = bif_trap1(&bif_trap_export[BIF_ets_select_1], p, continuation);
return DB_ERROR_NONE;
#undef RET_TO_BIF
@@ -1728,7 +1883,7 @@ int db_select_chunk_tree_common(Process *p, DbTable *tb,
make_small(reverse),
make_small(sc.got));
/* Don't let RET_TO_BIF macro free mpi.mp*/
- *ret = bif_trap1(bif_export[BIF_ets_select_1], p, continuation);
+ *ret = bif_trap1(&bif_trap_export[BIF_ets_select_1], p, continuation);
return DB_ERROR_NONE;
#undef RET_TO_BIF
@@ -2307,9 +2462,12 @@ static SWord db_free_table_continue_tree(DbTable *tbl, SWord reds)
sizeof(TreeDbTerm *) * STACK_NEED);
ASSERT(erts_flxctr_is_snapshot_ongoing(&tb->common.counters) ||
((APPROX_MEM_CONSUMED(tb)
- == sizeof(DbTable)) ||
+ == (sizeof(DbTable) +
+ erts_flxctr_nr_of_allocated_bytes(&tb->common.counters))) ||
(APPROX_MEM_CONSUMED(tb)
- == (sizeof(DbTable) + sizeof(DbFixation)))));
+ == (sizeof(DbTable) +
+ sizeof(DbFixation) +
+ erts_flxctr_nr_of_allocated_bytes(&tb->common.counters)))));
}
return reds;
}
@@ -2524,6 +2682,7 @@ static int analyze_pattern(DbTableCommon *tb, Eterm pattern,
Eterm least = THE_NON_VALUE;
Eterm most = THE_NON_VALUE;
enum ms_key_boundness boundness;
+ Uint freason;
mpi->key_boundness = MS_KEY_IMPOSSIBLE;
mpi->mp = NULL;
@@ -2612,12 +2771,17 @@ static int analyze_pattern(DbTableCommon *tb, Eterm pattern,
* match specs that happen to specify non existent keys etc.
*/
if ((mpi->mp = db_match_compile(matches, guards, bodies,
- num_heads, DCOMP_TABLE, NULL))
+ num_heads, DCOMP_TABLE, NULL,
+ &freason))
== NULL) {
if (buff != sbuff) {
erts_free(ERTS_ALC_T_DB_TMP, buff);
}
- return DB_ERROR_BADPARAM;
+ switch (freason) {
+ case BADARG: return DB_ERROR_BADPARAM;
+ case SYSTEM_LIMIT: return DB_ERROR_SYSRES;
+ default: ASSERT(0); return DB_ERROR_UNSPEC;
+ }
}
if (buff != sbuff) {
erts_free(ERTS_ALC_T_DB_TMP, buff);
@@ -3028,8 +3192,8 @@ found_prev:
}
-/* @brief Find object with smallest key of all larger than partially bound key.
- * Can be used as a starting point for a reverse iteration with pb_key.
+/** @brief Find object with smallest key of all larger than partially bound
+ * key. Can be used as a starting point for a reverse iteration with pb_key.
*
* @param pb_key The partially bound key. Example {42, '$1'}
* @param *rootpp Will return pointer to root pointer of tree with found object.
@@ -3078,8 +3242,8 @@ static TreeDbTerm *find_next_from_pb_key(DbTable *tbl, TreeDbTerm*** rootpp,
}
}
-/* @brief Find object with largest key of all smaller than partially bound key.
- * Can be used as a starting point for a forward iteration with pb_key.
+/** @brief Find object with largest key of all smaller than partially bound
+ * key. Can be used as a starting point for a forward iteration with pb_key.
*
* @param pb_key The partially bound key. Example {42, '$1'}
* @param *rootpp Will return pointer to root pointer of found object.
@@ -3355,6 +3519,50 @@ Eterm db_binary_info_tree_common(Process* p, TreeDbTerm* this)
}
+void* db_eterm_to_dbterm_tree_common(int compress, int keypos, Eterm obj)
+{
+ TreeDbTerm* term = new_dbterm_no_tab(compress, keypos, obj);
+ term->left = NULL;
+ term->right = NULL;
+ return term;
+}
+
+void* db_dbterm_list_prepend_tree_common(void *list, void *db_term)
+{
+ TreeDbTerm* l = list;
+ TreeDbTerm* t = db_term;
+ t->left = l;
+ return t;
+}
+
+void* db_dbterm_list_remove_first_tree_common(void **list)
+{
+ if (*list == NULL) {
+ return NULL;
+ } else {
+ TreeDbTerm* t = (*list);
+ TreeDbTerm* l = t->left;
+ *list = l;
+ return t;
+ }
+}
+
+/*
+ * Frees a TreeDbTerm without updating the memory footprint of the
+ * table.
+ */
+void db_free_dbterm_tree_common(int compressed, void* obj)
+{
+ TreeDbTerm* p = obj;
+ db_free_term_no_tab(compressed, p, offsetof(TreeDbTerm, dbterm));
+}
+
+Eterm db_get_dbterm_key_tree_common(DbTable* tb, void* db_term)
+{
+ TreeDbTerm *term = db_term;
+ return GETKEY(tb, term->dbterm.tpl);
+}
+
/*
* Traverse the tree with a callback function, used by db_match_xxx
*/
@@ -3953,6 +4161,7 @@ static int doit_select_replace(DbTableCommon *tb, TreeDbTerm **this,
int forward)
{
struct select_replace_context *sc = (struct select_replace_context *) ptr;
+ DbTerm* obj;
Eterm ret;
sc->lastobj = (*this)->dbterm.tpl;
@@ -3963,7 +4172,10 @@ static int doit_select_replace(DbTableCommon *tb, TreeDbTerm **this,
GETKEY_WITH_POS(sc->keypos, (*this)->dbterm.tpl)) > 0)) {
return 0;
}
- ret = db_match_dbterm(tb, sc->p, sc->mp, &(*this)->dbterm, NULL, 0);
+ obj = &(*this)->dbterm;
+ if (tb->compress)
+ obj = db_alloc_tmp_uncompressed(tb, obj);
+ ret = db_match_dbterm_uncompressed(tb, sc->p, sc->mp, obj, NULL, 0);
if (is_value(ret)) {
TreeDbTerm* new;
@@ -3982,6 +4194,8 @@ static int doit_select_replace(DbTableCommon *tb, TreeDbTerm **this,
free_term((DbTable*)tb, old);
++(sc->replaced);
}
+ if (tb->compress)
+ db_free_tmp_uncompressed(obj);
if (--(sc->max) <= 0) {
return 0;
}
diff --git a/erts/emulator/beam/erl_db_tree_util.h b/erts/emulator/beam/erl_db_tree_util.h
index 1fa210d741..75c82003b2 100644
--- a/erts/emulator/beam/erl_db_tree_util.h
+++ b/erts/emulator/beam/erl_db_tree_util.h
@@ -171,6 +171,13 @@ void db_finalize_dbterm_tree_common(int cret,
DbUpdateHandle *handle,
TreeDbTerm **root,
DbTableTree *stack_container);
+void* db_eterm_to_dbterm_tree_common(int compress, int keypos, Eterm obj);
+void* db_dbterm_list_prepend_tree_common(void* list, void* db_term);
+void* db_dbterm_list_remove_first_tree_common(void **list);
+int db_put_dbterm_tree_common(DbTableCommon *tb, TreeDbTerm **root, TreeDbTerm *value_to_insert,
+ int key_clash_fail, DbTableTree *stack_container);
+void db_free_dbterm_tree_common(int compressed, void* obj);
+Eterm db_get_dbterm_key_tree_common(DbTable* tb, void* db_term);
Sint cmp_partly_bound(Eterm partly_bound_key, Eterm bound_key);
TreeDbTerm *db_find_tree_node_common(DbTableCommon*, TreeDbTerm *root,
diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c
index 581313d880..658ac44824 100644
--- a/erts/emulator/beam/erl_db_util.c
+++ b/erts/emulator/beam/erl_db_util.c
@@ -148,7 +148,26 @@ do { \
#define add_dmc_err(EINFO, STR, VAR, TERM, SEV) \
vadd_dmc_err(EINFO, SEV, VAR, STR, TERM)
+#define ERTS_DB_STACK_MARGIN (sizeof(void *)*1024)
+static int
+stack_guard_downwards(char *limit)
+{
+ char c;
+ ASSERT(limit);
+ return erts_check_below_limit(&c, limit + ERTS_DB_STACK_MARGIN);
+}
+
+static int
+stack_guard_upwards(char *limit)
+{
+ char c;
+ ASSERT(limit);
+ return erts_check_above_limit(&c, limit - ERTS_DB_STACK_MARGIN);
+}
+
+static int (*stack_guard)(char *) = NULL;
+
static ERTS_INLINE Process *
get_proc(Process *cp, Uint32 cp_locks, Eterm id, Uint32 id_locks)
{
@@ -347,6 +366,8 @@ typedef struct dmc_context {
int is_guard; /* 1 if in guard, 0 if in body */
int special; /* 1 if the head in the match was a single expression */
DMCErrInfo *err_info;
+ char *stack_limit;
+ Uint freason;
} DMCContext;
/*
@@ -932,8 +953,6 @@ static Eterm match_spec_test(Process *p, Eterm against, Eterm spec, int trace);
static Eterm seq_trace_fake(Process *p, Eterm arg1);
-static void db_free_tmp_uncompressed(DbTerm* obj);
-
/*
** Interface routines.
@@ -1040,7 +1059,7 @@ Eterm erts_match_set_get_source(Binary *mpsp)
}
/* This one is for the tracing */
-Binary *erts_match_set_compile(Process *p, Eterm matchexpr, Eterm MFA) {
+Binary *erts_match_set_compile(Process *p, Eterm matchexpr, Eterm MFA, Uint *freasonp) {
Binary *bin;
Uint sz;
Eterm *hp;
@@ -1053,7 +1072,7 @@ Binary *erts_match_set_compile(Process *p, Eterm matchexpr, Eterm MFA) {
flags = DCOMP_TRACE | DCOMP_CALL_TRACE | DCOMP_ALLOW_TRACE_OPS;
}
- bin = db_match_set_compile(p, matchexpr, flags);
+ bin = db_match_set_compile(p, matchexpr, flags, freasonp);
if (bin != NULL) {
MatchProg *prog = Binary2MatchProg(bin);
sz = size_object(matchexpr);
@@ -1067,7 +1086,7 @@ Binary *erts_match_set_compile(Process *p, Eterm matchexpr, Eterm MFA) {
}
Binary *db_match_set_compile(Process *p, Eterm matchexpr,
- Uint flags)
+ Uint flags, Uint *freasonp)
{
Eterm l;
Eterm t;
@@ -1083,6 +1102,8 @@ Binary *db_match_set_compile(Process *p, Eterm matchexpr,
Eterm *buff;
Eterm sbuff[15];
+ *freasonp = BADARG;
+
if (!is_list(matchexpr))
return NULL;
num_heads = 0;
@@ -1141,7 +1162,8 @@ Binary *db_match_set_compile(Process *p, Eterm matchexpr,
if ((mps = db_match_compile(matches, guards, bodies,
num_heads,
flags,
- NULL)) == NULL) {
+ NULL,
+ freasonp)) == NULL) {
goto error;
}
compiled = 1;
@@ -1342,7 +1364,7 @@ int db_match_keeps_key(int keypos, Eterm match, Eterm guard, Eterm body)
return 0;
}
-Eterm db_match_set_lint(Process *p, Eterm matchexpr, Uint flags)
+static Eterm db_match_set_lint(Process *p, Eterm matchexpr, Uint flags)
{
Eterm l;
Eterm t;
@@ -1358,6 +1380,7 @@ Eterm db_match_set_lint(Process *p, Eterm matchexpr, Uint flags)
Eterm sbuff[15];
Eterm *buff = sbuff;
int i;
+ Uint freason = BADARG;
if (!is_list(matchexpr)) {
add_dmc_err(err_info, "Match programs are not in a list.",
@@ -1424,7 +1447,7 @@ Eterm db_match_set_lint(Process *p, Eterm matchexpr, Uint flags)
++i;
}
mp = db_match_compile(matches, guards, bodies, num_heads,
- flags, err_info);
+ flags, err_info, &freason);
if (mp != NULL) {
erts_bin_free(mp);
}
@@ -1502,12 +1525,17 @@ static Eterm erts_match_set_run_ets(Process *p, Binary *mpsp,
*/
void db_initialize_util(void){
+ char c;
qsort(guard_tab,
sizeof(guard_tab) / sizeof(DMCGuardBif),
sizeof(DMCGuardBif),
(int (*)(const void *, const void *)) &cmp_guard_bif);
match_pseudo_process_init();
erts_atomic32_init_nob(&trace_control_word, 0);
+ if (erts_check_if_stack_grows_downwards(&c))
+ stack_guard = stack_guard_downwards;
+ else
+ stack_guard = stack_guard_upwards;
}
@@ -1534,7 +1562,8 @@ Binary *db_match_compile(Eterm *matchexpr,
Eterm *body,
int num_progs,
Uint flags,
- DMCErrInfo *err_info)
+ DMCErrInfo *err_info,
+ Uint *freasonp)
{
DMCHeap heap;
DMC_STACK_TYPE(Eterm) stack;
@@ -1550,6 +1579,9 @@ Binary *db_match_compile(Eterm *matchexpr,
Binary *bp = NULL;
unsigned clause_start;
+ context.stack_limit = (char *) ethr_get_stacklimit();
+ context.freason = BADARG;
+
DMC_INIT_STACK(stack);
DMC_INIT_STACK(text);
@@ -1909,6 +1941,7 @@ error: /* Here is were we land when compilation failed. */
free_message_buffer(context.copy);
if (heap.vars != heap.vars_def)
erts_free(ERTS_ALC_T_DB_MS_CMPL_HEAP, (void *) heap.vars);
+ *freasonp = context.freason;
return bp;
}
@@ -1996,7 +2029,7 @@ Eterm db_prog_match(Process *c_p,
Process *tmpp;
Process *current_scheduled;
ErtsSchedulerData *esdp;
- Eterm (*bif)(Process*, ...);
+ BIF_RETTYPE (*bif)(BIF_ALIST);
Eterm bif_args[3];
int fail_label;
#ifdef DMC_DEBUG
@@ -2303,8 +2336,8 @@ restart:
*esp++ = t;
break;
case matchCall0:
- bif = (Eterm (*)(Process*, ...)) *pc++;
- t = (*bif)(build_proc, bif_args);
+ bif = (BIF_RETTYPE (*)(BIF_ALIST)) *pc++;
+ t = (*bif)(build_proc, bif_args, NULL);
if (is_non_value(t)) {
if (do_catch)
t = FAIL_TERM;
@@ -2314,8 +2347,8 @@ restart:
*esp++ = t;
break;
case matchCall1:
- bif = (Eterm (*)(Process*, ...)) *pc++;
- t = (*bif)(build_proc, esp-1);
+ bif = (BIF_RETTYPE (*)(BIF_ALIST)) *pc++;
+ t = (*bif)(build_proc, esp-1, NULL);
if (is_non_value(t)) {
if (do_catch)
t = FAIL_TERM;
@@ -2325,10 +2358,10 @@ restart:
esp[-1] = t;
break;
case matchCall2:
- bif = (Eterm (*)(Process*, ...)) *pc++;
+ bif = (BIF_RETTYPE (*)(BIF_ALIST)) *pc++;
bif_args[0] = esp[-1];
bif_args[1] = esp[-2];
- t = (*bif)(build_proc, bif_args);
+ t = (*bif)(build_proc, bif_args, NULL);
if (is_non_value(t)) {
if (do_catch)
t = FAIL_TERM;
@@ -2339,11 +2372,11 @@ restart:
esp[-1] = t;
break;
case matchCall3:
- bif = (Eterm (*)(Process*, ...)) *pc++;
+ bif = (BIF_RETTYPE (*)(BIF_ALIST)) *pc++;
bif_args[0] = esp[-1];
bif_args[1] = esp[-2];
bif_args[2] = esp[-3];
- t = (*bif)(build_proc, bif_args);
+ t = (*bif)(build_proc, bif_args, NULL);
if (is_non_value(t)) {
if (do_catch)
t = FAIL_TERM;
@@ -2612,7 +2645,10 @@ restart:
break;
case matchCaller:
ASSERT(c_p == self);
- if (!(c_p->cp) || !(cp = find_function_from_pc(c_p->cp))) {
+ t = c_p->stop[0];
+ if (is_not_CP(t)) {
+ *esp++ = am_undefined;
+ } else if (!(cp = find_function_from_pc(cp_val(t)))) {
*esp++ = am_undefined;
} else {
ehp = HAllocX(build_proc, 4, HEAP_XTRA);
@@ -2999,6 +3035,34 @@ void db_free_term(DbTable *tb, void* basep, Uint offset)
erts_db_free(ERTS_ALC_T_DB_TERM, tb, basep, size);
}
+Uint db_term_size(DbTable *tb, void* basep, Uint offset)
+{
+ DbTerm* db = (DbTerm*) ((byte*)basep + offset);
+ if (tb->common.compress) {
+ return db_alloced_size_comp(db);
+ }
+ else {
+ return offset + offsetof(DbTerm,tpl) + db->size*sizeof(Eterm);
+ }
+}
+
+void db_free_term_no_tab(int compress, void* basep, Uint offset)
+{
+ DbTerm* db = (DbTerm*) ((byte*)basep + offset);
+ Uint size;
+ if (compress) {
+ db_cleanup_offheap_comp(db);
+ size = db_alloced_size_comp(db);
+ }
+ else {
+ ErlOffHeap tmp_oh;
+ tmp_oh.first = db->first_oh;
+ erts_cleanup_offheap(&tmp_oh);
+ size = offset + offsetof(DbTerm,tpl) + db->size*sizeof(Eterm);
+ }
+ erts_db_free(ERTS_ALC_T_DB_TERM, NULL, basep, size);
+}
+
static ERTS_INLINE Uint align_up(Uint value, Uint pow2)
{
ASSERT((pow2 & (pow2-1)) == 0);
@@ -3007,7 +3071,7 @@ static ERTS_INLINE Uint align_up(Uint value, Uint pow2)
/* Compressed size of an uncompressed term
*/
-static Uint db_size_dbterm_comp(DbTableCommon* tb, Eterm obj)
+static Uint db_size_dbterm_comp(int keypos, Eterm obj)
{
Eterm* tpl = tuple_val(obj);
int i;
@@ -3016,11 +3080,11 @@ static Uint db_size_dbterm_comp(DbTableCommon* tb, Eterm obj)
+ sizeof(Uint); /* "alloc_size" */
for (i = arityval(*tpl); i>0; i--) {
- if (i != tb->keypos && is_not_immed(tpl[i])) {
+ if (i != keypos && is_not_immed(tpl[i])) {
size += erts_encode_ext_size_ets(tpl[i]);
}
}
- size += size_object(tpl[tb->keypos]) * sizeof(Eterm);
+ size += size_object(tpl[keypos]) * sizeof(Eterm);
return align_up(size, sizeof(Uint));
}
@@ -3036,13 +3100,13 @@ static ERTS_INLINE byte* elem2ext(Eterm* tpl, Uint ix)
return (byte*)tpl + (tpl[ix] >> _TAG_PRIMARY_SIZE);
}
-static void* copy_to_comp(DbTableCommon* tb, Eterm obj, DbTerm* dest,
+static void* copy_to_comp(int keypos, Eterm obj, DbTerm* dest,
Uint alloc_size)
{
ErlOffHeap tmp_offheap;
Eterm* src = tuple_val(obj);
Eterm* tpl = dest->tpl;
- Eterm key = src[tb->keypos];
+ Eterm key = src[keypos];
int arity = arityval(src[0]);
union {
Eterm* ep;
@@ -3056,10 +3120,10 @@ static void* copy_to_comp(DbTableCommon* tb, Eterm obj, DbTerm* dest,
tpl[arity + 1] = alloc_size;
tmp_offheap.first = NULL;
- tpl[tb->keypos] = copy_struct(key, size_object(key), &top.ep, &tmp_offheap);
+ tpl[keypos] = copy_struct(key, size_object(key), &top.ep, &tmp_offheap);
dest->first_oh = tmp_offheap.first;
for (i=1; i<=arity; i++) {
- if (i != tb->keypos) {
+ if (i != keypos) {
if (is_immed(src[i])) {
tpl[i] = src[i];
}
@@ -3131,14 +3195,17 @@ void* db_store_term(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj)
}
-void* db_store_term_comp(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj)
+void* db_store_term_comp(DbTableCommon *tb, /* May be NULL */
+ int keypos,
+ DbTerm* old,
+ Uint offset,Eterm obj)
{
- Uint new_sz = offset + db_size_dbterm_comp(tb, obj);
+ Uint new_sz = offset + db_size_dbterm_comp(keypos, obj);
byte* basep;
DbTerm* newp;
byte* top;
- ASSERT(tb->compress);
+ ASSERT(tb == NULL || tb->compress);
if (old != 0) {
Uint old_sz = db_alloced_size_comp(old);
db_cleanup_offheap_comp(old);
@@ -3158,7 +3225,7 @@ void* db_store_term_comp(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj)
}
newp->size = size_object(obj);
- top = copy_to_comp(tb, obj, newp, new_sz);
+ top = copy_to_comp(keypos, obj, newp, new_sz);
ASSERT(top <= basep + new_sz); (void)top;
/* ToDo: Maybe realloc if ((basep+new_sz) - top) > WASTED_SPACE_LIMIT */
@@ -3173,7 +3240,7 @@ void db_finalize_resize(DbUpdateHandle* handle, Uint offset)
DbTerm* newDbTerm;
Uint alloc_sz = offset +
(tbl->common.compress ?
- db_size_dbterm_comp(&tbl->common, make_tuple(handle->dbterm->tpl)) :
+ db_size_dbterm_comp(tbl->common.keypos, make_tuple(handle->dbterm->tpl)) :
sizeof(DbTerm)+sizeof(Eterm)*(handle->new_size-1));
byte* newp = erts_db_alloc(ERTS_ALC_T_DB_TERM, tbl, alloc_sz);
byte* oldp = *(handle->bp);
@@ -3189,7 +3256,7 @@ void db_finalize_resize(DbUpdateHandle* handle, Uint offset)
/* make a flat copy */
if (tbl->common.compress) {
- copy_to_comp(&tbl->common, make_tuple(handle->dbterm->tpl),
+ copy_to_comp(tbl->common.keypos, make_tuple(handle->dbterm->tpl),
newDbTerm, alloc_sz);
db_free_tmp_uncompressed(handle->dbterm);
}
@@ -4865,6 +4932,11 @@ static DMCRet dmc_expr(DMCContext *context,
Eterm tmp;
Eterm *p;
+ if (stack_guard(context->stack_limit)) {
+ context->freason = SYSTEM_LIMIT;
+ RETURN_TERM_ERROR("Excessive nesting; system limit reached near: %T",
+ t, context, *constant);
+ }
switch (t & _TAG_PRIMARY_MASK) {
case TAG_PRIMARY_LIST:
@@ -5238,7 +5310,8 @@ static Eterm match_spec_test(Process *p, Eterm against, Eterm spec, int trace)
Eterm l;
Uint32 ret_flags;
Uint sz;
- BeamInstr *save_cp;
+ Eterm save_cp;
+ Uint freason;
if (trace && !(is_list(against) || against == NIL)) {
return THE_NON_VALUE;
@@ -5247,11 +5320,11 @@ static Eterm match_spec_test(Process *p, Eterm against, Eterm spec, int trace)
const Uint cflags = (DCOMP_TRACE | DCOMP_FAKE_DESTRUCTIVE |
DCOMP_CALL_TRACE | DCOMP_ALLOW_TRACE_OPS);
lint_res = db_match_set_lint(p, spec, cflags);
- mps = db_match_set_compile(p, spec, cflags);
+ mps = db_match_set_compile(p, spec, cflags, &freason);
} else {
const Uint cflags = (DCOMP_TABLE | DCOMP_FAKE_DESTRUCTIVE);
lint_res = db_match_set_lint(p, spec, cflags);
- mps = db_match_set_compile(p, spec, cflags);
+ mps = db_match_set_compile(p, spec, cflags, &freason);
}
if (mps == NULL) {
@@ -5282,13 +5355,13 @@ static Eterm match_spec_test(Process *p, Eterm against, Eterm spec, int trace)
++n;
l = CDR(list_val(l));
}
- save_cp = p->cp;
- p->cp = NULL;
+ save_cp = p->stop[0];
+ p->stop[0] = NIL;
res = erts_match_set_run_trace(p, p,
mps, arr, n,
ERTS_PAM_COPY_RESULT|ERTS_PAM_IGNORE_TRACE_SILENT,
&ret_flags);
- p->cp = save_cp;
+ p->stop[0] = save_cp;
} else {
n = 0;
arr = NULL;
@@ -5358,17 +5431,13 @@ void db_free_tmp_uncompressed(DbTerm* obj)
erts_free(ERTS_ALC_T_TMP, obj);
}
-Eterm db_match_dbterm(DbTableCommon* tb, Process* c_p, Binary* bprog,
- DbTerm* obj, Eterm** hpp, Uint extra)
+Eterm db_match_dbterm_uncompressed(DbTableCommon* tb, Process* c_p, Binary* bprog,
+ DbTerm* obj, Eterm** hpp, Uint extra)
{
enum erts_pam_run_flags flags;
Uint32 dummy;
Eterm res;
- if (tb->compress) {
- obj = db_alloc_tmp_uncompressed(tb, obj);
- }
-
flags = (hpp ?
ERTS_PAM_COPY_RESULT | ERTS_PAM_CONTIGUOUS_TUPLE :
ERTS_PAM_TMP_RESULT | ERTS_PAM_CONTIGUOUS_TUPLE);
@@ -5380,9 +5449,19 @@ Eterm db_match_dbterm(DbTableCommon* tb, Process* c_p, Binary* bprog,
if (is_value(res) && hpp!=NULL) {
*hpp = HAlloc(c_p, extra);
}
+ return res;
+}
+Eterm db_match_dbterm(DbTableCommon* tb, Process* c_p, Binary* bprog,
+ DbTerm* obj, Eterm** hpp, Uint extra)
+{
+ Eterm res;
+ if (tb->compress) {
+ obj = db_alloc_tmp_uncompressed(tb, obj);
+ }
+ res = db_match_dbterm_uncompressed(tb, c_p, bprog, obj, hpp, extra);
if (tb->compress) {
- db_free_tmp_uncompressed(obj);
+ db_free_tmp_uncompressed(obj);
}
return res;
}
diff --git a/erts/emulator/beam/erl_db_util.h b/erts/emulator/beam/erl_db_util.h
index e41f2b0e6d..86170677ba 100644
--- a/erts/emulator/beam/erl_db_util.h
+++ b/erts/emulator/beam/erl_db_util.h
@@ -43,18 +43,19 @@
* checks for negative returns and issues BIF_ERRORS based
* upon these values.
*/
-#define DB_ERROR_NONE 0 /* No error */
-#define DB_ERROR_BADITEM -1 /* The item was malformed ie no
- tuple or to small*/
-#define DB_ERROR_BADTABLE -2 /* The Table is inconsisitent */
-#define DB_ERROR_SYSRES -3 /* Out of system resources */
-#define DB_ERROR_BADKEY -4 /* Returned if a key that should
- exist does not. */
+#define DB_ERROR_NONE_FALSE 1 /* No error am_false reult */
+#define DB_ERROR_NONE 0 /* No error */
+#define DB_ERROR_BADITEM -1 /* The item was malformed ie no
+ tuple or to small*/
+#define DB_ERROR_BADTABLE -2 /* The Table is inconsisitent */
+#define DB_ERROR_SYSRES -3 /* Out of system resources */
+#define DB_ERROR_BADKEY -4 /* Returned if a key that should
+ exist does not. */
#define DB_ERROR_BADPARAM -5 /* Returned if a specified slot does
not exist (hash table only) or
the state parameter in db_match_object
is broken.*/
-#define DB_ERROR_UNSPEC -10 /* Unspecified error */
+#define DB_ERROR_UNSPEC -10 /* Unspecified error */
/*#define DEBUG_CLONE*/
@@ -92,7 +93,7 @@ typedef struct {
int flags;
union {
struct {
- erts_rwmtx_t* lck;
+ struct DbTableHashLockAndCounter* lck_ctr;
} hash;
struct {
struct DbTableCATreeNode* base_node;
@@ -130,7 +131,8 @@ typedef struct db_table_method
Eterm* ret);
int (*db_put)(DbTable* tb, /* [in out] */
Eterm obj,
- int key_clash_fail); /* DB_ERROR_BADKEY if key exists */
+ int key_clash_fail, /* DB_ERROR_BADKEY if key exists */
+ SWord *consumed_reds_p);
int (*db_get)(Process* p,
DbTable* tb, /* [in out] */
Eterm key,
@@ -234,14 +236,20 @@ typedef struct db_table_method
** dbterm was not updated. If the handle was of a new object and cret is
** not DB_ERROR_NONE, the object is removed from the table. */
void (*db_finalize_dbterm)(int cret, DbUpdateHandle* handle);
-
+ void* (*db_eterm_to_dbterm)(int compress, int keypos, Eterm obj);
+ void* (*db_dbterm_list_prepend)(void* list, void* db_term);
+ void* (*db_dbterm_list_remove_first)(void** list);
+ int (*db_put_dbterm)(DbTable* tb, /* [in out] */
+ void* obj,
+ int key_clash_fail, /* DB_ERROR_BADKEY if key exists */
+ SWord *consumed_reds_p);
+ void (*db_free_dbterm)(int compressed, void* obj);
+ Eterm (*db_get_dbterm_key)(DbTable* tb, void* db_term);
int (*db_get_binary_info)(Process*, DbTable* tb, Eterm key, Eterm* ret);
-
/* Raw first/next same as first/next but also return pseudo deleted keys.
Only internal use by ets:info(_,binary) */
int (*db_raw_first)(Process*, DbTable*, Eterm* ret);
int (*db_raw_next)(Process*, DbTable*, Eterm key, Eterm* ret);
-
} DbTableMethod;
typedef struct db_fixation {
@@ -312,6 +320,12 @@ typedef struct db_table_common {
int keypos; /* defaults to 1 */
int compress;
+ /* For unfinished operations that needs to be helped */
+ void (*continuation)(long *reds_ptr,
+ void** state,
+ void* extra_context); /* To help yielded process */
+ erts_atomic_t continuation_state;
+ Binary* continuation_res_bin;
#ifdef ETS_DBG_FORCE_TRAP
erts_atomic_t dbg_force_trap; /* &1 force enabled, &2 trap this call */
#endif
@@ -359,6 +373,7 @@ Eterm db_copy_from_comp(DbTableCommon* tb, DbTerm* bp, Eterm** hpp,
ErlOffHeap* off_heap);
int db_eq_comp(DbTableCommon* tb, Eterm a, DbTerm* b);
DbTerm* db_alloc_tmp_uncompressed(DbTableCommon* tb, DbTerm* org);
+void db_free_tmp_uncompressed(DbTerm* obj);
ERTS_GLB_INLINE Eterm db_copy_object_from_ets(DbTableCommon* tb, DbTerm* bp,
Eterm** hpp, ErlOffHeap* off_heap);
@@ -407,6 +422,7 @@ ERTS_GLB_INLINE int db_eq(DbTableCommon* tb, Eterm a, DbTerm* b)
#define DB_READ (DB_PROTECTED|DB_PUBLIC)
#define DB_WRITE DB_PUBLIC
#define DB_INFO (DB_PROTECTED|DB_PUBLIC|DB_PRIVATE)
+#define DB_READ_TBL_STRUCT (DB_PROTECTED|DB_PUBLIC|DB_PRIVATE|DB_BUSY)
#define ONLY_WRITER(P,T) (((T)->common.status & (DB_PRIVATE|DB_PROTECTED)) \
&& (T)->common.owner == (P)->common.id)
@@ -424,8 +440,13 @@ void db_initialize_util(void);
Eterm db_getkey(int keypos, Eterm obj);
void db_cleanup_offheap_comp(DbTerm* p);
void db_free_term(DbTable *tb, void* basep, Uint offset);
+void db_free_term_no_tab(int compress, void* basep, Uint offset);
+Uint db_term_size(DbTable *tb, void* basep, Uint offset);
void* db_store_term(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj);
-void* db_store_term_comp(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj);
+void* db_store_term_comp(DbTableCommon *tb, /*May be NULL*/
+ int keypos,
+ DbTerm* old,
+ Uint offset,Eterm obj);
Eterm db_copy_element_from_ets(DbTableCommon* tb, Process* p, DbTerm* obj,
Uint pos, Eterm** hpp, Uint extra);
int db_has_map(Eterm obj);
@@ -436,9 +457,8 @@ void db_do_update_element(DbUpdateHandle* handle,
Eterm newval);
void db_finalize_resize(DbUpdateHandle* handle, Uint offset);
Eterm db_add_counter(Eterm** hpp, Wterm counter, Eterm incr);
-Eterm db_match_set_lint(Process *p, Eterm matchexpr, Uint flags);
Binary *db_match_set_compile(Process *p, Eterm matchexpr,
- Uint flags);
+ Uint flags, Uint *freasonp);
int db_match_keeps_key(int keypos, Eterm match, Eterm guard, Eterm body);
int erts_db_match_prog_destructor(Binary *);
@@ -520,9 +540,12 @@ typedef struct dmc_err_info {
Binary *db_match_compile(Eterm *matchexpr, Eterm *guards,
Eterm *body, int num_matches,
Uint flags,
- DMCErrInfo *err_info);
+ DMCErrInfo *err_info,
+ Uint *freasonp);
/* Returns newly allocated MatchProg binary with refc == 0*/
+Eterm db_match_dbterm_uncompressed(DbTableCommon* tb, Process* c_p, Binary* bprog,
+ DbTerm* obj, Eterm** hpp, Uint extra);
Eterm db_match_dbterm(DbTableCommon* tb, Process* c_p, Binary* bprog,
DbTerm* obj, Eterm** hpp, Uint extra);
@@ -546,9 +569,9 @@ ERTS_GLB_INLINE Eterm erts_db_make_match_prog_ref(Process *p, Binary *mp, Eterm
ERTS_GLB_INLINE Binary *erts_db_get_match_prog_binary(Eterm term);
ERTS_GLB_INLINE Binary *erts_db_get_match_prog_binary_unchecked(Eterm term);
-/* @brief Ensure off-heap header is word aligned, make a temporary copy if not.
- * Needed when inspecting ETS off-heap lists that may contain unaligned
- * ProcBins if table is 'compressed'.
+/** @brief Ensure off-heap header is word aligned, make a temporary copy if
+ * not. Needed when inspecting ETS off-heap lists that may contain unaligned
+ * ProcBins if table is 'compressed'.
*/
struct erts_tmp_aligned_offheap
{
diff --git a/erts/emulator/beam/erl_flxctr.c b/erts/emulator/beam/erl_flxctr.c
index 35f4a21508..35c4de1a27 100644
--- a/erts/emulator/beam/erl_flxctr.c
+++ b/erts/emulator/beam/erl_flxctr.c
@@ -46,6 +46,29 @@ typedef enum {
ERTS_FLXCTR_SNAPSHOT_ONGOING_TP_THREAD_DO_FREE = 2
} erts_flxctr_snapshot_status;
+#define ERTS_FLXCTR_DECENTRALIZED_COUNTER_ARRAY_SIZE \
+ (sizeof(ErtsFlxCtrDecentralizedCtrArray) + \
+ (sizeof(ErtsFlxCtrDecentralizedCtrArrayElem) * \
+ ERTS_FLXCTR_DECENTRALIZED_NO_SLOTS) + \
+ ERTS_CACHE_LINE_SIZE)
+
+#ifdef DEBUG
+#define FLXCTR_MEM_DEBUG 1
+#endif
+
+#ifdef FLXCTR_MEM_DEBUG
+static erts_atomic_t debug_mem_usage;
+#endif
+
+#ifdef FLXCTR_MEM_DEBUG
+#define FLXCTR_FREE(ALLOC_TYPE, ADDRESS) do { \
+ erts_free(ALLOC_TYPE, ADDRESS); \
+ erts_atomic_add_mb(&debug_mem_usage, -ERTS_FLXCTR_DECENTRALIZED_COUNTER_ARRAY_SIZE); \
+ } while(0)
+#else
+#define FLXCTR_FREE(ALLOC_TYPE, ADDRESS) erts_free(ALLOC_TYPE, ADDRESS)
+#endif
+
static void
thr_prg_wake_up_and_count(void* bin_p)
{
@@ -72,13 +95,13 @@ thr_prg_wake_up_and_count(void* bin_p)
}
/* Announce that the snapshot is done */
{
- Sint expected = ERTS_FLXCTR_SNAPSHOT_ONGOING;
- if (expected != erts_atomic_cmpxchg_mb(&next->snapshot_status,
- ERTS_FLXCTR_SNAPSHOT_NOT_ONGOING,
- expected)) {
- /* The CAS failed which means that this thread need to free the next array. */
- erts_free(info->alloc_type, next->block_start);
- }
+ Sint expected = ERTS_FLXCTR_SNAPSHOT_ONGOING;
+ if (expected != erts_atomic_cmpxchg_mb(&next->snapshot_status,
+ ERTS_FLXCTR_SNAPSHOT_NOT_ONGOING,
+ expected)) {
+ /* The CAS failed which means that this thread need to free the next array. */
+ FLXCTR_FREE(info->alloc_type, next->block_start);
+ }
}
/* Resume the process that requested the snapshot */
erts_proc_lock(p, ERTS_PROC_LOCK_STATUS);
@@ -86,7 +109,7 @@ thr_prg_wake_up_and_count(void* bin_p)
erts_resume(p, ERTS_PROC_LOCK_STATUS);
}
/* Free the memory that is no longer needed */
- erts_free(info->alloc_type, array->block_start);
+ FLXCTR_FREE(info->alloc_type, array->block_start);
erts_proc_unlock(p, ERTS_PROC_LOCK_STATUS);
erts_proc_dec_refc(p);
erts_bin_release(bin);
@@ -141,6 +164,14 @@ static void suspend_until_thr_prg(Process* p)
erts_schedule_thr_prgr_later_op(thr_prg_wake_up_later, state_bin, &info->later_op);
}
+size_t erts_flxctr_nr_of_allocated_bytes(ErtsFlxCtr* c)
+{
+ if (c->is_decentralized) {
+ return ERTS_FLXCTR_DECENTRALIZED_COUNTER_ARRAY_SIZE;
+ } else {
+ return 0;
+ }
+}
static ErtsFlxCtrDecentralizedCtrArray*
create_decentralized_ctr_array(ErtsAlcType_t alloc_type, Uint nr_of_counters) {
@@ -148,14 +179,14 @@ create_decentralized_ctr_array(ErtsAlcType_t alloc_type, Uint nr_of_counters) {
the array field is located at the start of a cache line */
char* bytes =
erts_alloc(alloc_type,
- sizeof(ErtsFlxCtrDecentralizedCtrArray) +
- (sizeof(ErtsFlxCtrDecentralizedCtrArrayElem) *
- ERTS_FLXCTR_DECENTRALIZED_NO_SLOTS) +
- ERTS_CACHE_LINE_SIZE);
+ ERTS_FLXCTR_DECENTRALIZED_COUNTER_ARRAY_SIZE);
void* block_start = bytes;
int bytes_to_next_cacheline_border;
ErtsFlxCtrDecentralizedCtrArray* array;
int i, sched;
+#ifdef FLXCTR_MEM_DEBUG
+ erts_atomic_add_mb(&debug_mem_usage, ERTS_FLXCTR_DECENTRALIZED_COUNTER_ARRAY_SIZE);
+#endif
bytes = &bytes[offsetof(ErtsFlxCtrDecentralizedCtrArray, array)];
bytes_to_next_cacheline_border =
ERTS_CACHE_LINE_SIZE - (((Uint)bytes) % ERTS_CACHE_LINE_SIZE);
@@ -178,6 +209,9 @@ create_decentralized_ctr_array(ErtsAlcType_t alloc_type, Uint nr_of_counters) {
void erts_flxctr_setup(int decentralized_counter_groups)
{
reader_groups_array_size = decentralized_counter_groups+1;
+#ifdef FLXCTR_MEM_DEBUG
+ erts_atomic_init_mb(&debug_mem_usage, 0);
+#endif
}
void erts_flxctr_init(ErtsFlxCtr* c,
@@ -203,7 +237,7 @@ void erts_flxctr_init(ErtsFlxCtr* c,
}
}
-void erts_flxctr_destroy(ErtsFlxCtr* c, ErtsAlcType_t type)
+void erts_flxctr_destroy(ErtsFlxCtr* c, ErtsAlcType_t alloc_type)
{
if (c->is_decentralized) {
if (erts_flxctr_is_snapshot_ongoing(c)) {
@@ -220,10 +254,10 @@ void erts_flxctr_destroy(ErtsFlxCtr* c, ErtsAlcType_t type)
snapshot is ongoing anymore and the freeing needs
to be done here */
ERTS_ASSERT(!erts_flxctr_is_snapshot_ongoing(c));
- erts_free(type, array->block_start);
+ FLXCTR_FREE(alloc_type, array->block_start);
}
} else {
- erts_free(type, ERTS_FLXCTR_GET_CTR_ARRAY_PTR(c)->block_start);
+ FLXCTR_FREE(alloc_type, ERTS_FLXCTR_GET_CTR_ARRAY_PTR(c)->block_start);
}
}
}
@@ -257,7 +291,7 @@ erts_flxctr_snapshot(ErtsFlxCtr* c,
ErtsFlxCtrSnapshotResult res =
{.type = ERTS_FLXCTR_TRY_AGAIN_AFTER_TRAP};
suspend_until_thr_prg(p);
- erts_free(alloc_type, new_array->block_start);
+ FLXCTR_FREE(alloc_type, new_array->block_start);
return res;
}
/* Create binary with info about the operation that can be
@@ -364,7 +398,19 @@ void erts_flxctr_reset(ErtsFlxCtr* c,
}
-void erts_flxctr_set_slot(int group) {
+void erts_flxctr_set_slot(int group)
+{
ErtsSchedulerData *esdp = erts_get_scheduler_data();
esdp->flxctr_slot_no = group;
}
+
+Sint erts_flxctr_debug_memory_usage(void)
+{
+#ifdef FLXCTR_MEM_DEBUG
+ return erts_atomic_read_mb(&debug_mem_usage);
+#else
+ return -1;
+#endif
+}
+
+
diff --git a/erts/emulator/beam/erl_flxctr.h b/erts/emulator/beam/erl_flxctr.h
index 5cab02b9eb..df60f3651e 100644
--- a/erts/emulator/beam/erl_flxctr.h
+++ b/erts/emulator/beam/erl_flxctr.h
@@ -288,6 +288,24 @@ int erts_flxctr_is_snapshot_ongoing(ErtsFlxCtr* c);
*/
int erts_flxctr_suspend_until_thr_prg_if_snapshot_ongoing(ErtsFlxCtr* c, Process* p);
+/**
+ * @brief This function returns the number of bytes that are allocated
+ * for for the given FlxCtr.
+ *
+ * @return nr of bytes allocated for the FlxCtr
+ */
+size_t erts_flxctr_nr_of_allocated_bytes(ErtsFlxCtr* c);
+
+/**
+ * @brief This debug function returns the amount of memory allocated
+ * for decentralized counter arrays when compiled with the DEBUG
+ * macro. The function returns -1 if the DEBUG macro is undefined.
+ *
+ * @return number of bytes allocated for decentralized counter arrays
+ * if in debug mode and otherwise -1
+ */
+Sint erts_flxctr_debug_memory_usage(void);
+
/* End: Public Interface */
/* Internal Declarations */
diff --git a/erts/emulator/beam/erl_fun.c b/erts/emulator/beam/erl_fun.c
index 9c866250bb..79a1fdb8b9 100644
--- a/erts/emulator/beam/erl_fun.c
+++ b/erts/emulator/beam/erl_fun.c
@@ -100,27 +100,6 @@ int erts_fun_table_sz(void)
}
ErlFunEntry*
-erts_put_fun_entry(Eterm mod, int uniq, int index)
-{
- ErlFunEntry template;
- ErlFunEntry* fe;
- erts_aint_t refc;
- ASSERT(is_atom(mod));
- template.old_uniq = uniq;
- template.old_index = index;
- template.module = mod;
- erts_fun_write_lock();
- fe = (ErlFunEntry *) hash_put(&erts_fun_table, (void*) &template);
- sys_memset(fe->uniq, 0, sizeof(fe->uniq));
- fe->index = 0;
- refc = erts_refc_inctest(&fe->refc, 0);
- if (refc < 2) /* New or pending delete */
- erts_refc_inc(&fe->refc, 1);
- erts_fun_write_unlock();
- return fe;
-}
-
-ErlFunEntry*
erts_put_fun_entry2(Eterm mod, int old_uniq, int old_index,
byte* uniq, int index, int arity)
{
@@ -130,12 +109,12 @@ erts_put_fun_entry2(Eterm mod, int old_uniq, int old_index,
ASSERT(is_atom(mod));
template.old_uniq = old_uniq;
- template.old_index = old_index;
+ template.index = index;
template.module = mod;
erts_fun_write_lock();
fe = (ErlFunEntry *) hash_put(&erts_fun_table, (void*) &template);
sys_memcpy(fe->uniq, uniq, sizeof(fe->uniq));
- fe->index = index;
+ fe->old_index = old_index;
fe->arity = arity;
refc = erts_refc_inctest(&fe->refc, 0);
if (refc < 2) /* New or pending delete */
@@ -144,13 +123,6 @@ erts_put_fun_entry2(Eterm mod, int old_uniq, int old_index,
return fe;
}
-struct my_key {
- Eterm mod;
- byte* uniq;
- int index;
- ErlFunEntry* fe;
-};
-
ErlFunEntry*
erts_get_fun_entry(Eterm mod, int uniq, int index)
{
@@ -159,7 +131,7 @@ erts_get_fun_entry(Eterm mod, int uniq, int index)
ASSERT(is_atom(mod));
template.old_uniq = uniq;
- template.old_index = index;
+ template.index = index;
template.module = mod;
erts_fun_read_lock();
ret = (ErlFunEntry *) hash_get(&erts_fun_table, (void*) &template);
@@ -199,36 +171,33 @@ erts_erase_fun_entry(ErlFunEntry* fe)
erts_fun_write_unlock();
}
+struct fun_purge_foreach_args {
+ BeamInstr *start;
+ BeamInstr *end;
+};
+
+static void fun_purge_foreach(ErlFunEntry *fe, struct fun_purge_foreach_args *arg)
+{
+ BeamInstr* addr = fe->address;
+ if (arg->start <= addr && addr < arg->end) {
+ fe->pend_purge_address = addr;
+ ERTS_THR_WRITE_MEMORY_BARRIER;
+ fe->address = unloaded_fun;
+#ifdef HIPE
+ fe->pend_purge_native_address = fe->native_address;
+ hipe_set_closure_stub(fe);
+#endif
+ erts_purge_state_add_fun(fe);
+ }
+}
+
void
erts_fun_purge_prepare(BeamInstr* start, BeamInstr* end)
{
- int limit;
- HashBucket** bucket;
- int i;
+ struct fun_purge_foreach_args args = {start, end};
erts_fun_read_lock();
- limit = erts_fun_table.size;
- bucket = erts_fun_table.bucket;
- for (i = 0; i < limit; i++) {
- HashBucket* b = bucket[i];
-
- while (b) {
- ErlFunEntry* fe = (ErlFunEntry *) b;
- BeamInstr* addr = fe->address;
-
- if (start <= addr && addr < end) {
- fe->pend_purge_address = addr;
- ERTS_THR_WRITE_MEMORY_BARRIER;
- fe->address = unloaded_fun;
-#ifdef HIPE
- fe->pend_purge_native_address = fe->native_address;
- hipe_set_closure_stub(fe);
-#endif
- erts_purge_state_add_fun(fe);
- }
- b = b->next;
- }
- }
+ hash_foreach(&erts_fun_table, (HFOREACH_FUN)fun_purge_foreach, &args);
erts_fun_read_unlock();
}
@@ -278,36 +247,34 @@ erts_fun_purge_complete(ErlFunEntry **funs, Uint no)
ERTS_THR_WRITE_MEMORY_BARRIER;
}
+struct dump_fun_foreach_args {
+ fmtfn_t to;
+ void *to_arg;
+};
+
+static void
+dump_fun_foreach(ErlFunEntry *fe, struct dump_fun_foreach_args *args)
+{
+ erts_print(args->to, args->to_arg, "=fun\n");
+ erts_print(args->to, args->to_arg, "Module: %T\n", fe->module);
+ erts_print(args->to, args->to_arg, "Uniq: %d\n", fe->old_uniq);
+ erts_print(args->to, args->to_arg, "Index: %d\n",fe->old_index);
+ erts_print(args->to, args->to_arg, "Address: %p\n", fe->address);
+#ifdef HIPE
+ erts_print(args->to, args->to_arg, "Native_address: %p\n", fe->native_address);
+#endif
+ erts_print(args->to, args->to_arg, "Refc: %ld\n", erts_refc_read(&fe->refc, 1));
+}
+
void
erts_dump_fun_entries(fmtfn_t to, void *to_arg)
{
- int limit;
- HashBucket** bucket;
- int i;
+ struct dump_fun_foreach_args args = {to, to_arg};
int lock = !ERTS_IS_CRASH_DUMPING;
-
if (lock)
erts_fun_read_lock();
- limit = erts_fun_table.size;
- bucket = erts_fun_table.bucket;
- for (i = 0; i < limit; i++) {
- HashBucket* b = bucket[i];
-
- while (b) {
- ErlFunEntry* fe = (ErlFunEntry *) b;
- erts_print(to, to_arg, "=fun\n");
- erts_print(to, to_arg, "Module: %T\n", fe->module);
- erts_print(to, to_arg, "Uniq: %d\n", fe->old_uniq);
- erts_print(to, to_arg, "Index: %d\n",fe->old_index);
- erts_print(to, to_arg, "Address: %p\n", fe->address);
-#ifdef HIPE
- erts_print(to, to_arg, "Native_address: %p\n", fe->native_address);
-#endif
- erts_print(to, to_arg, "Refc: %ld\n", erts_refc_read(&fe->refc, 1));
- b = b->next;
- }
- }
+ hash_foreach(&erts_fun_table, (HFOREACH_FUN)dump_fun_foreach, &args);
if (lock)
erts_fun_read_unlock();
}
@@ -315,15 +282,27 @@ erts_dump_fun_entries(fmtfn_t to, void *to_arg)
static HashValue
fun_hash(ErlFunEntry* obj)
{
- return (HashValue) (obj->old_uniq ^ obj->old_index ^ atom_val(obj->module));
+ return (HashValue) (obj->old_uniq ^ obj->index ^ atom_val(obj->module));
}
static int
fun_cmp(ErlFunEntry* obj1, ErlFunEntry* obj2)
{
- return !(obj1->module == obj2->module &&
+ /*
+ * OTP 23: Use 'index' (instead of 'old_index') when comparing fun
+ * entries. In OTP 23, multiple make_fun2 instructions may refer to the
+ * the same 'index' (for the wrapper function generated for the
+ * 'fun F/A' syntax).
+ *
+ * This is safe when loading code compiled with OTP R15 and later,
+ * because since R15 (2011), the 'index' has been reliably equal
+ * to 'old_index'. The loader refuses to load modules compiled before
+ * OTP R15.
+ */
+
+ return !(obj1->module == obj2->module &&
obj1->old_uniq == obj2->old_uniq &&
- obj1->old_index == obj2->old_index);
+ obj1->index == obj2->index);
}
static ErlFunEntry*
@@ -333,7 +312,7 @@ fun_alloc(ErlFunEntry* template)
sizeof(ErlFunEntry));
obj->old_uniq = template->old_uniq;
- obj->old_index = template->old_index;
+ obj->index = template->index;
obj->module = template->module;
erts_refc_init(&obj->refc, -1);
obj->address = unloaded_fun;
diff --git a/erts/emulator/beam/erl_fun.h b/erts/emulator/beam/erl_fun.h
index fb2901d866..eefc7a95bb 100644
--- a/erts/emulator/beam/erl_fun.h
+++ b/erts/emulator/beam/erl_fun.h
@@ -74,7 +74,6 @@ void erts_init_fun_table(void);
void erts_fun_info(fmtfn_t, void *);
int erts_fun_table_sz(void);
-ErlFunEntry* erts_put_fun_entry(Eterm mod, int uniq, int index);
ErlFunEntry* erts_get_fun_entry(Eterm mod, int uniq, int index);
ErlFunEntry* erts_put_fun_entry2(Eterm mod, int old_uniq, int old_index,
diff --git a/erts/emulator/beam/erl_gc.c b/erts/emulator/beam/erl_gc.c
index 7456686f12..b74eaccea8 100644
--- a/erts/emulator/beam/erl_gc.c
+++ b/erts/emulator/beam/erl_gc.c
@@ -65,6 +65,8 @@
# define HARDDEBUG 1
#endif
+extern BeamInstr beam_apply[2];
+
/*
* Returns number of elements in an array.
*/
@@ -695,10 +697,10 @@ garbage_collect(Process* p, ErlHeapFragment *live_hf_end,
ErtsMonotonicTime start_time;
ErtsSchedulerData *esdp = erts_proc_sched_data(p);
erts_aint32_t state;
- ERTS_MSACC_PUSH_STATE();
#ifdef USE_VM_PROBES
DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
#endif
+ ERTS_MSACC_PUSH_STATE();
ERTS_UNDEF(start_time, 0);
ERTS_CHK_MBUF_SZ(p);
@@ -961,13 +963,15 @@ garbage_collect_hibernate(Process* p, int check_long_gc)
*/
erts_atomic32_read_bor_nob(&p->state, ERTS_PSFLG_GC);
ErtsGcQuickSanityCheck(p);
- ASSERT(p->stop == p->hend); /* Stack must be empty. */
+ ASSERT(p->stop == p->hend - 1); /* Only allow one continuation pointer. */
+ ASSERT(p->stop[0] == make_cp(beam_apply+1));
/*
* Do it.
*/
heap_size = p->heap_sz + (p->old_htop - p->old_heap) + p->mbuf_sz;
+ heap_size += 1; /* Reserve place for continuation pointer */
heap = (Eterm*) ERTS_HEAP_ALLOC(ERTS_ALC_T_TMP_HEAP,
sizeof(Eterm)*heap_size);
@@ -993,13 +997,11 @@ garbage_collect_hibernate(Process* p, int check_long_gc)
p->high_water = htop;
p->htop = htop;
p->hend = p->heap + heap_size;
- p->stop = p->hend;
+ p->stop = p->hend - 1;
p->heap_sz = heap_size;
heap_size = actual_size = p->htop - p->heap;
- if (heap_size == 0) {
- heap_size = 1; /* We want a heap... */
- }
+ heap_size += 1; /* Reserve place for continuation pointer */
FLAGS(p) &= ~F_FORCE_GC;
p->live_hf_end = ERTS_INVALID_HFRAG_PTR;
@@ -1015,14 +1017,15 @@ garbage_collect_hibernate(Process* p, int check_long_gc)
* hibernated.
*/
- ASSERT(p->hend - p->stop == 0); /* Empty stack */
ASSERT(actual_size < p->heap_sz);
heap = ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP, sizeof(Eterm)*heap_size);
sys_memcpy((void *) heap, (void *) p->heap, actual_size*sizeof(Eterm));
ERTS_HEAP_FREE(ERTS_ALC_T_TMP_HEAP, p->heap, p->heap_sz*sizeof(Eterm));
- p->stop = p->hend = heap + heap_size;
+ p->hend = heap + heap_size;
+ p->stop = p->hend - 1;
+ p->stop[0] = make_cp(beam_apply+1);
offs = heap - p->heap;
area = (char *) p->heap;
@@ -1769,15 +1772,6 @@ do_minor(Process *p, ErlHeapFragment *live_hf_end,
sys_memcpy(n_heap + new_sz - n, p->stop, n * sizeof(Eterm));
p->stop = n_heap + new_sz - n;
-#ifdef USE_VM_PROBES
- if (HEAP_SIZE(p) != new_sz && DTRACE_ENABLED(process_heap_grow)) {
- DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
-
- dtrace_proc_str(p, pidbuf);
- DTRACE3(process_heap_grow, pidbuf, HEAP_SIZE(p), new_sz);
- }
-#endif
-
#ifdef HARDDEBUG
disallow_heap_frag_ref_in_heap(p, n_heap, n_htop);
#endif
@@ -1786,8 +1780,19 @@ do_minor(Process *p, ErlHeapFragment *live_hf_end,
HEAP_START(p) = n_heap;
HEAP_TOP(p) = n_htop;
- HEAP_SIZE(p) = new_sz;
HEAP_END(p) = n_heap + new_sz;
+
+#ifdef USE_VM_PROBES
+ if (HEAP_SIZE(p) != new_sz && DTRACE_ENABLED(process_heap_grow)) {
+ DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
+ Uint old_sz = HEAP_SIZE(p);
+
+ HEAP_SIZE(p) = new_sz;
+ dtrace_proc_str(p, pidbuf);
+ DTRACE3(process_heap_grow, pidbuf, old_sz, new_sz);
+ }
+#endif
+ HEAP_SIZE(p) = new_sz;
}
/*
@@ -1860,15 +1865,6 @@ major_collection(Process* p, ErlHeapFragment *live_hf_end,
sys_memcpy(n_heap + new_sz - stk_sz, p->stop, stk_sz * sizeof(Eterm));
p->stop = n_heap + new_sz - stk_sz;
-#ifdef USE_VM_PROBES
- if (HEAP_SIZE(p) != new_sz && DTRACE_ENABLED(process_heap_grow)) {
- DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
-
- dtrace_proc_str(p, pidbuf);
- DTRACE3(process_heap_grow, pidbuf, HEAP_SIZE(p), new_sz);
- }
-#endif
-
#ifdef HARDDEBUG
disallow_heap_frag_ref_in_heap(p, n_heap, n_htop);
#endif
@@ -1877,8 +1873,24 @@ major_collection(Process* p, ErlHeapFragment *live_hf_end,
HEAP_START(p) = n_heap;
HEAP_TOP(p) = n_htop;
- HEAP_SIZE(p) = new_sz;
HEAP_END(p) = n_heap + new_sz;
+
+#ifdef USE_VM_PROBES
+ /* Fire process_heap_grow tracepoint after all heap references have
+ * been updated. This allows to walk the stack. */
+ if (HEAP_SIZE(p) != new_sz && DTRACE_ENABLED(process_heap_grow)) {
+ DTRACE_CHARBUF(pidbuf, DTRACE_TERM_BUF_SIZE);
+ Uint old_sz = HEAP_SIZE(p);
+
+ /* Update the heap size before firing tracepoint */
+ HEAP_SIZE(p) = new_sz;
+
+ dtrace_proc_str(p, pidbuf);
+ DTRACE3(process_heap_grow, pidbuf, old_sz, new_sz);
+ }
+#endif
+ HEAP_SIZE(p) = new_sz;
+
GEN_GCS(p) = 0;
HIGH_WATER(p) = HEAP_TOP(p);
@@ -2602,13 +2614,17 @@ setup_rootset(Process *p, Eterm *objv, int nobj, Rootset *rootset)
/*
* The process may be garbage-collected while it is terminating.
- * (fvalue contains the EXIT reason and ftrace the saved stack trace.)
+ * fvalue contains the EXIT reason.
*/
if (is_not_immed(p->fvalue)) {
roots[n].v = &p->fvalue;
roots[n].sz = 1;
n++;
}
+
+ /*
+ * The raise/3 BIF will store the stacktrace in ftrace.
+ */
if (is_not_immed(p->ftrace)) {
roots[n].v = &p->ftrace;
roots[n].sz = 1;
@@ -2618,7 +2634,7 @@ setup_rootset(Process *p, Eterm *objv, int nobj, Rootset *rootset)
/*
* If a NIF or BIF has saved arguments, they need to be added
*/
- if (erts_setup_nif_export_rootset(p, &roots[n].v, &roots[n].sz))
+ if (erts_setup_nfunc_rootset(p, &roots[n].v, &roots[n].sz))
n++;
ASSERT(n <= rootset->size);
@@ -3288,7 +3304,7 @@ offset_one_rootset(Process *p, Sint offs, char* area, Uint area_size,
offset_heap_ptr(objv, nobj, offs, area, area_size);
}
offset_off_heap(p, offs, area, area_size);
- if (erts_setup_nif_export_rootset(p, &v, &sz))
+ if (erts_setup_nfunc_rootset(p, &v, &sz))
offset_heap_ptr(v, sz, offs, area, area_size);
}
diff --git a/erts/emulator/beam/erl_global_literals.c b/erts/emulator/beam/erl_global_literals.c
new file mode 100644
index 0000000000..c3e7b8a70b
--- /dev/null
+++ b/erts/emulator/beam/erl_global_literals.c
@@ -0,0 +1,69 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2020. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "sys.h"
+#include "global.h"
+#include "erl_global_literals.h"
+
+struct literal {
+ Eterm term;
+ ErtsLiteralArea* area;
+};
+
+static struct literal literals[ERTS_NUM_GLOBAL_LITERALS];
+
+Eterm* erts_alloc_global_literal(Uint index, Uint sz)
+{
+ ErtsLiteralArea* area;
+ Uint area_sz;
+
+ ASSERT(index < ERTS_NUM_GLOBAL_LITERALS);
+ area_sz = sizeof(ErtsLiteralArea) + (sz-1)*sizeof(Eterm);
+ area = erts_alloc(ERTS_ALC_T_LITERAL, area_sz);
+ area->end = area->start + sz;
+ literals[index].area = area;
+ return area->start;
+}
+
+void erts_register_global_literal(Uint index, Eterm term)
+{
+ Eterm* start;
+
+ ASSERT(index < ERTS_NUM_GLOBAL_LITERALS);
+ start = literals[index].area->start;
+ erts_set_literal_tag(&term, start, literals[index].area->end - start);
+ literals[index].term = term;
+}
+
+Eterm erts_get_global_literal(Uint index)
+{
+ ASSERT(index < ERTS_NUM_GLOBAL_LITERALS);
+ return literals[index].term;
+}
+
+ErtsLiteralArea* erts_get_global_literal_area(Uint index)
+{
+ ASSERT(index < ERTS_NUM_GLOBAL_LITERALS);
+ return literals[index].area;
+}
diff --git a/lib/erl_interface/src/legacy/erl_error.h b/erts/emulator/beam/erl_global_literals.h
index 0cce083ae7..15e873479d 100644
--- a/lib/erl_interface/src/legacy/erl_error.h
+++ b/erts/emulator/beam/erl_global_literals.h
@@ -1,8 +1,8 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1996-2020. All Rights Reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -14,13 +14,21 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
+ *
* %CopyrightEnd%
*/
-#ifndef _ERL_ERROR_H
-#define _ERL_ERROR_H
-/* Initialize thread/task-safe erl_errno handling */
-void erl_init_errno(void);
+#ifndef __ERL_GLOBAL_LITERALS_H__
+#define __ERL_GLOBAL_LITERALS_H__
+
+#define ERTS_LIT_OS_TYPE 0
+#define ERTS_LIT_OS_VERSION 1
+#define ERTS_LIT_DFLAGS_RECORD 2
+
+#define ERTS_NUM_GLOBAL_LITERALS 3
-#endif /* _ERL_ERROR_H */
+Eterm* erts_alloc_global_literal(Uint index, Uint sz);
+void erts_register_global_literal(Uint index, Eterm term);
+Eterm erts_get_global_literal(Uint index);
+ErtsLiteralArea* erts_get_global_literal_area(Uint index);
+#endif
diff --git a/erts/emulator/beam/erl_hl_timer.c b/erts/emulator/beam/erl_hl_timer.c
index b0eb0e85c0..aec31080d2 100644
--- a/erts/emulator/beam/erl_hl_timer.c
+++ b/erts/emulator/beam/erl_hl_timer.c
@@ -2819,6 +2819,10 @@ btm_tree_print(ErtsBifTimer *tmr, void *vbtmp, Sint reds)
{
int is_hlt = !!(tmr->type.head.roflgs & ERTS_TMR_ROFLG_HLT);
ErtsMonotonicTime tpos;
+
+ if (erts_atomic32_read_nob(&tmr->btm.state) != ERTS_TMR_STATE_ACTIVE)
+ return 1;
+
if (is_hlt)
tpos = 0;
else
diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c
index f61d265a9a..8c8e0c9862 100644
--- a/erts/emulator/beam/erl_init.c
+++ b/erts/emulator/beam/erl_init.c
@@ -429,6 +429,7 @@ erl_first_process_otp(char* mod_name, int argc, char** argv)
args = CONS(hp, boot_mod, args);
so.flags = erts_default_spo_flags;
+ so.opts = NIL;
res = erl_spawn_system_process(&parent, am_erl_init, am_start, args, &so);
ASSERT(is_internal_pid(res));
@@ -462,6 +463,7 @@ erl_system_process_otp(Eterm parent_pid, char* modname, int off_heap_msgq, int p
so.priority = prio;
so.max_gen_gcs = (Uint16) erts_atomic32_read_nob(&erts_max_gen_gcs);
so.scheduler = 0;
+ so.opts = NIL;
res = erl_spawn_system_process(parent, mod, am_start, NIL, &so);
ASSERT(is_internal_pid(res));
@@ -484,6 +486,7 @@ Eterm erts_internal_spawn_system_process_3(BIF_ALIST_3) {
ASSERT(erts_list_length(args) >= 0);
so.flags = erts_default_spo_flags;
+ so.opts = NIL;
res = erl_spawn_system_process(BIF_P, mod, func, args, &so);
if (is_non_value(res)) {
@@ -583,7 +586,7 @@ void erts_usage(void)
ERTS_ASYNC_THREAD_MIN_STACK_SIZE,
ERTS_ASYNC_THREAD_MAX_STACK_SIZE);
erts_fprintf(stderr, "-A number set number of threads in async thread pool,\n");
- erts_fprintf(stderr, " valid range is [0-%d]\n",
+ erts_fprintf(stderr, " valid range is [1-%d]\n",
ERTS_MAX_NO_OF_ASYNC_THREADS);
erts_fprintf(stderr, "-B[c|d|i] c to have Ctrl-c interrupt the Erlang shell,\n");
erts_fprintf(stderr, " d (or no extra option) to disable the break\n");
@@ -774,6 +777,7 @@ early_init(int *argc, char **argv) /*
int ncpu;
int ncpuonln;
int ncpuavail;
+ int ncpuquota;
int schdlrs;
int schdlrs_onln;
int schdlrs_percentage = 100;
@@ -811,7 +815,8 @@ early_init(int *argc, char **argv) /*
&max_reader_groups,
&ncpu,
&ncpuonln,
- &ncpuavail);
+ &ncpuavail,
+ &ncpuquota);
ignore_break = 0;
replace_intr = 0;
@@ -838,9 +843,18 @@ early_init(int *argc, char **argv) /*
* can initialize the allocators.
*/
no_schedulers = (Uint) (ncpu > 0 ? ncpu : 1);
- no_schedulers_online = (ncpuavail > 0
- ? ncpuavail
- : (ncpuonln > 0 ? ncpuonln : no_schedulers));
+
+ if (ncpuavail > 0) {
+ if (ncpuquota > 0) {
+ no_schedulers_online = MIN(ncpuquota, ncpuavail);
+ } else {
+ no_schedulers_online = ncpuavail;
+ }
+ } else if (ncpuonln > 0) {
+ no_schedulers_online = ncpuonln;
+ } else {
+ no_schedulers_online = no_schedulers;
+ }
schdlrs = no_schedulers;
schdlrs_onln = no_schedulers_online;
@@ -1129,6 +1143,9 @@ early_init(int *argc, char **argv) /*
i++;
}
+ if (erts_async_max_threads < 1)
+ erts_async_max_threads = 1;
+
/* apply any scheduler percentages */
if (schdlrs_percentage != 100 || schdlrs_onln_percentage != 100) {
schdlrs = schdlrs * schdlrs_percentage / 100;
diff --git a/erts/emulator/beam/erl_io_queue.c b/erts/emulator/beam/erl_io_queue.c
index 2ae5b56b5c..c82d67f893 100644
--- a/erts/emulator/beam/erl_io_queue.c
+++ b/erts/emulator/beam/erl_io_queue.c
@@ -1078,7 +1078,7 @@ static BIF_RETTYPE iol2v_yield(iol2v_state_t *state) {
state = boxed_state;
}
- ERTS_BIF_YIELD1(bif_export[BIF_iolist_to_iovec_1],
+ ERTS_BIF_YIELD1(&bif_trap_export[BIF_iolist_to_iovec_1],
state->process, state->magic_reference);
}
diff --git a/erts/emulator/beam/erl_lock_check.c b/erts/emulator/beam/erl_lock_check.c
index 4b8be01cca..9c0ebaa9d1 100644
--- a/erts/emulator/beam/erl_lock_check.c
+++ b/erts/emulator/beam/erl_lock_check.c
@@ -84,6 +84,7 @@ static erts_lc_lock_order_t erts_lock_order[] = {
{ "reg_tab", NULL },
{ "proc_main", "pid" },
{ "old_code", "address" },
+ { "nif_call_tab", NULL },
#ifdef HIPE
{ "hipe_mfait_lock", NULL },
#endif
@@ -130,6 +131,7 @@ static erts_lc_lock_order_t erts_lock_order[] = {
{ "pollwaiter", "address" },
{ "break_waiter_lock", NULL },
#endif /* __WIN32__ */
+ { "block_poll_thread", "index" },
{ "alcu_init_atoms", NULL },
{ "mseg_init_atoms", NULL },
{ "mmap_init_atoms", NULL },
@@ -148,6 +150,7 @@ static erts_lc_lock_order_t erts_lock_order[] = {
{ "instr_x", NULL },
{ "instr", NULL },
{ "dyn_lock_check", NULL },
+ { "nif_load", NULL },
{ "alcu_allocator", "index" },
{ "mseg", NULL },
{ "get_time", NULL },
diff --git a/erts/emulator/beam/erl_lock_count.h b/erts/emulator/beam/erl_lock_count.h
index 02e468a06a..32afef8a3e 100644
--- a/erts/emulator/beam/erl_lock_count.h
+++ b/erts/emulator/beam/erl_lock_count.h
@@ -78,7 +78,7 @@ typedef struct {
} erts_lcnt_time_t;
typedef struct {
- /* @brief log2 array of nano seconds occurences */
+ /** @brief log2 array of nano seconds occurences */
Uint32 ns[ERTS_LCNT_HISTOGRAM_SLOT_SIZE];
} erts_lcnt_hist_t;
@@ -271,7 +271,7 @@ int erts_lcnt_check_ref_installed(erts_lcnt_ref_t *ref);
erts_lcnt_lock_info_carrier_t *erts_lcnt_create_lock_info_carrier(int count);
-/* @brief Initializes the lock info at the given index.
+/** @brief Initializes the lock info at the given index.
* @param id An immediate erlang term with whatever extra data you want to
* identify this lock with.
* @param flags The flags the lock itself was initialized with. Keep in mind
@@ -300,9 +300,10 @@ void erts_lcnt_pre_thr_init(void);
void erts_lcnt_post_thr_init(void);
void erts_lcnt_late_init(void);
-/* @brief Called after everything in the system has been initialized, including
- * the schedulers. This is mainly a backwards compatibility shim for matching
- * the old lcnt behavior where all lock counting was enabled by default. */
+/** @brief Called after everything in the system has been initialized,
+ * including the schedulers. This is mainly a backwards compatibility shim for
+ * matching the old lcnt behavior where all lock counting was enabled by
+ * default. */
void erts_lcnt_post_startup(void);
void erts_lcnt_thread_setup(void);
diff --git a/erts/emulator/beam/erl_lock_flags.h b/erts/emulator/beam/erl_lock_flags.h
index ec2f88ccd8..adc217cc3d 100644
--- a/erts/emulator/beam/erl_lock_flags.h
+++ b/erts/emulator/beam/erl_lock_flags.h
@@ -71,10 +71,10 @@
typedef unsigned short erts_lock_flags_t;
typedef unsigned short erts_lock_options_t;
-/* @brief Gets the type name of the lock, honoring the RW flag if supplied. */
+/** @brief Gets the type name of the lock, honoring the RW flag if supplied. */
const char *erts_lock_flags_get_type_name(erts_lock_flags_t flags);
-/* @brief Gets a short-form description of the given lock options. (rw/r/w) */
+/** @brief Gets a short-form description of the given lock options. (rw/r/w) */
const char *erts_lock_options_get_short_desc(erts_lock_options_t options);
#endif /* ERTS_LOCK_FLAGS_H__ */
diff --git a/erts/emulator/beam/erl_map.c b/erts/emulator/beam/erl_map.c
index d3e355183d..e7c649c5a7 100644
--- a/erts/emulator/beam/erl_map.c
+++ b/erts/emulator/beam/erl_map.c
@@ -973,7 +973,13 @@ BIF_RETTYPE maps_keys_1(BIF_ALIST_1) {
HIPE_WRAPPER_BIF_DISABLE_GC(maps_merge, 2)
BIF_RETTYPE maps_merge_2(BIF_ALIST_2) {
- if (is_flatmap(BIF_ARG_1)) {
+ if (BIF_ARG_1 == BIF_ARG_2) {
+ /* Merging upon itself always returns itself */
+ if (is_map(BIF_ARG_1)) {
+ return BIF_ARG_1;
+ }
+ BIF_P->fvalue = BIF_ARG_1;
+ } else if (is_flatmap(BIF_ARG_1)) {
if (is_flatmap(BIF_ARG_2)) {
BIF_RET(flatmap_merge(BIF_P, BIF_ARG_1, BIF_ARG_2));
} else if (is_hashmap(BIF_ARG_2)) {
@@ -1008,6 +1014,9 @@ static Eterm flatmap_merge(Process *p, Eterm nodeA, Eterm nodeB) {
n1 = flatmap_get_size(mp1);
n2 = flatmap_get_size(mp2);
+ if (n1 == 0) return nodeB;
+ if (n2 == 0) return nodeA;
+
need = MAP_HEADER_FLATMAP_SZ + 1 + 2 * (n1 + n2);
hp = HAlloc(p, need);
@@ -1127,6 +1136,7 @@ static Eterm map_merge_mixed(Process *p, Eterm flat, Eterm tree, int swap_args)
mp = (flatmap_t*)flatmap_val(flat);
n = flatmap_get_size(mp);
+ if (n == 0) return tree;
ks = flatmap_get_keys(mp);
vs = flatmap_get_values(mp);
@@ -2061,10 +2071,7 @@ Eterm erts_hashmap_insert(Process *p, Uint32 hx, Eterm key, Eterm value,
hp = HAlloc(p, size);
res = erts_hashmap_insert_up(hp, key, value, &upsz, &stack);
}
-
DESTROY_ESTACK(stack);
- ERTS_VERIFY_UNUSED_TEMP_ALLOC(p);
- ERTS_HOLE_CHECK(p);
return res;
}
@@ -2602,8 +2609,6 @@ unroll:
HRelease(p, hp_end, hp);
not_found:
DESTROY_ESTACK(stack);
- ERTS_VERIFY_UNUSED_TEMP_ALLOC(p);
- ERTS_HOLE_CHECK(p);
UnUseTmpHeapNoproc(2);
return res;
}
diff --git a/erts/emulator/beam/erl_message.c b/erts/emulator/beam/erl_message.c
index 2b13199698..41574d99ba 100644
--- a/erts/emulator/beam/erl_message.c
+++ b/erts/emulator/beam/erl_message.c
@@ -450,6 +450,24 @@ erts_queue_message(Process* receiver, ErtsProcLocks receiver_locks,
}
/**
+ *
+ * @brief Send one message from *NOT* a local process.
+ *
+ * But with a token!
+ */
+void
+erts_queue_message_token(Process* receiver, ErtsProcLocks receiver_locks,
+ ErtsMessage* mp, Eterm msg, Eterm from, Eterm token)
+{
+ ASSERT(is_not_internal_pid(from));
+ ERL_MESSAGE_TERM(mp) = msg;
+ ERL_MESSAGE_FROM(mp) = from;
+ ERL_MESSAGE_TOKEN(mp) = token;
+ queue_messages(receiver, receiver_locks, mp, &mp->next, 1);
+}
+
+
+/**
* @brief Send one message from a local process.
*
* It is up to the caller of this function to set the
@@ -672,7 +690,7 @@ erts_send_message(Process* sender,
* Make sure we don't use the heap between those instances.
*/
if (have_seqtrace(stoken)) {
- seq_trace_update_send(sender);
+ seq_trace_update_serial(sender);
seq_trace_output(stoken, message, SEQ_TRACE_SEND,
receiver->common.id, sender);
@@ -1605,6 +1623,15 @@ void erts_factory_undo(ErtsHeapFactory* factory)
ERTS_HEAP_FRAG_SIZE(factory->heap_frags_saved->alloc_size));
}
}
+ if (factory->message) {
+ ASSERT(factory->message->data.attached != ERTS_MSG_COMBINED_HFRAG);
+ ASSERT(!factory->message->data.heap_frag);
+
+ /* Set the message to NIL in order for it not to be treated as
+ a distributed message by erts_cleanup_messages */
+ factory->message->m[0] = NIL;
+ erts_cleanup_messages(factory->message);
+ }
}
break;
diff --git a/erts/emulator/beam/erl_message.h b/erts/emulator/beam/erl_message.h
index 01a87a492a..27b0c55414 100644
--- a/erts/emulator/beam/erl_message.h
+++ b/erts/emulator/beam/erl_message.h
@@ -455,6 +455,7 @@ void free_message_buffer(ErlHeapFragment *);
void erts_queue_dist_message(Process*, ErtsProcLocks, ErtsDistExternal *,
ErlHeapFragment *, Eterm, Eterm);
void erts_queue_message(Process*, ErtsProcLocks,ErtsMessage*, Eterm, Eterm);
+void erts_queue_message_token(Process*, ErtsProcLocks,ErtsMessage*, Eterm, Eterm, Eterm);
void erts_queue_proc_message(Process* from,Process* to, ErtsProcLocks,ErtsMessage*, Eterm);
void erts_queue_proc_messages(Process* from, Process* to, ErtsProcLocks,
ErtsMessage*, ErtsMessage**, Uint);
diff --git a/erts/emulator/beam/erl_monitor_link.c b/erts/emulator/beam/erl_monitor_link.c
index afee54a69a..2145d7b71c 100644
--- a/erts/emulator/beam/erl_monitor_link.c
+++ b/erts/emulator/beam/erl_monitor_link.c
@@ -540,22 +540,40 @@ erts_mon_link_dist_create(Eterm nodename)
mld->links = NULL;
mld->monitors = NULL;
mld->orig_name_monitors = NULL;
+ mld->dist_pend_spawn_exit = NULL;
return mld;
}
-void
-erts_mon_link_dist_destroy__(ErtsMonLnkDist *mld)
+static void
+mon_link_dist_destroy(void* vmld)
{
+ ErtsMonLnkDist *mld = (ErtsMonLnkDist*)vmld;
ERTS_ML_ASSERT(erts_atomic_read_nob(&mld->refc) == 0);
ERTS_ML_ASSERT(!mld->alive);
ERTS_ML_ASSERT(!mld->links);
ERTS_ML_ASSERT(!mld->monitors);
ERTS_ML_ASSERT(!mld->orig_name_monitors);
+ ERTS_ML_ASSERT(!mld->dist_pend_spawn_exit);
erts_mtx_destroy(&mld->mtx);
erts_free(ERTS_ALC_T_ML_DIST, mld);
}
+void
+erts_schedule_mon_link_dist_destruction__(ErtsMonLnkDist *mld)
+{
+ ERTS_ML_ASSERT(erts_atomic_read_nob(&mld->refc) == 0);
+ ERTS_ML_ASSERT(!mld->alive);
+ ERTS_ML_ASSERT(!mld->links);
+ ERTS_ML_ASSERT(!mld->monitors);
+ ERTS_ML_ASSERT(!mld->orig_name_monitors);
+
+ erts_schedule_thr_prgr_later_cleanup_op(mon_link_dist_destroy,
+ mld,
+ &mld->cleanup_lop,
+ sizeof(ErtsMonLnkDist));
+}
+
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Monitor Operations *
\* */
@@ -834,10 +852,29 @@ erts_monitor_create(Uint16 type, Eterm ref, Eterm orgn, Eterm trgt, Eterm name)
Uint rsz, osz, tsz;
Eterm *hp;
ErlOffHeap oh;
- Uint16 name_flag = is_nil(name) ? ((Uint16) 0) : ERTS_ML_FLG_NAME;
+ Uint16 name_flag;
+ Uint16 pending_flag;
rsz = is_immed(ref) ? 0 : size_object(ref);
- tsz = is_immed(trgt) ? 0 : size_object(trgt);
+ if (trgt != am_pending) {
+ if (is_not_immed(trgt))
+ tsz = size_object(trgt);
+ else
+ tsz = 0;
+ pending_flag = (Uint16) 0;
+ name_flag = is_nil(name) ? ((Uint16) 0) : ERTS_ML_FLG_NAME;
+ }
+ else {
+ /* Pending spawn_request() */
+ pending_flag = ERTS_ML_FLG_SPAWN_PENDING;
+ /* Prepare for storage of exteral pid */
+ tsz = EXTERNAL_THING_HEAD_SIZE + 1;
+ /* name contains tag */
+
+ /* Not by name */
+ name_flag = (Uint16) 0;
+
+ }
if (type == ERTS_MON_TYPE_RESOURCE)
osz = 0;
else
@@ -851,6 +888,16 @@ erts_monitor_create(Uint16 type, Eterm ref, Eterm orgn, Eterm trgt, Eterm name)
hp = &mdep->heap[0];
+ if (pending_flag) {
+ /* Make room for the future pid... */
+#ifdef DEBUG
+ int i;
+ for (i = 0; i < EXTERNAL_THING_HEAD_SIZE + 1; i++)
+ hp[i] = THE_NON_VALUE;
+#endif
+ hp += EXTERNAL_THING_HEAD_SIZE + 1;
+ }
+
mdp = &mdep->md;
ERTS_ML_ASSERT(((void *) mdp) == ((void *) mdep));
@@ -858,7 +905,7 @@ erts_monitor_create(Uint16 type, Eterm ref, Eterm orgn, Eterm trgt, Eterm name)
mdp->origin.other.item = tsz ? copy_struct(trgt, tsz, &hp, &oh) : trgt;
mdp->origin.offset = (Uint16) offsetof(ErtsMonitorData, origin);
- mdp->origin.flags = ERTS_ML_FLG_EXTENDED|name_flag;
+ mdp->origin.flags = ERTS_ML_FLG_EXTENDED|name_flag|pending_flag;
mdp->origin.type = type;
if (type == ERTS_MON_TYPE_RESOURCE)
@@ -878,6 +925,25 @@ erts_monitor_create(Uint16 type, Eterm ref, Eterm orgn, Eterm trgt, Eterm name)
}
else {
mdep->u.name = name;
+ if (pending_flag) {
+ /* spawn_request() tag is in 'name' */
+ if (is_not_immed(name)) {
+ /*
+ * Save the tag in its own heap fragment with a
+ * little trick:
+ *
+ * bp->mem[0] = The tag
+ * bp->mem[1] = Beginning of heap
+ * mdep->u.name = Countinuation pointer to
+ * heap fragment...
+ */
+ Uint hsz = size_object(name)+1;
+ ErlHeapFragment *bp = new_message_buffer(hsz);
+ Eterm *hp = &bp->mem[1];
+ bp->mem[0] = copy_struct(name, hsz-1, &hp, &bp->off_heap);
+ mdep->u.name = make_cp((void*)bp);
+ }
+ }
mdp->origin.key_offset = (Uint16) offsetof(ErtsMonitorData, ref);
ERTS_ML_ASSERT(mdp->origin.key_offset >= mdp->origin.offset);
@@ -964,6 +1030,20 @@ erts_monitor_destroy__(ErtsMonitorData *mdp)
}
if (mdep->dist)
erts_mon_link_dist_dec_refc(mdep->dist);
+ if (mdp->origin.flags & ERTS_ML_FLG_SPAWN_PENDING) {
+ /*
+ * We have the spawn_request() tag stored in
+ * mdep->u.name via a little trick
+ * (see pending_flag in erts_monitor_create()).
+ * If non-immediate value make sure to release
+ * this heap fragment as well.
+ */
+ if (is_not_immed(mdep->u.name)) {
+ ErlHeapFragment *bp;
+ bp = (ErlHeapFragment *) cp_val(mdep->u.name);
+ free_message_buffer(bp);
+ }
+ }
erts_free(ERTS_ALC_T_MONITOR_EXT, mdp);
}
}
@@ -1024,8 +1104,8 @@ erts_monitor_size(ErtsMonitor *mon)
\* */
#ifdef ERTS_ML_DEBUG
-size_t erts_link_a_offset;
-size_t erts_link_b_offset;
+size_t erts_link_proc_offset;
+size_t erts_link_dist_offset;
size_t erts_link_key_offset;
#endif
@@ -1033,8 +1113,8 @@ static ERTS_INLINE void
link_init(void)
{
#ifdef ERTS_ML_DEBUG
- erts_link_a_offset = offsetof(ErtsLinkData, a);
- erts_link_b_offset = offsetof(ErtsLinkData, b);
+ erts_link_proc_offset = offsetof(ErtsLinkData, proc);
+ erts_link_dist_offset = offsetof(ErtsLinkData, dist);
erts_link_key_offset = offsetof(ErtsLink, other.item);
#endif
}
@@ -1053,29 +1133,45 @@ erts_link_tree_lookup_insert(ErtsLink **root, ErtsLink *lnk)
(ErtsMonLnkNode *) lnk);
}
+static ErtsMonLnkNode *
+create_link_internal(Eterm id, void *vtypep)
+{
+ ErtsMonLnkNode *lnk = erts_link_internal_create(*((Uint16 *) vtypep), id);
+ ERTS_ML_ASSERT(ml_cmp_keys(lnk->other.item, id) == 0);
+ return (ErtsMonLnkNode *) lnk;
+}
+
+ErtsLink *erts_link_internal_tree_lookup_create(ErtsLink **root, int *created,
+ Uint16 type, Eterm other)
+{
+ return (ErtsLink *) ml_rbt_lookup_create((ErtsMonLnkNode **) root,
+ other, create_link_internal,
+ (void *) &type,
+ created);
+}
+
typedef struct {
Uint16 type;
- Eterm a;
+ Eterm this;
} ErtsLinkCreateCtxt;
static ErtsMonLnkNode *
-create_link(Eterm b, void *vcctxt)
+create_link_external(Eterm other, void *vcctxt)
{
ErtsLinkCreateCtxt *cctxt = vcctxt;
- ErtsLinkData *ldp = erts_link_create(cctxt->type, cctxt->a, b);
- ERTS_ML_ASSERT(ml_cmp_keys(ldp->a.other.item, b) == 0);
- return (ErtsMonLnkNode *) &ldp->a;
+ ErtsLinkData *ldp = erts_link_external_create(cctxt->type, cctxt->this, other);
+ return (ErtsMonLnkNode *) &ldp->proc;
}
-ErtsLink *erts_link_tree_lookup_create(ErtsLink **root, int *created,
- Uint16 type, Eterm this,
- Eterm other)
+ErtsLink *erts_link_external_tree_lookup_create(ErtsLink **root, int *created,
+ Uint16 type, Eterm this,
+ Eterm other)
{
ErtsLinkCreateCtxt cctxt;
cctxt.type = type;
- cctxt.a = this;
+ cctxt.this = this;
return (ErtsLink *) ml_rbt_lookup_create((ErtsMonLnkNode **) root,
- other, create_link,
+ other, create_link_external,
(void *) &cctxt,
created);
}
@@ -1209,23 +1305,17 @@ erts_link_list_foreach_delete_yielding(ErtsLink **list,
arg, vyspp, limit);
}
-ErtsLinkData *
-erts_link_create(Uint16 type, Eterm a, Eterm b)
+ErtsLink *
+erts_link_internal_create(Uint16 type, Eterm id)
{
- ErtsLinkData *ldp;
-
+ ErtsILink *ilnk;
#ifdef ERTS_ML_DEBUG
switch (type) {
case ERTS_LNK_TYPE_PROC:
- ERTS_ML_ASSERT(is_internal_pid(a) && is_internal_pid(a));
+ ERTS_ML_ASSERT(is_internal_pid(id));
break;
case ERTS_LNK_TYPE_PORT:
- ERTS_ML_ASSERT(is_internal_pid(a) || is_internal_pid(b));
- ERTS_ML_ASSERT(is_internal_port(a) || is_internal_port(b));
- break;
- case ERTS_LNK_TYPE_DIST_PROC:
- ERTS_ML_ASSERT(is_internal_pid(a) || is_internal_pid(b));
- ERTS_ML_ASSERT(is_external_pid(a) || is_external_pid(b));
+ ERTS_ML_ASSERT(is_internal_pid(id) || is_internal_port(id));
break;
default:
ERTS_INTERNAL_ERROR("Invalid link type");
@@ -1233,129 +1323,128 @@ erts_link_create(Uint16 type, Eterm a, Eterm b)
}
#endif
- if (type != ERTS_LNK_TYPE_DIST_PROC) {
- ldp = erts_alloc(ERTS_ALC_T_LINK, sizeof(ErtsLinkData));
+ ilnk = erts_alloc(ERTS_ALC_T_LINK, sizeof(ErtsILink));
- ldp->a.other.item = b;
- ldp->a.flags = (Uint16) 0;
+ ilnk->link.other.item = id;
+ ilnk->link.key_offset = (Uint16) offsetof(ErtsLink, other.item);
+ ilnk->link.offset = (Uint16) 0;
+ ilnk->link.flags = (Uint16) 0;
+ ilnk->link.type = type;
+ ilnk->unlinking = 0;
- ldp->b.other.item = a;
- ldp->b.flags = (Uint16) 0;
- }
- else {
- ErtsLinkDataExtended *ldep;
- Uint size, hsz;
- Eterm *hp;
- ErlOffHeap oh;
+ return &ilnk->link;
+}
- if (is_internal_pid(a))
- hsz = NC_HEAP_SIZE(b);
- else
- hsz = NC_HEAP_SIZE(a);
- ERTS_ML_ASSERT(hsz > 0);
- size = sizeof(ErtsLinkDataExtended) - sizeof(Eterm);
- size += hsz*sizeof(Eterm);
+ErtsLinkData *
+erts_link_external_create(Uint16 type, Eterm this, Eterm other)
+{
+ ErtsLinkData *ldp;
+ ErtsELink *elnk;
+ Uint size, hsz;
+ Eterm *hp;
+ ErlOffHeap oh;
- ldp = erts_alloc(ERTS_ALC_T_LINK_EXT, size);
+ ERTS_ML_ASSERT(type == ERTS_LNK_TYPE_DIST_PROC);
+ ERTS_ML_ASSERT(is_internal_pid(this));
+ ERTS_ML_ASSERT(is_external_pid(other));
- ldp->a.flags = ERTS_ML_FLG_EXTENDED;
- ldp->b.flags = ERTS_ML_FLG_EXTENDED;
+ hsz = EXTERNAL_THING_HEAD_SIZE + 1;
- ldep = (ErtsLinkDataExtended *) ldp;
- hp = &ldep->heap[0];
+ size = sizeof(ErtsELink) - sizeof(Eterm);
+ size += hsz*sizeof(Eterm);
- ERTS_INIT_OFF_HEAP(&oh);
+ ldp = erts_alloc(ERTS_ALC_T_LINK_EXT, size);
- if (is_internal_pid(a)) {
- ldp->a.other.item = STORE_NC(&hp, &oh, b);
- ldp->b.other.item = a;
- }
- else {
- ldp->a.other.item = b;
- ldp->b.other.item = STORE_NC(&hp, &oh, a);
- }
+ elnk = (ErtsELink *) ldp;
+ hp = &elnk->heap[0];
- ldep->ohhp = oh.first;
- ldep->dist = NULL;
- }
+ ERTS_INIT_OFF_HEAP(&oh);
- erts_atomic32_init_nob(&ldp->refc, 2);
+ ldp->proc.other.item = STORE_NC(&hp, &oh, other);
+ ldp->proc.flags = ERTS_ML_FLG_EXTENDED;
+ ldp->proc.key_offset = (Uint16) offsetof(ErtsLink, other.item);
+ ldp->proc.offset = (Uint16) offsetof(ErtsLinkData, proc);
+ ldp->proc.type = type;
- ldp->a.key_offset = (Uint16) offsetof(ErtsLink, other.item);
- ldp->a.offset = (Uint16) offsetof(ErtsLinkData, a);
- ldp->a.type = type;
+ ldp->dist.other.item = this;
+ ldp->dist.flags = ERTS_ML_FLG_EXTENDED;
+ ldp->dist.key_offset = (Uint16) offsetof(ErtsLink, other.item);
+ ldp->dist.offset = (Uint16) offsetof(ErtsLinkData, dist);
+ ldp->dist.type = type;
- ldp->b.key_offset = (Uint16) offsetof(ErtsLink, other.item);
- ldp->b.offset = (Uint16) offsetof(ErtsLinkData, b);
- ldp->b.type = type;
+ elnk->ohhp = oh.first;
+ elnk->dist = NULL;
+ elnk->unlinking = 0;
+
+ erts_atomic32_init_nob(&ldp->refc, 2);
return ldp;
}
/*
- * erts_link_destroy__() should only be called from
+ * erts_link_destroy_elink__() should only be called from
* erts_link_release() or erts_link_release_both().
*/
void
-erts_link_destroy__(ErtsLinkData *ldp)
+erts_link_destroy_elink__(ErtsELink *elnk)
{
- ERTS_ML_ASSERT(erts_atomic32_read_nob(&ldp->refc) == 0);
- ERTS_ML_ASSERT(!(ldp->a.flags & ERTS_ML_FLG_IN_TABLE));
- ERTS_ML_ASSERT(!(ldp->b.flags & ERTS_ML_FLG_IN_TABLE));
- ERTS_ML_ASSERT((ldp->a.flags & ERTS_ML_FLGS_SAME)
- == (ldp->b.flags & ERTS_ML_FLGS_SAME));
-
- if (!(ldp->a.flags & ERTS_ML_FLG_EXTENDED))
- erts_free(ERTS_ALC_T_LINK, ldp);
- else {
- ErtsLinkDataExtended *ldep = (ErtsLinkDataExtended *) ldp;
- ErlOffHeap oh;
- if (ldep->ohhp) {
- ERTS_INIT_OFF_HEAP(&oh);
- oh.first = ldep->ohhp;
- erts_cleanup_offheap(&oh);
- }
- if (ldep->dist)
- erts_mon_link_dist_dec_refc(ldep->dist);
- erts_free(ERTS_ALC_T_LINK_EXT, ldep);
+ ErlOffHeap oh;
+ ERTS_ML_ASSERT(erts_atomic32_read_nob(&elnk->ld.refc) == 0);
+ ERTS_ML_ASSERT(!(elnk->ld.proc.flags & ERTS_ML_FLG_IN_TABLE));
+ ERTS_ML_ASSERT(!(elnk->ld.dist.flags & ERTS_ML_FLG_IN_TABLE));
+ ERTS_ML_ASSERT((elnk->ld.proc.flags & ERTS_ML_FLGS_SAME)
+ == (elnk->ld.dist.flags & ERTS_ML_FLGS_SAME));
+ ERTS_ML_ASSERT(elnk->ld.proc.flags & ERTS_ML_FLG_EXTENDED);
+ ERTS_ML_ASSERT(elnk->ld.dist.flags & ERTS_ML_FLG_EXTENDED);
+
+ if (elnk->ohhp) {
+ ERTS_INIT_OFF_HEAP(&oh);
+ oh.first = elnk->ohhp;
+ erts_cleanup_offheap(&oh);
}
+ if (elnk->dist)
+ erts_mon_link_dist_dec_refc(elnk->dist);
+ erts_free(ERTS_ALC_T_LINK_EXT, elnk);
}
void
erts_link_set_dead_dist(ErtsLink *lnk, Eterm nodename)
{
- ErtsLinkDataExtended *ldep;
- ldep = (ErtsLinkDataExtended *) erts_link_to_data(lnk);
+ ErtsELink *elnk;
ERTS_ML_ASSERT(lnk->flags & ERTS_ML_FLG_EXTENDED);
ERTS_ML_ASSERT(lnk->type == ERTS_LNK_TYPE_DIST_PROC);
- ERTS_ML_ASSERT(!ldep->dist);
- ldep->dist = erts_mon_link_dist_create(nodename);
- ldep->dist->alive = 0;
+ elnk = erts_link_to_elink(lnk);
+
+ ERTS_ML_ASSERT(!elnk->dist);
+
+ elnk->dist = erts_mon_link_dist_create(nodename);
+ elnk->dist->alive = 0;
}
Uint
erts_link_size(ErtsLink *lnk)
{
Uint size, refc;
- ErtsLinkData *ldp = erts_link_to_data(lnk);
- if (!(lnk->flags & ERTS_ML_FLG_EXTENDED))
- size = sizeof(ErtsLinkData);
+ if (!(lnk->flags & ERTS_ML_FLG_EXTENDED)) {
+ size = sizeof(ErtsLink);
+ refc = 1;
+ }
else {
- ErtsLinkDataExtended *ldep = (ErtsLinkDataExtended *) ldp;
+ ErtsELink *elnk = erts_link_to_elink(lnk);
ASSERT(lnk->type == ERTS_LNK_TYPE_DIST_PROC);
- ASSERT(is_external_pid_header(ldep->heap[0]));
+ ASSERT(is_external_pid_header(elnk->heap[0]));
- size = sizeof(ErtsLinkDataExtended);
- size += thing_arityval(ldep->heap[0])*sizeof(Eterm);
+ size = sizeof(ErtsELink);
+ size += thing_arityval(elnk->heap[0])*sizeof(Eterm);
+ refc = (Uint) erts_atomic32_read_nob(&elnk->ld.refc);
+ ASSERT(refc > 0);
}
- refc = (Uint) erts_atomic32_read_nob(&ldp->refc);
- ASSERT(refc > 0);
return size / refc;
}
diff --git a/erts/emulator/beam/erl_monitor_link.h b/erts/emulator/beam/erl_monitor_link.h
index 678025cb05..07f38ac9ca 100644
--- a/erts/emulator/beam/erl_monitor_link.h
+++ b/erts/emulator/beam/erl_monitor_link.h
@@ -396,6 +396,12 @@
#include "erl_proc_sig_queue.h"
#undef ERTS_PROC_SIG_QUEUE_TYPE_ONLY
+#define ERL_THR_PROGRESS_TSD_TYPE_ONLY
+#include "erl_thr_progress.h"
+#undef ERL_THR_PROGRESS_TSD_TYPE_ONLY
+
+#include "erl_alloc.h"
+
#if defined(DEBUG) || 0
# define ERTS_ML_DEBUG
#else
@@ -431,9 +437,22 @@
#define ERTS_ML_FLG_IN_SUBTABLE (((Uint16) 1) << 2)
#define ERTS_ML_FLG_NAME (((Uint16) 1) << 3)
#define ERTS_ML_FLG_EXTENDED (((Uint16) 1) << 4)
+#define ERTS_ML_FLG_SPAWN_PENDING (((Uint16) 1) << 5)
+#define ERTS_ML_FLG_SPAWN_MONITOR (((Uint16) 1) << 6)
+#define ERTS_ML_FLG_SPAWN_LINK (((Uint16) 1) << 7)
+#define ERTS_ML_FLG_SPAWN_ABANDONED (((Uint16) 1) << 8)
+#define ERTS_ML_FLG_SPAWN_NO_SMSG (((Uint16) 1) << 9)
+#define ERTS_ML_FLG_SPAWN_NO_EMSG (((Uint16) 1) << 10)
#define ERTS_ML_FLG_DBG_VISITED (((Uint16) 1) << 15)
+#define ERTS_ML_FLGS_SPAWN (ERTS_ML_FLG_SPAWN_PENDING \
+ | ERTS_ML_FLG_SPAWN_MONITOR \
+ | ERTS_ML_FLG_SPAWN_LINK \
+ | ERTS_ML_FLG_SPAWN_ABANDONED \
+ | ERTS_ML_FLG_SPAWN_NO_SMSG \
+ | ERTS_ML_FLG_SPAWN_NO_EMSG)
+
/* Flags that should be the same on both monitor/link halves */
#define ERTS_ML_FLGS_SAME \
(ERTS_ML_FLG_EXTENDED|ERTS_ML_FLG_NAME)
@@ -468,7 +487,7 @@ struct ErtsMonLnkNode__ {
Uint16 type;
};
-typedef struct {
+typedef struct ErtsMonLnkDist__ {
Eterm nodename;
Uint32 connection_id;
erts_atomic_t refc;
@@ -478,6 +497,8 @@ typedef struct {
ErtsMonLnkNode *monitors; /* Monitor double linked circular list */
ErtsMonLnkNode *orig_name_monitors; /* Origin named monitors
read-black tree */
+ ErtsMonLnkNode *dist_pend_spawn_exit;
+ ErtsThrPrgrLaterOp cleanup_lop;
} ErtsMonLnkDist;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
@@ -530,7 +551,7 @@ ERTS_GLB_INLINE void erts_ml_dl_list_delete__(ErtsMonLnkNode **list,
ErtsMonLnkNode *ml);
ERTS_GLB_INLINE ErtsMonLnkNode *erts_ml_dl_list_first__(ErtsMonLnkNode *list);
ERTS_GLB_INLINE ErtsMonLnkNode *erts_ml_dl_list_last__(ErtsMonLnkNode *list);
-void erts_mon_link_dist_destroy__(ErtsMonLnkDist *mld);
+void erts_schedule_mon_link_dist_destruction__(ErtsMonLnkDist *mld);
ERTS_GLB_INLINE void *erts_ml_node_to_main_struct__(ErtsMonLnkNode *mln);
/* implementations for globally inlined misc functions... */
@@ -548,7 +569,7 @@ erts_mon_link_dist_dec_refc(ErtsMonLnkDist *mld)
{
ERTS_ML_ASSERT(erts_atomic_read_nob(&mld->refc) > 0);
if (erts_atomic_dec_read_nob(&mld->refc) == 0)
- erts_mon_link_dist_destroy__(mld);
+ erts_schedule_mon_link_dist_destruction__(mld);
}
ERTS_GLB_INLINE void *
@@ -700,7 +721,7 @@ ErtsMonitor *erts_monitor_tree_lookup(ErtsMonitor *root, Eterm key);
* @returns Pointer to a monitor with the
* key 'key'. If no monitor with the key
* 'key' was found and 'mon' was inserted
- * 'mon' is returned.
+ * 'NULL' is returned.
*
*/
ErtsMonitor *erts_monotor_tree_lookup_insert(ErtsMonitor **root,
@@ -1397,7 +1418,7 @@ erts_monitor_release(ErtsMonitor *mon)
ErtsMonitorData *mdp = erts_monitor_to_data(mon);
ERTS_ML_ASSERT(erts_atomic32_read_nob(&mdp->refc) > 0);
- if (erts_atomic32_dec_read_nob(&mdp->refc) == 0) {
+ if (erts_atomic32_dec_read_mb(&mdp->refc) == 0) {
ERTS_ML_ASSERT(!(mdp->origin.flags & ERTS_ML_FLG_IN_TABLE));
ERTS_ML_ASSERT(!(mdp->target.flags & ERTS_ML_FLG_IN_TABLE));
@@ -1412,7 +1433,7 @@ erts_monitor_release_both(ErtsMonitorData *mdp)
== (mdp->target.flags & ERTS_ML_FLGS_SAME));
ERTS_ML_ASSERT(erts_atomic32_read_nob(&mdp->refc) >= 2);
- if (erts_atomic32_add_read_nob(&mdp->refc, (erts_aint32_t) -2) == 0) {
+ if (erts_atomic32_add_read_mb(&mdp->refc, (erts_aint32_t) -2) == 0) {
ERTS_ML_ASSERT(!(mdp->origin.flags & ERTS_ML_FLG_IN_TABLE));
ERTS_ML_ASSERT(!(mdp->target.flags & ERTS_ML_FLG_IN_TABLE));
@@ -1434,14 +1455,14 @@ erts_monitor_dist_insert(ErtsMonitor *mon, ErtsMonLnkDist *dist)
ERTS_ML_ASSERT(!mdep->dist);
ERTS_ML_ASSERT(dist);
- mdep->dist = dist;
-
- erts_mon_link_dist_inc_refc(dist);
erts_mtx_lock(&dist->mtx);
insert = dist->alive;
if (insert) {
+ mdep->dist = dist;
+ erts_mon_link_dist_inc_refc(dist);
+
if ((mon->flags & (ERTS_ML_FLG_NAME
| ERTS_ML_FLG_TARGET)) == ERTS_ML_FLG_NAME)
erts_monitor_tree_insert(&dist->orig_name_monitors, mon);
@@ -1528,18 +1549,26 @@ typedef struct ErtsMonLnkNode__ ErtsLink;
typedef int (*ErtsLinkFunc)(ErtsLink *, void *, Sint);
+/* Internal Link */
+typedef struct {
+ ErtsLink link;
+ Uint64 unlinking;
+} ErtsILink;
+
typedef struct {
- ErtsLink a;
- ErtsLink b;
+ ErtsLink proc;
+ ErtsLink dist;
erts_atomic32_t refc;
} ErtsLinkData;
+/* External Link */
typedef struct {
ErtsLinkData ld;
struct erl_off_heap_header *ohhp;
ErtsMonLnkDist *dist;
+ Uint64 unlinking;
Eterm heap[1]; /* heap start... */
-} ErtsLinkDataExtended;
+} ErtsELink;
/*
* --- Link tree operations ---
@@ -1577,14 +1606,14 @@ ErtsLink *erts_link_tree_lookup(ErtsLink *root, Eterm item);
* @returns Pointer to a link with the
* key 'key'. If no link with the key
* 'key' was found and 'lnk' was inserted
- * 'lnk' is returned.
+ * 'NULL' is returned.
*
*/
ErtsLink *erts_link_tree_lookup_insert(ErtsLink **root, ErtsLink *lnk);
/**
*
- * @brief Lookup or create a link in a link tree.
+ * @brief Lookup or create an external link in a link tree.
*
* Looks up a link with the key 'other' in the link tree. If it is not
* found, creates and insert a link with the key 'other'.
@@ -1603,9 +1632,40 @@ ErtsLink *erts_link_tree_lookup_insert(ErtsLink **root, ErtsLink *lnk);
*
* @param[in] other Id of other entity
*
+ * @returns Pointer to either an already existing
+ * link in the tree or a newly created
+ * and inserted link.
+ *
+ */
+ErtsLink *erts_link_external_tree_lookup_create(ErtsLink **root, int *created,
+ Uint16 type, Eterm this, Eterm other);
+
+/**
+ *
+ * @brief Lookup or create an internal link in a link tree.
+ *
+ * Looks up a link with the key 'other' in the link tree. If it is not
+ * found, creates and insert a link with the key 'other'.
+ *
+ * @param[in,out] root Pointer to pointer to root of link tree
+ *
+ * @param[out] created Pointer to integer. The integer is set to
+ * a non-zero value if no link with key
+ * 'other' was found, and a new link
+ * was created. If a link was found, it
+ * is set to zero.
+ *
+ * @param[in] type Type of link
+ *
+ * @param[in] other Id of other entity
+ *
+ * @returns Pointer to either an already existing
+ * link in the tree or a newly created
+ * and inserted link.
+ *
*/
-ErtsLink *erts_link_tree_lookup_create(ErtsLink **root, int *created,
- Uint16 type, Eterm this, Eterm other);
+ErtsLink *erts_link_internal_tree_lookup_create(ErtsLink **root, int *created,
+ Uint16 type, Eterm other);
/**
*
@@ -2032,60 +2092,74 @@ int erts_link_list_foreach_delete_yielding(ErtsLink **list,
/**
*
- * @brief Create a link
+ * @brief Create an external link
*
- * Can create all types of links
+ * An external link structure contains two links, one for usage in
+ * the link tree of the process and one for usage in the dist entry.
*
- * When the function is called it is assumed that:
- * - 'ref' is an internal ordinary reference if type is ERTS_MON_TYPE_PROC,
- * ERTS_MON_TYPE_PORT, ERTS_MON_TYPE_TIME_OFFSET, or ERTS_MON_TYPE_RESOURCE
- * - 'ref' is NIL if type is ERTS_MON_TYPE_NODE or ERTS_MON_TYPE_NODES
- * - 'ref' is and ordinary internal reference or an external reference if
- * type is ERTS_MON_TYPE_DIST_PROC
- * - 'name' is an atom or NIL if type is ERTS_MON_TYPE_PROC,
- * ERTS_MON_TYPE_PORT, or ERTS_MON_TYPE_DIST_PROC
- * - 'name is NIL if type is ERTS_MON_TYPE_TIME_OFFSET, ERTS_MON_TYPE_RESOURCE,
- * ERTS_MON_TYPE_NODE, or ERTS_MON_TYPE_NODES
- * If the above is not true, bad things will happen.
*
- * @param[in] type ERTS_MON_TYPE_PROC, ERTS_MON_TYPE_PORT,
- * ERTS_MON_TYPE_TIME_OFFSET, ERTS_MON_TYPE_DIST_PROC,
- * ERTS_MON_TYPE_RESOURCE, ERTS_MON_TYPE_NODE,
- * or ERTS_MON_TYPE_NODES
+ * @param[in] type ERTS_MON_TYPE_DIST_PROC
+ *
+ * @param[in] this The process identifier of the local
+ * process. The link structure in the
+ * 'dist' field a will have its
+ * 'other.item' field set to 'this'.
+ * The 'dist' link structure is to be
+ * inserted on the distribution entry.
*
- * @param[in] a The key of entity a. Link structure a will
- * have field other.item set to 'b'.
+ * @param[in] other The process identifier of the remote
+ * process. The link structure in the
+ * 'proc' field a will have its
+ * 'other.item' field set to 'other'.
+ * The 'proc' link structure is to be
+ * inserted on the local process.
*
- * @param[in] b The key of entity b. Link structure b will
- * have field other.item set to 'a'.
+ * @returns A pointer to the link data structure
+ * containing the link structures. The
+ * link data structure is in turn part
+ * of the external link structure
+ * (ErtsELink).
*
*/
-ErtsLinkData *erts_link_create(Uint16 type, Eterm a, Eterm b);
+ErtsLinkData *erts_link_external_create(Uint16 type, Eterm this, Eterm other);
/**
*
- * @brief Get pointer to link data structure
+ * @brief Create an internal link
+ *
+ * @param[in] type ERTS_MON_TYPE_PROC, ERTS_MON_TYPE_PORT,
+ *
+ * @param[in] id Id of the entity linked.
+ *
+ * @returns A pointer to the link stucture.
+ */
+ErtsLink *erts_link_internal_create(Uint16 type, Eterm id);
+
+/**
+ *
+ * @brief Get pointer to external link data structure
*
* @param[in] lnk Pointer to link
*
- * @returns Pointer to link data structure
+ * @returns Pointer to external link structure
*
*/
-ERTS_GLB_INLINE ErtsLinkData *erts_link_to_data(ErtsLink *lnk);
+ERTS_GLB_INLINE ErtsELink *erts_link_to_elink(ErtsLink *lnk);
/**
*
* @brief Get pointer to the other link structure part of the link
*
- * @param[in] lnk Pointer to link
+ * @param[in] lnk Pointer to link structure
*
- * @param[out] ldpp Pointer to pointer to link data structure,
- * if a non-NULL value is passed in the call
+ * @param[out] elnkpp Pointer to pointer to external link
+ * data structure, if a non-NULL value
+ * is passed in the call
*
- * @returns Pointer to other link
+ * @returns Pointer to other link structure
*
*/
-ERTS_GLB_INLINE ErtsLink *erts_link_to_other(ErtsLink *lnk, ErtsLinkData **ldpp);
+ERTS_GLB_INLINE ErtsLink *erts_link_to_other(ErtsLink *lnk, ErtsELink **elnkpp);
/**
*
@@ -2101,10 +2175,27 @@ ERTS_GLB_INLINE int erts_link_is_in_table(ErtsLink *lnk);
/**
*
+ * @brief Release an internal link
+ *
+ * When the function is called it is assumed that:
+ * - 'lnk' link is not part of any list or tree
+ * - 'lnk' is not referred to by any other structures
+ * If the above are not true, bad things will happen.
+ *
+ * @param[in] lnk Pointer to link
+ *
+ */
+ERTS_GLB_INLINE void erts_link_internal_release(ErtsLink *lnk);
+
+/**
+ *
* @brief Release link
*
- * When both link halves part of the link have been released the link
- * structure will be deallocated.
+ * Can be used to release a link half of an external
+ * link as well as an internal link. In the external
+ * case both link halves part of the external link have
+ * to been released before the link structure will be
+ * deallocated.
*
* When the function is called it is assumed that:
* - 'lnk' link is not part of any list or tree
@@ -2118,10 +2209,11 @@ ERTS_GLB_INLINE void erts_link_release(ErtsLink *lnk);
/**
*
- * @brief Release both link halves of a link simultaneously
+ * @brief Release both link halves of an external link
+ * simultaneously
*
- * Release both halves of a link simultaneously and deallocate
- * the structure.
+ * Release both halves of an external link simultaneously and
+ * deallocate the structure.
*
* When the function is called it is assumed that:
* - Neither of the parts of the link are part of any list or tree
@@ -2209,39 +2301,43 @@ erts_link_set_dead_dist(ErtsLink *lnk, Eterm nodename);
Uint erts_link_size(ErtsLink *lnk);
/* internal function... */
-void erts_link_destroy__(ErtsLinkData *ldp);
+void erts_link_destroy_elink__(ErtsELink *elnk);
/* implementations for globally inlined link functions... */
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
#ifdef ERTS_ML_DEBUG
-extern size_t erts_link_a_offset;
-extern size_t erts_link_b_offset;
+extern size_t erts_link_proc_offset;
+extern size_t erts_link_dist_offset;
extern size_t erts_link_key_offset;
#endif
-ERTS_GLB_INLINE ErtsLinkData *
-erts_link_to_data(ErtsLink *lnk)
+ERTS_GLB_INLINE ErtsELink *
+erts_link_to_elink(ErtsLink *lnk)
{
- ErtsLinkData *ldp = erts_ml_node_to_main_struct__((ErtsMonLnkNode *) lnk);
+ ErtsELink *elnk;
+
+ ERTS_ML_ASSERT(lnk->flags & ERTS_ML_FLG_EXTENDED);
+
+ elnk = erts_ml_node_to_main_struct__((ErtsMonLnkNode *) lnk);
#ifdef ERTS_ML_DEBUG
- ERTS_ML_ASSERT(erts_link_a_offset == (size_t) ldp->a.offset);
- ERTS_ML_ASSERT(erts_link_key_offset == (size_t) ldp->a.key_offset);
- ERTS_ML_ASSERT(erts_link_b_offset == (size_t) ldp->b.offset);
- ERTS_ML_ASSERT(erts_link_key_offset == (size_t) ldp->b.key_offset);
+ ERTS_ML_ASSERT(erts_link_proc_offset == (size_t) elnk->ld.proc.offset);
+ ERTS_ML_ASSERT(erts_link_key_offset == (size_t) elnk->ld.proc.key_offset);
+ ERTS_ML_ASSERT(erts_link_dist_offset == (size_t) elnk->ld.dist.offset);
+ ERTS_ML_ASSERT(erts_link_key_offset == (size_t) elnk->ld.dist.key_offset);
#endif
- return ldp;
+ return elnk;
}
ERTS_GLB_INLINE ErtsLink *
-erts_link_to_other(ErtsLink *lnk, ErtsLinkData **ldpp)
+erts_link_to_other(ErtsLink *lnk, ErtsELink **elnkpp)
{
- ErtsLinkData *ldp = erts_link_to_data(lnk);
- if (ldpp)
- *ldpp = ldp;
- return lnk == &ldp->a ? &ldp->b : &ldp->a;
+ ErtsELink *elnk = erts_link_to_elink(lnk);
+ if (elnkpp)
+ *elnkpp = elnk;
+ return lnk == &elnk->ld.proc ? &elnk->ld.dist : &elnk->ld.proc;
}
ERTS_GLB_INLINE int
@@ -2275,23 +2371,38 @@ erts_link_list_last(ErtsLink *list)
}
ERTS_GLB_INLINE void
+erts_link_internal_release(ErtsLink *lnk)
+{
+ ERTS_ML_ASSERT(lnk->type == ERTS_LNK_TYPE_PROC
+ || lnk->type == ERTS_LNK_TYPE_PORT);
+ ERTS_ML_ASSERT(!(lnk->flags & ERTS_ML_FLG_EXTENDED));
+ erts_free(ERTS_ALC_T_LINK, lnk);
+}
+
+ERTS_GLB_INLINE void
erts_link_release(ErtsLink *lnk)
{
- ErtsLinkData *ldp = erts_link_to_data(lnk);
- ERTS_ML_ASSERT(!(lnk->flags & ERTS_ML_FLG_IN_TABLE));
- ERTS_ML_ASSERT(erts_atomic32_read_nob(&ldp->refc) > 0);
- if (erts_atomic32_dec_read_nob(&ldp->refc) == 0)
- erts_link_destroy__(ldp);
+ if (!(lnk->flags & ERTS_ML_FLG_EXTENDED))
+ erts_link_internal_release(lnk);
+ else {
+ ErtsELink *elnk = erts_link_to_elink(lnk);
+ ERTS_ML_ASSERT(!(lnk->flags & ERTS_ML_FLG_IN_TABLE));
+ ERTS_ML_ASSERT(erts_atomic32_read_nob(&elnk->ld.refc) > 0);
+ if (erts_atomic32_dec_read_nob(&elnk->ld.refc) == 0)
+ erts_link_destroy_elink__(elnk);
+ }
}
ERTS_GLB_INLINE void
erts_link_release_both(ErtsLinkData *ldp)
{
- ERTS_ML_ASSERT(!(ldp->a.flags & ERTS_ML_FLG_IN_TABLE));
- ERTS_ML_ASSERT(!(ldp->b.flags & ERTS_ML_FLG_IN_TABLE));
+ ERTS_ML_ASSERT(!(ldp->proc.flags & ERTS_ML_FLG_IN_TABLE));
+ ERTS_ML_ASSERT(!(ldp->dist.flags & ERTS_ML_FLG_IN_TABLE));
ERTS_ML_ASSERT(erts_atomic32_read_nob(&ldp->refc) >= 2);
+ ERTS_ML_ASSERT(ldp->proc.flags & ERTS_ML_FLG_EXTENDED);
+ ERTS_ML_ASSERT(ldp->dist.flags & ERTS_ML_FLG_EXTENDED);
if (erts_atomic32_add_read_nob(&ldp->refc, (erts_aint32_t) -2) == 0)
- erts_link_destroy__(ldp);
+ erts_link_destroy_elink__((ErtsELink *) ldp);
}
ERTS_GLB_INLINE ErtsLink *
@@ -2322,25 +2433,25 @@ erts_link_tree_key_delete(ErtsLink **root, ErtsLink *lnk)
ERTS_GLB_INLINE int
erts_link_dist_insert(ErtsLink *lnk, ErtsMonLnkDist *dist)
{
- ErtsLinkDataExtended *ldep;
+ ErtsELink *elnk;
int insert;
ERTS_ML_ASSERT(lnk->flags & ERTS_ML_FLG_EXTENDED);
ERTS_ML_ASSERT(lnk->type == ERTS_LNK_TYPE_DIST_PROC);
- ldep = (ErtsLinkDataExtended *) erts_link_to_data(lnk);
+ elnk = erts_link_to_elink(lnk);
- ERTS_ML_ASSERT(!ldep->dist);
+ ERTS_ML_ASSERT(!elnk->dist);
ERTS_ML_ASSERT(dist);
- ldep->dist = dist;
-
- erts_mon_link_dist_inc_refc(dist);
erts_mtx_lock(&dist->mtx);
insert = dist->alive;
- if (insert)
+ if (insert) {
+ elnk->dist = dist;
+ erts_mon_link_dist_inc_refc(dist);
erts_link_list_insert(&dist->links, lnk);
+ }
erts_mtx_unlock(&dist->mtx);
@@ -2350,15 +2461,15 @@ erts_link_dist_insert(ErtsLink *lnk, ErtsMonLnkDist *dist)
ERTS_GLB_INLINE int
erts_link_dist_delete(ErtsLink *lnk)
{
- ErtsLinkDataExtended *ldep;
+ ErtsELink *elnk;
ErtsMonLnkDist *dist;
int delete;
ERTS_ML_ASSERT(lnk->flags & ERTS_ML_FLG_EXTENDED);
ERTS_ML_ASSERT(lnk->type == ERTS_LNK_TYPE_DIST_PROC);
- ldep = (ErtsLinkDataExtended *) erts_link_to_data(lnk);
- dist = ldep->dist;
+ elnk = erts_link_to_elink(lnk);
+ dist = elnk->dist;
if (!dist)
return -1;
diff --git a/erts/emulator/beam/erl_nfunc_sched.c b/erts/emulator/beam/erl_nfunc_sched.c
index b8cf2bee0e..8263a6e9b7 100644
--- a/erts/emulator/beam/erl_nfunc_sched.c
+++ b/erts/emulator/beam/erl_nfunc_sched.c
@@ -30,77 +30,37 @@
#include "erl_nfunc_sched.h"
#include "erl_trace.h"
-NifExport *
-erts_new_proc_nif_export(Process *c_p, int argc)
+ErtsNativeFunc *
+erts_new_proc_nfunc(Process *c_p, int argc)
{
+ ErtsNativeFunc *nep, *old_nep;
size_t size;
- int i;
- NifExport *nep, *old_nep;
-
- size = sizeof(NifExport) + (argc-1)*sizeof(Eterm);
- nep = erts_alloc(ERTS_ALC_T_NIF_TRAP_EXPORT, size);
- for (i = 0; i < ERTS_NUM_CODE_IX; i++)
- nep->exp.addressv[i] = &nep->exp.beam[0];
+ size = sizeof(ErtsNativeFunc) + (argc-1)*sizeof(Eterm);
+ nep = erts_alloc(ERTS_ALC_T_NFUNC_TRAP_WRAPPER, size);
nep->argc = -1; /* unused marker */
nep->argv_size = argc;
- nep->trace = NULL;
- old_nep = ERTS_PROC_SET_NIF_TRAP_EXPORT(c_p, nep);
+ old_nep = ERTS_PROC_SET_NFUNC_TRAP_WRAPPER(c_p, nep);
if (old_nep) {
- ASSERT(!nep->trace);
- erts_free(ERTS_ALC_T_NIF_TRAP_EXPORT, old_nep);
+ erts_free(ERTS_ALC_T_NFUNC_TRAP_WRAPPER, old_nep);
}
return nep;
}
void
-erts_destroy_nif_export(Process *p)
+erts_destroy_nfunc(Process *p)
{
- NifExport *nep = ERTS_PROC_SET_NIF_TRAP_EXPORT(p, NULL);
+ ErtsNativeFunc *nep = ERTS_PROC_SET_NFUNC_TRAP_WRAPPER(p, NULL);
if (nep) {
if (nep->m)
- erts_nif_export_cleanup_nif_mod(nep);
- erts_free(ERTS_ALC_T_NIF_TRAP_EXPORT, nep);
+ erts_nfunc_cleanup_nif_mod(nep);
+ erts_free(ERTS_ALC_T_NFUNC_TRAP_WRAPPER, nep);
}
}
-void
-erts_nif_export_save_trace(Process *c_p, NifExport *nep, int applying,
- Export* ep, BeamInstr *cp, Uint32 flags,
- Uint32 flags_meta, BeamInstr* I,
- ErtsTracer meta_tracer)
-{
- NifExportTrace *netp;
- ASSERT(nep && nep->argc >= 0);
- ASSERT(!nep->trace);
- netp = erts_alloc(ERTS_ALC_T_NIF_EXP_TRACE,
- sizeof(NifExportTrace));
- netp->applying = applying;
- netp->ep = ep;
- netp->cp = cp;
- netp->flags = flags;
- netp->flags_meta = flags_meta;
- netp->I = I;
- netp->meta_tracer = NIL;
- erts_tracer_update(&netp->meta_tracer, meta_tracer);
- nep->trace = netp;
-}
-
-void
-erts_nif_export_restore_trace(Process *c_p, Eterm result, NifExport *nep)
-{
- NifExportTrace *netp = nep->trace;
- nep->trace = NULL;
- erts_bif_trace_epilogue(c_p, result, netp->applying, netp->ep,
- netp->cp, netp->flags, netp->flags_meta,
- netp->I, netp->meta_tracer);
- erts_tracer_update(&netp->meta_tracer, NIL);
- erts_free(ERTS_ALC_T_NIF_EXP_TRACE, netp);
-}
-
-NifExport *
-erts_nif_export_schedule(Process *c_p, Process *dirty_shadow_proc,
+ErtsNativeFunc *
+erts_nfunc_schedule(Process *c_p, Process *dirty_shadow_proc,
ErtsCodeMFA *mfa, BeamInstr *pc,
BeamInstr instr,
void *dfunc, void *ifunc,
@@ -110,7 +70,7 @@ erts_nif_export_schedule(Process *c_p, Process *dirty_shadow_proc,
Process *used_proc;
ErtsSchedulerData *esdp;
Eterm* reg;
- NifExport* nep;
+ ErtsNativeFunc* nep;
int i;
ERTS_LC_ASSERT(erts_proc_lc_my_proc_locks(c_p)
@@ -133,10 +93,10 @@ erts_nif_export_schedule(Process *c_p, Process *dirty_shadow_proc,
reg = esdp->x_reg_array;
if (mfa)
- nep = erts_get_proc_nif_export(c_p, (int) mfa->arity);
+ nep = erts_get_proc_nfunc(c_p, (int) mfa->arity);
else {
/* If no mfa, this is not the first schedule... */
- nep = ERTS_PROC_GET_NIF_TRAP_EXPORT(c_p);
+ nep = ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(c_p);
ASSERT(nep && nep->argc >= 0);
}
@@ -148,16 +108,15 @@ erts_nif_export_schedule(Process *c_p, Process *dirty_shadow_proc,
for (i = 0; i < (int) mfa->arity; i++)
nep->argv[i] = reg[i];
nep->pc = pc;
- nep->cp = c_p->cp;
nep->mfa = mfa;
nep->current = c_p->current;
ASSERT(argc >= 0);
nep->argc = (int) mfa->arity;
nep->m = NULL;
- ASSERT(!erts_check_nif_export_in_area(c_p,
+ ASSERT(!erts_check_nfunc_in_area(c_p,
(char *) nep,
- (sizeof(NifExport)
+ (sizeof(ErtsNativeFunc)
+ (sizeof(Eterm)
*(nep->argc-1)))));
}
@@ -167,14 +126,14 @@ erts_nif_export_schedule(Process *c_p, Process *dirty_shadow_proc,
reg[i] = argv[i];
}
ASSERT(is_atom(mod) && is_atom(func));
- nep->exp.info.mfa.module = mod;
- nep->exp.info.mfa.function = func;
- nep->exp.info.mfa.arity = (Uint) argc;
- nep->exp.beam[0] = (BeamInstr) instr; /* call_nif || apply_bif */
- nep->exp.beam[1] = (BeamInstr) dfunc;
+ nep->trampoline.info.mfa.module = mod;
+ nep->trampoline.info.mfa.function = func;
+ nep->trampoline.info.mfa.arity = (Uint) argc;
+ nep->trampoline.call_op = (BeamInstr) instr; /* call_bif || call_nif */
+ nep->trampoline.dfunc = (BeamInstr) dfunc;
nep->func = ifunc;
used_proc->arity = argc;
used_proc->freason = TRAP;
- used_proc->i = (BeamInstr*) nep->exp.addressv[0];
+ used_proc->i = (BeamInstr*)&nep->trampoline.call_op;
return nep;
}
diff --git a/erts/emulator/beam/erl_nfunc_sched.h b/erts/emulator/beam/erl_nfunc_sched.h
index 1cb252eba5..4dae242d4f 100644
--- a/erts/emulator/beam/erl_nfunc_sched.h
+++ b/erts/emulator/beam/erl_nfunc_sched.h
@@ -25,92 +25,78 @@
#include "bif.h"
#include "error.h"
-typedef struct {
- int applying;
- Export* ep;
- BeamInstr *cp;
- Uint32 flags;
- Uint32 flags_meta;
- BeamInstr* I;
- ErtsTracer meta_tracer;
-} NifExportTrace;
-
/*
- * NIF exports need a few more items than the Export struct provides,
- * including the erl_module_nif* and a NIF function pointer, so the
- * NifExport below adds those. The Export member must be first in the
- * struct. A number of values are stored for error handling purposes
- * only.
+ * Native function wrappers are used to schedule native functions on both
+ * normal and dirty schedulers.
+ *
+ * A number of values are only stored for error handling, and the fields
+ * following `current` can be omitted when a wrapper is statically "scheduled"
+ * through placement in a function stub.
*
- * 'argc' is >= 0 when NifExport is in use, and < 0 when not.
+ * 'argc' is >= 0 when ErtsNativeFunc is in use, and < 0 when not.
*/
typedef struct {
- Export exp;
+ struct {
+ ErtsCodeInfo info;
+ BeamInstr call_op; /* call_bif || call_nif */
+ BeamInstr dfunc;
+ } trampoline;
+
struct erl_module_nif* m; /* NIF module, or NULL if BIF */
void *func; /* Indirect NIF or BIF to execute (may be unused) */
ErtsCodeMFA *current;/* Current as set when originally called */
- NifExportTrace *trace;
/* --- The following is only used on error --- */
BeamInstr *pc; /* Program counter */
- BeamInstr *cp; /* Continuation pointer */
ErtsCodeMFA *mfa; /* MFA of original call */
int argc; /* Number of arguments in original call */
int argv_size; /* Allocated size of argv */
Eterm argv[1]; /* Saved arguments from the original call */
-} NifExport;
-
-NifExport *erts_new_proc_nif_export(Process *c_p, int argc);
-void erts_nif_export_save_trace(Process *c_p, NifExport *nep, int applying,
- Export* ep, BeamInstr *cp, Uint32 flags,
- Uint32 flags_meta, BeamInstr* I,
- ErtsTracer meta_tracer);
-void erts_nif_export_restore_trace(Process *c_p, Eterm result, NifExport *nep);
-void erts_destroy_nif_export(Process *p);
-NifExport *erts_nif_export_schedule(Process *c_p, Process *dirty_shadow_proc,
- ErtsCodeMFA *mfa, BeamInstr *pc,
- BeamInstr instr,
- void *dfunc, void *ifunc,
- Eterm mod, Eterm func,
- int argc, const Eterm *argv);
-void erts_nif_export_cleanup_nif_mod(NifExport *ep); /* erl_nif.c */
-ERTS_GLB_INLINE NifExport *erts_get_proc_nif_export(Process *c_p, int extra);
-ERTS_GLB_INLINE int erts_setup_nif_export_rootset(Process* proc, Eterm** objv,
- Uint* nobj);
-ERTS_GLB_INLINE int erts_check_nif_export_in_area(Process *p,
- char *start, Uint size);
-ERTS_GLB_INLINE void erts_nif_export_restore(Process *c_p, NifExport *ep,
- Eterm result);
-ERTS_GLB_INLINE void erts_nif_export_restore_error(Process* c_p, BeamInstr **pc,
- Eterm *reg, ErtsCodeMFA **nif_mfa);
-ERTS_GLB_INLINE int erts_nif_export_check_save_trace(Process *c_p, Eterm result,
- int applying, Export* ep,
- BeamInstr *cp, Uint32 flags,
- Uint32 flags_meta, BeamInstr* I,
- ErtsTracer meta_tracer);
+} ErtsNativeFunc;
+
+ErtsNativeFunc *erts_new_proc_nfunc(Process *c_p, int argc);
+void erts_destroy_nfunc(Process *p);
+ErtsNativeFunc *erts_nfunc_schedule(Process *c_p, Process *dirty_shadow_proc,
+ ErtsCodeMFA *mfa, BeamInstr *pc,
+ BeamInstr instr,
+ void *dfunc, void *ifunc,
+ Eterm mod, Eterm func,
+ int argc, const Eterm *argv);
+void erts_nfunc_cleanup_nif_mod(ErtsNativeFunc *ep); /* erl_nif.c */
+ERTS_GLB_INLINE ErtsNativeFunc *erts_get_proc_nfunc(Process *c_p, int extra);
+ERTS_GLB_INLINE int erts_setup_nfunc_rootset(Process* proc, Eterm** objv,
+ Uint* nobj);
+ERTS_GLB_INLINE int erts_check_nfunc_in_area(Process *p,
+ char *start, Uint size);
+ERTS_GLB_INLINE void erts_nfunc_restore(Process *c_p, ErtsNativeFunc *ep,
+ Eterm result);
+ERTS_GLB_INLINE void erts_nfunc_restore_error(Process* c_p,
+ BeamInstr **pc,
+ Eterm *reg,
+ ErtsCodeMFA **nif_mfa);
ERTS_GLB_INLINE Process *erts_proc_shadow2real(Process *c_p);
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
-ERTS_GLB_INLINE NifExport *
-erts_get_proc_nif_export(Process *c_p, int argc)
+ERTS_GLB_INLINE ErtsNativeFunc *
+erts_get_proc_nfunc(Process *c_p, int argc)
{
- NifExport *nep = ERTS_PROC_GET_NIF_TRAP_EXPORT(c_p);
+ ErtsNativeFunc *nep = ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(c_p);
if (!nep || (nep->argc < 0 && nep->argv_size < argc))
- return erts_new_proc_nif_export(c_p, argc);
+ return erts_new_proc_nfunc(c_p, argc);
return nep;
}
/*
* If a process has saved arguments, they need to be part of the GC
* rootset. The function below is called from setup_rootset() in
- * erl_gc.c. Any exception term saved in the NifExport is also made
+ * erl_gc.c. Any exception term saved in the ErtsNativeFunc is also made
* part of the GC rootset here; it always resides in rootset[0].
*/
ERTS_GLB_INLINE int
-erts_setup_nif_export_rootset(Process* proc, Eterm** objv, Uint* nobj)
+erts_setup_nfunc_rootset(Process* proc, Eterm** objv, Uint* nobj)
{
- NifExport* ep = (NifExport*) ERTS_PROC_GET_NIF_TRAP_EXPORT(proc);
+ ErtsNativeFunc* ep = (ErtsNativeFunc*) ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(proc);
if (!ep || ep->argc <= 0)
return 0;
@@ -121,18 +107,16 @@ erts_setup_nif_export_rootset(Process* proc, Eterm** objv, Uint* nobj)
}
/*
- * Check if nif export points into code area...
+ * Check if native func wrapper points into code area...
*/
ERTS_GLB_INLINE int
-erts_check_nif_export_in_area(Process *p, char *start, Uint size)
+erts_check_nfunc_in_area(Process *p, char *start, Uint size)
{
- NifExport *nep = ERTS_PROC_GET_NIF_TRAP_EXPORT(p);
+ ErtsNativeFunc *nep = ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(p);
if (!nep || nep->argc < 0)
return 0;
if (ErtsInArea(nep->pc, start, size))
return 1;
- if (ErtsInArea(nep->cp, start, size))
- return 1;
if (ErtsInArea(nep->mfa, start, size))
return 1;
if (ErtsInArea(nep->current, start, size))
@@ -141,7 +125,7 @@ erts_check_nif_export_in_area(Process *p, char *start, Uint size)
}
ERTS_GLB_INLINE void
-erts_nif_export_restore(Process *c_p, NifExport *ep, Eterm result)
+erts_nfunc_restore(Process *c_p, ErtsNativeFunc *ep, Eterm result)
{
ASSERT(!ERTS_SCHEDULER_IS_DIRTY(erts_get_scheduler_data()));
ERTS_LC_ASSERT(!(c_p->static_flags
@@ -151,43 +135,21 @@ erts_nif_export_restore(Process *c_p, NifExport *ep, Eterm result)
c_p->current = ep->current;
ep->argc = -1; /* Unused nif-export marker... */
- if (ep->trace)
- erts_nif_export_restore_trace(c_p, result, ep);
}
ERTS_GLB_INLINE void
-erts_nif_export_restore_error(Process* c_p, BeamInstr **pc,
+erts_nfunc_restore_error(Process* c_p, BeamInstr **pc,
Eterm *reg, ErtsCodeMFA **nif_mfa)
{
- NifExport *nep = (NifExport *) ERTS_PROC_GET_NIF_TRAP_EXPORT(c_p);
+ ErtsNativeFunc *nep = (ErtsNativeFunc *) ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(c_p);
int ix;
ASSERT(nep);
*pc = nep->pc;
- c_p->cp = nep->cp;
*nif_mfa = nep->mfa;
for (ix = 0; ix < nep->argc; ix++)
reg[ix] = nep->argv[ix];
- erts_nif_export_restore(c_p, nep, THE_NON_VALUE);
-}
-
-ERTS_GLB_INLINE int
-erts_nif_export_check_save_trace(Process *c_p, Eterm result,
- int applying, Export* ep,
- BeamInstr *cp, Uint32 flags,
- Uint32 flags_meta, BeamInstr* I,
- ErtsTracer meta_tracer)
-{
- if (is_non_value(result) && c_p->freason == TRAP) {
- NifExport *nep = ERTS_PROC_GET_NIF_TRAP_EXPORT(c_p);
- if (nep && nep->argc >= 0) {
- erts_nif_export_save_trace(c_p, nep, applying, ep,
- cp, flags, flags_meta,
- I, meta_tracer);
- return 1;
- }
- }
- return 0;
+ erts_nfunc_restore(c_p, nep, THE_NON_VALUE);
}
ERTS_GLB_INLINE Process *
@@ -210,10 +172,10 @@ erts_proc_shadow2real(Process *c_p)
#if defined(ERTS_WANT_NFUNC_SCHED_INTERNALS__) && !defined(ERTS_NFUNC_SCHED_INTERNALS__)
#define ERTS_NFUNC_SCHED_INTERNALS__
-#define ERTS_I_BEAM_OP_TO_NIF_EXPORT(I) \
- (ASSERT(BeamIsOpCode(*(I), op_apply_bif) || \
- BeamIsOpCode(*(I), op_call_nif)), \
- ((NifExport *) (((char *) (I)) - offsetof(NifExport, exp.beam[0]))))
+#define ERTS_I_BEAM_OP_TO_NFUNC(I) \
+ (ASSERT(BeamIsOpCode(*(I), op_call_bif_W) || \
+ BeamIsOpCode(*(I), op_call_nif_WWW)), \
+ ((ErtsNativeFunc *) (((char *) (I)) - offsetof(ErtsNativeFunc, trampoline.call_op))))
#include "erl_message.h"
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c
index 87e469dd98..3f03243747 100644
--- a/erts/emulator/beam/erl_nif.c
+++ b/erts/emulator/beam/erl_nif.c
@@ -74,12 +74,25 @@
* 'handle'.
*/
struct erl_module_nif {
+ erts_refc_t refc; /* References to this struct
+ * +1 erl_module_instance (loaded Erlang code)
+ * +1 "dlopen" (loaded native code)
+ * +1 scheduled load finisher
+ * +1 for each owned resource type
+ */
+ erts_mtx_t load_mtx; /* protects load finish from unload */
+ struct ErtsNifFinish_* finish;
+ ErtsThrPrgrLaterOp lop;
+
void* priv_data;
void* handle; /* "dlopen" */
struct enif_entry_t entry;
- erts_refc_t rt_cnt; /* number of resource types */
- erts_refc_t rt_dtor_cnt; /* number of resource types with destructors */
- Module* mod; /* Can be NULL if orphan with dtor-resources left */
+ erts_refc_t dynlib_refc; /* References to loaded native code
+ +1 erl_module_instance
+ +1 for each owned resource type with callbacks
+ +1 for each ongoing dirty NIF call
+ */
+ Module* mod; /* Can be NULL if purged and dynlib_refc > 0 */
ErlNifFunc _funcs_copy_[1]; /* only used for old libs */
};
@@ -309,10 +322,10 @@ void erts_post_nif(ErlNifEnv* env)
/*
- * Initialize a NifExport struct. Create it if needed and store it in the
+ * Initialize a ErtsNativeFunc struct. Create it if needed and store it in the
* proc. The direct_fp function is what will be invoked by op_call_nif, and
* the indirect_fp function, if not NULL, is what the direct_fp function
- * will call. If the allocated NifExport isn't enough to hold all of argv,
+ * will call. If the allocated ErtsNativeFunc isn't enough to hold all of argv,
* allocate a larger one. Save 'current' and registers if first time this
* call is scheduled.
*/
@@ -321,7 +334,7 @@ static ERTS_INLINE ERL_NIF_TERM
schedule(ErlNifEnv* env, NativeFunPtr direct_fp, NativeFunPtr indirect_fp,
Eterm mod, Eterm func_name, int argc, const ERL_NIF_TERM argv[])
{
- NifExport *ep;
+ ErtsNativeFunc *ep;
Process *c_p, *dirty_shadow_proc;
execution_state(env, &c_p, NULL);
@@ -332,16 +345,16 @@ schedule(ErlNifEnv* env, NativeFunPtr direct_fp, NativeFunPtr indirect_fp,
ERTS_LC_ASSERT(ERTS_PROC_LOCK_MAIN & erts_proc_lc_my_proc_locks(c_p));
- ep = erts_nif_export_schedule(c_p, dirty_shadow_proc,
+ ep = erts_nfunc_schedule(c_p, dirty_shadow_proc,
c_p->current,
- c_p->cp,
- BeamOpCodeAddr(op_call_nif),
+ cp_val(c_p->stop[0]),
+ BeamOpCodeAddr(op_call_nif_WWW),
direct_fp, indirect_fp,
mod, func_name,
argc, (const Eterm *) argv);
if (!ep->m) {
/* First time this call is scheduled... */
- erts_refc_inc(&env->mod_nif->rt_dtor_cnt, 1);
+ erts_refc_inc(&env->mod_nif->dynlib_refc, 2);
ep->m = env->mod_nif;
}
return (ERL_NIF_TERM) THE_NON_VALUE;
@@ -356,7 +369,7 @@ erts_call_dirty_nif(ErtsSchedulerData *esdp, Process *c_p, BeamInstr *I, Eterm *
{
int exiting;
ERL_NIF_TERM *argv = (ERL_NIF_TERM *) reg;
- NifExport *nep = ERTS_I_BEAM_OP_TO_NIF_EXPORT(I);
+ ErtsNativeFunc *nep = ERTS_I_BEAM_OP_TO_NFUNC(I);
ErtsCodeMFA *codemfa = erts_code_to_codemfa(I);
NativeFunPtr dirty_nif = (NativeFunPtr) I[1];
ErlNifEnv env;
@@ -364,7 +377,7 @@ erts_call_dirty_nif(ErtsSchedulerData *esdp, Process *c_p, BeamInstr *I, Eterm *
#ifdef DEBUG
erts_aint32_t state = erts_atomic32_read_nob(&c_p->state);
- ASSERT(nep == ERTS_PROC_GET_NIF_TRAP_EXPORT(c_p));
+ ASSERT(nep == ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(c_p));
ASSERT(!c_p->scheduler_data);
ASSERT((state & ERTS_PSFLG_DIRTY_RUNNING)
@@ -815,7 +828,7 @@ int enif_send(ErlNifEnv* env, const ErlNifPid* to_pid,
}
#endif
if (have_seqtrace(stoken)) {
- seq_trace_update_send(c_p);
+ seq_trace_update_serial(c_p);
seq_trace_output(stoken, msg, SEQ_TRACE_SEND,
rp->common.id, c_p);
}
@@ -1761,16 +1774,20 @@ ERL_NIF_TERM enif_make_uint(ErlNifEnv* env, unsigned i)
ERL_NIF_TERM enif_make_long(ErlNifEnv* env, long i)
{
+#if SIZEOF_LONG < ERTS_SIZEOF_ETERM
+ return make_small(i);
+#else
if (IS_SSMALL(i)) {
return make_small(i);
}
-#if SIZEOF_LONG == ERTS_SIZEOF_ETERM
+# if SIZEOF_LONG == ERTS_SIZEOF_ETERM
return small_to_big(i, alloc_heap(env,2));
-#elif SIZEOF_LONG_LONG == ERTS_SIZEOF_ETERM
+# elif SIZEOF_LONG_LONG == ERTS_SIZEOF_ETERM
return make_small(i);
-#elif SIZEOF_LONG == 8
+# elif SIZEOF_LONG == 8
ensure_heap(env,3);
return erts_sint64_to_big(i, &env->hp);
+# endif
#endif
}
@@ -2189,7 +2206,10 @@ int enif_vsnprintf(char* buffer, size_t size, const char *format, va_list ap)
** Memory managed (GC'ed) "resource" objects **
***********************************************************/
-/* dummy node in circular list */
+/*
+ * Sentinel node in circular list of all resource types.
+ * List protected by code_write_permission.
+ */
struct enif_resource_type_t resource_type_list;
static ErlNifResourceType* find_resource_type(Eterm module, Eterm name)
@@ -2209,16 +2229,26 @@ static ErlNifResourceType* find_resource_type(Eterm module, Eterm name)
#define in_area(ptr,start,nbytes) \
((UWord)((char*)(ptr) - (char*)(start)) < (nbytes))
-static ERTS_INLINE int rt_have_callbacks(ErlNifResourceType* rt)
+static ERTS_INLINE int rt_have_callbacks(ErlNifResourceTypeInit* rti)
+{
+ return rti->dtor != NULL;
+}
+
+static void deref_nifmod(struct erl_module_nif* lib)
{
- return rt->dtor != NULL;
+ if (erts_refc_dectest(&lib->refc, 0) == 0) {
+ ASSERT(lib->handle == NULL);
+ ASSERT(lib->mod == NULL);
+ erts_free(ERTS_ALC_T_NIF, lib);
+ }
}
-static void close_lib(struct erl_module_nif* lib)
+static void close_dynlib(struct erl_module_nif* lib)
{
ASSERT(lib != NULL);
+ ASSERT(lib->mod == NULL);
ASSERT(lib->handle != NULL);
- ASSERT(erts_refc_read(&lib->rt_dtor_cnt,0) == 0);
+ ASSERT(erts_refc_read(&lib->dynlib_refc,0) == 0);
if (lib->entry.unload != NULL) {
struct enif_msg_environment_t msg_env;
@@ -2228,24 +2258,56 @@ static void close_lib(struct erl_module_nif* lib)
}
if (!erts_is_static_nif(lib->handle))
erts_sys_ddll_close(lib->handle);
+
lib->handle = NULL;
+ deref_nifmod(lib);
}
static void steal_resource_type(ErlNifResourceType* type)
{
struct erl_module_nif* lib = type->owner;
- if (rt_have_callbacks(type)
- && erts_refc_dectest(&lib->rt_dtor_cnt, 0) == 0
- && lib->mod == NULL) {
- /* last type with destructor gone, close orphan lib */
-
- close_lib(lib);
- }
- if (erts_refc_dectest(&lib->rt_cnt, 0) == 0
- && lib->mod == NULL) {
- erts_free(ERTS_ALC_T_NIF, lib);
+ if (rt_have_callbacks(&type->fn_real)
+ && erts_refc_dectest(&lib->dynlib_refc, 0) == 0) {
+ /* last resource type with callbacks gone, close purged lib */
+ close_dynlib(lib);
}
+ deref_nifmod(lib);
+}
+
+static erts_rwmtx_t erts_nif_call_tab_lock;
+
+static void resource_dtor_during_takeover(ErlNifEnv* env, void* obj)
+{
+ ErtsResource* resource = DATA_TO_RESOURCE(obj);
+ ErlNifResourceType* rt = resource->type;
+
+ erts_rwmtx_rlock(&erts_nif_call_tab_lock);
+ if (rt->fn_real.dtor)
+ rt->fn_real.dtor(env, obj);
+ erts_rwmtx_runlock(&erts_nif_call_tab_lock);
+}
+static void resource_stop_during_takeover(ErlNifEnv* env, void* obj,
+ ErlNifEvent e, int is_direct_call)
+{
+ ErtsResource* resource = DATA_TO_RESOURCE(obj);
+ ErlNifResourceType* rt = resource->type;
+
+ erts_rwmtx_rlock(&erts_nif_call_tab_lock);
+ ASSERT(rt->fn_real.stop);
+ rt->fn_real.stop(env, obj, e, is_direct_call);
+ erts_rwmtx_runlock(&erts_nif_call_tab_lock);
+}
+static void resource_down_during_takeover(ErlNifEnv* env, void* obj,
+ ErlNifPid* pid, ErlNifMonitor* mon)
+{
+ ErtsResource* resource = DATA_TO_RESOURCE(obj);
+ ErlNifResourceType* rt = resource->type;
+
+ erts_rwmtx_rlock(&erts_nif_call_tab_lock);
+ ASSERT(rt->fn_real.down);
+ rt->fn_real.down(env, obj, pid, mon);
+ erts_rwmtx_runlock(&erts_nif_call_tab_lock);
}
static void resource_dtor_nop(ErlNifEnv* env, void* obj)
@@ -2278,7 +2340,7 @@ ErlNifResourceType* open_resource_type(ErlNifEnv* env,
ErlNifResourceFlags op = flags;
Eterm module_am, name_am;
- ASSERT(erts_thr_progress_is_blocking());
+ ERTS_LC_ASSERT(erts_has_code_write_permission());
module_am = make_atom(env->mod_nif->mod->module);
name_am = enif_make_atom(env, name_str);
@@ -2292,7 +2354,8 @@ ErlNifResourceType* open_resource_type(ErlNifEnv* env,
erts_refc_init(&type->refc, 1);
op = ERL_NIF_RT_CREATE;
#ifdef DEBUG
- type->dtor = (void*)1;
+ type->fn.dtor = (void*)1;
+ type->fn_real.dtor = (void*)1;
type->owner = (void*)2;
type->prev = (void*)3;
type->next = (void*)4;
@@ -2355,14 +2418,16 @@ enif_open_resource_type_x(ErlNifEnv* env,
env->mod_nif->entry.sizeof_ErlNifResourceTypeInit);
}
-static void commit_opened_resource_types(struct erl_module_nif* lib)
+static void prepare_opened_rt(struct erl_module_nif* lib)
{
- while (opened_rt_list) {
- struct opened_resource_type* ort = opened_rt_list;
+ struct opened_resource_type* ort = opened_rt_list;
+ while (ort) {
ErlNifResourceType* type = ort->type;
if (ort->op == ERL_NIF_RT_CREATE) {
+ type->fn = ort->new_callbacks;
+ type->fn_real = ort->new_callbacks;
type->prev = &resource_type_list;
type->next = resource_type_list.next;
type->next->prev = type;
@@ -2370,20 +2435,55 @@ static void commit_opened_resource_types(struct erl_module_nif* lib)
}
else { /* ERL_NIF_RT_TAKEOVER */
steal_resource_type(type);
+
+ /*
+ * Prepare for atomic change of callbacks with lock-wrappers
+ */
+ type->fn.dtor = resource_dtor_during_takeover;
+ type->fn.stop = resource_stop_during_takeover;
+ type->fn.down = resource_down_during_takeover;
}
+ type->owner = lib;
- type->owner = lib;
- type->dtor = ort->new_callbacks.dtor;
- type->stop = ort->new_callbacks.stop;
- type->down = ort->new_callbacks.down;
+ if (rt_have_callbacks(&ort->new_callbacks))
+ erts_refc_inc(&lib->dynlib_refc, 2);
+ erts_refc_inc(&lib->refc, 2);
- if (rt_have_callbacks(type)) {
- erts_refc_inc(&lib->rt_dtor_cnt, 1);
- }
- erts_refc_inc(&lib->rt_cnt, 1);
+ ort = ort->next;
+ }
+}
- opened_rt_list = ort->next;
- erts_free(ERTS_ALC_T_TMP, ort);
+/*
+ * Do atomic change from old to new callbacks
+ */
+static void commit_opened_rt(void)
+{
+ struct opened_resource_type* ort = opened_rt_list;
+
+ ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&erts_nif_call_tab_lock));
+
+ while (ort) {
+ if (ort->op == ERL_NIF_RT_TAKEOVER) {
+ ort->type->fn_real = ort->new_callbacks;
+ }
+ ort = ort->next;
+ }
+}
+
+/*
+ * Cleanup and let callbacks be called directly without locking
+ */
+static void cleanup_opened_rt(void)
+{
+ struct opened_resource_type* ort = opened_rt_list;
+
+ while (opened_rt_list) {
+ ort = opened_rt_list;
+ if (ort->op == ERL_NIF_RT_TAKEOVER) {
+ ort->type->fn = ort->new_callbacks;
+ }
+ opened_rt_list = ort->next;
+ erts_free(ERTS_ALC_T_TMP, ort);
}
}
@@ -2494,7 +2594,7 @@ static void run_resource_dtor(void* vbin)
ErtsMonitor *root;
Uint refc;
- ASSERT(type->down);
+ ASSERT(type->fn.down);
erts_mtx_lock(&rm->lock);
ASSERT(erts_refc_read(&bin->intern.refc, 0) == 0);
kill = !rmon_is_dying(rm);
@@ -2523,10 +2623,10 @@ static void run_resource_dtor(void* vbin)
erts_mtx_destroy(&rm->lock);
}
- if (type->dtor != NULL) {
+ if (type->fn.dtor != NULL) {
struct enif_msg_environment_t msg_env;
pre_nif_noproc(&msg_env, type->owner, NULL);
- type->dtor(&msg_env.env, resource->data);
+ type->fn.dtor(&msg_env.env, resource->data);
post_nif_noproc(&msg_env);
}
if (erts_refc_dectest(&type->refc, 0) == 0) {
@@ -2543,9 +2643,9 @@ void erts_resource_stop(ErtsResource* resource, ErlNifEvent e,
int is_direct_call)
{
struct enif_msg_environment_t msg_env;
- ASSERT(resource->type->stop);
+ ASSERT(resource->type->fn.stop);
pre_nif_noproc(&msg_env, resource->type->owner, NULL);
- resource->type->stop(&msg_env.env, resource->data, e, is_direct_call);
+ resource->type->fn.stop(&msg_env.env, resource->data, e, is_direct_call);
post_nif_noproc(&msg_env);
}
@@ -2556,7 +2656,7 @@ void erts_nif_demonitored(ErtsResource* resource)
int free_me;
ASSERT(rmp);
- ASSERT(resource->type->down);
+ ASSERT(resource->type->fn.down);
erts_mtx_lock(&rmp->lock);
free_me = ((rmon_refc_dec_read(rmp) == 0) & !!rmon_is_dying(rmp));
@@ -2590,7 +2690,7 @@ void erts_fire_nif_monitor(ErtsMonitor *tmon)
omon = &mdp->origin;
ASSERT(rmp);
- ASSERT(resource->type->down);
+ ASSERT(resource->type->fn.down);
erts_mtx_lock(&rmp->lock);
@@ -2606,9 +2706,10 @@ void erts_fire_nif_monitor(ErtsMonitor *tmon)
erts_mtx_unlock(&rmp->lock);
if (!active) {
- ASSERT(!is_dying || erts_refc_read(&bin->binary.intern.refc, 0) == 0);
- if (is_dying && mrefc == 0)
+ if (is_dying && mrefc == 0) {
+ ASSERT(erts_refc_read(&bin->binary.intern.refc, 0) == 0);
erts_bin_free(&bin->binary);
+ }
erts_monitor_release(tmon);
}
else {
@@ -2617,7 +2718,7 @@ void erts_fire_nif_monitor(ErtsMonitor *tmon)
erts_ref_to_driver_monitor(mdp->ref, &nif_monitor);
nif_pid.pid = omon->other.item;
pre_nif_noproc(&msg_env, resource->type->owner, NULL);
- resource->type->down(&msg_env.env, resource->data, &nif_pid, &nif_monitor);
+ resource->type->fn.down(&msg_env.env, resource->data, &nif_pid, &nif_monitor);
post_nif_noproc(&msg_env);
erts_bin_release(&bin->binary);
@@ -2634,7 +2735,7 @@ void* enif_alloc_resource(ErlNifResourceType* type, size_t data_sz)
ErtsResource* resource;
size_t monitors_offs;
- if (type->down) {
+ if (type->fn.down) {
/* Put ErtsResourceMonitors after user data and properly aligned */
monitors_offs = ((data_sz + ERTS_ALLOC_ALIGN_BYTES - 1)
& ~((size_t)ERTS_ALLOC_ALIGN_BYTES - 1));
@@ -2656,7 +2757,7 @@ void* enif_alloc_resource(ErlNifResourceType* type, size_t data_sz)
erts_refc_init(&resource->nif_refc, 1);
#endif
erts_refc_inc(&resource->type->refc, 2);
- if (type->down) {
+ if (type->fn.down) {
resource->monitors = (ErtsResourceMonitors*) (resource->data + monitors_offs);
erts_mtx_init(&resource->monitors->lock, "resource_monitors", NIL,
ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
@@ -2838,25 +2939,25 @@ int enif_consume_timeslice(ErlNifEnv* env, int percent)
}
static ERTS_INLINE void
-nif_export_cleanup_nif_mod(NifExport *ep)
+nfunc_cleanup_nif_mod(ErtsNativeFunc *ep)
{
- if (erts_refc_dectest(&ep->m->rt_dtor_cnt, 0) == 0 && ep->m->mod == NULL)
- close_lib(ep->m);
+ if (erts_refc_dectest(&ep->m->dynlib_refc, 0) == 0)
+ close_dynlib(ep->m);
ep->m = NULL;
}
void
-erts_nif_export_cleanup_nif_mod(NifExport *ep)
+erts_nfunc_cleanup_nif_mod(ErtsNativeFunc *ep)
{
- nif_export_cleanup_nif_mod(ep);
+ nfunc_cleanup_nif_mod(ep);
}
static ERTS_INLINE void
-nif_export_restore(Process *c_p, NifExport *ep, Eterm res)
+nfunc_restore(Process *c_p, ErtsNativeFunc *ep, Eterm res)
{
- erts_nif_export_restore(c_p, ep, res);
+ erts_nfunc_restore(c_p, ep, res);
ASSERT(ep->m);
- nif_export_cleanup_nif_mod(ep);
+ nfunc_cleanup_nif_mod(ep);
}
@@ -2873,15 +2974,15 @@ static ERL_NIF_TERM
dirty_nif_finalizer(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
Process* proc;
- NifExport* ep;
+ ErtsNativeFunc* ep;
execution_state(env, &proc, NULL);
ASSERT(argc == 1);
ASSERT(!ERTS_SCHEDULER_IS_DIRTY(erts_proc_sched_data(proc)));
- ep = (NifExport*) ERTS_PROC_GET_NIF_TRAP_EXPORT(proc);
+ ep = (ErtsNativeFunc*) ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(proc);
ASSERT(ep);
- nif_export_restore(proc, ep, argv[0]);
+ nfunc_restore(proc, ep, argv[0]);
return argv[0];
}
@@ -2893,21 +2994,22 @@ dirty_nif_exception(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ERL_NIF_TERM ret;
Process* proc;
- NifExport* ep;
+ ErtsNativeFunc* ep;
Eterm exception;
execution_state(env, &proc, NULL);
ASSERT(argc == 1);
ASSERT(!ERTS_SCHEDULER_IS_DIRTY(erts_proc_sched_data(proc)));
- ep = (NifExport*) ERTS_PROC_GET_NIF_TRAP_EXPORT(proc);
+ ep = (ErtsNativeFunc*) ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(proc);
ASSERT(ep);
exception = argv[0]; /* argv overwritten by restore below... */
- nif_export_cleanup_nif_mod(ep);
+ nfunc_cleanup_nif_mod(ep);
ret = enif_raise_exception(env, exception);
- /* Restore orig info for error and clear nif export in handle_error() */
- proc->freason |= EXF_RESTORE_NIF;
+ /* Restore orig info for error and clear native func wrapper in
+ * handle_error() */
+ proc->freason |= EXF_RESTORE_NFUNC;
return ret;
}
@@ -2944,7 +3046,7 @@ static_schedule_dirty_nif(ErlNifEnv* env, erts_aint32_t dirty_psflg,
int argc, const ERL_NIF_TERM argv[])
{
Process *proc;
- NifExport *ep;
+ ErtsNativeFunc *ep;
Eterm mod, func;
NativeFunPtr fp;
@@ -2954,12 +3056,11 @@ static_schedule_dirty_nif(ErlNifEnv* env, erts_aint32_t dirty_psflg,
* Called in order to schedule statically determined
* dirty NIF calls...
*
- * Note that 'current' does not point into a NifExport
- * structure; only a structure with similar
- * parts (located in code).
+ * Note that 'current' does not point into a ErtsNativeFunc
+ * structure; only a structure with similar parts (located in code).
*/
- ep = ErtsContainerStruct(proc->current, NifExport, exp.info.mfa);
+ ep = ErtsContainerStruct(proc->current, ErtsNativeFunc, trampoline.info.mfa);
mod = proc->current->module;
func = proc->current->function;
fp = (NativeFunPtr) ep->func;
@@ -2987,7 +3088,6 @@ static_schedule_dirty_cpu_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
return static_schedule_dirty_nif(env, ERTS_PSFLG_DIRTY_CPU_PROC, argc, argv);
}
-
/*
* NIF execution wrapper used by enif_schedule_nif() for regular NIFs. It
* calls the actual NIF, restores original NIF MFA if necessary, and
@@ -2998,12 +3098,12 @@ execute_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
Process* proc;
NativeFunPtr fp;
- NifExport* ep;
+ ErtsNativeFunc* ep;
ERL_NIF_TERM result;
execution_state(env, &proc, NULL);
- ep = ErtsContainerStruct(proc->current, NifExport, exp.info.mfa);
+ ep = ErtsContainerStruct(proc->current, ErtsNativeFunc, trampoline.info.mfa);
fp = ep->func;
ASSERT(ep);
ASSERT(!env->exception_thrown);
@@ -3016,20 +3116,20 @@ execute_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
result = (*fp)(env, argc, argv);
- ASSERT(ep == ERTS_PROC_GET_NIF_TRAP_EXPORT(proc));
+ ASSERT(ep == ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(proc));
if (is_value(result) || proc->freason != TRAP) {
/* Done (not rescheduled)... */
ASSERT(ep->func == ERTS_DBG_NIF_NOT_SCHED_MARKER);
if (!env->exception_thrown)
- nif_export_restore(proc, ep, result);
+ nfunc_restore(proc, ep, result);
else {
- nif_export_cleanup_nif_mod(ep);
+ nfunc_cleanup_nif_mod(ep);
/*
* Restore orig info for error and clear nif
* export in handle_error()
*/
- proc->freason |= EXF_RESTORE_NIF;
+ proc->freason |= EXF_RESTORE_NFUNC;
}
}
@@ -3439,14 +3539,14 @@ int enif_monitor_process(ErlNifEnv* env, void* obj, const ErlNifPid* target_pid,
ASSERT(ERTS_MAGIC_BIN_FROM_UNALIGNED_DATA(rsrc)->magic_binary.destructor
== NIF_RESOURCE_DTOR);
ASSERT(erts_refc_read(&ERTS_MAGIC_BIN_FROM_UNALIGNED_DATA(rsrc)->binary.intern.refc, 0) != 0);
- ASSERT(!rsrc->monitors == !rsrc->type->down);
+ ASSERT(!rsrc->monitors == !rsrc->type->fn.down);
rm = rsrc->monitors;
if (!rm) {
- ASSERT(!rsrc->type->down);
+ ASSERT(!rsrc->type->fn.down);
return -1;
}
- ASSERT(rsrc->type->down);
+ ASSERT(rsrc->type->fn.down);
if (target_pid->pid == am_undefined)
return 1;
@@ -3996,7 +4096,7 @@ void erts_add_taint(Eterm mod_atom)
struct tainted_module_t *first, *t;
ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&erts_driver_list_lock)
- || erts_thr_progress_is_blocking());
+ || erts_has_code_write_permission());
first = (struct tainted_module_t*) erts_atomic_read_nob(&first_taint);
for (t=first ; t; t=t->next) {
@@ -4008,7 +4108,7 @@ void erts_add_taint(Eterm mod_atom)
if (t != NULL) {
t->module_atom = mod_atom;
t->next = first;
- erts_atomic_set_nob(&first_taint, (erts_aint_t)t);
+ erts_atomic_set_relb(&first_taint, (erts_aint_t)t);
}
}
@@ -4019,7 +4119,7 @@ Eterm erts_nif_taints(Process* p)
Eterm list = NIL;
Eterm* hp;
- first = (struct tainted_module_t*) erts_atomic_read_nob(&first_taint);
+ first = (struct tainted_module_t*) erts_atomic_read_acqb(&first_taint);
for (t=first ; t!=NULL; t=t->next) {
cnt++;
}
@@ -4091,6 +4191,8 @@ static struct erl_module_nif* create_lib(const ErlNifEntry* src)
bytes += src->num_of_funcs * sizeof(ErlNifFunc);
lib = erts_alloc(ERTS_ALC_T_NIF, bytes);
+ erts_mtx_init(&lib->load_mtx, "nif_load", NIL,
+ ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
dst = &lib->entry;
sys_memcpy(dst, src, offsetof(ErlNifEntry, vm_variant));
@@ -4132,8 +4234,89 @@ static struct erl_module_nif* create_lib(const ErlNifEntry* src)
return lib;
};
+/* load_nif/2 is implemented as an instruction as it needs to know where it
+ * was called from, and it's a pain to get that information in a BIF.
+ *
+ * This is a small stub that rejects apply(erlang, load_nif, [Path, Args]). */
+BIF_RETTYPE load_nif_2(BIF_ALIST_2) {
+ if (BIF_P->flags & F_HIPE_MODE) {
+ BIF_RET(load_nif_error(BIF_P, "notsup",
+ "Calling load_nif from HiPE compiled modules "
+ "not supported"));
+ }
+
+ BIF_RET(load_nif_error(BIF_P, "bad_lib",
+ "load_nif/2 must be explicitly called from the NIF "
+ "module. It cannot be called through apply/3."));
+}
+
+typedef struct {
+ HashBucket bucket;
+ ErtsCodeInfo* code_info;
+ ErtsCodeMFA mfa;
+ BeamInstr beam[4];
+} ErtsNifBeamStub;
+
+typedef struct ErtsNifFinish_ {
+ int nstubs_hashed; /* can be less than 'num_of_funcs' if load failed */
+ ErtsNifBeamStub beam_stubv[1];
+} ErtsNifFinish;
+
+#define sizeof_ErtsNifFinish(N) \
+ (offsetof(ErtsNifFinish, beam_stubv) + (N)*sizeof(ErtsNifBeamStub))
+
+static void load_nif_1st_finisher(void* vlib);
+static void load_nif_2nd_finisher(void* vlib);
+static void load_nif_3rd_finisher(void* vlib);
+static void release_beam_stubs(struct erl_module_nif* lib);
+static void erase_hashed_stubs(ErtsNifFinish*);
+
+struct hash erts_nif_call_tab;
+
+static HashValue nif_call_hash(ErtsNifBeamStub* obj)
+{
+ return ((HashValue)obj->code_info / sizeof(BeamInstr));
+}
+
+static int nif_call_cmp(ErtsNifBeamStub* tmpl, ErtsNifBeamStub* obj)
+{
+ return tmpl->code_info != obj->code_info;
+}
+
+static ErtsNifBeamStub* nif_call_alloc(ErtsNifBeamStub* tmpl)
+{
+ return tmpl;
+}
+
+static void nif_call_free(ErtsNifBeamStub* obj)
+{
+}
-BIF_RETTYPE load_nif_2(BIF_ALIST_2)
+static void nif_call_table_init(void)
+{
+ HashFunctions f;
+
+ erts_rwmtx_opt_t rwmtx_opt = ERTS_RWMTX_OPT_DEFAULT_INITER;
+ rwmtx_opt.lived = ERTS_RWMTX_LONG_LIVED;
+
+ erts_rwmtx_init_opt(&erts_nif_call_tab_lock, &rwmtx_opt, "nif_call_tab",
+ NIL, (ERTS_LOCK_FLAGS_PROPERTY_STATIC |
+ ERTS_LOCK_FLAGS_CATEGORY_GENERIC));
+
+ f.hash = (H_FUN) nif_call_hash;
+ f.cmp = (HCMP_FUN) nif_call_cmp;
+ f.alloc = (HALLOC_FUN) nif_call_alloc;
+ f.free = (HFREE_FUN) nif_call_free;
+ f.meta_alloc = (HMALLOC_FUN) erts_alloc;
+ f.meta_free = (HMFREE_FUN) erts_free;
+ f.meta_print = (HMPRINT_FUN) erts_print;
+
+ hash_init(ERTS_ALC_T_NIF, &erts_nif_call_tab, "nif_call_tab", 100, f);
+}
+
+static void patch_call_nif_early(ErlNifEntry*, struct erl_module_instance*);
+
+Eterm erts_load_nif(Process *c_p, BeamInstr *I, Eterm filename, Eterm args)
{
static const char bad_lib[] = "bad_lib";
static const char upgrade[] = "upgrade";
@@ -4156,42 +4339,21 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
struct erl_module_instance* this_mi;
struct erl_module_instance* prev_mi;
- if (BIF_P->flags & F_HIPE_MODE) {
- ret = load_nif_error(BIF_P, "notsup", "Calling load_nif from HiPE compiled "
- "modules not supported");
- BIF_RET(ret);
- }
-
encoding = erts_get_native_filename_encoding();
if (encoding == ERL_FILENAME_WIN_WCHAR) {
/* Do not convert the lib name to utf-16le yet, do that in win32 specific code */
/* since lib_name is used in error messages */
encoding = ERL_FILENAME_UTF8;
}
- lib_name = erts_convert_filename_to_encoding(BIF_ARG_1, NULL, 0,
+ lib_name = erts_convert_filename_to_encoding(filename, NULL, 0,
ERTS_ALC_T_TMP, 1, 0, encoding,
NULL, 0);
if (!lib_name) {
- BIF_ERROR(BIF_P, BADARG);
- }
-
- if (!erts_try_seize_code_write_permission(BIF_P)) {
- erts_free(ERTS_ALC_T_TMP, lib_name);
- ERTS_BIF_YIELD2(bif_export[BIF_load_nif_2],
- BIF_P, BIF_ARG_1, BIF_ARG_2);
+ return THE_NON_VALUE;
}
- /* Block system (is this the right place to do it?) */
- erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
- erts_thr_progress_block();
- erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
-
/* Find calling module */
- ASSERT(BIF_P->current != NULL);
- ASSERT(BIF_P->current->module == am_erlang
- && BIF_P->current->function == am_load_nif
- && BIF_P->current->arity == 2);
- caller = find_function_from_pc(BIF_P->cp);
+ caller = find_function_from_pc(I);
ASSERT(caller != NULL);
mod_atom = caller->module;
ASSERT(is_atom(mod_atom));
@@ -4211,7 +4373,7 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
this_mi = &module_p->curr;
prev_mi = &module_p->old;
if (in_area(caller, module_p->old.code_hdr, module_p->old.code_length)) {
- ret = load_nif_error(BIF_P, "old_code", "Calling load_nif from old "
+ ret = load_nif_error(c_p, "old_code", "Calling load_nif from old "
"module '%T' not allowed", mod_atom);
goto error;
} else if (module_p->on_load) {
@@ -4225,52 +4387,52 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
}
if (this_mi->nif != NULL) {
- ret = load_nif_error(BIF_P,"reload","NIF library already loaded"
+ ret = load_nif_error(c_p,"reload","NIF library already loaded"
" (reload disallowed since OTP 20).");
}
else if (init_func == NULL &&
(err=erts_sys_ddll_open(lib_name, &handle, &errdesc)) != ERL_DE_NO_ERROR) {
const char slogan[] = "Failed to load NIF library";
if (strstr(errdesc.str, lib_name) != NULL) {
- ret = load_nif_error(BIF_P, "load_failed", "%s: '%s'", slogan, errdesc.str);
+ ret = load_nif_error(c_p, "load_failed", "%s: '%s'", slogan, errdesc.str);
}
else {
- ret = load_nif_error(BIF_P, "load_failed", "%s %s: '%s'", slogan, lib_name, errdesc.str);
+ ret = load_nif_error(c_p, "load_failed", "%s %s: '%s'", slogan, lib_name, errdesc.str);
}
}
else if (init_func == NULL &&
erts_sys_ddll_load_nif_init(handle, &init_func, &errdesc) != ERL_DE_NO_ERROR) {
- ret = load_nif_error(BIF_P, bad_lib, "Failed to find library init"
+ ret = load_nif_error(c_p, bad_lib, "Failed to find library init"
" function: '%s'", errdesc.str);
}
else if ((taint ? erts_add_taint(mod_atom) : 0,
(entry = erts_sys_ddll_call_nif_init(init_func)) == NULL)) {
- ret = load_nif_error(BIF_P, bad_lib, "Library init-call unsuccessful");
+ ret = load_nif_error(c_p, bad_lib, "Library init-call unsuccessful");
}
else if (entry->major > ERL_NIF_MAJOR_VERSION
|| (entry->major == ERL_NIF_MAJOR_VERSION
&& entry->minor > ERL_NIF_MINOR_VERSION)) {
char* fmt = "That '%T' NIF library needs %s or newer. Either try to"
" recompile the NIF lib or use a newer erts runtime.";
- ret = load_nif_error(BIF_P, bad_lib, fmt, mod_atom, entry->min_erts);
+ ret = load_nif_error(c_p, bad_lib, fmt, mod_atom, entry->min_erts);
}
else if (entry->major < ERL_NIF_MIN_REQUIRED_MAJOR_VERSION_ON_LOAD
|| (entry->major==2 && entry->minor == 5)) { /* experimental maps */
char* fmt = "That old NIF library (%d.%d) is not compatible with this "
"erts runtime (%d.%d). Try recompile the NIF lib.";
- ret = load_nif_error(BIF_P, bad_lib, fmt, entry->major, entry->minor,
+ ret = load_nif_error(c_p, bad_lib, fmt, entry->major, entry->minor,
ERL_NIF_MAJOR_VERSION, ERL_NIF_MINOR_VERSION);
}
else if (AT_LEAST_VERSION(entry, 2, 1)
&& sys_strcmp(entry->vm_variant, ERL_NIF_VM_VARIANT) != 0) {
- ret = load_nif_error(BIF_P, bad_lib, "Library (%s) not compiled for "
+ ret = load_nif_error(c_p, bad_lib, "Library (%s) not compiled for "
"this vm variant (%s).",
entry->vm_variant, ERL_NIF_VM_VARIANT);
}
else if (!erts_is_atom_str((char*)entry->name, mod_atom, 1)) {
- ret = load_nif_error(BIF_P, bad_lib, "Library module name '%s' does not"
+ ret = load_nif_error(c_p, bad_lib, "Library module name '%s' does not"
" match calling module '%T'", entry->name, mod_atom);
}
else {
@@ -4278,37 +4440,66 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
entry = &lib->entry; /* Use a guaranteed modern lib entry from now on */
lib->handle = handle;
- erts_refc_init(&lib->rt_cnt, 0);
- erts_refc_init(&lib->rt_dtor_cnt, 0);
+ erts_refc_init(&lib->refc, 2); /* Erlang code + NIF code */
+ erts_refc_init(&lib->dynlib_refc, 1);
ASSERT(opened_rt_list == NULL);
lib->mod = module_p;
- for (i=0; i < entry->num_of_funcs && ret==am_ok; i++) {
+ lib->finish = erts_alloc(ERTS_ALC_T_NIF,
+ sizeof_ErtsNifFinish(entry->num_of_funcs));
+ lib->finish->nstubs_hashed = 0;
+
+ erts_rwmtx_rwlock(&erts_nif_call_tab_lock);
+ for (i=0; i < entry->num_of_funcs; i++) {
ErtsCodeInfo** ci_pp;
+ ErtsCodeInfo* ci;
ErlNifFunc* f = &entry->funcs[i];
+ ErtsNifBeamStub* stub = &lib->finish->beam_stubv[i];
+
+ stub->code_info = NULL; /* end marker in case we fail */
if (!erts_atom_get(f->name, sys_strlen(f->name), &f_atom, ERTS_ATOM_ENC_LATIN1)
|| (ci_pp = get_func_pp(this_mi->code_hdr, f_atom, f->arity))==NULL) {
- ret = load_nif_error(BIF_P,bad_lib,"Function not found %T:%s/%u",
+ ret = load_nif_error(c_p,bad_lib,"Function not found %T:%s/%u",
mod_atom, f->name, f->arity);
+ break;
}
- else if (f->flags != 0 &&
- f->flags != ERL_NIF_DIRTY_JOB_IO_BOUND &&
- f->flags != ERL_NIF_DIRTY_JOB_CPU_BOUND) {
- ret = load_nif_error(BIF_P, bad_lib,
- "Illegal flags field value %d for NIF %T:%s/%u",
+ ci = *ci_pp;
+
+ if (f->flags != 0 &&
+ f->flags != ERL_NIF_DIRTY_JOB_IO_BOUND &&
+ f->flags != ERL_NIF_DIRTY_JOB_CPU_BOUND) {
+
+ ret = load_nif_error(c_p, bad_lib, "Illegal flags field value %d for NIF %T:%s/%u",
f->flags, mod_atom, f->name, f->arity);
+ break;
}
- else if (erts_codeinfo_to_code(ci_pp[1]) - erts_codeinfo_to_code(ci_pp[0])
- < BEAM_NIF_MIN_FUNC_SZ)
- {
- ret = load_nif_error(BIF_P,bad_lib,"No explicit call to load_nif"
- " in module (%T:%s/%u too small)",
- mod_atom, f->name, f->arity);
- }
- /*erts_fprintf(stderr, "Found NIF %T:%s/%u\r\n",
- mod_atom, f->name, f->arity);*/
+
+ ASSERT(erts_codeinfo_to_code(ci_pp[1]) - erts_codeinfo_to_code(ci_pp[0])
+ >= BEAM_NATIVE_MIN_FUNC_SZ);
+
+ stub->code_info = ci;
+ stub->mfa = ci->mfa;
+ if (hash_put(&erts_nif_call_tab, stub) != stub) {
+ ret = load_nif_error(c_p, bad_lib, "Duplicate NIF entry for %T:%s/%u",
+ mod_atom, f->name, f->arity);
+ break;
+ }
+ lib->finish->nstubs_hashed++;
+
+ stub->beam[0] = BeamOpCodeAddr(op_call_nif_WWW);
+ stub->beam[2] = (BeamInstr) lib;
+ if (f->flags) {
+ stub->beam[3] = (BeamInstr) f->fptr;
+ stub->beam[1] = (f->flags == ERL_NIF_DIRTY_JOB_IO_BOUND) ?
+ (BeamInstr) static_schedule_dirty_io_nif :
+ (BeamInstr) static_schedule_dirty_cpu_nif;
+ }
+ else
+ stub->beam[1] = (BeamInstr) f->fptr;
}
+ erts_rwmtx_rwunlock(&erts_nif_call_tab_lock);
+ ASSERT(lib->finish->nstubs_hashed == lib->entry.num_of_funcs);
}
if (ret != am_ok) {
@@ -4324,67 +4515,71 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
if (prev_mi->nif != NULL) { /**************** Upgrade ***************/
void* prev_old_data = prev_mi->nif->priv_data;
if (entry->upgrade == NULL) {
- ret = load_nif_error(BIF_P, upgrade, "Upgrade not supported by this NIF library.");
+ ret = load_nif_error(c_p, upgrade, "Upgrade not supported by this NIF library.");
goto error;
}
- erts_pre_nif(&env, BIF_P, lib, NULL);
- veto = entry->upgrade(&env, &lib->priv_data, &prev_mi->nif->priv_data, BIF_ARG_2);
+ /*
+ * Go single scheduler during upgrade callback.
+ * Todo: Fix better solution with suspending callers and waiting for
+ * all calls to return (including dirty).
+ * Note that erts_thr_progress_block() will not block dirty NIFs.
+ */
+ erts_proc_unlock(c_p, ERTS_PROC_LOCK_MAIN);
+ erts_thr_progress_block();
+ erts_proc_lock(c_p, ERTS_PROC_LOCK_MAIN);
+ erts_pre_nif(&env, c_p, lib, NULL);
+ veto = entry->upgrade(&env, &lib->priv_data, &prev_mi->nif->priv_data, args);
erts_post_nif(&env);
+ erts_thr_progress_unblock();
if (veto) {
prev_mi->nif->priv_data = prev_old_data;
- ret = load_nif_error(BIF_P, upgrade, "Library upgrade-call unsuccessful (%d).", veto);
+ ret = load_nif_error(c_p, upgrade, "Library upgrade-call unsuccessful (%d).", veto);
}
}
else if (entry->load != NULL) { /********* Initial load ***********/
- erts_pre_nif(&env, BIF_P, lib, NULL);
- veto = entry->load(&env, &lib->priv_data, BIF_ARG_2);
+ erts_pre_nif(&env, c_p, lib, NULL);
+ veto = entry->load(&env, &lib->priv_data, args);
erts_post_nif(&env);
if (veto) {
- ret = load_nif_error(BIF_P, "load", "Library load-call unsuccessful (%d).", veto);
+ ret = load_nif_error(c_p, "load", "Library load-call unsuccessful (%d).", veto);
}
}
- if (ret == am_ok) {
- commit_opened_resource_types(lib);
+ if (ret == am_ok) {
/*
- ** Everything ok, patch the beam code with op_call_nif
- */
-
+ * Everything ok, make NIF code callable.
+ */
this_mi->nif = lib;
- for (i=0; i < entry->num_of_funcs; i++)
- {
- ErlNifFunc* f = &entry->funcs[i];
- ErtsCodeInfo* ci;
- BeamInstr *code_ptr;
+ prepare_opened_rt(lib);
+ /*
+ * The call table lock will make sure all NIFs and callbacks in module
+ * are made accessible atomically.
+ */
+ erts_rwmtx_rwlock(&erts_nif_call_tab_lock);
+ commit_opened_rt();
+ patch_call_nif_early(entry, this_mi);
+ erts_rwmtx_rwunlock(&erts_nif_call_tab_lock);
- erts_atom_get(f->name, sys_strlen(f->name), &f_atom, ERTS_ATOM_ENC_LATIN1);
- ci = *get_func_pp(this_mi->code_hdr, f_atom, f->arity);
- code_ptr = erts_codeinfo_to_code(ci);
+ cleanup_opened_rt();
- if (ci->u.gen_bp == NULL) {
- code_ptr[0] = BeamOpCodeAddr(op_call_nif);
- }
- else { /* Function traced, patch the original instruction word */
- GenericBp* g = ci->u.gen_bp;
- ASSERT(BeamIsOpCode(code_ptr[0], op_i_generic_breakpoint));
- g->orig_instr = BeamOpCodeAddr(op_call_nif);
- }
- if (f->flags) {
- code_ptr[3] = (BeamInstr) f->fptr;
- code_ptr[1] = (f->flags == ERL_NIF_DIRTY_JOB_IO_BOUND) ?
- (BeamInstr) static_schedule_dirty_io_nif :
- (BeamInstr) static_schedule_dirty_cpu_nif;
- }
- else
- code_ptr[1] = (BeamInstr) f->fptr;
- code_ptr[2] = (BeamInstr) lib;
- }
+ /*
+ * Now we wait thread progress, to make sure no process is still
+ * executing the beam code of the NIFs, before we can patch in the
+ * final fast multi word call_nif_WWW instructions.
+ */
+ erts_refc_inc(&lib->refc, 2);
+ erts_schedule_thr_prgr_later_op(load_nif_1st_finisher, lib,
+ &lib->lop);
}
else {
error:
rollback_opened_resource_types();
ASSERT(ret != am_ok);
if (lib != NULL) {
+ if (lib->finish != NULL) {
+ erase_hashed_stubs(lib->finish);
+ erts_free(ERTS_ALC_T_NIF, lib->finish);
+ }
erts_free(ERTS_ALC_T_NIF, lib);
}
if (handle != NULL && !erts_is_static_nif(handle)) {
@@ -4393,25 +4588,209 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
erts_sys_ddll_free_error(&errdesc);
}
- erts_thr_progress_unblock();
- erts_release_code_write_permission();
erts_free(ERTS_ALC_T_TMP, lib_name);
BIF_RET(ret);
}
+/*
+ * Write 'call_nif_early' as the first beam instruction for all NIFs
+ * which will make them callable.
+ *
+ * The 'call_nif_early' is a one word beam instruction which uses a lock
+ * protected hash lookup to get its "arguments". This guarantees an atomically
+ * safe publication of all NIFs in the module.
+ */
+static void patch_call_nif_early(ErlNifEntry* entry,
+ struct erl_module_instance* this_mi)
+{
+ const BeamInstr call_nif_early = BeamOpCodeAddr(op_call_nif_early);
+ int i;
+
+ ERTS_LC_ASSERT(erts_has_code_write_permission());
+ ERTS_LC_ASSERT(erts_lc_rwmtx_is_rwlocked(&erts_nif_call_tab_lock));
+
+ for (i=0; i < entry->num_of_funcs; i++)
+ {
+ ErlNifFunc* f = &entry->funcs[i];
+ BeamInstr volatile *code_ptr;
+ ErtsCodeInfo* ci;
+ Eterm f_atom;
+
+ erts_atom_get(f->name, sys_strlen(f->name), &f_atom, ERTS_ATOM_ENC_LATIN1);
+ ci = *get_func_pp(this_mi->code_hdr, f_atom, f->arity);
+ code_ptr = erts_codeinfo_to_code(ci);
+
+ if (ci->u.gen_bp) {
+ /*
+ * Function traced, patch the original instruction word
+ * Code write permission protects against racing breakpoint writes.
+ */
+ GenericBp* g = ci->u.gen_bp;
+ g->orig_instr = BeamSetCodeAddr(g->orig_instr, call_nif_early);
+ if (BeamIsOpCode(code_ptr[0], op_i_generic_breakpoint))
+ continue;
+ }
+ else
+ ASSERT(!BeamIsOpCode(code_ptr[0], op_i_generic_breakpoint));
+ code_ptr[0] = BeamSetCodeAddr(code_ptr[0], call_nif_early);
+ }
+}
+
+BeamInstr* erts_call_nif_early(Process* c_p, ErtsCodeInfo* ci)
+{
+ ErtsNifBeamStub* bs;
+ ErtsNifBeamStub tmpl;
+ tmpl.code_info = ci;
+
+ erts_rwmtx_rlock(&erts_nif_call_tab_lock);
+ bs = (ErtsNifBeamStub*) hash_get(&erts_nif_call_tab, &tmpl);
+ erts_rwmtx_runlock(&erts_nif_call_tab_lock);
+
+ ASSERT(bs);
+ return &bs->beam[0];
+}
+
+static void load_nif_1st_finisher(void* vlib)
+{
+ struct erl_module_nif* lib = (struct erl_module_nif*) vlib;
+ ErtsNifFinish* fin;
+ int i;
+
+ erts_mtx_lock(&lib->load_mtx);
+ fin = lib->finish;
+ if (fin) {
+ for (i=0; i < lib->entry.num_of_funcs; i++) {
+ BeamInstr* code_ptr;
+ code_ptr = erts_codeinfo_to_code(fin->beam_stubv[i].code_info);
+
+ code_ptr[1] = fin->beam_stubv[i].beam[1]; /* called function */
+ code_ptr[2] = fin->beam_stubv[i].beam[2]; /* erl_module_nif */
+ if (lib->entry.funcs[i].flags)
+ code_ptr[3] = fin->beam_stubv[i].beam[3]; /* real NIF */
+ }
+ }
+ erts_mtx_unlock(&lib->load_mtx);
+
+ if (fin) {
+ /*
+ * A second thread progress to get a memory barrier between the
+ * arguments of call_nif_WWW (written above) and the instruction word
+ * itself.
+ */
+ erts_schedule_thr_prgr_later_op(load_nif_2nd_finisher, lib,
+ &lib->lop);
+ }
+ else { /* Unloaded */
+ deref_nifmod(lib);
+ }
+}
+
+static void load_nif_2nd_finisher(void* vlib)
+{
+ struct erl_module_nif* lib = (struct erl_module_nif*) vlib;
+ ErtsNifFinish* fin;
+ int i;
+
+ /*
+ * We seize code write permission only to avoid any trace breakpoints
+ * to change while we patch the op_call_nif_WWW instruction.
+ */
+ if (!erts_try_seize_code_write_permission_aux(load_nif_2nd_finisher, vlib)) {
+ return;
+ }
+
+ erts_mtx_lock(&lib->load_mtx);
+ fin = lib->finish;
+ if (fin) {
+ for (i=0; i < lib->entry.num_of_funcs; i++) {
+ ErtsCodeInfo *ci = fin->beam_stubv[i].code_info;
+ BeamInstr volatile *code_ptr = erts_codeinfo_to_code(ci);
+
+ if (ci->u.gen_bp) {
+ /*
+ * Function traced, patch the original instruction word
+ */
+ GenericBp* g = ci->u.gen_bp;
+ ASSERT(BeamIsOpCode(g->orig_instr, op_call_nif_early));
+ g->orig_instr = BeamOpCodeAddr(op_call_nif_WWW);
+ if (BeamIsOpCode(code_ptr[0], op_i_generic_breakpoint))
+ continue;
+ }
+ ASSERT(BeamIsOpCode(code_ptr[0], op_call_nif_early));
+ code_ptr[0] = BeamOpCodeAddr(op_call_nif_WWW);
+ }
+ }
+ erts_mtx_unlock(&lib->load_mtx);
+
+ erts_release_code_write_permission();
+
+ if (fin) {
+ UWord bytes = sizeof_ErtsNifFinish(lib->entry.num_of_funcs);
+ /*
+ * A third and final thread progress, to make sure no one is executing
+ * the call_nif_early instructions anymore, before we can deallocate
+ * the beam stubs.
+ */
+ erts_schedule_thr_prgr_later_cleanup_op(load_nif_3rd_finisher, lib,
+ &lib->lop,
+ bytes);
+ }
+ else { /* Unloaded */
+ deref_nifmod(lib);
+ }
+}
+
+static void load_nif_3rd_finisher(void* vlib)
+{
+ struct erl_module_nif* lib = (struct erl_module_nif*) vlib;
+
+ release_beam_stubs(lib);
+ deref_nifmod(lib);
+}
+
+static void release_beam_stubs(struct erl_module_nif* lib)
+{
+ ErtsNifFinish* fin;
+
+ erts_mtx_lock(&lib->load_mtx);
+ fin = lib->finish;
+ lib->finish = NULL;
+ erts_mtx_unlock(&lib->load_mtx);
+
+ if (fin) {
+ erase_hashed_stubs(fin);
+ erts_free(ERTS_ALC_T_NIF, fin);
+ }
+}
+
+static void erase_hashed_stubs(ErtsNifFinish* fin)
+{
+ int i;
+
+ erts_rwmtx_rwlock(&erts_nif_call_tab_lock);
+ for (i=0; i < fin->nstubs_hashed; i++) {
+ void* erased = hash_erase(&erts_nif_call_tab, &fin->beam_stubv[i]);
+ ASSERT(erased); (void) erased;
+ }
+ erts_rwmtx_rwunlock(&erts_nif_call_tab_lock);
+}
+
void
erts_unload_nif(struct erl_module_nif* lib)
{
ErlNifResourceType* rt;
ErlNifResourceType* next;
- ASSERT(erts_thr_progress_is_blocking());
+
ASSERT(lib != NULL);
ASSERT(lib->mod != NULL);
+ ERTS_LC_ASSERT(erts_has_code_write_permission());
erts_tracer_nif_clear();
+ release_beam_stubs(lib);
+
for (rt = resource_type_list.next;
rt != &resource_type_list;
rt = next) {
@@ -4423,25 +4802,19 @@ erts_unload_nif(struct erl_module_nif* lib)
rt->next = NULL;
rt->prev = NULL;
if (erts_refc_dectest(&rt->refc, 0) == 0) {
- if (rt_have_callbacks(rt)) {
- erts_refc_dec(&lib->rt_dtor_cnt, 0);
- }
- erts_refc_dec(&lib->rt_cnt, 0);
+ if (rt_have_callbacks(&rt->fn_real))
+ erts_refc_dec(&lib->dynlib_refc, 1);
+ erts_refc_dec(&lib->refc, 1);
erts_free(ERTS_ALC_T_NIF, rt);
}
}
}
- if (erts_refc_read(&lib->rt_dtor_cnt, 0) == 0) {
- close_lib(lib);
- if (erts_refc_read(&lib->rt_cnt, 0) == 0) {
- erts_free(ERTS_ALC_T_NIF, lib);
- return;
- }
- }
- else {
- ASSERT(erts_refc_read(&lib->rt_cnt, 1) > 0);
- }
- lib->mod = NULL; /* orphan lib */
+ lib->mod = NULL; /* purged Elang module */
+
+ if (erts_refc_dectest(&lib->dynlib_refc, 0) == 0)
+ close_dynlib(lib);
+
+ deref_nifmod(lib);
}
void erl_nif_init()
@@ -4451,11 +4824,13 @@ void erl_nif_init()
resource_type_list.next = &resource_type_list;
resource_type_list.prev = &resource_type_list;
- resource_type_list.dtor = NULL;
+ resource_type_list.fn.dtor = NULL;
+ resource_type_list.fn_real.dtor = NULL;
resource_type_list.owner = NULL;
resource_type_list.module = THE_NON_VALUE;
resource_type_list.name = THE_NON_VALUE;
+ nif_call_table_init();
}
int erts_nif_get_funcs(struct erl_module_nif* mod,
diff --git a/erts/emulator/beam/erl_node_tables.c b/erts/emulator/beam/erl_node_tables.c
index b8c36f4ecd..75c6b6abce 100644
--- a/erts/emulator/beam/erl_node_tables.c
+++ b/erts/emulator/beam/erl_node_tables.c
@@ -172,13 +172,15 @@ dist_table_alloc(void *dep_tmpl)
erts_rwmtx_init_opt(&dep->rwmtx, &rwmtx_opt, "dist_entry", sysname,
ERTS_LOCK_FLAGS_CATEGORY_DISTRIBUTION);
dep->sysname = sysname;
+ dep->creation = 0; /* undefined */
dep->cid = NIL;
erts_atomic_init_nob(&dep->input_handler, (erts_aint_t) NIL);
dep->connection_id = 0;
dep->state = ERTS_DE_STATE_IDLE;
- dep->flags = 0;
+ dep->pending_nodedown = 0;
+ dep->suspended_nodeup = NULL;
+ dep->dflags = 0;
dep->opts = 0;
- dep->version = 0;
dep->mld = NULL;
@@ -201,7 +203,6 @@ dist_table_alloc(void *dep_tmpl)
erts_port_task_handle_init(&dep->dist_cmd);
dep->send = NULL;
dep->cache = NULL;
- dep->transcode_ctx = NULL;
dep->sequences = NULL;
/* Link in */
@@ -634,7 +635,7 @@ erts_set_dist_entry_not_connected(DistEntry *dep)
else {
ASSERT(dep->state != ERTS_DE_STATE_IDLE);
ASSERT(is_internal_port(dep->cid) || is_internal_pid(dep->cid));
- if (dep->flags & DFLAG_PUBLISHED) {
+ if (dep->dflags & DFLAG_PUBLISHED) {
ASSERT(erts_no_of_visible_dist_entries > 0);
erts_no_of_visible_dist_entries--;
head = &erts_visible_dist_entries;
@@ -658,7 +659,7 @@ erts_set_dist_entry_not_connected(DistEntry *dep)
dep->next->prev = dep->prev;
dep->state = ERTS_DE_STATE_IDLE;
- dep->flags = 0;
+ dep->dflags = 0;
dep->opts = 0;
dep->prev = NULL;
dep->cid = NIL;
@@ -700,7 +701,7 @@ erts_set_dist_entry_pending(DistEntry *dep)
erts_no_of_not_connected_dist_entries--;
dep->state = ERTS_DE_STATE_PENDING;
- dep->flags = (DFLAG_DIST_MANDATORY | DFLAG_DIST_HOPEFULLY | DFLAG_NO_MAGIC);
+ dep->dflags = (DFLAG_DIST_MANDATORY | DFLAG_DIST_HOPEFULLY | DFLAG_PENDING_CONNECT);
dep->connection_id = (dep->connection_id + 1) & ERTS_DIST_CON_ID_MASK;
ASSERT(!dep->mld);
@@ -719,7 +720,7 @@ erts_set_dist_entry_pending(DistEntry *dep)
}
void
-erts_set_dist_entry_connected(DistEntry *dep, Eterm cid, Uint flags)
+erts_set_dist_entry_connected(DistEntry *dep, Eterm cid, Uint64 flags)
{
erts_aint32_t set_qflgs;
@@ -731,6 +732,7 @@ erts_set_dist_entry_connected(DistEntry *dep, Eterm cid, Uint flags)
ASSERT(dep != erts_this_dist_entry);
ASSERT(is_nil(dep->cid));
ASSERT(dep->state == ERTS_DE_STATE_PENDING);
+ ASSERT(!dep->pending_nodedown);
ASSERT(is_internal_port(cid) || is_internal_pid(cid));
if(dep->prev) {
@@ -749,7 +751,7 @@ erts_set_dist_entry_connected(DistEntry *dep, Eterm cid, Uint flags)
erts_no_of_pending_dist_entries--;
dep->state = ERTS_DE_STATE_CONNECTED;
- dep->flags = flags & ~DFLAG_NO_MAGIC;
+ dep->dflags = flags & ~DFLAG_PENDING_CONNECT;
dep->cid = cid;
erts_atomic_set_nob(&dep->input_handler,
(erts_aint_t) cid);
@@ -976,7 +978,7 @@ static void print_node(void *venp, void *vpndp)
if(pndp->sysname == NIL) {
erts_print(pndp->to, pndp->to_arg, "Name: %T ", enp->sysname);
}
- erts_print(pndp->to, pndp->to_arg, " %d", enp->creation);
+ erts_print(pndp->to, pndp->to_arg, " %u", enp->creation);
#ifdef DEBUG
erts_print(pndp->to, pndp->to_arg, " (refc=%ld)",
erts_refc_read(&enp->refc, 0));
@@ -1019,7 +1021,7 @@ void erts_print_node_info(fmtfn_t to,
/* ----------------------------------------------------------------------- */
void
-erts_set_this_node(Eterm sysname, Uint creation)
+erts_set_this_node(Eterm sysname, Uint32 creation)
{
ERTS_LC_ASSERT(erts_thr_progress_is_blocking());
ASSERT(2 <= de_refc_read(erts_this_dist_entry, 2));
@@ -1639,18 +1641,21 @@ clear_visited_dist_monitors(DistEntry *dep)
static void insert_link_data(ErtsLink *lnk, int type, Eterm id)
{
- ErtsLinkData *ldp = erts_link_to_data(lnk);
- if ((ldp->a.flags & (ERTS_ML_FLG_DBG_VISITED
- | ERTS_ML_FLG_EXTENDED)) == ERTS_ML_FLG_EXTENDED) {
- ErtsLinkDataExtended *ldep = (ErtsLinkDataExtended *) ldp;
- if (ldep->ohhp) {
+ if ((lnk->flags & (ERTS_ML_FLG_DBG_VISITED
+ | ERTS_ML_FLG_EXTENDED)) != ERTS_ML_FLG_EXTENDED) {
+ lnk->flags |= ERTS_ML_FLG_DBG_VISITED;
+ }
+ else {
+ ErtsELink *elnk = erts_link_to_elink(lnk);
+ if (elnk->ohhp) {
ErlOffHeap oh;
ERTS_INIT_OFF_HEAP(&oh);
- oh.first = ldep->ohhp;
+ oh.first = elnk->ohhp;
insert_offheap(&oh, type, id);
}
+ elnk->ld.proc.flags |= ERTS_ML_FLG_DBG_VISITED;
+ elnk->ld.dist.flags |= ERTS_ML_FLG_DBG_VISITED;
}
- ldp->a.flags |= ERTS_ML_FLG_DBG_VISITED;
}
static int insert_link(ErtsLink *lnk, void *idp, Sint reds)
@@ -1662,8 +1667,13 @@ static int insert_link(ErtsLink *lnk, void *idp, Sint reds)
static int clear_visited_link(ErtsLink *lnk, void *p, Sint reds)
{
- ErtsLinkData *ldp = erts_link_to_data(lnk);
- ldp->a.flags &= ~ERTS_ML_FLG_DBG_VISITED;
+ if (!(lnk->flags & ERTS_ML_FLG_EXTENDED))
+ lnk->flags &= ~ERTS_ML_FLG_DBG_VISITED;
+ else {
+ ErtsELink *elnk = erts_link_to_elink(lnk);
+ elnk->ld.proc.flags &= ~ERTS_ML_FLG_DBG_VISITED;
+ elnk->ld.dist.flags &= ~ERTS_ML_FLG_DBG_VISITED;
+ }
return 1;
}
diff --git a/erts/emulator/beam/erl_node_tables.h b/erts/emulator/beam/erl_node_tables.h
index 55250b24cb..937bfb1d9e 100644
--- a/erts/emulator/beam/erl_node_tables.h
+++ b/erts/emulator/beam/erl_node_tables.h
@@ -23,6 +23,7 @@
typedef struct dist_entry_ DistEntry;
typedef struct ErtsDistOutputBuf_ ErtsDistOutputBuf;
+typedef struct ErtsDistOutputBufsContainer_ ErtsDistOutputBufsContainer;
void erts_ref_dist_entry(DistEntry *dep);
void erts_deref_dist_entry(DistEntry *dep);
@@ -95,26 +96,21 @@ enum dist_entry_state {
struct ErtsDistOutputBuf_ {
#ifdef DEBUG
Uint dbg_pattern;
- byte *ext_startp;
- byte *alloc_endp;
#endif
ErtsDistOutputBuf *next;
Binary *bin;
- /* Pointers to the distribution header,
- if NULL the distr header is in the extp */
- byte *hdrp;
- byte *hdr_endp;
- /* Pointers to the ctl + payload */
- byte *extp;
- byte *ext_endp;
- /* Start of payload and hopefull_flags, used by transcode */
- Uint hopefull_flags;
- byte *msg_start;
- /* start of the ext buffer, this is not always the same as extp
- as the atom cache handling can use less then the allotted buffer.
- This value is needed to calculate the size of this output buffer.*/
- byte *ext_start;
+ /*
+ * iov[0] reserved for driver
+ * iov[1] reserved for distribution header
+ * iov[2 ... vsize-1] data
+ */
+ ErlIOVec *eiov;
+};
+struct ErtsDistOutputBufsContainer_ {
+ Sint fragments;
+ byte *extp;
+ ErtsDistOutputBuf obuf[1]; /* longer if fragmented... */
};
typedef struct {
@@ -147,10 +143,11 @@ struct dist_entry_ {
NIL == free */
Uint32 connection_id; /* Connection id incremented on connect */
enum dist_entry_state state;
- Uint32 flags; /* Distribution flags, like hidden,
+ int pending_nodedown;
+ Process* suspended_nodeup;
+ Uint64 dflags; /* Distribution flags, like hidden,
atom cache etc. */
Uint32 opts;
- unsigned long version; /* Protocol version */
ErtsMonLnkDist *mld; /* Monitors and links */
@@ -173,8 +170,6 @@ struct dist_entry_ {
ErtsThrPrgrLaterOp later_op;
- struct transcode_context* transcode_ctx;
-
struct dist_sequences *sequences; /* Ongoing distribution sequences */
};
@@ -261,10 +256,10 @@ Uint erts_dist_table_size(void);
void erts_dist_table_info(fmtfn_t, void *);
void erts_set_dist_entry_not_connected(DistEntry *);
void erts_set_dist_entry_pending(DistEntry *);
-void erts_set_dist_entry_connected(DistEntry *, Eterm, Uint);
+void erts_set_dist_entry_connected(DistEntry *, Eterm, Uint64);
ErlNode *erts_find_or_insert_node(Eterm, Uint32, Eterm);
void erts_schedule_delete_node(ErlNode *);
-void erts_set_this_node(Eterm, Uint);
+void erts_set_this_node(Eterm, Uint32);
Uint erts_node_table_size(void);
void erts_init_node_tables(int);
void erts_node_table_info(fmtfn_t, void *);
diff --git a/erts/emulator/beam/erl_port.h b/erts/emulator/beam/erl_port.h
index 9983a22f56..917a5714af 100644
--- a/erts/emulator/beam/erl_port.h
+++ b/erts/emulator/beam/erl_port.h
@@ -368,6 +368,7 @@ Eterm erts_request_io_bytes(Process *c_p);
#define ERTS_PORT_REDS_EXIT (CONTEXT_REDS/100)
#define ERTS_PORT_REDS_CONNECT (CONTEXT_REDS/200)
#define ERTS_PORT_REDS_UNLINK (CONTEXT_REDS/200)
+#define ERTS_PORT_REDS_UNLINK_ACK (CONTEXT_REDS/200)
#define ERTS_PORT_REDS_LINK (CONTEXT_REDS/200)
#define ERTS_PORT_REDS_MONITOR (CONTEXT_REDS/200)
#define ERTS_PORT_REDS_DEMONITOR (CONTEXT_REDS/200)
@@ -852,7 +853,8 @@ enum {
ERTS_P2P_SIG_TYPE_LINK = 8,
ERTS_P2P_SIG_TYPE_UNLINK = 9,
ERTS_P2P_SIG_TYPE_MONITOR = 10,
- ERTS_P2P_SIG_TYPE_DEMONITOR = 11
+ ERTS_P2P_SIG_TYPE_DEMONITOR = 11,
+ ERTS_P2P_SIG_TYPE_UNLINK_ACK = 12
};
#define ERTS_P2P_SIG_TYPE_BITS 4
@@ -914,8 +916,12 @@ struct ErtsProc2PortSigData_ {
} link;
struct {
Eterm port_id;
- ErtsLink *lnk;
+ ErtsSigUnlinkOp *sulnk;
} unlink;
+ struct {
+ Eterm port_id;
+ ErtsSigUnlinkOp *sulnk;
+ } unlink_ack;
struct {
Eterm port_id;
ErtsMonitor *mon;
@@ -1004,7 +1010,8 @@ ErtsPortOpResult erts_port_output(Process *, int, Port *, Eterm, Eterm, Eterm *)
ErtsPortOpResult erts_port_exit(Process *, int, Port *, Eterm, Eterm, Eterm *);
ErtsPortOpResult erts_port_connect(Process *, int, Port *, Eterm, Eterm, Eterm *);
ErtsPortOpResult erts_port_link(Process *, Port *, ErtsLink *, Eterm *);
-ErtsPortOpResult erts_port_unlink(Process *, Port *, ErtsLink *, Eterm *);
+ErtsPortOpResult erts_port_unlink(Process *, Port *, ErtsSigUnlinkOp *, Eterm *);
+ErtsPortOpResult erts_port_unlink_ack(Process *, Port *, ErtsSigUnlinkOp *);
ErtsPortOpResult erts_port_control(Process *, Port *, unsigned int, Eterm, Eterm *);
ErtsPortOpResult erts_port_call(Process *, Port *, unsigned int, Eterm, Eterm *);
ErtsPortOpResult erts_port_info(Process *, Port *, Eterm, Eterm *);
diff --git a/erts/emulator/beam/erl_port_task.c b/erts/emulator/beam/erl_port_task.c
index 0c55e9bae2..3b56e5b574 100644
--- a/erts/emulator/beam/erl_port_task.c
+++ b/erts/emulator/beam/erl_port_task.c
@@ -1873,7 +1873,8 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp)
if (active) {
ErtsRunQueue *xrunq;
- ASSERT(!(erts_atomic32_read_nob(&pp->state) & ERTS_PORT_SFLGS_DEAD));
+ ASSERT(!(erts_atomic32_read_nob(&pp->state)
+ & ERTS_PORT_SFLG_INITIALIZING));
xrunq = erts_check_emigration_need(runq, ERTS_PORT_PRIO_LEVEL);
ERTS_LC_ASSERT(runq != xrunq);
diff --git a/erts/emulator/beam/erl_proc_sig_queue.c b/erts/emulator/beam/erl_proc_sig_queue.c
index 5a062a0302..7d4820f1db 100644
--- a/erts/emulator/beam/erl_proc_sig_queue.c
+++ b/erts/emulator/beam/erl_proc_sig_queue.c
@@ -51,7 +51,7 @@
* Note that not all signal are handled using this functionality!
*/
-#define ERTS_SIG_Q_OP_MAX 13
+#define ERTS_SIG_Q_OP_MAX 15
#define ERTS_SIG_Q_OP_EXIT 0 /* Exit signal due to bif call */
#define ERTS_SIG_Q_OP_EXIT_LINKED 1 /* Exit signal due to link break*/
@@ -66,7 +66,9 @@
#define ERTS_SIG_Q_OP_IS_ALIVE 10
#define ERTS_SIG_Q_OP_PROCESS_INFO 11
#define ERTS_SIG_Q_OP_SYNC_SUSPEND 12
-#define ERTS_SIG_Q_OP_RPC ERTS_SIG_Q_OP_MAX
+#define ERTS_SIG_Q_OP_RPC 13
+#define ERTS_SIG_Q_OP_DIST_SPAWN_REPLY 14
+#define ERTS_SIG_Q_OP_UNLINK_ACK ERTS_SIG_Q_OP_MAX
#define ERTS_SIG_Q_TYPE_MAX (ERTS_MON_LNK_TYPE_MAX + 5)
@@ -86,6 +88,10 @@
#define ERTS_SIG_IS_GEN_EXIT_EXTERNAL(sig) \
(ASSERT(ERTS_SIG_IS_GEN_EXIT(sig)),is_non_value(get_exit_signal_data(sig)->reason))
+
+#define ERTS_SIG_LNK_X_FLAG_NORMAL_KILLS (((Uint32) 1) << 0)
+#define ERTS_SIG_LNK_X_FLAG_CONNECTION_LOST (((Uint32) 1) << 1)
+
Process *ERTS_WRITE_UNLIKELY(erts_dirty_process_signal_handler);
Process *ERTS_WRITE_UNLIKELY(erts_dirty_process_signal_handler_high);
Process *ERTS_WRITE_UNLIKELY(erts_dirty_process_signal_handler_max);
@@ -120,7 +126,15 @@ typedef struct {
Eterm reason;
union {
Eterm ref;
- int normal_kills;
+ struct {
+ Uint32 flags;
+ /*
+ * connection_id is only set when the
+ * ERTS_SIG_LNK_X_FLAG_CONNECTION_LOST
+ * flag has been set...
+ */
+ Uint32 connection_id;
+ } link;
} u;
} ErtsExitSignalData;
@@ -131,10 +145,21 @@ typedef struct {
typedef struct {
ErtsSignalCommon common;
+ Eterm nodename;
+ Uint32 connection_id;
Eterm local; /* internal pid (immediate) */
Eterm remote; /* external pid (heap for it follow) */
+ Uint64 id;
Eterm heap[EXTERNAL_THING_HEAD_SIZE + 1];
-} ErtsSigDistLinkOp;
+} ErtsSigDistUnlinkOp;
+
+typedef struct {
+ Eterm message;
+ Eterm ref;
+ Eterm result;
+ ErtsLink *link;
+ Eterm *patch_point;
+} ErtsDistSpawnReplySigData;
typedef struct {
ErtsSignalCommon common;
@@ -215,6 +240,8 @@ static int handle_trace_change_state(Process *c_p,
ErtsMessage ***next_nm_sig);
static void getting_unlinked(Process *c_p, Eterm unlinker);
static void getting_linked(Process *c_p, Eterm linker);
+static void linking(Process *c_p, Eterm to);
+
static void group_leader_reply(Process *c_p, Eterm to,
Eterm ref, int success);
static int stretch_limit(Process *c_p, ErtsSigRecvTracing *tp,
@@ -270,39 +297,43 @@ destroy_dist_proc_demonitor(ErtsSigDistProcDemonitor *dmon)
erts_free(ERTS_ALC_T_DIST_DEMONITOR, dmon);
}
-static ERTS_INLINE ErtsSigDistLinkOp *
-make_sig_dist_link_op(int op, Eterm local, Eterm remote)
+static ERTS_INLINE ErtsSigDistUnlinkOp *
+make_sig_dist_unlink_op(int op, Eterm nodename, Uint32 conn_id,
+ Eterm local, Eterm remote, Uint64 id)
{
Eterm *hp;
ErlOffHeap oh = {0};
- ErtsSigDistLinkOp *sdlnk = erts_alloc(ERTS_ALC_T_SIG_DATA,
- sizeof(ErtsSigDistLinkOp));
+ ErtsSigDistUnlinkOp *sdulnk = erts_alloc(ERTS_ALC_T_SIG_DATA,
+ sizeof(ErtsSigDistUnlinkOp));
ASSERT(is_internal_pid(local));
ASSERT(is_external_pid(remote));
- hp = &sdlnk->heap[0];
+ hp = &sdulnk->heap[0];
- sdlnk->common.tag = ERTS_PROC_SIG_MAKE_TAG(op,
- ERTS_SIG_Q_TYPE_DIST_LINK,
- 0);
- sdlnk->local = local;
- sdlnk->remote = STORE_NC(&hp, &oh, remote);
-
- ASSERT(&sdlnk->heap[0] < hp);
- ASSERT(hp <= &sdlnk->heap[0] + sizeof(sdlnk->heap)/sizeof(sdlnk->heap[0]));
- ASSERT(boxed_val(sdlnk->remote) == &sdlnk->heap[0]);
+ sdulnk->common.tag = ERTS_PROC_SIG_MAKE_TAG(op,
+ ERTS_SIG_Q_TYPE_DIST_LINK,
+ 0);
+ sdulnk->nodename = nodename;
+ sdulnk->connection_id = conn_id;
+ sdulnk->local = local;
+ sdulnk->remote = STORE_NC(&hp, &oh, remote);
+ sdulnk->id = id;
+
+ ASSERT(&sdulnk->heap[0] < hp);
+ ASSERT(hp <= &sdulnk->heap[0] + sizeof(sdulnk->heap)/sizeof(sdulnk->heap[0]));
+ ASSERT(boxed_val(sdulnk->remote) == &sdulnk->heap[0]);
- return sdlnk;
+ return sdulnk;
}
static ERTS_INLINE void
-destroy_sig_dist_link_op(ErtsSigDistLinkOp *sdlnk)
+destroy_sig_dist_unlink_op(ErtsSigDistUnlinkOp *sdulnk)
{
- ASSERT(is_external_pid(sdlnk->remote));
- ASSERT(boxed_val(sdlnk->remote) == &sdlnk->heap[0]);
- erts_deref_node_entry(((ExternalThing *) &sdlnk->heap[0])->node,
- make_boxed(&sdlnk->heap[0]));
- erts_free(ERTS_ALC_T_SIG_DATA, sdlnk);
+ ASSERT(is_external_pid(sdulnk->remote));
+ ASSERT(boxed_val(sdulnk->remote) == &sdulnk->heap[0]);
+ erts_deref_node_entry(((ExternalThing *) &sdulnk->heap[0])->node,
+ make_boxed(&sdulnk->heap[0]));
+ erts_free(ERTS_ALC_T_SIG_DATA, sdulnk);
}
static ERTS_INLINE ErtsExitSignalData *
@@ -322,6 +353,17 @@ get_exit_signal_data(ErtsMessage *xsig)
+ xsig->hfrag.used_size);
}
+static ERTS_INLINE ErtsDistSpawnReplySigData *
+get_dist_spawn_reply_data(ErtsMessage *sig)
+{
+ ASSERT(ERTS_SIG_IS_NON_MSG(sig));
+ ASSERT(sig->hfrag.alloc_size > sig->hfrag.used_size);
+ ASSERT((sig->hfrag.alloc_size - sig->hfrag.used_size)*sizeof(UWord)
+ >= sizeof(ErtsDistSpawnReplySigData));
+ return (ErtsDistSpawnReplySigData *) (char *) (&sig->hfrag.mem[0]
+ + sig->hfrag.used_size);
+}
+
static ERTS_INLINE void
destroy_trace_info(ErtsSigTraceInfo *ti)
{
@@ -386,23 +428,13 @@ sig_enqueue_trace(Process *c_p, ErtsMessage **sigp, int op,
ErtsMessage* sig = *sigp;
Uint16 type = ERTS_PROC_SIG_TYPE(((ErtsSignal *) sig)->common.tag);
Eterm reason, from;
+ ErtsExitSignalData *xsigd;
- if (type == ERTS_SIG_Q_TYPE_GEN_EXIT) {
- ErtsExitSignalData *xsigd = get_exit_signal_data(sig);
- reason = xsigd->reason;
- from = xsigd->from;
- }
- else {
- ErtsLink *lnk = (ErtsLink *) sig, *olnk;
+ ASSERT(type == ERTS_SIG_Q_TYPE_GEN_EXIT);
- ASSERT(type == ERTS_LNK_TYPE_PROC
- || type == ERTS_LNK_TYPE_PORT
- || type == ERTS_LNK_TYPE_DIST_PROC);
-
- olnk = erts_link_to_other(lnk, NULL);
- reason = lnk->other.item;
- from = olnk->other.item;
- }
+ xsigd = get_exit_signal_data(sig);
+ reason = xsigd->reason;
+ from = xsigd->from;
if (is_pid(from)) {
@@ -946,6 +978,12 @@ erts_proc_sig_privqs_len(Process *c_p)
return proc_sig_privqs_len(c_p, 0);
}
+void
+erts_proc_sig_destroy_unlink_op(ErtsSigUnlinkOp *sulnk)
+{
+ erts_free(ERTS_ALC_T_SIG_DATA, sulnk);
+}
+
ErtsDistExternal *
erts_proc_sig_get_external(ErtsMessage *msgp)
{
@@ -974,7 +1012,8 @@ send_gen_exit_signal(Process *c_p, Eterm from_tag,
Eterm from, Eterm to,
Sint16 op, Eterm reason, ErtsDistExternal *dist_ext,
ErlHeapFragment *dist_ext_hfrag,
- Eterm ref, Eterm token, int normal_kills)
+ Eterm ref, Eterm token, int normal_kills,
+ Uint32 conn_lost, Uint32 conn_id)
{
ErtsExitSignalData *xsigd;
Eterm *hp, *start_hp, s_reason, s_ref, s_message, s_token, s_from;
@@ -997,7 +1036,7 @@ send_gen_exit_signal(Process *c_p, Eterm from_tag,
seq_trace = c_p && have_seqtrace(token);
if (seq_trace)
- seq_trace_update_send(c_p);
+ seq_trace_update_serial(c_p);
#ifdef USE_VM_PROBES
utag_sz = 0;
@@ -1112,12 +1151,18 @@ send_gen_exit_signal(Process *c_p, Eterm from_tag,
xsigd->reason = s_reason;
hfrag->next = dist_ext_hfrag;
- if (is_nil(s_ref))
- xsigd->u.normal_kills = normal_kills;
- else {
+ if (is_not_nil(s_ref)) {
ASSERT(is_ref(s_ref));
xsigd->u.ref = s_ref;
}
+ else {
+ xsigd->u.link.flags = 0;
+ if (normal_kills)
+ xsigd->u.link.flags |= ERTS_SIG_LNK_X_FLAG_NORMAL_KILLS;
+ if (conn_lost)
+ xsigd->u.link.flags |= ERTS_SIG_LNK_X_FLAG_CONNECTION_LOST;
+ xsigd->u.link.connection_id = conn_id;
+ }
hp += sizeof(ErtsExitSignalData)/sizeof(Eterm);
@@ -1260,7 +1305,7 @@ erts_proc_sig_send_exit(Process *c_p, Eterm from, Eterm to,
from_tag = dep->sysname;
}
send_gen_exit_signal(c_p, from_tag, from, to, ERTS_SIG_Q_OP_EXIT,
- reason, NULL, NULL, NIL, token, normal_kills);
+ reason, NULL, NULL, NIL, token, normal_kills, 0, 0);
}
void
@@ -1271,7 +1316,7 @@ erts_proc_sig_send_dist_exit(DistEntry *dep,
Eterm reason, Eterm token)
{
send_gen_exit_signal(NULL, dep->sysname, from, to, ERTS_SIG_Q_OP_EXIT,
- reason, dist_ext, hfrag, NIL, token, 0);
+ reason, dist_ext, hfrag, NIL, token, 0, 0, 0);
}
@@ -1279,26 +1324,36 @@ void
erts_proc_sig_send_link_exit(Process *c_p, Eterm from, ErtsLink *lnk,
Eterm reason, Eterm token)
{
- Eterm to;
+ Eterm to, from_tag, from_item;
+ int conn_lost;
+ Uint32 conn_id;
ASSERT(!c_p || c_p->common.id == from);
ASSERT(lnk);
to = lnk->other.item;
- if (is_not_immed(reason) || is_not_nil(token)) {
+ if (is_value(from)) {
ASSERT(is_internal_pid(from) || is_internal_port(from));
- send_gen_exit_signal(c_p, from, from, to, ERTS_SIG_Q_OP_EXIT_LINKED,
- reason, NULL, NULL, NIL, token, 0);
+ from_tag = from_item = from;
+ conn_id = 0;
+ conn_lost = 0;
}
else {
- /* Pass signal using old link structure... */
- ErtsSignal *sig = (ErtsSignal *) lnk;
- lnk->other.item = reason; /* pass reason via this other.item */
- sig->common.tag = ERTS_PROC_SIG_MAKE_TAG(ERTS_SIG_Q_OP_EXIT_LINKED,
- lnk->type, 0);
- if (proc_queue_signal(c_p, to, sig, ERTS_SIG_Q_OP_EXIT_LINKED))
- return; /* receiver will destroy lnk structure */
- }
- if (lnk)
- erts_link_release(lnk);
+ ErtsLink *olnk;
+ ErtsELink *elnk;
+
+ ASSERT(reason == am_noconnection);
+ ASSERT(lnk->flags & ERTS_ML_FLG_EXTENDED);
+ ASSERT(lnk->type == ERTS_LNK_TYPE_DIST_PROC);
+
+ olnk = erts_link_to_other(lnk, &elnk);
+
+ from_item = olnk->other.item;
+ from_tag = elnk->dist->nodename;
+ conn_id = elnk->dist->connection_id;
+ conn_lost = !0;
+ }
+ send_gen_exit_signal(c_p, from_tag, from_item, to, ERTS_SIG_Q_OP_EXIT_LINKED,
+ reason, NULL, NULL, NIL, token, 0, conn_lost, conn_id);
+ erts_link_release(lnk);
}
int
@@ -1318,24 +1373,78 @@ erts_proc_sig_send_link(Process *c_p, Eterm to, ErtsLink *lnk)
return proc_queue_signal(c_p, to, sig, ERTS_SIG_Q_OP_LINK);
}
-void
-erts_proc_sig_send_unlink(Process *c_p, ErtsLink *lnk)
+ErtsSigUnlinkOp *
+erts_proc_sig_make_unlink_op(Process *c_p, Eterm from)
+{
+ Uint64 id;
+ ErtsSigUnlinkOp *sulnk;
+ if (c_p)
+ id = erts_proc_sig_new_unlink_id(c_p);
+ else {
+ /*
+ * *Only* ports are allowed to call without current
+ * process pointer...
+ */
+ ASSERT(is_internal_port(from));
+ id = (Uint64) erts_raw_get_unique_monotonic_integer();
+ if (id == 0)
+ id = (Uint64) erts_raw_get_unique_monotonic_integer();
+ }
+
+ ASSERT(id != 0);
+
+ sulnk = erts_alloc(ERTS_ALC_T_SIG_DATA, sizeof(ErtsSigUnlinkOp));
+ sulnk->from = from;
+ sulnk->id = id;
+
+ return sulnk;
+}
+
+Uint64
+erts_proc_sig_send_unlink(Process *c_p, Eterm from, ErtsLink *lnk)
{
+ int res;
ErtsSignal *sig;
Eterm to;
+ ErtsSigUnlinkOp *sulnk;
+ Uint64 id;
- ASSERT(lnk);
+ ASSERT(lnk->type != ERTS_LNK_TYPE_PROC
+ || lnk->type != ERTS_LNK_TYPE_PORT);
+ ASSERT(lnk->flags & ERTS_ML_FLG_IN_TABLE);
- sig = (ErtsSignal *) lnk;
+ sulnk = erts_proc_sig_make_unlink_op(c_p, from);
+ id = sulnk->id;
+ sig = (ErtsSignal *) sulnk;
to = lnk->other.item;
+ sig->common.tag = ERTS_PROC_SIG_MAKE_TAG(ERTS_SIG_Q_OP_UNLINK,
+ lnk->type, 0);
ASSERT(is_internal_pid(to));
+ res = proc_queue_signal(c_p, to, sig, ERTS_SIG_Q_OP_UNLINK);
+ if (res == 0) {
+ erts_proc_sig_destroy_unlink_op(sulnk);
+ return 0;
+ }
+ return id;
+}
- sig->common.tag = ERTS_PROC_SIG_MAKE_TAG(ERTS_SIG_Q_OP_UNLINK,
- lnk->type, 0);
+void
+erts_proc_sig_send_unlink_ack(Process *c_p, Eterm from, ErtsSigUnlinkOp *sulnk)
+{
+ ErtsSignal *sig = (ErtsSignal *) sulnk;
+ Eterm to = sulnk->from;
+ Uint16 type;
+
+ ASSERT(is_internal_pid(to));
+ ASSERT(is_internal_pid(from) || is_internal_port(from));
- if (!proc_queue_signal(c_p, to, sig, ERTS_SIG_Q_OP_UNLINK))
- erts_link_release(lnk);
+ sulnk->from = from;
+ type = is_internal_pid(from) ? ERTS_LNK_TYPE_PROC : ERTS_LNK_TYPE_PORT;
+ sig->common.tag = ERTS_PROC_SIG_MAKE_TAG(ERTS_SIG_Q_OP_UNLINK_ACK,
+ type, 0);
+ if (!proc_queue_signal(c_p, to, sig, ERTS_SIG_Q_OP_UNLINK_ACK))
+ erts_proc_sig_destroy_unlink_op(sulnk);
}
void
@@ -1346,24 +1455,92 @@ erts_proc_sig_send_dist_link_exit(DistEntry *dep,
Eterm reason, Eterm token)
{
send_gen_exit_signal(NULL, dep->sysname, from, to, ERTS_SIG_Q_OP_EXIT_LINKED,
- reason, dist_ext, hfrag, NIL, token, 0);
+ reason, dist_ext, hfrag, NIL, token, 0, 0, 0);
}
+static void
+reply_dist_unlink_ack(Process *c_p, ErtsSigDistUnlinkOp *sdulnk);
+
void
-erts_proc_sig_send_dist_unlink(DistEntry *dep, Eterm from, Eterm to)
+erts_proc_sig_send_dist_unlink(DistEntry *dep, Uint32 conn_id,
+ Eterm from, Eterm to, Uint64 id)
{
+ /* Remote to local */
ErtsSignal *sig;
ASSERT(is_internal_pid(to));
ASSERT(is_external_pid(from));
ASSERT(dep == external_pid_dist_entry(from));
- sig = (ErtsSignal *) make_sig_dist_link_op(ERTS_SIG_Q_OP_UNLINK,
- to, from);
+ sig = (ErtsSignal *) make_sig_dist_unlink_op(ERTS_SIG_Q_OP_UNLINK,
+ dep->sysname, conn_id,
+ to, from, id);
if (!proc_queue_signal(NULL, to, sig, ERTS_SIG_Q_OP_UNLINK))
- destroy_sig_dist_link_op((ErtsSigDistLinkOp *) sig);
+ reply_dist_unlink_ack(NULL, (ErtsSigDistUnlinkOp *) sig);
+}
+
+void
+erts_proc_sig_send_dist_unlink_ack(Process *c_p, DistEntry *dep,
+ Uint32 conn_id, Eterm from, Eterm to,
+ Uint64 id)
+{
+ /* Remote to local */
+ ErtsSignal *sig;
+
+ ASSERT(is_internal_pid(to));
+ ASSERT(is_external_pid(from));
+ ASSERT(dep == external_pid_dist_entry(from));
+
+ sig = (ErtsSignal *) make_sig_dist_unlink_op(ERTS_SIG_Q_OP_UNLINK_ACK,
+ dep->sysname, conn_id,
+ to, from, id);
+
+ if (!proc_queue_signal(c_p, to, sig, ERTS_SIG_Q_OP_UNLINK_ACK))
+ destroy_sig_dist_unlink_op((ErtsSigDistUnlinkOp *) sig);
+}
+
+static void
+reply_dist_unlink_ack(Process *c_p, ErtsSigDistUnlinkOp *sdulnk)
+{
+ /* Local to remote */
+ ASSERT(is_external_pid(sdulnk->remote));
+
+ /*
+ * 'id' is zero if the other side not understand
+ * unlink-ack signals...
+ */
+ if (sdulnk->id) {
+ DistEntry *dep = external_pid_dist_entry(sdulnk->remote);
+
+ /*
+ * Do not set up new a connection; only send unlink ack
+ * on the same connection which the unlink operation was
+ * received on...
+ */
+ if (dep != erts_this_dist_entry && sdulnk->nodename == dep->sysname) {
+ ErtsDSigSendContext ctx;
+ int code = erts_dsig_prepare(&ctx, dep, c_p, 0,
+ ERTS_DSP_NO_LOCK, 1, 1, 0);
+ switch (code) {
+ case ERTS_DSIG_PREP_CONNECTED:
+ case ERTS_DSIG_PREP_PENDING:
+ if (sdulnk->connection_id == ctx.connection_id) {
+ code = erts_dsig_send_unlink_ack(&ctx,
+ sdulnk->local,
+ sdulnk->remote,
+ sdulnk->id);
+ ASSERT(code == ERTS_DSIG_SEND_OK);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ destroy_sig_dist_unlink_op(sdulnk);
}
void
@@ -1380,7 +1557,7 @@ erts_proc_sig_send_dist_monitor_down(DistEntry *dep, Eterm ref,
monitored = from;
send_gen_exit_signal(NULL, dep->sysname, monitored,
to, ERTS_SIG_Q_OP_MONITOR_DOWN,
- reason, dist_ext, hfrag, ref, NIL, 0);
+ reason, dist_ext, hfrag, ref, NIL, 0, 0, 0);
}
void
@@ -1451,7 +1628,8 @@ erts_proc_sig_send_monitor_down(ErtsMonitor *mon, Eterm reason)
}
send_gen_exit_signal(NULL, from_tag, monitored,
to, ERTS_SIG_Q_OP_MONITOR_DOWN,
- reason, NULL, NULL, mdp->ref, NIL, 0);
+ reason, NULL, NULL, mdp->ref, NIL,
+ 0, 0, 0);
}
erts_monitor_release(mon);
}
@@ -1751,6 +1929,104 @@ erts_proc_sig_send_sync_suspend(Process *c_p, Eterm to, Eterm tag, Eterm reply)
}
}
+int
+erts_proc_sig_send_dist_spawn_reply(Eterm node,
+ Eterm ref,
+ Eterm to,
+ ErtsLink *lnk,
+ Eterm result,
+ Eterm token)
+{
+ Uint hsz, ref_sz, result_sz, token_sz;
+ ErtsDistSpawnReplySigData *datap;
+ Eterm msg, ref_copy, result_copy, res_type,
+ token_copy, *hp, *hp_start, *patch_point;
+ ErlHeapFragment *hfrag;
+ ErlOffHeap *ohp;
+ ErtsMessage *mp;
+
+ ASSERT(is_atom(node));
+
+ /*
+ * A respons message to a spawn_request() operation
+ * looks like this:
+ * {Tag, Ref, ok|error, Pid|ErrorAtom}
+ *
+ * Tag is stored in its own heap fragment in the
+ * (pending) monitor struct and can be attached
+ * when creating the resulting message on
+ * reception of this signal.
+ */
+
+ hsz = ref_sz = size_object(ref);
+ hsz += 5 /* 4-tuple */;
+ if (is_atom(result)) {
+ res_type = am_error;
+ result_sz = 0;
+ }
+ else {
+ ASSERT(is_external_pid(result));
+ res_type = am_ok;
+ result_sz = size_object(result);
+ hsz += result_sz;
+ }
+
+ token_sz = is_immed(token) ? 0 : size_object(token);
+ hsz += token_sz;
+
+ hsz += sizeof(ErtsDistSpawnReplySigData)/sizeof(Eterm);
+
+ mp = erts_alloc_message(hsz, &hp);
+ hp_start = hp;
+ hfrag = &mp->hfrag;
+ mp->next = NULL;
+ ohp = &hfrag->off_heap;
+
+ ref_copy = copy_struct(ref, ref_sz, &hp, ohp);
+ result_copy = (is_atom(result)
+ ? result
+ : copy_struct(result, result_sz, &hp, ohp));
+ msg = TUPLE4(hp,
+ am_undefined,
+ ref_copy,
+ res_type,
+ result_copy);
+
+ patch_point = &hp[1];
+ ASSERT(*patch_point == am_undefined);
+
+ hp += 5;
+
+ token_copy = (!token_sz
+ ? token
+ : copy_struct(token, token_sz, &hp, ohp));
+
+ hfrag->used_size = hp - hp_start;
+
+ datap = (ErtsDistSpawnReplySigData *) (char *) hp;
+ datap->message = msg;
+ datap->ref = ref_copy;
+ datap->result = result_copy;
+ datap->link = lnk;
+ datap->patch_point = patch_point;
+
+ ERL_MESSAGE_TERM(mp) = ERTS_PROC_SIG_MAKE_TAG(ERTS_SIG_Q_OP_DIST_SPAWN_REPLY,
+ ERTS_SIG_Q_TYPE_UNDEFINED,
+ 0);
+ ERL_MESSAGE_FROM(mp) = node;
+ ERL_MESSAGE_TOKEN(mp) = token_copy;
+ if (!proc_queue_signal(NULL, to, (ErtsSignal *) mp,
+ ERTS_SIG_Q_OP_DIST_SPAWN_REPLY)) {
+ mp->next = NULL;
+ mp->data.attached = ERTS_MSG_COMBINED_HFRAG;
+ ERL_MESSAGE_TERM(mp) = msg;
+ erts_cleanup_messages(mp);
+ return 0;
+ }
+
+ return !0;
+}
+
Eterm
erts_proc_sig_send_rpc_request(Process *c_p,
Eterm to,
@@ -1901,6 +2177,7 @@ is_alive_response(Process *c_p, ErtsMessage *mp, int is_alive)
}
}
+
static ERTS_INLINE void
adjust_tracing_state(Process *c_p, ErtsSigRecvTracing *tracing, int setup)
{
@@ -2093,46 +2370,68 @@ handle_exit_signal(Process *c_p, ErtsSigRecvTracing *tracing,
{
ErtsMessage *conv_msg = NULL;
ErtsExitSignalData *xsigd = NULL;
- ErtsLinkData *ldp = NULL; /* Avoid erroneous warning... */
- ErtsLink *dlnk = NULL; /* Avoid erroneous warning... */
Eterm tag = ((ErtsSignal *) sig)->common.tag;
- Uint16 type = ERTS_PROC_SIG_TYPE(tag);
int op = ERTS_PROC_SIG_OP(tag);
int destroy = 0;
int ignore = 0;
int save = 0;
int exit = 0;
+ int linked = 0;
int cnt = 1;
Eterm reason;
Eterm from;
- if (type == ERTS_SIG_Q_TYPE_GEN_EXIT) {
- xsigd = get_exit_signal_data(sig);
- from = xsigd->from;
- if (op != ERTS_SIG_Q_OP_EXIT_LINKED)
- ignore = 0;
+ ASSERT(ERTS_PROC_SIG_TYPE(tag) == ERTS_SIG_Q_TYPE_GEN_EXIT);
+
+ xsigd = get_exit_signal_data(sig);
+ from = xsigd->from;
+
+ if (op == ERTS_SIG_Q_OP_EXIT_LINKED) {
+ ErtsLink *lnk, *dlnk = NULL;
+ ErtsELink *elnk = NULL;
+ lnk = erts_link_tree_lookup(ERTS_P_LINKS(c_p), from);
+ if (!lnk)
+ ignore = destroy = !0; /* No longer active */
+ else if (lnk->type != ERTS_LNK_TYPE_DIST_PROC) {
+ if (((ErtsILink *) lnk)->unlinking)
+ ignore = destroy = !0; /* No longer active */
+ else
+ linked = !0;
+ }
else {
- ErtsLink *llnk = erts_link_tree_lookup(ERTS_P_LINKS(c_p), from);
- if (!llnk) {
- /* Link no longer active; ignore... */
- ignore = !0;
- destroy = !0;
- }
- else {
- ignore = 0;
- erts_link_tree_delete(&ERTS_P_LINKS(c_p), llnk);
- if (llnk->type != ERTS_LNK_TYPE_DIST_PROC)
- erts_link_release(llnk);
- else {
- dlnk = erts_link_to_other(llnk, &ldp);
- if (erts_link_dist_delete(dlnk))
- erts_link_release_both(ldp);
- else
- erts_link_release(llnk);
- }
+ dlnk = erts_link_to_other(lnk, &elnk);
+ if (elnk->unlinking)
+ ignore = destroy = !0; /* No longer active */
+ else
+ linked = !0;
+ if ((xsigd->u.link.flags & ERTS_SIG_LNK_X_FLAG_CONNECTION_LOST)
+ && xsigd->u.link.connection_id != elnk->dist->connection_id) {
+ /*
+ * The exit signal is due to loss of connection. The link
+ * that triggered this was setup before that connection
+ * was lost, but was later unlinked. After that, the
+ * current link was setup using a new connection. That is,
+ * current link should be left unaffected, and the signal
+ * should be silently dropped.
+ */
+ linked = 0;
+ lnk = NULL;
+ ignore = destroy = !0;
}
}
+ if (lnk) {
+ /* Remove link... */
+ erts_link_tree_delete(&ERTS_P_LINKS(c_p), lnk);
+ if (!elnk)
+ erts_link_internal_release(lnk);
+ else if (erts_link_dist_delete(dlnk))
+ erts_link_release_both(&elnk->ld);
+ else
+ erts_link_release(lnk);
+ }
+ }
+ if (!ignore) {
/* This GEN_EXIT was received from another node, decode the exit reason */
if (ERTS_SIG_IS_GEN_EXIT_EXTERNAL(sig))
erts_proc_sig_decode_dist(c_p, ERTS_PROC_LOCK_MAIN, sig, 1);
@@ -2144,106 +2443,29 @@ handle_exit_signal(Process *c_p, ErtsSigRecvTracing *tracing,
ignore = !0;
destroy = !0;
}
+ }
- if (!ignore) {
+ if (!ignore) {
- if ((op != ERTS_SIG_Q_OP_EXIT || reason != am_kill)
- && (c_p->flags & F_TRAP_EXIT)) {
- convert_prepared_sig_to_msg(c_p, sig,
- xsigd->message, next_nm_sig);
- conv_msg = sig;
- }
- else if (reason == am_normal && !xsigd->u.normal_kills) {
- /* Ignore it... */
- destroy = !0;
- ignore = !0;
- }
- else {
- /* Terminate... */
- save = !0;
- exit = !0;
- if (op == ERTS_SIG_Q_OP_EXIT && reason == am_kill)
- reason = am_killed;
- }
+ if ((op != ERTS_SIG_Q_OP_EXIT || reason != am_kill)
+ && (c_p->flags & F_TRAP_EXIT)) {
+ convert_prepared_sig_to_msg(c_p, sig,
+ xsigd->message, next_nm_sig);
+ conv_msg = sig;
}
- }
- else { /* Link exit */
- ErtsLink *slnk = (ErtsLink *) sig;
- ErtsLink *llnk = erts_link_to_other(slnk, &ldp);
-
- ASSERT(type == ERTS_LNK_TYPE_PROC
- || type == ERTS_LNK_TYPE_PORT
- || type == ERTS_LNK_TYPE_DIST_PROC);
-
- from = llnk->other.item;
- reason = slnk->other.item; /* reason in other.item ... */
- ASSERT(is_pid(from) || is_internal_port(from));
- ASSERT(is_immed(reason));
- ASSERT(op == ERTS_SIG_Q_OP_EXIT_LINKED);
- dlnk = erts_link_tree_key_delete(&ERTS_P_LINKS(c_p), llnk);
- if (!dlnk) {
- ignore = !0; /* Link no longer active; ignore... */
- ldp = NULL;
+ else if (reason == am_normal
+ && !(xsigd->u.link.flags & ERTS_SIG_LNK_X_FLAG_NORMAL_KILLS)) {
+ /* Ignore it... */
+ destroy = !0;
+ ignore = !0;
}
else {
- Eterm pid;
- ErtsMessage *mp;
- ErtsProcLocks locks;
- Uint hsz;
- Eterm *hp;
- ErlOffHeap *ohp;
- ignore = 0;
- if (dlnk == llnk)
- dlnk = NULL;
- else
- ldp = NULL;
-
- ASSERT(is_immed(reason));
-
- if (!(c_p->flags & F_TRAP_EXIT)) {
- if (reason == am_normal)
- ignore = !0; /* Ignore it... */
- else
- exit = !0; /* Terminate... */
- }
- else {
-
- /*
- * Create and EXIT message and replace
- * the original signal with the message...
- */
-
- locks = ERTS_PROC_LOCK_MAIN;
-
- hsz = 4 + NC_HEAP_SIZE(from);
-
- mp = erts_alloc_message_heap(c_p, &locks, hsz, &hp, &ohp);
-
- if (locks != ERTS_PROC_LOCK_MAIN)
- erts_proc_unlock(c_p, locks & ~ERTS_PROC_LOCK_MAIN);
-
- pid = STORE_NC(&hp, ohp, from);
-
- ERL_MESSAGE_TERM(mp) = TUPLE3(hp, am_EXIT, pid, reason);
- ERL_MESSAGE_TOKEN(mp) = am_undefined;
- if (is_immed(pid))
- ERL_MESSAGE_FROM(mp) = pid;
- else {
- DistEntry *dep;
- ASSERT(is_external_pid(pid));
- dep = external_pid_dist_entry(pid);
- ERL_MESSAGE_FROM(mp) = dep->sysname;
- }
-
- /* Replace original signal with the exit message... */
- convert_to_msg(c_p, sig, mp, next_nm_sig);
-
- cnt += 4;
-
- conv_msg = mp;
- }
+ /* Terminate... */
+ save = !0;
+ exit = !0;
+ if (op == ERTS_SIG_Q_OP_EXIT && reason == am_kill)
+ reason = am_killed;
}
- destroy = !0;
}
if (ignore|exit) {
@@ -2264,25 +2486,16 @@ handle_exit_signal(Process *c_p, ErtsSigRecvTracing *tracing,
if (!exit) {
if (conv_msg)
erts_proc_notify_new_message(c_p, ERTS_PROC_LOCK_MAIN);
- if (op == ERTS_SIG_Q_OP_EXIT_LINKED && tracing->procs)
+ if (linked && tracing->procs) {
+ ASSERT(op == ERTS_SIG_Q_OP_EXIT_LINKED);
getting_unlinked(c_p, from);
+ }
}
if (destroy) {
cnt++;
- if (type == ERTS_SIG_Q_TYPE_GEN_EXIT) {
- sig->next = NULL;
- erts_cleanup_messages(sig);
- }
- else {
- if (ldp)
- erts_link_release_both(ldp);
- else {
- if (dlnk)
- erts_link_release(dlnk);
- erts_link_release((ErtsLink *) sig);
- }
- }
+ sig->next = NULL;
+ erts_cleanup_messages(sig);
}
*exited = exit;
@@ -2303,17 +2516,14 @@ static int
convert_to_down_message(Process *c_p,
ErtsMessage *sig,
ErtsMonitorData *mdp,
+ ErtsMonitor **omon,
Uint16 mon_type,
ErtsMessage ***next_nm_sig)
{
- /*
- * Create a 'DOWN' message and replace the signal
- * with it...
- */
int cnt = 0;
Eterm node = am_undefined;
ErtsMessage *mp;
- ErtsProcLocks locks;
+ ErtsProcLocks locks = ERTS_PROC_LOCK_MAIN;
Uint hsz;
Eterm *hp, ref, from, type, reason;
ErlOffHeap *ohp;
@@ -2322,96 +2532,166 @@ convert_to_down_message(Process *c_p,
ASSERT((mdp->origin.flags & ERTS_ML_FLGS_SAME)
== (mdp->target.flags & ERTS_ML_FLGS_SAME));
- hsz = 6; /* 5-tuple */
-
- if (mdp->origin.flags & ERTS_ML_FLG_NAME)
- hsz += 3; /* reg name 2-tuple */
- else {
- ASSERT(is_pid(mdp->origin.other.item)
- || is_internal_port(mdp->origin.other.item));
- hsz += NC_HEAP_SIZE(mdp->origin.other.item);
- }
-
- ASSERT(is_ref(mdp->ref));
- hsz += NC_HEAP_SIZE(mdp->ref);
-
- locks = ERTS_PROC_LOCK_MAIN;
-
/* reason is mdp->target.other.item */
reason = mdp->target.other.item;
ASSERT(is_immed(reason));
+ ASSERT(&mdp->origin == *omon);
+
+ if (mdp->origin.flags & ERTS_ML_FLG_SPAWN_PENDING) {
+ /*
+ * Create a spawn_request() error message and replace
+ * the signal with it...
+ */
+ Eterm tag;
+ ErtsMonitorDataExtended *mdep;
- mp = erts_alloc_message_heap(c_p, &locks, hsz, &hp, &ohp);
+ /* Should only happen when connection breaks... */
+ ASSERT(reason == am_noconnection);
- if (locks != ERTS_PROC_LOCK_MAIN)
- erts_proc_unlock(c_p, locks & ~ERTS_PROC_LOCK_MAIN);
+ if (mdp->origin.flags & (ERTS_ML_FLG_SPAWN_ABANDONED
+ | ERTS_ML_FLG_SPAWN_NO_EMSG)) {
+ /*
+ * Operation has been been abandoned or
+ * error message has been disabled...
+ */
+ erts_monitor_release(*omon);
+ *omon = NULL;
+ return 1;
+ }
- cnt += 4;
+ cnt += 4;
+
+ mdep = (ErtsMonitorDataExtended *) mdp;
+ hsz = 5; /* 4-tuple */
- ref = STORE_NC(&hp, ohp, mdp->ref);
+ ASSERT(is_ref(mdp->ref));
+ hsz += NC_HEAP_SIZE(mdp->ref);
+
+ /*
+ * The tag to patch into the resulting message
+ * is stored in mdep->u.name via a little trick
+ * (see pending_flag in erts_monitor_create()).
+ */
+ if (is_immed(mdep->u.name)) {
+ mp = erts_alloc_message_heap(c_p, &locks, hsz, &hp, &ohp);
+ if (locks != ERTS_PROC_LOCK_MAIN)
+ erts_proc_unlock(c_p, locks & ~ERTS_PROC_LOCK_MAIN);
+ tag = mdep->u.name;
+ }
+ else {
+ ErlHeapFragment *tag_hfrag;
+ mp = erts_alloc_message(hsz, &hp);
+ ohp = &mp->hfrag.off_heap;
+ tag_hfrag = (ErlHeapFragment *) cp_val(mdep->u.name);
+ tag = tag_hfrag->mem[0];
+ /* Save heap fragment of tag in message... */
+ ASSERT(mp->data.attached == ERTS_MSG_COMBINED_HFRAG);
+ tag_hfrag->next = mp->hfrag.next;
+ mp->hfrag.next = tag_hfrag;
+ }
+
+ /* Restore to normal monitor */
+ mdep->u.name = NIL;
+ mdp->origin.flags &= ~ERTS_ML_FLGS_SPAWN;
+
+ ref = STORE_NC(&hp, ohp, mdp->ref);
+
+ ERL_MESSAGE_FROM(mp) = am_undefined;
+ ERL_MESSAGE_TERM(mp) = TUPLE4(hp, tag, ref, am_error, reason);
- if (!(mdp->origin.flags & ERTS_ML_FLG_NAME)) {
- from = STORE_NC(&hp, ohp, mdp->origin.other.item);
}
else {
- ErtsMonitorDataExtended *mdep;
- ASSERT(mdp->origin.flags & ERTS_ML_FLG_EXTENDED);
- mdep = (ErtsMonitorDataExtended *) mdp;
- ASSERT(is_atom(mdep->u.name));
- if (mdep->dist)
- node = mdep->dist->nodename;
- else
- node = erts_this_dist_entry->sysname;
- from = TUPLE2(hp, mdep->u.name, node);
- hp += 3;
- }
+ /*
+ * Create a 'DOWN' message and replace the signal
+ * with it...
+ */
- ASSERT(mdp->origin.type == mon_type);
- switch (mon_type) {
- case ERTS_MON_TYPE_PORT:
- type = am_port;
- if (mdp->origin.other.item == am_undefined) {
- /* failed by name... */
- ERL_MESSAGE_FROM(mp) = am_system;
- }
+ hsz = 6; /* 5-tuple */
+
+ if (mdp->origin.flags & ERTS_ML_FLG_NAME)
+ hsz += 3; /* reg name 2-tuple */
else {
- ASSERT(is_internal_port(mdp->origin.other.item));
- ERL_MESSAGE_FROM(mp) = mdp->origin.other.item;
+ ASSERT(is_pid(mdp->origin.other.item)
+ || is_internal_port(mdp->origin.other.item));
+ hsz += NC_HEAP_SIZE(mdp->origin.other.item);
}
- break;
- case ERTS_MON_TYPE_PROC:
- type = am_process;
- if (mdp->origin.other.item == am_undefined) {
- /* failed by name... */
- ERL_MESSAGE_FROM(mp) = am_system;
+
+ ASSERT(is_ref(mdp->ref));
+ hsz += NC_HEAP_SIZE(mdp->ref);
+
+ mp = erts_alloc_message_heap(c_p, &locks, hsz, &hp, &ohp);
+
+ if (locks != ERTS_PROC_LOCK_MAIN)
+ erts_proc_unlock(c_p, locks & ~ERTS_PROC_LOCK_MAIN);
+
+ cnt += 4;
+
+ ref = STORE_NC(&hp, ohp, mdp->ref);
+
+ if (!(mdp->origin.flags & ERTS_ML_FLG_NAME)) {
+ from = STORE_NC(&hp, ohp, mdp->origin.other.item);
}
else {
- ASSERT(is_internal_pid(mdp->origin.other.item));
- ERL_MESSAGE_FROM(mp) = mdp->origin.other.item;
- }
- break;
- case ERTS_MON_TYPE_DIST_PROC:
- type = am_process;
- if (node == am_undefined) {
ErtsMonitorDataExtended *mdep;
ASSERT(mdp->origin.flags & ERTS_ML_FLG_EXTENDED);
mdep = (ErtsMonitorDataExtended *) mdp;
- ASSERT(mdep->dist);
- node = mdep->dist->nodename;
+ ASSERT(is_atom(mdep->u.name));
+ if (mdep->dist)
+ node = mdep->dist->nodename;
+ else
+ node = erts_this_dist_entry->sysname;
+ from = TUPLE2(hp, mdep->u.name, node);
+ hp += 3;
}
- ASSERT(is_atom(node) && node != am_undefined);
- ERL_MESSAGE_FROM(mp) = node;
- break;
- default:
- ERTS_INTERNAL_ERROR("Unexpected monitor type");
- type = am_undefined;
- ERL_MESSAGE_FROM(mp) = am_undefined;
- break;
- }
- ERL_MESSAGE_TERM(mp) = TUPLE5(hp, am_DOWN, ref,
- type, from, reason);
- hp += 6;
+ ASSERT(mdp->origin.type == mon_type);
+ switch (mon_type) {
+ case ERTS_MON_TYPE_PORT:
+ type = am_port;
+ if (mdp->origin.other.item == am_undefined) {
+ /* failed by name... */
+ ERL_MESSAGE_FROM(mp) = am_system;
+ }
+ else {
+ ASSERT(is_internal_port(mdp->origin.other.item));
+ ERL_MESSAGE_FROM(mp) = mdp->origin.other.item;
+ }
+ break;
+ case ERTS_MON_TYPE_PROC:
+ type = am_process;
+ if (mdp->origin.other.item == am_undefined) {
+ /* failed by name... */
+ ERL_MESSAGE_FROM(mp) = am_system;
+ }
+ else {
+ ASSERT(is_internal_pid(mdp->origin.other.item));
+ ERL_MESSAGE_FROM(mp) = mdp->origin.other.item;
+ }
+ break;
+ case ERTS_MON_TYPE_DIST_PROC:
+ type = am_process;
+ if (node == am_undefined) {
+ ErtsMonitorDataExtended *mdep;
+ ASSERT(mdp->origin.flags & ERTS_ML_FLG_EXTENDED);
+ mdep = (ErtsMonitorDataExtended *) mdp;
+ ASSERT(mdep->dist);
+ node = mdep->dist->nodename;
+ }
+ ASSERT(is_atom(node) && node != am_undefined);
+ ERL_MESSAGE_FROM(mp) = node;
+ break;
+ default:
+ ERTS_INTERNAL_ERROR("Unexpected monitor type");
+ type = am_undefined;
+ ERL_MESSAGE_FROM(mp) = am_undefined;
+ break;
+ }
+
+ ERL_MESSAGE_TERM(mp) = TUPLE5(hp, am_DOWN, ref,
+ type, from, reason);
+ hp += 6;
+
+ }
ERL_MESSAGE_TOKEN(mp) = am_undefined;
/* Replace original signal with the exit message... */
@@ -3154,6 +3434,309 @@ erts_proc_sig_handle_pending_suspend(Process *c_p)
ERTS_PROC_SET_PENDING_SUSPEND(c_p, NULL);
}
+static int
+handle_dist_spawn_reply(Process *c_p, ErtsSigRecvTracing *tracing,
+ ErtsMessage *sig, ErtsMessage ***next_nm_sig)
+{
+
+ ErtsDistSpawnReplySigData *datap = get_dist_spawn_reply_data(sig);
+ ErtsMonitorDataExtended *mdep;
+ Eterm msg = datap->message;
+ Eterm result = datap->result;
+ ErtsMonitor *omon;
+ int adjust_monitor;
+ ErlHeapFragment *tag_hfrag = NULL;
+ int convert_to_message = !0;
+ int cnt = 1;
+
+ ASSERT(is_atom(result) || is_external_pid(result));
+ ASSERT(is_atom(result) || size_object(result) == EXTERNAL_THING_HEAD_SIZE + 1);
+
+ omon = erts_monitor_tree_lookup(ERTS_P_MONITORS(c_p), datap->ref);
+
+ if (!omon || !(omon->flags & ERTS_ML_FLG_SPAWN_PENDING)) {
+ /* Stale reply; remove link that was setup... */
+ ErtsLink *lnk = datap->link;
+ if (lnk) {
+ ErtsELink *elnk;
+ ErtsLink *dlnk = erts_link_to_other(lnk, &elnk);
+ if (erts_link_dist_delete(dlnk))
+ erts_link_release_both(&elnk->ld);
+ else
+ erts_link_release(lnk);
+ }
+ remove_nm_sig(c_p, sig, next_nm_sig);
+ sig->data.attached = ERTS_MSG_COMBINED_HFRAG;
+ ERL_MESSAGE_TERM(sig) = msg;
+ sig->next = NULL;;
+ erts_cleanup_messages(sig);
+ return ++cnt;
+ }
+
+ mdep = (ErtsMonitorDataExtended *) erts_monitor_to_data(omon);
+
+#ifdef DEBUG
+ {
+ Eterm *tp;
+ int i;
+ ASSERT(erts_monitor_is_in_table(omon));
+ ASSERT(omon->flags & ERTS_ML_FLG_SPAWN_PENDING);
+ if (is_atom(result)) {
+ ASSERT(!datap->link);
+ }
+ else {
+ ASSERT(!datap->link || (omon->flags & ERTS_ML_FLG_SPAWN_LINK));
+ ASSERT(!(omon->flags & ERTS_ML_FLG_SPAWN_LINK) || datap->link);
+ }
+ ASSERT(omon->other.item == am_pending);
+ ASSERT(is_tuple_arity(datap->message, 4));
+ tp = tuple_val(datap->message);
+ ASSERT(tp[1] == am_undefined); /* patch point */
+ ASSERT(is_internal_ref(tp[2]));
+ ASSERT((tp[3] == am_ok && is_external_pid(tp[4]))
+ || (tp[3] == am_error && is_atom(tp[4])));
+ for (i = 0; i < EXTERNAL_THING_HEAD_SIZE + 1; i++) {
+ ASSERT(is_non_value(mdep->heap[i]));
+ }
+ }
+#endif
+
+ /*
+ * The tag to patch into the resulting message
+ * is stored in mdep->u.name via a little trick
+ * (see pending_flag in erts_monitor_create()).
+ */
+ if (is_immed(mdep->u.name)) {
+ tag_hfrag = NULL;
+ *datap->patch_point = mdep->u.name;
+ }
+ else {
+ tag_hfrag = (ErlHeapFragment *) cp_val(mdep->u.name);
+ *datap->patch_point = tag_hfrag->mem[0];
+ }
+ mdep->u.name = NIL; /* Restore to normal monitor */
+
+ if (is_atom(result)) { /* Spawn error; cleanup... */
+ /* Dist code should not have created a link on failure... */
+
+ ASSERT(is_not_atom(result) || !datap->link);
+ /* delete monitor structure... */
+ adjust_monitor = 0;
+ if (omon->flags & (ERTS_ML_FLG_SPAWN_ABANDONED
+ | ERTS_ML_FLG_SPAWN_NO_EMSG))
+ convert_to_message = 0;
+ }
+ else if (omon->flags & ERTS_ML_FLG_SPAWN_ABANDONED) {
+ /*
+ * Spawn operation has been abandoned and
+ * link option was passed. Send exit signal
+ * with exit reason 'abandoned'...
+ */
+ DistEntry *dep;
+ ErtsMonLnkDist *dist;
+ ErtsMonitorDataExtended *mdep;
+ ErtsLink *lnk;
+
+ mdep = (ErtsMonitorDataExtended *) erts_monitor_to_data(omon);
+ dist = mdep->dist;
+
+ ASSERT(omon->flags & ERTS_ML_FLG_SPAWN_LINK);
+
+ lnk = datap->link;
+ if (lnk) {
+ ErtsELink *elnk;
+ ErtsLink *dlnk;
+ dlnk = erts_link_to_other(lnk, &elnk);
+ if (erts_link_dist_delete(dlnk))
+ erts_link_release_both(&elnk->ld);
+ else
+ erts_link_release(lnk);
+ }
+
+ ASSERT(is_external_pid(result));
+ dep = external_pid_dist_entry(result);
+
+ if (dep != erts_this_dist_entry && dist->nodename == dep->sysname) {
+ ErtsDSigSendContext ctx;
+ int code = erts_dsig_prepare(&ctx, dep, c_p, 0,
+ ERTS_DSP_NO_LOCK, 1, 1, 0);
+ switch (code) {
+ case ERTS_DSIG_PREP_CONNECTED:
+ case ERTS_DSIG_PREP_PENDING:
+ if (dist->connection_id == ctx.connection_id) {
+ code = erts_dsig_send_exit_tt(&ctx,
+ c_p->common.id,
+ result,
+ am_abandoned,
+ SEQ_TRACE_TOKEN(c_p));
+ ASSERT(code == ERTS_DSIG_SEND_OK);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ /* delete monitor structure... */
+ adjust_monitor = 0;
+ /* drop message... */
+ convert_to_message = 0;
+ }
+ else {
+ /* Success... */
+ ASSERT(is_external_pid(result));
+
+ if (omon->flags & ERTS_ML_FLG_SPAWN_NO_SMSG)
+ convert_to_message = 0;
+
+ if (datap->link) {
+ cnt++;
+ erts_link_tree_insert(&ERTS_P_LINKS(c_p), datap->link);
+ if (tracing->procs)
+ linking(c_p, result);
+ }
+
+ adjust_monitor = !!(omon->flags & ERTS_ML_FLG_SPAWN_MONITOR);
+ if (adjust_monitor) {
+ /*
+ * Insert the actual pid of spawned process
+ * in origin part of monitor...
+ */
+ ErlOffHeap oh;
+ ErtsMonitorDataExtended *mdep;
+ Eterm *hp;
+ mdep = (ErtsMonitorDataExtended *) erts_monitor_to_data(omon);
+ hp = &(mdep)->heap[0];
+ omon->flags &= ~ERTS_ML_FLGS_SPAWN;
+ ERTS_INIT_OFF_HEAP(&oh);
+ oh.first = mdep->uptr.ohhp;
+ omon->other.item = copy_struct(result,
+ EXTERNAL_THING_HEAD_SIZE + 1,
+ &hp, &oh);
+ mdep->uptr.ohhp = oh.first;
+ cnt += 2;
+ }
+ }
+
+ if (!adjust_monitor) {
+ /*
+ * Delete monitor; either spawn error
+ * or no monitor requested...
+ */
+ ErtsMonitorData *mdp = erts_monitor_to_data(omon);
+
+ omon->flags &= ~ERTS_ML_FLGS_SPAWN;
+
+ erts_monitor_tree_delete(&ERTS_P_MONITORS(c_p), omon);
+
+ if (erts_monitor_dist_delete(&mdp->target))
+ erts_monitor_release_both(mdp);
+ else
+ erts_monitor_release(omon);
+ cnt += 2;
+ }
+
+ if (convert_to_message) {
+ convert_prepared_sig_to_msg(c_p, sig, msg, next_nm_sig);
+ if (tag_hfrag) {
+ /* Save heap fragment of tag in message... */
+ ASSERT(sig->data.attached == ERTS_MSG_COMBINED_HFRAG);
+ tag_hfrag->next = sig->hfrag.next;
+ sig->hfrag.next = tag_hfrag;
+ }
+ erts_proc_notify_new_message(c_p, ERTS_PROC_LOCK_MAIN);
+ }
+ else {
+ remove_nm_sig(c_p, sig, next_nm_sig);
+ sig->data.attached = ERTS_MSG_COMBINED_HFRAG;
+ ERL_MESSAGE_TERM(sig) = msg;
+ sig->next = NULL;;
+ erts_cleanup_messages(sig);
+ if (tag_hfrag) {
+ tag_hfrag->next = NULL;
+ free_message_buffer(tag_hfrag);
+ }
+ }
+ return cnt;
+}
+
+static int
+handle_dist_spawn_reply_exiting(Process *c_p,
+ ErtsMessage *sig,
+ ErtsMonitor **pend_spawn_mon_pp,
+ Eterm reason)
+{
+ ErtsDistSpawnReplySigData *datap = get_dist_spawn_reply_data(sig);
+ Eterm result = datap->result;
+ Eterm msg = datap->message;
+ ErtsMonitorData *mdp;
+ ErtsMonitor *omon;
+ int cnt = 1;
+
+ ASSERT(is_atom(result) || is_external_pid(result));
+ ASSERT(is_atom(result) || size_object(result) == EXTERNAL_THING_HEAD_SIZE + 1);
+
+ omon = erts_monitor_tree_lookup(*pend_spawn_mon_pp, datap->ref);
+ if (!omon) {
+ /* May happen when connection concurrently close... */
+ ErtsLink *lnk = datap->link;
+ if (lnk) {
+ ErtsELink *elnk;
+ ErtsLink *dlnk = erts_link_to_other(lnk, &elnk);
+ if (erts_link_dist_delete(dlnk))
+ erts_link_release_both(&elnk->ld);
+ else
+ erts_link_release(lnk);
+ }
+ cnt++;
+ }
+ else {
+ ASSERT(omon->flags & ERTS_ML_FLG_SPAWN_PENDING);
+ ASSERT(!datap->link || is_external_pid(result));
+
+ erts_monitor_tree_delete(pend_spawn_mon_pp, omon);
+ mdp = erts_monitor_to_data(omon);
+
+ if (!erts_dist_pend_spawn_exit_delete(&mdp->target))
+ mdp = NULL; /* Connection closed/closing... */
+ cnt++;
+
+ if (is_external_pid(result)) {
+ if ((omon->flags & ERTS_ML_FLG_SPAWN_MONITOR) && mdp) {
+ ErtsMonitorDataExtended *mdep = (ErtsMonitorDataExtended *) mdp;
+ erts_proc_exit_dist_demonitor(c_p,
+ external_pid_dist_entry(result),
+ mdep->dist->connection_id,
+ datap->ref,
+ result);
+ cnt++;
+ }
+ ASSERT(!datap->link || (omon->flags & ERTS_ML_FLG_SPAWN_LINK));
+ ASSERT(!(omon->flags & ERTS_ML_FLG_SPAWN_LINK) || datap->link);
+
+ if (datap->link) {
+ /* This link exit *should* have actual reason... */
+ ErtsProcExitContext pectxt = {c_p, reason};
+ /* unless operation has been abandoned... */
+ if (omon->flags & ERTS_ML_FLG_SPAWN_ABANDONED)
+ pectxt.reason = am_abandoned;
+ erts_proc_exit_handle_link(datap->link, (void *) &pectxt, -1);
+ cnt++;
+ }
+ }
+ if (mdp)
+ erts_monitor_release_both(mdp);
+ else
+ erts_monitor_release(omon);
+ cnt++;
+ }
+ sig->data.attached = ERTS_MSG_COMBINED_HFRAG;
+ ERL_MESSAGE_TERM(sig) = msg;
+ sig->next = NULL;
+ erts_cleanup_messages(sig);
+ cnt++;
+ return cnt;
+}
+
/*
* Called in order to handle incoming signals.
*/
@@ -3272,7 +3855,7 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
omon = &mdp->origin;
erts_monitor_tree_delete(&ERTS_P_MONITORS(c_p),
omon);
- cnt += convert_to_down_message(c_p, sig, mdp,
+ cnt += convert_to_down_message(c_p, sig, mdp, &omon,
type, next_nm_sig);
}
break;
@@ -3288,13 +3871,13 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
xsigd->u.ref);
if (omon) {
ASSERT(erts_monitor_is_origin(omon));
+ erts_monitor_tree_delete(&ERTS_P_MONITORS(c_p),
+ omon);
if (omon->type == ERTS_MON_TYPE_DIST_PROC) {
mdp = erts_monitor_to_data(omon);
if (erts_monitor_dist_delete(&mdp->target))
tmon = &mdp->target;
}
- erts_monitor_tree_delete(&ERTS_P_MONITORS(c_p),
- omon);
cnt += convert_prepared_down_message(c_p, sig,
xsigd->message,
next_nm_sig);
@@ -3463,27 +4046,27 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
}
case ERTS_SIG_Q_OP_LINK: {
- ErtsLink *rlnk, *lnk = (ErtsLink *) sig;
+ ErtsLink *lnk, *nlnk = (ErtsLink *) sig;
ERTS_PROC_SIG_HDBG_PRIV_CHKQ(c_p, &tracing, next_nm_sig);
remove_nm_sig(c_p, sig, next_nm_sig);
- rlnk = erts_link_tree_insert_addr_replace(&ERTS_P_LINKS(c_p),
- lnk);
- if (!rlnk) {
+ lnk = erts_link_tree_lookup_insert(&ERTS_P_LINKS(c_p), nlnk);
+ if (!lnk) {
if (tracing.procs)
- getting_linked(c_p, lnk->other.item);
+ getting_linked(c_p, nlnk->other.item);
}
else {
- if (rlnk->type != ERTS_LNK_TYPE_DIST_PROC)
- erts_link_release(rlnk);
+ /* Already linked or unlinking... */
+ if (nlnk->type != ERTS_LNK_TYPE_DIST_PROC)
+ erts_link_internal_release(nlnk);
else {
- ErtsLinkData *ldp;
- ErtsLink *dlnk = erts_link_to_other(rlnk, &ldp);
+ ErtsELink *elnk;
+ ErtsLink *dlnk = erts_link_to_other(nlnk, &elnk);
if (erts_link_dist_delete(dlnk))
- erts_link_release_both(ldp);
+ erts_link_release_both(&elnk->ld);
else
- erts_link_release(rlnk);
+ erts_link_release(nlnk);
}
}
@@ -3493,52 +4076,102 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
case ERTS_SIG_Q_OP_UNLINK: {
Uint16 type = ERTS_PROC_SIG_TYPE(tag);
- ErtsLinkData *ldp;
ErtsLink *llnk;
ERTS_PROC_SIG_HDBG_PRIV_CHKQ(c_p, &tracing, next_nm_sig);
remove_nm_sig(c_p, sig, next_nm_sig);
if (type == ERTS_SIG_Q_TYPE_DIST_LINK) {
- ErtsSigDistLinkOp *sdlnk = (ErtsSigDistLinkOp *) sig;
- ASSERT(type == ERTS_SIG_Q_TYPE_DIST_LINK);
- ASSERT(is_external_pid(sdlnk->remote));
- llnk = erts_link_tree_lookup(ERTS_P_LINKS(c_p), sdlnk->remote);
+ ErtsSigDistUnlinkOp *sdulnk = (ErtsSigDistUnlinkOp *) sig;
+ ASSERT(is_external_pid(sdulnk->remote));
+ llnk = erts_link_tree_lookup(ERTS_P_LINKS(c_p), sdulnk->remote);
if (llnk) {
- ErtsLink *dlnk = erts_link_to_other(llnk, &ldp);
- erts_link_tree_delete(&ERTS_P_LINKS(c_p), llnk);
- if (erts_link_dist_delete(dlnk))
- erts_link_release_both(ldp);
- else
- erts_link_release(llnk);
- cnt += 8;
- if (tracing.procs)
- getting_unlinked(c_p, sdlnk->remote);
+ ErtsELink *elnk;
+ ErtsLink *dlnk = erts_link_to_other(llnk, &elnk);
+ if (!elnk->unlinking) {
+ erts_link_tree_delete(&ERTS_P_LINKS(c_p), llnk);
+ if (erts_link_dist_delete(dlnk))
+ erts_link_release_both(&elnk->ld);
+ else
+ erts_link_release(llnk);
+ cnt += 8;
+ if (tracing.procs)
+ getting_unlinked(c_p, sdulnk->remote);
+ }
}
- destroy_sig_dist_link_op(sdlnk);
+ reply_dist_unlink_ack(c_p, sdulnk);
cnt++;
}
else {
- ErtsLinkData *ldp;
- ErtsLink *dlnk, *slnk;
- slnk = (ErtsLink *) sig;
- llnk = erts_link_to_other(slnk, &ldp);
- dlnk = erts_link_tree_key_delete(&ERTS_P_LINKS(c_p), llnk);
- if (!dlnk)
- erts_link_release(slnk);
- else {
+ ErtsSigUnlinkOp *sulnk = (ErtsSigUnlinkOp *) sig;
+ llnk = erts_link_tree_lookup(ERTS_P_LINKS(c_p),
+ sulnk->from);
+ if (llnk && !((ErtsILink *) llnk)->unlinking) {
if (tracing.procs)
- getting_unlinked(c_p, llnk->other.item);
- if (dlnk == llnk)
- erts_link_release_both(ldp);
- else {
- erts_link_release(slnk);
- erts_link_release(dlnk);
+ getting_unlinked(c_p, sulnk->from);
+ erts_link_tree_delete(&ERTS_P_LINKS(c_p), llnk);
+ erts_link_release(llnk);
+ cnt += 4;
+ }
+ if (is_internal_pid(sulnk->from))
+ erts_proc_sig_send_unlink_ack(c_p, c_p->common.id, sulnk);
+ else {
+ Port *prt;
+ ASSERT(is_internal_port(sulnk->from));
+ prt = erts_port_lookup(sulnk->from,
+ ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP);
+ if (prt)
+ erts_port_unlink_ack(c_p, prt, sulnk);
+ else
+ erts_proc_sig_destroy_unlink_op(sulnk);
+ }
+ }
+
+ ERTS_PROC_SIG_HDBG_PRIV_CHKQ(c_p, &tracing, next_nm_sig);
+ break;
+ }
+
+ case ERTS_SIG_Q_OP_UNLINK_ACK: {
+ Uint16 type = ERTS_PROC_SIG_TYPE(tag);
+
+ ERTS_PROC_SIG_HDBG_PRIV_CHKQ(c_p, &tracing, next_nm_sig);
+
+ remove_nm_sig(c_p, sig, next_nm_sig);
+ if (type == ERTS_SIG_Q_TYPE_DIST_LINK) {
+ ErtsSigDistUnlinkOp *sdulnk;
+ ErtsLink *lnk;
+ sdulnk = (ErtsSigDistUnlinkOp *) sig;
+ lnk = erts_link_tree_lookup(ERTS_P_LINKS(c_p),
+ sdulnk->remote);
+ if (lnk) {
+ ErtsELink *elnk = erts_link_to_elink(lnk);
+ if (elnk->unlinking == sdulnk->id) {
+ erts_link_tree_delete(&ERTS_P_LINKS(c_p), lnk);
+ if (erts_link_dist_delete(&elnk->ld.dist))
+ erts_link_release_both(&elnk->ld);
+ else
+ erts_link_release(lnk);
+ cnt += 8;
}
}
- cnt += 2;
+ destroy_sig_dist_unlink_op(sdulnk);
}
+ else {
+ ErtsSigUnlinkOp *sulnk;
+ ErtsILink *ilnk;
+
+ sulnk = (ErtsSigUnlinkOp *) sig;
+ ilnk = (ErtsILink *) erts_link_tree_lookup(ERTS_P_LINKS(c_p),
+ sulnk->from);
+ if (ilnk && ilnk->unlinking == sulnk->id) {
+ erts_link_tree_delete(&ERTS_P_LINKS(c_p), &ilnk->link);
+ erts_link_internal_release(&ilnk->link);
+ cnt += 4;
+ }
+ erts_proc_sig_destroy_unlink_op(sulnk);
+ }
+
ERTS_PROC_SIG_HDBG_PRIV_CHKQ(c_p, &tracing, next_nm_sig);
break;
}
@@ -3591,6 +4224,13 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
break;
}
+
+ case ERTS_SIG_Q_OP_DIST_SPAWN_REPLY: {
+ ERTS_PROC_SIG_HDBG_PRIV_CHKQ(c_p, &tracing, next_nm_sig);
+ cnt += handle_dist_spawn_reply(c_p, &tracing, sig, next_nm_sig);
+ ERTS_PROC_SIG_HDBG_PRIV_CHKQ(c_p, &tracing, next_nm_sig);
+ break;
+ }
default:
ERTS_INTERNAL_ERROR("Unknown signal");
@@ -3793,7 +4433,9 @@ stretch_limit(Process *c_p, ErtsSigRecvTracing *tp,
int
-erts_proc_sig_handle_exit(Process *c_p, Sint *redsp)
+erts_proc_sig_handle_exit(Process *c_p, Sint *redsp,
+ ErtsMonitor **pend_spawn_mon_pp,
+ Eterm reason)
{
int cnt;
Sint limit;
@@ -3875,7 +4517,8 @@ erts_proc_sig_handle_exit(Process *c_p, Sint *redsp)
break;
case ERTS_SIG_Q_OP_MONITOR: {
- ErtsProcExitContext pectxt = {c_p, am_noproc, NULL, NULL, NIL};
+ ErtsProcExitContext pectxt = {c_p, am_noproc, NULL, NULL,
+ NULL, NULL, NIL, 0};
erts_proc_exit_handle_monitor((ErtsMonitor *) sig,
(void *) &pectxt, -1);
cnt += 4;
@@ -3897,9 +4540,24 @@ erts_proc_sig_handle_exit(Process *c_p, Sint *redsp)
case ERTS_SIG_Q_OP_UNLINK:
if (type == ERTS_SIG_Q_TYPE_DIST_LINK)
- destroy_sig_dist_link_op((ErtsSigDistLinkOp *) sig);
- else
- erts_link_release((ErtsLink *) sig);
+ reply_dist_unlink_ack(c_p, (ErtsSigDistUnlinkOp *) sig);
+ else if (is_internal_pid(((ErtsSigUnlinkOp *) sig)->from))
+ erts_proc_sig_send_unlink_ack(c_p, c_p->common.id,
+ (ErtsSigUnlinkOp *) sig);
+ else {
+ Port *prt;
+ ASSERT(is_internal_port(((ErtsSigUnlinkOp *) sig)->from));
+ prt = erts_port_lookup(((ErtsSigUnlinkOp *) sig)->from,
+ ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP);
+ if (prt)
+ erts_port_unlink_ack(c_p, prt, (ErtsSigUnlinkOp *) sig);
+ else
+ erts_proc_sig_destroy_unlink_op((ErtsSigUnlinkOp *) sig);
+ }
+ break;
+
+ case ERTS_SIG_Q_OP_UNLINK_ACK:
+ erts_proc_sig_destroy_unlink_op((ErtsSigUnlinkOp *) sig);
break;
case ERTS_SIG_Q_OP_GROUP_LEADER: {
@@ -3927,6 +4585,13 @@ erts_proc_sig_handle_exit(Process *c_p, Sint *redsp)
break;
}
+ case ERTS_SIG_Q_OP_DIST_SPAWN_REPLY: {
+ cnt += handle_dist_spawn_reply_exiting(c_p, sig,
+ pend_spawn_mon_pp,
+ reason);
+ break;
+ }
+
case ERTS_SIG_Q_OP_TRACE_CHANGE_STATE:
destroy_trace_info((ErtsSigTraceInfo *) sig);
break;
@@ -3999,6 +4664,7 @@ clear_seq_trace_token(ErtsMessage *sig)
break;
case ERTS_SIG_Q_OP_PERSISTENT_MON_MSG:
+ case ERTS_SIG_Q_OP_DIST_SPAWN_REPLY:
ERTS_CLEAR_SEQ_TOKEN(sig);
break;
@@ -4006,6 +4672,7 @@ clear_seq_trace_token(ErtsMessage *sig)
case ERTS_SIG_Q_OP_DEMONITOR:
case ERTS_SIG_Q_OP_LINK:
case ERTS_SIG_Q_OP_UNLINK:
+ case ERTS_SIG_Q_OP_UNLINK_ACK:
case ERTS_SIG_Q_OP_TRACE_CHANGE_STATE:
case ERTS_SIG_Q_OP_GROUP_LEADER:
case ERTS_SIG_Q_OP_IS_ALIVE:
@@ -4073,6 +4740,7 @@ erts_proc_sig_signal_size(ErtsSignal *sig)
case ERTS_SIG_Q_OP_SYNC_SUSPEND:
case ERTS_SIG_Q_OP_PERSISTENT_MON_MSG:
case ERTS_SIG_Q_OP_IS_ALIVE:
+ case ERTS_SIG_Q_OP_DIST_SPAWN_REPLY:
size = ((ErtsMessage *) sig)->hfrag.alloc_size;
size *= sizeof(Eterm);
size += sizeof(ErtsMessage) - sizeof(Eterm);
@@ -4093,15 +4761,16 @@ erts_proc_sig_signal_size(ErtsSignal *sig)
break;
case ERTS_SIG_Q_OP_UNLINK:
- if (type == ERTS_SIG_Q_TYPE_DIST_LINK) {
- size = NC_HEAP_SIZE(((ErtsSigDistLinkOp *) sig)->remote);
+ case ERTS_SIG_Q_OP_UNLINK_ACK:
+ if (type != ERTS_SIG_Q_TYPE_DIST_LINK)
+ size = sizeof(ErtsSigUnlinkOp);
+ else {
+ size = NC_HEAP_SIZE(((ErtsSigDistUnlinkOp *) sig)->remote);
size--;
size *= sizeof(Eterm);
- size += sizeof(ErtsSigDistLinkOp);
- break;
+ size += sizeof(ErtsSigDistUnlinkOp);
}
- /* Fall through... */
-
+ break;
case ERTS_SIG_Q_OP_LINK:
size = erts_link_size((ErtsLink *) sig);
break;
@@ -4342,6 +5011,13 @@ getting_linked(Process *c_p, Eterm linker)
am_getting_linked, linker);
}
+static void
+linking(Process *c_p, Eterm to)
+{
+ trace_proc(c_p, ERTS_PROC_LOCK_MAIN, c_p,
+ am_link, to);
+}
+
static ERTS_INLINE void
handle_message_enqueued_tracing(Process *c_p,
ErtsSigRecvTracing *tracing,
@@ -4535,7 +5211,7 @@ move_msg_to_heap(Process *c_p, ErtsMessage *mp)
&& mp->data.attached
&& mp->data.attached != ERTS_MSG_COMBINED_HFRAG) {
ErlHeapFragment *bp;
-
+
bp = erts_message_to_heap_frag(mp);
if (bp->next)
@@ -4747,12 +5423,14 @@ erts_proc_sig_debug_foreach_sig(Process *c_p,
case ERTS_SIG_Q_OP_UNLINK:
if (type == ERTS_SIG_Q_TYPE_DIST_LINK) {
- debug_foreach_sig_fake_oh(((ErtsSigDistLinkOp *) sig)->remote,
+ debug_foreach_sig_fake_oh(((ErtsSigDistUnlinkOp *) sig)->remote,
oh_func, arg);
- break;
}
- /* Fall through... */
-
+ break;
+
+ case ERTS_SIG_Q_OP_UNLINK_ACK:
+ break;
+
case ERTS_SIG_Q_OP_LINK:
lnk_func((ErtsLink *) sig, arg, -1);
break;
diff --git a/erts/emulator/beam/erl_proc_sig_queue.h b/erts/emulator/beam/erl_proc_sig_queue.h
index 2c2958fb39..bbff433711 100644
--- a/erts/emulator/beam/erl_proc_sig_queue.h
+++ b/erts/emulator/beam/erl_proc_sig_queue.h
@@ -30,6 +30,7 @@
* - Persistent monitor message
* - Link
* - Unlink
+ * - Unlink Ack
* - Group leader
* - Is process alive
* - Process info request
@@ -100,6 +101,12 @@ typedef struct {
Eterm tag;
} ErtsSignalCommon;
+typedef struct {
+ ErtsSignalCommon common;
+ Eterm from;
+ Uint64 id;
+} ErtsSigUnlinkOp;
+
#define ERTS_SIG_HANDLE_REDS_MAX_PREFERED (CONTEXT_REDS/40)
#ifdef ERTS_PROC_SIG_HARD_DEBUG
@@ -295,19 +302,97 @@ erts_proc_sig_send_link(Process *c_p, Eterm to, ErtsLink *lnk);
/**
*
+ * @brief Create a new unlink identifier
+ *
+ * The newly created unlink identifier is to be used in an
+ * unlink operation.
+ *
+ * @param[in] c_p Pointer to process struct of
+ * currently executing process.
+ *
+ * @return A new 64-bit unlink identifier
+ * unique in context of the
+ * calling process. The identifier
+ * may be any value but zero.
+ */
+ERTS_GLB_INLINE Uint64 erts_proc_sig_new_unlink_id(Process *c_p);
+
+/**
+ *
+ * @brief Create an unlink op signal structure
+ *
+ * The structure will contain a newly created unlink
+ * identifier to be used in the operation.
+ *
+ * @param[in] c_p Pointer to process struct of
+ * currently executing process
+ * ('from' is a process
+ * identifier), or NULL if not
+ * called in the context of an
+ * executing process ('from' is
+ * a port identifier).
+ *
+ * @param[in] from Id (as an erlang term) of
+ * entity sending the unlink
+ * signal.
+ *
+ * @return A pointer to the unlink op
+ * structure.
+ */
+ErtsSigUnlinkOp *
+erts_proc_sig_make_unlink_op(Process *c_p, Eterm from);
+
+/**
+ *
+ * @brief Destroy an unlink op signal structure
+ *
+ * @param[in] sulnk A pointer to the unlink op
+ * structure.
+ */
+void
+erts_proc_sig_destroy_unlink_op(ErtsSigUnlinkOp *sulnk);
+
+/**
+ *
* @brief Send an unlink signal to a process.
*
*
* @param[in] c_p Pointer to process struct of
* currently executing process.
*
+ * @param[in] from Id (as an erlang term) of
+ * entity sending the unlink
+ * signal.
+ *
* @param[in] lnk Pointer to link structure from
* the sending side. It should
* contain information about
* receiver.
*/
+Uint64
+erts_proc_sig_send_unlink(Process *c_p, Eterm from, ErtsLink *lnk);
+
+/**
+ *
+ * @brief Send an unlink acknowledgment signal to a process.
+ *
+ *
+ * @param[in] c_p Pointer to process struct of
+ * currently executing process.
+ *
+ * @param[in] from Id (as an erlang term) of
+ * entity sending the unlink
+ * signal.
+ *
+ * @param[in] sulnk A pointer to the unlink op
+ * structure. This structure
+ * was typically received by
+ * the caller in an unlink
+ * signal.
+ */
void
-erts_proc_sig_send_unlink(Process *c_p, ErtsLink *lnk);
+erts_proc_sig_send_unlink_ack(Process *c_p, Eterm from,
+ ErtsSigUnlinkOp *sulnk);
/**
*
@@ -343,11 +428,35 @@ erts_proc_sig_send_dist_link_exit(struct dist_entry_ *dep,
/**
*
- * @brief Send an unlink signal to a process.
+ * @brief Send an unlink signal to a local process.
*
* This function is used instead of erts_proc_sig_send_unlink()
- * when the signal arrives via the distribution and
- * therefore no link structure is available.
+ * when the signal arrives via the distribution.
+ *
+ * @param[in] dep Distribution entry of channel
+ * that the signal arrived on.
+ *
+ * @param[in] from Identifier of sender.
+ *
+ * @param[in] to Identifier of receiver.
+ *
+ * @param[in] id Identifier of unlink operation.
+ */
+void
+erts_proc_sig_send_dist_unlink(DistEntry *dep, Uint32 conn_id,
+ Eterm from, Eterm to, Uint64 id);
+
+/**
+ *
+ * @brief Send an unlink acknowledgment signal to a local process.
+ *
+ * This function is used instead of erts_proc_sig_send_unlink_ack()
+ * when the signal arrives via the distribution.
+ *
+ * @param[in] c_p Pointer to process struct of
+ * currently executing process or
+ * NULL if not called in the context
+ * of an executing process.
*
* @param[in] dep Distribution entry of channel
* that the signal arrived on.
@@ -356,10 +465,12 @@ erts_proc_sig_send_dist_link_exit(struct dist_entry_ *dep,
*
* @param[in] to Identifier of receiver.
*
+ * @param[in] id Identifier of unlink operation.
*/
void
-erts_proc_sig_send_dist_unlink(struct dist_entry_ *dep,
- Eterm from, Eterm to);
+erts_proc_sig_send_dist_unlink_ack(Process *c_p, DistEntry *dep,
+ Uint32 conn_id, Eterm from, Eterm to,
+ Uint64 id);
/**
*
@@ -706,6 +817,14 @@ erts_proc_sig_send_rpc_request(Process *c_p,
Eterm (*func)(Process *, void *, int *, ErlHeapFragment **),
void *arg);
+int
+erts_proc_sig_send_dist_spawn_reply(Eterm node,
+ Eterm ref,
+ Eterm to,
+ ErtsLink *lnk,
+ Eterm result,
+ Eterm token);
+
/*
* End of send operations of currently supported process signals.
*/
@@ -787,7 +906,9 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
* queue.
*/
int
-erts_proc_sig_handle_exit(Process *c_p, Sint *redsp);
+erts_proc_sig_handle_exit(Process *c_p, Sint *redsp,
+ ErtsMonitor **pend_spawn_mon_pp,
+ Eterm reason);
/**
*
@@ -1059,6 +1180,18 @@ Sint erts_proc_sig_fetch_msgq_len_offs__(Process *proc);
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
+ERTS_GLB_INLINE Uint64
+erts_proc_sig_new_unlink_id(Process *c_p)
+{
+ Uint64 id;
+ ASSERT(c_p);
+
+ id = (Uint64) c_p->uniq++;
+ if (id == 0)
+ id = (Uint64) c_p->uniq++;
+ return id;
+}
+
ERTS_GLB_INLINE Sint
erts_proc_sig_fetch(Process *proc)
{
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index 406336fea7..55d0690d0d 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -44,7 +44,6 @@
#include "erl_thr_queue.h"
#include "erl_async.h"
#include "dtrace-wrapper.h"
-#include "lttng-wrapper.h"
#include "erl_ptab.h"
#include "erl_bif_unique.h"
#define ERTS_WANT_TIMER_WHEEL_API
@@ -181,7 +180,6 @@ sched_get_busy_wait_params(ErtsSchedulerData *esdp)
}
static ErtsAuxWorkData *aux_thread_aux_work_data;
-static ErtsAuxWorkData *poll_thread_aux_work_data;
#define ERTS_SCHDLR_SSPND_CHNG_NMSB (((erts_aint32_t) 1) << 0)
#define ERTS_SCHDLR_SSPND_CHNG_MSB (((erts_aint32_t) 1) << 1)
@@ -410,7 +408,21 @@ typedef union {
static ErtsAlignedSchedulerSleepInfo *aligned_sched_sleep_info;
static ErtsAlignedSchedulerSleepInfo *aligned_dirty_cpu_sched_sleep_info;
static ErtsAlignedSchedulerSleepInfo *aligned_dirty_io_sched_sleep_info;
-static ErtsAlignedSchedulerSleepInfo *aligned_poll_thread_sleep_info;
+
+typedef struct {
+ erts_mtx_t mtx;
+ erts_cnd_t cnd;
+ int blocked;
+ int id;
+} ErtsBlockPollThreadData;
+
+typedef union {
+ ErtsBlockPollThreadData block_data;
+ char align[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(ErtsBlockPollThreadData))];
+} ErtsAlignedBlockPollThreadData;
+
+
+static ErtsAlignedBlockPollThreadData *ERTS_WRITE_UNLIKELY(block_poll_thread_data);
static Uint last_reductions;
static Uint last_exact_reductions;
@@ -479,10 +491,6 @@ ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(proclist,
200,
ERTS_ALC_T_PROC_LIST)
-#define ERTS_POLL_THREAD_SLEEP_INFO_IX(IX) \
- (ASSERT(0 <= ((int) (IX)) \
- && ((int) (IX)) < ((int) erts_no_poll_threads)), \
- &aligned_poll_thread_sleep_info[(IX)].ssi)
#define ERTS_SCHED_SLEEP_INFO_IX(IX) \
(ASSERT(((int)-1) <= ((int) (IX)) \
&& ((int) (IX)) < ((int) erts_no_schedulers)), \
@@ -708,10 +716,10 @@ erts_pre_init_process(void)
erts_psd_required_locks[ERTS_PSD_DELAYED_GC_TASK_QS].set_locks
= ERTS_PSD_DELAYED_GC_TASK_QS_SET_LOCKS;
- erts_psd_required_locks[ERTS_PSD_NIF_TRAP_EXPORT].get_locks
- = ERTS_PSD_NIF_TRAP_EXPORT_GET_LOCKS;
- erts_psd_required_locks[ERTS_PSD_NIF_TRAP_EXPORT].set_locks
- = ERTS_PSD_NIF_TRAP_EXPORT_SET_LOCKS;
+ erts_psd_required_locks[ERTS_PSD_NFUNC_TRAP_WRAPPER].get_locks
+ = ERTS_PSD_NFUNC_TRAP_WRAPPER_GET_LOCKS;
+ erts_psd_required_locks[ERTS_PSD_NFUNC_TRAP_WRAPPER].set_locks
+ = ERTS_PSD_NFUNC_TRAP_WRAPPER_SET_LOCKS;
erts_psd_required_locks[ERTS_PSD_ETS_OWNED_TABLES].get_locks
= ERTS_PSD_ETS_OWNED_TABLES_GET_LOCKS;
@@ -1626,6 +1634,11 @@ unset_aux_work_flags(ErtsSchedulerSleepInfo *ssi, erts_aint32_t flgs)
return erts_atomic32_read_band_nob(&ssi->aux_work, ~flgs);
}
+static ERTS_INLINE erts_aint32_t
+unset_aux_work_flags_mb(ErtsSchedulerSleepInfo *ssi, erts_aint32_t flgs)
+{
+ return erts_atomic32_read_band_mb(&ssi->aux_work, ~flgs);
+}
static ERTS_INLINE void
haw_chk_later_cleanup_op_wakeup(ErtsAuxWorkData *awdp, ErtsThrPrgrVal val)
@@ -1721,9 +1734,7 @@ handle_delayed_aux_work_wakeup(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, in
if (!waiting && awdp->delayed_wakeup.next > awdp->esdp->reductions)
return aux_work;
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_DELAYED_AW_WAKEUP);
-
- ERTS_THR_MEMORY_BARRIER;
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_DELAYED_AW_WAKEUP);
max_jix = awdp->delayed_wakeup.jix;
awdp->delayed_wakeup.jix = -1;
@@ -1831,7 +1842,9 @@ misc_aux_work_clean(ErtsThrQ_t *q,
return aux_work | ERTS_SSI_AUX_WORK_MISC;
case ERTS_THR_Q_NEED_THR_PRGR:
set_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_MISC_THR_PRGR);
- haw_thr_prgr_soft_wakeup(awdp, erts_thr_q_need_thr_progress(q));
+ awdp->misc.thr_prgr = erts_thr_q_need_thr_progress(q);
+ haw_thr_prgr_soft_wakeup(awdp, awdp->misc.thr_prgr);
+ break;
case ERTS_THR_Q_CLEAN:
break;
}
@@ -1845,7 +1858,7 @@ handle_misc_aux_work(ErtsAuxWorkData *awdp,
{
ErtsThrQ_t *q = &misc_aux_work_queues[awdp->sched_id].q;
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_MISC);
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_MISC);
while (1) {
erts_misc_aux_work_t *mawp = erts_thr_q_dequeue(q);
if (!mawp)
@@ -1947,7 +1960,7 @@ handle_async_ready(ErtsAuxWorkData *awdp,
ASSERT(!awdp->esdp || !ERTS_SCHEDULER_IS_DIRTY(awdp->esdp));
- unset_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_ASYNC_READY);
+ unset_aux_work_flags_mb(ssi, ERTS_SSI_AUX_WORK_ASYNC_READY);
if (erts_check_async_ready(awdp->async_ready.queue)) {
if (set_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_ASYNC_READY)
& ERTS_SSI_AUX_WORK_ASYNC_READY_CLEAN) {
@@ -2003,8 +2016,8 @@ handle_fix_alloc(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
ASSERT(!awdp->esdp || !ERTS_SCHEDULER_IS_DIRTY(awdp->esdp));
- unset_aux_work_flags(ssi, (ERTS_SSI_AUX_WORK_FIX_ALLOC_LOWER_LIM
- | ERTS_SSI_AUX_WORK_FIX_ALLOC_DEALLOC));
+ unset_aux_work_flags_mb(ssi, (ERTS_SSI_AUX_WORK_FIX_ALLOC_LOWER_LIM
+ | ERTS_SSI_AUX_WORK_FIX_ALLOC_DEALLOC));
aux_work &= ~(ERTS_SSI_AUX_WORK_FIX_ALLOC_LOWER_LIM
| ERTS_SSI_AUX_WORK_FIX_ALLOC_DEALLOC);
res = erts_alloc_fix_alloc_shrink(awdp->sched_id, aux_work);
@@ -2052,7 +2065,7 @@ handle_delayed_dealloc(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waitin
ASSERT(!awdp->esdp || !ERTS_SCHEDULER_IS_DIRTY(awdp->esdp));
- unset_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_DD);
+ unset_aux_work_flags_mb(ssi, ERTS_SSI_AUX_WORK_DD);
ERTS_MSACC_SET_STATE_CACHED_M_X(ERTS_MSACC_STATE_ALLOC);
erts_alloc_scheduler_handle_delayed_dealloc((void *) awdp->esdp,
&need_thr_progress,
@@ -2148,7 +2161,7 @@ handle_canceled_timers(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waitin
ASSERT(!awdp->esdp || !ERTS_SCHEDULER_IS_DIRTY(awdp->esdp));
- unset_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_CNCLD_TMRS);
+ unset_aux_work_flags_mb(ssi, ERTS_SSI_AUX_WORK_CNCLD_TMRS);
erts_handle_canceled_timers((void *) awdp->esdp,
&need_thr_progress,
&wakeup,
@@ -2318,7 +2331,7 @@ handle_debug_wait_completed(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int w
awdp->debug.wait_completed.callback = NULL;
awdp->debug.wait_completed.arg = NULL;
- unset_aux_work_flags(ssi, ERTS_SSI_AUX_WORK_DEBUG_WAIT_COMPLETED);
+ unset_aux_work_flags_mb(ssi, ERTS_SSI_AUX_WORK_DEBUG_WAIT_COMPLETED);
return aux_work & ~ERTS_SSI_AUX_WORK_DEBUG_WAIT_COMPLETED;
}
@@ -2454,7 +2467,7 @@ int erts_halt_code;
static ERTS_INLINE erts_aint32_t
handle_reap_ports(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
{
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_REAP_PORTS);
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_REAP_PORTS);
ERTS_RUNQ_FLGS_SET(awdp->esdp->run_queue, ERTS_RUNQ_FLG_HALTING);
if (erts_atomic32_dec_read_acqb(&erts_halt_progress) == 0) {
@@ -2549,7 +2562,7 @@ handle_yield(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
static ERTS_INLINE erts_aint32_t
handle_mseg_cache_check(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
{
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_MSEG_CACHE_CHECK);
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_MSEG_CACHE_CHECK);
erts_mseg_cache_check();
return aux_work & ~ERTS_SSI_AUX_WORK_MSEG_CACHE_CHECK;
}
@@ -2560,7 +2573,7 @@ handle_mseg_cache_check(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiti
static ERTS_INLINE erts_aint32_t
handle_setup_aux_work_timer(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
{
- unset_aux_work_flags(awdp->ssi, ERTS_SSI_AUX_WORK_SET_TMO);
+ unset_aux_work_flags_mb(awdp->ssi, ERTS_SSI_AUX_WORK_SET_TMO);
setup_aux_work_timer(awdp->esdp);
return aux_work & ~ERTS_SSI_AUX_WORK_SET_TMO;
}
@@ -2999,8 +3012,10 @@ sched_set_sleeptype(ErtsSchedulerSleepInfo *ssi, erts_aint32_t sleep_type)
erts_aint32_t nflgs = ERTS_SSI_FLG_SLEEPING|ERTS_SSI_FLG_WAITING|sleep_type;
erts_aint32_t xflgs = ERTS_SSI_FLG_SLEEPING|ERTS_SSI_FLG_WAITING;
- if (sleep_type == ERTS_SSI_FLG_TSE_SLEEPING)
+ if (sleep_type == ERTS_SSI_FLG_TSE_SLEEPING) {
+ erts_tse_use(ssi->event);
erts_tse_reset(ssi->event);
+ }
else {
ASSERT(sleep_type == ERTS_SSI_FLG_POLL_SLEEPING);
erts_check_io_interrupt(ssi->psi, 0);
@@ -3044,6 +3059,7 @@ thr_prgr_wait(void *vssi)
ErtsSchedulerSleepInfo *ssi = (ErtsSchedulerSleepInfo *) vssi;
erts_aint32_t xflgs = ERTS_SSI_FLG_SLEEPING;
+ erts_tse_use(ssi->event);
erts_tse_reset(ssi->event);
while (1) {
@@ -3058,6 +3074,7 @@ thr_prgr_wait(void *vssi)
break;
xflgs = aflgs;
}
+ erts_tse_return(ssi->event);
}
static void
@@ -3097,6 +3114,7 @@ aux_thread(void *unused)
erts_port_task_pre_alloc_init_thread();
ssi->event = erts_tse_fetch();
+ erts_tse_return(ssi->event);
erts_msacc_init_thread("aux", 1, 1);
@@ -3106,7 +3124,7 @@ aux_thread(void *unused)
callbacks.wait = thr_prgr_wait;
callbacks.finalize_wait = thr_prgr_fin_wait;
- tpd = erts_thr_progress_register_managed_thread(NULL, &callbacks, 1);
+ tpd = erts_thr_progress_register_managed_thread(NULL, &callbacks, 1, 0);
init_aux_work_data(awdp, NULL, NULL);
awdp->ssi = ssi;
@@ -3155,7 +3173,7 @@ aux_thread(void *unused)
if (flgs & ERTS_SSI_FLG_SLEEPING) {
ASSERT(flgs & ERTS_SSI_FLG_POLL_SLEEPING);
ASSERT(flgs & ERTS_SSI_FLG_WAITING);
- erts_check_io(ssi->psi, ERTS_POLL_INF_TIMEOUT);
+ erts_check_io(ssi->psi, ERTS_POLL_INF_TIMEOUT, 0);
}
}
#else
@@ -3175,6 +3193,7 @@ aux_thread(void *unused)
} while (res == EINTR);
ERTS_MSACC_SET_STATE_CACHED(ERTS_MSACC_STATE_OTHER);
}
+ erts_tse_return(ssi->event);
}
erts_thr_progress_finalize_wait(tpd);
#endif
@@ -3185,15 +3204,46 @@ aux_thread(void *unused)
return NULL;
}
+static void
+pt_wake(void *vbpt)
+{
+ ErtsBlockPollThreadData *bpt = (ErtsBlockPollThreadData *) vbpt;
+ erts_mtx_lock(&bpt->mtx);
+ bpt->blocked = 0;
+ erts_cnd_signal(&bpt->cnd);
+ erts_mtx_unlock(&bpt->mtx);
+}
+
+static void
+pt_wait(void *vbpt)
+{
+ ErtsBlockPollThreadData *bpt = (ErtsBlockPollThreadData *) vbpt;
+ erts_mtx_lock(&bpt->mtx);
+ while (bpt->blocked)
+ erts_cnd_wait(&bpt->cnd, &bpt->mtx);
+ erts_mtx_unlock(&bpt->mtx);
+}
+
+static void
+pt_prep_wait(void *vbpt)
+{
+ ErtsBlockPollThreadData *bpt = (ErtsBlockPollThreadData *) vbpt;
+ erts_mtx_lock(&bpt->mtx);
+ bpt->blocked = !0;
+ erts_mtx_unlock(&bpt->mtx);
+}
+
+static void
+pt_fin_wait(void *vbpt)
+{
+
+}
+
static void *
-poll_thread(void *arg)
+poll_thread(void *vbpt)
{
- int id = (int)(UWord)arg;
- ErtsAuxWorkData *awdp = poll_thread_aux_work_data+id;
- ErtsSchedulerSleepInfo *ssi = ERTS_POLL_THREAD_SLEEP_INFO_IX(id);
- erts_aint32_t aux_work;
+ ErtsBlockPollThreadData *bpt = (ErtsBlockPollThreadData *) vbpt;
ErtsThrPrgrCallbacks callbacks;
- int thr_prgr_active = 1;
struct erts_poll_thread *psi;
ErtsThrPrgrData *tpd;
ERTS_MSACC_DECLARE_CACHE();
@@ -3206,59 +3256,24 @@ poll_thread(void *arg)
#endif
erts_port_task_pre_alloc_init_thread();
- ssi->event = erts_tse_fetch();
- erts_msacc_init_thread("poll", id, 0);
+ erts_msacc_init_thread("poll", bpt->id, 0);
- callbacks.arg = (void *) ssi;
- callbacks.wakeup = thr_prgr_wakeup;
- callbacks.prepare_wait = thr_prgr_prep_wait;
- callbacks.wait = thr_prgr_wait;
- callbacks.finalize_wait = thr_prgr_fin_wait;
-
- tpd = erts_thr_progress_register_managed_thread(NULL, &callbacks, 0);
- init_aux_work_data(awdp, NULL, NULL);
- awdp->ssi = ssi;
-
- psi = erts_create_pollset_thread(id, tpd);
+ callbacks.arg = vbpt;
+ callbacks.wakeup = pt_wake;
+ callbacks.prepare_wait = pt_prep_wait;
+ callbacks.wait = pt_wait;
+ callbacks.finalize_wait = pt_fin_wait;
- ssi->psi = psi;
+ tpd = erts_thr_progress_register_managed_thread(NULL, &callbacks, 0, !0);
- sched_prep_spin_wait(ssi);
+ psi = erts_create_pollset_thread(bpt->id, tpd);
ERTS_MSACC_SET_STATE_CACHED(ERTS_MSACC_STATE_OTHER);
while (1) {
- erts_aint32_t flgs;
-
- aux_work = erts_atomic32_read_acqb(&ssi->aux_work);
- if (aux_work) {
- if (!thr_prgr_active)
- erts_thr_progress_active(tpd, thr_prgr_active = 1);
- aux_work = handle_aux_work(awdp, aux_work, 1);
- ERTS_MSACC_UPDATE_CACHE();
- if (aux_work && erts_thr_progress_update(tpd))
- erts_thr_progress_leader_update(tpd);
- }
-
- if (!aux_work) {
- if (thr_prgr_active)
- erts_thr_progress_active(tpd, thr_prgr_active = 0);
-
- flgs = sched_spin_wait(ssi, 0);
-
- if (flgs & ERTS_SSI_FLG_SLEEPING) {
- ASSERT(flgs & ERTS_SSI_FLG_WAITING);
- flgs = sched_set_sleeptype(ssi, ERTS_SSI_FLG_POLL_SLEEPING);
- if (flgs & ERTS_SSI_FLG_SLEEPING) {
- ASSERT(flgs & ERTS_SSI_FLG_POLL_SLEEPING);
- ASSERT(flgs & ERTS_SSI_FLG_WAITING);
- erts_check_io(psi, ERTS_POLL_INF_TIMEOUT);
- }
- }
- }
-
- flgs = sched_prep_spin_wait(ssi);
+ erts_check_io_interrupt(psi, 0);
+ erts_check_io(psi, ERTS_POLL_INF_TIMEOUT, !0);
}
return NULL;
}
@@ -3446,7 +3461,7 @@ scheduler_wait(int *fcalls, ErtsSchedulerData *esdp, ErtsRunQueue *rq)
if (flgs & ERTS_SSI_FLG_SLEEPING) {
ASSERT(flgs & ERTS_SSI_FLG_POLL_SLEEPING);
ASSERT(flgs & ERTS_SSI_FLG_WAITING);
- erts_check_io(ssi->psi, timeout_time);
+ erts_check_io(ssi->psi, timeout_time, 0);
current_time = erts_get_monotonic_time(esdp);
}
}
@@ -3487,6 +3502,7 @@ scheduler_wait(int *fcalls, ErtsSchedulerData *esdp, ErtsRunQueue *rq)
erts_get_monotonic_time(esdp);
} while (res == EINTR);
}
+ erts_tse_return(ssi->event);
}
if (!ERTS_SCHEDULER_IS_DIRTY(esdp))
erts_thr_progress_finalize_wait(erts_thr_prgr_data(esdp));
@@ -6034,18 +6050,6 @@ erts_init_scheduling(int no_schedulers, int no_schedulers_online, int no_poll_th
erts_atomic32_init_nob(&ssi->aux_work, 0);
}
- aligned_poll_thread_sleep_info =
- erts_alloc_permanent_cache_aligned(
- ERTS_ALC_T_SCHDLR_SLP_INFO,
- no_poll_threads*sizeof(ErtsAlignedSchedulerSleepInfo));
- for (ix = 0; ix < no_poll_threads; ix++) {
- ErtsSchedulerSleepInfo *ssi = &aligned_poll_thread_sleep_info[ix].ssi;
- ssi->esdp = NULL;
- erts_atomic32_init_nob(&ssi->flags, 0);
- ssi->event = NULL; /* initialized in poll_thread */
- erts_atomic32_init_nob(&ssi->aux_work, 0);
- }
-
/* Create and initialize scheduler specific data */
daww_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE((sizeof(ErtsDelayedAuxWorkWakeupJob)
@@ -6106,10 +6110,6 @@ erts_init_scheduling(int no_schedulers, int no_schedulers_online, int no_poll_th
erts_alloc_permanent_cache_aligned(ERTS_ALC_T_SCHDLR_DATA,
sizeof(ErtsAuxWorkData));
- poll_thread_aux_work_data =
- erts_alloc_permanent_cache_aligned(ERTS_ALC_T_SCHDLR_DATA,
- no_poll_threads * sizeof(ErtsAuxWorkData));
-
init_no_runqs(no_schedulers_online, no_schedulers_online);
balance_info.last_active_runqs = no_schedulers;
erts_mtx_init(&balance_info.update_mtx, "migration_info_update", NIL,
@@ -6378,7 +6378,7 @@ check_enqueue_in_prio_queue(Process *c_p,
{
erts_aint32_t aprio, qbit, max_qbit;
- aprio = (actual >> ERTS_PSFLGS_ACT_PRIO_OFFSET) & ERTS_PSFLGS_PRIO_MASK;
+ aprio = ((*newp) >> ERTS_PSFLGS_ACT_PRIO_OFFSET) & ERTS_PSFLGS_PRIO_MASK;
qbit = 1 << aprio;
*prq_prio_p = aprio;
@@ -6495,8 +6495,8 @@ schedule_out_process(ErtsRunQueue *c_rq, erts_aint32_t state, Process *p,
ASSERT(!(state & (ERTS_PSFLG_DIRTY_IO_PROC
|ERTS_PSFLG_DIRTY_CPU_PROC))
- || (BeamIsOpCode(*p->i, op_call_nif)
- || BeamIsOpCode(*p->i, op_apply_bif)));
+ || (BeamIsOpCode(*p->i, op_call_nif_WWW)
+ || BeamIsOpCode(*p->i, op_call_bif_W)));
a = state;
@@ -6837,6 +6837,12 @@ active_sys_enqueue(Process *p, ErtsProcSysTask *sys_task,
enqueue = ERTS_ENQUEUE_NOT;
n |= enable_flags;
+ if (sys_task && ERTS_PSFLGS_GET_ACT_PRIO(a) > task_prio) {
+ /* elevate prio for system task... */
+ n &= ~ERTS_PSFLGS_ACT_PRIO_MASK;
+ n |= (task_prio << ERTS_PSFLGS_ACT_PRIO_OFFSET);
+ }
+
if (!(a & (ERTS_PSFLG_RUNNING
| ERTS_PSFLG_RUNNING_SYS
| ERTS_PSFLG_DIRTY_RUNNING
@@ -6930,40 +6936,12 @@ static int
schedule_process_sys_task(Process *p, erts_aint32_t prio, ErtsProcSysTask *st,
erts_aint32_t *fail_state_p)
{
- erts_aint32_t fail_state, state;
-
- fail_state = *fail_state_p;
-
- /* Elevate priority if needed. */
- state = erts_atomic32_read_acqb(&p->state);
- if (ERTS_PSFLGS_GET_ACT_PRIO(state) <= prio) {
- if (state & fail_state) {
- *fail_state_p = state & fail_state;
- return 0;
- }
- }
- else {
- erts_aint32_t n, a, e;
-
- a = state;
- do {
- if (a & fail_state) {
- *fail_state_p = a & fail_state;
- return 0;
- }
- if (ERTS_PSFLGS_GET_ACT_PRIO(a) <= prio) {
- n = a;
- break;
- }
- n = e = a;
- n &= ~ERTS_PSFLGS_ACT_PRIO_MASK;
- n |= (prio << ERTS_PSFLGS_ACT_PRIO_OFFSET);
- a = erts_atomic32_cmpxchg_nob(&p->state, n, e);
- } while (a != e);
-
- state = n;
+ erts_aint32_t fail_state = *fail_state_p;
+ erts_aint32_t state = erts_atomic32_read_acqb(&p->state);
+ if (state & fail_state) {
+ *fail_state_p = state & fail_state;
+ return 0;
}
-
return !(active_sys_enqueue(p, st, prio, ERTS_PSFLG_SYS_TASKS,
state, fail_state_p) & fail_state);
}
@@ -7157,6 +7135,7 @@ sched_set_suspended_sleeptype(ErtsSchedulerSleepInfo *ssi,
| ERTS_SSI_FLG_SUSPENDED);
ASSERT(sleep_type == ERTS_SSI_FLG_TSE_SLEEPING);
+ erts_tse_use(ssi->event);
erts_tse_reset(ssi->event);
while (1) {
@@ -7515,6 +7494,7 @@ suspend_scheduler_sleep(ErtsSchedulerData *esdp,
}
}
}
+ erts_tse_return(ssi->event);
}
}
@@ -8458,6 +8438,7 @@ sched_thread_func(void *vesdp)
tse = erts_tse_fetch();
erts_tse_prepare_timed(tse);
ERTS_SCHED_SLEEP_INFO_IX(no - 1)->event = tse;
+ erts_tse_return(tse);
callbacks.arg = (void *) esdp->ssi;
callbacks.wakeup = thr_prgr_wakeup;
callbacks.prepare_wait = thr_prgr_prep_wait;
@@ -8466,7 +8447,7 @@ sched_thread_func(void *vesdp)
erts_msacc_init_thread("scheduler", no, 1);
- erts_thr_progress_register_managed_thread(esdp, &callbacks, 0);
+ erts_thr_progress_register_managed_thread(esdp, &callbacks, 0, 0);
#if ERTS_POLL_USE_SCHEDULER_POLLING
esdp->ssi->psi = erts_create_pollset_thread(-1, NULL);
@@ -8522,6 +8503,7 @@ sched_dirty_cpu_thread_func(void *vesdp)
Uint no = esdp->dirty_no;
ASSERT(no != 0);
ERTS_DIRTY_CPU_SCHED_SLEEP_INFO_IX(no-1)->event = erts_tse_fetch();
+ erts_tse_return(ERTS_DIRTY_CPU_SCHED_SLEEP_INFO_IX(no-1)->event);
callbacks.arg = (void *) esdp->ssi;
callbacks.wakeup = thr_prgr_wakeup;
callbacks.prepare_wait = NULL;
@@ -8568,6 +8550,7 @@ sched_dirty_io_thread_func(void *vesdp)
Uint no = esdp->dirty_no;
ASSERT(no != 0);
ERTS_DIRTY_IO_SCHED_SLEEP_INFO_IX(no-1)->event = erts_tse_fetch();
+ erts_tse_return(ERTS_DIRTY_IO_SCHED_SLEEP_INFO_IX(no-1)->event);
callbacks.arg = (void *) esdp->ssi;
callbacks.wakeup = thr_prgr_wakeup;
callbacks.prepare_wait = NULL;
@@ -8611,7 +8594,7 @@ erts_start_schedulers(void)
{
ethr_tid tid;
int res = 0;
- char name[16];
+ char name[32];
ethr_thr_opts opts = ETHR_THR_OPTS_DEFAULT_INITER;
int ix;
@@ -8621,7 +8604,7 @@ erts_start_schedulers(void)
if (erts_runq_supervision_interval) {
opts.suggested_stack_size = 16;
- erts_snprintf(opts.name, 16, "runq_supervisor");
+ erts_snprintf(opts.name, sizeof(name), "runq_supervisor");
erts_atomic_init_nob(&runq_supervisor_sleeping, 0);
if (0 != ethr_event_init(&runq_supervision_event))
erts_exit(ERTS_ABORT_EXIT, "Failed to create run-queue supervision event\n");
@@ -8642,7 +8625,7 @@ erts_start_schedulers(void)
for (ix = 0; ix < erts_no_schedulers; ix++) {
ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(ix);
ASSERT(ix == esdp->no - 1);
- erts_snprintf(opts.name, 16, "%lu_scheduler", ix + 1);
+ erts_snprintf(opts.name, sizeof(name), "%lu_scheduler", ix + 1);
res = ethr_thr_create(&esdp->tid, sched_thread_func, (void*)esdp, &opts);
if (res != 0) {
erts_exit(ERTS_ABORT_EXIT, "Failed to create scheduler thread %d, error = %d\n", ix, res);
@@ -8656,7 +8639,7 @@ erts_start_schedulers(void)
{
for (ix = 0; ix < erts_no_dirty_cpu_schedulers; ix++) {
ErtsSchedulerData *esdp = ERTS_DIRTY_CPU_SCHEDULER_IX(ix);
- erts_snprintf(opts.name, 16, "%d_dirty_cpu_scheduler", ix + 1);
+ erts_snprintf(opts.name, sizeof(name), "%d_dirty_cpu_scheduler", ix + 1);
opts.suggested_stack_size = erts_dcpu_sched_thread_suggested_stack_size;
res = ethr_thr_create(&esdp->tid,sched_dirty_cpu_thread_func,(void*)esdp,&opts);
if (res != 0)
@@ -8664,7 +8647,7 @@ erts_start_schedulers(void)
}
for (ix = 0; ix < erts_no_dirty_io_schedulers; ix++) {
ErtsSchedulerData *esdp = ERTS_DIRTY_IO_SCHEDULER_IX(ix);
- erts_snprintf(opts.name, 16, "%d_dirty_io_scheduler", ix + 1);
+ erts_snprintf(opts.name, sizeof(name), "%d_dirty_io_scheduler", ix + 1);
opts.suggested_stack_size = erts_dio_sched_thread_suggested_stack_size;
res = ethr_thr_create(&esdp->tid,sched_dirty_io_thread_func,(void*)esdp,&opts);
if (res != 0)
@@ -8672,16 +8655,31 @@ erts_start_schedulers(void)
}
}
- erts_snprintf(opts.name, 16, "aux");
+ erts_snprintf(opts.name, sizeof(name), "aux");
res = ethr_thr_create(&tid, aux_thread, NULL, &opts);
if (res != 0)
erts_exit(ERTS_ABORT_EXIT, "Failed to create aux thread, error = %d\n", res);
+ block_poll_thread_data = (ErtsAlignedBlockPollThreadData *)
+ erts_alloc_permanent_cache_aligned(ERTS_ALC_T_BLOCK_PTHR_DATA,
+ sizeof(ErtsAlignedBlockPollThreadData)
+ * erts_no_poll_threads);
+
+
for (ix = 0; ix < erts_no_poll_threads; ix++) {
- erts_snprintf(opts.name, 16, "%d_poller", ix);
+ ErtsBlockPollThreadData *bpt = &block_poll_thread_data[ix].block_data;
+ erts_mtx_init(&bpt->mtx, "block_poll_thread",
+ make_small(ix),
+ (ERTS_LOCK_FLAGS_PROPERTY_STATIC
+ | ERTS_LOCK_FLAGS_CATEGORY_IO));
+ erts_cnd_init(&bpt->cnd);
+ bpt->blocked = 0;
+ bpt->id = ix;
+
+ erts_snprintf(opts.name, sizeof(name), "%d_poller", ix);
- res = ethr_thr_create(&tid, poll_thread, (void*)(UWord)ix, &opts);
+ res = ethr_thr_create(&tid, poll_thread, (void*) bpt, &opts);
if (res != 0)
erts_exit(ERTS_ABORT_EXIT, "Failed to create poll thread\n");
}
@@ -8704,6 +8702,9 @@ erts_internal_suspend_process_2(BIF_ALIST_2)
if (BIF_P->common.id == BIF_ARG_1)
BIF_RET(am_badarg); /* We are not allowed to suspend ourselves */
+ if (!is_internal_pid(BIF_ARG_1))
+ BIF_RET(am_badarg);
+
if (is_not_nil(BIF_ARG_2)) {
/* Parse option list */
Eterm arg = BIF_ARG_2;
@@ -8852,6 +8853,9 @@ resume_process_1(BIF_ALIST_1)
if (BIF_P->common.id == BIF_ARG_1)
BIF_ERROR(BIF_P, BADARG);
+ if (!is_internal_pid(BIF_ARG_1))
+ BIF_ERROR(BIF_P, BADARG);
+
mon = erts_monitor_tree_lookup(ERTS_P_MONITORS(BIF_P),
BIF_ARG_1);
if (!mon) {
@@ -9142,11 +9146,10 @@ erts_set_process_priority(Process *p, Eterm value)
}
max_qbit = 0;
- ASSERT((a & ERTS_PSFLG_SYS_TASKS)
- ? !!p->sys_task_qs
- : !p->sys_task_qs);
- if (a & ERTS_PSFLG_SYS_TASKS)
+ if (a & ERTS_PSFLG_SYS_TASKS) {
+ ASSERT(p->sys_task_qs);
max_qbit |= p->sys_task_qs->qmask;
+ }
if (a & ERTS_PSFLG_DELAYED_SYS) {
ErtsProcSysTaskQs *qs;
qs = ERTS_PROC_GET_DELAYED_GC_TASK_QS(p);
@@ -9570,9 +9573,8 @@ Process *erts_schedule(ErtsSchedulerData *esdp, Process *p, int calls)
erts_runq_unlock(rq);
ERTS_MSACC_SET_STATE_CACHED_M(ERTS_MSACC_STATE_CHECK_IO);
- LTTNG2(scheduler_poll, esdp->no, 1);
- erts_check_io(esdp->ssi->psi, ERTS_POLL_NO_TIMEOUT);
+ erts_check_io(esdp->ssi->psi, ERTS_POLL_NO_TIMEOUT, 0);
ERTS_MSACC_POP_STATE_M();
current_time = erts_get_monotonic_time(esdp);
@@ -11118,8 +11120,13 @@ erts_set_gc_state(Process *c_p, int enable)
ERTS_LC_ASSERT(ERTS_PROC_LOCK_MAIN == erts_proc_lc_my_proc_locks(c_p));
if (!enable) {
- c_p->flags |= F_DISABLE_GC;
- return 0;
+ /* Strictly speaking it's not illegal to disable the GC when it's
+ * already disabled, but we risk enabling the GC prematurely if (for
+ * example) a BIF were to blindly disable it when trapping and then
+ * re-enable it before returning its result. */
+ ASSERT(!(c_p->flags & F_DISABLE_GC));
+ c_p->flags |= F_DISABLE_GC;
+ return 0;
}
c_p->flags &= ~F_DISABLE_GC;
@@ -11183,7 +11190,7 @@ erts_set_gc_state(Process *c_p, int enable)
first1 = dgc_tsk_qs->q[prio];
last1 = first1->prev;
first2 = stsk_qs->q[prio];
- last2 = first1->prev;
+ last2 = first2->prev;
last1->next = first2;
first2->prev = last1;
@@ -11500,6 +11507,180 @@ alloc_process(ErtsRunQueue *rq, int bound, erts_aint32_t state)
return p;
}
+int
+erts_parse_spawn_opts(ErlSpawnOpts *sop, Eterm opts_list, Eterm *tag,
+ int message_opt)
+{
+ /*
+ * Returns:
+ * - 0 on success
+ * - <0 on badopt
+ * - >0 on badarg (not prober list)
+ */
+ int result = 0;
+ Eterm ap = opts_list;
+
+ if (tag)
+ *tag = am_spawn_reply;
+ /*
+ * Store default values for options.
+ */
+ sop->multi_set = 0;
+ sop->flags = erts_default_spo_flags;
+ sop->min_heap_size = H_MIN_SIZE;
+ sop->min_vheap_size = BIN_VH_MIN_SIZE;
+ sop->max_heap_size = H_MAX_SIZE;
+ sop->max_heap_flags = H_MAX_FLAGS;
+ sop->priority = PRIORITY_NORMAL;
+ sop->max_gen_gcs = (Uint16) erts_atomic32_read_nob(&erts_max_gen_gcs);
+ sop->scheduler = 0;
+
+ /*
+ * Walk through the option list.
+ */
+ while (is_list(ap)) {
+ Eterm arg = CAR(list_val(ap));
+ ap = CDR(list_val(ap));
+ if (arg == am_link) {
+ if (sop->flags & SPO_LINK)
+ sop->multi_set = !0;
+ sop->flags |= SPO_LINK;
+ } else if (arg == am_monitor) {
+ if (sop->flags & SPO_MONITOR)
+ sop->multi_set = !0;
+ sop->flags |= SPO_MONITOR;
+ } else if (is_tuple(arg)) {
+ Eterm* tp2 = tuple_val(arg);
+ Eterm val;
+ if (*tp2 != make_arityval(2)) {
+ result = -1;
+ continue;
+ }
+ arg = tp2[1];
+ val = tp2[2];
+ if (arg == am_priority) {
+ if (sop->flags & SPO_PRIORITY)
+ sop->multi_set = !0;
+ sop->flags |= SPO_PRIORITY;
+ if (val == am_max)
+ sop->priority = PRIORITY_MAX;
+ else if (val == am_high)
+ sop->priority = PRIORITY_HIGH;
+ else if (val == am_normal)
+ sop->priority = PRIORITY_NORMAL;
+ else if (val == am_low)
+ sop->priority = PRIORITY_LOW;
+ else
+ result = -1;
+ } else if (arg == am_message_queue_data) {
+ if (sop->flags & (SPO_OFF_HEAP_MSGQ|SPO_ON_HEAP_MSGQ))
+ sop->multi_set = !0;
+ switch (val) {
+ case am_on_heap:
+ sop->flags &= ~SPO_OFF_HEAP_MSGQ;
+ sop->flags |= SPO_ON_HEAP_MSGQ;
+ break;
+ case am_off_heap:
+ sop->flags &= ~SPO_ON_HEAP_MSGQ;
+ sop->flags |= SPO_OFF_HEAP_MSGQ;
+ break;
+ default:
+ result = -1;
+ break;
+ }
+ } else if (arg == am_min_heap_size && is_small(val)) {
+ Sint min_heap_size = signed_val(val);
+ if (sop->flags & SPO_MIN_HEAP_SIZE)
+ sop->multi_set = !0;
+ sop->flags |= SPO_MIN_HEAP_SIZE;
+ if (min_heap_size < 0) {
+ result = -1;
+ } else if (min_heap_size < H_MIN_SIZE) {
+ sop->min_heap_size = H_MIN_SIZE;
+ } else {
+ sop->min_heap_size = erts_next_heap_size(min_heap_size, 0);
+ }
+ } else if (arg == am_max_heap_size) {
+ if (sop->flags & SPO_MAX_HEAP_SIZE)
+ sop->multi_set = !0;
+ sop->flags |= SPO_MAX_HEAP_SIZE;
+ if (!erts_max_heap_size(val, &sop->max_heap_size, &sop->max_heap_flags))
+ result = -1;
+ } else if (arg == am_min_bin_vheap_size && is_small(val)) {
+ Sint min_vheap_size = signed_val(val);
+ if (sop->flags & SPO_MIN_VHEAP_SIZE)
+ sop->multi_set = !0;
+ sop->flags |= SPO_MIN_VHEAP_SIZE;
+ if (min_vheap_size < 0) {
+ result = -1;
+ } else if (min_vheap_size < BIN_VH_MIN_SIZE) {
+ sop->min_vheap_size = BIN_VH_MIN_SIZE;
+ } else {
+ sop->min_vheap_size = erts_next_heap_size(min_vheap_size, 0);
+ }
+ } else if (arg == am_fullsweep_after && is_small(val)) {
+ Sint max_gen_gcs = signed_val(val);
+ if (sop->flags & SPO_MAX_GEN_GCS)
+ sop->multi_set = !0;
+ sop->flags |= SPO_MAX_GEN_GCS;
+ if (max_gen_gcs < 0) {
+ result = -1;
+ } else {
+ sop->max_gen_gcs = max_gen_gcs;
+ }
+ } else if (arg == am_scheduler && is_small(val)) {
+ Sint scheduler = signed_val(val);
+ if (sop->flags & SPO_SCHEDULER)
+ sop->multi_set = !0;
+ sop->flags |= SPO_SCHEDULER;
+ if (scheduler < 0 || erts_no_schedulers < scheduler)
+ result = -1;
+ else
+ sop->scheduler = (int) scheduler;
+ } else if (arg == am_reply) {
+ if (!message_opt)
+ result = -1;
+ else if (val == am_error_only) {
+ sop->flags |= SPO_NO_SMSG;
+ sop->flags &= ~SPO_NO_EMSG;
+ }
+ else if (val == am_success_only) {
+ sop->flags &= ~SPO_NO_SMSG;
+ sop->flags |= SPO_NO_EMSG;
+ }
+ else if (val == am_no) {
+ sop->flags |= SPO_NO_SMSG;
+ sop->flags |= SPO_NO_EMSG;
+ }
+ else if (val == am_yes) {
+ sop->flags &= ~SPO_NO_SMSG;
+ sop->flags &= ~SPO_NO_EMSG;
+ }
+ else
+ result = -1;
+ } else if (arg == am_reply_tag) {
+ if (!tag)
+ result = -1;
+ else
+ *tag = val;
+ } else {
+ result = -1;
+ }
+ } else {
+ result = -1;
+ }
+ }
+ if (is_not_nil(ap)) {
+ return 1;
+ }
+
+ if (sop->max_heap_size != 0 && sop->max_heap_size < sop->min_heap_size) {
+ result = -1;
+ }
+
+ return result;
+}
+
Eterm
erl_create_process(Process* parent, /* Parent of process (default group leader). */
Eterm mod, /* Tagged atom for module. */
@@ -11519,6 +11700,8 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
erts_aint32_t state = 0;
erts_aint32_t prio = (erts_aint32_t) PRIORITY_NORMAL;
ErtsProcLocks locks = ERTS_PROC_LOCKS_ALL;
+ Eterm node_token_heap[6];
+ Eterm group_leader, parent_id, spawn_ref, token;
#ifdef SHCOPY_SPAWN
erts_shcopy_t info;
INITIALIZE_SHCOPY(info);
@@ -11526,8 +11709,25 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
erts_literal_area_t litarea;
INITIALIZE_LITERAL_PURGE_AREA(litarea);
#endif
-
- erts_proc_lock(parent, ERTS_PROC_LOCKS_ALL_MINOR);
+
+ if (!parent) {
+ token = so->token;
+ group_leader = so->group_leader;
+ parent_id = so->parent_id;
+ spawn_ref = so->mref;
+ }
+ else {
+ token = SEQ_TRACE_TOKEN(parent);
+ erts_proc_lock(parent, ERTS_PROC_LOCKS_ALL_MINOR);
+ group_leader = parent->group_leader;
+ parent_id = parent->common.id;
+ if (so->flags & (SPO_MONITOR | SPO_ASYNC))
+ spawn_ref = so->mref = erts_make_ref(parent);
+ else if (have_seqtrace(token))
+ spawn_ref = erts_make_ref(parent);
+ else
+ spawn_ref = THE_NON_VALUE;
+ }
/*
* Check for errors.
@@ -11538,6 +11738,11 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
goto error;
}
+ if (arity > MAX_SMALL) {
+ so->error_code = SYSTEM_LIMIT;
+ goto error;
+ }
+
if (so->flags & SPO_USE_ARGS) {
if (so->scheduler) {
int ix = so->scheduler-1;
@@ -11562,14 +11767,21 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
ASSERT((qs_flags & FS_ON_HEAP_MSGQ) || (qs_flags & FS_OFF_HEAP_MSGQ));
- if (!rq)
- rq = erts_get_runq_proc(parent, NULL);
+ if (!rq) {
+ if (parent)
+ rq = erts_get_runq_proc(parent, NULL);
+ else {
+ ErtsSchedulerData *esdp = erts_get_scheduler_data();
+ ASSERT(esdp->type == ERTS_SCHED_NORMAL);
+ rq = esdp->run_queue;
+ }
+ }
+ ASSERT(rq);
p = alloc_process(rq, bound, state); /* All proc locks are locked by this thread
on success */
if (!p) {
- erts_send_error_to_logger_str(parent->group_leader,
- "Too many processes\n");
+ erts_send_error_to_logger_str(group_leader, "Too many processes\n");
so->error_code = SYSTEM_LIMIT;
goto error;
}
@@ -11579,7 +11791,7 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
#else
arg_size = size_object_litopt(args, &litarea);
#endif
- heap_need = arg_size;
+ heap_need = arg_size + 1; /* Reserve place for continuation pointer */
p->flags = flags;
p->sig_qs.flags = qs_flags;
@@ -11603,18 +11815,14 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
p->schedule_count = 0;
ASSERT(p->min_heap_size == erts_next_heap_size(p->min_heap_size, 0));
- p->u.initial.module = mod;
- p->u.initial.function = func;
- p->u.initial.arity = (Uint) arity;
-
/*
* Must initialize binary lists here before copying binaries to process.
*/
p->off_heap.first = NULL;
p->off_heap.overhead = 0;
- heap_need +=
- IS_CONST(parent->group_leader) ? 0 : NC_HEAP_SIZE(parent->group_leader);
+ if (is_not_immed(group_leader))
+ heap_need += NC_HEAP_SIZE(group_leader);
if (heap_need < p->min_heap_size) {
sz = heap_need = p->min_heap_size;
@@ -11629,7 +11837,8 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
p->old_hend = p->old_htop = p->old_heap = NULL;
p->high_water = p->heap;
p->gen_gcs = 0;
- p->stop = p->hend = p->heap + sz;
+ p->hend = p->heap + sz;
+ p->stop = p->hend - 1; /* Reserve place for continuation pointer */
p->htop = p->heap;
p->heap_sz = sz;
p->abandoned_heap = NULL;
@@ -11647,7 +11856,7 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
p->current = &p->u.initial;
p->i = (BeamInstr *) beam_apply;
- p->cp = (BeamInstr *) beam_apply+1;
+ p->stop[0] = make_cp(beam_apply + 1);
p->arg_reg = p->def_arg_reg;
p->max_arg_reg = sizeof(p->def_arg_reg)/sizeof(p->def_arg_reg[0]);
@@ -11673,20 +11882,21 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
ERTS_P_MONITORS(p) = NULL;
ERTS_P_LT_MONITORS(p) = NULL;
- ASSERT(is_pid(parent->group_leader));
+ ASSERT(is_pid(group_leader));
- if (parent->group_leader == ERTS_INVALID_PID)
+ if (group_leader == ERTS_INVALID_PID)
p->group_leader = p->common.id;
else {
/* Needs to be done after the heap has been set up */
p->group_leader =
- IS_CONST(parent->group_leader)
- ? parent->group_leader
- : STORE_NC(&p->htop, &p->off_heap, parent->group_leader);
+ IS_CONST(group_leader)
+ ? group_leader
+ : STORE_NC(&p->htop, &p->off_heap, group_leader);
}
erts_get_default_proc_tracing(&ERTS_TRACE_FLAGS(p), &ERTS_TRACER(p));
+ p->uniq = 1;
p->sig_qs.first = NULL;
p->sig_qs.last = &p->sig_qs.first;
p->sig_qs.cont = NULL;
@@ -11710,14 +11920,11 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
p->mbuf_sz = 0;
erts_atomic_init_nob(&p->psd, (erts_aint_t) NULL);
p->dictionary = NULL;
- p->seq_trace_lastcnt = 0;
- p->seq_trace_clock = 0;
- SEQ_TRACE_TOKEN(p) = NIL;
#ifdef USE_VM_PROBES
DT_UTAG(p) = NIL;
DT_UTAG_FLAGS(p) = 0;
#endif
- p->parent = (parent->common.id == ERTS_INVALID_PID
+ p->parent = (!parent || parent->common.id == ERTS_INVALID_PID
? NIL
: parent->common.id);
@@ -11733,7 +11940,7 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
p->fp_exception = 0;
#endif
- if (IS_TRACED(parent)) {
+ if (parent && IS_TRACED(parent)) {
if (ERTS_TRACE_FLAGS(parent) & F_TRACE_SOS) {
ERTS_TRACE_FLAGS(p) |= (ERTS_TRACE_FLAGS(parent) & TRACEE_FLAGS);
erts_tracer_replace(&p->common, ERTS_TRACER(parent));
@@ -11746,21 +11953,126 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
ERTS_TRACE_FLAGS(parent) &= ~(F_TRACE_SOS1 | F_TRACE_SOS);
}
if (so->flags & SPO_LINK && ERTS_TRACE_FLAGS(parent) & (F_TRACE_SOL|F_TRACE_SOL1)) {
- ERTS_TRACE_FLAGS(p) |= (ERTS_TRACE_FLAGS(parent)&TRACEE_FLAGS);
- erts_tracer_replace(&p->common, ERTS_TRACER(parent));
- if (ERTS_TRACE_FLAGS(parent) & F_TRACE_SOL1) {/*maybe override*/
- ERTS_TRACE_FLAGS(p) &= ~(F_TRACE_SOL1 | F_TRACE_SOL);
- ERTS_TRACE_FLAGS(parent) &= ~(F_TRACE_SOL1 | F_TRACE_SOL);
- }
+ ERTS_TRACE_FLAGS(p) |= (ERTS_TRACE_FLAGS(parent)&TRACEE_FLAGS);
+ erts_tracer_replace(&p->common, ERTS_TRACER(parent));
+ if (ERTS_TRACE_FLAGS(parent) & F_TRACE_SOL1) {/*maybe override*/
+ ERTS_TRACE_FLAGS(p) &= ~(F_TRACE_SOL1 | F_TRACE_SOL);
+ ERTS_TRACE_FLAGS(parent) &= ~(F_TRACE_SOL1 | F_TRACE_SOL);
+ }
+ }
+ }
+
+ /* seq_trace is handled before regular tracing as the latter may touch the
+ * trace token. */
+ if (!have_seqtrace(token)) {
+ SEQ_TRACE_TOKEN(p) = NIL;
+ p->seq_trace_lastcnt = 0;
+ p->seq_trace_clock = 0;
+ }
+ else {
+ Eterm tmp_heap[9]; /* 8-tuple */
+ Eterm seq_msg;
+ Uint token_sz;
+ Eterm *hp;
+
+ if (parent) {
+ seq_trace_update_serial(parent);
+ token = SEQ_TRACE_TOKEN(parent);
+ ASSERT(SEQ_TRACE_T_ARITY(token) == 5);
+ sys_memcpy(&node_token_heap[0],
+ (void *) tuple_val(token),
+ sizeof(Eterm)*6);
+ token = make_tuple(&node_token_heap[0]);
}
- if (ARE_TRACE_FLAGS_ON(parent, F_TRACE_PROCS)) {
+
+ ASSERT(SEQ_TRACE_T_ARITY(token) == 5);
+ ASSERT(is_immed(SEQ_TRACE_T_FLAGS(token)));
+ ASSERT(is_immed(SEQ_TRACE_T_SERIAL(token)));
+ ASSERT(is_immed(SEQ_TRACE_T_LASTCNT(token)));
+
+ token_sz = size_object(token);
+
+ hp = HAlloc(p, token_sz);
+ SEQ_TRACE_TOKEN(p) = copy_struct(token, token_sz, &hp, &MSO(p));
+
+ ASSERT((locks & (ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE)) ==
+ (ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE));
+
+ locks &= ~(ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
+ erts_proc_unlock(p, ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
+
+ if (parent) {
+ /* Simulate spawn_request message... */
+ Eterm tmp_heap2[4];
+ Eterm mfa;
+ erts_proc_unlock(parent, ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
+ mfa = TUPLE3(&tmp_heap2[0], mod, func, make_small(arity)) ;
+ seq_msg = TUPLE8(&tmp_heap[0], am_spawn_request,
+ spawn_ref, parent_id, group_leader,
+ mfa, so->opts, so->tag, args);
+ seq_trace_output(token, seq_msg, SEQ_TRACE_SEND,
+ p->common.id, parent);
+ seq_trace_output(token, seq_msg, SEQ_TRACE_RECEIVE,
+ p->common.id, parent);
+
+ /* The counters behave the same way on spawning as they do on messages;
+ * we don't inherit our parent's lastcnt. */
+ p->seq_trace_clock = unsigned_val(SEQ_TRACE_T_SERIAL(token));
+ p->seq_trace_lastcnt = p->seq_trace_clock;
+
+ }
+ else {
+ /*
+ * The spawn request is presented as two messages
+ * in dist case. It is sent as one signal over the
+ * distribution with the argument list as payload.
+ * The payload will be delivered as an ordinary
+ * message of its own, as the first message to the
+ * newly created process (in order to decode it in
+ * in the newly created process). For more info see
+ * erts_internal:dist_spawn_init() in erts_interal.erl.
+ * We expose these as two messages when seq-tracing
+ * in order not having to decode the argument list
+ * here. The remote node has passed a token with
+ * serial bumped twice, i.e., the first message should
+ * use a serial of one less than in the actual token;
+ * adjust serial and then restore it for use with
+ * the argument list message...
+ */
+ Eterm serial;
+ Uint serial_num;
+ ASSERT(eq(SEQ_TRACE_T_SENDER(token), parent_id));
+ serial = SEQ_TRACE_T_SERIAL(token);
+ serial_num = unsigned_val(serial);
+ serial_num--;
+ SEQ_TRACE_T_SERIAL(token) = make_small(serial_num);
+
+ seq_msg = TUPLE6(&tmp_heap[0], am_spawn_request,
+ spawn_ref, parent_id, group_leader,
+ so->mfa, so->opts);
+ seq_trace_output(token, seq_msg, SEQ_TRACE_RECEIVE,
+ p->common.id, p);
+
+ /* as on receive... */
+ p->seq_trace_clock = serial_num;
+ p->seq_trace_lastcnt = serial_num;
+
+ /* Restore serial for the argument list message... */
+ SEQ_TRACE_T_SERIAL(token) = serial;
+ }
+ }
+
+ if (parent && IS_TRACED_FL(parent, F_TRACE_PROCS)) {
+ /* The locks may already be released if seq_trace is enabled as well. */
+ if ((locks & (ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE))
+ == (ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE)) {
locks &= ~(ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
erts_proc_unlock(p, ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
erts_proc_unlock(parent, ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
- trace_proc_spawn(parent, am_spawn, p->common.id, mod, func, args);
- if (so->flags & SPO_LINK)
- trace_proc(parent, locks, parent, am_link, p->common.id);
}
+ trace_proc_spawn(parent, am_spawn, p->common.id, mod, func, args);
+ if (so->flags & SPO_LINK)
+ trace_proc(parent, locks, parent, am_link, p->common.id);
}
if (IS_TRACED_FL(p, F_TRACE_PROCS)) {
@@ -11769,49 +12081,191 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
/* This happens when parent was not traced, but child is */
locks &= ~(ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
erts_proc_unlock(p, ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
- erts_proc_unlock(parent, ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
+ if (parent)
+ erts_proc_unlock(parent, ERTS_PROC_LOCK_STATUS|ERTS_PROC_LOCK_TRACE);
}
- trace_proc_spawn(p, am_spawned, parent->common.id, mod, func, args);
+ trace_proc_spawn(p, am_spawned, parent_id, mod, func, args);
if (so->flags & SPO_LINK)
- trace_proc(p, locks, p, am_getting_linked, parent->common.id);
+ trace_proc(p, locks, p, am_getting_linked, parent_id);
}
/*
* Check if this process should be initially linked to its parent.
*/
- if (so->flags & SPO_LINK) {
- ErtsLink *lnk;
- ErtsLinkData *ldp = erts_link_create(ERTS_LNK_TYPE_PROC,
- parent->common.id,
- p->common.id);
- lnk = erts_link_tree_lookup_insert(&ERTS_P_LINKS(parent), &ldp->a);
- if (lnk) {
- /*
- * This should more or less never happen, but could
- * potentially happen if pid:s wrap...
- */
- erts_link_release(lnk);
+ if (parent) {
+ /* Node local spawn... */
+
+ if (so->flags & SPO_LINK) {
+ ErtsLink *lnk;
+ lnk = erts_link_internal_create(ERTS_LNK_TYPE_PROC, p->common.id);
+ if (!!erts_link_tree_lookup_insert(&ERTS_P_LINKS(parent), lnk)) {
+ /*
+ * This should more or less never happen, but could
+ * potentially happen if pid:s wrap...
+ */
+ erts_link_release(lnk);
+ }
+ lnk = erts_link_internal_create(ERTS_LNK_TYPE_PROC,
+ parent->common.id);
+ erts_link_tree_insert(&ERTS_P_LINKS(p), lnk);
}
- erts_link_tree_insert(&ERTS_P_LINKS(p), &ldp->b);
- }
- /*
- * Test whether this process should be initially monitored by its parent.
- */
- if (so->flags & SPO_MONITOR) {
- Eterm mref = erts_make_ref(parent);
- ErtsMonitorData *mdp = erts_monitor_create(ERTS_MON_TYPE_PROC,
- mref,
- parent->common.id,
- p->common.id,
- NIL);
- erts_monitor_tree_insert(&ERTS_P_MONITORS(parent), &mdp->origin);
- erts_monitor_list_insert(&ERTS_P_LT_MONITORS(p), &mdp->target);
- so->mref = mref;
+ /*
+ * Test whether this process should be initially monitored by its parent.
+ */
+ if (so->flags & SPO_MONITOR) {
+ ErtsMonitorData *mdp = erts_monitor_create(ERTS_MON_TYPE_PROC,
+ spawn_ref,
+ parent->common.id,
+ p->common.id,
+ NIL);
+ erts_monitor_tree_insert(&ERTS_P_MONITORS(parent), &mdp->origin);
+ erts_monitor_list_insert(&ERTS_P_LT_MONITORS(p), &mdp->target);
+ }
+
+ ASSERT(locks & ERTS_PROC_LOCK_MSGQ);
+
+ if (so->flags & SPO_ASYNC) { /* spawn_request() */
+ Eterm *tp;
+
+ ASSERT(is_tuple_arity(so->mfa, 3));
+ tp = tuple_val(so->mfa);
+ ASSERT(is_atom(tp[1]));
+ ASSERT(is_atom(tp[2]));
+ ASSERT(is_small(tp[3]));
+ p->u.initial.module = tp[1];
+ p->u.initial.function = tp[2];
+ p->u.initial.arity = (Uint) unsigned_val(tp[3]);
+
+ ASSERT(is_value(so->tag));
+ if (have_seqtrace(token)) {
+ seq_trace_update_serial(p);
+ token = SEQ_TRACE_TOKEN(p);
+ }
+
+ if (!(so->flags & SPO_NO_SMSG)) {
+ /*
+ * Ensure spawn reply success message reach parent before
+ * any down or exit signals from child...
+ */
+ erts_send_local_spawn_reply(parent, locks, p,
+ so->tag, spawn_ref,
+ p->common.id, token);
+ }
+ }
+ else { /* synchronous spawn */
+
+ p->u.initial.module = mod;
+ p->u.initial.function = func;
+ p->u.initial.arity = (Uint) arity;
+
+ if (have_seqtrace(token)) {
+ /* Simulate spawn reply message... */
+ Eterm tmp_heap[5];
+ Eterm seq_msg;
+ Uint serial;
+
+ seq_trace_update_serial(p);
+ token = SEQ_TRACE_TOKEN(p);
+ serial = SEQ_TRACE_T_SERIAL(token);
+ seq_msg = TUPLE4(&tmp_heap[0], so->tag, spawn_ref,
+ am_ok, p->common.id);
+ seq_trace_output(token, seq_msg, SEQ_TRACE_SEND,
+ parent_id, parent);
+
+ /* Update parent as if receive... */
+ parent->seq_trace_lastcnt = serial;
+ parent->seq_trace_clock = serial;
+ seq_trace_output(token, seq_msg, SEQ_TRACE_RECEIVE,
+ parent_id, parent);
+ }
+
+ }
+
+ erts_proc_unlock(p, locks);
+ erts_proc_unlock(parent, locks & ERTS_PROC_LOCKS_ALL_MINOR);
+
}
+ else {
+ /* Distributed spawn */
+ ErtsDSigSendContext ctx;
+ int code;
+ Eterm *tp;
+
+ ASSERT(is_tuple_arity(so->mfa, 3));
+ tp = tuple_val(so->mfa);
+ ASSERT(is_atom(tp[1]));
+ ASSERT(is_atom(tp[2]));
+ ASSERT(is_small(tp[3]));
+ p->u.initial.module = tp[1];
+ p->u.initial.function = tp[2];
+ p->u.initial.arity = (Uint) unsigned_val(tp[3]);
+
+ ASSERT(locks & ERTS_PROC_LOCK_MSGQ);
+ /*
+ * Pass the (on external format) encoded argument list as
+ * *first* message to the process. Note that this message
+ * *must* be first in the message queue of the newly
+ * spawned process!
+ */
+ erts_queue_dist_message(p, locks, so->edep, so->ede_hfrag,
+ token, parent_id);
- erts_proc_unlock(p, locks);
+ erts_proc_unlock(p, locks & ERTS_PROC_LOCKS_ALL_MINOR);
+
+ if (so->flags & SPO_LINK) {
+ ErtsLinkData *ldp;
+ ldp = erts_link_external_create(ERTS_LNK_TYPE_DIST_PROC,
+ p->common.id, parent_id);
+ erts_link_tree_insert(&ERTS_P_LINKS(p), &ldp->proc);
+ if (!erts_link_dist_insert(&ldp->dist, so->mld)) {
+ erts_proc_sig_send_link_exit(NULL, THE_NON_VALUE, &ldp->dist,
+ am_noconnection, NIL);
+ }
+ }
+
+ if (so->flags & SPO_MONITOR) {
+ ErtsMonitorData *mdp;
+ mdp = erts_monitor_create(ERTS_MON_TYPE_DIST_PROC,
+ spawn_ref, parent_id,
+ p->common.id, NIL);
+ if (erts_monitor_dist_insert(&mdp->origin, so->mld)) {
+ erts_monitor_tree_insert(&ERTS_P_MONITORS(p), &mdp->target);
+ }
+ else {
+ erts_monitor_release_both(mdp);
+ }
+ }
+
+ if (have_seqtrace(token)) {
+ Eterm tmp_heap[5];
+ Eterm seq_msg;
+ seq_trace_update_serial(p);
+ seq_msg = TUPLE4(&tmp_heap[0], so->tag,
+ spawn_ref, am_ok, p->common.id);
+ token = SEQ_TRACE_TOKEN(p);
+ seq_trace_output(token, seq_msg, SEQ_TRACE_SEND, parent_id, p);
+ }
+
+ code = erts_dsig_prepare(&ctx, so->dist_entry, NULL, 0,
+ ERTS_DSP_NO_LOCK, 1, 1, 0);
+ if (code == ERTS_DSIG_PREP_CONNECTED) {
+ int dsflags = 0;
+ if (so->flags & SPO_LINK)
+ dsflags |= ERTS_DIST_SPAWN_FLAG_LINK;
+ if (so->flags & SPO_MONITOR)
+ dsflags |= ERTS_DIST_SPAWN_FLAG_MONITOR;
+ code = erts_dsig_send_spawn_reply(&ctx, spawn_ref,
+ parent_id,
+ make_small(dsflags),
+ p->common.id,
+ token);
+ ASSERT(code == ERTS_DSIG_SEND_OK);
+ }
+
+ erts_proc_unlock(p, locks & ERTS_PROC_LOCK_MAIN);
+ }
res = p->common.id;
@@ -11819,8 +12273,6 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
* Schedule process for execution.
*/
- erts_proc_unlock(parent, locks & ERTS_PROC_LOCKS_ALL_MINOR);
-
schedule_process(p, state, 0);
VERBOSE(DEBUG_PROCESSES, ("Created a new process: %T\n",p->common.id));
@@ -11839,11 +12291,65 @@ erl_create_process(Process* parent, /* Parent of process (default group leader).
error:
- erts_proc_unlock(parent, locks & ERTS_PROC_LOCKS_ALL_MINOR);
+ if (parent)
+ erts_proc_unlock(parent, locks & ERTS_PROC_LOCKS_ALL_MINOR);
return res;
}
+void
+erts_send_local_spawn_reply(Process *parent, ErtsProcLocks parent_locks,
+ Process *child, Eterm tag, Eterm ref,
+ Eterm result, Eterm token)
+{
+ ErtsMessage *mp;
+ ErlOffHeap *ohp;
+ Eterm *hp;
+ Eterm msg, ref_copy, ref_sz, tag_copy, tag_sz,
+ token_copy, token_sz, type;
+ ErtsProcLocks locks = parent_locks;
+
+ type = child ? am_ok : am_error;
+
+ if (have_seqtrace(token) && child)
+ token_sz = size_object(token);
+ else {
+ token_copy = token = NIL;
+ token_sz = 0;
+ }
+
+ ref_sz = size_object(ref);
+ tag_sz = is_immed(tag) ? 0 : size_object(tag);
+ mp = erts_alloc_message_heap(parent, &locks,
+ 5 + tag_sz + ref_sz + token_sz,
+ &hp, &ohp);
+ ref_copy = copy_struct(ref, ref_sz, &hp, ohp);
+ tag_copy = is_immed(tag) ? tag : copy_struct(tag, tag_sz, &hp, ohp);
+ msg = TUPLE4(hp, tag_copy, ref_copy, type, result);
+ hp += 5;
+
+ if (have_seqtrace(token)) {
+ token_copy = copy_struct(token, token_sz, &hp, ohp);
+ seq_trace_output(token_copy, msg, SEQ_TRACE_SEND,
+ parent->common.id, parent);
+ }
+
+ if (!child) { /* error reply */
+ ASSERT(is_atom(result));
+ erts_queue_message(parent, parent_locks, mp, msg,
+ erts_this_dist_entry->sysname);
+ }
+ else { /* success reply */
+ ASSERT(child->common.id == result);
+ ERL_MESSAGE_TOKEN(mp) = token_copy;
+ erts_queue_proc_message(child, parent, parent_locks, mp, msg);
+ }
+
+ ASSERT((parent_locks & locks) == parent_locks);
+ if (locks != parent_locks)
+ erts_proc_unlock(parent, locks & ~parent_locks);
+}
+
/*
* Initiates a pseudo process that can be used
* for arithmetic BIFs.
@@ -11894,6 +12400,7 @@ void erts_init_empty_process(Process *p)
ERTS_P_MONITORS(p) = NULL;
ERTS_P_LT_MONITORS(p) = NULL;
ERTS_P_LINKS(p) = NULL; /* List of links */
+ p->uniq = 1;
p->sig_qs.first = NULL;
p->sig_qs.last = &p->sig_qs.first;
p->sig_qs.cont = NULL;
@@ -11920,7 +12427,6 @@ void erts_init_empty_process(Process *p)
p->u.initial.function = 0;
p->u.initial.arity = 0;
p->catches = 0;
- p->cp = NULL;
p->i = NULL;
p->current = NULL;
@@ -11998,7 +12504,6 @@ erts_debug_verify_clean_empty_process(Process* p)
ASSERT(p->bif_timers == NULL);
ASSERT(p->dictionary == NULL);
ASSERT(p->catches == 0);
- ASSERT(p->cp == NULL);
ASSERT(p->i == NULL);
ASSERT(p->current == NULL);
@@ -12059,7 +12564,7 @@ delete_process(Process* p)
if (pbt)
erts_free(ERTS_ALC_T_BPD, (void *) pbt);
- erts_destroy_nif_export(p);
+ erts_destroy_nfunc(p);
/* Cleanup psd */
@@ -12077,7 +12582,7 @@ delete_process(Process* p)
* The mso list should not be used anymore, but if it is, make sure that
* we'll notice.
*/
- p->off_heap.first = (void *) 0x8DEFFACD;
+ p->off_heap.first = (void*)(UWord)0x8DEFFACD;
if (p->arg_reg != p->def_arg_reg) {
erts_free(ERTS_ALC_T_ARG_REG, p->arg_reg);
@@ -12279,6 +12784,156 @@ erts_proc_exit_handle_dist_monitor(ErtsMonitor *mon, void *vctxt, Sint reds)
return reds_consumed;
}
+static int
+proc_exit_handle_pend_spawn_monitors(ErtsMonitor *mon, void *vctxt, Sint reds)
+{
+ ErtsProcExitContext *ctxt = (ErtsProcExitContext *) vctxt;
+ Process *c_p = ctxt->c_p;
+ Eterm reason = ctxt->reason;
+ Eterm item, *hp;
+ Uint item_sz;
+ int code;
+ ErtsDSigSendContext ctx;
+ ErtsMonitorData *mdp;
+ ErtsMonLnkDist *dist;
+ DistEntry *dep;
+ ErtsHeapFactory factory;
+ Sint reds_consumed = 0;
+
+ ASSERT(c_p->flags & F_DISABLE_GC);
+ ASSERT(erts_monitor_is_origin(mon) && mon->type == ERTS_MON_TYPE_DIST_PROC);
+ ASSERT(ctxt->dist_state == NIL);
+ ASSERT(!ctxt->wait_pend_spawn_monitor);
+ ASSERT(!ctxt->yield);
+
+ ASSERT(mon->flags & ERTS_ML_FLG_SPAWN_PENDING);
+
+ mdp = erts_monitor_to_data(mon);
+ dist = ((ErtsMonitorDataExtended *) mdp)->dist;
+
+ if (!(mon->flags & ERTS_ML_FLG_SPAWN_LINK)) {
+ /* Just cleanup... */
+ if (!erts_dist_pend_spawn_exit_delete(&mdp->target)) {
+ mdp = NULL;
+ }
+ goto done;
+ }
+
+ code = erts_dist_pend_spawn_exit_parent_wait(c_p,
+ ERTS_PROC_LOCK_MAIN,
+ mon);
+ if (code == 0) {
+ /* Connection closing; cleanup... */
+ mdp = NULL;
+ goto done;
+ }
+
+ if (code < 0) {
+ /* We got suspended need to wait for spawn-reply... */
+ ctxt->wait_pend_spawn_monitor = mon;
+ ctxt->yield = !0;
+ return reds;
+ }
+
+ ASSERT(is_external_pid(mon->other.item) || is_atom(mon->other.item));
+
+ /* If other.item is an atom the spawn failed... */
+
+ if (is_not_external_pid(mon->other.item))
+ goto done; /* Cleanup */
+
+ if (mon->flags & ERTS_ML_FLG_SPAWN_ABANDONED)
+ reason = am_abandoned;
+
+ /* Send exit signal... */
+ dep = external_pid_dist_entry(mon->other.item);
+
+ code = erts_dsig_prepare(&ctx, dep, c_p, ERTS_PROC_LOCK_MAIN,
+ ERTS_DSP_NO_LOCK, 0, 0, 0);
+
+ ctx.reds = (Sint) (reds * TERM_TO_BINARY_LOOP_FACTOR);
+
+ switch (code) {
+ case ERTS_DSIG_PREP_NOT_ALIVE:
+ case ERTS_DSIG_PREP_NOT_CONNECTED:
+ break;
+ case ERTS_DSIG_PREP_PENDING:
+ case ERTS_DSIG_PREP_CONNECTED:
+ if (dist->connection_id != ctx.connection_id)
+ break;
+ erts_factory_proc_init(&factory, c_p);
+ item_sz = size_object(mon->other.item);
+ hp = erts_produce_heap(&factory, item_sz, 0);
+ item = copy_struct(mon->other.item, item_sz, &hp, factory.off_heap);
+ erts_factory_close(&factory);
+ code = erts_dsig_send_exit_tt(&ctx,
+ c_p->common.id,
+ item,
+ reason,
+ SEQ_TRACE_TOKEN(c_p));
+ reds_consumed = reds - (ctx.reds / TERM_TO_BINARY_LOOP_FACTOR);
+ switch (code) {
+ case ERTS_DSIG_SEND_YIELD:
+ reds_consumed = reds; /* force yield */
+ ctxt->yield = 1;
+ break;
+ case ERTS_DSIG_SEND_CONTINUE:
+ ctxt->dist_state = erts_dsend_export_trap_context(c_p, &ctx);
+ reds_consumed = reds; /* force yield */
+ ctxt->yield = 1;
+ break;
+ case ERTS_DSIG_SEND_OK:
+ break;
+ case ERTS_DSIG_SEND_TOO_LRG:
+ erts_kill_dist_connection(dep, dist->connection_id);
+ break;
+ default:
+ ASSERT(! "Invalid dsig send exit result");
+ break;
+ }
+ break;
+ default:
+ ASSERT(! "Invalid dsig prep exit result");
+ break;
+ }
+
+done:
+
+ if (mdp)
+ erts_monitor_release_both(mdp);
+ else
+ erts_monitor_release(mon);
+
+ return reds_consumed;
+}
+
+void
+erts_proc_exit_dist_demonitor(Process *c_p, DistEntry *dep, Uint32 conn_id,
+ Eterm ref, Eterm watched)
+{
+ ErtsDSigSendContext ctx;
+ int code;
+
+ ASSERT(is_internal_ref(ref));
+ ASSERT(is_atom(watched) || is_external_pid(watched));
+
+ code = erts_dsig_prepare(&ctx, dep, NULL, 0,
+ ERTS_DSP_NO_LOCK, 1, 1, 0);
+ switch (code) {
+ case ERTS_DSIG_PREP_CONNECTED:
+ case ERTS_DSIG_PREP_PENDING:
+ if (conn_id == ctx.connection_id) {
+ code = erts_dsig_send_demonitor(&ctx,
+ c_p->common.id,
+ watched,
+ ref);
+ ASSERT(code == ERTS_DSIG_SEND_OK);
+ }
+ default:
+ break;
+ }
+}
+
int
erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt, Sint reds)
{
@@ -12399,10 +13054,15 @@ erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt, Sint reds)
case ERTS_MON_TYPE_DIST_PROC: {
ErtsMonLnkDist *dist;
DistEntry *dep;
- ErtsDSigSendContext ctx;
- int code;
Eterm watched;
+ if (mon->flags & ERTS_ML_FLG_SPAWN_PENDING) {
+ if (!erts_dist_pend_spawn_exit_parent_setup(mon))
+ break; /* Drop it... */
+ erts_monitor_tree_insert(&ctxt->pend_spawn_monitors, mon);
+ return 1;
+ }
+
mdp = erts_monitor_to_data(mon);
dist = ((ErtsMonitorDataExtended *) mdp)->dist;
ASSERT(dist);
@@ -12416,21 +13076,8 @@ erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt, Sint reds)
ASSERT(is_external_pid(watched));
dep = external_pid_dist_entry(watched);
}
- code = erts_dsig_prepare(&ctx, dep, NULL, 0,
- ERTS_DSP_NO_LOCK, 1, 1, 0);
- switch (code) {
- case ERTS_DSIG_PREP_CONNECTED:
- case ERTS_DSIG_PREP_PENDING:
- if (dist->connection_id == ctx.connection_id) {
- code = erts_dsig_send_demonitor(&ctx,
- c_p->common.id,
- watched,
- mdp->ref);
- ASSERT(code == ERTS_DSIG_SEND_OK);
- }
- default:
- break;
- }
+ erts_proc_exit_dist_demonitor(c_p, dep, dist->connection_id,
+ mdp->ref, watched);
if (!erts_monitor_dist_delete(&mdp->target))
mdp = NULL;
res = 100;
@@ -12461,7 +13108,7 @@ erts_proc_exit_handle_dist_link(ErtsLink *lnk, void *vctxt, Sint reds)
ErtsMonLnkDist *dist;
DistEntry *dep;
ErtsLink *dlnk;
- ErtsLinkData *ldp = NULL;
+ ErtsELink *elnk = NULL;
ErtsHeapFactory factory;
Sint reds_consumed = 0;
@@ -12470,8 +13117,8 @@ erts_proc_exit_handle_dist_link(ErtsLink *lnk, void *vctxt, Sint reds)
ASSERT(ctxt->dist_state == NIL);
ASSERT(!ctxt->yield);
- dlnk = erts_link_to_other(lnk, &ldp);
- dist = ((ErtsLinkDataExtended *) ldp)->dist;
+ dlnk = erts_link_to_other(lnk, &elnk);
+ dist = elnk->dist;
ASSERT(is_external_pid(lnk->other.item));
dep = external_pid_dist_entry(lnk->other.item);
@@ -12479,7 +13126,7 @@ erts_proc_exit_handle_dist_link(ErtsLink *lnk, void *vctxt, Sint reds)
ASSERT(dep != erts_this_dist_entry);
if (!erts_link_dist_delete(dlnk))
- ldp = NULL;
+ elnk = NULL;
code = erts_dsig_prepare(&ctx, dep, c_p, ERTS_PROC_LOCK_MAIN,
ERTS_DSP_NO_LOCK, 0, 0, 0);
@@ -12529,8 +13176,8 @@ erts_proc_exit_handle_dist_link(ErtsLink *lnk, void *vctxt, Sint reds)
ASSERT(! "Invalid dsig prep exit monitor result");
break;
}
- if (ldp)
- erts_link_release_both(ldp);
+ if (elnk)
+ erts_link_release_both(&elnk->ld);
else if (lnk)
erts_link_release(lnk);
return reds_consumed;
@@ -12542,7 +13189,7 @@ erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds)
ErtsProcExitContext *ctxt = (ErtsProcExitContext *) vctxt;
Process *c_p = ((ErtsProcExitContext *) vctxt)->c_p;
Eterm reason = ((ErtsProcExitContext *) vctxt)->reason;
- ErtsLinkData *ldp = NULL;
+ ErtsELink *elnk = NULL;
switch (lnk->type) {
case ERTS_LNK_TYPE_PROC:
@@ -12574,8 +13221,8 @@ erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds)
int code;
if (is_immed(reason)) {
- dlnk = erts_link_to_other(lnk, &ldp);
- dist = ((ErtsLinkDataExtended *) ldp)->dist;
+ dlnk = erts_link_to_other(lnk, &elnk);
+ dist = elnk->dist;
ASSERT(is_external_pid(lnk->other.item));
dep = external_pid_dist_entry(lnk->other.item);
@@ -12583,7 +13230,7 @@ erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds)
ASSERT(dep != erts_this_dist_entry);
if (!erts_link_dist_delete(dlnk))
- ldp = NULL;
+ elnk = NULL;
code = erts_dsig_prepare(&ctx, dep, c_p, 0, ERTS_DSP_NO_LOCK, 1, 1, 0);
switch (code) {
@@ -12612,8 +13259,8 @@ erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds)
break;
}
- if (ldp)
- erts_link_release_both(ldp);
+ if (elnk)
+ erts_link_release_both(&elnk->ld);
else if (lnk)
erts_link_release(lnk);
return 1;
@@ -12694,6 +13341,7 @@ enum continue_exit_phase {
ERTS_CONTINUE_EXIT_HANDLE_PROC_SIG,
ERTS_CONTINUE_EXIT_DIST_LINKS,
ERTS_CONTINUE_EXIT_DIST_MONITORS,
+ ERTS_CONTINUE_EXIT_DIST_PEND_SPAWN_MONITORS,
ERTS_CONTINUE_EXIT_DONE,
};
@@ -12943,6 +13591,8 @@ restart:
trap_state->pectxt.reason = trap_state->reason;
trap_state->pectxt.dist_links = NULL;
trap_state->pectxt.dist_monitors = NULL;
+ trap_state->pectxt.pend_spawn_monitors = NULL;
+ trap_state->pectxt.wait_pend_spawn_monitor = NULL;
trap_state->pectxt.dist_state = NIL;
trap_state->pectxt.yield = 0;
@@ -12999,8 +13649,11 @@ restart:
case ERTS_CONTINUE_EXIT_HANDLE_PROC_SIG: {
Sint r = reds;
- if (!erts_proc_sig_handle_exit(p, &r))
+ if (!erts_proc_sig_handle_exit(p, &r,
+ &trap_state->pectxt.pend_spawn_monitors,
+ trap_state->reason)) {
goto yield;
+ }
reds -= r;
@@ -13065,6 +13718,31 @@ restart:
if (reds <= 0 || trap_state->pectxt.yield)
goto yield;
+ trap_state->phase = ERTS_CONTINUE_EXIT_DIST_PEND_SPAWN_MONITORS;
+ }
+ case ERTS_CONTINUE_EXIT_DIST_PEND_SPAWN_MONITORS: {
+
+ if (is_not_nil(trap_state->pectxt.dist_state))
+ goto continue_dist_send;
+
+ if (trap_state->pectxt.wait_pend_spawn_monitor) {
+ ErtsMonitor *mon = trap_state->pectxt.wait_pend_spawn_monitor;
+ trap_state->pectxt.wait_pend_spawn_monitor = NULL;
+ reds -= (proc_exit_handle_pend_spawn_monitors(
+ mon, (void *) &trap_state->pectxt, reds));
+ if (reds <= 0 || trap_state->pectxt.yield)
+ goto yield;
+ }
+
+ reds = erts_monitor_tree_foreach_delete_yielding(
+ &trap_state->pectxt.pend_spawn_monitors,
+ proc_exit_handle_pend_spawn_monitors,
+ (void *) &trap_state->pectxt,
+ &trap_state->yield_state,
+ reds);
+ if (reds <= 0 || trap_state->pectxt.yield)
+ goto yield;
+
trap_state->phase = ERTS_CONTINUE_EXIT_DONE;
}
case ERTS_CONTINUE_EXIT_DONE: {
@@ -13284,9 +13962,6 @@ erts_program_counter_info(fmtfn_t to, void *to_arg, Process *p)
erts_print(to, to_arg, "Program counter: %p (", p->i);
print_function_from_pc(to, to_arg, p->i);
erts_print(to, to_arg, ")\n");
- erts_print(to, to_arg, "CP: %p (", p->cp);
- print_function_from_pc(to, to_arg, p->cp);
- erts_print(to, to_arg, ")\n");
state = erts_atomic32_read_acqb(&p->state);
if (!(state & (ERTS_PSFLG_RUNNING
| ERTS_PSFLG_RUNNING_SYS
@@ -13563,9 +14238,6 @@ static void print_current_process_info(fmtfn_t to, void *to_arg,
erts_print(to, to_arg, "Current Process Program counter: %p (", p->i);
print_function_from_pc(to, to_arg, p->i);
erts_print(to, to_arg, ")\n");
- erts_print(to, to_arg, "Current Process CP: %p (", p->cp);
- print_function_from_pc(to, to_arg, p->cp);
- erts_print(to, to_arg, ")\n");
/* Getting this stacktrace can segfault if we are very very
unlucky if called while a process is being garbage collected.
@@ -13597,7 +14269,7 @@ static void print_current_process_info(fmtfn_t to, void *to_arg,
*
* A BIF that calls this should make sure to schedule out to never come back:
* erts_halt(code);
- * ERTS_BIF_YIELD1(bif_export[BIF_erlang_halt_1], BIF_P, NIL);
+ * ERTS_BIF_YIELD1(&bif_trap_export[BIF_erlang_halt_1], BIF_P, NIL);
*/
void erts_halt(int code)
{
diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h
index 3c606fe9eb..9e5ea73869 100644
--- a/erts/emulator/beam/erl_process.h
+++ b/erts/emulator/beam/erl_process.h
@@ -812,7 +812,7 @@ erts_reset_max_len(ErtsRunQueue *rq, ErtsRunQueueInfo *rqi)
#define ERTS_PSD_SCHED_ID 2
#define ERTS_PSD_CALL_TIME_BP 3
#define ERTS_PSD_DELAYED_GC_TASK_QS 4
-#define ERTS_PSD_NIF_TRAP_EXPORT 5
+#define ERTS_PSD_NFUNC_TRAP_WRAPPER 5
#define ERTS_PSD_ETS_OWNED_TABLES 6
#define ERTS_PSD_ETS_FIXED_TABLES 7
#define ERTS_PSD_DIST_ENTRY 8
@@ -849,8 +849,8 @@ typedef struct {
#define ERTS_PSD_DELAYED_GC_TASK_QS_GET_LOCKS ERTS_PROC_LOCK_MAIN
#define ERTS_PSD_DELAYED_GC_TASK_QS_SET_LOCKS ERTS_PROC_LOCK_MAIN
-#define ERTS_PSD_NIF_TRAP_EXPORT_GET_LOCKS ERTS_PROC_LOCK_MAIN
-#define ERTS_PSD_NIF_TRAP_EXPORT_SET_LOCKS ERTS_PROC_LOCK_MAIN
+#define ERTS_PSD_NFUNC_TRAP_WRAPPER_GET_LOCKS ERTS_PROC_LOCK_MAIN
+#define ERTS_PSD_NFUNC_TRAP_WRAPPER_SET_LOCKS ERTS_PROC_LOCK_MAIN
#define ERTS_PSD_ETS_OWNED_TABLES_GET_LOCKS ERTS_PROC_LOCK_STATUS
#define ERTS_PSD_ETS_OWNED_TABLES_SET_LOCKS ERTS_PROC_LOCK_STATUS
@@ -975,7 +975,6 @@ struct process {
unsigned max_arg_reg; /* Maximum number of argument registers available. */
Eterm def_arg_reg[6]; /* Default array for argument registers. */
- BeamInstr* cp; /* (untagged) Continuation pointer (for threaded code). */
BeamInstr* i; /* Program counter for threaded code. */
Sint catches; /* Number of catches on stack */
Sint fcalls; /*
@@ -993,6 +992,7 @@ struct process {
Process *next; /* Pointer to next process in run queue */
+ Sint64 uniq; /* Used for process unique integer */
ErtsSignalPrivQueues sig_qs; /* Signal queues */
ErtsBifTimers *bif_timers; /* Bif timers aiming at this process */
@@ -1326,12 +1326,46 @@ void erts_check_for_holes(Process* p);
* Possible flags for the flags field in ErlSpawnOpts below.
*/
-#define SPO_LINK 1
-#define SPO_USE_ARGS 2
-#define SPO_MONITOR 4
-#define SPO_SYSTEM_PROC 8
-#define SPO_OFF_HEAP_MSGQ 16
-#define SPO_ON_HEAP_MSGQ 32
+#define SPO_IX_LINK 0
+#define SPO_IX_MONITOR 1
+#define SPO_IX_SYSTEM_PROC 2
+#define SPO_IX_OFF_HEAP_MSGQ 3
+#define SPO_IX_ON_HEAP_MSGQ 4
+#define SPO_IX_MIN_HEAP_SIZE 5
+#define SPO_IX_MIN_VHEAP_SIZE 6
+#define SPO_IX_PRIORITY 7
+#define SPO_IX_MAX_GEN_GCS 8
+#define SPO_IX_MAX_HEAP_SIZE 9
+#define SPO_IX_SCHEDULER 10
+#define SPO_IX_ASYNC 11
+#define SPO_IX_NO_SMSG 12
+#define SPO_IX_NO_EMSG 13
+
+#define SPO_NO_INDICES (SPO_IX_ASYNC+1)
+
+#define SPO_LINK (1 << SPO_IX_LINK)
+#define SPO_MONITOR (1 << SPO_IX_MONITOR)
+#define SPO_SYSTEM_PROC (1 << SPO_IX_SYSTEM_PROC)
+#define SPO_OFF_HEAP_MSGQ (1 << SPO_IX_OFF_HEAP_MSGQ)
+#define SPO_ON_HEAP_MSGQ (1 << SPO_IX_ON_HEAP_MSGQ)
+#define SPO_MIN_HEAP_SIZE (1 << SPO_IX_MIN_HEAP_SIZE)
+#define SPO_MIN_VHEAP_SIZE (1 << SPO_IX_MIN_VHEAP_SIZE)
+#define SPO_PRIORITY (1 << SPO_IX_PRIORITY)
+#define SPO_MAX_GEN_GCS (1 << SPO_IX_MAX_GEN_GCS)
+#define SPO_MAX_HEAP_SIZE (1 << SPO_IX_MAX_HEAP_SIZE)
+#define SPO_SCHEDULER (1 << SPO_IX_SCHEDULER)
+#define SPO_ASYNC (1 << SPO_IX_ASYNC)
+#define SPO_NO_SMSG (1 << SPO_IX_NO_SMSG)
+#define SPO_NO_EMSG (1 << SPO_IX_NO_EMSG)
+
+#define SPO_MAX_FLAG SPO_NO_EMSG
+
+#define SPO_USE_ARGS \
+ (SPO_MIN_HEAP_SIZE \
+ | SPO_PRIORITY \
+ | SPO_MAX_GEN_GCS \
+ | SPO_MAX_HEAP_SIZE \
+ | SPO_SCHEDULER)
extern int ERTS_WRITE_UNLIKELY(erts_default_spo_flags);
@@ -1341,7 +1375,23 @@ extern int ERTS_WRITE_UNLIKELY(erts_default_spo_flags);
typedef struct {
int flags;
int error_code; /* Error code returned from create_process(). */
- Eterm mref; /* Monitor ref returned (if SPO_MONITOR was given). */
+ Eterm mref; /* Monitor ref returned (if SPO_MONITOR was given).
+ (output if local; input if distributed) */
+
+ int multi_set;
+
+ Eterm tag; /* If SPO_ASYNC */
+ Eterm opts; /* Option list for seq-trace... */
+
+ /* Input fields used for distributed spawn only */
+ Eterm parent_id;
+ Eterm group_leader;
+ Eterm mfa;
+ DistEntry *dist_entry;
+ ErtsMonLnkDist *mld; /* copied from dist_entry->mld */
+ ErtsDistExternal *edep;
+ ErlHeapFragment *ede_hfrag;
+ Eterm token;
/*
* The following items are only initialized if the SPO_USE_ARGS flag is set.
@@ -1354,6 +1404,7 @@ typedef struct {
Uint max_heap_size; /* Maximum heap size in words */
Uint max_heap_flags; /* Maximum heap flags (kill | log) */
int scheduler;
+
} ErlSpawnOpts;
/*
@@ -1541,6 +1592,7 @@ extern int erts_system_profile_ts_type;
#define SEQ_TRACE_SEND (1 << 0)
#define SEQ_TRACE_RECEIVE (1 << 1)
#define SEQ_TRACE_PRINT (1 << 2)
+/* (This three-bit gap contains the timestamp.) */
#define ERTS_SEQ_TRACE_FLAGS_TS_TYPE_SHIFT 3
@@ -1852,6 +1904,12 @@ Eterm erts_bind_schedulers(Process *c_p, Eterm how);
ErtsRunQueue *erts_schedid2runq(Uint);
Process *erts_schedule(ErtsSchedulerData *, Process*, int);
void erts_schedule_misc_op(void (*)(void *), void *);
+int erts_parse_spawn_opts(ErlSpawnOpts *sop, Eterm opts_list, Eterm *tag,
+ int success_message_opt);
+void
+erts_send_local_spawn_reply(Process *parent, ErtsProcLocks parent_locks,
+ Process *child, Eterm tag, Eterm ref,
+ Eterm result, Eterm token);
Eterm erl_create_process(Process*, Eterm, Eterm, Eterm, ErlSpawnOpts*);
void erts_set_self_exiting(Process *, Eterm);
void erts_do_exit_process(Process*, Eterm);
@@ -1882,12 +1940,17 @@ typedef struct {
Eterm reason;
ErtsLink *dist_links;
ErtsMonitor *dist_monitors;
+ ErtsMonitor *pend_spawn_monitors;
+ ErtsMonitor *wait_pend_spawn_monitor;
Eterm dist_state;
int yield;
} ErtsProcExitContext;
int erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt, Sint reds);
int erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds);
+void erts_proc_exit_dist_demonitor(Process *c_p, DistEntry *dep, Uint32 conn_id,
+ Eterm ref, Eterm watched);
+
Eterm erts_get_process_priority(erts_aint32_t state);
Eterm erts_set_process_priority(Process *p, Eterm prio);
@@ -1917,6 +1980,7 @@ Uint erts_debug_nbalance(void);
#define ERTS_DEBUG_WAIT_COMPLETED_DEALLOCATIONS (1 << 0)
#define ERTS_DEBUG_WAIT_COMPLETED_TIMER_CANCELLATIONS (1 << 1)
#define ERTS_DEBUG_WAIT_COMPLETED_AUX_WORK (1 << 2)
+#define ERTS_DEBUG_WAIT_COMPLETED_THREAD_PROGRESS (1 << 3)
int erts_debug_wait_completed(Process *c_p, int flags);
@@ -2089,10 +2153,10 @@ erts_psd_set(Process *p, int ix, void *data)
#define ERTS_PROC_SET_DELAYED_GC_TASK_QS(P, PBT) \
((ErtsProcSysTaskQs *) erts_psd_set((P), ERTS_PSD_DELAYED_GC_TASK_QS, (void *) (PBT)))
-#define ERTS_PROC_GET_NIF_TRAP_EXPORT(P) \
- erts_psd_get((P), ERTS_PSD_NIF_TRAP_EXPORT)
-#define ERTS_PROC_SET_NIF_TRAP_EXPORT(P, NTE) \
- erts_psd_set((P), ERTS_PSD_NIF_TRAP_EXPORT, (void *) (NTE))
+#define ERTS_PROC_GET_NFUNC_TRAP_WRAPPER(P) \
+ erts_psd_get((P), ERTS_PSD_NFUNC_TRAP_WRAPPER)
+#define ERTS_PROC_SET_NFUNC_TRAP_WRAPPER(P, NTE) \
+ erts_psd_set((P), ERTS_PSD_NFUNC_TRAP_WRAPPER, (void *) (NTE))
#define ERTS_PROC_GET_DIST_ENTRY(P) \
((DistEntry *) erts_psd_get((P), ERTS_PSD_DIST_ENTRY))
@@ -2644,6 +2708,7 @@ erts_get_atom_cache_map(Process *c_p)
* erl_process.c on windows.
*/
# define ERTS_TIME2REDS_IMPL__ erts_time2reds__
+Sint64 erts_time2reds(ErtsMonotonicTime start, ErtsMonotonicTime end);
#else
# define ERTS_TIME2REDS_IMPL__ erts_time2reds
#endif
@@ -2696,7 +2761,9 @@ ERTS_GLB_INLINE void erts_sched_poke(ErtsSchedulerSleepInfo *ssi);
void erts_aux_thread_poke(void);
ERTS_GLB_INLINE Uint32 erts_sched_local_random_hash_64_to_32_shift(Uint64 key);
ERTS_GLB_INLINE Uint32 erts_sched_local_random(Uint additional_seed);
-
+#ifdef DEBUG
+ERTS_GLB_INLINE float erts_sched_local_random_float(Uint additional_seed);
+#endif
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
@@ -2746,6 +2813,20 @@ Uint32 erts_sched_local_random(Uint additional_seed)
return erts_sched_local_random_hash_64_to_32_shift(seed);
}
+#ifdef DEBUG
+
+/*
+ * This function returns a random float between 0.0 and 1.0.
+ */
+ERTS_GLB_INLINE
+float erts_sched_local_random_float(Uint additional_seed)
+{
+ Uint32 rnd = erts_sched_local_random(additional_seed);
+ return (float)(((double)rnd)/((double)ERTS_UINT32_MAX));
+}
+
+#endif /* #ifdef DEBUG */
+
#endif /* #if ERTS_GLB_INLINE_INCL_FUNC_DEF */
diff --git a/erts/emulator/beam/erl_process_dict.c b/erts/emulator/beam/erl_process_dict.c
index 64ee483079..248da52db5 100644
--- a/erts/emulator/beam/erl_process_dict.c
+++ b/erts/emulator/beam/erl_process_dict.c
@@ -1052,7 +1052,7 @@ static unsigned int next_array_size(unsigned int need)
1342177280UL,
2684354560UL
};
- int hi = sizeof(tab) / sizeof(Uint) - 1;
+ int hi = sizeof(tab) / sizeof(tab[0]) - 1;
int lo = 1;
int cur = 4;
diff --git a/erts/emulator/beam/erl_process_dump.c b/erts/emulator/beam/erl_process_dump.c
index a6c1ae14b5..69ff526e14 100644
--- a/erts/emulator/beam/erl_process_dump.c
+++ b/erts/emulator/beam/erl_process_dump.c
@@ -36,6 +36,7 @@
#define ERTS_WANT_EXTERNAL_TAGS
#include "external.h"
#include "erl_proc_sig_queue.h"
+#include "erl_global_literals.h"
#define PTR_FMT "%bpX"
#define ETERM_FMT "%beX"
@@ -840,6 +841,12 @@ dump_literals(fmtfn_t to, void *to_arg)
erts_rlock_old_code(code_ix);
erts_print(to, to_arg, "=literals\n");
+
+ for (i = 0; i < ERTS_NUM_GLOBAL_LITERALS; i++) {
+ ErtsLiteralArea* area = erts_get_global_literal_area(i);
+ dump_module_literals(to, to_arg, area);
+ }
+
for (i = 0; i < num_lit_areas; i++) {
if (lit_areas[i]->off_heap) {
dump_module_literals(to, to_arg, lit_areas[i]);
diff --git a/erts/emulator/beam/erl_ptab.c b/erts/emulator/beam/erl_ptab.c
index 38c095fb4a..94ea030f6f 100644
--- a/erts/emulator/beam/erl_ptab.c
+++ b/erts/emulator/beam/erl_ptab.c
@@ -27,10 +27,10 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
+#include "global.h"
#define ERTS_PTAB_WANT_BIF_IMPL__
#define ERTS_PTAB_WANT_DEBUG_FUNCS__
#include "erl_ptab.h"
-#include "global.h"
#include "erl_binary.h"
typedef struct ErtsPTabListBifData_ ErtsPTabListBifData;
diff --git a/erts/emulator/beam/erl_sched_spec_pre_alloc.c b/erts/emulator/beam/erl_sched_spec_pre_alloc.c
index 9766e76a83..d24bb727ce 100644
--- a/erts/emulator/beam/erl_sched_spec_pre_alloc.c
+++ b/erts/emulator/beam/erl_sched_spec_pre_alloc.c
@@ -32,6 +32,7 @@
# include "config.h"
#endif
+#if !defined(VALGRIND) && !defined(ADDRESS_SANITIZER)
#include "erl_process.h"
#include "erl_thr_progress.h"
@@ -347,3 +348,4 @@ erts_sspa_process_remote_frees(erts_sspa_chunk_header_t *chdr,
return res;
}
+#endif /* !defined(VALGRIND) && !defined(ADDRESS_SANITIZER) */
diff --git a/erts/emulator/beam/erl_sched_spec_pre_alloc.h b/erts/emulator/beam/erl_sched_spec_pre_alloc.h
index d1d141c542..38d967caec 100644
--- a/erts/emulator/beam/erl_sched_spec_pre_alloc.h
+++ b/erts/emulator/beam/erl_sched_spec_pre_alloc.h
@@ -188,7 +188,7 @@ erts_sspa_alloc(erts_sspa_data_t *data, int cix)
erts_sspa_chunk_t *chnk;
erts_sspa_chunk_header_t *chdr;
erts_sspa_blk_t *res;
- ERTS_MSACC_PUSH_AND_SET_STATE_M_X(ERTS_MSACC_STATE_ALLOC);
+ ERTS_MSACC_PUSH_AND_SET_STATE_X(ERTS_MSACC_STATE_ALLOC);
chnk = erts_sspa_cix2chunk(data, cix);
chdr = &chnk->aligned.header;
@@ -204,13 +204,13 @@ erts_sspa_alloc(erts_sspa_data_t *data, int cix)
}
if (chdr->local.cnt <= chdr->local.lim) {
res = erts_sspa_process_remote_frees(chdr, res);
- ERTS_MSACC_POP_STATE_M_X();
+ ERTS_MSACC_POP_STATE_X();
return (char*) res;
}
else if (chdr->head.no_thr_progress_check < ERTS_SSPA_FORCE_THR_CHECK_PROGRESS)
chdr->head.no_thr_progress_check++;
ASSERT(res);
- ERTS_MSACC_POP_STATE_M_X();
+ ERTS_MSACC_POP_STATE_X();
return (char *) res;
}
diff --git a/erts/emulator/beam/erl_sys_driver.h b/erts/emulator/beam/erl_sys_driver.h
index a58f11036c..85c2d6c4ca 100644
--- a/erts/emulator/beam/erl_sys_driver.h
+++ b/erts/emulator/beam/erl_sys_driver.h
@@ -31,7 +31,7 @@
#define ERL_SYS_DRV
-typedef long ErlDrvEvent; /* An event to be selected on. */
+typedef SWord ErlDrvEvent; /* An event to be selected on. */
typedef struct _SysDriverOpts SysDriverOpts;
diff --git a/erts/emulator/beam/erl_thr_progress.c b/erts/emulator/beam/erl_thr_progress.c
index bac437efe9..ac3719f86e 100644
--- a/erts/emulator/beam/erl_thr_progress.c
+++ b/erts/emulator/beam/erl_thr_progress.c
@@ -558,7 +558,8 @@ erts_thr_progress_register_unmanaged_thread(ErtsThrPrgrCallbacks *callbacks)
ErtsThrPrgrData *
erts_thr_progress_register_managed_thread(ErtsSchedulerData *esdp,
ErtsThrPrgrCallbacks *callbacks,
- int pref_wakeup)
+ int pref_wakeup,
+ int deep_sleeper)
{
ErtsThrPrgrData *tpd = perhaps_thr_prgr_data(NULL);
int is_blocking = 0, managed;
@@ -593,6 +594,7 @@ erts_thr_progress_register_managed_thread(ErtsSchedulerData *esdp,
tpd->is_managed = 1;
tpd->is_blocking = is_blocking;
tpd->is_temporary = 0;
+ tpd->is_deep_sleeper = deep_sleeper;
#ifdef ERTS_ENABLE_LOCK_CHECK
tpd->is_delaying = 1;
#endif
@@ -763,7 +765,6 @@ leader_update(ErtsThrPrgrData *tpd)
(void) block_thread(tpd);
}
else {
- int force_wakeup_check = 0;
erts_aint32_t set_flags = ERTS_THR_PRGR_LFLG_NO_LEADER;
tpd->leader = 0;
tpd->leader_state.current = ERTS_THR_PRGR_VAL_WAITING;
@@ -788,21 +789,11 @@ leader_update(ErtsThrPrgrData *tpd)
/* Need to check umrefc again */
ETHR_MEMBAR(ETHR_StoreLoad);
refc = erts_atomic_read_nob(&intrnl->umrefc[umrefc_ix].refc);
- if (refc == 0) {
- /* Need to force wakeup check */
- force_wakeup_check = 1;
+ if (refc == 0 && got_sched_wakeups()) {
+ /* Someone need to make progress */
+ wakeup_managed(tpd->id);
}
}
-
- if ((force_wakeup_check
- || ((lflgs & (ERTS_THR_PRGR_LFLG_NO_LEADER
- | ERTS_THR_PRGR_LFLG_WAITING_UM
- | ERTS_THR_PRGR_LFLG_ACTIVE_MASK))
- == ERTS_THR_PRGR_LFLG_NO_LEADER))
- && got_sched_wakeups()) {
- /* Someone need to make progress */
- wakeup_managed(tpd->id);
- }
}
}
@@ -888,7 +879,10 @@ erts_thr_progress_prepare_wait(ErtsThrPrgrData *tpd)
== ERTS_THR_PRGR_LFLG_NO_LEADER
&& got_sched_wakeups()) {
/* Someone need to make progress */
- wakeup_managed(tpd->id);
+ if (tpd->is_deep_sleeper)
+ wakeup_managed(1);
+ else
+ wakeup_managed(tpd->id);
}
}
@@ -1072,11 +1066,13 @@ request_wakeup_managed(ErtsThrPrgrData *tpd, ErtsThrPrgrVal value)
/*
* Only managed threads that aren't in waiting state
- * are allowed to call this function.
+ * and aren't deep sleepers are allowed to call this
+ * function.
*/
ASSERT(tpd->is_managed);
ASSERT(tpd->confirmed != ERTS_THR_PRGR_VAL_WAITING);
+ ASSERT(!tpd->is_deep_sleeper);
if (has_reached_wakeup(value)) {
wakeup_managed(tpd->id);
@@ -1345,6 +1341,8 @@ thr_progress_block(ErtsThrPrgrData *tpd, int wait)
bc = erts_atomic32_read_acqb(&intrnl->misc.data.block_count);
}
}
+
+ /* tse event returned in erts_thr_progress_unblock() */
return bc;
}
diff --git a/erts/emulator/beam/erl_thr_progress.h b/erts/emulator/beam/erl_thr_progress.h
index 00a9e61407..3272926365 100644
--- a/erts/emulator/beam/erl_thr_progress.h
+++ b/erts/emulator/beam/erl_thr_progress.h
@@ -68,6 +68,7 @@ typedef struct {
int leader; /* Needs to be first in the managed threads part */
int active;
+ int is_deep_sleeper;
ErtsThrPrgrVal confirmed;
ErtsThrPrgrLeaderState leader_state;
} ErtsThrPrgrData;
@@ -124,7 +125,7 @@ extern ErtsThrPrgr erts_thr_prgr__;
void erts_thr_progress_pre_init(void);
void erts_thr_progress_init(int no_schedulers, int managed, int unmanaged);
ErtsThrPrgrData *erts_thr_progress_register_managed_thread(
- ErtsSchedulerData *esdp, ErtsThrPrgrCallbacks *, int);
+ ErtsSchedulerData *esdp, ErtsThrPrgrCallbacks *, int, int);
void erts_thr_progress_register_unmanaged_thread(ErtsThrPrgrCallbacks *);
void erts_thr_progress_active(ErtsThrPrgrData *, int on);
void erts_thr_progress_wakeup(ErtsThrPrgrData *,
diff --git a/erts/emulator/beam/erl_threads.h b/erts/emulator/beam/erl_threads.h
index e3b05ed593..efaabf5f23 100644
--- a/erts/emulator/beam/erl_threads.h
+++ b/erts/emulator/beam/erl_threads.h
@@ -494,6 +494,7 @@ ERTS_GLB_INLINE void erts_tsd_key_delete(erts_tsd_key_t key);
ERTS_GLB_INLINE void erts_tsd_set(erts_tsd_key_t key, void *value);
ERTS_GLB_INLINE void * erts_tsd_get(erts_tsd_key_t key);
ERTS_GLB_INLINE erts_tse_t *erts_tse_fetch(void);
+ERTS_GLB_INLINE void erts_tse_use(erts_tse_t *ep);
ERTS_GLB_INLINE void erts_tse_return(erts_tse_t *ep);
ERTS_GLB_INLINE void erts_tse_prepare_timed(erts_tse_t *ep);
ERTS_GLB_INLINE void erts_tse_set(erts_tse_t *ep);
@@ -2401,6 +2402,23 @@ ERTS_GLB_INLINE erts_tse_t *erts_tse_fetch(void)
return (erts_tse_t *) ethr_get_ts_event();
}
+ERTS_GLB_INLINE void erts_tse_use(erts_tse_t *ep)
+{
+ /*
+ * When enabling use on event from emulator
+ * it *must* not already be in use...
+ */
+#ifdef DEBUG
+ erts_tse_t *tmp_ep;
+ ASSERT(!(ep->iflgs & ETHR_TS_EV_BUSY));
+ tmp_ep =
+#else
+ (void)
+#endif
+ ethr_use_ts_event(ep);
+ ASSERT(ep == tmp_ep);
+}
+
ERTS_GLB_INLINE void erts_tse_return(erts_tse_t *ep)
{
ethr_leave_ts_event(ep);
@@ -2408,7 +2426,9 @@ ERTS_GLB_INLINE void erts_tse_return(erts_tse_t *ep)
ERTS_GLB_INLINE void erts_tse_prepare_timed(erts_tse_t *ep)
{
- int res = ethr_event_prepare_timed(&((ethr_ts_event *) ep)->event);
+ int res;
+ ETHR_ASSERT(ep->iflgs & ETHR_TS_EV_BUSY);
+ res = ethr_event_prepare_timed(&((ethr_ts_event *) ep)->event);
if (res != 0)
erts_thr_fatal_error(res, "prepare timed");
}
@@ -2420,6 +2440,7 @@ ERTS_GLB_INLINE void erts_tse_set(erts_tse_t *ep)
ERTS_GLB_INLINE void erts_tse_reset(erts_tse_t *ep)
{
+ ETHR_ASSERT(ep->iflgs & ETHR_TS_EV_BUSY);
ethr_event_reset(&((ethr_ts_event *) ep)->event);
}
@@ -2427,6 +2448,7 @@ ERTS_GLB_INLINE int erts_tse_wait(erts_tse_t *ep)
{
int res;
ERTS_MSACC_PUSH_AND_SET_STATE(ERTS_MSACC_STATE_SLEEP);
+ ETHR_ASSERT(ep->iflgs & ETHR_TS_EV_BUSY);
res = ethr_event_wait(&((ethr_ts_event *) ep)->event);
ERTS_MSACC_POP_STATE();
return res;
@@ -2436,6 +2458,7 @@ ERTS_GLB_INLINE int erts_tse_swait(erts_tse_t *ep, int spincount)
{
int res;
ERTS_MSACC_PUSH_AND_SET_STATE(ERTS_MSACC_STATE_SLEEP);
+ ETHR_ASSERT(ep->iflgs & ETHR_TS_EV_BUSY);
res = ethr_event_swait(&((ethr_ts_event *) ep)->event, spincount);
ERTS_MSACC_POP_STATE();
return res;
@@ -2445,6 +2468,7 @@ ERTS_GLB_INLINE int erts_tse_twait(erts_tse_t *ep, Sint64 tmo)
{
int res;
ERTS_MSACC_PUSH_AND_SET_STATE(ERTS_MSACC_STATE_SLEEP);
+ ETHR_ASSERT(ep->iflgs & ETHR_TS_EV_BUSY);
res = ethr_event_twait(&((ethr_ts_event *) ep)->event,
(ethr_sint64_t) tmo);
ERTS_MSACC_POP_STATE();
@@ -2455,6 +2479,7 @@ ERTS_GLB_INLINE int erts_tse_stwait(erts_tse_t *ep, int spincount, Sint64 tmo)
{
int res;
ERTS_MSACC_PUSH_AND_SET_STATE(ERTS_MSACC_STATE_SLEEP);
+ ETHR_ASSERT(ep->iflgs & ETHR_TS_EV_BUSY);
res = ethr_event_stwait(&((ethr_ts_event *) ep)->event,
spincount,
(ethr_sint64_t) tmo);
diff --git a/erts/emulator/beam/erl_trace.c b/erts/emulator/beam/erl_trace.c
index 8158a63d15..07aa6d7e21 100644
--- a/erts/emulator/beam/erl_trace.c
+++ b/erts/emulator/beam/erl_trace.c
@@ -830,7 +830,7 @@ trace_receive(Process* receiver,
}
int
-seq_trace_update_send(Process *p)
+seq_trace_update_serial(Process *p)
{
ErtsTracer seq_tracer = erts_get_system_seq_tracer();
ASSERT((is_tuple(SEQ_TRACE_TOKEN(p)) || is_nil(SEQ_TRACE_TOKEN(p))));
@@ -853,6 +853,18 @@ seq_trace_update_send(Process *p)
return 1;
}
+void
+erts_seq_trace_update_node_token(Eterm token)
+{
+ Eterm serial;
+ Uint serial_num;
+ SEQ_TRACE_T_SENDER(token) = erts_this_dist_entry->sysname;
+ serial = SEQ_TRACE_T_SERIAL(token);
+ serial_num = unsigned_val(serial);
+ serial_num++;
+ SEQ_TRACE_T_SERIAL(token) = make_small(serial_num);
+}
+
/* Send a sequential trace message to the sequential tracer.
* p is the caller (which contains the trace token),
@@ -929,6 +941,9 @@ seq_trace_output_generic(Eterm token, Eterm msg, Uint type,
#undef LOCAL_HEAP_SIZE
}
+
+
+
/* Send {trace_ts, Pid, return_to, {Mod, Func, Arity}, Timestamp}
* or {trace, Pid, return_to, {Mod, Func, Arity}}
*/
@@ -2228,7 +2243,7 @@ sys_msg_dispatcher_func(void *unused)
callbacks.wait = sys_msg_dispatcher_wait;
callbacks.finalize_wait = sys_msg_dispatcher_fin_wait;
- tpd = erts_thr_progress_register_managed_thread(NULL, &callbacks, 0);
+ tpd = erts_thr_progress_register_managed_thread(NULL, &callbacks, 0, 0);
while (1) {
int end_wait = 0;
@@ -2247,6 +2262,7 @@ sys_msg_dispatcher_func(void *unused)
/* Fetch current trace message queue ... */
if (!sys_message_queue) {
+ wait = 1;
erts_mtx_unlock(&smq_mtx);
end_wait = 1;
erts_thr_progress_active(tpd, 0);
@@ -2254,8 +2270,23 @@ sys_msg_dispatcher_func(void *unused)
erts_mtx_lock(&smq_mtx);
}
- while (!sys_message_queue)
- erts_cnd_wait(&smq_cnd, &smq_mtx);
+ while (!sys_message_queue) {
+ if (wait)
+ erts_cnd_wait(&smq_cnd, &smq_mtx);
+ if (sys_message_queue)
+ break;
+ wait = 1;
+ erts_mtx_unlock(&smq_mtx);
+ /*
+ * Ensure thread progress continue. We might have
+ * been the last thread to go to sleep. In that case
+ * erts_thr_progress_finalize_wait() will take care
+ * of it...
+ */
+ erts_thr_progress_finalize_wait(tpd);
+ erts_thr_progress_prepare_wait(tpd);
+ erts_mtx_lock(&smq_mtx);
+ }
local_sys_message_queue = sys_message_queue;
sys_message_queue = NULL;
diff --git a/erts/emulator/beam/erl_trace.h b/erts/emulator/beam/erl_trace.h
index 546972db6d..91633ca7f7 100644
--- a/erts/emulator/beam/erl_trace.h
+++ b/erts/emulator/beam/erl_trace.h
@@ -142,12 +142,6 @@ void monitor_generic(Process *p, Eterm type, Eterm spec);
Uint erts_trace_flag2bit(Eterm flag);
int erts_trace_flags(Eterm List,
Uint *pMask, ErtsTracer *pTracer, int *pCpuTimestamp);
-Eterm erts_bif_trace(int bif_index, Process* p, Eterm* args, BeamInstr *I);
-Eterm
-erts_bif_trace_epilogue(Process *p, Eterm result, int applying,
- Export* ep, BeamInstr *cp, Uint32 flags,
- Uint32 flags_meta, BeamInstr* I,
- ErtsTracer meta_tracer);
void erts_send_pending_trace_msgs(ErtsSchedulerData *esdp);
#define ERTS_CHK_PEND_TRACE_MSGS(ESDP) \
@@ -163,7 +157,10 @@ seq_trace_output_generic((token), (msg), (type), (receiver), NULL, (exitfrom))
void seq_trace_output_generic(Eterm token, Eterm msg, Uint type,
Eterm receiver, Process *process, Eterm exitfrom);
-int seq_trace_update_send(Process *process);
+/* Bump the sequence number if tracing is enabled; must be used before sending
+ * send trace messages. */
+int seq_trace_update_serial(Process *process);
+void erts_seq_trace_update_node_token(Eterm token);
Eterm erts_seq_trace(Process *process,
Eterm atom_type, Eterm atom_true_or_false,
diff --git a/erts/emulator/beam/erl_utils.h b/erts/emulator/beam/erl_utils.h
index 5144bbc1ee..2cac62b7f7 100644
--- a/erts/emulator/beam/erl_utils.h
+++ b/erts/emulator/beam/erl_utils.h
@@ -70,6 +70,7 @@ int erts_fit_in_bits_uint(Uint);
Sint erts_list_length(Eterm);
int erts_is_builtin(Eterm, Eterm, int);
Uint32 make_hash2(Eterm);
+Uint32 trapping_make_hash2(Eterm, Eterm*, struct process*);
Uint32 make_hash(Eterm);
Uint32 make_internal_hash(Eterm, Uint32 salt);
@@ -130,15 +131,28 @@ Sint erts_cmp_compound(Eterm, Eterm, int, int);
#define CMP_GT(a,b) ((a) != (b) && CMP((a),(b)) > 0)
#define CMP_EQ_ACTION(X,Y,Action) \
- if ((X) != (Y)) { CMP_SPEC((X),(Y),!=,Action,1); }
+ if ((X) != (Y)) { EQ_SPEC((X),(Y),!=,Action); }
#define CMP_NE_ACTION(X,Y,Action) \
- if ((X) == (Y)) { Action; } else { CMP_SPEC((X),(Y),==,Action,1); }
-#define CMP_GE_ACTION(X,Y,Action) \
- if ((X) != (Y)) { CMP_SPEC((X),(Y),<,Action,0); }
+ if ((X) == (Y)) { Action; } else { EQ_SPEC((X),(Y),==,Action); }
+
+#define EQ_SPEC(X,Y,Op,Action) \
+ if (is_both_immed(X, Y)) { \
+ if (X Op Y) { Action; }; \
+ } else if (is_float(X) && is_float(Y)) { \
+ FloatDef af, bf; \
+ GET_DOUBLE(X, af); \
+ GET_DOUBLE(Y, bf); \
+ if (af.fd Op bf.fd) { Action; }; \
+ } else { \
+ if (erts_cmp_compound(X,Y,0,1) Op 0) { Action; }; \
+ }
+
+#define CMP_GE_ACTION(X,Y,Action) \
+ if ((X) != (Y)) { CMP_SPEC((X),(Y),<,Action); }
#define CMP_LT_ACTION(X,Y,Action) \
- if ((X) == (Y)) { Action; } else { CMP_SPEC((X),(Y),>=,Action,0); }
+ if ((X) == (Y)) { Action; } else { CMP_SPEC((X),(Y),>=,Action); }
-#define CMP_SPEC(X,Y,Op,Action,EqOnly) \
+#define CMP_SPEC(X,Y,Op,Action) \
if (is_atom(X) && is_atom(Y)) { \
if (erts_cmp_atoms(X, Y) Op 0) { Action; }; \
} else if (is_both_small(X, Y)) { \
@@ -149,7 +163,26 @@ Sint erts_cmp_compound(Eterm, Eterm, int, int);
GET_DOUBLE(Y, bf); \
if (af.fd Op bf.fd) { Action; }; \
} else { \
- if (erts_cmp_compound(X,Y,0,EqOnly) Op 0) { Action; }; \
+ if (erts_cmp_compound(X,Y,0,0) Op 0) { Action; }; \
+ }
+
+/*
+ * When either operand for is_lt or is_ge is a literal, that literal is
+ * almost always an integer and almost never an atom. Therefore, only
+ * special case the comparison of small integers before calling the
+ * general compare function.
+ */
+
+#define CMP_GE_LITERAL_ACTION(X,Y,Action) \
+ if ((X) != (Y)) { CMP_LITERAL_SPEC((X),(Y),<,Action); }
+#define CMP_LT_LITERAL_ACTION(X,Y,Action) \
+ if ((X) == (Y)) { Action; } else { CMP_LITERAL_SPEC((X),(Y),>=,Action); }
+
+#define CMP_LITERAL_SPEC(X,Y,Op,Action) \
+ if (is_both_small(X, Y)) { \
+ if (signed_val(X) Op signed_val(Y)) { Action; }; \
+ } else { \
+ if (erts_cmp_compound(X,Y,0,0) Op 0) { Action; }; \
}
#define erts_float_comp(x,y) (((x)<(y)) ? -1 : (((x)==(y)) ? 0 : 1))
diff --git a/erts/emulator/beam/erlang_dtrace.d b/erts/emulator/beam/erlang_dtrace.d
index 8792138d53..2af2af2b37 100644
--- a/erts/emulator/beam/erlang_dtrace.d
+++ b/erts/emulator/beam/erlang_dtrace.d
@@ -176,7 +176,7 @@ provider erlang {
* Fired whenever a user function returns.
*
* @param p the PID (string form) of the process
- * @param mfa the m:f/a of the function
+ * @param mfa the m:f/a of the function being returned from
* @param depth the stack depth
*/
probe function__return(char *p, char *mfa, int depth);
@@ -193,7 +193,7 @@ provider erlang {
* Fired whenever a Built In Function returns.
*
* @param p the PID (string form) of the process
- * @param mfa the m:f/a of the function
+ * @param mfa the m:f/a of the function being returned from
*/
probe bif__return(char *p, char *mfa);
@@ -209,7 +209,7 @@ provider erlang {
* Fired whenever a Native Function returns.
*
* @param p the PID (string form) of the process
- * @param mfa the m:f/a of the function
+ * @param mfa the m:f/a of the function being returned from
*/
probe nif__return(char *p, char *mfa);
@@ -617,29 +617,6 @@ provider erlang {
probe driver__stop_select(char *name);
- /* Async driver pool */
-
- /**
- * Show the post-add length of the async driver thread pool member's queue.
- *
- * NOTE: The port name is not available: additional lock(s) must
- * be acquired in order to get the port name safely in an SMP
- * environment. The same is true for the aio__pool_get probe.
- *
- * @param port the Port (string form)
- * @param new queue length
- */
- probe aio_pool__add(char *, int);
-
- /**
- * Show the post-get length of the async driver thread pool member's queue.
- *
- * @param port the Port (string form)
- * @param new queue length
- */
- probe aio_pool__get(char *, int);
-
-
/*
* The set of probes called by the erlang tracer nif backend. In order
* to receive events on these you both have to enable tracing in erlang
@@ -678,16 +655,6 @@ provider erlang {
*/
/*
- * NOTE: For file_drv_return + SMP + R14B03 (and perhaps other
- * releases), the sched-thread-id will be the same as the
- * work-thread-id: erl_async.c's async_main() function
- * will call the asynchronous invoke function and then
- * immediately call the drivers ready_async function while
- * inside the same I/O worker pool thread.
- * For R14B03's source, see erl_async.c lines 302-317.
- */
-
-/*
* The set of probes for use by Erlang code ... moved to here from
* lib/runtime_tools/c_src/dtrace_user.d until a more portable solution
* is found. This move pollutes the Erlang VM with functions that are
diff --git a/erts/emulator/beam/erlang_lttng.h b/erts/emulator/beam/erlang_lttng.h
index 9b93d77f6e..213830e6fd 100644
--- a/erts/emulator/beam/erlang_lttng.h
+++ b/erts/emulator/beam/erlang_lttng.h
@@ -30,21 +30,6 @@
#include <lttng/tracepoint.h>
-/* Schedulers */
-
-TRACEPOINT_EVENT(
- org_erlang_otp,
- scheduler_poll,
- TP_ARGS(
- int, id,
- int, runnable
- ),
- TP_FIELDS(
- ctf_integer(int, scheduler, id)
- ctf_integer(int, runnable, runnable)
- )
-)
-
#ifndef LTTNG_CARRIER_STATS
#define LTTNG_CARRIER_STATS
typedef struct {
@@ -292,35 +277,6 @@ TRACEPOINT_EVENT(
)
)
-/* Async pool */
-
-TRACEPOINT_EVENT(
- org_erlang_otp,
- aio_pool_get,
- TP_ARGS(
- char*, port,
- int, length
- ),
- TP_FIELDS(
- ctf_string(port, port)
- ctf_integer(int, length, length)
- )
-)
-
-TRACEPOINT_EVENT(
- org_erlang_otp,
- aio_pool_put,
- TP_ARGS(
- char*, port,
- int, length
- ),
- TP_FIELDS(
- ctf_string(port, port)
- ctf_integer(int, length, length)
- )
-)
-
-
/* Memory Allocator */
TRACEPOINT_EVENT(
diff --git a/erts/emulator/beam/error.h b/erts/emulator/beam/error.h
index 64c08b1570..cc80fd09df 100644
--- a/erts/emulator/beam/error.h
+++ b/erts/emulator/beam/error.h
@@ -66,13 +66,13 @@
#define EXF_OFFSET EXTAG_BITS
#define EXF_BITS 7
-#define EXF_PANIC (1<<(0+EXF_OFFSET)) /* ignore catches */
-#define EXF_THROWN (1<<(1+EXF_OFFSET)) /* nonlocal return */
-#define EXF_LOG (1<<(2+EXF_OFFSET)) /* write to logger on termination */
-#define EXF_NATIVE (1<<(3+EXF_OFFSET)) /* occurred in native code */
-#define EXF_SAVETRACE (1<<(4+EXF_OFFSET)) /* save stack trace in internal form */
-#define EXF_ARGLIST (1<<(5+EXF_OFFSET)) /* has arglist for top of trace */
-#define EXF_RESTORE_NIF (1<<(6+EXF_OFFSET)) /* restore original bif/nif */
+#define EXF_PANIC (1<<(0+EXF_OFFSET)) /* ignore catches */
+#define EXF_THROWN (1<<(1+EXF_OFFSET)) /* nonlocal return */
+#define EXF_LOG (1<<(2+EXF_OFFSET)) /* write to logger on termination */
+#define EXF_NATIVE (1<<(3+EXF_OFFSET)) /* occurred in native code */
+#define EXF_SAVETRACE (1<<(4+EXF_OFFSET)) /* save stack trace in internal form */
+#define EXF_ARGLIST (1<<(5+EXF_OFFSET)) /* has arglist for top of trace */
+#define EXF_RESTORE_NFUNC (1<<(6+EXF_OFFSET)) /* restore original bif/nif */
#define EXC_FLAGBITS (((1<<(EXF_BITS+EXF_OFFSET))-1) \
& ~((1<<(EXF_OFFSET))-1))
@@ -155,10 +155,8 @@
/* No matching try clause */
#define EXC_NOTSUP ((17 << EXC_OFFSET) | EXC_ERROR)
/* Not supported */
-
#define EXC_BADMAP ((18 << EXC_OFFSET) | EXC_ERROR)
/* Bad map */
-
#define EXC_BADKEY ((19 << EXC_OFFSET) | EXC_ERROR)
/* Bad key in map */
diff --git a/erts/emulator/beam/export.c b/erts/emulator/beam/export.c
index 946ffeffb8..af1b1c2892 100644
--- a/erts/emulator/beam/export.c
+++ b/erts/emulator/beam/export.c
@@ -129,14 +129,17 @@ export_alloc(struct export_entry* tmpl_e)
obj->info.mfa.module = tmpl->info.mfa.module;
obj->info.mfa.function = tmpl->info.mfa.function;
obj->info.mfa.arity = tmpl->info.mfa.arity;
- obj->beam[0] = 0;
+ obj->bif_number = -1;
+ obj->is_bif_traced = 0;
+
+ memset(&obj->trampoline, 0, sizeof(obj->trampoline));
+
if (BeamOpsAreInitialized()) {
- obj->beam[0] = BeamOpCodeAddr(op_call_error_handler);
+ obj->trampoline.op = BeamOpCodeAddr(op_call_error_handler);
}
- obj->beam[1] = 0;
for (ix=0; ix<ERTS_NUM_CODE_IX; ix++) {
- obj->addressv[ix] = obj->beam;
+ obj->addressv[ix] = obj->trampoline.raw;
blob->entryv[ix].slot.index = -1;
blob->entryv[ix].ep = &blob->exp;
@@ -196,6 +199,19 @@ init_export_table(void)
}
}
+static struct export_entry* init_template(struct export_templ* templ,
+ Eterm m, Eterm f, unsigned a)
+{
+ templ->entry.ep = &templ->exp;
+ templ->entry.slot.index = -1;
+ templ->exp.info.mfa.module = m;
+ templ->exp.info.mfa.function = f;
+ templ->exp.info.mfa.arity = a;
+ templ->exp.bif_number = -1;
+ templ->exp.is_bif_traced = 0;
+ return &templ->entry;
+}
+
/*
* Return a pointer to the export entry for the given function,
* or NULL otherwise. Notes:
@@ -214,41 +230,15 @@ erts_find_export_entry(Eterm m, Eterm f, unsigned int a,ErtsCodeIndex code_ix);
Export*
erts_find_export_entry(Eterm m, Eterm f, unsigned int a, ErtsCodeIndex code_ix)
{
- HashValue hval = EXPORT_HASH((BeamInstr) m, (BeamInstr) f, (BeamInstr) a);
- int ix;
- HashBucket* b;
-
- ix = hval % export_tables[code_ix].htable.size;
- b = export_tables[code_ix].htable.bucket[ix];
-
- /*
- * Note: We have inlined the code from hash.c for speed.
- */
-
- while (b != (HashBucket*) 0) {
- Export* ep = ((struct export_entry*) b)->ep;
- if (ep->info.mfa.module == m &&
- ep->info.mfa.function == f &&
- ep->info.mfa.arity == a) {
- return ep;
- }
- b = b->next;
- }
+ struct export_templ templ;
+ struct export_entry *ee =
+ hash_fetch(&export_tables[code_ix].htable,
+ init_template(&templ, m, f, a),
+ (H_FUN)export_hash, (HCMP_FUN)export_cmp);
+ if (ee) return ee->ep;
return NULL;
}
-static struct export_entry* init_template(struct export_templ* templ,
- Eterm m, Eterm f, unsigned a)
-{
- templ->entry.ep = &templ->exp;
- templ->entry.slot.index = -1;
- templ->exp.info.mfa.module = m;
- templ->exp.info.mfa.function = f;
- templ->exp.info.mfa.arity = a;
- return &templ->entry;
-}
-
-
/*
* Find the export entry for a loaded function.
* Returns a NULL pointer if the given function is not loaded, or
@@ -268,8 +258,8 @@ erts_find_function(Eterm m, Eterm f, unsigned int a, ErtsCodeIndex code_ix)
ee = hash_get(&export_tables[code_ix].htable, init_template(&templ, m, f, a));
if (ee == NULL ||
- (ee->ep->addressv[code_ix] == ee->ep->beam &&
- ! BeamIsOpCode(ee->ep->beam[0], op_i_generic_breakpoint))) {
+ (ee->ep->addressv[code_ix] == ee->ep->trampoline.raw &&
+ ! BeamIsOpCode(ee->ep->trampoline.op, op_i_generic_breakpoint))) {
return NULL;
}
return ee->ep;
diff --git a/erts/emulator/beam/export.h b/erts/emulator/beam/export.h
index ae8dfa4cf8..91c4844d20 100644
--- a/erts/emulator/beam/export.h
+++ b/erts/emulator/beam/export.h
@@ -31,24 +31,72 @@
typedef struct export
{
- void* addressv[ERTS_NUM_CODE_IX]; /* Pointer to code for function. */
-
- ErtsCodeInfo info; /* MUST be just before beam[] */
-
- /*
- * beam[0]: This entry is 0 unless the 'addressv' field points to it.
- * Threaded code instruction to load function
- * (em_call_error_handler), execute BIF (em_apply_bif),
- * or a breakpoint instruction (op_i_generic_breakpoint).
- * beam[1]: Function pointer to BIF function (for BIFs only),
- * or pointer to threaded code if the module has an
- * on_load function that has not been run yet, or pointer
- * to code if function beam[0] is a breakpoint instruction.
- * Otherwise: 0.
- */
- BeamInstr beam[2];
+ /* Pointer to code for function. */
+ void* addressv[ERTS_NUM_CODE_IX];
+
+ /* Index into bif_table[], or -1 if not a BIF. */
+ int bif_number;
+ /* Non-zero if this is a BIF that's traced. */
+ int is_bif_traced;
+
+ /* This is a small trampoline function that can be used for lazy code
+ * loading, global call tracing, and so on. It's only valid when
+ * addressv points to it and should otherwise be left zeroed.
+ *
+ * Needless to say, the order of the fields below is significant. */
+ ErtsCodeInfo info;
+ union {
+ BeamInstr op; /* Union discriminant. */
+
+ struct {
+ BeamInstr op; /* op_i_generic_breakpoint */
+ BeamInstr address; /* Address of the original function */
+ } breakpoint;
+
+ /* This is used when a module refers to (imports) a function that
+ * hasn't been loaded yet. Upon loading we create an export entry which
+ * redirects to the error_handler so that the appropriate module will
+ * be loaded when called (or crash).
+ *
+ * This is also used when a module has an on_load callback as we need
+ * to defer all calls until the callback returns. `deferred` contains
+ * the address of the original function in this case, and there's an
+ * awkward condiditon where `deferred` may be set while op is zero. See
+ * erlang:finish_after_on_load/2 for details. */
+ struct {
+ BeamInstr op; /* op_call_error_handler, or 0 during the last
+ * phase of code loading when on_load is
+ * present. See above. */
+ BeamInstr deferred;
+ } not_loaded;
+
+ struct {
+ BeamInstr op; /* op_trace_jump_W */
+ BeamInstr address; /* Address of the traced function */
+ } trace;
+
+ BeamInstr raw[2]; /* For use in address comparisons, should not
+ * be tampered directly. */
+ } trampoline;
} Export;
+#ifdef DEBUG
+#define DBG_CHECK_EXPORT(EP, CX) \
+ do { \
+ if((EP)->addressv[CX] == (EP)->trampoline.raw) { \
+ /* The entry currently points at the trampoline, so the
+ * instructions must be valid. */ \
+ ASSERT(((BeamIsOpCode((EP)->trampoline.op, op_i_generic_breakpoint)) && \
+ (EP)->trampoline.breakpoint.address != 0) || \
+ ((BeamIsOpCode((EP)->trampoline.op, op_trace_jump_W)) && \
+ (EP)->trampoline.trace.address != 0) || \
+ /* (EP)->trampoline.not_loaded.deferred may be zero. */ \
+ (BeamIsOpCode((EP)->trampoline.op, op_call_error_handler))); \
+ } \
+ } while(0)
+#else
+#define DBG_CHECK_EXPORT(EP, CX) ((void)(EP), (void)(CX))
+#endif
void init_export_table(void);
void export_info(fmtfn_t, void *);
@@ -71,9 +119,6 @@ extern erts_mtx_t export_staging_lock;
#define export_staging_unlock() erts_mtx_unlock(&export_staging_lock)
#include "beam_load.h" /* For em_* extern declarations */
-#define ExportIsBuiltIn(EntryPtr) \
-(((EntryPtr)->addressv[erts_active_code_ix()] == (EntryPtr)->beam) && \
- (BeamIsOpCode((EntryPtr)->beam[0], op_apply_bif)))
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c
index c8d0bf7f40..1d1c473ab9 100644
--- a/erts/emulator/beam/external.c
+++ b/erts/emulator/beam/external.c
@@ -46,23 +46,39 @@
#include "erl_bits.h"
#include "erl_zlib.h"
#include "erl_map.h"
+#include "erl_proc_sig_queue.h"
+#include "erl_trace.h"
+
+#define PASS_THROUGH 'p'
#define in_area(ptr,start,nbytes) ((UWord)((char*)(ptr) - (char*)(start)) < (nbytes))
#define MAX_STRING_LEN 0xffff
-/* MAX value for the creation field in pid, port and reference
- for the local node and for the current external format.
-
- Larger creation values than this are allowed in external pid, port and refs
- encoded with NEW_PID_EXT, NEW_PORT_EXT and NEWER_REFERENCE_EXT.
- The point here is to prepare for future upgrade to 32-bit creation.
- OTP-19 (erts-8.0) can handle big creation values from other (newer) nodes,
- but do not use big creation values for the local node yet,
- as we still may have to communicate with older nodes.
+/*
+ * MAX value for the creation field in pid, port and reference
+ * for the old PID_EXT, PORT_EXT, REFERENCE_EXT and NEW_REFERENCE_EXT.
+ * Older nodes (OTP 19-22) will send us these so we must be able to decode them.
+ *
+ * From OTP 23 DFLAG_BIG_CREATION is mandatory so this node will always
+ * encode with new big 32-bit creations using NEW_PID_EXT, NEW_PORT_EXT
+ * and NEWER_REFERENCE_EXT.
*/
-#define ERTS_MAX_LOCAL_CREATION (3)
-#define is_valid_creation(Cre) ((unsigned)(Cre) <= ERTS_MAX_LOCAL_CREATION)
+#define ERTS_MAX_TINY_CREATION (3)
+#define is_tiny_creation(Cre) ((unsigned)(Cre) <= ERTS_MAX_TINY_CREATION)
+
+/*
+ * When 0 is used as creation, the real creation
+ * is unknown. Creation 0 on data will be changed to current
+ * creation of the node which it belongs to when it enters
+ * that node.
+ * This typically happens when a remote pid is created with
+ * list_to_pid/1 and then sent to the remote node. This behavior
+ * has the undesirable effect that a pid can be passed between nodes,
+ * and as a result of that not being equal to itself (the pid that
+ * comes back isn't equal to the original pid).
+ *
+ */
#undef ERTS_DEBUG_USE_DIST_SEP
#ifdef DEBUG
@@ -82,29 +98,15 @@
*/
#define IS_SSMALL32(x) (((Uint) (((x) >> (32-1)) + 1)) < 2)
-/*
- * Valid creations for nodes are 1, 2, or 3. 0 can also be sent
- * as creation, though. When 0 is used as creation, the real creation
- * is unknown. Creation 0 on data will be changed to current
- * creation of the node which it belongs to when it enters
- * that node.
- * This typically happens when a remote pid is created with
- * list_to_pid/1 and then sent to the remote node. This behavior
- * has the undesirable effect that a pid can be passed between nodes,
- * and as a result of that not being equal to itself (the pid that
- * comes back isn't equal to the original pid).
- *
- */
-
static Export term_to_binary_trap_export;
-static byte* enc_term(ErtsAtomCacheMap *, Eterm, byte*, Uint32, struct erl_off_heap_header** off_heap);
+static byte* enc_term(ErtsAtomCacheMap *, Eterm, byte*, Uint64, struct erl_off_heap_header** off_heap);
struct TTBEncodeContext_;
-static int enc_term_int(struct TTBEncodeContext_*,ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, Uint32 dflags,
+static int enc_term_int(struct TTBEncodeContext_*,ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, Uint64 dflags,
struct erl_off_heap_header** off_heap, Sint *reds, byte **res);
static int is_external_string(Eterm obj, Uint* lenp);
-static byte* enc_atom(ErtsAtomCacheMap *, Eterm, byte*, Uint32);
-static byte* enc_pid(ErtsAtomCacheMap *, Eterm, byte*, Uint32);
+static byte* enc_atom(ErtsAtomCacheMap *, Eterm, byte*, Uint64);
+static byte* enc_pid(ErtsAtomCacheMap *, Eterm, byte*, Uint64);
struct B2TContext_t;
static byte* dec_term(ErtsDistExternal*, ErtsHeapFactory*, byte*, Eterm*, struct B2TContext_t*, int);
static byte* dec_atom(ErtsDistExternal *, byte*, Eterm*);
@@ -112,19 +114,23 @@ static byte* dec_pid(ErtsDistExternal *, ErtsHeapFactory*, byte*, Eterm*, byte t
static Sint decoded_size(byte *ep, byte* endp, int internal_tags, struct B2TContext_t*);
static BIF_RETTYPE term_to_binary_trap_1(BIF_ALIST_1);
-static Eterm erts_term_to_binary_int(Process* p, Eterm Term, Eterm opts, int level,
- Uint flags, Binary *context_b);
+static Eterm erts_term_to_binary_int(Process* p, Sint bif_ix, Eterm Term, Eterm opts, int level,
+ Uint64 dflags, Binary *context_b, int iovec,
+ Uint fragment_size);
-static Uint encode_size_struct2(ErtsAtomCacheMap *, Eterm, unsigned);
-struct TTBSizeContext_;
-static ErtsExtSzRes encode_size_struct_int(struct TTBSizeContext_*, ErtsAtomCacheMap *acmp,
- Eterm obj, unsigned dflags, Sint *reds, Uint *res);
+static Uint encode_size_struct2(ErtsAtomCacheMap *, Eterm, Uint64);
+static ErtsExtSzRes encode_size_struct_int(TTBSizeContext*, ErtsAtomCacheMap *acmp,
+ Eterm obj, Uint64 dflags, Sint *reds, Uint *res);
static Export binary_to_term_trap_export;
static BIF_RETTYPE binary_to_term_trap_1(BIF_ALIST_1);
-static Sint transcode_dist_obuf(ErtsDistOutputBuf*, DistEntry*, Uint32 dflags, Sint reds);
-
-
+static Sint transcode_dist_obuf(ErtsDistOutputBuf*, DistEntry*, Uint64 dflags, Sint reds);
+static byte *hopefull_bit_binary(TTBEncodeContext* ctx, byte **epp, Binary *pb_val, Eterm pb_term,
+ byte *bytes, byte bitoffs, byte bitsize, Uint sz);
+static void hopefull_export(TTBEncodeContext* ctx, byte **epp, Export* exp, Uint32 dflags,
+ struct erl_off_heap_header** off_heap);
+static void store_in_vec(TTBEncodeContext *ctx, byte *ep, Binary *ohbin, Eterm ohpb,
+ byte *ohp, Uint ohsz);
void erts_init_external(void) {
erts_init_trap_export(&term_to_binary_trap_export,
@@ -221,7 +227,7 @@ erts_destroy_atom_cache_map(ErtsAtomCacheMap *acmp)
}
static ERTS_INLINE void
-insert_acache_map(ErtsAtomCacheMap *acmp, Eterm atom, Uint32 dflags)
+insert_acache_map(ErtsAtomCacheMap *acmp, Eterm atom, Uint64 dflags)
{
if (acmp && acmp->sz < ERTS_MAX_INTERNAL_ATOM_CACHE_ENTRIES) {
int ix;
@@ -237,7 +243,7 @@ insert_acache_map(ErtsAtomCacheMap *acmp, Eterm atom, Uint32 dflags)
}
static ERTS_INLINE int
-get_iix_acache_map(ErtsAtomCacheMap *acmp, Eterm atom, Uint32 dflags)
+get_iix_acache_map(ErtsAtomCacheMap *acmp, Eterm atom, Uint64 dflags)
{
if (!acmp)
return -1;
@@ -257,7 +263,7 @@ get_iix_acache_map(ErtsAtomCacheMap *acmp, Eterm atom, Uint32 dflags)
}
void
-erts_finalize_atom_cache_map(ErtsAtomCacheMap *acmp, Uint32 dflags)
+erts_finalize_atom_cache_map(ErtsAtomCacheMap *acmp, Uint64 dflags)
{
if (acmp) {
int long_atoms = 0; /* !0 if one or more atoms are longer than 255. */
@@ -295,10 +301,16 @@ erts_finalize_atom_cache_map(ErtsAtomCacheMap *acmp, Uint32 dflags)
}
Uint
-erts_encode_ext_dist_header_size(ErtsAtomCacheMap *acmp, Uint fragments)
+erts_encode_ext_dist_header_size(TTBEncodeContext *ctx,
+ ErtsAtomCacheMap *acmp,
+ Uint fragments)
{
- if (!acmp)
- return 0;
+ if (ctx->dflags & DFLAG_PENDING_CONNECT) {
+ /* HOPEFUL_DATA + hopefull flags + hopefull ix + payload ix */
+ return 1 + 8 + 4 + 4;
+ }
+ else if (!acmp && !(ctx->dflags & DFLAG_FRAGMENTS))
+ return 1; /* pass through */
else {
int fix_sz
= 1 /* VERSION_MAGIC */
@@ -306,48 +318,82 @@ erts_encode_ext_dist_header_size(ErtsAtomCacheMap *acmp, Uint fragments)
+ 1 /* dist header flags */
+ 1 /* number of internal cache entries */
;
- ASSERT(acmp->hdr_sz >= 0);
+
if (fragments > 1)
fix_sz += 8 /* sequence id */
+ 8 /* number of fragments */
;
- return fix_sz + acmp->hdr_sz;
+ if (acmp) {
+ ASSERT(acmp->hdr_sz >= 0);
+ fix_sz += acmp->hdr_sz;
+ } else {
+ ASSERT(ctx->dflags & DFLAG_FRAGMENTS);
+ }
+
+ return fix_sz;
}
}
-byte *erts_encode_ext_dist_header_setup(byte *ctl_ext, ErtsAtomCacheMap *acmp,
+byte *erts_encode_ext_dist_header_setup(TTBEncodeContext *ctx,
+ byte *ctl_ext, ErtsAtomCacheMap *acmp,
Uint fragments, Eterm from)
{
/* Maximum number of atom must be less than the maximum of a 32 bits
unsigned integer. Check is done in erl_init.c, erl_start function. */
- if (!acmp)
- return ctl_ext;
+ if (ctx->dflags & DFLAG_PENDING_CONNECT) {
+ byte *ep = ctl_ext;
+ ep -= 4;
+ ctx->payload_ixp = ep;
+ put_int32(0, ep);
+ ep -= 4;
+ ctx->hopefull_ixp = ep;
+ put_int32(ERTS_NO_HIX, ep);
+ ep -= 8;
+ ctx->hopefull_flagsp = ep;
+ put_int64(0, ep);
+ *--ep = HOPEFUL_DATA;
+ return ep;
+ }
+ else if (!acmp && !(ctx->dflags & DFLAG_FRAGMENTS)) {
+ byte *ep = ctl_ext;
+ *--ep = PASS_THROUGH;
+ return ep;
+ }
else {
int i;
byte *ep = ctl_ext;
- byte dist_hdr_flags = acmp->long_atoms ? ERTS_DIST_HDR_LONG_ATOMS_FLG : 0;
- ASSERT(acmp->hdr_sz >= 0);
- /*
- * Write cache update instructions. Note that this is a purely
- * internal format, never seen on the wire. This section is later
- * rewritten by erts_encode_ext_dist_header_finalize() while updating
- * the cache. We write the header backwards just before the
- * actual term(s).
- */
- for (i = acmp->sz-1; i >= 0; i--) {
- Uint32 aval;
- ASSERT(0 <= acmp->cix[i] && acmp->cix[i] < ERTS_ATOM_CACHE_SIZE);
- ASSERT(i == acmp->cache[acmp->cix[i]].iix);
- ASSERT(is_atom(acmp->cache[acmp->cix[i]].atom));
-
- aval = (Uint32) atom_val(acmp->cache[acmp->cix[i]].atom);
- ep -= 4;
- put_int32(aval, ep);
- ep -= 2;
- put_int16(acmp->cix[i], ep);
- }
- --ep;
- put_int8(acmp->sz, ep);
+ byte dist_hdr_flags = acmp && acmp->long_atoms ? ERTS_DIST_HDR_LONG_ATOMS_FLG : 0;
+ ASSERT(!acmp || acmp->hdr_sz >= 0);
+
+ if (acmp) {
+ /*
+ * Write cache update instructions. Note that this is a purely
+ * internal format, never seen on the wire. This section is later
+ * rewritten by erts_encode_ext_dist_header_finalize() while updating
+ * the cache. We write the header backwards just before the
+ * actual term(s).
+ */
+ for (i = acmp->sz-1; i >= 0; i--) {
+ Uint32 aval;
+ ASSERT(0 <= acmp->cix[i] && acmp->cix[i] < ERTS_ATOM_CACHE_SIZE);
+ ASSERT(i == acmp->cache[acmp->cix[i]].iix);
+ ASSERT(is_atom(acmp->cache[acmp->cix[i]].atom));
+
+ aval = (Uint32) atom_val(acmp->cache[acmp->cix[i]].atom);
+ ep -= 4;
+ put_int32(aval, ep);
+ ep -= 2;
+ put_int16(acmp->cix[i], ep);
+ }
+ --ep;
+ put_int8(acmp->sz, ep);
+ } else {
+ ASSERT(ctx->dflags & DFLAG_FRAGMENTS);
+ /* If we don't have an atom cache but are using a dist header we just put 0
+ in the atom cache size slot */
+ --ep;
+ put_int8(0, ep);
+ }
--ep;
put_int8(dist_hdr_flags, ep);
if (fragments > 1) {
@@ -382,11 +428,9 @@ byte *erts_encode_ext_dist_header_fragment(byte **hdrpp,
}
-#define PASS_THROUGH 'p'
-
Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf* ob,
DistEntry* dep,
- Uint32 dflags,
+ Uint64 dflags,
Sint reds)
{
byte *ip;
@@ -395,69 +439,34 @@ Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf* ob,
byte dist_hdr_flags;
int long_atoms;
Uint64 seq_id = 0, frag_id = 0;
- register byte *ep = ob->hdrp ? ob->hdrp : ob->extp;
+ register byte *ep = ob->eiov->iov[1].iov_base;
ASSERT(dflags & DFLAG_UTF8_ATOMS);
/*
* The buffer can have different layouts at this point depending on
* what was known when encoded:
*
- * Pending connection: CtrlTerm [, MsgTerm]
+ * Pending connection: HOPEFUL_DATA, HFlgs, HIX, PIX, CtrlTerm [, MsgTerm]
* With atom cache : VERSION_MAGIC, DIST_HEADER, ..., CtrlTerm [, MsgTerm]
* No atom cache : VERSION_MAGIC, CtrlTerm [, VERSION_MAGIC, MsgTerm]
*/
- if (ep[0] != VERSION_MAGIC || dep->transcode_ctx) {
- /*
- * Was encoded without atom cache toward pending connection.
- */
- ASSERT(ep[0] == SMALL_TUPLE_EXT || ep[0] == LARGE_TUPLE_EXT);
-
- if (~dflags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)
- && ep[0] == SMALL_TUPLE_EXT
- && ep[1] == 4
- && ep[2] == SMALL_INTEGER_EXT
- && (ep[3] == DOP_MONITOR_P ||
- ep[3] == DOP_MONITOR_P_EXIT ||
- ep[3] == DOP_DEMONITOR_P)) {
- /*
- * Receiver does not support process monitoring.
- * Suppress monitor control msg (see erts_dsig_send_monitor)
- * by converting it to an empty (tick) packet.
- */
- ob->ext_endp = ob->extp;
- return reds;
- }
- if (~dflags & (DFLAG_BIT_BINARIES | DFLAG_EXPORT_PTR_TAG
- | DFLAG_DIST_HDR_ATOM_CACHE)) {
- reds = transcode_dist_obuf(ob, dep, dflags, reds);
- if (reds < 0)
- return reds;
- ep = ob->extp;
- }
- if (dflags & DFLAG_DIST_HDR_ATOM_CACHE) {
- /*
- * Encoding was done without atom caching but receiver expects
- * a dist header, so we prepend an empty one.
- */
- *--ep = 0; /* NumberOfAtomCacheRefs */
- *--ep = DIST_HEADER;
- *--ep = VERSION_MAGIC;
- }
- goto done;
- }
- else if (ep[1] != DIST_HEADER && ep[1] != DIST_FRAG_HEADER && ep[1] != DIST_FRAG_CONT) {
- ASSERT(ep[1] == SMALL_TUPLE_EXT || ep[1] == LARGE_TUPLE_EXT);
- ASSERT(!(dflags & DFLAG_DIST_HDR_ATOM_CACHE));
- /* Node without atom cache, 'pass through' needed */
- *--ep = PASS_THROUGH;
- goto done;
+ if (ep[0] == HOPEFUL_DATA)
+ return transcode_dist_obuf(ob, dep, dflags, reds);
+
+ if (ep[0] == PASS_THROUGH) {
+ ASSERT(!(dflags & (DFLAG_DIST_HDR_ATOM_CACHE|DFLAG_FRAGMENTS)));
+ ASSERT(ob->eiov->iov[1].iov_len == 1);
+ return reds;
}
if (ep[1] == DIST_FRAG_CONT) {
- ep = ob->extp;
- goto done;
- } else if (ep[1] == DIST_FRAG_HEADER) {
+ ASSERT(ep[0] == VERSION_MAGIC);
+ ASSERT(ob->eiov->iov[1].iov_len == 18);
+ return reds;
+ }
+
+ if (ep[1] == DIST_FRAG_HEADER) {
/* skip the seq id and frag id */
seq_id = get_int64(&ep[2]);
ep += 8;
@@ -481,10 +490,7 @@ Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf* ob,
ip = &instr_buf[0];
sys_memcpy((void *) ip, (void *) ep, sz);
ep += sz;
- /* ep now points to the beginning of the control message term */
-#ifdef ERTS_DEBUG_USE_DIST_SEP
- ASSERT(*ep == VERSION_MAGIC);
-#endif
+ ASSERT(ep == (byte *) (ob->eiov->iov[1].iov_base + ob->eiov->iov[1].iov_len));
if (ci > 0) {
Uint32 flgs_buf[((ERTS_DIST_HDR_ATOM_CACHE_FLAG_BYTES(
ERTS_MAX_INTERNAL_ATOM_CACHE_ENTRIES)-1)
@@ -597,49 +603,82 @@ Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf* ob,
*--ep = DIST_HEADER;
}
*--ep = VERSION_MAGIC;
-done:
- ob->extp = ep;
- ASSERT((byte*)ob->bin->orig_bytes <= ob->extp && ob->extp < ob->ext_endp);
+
+ sz = ((byte *) ob->eiov->iov[1].iov_base) - ep;
+ ob->eiov->size += sz;
+ ob->eiov->iov[1].iov_len += sz;
+ ob->eiov->iov[1].iov_base = ep;
+
return reds < 0 ? 0 : reds;
}
ErtsExtSzRes
-erts_encode_dist_ext_size(Eterm term, Uint32 flags, ErtsAtomCacheMap *acmp, Uint* szp)
+erts_encode_dist_ext_size(Eterm term,
+ ErtsAtomCacheMap *acmp,
+ TTBSizeContext* ctx,
+ Uint* szp, Sint *redsp,
+ Sint *vlenp, Uint *fragmentsp)
{
Uint sz;
- ErtsExtSzRes res = encode_size_struct_int(NULL, acmp, term, flags, NULL, &sz);
- if (res == ERTS_EXT_SZ_OK) {
+ ErtsExtSzRes res;
+
+ ASSERT(ctx);
+ ASSERT(szp);
+ ASSERT(vlenp);
+ ASSERT(fragmentsp);
+
+ sz = *szp;
+
+ if (!ctx->wstack.wstart) {
+ /*
+ * First call for this 'term'. We might however encode
+ * multiple terms and this might not be the first term
+ * in the sequence. 'ctx' should contain valid info about
+ * about previous terms regarding fragments, and vlen.
+ * 'szp' should contain valid info about the total size
+ * of previous terms.
+ */
+ if (ctx->vlen < 0) {
+ /* First term as well */
+ ctx->vlen = 0;
+ if (ctx->dflags & DFLAG_FRAGMENTS)
+ ctx->fragment_size = ERTS_DIST_FRAGMENT_SIZE;
+ }
+
#ifndef ERTS_DEBUG_USE_DIST_SEP
- if (!(flags & (DFLAG_DIST_HDR_ATOM_CACHE | DFLAG_NO_MAGIC)))
+ if (!(ctx->dflags & (DFLAG_DIST_HDR_ATOM_CACHE|DFLAG_FRAGMENTS)))
#endif
sz++ /* VERSION_MAGIC */;
- *szp += sz;
}
- return res;
-}
-ErtsExtSzRes
-erts_encode_dist_ext_size_ctx(Eterm term, ErtsDSigSendContext *ctx, Uint* szp)
-{
- Uint sz;
- ErtsExtSzRes res = encode_size_struct_int(&ctx->u.sc, ctx->acmp, term,
- ctx->flags, &ctx->reds, &sz);
+ res = encode_size_struct_int(ctx, acmp, term, ctx->dflags, redsp, &sz);
+
if (res == ERTS_EXT_SZ_OK) {
-#ifndef ERTS_DEBUG_USE_DIST_SEP
- if (!(ctx->flags & (DFLAG_DIST_HDR_ATOM_CACHE | DFLAG_NO_MAGIC)))
-#endif
- sz++ /* VERSION_MAGIC */;
+ Uint total_size, fragments;
- *szp += sz;
+ /*
+ * Each fragment use
+ * - one element for driver header
+ * - one element for fragment header
+ * - and (at least) one for data
+ */
+ total_size = sz + ctx->extra_size;
+ fragments = (total_size - 1)/ctx->fragment_size + 1;
+
+ *szp = sz;
+ *fragmentsp = fragments;
+ *vlenp = ctx->vlen + 3*fragments;
}
+
return res;
}
ErtsExtSzRes erts_encode_ext_size_2(Eterm term, unsigned dflags, Uint *szp)
{
- ErtsExtSzRes res = encode_size_struct_int(NULL, NULL, term, dflags,
- NULL, szp);
+ ErtsExtSzRes res;
+ *szp = 0;
+ res = encode_size_struct_int(NULL, NULL, term, dflags, NULL, szp);
(*szp)++ /* VERSION_MAGIC */;
return res;
}
@@ -651,20 +690,44 @@ ErtsExtSzRes erts_encode_ext_size(Eterm term, Uint *szp)
Uint erts_encode_ext_size_ets(Eterm term)
{
- return encode_size_struct2(NULL, term, TERM_TO_BINARY_DFLAGS|DFLAG_INTERNAL_TAGS);
+ return encode_size_struct2(NULL, term,
+ TERM_TO_BINARY_DFLAGS|DFLAG_ETS_COMPRESSED);
}
-int erts_encode_dist_ext(Eterm term, byte **ext, Uint32 flags, ErtsAtomCacheMap *acmp,
- TTBEncodeContext* ctx, Sint* reds)
+int erts_encode_dist_ext(Eterm term, byte **ext, Uint64 flags, ErtsAtomCacheMap *acmp,
+ TTBEncodeContext* ctx, Uint *fragmentsp, Sint* reds)
{
- if (!ctx || !ctx->wstack.wstart) {
- #ifndef ERTS_DEBUG_USE_DIST_SEP
- if (!(flags & (DFLAG_DIST_HDR_ATOM_CACHE | DFLAG_NO_MAGIC)))
- #endif
+ int res;
+ ASSERT(ctx);
+
+ if (!ctx->wstack.wstart) {
+ ctx->cptr = *ext;
+#ifndef ERTS_DEBUG_USE_DIST_SEP
+ if (!(flags & (DFLAG_DIST_HDR_ATOM_CACHE|DFLAG_PENDING_CONNECT|DFLAG_FRAGMENTS)))
+#endif
*(*ext)++ = VERSION_MAGIC;
+#ifndef ERTS_DEBUG_USE_DIST_SEP
+ if (flags & DFLAG_PENDING_CONNECT) {
+ Sint payload_ix = ctx->vlen;
+ ASSERT(ctx->payload_ixp);
+ if (payload_ix) {
+ /* we potentially need a version magic on the payload... */
+ (*ext)++;
+ ctx->cptr = *ext;
+ put_int32(payload_ix, ctx->payload_ixp);
+ }
+ }
+#endif
}
- return enc_term_int(ctx, acmp, term, *ext, flags, NULL, reds, ext);
+ res = enc_term_int(ctx, acmp, term, *ext, flags, NULL, reds, ext);
+ if (fragmentsp)
+ *fragmentsp = res == 0 ? ctx->frag_ix + 1 : ctx->frag_ix;
+ if (flags & DFLAG_PENDING_CONNECT) {
+ ASSERT(ctx->hopefull_flagsp);
+ put_int64(ctx->hopefull_flags, ctx->hopefull_flagsp);
+ }
+ return res;
}
void erts_encode_ext(Eterm term, byte **ext)
@@ -681,7 +744,7 @@ void erts_encode_ext(Eterm term, byte **ext)
byte* erts_encode_ext_ets(Eterm term, byte *ep, struct erl_off_heap_header** off_heap)
{
- return enc_term(NULL, term, ep, TERM_TO_BINARY_DFLAGS|DFLAG_INTERNAL_TAGS,
+ return enc_term(NULL, term, ep, TERM_TO_BINARY_DFLAGS|DFLAG_ETS_COMPRESSED,
off_heap);
}
@@ -786,7 +849,7 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
ASSERT(dep);
erts_de_rlock(dep);
- ASSERT(dep->flags & DFLAG_UTF8_ATOMS);
+ ASSERT(dep->dflags & DFLAG_UTF8_ATOMS);
if ((dep->state != ERTS_DE_STATE_CONNECTED &&
@@ -796,7 +859,7 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
return ERTS_PREP_DIST_EXT_CLOSED;
}
- if (!(dep->flags & DFLAG_DIST_HDR_ATOM_CACHE)) {
+ if (!(dep->dflags & (DFLAG_DIST_HDR_ATOM_CACHE|DFLAG_FRAGMENTS))) {
/* Skip PASS_THROUGH */
ext++;
size--;
@@ -820,13 +883,14 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
edep->heap_size = -1;
edep->flags = 0;
edep->dep = dep;
+ edep->mld = dep->mld;
edep->connection_id = conn_id;
edep->data->ext_endp = ext+size;
edep->data->binp = binp;
edep->data->seq_id = 0;
edep->data->frag_id = 1;
- if (dep->flags & DFLAG_DIST_HDR_ATOM_CACHE)
+ if (dep->dflags & (DFLAG_DIST_HDR_ATOM_CACHE|DFLAG_FRAGMENTS))
edep->flags |= ERTS_DIST_EXT_DFLAG_HDR;
if (ep[1] != DIST_HEADER && ep[1] != DIST_FRAG_HEADER && ep[1] != DIST_FRAG_CONT) {
@@ -836,7 +900,7 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
edep->data->extp = ext;
}
else if (ep[1] == DIST_FRAG_CONT) {
- if (!(dep->flags & DFLAG_FRAGMENTS))
+ if (!(dep->dflags & DFLAG_FRAGMENTS))
goto bad_hdr;
edep->attab.size = 0;
edep->data->extp = ext + 1 + 1 + 8 + 8;
@@ -853,7 +917,7 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
goto bad_hdr;
if (ep[1] == DIST_FRAG_HEADER) {
- if (!(dep->flags & DFLAG_FRAGMENTS))
+ if (!(dep->dflags & DFLAG_FRAGMENTS))
goto bad_hdr;
edep->data->seq_id = get_int64(&ep[2]);
edep->data->frag_id = get_int64(&ep[2+8]);
@@ -1286,8 +1350,11 @@ static BIF_RETTYPE term_to_binary_trap_1(BIF_ALIST_1)
Eterm Term = tp[1];
Eterm Opts = tp[2];
Eterm bt = tp[3];
+ Eterm bix = tp[4];
+ Sint bif_ix = signed_val(bix);
Binary *bin = erts_magic_ref2bin(bt);
- Eterm res = erts_term_to_binary_int(BIF_P, Term, Opts, 0, 0,bin);
+ Eterm res = erts_term_to_binary_int(BIF_P, bif_ix, Term, Opts,
+ 0, 0,bin, 0, ~((Uint) 0));
if (is_non_value(res)) {
if (erts_set_gc_state(BIF_P, 1)
|| MSO(BIF_P).overhead > BIN_VHEAP_SZ(BIF_P)) {
@@ -1295,10 +1362,10 @@ static BIF_RETTYPE term_to_binary_trap_1(BIF_ALIST_1)
}
if (Opts == am_undefined)
ERTS_BIF_ERROR_TRAPPED1(BIF_P, SYSTEM_LIMIT,
- bif_export[BIF_term_to_binary_1], Term);
+ &bif_trap_export[bif_ix], Term);
else
ERTS_BIF_ERROR_TRAPPED2(BIF_P, SYSTEM_LIMIT,
- bif_export[BIF_term_to_binary_2], Term, Opts);
+ &bif_trap_export[bif_ix], Term, Opts);
}
if (is_tuple(res)) {
ASSERT(BIF_P->flags & F_DISABLE_GC);
@@ -1316,8 +1383,10 @@ HIPE_WRAPPER_BIF_DISABLE_GC(term_to_binary, 1)
BIF_RETTYPE term_to_binary_1(BIF_ALIST_1)
{
- Eterm res = erts_term_to_binary_int(BIF_P, BIF_ARG_1, am_undefined,
- 0, TERM_TO_BINARY_DFLAGS, NULL);
+ Eterm res = erts_term_to_binary_int(BIF_P, BIF_term_to_binary_1,
+ BIF_ARG_1, am_undefined,
+ 0, TERM_TO_BINARY_DFLAGS, NULL, 0,
+ ~((Uint) 0));
if (is_non_value(res)) {
ASSERT(!(BIF_P->flags & F_DISABLE_GC));
BIF_ERROR(BIF_P, SYSTEM_LIMIT);
@@ -1331,22 +1400,43 @@ BIF_RETTYPE term_to_binary_1(BIF_ALIST_1)
}
}
-HIPE_WRAPPER_BIF_DISABLE_GC(term_to_binary, 2)
+HIPE_WRAPPER_BIF_DISABLE_GC(term_to_iovec, 1)
-BIF_RETTYPE term_to_binary_2(BIF_ALIST_2)
+BIF_RETTYPE term_to_iovec_1(BIF_ALIST_1)
+{
+ Eterm res = erts_term_to_binary_int(BIF_P, BIF_term_to_iovec_1,
+ BIF_ARG_1, am_undefined,
+ 0, TERM_TO_BINARY_DFLAGS, NULL, !0,
+ ~((Uint) 0));
+ if (is_non_value(res)) {
+ ASSERT(!(BIF_P->flags & F_DISABLE_GC));
+ BIF_ERROR(BIF_P, SYSTEM_LIMIT);
+ }
+ if (is_tuple(res)) {
+ erts_set_gc_state(BIF_P, 0);
+ BIF_TRAP1(&term_to_binary_trap_export,BIF_P,res);
+ } else {
+ ASSERT(!(BIF_P->flags & F_DISABLE_GC));
+ BIF_RET(res);
+ }
+}
+
+static ERTS_INLINE int
+parse_t2b_opts(Eterm opts, Uint *flagsp, int *levelp, int *iovecp, Uint *fsizep)
{
- Process* p = BIF_P;
- Eterm Term = BIF_ARG_1;
- Eterm Flags = BIF_ARG_2;
int level = 0;
+ int iovec = 0;
Uint flags = TERM_TO_BINARY_DFLAGS;
- Eterm res;
+ Uint fsize = ~((Uint) 0); /* one fragment */
- while (is_list(Flags)) {
- Eterm arg = CAR(list_val(Flags));
+ while (is_list(opts)) {
+ Eterm arg = CAR(list_val(opts));
Eterm* tp;
if (arg == am_compressed) {
level = Z_DEFAULT_COMPRESSION;
+ }
+ else if (iovecp && arg == am_iovec) {
+ iovec = !0;
} else if (is_tuple(arg) && *(tp = tuple_val(arg)) == make_arityval(2)) {
if (tp[1] == am_minor_version && is_small(tp[2])) {
switch (signed_val(tp[2])) {
@@ -1360,34 +1450,66 @@ BIF_RETTYPE term_to_binary_2(BIF_ALIST_2)
flags = TERM_TO_BINARY_DFLAGS | DFLAG_UTF8_ATOMS;
break;
default:
- goto error;
+ return 0; /* badarg */
}
} else if (tp[1] == am_compressed && is_small(tp[2])) {
level = signed_val(tp[2]);
if (!(0 <= level && level < 10)) {
- goto error;
+ return 0; /* badarg */
}
- } else {
- goto error;
+ } else if (fsizep) {
+ if (ERTS_IS_ATOM_STR("fragment", tp[1])) {
+ if (!term_to_Uint(tp[2], &fsize))
+ return 0; /* badarg */
+ }
+ else {
+ return 0; /* badarg */
+ }
+ }
+ else {
+ return 0; /* badarg */
}
} else {
- error:
- BIF_ERROR(p, BADARG);
+ return 0; /* badarg */
}
- Flags = CDR(list_val(Flags));
+ opts = CDR(list_val(opts));
}
- if (is_not_nil(Flags)) {
- goto error;
+ if (is_not_nil(opts)) {
+ return 0; /* badarg */
}
- res = erts_term_to_binary_int(p, Term, BIF_ARG_2,
- level, flags, NULL);
+ *flagsp = flags;
+ *levelp = level;
+ if (iovecp)
+ *iovecp = iovec;
+ if (fsizep)
+ *fsizep = fsize;
+
+ return !0; /* ok */
+}
+
+HIPE_WRAPPER_BIF_DISABLE_GC(term_to_binary, 2)
+
+BIF_RETTYPE term_to_binary_2(BIF_ALIST_2)
+{
+ int level;
+ Uint flags;
+ Eterm res;
+
+ if (!parse_t2b_opts(BIF_ARG_2, &flags, &level, NULL, NULL)) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
+ res = erts_term_to_binary_int(BIF_P, BIF_term_to_binary_2,
+ BIF_ARG_1, BIF_ARG_2,
+ level, flags, NULL, 0,
+ ~((Uint) 0));
if (is_non_value(res)) {
ASSERT(!(BIF_P->flags & F_DISABLE_GC));
BIF_ERROR(BIF_P, SYSTEM_LIMIT);
}
if (is_tuple(res)) {
- erts_set_gc_state(p, 0);
+ erts_set_gc_state(BIF_P, 0);
BIF_TRAP1(&term_to_binary_trap_export,BIF_P,res);
} else {
ASSERT(!(BIF_P->flags & F_DISABLE_GC));
@@ -1395,6 +1517,67 @@ BIF_RETTYPE term_to_binary_2(BIF_ALIST_2)
}
}
+HIPE_WRAPPER_BIF_DISABLE_GC(term_to_iovec, 2)
+
+BIF_RETTYPE term_to_iovec_2(BIF_ALIST_2)
+{
+ int level;
+ Uint flags;
+ Eterm res;
+
+ if (!parse_t2b_opts(BIF_ARG_2, &flags, &level, NULL, NULL)) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
+ res = erts_term_to_binary_int(BIF_P, BIF_term_to_iovec_2,
+ BIF_ARG_1, BIF_ARG_2,
+ level, flags, NULL, !0,
+ ~((Uint) 0));
+ if (is_non_value(res)) {
+ ASSERT(!(BIF_P->flags & F_DISABLE_GC));
+ BIF_ERROR(BIF_P, SYSTEM_LIMIT);
+ }
+ if (is_tuple(res)) {
+ erts_set_gc_state(BIF_P, 0);
+ BIF_TRAP1(&term_to_binary_trap_export,BIF_P,res);
+ } else {
+ ASSERT(!(BIF_P->flags & F_DISABLE_GC));
+ BIF_RET(res);
+ }
+}
+
+Eterm
+erts_debug_term_to_binary(Process *p, Eterm term, Eterm opts)
+{
+ Eterm ret;
+ int level, iovec;
+ Uint flags;
+ Uint fsize;
+
+ if (!parse_t2b_opts(opts, &flags, &level, &iovec, &fsize)) {
+ ERTS_BIF_PREP_ERROR(ret, p, BADARG);
+ }
+ else {
+ Eterm res = erts_term_to_binary_int(p, BIF_term_to_binary_2,
+ term, opts, level, flags,
+ NULL, iovec, fsize);
+
+ if (is_non_value(res)) {
+ ASSERT(!(p->flags & F_DISABLE_GC));
+ ERTS_BIF_PREP_ERROR(ret, p, SYSTEM_LIMIT);
+ }
+ else if (is_tuple(res)) {
+ erts_set_gc_state(p, 0);
+ ERTS_BIF_PREP_TRAP1(ret, &term_to_binary_trap_export,p,res);
+ }
+ else {
+ ASSERT(!(p->flags & F_DISABLE_GC));
+ ERTS_BIF_PREP_RET(ret, res);
+ }
+ }
+ return ret;
+}
+
enum B2TState { /* order is somewhat significant */
B2TPrepare,
@@ -1804,8 +1987,8 @@ static BIF_RETTYPE binary_to_term_int(Process* p, Eterm bin, B2TContext *ctx)
case B2TBadArg:
BUMP_REDS(p, (initial_reds - ctx->reds) / B2T_BYTES_PER_REDUCTION);
- ASSERT(ctx->bif == bif_export[BIF_binary_to_term_1]
- || ctx->bif == bif_export[BIF_binary_to_term_2]);
+ ASSERT(ctx->bif == &bif_trap_export[BIF_binary_to_term_1]
+ || ctx->bif == &bif_trap_export[BIF_binary_to_term_2]);
if (is_first_call)
ERTS_BIF_PREP_ERROR(ret_val, p, BADARG);
@@ -1886,7 +2069,7 @@ BIF_RETTYPE binary_to_term_1(BIF_ALIST_1)
ctx.flags = 0;
ctx.used_bytes = 0;
ctx.trap_bin = THE_NON_VALUE;
- ctx.bif = bif_export[BIF_binary_to_term_1];
+ ctx.bif = &bif_trap_export[BIF_binary_to_term_1];
ctx.arg[0] = BIF_ARG_1;
ctx.arg[1] = THE_NON_VALUE;
return binary_to_term_int(BIF_P, BIF_ARG_1, &ctx);
@@ -1921,7 +2104,7 @@ BIF_RETTYPE binary_to_term_2(BIF_ALIST_2)
goto error;
ctx.trap_bin = THE_NON_VALUE;
- ctx.bif = bif_export[BIF_binary_to_term_2];
+ ctx.bif = &bif_trap_export[BIF_binary_to_term_2];
ctx.arg[0] = BIF_ARG_1;
ctx.arg[1] = BIF_ARG_2;
return binary_to_term_int(BIF_P, BIF_ARG_1, &ctx);
@@ -1935,7 +2118,7 @@ external_size_1(BIF_ALIST_1)
{
Process* p = BIF_P;
Eterm Term = BIF_ARG_1;
- Uint size;
+ Uint size = 0;
switch (erts_encode_ext_size(Term, &size)) {
case ERTS_EXT_SZ_SYSTEM_LIMIT:
@@ -1957,7 +2140,7 @@ external_size_1(BIF_ALIST_1)
Eterm
external_size_2(BIF_ALIST_2)
{
- Uint size;
+ Uint size = 0;
Uint flags = TERM_TO_BINARY_DFLAGS;
while (is_list(BIF_ARG_2)) {
@@ -2006,7 +2189,7 @@ external_size_2(BIF_ALIST_2)
}
static Eterm
-erts_term_to_binary_simple(Process* p, Eterm Term, Uint size, int level, Uint flags)
+erts_term_to_binary_simple(Process* p, Eterm Term, Uint size, int level, Uint64 dflags)
{
Eterm bin;
size_t real_size;
@@ -2022,7 +2205,7 @@ erts_term_to_binary_simple(Process* p, Eterm Term, Uint size, int level, Uint fl
bytes = erts_alloc(ERTS_ALC_T_TMP, size);
}
- if ((endp = enc_term(NULL, Term, bytes, flags, NULL))
+ if ((endp = enc_term(NULL, Term, bytes, dflags, NULL))
== NULL) {
erts_exit(ERTS_ERROR_EXIT, "%s, line %d: bad term: %x\n",
__FILE__, __LINE__, Term);
@@ -2067,7 +2250,7 @@ erts_term_to_binary_simple(Process* p, Eterm Term, Uint size, int level, Uint fl
bin = new_binary(p, (byte *)NULL, size);
bytes = binary_bytes(bin);
bytes[0] = VERSION_MAGIC;
- if ((endp = enc_term(NULL, Term, bytes+1, flags, NULL))
+ if ((endp = enc_term(NULL, Term, bytes+1, dflags, NULL))
== NULL) {
erts_exit(ERTS_ERROR_EXIT, "%s, line %d: bad term: %x\n",
__FILE__, __LINE__, Term);
@@ -2082,8 +2265,8 @@ erts_term_to_binary_simple(Process* p, Eterm Term, Uint size, int level, Uint fl
}
Eterm
-erts_term_to_binary(Process* p, Eterm Term, int level, Uint flags) {
- Uint size;
+erts_term_to_binary(Process* p, Eterm Term, int level, Uint64 flags) {
+ Uint size = 0;
switch (encode_size_struct_int(NULL, NULL, Term, flags, NULL, &size)) {
case ERTS_EXT_SZ_SYSTEM_LIMIT:
return THE_NON_VALUE;
@@ -2121,6 +2304,8 @@ static int ttb_context_destructor(Binary *context_bin)
erts_bin_free(context->s.ec.result_bin);
context->s.ec.result_bin = NULL;
}
+ if (context->s.ec.iov)
+ erts_free(ERTS_ALC_T_T2B_VEC, context->s.ec.iov);
break;
case TTBCompress:
erl_zlib_deflate_finish(&(context->s.cc.stream));
@@ -2142,8 +2327,60 @@ static int ttb_context_destructor(Binary *context_bin)
return 1;
}
-static Eterm erts_term_to_binary_int(Process* p, Eterm Term, Eterm opts, int level,
- Uint flags, Binary *context_b)
+Uint
+erts_ttb_iov_size(int use_termv, Sint vlen, Uint fragments)
+{
+ Uint sz;
+ ASSERT(vlen > 0);
+ ASSERT(fragments > 0);
+ sz = sizeof(SysIOVec)*vlen;
+ sz += sizeof(ErlDrvBinary *)*vlen;
+ if (use_termv)
+ sz += sizeof(Eterm)*vlen;
+ sz += sizeof(ErlIOVec *)*fragments;
+ sz += sizeof(ErlIOVec)*fragments;
+ ASSERT(sz % sizeof(void*) == 0);
+ return sz;
+}
+
+void
+erts_ttb_iov_init(TTBEncodeContext *ctx, int use_termv, char *ptr,
+ Sint vlen, Uint fragments, Uint fragment_size)
+{
+ ctx->vlen = 0;
+ ctx->size = 0;
+
+ ctx->iov = (SysIOVec *) ptr;
+ ptr += sizeof(SysIOVec)*vlen;
+ ASSERT(((UWord) ptr) % sizeof(void *) == 0);
+
+ ctx->binv = (ErlDrvBinary **) ptr;
+ ptr += sizeof(ErlDrvBinary *)*vlen;
+
+ if (!use_termv)
+ ctx->termv = NULL;
+ else {
+ ctx->termv = (Eterm *) ptr;
+ ptr += sizeof(Eterm)*vlen;
+ }
+
+ ctx->fragment_eiovs = (ErlIOVec *) ptr;
+ ptr += sizeof(ErlIOVec)*fragments;
+ ASSERT(((UWord) ptr) % sizeof(void *) == 0);
+
+ ctx->frag_ix = -1;
+ ctx->fragment_size = fragment_size;
+
+#ifdef DEBUG
+ ctx->cptr = NULL;
+ ctx->debug_fragments = fragments;
+ ctx->debug_vlen = vlen;
+#endif
+}
+
+static Eterm erts_term_to_binary_int(Process* p, Sint bif_ix, Eterm Term, Eterm opts,
+ int level, Uint64 dflags, Binary *context_b,
+ int iovec, Uint fragment_size)
{
Eterm *hp;
Eterm res;
@@ -2157,6 +2394,10 @@ static Eterm erts_term_to_binary_int(Process* p, Eterm Term, Eterm opts, int lev
TTBContext c_buff;
TTBContext *context = &c_buff;
+ ASSERT(bif_ix > 0 && IS_USMALL(!0, bif_ix));
+ ASSERT(bif_ix == BIF_term_to_binary_1 || bif_ix == BIF_term_to_binary_2
+ || bif_ix == BIF_term_to_iovec_1 || bif_ix == BIF_term_to_iovec_2);
+
#define EXPORT_CONTEXT() \
do { \
if (context_b == NULL) { \
@@ -2169,36 +2410,47 @@ static Eterm erts_term_to_binary_int(Process* p, Eterm Term, Eterm opts, int lev
#define RETURN_STATE() \
do { \
- hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE + 1 + 3); \
+ hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE + 1 + 4); \
c_term = erts_mk_magic_ref(&hp, &MSO(p), context_b); \
- res = TUPLE3(hp, Term, opts, c_term); \
+ res = TUPLE4(hp, Term, opts, c_term, make_small(bif_ix)); \
BUMP_ALL_REDS(p); \
return res; \
} while (0);
-
if (context_b == NULL) {
/* Setup enough to get started */
context->state = TTBSize;
context->alive = 1;
- context->s.sc.wstack.wstart = NULL;
- context->s.sc.flags = flags;
+ ERTS_INIT_TTBSizeContext(&context->s.sc, dflags);
context->s.sc.level = level;
+ context->s.sc.fragment_size = fragment_size;
+ if (!level) {
+ context->s.sc.vlen = iovec ? 0 : -1;
+ context->s.sc.iovec = iovec;
+ }
+ else {
+ context->s.sc.vlen = -1;
+ context->s.sc.iovec = 0;
+ }
} else {
context = ERTS_MAGIC_BIN_DATA(context_b);
- }
+ }
+
/* Initialization done, now we will go through the states */
for (;;) {
switch (context->state) {
case TTBSize:
{
- Uint size;
+ Uint size, fragments = 1;
Binary *result_bin;
- int level;
- Uint flags;
- /* Try for fast path */
+ int level = context->s.sc.level;
+ Sint vlen;
+ iovec = context->s.sc.iovec;
+ fragment_size = context->s.sc.fragment_size;
+ size = 1; /* VERSION_MAGIC */
switch (encode_size_struct_int(&context->s.sc, NULL, Term,
- context->s.sc.flags, &reds, &size)) {
+ context->s.sc.dflags, &reds,
+ &size)) {
case ERTS_EXT_SZ_SYSTEM_LIMIT:
BUMP_REDS(p, (initial_reds - reds) / TERM_TO_BINARY_LOOP_FACTOR);
return THE_NON_VALUE;
@@ -2209,14 +2461,23 @@ static Eterm erts_term_to_binary_int(Process* p, Eterm Term, Eterm opts, int lev
case ERTS_EXT_SZ_OK:
break;
}
- ++size; /* VERSION_MAGIC */
/* Move these to next state */
- flags = context->s.sc.flags;
- level = context->s.sc.level;
- if (size <= ERL_ONHEAP_BIN_LIMIT) {
+ dflags = context->s.sc.dflags;
+ vlen = context->s.sc.vlen;
+ if (vlen >= 0) {
+ Uint total_size = size + context->s.sc.extra_size;
+ fragments = (total_size - 1)/fragment_size + 1;
+ vlen += 3*fragments;
+ ASSERT(vlen);
+ }
+ else if (size <= ERL_ONHEAP_BIN_LIMIT) {
/* Finish in one go */
res = erts_term_to_binary_simple(p, Term, size,
- level, flags);
+ level, dflags);
+ if (iovec) {
+ Eterm *hp = HAlloc(p, 2);
+ res = CONS(hp, res, NIL);
+ }
BUMP_REDS(p, 1);
return res;
}
@@ -2225,37 +2486,156 @@ static Eterm erts_term_to_binary_int(Process* p, Eterm Term, Eterm opts, int lev
result_bin->orig_bytes[0] = (byte)VERSION_MAGIC;
/* Next state immediately, no need to export context */
context->state = TTBEncode;
- context->s.ec.flags = flags;
+ ERTS_INIT_TTBEncodeContext(&context->s.ec, dflags);
context->s.ec.level = level;
- context->s.ec.wstack.wstart = NULL;
context->s.ec.result_bin = result_bin;
+ context->s.ec.iovec = iovec;
+ if (vlen >= 0) {
+ Uint sz = erts_ttb_iov_size(!0, vlen, fragments);
+ char *ptr = (char *) erts_alloc(ERTS_ALC_T_T2B_VEC, sz);
+ erts_ttb_iov_init(&context->s.ec, !0, ptr, vlen,
+ fragments, fragment_size);
+ context->s.ec.cptr = (byte *) &result_bin->orig_bytes[0];
+ }
break;
}
case TTBEncode:
{
- byte *endp;
+ byte *endp, *tmp;
byte *bytes = (byte *) context->s.ec.result_bin->orig_bytes;
size_t real_size;
Binary *result_bin;
+ Sint realloc_offset;
+ Uint fragments;
- flags = context->s.ec.flags;
- if (enc_term_int(&context->s.ec, NULL,Term, bytes+1, flags, NULL, &reds, &endp) < 0) {
+ dflags = context->s.ec.dflags;
+ if (enc_term_int(&context->s.ec, NULL,Term, bytes+1, dflags,
+ NULL, &reds, &endp) < 0) {
EXPORT_CONTEXT();
RETURN_STATE();
}
real_size = endp - bytes;
+ tmp = (byte *) &context->s.ec.result_bin->orig_bytes[0];
result_bin = erts_bin_realloc(context->s.ec.result_bin,real_size);
+ realloc_offset = (byte *) &result_bin->orig_bytes[0] - tmp;
level = context->s.ec.level;
BUMP_REDS(p, (initial_reds - reds) / TERM_TO_BINARY_LOOP_FACTOR);
if (level == 0 || real_size < 6) { /* We are done */
+ Sint cbin_refc_diff;
+ Eterm result, rb_term, *hp, *hp_end;
+ Uint hsz;
+ int ix;
+ SysIOVec *iov;
+ Eterm *termv;
return_normal:
+ fragments = context->s.ec.frag_ix + 1;
context->s.ec.result_bin = NULL;
context->alive = 0;
if (context_b && erts_refc_read(&context_b->intern.refc,0) == 0) {
erts_bin_free(context_b);
}
- return erts_build_proc_bin(&MSO(p), HAlloc(p, PROC_BIN_SIZE),
- result_bin);
+ if (!context->s.ec.iov) {
+ hsz = PROC_BIN_SIZE + (iovec ? 2 : 0);
+ hp = HAlloc(p, hsz);
+ result = erts_build_proc_bin(&MSO(p), hp, result_bin);
+ if (iovec) {
+ hp += PROC_BIN_SIZE;
+ result = CONS(hp, result, NIL);
+ }
+ return result;
+ }
+ iovec = context->s.ec.iovec;
+ ASSERT(iovec);
+ iov = context->s.ec.iov;
+ termv = context->s.ec.termv;
+ ASSERT(context->s.ec.vlen <= context->s.ec.debug_vlen);
+ ASSERT(fragments <= context->s.ec.debug_fragments);
+ /* first two elements should be unused */
+ ASSERT(context->s.ec.vlen >= 3*fragments);
+ ASSERT(!iov[0].iov_base && !iov[0].iov_len);
+ ASSERT(!iov[1].iov_base && !iov[1].iov_len);
+
+ hsz = (2 /* cons */
+ + (PROC_BIN_SIZE > ERL_SUB_BIN_SIZE
+ ? PROC_BIN_SIZE
+ : ERL_SUB_BIN_SIZE)); /* max size per vec */
+ hsz *= context->s.ec.vlen - 2*fragments; /* number of vecs */
+ hp = HAlloc(p, hsz);
+ hp_end = hp + hsz;
+ rb_term = THE_NON_VALUE;
+ result = NIL;
+ ASSERT(erts_refc_read(&result_bin->intern.refc, 1) == 1);
+ cbin_refc_diff = -1;
+ for (ix = context->s.ec.vlen - 1; ix > 1; ix--) {
+ Eterm bin_term, pb_term;
+ Uint pb_size;
+ ProcBin *pb;
+ SysIOVec *iovp = &iov[ix];
+ if (!iovp->iov_base)
+ continue; /* empty slot for header */
+ pb_term = termv[ix];
+ if (is_value(pb_term)) {
+ pb_size = binary_size(pb_term);
+ pb = (ProcBin *) binary_val(pb_term);
+ }
+ else {
+ iovp->iov_base = (void *) (((byte *) iovp->iov_base)
+ + realloc_offset);
+ pb_size = result_bin->orig_size;
+ if (is_non_value(rb_term))
+ pb = NULL;
+ else {
+ pb = (ProcBin *) binary_val(rb_term);
+ pb_term = rb_term;
+ }
+ }
+ /*
+ * We intentionally avoid using sub binaries
+ * since the GC might convert those to heap
+ * binaries and by this ruin the nice preparation
+ * for usage of this data as I/O vector in
+ * nifs/drivers.
+ */
+ if (is_value(pb_term) && iovp->iov_len == pb_size)
+ bin_term = pb_term;
+ else {
+ Binary *bin;
+ if (is_value(pb_term)) {
+ bin = ((ProcBin *) binary_val(pb_term))->val;
+ erts_refc_inc(&bin->intern.refc, 2);
+ }
+ else {
+ bin = result_bin;
+ cbin_refc_diff++;
+ }
+ pb = (ProcBin *) (char *) hp;
+ hp += PROC_BIN_SIZE;
+ pb->thing_word = HEADER_PROC_BIN;
+ pb->size = (Uint) iovp->iov_len;
+ pb->next = MSO(p).first;
+ MSO(p).first = (struct erl_off_heap_header*) pb;
+ pb->val = bin;
+ pb->bytes = (byte*) iovp->iov_base;
+ pb->flags = 0;
+ OH_OVERHEAD(&MSO(p), pb->size / sizeof(Eterm));
+ bin_term = make_binary(pb);
+ }
+ result = CONS(hp, bin_term, result);
+ hp += 2;
+ }
+ ASSERT(hp <= hp_end);
+ HRelease(p, hp_end, hp);
+ context->s.ec.iov = NULL;
+ erts_free(ERTS_ALC_T_T2B_VEC, iov);
+ if (cbin_refc_diff) {
+ ASSERT(cbin_refc_diff >= -1);
+ if (cbin_refc_diff > 0)
+ erts_refc_add(&result_bin->intern.refc,
+ cbin_refc_diff, 1);
+ else
+ erts_bin_free(result_bin);
+ }
+ return result;
}
/* Continue with compression... */
/* To make absolutely sure that zlib does not barf on a reallocated context,
@@ -2372,7 +2752,7 @@ static Eterm erts_term_to_binary_int(Process* p, Eterm Term, Eterm opts, int lev
*/
static byte*
-enc_atom(ErtsAtomCacheMap *acmp, Eterm atom, byte *ep, Uint32 dflags)
+enc_atom(ErtsAtomCacheMap *acmp, Eterm atom, byte *ep, Uint64 dflags)
{
int iix;
int len;
@@ -2380,7 +2760,7 @@ enc_atom(ErtsAtomCacheMap *acmp, Eterm atom, byte *ep, Uint32 dflags)
ASSERT(is_atom(atom));
- if (dflags & DFLAG_INTERNAL_TAGS) {
+ if (dflags & DFLAG_ETS_COMPRESSED) {
Uint aval = atom_val(atom);
ASSERT(aval < (1<<24));
if (aval >= (1 << 16)) {
@@ -2457,19 +2837,20 @@ enc_atom(ErtsAtomCacheMap *acmp, Eterm atom, byte *ep, Uint32 dflags)
/*
* We use this atom as sysname in local pid/port/refs
- * for the ETS compressed format (DFLAG_INTERNAL_TAGS).
+ * for the ETS compressed format
*
*/
#define INTERNAL_LOCAL_SYSNAME am_ErtsSecretAtom
static byte*
-enc_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint32 dflags)
+enc_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint64 dflags)
{
Uint on, os;
- Eterm sysname = ((is_internal_pid(pid) && (dflags & DFLAG_INTERNAL_TAGS))
+ Eterm sysname = ((is_internal_pid(pid) && (dflags & DFLAG_ETS_COMPRESSED))
? INTERNAL_LOCAL_SYSNAME : pid_node_name(pid));
Uint32 creation = pid_creation(pid);
- byte* tagp = ep++;
+
+ *ep++ = NEW_PID_EXT;
/* insert atom here containing host and sysname */
ep = enc_atom(acmp, sysname, ep, dflags);
@@ -2481,15 +2862,8 @@ enc_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint32 dflags)
ep += 4;
put_int32(os, ep);
ep += 4;
- if (creation <= ERTS_MAX_LOCAL_CREATION) {
- *tagp = PID_EXT;
- *ep++ = creation;
- } else {
- ASSERT(is_external_pid(pid));
- *tagp = NEW_PID_EXT;
- put_int32(creation, ep);
- ep += 4;
- }
+ put_int32(creation, ep);
+ ep += 4;
return ep;
}
@@ -2609,7 +2983,7 @@ dec_pid(ErtsDistExternal *edep, ErtsHeapFactory* factory, byte* ep,
if (tag == PID_EXT) {
cre = get_int8(ep);
ep += 1;
- if (!is_valid_creation(cre)) {
+ if (!is_tiny_creation(cre)) {
return NULL;
}
} else {
@@ -2653,7 +3027,7 @@ dec_pid(ErtsDistExternal *edep, ErtsHeapFactory* factory, byte* ep,
#define ENC_LAST_ARRAY_ELEMENT ((Eterm) 6)
static byte*
-enc_term(ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, Uint32 dflags,
+enc_term(ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, Uint64 dflags,
struct erl_off_heap_header** off_heap)
{
byte *res;
@@ -2662,7 +3036,8 @@ enc_term(ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, Uint32 dflags,
}
static int
-enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, Uint32 dflags,
+enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
+ Uint64 dflags,
struct erl_off_heap_header** off_heap, Sint *reds, byte **res)
{
DECLARE_WSTACK(s);
@@ -2673,10 +3048,12 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
Eterm val;
FloatDef f;
Sint r = 0;
+ int use_iov = 0;
if (ctx) {
WSTACK_CHANGE_ALLOCATOR(s, ERTS_ALC_T_SAVED_ESTACK);
r = *reds;
+ use_iov = !!ctx->iov;
if (ctx->wstack.wstart) { /* restore saved stacks and byte pointer */
WSTACK_RESTORE(s, &ctx->wstack);
@@ -2867,28 +3244,21 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
case REF_DEF:
case EXTERNAL_REF_DEF: {
Uint32 *ref_num;
- Eterm sysname = (((dflags & DFLAG_INTERNAL_TAGS) && is_internal_ref(obj))
+ Eterm sysname = (((dflags & DFLAG_ETS_COMPRESSED) && is_internal_ref(obj))
? INTERNAL_LOCAL_SYSNAME : ref_node_name(obj));
Uint32 creation = ref_creation(obj);
- byte* tagp = ep++;
ASSERT(dflags & DFLAG_EXTENDED_REFERENCES);
erts_magic_ref_save_bin(obj);
+ *ep++ = NEWER_REFERENCE_EXT;
i = ref_no_numbers(obj);
put_int16(i, ep);
ep += 2;
ep = enc_atom(acmp, sysname, ep, dflags);
- if (creation <= ERTS_MAX_LOCAL_CREATION) {
- *tagp = NEW_REFERENCE_EXT;
- *ep++ = creation;
- } else {
- ASSERT(is_external_ref(obj));
- *tagp = NEWER_REFERENCE_EXT;
- put_int32(creation, ep);
- ep += 4;
- }
+ put_int32(creation, ep);
+ ep += 4;
ref_num = ref_numbers(obj);
for (j = 0; j < i; j++) {
put_int32(ref_num[j], ep);
@@ -2898,24 +3268,17 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
}
case PORT_DEF:
case EXTERNAL_PORT_DEF: {
- Eterm sysname = (((dflags & DFLAG_INTERNAL_TAGS) && is_internal_port(obj))
+ Eterm sysname = (((dflags & DFLAG_ETS_COMPRESSED) && is_internal_port(obj))
? INTERNAL_LOCAL_SYSNAME : port_node_name(obj));
Uint32 creation = port_creation(obj);
- byte* tagp = ep++;
+ *ep++ = NEW_PORT_EXT;
ep = enc_atom(acmp, sysname, ep, dflags);
j = port_number(obj);
put_int32(j, ep);
ep += 4;
- if (creation <= ERTS_MAX_LOCAL_CREATION) {
- *tagp = PORT_EXT;
- *ep++ = creation;
- } else {
- ASSERT(is_external_port(obj));
- *tagp = NEW_PORT_EXT;
- put_int32(creation, ep);
- ep += 4;
- }
+ put_int32(creation, ep);
+ ep += 4;
break;
}
case LIST_DEF:
@@ -3042,9 +3405,44 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
Uint bitsize;
byte* bytes;
byte* data_dst;
+ Uint off_heap_bytesize = 0;
+ Uint off_heap_tail;
+ Eterm pb_term;
+ Binary *pb_val;
+ ASSERT(!(dflags & DFLAG_PENDING_CONNECT) || (ctx && ctx->iov));
+
ERTS_GET_BINARY_BYTES(obj, bytes, bitoffs, bitsize);
- if (dflags & DFLAG_INTERNAL_TAGS) {
+ if (use_iov) {
+ if (bitoffs == 0) {
+ ProcBin* pb = (ProcBin*) binary_val(obj);
+ off_heap_bytesize = pb->size;
+ if (off_heap_bytesize <= ERL_ONHEAP_BIN_LIMIT)
+ off_heap_bytesize = 0;
+ else {
+ pb_term = obj;
+ if (pb->thing_word == HEADER_SUB_BIN) {
+ ErlSubBin* sub = (ErlSubBin*)pb;
+ pb_term = sub->orig;
+ pb = (ProcBin*) binary_val(pb_term);
+ }
+ if (pb->thing_word != HEADER_PROC_BIN)
+ off_heap_bytesize = 0;
+ else {
+ if (pb->flags) {
+ char* before_realloc = pb->val->orig_bytes;
+ erts_emasculate_writable_binary(pb);
+ bytes += (pb->val->orig_bytes - before_realloc);
+ ASSERT((byte *) &pb->val->orig_bytes[0] <= bytes
+ && bytes < ((byte *) &pb->val->orig_bytes[0]
+ + pb->val->orig_size));
+ }
+ pb_val = pb->val;
+ }
+ }
+ }
+ }
+ else if (dflags & DFLAG_ETS_COMPRESSED) {
ProcBin* pb = (ProcBin*) binary_val(obj);
Uint bytesize = pb->size;
if (pb->thing_word == HEADER_SUB_BIN) {
@@ -3087,20 +3485,51 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
j = binary_size(obj);
put_int32(j, ep);
ep += 4;
- data_dst = ep;
- ep += j;
+ if (off_heap_bytesize)
+ off_heap_tail = 0;
+ else {
+ data_dst = ep;
+ ep += j;
+ }
} else if (dflags & DFLAG_BIT_BINARIES) {
/* Bit-level binary. */
- *ep++ = BIT_BINARY_EXT;
- j = binary_size(obj);
- put_int32((j+1), ep);
- ep += 4;
- *ep++ = bitsize;
- ep[j] = 0; /* Zero unused bits at end of binary */
- data_dst = ep;
- ep += j + 1;
- if (ctx)
- ctx->hopefull_flags |= DFLAG_BIT_BINARIES;
+ if (dflags & DFLAG_PENDING_CONNECT) {
+ j = off_heap_bytesize;
+ if (!j) {
+ pb_val = NULL;
+ pb_term = THE_NON_VALUE;
+ j = binary_size(obj);
+ }
+ data_dst = hopefull_bit_binary(ctx, &ep, pb_val, pb_term,
+ bytes, bitoffs, bitsize, j);
+ if (!data_dst)
+ break; /* off heap binary referred... */
+ ASSERT(!off_heap_bytesize);
+ off_heap_tail = 0;
+ /*
+ * Trailing bits already written by hopefull_bit_binary();
+ * now go copy all whole octets...
+ */
+ bitsize = 0;
+ }
+ else {
+ *ep++ = BIT_BINARY_EXT;
+ j = binary_size(obj);
+ put_int32((j+1), ep);
+ ep += 4;
+ *ep++ = bitsize;
+ if (off_heap_bytesize) {
+ /* trailing bits */
+ ep[0] = 0;
+ copy_binary_to_buffer(ep, 0, bytes + j, 0, bitsize);
+ off_heap_tail = 1;
+ }
+ else {
+ ep[j] = 0; /* Zero unused bits at end of binary */
+ data_dst = ep;
+ ep += j + 1;
+ }
+ }
} else {
/*
* Bit-level binary, but the receiver doesn't support it.
@@ -3112,13 +3541,30 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
j = binary_size(obj);
put_int32((j+1), ep);
ep += 4;
- ep[j] = 0; /* Zero unused bits at end of binary */
- data_dst = ep;
- ep += j+1;
- *ep++ = SMALL_INTEGER_EXT;
- *ep++ = bitsize;
+
+ if (off_heap_bytesize) {
+ /* trailing bits */
+ ep[0] = 0;
+ copy_binary_to_buffer(ep, 0, bytes + j, 0, bitsize);
+ ep[1] = SMALL_INTEGER_EXT;
+ ep[2] = bitsize;
+ off_heap_tail = 3;
+ }
+ else {
+ ep[j] = 0; /* Zero unused bits at end of binary */
+ data_dst = ep;
+ ep += j+1;
+ *ep++ = SMALL_INTEGER_EXT;
+ *ep++ = bitsize;
+ }
}
- if (ctx && j > r * TERM_TO_BINARY_MEMCPY_FACTOR) {
+ if (off_heap_bytesize) {
+ ASSERT(pb_val);
+ store_in_vec(ctx, ep, pb_val, pb_term,
+ bytes, off_heap_bytesize);
+ ep += off_heap_tail;
+ }
+ else if (ctx && j > r * TERM_TO_BINARY_MEMCPY_FACTOR) {
WSTACK_PUSH5(s, (UWord)data_dst, (UWord)bytes, bitoffs,
ENC_BIN_COPY, 8*j + bitsize);
} else {
@@ -3130,14 +3576,15 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
case EXPORT_DEF:
{
Export* exp = *((Export **) (export_val(obj) + 1));
- if ((dflags & DFLAG_EXPORT_PTR_TAG) != 0) {
+ ASSERT(!(dflags & DFLAG_PENDING_CONNECT) || (ctx && ctx->iov));
+ if (dflags & DFLAG_PENDING_CONNECT)
+ hopefull_export(ctx, &ep, exp, dflags, off_heap);
+ else if ((dflags & DFLAG_EXPORT_PTR_TAG) != 0) {
*ep++ = EXPORT_EXT;
ep = enc_atom(acmp, exp->info.mfa.module, ep, dflags);
ep = enc_atom(acmp, exp->info.mfa.function, ep, dflags);
ep = enc_term(acmp, make_small(exp->info.mfa.arity),
ep, dflags, off_heap);
- if (ctx)
- ctx->hopefull_flags |= DFLAG_EXPORT_PTR_TAG;
} else {
/* Tag, arity */
*ep++ = SMALL_TUPLE_EXT;
@@ -3187,11 +3634,255 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
if (ctx) {
ASSERT(ctx->wstack.wstart == NULL);
*reds = r;
+ if (use_iov)
+ store_in_vec(ctx, ep, NULL, THE_NON_VALUE, NULL, 0);
}
*res = ep;
return 0;
}
+static ERTS_INLINE void
+store_in_vec_aux(TTBEncodeContext *ctx,
+ Binary *bin,
+ Eterm term,
+ byte *ptr,
+ Uint len)
+{
+ ErlDrvBinary *dbin = Binary2ErlDrvBinary(bin);
+ int vlen = ctx->vlen;
+ Uint iov_len;
+ ErlIOVec *feiovp;
+
+ ASSERT(((byte *) &bin->orig_bytes[0]) <= ptr);
+ ASSERT(ptr + len <= ((byte *) &bin->orig_bytes[0]) + bin->orig_size);
+
+ if (ctx->frag_ix >= 0) {
+ feiovp = &ctx->fragment_eiovs[ctx->frag_ix];
+ ASSERT(0 < feiovp->size);
+ ASSERT(feiovp->size <= ctx->fragment_size);
+ if (feiovp->size != ctx->fragment_size) {
+ /* current fragment not full yet... */
+ iov_len = ctx->fragment_size - feiovp->size;
+ if (len < iov_len)
+ iov_len = len;
+ goto store_iov_data;
+ }
+ }
+
+ while (len) {
+ /* Start new fragment... */
+ ctx->frag_ix++;
+ feiovp = &ctx->fragment_eiovs[ctx->frag_ix];
+ ASSERT(ctx->frag_ix >= 0);
+
+ if (ctx->termv) {
+ ctx->termv[vlen] = THE_NON_VALUE;
+ ctx->termv[vlen+1] = THE_NON_VALUE;
+ }
+
+ feiovp->vsize = 2;
+ feiovp->size = 0;
+ feiovp->iov = &ctx->iov[vlen];
+ feiovp->binv = &ctx->binv[vlen];
+
+ /* entry for driver header */
+ ctx->iov[vlen].iov_base = NULL;
+ ctx->iov[vlen].iov_len = 0;
+ ctx->binv[vlen] = NULL;
+ vlen++;
+
+ /* entry for dist header */
+ ctx->iov[vlen].iov_base = NULL;
+ ctx->iov[vlen].iov_len = 0;
+ ctx->binv[vlen] = NULL;
+ vlen++;
+
+ iov_len = len < ctx->fragment_size ? len : ctx->fragment_size;
+
+ store_iov_data:
+
+ ASSERT(iov_len);
+
+ do {
+ Uint iov_len_left;
+
+ if (iov_len <= MAX_SYSIOVEC_IOVLEN)
+ iov_len_left = 0;
+ else {
+ iov_len_left = iov_len - MAX_SYSIOVEC_IOVLEN;
+ iov_len = MAX_SYSIOVEC_IOVLEN;
+ }
+
+ ctx->iov[vlen].iov_base = ptr;
+ ctx->iov[vlen].iov_len = iov_len;
+ ctx->binv[vlen] = dbin;
+ if (ctx->termv)
+ ctx->termv[vlen] = term;
+ else
+ erts_refc_inc(&bin->intern.refc, 2);
+ ctx->size += iov_len;
+ len -= iov_len;
+ ptr += iov_len;
+ vlen++;
+ feiovp->size += iov_len;
+ feiovp->vsize++;
+
+ iov_len = iov_len_left;
+ } while (iov_len);
+ }
+
+ ctx->vlen = vlen;
+}
+
+static void
+store_in_vec(TTBEncodeContext *ctx,
+ byte *ep,
+ Binary *ohbin,
+ Eterm ohpb,
+ byte *ohp,
+ Uint ohsz)
+{
+ byte *cp = ctx->cptr;
+ if (cp != ep) {
+ /* save data in common binary... */
+ store_in_vec_aux(ctx,
+ ctx->result_bin,
+ THE_NON_VALUE,
+ cp,
+ ep - cp);
+ ASSERT(ctx->vlen <= ctx->debug_vlen);
+ ASSERT(ctx->frag_ix <= ctx->debug_fragments);
+ ctx->cptr = ep;
+ }
+ if (ohbin) {
+ /* save off-heap binary... */
+ store_in_vec_aux(ctx,
+ ohbin,
+ ohpb,
+ ohp,
+ ohsz);
+ ASSERT(ctx->vlen <= ctx->debug_vlen);
+ ASSERT(ctx->frag_ix <= ctx->debug_fragments);
+ }
+}
+
+static byte *
+begin_hopefull_data(TTBEncodeContext *ctx, byte *ep)
+{
+ store_in_vec(ctx, ep, NULL, THE_NON_VALUE, NULL, 0);
+ ASSERT(ERTS_NO_HIX == (Uint32) get_int32(ctx->hopefull_ixp));
+ put_int32(ctx->vlen, ctx->hopefull_ixp);
+ ctx->hopefull_ixp = ep;
+ put_int32(ERTS_NO_HIX, ep);
+ ep += 4;
+ ctx->cptr = ep;
+ return ep;
+}
+
+static byte *
+end_hopefull_data(TTBEncodeContext *ctx, byte *ep, Uint fallback_size)
+{
+ Uint sz;
+ store_in_vec(ctx, ep, NULL, THE_NON_VALUE, NULL, 0);
+ /*
+ * Reserve extra room for fallback if needed. The four
+ * bytes used for hopefull index can be used for
+ * fallback encoding...
+ */
+ sz = ep - ctx->hopefull_ixp;
+ if (fallback_size > sz) {
+ ep += fallback_size - sz;
+ ctx->cptr = ep;
+ }
+ return ep;
+}
+
+static byte *
+hopefull_bit_binary(TTBEncodeContext* ctx, byte **epp, Binary *pb_val, Eterm pb_term,
+ byte *bytes, byte bitoffs, byte bitsize, Uint sz)
+{
+ byte *octets, *ep = *epp;
+
+ ctx->hopefull_flags |= DFLAG_BIT_BINARIES;
+
+ /*
+ * The fallback:
+ *
+ * SMALL_TUPLE_EXT - 1 byte
+ * 2 - 1 byte
+ * BINARY_EXT - 1 byte
+ * whole octet size ('sz') - 4 byte
+ * whole octets - 'sz' bytes
+ * trailing bits - 1 byte
+ * SMALL_INTEGER_EXT - 1 byte
+ * bitsize - 1 byte
+ */
+
+ /* bit binary prelude in one hopefull data element */
+ ep = begin_hopefull_data(ctx, ep);
+ *ep++ = BIT_BINARY_EXT;
+ put_int32((sz+1), ep);
+ ep += 4;
+ *ep++ = bitsize;
+ ep = end_hopefull_data(ctx, ep, 1+1+1+4);
+
+ /* All whole octets... */
+ if (pb_val) {
+ octets = NULL;
+ store_in_vec(ctx, ep, pb_val, pb_term, bytes, sz);
+ }
+ else {
+ /* ... will be copied here afterwards */
+ octets = ep;
+ ep += sz;
+ }
+
+ /* copy trailing bits into new hopefull data element */
+ ep = begin_hopefull_data(ctx, ep);
+ *ep = 0; /* Clear the bit in the byte */
+
+ copy_binary_to_buffer(ep, 0, bytes + sz, bitoffs, bitsize);
+ ep++;
+
+ ep = end_hopefull_data(ctx, ep, 1+1+1);
+ *epp = ep;
+
+ return octets;
+}
+
+static void
+hopefull_export(TTBEncodeContext* ctx, byte **epp, Export* exp, Uint32 dflags,
+ struct erl_off_heap_header** off_heap)
+{
+ Uint fallback_sz;
+ byte *ep = *epp, *mod_start;
+
+ /*
+ * The fallback:
+ *
+ * SMALL_TUPLE_EXT - 1 byte
+ * 2 - 1 byte
+ * module atom... - M bytes
+ * function atom... - F bytes
+ */
+
+ ctx->hopefull_flags |= DFLAG_EXPORT_PTR_TAG;
+
+ ep = begin_hopefull_data(ctx, ep);
+
+ *ep++ = EXPORT_EXT;
+ mod_start = ep;
+ ep = enc_atom(NULL, exp->info.mfa.module, ep, dflags);
+ ep = enc_atom(NULL, exp->info.mfa.function, ep, dflags);
+ fallback_sz = 2 + (ep - mod_start);
+ ep = enc_term(NULL, make_small(exp->info.mfa.arity),
+ ep, dflags, off_heap);
+
+ ep = end_hopefull_data(ctx, ep, fallback_sz);
+
+ *epp = ep;
+}
+
/** @brief Is it a list of bytes not longer than MAX_STRING_LEN?
* @param lenp out: string length or number of list cells traversed
* @return true/false
@@ -3611,7 +4302,7 @@ dec_term_atom_common:
if (tag == PORT_EXT) {
cre = get_int8(ep);
ep++;
- if (!is_valid_creation(cre)) {
+ if (!is_tiny_creation(cre)) {
goto error;
}
}
@@ -3658,7 +4349,7 @@ dec_term_atom_common:
cre = get_int8(ep);
ep += 1;
- if (!is_valid_creation(cre)) {
+ if (!is_tiny_creation(cre)) {
goto error;
}
goto ref_ext_common;
@@ -3672,7 +4363,7 @@ dec_term_atom_common:
cre = get_int8(ep);
ep += 1;
- if (!is_valid_creation(cre)) {
+ if (!is_tiny_creation(cre)) {
goto error;
}
r0 = get_int32(ep);
@@ -4067,73 +4758,6 @@ dec_term_atom_common:
next = &(funp->creator);
break;
}
- case FUN_EXT:
- {
- ErlFunThing* funp = (ErlFunThing *) hp;
- Eterm module;
- Sint old_uniq;
- Sint old_index;
- unsigned num_free;
- int i;
- Eterm temp;
-
- num_free = get_int32(ep);
- ep += 4;
- hp += ERL_FUN_SIZE;
- hp += num_free;
- factory->hp = hp;
- funp->thing_word = HEADER_FUN;
- funp->num_free = num_free;
- *objp = make_fun(funp);
-
- /* Creator pid */
- if ((*ep != PID_EXT && *ep != NEW_PID_EXT)
- || (ep = dec_pid(edep, factory, ep+1,
- &funp->creator, *ep))==NULL) {
- goto error;
- }
-
- /* Module */
- if ((ep = dec_atom(edep, ep, &module)) == NULL) {
- goto error;
- }
-
- /* Index */
- if ((ep = dec_term(edep, factory, ep, &temp, NULL, 0)) == NULL) {
- goto error;
- }
- if (!is_small(temp)) {
- goto error;
- }
- old_index = unsigned_val(temp);
-
- /* Uniq */
- if ((ep = dec_term(edep, factory, ep, &temp, NULL, 0)) == NULL) {
- goto error;
- }
- if (!is_small(temp)) {
- goto error;
- }
-
- /*
- * It is safe to link the fun into the fun list only when
- * no more validity tests can fail.
- */
- funp->next = factory->off_heap->first;
- factory->off_heap->first = (struct erl_off_heap_header*)funp;
- old_uniq = unsigned_val(temp);
-
- funp->fe = erts_put_fun_entry(module, old_uniq, old_index);
- funp->arity = funp->fe->address[-1] - num_free;
- hp = factory->hp;
-
- /* Environment */
- for (i = num_free-1; i >= 0; i--) {
- funp->env[i] = (Eterm) next;
- next = funp->env + i;
- }
- break;
- }
case ATOM_INTERNAL_REF2:
n = get_int16(ep);
ep += 2;
@@ -4302,10 +4926,11 @@ error_hamt:
(except for cached atoms) */
static Uint encode_size_struct2(ErtsAtomCacheMap *acmp,
Eterm obj,
- unsigned dflags) {
- Uint size;
+ Uint64 dflags) {
+ Uint size = 0;
ErtsExtSzRes res = encode_size_struct_int(NULL, acmp, obj,
- dflags, NULL, &size);
+ dflags, NULL,
+ &size);
/*
* encode_size_struct2() only allowed when
* we know the result will always be OK!
@@ -4316,18 +4941,23 @@ static Uint encode_size_struct2(ErtsAtomCacheMap *acmp,
static ErtsExtSzRes
encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
- unsigned dflags, Sint *reds, Uint *res)
+ Uint64 dflags, Sint *reds, Uint *res)
{
DECLARE_WSTACK(s);
Uint m, i, arity;
- Uint result = 0;
+ Uint result = *res;
Sint r = 0;
+ int vlen = -1;
if (ctx) {
WSTACK_CHANGE_ALLOCATOR(s, ERTS_ALC_T_SAVED_ESTACK);
r = *reds;
- if (ctx->wstack.wstart) { /* restore saved stack */
+ vlen = ctx->vlen;
+
+ if (!ctx->wstack.wstart)
+ ctx->last_result = result;
+ else { /* restore saved stack */
WSTACK_RESTORE(s, &ctx->wstack);
result = ctx->result;
obj = ctx->obj;
@@ -4346,6 +4976,7 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
*reds = 0;
ctx->obj = obj;
ctx->result = result;
+ ctx->vlen = vlen;
WSTACK_SAVE(s, &ctx->wstack);
return ERTS_EXT_SZ_YIELD;
}
@@ -4354,7 +4985,7 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
result++;
break;
case ATOM_DEF:
- if (dflags & DFLAG_INTERNAL_TAGS) {
+ if (dflags & DFLAG_ETS_COMPRESSED) {
if (atom_val(obj) >= (1<<16)) {
result += 1 + 3;
}
@@ -4408,30 +5039,21 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
result += 1 + 4 + 1 + i; /* tag,size,sign,digits */
break;
case EXTERNAL_PID_DEF:
- if (external_pid_creation(obj) > ERTS_MAX_LOCAL_CREATION)
- result += 3;
- /*fall through*/
case PID_DEF:
result += (1 + encode_size_struct2(acmp, pid_node_name(obj), dflags) +
- 4 + 4 + 1);
+ 4 + 4 + 4);
break;
case EXTERNAL_REF_DEF:
- if (external_ref_creation(obj) > ERTS_MAX_LOCAL_CREATION)
- result += 3;
- /*fall through*/
case REF_DEF:
ASSERT(dflags & DFLAG_EXTENDED_REFERENCES);
i = ref_no_numbers(obj);
result += (1 + 2 + encode_size_struct2(acmp, ref_node_name(obj), dflags) +
- 1 + 4*i);
+ 4 + 4*i);
break;
case EXTERNAL_PORT_DEF:
- if (external_port_creation(obj) > ERTS_MAX_LOCAL_CREATION)
- result += 3;
- /*fall through*/
case PORT_DEF:
result += (1 + encode_size_struct2(acmp, port_node_name(obj), dflags) +
- 4 + 1);
+ 4 + 4);
break;
case LIST_DEF: {
int is_str = is_external_string(obj, &m);
@@ -4525,39 +5147,134 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
break;
case BINARY_DEF: {
ProcBin* pb = (ProcBin*) binary_val(obj);
- Uint tot_bytes = pb->size;
- if (!(dflags & DFLAG_INTERNAL_TAGS)) {
-#ifdef ARCH_64
- if (tot_bytes >= (Uint) 0xffffffff) {
- if (pb->thing_word == HEADER_SUB_BIN) {
- ErlSubBin* sub = (ErlSubBin*) pb;
- tot_bytes += (sub->bitoffs + sub->bitsize+ 7) / 8;
- }
- if (tot_bytes > (Uint) 0xffffffff) {
- WSTACK_DESTROY(s);
- return ERTS_EXT_SZ_SYSTEM_LIMIT;
- }
- }
-#endif
- }
- else {
+ Uint bin_size = pb->size;
+ byte bitoffs = 0;
+ byte bitsize = 0;
+ if (dflags & DFLAG_ETS_COMPRESSED) {
ProcBin* pb = (ProcBin*) binary_val(obj);
Uint sub_extra = 0;
if (pb->thing_word == HEADER_SUB_BIN) {
ErlSubBin* sub = (ErlSubBin*) pb;
+ bitoffs = sub->bitoffs;
+ bitsize = sub->bitsize;
pb = (ProcBin*) binary_val(sub->orig);
sub_extra = 2; /* bitoffs and bitsize */
- tot_bytes += (sub->bitoffs + sub->bitsize+ 7) / 8;
+ bin_size += (bitoffs + bitsize + 7) / 8;
}
if (pb->thing_word == HEADER_PROC_BIN
- && heap_bin_size(tot_bytes) > PROC_BIN_SIZE) {
+ && heap_bin_size(bin_size) > PROC_BIN_SIZE) {
result += 1 + sub_extra + sizeof(ProcBin);
break;
}
+ }
+ else {
+#ifdef ARCH_64
+ if (bin_size >= (Uint) 0xffffffff) {
+ if (pb->thing_word == HEADER_SUB_BIN) {
+ ErlSubBin* sub = (ErlSubBin*) pb;
+ bin_size += (sub->bitoffs + sub->bitsize+ 7) / 8;
+ }
+ if (bin_size > (Uint) 0xffffffff) {
+ WSTACK_DESTROY(s);
+ return ERTS_EXT_SZ_SYSTEM_LIMIT;
+ }
+ }
+#endif
+ if (pb->thing_word == HEADER_SUB_BIN) {
+ ErlSubBin* sub = (ErlSubBin*) pb;
+ bitoffs = sub->bitoffs;
+ bitsize = sub->bitsize;
+ pb = (ProcBin*) binary_val(sub->orig);
+ }
+ if (vlen >= 0) {
+ Uint csz;
+ if (pb->thing_word == HEADER_PROC_BIN
+ && bitoffs == 0
+ && bin_size > ERL_ONHEAP_BIN_LIMIT) {
+ Uint trailing_result;
+ if (bitsize == 0) {
+ result += (1 /* BIT_BINARY_EXT */
+ + 4 /* size */);
+ trailing_result = 0;
+ }
+ else if (dflags & DFLAG_BIT_BINARIES) {
+ result += (1 /* BIT_BINARY_EXT */
+ + 4 /* size */
+ + 1 /* trailing bitsize */);
+ trailing_result = 1 /* trailing bits */;
+ }
+ else {
+ /* sigh... */
+ result += (1 /* SMALL_TUPLE_EXT */
+ + 1 /* 2 tuple size */
+ + 1 /* BINARY_EXT */
+ + 4 /* binary size */);
+ trailing_result = (1 /* SMALL_INTEGER_EXT */
+ + 1 /* bitsize */);
+ }
+ csz = result - ctx->last_result;
+ ctx->last_result = result;
+ result += trailing_result;
+ vlen += 2; /* data leading up to binary and binary */
+
+ /* potentially multiple elements leading up to binary */
+ vlen += csz/MAX_SYSIOVEC_IOVLEN;
+ /* potentially multiple elements for binary */
+ vlen += bin_size/MAX_SYSIOVEC_IOVLEN;
+ ctx->extra_size += bin_size;
+
+ if (dflags & DFLAG_PENDING_CONNECT) {
+ ASSERT(dflags & DFLAG_BIT_BINARIES);
+ vlen += 2; /* for hopefull prolog and epilog */
+ result += (4 /* for hopefull prolog (see below) */
+ + 4); /* for hopefull epilog (see below) */
+ ctx->last_result = result;
+ }
+ break;
+ }
+ }
}
- result += 1 + 4 + binary_size(obj) +
- 5; /* For unaligned binary */
+
+ if (bitsize == 0) {
+ result += (1 /* BIT_BINARY_EXT */
+ + 4 /* size */
+ + bin_size);
+ }
+ else if (dflags & DFLAG_PENDING_CONNECT) {
+ /* This is the odd case when we have an un-aligned bit-string
+ during a pending connect. */
+ Uint csz = result - ctx->last_result;
+ ASSERT(dflags & DFLAG_BIT_BINARIES);
+ /* potentially multiple elements leading up to binary */
+ vlen += (csz + MAX_SYSIOVEC_IOVLEN - 1)/MAX_SYSIOVEC_IOVLEN;
+
+ vlen++; /* hopefull prolog */
+ /*
+ * Size for hopefull prolog is max of
+ * - fallback: 1 + 1 + 1 + 4
+ * - hopfull index + bit binary prolog: 4 + 1 + 4 + 1
+ */
+ result += 4 + 1 + 4 + 1;
+ /* potentially multiple elements for binary */
+ vlen += bin_size/MAX_SYSIOVEC_IOVLEN + 1;
+ result += bin_size;
+ vlen++; /* hopefull epiolog */
+ /*
+ * Size for hopefull epiolog is max of
+ * - fallback: 1 + 1 + 1
+ * - hopfull index + bit binary epilog: 4 + 1
+ */
+ result += 4 + 1;
+ ctx->last_result = result;
+ }
+ else if (dflags & DFLAG_BIT_BINARIES) {
+ result += 1 + 4 + 1 + bin_size + 1;
+ }
+ else {
+ /* Sigh... */
+ result += 1 + 1 + 1 + 4 + bin_size + 1 + 1 + 1;
+ }
break;
}
case FUN_DEF:
@@ -4584,10 +5301,25 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
case EXPORT_DEF:
{
Export* ep = *((Export **) (export_val(obj) + 1));
+ Uint tmp_result = result;
result += 1;
result += encode_size_struct2(acmp, ep->info.mfa.module, dflags);
result += encode_size_struct2(acmp, ep->info.mfa.function, dflags);
result += encode_size_struct2(acmp, make_small(ep->info.mfa.arity), dflags);
+ if (dflags & DFLAG_PENDING_CONNECT) {
+ Uint csz;
+ /*
+ * Fallback is 1 + 1 + Module size + Function size, that is,
+ * the hopefull index + hopefull encoding is larger...
+ */
+ ASSERT(dflags & DFLAG_EXPORT_PTR_TAG);
+ csz = tmp_result - ctx->last_result;
+ /* potentially multiple elements leading up to hopefull entry */
+ vlen += (csz/MAX_SYSIOVEC_IOVLEN + 1
+ + 1); /* hopefull entry */
+ result += 4; /* hopefull index */
+ ctx->last_result = result;
+ }
}
break;
@@ -4630,6 +5362,14 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
if (ctx) {
ASSERT(ctx->wstack.wstart == NULL);
*reds = r < 0 ? 0 : r;
+
+ if (vlen >= 0) {
+ Uint csz;
+ csz = result - ctx->last_result;
+ if (csz)
+ vlen += csz/MAX_SYSIOVEC_IOVLEN + 1;
+ ctx->vlen = vlen;
+ }
}
*res = result;
return ERTS_EXT_SZ_OK;
@@ -4898,9 +5638,6 @@ init_done:
total_size = get_int32(ep);
CHKSIZE(total_size);
ep += 1+16+4+4;
- /*FALLTHROUGH*/
-
- case FUN_EXT:
CHKSIZE(4);
num_free = get_int32(ep);
ep += 4;
@@ -4911,6 +5648,12 @@ init_done:
heap_size += ERL_FUN_SIZE + num_free;
break;
}
+ case FUN_EXT:
+ /*
+ * OTP 23: No longer support decoding the old fun
+ * representation.
+ */
+ goto error;
case ATOM_INTERNAL_REF2:
SKIP(2+atom_extra_skip);
atom_extra_skip = 0;
@@ -4968,181 +5711,522 @@ error:
#undef CHKSIZE
}
+#define ERTS_TRANSCODE_REDS_FACT 4
+typedef struct {
+ ErtsHeapFactory factory;
+ Eterm *hp;
+} ErtsTranscodeDecodeState;
-struct transcode_context {
- enum {
- TRANSCODE_DEC_MSG_SIZE,
- TRANSCODE_DEC_MSG,
- TRANSCODE_ENC_CTL,
- TRANSCODE_ENC_MSG
- }state;
- Eterm ctl_term;
- Eterm* ctl_heap;
- ErtsHeapFactory ctl_factory;
- Eterm* msg_heap;
- B2TContext b2t;
- TTBEncodeContext ttb;
-#ifdef DEBUG
- ErtsDistOutputBuf* dbg_ob;
-#endif
-};
-
-void transcode_free_ctx(DistEntry* dep)
+static Eterm
+transcode_decode_ctl_msg(ErtsTranscodeDecodeState *state,
+ SysIOVec *iov,
+ int end_ix)
{
- struct transcode_context* ctx = dep->transcode_ctx;
+ Eterm ctl_msg, *hp;
+ Uint buf_sz;
+ byte *buf_start, *buf_end;
+ byte *ptr;
+ Uint hsz;
+
+ if (end_ix == 3) {
+ /* The whole control message is in iov[2].iov_base */
+ buf_sz = (Uint) iov[2].iov_len;
+ buf_start = (byte *) iov[2].iov_base;
+ buf_end = buf_start + buf_sz;
+ }
+ else {
+ /* Control message over multiple buffers... */
+ int ix;
+ buf_sz = 0;
+ for (ix = 2; ix < end_ix; ix++)
+ buf_sz += iov[ix].iov_len;
+ ptr = buf_start = erts_alloc(ERTS_ALC_T_TMP, buf_sz);
+ buf_end = buf_start + buf_sz;
+ for (ix = 2; ix < end_ix; ix++) {
+ sys_memcpy((void *) ptr,
+ (void *) iov[ix].iov_base,
+ iov[ix].iov_len);
+ ptr += iov[ix].iov_len;
+ }
+ }
- erts_factory_close(&ctx->ctl_factory);
- erts_free(ERTS_ALC_T_DIST_TRANSCODE, ctx->ctl_heap);
+ hsz = decoded_size(buf_start, buf_end, 0, NULL);
+ state->hp = hp = erts_alloc(ERTS_ALC_T_TMP, hsz*sizeof(Eterm));
+ erts_factory_tmp_init(&state->factory, hp, hsz, ERTS_ALC_T_TMP);
+
+ ptr = dec_term(NULL, &state->factory, buf_start, &ctl_msg, NULL, 0);
+ ASSERT(ptr); (void)ptr;
+ ASSERT(is_tuple(ctl_msg));
+
+ if (buf_start != (byte *) iov[2].iov_base)
+ erts_free(ERTS_ALC_T_TMP, buf_start);
+
+ return ctl_msg;
+}
- if (ctx->msg_heap) {
- erts_factory_close(&ctx->b2t.u.dc.factory);
- erts_free(ERTS_ALC_T_DIST_TRANSCODE, ctx->msg_heap);
- }
- erts_free(ERTS_ALC_T_DIST_TRANSCODE, ctx);
- dep->transcode_ctx = NULL;
+static void
+transcode_decode_state_destroy(ErtsTranscodeDecodeState *state)
+{
+ erts_factory_close(&state->factory);
+ erts_free(ERTS_ALC_T_TMP, state->hp);
}
+static
Sint transcode_dist_obuf(ErtsDistOutputBuf* ob,
DistEntry* dep,
- Uint32 dflags,
+ Uint64 dflags,
Sint reds)
{
- Sint hsz;
- byte* decp;
- const int have_msg = !!ob->msg_start;
- int i;
- struct transcode_context* ctx = dep->transcode_ctx;
+ ErlIOVec* eiov = ob->eiov;
+ SysIOVec* iov = eiov->iov;
+ byte *hdr;
+ Uint64 hopefull_flags;
+ Uint32 hopefull_ix, payload_ix;
+ Sint start_r, r;
+ Uint new_len;
+ byte *ep;
- if (!ctx) { /* first call for 'ob' */
- ASSERT(!(ob->hopefull_flags & ~(Uint)(DFLAG_BIT_BINARIES |
- DFLAG_EXPORT_PTR_TAG)));
- if (~dflags & ob->hopefull_flags) {
- /*
- * Receiver does not support bitstrings and/or export funs
- * and output buffer contains such message tags (hopefull_flags).
- * Must transcode control and message terms to use tuple fallbacks.
- */
- ctx = erts_alloc(ERTS_ALC_T_DIST_TRANSCODE, sizeof(struct transcode_context));
- dep->transcode_ctx = ctx;
- #ifdef DEBUG
- ctx->dbg_ob = ob;
- #endif
-
- hsz = decoded_size(ob->extp, ob->ext_endp, 0, NULL);
- ctx->ctl_heap = erts_alloc(ERTS_ALC_T_DIST_TRANSCODE, hsz*sizeof(Eterm));
- erts_factory_tmp_init(&ctx->ctl_factory, ctx->ctl_heap, hsz, ERTS_ALC_T_DIST_TRANSCODE);
- ctx->msg_heap = NULL;
-
- decp = dec_term(NULL, &ctx->ctl_factory, ob->extp, &ctx->ctl_term, NULL, 0);
- if (have_msg) {
- ASSERT(decp == ob->msg_start); (void)decp;
- ctx->b2t.u.sc.ep = NULL;
- ctx->b2t.state = B2TSize;
- ctx->b2t.aligned_alloc = NULL;
- ctx->b2t.b2ts.exttmp = 0;
- ctx->state = TRANSCODE_DEC_MSG_SIZE;
- }
- else {
- ASSERT(decp == ob->ext_endp);
- ctx->state = TRANSCODE_ENC_CTL;
+ if (reds < 0)
+ return reds;
+
+ /*
+ * HOPEFUL_DATA header always present in io vector
+ * element 1:
+ *
+ * +---+--------------+-----------+----------+
+ * |'H'|Hopefull Flags|Hopefull IX|Payload IX|
+ * +---+--------------+-----------+----------+
+ * 1 8 4 4
+ *
+ * Hopefull flags: Flags corresponding to actual
+ * hopefull encodings in this
+ * buffer.
+ * Hopefull IX: Vector index of first hopefull
+ * encoding. Each hopefull encoding
+ * is preceeded by 4 bytes containing
+ * next vector index of hopefull
+ * encoding. ERTS_NO_HIX marks the
+ * end.
+ * Payload IX: Vector index of the beginning
+ * of the payload if there is
+ * one; otherwise, zero.
+ */
+ hdr = (byte *) iov[1].iov_base;
+
+ ASSERT(HOPEFUL_DATA == *((byte *)iov[1].iov_base));
+ ASSERT(iov[1].iov_len == 1+8+4+4);
+
+ /* Control message always begin in vector element 2 */
+ ep = iov[2].iov_base;
+ ASSERT(ep[0] == SMALL_TUPLE_EXT || ep[0] == LARGE_TUPLE_EXT);
+
+ if (~dflags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)
+ && ep[0] == SMALL_TUPLE_EXT
+ && ep[1] == 4
+ && ep[2] == SMALL_INTEGER_EXT
+ && (ep[3] == DOP_MONITOR_P ||
+ ep[3] == DOP_MONITOR_P_EXIT ||
+ ep[3] == DOP_DEMONITOR_P)) {
+ /*
+ * Receiver does not support process monitoring.
+ * Suppress monitor control msg (see erts_dsig_send_monitor)
+ * by converting it to an empty (tick) packet.
+ */
+ int i;
+ for (i = 1; i < ob->eiov->vsize; i++) {
+ if (ob->eiov->binv[i])
+ driver_free_binary(ob->eiov->binv[i]);
+ }
+ ob->eiov->vsize = 1;
+ ob->eiov->size = 0;
+ return reds;
+ }
+
+ hdr++;
+ hopefull_flags = get_int64(hdr);
+
+ hdr += 8;
+ hopefull_ix = get_int32(hdr);
+
+ if ((~dflags & DFLAG_SPAWN)
+ && ep[0] == SMALL_TUPLE_EXT
+ && ((ep[1] == 6
+ && ep[2] == SMALL_INTEGER_EXT
+ && ep[3] == DOP_SPAWN_REQUEST)
+ || (ep[1] == 8
+ && ep[2] == SMALL_INTEGER_EXT
+ && ep[3] == DOP_SPAWN_REQUEST_TT))) {
+ /*
+ * Receiver does not support distributed spawn. Convert
+ * this packet to an empty (tick) packet, and inform
+ * spawning process that this is not supported...
+ */
+ ErtsTranscodeDecodeState tds;
+ Eterm ctl_msg, ref, pid, token, *tp;
+ int i;
+
+ hdr += 4;
+ payload_ix = get_int32(hdr);
+ ASSERT(payload_ix >= 3);
+
+ ctl_msg = transcode_decode_ctl_msg(&tds, iov, payload_ix);
+
+ ASSERT(is_tuple_arity(ctl_msg, 6)
+ || is_tuple_arity(ctl_msg, 8));
+ tp = tuple_val(ctl_msg);
+ ASSERT(tp[1] == make_small(DOP_SPAWN_REQUEST)
+ || tp[1] == make_small(DOP_SPAWN_REQUEST_TT));
+
+ ref = tp[2];
+ pid = tp[3];
+ if (tp[1] == make_small(DOP_SPAWN_REQUEST))
+ token = NIL;
+ else {
+ token = tp[8];
+ erts_seq_trace_update_node_token(token);
+ }
+ ASSERT(is_internal_ordinary_ref(tp[2]));
+ ASSERT(is_internal_pid(tp[3]));
+
+ (void) erts_proc_sig_send_dist_spawn_reply(dep->sysname,
+ ref, pid,
+ NULL, am_notsup,
+ token);
+
+ transcode_decode_state_destroy(&tds);
+
+ for (i = 1; i < ob->eiov->vsize; i++) {
+ if (ob->eiov->binv[i])
+ driver_free_binary(ob->eiov->binv[i]);
+ }
+ ob->eiov->vsize = 1;
+ ob->eiov->size = 0;
+
+ reds -= 4;
+
+ if (reds < 0)
+ return 0;
+ return reds;
+ }
+
+ if ((~dflags & DFLAG_UNLINK_ID)
+ && ep[0] == SMALL_TUPLE_EXT
+ && ep[1] == 4
+ && ep[2] == SMALL_INTEGER_EXT
+ && (ep[3] == DOP_UNLINK_ID_ACK || ep[3] == DOP_UNLINK_ID)) {
+
+ if (ep[3] == DOP_UNLINK_ID_ACK) {
+ /* Drop DOP_UNLINK_ID_ACK signal... */
+ int i;
+ for (i = 1; i < ob->eiov->vsize; i++) {
+ if (ob->eiov->binv[i])
+ driver_free_binary(ob->eiov->binv[i]);
}
+ ob->eiov->vsize = 1;
+ ob->eiov->size = 0;
}
- else if (!(dflags & DFLAG_DIST_HDR_ATOM_CACHE)) {
+ else {
+ Eterm ctl_msg, remote, local, *tp;
+ ErtsTranscodeDecodeState tds;
+ Uint64 id;
+ byte *ptr;
+ ASSERT(ep[3] == DOP_UNLINK_ID);
+ /*
+ * Rewrite the DOP_UNLINK_ID signal into a
+ * DOP_UNLINK signal and send an unlink ack
+ * to the local sender.
+ */
+
/*
- * No need for full transcoding, but primitive receiver (erl_/jinterface)
- * expects VERSION_MAGIC before both control and message terms.
+ * decode control message so we get info
+ * needed for unlink ack signal to send...
*/
- if (ob->msg_start) {
- Sint ctl_bytes = ob->msg_start - ob->extp;
- ASSERT(ob->extp < ob->msg_start && ob->msg_start < ob->ext_endp);
- /* Move control term back 1 byte to make room */
- sys_memmove(ob->extp-1, ob->extp, ctl_bytes);
- *--(ob->msg_start) = VERSION_MAGIC;
- --(ob->extp);
- reds -= ctl_bytes / (B2T_BYTES_PER_REDUCTION * B2T_MEMCPY_FACTOR);
+ ASSERT(get_int32(hdr + 4) == 0); /* No payload */
+ ctl_msg = transcode_decode_ctl_msg(&tds, iov, eiov->vsize);
+
+ ASSERT(is_tuple_arity(ctl_msg, 4));
+
+ tp = tuple_val(ctl_msg);
+ ASSERT(tp[1] == make_small(DOP_UNLINK_ID));
+
+ if (!term_to_Uint64(tp[2], &id))
+ ERTS_INTERNAL_ERROR("Invalid encoding of DOP_UNLINK_ID signal");
+
+ local = tp[3];
+ remote = tp[4];
+
+ ASSERT(is_internal_pid(local));
+ ASSERT(is_external_pid(remote));
+
+ /*
+ * Rewrite buffer to an unlink signal by removing
+ * second element and change first element to
+ * DOP_UNLINK. That is, to: {DOP_UNLINK, local, remote}
+ */
+
+ ptr = &ep[4];
+ switch (*ptr) {
+ case SMALL_INTEGER_EXT:
+ ptr += 1;
+ break;
+ case INTEGER_EXT:
+ ptr += 4;
+ break;
+ case SMALL_BIG_EXT:
+ ptr += 1;
+ ASSERT(*ptr <= 8);
+ ptr += *ptr + 1;
+ break;
+ default:
+ ERTS_INTERNAL_ERROR("Invalid encoding of DOP_UNLINK_ID signal");
+ break;
}
- *--(ob->extp) = VERSION_MAGIC;
- goto done;
+
+ ASSERT((ptr - ep) <= 16);
+ ASSERT((ptr - ep) <= iov[2].iov_len);
+
+ *(ptr--) = DOP_UNLINK;
+ *(ptr--) = SMALL_INTEGER_EXT;
+ *(ptr--) = 3;
+ *ptr = SMALL_TUPLE_EXT;
+
+ iov[2].iov_base = ptr;
+ iov[2].iov_len -= (ptr - ep);
+
+#ifdef DEBUG
+ {
+ ErtsTranscodeDecodeState dbg_tds;
+ Eterm new_ctl_msg = transcode_decode_ctl_msg(&dbg_tds,
+ iov,
+ eiov->vsize);
+ ASSERT(is_tuple_arity(new_ctl_msg, 3));
+ tp = tuple_val(new_ctl_msg);
+ ASSERT(tp[1] == make_small(DOP_UNLINK));
+ ASSERT(tp[2] == local);
+ ASSERT(eq(tp[3], remote));
+ transcode_decode_state_destroy(&dbg_tds);
+ }
+#endif
+
+ /* Send unlink ack to local sender... */
+ erts_proc_sig_send_dist_unlink_ack(NULL, dep,
+ dep->connection_id,
+ remote, local, id);
+
+ transcode_decode_state_destroy(&tds);
+
+ reds -= 5;
}
- else
- goto done;
- }
- else { /* continue after yield */
- ASSERT(ctx->dbg_ob == ob);
+ if (reds < 0)
+ return 0;
+ return reds;
}
- ctx->b2t.reds = reds * B2T_BYTES_PER_REDUCTION;
+
+ start_r = r = reds*ERTS_TRANSCODE_REDS_FACT;
- switch (ctx->state) {
- case TRANSCODE_DEC_MSG_SIZE:
- hsz = decoded_size(ob->msg_start, ob->ext_endp, 0, &ctx->b2t);
- if (ctx->b2t.state == B2TSize) {
- return -1;
- }
- ASSERT(ctx->b2t.state == B2TDecodeInit);
- ctx->msg_heap = erts_alloc(ERTS_ALC_T_DIST_TRANSCODE, hsz*sizeof(Eterm));
- ctx->b2t.u.dc.ep = ob->msg_start;
- ctx->b2t.u.dc.res = (Eterm) NULL;
- ctx->b2t.u.dc.next = &ctx->b2t.u.dc.res;
- erts_factory_tmp_init(&ctx->b2t.u.dc.factory,
- ctx->msg_heap, hsz, ERTS_ALC_T_DIST_TRANSCODE);
- ctx->b2t.u.dc.flat_maps.wstart = NULL;
- ctx->b2t.u.dc.hamt_array.pstart = NULL;
- ctx->b2t.state = B2TDecode;
-
- ctx->state = TRANSCODE_DEC_MSG;
- case TRANSCODE_DEC_MSG:
- if (ctx->b2t.reds <= 0)
- ctx->b2t.reds = 1;
- decp = dec_term(NULL, NULL, NULL, NULL, &ctx->b2t, 0);
- if (ctx->b2t.state < B2TDone) {
- return -1;
- }
- ASSERT(ctx->b2t.state == B2TDone);
- ASSERT(decp && decp <= ob->ext_endp);
- reds = ctx->b2t.reds / B2T_BYTES_PER_REDUCTION;
- b2t_destroy_context(&ctx->b2t);
-
- ctx->state = TRANSCODE_ENC_CTL;
- case TRANSCODE_ENC_CTL:
- if (!(dflags & DFLAG_DIST_HDR_ATOM_CACHE)) {
- ASSERT(!(dflags & DFLAG_NO_MAGIC));
- ob->extp -= 2; /* VERSION_MAGIC x 2 */
- }
- ob->ext_endp = ob->extp;
- i = erts_encode_dist_ext(ctx->ctl_term, &ob->ext_endp, dflags,
- NULL, NULL, NULL);
- ASSERT(i == 0); (void)i;
- ASSERT(ob->ext_endp <= ob->alloc_endp);
+ if (~dflags & hopefull_flags) {
- if (!have_msg) {
- break;
+ while (hopefull_ix != ERTS_NO_HIX) {
+ Uint32 new_hopefull_ix;
+
+ if (r <= 0) { /* yield... */
+ /* save current hopefull_ix... */
+ ep = (byte *) iov[1].iov_base;
+ ep += 5;
+ put_int32(hopefull_ix, ep);
+ return -1;
+ }
+
+ /* Read next hopefull index */
+ ep = (byte *) iov[hopefull_ix].iov_base;
+ ep -= 4;
+ new_hopefull_ix = get_int32(ep);
+ ASSERT(new_hopefull_ix == ERTS_NO_HIX
+ || (hopefull_ix < new_hopefull_ix
+ && new_hopefull_ix < eiov->vsize));
+
+ ep = (byte *) iov[hopefull_ix].iov_base;
+ switch (*ep) {
+
+ case EXPORT_EXT: {
+ byte *start_ep, *end_ep;
+ Eterm module, function;
+ if (!(hopefull_flags & DFLAG_EXPORT_PTR_TAG))
+ break;
+ /* Read original encoding... */
+ ep++;
+ start_ep = ep;
+ ep = dec_atom(NULL, ep, &module);
+ ASSERT(ep && is_atom(module));
+ ep = dec_atom(NULL, ep, &function);
+ ASSERT(ep && is_atom(function));
+ end_ep = ep;
+ ASSERT(*ep == SMALL_INTEGER_EXT
+ || *ep == INTEGER_EXT
+ || *ep == SMALL_BIG_EXT
+ || *ep == LARGE_BIG_EXT);
+
+ /*
+ * module and function atoms are encoded
+ * between start_ep and end_ep. Prepend a
+ * 2-tuple tag before the atoms and
+ * remove arity at end.
+ */
+
+ /* write fallback */
+
+ ep = start_ep;
+ ep--;
+ put_int8(2, ep);
+ ep--;
+ *ep = SMALL_TUPLE_EXT;
+
+ iov[hopefull_ix].iov_base = ep;
+
+ /* Update iov sizes... */
+ new_len = end_ep - ep;
+ eiov->size -= iov[hopefull_ix].iov_len;
+ eiov->size += new_len;
+ iov[hopefull_ix].iov_len = new_len;
+ r--;
+ break;
+ }
+
+ case BIT_BINARY_EXT: {
+ Uint bin_sz;
+ byte bitsize, epilog_byte;
+ ASSERT(hopefull_ix != ERTS_NO_HIX);
+ if (!(hopefull_flags & DFLAG_BIT_BINARIES)) {
+ /* skip to epilog... */
+ hopefull_ix = new_hopefull_ix;
+ ep = (byte *) iov[hopefull_ix].iov_base;
+ ep -= 4;
+ new_hopefull_ix = get_int32(ep);
+ ASSERT(new_hopefull_ix == ERTS_NO_HIX
+ || (hopefull_ix < new_hopefull_ix
+ && new_hopefull_ix < eiov->vsize));
+ break;
+ }
+
+ /* read original encoded prolog... */
+ ep++;
+ bin_sz = get_int32(ep);
+ ep += 4;
+ bitsize = *ep++;
+
+ /* write fallback prolog... */
+ iov[hopefull_ix].iov_base -= 4;
+ ep = (byte *) iov[hopefull_ix].iov_base;
+
+ *ep++ = SMALL_TUPLE_EXT;
+ *ep++ = 2;
+ *ep++ = BINARY_EXT;
+ put_int32(bin_sz, ep);
+ ep += 4;
+
+ /* Update iov sizes... */
+ new_len = ep - (byte *) iov[hopefull_ix].iov_base;
+ eiov->size -= iov[hopefull_ix].iov_len;
+ eiov->size += new_len;
+ iov[hopefull_ix].iov_len = new_len;
+ r--;
+#ifdef DEBUG
+ /*
+ * The binary data between the prolog and the
+ * epilog should be of size 'bin_sz - 1' and
+ * exists in the iov elements between prolog
+ * and epilog...
+ */
+ {
+ Uint ix, debug_bin_sz = 0;
+ for (ix = hopefull_ix+1; ix < new_hopefull_ix; ix++)
+ debug_bin_sz += iov[ix].iov_len;
+ ASSERT(debug_bin_sz == bin_sz - 1);
+ }
+#endif
+ /* jump to epilog... */
+ hopefull_ix = new_hopefull_ix;
+ ep = (byte *) iov[hopefull_ix].iov_base;
+
+ /* read original encoded epilog... */
+ epilog_byte = *ep;
+
+ ASSERT(1 == iov[hopefull_ix].iov_len);
+
+ iov[hopefull_ix].iov_base -= 4;
+ ep = (byte *) iov[hopefull_ix].iov_base;
+ new_hopefull_ix = get_int32(ep);
+ ASSERT(new_hopefull_ix == ERTS_NO_HIX
+ || (hopefull_ix < new_hopefull_ix
+ && new_hopefull_ix < eiov->vsize));
+
+ /* write fallback epilog... */
+
+ *ep++ = epilog_byte;
+ *ep++ = SMALL_INTEGER_EXT;
+ *ep++ = bitsize;
+
+ /* Update iov sizes... */
+ new_len = ep - (byte *) iov[hopefull_ix].iov_base;
+ eiov->size -= iov[hopefull_ix].iov_len;
+ eiov->size += new_len;
+ iov[hopefull_ix].iov_len = new_len;
+ r--;
+ break;
+ }
+
+ default:
+ ERTS_INTERNAL_ERROR("Unexpected external tag");
+ break;
+ }
+
+ hopefull_ix = new_hopefull_ix;
+ r--;
}
- ob->msg_start = ob->ext_endp;
- ctx->ttb.wstack.wstart = NULL;
- ctx->ttb.flags = dflags;
- ctx->ttb.hopefull_flags = 0;
- ctx->ttb.level = 0;
-
- ctx->state = TRANSCODE_ENC_MSG;
- case TRANSCODE_ENC_MSG:
- reds *= TERM_TO_BINARY_LOOP_FACTOR;
- if (erts_encode_dist_ext(ctx->b2t.u.dc.res, &ob->ext_endp, dflags, NULL,
- &ctx->ttb, &reds)) {
- return -1;
+ }
+
+ /*
+ * Replace hopefull data header with actual header...
+ */
+ ep = (byte *) iov[1].iov_base;
+ eiov->size -= iov[1].iov_len;
+
+ if (dflags & (DFLAG_DIST_HDR_ATOM_CACHE|DFLAG_FRAGMENTS)) {
+ /*
+ * Encoding was done without atom caching but receiver expects
+ * a dist header, so we prepend an empty one.
+ */
+ *ep++ = VERSION_MAGIC;
+ *ep++ = DIST_HEADER;
+ *ep++ = 0; /* NumberOfAtomCacheRefs */
+ }
+ else {
+ hdr += 4;
+ payload_ix = get_int32(hdr);
+
+ if (payload_ix) {
+ ASSERT(0 < payload_ix && payload_ix < eiov->vsize);
+ /* Prepend version magic on payload. */
+ iov[payload_ix].iov_base--;
+ *((byte *) iov[payload_ix].iov_base) = VERSION_MAGIC;
+ iov[payload_ix].iov_len++;
+ eiov->size++;
+ r--;
}
- reds /= TERM_TO_BINARY_LOOP_FACTOR;
- ASSERT(ob->ext_endp <= ob->alloc_endp);
- ASSERT(!ctx->ttb.hopefull_flags);
+ *ep++ = PASS_THROUGH;
+ *ep++ = VERSION_MAGIC;
}
- transcode_free_ctx(dep);
-done:
- if (!(dflags & DFLAG_DIST_HDR_ATOM_CACHE))
- *--(ob->extp) = PASS_THROUGH;
+ iov[1].iov_len = ep - (byte *) iov[1].iov_base;
+ eiov->size += iov[1].iov_len;
- if (reds < 0)
- reds = 0;
+ r--;
+ /* done... */
+
+ reds -= (start_r - r)/ERTS_TRANSCODE_REDS_FACT + 1;
+ if (reds < 0)
+ return 0;
return reds;
}
diff --git a/erts/emulator/beam/external.h b/erts/emulator/beam/external.h
index ea2b8598ff..937dc532f6 100644
--- a/erts/emulator/beam/external.h
+++ b/erts/emulator/beam/external.h
@@ -60,6 +60,7 @@
#define DIST_HEADER 'D'
#define DIST_FRAG_HEADER 'E'
#define DIST_FRAG_CONT 'F'
+#define HOPEFUL_DATA 'H'
#define ATOM_CACHE_REF 'R'
#define ATOM_INTERNAL_REF2 'I'
#define ATOM_INTERNAL_REF3 'K'
@@ -90,6 +91,8 @@
#undef ERL_NODE_TABLES_BASIC_ONLY
#include "erl_alloc.h"
+#define ERTS_NO_HIX (~((Uint32) 0))
+
#define ERTS_ATOM_CACHE_SIZE 2048
typedef struct cache {
@@ -122,7 +125,7 @@ typedef struct {
#define ERTS_DIST_EXT_ATOM_TRANS_TAB ((Uint32) 0x2)
#define ERTS_DIST_EXT_BTT_SAFE ((Uint32) 0x4)
-#define ERTS_DIST_CON_ID_MASK ((Uint32) 0x00ffffff) /* also in net_kernel.erl */
+#define ERTS_DIST_CON_ID_MASK ((Uint32) 0x00ffffff)
struct binary;
typedef struct erl_dist_external_data ErtsDistExternalData;
@@ -141,6 +144,7 @@ typedef struct erl_dist_external {
Uint32 flags;
Uint32 connection_id;
ErtsDistExternalData *data;
+ struct ErtsMonLnkDist__ *mld; /* copied from DistEntry.mld */
ErtsAtomTranslationTable attab;
} ErtsDistExternal;
@@ -153,16 +157,19 @@ typedef struct {
/* -------------------------------------------------------------------------- */
+struct TTBSizeContext_;
+struct TTBEncodeContext_;
void erts_init_atom_cache_map(ErtsAtomCacheMap *);
void erts_reset_atom_cache_map(ErtsAtomCacheMap *);
void erts_destroy_atom_cache_map(ErtsAtomCacheMap *);
-void erts_finalize_atom_cache_map(ErtsAtomCacheMap *, Uint32);
+void erts_finalize_atom_cache_map(ErtsAtomCacheMap *, Uint64);
-Uint erts_encode_ext_dist_header_size(ErtsAtomCacheMap *, Uint);
-byte *erts_encode_ext_dist_header_setup(byte *, ErtsAtomCacheMap *, Uint, Eterm);
+Uint erts_encode_ext_dist_header_size(struct TTBEncodeContext_ *ctx, ErtsAtomCacheMap *, Uint);
+byte *erts_encode_ext_dist_header_setup(struct TTBEncodeContext_ *ctx, byte *,
+ ErtsAtomCacheMap *, Uint, Eterm);
byte *erts_encode_ext_dist_header_fragment(byte **, Uint, Eterm);
-Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf*, DistEntry *, Uint32 dflags, Sint reds);
+Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf*, DistEntry *, Uint64 dflags, Sint reds);
struct erts_dsig_send_context;
typedef enum {
@@ -171,12 +178,13 @@ typedef enum {
ERTS_EXT_SZ_SYSTEM_LIMIT
} ErtsExtSzRes;
-ErtsExtSzRes erts_encode_dist_ext_size(Eterm, Uint32, ErtsAtomCacheMap*, Uint* szp);
-ErtsExtSzRes erts_encode_dist_ext_size_ctx(Eterm term, struct erts_dsig_send_context* ctx, Uint* szp);
-struct TTBEncodeContext_;
-int erts_encode_dist_ext(Eterm, byte **, Uint32, ErtsAtomCacheMap *,
- struct TTBEncodeContext_ *, Sint* reds);
-
+ErtsExtSzRes erts_encode_dist_ext_size(Eterm term, ErtsAtomCacheMap *acmp,
+ struct TTBSizeContext_ *ctx,
+ Uint* szp, Sint *redsp,
+ Sint *vlenp, Uint *fragments);
+int erts_encode_dist_ext(Eterm, byte **, Uint64, ErtsAtomCacheMap *,
+ struct TTBEncodeContext_ *, Uint *,
+ Sint *);
ErtsExtSzRes erts_encode_ext_size(Eterm, Uint *szp);
ErtsExtSzRes erts_encode_ext_size_2(Eterm, unsigned, Uint *szp);
Uint erts_encode_ext_size_ets(Eterm);
@@ -207,7 +215,8 @@ Sint erts_decode_ext_size_ets(byte*, Uint);
Eterm erts_decode_ext(ErtsHeapFactory*, byte**, Uint32 flags);
Eterm erts_decode_ext_ets(ErtsHeapFactory*, byte*);
-Eterm erts_term_to_binary(Process* p, Eterm Term, int level, Uint flags);
+Eterm erts_term_to_binary(Process* p, Eterm Term, int level, Uint64 flags);
+Eterm erts_debug_term_to_binary(Process *p, Eterm term, Eterm opts);
Sint erts_binary2term_prepare(ErtsBinary2TermState *, byte *, Sint);
void erts_binary2term_abort(ErtsBinary2TermState *);
diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h
index e429274910..5e3c283846 100644
--- a/erts/emulator/beam/global.h
+++ b/erts/emulator/beam/global.h
@@ -74,9 +74,8 @@ struct enif_resource_type_t
struct enif_resource_type_t* next; /* list of all resource types */
struct enif_resource_type_t* prev;
struct erl_module_nif* owner; /* that created this type and thus implements the destructor*/
- ErlNifResourceDtor* dtor; /* user destructor function */
- ErlNifResourceStop* stop;
- ErlNifResourceDown* down;
+ ErlNifResourceTypeInit fn;
+ ErlNifResourceTypeInit fn_real;
erts_refc_t refc; /* num of resources of this type (HOTSPOT warning)
+1 for active erl_module_nif */
Eterm module;
@@ -110,6 +109,7 @@ typedef struct ErtsResource_
extern Eterm erts_bld_resource_ref(Eterm** hp, ErlOffHeap*, ErtsResource*);
+extern BeamInstr* erts_call_nif_early(Process* c_p, ErtsCodeInfo* ci);
extern void erts_pre_nif(struct enif_environment_t*, Process*,
struct erl_module_nif*, Process* tracee);
extern void erts_post_nif(struct enif_environment_t* env);
@@ -122,6 +122,10 @@ void erts_nif_demonitored(ErtsResource* resource);
extern void erts_add_taint(Eterm mod_atom);
extern Eterm erts_nif_taints(Process* p);
extern void erts_print_nif_taints(fmtfn_t to, void* to_arg);
+
+/* Loads the specified NIF. The caller must have code write permission. */
+Eterm erts_load_nif(Process *c_p, BeamInstr *I, Eterm filename, Eterm args);
+
void erts_unload_nif(struct erl_module_nif* nif);
extern void erl_nif_init(void);
extern int erts_nif_get_funcs(struct erl_module_nif*,
@@ -885,6 +889,8 @@ void erts_bif_info_init(void);
/* bif.c */
+void erts_write_bif_wrapper(Export *export, BeamInstr *address);
+
void erts_queue_monitor_message(Process *,
ErtsProcLocks*,
Eterm,
@@ -926,6 +932,8 @@ void erts_queue_release_literals(Process *c_p, ErtsLiteralArea* literals);
#define ERTS_LITERAL_AREA_ALLOC_SIZE(N) \
(sizeof(ErtsLiteralArea) + sizeof(Eterm)*((N) - 1))
+#define ERTS_LITERAL_AREA_SIZE(AP) \
+ (ERTS_LITERAL_AREA_ALLOC_SIZE((AP)->end - (AP)->start))
extern erts_atomic_t erts_copy_literal_area__;
#define ERTS_COPY_LITERAL_AREA() \
@@ -1135,6 +1143,12 @@ extern int is_node_name_atom(Eterm a);
extern int erts_net_message(Port *, DistEntry *, Uint32 conn_id,
byte *, ErlDrvSizeT, Binary *, byte *, ErlDrvSizeT);
+int erts_dist_pend_spawn_exit_delete(ErtsMonitor *mon);
+int erts_dist_pend_spawn_exit_parent_setup(ErtsMonitor *mon);
+int erts_dist_pend_spawn_exit_parent_wait(Process *c_p,
+ ErtsProcLocks locks,
+ ErtsMonitor *mon);
+
extern void init_dist(void);
extern int stop_dist(void);
@@ -1152,6 +1166,7 @@ void erts_dirty_process_main(ErtsSchedulerData *);
Eterm build_stacktrace(Process* c_p, Eterm exc);
Eterm expand_error_value(Process* c_p, Uint freason, Eterm Value);
void erts_save_stacktrace(Process* p, struct StackTrace* s, int depth);
+BeamInstr *erts_printable_return_address(Process* p, Eterm *E) ERTS_NOINLINE;
/* erl_init.c */
@@ -1291,7 +1306,6 @@ Sint erts_binary_set_loop_limit(Sint limit);
/* erl_bif_persistent.c */
void erts_init_bif_persistent_term(void);
-Uint erts_persistent_term_count(void);
void erts_init_persistent_dumping(void);
extern ErtsLiteralArea** erts_persistent_areas;
extern Uint erts_num_persistent_areas;
@@ -1495,7 +1509,8 @@ do { \
#define MatchSetGetSource(MPSP) erts_match_set_get_source(MPSP)
-extern Binary *erts_match_set_compile(Process *p, Eterm matchexpr, Eterm MFA);
+extern Binary *erts_match_set_compile(Process *p, Eterm matchexpr, Eterm MFA,
+ Uint *freasonp);
extern void erts_match_set_release_result(Process* p);
ERTS_GLB_INLINE void erts_match_set_release_result_trace(Process* p, Eterm);
diff --git a/erts/emulator/beam/hash.c b/erts/emulator/beam/hash.c
index 8954dbb06c..177b7cc3d1 100644
--- a/erts/emulator/beam/hash.c
+++ b/erts/emulator/beam/hash.c
@@ -30,37 +30,19 @@
#include "hash.h"
/*
-** List of sizes (all are primes)
-*/
-static const int h_size_table[] = {
- 2, 5, 11, 23, 47, 97, 197, 397, 797, /* double upto here */
- 1201, 1597,
- 2411, 3203,
- 4813, 6421,
- 9643, 12853,
- 19289, 25717,
- 51437,
- 102877,
- 205759,
- 411527,
- 823117,
- 1646237,
- 3292489,
- 6584983,
- 13169977,
- 26339969,
- 52679969,
- -1
-};
-
-/*
** Get info about hash
**
*/
+#define MAX_SHIFT (ERTS_SIZEOF_TERM * 8)
+
+static int hash_get_slots(Hash *h) {
+ return UWORD_CONSTANT(1) << (MAX_SHIFT - h->shift);
+}
+
void hash_get_info(HashInfo *hi, Hash *h)
{
- int size = h->size;
+ int size = hash_get_slots(h);
int i;
int max_depth = 0;
int objects = 0;
@@ -84,7 +66,7 @@ void hash_get_info(HashInfo *hi, Hash *h)
ASSERT(objects == h->nobjs);
hi->name = h->name;
- hi->size = h->size;
+ hi->size = hash_get_slots(h);
hi->used = used;
hi->objs = h->nobjs;
hi->depth = max_depth;
@@ -118,15 +100,15 @@ hash_table_sz(Hash *h)
int i;
for(i=0;h->name[i];i++);
i++;
- return sizeof(Hash) + h->size*sizeof(HashBucket*) + i;
+ return sizeof(Hash) + hash_get_slots(h)*sizeof(HashBucket*) + i;
}
static ERTS_INLINE void set_thresholds(Hash* h)
{
- h->grow_threshold = (8*h->size)/5; /* grow at 160% load */
- if (h->size_ix > h->min_size_ix)
- h->shrink_threshold = h->size / 5; /* shrink at 20% load */
+ h->grow_threshold = (8*hash_get_slots(h))/5; /* grow at 160% load */
+ if (h->shift < h->max_shift)
+ h->shrink_threshold = hash_get_slots(h) / 5; /* shrink at 20% load */
else
h->shrink_threshold = -1; /* never shrink below inital size */
}
@@ -138,29 +120,27 @@ static ERTS_INLINE void set_thresholds(Hash* h)
Hash* hash_init(int type, Hash* h, char* name, int size, HashFunctions fun)
{
int sz;
- int ix = 0;
+ int shift = 1;
h->meta_alloc_type = type;
- while (h_size_table[ix] != -1 && h_size_table[ix] < size)
- ix++;
- if (h_size_table[ix] == -1)
- return NULL;
-
- size = h_size_table[ix];
- sz = size*sizeof(HashBucket*);
+ while ((UWORD_CONSTANT(1) << shift) < size)
+ shift++;
- h->bucket = (HashBucket**) fun.meta_alloc(h->meta_alloc_type, sz);
-
- memzero(h->bucket, sz);
h->is_allocated = 0;
h->name = name;
h->fun = fun;
- h->size = size;
- h->size_ix = ix;
- h->min_size_ix = ix;
+ h->shift = MAX_SHIFT - shift;
+ h->max_shift = h->shift;
h->nobjs = 0;
set_thresholds(h);
+
+ sz = hash_get_slots(h) * sizeof(HashBucket*);
+ h->bucket = (HashBucket**) fun.meta_alloc(h->meta_alloc_type, sz);
+ memzero(h->bucket, sz);
+
+ ASSERT(h->shift > 0 && h->shift < 64);
+
return h;
}
@@ -183,7 +163,7 @@ Hash* hash_new(int type, char* name, int size, HashFunctions fun)
*/
void hash_delete(Hash* h)
{
- int old_size = h->size;
+ int old_size = hash_get_slots(h);
int i;
for (i = 0; i < old_size; i++) {
@@ -206,22 +186,20 @@ void hash_delete(Hash* h)
static void rehash(Hash* h, int grow)
{
int sz;
- int old_size = h->size;
+ int old_size = hash_get_slots(h);
HashBucket** new_bucket;
int i;
if (grow) {
- if ((h_size_table[h->size_ix+1]) == -1)
- return;
- h->size_ix++;
+ h->shift--;
}
else {
- if (h->size_ix == 0)
+ if (h->shift == h->max_shift)
return;
- h->size_ix--;
+ h->shift++;
}
- h->size = h_size_table[h->size_ix];
- sz = h->size*sizeof(HashBucket*);
+
+ sz = hash_get_slots(h)*sizeof(HashBucket*);
new_bucket = (HashBucket **) h->fun.meta_alloc(h->meta_alloc_type, sz);
memzero(new_bucket, sz);
@@ -230,7 +208,7 @@ static void rehash(Hash* h, int grow)
HashBucket* b = h->bucket[i];
while (b != (HashBucket*) 0) {
HashBucket* b_next = b->next;
- int ix = b->hvalue % h->size;
+ Uint ix = hash_get_slot(h, b->hvalue);
b->next = new_bucket[ix];
new_bucket[ix] = b;
b = b_next;
@@ -247,16 +225,7 @@ static void rehash(Hash* h, int grow)
*/
void* hash_get(Hash* h, void* tmpl)
{
- HashValue hval = h->fun.hash(tmpl);
- int ix = hval % h->size;
- HashBucket* b = h->bucket[ix];
-
- while(b != (HashBucket*) 0) {
- if ((b->hvalue == hval) && (h->fun.cmp(tmpl, (void*)b) == 0))
- return (void*) b;
- b = b->next;
- }
- return (void*) 0;
+ return hash_fetch(h, tmpl, h->fun.hash, h->fun.cmp);
}
/*
@@ -265,7 +234,7 @@ void* hash_get(Hash* h, void* tmpl)
void* hash_put(Hash* h, void* tmpl)
{
HashValue hval = h->fun.hash(tmpl);
- int ix = hval % h->size;
+ Uint ix = hash_get_slot(h, hval);
HashBucket* b = h->bucket[ix];
while(b != (HashBucket*) 0) {
@@ -291,7 +260,7 @@ void* hash_put(Hash* h, void* tmpl)
void* hash_erase(Hash* h, void* tmpl)
{
HashValue hval = h->fun.hash(tmpl);
- int ix = hval % h->size;
+ Uint ix = hash_get_slot(h, hval);
HashBucket* b = h->bucket[ix];
HashBucket* prev = 0;
@@ -323,7 +292,7 @@ void *
hash_remove(Hash *h, void *tmpl)
{
HashValue hval = h->fun.hash(tmpl);
- int ix = hval % h->size;
+ Uint ix = hash_get_slot(h, hval);
HashBucket *b = h->bucket[ix];
HashBucket *prev = NULL;
@@ -343,11 +312,11 @@ hash_remove(Hash *h, void *tmpl)
return NULL;
}
-void hash_foreach(Hash* h, void (*func)(void *, void *), void *func_arg2)
+void hash_foreach(Hash* h, HFOREACH_FUN func, void *func_arg2)
{
int i;
- for (i = 0; i < h->size; i++) {
+ for (i = 0; i < hash_get_slots(h); i++) {
HashBucket* b = h->bucket[i];
while(b != (HashBucket*) 0) {
(*func)((void *) b, func_arg2);
diff --git a/erts/emulator/beam/hash.h b/erts/emulator/beam/hash.h
index d319aaca83..4e8eb6594b 100644
--- a/erts/emulator/beam/hash.h
+++ b/erts/emulator/beam/hash.h
@@ -18,16 +18,16 @@
* %CopyrightEnd%
*/
-/*
-** General hash functions
-**
-*/
+/**
+ * General hash functions
+ *
+ **/
#ifndef __HASH_H__
#define __HASH_H__
#include "sys.h"
-typedef unsigned long HashValue;
+typedef UWord HashValue;
typedef struct hash Hash;
typedef int (*HCMP_FUN)(void*, void*);
@@ -38,6 +38,7 @@ typedef void (*HFREE_FUN)(void*);
typedef void* (*HMALLOC_FUN)(int,size_t);
typedef void (*HMFREE_FUN)(int,void*);
typedef int (*HMPRINT_FUN)(fmtfn_t,void*,char*, ...);
+typedef void (*HFOREACH_FUN)(void *, void *);
/*
** This bucket must be placed in top of
@@ -75,11 +76,10 @@ struct hash
int is_allocated; /* 0 iff hash structure is on stack or is static */
int meta_alloc_type; /* argument to pass to meta_alloc and meta_free */
char* name; /* Table name (static string, for debugging) */
- int size; /* Number of slots */
+ int shift; /* How much to shift the hash value */
+ int max_shift; /* Never shift more than this value */
int shrink_threshold;
int grow_threshold;
- int size_ix; /* Size index in size table */
- int min_size_ix; /* Never shrink table smaller than this */
int nobjs; /* Number of objects in table */
HashBucket** bucket; /* Vector of bucket pointers (objects) */
};
@@ -96,6 +96,54 @@ void* hash_get(Hash*, void*);
void* hash_put(Hash*, void*);
void* hash_erase(Hash*, void*);
void* hash_remove(Hash*, void*);
-void hash_foreach(Hash*, void (*func)(void *, void *), void *);
+void hash_foreach(Hash*, HFOREACH_FUN, void *);
+
+ERTS_GLB_INLINE Uint hash_get_slot(Hash *h, HashValue hv);
+ERTS_GLB_INLINE void* hash_fetch(Hash *, void*, H_FUN, HCMP_FUN);
+
+#if ERTS_GLB_INLINE_INCL_FUNC_DEF
+
+ERTS_GLB_INLINE Uint
+hash_get_slot(Hash *h, HashValue hv)
+{
+ /* This slot mapping function uses fibonacci hashing in order to
+ * protect itself against a very bad hash function. This is not
+ * a hash function, so the user of hash.h should still spend time
+ * to figure out a good hash function for its data.
+ *
+ * See https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/
+ * for some thoughts and ideas about fibonacci hashing.
+ */
+
+ /* This is not strictly part of the fibonacci hashing algorithm
+ * but it does help to spread the values of the mapping function better.
+ */
+ hv ^= hv >> h->shift;
+#ifdef ARCH_64
+ /* 2^64 / 1.61803398875 = 11400714819323198485.... */
+ return (UWORD_CONSTANT(11400714819323198485) * hv) >> h->shift;
+#else
+ /* 2^32 / 1.61803398875 = 2654435769.... */
+ return (UWORD_CONSTANT(2654435769) * hv) >> h->shift;
+#endif
+}
+
+ERTS_GLB_INLINE void* hash_fetch(Hash *h, void* tmpl, H_FUN hash, HCMP_FUN cmp)
+{
+ HashValue hval = hash(tmpl);
+ Uint ix = hash_get_slot(h, hval);
+ HashBucket* b = h->bucket[ix];
+ ASSERT(h->fun.hash == hash);
+ ASSERT(h->fun.cmp == cmp);
+
+ while(b != (HashBucket*) 0) {
+ if ((b->hvalue == hval) && (cmp(tmpl, (void*)b) == 0))
+ return (void*) b;
+ b = b->next;
+ }
+ return (void*) 0;
+}
+
+#endif /* ERTS_GLB_INLINE_INCL_FUNC_DEF */
#endif
diff --git a/erts/emulator/beam/index.c b/erts/emulator/beam/index.c
index be1771b037..09d3c24424 100644
--- a/erts/emulator/beam/index.c
+++ b/erts/emulator/beam/index.c
@@ -114,35 +114,26 @@ int index_get(IndexTable* t, void* tmpl)
return -1;
}
-void erts_index_merge(Hash* src, IndexTable* dst)
+static void index_merge_foreach(IndexSlot *p, IndexTable *dst)
{
- int limit = src->size;
- HashBucket** bucket = src->bucket;
- int i;
-
- for (i = 0; i < limit; i++) {
- HashBucket* b = bucket[i];
- IndexSlot* p;
- int ix;
-
- while (b) {
- Uint sz;
- ix = dst->entries++;
- if (ix >= dst->size) {
- if (ix >= dst->limit) {
- erts_exit(ERTS_ERROR_EXIT, "no more index entries in %s (max=%d)\n",
- dst->htable.name, dst->limit);
- }
- sz = INDEX_PAGE_SIZE*sizeof(IndexSlot*);
- dst->seg_table[ix>>INDEX_PAGE_SHIFT] = erts_alloc(dst->type, sz);
- dst->size += INDEX_PAGE_SIZE;
- }
- p = (IndexSlot*) b;
- p->index = ix;
- dst->seg_table[ix>>INDEX_PAGE_SHIFT][ix&INDEX_PAGE_MASK] = p;
- b = b->next;
- }
+ Uint sz;
+ int ix = dst->entries++;
+ if (ix >= dst->size) {
+ if (ix >= dst->limit) {
+ erts_exit(ERTS_ERROR_EXIT, "no more index entries in %s (max=%d)\n",
+ dst->htable.name, dst->limit);
+ }
+ sz = INDEX_PAGE_SIZE*sizeof(IndexSlot*);
+ dst->seg_table[ix>>INDEX_PAGE_SHIFT] = erts_alloc(dst->type, sz);
+ dst->size += INDEX_PAGE_SIZE;
}
+ p->index = ix;
+ dst->seg_table[ix>>INDEX_PAGE_SHIFT][ix&INDEX_PAGE_MASK] = p;
+}
+
+void erts_index_merge(Hash* src, IndexTable* dst)
+{
+ hash_foreach(src, (HFOREACH_FUN)index_merge_foreach, dst);
}
void index_erase_latest_from(IndexTable* t, Uint from_ix)
diff --git a/erts/emulator/beam/instrs.tab b/erts/emulator/beam/instrs.tab
index 6d32d44d9a..697c777825 100644
--- a/erts/emulator/beam/instrs.tab
+++ b/erts/emulator/beam/instrs.tab
@@ -19,7 +19,12 @@
// %CopyrightEnd%
//
-// Stack manipulation instructions
+//
+// Stack manipulation instructions follow.
+//
+// See the comment for AH() in macros.tab for information about
+// the layout of stack frames.
+//
allocate(NeedStack, Live) {
$AH($NeedStack, 0, $Live);
@@ -58,105 +63,170 @@ allocate_heap_zero(NeedStack, NeedHeap, Live) {
deallocate(Deallocate) {
//| -no_prefetch
- SET_CP(c_p, (BeamInstr *) cp_val(*E));
E = ADD_BYTE_OFFSET(E, $Deallocate);
}
-deallocate_return(Deallocate) {
- //| -no_next
- int words_to_pop = $Deallocate;
- SET_I((BeamInstr *) cp_val(*E));
- E = ADD_BYTE_OFFSET(E, words_to_pop);
- CHECK_TERM(x(0));
- DispatchReturn;
+//
+// Micro-benchmarks showed that the deallocate_return instruction
+// became slower when the continuation pointer was moved from
+// the process struct to the stack. The reason seems to be read
+// dependencies, i.e. that the CPU cannot figure out beforehand
+// from which position on the stack the continuation pointer
+// should be fetched.
+//
+// Initializing num_bytes with a constant value seems to restore
+// the lost speed, so we've specialized the instruction for the
+// most common values.
+//
+
+deallocate_return0 := dealloc_ret.n0.execute;
+deallocate_return1 := dealloc_ret.n1.execute;
+deallocate_return2 := dealloc_ret.n2.execute;
+deallocate_return3 := dealloc_ret.n3.execute;
+deallocate_return4 := dealloc_ret.n4.execute;
+deallocate_return := dealloc_ret.var.execute;
+
+dealloc_ret.head() {
+ Uint num_bytes;
}
-move_deallocate_return(Src, Deallocate) {
- x(0) = $Src;
- $deallocate_return($Deallocate);
+dealloc_ret.n0() {
+ num_bytes = (0+1) * sizeof(Eterm);
}
-// Call instructions
+dealloc_ret.n1() {
+ num_bytes = (1+1) * sizeof(Eterm);
+}
+
+dealloc_ret.n2() {
+ num_bytes = (2+1) * sizeof(Eterm);
+}
+
+dealloc_ret.n3() {
+ num_bytes = (3+1) * sizeof(Eterm);
+}
-DISPATCH_REL(CallDest) {
+dealloc_ret.n4() {
+ num_bytes = (4+1) * sizeof(Eterm);
+}
+
+dealloc_ret.var(Deallocate) {
+ num_bytes = $Deallocate;
+}
+
+dealloc_ret.execute() {
//| -no_next
- $SET_I_REL($CallDest);
- DTRACE_LOCAL_CALL(c_p, erts_code_to_codemfa(I));
- Dispatch();
+
+ E = ADD_BYTE_OFFSET(E, num_bytes);
+ $RETURN();
+ CHECK_TERM(x(0));
+ $DISPATCH_RETURN();
}
-DISPATCH_ABS(CallDest) {
+move_deallocate_return(Src, Deallocate) {
//| -no_next
- SET_I((BeamInstr *) $CallDest);
- DTRACE_LOCAL_CALL(c_p, erts_code_to_codemfa(I));
- Dispatch();
+
+ /*
+ * Explicitly do reads first to mitigate the impact of read
+ * dependencies.
+ */
+
+ Uint bytes_to_pop = $Deallocate;
+ Eterm src = $Src;
+ E = ADD_BYTE_OFFSET(E, bytes_to_pop);
+ x(0) = src;
+ DTRACE_RETURN_FROM_PC(c_p, I);
+ $RETURN();
+ CHECK_TERM(x(0));
+ $DISPATCH_RETURN();
}
+// Call instructions
+
i_call(CallDest) {
- SET_CP(c_p, $NEXT_INSTRUCTION);
+ //| -no_next
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
$DISPATCH_REL($CallDest);
}
move_call(Src, CallDest) {
- x(0) = $Src;
- SET_CP(c_p, $NEXT_INSTRUCTION);
- $DISPATCH_REL($CallDest);
+ //| -no_next
+ Eterm call_dest = $CallDest;
+ Eterm src = $Src;
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
+ x(0) = src;
+ $DISPATCH_REL(call_dest);
}
i_call_last(CallDest, Deallocate) {
+ //| -no_next
$deallocate($Deallocate);
$DISPATCH_REL($CallDest);
}
move_call_last(Src, CallDest, Deallocate) {
- x(0) = $Src;
- $i_call_last($CallDest, $Deallocate);
+ //| -no_next
+ Eterm call_dest = $CallDest;
+ Eterm src = $Src;
+ $deallocate($Deallocate);
+ x(0) = src;
+ $DISPATCH_REL(call_dest);
}
i_call_only(CallDest) {
+ //| -no_next
$DISPATCH_REL($CallDest);
}
move_call_only(Src, CallDest) {
- x(0) = $Src;
- $i_call_only($CallDest);
-}
-
-DISPATCHX(Dest) {
//| -no_next
- DTRACE_GLOBAL_CALL_FROM_EXPORT(c_p, $Dest);
- // Dispatchx assumes the Export* is in Arg(0)
- I = (&$Dest) - 1;
- Dispatchx();
+ Eterm call_dest = $CallDest;
+ Eterm src = $Src;
+ x(0) = src;
+ $DISPATCH_REL(call_dest);
}
i_call_ext(Dest) {
- SET_CP(c_p, $NEXT_INSTRUCTION);
- $DISPATCHX($Dest);
+ //| -no_next
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
+ $DISPATCH_EXPORT($Dest);
}
-i_move_call_ext(Src, Dest) {
- x(0) = $Src;
- $i_call_ext($Dest);
+i_move_call_ext(Src, CallDest) {
+ //| -no_next
+ Eterm call_dest = $CallDest;
+ Eterm src = $Src;
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
+ x(0) = src;
+ $DISPATCH_EXPORT(call_dest);
}
i_call_ext_only(Dest) {
- $DISPATCHX($Dest);
+ //| -no_next
+ $DISPATCH_EXPORT($Dest);
}
-i_move_call_ext_only(Dest, Src) {
- x(0) = $Src;
- $i_call_ext_only($Dest);
+i_move_call_ext_only(CallDest, Src) {
+ //| -no_next
+ Eterm call_dest = $CallDest;
+ Eterm src = $Src;
+ x(0) = src;
+ $DISPATCH_EXPORT(call_dest);
}
i_call_ext_last(Dest, Deallocate) {
+ //| -no_next
$deallocate($Deallocate);
- $DISPATCHX($Dest);
+ $DISPATCH_EXPORT($Dest);
}
-i_move_call_ext_last(Dest, StackOffset, Src) {
- x(0) = $Src;
- $i_call_ext_last($Dest, $StackOffset);
+i_move_call_ext_last(CallDest, Deallocate, Src) {
+ //| -no_next
+ Eterm call_dest = $CallDest;
+ Eterm src = $Src;
+ $deallocate($Deallocate);
+ x(0) = src;
+ $DISPATCH_EXPORT(call_dest);
}
APPLY(I, Deallocate, Next) {
@@ -167,21 +237,24 @@ APPLY(I, Deallocate, Next) {
}
HANDLE_APPLY_ERROR() {
- I = handle_error(c_p, I, reg, &bif_export[BIF_apply_3]->info.mfa);
+ static ErtsCodeMFA apply3_mfa = {am_erlang, am_apply, 3};
+ I = handle_error(c_p, I, reg, &apply3_mfa);
goto post_error_handling;
}
i_apply() {
+ //| -no_next
BeamInstr *next;
$APPLY(NULL, 0, next);
if (ERTS_LIKELY(next != NULL)) {
- SET_CP(c_p, $NEXT_INSTRUCTION);
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
$DISPATCH_ABS(next);
}
$HANDLE_APPLY_ERROR();
}
i_apply_last(Deallocate) {
+ //| -no_next
BeamInstr *next;
$APPLY(I, $Deallocate, next);
if (ERTS_LIKELY(next != NULL)) {
@@ -192,6 +265,7 @@ i_apply_last(Deallocate) {
}
i_apply_only() {
+ //| -no_next
BeamInstr *next;
$APPLY(I, 0, next);
if (ERTS_LIKELY(next != NULL)) {
@@ -208,16 +282,18 @@ FIXED_APPLY(Arity, I, Deallocate, Next) {
}
apply(Arity) {
+ //| -no_next
BeamInstr *next;
$FIXED_APPLY($Arity, NULL, 0, next);
if (ERTS_LIKELY(next != NULL)) {
- SET_CP(c_p, $NEXT_INSTRUCTION);
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
$DISPATCH_ABS(next);
}
$HANDLE_APPLY_ERROR();
}
apply_last(Arity, Deallocate) {
+ //| -no_next
BeamInstr *next;
$FIXED_APPLY($Arity, I, $Deallocate, next);
if (ERTS_LIKELY(next != NULL)) {
@@ -237,23 +313,19 @@ HANDLE_APPLY_FUN_ERROR() {
goto find_func_info;
}
-DISPATCH_FUN(I) {
- //| -no_next
- SET_I($I);
- Dispatchfun();
-}
-
i_apply_fun() {
+ //| -no_next
BeamInstr *next;
$APPLY_FUN(next);
if (ERTS_LIKELY(next != NULL)) {
- SET_CP(c_p, $NEXT_INSTRUCTION);
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
$DISPATCH_FUN(next);
}
$HANDLE_APPLY_FUN_ERROR();
}
i_apply_fun_last(Deallocate) {
+ //| -no_next
BeamInstr *next;
$APPLY_FUN(next);
if (ERTS_LIKELY(next != NULL)) {
@@ -264,6 +336,7 @@ i_apply_fun_last(Deallocate) {
}
i_apply_fun_only() {
+ //| -no_next
BeamInstr *next;
$APPLY_FUN(next);
if (ERTS_LIKELY(next != NULL)) {
@@ -280,16 +353,18 @@ CALL_FUN(Fun, Next) {
}
i_call_fun(Fun) {
+ //| -no_next
BeamInstr *next;
$CALL_FUN($Fun, next);
if (ERTS_LIKELY(next != NULL)) {
- SET_CP(c_p, $NEXT_INSTRUCTION);
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
$DISPATCH_FUN(next);
}
$HANDLE_APPLY_FUN_ERROR();
}
i_call_fun_last(Fun, Deallocate) {
+ //| -no_next
BeamInstr *next;
$CALL_FUN($Fun, next);
if (ERTS_LIKELY(next != NULL)) {
@@ -301,18 +376,12 @@ i_call_fun_last(Fun, Deallocate) {
return() {
//| -no_next
- SET_I(c_p->cp);
- DTRACE_RETURN_FROM_PC(c_p);
-
- /*
- * We must clear the CP to make sure that a stale value do not
- * create a false module dependcy preventing code upgrading.
- * It also means that we can use the CP in stack backtraces.
- */
- c_p->cp = 0;
+ DTRACE_RETURN_FROM_PC(c_p, I);
+ $RETURN();
CHECK_TERM(r(0));
HEAP_SPACE_VERIFIED(0);
- DispatchReturn;
+
+ $DISPATCH_RETURN();
}
get_list(Src, Hd, Tl) {
@@ -478,16 +547,21 @@ i_make_fun(FunP, NumFree) {
}
move_trim(Src, Dst, Words) {
- Uint cp = E[0];
$Dst = $Src;
- E += $Words;
- E[0] = cp;
+ $i_trim($Words);
}
i_trim(Words) {
- Uint cp = E[0];
E += $Words;
- E[0] = cp;
+
+ /*
+ * Clear the reserved location for the continuation pointer at
+ * E[0]. This is not strictly necessary for correctness, but if a
+ * GC is triggered before E[0] is overwritten by another
+ * continuation pointer the now dead term at E[0] would be
+ * retained by the GC.
+ */
+ E[0] = NIL;
}
move(Src, Dst) {
@@ -599,9 +673,9 @@ move_window5(S1, S2, S3, S4, S5, D) {
move_return(Src) {
//| -no_next
x(0) = $Src;
- SET_I(c_p->cp);
- c_p->cp = 0;
- DispatchReturn;
+ DTRACE_RETURN_FROM_PC(c_p, I);
+ $RETURN();
+ $DISPATCH_RETURN();
}
move_x1(Src) {
@@ -683,10 +757,11 @@ swap(R1, R2) {
$R2 = V;
}
-swap_temp(R1, R2, Tmp) {
- Eterm V = $R1;
- $R1 = $R2;
- $R2 = $Tmp = V;
+swap2(R1, R2, R3) {
+ Eterm V = $R2;
+ $R2 = $R1;
+ $R1 = $R3;
+ $R3 = V;
}
test_heap(Nh, Live) {
@@ -952,8 +1027,17 @@ is_ge(Fail, X, Y) {
CMP_GE_ACTION($X, $Y, $FAIL($Fail));
}
-badarg(Fail) {
- $BADARG($Fail);
+is_lt_literal(Fail, X, Y) {
+ CMP_LT_LITERAL_ACTION($X, $Y, $FAIL($Fail));
+}
+
+is_ge_literal(Fail, X, Y) {
+ CMP_GE_LITERAL_ACTION($X, $Y, $FAIL($Fail));
+}
+
+badarg_body() {
+ c_p->freason = BADARG;
+ $FAIL_BODY();
//| -no_next;
}
@@ -977,8 +1061,9 @@ if_end() {
//| -no_next;
}
-system_limit(Fail) {
- $SYSTEM_LIMIT($Fail);
+system_limit_body() {
+ c_p->freason = SYSTEM_LIMIT;
+ $FAIL_BODY();
//| -no_next;
}
@@ -991,6 +1076,7 @@ catch_end(Y) {
$try_end($Y);
if (is_non_value(r(0))) {
c_p->fvalue = NIL;
+ c_p->ftrace = NIL;
if (x(1) == am_throw) {
r(0) = x(2);
} else {
@@ -1024,6 +1110,7 @@ try_case(Y) {
$try_end($Y);
ASSERT(is_non_value(r(0)));
c_p->fvalue = NIL;
+ c_p->ftrace = NIL;
r(0) = x(1);
x(1) = x(2);
x(2) = x(3);
diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c
index d7fa2f2696..d2c6dffa53 100644
--- a/erts/emulator/beam/io.c
+++ b/erts/emulator/beam/io.c
@@ -768,7 +768,7 @@ driver_create_port(ErlDrvPort creator_port_ix, /* Creating port */
Port* port;
erts_driver_t *driver;
erts_mtx_t *driver_lock = NULL;
- ErtsLinkData *ldp;
+ ErtsLink *port_lnk, *proc_lnk;
ERTS_CHK_NO_PROC_LOCKS;
@@ -814,15 +814,13 @@ driver_create_port(ErlDrvPort creator_port_ix, /* Creating port */
}
ERTS_LC_ASSERT(erts_lc_is_port_locked(port));
- ldp = erts_link_create(ERTS_LNK_TYPE_PORT,
- port->common.id, pid);
- ASSERT(ldp->a.other.item == pid);
- ASSERT(ldp->b.other.item == port->common.id);
- erts_link_tree_insert(&ERTS_P_LINKS(port), &ldp->a);
-
- if (!erts_proc_sig_send_link(NULL, pid, &ldp->b)) {
- erts_link_tree_delete(&ERTS_P_LINKS(port), &ldp->a);
- erts_link_release_both(ldp);
+ port_lnk = erts_link_internal_create(ERTS_LNK_TYPE_PORT, pid);
+ erts_link_tree_insert(&ERTS_P_LINKS(port), port_lnk);
+ proc_lnk = erts_link_internal_create(ERTS_LNK_TYPE_PORT, port->common.id);
+ if (!erts_proc_sig_send_link(NULL, pid, proc_lnk)) {
+ erts_link_tree_delete(&ERTS_P_LINKS(port), port_lnk);
+ erts_link_internal_release(proc_lnk);
+ erts_link_internal_release(port_lnk);
if (driver->handle) {
erts_rwmtx_rlock(&erts_driver_list_lock);
erts_ddll_decrement_port_count(driver->handle);
@@ -1229,14 +1227,9 @@ erts_schedule_port2port_signal(Eterm port_num, ErtsProc2PortSigData *sigdp,
int task_flags,
ErtsProc2PortSigCallback callback)
{
- Port *prt = erts_port_lookup_raw(port_num);
-
- if (!prt)
- return -1;
-
sigdp->caller = ERTS_INVALID_PID;
- return erts_port_task_schedule(prt->common.id,
+ return erts_port_task_schedule(port_num,
NULL,
ERTS_PORT_TASK_PROC_SIG,
sigdp,
@@ -2363,16 +2356,17 @@ set_port_connected(int bang_op,
if (is_not_internal_pid(connect))
return ERTS_PORT_OP_DROPPED;
- lnk = erts_link_tree_lookup_create(&ERTS_P_LINKS(prt), &created,
- ERTS_LNK_TYPE_PORT, prt->common.id,
- connect);
+ lnk = erts_link_internal_tree_lookup_create(&ERTS_P_LINKS(prt),
+ &created,
+ ERTS_LNK_TYPE_PORT,
+ connect);
if (created) {
- ErtsLinkData *ldp;
- ErtsLink *olnk = erts_link_to_other(lnk, &ldp);
- ASSERT(olnk->other.item == prt->common.id);
+ ErtsLink *olnk = erts_link_internal_create(ERTS_LNK_TYPE_PORT,
+ prt->common.id);
if (!erts_proc_sig_send_link(NULL, connect, olnk)) {
erts_link_tree_delete(&ERTS_P_LINKS(prt), lnk);
- erts_link_release_both(ldp);
+ erts_link_internal_release(lnk);
+ erts_link_internal_release(olnk);
return ERTS_PORT_OP_DROPPED;
}
if (IS_TRACED_FL(prt, F_TRACE_PORTS))
@@ -2490,24 +2484,27 @@ erts_port_connect(Process *c_p,
}
static void
-port_unlink(Port *prt, ErtsLink *lnk)
+port_unlink_failure(Eterm port_id, ErtsSigUnlinkOp *sulnk)
{
- ErtsLinkData *ldp;
- ErtsLink *dlnk, *llnk;
+ erts_proc_sig_send_unlink_ack(NULL, port_id, sulnk);
+}
- llnk = erts_link_to_other(lnk, &ldp);
- dlnk = erts_link_tree_key_delete(&ERTS_P_LINKS(prt), llnk);
- if (!dlnk)
- erts_link_release(lnk);
+static void
+port_unlink(Port *prt, erts_aint32_t state, ErtsSigUnlinkOp *sulnk)
+{
+ if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
+ port_unlink_failure(prt->common.id, sulnk);
else {
- if (IS_TRACED_FL(prt, F_TRACE_PORTS))
- trace_port(prt, am_getting_unlinked, llnk->other.item);
- if (dlnk == llnk)
- erts_link_release_both(ldp);
- else {
- erts_link_release(lnk);
- erts_link_release(dlnk);
+ ErtsILink *ilnk;
+ ilnk = (ErtsILink *) erts_link_tree_lookup(ERTS_P_LINKS(prt),
+ sulnk->from);
+ if (ilnk && !ilnk->unlinking) {
+ if (IS_TRACED_FL(prt, F_TRACE_PORTS))
+ trace_port(prt, am_getting_unlinked, sulnk->from);
+ erts_link_tree_delete(&ERTS_P_LINKS(prt), &ilnk->link);
+ erts_link_internal_release(&ilnk->link);
}
+ erts_proc_sig_send_unlink_ack(NULL, prt->common.id, sulnk);
}
}
@@ -2515,14 +2512,16 @@ static int
port_sig_unlink(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
{
if (op == ERTS_PROC2PORT_SIG_EXEC)
- port_unlink(prt, sigdp->u.unlink.lnk);
+ port_unlink(prt, state, sigdp->u.unlink.sulnk);
+ else
+ port_unlink_failure(sigdp->u.unlink.port_id, sigdp->u.unlink.sulnk);
if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY)
port_sched_op_reply(sigdp->caller, sigdp->ref, am_true, prt);
return ERTS_PORT_REDS_UNLINK;
}
ErtsPortOpResult
-erts_port_unlink(Process *c_p, Port *prt, ErtsLink *lnk, Eterm *refp)
+erts_port_unlink(Process *c_p, Port *prt, ErtsSigUnlinkOp *sulnk, Eterm *refp)
{
ErtsProc2PortSigData *sigdp;
ErtsTryImmDrvCallState try_call_state
@@ -2533,13 +2532,16 @@ erts_port_unlink(Process *c_p, Port *prt, ErtsLink *lnk, Eterm *refp)
!refp,
am_unlink);
+ ASSERT(is_internal_pid(sulnk->from));
+
switch (try_imm_drv_call(&try_call_state)) {
case ERTS_TRY_IMM_DRV_CALL_OK:
- port_unlink(prt, lnk);
+ port_unlink(prt, try_call_state.state, sulnk);
finalize_imm_drv_call(&try_call_state);
BUMP_REDS(c_p, ERTS_PORT_REDS_UNLINK);
return ERTS_PORT_OP_DONE;
case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
+ port_unlink_failure(prt->common.id, sulnk);
return ERTS_PORT_OP_DROPPED;
default:
/* Schedule call instead... */
@@ -2549,7 +2551,7 @@ erts_port_unlink(Process *c_p, Port *prt, ErtsLink *lnk, Eterm *refp)
sigdp = erts_port_task_alloc_p2p_sig_data();
sigdp->flags = ERTS_P2P_SIG_TYPE_UNLINK;
sigdp->u.unlink.port_id = prt->common.id;
- sigdp->u.unlink.lnk = lnk;
+ sigdp->u.unlink.sulnk = sulnk;
return erts_schedule_proc2port_signal(c_p,
prt,
@@ -2562,24 +2564,100 @@ erts_port_unlink(Process *c_p, Port *prt, ErtsLink *lnk, Eterm *refp)
}
static void
+port_unlink_ack_failure(Eterm port_id, ErtsSigUnlinkOp *sulnk)
+{
+ erts_proc_sig_destroy_unlink_op(sulnk);
+}
+
+static void
+port_unlink_ack(Port *prt, erts_aint32_t state, ErtsSigUnlinkOp *sulnk)
+{
+ if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
+ port_unlink_ack_failure(prt->common.id, sulnk);
+ else {
+ ErtsILink *ilnk;
+ ilnk = (ErtsILink *) erts_link_tree_lookup(ERTS_P_LINKS(prt),
+ sulnk->from);
+ if (ilnk && ilnk->unlinking == sulnk->id) {
+ erts_link_tree_delete(&ERTS_P_LINKS(prt), &ilnk->link);
+ erts_link_internal_release(&ilnk->link);
+ }
+ erts_proc_sig_destroy_unlink_op(sulnk);
+ }
+}
+
+static int
+port_sig_unlink_ack(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
+{
+ if (op == ERTS_PROC2PORT_SIG_EXEC)
+ port_unlink_ack(prt, state, sigdp->u.unlink_ack.sulnk);
+ else
+ port_unlink_ack_failure(sigdp->u.unlink_ack.port_id, sigdp->u.unlink_ack.sulnk);
+ return ERTS_PORT_REDS_UNLINK_ACK;
+}
+
+ErtsPortOpResult
+erts_port_unlink_ack(Process *c_p, Port *prt, ErtsSigUnlinkOp *sulnk)
+{
+ ErtsProc2PortSigData *sigdp;
+ ErtsTryImmDrvCallState try_call_state
+ = ERTS_INIT_TRY_IMM_DRV_CALL_STATE(c_p,
+ prt,
+ ERTS_PORT_SFLGS_DEAD,
+ 0,
+ !0,
+ am_unlink);
+
+ ASSERT(c_p);
+ sulnk->from = c_p->common.id;
+
+ switch (try_imm_drv_call(&try_call_state)) {
+ case ERTS_TRY_IMM_DRV_CALL_OK:
+ port_unlink_ack(prt, try_call_state.state, sulnk);
+ finalize_imm_drv_call(&try_call_state);
+ BUMP_REDS(c_p, ERTS_PORT_REDS_UNLINK_ACK);
+ return ERTS_PORT_OP_DONE;
+ case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
+ port_unlink_ack_failure(prt->common.id, sulnk);
+ return ERTS_PORT_OP_DROPPED;
+ default:
+ /* Schedule call instead... */
+ break;
+ }
+
+ sigdp = erts_port_task_alloc_p2p_sig_data();
+ sigdp->flags = ERTS_P2P_SIG_TYPE_UNLINK_ACK;
+ sigdp->u.unlink_ack.port_id = prt->common.id;
+ sigdp->u.unlink_ack.sulnk = sulnk;
+
+ return erts_schedule_proc2port_signal(c_p,
+ prt,
+ c_p->common.id,
+ NULL,
+ sigdp,
+ 0,
+ NULL,
+ port_sig_unlink_ack);
+}
+
+static void
port_link_failure(Eterm port_id, ErtsLink *lnk)
{
erts_proc_sig_send_link_exit(NULL, port_id, lnk, am_noproc, NIL);
}
static void
-port_link(Port *prt, erts_aint32_t state, ErtsLink *lnk)
+port_link(Port *prt, erts_aint32_t state, ErtsLink *nlnk)
{
if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
- port_link_failure(prt->common.id, lnk);
+ port_link_failure(prt->common.id, nlnk);
else {
- ErtsLink *rlnk;
- rlnk = erts_link_tree_insert_addr_replace(&ERTS_P_LINKS(prt),
- lnk);
- if (rlnk)
- erts_link_release(rlnk);
+ ErtsLink *lnk;
+ lnk = erts_link_tree_lookup_insert(&ERTS_P_LINKS(prt), nlnk);
+ if (lnk)
+ erts_link_release(nlnk);
else if (IS_TRACED_FL(prt, F_TRACE_PORTS))
- trace_port(prt, am_getting_linked, lnk->other.item);
+ trace_port(prt, am_getting_linked, nlnk->other.item);
}
}
@@ -2780,19 +2858,37 @@ erts_port_demonitor(Process *c_p, Port *prt, ErtsMonitor *mon)
port_sig_demonitor);
}
+
+/* Unlink (an internal process) from a port */
static void
-init_ack_send_reply(Port *port, Eterm resp)
+unlink_proc(Port *prt, Eterm pid)
{
+ ErtsILink *ilnk;
- if (!is_internal_port(resp)) {
- Eterm proc = port->async_open_port->to;
- ErtsLink *lnk = erts_link_tree_lookup(ERTS_P_LINKS(port),
- proc);
- if (lnk) {
- erts_link_tree_delete(&ERTS_P_LINKS(port), lnk);
- erts_proc_sig_send_unlink(NULL, lnk);
+ ASSERT(prt);
+ ASSERT(is_internal_pid(pid));
+
+ ilnk = (ErtsILink *) erts_link_tree_lookup(ERTS_P_LINKS(prt),
+ pid);
+ if (ilnk && !ilnk->unlinking) {
+ Uint64 id = erts_proc_sig_send_unlink(NULL,
+ prt->common.id,
+ &ilnk->link);
+ if (id != 0)
+ ilnk->unlinking = id;
+ else {
+ erts_link_tree_delete(&ERTS_P_LINKS(prt), &ilnk->link);
+ erts_link_internal_release(&ilnk->link);
}
}
+}
+
+static void
+init_ack_send_reply(Port *port, Eterm resp)
+{
+
+ if (!is_internal_port(resp))
+ unlink_proc(port, port->async_open_port->to);
port_sched_op_reply(port->async_open_port->to,
port->async_open_port->ref,
resp,
@@ -7079,7 +7175,6 @@ driver_failure_term(ErlDrvPort ix, Eterm term, int eof)
int driver_exit(ErlDrvPort ix, int err)
{
Port* prt = erts_drvport2port(ix);
- ErtsLink *lnk;
Eterm connected;
ERTS_CHK_NO_PROC_LOCKS;
@@ -7088,11 +7183,7 @@ int driver_exit(ErlDrvPort ix, int err)
return -1;
connected = ERTS_PORT_GET_CONNECTED(prt);
- lnk = erts_link_tree_lookup(ERTS_P_LINKS(prt), connected);
- if (lnk) {
- erts_link_tree_delete(&ERTS_P_LINKS(prt), lnk);
- erts_proc_sig_send_unlink(NULL, lnk);
- }
+ unlink_proc(prt, connected);
if (err == 0)
return driver_failure_term(ix, am_normal, 0);
diff --git a/erts/emulator/beam/macros.tab b/erts/emulator/beam/macros.tab
index 7703dd5598..b7ec1f4805 100644
--- a/erts/emulator/beam/macros.tab
+++ b/erts/emulator/beam/macros.tab
@@ -104,14 +104,136 @@ GC_TEST_PRESERVE(NeedHeap, Live, PreserveTerm) {
// Make sure that there are NeedStack + NeedHeap + 1 words available
-// on the combined heap/stack segment, then allocates NeedHeap + 1
-// words on the stack and saves CP.
+// on the combined heap/stack segment, then decrement the stack
+// pointer by (NeedStack + 1) words. Finally clear the word reserved
+// for the continuation pointer at the top of the stack.
+//
+// Stack frame layout:
+//
+// +-----------+
+// y(N) | Term |
+// +-----------+
+// .
+// .
+// .
+// +-----------+
+// y(0) | Term |
+// +-----------+
+// E ==> | NIL or CP |
+// +-----------+
+//
+// When the function owning the stack frame is the currently executing
+// function, the word at the top of the stack is NIL. When calling
+// another function, the continuation pointer will be stored in the
+// word at the top of the stack. When returning to the function
+// owning the stack frame, the word at the stack top will again be set
+// to NIL.
+
AH(NeedStack, NeedHeap, Live) {
unsigned needed = $NeedStack + 1;
$GC_TEST(needed, $NeedHeap, $Live);
E -= needed;
- *E = make_cp(c_p->cp);
- c_p->cp = 0;
+ *E = NIL;
+}
+
+
+//
+// Helpers for call instructions
+//
+
+DISPATCH() {
+ BeamInstr dis_next;
+
+ dis_next = *I;
+ CHECK_ARGS(I);
+
+ if (FCALLS > 0 || FCALLS > neg_o_reds) {
+ FCALLS--;
+ Goto(dis_next);
+ } else {
+ goto context_switch;
+ }
+}
+
+DISPATCH_ABS(CallDest) {
+ SET_I((BeamInstr *) $CallDest);
+ DTRACE_LOCAL_CALL(c_p, erts_code_to_codemfa(I));
+
+ $DISPATCH();
+}
+
+DISPATCH_EXPORT(Export) {
+ BeamInstr dis_next;
+ Export *ep;
+
+ ep = (Export*)($Export);
+
+ DTRACE_GLOBAL_CALL_FROM_EXPORT(c_p, ep);
+
+ SET_I(ep->addressv[erts_active_code_ix()]);
+ CHECK_ARGS(I);
+ dis_next = *I;
+
+ if (ERTS_UNLIKELY(FCALLS <= 0)) {
+ if (ERTS_PROC_GET_SAVED_CALLS_BUF(c_p) && FCALLS > neg_o_reds) {
+ save_calls(c_p, ep);
+ } else {
+ goto context_switch;
+ }
+ }
+
+ FCALLS--;
+ Goto(dis_next);
+}
+
+DISPATCH_FUN(I) {
+ BeamInstr dis_next;
+
+ SET_I($I);
+
+ dis_next = *I;
+ CHECK_ARGS(I);
+
+ if (FCALLS > 0 || FCALLS > neg_o_reds) {
+ FCALLS--;
+ Goto(dis_next);
+ } else {
+ goto context_switch_fun;
+ }
+}
+
+DISPATCH_REL(CallDest) {
+ $SET_I_REL($CallDest);
+ DTRACE_LOCAL_CALL(c_p, erts_code_to_codemfa(I));
+
+ $DISPATCH();
+}
+
+DISPATCH_RETURN() {
+ if (FCALLS > 0 || FCALLS > neg_o_reds) {
+ FCALLS--;
+ Goto(*I);
+ } else {
+ c_p->current = NULL;
+ c_p->arity = 1;
+ goto context_switch3;
+ }
+}
+
+// Save the continuation pointer in the reserved slot at the
+// top of the stack as preparation for doing a function call.
+
+SAVE_CONTINUATION_POINTER(IP) {
+ ASSERT(VALID_INSTR(*($IP)));
+ *E = (BeamInstr) ($IP);
+}
+
+// Return to the function whose continuation pointer is stored
+// at the top of the stack and set that word to NIL.
+
+RETURN() {
+ SET_I(cp_val(*E));
+ *E = NIL;
}
NEXT0() {
@@ -167,7 +289,7 @@ BIF_ERROR_ARITY_1(Fail, BIF, Op1) {
}
reg[0] = $Op1;
SWAPOUT;
- I = handle_error(c_p, I, reg, &bif_export[$BIF]->info.mfa);
+ I = handle_error(c_p, I, reg, &bif_trap_export[$BIF].info.mfa);
goto post_error_handling;
}
@@ -179,6 +301,6 @@ BIF_ERROR_ARITY_2(Fail, BIF, Op1, Op2) {
reg[0] = $Op1;
reg[1] = $Op2;
SWAPOUT;
- I = handle_error(c_p, I, reg, &bif_export[$BIF]->info.mfa);
+ I = handle_error(c_p, I, reg, &bif_trap_export[$BIF].info.mfa);
goto post_error_handling;
}
diff --git a/erts/emulator/beam/msg_instrs.tab b/erts/emulator/beam/msg_instrs.tab
index 10fd5ad48d..3d350e65cb 100644
--- a/erts/emulator/beam/msg_instrs.tab
+++ b/erts/emulator/beam/msg_instrs.tab
@@ -106,8 +106,10 @@ i_loop_rec(Dest) {
c_p->arity = 0;
c_p->current = NULL;
c_p->fcalls = FCALLS;
+ ERTS_UNREQ_PROC_MAIN_LOCK(c_p);
FCALLS -= erts_proc_sig_receive_helper(c_p, FCALLS, neg_o_reds,
&msgp, &get_out);
+ ERTS_REQ_PROC_MAIN_LOCK(c_p);
SWAPIN;
if (ERTS_UNLIKELY(msgp == NULL)) {
if (get_out) {
@@ -210,7 +212,8 @@ remove_message() {
ASSERT(is_small(SEQ_TRACE_TOKEN_SERIAL(c_p)));
ASSERT(is_small(SEQ_TRACE_TOKEN_LASTCNT(c_p)));
ASSERT(is_small(SEQ_TRACE_TOKEN_FLAGS(c_p)));
- ASSERT(is_pid(SEQ_TRACE_TOKEN_SENDER(c_p)));
+ ASSERT(is_pid(SEQ_TRACE_TOKEN_SENDER(c_p))
+ || is_atom(SEQ_TRACE_TOKEN_SENDER(c_p)));
c_p->seq_trace_lastcnt = unsigned_val(SEQ_TRACE_TOKEN_SERIAL(c_p));
if (c_p->seq_trace_clock < unsigned_val(SEQ_TRACE_TOKEN_SERIAL(c_p))) {
c_p->seq_trace_clock = unsigned_val(SEQ_TRACE_TOKEN_SERIAL(c_p));
diff --git a/erts/emulator/beam/ops.tab b/erts/emulator/beam/ops.tab
index b9d4f6afcc..14afdd8553 100644
--- a/erts/emulator/beam/ops.tab
+++ b/erts/emulator/beam/ops.tab
@@ -74,22 +74,29 @@ trace_jump W
return
-# To ensure that a "move Src x(0)" instruction can be combined with
-# the following call instruction, we need to make sure that there is
-# no line/1 instruction between the move and the call.
#
-# A tail-recursive call to an external function (BIF or non-BIF) will
-# never be saved on the stack, so there is no reason to keep the line
-# instruction.
+# A tail call will not refer to the current function on error unless it's a
+# BIF, so we can omit the line instruction for non-BIFs.
+#
-move S X0=x==0 | line Loc | call_ext Ar Func => \
- line Loc | move S X0 | call_ext Ar Func
-move S X0=x==0 | line Loc | call_ext_last Ar Func D => \
+move S X0=x==0 | line Loc | call_ext_last Ar Func=u$is_not_bif D => \
move S X0 | call_ext_last Ar Func D
-move S X0=x==0 | line Loc | call_ext_only Ar Func => \
+move S X0=x==0 | line Loc | call_ext_only Ar Func=u$is_not_bif => \
move S X0 | call_ext_only Ar Func
-move S X0=x==0 | line Loc | call Ar Func => \
- line Loc | move S X0 | call Ar Func
+
+move S X0=x==0 | line Loc | call_last Ar Func D => \
+ move S X0 | call_last Ar Func D
+move S X0=x==0 | line Loc | call_only Ar Func => \
+ move S X0 | call_only Ar Func
+
+# To ensure that a "move Src x(0)" instruction can be combined with
+# the following call instruction, we need to make sure that there is
+# no line/1 instruction between the move and the call. (We don't
+# need to match the call instruction, because reordering the move
+# and line instructions would be harmless even if no call instruction
+# follows.)
+
+move S X0=x==0 | line Loc => line Loc | move S X0
line Loc | func_info M F A => func_info M F A | line Loc
@@ -255,8 +262,17 @@ raise Trace Value => move Trace x | move Value x=1 | move x x=2 | i_raise
i_raise
# Internal now, but could be useful to make known to the compiler.
-badarg j
-system_limit j
+badarg/1
+badarg p => badarg_body
+badarg Fail=f => jump Fail
+
+badarg_body
+
+system_limit/1
+system_limit p => system_limit_body
+system_limit Fail=f => jump Fail
+
+system_limit_body
%hot
@@ -273,26 +289,19 @@ move_jump f c r
# Movement to and from the stack is common.
# Try to pack as much as we can into one instruction.
-# Window move
-move_window/5
-move_window/6
-
# x -> y
-move X1=x Y1=y | move X2=x Y2=y | move X3=x Y3=y | succ(Y1,Y2) | succ(Y2,Y3) => \
- move_window X1 X2 X3 Y1 Y3
-
-move X1=x Y1=y | move X2=x Y2=y | succ(Y1,Y2) => \
+move X1=x Y1=y | move X2=x Y2=y | succ(Y1, Y2) => \
move_window2 X1 X2 Y1
-move_window X1=x X2=x X3=x Y1=y Y3=y | move X4=x Y4=y | succ(Y3,Y4) => \
- move_window X1 X2 X3 X4 Y1 Y4
+move_window2 X1 X2 Y1 | move X3=x Y3=y | offset(Y1, Y3, 2) => \
+ move_window3 X1 X2 X3 Y1
-move_window X1=x X2=x X3=x X4=x Y1=y Y4=y | move X5=x Y5=y | succ(Y4,Y5) => \
- move_window5 X1 X2 X3 X4 X5 Y1
+move_window3 X1 X2 X3 Y1 | move X4=x Y4=y | offset(Y1, Y4, 3) => \
+ move_window4 X1 X2 X3 X4 Y1
-move_window X1=x X2=x X3=x Y1=y Y3=y => move_window3 X1 X2 X3 Y1
-move_window X1=x X2=x X3=x X4=x Y1=y Y4=y => move_window4 X1 X2 X3 X4 Y1
+move_window4 X1 X2 X3 X4 Y1=y | move X5=x Y5=y | offset(Y1, Y5, 4) => \
+ move_window5 X1 X2 X3 X4 X5 Y1
move_window2 x x y
move_window3 x x x y
@@ -324,76 +333,15 @@ move_src_window2 y x x
move_src_window3 y x x x
move_src_window4 y x x x x
-# Swap registers.
-move R1=xy Tmp=x | move R2=xy R1 | move Tmp R2 => swap_temp R1 R2 Tmp
-
-# The compiler uses x(1022) when swapping registers. It will definitely
-# not be used again.
-swap_temp R1 R2 Tmp=x==1022 => swap R1 R2
-
-swap_temp R1 R2 Tmp | move Src Tmp => swap R1 R2 | move Src Tmp
-
-swap_temp R1 R2 Tmp | line Loc | apply Live | is_killed_apply(Tmp, Live) => \
- swap R1 R2 | line Loc | apply Live
-swap_temp R1 R2 Tmp | line Loc | apply_last Live D | is_killed_apply(Tmp, Live) => \
- swap R1 R2 | line Loc | apply_last Live D
-
-swap_temp R1 R2 Tmp | line Loc | call_fun Live | is_killed_by_call_fun(Tmp, Live) => \
- swap R1 R2 | line Loc | call_fun Live
-swap_temp R1 R2 Tmp | make_fun2 OldIndex=u | is_killed_by_make_fun(Tmp, OldIndex) => \
- swap R1 R2 | make_fun2 OldIndex
-
-swap_temp R1 R2 Tmp | line Loc | call Live Addr | is_killed(Tmp, Live) => \
- swap R1 R2 | line Loc | call Live Addr
-swap_temp R1 R2 Tmp | call_only Live Addr | \
- is_killed(Tmp, Live) => swap R1 R2 | call_only Live Addr
-swap_temp R1 R2 Tmp | call_last Live Addr D | \
- is_killed(Tmp, Live) => swap R1 R2 | call_last Live Addr D
-
-swap_temp R1 R2 Tmp | line Loc | call_ext Live Addr | is_killed(Tmp, Live) => \
- swap R1 R2 | line Loc | call_ext Live Addr
-swap_temp R1 R2 Tmp | line Loc | call_ext_only Live Addr | \
- is_killed(Tmp, Live) => swap R1 R2 | line Loc | call_ext_only Live Addr
-swap_temp R1 R2 Tmp | line Loc | call_ext_last Live Addr D | \
- is_killed(Tmp, Live) => swap R1 R2 | line Loc | call_ext_last Live Addr D
-
-swap_temp R1 R2 Tmp | call_ext Live Addr | is_killed(Tmp, Live) => \
- swap R1 R2 | call_ext Live Addr
-swap_temp R1 R2 Tmp | call_ext_only Live Addr | is_killed(Tmp, Live) => \
- swap R1 R2 | call_ext_only Live Addr
-swap_temp R1 R2 Tmp | call_ext_last Live Addr D | is_killed(Tmp, Live) => \
- swap R1 R2 | call_ext_last Live Addr D
-
-swap_temp R1 R2 Tmp | move Src Any | line Loc | call Live Addr | \
- is_killed(Tmp, Live) | distinct(Tmp, Src) => \
- swap R1 R2 | move Src Any | line Loc | call Live Addr
-swap_temp R1 R2 Tmp | move Src Any | line Loc | call_ext Live Addr | \
- is_killed(Tmp, Live) | distinct(Tmp, Src) => \
- swap R1 R2 | move Src Any | line Loc | call_ext Live Addr
-swap_temp R1 R2 Tmp | move Src Any | call_only Live Addr | \
- is_killed(Tmp, Live) | distinct(Tmp, Src) => \
- swap R1 R2 | move Src Any | call_only Live Addr
-swap_temp R1 R2 Tmp | move Src Any | line Loc | call_ext_only Live Addr | \
- is_killed(Tmp, Live) | distinct(Tmp, Src) => \
- swap R1 R2 | move Src Any | line Loc | call_ext_only Live Addr
-swap_temp R1 R2 Tmp | move Src Any | line Loc | call_fun Live | \
- is_killed(Tmp, Live) | distinct(Tmp, Src) => \
- swap R1 R2 | move Src Any | line Loc | call_fun Live
-
-swap_temp R1 R2 Tmp | line Loc | send | is_killed_by_send(Tmp) => \
- swap R1 R2 | line Loc | send
-
-# swap_temp/3 with Y register operands are rare.
-swap_temp R1 R2=y Tmp => swap R1 R2 | move R2 Tmp
-swap_temp R1=y R2 Tmp => swap R1 R2 | move R2 Tmp
-
swap R1=x R2=y => swap R2 R1
-swap_temp x x x
-
swap xy x
swap y y
+swap R1=x R2=x | swap R3=x R1 => swap2 R1 R2 R3
+
+swap2 x x x
+
# move_shift
move SD=x D=x | move Src=cxy SD=x | distinct(D, Src) => move_shift Src SD D
@@ -442,6 +390,12 @@ move S1=x D1=x | move Y1=y X1=x | independent_moves(Y1, X1, S1, D1) => \
move2_par Y1 X1 S1 D1
move2_par y x x x
+# move2_par y y y y
+
+move Y1=y Y2=y | move Y3=y Y4=y | independent_moves(Y1, Y2, Y3, Y4) => \
+ move2_par Y1 Y2 Y3 Y4
+move2_par y y y y
+
# move3
move2_par Y1=y X1=x Y2=y X2=x | move Y3=y X3=x => move3 Y1 X1 Y2 X2 Y3 X3
@@ -450,16 +404,6 @@ move2_par X1=x X2=x X3=x X4=x | move X5=x X6=x => move3 X1 X2 X3 X4 X5 X6
move3 y x y x y x
move3 x x x x x x
-# move_x1, move_x2
-
-move C=aiq X=x==1 => move_x1 C
-move C=aiq X=x==2 => move_x2 C
-
-move n D=y => init D
-
-move_x1 c
-move_x2 c
-
move xy xy
move c xy
move n x
@@ -542,16 +486,27 @@ is_eq_exact f? y y
is_ne_exact f? S S
+# When either operand for is_lt or is_ge is a literal,
+# that literal is almost always an integer and almost never
+# an atom. Therefore we use a specialized instruction when
+# one of the operands is a literal.
+
+is_lt Fail Src=x Lit=c => is_lt_literal Fail Src Lit
+is_lt Fail Lit=c Src=x => is_lt_literal Fail Lit Src
+
is_lt f? x x
-is_lt f? x c
-is_lt f? c x
+is_lt_literal f? x c
+is_lt_literal f? c x
%cold
is_lt f? s s
%hot
+is_ge Fail Src=x Lit=c => is_ge_literal Fail Src Lit
+is_ge Fail Lit=c Src=x => is_ge_literal Fail Lit Src
+
is_ge f? x x
-is_ge f? x c
-is_ge f? c x
+is_ge_literal f? x c
+is_ge_literal f? c x
%cold
is_ge f? s s
%hot
@@ -635,8 +590,9 @@ put_list s s d
%cold
normal_exit
continue_exit
-apply_bif
-call_nif
+call_bif W
+call_nif W W W
+call_nif_early
call_error_handler
error_action_code
return_trace
@@ -657,8 +613,20 @@ move S x==0 | deallocate D | return => move_deallocate_return S D
move_deallocate_return xycn Q
+deallocate u==0 | return => deallocate_return0
+deallocate u==1 | return => deallocate_return1
+deallocate u==2 | return => deallocate_return2
+deallocate u==3 | return => deallocate_return3
+deallocate u==4 | return => deallocate_return4
+
deallocate D | return => deallocate_return D
+deallocate_return0
+deallocate_return1
+deallocate_return2
+deallocate_return3
+deallocate_return4
+
deallocate_return Q
test_heap Need u==1 | put_list Y=y x==0 x==0 => test_heap_1_put_list Need Y
@@ -836,62 +804,22 @@ allocate_init t t? y
# External function and bif calls.
#################################################################
-#
-# The BIFs erts_internal:check_process_code/1 must be called like a function,
-# to ensure that c_p->i (program counter) is set correctly (an ordinary
-# BIF call doesn't set it).
-#
-
-call_ext u==1 Bif=u$bif:erts_internal:check_process_code/1 => i_call_ext Bif
-call_ext_last u==1 Bif=u$bif:erts_internal:check_process_code/1 D => i_call_ext_last Bif D
-call_ext_only u==1 Bif=u$bif:erts_internal:check_process_code/1 => i_call_ext_only Bif
+# Expands into call_light_bif(_only)/2
+call_light_bif/1
+call_light_bif_only/1
+call_light_bif_last/2
#
-# The BIFs erts_internal:garbage_collect/1 must be called like a function,
-# to allow them to invoke the garbage collector. (The stack pointer must
-# be saved and p->arity must be zeroed, which is not done on ordinary BIF calls.)
+# The load_nif/2 BIF is an instruction.
#
-call_ext u==1 Bif=u$bif:erts_internal:garbage_collect/1 => i_call_ext Bif
-call_ext_last u==1 Bif=u$bif:erts_internal:garbage_collect/1 D => i_call_ext_last Bif D
-call_ext_only u==1 Bif=u$bif:erts_internal:garbage_collect/1 => i_call_ext_only Bif
-#
-# put/2 and erase/1 must be able to do garbage collection, so we must call
-# them like functions.
-#
-
-call_ext u==2 Bif=u$bif:erlang:put/2 => i_call_ext Bif
-call_ext_last u==2 Bif=u$bif:erlang:put/2 D => i_call_ext_last Bif D
-call_ext_only u==2 Bif=u$bif:erlang:put/2 => i_call_ext_only Bif
-
-call_ext u==1 Bif=u$bif:erlang:erase/1 => i_call_ext Bif
-call_ext_last u==1 Bif=u$bif:erlang:erase/1 D => i_call_ext_last Bif D
-call_ext_only u==1 Bif=u$bif:erlang:erase/1 => i_call_ext_only Bif
-
-#
-# The process_info/1,2 BIF should be called like a function, to force
-# the emulator to set c_p->current before calling it (a BIF call doesn't
-# set it).
-#
-# In addition, we force the use of a non-tail-recursive call. This will ensure
-# that c_p->cp points into the function making the call.
-#
-
-call_ext u==1 Bif=u$bif:erlang:process_info/1 => i_call_ext Bif
-call_ext_last u==1 Bif=u$bif:erlang:process_info/1 D => i_call_ext Bif | deallocate_return D
-call_ext_only Ar=u==1 Bif=u$bif:erlang:process_info/1 => allocate u Ar | i_call_ext Bif | deallocate_return u
-
-call_ext u==2 Bif=u$bif:erlang:process_info/2 => i_call_ext Bif
-call_ext_last u==2 Bif=u$bif:erlang:process_info/2 D => i_call_ext Bif | deallocate_return D
-call_ext_only Ar=u==2 Bif=u$bif:erlang:process_info/2 => allocate u Ar | i_call_ext Bif | deallocate_return u
-
-#
-# load_nif/2 also needs to know calling function like process_info
-#
-call_ext u==2 Bif=u$bif:erlang:load_nif/2 => i_call_ext Bif
-call_ext_last u==2 Bif=u$bif:erlang:load_nif/2 D => i_call_ext Bif | deallocate_return D
-call_ext_only Ar=u==2 Bif=u$bif:erlang:load_nif/2 => allocate u Ar | i_call_ext Bif | deallocate_return u
+call_ext u==2 u$func:erlang:load_nif/2 => i_load_nif
+call_ext_last u==2 u$func:erlang:load_nif/2 D => i_load_nif | deallocate_return D
+call_ext_only u==2 u$func:erlang:load_nif/2 => i_load_nif | return
+%cold
+i_load_nif
+%hot
#
# apply/2 is an instruction, not a BIF.
@@ -905,36 +833,9 @@ call_ext_only u==2 u$func:erlang:apply/2 => i_apply_fun_only
# The apply/3 BIF is an instruction.
#
-call_ext u==3 u$bif:erlang:apply/3 => i_apply
-call_ext_last u==3 u$bif:erlang:apply/3 D => i_apply_last D
-call_ext_only u==3 u$bif:erlang:apply/3 => i_apply_only
-
-#
-# The exit/1 and throw/1 BIFs never execute the instruction following them;
-# thus there is no need to generate any return instruction.
-#
-
-call_ext_last u==1 Bif=u$bif:erlang:exit/1 D => call_bif Bif
-call_ext_last u==1 Bif=u$bif:erlang:throw/1 D => call_bif Bif
-
-call_ext_only u==1 Bif=u$bif:erlang:exit/1 => call_bif Bif
-call_ext_only u==1 Bif=u$bif:erlang:throw/1 => call_bif Bif
-
-#
-# The error/1 and error/2 BIFs never execute the instruction following them;
-# thus there is no need to generate any return instruction.
-# However, they generate stack backtraces, so if the call instruction
-# is call_ext_only/2 instruction, we explicitly do an allocate/2 to store
-# the continuation pointer on the stack.
-#
-
-call_ext_last u==1 Bif=u$bif:erlang:error/1 D => call_bif Bif
-call_ext_last u==2 Bif=u$bif:erlang:error/2 D => call_bif Bif
-
-call_ext_only Ar=u==1 Bif=u$bif:erlang:error/1 => \
- allocate u Ar | call_bif Bif
-call_ext_only Ar=u==2 Bif=u$bif:erlang:error/2 => \
- allocate u Ar | call_bif Bif
+call_ext u==3 u$func:erlang:apply/3 => i_apply
+call_ext_last u==3 u$func:erlang:apply/3 D => i_apply_last D
+call_ext_only u==3 u$func:erlang:apply/3 => i_apply_only
#
# The yield/0 BIF is an instruction
@@ -1035,8 +936,16 @@ call_ext_last Arity u$func:erlang:dt_append_vm_tag_data/1 D => \
call_ext_only Arity u$func:erlang:dt_append_vm_tag_data/1 => \
return
-# Can happen after one of the transformations above.
-move Discarded x==0 | move Something x==0 => move Something x=0
+# After one of the transformations above there can be a redundant move
+# instruction that can be discarded.
+#
+# The distinct() guard protects against the second move instruction being
+# `{move,{x,0},{x,0}}`, which is never generated by the compiler but could
+# be generated by an alternative code generator or found in handwritten
+# BEAM code.
+
+move Discarded x==0 | move Something Dst=x==0 | distinct(Something, Dst) => \
+ move Something x=0
%endif
@@ -1048,17 +957,24 @@ call_ext_only u==0 u$func:os:perf_counter/0 => \
i_perf_counter | return
#
-# The general case for BIFs that have no special instructions.
-# A BIF used in the tail must be followed by a return instruction.
+# BIFs like process_info/1,2 require up-to-date information about the current
+# emulator state, which the ordinary call_light_bif instruction doesn't save.
#
-# To make trapping and stack backtraces work correctly, we make sure that
-# the continuation pointer is always stored on the stack.
-call_ext u Bif=u$is_bif => call_bif Bif
+call_ext u Bif=u$is_bif | is_heavy_bif(Bif) => \
+ i_call_ext Bif
+call_ext_last u Bif=u$is_bif D | is_heavy_bif(Bif) => \
+ i_call_ext Bif | deallocate_return D
+call_ext_only Ar=u Bif=u$is_bif | is_heavy_bif(Bif) => \
+ allocate u Ar | i_call_ext Bif | deallocate_return u
-call_ext_last u Bif=u$is_bif D => deallocate D | call_bif_only Bif
+#
+# The general case for BIFs that have no special requirements.
+#
-call_ext_only Ar=u Bif=u$is_bif => call_bif_only Bif
+call_ext u Bif=u$is_bif => call_light_bif Bif
+call_ext_last u Bif=u$is_bif D => call_light_bif_last Bif D
+call_ext_only Ar=u Bif=u$is_bif => call_light_bif_only Bif
#
# Any remaining calls are calls to Erlang functions, not BIFs.
@@ -1083,14 +999,32 @@ i_apply_fun
i_apply_fun_last Q
i_apply_fun_only
+#
+# When a BIF is traced, these instructions make a body call through the export
+# entry instead of calling the BIF directly (setting up a temporary stack frame
+# if needed). We therefore retain the stack frame in call_light_bif_last, and
+# add a deallocate_return after call_light_bif_only to remove the temporary
+# stack frame before returning.
+#
+
+call_light_bif Bif=u$is_bif => \
+ call_light_bif Bif Bif
+
+call_light_bif_last Bif=u$is_bif D => \
+ call_light_bif Bif Bif | deallocate_return D
+
+call_light_bif_only Bif=u$is_bif => \
+ call_light_bif_only Bif Bif | deallocate_return u
+
+call_light_bif b e
+call_light_bif_only b e
+
%cold
-i_hibernate
+i_hibernate
i_perf_counter
-%hot
-call_bif e
-call_bif_only e
+%hot
#
# Calls to non-building and guard BIFs.
@@ -1252,7 +1186,7 @@ i_bs_get_integer_imm Ms Bits Live Fail Flags Y=y => \
i_bs_get_integer_small_imm xy W f? t x
i_bs_get_integer_imm xy W t f? t x
-i_bs_get_integer xy f? t t s d
+i_bs_get_integer xy f? t t S d
i_bs_get_integer_8 xy f? d
i_bs_get_integer_16 xy f? d
@@ -1265,7 +1199,7 @@ bs_get_binary2 Fail=f Ms=xy Live=u Sz=sq Unit=u Flags=u Dst=d => \
gen_get_binary2(Fail, Ms, Live, Sz, Unit, Flags, Dst)
i_bs_get_binary_imm2 xy f? t W t d
-i_bs_get_binary2 xy f t? s t d
+i_bs_get_binary2 xy f t? S t d
i_bs_get_binary_all2 xy f? t t d
# Fetching float from binaries.
@@ -1302,16 +1236,32 @@ bs_get_tail xy d t
# "slots" in the context itself, which lets us continue matching even after
# we've passed it off to another function.
+bs_start_match4 a==am_no_fail Live=u Src=xy Ctx=d => \
+ bs_start_match3 p Src Live Ctx
+bs_start_match4 Fail=f Live=u Src=xy Ctx=d => \
+ bs_start_match3 Fail Src Live Ctx
+
%if ARCH_64
+
+# This instruction nops on 64-bit platforms
+bs_start_match4 a==am_resume Live Same Same =>
+bs_start_match4 a==am_resume Live Ctx Dst => move Ctx Dst
+
bs_start_match3 Fail Bin Live Ctx | bs_get_position Ctx Pos=x Ignored => \
i_bs_start_match3_gp Bin Live Fail Ctx Pos
-i_bs_start_match3_gp xy t f d x
+i_bs_start_match3_gp xy t j d x
+
+%else
+
+bs_start_match4 a==am_resume Live Ctx Dst => \
+ bs_start_match4 a=am_no_fail Live Ctx Dst
+
%endif
-bs_start_match3 Fail=f ica Live Dst => jump Fail
+bs_start_match3 Fail=j ica Live Dst => jump Fail
bs_start_match3 Fail Bin Live Dst => i_bs_start_match3 Bin Live Fail Dst
-i_bs_start_match3 xy t f d
+i_bs_start_match3 xy t j d
# Match context position instructions. 64-bit assumes that all positions can
# fit into an unsigned small.
@@ -1406,6 +1356,9 @@ bs_append Fail Size Extra Live Unit Bin Flags Dst => \
bs_private_append Fail Size Unit Bin Flags Dst => \
i_bs_private_append Fail Unit Size Bin Dst
+i_bs_private_append Fail Unit Size Bin Dst=y => \
+ i_bs_private_append Fail Unit Size Bin x | move x Dst
+
bs_init_writable
i_bs_append j? I t? t s xy
@@ -1793,3 +1746,21 @@ i_recv_set
build_stacktrace
raw_raise
+
+#
+# Specialized move instructions. Since they don't require a second
+# instruction, we have intentionally placed them after any other
+# transformation rules that starts with a move instruction in order to
+# produce better code for the transformation engine.
+#
+
+# move_x1, move_x2
+
+move C=aiq X=x==1 => move_x1 C
+move C=aiq X=x==2 => move_x2 C
+
+move n D=y => init D
+
+move_x1 c
+move_x2 c
+
diff --git a/erts/emulator/beam/packet_parser.c b/erts/emulator/beam/packet_parser.c
index 4b526887b5..8f24725326 100644
--- a/erts/emulator/beam/packet_parser.c
+++ b/erts/emulator/beam/packet_parser.c
@@ -816,9 +816,6 @@ int packet_parse_http(const char* buf, int len, int* statep,
ptr++;
if (--n == 0) return -1;
}
- while (n && SP(ptr)) { /* Skip white space before ':' */
- ptr++; n--;
- }
if (*ptr != ':') {
return -1;
}
@@ -837,7 +834,9 @@ int packet_parse_http(const char* buf, int len, int* statep,
while (n && SP(ptr)) {
ptr++; n--;
}
- return pcb->http_header(arg, name, name_ptr, name_len,
+ return pcb->http_header(arg, name,
+ name_ptr, name_len,
+ buf, name_len,
ptr, n);
}
return -1;
diff --git a/erts/emulator/beam/packet_parser.h b/erts/emulator/beam/packet_parser.h
index 358d650804..b05623efec 100644
--- a/erts/emulator/beam/packet_parser.h
+++ b/erts/emulator/beam/packet_parser.h
@@ -77,8 +77,10 @@ typedef int HttpResponseMessageFn(void* arg, int major, int minor, int status,
typedef int HttpRequestMessageFn(void* arg, const http_atom_t* meth, const char* meth_ptr,
int meth_len, const PacketHttpURI*, int major, int minor);
typedef int HttpEohMessageFn(void *arg);
-typedef int HttpHeaderMessageFn(void* arg, const http_atom_t* name, const char* name_ptr,
- int name_len, const char* value_ptr, int value_len);
+typedef int HttpHeaderMessageFn(void* arg, const http_atom_t* name,
+ const char* name_ptr, int name_len,
+ const char* oname_ptr, int oname_len,
+ const char* value_ptr, int value_len);
typedef int HttpErrorMessageFn(void* arg, const char* buf, int len);
typedef int SslTlsFn(void* arg, unsigned type, unsigned major, unsigned minor,
const char* data, int len, const char* prefix, int plen);
diff --git a/erts/emulator/beam/register.c b/erts/emulator/beam/register.c
index c7e02c6d48..8e44b527a2 100644
--- a/erts/emulator/beam/register.c
+++ b/erts/emulator/beam/register.c
@@ -265,10 +265,8 @@ Eterm
erts_whereis_name_to_id(Process *c_p, Eterm name)
{
Eterm res = am_undefined;
- HashValue hval;
- int ix;
- HashBucket* b;
ErtsProcLocks c_p_locks = 0;
+ RegProc *rp, tmpl;
if (c_p) {
c_p_locks = ERTS_PROC_LOCK_MAIN;
ERTS_CHK_HAVE_ONLY_MAIN_PROC_LOCK(c_p);
@@ -278,29 +276,14 @@ erts_whereis_name_to_id(Process *c_p, Eterm name)
if (c_p && !c_p_locks)
erts_proc_lock(c_p, ERTS_PROC_LOCK_MAIN);
- hval = REG_HASH(name);
- ix = hval % process_reg.size;
- b = process_reg.bucket[ix];
+ tmpl.name = name;
+ rp = hash_fetch(&process_reg, &tmpl, (H_FUN)reg_hash, (HCMP_FUN)reg_cmp);
- /*
- * Note: We have inlined the code from hash.c for speed.
- */
-
- while (b) {
- RegProc* rp = (RegProc *) b;
- if (rp->name == name) {
- /*
- * SMP NOTE: No need to lock registered entity since it cannot
- * be removed without acquiring write reg lock and id on entity
- * is read only.
- */
- if (rp->p)
- res = rp->p->common.id;
- else if (rp->pt)
- res = rp->pt->common.id;
- break;
- }
- b = b->next;
+ if (rp) {
+ if (rp->p)
+ res = rp->p->common.id;
+ else if (rp->pt)
+ res = rp->pt->common.id;
}
reg_read_unlock();
@@ -321,10 +304,7 @@ erts_whereis_name(Process *c_p,
Port** port,
int lock_port)
{
- RegProc* rp = NULL;
- HashValue hval;
- int ix;
- HashBucket* b;
+ RegProc* rp = NULL, tmpl;
ErtsProcLocks current_c_p_locks;
Port *pending_port = NULL;
@@ -342,21 +322,8 @@ erts_whereis_name(Process *c_p,
* - current_c_p_locks (either c_p_locks or 0) on c_p
*/
- hval = REG_HASH(name);
- ix = hval % process_reg.size;
- b = process_reg.bucket[ix];
-
- /*
- * Note: We have inlined the code from hash.c for speed.
- */
-
- while (b) {
- if (((RegProc *) b)->name == name) {
- rp = (RegProc *) b;
- break;
- }
- b = b->next;
- }
+ tmpl.name = name;
+ rp = hash_fetch(&process_reg, &tmpl, (H_FUN)reg_hash, (HCMP_FUN)reg_cmp);
if (proc) {
if (!rp)
@@ -564,18 +531,6 @@ int erts_unregister_name(Process *c_p,
return res;
}
-int process_reg_size(void)
-{
- int size;
- int lock = !ERTS_IS_CRASH_DUMPING;
- if (lock)
- reg_read_lock();
- size = process_reg.size;
- if (lock)
- reg_read_unlock();
- return size;
-}
-
int process_reg_sz(void)
{
int sz;
@@ -592,15 +547,24 @@ int process_reg_sz(void)
#include "bif.h"
+struct registered_foreach_arg {
+ Eterm res;
+ Eterm *hp;
+};
+
+static void
+registered_foreach(RegProc *reg, struct registered_foreach_arg *arg)
+{
+ arg->res = CONS(arg->hp, reg->name, arg->res);
+ arg->hp += 2;
+}
+
/* return a list of the registered processes */
BIF_RETTYPE registered_0(BIF_ALIST_0)
{
- int i;
- Eterm res;
+ struct registered_foreach_arg arg;
Uint need;
- Eterm* hp;
- HashBucket **bucket;
ErtsProcLocks proc_locks = ERTS_PROC_LOCK_MAIN;
ERTS_CHK_HAVE_ONLY_MAIN_PROC_LOCK(BIF_P);
@@ -608,41 +572,21 @@ BIF_RETTYPE registered_0(BIF_ALIST_0)
if (!proc_locks)
erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
- bucket = process_reg.bucket;
-
- /* work out how much heap we need & maybe garb, by scanning through
- the registered process table */
- need = 0;
- for (i = 0; i < process_reg.size; i++) {
- HashBucket *b = bucket[i];
- while (b != NULL) {
- need += 2;
- b = b->next;
- }
- }
+ /* work out how much heap we need */
+ need = process_reg.nobjs * 2;
if (need == 0) {
reg_read_unlock();
BIF_RET(NIL);
}
- hp = HAlloc(BIF_P, need);
-
- /* scan through again and make the list */
- res = NIL;
+ /* scan through again and make the list */
+ arg.hp = HAlloc(BIF_P, need);
+ arg.res = NIL;
- for (i = 0; i < process_reg.size; i++) {
- HashBucket *b = bucket[i];
- while (b != NULL) {
- RegProc *reg = (RegProc *) b;
-
- res = CONS(hp, reg->name, res);
- hp += 2;
- b = b->next;
- }
- }
+ hash_foreach(&process_reg, (HFOREACH_FUN)registered_foreach, &arg);
reg_read_unlock();
- BIF_RET(res);
+ BIF_RET(arg.res);
}
diff --git a/erts/emulator/beam/register.h b/erts/emulator/beam/register.h
index 27a314ca78..c77bd03653 100644
--- a/erts/emulator/beam/register.h
+++ b/erts/emulator/beam/register.h
@@ -41,7 +41,6 @@ typedef struct reg_proc
Eterm name; /* Atom name */
} RegProc;
-int process_reg_size(void);
int process_reg_sz(void);
void init_register_table(void);
void register_info(fmtfn_t, void *);
diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h
index fed84f8b52..90e3008102 100644
--- a/erts/emulator/beam/sys.h
+++ b/erts/emulator/beam/sys.h
@@ -92,6 +92,12 @@
# define ERTS_GLB_INLINE_INCL_FUNC_DEF 0
#endif
+#ifdef __GNUC__
+# define ERTS_NOINLINE __attribute__((__noinline__))
+#else
+# define ERTS_NOINLINE
+#endif
+
#if defined(VALGRIND) && !defined(NO_FPE_SIGNALS)
# define NO_FPE_SIGNALS
#endif
@@ -172,7 +178,8 @@ typedef ERTS_SYS_FD_TYPE ErtsSysFdType;
# define ERTS_UNLIKELY(BOOL) (BOOL)
#endif
-#if ERTS_AT_LEAST_GCC_VSN__(2, 96, 0)
+/* AIX doesn't like this and claims section conflicts */
+#if ERTS_AT_LEAST_GCC_VSN__(2, 96, 0) && !defined(_AIX)
#if (defined(__APPLE__) && defined(__MACH__)) || defined(__DARWIN__)
# define ERTS_WRITE_UNLIKELY(X) X __attribute__ ((section ("__DATA,ERTS_LOW_WRITE") ))
#else
@@ -211,7 +218,7 @@ typedef ERTS_SYS_FD_TYPE ErtsSysFdType;
#endif
/* In VC++, noreturn is a declspec that has to be before the types,
- * but in GNUC it is an att ribute to be placed between return type
+ * but in GNUC it is an attribute to be placed between return type
* and function name, hence __decl_noreturn <types> __noreturn <function name>
*
* at some platforms (e.g. Android) __noreturn is defined at sys/cdef.h
@@ -254,6 +261,39 @@ __decl_noreturn void __noreturn erl_assert_error(const char* expr, const char *f
#endif
/*
+ * ERTS_GCC_DIAG_ON and ERTS_GCC_DIAG_OFF can be used to temporarly
+ * disable a gcc or clang warning in a file.
+ *
+ * Example:
+ * GCC_DIAG_OFF(unused-function)
+ * static int test(){ return 0;}
+ * GCC_DIAG_ON(unused-function)
+ *
+ * These macros were orginally authored by Jonathan Wakely and has
+ * been modified by Patrick Horgan.
+ *
+ * Source: http://dbp-consulting.com/tutorials/SuppressingGCCWarnings.html
+ *
+ */
+#if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402
+#define ERTS_GCC_DIAG_STR(s) #s
+#define ERTS_GCC_DIAG_JOINSTR(x,y) ERTS_GCC_DIAG_STR(x ## y)
+# define ERTS_GCC_DIAG_DO_PRAGMA(x) _Pragma (#x)
+# define ERTS_GCC_DIAG_PRAGMA(x) ERTS_GCC_DIAG_DO_PRAGMA(GCC diagnostic x)
+# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
+# define ERTS_GCC_DIAG_OFF(x) ERTS_GCC_DIAG_PRAGMA(push) \
+ ERTS_GCC_DIAG_PRAGMA(ignored ERTS_GCC_DIAG_JOINSTR(-W,x))
+# define ERTS_GCC_DIAG_ON(x) ERTS_GCC_DIAG_PRAGMA(pop)
+# else
+# define ERTS_GCC_DIAG_OFF(x) ERTS_GCC_DIAG_PRAGMA(ignored ERTS_GCC_DIAG_JOINSTR(-W,x))
+# define ERTS_GCC_DIAG_ON(x) ERTS_GCC_DIAG_PRAGMA(warning ERTS_GCC_DIAG_JOINSTR(-W,x))
+# endif
+#else
+# define ERTS_GCC_DIAG_OFF(x)
+# define ERTS_GCC_DIAG_ON(x)
+#endif
+
+/*
* Compile time assert
* (the actual compiler error msg can be a bit confusing)
*/
@@ -666,7 +706,16 @@ typedef struct preload {
*/
typedef Eterm ErtsTracer;
-#include "erl_osenv.h"
+
+/*
+ * This structure contains the rb tree for the erlang osenv copy
+ * see erl_osenv.h for more details.
+ */
+typedef struct __erts_osenv_t {
+ struct __env_rbtnode_t *tree;
+ int variable_count;
+ int content_size;
+} erts_osenv_t;
extern char *erts_default_arg0;
diff --git a/erts/emulator/beam/time.c b/erts/emulator/beam/time.c
index 97f58f4e31..05f932ad84 100644
--- a/erts/emulator/beam/time.c
+++ b/erts/emulator/beam/time.c
@@ -195,6 +195,9 @@
#if 0
# define ERTS_TW_HARD_DEBUG
#endif
+#if 0
+# define ERTS_TW_DEBUG
+#endif
#if defined(ERTS_TW_HARD_DEBUG) && !defined(ERTS_TW_DEBUG)
# define ERTS_TW_DEBUG
@@ -480,6 +483,8 @@ find_next_timeout(ErtsSchedulerData *esdp, ErtsTimerWheel *tiw)
ERTS_HARD_DBG_CHK_WHEELS(tiw, 0);
+ ERTS_TW_ASSERT(tiw->at_once.nto == 0);
+ ERTS_TW_ASSERT(tiw->nto == tiw->soon.nto + tiw->later.nto);
ERTS_TW_ASSERT(tiw->yield_slot == ERTS_TW_SLOT_INACTIVE);
if (tiw->nto == 0) { /* no timeouts in wheel */
@@ -866,6 +871,8 @@ erts_bump_timers(ErtsTimerWheel *tiw, ErtsMonotonicTime curr_time)
}
if (tiw->pos >= bump_to) {
+ if (tiw->at_once.nto)
+ continue;
ERTS_MSACC_POP_STATE_M_X();
break;
}
diff --git a/erts/emulator/beam/trace_instrs.tab b/erts/emulator/beam/trace_instrs.tab
index ed37a11d36..c128400637 100644
--- a/erts/emulator/beam/trace_instrs.tab
+++ b/erts/emulator/beam/trace_instrs.tab
@@ -20,16 +20,15 @@
//
return_trace() {
- ErtsCodeMFA* mfa = (ErtsCodeMFA *)(E[0]);
+ ErtsCodeMFA* mfa = (ErtsCodeMFA *)(E[1]);
SWAPOUT; /* Needed for shared heap */
ERTS_UNREQ_PROC_MAIN_LOCK(c_p);
- erts_trace_return(c_p, mfa, r(0), ERTS_TRACER_FROM_ETERM(E+1)/* tracer */);
+ erts_trace_return(c_p, mfa, r(0), ERTS_TRACER_FROM_ETERM(E+2)/* tracer */);
ERTS_REQ_PROC_MAIN_LOCK(c_p);
SWAPIN;
- c_p->cp = NULL;
- SET_I((BeamInstr *) cp_val(E[2]));
E += 3;
+ $RETURN();
Goto(*I);
//| -no_next
}
@@ -45,14 +44,12 @@ i_generic_breakpoint() {
}
i_return_time_trace() {
- ErtsCodeInfo *cinfo = (!is_CP(E[0]) ? NULL
- : erts_code_to_codeinfo((BeamInstr*)E[0]));
+ ErtsCodeInfo *cinfo = (!is_CP(E[1]) ? NULL : erts_code_to_codeinfo((BeamInstr*)E[1]));
SWAPOUT;
erts_trace_time_return(c_p, cinfo);
SWAPIN;
- c_p->cp = NULL;
- SET_I((BeamInstr *) cp_val(E[1]));
E += 2;
+ $RETURN();
Goto(*I);
//| -no_next
}
@@ -60,8 +57,10 @@ i_return_time_trace() {
i_return_to_trace() {
if (IS_TRACED_FL(c_p, F_TRACE_RETURN_TO)) {
Uint *cpp = (Uint*) E;
+ while (is_not_CP(*cpp)) {
+ cpp++;
+ }
for(;;) {
- ASSERT(is_CP(*cpp));
if (IsOpCode(*cp_val(*cpp), return_trace)) {
do
++cpp;
@@ -81,9 +80,8 @@ i_return_to_trace() {
ERTS_REQ_PROC_MAIN_LOCK(c_p);
SWAPIN;
}
- c_p->cp = NULL;
- SET_I((BeamInstr *) cp_val(E[0]));
E += 1;
+ $RETURN();
Goto(*I);
//| -no_next
}
@@ -109,7 +107,7 @@ i_hibernate() {
goto do_schedule;
} else {
HEAVY_SWAPIN;
- I = handle_error(c_p, I, reg, &bif_export[BIF_hibernate_3]->info.mfa);
+ I = handle_error(c_p, I, reg, &bif_trap_export[BIF_hibernate_3].info.mfa);
goto post_error_handling;
}
//| -no_next
diff --git a/erts/emulator/beam/utils.c b/erts/emulator/beam/utils.c
index 809bb7b42c..d1731ae092 100644
--- a/erts/emulator/beam/utils.c
+++ b/erts/emulator/beam/utils.c
@@ -66,7 +66,7 @@
#undef M_MMAP_THRESHOLD
#undef M_MMAP_MAX
-#if defined(__GLIBC__) && defined(HAVE_MALLOC_H)
+#if (defined(__GLIBC__) || defined(_AIX)) && defined(HAVE_MALLOC_H)
#include <malloc.h>
#endif
@@ -907,7 +907,7 @@ tail_recur:
hash = hash * FUNNY_NUMBER10 + num_free;
hash = hash*FUNNY_NUMBER1 +
(atom_tab(atom_val(funp->fe->module))->slot.bucket.hvalue);
- hash = hash*FUNNY_NUMBER2 + funp->fe->old_index;
+ hash = hash*FUNNY_NUMBER2 + funp->fe->index;
hash = hash*FUNNY_NUMBER2 + funp->fe->old_uniq;
if (num_free > 0) {
if (num_free > 1) {
@@ -1069,54 +1069,237 @@ do { \
#define HCONST 0x9e3779b9UL /* the golden ratio; an arbitrary value */
-static Uint32
-block_hash(byte *k, Uint length, Uint32 initval)
+typedef struct {
+ Uint32 a,b,c;
+} ErtsBlockHashHelperCtx;
+
+#define BLOCK_HASH_BYTES_PER_ITER 12
+
+/* The three functions below are separated into different functions even
+ though they are always used together to make trapping and handling
+ of unaligned binaries easier. Examples of how they are used can be
+ found in block_hash and make_hash2_helper.*/
+static ERTS_INLINE
+void block_hash_setup(Uint32 initval,
+ ErtsBlockHashHelperCtx* ctx /* out parameter */)
+{
+ ctx->a = ctx->b = HCONST;
+ ctx->c = initval; /* the previous hash value */
+}
+
+static ERTS_INLINE
+void block_hash_buffer(byte *buf,
+ Uint buf_length,
+ ErtsBlockHashHelperCtx* ctx /* out parameter */)
{
- Uint32 a,b,c;
- Uint len;
-
- /* Set up the internal state */
- len = length;
- a = b = HCONST;
- c = initval; /* the previous hash value */
-
- while (len >= 12)
- {
- a += (k[0] +((Uint32)k[1]<<8) +((Uint32)k[2]<<16) +((Uint32)k[3]<<24));
- b += (k[4] +((Uint32)k[5]<<8) +((Uint32)k[6]<<16) +((Uint32)k[7]<<24));
- c += (k[8] +((Uint32)k[9]<<8) +((Uint32)k[10]<<16)+((Uint32)k[11]<<24));
- MIX(a,b,c);
- k += 12; len -= 12;
- }
-
- c += length;
- switch(len) /* all the case statements fall through */
- {
- case 11: c+=((Uint32)k[10]<<24);
- case 10: c+=((Uint32)k[9]<<16);
- case 9 : c+=((Uint32)k[8]<<8);
- /* the first byte of c is reserved for the length */
- case 8 : b+=((Uint32)k[7]<<24);
- case 7 : b+=((Uint32)k[6]<<16);
- case 6 : b+=((Uint32)k[5]<<8);
- case 5 : b+=k[4];
- case 4 : a+=((Uint32)k[3]<<24);
- case 3 : a+=((Uint32)k[2]<<16);
- case 2 : a+=((Uint32)k[1]<<8);
- case 1 : a+=k[0];
- /* case 0: nothing left to add */
- }
- MIX(a,b,c);
- return c;
+ Uint len = buf_length;
+ byte *k = buf;
+ ASSERT(buf_length % BLOCK_HASH_BYTES_PER_ITER == 0);
+ while (len >= BLOCK_HASH_BYTES_PER_ITER) {
+ ctx->a += (k[0] +((Uint32)k[1]<<8) +((Uint32)k[2]<<16) +((Uint32)k[3]<<24));
+ ctx->b += (k[4] +((Uint32)k[5]<<8) +((Uint32)k[6]<<16) +((Uint32)k[7]<<24));
+ ctx->c += (k[8] +((Uint32)k[9]<<8) +((Uint32)k[10]<<16)+((Uint32)k[11]<<24));
+ MIX(ctx->a,ctx->b,ctx->c);
+ k += BLOCK_HASH_BYTES_PER_ITER; len -= BLOCK_HASH_BYTES_PER_ITER;
+ }
}
+static ERTS_INLINE
+Uint32 block_hash_final_bytes(byte *buf,
+ Uint buf_length,
+ Uint full_length,
+ ErtsBlockHashHelperCtx* ctx)
+{
+ Uint len = buf_length;
+ byte *k = buf;
+ ctx->c += full_length;
+ switch(len)
+ { /* all the case statements fall through */
+ case 11: ctx->c+=((Uint32)k[10]<<24);
+ case 10: ctx->c+=((Uint32)k[9]<<16);
+ case 9 : ctx->c+=((Uint32)k[8]<<8);
+ /* the first byte of c is reserved for the length */
+ case 8 : ctx->b+=((Uint32)k[7]<<24);
+ case 7 : ctx->b+=((Uint32)k[6]<<16);
+ case 6 : ctx->b+=((Uint32)k[5]<<8);
+ case 5 : ctx->b+=k[4];
+ case 4 : ctx->a+=((Uint32)k[3]<<24);
+ case 3 : ctx->a+=((Uint32)k[2]<<16);
+ case 2 : ctx->a+=((Uint32)k[1]<<8);
+ case 1 : ctx->a+=k[0];
+ /* case 0: nothing left to add */
+ }
+ MIX(ctx->a,ctx->b,ctx->c);
+ return ctx->c;
+}
+
+static
Uint32
-make_hash2(Eterm term)
+block_hash(byte *block, Uint block_length, Uint32 initval)
{
+ ErtsBlockHashHelperCtx ctx;
+ Uint no_bytes_not_in_loop =
+ (block_length % BLOCK_HASH_BYTES_PER_ITER);
+ Uint no_bytes_to_process_in_loop =
+ block_length - no_bytes_not_in_loop;
+ byte *final_bytes = block + no_bytes_to_process_in_loop;
+ block_hash_setup(initval, &ctx);
+ block_hash_buffer(block,
+ no_bytes_to_process_in_loop,
+ &ctx);
+ return block_hash_final_bytes(final_bytes,
+ no_bytes_not_in_loop,
+ block_length,
+ &ctx);
+}
+
+typedef enum {
+ tag_primary_list,
+ arityval_subtag,
+ hamt_subtag_head_flatmap,
+ map_subtag,
+ fun_subtag,
+ neg_big_subtag,
+ sub_binary_subtag_1,
+ sub_binary_subtag_2,
+ hash2_common_1,
+ hash2_common_2,
+ hash2_common_3,
+} ErtsMakeHash2TrapLocation;
+
+typedef struct {
+ int c;
+ Uint32 sh;
+ Eterm* ptr;
+} ErtsMakeHash2Context_TAG_PRIMARY_LIST;
+
+typedef struct {
+ int i;
+ int arity;
+ Eterm* elem;
+} ErtsMakeHash2Context_ARITYVAL_SUBTAG;
+
+typedef struct {
+ Eterm *ks;
+ Eterm *vs;
+ int i;
+ Uint size;
+} ErtsMakeHash2Context_HAMT_SUBTAG_HEAD_FLATMAP;
+
+typedef struct {
+ Eterm* ptr;
+ int i;
+} ErtsMakeHash2Context_MAP_SUBTAG;
+
+typedef struct {
+ Uint num_free;
+ Eterm* bptr;
+} ErtsMakeHash2Context_FUN_SUBTAG;
+
+typedef struct {
+ Eterm* ptr;
+ Uint i;
+ Uint n;
+ Uint32 con;
+} ErtsMakeHash2Context_NEG_BIG_SUBTAG;
+
+typedef struct {
+ byte* bptr;
+ Uint sz;
+ Uint bitsize;
+ Uint bitoffs;
+ Uint no_bytes_processed;
+ ErtsBlockHashHelperCtx block_hash_ctx;
+ /* The following fields are only used when bitoffs != 0 */
+ byte* buf;
+ int done;
+
+} ErtsMakeHash2Context_SUB_BINARY_SUBTAG;
+
+typedef struct {
+ int dummy__; /* Empty structs are not supported on all platforms */
+} ErtsMakeHash2Context_EMPTY;
+
+typedef struct {
+ ErtsMakeHash2TrapLocation trap_location;
+ /* specific to the trap location: */
+ union {
+ ErtsMakeHash2Context_TAG_PRIMARY_LIST tag_primary_list;
+ ErtsMakeHash2Context_ARITYVAL_SUBTAG arityval_subtag;
+ ErtsMakeHash2Context_HAMT_SUBTAG_HEAD_FLATMAP hamt_subtag_head_flatmap;
+ ErtsMakeHash2Context_MAP_SUBTAG map_subtag;
+ ErtsMakeHash2Context_FUN_SUBTAG fun_subtag;
+ ErtsMakeHash2Context_NEG_BIG_SUBTAG neg_big_subtag;
+ ErtsMakeHash2Context_SUB_BINARY_SUBTAG sub_binary_subtag_1;
+ ErtsMakeHash2Context_SUB_BINARY_SUBTAG sub_binary_subtag_2;
+ ErtsMakeHash2Context_EMPTY hash2_common_1;
+ ErtsMakeHash2Context_EMPTY hash2_common_2;
+ ErtsMakeHash2Context_EMPTY hash2_common_3;
+ } trap_location_state;
+ /* same for all trap locations: */
+ Eterm term;
Uint32 hash;
Uint32 hash_xor_pairs;
- DeclareTmpHeapNoproc(tmp_big,2);
+ ErtsEStack stack;
+} ErtsMakeHash2Context;
+
+static int make_hash2_ctx_bin_dtor(Binary *context_bin) {
+ ErtsMakeHash2Context* context = ERTS_MAGIC_BIN_DATA(context_bin);
+ DESTROY_SAVED_ESTACK(&context->stack);
+ if (context->trap_location == sub_binary_subtag_2 &&
+ context->trap_location_state.sub_binary_subtag_2.buf != NULL) {
+ erts_free(ERTS_ALC_T_PHASH2_TRAP, context->trap_location_state.sub_binary_subtag_2.buf);
+ }
+ return 1;
+}
+/* hash2_save_trap_state is called seldom so we want to avoid inlining */
+static ERTS_NOINLINE
+Eterm hash2_save_trap_state(Eterm state_mref,
+ Uint32 hash_xor_pairs,
+ Uint32 hash,
+ Process* p,
+ Eterm term,
+ Eterm* ESTK_DEF_STACK(s),
+ ErtsEStack s,
+ ErtsMakeHash2TrapLocation trap_location,
+ void* trap_location_state_ptr,
+ size_t trap_location_state_size) {
+ Binary* state_bin;
+ ErtsMakeHash2Context* context;
+ if (state_mref == THE_NON_VALUE) {
+ Eterm* hp;
+ state_bin = erts_create_magic_binary(sizeof(ErtsMakeHash2Context),
+ make_hash2_ctx_bin_dtor);
+ hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE);
+ state_mref = erts_mk_magic_ref(&hp, &MSO(p), state_bin);
+ } else {
+ state_bin = erts_magic_ref2bin(state_mref);
+ }
+ context = ERTS_MAGIC_BIN_DATA(state_bin);
+ context->term = term;
+ context->hash = hash;
+ context->hash_xor_pairs = hash_xor_pairs;
+ ESTACK_SAVE(s, &context->stack);
+ context->trap_location = trap_location;
+ sys_memcpy(&context->trap_location_state,
+ trap_location_state_ptr,
+ trap_location_state_size);
+ erts_set_gc_state(p, 0);
+ BUMP_ALL_REDS(p);
+ return state_mref;
+}
+#undef NOINLINE_HASH2_SAVE_TRAP_STATE
+
+/* Writes back a magic reference to *state_mref_write_back when the
+ function traps */
+static ERTS_INLINE Uint32
+make_hash2_helper(Eterm term_param, const int can_trap, Eterm* state_mref_write_back, Process* p)
+{
+ static const Uint ITERATIONS_PER_RED = 64;
+ Uint32 hash;
+ Uint32 hash_xor_pairs;
+ Eterm term = term_param;
ERTS_UNDEF(hash_xor_pairs, 0);
/* (HCONST * {2, ..., 22}) mod 2^32 */
@@ -1168,12 +1351,63 @@ make_hash2(Eterm term)
#define IS_SSMALL28(x) (((Uint) (((x) >> (28-1)) + 1)) < 2)
+#define NOT_SSMALL28_HASH(SMALL) \
+ do { \
+ Uint64 t; \
+ Uint32 x, y; \
+ Uint32 con; \
+ if (SMALL < 0) { \
+ con = HCONST_10; \
+ t = (Uint64)(SMALL * (-1)); \
+ } else { \
+ con = HCONST_11; \
+ t = SMALL; \
+ } \
+ x = t & 0xffffffff; \
+ y = t >> 32; \
+ UINT32_HASH_2(x, y, con); \
+ } while(0)
+
#ifdef ARCH_64
# define POINTER_HASH(Ptr, AConst) UINT32_HASH_2((Uint32)(UWord)(Ptr), (((UWord)(Ptr)) >> 32), AConst)
#else
# define POINTER_HASH(Ptr, AConst) UINT32_HASH(Ptr, AConst)
#endif
+#define TRAP_LOCATION_NO_RED(location_name) \
+ do { \
+ if(can_trap && iterations_until_trap <= 0) { \
+ *state_mref_write_back = \
+ hash2_save_trap_state(state_mref, \
+ hash_xor_pairs, \
+ hash, \
+ p, \
+ term, \
+ ESTK_DEF_STACK(s), \
+ s, \
+ location_name, \
+ &ctx, \
+ sizeof(ctx)); \
+ return 0; \
+ L_##location_name: \
+ ctx = context->trap_location_state. location_name; \
+ } \
+ } while(0)
+
+#define TRAP_LOCATION(location_name) \
+ do { \
+ if (can_trap) { \
+ iterations_until_trap--; \
+ TRAP_LOCATION_NO_RED(location_name); \
+ } \
+ } while(0)
+
+#define TRAP_LOCATION_NO_CTX(location_name) \
+ do { \
+ ErtsMakeHash2Context_EMPTY ctx; \
+ TRAP_LOCATION(location_name); \
+ } while(0)
+
/* Optimization. Simple cases before declaration of estack. */
if (primary_tag(term) == TAG_PRIMARY_IMMED1) {
switch (term & _TAG_IMMED1_MASK) {
@@ -1186,51 +1420,94 @@ make_hash2(Eterm term)
break;
case _TAG_IMMED1_SMALL:
{
- Sint x = signed_val(term);
-
- if (SMALL_BITS > 28 && !IS_SSMALL28(x)) {
- term = small_to_big(x, tmp_big);
- break;
+ Sint small = signed_val(term);
+ if (SMALL_BITS > 28 && !IS_SSMALL28(small)) {
+ hash = 0;
+ NOT_SSMALL28_HASH(small);
+ return hash;
}
hash = 0;
- SINT32_HASH(x, HCONST);
+ SINT32_HASH(small, HCONST);
return hash;
}
}
};
{
Eterm tmp;
+ long max_iterations = 0;
+ long iterations_until_trap = 0;
+ Eterm state_mref = THE_NON_VALUE;
+ ErtsMakeHash2Context* context = NULL;
DECLARE_ESTACK(s);
-
- UseTmpHeapNoproc(2);
+ ESTACK_CHANGE_ALLOCATOR(s, ERTS_ALC_T_SAVED_ESTACK);
+ if(can_trap){
+#ifdef DEBUG
+ (void)ITERATIONS_PER_RED;
+ iterations_until_trap = max_iterations =
+ (1103515245 * (ERTS_BIF_REDS_LEFT(p)) + 12345) % 227;
+#else
+ iterations_until_trap = max_iterations =
+ ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(p);
+#endif
+ }
+ if (can_trap && is_internal_magic_ref(term)) {
+ Binary* state_bin;
+ state_mref = term;
+ state_bin = erts_magic_ref2bin(state_mref);
+ if (ERTS_MAGIC_BIN_DESTRUCTOR(state_bin) == make_hash2_ctx_bin_dtor) {
+ /* Restore state after a trap */
+ context = ERTS_MAGIC_BIN_DATA(state_bin);
+ term = context->term;
+ hash = context->hash;
+ hash_xor_pairs = context->hash_xor_pairs;
+ ESTACK_RESTORE(s, &context->stack);
+ ASSERT(p->flags & F_DISABLE_GC);
+ erts_set_gc_state(p, 1);
+ switch (context->trap_location) {
+ case hash2_common_3: goto L_hash2_common_3;
+ case tag_primary_list: goto L_tag_primary_list;
+ case arityval_subtag: goto L_arityval_subtag;
+ case hamt_subtag_head_flatmap: goto L_hamt_subtag_head_flatmap;
+ case map_subtag: goto L_map_subtag;
+ case fun_subtag: goto L_fun_subtag;
+ case neg_big_subtag: goto L_neg_big_subtag;
+ case sub_binary_subtag_1: goto L_sub_binary_subtag_1;
+ case sub_binary_subtag_2: goto L_sub_binary_subtag_2;
+ case hash2_common_1: goto L_hash2_common_1;
+ case hash2_common_2: goto L_hash2_common_2;
+ }
+ }
+ }
hash = 0;
for (;;) {
switch (primary_tag(term)) {
case TAG_PRIMARY_LIST:
{
- int c = 0;
- Uint32 sh = 0;
- Eterm* ptr = list_val(term);
- while (is_byte(*ptr)) {
+ ErtsMakeHash2Context_TAG_PRIMARY_LIST ctx = {
+ .c = 0,
+ .sh = 0,
+ .ptr = list_val(term)};
+ while (is_byte(*ctx.ptr)) {
/* Optimization for strings. */
- sh = (sh << 8) + unsigned_val(*ptr);
- if (c == 3) {
- UINT32_HASH(sh, HCONST_4);
- c = sh = 0;
+ ctx.sh = (ctx.sh << 8) + unsigned_val(*ctx.ptr);
+ if (ctx.c == 3) {
+ UINT32_HASH(ctx.sh, HCONST_4);
+ ctx.c = ctx.sh = 0;
} else {
- c++;
+ ctx.c++;
}
- term = CDR(ptr);
+ term = CDR(ctx.ptr);
if (is_not_list(term))
break;
- ptr = list_val(term);
+ ctx.ptr = list_val(term);
+ TRAP_LOCATION(tag_primary_list);
}
- if (c > 0)
- UINT32_HASH(sh, HCONST_4);
+ if (ctx.c > 0)
+ UINT32_HASH(ctx.sh, HCONST_4);
if (is_list(term)) {
- tmp = CDR(ptr);
+ tmp = CDR(ctx.ptr);
ESTACK_PUSH(s, tmp);
- term = CAR(ptr);
+ term = CAR(ctx.ptr);
}
}
break;
@@ -1241,34 +1518,39 @@ make_hash2(Eterm term)
switch (hdr & _TAG_HEADER_MASK) {
case ARITYVAL_SUBTAG:
{
- int i;
- int arity = header_arity(hdr);
- Eterm* elem = tuple_val(term);
- UINT32_HASH(arity, HCONST_9);
- if (arity == 0) /* Empty tuple */
+ ErtsMakeHash2Context_ARITYVAL_SUBTAG ctx = {
+ .i = 0,
+ .arity = header_arity(hdr),
+ .elem = tuple_val(term)};
+ UINT32_HASH(ctx.arity, HCONST_9);
+ if (ctx.arity == 0) /* Empty tuple */
goto hash2_common;
- for (i = arity; ; i--) {
- term = elem[i];
- if (i == 1)
+ for (ctx.i = ctx.arity; ; ctx.i--) {
+ term = ctx.elem[ctx.i];
+ if (ctx.i == 1)
break;
ESTACK_PUSH(s, term);
+ TRAP_LOCATION(arityval_subtag);
}
}
break;
case MAP_SUBTAG:
{
- Eterm* ptr = boxed_val(term) + 1;
Uint size;
- int i;
+ ErtsMakeHash2Context_MAP_SUBTAG ctx = {
+ .ptr = boxed_val(term) + 1,
+ .i = 0};
switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
case HAMT_SUBTAG_HEAD_FLATMAP:
{
flatmap_t *mp = (flatmap_t *)flatmap_val(term);
- Eterm *ks = flatmap_get_keys(mp);
- Eterm *vs = flatmap_get_values(mp);
- size = flatmap_get_size(mp);
- UINT32_HASH(size, HCONST_16);
- if (size == 0)
+ ErtsMakeHash2Context_HAMT_SUBTAG_HEAD_FLATMAP ctx = {
+ .ks = flatmap_get_keys(mp),
+ .vs = flatmap_get_values(mp),
+ .i = 0,
+ .size = flatmap_get_size(mp)};
+ UINT32_HASH(ctx.size, HCONST_16);
+ if (ctx.size == 0)
goto hash2_common;
/* We want a portable hash function that is *independent* of
@@ -1281,17 +1563,18 @@ make_hash2(Eterm term)
ESTACK_PUSH(s, HASH_MAP_TAIL);
hash = 0;
hash_xor_pairs = 0;
- for (i = size - 1; i >= 0; i--) {
+ for (ctx.i = ctx.size - 1; ctx.i >= 0; ctx.i--) {
ESTACK_PUSH(s, HASH_MAP_PAIR);
- ESTACK_PUSH(s, vs[i]);
- ESTACK_PUSH(s, ks[i]);
+ ESTACK_PUSH(s, ctx.vs[ctx.i]);
+ ESTACK_PUSH(s, ctx.ks[ctx.i]);
+ TRAP_LOCATION(hamt_subtag_head_flatmap);
}
goto hash2_common;
}
case HAMT_SUBTAG_HEAD_ARRAY:
case HAMT_SUBTAG_HEAD_BITMAP:
- size = *ptr++;
+ size = *ctx.ptr++;
UINT32_HASH(size, HCONST_16);
if (size == 0)
goto hash2_common;
@@ -1303,27 +1586,28 @@ make_hash2(Eterm term)
}
switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
case HAMT_SUBTAG_HEAD_ARRAY:
- i = 16;
+ ctx.i = 16;
break;
case HAMT_SUBTAG_HEAD_BITMAP:
case HAMT_SUBTAG_NODE_BITMAP:
- i = hashmap_bitcount(MAP_HEADER_VAL(hdr));
+ ctx.i = hashmap_bitcount(MAP_HEADER_VAL(hdr));
break;
default:
erts_exit(ERTS_ERROR_EXIT, "bad header");
}
- while (i) {
- if (is_list(*ptr)) {
- Eterm* cons = list_val(*ptr);
+ while (ctx.i) {
+ if (is_list(*ctx.ptr)) {
+ Eterm* cons = list_val(*ctx.ptr);
ESTACK_PUSH(s, HASH_MAP_PAIR);
ESTACK_PUSH(s, CDR(cons));
ESTACK_PUSH(s, CAR(cons));
}
else {
- ASSERT(is_boxed(*ptr));
- ESTACK_PUSH(s, *ptr);
+ ASSERT(is_boxed(*ctx.ptr));
+ ESTACK_PUSH(s, *ctx.ptr);
}
- i--; ptr++;
+ ctx.i--; ctx.ptr++;
+ TRAP_LOCATION(map_subtag);
}
goto hash2_common;
}
@@ -1344,22 +1628,25 @@ make_hash2(Eterm term)
case FUN_SUBTAG:
{
ErlFunThing* funp = (ErlFunThing *) fun_val(term);
- Uint num_free = funp->num_free;
+ ErtsMakeHash2Context_FUN_SUBTAG ctx = {
+ .num_free = funp->num_free,
+ .bptr = NULL};
UINT32_HASH_2
- (num_free,
+ (ctx.num_free,
atom_tab(atom_val(funp->fe->module))->slot.bucket.hvalue,
HCONST);
UINT32_HASH_2
- (funp->fe->old_index, funp->fe->old_uniq, HCONST);
- if (num_free == 0) {
+ (funp->fe->index, funp->fe->old_uniq, HCONST);
+ if (ctx.num_free == 0) {
goto hash2_common;
} else {
- Eterm* bptr = funp->env + num_free - 1;
- while (num_free-- > 1) {
- term = *bptr--;
+ ctx.bptr = funp->env + ctx.num_free - 1;
+ while (ctx.num_free-- > 1) {
+ term = *ctx.bptr--;
ESTACK_PUSH(s, term);
+ TRAP_LOCATION(fun_subtag);
}
- term = *bptr;
+ term = *ctx.bptr;
}
}
break;
@@ -1367,70 +1654,190 @@ make_hash2(Eterm term)
case HEAP_BINARY_SUBTAG:
case SUB_BINARY_SUBTAG:
{
- byte* bptr;
- unsigned sz = binary_size(term);
+#define BYTE_BITS 8
+ ErtsMakeHash2Context_SUB_BINARY_SUBTAG ctx = {
+ .bptr = 0,
+ /* !!!!!!!!!!!!!!!!!!!! OBS !!!!!!!!!!!!!!!!!!!!
+ *
+ * The size is truncated to 32 bits on the line
+ * below so that the code is compatible with old
+ * versions of the code. This means that hash
+ * values for binaries with a size greater than
+ * 4GB do not take all bytes in consideration.
+ *
+ * !!!!!!!!!!!!!!!!!!!! OBS !!!!!!!!!!!!!!!!!!!!
+ */
+ .sz = (0xFFFFFFFF & binary_size(term)),
+ .bitsize = 0,
+ .bitoffs = 0,
+ .no_bytes_processed = 0
+ };
Uint32 con = HCONST_13 + hash;
- Uint bitoffs;
- Uint bitsize;
-
- ERTS_GET_BINARY_BYTES(term, bptr, bitoffs, bitsize);
- if (sz == 0 && bitsize == 0) {
+ Uint iters_for_bin = MAX(1, ctx.sz / BLOCK_HASH_BYTES_PER_ITER);
+ ERTS_GET_BINARY_BYTES(term, ctx.bptr, ctx.bitoffs, ctx.bitsize);
+ if (ctx.sz == 0 && ctx.bitsize == 0) {
hash = con;
- } else {
- if (bitoffs == 0) {
- hash = block_hash(bptr, sz, con);
- if (bitsize > 0) {
- UINT32_HASH_2(bitsize, (bptr[sz] >> (8 - bitsize)),
- HCONST_15);
- }
- } else {
- byte* buf = (byte *) erts_alloc(ERTS_ALC_T_TMP,
- sz + (bitsize != 0));
- erts_copy_bits(bptr, bitoffs, 1, buf, 0, 1, sz*8+bitsize);
- hash = block_hash(buf, sz, con);
- if (bitsize > 0) {
- UINT32_HASH_2(bitsize, (buf[sz] >> (8 - bitsize)),
- HCONST_15);
- }
- erts_free(ERTS_ALC_T_TMP, (void *) buf);
- }
+ } else if (ctx.bitoffs == 0 &&
+ (!can_trap ||
+ (iterations_until_trap - iters_for_bin) > 0)) {
+ /* No need to trap while hashing binary */
+ if (can_trap) iterations_until_trap -= iters_for_bin;
+ hash = block_hash(ctx.bptr, ctx.sz, con);
+ if (ctx.bitsize > 0) {
+ UINT32_HASH_2(ctx.bitsize,
+ (ctx.bptr[ctx.sz] >> (BYTE_BITS - ctx.bitsize)),
+ HCONST_15);
+ }
+ } else if (ctx.bitoffs == 0) {
+ /* Need to trap while hashing binary */
+ ErtsBlockHashHelperCtx* block_hash_ctx = &ctx.block_hash_ctx;
+ block_hash_setup(con, block_hash_ctx);
+ do {
+ Uint max_bytes_to_process =
+ iterations_until_trap <= 0 ? BLOCK_HASH_BYTES_PER_ITER :
+ iterations_until_trap * BLOCK_HASH_BYTES_PER_ITER;
+ Uint bytes_left = ctx.sz - ctx.no_bytes_processed;
+ Uint even_bytes_left =
+ bytes_left - (bytes_left % BLOCK_HASH_BYTES_PER_ITER);
+ Uint bytes_to_process =
+ MIN(max_bytes_to_process, even_bytes_left);
+ block_hash_buffer(&ctx.bptr[ctx.no_bytes_processed],
+ bytes_to_process,
+ block_hash_ctx);
+ ctx.no_bytes_processed += bytes_to_process;
+ iterations_until_trap -=
+ MAX(1, bytes_to_process / BLOCK_HASH_BYTES_PER_ITER);
+ TRAP_LOCATION_NO_RED(sub_binary_subtag_1);
+ block_hash_ctx = &ctx.block_hash_ctx; /* Restore after trap */
+ } while ((ctx.sz - ctx.no_bytes_processed) >=
+ BLOCK_HASH_BYTES_PER_ITER);
+ hash = block_hash_final_bytes(ctx.bptr +
+ ctx.no_bytes_processed,
+ ctx.sz - ctx.no_bytes_processed,
+ ctx.sz,
+ block_hash_ctx);
+ if (ctx.bitsize > 0) {
+ UINT32_HASH_2(ctx.bitsize,
+ (ctx.bptr[ctx.sz] >> (BYTE_BITS - ctx.bitsize)),
+ HCONST_15);
+ }
+ } else if (/* ctx.bitoffs != 0 && */
+ (!can_trap ||
+ (iterations_until_trap - iters_for_bin) > 0)) {
+ /* No need to trap while hashing binary */
+ Uint nr_of_bytes = ctx.sz + (ctx.bitsize != 0);
+ byte *buf = erts_alloc(ERTS_ALC_T_TMP, nr_of_bytes);
+ Uint nr_of_bits_to_copy = ctx.sz*BYTE_BITS+ctx.bitsize;
+ if (can_trap) iterations_until_trap -= iters_for_bin;
+ erts_copy_bits(ctx.bptr,
+ ctx.bitoffs, 1, buf, 0, 1, nr_of_bits_to_copy);
+ hash = block_hash(buf, ctx.sz, con);
+ if (ctx.bitsize > 0) {
+ UINT32_HASH_2(ctx.bitsize,
+ (buf[ctx.sz] >> (BYTE_BITS - ctx.bitsize)),
+ HCONST_15);
+ }
+ erts_free(ERTS_ALC_T_TMP, buf);
+ } else /* ctx.bitoffs != 0 && */ {
+#ifdef DEBUG
+#define BINARY_BUF_SIZE (BLOCK_HASH_BYTES_PER_ITER * 3)
+#else
+#define BINARY_BUF_SIZE (BLOCK_HASH_BYTES_PER_ITER * 256)
+#endif
+#define BINARY_BUF_SIZE_BITS (BINARY_BUF_SIZE*BYTE_BITS)
+ /* Need to trap while hashing binary */
+ ErtsBlockHashHelperCtx* block_hash_ctx = &ctx.block_hash_ctx;
+ Uint nr_of_bytes = ctx.sz + (ctx.bitsize != 0);
+ ERTS_CT_ASSERT(BINARY_BUF_SIZE % BLOCK_HASH_BYTES_PER_ITER == 0);
+ ctx.buf = erts_alloc(ERTS_ALC_T_PHASH2_TRAP,
+ MIN(nr_of_bytes, BINARY_BUF_SIZE));
+ block_hash_setup(con, block_hash_ctx);
+ do {
+ Uint bytes_left =
+ ctx.sz - ctx.no_bytes_processed;
+ Uint even_bytes_left =
+ bytes_left - (bytes_left % BLOCK_HASH_BYTES_PER_ITER);
+ Uint bytes_to_process =
+ MIN(BINARY_BUF_SIZE, even_bytes_left);
+ Uint nr_of_bits_left =
+ (ctx.sz*BYTE_BITS+ctx.bitsize) -
+ ctx.no_bytes_processed*BYTE_BITS;
+ Uint nr_of_bits_to_copy =
+ MIN(nr_of_bits_left, BINARY_BUF_SIZE_BITS);
+ ctx.done = nr_of_bits_left == nr_of_bits_to_copy;
+ erts_copy_bits(ctx.bptr + ctx.no_bytes_processed,
+ ctx.bitoffs, 1, ctx.buf, 0, 1,
+ nr_of_bits_to_copy);
+ block_hash_buffer(ctx.buf,
+ bytes_to_process,
+ block_hash_ctx);
+ ctx.no_bytes_processed += bytes_to_process;
+ iterations_until_trap -=
+ MAX(1, bytes_to_process / BLOCK_HASH_BYTES_PER_ITER);
+ TRAP_LOCATION_NO_RED(sub_binary_subtag_2);
+ block_hash_ctx = &ctx.block_hash_ctx; /* Restore after trap */
+ } while (!ctx.done);
+ nr_of_bytes = ctx.sz + (ctx.bitsize != 0);
+ hash = block_hash_final_bytes(ctx.buf +
+ (ctx.no_bytes_processed -
+ ((nr_of_bytes-1) / BINARY_BUF_SIZE) * BINARY_BUF_SIZE),
+ ctx.sz - ctx.no_bytes_processed,
+ ctx.sz,
+ block_hash_ctx);
+ if (ctx.bitsize > 0) {
+ Uint last_byte_index =
+ nr_of_bytes - (((nr_of_bytes-1) / BINARY_BUF_SIZE) * BINARY_BUF_SIZE) -1;
+ UINT32_HASH_2(ctx.bitsize,
+ (ctx.buf[last_byte_index] >> (BYTE_BITS - ctx.bitsize)),
+ HCONST_15);
+ }
+ erts_free(ERTS_ALC_T_PHASH2_TRAP, ctx.buf);
+ context->trap_location_state.sub_binary_subtag_2.buf = NULL;
}
goto hash2_common;
+#undef BYTE_BITS
+#undef BINARY_BUF_SIZE
+#undef BINARY_BUF_SIZE_BITS
}
break;
case POS_BIG_SUBTAG:
case NEG_BIG_SUBTAG:
{
- Eterm* ptr = big_val(term);
- Uint i = 0;
- Uint n = BIG_SIZE(ptr);
- Uint32 con = BIG_SIGN(ptr) ? HCONST_10 : HCONST_11;
+ Eterm* big_val_ptr = big_val(term);
+ ErtsMakeHash2Context_NEG_BIG_SUBTAG ctx = {
+ .ptr = big_val_ptr,
+ .i = 0,
+ .n = BIG_SIZE(big_val_ptr),
+ .con = BIG_SIGN(big_val_ptr) ? HCONST_10 : HCONST_11};
#if D_EXP == 16
do {
Uint32 x, y;
- x = i < n ? BIG_DIGIT(ptr, i++) : 0;
- x += (Uint32)(i < n ? BIG_DIGIT(ptr, i++) : 0) << 16;
- y = i < n ? BIG_DIGIT(ptr, i++) : 0;
- y += (Uint32)(i < n ? BIG_DIGIT(ptr, i++) : 0) << 16;
- UINT32_HASH_2(x, y, con);
- } while (i < n);
+ x = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
+ x += (Uint32)(ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0) << 16;
+ y = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
+ y += (Uint32)(ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0) << 16;
+ UINT32_HASH_2(x, y, ctx.con);
+ TRAP_LOCATION(neg_big_subtag);
+ } while (ctx.i < ctx.n);
#elif D_EXP == 32
do {
Uint32 x, y;
- x = i < n ? BIG_DIGIT(ptr, i++) : 0;
- y = i < n ? BIG_DIGIT(ptr, i++) : 0;
- UINT32_HASH_2(x, y, con);
- } while (i < n);
+ x = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
+ y = ctx.i < ctx.n ? BIG_DIGIT(ctx.ptr, ctx.i++) : 0;
+ UINT32_HASH_2(x, y, ctx.con);
+ TRAP_LOCATION(neg_big_subtag);
+ } while (ctx.i < ctx.n);
#elif D_EXP == 64
do {
Uint t;
Uint32 x, y;
- ASSERT(i < n);
- t = BIG_DIGIT(ptr, i++);
+ ASSERT(ctx.i < ctx.n);
+ t = BIG_DIGIT(ctx.ptr, ctx.i++);
x = t & 0xffffffff;
y = t >> 32;
- UINT32_HASH_2(x, y, con);
- } while (i < n);
+ UINT32_HASH_2(x, y, ctx.con);
+ TRAP_LOCATION(neg_big_subtag);
+ } while (ctx.i < ctx.n);
#else
#error "unsupported D_EXP size"
#endif
@@ -1508,13 +1915,13 @@ make_hash2(Eterm term)
}
case _TAG_IMMED1_SMALL:
{
- Sint x = signed_val(term);
+ Sint small = signed_val(term);
+ if (SMALL_BITS > 28 && !IS_SSMALL28(small)) {
+ NOT_SSMALL28_HASH(small);
+ } else {
+ SINT32_HASH(small, HCONST);
+ }
- if (SMALL_BITS > 28 && !IS_SSMALL28(x)) {
- term = small_to_big(x, tmp_big);
- break;
- }
- SINT32_HASH(x, HCONST);
goto hash2_common;
}
}
@@ -1529,7 +1936,10 @@ make_hash2(Eterm term)
if (ESTACK_ISEMPTY(s)) {
DESTROY_ESTACK(s);
- UnUseTmpHeapNoproc(2);
+ if (can_trap) {
+ BUMP_REDS(p, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED);
+ ASSERT(!(p->flags & F_DISABLE_GC));
+ }
return hash;
}
@@ -1540,18 +1950,37 @@ make_hash2(Eterm term)
hash = (Uint32) ESTACK_POP(s);
UINT32_HASH(hash_xor_pairs, HCONST_19);
hash_xor_pairs = (Uint32) ESTACK_POP(s);
+ TRAP_LOCATION_NO_CTX(hash2_common_1);
goto hash2_common;
}
case HASH_MAP_PAIR:
hash_xor_pairs ^= hash;
hash = 0;
+ TRAP_LOCATION_NO_CTX(hash2_common_2);
goto hash2_common;
default:
break;
}
+
}
+ TRAP_LOCATION_NO_CTX(hash2_common_3);
}
}
+#undef TRAP_LOCATION_NO_RED
+#undef TRAP_LOCATION
+#undef TRAP_LOCATION_NO_CTX
+}
+
+Uint32
+make_hash2(Eterm term)
+{
+ return make_hash2_helper(term, 0, NULL, NULL);
+}
+
+Uint32
+trapping_make_hash2(Eterm term, Eterm* state_mref_write_back, Process* p)
+{
+ return make_hash2_helper(term, 1, state_mref_write_back, p);
}
/* Term hash function for internal use.
@@ -1731,7 +2160,7 @@ make_internal_hash(Eterm term, Uint32 salt)
ErlFunThing* funp = (ErlFunThing *) fun_val(term);
Uint num_free = funp->num_free;
UINT32_HASH_2(num_free, funp->fe->module, HCONST_20);
- UINT32_HASH_2(funp->fe->old_index, funp->fe->old_uniq, HCONST_21);
+ UINT32_HASH_2(funp->fe->index, funp->fe->old_uniq, HCONST_21);
if (num_free == 0) {
goto pop_next;
} else {
@@ -2381,7 +2810,7 @@ tailrecur_ne:
f1 = (ErlFunThing *) fun_val(a);
f2 = (ErlFunThing *) fun_val(b);
if (f1->fe->module != f2->fe->module ||
- f1->fe->old_index != f2->fe->old_index ||
+ f1->fe->index != f2->fe->index ||
f1->fe->old_uniq != f2->fe->old_uniq ||
f1->num_free != f2->num_free) {
goto not_equal;
@@ -2544,10 +2973,14 @@ tailrecur_ne:
bb = hashmap_val(b) + 1;
switch (hdr & _HEADER_MAP_SUBTAG_MASK) {
case HAMT_SUBTAG_HEAD_ARRAY:
+ if (aa[0] != bb[0])
+ goto not_equal;
aa++; bb++;
sz = 16;
break;
case HAMT_SUBTAG_HEAD_BITMAP:
+ if (aa[0] != bb[0])
+ goto not_equal;
aa++; bb++;
case HAMT_SUBTAG_NODE_BITMAP:
sz = hashmap_bitcount(MAP_HEADER_VAL(hdr));
@@ -2701,8 +3134,7 @@ Sint erts_cmp_compound(Eterm a, Eterm b, int exact, int eq_only)
if((AN)->sysname != (BN)->sysname) \
RETURN_NEQ(erts_cmp_atoms((AN)->sysname, (BN)->sysname)); \
ASSERT((AN)->creation != (BN)->creation); \
- if ((AN)->creation != 0 && (BN)->creation != 0) \
- RETURN_NEQ(((AN)->creation < (BN)->creation) ? -1 : 1); \
+ RETURN_NEQ(((AN)->creation < (BN)->creation) ? -1 : 1); \
} \
} while (0)
@@ -2976,7 +3408,7 @@ tailrecur_ne:
if (diff != 0) {
RETURN_NEQ(diff);
}
- diff = f1->fe->old_index - f2->fe->old_index;
+ diff = f1->fe->index - f2->fe->index;
if (diff != 0) {
RETURN_NEQ(diff);
}
@@ -4454,201 +4886,6 @@ erts_get_emu_args(Process *c_p)
return res;
}
-
-Eterm
-erts_get_ethread_info(Process *c_p)
-{
- Uint sz, *szp;
- Eterm res, *hp, **hpp, *end_hp = NULL;
-
- sz = 0;
- szp = &sz;
- hpp = NULL;
-
- while (1) {
- Eterm tup, list, name;
-#if defined(ETHR_NATIVE_ATOMIC32_IMPL) \
- || defined(ETHR_NATIVE_ATOMIC64_IMPL) \
- || defined(ETHR_NATIVE_DW_ATOMIC_IMPL)
- char buf[1024];
- int i;
- char **str;
-#endif
-
- res = NIL;
-
-#ifdef ETHR_X86_MEMBAR_H__
-
- tup = erts_bld_tuple(hpp, szp, 2,
- erts_bld_string(hpp, szp, "sse2"),
-#ifdef ETHR_X86_RUNTIME_CONF_HAVE_SSE2__
- erts_bld_string(hpp, szp,
- (ETHR_X86_RUNTIME_CONF_HAVE_SSE2__
- ? "yes" : "no"))
-#else
- erts_bld_string(hpp, szp, "yes")
-#endif
- );
- res = erts_bld_cons(hpp, szp, tup, res);
-
- tup = erts_bld_tuple(hpp, szp, 2,
- erts_bld_string(hpp, szp,
- "x86"
-#ifdef ARCH_64
- "_64"
-#endif
- " OOO"),
- erts_bld_string(hpp, szp,
-#ifdef ETHR_X86_OUT_OF_ORDER
- "yes"
-#else
- "no"
-#endif
- ));
-
- res = erts_bld_cons(hpp, szp, tup, res);
-#endif
-
-#ifdef ETHR_SPARC_V9_MEMBAR_H__
-
- tup = erts_bld_tuple(hpp, szp, 2,
- erts_bld_string(hpp, szp, "Sparc V9"),
- erts_bld_string(hpp, szp,
-#if defined(ETHR_SPARC_TSO)
- "TSO"
-#elif defined(ETHR_SPARC_PSO)
- "PSO"
-#elif defined(ETHR_SPARC_RMO)
- "RMO"
-#else
- "undefined"
-#endif
- ));
-
- res = erts_bld_cons(hpp, szp, tup, res);
-
-#endif
-
-#ifdef ETHR_PPC_MEMBAR_H__
-
- tup = erts_bld_tuple(hpp, szp, 2,
- erts_bld_string(hpp, szp, "lwsync"),
- erts_bld_string(hpp, szp,
-#if defined(ETHR_PPC_HAVE_LWSYNC)
- "yes"
-#elif defined(ETHR_PPC_HAVE_NO_LWSYNC)
- "no"
-#elif defined(ETHR_PPC_RUNTIME_CONF_HAVE_LWSYNC__)
- ETHR_PPC_RUNTIME_CONF_HAVE_LWSYNC__ ? "yes" : "no"
-#else
- "undefined"
-#endif
- ));
-
- res = erts_bld_cons(hpp, szp, tup, res);
-
-#endif
-
- tup = erts_bld_tuple(hpp, szp, 2,
- erts_bld_string(hpp, szp, "Native rw-spinlocks"),
-#ifdef ETHR_NATIVE_RWSPINLOCK_IMPL
- erts_bld_string(hpp, szp, ETHR_NATIVE_RWSPINLOCK_IMPL)
-#else
- erts_bld_string(hpp, szp, "no")
-#endif
- );
- res = erts_bld_cons(hpp, szp, tup, res);
-
- tup = erts_bld_tuple(hpp, szp, 2,
- erts_bld_string(hpp, szp, "Native spinlocks"),
-#ifdef ETHR_NATIVE_SPINLOCK_IMPL
- erts_bld_string(hpp, szp, ETHR_NATIVE_SPINLOCK_IMPL)
-#else
- erts_bld_string(hpp, szp, "no")
-#endif
- );
- res = erts_bld_cons(hpp, szp, tup, res);
-
-
- list = NIL;
-#ifdef ETHR_NATIVE_DW_ATOMIC_IMPL
- if (ethr_have_native_dw_atomic()) {
- name = erts_bld_string(hpp, szp, ETHR_NATIVE_DW_ATOMIC_IMPL);
- str = ethr_native_dw_atomic_ops();
- for (i = 0; str[i]; i++) {
- erts_snprintf(buf, sizeof(buf), "ethr_native_dw_atomic_%s()", str[i]);
- list = erts_bld_cons(hpp, szp,
- erts_bld_string(hpp, szp, buf),
- list);
- }
- str = ethr_native_su_dw_atomic_ops();
- for (i = 0; str[i]; i++) {
- erts_snprintf(buf, sizeof(buf), "ethr_native_su_dw_atomic_%s()", str[i]);
- list = erts_bld_cons(hpp, szp,
- erts_bld_string(hpp, szp, buf),
- list);
- }
- }
- else
-#endif
- name = erts_bld_string(hpp, szp, "no");
-
- tup = erts_bld_tuple(hpp, szp, 3,
- erts_bld_string(hpp, szp, "Double word native atomics"),
- name,
- list);
- res = erts_bld_cons(hpp, szp, tup, res);
-
- list = NIL;
-#ifdef ETHR_NATIVE_ATOMIC64_IMPL
- name = erts_bld_string(hpp, szp, ETHR_NATIVE_ATOMIC64_IMPL);
- str = ethr_native_atomic64_ops();
- for (i = 0; str[i]; i++) {
- erts_snprintf(buf, sizeof(buf), "ethr_native_atomic64_%s()", str[i]);
- list = erts_bld_cons(hpp, szp,
- erts_bld_string(hpp, szp, buf),
- list);
- }
-#else
- name = erts_bld_string(hpp, szp, "no");
-#endif
- tup = erts_bld_tuple(hpp, szp, 3,
- erts_bld_string(hpp, szp, "64-bit native atomics"),
- name,
- list);
- res = erts_bld_cons(hpp, szp, tup, res);
-
- list = NIL;
-#ifdef ETHR_NATIVE_ATOMIC32_IMPL
- name = erts_bld_string(hpp, szp, ETHR_NATIVE_ATOMIC32_IMPL);
- str = ethr_native_atomic32_ops();
- for (i = 0; str[i]; i++) {
- erts_snprintf(buf, sizeof(buf), "ethr_native_atomic32_%s()", str[i]);
- list = erts_bld_cons(hpp, szp,
- erts_bld_string(hpp, szp, buf),
- list);
- }
-#else
- name = erts_bld_string(hpp, szp, "no");
-#endif
- tup = erts_bld_tuple(hpp, szp, 3,
- erts_bld_string(hpp, szp, "32-bit native atomics"),
- name,
- list);
- res = erts_bld_cons(hpp, szp, tup, res);
-
- if (hpp) {
- HRelease(c_p, end_hp, *hpp)
- return res;
- }
-
- hp = HAlloc(c_p, sz);
- end_hp = hp + sz;
- hpp = &hp;
- szp = NULL;
- }
-}
-
/*
* To be used to silence unused result warnings, but do not abuse it.
*/
diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c
index 2ef452fa01..49db7077de 100644
--- a/erts/emulator/drivers/common/inet_drv.c
+++ b/erts/emulator/drivers/common/inet_drv.c
@@ -22,9 +22,10 @@
#include "config.h"
#endif
+
/* If we HAVE_SCTP_H and Solaris, we need to define the following in
- order to get SCTP working:
-*/
+ * order to get SCTP working:
+ */
#if (defined(HAVE_SCTP_H) && defined(__sun) && defined(__SVR4))
#define SOLARIS10 1
/* WARNING: This is not quite correct, it may also be Solaris 11! */
@@ -81,6 +82,32 @@
/* All platforms fail on malloc errors. */
#define FATAL_MALLOC
+/* The linux kernel sctp include files have an alignment bug
+ that causes warnings of this type to appear:
+
+ drivers/common/inet_drv.c:3196:47: error: taking address of packed member of 'struct sctp_paddr_change' may result in an unaligned pointer value [-Werror=address-of-packed-member]
+ 3196 | i = load_inet_get_address(spec, i, desc, &sptr->spc_aaddr);
+
+ So we need to suppress those, without disabling all warning
+ diagnostics of that type.
+
+ See https://lore.kernel.org/patchwork/patch/1108122/ for the
+ patch that fixes this bug. In a few years we should be able to
+ remove this suppression. */
+#ifdef HAVE_GCC_DIAG_IGNORE_WADDRESS_OF_PACKED_MEMBER
+#define PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER() \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Waddress-of-packed-member\"") \
+ do { } while(0)
+#define POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER() \
+ _Pragma("GCC diagnostic pop") \
+ do { } while(0)
+#else
+#define PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER() \
+ do { } while(0)
+#define POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER() \
+ do { } while(0)
+#endif
#include "erl_driver.h"
@@ -578,8 +605,8 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n)
(d)->event_mask = (onoff) ? \
((d)->event_mask | (flags)) : \
((d)->event_mask & ~(flags)); \
- DEBUGF(("(%s / %d) sock_select(%ld): flags=%02X, onoff=%d, event_mask=%02lX\r\n", \
- __FILE__, __LINE__, (long) (d)->port, (flags), (onoff), (unsigned long) (d)->event_mask)); \
+ DEBUGF(("(%s / %d) sock_select(%p): flags=%02X, onoff=%d, event_mask=%02lX\r\n", \
+ __FILE__, __LINE__, (d)->port, (flags), (onoff), (unsigned long) (d)->event_mask)); \
inet_driver_select((d)->port, (ErlDrvEvent)(long)(d)->event, (flags), (onoff)); \
} while(0)
@@ -591,21 +618,15 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n)
#ifdef HAVE_SOCKLEN_T
# define SOCKLEN_T socklen_t
+#elif defined(__WIN32__)
+# define SOCKLEN_T int
#else
+# warning "Non-Windows OS without type 'socklen_t'"
# define SOCKLEN_T size_t
#endif
#include "packet_parser.h"
-#define get_int24(s) ((((unsigned char*) (s))[0] << 16) | \
- (((unsigned char*) (s))[1] << 8) | \
- (((unsigned char*) (s))[2]))
-
-#define get_little_int32(s) ((((unsigned char*) (s))[3] << 24) | \
- (((unsigned char*) (s))[2] << 16) | \
- (((unsigned char*) (s))[1] << 8) | \
- (((unsigned char*) (s))[0]))
-
#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE)
/* strnlen doesn't exist everywhere */
@@ -1113,8 +1134,8 @@ typedef struct {
inet_address* peer_ptr; /* fake peername or NULL */
inet_address* name_ptr; /* fake sockname or NULL */
- SOCKLEN_T peer_addr_len; /* fake peername size */
- SOCKLEN_T name_addr_len; /* fake sockname size */
+ unsigned int peer_addr_len; /* fake peername size */
+ unsigned int name_addr_len; /* fake sockname size */
int bufsz; /* minimum buffer constraint */
unsigned int hsz; /* the list header size, -1 is large !!! */
@@ -1151,9 +1172,24 @@ typedef struct {
#define TCP_MAX_PACKET_SIZE 0x4000000 /* 64 M */
-#define MAX_VSIZE 16 /* Max number of entries allowed in an I/O
- * vector sock_sendv().
- */
+/* Max number of entries allowed in an I/O vector sock_sendv(). */
+#if defined(__WIN32__)
+/*
+ * Windows 95, 98, and ME is limited to 16, but we do not
+ * support those. Documentation unfortunately does not say
+ * anything about newer windows, so we guess 1024 which
+ * seems to be what most systems use...
+ */
+#define MAX_VSIZE 1024
+#elif !defined(NO_SYSCONF)
+static int iov_max;
+#define MAX_VSIZE iov_max
+#elif defined(IOV_MAX)
+#define MAX_VSIZE IOV_MAX
+#else
+/* POSIX require at least 16 */
+#define MAX_VSIZE 16
+#endif
static int tcp_inet_init(void);
static void tcp_inet_stop(ErlDrvData);
@@ -2104,7 +2140,7 @@ static int enq_async_w_tmo(inet_descriptor* desc, char* buf, int req, unsigned t
if ((opp = desc->oph) == NULL) /* queue empty */
opp = desc->oph = desc->opt = desc->op_queue;
else if (desc->oph == desc->opt) { /* queue full */
- DEBUGF(("enq(%ld): queue full\r\n", (long)desc->port));
+ DEBUGF(("enq(%p): queue full\r\n", desc->port));
return -1;
}
@@ -2116,8 +2152,8 @@ static int enq_async_w_tmo(inet_descriptor* desc, char* buf, int req, unsigned t
memcpy(&(opp->monitor),monitorp,sizeof(ErlDrvMonitor));
}
- DEBUGF(("enq(%ld): %d %ld %d\r\n",
- (long) desc->port, opp->id, opp->caller, opp->req));
+ DEBUGF(("enq(%p): %d %ld %d\r\n",
+ desc->port, opp->id, opp->caller, opp->req));
opp++;
if (opp >= desc->op_queue + INET_MAX_ASYNC)
@@ -2141,7 +2177,7 @@ static int deq_async_w_tmo(inet_descriptor* desc, int* ap, ErlDrvTermData* cp,
inet_async_op* opp;
if ((opp = desc->opt) == NULL) { /* queue empty */
- DEBUGF(("deq(%ld): queue empty\r\n", (long)desc->port));
+ DEBUGF(("deq(%p): queue empty\r\n", desc->port));
return -1;
}
*ap = opp->id;
@@ -2154,8 +2190,8 @@ static int deq_async_w_tmo(inet_descriptor* desc, int* ap, ErlDrvTermData* cp,
memcpy(monitorp,&(opp->monitor),sizeof(ErlDrvMonitor));
}
- DEBUGF(("deq(%ld): %d %ld %d\r\n",
- (long)desc->port, opp->id, opp->caller, opp->req));
+ DEBUGF(("deq(%p): %d %ld %d\r\n",
+ desc->port, opp->id, opp->caller, opp->req));
opp++;
if (opp >= desc->op_queue + INET_MAX_ASYNC)
@@ -2377,7 +2413,8 @@ static int inet_port_data(inet_descriptor* desc, const char* buf, int len)
{
unsigned int hsz = desc->hsz;
- DEBUGF(("inet_port_data(%ld): len = %d\r\n", (long)desc->port, len));
+ DEBUGF(("inet_port_data(%p): len = %d\r\n",
+ desc->port, len));
if ((desc->mode == INET_MODE_LIST) || (hsz > len))
return driver_output2(desc->port, (char*)buf, len, NULL, 0);
@@ -2395,8 +2432,8 @@ inet_port_binary_data(inet_descriptor* desc, ErlDrvBinary* bin, int offs, int le
{
unsigned int hsz = desc->hsz;
- DEBUGF(("inet_port_binary_data(%ld): offs=%d, len = %d\r\n",
- (long)desc->port, offs, len));
+ DEBUGF(("inet_port_binary_data(%p): offs=%d, len = %d\r\n",
+ desc->port, offs, len));
if ((desc->mode == INET_MODE_LIST) || (hsz > len))
return driver_output2(desc->port, bin->orig_bytes+offs, len, NULL, 0);
@@ -2574,16 +2611,18 @@ http_request_inetdrv(void* arg, const http_atom_t* meth, const char* meth_ptr,
}
static int
-http_header_inetdrv(void* arg, const http_atom_t* name, const char* name_ptr,
- int name_len, const char* value_ptr, int value_len)
+http_header_inetdrv(void* arg, const http_atom_t* name,
+ const char* name_ptr, int name_len,
+ const char* oname_ptr, int oname_len,
+ const char* value_ptr, int value_len)
{
tcp_descriptor* desc = (tcp_descriptor*) arg;
int i = 0;
- ErlDrvTermData spec[26];
+ ErlDrvTermData spec[27];
ErlDrvTermData caller = ERL_DRV_NIL;
if (desc->inet.active == INET_PASSIVE) {
- /* {inet_async,S,Ref,{ok,{http_header,Bit,Name,IValue,Value}} */
+ /* {inet_async,S,Ref,{ok,{http_header,Bit,Name,Oname,Value}} */
int req;
int aid;
@@ -2596,7 +2635,7 @@ http_header_inetdrv(void* arg, const http_atom_t* name, const char* name_ptr,
i = LOAD_ATOM(spec, i, am_ok);
}
else {
- /* {http, S, {http_header,Bit,Name,IValue,Value}} */
+ /* {http, S, {http_header,Bit,Name,Oname,Value}} */
i = LOAD_ATOM(spec, i, am_http);
i = LOAD_PORT(spec, i, desc->inet.dport);
}
@@ -2610,19 +2649,19 @@ http_header_inetdrv(void* arg, const http_atom_t* name, const char* name_ptr,
i = LOAD_INT(spec, i, 0);
i = http_load_string(desc, spec, i, name_ptr, name_len);
}
- i = LOAD_ATOM(spec, i, am_undefined);
+ i = http_load_string(desc, spec, i, oname_ptr, oname_len);
i = http_load_string(desc, spec, i, value_ptr, value_len);
i = LOAD_TUPLE(spec, i, 5);
if (desc->inet.active == INET_PASSIVE) {
i = LOAD_TUPLE(spec, i, 2);
i = LOAD_TUPLE(spec, i, 4);
- ASSERT(i <= 26);
+ ASSERT(i <= 27);
return erl_drv_send_term(desc->inet.dport, caller, spec, i);
}
else {
i = LOAD_TUPLE(spec, i, 3);
- ASSERT(i <= 26);
+ ASSERT(i <= 27);
return erl_drv_output_term(desc->inet.dport, spec, i);
}
}
@@ -2788,7 +2827,8 @@ static int inet_async_data(inet_descriptor* desc, const char* buf, int len)
int aid;
int i = 0;
- DEBUGF(("inet_async_data(%ld): len = %d\r\n", (long)desc->port, len));
+ DEBUGF(("inet_async_data(%p): len = %d\r\n",
+ desc->port, len));
if (deq_async(desc, &aid, &caller, &req) < 0)
return -1;
@@ -3170,7 +3210,9 @@ static int sctp_parse_async_event
ASSERT(sptr->spc_length <= sz); /* No buffer overrun */
i = LOAD_ATOM (spec, i, am_sctp_paddr_change);
+ PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER();
i = load_inet_get_address(spec, i, desc, &sptr->spc_aaddr);
+ POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER();
switch (sptr->spc_state)
{
@@ -3481,8 +3523,8 @@ inet_async_binary_data
int ok_pos;
#endif
- DEBUGF(("inet_async_binary_data(%ld): offs=%d, len=%d\r\n",
- (long)desc->port, offs, len));
+ DEBUGF(("inet_async_binary_data(%p): offs=%d, len=%d\r\n",
+ desc->port, offs, len));
if (deq_async(desc, &aid, &caller, &req) < 0)
return -1;
@@ -3588,7 +3630,8 @@ static int tcp_message(inet_descriptor* desc, const char* buf, int len)
ErlDrvTermData spec[20];
int i = 0;
- DEBUGF(("tcp_message(%ld): len = %d\r\n", (long)desc->port, len));
+ DEBUGF(("tcp_message(%p): len = %d\r\n",
+ desc->port, len));
/* XXX fprintf(stderr,"tcp_message send.\r\n"); */
i = LOAD_ATOM(spec, i, am_tcp);
@@ -3626,7 +3669,8 @@ tcp_binary_message(inet_descriptor* desc, ErlDrvBinary* bin, int offs, int len)
ErlDrvTermData spec[20];
int i = 0;
- DEBUGF(("tcp_binary_message(%ld): len = %d\r\n", (long)desc->port, len));
+ DEBUGF(("tcp_binary_message(%p): len = %d\r\n",
+ desc->port, len));
i = LOAD_ATOM(spec, i, am_tcp);
i = LOAD_PORT(spec, i, desc->dport);
@@ -3656,7 +3700,7 @@ static int tcp_closed_message(tcp_descriptor* desc)
ErlDrvTermData spec[6];
int i = 0;
- DEBUGF(("tcp_closed_message(%ld):\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_closed_message(%p):\r\n", desc->inet.port));
if (!(desc->tcp_add_flags & TCP_ADDF_CLOSE_SENT)) {
desc->tcp_add_flags |= TCP_ADDF_CLOSE_SENT;
@@ -3678,7 +3722,8 @@ static int tcp_error_message(tcp_descriptor* desc, int err)
ErlDrvTermData am_err = error_atom(err);
int i = 0;
- DEBUGF(("tcp_error_message(%ld): %d\r\n", (long)desc->inet.port, err));
+ DEBUGF(("tcp_error_message(%p): %d\r\n",
+ desc->inet.port, err));
i = LOAD_ATOM(spec, i, am_tcp_error);
i = LOAD_PORT(spec, i, desc->inet.dport);
@@ -3708,8 +3753,8 @@ static int packet_binary_message(inet_descriptor* desc,
int alen;
char* data = bin->orig_bytes+offs;
- DEBUGF(("packet_binary_message(%ld): len = %d\r\n",
- (long)desc->port, len));
+ DEBUGF(("packet_binary_message(%p): len = %d\r\n",
+ desc->port, len));
# ifdef HAVE_SCTP
i = LOAD_ATOM(spec, i, IS_SCTP(desc) ? am_sctp : am_udp); /* UDP|SCTP */
# else
@@ -3808,7 +3853,7 @@ static int packet_binary_message(inet_descriptor* desc,
ErlDrvTermData spec[6];
int i = 0;
- DEBUGF(("packet_passive_message(%ld):\r\n", (long)desc->port));
+ DEBUGF(("packet_passive_message(%p):\r\n", desc->port));
#if !defined(HAVE_UDP) && !defined(HAVE_SCTP)
i = LOAD_ATOM(spec, i, am_tcp_passive);
@@ -3840,8 +3885,8 @@ static int packet_error_message(udp_descriptor* udesc, int err)
ErlDrvTermData am_err = error_atom(err);
int i = 0;
- DEBUGF(("packet_error_message(%ld): %d\r\n",
- (long)desc->port, err));
+ DEBUGF(("packet_error_message(%p): %d\r\n",
+ desc->port, err));
# ifdef HAVE_SCTP
if (IS_SCTP(desc) )
@@ -4116,6 +4161,18 @@ static int inet_init()
if (!sock_init())
goto error;
+#if !defined(__WIN32__) && !defined(NO_SYSCONF)
+ iov_max = (int) sysconf(_SC_IOV_MAX);
+ if (iov_max < 0) {
+#ifdef IOV_MAX
+ iov_max = IOV_MAX;
+#else
+ iov_max = 16; /* min value required by POSIX */
+#endif
+ }
+ ASSERT(iov_max >= 16);
+#endif
+
if (0 != erl_drv_tsd_key_create("inet_buffer_stack_key", &buffer_stack_key))
goto error;
@@ -4626,10 +4683,10 @@ static void desc_close(inet_descriptor* desc)
* be selecting on it.
*/
if (!INET_IGNORED(desc))
- driver_select(desc->port,(ErlDrvEvent)(long)desc->event,
+ driver_select(desc->port,(ErlDrvEvent)(SWord)desc->event,
ERL_DRV_USE, 0);
else
- inet_stop_select((ErlDrvEvent)(long)desc->event,NULL);
+ inet_stop_select((ErlDrvEvent)(SWord)desc->event,NULL);
desc->event = INVALID_EVENT; /* closed by stop_select callback */
desc->s = INVALID_SOCKET;
desc->event_mask = 0;
@@ -4778,7 +4835,7 @@ static ErlDrvSSizeT inet_ctl_fdopen(inet_descriptor* desc, int domain, int type,
char** rbuf, ErlDrvSizeT rsize)
{
inet_address name;
- unsigned int sz;
+ SOCKLEN_T sz;
if (bound) {
/* check that it is a socket and that the socket is bound */
@@ -5852,9 +5909,10 @@ static ErlDrvSSizeT inet_ctl_getifaddrs(inet_descriptor* desc_p,
*buf_p++ = INET_REP_OK;
/* Iterate over MIB_IPADDRTABLE or IP_ADAPTER_ADDRESSES */
- for (ia_p = NULL, ip_addrs_p ? ((void *)(i = 0)) : (ia_p = ip_adaddrs_p);
+ ia_p = NULL;
+ for (ip_addrs_p ? (void)(i = 0) : (void)(ia_p = ip_adaddrs_p);
ip_addrs_p ? (i < ip_addrs_p->dwNumEntries) : (ia_p != NULL);
- ip_addrs_p ? ((void *)(i++)) : (ia_p = ia_p->Next)) {
+ ip_addrs_p ? (void)(i++) : (void)(ia_p = ia_p->Next)) {
MIB_IPADDRROW *ipaddrrow_p = NULL;
DWORD flags = INET_IFF_MULTICAST;
DWORD index = 0;
@@ -6363,35 +6421,35 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
switch(opt) {
case INET_LOPT_HEADER:
- DEBUGF(("inet_set_opts(%ld): s=%d, HEADER=%d\r\n",
- (long)desc->port, desc->s,ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, HEADER=%d\r\n",
+ desc->port, desc->s,ival));
desc->hsz = ival;
continue;
case INET_LOPT_MODE:
/* List or Binary: */
- DEBUGF(("inet_set_opts(%ld): s=%d, MODE=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, MODE=%d\r\n",
+ desc->port, desc->s, ival));
desc->mode = ival;
continue;
case INET_LOPT_DELIVER:
- DEBUGF(("inet_set_opts(%ld): s=%d, DELIVER=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, DELIVER=%d\r\n",
+ desc->port, desc->s, ival));
desc->deliver = ival;
continue;
case INET_LOPT_BUFFER:
- DEBUGF(("inet_set_opts(%ld): s=%d, BUFFER=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, BUFFER=%d\r\n",
+ desc->port, desc->s, ival));
if (ival < INET_MIN_BUFFER) ival = INET_MIN_BUFFER;
desc->bufsz = ival;
desc->flags |= INET_FLG_BUFFER_SET;
continue;
case INET_LOPT_ACTIVE:
- DEBUGF(("inet_set_opts(%ld): s=%d, ACTIVE=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, ACTIVE=%d\r\n",
+ desc->port, desc->s, ival));
desc->active = ival;
if (desc->active == INET_MULTI) {
long ac = desc->active_count;
@@ -6428,20 +6486,20 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
continue;
case INET_LOPT_PACKET:
- DEBUGF(("inet_set_opts(%ld): s=%d, PACKET=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, PACKET=%d\r\n",
+ desc->port, desc->s, ival));
desc->htype = ival;
continue;
case INET_LOPT_PACKET_SIZE:
- DEBUGF(("inet_set_opts(%ld): s=%d, PACKET_SIZE=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, PACKET_SIZE=%d\r\n",
+ desc->port, desc->s, ival));
desc->psize = (unsigned int)ival;
continue;
case INET_LOPT_EXITONCLOSE:
- DEBUGF(("inet_set_opts(%ld): s=%d, EXITONCLOSE=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, EXITONCLOSE=%d\r\n",
+ desc->port, desc->s, ival));
desc->exitf = ival;
continue;
@@ -6545,8 +6603,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
continue;
case INET_LOPT_LINE_DELIM:
- DEBUGF(("inet_set_opts(%ld): s=%d, LINE_DELIM=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, LINE_DELIM=%d\r\n",
+ desc->port, desc->s, ival));
desc->delimiter = (char)ival;
continue;
@@ -6555,33 +6613,33 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
continue; /* Bjorn says */
#else
type = SO_REUSEADDR;
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_REUSEADDR=%d\r\n",
- (long)desc->port, desc->s,ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_REUSEADDR=%d\r\n",
+ desc->port, desc->s,ival));
break;
#endif
case INET_OPT_KEEPALIVE: type = SO_KEEPALIVE;
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_KEEPALIVE=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_KEEPALIVE=%d\r\n",
+ desc->port, desc->s, ival));
break;
case INET_OPT_DONTROUTE: type = SO_DONTROUTE;
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_DONTROUTE=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_DONTROUTE=%d\r\n",
+ desc->port, desc->s, ival));
break;
case INET_OPT_BROADCAST: type = SO_BROADCAST;
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_BROADCAST=%d\r\n",
- (long)desc->port, desc->s,ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_BROADCAST=%d\r\n",
+ desc->port, desc->s, ival));
break;
case INET_OPT_OOBINLINE: type = SO_OOBINLINE;
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_OOBINLINE=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_OOBINLINE=%d\r\n",
+ desc->port, desc->s, ival));
break;
case INET_OPT_SNDBUF: type = SO_SNDBUF;
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_SNDBUF=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_SNDBUF=%d\r\n",
+ desc->port, desc->s, ival));
break;
case INET_OPT_RCVBUF: type = SO_RCVBUF;
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_RCVBUF=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_RCVBUF=%d\r\n",
+ desc->port, desc->s, ival));
if (!(desc->flags & INET_FLG_BUFFER_SET)) {
/* make sure we have desc->bufsz >= SO_RCVBUF */
if (ival > (1 << 16) && desc->stype == SOCK_DGRAM && !IS_SCTP(desc))
@@ -6602,8 +6660,9 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
len -= 4;
arg_ptr = (char*) &li_val;
arg_sz = sizeof(li_val);
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_LINGER=%d,%d",
- (long)desc->port, desc->s, li_val.l_onoff,li_val.l_linger));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_LINGER=%d,%d",
+ desc->port, desc->s,
+ li_val.l_onoff,li_val.l_linger));
if (desc->sprotocol == IPPROTO_TCP) {
tcp_descriptor* tdesc = (tcp_descriptor*) desc;
if (li_val.l_onoff && li_val.l_linger == 0)
@@ -6617,8 +6676,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
#ifdef SO_PRIORITY
type = SO_PRIORITY;
propagate = 1; /* We do want to know if this fails */
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_PRIORITY=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_PRIORITY=%d\r\n",
+ desc->port, desc->s, ival));
break;
#else
/* inet_fill_opts always returns a value for this option,
@@ -6630,8 +6689,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
proto = IPPROTO_IP;
type = IP_TOS;
propagate = 1;
- DEBUGF(("inet_set_opts(%ld): s=%d, IP_TOS=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IP_TOS=%d\r\n",
+ desc->port, desc->s, ival));
break;
#else
/* inet_fill_opts always returns a value for this option,
@@ -6643,8 +6702,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
proto = IPPROTO_IPV6;
type = IPV6_TCLASS;
propagate = 1;
- DEBUGF(("inet_set_opts(%ld): s=%d, IPV6_TCLASS=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IPV6_TCLASS=%d\r\n",
+ desc->port, desc->s, ival));
break;
#endif
#if defined(IP_TTL) && defined(IPPROTO_IP)
@@ -6652,8 +6711,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
proto = IPPROTO_IP;
type = IP_TTL;
propagate = 1;
- DEBUGF(("inet_set_opts(%ld): s=%d, IP_TTL=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IP_TTL=%d\r\n",
+ desc->port, desc->s, ival));
break;
#endif
#if defined(IP_RECVTOS) && defined(IPPROTO_IP)
@@ -6665,8 +6724,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
ival ?
(desc->recv_cmsgflags | INET_CMSG_RECVTOS) :
(desc->recv_cmsgflags & ~INET_CMSG_RECVTOS);
- DEBUGF(("inet_set_opts(%ld): s=%d, IP_RECVTOS=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IP_RECVTOS=%d\r\n",
+ desc->port, desc->s, ival));
break;
#endif
#if defined(IPV6_RECVTCLASS) && defined(IPPROTO_IPV6)
@@ -6678,8 +6737,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
ival ?
(desc->recv_cmsgflags | INET_CMSG_RECVTCLASS) :
(desc->recv_cmsgflags & ~INET_CMSG_RECVTCLASS);
- DEBUGF(("inet_set_opts(%ld): s=%d, IPV6_RECVTCLASS=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IPV6_RECVTCLASS=%d\r\n",
+ desc->port, desc->s, ival));
break;
#endif
#if defined(IP_RECVTTL) && defined(IPPROTO_IP)
@@ -6691,24 +6750,24 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
ival ?
(desc->recv_cmsgflags | INET_CMSG_RECVTTL) :
(desc->recv_cmsgflags & ~INET_CMSG_RECVTTL);
- DEBUGF(("inet_set_opts(%ld): s=%d, IP_RECVTTL=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IP_RECVTTL=%d\r\n",
+ desc->port, desc->s, ival));
break;
#endif
case TCP_OPT_NODELAY:
proto = IPPROTO_TCP;
type = TCP_NODELAY;
- DEBUGF(("inet_set_opts(%ld): s=%d, TCP_NODELAY=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, TCP_NODELAY=%d\r\n",
+ desc->port, desc->s, ival));
break;
case TCP_OPT_NOPUSH:
#if defined(INET_TCP_NOPUSH)
proto = IPPROTO_TCP;
type = INET_TCP_NOPUSH;
- DEBUGF(("inet_set_opts(%ld): s=%d, t=%d TCP_NOPUSH=%d\r\n",
- (long)desc->port, desc->s, type, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, t=%d TCP_NOPUSH=%d\r\n",
+ desc->port, desc->s, type, ival));
break;
#else
/* inet_fill_opts always returns a value for this option,
@@ -6721,37 +6780,38 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
case UDP_OPT_MULTICAST_TTL:
proto = IPPROTO_IP;
type = IP_MULTICAST_TTL;
- DEBUGF(("inet_set_opts(%ld): s=%d, IP_MULTICAST_TTL=%d\r\n",
- (long)desc->port,desc->s,ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IP_MULTICAST_TTL=%d\r\n",
+ desc->port, desc->s,ival));
break;
case UDP_OPT_MULTICAST_LOOP:
proto = IPPROTO_IP;
type = IP_MULTICAST_LOOP;
- DEBUGF(("inet_set_opts(%ld): s=%d, IP_MULTICAST_LOOP=%d\r\n",
- (long)desc->port,desc->s,ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IP_MULTICAST_LOOP=%d\r\n",
+ desc->port, desc->s,ival));
break;
case UDP_OPT_MULTICAST_IF:
proto = IPPROTO_IP;
type = IP_MULTICAST_IF;
- DEBUGF(("inet_set_opts(%ld): s=%d, IP_MULTICAST_IF=%x\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IP_MULTICAST_IF=%x\r\n",
+ desc->port, desc->s, ival));
ival = sock_htonl(ival);
break;
case UDP_OPT_ADD_MEMBERSHIP:
proto = IPPROTO_IP;
type = IP_ADD_MEMBERSHIP;
- DEBUGF(("inet_set_opts(%ld): s=%d, IP_ADD_MEMBERSHIP=%d\r\n",
- (long)desc->port, desc->s,ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IP_ADD_MEMBERSHIP=%d\r\n",
+ desc->port, desc->s,ival));
goto L_set_mreq;
case UDP_OPT_DROP_MEMBERSHIP:
proto = IPPROTO_IP;
type = IP_DROP_MEMBERSHIP;
- DEBUGF(("inet_set_opts(%ld): s=%d, IP_DROP_MEMBERSHIP=%x\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, "
+ "IP_DROP_MEMBERSHIP=%x\r\n",
+ desc->port, desc->s, ival));
L_set_mreq:
mreq_val.imr_multiaddr.s_addr = sock_htonl(ival);
ival = get_int32(ptr);
@@ -6769,8 +6829,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
proto = IPPROTO_IPV6;
type = IPV6_V6ONLY;
propagate = 1;
- DEBUGF(("inet_set_opts(%ld): s=%d, IPV6_V6ONLY=%d\r\n",
- (long)desc->port, desc->s, ival));
+ DEBUGF(("inet_set_opts(%p): s=%d, IPV6_V6ONLY=%d\r\n",
+ desc->port, desc->s, ival));
break;
#elif defined(__WIN32__) && defined(HAVE_IN6) && defined(AF_INET6)
/* Fake a'la OpenBSD; set to 'true' is fine but 'false' invalid. */
@@ -6817,8 +6877,8 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
arg_sz = sizeof(ifname);
propagate = 1; /* We do want to know if this fails */
- DEBUGF(("inet_set_opts(%ld): s=%d, SO_BINDTODEVICE=%s\r\n",
- (long)desc->port, desc->s, ifname));
+ DEBUGF(("inet_set_opts(%p): s=%d, SO_BINDTODEVICE=%s\r\n",
+ desc->port, desc->s, ifname));
break;
#endif
@@ -6835,32 +6895,40 @@ static int inet_set_opts(inet_descriptor* desc, char* ptr, int len)
if (propagate && res != 0) {
return -1;
}
- DEBUGF(("inet_set_opts(%ld): s=%d returned %d\r\n",
- (long)desc->port, desc->s, res));
+ DEBUGF(("inet_set_opts(%p): s=%d returned %d\r\n",
+ desc->port, desc->s, res));
}
if ( ((desc->stype == SOCK_STREAM) && IS_CONNECTED(desc)) ||
((desc->stype == SOCK_DGRAM) && IS_OPEN(desc))) {
+ int trigger_recv;
+
+ /* XXX: UDP sockets could also trigger immediate read here NIY */
+ trigger_recv =
+ (desc->stype==SOCK_STREAM) &&
+ !old_active &&
+ (desc->active == INET_ONCE || desc->active == INET_MULTI) &&
+ (desc->htype == old_htype);
+
+ if (trigger_recv) {
+ return 2;
+ }
if (desc->active != old_active) {
/* Need to cancel the read_packet timer if we go from active to passive. */
if (desc->active == INET_PASSIVE && desc->stype == SOCK_DGRAM)
driver_cancel_timer(desc->port);
- sock_select(desc, (FD_READ|FD_CLOSE), (desc->active>0));
+
+ sock_select(desc, (FD_READ|FD_CLOSE), (desc->active>0));
}
/* XXX: UDP sockets could also trigger immediate read here NIY */
if ((desc->stype==SOCK_STREAM) && desc->active) {
if (!old_active || (desc->htype != old_htype)) {
/* passive => active change OR header type change in active mode */
- /* Return > 1 if only active changed to INET_ONCE -> direct read if
- header type is unchanged. */
- /* XXX fprintf(stderr,"desc->htype == %d, old_htype == %d,
- desc->active == %d, old_active == %d\r\n",(int)desc->htype,
- (int) old_htype, (int) desc->active, (int) old_active );*/
- return 1+(desc->htype == old_htype &&
- (desc->active == INET_ONCE || desc->active == INET_MULTI));
+ return 1;
}
+
return 0;
}
}
@@ -6952,9 +7020,9 @@ static int sctp_set_opts(inet_descriptor* desc, char* ptr, int len)
struct sctp_paddrparams pap;
struct sctp_sndrcvinfo sri;
struct sctp_event_subscribe es;
-# ifdef SCTP_DELAYED_ACK_TIME
+#if defined(HAVE_DECL_SCTP_DELAYED_ACK_TIME) && defined(SCTP_ASSOC_VALUE_ASSOC_ID)
struct sctp_assoc_value av; /* Not in SOLARIS10 */
-# endif
+#endif
# ifdef SO_BINDTODEVICE
char ifname[IFNAMSIZ];
# endif
@@ -7473,8 +7541,9 @@ static int sctp_set_opts(inet_descriptor* desc, char* ptr, int len)
arg_sz = sizeof ( arg.es);
break;
}
- /* The following is not available on Solaris 10: */
-# ifdef SCTP_DELAYED_ACK_TIME
+ /* The following is not available on
+ * Solaris 10 or NetBSD or ... */
+#if defined(HAVE_DECL_SCTP_DELAYED_ACK_TIME) && defined(SCTP_ASSOC_VALUE_ASSOC_ID)
case SCTP_OPT_DELAYED_ACK_TIME:
{
CHKLEN(curr, ASSOC_ID_LEN + 4);
@@ -7487,7 +7556,7 @@ static int sctp_set_opts(inet_descriptor* desc, char* ptr, int len)
arg_sz = sizeof ( arg.av);
break;
}
-# endif
+#endif
default:
/* XXX: No more supported SCTP options. In particular, authentica-
tion options (SCTP_AUTH_CHUNK, SCTP_AUTH_KEY, SCTP_PEER_AUTH_
@@ -8100,7 +8169,9 @@ static int load_paddrinfo (ErlDrvTermData * spec, int i,
{
i = LOAD_ATOM (spec, i, am_sctp_paddrinfo);
i = LOAD_ASSOC_ID (spec, i, pai->spinfo_assoc_id);
+ PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER();
i = load_inet_get_address(spec, i, desc, &pai->spinfo_address);
+ POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER();
switch(pai->spinfo_state)
{
case SCTP_ACTIVE:
@@ -8620,7 +8691,9 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
ASSERT(0);
}
i = LOAD_ASSOC_ID (spec, i, sp.sspp_assoc_id);
+ PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER();
i = load_inet_get_address(spec, i, desc, &sp.sspp_addr);
+ POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER();
i = LOAD_TUPLE (spec, i, 3);
i = LOAD_TUPLE (spec, i, 2);
break;
@@ -8680,7 +8753,9 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
i = LOAD_ATOM (spec, i, am_sctp_peer_addr_params);
i = LOAD_ATOM (spec, i, am_sctp_paddrparams);
i = LOAD_ASSOC_ID (spec, i, ap.spp_assoc_id);
+ PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER();
i = load_inet_get_address(spec, i, desc, &ap.spp_address);
+ POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER();
i = LOAD_INT (spec, i, ap.spp_hbinterval);
i = LOAD_INT (spec, i, ap.spp_pathmaxrxt);
@@ -8788,8 +8863,9 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
i = LOAD_TUPLE (spec, i, 2);
break;
}
- /* The following option is not available in Solaris 10: */
-# if HAVE_DECL_SCTP_DELAYED_ACK_TIME
+ /* The following option is not available on:
+ * Solaris 10 or NetBSD or ... */
+#if defined(HAVE_DECL_SCTP_DELAYED_ACK_TIME) && defined(SCTP_ASSOC_VALUE_ASSOC_ID)
case SCTP_OPT_DELAYED_ACK_TIME:
{
struct sctp_assoc_value av;
@@ -9093,8 +9169,8 @@ static void inet_emergency_close(ErlDrvData data)
/* valid for any (UDP, TCP or SCTP) descriptor */
tcp_descriptor* tcp_desc = (tcp_descriptor*)data;
inet_descriptor* desc = INETP(tcp_desc);
- DEBUGF(("inet_emergency_close(%ld) {s=%d\r\n",
- (long)desc->port, desc->s));
+ DEBUGF(("inet_emergency_close(%p) {s=%d\r\n",
+ desc->port, desc->s));
if (desc->s != INVALID_SOCKET) {
sock_close(desc->s);
}
@@ -9215,7 +9291,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
default: dstlen += 5; break;
}
}
- DEBUGF(("inet_ctl(%ld): GETSTAT\r\n", (long) desc->port));
+ DEBUGF(("inet_ctl(%p): GETSTAT\r\n", (long) desc->port));
if (dstlen > INET_MAX_OPT_BUFFER) /* sanity check */
return 0;
if (dstlen > rsize) {
@@ -9231,7 +9307,8 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
case INET_REQ_SUBSCRIBE: {
char* dst;
int dstlen = 1 /* Reply code */ + len*5;
- DEBUGF(("inet_ctl(%ld): INET_REQ_SUBSCRIBE\r\n", (long) desc->port));
+ DEBUGF(("inet_ctl(%p): INET_REQ_SUBSCRIBE\r\n",
+ desc->port));
if (dstlen > INET_MAX_OPT_BUFFER) /* sanity check */
return 0;
if (dstlen > rsize) {
@@ -9246,7 +9323,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
case INET_REQ_GETOPTS: { /* get options */
ErlDrvSSizeT replen;
- DEBUGF(("inet_ctl(%ld): GETOPTS\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): GETOPTS\r\n", desc->port));
#ifdef HAVE_SCTP
if (IS_SCTP(desc))
{
@@ -9261,36 +9338,36 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
}
case INET_REQ_GETIFLIST: {
- DEBUGF(("inet_ctl(%ld): GETIFLIST\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): GETIFLIST\r\n", desc->port));
if (!IS_OPEN(desc))
return ctl_xerror(EXBADPORT, rbuf, rsize);
return inet_ctl_getiflist(desc, rbuf, rsize);
}
case INET_REQ_GETIFADDRS: {
- DEBUGF(("inet_ctl(%ld): GETIFADDRS\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): GETIFADDRS\r\n", desc->port));
if (!IS_OPEN(desc))
return ctl_xerror(EXBADPORT, rbuf, rsize);
return inet_ctl_getifaddrs(desc, rbuf, rsize);
}
case INET_REQ_IFGET: {
- DEBUGF(("inet_ctl(%ld): IFGET\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): IFGET\r\n", desc->port));
if (!IS_OPEN(desc))
return ctl_xerror(EXBADPORT, rbuf, rsize);
return inet_ctl_ifget(desc, buf, len, rbuf, rsize);
}
case INET_REQ_IFSET: {
- DEBUGF(("inet_ctl(%ld): IFSET\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): IFSET\r\n", desc->port));
if (!IS_OPEN(desc))
return ctl_xerror(EXBADPORT, rbuf, rsize);
return inet_ctl_ifset(desc, buf, len, rbuf, rsize);
}
case INET_REQ_SETOPTS: { /* set options */
- DEBUGF(("inet_ctl(%ld): SETOPTS\r\n", (long)desc->port));
- /* XXX fprintf(stderr,"inet_ctl(%ld): SETOPTS (len = %d)\r\n", (long)desc->port,(int) len); */
+ DEBUGF(("inet_ctl(%p): SETOPTS\r\n", desc->port));
+ /* XXX fprintf(stderr,"inet_ctl(%p): SETOPTS (len = %d)\r\n", desc->port,(int) len); */
switch(inet_set_opts(desc, buf, len)) {
case -1:
return ctl_error(EINVAL, rbuf, rsize);
@@ -9309,7 +9386,9 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
* Same as above, but active changed to once w/o header type
* change, so try a read instead of just deliver.
*/
- tcp_recv((tcp_descriptor *) desc, 0);
+ if ((tcp_recv((tcp_descriptor *) desc, 0) >= 0) && desc->active) {
+ sock_select(desc, (FD_READ|FD_CLOSE), 1);
+ }
return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize);
}
}
@@ -9317,7 +9396,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
case INET_REQ_GETSTATUS: {
char tbuf[4];
- DEBUGF(("inet_ctl(%ld): GETSTATUS\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): GETSTATUS\r\n", desc->port));
put_int32(desc->state, tbuf);
return ctl_reply(INET_REP_OK, tbuf, 4, rbuf, rsize);
}
@@ -9325,7 +9404,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
case INET_REQ_GETTYPE: {
char tbuf[8];
- DEBUGF(("inet_ctl(%ld): GETTYPE\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): GETTYPE\r\n", desc->port));
if (desc->sfamily == AF_INET) {
put_int32(INET_AF_INET, &tbuf[0]);
}
@@ -9362,7 +9441,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
case INET_REQ_GETFD: {
char tbuf[4];
- DEBUGF(("inet_ctl(%ld): GETFD\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): GETFD\r\n", desc->port));
if (!IS_OPEN(desc))
return ctl_error(EINVAL, rbuf, rsize);
put_int32((long)desc->s, tbuf);
@@ -9372,7 +9451,8 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
case INET_REQ_GETHOSTNAME: { /* get host name */
char tbuf[INET_MAXHOSTNAMELEN + 1];
- DEBUGF(("inet_ctl(%ld): GETHOSTNAME\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): GETHOSTNAME\r\n",
+ desc->port));
if (len != 0)
return ctl_error(EINVAL, rbuf, rsize);
@@ -9383,7 +9463,8 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
}
case INET_REQ_GETPADDRS: {
- DEBUGF(("inet_ctl(%ld): INET_GETPADDRS\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): INET_GETPADDRS\r\n",
+ desc->port));
if (len != 4) return ctl_error(EINVAL, rbuf, rsize);
@@ -9420,7 +9501,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
inet_address* ptr;
unsigned int sz;
- DEBUGF(("inet_ctl(%ld): PEER\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): PEER\r\n", desc->port));
if (!(desc->state & INET_F_ACTIVE))
return ctl_error(ENOTCONN, rbuf, rsize);
@@ -9454,13 +9535,14 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
return ctl_xerror(xerror, rbuf, rsize);
else {
desc->peer_ptr = &desc->peer_addr;
- desc->peer_addr_len = (SOCKLEN_T) len;
+ desc->peer_addr_len = (unsigned int) len;
return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize);
}
}
case INET_REQ_GETLADDRS: {
- DEBUGF(("inet_ctl(%ld): INET_GETLADDRS\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): INET_GETLADDRS\r\n",
+ desc->port));
if (len != 4) return ctl_error(EINVAL, rbuf, rsize);
@@ -9498,7 +9580,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
inet_address* ptr;
unsigned int sz;
- DEBUGF(("inet_ctl(%ld): NAME\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): NAME\r\n", desc->port));
if ((ptr = desc->name_ptr) != NULL) {
sz = desc->name_addr_len;
@@ -9529,7 +9611,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
return ctl_xerror(xerror, rbuf, rsize);
else {
desc->name_ptr = &desc->name_addr;
- desc->name_addr_len = (SOCKLEN_T) len;
+ desc->name_addr_len = (unsigned int) len;
return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize);
}
}
@@ -9539,7 +9621,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
inet_address local;
int port;
- DEBUGF(("inet_ctl(%ld): BIND\r\n", (long)desc->port));
+ DEBUGF(("inet_ctl(%p): BIND\r\n", desc->port));
if (len < 2)
return ctl_error(EINVAL, rbuf, rsize);
@@ -9568,8 +9650,8 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
}
case INET_REQ_IGNOREFD: {
- DEBUGF(("inet_ctl(%ld): IGNOREFD, IGNORED = %d\r\n",
- (long)desc->port,(int)*buf));
+ DEBUGF(("inet_ctl(%p): IGNOREFD, IGNORED = %d\r\n",
+ desc->port,(int)*buf));
/*
* FD can only be ignored for connected TCP connections for now,
@@ -9743,8 +9825,9 @@ static int tcp_expand_buffer(tcp_descriptor* desc, int len)
return 0;
}
- DEBUGF(("tcp_expand_buffer(%ld): s=%d, from %ld to %d\r\n",
- (long)desc->inet.port, desc->inet.s, desc->i_buf->orig_size, ulen));
+ DEBUGF(("tcp_expand_buffer(%p): s=%d, from %ld to %d\r\n",
+ desc->inet.port, desc->inet.s,
+ desc->i_buf->orig_size, ulen));
offs1 = desc->i_ptr_start - desc->i_buf->orig_bytes;
offs2 = desc->i_ptr - desc->i_ptr_start;
@@ -9844,7 +9927,7 @@ static int tcp_inet_init(void)
static ErlDrvData prep_tcp_inet_start(ErlDrvPort port, char* args)
{
tcp_descriptor* desc;
- DEBUGF(("tcp_inet_start(%ld) {\r\n", (long)port));
+ DEBUGF(("tcp_inet_start(%p) {\r\n", port));
desc = (tcp_descriptor*)
inet_start(port, sizeof(tcp_descriptor), IPPROTO_TCP);
@@ -9865,7 +9948,7 @@ static ErlDrvData prep_tcp_inet_start(ErlDrvPort port, char* args)
desc->mtd = NULL;
desc->mtd_cache = NULL;
desc->multi_first = desc->multi_last = NULL;
- DEBUGF(("tcp_inet_start(%ld) }\r\n", (long)port));
+ DEBUGF(("tcp_inet_start(%p) }\r\n", port));
return (ErlDrvData) desc;
}
@@ -9918,7 +10001,7 @@ static tcp_descriptor* tcp_inet_copy(tcp_descriptor* desc,SOCKET s,
/* The new port will be linked and connected to the original caller */
port = driver_create_port(port, owner, "tcp_inet", (ErlDrvData) copy_desc);
- if ((long)port == -1) {
+ if ((SWord)port == -1) {
*err = INET_ERRNO_SYSTEM_LIMIT;
FREE(copy_desc);
return NULL;
@@ -9981,13 +10064,22 @@ static void tcp_close_check(tcp_descriptor* desc)
static void tcp_inet_stop(ErlDrvData e)
{
tcp_descriptor* desc = (tcp_descriptor*)e;
- DEBUGF(("tcp_inet_stop(%ld) {s=%d\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_inet_stop(%p) {s=%d\r\n",
+ desc->inet.port, desc->inet.s));
tcp_close_check(desc);
tcp_clear_input(desc);
- DEBUGF(("tcp_inet_stop(%ld) }\r\n", (long)desc->inet.port));
+#ifdef HAVE_SENDFILE
+ if(desc->tcp_add_flags & TCP_ADDF_SENDFILE) {
+ desc->tcp_add_flags &= ~TCP_ADDF_SENDFILE;
+ close(desc->sendfile.dup_file_fd);
+ DEBUGF(("tcp_inet_stop(%p): SENDFILE dup closed %d\r\n",
+ desc->inet.port, desc->sendfile.dup_file_fd));
+ }
+#endif
+
+ DEBUGF(("tcp_inet_stop(%p) }\r\n", desc->inet.port));
inet_stop(INETP(desc));
}
@@ -10002,12 +10094,6 @@ static void tcp_inet_stop(ErlDrvData e)
* will be freed through tcp_inet_stop later on. */
static void tcp_desc_close(tcp_descriptor* desc)
{
-#ifdef HAVE_SENDFILE
- if(desc->tcp_add_flags & TCP_ADDF_SENDFILE) {
- desc->tcp_add_flags &= ~TCP_ADDF_SENDFILE;
- close(desc->sendfile.dup_file_fd);
- }
-#endif
tcp_clear_input(desc);
tcp_clear_output(desc);
@@ -10035,7 +10121,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
switch(cmd) {
case INET_REQ_OPEN: { /* open socket and return internal index */
int domain;
- DEBUGF(("tcp_inet_ctl(%ld): OPEN\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_ctl(%p): OPEN\r\n",
+ desc->inet.port));
if (len != 2) return ctl_error(EINVAL, rbuf, rsize);
switch(buf[0]) {
case INET_AF_INET:
@@ -10062,7 +10149,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
case INET_REQ_FDOPEN: { /* pass in an open (and optionally bound) socket */
int domain;
int bound;
- DEBUGF(("tcp_inet_ctl(%ld): FDOPEN\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_ctl(%p): FDOPEN\r\n",
+ desc->inet.port));
if (len != 6 && len != 10) return ctl_error(EINVAL, rbuf, rsize);
switch(buf[0]) {
case INET_AF_INET:
@@ -10095,7 +10183,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
case INET_REQ_LISTEN: { /* argument backlog */
int backlog;
- DEBUGF(("tcp_inet_ctl(%ld): LISTEN\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_ctl(%p): LISTEN\r\n",
+ desc->inet.port));
if (desc->inet.state == INET_STATE_CLOSED)
return ctl_xerror(EXBADPORT, rbuf, rsize);
if (!IS_OPEN(INETP(desc)))
@@ -10115,7 +10204,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
char tbuf[2], *xerror;
unsigned timeout;
- DEBUGF(("tcp_inet_ctl(%ld): CONNECT\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_ctl(%p): CONNECT\r\n",
+ desc->inet.port));
/* INPUT: Timeout(4), Port(2), Address(N) */
if (!IS_OPEN(INETP(desc)))
@@ -10164,7 +10254,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
unsigned int n;
SOCKET s;
- DEBUGF(("tcp_inet_ctl(%ld): ACCEPT\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_ctl(%p): ACCEPT\r\n",
+ desc->inet.port));
/* INPUT: Timeout(4) */
if ((desc->inet.state != INET_STATE_LISTENING && desc->inet.state != INET_STATE_ACCEPTING &&
@@ -10267,7 +10358,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
}
}
case INET_REQ_CLOSE:
- DEBUGF(("tcp_inet_ctl(%ld): CLOSE\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_ctl(%p): CLOSE\r\n",
+ desc->inet.port));
tcp_close_check(desc);
tcp_desc_close(desc);
return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize);
@@ -10278,8 +10370,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
char tbuf[2];
int n;
- DEBUGF(("tcp_inet_ctl(%ld): RECV (s=%d)\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_inet_ctl(%p): RECV (s=%d)\r\n",
+ desc->inet.port, desc->inet.s));
/* INPUT: Timeout(4), Length(4) */
if (!IS_CONNECTED(INETP(desc))) {
if (desc->tcp_add_flags & TCP_ADDF_DELAYED_CLOSE_RECV) {
@@ -10298,8 +10390,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
timeout = get_int32(buf);
buf += 4;
n = get_int32(buf);
- DEBUGF(("tcp_inet_ctl(%ld) timeout = %d, n = %d\r\n",
- (long)desc->inet.port,timeout,n));
+ DEBUGF(("tcp_inet_ctl(%p) timeout = %d, n = %d\r\n",
+ desc->inet.port,timeout,n));
if ((desc->inet.htype != TCP_PB_RAW) && (n != 0))
return ctl_error(EINVAL, rbuf, rsize);
if (n > TCP_MAX_PACKET_SIZE)
@@ -10324,7 +10416,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
}
case TCP_REQ_UNRECV: {
- DEBUGF(("tcp_inet_ctl(%ld): UNRECV\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_ctl(%p): UNRECV\r\n",
+ desc->inet.port));
if (!IS_CONNECTED(INETP(desc)))
return ctl_error(ENOTCONN, rbuf, rsize);
tcp_push_buffer(desc, buf, len);
@@ -10334,7 +10427,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
}
case TCP_REQ_SHUTDOWN: {
int how;
- DEBUGF(("tcp_inet_ctl(%ld): FDOPEN\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_ctl(%p): FDOPEN\r\n",
+ desc->inet.port));
if (!IS_CONNECTED(INETP(desc))) {
return ctl_error(ENOTCONN, rbuf, rsize);
}
@@ -10367,12 +10461,20 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
int raw_file_fd;
- DEBUGF(("tcp_inet_ctl(%ld): SENDFILE\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_ctl(%p): SENDFILE\r\n",
+ desc->inet.port));
if (len != required_len) {
return ctl_error(EINVAL, rbuf, rsize);
} else if (!IS_CONNECTED(INETP(desc))) {
return ctl_error(ENOTCONN, rbuf, rsize);
+ } else if (desc->tcp_add_flags & TCP_ADDF_SENDFILE) {
+ /* This should not happen as prim_inet.erl makes
+ sure that only the controlling process can
+ use the sendfile operation. But we add this
+ check here anyways just in case that prim_inet
+ is changed... */
+ return ctl_error(EINVAL, rbuf, rsize);
}
sys_memcpy(&raw_file_fd, buf, sizeof(raw_file_fd));
@@ -10380,6 +10482,9 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
desc->sendfile.dup_file_fd = dup(raw_file_fd);
+ DEBUGF(("tcp_inet_ctl(%p): SENDFILE dup %d\r\n",
+ desc->inet.port, desc->sendfile.dup_file_fd));
+
if(desc->sendfile.dup_file_fd == -1) {
return ctl_error(errno, rbuf, rsize);
}
@@ -10412,7 +10517,8 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
}
default:
- DEBUGF(("tcp_inet_ctl(%ld): %u\r\n", (long)desc->inet.port, cmd));
+ DEBUGF(("tcp_inet_ctl(%p): %u\r\n",
+ desc->inet.port, cmd));
return inet_ctl(INETP(desc), cmd, buf, len, rbuf, rsize);
}
@@ -10431,6 +10537,11 @@ static void tcp_inet_send_timeout(ErlDrvData e, ErlDrvTermData dummy)
if (desc->send_timeout_close) {
tcp_desc_close(desc);
}
+ /* Q: Why not keep port busy as send queue may still be full (ERL-1390)?
+ *
+ * A: If kept busy, a following send call would hang without a timeout
+ * as it would get suspended in erlang:port_command waiting on busy port.
+ */
}
/*
@@ -10449,8 +10560,8 @@ static void tcp_inet_timeout(ErlDrvData e)
tcp_descriptor* desc = (tcp_descriptor*)e;
int state = desc->inet.state;
- DEBUGF(("tcp_inet_timeout(%ld) {s=%d\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_inet_timeout(%p) {s=%d\r\n",
+ desc->inet.port, desc->inet.s));
if ((state & INET_F_MULTI_CLIENT)) { /* Multi-client always means multi-timers */
fire_multi_timers(desc, desc->inet.port, e);
} else if ((state & INET_STATE_CONNECTED) == INET_STATE_CONNECTED) {
@@ -10472,7 +10583,7 @@ static void tcp_inet_timeout(ErlDrvData e)
desc->inet.state = INET_STATE_LISTENING;
async_error_am(INETP(desc), am_timeout);
}
- DEBUGF(("tcp_inet_timeout(%ld) }\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_timeout(%p) }\r\n", desc->inet.port));
}
static void tcp_inet_multi_timeout(ErlDrvData e, ErlDrvTermData caller)
@@ -10508,13 +10619,13 @@ static void tcp_inet_command(ErlDrvData e, char *buf, ErlDrvSizeT len)
tcp_descriptor* desc = (tcp_descriptor*)e;
desc->inet.caller = driver_caller(desc->inet.port);
- DEBUGF(("tcp_inet_command(%ld) {s=%d\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_inet_command(%p) {s=%d\r\n",
+ desc->inet.port, desc->inet.s));
if (!IS_CONNECTED(INETP(desc)))
inet_reply_error(INETP(desc), ENOTCONN);
else if (tcp_send(desc, buf, len) == 0)
inet_reply_ok(INETP(desc));
- DEBUGF(("tcp_inet_command(%ld) }\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_command(%p) }\r\n", desc->inet.port));
}
static void tcp_inet_commandv(ErlDrvData e, ErlIOVec* ev)
@@ -10522,8 +10633,8 @@ static void tcp_inet_commandv(ErlDrvData e, ErlIOVec* ev)
tcp_descriptor* desc = (tcp_descriptor*)e;
desc->inet.caller = driver_caller(desc->inet.port);
- DEBUGF(("tcp_inet_commanv(%ld) {s=%d\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_inet_commanv(%p) {s=%d\r\n",
+ desc->inet.port, desc->inet.s));
if (!IS_CONNECTED(INETP(desc))) {
if (desc->tcp_add_flags & TCP_ADDF_DELAYED_CLOSE_SEND) {
desc->tcp_add_flags &= ~TCP_ADDF_DELAYED_CLOSE_SEND;
@@ -10542,7 +10653,7 @@ static void tcp_inet_commandv(ErlDrvData e, ErlIOVec* ev)
tcp_shutdown_error(desc, EPIPE);
else if (tcp_sendv(desc, ev) == 0)
inet_reply_ok(INETP(desc));
- DEBUGF(("tcp_inet_commandv(%ld) }\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_commandv(%p) }\r\n", desc->inet.port));
}
static void tcp_inet_flush(ErlDrvData e)
@@ -10557,11 +10668,9 @@ static void tcp_inet_flush(ErlDrvData e)
#ifdef HAVE_SENDFILE
/* The old file driver aborted when it was stopped during sendfile, so
- * we'll clear the flag and discard all output. */
+ * we'll clear the flag and discard all output. It is the job of
+ * tcp_inet_stop to close the extra sendfile fd. */
if(desc->tcp_add_flags & TCP_ADDF_SENDFILE) {
- desc->tcp_add_flags &= ~TCP_ADDF_SENDFILE;
- close(desc->sendfile.dup_file_fd);
-
discard_output = 1;
}
#endif
@@ -10613,10 +10722,10 @@ static void inet_stop_select(ErlDrvEvent event, void* _)
static int tcp_recv_closed(tcp_descriptor* desc)
{
#ifdef DEBUG
- long port = (long) desc->inet.port; /* Used after driver_exit() */
+ ErlDrvPort port = desc->inet.port; /* Used after driver_exit() */
#endif
int blocking_send = 0;
- DEBUGF(("tcp_recv_closed(%ld): s=%d, in %s, line %d\r\n",
+ DEBUGF(("tcp_recv_closed(%p): s=%d, in %s, line %d\r\n",
port, desc->inet.s, __FILE__, __LINE__));
if (IS_BUSY(INETP(desc))) {
/* A send is blocked */
@@ -10625,12 +10734,12 @@ static int tcp_recv_closed(tcp_descriptor* desc)
if (desc->busy_on_send) {
cancel_multi_timer(desc, INETP(desc)->port, &tcp_inet_send_timeout);
desc->busy_on_send = 0;
- DEBUGF(("tcp_recv_closed(%ld): busy on send\r\n", port));
+ DEBUGF(("tcp_recv_closed(%p): busy on send\r\n", port));
}
desc->inet.state &= ~INET_F_BUSY;
set_busy_port(desc->inet.port, 0);
inet_reply_error_am(INETP(desc), am_closed);
- DEBUGF(("tcp_recv_closed(%ld): busy reply 'closed'\r\n", port));
+ DEBUGF(("tcp_recv_closed(%p): busy reply 'closed'\r\n", port));
blocking_send = 1;
}
#ifdef HAVE_SENDFILE
@@ -10659,7 +10768,8 @@ static int tcp_recv_closed(tcp_descriptor* desc)
}
async_error_am_all(INETP(desc), am_closed);
/* next time EXBADSEQ will be delivered */
- DEBUGF(("tcp_recv_closed(%ld): passive reply all 'closed'\r\n", port));
+ DEBUGF(("tcp_recv_closed(%p): passive reply all 'closed'\r\n",
+ port));
} else {
tcp_clear_input(desc);
tcp_closed_message(desc);
@@ -10668,9 +10778,9 @@ static int tcp_recv_closed(tcp_descriptor* desc)
} else {
desc_close_read(INETP(desc));
}
- DEBUGF(("tcp_recv_closed(%ld): active close\r\n", port));
+ DEBUGF(("tcp_recv_closed(%p): active close\r\n", port));
}
- DEBUGF(("tcp_recv_closed(%ld): done\r\n", port));
+ DEBUGF(("tcp_recv_closed(%p): done\r\n", port));
return -1;
}
@@ -10747,8 +10857,8 @@ static int tcp_remain(tcp_descriptor* desc, int* len)
desc->inet.psize, desc->i_bufsz,
desc->inet.delimiter, &desc->http_state);
- DEBUGF(("tcp_remain(%ld): s=%d, n=%d, nfill=%d nsz=%d, tlen %d\r\n",
- (long)desc->inet.port, desc->inet.s, n, nfill, nsz, tlen));
+ DEBUGF(("tcp_remain(%p): s=%d, n=%d, nfill=%d nsz=%d, tlen %d\r\n",
+ desc->inet.port, desc->inet.s, n, nfill, nsz, tlen));
if (tlen > 0) {
if (tlen <= n) { /* got a packet */
@@ -10924,8 +11034,8 @@ static int tcp_recv(tcp_descriptor* desc, int request_len)
else /* remain already set use it */
nread = desc->i_remain;
- DEBUGF(("tcp_recv(%ld): s=%d about to read %d bytes...\r\n",
- (long)desc->inet.port, desc->inet.s, nread));
+ DEBUGF(("tcp_recv(%p): s=%d about to read %d bytes...\r\n",
+ desc->inet.port, desc->inet.s, nread));
n = sock_recv(desc->inet.s, desc->i_ptr, nread, 0);
@@ -11079,8 +11189,8 @@ static void tcp_inet_event(ErlDrvData e, ErlDrvEvent event)
WSANETWORKEVENTS netEv;
int err;
- DEBUGF(("tcp_inet_event(%ld) {s=%d\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_inet_event(%p) {s=%d\r\n",
+ desc->inet.port, desc->inet.s));
if (WSAEnumNetworkEvents(desc->inet.s, desc->inet.event,
&netEv) != 0) {
DEBUGF((" => EnumNetworkEvents = %d\r\n", sock_errno() ));
@@ -11179,11 +11289,12 @@ static void tcp_inet_event(ErlDrvData e, ErlDrvEvent event)
else
tcp_recv_closed(desc);
}
- DEBUGF(("tcp_inet_event(%ld) }\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_event(%p) }\r\n", desc->inet.port));
return;
error:
- DEBUGF(("tcp_inet_event(%ld) error}\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_event(%p) error}\r\n",
+ desc->inet.port));
return;
}
@@ -11201,8 +11312,8 @@ static int tcp_inet_input(tcp_descriptor* desc, HANDLE event)
long port = (long) desc->inet.port; /* Used after driver_exit() */
#endif
ASSERT(!INET_IGNORED(INETP(desc)));
- DEBUGF(("tcp_inet_input(%ld) {s=%d\r\n", port, desc->inet.s));
- /* XXX fprintf(stderr,"tcp_inet_input(%ld) {s=%d}\r\n",(long) desc->inet.port, desc->inet.s); */
+ DEBUGF(("tcp_inet_input(%p) {s=%d\r\n", port, desc->inet.s));
+ /* XXX fprintf(stderr,"tcp_inet_input(%p) {s=%d}\r\n",(long) desc->inet.port, desc->inet.s); */
if (desc->inet.state == INET_STATE_ACCEPTING) {
SOCKET s;
unsigned int len;
@@ -11339,11 +11450,11 @@ static int tcp_inet_input(tcp_descriptor* desc, HANDLE event)
else {
/* maybe a close op from connection attempt?? */
sock_select(INETP(desc),FD_ACCEPT,0);
- DEBUGF(("tcp_inet_input(%ld): s=%d bad state: %04x\r\n",
+ DEBUGF(("tcp_inet_input(%p): s=%d bad state: %04x\r\n",
port, desc->inet.s, desc->inet.state));
}
done:
- DEBUGF(("tcp_inet_input(%ld) }\r\n", port));
+ DEBUGF(("tcp_inet_input(%p) }\r\n", port));
return ret;
}
@@ -11371,8 +11482,8 @@ static int tcp_send_or_shutdown_error(tcp_descriptor* desc, int err)
* socket option is enabled). We just have to distinguish between passive
* and active sockets.
*/
- DEBUGF(("driver_failure_eof(%ld) in %s, line %d\r\n",
- (long)desc->inet.port, __FILE__, __LINE__));
+ DEBUGF(("driver_failure_eof(%p) in %s, line %d\r\n",
+ desc->inet.port, __FILE__, __LINE__));
if (desc->inet.active) {
ErlDrvTermData err_atom;
if (show_econnreset) {
@@ -11515,8 +11626,8 @@ static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev)
if ((desc->tcp_add_flags & TCP_ADDF_SENDFILE) || sz > 0) {
driver_enqv(ix, ev, 0);
if (sz+ev->size >= desc->high) {
- DEBUGF(("tcp_sendv(%ld): s=%d, sender forced busy\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_sendv(%p): s=%d, sender forced busy\r\n",
+ desc->inet.port, desc->inet.s));
desc->inet.state |= INET_F_BUSY; /* mark for low-watermark */
desc->inet.busy_caller = desc->inet.caller;
set_busy_port(desc->inet.port, 1);
@@ -11532,8 +11643,10 @@ static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev)
else {
int vsize = (ev->vsize > MAX_VSIZE) ? MAX_VSIZE : ev->vsize;
- DEBUGF(("tcp_sendv(%ld): s=%d, about to send "LLU","LLU" bytes\r\n",
- (long)desc->inet.port, desc->inet.s, (llu_t)h_len, (llu_t)len));
+ DEBUGF(("tcp_sendv(%p): s=%d, "
+ "about to send "LLU","LLU" bytes\r\n",
+ desc->inet.port, desc->inet.s,
+ (llu_t)h_len, (llu_t)len));
if (INET_IGNORED(INETP(desc))) {
INETP(desc)->flags |= INET_IGNORE_WRITE;
@@ -11547,9 +11660,9 @@ static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev)
vsize, &n, 0))) {
if ((sock_errno() != ERRNO_BLOCK) && (sock_errno() != EINTR)) {
int err = sock_errno();
- DEBUGF(("tcp_sendv(%ld): s=%d, "
+ DEBUGF(("tcp_sendv(%p): s=%d, "
"sock_sendv(size=2) errno = %d\r\n",
- (long)desc->inet.port, desc->inet.s, err));
+ desc->inet.port, desc->inet.s, err));
return tcp_send_error(desc, err);
}
#ifdef __WIN32__
@@ -11562,14 +11675,14 @@ static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev)
return 0;
}
else {
- DEBUGF(("tcp_sendv(%ld): s=%d, only sent "
+ DEBUGF(("tcp_sendv(%p): s=%d, only sent "
LLU"/%d of "LLU"/%d bytes/items\r\n",
- (long)desc->inet.port, desc->inet.s,
+ desc->inet.port, desc->inet.s,
(llu_t)n, vsize, (llu_t)ev->size, ev->vsize));
}
- DEBUGF(("tcp_sendv(%ld): s=%d, Send failed, queuing\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_sendv(%p): s=%d, Send failed, queuing\r\n",
+ desc->inet.port, desc->inet.s));
driver_enqv(ix, ev, n);
if (!INET_IGNORED(INETP(desc)))
sock_select(INETP(desc),(FD_WRITE|FD_CLOSE), 1);
@@ -11618,8 +11731,8 @@ static int tcp_send(tcp_descriptor* desc, char* ptr, ErlDrvSizeT len)
driver_enq(ix, buf, h_len);
driver_enq(ix, ptr, len);
if (sz+h_len+len >= desc->high) {
- DEBUGF(("tcp_send(%ld): s=%d, sender forced busy\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_send(%p): s=%d, sender forced busy\r\n",
+ desc->inet.port, desc->inet.s));
desc->inet.state |= INET_F_BUSY; /* mark for low-watermark */
desc->inet.busy_caller = desc->inet.caller;
set_busy_port(desc->inet.port, 1);
@@ -11638,8 +11751,10 @@ static int tcp_send(tcp_descriptor* desc, char* ptr, ErlDrvSizeT len)
iov[1].iov_base = ptr;
iov[1].iov_len = len;
- DEBUGF(("tcp_send(%ld): s=%d, about to send "LLU","LLU" bytes\r\n",
- (long)desc->inet.port, desc->inet.s, (llu_t)h_len, (llu_t)len));
+ DEBUGF(("tcp_send(%p): s=%d, "
+ "about to send "LLU","LLU" bytes\r\n",
+ desc->inet.port, desc->inet.s,
+ (llu_t)h_len, (llu_t)len));
if (INET_IGNORED(INETP(desc))) {
INETP(desc)->flags |= INET_IGNORE_WRITE;
n = 0;
@@ -11649,8 +11764,9 @@ static int tcp_send(tcp_descriptor* desc, char* ptr, ErlDrvSizeT len)
} else if (IS_SOCKET_ERROR(sock_sendv(desc->inet.s,iov,2,&n,0))) {
if ((sock_errno() != ERRNO_BLOCK) && (sock_errno() != EINTR)) {
int err = sock_errno();
- DEBUGF(("tcp_send(%ld): s=%d,sock_sendv(size=2) errno = %d\r\n",
- (long)desc->inet.port, desc->inet.s, err));
+ DEBUGF(("tcp_send(%p): s=%d, "
+ "sock_sendv(size=2) errno = %d\r\n",
+ desc->inet.port, desc->inet.s, err));
return tcp_send_error(desc, err);
}
#ifdef __WIN32__
@@ -11663,8 +11779,8 @@ static int tcp_send(tcp_descriptor* desc, char* ptr, ErlDrvSizeT len)
return 0;
}
- DEBUGF(("tcp_send(%ld): s=%d, Send failed, queuing",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_send(%p): s=%d, Send failed, queuing",
+ desc->inet.port, desc->inet.s));
if (n < h_len) {
driver_enq(ix, buf+n, h_len-n);
@@ -11715,6 +11831,9 @@ static int tcp_sendfile_completed(tcp_descriptor* desc) {
desc->tcp_add_flags &= ~TCP_ADDF_SENDFILE;
close(desc->sendfile.dup_file_fd);
+ DEBUGF(("tcp_sendfile_completed(%p): SENDFILE dup closed %d\r\n",
+ desc->inet.port, desc->sendfile.dup_file_fd));
+
/* While we flushed the output queue prior to sending the file, we've
* deferred clearing busy status until now as there's no point in doing so
* while we still have a file to send.
@@ -11799,7 +11918,8 @@ static int tcp_inet_sendfile(tcp_descriptor* desc) {
int result = 0;
ssize_t n;
- DEBUGF(("tcp_inet_sendfile(%ld) {s=%d\r\n", (long)ix, desc->inet.s));
+ DEBUGF(("tcp_inet_sendfile(%p) {s=%d\r\n",
+ ix, desc->inet.s));
/* If there was any data in the queue by the time sendfile was issued,
* we'll need to skip it first. Note that we don't clear busy status until
@@ -11931,8 +12051,8 @@ static int tcp_inet_sendfile(tcp_descriptor* desc) {
socket_error: {
int socket_errno = sock_errno();
- DEBUGF(("tcp_inet_sendfile(%ld): send errno = %d (errno %d)\r\n",
- (long)desc->inet.port, socket_errno, errno));
+ DEBUGF(("tcp_inet_sendfile(%p): send errno = %d (errno %d)\r\n",
+ desc->inet.port, socket_errno, errno));
tcp_sendfile_aborted(desc, socket_errno);
result = tcp_send_error(desc, socket_errno);
@@ -11941,7 +12061,7 @@ socket_error: {
}
done:
- DEBUGF(("tcp_inet_sendfile(%ld) }\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_sendfile(%p) }\r\n", desc->inet.port));
return result;
}
#endif /* HAVE_SENDFILE */
@@ -11956,8 +12076,8 @@ static int tcp_inet_output(tcp_descriptor* desc, HANDLE event)
ErlDrvPort ix = desc->inet.port;
ASSERT(!INET_IGNORED(INETP(desc)));
- DEBUGF(("tcp_inet_output(%ld) {s=%d\r\n",
- (long)desc->inet.port, desc->inet.s));
+ DEBUGF(("tcp_inet_output(%p) {s=%d\r\n",
+ desc->inet.port, desc->inet.s));
if (desc->inet.state == INET_STATE_CONNECTING) {
sock_select(INETP(desc),FD_CONNECT,0);
@@ -12026,13 +12146,16 @@ static int tcp_inet_output(tcp_descriptor* desc, HANDLE event)
goto done;
}
vsize = vsize > MAX_VSIZE ? MAX_VSIZE : vsize;
- DEBUGF(("tcp_inet_output(%ld): s=%d, About to send %d items\r\n",
- (long)desc->inet.port, desc->inet.s, vsize));
+ DEBUGF(("tcp_inet_output(%p): s=%d, "
+ "About to send %d items\r\n",
+ desc->inet.port, desc->inet.s, vsize));
if (IS_SOCKET_ERROR(sock_sendv(desc->inet.s, iov, vsize, &n, 0))) {
write_error:
if ((sock_errno() != ERRNO_BLOCK) && (sock_errno() != EINTR)) {
- DEBUGF(("tcp_inet_output(%ld): sock_sendv(%d) errno = %d (errno %d)\r\n",
- (long)desc->inet.port, vsize, sock_errno(), errno));
+ DEBUGF(("tcp_inet_output(%p): "
+ "sock_sendv(%d) errno = %d (errno %d)\r\n",
+ desc->inet.port, vsize,
+ sock_errno(), errno));
ret = tcp_send_error(desc, sock_errno());
goto done;
}
@@ -12080,11 +12203,11 @@ static int tcp_inet_output(tcp_descriptor* desc, HANDLE event)
}
else {
sock_select(INETP(desc),FD_CONNECT,0);
- DEBUGF(("tcp_inet_output(%ld): bad state: %04x\r\n",
- (long)desc->inet.port, desc->inet.state));
+ DEBUGF(("tcp_inet_output(%p): bad state: %04x\r\n",
+ desc->inet.port, desc->inet.state));
}
done:
- DEBUGF(("tcp_inet_output(%ld) }\r\n", (long)desc->inet.port));
+ DEBUGF(("tcp_inet_output(%p) }\r\n", desc->inet.port));
return ret;
}
@@ -12246,14 +12369,15 @@ static void packet_inet_stop(ErlDrvData e)
into "udp_descriptor*" or "inet_descriptor*":
*/
udp_descriptor * udesc = (udp_descriptor*) e;
- inet_descriptor* descr = INETP(udesc);
+ inet_descriptor* desc = INETP(udesc);
if (udesc->i_buf != NULL) {
release_buffer(udesc->i_buf);
udesc->i_buf = NULL;
}
- ASSERT(NO_SUBSCRIBERS(&(descr->empty_out_q_subs)));
- inet_stop(descr);
+ ASSERT(NO_SUBSCRIBERS(&(desc->empty_out_q_subs)));
+ async_error_am_all(desc, am_closed);
+ inet_stop(desc);
}
static int packet_error(udp_descriptor* udesc, int err)
@@ -12280,7 +12404,8 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
cmd -= ERTS_INET_DRV_CONTROL_MAGIC_NUMBER;
switch(cmd) {
case INET_REQ_OPEN: /* open socket and return internal index */
- DEBUGF(("packet_inet_ctl(%ld): OPEN\r\n", (long)desc->port));
+ DEBUGF(("packet_inet_ctl(%p): OPEN\r\n",
+ desc->port));
if (len != 2) {
return ctl_error(EINVAL, rbuf, rsize);
}
@@ -12329,7 +12454,8 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
case INET_REQ_FDOPEN: { /* pass in an open (and optionally bound) socket */
SOCKET s;
int bound;
- DEBUGF(("packet inet_ctl(%ld): FDOPEN\r\n", (long)desc->port));
+ DEBUGF(("packet inet_ctl(%p): FDOPEN\r\n",
+ desc->port));
if (len != 6 && len != 10) {
return ctl_error(EINVAL, rbuf, rsize);
}
@@ -12382,7 +12508,8 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
case INET_REQ_CLOSE:
- DEBUGF(("packet_inet_ctl(%ld): CLOSE\r\n", (long)desc->port));
+ DEBUGF(("packet_inet_ctl(%p): CLOSE\r\n",
+ desc->port));
erl_inet_close(desc);
return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize);
@@ -12400,7 +12527,8 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
#ifdef HAVE_SCTP
unsigned timeout;
#endif
- DEBUGF(("packet_inet_ctl(%ld): CONNECT\r\n", (long)desc->port));
+ DEBUGF(("packet_inet_ctl(%p): CONNECT\r\n",
+ desc->port));
/* INPUT: [ Timeout(4), Port(2), Address(N) ] */
@@ -12487,7 +12615,8 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
*/
int backlog;
- DEBUGF(("packet_inet_ctl(%ld): LISTEN\r\n", (long)desc->port));
+ DEBUGF(("packet_inet_ctl(%p): LISTEN\r\n",
+ desc->port));
if (!IS_SCTP(desc))
return ctl_xerror(EXBADPORT, rbuf, rsize);
if (!IS_OPEN(desc))
@@ -12552,7 +12681,8 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
int err;
SOCKET new_socket;
- DEBUGF(("packet_inet_ctl(%ld): PEELOFF\r\n", (long)desc->port));
+ DEBUGF(("packet_inet_ctl(%p): PEELOFF\r\n",
+ desc->port));
if (!IS_SCTP(desc))
return ctl_xerror(EXBADPORT, rbuf, rsize);
if (!IS_OPEN(desc))
@@ -12593,7 +12723,8 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
unsigned timeout;
char tbuf[2];
- DEBUGF(("packet_inet_ctl(%ld): RECV\r\n", (long)desc->port));
+ DEBUGF(("packet_inet_ctl(%p): RECV\r\n",
+ desc->port));
/* INPUT: Timeout(4), Length(4) */
if (!IS_OPEN(desc))
return ctl_xerror(EXBADPORT, rbuf, rsize);
diff --git a/erts/emulator/drivers/unix/ttsl_drv.c b/erts/emulator/drivers/unix/ttsl_drv.c
index 2f4301cf9c..05bec288d4 100644
--- a/erts/emulator/drivers/unix/ttsl_drv.c
+++ b/erts/emulator/drivers/unix/ttsl_drv.c
@@ -1325,17 +1325,17 @@ static int start_termcap(void)
capbuf = driver_alloc(1024);
if (!capbuf)
- goto false;
+ goto termcap_false;
eres = erl_drv_getenv("TERM", capbuf, &envsz);
if (eres == 0)
env = capbuf;
else if (eres < 0) {
DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d\n", eres));
- goto false;
+ goto termcap_false;
} else /* if (eres > 1) */ {
char *envbuf = driver_alloc(envsz);
if (!envbuf)
- goto false;
+ goto termcap_false;
while (1) {
char *newenvbuf;
eres = erl_drv_getenv("TERM", envbuf, &envsz);
@@ -1345,7 +1345,7 @@ static int start_termcap(void)
if (eres < 0 || !newenvbuf) {
DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d or realloc buf == %p\n", eres, newenvbuf));
env = newenvbuf ? newenvbuf : envbuf;
- goto false;
+ goto termcap_false;
}
envbuf = newenvbuf;
}
@@ -1353,7 +1353,7 @@ static int start_termcap(void)
}
if ((tres = tgetent((char*)lbuf, env)) <= 0) {
DEBUGLOG(("start_termcap: failure in tgetent(..) = %d\n", tres));
- goto false;
+ goto termcap_false;
}
if (env != capbuf) {
env = NULL;
@@ -1375,7 +1375,7 @@ static int start_termcap(void)
return TRUE;
}
DEBUGLOG(("start_termcap: failed start\n"));
- false:
+ termcap_false:
if (env && env != capbuf)
driver_free(env);
if (capbuf)
diff --git a/erts/emulator/drivers/win32/registry_drv.c b/erts/emulator/drivers/win32/registry_drv.c
index a03a030df8..7d71ef7f53 100644
--- a/erts/emulator/drivers/win32/registry_drv.c
+++ b/erts/emulator/drivers/win32/registry_drv.c
@@ -195,7 +195,7 @@ reg_from_erlang(ErlDrvData clientData, char* buf, ErlDrvSizeT count)
* [HKEY(DWORD), KeyString(string)]
*/
- hkey = (HKEY) get_int32(buf+0);
+ hkey = (HKEY)(SWord) get_int32(buf+0);
rp->hkey_root = hkey;
key = buf+4;
result = RegOpenKeyEx(hkey, key, 0, rp->sam, &newKey);
@@ -217,7 +217,7 @@ reg_from_erlang(ErlDrvData clientData, char* buf, ErlDrvSizeT count)
HKEY newKey;
DWORD disposition;
- hkey = (HKEY) get_int32(buf+0);
+ hkey = (HKEY)(SWord) get_int32(buf+0);
rp->hkey_root = hkey;
key = buf+4;
result = RegCreateKeyEx(hkey, key, 0, "", 0, rp->sam, NULL,
@@ -512,7 +512,7 @@ state_reply(RegPort* rp, /* Pointer to port structure. */
s[0] = 's';
i = 1;
- put_int32((DWORD) root, s+i);
+ put_int32((DWORD)(SWord) root, s+i);
i += 4;
memcpy(s+i, name, nameSize);
ASSERT(i+nameSize == needed);
diff --git a/erts/emulator/drivers/win32/ttsl_drv.c b/erts/emulator/drivers/win32/ttsl_drv.c
index 9b72d1fea9..8d59682766 100644
--- a/erts/emulator/drivers/win32/ttsl_drv.c
+++ b/erts/emulator/drivers/win32/ttsl_drv.c
@@ -149,7 +149,7 @@ static int ttysl_init()
static ErlDrvData ttysl_start(ErlDrvPort port, char* buf)
{
- if ((int)ttysl_port != -1 || console_thread == NULL) {
+ if ((SWord)ttysl_port != -1 || console_thread == NULL) {
return ERL_DRV_ERROR_GENERAL;
}
start_lbuf();
@@ -215,7 +215,7 @@ static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
static void ttysl_stop(ErlDrvData ttysl_data)
{
- if ((int)ttysl_port != -1) {
+ if ((SWord)ttysl_port != -1) {
driver_select(ttysl_port, console_input_event, ERL_DRV_READ, 0);
}
diff --git a/erts/emulator/drivers/win32/win_con.c b/erts/emulator/drivers/win32/win_con.c
index 0bcccfe405..bbb3772f85 100644
--- a/erts/emulator/drivers/win32/win_con.c
+++ b/erts/emulator/drivers/win32/win_con.c
@@ -157,7 +157,7 @@ static RECT winPos;
static BOOL toolbarVisible;
static BOOL destroyed = FALSE;
-static int lines_to_save = 1000; /* Maximum number of screen lines to save. */
+static int lines_to_save = 10000; /* Maximum number of screen lines to save. */
#define TITLE_BUF_SZ 256
@@ -205,6 +205,18 @@ static void window_title(struct title_buf *);
static void free_window_title(struct title_buf *);
static void Client_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags);
+#ifdef HARDDEBUG
+/* For really hard GUI startup debugging, place DEBUGBOX() macros in code
+ and get modal message boxes with the line number. */
+static void debug_box(int line) {
+ TCHAR buff[1024];
+ swprintf(buff,1024,TEXT("DBG:%d"),line);
+ MessageBox(NULL,buff,TEXT("DBG"),MB_OK|MB_APPLMODAL);
+}
+
+#define DEBUGBOX() debug_box(__LINE__)
+#endif
+
#define CON_VPRINTF_BUF_INC_SIZE 1024
static erts_dsprintf_buf_t *
@@ -430,6 +442,13 @@ ConThreadInit(LPVOID param)
struct title_buf title;
/*DebugBreak();*/
+#ifdef HARDDEBUG
+ if(AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) {
+ freopen("CONOUT$", "w", stdout);
+ freopen("CONOUT$", "w", stderr);
+ }
+#endif
+
hInstance = GetModuleHandle(NULL);
StartupInfo.dwFlags = 0;
GetStartupInfo(&StartupInfo);
@@ -549,7 +568,7 @@ FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
case WM_CREATE:
/* client window creation */
window_title(&title);
- hClientWnd = CreateWindowEx(WS_EX_CLIENTEDGE, szClientClass, title.name,
+ hClientWnd = CreateWindowEx(0, szClientClass, title.name,
WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
@@ -610,7 +629,7 @@ FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
lpttt->hinst = hInstance;
/* check for combobox handle */
if (lpttt->uFlags&TTF_IDISHWND) {
- if ((lpttt->hdr.idFrom == (UINT) hComboWnd)) {
+ if ((lpttt->hdr.idFrom == (UINT_PTR) hComboWnd)) {
lstrcpy(lpttt->lpszText,TEXT("Command History"));
break;
}
@@ -1313,13 +1332,21 @@ LoadUserPreferences(void)
DWORD size;
DWORD res;
DWORD type;
-
+ HFONT hfont;
/* default prefs */
- GetObject(GetStockObject(SYSTEM_FIXED_FONT),sizeof(LOGFONT),(PSTR)&logfont);
+ hfont = CreateFont(0,0, 0,0, 0, FALSE,FALSE,FALSE,
+ ANSI_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS,
+ CLEARTYPE_QUALITY, FIXED_PITCH, TEXT("Consolas"));
+ if(hfont) {
+ GetObject(hfont, sizeof(LOGFONT), (PSTR)&logfont);
+ DeleteObject(hfont);
+ } else {
+ GetObject(GetStockObject(SYSTEM_FIXED_FONT),sizeof(LOGFONT),(PSTR)&logfont);
+ }
fgColor = GetSysColor(COLOR_WINDOWTEXT);
bkgColor = GetSysColor(COLOR_WINDOW);
winPos.left = -1;
- toolbarVisible = TRUE;
+ toolbarVisible = FALSE;
if (RegCreateKeyEx(HKEY_CURRENT_USER, USER_KEY, 0, 0,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
@@ -1774,7 +1801,7 @@ void ConChooseColor(HWND hwnd)
SetBkColor(hdc,bkgColor);
ReleaseDC(hwnd,hdc);
hbrush = CreateSolidBrush(bkgColor);
- DeleteObject((HBRUSH)SetClassLong(hClientWnd,GCL_HBRBACKGROUND,(LONG)hbrush));
+ DeleteObject((HBRUSH)SetClassLongPtr(hClientWnd,GCL_HBRBACKGROUND,(LONG_PTR)hbrush));
InvalidateRect(hwnd,NULL,TRUE);
}
}
@@ -1972,9 +1999,11 @@ DIALOG_PROC_RET CALLBACK AboutDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARA
OffsetRect(&rc, -rc.left, -rc.top);
OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2),
- rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE);
- SetDlgItemText(hDlg, ID_VERSIONSTRING,
- TEXT("Erlang emulator version ") TEXT(ERLANG_VERSION));
+ rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE);
+ SetDlgItemText(hDlg, ID_OTP_VERSIONSTRING,
+ TEXT("OTP version ") TEXT(ERLANG_OTP_VERSION));
+ SetDlgItemText(hDlg, ID_ERTS_VERSIONSTRING,
+ TEXT("Erlang emulator version ") TEXT(ERLANG_VERSION));
return (DIALOG_PROC_RET) TRUE;
case WM_COMMAND:
switch (LOWORD(wParam)) {
@@ -2012,7 +2041,7 @@ ConDrawText(HWND hwnd)
TCHAR *bu = (TCHAR *) ALLOC((num_chars+1) * sizeof(TCHAR));
memcpy(bu,buf,num_chars * sizeof(TCHAR));
bu[num_chars]='\0';
- fprintf(stderr,TEXT("ConDrawText\"%s\"\n"),bu);
+ fprintf(stderr,"ConDrawText\"%S\"\n",bu);
FREE(bu);
fflush(stderr);
}
@@ -2211,17 +2240,6 @@ static TBADDBITMAP tbbitmap =
HINST_COMMCTRL, IDB_STD_SMALL_COLOR,
};
-#ifdef HARDDEBUG
-/* For really hard GUI startup debugging, place DEBUGBOX() macros in code
- and get modal message boxes with the line number. */
-static void debug_box(int line) {
- TCHAR buff[1024];
- swprintf(buff,1024,TEXT("DBG:%d"),line);
- MessageBox(NULL,buff,TEXT("DBG"),MB_OK|MB_APPLMODAL);
-}
-
-#define DEBUGBOX() debug_box(__LINE__)
-#endif
static HWND
InitToolBar(HWND hwndParent)
@@ -2243,7 +2261,7 @@ InitToolBar(HWND hwndParent)
SendMessage(hwndTB,TB_BUTTONSTRUCTSIZE,
(WPARAM) sizeof(TBBUTTON),0);
tbbitmap.hInst = NULL;
- tbbitmap.nID = (UINT) CreateMappedBitmap(beam_module, 1,0, &colorMap, 1);
+ tbbitmap.nID = (UINT_PTR) CreateMappedBitmap(beam_module, 1,0, &colorMap, 1);
SendMessage(hwndTB, TB_ADDBITMAP, (WPARAM) 4,
(LPARAM) &tbbitmap);
@@ -2269,7 +2287,7 @@ InitToolBar(HWND hwndParent)
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_IDISHWND|TTF_CENTERTIP|TTF_SUBCLASS;
ti.hwnd = hwndTB;;
- ti.uId = (UINT)hComboWnd;
+ ti.uId = (UINT_PTR)hComboWnd;
ti.lpszText = LPSTR_TEXTCALLBACK;
hwndTT = (HWND)SendMessage(hwndTB,TB_GETTOOLTIPS,0,0);
SendMessage(hwndTT,TTM_ADDTOOL,0,(LPARAM)&ti);
diff --git a/erts/emulator/hipe/hipe_amd64_asm.m4 b/erts/emulator/hipe/hipe_amd64_asm.m4
index 409fd0ef89..c0a289c20b 100644
--- a/erts/emulator/hipe/hipe_amd64_asm.m4
+++ b/erts/emulator/hipe/hipe_amd64_asm.m4
@@ -261,7 +261,7 @@ dnl
dnl This must be called before SWITCH_ERLANG_TO_C{,QUICK}.
dnl This must not be called if the C BIF's arity > 6.
dnl
-define(NBIF_MOVE_REG,`ifelse($1,$2,`# movq $2, $1',`movq $2, $1')')dnl
+define(NBIF_MOVE_REG,`ifelse($1,$2,`// movq $2, $1',`movq $2, $1')')dnl
define(NBIF_REG_ARG,`NBIF_MOVE_REG($1,ARG$2)')dnl
define(NBIF_STK_LOAD,`movq $2(NSP), $1')dnl
define(NBIF_STK_ARG,`NBIF_STK_LOAD($1,eval(8*($2-$3)))')dnl
diff --git a/erts/emulator/hipe/hipe_amd64_glue.S b/erts/emulator/hipe/hipe_amd64_glue.S
index f3404888d5..c717ae8192 100644
--- a/erts/emulator/hipe/hipe_amd64_glue.S
+++ b/erts/emulator/hipe/hipe_amd64_glue.S
@@ -76,7 +76,7 @@ CSYM(x86_call_to_native):
* This is where native code returns to emulated code.
*/
ASYM(nbif_return):
- movq %rax, P_ARG0(P) # save retval
+ movq %rax, P_ARG0(P) /* save retval */
movl $HIPE_MODE_SWITCH_RES_RETURN, %eax
/* FALLTHROUGH to .flush_exit
*
@@ -96,12 +96,12 @@ ASYM(nbif_return):
SWITCH_ERLANG_TO_C_QUICK
SET_GC_SAFE
/* restore C callee-save registers, drop frame, return */
- movq (%rsp), %rbp # kills P
+ movq (%rsp), %rbp /* kills P */
movq 8(%rsp), %rbx
movq 16(%rsp), %r12
movq 24(%rsp), %r13
movq 32(%rsp), %r14
- movq 40(%rsp), %r15 # kills HP
+ movq 40(%rsp), %r15 /* kills HP */
addq $(7*8), %rsp
ret
@@ -260,10 +260,10 @@ ASYM(nbif_suspend_msg):
ASYM(nbif_suspend_msg_timeout):
movq P_FLAGS(P), %rax
/* this relies on F_TIMO (1<<2) fitting in a byte */
- testb $F_TIMO, %al # F_TIMO set?
- jz .no_timeout # if not set, suspend
+ testb $F_TIMO, %al /* F_TIMO set? */
+ jz .no_timeout /* if not set, suspend */
/* timeout has occurred */
- xorl %eax, %eax # return 0 to signal timeout
+ xorl %eax, %eax /* return 0 to signal timeout */
NSP_RET0
.no_timeout:
movl $HIPE_MODE_SWITCH_RES_WAIT_TIMEOUT, %eax
@@ -308,7 +308,7 @@ CSYM(x86_tailcall_to_native):
CSYM(x86_throw_to_native):
ENTER_FROM_C
/* invoke the handler */
- jmp *P_NCALLEE(P) # set by hipe_find_handler()
+ jmp *P_NCALLEE(P) /* set by hipe_find_handler() */
/*
* This is the default exception handler for native code.
@@ -346,15 +346,15 @@ nbif_4_gc_after_bif:
/*FALLTHROUGH*/
.align 4
.gc_after_bif:
- movl %edx, P_NARITY(P) # Note: narity is a 32-bit field
+ movl %edx, P_NARITY(P) /* Note: narity is a 32-bit field */
subq $(16-8), %rsp
movq P, %rdi
movq %rax, %rsi
- xorl %edx, %edx # Pass NULL in regs
- xorl %ecx, %ecx # Pass 0 in arity
+ xorl %edx, %edx /* Pass NULL in regs */
+ xorl %ecx, %ecx /* Pass 0 in arity */
call CSYM(erts_gc_after_bif_call)
addq $(16-8), %rsp
- movl $0, P_NARITY(P) # Note: narity is a 32-bit field
+ movl $0, P_NARITY(P) /* Note: narity is a 32-bit field */
ret
/*
@@ -396,15 +396,15 @@ nbif_4_simple_exception:
* The stack/heap registers were just read from P.
* - %eax should contain the current call's arity
*/
- movl %eax, P_NARITY(P) # Note: narity is a 32-bit field
+ movl %eax, P_NARITY(P) /* Note: narity is a 32-bit field */
/* find and prepare to invoke the handler */
- SWITCH_ERLANG_TO_C_QUICK # The cached state is clean and need not be saved.
+ SWITCH_ERLANG_TO_C_QUICK /* The cached state is clean and need not be saved. */
SET_GC_SAFE
movq P, %rdi
- call CSYM(hipe_handle_exception) # Note: hipe_handle_exception() conses
- SWITCH_C_TO_ERLANG # %rsp updated by hipe_find_handler()
+ call CSYM(hipe_handle_exception) /* Note: hipe_handle_exception() conses */
+ SWITCH_C_TO_ERLANG /* %rsp updated by hipe_find_handler() */
/* now invoke the handler */
- jmp *P_NCALLEE(P) # set by hipe_find_handler()
+ jmp *P_NCALLEE(P) /* set by hipe_find_handler() */
/*
* A BIF failed with freason TRAP:
@@ -412,7 +412,7 @@ nbif_4_simple_exception:
* - the native heap/stack/reds registers are saved in P
*/
.handle_trap:
- movl %eax, P_NARITY(P) # Note: narity is a 32-bit field
+ movl %eax, P_NARITY(P) /* Note: narity is a 32-bit field */
movl $HIPE_MODE_SWITCH_RES_TRAP, %eax
jmp .nosave_exit
@@ -422,17 +422,17 @@ nbif_4_simple_exception:
*/
GLOBAL(ASYM(nbif_stack_trap_ra))
.align 4
-ASYM(nbif_stack_trap_ra): # a return address, not a function
- # This only handles a single return value.
- # If we have more, we need to save them in the PCB.
- movq %rax, TEMP_RV # save retval
+ASYM(nbif_stack_trap_ra): /* a return address, not a function */
+ /* This only handles a single return value. */
+ /* If we have more, we need to save them in the PCB. */
+ movq %rax, TEMP_RV /* save retval */
SWITCH_ERLANG_TO_C_QUICK
movq P, %rdi
- call CSYM(hipe_handle_stack_trap) # must not cons; preserves TEMP_RV
- movq %rax, %rdx # original RA
+ call CSYM(hipe_handle_stack_trap) /* must not cons; preserves TEMP_RV */
+ movq %rax, %rdx /* original RA */
SWITCH_C_TO_ERLANG_QUICK
- movq TEMP_RV, %rax # restore retval
- jmp *%rdx # resume at original RA
+ movq TEMP_RV, %rax /* restore retval */
+ jmp *%rdx /* resume at original RA */
/*
* nbif_inc_stack_0
@@ -443,8 +443,8 @@ ASYM(nbif_inc_stack_0):
SWITCH_ERLANG_TO_C_QUICK
STORE_ARG_REGS
movq P, %rdi
- # hipe_inc_nstack reads and writes NSP and NSP_LIMIT,
- # but does not access HP or FCALLS (or the non-amd64 NRA).
+ /* hipe_inc_nstack reads and writes NSP and NSP_LIMIT, */
+ /* but does not access HP or FCALLS (or the non-amd64 NRA). */
call CSYM(hipe_inc_nstack)
LOAD_ARG_REGS
SWITCH_C_TO_ERLANG_QUICK
diff --git a/erts/emulator/hipe/hipe_arm_asm.m4 b/erts/emulator/hipe/hipe_arm_asm.m4
index 68a6faa70b..fe78c2b777 100644
--- a/erts/emulator/hipe/hipe_arm_asm.m4
+++ b/erts/emulator/hipe/hipe_arm_asm.m4
@@ -175,7 +175,7 @@ dnl It will be a memory load via NSP when ARGNO >= NR_ARG_REGS.
dnl It will be a register move when 0 <= ARGNO < NR_ARG_REGS; if
dnl the source and destination are the same, the move is suppressed.
dnl
-define(NBIF_MOVE_REG,`ifelse($1,$2,`# mov $1, $2',`mov $1, $2')')dnl
+define(NBIF_MOVE_REG,`ifelse($1,$2,`// mov $1, $2',`mov $1, $2')')dnl
define(NBIF_REG_ARG,`NBIF_MOVE_REG($1,ARG$2)')dnl
define(NBIF_STK_LOAD,`ldr $1, [NSP, #$2]')dnl
define(NBIF_STK_ARG,`NBIF_STK_LOAD($1,eval(4*(($2-$3)-1)))')dnl
diff --git a/erts/emulator/hipe/hipe_arm_glue.S b/erts/emulator/hipe/hipe_arm_glue.S
index 5b7f8ad52d..5071b10287 100644
--- a/erts/emulator/hipe/hipe_arm_glue.S
+++ b/erts/emulator/hipe/hipe_arm_glue.S
@@ -436,8 +436,8 @@ nbif_4_simple_exception:
.global nbif_stack_trap_ra
.type nbif_stack_trap_ra, %function
nbif_stack_trap_ra: /* a return address, not a function */
- # This only handles a single return value.
- # If we have more, we need to save them in the PCB.
+ /* This only handles a single return value. */
+ /* If we have more, we need to save them in the PCB. */
mov TEMP_ARG0, r0 /* save retval */
str NSP, [P, #P_NSP]
mov r0, P
@@ -457,12 +457,12 @@ hipe_arm_inc_stack:
mov TEMP_ARG0, lr
str NSP, [P, #P_NSP]
mov r0, P
- # hipe_inc_nstack reads and writes NSP and NSP_LIMIT,
- # but does not access LR/RA, HP, or FCALLS.
+ /* hipe_inc_nstack reads and writes NSP and NSP_LIMIT, */
+ /* but does not access LR/RA, HP, or FCALLS. */
bl hipe_inc_nstack
ldr NSP, [P, #P_NSP]
LOAD_ARG_REGS
- # this relies on LOAD_ARG_REGS not clobbering TEMP_ARG0
+ /* this relies on LOAD_ARG_REGS not clobbering TEMP_ARG0 */
mov pc, TEMP_ARG0
#if defined(__linux__) && defined(__ELF__)
diff --git a/erts/emulator/hipe/hipe_bif2.c b/erts/emulator/hipe/hipe_bif2.c
index 7e04f7d9c0..13acf2e6b3 100644
--- a/erts/emulator/hipe/hipe_bif2.c
+++ b/erts/emulator/hipe/hipe_bif2.c
@@ -193,5 +193,6 @@ BIF_RETTYPE hipe_bifs_llvm_fix_pinned_regs_0(BIF_ALIST_0)
BIF_RETTYPE hipe_bifs_build_stacktrace_1(BIF_ALIST_1)
{
+ BIF_P->ftrace = NIL;
BIF_RET(build_stacktrace(BIF_P, BIF_ARG_1));
}
diff --git a/erts/emulator/hipe/hipe_bif_list.m4 b/erts/emulator/hipe/hipe_bif_list.m4
index 7468860c37..77b5b2c275 100644
--- a/erts/emulator/hipe/hipe_bif_list.m4
+++ b/erts/emulator/hipe/hipe_bif_list.m4
@@ -290,6 +290,8 @@ noproc_primop_interface_1(nbif_atomic_inc, hipe_atomic_inc)
*/
gc_bif_interface_1(nbif_term_to_binary_1, hipe_wrapper_term_to_binary_1)
gc_bif_interface_2(nbif_term_to_binary_2, hipe_wrapper_term_to_binary_2)
+gc_bif_interface_1(nbif_term_to_iovec_1, hipe_wrapper_term_to_iovec_1)
+gc_bif_interface_2(nbif_term_to_iovec_2, hipe_wrapper_term_to_iovec_2)
gc_bif_interface_1(nbif_binary_to_term_1, hipe_wrapper_binary_to_term_1)
gc_bif_interface_2(nbif_binary_to_term_2, hipe_wrapper_binary_to_term_2)
gc_bif_interface_1(nbif_binary_to_list_1, hipe_wrapper_binary_to_list_1)
diff --git a/erts/emulator/hipe/hipe_debug.c b/erts/emulator/hipe/hipe_debug.c
index 138e4f7da3..2e34cfac59 100644
--- a/erts/emulator/hipe/hipe_debug.c
+++ b/erts/emulator/hipe/hipe_debug.c
@@ -232,7 +232,6 @@ void hipe_print_pcb(Process *p)
U("intial.fun ", u.initial.function);
U("intial.ari ", u.initial.arity);
U("current ", current);
- P("cp ", cp);
P("i ", i);
U("catches ", catches);
U("arity ", arity);
diff --git a/erts/emulator/hipe/hipe_instrs.tab b/erts/emulator/hipe/hipe_instrs.tab
index a01baebddf..8aa8544b2a 100644
--- a/erts/emulator/hipe/hipe_instrs.tab
+++ b/erts/emulator/hipe/hipe_instrs.tab
@@ -86,15 +86,14 @@ hipe_trap.post() {
switch( c_p->def_arg_reg[3] ) {
case HIPE_MODE_SWITCH_RES_RETURN:
ASSERT(is_value(reg[0]));
- SET_I(c_p->cp);
- c_p->cp = 0;
+ $RETURN();
Goto(*I);
case HIPE_MODE_SWITCH_RES_CALL_EXPORTED:
c_p->i = c_p->hipe.u.callee_exp->addressv[erts_active_code_ix()];
/*fall through*/
case HIPE_MODE_SWITCH_RES_CALL_BEAM:
SET_I(c_p->i);
- Dispatch();
+ $DISPATCH();
case HIPE_MODE_SWITCH_RES_CALL_CLOSURE:
/* This can be used to call any function value, but currently
it's only used to call closures referring to unloaded
@@ -105,13 +104,11 @@ hipe_trap.post() {
next = call_fun(c_p, c_p->arity - 1, reg, THE_NON_VALUE);
HEAVY_SWAPIN;
if (next != NULL) {
- SET_I(next);
- Dispatchfun();
+ $DISPATCH_FUN(next);
}
goto find_func_info;
}
case HIPE_MODE_SWITCH_RES_THROW:
- c_p->cp = NULL;
I = handle_error(c_p, I, reg, NULL);
goto post_error_handling;
default:
diff --git a/erts/emulator/hipe/hipe_mode_switch.c b/erts/emulator/hipe/hipe_mode_switch.c
index d46feb4de9..ccef9b9574 100644
--- a/erts/emulator/hipe/hipe_mode_switch.c
+++ b/erts/emulator/hipe/hipe_mode_switch.c
@@ -202,15 +202,13 @@ hipe_push_beam_trap_frame(Process *p, Eterm reg[], unsigned arity)
p->stop -= 2;
p->stop[1] = hipe_beam_catch_throw;
}
- p->stop[0] = make_cp(p->cp);
+ p->stop[0] = (BeamInstr) hipe_beam_pc_return;
++p->catches;
- p->cp = hipe_beam_pc_return;
}
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;
}
@@ -263,7 +261,7 @@ Process *hipe_mode_switch(Process *p, unsigned cmd, Eterm reg[])
unsigned arity = cmd >> 8;
/* p->hipe.u.ncallee set in beam_emu */
- if (p->cp == hipe_beam_pc_return) {
+ if (cp_val(p->stop[0]) == hipe_beam_pc_return) {
/* Native called BEAM, which now tailcalls native. */
hipe_pop_beam_trap_frame(p);
result = hipe_tailcall_to_native(p, arity, reg);
@@ -292,7 +290,7 @@ Process *hipe_mode_switch(Process *p, unsigned cmd, Eterm reg[])
/* just like a normal call from now on */
/* p->hipe.u.ncallee set in beam_emu */
- if (p->cp == hipe_beam_pc_return) {
+ if (cp_val(p->stop[0]) == hipe_beam_pc_return) {
/* Native called BEAM, which now tailcalls native. */
hipe_pop_beam_trap_frame(p);
result = hipe_tailcall_to_native(p, arity, reg);
diff --git a/erts/emulator/hipe/hipe_ppc_asm.m4 b/erts/emulator/hipe/hipe_ppc_asm.m4
index be25d65725..f4c90bcbab 100644
--- a/erts/emulator/hipe/hipe_ppc_asm.m4
+++ b/erts/emulator/hipe/hipe_ppc_asm.m4
@@ -270,7 +270,7 @@ dnl It will be a memory load via NSP when ARGNO >= NR_ARG_REGS.
dnl It will be a register move when 0 <= ARGNO < NR_ARG_REGS; if
dnl the source and destination are the same, the move is suppressed.
dnl
-define(NBIF_MOVE_REG,`ifelse($1,$2,`# mr $1, $2',`mr $1, $2')')dnl
+define(NBIF_MOVE_REG,`ifelse($1,$2,`// mr $1, $2',`mr $1, $2')')dnl
define(NBIF_REG_ARG,`NBIF_MOVE_REG($1,ARG$2)')dnl
define(NBIF_STK_LOAD,`LOAD $1, $2(NSP)')dnl
define(NBIF_STK_ARG,`NBIF_STK_LOAD($1,eval(WSIZE*(($2-$3)-1)))')dnl
diff --git a/erts/emulator/hipe/hipe_ppc_glue.S b/erts/emulator/hipe/hipe_ppc_glue.S
index 44351dc06c..15e734c414 100644
--- a/erts/emulator/hipe/hipe_ppc_glue.S
+++ b/erts/emulator/hipe/hipe_ppc_glue.S
@@ -39,14 +39,14 @@
* Usage: mflr r0 SEMI bl .enter
*/
.enter:
- # Our PPC64 ELF ABI frame must include:
- # - 48 (6*8) bytes for AIX-like linkage area
- # - 64 (8*8) bytes for AIX-like parameter area for
- # recursive C calls with up to 8 parameter words
- # - padding to make the frame a multiple of 16 bytes
- # - 144 (18*8) bytes for saving r14-r31
- # The final size is 256 bytes.
- # stdu is required for atomic alloc+init
+ /* Our PPC64 ELF ABI frame must include: */
+ /* - 48 (6*8) bytes for AIX-like linkage area */
+ /* - 64 (8*8) bytes for AIX-like parameter area for */
+ /* recursive C calls with up to 8 parameter words */
+ /* - padding to make the frame a multiple of 16 bytes */
+ /* - 144 (18*8) bytes for saving r14-r31 */
+ /* The final size is 256 bytes. */
+ /* stdu is required for atomic alloc+init */
stdu r1,-256(r1) /* 0(r1) contains r1+256 */
std r14, 112(r1)
std r15, 120(r1)
@@ -122,14 +122,14 @@
* Usage: mflr r0 SEMI bl .enter
*/
.enter:
- # A unified Linux/OSX C frame must include:
- # - 24 bytes for AIX/OSX-like linkage area
- # - 28 bytes for AIX/OSX-like parameter area for
- # recursive C calls with up to 7 parameter words
- # - 76 bytes for saving r14-r31 and LR
- # - padding to make it a multiple of 16 bytes
- # The final size is 128 bytes.
- # stwu is required for atomic alloc+init
+ /* A unified Linux/OSX C frame must include: */
+ /* - 24 bytes for AIX/OSX-like linkage area */
+ /* - 28 bytes for AIX/OSX-like parameter area for */
+ /* recursive C calls with up to 7 parameter words */
+ /* - 76 bytes for saving r14-r31 and LR */
+ /* - padding to make it a multiple of 16 bytes */
+ /* The final size is 128 bytes. */
+ /* stwu is required for atomic alloc+init */
stwu r1,-128(r1) /* 0(r1) contains r1+128 */
stw r14, 52(r1)
stw r15, 56(r1)
@@ -577,8 +577,8 @@ CSYM(nbif_4_simple_exception):
*/
GLOBAL(ASYM(nbif_stack_trap_ra))
ASYM(nbif_stack_trap_ra): /* a return address, not a function */
- # This only handles a single return value.
- # If we have more, we need to save them in the PCB.
+ /* This only handles a single return value. */
+ /* If we have more, we need to save them in the PCB. */
mr TEMP_ARG0, r3 /* save retval */
STORE NSP, P_NSP(P)
mr r3, P
@@ -597,8 +597,8 @@ ASYM(hipe_ppc_inc_stack):
mflr TEMP_ARG0
STORE NSP, P_NSP(P)
mr r3, P
- # hipe_inc_nstack reads and writes NSP and NSP_LIMIT,
- # but does not access LR/RA, HP, or FCALLS.
+ /* hipe_inc_nstack reads and writes NSP and NSP_LIMIT, */
+ /* but does not access LR/RA, HP, or FCALLS. */
bl CSYM(hipe_inc_nstack)
mtlr TEMP_ARG0
LOAD NSP, P_NSP(P)
diff --git a/erts/emulator/hipe/hipe_sparc_asm.m4 b/erts/emulator/hipe/hipe_sparc_asm.m4
index 8a9a516eab..2fcdf997b3 100644
--- a/erts/emulator/hipe/hipe_sparc_asm.m4
+++ b/erts/emulator/hipe/hipe_sparc_asm.m4
@@ -166,7 +166,7 @@ dnl It will be a memory load via NSP when ARGNO >= NR_ARG_REGS.
dnl It will be a register move when 0 <= ARGNO < NR_ARG_REGS; if
dnl the source and destination are the same, the move is suppressed.
dnl
-define(NBIF_MOVE_REG,`ifelse($1,$2,`! mov $2, $1',`mov $2, $1')')dnl
+define(NBIF_MOVE_REG,`ifelse($1,$2,`// mov $2, $1',`mov $2, $1')')dnl
define(NBIF_REG_ARG,`NBIF_MOVE_REG($1,ARG$2)')dnl
define(NBIF_STK_LOAD,`ld [NSP+$2], $1')dnl
define(NBIF_STK_ARG,`NBIF_STK_LOAD($1,eval(4*(($2-$3)-1)))')dnl
diff --git a/erts/emulator/hipe/hipe_x86_glue.S b/erts/emulator/hipe/hipe_x86_glue.S
index 8d6e377730..51bbb8098f 100644
--- a/erts/emulator/hipe/hipe_x86_glue.S
+++ b/erts/emulator/hipe/hipe_x86_glue.S
@@ -73,7 +73,7 @@ CSYM(x86_call_to_native):
* This is where native code returns to emulated code.
*/
ASYM(nbif_return):
- movl %eax, P_ARG0(P) # save retval
+ movl %eax, P_ARG0(P) /* save retval */
movl $HIPE_MODE_SWITCH_RES_RETURN, %eax
/* FALLTHROUGH to .flush_exit
*
@@ -93,9 +93,9 @@ ASYM(nbif_return):
SWITCH_ERLANG_TO_C_QUICK
/* restore C callee-save registers, drop frame, return */
movl 28(%esp), %edi
- movl 32(%esp), %esi # kills HP, if HP_IN_ESI is true
+ movl 32(%esp), %esi /* kills HP, if HP_IN_ESI is true */
movl 36(%esp), %ebx
- movl 40(%esp), %ebp # kills P
+ movl 40(%esp), %ebp /* kills P */
addl $44, %esp
ret
@@ -237,10 +237,10 @@ ASYM(nbif_suspend_msg):
ASYM(nbif_suspend_msg_timeout):
movl P_FLAGS(P), %eax
/* this relies on F_TIMO (1<<2) fitting in a byte */
- testb $F_TIMO, %al # F_TIMO set?
- jz .no_timeout # if not set, suspend
+ testb $F_TIMO, %al /* F_TIMO set? */
+ jz .no_timeout /* if not set, suspend */
/* timeout has occurred */
- xorl %eax, %eax # return 0 to signal timeout
+ xorl %eax, %eax /* return 0 to signal timeout */
NSP_RET0
.no_timeout:
movl $HIPE_MODE_SWITCH_RES_WAIT_TIMEOUT, %eax
@@ -285,7 +285,7 @@ CSYM(x86_tailcall_to_native):
CSYM(x86_throw_to_native):
ENTER_FROM_C
/* invoke the handler */
- jmp *P_NCALLEE(P) # set by hipe_find_handler()
+ jmp *P_NCALLEE(P) /* set by hipe_find_handler() */
/*
* This is the default exception handler for native code.
@@ -327,8 +327,8 @@ nbif_4_gc_after_bif:
subl $(32-4), %esp
movl P, (%esp)
movl %eax, 4(%esp)
- movl $0, 8(%esp) # Pass NULL in regs
- movl $0, 12(%esp) # Pass 0 in arity
+ movl $0, 8(%esp) /* Pass NULL in regs */
+ movl $0, 12(%esp) /* Pass 0 in arity */
call CSYM(erts_gc_after_bif_call)
addl $(32-4), %esp
movl $0, P_NARITY(P)
@@ -375,12 +375,12 @@ nbif_4_simple_exception:
*/
movl %eax, P_NARITY(P)
/* find and prepare to invoke the handler */
- SWITCH_ERLANG_TO_C_QUICK # The cached state is clean and need not be saved.
+ SWITCH_ERLANG_TO_C_QUICK /* The cached state is clean and need not be saved. */
movl P, (%esp)
- call CSYM(hipe_handle_exception) # Note: hipe_handle_exception() conses
- SWITCH_C_TO_ERLANG # %esp updated by hipe_find_handler()
+ call CSYM(hipe_handle_exception) /* Note: hipe_handle_exception() conses */
+ SWITCH_C_TO_ERLANG /* %esp updated by hipe_find_handler() */
/* now invoke the handler */
- jmp *P_NCALLEE(P) # set by hipe_find_handler()
+ jmp *P_NCALLEE(P) /* set by hipe_find_handler() */
/*
* A BIF failed with freason TRAP:
@@ -398,17 +398,17 @@ nbif_4_simple_exception:
*/
GLOBAL(ASYM(nbif_stack_trap_ra))
.align 4
-ASYM(nbif_stack_trap_ra): # a return address, not a function
- # This only handles a single return value.
- # If we have more, we need to save them in the PCB.
- movl %eax, TEMP_RV # save retval
+ASYM(nbif_stack_trap_ra): /* a return address, not a function */
+ /* This only handles a single return value. */
+ /* If we have more, we need to save them in the PCB. */
+ movl %eax, TEMP_RV /* save retval */
SWITCH_ERLANG_TO_C_QUICK
movl P, (%esp)
- call CSYM(hipe_handle_stack_trap) # must not cons; preserves TEMP_RV
- movl %eax, %edx # original RA
+ call CSYM(hipe_handle_stack_trap) /* must not cons; preserves TEMP_RV */
+ movl %eax, %edx /* original RA */
SWITCH_C_TO_ERLANG_QUICK
- movl TEMP_RV, %eax # restore retval
- jmp *%edx # resume at original RA
+ movl TEMP_RV, %eax /* restore retval */
+ jmp *%edx /* resume at original RA */
/*
* nbif_inc_stack_0
@@ -419,8 +419,8 @@ ASYM(nbif_inc_stack_0):
SWITCH_ERLANG_TO_C_QUICK
STORE_CALLER_SAVE
movl P, (%esp)
- # hipe_inc_nstack reads and writes NSP and NSP_LIMIT,
- # but does not access HP or FCALLS (or the non-x86 NRA).
+ /* hipe_inc_nstack reads and writes NSP and NSP_LIMIT, */
+ /* but does not access HP or FCALLS (or the non-x86 NRA). */
call CSYM(hipe_inc_nstack)
LOAD_CALLER_SAVE
SWITCH_C_TO_ERLANG_QUICK
diff --git a/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md b/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md
new file mode 100644
index 0000000000..e68a57a7ab
--- /dev/null
+++ b/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md
@@ -0,0 +1,62 @@
+Automatic Yielding of C Code
+============================
+
+Introduction
+------------
+
+Erlang [NIFs](http://erlang.org/doc/tutorial/nif.html) and
+[BIFs](http://erlang.org/pipermail/erlang-questions/2009-October/046899.html)
+should not run for a too long time without yielding (often referred to
+as trapping in the source code of ERTS). The Erlang/OTP system gets
+unresponsive, and some task may get prioritized unfairly if NIFs and
+BIFs occupy scheduler threads for a too long time. Therefore, the most
+commonly used NIFs and BIFs that may run for a long time can yield.
+
+Problems
+--------
+
+Erlang NIFs and BIFs are typically implemented in the C programming
+language. The C programming language does not have built-in support
+for automatic yielding in the middle of a routine (referred to as
+[coroutine support](https://en.wikipedia.org/wiki/Coroutine) in other
+programming languages). Therefore, most NIFs and BIFs implement
+yielding manually. Manual implementation of yielding has the advantage
+of giving the programmer control over what should be saved and when
+yielding should happen. Unfortunately, manual implementation of
+yielding also leads to code with a lot of boilerplate that is more
+difficult to read than corresponding code that does not
+yield. Furthermore, manual implementation of yielding can be
+time-consuming and error-prone, especially if the NIF or BIF is
+complicated.
+
+Solution
+--------
+
+A source-to-source transformer, called Yielding C Fun (YCF), has been
+created to make it easier to implement yielding NIFs and BIFs. YCF is
+a tool that takes a set of function names and a C source code file and
+transforms the functions with the given names in the source code file
+into yieldable versions that can be used as coroutines. YCF has been
+created with yielding NIFs and BIFs in mind and has several features
+that can be handy when implementing yielding NIFs and BIFs. The reader
+is recommended to look at YCF's documentation for a detailed
+description of YCF.
+
+Yielding C Fun's Source Code and Documentation
+----------------------------------------------
+
+The source code of YCF is included in the folder
+`"$ERL_TOP"/erts/lib_src/yielding_c_fun/` inside the source tree of
+the Erlang/OTP system. The documentation of YCF can be found in
+`"$ERL_TOP"/erts/lib_src/yielding_c_fun/README.md`. A rendered version
+of YCF documentation can be found
+[here](https://github.com/erlang/otp/erts/lib_src/yielding_c_fun/README.md).
+
+Yielding C Fun in the Erlang Run-time System
+-------------------------------------------
+
+YCF is used to implement yielding in the BIFs for the `ets:insert/2`
+and `ets:insert_new/2` functions when these two functions get a list
+as their second parameter. The implementation of these two functions
+is a good starting point if one wants to use YCF to implement
+something else inside ERTS.
diff --git a/erts/emulator/internal_doc/NewLinking.tla b/erts/emulator/internal_doc/NewLinking.tla
new file mode 100644
index 0000000000..832bbaf037
--- /dev/null
+++ b/erts/emulator/internal_doc/NewLinking.tla
@@ -0,0 +1,194 @@
+\*
+\* %CopyrightBegin%
+\*
+\* Copyright Ericsson AB 2021. All Rights Reserved.
+\*
+\* Licensed under the Apache License, Version 2.0 (the "License");
+\* you may not use this file except in compliance with the License.
+\* You may obtain a copy of the License at
+\*
+\* http://www.apache.org/licenses/LICENSE-2.0
+\*
+\* Unless required by applicable law or agreed to in writing, software
+\* distributed under the License is distributed on an "AS IS" BASIS,
+\* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+\* See the License for the specific language governing permissions and
+\* limitations under the License.
+\*
+\* %CopyrightEnd%
+\*
+
+(*
+ --- Model of the new link protocol introduced in Erlang/OTP 23.3 ---
+
+ The protocol is documented in the ERTS User's Guide ->
+ Distribution Protocol -> Protocol Between Connected Nodes ->
+ Link Protocol -> New Link Protocol
+
+ This model only models a link between two processes. This since a link
+ between one pair of processes is completely independent of links between
+ other pairs of processes. This model also assumes that the connection
+ between the processes does not fail. In the real world a connection can
+ of course fail. This is however taken care of by clearing the link
+ information on both ends when a connection fails, and tracking which
+ instantiation of a connection between the nodes signals arrive on and
+ ignoring signals from old instantiations of connections. That is,
+ connection loss is trivially taken care of since we just start over
+ again from scratch if the connection is lost.
+
+ The documentation of the protocol talks about "process local information
+ about links". This information is stored in the process state record below.
+ The 'other' field contains the identifier of the other process. The 'type'
+ field acts as "active flag". The link is active when 'type' equals
+ "linked" and not active when 'type' equals "unlinked". If the
+ 'wait_unlink_ack' field contains a value larger than or equal to zero
+ it is the "unlink id" of an unlink request we have issued and are waiting
+ for to get acknowledged. If the 'wait_unlink_ack' field contains -1 we
+ are not waiting for an acknowledgment. When 'type' equals "unlinked" and
+ 'wait_unlink_ack' equals -1, we would in the documented protocol have
+ removed the "process local information about the link". In this model we,
+ however, keep the state, but in this state instead of removing it.
+ Messages are tagged with a message number in order to model the signal
+ order of Erlang. The message number of the unlink signal is also used as
+ "unlink id".
+
+ The model has been checked with the following parameters:
+
+ Declared constants:
+ PROCS <- {"a", "b"}
+
+ Temporal formula:
+ FairSpec
+
+ Deadlock:
+ disabled
+
+ Invariants:
+ TypeOK
+ ValidState
+
+ State Constraint:
+ /\ \E proc \in PROCS : procState[proc].next_send =< 15
+ /\ Cardinality(msgs) =< 10
+
+ That is, we have checked all states where processes send up to 15 signals
+ with at most 10 outstanding signals.
+
+ Deadlock checking has been disabled since we intentionally stop when
+ we have no outstanding signals (in 'Next') in order to avoid checking
+ signal sequences equivalent to sequences we already have checked.
+
+*)
+------------------------------ MODULE NewLinking ------------------------------
+EXTENDS Integers, TLC, FiniteSets
+CONSTANTS PROCS \* PROCS should be a set of exactly two process names
+
+VARIABLES
+ procState, \* Set of process states; procState[proc] is state of proc
+ msgs \* Set of messages sent
+
+vars == <<procState, msgs>>
+
+\* Set of possible process states...
+procStateRec ==
+ [self : PROCS,
+ other: PROCS,
+ type : {"linked", "unlinked"},
+ wait_unlink_ack : Nat \cup {-1},
+ next_send : Nat,
+ next_recv : Nat]
+
+\* Set of possible messages...
+Messages ==
+ [type : {"link", "unlink", "unlink_ack"}, from : PROCS, to : PROCS, msg_no : Nat, ack : Nat \cup {-1}]
+
+TypeOK ==
+ /\ procState \in [PROCS -> procStateRec]
+ /\ msgs \subseteq Messages
+
+Init ==
+ /\ msgs = {}
+ /\ procState = [p \in PROCS |-> [self |-> p,
+ other |-> CHOOSE p2 \in PROCS : p2 /= p,
+ type |-> "unlinked",
+ next_send |-> 0,
+ next_recv |-> 0,
+ wait_unlink_ack |-> -1]]
+
+MkMsg(self, mtype, accnr) ==
+ [type |-> mtype,
+ from |-> procState[self].self,
+ to |-> procState[self].other,
+ msg_no |-> procState[self].next_send,
+ ack |-> accnr]
+
+Link(self) ==
+ /\ procState[self].type = "unlinked"
+ /\ msgs' = msgs \cup {MkMsg(self, "link", -1)}
+ /\ procState' = [procState EXCEPT ![self].type = "linked",
+ ![self].next_send = @ + 1,
+ ![self].wait_unlink_ack = -1]
+
+Unlink(self) ==
+ /\ procState[self].type = "linked"
+ /\ msgs' = msgs \cup {MkMsg(self, "unlink", -1)}
+ /\ procState' = [procState EXCEPT ![self].type = "unlinked",
+ ![self].next_send = @ + 1,
+ ![self].wait_unlink_ack = procState[self].next_send]
+
+RecvLink(self, msg) ==
+ LET type == IF procState[self].wait_unlink_ack /= -1
+ THEN "unlinked"
+ ELSE "linked"
+ IN /\ msgs' = msgs \ {msg}
+ /\ procState' = [procState EXCEPT ![self].type = type,
+ ![self].next_recv = @ + 1]
+
+RecvUnlink(self, msg) ==
+ /\ msgs' = (msgs \ {msg}) \cup {MkMsg(self,
+ "unlink_ack",
+ procState[self].next_recv)}
+ /\ procState' = [procState EXCEPT ![self].type = "unlinked",
+ ![self].next_recv = @ + 1,
+ ![self].next_send = @ + 1]
+
+RecvUnlinkAck(self, msg) ==
+ LET wack == IF procState[self].wait_unlink_ack = msg.ack
+ THEN -1
+ ELSE procState[self].wait_unlink_ack
+ IN /\ msgs' = msgs \ {msg}
+ /\ procState' = [procState EXCEPT ![self].next_recv = @ + 1,
+ ![self].wait_unlink_ack = wack]
+
+Recv(self) ==
+ /\ \E m \in msgs : /\ m.to = self
+ /\ m.msg_no = procState[self].next_recv
+ /\ LET msg == CHOOSE m \in msgs : /\ m.to = self
+ /\ m.msg_no = procState[self].next_recv
+ IN CASE msg.type = "link" -> RecvLink(self, msg)
+ [] msg.type = "unlink" -> RecvUnlink(self, msg)
+ [] msg.type = "unlink_ack" -> RecvUnlinkAck(self, msg)
+
+(*
+ If we have no outstanding messages; both processes should
+ have the same view about whether they are linked or not...
+*)
+ValidState ==
+ IF msgs /= {}
+ THEN TRUE
+ ELSE \A p \in PROCS : \A p2 \in PROCS : procState[p].type = procState[p2].type
+
+Next ==
+ /\ (msgs /= {} \/ \A p \in PROCS : procState[p].next_send = 0)
+ /\ \E p \in PROCS : \/ Recv(p)
+ \/ Link(p)
+ \/ Unlink(p)
+
+Spec == Init /\ [][Next]_vars
+
+FairSpec == Spec /\ WF_vars(Next)
+
+=============================================================================
+\* Modification History
+\* Last modified Mon Jan 25 11:26:06 CET 2021 by rickard.green
+\* Created Wed Jan 20 13:11:46 CET 2021 by rickard.green
diff --git a/erts/emulator/internal_doc/beam_makeops.md b/erts/emulator/internal_doc/beam_makeops.md
index 2880099b70..1ec94d4ff9 100644
--- a/erts/emulator/internal_doc/beam_makeops.md
+++ b/erts/emulator/internal_doc/beam_makeops.md
@@ -1087,12 +1087,14 @@ use as a temporary X register.
* `y` - Y register. The default value is 0.
-* `l` - Foating point register number. The default value is 0.
+* `l` - Floating point register number. The default value is 0.
* `i` - Tagged literal integer. The default value is 0.
* `a` - Tagged atom. The default value is the empty atom (`am_Empty`).
+* `p` - Zero failure label.
+
* `n` - NIL (`[]`, the empty list).
#### Function call on the right side ####
@@ -1457,26 +1459,26 @@ all instructions. It expands to the address of the next instruction.
Here is an example:
i_call(CallDest) {
- SET_CP(c_p, $NEXT_INSTRUCTION);
+ //| -no_next
+ $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
$DISPATCH_REL($CallDest);
}
-When calling a function, the return address is first stored in `c_p->cp`
-(using the `SET_CP()` macro defined in `beam_emu.c`), and then control is
+When calling a function, the return address is first stored in `E[0]`
+(using the `$SAVE_CONTINUATION_POINTER()` macro), and then control is
transferred to the callee. Here is the generated code:
OpCase(i_call_f):
{
- SET_CP(c_p, I+1);
- ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
- I += fb(BeamExtraData(I[0])) + 0;;
- DTRACE_LOCAL_CALL(c_p, erts_code_to_codemfa(I));
- Dispatch();;
+ ASSERT(VALID_INSTR(*(I+2)));
+ *E = (BeamInstr) (I+2);;
+
+ /* ... dispatch code intentionally left out ... */
}
-We can see that that `$NEXT_INSTRUCTION` has been expanded to `I+1`.
+We can see that that `$NEXT_INSTRUCTION` has been expanded to `I+2`.
That makes sense since the size of the `i_call_f/1` instruction is
-one word.
+two words.
##### The IP_ADJUSTMENT pre-bound variable #####
diff --git a/erts/emulator/nifs/common/prim_file_nif.c b/erts/emulator/nifs/common/prim_file_nif.c
index 0dc869a83f..d26be2bc3e 100644
--- a/erts/emulator/nifs/common/prim_file_nif.c
+++ b/erts/emulator/nifs/common/prim_file_nif.c
@@ -162,6 +162,7 @@ WRAP_FILE_HANDLE_EXPORT(allocate_nif)
WRAP_FILE_HANDLE_EXPORT(advise_nif)
WRAP_FILE_HANDLE_EXPORT(get_handle_nif)
WRAP_FILE_HANDLE_EXPORT(ipread_s32bu_p32bu_nif)
+WRAP_FILE_HANDLE_EXPORT(read_handle_info_nif)
static ErlNifFunc nif_funcs[] = {
/* File handle ops */
@@ -176,6 +177,7 @@ static ErlNifFunc nif_funcs[] = {
{"truncate_nif", 1, truncate_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"allocate_nif", 3, allocate_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"advise_nif", 4, advise_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"read_handle_info_nif", 1, read_handle_info_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
/* Filesystem ops */
{"make_hard_link_nif", 2, make_hard_link_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
@@ -231,6 +233,7 @@ static int load(ErlNifEnv *env, void** priv_data, ERL_NIF_TERM prim_file_pid)
am_append = enif_make_atom(env, "append");
am_sync = enif_make_atom(env, "sync");
am_skip_type_check = enif_make_atom(env, "skip_type_check");
+ am_directory = enif_make_atom(env, "directory");
am_read_write = enif_make_atom(env, "read_write");
am_none = enif_make_atom(env, "none");
@@ -447,6 +450,8 @@ static enum efile_modes_t efile_translate_modelist(ErlNifEnv *env, ERL_NIF_TERM
modes |= EFILE_MODE_SYNC;
} else if(enif_is_identical(head, am_skip_type_check)) {
modes |= EFILE_MODE_SKIP_TYPE_CHECK;
+ } else if (enif_is_identical(head, am_directory)) {
+ modes |= EFILE_MODE_DIRECTORY;
} else {
/* Modes like 'raw', 'ram', 'delayed_writes' etc are handled
* further up the chain. */
@@ -893,6 +898,26 @@ static ERL_NIF_TERM get_handle_nif_impl(efile_data_t *d, ErlNifEnv *env, int arg
return efile_get_handle(env, d);
}
+static ERL_NIF_TERM build_file_info(ErlNifEnv *env, efile_fileinfo_t *info) {
+ /* #file_info as declared in file.hrl */
+ return enif_make_tuple(env, 14,
+ am_file_info,
+ enif_make_uint64(env, info->size),
+ efile_filetype_to_atom(info->type),
+ efile_access_to_atom(info->access),
+ enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info->a_time)),
+ enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info->m_time)),
+ enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info->c_time)),
+ enif_make_uint(env, info->mode),
+ enif_make_uint(env, info->links),
+ enif_make_uint(env, info->major_device),
+ enif_make_uint(env, info->minor_device),
+ enif_make_uint(env, info->inode),
+ enif_make_uint(env, info->uid),
+ enif_make_uint(env, info->gid)
+ );
+}
+
static ERL_NIF_TERM read_info_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
posix_errno_t posix_errno;
@@ -911,23 +936,20 @@ static ERL_NIF_TERM read_info_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM a
return posix_error_to_tuple(env, posix_errno);
}
- /* #file_info as declared in file.hrl */
- return enif_make_tuple(env, 14,
- am_file_info,
- enif_make_uint64(env, info.size),
- efile_filetype_to_atom(info.type),
- efile_access_to_atom(info.access),
- enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info.a_time)),
- enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info.m_time)),
- enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info.c_time)),
- enif_make_uint(env, info.mode),
- enif_make_uint(env, info.links),
- enif_make_uint(env, info.major_device),
- enif_make_uint(env, info.minor_device),
- enif_make_uint(env, info.inode),
- enif_make_uint(env, info.uid),
- enif_make_uint(env, info.gid)
- );
+ return build_file_info(env, &info);
+}
+
+static ERL_NIF_TERM read_handle_info_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+ efile_fileinfo_t info = {0};
+
+ ASSERT(argc == 0);
+
+ if((posix_errno = efile_read_handle_info(d, &info))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return build_file_info(env, &info);
}
static ERL_NIF_TERM set_permissions_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
@@ -1244,12 +1266,16 @@ static ERL_NIF_TERM read_file_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM a
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
- } else if((posix_errno = efile_read_info(&path, 1, &info))) {
- return posix_error_to_tuple(env, posix_errno);
} else if((posix_errno = efile_open(&path, EFILE_MODE_READ, efile_resource_type, &d))) {
return posix_error_to_tuple(env, posix_errno);
}
+ /* read_file() wants to know the file size, so retrieve it now from the
+ open file handle. In theory, efile_read_handle_info() may fail with
+ ENOTSUP, fall back to the "unknown size" logic if that happens. */
+ if (efile_read_handle_info(d, &info) != 0) {
+ info.size = 0;
+ }
posix_errno = read_file(d, info.size, &result);
erts_atomic32_set_acqb(&d->state, EFILE_STATE_CLOSED);
diff --git a/erts/emulator/nifs/common/prim_file_nif.h b/erts/emulator/nifs/common/prim_file_nif.h
index b2e30c59dd..1cf5d52192 100644
--- a/erts/emulator/nifs/common/prim_file_nif.h
+++ b/erts/emulator/nifs/common/prim_file_nif.h
@@ -30,6 +30,8 @@ enum efile_modes_t {
EFILE_MODE_SKIP_TYPE_CHECK = (1 << 5), /* Special for device files on Unix. */
EFILE_MODE_NO_TRUNCATE = (1 << 6), /* Special for reopening on VxWorks. */
+ EFILE_MODE_DIRECTORY = (1 << 7),
+
EFILE_MODE_READ_WRITE = EFILE_MODE_READ | EFILE_MODE_WRITE
};
@@ -110,7 +112,7 @@ typedef struct {
typedef ErlNifBinary efile_path_t;
-/* @brief Translates the given "raw name" into the format expected by the APIs
+/** @brief Translates the given "raw name" into the format expected by the APIs
* used by the underlying implementation. The result is transient and does not
* need to be released.
*
@@ -121,30 +123,30 @@ typedef ErlNifBinary efile_path_t;
* prim_file:internal_native2name for compatibility reasons. */
posix_errno_t efile_marshal_path(ErlNifEnv *env, ERL_NIF_TERM path, efile_path_t *result);
-/* @brief Returns the underlying handle as an implementation-defined term.
+/** @brief Returns the underlying handle as an implementation-defined term.
*
* This is an internal function intended to support tests and tricky
* operations like sendfile(2). */
ERL_NIF_TERM efile_get_handle(ErlNifEnv *env, efile_data_t *d);
-/* @brief Read until EOF or the given iovec has been filled.
+/** @brief Read until EOF or the given iovec has been filled.
*
* @return -1 on failure, or the number of bytes read on success. The return
* value will be 0 if no bytes could be read before EOF or the end of the
* iovec. */
Sint64 efile_readv(efile_data_t *d, SysIOVec *iov, int iovlen);
-/* @brief Write the entirety of the given iovec.
+/** @brief Write the entirety of the given iovec.
*
* @return -1 on failure, or the number of bytes written on success. "Partial"
* failures will be reported with -1 and not the number of bytes we managed to
* write to disk before the failure. */
Sint64 efile_writev(efile_data_t *d, SysIOVec *iov, int iovlen);
-/* @brief As \c efile_readv, but starting from a file offset. */
+/** @brief As \c efile_readv, but starting from a file offset. */
Sint64 efile_preadv(efile_data_t *d, Sint64 offset, SysIOVec *iov, int iovlen);
-/* @brief As \c efile_writev, but starting from a file offset. */
+/** @brief As \c efile_writev, but starting from a file offset. */
Sint64 efile_pwritev(efile_data_t *d, Sint64 offset, SysIOVec *iov, int iovlen);
int efile_seek(efile_data_t *d, enum efile_seek_t seek, Sint64 offset, Sint64 *new_position);
@@ -168,6 +170,7 @@ int efile_close(efile_data_t *d, posix_errno_t *error);
/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */
posix_errno_t efile_read_info(const efile_path_t *path, int follow_link, efile_fileinfo_t *result);
+posix_errno_t efile_read_handle_info(efile_data_t *d, efile_fileinfo_t *result);
/** @brief Sets the file times to the given values. Refer to efile_fileinfo_t
* for a description of each. */
diff --git a/erts/emulator/nifs/common/prim_net_nif.c b/erts/emulator/nifs/common/prim_net_nif.c
index b8a559a5b8..689dbd112a 100644
--- a/erts/emulator/nifs/common/prim_net_nif.c
+++ b/erts/emulator/nifs/common/prim_net_nif.c
@@ -65,6 +65,14 @@
#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>
+#elif defined(__PASE__)
+/* PASE has this, but under a different name because AIX doesn't have it. */
+#include <as400_protos.h>
+/*
+ * We don't redefine the function names because they're used in other
+ * contexts, but the struct is safe to rename.
+ */
+#define ifaddrs ifaddrs_pase
#endif
#ifdef HAVE_NETPACKET_PACKET_H
@@ -278,22 +286,42 @@ ENET_NIF_FUNCS
/* And here comes the functions that does the actual work (for the most part) */
static ERL_NIF_TERM enet_command(ErlNifEnv* env,
ERL_NIF_TERM cmd);
+#if defined(HAVE_GETHOSTNAME)
static ERL_NIF_TERM enet_gethostname(ErlNifEnv* env);
+#endif
+
+#if defined(HAVE_GETNAMEINFO)
static ERL_NIF_TERM enet_getnameinfo(ErlNifEnv* env,
const ESockAddress* saP,
SOCKLEN_T saLen,
int flags);
+#endif
+
+#if defined(HAVE_GETADDRINFO)
static ERL_NIF_TERM enet_getaddrinfo(ErlNifEnv* env,
char* host,
char* serv);
+#endif
+
+#if defined(HAVE_GETIFADDRS) || defined(__PASE__)
static ERL_NIF_TERM enet_getifaddrs(ErlNifEnv* env,
char* netns);
+#endif
+
+#if defined(HAVE_IF_NAMETOINDEX)
static ERL_NIF_TERM enet_if_name2index(ErlNifEnv* env,
char* ifn);
+#endif
+
+#if defined(HAVE_IF_INDEXTONAME)
static ERL_NIF_TERM enet_if_index2name(ErlNifEnv* env,
unsigned int id);
+#endif
+
+#if defined(HAVE_IF_NAMEINDEX) && defined(HAVE_IF_FREENAMEINDEX)
static ERL_NIF_TERM enet_if_names(ErlNifEnv* env);
static unsigned int enet_if_names_length(struct if_nameindex* p);
+#endif
/*
static void net_dtor(ErlNifEnv* env, void* obj);
@@ -348,8 +376,8 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env,
const ERL_NIF_TERM eString,
char** stringP);
static ERL_NIF_TERM decode_bool(ErlNifEnv* env,
- ERL_NIF_TERM eBool,
- BOOLEAN_T* bool);
+ ERL_NIF_TERM ebool,
+ BOOLEAN_T* ibool);
static ERL_NIF_TERM encode_address_infos(ErlNifEnv* env,
struct addrinfo* addrInfo);
static ERL_NIF_TERM encode_address_info(ErlNifEnv* env,
@@ -609,7 +637,7 @@ ERL_NIF_TERM nif_gethostname(ErlNifEnv* env,
{
#if defined(__WIN32__)
return enif_raise_exception(env, MKA(env, "notsup"));
-#else
+#elif defined(HAVE_GETHOSTNAME)
ERL_NIF_TERM result;
NDBG( ("NET", "nif_gethostname -> entry (%d)\r\n", argc) );
@@ -622,11 +650,13 @@ ERL_NIF_TERM nif_gethostname(ErlNifEnv* env,
NDBG( ("NET", "nif_gethostname -> done when result: %T\r\n", result) );
return result;
+#else
+ return esock_make_error(env, esock_atom_enotsup);
#endif
}
-#if !defined(__WIN32__)
+#if !defined(__WIN32__) && defined(HAVE_GETHOSTNAME)
static
ERL_NIF_TERM enet_gethostname(ErlNifEnv* env)
{
@@ -685,7 +715,7 @@ ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env,
{
#if defined(__WIN32__)
return enif_raise_exception(env, MKA(env, "notsup"));
-#else
+#elif defined(HAVE_GETNAMEINFO)
ERL_NIF_TERM result;
ERL_NIF_TERM eSockAddr, eFlags;
int flags = 0; // Just in case...
@@ -723,6 +753,8 @@ ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env,
"\r\n %T\r\n", result) );
return result;
+#else
+ return esock_make_error(env, esock_atom_enotsup);
#endif
}
@@ -731,7 +763,7 @@ ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env,
/* Given the provided sock(et) address (and flags), retreive the host and
* service info.
*/
-#if !defined(__WIN32__)
+#if !defined(__WIN32__) && defined(HAVE_GETNAMEINFO)
static
ERL_NIF_TERM enet_getnameinfo(ErlNifEnv* env,
const ESockAddress* saP,
@@ -847,7 +879,7 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env,
{
#if defined(__WIN32__)
return enif_raise_exception(env, MKA(env, "notsup"));
-#else
+#elif defined(HAVE_GETADDRINFO)
ERL_NIF_TERM result, eHostName, eServName; //, eHints;
char* hostName;
char* servName;
@@ -901,11 +933,13 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env,
"\r\n %T\r\n", result) );
return result;
+#else
+ return esock_make_error(env, esock_atom_enotsup);
#endif
}
-#if !defined(__WIN32__)
+#if !defined(__WIN32__) && defined(HAVE_GETADDRINFO)
static
ERL_NIF_TERM enet_getaddrinfo(ErlNifEnv* env,
char* host,
@@ -1030,8 +1064,10 @@ ERL_NIF_TERM nif_getifaddrs(ErlNifEnv* env,
{
#if defined(__WIN32__)
return enif_raise_exception(env, MKA(env, "notsup"));
-#elif defined(HAVE_GETIFADDRS)
+#elif defined(HAVE_GETIFADDRS) || defined(__PASE__)
+#ifdef HAVE_SETNS
ERL_NIF_TERM extra;
+#endif
char* netns;
ERL_NIF_TERM result;
@@ -1041,7 +1077,10 @@ ERL_NIF_TERM nif_getifaddrs(ErlNifEnv* env,
!IS_MAP(env, argv[0])) {
return enif_make_badarg(env);
}
+#ifdef HAVE_SETNS
extra = argv[0];
+#endif
+
#ifdef HAVE_SETNS
/* We *currently* only support one extra option: netns */
@@ -1066,7 +1105,7 @@ ERL_NIF_TERM nif_getifaddrs(ErlNifEnv* env,
}
-#ifdef HAVE_GETIFADDRS
+#if defined(HAVE_GETIFADDRS) || defined(__PASE__)
#ifdef HAVE_SETNS
/* enet_getifaddrs_netns - extract the netns field from the 'extra' map
*
@@ -1146,9 +1185,17 @@ ERL_NIF_TERM enet_getifaddrs(ErlNifEnv* env, char* netns)
return esock_make_error_errno(env, save_errno);
#endif
+#ifdef __PASE__
+ if (0 == Qp2getifaddrs(&ifap)) {
+#else
if (0 == getifaddrs(&ifap)) {
+#endif
result = enet_getifaddrs_process(env, ifap);
+#ifdef __PASE__
+ Qp2freeifaddrs(ifap);
+#else
freeifaddrs(ifap);
+#endif
} else {
save_errno = get_errno();
@@ -1541,7 +1588,7 @@ ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env,
{
#if defined(__WIN32__)
return enif_raise_exception(env, MKA(env, "notsup"));
-#else
+#elif defined(HAVE_IF_NAMETOINDEX)
ERL_NIF_TERM eifn, result;
char ifn[IF_NAMESIZE+1];
@@ -1565,12 +1612,14 @@ ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env,
NDBG( ("NET", "nif_if_name2index -> done when result: %T\r\n", result) );
return result;
+#else
+ return esock_make_error(env, esock_atom_enotsup);
#endif
}
-#if !defined(__WIN32__)
+#if !defined(__WIN32__) && defined(HAVE_IF_NAMETOINDEX)
static
ERL_NIF_TERM enet_if_name2index(ErlNifEnv* env,
char* ifn)
@@ -1613,7 +1662,7 @@ ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env,
{
#if defined(__WIN32__)
return enif_raise_exception(env, MKA(env, "notsup"));
-#else
+#elif defined(HAVE_IF_INDEXTONAME)
ERL_NIF_TERM result;
unsigned int idx;
@@ -1633,12 +1682,14 @@ ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env,
NDBG( ("NET", "nif_if_index2name -> done when result: %T\r\n", result) );
return result;
+#else
+ return esock_make_error(env, esock_atom_enotsup);
#endif
}
-#if !defined(__WIN32__)
+#if !defined(__WIN32__) && defined(HAVE_IF_INDEXTONAME)
static
ERL_NIF_TERM enet_if_index2name(ErlNifEnv* env,
unsigned int idx)
@@ -1676,9 +1727,9 @@ ERL_NIF_TERM nif_if_names(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
-#if defined(__WIN32__)
+#if defined(__WIN32__) || (defined(__ANDROID__) && (__ANDROID_API__ < 24))
return enif_raise_exception(env, MKA(env, "notsup"));
-#else
+#elif defined(HAVE_IF_NAMEINDEX) && defined(HAVE_IF_FREENAMEINDEX)
ERL_NIF_TERM result;
NDBG( ("NET", "nif_if_names -> entry (%d)\r\n", argc) );
@@ -1692,12 +1743,19 @@ ERL_NIF_TERM nif_if_names(ErlNifEnv* env,
NDBG( ("NET", "nif_if_names -> done when result: %T\r\n", result) );
return result;
+#else
+ return esock_make_error(env, esock_atom_enotsup);
#endif
}
-#if !defined(__WIN32__)
+/* if_nameindex and if_freenameindex were added in Android 7.0 Nougat. With
+the Android NDK Unified Headers, check that the build is targeting at least
+the corresponding API level 24. */
+/* Can we replace the ANDROID tests with the HAVE_... ? */
+#if !defined(__WIN32__) && !(defined(__ANDROID__) && (__ANDROID_API__ < 24))
+#if defined(HAVE_IF_NAMEINDEX) && defined(HAVE_IF_FREENAMEINDEX)
static
ERL_NIF_TERM enet_if_names(ErlNifEnv* env)
{
@@ -1770,7 +1828,8 @@ unsigned int enet_if_names_length(struct if_nameindex* p)
return len;
}
-#endif // if !defined(__WIN32__)
+#endif // if defined(HAVE_IF_NAMEINDEX) && defined(HAVE_IF_FREENAMEINDEX)
+#endif // if !defined(__WIN32__) && ...
@@ -1931,14 +1990,14 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env,
static
ERL_NIF_TERM decode_bool(ErlNifEnv* env,
- ERL_NIF_TERM eBool,
- BOOLEAN_T* bool)
+ ERL_NIF_TERM ebool,
+ BOOLEAN_T* ibool)
{
- if (COMPARE(eBool, esock_atom_true) == 0) {
- *bool = TRUE;
+ if (COMPARE(ebool, esock_atom_true) == 0) {
+ *ibool = TRUE;
return esock_atom_ok;
- } else if (COMPARE(eBool, esock_atom_false) == 0) {
- *bool = FALSE;
+ } else if (COMPARE(ebool, esock_atom_false) == 0) {
+ *ibool = FALSE;
return esock_atom_ok;
} else {
return esock_make_error(env, esock_atom_einval);
diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/prim_socket_nif.c
index fbda73d518..f9d535e15b 100644
--- a/erts/emulator/nifs/common/socket_nif.c
+++ b/erts/emulator/nifs/common/prim_socket_nif.c
@@ -1,4 +1,4 @@
-/*
+ /*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2018-2020. All Rights Reserved.
@@ -368,7 +368,7 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL;
#define ESOCK_GLOBAL_DEBUG_DEFAULT FALSE
#define ESOCK_DEBUG_DEFAULT FALSE
-/* Counters and stuff (Don't know where to sen2 this stuff anyway) */
+/* Counters and stuff (Don't know where to send this stuff anyway) */
#define ESOCK_NIF_IOW_DEFAULT FALSE
@@ -423,70 +423,28 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL;
-/* *** Socket state defs *** */
-
-#define ESOCK_FLAG_OPEN 0x0001
-#define ESOCK_FLAG_ACTIVE 0x0004
-#define ESOCK_FLAG_LISTEN 0x0008
-#define ESOCK_FLAG_CON 0x0010
-#define ESOCK_FLAG_ACC 0x0020
-#define ESOCK_FLAG_BUSY 0x0040
-#define ESOCK_FLAG_CLOSE 0x0080
+/* *** Socket state defs, debug only *** */
-#define ESOCK_STATE_CLOSED (0)
-#define ESOCK_STATE_OPEN (ESOCK_FLAG_OPEN)
-#define ESOCK_STATE_CONNECTED (ESOCK_STATE_OPEN | ESOCK_FLAG_ACTIVE)
-#define ESOCK_STATE_LISTENING (ESOCK_STATE_OPEN | ESOCK_FLAG_LISTEN)
-#define ESOCK_STATE_CONNECTING (ESOCK_STATE_OPEN | ESOCK_FLAG_CON)
-#define ESOCK_STATE_ACCEPTING (ESOCK_STATE_LISTENING | ESOCK_FLAG_ACC)
-#define ESOCK_STATE_CLOSING (ESOCK_FLAG_CLOSE)
-#define ESOCK_STATE_DTOR (0xFFFF)
+#define ESOCK_STATE_BOUND 0x0001 /* readState */
+#define ESOCK_STATE_LISTENING 0x0002 /* readState */
+#define ESOCK_STATE_ACCEPTING 0x0004 /* readState */
+#define ESOCK_STATE_CONNECTING 0x0010 /* writeState */
+#define ESOCK_STATE_CONNECTED 0x0020 /* writeState */
+#define ESOCK_STATE_DTOR 0x8000
#define IS_CLOSED(d) \
- ((d)->state == ESOCK_STATE_CLOSED)
-
-/*
-#define IS_STATE(d, f) \
- (((d)->state & (f)) == (f))
-*/
-
-#define IS_CLOSING(d) \
- (((d)->state & ESOCK_STATE_CLOSING) == ESOCK_STATE_CLOSING)
+ ((d)->sock == INVALID_SOCKET)
-#define IS_OPEN(d) \
- (((d)->state & ESOCK_FLAG_OPEN) == ESOCK_FLAG_OPEN)
+#define IS_CLOSING(d) \
+ ((d)->closing)
-#define IS_CONNECTED(d) \
- (((d)->state & ESOCK_STATE_CONNECTED) == ESOCK_STATE_CONNECTED)
-
-#define IS_CONNECTING(d) \
- (((d)->state & ESOCK_FLAG_CON) == ESOCK_FLAG_CON)
-
-/*
-#define IS_BUSY(d) \
- (((d)->state & ESOCK_FLAG_BUSY) == ESOCK_FLAG_BUSY)
-*/
+#define IS_OPEN(d) \
+ (! (IS_CLOSED(d) || IS_CLOSING(d)) )
+
#define ESOCK_GET_RESOURCE(ENV, REF, RES) \
enif_get_resource((ENV), (REF), esocks, (RES))
-#define ESOCK_SEND_FLAG_CONFIRM 0
-#define ESOCK_SEND_FLAG_DONTROUTE 1
-#define ESOCK_SEND_FLAG_EOR 2
-#define ESOCK_SEND_FLAG_MORE 3
-#define ESOCK_SEND_FLAG_NOSIGNAL 4
-#define ESOCK_SEND_FLAG_OOB 5
-#define ESOCK_SEND_FLAG_LOW ESOCK_SEND_FLAG_CONFIRM
-#define ESOCK_SEND_FLAG_HIGH ESOCK_SEND_FLAG_OOB
-
-#define ESOCK_RECV_FLAG_CMSG_CLOEXEC 0
-#define ESOCK_RECV_FLAG_ERRQUEUE 1
-#define ESOCK_RECV_FLAG_OOB 2
-#define ESOCK_RECV_FLAG_PEEK 3
-#define ESOCK_RECV_FLAG_TRUNC 4
-#define ESOCK_RECV_FLAG_LOW ESOCK_RECV_FLAG_CMSG_CLOEXEC
-#define ESOCK_RECV_FLAG_HIGH ESOCK_RECV_FLAG_TRUNC
-
#define ESOCK_RECV_BUFFER_COUNT_DEFAULT 0
#define ESOCK_RECV_BUFFER_SIZE_DEFAULT 8192
#define ESOCK_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024
@@ -523,39 +481,29 @@ typedef union {
} SocketState;
*/
-/*
-#define IS_OPEN(d) ((d)->state.flags.open)
-#define IS_CONNECTED(d) ((d)->state.flags.connect == ESOCK_STATE_CONNECTED)
-#define IS_CONNECTING(d) ((d)->state.flags.connect == ESOCK_STATE_CONNECTING)
-*/
-
-
/*----------------------------------------------------------------------------
* Interface constants.
*
* This section must be "identical" to the corresponding socket.erl
*/
-/* domain */
-#define ESOCK_DOMAIN_LOCAL 1
-#define ESOCK_DOMAIN_INET 2
-#define ESOCK_DOMAIN_INET6 3
+#define ESOCK_SEND_FLAG_CONFIRM (1 << 0)
+#define ESOCK_SEND_FLAG_DONTROUTE (1 << 1)
+#define ESOCK_SEND_FLAG_EOR (1 << 2)
+#define ESOCK_SEND_FLAG_MORE (1 << 3)
+#define ESOCK_SEND_FLAG_NOSIGNAL (1 << 4)
+#define ESOCK_SEND_FLAG_OOB (1 << 5)
+/**/
+#define ESOCK_SEND_FLAG_MASK ((1 << 6) - 1)
-/* type */
-#define ESOCK_TYPE_STREAM 1
-#define ESOCK_TYPE_DGRAM 2
-#define ESOCK_TYPE_RAW 3
-// #define ESOCK_TYPE_RDM 4
-#define ESOCK_TYPE_SEQPACKET 5
+#define ESOCK_RECV_FLAG_CMSG_CLOEXEC (1 << 0)
+#define ESOCK_RECV_FLAG_ERRQUEUE (1 << 1)
+#define ESOCK_RECV_FLAG_OOB (1 << 2)
+#define ESOCK_RECV_FLAG_PEEK (1 << 3)
+#define ESOCK_RECV_FLAG_TRUNC (1 << 4)
+/**/
+#define ESOCK_RECV_FLAG_MASK ((1 << 5) - 1)
-/* protocol */
-#define ESOCK_PROTOCOL_DEFAULT 0
-#define ESOCK_PROTOCOL_IP 1
-#define ESOCK_PROTOCOL_TCP 2
-#define ESOCK_PROTOCOL_UDP 3
-#define ESOCK_PROTOCOL_SCTP 4
-#define ESOCK_PROTOCOL_ICMP 5
-#define ESOCK_PROTOCOL_IGMP 6
/* shutdown how */
#define ESOCK_SHUTDOWN_HOW_RD 0
@@ -563,136 +511,227 @@ typedef union {
#define ESOCK_SHUTDOWN_HOW_RDWR 2
-#define ESOCK_OPT_LEVEL_OTP 0
-#define ESOCK_OPT_LEVEL_SOCKET 1
-#define ESOCK_OPT_LEVEL_IP 2
-#define ESOCK_OPT_LEVEL_IPV6 3
-#define ESOCK_OPT_LEVEL_TCP 4
-#define ESOCK_OPT_LEVEL_UDP 5
-#define ESOCK_OPT_LEVEL_SCTP 6
-
-#define ESOCK_OPT_OTP_DEBUG 1
-#define ESOCK_OPT_OTP_IOW 2
-#define ESOCK_OPT_OTP_CTRL_PROC 3
-#define ESOCK_OPT_OTP_RCVBUF 4
-#define ESOCK_OPT_OTP_RCVCTRLBUF 6
-#define ESOCK_OPT_OTP_SNDCTRLBUF 7
-#define ESOCK_OPT_OTP_FD 8
-#define ESOCK_OPT_OTP_META 9
-#define ESOCK_OPT_OTP_DOMAIN 0xFF01 // INTERNAL AND ONLY GET
-#define ESOCK_OPT_OTP_TYPE 0xFF02 // INTERNAL AND ONLY GET
-#define ESOCK_OPT_OTP_PROTOCOL 0xFF03 // INTERNAL AND ONLY GET
-#define ESOCK_OPT_OTP_DTP 0xFF04 // INTERNAL AND ONLY GET
-
-#define ESOCK_OPT_SOCK_ACCEPTCONN 1
-#define ESOCK_OPT_SOCK_BINDTODEVICE 3
-#define ESOCK_OPT_SOCK_BROADCAST 4
-#define ESOCK_OPT_SOCK_DEBUG 6
-#define ESOCK_OPT_SOCK_DOMAIN 7
-#define ESOCK_OPT_SOCK_DONTROUTE 8
-#define ESOCK_OPT_SOCK_KEEPALIVE 10
-#define ESOCK_OPT_SOCK_LINGER 11
-#define ESOCK_OPT_SOCK_OOBINLINE 13
-#define ESOCK_OPT_SOCK_PASSCRED 14
-#define ESOCK_OPT_SOCK_PEEK_OFF 15
-#define ESOCK_OPT_SOCK_PRIORITY 17
-#define ESOCK_OPT_SOCK_PROTOCOL 18
-#define ESOCK_OPT_SOCK_RCVBUF 19
-#define ESOCK_OPT_SOCK_RCVLOWAT 21
-#define ESOCK_OPT_SOCK_RCVTIMEO 22
-#define ESOCK_OPT_SOCK_REUSEADDR 23
-#define ESOCK_OPT_SOCK_REUSEPORT 24
-#define ESOCK_OPT_SOCK_SNDBUF 27
-#define ESOCK_OPT_SOCK_SNDLOWAT 29
-#define ESOCK_OPT_SOCK_SNDTIMEO 30
-#define ESOCK_OPT_SOCK_TIMESTAMP 31
-#define ESOCK_OPT_SOCK_TYPE 32
-
-#define ESOCK_OPT_IP_ADD_MEMBERSHIP 1
-#define ESOCK_OPT_IP_ADD_SOURCE_MEMBERSHIP 2
-#define ESOCK_OPT_IP_BLOCK_SOURCE 3
-#define ESOCK_OPT_IP_DROP_MEMBERSHIP 5
-#define ESOCK_OPT_IP_DROP_SOURCE_MEMBERSHIP 6
-#define ESOCK_OPT_IP_FREEBIND 7
-#define ESOCK_OPT_IP_HDRINCL 8
-#define ESOCK_OPT_IP_MINTTL 9
-#define ESOCK_OPT_IP_MSFILTER 10
-#define ESOCK_OPT_IP_MTU 11
-#define ESOCK_OPT_IP_MTU_DISCOVER 12
-#define ESOCK_OPT_IP_MULTICAST_ALL 13
-#define ESOCK_OPT_IP_MULTICAST_IF 14
-#define ESOCK_OPT_IP_MULTICAST_LOOP 15
-#define ESOCK_OPT_IP_MULTICAST_TTL 16
-#define ESOCK_OPT_IP_NODEFRAG 17
-#define ESOCK_OPT_IP_PKTINFO 19
-#define ESOCK_OPT_IP_RECVDSTADDR 20
-#define ESOCK_OPT_IP_RECVERR 21
-#define ESOCK_OPT_IP_RECVIF 22
-#define ESOCK_OPT_IP_RECVOPTS 23
-#define ESOCK_OPT_IP_RECVORIGDSTADDR 24
-#define ESOCK_OPT_IP_RECVTOS 25
-#define ESOCK_OPT_IP_RECVTTL 26
-#define ESOCK_OPT_IP_RETOPTS 27
-#define ESOCK_OPT_IP_ROUTER_ALERT 28
-#define ESOCK_OPT_IP_SENDSRCADDR 29 // Same as IP_RECVDSTADDR?
-#define ESOCK_OPT_IP_TOS 30
-#define ESOCK_OPT_IP_TRANSPARENT 31
-#define ESOCK_OPT_IP_TTL 32
-#define ESOCK_OPT_IP_UNBLOCK_SOURCE 33
-
-#define ESOCK_OPT_IPV6_ADDRFORM 1
-#define ESOCK_OPT_IPV6_ADD_MEMBERSHIP 2
-#define ESOCK_OPT_IPV6_AUTHHDR 3
-#define ESOCK_OPT_IPV6_DROP_MEMBERSHIP 6
-#define ESOCK_OPT_IPV6_DSTOPTS 7
-#define ESOCK_OPT_IPV6_FLOWINFO 11
-#define ESOCK_OPT_IPV6_HOPLIMIT 12
-#define ESOCK_OPT_IPV6_HOPOPTS 13
-#define ESOCK_OPT_IPV6_MTU 17
-#define ESOCK_OPT_IPV6_MTU_DISCOVER 18
-#define ESOCK_OPT_IPV6_MULTICAST_HOPS 19
-#define ESOCK_OPT_IPV6_MULTICAST_IF 20
-#define ESOCK_OPT_IPV6_MULTICAST_LOOP 21
-#define ESOCK_OPT_IPV6_RECVERR 24
-#define ESOCK_OPT_IPV6_RECVHOPLIMIT 25
-#define ESOCK_OPT_IPV6_RECVPKTINFO 26 // PKTINFO on FreeBSD
-#define ESOCK_OPT_IPV6_RECVTCLASS 27 // Linux and ?
-#define ESOCK_OPT_IPV6_ROUTER_ALERT 28
-#define ESOCK_OPT_IPV6_RTHDR 29
-#define ESOCK_OPT_IPV6_TCLASS 30
-#define ESOCK_OPT_IPV6_UNICAST_HOPS 31
-#define ESOCK_OPT_IPV6_V6ONLY 33
-
-#define ESOCK_OPT_TCP_CONGESTION 1
-#define ESOCK_OPT_TCP_CORK 2
-#define ESOCK_OPT_TCP_MAXSEG 7
-#define ESOCK_OPT_TCP_NODELAY 9
-
-#define ESOCK_OPT_UDP_CORK 1
-
-#define ESOCK_OPT_SCTP_ASSOCINFO 2
-#define ESOCK_OPT_SCTP_AUTOCLOSE 8
-#define ESOCK_OPT_SCTP_DISABLE_FRAGMENTS 12
-#define ESOCK_OPT_SCTP_EVENTS 14
-#define ESOCK_OPT_SCTP_INITMSG 18
-#define ESOCK_OPT_SCTP_MAXSEG 21
-#define ESOCK_OPT_SCTP_NODELAY 23
-#define ESOCK_OPT_SCTP_RTOINFO 29
+
+/* domain */
+#define ESOCK_DOMAIN_LOCAL 1
+#define ESOCK_DOMAIN_INET 2
+#define ESOCK_DOMAIN_INET6 3
+
+/* type */
+#define ESOCK_TYPE_STREAM 101
+#define ESOCK_TYPE_DGRAM 102
+#define ESOCK_TYPE_RAW 103
+// #define ESOCK_TYPE_RDM 104
+#define ESOCK_TYPE_SEQPACKET 105
+
+/* protocol */
+#define ESOCK_PROTOCOL_DEFAULT 200
+#define ESOCK_PROTOCOL_IP 201
+#define ESOCK_PROTOCOL_TCP 202
+#define ESOCK_PROTOCOL_UDP 203
+#define ESOCK_PROTOCOL_SCTP 204
+#define ESOCK_PROTOCOL_ICMP 205
+#define ESOCK_PROTOCOL_IGMP 206
+
+/* option level */
+#define ESOCK_OPT_LEVEL_OTP 301
+#define ESOCK_OPT_LEVEL_SOCKET 302
+#define ESOCK_OPT_LEVEL_IP 303
+#define ESOCK_OPT_LEVEL_IPV6 304
+#define ESOCK_OPT_LEVEL_TCP 305
+#define ESOCK_OPT_LEVEL_UDP 306
+#define ESOCK_OPT_LEVEL_SCTP 307
+
+/* level 'otp' options */
+#define ESOCK_OPT_OTP_DEBUG 1001
+#define ESOCK_OPT_OTP_IOW 1002
+#define ESOCK_OPT_OTP_CTRL_PROC 1003
+#define ESOCK_OPT_OTP_RCVBUF 1004
+//#define ESOCK_OPT_OTP_SNDBUF 1005
+#define ESOCK_OPT_OTP_RCVCTRLBUF 1006
+#define ESOCK_OPT_OTP_SNDCTRLBUF 1007
+#define ESOCK_OPT_OTP_FD 1008
+#define ESOCK_OPT_OTP_META 1009
+#define ESOCK_OPT_OTP_USE_REGISTRY 1010
+/**/
+#define ESOCK_OPT_OTP_DOMAIN 1999 // INTERNAL AND ONLY GET
+#define ESOCK_OPT_OTP_TYPE 1998 // INTERNAL AND ONLY GET
+#define ESOCK_OPT_OTP_PROTOCOL 1997 // INTERNAL AND ONLY GET
+#define ESOCK_OPT_OTP_DTP 1996 // INTERNAL AND ONLY GET
+
+/* level 'socket' options */
+#define ESOCK_OPT_SOCK_ACCEPTCONN 2001
+//#define ESOCK_OPT_SOCK_ACCEPTFILTER 2002
+#define ESOCK_OPT_SOCK_BINDTODEVICE 2003
+#define ESOCK_OPT_SOCK_BROADCAST 2004
+//#define ESOCK_OPT_SOCK_BUSY_POLL 2005
+#define ESOCK_OPT_SOCK_DEBUG 2006
+#define ESOCK_OPT_SOCK_DOMAIN 2007
+#define ESOCK_OPT_SOCK_DONTROUTE 2008
+//#define ESOCK_OPT_SOCK_ERROR 2009
+#define ESOCK_OPT_SOCK_KEEPALIVE 2010
+#define ESOCK_OPT_SOCK_LINGER 2011
+//#define ESOCK_OPT_SOCK_MARK 2012
+#define ESOCK_OPT_SOCK_OOBINLINE 2013
+#define ESOCK_OPT_SOCK_PASSCRED 2014
+#define ESOCK_OPT_SOCK_PEEK_OFF 2015
+//#define ESOCK_OPT_SOCK_PEERCRED 2016
+#define ESOCK_OPT_SOCK_PRIORITY 2017
+#define ESOCK_OPT_SOCK_PROTOCOL 2018
+#define ESOCK_OPT_SOCK_RCVBUF 2019
+//#define ESOCK_OPT_SOCK_RCVBUFFORCE 2020
+#define ESOCK_OPT_SOCK_RCVLOWAT 2021
+#define ESOCK_OPT_SOCK_RCVTIMEO 2022
+#define ESOCK_OPT_SOCK_REUSEADDR 2023
+#define ESOCK_OPT_SOCK_REUSEPORT 2024
+//#define ESOCK_OPT_SOCK_RXQ_OVFL 2025
+//#define ESOCK_OPT_SOCK_SETFIB 2026
+#define ESOCK_OPT_SOCK_SNDBUF 2027
+//#define ESOCK_OPT_SOCK_SNDBUFFORCE 2028
+#define ESOCK_OPT_SOCK_SNDLOWAT 2029
+#define ESOCK_OPT_SOCK_SNDTIMEO 2030
+#define ESOCK_OPT_SOCK_TIMESTAMP 2031
+#define ESOCK_OPT_SOCK_TYPE 2032
+
+/* level 'ip' options */
+#define ESOCK_OPT_IP_ADD_MEMBERSHIP 3001
+#define ESOCK_OPT_IP_ADD_SOURCE_MEMBERSHIP 3002
+#define ESOCK_OPT_IP_BLOCK_SOURCE 3003
+//#define ESOCK_OPT_IP_DONTFRAG 3004
+#define ESOCK_OPT_IP_DROP_MEMBERSHIP 3005
+#define ESOCK_OPT_IP_DROP_SOURCE_MEMBERSHIP 3006
+#define ESOCK_OPT_IP_FREEBIND 3007
+#define ESOCK_OPT_IP_HDRINCL 3008
+#define ESOCK_OPT_IP_MINTTL 3009
+#define ESOCK_OPT_IP_MSFILTER 3010
+#define ESOCK_OPT_IP_MTU 3011
+#define ESOCK_OPT_IP_MTU_DISCOVER 3012
+#define ESOCK_OPT_IP_MULTICAST_ALL 3013
+#define ESOCK_OPT_IP_MULTICAST_IF 3014
+#define ESOCK_OPT_IP_MULTICAST_LOOP 3015
+#define ESOCK_OPT_IP_MULTICAST_TTL 3016
+#define ESOCK_OPT_IP_NODEFRAG 3017
+//#define ESOCK_OPT_IP_OPTIONS 3018
+#define ESOCK_OPT_IP_PKTINFO 3019
+#define ESOCK_OPT_IP_RECVDSTADDR 3020
+#define ESOCK_OPT_IP_RECVERR 3021
+#define ESOCK_OPT_IP_RECVIF 3022
+#define ESOCK_OPT_IP_RECVOPTS 3023
+#define ESOCK_OPT_IP_RECVORIGDSTADDR 3024
+#define ESOCK_OPT_IP_RECVTOS 3025
+#define ESOCK_OPT_IP_RECVTTL 3026
+#define ESOCK_OPT_IP_RETOPTS 3027
+#define ESOCK_OPT_IP_ROUTER_ALERT 3028
+#define ESOCK_OPT_IP_SENDSRCADDR 3029 // Same as IP_RECVDSTADDR?
+#define ESOCK_OPT_IP_TOS 3030
+#define ESOCK_OPT_IP_TRANSPARENT 3031
+#define ESOCK_OPT_IP_TTL 3032
+#define ESOCK_OPT_IP_UNBLOCK_SOURCE 3033
+
+/* level 'ipv6' options */
+#define ESOCK_OPT_IPV6_ADDRFORM 4001
+#define ESOCK_OPT_IPV6_ADD_MEMBERSHIP 4002
+#define ESOCK_OPT_IPV6_AUTHHDR 4003
+//#define ESOCK_OPT_IPV6_AUTH_LEVEL 4004
+//#define ESOCK_OPT_IPV6_CHECKSUM 4005
+#define ESOCK_OPT_IPV6_DROP_MEMBERSHIP 4006
+#define ESOCK_OPT_IPV6_DSTOPTS 4007
+//#define ESOCK_OPT_IPV6_ESP_NETWORK_LEVEL 4008
+//#define ESOCK_OPT_IPV6_ESP_TRANS_LEVEL 4009
+//#define ESOCK_OPT_IPV6_FAITH 4010
+#define ESOCK_OPT_IPV6_FLOWINFO 4011
+#define ESOCK_OPT_IPV6_HOPLIMIT 4012
+#define ESOCK_OPT_IPV6_HOPOPTS 4013
+//#define ESOCK_OPT_IPV6_IPCOMP_LEVEL 4014
+//#define ESOCK_OPT_IPV6_JOIN_GROUP 4015
+//#define ESOCK_OPT_IPV6_LEAVE_GROUP 4016
+#define ESOCK_OPT_IPV6_MTU 4017
+#define ESOCK_OPT_IPV6_MTU_DISCOVER 4018
+#define ESOCK_OPT_IPV6_MULTICAST_HOPS 4019
+#define ESOCK_OPT_IPV6_MULTICAST_IF 4020
+#define ESOCK_OPT_IPV6_MULTICAST_LOOP 4021
+//#define ESOCK_OPT_IPV6_PORTRANGE 4022
+//#define ESOCK_OPT_IPV6_PKTOPTIONS 4023
+#define ESOCK_OPT_IPV6_RECVERR 4024
+#define ESOCK_OPT_IPV6_RECVHOPLIMIT 4025
+#define ESOCK_OPT_IPV6_RECVPKTINFO 4026 // PKTINFO on FreeBSD
+#define ESOCK_OPT_IPV6_RECVTCLASS 4027 // Linux and ?
+#define ESOCK_OPT_IPV6_ROUTER_ALERT 4028
+#define ESOCK_OPT_IPV6_RTHDR 4029
+#define ESOCK_OPT_IPV6_TCLASS 4030
+#define ESOCK_OPT_IPV6_UNICAST_HOPS 4031
+//#define ESOCK_OPT_IPV6_USE_MIN_MTU 4032
+#define ESOCK_OPT_IPV6_V6ONLY 4033
+
+/* level 'tcp' options */
+#define ESOCK_OPT_TCP_CONGESTION 5001
+#define ESOCK_OPT_TCP_CORK 5002
+//#define ESOCK_OPT_TCP_INFO 5003
+//#define ESOCK_OPT_TCP_KEEPCNT 5004
+//#define ESOCK_OPT_TCP_KEEPIDLE 5005
+//#define ESOCK_OPT_TCP_KEEPINTVL 5006
+#define ESOCK_OPT_TCP_MAXSEG 5007
+//#define ESOCK_OPT_TCP_MD5SIG 5008
+#define ESOCK_OPT_TCP_NODELAY 5009
+//#define ESOCK_OPT_TCP_NOOPT 5010
+//#define ESOCK_OPT_TCP_NOPUSH 5011
+//#define ESOCK_OPT_TCP_SYNCNT 5012
+//#define ESOCK_OPT_TCP_USER_TIMEOUT 5013
+
+/* level 'udp' options */
+#define ESOCK_OPT_UDP_CORK 6001
+
+/* level 'sctp' options */
+//#define ESOCK_OPT_SCTP_ADAPTION_LAYER 7001
+#define ESOCK_OPT_SCTP_ASSOCINFO 7002
+//#define ESOCK_OPT_SCTP_AUTH_ACTIVE_KEY 7003
+//#define ESOCK_OPT_SCTP_AUTH_ASCONF 7004
+//#define ESOCK_OPT_SCTP_AUTH_CHUNK 7005
+//#define ESOCK_OPT_SCTP_AUTH_KEY 7006
+//#define ESOCK_OPT_SCTP_AUTH_DELETE_KEY 7007
+#define ESOCK_OPT_SCTP_AUTOCLOSE 7008
+//#define ESOCK_OPT_SCTP_CONTEXT 7009
+//#define ESOCK_OPT_SCTP_DEFAULT_SEND_PARAMS 7010
+//#define ESOCK_OPT_SCTP_DELAYED_ACK_TIME 7011
+#define ESOCK_OPT_SCTP_DISABLE_FRAGMENTS 7012
+//#define ESOCK_OPT_SCTP_HMAC_IDENT 7013
+#define ESOCK_OPT_SCTP_EVENTS 7014
+//#define ESOCK_OPT_SCTP_EXPLICIT_EOR 7015
+//#define ESOCK_OPT_SCTP_FRAGMENT_INTERLEAVE 7016
+//#define ESOCK_OPT_SCTP_GET_PEER_ADDR_INFO 7017
+#define ESOCK_OPT_SCTP_INITMSG 7018
+//#define ESOCK_OPT_SCTP_I_WANT_MAPPED_V4_ADDR 7019
+//#define ESOCK_OPT_SCTP_LOCAL_AUTH_CHUNKS 7020
+#define ESOCK_OPT_SCTP_MAXSEG 7021
+//#define ESOCK_OPT_SCTP_MAXBURST 7022
+#define ESOCK_OPT_SCTP_NODELAY 7023
+//#define ESOCK_OPT_SCTP_PARTIAL_DELIVERY_POINT 7024
+//#define ESOCK_OPT_SCTP_PEER_ADDR_PARAMS 7025
+//#define ESOCK_OPT_SCTP_PEER_AUTH_CHUNKS 7026
+//#define ESOCK_OPT_SCTP_PRIMARY_ADDR 7027
+//#define ESOCK_OPT_SCTP_RESET_STREAMS 7028
+#define ESOCK_OPT_SCTP_RTOINFO 7029
+//#define ESOCK_OPT_SCTP_SET_PEER_PRIMARY_ADDR 7030
+//#define ESOCK_OPT_SCTP_STATUS 7031
+//#define ESOCK_OPT_SCTP_USE_EXT_RECVINFO 7032
+
+
+/*--------------------------------------------------------------------------*/
+
/* We should *eventually* use this instead of hard-coding the size (to 1) */
#define ESOCK_RECVMSG_IOVEC_SZ 1
-#define ESOCK_CMD_DEBUG 0x0001
+#define ESOCK_CMD_DEBUG 0x0001
+#define ESOCK_CMD_SOCKET_DEBUG 0x0002
+#define ESOCK_CMD_USE_SOCKET_REGISTRY 0x0003
-#define ESOCK_SUPPORTS_OPTIONS 0x0001
-#define ESOCK_SUPPORTS_SCTP 0x0002
-#define ESOCK_SUPPORTS_IPV6 0x0003
-#define ESOCK_SUPPORTS_LOCAL 0x0004
-#define ESOCK_SUPPORTS_SEND_FLAGS 0x0005
-#define ESOCK_SUPPORTS_RECV_FLAGS 0x0006
-
-#define ESOCK_WHICH_PROTO_ERROR -1
-#define ESOCK_WHICH_PROTO_UNSUP -2
+#define ESOCK_WHICH_DOMAIN_ERROR -1
+#define ESOCK_WHICH_DOMAIN_UNSUP -2
+#define ESOCK_WHICH_TYPE_ERROR -1
+#define ESOCK_WHICH_TYPE_UNSUP -2
+#define ESOCK_WHICH_PROTO_ERROR -1
+#define ESOCK_WHICH_PROTO_UNSUP -2
/* =================================================================== *
@@ -702,9 +741,10 @@ typedef union {
* =================================================================== */
/* Global socket debug */
-#define SGDBG( proto ) ESOCK_DBG_PRINTF( data.dbg , proto )
+#define SGDBG( proto ) ESOCK_DBG_PRINTF( data.dbg , proto )
/* Socket specific debug */
-#define SSDBG( __D__ , proto ) ESOCK_DBG_PRINTF( (__D__)->dbg , proto )
+#define SSDBG( __D__ , proto ) ESOCK_DBG_PRINTF( (__D__)->dbg , proto )
+#define SSDBG2( __DBG__ , proto ) ESOCK_DBG_PRINTF( (__DBG__) , proto )
#define ESOCK_CNT_INC( __E__, __D__, SF, ACNT, CNT, INC) \
{ \
@@ -899,33 +939,27 @@ typedef struct {
*/
Uint32 pattern;
- /* +++ The actual socket +++ */
- SOCKET sock;
- HANDLE event;
-
/* +++ Stuff "about" the socket +++ */
+
+ /* "Constant" - set when socket is created and never changed */
int domain;
int type;
int protocol;
- unsigned int state;
- ESockAddress remote;
- unsigned int addrLen;
-
- /* +++ Controller (owner) process +++ */
- ErlNifPid ctrlPid;
- ESockMonitor ctrlMon;
-
- /* +++ Connector process +++ */
- ErlNifPid connPid;
- ESockMonitor connMon;
+ /* The state is for debugging only, decisions are made based
+ * on other variables. The state is divided in a readState half
+ * and a writeState half that can be OR:ed together to create
+ * the complete state. The halves are locked by their
+ * corresponding lock.
+ */
/* +++ Write stuff +++ */
ErlNifMutex* writeMtx;
+ /**/
+ unsigned int writeState; // For debugging
ESockRequestor currentWriter;
ESockRequestor* currentWriterP; // NULL or points to currentWriter
ESockRequestQueue writersQ;
- BOOLEAN_T isWritable;
ESockCounter writePkgCnt;
ESockCounter writePkgMax;
ESockCounter writePkgMaxCnt;
@@ -933,13 +967,20 @@ typedef struct {
ESockCounter writeTries;
ESockCounter writeWaits;
ESockCounter writeFails;
+ /* +++ Connector +++ */
+ ESockRequestor connector;
+ ESockRequestor* connectorP;
+ /* +++ Config stuff +++ */
+ size_t wCtrlSz; // Write control buffer size
+ ESockMeta meta; // Level 'otp' option 'meta' term
/* +++ Read stuff +++ */
ErlNifMutex* readMtx;
+ /**/
+ unsigned int readState; // For debugging
ESockRequestor currentReader;
ESockRequestor* currentReaderP; // NULL or points to currentReader
ESockRequestQueue readersQ;
- BOOLEAN_T isReadable;
ErlNifBinary rbuffer; // DO WE NEED THIS
Uint32 readCapacity; // DO WE NEED THIS
ESockCounter readPkgCnt;
@@ -949,9 +990,7 @@ typedef struct {
ESockCounter readTries;
ESockCounter readWaits;
ESockCounter readFails;
-
/* +++ Accept stuff +++ */
- ErlNifMutex* accMtx;
ESockRequestor currentAcceptor;
ESockRequestor* currentAcceptorP; // NULL or points to currentAcceptor
ESockRequestQueue acceptorsQ;
@@ -959,10 +998,8 @@ typedef struct {
ESockCounter accTries;
ESockCounter accWaits;
ESockCounter accFails;
-
- /* +++ Config & Misc stuff +++ */
- ErlNifMutex* cfgMtx;
- size_t rBufSz; // Read buffer size (when data length = 0)
+ /* +++ Config stuff +++ */
+ size_t rBufSz; // Read buffer size (when data length = 0)
/* rNum and rNumCnt are used (together with rBufSz) when calling the recv
* function with the Length argument set to 0 (zero).
* If rNum is 0 (zero), then rNumCnt is not used and only *one* read will
@@ -970,24 +1007,34 @@ typedef struct {
* getopt, the value will be reported as an integer. If the rNum has a
* value greater then 0 (zero), then it will instead be reported as {N, BufSz}.
*/
- unsigned int rNum; // recv: Number of reads using rBufSz
- unsigned int rNumCnt; // recv: Current number of reads (so far)
- size_t rCtrlSz; // Read control buffer size
- size_t wCtrlSz; // Write control buffer size
- ESockMeta meta; // Level 'otp' option 'meta' term
- BOOLEAN_T iow; // Inform On (counter) Wrap
- BOOLEAN_T dbg;
+ unsigned int rNum; // recv: Number of reads using rBufSz
+ unsigned int rNumCnt; // recv: Current number of reads (so far)
+ size_t rCtrlSz; // Read control buffer size
+ /* Locked by readMtx and writeMtx combined for writing,
+ * which means only one of them is required for reading
+ */
/* +++ Close stuff +++ */
- ErlNifMutex* closeMtx;
- ErlNifPid closerPid;
- ESockMonitor closerMon;
- ErlNifEnv* closeEnv;
- ERL_NIF_TERM closeRef;
- BOOLEAN_T closeLocal;
-
- /* Lock order: closeMtx, readMtx, accMtx, writeMtx, cfgMtx
- * unordered: cntMtx
+ BOOLEAN_T closing; // We are calling esock_select_stop
+ ErlNifPid closerPid;
+ ESockMonitor closerMon;
+ ErlNifEnv* closeEnv;
+ ERL_NIF_TERM closeRef;
+ /* +++ Inform On (counter) Wrap +++ */
+ BOOLEAN_T iow;
+ /* +++ Controller (owner) process +++ */
+ ErlNifPid ctrlPid;
+ ESockMonitor ctrlMon;
+ /* +++ The actual socket +++ */
+ SOCKET sock;
+ HANDLE event;
+ SOCKET origFD; // A 'socket' created from this FD
+ BOOLEAN_T closeOnClose; // Have we dup'ed or not
+ /* +++ The dbg flag for SSDBG +++ */
+ BOOLEAN_T dbg;
+ BOOLEAN_T useReg;
+
+ /* Lock order: readMtx, writeMtx, cntMtx
*/
} ESockDescriptor;
@@ -1001,6 +1048,7 @@ typedef struct {
/* XXX Should be locked but too awkward and small gain */
BOOLEAN_T dbg;
+ BOOLEAN_T useReg;
/* Registry stuff */
ErlNifPid regPid; /* Constant - not locked */
@@ -1013,7 +1061,7 @@ typedef struct {
ErlNifMutex* cntMtx; /* Locks the below */
/* Its extreme overkill to have these counters be 64-bit,
* but since the other counters are, its much simpler to
- * let to let these be 64-bit also
+ * let these be 64-bit also.
*/
ESockCounter numSockets;
ESockCounter numTypeStreams;
@@ -1026,6 +1074,8 @@ typedef struct {
ESockCounter numProtoTCP;
ESockCounter numProtoUDP;
ESockCounter numProtoSCTP;
+ //
+ BOOLEAN_T sockDbg;
} ESockData;
@@ -1072,7 +1122,6 @@ extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */
* nif_getopt
* nif_sockname
* nif_peername
- * nif_finalize_connection
* nif_finalize_close
* nif_cancel
*/
@@ -1098,7 +1147,6 @@ extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */
ESOCK_NIF_FUNC_DEF(getopt); \
ESOCK_NIF_FUNC_DEF(sockname); \
ESOCK_NIF_FUNC_DEF(peername); \
- ESOCK_NIF_FUNC_DEF(finalize_connection); \
ESOCK_NIF_FUNC_DEF(finalize_close); \
ESOCK_NIF_FUNC_DEF(cancel);
@@ -1121,7 +1169,12 @@ static BOOLEAN_T ecommand2command(ErlNifEnv* env,
static ERL_NIF_TERM esock_command(ErlNifEnv* env,
Uint16 cmd,
ERL_NIF_TERM ecdata);
-static ERL_NIF_TERM esock_command_debug(ErlNifEnv* env, ERL_NIF_TERM ecdata);
+static ERL_NIF_TERM esock_command_debug(ErlNifEnv* env,
+ ERL_NIF_TERM ecdata);
+static ERL_NIF_TERM esock_command_socket_debug(ErlNifEnv* env,
+ ERL_NIF_TERM ecdata);
+static ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv* env,
+ ERL_NIF_TERM ecdata);
static ERL_NIF_TERM esock_global_info(ErlNifEnv* env);
static ERL_NIF_TERM esock_socket_info(ErlNifEnv* env,
@@ -1134,6 +1187,8 @@ static ERL_NIF_TERM esock_socket_info_protocol(ErlNifEnv* env,
ESockDescriptor* descP);
static ERL_NIF_TERM esock_socket_info_counters(ErlNifEnv* env,
ESockDescriptor* descP);
+static ERL_NIF_TERM esock_socket_info_ctype(ErlNifEnv* env,
+ ESockDescriptor* descP);
#define ESOCK_SOCKET_INFO_REQ_FUNCS \
ESOCK_SOCKET_INFO_REQ_FUNC_DEF(readers); \
ESOCK_SOCKET_INFO_REQ_FUNC_DEF(writers); \
@@ -1147,29 +1202,54 @@ ESOCK_SOCKET_INFO_REQ_FUNCS
static ERL_NIF_TERM socket_info_reqs(ErlNifEnv* env,
ESockDescriptor* descP,
- ErlNifMutex* mtx,
ESockRequestor* crp,
ESockRequestQueue* q);
-static ERL_NIF_TERM esock_supports(ErlNifEnv* env, int key);
-static ERL_NIF_TERM esock_supports_options(ErlNifEnv* env);
+static ERL_NIF_TERM esock_supports_0(ErlNifEnv* env);
+static ERL_NIF_TERM esock_supports_1(ErlNifEnv* env, ERL_NIF_TERM key);
+static ERL_NIF_TERM esock_supports_2(ErlNifEnv* env,
+ ERL_NIF_TERM key1, int key2);
+//static ERL_NIF_TERM esock_supports_options(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_socket(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_ip(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_ipv6(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_tcp(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_udp(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_sctp(ErlNifEnv* env);
-static ERL_NIF_TERM esock_supports_sctp(ErlNifEnv* env);
-static ERL_NIF_TERM esock_supports_ipv6(ErlNifEnv* env);
-static ERL_NIF_TERM esock_supports_local(ErlNifEnv* env);
+//static ERL_NIF_TERM esock_supports_sctp(ErlNifEnv* env);
+//static ERL_NIF_TERM esock_supports_ipv6(ErlNifEnv* env);
+//static ERL_NIF_TERM esock_supports_local(ErlNifEnv* env);
+//static ERL_NIF_TERM esock_supports_netns(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_send_flags(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_recv_flags(ErlNifEnv* env);
-static ERL_NIF_TERM esock_open(ErlNifEnv* env,
- int domain,
- int type,
- int protocol,
- char* netns);
+static ERL_NIF_TERM esock_open2(ErlNifEnv* env,
+ int fd,
+ ERL_NIF_TERM eextra);
+static BOOLEAN_T esock_open2_todup(ErlNifEnv* env,
+ ERL_NIF_TERM eextra);
+static BOOLEAN_T esock_open2_get_domain(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ int* domain);
+static BOOLEAN_T esock_open2_get_type(ErlNifEnv* env,
+ ERL_NIF_TERM eopt,
+ int* type);
+static BOOLEAN_T esock_open2_get_protocol(ErlNifEnv* env,
+ ERL_NIF_TERM eopts,
+ int* protocol);
+static ERL_NIF_TERM esock_open4(ErlNifEnv* env,
+ int domain,
+ int type,
+ int protocol,
+ ERL_NIF_TERM eopts);
+static BOOLEAN_T esock_open_is_debug(ErlNifEnv* env,
+ ERL_NIF_TERM eextra,
+ BOOLEAN_T dflt);
+static BOOLEAN_T esock_open_use_registry(ErlNifEnv* env,
+ ERL_NIF_TERM eextra,
+ BOOLEAN_T dflt);
+static BOOLEAN_T esock_open_which_domain(SOCKET sock, int* domain);
+static BOOLEAN_T esock_open_which_type(SOCKET sock, int* type);
static BOOLEAN_T esock_open_which_protocol(SOCKET sock, int* proto);
static ERL_NIF_TERM esock_bind(ErlNifEnv* env,
@@ -1178,7 +1258,9 @@ static ERL_NIF_TERM esock_bind(ErlNifEnv* env,
unsigned int addrLen);
static ERL_NIF_TERM esock_connect(ErlNifEnv* env,
ESockDescriptor* descP,
- ERL_NIF_TERM sockRef);
+ ERL_NIF_TERM sockRef,
+ ESockAddress* addrP,
+ socklen_t addrLen);
static ERL_NIF_TERM esock_listen(ErlNifEnv* env,
ESockDescriptor* descP,
int backlog);
@@ -1186,10 +1268,6 @@ static ERL_NIF_TERM esock_accept(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
ERL_NIF_TERM ref);
-static ERL_NIF_TERM esock_accept_listening(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM ref);
static ERL_NIF_TERM esock_accept_listening_error(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
@@ -1200,21 +1278,16 @@ static ERL_NIF_TERM esock_accept_listening_accept(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
SOCKET accSock,
- ErlNifPid caller,
- ESockAddress* remote);
-static ERL_NIF_TERM esock_accept_accepting(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM ref);
+ ErlNifPid caller);
static ERL_NIF_TERM esock_accept_accepting_current(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
ERL_NIF_TERM ref);
-static ERL_NIF_TERM esock_accept_accepting_current_accept(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- SOCKET accSock,
- ESockAddress* remote);
+static ERL_NIF_TERM
+esock_accept_accepting_current_accept(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ SOCKET accSock);
static ERL_NIF_TERM esock_accept_accepting_current_error(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
@@ -1228,14 +1301,12 @@ static ERL_NIF_TERM esock_accept_busy_retry(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
ERL_NIF_TERM accRef,
- ErlNifPid* pid,
- unsigned int nextState);
+ ErlNifPid* pid);
static BOOLEAN_T esock_accept_accepted(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
SOCKET accSock,
ErlNifPid pid,
- ESockAddress* remote,
ERL_NIF_TERM* result);
static ERL_NIF_TERM esock_send(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -1278,11 +1349,8 @@ static ERL_NIF_TERM esock_recvmsg(ErlNifEnv* env,
int flags);
static ERL_NIF_TERM esock_close(ErlNifEnv* env,
ESockDescriptor* descP);
-static BOOLEAN_T esock_close_check(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM* reason);
-static ERL_NIF_TERM esock_close_do(ErlNifEnv* env,
- ESockDescriptor* descP);
+static int esock_do_stop(ErlNifEnv* env,
+ ESockDescriptor* descP);
static ERL_NIF_TERM esock_shutdown(ErlNifEnv* env,
ESockDescriptor* descP,
int how);
@@ -1299,22 +1367,22 @@ static ERL_NIF_TERM esock_setopt_otp(ErlNifEnv* env,
ESockDescriptor* descP,
int eOpt,
ERL_NIF_TERM eVal);
-/* *** esock_setopt_otp_debug ***
- * *** esock_setopt_otp_iow ***
- * *** esock_setopt_otp_ctrl_proc ***
- * *** esock_setopt_otp_rcvbuf ***
- * *** esock_setopt_otp_rcvctrlbuf ***
- * *** esock_setopt_otp_sndctrlbuf ***
- * *** esock_setopt_otp_meta ***
- */
-#define ESOCK_SETOPT_OTP_FUNCS \
- ESOCK_SETOPT_OTP_FUNC_DEF(debug); \
- ESOCK_SETOPT_OTP_FUNC_DEF(meta); \
- ESOCK_SETOPT_OTP_FUNC_DEF(iow); \
- ESOCK_SETOPT_OTP_FUNC_DEF(ctrl_proc); \
- ESOCK_SETOPT_OTP_FUNC_DEF(rcvbuf); \
- ESOCK_SETOPT_OTP_FUNC_DEF(rcvctrlbuf); \
- ESOCK_SETOPT_OTP_FUNC_DEF(sndctrlbuf);
+/* *** esock_setopt_otp_debug ***
+ * *** esock_setopt_otp_iow ***
+ * *** esock_setopt_otp_ctrl_proc ***
+ * *** esock_setopt_otp_rcvbuf ***
+ * *** esock_setopt_otp_rcvctrlbuf ***
+ * *** esock_setopt_otp_sndctrlbuf ***
+ * *** esock_setopt_otp_meta ***
+ */
+#define ESOCK_SETOPT_OTP_FUNCS \
+ ESOCK_SETOPT_OTP_FUNC_DEF(debug); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(iow); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(ctrl_proc); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(rcvbuf); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(rcvctrlbuf); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(sndctrlbuf); \
+ ESOCK_SETOPT_OTP_FUNC_DEF(meta);
#define ESOCK_SETOPT_OTP_FUNC_DEF(F) \
static ERL_NIF_TERM esock_setopt_otp_##F(ErlNifEnv* env, \
ESockDescriptor* descP, \
@@ -1401,7 +1469,7 @@ static ERL_NIF_TERM esock_setopt_lvl_sock_rcvlowat(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM eVal);
#endif
-#if defined(SO_RCVTIMEO)
+#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_setopt_lvl_sock_rcvtimeo(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM eVal);
@@ -1426,7 +1494,7 @@ static ERL_NIF_TERM esock_setopt_lvl_sock_sndlowat(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM eVal);
#endif
-#if defined(SO_SNDTIMEO)
+#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_setopt_lvl_sock_sndtimeo(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM eVal);
@@ -1831,31 +1899,33 @@ static ERL_NIF_TERM esock_getopt(ErlNifEnv* env,
static ERL_NIF_TERM esock_getopt_otp(ErlNifEnv* env,
ESockDescriptor* descP,
int eOpt);
-/* *** esock_getopt_otp_debug ***
- * *** esock_getopt_otp_iow ***
- * *** esock_getopt_otp_ctrl_proc ***
- * *** esock_getopt_otp_rcvbuf ***
- * *** esock_getopt_otp_rcvctrlbuf ***
- * *** esock_getopt_otp_sndctrlbuf ***
- * *** esock_getopt_otp_fd ***
- * *** esock_getopt_otp_meta ***
- * *** esock_getopt_otp_domain ***
- * *** esock_getopt_otp_type ***
- * *** esock_getopt_otp_protocol ***
- * *** esock_getopt_otp_dtp ***
- */
-#define ESOCK_GETOPT_OTP_FUNCS \
- ESOCK_GETOPT_OTP_FUNC_DEF(debug); \
- ESOCK_GETOPT_OTP_FUNC_DEF(meta); \
- ESOCK_GETOPT_OTP_FUNC_DEF(iow); \
- ESOCK_GETOPT_OTP_FUNC_DEF(ctrl_proc); \
- ESOCK_GETOPT_OTP_FUNC_DEF(rcvbuf); \
- ESOCK_GETOPT_OTP_FUNC_DEF(rcvctrlbuf); \
- ESOCK_GETOPT_OTP_FUNC_DEF(sndctrlbuf); \
- ESOCK_GETOPT_OTP_FUNC_DEF(fd); \
- ESOCK_GETOPT_OTP_FUNC_DEF(domain); \
- ESOCK_GETOPT_OTP_FUNC_DEF(type); \
- ESOCK_GETOPT_OTP_FUNC_DEF(protocol); \
+/* *** esock_getopt_otp_debug ***
+ * *** esock_getopt_otp_iow ***
+ * *** esock_getopt_otp_ctrl_proc ***
+ * *** esock_getopt_otp_rcvbuf ***
+ * *** esock_getopt_otp_rcvctrlbuf ***
+ * *** esock_getopt_otp_sndctrlbuf ***
+ * *** esock_getopt_otp_fd ***
+ * *** esock_getopt_otp_meta ***
+ * *** esock_getopt_otp_use_registry ***
+ * *** esock_getopt_otp_domain ***
+ * *** esock_getopt_otp_type ***
+ * *** esock_getopt_otp_protocol ***
+ * *** esock_getopt_otp_dtp ***
+ */
+#define ESOCK_GETOPT_OTP_FUNCS \
+ ESOCK_GETOPT_OTP_FUNC_DEF(debug); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(meta); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(iow); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(ctrl_proc); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(rcvbuf); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(rcvctrlbuf); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(sndctrlbuf); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(fd); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(use_registry); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(domain); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(type); \
+ ESOCK_GETOPT_OTP_FUNC_DEF(protocol); \
ESOCK_GETOPT_OTP_FUNC_DEF(dtp);
#define ESOCK_GETOPT_OTP_FUNC_DEF(F) \
static ERL_NIF_TERM esock_getopt_otp_##F(ErlNifEnv* env, \
@@ -1939,7 +2009,7 @@ static ERL_NIF_TERM esock_getopt_lvl_sock_rcvbuf(ErlNifEnv* env,
static ERL_NIF_TERM esock_getopt_lvl_sock_rcvlowat(ErlNifEnv* env,
ESockDescriptor* descP);
#endif
-#if defined(SO_RCVTIMEO)
+#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_getopt_lvl_sock_rcvtimeo(ErlNifEnv* env,
ESockDescriptor* descP);
#endif
@@ -1959,7 +2029,7 @@ static ERL_NIF_TERM esock_getopt_lvl_sock_sndbuf(ErlNifEnv* env,
static ERL_NIF_TERM esock_getopt_lvl_sock_sndlowat(ErlNifEnv* env,
ESockDescriptor* descP);
#endif
-#if defined(SO_SNDTIMEO)
+#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_getopt_lvl_sock_sndtimeo(ErlNifEnv* env,
ESockDescriptor* descP);
#endif
@@ -2284,11 +2354,13 @@ static ERL_NIF_TERM esock_setopt_int_opt(ErlNifEnv* env,
int level,
int opt,
ERL_NIF_TERM eVal);
+#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv* env,
ESockDescriptor* descP,
int level,
int opt,
ERL_NIF_TERM eVal);
+#endif
#if defined(USE_GETOPT_STR_OPT)
static ERL_NIF_TERM esock_getopt_str_opt(ErlNifEnv* env,
@@ -2305,10 +2377,12 @@ static ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv* env,
ESockDescriptor* descP,
int level,
int opt);
+#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv* env,
ESockDescriptor* descP,
int level,
int opt);
+#endif
static BOOLEAN_T send_check_writer(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -2443,10 +2517,10 @@ static ERL_NIF_TERM recvmsg_check_msg(ErlNifEnv* env,
ErlNifBinary* ctrlBufP,
ERL_NIF_TERM sockRef);
-static ERL_NIF_TERM esock_finalize_connection(ErlNifEnv* env,
- ESockDescriptor* descP);
static ERL_NIF_TERM esock_finalize_close(ErlNifEnv* env,
ESockDescriptor* descP);
+static int esock_close_socket(ErlNifEnv* env,
+ ESockDescriptor* descP);
extern char* encode_msghdr(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -2589,7 +2663,6 @@ static void inform_waiting_procs(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
ESockRequestQueue* q,
- BOOLEAN_T free,
ERL_NIF_TERM reason);
static int socket_setopt(int sock,
@@ -2598,8 +2671,6 @@ static int socket_setopt(int sock,
const void* optVal,
const socklen_t optLen);
-static BOOLEAN_T is_connector(ErlNifEnv* env,
- ESockDescriptor* descP);
static BOOLEAN_T verify_is_connected(ESockDescriptor* descP, int* err);
static ESockDescriptor* alloc_descriptor(SOCKET sock, HANDLE event);
@@ -2618,7 +2689,9 @@ static BOOLEAN_T elevel2level(BOOLEAN_T isEncoded,
BOOLEAN_T* isOTP,
int* level);
#ifdef HAVE_SETNS
-static BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns);
+static BOOLEAN_T esock_open4_get_netns(ErlNifEnv* env,
+ ERL_NIF_TERM opts,
+ char** netns);
static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err);
static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err);
#endif
@@ -2688,7 +2761,11 @@ ESOCK_OPERATOR_FUNCS_DEFS
static BOOLEAN_T requestor_pop(ESockRequestQueue* q,
ESockRequestor* reqP);
-static void requestor_clear(ESockRequestor* reqP);
+static void requestor_init(ESockRequestor* reqP);
+static int requestor_release(const char* slogan,
+ ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockRequestor* reqP);
static BOOLEAN_T qsearch4pid(ErlNifEnv* env,
ESockRequestQueue* q,
@@ -2714,10 +2791,8 @@ static int esock_demonitor(const char* slogan,
static void esock_monitor_init(ESockMonitor* mon);
static ERL_NIF_TERM esock_make_monitor_term(ErlNifEnv* env,
const ESockMonitor* monP);
-static void esock_release_current(const char* slogan,
- ErlNifEnv* env,
- ESockDescriptor* descP,
- ESockRequestor* current);
+static BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP,
+ const ErlNifMonitor* mon);
#endif // if defined(__WIN32__)
@@ -2731,7 +2806,7 @@ static size_t my_strnlen(const char *s, size_t maxlen);
static void esock_dtor(ErlNifEnv* env, void* obj);
static void esock_stop(ErlNifEnv* env,
void* obj,
- int fd,
+ ErlNifEvent fd,
int is_direct_call);
static void esock_down(ErlNifEnv* env,
void* obj,
@@ -2743,15 +2818,18 @@ static void esock_down(ErlNifEnv* env,
static void esock_down_acceptor(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
- const ErlNifPid* pid);
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon);
static void esock_down_writer(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
- const ErlNifPid* pid);
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon);
static void esock_down_reader(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
- const ErlNifPid* pid);
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon);
static void esock_send_reg_add_msg(ErlNifEnv* env,
ERL_NIF_TERM sockRef);
@@ -2823,10 +2901,8 @@ static int esock_select_cancel(ErlNifEnv* env,
enum ErlNifSelectFlags mode,
void* obj);
-static BOOLEAN_T extract_debug(ErlNifEnv* env,
- ERL_NIF_TERM map);
-static BOOLEAN_T extract_iow(ErlNifEnv* env,
- ERL_NIF_TERM map);
+static char* extract_debug_filename(ErlNifEnv* env,
+ ERL_NIF_TERM map);
#endif // if defined(__WIN32__)
@@ -2858,7 +2934,7 @@ static const struct in6_addr in6addr_loopback =
/* (special) error string constants */
-static char str_exmon[] = "exmonitor"; // failed monitor
+static char str_exmonitor[] = "exmonitor"; // failed monitor
static char str_exself[] = "exself"; // failed self
static char str_exsend[] = "exsend"; // failed send
@@ -3085,19 +3161,19 @@ static char str_exsend[] = "exsend"; // failed send
GLOBAL_ATOM_DECL(user_timeout); \
GLOBAL_ATOM_DECL(use_ext_recvinfo); \
GLOBAL_ATOM_DECL(use_min_mtu); \
- GLOBAL_ATOM_DECL(v6only);
+ GLOBAL_ATOM_DECL(v6only)
/* *** Global error reason atoms *** */
#define GLOBAL_ERROR_REASON_ATOMS \
GLOBAL_ATOM_DECL(eagain); \
GLOBAL_ATOM_DECL(eafnosupport); \
- GLOBAL_ATOM_DECL(einval);
+ GLOBAL_ATOM_DECL(einval)
#define GLOBAL_ATOM_DECL(A) ERL_NIF_TERM esock_atom_##A
-GLOBAL_ATOMS
-GLOBAL_ERROR_REASON_ATOMS
+GLOBAL_ATOMS;
+GLOBAL_ERROR_REASON_ATOMS;
#undef GLOBAL_ATOM_DECL
ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket')
@@ -3123,11 +3199,14 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket')
LOCAL_ATOM_DECL(cookie_life); \
LOCAL_ATOM_DECL(counter_wrap); \
LOCAL_ATOM_DECL(counters); \
+ LOCAL_ATOM_DECL(ctype); \
LOCAL_ATOM_DECL(data_in); \
+ LOCAL_ATOM_DECL(debug_filename); \
LOCAL_ATOM_DECL(del); \
LOCAL_ATOM_DECL(dest_unreach); \
LOCAL_ATOM_DECL(do); \
LOCAL_ATOM_DECL(dont); \
+ LOCAL_ATOM_DECL(dup); \
LOCAL_ATOM_DECL(exclude); \
LOCAL_ATOM_DECL(false); \
LOCAL_ATOM_DECL(frag_needed); \
@@ -3147,10 +3226,12 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket')
LOCAL_ATOM_DECL(max_instreams); \
LOCAL_ATOM_DECL(max_rxt); \
LOCAL_ATOM_DECL(min); \
+ LOCAL_ATOM_DECL(missing); \
LOCAL_ATOM_DECL(mode); \
LOCAL_ATOM_DECL(multiaddr); \
LOCAL_ATOM_DECL(net_unknown); \
LOCAL_ATOM_DECL(net_unreach); \
+ LOCAL_ATOM_DECL(netns); \
LOCAL_ATOM_DECL(none); \
LOCAL_ATOM_DECL(noroute); \
LOCAL_ATOM_DECL(not_neighbour); \
@@ -3173,6 +3254,7 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket')
LOCAL_ATOM_DECL(num_tstreams); \
LOCAL_ATOM_DECL(num_writers); \
LOCAL_ATOM_DECL(offender); \
+ LOCAL_ATOM_DECL(options); \
LOCAL_ATOM_DECL(origin); \
LOCAL_ATOM_DECL(partial_delivery); \
LOCAL_ATOM_DECL(peer_error); \
@@ -3181,52 +3263,55 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket')
LOCAL_ATOM_DECL(policy_fail); \
LOCAL_ATOM_DECL(port_unreach); \
LOCAL_ATOM_DECL(probe); \
- LOCAL_ATOM_DECL(readable); \
LOCAL_ATOM_DECL(read_byte); \
LOCAL_ATOM_DECL(read_fails); \
LOCAL_ATOM_DECL(read_pkg); \
LOCAL_ATOM_DECL(read_pkg_max); \
LOCAL_ATOM_DECL(read_tries); \
LOCAL_ATOM_DECL(read_waits); \
+ LOCAL_ATOM_DECL(recv_flags); \
+ LOCAL_ATOM_DECL(registry); \
LOCAL_ATOM_DECL(reject_route); \
+ LOCAL_ATOM_DECL(remote); \
LOCAL_ATOM_DECL(select); \
LOCAL_ATOM_DECL(sender_dry); \
LOCAL_ATOM_DECL(send_failure); \
+ LOCAL_ATOM_DECL(send_flags); \
LOCAL_ATOM_DECL(shutdown); \
LOCAL_ATOM_DECL(slist); \
- LOCAL_ATOM_DECL(sock_err); \
+ LOCAL_ATOM_DECL(socket_debug); \
LOCAL_ATOM_DECL(sourceaddr); \
LOCAL_ATOM_DECL(time_exceeded); \
LOCAL_ATOM_DECL(timeout); \
LOCAL_ATOM_DECL(true); \
LOCAL_ATOM_DECL(txstatus); \
LOCAL_ATOM_DECL(txtime); \
+ LOCAL_ATOM_DECL(use_registry); \
LOCAL_ATOM_DECL(want); \
- LOCAL_ATOM_DECL(writable); \
LOCAL_ATOM_DECL(write_byte); \
LOCAL_ATOM_DECL(write_fails); \
LOCAL_ATOM_DECL(write_pkg); \
LOCAL_ATOM_DECL(write_pkg_max); \
LOCAL_ATOM_DECL(write_tries); \
LOCAL_ATOM_DECL(write_waits); \
- LOCAL_ATOM_DECL(zerocopy);
+ LOCAL_ATOM_DECL(zerocopy)
/* Local error reason atoms */
#define LOCAL_ERROR_REASON_ATOMS \
LOCAL_ATOM_DECL(econnreset); \
LOCAL_ATOM_DECL(eisconn); \
- LOCAL_ATOM_DECL(enotclosing); \
LOCAL_ATOM_DECL(enotconn); \
+ \
LOCAL_ATOM_DECL(exalloc); \
LOCAL_ATOM_DECL(exbadstate); \
- LOCAL_ATOM_DECL(exbusy); \
- LOCAL_ATOM_DECL(exmon); \
+ LOCAL_ATOM_DECL(exmonitor); \
+ LOCAL_ATOM_DECL(exselect); \
LOCAL_ATOM_DECL(exself); \
- LOCAL_ATOM_DECL(exsend);
+ LOCAL_ATOM_DECL(exsend)
#define LOCAL_ATOM_DECL(LA) static ERL_NIF_TERM atom_##LA
-LOCAL_ATOMS
-LOCAL_ERROR_REASON_ATOMS
+LOCAL_ATOMS;
+LOCAL_ERROR_REASON_ATOMS;
#undef LOCAL_ATOM_DECL
@@ -3277,6 +3362,7 @@ static ESOCK_INLINE ErlNifEnv* esock_alloc_env(const char* slogan)
*
* The "proper" socket functions:
* ------------------------------
+ * nif_open(FD, Extra)
* nif_open(Domain, Type, Protocol, Extra)
* nif_bind(Sock, LocalAddr)
* nif_connect(Sock, SockAddr)
@@ -3338,8 +3424,22 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env,
if (!ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) {
return enif_make_badarg(env);
}
- SSDBG( descP, ("SOCKET", "nif_info -> get socket info\r\n") );
+
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ SSDBG( descP, ("SOCKET", "nif_info(%T) {%d,%s,0x%X} -> get socket info\r\n",
+ argv[0], descP->sock, B2S(descP->closing),
+ descP->readState | descP->writeState) );
+
info = esock_socket_info(env, descP);
+
+ SSDBG( descP, ("SOCKET", "nif_info(%T) -> get socket info done with"
+ "\r\n info: %T"
+ "\r\n", argv[0], info) );
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
}
break;
@@ -3370,44 +3470,66 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP;
MLOCK(data.cntMtx);
- numBits = MKCT(env, atom_num_cnt_bits, ESOCK_COUNTER_SIZE);
- numSockets = MKCT(env, atom_num_sockets, data.numSockets);
- numTypeDGrams = MKCT(env, atom_num_tdgrams, data.numTypeDGrams);
- numTypeStreams = MKCT(env, atom_num_tstreams, data.numTypeStreams);
- numTypeSeqPkgs = MKCT(env, atom_num_tseqpkgs, data.numTypeSeqPkgs);
- numDomLocal = MKCT(env, atom_num_dlocal, data.numDomainLocal);
- numDomInet = MKCT(env, atom_num_dinet, data.numDomainInet);
- numDomInet6 = MKCT(env, atom_num_dinet6, data.numDomainInet6);
- numProtoIP = MKCT(env, atom_num_pip, data.numProtoIP);
- numProtoTCP = MKCT(env, atom_num_ptcp, data.numProtoTCP);
- numProtoUDP = MKCT(env, atom_num_pudp, data.numProtoUDP);
- numProtoSCTP = MKCT(env, atom_num_psctp, data.numProtoSCTP);
+ numBits = MKUI(env, ESOCK_COUNTER_SIZE);
+ numSockets = MKUI(env, data.numSockets);
+ numTypeDGrams = MKUI(env, data.numTypeDGrams);
+ numTypeStreams = MKUI(env, data.numTypeStreams);
+ numTypeSeqPkgs = MKUI(env, data.numTypeSeqPkgs);
+ numDomLocal = MKUI(env, data.numDomainLocal);
+ numDomInet = MKUI(env, data.numDomainInet);
+ numDomInet6 = MKUI(env, data.numDomainInet6);
+ numProtoIP = MKUI(env, data.numProtoIP);
+ numProtoTCP = MKUI(env, data.numProtoTCP);
+ numProtoUDP = MKUI(env, data.numProtoUDP);
+ numProtoSCTP = MKUI(env, data.numProtoSCTP);
MUNLOCK(data.cntMtx);
{
- ERL_NIF_TERM gcnt[] =
+ ERL_NIF_TERM gcntVals[] =
{numBits,
numSockets,
numTypeDGrams, numTypeStreams, numTypeSeqPkgs,
numDomLocal, numDomInet, numDomInet6,
numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP};
- unsigned int lenGCnt =
- sizeof(gcnt) / sizeof(ERL_NIF_TERM);
- ERL_NIF_TERM
- lgcnt = MKLA(env, gcnt, lenGCnt),
- keys[] = {esock_atom_debug, atom_iow, atom_counters},
- vals[] = {BOOL2ATOM(data.dbg), BOOL2ATOM(data.iow), lgcnt},
- info;
- unsigned int
- numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM),
- numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
-
- ESOCK_ASSERT( (numKeys == numVals) );
-
- if (!MKMA(env, keys, vals, numKeys, &info))
+ ERL_NIF_TERM gcntKeys[] =
+ {atom_num_cnt_bits,
+ atom_num_sockets,
+ atom_num_tdgrams, atom_num_tstreams, atom_num_tseqpkgs,
+ atom_num_dlocal, atom_num_dinet, atom_num_dinet6,
+ atom_num_pip, atom_num_ptcp, atom_num_pudp, atom_num_psctp};
+ unsigned int numGCntVals = sizeof(gcntVals) / sizeof(ERL_NIF_TERM);
+ unsigned int numGCntKeys = sizeof(gcntKeys) / sizeof(ERL_NIF_TERM);
+ ERL_NIF_TERM gcnt;
+
+ ESOCK_ASSERT( (numGCntKeys == numGCntVals) );
+
+ if (!MKMA(env, gcntKeys, gcntVals, numGCntKeys, &gcnt))
return enif_make_badarg(env);
- return info;
+ {
+ ERL_NIF_TERM
+ keys[] = {esock_atom_debug,
+ atom_socket_debug,
+ atom_use_registry,
+ atom_iow,
+ atom_counters},
+ vals[] = {BOOL2ATOM(data.dbg),
+ BOOL2ATOM(data.sockDbg),
+ BOOL2ATOM(data.useReg),
+ BOOL2ATOM(data.iow),
+ gcnt},
+ info;
+ unsigned int
+ numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM),
+ numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, &info))
+ return enif_make_badarg(env);
+
+ return info;
+ }
}
}
@@ -3419,10 +3541,6 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
* type: The type of the socket
* protocol: The protocol of the socket
* ctrl: Controlling process of the socket)
- * (readable: Is the socket readable)
- * (writable: Is the socket writable)
- * (connected: Is the socket connected)
- * (remote: (socket) Address of the peer (if connected))
* counters: A list of each socket counter and there current values
* readers: The number of current and waiting readers
* writers: The number of current and waiting writers
@@ -3436,53 +3554,49 @@ ERL_NIF_TERM esock_socket_info(ErlNifEnv* env,
ERL_NIF_TERM type = esock_socket_info_type(env, descP);
ERL_NIF_TERM protocol = esock_socket_info_protocol(env, descP);
ERL_NIF_TERM ctrlPid = MKPID(env, &descP->ctrlPid);
- ERL_NIF_TERM readable = BOOL2ATOM(descP->isReadable);
- ERL_NIF_TERM writable = BOOL2ATOM(descP->isWritable);
- // ERL_NIF_TERM connected = BOOL2ATOM(descP->isConnected);
+ ERL_NIF_TERM ctype = esock_socket_info_ctype(env, descP);
ERL_NIF_TERM counters = esock_socket_info_counters(env, descP);
ERL_NIF_TERM readers = esock_socket_info_readers(env, descP);
ERL_NIF_TERM writers = esock_socket_info_writers(env, descP);
ERL_NIF_TERM acceptors = esock_socket_info_acceptors(env, descP);
- ERL_NIF_TERM keys[] = {esock_atom_domain,
- esock_atom_type,
- esock_atom_protocol,
- esock_atom_ctrl,
- atom_readable,
- atom_writable,
- atom_counters,
- atom_num_readers,
- atom_num_writers,
- atom_num_acceptors};
- ERL_NIF_TERM vals[] = {domain,
- type,
- protocol,
- ctrlPid,
- readable,
- writable,
- counters,
- readers,
- writers,
- acceptors};
- ERL_NIF_TERM info;
- unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
- unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
- SSDBG( descP, ("SOCKET", "esock_socket_info -> "
- "\r\n numKeys: %d"
- "\r\n numVals: %d"
- "\r\n", numKeys, numVals) );
-
- ESOCK_ASSERT( (numKeys == numVals) );
+ {
+ ERL_NIF_TERM keys[]
+ = {esock_atom_domain,
+ esock_atom_type,
+ esock_atom_protocol,
+ esock_atom_ctrl,
+ atom_ctype,
+ atom_counters,
+ atom_num_readers,
+ atom_num_writers,
+ atom_num_acceptors};
+ ERL_NIF_TERM vals[]
+ = {domain,
+ type,
+ protocol,
+ ctrlPid,
+ ctype,
+ counters,
+ readers,
+ writers,
+ acceptors};
+ ERL_NIF_TERM info;
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ SSDBG( descP, ("SOCKET", "esock_socket_info -> "
+ "\r\n numKeys: %d"
+ "\r\n numVals: %d"
+ "\r\n", numKeys, numVals) );
- if (!MKMA(env, keys, vals, numKeys, &info))
- return enif_make_badarg(env);
+ ESOCK_ASSERT( (numKeys == numVals) );
- SSDBG( descP, ("SOCKET", "esock_socket_info -> done with"
- "\r\n info: %T"
- "\r\n", info) );
+ if (!MKMA(env, keys, vals, numKeys, &info))
+ return enif_make_badarg(env);
- return info;
-
+ return info;
+ }
}
@@ -3541,54 +3655,101 @@ ERL_NIF_TERM esock_socket_info_protocol(ErlNifEnv* env,
/*
+ * Encode the socket "create type"
+ * That is "show" how this socket was created:
+ *
+ * normal | fromfd | {fromfd, integer()}
+ */
+static
+ERL_NIF_TERM esock_socket_info_ctype(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ ERL_NIF_TERM ctype;
+
+ SSDBG( descP, ("SOCKET", "esock_socket_info_ctype {%d} -> entry with"
+ "\r\n origFD: %d"
+ "\r\n closeOnClose: %s"
+ "\r\n", descP->sock,
+ descP->origFD, B2S(descP->closeOnClose)) );
+
+ if (descP->origFD > 0) {
+ /* Created from other FD */
+ if (descP->closeOnClose) {
+ /* We *have* dup'ed: {fromfd, integer()} */
+ ctype = MKT2(env, MKA(env, "fromfd"), MKI(env, descP->origFD));
+ } else {
+ /* We have *not* dup'ed: fromfd */
+ ctype = MKA(env, "fromfd");
+ }
+ } else {
+ /* Normal socket */
+ ctype = MKA(env, "normal");
+ }
+
+ SSDBG( descP, ("SOCKET", "esock_socket_info_ctype {%d} -> done:"
+ "\r\n ctype: %T"
+ "\r\n", descP->sock, ctype) );
+
+ return ctype;
+}
+
+
+/*
* Collect all counters for a socket.
*/
static
ERL_NIF_TERM esock_socket_info_counters(ErlNifEnv* env,
ESockDescriptor* descP)
{
+ ERL_NIF_TERM keys[] = {atom_read_byte,
+ atom_read_fails,
+ atom_read_pkg,
+ atom_read_pkg_max,
+ atom_read_tries,
+ atom_read_waits,
+ atom_write_byte,
+ atom_write_fails,
+ atom_write_pkg,
+ atom_write_pkg_max,
+ atom_write_tries,
+ atom_write_waits,
+ atom_acc_success,
+ atom_acc_fails,
+ atom_acc_tries,
+ atom_acc_waits};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ ERL_NIF_TERM vals[] = {MKUI(env, descP->readByteCnt),
+ MKUI(env, descP->readFails),
+ MKUI(env, descP->readPkgCnt),
+ MKUI(env, descP->readPkgMax),
+ MKUI(env, descP->readTries),
+ MKUI(env, descP->readWaits),
+ MKUI(env, descP->writeByteCnt),
+ MKUI(env, descP->writeFails),
+ MKUI(env, descP->writePkgCnt),
+ MKUI(env, descP->writePkgMax),
+ MKUI(env, descP->writeTries),
+ MKUI(env, descP->writeWaits),
+ MKUI(env, descP->accSuccess),
+ MKUI(env, descP->accFails),
+ MKUI(env, descP->accTries),
+ MKUI(env, descP->accWaits)};
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
ERL_NIF_TERM info;
- MLOCK(descP->readMtx);
- MLOCK(descP->writeMtx);
+ SSDBG( descP, ("SOCKET", "esock_socket_info_counters -> "
+ "\r\n numKeys: %d"
+ "\r\n numVals: %d"
+ "\r\n", numKeys, numVals) );
- {
- ERL_NIF_TERM readByteCnt = MKCT(env, atom_read_byte, descP->readByteCnt);
- ERL_NIF_TERM readFails = MKCT(env, atom_read_fails, descP->readFails);
- ERL_NIF_TERM readPkgCnt = MKCT(env, atom_read_pkg, descP->readPkgCnt);
- ERL_NIF_TERM readPkgMax = MKCT(env, atom_read_pkg_max, descP->readPkgMax);
- ERL_NIF_TERM readTries = MKCT(env, atom_read_tries, descP->readTries);
- ERL_NIF_TERM readWaits = MKCT(env, atom_read_waits, descP->readWaits);
- ERL_NIF_TERM writeByteCnt = MKCT(env, atom_write_byte, descP->writeByteCnt);
- ERL_NIF_TERM writeFails = MKCT(env, atom_write_fails, descP->writeFails);
- ERL_NIF_TERM writePkgCnt = MKCT(env, atom_write_pkg, descP->writePkgCnt);
- ERL_NIF_TERM writePkgMax = MKCT(env, atom_write_pkg_max, descP->writePkgMax);
- ERL_NIF_TERM writeTries = MKCT(env, atom_write_tries, descP->writeTries);
- ERL_NIF_TERM writeWaits = MKCT(env, atom_write_waits, descP->writeWaits);
-
- ERL_NIF_TERM accSuccess = MKCT(env, atom_acc_success, descP->accSuccess);
- ERL_NIF_TERM accFails = MKCT(env, atom_acc_fails, descP->accFails);
- ERL_NIF_TERM accTries = MKCT(env, atom_acc_tries, descP->accTries);
- ERL_NIF_TERM accWaits = MKCT(env, atom_acc_waits, descP->accWaits);
- ERL_NIF_TERM acnt[] = {readByteCnt, readFails, readPkgCnt, readPkgMax,
- readTries, readWaits,
- writeByteCnt, writeFails, writePkgCnt, writePkgMax,
- writeTries, writeWaits,
- accSuccess, accFails, accTries, accWaits};
- unsigned int lenACnt = sizeof(acnt) / sizeof(ERL_NIF_TERM);
-
- info = MKLA(env, acnt, lenACnt);
+ ESOCK_ASSERT( (numKeys == numVals) );
+ if (!MKMA(env, keys, vals, numKeys, &info)) {
SSDBG( descP, ("SOCKET", "esock_socket_info_counters -> "
- "\r\n lenACnt: %d"
- "\r\n info: %T"
- "\r\n", lenACnt, info) );
-
+ "failed creating counters map\r\n") );
+ return enif_make_badarg(env);
}
- MUNLOCK(descP->writeMtx);
- MUNLOCK(descP->readMtx);
-
SSDBG( descP, ("SOCKET", "esock_socket_info_counters -> done with"
"\r\n info: %T"
"\r\n", info) );
@@ -3671,6 +3832,14 @@ ERL_NIF_TERM esock_command(ErlNifEnv* env, Uint16 cmd, ERL_NIF_TERM ecdata)
result = esock_command_debug(env, ecdata);
break;
+ case ESOCK_CMD_SOCKET_DEBUG:
+ result = esock_command_socket_debug(env, ecdata);
+ break;
+
+ case ESOCK_CMD_USE_SOCKET_REGISTRY:
+ result = esock_command_use_socket_registry(env, ecdata);
+ break;
+
default:
result = esock_make_error(env, esock_atom_einval);
break;
@@ -3679,8 +3848,6 @@ ERL_NIF_TERM esock_command(ErlNifEnv* env, Uint16 cmd, ERL_NIF_TERM ecdata)
return result;
}
-
-
static
ERL_NIF_TERM esock_command_debug(ErlNifEnv* env, ERL_NIF_TERM ecdata)
{
@@ -3702,6 +3869,61 @@ ERL_NIF_TERM esock_command_debug(ErlNifEnv* env, ERL_NIF_TERM ecdata)
return result;
}
+
+static
+ERL_NIF_TERM esock_command_socket_debug(ErlNifEnv* env, ERL_NIF_TERM ecdata)
+{
+ BOOLEAN_T dbg = FALSE;
+
+ /* The data *should* be a boolean() */
+
+ if (COMPARE(ecdata, esock_atom_true) == 0) {
+ dbg = TRUE;
+ } else if (COMPARE(ecdata, esock_atom_false) == 0) {
+ dbg = FALSE;
+ } else {
+
+ SGDBG( ("SOCKET",
+ "esock_command_socket_debug -> invalid debug value: %T\r\n",
+ ecdata) );
+
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ MLOCK(data.cntMtx);
+ data.sockDbg = dbg;
+ MUNLOCK(data.cntMtx);
+
+ return esock_atom_ok;;
+}
+
+static
+ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv* env, ERL_NIF_TERM ecdata)
+{
+ BOOLEAN_T useReg = FALSE;
+
+ /* The data *should* be a boolean() */
+
+ if (COMPARE(ecdata, esock_atom_true) == 0) {
+ useReg = TRUE;
+ } else if (COMPARE(ecdata, esock_atom_false) == 0) {
+ useReg = FALSE;
+ } else {
+
+ SGDBG( ("SOCKET",
+ "esock_command_use_socket_registry -> invalid debug value: %T\r\n",
+ ecdata) );
+
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ MLOCK(data.cntMtx);
+ data.useReg = useReg;
+ MUNLOCK(data.cntMtx);
+
+ return esock_atom_ok;;
+}
+
#endif
@@ -3715,17 +3937,17 @@ ERL_NIF_TERM esock_command_debug(ErlNifEnv* env, ERL_NIF_TERM ecdata)
*/
#if !defined(__WIN32__)
-#define ESOCK_INFO_REQ_FUNCS \
- ESOCK_INFO_REQ_FUNC_DECL(readers, readMtx, currentReaderP, readersQ) \
- ESOCK_INFO_REQ_FUNC_DECL(writers, writeMtx, currentWriterP, writersQ) \
- ESOCK_INFO_REQ_FUNC_DECL(acceptors, accMtx, currentAcceptorP, acceptorsQ)
-
-#define ESOCK_INFO_REQ_FUNC_DECL(F, MTX, CRP, Q) \
- static \
- ERL_NIF_TERM esock_socket_info_##F(ErlNifEnv* env, \
- ESockDescriptor* descP) \
- { \
- return socket_info_reqs(env, descP, descP->MTX, descP->CRP, &descP->Q); \
+#define ESOCK_INFO_REQ_FUNCS \
+ ESOCK_INFO_REQ_FUNC_DECL(readers, currentReaderP, readersQ) \
+ ESOCK_INFO_REQ_FUNC_DECL(writers, currentWriterP, writersQ) \
+ ESOCK_INFO_REQ_FUNC_DECL(acceptors, currentAcceptorP, acceptorsQ)
+
+#define ESOCK_INFO_REQ_FUNC_DECL(F, CRP, Q) \
+ static \
+ ERL_NIF_TERM esock_socket_info_##F(ErlNifEnv* env, \
+ ESockDescriptor* descP) \
+ { \
+ return socket_info_reqs(env, descP, descP->CRP, &descP->Q); \
}
ESOCK_INFO_REQ_FUNCS
#undef ESOCK_INFO_REQ_FUNC_DECL
@@ -3734,7 +3956,6 @@ ESOCK_INFO_REQ_FUNCS
static
ERL_NIF_TERM socket_info_reqs(ErlNifEnv* env,
ESockDescriptor* descP,
- ErlNifMutex* mtx,
ESockRequestor* crp,
ESockRequestQueue* q)
{
@@ -3742,8 +3963,6 @@ ERL_NIF_TERM socket_info_reqs(ErlNifEnv* env,
ERL_NIF_TERM info;
unsigned int cnt = 0;
- MLOCK(mtx);
-
if (crp != NULL) {
// We have an active requestor!
cnt++;
@@ -3756,8 +3975,6 @@ ERL_NIF_TERM socket_info_reqs(ErlNifEnv* env,
}
}
- MUNLOCK(mtx);
-
info = MKUI(env, cnt);
SSDBG( descP, ("SOCKET", "socket_info_reqs -> done with"
@@ -3789,6 +4006,9 @@ ERL_NIF_TERM socket_info_reqs(ErlNifEnv* env,
* sctp boolean()
* ipv6 boolean()
* local boolean()
+ * netns boolean()
+ * send_flags [{SendFlag, boolean()}]
+ * recv_flags [{RecvFlag, boolean()}]
*/
static
@@ -3799,97 +4019,141 @@ ERL_NIF_TERM nif_supports(ErlNifEnv* env,
#if defined(__WIN32__)
return enif_raise_exception(env, MKA(env, "notsup"));
#else
- int key;
+ int key2;
SGDBG( ("SOCKET", "nif_supports -> entry with %d args\r\n", argc) );
/* Extract arguments and perform preliminary validation */
- if ((argc != 1) ||
- !GET_INT(env, argv[0], &key)) {
+ if (argc == 0)
+ return esock_supports_0(env);
+
+ if (argc == 1)
+ return esock_supports_1(env, argv[0]);
+
+ if (! GET_INT(env, argv[1], &key2))
return enif_make_badarg(env);
- }
- return esock_supports(env, key);
+ if (argc == 2)
+ return esock_supports_2(env, argv[0], key2);
+
+ return enif_make_badarg(env);
#endif
}
-
-
-/* esock_supports - what features do we support
+/* esock_supports - what features do we support?
*
- * This is to prove information about what features actually
+ * This gives information about what features actually
* work on the current platform.
*/
#if !defined(__WIN32__)
+
static
-ERL_NIF_TERM esock_supports(ErlNifEnv* env, int key)
+ERL_NIF_TERM esock_supports_0(ErlNifEnv* env)
{
- ERL_NIF_TERM result;
+ SocketTArray opts = TARRAY_CREATE(8);
+ ERL_NIF_TERM is_supported, opts_list;
- SGDBG( ("SOCKET", "esock_supports -> entry with 0x%lX\r\n", key) );
+ SGDBG( ("SOCKET", "esock_supports_0 -> entry\r\n") );
- switch (key) {
- case ESOCK_SUPPORTS_OPTIONS:
- result = esock_supports_options(env);
- break;
+#if defined(HAVE_SCTP)
+ is_supported = esock_atom_true;
+#else
+ is_supported = esock_atom_false;
+#endif
+ TARRAY_ADD(opts, MKT2(env, esock_atom_sctp, is_supported));
+
+ /* Is this (test) really sufficient for testing if we support IPv6? */
+#if defined(HAVE_IPV6)
+ is_supported = esock_atom_true;
+#else
+ is_supported = esock_atom_false;
+#endif
+ TARRAY_ADD(opts, MKT2(env, esock_atom_ipv6, is_supported));
- case ESOCK_SUPPORTS_SCTP:
- result = esock_supports_sctp(env);
- break;
+#if defined(AF_LOCAL)
+ is_supported = esock_atom_true;
+#else
+ is_supported = esock_atom_false;
+#endif
+ TARRAY_ADD(opts, MKT2(env, esock_atom_local, is_supported));
- case ESOCK_SUPPORTS_IPV6:
- result = esock_supports_ipv6(env);
- break;
+#if defined(HAVE_SETNS)
+ is_supported = esock_atom_true;
+#else
+ is_supported = esock_atom_false;
+#endif
+ TARRAY_ADD(opts, MKT2(env, atom_netns, is_supported));
- case ESOCK_SUPPORTS_LOCAL:
- result = esock_supports_local(env);
- break;
+ TARRAY_TOLIST(opts, env, &opts_list);
+ return opts_list;
+}
- case ESOCK_SUPPORTS_SEND_FLAGS:
- result = esock_supports_send_flags(env);
- break;
+static
+ERL_NIF_TERM esock_supports_1(ErlNifEnv* env, ERL_NIF_TERM key)
+{
+ ERL_NIF_TERM result;
- case ESOCK_SUPPORTS_RECV_FLAGS:
- result = esock_supports_recv_flags(env);
- break;
+ SGDBG( ("SOCKET",
+ "esock_supports_2 -> entry"
+ "\r\n key: %T"
+ "\r\n", key) );
- default:
- result = esock_atom_false;
- break;
- }
+ if (COMPARE(key, atom_send_flags) == 0)
+ result = esock_supports_send_flags(env);
+ else if (COMPARE(key, atom_recv_flags) == 0)
+ result = esock_supports_recv_flags(env);
+ else
+ result = MKEL(env);
return result;
}
-#endif
-
-#if !defined(__WIN32__)
static
-ERL_NIF_TERM esock_supports_options(ErlNifEnv* env)
-{
- ERL_NIF_TERM sockOpts = esock_supports_options_socket(env);
- ERL_NIF_TERM sockOptsT = MKT2(env, esock_atom_socket, sockOpts);
- ERL_NIF_TERM ipOpts = esock_supports_options_ip(env);
- ERL_NIF_TERM ipOptsT = MKT2(env, esock_atom_ip, ipOpts);
- ERL_NIF_TERM ipv6Opts = esock_supports_options_ipv6(env);
- ERL_NIF_TERM ipv6OptsT = MKT2(env, esock_atom_ipv6, ipv6Opts);
- ERL_NIF_TERM tcpOpts = esock_supports_options_tcp(env);
- ERL_NIF_TERM tcpOptsT = MKT2(env, esock_atom_tcp, tcpOpts);
- ERL_NIF_TERM udpOpts = esock_supports_options_udp(env);
- ERL_NIF_TERM udpOptsT = MKT2(env, esock_atom_udp, udpOpts);
- ERL_NIF_TERM sctpOpts = esock_supports_options_sctp(env);
- ERL_NIF_TERM sctpOptsT = MKT2(env, esock_atom_sctp, sctpOpts);
- ERL_NIF_TERM optsA[] = {sockOptsT,
- ipOptsT, ipv6OptsT,
- tcpOptsT, udpOptsT, sctpOptsT};
- unsigned int lenOptsA = sizeof(optsA) / sizeof(ERL_NIF_TERM);
- ERL_NIF_TERM optsL = MKLA(env, optsA, lenOptsA);
+ERL_NIF_TERM esock_supports_2(ErlNifEnv* env, ERL_NIF_TERM key1, int key2)
+{
+ ERL_NIF_TERM result;
- return optsL;
+ SGDBG( ("SOCKET",
+ "esock_supports_2 -> entry"
+ "\r\n key1: %%T"
+ "\r\n key2: %d"
+ "\r\n", key1, key2) );
+
+ if (COMPARE(key1, atom_options) == 0) {
+
+ switch (key2) {
+ case ESOCK_OPT_LEVEL_SOCKET:
+ result = esock_supports_options_socket(env);
+ break;
+ case ESOCK_OPT_LEVEL_IP:
+ result = esock_supports_options_ip(env);
+ break;
+ case ESOCK_OPT_LEVEL_IPV6:
+ result = esock_supports_options_ipv6(env);
+ break;
+ case ESOCK_OPT_LEVEL_TCP:
+ result = esock_supports_options_tcp(env);
+ break;
+ case ESOCK_OPT_LEVEL_UDP:
+ result = esock_supports_options_udp(env);
+ break;
+ case ESOCK_OPT_LEVEL_SCTP:
+ result = esock_supports_options_sctp(env);
+ break;
+ default:
+ result = MKEL(env);
+ break;
+ }
+
+ } else {
+ result = MKEL(env);
+ }
+
+ return result;
}
-#endif
+#endif
#if !defined(__WIN32__)
@@ -4076,7 +4340,7 @@ ERL_NIF_TERM esock_supports_options_socket(ErlNifEnv* env)
/* *** ESOCK_OPT_SOCK_RCVTIMEO => SO_RCVTIMEO *** */
-#if defined(SO_RCVTIMEO)
+#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_true);
#else
tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_false);
@@ -4136,7 +4400,7 @@ ERL_NIF_TERM esock_supports_options_socket(ErlNifEnv* env)
/* *** ESOCK_OPT_SOCK_SNDTIMEO => SO_SNDTIMEO *** */
-#if defined(SO_SNDTIMEO)
+#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_true);
#else
tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_false);
@@ -5069,6 +5333,7 @@ ERL_NIF_TERM esock_supports_options_sctp(ErlNifEnv* env)
#endif
+#if 0
#if !defined(__WIN32__)
static
@@ -5127,6 +5392,25 @@ ERL_NIF_TERM esock_supports_local(ErlNifEnv* env)
#if !defined(__WIN32__)
static
+ERL_NIF_TERM esock_supports_netns(ErlNifEnv* env)
+{
+ ERL_NIF_TERM supports;
+
+#if defined(HAVE_SETNS)
+ supports = esock_atom_true;
+#else
+ supports = esock_atom_false;
+#endif
+
+ return supports;
+}
+#endif
+
+#endif // #if 0
+
+
+#if !defined(__WIN32__)
+static
ERL_NIF_TERM esock_supports_send_flags(ErlNifEnv* env)
{
SocketTArray sflags = TARRAY_CREATE(8);
@@ -5265,16 +5549,40 @@ ERL_NIF_TERM esock_supports_recv_flags(ErlNifEnv* env)
*
* Description:
* Create an endpoint for communication.
- *
- * Arguments:
+ * This function "exist" in two variants.
+ * One with two args and onewith four.
+ *
+ * Arguments (2):
+ * FD - File Descriptor (of an already open socket).
+ * Extra - A map with extra options.
+ * The options are:
+ * [M] dup - boolean() - Shall the fd be dup'ed or not.
+ * [O] bound - boolean() - Is the fd already bound.
+ * [O] domain - domain() - We may not be able to retrieve
+ * this on all platforms, and in
+ * *those* cases this *must* be
+ * provided.
+ * [O] type - type() - We may not be able to retrieve
+ * this on all platforms, and in
+ * *those* cases this *must* be
+ * provided.
+ * [O] proto - protocol() - We may not be able to retrieve
+ * this on all platforms, and in
+ * *those* cases this *must* be
+ * provided.
+ * [O] use_registry - boolean() - Shall we use the socket
+ * registry for this socket.
+ * Arguments (4):
* Domain - The domain, for example 'inet'
* Type - Type of socket, for example 'stream'
* Protocol - The protocol, for example 'tcp'
* Extra - A map with "obscure" options.
- * Currently the only allowed option is netns (network namespace).
+ * Currently the only allowed option are:
+ * netns - string() - Network namespace.
+ * use_registry - boolean() - Shall we use the socket
+ * registry for this socket.
* This is *only* allowed on linux!
- * We should also use this for the fd value, in case we should use
- * an already existing (file) descriptor.
+ *
*/
static
ERL_NIF_TERM nif_open(ErlNifEnv* env,
@@ -5284,72 +5592,351 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env,
#if defined(__WIN32__)
return enif_raise_exception(env, MKA(env, "notsup"));
#else
- int edomain, etype;
- ERL_NIF_TERM eproto; // This is normally an int, but can also be '{raw, int}'
- ERL_NIF_TERM emap;
- int domain, type, proto;
- char* netns;
ERL_NIF_TERM result;
- SGDBG( ("SOCKET", "nif_open -> entry with %d args\r\n", argc) );
-
- /* Extract arguments and perform preliminary validation */
+ SGDBG( ("SOCKET", "nif_open -> "
+ "\r\n argc: %d"
+ "\r\n", argc) );
- if ((argc != 4) ||
- !GET_INT(env, argv[0], &edomain) ||
- !GET_INT(env, argv[1], &etype) ||
- !IS_MAP(env, argv[3])) {
- return enif_make_badarg(env);
- }
- eproto = argv[2];
- emap = argv[3];
+ switch (argc) {
+ case 2:
+ /* The FD-version */
+ {
+ int fd;
+ ERL_NIF_TERM eopts;
- SGDBG( ("SOCKET", "nif_open -> "
- "\r\n edomain: %T"
- "\r\n etype: %T"
- "\r\n eproto: %T"
- "\r\n extra: %T"
- "\r\n", argv[0], argv[1], eproto, emap) );
-
- if (!edomain2domain(edomain, &domain)) {
- SGDBG( ("SOCKET", "nif_open -> invalid domain: %d\r\n", edomain) );
- return esock_make_error(env, esock_atom_einval);
+ if (!GET_INT(env, argv[0], &fd) ||
+ !IS_MAP(env, argv[1])) {
+ return enif_make_badarg(env);
+ }
+ eopts = argv[1];
+
+ SGDBG( ("SOCKET", "nif_open -> "
+ "\r\n FD: %d"
+ "\r\n eopts: %T"
+ "\r\n", fd, eopts) );
+
+ MLOCK(data.cntMtx);
+ result = esock_open2(env, fd, eopts);
+ MUNLOCK(data.cntMtx);
+ }
+ break;
+
+ case 4:
+ /* The normal version */
+ {
+ int edomain, etype;
+ ERL_NIF_TERM eproto; // integer() (normal case) | {raw, integer()}
+ ERL_NIF_TERM eopts;
+ int domain, type, proto;
+
+ /* Extract arguments and perform preliminary validation */
+
+ if (!GET_INT(env, argv[0], &edomain) ||
+ !GET_INT(env, argv[1], &etype) ||
+ !IS_MAP(env, argv[3])) {
+ return enif_make_badarg(env);
+ }
+ eproto = argv[2];
+ eopts = argv[3];
+
+ SGDBG( ("SOCKET", "nif_open -> "
+ "\r\n edomain: %T"
+ "\r\n etype: %T"
+ "\r\n eproto: %T"
+ "\r\n eopts: %T"
+ "\r\n", argv[0], argv[1], eproto, eopts) );
+
+ if (!edomain2domain(edomain, &domain)) {
+ SGDBG( ("SOCKET", "nif_open -> invalid domain: %d\r\n", edomain) );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ if (!etype2type(etype, &type)) {
+ SGDBG( ("SOCKET", "nif_open -> invalid type: %d\r\n", etype) );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ if (!eproto2proto(env, eproto, &proto)) {
+ SGDBG( ("SOCKET", "nif_open -> invalid protocol: %d\r\n", eproto) );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ MLOCK(data.cntMtx);
+ result = esock_open4(env, domain, type, proto, eopts);
+ MUNLOCK(data.cntMtx);
+ }
+ break;
+
+ default:
+ SGDBG( ("SOCKET", "nif_open -> invalid number of arguments: %d"
+ "\r\n", argc) );
+ result = enif_make_badarg(env);
+ break;
}
- if (!etype2type(etype, &type)) {
- SGDBG( ("SOCKET", "nif_open -> invalid type: %d\r\n", etype) );
- return esock_make_error(env, esock_atom_einval);
+ SGDBG( ("SOCKET", "nif_open -> done with result: "
+ "\r\n %T"
+ "\r\n", result) );
+
+ return result;
+
+#endif // if defined(__WIN32__)
+}
+
+
+/* esock_open - create an endpoint (from an existing fd) for communication
+ *
+ * Assumes the input has been validated.
+ *
+ * Normally we want debugging on (individual) sockets to be controlled
+ * by the sockets own debug flag. But since we don't even have a socket
+ * yet, we must use the global debug flag.
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM esock_open2(ErlNifEnv* env,
+ int fd,
+ ERL_NIF_TERM eopts)
+{
+ BOOLEAN_T dbg = esock_open_is_debug(env, eopts, data.sockDbg);
+ BOOLEAN_T useReg = esock_open_use_registry(env, eopts, data.useReg);
+ ESockDescriptor* descP;
+ ERL_NIF_TERM res, reason;
+ int domain, type, protocol;
+ int save_errno = 0;
+ BOOLEAN_T closeOnClose;
+ SOCKET sock;
+ HANDLE event;
+
+ SSDBG2( dbg,
+ ("SOCKET", "esock_open2 -> entry with"
+ "\r\n fd: %d"
+ "\r\n eopts: %T"
+ "\r\n", fd, eopts) );
+
+ /*
+ * Before we do anything else, we try to retrieve domain, type and protocol
+ * This information is either present in the eopts map or if not we need
+ * to "get" it from the system (getsockopt).
+ * Note that its not possible to get all of these on all platoforms,
+ * and in those cases the user *must* provide us with them (eopts).
+ *
+ * We try the system first (since its more reliable) and if that fails
+ * we check the eopts map. If neither one works, we *give up*!
+ */
+
+ if (!esock_open_which_domain(fd, &domain)) {
+ SSDBG2( dbg,
+ ("SOCKET",
+ "esock_open2 -> failed get domain from system\r\n") );
+ if (!esock_open2_get_domain(env, eopts, &domain)) {
+ reason = MKA(env, "epfnosupport");
+ return esock_make_error(env, reason);
+ }
+ }
+
+ if (!esock_open_which_type(fd, &type)) {
+ SSDBG2( dbg,
+ ("SOCKET", "esock_open2 -> failed get type from system\r\n") );
+ if (!esock_open2_get_type(env, eopts, &type)) {
+ reason = MKA(env, "esocktnosupport");
+ return esock_make_error(env, reason);
+
+ }
+ }
+
+ if (!esock_open_which_protocol(fd, &protocol)) {
+ SSDBG2( dbg,
+ ("SOCKET", "esock_open2 -> failed get protocol from system\r\n") );
+ if (!esock_open2_get_protocol(env, eopts, &protocol)) {
+ reason = MKA(env, "eprotonosupport");
+ return esock_make_error(env, reason);
+ }
}
- if (!eproto2proto(env, eproto, &proto)) {
- SGDBG( ("SOCKET", "nif_open -> invalid protocol: %d\r\n", eproto) );
- return esock_make_error(env, esock_atom_einval);
+
+ SSDBG2( dbg,
+ ("SOCKET", "esock_open2 -> "
+ "\r\n domain: %d"
+ "\r\n type: %d"
+ "\r\n protocol: %d"
+ "\r\n", domain, type, protocol) );
+
+
+ if (esock_open2_todup(env, eopts)) {
+ /* We shall dup the socket */
+ if (IS_SOCKET_ERROR(sock = dup(fd))) {
+ save_errno = sock_errno();
+
+ SSDBG2( dbg,
+ ("SOCKET",
+ "esock_open2 -> dup failed: %d\r\n",
+ save_errno) );
+
+ reason = MKA(env, erl_errno_id(save_errno));
+ return esock_make_error(env, reason);
+ }
+ closeOnClose = TRUE;
+ } else {
+ sock = fd;
+ closeOnClose = FALSE;
}
-#ifdef HAVE_SETNS
- /* We *currently* only support one extra option: netns */
- if (!emap2netns(env, emap, &netns)) {
- SGDBG( ("SOCKET", "nif_open -> namespace: %s\r\n", netns) );
+ event = sock;
+
+ SET_NONBLOCKING(sock);
+
+ /* Create and initiate the socket "descriptor" */
+ if ((descP = alloc_descriptor(sock, event)) == NULL) {
+ if (closeOnClose) sock_close(sock);
+ // Not sure if this is really the proper error, but...
return enif_make_badarg(env);
}
-#else
- netns = NULL;
-#endif
+ descP->domain = domain;
+ descP->type = type;
+ descP->protocol = protocol;
+ descP->closeOnClose = closeOnClose;
+ descP->origFD = fd;
- result = esock_open(env, domain, type, proto, netns);
+ /* Check if we are already connected, if so change state */
+ {
+ ESockAddress remote;
+ socklen_t addrLen = sizeof(remote);
+ sys_memzero((char *) &remote, addrLen);
+ if (sock_peer(descP->sock,
+ (struct sockaddr*) &remote,
+ &addrLen) == 0) {
+ SSDBG2( dbg, ("SOCKET", "esock_open2 -> connected\r\n") );
+ descP->writeState |= ESOCK_STATE_CONNECTED;
+ } else {
+ SSDBG2( dbg, ("SOCKET", "esock_open2 -> not connected\r\n") );
+ }
+ }
- SGDBG( ("SOCKET", "nif_open -> done with result: "
- "\r\n %T"
- "\r\n", result) );
+ /* And create the 'socket' resource */
+ res = enif_make_resource(env, descP);
+ enif_release_resource(descP);
- return result;
+ /* Keep track of the creator
+ * This should not be a problem, but just in case
+ * the *open* function is used with the wrong kind
+ * of environment...
+ */
+ if (enif_self(env, &descP->ctrlPid) == NULL)
+ return esock_make_error(env, atom_exself);
-#endif // if defined(__WIN32__)
+ if (MONP("esock_open2 -> ctrl",
+ env, descP,
+ &descP->ctrlPid,
+ &descP->ctrlMon) != 0)
+ return esock_make_error(env, atom_exmonitor);
+
+
+ descP->dbg = dbg;
+ descP->useReg = useReg;
+ inc_socket(domain, type, protocol);
+
+ /* And finally (maybe) update the registry.
+ * Shall we keep track of the fact that this socket is created elsewhere?
+ */
+ if (descP->useReg) esock_send_reg_add_msg(env, res);
+
+ SSDBG2( dbg,
+ ("SOCKET", "esock_open2 -> done: %T\r\n", res) );
+
+ return esock_make_ok2(env, res);
}
-/* esock_open - create an endpoint for communication
+/* The eextra contains a boolean 'dup' key. Defaults to TRUE.
+ */
+static
+BOOLEAN_T esock_open2_todup(ErlNifEnv* env, ERL_NIF_TERM eextra)
+{
+ return esock_get_bool_from_map(env, eextra, atom_dup, TRUE);
+}
+
+/* The eextra contains an integer 'domain' key.
+ */
+static
+BOOLEAN_T esock_open2_get_domain(ErlNifEnv* env,
+ ERL_NIF_TERM eopts, int* domain)
+{
+ ERL_NIF_TERM key = MKA(env, "domain");
+ int edomain;
+
+ SGDBG( ("SOCKET", "esock_open2_get_domain -> entry with"
+ "\r\n eopts: %T"
+ "\r\n", eopts) );
+
+ if (esock_get_int_from_map(env, eopts, key, &edomain)) {
+ /* decode */
+ if (edomain2domain(edomain, domain))
+ return TRUE;
+ else
+ return FALSE;
+ } else {
+ *domain = edomain; // Contains an "error code"
+ return FALSE;
+ }
+}
+
+/* The eextra contains an integer 'type' key.
+ */
+static
+BOOLEAN_T esock_open2_get_type(ErlNifEnv* env,
+ ERL_NIF_TERM eopts, int* type)
+{
+ ERL_NIF_TERM key = MKA(env, "type");
+ int etype;
+
+ SGDBG( ("SOCKET", "esock_open2_get_type -> entry with"
+ "\r\n eopts: %T"
+ "\r\n", eopts) );
+
+ if (esock_get_int_from_map(env, eopts, key, &etype)) {
+ /* decode */
+ if (etype2type(etype, type))
+ return TRUE;
+ else
+ return FALSE;
+ } else {
+ *type = etype; // Contains an "error code"
+ return FALSE;
+ }
+}
+
+/* The eextra contains an integer 'protocol' key.
+ */
+static
+BOOLEAN_T esock_open2_get_protocol(ErlNifEnv* env,
+ ERL_NIF_TERM eopts, int* protocol)
+{
+ ERL_NIF_TERM key = MKA(env, "protocol");
+ int eproto;
+
+ SGDBG( ("SOCKET", "esock_open2_get_protocol -> entry with"
+ "\r\n eopts: %T"
+ "\r\n", eopts) );
+
+ if (esock_get_int_from_map(env, eopts, key, &eproto)) {
+ /* decode */
+ if (eproto2proto(env, eproto, protocol))
+ return TRUE;
+ else
+ return FALSE;
+ } else {
+ *protocol = eproto; // Contains an "error code"
+ return FALSE;
+ }
+}
+
+#endif // if defined(__WIN32__)
+
+
+/* esock_open4 - create an endpoint for communication
*
* Assumes the input has been validated.
*
@@ -5358,38 +5945,57 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env,
* yet, we must use the global debug flag.
*/
#if !defined(__WIN32__)
-
static
-ERL_NIF_TERM esock_open(ErlNifEnv* env,
- int domain, int type, int protocol,
- char* netns)
+ERL_NIF_TERM esock_open4(ErlNifEnv* env,
+ int domain,
+ int type,
+ int protocol,
+ ERL_NIF_TERM eopts)
{
+ BOOLEAN_T dbg = esock_open_is_debug(env, eopts, data.sockDbg);
+ BOOLEAN_T useReg = esock_open_use_registry(env, eopts, data.useReg);
ESockDescriptor* descP;
ERL_NIF_TERM res;
- int proto = protocol, save_errno = 0;
+ int proto = protocol, save_errno;
SOCKET sock;
HANDLE event;
+ char* netns;
#ifdef HAVE_SETNS
int current_ns = 0;
#endif
- SGDBG( ("SOCKET", "esock_open -> entry with"
- "\r\n domain: %d"
- "\r\n type: %d"
- "\r\n protocol: %d"
- "\r\n netns: %s"
- "\r\n", domain, type, protocol, ((netns == NULL) ? "NULL" : netns)) );
+ SSDBG2( dbg,
+ ("SOCKET", "esock_open4 -> entry with"
+ "\r\n domain: %d"
+ "\r\n type: %d"
+ "\r\n protocol: %d"
+ "\r\n eopts: %T"
+ "\r\n", domain, type, protocol, eopts) );
+
+
+#ifdef HAVE_SETNS
+ if (esock_open4_get_netns(env, eopts, &netns)) {
+ SGDBG( ("SOCKET", "nif_open -> namespace: %s\r\n", netns) );
+ }
+#else
+ netns = NULL;
+#endif
+
#ifdef HAVE_SETNS
if ((netns != NULL) &&
- !change_network_namespace(netns, &current_ns, &save_errno))
+ !change_network_namespace(netns, &current_ns, &save_errno)) {
+ FREE(netns);
return esock_make_error_errno(env, save_errno);
+ }
#endif
- if ((sock = sock_open(domain, type, proto)) == INVALID_SOCKET)
+ if (IS_SOCKET_ERROR(sock = sock_open(domain, type, proto))) {
+ if (netns != NULL) FREE(netns);
return esock_make_error_errno(env, sock_errno());
+ }
- SGDBG( ("SOCKET", "esock_open -> open success: %d\r\n", sock) );
+ SSDBG2( dbg, ("SOCKET", "esock_open -> open success: %d\r\n", sock) );
/* NOTE that if the protocol = 0 (default) and the domain is not
@@ -5404,12 +6010,14 @@ ERL_NIF_TERM esock_open(ErlNifEnv* env,
if (!esock_open_which_protocol(sock, &proto)) {
if (proto == ESOCK_WHICH_PROTO_ERROR) {
save_errno = sock_errno();
- while ((sock_close(sock) == INVALID_SOCKET) &&
+ while (IS_SOCKET_ERROR(sock_close(sock)) &&
(sock_errno() == EINTR));
+ if (netns != NULL) FREE(netns);
return esock_make_error_errno(env, save_errno);
} else {
- while ((sock_close(sock) == INVALID_SOCKET) &&
+ while (IS_SOCKET_ERROR(sock_close(sock)) &&
(sock_errno() == EINTR));
+ if (netns != NULL) FREE(netns);
return esock_make_error(env, esock_atom_eafnosupport);
}
}
@@ -5417,21 +6025,23 @@ ERL_NIF_TERM esock_open(ErlNifEnv* env,
#ifdef HAVE_SETNS
if ((netns != NULL) &&
- !restore_network_namespace(current_ns, sock, &save_errno))
+ !restore_network_namespace(current_ns, sock, &save_errno)) {
+ FREE(netns);
return esock_make_error_errno(env, save_errno);
+ }
+
+ if (netns != NULL) FREE(netns);
- if (netns != NULL)
- FREE(netns);
#endif
if ((event = sock_create_event(sock)) == INVALID_EVENT) {
save_errno = sock_errno();
- while ((sock_close(sock) == INVALID_SOCKET) && (sock_errno() == EINTR));
+ while (IS_SOCKET_ERROR(sock_close(sock)) && (sock_errno() == EINTR));
return esock_make_error_errno(env, save_errno);
}
- SGDBG( ("SOCKET", "esock_open -> event success: %d\r\n", event) );
+ SSDBG2( dbg, ("SOCKET", "esock_open4 -> event success: %d\r\n", event) );
SET_NONBLOCKING(sock);
@@ -5443,20 +6053,10 @@ ERL_NIF_TERM esock_open(ErlNifEnv* env,
return enif_make_badarg(env);
}
- descP->state = ESOCK_STATE_OPEN;
descP->domain = domain;
descP->type = type;
descP->protocol = proto;
- /* Does this apply to other types? Such as RAW?
- * Also, is this really correct? Should we not wait for bind?
- */
- if ((type == SOCK_DGRAM) ||
- (type == SOCK_SEQPACKET)) {
- descP->isReadable = TRUE;
- descP->isWritable = TRUE;
- }
-
/*
* Should we keep track of sockets (resources) in some way?
* Doing it here will require mutex to ensure data integrity,
@@ -5470,25 +6070,99 @@ ERL_NIF_TERM esock_open(ErlNifEnv* env,
* the *open* function is used with the wrong kind
* of environment...
*/
- if (enif_self(env, &descP->ctrlPid) == NULL)
+ if (enif_self(env, &descP->ctrlPid) == NULL) {
+ sock_close(sock);
+ descP->sock = INVALID_SOCKET;
return esock_make_error(env, atom_exself);
+ }
if (MONP("esock_open -> ctrl",
env, descP,
&descP->ctrlPid,
- &descP->ctrlMon) != 0)
- return esock_make_error(env, atom_exmon);
+ &descP->ctrlMon) != 0) {
+ sock_close(sock);
+ descP->sock = INVALID_SOCKET;
+ return esock_make_error(env, atom_exmonitor);
+ }
+ descP->dbg = data.sockDbg;
+ descP->useReg = useReg;
inc_socket(domain, type, protocol);
- /* And finally update the registry */
- esock_send_reg_add_msg(env, res);
+ /* And finally (maybe) update the registry */
+ if (descP->useReg) esock_send_reg_add_msg(env, res);
return esock_make_ok2(env, res);
}
+/* The eextra map "may" contain a boolean 'debug' key.
+ */
+static
+BOOLEAN_T esock_open_is_debug(ErlNifEnv* env, ERL_NIF_TERM eextra,
+ BOOLEAN_T dflt)
+{
+ return esock_get_bool_from_map(env, eextra, esock_atom_debug, dflt);
+}
+
+
+static
+BOOLEAN_T esock_open_use_registry(ErlNifEnv* env, ERL_NIF_TERM eextra,
+ BOOLEAN_T dflt)
+{
+ return esock_get_bool_from_map(env, eextra, atom_use_registry, dflt);
+}
+
+
+static
+BOOLEAN_T esock_open_which_domain(SOCKET sock, int* domain)
+{
+#if defined(SO_DOMAIN)
+ int val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ res = sock_getopt(sock, SOL_SOCKET, SO_DOMAIN, &val, &valSz);
+
+ if (res != 0) {
+ *domain = ESOCK_WHICH_DOMAIN_ERROR;
+ return FALSE;
+ } else {
+ *domain = val;
+ return TRUE;
+ }
+#else
+ *domain = ESOCK_WHICH_DOMAIN_UNSUP;
+ return FALSE;
+#endif
+}
+
+
+static
+BOOLEAN_T esock_open_which_type(SOCKET sock, int* type)
+{
+#if defined(SO_TYPE)
+ int val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ res = sock_getopt(sock, SOL_SOCKET, SO_TYPE, &val, &valSz);
+
+ if (res != 0) {
+ *type = ESOCK_WHICH_TYPE_ERROR;
+ return FALSE;
+ } else {
+ *type = val;
+ return TRUE;
+ }
+#else
+ *type = ESOCK_WHICH_TYPE_UNSUP;
+ return FALSE;
+#endif
+}
+
+
static
BOOLEAN_T esock_open_which_protocol(SOCKET sock, int* proto)
{
@@ -5533,19 +6207,20 @@ BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err)
int new_ns = 0;
SGDBG( ("SOCKET", "change_network_namespace -> entry with"
- "\r\n new ns: %s", netns) );
+ "\r\n new ns: %s"
+ "\r\n", netns) );
if (netns != NULL) {
current_ns = open("/proc/self/ns/net", O_RDONLY);
- if (current_ns == INVALID_SOCKET) {
+ if (IS_SOCKET_ERROR(current_ns)) {
*cns = current_ns;
*err = sock_errno();
return FALSE;
}
new_ns = open(netns, O_RDONLY);
- if (new_ns == INVALID_SOCKET) {
+ if (IS_SOCKET_ERROR(new_ns)) {
save_errno = sock_errno();
- while (close(current_ns) == INVALID_SOCKET &&
+ while (IS_SOCKET_ERROR(close(current_ns)) &&
sock_errno() == EINTR);
*cns = -1;
*err = save_errno;
@@ -5553,15 +6228,15 @@ BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err)
}
if (setns(new_ns, CLONE_NEWNET) != 0) {
save_errno = sock_errno();
- while ((close(new_ns) == INVALID_SOCKET) &&
+ while (IS_SOCKET_ERROR(close(new_ns)) &&
(sock_errno() == EINTR));
- while ((close(current_ns) == INVALID_SOCKET) &&
+ while (IS_SOCKET_ERROR(close(current_ns)) &&
(sock_errno() == EINTR));
*cns = -1;
*err = save_errno;
return FALSE;
} else {
- while ((close(new_ns) == INVALID_SOCKET) &&
+ while (IS_SOCKET_ERROR(close(new_ns)) &&
(sock_errno() == EINTR));
*cns = current_ns;
*err = 0;
@@ -5586,7 +6261,8 @@ BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err)
int save_errno;
SGDBG( ("SOCKET", "restore_network_namespace -> entry with"
- "\r\n ns: %d", ns) );
+ "\r\n ns: %d"
+ "\r\n", ns) );
if (ns != INVALID_SOCKET) {
if (setns(ns, CLONE_NEWNET) != 0) {
@@ -5597,15 +6273,15 @@ BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err)
*/
if (sock != INVALID_SOCKET)
save_errno = sock_errno();
- while (close(sock) == INVALID_SOCKET &&
+ while (IS_SOCKET_ERROR(close(sock)) &&
sock_errno() == EINTR);
sock = INVALID_SOCKET;
- while (close(ns) == INVALID_SOCKET &&
+ while (IS_SOCKET_ERROR(close(ns)) &&
sock_errno() == EINTR);
*err = save_errno;
return FALSE;
} else {
- while (close(ns) == INVALID_SOCKET &&
+ while (IS_SOCKET_ERROR(close(ns)) &&
sock_errno() == EINTR);
*err = 0;
return TRUE;
@@ -5639,7 +6315,7 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env,
return enif_raise_exception(env, MKA(env, "notsup"));
#else
ESockDescriptor* descP;
- ERL_NIF_TERM eSockAddr;
+ ERL_NIF_TERM eSockAddr, ret;
ESockAddress sockAddr;
unsigned int addrLen;
char* xres;
@@ -5654,26 +6330,27 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env,
}
eSockAddr = argv[1];
- if (IS_CLOSED(descP) || IS_CLOSING(descP))
- return esock_make_error(env, atom_closed);
-
+ if ((xres = esock_decode_sockaddr(env, eSockAddr, &sockAddr, &addrLen)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ MLOCK(descP->readMtx);
+
SSDBG( descP,
- ("SOCKET", "nif_bind -> args when sock = %d (0x%lX)"
- "\r\n Socket: %T"
+ ("SOCKET", "nif_bind(%T) {%d,%s,0x%X} ->"
"\r\n SockAddr: %T"
- "\r\n", descP->sock, descP->state, argv[0], eSockAddr) );
+ "\r\n",
+ argv[0], descP->sock, B2S(descP->closing), descP->readState,
+ eSockAddr) );
- /* Make sure we are ready
- * Not sure how this would even happen, but...
- */
- if (descP->state != ESOCK_STATE_OPEN)
- return esock_make_error(env, atom_exbadstate);
+ ret = esock_bind(env, descP, &sockAddr, addrLen);
- if ((xres = esock_decode_sockaddr(env, eSockAddr, &sockAddr, &addrLen)) != NULL)
- return esock_make_error_str(env, xres);
-
- return esock_bind(env, descP, &sockAddr, addrLen);
+ SSDBG( descP, ("SOCKET", "nif_bind(%T) -> done with"
+ "\r\n ret: %T"
+ "\r\n", argv[0], ret) );
+
+ MUNLOCK(descP->readMtx);
+ return ret;
#endif // if defined(__WIN32__)
}
@@ -5685,19 +6362,23 @@ ERL_NIF_TERM esock_bind(ErlNifEnv* env,
ESockAddress* sockAddrP,
unsigned int addrLen)
{
- int port, ntohs_port;
+ int port;
- SSDBG( descP, ("SOCKET", "esock_bind -> try bind\r\n") );
+ if (! IS_OPEN(descP))
+ return esock_make_error(env, atom_closed);
if (IS_SOCKET_ERROR(sock_bind(descP->sock,
(struct sockaddr*) sockAddrP, addrLen))) {
return esock_make_error_errno(env, sock_errno());
}
- SSDBG( descP, ("SOCKET", "esock_bind -> bound - get port\r\n") );
+ descP->readState |= ESOCK_STATE_BOUND;
port = which_address_port(sockAddrP);
- SSDBG( descP, ("SOCKET", "esock_bind -> port: %d\r\n", port) );
+
+ SSDBG( descP, ("SOCKET", "esock_bind {%d} -> port: %d\r\n",
+ descP->sock, sock_ntohs(port)) );
+
if (port == 0) {
SOCKLEN_T len = sizeof(ESockAddress);
sys_memzero((char *) sockAddrP, len);
@@ -5706,14 +6387,8 @@ ERL_NIF_TERM esock_bind(ErlNifEnv* env,
} else if (port == -1) {
port = 0;
}
-
- ntohs_port = sock_ntohs(port);
- SSDBG( descP, ("SOCKET",
- "esock_bind -> done with port = %d\r\n", ntohs_port) );
-
- return esock_make_ok2(env, MKI(env, ntohs_port));
-
+ return esock_make_ok2(env, MKI(env, sock_ntohs(port)));
}
#endif // if !defined(__WIN32__)
@@ -5741,47 +6416,60 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env,
return enif_raise_exception(env, MKA(env, "notsup"));
#else
ESockDescriptor* descP;
- ERL_NIF_TERM res, eSockAddr, sockRef;
+ ERL_NIF_TERM res, sockRef;
char* xres;
+ ESockAddress addr, *addrP;
+ socklen_t addrLen;
SGDBG( ("SOCKET", "nif_connect -> entry with argc: %d\r\n", argc) );
/* Extract arguments and perform preliminary validation */
- sockRef = argv[0];
- if ((argc != 2) ||
- !ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
+ if (argc >= 1) {
+ sockRef = argv[0];
+ if (! ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP))
+ return enif_make_badarg(env);
+ } else {
return enif_make_badarg(env);
}
- eSockAddr = argv[1];
- SSDBG( descP,
- ("SOCKET", "nif_connect -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n SockAddr: %T"
- "\r\n", descP->sock, argv[0], eSockAddr) );
-
- if ((xres = esock_decode_sockaddr(env, eSockAddr,
- &descP->remote,
- &descP->addrLen)) != NULL) {
- return esock_make_error_str(env, xres);
- }
+ if (argc >= 2) {
+ ERL_NIF_TERM eSockAddr = argv[1];
+ if ((xres = esock_decode_sockaddr(env, eSockAddr, &addr, &addrLen))
+ != NULL) {
+ return esock_make_error_str(env, xres);
+ }
+ addrP = &addr;
- /* Only a *!%&$*# would send an opened but non-connected socket
- * somewhere (before its actually usable), but just to be on the
- * safe side we do the best we can to avoid complications...
- */
+ MLOCK(descP->writeMtx);
- MLOCK(descP->readMtx);
- MLOCK(descP->writeMtx);
- MLOCK(descP->cfgMtx);
+ SSDBG( descP,
+ ("SOCKET", "nif_connect(%T), {%d,%s,0x%X} ->"
+ "\r\n SockAddr: %T"
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->writeState,
+ eSockAddr) );
+ } else {
+ addrP = NULL;
+ addrLen = 0;
- res = esock_connect(env, descP, sockRef);
+ MLOCK(descP->writeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_connect(%T), {%d,%s,0x%X} ->"
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->writeState
+ ) );
+ }
+
+ res = esock_connect(env, descP, sockRef, addrP, addrLen);
+
+ SSDBG( descP, ("SOCKET", "nif_connect(%T) -> done with"
+ "\r\n res: %T"
+ "\r\n", sockRef, res) );
- MUNLOCK(descP->cfgMtx);
MUNLOCK(descP->writeMtx);
- MUNLOCK(descP->readMtx);
return res;
@@ -5793,204 +6481,125 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env,
static
ERL_NIF_TERM esock_connect(ErlNifEnv* env,
ESockDescriptor* descP,
- ERL_NIF_TERM sockRef)
+ ERL_NIF_TERM sockRef,
+ ESockAddress* addrP,
+ socklen_t addrLen)
{
- ERL_NIF_TERM res, ref;
- int code, sres, save_errno = 0;
+ int sres, save_errno;
+ ErlNifPid self;
/*
- * Verify that we are where in the proper state
+ * Verify that we are in the proper state
*/
- if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ if (! IS_OPEN(descP))
return esock_make_error(env, atom_closed);
- if (!IS_OPEN(descP)) {
- SSDBG( descP, ("SOCKET", "esock_connect -> not open\r\n") );
- return esock_make_error(env, atom_exbadstate);
- }
-
- if (IS_CONNECTED(descP)) {
- SSDBG( descP, ("SOCKET", "esock_connect -> already connected\r\n") );
- return esock_make_error(env, atom_eisconn);
- }
-
- if (IS_CONNECTING(descP) && !is_connector(env, descP)) {
- SSDBG( descP, ("SOCKET", "esock_connect -> already connecting\r\n") );
- return esock_make_error(env, esock_atom_einval);
- }
-
+ if (enif_self(env, &self) == NULL)
+ return esock_make_error(env, atom_exself);
- /*
- * And attempt to connect
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
*/
+ if (descP->currentWriterP != NULL)
+ return esock_make_error(env, esock_atom_einval);
- code = sock_connect(descP->sock,
- (struct sockaddr*) &descP->remote,
- descP->addrLen);
- save_errno = sock_errno();
-
- SSDBG( descP, ("SOCKET", "esock_connect -> connect result: %d, %d\r\n",
- code, save_errno) );
-
- if (IS_SOCKET_ERROR(code)) {
- switch (save_errno) {
- case ERRNO_BLOCK: /* Winsock2 */
- case EINPROGRESS: /* Unix & OSE!! */
- SSDBG( descP, ("SOCKET",
- "esock_connect -> would block => select\r\n") );
-
- ref = MKREF(env);
-
- if (IS_CONNECTING(descP)) {
- /* Glitch */
- res = esock_make_ok2(env, ref);
- } else {
-
- /* First time here */
-
- if (enif_self(env, &descP->connPid) == NULL)
- return esock_make_error(env, atom_exself);
-
- if (MONP("esock_connect -> conn",
- env, descP,
- &descP->connPid,
- &descP->connMon) != 0)
- return esock_make_error(env, atom_exmon);
-
- descP->state = ESOCK_STATE_CONNECTING;
-
- if ((sres = esock_select_write(env, descP->sock, descP, NULL,
- sockRef, ref)) < 0) {
- res = esock_make_error(env,
- MKT2(env,
- esock_atom_select_failed,
- MKI(env, sres)));
- } else {
- res = esock_make_ok2(env, ref);
- }
- }
- break;
-
- case EISCONN:
- SSDBG( descP, ("SOCKET",
- "esock_connect -> *already* connected\r\n") );
- {
- /* This is ***strange*** so make sure */
- int err = 0;
- if (!verify_is_connected(descP, &err)) {
- descP->state = ESOCK_STATE_OPEN; /* restore state */
- res = esock_make_error_errno(env, err);
- } else {
- descP->state = ESOCK_STATE_CONNECTED;
- /* And just to be on the safe side, reset these */
- enif_set_pid_undefined(&descP->connPid);
- DEMONP("esock_connect -> connected",
- env, descP, &descP->connMon);
- descP->isReadable = TRUE;
- descP->isWritable = TRUE;
- res = esock_atom_ok;
- }
- }
- break;
+ if (descP->connectorP != NULL) {
+ /* Connect in progress */
- default:
- SSDBG( descP, ("SOCKET", "esock_connect -> other error(1): %d\r\n",
- save_errno) );
- res = esock_make_error_errno(env, save_errno);
- break;
+ if (COMPARE_PIDS(&self, &descP->connector.pid) != 0) {
+ /* Other process has connect in progress */
+ return esock_make_error_errno(env, EALREADY);
}
- } else if (code == 0) { /* ok we are connected */
-
- SSDBG( descP, ("SOCKET", "esock_connect -> connected\r\n") );
+ /* Finalize after received select message */
- descP->state = ESOCK_STATE_CONNECTED;
- enif_set_pid_undefined(&descP->connPid);
- DEMONP("esock_connect -> connected", env, descP, &descP->connMon);
- descP->isReadable = TRUE;
- descP->isWritable = TRUE;
+ requestor_release("esock_connect finalize -> connected",
+ env, descP, descP->connectorP);
+ descP->connectorP = NULL;
- res = esock_atom_ok;
+ descP->writeState &= ~ESOCK_STATE_CONNECTING;
- } else {
- /* Do we really need this case? */
+ if (! verify_is_connected(descP, &save_errno)) {
+ return esock_make_error_errno(env, save_errno);
+ }
- SSDBG( descP, ("SOCKET", "esock_connect -> other error(2): %d\r\n",
- save_errno) );
+ descP->writeState |= ESOCK_STATE_CONNECTED;
- res = esock_make_error_errno(env, save_errno);
+ return esock_atom_ok;
}
- return res;
+ /* No connect in progress */
-}
-#endif // if !defined(__WIN32__)
+ if (addrP == NULL)
+ return esock_make_error(env, esock_atom_einval);
+
+ /* Initial connect call, with address */
+ if (sock_connect(descP->sock, (struct sockaddr*) addrP, addrLen) == 0) {
+ /* Success already! */
+ SSDBG( descP, ("SOCKET", "esock_connect {%d} -> connected\r\n",
+ descP->sock) );
-/* ----------------------------------------------------------------------
- * nif_finalize_connection
- *
- * Description:
- * Make socket ready for input and output.
- * This function is called if we where made to wait when we called the
- * nif_connect function (we made a select, and the select message has
- * now been received).
- *
- * Arguments:
- * Socket (ref) - Points to the socket descriptor.
- */
-static
-ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env,
- int argc,
- const ERL_NIF_TERM argv[])
-{
-#if defined(__WIN32__)
- return enif_raise_exception(env, MKA(env, "notsup"));
-#else
- ESockDescriptor* descP;
+ descP->writeState |= ESOCK_STATE_CONNECTED;
- /* Extract arguments and perform preliminary validation */
-
- if ((argc != 1) ||
- !ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) {
- return enif_make_badarg(env);
+ return esock_atom_ok;
}
- return esock_finalize_connection(env, descP);
+ /* Connect returned error */
+ save_errno = sock_errno();
-#endif
-}
+ switch (save_errno) {
+ case ERRNO_BLOCK: /* Winsock2 */
+ case EINPROGRESS: /* Unix & OSE!! */
+ SSDBG( descP,
+ ("SOCKET", "esock_connect {%d} -> would block => select\r\n",
+ descP->sock) );
+ {
+ /* Initiate connector */
-/* *** esock_finalize_connection ***
- * Perform the final check to verify a connection.
- */
-#if !defined(__WIN32__)
-static
-ERL_NIF_TERM esock_finalize_connection(ErlNifEnv* env,
- ESockDescriptor* descP)
-{
- int error;
+ descP->connector.pid = self;
+ if (MONP("esock_connect -> conn",
+ env, descP, &self, &descP->connector.mon) != 0) {
- if (!IS_CONNECTING(descP))
- return esock_make_error(env, atom_enotconn);
+ MON_INIT(&descP->connector.mon);
+ return esock_make_error(env, atom_exmonitor);
+ }
+ descP->connector.env = esock_alloc_env("connector");
+ descP->connectorP = &descP->connector;
+ descP->connector.ref = MKREF(descP->connector.env);
+
+ if ((sres =
+ esock_select_write(env, descP->sock, descP, NULL,
+ sockRef,
+ CP_TERM(env,
+ descP->connector.ref))) < 0) {
+
+ requestor_release("esock_connect -> select failed",
+ env, descP, descP->connectorP);
+ descP->connectorP = NULL;
+ return esock_make_error(env, atom_exselect);
+ } else {
+ descP->writeState |= ESOCK_STATE_CONNECTING;
+ return
+ MKT2(env, atom_select,
+ CP_TERM(env, descP->connector.ref));
+ }
+ }
+ break;
- if (!verify_is_connected(descP, &error)) {
- descP->state = ESOCK_STATE_OPEN; /* restore state */
- return esock_make_error_errno(env, error);
- }
+ default:
+ SSDBG( descP,
+ ("SOCKET", "esock_connect {%d} -> error: %d\r\n",
+ descP->sock, save_errno) );
- descP->state = ESOCK_STATE_CONNECTED;
- enif_set_pid_undefined(&descP->connPid);
- DEMONP("esock_finalize_connection -> connected",
- env, descP, &descP->connMon);
- descP->isReadable = TRUE;
- descP->isWritable = TRUE;
+ return esock_make_error_errno(env, save_errno);
- return esock_atom_ok;
+ } // switch(save_errno)
}
-#endif
+#endif // if !defined(__WIN32__)
+
/* *** verify_is_connected ***
@@ -6006,70 +6615,50 @@ BOOLEAN_T verify_is_connected(ESockDescriptor* descP, int* err)
* This *should* work on Windows NT too, but doesn't.
* An bug in Winsock 2.0 for Windows NT?
*
- * See "Unix Netwok Programming", W.R.Stevens, p 412 for a
- * discussion about Unix portability and non blocking connect.
+ * See "Unix Netwok Programming", "The Sockets Networking API",
+ * W.R.Stevens, Volume 1, third edition, 16.4 Nonblocking 'connect',
+ * before Interrupted 'connect' (p 412) for a discussion about
+ * Unix portability and non blocking connect.
*/
-#ifndef SO_ERROR
-
- int sz, code;
+ int error = 0;
- sz = sizeof(descP->remote);
- sys_memzero((char *) &descP->remote, sz);
- code = sock_peer(desc->sock,
- (struct sockaddr*) &descP->remote, &sz);
+#ifdef SO_ERROR
+ SOCKLEN_T sz = sizeof(error);
- if (IS_SOCKET_ERROR(code)) {
- *err = sock_errno();
- return FALSE;
+ if (IS_SOCKET_ERROR(sock_getopt(descP->sock, SOL_SOCKET, SO_ERROR,
+ (void *)&error, &sz))) {
+ // Solaris does it this way according to W.R.Stevens
+ error = sock_errno();
+ }
+#elif 1
+ char buf[0];
+ if (IS_SOCKET_ERROR(read(descP->sock, buf, 0))) {
+ error = sock_errno();
}
-
#else
+ /* This variant probably returns wrong error value
+ * ENOTCONN instead of the actual connect error
+ */
+ ESockAddress remote;
+ socklen_t addrLen = sizeof(remote);
+ sys_memzero((char *) &remote, addrLen);
+ if (IS_SOCKET_ERROR(sock_peer(descP->sock,
+ (struct sockaddr*) &remote, &addrLen))) {
+ error = sock_errno();
+ }
+#endif
- int error = 0; /* Has to be initiated, we check it */
- unsigned int sz = sizeof(error); /* even if we get -1 */
- int code = sock_getopt(descP->sock,
- SOL_SOCKET, SO_ERROR,
- (void *)&error, &sz);
-
- if ((code < 0) || error) {
+ if (error != 0) {
*err = error;
return FALSE;
}
-
-#endif /* SO_ERROR */
-
- *err = 0;
-
return TRUE;
}
#endif
-/* *** is_connector ***
- * Check if the current process is the connector process.
- */
-#if !defined(__WIN32__)
-static
-BOOLEAN_T is_connector(ErlNifEnv* env,
- ESockDescriptor* descP)
-{
- ErlNifPid caller;
-
- if (enif_self(env, &caller) == NULL)
- return FALSE;
-
- if (COMPARE_PIDS(&descP->connPid, &caller) == 0)
- return TRUE;
- else
- return FALSE;
-
-}
-#endif
-
-
-
/* ----------------------------------------------------------------------
* nif_listen
*
@@ -6091,6 +6680,7 @@ ERL_NIF_TERM nif_listen(ErlNifEnv* env,
#else
ESockDescriptor* descP;
int backlog;
+ ERL_NIF_TERM ret;
SGDBG( ("SOCKET", "nif_listen -> entry with argc: %d\r\n", argc) );
@@ -6102,14 +6692,24 @@ ERL_NIF_TERM nif_listen(ErlNifEnv* env,
return enif_make_badarg(env);
}
+ MLOCK(descP->readMtx);
+
SSDBG( descP,
- ("SOCKET", "nif_listen -> args when sock = %d:"
- "\r\n Socket: %T"
+ ("SOCKET", "nif_listen(%T), {%d,%s,0x%X} ->"
"\r\n backlog: %d"
- "\r\n", descP->sock, argv[0], backlog) );
-
- return esock_listen(env, descP, backlog);
+ "\r\n",
+ argv[0], descP->sock, B2S(descP->closing), descP->readState,
+ backlog) );
+
+ ret = esock_listen(env, descP, backlog);
+ SSDBG( descP, ("SOCKET", "nif_listen(%T) -> done with"
+ "\r\n ret: %T"
+ "\r\n", argv[0], ret) );
+
+ MUNLOCK(descP->readMtx);
+
+ return ret;
#endif // if defined(__WIN32__)
}
@@ -6123,18 +6723,11 @@ ERL_NIF_TERM esock_listen(ErlNifEnv* env,
{
/*
- * Verify that we are where in the proper state
+ * Verify that we are in the proper state
*/
- if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ if (! IS_OPEN(descP))
return esock_make_error(env, atom_closed);
-
- if (descP->state == ESOCK_STATE_CLOSED)
- return esock_make_error(env, atom_exbadstate);
-
- if (!IS_OPEN(descP))
- return esock_make_error(env, atom_exbadstate);
-
/*
* And attempt to make socket listening
@@ -6143,7 +6736,7 @@ ERL_NIF_TERM esock_listen(ErlNifEnv* env,
if (IS_SOCKET_ERROR(sock_listen(descP->sock, backlog)))
return esock_make_error_errno(env, sock_errno());
- descP->state = ESOCK_STATE_LISTENING;
+ descP->readState |= ESOCK_STATE_LISTENING;
return esock_atom_ok;
@@ -6169,7 +6762,9 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env,
const ERL_NIF_TERM argv[])
{
#if defined(__WIN32__)
+
return enif_raise_exception(env, MKA(env, "notsup"));
+
#else
ESockDescriptor* descP;
ERL_NIF_TERM sockRef, ref, res;
@@ -6185,24 +6780,19 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env,
}
ref = argv[1];
- MLOCK(descP->accMtx);
+ MLOCK(descP->readMtx);
SSDBG( descP,
- ("SOCKET", "nif_accept -> args when sock = %d:"
- "\r\n Socket: %T"
+ ("SOCKET", "nif_accept%T), {%d,%s,0x%X} ->"
"\r\n ReqRef: %T"
- "\r\nwhen"
- "\r\n State: %s"
- "\r\n Current Acceptor Addr: 0x%lX"
+ "\r\n Current Acceptor Addr: %p"
"\r\n Current Acceptor pid: %T"
"\r\n Current Acceptor mon: %T"
"\r\n Current Acceptor env: 0x%lX"
"\r\n Current Acceptor ref: %T"
"\r\n",
- descP->sock,
- sockRef, ref,
- ((descP->state == ESOCK_STATE_LISTENING) ? "listening" :
- ((descP->state == ESOCK_STATE_ACCEPTING) ? "accepting" : "other")),
+ sockRef, descP->sock, B2S(descP->closing), descP->readState,
+ ref,
descP->currentAcceptorP,
descP->currentAcceptor.pid,
esock_make_monitor_term(env, &descP->currentAcceptor.mon),
@@ -6211,102 +6801,99 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env,
res = esock_accept(env, descP, sockRef, ref);
- MUNLOCK(descP->accMtx);
+ SSDBG( descP, ("SOCKET", "nif_accept(%T) -> done with"
+ "\r\n res: %T"
+ "\r\n", sockRef, res) );
+
+ MUNLOCK(descP->readMtx);
return res;
#endif // if defined(__WIN32__)
}
-
#if !defined(__WIN32__)
+
static
ERL_NIF_TERM esock_accept(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
- ERL_NIF_TERM ref)
+ ERL_NIF_TERM accRef)
{
- ERL_NIF_TERM res;
+ ErlNifPid caller;
- if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ if (! IS_OPEN(descP))
return esock_make_error(env, atom_closed);
-
- switch (descP->state) {
- case ESOCK_STATE_LISTENING:
- res = esock_accept_listening(env, descP, sockRef, ref);
- break;
- case ESOCK_STATE_ACCEPTING:
- res = esock_accept_accepting(env, descP, sockRef, ref);
- break;
+ /* Accept and Read uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->currentReaderP != NULL)
+ return esock_make_error(env, esock_atom_einval);
- default:
- res = esock_make_error(env, esock_atom_einval);
- break;
- }
+ if (enif_self(env, &caller) == NULL)
+ return esock_make_error(env, atom_exself);
- return res;
-}
-#endif // if !defined(__WIN32__)
+ if (descP->currentAcceptorP == NULL) {
+ SOCKET accSock;
+ /* We have no active acceptor (and therefor no acceptors in queue)
+ */
-/* *** esock_accept_listening ***
- *
- * We have no active acceptor (and therefor no acceptors in queue).
- */
-#if !defined(__WIN32__)
-static
-ERL_NIF_TERM esock_accept_listening(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM accRef)
-{
- ESockAddress remote;
- unsigned int n;
- SOCKET accSock;
- int save_errno;
- ErlNifPid caller;
- ERL_NIF_TERM res;
+ SSDBG( descP, ("SOCKET", "esock_accept {%d} -> try accept\r\n",
+ descP->sock) );
- SSDBG( descP, ("SOCKET", "esock_accept_listening -> get caller\r\n") );
+ accSock = sock_accept(descP->sock, NULL, NULL);
- if (enif_self(env, &caller) == NULL)
- return esock_make_error(env, atom_exself);
+ if (IS_SOCKET_ERROR(accSock)) {
+ int save_errno;
- n = sizeof(remote);
- sys_memzero((char *) &remote, n);
- SSDBG( descP, ("SOCKET", "esock_accept_listening -> try accept\r\n") );
- accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n);
- if (accSock == INVALID_SOCKET) {
+ save_errno = sock_errno();
- save_errno = sock_errno();
+ return esock_accept_listening_error(env, descP, sockRef,
+ accRef, caller, save_errno);
+ } else {
+ /* We got an incoming connection */
+ return
+ esock_accept_listening_accept(env, descP, sockRef,
+ accSock, caller);
+ }
+ } else {
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_listening -> accept failed (%d)\r\n",
- save_errno) );
+ /* We have an active acceptor and possibly acceptors waiting in queue.
+ * If the pid of the calling process is not the pid of the "current process",
+ * push the requester onto the (acceptor) queue.
+ */
- res = esock_accept_listening_error(env, descP, sockRef, accRef,
- caller, save_errno);
+ SSDBG( descP, ("SOCKET", "esock_accept_accepting -> check: "
+ "is caller current acceptor:"
+ "\r\n Caller: %T"
+ "\r\n Current: %T"
+ "\r\n", caller, descP->currentAcceptor.pid) );
- } else {
+ if (COMPARE_PIDS(&descP->currentAcceptor.pid, &caller) == 0) {
- /*
- * We got one
- */
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_accept_accepting {%d} -> current acceptor\r\n",
+ descP->sock) );
- SSDBG( descP, ("SOCKET", "esock_accept_listening -> success\r\n") );
+ return esock_accept_accepting_current(env, descP, sockRef, accRef);
- res = esock_accept_listening_accept(env, descP,
- sockRef, accSock, caller, &remote);
+ } else {
- }
+ /* Not the "current acceptor", so (maybe) push onto queue */
- SSDBG( descP, ("SOCKET", "esock_accept_listening -> done: %T\r\n", res) );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_accept_accepting {%d} -> *not* current acceptor\r\n",
+ descP->sock) );
- return res;
-}
+ return esock_accept_accepting_other(env, descP, accRef, caller);
+ }
+ }
+}
/* *** esock_accept_listening_error ***
*
@@ -6330,7 +6917,9 @@ ERL_NIF_TERM esock_accept_listening_error(ErlNifEnv* env,
/* *** Try again later *** */
SSDBG( descP,
- ("SOCKET", "esock_accept_listening_error -> would block\r\n") );
+ ("SOCKET",
+ "esock_accept_listening_error {%d} -> would block\r\n",
+ descP->sock) );
ESOCK_CNT_INC(env, descP, sockRef, atom_acc_tries, &descP->accTries, 1);
@@ -6340,21 +6929,20 @@ ERL_NIF_TERM esock_accept_listening_error(ErlNifEnv* env,
&descP->currentAcceptor.pid,
&descP->currentAcceptor.mon) != 0) {
enif_set_pid_undefined(&descP->currentAcceptor.pid);
- res = esock_make_error(env, atom_exmon);
+ res = esock_make_error(env, atom_exmonitor);
} else {
ESOCK_ASSERT(!descP->currentAcceptor.env);
descP->currentAcceptor.env = esock_alloc_env("current acceptor");
descP->currentAcceptor.ref = CP_TERM(descP->currentAcceptor.env,
accRef);
descP->currentAcceptorP = &descP->currentAcceptor;
- res = esock_accept_busy_retry(env, descP,
- sockRef, accRef,
- NULL, ESOCK_STATE_ACCEPTING);
+ res = esock_accept_busy_retry(env, descP, sockRef, accRef, NULL);
}
} else {
SSDBG( descP,
("SOCKET",
- "esock_accept_listening -> errno: %d\r\n", save_errno) );
+ "esock_accept_listening {%d} -> errno: %d\r\n",
+ descP->sock, save_errno) );
ESOCK_CNT_INC(env, descP, sockRef, atom_acc_fails, &descP->accFails, 1);
@@ -6374,71 +6962,14 @@ ERL_NIF_TERM esock_accept_listening_accept(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
SOCKET accSock,
- ErlNifPid caller,
- ESockAddress* remote)
+ ErlNifPid caller)
{
ERL_NIF_TERM res;
- esock_accept_accepted(env, descP, sockRef, accSock, caller, remote, &res);
+ esock_accept_accepted(env, descP, sockRef, accSock, caller, &res);
return res;
}
-#endif // if !defined(__WIN32__)
-
-
-
-/* *** esock_accept_accepting ***
- *
- * We have an active acceptor and possibly acceptors waiting in queue.
- * If the pid of the calling process is not the pid of the "current process",
- * push the requester onto the (acceptor) queue.
- */
-#if !defined(__WIN32__)
-static
-ERL_NIF_TERM esock_accept_accepting(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- ERL_NIF_TERM ref)
-{
- ErlNifPid caller;
- ERL_NIF_TERM res;
-
- SSDBG( descP, ("SOCKET", "esock_accept_accepting -> get caller\r\n") );
-
- if (enif_self(env, &caller) == NULL)
- return esock_make_error(env, atom_exself);
-
- SSDBG( descP, ("SOCKET", "esock_accept_accepting -> check: "
- "are caller current acceptor:"
- "\r\n Caller: %T"
- "\r\n Current: %T"
- "\r\n", caller, descP->currentAcceptor.pid) );
-
- if (COMPARE_PIDS(&descP->currentAcceptor.pid, &caller) == 0) {
-
- SSDBG( descP,
- ("SOCKET", "esock_accept_accepting -> current acceptor\r\n") );
-
- res = esock_accept_accepting_current(env, descP, sockRef, ref);
-
- } else {
-
- /* Not the "current acceptor", so (maybe) push onto queue */
-
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting -> *not* current acceptor\r\n") );
-
- res = esock_accept_accepting_other(env, descP, ref, caller);
-
- }
-
- SSDBG( descP, ("SOCKET", "esock_accept_accepting -> done: %T\r\n", res) );
-
- return res;
-
-}
-
/* *** esock_accept_accepting_current ***
@@ -6450,37 +6981,27 @@ ERL_NIF_TERM esock_accept_accepting_current(ErlNifEnv* env,
ERL_NIF_TERM sockRef,
ERL_NIF_TERM accRef)
{
- ESockAddress remote;
- unsigned int n;
SOCKET accSock;
int save_errno;
ERL_NIF_TERM res;
- SSDBG( descP, ("SOCKET",
- "esock_accept_accepting_current -> try accept\r\n") );
- n = sizeof(descP->remote);
- sys_memzero((char *) &remote, n);
- accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n);
- if (accSock == INVALID_SOCKET) {
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_accept_accepting_current {%d} -> try accept\r\n",
+ descP->sock) );
- save_errno = sock_errno();
+ accSock = sock_accept(descP->sock, NULL, NULL);
- SSDBG( descP,
- ("SOCKET",
- "esock_accept_accepting_current -> accept failed: %d\r\n",
- save_errno) );
+ if (IS_SOCKET_ERROR(accSock)) {
+
+ save_errno = sock_errno();
res = esock_accept_accepting_current_error(env, descP, sockRef,
accRef, save_errno);
-
} else {
- SSDBG( descP, ("SOCKET",
- "esock_accept_accepting_current -> accepted\r\n") );
-
res = esock_accept_accepting_current_accept(env, descP, sockRef,
- accSock, &remote);
-
+ accSock);
}
return res;
@@ -6495,22 +7016,27 @@ static
ERL_NIF_TERM esock_accept_accepting_current_accept(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
- SOCKET accSock,
- ESockAddress* remote)
+ SOCKET accSock)
{
ERL_NIF_TERM res;
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_accept_accepting_current_accept {%d}"
+ "\r\n", descP->sock) );
+
if (esock_accept_accepted(env, descP, sockRef, accSock,
- descP->currentAcceptor.pid, remote, &res)) {
+ descP->currentAcceptor.pid, &res)) {
if (!activate_next_acceptor(env, descP, sockRef)) {
SSDBG( descP,
("SOCKET",
- "esock_accept_accepting_current_accept -> "
- "no more acceptors\r\n") );
+ "esock_accept_accepting_current_accept {%d} ->"
+ " no more acceptors"
+ "\r\n", descP->sock) );
- descP->state = ESOCK_STATE_LISTENING;
+ descP->readState &= ~ESOCK_STATE_ACCEPTING;
descP->currentAcceptorP = NULL;
}
@@ -6544,23 +7070,26 @@ ERL_NIF_TERM esock_accept_accepting_current_error(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "esock_accept_accepting_current_error -> "
- "would block: try again\r\n") );
+ "esock_accept_accepting_current_error {%d} -> "
+ "would block: try again\r\n", descP->sock) );
ESOCK_CNT_INC(env, descP, sockRef, atom_acc_waits, &descP->accWaits, 1);
res = esock_accept_busy_retry(env, descP, sockRef, opRef,
- &descP->currentAcceptor.pid,
- /* No state change */
- descP->state);
+ &descP->currentAcceptor.pid);
} else {
ESockRequestor req;
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_accept_accepting_current_error {%d} -> "
+ "error: %d\r\n", descP->sock, save_errno) );
+
ESOCK_CNT_INC(env, descP, sockRef, atom_acc_fails, &descP->accFails, 1);
- esock_release_current("esock_accept_accepting_current_error",
- env, descP, descP->currentAcceptorP);
+ requestor_release("esock_accept_accepting_current_error",
+ env, descP, descP->currentAcceptorP);
reason = MKA(env, erl_errno_id(save_errno));
res = esock_make_error(env, reason);
@@ -6569,8 +7098,8 @@ ERL_NIF_TERM esock_accept_accepting_current_error(ErlNifEnv* env,
while (acceptor_pop(env, descP, &req)) {
SSDBG( descP,
("SOCKET",
- "esock_accept_accepting_current_error -> abort %T\r\n",
- req.pid) );
+ "esock_accept_accepting_current_error {%d} -> abort %T\r\n",
+ descP->sock, req.pid) );
esock_send_abort_msg(env, sockRef, req.ref, req.env,
reason, &req.pid);
req.env = NULL;
@@ -6604,7 +7133,6 @@ ERL_NIF_TERM esock_accept_accepting_other(ErlNifEnv* env,
return result;
}
-#endif // if !defined(__WIN32__)
@@ -6612,17 +7140,15 @@ ERL_NIF_TERM esock_accept_accepting_other(ErlNifEnv* env,
*
* Perform a retry select. If successful, set nextState.
*/
-#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_accept_busy_retry(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
ERL_NIF_TERM accRef,
- ErlNifPid* pid,
- unsigned int nextState)
+ ErlNifPid* pid)
{
int sres;
- ERL_NIF_TERM res, reason;
+ ERL_NIF_TERM res;
if ((sres = esock_select_read(env, descP->sock, descP, pid,
sockRef, accRef)) < 0) {
@@ -6635,17 +7161,18 @@ ERL_NIF_TERM esock_accept_busy_retry(ErlNifEnv* env,
if (!activate_next_acceptor(env, descP, sockRef)) {
SSDBG( descP,
("SOCKET",
- "esock_accept_busy_retry -> no more acceptors\r\n") );
+ "esock_accept_busy_retry {%d} -> no more acceptors\r\n",
+ descP->sock) );
+
+ descP->readState &= ~ESOCK_STATE_ACCEPTING;
- descP->state = ESOCK_STATE_LISTENING;
descP->currentAcceptorP = NULL;
}
- reason = MKT2(env, esock_atom_select_failed, MKI(env, sres));
- res = esock_make_error(env, reason);
+ res = esock_make_error(env, atom_exselect);
} else {
- descP->state = nextState;
- res = esock_make_error(env, esock_atom_eagain); // OK!!
+ descP->readState |= ESOCK_STATE_ACCEPTING;
+ res = esock_make_error(env, esock_atom_eagain); // OK!!
}
return res;
@@ -6663,7 +7190,6 @@ BOOLEAN_T esock_accept_accepted(ErlNifEnv* env,
ERL_NIF_TERM sockRef,
SOCKET accSock,
ErlNifPid pid,
- ESockAddress* remote,
ERL_NIF_TERM* result)
{
ESockDescriptor* accDescP;
@@ -6679,7 +7205,7 @@ BOOLEAN_T esock_accept_accepted(ErlNifEnv* env,
if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) {
save_errno = sock_errno();
- while ((sock_close(accSock) == INVALID_SOCKET) &&
+ while (IS_SOCKET_ERROR(sock_close(accSock)) &&
(sock_errno() == EINTR));
*result = esock_make_error_errno(env, save_errno);
return FALSE;
@@ -6694,6 +7220,9 @@ BOOLEAN_T esock_accept_accepted(ErlNifEnv* env,
accDescP->domain = descP->domain;
accDescP->type = descP->type;
accDescP->protocol = descP->protocol;
+
+ MLOCK(descP->writeMtx);
+
accDescP->rBufSz = descP->rBufSz; // Inherit buffer size
accDescP->rNum = descP->rNum; // Inherit buffer uses
accDescP->rNumCnt = 0;
@@ -6701,6 +7230,7 @@ BOOLEAN_T esock_accept_accepted(ErlNifEnv* env,
accDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size
accDescP->iow = descP->iow; // Inherit iow
accDescP->dbg = descP->dbg; // Inherit debug flag
+ accDescP->useReg = descP->useReg; // Inherit useReg flag
accRef = enif_make_resource(env, accDescP);
enif_release_resource(accDescP);
@@ -6712,26 +7242,26 @@ BOOLEAN_T esock_accept_accepted(ErlNifEnv* env,
&accDescP->ctrlMon) != 0) {
sock_close(accSock);
enif_set_pid_undefined(&descP->ctrlPid);
- *result = esock_make_error(env, atom_exmon);
+ MUNLOCK(descP->writeMtx);
+ *result = esock_make_error(env, atom_exmonitor);
return FALSE;
}
- accDescP->remote = *remote;
SET_NONBLOCKING(accDescP->sock);
- accDescP->state = ESOCK_STATE_CONNECTED;
- accDescP->isReadable = TRUE;
- accDescP->isWritable = TRUE;
+ descP->writeState |= ESOCK_STATE_CONNECTED;
+
+ MUNLOCK(descP->writeMtx);
- /* And finally update the registry */
- esock_send_reg_add_msg(env, accRef);
+ /* And finally (maybe) update the registry */
+ if (descP->useReg) esock_send_reg_add_msg(env, accRef);
*result = esock_make_ok2(env, accRef);
return TRUE;
-
}
-#endif // if !defined(__WIN32__)
+
+#endif // if !defined(__WIN32__) /* for accept */
@@ -6781,23 +7311,22 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env,
return enif_make_badarg(env);
}
- SSDBG( descP,
- ("SOCKET", "nif_send -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n SendRef: %T"
- "\r\n Size of data: %d"
- "\r\n eFlags: 0x%lX"
- "\r\n", descP->sock, sockRef, sendRef, sndData.size, eflags) );
-
if (!esendflags2sendflags(eflags, &flags)) {
SSDBG( descP, ("SOCKET", "nif_send -> sendflags decode failed\r\n") );
return esock_make_error(env, esock_atom_einval);
}
- SSDBG( descP, ("SOCKET", "nif_send -> flags: 0x%lX\r\n", flags) );
-
MLOCK(descP->writeMtx);
+ SSDBG( descP,
+ ("SOCKET", "nif_send(%T), {%d,%s,0x%X} ->"
+ "\r\n SendRef: %T"
+ "\r\n Data size: %u"
+ "\r\n flags: 0x%X"
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->writeState,
+ sendRef, sndData.size, flags) );
+
/* We need to handle the case when another process tries
* to write at the same time.
* If the current write could not write its entire package
@@ -6808,8 +7337,15 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env,
res = esock_send(env, descP, sockRef, sendRef, &sndData, flags);
+ SSDBG( descP, ("SOCKET", "nif_send(%T) -> done with"
+ "\r\n res: %T"
+ "\r\n", sockRef, res) );
+
MUNLOCK(descP->writeMtx);
+ SGDBG( ("SOCKET", "nif_send -> done with result: "
+ "\r\n %T"
+ "\r\n", res) );
return res;
#endif // if defined(__WIN32__)
@@ -6837,15 +7373,20 @@ ERL_NIF_TERM esock_send(ErlNifEnv* env,
ssize_t written;
ERL_NIF_TERM writerCheck;
- if (!descP->isWritable) {
- SSDBG( descP, ("SOCKET", "esock_send -> return not writable\r\n") );
+ if (! IS_OPEN(descP))
return esock_make_error(env, atom_closed);
- }
- /* Ensure that we either have no current writer or that we are it */
- if (!send_check_writer(env, descP, sendRef, &writerCheck)) {
- SSDBG( descP, ("SOCKET", "esock_send -> writer check failed: "
- "\r\n %T\r\n", writerCheck) );
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->connectorP != NULL)
+ return esock_make_error(env, esock_atom_einval);
+
+ /* Ensure that we either have no current writer or we are it,
+ * or enqueue this process if there is a current writer */
+ if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
+ SSDBG( descP, ("SOCKET", "esock_send {%d} -> writer check failed: "
+ "\r\n %T\r\n", descP->sock, writerCheck) );
return writerCheck;
}
@@ -6858,7 +7399,7 @@ ERL_NIF_TERM esock_send(ErlNifEnv* env,
if (IS_SOCKET_ERROR(written))
save_errno = sock_errno();
else
- save_errno = -1; // The value does not actually matter in this case
+ save_errno = 0; // The value does not actually matter in this case
return send_check_result(env, descP,
written, sndDataP->size, save_errno,
@@ -6920,16 +7461,6 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
SSDBG( descP, ("SOCKET", "nif_sendto -> get resource failed\r\n") );
return enif_make_badarg(env);
}
-
- SSDBG( descP,
- ("SOCKET", "nif_sendto -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n sendRef: %T"
- "\r\n size of data: %d"
- "\r\n eSockAddr: %T"
- "\r\n eflags: %d"
- "\r\n",
- descP->sock, sockRef, sendRef, sndData.size, eSockAddr, eflags) );
if (!esendflags2sendflags(eflags, &flags)) {
SSDBG( descP, ("SOCKET", "nif_sendto -> sendflags decode failed\r\n") );
@@ -6946,14 +7477,24 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
MLOCK(descP->writeMtx);
+ SSDBG( descP,
+ ("SOCKET", "nif_sendto(%T), {%d,%s,0x%X} ->"
+ "\r\n sendRef: %T"
+ "\r\n Data size: %u"
+ "\r\n eSockAddr: %T"
+ "\r\n flags: 0x%X"
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->readState,
+ sendRef, sndData.size, eSockAddr, flags) );
+
res = esock_sendto(env, descP, sockRef, sendRef, &sndData, flags,
&remoteAddr, remoteAddrLen);
- MUNLOCK(descP->writeMtx);
+ SSDBG( descP, ("SOCKET", "nif_sendto(%T) -> done with"
+ "\r\n res: %T"
+ "\r\n", sockRef, res) );
- SGDBG( ("SOCKET", "nif_sendto -> done with result: "
- "\r\n %T"
- "\r\n", res) );
+ MUNLOCK(descP->writeMtx);
return res;
@@ -6976,15 +7517,20 @@ ERL_NIF_TERM esock_sendto(ErlNifEnv* env,
ssize_t written;
ERL_NIF_TERM writerCheck;
- if (!descP->isWritable) {
- SSDBG( descP, ("SOCKET", "esock_sendto -> return not writable\r\n") );
+ if (! IS_OPEN(descP))
return esock_make_error(env, atom_closed);
- }
- /* Ensure that we either have no current writer or we are it */
- if (!send_check_writer(env, descP, sendRef, &writerCheck)) {
- SSDBG( descP, ("SOCKET", "esock_sendto -> writer check failed: "
- "\r\n %T\r\n", writerCheck) );
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->connectorP != NULL)
+ return esock_make_error(env, esock_atom_einval);
+
+ /* Ensure that we either have no current writer or we are it,
+ * or enqueue this process if there is a current writer */
+ if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
+ SSDBG( descP, ("SOCKET", "esock_sendto {%d} -> writer check failed: "
+ "\r\n %T\r\n", descP->sock, writerCheck) );
return writerCheck;
}
@@ -7005,7 +7551,7 @@ ERL_NIF_TERM esock_sendto(ErlNifEnv* env,
if (IS_SOCKET_ERROR(written))
save_errno = sock_errno();
else
- save_errno = -1; // The value does not actually matter in this case
+ save_errno = 0; // The value does not actually matter in this case
return send_check_result(env, descP, written, dataP->size, save_errno,
sockRef, sendRef);
@@ -7059,14 +7605,6 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env,
return enif_make_badarg(env);
}
- SSDBG( descP,
- ("SOCKET", "nif_sendmsg -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n sendRef: %T"
- "\r\n eflags: %d"
- "\r\n",
- descP->sock, argv[0], sendRef, eflags) );
-
if (!esendflags2sendflags(eflags, &flags)) {
SSDBG( descP, ("SOCKET", "nif_sendmsg -> sendflags decode failed\r\n") );
return esock_make_error(env, esock_atom_einval);
@@ -7074,14 +7612,21 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env,
MLOCK(descP->writeMtx);
+ SSDBG( descP,
+ ("SOCKET", "nif_sendmsg(%T), {%d,%s,0x%X} ->"
+ "\r\n SendRef: %T"
+ "\r\n flags: 0x%X"
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->writeState,
+ sendRef, flags) );
+
res = esock_sendmsg(env, descP, sockRef, sendRef, eMsgHdr, flags);
MUNLOCK(descP->writeMtx);
- SSDBG( descP,
- ("SOCKET", "nif_sendmsg -> done with result: "
- "\r\n %T"
- "\r\n", res) );
+ SSDBG( descP, ("SOCKET", "nif_sendmsg(%T) -> done with"
+ "\r\n res: %T"
+ "\r\n", sockRef, res) );
return res;
@@ -7111,53 +7656,50 @@ ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
ERL_NIF_TERM writerCheck;
char* xres;
- if (!descP->isWritable) {
- SSDBG( descP, ("SOCKET", "esock_sendmsg -> not writable\r\n") );
- return esock_make_error(env, atom_closed);
- }
+ if (! IS_OPEN(descP))
+ return esock_make_error(env, atom_closed);
- /* Ensure that we either have no current writer or we are it */
- if (!send_check_writer(env, descP, sendRef, &writerCheck)) {
+ /* Connect and Write uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->connectorP != NULL)
+ return esock_make_error(env, esock_atom_einval);
+
+ /* Ensure that we either have no current writer or we are it,
+ * or enqueue this process if there is a current writer */
+ if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
SSDBG( descP,
- ("SOCKET", "esock_sendmsg -> writer check failed: "
- "\r\n %T\r\n", writerCheck) );
+ ("SOCKET", "esock_sendmsg {%d} -> writer check failed: "
+ "\r\n %T\r\n", descP->sock, writerCheck) );
return writerCheck;
}
- /* Depending on if we are *connected* or not, we require
- * different things in the msghdr map.
+ /* Initiate the .name and .namelen fields depending on if
+ * we have an address or not
*/
- if (IS_CONNECTED(descP)) {
-
- /* We don't need the address */
+ if (! GET_MAP_VAL(env, eMsgHdr, esock_atom_addr, &eAddr)) {
SSDBG( descP, ("SOCKET",
- "esock_sendmsg -> connected: no address\r\n") );
+ "esock_sendmsg {%d} -> no address\r\n", descP->sock) );
msgHdr.msg_name = NULL;
msgHdr.msg_namelen = 0;
-
} else {
-
- /* We need the address */
-
msgHdr.msg_name = (void*) &addr;
msgHdr.msg_namelen = sizeof(addr);
sys_memzero((char *) msgHdr.msg_name, msgHdr.msg_namelen);
- if (!GET_MAP_VAL(env, eMsgHdr, esock_atom_addr, &eAddr))
- return esock_make_error(env, esock_atom_einval);
- SSDBG( descP, ("SOCKET", "esock_sendmsg -> not connected: "
+ SSDBG( descP, ("SOCKET", "esock_sendmsg {%d} ->"
"\r\n address: %T"
- "\r\n", eAddr) );
+ "\r\n", descP->sock, eAddr) );
if ((xres = esock_decode_sockaddr(env, eAddr,
msgHdr.msg_name,
- &msgHdr.msg_namelen)) != NULL)
+ &msgHdr.msg_namelen)) != NULL) {
return esock_make_error_str(env, xres);
+ }
}
-
/* Extract the (other) attributes of the msghdr map: iov and maybe ctrl */
/* The *mandatory* iov, which must be a list */
@@ -7167,7 +7709,9 @@ ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
if (!GET_LIST_LEN(env, eIOV, &iovLen) && (iovLen > 0))
return esock_make_error(env, esock_atom_einval);
- SSDBG( descP, ("SOCKET", "esock_sendmsg -> iov length: %d\r\n", iovLen) );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_sendmsg {%d} -> iov length: %d\r\n", descP->sock, iovLen) );
iovBins = MALLOC(iovLen * sizeof(ErlNifBinary));
ESOCK_ASSERT( (iovBins != NULL) );
@@ -7175,7 +7719,7 @@ ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
iov = MALLOC(iovLen * sizeof(struct iovec));
ESOCK_ASSERT( (iov != NULL) );
- /* The *opional* ctrl */
+ /* The *optional* ctrl */
if (GET_MAP_VAL(env, eMsgHdr, esock_atom_ctrl, &eCtrl)) {
ctrlBufLen = descP->wCtrlSz;
ctrlBuf = (char*) MALLOC(ctrlBufLen);
@@ -7185,10 +7729,11 @@ ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
ctrlBufLen = 0;
ctrlBuf = NULL;
}
- SSDBG( descP, ("SOCKET", "esock_sendmsg -> optional ctrl: "
+ SSDBG( descP, ("SOCKET", "esock_sendmsg {%d} -> optional ctrl: "
"\r\n ctrlBuf: 0x%lX"
"\r\n ctrlBufLen: %d"
- "\r\n eCtrl: %T\r\n", ctrlBuf, ctrlBufLen, eCtrl) );
+ "\r\n eCtrl: %T"
+ "\r\n", descP->sock, ctrlBuf, ctrlBufLen, eCtrl) );
/* Decode the iov and initiate that part of the msghdr */
if ((xres = esock_decode_iov(env, eIOV,
@@ -7201,11 +7746,9 @@ ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
msgHdr.msg_iov = iov;
msgHdr.msg_iovlen = iovLen;
-
SSDBG( descP, ("SOCKET",
- "esock_sendmsg -> "
- "total (iov) data size: %d\r\n", dataSize) );
-
+ "esock_sendmsg {%d} -> "
+ "total (iov) data size: %d\r\n", descP->sock, dataSize) );
/* Decode the ctrl and initiate that part of the msghdr.
*/
@@ -7224,11 +7767,9 @@ ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
msgHdr.msg_control = ctrlBuf;
msgHdr.msg_controllen = ctrlBufUsed;
-
/* The msg-flags field is not used when sending, but zero it just in case */
msgHdr.msg_flags = 0;
-
/* We ignore the wrap for the moment.
* Maybe we should issue a wrap-message to controlling process...
*/
@@ -7240,7 +7781,7 @@ ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
if (IS_SOCKET_ERROR(written))
save_errno = sock_errno();
else
- save_errno = -1; // OK or not complete: this value should not matter in this case
+ save_errno = 0; // OK or not complete: this value should not matter in this case
res = send_check_result(env, descP, written, dataSize, save_errno,
sockRef, sendRef);
@@ -7269,7 +7810,7 @@ ERL_NIF_TERM esock_sendmsg(ErlNifEnv* env,
* Flags - Send flags.
*/
-#ifdef FOBAR
+#ifdef FOOBAR
static
ERL_NIF_TERM nwritev(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -7375,11 +7916,17 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env,
if (!erecvflags2recvflags(eflags, &flags))
return esock_make_error(env, esock_atom_einval);
- SSDBG( descP, ("SOCKET", "nif_recv -> flags: 0x%lX\r\n",
- (unsigned)flags) );
-
MLOCK(descP->readMtx);
+ SSDBG( descP,
+ ("SOCKET", "nif_recv(%T), {%d,%s,0x%X} ->"
+ "\r\n recvRef: %T"
+ "\r\n len: %ld"
+ "\r\n flags: 0x%X"
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->readState,
+ recvRef, (long) len, flags) );
+
/* We need to handle the case when another process tries
* to receive at the same time.
* If the current recv could not read its entire package
@@ -7390,9 +7937,10 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env,
res = esock_recv(env, descP, sockRef, recvRef, (size_t)len, flags);
- MUNLOCK(descP->readMtx);
+ SSDBG( descP, ("SOCKET", "nif_recv(%T) -> done"
+ "\r\n", sockRef) );
- SSDBG( descP, ("SOCKET", "nif_recv -> done: %T\r\n", res) );
+ MUNLOCK(descP->readMtx);
return res;
@@ -7420,19 +7968,28 @@ ERL_NIF_TERM esock_recv(ErlNifEnv* env,
int save_errno;
size_t bufSz = (len ? len : descP->rBufSz);
- SSDBG( descP, ("SOCKET", "esock_recv -> entry with"
- "\r\n len: %lu (%d:lu)"
- "\r\n flags: %d"
- "\r\n",
- (unsigned long)len, descP->rNumCnt, (unsigned long)bufSz,
- flags) );
+ SSDBG( descP, ("SOCKET", "esock_recv {%d} -> entry with"
+ "\r\n count,size: (%u:lu)"
+ "\r\n", descP->sock, (unsigned long) len,
+ descP->rNumCnt, (unsigned long) bufSz) );
- if (!descP->isReadable)
+ if (! IS_OPEN(descP))
return esock_make_error(env, atom_closed);
- /* Ensure that we either have no current reader or that we are it */
- if (!recv_check_reader(env, descP, recvRef, &readerCheck))
+ /* Accept and Read uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->currentAcceptorP != NULL)
+ return esock_make_error(env, esock_atom_einval);
+
+ /* Ensure that we either have no current reader or that we are it,
+ * or enqueue this process if there is a current reader */
+ if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
+ SSDBG( descP,
+ ("SOCKET", "esock_recv {%d} -> reader check failed: "
+ "\r\n %T\r\n", descP->sock, readerCheck) );
return readerCheck;
+ }
/* Allocate a buffer:
* Either as much as we want to read or (if zero (0)) use the "default"
@@ -7444,18 +8001,19 @@ ERL_NIF_TERM esock_recv(ErlNifEnv* env,
ESOCK_CNT_INC(env, descP, sockRef, atom_read_tries, &descP->readTries, 1);
// If it fails (read = -1), we need errno...
- SSDBG( descP, ("SOCKET", "esock_recv -> try read (%lu)\r\n",
- (unsigned long)buf.size) );
+ SSDBG( descP, ("SOCKET", "esock_recv {%d} -> try read (%lu)\r\n",
+ descP->sock, (unsigned long) buf.size) );
+
read = sock_recv(descP->sock, buf.data, buf.size, flags);
if (IS_SOCKET_ERROR(read)) {
save_errno = sock_errno();
} else {
- save_errno = -1; // The value does not actually matter in this case
+ save_errno = 0; // The value does not actually matter in this case
}
SSDBG( descP, ("SOCKET",
- "esock_recv -> read: %ld (%d)\r\n",
- (long)read, save_errno) );
+ "esock_recv {%d} -> read: %ld (%d)\r\n",
+ descP->sock, (long) read, save_errno) );
return recv_check_result(env, descP,
read, len,
@@ -7502,7 +8060,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
#else
ESockDescriptor* descP;
ERL_NIF_TERM sockRef, recvRef;
- unsigned int bufSz;
+ unsigned int len;
unsigned int eflags;
int flags;
ERL_NIF_TERM res;
@@ -7512,7 +8070,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
/* Extract arguments and perform preliminary validation */
if ((argc != 4) ||
- !GET_UINT(env, argv[2], &bufSz) ||
+ !GET_UINT(env, argv[2], &len) ||
!GET_UINT(env, argv[3], &eflags)) {
return enif_make_badarg(env);
}
@@ -7523,17 +8081,6 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
return enif_make_badarg(env);
}
- SSDBG( descP,
- ("SOCKET", "nif_recvfrom -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n recvRef: %T"
- "\r\n bufSz: %d"
- "\r\n eflags: %d"
- "\r\n", descP->sock, argv[0], recvRef, bufSz, eflags) );
-
- /* if (IS_OPEN(descP)) */
- /* return esock_make_error(env, atom_enotconn); */
-
if (!erecvflags2recvflags(eflags, &flags)) {
SSDBG( descP, ("SOCKET", "nif_recvfrom -> recvflags decode failed\r\n") );
return esock_make_error(env, esock_atom_einval);
@@ -7541,6 +8088,15 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
MLOCK(descP->readMtx);
+ SSDBG( descP,
+ ("SOCKET", "nif_recvfrom(%T), {%d,%s,0x%X} ->"
+ "\r\n recvRef: %T"
+ "\r\n len: %u"
+ "\r\n flags: 0x%X"
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->readState,
+ recvRef, len, flags) );
+
/* <KOLLA>
* We need to handle the case when another process tries
* to receive at the same time.
@@ -7556,7 +8112,10 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
* </KOLLA>
*/
- res = esock_recvfrom(env, descP, sockRef, recvRef, bufSz, flags);
+ res = esock_recvfrom(env, descP, sockRef, recvRef, len, flags);
+
+ SSDBG( descP, ("SOCKET", "nif_recvfrom(%T) -> done"
+ "\r\n", sockRef) );
MUNLOCK(descP->readMtx);
@@ -7587,17 +8146,27 @@ ERL_NIF_TERM esock_recvfrom(ErlNifEnv* env,
ERL_NIF_TERM readerCheck;
int bufSz = (len ? len : descP->rBufSz);
- SSDBG( descP, ("SOCKET", "esock_recvfrom -> entry with"
- "\r\n len: %d (%d)"
- "\r\n flags: %d"
- "\r\n", len, bufSz, flags) );
+ SSDBG( descP, ("SOCKET", "esock_recvfrom {%d} -> entry with"
+ "\r\n bufSz: %d"
+ "\r\n", descP->sock, bufSz) );
- if (!descP->isReadable)
+ if (! IS_OPEN(descP))
return esock_make_error(env, atom_closed);
- /* Ensure that we either have no current reader or that we are it */
- if (!recv_check_reader(env, descP, recvRef, &readerCheck))
+ /* Accept and Read uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->currentAcceptorP != NULL)
+ return esock_make_error(env, esock_atom_einval);
+
+ /* Ensure that we either have no current reader or that we are it,
+ * or enqueue this process if there is a current reader */
+ if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
+ SSDBG( descP,
+ ("SOCKET", "esock_recv {%d} -> reader check failed: "
+ "\r\n %T\r\n", descP->sock, readerCheck) );
return readerCheck;
+ }
/* Allocate a buffer:
* Either as much as we want to read or (if zero (0)) use the "default"
@@ -7616,7 +8185,7 @@ ERL_NIF_TERM esock_recvfrom(ErlNifEnv* env,
if (IS_SOCKET_ERROR(read))
save_errno = sock_errno();
else
- save_errno = -1; // The value does not actually matter in this case
+ save_errno = 0; // The value does not actually matter in this case
return recvfrom_check_result(env, descP,
read,
@@ -7690,24 +8259,22 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env,
if (!ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
return enif_make_badarg(env);
}
-
- SSDBG( descP,
- ("SOCKET", "nif_recvmsg -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n recvRef: %T"
- "\r\n bufSz: %d"
- "\r\n ctrlSz: %d"
- "\r\n eflags: %d"
- "\r\n", descP->sock, argv[0], recvRef, bufSz, ctrlSz, eflags) );
-
- /* if (IS_OPEN(descP)) */
- /* return esock_make_error(env, atom_enotconn); */
if (!erecvflags2recvflags(eflags, &flags))
return esock_make_error(env, esock_atom_einval);
MLOCK(descP->readMtx);
+ SSDBG( descP,
+ ("SOCKET", "nif_recvmsg(%T), {%d,%s,0x%X} ->"
+ "\r\n recvRef: %T"
+ "\r\n bufSz: %u"
+ "\r\n ctrlSz: %u"
+ "\r\n flags: 0x%X"
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->readState,
+ recvRef, bufSz, ctrlSz, flags) );
+
/* <KOLLA>
*
* We need to handle the case when another process tries
@@ -7727,6 +8294,9 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env,
res = esock_recvmsg(env, descP, sockRef, recvRef, bufSz, ctrlSz, flags);
+ SSDBG( descP, ("SOCKET", "nif_recvmsg(%T) -> done"
+ "\r\n", sockRef) );
+
MUNLOCK(descP->readMtx);
return res;
@@ -7761,18 +8331,28 @@ ERL_NIF_TERM esock_recvmsg(ErlNifEnv* env,
ERL_NIF_TERM readerCheck;
ESockAddress addr;
- SSDBG( descP, ("SOCKET", "esock_recvmsg -> entry with"
+ SSDBG( descP, ("SOCKET", "esock_recvmsg {%d} -> entry with"
"\r\n bufSz: %d (%d)"
"\r\n ctrlSz: %d (%d)"
- "\r\n flags: %d"
- "\r\n", bufSz, bufLen, ctrlSz, ctrlLen, flags) );
+ "\r\n", descP->sock, bufSz, bufLen, ctrlSz, ctrlLen) );
- if (!descP->isReadable)
+ if (! IS_OPEN(descP))
return esock_make_error(env, atom_closed);
- /* Ensure that we either have no current reader or that we are it */
- if (!recv_check_reader(env, descP, recvRef, &readerCheck))
+ /* Accept and Read uses the same select flag
+ * so they can not be simultaneous
+ */
+ if (descP->currentAcceptorP != NULL)
+ return esock_make_error(env, esock_atom_einval);
+
+ /* Ensure that we either have no current reader or that we are it,
+ * or enqueue this process if there is a current reader */
+ if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
+ SSDBG( descP,
+ ("SOCKET", "esock_recv {%d} -> reader check failed: "
+ "\r\n %T\r\n", descP->sock, readerCheck) );
return readerCheck;
+ }
/*
for (i = 0; i < sizeof(buf); i++) {
@@ -7813,7 +8393,7 @@ ERL_NIF_TERM esock_recvmsg(ErlNifEnv* env,
if (IS_SOCKET_ERROR(read))
save_errno = sock_errno();
else
- save_errno = -1; // The value does not actually matter in this case
+ save_errno = 0; // The value does not actually matter in this case
return recvmsg_check_result(env, descP,
read,
@@ -7856,16 +8436,21 @@ ERL_NIF_TERM nif_close(ErlNifEnv* env,
return enif_make_badarg(env);
}
- if (IS_CLOSED(descP) || IS_CLOSING(descP))
- return esock_make_error(env, atom_closed);
-
- MLOCK(descP->closeMtx);
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_close(%T), {%d,%s,0x%X}\r\n",
+ argv[0], descP->sock, B2S(descP->closing), descP->readState) );
res = esock_close(env, descP);
- MUNLOCK(descP->closeMtx);
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
- SSDBG( descP, ("SOCKET", "nif_close -> res: %T\r\n", res) );
+ SSDBG( descP, ("SOCKET", "nif_close(%T) -> done"
+ "\r\n res: %T"
+ "\r\n", argv[0], res) );
return res;
#endif // if defined(__WIN32__)
@@ -7877,181 +8462,254 @@ static
ERL_NIF_TERM esock_close(ErlNifEnv* env,
ESockDescriptor* descP)
{
- ERL_NIF_TERM reply, reason;
- BOOLEAN_T doClose;
+ int sres;
- SSDBG( descP, ("SOCKET",
- "esock_close -> [%d] entry (0x%lX, 0x%lX, 0x%lX, 0x%lX)\r\n",
- descP->sock,
- descP->state,
- descP->currentWriterP,
- descP->currentReaderP,
- descP->currentAcceptorP) );
+ if (! IS_OPEN(descP))
+ /* A bit of cheeting; maybe not closed yet - do we need a queue? */
+ return esock_make_error(env, atom_closed);
- doClose = esock_close_check(env, descP, &reason);
+ /* Store the PID of the caller,
+ * since we need to inform it when we
+ * (that is, the stop callback function)
+ * completes.
+ */
+
+ if (enif_self(env, &descP->closerPid) == NULL)
+ return esock_make_error(env, atom_exself);
+
+ /* If the caller is not the owner; monitor the caller,
+ * since we should complete this operation even if the caller dies
+ * (for whatever reason).
+ */
+ if (COMPARE_PIDS(&descP->closerPid, &descP->ctrlPid) != 0) {
+
+ if (MONP("esock_close_check -> closer",
+ env, descP,
+ &descP->closerPid,
+ &descP->closerMon) != 0) {
+
+ enif_set_pid_undefined(&descP->closerPid);
+ return esock_make_error(env, atom_exmonitor);
+
+ }
+ }
+
+ /* Create closeRef */
+ descP->closeEnv = esock_alloc_env("esock_close_do - close-env");
+ descP->closeRef = MKREF(descP->closeEnv);
+ descP->closing = TRUE;
+
+ /* Call or schedule call to esock_stop() */
+ sres = esock_do_stop(env, descP);
+
+ if (sres < 0) { // Error
+ /* Calling esock_select_stop failed in some mysterious way,
+ * we are kind of toasted - we'll leave the socket leaked
+ * with descP->closing == TRUE
+ */
+
+ esock_warning_msg("Failed select stop when closing socket: "
+ "\r\n Select Res: %d"
+ "\r\n Controlling Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Closer: %T"
+ "\r\n", sres,
+ descP->ctrlPid, descP->sock,
+ descP->closerPid);
+
+ /* <KOLLA>
+ *
+ * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET,
+ * SO WE DON'T LET STUFF LEAK.
+ * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH
+ * THE CLOSE, WHAT TO DO? ABORT?
+ *
+ * XXX Maybe send a concerned message to the registry?
+ *
+ * </KOLLA>
+ */
+
+ enif_set_pid_undefined(&descP->closerPid);
+ DEMONP("esock_close_do -> closer", env, descP, &descP->closerMon);
+ esock_free_env("esock_close_do - close-env", descP->closeEnv);
+ descP->closeEnv = NULL;
+ descP->closeRef = esock_atom_undefined;
+ return esock_make_error(env, atom_exselect);
- if (doClose) {
- reply = esock_close_do(env, descP);
- } else {
- reply = esock_make_error(env, reason);
}
+ if (sres & ERL_NIF_SELECT_STOP_CALLED) {
+
+ /* Prep done - inform the caller it can finalize (close) directly */
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_close {%d} -> stop was called\r\n",
+ descP->sock) );
+
+ return esock_atom_ok;
+ }
+
+ // if (sres & ERL_NIF_SELECT_STOP_SCHEDULED)
+
+ /* The stop callback function has been *scheduled* which means that we
+ * have to wait for it to complete. */
SSDBG( descP,
- ("SOCKET", "esock_close -> [%d] done when: "
- "\r\n state: 0x%lX"
- "\r\n reply: %T"
- "\r\n", descP->sock, descP->state, reply) );
+ ("SOCKET", "esock_close {%d} -> stop was scheduled\r\n",
+ descP->sock) );
- return reply;
-}
+ return esock_make_ok2(env, CP_TERM(env, descP->closeRef));
+}
-/* *** esock_close_check ***
- *
- * Check if we should try to perform the first stage close.
- */
static
-BOOLEAN_T esock_close_check(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM* reason)
-{
- BOOLEAN_T doClose;
+int esock_do_stop(ErlNifEnv* env,
+ ESockDescriptor* descP) {
+ int sres;
+ ERL_NIF_TERM sockRef;
- if (descP->state == ESOCK_STATE_CLOSED) {
+ sockRef = enif_make_resource(env, descP);
+ sres = esock_select_stop(env, descP->sock, descP);
- doClose = FALSE;
- *reason = atom_closed;
+ if (sres < 0) { // Error
- } else if (descP->state == ESOCK_STATE_CLOSING) {
+ SSDBG( descP,
+ ("SOCKET", "esock_do_stop {%d} -> stop failed: %d\r\n",
+ descP->sock, sres) );
- doClose = FALSE;
- *reason = atom_closing;
+ return sres;
+ }
- } else {
+ /* +++++++ Current and waiting Writers +++++++ */
+
+ if (descP->currentWriterP != NULL) {
- /* Store the PID of the caller,
- * since we need to inform it when we
- * (that is, the stop callback function)
- * completes.
+ /* We have a current Writer; was it deselected?
*/
- if (enif_self(env, &descP->closerPid) == NULL) {
+ if (sres & ERL_NIF_SELECT_WRITE_CANCELLED) {
- doClose = FALSE;
- *reason = atom_exself;
+ /* The current Writer will not get a select message
+ * - send it an abort message
+ */
- } else {
+ esock_stop_handle_current(env,
+ "writer",
+ descP, sockRef, descP->currentWriterP);
+ }
- /* Monitor the caller, since we should complete this
- * operation even if the caller dies (for whatever reason).
- *
- * <KOLLA>
- *
- * Can we actually use this for anything?
- *
- * </KOLLA>
- */
+ /* Inform the waiting Writers (in the same way) */
- if (MONP("esock_close_check -> closer",
- env, descP,
- &descP->closerPid,
- &descP->closerMon) != 0) {
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_do_stop {%d} -> handle waiting writer(s)\r\n",
+ descP->sock) );
- doClose = FALSE;
- *reason = atom_exmon;
+ inform_waiting_procs(env, "writer",
+ descP, sockRef, &descP->writersQ, atom_closed);
- } else {
+ descP->currentWriterP = NULL;
+ }
+
+ /* +++++++ Connector +++++++
+ * Note that there should not be Writers and a Connector
+ * at the same time so the check for if the
+ * current Writer/Connecter was deselected is only correct
+ * under that assumption
+ */
- descP->closeLocal = TRUE;
- descP->state = ESOCK_STATE_CLOSING;
- descP->isReadable = FALSE;
- descP->isWritable = FALSE;
- doClose = TRUE;
- *reason = esock_atom_undefined; // NOT used !!
+ if (descP->connectorP != NULL) {
- }
+ /* We have a Connector; was it deselected?
+ */
+
+ if (sres & ERL_NIF_SELECT_WRITE_CANCELLED) {
+
+ /* The Connector will not get a select message
+ * - send it an abort message
+ */
+
+ esock_stop_handle_current(env,
+ "connector",
+ descP, sockRef, descP->connectorP);
}
+
+ descP->connectorP = NULL;
}
- return doClose;
-
-}
+ /* +++++++ Current and waiting Readers +++++++ */
+ if (descP->currentReaderP != NULL) {
+ /* We have a current Reader; was it deselected?
+ */
-/* *** esock_close_do ***
- *
- * Perform (do) the first stage close.
- */
-static
-ERL_NIF_TERM esock_close_do(ErlNifEnv* env,
- ESockDescriptor* descP)
-{
- int domain = descP->domain;
- int type = descP->type;
- int protocol = descP->protocol;
- int sres;
- ERL_NIF_TERM reply, reason;
+ if (sres & ERL_NIF_SELECT_READ_CANCELLED) {
- descP->closeEnv = esock_alloc_env("esock_close_do - close-env");
- descP->closeRef = MKREF(descP->closeEnv);
+ /* The current Reader will not get a select message
+ * - send it an abort message
+ */
- sres = esock_select_stop(env, descP->sock, descP);
+ esock_stop_handle_current(env,
+ "reader",
+ descP, sockRef, descP->currentReaderP);
+ }
- if (sres & ERL_NIF_SELECT_STOP_CALLED) {
+ /* Inform the Readers (in the same way) */
- /* Prep done - inform the caller it can finalize (close) directly */
SSDBG( descP,
("SOCKET",
- "esock_close -> [%d] stop was called\r\n", descP->sock) );
+ "esock_do_stop {%d} -> handle waiting reader(s)\r\n",
+ descP->sock) );
- dec_socket(domain, type, protocol);
- reply = esock_atom_ok;
+ inform_waiting_procs(env, "writer",
+ descP, sockRef, &descP->readersQ, atom_closed);
- } else if (sres & ERL_NIF_SELECT_STOP_SCHEDULED) {
+ descP->currentReaderP = NULL;
+ }
- /* The stop callback function has been *scheduled* which means that we
- * have to wait for it to complete. */
- SSDBG( descP,
- ("SOCKET", "esock_close -> [%d] stop was scheduled\r\n",
- descP->sock) );
+ /* +++++++ Current and waiting Acceptors +++++++
+ *
+ * Note that there should not be Readers and Acceptors
+ * at the same time so the check for if the
+ * current Reader/Acceptor was deselected is only correct
+ * under that assumption
+ */
- dec_socket(domain, type, protocol); // SHALL WE DO THIS AT finalize?
- reply = esock_make_ok2(env, enif_make_copy(env, descP->closeRef));
+ if (descP->currentAcceptorP != NULL) {
- } else {
+ /* We have a current Acceptor; was it deselected?
+ */
- SSDBG( descP,
- ("SOCKET", "esock_close -> [%d] stop failed: %d\r\n",
- descP->sock, sres) );
+ if (sres & ERL_NIF_SELECT_READ_CANCELLED) {
- /* <KOLLA>
- *
- * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET,
- * SO WE DON'T LET STUFF LEAK.
- * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH
- * THE CLOSE, WHAT TO DO? ABORT?
- *
- * </KOLLA>
- */
+ /* The current Acceptor will not get a select message
+ * - send it an abort message
+ */
- // Do we need this?
- DEMONP("esock_close_do -> closer", env, descP, &descP->closerMon);
+ esock_stop_handle_current(env,
+ "acceptor",
+ descP, sockRef, descP->currentAcceptorP);
+ }
- reason = MKT2(env, esock_atom_select_failed, MKI(env, sres));
- reply = esock_make_error(env, reason);
- }
+ /* Inform the waiting Acceptor (in the same way) */
- return reply;
-}
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_do_stop {%d} -> handle waiting acceptors(s)\r\n",
+ descP->sock) );
+ inform_waiting_procs(env, "acceptor",
+ descP, sockRef, &descP->acceptorsQ, atom_closed);
+ descP->currentAcceptorP = NULL;
+ }
+ return sres;
+}
#endif // if !defined(__WIN32__)
-
/* ----------------------------------------------------------------------
* nif_finalize_close
*
@@ -8067,10 +8725,11 @@ ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
+ ESockDescriptor* descP;
+ ERL_NIF_TERM result;
#if defined(__WIN32__)
return enif_raise_exception(env, MKA(env, "notsup"));
#else
- ESockDescriptor* descP;
/* Extract arguments and perform preliminary validation */
@@ -8079,7 +8738,23 @@ ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env,
return enif_make_badarg(env);
}
- return esock_finalize_close(env, descP);
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_finalize_close(%T), {%d,%s,0x%X}\r\n",
+ argv[0], descP->sock, B2S(descP->closing), descP->readState) );
+
+ result = esock_finalize_close(env, descP);
+
+ SSDBG( descP, ("SOCKET", "nif_finalize_close(%T) -> done with"
+ "\r\n result: %T"
+ "\r\n", argv[0], result) );
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+
+ return result;
#endif // if defined(__WIN32__)
}
@@ -8092,13 +8767,36 @@ static
ERL_NIF_TERM esock_finalize_close(ErlNifEnv* env,
ESockDescriptor* descP)
{
- ERL_NIF_TERM reply;
+ int err;
+ ErlNifPid self;
if (IS_CLOSED(descP))
- return esock_atom_ok;
+ return esock_make_error(env, atom_closed);
+
+ if (! IS_CLOSING(descP))
+ return esock_make_error_errno(env, EALREADY);
+
+ if (enif_self(env, &self) == NULL)
+ return esock_make_error(env, atom_exself);
+
+ if (COMPARE_PIDS(&descP->closerPid, &self) != 0)
+ return esock_make_error_errno(env, EALREADY);
+
+ /* closeEnv should be NULL or else esock_stop() has not been called */
+ if (descP->closeEnv != NULL)
+ return esock_make_error_errno(env, EALREADY);
+
+ /* This process is the closer - go ahead and close the socket */
- if (!IS_CLOSING(descP))
- return esock_make_error(env, atom_enotclosing);
+ /* Stop monitoring the closer */
+
+ enif_set_pid_undefined(&descP->closerPid);
+ DEMONP("esock_finalize_close -> closer", env, descP, &descP->closerMon);
+
+ /* Stop monitoring the owner */
+
+ enif_set_pid_undefined(&descP->ctrlPid);
+ DEMONP("esock_finalize_close -> ctrl", env, descP, &descP->ctrlMon);
/* This nif is executed in a dirty scheduler just so that
* it can "hang" (whith minumum effect on the VM) while the
@@ -8108,31 +8806,46 @@ ERL_NIF_TERM esock_finalize_close(ErlNifEnv* env,
*/
SET_BLOCKING(descP->sock);
- if (sock_close(descP->sock) != 0) {
- int save_errno = sock_errno();
+ err = esock_close_socket(env, descP);
- if (save_errno != ERRNO_BLOCK) {
+ if (err != 0) {
+ if (err == ERRNO_BLOCK) {
/* Not all data in the buffers where sent,
* make sure the caller gets this.
*/
- reply = esock_make_error(env, atom_timeout);
+ return esock_make_error(env, atom_timeout);
} else {
- reply = esock_make_error_errno(env, save_errno);
+ return esock_make_error_errno(env, err);
}
- } else {
- reply = esock_atom_ok;
}
+
+ return esock_atom_ok;
+}
+#endif // if !defined(__WIN32__)
+
+
+static int esock_close_socket(ErlNifEnv* env,
+ ESockDescriptor* descP) {
+ int err = 0;
+ ERL_NIF_TERM sockRef;
+
+ if (descP->closeOnClose && sock_close(descP->sock) != 0)
+ err = sock_errno();
sock_close_event(descP->event);
- descP->sock = INVALID_SOCKET;
- descP->event = INVALID_EVENT;
+ descP->sock = INVALID_SOCKET;
+ descP->event = INVALID_EVENT;
+ descP->closing = FALSE;
+ dec_socket(descP->domain, descP->type, descP->protocol);
- descP->state = ESOCK_STATE_CLOSED;
+ /* (maybe) Update the registry */
+ if (descP->useReg) {
+ sockRef = enif_make_resource(env, descP);
+ esock_send_reg_del_msg(env, sockRef);
+ }
- return reply;
+ return err;
}
-#endif // if !defined(__WIN32__)
-
/* ----------------------------------------------------------------------
@@ -8157,6 +8870,7 @@ ERL_NIF_TERM nif_shutdown(ErlNifEnv* env,
ESockDescriptor* descP;
unsigned int ehow;
int how;
+ ERL_NIF_TERM res;
if ((argc != 2) ||
!ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP) ||
@@ -8164,14 +8878,30 @@ ERL_NIF_TERM nif_shutdown(ErlNifEnv* env,
return enif_make_badarg(env);
}
- if (IS_CLOSED(descP))
- return esock_make_error(env, atom_closed);
-
if (!ehow2how(ehow, &how))
return enif_make_badarg(env);
- return esock_shutdown(env, descP, how);
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_shutdown(%T), {%d,%s,0x%X} ->"
+ "\r\n how: %d"
+ "\r\n",
+ argv[0], descP->sock, B2S(descP->closing),
+ descP->readState | descP->writeState,
+ how) );
+
+ res = esock_shutdown(env, descP, how);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+
+ SSDBG( descP, ("SOCKET", "nif_shutdown(%T) -> done with"
+ "\r\n res: %T"
+ "\r\n", argv[0], res) );
+ return res;
#endif // if defined(__WIN32__)
}
@@ -8183,27 +8913,13 @@ ERL_NIF_TERM esock_shutdown(ErlNifEnv* env,
ESockDescriptor* descP,
int how)
{
- ERL_NIF_TERM reply;
-
- if (sock_shutdown(descP->sock, how) == 0) {
- switch (how) {
- case SHUT_RD:
- descP->isReadable = FALSE;
- break;
- case SHUT_WR:
- descP->isWritable = FALSE;
- break;
- case SHUT_RDWR:
- descP->isReadable = FALSE;
- descP->isWritable = FALSE;
- break;
- }
- reply = esock_atom_ok;
- } else {
- reply = esock_make_error_errno(env, sock_errno());
- }
-
- return reply;
+ if (! IS_OPEN(descP))
+ return esock_make_error(env, atom_closed);
+
+ if (sock_shutdown(descP->sock, how) == 0)
+ return esock_atom_ok;
+ else
+ return esock_make_error_errno(env, sock_errno());
}
#endif // if !defined(__WIN32__)
@@ -8237,12 +8953,9 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env,
return enif_raise_exception(env, MKA(env, "notsup"));
#else
ESockDescriptor* descP = NULL;
- int eLevel, level = -1;
- int eOpt;
- ERL_NIF_TERM eIsEncoded;
- ERL_NIF_TERM eVal;
+ int eLevel, level = -1, eOpt;
+ ERL_NIF_TERM eIsEncoded, eVal;
BOOLEAN_T isEncoded, isOTP;
- ERL_NIF_TERM result;
SGDBG( ("SOCKET", "nif_setopt -> entry with argc: %d\r\n", argc) );
@@ -8258,45 +8971,21 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env,
eIsEncoded = argv[1];
eVal = argv[4];
- if (IS_CLOSED(descP) || IS_CLOSING(descP))
- return esock_make_error(env, atom_closed);
-
isEncoded = esock_decode_bool(eIsEncoded);
/* SGDBG( ("SOCKET", "nif_setopt -> eIsDecoded (%T) decoded: %d\r\n", */
/* eIsEncoded, isEncoded) ); */
if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) {
- SSDBG( descP, ("SOCKET", "nif_seopt -> failed decode level\r\n") );
+ SGDBG( ("SOCKET", "nif_setopt(%T) -> failed decode level\r\n",
+ argv[0]) );
return esock_make_error(env, esock_atom_einval);
}
- SSDBG( descP,
- ("SOCKET", "nif_setopt -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n Encoded: %d (%T)"
- "\r\n Level: %d (%d)"
- "\r\n Opt: %d"
- "\r\n Value: %T"
- "\r\n",
- descP->sock, argv[0],
- isEncoded, eIsEncoded,
- level, eLevel,
- eOpt, eVal) );
-
- MLOCK(descP->cfgMtx);
-
- result = esock_setopt(env, descP, isEncoded, isOTP, level, eOpt, eVal);
-
- MUNLOCK(descP->cfgMtx);
-
- SSDBG( descP,
- ("SOCKET", "nif_setopt -> done when"
- "\r\n result: %T"
- "\r\n", result) );
-
- return result;
-
+ MLOCK(descP->readMtx);
+ return esock_setopt(env, descP, isEncoded, isOTP, level, eOpt, eVal);
+ /* Surprise! MUNLOCK in called function */
+
#endif // if defined(__WIN32__)
}
@@ -8311,20 +9000,21 @@ ERL_NIF_TERM esock_setopt(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal)
{
- ERL_NIF_TERM result;
+ if (! IS_OPEN(descP)) {
+ MUNLOCK(descP->readMtx);
+ return esock_make_error(env, atom_closed);
+ }
if (isOTP) {
/* These are not actual socket options,
* but options for our implementation.
*/
- result = esock_setopt_otp(env, descP, eOpt, eVal);
- } else if (!isEncoded) {
- result = esock_setopt_native(env, descP, level, eOpt, eVal);
+ return esock_setopt_otp(env, descP, eOpt, eVal);
+ } else if (! isEncoded) {
+ return esock_setopt_native(env, descP, level, eOpt, eVal);
} else {
- result = esock_setopt_level(env, descP, level, eOpt, eVal);
+ return esock_setopt_level(env, descP, level, eOpt, eVal);
}
-
- return result;
}
@@ -8339,42 +9029,62 @@ ERL_NIF_TERM esock_setopt_otp(ErlNifEnv* env,
{
ERL_NIF_TERM result;
- SSDBG( descP,
- ("SOCKET", "esock_setopt_otp -> entry with"
- "\r\n eOpt: %d"
- "\r\n eVal: %T"
- "\r\n", eOpt, eVal) );
-
switch (eOpt) {
case ESOCK_OPT_OTP_DEBUG:
+ MLOCK(descP->writeMtx);
result = esock_setopt_otp_debug(env, descP, eVal);
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_IOW:
+ MLOCK(descP->writeMtx);
result = esock_setopt_otp_iow(env, descP, eVal);
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_CTRL_PROC:
+ MLOCK(descP->writeMtx);
result = esock_setopt_otp_ctrl_proc(env, descP, eVal);
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_RCVBUF:
result = esock_setopt_otp_rcvbuf(env, descP, eVal);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_RCVCTRLBUF:
result = esock_setopt_otp_rcvctrlbuf(env, descP, eVal);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_SNDCTRLBUF:
+ MLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
result = esock_setopt_otp_sndctrlbuf(env, descP, eVal);
+ MUNLOCK(descP->writeMtx);
break;
case ESOCK_OPT_OTP_META:
+ MLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
result = esock_setopt_otp_meta(env, descP, eVal);
+ MUNLOCK(descP->writeMtx);
break;
default:
+ MLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp {%d} -> einval with"
+ "\r\n eOpt: %d"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eOpt, eVal) );
+ MUNLOCK(descP->writeMtx);
+
result = esock_make_error(env, esock_atom_einval);
break;
}
@@ -8392,6 +9102,11 @@ ERL_NIF_TERM esock_setopt_otp_debug(ErlNifEnv* env,
{
descP->dbg = esock_decode_bool(eVal);
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_debug {%d} -> ok"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
return esock_atom_ok;
}
@@ -8405,6 +9120,11 @@ ERL_NIF_TERM esock_setopt_otp_iow(ErlNifEnv* env,
{
descP->iow = esock_decode_bool(eVal);
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_iow {%d} -> ok"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
return esock_atom_ok;
}
@@ -8423,9 +9143,9 @@ ERL_NIF_TERM esock_setopt_otp_ctrl_proc(ErlNifEnv* env,
int xres;
SSDBG( descP,
- ("SOCKET", "esock_setopt_otp_ctrl_proc -> entry with"
+ ("SOCKET", "esock_setopt_otp_ctrl_proc {%d} -> entry"
"\r\n eVal: %T"
- "\r\n", eVal) );
+ "\r\n", descP->sock, eVal) );
/* Before we begin, ensure that caller is (current) controlling-process */
if (enif_self(env, &caller) == NULL)
@@ -8460,7 +9180,9 @@ ERL_NIF_TERM esock_setopt_otp_ctrl_proc(ErlNifEnv* env,
descP->ctrlPid = newCtrlPid;
descP->ctrlMon = newCtrlMon;
- SSDBG( descP, ("SOCKET", "esock_setopt_otp_ctrl_proc -> done\r\n") );
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_ctrl_proc {%d} -> ok"
+ "\r\n", descP->sock) );
return esock_atom_ok;
}
@@ -8485,6 +9207,11 @@ ERL_NIF_TERM esock_setopt_otp_rcvbuf(ErlNifEnv* env,
size_t bufSz;
char* xres;
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_rcvbuf {%d} -> entry"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
if (IS_NUM(env, eVal)) {
/* This will have the effect that the buffer size will be
@@ -8522,6 +9249,10 @@ ERL_NIF_TERM esock_setopt_otp_rcvbuf(ErlNifEnv* env,
descP->rNum = n;
descP->rBufSz = bufSz;
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_rcvbuf {%d} -> ok"
+ "\r\n", descP->sock) );
+
return esock_atom_ok;
}
@@ -8537,6 +9268,11 @@ ERL_NIF_TERM esock_setopt_otp_rcvctrlbuf(ErlNifEnv* env,
size_t val;
char* xres;
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_recvctrlbuf {%d} -> entry"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
if ((xres = esock_decode_bufsz(env,
eVal,
ESOCK_RECV_CTRL_BUFFER_SIZE_DEFAULT,
@@ -8545,6 +9281,10 @@ ERL_NIF_TERM esock_setopt_otp_rcvctrlbuf(ErlNifEnv* env,
descP->rCtrlSz = val;
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_rcvctrlbuf {%d} -> ok"
+ "\r\n", descP->sock) );
+
return esock_atom_ok;
}
@@ -8560,6 +9300,11 @@ ERL_NIF_TERM esock_setopt_otp_sndctrlbuf(ErlNifEnv* env,
size_t val;
char* xres;
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_sndvctrlbuf {%d} -> entry"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
if ((xres = esock_decode_bufsz(env,
eVal,
ESOCK_SEND_CTRL_BUFFER_SIZE_DEFAULT,
@@ -8568,6 +9313,10 @@ ERL_NIF_TERM esock_setopt_otp_sndctrlbuf(ErlNifEnv* env,
descP->wCtrlSz = val;
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_sndctrlbuf {%d} -> ok"
+ "\r\n", descP->sock) );
+
return esock_atom_ok;
}
@@ -8581,6 +9330,11 @@ ERL_NIF_TERM esock_setopt_otp_meta(ErlNifEnv* env,
{
ErlNifPid caller;
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_meta {%d} -> entry"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
if (enif_self(env, &caller) == NULL)
return esock_make_error(env, atom_exself);
@@ -8594,6 +9348,10 @@ ERL_NIF_TERM esock_setopt_otp_meta(ErlNifEnv* env,
enif_clear_env(descP->meta.env);
descP->meta.ref = CP_TERM(descP->meta.env, eVal);
+ SSDBG( descP,
+ ("SOCKET", "esock_setopt_otp_meta {%d} -> ok"
+ "\r\n", descP->sock) );
+
return esock_atom_ok;
}
@@ -8612,12 +9370,16 @@ ERL_NIF_TERM esock_setopt_native(ErlNifEnv* env,
ErlNifBinary val;
ERL_NIF_TERM result;
+ MLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+
SSDBG( descP,
- ("SOCKET", "esock_setopt_native -> entry with"
+ ("SOCKET", "esock_setopt_native {%d} -> entry"
"\r\n level: %d"
"\r\n opt: %d"
- "\r\n eVal: %T"
- "\r\n", level, opt, eVal) );
+ "\r\n eVal: %T"
+ "\r\n", descP->sock,
+ level, opt, eVal) );
if (GET_BIN(env, eVal, &val)) {
int res = socket_setopt(descP->sock, level, opt,
@@ -8631,9 +9393,11 @@ ERL_NIF_TERM esock_setopt_native(ErlNifEnv* env,
}
SSDBG( descP,
- ("SOCKET", "esock_setopt_native -> done when"
+ ("SOCKET", "esock_setopt_native {%d} -> done when"
"\r\n result: %T"
- "\r\n", result) );
+ "\r\n", descP->sock, result) );
+
+ MUNLOCK(descP->writeMtx);
return result;
}
@@ -8651,10 +9415,14 @@ ERL_NIF_TERM esock_setopt_level(ErlNifEnv* env,
{
ERL_NIF_TERM result;
+ MLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+
SSDBG( descP,
- ("SOCKET", "esock_setopt_level -> entry with"
+ ("SOCKET", "esock_setopt_level {%d} -> entry with"
"\r\n level: %d"
- "\r\n", level) );
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, level, eVal) );
switch (level) {
case SOL_SOCKET:
@@ -8702,9 +9470,11 @@ ERL_NIF_TERM esock_setopt_level(ErlNifEnv* env,
}
SSDBG( descP,
- ("SOCKET", "esock_setopt_level -> done when"
+ ("SOCKET", "esock_setopt_level {%d} -> done when"
"\r\n result: %T"
- "\r\n", result) );
+ "\r\n", descP->sock, result) );
+
+ MUNLOCK(descP->writeMtx);
return result;
}
@@ -8799,7 +9569,7 @@ ERL_NIF_TERM esock_setopt_lvl_socket(ErlNifEnv* env,
break;
#endif
-#if defined(SO_RCVTIMEO)
+#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
case ESOCK_OPT_SOCK_RCVTIMEO:
result = esock_setopt_lvl_sock_rcvtimeo(env, descP, eVal);
break;
@@ -8829,7 +9599,7 @@ ERL_NIF_TERM esock_setopt_lvl_socket(ErlNifEnv* env,
break;
#endif
-#if defined(SO_SNDTIMEO)
+#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
case ESOCK_OPT_SOCK_SNDTIMEO:
result = esock_setopt_lvl_sock_sndtimeo(env, descP, eVal);
break;
@@ -9007,7 +9777,7 @@ ERL_NIF_TERM esock_setopt_lvl_sock_rcvlowat(ErlNifEnv* env,
#endif
-#if defined(SO_RCVTIMEO)
+#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_setopt_lvl_sock_rcvtimeo(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -9062,7 +9832,7 @@ ERL_NIF_TERM esock_setopt_lvl_sock_sndlowat(ErlNifEnv* env,
#endif
-#if defined(SO_SNDTIMEO)
+#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_setopt_lvl_sock_sndtimeo(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -10411,8 +11181,12 @@ ERL_NIF_TERM esock_setopt_lvl_ipv6_addrform(ErlNifEnv* env,
domain) );
res = socket_setopt(descP->sock,
- SOL_IPV6, IPV6_ADDRFORM,
- &domain, sizeof(domain));
+#if defined(SOL_IPV6)
+ SOL_IPV6,
+#else
+ IPPROTO_IPV6,
+#endif
+ IPV6_ADDRFORM, &domain, sizeof(domain));
if (res != 0)
result = esock_make_error_errno(env, sock_errno());
@@ -10442,7 +11216,14 @@ ERL_NIF_TERM esock_setopt_lvl_ipv6_authhdr(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM eVal)
{
- return esock_setopt_bool_opt(env, descP, SOL_IPV6, IPV6_AUTHHDR, eVal);
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+
+ return esock_setopt_bool_opt(env, descP, level, IPV6_AUTHHDR, eVal);
}
#endif
@@ -11650,14 +12431,6 @@ ERL_NIF_TERM esock_setopt_int_opt(ErlNifEnv* env,
if (GET_INT(env, eVal, &val)) {
int res;
- /*
- SSDBG( descP,
- ("SOCKET", "esock_setopt_int_opt -> set option"
- "\r\n opt: %d"
- "\r\n val: %d"
- "\r\n", opt, val) );
- */
-
res = socket_setopt(descP->sock, level, opt, &val, sizeof(val));
if (res != 0)
@@ -11689,7 +12462,7 @@ ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv* env,
if (GET_STR(env, eVal, val, max) > 0) {
int optLen = strlen(val);
- int res = socket_setopt(descP->sock, level, opt, &val, optLen);
+ int res = socket_setopt(descP->sock, level, opt, val, optLen);
if (res != 0)
result = esock_make_error_errno(env, sock_errno());
@@ -11709,6 +12482,7 @@ ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv* env,
/* esock_setopt_timeval_opt - set an option that has an (timeval) bool value
*/
+#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -11747,6 +12521,7 @@ ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv* env,
return result;
}
+#endif
@@ -11947,7 +12722,6 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env,
int eLevel, level = -1;
ERL_NIF_TERM eIsEncoded, eOpt;
BOOLEAN_T isEncoded, isOTP;
- ERL_NIF_TERM result;
SGDBG( ("SOCKET", "nif_getopt -> entry with argc: %d\r\n", argc) );
@@ -11960,29 +12734,14 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env,
eIsEncoded = argv[1];
eOpt = argv[3]; // Is "normally" an int, but if raw mode: {Int, ValueSz}
- if (IS_CLOSED(descP))
- return esock_make_error(env, atom_closed);
-
- SSDBG( descP,
- ("SOCKET", "nif_getopt -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n eIsEncoded: %T"
- "\r\n eLevel: %d"
- "\r\n eOpt: %T"
- "\r\n", descP->sock, argv[0], eIsEncoded, eLevel, eOpt) );
-
isEncoded = esock_decode_bool(eIsEncoded);
if (!elevel2level(isEncoded, eLevel, &isOTP, &level))
return esock_make_error(env, esock_atom_einval);
- MLOCK(descP->cfgMtx);
-
- result = esock_getopt(env, descP, isEncoded, isOTP, level, eOpt);
-
- MUNLOCK(descP->cfgMtx);
-
- return result;
+ MLOCK(descP->readMtx);
+ return esock_getopt(env, descP, isEncoded, isOTP, level, eOpt);
+ /* Surprise! MUNLOCK in called function */
#endif // if defined(__WIN32__)
}
@@ -12009,28 +12768,30 @@ ERL_NIF_TERM esock_getopt(ErlNifEnv* env,
"\r\n eOpt: %T"
"\r\n", B2S(isEncoded), B2S(isOTP), level, eOpt) );
- if (isOTP) {
+ if (! IS_OPEN(descP)) {
+ result = esock_make_error(env, atom_closed);
+ } else if (isOTP) {
/* These are not actual socket options,
* but options for our implementation.
*/
if (GET_INT(env, eOpt, &opt))
- result = esock_getopt_otp(env, descP, opt);
+ return esock_getopt_otp(env, descP, opt);
else
result = esock_make_error(env, esock_atom_einval);
} else if (!isEncoded) {
- result = esock_getopt_native(env, descP, level, eOpt);
+ return esock_getopt_native(env, descP, level, eOpt);
} else {
if (GET_INT(env, eOpt, &opt))
- result = esock_getopt_level(env, descP, level, opt);
+ return esock_getopt_level(env, descP, level, opt);
else
- result = esock_make_error(env, esock_atom_einval);
+ return esock_getopt_native(env, descP, level, eOpt);
}
SSDBG( descP,
("SOCKET", "esock_getopt -> done when"
"\r\n result: %T"
"\r\n", result) );
-
+ MUNLOCK(descP->readMtx);
return result;
}
@@ -12053,63 +12814,87 @@ ERL_NIF_TERM esock_getopt_otp(ErlNifEnv* env,
switch (eOpt) {
case ESOCK_OPT_OTP_DEBUG:
result = esock_getopt_otp_debug(env, descP);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_IOW:
result = esock_getopt_otp_iow(env, descP);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_CTRL_PROC:
result = esock_getopt_otp_ctrl_proc(env, descP);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_RCVBUF:
result = esock_getopt_otp_rcvbuf(env, descP);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_RCVCTRLBUF:
result = esock_getopt_otp_rcvctrlbuf(env, descP);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_SNDCTRLBUF:
+ MLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
result = esock_getopt_otp_sndctrlbuf(env, descP);
+ MUNLOCK(descP->writeMtx);
break;
case ESOCK_OPT_OTP_FD:
result = esock_getopt_otp_fd(env, descP);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_META:
+ MLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
result = esock_getopt_otp_meta(env, descP);
+ MUNLOCK(descP->writeMtx);
+ break;
+
+ case ESOCK_OPT_OTP_USE_REGISTRY:
+ MLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ result = esock_getopt_otp_use_registry(env, descP);
+ MUNLOCK(descP->writeMtx);
break;
/* *** INTERNAL *** */
case ESOCK_OPT_OTP_DOMAIN:
result = esock_getopt_otp_domain(env, descP);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_TYPE:
result = esock_getopt_otp_type(env, descP);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_PROTOCOL:
result = esock_getopt_otp_protocol(env, descP);
+ MUNLOCK(descP->readMtx);
break;
case ESOCK_OPT_OTP_DTP:
result = esock_getopt_otp_dtp(env, descP);
+ MUNLOCK(descP->readMtx);
break;
default:
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp {%d} -> einval with"
+ "\r\n eOpt: %d"
+ "\r\n", descP->sock, eOpt) );
+ MUNLOCK(descP->readMtx);
+
result = esock_make_error(env, esock_atom_einval);
break;
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_otp -> done when"
- "\r\n result: %T"
- "\r\n", result) );
-
return result;
}
@@ -12134,6 +12919,11 @@ ERL_NIF_TERM esock_getopt_otp_iow(ErlNifEnv* env,
{
ERL_NIF_TERM eVal = esock_encode_bool(descP->iow);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_iow {%d} ->"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
return esock_make_ok2(env, eVal);
}
@@ -12146,6 +12936,11 @@ ERL_NIF_TERM esock_getopt_otp_ctrl_proc(ErlNifEnv* env,
{
ERL_NIF_TERM eVal = MKPID(env, &descP->ctrlPid);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_ctrlProc {%d} ->"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
return esock_make_ok2(env, eVal);
}
@@ -12165,6 +12960,11 @@ ERL_NIF_TERM esock_getopt_otp_rcvbuf(ErlNifEnv* env,
eVal = MKT2(env, MKI(env, descP->rNum), MKI(env, descP->rBufSz));
}
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_rcvbuf {%d} ->"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
return esock_make_ok2(env, eVal);
}
@@ -12177,6 +12977,11 @@ ERL_NIF_TERM esock_getopt_otp_rcvctrlbuf(ErlNifEnv* env,
{
ERL_NIF_TERM eVal = MKI(env, descP->rCtrlSz);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_rcvctrlbuf {%d} ->"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
return esock_make_ok2(env, eVal);
}
@@ -12189,6 +12994,11 @@ ERL_NIF_TERM esock_getopt_otp_sndctrlbuf(ErlNifEnv* env,
{
ERL_NIF_TERM eVal = MKI(env, descP->wCtrlSz);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_sndctrlbuf {%d} ->"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
return esock_make_ok2(env, eVal);
}
@@ -12201,6 +13011,11 @@ ERL_NIF_TERM esock_getopt_otp_fd(ErlNifEnv* env,
{
ERL_NIF_TERM eVal = MKI(env, descP->sock);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_fd {%d} ->"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
return esock_make_ok2(env, eVal);
}
@@ -12213,6 +13028,23 @@ ERL_NIF_TERM esock_getopt_otp_meta(ErlNifEnv* env,
{
ERL_NIF_TERM eVal = CP_TERM(env, descP->meta.ref);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_meta {%d} ->"
+ "\r\n eVal: %T"
+ "\r\n", descP->sock, eVal) );
+
+ return esock_make_ok2(env, eVal);
+}
+
+
+/* esock_getopt_otp_use_registry - Handle the OTP (level) use_registry option
+ */
+static
+ERL_NIF_TERM esock_getopt_otp_use_registry(ErlNifEnv* env,
+ ESockDescriptor* descP)
+{
+ ERL_NIF_TERM eVal = esock_encode_bool(descP->useReg);
+
return esock_make_ok2(env, eVal);
}
@@ -12258,6 +13090,11 @@ ERL_NIF_TERM esock_getopt_otp_domain(ErlNifEnv* env,
domain = getopt_otp_domain(env, descP->domain);
result = esock_make_ok2(env, domain);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_domain {%d} ->"
+ "\r\n result: %T"
+ "\r\n", descP->sock, result) );
+
return result;
}
@@ -12276,7 +13113,7 @@ ERL_NIF_TERM getopt_otp_type(ErlNifEnv* env, int type)
result = esock_atom_dgram;
break;
-#ifdef HAVE_SCTP
+#ifdef SOCK_SEQPACKET
case SOCK_SEQPACKET:
result = esock_atom_seqpacket;
break;
@@ -12285,9 +13122,11 @@ ERL_NIF_TERM getopt_otp_type(ErlNifEnv* env, int type)
result = esock_atom_raw;
break;
+#ifdef SOCK_RDM
case SOCK_RDM:
result = esock_atom_rdm;
break;
+#endif
default:
result = MKI(env, type);
@@ -12308,6 +13147,11 @@ ERL_NIF_TERM esock_getopt_otp_type(ErlNifEnv* env,
type = getopt_otp_type(env, descP->type);
result = esock_make_ok2(env, type);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_type {%d} ->"
+ "\r\n result: %T"
+ "\r\n", descP->sock, result) );
+
return result;
}
@@ -12365,6 +13209,11 @@ ERL_NIF_TERM esock_getopt_otp_protocol(ErlNifEnv* env,
protocol = getopt_otp_protocol(env, descP);
result = esock_make_ok2(env, protocol);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_protocol {%d} ->"
+ "\r\n result: %T"
+ "\r\n", descP->sock, result) );
+
return result;
}
@@ -12384,6 +13233,11 @@ ERL_NIF_TERM esock_getopt_otp_dtp(ErlNifEnv* env,
dtp = MKT3(env, domain, type, protocol);
result = esock_make_ok2(env, dtp);
+ SSDBG( descP,
+ ("SOCKET", "esock_getopt_otp_dtp {%d} ->"
+ "\r\n result: %T"
+ "\r\n", descP->sock, result) );
+
return result;
}
@@ -12404,10 +13258,11 @@ ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
SOCKOPTLEN_T valueSz;
SSDBG( descP,
- ("SOCKET", "esock_getopt_native -> entry with"
+ ("SOCKET", "esock_getopt_native {%d} -> entry"
"\r\n level: %d"
- "\r\n eOpt: %T"
- "\r\n", level, eOpt) );
+ "\r\n eOpt: %T"
+ "\r\n", descP->sock,
+ level, eOpt) );
/* <KOLLA>
* We should really make it possible to specify more common specific types,
@@ -12418,10 +13273,10 @@ ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
if (decode_native_get_opt(env, eOpt, &opt, &valueType, (int*) &valueSz)) {
SSDBG( descP,
- ("SOCKET", "esock_getopt_native -> decoded opt"
+ ("SOCKET", "esock_getopt_native {%d} -> decoded opt"
"\r\n valueType: %d (%s)"
"\r\n ValueSize: %d"
- "\r\n", valueType, VT2S(valueType), valueSz) );
+ "\r\n", descP->sock, valueType, VT2S(valueType), valueSz) );
switch (valueType) {
case ESOCK_OPT_VALUE_TYPE_UNSPEC:
@@ -12443,9 +13298,11 @@ ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env,
}
SSDBG( descP,
- ("SOCKET", "esock_getopt_native -> done when"
+ ("SOCKET", "esock_getopt_native {%d} -> done when"
"\r\n result: %T"
- "\r\n", result) );
+ "\r\n", descP->sock, result) );
+
+ MUNLOCK(descP->readMtx);
return result;
}
@@ -12461,13 +13318,6 @@ ERL_NIF_TERM esock_getopt_native_unspec(ErlNifEnv* env,
ERL_NIF_TERM result = esock_make_error(env, esock_atom_einval);
int res;
- SSDBG( descP,
- ("SOCKET", "esock_getopt_native_unspec -> entry with"
- "\r\n level: %d"
- "\r\n opt: %d"
- "\r\n valueSz: %d"
- "\r\n", level, opt, valueSz) );
-
if (valueSz == 0) {
res = sock_getopt(descP->sock, level, opt, NULL, NULL);
if (res != 0)
@@ -12478,9 +13328,6 @@ ERL_NIF_TERM esock_getopt_native_unspec(ErlNifEnv* env,
SOCKOPTLEN_T vsz = valueSz;
ErlNifBinary val;
- SSDBG( descP, ("SOCKET",
- "esock_getopt_native_unspec -> try alloc buffer\r\n") );
-
if (ALLOC_BIN(vsz, &val)) {
int saveErrno;
res = sock_getopt(descP->sock, level, opt, val.data, &vsz);
@@ -12510,11 +13357,6 @@ ERL_NIF_TERM esock_getopt_native_unspec(ErlNifEnv* env,
}
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_native_unspec -> done when"
- "\r\n result: %T"
- "\r\n", result) );
-
return result;
}
@@ -12531,10 +13373,10 @@ ERL_NIF_TERM esock_getopt_level(ErlNifEnv* env,
ERL_NIF_TERM result;
SSDBG( descP,
- ("SOCKET", "esock_getopt_level -> entry with"
+ ("SOCKET", "esock_getopt_level {%d} -> entry with"
"\r\n level: %d"
"\r\n eOpt: %d"
- "\r\n", level, eOpt) );
+ "\r\n", descP->sock, level, eOpt) );
switch (level) {
case SOL_SOCKET:
@@ -12579,9 +13421,11 @@ ERL_NIF_TERM esock_getopt_level(ErlNifEnv* env,
}
SSDBG( descP,
- ("SOCKET", "esock_getopt_level -> done when"
+ ("SOCKET", "esock_getopt_level {%d} -> done when"
"\r\n result: %T"
- "\r\n", result) );
+ "\r\n", descP->sock, result) );
+
+ MUNLOCK(descP->readMtx);
return result;
}
@@ -12596,11 +13440,6 @@ ERL_NIF_TERM esock_getopt_lvl_socket(ErlNifEnv* env,
{
ERL_NIF_TERM result;
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_socket -> entry with"
- "\r\n eOpt: %d"
- "\r\n", eOpt) );
-
switch (eOpt) {
#if defined(SO_ACCEPTCONN)
case ESOCK_OPT_SOCK_ACCEPTCONN:
@@ -12692,7 +13531,7 @@ ERL_NIF_TERM esock_getopt_lvl_socket(ErlNifEnv* env,
break;
#endif
-#if defined(SO_RCVTIMEO)
+#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
case ESOCK_OPT_SOCK_RCVTIMEO:
result = esock_getopt_lvl_sock_rcvtimeo(env, descP);
break;
@@ -12722,7 +13561,7 @@ ERL_NIF_TERM esock_getopt_lvl_socket(ErlNifEnv* env,
break;
#endif
-#if defined(SO_SNDTIMEO)
+#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
case ESOCK_OPT_SOCK_SNDTIMEO:
result = esock_getopt_lvl_sock_sndtimeo(env, descP);
break;
@@ -12745,11 +13584,6 @@ ERL_NIF_TERM esock_getopt_lvl_socket(ErlNifEnv* env,
break;
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_socket -> done when"
- "\r\n result: %T"
- "\r\n", result) );
-
return result;
}
@@ -12769,9 +13603,6 @@ static
ERL_NIF_TERM esock_getopt_lvl_sock_bindtodevice(ErlNifEnv* env,
ESockDescriptor* descP)
{
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_sock_bindtodevice -> entry with\r\n") );
-
return esock_getopt_str_opt(env, descP,
SOL_SOCKET, SO_BINDTODEVICE, IFNAMSIZ+1);
}
@@ -13027,7 +13858,7 @@ ERL_NIF_TERM esock_getopt_lvl_sock_rcvlowat(ErlNifEnv* env,
#endif
-#if defined(SO_RCVTIMEO)
+#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_getopt_lvl_sock_rcvtimeo(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -13077,7 +13908,7 @@ ERL_NIF_TERM esock_getopt_lvl_sock_sndlowat(ErlNifEnv* env,
#endif
-#if defined(SO_SNDTIMEO)
+#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_getopt_lvl_sock_sndtimeo(ErlNifEnv* env,
ESockDescriptor* descP)
@@ -13119,7 +13950,7 @@ ERL_NIF_TERM esock_getopt_lvl_sock_type(ErlNifEnv* env,
case SOCK_DGRAM:
result = esock_make_ok2(env, esock_atom_dgram);
break;
-#ifdef HAVE_SCTP
+#ifdef SOCK_SEQPACKET
case SOCK_SEQPACKET:
result = esock_make_ok2(env, esock_atom_seqpacket);
break;
@@ -13127,9 +13958,11 @@ ERL_NIF_TERM esock_getopt_lvl_sock_type(ErlNifEnv* env,
case SOCK_RAW:
result = esock_make_ok2(env, esock_atom_raw);
break;
+#ifdef SOCK_RDM
case SOCK_RDM:
result = esock_make_ok2(env, esock_atom_rdm);
break;
+#endif
default:
reason = MKT2(env, esock_atom_unknown, MKI(env, val));
result = esock_make_error(env, reason);
@@ -13151,11 +13984,6 @@ ERL_NIF_TERM esock_getopt_lvl_ip(ErlNifEnv* env,
{
ERL_NIF_TERM result;
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_ip -> entry with"
- "\r\n eOpt: %d"
- "\r\n", eOpt) );
-
switch (eOpt) {
#if defined(IP_FREEBIND)
case ESOCK_OPT_IP_FREEBIND:
@@ -13302,17 +14130,10 @@ ERL_NIF_TERM esock_getopt_lvl_ip(ErlNifEnv* env,
#endif
default:
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_ip -> unknown opt %d\r\n", eOpt) );
result = esock_make_error(env, esock_atom_einval);
break;
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_ip -> done when"
- "\r\n result: %T"
- "\r\n", result) );
-
return result;
}
@@ -13808,11 +14629,6 @@ ERL_NIF_TERM esock_getopt_lvl_ipv6(ErlNifEnv* env,
{
ERL_NIF_TERM result;
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_ipv6 -> entry with"
- "\r\n eOpt: %d"
- "\r\n", eOpt) );
-
switch (eOpt) {
#if defined(IPV6_AUTHHDR)
case ESOCK_OPT_IPV6_AUTHHDR:
@@ -13933,11 +14749,6 @@ ERL_NIF_TERM esock_getopt_lvl_ipv6(ErlNifEnv* env,
break;
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_ipv6 -> done when"
- "\r\n result: %T"
- "\r\n", result) );
-
return result;
}
@@ -14409,11 +15220,6 @@ ERL_NIF_TERM esock_getopt_lvl_sctp(ErlNifEnv* env,
{
ERL_NIF_TERM result;
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_sctp -> entry with"
- "\r\n opt: %d"
- "\r\n", eOpt) );
-
switch (eOpt) {
#if defined(SCTP_ASSOCINFO)
case ESOCK_OPT_SCTP_ASSOCINFO:
@@ -14462,11 +15268,6 @@ ERL_NIF_TERM esock_getopt_lvl_sctp(ErlNifEnv* env,
break;
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_sctp -> done when"
- "\r\n result: %T"
- "\r\n", result) );
-
return result;
}
@@ -14480,7 +15281,8 @@ ERL_NIF_TERM esock_getopt_lvl_sctp(ErlNifEnv* env,
* association (and not an endpoint) then it will have an
* assoc id. But since the sctp support at present is "limited",
* we leave it for now.
- * What do we do if this is an endpoint? Invalid op?
+ * What do we do if this is an endpoint? Invalid op? Or just leave
+ * it for the OS?
*
* </KOLLA>
*/
@@ -14494,8 +15296,6 @@ ERL_NIF_TERM esock_getopt_lvl_sctp_associnfo(ErlNifEnv* env,
SOCKOPTLEN_T valSz = sizeof(val);
int res;
- SSDBG( descP, ("SOCKET", "esock_getopt_lvl_sctp_associnfo -> entry\r\n") );
-
sys_memzero((char*) &val, valSz);
res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_ASSOCINFO, &val, &valSz);
@@ -14522,12 +15322,6 @@ ERL_NIF_TERM esock_getopt_lvl_sctp_associnfo(ErlNifEnv* env,
result = esock_make_ok2(env, eAssocParams);
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_sctp_associnfo -> done with"
- "\r\n res: %d"
- "\r\n result: %T"
- "\r\n", res, result) );
-
return result;
}
#endif
@@ -14571,8 +15365,6 @@ ERL_NIF_TERM esock_getopt_lvl_sctp_initmsg(ErlNifEnv* env,
SOCKOPTLEN_T valSz = sizeof(val);
int res;
- SSDBG( descP, ("SOCKET", "esock_getopt_lvl_sctp_initmsg -> entry\r\n") );
-
sys_memzero((char*) &val, valSz);
res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_INITMSG, &val, &valSz);
@@ -14597,12 +15389,6 @@ ERL_NIF_TERM esock_getopt_lvl_sctp_initmsg(ErlNifEnv* env,
result = esock_make_ok2(env, eInitMsg);
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_sctp_initmsg -> done with"
- "\r\n res: %d"
- "\r\n result: %T"
- "\r\n", res, result) );
-
return result;
}
#endif
@@ -14655,8 +15441,6 @@ ERL_NIF_TERM esock_getopt_lvl_sctp_rtoinfo(ErlNifEnv* env,
SOCKOPTLEN_T valSz = sizeof(val);
int res;
- SSDBG( descP, ("SOCKET", "esock_getopt_lvl_sctp_rtoinfo -> entry\r\n") );
-
sys_memzero((char*) &val, valSz);
res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_RTOINFO, &val, &valSz);
@@ -14680,12 +15464,6 @@ ERL_NIF_TERM esock_getopt_lvl_sctp_rtoinfo(ErlNifEnv* env,
result = esock_make_ok2(env, eRTOInfo);
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_lvl_sctp_rtoinfo -> done with"
- "\r\n res: %d"
- "\r\n result: %T"
- "\r\n", res, result) );
-
return result;
}
#endif
@@ -14709,11 +15487,6 @@ ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv* env,
SOCKOPTLEN_T valSz = sizeof(val);
int res;
- SSDBG( descP, ("SOCKET", "esock_getopt_bool_opt -> entry with"
- "\r\n: level: %d"
- "\r\n: opt: %d"
- "\r\n", level, opt) );
-
res = sock_getopt(descP->sock, level, opt, &val, &valSz);
if (res != 0) {
@@ -14723,12 +15496,6 @@ ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv* env,
result = esock_make_ok2(env, bval);
}
-
- SSDBG( descP, ("SOCKET", "esock_getopt_bool_opt -> done when"
- "\r\n: res: %d"
- "\r\n: result: %T"
- "\r\n", res, result) );
-
return result;
}
@@ -14761,6 +15528,7 @@ ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv* env,
/* esock_getopt_timeval_opt - get an timeval option
*/
+#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv* env,
ESockDescriptor* descP,
@@ -14772,12 +15540,6 @@ ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv* env,
SOCKOPTLEN_T valSz = sizeof(val);
int res;
- SSDBG( descP,
- ("SOCKET", "esock_getopt_timeval_opt -> entry with"
- "\r\n level: %d"
- "\r\n opt: %d"
- "\r\n", level, opt) );
-
sys_memzero((char*) &val, valSz);
res = sock_getopt(descP->sock, level, opt, &val, &valSz);
@@ -14793,13 +15555,9 @@ ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv* env,
result = esock_make_ok2(env, eTimeVal);
}
- SSDBG( descP,
- ("SOCKET", "esock_getopt_timeval_opt -> done when"
- "\r\n result: %T"
- "\r\n", result) );
-
return result;
}
+#endif
@@ -14827,13 +15585,6 @@ ERL_NIF_TERM esock_getopt_str_opt(ErlNifEnv* env,
SOCKOPTLEN_T valSz = max;
int res;
- SSDBG( descP,
- ("SOCKET", "esock_getopt_str_opt -> entry with"
- "\r\n level: %d"
- "\r\n opt: %d"
- "\r\n max: %d"
- "\r\n", level, opt, max) );
-
res = sock_getopt(descP->sock, level, opt, val, &valSz);
if (res != 0) {
@@ -14843,12 +15594,6 @@ ERL_NIF_TERM esock_getopt_str_opt(ErlNifEnv* env,
result = esock_make_ok2(env, sval);
}
-
- SSDBG( descP,
- ("SOCKET", "esock_getopt_str_opt -> done when"
- "\r\n result: %T"
- "\r\n", result) );
-
FREE(val);
return result;
@@ -14888,17 +15633,19 @@ ERL_NIF_TERM nif_sockname(ErlNifEnv* env,
return enif_make_badarg(env);
}
- if (IS_CLOSED(descP) || IS_CLOSING(descP))
- return esock_make_error(env, atom_closed);
-
+ MLOCK(descP->readMtx);
+
SSDBG( descP,
- ("SOCKET", "nif_sockname -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n", descP->sock, argv[0]) );
+ ("SOCKET", "nif_sockname(%T) {%d}"
+ "\r\n", argv[0], descP->sock) );
res = esock_sockname(env, descP);
- SSDBG( descP, ("SOCKET", "nif_sockname -> done with res = %T\r\n", res) );
+ SSDBG( descP,
+ ("SOCKET", "nif_sockname(%T) {%d} -> done with res = %T\r\n",
+ argv[0], descP->sock, res) );
+
+ MUNLOCK(descP->readMtx);
return res;
#endif // if defined(__WIN32__)
@@ -14915,6 +15662,9 @@ ERL_NIF_TERM esock_sockname(ErlNifEnv* env,
ESockAddress* saP = &sa;
unsigned int sz = sizeof(ESockAddress);
+ if (! IS_OPEN(descP))
+ return esock_make_error(env, atom_closed);
+
sys_memzero((char*) saP, sz);
if (IS_SOCKET_ERROR(sock_name(descP->sock, (struct sockaddr*) saP, &sz))) {
return esock_make_error_errno(env, sock_errno());
@@ -14962,17 +15712,22 @@ ERL_NIF_TERM nif_peername(ErlNifEnv* env,
return enif_make_badarg(env);
}
- if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ if (! IS_OPEN(descP))
return esock_make_error(env, atom_closed);
+ MLOCK(descP->readMtx);
+
SSDBG( descP,
- ("SOCKET", "nif_peername -> args when sock = %d:"
- "\r\n Socket: %T"
- "\r\n", descP->sock, argv[0]) );
+ ("SOCKET", "nif_peername(%T) {%d}"
+ "\r\n", argv[0], descP->sock) );
res = esock_peername(env, descP);
- SSDBG( descP, ("SOCKET", "nif_peername -> done with res = %T\r\n", res) );
+ SSDBG( descP,
+ ("SOCKET", "nif_peername(%T) {%d} -> done with res = %T\r\n",
+ argv[0], descP->sock, res) );
+
+ MUNLOCK(descP->readMtx);
return res;
#endif // if defined(__WIN32__)
@@ -15026,7 +15781,7 @@ ERL_NIF_TERM nif_cancel(ErlNifEnv* env,
return enif_raise_exception(env, MKA(env, "notsup"));
#else
ESockDescriptor* descP;
- ERL_NIF_TERM op, sockRef, opRef, result;
+ ERL_NIF_TERM op, sockRef, opRef;
SGDBG( ("SOCKET", "nif_cancel -> entry with argc: %d\r\n", argc) );
@@ -15040,23 +15795,8 @@ ERL_NIF_TERM nif_cancel(ErlNifEnv* env,
op = argv[1];
opRef = argv[2];
- if (IS_CLOSED(descP) || IS_CLOSING(descP))
- return esock_make_error(env, atom_closed);
-
- SSDBG( descP,
- ("SOCKET", "nif_cancel -> args when sock = %d:"
- "\r\n op: %T"
- "\r\n opRef: %T"
- "\r\n", descP->sock, op, opRef) );
-
- result = esock_cancel(env, descP, op, sockRef, opRef);
-
- SSDBG( descP,
- ("SOCKET", "nif_cancel -> done with result: "
- "\r\n %T"
- "\r\n", result) );
+ return esock_cancel(env, descP, op, sockRef, opRef);
- return result;
#endif // if !defined(__WIN32__)
}
@@ -15094,7 +15834,26 @@ ERL_NIF_TERM esock_cancel(ErlNifEnv* env,
} else if (COMPARE(op, esock_atom_recvmsg) == 0) {
return esock_cancel_recv(env, descP, sockRef, opRef);
} else {
- return esock_make_error(env, esock_atom_einval);
+ ERL_NIF_TERM res;
+
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "esock_cancel(%T), {%d,%s,0x%X} -> einval"
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing),
+ descP->readState | descP->writeState) );
+
+ if (! IS_OPEN(descP))
+ res = esock_make_error(env, atom_closed);
+ else
+ res = esock_make_error(env, esock_atom_einval);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+
+ return res;
}
}
@@ -15109,7 +15868,27 @@ ERL_NIF_TERM esock_cancel_connect(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM opRef)
{
- return esock_cancel_write_select(env, descP, opRef);
+ ERL_NIF_TERM res;
+
+ MLOCK(descP->writeMtx);
+
+ if (! IS_OPEN(descP))
+ res = esock_make_error(env, atom_closed);
+ else
+ res = esock_cancel_write_select(env, descP, opRef);
+
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_cancel_connect {%d,%s,0x%X} ->"
+ "\r\n opRef: %T"
+ "\r\n res: %T"
+ "\r\n",
+ descP->sock, B2S(descP->closing), descP->writeState,
+ opRef, res) );
+
+ MUNLOCK(descP->writeMtx);
+
+ return res;
}
@@ -15131,32 +15910,43 @@ ERL_NIF_TERM esock_cancel_accept(ErlNifEnv* env,
{
ERL_NIF_TERM res;
+ MLOCK(descP->readMtx);
+
SSDBG( descP,
- ("SOCKET", "esock_cancel_accept -> entry with"
+ ("SOCKET",
+ "esock_cancel_accept(%T), {%d,%s,0x%X} ->"
"\r\n opRef: %T"
"\r\n %s"
- "\r\n", opRef,
- ((descP->currentAcceptorP == NULL) ? "without acceptor" : "with acceptor")) );
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->readState,
+ opRef,
+ ((descP->currentAcceptorP == NULL) ?
+ "without acceptor" : "with acceptor")) );
- MLOCK(descP->accMtx);
+ if (! IS_OPEN(descP)) {
- if (descP->currentAcceptorP != NULL) {
- if (COMPARE(opRef, descP->currentAcceptor.ref) == 0) {
- res = esock_cancel_accept_current(env, descP, sockRef);
+ res = esock_make_error(env, atom_closed);
+
+ } else {
+
+ if (descP->currentAcceptorP != NULL) {
+ if (COMPARE(opRef, descP->currentAcceptor.ref) == 0) {
+ res = esock_cancel_accept_current(env, descP, sockRef);
+ } else {
+ res = esock_cancel_accept_waiting(env, descP, opRef);
+ }
} else {
- res = esock_cancel_accept_waiting(env, descP, opRef);
+ /* Or badarg? */
+ res = esock_make_error(env, esock_atom_einval);
}
- } else {
- /* Or badarg? */
- res = esock_make_error(env, esock_atom_einval);
}
- MUNLOCK(descP->accMtx);
-
SSDBG( descP,
- ("SOCKET", "esock_cancel_accept -> done with result:"
+ ("SOCKET", "esock_cancel_accept(%T) -> done with result:"
"\r\n %T"
- "\r\n", res) );
+ "\r\n", sockRef, res) );
+
+ MUNLOCK(descP->readMtx);
return res;
}
@@ -15173,30 +15963,27 @@ ERL_NIF_TERM esock_cancel_accept_current(ErlNifEnv* env,
{
ERL_NIF_TERM res;
- SSDBG( descP, ("SOCKET", "esock_cancel_accept_current -> entry\r\n") );
-
DEMONP("esock_cancel_accept_current -> current acceptor",
env, descP, &descP->currentAcceptor.mon);
res = esock_cancel_read_select(env, descP, descP->currentAcceptor.ref);
- SSDBG( descP, ("SOCKET",
- "esock_cancel_accept_current -> cancel res: %T\r\n", res) );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_cancel_accept_current(%T) {%d} -> cancel res: %T"
+ "\r\n", sockRef, descP->sock, res) );
if (!activate_next_acceptor(env, descP, sockRef)) {
SSDBG( descP,
("SOCKET",
- "esock_cancel_accept_current -> no more acceptors\r\n") );
+ "esock_cancel_accept_current(%T) {%d} -> no more acceptors\r\n",
+ sockRef, descP->sock) );
- descP->state = ESOCK_STATE_LISTENING;
+ descP->readState &= ~ESOCK_STATE_ACCEPTING;
descP->currentAcceptorP = NULL;
}
- SSDBG( descP, ("SOCKET", "esock_cancel_accept_current -> done with result:"
- "\r\n %T"
- "\r\n", res) );
-
return res;
}
@@ -15242,29 +16029,39 @@ ERL_NIF_TERM esock_cancel_send(ErlNifEnv* env,
MLOCK(descP->writeMtx);
SSDBG( descP,
- ("SOCKET", "esock_cancel_send -> entry with"
+ ("SOCKET",
+ "esock_cancel_send(%T), {%d,%s,0x%X} -> entry with"
"\r\n opRef: %T"
"\r\n %s"
- "\r\n", opRef,
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->writeState,
+ opRef,
((descP->currentWriterP == NULL) ? "without writer" : "with writer")) );
- if (descP->currentWriterP != NULL) {
- if (COMPARE(opRef, descP->currentWriter.ref) == 0) {
- res = esock_cancel_send_current(env, descP, sockRef);
+ if (! IS_OPEN(descP)) {
+
+ res = esock_make_error(env, atom_closed);
+
+ } else {
+
+ if (descP->currentWriterP != NULL) {
+ if (COMPARE(opRef, descP->currentWriter.ref) == 0) {
+ res = esock_cancel_send_current(env, descP, sockRef);
+ } else {
+ res = esock_cancel_send_waiting(env, descP, opRef);
+ }
} else {
- res = esock_cancel_send_waiting(env, descP, opRef);
+ /* Or badarg? */
+ res = esock_make_error(env, esock_atom_einval);
}
- } else {
- /* Or badarg? */
- res = esock_make_error(env, esock_atom_einval);
}
- MUNLOCK(descP->writeMtx);
-
SSDBG( descP,
- ("SOCKET", "esock_cancel_send -> done with result:"
+ ("SOCKET", "esock_cancel_send(%T) {%d} -> done with result:"
"\r\n %T"
- "\r\n", res) );
+ "\r\n", sockRef, descP->sock, res) );
+
+ MUNLOCK(descP->writeMtx);
return res;
}
@@ -15282,26 +16079,23 @@ ERL_NIF_TERM esock_cancel_send_current(ErlNifEnv* env,
{
ERL_NIF_TERM res;
- SSDBG( descP, ("SOCKET", "esock_cancel_send_current -> entry\r\n") );
-
DEMONP("esock_cancel_send_current -> current writer",
env, descP, &descP->currentWriter.mon);
res = esock_cancel_write_select(env, descP, descP->currentWriter.ref);
SSDBG( descP,
- ("SOCKET", "esock_cancel_send_current -> cancel res: %T\r\n", res) );
+ ("SOCKET", "esock_cancel_send_current(%T) {%d} -> cancel res: %T"
+ "\r\n", sockRef, descP->sock, res) );
if (!activate_next_writer(env, descP, sockRef)) {
SSDBG( descP,
- ("SOCKET", "esock_cancel_send_current -> no more writers\r\n") );
+ ("SOCKET",
+ "esock_cancel_send_current(%T) {%d} -> no more writers"
+ "\r\n", sockRef, descP->sock) );
descP->currentWriterP = NULL;
}
- SSDBG( descP, ("SOCKET", "esock_cancel_send_current -> done with result:"
- "\r\n %T"
- "\r\n", res) );
-
return res;
}
@@ -15347,29 +16141,39 @@ ERL_NIF_TERM esock_cancel_recv(ErlNifEnv* env,
MLOCK(descP->readMtx);
SSDBG( descP,
- ("SOCKET", "esock_cancel_recv -> entry with"
+ ("SOCKET",
+ "esock_cancel_recv(%T), {%d,%s,0x%X} -> entry with"
"\r\n opRef: %T"
"\r\n %s"
- "\r\n", opRef,
+ "\r\n",
+ sockRef, descP->sock, B2S(descP->closing), descP->readState,
+ opRef,
((descP->currentReaderP == NULL) ? "without reader" : "with reader")) );
- if (descP->currentReaderP != NULL) {
- if (COMPARE(opRef, descP->currentReader.ref) == 0) {
- res = esock_cancel_recv_current(env, descP, sockRef);
+ if (! IS_OPEN(descP)) {
+
+ res = esock_make_error(env, atom_closed);
+
+ } else {
+
+ if (descP->currentReaderP != NULL) {
+ if (COMPARE(opRef, descP->currentReader.ref) == 0) {
+ res = esock_cancel_recv_current(env, descP, sockRef);
+ } else {
+ res = esock_cancel_recv_waiting(env, descP, opRef);
+ }
} else {
- res = esock_cancel_recv_waiting(env, descP, opRef);
+ /* Or badarg? */
+ res = esock_make_error(env, esock_atom_einval);
}
- } else {
- /* Or badarg? */
- res = esock_make_error(env, esock_atom_einval);
}
- MUNLOCK(descP->readMtx);
-
SSDBG( descP,
- ("SOCKET", "esock_cancel_recv -> done with result:"
+ ("SOCKET", "esock_cancel_recv(%T) {%d} -> done with result:"
"\r\n %T"
- "\r\n", res) );
+ "\r\n", sockRef, descP->sock, res) );
+
+ MUNLOCK(descP->readMtx);
return res;
}
@@ -15386,26 +16190,23 @@ ERL_NIF_TERM esock_cancel_recv_current(ErlNifEnv* env,
{
ERL_NIF_TERM res;
- SSDBG( descP, ("SOCKET", "esock_cancel_recv_current -> entry\r\n") );
-
DEMONP("esock_cancel_recv_current -> current reader",
env, descP, &descP->currentReader.mon);
res = esock_cancel_read_select(env, descP, descP->currentReader.ref);
SSDBG( descP,
- ("SOCKET", "esock_cancel_recv_current -> cancel res: %T\r\n", res) );
+ ("SOCKET", "esock_cancel_recv_current(%T) {%d} -> cancel res: %T"
+ "\r\n", sockRef, descP->sock, res) );
if (!activate_next_reader(env, descP, sockRef)) {
SSDBG( descP,
- ("SOCKET", "esock_cancel_recv_current -> no more readers\r\n") );
+ ("SOCKET",
+ "esock_cancel_recv_current(%T) {%d} -> no more readers"
+ "\r\n", sockRef, descP->sock) );
descP->currentReaderP = NULL;
}
- SSDBG( descP, ("SOCKET", "esock_cancel_recv_current -> done with result:"
- "\r\n %T"
- "\r\n", res) );
-
return res;
}
@@ -15479,9 +16280,11 @@ ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv* env,
}
} else {
/* Stopped? */
- SSDBG( descP, ("SOCKET",
- "esock_cancel_mode_select -> failed: %d (0x%lX)"
- "\r\n", selectRes, selectRes) );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_cancel_mode_select {%d} -> failed: %d (0x%lX)"
+ "\r\n", descP->sock, selectRes, selectRes) );
+
return esock_make_error(env, esock_atom_einval);
}
@@ -15514,8 +16317,12 @@ BOOLEAN_T send_check_writer(ErlNifEnv* env,
if (enif_self(env, &caller) == NULL) {
*checkResult = esock_make_error(env, atom_exself);
- SSDBG( descP, ("SOCKET",
- "send_check_writer -> exself\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "send_check_writer {%d} -> exself"
+ "\r\n ref: %T"
+ "\r\n", descP->sock, ref) );
+
return FALSE;
}
@@ -15524,22 +16331,23 @@ BOOLEAN_T send_check_writer(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "send_check_writer -> not (current) writer\r\n") );
+ "send_check_writer {%d} -> not (current) writer"
+ "\r\n ref: %T"
+ "\r\n", descP->sock, ref) );
if (!writer_search4pid(env, descP, &caller))
*checkResult = writer_push(env, descP, caller, ref);
else
- *checkResult = esock_make_error(env, atom_exbusy);
+ *checkResult = esock_make_error_errno(env, EALREADY);
SSDBG( descP,
("SOCKET",
- "send_check_writer -> queue (push) result: %T\r\n",
- *checkResult) );
+ "send_check_writer {%d} -> queue (push) result: %T\r\n"
+ "\r\n ref: %T"
+ "\r\n", descP->sock, *checkResult, ref) );
return FALSE;
-
}
-
}
// Does not actually matter in this case, but ...
@@ -15576,11 +16384,13 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env,
ERL_NIF_TERM res;
SSDBG( descP,
- ("SOCKET", "send_check_result -> entry with"
+ ("SOCKET", "send_check_result(%T) {%d} -> entry with"
"\r\n written: %d"
"\r\n dataSize: %d"
"\r\n saveErrno: %d"
- "\r\n", written, dataSize, saveErrno) );
+ "\r\n sendRef: %T"
+ "\r\n", sockRef, descP->sock,
+ written, dataSize, saveErrno, sendRef) );
if (written >= dataSize) {
@@ -15598,10 +16408,12 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env,
/* Ok, try again later */
- SSDBG( descP, ("SOCKET", "send_check_result -> try again\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "send_check_result(%T) {%d} -> try again"
+ "\r\n", sockRef, descP->sock) );
res = send_check_retry(env, descP, written, sockRef, sendRef);
-
}
} else {
@@ -15609,15 +16421,21 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env,
/* Not the entire package */
SSDBG( descP,
- ("SOCKET", "send_check_result -> "
- "not entire package written (%d of %d)\r\n",
+ ("SOCKET",
+ "send_check_result(%T) {%d} -> "
+ "not entire package written (%d of %d)"
+ "\r\n", sockRef, descP->sock,
written, dataSize) );
res = send_check_retry(env, descP, written, sockRef, sendRef);
-
}
- SSDBG( descP, ("SOCKET", "send_check_result -> done: %T\r\n", res) );
+ SSDBG( descP,
+ ("SOCKET",
+ "send_check_result(%T) {%d} -> done:"
+ "\r\n res: %T"
+ "\r\n", sockRef, descP->sock,
+ res) );
return res;
}
@@ -15644,8 +16462,9 @@ ERL_NIF_TERM send_check_ok(ErlNifEnv* env,
descP->writePkgMaxCnt = 0;
SSDBG( descP,
- ("SOCKET", "send_check_ok -> "
- "everything written (%d,%d) - done\r\n", dataSize, written) );
+ ("SOCKET", "send_check_ok(%T) {%d} -> "
+ "everything written (%d,%d) - done\r\n",
+ sockRef, descP->sock, dataSize, written) );
if (descP->currentWriterP != NULL) {
DEMONP("send_check_ok -> current writer",
@@ -15657,7 +16476,8 @@ ERL_NIF_TERM send_check_ok(ErlNifEnv* env,
if (!activate_next_writer(env, descP, sockRef)) {
SSDBG( descP,
- ("SOCKET", "send_check_ok -> no more writers\r\n") );
+ ("SOCKET", "send_check_ok(%T) {%d} -> no more writers\r\n",
+ sockRef, descP->sock) );
descP->currentWriterP = NULL;
}
@@ -15682,7 +16502,8 @@ ERL_NIF_TERM send_check_fail(ErlNifEnv* env,
ESOCK_CNT_INC(env, descP, sockRef, atom_write_fails, &descP->writeFails, 1);
- SSDBG( descP, ("SOCKET", "send_check_fail -> error: %d\r\n", saveErrno) );
+ SSDBG( descP, ("SOCKET", "send_check_fail(%T) {%d} -> error: %d\r\n",
+ sockRef, descP->sock, saveErrno) );
reason = MKA(env, erl_errno_id(saveErrno));
@@ -15695,8 +16516,8 @@ ERL_NIF_TERM send_check_fail(ErlNifEnv* env,
if (descP->currentWriterP != NULL) {
- esock_release_current("send_check_fail",
- env, descP, descP->currentWriterP);
+ requestor_release("send_check_fail",
+ env, descP, descP->currentWriterP);
send_error_waiting_writers(env, descP, sockRef, reason);
@@ -15706,7 +16527,7 @@ ERL_NIF_TERM send_check_fail(ErlNifEnv* env,
return esock_make_error(env, reason);
}
-/* *** send_error_current_writer ***
+/* *** send_error_waiting_writers ***
*
* Process all waiting writers when a fatal error has occured.
* All waiting writers will be "aborted", that is a
@@ -15723,11 +16544,17 @@ void send_error_waiting_writers(ErlNifEnv* env,
req.env = NULL; /* read by writer_pop before free */
while (writer_pop(env, descP, &req)) {
SSDBG( descP,
- ("SOCKET", "send_error_current_writer -> abort %T\r\n",
- req.pid) );
+ ("SOCKET",
+ "send_error_waiting_writers(%T) {%d} -> abort"
+ "\r\n pid: %T"
+ "\r\n reason: %T"
+ "\r\n",
+ sockRef, descP->sock, &req.pid, reason) );
+
esock_send_abort_msg(env, sockRef, req.ref, req.env,
reason, &req.pid);
req.env = NULL;
+
DEMONP("send_error_waiting_writers -> pop'ed writer",
env, descP, &req.mon);
}
@@ -15765,7 +16592,7 @@ ERL_NIF_TERM send_check_retry(ErlNifEnv* env,
&descP->currentWriter.pid,
&descP->currentWriter.mon) != 0) {
enif_set_pid_undefined(&descP->currentWriter.pid);
- return esock_make_error(env, atom_exmon);
+ return esock_make_error(env, atom_exmonitor);
} else {
ESOCK_ASSERT(descP->currentWriter.env == NULL);
descP->currentWriter.env = esock_alloc_env("current-writer");
@@ -15774,6 +16601,7 @@ ERL_NIF_TERM send_check_retry(ErlNifEnv* env,
descP->currentWriterP = &descP->currentWriter;
}
} else {
+ enif_clear_env(descP->currentWriter.env);
descP->currentWriter.ref = CP_TERM(descP->currentWriter.env, sendRef);
}
@@ -15854,22 +16682,22 @@ BOOLEAN_T recv_check_reader(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "recv_check_reader -> not (current) reader\r\n") );
+ "recv_check_reader {%d} -> not (current) reader"
+ "\r\n ref: %T"
+ "\r\n", descP->sock, ref) );
if (!reader_search4pid(env, descP, &caller))
*checkResult = reader_push(env, descP, caller, ref);
else
- *checkResult = esock_make_error(env, atom_exbusy);
+ *checkResult = esock_make_error_errno(env, EALREADY);
SSDBG( descP,
("SOCKET",
- "recv_check_reader -> queue (push) result: %T\r\n",
- *checkResult) );
+ "recv_check_reader {%d} -> queue (push) result: %T\r\n",
+ descP->sock, *checkResult) );
return FALSE;
-
}
-
}
// Does not actually matter in this case, but ...
@@ -15902,7 +16730,7 @@ char* recv_init_current_reader(ErlNifEnv* env,
&descP->currentReader.pid,
&descP->currentReader.mon) != 0) {
enif_set_pid_undefined(&descP->currentReader.pid);
- return str_exmon;
+ return str_exmonitor;
} else {
ESOCK_ASSERT(!descP->currentReader.env);
descP->currentReader.env = esock_alloc_env("current-reader");
@@ -15920,6 +16748,7 @@ char* recv_init_current_reader(ErlNifEnv* env,
* Make use of the existing environment
*/
+ enif_clear_env(descP->currentReader.env);
descP->currentReader.ref = CP_TERM(descP->currentReader.env, recvRef);
}
@@ -15951,7 +16780,8 @@ ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "recv_update_current_reader -> no more readers\r\n") );
+ "recv_update_current_reader(%T) {%d} -> no more readers\r\n",
+ sockRef, descP->sock) );
descP->currentReaderP = NULL;
}
@@ -15979,14 +16809,19 @@ void recv_error_current_reader(ErlNifEnv* env,
if (descP->currentReaderP != NULL) {
ESockRequestor req;
- esock_release_current("recv_error_current_reader",
- env, descP, descP->currentReaderP);
+ requestor_release("recv_error_current_reader",
+ env, descP, descP->currentReaderP);
req.env = NULL; /* read by reader_pop before free */
while (reader_pop(env, descP, &req)) {
+
SSDBG( descP,
- ("SOCKET", "recv_error_current_reader -> abort %T\r\n",
- req.pid) );
+ ("SOCKET", "recv_error_current_reader(%T) {%d} -> abort"
+ "\r\n pid: %T"
+ "\r\n reason %T"
+ "\r\n", sockRef, descP->sock,
+ req.pid, reason) );
+
esock_send_abort_msg(env, sockRef, req.ref, req.env,
reason, &req.pid);
req.env = NULL;
@@ -16017,13 +16852,13 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
ERL_NIF_TERM res;
SSDBG( descP,
- ("SOCKET", "recv_check_result -> entry with"
+ ("SOCKET", "recv_check_result(%T) {%d} -> entry with"
"\r\n read: %ld"
"\r\n toRead: %lu"
"\r\n saveErrno: %d"
"\r\n recvRef: %T"
- "\r\n",
- (long)read, (unsigned long)toRead, saveErrno, recvRef) );
+ "\r\n", sockRef, descP->sock,
+ (long) read, (unsigned long) toRead, saveErrno, recvRef) );
/* <KOLLA>
@@ -16067,8 +16902,8 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "recv_check_result -> [%lu] filled the buffer\r\n",
- (unsigned long)toRead) );
+ "recv_check_result(%T) {%d} -> [%lu] filled the buffer\r\n",
+ sockRef, descP->sock, (unsigned long) toRead) );
res = recv_check_full(env, descP, read, toRead, bufP,
sockRef, recvRef);
@@ -16086,14 +16921,13 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "recv_check_result -> [%lu] "
+ "recv_check_result(%T) {%d} -> [%lu] "
"did not fill the buffer (%ld of %lu)\r\n",
- (unsigned long)toRead,
- (long)read, (unsigned long)bufP->size) );
+ sockRef, descP->sock, (unsigned long) toRead,
+ (long) read, (unsigned long) bufP->size) );
res = recv_check_partial(env, descP, read, toRead, bufP,
sockRef, recvRef);
-
}
}
@@ -16137,11 +16971,12 @@ ERL_NIF_TERM recv_check_full(ErlNifEnv* env,
*/
SSDBG( descP,
- ("SOCKET", "recv_check_full -> shall we continue reading"
+ ("SOCKET", "recv_check_full(%T) {%d} -> shall we continue reading?"
"\r\n read: %ld"
"\r\n rNum: %u"
"\r\n rNumCnt: %u"
- "\r\n", (unsigned long)read, descP->rNum, descP->rNumCnt) );
+ "\r\n", sockRef, descP->sock,
+ (unsigned long) read, descP->rNum, descP->rNumCnt) );
res = recv_check_full_maybe_done(env, descP, read, toRead, bufP,
sockRef, recvRef);
@@ -16152,9 +16987,9 @@ ERL_NIF_TERM recv_check_full(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "recv_check_full -> [%lu] "
+ "recv_check_full(%T) {%d} -> [%lu] "
"we got exactly what we could fit\r\n",
- (unsigned long)toRead) );
+ sockRef, descP->sock, (unsigned long) toRead) );
res = recv_check_full_done(env, descP, read, bufP, sockRef);
@@ -16231,8 +17066,9 @@ ERL_NIF_TERM recv_check_full_maybe_done(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "recv_check_full_maybe_done -> [%lu] "
- "we are done for now - read more\r\n", (unsigned long)toRead) );
+ "recv_check_full_maybe_done(%T) {%d} -> [%lu] "
+ "we are done for now - read more\r\n",
+ sockRef, descP->sock, (unsigned long)toRead) );
return esock_make_ok3(env, atom_false, MKBIN(env, bufP));
}
@@ -16294,7 +17130,11 @@ ERL_NIF_TERM recv_check_fail(ErlNifEnv* env,
/* +++ Oups - closed +++ */
- SSDBG( descP, ("SOCKET", "recv_check_fail econnreset -> closed\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "recv_check_fail(%T) {%d} -> econnreset: closed"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, recvRef) );
// This is a bit overkill (to count here), but just in case...
ESOCK_CNT_INC(env, descP, sockRef, atom_read_fails,
@@ -16305,14 +17145,21 @@ ERL_NIF_TERM recv_check_fail(ErlNifEnv* env,
} else if ((saveErrno == ERRNO_BLOCK) ||
(saveErrno == EAGAIN)) {
- SSDBG( descP, ("SOCKET", "recv_check_fail -> eagain\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "recv_check_fail(%T) {%d} -> eagain"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, recvRef) );
res = recv_check_retry(env, descP, sockRef, recvRef);
} else {
- SSDBG( descP, ("SOCKET", "recv_check_fail -> errno: %d\r\n",
- saveErrno) );
+ SSDBG( descP,
+ ("SOCKET",
+ "recv_check_fail(%T) {%d} -> errno: %d\r\n"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, saveErrno, recvRef) );
ESOCK_CNT_INC(env, descP, sockRef, atom_read_fails,
&descP->readFails, 1);
@@ -16356,17 +17203,6 @@ ERL_NIF_TERM recv_check_fail_econnreset(ErlNifEnv* env,
recv_error_current_reader(env, descP, sockRef, reason);
- MUNLOCK(descP->readMtx);
-
- MLOCK(descP->closeMtx);
- MLOCK(descP->readMtx);
-
- descP->isReadable = FALSE;
- descP->closeLocal = FALSE;
- descP->state = ESOCK_STATE_CLOSING;
-
- MUNLOCK(descP->closeMtx);
-
return res;
}
@@ -16390,14 +17226,18 @@ ERL_NIF_TERM recv_check_retry(ErlNifEnv* env,
if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL)
return esock_make_error_str(env, xres);
- SSDBG( descP, ("SOCKET", "recv_check_retry -> SELECT for more\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "recv_check_retry(%T) {%d} -> SELECT for more"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock, recvRef) );
if ((sres = esock_select_read(env, descP->sock, descP, NULL,
sockRef, recvRef)) < 0) {
/* Ouch
* Now what? We have copied ref into *its own* environment!
*/
- reason = MKT2(env, esock_atom_select_failed, MKI(env, sres));
+ reason = atom_exselect;
} else {
reason = esock_atom_eagain;
}
@@ -16451,16 +17291,22 @@ ERL_NIF_TERM recv_check_partial(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "recv_check_partial -> [%lu] split buffer\r\n",
- (unsigned long)toRead) );
+ "recv_check_partial(%T) {%d} -> [%lu] split buffer"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock,
+ (unsigned long) toRead, recvRef) );
res = recv_check_partial_done(env, descP, read, bufP, sockRef);
} else {
- SSDBG( descP, ("SOCKET", "recv_check_partial -> [%lu] "
- "only part of message - expect more\r\n",
- (unsigned long)toRead) );
+ SSDBG( descP,
+ ("SOCKET",
+ "recv_check_partial(%T) {%d} -> [%lu]"
+ " only part of message - expect more"
+ "\r\n recvRef: %T"
+ "\r\n", sockRef, descP->sock,
+ (unsigned long) toRead, recvRef) );
res = recv_check_partial_part(env, descP, read, bufP, sockRef, recvRef);
}
@@ -16502,8 +17348,8 @@ ERL_NIF_TERM recv_check_partial_done(ErlNifEnv* env,
data = MKSBIN(env, data, 0, read);
SSDBG( descP,
- ("SOCKET", "recv_check_partial_done -> [%ld] done\r\n",
- (long)read) );
+ ("SOCKET", "recv_check_partial_done(%T) {%d} -> [%ld] done\r\n",
+ sockRef, descP->sock, (long) read) );
return esock_make_ok3(env, atom_true, data);
}
@@ -16584,11 +17430,12 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
ERL_NIF_TERM data, res;
SSDBG( descP,
- ("SOCKET", "recvfrom_check_result -> entry with"
+ ("SOCKET", "recvfrom_check_result(%T) {%d} -> entry with"
"\r\n read: %d"
"\r\n saveErrno: %d"
"\r\n recvRef: %T"
- "\r\n", read, saveErrno, recvRef) );
+ "\r\n", sockRef, descP->sock,
+ read, saveErrno, recvRef) );
if (read < 0) {
@@ -16661,11 +17508,12 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env,
ERL_NIF_TERM res;
SSDBG( descP,
- ("SOCKET", "recvmsg_check_result -> entry with"
+ ("SOCKET", "recvmsg_check_result(%T) {%d} -> entry with"
"\r\n read: %d"
"\r\n saveErrno: %d"
"\r\n recvRef: %T"
- "\r\n", read, saveErrno, recvRef) );
+ "\r\n", sockRef, descP->sock,
+ read, saveErrno, recvRef) );
/* <KOLLA>
@@ -16753,8 +17601,9 @@ ERL_NIF_TERM recvmsg_check_msg(ErlNifEnv* env,
SSDBG( descP,
("SOCKET",
- "recvmsg_check_result -> "
- "(msghdr) encode failed: %s\r\n", xres) );
+ "recvmsg_check_result(%T) {%d} -> "
+ "(msghdr) encode failed: %s\r\n",
+ sockRef, descP->sock, xres) );
/* So this is a bit strange. We did "successfully" read 'read' bytes,
* but then we fail to process the message header. So what counters
@@ -16780,7 +17629,9 @@ ERL_NIF_TERM recvmsg_check_msg(ErlNifEnv* env,
} else {
SSDBG( descP,
- ("SOCKET", "recvmsg_check_result -> (msghdr) encode ok\r\n") );
+ ("SOCKET", "recvmsg_check_result(%T) {%d} ->"
+ " (msghdr) encode ok\r\n",
+ sockRef, descP->sock) );
ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg, &descP->readPkgCnt, 1);
ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
@@ -16821,9 +17672,9 @@ char* encode_msghdr(ErlNifEnv* env,
ERL_NIF_TERM addr, iov, ctrl, flags;
SSDBG( descP,
- ("SOCKET", "encode_msghdr -> entry with"
+ ("SOCKET", "encode_msghdr {%d} -> entry with"
"\r\n read: %d"
- "\r\n", read) );
+ "\r\n", descP->sock, read) );
/* The address is not used if we are connected (unless, maybe,
* family is 'local'), so check (length = 0) before we try to encodel
@@ -16838,7 +17689,10 @@ char* encode_msghdr(ErlNifEnv* env,
addr = esock_atom_undefined;
}
- SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode iov\r\n") );
+ SSDBG( descP,
+ ("SOCKET", "encode_msghdr {%d} -> try encode iov\r\n",
+ descP->sock) );
+
if ((xres = esock_encode_iov(env,
read,
msgHdrP->msg_iov,
@@ -16847,20 +17701,29 @@ char* encode_msghdr(ErlNifEnv* env,
&iov)) != NULL)
return xres;
- SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode cmsghdrs\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "encode_msghdr {%d} -> try encode cmsghdrs\r\n",
+ descP->sock) );
+
if ((xres = encode_cmsghdrs(env, descP, ctrlBufP, msgHdrP, &ctrl)) != NULL)
return xres;
- SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode flags\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "encode_msghdr {%d} -> try encode flags\r\n",
+ descP->sock) );
+
if ((xres = encode_msghdr_flags(env, descP, msgHdrP->msg_flags, &flags)) != NULL)
return xres;
SSDBG( descP,
- ("SOCKET", "encode_msghdr -> components encoded:"
+ ("SOCKET", "encode_msghdr {%d} -> components encoded:"
"\r\n addr: %T"
"\r\n ctrl: %T"
"\r\n flags: %T"
- "\r\n", addr, ctrl, flags) );
+ "\r\n", descP->sock, addr, ctrl, flags) );
+
{
ERL_NIF_TERM keys[] = {esock_atom_addr,
esock_atom_iov,
@@ -16873,16 +17736,24 @@ char* encode_msghdr(ErlNifEnv* env,
ESOCK_ASSERT( (numKeys == numVals) );
- SSDBG( descP, ("SOCKET", "encode_msghdr -> create msghdr map\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "encode_msghdr {%d} -> create msghdr map\r\n",
+ descP->sock) );
+
if (!MKMA(env, keys, vals, numKeys, &tmp))
return ESOCK_STR_EINVAL;
- SSDBG( descP, ("SOCKET", "encode_msghdr -> msghdr encoded\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "encode_msghdr {%d}-> msghdr encoded\r\n",
+ descP->sock) );
*eSockAddr = tmp;
}
- SSDBG( descP, ("SOCKET", "encode_msghdr -> done\r\n") );
+ SSDBG( descP,
+ ("SOCKET", "encode_msghdr {%d} -> done\r\n", descP->sock) );
return NULL;
}
@@ -16922,10 +17793,10 @@ char* encode_cmsghdrs(ErlNifEnv* env,
struct cmsghdr* firstP = CMSG_FIRSTHDR(msgHdrP);
struct cmsghdr* currentP;
- SSDBG( descP, ("SOCKET", "encode_cmsghdrs -> entry when"
+ SSDBG( descP, ("SOCKET", "encode_cmsghdrs {%d} -> entry when"
"\r\n msg ctrl len: %d"
"\r\n (ctrl) firstP: 0x%lX"
- "\r\n",
+ "\r\n", descP->sock,
msgHdrP->msg_controllen, firstP) );
for (currentP = firstP;
@@ -16943,9 +17814,9 @@ char* encode_cmsghdrs(ErlNifEnv* env,
currentP = CMSG_NXTHDR(msgHdrP, currentP)) {
SSDBG( descP,
- ("SOCKET", "encode_cmsghdrs -> process cmsg header when"
+ ("SOCKET", "encode_cmsghdrs {%d} -> process cmsg header when"
"\r\n TArray Size: %d"
- "\r\n", TARRAY_SZ(cmsghdrs)) );
+ "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) );
/* MUST check this since on Linux the returned "cmsg" may actually
* go too far!
@@ -16958,13 +17829,13 @@ char* encode_cmsghdrs(ErlNifEnv* env,
*/
SSDBG( descP,
- ("SOCKET", "encode_cmsghdrs -> check failed when: "
+ ("SOCKET", "encode_cmsghdrs {%d} -> check failed when: "
"\r\n currentP: 0x%lX"
"\r\n (current) cmsg_len: %d"
"\r\n firstP: 0x%lX"
"\r\n => %d"
"\r\n msg ctrl len: %d"
- "\r\n",
+ "\r\n", descP->sock,
CHARP(currentP), currentP->cmsg_len, CHARP(firstP),
(CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP),
msgHdrP->msg_controllen) );
@@ -16978,10 +17849,10 @@ char* encode_cmsghdrs(ErlNifEnv* env,
size_t dataLen = currentP->cmsg_len - (CHARP(currentP)-CHARP(dataP));
SSDBG( descP,
- ("SOCKET", "encode_cmsghdrs -> cmsg header data: "
+ ("SOCKET", "encode_cmsghdrs {%d} -> cmsg header data: "
"\r\n dataPos: %d"
"\r\n dataLen: %d"
- "\r\n", dataPos, dataLen) );
+ "\r\n", descP->sock, dataPos, dataLen) );
/* We can't give up just because its an unknown protocol,
* so if its a protocol we don't know, we return its integer
@@ -17003,11 +17874,11 @@ char* encode_cmsghdrs(ErlNifEnv* env,
data = MKSBIN(env, ctrlBuf, dataPos, dataLen);
SSDBG( descP,
- ("SOCKET", "encode_cmsghdrs -> "
+ ("SOCKET", "encode_cmsghdrs {%d} -> "
"\r\n level: %T"
"\r\n type: %T"
"\r\n data: %T"
- "\r\n", level, type, data) );
+ "\r\n", descP->sock, level, type, data) );
/* And finally create the 'cmsghdr' map -
* and if successfull add it to the tarray.
@@ -17036,9 +17907,9 @@ char* encode_cmsghdrs(ErlNifEnv* env,
}
SSDBG( descP,
- ("SOCKET", "encode_cmsghdrs -> cmsg headers processed when"
+ ("SOCKET", "encode_cmsghdrs {%d} -> cmsg headers processed when"
"\r\n TArray Size: %d"
- "\r\n", TARRAY_SZ(cmsghdrs)) );
+ "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) );
/* The tarray is populated - convert it to a list */
TARRAY_TOLIST(cmsghdrs, env, eCMsgHdr);
@@ -17075,22 +17946,27 @@ char* decode_cmsghdrs(ErlNifEnv* env,
int i;
char* xres;
- SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> entry with"
+ SSDBG( descP, ("SOCKET", "decode_cmsghdrs {%d} -> entry with"
+ "\r\n eCMsgHdr: %T"
"\r\n cmsgHdrBufP: 0x%lX"
"\r\n cmsgHdrBufLen: %d"
- "\r\n", cmsgHdrBufP, cmsgHdrBufLen) );
+ "\r\n", descP->sock,
+ eCMsgHdr, cmsgHdrBufP, cmsgHdrBufLen) );
if (IS_LIST(env, eCMsgHdr) && GET_LIST_LEN(env, eCMsgHdr, &len)) {
- SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> list length: %d\r\n", len) );
+ SSDBG( descP,
+ ("SOCKET",
+ "decode_cmsghdrs {%d} -> list length: %d\r\n",
+ descP->sock, len) );
for (i = 0, list = eCMsgHdr, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP;
i < len; i++) {
- SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> process elem %d:"
+ SSDBG( descP, ("SOCKET", "decode_cmsghdrs {%d} -> process elem %d:"
"\r\n (buffer) rem: %u"
"\r\n (buffer) totUsed: %u"
- "\r\n", i, rem, totUsed) );
+ "\r\n", descP->sock, i, rem, totUsed) );
/* Extract the (current) head of the (cmsg hdr) list */
if (!GET_LIST_ELEM(env, list, &elem, &tail))
@@ -17107,9 +17983,10 @@ char* decode_cmsghdrs(ErlNifEnv* env,
}
- SSDBG( descP, ("SOCKET",
- "decode_cmsghdrs -> all %d ctrl headers processed\r\n",
- len) );
+ SSDBG( descP,
+ ("SOCKET",
+ "decode_cmsghdrs {%d} -> all %d ctrl headers processed\r\n",
+ descP->sock, len) );
xres = NULL;
} else {
@@ -17118,9 +17995,10 @@ char* decode_cmsghdrs(ErlNifEnv* env,
*cmsgHdrBufUsed = totUsed;
- SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> done with %s when"
+ SSDBG( descP, ("SOCKET", "decode_cmsghdrs {%d} -> done with %s when"
"\r\n totUsed = %u\r\n",
- ((xres != NULL) ? xres : "NULL"), totUsed) );
+ descP->sock, ((xres != NULL) ? xres : "NULL"),
+ totUsed) );
return xres;
}
@@ -17151,9 +18029,9 @@ char* decode_cmsghdr(ErlNifEnv* env,
size_t rem,
size_t* used)
{
- SSDBG( descP, ("SOCKET", "decode_cmsghdr -> entry with"
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> entry with"
"\r\n eCMsgHdr: %T"
- "\r\n", eCMsgHdr) );
+ "\r\n", descP->sock, eCMsgHdr) );
if (IS_MAP(env, eCMsgHdr)) {
ERL_NIF_TERM eLevel, eType, eData;
@@ -17165,32 +18043,34 @@ char* decode_cmsghdr(ErlNifEnv* env,
if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_level, &eLevel))
return ESOCK_STR_EINVAL;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eLevel: %T"
- "\r\n", eLevel) );
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> eLevel: %T"
+ "\r\n", descP->sock, eLevel) );
if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_type, &eType))
return ESOCK_STR_EINVAL;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eType: %T"
- "\r\n", eType) );
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> eType: %T"
+ "\r\n", descP->sock, eType) );
if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_data, &eData))
return ESOCK_STR_EINVAL;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eData: %T"
- "\r\n", eData) );
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> eData: %T"
+ "\r\n", descP->sock, eData) );
/* Second, decode level */
if ((xres = decode_cmsghdr_level(env, eLevel, &level)) != NULL)
return xres;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr -> level: %d\r\n", level) );
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d}-> level: %d\r\n",
+ descP->sock, level) );
/* third, decode type */
if ((xres = decode_cmsghdr_type(env, level, eType, &type)) != NULL)
return xres;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr -> type: %d\r\n", type) );
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> type: %d\r\n",
+ descP->sock, type) );
/* And finally data
* If its a binary, we are done. Otherwise, we need to check
@@ -17227,16 +18107,16 @@ char* decode_cmsghdr_data(ErlNifEnv* env,
{
char* xres;
- SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> entry with"
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data {%d} -> entry with"
"\r\n eData: %T"
- "\r\n", eData) );
+ "\r\n", descP->sock, eData) );
if (IS_BIN(env, eData)) {
ErlNifBinary bin;
if (GET_BIN(env, eData, &bin)) {
- SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> "
- "do final decode with binary\r\n") );
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data {%d} -> "
+ "do final decode with binary\r\n", descP->sock) );
return decode_cmsghdr_final(descP, bufP, rem, level, type,
(char*) bin.data, bin.size,
used);
@@ -17262,9 +18142,9 @@ char* decode_cmsghdr_data(ErlNifEnv* env,
{
int data;
if (decode_ip_tos(env, eData, &data)) {
- SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> "
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data {%d} -> "
"do final decode with tos (=%d)"
- "\r\n", data) );
+ "\r\n", descP->sock, data) );
return decode_cmsghdr_final(descP, bufP, rem, level, type,
(char*) &data,
sizeof(data),
@@ -17282,9 +18162,9 @@ char* decode_cmsghdr_data(ErlNifEnv* env,
{
int data;
if (GET_INT(env, eData, &data)) {
- SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> "
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data {%d} -> "
"do final decode with ttl (=%d)"
- "\r\n", data) );
+ "\r\n", descP->sock, data) );
return decode_cmsghdr_final(descP, bufP, rem, level, type,
(char*) &data,
sizeof(data),
@@ -17313,9 +18193,9 @@ char* decode_cmsghdr_data(ErlNifEnv* env,
{
int data;
if (GET_INT(env, eData, &data)) {
- SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> "
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data {%d} -> "
"do final decode with tclass (=%d)"
- "\r\n", data) );
+ "\r\n", descP->sock, data) );
return decode_cmsghdr_final(descP, bufP, rem, level, type,
(char*) &data,
sizeof(data),
@@ -17368,11 +18248,12 @@ char* decode_cmsghdr_final(ESockDescriptor* descP,
int len = CMSG_LEN(sz); // length of *actual* data
int space = CMSG_SPACE(sz); // length of (actual) data + padding
- SSDBG( descP, ("SOCKET", "decode_cmsghdr_final -> entry when"
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_final {%d} -> entry when"
"\r\n level: %d"
"\r\n type: %d"
"\r\n sz: %d => %d, %d, %d"
- "\r\n", level, type, sz, len, space, rem) );
+ "\r\n", descP->sock,
+ level, type, sz, len, space, rem) );
if (rem >= space) {
struct cmsghdr* cmsgP = (struct cmsghdr*) bufP;
@@ -17387,14 +18268,16 @@ char* decode_cmsghdr_final(ESockDescriptor* descP,
sys_memcpy(CMSG_DATA(cmsgP), data, sz);
*used = space;
} else {
- SSDBG( descP, ("SOCKET", "decode_cmsghdr_final -> "
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_final {%d} -> "
"not enough space (needs %d, have %d)\r\n",
- space, rem) );
+ descP->sock, space, rem) );
*used = 0;
return ESOCK_STR_EINVAL;
}
- SSDBG( descP, ("SOCKET", "decode_cmsghdr_final -> done\r\n") );
+ SSDBG( descP, ("SOCKET",
+ "decode_cmsghdr_final {%d} -> done ok\r\n",
+ descP->sock) );
return NULL;
}
@@ -18513,9 +19396,9 @@ char* encode_msghdr_flags(ErlNifEnv* env,
ERL_NIF_TERM* flags)
{
SSDBG( descP,
- ("SOCKET", "encode_cmsghdrs_flags -> entry with"
+ ("SOCKET", "encode_cmsghdrs_flags {%d} -> entry with"
"\r\n msgFlags: %d (0x%lX)"
- "\r\n", msgFlags, msgFlags) );
+ "\r\n", descP->sock, msgFlags, msgFlags) );
if (msgFlags == 0) {
*flags = MKEL(env);
@@ -18549,9 +19432,9 @@ char* encode_msghdr_flags(ErlNifEnv* env,
#endif
SSDBG( descP,
- ("SOCKET", "esock_encode_cmsghdrs -> flags processed when"
+ ("SOCKET", "esock_encode_cmsghdrs {%d} -> flags processed when"
"\r\n TArray size: %d"
- "\r\n", TARRAY_SZ(ta)) );
+ "\r\n", descP->sock, TARRAY_SZ(ta)) );
TARRAY_TOLIST(ta, env, flags);
@@ -18974,16 +19857,16 @@ ESockDescriptor* alloc_descriptor(SOCKET sock, HANDLE event)
descP->pattern = ESOCK_DESC_PATTERN_CREATED;
- enif_set_pid_undefined(&descP->connPid);
- MON_INIT(&descP->connMon);
+ requestor_init(&descP->connector);
+ descP->connectorP = NULL;
sprintf(buf, "esock.w[%d]", sock);
descP->writeMtx = MCREATE(buf);
- requestor_clear(&descP->currentWriter);
+ descP->writeState = 0;
+ requestor_init(&descP->currentWriter);
descP->currentWriterP = NULL; // currentWriter not used
descP->writersQ.first = NULL;
descP->writersQ.last = NULL;
- descP->isWritable = FALSE; // TRUE;
descP->writePkgCnt = 0;
descP->writePkgMax = 0;
descP->writePkgMaxCnt = 0;
@@ -18994,11 +19877,11 @@ ESockDescriptor* alloc_descriptor(SOCKET sock, HANDLE event)
sprintf(buf, "esock.r[%d]", sock);
descP->readMtx = MCREATE(buf);
- requestor_clear(&descP->currentReader);
+ descP->readState = 0;
+ requestor_init(&descP->currentReader);
descP->currentReaderP = NULL; // currentReader not used
descP->readersQ.first = NULL;
descP->readersQ.last = NULL;
- descP->isReadable = FALSE; // TRUE;
descP->readPkgCnt = 0;
descP->readPkgMax = 0;
descP->readPkgMaxCnt = 0;
@@ -19006,10 +19889,8 @@ ESockDescriptor* alloc_descriptor(SOCKET sock, HANDLE event)
descP->readTries = 0;
descP->readWaits = 0;
descP->readFails = 0;
-
sprintf(buf, "esock.acc[%d]", sock);
- descP->accMtx = MCREATE(buf);
- requestor_clear(&descP->currentAcceptor);
+ requestor_init(&descP->currentAcceptor);
descP->currentAcceptorP = NULL; // currentAcceptor not used
descP->acceptorsQ.first = NULL;
descP->acceptorsQ.last = NULL;
@@ -19019,27 +19900,29 @@ ESockDescriptor* alloc_descriptor(SOCKET sock, HANDLE event)
descP->accWaits = 0;
sprintf(buf, "esock.close[%d]", sock);
- descP->closeMtx = MCREATE(buf);
+ descP->closing = FALSE;
descP->closeEnv = NULL;
descP->closeRef = esock_atom_undefined;
enif_set_pid_undefined(&descP->closerPid);
MON_INIT(&descP->closerMon);
sprintf(buf, "esock.cfg[%d]", sock);
- descP->cfgMtx = MCREATE(buf);
- descP->rBufSz = ESOCK_RECV_BUFFER_SIZE_DEFAULT;
- descP->rNum = ESOCK_RECV_BUFFER_COUNT_DEFAULT;
- descP->rNumCnt = 0;
- descP->rCtrlSz = ESOCK_RECV_CTRL_BUFFER_SIZE_DEFAULT;
- descP->wCtrlSz = ESOCK_SEND_CTRL_BUFFER_SIZE_DEFAULT;
- descP->iow = FALSE;
- descP->dbg = ESOCK_DEBUG_DEFAULT;
- descP->meta.env = esock_alloc_env("alloc_descriptor - "
- "meta-env");
- descP->meta.ref = esock_atom_undefined;
-
- descP->sock = sock;
- descP->event = event;
+ descP->rBufSz = ESOCK_RECV_BUFFER_SIZE_DEFAULT;
+ descP->rNum = ESOCK_RECV_BUFFER_COUNT_DEFAULT;
+ descP->rNumCnt = 0;
+ descP->rCtrlSz = ESOCK_RECV_CTRL_BUFFER_SIZE_DEFAULT;
+ descP->wCtrlSz = ESOCK_SEND_CTRL_BUFFER_SIZE_DEFAULT;
+ descP->iow = FALSE;
+ descP->dbg = ESOCK_DEBUG_DEFAULT; // Overwritten by caller
+ descP->useReg = ESOCK_USE_SOCKET_REGISTRY; // Overwritten by caller
+ descP->meta.env = esock_alloc_env("alloc_descriptor - "
+ "meta-env");
+ descP->meta.ref = esock_atom_undefined;
+
+ descP->sock = sock;
+ descP->event = event;
+ descP->origFD = INVALID_SOCKET;
+ descP->closeOnClose = TRUE;
enif_set_pid_undefined(&descP->ctrlPid);
MON_INIT(&descP->ctrlMon);
@@ -19077,7 +19960,7 @@ void dec_socket(int domain, int type, int protocol)
cnt_dec(&data.numTypeStreams, 1);
else if (type == SOCK_DGRAM)
cnt_dec(&data.numTypeDGrams, 1);
-#ifdef HAVE_SCTP
+#ifdef SOCK_SEQPACKET
else if (type == SOCK_SEQPACKET)
cnt_dec(&data.numTypeSeqPkgs, 1);
#endif
@@ -19103,8 +19986,6 @@ void dec_socket(int domain, int type, int protocol)
static
void inc_socket(int domain, int type, int protocol)
{
- MLOCK(data.cntMtx);
-
cnt_inc(&data.numSockets, 1);
/* *** Domain counter *** */
@@ -19124,7 +20005,7 @@ void inc_socket(int domain, int type, int protocol)
cnt_inc(&data.numTypeStreams, 1);
else if (type == SOCK_DGRAM)
cnt_inc(&data.numTypeDGrams, 1);
-#ifdef HAVE_SCTP
+#ifdef SOCK_SEQPACKET
else if (type == SOCK_SEQPACKET)
cnt_inc(&data.numTypeSeqPkgs, 1);
#endif
@@ -19140,8 +20021,6 @@ void inc_socket(int domain, int type, int protocol)
else if (protocol == IPPROTO_SCTP)
cnt_inc(&data.numProtoSCTP, 1);
#endif
-
- MUNLOCK(data.cntMtx);
}
@@ -19207,7 +20086,7 @@ BOOLEAN_T etype2type(int etype, int* type)
*type = SOCK_RAW;
break;
-#ifdef HAVE_SCTP
+#ifdef SOCK_SEQPACKET
case ESOCK_TYPE_SEQPACKET:
*type = SOCK_SEQPACKET;
break;
@@ -19304,58 +20183,36 @@ BOOLEAN_T eproto2proto(ErlNifEnv* env,
#ifdef HAVE_SETNS
- /* emap2netns - extract the netns field from the extra map
- *
- * Note that currently we only support one extra option, the netns.
+/* esock_open4_get_netns - extract the netns field from the opts map
*/
static
-BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns)
+BOOLEAN_T esock_open4_get_netns(ErlNifEnv* env, ERL_NIF_TERM opts, char** netns)
{
- size_t sz;
- ERL_NIF_TERM key;
- ERL_NIF_TERM value;
- unsigned int len;
+ ERL_NIF_TERM val;
+ ErlNifBinary bin;
char* buf;
- int written;
-
- /* Note that its acceptable that the extra map is empty */
- if (!enif_get_map_size(env, map, &sz) ||
- (sz != 1)) {
- *netns = NULL;
- return TRUE;
- }
/* The currently only supported extra option is: netns */
- key = enif_make_atom(env, "netns");
- if (!GET_MAP_VAL(env, map, key, &value)) {
+ if (!GET_MAP_VAL(env, opts, atom_netns, &val)) {
*netns = NULL; // Just in case...
return FALSE;
}
- /* So far so good. The value should be a string, check. */
- if (!enif_is_list(env, value)) {
+ /* The value should be a binary file name */
+ if (! enif_inspect_binary(env, val, &bin)) {
*netns = NULL; // Just in case...
return FALSE;
}
- if (!enif_get_list_length(env, value, &len)) {
+ if ((buf = MALLOC(bin.size+1)) == NULL) {
*netns = NULL; // Just in case...
return FALSE;
}
- if ((buf = MALLOC(len+1)) == NULL) {
- *netns = NULL; // Just in case...
- return FALSE;
- }
-
- written = enif_get_string(env, value, buf, len+1, ERL_NIF_LATIN1);
- if (written == (len+1)) {
- *netns = buf;
- return TRUE;
- } else {
- *netns = NULL; // Just in case...
- return FALSE;
- }
+ sys_memcpy(buf, bin.data, bin.size);
+ buf[bin.size] = '\0';
+ *netns = buf;
+ return TRUE;
}
#endif
@@ -19369,93 +20226,73 @@ BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns)
static
BOOLEAN_T esendflags2sendflags(unsigned int eflags, int* flags)
{
- unsigned int ef;
- int tmp = 0;
+ int tmp = 0;
- /* First, check if we have any flags at all */
+ /* Optimize for no flags */
if (eflags == 0) {
*flags = 0;
return TRUE;
}
-
- for (ef = ESOCK_SEND_FLAG_LOW; ef <= ESOCK_SEND_FLAG_HIGH; ef++) {
- switch (ef) {
- case ESOCK_SEND_FLAG_CONFIRM:
- if ((1 << ESOCK_SEND_FLAG_CONFIRM) & eflags) {
+ /* Check for flags out of range */
+ if ((eflags & ~ESOCK_SEND_FLAG_MASK) != 0) {
+ esock_warning_msg("Use of unknown send flag (0x%lX)\r\n",
+ eflags);
+ }
+
+ if ((eflags & ESOCK_SEND_FLAG_CONFIRM) != 0) {
#if defined(MSG_CONFIRM)
- tmp |= MSG_CONFIRM;
+ tmp |= MSG_CONFIRM;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
+ }
- case ESOCK_SEND_FLAG_DONTROUTE:
- if ((1 << ESOCK_SEND_FLAG_DONTROUTE) & eflags) {
+ if ((eflags & ESOCK_SEND_FLAG_DONTROUTE) != 0) {
#if defined(MSG_DONTROUTE)
- tmp |= MSG_DONTROUTE;
+ tmp |= MSG_DONTROUTE;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
+ }
- case ESOCK_SEND_FLAG_EOR:
- if ((1 << ESOCK_SEND_FLAG_EOR) & eflags) {
+ if ((eflags & ESOCK_SEND_FLAG_EOR) != 0) {
#if defined(MSG_EOR)
- tmp |= MSG_EOR;
+ tmp |= MSG_EOR;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
+ }
- case ESOCK_SEND_FLAG_MORE:
- if ((1 << ESOCK_SEND_FLAG_MORE) & eflags) {
+ if ((eflags & ESOCK_SEND_FLAG_MORE) != 0) {
#if defined(MSG_MORE)
- tmp |= MSG_MORE;
+ tmp |= MSG_MORE;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
+ }
- case ESOCK_SEND_FLAG_NOSIGNAL:
- if ((1 << ESOCK_SEND_FLAG_NOSIGNAL) & eflags) {
+ if ((eflags & ESOCK_SEND_FLAG_NOSIGNAL) != 0) {
#if defined(MSG_NOSIGNAL)
- tmp |= MSG_NOSIGNAL;
+ tmp |= MSG_NOSIGNAL;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
+ }
- case ESOCK_SEND_FLAG_OOB:
- if ((1 << ESOCK_SEND_FLAG_OOB) & eflags) {
+ if ((eflags & ESOCK_SEND_FLAG_OOB) != 0) {
#if defined(MSG_OOB)
- tmp |= MSG_OOB;
+ tmp |= MSG_OOB;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
-
- default:
- esock_warning_msg("Use of unknown send flag %d (0x%lX)\r\n",
- ef, eflags);
- return FALSE;
- }
-
}
*flags = tmp;
-
return TRUE;
}
-
/* erecvflags2recvflags - convert internal (erlang) send flags to (proper)
* send flags.
*
@@ -19465,92 +20302,75 @@ BOOLEAN_T esendflags2sendflags(unsigned int eflags, int* flags)
static
BOOLEAN_T erecvflags2recvflags(unsigned int eflags, int* flags)
{
- unsigned int ef;
- int tmp = 0;
+ int tmp = 0;
+
+ /* Optimize for no flags */
if (eflags == 0) {
*flags = 0;
return TRUE;
}
- for (ef = ESOCK_RECV_FLAG_LOW; ef <= ESOCK_RECV_FLAG_HIGH; ef++) {
+ if ((eflags & ~ESOCK_RECV_FLAG_MASK) != 0) {
+ esock_warning_msg("Use of unknown recv flag (0x%lX)\r\n",
+ eflags);
+ }
- switch (ef) {
- case ESOCK_RECV_FLAG_CMSG_CLOEXEC:
- if ((1 << ESOCK_RECV_FLAG_CMSG_CLOEXEC) & eflags) {
+ if ((eflags & ESOCK_RECV_FLAG_CMSG_CLOEXEC) != 0) {
#if defined(MSG_CMSG_CLOEXEC)
- tmp |= MSG_CMSG_CLOEXEC;
+ tmp |= MSG_CMSG_CLOEXEC;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
+ }
- case ESOCK_RECV_FLAG_ERRQUEUE:
- if ((1 << ESOCK_RECV_FLAG_ERRQUEUE) & eflags) {
+ if ((eflags & ESOCK_RECV_FLAG_ERRQUEUE) != 0) {
#if defined(MSG_ERRQUEUE)
- tmp |= MSG_ERRQUEUE;
+ tmp |= MSG_ERRQUEUE;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
+ }
- case ESOCK_RECV_FLAG_OOB:
- if ((1 << ESOCK_RECV_FLAG_OOB) & eflags) {
+ if ((eflags & ESOCK_RECV_FLAG_OOB) != 0) {
#if defined(MSG_OOB)
- tmp |= MSG_OOB;
+ tmp |= MSG_OOB;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
+ }
- /*
- * <KOLLA>
- *
- * We need to handle this, because it may effect the read algorithm
- *
- * </KOLLA>
- */
- case ESOCK_RECV_FLAG_PEEK:
- if ((1 << ESOCK_RECV_FLAG_PEEK) & eflags) {
+ /*
+ * <KOLLA>
+ *
+ * We need to handle this, because it may effect the read algorithm
+ *
+ * </KOLLA>
+ */
+ if ((eflags & ESOCK_RECV_FLAG_PEEK) != 0) {
#if defined(MSG_PEEK)
- tmp |= MSG_PEEK;
+ tmp |= MSG_PEEK;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
+ }
- case ESOCK_RECV_FLAG_TRUNC:
- if ((1 << ESOCK_RECV_FLAG_TRUNC) & eflags) {
+ if ((eflags & ESOCK_RECV_FLAG_TRUNC) != 0) {
#if defined(MSG_TRUNC)
- tmp |= MSG_TRUNC;
+ tmp |= MSG_TRUNC;
#else
- return FALSE;
+ return FALSE;
#endif
- }
- break;
-
- default:
- esock_warning_msg("Use of unknown recv flag %d (0x%lX)\r\n",
- ef, eflags);
- return FALSE;
- }
-
}
*flags = tmp;
-
return TRUE;
}
-/* eproto2proto - convert internal (erlang) protocol to (proper) protocol
- *
- * Note that only a subset is supported.
+/* ehow2how - convert internal (erlang) "shutdown how" to
+ * (proper) "shutdown how"
*/
static
BOOLEAN_T ehow2how(unsigned int ehow, int* how)
@@ -19611,6 +20431,10 @@ BOOLEAN_T ecommand2command(ErlNifEnv* env,
}
if (COMPARE(ecmd, esock_atom_debug) == 0) {
*command = ESOCK_CMD_DEBUG;
+ } else if (COMPARE(ecmd, atom_socket_debug) == 0) {
+ *command = ESOCK_CMD_SOCKET_DEBUG;
+ } else if (COMPARE(ecmd, atom_use_registry) == 0) {
+ *command = ESOCK_CMD_USE_SOCKET_REGISTRY;
} else {
SGDBG( ("SOCKET", "ecommand2command -> unknown command %T\r\n", ecmd) );
return FALSE;
@@ -19728,22 +20552,11 @@ char* esock_send_close_msg(ErlNifEnv* env,
ErlNifPid* pid)
{
ERL_NIF_TERM sockRef, msg;
- ErlNifEnv* menv;
- char* result;
-
- if (descP->closeEnv != NULL) {
- sockRef = enif_make_resource(descP->closeEnv, descP);
- msg = mk_close_msg(descP->closeEnv, sockRef, descP->closeRef);
- menv = descP->closeEnv;
- } else {
- sockRef = enif_make_resource(env, descP);
- msg = mk_close_msg(env, sockRef, descP->closeRef);
- menv = NULL; // This has the effect that the message will be copied
- }
-
- result = esock_send_msg(env, pid, msg, menv);
- descP->closeEnv = NULL;
- return result;
+
+ sockRef = enif_make_resource(descP->closeEnv, descP);
+ msg = mk_close_msg(descP->closeEnv, sockRef, descP->closeRef);
+
+ return esock_send_msg(env, pid, msg, descP->closeEnv);
}
@@ -19781,8 +20594,7 @@ char* esock_send_msg(ErlNifEnv* env,
ErlNifEnv* msgEnv)
{
int res = enif_send(env, pid, msgEnv, msg);
- if (msgEnv)
- esock_free_env("esock_msg_send - msg-env", msgEnv);
+ esock_free_env("esock_msg_send - msg-env", msgEnv);
if (!res)
return str_exsend;
@@ -19916,7 +20728,7 @@ ERL_NIF_TERM mk_select_msg(ErlNifEnv* env,
*
* {'$socket', Socket, Tag, Info}
*
- * Socket :: socket() (#socket{})
+ * Socket :: socket:socket()
* Tag :: atom()
* Info :: term()
*
@@ -19935,15 +20747,15 @@ ERL_NIF_TERM mk_socket_msg(ErlNifEnv* env,
/* *** mk_socket ***
*
- * Simple utility function that construct the socket resord:
+ * Simple utility function that construct the socket tuple:
*
- * #socket{ref = SockRef} => {socket, SockRef :: reference()}
+ * socket:socket() :: {'$socket', SockRef :: reference()}
*/
static
ERL_NIF_TERM mk_socket(ErlNifEnv* env,
ERL_NIF_TERM sockRef)
{
- return MKT2(env, esock_atom_socket, sockRef);
+ return MKT2(env, esock_atom_socket_tag, sockRef);
}
#endif // #if defined(__WIN32__)
@@ -20008,9 +20820,10 @@ int esock_select_write(ErlNifEnv* env,
*
* WARNING: enif_select may call esock_stop directly
* in which case deadlock is avoided by esock_stop that checks
- * if it got a direct call and then does not lock closeMtx.
+ * if it got a direct call and then does not lock readMtx and writeMtx.
*
- * So closeMtx is supposed to be locked when this function is called.
+ * So readMtx and writeMtx are supposed to be locked
+ * when this function is called.
*/
static
int esock_select_stop(ErlNifEnv* env,
@@ -20072,17 +20885,20 @@ int esock_select_cancel(ErlNifEnv* env,
\
/* There was another one */ \
\
- SSDBG( descP, \
+ SSDBG( descP, \
("SOCKET", \
- "activate_next_" #F " -> new (active) requestor: " \
+ "activate_next_" #F "(%T) {%d} ->" \
+ " new (active) requestor: " \
"\r\n pid: %T" \
"\r\n ref: %T" \
- "\r\n", reqP->pid, reqP->ref) ); \
+ "\r\n", sockRef, descP->sock, \
+ reqP->pid, reqP->ref) ); \
\
/* We need to copy req ref to 'env' */ \
- if ((sres = esock_select_##S(env, descP->sock, descP, \
- &reqP->pid, sockRef, \
- CP_TERM(env, reqP->ref))) < 0) { \
+ if ((sres = \
+ esock_select_##S(env, descP->sock, descP, \
+ &reqP->pid, sockRef, \
+ CP_TERM(env, reqP->ref))) < 0) { \
\
/* We need to inform this process, reqP->pid, */ \
/* that we failed to select, so we don't leave */ \
@@ -20109,7 +20925,9 @@ int esock_select_cancel(ErlNifEnv* env,
\
SSDBG( descP, \
("SOCKET", \
- "activate_next_" #F " -> no more requestors\r\n") ); \
+ "activate_next_" #F "(%T) {%d} ->" \
+ " no more requestors\r\n", \
+ sockRef, descP->sock) ); \
\
popped = TRUE; \
activated = FALSE; \
@@ -20118,8 +20936,9 @@ int esock_select_cancel(ErlNifEnv* env,
} while (!popped); \
\
SSDBG( descP, \
- ("SOCKET", "activate_next_" #F " -> " \
- "done with %s\r\n", B2S(activated)) ); \
+ ("SOCKET", "activate_next_" #F "(%T) {%d} -> " \
+ "done with %s\r\n", \
+ sockRef, descP->sock, B2S(activated)) ); \
\
return activated; \
}
@@ -20195,10 +21014,10 @@ REQ_SEARCH4PID_FUNCS
if (MONP("reader_push -> " #F " request", \
env, descP, &pid, &reqP->mon) != 0) { \
FREE(e); \
- return esock_make_error(env, atom_exmon); \
+ return esock_make_error(env, atom_exmonitor); \
} \
- reqP->env = esock_alloc_env(#F "_push"); \
- reqP->ref = enif_make_copy(reqP->env, ref); \
+ reqP->env = esock_alloc_env(#F "_push"); \
+ reqP->ref = CP_TERM(reqP->env, ref); \
\
qpush(&descP->Q, e); \
\
@@ -20271,8 +21090,7 @@ BOOLEAN_T requestor_pop(ESockRequestQueue* q,
{
ESockRequestQueueElement* e = qpop(q);
- if (reqP->env)
- esock_free_env("requestor_pop", reqP->env);
+ esock_free_env("requestor_pop", reqP->env);
if (e != NULL) {
reqP->pid = e->data.pid;
@@ -20283,19 +21101,34 @@ BOOLEAN_T requestor_pop(ESockRequestQueue* q,
return TRUE;
} else {
/* Queue was empty */
- requestor_clear(reqP);
+ requestor_init(reqP);
return FALSE;
}
}
-static void requestor_clear(ESockRequestor* reqP) {
+static void requestor_init(ESockRequestor* reqP) {
enif_set_pid_undefined(&reqP->pid);
MON_INIT(&reqP->mon);
reqP->env = NULL;
reqP->ref = esock_atom_undefined;
}
+static int requestor_release(const char* slogan,
+ ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ESockRequestor* reqP) {
+ int res;
+
+ enif_set_pid_undefined(&reqP->pid);
+ res = DEMONP(slogan, env, descP, &reqP->mon);
+ esock_free_env(slogan, reqP->env);
+ reqP->env = NULL;
+ reqP->ref = esock_atom_undefined;
+
+ return res;
+}
+
static
BOOLEAN_T qsearch4pid(ErlNifEnv* env,
@@ -20389,8 +21222,7 @@ BOOLEAN_T qunqueue(ErlNifEnv* env,
}
}
- if (e->data.env)
- esock_free_env("qunqueue", e->data.env);
+ esock_free_env("qunqueue", e->data.env);
FREE(e);
return TRUE;
@@ -20465,16 +21297,27 @@ int esock_monitor(const char* slogan,
{
int res;
- SSDBG( descP, ("SOCKET", "[%d][%T] %s: try monitor\r\n",
+ SSDBG( descP, ("SOCKET",
+ "esock_monitor {%d} [%T] %s: try monitor\r\n",
descP->sock, esock_self(env), slogan) );
+
res = enif_monitor_process(env, descP, pid, &monP->mon);
if (res != 0) {
monP->isActive = FALSE;
- SSDBG( descP, ("SOCKET", "[%d][%T] %s: monitor failed: %d\r\n",
- descP->sock, esock_self(env), slogan, res) );
+
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_monitor {%d} [%T] %s: monitor failed: %d\r\n",
+ descP->sock, esock_self(env), slogan, res) );
} else {
monP->isActive = TRUE;
+
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_monitor {%d} [%T] %s: monitor ok: %T\r\n",
+ descP->sock, esock_self(env), slogan,
+ esock_make_monitor_term(env, monP)) );
}
return res;
@@ -20489,19 +21332,21 @@ int esock_demonitor(const char* slogan,
{
int res;
- if (!monP->isActive)
+ if (! monP->isActive)
return 1;
- SSDBG( descP, ("SOCKET", "[%d][%T] %s: try demonitor\r\n",
- descP->sock, esock_self(env), slogan) );
+ SSDBG( descP, ("SOCKET",
+ "esock_demonitor {%d} [%T] %s: try demonitor %T\r\n",
+ descP->sock, esock_self(env), slogan,
+ esock_make_monitor_term(env, monP)) );
res = enif_demonitor_process(env, descP, &monP->mon);
+ esock_monitor_init(monP);
- if (res == 0) {
- MON_INIT(monP);
- } else {
+ if (res != 0) {
SSDBG( descP,
- ("SOCKET", "[%d][%T] %s: demonitor failed: %d\r\n",
+ ("SOCKET",
+ "esock_demonitor {%d}[%T] %s: demonitor failed: %d\r\n",
descP->sock, esock_self(env), slogan, res) );
}
@@ -20525,17 +21370,12 @@ ERL_NIF_TERM esock_make_monitor_term(ErlNifEnv* env, const ESockMonitor* monP)
return esock_atom_undefined;
}
-
-
-static
-void esock_release_current(const char* slogan,
- ErlNifEnv* env,
- ESockDescriptor* descP,
- ESockRequestor* current)
-{
- DEMONP(slogan, env, descP, &current->mon);
- esock_free_env(slogan, current->env);
- requestor_clear(current);
+static BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP,
+ const ErlNifMonitor* mon) {
+ if (monP->isActive)
+ return enif_compare_monitors(&monP->mon, mon) == 0;
+ else
+ return FALSE;
}
#endif // if !defined(__WIN32__)
@@ -20553,8 +21393,7 @@ static void free_request_queue(ESockRequestQueue* q)
while (q->first) {
ESockRequestQueueElement* free_me = q->first;
q->first = free_me->nextP;
- if (free_me->data.env)
- esock_free_env("dtor", free_me->data.env);
+ esock_free_env("dtor", free_me->data.env);
FREE(free_me);
}
}
@@ -20575,27 +21414,14 @@ void esock_dtor(ErlNifEnv* env, void* obj)
SGDBG( ("SOCKET", "dtor -> try destroy write mutex\r\n") );
MDESTROY(descP->writeMtx); descP->writeMtx = NULL;
- SGDBG( ("SOCKET", "dtor -> try destroy accept mutex\r\n") );
- MDESTROY(descP->accMtx); descP->accMtx = NULL;
-
- SGDBG( ("SOCKET", "dtor -> try destroy close mutex\r\n") );
- MDESTROY(descP->closeMtx); descP->closeMtx = NULL;
+ esock_free_env("dtor reader", descP->currentReader.env);
+ descP->currentReader.env = NULL;
- SGDBG( ("SOCKET", "dtor -> try destroy config mutex\r\n") );
- MDESTROY(descP->cfgMtx); descP->cfgMtx = NULL;
+ esock_free_env("dtor writer", descP->currentWriter.env);
+ descP->currentWriter.env = NULL;
- if (descP->currentReader.env) {
- esock_free_env("dtor reader", descP->currentReader.env);
- descP->currentReader.env = NULL;
- }
- if (descP->currentWriter.env) {
- esock_free_env("dtor writer", descP->currentWriter.env);
- descP->currentWriter.env = NULL;
- }
- if (descP->currentAcceptor.env) {
- esock_free_env("dtor acceptor", descP->currentAcceptor.env);
- descP->currentAcceptor.env = NULL;
- }
+ esock_free_env("dtor acceptor", descP->currentAcceptor.env);
+ descP->currentAcceptor.env = NULL;
SGDBG( ("SOCKET", "dtor -> try free readers request queue\r\n") );
free_request_queue(&descP->readersQ);
@@ -20606,8 +21432,20 @@ void esock_dtor(ErlNifEnv* env, void* obj)
SGDBG( ("SOCKET", "dtor -> try free acceptors request queue\r\n") );
free_request_queue(&descP->acceptorsQ);
+ esock_free_env("dtor close-env", descP->closeEnv);
+ descP->closeEnv = NULL;
+
+ esock_free_env("dtor meta-env", descP->meta.env);
+ descP->meta.env = NULL;
+
+ if (! IS_CLOSED(descP)) {
+ SGDBG( ("SOCKET", "dtor -> had to close socket\r\n") );
+ (void) esock_close_socket(env, descP);
+ }
+
SGDBG( ("SOCKET", "dtor -> set state and pattern\r\n") );
- descP->state = ESOCK_STATE_DTOR;
+ descP->readState |= ESOCK_STATE_DTOR;
+ descP->writeState |= ESOCK_STATE_DTOR;
descP->pattern = ESOCK_DESC_PATTERN_DTOR;
SGDBG( ("SOCKET", "dtor -> done\r\n") );
@@ -20631,30 +21469,23 @@ void esock_dtor(ErlNifEnv* env, void* obj)
*
*/
static
-void esock_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call)
+void esock_stop(ErlNifEnv* env, void* obj, ErlNifEvent fd, int is_direct_call)
{
#if !defined(__WIN32__)
ESockDescriptor* descP = (ESockDescriptor*) obj;
- ERL_NIF_TERM sockRef;
- SSDBG( descP,
- ("SOCKET", "esock_stop -> entry when %s"
- "\r\n sock: %d (%d)"
- "\r\n",
- ((is_direct_call) ? "called" : "scheduled"), descP->sock, fd) );
-
- /* +++ Lock it down +++ */
-
- /* If we are called with a direct call; we already have closeMtx
+ /* If we are called with a direct call;
+ * we already have readMtx and writeMtx
*/
- if (!is_direct_call) MLOCK(descP->closeMtx);
- MLOCK(descP->readMtx);
- MLOCK(descP->accMtx);
- MLOCK(descP->writeMtx);
- MLOCK(descP->cfgMtx);
+ if (!is_direct_call) {
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+ }
- SSDBG( descP, ("SOCKET", "esock_stop -> "
- "[%d, %T] all mutex(s) locked when counters:"
+ SSDBG( descP, ("SOCKET", "esock_stop {%d/%d} -> when %s"
+ "\r\n ctrlPid: %T"
+ "\r\n closerPid: %T"
+ "\r\ncounters:"
"\r\n writePkgCnt: %u"
"\r\n writePkgMax: %u"
"\r\n writeByteCnt: %u"
@@ -20667,7 +21498,11 @@ void esock_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call)
"\r\n readTries: %u"
"\r\n readWaits: %u"
"\r\n",
- descP->sock, descP->ctrlPid,
+ descP->sock, fd,
+ (is_direct_call) ? "called" : "scheduled",
+ descP->ctrlPid,
+ descP->closerPid,
+ //
descP->writePkgCnt,
descP->writePkgMax,
descP->writeByteCnt,
@@ -20680,149 +21515,83 @@ void esock_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call)
descP->readTries,
descP->readWaits) );
- sockRef = enif_make_resource(env, descP);
- descP->state = ESOCK_STATE_CLOSING; // Just in case...???
- descP->isReadable = FALSE;
- descP->isWritable = FALSE;
-
- /* We should check that we actually have a monitor.
- * This *should* be done with a "NULL" monitor value,
- * which there currently is none...
- * If we got here because the controlling process died,
- * there is no point to demonitor. Also, we do not actually
- * have a monitor in that case...
- */
- DEMONP("esock_stop -> ctrl", env, descP, &descP->ctrlMon);
-
-
-
/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*
- * Check current and waiting Writers
+ * Inform waiting Closer, or close socket
*
* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
- if (descP->currentWriterP != NULL) {
+ if (enif_is_pid_undefined(&descP->closerPid)) {
+ int err;
- /* We have a (current) writer and *may* therefor also have
- * writers waiting.
- */
+ /* We do not have a closer process
+ * - we have to do an unclean (non blocking) close */
- esock_stop_handle_current(env,
- "writer",
- descP, sockRef, &descP->currentWriter);
+ err = esock_close_socket(env, descP);
- /* And also deal with the waiting writers (in the same way) */
- SSDBG( descP, ("SOCKET", "esock_stop -> handle waiting writer(s)\r\n") );
- inform_waiting_procs(env, "writer",
- descP, sockRef, &descP->writersQ, TRUE, atom_closed);
- }
+ if (err != 0)
+ esock_warning_msg("Failed closing socket for terminating "
+ "controlling process: "
+ "\r\n Controlling Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Errno: %d (%T)"
+ "\r\n",
+ descP->ctrlPid, descP->sock,
+ err,
+ MKA(env, erl_errno_id(err)));
+ } else {
+ /* We have a closer process, so this is nif_close */
- /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- *
- * Check current and waiting Readers
- *
- * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- */
+ if (is_direct_call) {
- if (descP->currentReaderP != NULL) {
-
- /* We have a (current) reader and *may* therefor also have
- * readers waiting.
- */
-
- esock_stop_handle_current(env,
- "reader",
- descP, sockRef, &descP->currentReader);
-
- /* And also deal with the waiting readers (in the same way) */
- SSDBG( descP, ("SOCKET", "esock_stop -> handle waiting reader(s)\r\n") );
- inform_waiting_procs(env, "reader",
- descP, sockRef, &descP->readersQ, TRUE, atom_closed);
- }
+ /* We do not send a message to the closer process since it will
+ * be informed with the return value from nif_close,
+ * but we need to free the environment here as
+ * the message send in the else branch below does
+ */
+ esock_free_env("esock_stop - close-env", descP->closeEnv);
+ } else {
+ char *result;
- /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- *
- * Check current and waiting Acceptors
- *
- * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- */
+ /* +++ send close message to the waiting closer +++ */
+ result = esock_send_close_msg(env, descP, &descP->closerPid);
- if (descP->currentAcceptorP != NULL) {
+ if (result != NULL)
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_stop {%d} -> send close failed: %s"
+ "\r\n closer: %T"
+ "\r\n", descP->sock, result,
+ descP->closerPid) );
+ }
- /* We have a (current) acceptor and *may* therefor also have
- * acceptors waiting.
+ /* Setting closeEnv = NULL is used as a marker for
+ * esock_down() that this callback (esock_stop())
+ * has been executed and will not close the socket
*/
- esock_stop_handle_current(env,
- "acceptor",
- descP, sockRef, &descP->currentAcceptor);
-
- /* And also deal with the waiting acceptors (in the same way) */
- SSDBG( descP, ("SOCKET", "esock_stop -> handle waiting acceptor(s)\r\n") );
- inform_waiting_procs(env, "acceptor",
- descP, sockRef, &descP->acceptorsQ, TRUE, atom_closed);
- }
-
-
-
- /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- *
- * Maybe inform waiting closer
- *
- * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- */
-
- if (descP->sock != INVALID_SOCKET) {
-
- if (descP->closeLocal) {
-
- if (!is_direct_call) {
-
- /* +++ send close message to the waiting process +++ */
-
- esock_send_close_msg(env, descP, &descP->closerPid);
-
- DEMONP("esock_stop -> closer", env, descP, &descP->closerMon);
-
- } else {
-
- /* We only need to explicitly free the environment here
- * since the message send takes care of it if scheduled.
- */
-
- if (descP->closeEnv != NULL) {
- esock_free_env("esock_stop - close-env", descP->closeEnv);
- descP->closeEnv = NULL;
- }
-
- }
- }
+ descP->closeEnv = NULL;
+ descP->closeRef = esock_atom_undefined;
}
- if (descP->meta.env != NULL) {
- esock_free_env("esock_stop - meta-env", descP->meta.env);
- descP->meta.env = NULL;
- }
-
- SSDBG( descP, ("SOCKET", "esock_stop -> unlock all mutex(s)\r\n") );
+ /* +++++++ Clear the meta option +++++++ */
- MUNLOCK(descP->cfgMtx);
- MUNLOCK(descP->writeMtx);
- MUNLOCK(descP->accMtx);
- MUNLOCK(descP->readMtx);
- if (!is_direct_call) MUNLOCK(descP->closeMtx);
-
- /* And finally update the registry */
- esock_send_reg_del_msg(env, sockRef);
+ enif_clear_env(descP->meta.env);
+ descP->meta.ref = esock_atom_undefined;
SSDBG( descP,
- ("SOCKET", "esock_stop -> done (%d, %d)\r\n", descP->sock, fd) );
+ ("SOCKET",
+ "esock_stop {%d/%d} -> done\r\n",
+ descP->sock, fd) );
+ if (!is_direct_call) {
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+ }
#endif // if !defined(__WIN32__)
}
@@ -20841,34 +21610,32 @@ void esock_stop_handle_current(ErlNifEnv* env,
ERL_NIF_TERM sockRef,
ESockRequestor* reqP)
{
- SSDBG( descP, ("SOCKET", "esock_stop -> handle current %s\r\n", role) );
-
DEMONP("esock_stop_handle_current", env, descP, &reqP->mon);
- if ((! enif_is_pid_undefined(&descP->closerPid)) &&
- (COMPARE_PIDS(&descP->closerPid, &reqP->pid) != 0)) {
-
- SSDBG( descP, ("SOCKET", "esock_stop_handle_current -> "
- "send abort message to current %s %T\r\n",
- role, reqP->pid) );
+ SSDBG( descP, ("SOCKET",
+ "esock_stop_handle_current {%d} ->"
+ " send abort message to current %s %T %T\r\n",
+ descP->sock, role, reqP->pid, reqP->ref) );
- if (esock_send_abort_msg(env, sockRef, reqP->ref, reqP->env,
- atom_closed, &reqP->pid) != NULL) {
+ if (esock_send_abort_msg(env, sockRef, reqP->ref, reqP->env,
+ atom_closed, &reqP->pid) != NULL) {
- esock_warning_msg("esock_stop_handle_current: "
- "Failed sending abort (closed) message to "
- "current %s %T\r\n",
- role, reqP->pid);
- }
- reqP->env = NULL;
+ esock_warning_msg("esock_stop_handle_current {%d}:"
+ " Failed sending abort (closed) message to"
+ " current %s %T\r\n",
+ descP->sock, role, reqP->pid);
}
+
+ enif_set_pid_undefined(&reqP->pid);
+ reqP->env = NULL;
+ reqP->ref = esock_atom_undefined;
}
/* This function traverse the queue and sends the specified
* nif_abort message with the specified reason to each member,
- * and if the 'free' argument is TRUE, the queue will be emptied.
+ * and empty the queue.
*/
static
void inform_waiting_procs(ErlNifEnv* env,
@@ -20876,7 +21643,6 @@ void inform_waiting_procs(ErlNifEnv* env,
ESockDescriptor* descP,
ERL_NIF_TERM sockRef,
ESockRequestQueue* q,
- BOOLEAN_T free,
ERL_NIF_TERM reason)
{
ESockRequestQueueElement* currentP = q->first;
@@ -20892,15 +21658,16 @@ void inform_waiting_procs(ErlNifEnv* env,
*
* Should we inform anyone if we fail to demonitor?
* NOT SURE WHAT THAT WOULD REPRESENT AND IT IS NOT
- * IMPORTANT IN *THIS* CASE, BUT ITS A FUNDAMENTAL OP...
+ * IMPORTANT IN *THIS* CASE, BUT IT'S A FUNDAMENTAL OP...
*
* </KOLLA>
*/
SSDBG( descP,
("SOCKET",
- "inform_waiting_procs -> "
+ "inform_waiting_procs(%T) {%d} -> "
"send abort message to waiting %s %T\r\n",
+ sockRef, descP->sock,
role, currentP->data.pid) );
if (esock_send_abort_msg(env,
@@ -20923,14 +21690,12 @@ void inform_waiting_procs(ErlNifEnv* env,
DEMONP("inform_waiting_procs -> current 'request'",
env, descP, &currentP->data.mon);
nextP = currentP->nextP;
- if (free) FREE(currentP);
+ FREE(currentP);
currentP = nextP;
}
- if (free) {
- q->first = NULL;
- q->last = NULL;
- }
+ q->first = NULL;
+ q->last = NULL;
}
#endif // if !defined(__WIN32__)
@@ -20947,11 +21712,11 @@ void esock_down(ErlNifEnv* env,
{
#if !defined(__WIN32__)
ESockDescriptor* descP = (ESockDescriptor*) obj;
- int sres;
- ERL_NIF_TERM sockRef;
- SSDBG( descP, ("SOCKET", "esock_down -> entry with"
- "\r\n sock: %d"
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ SSDBG( descP, ("SOCKET", "esock_down {%d} -> entry with"
"\r\n pid: %T"
"\r\n Close: %s (%s)"
"\r\n",
@@ -20959,98 +21724,80 @@ void esock_down(ErlNifEnv* env,
B2S(IS_CLOSED(descP)),
B2S(IS_CLOSING(descP))) );
- if (!IS_CLOSED(descP)) {
-
- if (COMPARE_PIDS(&descP->ctrlPid, pid) == 0) {
+ if (COMPARE_PIDS(&descP->closerPid, pid) == 0) {
- /* We don't bother with the queue cleanup here -
- * we leave it to the stop callback function.
- */
+ /* The closer process went down
+ * - it will not be able to call nif_finalize_close
+ */
- MLOCK(descP->closeMtx);
+ if (MON_EQ(&descP->closerMon, mon)) {
+ MON_INIT(&descP->closerMon);
SSDBG( descP,
- ("SOCKET", "esock_down -> controlling process exit\r\n") );
-
- descP->state = ESOCK_STATE_CLOSING;
- descP->closeLocal = TRUE;
- descP->closerPid = *pid;
- MON_INIT(&descP->closerMon);
+ ("SOCKET",
+ "esock_down {%d} -> closer process exit\r\n",
+ descP->sock) );
- sres = esock_select_stop(env, descP->sock, descP);
+ } else if (MON_EQ(&descP->ctrlMon, mon)) {
+ MON_INIT(&descP->ctrlMon);
- if (sres & ERL_NIF_SELECT_STOP_CALLED) {
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down {%d} -> closer controlling process exit\r\n",
+ descP->sock) );
+ }
- /* We are done - we can finalize (socket close) directly */
- SSDBG( descP,
- ("SOCKET",
- "esock_down -> [%d] stop called\r\n", descP->sock) );
+ enif_set_pid_undefined(&descP->closerPid);
- dec_socket(descP->domain, descP->type, descP->protocol);
- descP->state = ESOCK_STATE_CLOSED;
+ if (descP->closeEnv != NULL) {
- /* And finally close the socket.
- * Since we close the socket because of an exiting owner,
- * we do not need to wait for buffers to sync (linger).
- * If the owner wish to ensure the buffer are written,
- * it should have closed the socket explicitly...
- */
- if (sock_close(descP->sock) != 0) {
- int save_errno = sock_errno();
-
- esock_warning_msg("Failed closing socket for terminating "
- "controlling process: "
- "\r\n Controlling Process: %T"
- "\r\n Descriptor: %d"
- "\r\n Errno: %d (%T)"
- "\r\n",
- pid, descP->sock,
- save_errno,
- MKA(env, erl_errno_id(save_errno)));
- }
- sock_close_event(descP->event);
+ /* esock_stop has not been called yet
+ * - clear the closer so esock_stop will close the socket */
+ esock_free_env("esock_down - close-env", descP->closeEnv);
+ descP->closeEnv = NULL;
+ descP->closeRef = esock_atom_undefined;
- descP->sock = INVALID_SOCKET;
- descP->event = INVALID_EVENT;
+ } else {
+ int err;
- descP->state = ESOCK_STATE_CLOSED;
+ /* esock_stop has sent its message to the closer,
+ * but the closer died before calling nif_finalize_close
+ * - we have to do an unclean (non blocking) socket close here
+ */
- } else if (sres & ERL_NIF_SELECT_STOP_SCHEDULED) {
+ err = esock_close_socket(env, descP);
+
+ if (err != 0)
+ esock_warning_msg("Failed closing socket for terminating "
+ "closer process: "
+ "\r\n Closer Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Errno: %d (%T)"
+ "\r\n",
+ pid, descP->sock,
+ err,
+ MKA(env, erl_errno_id(err)));
+ }
+ } else if (MON_EQ(&descP->ctrlMon, mon)) {
+ MON_INIT(&descP->ctrlMon);
- /* The stop callback function has been *scheduled* which means
- * that "should" wait for it to complete. But since we are in
- * a callback (down) function, we cannot...
- * So, we must close the socket
- */
- SSDBG( descP,
- ("SOCKET",
- "esock_down -> [%d] stop scheduled\r\n",
- descP->sock) );
+ /* The owner went down */
- dec_socket(descP->domain, descP->type, descP->protocol);
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down {%d} -> controlling process exit\r\n",
+ descP->sock) );
- /* And now what? We can't wait for the stop function here...
- * So, we simply close it here and leave the rest of the "close"
- * for later (when the stop function actually gets called...
- */
+ if (IS_OPEN(descP)) {
+ int sres;
- if (sock_close(descP->sock) != 0) {
- int save_errno = sock_errno();
-
- esock_warning_msg("Failed closing socket for terminating "
- "controlling process: "
- "\r\n Controlling Process: %T"
- "\r\n Descriptor: %d"
- "\r\n Errno: %d (%T)"
- "\r\n",
- pid, descP->sock,
- save_errno,
- MKA(env, erl_errno_id(save_errno)));
- }
- sock_close_event(descP->event);
+ /* Socket not closed and no close in progress - initiate close */
- } else {
+ descP->closing = TRUE;
+ sres = esock_do_stop(env, descP);
+ if (sres < 0)
+ /* XXX Should we notify the registry? */
esock_warning_msg("Failed selecting stop when handling down "
"of controlling process: "
"\r\n Select Res: %d"
@@ -21059,51 +21806,57 @@ void esock_down(ErlNifEnv* env,
"\r\n Monitor: %T"
"\r\n", sres, pid, descP->sock,
MON2T(env, mon));
- }
-
- MUNLOCK(descP->closeMtx);
+ }
- } else if (COMPARE_PIDS(&descP->connPid, pid) == 0) {
+ } else if (descP->connectorP != NULL &&
+ MON_EQ(&descP->connector.mon, mon)) {
+ MON_INIT(&descP->connector.mon);
- /* The connPid is only set during the connection.
- * The same goes for the monitor (connMon).
- */
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down {%d} -> connector process exit\r\n",
+ descP->sock) );
- descP->state = ESOCK_STATE_OPEN; /* restore state */
- enif_set_pid_undefined(&descP->connPid);
- DEMONP("esock_down -> connector",
- env, descP, &descP->connMon);
+ /* connectorP is only set during connection.
+ * Forget all about the ongoing connection.
+ * We might end up connected, but the process that initiated
+ * the connection has died and will never know
+ */
- } else {
+ requestor_release("esock_down->connector",
+ env, descP, descP->connectorP);
- /* check all operation queue(s): acceptor, writer and reader.
- *
- * Is it really any point in doing this if the socket is closed?
- *
- */
+ descP->writeState &= ~ESOCK_STATE_CONNECTING;
- SSDBG( descP, ("SOCKET", "esock_down -> other process term\r\n") );
+ descP->connectorP = NULL;
- sockRef = enif_make_resource(env, descP);
+ } else {
+ ERL_NIF_TERM sockRef;
- MLOCK(descP->readMtx);
- MLOCK(descP->accMtx);
- MLOCK(descP->writeMtx);
+ /* check all operation queue(s): acceptor, writer and reader.
+ *
+ * Is it really any point in doing this if the socket is closed?
+ *
+ */
- if (descP->currentReaderP != NULL)
- esock_down_reader(env, descP, sockRef, pid);
- if (descP->currentAcceptorP != NULL)
- esock_down_acceptor(env, descP, sockRef, pid);
- if (descP->currentWriterP != NULL)
- esock_down_writer(env, descP, sockRef, pid);
+ sockRef = enif_make_resource(env, descP);
- MUNLOCK(descP->writeMtx);
- MUNLOCK(descP->accMtx);
- MUNLOCK(descP->readMtx);
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down(%T) {%d} -> other process term\r\n",
+ sockRef, descP->sock) );
- }
+ if (descP->currentReaderP != NULL)
+ esock_down_reader(env, descP, sockRef, pid, mon);
+ if (descP->currentAcceptorP != NULL)
+ esock_down_acceptor(env, descP, sockRef, pid, mon);
+ if (descP->currentWriterP != NULL)
+ esock_down_writer(env, descP, sockRef, pid, mon);
}
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+
SSDBG( descP, ("SOCKET", "esock_down -> done\r\n") );
#endif // if !defined(__WIN32__)
@@ -21118,23 +21871,30 @@ void esock_down(ErlNifEnv* env,
*/
#if !defined(__WIN32__)
static
-void esock_down_acceptor(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- const ErlNifPid* pid)
+void esock_down_acceptor(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon)
{
- if (COMPARE_PIDS(&descP->currentAcceptor.pid, pid) == 0) {
+ if (MON_EQ(&descP->currentAcceptor.mon, mon)) {
+ MON_INIT(&descP->currentAcceptor.mon);
- SSDBG( descP, ("SOCKET",
- "esock_down_acceptor -> "
- "current acceptor - try activate next\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down_acceptor(%T) {%d} -> "
+ "current acceptor - try activate next\r\n",
+ sockRef, descP->sock) );
if (!activate_next_acceptor(env, descP, sockRef)) {
SSDBG( descP,
- ("SOCKET", "esock_down_acceptor -> no more writers\r\n") );
+ ("SOCKET",
+ "esock_down_acceptor(%T) {%d} -> no more writers\r\n",
+ sockRef, descP->sock) );
+
+ descP->readState &= ~ESOCK_STATE_ACCEPTING;
- descP->state = ESOCK_STATE_LISTENING;
descP->currentAcceptorP = NULL;
}
@@ -21142,9 +21902,11 @@ void esock_down_acceptor(ErlNifEnv* env,
/* Maybe unqueue one of the waiting acceptors */
- SSDBG( descP, ("SOCKET",
- "esock_down_acceptor -> "
- "not current acceptor - maybe a waiting acceptor\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down_acceptor(%T) {%d} -> "
+ "not current acceptor - maybe a waiting acceptor\r\n",
+ sockRef, descP->sock) );
acceptor_unqueue(env, descP, pid);
}
@@ -21159,21 +21921,27 @@ void esock_down_acceptor(ErlNifEnv* env,
*
*/
static
-void esock_down_writer(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- const ErlNifPid* pid)
+void esock_down_writer(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon)
{
- if (COMPARE_PIDS(&descP->currentWriter.pid, pid) == 0) {
+ if (MON_EQ(&descP->currentWriter.mon, mon)) {
+ MON_INIT(&descP->currentWriter.mon);
- SSDBG( descP, ("SOCKET",
- "esock_down_writer -> "
- "current writer - try activate next\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down_writer(%T) {%d} -> "
+ "current writer - try activate next\r\n",
+ sockRef, descP->sock) );
if (!activate_next_writer(env, descP, sockRef)) {
- SSDBG( descP, ("SOCKET",
- "esock_down_writer -> no active writer\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down_writer(%T) {%d} -> no active writer\r\n",
+ sockRef, descP->sock) );
descP->currentWriterP = NULL;
}
@@ -21182,9 +21950,11 @@ void esock_down_writer(ErlNifEnv* env,
/* Maybe unqueue one of the waiting writer(s) */
- SSDBG( descP, ("SOCKET",
- "esock_down_writer -> "
- "not current writer - maybe a waiting writer\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down_writer(%T) {%d} -> "
+ "not current writer - maybe a waiting writer\r\n",
+ sockRef, descP->sock) );
writer_unqueue(env, descP, pid);
}
@@ -21199,22 +21969,27 @@ void esock_down_writer(ErlNifEnv* env,
*
*/
static
-void esock_down_reader(ErlNifEnv* env,
- ESockDescriptor* descP,
- ERL_NIF_TERM sockRef,
- const ErlNifPid* pid)
+void esock_down_reader(ErlNifEnv* env,
+ ESockDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon)
{
- if (COMPARE_PIDS(&descP->currentReader.pid, pid) == 0) {
+ if (MON_EQ(&descP->currentReader.mon, mon)) {
+ MON_INIT(&descP->currentReader.mon);
- SSDBG( descP, ("SOCKET",
- "esock_down_reader -> "
- "current reader - try activate next\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down_reader(%T) {%d} -> "
+ "current reader - try activate next\r\n",
+ sockRef, descP->sock) );
if (!activate_next_reader(env, descP, sockRef)) {
SSDBG( descP,
("SOCKET",
- "esock_down_reader -> no more readers\r\n") );
+ "esock_down_reader(%T) {%d} -> no more readers\r\n",
+ sockRef, descP->sock) );
descP->currentReaderP = NULL;
}
@@ -21223,9 +21998,11 @@ void esock_down_reader(ErlNifEnv* env,
/* Maybe unqueue one of the waiting reader(s) */
- SSDBG( descP, ("SOCKET",
- "esock_down_reader -> "
- "not current reader - maybe a waiting reader\r\n") );
+ SSDBG( descP,
+ ("SOCKET",
+ "esock_down_reader(%T) {%d} -> "
+ "not current reader - maybe a waiting reader\r\n",
+ sockRef, descP->sock) );
reader_unqueue(env, descP, pid);
}
@@ -21245,14 +22022,16 @@ ErlNifFunc esock_funcs[] =
// Some utility and support functions
{"nif_info", 0, nif_info, 0},
{"nif_info", 1, nif_info, 0},
+ {"nif_supports", 0, nif_supports, 0},
{"nif_supports", 1, nif_supports, 0},
+ {"nif_supports", 2, nif_supports, 0},
{"nif_command", 1, nif_command, 0},
// The proper "socket" interface
- // nif_open/1 is (supposed to be) used when we already have a file descriptor
- // {"nif_open", 1, nif_open, 0},
+ {"nif_open", 2, nif_open, 0},
{"nif_open", 4, nif_open, 0},
{"nif_bind", 2, nif_bind, 0},
+ {"nif_connect", 1, nif_connect, 0},
{"nif_connect", 2, nif_connect, 0},
{"nif_listen", 2, nif_listen, 0},
{"nif_accept", 2, nif_accept, 0},
@@ -21272,41 +22051,39 @@ ErlNifFunc esock_funcs[] =
/* Misc utility functions */
/* "Extra" functions to "complete" the socket interface.
- * For instance, the function nif_finalize_connection
- * is called after the connect *select* has "completed".
+ * For instance, the function nif_finalize_close
+ * is called after the close *select* has "completed".
*/
- {"nif_finalize_connection", 1, nif_finalize_connection, 0},
{"nif_cancel", 3, nif_cancel, 0},
{"nif_finalize_close", 1, nif_finalize_close, ERL_NIF_DIRTY_JOB_IO_BOUND}
};
#if !defined(__WIN32__)
-static
-BOOLEAN_T extract_debug(ErlNifEnv* env,
- ERL_NIF_TERM map)
-{
- /*
- * We need to do this here since the "proper" atom has not been
- * created when this function is called.
- */
- ERL_NIF_TERM debug = MKA(env, "debug");
-
- return esock_extract_bool_from_map(env, map, debug, ESOCK_GLOBAL_DEBUG_DEFAULT);
-}
static
-BOOLEAN_T extract_iow(ErlNifEnv* env,
- ERL_NIF_TERM map)
+char* extract_debug_filename(ErlNifEnv* env,
+ ERL_NIF_TERM map)
{
- /*
- * We need to do this here since the "proper" atom has not been
- * created when this function is called.
- */
- ERL_NIF_TERM iow = MKA(env, "iow");
+ /* See the functions above */
+ ERL_NIF_TERM val;
+ ErlNifBinary bin;
+ char *filename;
- return esock_extract_bool_from_map(env, map, iow, ESOCK_NIF_IOW_DEFAULT);
+ if (! GET_MAP_VAL(env, map, atom_debug_filename, &val))
+ return NULL;
+
+ if (! enif_inspect_binary(env, val, &bin))
+ return NULL;
+
+ if ((filename = MALLOC(bin.size + 1)) == NULL)
+ return NULL;
+
+ sys_memcpy(filename, bin.data, bin.size);
+ filename[bin.size] = '\0';
+ return filename;
}
+
#endif // if !defined(__WIN32__)
@@ -21318,15 +22095,58 @@ BOOLEAN_T extract_iow(ErlNifEnv* env,
static
int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
{
-#if !defined(__WIN32__)
- esock_dbg_init(ESOCK_DBGOUT_DEFAULT);
- // esock_dbg_init(ESOCK_DBGOUT_UNIQUE);
+ /* +++ Local atoms and error reason atoms +++ */
+#define LOCAL_ATOM_DECL(A) atom_##A = MKA(env, #A)
+ LOCAL_ATOMS;
+ LOCAL_ERROR_REASON_ATOMS;
+#undef LOCAL_ATOM_DECL
+
+ /* Global atom(s) and error reason atom(s) */
+#define GLOBAL_ATOM_DECL(A) esock_atom_##A = MKA(env, #A)
+ GLOBAL_ATOMS;
+ GLOBAL_ERROR_REASON_ATOMS;
+#undef GLOBAL_ATOM_DECL
- data.dbg = extract_debug(env, load_info);
- data.iow = extract_iow(env, load_info);
+ esock_atom_socket_tag = MKA(env, "$socket");
- esock_extract_pid_from_map(env, load_info,
- MKA(env, "registry"), &data.regPid);
+#if !defined(__WIN32__)
+
+ if (! esock_extract_pid_from_map(env, load_info,
+ atom_registry,
+ &data.regPid))
+ return 1; // Failure - no registry pid
+
+ data.useReg =
+ esock_extract_bool_from_map(env, load_info,
+ atom_use_registry,
+ ESOCK_USE_SOCKET_REGISTRY);
+
+ data.iow =
+ esock_extract_bool_from_map(env, load_info,
+ atom_iow,
+ ESOCK_NIF_IOW_DEFAULT);
+
+ {
+ char *debug_filename;
+
+ debug_filename = extract_debug_filename(env, load_info);
+
+ if (esock_dbg_init(debug_filename)) {
+ // Pick up early debug flags only if debug_filename is ok
+
+ data.dbg =
+ esock_extract_bool_from_map(env, load_info,
+ esock_atom_debug,
+ ESOCK_GLOBAL_DEBUG_DEFAULT);
+ data.sockDbg =
+ esock_extract_bool_from_map(env, load_info,
+ atom_socket_debug,
+ ESOCK_DEBUG_DEFAULT);
+ }
+
+ if (debug_filename != NULL)
+ FREE(debug_filename);
+ }
/* +++ Global Counters +++ */
data.cntMtx = MCREATE("esock.gcnt");
@@ -21341,28 +22161,17 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
data.numProtoTCP = 0;
data.numProtoUDP = 0;
data.numProtoSCTP = 0;
-#endif
-
- /* +++ Local atoms and error reason atoms +++ */
-#define LOCAL_ATOM_DECL(A) atom_##A = MKA(env, #A)
-LOCAL_ATOMS
-LOCAL_ERROR_REASON_ATOMS
-#undef LOCAL_ATOM_DECL
- /* Global atom(s) and error reason atom(s) */
-#define GLOBAL_ATOM_DECL(A) esock_atom_##A = MKA(env, #A)
-GLOBAL_ATOMS
-GLOBAL_ERROR_REASON_ATOMS
-#undef GLOBAL_ATOM_DECL
- esock_atom_socket_tag = MKA(env, "$socket");
+#endif
esocks = enif_open_resource_type_x(env,
"sockets",
&esockInit,
ERL_NIF_RT_CREATE,
NULL);
-
- return !esocks;
+ return esocks != NULL ?
+ 0: // Success
+ 1; // Failure
}
/*
@@ -21373,4 +22182,4 @@ GLOBAL_ERROR_REASON_ATOMS
* NULL: THIS IS NOT USED
* unload: NULL (not used)
*/
-ERL_NIF_INIT(socket, esock_funcs, on_load, NULL, NULL, NULL)
+ERL_NIF_INIT(prim_socket, esock_funcs, on_load, NULL, NULL, NULL)
diff --git a/erts/emulator/nifs/common/socket_dbg.c b/erts/emulator/nifs/common/socket_dbg.c
index fb0a02f3b2..deb531415f 100644
--- a/erts/emulator/nifs/common/socket_dbg.c
+++ b/erts/emulator/nifs/common/socket_dbg.c
@@ -40,21 +40,56 @@
FILE* esock_dbgout = NULL;
extern
-void esock_dbg_init(char* filename)
+BOOLEAN_T esock_dbg_init(char* filename)
{
- if (filename != NULL) {
- if (strcmp(filename, ESOCK_DBGOUT_DEFAULT) == 0) {
+ size_t n;
+ FILE *fp;
+ const char mode[] = "w+";
+
+ if (filename == NULL) {
esock_dbgout = stdout;
- } else if (strcmp(filename, ESOCK_DBGOUT_UNIQUE) == 0) {
- char template[] = "/tmp/esock-dbg-XXXXXX";
- esock_dbgout = fdopen(mkstemp(template), "w+");
+ return TRUE; // Valid default stdout
+ }
+
+ if ((n = strlen(filename)) == 0) {
+ esock_dbgout = stdout;
+ return TRUE; // Valid selection of stdout
+ }
+
+ fp = NULL;
+
+ /* If there is trailing ?????? replace it with XXXXXX
+ * and use mkstemp() to create an unique file name
+ */
+ if (n >= 6) {
+ size_t k;
+
+ for (k = n - 6; k < n; k++)
+ if (filename[k] != '?') break;
+
+ if (k == n) {
+ int fd;
+ /* ?:s up to the end */
+
+ for (k = n - 6; k < n; k++)
+ filename[k] = 'X';
+
+ if ((fd = mkstemp(filename)) >= 0)
+ fp = fdopen(fd, mode);
+ } else {
+ fp = fopen(filename, mode);
+ }
} else {
- esock_dbgout = fopen(filename, "w+");
+ fp = fopen(filename, mode);
}
- } else {
- char template[] = "/tmp/esock-dbg-XXXXXX";
- esock_dbgout = fdopen(mkstemp(template), "w+");
- }
+
+ if (fp != NULL) {
+ esock_dbgout = fp;
+ return TRUE; // Succesful file open
+ }
+
+ esock_dbgout = stdout;
+ return FALSE; // Selected file did not work
}
@@ -67,8 +102,8 @@ extern
void esock_dbg_printf( const char* prefix, const char* format, ... )
{
va_list args;
- char f[512 + strlen(format)]; // This has to suffice...
- char stamp[50];
+ char f[512]; // This has to suffice...
+ char stamp[64];
int res;
/*
@@ -85,13 +120,12 @@ void esock_dbg_printf( const char* prefix, const char* format, ... )
prefix, TSNAME(), format);
}
- if (res > 0) {
+ if (res < sizeof(f)) {
va_start (args, format);
- enif_vfprintf (esock_dbgout, f, args);
+ enif_vfprintf(esock_dbgout, f, args);
va_end (args);
+
fflush(esock_dbgout);
}
-
- return;
}
diff --git a/erts/emulator/nifs/common/socket_dbg.h b/erts/emulator/nifs/common/socket_dbg.h
index a4012d69dc..295e6fa1f3 100644
--- a/erts/emulator/nifs/common/socket_dbg.h
+++ b/erts/emulator/nifs/common/socket_dbg.h
@@ -27,10 +27,7 @@
#ifndef SOCKET_DBG_H__
#define SOCKET_DBG_H__
-/* Used when calling the init function */
-#define ESOCK_DBGOUT_DEFAULT "stdout"
-#define ESOCK_DBGOUT_UNIQUE "unique"
-
+#include "socket_int.h"
/* Used in debug printouts */
#ifdef __WIN32__
@@ -43,13 +40,14 @@ typedef unsigned long long llu_t;
extern FILE* esock_dbgout; // Initiated by the 'init' function
#define ESOCK_DBG_PRINTF( ___COND___ , proto ) \
- if ( ___COND___ ) { \
- esock_dbg_printf proto; \
- fflush(esock_dbgout); \
- }
+ do \
+ if ( ___COND___ ) { \
+ esock_dbg_printf proto; \
+ } \
+ while (0)
-extern void esock_dbg_init(char* filename);
+extern BOOLEAN_T esock_dbg_init(char* filename);
extern void esock_dbg_printf( const char* prefix, const char* format, ... );
#endif // SOCKET_DBG_H__
diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h
index 4580518572..c224f82c47 100644
--- a/erts/emulator/nifs/common/socket_int.h
+++ b/erts/emulator/nifs/common/socket_int.h
@@ -91,6 +91,9 @@ typedef union {
struct sockaddr_ll ll;
#endif
+ /* Max size sockaddr on system */
+ struct sockaddr_storage ss;
+
} ESockAddress;
@@ -113,7 +116,7 @@ typedef unsigned int BOOLEAN_T;
* "Global" atoms (esock_atom_...)
*
* Note that when an (global) atom is added here, it must also be added
- * in the socket_nif.c file!
+ * in the prim_socket_nif.c file!
*/
#define GLOBAL_ATOM_DEFS \
@@ -393,6 +396,7 @@ GLOBAL_ERROR_REASON_ATOM_DEFS
#define DEMONP(S,E,D,M) esock_demonitor((S), (E), (D), (M))
#define MON_INIT(M) esock_monitor_init((M))
#define MON2T(E, M) enif_make_monitor_term((E), (M))
+#define MON_EQ(M1, M2) esock_monitor_eq((M1), (M2))
#define COMPARE(A, B) enif_compare((A), (B))
#define COMPARE_PIDS(P1, P2) enif_compare_pids((P1), (P2))
diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c
index 93152bd36e..7c9b38ddec 100644
--- a/erts/emulator/nifs/common/socket_util.c
+++ b/erts/emulator/nifs/common/socket_util.c
@@ -111,6 +111,119 @@ static BOOLEAN_T esock_extract_from_map(ErlNifEnv* env,
ERL_NIF_TERM key,
ERL_NIF_TERM* val);
+
+
+/* *** esock_get_bool_from_map ***
+ *
+ * Simple utility function used to extract a boolean value from a map.
+ * If it fails to extract the value (for whatever reason) the default
+ * value is returned.
+ */
+
+extern
+BOOLEAN_T esock_get_bool_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ BOOLEAN_T def)
+{
+ ERL_NIF_TERM val;
+
+ if (!GET_MAP_VAL(env, map, key, &val)) {
+ return def;
+ } else {
+ if (COMPARE(val, esock_atom_true) == 0)
+ return TRUE;
+ else
+ return FALSE;
+ }
+}
+
+
+/* *** esock_get_bool_from_map ***
+ *
+ * Simple utility function used to extract a integer value from a map.
+ */
+
+extern
+BOOLEAN_T esock_get_int_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ int* val)
+{
+ ERL_NIF_TERM eval;
+
+ if (!GET_MAP_VAL(env, map, key, &eval)) {
+ *val = -1;
+ return FALSE;
+ }
+
+ if (!IS_NUM(env, eval)) {
+ *val = -2;
+ return FALSE;
+ }
+
+ if (!GET_INT(env, eval, val)) {
+ *val = -3;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+
+/* *** esock_get_string_from_map ***
+ *
+ * Simple utility function used to extract a (latin1) string value from a map.
+ * This function will allocate a buffer to put the string!
+ */
+
+extern
+BOOLEAN_T esock_get_string_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ char** str)
+{
+ ERL_NIF_TERM value;
+ unsigned int len;
+ char* buf;
+ int written;
+
+ /* The currently only supported extra option is: netns */
+ if (!GET_MAP_VAL(env, map, key, &value)) {
+ *str = NULL;
+ return FALSE;
+ }
+
+ /* So far so good. The value should be a string, check. */
+ if (!enif_is_list(env, value)) {
+ *str = NULL;
+ return FALSE;
+ }
+
+ if (!enif_get_list_length(env, value, &len)) {
+ *str = NULL;
+ return FALSE;
+ }
+
+ if ((buf = MALLOC(len+1)) == NULL) {
+ *str = NULL;
+ return FALSE;
+ }
+
+ written = enif_get_string(env, value, buf, len+1, ERL_NIF_LATIN1);
+ if (written == (len+1)) {
+ *str = buf;
+ return TRUE;
+ } else {
+ *str = NULL;
+ return FALSE;
+ }
+}
+
+
+
+
/* +++ esock_encode_iov +++
*
* Encode an IO Vector. In erlang we represented this as a list of binaries.
@@ -327,9 +440,9 @@ char* esock_encode_sockaddr(ErlNifEnv* env,
UDBG( ("SUTIL", "esock_encode_sockaddr -> entry with"
"\r\n family: %d"
"\r\n addrLen: %d"
- "\r\n", sockAddrP->sa.sa_family, addrLen) );
+ "\r\n", sockAddrP->ss.ss_family, addrLen) );
- switch (sockAddrP->sa.sa_family) {
+ switch (sockAddrP->ss.ss_family) {
case AF_INET:
xres = esock_encode_sockaddr_in4(env, &sockAddrP->in4, addrLen, eSockAddr);
break;
@@ -1240,7 +1353,7 @@ char* esock_decode_type(ErlNifEnv* env,
} else if (COMPARE(esock_atom_raw, eType) == 0) {
*type = SOCK_RAW;
-#if defined(HAVE_SCTP)
+#ifdef SOCK_SEQPACKET
} else if (COMPARE(esock_atom_seqpacket, eType) == 0) {
*type = SOCK_SEQPACKET;
#endif
@@ -1285,7 +1398,7 @@ char* esock_encode_type(ErlNifEnv* env,
*eType = esock_atom_raw;
break;
-#if defined(HAVE_SCTP)
+#ifdef SOCK_SEQPACKET
case SOCK_SEQPACKET:
*eType = esock_atom_seqpacket;
break;
@@ -1487,6 +1600,7 @@ void esock_encode_packet_pkttype(ErlNifEnv* env,
break;
#endif
+ /* Unused? Not user space? */
#if defined(PACKET_LOOPBACK)
case PACKET_LOOPBACK:
*ePktType = esock_atom_loopback;
@@ -1505,11 +1619,17 @@ void esock_encode_packet_pkttype(ErlNifEnv* env,
break;
#endif
+ /* Unused? Not user space?
+ * Also, has the same value as PACKET_USER,
+ * so may result in a compiler error (at least
+ * on some platforms: ANDROID).
+ *
#if defined(PACKET_FASTROUTE)
case PACKET_FASTROUTE:
*ePktType = esock_atom_fastroute;
break;
#endif
+ */
default:
*ePktType = MKUI(env, pkttype);
@@ -1656,8 +1776,10 @@ BOOLEAN_T esock_extract_bool_from_map(ErlNifEnv* env,
if (COMPARE(val, esock_atom_true) == 0)
return TRUE;
- else
+ else if (COMPARE(val, esock_atom_false) == 0)
return FALSE;
+ else
+ return def;
}
@@ -1967,12 +2089,11 @@ BOOLEAN_T esock_timestamp_str(char *buf, unsigned int len)
extern
BOOLEAN_T esock_format_timestamp(ErlNifTime timestamp, char *buf, unsigned int len)
{
- int ret;
+ unsigned ret;
#if defined(ESOCK_USE_PRETTY_TIMESTAMP)
time_t sec = timestamp / 1000000; // (if _MSEC) sec = time / 1000;
time_t usec = timestamp % 1000000; // (if _MSEC) msec = time % 1000;
- int buflen;
struct tm t;
if (localtime_r(&sec, &t) == NULL)
@@ -1981,10 +2102,10 @@ BOOLEAN_T esock_format_timestamp(ErlNifTime timestamp, char *buf, unsigned int l
ret = strftime(buf, len, "%d-%b-%Y::%T", &t);
if (ret == 0)
return FALSE;
- len -= ret - 1;
- buflen = strlen(buf);
+ len -= ret;
+ buf += ret;
- ret = enif_snprintf(&buf[buflen], len, ".%06b64d", usec);
+ ret = enif_snprintf(buf, len, ".%06lu", (unsigned long) usec);
if (ret >= len)
return FALSE;
@@ -1992,11 +2113,11 @@ BOOLEAN_T esock_format_timestamp(ErlNifTime timestamp, char *buf, unsigned int l
#else
- ret = enif_snprintf(buf, len, "%b64d", timestamp);
- if (ret == 0)
+ ret = enif_snprintf(buf, len, "%lu", (unsigned long) timestamp);
+ if (ret >= len)
return FALSE;
- else
- return TRUE;
+
+ return TRUE;
#endif
}
diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h
index 398fb40a5a..0a1eb92339 100644
--- a/erts/emulator/nifs/common/socket_util.h
+++ b/erts/emulator/nifs/common/socket_util.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2018-2019. All Rights Reserved.
+ * Copyright Ericsson AB 2018-2020. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
* %CopyrightEnd%
*
* ----------------------------------------------------------------------
- * Purpose : Utility "stuff" for socket and net.
+ * Purpose : Utility "stuff" for socket and prim_net (nifs).
* ----------------------------------------------------------------------
*
*/
@@ -43,6 +43,22 @@
#define ESOCK_ASSERT(e) ((void) ((e) ? 1 : (ESOCK_ABORT(#e), 0)))
extern
+BOOLEAN_T esock_get_bool_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ BOOLEAN_T def);
+extern
+BOOLEAN_T esock_get_int_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ int* val);
+extern
+BOOLEAN_T esock_get_string_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ char** str);
+
+extern
char* esock_encode_iov(ErlNifEnv* env,
int read,
struct iovec* iov,
diff --git a/erts/emulator/nifs/unix/unix_prim_file.c b/erts/emulator/nifs/unix/unix_prim_file.c
index a697aa4872..d110ead1e5 100644
--- a/erts/emulator/nifs/unix/unix_prim_file.c
+++ b/erts/emulator/nifs/unix/unix_prim_file.c
@@ -107,7 +107,7 @@ ERL_NIF_TERM efile_get_handle(ErlNifEnv *env, efile_data_t *d) {
return result;
}
-static int open_file_type_check(const efile_path_t *path, int fd) {
+static int open_file_is_dir(const efile_path_t *path, int fd) {
struct stat file_info;
int error;
@@ -119,27 +119,14 @@ static int open_file_type_check(const efile_path_t *path, int fd) {
(void)path;
#endif
- if(error < 0) {
- /* If we failed to stat assume success and let the next call handle the
- * error. The old driver checked whether the file was to be used
- * immediately in a read within the call, but the new implementation
- * never does that. */
- return 1;
- }
-
- /* Allow everything that isn't a directory, and error out on the next call
- * if it's unsupported. */
- if(S_ISDIR(file_info.st_mode)) {
- return 0;
- }
-
- return 1;
+ /* Assume not a directory on error. */
+ return error == 0 && S_ISDIR(file_info.st_mode);
}
posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes,
ErlNifResourceType *nif_type, efile_data_t **d) {
- int flags, fd;
+ int mode, flags, fd;
flags = 0;
@@ -174,18 +161,38 @@ posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes,
#endif
}
+ if(modes & EFILE_MODE_DIRECTORY) {
+ mode = DIR_MODE;
+#ifdef O_DIRECTORY
+ flags |= O_DIRECTORY;
+#endif
+ } else {
+ mode = FILE_MODE;
+ }
+
do {
- fd = open((const char*)path->data, flags, FILE_MODE);
+ fd = open((const char*)path->data, flags, mode);
} while(fd == -1 && errno == EINTR);
if(fd != -1) {
efile_unix_t *u;
- if(!(modes & EFILE_MODE_SKIP_TYPE_CHECK) && !open_file_type_check(path, fd)) {
+#ifndef O_DIRECTORY
+ /* On platforms without O_DIRECTORY support, ensure that using the
+ * directory flag to open a file fails. */
+ if(!(modes & EFILE_MODE_SKIP_TYPE_CHECK) &&
+ (modes & EFILE_MODE_DIRECTORY) && !open_file_is_dir(path, fd)) {
close(fd);
+ return ENOTDIR;
+ }
+#endif
- /* This is blatantly incorrect, but we're documented as returning
- * this for everything that isn't a file. */
+ /* open() works on directories without the O_DIRECTORY flag but for
+ * consistency across platforms we require that the user has requested
+ * directory mode. */
+ if(!(modes & EFILE_MODE_SKIP_TYPE_CHECK) &&
+ !(modes & EFILE_MODE_DIRECTORY) && open_file_is_dir(path, fd)) {
+ close(fd);
return EISDIR;
}
@@ -555,7 +562,7 @@ int efile_allocate(efile_data_t *d, Sint64 offset, Sint64 length) {
#if defined(HAVE_FALLOCATE)
/* Linux-specific */
do {
- ret = fallocate(u->fd, FALLOC_FL_KEEP_SIZE, offset, length);
+ ret = fallocate(u->fd, 0, offset, length);
} while(ret < 0 && errno == EINTR);
#elif defined(F_PREALLOCATE)
/* Mac-specific */
@@ -656,6 +663,33 @@ int efile_truncate(efile_data_t *d) {
return 1;
}
+static void build_file_info(struct stat *data, efile_fileinfo_t *result) {
+ if(S_ISCHR(data->st_mode) || S_ISBLK(data->st_mode)) {
+ result->type = EFILE_FILETYPE_DEVICE;
+ } else if(S_ISDIR(data->st_mode)) {
+ result->type = EFILE_FILETYPE_DIRECTORY;
+ } else if(S_ISREG(data->st_mode)) {
+ result->type = EFILE_FILETYPE_REGULAR;
+ } else if(S_ISLNK(data->st_mode)) {
+ result->type = EFILE_FILETYPE_SYMLINK;
+ } else {
+ result->type = EFILE_FILETYPE_OTHER;
+ }
+
+ result->a_time = (Sint64)data->st_atime;
+ result->m_time = (Sint64)data->st_mtime;
+ result->c_time = (Sint64)data->st_ctime;
+ result->size = data->st_size;
+
+ result->major_device = data->st_dev;
+ result->minor_device = data->st_rdev;
+ result->links = data->st_nlink;
+ result->inode = data->st_ino;
+ result->mode = data->st_mode;
+ result->uid = data->st_uid;
+ result->gid = data->st_gid;
+}
+
posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_fileinfo_t *result) {
struct stat data;
@@ -669,30 +703,7 @@ posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_
}
}
- if(S_ISCHR(data.st_mode) || S_ISBLK(data.st_mode)) {
- result->type = EFILE_FILETYPE_DEVICE;
- } else if(S_ISDIR(data.st_mode)) {
- result->type = EFILE_FILETYPE_DIRECTORY;
- } else if(S_ISREG(data.st_mode)) {
- result->type = EFILE_FILETYPE_REGULAR;
- } else if(S_ISLNK(data.st_mode)) {
- result->type = EFILE_FILETYPE_SYMLINK;
- } else {
- result->type = EFILE_FILETYPE_OTHER;
- }
-
- result->a_time = (Sint64)data.st_atime;
- result->m_time = (Sint64)data.st_mtime;
- result->c_time = (Sint64)data.st_ctime;
- result->size = data.st_size;
-
- result->major_device = data.st_dev;
- result->minor_device = data.st_rdev;
- result->links = data.st_nlink;
- result->inode = data.st_ino;
- result->mode = data.st_mode;
- result->uid = data.st_uid;
- result->gid = data.st_gid;
+ build_file_info(&data, result);
#ifndef NO_ACCESS
result->access = EFILE_ACCESS_NONE;
@@ -711,6 +722,56 @@ posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_
return 0;
}
+static int check_access(struct stat *st) {
+ int ret = EFILE_ACCESS_NONE;
+
+ if(st->st_uid == geteuid()) {
+ if(st->st_mode & S_IRUSR) {
+ ret |= EFILE_ACCESS_READ;
+ }
+ if(st->st_mode & S_IWUSR) {
+ ret |= EFILE_ACCESS_WRITE;
+ }
+ return ret;
+ }
+
+ if(st->st_gid == getegid()) {
+ if(st->st_mode & S_IRGRP) {
+ ret |= EFILE_ACCESS_READ;
+ }
+ if(st->st_mode & S_IWGRP) {
+ ret |= EFILE_ACCESS_WRITE;
+ }
+ return ret;
+ }
+
+ if(st->st_mode & S_IROTH) {
+ ret |= EFILE_ACCESS_READ;
+ }
+ if(st->st_mode & S_IWOTH) {
+ ret |= EFILE_ACCESS_WRITE;
+ }
+ return ret;
+}
+
+posix_errno_t efile_read_handle_info(efile_data_t *d, efile_fileinfo_t *result) {
+ struct stat data;
+ efile_unix_t *u = (efile_unix_t*)d;
+
+#ifdef HAVE_FSTAT
+ if(fstat(u->fd, &data) < 0) {
+ return errno;
+ }
+
+ build_file_info(&data, result);
+ result->access = check_access(&data);
+
+ return 0;
+#else
+ return ENOTSUP;
+#endif
+}
+
posix_errno_t efile_set_permissions(const efile_path_t *path, Uint32 permissions) {
const mode_t MUTABLE_MODES = (S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO);
mode_t new_modes = permissions & MUTABLE_MODES;
diff --git a/erts/emulator/nifs/win32/win_prim_file.c b/erts/emulator/nifs/win32/win_prim_file.c
index b465d36ac2..a6c08c4162 100644
--- a/erts/emulator/nifs/win32/win_prim_file.c
+++ b/erts/emulator/nifs/win32/win_prim_file.c
@@ -269,7 +269,18 @@ static int normalize_path_result(ErlNifBinary *path) {
return enif_realloc_binary(path, length * sizeof(WCHAR));
}
-/* @brief Checks whether all the given attributes are set on the object at the
+/** @brief Checks whether all the given attributes are set on the object at the
+ * given handle. Note that it assumes false on errors. */
+static int handle_has_file_attributes(HANDLE handle, DWORD mask) {
+ BY_HANDLE_FILE_INFORMATION native_file_info;
+ if(!GetFileInformationByHandle(handle, &native_file_info)) {
+ return 0;
+ }
+
+ return !!((native_file_info.dwFileAttributes & mask) == mask);
+}
+
+/** @brief Checks whether all the given attributes are set on the object at the
* given path. Note that it assumes false on errors. */
static int has_file_attributes(const efile_path_t *path, DWORD mask) {
DWORD attributes = GetFileAttributesW((WCHAR*)path->data);
@@ -313,7 +324,7 @@ static int get_drive_number(const efile_path_t *path) {
return -1;
}
-/* @brief Checks whether two *paths* are on the same mount point; they don't
+/** @brief Checks whether two *paths* are on the same mount point; they don't
* have to refer to existing or accessible files/directories. */
static int has_same_mount_point(const efile_path_t *path_a, const efile_path_t *path_b) {
WCHAR *mount_a, *mount_b;
@@ -412,10 +423,15 @@ posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes,
ASSERT_PATH_FORMAT(path);
+ attributes = 0;
access_flags = 0;
open_mode = 0;
- if(modes & EFILE_MODE_READ && !(modes & EFILE_MODE_WRITE)) {
+ if(modes & EFILE_MODE_DIRECTORY) {
+ attributes = FILE_FLAG_BACKUP_SEMANTICS;
+ access_flags = GENERIC_READ;
+ open_mode = OPEN_EXISTING;
+ } else if(modes & EFILE_MODE_READ && !(modes & EFILE_MODE_WRITE)) {
access_flags = GENERIC_READ;
open_mode = OPEN_EXISTING;
} else if(modes & EFILE_MODE_WRITE && !(modes & EFILE_MODE_READ)) {
@@ -438,9 +454,9 @@ posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes,
}
if(modes & EFILE_MODE_SYNC) {
- attributes = FILE_FLAG_WRITE_THROUGH;
+ attributes |= FILE_FLAG_WRITE_THROUGH;
} else {
- attributes = FILE_ATTRIBUTE_NORMAL;
+ attributes |= FILE_ATTRIBUTE_NORMAL;
}
handle = CreateFileW((WCHAR*)path->data, access_flags,
@@ -449,6 +465,12 @@ posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes,
if(handle != INVALID_HANDLE_VALUE) {
efile_win_t *w;
+ /* Directory mode specified, but path is not a directory. */
+ if((modes & EFILE_MODE_DIRECTORY) && !handle_has_file_attributes(handle, FILE_ATTRIBUTE_DIRECTORY)) {
+ CloseHandle(handle);
+ return ENOTDIR;
+ }
+
w = (efile_win_t*)enif_alloc_resource(nif_type, sizeof(efile_win_t));
w->handle = handle;
@@ -461,7 +483,7 @@ posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes,
/* Rewrite all failures on directories to EISDIR to match the old
* driver. */
- if(has_file_attributes(path, FILE_ATTRIBUTE_DIRECTORY)) {
+ if(!(modes & EFILE_MODE_DIRECTORY) && has_file_attributes(path, FILE_ATTRIBUTE_DIRECTORY)) {
return EISDIR;
}
@@ -751,6 +773,71 @@ static int is_name_surrogate(const efile_path_t *path) {
return result;
}
+static void build_file_info(BY_HANDLE_FILE_INFORMATION *native_file_info, const efile_path_t *path, int is_link, efile_fileinfo_t *result) {
+ DWORD attributes;
+
+ attributes = native_file_info->dwFileAttributes;
+
+ if(is_link) {
+ result->type = EFILE_FILETYPE_SYMLINK;
+ /* This should be _S_IFLNK, but the old driver always set
+ * non-directories to _S_IFREG. */
+ result->mode |= _S_IFREG;
+ } else if(attributes & FILE_ATTRIBUTE_DIRECTORY) {
+ result->type = EFILE_FILETYPE_DIRECTORY;
+ result->mode |= _S_IFDIR | _S_IEXEC;
+ } else {
+ if(is_executable_file(path)) {
+ result->mode |= _S_IEXEC;
+ }
+
+ result->type = EFILE_FILETYPE_REGULAR;
+ result->mode |= _S_IFREG;
+ }
+
+ if(!(attributes & FILE_ATTRIBUTE_READONLY)) {
+ result->access = EFILE_ACCESS_READ | EFILE_ACCESS_WRITE;
+ result->mode |= _S_IREAD | _S_IWRITE;
+ } else {
+ result->access = EFILE_ACCESS_READ;
+ result->mode |= _S_IREAD;
+ }
+
+ /* Propagate user mode-bits to group/other fields */
+ result->mode |= (result->mode & 0700) >> 3;
+ result->mode |= (result->mode & 0700) >> 6;
+
+ result->size =
+ ((Uint64)native_file_info->nFileSizeHigh << 32ull) |
+ (Uint64)native_file_info->nFileSizeLow;
+ result->links = MAX(1, native_file_info->nNumberOfLinks);
+
+ result->major_device = get_drive_number(path);
+ result->minor_device = 0;
+ result->inode = 0;
+ result->uid = 0;
+ result->gid = 0;
+}
+
+static void build_file_info_times(BY_HANDLE_FILE_INFORMATION *native_file_info, efile_fileinfo_t *result) {
+ FILETIME_TO_EPOCH(result->m_time, native_file_info->ftLastWriteTime);
+ FILETIME_TO_EPOCH(result->a_time, native_file_info->ftLastAccessTime);
+ FILETIME_TO_EPOCH(result->c_time, native_file_info->ftCreationTime);
+
+ if(result->m_time == -EPOCH_DIFFERENCE) {
+ /* Default to 1970 just like the old driver. */
+ result->m_time = 0;
+ }
+
+ if(result->a_time == -EPOCH_DIFFERENCE) {
+ result->a_time = result->m_time;
+ }
+
+ if(result->c_time == -EPOCH_DIFFERENCE) {
+ result->c_time = result->m_time;
+ }
+}
+
posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_fileinfo_t *result) {
BY_HANDLE_FILE_INFORMATION native_file_info;
DWORD attributes;
@@ -770,23 +857,41 @@ posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_
return windows_to_posix_errno(last_error);
}
- attributes = FILE_ATTRIBUTE_DIRECTORY;
+ native_file_info.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
} else if(is_path_root(path)) {
/* Local (or mounted) roots can be queried with GetFileAttributesW but
* lack support for GetFileInformationByHandle, so we'll skip that
* part. */
+ native_file_info.dwFileAttributes = attributes;
} else {
HANDLE handle;
+ DWORD last_error;
+ DWORD flags;
if(attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
is_link = is_name_surrogate(path);
}
+ flags = FILE_FLAG_BACKUP_SEMANTICS;
+ if(!follow_links && is_link) {
+ flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+ }
+
+ handle = CreateFileW((const WCHAR*)path->data, GENERIC_READ,
+ FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, flags, NULL);
+ last_error = GetLastError();
+
+ if(handle == INVALID_HANDLE_VALUE) {
+ return windows_to_posix_errno(last_error);
+ }
+
if(follow_links && is_link) {
posix_errno_t posix_errno;
efile_path_t resolved_path;
- posix_errno = internal_read_link(path, &resolved_path);
+ posix_errno = internal_read_link(handle, &resolved_path);
+
+ CloseHandle(handle);
if(posix_errno != 0) {
return posix_errno;
@@ -795,74 +900,43 @@ posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_
return efile_read_info(&resolved_path, 0, result);
}
- handle = CreateFileW((const WCHAR*)path->data, GENERIC_READ,
- FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
- NULL);
-
- /* The old driver never cared whether this succeeded. */
- if(handle != INVALID_HANDLE_VALUE) {
- GetFileInformationByHandle(handle, &native_file_info);
- CloseHandle(handle);
- }
+ GetFileInformationByHandle(handle, &native_file_info);
+ CloseHandle(handle);
- FILETIME_TO_EPOCH(result->m_time, native_file_info.ftLastWriteTime);
- FILETIME_TO_EPOCH(result->a_time, native_file_info.ftLastAccessTime);
- FILETIME_TO_EPOCH(result->c_time, native_file_info.ftCreationTime);
+ build_file_info_times(&native_file_info, result);
+ }
- if(result->m_time == -EPOCH_DIFFERENCE) {
- /* Default to 1970 just like the old driver. */
- result->m_time = 0;
- }
+ build_file_info(&native_file_info, path, is_link, result);
- if(result->a_time == -EPOCH_DIFFERENCE) {
- result->a_time = result->m_time;
- }
+ return 0;
+}
- if(result->c_time == -EPOCH_DIFFERENCE) {
- result->c_time = result->m_time;
- }
- }
+posix_errno_t efile_read_handle_info(efile_data_t *d, efile_fileinfo_t *result) {
+ efile_win_t *w = (efile_win_t*)d;
+ HANDLE handle;
+ BY_HANDLE_FILE_INFORMATION native_file_info;
+ posix_errno_t posix_errno;
+ efile_path_t path;
+ int length;
- if(is_link) {
- result->type = EFILE_FILETYPE_SYMLINK;
- /* This should be _S_IFLNK, but the old driver always set
- * non-directories to _S_IFREG. */
- result->mode |= _S_IFREG;
- } else if(attributes & FILE_ATTRIBUTE_DIRECTORY) {
- result->type = EFILE_FILETYPE_DIRECTORY;
- result->mode |= _S_IFDIR | _S_IEXEC;
- } else {
- if(is_executable_file(path)) {
- result->mode |= _S_IEXEC;
- }
+ sys_memset(&native_file_info, 0, sizeof(native_file_info));
- result->type = EFILE_FILETYPE_REGULAR;
- result->mode |= _S_IFREG;
+ posix_errno = internal_read_link(w->handle, &path);
+ if(posix_errno != 0) {
+ return posix_errno;
}
- if(!(attributes & FILE_ATTRIBUTE_READONLY)) {
- result->access = EFILE_ACCESS_READ | EFILE_ACCESS_WRITE;
- result->mode |= _S_IREAD | _S_IWRITE;
+ if(GetFileInformationByHandle(w->handle, &native_file_info)) {
+ build_file_info_times(&native_file_info, result);
+ } else if(is_path_root(&path)) {
+ /* GetFileInformationByHandle is not supported on path roots, so
+ * fall back to efile_read_info. */
+ return efile_read_info(&path, 0, result);
} else {
- result->access = EFILE_ACCESS_READ;
- result->mode |= _S_IREAD;
+ return windows_to_posix_errno(GetLastError());
}
- /* Propagate user mode-bits to group/other fields */
- result->mode |= (result->mode & 0700) >> 3;
- result->mode |= (result->mode & 0700) >> 6;
-
- result->size =
- ((Uint64)native_file_info.nFileSizeHigh << 32ull) |
- (Uint64)native_file_info.nFileSizeLow;
-
- result->links = MAX(1, native_file_info.nNumberOfLinks);
-
- result->major_device = get_drive_number(path);
- result->minor_device = 0;
- result->inode = 0;
- result->uid = 0;
- result->gid = 0;
+ build_file_info(&native_file_info, &path, 0, result);
return 0;
}
@@ -941,31 +1015,20 @@ posix_errno_t efile_set_time(const efile_path_t *path, Sint64 a_time, Sint64 m_t
return windows_to_posix_errno(last_error);
}
-static posix_errno_t internal_read_link(const efile_path_t *path, efile_path_t *result) {
+static posix_errno_t internal_read_link(HANDLE link_handle, efile_path_t *result) {
DWORD required_length, actual_length;
- HANDLE link_handle;
DWORD last_error;
- link_handle = CreateFileW((WCHAR*)path->data, GENERIC_READ,
- FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
- last_error = GetLastError();
-
- if(link_handle == INVALID_HANDLE_VALUE) {
- return windows_to_posix_errno(last_error);
- }
-
required_length = GetFinalPathNameByHandleW(link_handle, NULL, 0, 0);
last_error = GetLastError();
if(required_length <= 0) {
- CloseHandle(link_handle);
return windows_to_posix_errno(last_error);
}
/* Unlike many other path functions (eg. GetFullPathNameW), this one
* includes the NUL terminator in its required length. */
if(!enif_alloc_binary(required_length * sizeof(WCHAR), result)) {
- CloseHandle(link_handle);
return ENOMEM;
}
@@ -973,8 +1036,6 @@ static posix_errno_t internal_read_link(const efile_path_t *path, efile_path_t *
(WCHAR*)result->data, required_length, 0);
last_error = GetLastError();
- CloseHandle(link_handle);
-
if(actual_length == 0 || actual_length >= required_length) {
enif_release_binary(result);
return windows_to_posix_errno(last_error);
@@ -992,6 +1053,8 @@ posix_errno_t efile_read_link(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_
posix_errno_t posix_errno;
ErlNifBinary result_bin;
DWORD attributes;
+ HANDLE handle;
+ DWORD last_error;
ASSERT_PATH_FORMAT(path);
@@ -1007,7 +1070,17 @@ posix_errno_t efile_read_link(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_
return EINVAL;
}
- posix_errno = internal_read_link(path, &result_bin);
+ handle = CreateFileW((WCHAR*)path->data, GENERIC_READ,
+ FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ last_error = GetLastError();
+
+ if(handle == INVALID_HANDLE_VALUE) {
+ return windows_to_posix_errno(last_error);
+ }
+ posix_errno = internal_read_link(handle, &result_bin);
+
+ CloseHandle(handle);
if(posix_errno == 0) {
if(!normalize_path_result(&result_bin)) {
diff --git a/erts/emulator/pcre/LICENCE b/erts/emulator/pcre/LICENCE
index 760a6666b6..57a544814c 100644
--- a/erts/emulator/pcre/LICENCE
+++ b/erts/emulator/pcre/LICENCE
@@ -25,7 +25,7 @@ Email domain: cam.ac.uk
University of Cambridge Computing Service,
Cambridge, England.
-Copyright (c) 1997-2019 University of Cambridge
+Copyright (c) 1997-2020 University of Cambridge
All rights reserved.
@@ -36,7 +36,7 @@ Written by: Zoltan Herczeg
Email local part: hzmester
Email domain: freemail.hu
-Copyright(c) 2010-2019 Zoltan Herczeg
+Copyright(c) 2010-2020 Zoltan Herczeg
All rights reserved.
@@ -47,7 +47,7 @@ Written by: Zoltan Herczeg
Email local part: hzmester
Email domain: freemail.hu
-Copyright(c) 2009-2019 Zoltan Herczeg
+Copyright(c) 2009-2020 Zoltan Herczeg
All rights reserved.
diff --git a/erts/emulator/pcre/README.pcre_update.md b/erts/emulator/pcre/README.pcre_update.md
index 5df1e15bde..7703db614d 100644
--- a/erts/emulator/pcre/README.pcre_update.md
+++ b/erts/emulator/pcre/README.pcre_update.md
@@ -244,7 +244,10 @@ To begin with you will need a default table for Latin-1 characters, so:
Compare it to the pcre\_latin\_1\_table.c in the old version, they
should not differ in any significant way. If they do, it might be
-that you do not have the sv_SE locale installed on your machine.
+that you do not have the `sv_SE` locale installed on your machine.
+
+You can test whether it's installed with `locale -a | grep sv_SE$`, and
+install with `sudo locale-gen sv_SE && sudo update-locale` if needed.
A good starting point is then to try to find all files in the new
version of the library that have (probably) the same names as the
diff --git a/erts/emulator/pcre/local_config.h b/erts/emulator/pcre/local_config.h
index c3b4dab586..178c4d4281 100644
--- a/erts/emulator/pcre/local_config.h
+++ b/erts/emulator/pcre/local_config.h
@@ -86,4 +86,4 @@
#define SUPPORT_UTF
/* Version number of package */
-#define VERSION "8.42"
+#define VERSION "8.44"
diff --git a/erts/emulator/pcre/pcre-8.43.tar.bz2 b/erts/emulator/pcre/pcre-8.43.tar.bz2
deleted file mode 100644
index e20c601f71..0000000000
--- a/erts/emulator/pcre/pcre-8.43.tar.bz2
+++ /dev/null
Binary files differ
diff --git a/erts/emulator/pcre/pcre-8.44.tar.bz2 b/erts/emulator/pcre/pcre-8.44.tar.bz2
new file mode 100644
index 0000000000..dc978b77a7
--- /dev/null
+++ b/erts/emulator/pcre/pcre-8.44.tar.bz2
Binary files differ
diff --git a/erts/emulator/pcre/pcre.h b/erts/emulator/pcre/pcre.h
index 49c9fc6dc8..c33c93b720 100644
--- a/erts/emulator/pcre/pcre.h
+++ b/erts/emulator/pcre/pcre.h
@@ -43,9 +43,9 @@ POSSIBILITY OF SUCH DAMAGE.
/* The current PCRE version information. */
#define PCRE_MAJOR 8
-#define PCRE_MINOR 43
+#define PCRE_MINOR 44
#define PCRE_PRERELEASE
-#define PCRE_DATE 2019-02-23
+#define PCRE_DATE 2020-02-12
/* When an application links to a PCRE DLL in Windows, the symbols that are
imported have to be identified as such. When building PCRE, the appropriate
diff --git a/erts/emulator/pcre/pcre_compile.c b/erts/emulator/pcre/pcre_compile.c
index 6ac222b27e..80b966869a 100644
--- a/erts/emulator/pcre/pcre_compile.c
+++ b/erts/emulator/pcre/pcre_compile.c
@@ -6,7 +6,7 @@
and semantics are as close as possible to those of the Perl 5 language.
Written by Philip Hazel
- Copyright (c) 1997-2018 University of Cambridge
+ Copyright (c) 1997-2020 University of Cambridge
-----------------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
@@ -69,7 +69,7 @@ COMPILE_PCREx macro will already be appropriately set. */
/* Macro for setting individual bits in class bitmaps. */
-#define SETBIT(a,b) a[(b)/8] |= (1 << ((b)&7))
+#define SETBIT(a,b) a[(b)/8] |= (1U << ((b)&7))
/* Maximum length value to check against when making sure that the integer that
holds the compiled pattern length does not overflow. We make it a bit less than
@@ -130,8 +130,8 @@ overrun before it actually does run off the end of the data block. */
/* Private flags added to firstchar and reqchar. */
-#define REQ_CASELESS (1 << 0) /* Indicates caselessness */
-#define REQ_VARY (1 << 1) /* Reqchar followed non-literal item */
+#define REQ_CASELESS (1U << 0) /* Indicates caselessness */
+#define REQ_VARY (1U << 1) /* Reqchar followed non-literal item */
/* Negative values for the firstchar and reqchar flags */
#define REQ_UNSET (-2)
#define REQ_NONE (-1)
@@ -3612,7 +3612,7 @@ for(;;)
if (chr > 255) break;
class_bitset = (pcre_uint8 *)
((list_ptr == list ? code : base_end) - list_ptr[2]);
- if ((class_bitset[chr >> 3] & (1 << (chr & 7))) != 0) return FALSE;
+ if ((class_bitset[chr >> 3] & (1U << (chr & 7))) != 0) return FALSE;
break;
#if defined SUPPORT_UTF || !defined COMPILE_PCRE8
@@ -7131,17 +7131,19 @@ for (;; ptr++)
int n = 0;
ptr++;
while(IS_DIGIT(*ptr))
+ {
n = n * 10 + *ptr++ - CHAR_0;
+ if (n > 255)
+ {
+ *errorcodeptr = ERR38;
+ goto FAILED;
+ }
+ }
if (*ptr != CHAR_RIGHT_PARENTHESIS)
{
*errorcodeptr = ERR39;
goto FAILED;
}
- if (n > 255)
- {
- *errorcodeptr = ERR38;
- goto FAILED;
- }
*code++ = n;
PUT(code, 0, (int)(ptr - cd->start_pattern + 1)); /* Pattern offset */
PUT(code, LINK_SIZE, 0); /* Default length */
@@ -7457,7 +7459,7 @@ for (;; ptr++)
{
open_capitem *oc;
recno = GET2(slot, 0);
- cd->backref_map |= (recno < 32)? (1 << recno) : 1;
+ cd->backref_map |= (recno < 32)? (1U << recno) : 1;
if (recno > cd->top_backref) cd->top_backref = recno;
/* Check to see if this back reference is recursive, that it, it
@@ -8068,7 +8070,7 @@ for (;; ptr++)
item_hwm_offset = cd->hwm - cd->start_workspace;
*code++ = ((options & PCRE_CASELESS) != 0)? OP_REFI : OP_REF;
PUT2INC(code, 0, recno);
- cd->backref_map |= (recno < 32)? (1 << recno) : 1;
+ cd->backref_map |= (recno < 32)? (1U << recno) : 1;
if (recno > cd->top_backref) cd->top_backref = recno;
/* Check to see if this back reference is recursive, that it, it
@@ -8681,7 +8683,7 @@ do {
op == OP_SCBRA || op == OP_SCBRAPOS)
{
int n = GET2(scode, 1+LINK_SIZE);
- int new_map = bracket_map | ((n < 32)? (1 << n) : 1);
+ int new_map = bracket_map | ((n < 32)? (1U << n) : 1);
if (!is_anchored(scode, new_map, cd, atomcount)) return FALSE;
}
@@ -8809,7 +8811,7 @@ do {
op == OP_SCBRA || op == OP_SCBRAPOS)
{
int n = GET2(scode, 1+LINK_SIZE);
- int new_map = bracket_map | ((n < 32)? (1 << n) : 1);
+ int new_map = bracket_map | ((n < 32)? (1U << n) : 1);
if (!is_startline(scode, new_map, cd, atomcount, inassert)) return FALSE;
}
@@ -9033,7 +9035,7 @@ Returns: pointer to compiled data block, or NULL on error,
#if defined(ERLANG_INTEGRATION)
PCRE_EXP_DEFN pcre * PCRE_CALL_CONVENTION
erts_pcre_compile(const char *pattern, int options, const char **errorptr,
- int *erroroffset, const unsigned char *tables)
+ int *erroroffset, const unsigned char *tables)
#else
PCRE_EXP_DEFN pcre * PCRE_CALL_CONVENTION
pcre_compile(const char *pattern, int options, const char **errorptr,
@@ -9051,8 +9053,8 @@ pcre32_compile(PCRE_SPTR32 pattern, int options, const char **errorptr,
{
#if defined COMPILE_PCRE8
#if defined(ERLANG_INTEGRATION)
-return erts_pcre_compile2(pattern, options, NULL, errorptr,
- erroroffset, tables);
+return erts_pcre_compile2(pattern, options, NULL, errorptr,
+ erroroffset, tables);
#else
return pcre_compile2(pattern, options, NULL, errorptr, erroroffset, tables);
#endif
diff --git a/erts/emulator/pcre/pcre_jit_compile.c b/erts/emulator/pcre/pcre_jit_compile.c
index 2d2288f81e..44288b7ec2 100644
--- a/erts/emulator/pcre/pcre_jit_compile.c
+++ b/erts/emulator/pcre/pcre_jit_compile.c
@@ -3938,10 +3938,10 @@ static sljit_s32 character_to_int32(pcre_uchar chr)
sljit_s32 value = (sljit_s32)chr;
#if defined COMPILE_PCRE8
#define SSE2_COMPARE_TYPE_INDEX 0
-return (value << 24) | (value << 16) | (value << 8) | value;
+return ((unsigned int)value << 24) | ((unsigned int)value << 16) | ((unsigned int)value << 8) | (unsigned int)value;
#elif defined COMPILE_PCRE16
#define SSE2_COMPARE_TYPE_INDEX 1
-return (value << 16) | value;
+return ((unsigned int)value << 16) | value;
#elif defined COMPILE_PCRE32
#define SSE2_COMPARE_TYPE_INDEX 2
return value;
@@ -8507,7 +8507,7 @@ if (opcode == OP_ONCE)
/* We temporarily encode the needs_control_head in the lowest bit.
Note: on the target architectures of SLJIT the ((x << 1) >> 1) returns
the same value for small signed numbers (including negative numbers). */
- BACKTRACK_AS(bracket_backtrack)->u.framesize = (BACKTRACK_AS(bracket_backtrack)->u.framesize << 1) | (needs_control_head ? 1 : 0);
+ BACKTRACK_AS(bracket_backtrack)->u.framesize = ((unsigned int)BACKTRACK_AS(bracket_backtrack)->u.framesize << 1) | (needs_control_head ? 1 : 0);
}
return cc + repeat_length;
}
diff --git a/erts/emulator/sys/common/erl_check_io.c b/erts/emulator/sys/common/erl_check_io.c
index 344d51f768..964f2df27e 100644
--- a/erts/emulator/sys/common/erl_check_io.c
+++ b/erts/emulator/sys/common/erl_check_io.c
@@ -184,11 +184,14 @@ int ERTS_WRITE_UNLIKELY(erts_no_pollsets) = 1;
int ERTS_WRITE_UNLIKELY(erts_no_poll_threads) = 1;
struct drv_ev_state_shared drv_ev_state;
-static ERTS_INLINE int fd_hash(ErtsSysFdType fd) {
+static ERTS_INLINE int fd_hash(ErtsSysFdType fd)
+{
+#ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS
int hash = (int)fd;
-# ifndef ERTS_SYS_CONTINOUS_FD_NUMBERS
+#else
+ int hash = (int)(SWord)fd;
hash ^= (hash >> 9);
-# endif
+#endif
return hash;
}
@@ -1082,7 +1085,7 @@ enif_select_x(ErlNifEnv* env,
pid ? pid->pid : THE_NON_VALUE, THE_NON_VALUE);
if (mode & ERL_NIF_SELECT_STOP) {
- ASSERT(resource->type->stop);
+ ASSERT(resource->type->fn.stop);
if (IS_FD_UNKNOWN(state)) {
/* fast track to stop callback */
call_stop = CALL_STOP;
@@ -1339,7 +1342,7 @@ print_driver_name(erts_dsprintf_buf_t *dsbufp, Eterm id)
static void
steal(erts_dsprintf_buf_t *dsbufp, ErtsDrvEventState *state, int mode)
{
- erts_dsprintf(dsbufp, "stealing control of fd=%d from ", (int) state->fd);
+ erts_dsprintf(dsbufp, "stealing control of fd=%bpd from ", (SWord) state->fd);
switch (state->type) {
case ERTS_EV_TYPE_DRV_SEL: {
int deselect_mode = 0;
@@ -1363,7 +1366,7 @@ steal(erts_dsprintf_buf_t *dsbufp, ErtsDrvEventState *state, int mode)
if (deselect_mode)
deselect(state, deselect_mode);
else {
- erts_dsprintf(dsbufp, "no one", (int) state->fd);
+ erts_dsprintf(dsbufp, "no one");
ASSERT(0);
}
erts_dsprintf(dsbufp, "\n");
@@ -1394,7 +1397,7 @@ steal(erts_dsprintf_buf_t *dsbufp, ErtsDrvEventState *state, int mode)
break;
}
default:
- erts_dsprintf(dsbufp, "no one\n", (int) state->fd);
+ erts_dsprintf(dsbufp, "no one\n");
ASSERT(0);
}
}
@@ -1405,10 +1408,10 @@ print_drv_select_op(erts_dsprintf_buf_t *dsbufp,
{
Port *pp = erts_drvport2port(ix);
erts_dsprintf(dsbufp,
- "driver_select(%p, %d,%s%s%s%s, %d) "
+ "driver_select(%p, %bpd,%s%s%s%s, %d) "
"by ",
ix,
- (int) fd,
+ (SWord) fd,
mode & ERL_DRV_READ ? " ERL_DRV_READ" : "",
mode & ERL_DRV_WRITE ? " ERL_DRV_WRITE" : "",
mode & ERL_DRV_USE ? " ERL_DRV_USE" : "",
@@ -1424,8 +1427,8 @@ print_nif_select_op(erts_dsprintf_buf_t *dsbufp,
ErtsResource* resource, Eterm ref)
{
erts_dsprintf(dsbufp,
- "enif_select(_, %d,%s%s%s, %T:%T, %T) ",
- (int) fd,
+ "enif_select(_, %bpd,%s%s%s, %T:%T, %T) ",
+ (SWord) fd,
mode & ERL_NIF_SELECT_READ ? " READ" : "",
mode & ERL_NIF_SELECT_WRITE ? " WRITE" : "",
(mode & ERL_NIF_SELECT_STOP ? " STOP"
@@ -1655,7 +1658,7 @@ erts_create_pollset_thread(int id, ErtsThrPrgrData *tpd) {
}
void
-erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
+erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only_thread)
{
int pollres_len;
int poll_ret, i;
@@ -1669,6 +1672,9 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
pollres_len = psi->pollres_len;
+ if (poll_only_thread)
+ erts_thr_progress_active(psi->tpd, 0);
+
#if ERTS_POLL_USE_FALLBACK
if (psi->ps == get_fallback_pollset()) {
@@ -1680,6 +1686,9 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
poll_ret = erts_poll_wait(psi->ps, psi->pollres, &pollres_len, psi->tpd, timeout_time);
}
+ if (poll_only_thread)
+ erts_thr_progress_active(psi->tpd, 1);
+
#ifdef ERTS_ENABLE_LOCK_CHECK
erts_lc_check_exact(NULL, 0); /* No locks should be locked */
#endif
@@ -1715,10 +1724,10 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
ErtsDrvEventState *state;
ErtsPollEvents revents = ERTS_POLL_RES_GET_EVTS(&psi->pollres[i]);
- /* The fd will be set to -1 if a pollset internal fd was triggered
+ /* The fd will be set to INVALID if a pollset internal fd was triggered
that was determined to be too expensive to remove from the result.
*/
- if (fd == -1) continue;
+ if (fd == ERTS_SYS_FD_INVALID) continue;
erts_mtx_lock(fd_mtx(fd));
@@ -1758,8 +1767,10 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
reactive_events = state->active_events;
- if (state->flags & ERTS_EV_FLAG_IN_SCHEDULER)
+ if (state->flags & ERTS_EV_FLAG_IN_SCHEDULER) {
reactive_events &= ~ERTS_POLL_EV_IN;
+ state->active_events |= ERTS_POLL_EV_IN;
+ }
/* Reactivate the poll op if there are still active events */
if (reactive_events) {
@@ -1866,7 +1877,7 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
dsbufp = erts_create_logger_dsbuf();
erts_dsprintf(dsbufp,
"Invalid event request type for fd in erts_poll()! "
- "fd=%d, event request type=%d\n", (int) state->fd,
+ "fd=%bpd, event request type=%d\n", (SWord) state->fd,
(int) state->type);
ASSERT(0);
deselect(state, 0);
@@ -1944,8 +1955,8 @@ bad_fd_in_pollset(ErtsDrvEventState *state, Eterm inport, Eterm outport)
}
}
erts_dsprintf(dsbufp,
- "Bad %s fd in erts_poll()! fd=%d, ",
- io_str, (int) state->fd);
+ "Bad %s fd in erts_poll()! fd=%bpd, ",
+ io_str, (SWord) state->fd);
if (state->type == ERTS_EV_TYPE_DRV_SEL) {
if (is_nil(port)) {
ErtsPortNames *ipnp = erts_get_port_names(inport, ERTS_INVALID_ERL_DRV_PORT);
@@ -1978,7 +1989,8 @@ bad_fd_in_pollset(ErtsDrvEventState *state, Eterm inport, Eterm outport)
}
}
else {
- erts_dsprintf(dsbufp, "Bad fd in erts_poll()! fd=%d\n", (int) state->fd);
+ erts_dsprintf(dsbufp, "Bad fd in erts_poll()! fd=%bpd\n",
+ (SWord) state->fd);
}
erts_send_error_to_logger_nogl(dsbufp);
@@ -1997,7 +2009,7 @@ stale_drv_select(Eterm id, ErtsDrvEventState *state, int mode)
static SafeHashValue drv_ev_state_hash(void *des)
{
- SafeHashValue val = (SafeHashValue) ((ErtsDrvEventState *) des)->fd;
+ SafeHashValue val = (SafeHashValue)(SWord) ((ErtsDrvEventState *) des)->fd;
return val ^ (val >> 8); /* Good enough for aligned pointer values? */
}
@@ -2548,8 +2560,9 @@ static int erts_debug_print_checkio_state(erts_dsprintf_buf_t *dsbufp,
#ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS
ErtsPollEvents aio_events = state->active_events;
#endif
- erts_dsprintf(dsbufp, "pollset=%d fd=%d ",
- state->flags & ERTS_EV_FLAG_FALLBACK ? -1 : get_pollset_id(fd), (int) fd);
+ erts_dsprintf(dsbufp, "pollset=%d fd=%bpd ",
+ state->flags & ERTS_EV_FLAG_FALLBACK ? -1 : get_pollset_id(fd),
+ (SWord) fd);
#if defined(HAVE_FSTAT) && !defined(NO_FSTAT_ON_SYS_FD_TYPE)
if (fstat((int) fd, &stat_buf) < 0)
diff --git a/erts/emulator/sys/common/erl_check_io.h b/erts/emulator/sys/common/erl_check_io.h
index 2ebcbe2743..ac285119bc 100644
--- a/erts/emulator/sys/common/erl_check_io.h
+++ b/erts/emulator/sys/common/erl_check_io.h
@@ -67,8 +67,12 @@ int erts_check_io_max_files(void);
* not return unless erts_check_io_interrupt(pt, 1) is called by another thread.
*
* @param pt the poll thread structure to use.
+ * @param timeout_time timeout
+ * @param poll_only_thread non zero when poll is the only thing the
+ * calling thread does
*/
-void erts_check_io(struct erts_poll_thread *pt, ErtsMonotonicTime timeout_time);
+void erts_check_io(struct erts_poll_thread *pt, ErtsMonotonicTime timeout_time,
+ int poll_only_thread);
/**
* Initialize the check io framework. This function will parse the arguments
* and delete any entries that it is interested in.
diff --git a/erts/emulator/sys/common/erl_mmap.c b/erts/emulator/sys/common/erl_mmap.c
index b0d9fc0776..78c20ea98d 100644
--- a/erts/emulator/sys/common/erl_mmap.c
+++ b/erts/emulator/sys/common/erl_mmap.c
@@ -2130,13 +2130,18 @@ void
erts_mmap_init(ErtsMemMapper* mm, ErtsMMapInit *init)
{
static int is_first_call = 1;
- int virtual_map = 0;
char *start = NULL, *end = NULL;
UWord pagesize;
+ int virtual_map = 0;
+
+ (void)virtual_map;
+
#if defined(__WIN32__)
- SYSTEM_INFO sysinfo;
- GetSystemInfo(&sysinfo);
- pagesize = (UWord) sysinfo.dwPageSize;
+ {
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo(&sysinfo);
+ pagesize = (UWord) sysinfo.dwPageSize;
+ }
#elif defined(_SC_PAGESIZE)
pagesize = (UWord) sysconf(_SC_PAGESIZE);
#elif defined(HAVE_GETPAGESIZE)
diff --git a/erts/emulator/sys/common/erl_mmap.h b/erts/emulator/sys/common/erl_mmap.h
index a30b7d2f6d..7a3fdd0aa9 100644
--- a/erts/emulator/sys/common/erl_mmap.h
+++ b/erts/emulator/sys/common/erl_mmap.h
@@ -49,7 +49,8 @@
* See the following message on how MAP_NORESERVE was treated on FreeBSD:
* <http://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20150202/122958.html>
*/
-# if defined(MAP_FIXED) && (defined(MAP_NORESERVE) || defined(__FreeBSD__))
+# if (defined(MAP_FIXED) && (defined(MAP_NORESERVE) || defined(__FreeBSD__)) \
+ && !defined(ADDRESS_SANITIZER))
# define ERTS_HAVE_OS_PHYSICAL_MEMORY_RESERVATION 1
# endif
#endif
@@ -203,16 +204,18 @@ ERTS_GLB_INLINE void erts_mem_discard(void *p, UWord size);
data[i] = pattern[i % sizeof(pattern)];
}
}
-#elif defined(HAVE_SYS_MMAN_H) && !(defined(__sun) || defined(__sun__))
+#elif defined(HAVE_SYS_MMAN_H) && defined(HAVE_MADVISE) && !(defined(__sun) || defined(__sun__))
#include <sys/mman.h>
ERTS_GLB_INLINE void erts_mem_discard(void *ptr, UWord size) {
+ /* Note that we don't fall back to MADV_DONTNEED since it promises that
+ * the given region will be zeroed on access, which turned out to be
+ * too much of a performance hit. */
#ifdef MADV_FREE
- /* This is preferred as it doesn't necessarily free the pages right
- * away, which is a bit faster than MADV_DONTNEED. */
madvise(ptr, size, MADV_FREE);
#else
- madvise(ptr, size, MADV_DONTNEED);
+ (void)ptr;
+ (void)size;
#endif
}
#elif defined(_WIN32)
diff --git a/erts/emulator/sys/common/erl_osenv.h b/erts/emulator/sys/common/erl_osenv.h
index 4777f2148a..d0902e0ba1 100644
--- a/erts/emulator/sys/common/erl_osenv.h
+++ b/erts/emulator/sys/common/erl_osenv.h
@@ -35,16 +35,10 @@
# include "config.h"
#endif
-typedef struct __erts_osenv_data_t erts_osenv_data_t;
-
-typedef struct __erts_osenv_t {
- struct __env_rbtnode_t *tree;
- int variable_count;
- int content_size;
-} erts_osenv_t;
-
#include "sys.h"
+typedef struct __erts_osenv_data_t erts_osenv_data_t;
+
struct __erts_osenv_data_t {
Sint length;
void *data;
@@ -53,7 +47,7 @@ struct __erts_osenv_data_t {
void erts_osenv_init(erts_osenv_t *env);
void erts_osenv_clear(erts_osenv_t *env);
-/* @brief Merges \c with into \c env
+/** @brief Merges \c with into \c env
*
* @param overwrite Whether to overwrite existing entries or keep them as they
* are. */
@@ -61,25 +55,25 @@ void erts_osenv_merge(erts_osenv_t *env, const erts_osenv_t *with, int overwrite
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
-/* @brief Copies env[key] into \c value
+/** @brief Copies env[key] into \c value
*
* @return 1 on success, 0 if the key couldn't be found, and -1 if the input
* was invalid. */
int erts_osenv_get_term(const erts_osenv_t *env, struct process *process,
Eterm key, Eterm *value);
-/* @brief Copies \c value into \c env[key]
+/** @brief Copies \c value into \c env[key]
*
* @return 1 on success, -1 if the input was invalid. */
int erts_osenv_put_term(erts_osenv_t *env, Eterm key, Eterm value);
-/* @brief Removes \c env[key]
+/** @brief Removes \c env[key]
*
* @return 1 on success, 0 if the key couldn't be found, and -1 if the input
* was invalid. */
int erts_osenv_unset_term(erts_osenv_t *env, Eterm key);
-/* @brief Copies env[key] into \c value
+/** @brief Copies env[key] into \c value
*
* @param value [in,out] The buffer to copy the value into, may be NULL if you
* only wish to query presence.
@@ -89,13 +83,13 @@ int erts_osenv_unset_term(erts_osenv_t *env, Eterm key);
int erts_osenv_get_native(const erts_osenv_t *env, const erts_osenv_data_t *key,
erts_osenv_data_t *value);
-/* @brief Copies \c value into \c env[key]
+/** @brief Copies \c value into \c env[key]
*
* @return 1 on success, -1 on failure. */
int erts_osenv_put_native(erts_osenv_t *env, const erts_osenv_data_t *key,
const erts_osenv_data_t *value);
-/* @brief Removes \c key from the env.
+/** @brief Removes \c key from the env.
*
* @return 1 on success, 0 if the key couldn't be found. */
int erts_osenv_unset_native(erts_osenv_t *env, const erts_osenv_data_t *key);
@@ -109,8 +103,8 @@ typedef void (*erts_osenv_foreach_native_cb_t)(void *state,
const erts_osenv_data_t *key,
const erts_osenv_data_t *value);
-/* @brief Walks through all environment variables, calling \c callback for each
- * one. It's unsafe to modify \c env within the callback. */
+/** @brief Walks through all environment variables, calling \c callback for
+ * each one. It's unsafe to modify \c env within the callback. */
void erts_osenv_foreach_term(const erts_osenv_t *env, struct process *process,
void *state, erts_osenv_foreach_term_cb_t callback);
diff --git a/erts/emulator/sys/common/erl_poll.c b/erts/emulator/sys/common/erl_poll.c
index e669572499..9f0c82dc4b 100644
--- a/erts/emulator/sys/common/erl_poll.c
+++ b/erts/emulator/sys/common/erl_poll.c
@@ -374,6 +374,7 @@ uint32_t epoll_events(int kp_fd, int fd);
#define ERTS_POLL_NOT_WOKEN 0
#define ERTS_POLL_WOKEN -1
#define ERTS_POLL_WOKEN_INTR 1
+#define ERTS_POLL_WSTATE_UNUSED ~0
static ERTS_INLINE void
reset_wakeup_state(ErtsPollSet *ps)
@@ -384,12 +385,16 @@ reset_wakeup_state(ErtsPollSet *ps)
static ERTS_INLINE int
is_woken(ErtsPollSet *ps)
{
+ if (!ERTS_POLL_USE_WAKEUP(ps))
+ return 0;
return erts_atomic32_read_acqb(&ps->wakeup_state) != ERTS_POLL_NOT_WOKEN;
}
static ERTS_INLINE int
is_interrupted_reset(ErtsPollSet *ps)
{
+ if (!ERTS_POLL_USE_WAKEUP(ps))
+ return 0;
return (erts_atomic32_xchg_acqb(&ps->wakeup_state, ERTS_POLL_NOT_WOKEN)
== ERTS_POLL_WOKEN_INTR);
}
@@ -397,7 +402,10 @@ is_interrupted_reset(ErtsPollSet *ps)
static ERTS_INLINE void
woke_up(ErtsPollSet *ps)
{
- erts_aint32_t wakeup_state = erts_atomic32_read_acqb(&ps->wakeup_state);
+ erts_aint32_t wakeup_state;
+ if (!ERTS_POLL_USE_WAKEUP(ps))
+ return;
+ wakeup_state = erts_atomic32_read_acqb(&ps->wakeup_state);
if (wakeup_state == ERTS_POLL_NOT_WOKEN)
(void) erts_atomic32_cmpxchg_nob(&ps->wakeup_state,
ERTS_POLL_WOKEN,
@@ -450,6 +458,7 @@ cleanup_wakeup_pipe(ErtsPollSet *ps)
int intr = 0;
int fd = ps->wake_fds[0];
int res;
+ ASSERT(ERTS_POLL_USE_WAKEUP(ps));
do {
char buf[32];
res = read(fd, buf, sizeof(buf));
@@ -475,6 +484,13 @@ create_wakeup_pipe(ErtsPollSet *ps)
int wake_fds[2];
ps->wake_fds[0] = -1;
ps->wake_fds[1] = -1;
+ if (!ERTS_POLL_USE_WAKEUP(ps)) {
+ erts_atomic32_init_nob(&ps->wakeup_state,
+ (erts_aint32_t) ERTS_POLL_WSTATE_UNUSED);
+ return;
+ }
+ erts_atomic32_init_nob(&ps->wakeup_state,
+ (erts_aint32_t) ERTS_POLL_NOT_WOKEN);
if (pipe(wake_fds) < 0) {
fatal_error("%s:%d:create_wakeup_pipe(): "
"Failed to create pipe: %s (%d)\n",
@@ -483,6 +499,7 @@ create_wakeup_pipe(ErtsPollSet *ps)
erl_errno_id(errno),
errno);
}
+
SET_NONBLOCKING(wake_fds[0]);
SET_NONBLOCKING(wake_fds[1]);
@@ -629,12 +646,13 @@ int erts_poll_new_table_len(int old_len, int need_len)
}
else {
new_len = old_len;
+ if (new_len < ERTS_FD_TABLE_MIN_LENGTH)
+ new_len = ERTS_FD_TABLE_MIN_LENGTH;
do {
if (new_len < ERTS_FD_TABLE_EXP_THRESHOLD)
new_len *= 2;
else
new_len += ERTS_FD_TABLE_EXP_THRESHOLD;
-
} while (new_len < need_len);
}
ASSERT(new_len >= need_len);
@@ -1459,13 +1477,13 @@ ERTS_POLL_EXPORT(save_result)(ErtsPollSet *ps, ErtsPollResFd pr[], int max_res,
if (ERTS_POLL_USE_WAKEUP(ps) && fd == wake_fd) {
cleanup_wakeup_pipe(ps);
- ERTS_POLL_RES_SET_FD(&pr[i], -1);
+ ERTS_POLL_RES_SET_FD(&pr[i], ERTS_SYS_FD_INVALID);
ERTS_POLL_RES_SET_EVTS(&pr[i], ERTS_POLL_EV_NONE);
res--;
}
#if ERTS_POLL_USE_TIMERFD
else if (fd == ps->timer_fd) {
- ERTS_POLL_RES_SET_FD(&pr[i], -1);
+ ERTS_POLL_RES_SET_FD(&pr[i], ERTS_SYS_FD_INVALID);
ERTS_POLL_RES_SET_EVTS(&pr[i], ERTS_POLL_EV_NONE);
res--;
}
@@ -1955,8 +1973,7 @@ ERTS_POLL_EXPORT(erts_poll_wait)(ErtsPollSet *ps,
ERTS_MSACC_SET_STATE_CACHED(ERTS_MSACC_STATE_CHECK_IO);
}
- if (ERTS_POLL_USE_WAKEUP(ps))
- woke_up(ps);
+ woke_up(ps);
if (res < 0) {
#if ERTS_POLL_USE_SELECT
@@ -2046,6 +2063,21 @@ ERTS_POLL_EXPORT(erts_poll_init)(int *concurrent_updates)
max_fds = OPEN_MAX;
#endif
+ if (max_fds < 0 && errno == 0) {
+ /* On macOS 11 and higher, it possible to have an unlimited
+ * number of open files per process. ERTS will need an actual
+ * limit, though, so we will set it to a largish value. The
+ * number below is the hard number of file descriptors per
+ * process as returned by `sysctl kern.maxfilesperproc`, which
+ * seems to be the limit in practice.
+ *
+ * Note: The size of the port table will be based on max_fds,
+ * so we don't want to set it to a huge value such as
+ * MAX_INT.
+ */
+ max_fds = 24576;
+ }
+
#if ERTS_POLL_USE_SELECT && defined(FD_SETSIZE) && \
!defined(_DARWIN_UNLIMITED_SELECT)
if (max_fds > FD_SETSIZE)
@@ -2134,7 +2166,6 @@ ERTS_POLL_EXPORT(erts_poll_create_pollset)(int id)
ps->oneshot = 1;
#endif
- erts_atomic32_init_nob(&ps->wakeup_state, (erts_aint32_t) 0);
create_wakeup_pipe(ps);
#if ERTS_POLL_USE_TIMERFD
diff --git a/erts/emulator/sys/common/erl_poll.h b/erts/emulator/sys/common/erl_poll.h
index d40dabc529..096c14816e 100644
--- a/erts/emulator/sys/common/erl_poll.h
+++ b/erts/emulator/sys/common/erl_poll.h
@@ -1,8 +1,8 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2006-2018. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2006-2019. All Rights Reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -161,7 +161,10 @@ typedef enum {
#include <sys/epoll.h>
#if ERTS_POLL_USE_EPOLL
-#ifdef HAVE_SYS_TIMERFD_H
+/* sys/timerfd.h was added in Android 4.4 KitKat. With the Android NDK
+Unified Headers, check that the build is targeting at least the
+corresponding API level 19. */
+#if defined(HAVE_SYS_TIMERFD_H) && !(defined(__ANDROID__) && (__ANDROID_API__ < 19))
#include <sys/timerfd.h>
#undef ERTS_POLL_USE_TIMERFD
#define ERTS_POLL_USE_TIMERFD 1
diff --git a/erts/emulator/sys/common/erl_sys_common_misc.c b/erts/emulator/sys/common/erl_sys_common_misc.c
index d34e1a9ec0..fd7c3b2cda 100644
--- a/erts/emulator/sys/common/erl_sys_common_misc.c
+++ b/erts/emulator/sys/common/erl_sys_common_misc.c
@@ -1,8 +1,8 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2006-2018. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2006-2020. All Rights Reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -49,9 +49,9 @@
static int filename_encoding = ERL_FILENAME_UNKNOWN;
static int filename_warning = ERL_FILENAME_WARNING_WARNING;
-#if defined(__WIN32__) || defined(__DARWIN__)
-/* Default unicode on windows and MacOS X */
-static int user_filename_encoding = ERL_FILENAME_UTF8;
+#if defined(__WIN32__) || defined(__DARWIN__) || defined(__ANDROID__)
+/* Default to Unicode on Windows, MacOS X and Android */
+static int user_filename_encoding = ERL_FILENAME_UTF8;
#else
static int user_filename_encoding = ERL_FILENAME_UNKNOWN;
#endif
diff --git a/erts/emulator/sys/unix/erl_child_setup.c b/erts/emulator/sys/unix/erl_child_setup.c
index 129861ebd5..103eb40288 100644
--- a/erts/emulator/sys/unix/erl_child_setup.c
+++ b/erts/emulator/sys/unix/erl_child_setup.c
@@ -63,10 +63,13 @@
#include "erl_driver.h"
#include "sys_uds.h"
-#include "hash.h"
#include "erl_term.h"
#include "erl_child_setup.h"
+#undef ERTS_GLB_INLINE_INCL_FUNC_DEF
+#define ERTS_GLB_INLINE_INCL_FUNC_DEF 1
+#include "hash.h"
+
#define SET_CLOEXEC(fd) fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC)
#if defined(__ANDROID__)
@@ -75,6 +78,10 @@
#define SHELL "/bin/sh"
#endif /* __ANDROID__ */
+#if !defined(MSG_DONTWAIT) && defined(MSG_NONBLOCK)
+#define MSG_DONTWAIT MSG_NONBLOCK
+#endif
+
//#define HARD_DEBUG
#ifdef HARD_DEBUG
#define DEBUG_PRINT(fmt, ...) fprintf(stderr, "%d:" fmt "\r\n", getpid(), ##__VA_ARGS__)
@@ -411,6 +418,7 @@ main(int argc, char *argv[])
int uds_fd = 3, max_fd = 3;
#ifndef HAVE_CLOSEFROM
int i;
+ DIR *dir;
#endif
struct sigaction sa;
@@ -426,11 +434,29 @@ main(int argc, char *argv[])
#if defined(HAVE_CLOSEFROM)
closefrom(4);
#else
- for (i = 4; i < max_files; i++)
+ dir = opendir("/dev/fd");
+ if (dir == NULL) { /* /dev/fd not available */
+ for (i = 4; i < max_files; i++)
+#if defined(__ANDROID__)
+ if (i != system_properties_fd())
+#endif
+ (void) close(i);
+ } else {
+ /* Iterate over fds obtained from /dev/fd */
+ struct dirent *entry;
+ int dir_fd = dirfd(dir);
+
+ while ((entry = readdir(dir)) != NULL) {
+ i = atoi(entry->d_name);
#if defined(__ANDROID__)
- if (i != system_properties_fd())
+ if (i != system_properties_fd())
#endif
- (void) close(i);
+ if (i >= 4 && i != dir_fd)
+ (void) close(i);
+ }
+
+ closedir(dir);
+ }
#endif
if (pipe(sigchld_pipe) < 0) {
diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c
index 6935c0cca9..46a035214b 100644
--- a/erts/emulator/sys/unix/sys.c
+++ b/erts/emulator/sys/unix/sys.c
@@ -49,6 +49,10 @@
#include <sys/ioctl.h>
#endif
+#ifdef ADDRESS_SANITIZER
+# include <sanitizer/asan_interface.h>
+#endif
+
#define ERTS_WANT_BREAK_HANDLING
#define WANT_NONBLOCKING /* must define this to pull in defs from sys.h */
#include "sys.h"
@@ -386,6 +390,9 @@ void erts_sys_sigsegv_handler(int signo) {
*/
int
erts_sys_is_area_readable(char *start, char *stop) {
+#ifdef ADDRESS_SANITIZER
+ return __asan_region_is_poisoned(start, stop-start) == NULL;
+#else
int fds[2];
if (!pipe(fds)) {
/* We let write try to figure out if the pointers are readable */
@@ -400,7 +407,7 @@ erts_sys_is_area_readable(char *start, char *stop) {
return 1;
}
return 0;
-
+#endif
}
static ERTS_INLINE int
@@ -692,6 +699,10 @@ void
erts_sys_unix_later_init(void)
{
sys_signal(SIGTERM, generic_signal_handler);
+
+ /* Ignore SIGCHLD to ensure orphaned processes don't turn into zombies on
+ * death when we're pid 1. */
+ sys_signal(SIGCHLD, SIG_IGN);
}
int sys_max_files(void)
@@ -743,10 +754,17 @@ void os_version(int *pMajor, int *pMinor, int *pBuild) {
* X.Y or X.Y.Z. */
(void) uname(&uts);
+#ifdef _AIX
+ /* AIX stores the major in version and minor in release */
+ *pMajor = atoi(uts.version);
+ *pMinor = atoi(uts.release);
+ *pBuild = 0; /* XXX: get oslevel for AIX or TR on i */
+#else
release = uts.release;
*pMajor = get_number(&release); /* Pointer to major version. */
*pMinor = get_number(&release); /* Pointer to minor version. */
*pBuild = get_number(&release); /* Pointer to build number. */
+#endif
}
void erts_do_break_handling(void)
diff --git a/erts/emulator/sys/unix/sys_drivers.c b/erts/emulator/sys/unix/sys_drivers.c
index a31304a4a7..152d1757ba 100644
--- a/erts/emulator/sys/unix/sys_drivers.c
+++ b/erts/emulator/sys/unix/sys_drivers.c
@@ -55,6 +55,7 @@
#define WANT_NONBLOCKING /* must define this to pull in defs from sys.h */
#include "sys.h"
+#include "erl_osenv.h"
#include "erl_threads.h"
diff --git a/erts/emulator/sys/unix/sys_uds.c b/erts/emulator/sys/unix/sys_uds.c
index c9f73622ba..c3094d20d4 100644
--- a/erts/emulator/sys/unix/sys_uds.c
+++ b/erts/emulator/sys/unix/sys_uds.c
@@ -23,7 +23,7 @@
#endif
#if defined(__sun__) && !defined(_XOPEN_SOURCE)
-#define _XOPEN_SOURCE 500
+#define _XOPEN_SOURCE 600
#endif
#include <limits.h>
diff --git a/erts/emulator/sys/win32/erl_win_sys.h b/erts/emulator/sys/win32/erl_win_sys.h
index b00ba287e2..4c64494ac0 100644
--- a/erts/emulator/sys/win32/erl_win_sys.h
+++ b/erts/emulator/sys/win32/erl_win_sys.h
@@ -52,10 +52,6 @@
#include <fcntl.h>
#include <time.h>
#include <sys/timeb.h>
-#pragma comment(linker,"/manifestdependency:\"type='win32' "\
- "name='Microsoft.Windows.Common-Controls' "\
- "version='6.0.0.0' processorArchitecture='*' "\
- "publicKeyToken='6595b64144ccf1df' language='*'\"")
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c
index 957ade51c3..244fed720d 100644
--- a/erts/emulator/sys/win32/sys.c
+++ b/erts/emulator/sys/win32/sys.c
@@ -27,6 +27,7 @@
#endif
#include "sys.h"
+#include "erl_osenv.h"
#include "erl_alloc.h"
#include "erl_sys_driver.h"
#include "global.h"
@@ -1697,7 +1698,7 @@ create_child_process
erts_free(ERTS_ALC_T_TMP, qte);
}
- DEBUGF((stderr,"Creating child process: %S, createFlags = %d\n", newcmdline, createFlags));
+ DEBUGF(("Creating child process: %S, createFlags = %d\n", newcmdline, createFlags));
ok = CreateProcessW((wchar_t *) appname,
(wchar_t *) newcmdline,
NULL,
@@ -1995,7 +1996,7 @@ threaded_reader(LPVOID param)
aio->bytesTransferred = n;
}
SetEvent(aio->ov.hEvent);
- if ((aio->flags & DF_XLAT_CR) == 0 && aio->bytesTransferred == 0) {
+ if (aio->bytesTransferred == 0) {
break;
}
if (aio->pendingError != NO_ERROR) {
@@ -2111,15 +2112,15 @@ translate_fd(int fd)
handle = GetStdHandle(STD_ERROR_HANDLE);
break;
default:
- return (HANDLE) fd;
+ return (HANDLE)(SWord) fd;
}
- DEBUGF(("translate_fd(%d) -> std(%d)\n", fd, handle));
+ DEBUGF(("translate_fd(%d) -> std(%p)\n", fd, (void*)handle));
if (handle == INVALID_HANDLE_VALUE || handle == 0) {
handle = CreateFile("nul", access, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}
- DEBUGF(("translate_fd(%d) -> %d\n", fd, handle));
+ DEBUGF(("translate_fd(%d) -> %p\n", fd, (void*)handle));
return handle;
}
@@ -2924,9 +2925,9 @@ unsigned char* sys_preload_begin(Preload* pp)
ASSERT(beam_module != NULL);
resource = res_name[pp-preloaded];
- DEBUGF(("Loading name: %s; size: %d; resource: %p\n",
+ DEBUGF(("Loading name: %s; size: %d; resource: %u\n",
pp->name, pp->size, resource));
- hRes = FindResource(beam_module, (char *) resource, "ERLANG_CODE");
+ hRes = FindResource(beam_module, (char*)(UWord) resource, "ERLANG_CODE");
return pp->code = LoadResource(beam_module, hRes);
}
diff --git a/erts/emulator/sys/win32/sys_env.c b/erts/emulator/sys/win32/sys_env.c
index c78161b344..36223c2c14 100644
--- a/erts/emulator/sys/win32/sys_env.c
+++ b/erts/emulator/sys/win32/sys_env.c
@@ -23,6 +23,7 @@
#endif
#include "sys.h"
+#include "erl_osenv.h"
#include "erl_sys_driver.h"
#include "erl_alloc.h"
diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile
index 44470d6c08..3825796322 100644
--- a/erts/emulator/test/Makefile
+++ b/erts/emulator/test/Makefile
@@ -28,21 +28,6 @@ EBIN = .
# Target Specs
# ----------------------------------------------------
-SOCKET_MODULES = \
- socket_test_lib \
- socket_test_logger \
- socket_test_evaluator \
- socket_test_ttest_lib \
- socket_test_ttest_tcp_gen \
- socket_test_ttest_tcp_socket \
- socket_test_ttest_tcp_client \
- socket_test_ttest_tcp_client_gen \
- socket_test_ttest_tcp_client_socket \
- socket_test_ttest_tcp_server \
- socket_test_ttest_tcp_server_gen \
- socket_test_ttest_tcp_server_socket \
- socket_SUITE
-
MODULES= \
a_SUITE \
after_SUITE \
@@ -87,6 +72,7 @@ MODULES= \
gc_SUITE \
guard_SUITE \
hash_SUITE \
+ hash_property_test_SUITE \
hibernate_SUITE \
hipe_SUITE \
iovec_SUITE \
@@ -122,7 +108,6 @@ MODULES= \
signal_SUITE \
small_SUITE \
smoke_test_SUITE \
- $(SOCKET_MODULES) \
statistics_SUITE \
system_info_SUITE \
system_profile_SUITE \
@@ -148,6 +133,7 @@ MODULES= \
dgawd_handler \
random_iolist \
erts_test_utils \
+ erts_test_destructor \
crypto_reference \
literal_area_collector_test
@@ -171,13 +157,8 @@ NATIVE_MODULES= $(NATIVE:%=%_native_SUITE)
NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl)
ERL_FILES= $(MODULES:%=%.erl)
-HRL_FILES= \
- socket_test_evaluator.hrl \
- socket_test_ttest.hrl \
- socket_test_ttest_client.hrl
TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR))
EMAKEFILE=Emakefile
@@ -221,7 +202,6 @@ clean:
docs:
targets: $(TARGET_FILES)
-socket_targets: $(SOCKET_TARGETS)
# ----------------------------------------------------
# Special targets
@@ -243,11 +223,11 @@ release_spec:
release_tests_spec: make_emakefile
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(EMAKEFILE) $(TEST_SPEC_FILES) \
- $(ERL_FILES) $(HRL_FILES) "$(RELSYSDIR)"
+ $(ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(NO_OPT_ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(NATIVE_ERL_FILES) "$(RELSYSDIR)"
chmod -R u+w "$(RELSYSDIR)"
- tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -)
+ tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -)
release_docs_spec:
diff --git a/erts/emulator/test/a_SUITE.erl b/erts/emulator/test/a_SUITE.erl
index 5b04a15b85..f3752ab1c3 100644
--- a/erts/emulator/test/a_SUITE.erl
+++ b/erts/emulator/test/a_SUITE.erl
@@ -30,13 +30,14 @@
-include_lib("common_test/include/ct.hrl").
-export([all/0, suite/0, init_per_suite/1, end_per_suite/1,
- leaked_processes/1, long_timers/1, pollset_size/1]).
+ leaked_processes/1, long_timers/1, pollset_size/1,
+ used_thread_specific_events/1]).
suite() ->
[{ct_hooks,[ts_install_cth]}].
all() ->
- [leaked_processes, long_timers, pollset_size].
+ [leaked_processes, long_timers, pollset_size, used_thread_specific_events].
%% Start some system servers now to avoid having them
%% reported as leaks.
@@ -95,6 +96,25 @@ pollset_size(Config) when is_list(Config) ->
{comment, "Testcase started! This test will run in parallel with the "
"erts testsuite and ends in the z_SUITE:pollset_size/1 testcase."}.
+used_thread_specific_events(Config) when is_list(Config) ->
+ Parent = self(),
+ Go = make_ref(),
+ spawn(fun () ->
+ Name = used_thread_specific_events_holder,
+ true = register(Name, self()),
+ UsedTSE = erlang:system_info(ethread_used_tse),
+ io:format("UsedTSE: ~p~n", [UsedTSE]),
+ Parent ! Go,
+ receive
+ {get_used_tse, Pid} ->
+ Pid ! {used_tse, UsedTSE}
+ end
+ end),
+ receive Go -> ok end,
+ {comment, "Testcase started! This test will run in parallel with the "
+ "erts testsuite and ends in the z_SUITE:used_thread_specific_events/1 testcase."}.
+
+
%%
%% Internal functions...
%%
diff --git a/erts/emulator/test/alloc_SUITE.erl b/erts/emulator/test/alloc_SUITE.erl
index 4e0243c1cd..97cd90d72c 100644
--- a/erts/emulator/test/alloc_SUITE.erl
+++ b/erts/emulator/test/alloc_SUITE.erl
@@ -19,8 +19,8 @@
-module(alloc_SUITE).
-author('rickard.green@uab.ericsson.se').
--export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2]).
-
+-export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2,
+ init_per_suite/1, end_per_suite/1]).
-export([basic/1,
coalesce/1,
threads/1,
@@ -32,7 +32,8 @@
erts_mmap/1,
cpool/1,
set_dyn_param/1,
- migration/1]).
+ migration/1,
+ cpool_opt/1]).
-include_lib("common_test/include/ct.hrl").
@@ -43,7 +44,20 @@ suite() ->
all() ->
[basic, coalesce, threads, realloc_copy, bucket_index,
set_dyn_param,
- bucket_mask, rbtree, mseg_clear_cache, erts_mmap, cpool, migration].
+ bucket_mask, rbtree, mseg_clear_cache, erts_mmap, cpool, migration,
+ cpool_opt].
+
+init_per_suite(Config) ->
+ case test_server:memory_checker() of
+ MC when MC =:= valgrind; MC =:= asan ->
+ %% No point testing own allocators under valgrind or asan.
+ {skip, "Memory checker " ++ atom_to_list(MC)};
+ none ->
+ Config
+ end.
+
+end_per_suite(_Config) ->
+ ok.
init_per_testcase(Case, Config) when is_list(Config) ->
[{testcase, Case},{debug,false}|Config].
@@ -74,6 +88,51 @@ migration(Cfg) ->
drv_case(Cfg, concurrent, "+MZe true +MRe false +MZas ageffcbf"),
drv_case(Cfg, concurrent, "+MZe true +MRe false +MZas chaosff").
+cpool_opt(Config) when is_list(Config) ->
+ OldEnv = clear_env(),
+ try
+ {ok, NodeA} = start_node(Config, "+Mue true +Mut true +Muacul de +Mucp @", []),
+ {cp, '@'} = get_cp_opt(NodeA, binary_alloc),
+ {cp, '@'} = get_cp_opt(NodeA, std_alloc),
+ {cp, '@'} = get_cp_opt(NodeA, ets_alloc),
+ {cp, '@'} = get_cp_opt(NodeA, fix_alloc),
+ {cp, '@'} = get_cp_opt(NodeA, eheap_alloc),
+ {cp, '@'} = get_cp_opt(NodeA, ll_alloc),
+ {cp, '@'} = get_cp_opt(NodeA, driver_alloc),
+ {cp, '@'} = get_cp_opt(NodeA, sl_alloc),
+ stop_node(NodeA),
+ {ok, NodeB} = start_node(Config, "+Mue true +Mut true +Muacul de +Mucp :", []),
+ {cp, 'B'} = get_cp_opt(NodeB, binary_alloc),
+ {cp, 'D'} = get_cp_opt(NodeB, std_alloc),
+ {cp, 'E'} = get_cp_opt(NodeB, ets_alloc),
+ {cp, 'F'} = get_cp_opt(NodeB, fix_alloc),
+ {cp, 'H'} = get_cp_opt(NodeB, eheap_alloc),
+ {cp, 'L'} = get_cp_opt(NodeB, ll_alloc),
+ {cp, 'R'} = get_cp_opt(NodeB, driver_alloc),
+ {cp, 'S'} = get_cp_opt(NodeB, sl_alloc),
+ stop_node(NodeB),
+ {ok, NodeC} = start_node(Config, "+Mue true +Mut true +Muacul de +Mucp : +MEcp H", []),
+ {cp, 'B'} = get_cp_opt(NodeC, binary_alloc),
+ {cp, 'D'} = get_cp_opt(NodeC, std_alloc),
+ {cp, 'H'} = get_cp_opt(NodeC, ets_alloc),
+ {cp, 'F'} = get_cp_opt(NodeC, fix_alloc),
+ {cp, 'H'} = get_cp_opt(NodeC, eheap_alloc),
+ {cp, 'L'} = get_cp_opt(NodeC, ll_alloc),
+ {cp, 'R'} = get_cp_opt(NodeC, driver_alloc),
+ {cp, 'S'} = get_cp_opt(NodeC, sl_alloc),
+ stop_node(NodeC)
+ after
+ restore_env(OldEnv)
+ end,
+ ok.
+
+get_cp_opt(Node, Alloc) ->
+ AInfo = rpc:call(Node, erlang, system_info, [{allocator,Alloc}]),
+ {instance, 1, IList} = lists:keyfind(1, 2, AInfo),
+ {options, OList} = lists:keyfind(options, 1, IList),
+ lists:keyfind(cp, 1, OList).
+
+
erts_mmap(Config) when is_list(Config) ->
case {os:type(), mmsc_flags()} of
{{unix,_}, false} ->
@@ -302,45 +361,52 @@ wait_for_memory_deallocations() ->
end.
print_stats(migration) ->
- IFun = fun({instance,Inr,Istats}, {Bacc,Cacc,Pacc}) ->
- {mbcs,MBCS} = lists:keyfind(mbcs, 1, Istats),
- Btup = lists:keyfind(blocks, 1, MBCS),
- Ctup = lists:keyfind(carriers, 1, MBCS),
-
- Ptup = case lists:keyfind(mbcs_pool, 1, Istats) of
- {mbcs_pool,POOL} ->
- {blocks, Bpool} = lists:keyfind(blocks, 1, POOL),
- {carriers, Cpool} = lists:keyfind(carriers, 1, POOL),
- {pool, Bpool, Cpool};
- false ->
- {pool, 0, 0}
- end,
- io:format("{instance,~p,~p,~p,~p}}\n",
- [Inr, Btup, Ctup, Ptup]),
- {tuple_add(Bacc,Btup),tuple_add(Cacc,Ctup),
- tuple_add(Pacc,Ptup)};
- (_, Acc) -> Acc
+ IFun = fun({instance,_,Stats}, {Regular0, Pooled0}) ->
+ {mbcs,MBCS} = lists:keyfind(mbcs, 1, Stats),
+ {sbcs,SBCS} = lists:keyfind(sbcs, 1, Stats),
+
+ Regular = MBCS ++ SBCS ++ Regular0,
+ case lists:keyfind(mbcs_pool, 1, Stats) of
+ {mbcs_pool,Pool} -> {Regular, Pool ++ Pooled0};
+ false -> {Regular, Pooled0}
+ end;
+ (_, Acc) ->
+ Acc
end,
- {Btot,Ctot,Ptot} = lists:foldl(IFun,
- {{blocks,0,0,0},{carriers,0,0,0},{pool,0,0}},
- erlang:system_info({allocator,test_alloc})),
+ Stats = erlang:system_info({allocator,test_alloc}),
+ {Regular, Pooled} = lists:foldl(IFun, {[], []}, Stats),
- {pool, PBtot, PCtot} = Ptot,
- io:format("Number of blocks : ~p\n", [Btot]),
- io:format("Number of carriers: ~p\n", [Ctot]),
- io:format("Number of pooled blocks : ~p\n", [PBtot]),
- io:format("Number of pooled carriers: ~p\n", [PCtot]);
-print_stats(_) -> ok.
+ {RegBlocks, RegCarriers} = summarize_alloc_stats(Regular, {0, 0}),
+ {PooledBlocks, PooledCarriers} = summarize_alloc_stats(Pooled, {0, 0}),
-tuple_add(T1, T2) ->
- list_to_tuple(lists:zipwith(fun(E1,E2) when is_number(E1), is_number(E2) ->
- E1 + E2;
- (A,A) ->
- A
- end,
- tuple_to_list(T1), tuple_to_list(T2))).
+ io:format("Number of blocks : ~p\n", [RegBlocks]),
+ io:format("Number of carriers: ~p\n", [RegCarriers]),
+ io:format("Number of pooled blocks : ~p\n", [PooledBlocks]),
+ io:format("Number of pooled carriers: ~p\n", [PooledCarriers]);
+print_stats(_) ->
+ ok.
+summarize_alloc_stats([{blocks,L} | Rest], {Blocks0, Carriers}) ->
+ Blocks = count_blocks([S || {_Type, S} <- L], Blocks0),
+ summarize_alloc_stats(Rest, {Blocks, Carriers});
+summarize_alloc_stats([{carriers, Count, _, _} | Rest], {Blocks, Carriers0}) ->
+ summarize_alloc_stats(Rest, {Blocks, Carriers0 + Count});
+summarize_alloc_stats([{carriers, Count} | Rest], {Blocks, Carriers0}) ->
+ summarize_alloc_stats(Rest, {Blocks, Carriers0 + Count});
+summarize_alloc_stats([_ | Rest], Acc) ->
+ summarize_alloc_stats(Rest, Acc);
+summarize_alloc_stats([], Acc) ->
+ Acc.
+
+count_blocks([{count, Count, _, _} | Rest], Acc) ->
+ count_blocks(Rest, Acc + Count);
+count_blocks([{count, Count} | Rest], Acc) ->
+ count_blocks(Rest, Acc + Count);
+count_blocks([_ | Rest], Acc) ->
+ count_blocks(Rest, Acc);
+count_blocks([], Acc) ->
+ Acc.
one_shot(CaseName) ->
State = CaseName:start({1, 0, erlang:system_info(build_type)}),
@@ -363,7 +429,7 @@ many_shot(CaseName, I, Mem) ->
Result1.
concurrent(CaseName) ->
- NSched = erlang:system_info(schedulers),
+ NSched = erlang:system_info(schedulers_online),
Mem = (free_memory() * 3) div 4,
PRs = lists:map(fun(I) -> spawn_opt(fun() ->
many_shot(CaseName, I,
@@ -472,3 +538,39 @@ free_memory() ->
ct:fail({"os_mon not built"})
end.
+clear_env() ->
+ ErlRelFlagsName =
+ "ERL_OTP"
+ ++ erlang:system_info(otp_release)
+ ++ "_FLAGS",
+ ErlFlags = os:getenv("ERL_FLAGS"),
+ os:unsetenv("ERL_FLAGS"),
+ ErlAFlags = os:getenv("ERL_AFLAGS"),
+ os:unsetenv("ERL_AFLAGS"),
+ ErlZFlags = os:getenv("ERL_ZFLAGS"),
+ os:unsetenv("ERL_ZFLAGS"),
+ ErlRelFlags = os:getenv(ErlRelFlagsName),
+ os:unsetenv(ErlRelFlagsName),
+ {ErlFlags, ErlAFlags, ErlZFlags, ErlRelFlags}.
+
+restore_env({ErlFlags, ErlAFlags, ErlZFlags, ErlRelFlags}) ->
+ if ErlFlags == false -> ok;
+ true -> os:putenv("ERL_FLAGS", ErlFlags)
+ end,
+ if ErlAFlags == false -> ok;
+ true -> os:putenv("ERL_AFLAGS", ErlAFlags)
+ end,
+ if ErlZFlags == false -> ok;
+ true -> os:putenv("ERL_ZFLAGS", ErlZFlags)
+ end,
+ if ErlRelFlags == false -> ok;
+ true ->
+ ErlRelFlagsName =
+ "ERL_OTP"
+ ++ erlang:system_info(otp_release)
+ ++ "_FLAGS",
+ os:putenv(ErlRelFlagsName, ErlRelFlags)
+ end,
+ ok.
+
+
diff --git a/erts/emulator/test/async_ports_SUITE_data/cport.c b/erts/emulator/test/async_ports_SUITE_data/cport.c
index 033aff382a..621b109444 100644
--- a/erts/emulator/test/async_ports_SUITE_data/cport.c
+++ b/erts/emulator/test/async_ports_SUITE_data/cport.c
@@ -10,27 +10,19 @@
#endif
typedef unsigned char byte;
+int write_exact(byte *buf, int len);
-int read_cmd(byte *buf)
-{
- int len;
- if (read_exact(buf, 4) != 4)
- return(-1);
+int read_exact(byte *buf, int len);
- len = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
- return read_exact(buf, len);
-}
-
-int write_cmd(byte *buf, int len)
+int write_exact(byte *buf, int len)
{
- byte li[4];
- li[0] = (len >> 24) & 0xff;
- li[1] = (len >> 16) & 0xff;
- li[2] = (len >> 8) & 0xff;
- li[3] = len & 0xff;
- write_exact(&li, 4);
-
- return write_exact(buf, len);
+ int i, wrote = 0;
+ do {
+ if ((i = write(1, buf+wrote, len-wrote)) < 0)
+ return (i);
+ wrote += i;
+ } while (wrote<len);
+ return len;
}
int read_exact(byte *buf, int len)
@@ -46,15 +38,26 @@ int read_exact(byte *buf, int len)
return len;
}
-int write_exact(byte *buf, int len)
+int write_cmd(byte *buf, int len)
{
- int i, wrote = 0;
- do {
- if ((i = write(1, buf+wrote, len-wrote)) < 0)
- return (i);
- wrote += i;
- } while (wrote<len);
- return len;
+ byte li[4];
+ li[0] = (len >> 24) & 0xff;
+ li[1] = (len >> 16) & 0xff;
+ li[2] = (len >> 8) & 0xff;
+ li[3] = len & 0xff;
+ write_exact(li, 4);
+
+ return write_exact(buf, len);
+}
+
+int read_cmd(byte *buf)
+{
+ int len;
+ if (read_exact(buf, 4) != 4)
+ return(-1);
+
+ len = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
+ return read_exact(buf, len);
}
byte static_buf[31457280]; // 30 mb
diff --git a/erts/emulator/test/beam_SUITE.erl b/erts/emulator/test/beam_SUITE.erl
index d3b3b96b14..b1f1e115ac 100644
--- a/erts/emulator/test/beam_SUITE.erl
+++ b/erts/emulator/test/beam_SUITE.erl
@@ -132,7 +132,8 @@ packed_registers(Config) when is_list(Config) ->
"_ = id(2),\n"
"id([_@Vars,_@NewVars,_@MoreNewVars]).\n"
"id(I) -> I.\n"]),
- merl:compile_and_load(Code),
+
+ merl:compile_and_load(Code, []),
%% Optionally print the generated code.
PrintCode = false, %Change to true to print code.
diff --git a/erts/emulator/test/bif_SUITE.erl b/erts/emulator/test/bif_SUITE.erl
index 5195085367..924c24b2fb 100644
--- a/erts/emulator/test/bif_SUITE.erl
+++ b/erts/emulator/test/bif_SUITE.erl
@@ -29,8 +29,8 @@
shadow_comments/1,list_to_utf8_atom/1,
specs/1,improper_bif_stubs/1,auto_imports/1,
t_list_to_existing_atom/1,os_env/1,otp_7526/1,
- binary_to_atom/1,binary_to_existing_atom/1,
- atom_to_binary/1,min_max/1, erlang_halt/1,
+ t_binary_to_atom/1,t_binary_to_existing_atom/1,
+ t_atom_to_binary/1,min_max/1, erlang_halt/1,
erl_crash_dump_bytes/1,
is_builtin/1, error_stacktrace/1,
error_stacktrace_during_call_trace/1,
@@ -39,7 +39,8 @@
process_info_blast/1,
os_env_case_sensitivity/1,
test_length/1,
- fixed_apply_badarg/1]).
+ fixed_apply_badarg/1,
+ external_fun_apply3/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -50,12 +51,12 @@ all() ->
specs, improper_bif_stubs, auto_imports,
t_list_to_existing_atom, os_env, otp_7526,
display, display_string, list_to_utf8_atom,
- atom_to_binary, binary_to_atom, binary_to_existing_atom,
+ t_atom_to_binary, t_binary_to_atom, t_binary_to_existing_atom,
erl_crash_dump_bytes, min_max, erlang_halt, is_builtin,
error_stacktrace, error_stacktrace_during_call_trace,
group_leader_prio, group_leader_prio_dirty,
is_process_alive, process_info_blast, os_env_case_sensitivity,
- test_length,fixed_apply_badarg].
+ test_length,fixed_apply_badarg,external_fun_apply3].
%% Uses erlang:display to test that erts_printf does not do deep recursion
display(Config) when is_list(Config) ->
@@ -353,8 +354,6 @@ auto_imports([], Errors) ->
extract_functions(M, Abstr) ->
[{{M,F,A},Body} || {function,_,F,A,Body} <- Abstr].
-check_stub({erlang,apply,3}, _) ->
- ok;
check_stub({_,F,A}, B) ->
try
[{clause,_,Args,[],Body}] = B,
@@ -496,7 +495,7 @@ test_7526(N) ->
-define(BADARG(E), {'EXIT',{badarg,_}} = (catch E)).
-define(SYS_LIMIT(E), {'EXIT',{system_limit,_}} = (catch E)).
-binary_to_atom(Config) when is_list(Config) ->
+t_binary_to_atom(Config) when is_list(Config) ->
HalfLong = lists:seq(0, 127),
HalfLongAtom = list_to_atom(HalfLong),
HalfLongBin = list_to_binary(HalfLong),
@@ -524,8 +523,10 @@ binary_to_atom(Config) when is_list(Config) ->
test_binary_to_atom(<<C/utf8>>, utf8)
end],
- <<"こんにちは"/utf8>> =
- atom_to_binary(test_binary_to_atom(<<"こんにちは"/utf8>>, utf8), utf8),
+ ExoticBin = <<"こんにちは"/utf8>>,
+ ExoticAtom = test_binary_to_atom(ExoticBin, utf8),
+ ExoticBin = atom_to_binary(ExoticAtom, utf8),
+ ExoticBin = atom_to_binary(ExoticAtom),
%% badarg failures.
fail_binary_to_atom(atom),
@@ -543,6 +544,7 @@ binary_to_atom(Config) when is_list(Config) ->
%% Bad UTF8 sequences.
?BADARG(binary_to_atom(id(<<255>>), utf8)),
+ ?BADARG(binary_to_atom(id(<<255>>))),
?BADARG(binary_to_atom(id(<<255,0>>), utf8)),
?BADARG(binary_to_atom(id(<<16#C0,16#80>>), utf8)), %Overlong 0.
<<B:1/binary, _/binary>> = id(<<194, 163>>), %Truncated character ERL-474
@@ -550,6 +552,7 @@ binary_to_atom(Config) when is_list(Config) ->
%% system_limit failures.
?SYS_LIMIT(binary_to_atom(id(<<0:512/unit:8,255>>), utf8)),
+ ?SYS_LIMIT(binary_to_atom(id(<<0:512/unit:8,255>>))),
?SYS_LIMIT(binary_to_atom(id(<<0:512/unit:8,255,0>>), utf8)),
?SYS_LIMIT(binary_to_atom(<<0:256/unit:8>>, latin1)),
?SYS_LIMIT(binary_to_atom(<<0:257/unit:8>>, latin1)),
@@ -562,6 +565,14 @@ binary_to_atom(Config) when is_list(Config) ->
test_binary_to_atom(Bin0, Encoding) ->
Res = binary_to_atom(Bin0, Encoding),
Res = binary_to_existing_atom(Bin0, Encoding),
+ if
+ Encoding =:= utf8;
+ Encoding =:= unicode ->
+ Res = binary_to_atom(Bin0),
+ Res = binary_to_existing_atom(Bin0);
+ true ->
+ ok
+ end,
Bin1 = id(<<7:3,Bin0/binary,32:5>>),
Sz = byte_size(Bin0),
<<_:3,UnalignedBin:Sz/binary,_:5>> = Bin1,
@@ -581,6 +592,12 @@ fail_binary_to_atom(Bin) ->
ok
end,
try
+ binary_to_atom(Bin)
+ catch
+ error:badarg ->
+ ok
+ end,
+ try
binary_to_existing_atom(Bin, latin1)
catch
error:badarg ->
@@ -591,10 +608,16 @@ fail_binary_to_atom(Bin) ->
catch
error:badarg ->
ok
+ end,
+ try
+ binary_to_existing_atom(Bin)
+ catch
+ error:badarg ->
+ ok
end.
-binary_to_existing_atom(Config) when is_list(Config) ->
+t_binary_to_existing_atom(Config) when is_list(Config) ->
UnlikelyBin = <<"ou0897979655678dsfj923874390867er869fds973qerueoru">>,
try
binary_to_existing_atom(UnlikelyBin, latin1),
@@ -609,6 +632,12 @@ binary_to_existing_atom(Config) when is_list(Config) ->
catch
error:badarg -> ok
end,
+ try
+ binary_to_existing_atom(UnlikelyBin),
+ ct:fail(atom_exists)
+ catch
+ error:badarg -> ok
+ end,
UnlikelyAtom = binary_to_atom(id(UnlikelyBin), latin1),
UnlikelyAtom = binary_to_existing_atom(UnlikelyBin, latin1),
@@ -625,7 +654,7 @@ binary_to_existing_atom(Config) when is_list(Config) ->
ok.
-atom_to_binary(Config) when is_list(Config) ->
+t_atom_to_binary(Config) when is_list(Config) ->
HalfLong = lists:seq(0, 127),
HalfLongAtom = list_to_atom(HalfLong),
HalfLongBin = list_to_binary(HalfLong),
@@ -641,12 +670,15 @@ atom_to_binary(Config) when is_list(Config) ->
LongBin = atom_to_binary(LongAtom, latin1),
%% utf8.
+ <<>> = atom_to_binary(''),
<<>> = atom_to_binary('', utf8),
<<>> = atom_to_binary('', unicode),
<<127>> = atom_to_binary('\177', utf8),
<<"abcdef">> = atom_to_binary(abcdef, utf8),
HalfLongBin = atom_to_binary(HalfLongAtom, utf8),
+ HalfLongBin = atom_to_binary(HalfLongAtom),
LongAtomBin = atom_to_binary(LongAtom, utf8),
+ LongAtomBin = atom_to_binary(LongAtom),
verify_long_atom_bin(LongAtomBin, 0),
%% Failing cases.
@@ -678,8 +710,15 @@ fail_atom_to_binary(Term) ->
catch
error:badarg ->
ok
+ end,
+ try
+ atom_to_binary(Term)
+ catch
+ error:badarg ->
+ ok
end.
+
min_max(Config) when is_list(Config) ->
a = erlang:min(id(a), a),
a = erlang:min(id(a), b),
@@ -751,7 +790,12 @@ erlang_halt(Config) when is_list(Config) ->
% This test triggers a segfault when dumping a crash dump
% to make sure that we can handle it properly.
+
+ %% Prevent address sanitizer from catching SEGV in slave node
+ AsanOpts = add_asan_opt("handle_segv=0"),
{ok,N4} = slave:start(H, halt_node4),
+ reset_asan_opts(AsanOpts),
+
CrashDump = filename:join(proplists:get_value(priv_dir,Config),
"segfault_erl_crash.dump"),
true = rpc:call(N4, os, putenv, ["ERL_CRASH_DUMP",CrashDump]),
@@ -769,6 +813,25 @@ erlang_halt(Config) when is_list(Config) ->
ok
end.
+add_asan_opt(Opt) ->
+ case test_server:is_asan() of
+ true ->
+ case os:getenv("ASAN_OPTIONS") of
+ false ->
+ os:putenv("ASAN_OPTIONS", Opt),
+ undefined;
+ AO ->
+ os:putenv("ASAN_OPTIONS", AO ++ [$: | Opt]),
+ AO
+ end;
+ _ ->
+ false
+ end.
+
+reset_asan_opts(false) -> ok;
+reset_asan_opts(undefined) -> os:unsetenv("ASAN_OPTIONS");
+reset_asan_opts(AO) -> os:putenv("ASAN_OPTIONS", AO).
+
wait_until_stable_size(_File,-10) ->
{error,enoent};
wait_until_stable_size(File,PrevSz) ->
@@ -860,6 +923,7 @@ error_stacktrace_test() ->
Types = [apply_const_last, apply_const, apply_last,
apply, double_apply_const_last, double_apply_const,
double_apply_last, double_apply, multi_apply_const_last,
+ apply_const_only, apply_only,
multi_apply_const, multi_apply_last, multi_apply,
call_const_last, call_last, call_const, call],
lists:foreach(fun (Type) ->
@@ -896,12 +960,18 @@ error_stacktrace_test() ->
Types),
ok.
-stk([], Type, Func) ->
- tail(Type, Func, jump),
- ok;
stk([_|L], Type, Func) ->
stk(L, Type, Func),
- ok.
+ %% Force the compiler to keep this body-recursive. We want the stack trace
+ %% to have one entry here and another in the base case to test that
+ %% multiple frames in the same function aren't removed unless they're
+ %% identical.
+ id(ok);
+stk([], Type, Func) ->
+ put(erlang, erlang),
+ put(tail, []),
+ tail(Type, Func, jump),
+ id(ok).
tail(Type, Func, jump) ->
tail(Type, Func, do);
@@ -910,6 +980,12 @@ tail(Type, error_1, do) ->
tail(Type, error_2, do) ->
do_error_2(Type).
+do_error_2(apply_const_only) ->
+ apply(erlang, error, [oops, [apply_const_only]]);
+do_error_2(apply_only) ->
+ Erlang = get(erlang),
+ Tail = get(tail),
+ apply(Erlang, error, [oops, [apply_only|Tail]]);
do_error_2(apply_const_last) ->
erlang:apply(erlang, error, [oops, [apply_const_last]]);
do_error_2(apply_const) ->
@@ -951,6 +1027,12 @@ do_error_2(call) ->
erlang:error(id(oops), id([call])).
+do_error_1(apply_const_only) ->
+ apply(erlang, error, [oops]);
+do_error_1(apply_only) ->
+ Erlang = get(erlang),
+ Tail = get(tail),
+ apply(Erlang, error, [oops|Tail]);
do_error_1(apply_const_last) ->
erlang:apply(erlang, error, [oops]);
do_error_1(apply_const) ->
@@ -1258,6 +1340,17 @@ fixed_apply_badarg(Config) when is_list(Config) ->
ok.
+external_fun_apply3(_Config) ->
+ %% erlang:apply/3 would always badarg when called through an external fun.
+
+ Apply = id(fun erlang:apply/3),
+ Self = Apply(erlang, self, []),
+ true = is_pid(Self),
+
+ {'EXIT',{undef,_}} = (catch Apply(does, 'not', [exist])),
+
+ ok.
+
%% helpers
id(I) -> I.
diff --git a/erts/emulator/test/binary_SUITE.erl b/erts/emulator/test/binary_SUITE.erl
index 06db9d3ff3..49d2dfb983 100644
--- a/erts/emulator/test/binary_SUITE.erl
+++ b/erts/emulator/test/binary_SUITE.erl
@@ -73,7 +73,8 @@
robustness/1,otp_8117/1,
otp_8180/1, trapping/1, large/1,
error_after_yield/1, cmp_old_impl/1,
- t2b_system_limit/1]).
+ t2b_system_limit/1,
+ term_to_iovec/1]).
%% Internal exports.
-export([sleeper/0,trapping_loop/4]).
@@ -92,8 +93,8 @@ all() ->
b2t_used_big,
bad_binary_to_term_2, safe_binary_to_term2,
bad_binary_to_term, bad_terms, t_hash, bad_size,
- sub_bin_copy,
- bad_term_to_binary, t2b_system_limit, more_bad_terms,
+ sub_bin_copy, bad_term_to_binary, t2b_system_limit,
+ term_to_iovec, more_bad_terms,
otp_5484, otp_5933,
ordering, unaligned_order, gc_test,
bit_sized_binary_sizes, otp_6817, otp_8117, deep,
@@ -456,6 +457,7 @@ bad_term_to_binary(Config) when is_list(Config) ->
T = id({a,b,c}),
{'EXIT',{badarg,_}} = (catch term_to_binary(T, not_a_list)),
{'EXIT',{badarg,_}} = (catch term_to_binary(T, [blurf])),
+ {'EXIT',{badarg,_}} = (catch term_to_binary(T, [iovec])),
{'EXIT',{badarg,_}} = (catch term_to_binary(T, [{compressed,-1}])),
{'EXIT',{badarg,_}} = (catch term_to_binary(T, [{compressed,10}])),
{'EXIT',{badarg,_}} = (catch term_to_binary(T, [{compressed,cucumber}])),
@@ -473,9 +475,7 @@ t2b_system_limit(Config) when is_list(Config) ->
memsup:get_system_memory_data()) of
Memory when is_integer(Memory),
Memory > 6*1024*1024*1024 ->
- test_t2b_system_limit(),
- garbage_collect(),
- ok;
+ do_t2b_system_limit();
_ ->
{skipped, "Not enough memory on this machine"}
end;
@@ -483,37 +483,96 @@ t2b_system_limit(Config) when is_list(Config) ->
{skipped, "Only interesting on 64-bit builds"}
end.
-test_t2b_system_limit() ->
- io:format("Creating HugeBin~n", []),
- Bits = ((1 bsl 32)+1)*8,
- HugeBin = <<0:Bits>>,
-
- io:format("Testing term_to_binary(HugeBin)~n", []),
- {'EXIT',{system_limit,[{erlang,term_to_binary,
- [HugeBin],
- _} |_]}} = (catch term_to_binary(HugeBin)),
-
- io:format("Testing term_to_binary(HugeBin, [compressed])~n", []),
- {'EXIT',{system_limit,[{erlang,term_to_binary,
- [HugeBin, [compressed]],
- _} |_]}} = (catch term_to_binary(HugeBin, [compressed])),
+do_t2b_system_limit() ->
+ F = fun() ->
+ io:format("Creating HugeBin~n", []),
+ Bits = (1 bsl 32) + 1,
+ HugeBin = <<0:Bits/unit:8>>,
+ test_t2b_system_limit(HugeBin, term_to_binary,
+ fun erlang:term_to_binary/1,
+ fun erlang:term_to_binary/2),
+ test_t2b_system_limit(HugeBin, term_to_iovec,
+ fun erlang:term_to_iovec/1,
+ fun erlang:term_to_iovec/2),
+ garbage_collect(),
+ ok
+ end,
+ Opts = [{args, "-pa " ++ filename:dirname(code:which(?MODULE))}],
+ {ok,Node} = test_server:start_node(?FUNCTION_NAME, slave, Opts),
+ erpc:call(Node, F).
+
+test_t2b_system_limit(HugeBin, Name, F1, F2) ->
+ io:format("Testing ~p(HugeBin)~n", [Name]),
+ {'EXIT',{system_limit,[{erlang,Name,[HugeBin],_}|_]}} =
+ t2b_eval(fun() -> F1(HugeBin) end),
+
+ io:format("Testing ~p(HugeBin, [compressed])~n", [Name]),
+ {'EXIT',{system_limit,[{erlang,Name,[HugeBin,[compressed]],_}|_]}} =
+ t2b_eval(fun() -> F2(HugeBin, [compressed]) end),
%% Check that it works also after we have trapped...
io:format("Creating HugeListBin~n", []),
- HugeListBin = [lists:duplicate(2000000,2000000), HugeBin],
+ HugeListBin = [lists:duplicate(2000000, 2000000), HugeBin],
- io:format("Testing term_to_binary(HugeListBin)~n", []),
- {'EXIT',{system_limit,[{erlang,term_to_binary,
- [HugeListBin],
- _} |_]}} = (catch term_to_binary(HugeListBin)),
+ io:format("Testing ~p(HugeListBin)~n", [Name]),
+ {'EXIT',{system_limit,[{erlang,Name,[HugeListBin],_}|_]}} =
+ t2b_eval(fun() -> F1(HugeListBin) end),
- io:format("Testing term_to_binary(HugeListBin, [compressed])~n", []),
- {'EXIT',{system_limit,[{erlang,term_to_binary,
- [HugeListBin, [compressed]],
- _} |_]}} = (catch term_to_binary(HugeListBin, [compressed])),
+ io:format("Testing ~p(HugeListBin, [compressed])~n", [Name]),
+ {'EXIT',{system_limit,[{erlang,Name,[HugeListBin,[compressed]],_}|_]}} =
+ t2b_eval(fun() -> F2(HugeListBin, [compressed]) end),
ok.
+t2b_eval(F) ->
+ Result = (catch F()),
+ io:put_chars(io_lib:format("~P\n", [Result,100])),
+ Result.
+
+term_to_iovec(Config) when is_list(Config) ->
+ Bin = list_to_binary(lists:duplicate(1000,100)),
+ Bin2 = list_to_binary(lists:duplicate(65,100)),
+ check_term_to_iovec({[Bin, atom, Bin, 1244, make_ref(), Bin]}),
+ check_term_to_iovec(Bin),
+ check_term_to_iovec([Bin,Bin,Bin,Bin]),
+ check_term_to_iovec(blipp),
+ check_term_to_iovec(lists:duplicate(1000,100)),
+ check_term_to_iovec([[Bin2]]),
+ check_term_to_iovec([erlang:ports(), Bin, erlang:processes()]),
+ ok.
+
+check_term_to_iovec(Term) ->
+ IoVec1 = erlang:term_to_iovec(Term),
+ ok = check_is_iovec(IoVec1),
+ IoVec2 = erlang:term_to_iovec(Term, []),
+ ok = check_is_iovec(IoVec2),
+ B = erlang:term_to_binary(Term),
+ IoVec1Bin = erlang:iolist_to_binary(IoVec1),
+ IoVec2Bin = erlang:iolist_to_binary(IoVec2),
+ try
+ B = IoVec1Bin
+ catch
+ _:_ ->
+ io:format("Binary: ~p~n", [B]),
+ io:format("I/O vec1 binary: ~p~n", [IoVec1Bin]),
+ io:format("I/O vec1: ~p~n", [IoVec1]),
+ ct:fail(not_same_result)
+ end,
+ try
+ B = IoVec2Bin
+ catch
+ _:_ ->
+ io:format("Binary: ~p~n", [B]),
+ io:format("I/O vec2 binary: ~p~n", [IoVec2Bin]),
+ io:format("I/O vec2: ~p~n", [IoVec2]),
+ ct:fail(not_same_result)
+ end.
+
+check_is_iovec([]) ->
+ ok;
+check_is_iovec([B|Bs]) when is_binary(B) ->
+ check_is_iovec(Bs).
+
%% Tests binary_to_term/1 and term_to_binary/1.
terms(Config) when is_list(Config) ->
@@ -1862,9 +1921,16 @@ huge_iolist(X, Sz, Lim) ->
huge_iolist([X, X], Sz*2, Lim).
cmp_node(Node, {M, F, A}) ->
- Res = rpc:call(Node, M, F, A),
+ ResN = rpc:call(Node, M, F, A),
Res = apply(M, F, A),
- ok.
+ case ResN =:= Res of
+ true ->
+ ok;
+ false ->
+ io:format("~p: ~p~n~p: ~p~n",
+ [Node, ResN, node(), Res]),
+ ct:fail(different_results)
+ end.
make_sub_binary(Bin) when is_binary(Bin) ->
{_,B} = split_binary(list_to_binary([0,1,3,Bin]), 3),
diff --git a/erts/emulator/test/bs_bincomp_SUITE.erl b/erts/emulator/test/bs_bincomp_SUITE.erl
index c481e93e41..2dcebc2ef9 100644
--- a/erts/emulator/test/bs_bincomp_SUITE.erl
+++ b/erts/emulator/test/bs_bincomp_SUITE.erl
@@ -112,7 +112,14 @@ mixed(Config) when is_list(Config) ->
[(X+Y) || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, <<Y:3>> <= <<1:3,2:3>>],
[2,3,3,4,4,5,5,6] =
[(X+Y) || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, Y <- [1,2]],
- ok.
+
+ %% OTP-16899: Nested binary comprehensions would fail to load.
+ <<0,1,0,2,0,3,99>> = mixed_nested([1,2,3]),
+
+ ok.
+
+mixed_nested(L) ->
+ << << << << E:16 >> || E <- L >> || true >>/binary, 99:(id(8))>>.
%% OTP-8179: Call tracing on binary comprehensions would cause a crash.
tracing(Config) when is_list(Config) ->
@@ -148,3 +155,8 @@ tracer(Parent, N) ->
tracer(Parent, N+1)
end
end.
+
+%%% Common utilities.
+
+id(I) ->
+ I.
diff --git a/erts/emulator/test/bs_construct_SUITE.erl b/erts/emulator/test/bs_construct_SUITE.erl
index 29d9c5a74e..25549a4b73 100644
--- a/erts/emulator/test/bs_construct_SUITE.erl
+++ b/erts/emulator/test/bs_construct_SUITE.erl
@@ -27,7 +27,8 @@
mem_leak/1, coerce_to_float/1, bjorn/1, append_empty_is_same/1,
huge_float_field/1, system_limit/1, badarg/1,
copy_writable_binary/1, kostis/1, dynamic/1, bs_add/1,
- otp_7422/1, zero_width/1, bad_append/1, bs_append_overflow/1]).
+ otp_7422/1, zero_width/1, bad_append/1, bs_append_overflow/1,
+ reductions/1]).
-include_lib("common_test/include/ct.hrl").
@@ -40,7 +41,7 @@ all() ->
in_guard, mem_leak, coerce_to_float, bjorn, append_empty_is_same,
huge_float_field, system_limit, badarg,
copy_writable_binary, kostis, dynamic, bs_add, otp_7422, zero_width,
- bad_append, bs_append_overflow].
+ bad_append, bs_append_overflow, reductions].
init_per_suite(Config) ->
Config.
@@ -889,6 +890,33 @@ bs_append_overflow_unsigned() ->
C = <<A/binary,1,B/binary>>,
true = byte_size(B) < byte_size(C).
+reductions(_Config) ->
+ TwoMeg = <<0:(2_000*1024)/unit:8>>,
+ reds_at_least(2000, fun() -> <<0:8,TwoMeg/binary>> end),
+ reds_at_least(4000, fun() -> <<0:8,TwoMeg/binary,TwoMeg/binary>> end),
+ reds_at_least(1000, fun() -> <<0:8,TwoMeg:(1000*1024)/binary>> end),
+
+ %% Here we expect about 500 reductions in the bs_append
+ %% instruction for setting up a writable binary and about 2000
+ %% reductions in the bs_put_binary instruction for copying the
+ %% binary data.
+ reds_at_least(2500, fun() -> <<TwoMeg/binary,TwoMeg:(2000*1024)/binary>> end),
+ ok.
+
+reds_at_least(N, Fun) ->
+ receive after 1 -> ok end,
+ {reductions,Red0} = process_info(self(), reductions),
+ _ = Fun(),
+ {reductions,Red1} = process_info(self(), reductions),
+ Diff = Red1 - Red0,
+ io:format("Expected at least ~p; got ~p\n", [N,Diff]),
+ if
+ Diff >= N ->
+ ok;
+ Diff ->
+ ct:fail({expected,N,got,Diff})
+ end.
+
id(I) -> I.
memsize() ->
diff --git a/erts/emulator/test/call_trace_SUITE.erl b/erts/emulator/test/call_trace_SUITE.erl
index 79eb378c35..3b5f845629 100644
--- a/erts/emulator/test/call_trace_SUITE.erl
+++ b/erts/emulator/test/call_trace_SUITE.erl
@@ -832,21 +832,27 @@ deep_exception() ->
R1 -> ct:fail({returned,abbr(R1)})
catch error:badarg -> ok
end,
- expect(fun ({trace,S,call,{lists,reverse,[L1,L2]}})
+ expect(fun ({trace,S,call,{lists,reverse,[L1,L2]}}, Traps)
when is_list(L1), is_list(L2), S == Self ->
- next;
+ %% Each trapping call to reverse/2 must have a corresponding
+ %% exception_from
+ {next, Traps + 1};
({trace,S,exception_from,
- {lists,reverse,2},{error,badarg}})
+ {lists,reverse,2},{error,badarg}}, Traps)
+ when S == Self, Traps > 1 ->
+ {next, Traps - 1};
+ ({trace,S,exception_from,
+ {lists,reverse,2},{error,badarg}}, 1)
when S == Self ->
expected;
- ('_') ->
+ ('_', _Traps) ->
{trace,Self,exception_from,
{lists,reverse,2},{error,badarg}};
- (_) ->
+ (_, _Traps) ->
{unexpected,
{trace,Self,exception_from,
{lists,reverse,2},{error,badarg}}}
- end),
+ end, 0),
deep_exception(?LINE, deep_5, [1,2], 7,
[{trace,Self,call,{erlang,error,[undef]}},
{trace,Self,exception_from,{erlang,error,1},
@@ -896,21 +902,27 @@ deep_exception() ->
R2 -> ct:fail({returned,abbr(R2)})
catch error:badarg -> ok
end,
- expect(fun ({trace,S,call,{lists,reverse,[L1,L2]}})
+ expect(fun ({trace,S,call,{lists,reverse,[L1,L2]}}, Traps)
when is_list(L1), is_list(L2), S == Self ->
- next;
+ %% Each trapping call to reverse/2 must have a corresponding
+ %% exception_from
+ {next, Traps + 1};
+ ({trace,S,exception_from,
+ {lists,reverse,2},{error,badarg}}, Traps)
+ when S == Self, Traps > 1 ->
+ {next, Traps - 1};
({trace,S,exception_from,
- {lists,reverse,2},{error,badarg}})
+ {lists,reverse,2},{error,badarg}}, 1)
when S == Self ->
expected;
- ('_') ->
+ ('_', _Traps) ->
{trace,Self,exception_from,
{lists,reverse,2},{error,badarg}};
- (_) ->
+ (_, _Traps) ->
{unexpected,
{trace,Self,exception_from,
{lists,reverse,2},{error,badarg}}}
- end),
+ end, 0),
deep_exception(?LINE, apply, [?MODULE,deep_5,[1,2]], 7,
[{trace,Self,call,{erlang,error,[undef]}},
{trace,Self,exception_from,{erlang,error,1},
@@ -975,21 +987,27 @@ deep_exception() ->
R3 -> ct:fail({returned,abbr(R3)})
catch error:badarg -> ok
end,
- expect(fun ({trace,S,call,{lists,reverse,[L1,L2]}})
+ expect(fun ({trace,S,call,{lists,reverse,[L1,L2]}}, Traps)
when is_list(L1), is_list(L2), S == Self ->
- next;
+ %% Each trapping call to reverse/2 must have a corresponding
+ %% exception_from
+ {next, Traps + 1};
+ ({trace,S,exception_from,
+ {lists,reverse,2},{error,badarg}}, Traps)
+ when S == Self, Traps > 1 ->
+ {next, Traps - 1};
({trace,S,exception_from,
- {lists,reverse,2},{error,badarg}})
+ {lists,reverse,2},{error,badarg}}, 1)
when S == Self ->
expected;
- ('_') ->
+ ('_', _Traps) ->
{trace,Self,exception_from,
{lists,reverse,2},{error,badarg}};
- (_) ->
+ (_, _Traps) ->
{unexpected,
{trace,Self,exception_from,
{lists,reverse,2},{error,badarg}}}
- end),
+ end, 0),
deep_exception(?LINE, apply,
[fun () -> ?MODULE:deep_5(1,2) end, []], 7,
[{trace,Self,call,{erlang,error,[undef]}},
@@ -1249,6 +1267,24 @@ expect(Message) ->
ct:fail(no_trace_message)
end.
+expect(Validator, State0) when is_function(Validator) ->
+ receive
+ M ->
+ case Validator(M, State0) of
+ expected ->
+ ok = io:format("Expected and got ~p", [abbr(M)]);
+ {next, State} ->
+ ok = io:format("Expected and got ~p", [abbr(M)]),
+ expect(Validator, State);
+ {unexpected,Message} ->
+ io:format("Expected ~p; got ~p", [abbr(Message),abbr(M)]),
+ ct:fail({unexpected,abbr([M|flush()])})
+ end
+ after 5000 ->
+ io:format("Expected ~p; got nothing", [abbr(Validator('_'))]),
+ ct:fail(no_trace_message)
+ end.
+
trace_info(What, Key) ->
get(tracer) ! {apply,self(),{erlang,trace_info,[What,Key]}},
Res = receive
diff --git a/erts/emulator/test/code_SUITE_data/call_purged_fun_tester.erl b/erts/emulator/test/code_SUITE_data/call_purged_fun_tester.erl
index 699f0c1161..b08cd5e654 100644
--- a/erts/emulator/test/code_SUITE_data/call_purged_fun_tester.erl
+++ b/erts/emulator/test/code_SUITE_data/call_purged_fun_tester.erl
@@ -8,8 +8,7 @@
do(Priv, Data, Type, Opts) ->
try do_it(Priv, Data, Type, Opts)
catch
- C:E ->
- ST = erlang:get_stacktrace(),
+ C:E:ST ->
io:format("Caught exception from line ~p:\n~p\n",
[get(the_line), ST]),
io:format("Message queue: ~p\n", [process_info(self(), messages)]),
diff --git a/erts/emulator/test/counters_SUITE.erl b/erts/emulator/test/counters_SUITE.erl
index b3f0358c1e..0eb0599e32 100644
--- a/erts/emulator/test/counters_SUITE.erl
+++ b/erts/emulator/test/counters_SUITE.erl
@@ -130,7 +130,7 @@ limits_do(Ref) ->
%% Verify that independent workers, using different counters
%% within the same array, do not interfere with each other.
indep(Config) when is_list(Config) ->
- NScheds = erlang:system_info(schedulers),
+ NScheds = erlang:system_info(schedulers_online),
Ref = counters:new(NScheds,[write_concurrency]),
Rounds = 100,
Papa = self(),
@@ -182,7 +182,7 @@ indep_subber(_Ref, _I, Val) ->
write_concurrency(Config) when is_list(Config) ->
rand:seed(exs1024s),
io:format("*** SEED: ~p ***\n", [rand:export_seed()]),
- NScheds = erlang:system_info(schedulers),
+ NScheds = erlang:system_info(schedulers_online),
Size = 100,
Ref = counters:new(Size,[write_concurrency]),
Rounds = 1000,
diff --git a/erts/emulator/test/decode_packet_SUITE.erl b/erts/emulator/test/decode_packet_SUITE.erl
index ef13b515fb..099dfabcb6 100644
--- a/erts/emulator/test/decode_packet_SUITE.erl
+++ b/erts/emulator/test/decode_packet_SUITE.erl
@@ -301,8 +301,8 @@ http(Config) when is_list(Config) ->
StrA = list_to_atom(Str),
StrB = list_to_binary(Str),
Bin = <<StrB/binary,": ",ValB/binary,"\r\n",Rest/binary>>,
- {ok, {http_header,N,StrA,undefined,Val}, Rest} = decode_pkt(httph,Bin),
- {ok, {http_header,N,StrA,undefined,ValB}, Rest} = decode_pkt(httph_bin,Bin),
+ {ok, {http_header,N,StrA,Str,Val}, Rest} = decode_pkt(httph,Bin),
+ {ok, {http_header,N,StrA,StrB,ValB}, Rest} = decode_pkt(httph_bin,Bin),
N + 1
end,
lists:foldl(HdrF, 1, http_hdr_strings()),
@@ -330,6 +330,15 @@ http(Config) when is_list(Config) ->
%% Response with empty phrase
{ok,{http_response,{1,1},200,[]},<<>>} = decode_pkt(http, <<"HTTP/1.1 200\r\n">>, []),
{ok,{http_response,{1,1},200,<<>>},<<>>} = decode_pkt(http_bin, <<"HTTP/1.1 200\r\n">>, []),
+
+
+ %% Test error cases
+ {ok,{http_error,"Host\t: localhost:8000\r\n"},<<"a">>} =
+ decode_pkt(httph, <<"Host\t: localhost:8000\r\na">>, []),
+ {ok,{http_error,"Host : localhost:8000\r\n"},<<"a">>} =
+ decode_pkt(httph, <<"Host : localhost:8000\r\na">>, []),
+ {ok,{http_error," : localhost:8000\r\n"},<<"a">>} =
+ decode_pkt(httph, <<" : localhost:8000\r\na">>, []),
ok.
http_with_bin(http) ->
@@ -364,31 +373,40 @@ http_request(Msg) ->
{http_request, 'POST', {abs_path, "/invalid/url" }, {1,1}},
{http_request, 'POST', {abs_path,<<"/invalid/url">>}, {1,1}}},
{"Connection: close\r\n",
- {http_header,2,'Connection',undefined, "close"},
- {http_header,2,'Connection',undefined,<<"close">>}},
- {"Host\t : localhost:8000\r\n", % white space before :
- {http_header,14,'Host',undefined, "localhost:8000"},
- {http_header,14,'Host',undefined,<<"localhost:8000">>}},
+ {http_header,2,'Connection', "Connection" , "close"},
+ {http_header,2,'Connection',<<"Connection">>,<<"close">>}},
{"User-Agent: perl post\r\n",
- {http_header,24,'User-Agent',undefined, "perl post"},
- {http_header,24,'User-Agent',undefined,<<"perl post">>}},
+ {http_header,24,'User-Agent', "User-Agent" , "perl post"},
+ {http_header,24,'User-Agent',<<"User-Agent">>,<<"perl post">>}},
{"Content-Length: 4\r\n",
- {http_header,38,'Content-Length',undefined, "4"},
- {http_header,38,'Content-Length',undefined,<<"4">>}},
+ {http_header,38,'Content-Length', "Content-Length" , "4"},
+ {http_header,38,'Content-Length',<<"Content-Length">>,<<"4">>}},
{"Content-Type: text/xml; charset=utf-8\r\n",
- {http_header,42,'Content-Type',undefined, "text/xml; charset=utf-8"},
- {http_header,42,'Content-Type',undefined,<<"text/xml; charset=utf-8">>}},
+ {http_header,42,'Content-Type', "Content-Type" , "text/xml; charset=utf-8"},
+ {http_header,42,'Content-Type',<<"Content-Type">>,<<"text/xml; charset=utf-8">>}},
{"Other-Field: with some text\r\n",
- {http_header,0, "Other-Field" ,undefined, "with some text"},
- {http_header,0,<<"Other-Field">>,undefined,<<"with some text">>}},
+ {http_header,0, "Other-Field" , "Other-Field" , "with some text"},
+ {http_header,0,<<"Other-Field">>,<<"Other-Field">>,<<"with some text">>}},
+ {"Content--Type: text/xml; charset=utf-8\r\n",
+ {http_header,0, "Content--type" , "Content--Type" , "text/xml; charset=utf-8"},
+ {http_header,0,<<"Content--type">>,<<"Content--Type">>,<<"text/xml; charset=utf-8">>}},
+ {"Content---Type: text/xml; charset=utf-8\r\n",
+ {http_header,0, "Content---Type" , "Content---Type" , "text/xml; charset=utf-8"},
+ {http_header,0,<<"Content---Type">>,<<"Content---Type">>,<<"text/xml; charset=utf-8">>}},
+ {"CONTENT-type: text/xml; charset=utf-8\r\n",
+ {http_header,42,'Content-Type', "CONTENT-type" , "text/xml; charset=utf-8"},
+ {http_header,42,'Content-Type',<<"CONTENT-type">>,<<"text/xml; charset=utf-8">>}},
+ {"OTHER-field: with some text\r\n",
+ {http_header,0, "Other-Field" , "OTHER-field" , "with some text"},
+ {http_header,0,<<"Other-Field">>,<<"OTHER-field">>,<<"with some text">>}},
{"Make-sure-a-LONG-HEaDer-fIeLd-is-fORMATTED-NicelY: with some text\r\n",
- {http_header,0, "Make-Sure-A-Long-Header-Field-Is-Formatted-Nicely" ,undefined, "with some text"},
- {http_header,0,<<"Make-Sure-A-Long-Header-Field-Is-Formatted-Nicely">>,undefined,<<"with some text">>}},
+ {http_header,0, "Make-Sure-A-Long-Header-Field-Is-Formatted-Nicely" , "Make-sure-a-LONG-HEaDer-fIeLd-is-fORMATTED-NicelY" , "with some text"},
+ {http_header,0,<<"Make-Sure-A-Long-Header-Field-Is-Formatted-Nicely">>,<<"Make-sure-a-LONG-HEaDer-fIeLd-is-fORMATTED-NicelY">>,<<"with some text">>}},
{"Multi-Line: Once upon a time in a land far far away,\r\n"
" there lived a princess imprisoned in the highest tower\r\n"
" of the most haunted castle.\r\n",
- {http_header,0, "Multi-Line" ,undefined, "Once upon a time in a land far far away,\r\n there lived a princess imprisoned in the highest tower\r\n of the most haunted castle."},
- {http_header,0,<<"Multi-Line">>,undefined,<<"Once upon a time in a land far far away,\r\n there lived a princess imprisoned in the highest tower\r\n of the most haunted castle.">>}},
+ {http_header,0, "Multi-Line" , "Multi-Line" , "Once upon a time in a land far far away,\r\n there lived a princess imprisoned in the highest tower\r\n of the most haunted castle."},
+ {http_header,0,<<"Multi-Line">>,<<"Multi-Line">>,<<"Once upon a time in a land far far away,\r\n there lived a princess imprisoned in the highest tower\r\n of the most haunted castle.">>}},
{"\r\n",
http_eoh,
http_eoh}],
@@ -404,17 +422,17 @@ http_response(Msg) ->
{http_response, {1,0}, 404, "Object Not Found"},
{http_response, {1,0}, 404, <<"Object Not Found">>}},
{"Server: inets/4.7.16\r\n",
- {http_header, 30, 'Server', undefined, "inets/4.7.16"},
- {http_header, 30, 'Server', undefined, <<"inets/4.7.16">>}},
+ {http_header, 30, 'Server', "Server" , "inets/4.7.16"},
+ {http_header, 30, 'Server', <<"Server">>, <<"inets/4.7.16">>}},
{"Date: Fri, 04 Jul 2008 17:16:22 GMT\r\n",
- {http_header, 3, 'Date', undefined, "Fri, 04 Jul 2008 17:16:22 GMT"},
- {http_header, 3, 'Date', undefined, <<"Fri, 04 Jul 2008 17:16:22 GMT">>}},
+ {http_header, 3, 'Date', "Date" , "Fri, 04 Jul 2008 17:16:22 GMT"},
+ {http_header, 3, 'Date', <<"Date">>, <<"Fri, 04 Jul 2008 17:16:22 GMT">>}},
{"Content-Type: text/html\r\n",
- {http_header, 42, 'Content-Type', undefined, "text/html"},
- {http_header, 42, 'Content-Type', undefined, <<"text/html">>}},
+ {http_header, 42, 'Content-Type', "Content-Type" , "text/html"},
+ {http_header, 42, 'Content-Type', <<"Content-Type">>, <<"text/html">>}},
{"Content-Length: 207\r\n",
- {http_header, 38, 'Content-Length', undefined, "207"},
- {http_header, 38, 'Content-Length', undefined, <<"207">>}},
+ {http_header, 38, 'Content-Length', "Content-Length" , "207"},
+ {http_header, 38, 'Content-Length', <<"Content-Length">>, <<"207">>}},
{"\r\n",
http_eoh,
http_eoh}],
@@ -542,7 +560,7 @@ otp_8536_do(N) ->
Bin = <<Hdr/binary, ": ", Data/binary, "\r\n\r\n">>,
io:format("Bin='~p'\n",[Bin]),
- {ok,{http_header,0,Hdr2,undefined,Data2},<<"\r\n">>} = decode_pkt(httph_bin, Bin, []),
+ {ok,{http_header,0,Hdr2,Hdr2,Data2},<<"\r\n">>} = decode_pkt(httph_bin, Bin, []),
%% Do something to trash the C-stack, how about another decode_packet:
decode_pkt(httph_bin,<<Letters/binary, ": ", Data/binary, "\r\n\r\n">>, []),
diff --git a/erts/emulator/test/dirty_bif_SUITE.erl b/erts/emulator/test/dirty_bif_SUITE.erl
index aedcc1dc35..f8819b4793 100644
--- a/erts/emulator/test/dirty_bif_SUITE.erl
+++ b/erts/emulator/test/dirty_bif_SUITE.erl
@@ -397,7 +397,9 @@ dirty_process_trace(Config) when is_list(Config) ->
access_dirty_process(
Config,
fun() ->
- erlang:trace_pattern({erts_debug,dirty_io,2},
+ %% BIFs can only be traced when their modules are loaded.
+ code:ensure_loaded(erts_debug),
+ 1 = erlang:trace_pattern({erts_debug,dirty_io,2},
[{'_',[],[{return_trace}]}],
[local,meta]),
ok
diff --git a/erts/emulator/test/distribution_SUITE.erl b/erts/emulator/test/distribution_SUITE.erl
index 25e6ea89c8..128c9b07e0 100644
--- a/erts/emulator/test/distribution_SUITE.erl
+++ b/erts/emulator/test/distribution_SUITE.erl
@@ -68,7 +68,14 @@
message_latency_large_link_exit/1,
message_latency_large_monitor_exit/1,
message_latency_large_exit2/1,
- system_limit/1]).
+ message_latency_large_message/0,
+ message_latency_large_link_exit/0,
+ message_latency_large_monitor_exit/0,
+ message_latency_large_exit2/0,
+ system_limit/1,
+ hopefull_data_encoding/1,
+ hopefull_export_fun_bug/1,
+ huge_iovec/1]).
%% Internal exports.
-export([sender/3, receiver2/2, dummy_waiter/0, dead_process/0,
@@ -97,7 +104,9 @@ all() ->
contended_atom_cache_entry, contended_unicode_atom_cache_entry,
{group, message_latency},
{group, bad_dist}, {group, bad_dist_ext},
- start_epmd_false, epmd_module, system_limit].
+ start_epmd_false, epmd_module, system_limit,
+ hopefull_data_encoding, hopefull_export_fun_bug,
+ huge_iovec].
groups() ->
[{bulk_send, [], [bulk_send_small, bulk_send_big, bulk_send_bigbig]},
@@ -1406,7 +1415,6 @@ get_conflicting_unicode_atoms(CIX, N) ->
%% kept as they continue to find bugs in the distribution implementation.
message_latency_large_message(Config) when is_list(Config) ->
measure_latency_large_message(?FUNCTION_NAME, fun(Dropper, Payload) -> Dropper ! Payload end).
-
message_latency_large_exit2(Config) when is_list(Config) ->
measure_latency_large_message(?FUNCTION_NAME, fun erlang:exit/2).
@@ -1414,10 +1422,20 @@ message_latency_large_link_exit(Config) when is_list(Config) ->
message_latency_large_exit(?FUNCTION_NAME, fun erlang:link/1).
message_latency_large_monitor_exit(Config) when is_list(Config) ->
- message_latency_large_exit(?FUNCTION_NAME, fun(Dropper) ->
- Dropper ! {monitor, self()},
- receive ok -> ok end
- end).
+ message_latency_large_exit(?FUNCTION_NAME,
+ fun(Dropper) ->
+ Dropper ! {monitor, self()},
+ receive ok -> ok end
+ end).
+
+message_latency_large_message() ->
+ [{timetrap, {minutes, 6}}].
+message_latency_large_exit2() ->
+ message_latency_large_message().
+message_latency_large_link_exit() ->
+ message_latency_large_message().
+message_latency_large_monitor_exit() ->
+ message_latency_large_message().
message_latency_large_exit(Nodename, ReasonFun) ->
measure_latency_large_message(
@@ -1499,18 +1517,19 @@ measure_latency_large_message(Nodename, DataFun) ->
measure_latency(DataFun, Dropper, Echo, Payload) ->
+ TCProc = self(),
+
flush(),
Senders = [spawn_monitor(
fun F() ->
DataFun(Dropper, Payload),
- receive
- die -> ok
- after 0 ->
- F()
- end
+ F()
end) || _ <- lists:seq(1,2)],
+ %% Link in order to cleanup properly if TC crashes
+ [link(Sender) || {Sender,_} <- Senders],
+
wait_for_busy_dist(2 * 60 * 1000, 10),
{TS, Times} =
@@ -1527,7 +1546,8 @@ measure_latency(DataFun, Dropper, Echo, Payload) ->
ct:pal("Times: Avg: ~p Max: ~p Min: ~p Var: ~p",
[Avg, lists:max(Times), lists:min(Times), StdDev]),
[begin
- Sender ! die,
+ unlink(Sender),
+ exit(Sender,die),
receive
{'DOWN', Ref, process, _, _} ->
ok
@@ -1730,14 +1750,14 @@ start_monitor(Offender,P) ->
just_stay_alive -> ok
end
end),
- Ref = receive
+ Res = receive
{Q,ref,R} ->
- R
+ {Q, R}
after 5000 ->
error
end,
- io:format("Ref is ~p~n",[Ref]),
- ok.
+ io:format("Res is ~p~n",[Res]),
+ Res.
start_link(Offender,P) ->
Parent = self(),
Q = spawn(Offender,
@@ -1749,14 +1769,14 @@ start_link(Offender,P) ->
just_stay_alive -> ok
end
end),
- Ref = receive
+ Res = receive
{Q,ref,R} ->
R
after 5000 ->
error
end,
- io:format("Ref is ~p~n",[Ref]),
- ok.
+ io:format("Res is ~p~n",[Res]),
+ Res.
%% Test dist messages with valid structure (binary to term ok) but malformed control content
bad_dist_structure(Config) when is_list(Config) ->
@@ -1904,20 +1924,38 @@ bad_dist_fragments(Config) when is_list(Config) ->
[{hdr, 3, binary:part(Msg, 10,byte_size(Msg)-10)},
close]),
- start_monitor(Offender,P),
- ExitVictim = spawn(Victim, fun() -> receive ok -> ok end end),
- send_bad_fragments(Offender, Victim, P,{?DOP_PAYLOAD_EXIT,P,ExitVictim},2,
+ ExitVictim = spawn(Victim, fun() ->
+ receive
+ {link, Proc} ->
+ link(Proc),
+ Parent ! {self(), linked}
+ end,
+ receive ok -> ok end
+ end),
+ OP1 = start_link(Offender,ExitVictim),
+ ExitVictim ! {link, OP1},
+ receive {ExitVictim, linked} -> ok end,
+ send_bad_fragments(Offender, Victim, ExitVictim,{?DOP_PAYLOAD_EXIT,OP1,ExitVictim},0,
[{hdr, 1, [131]}]),
- start_monitor(Offender,P),
Exit2Victim = spawn(Victim, fun() -> receive ok -> ok end end),
- send_bad_fragments(Offender, Victim, P,{?DOP_PAYLOAD_EXIT2,P,Exit2Victim},2,
+ {OP2, _} = start_monitor(Offender,Exit2Victim),
+ send_bad_fragments(Offender, Victim, Exit2Victim,{?DOP_PAYLOAD_EXIT2,OP2,Exit2Victim},0,
[{hdr, 1, [132]}]),
- start_monitor(Offender,P),
- DownVictim = spawn(Victim, fun() -> receive ok -> ok end end),
- DownRef = erlang:monitor(process, DownVictim),
- send_bad_fragments(Offender, Victim, P,{?DOP_PAYLOAD_MONITOR_P_EXIT,P,DownVictim,DownRef},2,
+ DownVictim = spawn(Victim, fun() ->
+ receive
+ {monitor, Proc} ->
+ DR = erlang:monitor(process, Proc),
+ Parent ! {self(), DR}
+ end,
+ Parent ! {self, DR},
+ receive ok -> ok end
+ end),
+ {OP3, _} = start_monitor(Offender,DownVictim),
+ DownVictim ! {monitor, OP3},
+ DownRef = receive {DownVictim, DR} -> DR end,
+ send_bad_fragments(Offender, Victim, DownVictim,{?DOP_PAYLOAD_MONITOR_P_EXIT,OP3,DownVictim,DownRef},0,
[{hdr, 1, [133]}]),
P ! two,
@@ -2567,6 +2605,212 @@ address_please(_Name, _Address, _AddressFamily) ->
IP = {127,0,0,1},
{ok, IP}.
+hopefull_data_encoding(Config) when is_list(Config) ->
+ test_hopefull_data_encoding(Config, true),
+ test_hopefull_data_encoding(Config, false).
+
+test_hopefull_data_encoding(Config, Fallback) when is_list(Config) ->
+ {ok, ProxyNode} = start_node(hopefull_data_normal),
+ {ok, BouncerNode} = start_node(hopefull_data_bouncer, "-hidden"),
+ case Fallback of
+ false ->
+ ok;
+ true ->
+ rpc:call(BouncerNode, erts_debug, set_internal_state,
+ [available_internal_state, true]),
+ false = rpc:call(BouncerNode, erts_debug, set_internal_state,
+ [remove_hopefull_dflags, true])
+ end,
+ Tester = self(),
+ R1 = make_ref(),
+ R2 = make_ref(),
+ R3 = make_ref(),
+ Bouncer = spawn_link(BouncerNode, fun () -> bounce_loop() end),
+ Proxy = spawn_link(ProxyNode,
+ fun () ->
+ register(bouncer, self()),
+ %% We create the data on the proxy node in order
+ %% to create the correct sub binaries
+ HData = mk_hopefull_data(R1, Tester),
+ %% Verify same result between this node and tester
+ Tester ! [R1, HData],
+ %% Test when connection has not been setup yet
+ Bouncer ! {Tester, [R2, HData]},
+ Sync = make_ref(),
+ Bouncer ! {self(), Sync},
+ receive Sync -> ok end,
+ %% Test when connection is already up
+ Bouncer ! {Tester, [R3, HData]},
+ receive after infinity -> ok end
+ end),
+ HData =
+ receive
+ [R1, HData1] ->
+ HData1
+ end,
+ receive
+ [R2, HData2] ->
+ case Fallback of
+ false ->
+ HData = HData2;
+ true ->
+ check_hopefull_fallback_data(HData, HData2)
+ end
+ end,
+ receive
+ [R3, HData3] ->
+ case Fallback of
+ false ->
+ HData = HData3;
+ true ->
+ check_hopefull_fallback_data(HData, HData3)
+ end
+ end,
+ unlink(Proxy),
+ exit(Proxy, bye),
+ unlink(Bouncer),
+ exit(Bouncer, bye),
+ stop_node(ProxyNode),
+ stop_node(BouncerNode),
+ ok.
+
+bounce_loop() ->
+ receive
+ {SendTo, Data} ->
+ SendTo ! Data
+ end,
+ bounce_loop().
+
+mk_hopefull_data(RemoteRef, RemotePid) ->
+ HugeBs = list_to_bitstring([lists:duplicate(12*1024*1024, 85), <<6:6>>]),
+ <<_:1/bitstring,HugeBs2/bitstring>> = HugeBs,
+ mk_hopefull_data(list_to_binary(lists:seq(1,255))) ++
+ [1234567890, HugeBs, fun gurka:banan/3, fun erlang:node/1,
+ RemotePid, self(), fun erlang:self/0] ++
+ mk_hopefull_data(list_to_binary(lists:seq(1,32))) ++
+ [an_atom,
+ fun lists:reverse/1, RemoteRef, make_ref(), HugeBs2,
+ fun blipp:blapp/7].
+
+mk_hopefull_data(BS) ->
+ BSsz = bit_size(BS),
+ lists:concat(
+ [lists:map(fun (Offset) ->
+ <<NewBs:Offset/bitstring, _/bitstring>> = BS,
+ NewBs
+ end, lists:seq(1, 16)),
+ lists:map(fun (Offset) ->
+ <<_:Offset/bitstring, NewBs/bitstring>> = BS,
+ NewBs
+ end, lists:seq(1, 16)),
+ lists:map(fun (Offset) ->
+ <<NewBs:Offset/bitstring, _/bitstring>> = BS,
+ NewBs
+ end, lists:seq(BSsz-16, BSsz-1)),
+ lists:map(fun (Offset) ->
+ PreOffset = Offset rem 16,
+ <<_:PreOffset/bitstring, NewBs:Offset/bitstring, _/bitstring>> = BS,
+ NewBs
+ end, lists:seq(BSsz-32, BSsz-17)),
+ lists:map(fun (Offset) ->
+ <<NewBs:Offset/bitstring, _/bitstring>> = BS,
+ [NewBs]
+ end, lists:seq(1, 16)),
+ lists:map(fun (Offset) ->
+ <<_:Offset/bitstring, NewBs/bitstring>> = BS,
+ [NewBs]
+ end, lists:seq(1, 16)),
+ lists:map(fun (Offset) ->
+ <<NewBs:Offset/bitstring, _/bitstring>> = BS,
+ [NewBs]
+ end, lists:seq(BSsz-16, BSsz-1)),
+ lists:map(fun (Offset) ->
+ PreOffset = Offset rem 16,
+ <<_:PreOffset/bitstring, NewBs:Offset/bitstring, _/bitstring>> = BS,
+ [NewBs]
+ end, lists:seq(BSsz-32, BSsz-17))]).
+
+check_hopefull_fallback_data([], []) ->
+ ok;
+check_hopefull_fallback_data([X|Xs],[Y|Ys]) ->
+ chk_hopefull_fallback(X, Y),
+ check_hopefull_fallback_data(Xs,Ys).
+
+chk_hopefull_fallback(Binary, FallbackBinary) when is_binary(Binary) ->
+ Binary = FallbackBinary;
+chk_hopefull_fallback([BitStr], [{Bin, BitSize}]) when is_bitstring(BitStr) ->
+ chk_hopefull_fallback(BitStr, {Bin, BitSize});
+chk_hopefull_fallback(BitStr, {Bin, BitSize}) when is_bitstring(BitStr) ->
+ true = is_binary(Bin),
+ true = is_integer(BitSize),
+ true = BitSize > 0,
+ true = BitSize < 8,
+ Hsz = size(Bin) - 1,
+ <<Head:Hsz/binary, I/integer>> = Bin,
+ IBits = I bsr (8 - BitSize),
+ FallbackBitStr = list_to_bitstring([Head,<<IBits:BitSize>>]),
+ BitStr = FallbackBitStr,
+ ok;
+chk_hopefull_fallback(Func, {ModName, FuncName}) when is_function(Func) ->
+ {M, F, _} = erlang:fun_info_mfa(Func),
+ M = ModName,
+ F = FuncName,
+ ok;
+chk_hopefull_fallback(Other, SameOther) ->
+ Other = SameOther,
+ ok.
+
+%% ERL-1254
+hopefull_export_fun_bug(Config) when is_list(Config) ->
+ Msg = [1, fun blipp:blapp/7,
+ 2, fun blipp:blapp/7],
+ {dummy, dummy@dummy} ! Msg. % Would crash on debug VM
+
+huge_iovec(Config) ->
+ %% Make sure that we can pass a term that will produce
+ %% an io-vector larger than IOV_MAX over the distribution...
+ %% IOV_MAX is typically 1024. Currently we produce an
+ %% element in the io-vector for all off heap binaries...
+ NoBinaries = 1 bsl 14,
+ BinarySize = 65,
+ {ok, Node} = start_node(huge_iovec),
+ P = spawn_link(Node,
+ fun () ->
+ receive {From, Data} ->
+ From ! {self(), Data}
+ end
+ end),
+ RBL = mk_rand_bin_list(BinarySize, NoBinaries),
+ %% Check that it actually will produce a huge iovec...
+ %% If we set a limit on the size of the binaries
+ %% that will produce an element in the io-vector
+ %% we need to adjust this testcase...
+ true = length(term_to_iovec(RBL)) >= NoBinaries,
+ P ! {self(), RBL},
+ receive
+ {P, EchoedRBL} ->
+ stop_node(Node),
+ RBL = EchoedRBL
+ end,
+ ok.
+
+mk_rand_bin_list(Bytes, Binaries) ->
+ mk_rand_bin_list(Bytes, Binaries, []).
+
+mk_rand_bin_list(_Bytes, 0, Acc) ->
+ Acc;
+mk_rand_bin_list(Bytes, Binaries, Acc) ->
+ mk_rand_bin_list(Bytes, Binaries-1, [mk_rand_bin(Bytes) | Acc]).
+
+mk_rand_bin(Bytes) ->
+ mk_rand_bin(Bytes, []).
+
+mk_rand_bin(0, Data) ->
+ list_to_binary(Data);
+mk_rand_bin(N, Data) ->
+ mk_rand_bin(N-1, [rand:uniform(256) - 1 | Data]).
+
+
%%% Utilities
timestamp() ->
@@ -3034,3 +3278,5 @@ free_memory() ->
error : undef ->
ct:fail({"os_mon not built"})
end.
+
+
diff --git a/erts/emulator/test/driver_SUITE.erl b/erts/emulator/test/driver_SUITE.erl
index 95cae93225..ddbade85c5 100644
--- a/erts/emulator/test/driver_SUITE.erl
+++ b/erts/emulator/test/driver_SUITE.erl
@@ -82,6 +82,8 @@
consume_timeslice/1,
env/1,
poll_pipe/1,
+ lots_of_used_fds_on_boot/1,
+ lots_of_used_fds_on_boot_slave/1,
z_test/1]).
-export([bin_prefix/2]).
@@ -159,7 +161,9 @@ groups() ->
[a_test, use_fallback_pollset,
bad_fd_in_pollset, fd_change,
steal_control, smp_select,
- driver_select_use, z_test]},
+ driver_select_use,
+ lots_of_used_fds_on_boot,
+ z_test]},
{ioq_exit, [],
[ioq_exit_ready_input, ioq_exit_ready_output,
ioq_exit_timeout, ioq_exit_ready_async,
@@ -1859,6 +1863,79 @@ driver_select_use0(Config) ->
ok = erl_ddll:stop(),
ok.
+lots_of_used_fds_on_boot(Config) ->
+ case os:type() of
+ {unix, _} -> lots_of_used_fds_on_boot_test(Config);
+ _ -> {skipped, "Unix only test"}
+ end.
+
+lots_of_used_fds_on_boot_test(Config) ->
+ %% Start a node in a wrapper which have lots of fds
+ %% open. This used to hang the whole VM at boot in
+ %% an eternal loop trying to figure out how to size
+ %% arrays in erts_poll() implementation.
+ Name = lots_of_used_fds_on_boot,
+ HostSuffix = lists:dropwhile(fun ($@) -> false; (_) -> true end,
+ atom_to_list(node())),
+ FullName = list_to_atom(atom_to_list(Name) ++ HostSuffix),
+ Pa = filename:dirname(code:which(?MODULE)),
+ Prog = case catch init:get_argument(progname) of
+ {ok,[[P]]} -> P;
+ _ -> exit(no_progname_argument_found)
+ end,
+ NameSw = case net_kernel:longnames() of
+ false -> "-sname ";
+ true -> "-name ";
+ _ -> exit(not_distributed_node)
+ end,
+ {ok, Pwd} = file:get_cwd(),
+ NameStr = atom_to_list(Name),
+ DataDir = proplists:get_value(data_dir, Config),
+ Wrapper = filename:join(DataDir, "lots_of_fds_used_wrapper"),
+ CmdLine = Wrapper ++ " " ++ Prog ++ " -noshell -noinput "
+ ++ NameSw ++ " " ++ NameStr ++ " "
+ ++ "-pa " ++ Pa ++ " "
+ ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ NameStr ++ " "
+ ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()) ++ " "
+ ++ "-s " ++ atom_to_list(?MODULE) ++ " lots_of_used_fds_on_boot_slave "
+ ++ atom_to_list(node()),
+ io:format("Starting node ~p: ~s~n", [FullName, CmdLine]),
+ net_kernel:monitor_nodes(true),
+ Port = case open_port({spawn, CmdLine}, [exit_status]) of
+ Prt when is_port(Prt) ->
+ Prt;
+ OPError ->
+ exit({failed_to_start_node, {open_port_error, OPError}})
+ end,
+ receive
+ {Port, {exit_status, 17}} ->
+ {skip, "Cannot open enough fds to test this"};
+ {Port, {exit_status, Error}} ->
+ exit({failed_to_start_node, {exit_status, Error}});
+ {nodeup, FullName} ->
+ io:format("~p connected!~n", [FullName]),
+ FullName = rpc:call(FullName, erlang, node, []),
+ rpc:cast(FullName, erlang, halt, []),
+ receive
+ {Port, {exit_status, 0}} ->
+ ok;
+ {Port, {exit_status, Error}} ->
+ exit({unexpected_exit_status, Error})
+ after 5000 ->
+ exit(missing_exit_status)
+ end
+ after 5000 ->
+ exit(connection_timeout)
+ end.
+
+lots_of_used_fds_on_boot_slave([Master]) ->
+ erlang:monitor_node(Master, true),
+ receive
+ {nodedown, Master} ->
+ erlang:halt()
+ end,
+ ok.
+
thread_mseg_alloc_cache_clean(Config) when is_list(Config) ->
case {erlang:system_info(threads),
erlang:system_info({allocator,mseg_alloc}),
diff --git a/erts/emulator/test/driver_SUITE_data/Makefile.src b/erts/emulator/test/driver_SUITE_data/Makefile.src
index bcabaa689d..77cbd34fb1 100644
--- a/erts/emulator/test/driver_SUITE_data/Makefile.src
+++ b/erts/emulator/test/driver_SUITE_data/Makefile.src
@@ -1,3 +1,7 @@
+CC = @CC@
+LD = @LD@
+CFLAGS = @CFLAGS@ @DEFS@
+CROSSLDFLAGS = @CROSSLDFLAGS@
MISC_DRVS = outputv_drv@dll@ \
timer_drv@dll@ \
@@ -30,7 +34,15 @@ VSN_MISMATCH_DRVS = zero_extended_marker_garb_drv@dll@ \
smaller_major_vsn_drv@dll@ \
smaller_minor_vsn_drv@dll@
-all: $(MISC_DRVS) $(SYS_INFO_DRVS) $(VSN_MISMATCH_DRVS)
+PROGS = lots_of_fds_used_wrapper@exe@
+
+all: $(MISC_DRVS) $(SYS_INFO_DRVS) $(VSN_MISMATCH_DRVS) $(PROGS)
+
+lots_of_fds_used_wrapper@exe@: lots_of_fds_used_wrapper@obj@
+ $(LD) $(CROSSLDFLAGS) -o lots_of_fds_used_wrapper lots_of_fds_used_wrapper@obj@ @LIBS@
+
+lots_of_fds_used_wrapper@obj@: lots_of_fds_used_wrapper.c
+ $(CC) -c -o lots_of_fds_used_wrapper@obj@ $(CFLAGS) lots_of_fds_used_wrapper.c
@SHLIB_RULES@
diff --git a/erts/emulator/test/driver_SUITE_data/lots_of_fds_used_wrapper.c b/erts/emulator/test/driver_SUITE_data/lots_of_fds_used_wrapper.c
new file mode 100644
index 0000000000..34d84827d5
--- /dev/null
+++ b/erts/emulator/test/driver_SUITE_data/lots_of_fds_used_wrapper.c
@@ -0,0 +1,61 @@
+#if !defined(__WIN32__)
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#endif
+
+int
+main(int argc, char *argv[])
+{
+#if !defined(__WIN32__)
+
+ char **exec_argv;
+ int fds[12000];
+ int max = sizeof(fds)/sizeof(fds[0]);
+ int i;
+
+ /* Open a bit more than 1024 file descriptors... */
+ for (i = 0; i < max; i++) {
+ fds[i] = open("/dev/null", 0, O_WRONLY);
+ if (fds[i] < 0) {
+ if (i < 1200)
+ return 17; /* Not enough fds for the test... */
+ max = i;
+ break;
+ }
+ }
+
+ /*
+ * Close some of the latest fds to give room for
+ * the emulators usage...
+ */
+ for (i = max-150; i < max; i++)
+ close(fds[i]);
+
+ if (argc < 2)
+ return 1;
+
+ /*
+ * Ensure NULL pointer after last argument...
+ */
+ exec_argv = malloc(sizeof(char *)*argc);
+ if (!exec_argv)
+ return 2;
+
+ for (i = 0; i < argc-1; i++) {
+ /* printf("arg=%d: %s\n", i, argv[i+1]); */
+ exec_argv[i] = argv[i+1];
+ }
+ exec_argv[i] = NULL;
+
+ execvp(exec_argv[0], exec_argv);
+
+ perror("Failed to exec");
+
+#endif
+
+ return 3;
+}
diff --git a/erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c b/erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c
index 685cda3e07..b69d75c31d 100644
--- a/erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c
+++ b/erts/emulator/test/driver_SUITE_data/peek_non_existing_queue_drv.c
@@ -47,6 +47,10 @@
#include <windows.h>
#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
#include <errno.h>
#include "erl_driver.h"
diff --git a/erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c b/erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c
index 56183c9484..503d8b902c 100644
--- a/erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c
+++ b/erts/emulator/test/driver_SUITE_data/thr_msg_blast_drv.c
@@ -18,6 +18,10 @@
* %CopyrightEnd%
*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
#include "erl_driver.h"
#define THR_MSG_BLAST_NO_PROCS 10
diff --git a/erts/emulator/test/emulator.spec b/erts/emulator/test/emulator.spec
index 7a6dd83020..087bd8880d 100644
--- a/erts/emulator/test/emulator.spec
+++ b/erts/emulator/test/emulator.spec
@@ -1,2 +1,3 @@
{enable_builtin_hooks, false}.
{suites,"../emulator_test",all}.
+{skip_groups,"../emulator_test",hash_SUITE,[phash2_benchmark],"Benchmark only"}.
diff --git a/erts/emulator/test/emulator_bench.spec b/erts/emulator/test/emulator_bench.spec
index 03638bfa23..8b1bb71a40 100644
--- a/erts/emulator/test/emulator_bench.spec
+++ b/erts/emulator/test/emulator_bench.spec
@@ -1,3 +1,4 @@
{groups,"../emulator_test",estone_SUITE,[estone_bench]}.
{groups,"../emulator_test",binary_SUITE,[iolist_size_benchmarks]}.
{groups,"../emulator_test",erts_debug_SUITE,[interpreter_size_bench]}.
+{groups,"../emulator_test",hash_SUITE,[phash2_benchmark]}.
diff --git a/erts/emulator/test/erl_drv_thread_SUITE_data/rwlock.c b/erts/emulator/test/erl_drv_thread_SUITE_data/rwlock.c
index 98d0162b55..9c1b242007 100644
--- a/erts/emulator/test/erl_drv_thread_SUITE_data/rwlock.c
+++ b/erts/emulator/test/erl_drv_thread_SUITE_data/rwlock.c
@@ -18,6 +18,7 @@
*/
#include "testcase_driver.h"
+#include <stdio.h>
#ifdef __WIN32__
#include <windows.h>
@@ -25,6 +26,7 @@
#include <unistd.h>
#endif
#include <errno.h>
+#include <stdio.h>
#define NO_OF_THREADS 17
diff --git a/erts/emulator/test/erl_link_SUITE.erl b/erts/emulator/test/erl_link_SUITE.erl
index ed444f2599..7e8a6b2d2c 100644
--- a/erts/emulator/test/erl_link_SUITE.erl
+++ b/erts/emulator/test/erl_link_SUITE.erl
@@ -45,7 +45,11 @@
otp_5772_dist_link/1,
otp_5772_monitor/1,
otp_5772_dist_monitor/1,
- otp_7946/1]).
+ otp_7946/1,
+ otp_17127_local_link_with_simultaneous_link_unlink/1,
+ otp_17127_dist_link_with_simultaneous_link_unlink/1,
+ otp_17127_local_random/1,
+ otp_17127_dist_random/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
@@ -55,6 +59,7 @@
-record(erl_link, {type, % process | port | dist_process
pid = [],
+ state, % linked | unlinking
id}).
% This is to be kept in sync with erl_bif_info.c (make_monitor_list)
@@ -74,7 +79,10 @@ all() ->
[links, dist_links, monitor_nodes, process_monitors,
dist_process_monitors, busy_dist_port_monitor,
busy_dist_port_link, otp_5772_link, otp_5772_dist_link,
- otp_5772_monitor, otp_5772_dist_monitor, otp_7946].
+ otp_5772_monitor, otp_5772_dist_monitor, otp_7946,
+ otp_17127_local_link_with_simultaneous_link_unlink,
+ otp_17127_dist_link_with_simultaneous_link_unlink,
+ otp_17127_local_random, otp_17127_dist_random].
init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
case catch erts_debug:get_internal_state(available_internal_state) of
@@ -109,6 +117,8 @@ dist_links(Config) when is_list(Config) ->
TP4 = spawn(?MODULE, test_proc, []),
TP5 = spawn(?MODULE, test_proc, []),
TP6 = spawn(Node, ?MODULE, test_proc, []),
+ io:format("TP4=~p~nTP5=~p~nTP6=~p~n", [TP4, TP5, TP6]),
+
true = tp_call(TP6, fun() -> link(TP4) end),
check_link(TP4, TP6),
true = tp_call(TP5,
@@ -490,6 +500,119 @@ otp_7946(Config) when is_list(Config) ->
Reason = noconnection
end.
+otp_17127_local_link_with_simultaneous_link_unlink(Config) when is_list(Config) ->
+ otp_17127_link_with_simultaneous_link_unlink_test(node(), node()).
+
+otp_17127_dist_link_with_simultaneous_link_unlink(Config) when is_list(Config) ->
+ [NodeName] = get_names(1, otp_17127),
+ {ok, Node} = start_node(NodeName),
+ Res = otp_17127_link_with_simultaneous_link_unlink_test(node(), Node),
+ stop_node(Node),
+ Res.
+
+otp_17127_link_with_simultaneous_link_unlink_test(NodeA, NodeB) ->
+ FunA = fun (Other) ->
+ link(Other)
+ end,
+ FunB = fun (Other) ->
+ link(Other),
+ unlink(Other)
+ end,
+ otp_17127_test(NodeA, FunA, NodeB, FunB).
+
+otp_17127_local_random(Config) when is_list(Config) ->
+ otp_17127_random_test(node(), node(), 100).
+
+otp_17127_dist_random(Config) when is_list(Config) ->
+ [NodeName] = get_names(1, otp_17127),
+ {ok, Node} = start_node(NodeName),
+ Res = otp_17127_random_test(node(), Node, 20),
+ stop_node(Node),
+ Res.
+
+otp_17127_random_test(_NodeA, _NodeB, 0) ->
+ ok;
+otp_17127_random_test(NodeA, NodeB, N) ->
+ Fun = fun (Other) ->
+ rand_proc(Other, rand:uniform(500))
+ end,
+ otp_17127_test(NodeA, Fun, NodeB, Fun),
+ otp_17127_random_test(NodeA, NodeB, N-1).
+
+rand_proc(_Other, 0) ->
+ ok;
+rand_proc(Other, N) ->
+ case rand:uniform(3) of
+ 1 -> link(Other);
+ 2 -> unlink(Other);
+ 3 -> erlang:yield()
+ end,
+ rand_proc(Other, N-1).
+
+otp_17127_test(NodeA, FunA, NodeB, FunB) ->
+ process_flag(priority, high),
+ {SchedA, SchedB} = case NodeA == NodeB of
+ false ->
+ {[], []};
+ true ->
+ NS = erlang:system_info(schedulers_online),
+ process_flag(scheduler, 1),
+ {[{scheduler, (1 rem NS) + 1}],
+ [{scheduler, (2 rem NS) + 1}]}
+ end,
+ A = spawn_opt(NodeA,
+ fun () ->
+ receive
+ {go, Tester, Other, Later} ->
+ busy_wait_until(Later),
+ FunA(Other),
+ receive ping -> Other ! pong end,
+ receive pling -> ok end,
+ Tester ! {self(), done}
+ end,
+ receive after infinity -> ok end
+ end, SchedA),
+ B = spawn_opt(NodeB,
+ fun () ->
+ receive
+ {go, Tester, Other, Later} ->
+ busy_wait_until(Later),
+ FunB(Other),
+ Other ! ping,
+ receive pong -> Other ! pling end,
+ Tester ! {self(), done}
+ end,
+ receive after infinity -> ok end
+ end, SchedB),
+ io:format("A = ~p~nB = ~p~n", [A, B]),
+ Later = case NodeA == NodeB of
+ true ->
+ GoTime = (erlang:monotonic_time()
+ + erlang:convert_time_unit(100, millisecond, native)),
+ fun () ->
+ erlang:monotonic_time() >= GoTime
+ end;
+ false ->
+ GoTime = (os:system_time(nanosecond)
+ + erlang:convert_time_unit(500, millisecond, nanosecond)),
+ fun () ->
+ os:system_time(nanosecond) >= GoTime
+ end
+ end,
+ erlang:yield(),
+ A ! {go, self(), B, Later},
+ B ! {go, self(), A, Later},
+ receive {A, done} -> ok end,
+ receive {B, done} -> ok end,
+ try
+ check_consistent_link_state(A, B),
+ true = rpc:call(node(A), erlang, is_process_alive, [A]),
+ true = rpc:call(node(B), erlang, is_process_alive, [B])
+ after
+ exit(A, kill),
+ exit(B, kill)
+ end.
+
%%
%% -- Internal utils --------------------------------------------------------
%%
@@ -668,6 +791,13 @@ wait_until(Fun) ->
end
end.
+busy_wait_until(Fun) ->
+ case Fun() of
+ true -> ok;
+ _ ->
+ busy_wait_until(Fun)
+ end.
+
forever(Fun) ->
Fun(),
forever(Fun).
@@ -782,9 +912,13 @@ find_erl_monitor(Pid, Item) ->
find_erl_link(Obj, Type, Item) when is_pid(Item); is_port(Item) ->
LinkList = get_link_list(Obj),
io:format("~p LinkList: ~p~n", [Obj, LinkList]),
- lists:foldl(fun (#erl_link{type = T, pid = I} = EL,
+ lists:foldl(fun (#erl_link{type = T, pid = I, state = linked} = EL,
Acc) when T == Type, I == Item ->
[EL|Acc];
+ ({erl_link, T, P, I},
+ Acc) when T == Type, P == Item ->
+ %% Old emulator without state (linked if record exists)...
+ [#erl_link{type = T, pid = P, id = I, state = linked}|Acc];
(_, Acc) ->
Acc
end,
@@ -794,9 +928,13 @@ find_erl_link(Obj, Type, Id) when is_integer(Id) ->
%% Find by Id
LinkList = get_link_list(Obj),
io:format("~p LinkList: ~p~n", [Obj, LinkList]),
- lists:foldl(fun (#erl_link{type = T, id = I} = EL,
+ lists:foldl(fun (#erl_link{type = T, id = I, state = linked} = EL,
Acc) when T == Type, I == Id ->
[EL|Acc];
+ ({erl_link, T, P, I},
+ Acc) when T == Type, I == Id ->
+ %% Old emulator without state (linked if record exists)...
+ [#erl_link{type = T, pid = P, id = I, state = linked}|Acc];
(_, Acc) ->
Acc
end,
@@ -816,14 +954,25 @@ get_link_type(A, B) when is_pid(A),
dist_process
end.
+check_consistent_link_state(A, B) ->
+ %% Both processes should agree on whether
+ %% they are linked or not...
+ LinkType = get_link_type(A, B),
+ case find_erl_link(A, LinkType, B) of
+ [] ->
+ check_unlink(A, B),
+ io:format("~p and ~p are not linked~n", [A, B]);
+ _ ->
+ check_link(A, B),
+ io:format("~p and ~p are linked~n", [A, B])
+ end.
+
check_link(A, B) when node(A) == node(B) ->
LinkType = get_link_type(A, B),
[#erl_link{type = LinkType,
- pid = B,
- id = Id}] = find_erl_link(A, LinkType, B),
+ pid = B}] = find_erl_link(A, LinkType, B),
[#erl_link{type = LinkType,
- pid = A,
- id = Id}] = find_erl_link(B, LinkType, A),
+ pid = A}] = find_erl_link(B, LinkType, A),
[] = find_erl_link({node(A), node(B)},
LinkType,
A),
diff --git a/erts/emulator/test/erts_debug_SUITE.erl b/erts/emulator/test/erts_debug_SUITE.erl
index 32efd6cf84..01473874e8 100644
--- a/erts/emulator/test/erts_debug_SUITE.erl
+++ b/erts/emulator/test/erts_debug_SUITE.erl
@@ -229,7 +229,10 @@ alloc_blocks_size(Config) when is_list(Config) ->
ok = rpc:call(Node, ?MODULE, do_alloc_blocks_size, []),
true = test_server:stop_node(Node)
end,
- F("+Meamax"),
+ case test_server:is_asan() of
+ false -> F("+Meamax");
+ true -> skip
+ end,
F("+Meamin"),
F(""),
ok.
diff --git a/erts/emulator/test/erts_test_destructor.erl b/erts/emulator/test/erts_test_destructor.erl
new file mode 100644
index 0000000000..311bb0aaf9
--- /dev/null
+++ b/erts/emulator/test/erts_test_destructor.erl
@@ -0,0 +1,41 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% A NIF resource that sends a message in the destructor.
+
+-module(erts_test_destructor).
+
+-export([init/1, send/2]).
+
+init(Config) ->
+ case is_loaded() of
+ false ->
+ Path = proplists:get_value(data_dir, Config),
+ erlang:load_nif(filename:join(Path,?MODULE), []);
+ true ->
+ ok
+ end.
+
+is_loaded() ->
+ false.
+
+%% Create a resource which sends Msg to Pid when destructed.
+send(_Pid, _Msg) ->
+ erlang:nif_error("NIF not loaded").
diff --git a/erts/emulator/test/estone_SUITE_data/estone_cat.c b/erts/emulator/test/estone_SUITE_data/estone_cat.c
index a34bda4384..cbdf3db6c9 100644
--- a/erts/emulator/test/estone_SUITE_data/estone_cat.c
+++ b/erts/emulator/test/estone_SUITE_data/estone_cat.c
@@ -12,9 +12,11 @@
#include <fcntl.h>
#include <errno.h>
-main(argc, argv)
-int argc;
-char *argv[];
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+int main(int argc, char* argv[])
{
char buf[16384];
int n;
diff --git a/erts/emulator/test/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl
index 2a03dc9825..2b8581e923 100644
--- a/erts/emulator/test/exception_SUITE.erl
+++ b/erts/emulator/test/exception_SUITE.erl
@@ -303,57 +303,42 @@ maxbig_gc() ->
Maxbig.
stacktrace(Conf) when is_list(Conf) ->
- Tag = make_ref(),
- {_,Mref} = spawn_monitor(fun() -> exit({Tag,erlang:get_stacktrace()}) end),
- {Tag,[]} = receive {'DOWN',Mref,_,_,Info} -> Info end,
V = [make_ref()|self()],
- {value2,{caught1,badarg,[{erlang,abs,[V],_}|_]=St1}} =
- stacktrace_1({'abs',V}, error, {value,V}),
- St1 = erase(stacktrace1),
- St1 = erase(stacktrace2),
- St1 = erlang:get_stacktrace(),
- {caught2,{error,badarith},[{erlang,'+',[0,a],_},{?MODULE,my_add,2,_}|_]=St2} =
- stacktrace_1({'div',{1,0}}, error, {'add',{0,a}}),
- [{erlang,'div',[1,0],_},{?MODULE,my_div,2,_}|_] = erase(stacktrace1),
- St2 = erase(stacktrace2),
- St2 = erlang:get_stacktrace(),
- {caught2,{error,{try_clause,V}},[{?MODULE,stacktrace_1,3,_}|_]=St3} =
- stacktrace_1({value,V}, error, {value,V}),
- St3 = erase(stacktrace1),
- St3 = erase(stacktrace2),
- St3 = erlang:get_stacktrace(),
- {caught2,{throw,V},[{?MODULE,foo,1,_}|_]=St4} =
- stacktrace_1({value,V}, error, {throw,V}),
- [{?MODULE,stacktrace_1,3,_}|_] = erase(stacktrace1),
- St4 = erase(stacktrace2),
- St4 = erlang:get_stacktrace(),
+ {value2,{caught1,badarg,[{erlang,abs,[V],_}|_]}} =
+ stacktrace_1({'abs',V}, error, {value,V}),
+ {caught2,{error,badarith},[{erlang,'+',[0,a],_},{?MODULE,my_add,2,_}|_]} =
+ stacktrace_1({'div',{1,0}}, error, {'add',{0,a}}),
+ {caught2,{error,{try_clause,V}},[{?MODULE,stacktrace_1,3,_}|_]} =
+ stacktrace_1({value,V}, error, {value,V}),
+ {caught2,{throw,V},[{?MODULE,foo,1,_}|_]} =
+ stacktrace_1({value,V}, error, {throw,V}),
try
stacktrace_2()
catch
- error:{badmatch,_} ->
+ error:{badmatch,_}:Stk ->
[{?MODULE,stacktrace_2,0,_},
- {?MODULE,stacktrace,1,_}|_] =
- erlang:get_stacktrace(),
+ {?MODULE,stacktrace,1,_}|_] = Stk,
ok
end.
stacktrace_1(X, C1, Y) ->
- erase(stacktrace1),
- erase(stacktrace2),
try try foo(X) of
C1 -> value1
catch
- C1:D1 -> {caught1,D1,erlang:get_stacktrace()}
+ C1:D1:Stk1 ->
+ [] = erlang:get_stacktrace(),
+ {caught1,D1,Stk1}
after
- put(stacktrace1, erlang:get_stacktrace()),
foo(Y)
end of
V2 -> {value2,V2}
catch
- C2:D2 -> {caught2,{C2,D2},erlang:get_stacktrace()}
+ C2:D2:Stk2 ->
+ [] = erlang:get_stacktrace(),
+ {caught2,{C2,D2},Stk2}
after
- put(stacktrace2, erlang:get_stacktrace())
+ ok
end.
stacktrace_2() ->
@@ -364,76 +349,71 @@ stacktrace_2() ->
nested_stacktrace(Conf) when is_list(Conf) ->
V = [{make_ref()}|[self()]],
value1 =
- nested_stacktrace_1({{value,{V,x1}},void,{V,x1}},
- {void,void,void}),
+ nested_stacktrace_1({{value,{V,x1}},void,{V,x1}},
+ {void,void,void}),
{caught1,
[{erlang,'+',[V,x1],_},{?MODULE,my_add,2,_}|_],
- value2,
- [{erlang,'+',[V,x1],_},{?MODULE,my_add,2,_}|_]} =
- nested_stacktrace_1({{'add',{V,x1}},error,badarith},
- {{value,{V,x2}},void,{V,x2}}),
+ value2} =
+ nested_stacktrace_1({{'add',{V,x1}},error,badarith},
+ {{value,{V,x2}},void,{V,x2}}),
{caught1,
[{erlang,'+',[V,x1],_},{?MODULE,my_add,2,_}|_],
- {caught2,[{erlang,abs,[V],_}|_]},
- [{erlang,abs,[V],_}|_]} =
- nested_stacktrace_1({{'add',{V,x1}},error,badarith},
- {{'abs',V},error,badarg}),
+ {caught2,[{erlang,abs,[V],_}|_]}} =
+ nested_stacktrace_1({{'add',{V,x1}},error,badarith},
+ {{'abs',V},error,badarg}),
ok.
nested_stacktrace_1({X1,C1,V1}, {X2,C2,V2}) ->
try foo(X1) of
V1 -> value1
catch
- C1:V1 ->
- S1 = erlang:get_stacktrace(),
- T2 =
- try foo(X2) of
- V2 -> value2
- catch
- C2:V2 -> {caught2,erlang:get_stacktrace()}
+ C1:V1:S1 ->
+ T2 = try foo(X2) of
+ V2 -> value2
+ catch
+ C2:V2:S2 -> {caught2,S2}
end,
- {caught1,S1,T2,erlang:get_stacktrace()}
+ {caught1,S1,T2}
end.
raise(Conf) when is_list(Conf) ->
erase(raise),
- A =
- try
- try foo({'div',{1,0}})
+ A =
+ try
+ try foo({'div',{1,0}})
+ catch
+ error:badarith:A0 ->
+ put(raise, A0),
+ erlang:raise(error, badarith, A0)
+ end
catch
- error:badarith:A0 ->
- put(raise, A0 = erlang:get_stacktrace()),
- erlang:raise(error, badarith, A0)
- end
- catch
- error:badarith:A1 ->
- A1 = erlang:get_stacktrace(),
- A1 = get(raise)
- end,
- A = erlang:get_stacktrace(),
+ error:badarith:A1 ->
+ A1 = get(raise)
+ end,
A = get(raise),
[{erlang,'div',[1, 0], _},{?MODULE,my_div,2,_}|_] = A,
%%
N = 8, % Must be even
N = erlang:system_flag(backtrace_depth, N),
- B = odd_even(N, []),
- try even(N)
- catch error:function_clause -> ok
+ try
+ even(N)
+ catch
+ error:function_clause -> ok
end,
- B = erlang:get_stacktrace(),
%%
- C0 = odd_even(N+1, []),
- C = lists:sublist(C0, N),
- try odd(N+1)
- catch error:function_clause -> ok
+ C = odd_even(N+1, []),
+ try
+ odd(N+1)
+ catch
+ error:function_clause -> ok
end,
- C = erlang:get_stacktrace(),
- try erlang:raise(error, function_clause, C0)
- catch error:function_clause -> ok
+ try
+ erlang:raise(error, function_clause, C)
+ catch
+ error:function_clause -> ok
end,
- C = erlang:get_stacktrace(),
ok.
odd_even(N, R) when is_integer(N), N > 1 ->
@@ -601,11 +581,11 @@ do_exception_with_heap_frag(Bin, [Sz|Sizes]) ->
try
binary_to_term(Bin)
catch
- _:_ ->
+ _:_:Stk ->
%% term_to_binary/1 is an easy way to traverse the
%% entire stacktrace term to make sure that every part
%% of it is OK.
- term_to_binary(erlang:get_stacktrace())
+ term_to_binary(Stk)
end,
id(Filler)
end),
@@ -766,6 +746,13 @@ line_numbers(Config) when is_list(Config) ->
{?MODULE,line_numbers,1,_}|_]}} =
(catch increment2(x)),
+ {'EXIT',{{badmap,not_a_map},
+ [{?MODULE,update_map,1,[{file,"map.erl"},{line,3}]}|_]}} =
+ (catch update_map(not_a_map)),
+ {'EXIT',{{badkey,a},
+ [{?MODULE,update_map,1,[{file,"map.erl"},{line,4}]}|_]}} =
+ (catch update_map(#{})),
+
ok.
id(I) -> I.
@@ -811,8 +798,8 @@ close_calls(Where) -> %Line 2
call2(), %Line 6
call3(), %Line 7
no_crash %Line 8
- catch error:crash ->
- erlang:get_stacktrace() %Line 10
+ catch error:crash:Stk ->
+ Stk %Line 10
end. %Line 11
call1() -> %Line 13
@@ -878,3 +865,8 @@ increment1(Arg) -> %Line 43
increment2(Arg) -> %Line 46
_ = id(Arg), %Line 47
Arg + 1. %Line 48
+
+-file("map.erl", 1).
+update_map(M0) -> %Line 2
+ M = M0#{new => value}, %Line 3
+ M#{a := b}. %Line 4
diff --git a/erts/emulator/test/hash_SUITE.erl b/erts/emulator/test/hash_SUITE.erl
index 3cbb3c7d5f..86b4460b38 100644
--- a/erts/emulator/test/hash_SUITE.erl
+++ b/erts/emulator/test/hash_SUITE.erl
@@ -33,7 +33,25 @@
-module(hash_SUITE).
-export([basic_test/0,cmp_test/1,range_test/0,spread_test/1,
phash2_test/0, otp_5292_test/0,
- otp_7127_test/0]).
+ otp_7127_test/0,
+ run_phash2_benchmarks/0,
+ test_phash2_binary_aligned_and_unaligned_equal/1,
+ test_phash2_4GB_plus_bin/1,
+ test_phash2_10MB_plus_bin/1,
+ test_phash2_large_map/1,
+ test_phash2_shallow_long_list/1,
+ test_phash2_deep_list/1,
+ test_phash2_deep_tuple/1,
+ test_phash2_deep_tiny/1,
+ test_phash2_with_42/1,
+ test_phash2_with_short_tuple/1,
+ test_phash2_with_short_list/1,
+ test_phash2_with_tiny_bin/1,
+ test_phash2_with_tiny_unaligned_sub_binary/1,
+ test_phash2_with_small_unaligned_sub_binary/1,
+ test_phash2_with_large_bin/1,
+ test_phash2_with_large_unaligned_sub_binary/1,
+ test_phash2_with_super_large_unaligned_sub_binary/1]).
%%
%% Define to run outside of test server
@@ -43,13 +61,15 @@
%%
%% Define for debug output
%%
-%-define(debug,1).
+-define(debug,1).
-ifdef(STANDALONE).
-define(config(A,B),config(A,B)).
+-record(event, {name, data}).
-export([config/2]).
-else.
-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
-endif.
-ifdef(debug).
@@ -67,12 +87,15 @@
-ifdef(STANDALONE).
config(priv_dir,_) ->
".".
+notify(X) ->
+ erlang:display(X).
-else.
%% When run in test server.
--export([all/0, suite/0,
+-export([groups/0, all/0, suite/0,
test_basic/1,test_cmp/1,test_range/1,test_spread/1,
test_phash2/1,otp_5292/1,bit_level_binaries/1,otp_7127/1,
- test_hash_zero/1]).
+ test_hash_zero/1, init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -81,7 +104,71 @@ suite() ->
all() ->
[test_basic, test_cmp, test_range, test_spread,
test_phash2, otp_5292, bit_level_binaries, otp_7127,
- test_hash_zero].
+ test_hash_zero, test_phash2_binary_aligned_and_unaligned_equal,
+ test_phash2_4GB_plus_bin,
+ test_phash2_10MB_plus_bin,
+ {group, phash2_benchmark_tests},
+ {group, phash2_benchmark}].
+
+get_phash2_benchmarks() ->
+ [
+ test_phash2_large_map,
+ test_phash2_shallow_long_list,
+ test_phash2_deep_list,
+ test_phash2_deep_tuple,
+ test_phash2_deep_tiny,
+ test_phash2_with_42,
+ test_phash2_with_short_tuple,
+ test_phash2_with_short_list,
+ test_phash2_with_tiny_bin,
+ test_phash2_with_tiny_unaligned_sub_binary,
+ test_phash2_with_small_unaligned_sub_binary,
+ test_phash2_with_large_bin,
+ test_phash2_with_large_unaligned_sub_binary,
+ test_phash2_with_super_large_unaligned_sub_binary
+ ].
+
+groups() ->
+ [
+ {
+ phash2_benchmark_tests,
+ [],
+ get_phash2_benchmarks()
+ },
+ {
+ phash2_benchmark,
+ [],
+ get_phash2_benchmarks()
+ }
+ ].
+
+
+init_per_suite(Config) ->
+ io:format("START APPS~n"),
+ A0 = case application:start(sasl) of
+ ok -> [sasl];
+ _ -> []
+ end,
+ A = case application:start(os_mon) of
+ ok -> [os_mon|A0];
+ _ -> A0
+ end,
+ io:format("APPS STARTED~n"),
+ [{started_apps, A}|Config].
+
+end_per_suite(Config) ->
+ As = proplists:get_value(started_apps, Config),
+ lists:foreach(fun (A) -> application:stop(A) end, As),
+ Config.
+
+init_per_group(phash2_benchmark_tests, Config) ->
+ [phash2_benchmark_tests |Config];
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
%% Tests basic functionality of erlang:phash and that the
%% hashes has not changed (neither hash nor phash)
@@ -119,6 +206,9 @@ otp_7127(Config) when is_list(Config) ->
test_hash_zero(Config) when is_list(Config) ->
hash_zero_test().
+
+notify(X) ->
+ ct_event:notify(X).
-endif.
@@ -133,26 +223,17 @@ basic_test() ->
16#77777777777777],16#FFFFFFFF),
ExternalReference = <<131,114,0,3,100,0,13,110,111,110,111,100,101,64,
110,111,104,111,115,116,0,0,0,0,122,0,0,0,0,0,0,0,0>>,
- 1113403635 = erlang:phash(binary_to_term(ExternalReference),
- 16#FFFFFFFF),
- ExternalFun = <<131,117,0,0,0,3,103,100,0,13,110,111,110,111,100,101,64,
- 110,111,104,111,115,116,0,0,0,38,0,0,0,0,0,100,0,8,101,
- 114,108,95,101,118,97,108,97,20,98,5,182,139,98,108,0,0,
- 0,3,104,2,100,0,1,66,109,0,0,0,33,131,114,0,3,100,0,13,
- 110,111,110,111,100,101,64,110,111,104,111,115,116,0,0,
- 0,0,122,0,0,0,0,0,0,0,0,104,2,100,0,1,76,107,0,33,131,
- 114,0,3,100,0,13,110,111,110,111,100,101,64,110,111,104,
- 111,115,116,0,0,0,0,122,0,0,0,0,0,0,0,0,104,2,100,0,1,82,
- 114,0,3,100,0,13,110,111,110,111,100,101,64,110,111,104,
- 111,115,116,0,0,0,0,122,0,0,0,0,0,0,0,0,106,108,0,0,0,1,
- 104,5,100,0,6,99,108,97,117,115,101,97,1,106,106,108,0,0,
- 0,1,104,3,100,0,7,105,110,116,101,103,101,114,97,1,97,1,
- 106,106,104,3,100,0,4,101,118,97,108,104,2,100,0,5,115,
- 104,101,108,108,100,0,10,108,111,99,97,108,95,102,117,
- 110,99,108,0,0,0,1,103,100,0,13,110,111,110,111,100,101,
- 64,110,111,104,111,115,116,0,0,0,22,0,0,0,0,0,106>>,
- 170987488 = erlang:phash(binary_to_term(ExternalFun),
- 16#FFFFFFFF),
+ ExternalReference = <<131,114,0,3,100,0,13,110,111,110,111,100,101,64,
+ 110,111,104,111,115,116,0,0,0,0,122,0,0,0,0,0,0,0,0>>,
+ 1113403635 = phash_from_external(ExternalReference),
+
+ ExternalFun = <<131,112,0,0,0,70,1,212,190,220,28,179,144,194,131,
+ 19,215,105,97,77,251,125,93,0,0,0,0,0,0,0,2,100,0,1,
+ 116,97,0,98,6,165,246,224,103,100,0,13,110,111,
+ 110,111,100,101,64,110,111,104,111,115,116,0,0,0,91,
+ 0,0,0,0,0,97,2,97,1>>,
+ 25769064 = phash_from_external(ExternalFun),
+
case (catch erlang:phash(1,0)) of
{'EXIT',{badarg, _}} ->
ok;
@@ -160,6 +241,8 @@ basic_test() ->
exit(phash_accepted_zero_as_range)
end.
+phash_from_external(Ext) ->
+ erlang:phash(binary_to_term(Ext), 16#FFFFFFFF).
range_test() ->
F = fun(From,From,_FF) ->
@@ -354,6 +437,7 @@ phash2_test() ->
%% bit-level binaries
{<<0:7>>, 1055790816},
+ {(fun()-> B = <<255,7:3>>, <<_:4,D/bitstring>> = B, D end)(), 911751529},
{<<"abc",13:4>>, 670412287},
{<<5:3,"12345678901234567890">>, 289973273},
@@ -424,6 +508,199 @@ phash2_test() ->
[] = [{E,H,H2} || {E,H} <- L, (H2 = erlang:phash2(E, Max)) =/= H],
ok.
+test_phash2_binary_aligned_and_unaligned_equal(Config) when is_list(Config) ->
+ erts_debug:set_internal_state(available_internal_state, true),
+ test_aligned_and_unaligned_equal_up_to(256*12+255),
+ erts_debug:set_internal_state(available_internal_state, false).
+
+test_aligned_and_unaligned_equal_up_to(BinSize) ->
+ Results =
+ lists:map(fun(Size) ->
+ test_aligned_and_unaligned_equal(Size)
+ end, lists:seq(1, BinSize)),
+ %% DataDir = filename:join(filename:dirname(code:which(?MODULE)), "hash_SUITE_data"),
+ %% ExpResFile = filename:join(DataDir, "phash2_bin_expected_results.txt"),
+ %% {ok, [ExpRes]} = file:consult(ExpResFile),
+ %% %% ok = file:write_file(ExpResFile, io_lib:format("~w.~n", [Results])),
+ %% Results = ExpRes,
+ 110469206 = erlang:phash2(Results).
+
+test_aligned_and_unaligned_equal(BinSize) ->
+ Bin = make_random_bin(BinSize),
+ LastByte = last_byte(Bin),
+ LastInBitstring = LastByte rem 11,
+ Bitstring = << Bin/binary, <<LastInBitstring:5>>/bitstring >>,
+ UnalignedBin = make_unaligned_sub_bitstring(Bin),
+ UnalignedBitstring = make_unaligned_sub_bitstring(Bitstring),
+ case erts_debug:get_internal_state(available_internal_state) of
+ false -> erts_debug:set_internal_state(available_internal_state, true);
+ _ -> ok
+ end,
+ erts_debug:set_internal_state(reds_left, 3),
+ BinHash = erlang:phash2(Bin),
+ BinHash = erlang:phash2(Bin),
+ erts_debug:set_internal_state(reds_left, 3),
+ UnalignedBinHash = erlang:phash2(UnalignedBin),
+ UnalignedBinHash = erlang:phash2(UnalignedBin),
+ BinHash = UnalignedBinHash,
+ erts_debug:set_internal_state(reds_left, 3),
+ BitstringHash = erlang:phash2(Bitstring),
+ BitstringHash = erlang:phash2(Bitstring),
+ erts_debug:set_internal_state(reds_left, 3),
+ UnalignedBitstringHash = erlang:phash2(UnalignedBitstring),
+ UnalignedBitstringHash = erlang:phash2(UnalignedBitstring),
+ BitstringHash = UnalignedBitstringHash,
+ {BinHash, BitstringHash}.
+
+last_byte(Bin) ->
+ NotLastByteSize = (erlang:bit_size(Bin)) - 8,
+ <<_:NotLastByteSize/bitstring, LastByte:8>> = Bin,
+ LastByte.
+
+test_phash2_4GB_plus_bin(Config) when is_list(Config) ->
+ run_when_enough_resources(
+ fun() ->
+ {ok, N} = start_node(?FUNCTION_NAME),
+ erpc:call(N,
+ fun() ->
+ erts_debug:set_internal_state(available_internal_state, true),
+ %% Created Bin4GB here so it only needs to be created once
+ erts_debug:set_internal_state(force_gc, self()),
+ Bin4GB = get_4GB_bin(),
+ test_phash2_plus_bin_helper1(Bin4GB, <<>>, <<>>, 13708901),
+ erts_debug:set_internal_state(force_gc, self()),
+ test_phash2_plus_bin_helper1(Bin4GB, <<>>, <<3:5>>, 66617678),
+ erts_debug:set_internal_state(force_gc, self()),
+ test_phash2_plus_bin_helper1(Bin4GB, <<13>>, <<>>, 31308392),
+ erts_debug:set_internal_state(force_gc, self()),
+ erts_debug:set_internal_state(available_internal_state, false)
+ end),
+ stop_node(N)
+ end).
+
+
+test_phash2_10MB_plus_bin(Config) when is_list(Config) ->
+ erts_debug:set_internal_state(available_internal_state, true),
+ erts_debug:set_internal_state(force_gc, self()),
+ Bin10MB = get_10MB_bin(),
+ test_phash2_plus_bin_helper1(Bin10MB, <<>>, <<>>, 22776267),
+ erts_debug:set_internal_state(force_gc, self()),
+ test_phash2_plus_bin_helper1(Bin10MB, <<>>, <<3:5>>, 124488972),
+ erts_debug:set_internal_state(force_gc, self()),
+ test_phash2_plus_bin_helper1(Bin10MB, <<13>>, <<>>, 72958346),
+ erts_debug:set_internal_state(force_gc, self()),
+ erts_debug:set_internal_state(available_internal_state, false).
+
+get_10MB_bin() ->
+ TmpBin = make_random_bin(10239),
+ Bin = erlang:iolist_to_binary([0, TmpBin]),
+ IOList10MB = duplicate_iolist(Bin, 10),
+ Bin10MB = erlang:iolist_to_binary(IOList10MB),
+ 10485760 = size(Bin10MB),
+ Bin10MB.
+
+get_4GB_bin() ->
+ TmpBin = make_random_bin(65535),
+ Bin = erlang:iolist_to_binary([0, TmpBin]),
+ IOList4GB = duplicate_iolist(Bin, 16),
+ Bin4GB = erlang:iolist_to_binary(IOList4GB),
+ 4294967296 = size(Bin4GB),
+ Bin4GB.
+
+duplicate_iolist(IOList, 0) ->
+ IOList;
+duplicate_iolist(IOList, NrOfTimes) ->
+ duplicate_iolist([IOList, IOList], NrOfTimes - 1).
+
+test_phash2_plus_bin_helper1(Bin4GB, ExtraBytes, ExtraBits, ExpectedHash) ->
+ test_phash2_plus_bin_helper2(Bin4GB, fun id/1, ExtraBytes, ExtraBits, ExpectedHash),
+ test_phash2_plus_bin_helper2(Bin4GB, fun make_unaligned_sub_bitstring/1, ExtraBytes, ExtraBits, ExpectedHash).
+
+test_phash2_plus_bin_helper2(Bin, TransformerFun, ExtraBytes, ExtraBits, ExpectedHash) ->
+ ExtraBitstring = << ExtraBytes/binary, ExtraBits/bitstring >>,
+ LargerBitstring = << ExtraBytes/binary,
+ ExtraBits/bitstring,
+ Bin/bitstring >>,
+ LargerTransformedBitstring = TransformerFun(LargerBitstring),
+ ExtraBitstringHash = erlang:phash2(ExtraBitstring),
+ ExpectedHash =
+ case size(LargerTransformedBitstring) < 4294967296 of
+ true ->
+ erts_debug:set_internal_state(force_gc, self()),
+ erts_debug:set_internal_state(reds_left, 1),
+ Hash = erlang:phash2(LargerTransformedBitstring),
+ Hash = erlang:phash2(LargerTransformedBitstring),
+ Hash;
+ false ->
+ erts_debug:set_internal_state(force_gc, self()),
+ erts_debug:set_internal_state(reds_left, 1),
+ ExtraBitstringHash = erlang:phash2(LargerTransformedBitstring),
+ ExtraBitstringHash = erlang:phash2(LargerTransformedBitstring),
+ ExtraBitstringHash
+ end.
+
+run_when_enough_resources(Fun) ->
+ Bits = 8 * erlang:system_info({wordsize,external}),
+ Mem = total_memory(),
+ Build = erlang:system_info(build_type),
+
+ if Bits =:= 64, is_integer(Mem), Mem >= 31,
+ Build =/= valgrind, Build =/= asan ->
+ Fun();
+
+ true ->
+ {skipped,
+ io_lib:format("Not enough resources (System Memory = ~p, Bits = ~p, Build = ~p)",
+ [Mem, Bits, Build])}
+ end.
+
+%% Total memory in GB
+total_memory() ->
+ try
+ MemoryData = memsup:get_system_memory_data(),
+ case lists:keysearch(total_memory, 1, MemoryData) of
+ {value, {total_memory, TM}} ->
+ TM div (1024*1024*1024);
+ false ->
+ {value, {system_total_memory, STM}} =
+ lists:keysearch(system_total_memory, 1, MemoryData),
+ STM div (1024*1024*1024)
+ end
+ catch
+ _ : _ ->
+ undefined
+ end.
+
+start_node(X) ->
+ start_node(X, [], []).
+
+start_node(X, Y) ->
+ start_node(X, Y, []).
+
+start_node(Name, Args, Rel) when is_atom(Name), is_list(Rel) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ Cookie = atom_to_list(erlang:get_cookie()),
+ RelArg = case Rel of
+ [] -> [];
+ _ -> [{erl,[{release,Rel}]}]
+ end,
+ test_server:start_node(Name, slave,
+ [{args,
+ Args++" -setcookie "++Cookie++" -pa \""++Pa++"\""}
+ | RelArg]);
+start_node(Config, Args, Rel) when is_list(Config), is_list(Rel) ->
+ Name = list_to_atom((atom_to_list(?MODULE)
+ ++ "-"
+ ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(second))
+ ++ "-"
+ ++ integer_to_list(erlang:unique_integer([positive])))),
+ start_node(Name, Args, Rel).
+
+stop_node(Node) ->
+ test_server:stop_node(Node).
+
-ifdef(FALSE).
f1() ->
abc.
@@ -436,14 +713,23 @@ f3(X, Y) ->
-endif.
otp_5292_test() ->
- PH = fun(E) -> [erlang:phash(E, 1 bsl 32),
- erlang:phash(-E, 1 bsl 32),
- erlang:phash2(E, 1 bsl 32),
- erlang:phash2(-E, 1 bsl 32)]
- end,
+ PH = fun(E) ->
+ EInList = [1, 2, 3, E],
+ EInList2 = [E, 1, 2, 3],
+ NegEInList = [1, 2, 3, -E],
+ NegEInList2 = [-E, 1, 2, 3],
+ [erlang:phash(E, 1 bsl 32),
+ erlang:phash(-E, 1 bsl 32),
+ erlang:phash2(E, 1 bsl 32),
+ erlang:phash2(-E, 1 bsl 32),
+ erlang:phash2(EInList, 1 bsl 32),
+ erlang:phash2(EInList2, 1 bsl 32),
+ erlang:phash2(NegEInList, 1 bsl 32),
+ erlang:phash2(NegEInList2, 1 bsl 32)]
+ end,
S2 = md5([md5(hash_int(S, E, PH)) || {Start, N, Sz} <- d(),
{S, E} <- int(Start, N, Sz)]),
- <<124,81,198,121,174,233,19,137,10,83,33,80,226,111,238,99>> = S2,
+ <<234,63,192,76,253,57,250,32,44,11,73,1,161,102,14,238>> = S2,
ok.
d() ->
@@ -684,3 +970,313 @@ unaligned_sub_bitstr(Bin0) when is_bitstring(Bin0) ->
id(I) -> I.
+
+%% Benchmarks for phash2
+
+run_phash2_benchmarks() ->
+ Benchmarks = [
+ test_phash2_large_map,
+ test_phash2_shallow_long_list,
+ test_phash2_deep_list,
+ test_phash2_deep_tuple,
+ test_phash2_deep_tiny,
+ test_phash2_with_42,
+ test_phash2_with_short_tuple,
+ test_phash2_with_short_list,
+ test_phash2_with_tiny_bin,
+ test_phash2_with_tiny_unaligned_sub_binary,
+ test_phash2_with_small_unaligned_sub_binary,
+ test_phash2_with_large_bin,
+ test_phash2_with_large_unaligned_sub_binary,
+ test_phash2_with_super_large_unaligned_sub_binary
+ ],
+ [print_comment(B) || B <- Benchmarks].
+
+
+print_comment(FunctionName) ->
+ io:format("~p~n", [FunctionName]),
+ io:format("~s~n", [element(2, erlang:apply(?MODULE, FunctionName, [[]]))]).
+
+nr_of_iters(BenchmarkNumberOfIterations, Config) ->
+ case lists:member(phash2_benchmark_tests, Config) of
+ true -> 1;
+ false -> BenchmarkNumberOfIterations
+ end.
+
+
+test_phash2_large_map(Config) when is_list(Config) ->
+ {Size, ExpectedHash} =
+ case {total_memory(), erlang:system_info(wordsize)} of
+ {Mem, 8} when is_integer(Mem) andalso Mem > 2 ->
+ {1000000, 121857429};
+ _ ->
+ {1000, 66609305}
+ end,
+ run_phash2_test_and_benchmark(nr_of_iters(45, Config),
+ get_map(Size),
+ ExpectedHash).
+
+test_phash2_shallow_long_list(Config) when is_list(Config) ->
+ {Size, ExpectedHash} =
+ case {total_memory(), erlang:system_info(wordsize)} of
+ {Mem, 8} when is_integer(Mem) andalso Mem > 2 ->
+ {1000000, 78700388};
+ _ ->
+ {1000, 54749638}
+ end,
+ run_phash2_test_and_benchmark(nr_of_iters(1, Config),
+ lists:duplicate(Size, get_complex_tuple()),
+ ExpectedHash).
+
+test_phash2_deep_list(Config) when is_list(Config) ->
+ {Size, ExpectedHash} =
+ case {total_memory(), erlang:system_info(wordsize)} of
+ {Mem, 8} when is_integer(Mem) andalso Mem > 2 ->
+ {500000, 17986444};
+ _ ->
+ {1000, 81794308}
+ end,
+ run_phash2_test_and_benchmark(nr_of_iters(1, Config),
+ make_deep_list(Size, get_complex_tuple()),
+ ExpectedHash).
+
+test_phash2_deep_tuple(Config) when is_list(Config) ->
+ {Size, ExpectedHash} =
+ case {total_memory(), erlang:system_info(wordsize)} of
+ {Mem, 8} when is_integer(Mem) andalso Mem > 2 ->
+ {500000, 116594715};
+ _ ->
+ {500, 109057352}
+ end,
+ run_phash2_test_and_benchmark(nr_of_iters(1, Config),
+ make_deep_tuple(Size, get_complex_tuple()),
+ ExpectedHash).
+
+test_phash2_deep_tiny(Config) when is_list(Config) ->
+ run_phash2_test_and_benchmark(nr_of_iters(1000000, Config),
+ make_deep_list(19, 42),
+ 111589624).
+
+test_phash2_with_42(Config) when is_list(Config) ->
+ run_phash2_test_and_benchmark(nr_of_iters(20000000, Config),
+ 42,
+ 30328728).
+
+test_phash2_with_short_tuple(Config) when is_list(Config) ->
+ run_phash2_test_and_benchmark(nr_of_iters(10000000, Config),
+ {a,b,<<"hej">>, "hej"},
+ 50727199).
+
+test_phash2_with_short_list(Config) when is_list(Config) ->
+ run_phash2_test_and_benchmark(nr_of_iters(10000000, Config),
+ [a,b,"hej", "hello"],
+ 117108642).
+
+test_phash2_with_tiny_bin(Config) when is_list(Config) ->
+ run_phash2_test_and_benchmark(nr_of_iters(20000000, Config),
+ make_random_bin(10),
+ 129616602).
+
+test_phash2_with_tiny_unaligned_sub_binary(Config) when is_list(Config) ->
+ run_phash2_test_and_benchmark(nr_of_iters(10000000, Config),
+ make_unaligned_sub_binary(make_random_bin(11)),
+ 59364725).
+
+test_phash2_with_small_unaligned_sub_binary(Config) when is_list(Config) ->
+ run_phash2_test_and_benchmark(nr_of_iters(400000, Config),
+ make_unaligned_sub_binary(make_random_bin(1001)),
+ 130388119).
+
+test_phash2_with_large_bin(Config) when is_list(Config) ->
+ {Size, ExpectedHash} =
+ case {total_memory(), erlang:system_info(wordsize)} of
+ {Mem, 8} when is_integer(Mem) andalso Mem > 2 ->
+ {10000000, 48249379};
+ _ ->
+ {1042, 14679520}
+ end,
+ run_phash2_test_and_benchmark(nr_of_iters(150, Config),
+ make_random_bin(Size),
+ ExpectedHash).
+
+test_phash2_with_large_unaligned_sub_binary(Config) when is_list(Config) ->
+ {Size, ExpectedHash} =
+ case {total_memory(), erlang:system_info(wordsize)} of
+ {Mem, 8} when is_integer(Mem) andalso Mem > 2 ->
+ {10000001, 122836437};
+ _ ->
+ {10042, 127144287}
+ end,
+ run_phash2_test_and_benchmark(nr_of_iters(50, Config),
+ make_unaligned_sub_binary(make_random_bin(Size)),
+ ExpectedHash).
+
+test_phash2_with_super_large_unaligned_sub_binary(Config) when is_list(Config) ->
+ {Size, ExpectedHash} =
+ case {total_memory(), erlang:system_info(wordsize)} of
+ {Mem, 8} when is_integer(Mem) andalso Mem > 2 ->
+ {20000001, 112086727};
+ _ ->
+ {20042, 91996619}
+ end,
+ run_phash2_test_and_benchmark(nr_of_iters(20, Config),
+ make_unaligned_sub_binary(make_random_bin(Size)),
+ ExpectedHash).
+
+make_deep_list(1, Item) ->
+ {Item, Item};
+make_deep_list(Depth, Item) ->
+ [{Item, Item}, make_deep_list(Depth - 1, Item)].
+
+make_deep_tuple(1, Item) ->
+ [Item, Item];
+make_deep_tuple(Depth, Item) ->
+ {[Item, Item], make_deep_tuple(Depth - 1, Item)}.
+
+% Helper functions for benchmarking
+
+loop(0, _) -> ok;
+loop(Iterations, Fun) ->
+ Fun(),
+ loop(Iterations - 1, Fun).
+
+run_phash2_test_and_benchmark(Iterations, Term, ExpectedHash) ->
+ Parent = self(),
+ Test =
+ fun() ->
+ Hash = erlang:phash2(Term),
+ case ExpectedHash =:= Hash of
+ false ->
+ Parent ! {got_bad_hash, Hash},
+ ExpectedHash = Hash;
+ _ -> ok
+ end
+ end,
+ Benchmark =
+ fun() ->
+ garbage_collect(),
+ {Time, _} =timer:tc(fun() -> loop(Iterations, Test) end),
+ Parent ! Time
+ end,
+ spawn(Benchmark),
+ receive
+ {got_bad_hash, Hash} ->
+ ExpectedHash = Hash;
+ Time ->
+ TimeInS = case (Time/1000000) of
+ 0.0 -> 0.0000000001;
+ T -> T
+ end,
+ IterationsPerSecond = Iterations / TimeInS,
+ notify(#event{ name = benchmark_data, data = [{value, IterationsPerSecond}]}),
+ {comment, io_lib:format("Iterations per second: ~p, Iterations ~p, Benchmark time: ~p seconds)",
+ [IterationsPerSecond, Iterations, Time/1000000])}
+ end.
+
+get_complex_tuple() ->
+ BPort = <<131,102,100,0,13,110,111,110,111,100,101,64,110,111,104,
+ 111,115,116,0,0,0,1,0>>,
+ Port = binary_to_term(BPort),
+
+ BXPort = <<131,102,100,0,11,97,112,97,64,108,101,103,111,108,97,115,
+ 0,0,0,24,3>>,
+ XPort = binary_to_term(BXPort),
+
+ BRef = <<131,114,0,3,100,0,13,110,111,110,111,100,101,64,110,111,104,
+ 111,115,116,0,0,0,1,255,0,0,0,0,0,0,0,0>>,
+ Ref = binary_to_term(BRef),
+
+ BXRef = <<131,114,0,3,100,0,11,97,112,97,64,108,101,103,111,108,97,115,
+ 2,0,0,0,155,0,0,0,0,0,0,0,0>>,
+ XRef = binary_to_term(BXRef),
+
+ BXPid = <<131,103,100,0,11,97,112,97,64,108,101,103,111,108,97,115,
+ 0,0,0,36,0,0,0,0,1>>,
+ XPid = binary_to_term(BXPid),
+
+
+ %% X = f1(), Y = f2(), Z = f3(X, Y),
+
+ %% F1 = fun f1/0, % -> abc
+ B1 = <<131,112,0,0,0,66,0,215,206,77,69,249,50,170,17,129,47,21,98,
+ 13,196,76,242,0,0,0,1,0,0,0,0,100,0,1,116,97,1,98,2,195,126,
+ 58,103,100,0,13,110,111,110,111,100,101,64,110,111,104,111,
+ 115,116,0,0,0,112,0,0,0,0,0>>,
+ F1 = binary_to_term(B1),
+
+ %% F2 = fun f2/0, % -> abd
+ B2 = <<131,112,0,0,0,66,0,215,206,77,69,249,50,170,17,129,47,21,98,
+ 13,196,76,242,0,0,0,2,0,0,0,0,100,0,1,116,97,2,98,3,130,152,
+ 185,103,100,0,13,110,111,110,111,100,101,64,110,111,104,111,
+ 115,116,0,0,0,112,0,0,0,0,0>>,
+ F2 = binary_to_term(B2),
+
+ %% F3 = fun f3/2, % -> {abc, abd}
+ B3 = <<131,112,0,0,0,66,2,215,206,77,69,249,50,170,17,129,47,21,98,
+ 13,196,76,242,0,0,0,3,0,0,0,0,100,0,1,116,97,3,98,7,168,160,
+ 93,103,100,0,13,110,111,110,111,100,101,64,110,111,104,111,
+ 115,116,0,0,0,112,0,0,0,0,0>>,
+ F3 = binary_to_term(B3),
+
+ %% F4 = fun () -> 123456789012345678901234567 end,
+ B4 = <<131,112,0,0,0,66,0,215,206,77,69,249,50,170,17,129,47,21,98,
+ 13,196,76,242,0,0,0,4,0,0,0,0,100,0,1,116,97,4,98,2,230,21,
+ 171,103,100,0,13,110,111,110,111,100,101,64,110,111,104,111,
+ 115,116,0,0,0,112,0,0,0,0,0>>,
+ F4 = binary_to_term(B4),
+
+ %% F5 = fun() -> {X,Y,Z} end,
+ B5 = <<131,112,0,0,0,92,0,215,206,77,69,249,50,170,17,129,47,21,98,
+ 13,196,76,242,0,0,0,5,0,0,0,3,100,0,1,116,97,5,98,0,99,101,
+ 130,103,100,0,13,110,111,110,111,100,101,64,110,111,104,111,
+ 115,116,0,0,0,112,0,0,0,0,0,100,0,3,97,98,99,100,0,3,97,98,
+ 100,104,2,100,0,3,97,98,99,100,0,3,97,98,100>>,
+ F5 = binary_to_term(B5),
+ {{1,{2}},an_atom, 1, 3434.923942394,<<"this is a binary">>,
+ make_unaligned_sub_binary(<<"this is also a binary">>),c,d,e,f,g,h,i,j,k,l,[f],
+ 999999999999999999666666662123123123123324234999999999999999, 234234234,
+ BPort, Port, BXPort, XPort, BRef, Ref, BXRef, XRef, BXPid, XPid, F1, F2, F3, F4, F5,
+ #{a => 1, b => 2, c => 3, d => 4, e => 5, f => 6, g => 7, h => 8, i => 9,
+ j => 1, k => 1, l => 123123123123213, m => [1,2,3,4,5,6,7,8], o => 5, p => 6,
+ q => 7, r => 8, s => 9}}.
+
+get_map_helper(MapSoFar, 0) ->
+ MapSoFar;
+get_map_helper(MapSoFar, NumOfItemsToAdd) ->
+ NewMapSoFar = maps:put(NumOfItemsToAdd, NumOfItemsToAdd, MapSoFar),
+ get_map_helper(NewMapSoFar, NumOfItemsToAdd -1).
+
+get_map(Size) ->
+ get_map_helper(#{}, Size).
+
+
+%% Copied from binary_SUITE
+make_unaligned_sub_binary(Bin0) when is_binary(Bin0) ->
+ Bin1 = <<0:3,Bin0/binary,31:5>>,
+ Sz = size(Bin0),
+ <<0:3,Bin:Sz/binary,31:5>> = id(Bin1),
+ Bin.
+
+make_unaligned_sub_bitstring(Bin0) ->
+ Bin1 = <<0:3,Bin0/bitstring,31:5>>,
+ Sz = erlang:bit_size(Bin0),
+ <<0:3,Bin:Sz/bitstring,31:5>> = id(Bin1),
+ Bin.
+
+make_random_bin(Size) ->
+ make_random_bin(Size, []).
+
+make_random_bin(0, Acc) ->
+ iolist_to_binary(Acc);
+make_random_bin(Size, []) ->
+ make_random_bin(Size - 1, [simple_rand() rem 256]);
+make_random_bin(Size, [N | Tail]) ->
+ make_random_bin(Size - 1, [simple_rand(N) rem 256, N |Tail]).
+
+simple_rand() ->
+ 123456789.
+simple_rand(Seed) ->
+ A = 1103515245,
+ C = 12345,
+ M = (1 bsl 31),
+ (A * Seed + C) rem M.
diff --git a/erts/emulator/test/hash_property_test_SUITE.erl b/erts/emulator/test/hash_property_test_SUITE.erl
new file mode 100644
index 0000000000..b4c7810a52
--- /dev/null
+++ b/erts/emulator/test/hash_property_test_SUITE.erl
@@ -0,0 +1,103 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% %%%
+%%% WARNING %%%
+%%% %%%
+%%% This is experimental code which may be changed or removed %%%
+%%% anytime without any warning. %%%
+%%% %%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-module(hash_property_test_SUITE).
+
+-export([suite/0,all/0,groups/0,init_per_suite/1,
+ end_per_suite/1,init_per_group/2,end_per_group/2]).
+
+-export([test_phash2_no_diff/1,
+ test_phash2_no_diff_long/1,
+ test_phash2_no_diff_between_versions/1]).
+
+-include_lib("common_test/include/ct.hrl").
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() -> [{group, proper}].
+
+groups() ->
+ [{proper, [], [test_phash2_no_diff,
+ test_phash2_no_diff_long,
+ test_phash2_no_diff_between_versions]}].
+
+
+%%% First prepare Config and compile the property tests for the found tool:
+init_per_suite(Config) ->
+ ct_property_test:init_per_suite(Config).
+
+end_per_suite(Config) ->
+ Config.
+
+%%% Only proper is supported
+init_per_group(proper, Config) ->
+ case proplists:get_value(property_test_tool,Config) of
+ proper -> Config;
+ X -> {skip, lists:concat([X," is not supported"])}
+ end;
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+test_phash2_no_diff(Config) when is_list(Config) ->
+ true = ct_property_test:quickcheck(
+ phash2_properties:prop_phash2_same_with_same_input(),
+ Config).
+
+test_phash2_no_diff_long(Config) when is_list(Config) ->
+ true = ct_property_test:quickcheck(
+ phash2_properties:prop_phash2_same_with_same_long_input(),
+ Config).
+
+test_phash2_no_diff_between_versions(Config) when is_list(Config) ->
+ R = "21",
+ case test_server:is_release_available(R) of
+ true ->
+ Rel = {release,R},
+ case test_server:start_node(rel21,peer,[{erl,[Rel]}]) of
+ {error, Reason} -> {skip, io_lib:format("Could not start node: ~p~n", [Reason])};
+ {ok, Node} ->
+ try
+ true = ct_property_test:quickcheck(
+ phash2_properties:prop_phash2_same_in_different_versions(Node),
+ Config),
+ true = ct_property_test:quickcheck(
+ phash2_properties:prop_phash2_same_in_different_versions_with_long_input(Node),
+ Config)
+ after
+ test_server:stop_node(Node)
+ end
+ end;
+ false ->
+ {skip, io_lib:format("Release ~s not available~n", [R])}
+ end.
diff --git a/erts/emulator/test/hibernate_SUITE.erl b/erts/emulator/test/hibernate_SUITE.erl
index a20f306e04..d65d0ff2fd 100644
--- a/erts/emulator/test/hibernate_SUITE.erl
+++ b/erts/emulator/test/hibernate_SUITE.erl
@@ -46,12 +46,17 @@ all() ->
basic(Config) when is_list(Config) ->
Ref = make_ref(),
Info = {self(),Ref},
- ExpectedHeapSz = erts_debug:size([Info]),
+ ExpectedHeapSz = expected_heap_size([Info]),
Child = spawn_link(fun() -> basic_hibernator(Info) end),
hibernate_wake_up(100, ExpectedHeapSz, Child),
Child ! please_quit_now,
ok.
+expected_heap_size(Term) ->
+ %% When hibernating, an extra word will be allocated on the stack
+ %% for a continuation pointer.
+ erts_debug:size(Term) + 1.
+
hibernate_wake_up(0, _, _) -> ok;
hibernate_wake_up(N, ExpectedHeapSz, Child) ->
{heap_size,Before} = process_info(Child, heap_size),
@@ -142,7 +147,7 @@ whats_up_calc(A1, A2, A3, A4, A5, A6, A7, A8, A9, Acc) ->
dynamic_call(Config) when is_list(Config) ->
Ref = make_ref(),
Info = {self(),Ref},
- ExpectedHeapSz = erts_debug:size([Info]),
+ ExpectedHeapSz = expected_heap_size([Info]),
Child = spawn_link(fun() -> ?MODULE:dynamic_call_hibernator(Info, hibernate) end),
hibernate_wake_up(100, ExpectedHeapSz, Child),
Child ! please_quit_now,
diff --git a/erts/emulator/test/hipe_SUITE.erl b/erts/emulator/test/hipe_SUITE.erl
index e62d4260f6..9741872fd8 100644
--- a/erts/emulator/test/hipe_SUITE.erl
+++ b/erts/emulator/test/hipe_SUITE.erl
@@ -131,8 +131,8 @@ t_trycatch(Config) ->
t_trycatch_1([S|Ss]) ->
io:format("~p", [S]),
compile_and_load(S),
- call_trycatch(try_catch),
- call_trycatch(plain_catch),
+ call_trycatch(),
+ call_catch(),
io:nl(),
t_trycatch_1(Ss);
t_trycatch_1([]) ->
@@ -144,38 +144,49 @@ trycatch_combine([N|Ns]) ->
trycatch_combine([]) ->
[[]].
-call_trycatch(Func) ->
- case do_call_trycatch(error, Func, {error,whatever}) of
+call_trycatch() ->
+ case trycatch_1:one_try_catch({error,whatever}) of
{error,whatever,[{trycatch_3,three,1,_}|_]} ->
ok
end,
- case do_call_trycatch(error, Func, fc) of
+ case trycatch_1:one_try_catch(fc) of
{error,function_clause,[{trycatch_3,three,[fc],_}|_]} ->
ok;
{error,function_clause,[{trycatch_3,three,1,_}|_]} ->
+ true = trycatch_3:module_info(native),
ok
end,
- case do_call_trycatch(throw, Func, {throw,{a,b}}) of
+ case trycatch_1:one_try_catch({throw,{a,b}}) of
{throw,{a,b},[{trycatch_3,three,1,_}|_]} ->
ok
end,
- case do_call_trycatch(exit, Func, {exit,{a,b,c}}) of
+ case trycatch_1:one_try_catch({exit,{a,b,c}}) of
{exit,{a,b,c},[{trycatch_3,three,1,_}|_]} ->
ok
end,
ok.
-do_call_trycatch(_Class, try_catch, Argument) ->
- trycatch_1:one_try_catch(Argument);
-do_call_trycatch(error, plain_catch, Argument) ->
- {{'EXIT',{Reason,Stk}},Stk} = trycatch_1:one_plain_catch(Argument),
- {error,Reason,Stk};
-do_call_trycatch(throw, plain_catch, Argument) ->
- {Reason,Stk} = trycatch_1:one_plain_catch(Argument),
- {throw,Reason,Stk};
-do_call_trycatch(exit, plain_catch, Argument) ->
- {{'EXIT',Reason},Stk} = trycatch_1:one_plain_catch(Argument),
- {exit,Reason,Stk}.
+call_catch() ->
+ case trycatch_1:one_plain_catch({error,whatever}) of
+ {'EXIT',{whatever,[{trycatch_3,three,1,_}|_]}} ->
+ ok
+ end,
+
+ case trycatch_1:one_plain_catch(fc) of
+ {'EXIT',{function_clause,[{trycatch_3,three,[fc],_}|_]}} ->
+ ok;
+ {'EXIT',{function_clause,[{trycatch_3,three,1,_}|_]}} ->
+ true = trycatch_3:module_info(native)
+ end,
+ case trycatch_1:one_plain_catch({throw,{a,b}}) of
+ {a,b} ->
+ ok
+ end,
+ case trycatch_1:one_plain_catch({exit,{a,b,c}}) of
+ {'EXIT',{a,b,c}} ->
+ ok
+ end,
+ ok.
compile_and_load(Sources) ->
_ = [begin
diff --git a/erts/emulator/test/hipe_SUITE_data/trycatch_1.erl b/erts/emulator/test/hipe_SUITE_data/trycatch_1.erl
index 702b14b5b9..f7d0e3bd1e 100644
--- a/erts/emulator/test/hipe_SUITE_data/trycatch_1.erl
+++ b/erts/emulator/test/hipe_SUITE_data/trycatch_1.erl
@@ -5,10 +5,9 @@ one_try_catch(Term) ->
try
trycatch_2:two(Term)
catch
- C:R ->
- Stk = erlang:get_stacktrace(),
+ C:R:Stk ->
{C,R,Stk}
end.
one_plain_catch(Term) ->
- {catch trycatch_2:two(Term),erlang:get_stacktrace()}.
+ catch trycatch_2:two(Term).
diff --git a/erts/emulator/test/lcnt_SUITE.erl b/erts/emulator/test/lcnt_SUITE.erl
index d09bfd5776..3f56c820f4 100644
--- a/erts/emulator/test/lcnt_SUITE.erl
+++ b/erts/emulator/test/lcnt_SUITE.erl
@@ -192,7 +192,9 @@ remove_untoggleable_locks([]) ->
[];
remove_untoggleable_locks([{resource_monitors, _, _, _} | T]) ->
remove_untoggleable_locks(T);
-remove_untoggleable_locks([{'esock[gcnt]', _, _, _} | T]) ->
+remove_untoggleable_locks([{nif_load, _, _, _} | T]) ->
+ remove_untoggleable_locks(T);
+remove_untoggleable_locks([{'esock.gcnt', _, _, _} | T]) ->
%% Global lock used by socket NIF
remove_untoggleable_locks(T);
remove_untoggleable_locks([H | T]) ->
diff --git a/erts/emulator/test/list_bif_SUITE.erl b/erts/emulator/test/list_bif_SUITE.erl
index d16628fa51..b398e22707 100644
--- a/erts/emulator/test/list_bif_SUITE.erl
+++ b/erts/emulator/test/list_bif_SUITE.erl
@@ -156,22 +156,18 @@ t_list_to_ext_pidportref(Config) when is_list(Config) ->
Port2 = list_to_port(PortStr),
Ref2 = list_to_ref(RefStr),
- %% The local roundtrips of externals does not work
- %% as 'creation' is missing in the string formats and we don't know
- %% the 'creation' of the connected node.
- false = (Pid =:= Pid2),
- false = (Port =:= Port2),
- false = (Ref =:= Ref2),
-
- %% Local roundtrip kind of "works" for '==' since OTP-22.0 (bf7c722bd3b)
- %% Operator '==' treats 0-creations as wildcards
- %% which breaks term transitivity (A==B and B==C => B==C).
+ %% Local roundtrips of externals work from OTP-23
+ %% as even though 'creation' is missing in the string formats
+ %% we know the 'creation' of the connected node and list_to_* use that.
+ true = (Pid =:= Pid2),
+ true = (Port =:= Port2),
+ true = (Ref =:= Ref2),
true = (Pid == Pid2),
true = (Port == Port2),
true = (Ref == Ref2),
- %% It works when sent back to node with matching name, as 0-creations
- %% will be converted to the local node creation.
+ %% And it works when sent back to the same node instance,
+ %% which was connected when list_to_* were called.
true = rpc:call(Node, erlang, '=:=', [Pid, Pid2]),
true = rpc:call(Node, erlang, '==', [Pid, Pid2]),
true = rpc:call(Node, erlang, '=:=', [Port, Port2]),
@@ -179,9 +175,57 @@ t_list_to_ext_pidportref(Config) when is_list(Config) ->
true = rpc:call(Node, erlang, '=:=', [Ref, Ref2]),
true = rpc:call(Node, erlang, '==', [Ref, Ref2]),
+ %% Make sure no ugly comparison with 0-creation as wildcard is done.
+ Pid0 = make_0_creation(Pid),
+ Port0 = make_0_creation(Port),
+ Ref0 = make_0_creation(Ref),
+ false = (Pid =:= Pid0),
+ false = (Port =:= Port0),
+ false = (Ref =:= Ref0),
+ false = (Pid == Pid0),
+ false = (Port == Port0),
+ false = (Ref == Ref0),
+
+ %% Check 0-creations are converted to local node creations
+ %% when sent to matching node name.
+ true = rpc:call(Node, erlang, '=:=', [Pid, Pid0]),
+ true = rpc:call(Node, erlang, '==', [Pid, Pid0]),
+ true = rpc:call(Node, erlang, '=:=', [Port, Port0]),
+ true = rpc:call(Node, erlang, '==', [Port, Port0]),
+ true = rpc:call(Node, erlang, '=:=', [Ref, Ref0]),
+ true = rpc:call(Node, erlang, '==', [Ref, Ref0]),
+
slave:stop(Node),
ok.
+-define(NEW_PID_EXT, 88).
+-define(NEW_PORT_EXT, 89).
+-define(NEWER_REFERENCE_EXT, 90).
+
+%% Copy pid/port/ref but set creation=0
+make_0_creation(X) when is_pid(X); is_port(X); is_reference(X) ->
+ B = term_to_binary(X),
+ Sz = byte_size(B),
+ B2 = case B of
+ <<131, ?NEW_PID_EXT, _/binary>> ->
+ PreSz = Sz - 4,
+ <<_:PreSz/binary, Cr:32>> = B,
+ true = (Cr =/= 0),
+ <<B:PreSz/binary, 0:32>>;
+ <<131, ?NEW_PORT_EXT, _/binary>> ->
+ PreSz = Sz - 4,
+ <<_:PreSz/binary, Cr:32>> = B,
+ true = (Cr =/= 0),
+ <<B:PreSz/binary, 0:32>>;
+ <<131, ?NEWER_REFERENCE_EXT, Len:16, _/binary>> ->
+ PostSz = Len*4,
+ PreSz = Sz - (4 + PostSz),
+ <<_:PreSz/binary, Cr:32, PostFix:PostSz/binary>> = B,
+ true = (Cr =/= 0),
+ <<B:PreSz/binary, 0:32, PostFix/binary>>
+ end,
+ binary_to_term(B2).
+
%% Test list_to_float/1 with correct and incorrect arguments.
diff --git a/erts/emulator/test/lttng_SUITE.erl b/erts/emulator/test/lttng_SUITE.erl
index 81a7b92af0..ceeff05e75 100644
--- a/erts/emulator/test/lttng_SUITE.erl
+++ b/erts/emulator/test/lttng_SUITE.erl
@@ -32,8 +32,7 @@
t_driver_ready_input_output/1,
t_driver_timeout/1,
t_driver_caller/1,
- t_driver_flush/1,
- t_scheduler_poll/1]).
+ t_driver_flush/1]).
-export([ets_load/0]).
@@ -43,7 +42,7 @@ suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap, {minutes, 1}}].
-all() ->
+all() ->
[t_lttng_list,
t_memory_carrier,
t_carrier_pool,
@@ -52,9 +51,7 @@ all() ->
t_driver_control,
t_driver_timeout,
t_driver_caller,
- t_driver_flush,
- t_scheduler_poll].
-
+ t_driver_flush].
init_per_suite(Config) ->
case erlang:system_info(dynamic_trace) of
@@ -88,8 +85,6 @@ end_per_testcase(Case, _Config) ->
%% org_erlang_otp:carrier_pool_put
%% org_erlang_otp:carrier_destroy
%% org_erlang_otp:carrier_create
-%% org_erlang_otp:aio_pool_put
-%% org_erlang_otp:aio_pool_get
%% org_erlang_otp:driver_control
%% org_erlang_otp:driver_call
%% org_erlang_otp:driver_finish
@@ -105,7 +100,6 @@ end_per_testcase(Case, _Config) ->
%% org_erlang_otp:driver_outputv
%% org_erlang_otp:driver_init
%% org_erlang_otp:driver_start
-%% org_erlang_otp:scheduler_poll
%%
%% Testcases
@@ -264,35 +258,6 @@ t_driver_caller(Config) ->
ok = check_tracepoint("org_erlang_otp:driver_init", Res),
ok = check_tracepoint("org_erlang_otp:driver_finish", Res),
ok.
-
-%% org_erlang_otp:scheduler_poll
-t_scheduler_poll(Config) ->
- ok = lttng_start_event("org_erlang_otp:scheduler_poll", Config),
-
- N = 100,
-
- Me = self(),
- Pid = spawn_link(fun() -> tcp_server(Me, {active, N*2}) end),
- receive {Pid, accept} -> ok end,
-
- %% We want to create a scenario where the fd is moved into a scheduler
- %% pollset, this means we have to send many small packages to the
- %% same socket, but not fast enough for them to all arrive at the
- %% same time.
- {ok, Sock} = gen_tcp:connect("localhost", 5679, [binary, {packet, 2}]),
- [begin gen_tcp:send(Sock,txt()), receive ok -> ok end end || _ <- lists:seq(1,N)],
-
- ok = memory_load(),
-
- [begin gen_tcp:send(Sock,txt()), receive ok -> ok end end || _ <- lists:seq(1,N)],
-
- ok = gen_tcp:close(Sock),
- Pid ! die,
- receive {Pid, done} -> ok end,
-
- Res = lttng_stop_and_view(Config),
- ok = check_tracepoint("org_erlang_otp:scheduler_poll", Res),
- ok.
%% org_erlang_otp:driver_flush
t_driver_flush(Config) ->
@@ -333,24 +298,6 @@ chk_caller(Port, Callback, ExpectedCaller) ->
ExpectedCaller = Caller
end.
-memory_load() ->
- Me = self(),
- Pids0 = [spawn_link(fun() -> memory_loop(Me, 20, <<42>>) end) || _ <- lists:seq(1,30)],
- timer:sleep(50),
- Pids1 = [spawn_link(fun() -> memory_loop(Me, 20, <<42>>) end) || _ <- lists:seq(1,30)],
- [receive {Pid, done} -> ok end || Pid <- Pids0 ++ Pids1],
- timer:sleep(500),
- ok.
-
-memory_loop(Parent, N, Bin) ->
- memory_loop(Parent, N, Bin, []).
-
-memory_loop(Parent, 0, _Bin, _) ->
- Parent ! {self(), done};
-memory_loop(Parent, N, Bin0, Ls) ->
- Bin = binary:copy(<<Bin0/binary, Bin0/binary>>),
- memory_loop(Parent, N - 1, Bin, [a,b,c|Ls]).
-
ets_load(Config) ->
%% Have to do on a fresh node to guarantee that carriers are created
@@ -429,8 +376,6 @@ txt() ->
"%% org_erlang_otp:carrier_pool_put\n"
"%% org_erlang_otp:carrier_destroy\n"
"%% org_erlang_otp:carrier_create\n"
- "%% org_erlang_otp:aio_pool_put\n"
- "%% org_erlang_otp:aio_pool_get\n"
"%% org_erlang_otp:driver_control\n"
"%% org_erlang_otp:driver_call\n"
"%% org_erlang_otp:driver_finish\n"
@@ -445,8 +390,7 @@ txt() ->
"%% org_erlang_otp:driver_output\n"
"%% org_erlang_otp:driver_outputv\n"
"%% org_erlang_otp:driver_init\n"
- "%% org_erlang_otp:driver_start\n"
- "%% org_erlang_otp:scheduler_poll">>.
+ "%% org_erlang_otp:driver_start">>.
load_driver(Dir, Driver) ->
case erl_ddll:load_driver(Dir, Driver) of
@@ -464,12 +408,6 @@ have_carriers(Alloc) ->
_ -> true
end.
-have_async_threads() ->
- Tps = erlang:system_info(thread_pool_size),
- if Tps =:= 0 -> false;
- true -> true
- end.
-
%% lttng
lttng_stop_and_view(Config) ->
Path = proplists:get_value(priv_dir, Config),
diff --git a/erts/emulator/test/map_SUITE.erl b/erts/emulator/test/map_SUITE.erl
index 4b638b9082..dbf6fa58ed 100644
--- a/erts/emulator/test/map_SUITE.erl
+++ b/erts/emulator/test/map_SUITE.erl
@@ -1872,15 +1872,18 @@ t_bif_map_get(Config) when is_list(Config) ->
"v3" = maps:get(<<"k2">>, M1),
%% error cases
+ %%
+ %% Note that the stack trace is ignored because the compiler may have
+ %% rewritten maps:get/2 to map_get.
do_badmap(fun(T) ->
- {'EXIT',{{badmap,T},[{maps,get,_,_}|_]}} =
+ {'EXIT',{{badmap,T},_}} =
(catch maps:get(a, T))
end),
- {'EXIT',{{badkey,{1,1}},[{maps,get,_,_}|_]}} =
+ {'EXIT',{{badkey,{1,1}},_}} =
(catch maps:get({1,1}, #{{1,1.0} => "tuple"})),
- {'EXIT',{{badkey,a},[{maps,get,_,_}|_]}} = (catch maps:get(a, #{})),
- {'EXIT',{{badkey,a},[{maps,get,_,_}|_]}} =
+ {'EXIT',{{badkey,a},_}} = (catch maps:get(a, #{})),
+ {'EXIT',{{badkey,a},_}} =
(catch maps:get(a, #{b=>1, c=>2})),
ok.
@@ -1942,8 +1945,11 @@ t_bif_map_is_key(Config) when is_list(Config) ->
false = maps:is_key(1.0, maps:put(1, "number", M1)),
%% error case
+ %%
+ %% Note that the stack trace is ignored because the compiler may have
+ %% rewritten maps:is_key/2 to is_map_key.
do_badmap(fun(T) ->
- {'EXIT',{{badmap,T},[{maps,is_key,_,_}|_]}} =
+ {'EXIT',{{badmap,T},_}} =
(catch maps:is_key(a, T))
end),
ok.
diff --git a/erts/emulator/test/match_spec_SUITE.erl b/erts/emulator/test/match_spec_SUITE.erl
index 21de6b1002..686b431876 100644
--- a/erts/emulator/test/match_spec_SUITE.erl
+++ b/erts/emulator/test/match_spec_SUITE.erl
@@ -198,7 +198,8 @@ caller_and_return_to(Config) ->
{trace,Tracee,call,{?MODULE,do_the_put,[test]},{?MODULE,do_put,1}},
{trace,Tracee,call,{erlang,integer_to_list,[1]},{?MODULE,do_the_put,1}},
{trace,Tracee,return_to,{?MODULE,do_the_put,1}},
- {trace,Tracee,call,{erlang,put,[test,"1"]},{?MODULE,do_put,1}},
+ {trace,Tracee,call,{erlang,put,[test,"1"]},{?MODULE,do_the_put,1}},
+ {trace,Tracee,return_to,{?MODULE,do_the_put,1}},
{trace,Tracee,return_to,{?MODULE,do_put,1}},
%% These last trace messages are a bit strange...
diff --git a/erts/emulator/test/mtx_SUITE_data/Makefile.src b/erts/emulator/test/mtx_SUITE_data/Makefile.src
index 1816dc6798..c736cfa7dd 100644
--- a/erts/emulator/test/mtx_SUITE_data/Makefile.src
+++ b/erts/emulator/test/mtx_SUITE_data/Makefile.src
@@ -28,8 +28,10 @@ LIBS = @ERTS_LIBS@
all: $(NIF_LIBS)
+WSL=@WSL@
+
mtx_SUITE.c: force_rebuild
- touch mtx_SUITE.c
+ $(WSL) touch mtx_SUITE.c
force_rebuild:
echo "Force rebuild to compensate for emulator type dependencies"
diff --git a/erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c b/erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c
index 46ee8b5540..6f662ae514 100644
--- a/erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c
+++ b/erts/emulator/test/mtx_SUITE_data/mtx_SUITE.c
@@ -39,6 +39,7 @@
#include <errno.h>
#include <stdio.h>
+#include <string.h>
static int
fail(const char *file, int line, const char *function, const char *assertion);
diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl
index 739e6214ec..2cc3b8db32 100644
--- a/erts/emulator/test/nif_SUITE.erl
+++ b/erts/emulator/test/nif_SUITE.erl
@@ -33,6 +33,9 @@
init_per_testcase/2, end_per_testcase/2,
basic/1, reload_error/1, upgrade/1, heap_frag/1,
t_on_load/1,
+ t_load_race/1,
+ t_call_nif_early/1,
+ load_traced_nif/1,
select/1, select_steal/1,
monitor_process_a/1,
monitor_process_b/1,
@@ -93,6 +96,9 @@ all() ->
{group, monitor},
monitor_frenzy,
hipe,
+ t_load_race,
+ t_call_nif_early,
+ load_traced_nif,
binaries, get_string, get_atom, maps, api_macros, from_array,
iolist_as_binary, resource, resource_binary,
threading, send, send2, send3,
@@ -500,6 +506,141 @@ t_on_load(Config) when is_list(Config) ->
verify_tmpmem(TmpMem),
ok.
+%% Test erlang:load_nif/2 waiting for code_write_permission.
+t_load_race(Config) ->
+ Data = proplists:get_value(data_dir, Config),
+ File = filename:join(Data, "nif_mod"),
+ {ok,nif_mod,Bin} = compile:file(File, [binary,return_errors]),
+ {module,nif_mod} = erlang:load_module(nif_mod,Bin),
+ try
+ erts_debug:set_internal_state(code_write_permission, true),
+ Papa = self(),
+ spawn_link(fun() ->
+ ok = nif_mod:load_nif_lib(Config, 1),
+ Papa ! "NIF loaded"
+ end),
+ timer:sleep(100),
+ timeout = receive_any(0)
+ after
+ true = erts_debug:set_internal_state(code_write_permission, false)
+ end,
+
+ "NIF loaded" = receive_any(),
+ true = erlang:delete_module(nif_mod),
+ true = erlang:purge_module(nif_mod),
+ ok.
+
+-define(NIFS_NOT_LOADED, 0).
+-define(NIFS_LOADED, 1).
+
+%% Test calling functions while loading their NIF implementation.
+%% Verify the change from beam to NIF is atomic.
+t_call_nif_early(Config) ->
+ Flag = atomics:new(1, []),
+ atomics:put(Flag, 1, ?NIFS_NOT_LOADED),
+
+ Data = proplists:get_value(data_dir, Config),
+ File = filename:join(Data, "nif_mod"),
+ {ok,nif_mod,Bin} = compile:file(File, [binary,return_errors]),
+ {module,nif_mod} = erlang:load_module(nif_mod,Bin),
+
+ Papa = self(),
+ NProcs = erlang:system_info(schedulers_online),
+ Pids = [spawn_opt(fun() ->
+ undefined = nif_mod:lib_version(),
+ Papa ! ready,
+ early_caller_1(Flag, 1)
+ end,
+ [link, {scheduler,N}])
+ || N <- lists:seq(1, NProcs)],
+
+ [begin ok = receive ready -> ok end end
+ || _ <- Pids],
+
+ ok = nif_mod:load_nif_lib(Config, 1),
+ atomics:put(Flag, 1, ?NIFS_LOADED),
+
+ %% Wait for all scheduled load finishers to complete.
+ erts_debug:set_internal_state(wait, thread_progress),
+ erts_debug:set_internal_state(wait, thread_progress),
+ erts_debug:set_internal_state(wait, thread_progress),
+
+ [P ! done || P <- Pids],
+ ok.
+
+
+early_caller_1(Flag, BeamCnt) ->
+ case atomics:get(Flag, 1) of
+ ?NIFS_NOT_LOADED ->
+ case nif_mod_lib_version(BeamCnt) of
+ undefined -> % Beam called
+ early_caller_1(Flag, BeamCnt+1);
+ 1 -> % Nif called
+ atomics:put(Flag, 1, ?NIFS_LOADED),
+ early_caller_2(BeamCnt, 1)
+ end;
+ ?NIFS_LOADED ->
+ %% Someone else called a NIF
+ early_caller_2(BeamCnt, 0)
+ end.
+
+early_caller_2(BeamCnt, NifCnt) ->
+ %% From now on we only expect to call NIFs
+ 1 = nif_mod_lib_version(BeamCnt+NifCnt),
+ receive done ->
+ io:format("Beam calls=~p, Nif calls=~p\n", [BeamCnt, NifCnt+1])
+ after 0 ->
+ early_caller_2(BeamCnt, NifCnt+1)
+ end.
+
+nif_mod_lib_version(Cnt) ->
+ case Cnt rem 2 of
+ 0 -> nif_mod:lib_version();
+ 1 -> nif_mod:lib_version_check()
+ end.
+
+
+%% Test load of module where a NIF stub is already traced.
+load_traced_nif(Config) when is_list(Config) ->
+ TmpMem = tmpmem(),
+
+ Data = proplists:get_value(data_dir, Config),
+ File = filename:join(Data, "nif_mod"),
+ {ok,nif_mod,Bin} = compile:file(File, [binary,return_errors]),
+ {module,nif_mod} = erlang:load_module(nif_mod,Bin),
+
+ Tracee = spawn_link(fun Loop() -> receive {lib_version,ExpRet} ->
+ ExpRet = nif_mod:lib_version()
+ end,
+ Loop()
+ end),
+ 1 = erlang:trace_pattern({nif_mod,lib_version,0}, true, [local]),
+ 1 = erlang:trace(Tracee, true, [call]),
+
+ Tracee ! {lib_version, undefined},
+ {trace, Tracee, call, {nif_mod,lib_version,[]}} = receive_any(1000),
+
+ ok = nif_mod:load_nif_lib(Config, 1),
+
+ Tracee ! {lib_version, 1},
+ {trace, Tracee, call, {nif_mod,lib_version,[]}} = receive_any(1000),
+
+ %% Wait for NIF loading to finish and write final call_nif instruction
+ timer:sleep(500),
+
+ Tracee ! {lib_version, 1},
+ {trace, Tracee, call, {nif_mod,lib_version,[]}} = receive_any(1000),
+
+ true = erlang:delete_module(nif_mod),
+ true = erlang:purge_module(nif_mod),
+
+ unlink(Tracee),
+ exit(Tracee, kill),
+
+ verify_tmpmem(TmpMem),
+ ok.
+
+
-define(ERL_NIF_SELECT_READ, (1 bsl 0)).
-define(ERL_NIF_SELECT_WRITE, (1 bsl 1)).
-define(ERL_NIF_SELECT_STOP, (1 bsl 2)).
@@ -1293,6 +1434,21 @@ maps(Config) when is_list(Config) ->
end,
{1,#{}}),
+ M5 = lists:foldl(fun(N, MapIn) ->
+ {1, #{N := value}=MapOut} = make_map_put_nif(MapIn, N, value),
+ MapOut
+ end,
+ #{},
+ lists:seq(1,40)),
+ M6 = lists:foldl(fun(N, MapIn) ->
+ {1, MapOut} = make_map_remove_nif(MapIn, N),
+ ok = maps:get(N, MapOut, ok),
+ MapOut
+ end,
+ M5,
+ lists:seq(1,40)),
+ true = (M6 =:= #{}),
+
has_duplicate_keys = maps_from_list_nif([{1,1},{1,1}]),
verify_tmpmem(TmpMem),
@@ -2520,26 +2676,7 @@ dummy_call(_) ->
ok.
tmpmem() ->
- case erlang:system_info({allocator,temp_alloc}) of
- false -> undefined;
- MemInfo ->
- MSBCS = lists:foldl(
- fun ({instance, 0, _}, Acc) ->
- Acc; % Ignore instance 0
- ({instance, _, L}, Acc) ->
- {value,{_,MBCS}} = lists:keysearch(mbcs, 1, L),
- {value,{_,SBCS}} = lists:keysearch(sbcs, 1, L),
- [MBCS,SBCS | Acc]
- end,
- [],
- MemInfo),
- lists:foldl(
- fun(L, {Bl0,BlSz0}) ->
- {value,{_,Bl,_,_}} = lists:keysearch(blocks, 1, L),
- {value,{_,BlSz,_,_}} = lists:keysearch(blocks_size, 1, L),
- {Bl0+Bl,BlSz0+BlSz}
- end, {0,0}, MSBCS)
- end.
+ erts_debug:alloc_blocks_size(temp_alloc).
verify_tmpmem(MemInfo) ->
%%wait_for_test_procs(),
diff --git a/erts/emulator/test/nif_SUITE_data/Makefile.src b/erts/emulator/test/nif_SUITE_data/Makefile.src
index de06026780..69dd2d6757 100644
--- a/erts/emulator/test/nif_SUITE_data/Makefile.src
+++ b/erts/emulator/test/nif_SUITE_data/Makefile.src
@@ -24,7 +24,8 @@ tsd@dll@: tester.c testcase_driver.h
DRIVER_DIR = ../erl_drv_thread_SUITE_data
+WSL=@WSL@
+
basic.c rwlock.c tsd.c: $(DRIVER_DIR)/$@
- cat head.txt > $@
- cat $(DRIVER_DIR)/$@ | sed -e 's/erl_drv_/enif_/g' -e 's/driver_/enif_/g' -e 's/ErlDrv/ErlNif/g' >> $@
- cat tail.txt >> $@
+ $(WSL) sed -e 's/erl_drv_/enif_/g' -e 's/driver_/enif_/g' -e 's/ErlDrv/ErlNif/g' $(DRIVER_DIR)/$@ > $@.tmp
+ $(WSL) cat head.txt $@.tmp tail.txt > $@
diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
index f3cab31f33..93708fa99c 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
+++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
@@ -2123,6 +2123,19 @@ static ERL_NIF_TERM make_map_put_nif(ErlNifEnv* env, int argc, const ERL_NIF_TER
{
ERL_NIF_TERM map_out = enif_make_atom(env, "undefined");
int ret = enif_make_map_put(env, argv[0], argv[1], argv[2], &map_out);
+
+ /* build same map in dynamic env */
+ ErlNifEnv* dynenv = enif_alloc_env();
+ ERL_NIF_TERM map_out2 = enif_make_atom(env, "undefined");
+ int ret2 = enif_make_map_put(dynenv,
+ enif_make_copy(dynenv, argv[0]),
+ enif_make_copy(dynenv, argv[1]),
+ enif_make_copy(dynenv, argv[2]),
+ &map_out2);
+ if (ret != ret2 || !enif_is_identical(map_out, map_out2))
+ map_out = enif_make_string(env, "dynenv failure", ERL_NIF_LATIN1);
+ enif_free_env(dynenv);
+
return enif_make_tuple2(env, enif_make_int(env,ret), map_out);
}
static ERL_NIF_TERM get_map_value_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
@@ -2136,12 +2149,37 @@ static ERL_NIF_TERM make_map_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_
{
ERL_NIF_TERM map_out = enif_make_atom(env, "undefined");
int ret = enif_make_map_update(env, argv[0], argv[1], argv[2], &map_out);
+
+ /* build same map in dynamic env */
+ ErlNifEnv* dynenv = enif_alloc_env();
+ ERL_NIF_TERM map_out2 = enif_make_atom(env, "undefined");
+ int ret2 = enif_make_map_update(dynenv,
+ enif_make_copy(dynenv, argv[0]),
+ enif_make_copy(dynenv, argv[1]),
+ enif_make_copy(dynenv, argv[2]),
+ &map_out2);
+ if (ret != ret2 || !enif_is_identical(map_out, map_out2))
+ map_out = enif_make_string(env, "dynenv failure", ERL_NIF_LATIN1);
+ enif_free_env(dynenv);
+
return enif_make_tuple2(env, enif_make_int(env,ret), map_out);
}
static ERL_NIF_TERM make_map_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ERL_NIF_TERM map_out = enif_make_atom(env, "undefined");
int ret = enif_make_map_remove(env, argv[0], argv[1], &map_out);
+
+ /* build same map in dynamic env */
+ ErlNifEnv* dynenv = enif_alloc_env();
+ ERL_NIF_TERM map_out2 = enif_make_atom(env, "undefined");
+ int ret2 = enif_make_map_remove(dynenv,
+ enif_make_copy(dynenv, argv[0]),
+ enif_make_copy(dynenv, argv[1]),
+ &map_out2);
+ if (ret != ret2 || !enif_is_identical(map_out, map_out2))
+ map_out = enif_make_string(env, "dynenv failure", ERL_NIF_LATIN1);
+ enif_free_env(dynenv);
+
return enif_make_tuple2(env, enif_make_int(env,ret), map_out);
}
@@ -3345,15 +3383,15 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
ret = enif_make_resource(env, ioq);
enif_release_resource(ioq);
return ret;
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "inspect"))) {
+ } else if (argc >= 2 && enif_is_identical(argv[0], enif_make_atom(env, "inspect"))) {
ErlNifIOVec vec, *iovec = NULL;
int i, iovcnt;
ERL_NIF_TERM *elems, tail, list;
ErlNifEnv *myenv = NULL;
- if (enif_is_identical(argv[2], enif_make_atom(env, "use_stack")))
+ if (argv >= 3 && enif_is_identical(argv[2], enif_make_atom(env, "use_stack")))
iovec = &vec;
- if (enif_is_identical(argv[3], enif_make_atom(env, "use_env")))
+ if (argc >= 4 && enif_is_identical(argv[3], enif_make_atom(env, "use_env")))
myenv = env;
if (!enif_inspect_iovec(myenv, ~(size_t)0, argv[1], &tail, &iovec))
return enif_make_badarg(env);
@@ -3378,13 +3416,13 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
list = enif_make_list_from_array(env, elems, iovcnt);
enif_free(elems);
return list;
- } else {
+ } else if (argc >= 2) {
unsigned skip;
if (!enif_get_resource(env, argv[1], ioq_resource_type, (void**)&ioq)
|| !ioq->q)
return enif_make_badarg(env);
- if (enif_is_identical(argv[0], enif_make_atom(env, "example"))) {
+ if (argc == 3 && enif_is_identical(argv[0], enif_make_atom(env, "example"))) {
#ifndef __WIN32__
int fd[2], res = 0, cnt = 0;
ERL_NIF_TERM tail;
@@ -3434,7 +3472,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
enif_ioq_destroy(ioq->q);
ioq->q = NULL;
return enif_make_atom(env, "false");
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "enqv"))) {
+ } else if (argc >= 4 && enif_is_identical(argv[0], enif_make_atom(env, "enqv"))) {
ErlNifIOVec vec, *iovec = &vec;
ERL_NIF_TERM tail;
@@ -3446,7 +3484,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
return enif_make_badarg(env);
return enif_make_atom(env, "true");
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "enqb"))) {
+ } else if (argc >= 4 && enif_is_identical(argv[0], enif_make_atom(env, "enqb"))) {
ErlNifBinary bin;
if (!enif_get_uint(env, argv[3], &skip) ||
!enif_inspect_binary(env, argv[2], &bin))
@@ -3456,7 +3494,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
return enif_make_badarg(env);
return enif_make_atom(env, "true");
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "enqbraw"))) {
+ } else if (argc >= 4 && enif_is_identical(argv[0], enif_make_atom(env, "enqbraw"))) {
ErlNifBinary bin;
ErlNifBinary localbin;
int i;
@@ -3480,7 +3518,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
return enif_make_atom(env, "false");
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "peek"))) {
+ } else if (argc >= 3 && enif_is_identical(argv[0], enif_make_atom(env, "peek"))) {
int iovlen, num, i, off = 0;
SysIOVec *iov = enif_ioq_peek(ioq->q, &iovlen);
ErlNifBinary bin;
@@ -3496,7 +3534,7 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
return enif_make_binary(env, &bin);
- } else if (enif_is_identical(argv[0], enif_make_atom(env, "deq"))) {
+ } else if (argc >= 3 && enif_is_identical(argv[0], enif_make_atom(env, "deq"))) {
int num;
size_t sz;
ErlNifUInt64 sz64;
@@ -3634,7 +3672,6 @@ static ErlNifFunc nif_funcs[] =
{"release_resource", 1, release_resource},
{"release_resource_from_thread", 1, release_resource_from_thread},
{"last_resource_dtor_call_nif", 0, last_resource_dtor_call_nif},
- {"make_new_resource", 2, make_new_resource},
{"check_is", 11, check_is},
{"check_is_exception", 0, check_is_exception},
{"length_test", 6, length_test},
diff --git a/erts/emulator/test/nif_SUITE_data/nif_api_2_0/erl_nif.h b/erts/emulator/test/nif_SUITE_data/nif_api_2_0/erl_nif.h
index 4b2b7550e5..90ed8da0b6 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_api_2_0/erl_nif.h
+++ b/erts/emulator/test/nif_SUITE_data/nif_api_2_0/erl_nif.h
@@ -164,11 +164,7 @@ extern TWinDynNifCallbacks WinDynNifCallbacks;
#else
# define ERL_NIF_INIT_GLOB
# define ERL_NIF_INIT_BODY
-# if defined(VXWORKS)
-# define ERL_NIF_INIT_DECL(MODNAME) ErlNifEntry* MODNAME ## _init(void)
-# else
# define ERL_NIF_INIT_DECL(MODNAME) ErlNifEntry* nif_init(void)
-# endif
#endif
diff --git a/erts/emulator/test/nif_SUITE_data/nif_mod.c b/erts/emulator/test/nif_SUITE_data/nif_mod.c
index 90c24f6448..f2f49d0bde 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_mod.c
+++ b/erts/emulator/test/nif_SUITE_data/nif_mod.c
@@ -357,10 +357,13 @@ static ErlNifFunc nif_funcs[] =
{"nif_api_version", 0, nif_api_version},
{"get_priv_data_ptr", 0, get_priv_data_ptr},
{"make_new_resource", 2, make_new_resource},
- {"get_resource", 2, get_resource}
+ {"get_resource", 2, get_resource},
#ifdef HAVE_ENIF_MONITOR_PROCESS
- ,{"monitor_process", 3, monitor_process}
+ {"monitor_process", 3, monitor_process},
#endif
+ /* Keep lib_version_check last to maximize the loading "patch distance"
+ between it and lib_version */
+ {"lib_version_check", 0, lib_version}
};
#if NIF_LIB_VER != 3
diff --git a/erts/emulator/test/nif_SUITE_data/nif_mod.erl b/erts/emulator/test/nif_SUITE_data/nif_mod.erl
index 9b886220aa..1fdaf5dae4 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_mod.erl
+++ b/erts/emulator/test/nif_SUITE_data/nif_mod.erl
@@ -22,7 +22,8 @@
-include_lib("common_test/include/ct.hrl").
--export([load_nif_lib/2, load_nif_lib/3, start/0, lib_version/0,
+-export([load_nif_lib/2, load_nif_lib/3, start/0,
+ lib_version/0, lib_version_check/0,
get_priv_data_ptr/0, make_new_resource/2, get_resource/2,
monitor_process/3]).
@@ -92,5 +93,16 @@ make_new_resource(_,_) -> ?nif_stub.
get_resource(_,_) -> ?nif_stub.
monitor_process(_,_,_) -> ?nif_stub.
+lib_version_check() ->
+ %% Do a recursive call to test that we are able to return
+ %% while this function has been NIF patched.
+ X = lib_version(),
+ case lib_version() of
+ X -> X;
+ V ->
+ undefined = X,
+ V
+ end.
+
nif_stub_error(Line) ->
exit({nif_not_loaded,module,?MODULE,line,Line}).
diff --git a/erts/emulator/test/node_container_SUITE.erl b/erts/emulator/test/node_container_SUITE.erl
index f7a1662ee7..2beae8bf21 100644
--- a/erts/emulator/test/node_container_SUITE.erl
+++ b/erts/emulator/test/node_container_SUITE.erl
@@ -812,9 +812,19 @@ mkpidlist(N, Ps) -> mkpidlist(N-1, [spawn(fun () -> ok end)|Ps]).
iter_max_procs(Config) when is_list(Config) ->
NoMoreTests = make_ref(),
erlang:send_after(10000, self(), NoMoreTests),
- Res = chk_max_proc_line(),
- Res = chk_max_proc_line(),
- done = chk_max_proc_line_until(NoMoreTests, Res),
+
+ %% Disable logging to avoid "Too many processes" log which can
+ %% cause ct_logs to crash when trying to spawn "async print job".
+ #{level := LoggerLevel} = logger:get_primary_config(),
+ ok = logger:set_primary_config(level, none),
+ Res = try
+ R = chk_max_proc_line(),
+ R = chk_max_proc_line(),
+ done = chk_max_proc_line_until(NoMoreTests, R),
+ R
+ after
+ logger:set_primary_config(level, LoggerLevel)
+ end,
Cmt = io_lib:format("max processes = ~p; "
"process line length = ~p",
[element(2, Res), element(1, Res)]),
diff --git a/erts/emulator/test/nofrag_SUITE.erl b/erts/emulator/test/nofrag_SUITE.erl
index 8b1519ae36..d4c74579e2 100644
--- a/erts/emulator/test/nofrag_SUITE.erl
+++ b/erts/emulator/test/nofrag_SUITE.erl
@@ -22,6 +22,11 @@
-include_lib("common_test/include/ct.hrl").
+%% This suite alters the return values of functions which breaks certain
+%% assumptions made by the compiler, so we have to turn off module-level type
+%% optimization to be safe.
+-compile(no_module_opt).
+
-export([all/0, suite/0,
error_handler/1,error_handler_apply/1,
error_handler_fixed_apply/1,error_handler_fun/1,
diff --git a/erts/emulator/test/num_bif_SUITE.erl b/erts/emulator/test/num_bif_SUITE.erl
index 6b834705cf..fded36431e 100644
--- a/erts/emulator/test/num_bif_SUITE.erl
+++ b/erts/emulator/test/num_bif_SUITE.erl
@@ -544,7 +544,7 @@ t_string_to_integer(Config) when is_list(Config) ->
test_sti(268435455),
test_sti(-268435455),
- % Interesting values around 2-pows, such as MIN_SMALL and MAX_SMALL.
+ %% Interesting values around 2-pows, such as MIN_SMALL and MAX_SMALL.
lists:foreach(fun(Bits) ->
N = 1 bsl Bits,
test_sti(N - 1),
@@ -553,11 +553,11 @@ t_string_to_integer(Config) when is_list(Config) ->
end,
lists:seq(16, 130)),
- %% Bignums.
+ %% Bignums
test_sti(123456932798748738738,16),
test_sti(list_to_integer(lists:duplicate(2000, $1))),
- %% unalign string
+ %% Unaligned string
Str = <<"10">>,
UnalignStr = <<0:3, (id(Str))/binary, 0:5>>,
<<_:3, SomeStr:2/binary, _:5>> = id(UnalignStr),
@@ -568,32 +568,39 @@ t_string_to_integer(Config) when is_list(Config) ->
{'EXIT', {badarg, _}} =
(catch binary_to_integer(Value)),
{'EXIT', {badarg, _}} =
- (catch erlang:list_to_integer(Value))
+ (catch list_to_integer(Value))
end,[atom,1.2,0.0,[$1,[$2]]]),
- % Default base error cases
+ %% Default base error cases
lists:foreach(fun(Value) ->
{'EXIT', {badarg, _}} =
- (catch erlang:binary_to_integer(
- list_to_binary(Value))),
+ (catch binary_to_integer(list_to_binary(Value))),
{'EXIT', {badarg, _}} =
- (catch erlang:list_to_integer(Value))
+ (catch list_to_integer(Value))
end,["1.0"," 1"," -1","","+"]),
- % Custom base error cases
+ %% Custom base error cases
lists:foreach(fun({Value,Base}) ->
{'EXIT', {badarg, _}} =
- (catch binary_to_integer(
- list_to_binary(Value),Base)),
+ (catch binary_to_integer(list_to_binary(Value), Base)),
{'EXIT', {badarg, _}} =
- (catch erlang:list_to_integer(Value,Base))
- end,[{" 1",1},{" 1",37},{"2",2},{"B",11},{"b",11},{":", 16},
- {"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111z",16},
- {"1z111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",16},
- {"111z11111111",16}]),
-
- %% log2 calculation overflow bug in do_integer_to_list (OTP-12624)
- %% Would crash with segv
+ (catch list_to_integer(Value, Base))
+ end,
+ [{" 1",1},{" 1",37},{"2",2},{"B",11},{"b",11},{":", 16},
+ {"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111z",16},
+ {"1z111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",16},
+ {"111z11111111",16},
+ %% Untagging atoms at the beginning of atom.names
+ %% would produce a base in the valid range.
+ {"10",true}, %Base 4
+ {"10",'_'}, %Base 8
+ {"10",nonode@nohost}, %Base 12
+ {"10",'$end_of_table'}, %Base 16
+ {"10",''} %Base 20
+ ]),
+
+ %% log2 calculation overflow bug in do_integer_to_list (OTP-12624).
+ %% Would crash with segementation fault.
0 = list_to_integer(lists:duplicate(10000000,$0)),
ok.
diff --git a/erts/emulator/test/os_signal_SUITE.erl b/erts/emulator/test/os_signal_SUITE.erl
index 6bafb0e18c..7bd8985dc7 100644
--- a/erts/emulator/test/os_signal_SUITE.erl
+++ b/erts/emulator/test/os_signal_SUITE.erl
@@ -275,6 +275,15 @@ t_sigalrm(_Config) ->
ok.
t_sigchld_fork(_Config) ->
+ case test_server:is_asan() of
+ true ->
+ %% Avoid false leak reports from forked process
+ {skip, "Address sanitizer"};
+ false ->
+ sigchld_fork()
+ end.
+
+sigchld_fork() ->
Pid1 = setup_service(),
ok = os:set_signal(sigchld, handle),
{ok,OsPid} = os_signal_SUITE:fork(),
diff --git a/erts/emulator/test/persistent_term_SUITE.erl b/erts/emulator/test/persistent_term_SUITE.erl
index 09dd764dbc..f4511eb483 100644
--- a/erts/emulator/test/persistent_term_SUITE.erl
+++ b/erts/emulator/test/persistent_term_SUITE.erl
@@ -23,6 +23,7 @@
-export([all/0,suite/0,init_per_suite/1,end_per_suite/1,
basic/1,purging/1,sharing/1,get_trapping/1,
+ destruction/1,
info/1,info_trapping/1,killed_while_trapping/1,
off_heap_values/1,keys/1,collisions/1,
init_restart/1, put_erase_trapping/1,
@@ -38,11 +39,13 @@ suite() ->
all() ->
[basic,purging,sharing,get_trapping,info,info_trapping,
+ destruction,
killed_while_trapping,off_heap_values,keys,collisions,
init_restart, put_erase_trapping, killed_while_trapping_put,
killed_while_trapping_erase].
init_per_suite(Config) ->
+ erts_debug:set_internal_state(available_internal_state, true),
%% Put a term in the dict so that we know that the testcases handle
%% stray terms left by stdlib or other test suites.
persistent_term:put(init_per_suite, {?MODULE}),
@@ -50,6 +53,7 @@ init_per_suite(Config) ->
end_per_suite(Config) ->
persistent_term:erase(init_per_suite),
+ erts_debug:set_internal_state(available_internal_state, false),
Config.
basic(_Config) ->
@@ -152,19 +156,25 @@ purging_tester(Parent, Key) ->
receive
{Parent,erased} ->
{'EXIT',{badarg,_}} = (catch persistent_term:get(Key)),
- purging_tester_1(Term);
+ purging_tester_1(Term, 1);
{Parent,replaced} ->
{?MODULE,new} = persistent_term:get(Key),
- purging_tester_1(Term)
+ purging_tester_1(Term, 1)
end.
%% Wait for the term to be copied into this process.
-purging_tester_1(Term) ->
+purging_tester_1(Term, Timeout) ->
purging_check_term(Term),
- receive after 1 -> ok end,
+ receive after Timeout -> ok end,
case erts_debug:size_shared(Term) of
0 ->
- purging_tester_1(Term);
+ case Timeout of
+ 1000 ->
+ flush_later_ops(),
+ purging_tester_1(Term, 1);
+ _ ->
+ purging_tester_1(Term, Timeout*10)
+ end;
Size ->
%% The term has been copied into this process.
purging_check_term(Term),
@@ -174,6 +184,83 @@ purging_tester_1(Term) ->
purging_check_term({term,[<<"abc",0:777/unit:8>>]}) ->
ok.
+%% Make sure terms are really deallocated when overwritten or erased.
+destruction(Config) ->
+ ok = erts_test_destructor:init(Config),
+
+ NKeys = 100,
+ Keys = lists:seq(0,NKeys-1),
+ [begin
+ V = erts_test_destructor:send(self(), K),
+ persistent_term:put({?MODULE,K}, V)
+ end
+ || K <- Keys],
+
+ %% Erase or overwrite all keys in "random" order.
+ lists:foldl(fun(_, K) ->
+ case erlang:phash2(K) band 1 of
+ 0 ->
+ %%io:format("erase key ~p\n", [K]),
+ persistent_term:erase({?MODULE,K});
+ 1 ->
+ %%io:format("replace key ~p\n", [K]),
+ persistent_term:put({?MODULE,K}, value)
+ end,
+ (K + 13) rem NKeys
+ end,
+ 17, Keys),
+
+ destruction_1(Keys).
+
+destruction_1(Keys) ->
+ erlang:garbage_collect(),
+
+ %% Receive all destruction messages
+ MsgLst = destruction_recv(length(Keys), [], 2),
+ ok = case lists:sort(MsgLst) of
+ Keys ->
+ ok;
+ _ ->
+ io:format("GOT ~p\n", [MsgLst]),
+ io:format("MISSING ~p\n", [Keys -- MsgLst]),
+ error
+ end,
+
+ %% Cleanup all remaining
+ [persistent_term:erase({?MODULE,K}) || K <- Keys],
+ ok.
+
+destruction_recv(0, Acc, _) ->
+ Acc;
+destruction_recv(N, Acc, Flush) ->
+ receive M ->
+ destruction_recv(N-1, [M | Acc], Flush)
+ after 1000 ->
+ io:format("TIMEOUT. Missing ~p destruction messages.\n", [N]),
+ case Flush of
+ 0 ->
+ Acc;
+ _ ->
+ io:format("Try flush last literal area cleanup...\n"),
+ flush_later_ops(),
+ destruction_recv(N, Acc, Flush-1)
+ end
+ end.
+
+%% Both persistent_term itself and erts_literal_are_collector use
+%% erts_schedule_thr_prgr_later_cleanup_op() to schedule purge and deallocation
+%% of literals. To avoid waiting forever on sleeping schedulers we flush
+%% all later ops to make these cleanup jobs go through.
+flush_later_ops() ->
+ try
+ erts_debug:set_internal_state(wait, thread_progress)
+ catch
+ error:system_limit ->
+ ok % already ongoing; called by other process
+ end,
+ ok.
+
+
%% Test that sharing is preserved when storing terms.
sharing(_Config) ->
@@ -517,17 +604,12 @@ colliding_keys() ->
%% Verify that the keys still collide (this will fail if the
%% internal hash function has been changed).
- erts_debug:set_internal_state(available_internal_state, true),
- try
- case erlang:system_info(wordsize) of
- 8 ->
- verify_colliding_keys(L);
- 4 ->
- %% Not guaranteed to collide on a 32-bit system.
- ok
- end
- after
- erts_debug:set_internal_state(available_internal_state, false)
+ case erlang:system_info(wordsize) of
+ 8 ->
+ verify_colliding_keys(L);
+ 4 ->
+ %% Not guaranteed to collide on a 32-bit system.
+ ok
end,
L.
@@ -611,19 +693,25 @@ chk({Info, _Initial} = Chk) ->
ok = persistent_term:put(Key, {term,Info}),
Term = persistent_term:get(Key),
true = persistent_term:erase(Key),
- chk_not_stuck(Term),
+ chk_not_stuck(Term, 1),
[persistent_term:erase(K) || {K, _} <- pget(Chk)],
ok.
-chk_not_stuck(Term) ->
+chk_not_stuck(Term, Timeout) ->
%% Hash tables to be deleted are put onto a queue.
%% Make sure that the queue isn't stuck by a table with
%% a non-zero ref count.
case erts_debug:size_shared(Term) of
0 ->
- erlang:yield(),
- chk_not_stuck(Term);
+ receive after Timeout -> ok end,
+ case Timeout of
+ 1000 ->
+ flush_later_ops(),
+ chk_not_stuck(Term, 1);
+ _ ->
+ chk_not_stuck(Term, Timeout*10)
+ end;
_ ->
ok
end.
@@ -633,7 +721,6 @@ pget({_, Initial}) ->
killed_while_trapping_put(_Config) ->
- erts_debug:set_internal_state(available_internal_state, true),
repeat(
fun() ->
NrOfPutsInChild = 10000,
@@ -647,10 +734,9 @@ killed_while_trapping_put(_Config) ->
do_erases(NrOfPutsInChild)
end,
10),
- erts_debug:set_internal_state(available_internal_state, false).
+ ok.
killed_while_trapping_erase(_Config) ->
- erts_debug:set_internal_state(available_internal_state, true),
repeat(
fun() ->
NrOfErases = 2500,
@@ -664,15 +750,14 @@ killed_while_trapping_erase(_Config) ->
do_erases(NrOfErases)
end,
10),
- erts_debug:set_internal_state(available_internal_state, false).
+ ok.
put_erase_trapping(_Config) ->
NrOfItems = 5000,
- erts_debug:set_internal_state(available_internal_state, true),
do_puts(NrOfItems, first),
do_puts(NrOfItems, second),
do_erases(NrOfItems),
- erts_debug:set_internal_state(available_internal_state, false).
+ ok.
do_puts(0, _) -> ok;
do_puts(NrOfPuts, ValuePrefix) ->
diff --git a/erts/emulator/test/persistent_term_SUITE_data/Makefile.src b/erts/emulator/test/persistent_term_SUITE_data/Makefile.src
new file mode 100644
index 0000000000..29b7fcb647
--- /dev/null
+++ b/erts/emulator/test/persistent_term_SUITE_data/Makefile.src
@@ -0,0 +1,8 @@
+
+NIF_LIBS = erts_test_destructor@dll@
+
+all: $(NIF_LIBS)
+
+@SHLIB_RULES@
+
+$(NIF_LIBS): erts_test_destructor.c
diff --git a/erts/emulator/test/persistent_term_SUITE_data/erts_test_destructor.c b/erts/emulator/test/persistent_term_SUITE_data/erts_test_destructor.c
new file mode 100644
index 0000000000..808334f1c4
--- /dev/null
+++ b/erts/emulator/test/persistent_term_SUITE_data/erts_test_destructor.c
@@ -0,0 +1,83 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+#include <erl_nif.h>
+
+#include <stdio.h>
+
+
+static ErlNifResourceType* resource_type;
+static void resource_dtor(ErlNifEnv* env, void* obj);
+
+typedef struct {
+ ErlNifPid to;
+ ERL_NIF_TERM msg;
+ ErlNifEnv* msg_env;
+} DtorSender;
+
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ resource_type = enif_open_resource_type(env,NULL,"DtorSender",resource_dtor,
+ ERL_NIF_RT_CREATE, NULL);
+ return 0;
+}
+
+static ERL_NIF_TERM is_loaded_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ return enif_make_atom(env, "true");
+}
+
+static ERL_NIF_TERM send_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ DtorSender *p;
+ ErlNifPid pid;
+ ERL_NIF_TERM res;
+
+ p = enif_alloc_resource(resource_type, sizeof(DtorSender));
+
+ if (!enif_get_local_pid(env, argv[0], &p->to)) {
+ p->msg_env = NULL;
+ enif_release_resource(p);
+ return enif_make_badarg(env);
+ }
+ p->msg_env = enif_alloc_env();
+ p->msg = enif_make_copy(p->msg_env, argv[1]);
+ res = enif_make_resource(env, p);
+ enif_release_resource(p);
+ return res;
+}
+
+static void resource_dtor(ErlNifEnv* env, void* obj)
+{
+ DtorSender *p = (DtorSender*)obj;
+
+ if (p->msg_env) {
+ enif_send(env, &p->to, p->msg_env, p->msg);
+ enif_free(p->msg_env);
+ }
+}
+
+
+static ErlNifFunc nif_funcs[] =
+{
+ {"is_loaded", 0, is_loaded_nif},
+ {"send", 2, send_nif}
+};
+
+ERL_NIF_INIT(erts_test_destructor,nif_funcs,load,NULL,NULL,NULL)
diff --git a/erts/emulator/test/port_SUITE.erl b/erts/emulator/test/port_SUITE.erl
index eb9b94a316..8a67bf7512 100644
--- a/erts/emulator/test/port_SUITE.erl
+++ b/erts/emulator/test/port_SUITE.erl
@@ -1052,7 +1052,9 @@ huge_env(Config) when is_list(Config) ->
%% Test to spawn program with command payload buffer
%% just around pipe capacity (9f779819f6bda734c5953468f7798)
pipe_limit_env(Config) when is_list(Config) ->
+ WSL = os:getenv("WSLENV") =/= false,
Cmd = case os:type() of
+ {win32,_} when WSL -> "cmd.exe /q /c wsl true";
{win32,_} -> "cmd /q /c true";
_ -> "true"
end,
@@ -1706,7 +1708,11 @@ spawn_executable(Config) when is_list(Config) ->
ok.
unregister_name(Config) when is_list(Config) ->
- true = register(crash, open_port({spawn, "sleep 100"}, [])),
+ Cmd = case os:getenv("WSLENV") of
+ false -> "sleep 5";
+ _ -> "wsl.exe sleep 5"
+ end,
+ true = register(crash, open_port({spawn, Cmd}, [])),
true = unregister(crash).
test_bat_file(Dir) ->
diff --git a/erts/emulator/test/port_bif_SUITE.erl b/erts/emulator/test/port_bif_SUITE.erl
index b71f8f2715..c238d0bb40 100644
--- a/erts/emulator/test/port_bif_SUITE.erl
+++ b/erts/emulator/test/port_bif_SUITE.erl
@@ -487,11 +487,16 @@ busy_options(Config) when is_list(Config) ->
process_flag(trap_exit, true),
Tester = self(),
- HejLoop = fun (Prt, _F, 1000) ->
+
+ %% We want this loop to write enough data to tirgger the busy limits,
+ %% this means that it first has to fill the pipe buffer on Linux which
+ %% can be anything from 4k to 1 MB, so we make sure to fill it with
+ %% at least 2 MB of data here.
+ HejLoop = fun (Prt, _F, N) when N > (2 bsl 20) ->
Prt;
(Prt, F, N) ->
Prt ! {Tester, {command, Data}},
- F(Prt, F, N+1)
+ F(Prt, F, N + iolist_size(Data))
end,
io:format("Test1...~n", []),
@@ -640,6 +645,3 @@ wait_until_aux(Fun, End) ->
end
end
end.
-
-
-
diff --git a/erts/emulator/test/port_bif_SUITE_data/port_test.c b/erts/emulator/test/port_bif_SUITE_data/port_test.c
index 923ab99ccc..ef6d12dc93 100644
--- a/erts/emulator/test/port_bif_SUITE_data/port_test.c
+++ b/erts/emulator/test/port_bif_SUITE_data/port_test.c
@@ -10,6 +10,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include <ctype.h>
#ifndef __WIN32__
#include <unistd.h>
@@ -32,7 +33,7 @@
exit(1); \
}
-#define MAIN(argc, argv) main(argc, argv)
+#define MAIN(argc, argv) int main(argc, argv)
extern int errno;
diff --git a/erts/emulator/test/process_SUITE.erl b/erts/emulator/test/process_SUITE.erl
index b7971856b2..5619807791 100644
--- a/erts/emulator/test/process_SUITE.erl
+++ b/erts/emulator/test/process_SUITE.erl
@@ -64,13 +64,24 @@
gc_request_when_gc_disabled/1,
gc_request_blast_when_gc_disabled/1,
otp_16436/1,
- otp_16642/1]).
+ otp_16642/1,
+ spawn_huge_arglist/1,
+ spawn_request_bif/1,
+ spawn_request_monitor_demonitor/1,
+ spawn_request_monitor_child_exit/1,
+ spawn_request_link_child_exit/1,
+ spawn_request_link_parent_exit/1,
+ spawn_request_abandon_bif/1,
+ dist_spawn_monitor/1,
+ spawn_old_node/1,
+ spawn_new_node/1,
+ spawn_request_reply_option/1]).
-export([prio_server/2, prio_client/2, init/1, handle_event/2]).
-export([init_per_testcase/2, end_per_testcase/2]).
-export([hangaround/2, processes_bif_test/0, do_processes/1,
- processes_term_proc_list_test/1]).
+ processes_term_proc_list_test/1, huge_arglist_child/255]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -91,7 +102,19 @@ all() ->
bump_reductions, low_prio, yield, yield2, otp_4725,
bad_register, garbage_collect, process_info_messages,
process_flag_badarg, process_flag_heap_size,
- spawn_opt_heap_size, spawn_opt_max_heap_size, otp_6237,
+ spawn_opt_heap_size, spawn_opt_max_heap_size,
+ spawn_huge_arglist,
+ spawn_request_bif,
+ spawn_request_monitor_demonitor,
+ spawn_request_monitor_child_exit,
+ spawn_request_link_child_exit,
+ spawn_request_link_parent_exit,
+ spawn_request_abandon_bif,
+ dist_spawn_monitor,
+ spawn_old_node,
+ spawn_new_node,
+ spawn_request_reply_option,
+ otp_6237,
{group, processes_bif},
{group, otp_7738}, garb_other_running,
{group, system_task}].
@@ -1105,7 +1128,7 @@ process_info_status_handled_signal(Config) when is_list(Config) ->
%% And a bug where process_info(reductions) on a process which was releasing its
%% main lock during execution could result in negative reduction diffs.
process_info_reductions(Config) when is_list(Config) ->
- {S1, S2} = case erlang:system_info(schedulers) of
+ {S1, S2} = case erlang:system_info(schedulers_online) of
1 -> {1,1};
_ -> {1,2}
end,
@@ -1590,10 +1613,13 @@ process_flag_badarg(Config) when is_list(Config) ->
chk_badarg(fun () -> process_flag(priority, 4711) end),
chk_badarg(fun () -> process_flag(save_calls, hmmm) end),
- P= spawn_link(fun () -> receive die -> ok end end),
+ {P,Mref} = spawn_monitor(fun () -> receive "in vain" -> no end end),
chk_badarg(fun () -> process_flag(P, save_calls, hmmm) end),
chk_badarg(fun () -> process_flag(gurka, save_calls, hmmm) end),
- P ! die,
+ exit(P, die),
+ chk_badarg(fun () -> process_flag(P, save_calls, 0) end),
+ {'DOWN', Mref, process, P, die} = receive M -> M end,
+ chk_badarg(fun () -> process_flag(P, save_calls, 0) end),
ok.
-include_lib("stdlib/include/ms_transform.hrl").
@@ -2251,7 +2277,7 @@ max_heap_size_test(Option, Size, Kill, ErrorLogger)
when is_map(Option); is_integer(Option) ->
max_heap_size_test([{max_heap_size, Option}], Size, Kill, ErrorLogger);
max_heap_size_test(Option, Size, Kill, ErrorLogger) ->
- OomFun = fun F() -> timer:sleep(5),[lists:seq(1,1000)|F()] end,
+ OomFun = fun () -> oom_fun([]) end,
Pid = spawn_opt(OomFun, Option),
{max_heap_size, MHSz} = erlang:process_info(Pid, max_heap_size),
ct:log("Default: ~p~nOption: ~p~nProc: ~p~n",
@@ -2294,6 +2320,13 @@ max_heap_size_test(Option, Size, Kill, ErrorLogger) ->
%% Make sure that there are no unexpected messages.
receive_unexpected().
+oom_fun(Acc0) ->
+ %% This is tail-recursive since the compiler is smart enough to figure
+ %% out that a body-recursive variant never returns, and loops forever
+ %% without keeping the list alive.
+ timer:sleep(5),
+ oom_fun([lists:seq(1, 1000) | Acc0]).
+
receive_error_messages(Pid) ->
receive
{error, _, {emulator, _, [Pid|_]}} ->
@@ -2328,6 +2361,963 @@ handle_event(Event, Pid) ->
Pid ! Event,
{ok, Pid}.
+huge_arglist_child(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9,
+ A10, A11, A12, A13, A14, A15, A16, A17, A18, A19,
+ A20, A21, A22, A23, A24, A25, A26, A27, A28, A29,
+ A30, A31, A32, A33, A34, A35, A36, A37, A38, A39,
+ A40, A41, A42, A43, A44, A45, A46, A47, A48, A49,
+ A50, A51, A52, A53, A54, A55, A56, A57, A58, A59,
+ A60, A61, A62, A63, A64, A65, A66, A67, A68, A69,
+ A70, A71, A72, A73, A74, A75, A76, A77, A78, A79,
+ A80, A81, A82, A83, A84, A85, A86, A87, A88, A89,
+ A90, A91, A92, A93, A94, A95, A96, A97, A98, A99,
+ A100, A101, A102, A103, A104, A105, A106, A107, A108, A109,
+ A110, A111, A112, A113, A114, A115, A116, A117, A118, A119,
+ A120, A121, A122, A123, A124, A125, A126, A127, A128, A129,
+ A130, A131, A132, A133, A134, A135, A136, A137, A138, A139,
+ A140, A141, A142, A143, A144, A145, A146, A147, A148, A149,
+ A150, A151, A152, A153, A154, A155, A156, A157, A158, A159,
+ A160, A161, A162, A163, A164, A165, A166, A167, A168, A169,
+ A170, A171, A172, A173, A174, A175, A176, A177, A178, A179,
+ A180, A181, A182, A183, A184, A185, A186, A187, A188, A189,
+ A190, A191, A192, A193, A194, A195, A196, A197, A198, A199,
+ A200, A201, A202, A203, A204, A205, A206, A207, A208, A209,
+ A210, A211, A212, A213, A214, A215, A216, A217, A218, A219,
+ A220, A221, A222, A223, A224, A225, A226, A227, A228, A229,
+ A230, A231, A232, A233, A234, A235, A236, A237, A238, A239,
+ A240, A241, A242, A243, A244, A245, A246, A247, A248, A249,
+ A250, A251, A252, A253, A254) ->
+ receive go -> ok end,
+ exit([A0, A1, A2, A3, A4, A5, A6, A7, A8, A9,
+ A10, A11, A12, A13, A14, A15, A16, A17, A18, A19,
+ A20, A21, A22, A23, A24, A25, A26, A27, A28, A29,
+ A30, A31, A32, A33, A34, A35, A36, A37, A38, A39,
+ A40, A41, A42, A43, A44, A45, A46, A47, A48, A49,
+ A50, A51, A52, A53, A54, A55, A56, A57, A58, A59,
+ A60, A61, A62, A63, A64, A65, A66, A67, A68, A69,
+ A70, A71, A72, A73, A74, A75, A76, A77, A78, A79,
+ A80, A81, A82, A83, A84, A85, A86, A87, A88, A89,
+ A90, A91, A92, A93, A94, A95, A96, A97, A98, A99,
+ A100, A101, A102, A103, A104, A105, A106, A107, A108, A109,
+ A110, A111, A112, A113, A114, A115, A116, A117, A118, A119,
+ A120, A121, A122, A123, A124, A125, A126, A127, A128, A129,
+ A130, A131, A132, A133, A134, A135, A136, A137, A138, A139,
+ A140, A141, A142, A143, A144, A145, A146, A147, A148, A149,
+ A150, A151, A152, A153, A154, A155, A156, A157, A158, A159,
+ A160, A161, A162, A163, A164, A165, A166, A167, A168, A169,
+ A170, A171, A172, A173, A174, A175, A176, A177, A178, A179,
+ A180, A181, A182, A183, A184, A185, A186, A187, A188, A189,
+ A190, A191, A192, A193, A194, A195, A196, A197, A198, A199,
+ A200, A201, A202, A203, A204, A205, A206, A207, A208, A209,
+ A210, A211, A212, A213, A214, A215, A216, A217, A218, A219,
+ A220, A221, A222, A223, A224, A225, A226, A227, A228, A229,
+ A230, A231, A232, A233, A234, A235, A236, A237, A238, A239,
+ A240, A241, A242, A243, A244, A245, A246, A247, A248, A249,
+ A250, A251, A252, A253, A254]).
+
+spawn_huge_arglist(Config) when is_list(Config) ->
+ %% Huge in two different ways; encoded size and
+ %% length...
+ ArgListHead = [make_ref(),
+ lists:duplicate(1000000, $a),
+ <<1:8388608>>,
+ processes(),
+ erlang:ports(),
+ {hej, hopp},
+ <<17:8388608>>,
+ lists:duplicate(3000000, $x),
+ #{ a => 1, b => 2, c => 3, d => 4, e => 5}],
+ ArgList = ArgListHead ++ lists:seq(1, 255 - length(ArgListHead)),
+
+ io:format("size(term_to_binary(ArgList)) = ~p~n",
+ [size(term_to_binary(ArgList))]),
+
+ io:format("Testing spawn with huge argument list on local node...~n", []),
+ spawn_huge_arglist_test(true, node(), ArgList),
+ io:format("Testing spawn with huge argument list on local node with Node...~n", []),
+ spawn_huge_arglist_test(false, node(), ArgList),
+ {ok, Node} = start_node(Config),
+ _ = rpc:call(Node, ?MODULE, module_info, []),
+ io:format("Testing spawn with huge argument list on remote node ~p...~n", [Node]),
+ spawn_huge_arglist_test(false, Node, ArgList),
+ stop_node(Node),
+ ok.
+
+spawn_huge_arglist_test(Local, Node, ArgList) ->
+
+ R1 = case Local of
+ true ->
+ spawn_request(?MODULE, huge_arglist_child, ArgList, [monitor]);
+ false ->
+ spawn_request(Node, ?MODULE, huge_arglist_child, ArgList, [monitor])
+ end,
+ receive
+ {spawn_reply, R1, ok, Pid1} ->
+ Pid1 ! go,
+ receive
+ {'DOWN', R1, process, Pid1, Reason1} ->
+ ArgList = Reason1
+ end
+ end,
+
+ {Pid2, R2} = case Local of
+ true ->
+ spawn_monitor(?MODULE, huge_arglist_child, ArgList);
+ false ->
+ spawn_monitor(Node, ?MODULE, huge_arglist_child, ArgList)
+ end,
+ Node = node(Pid2),
+ Pid2 ! go,
+ receive
+ {'DOWN', R2, process, Pid2, Reason2} ->
+ ArgList = Reason2
+ end,
+
+ {Pid3, R3} = case Local of
+ true ->
+ spawn_opt(?MODULE, huge_arglist_child, ArgList, [monitor]);
+ false ->
+ spawn_opt(Node, ?MODULE, huge_arglist_child, ArgList, [monitor])
+ end,
+ Node = node(Pid3),
+ Pid3 ! go,
+ receive
+ {'DOWN', R3, process, Pid3, Reason3} ->
+ ArgList = Reason3
+ end,
+
+ OldTA = process_flag(trap_exit, true),
+ Pid4 = case Local of
+ true ->
+ spawn_link(?MODULE, huge_arglist_child, ArgList);
+ false ->
+ spawn_link(Node, ?MODULE, huge_arglist_child, ArgList)
+ end,
+ Node = node(Pid4),
+ Pid4 ! go,
+ receive
+ {'EXIT', Pid4, Reason4} ->
+ ArgList = Reason4
+ end,
+
+ true = process_flag(trap_exit, OldTA),
+
+ Pid5 = case Local of
+ true ->
+ spawn(?MODULE, huge_arglist_child, ArgList);
+ false ->
+ spawn(Node, ?MODULE, huge_arglist_child, ArgList)
+ end,
+ Node = node(Pid5),
+ R5 = erlang:monitor(process, Pid5),
+ Pid5 ! go,
+ receive
+ {'DOWN', R5, process, Pid5, Reason5} ->
+ ArgList = Reason5
+ end,
+ ok.
+
+spawn_request_bif(Config) when is_list(Config) ->
+ io:format("Testing spawn_request() on local node...~n", []),
+ spawn_request_bif_test(true, node()),
+ io:format("Testing spawn_request() on local node with Node...~n", []),
+ spawn_request_bif_test(false, node()),
+ {ok, Node} = start_node(Config),
+ io:format("Testing spawn_request() on remote node ~p...~n", [Node]),
+ spawn_request_bif_test(false, Node),
+ stop_node(Node),
+ ok.
+
+spawn_request_bif_test(Local, Node) ->
+
+ Me = self(),
+
+ process_flag(trap_exit, true),
+
+ T1 = {test, 1},
+ F1 = fun () -> exit({exit, T1}) end,
+ R1 = if Local ->
+ spawn_request(F1, [{reply_tag, T1}, monitor, link]);
+ true ->
+ spawn_request(Node, F1, [{reply_tag, T1}, monitor, link])
+ end,
+ receive
+ {T1, R1, ok, P1} ->
+ receive
+ {'DOWN', R1, process, P1, {exit, T1}} ->
+ ok
+ end,
+ receive
+ {'EXIT', P1, {exit, T1}} ->
+ ok
+ end
+ end,
+
+ R1b = if Local ->
+ spawn_request(F1, [monitor, link]);
+ true ->
+ spawn_request(Node, F1, [monitor, link])
+ end,
+ receive
+ {spawn_reply, R1b, ok, P1b} ->
+ receive
+ {'DOWN', R1b, process, P1b, {exit, T1}} ->
+ ok
+ end,
+ receive
+ {'EXIT', P1b, {exit, T1}} ->
+ ok
+ end
+ end,
+
+ Ref1c = make_ref(),
+ F1c = fun () -> Me ! Ref1c end,
+ R1c = if Local ->
+ spawn_request(F1c);
+ true ->
+ spawn_request(Node, F1c)
+ end,
+ receive
+ {spawn_reply, R1c, ok, _P1c} ->
+ receive Ref1c -> ok end
+ end,
+
+ R1e = if Local ->
+ spawn_request(F1, [monitors, links, {reply_tag, T1}]);
+ true ->
+ spawn_request(Node, F1, [monitors, links, {reply_tag, T1}])
+ end,
+ receive
+ {T1, R1e, error, BadOpt1} ->
+ badopt = BadOpt1,
+ ok
+ end,
+ ok = try
+ BadF = fun (X) -> exit({X,T1}) end,
+ if Local ->
+ spawn_request(BadF, [monitor, {reply_tag, T1}, link]);
+ true ->
+ spawn_request(Node, BadF, [monitor, {reply_tag, T1}, link])
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ spawn_request(<<"node">>, F1, [monitor, link], T1),
+ nok
+ catch
+ error:badarg -> ok
+ end,
+
+ T2 = {test, 2},
+ M2 = erlang,
+ F2 = exit,
+ Reason2 = {exit, T2},
+ Args2 = [Reason2],
+ R2 = if Local ->
+ spawn_request(M2, F2, Args2, [monitor, link, {reply_tag, T2}]);
+ true ->
+ spawn_request(Node, M2, F2, Args2, [monitor, link, {reply_tag, T2}])
+ end,
+ receive
+ {T2, R2, ok, P2} ->
+ receive
+ {'DOWN', R2, process, P2, Reason2} ->
+ ok
+ end,
+ receive
+ {'EXIT', P2, Reason2} ->
+ ok
+ end
+ end,
+
+ R2b = if Local ->
+ spawn_request(M2, F2, Args2, [monitor, link]);
+ true ->
+ spawn_request(Node, M2, F2, Args2, [monitor, link])
+ end,
+ receive
+ {spawn_reply, R2b, ok, P2b} ->
+ receive
+ {'DOWN', R2b, process, P2b, Reason2} ->
+ ok
+ end,
+ receive
+ {'EXIT', P2b, Reason2} ->
+ ok
+ end
+ end,
+
+ Ref2c = make_ref(),
+ R2c = if Local ->
+ spawn_request(erlang, send, [Me, Ref2c]);
+ true ->
+ spawn_request(Node, erlang, send, [Me, Ref2c])
+ end,
+ receive
+ {spawn_reply, R2c, ok, _P2c} ->
+ receive Ref2c -> ok end
+ end,
+
+ R2e = if Local ->
+ spawn_request(M2, F2, Args2, [monitors, {reply_tag, T2}, links]);
+ true ->
+ spawn_request(Node, M2, F2, Args2, [monitors, {reply_tag, T2}, links])
+ end,
+ receive
+ {T2, R2e, error, BadOpt2} ->
+ badopt = BadOpt2,
+ ok
+ end,
+
+ R2eb = if Local ->
+ spawn_request(M2, F2, Args2, [monitors, links]);
+ true ->
+ spawn_request(Node, M2, F2, Args2, [monitors, links])
+ end,
+ receive
+ {spawn_reply, R2eb, error, BadOpt2b} ->
+ badopt = BadOpt2b,
+ ok
+ end,
+
+ ok = try
+ if Local ->
+ spawn_request(M2, F2, [Args2|oops], [monitor, link, {reply_tag, T2}]);
+ true ->
+ spawn_request(Node, M2, F2, [Args2|oops], [monitor, link, {reply_tag, T2}])
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ if Local ->
+ spawn_request(M2, F2, [Args2|oops], [monitor, {reply_tag, blupp}, link]);
+ true ->
+ spawn_request(Node, M2, F2, [Args2|oops], [monitor, {reply_tag, blupp}, link])
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ if Local ->
+ spawn_request(M2, F2, [Args2|oops]);
+ true ->
+ spawn_request(Node, M2, F2, [Args2|oops])
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ if Local ->
+ spawn_request(M2, <<"exit">>, Args2, [monitor, {reply_tag, T2}, link]);
+ true ->
+ spawn_request(Node, M2, <<"exit">>, Args2, [monitor, {reply_tag, T2}, link])
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ if Local ->
+ spawn_request(M2, <<"exit">>, Args2, [monitor, link]);
+ true ->
+ spawn_request(Node, M2, <<"exit">>, Args2, [monitor, link])
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ if Local ->
+ spawn_request(M2, <<"exit">>, Args2);
+ true ->
+ spawn_request(Node, M2, <<"exit">>, Args2)
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ if Local ->
+ spawn_request(<<"erlang">>, F2, Args2, [{reply_tag, T2}, monitor, link]);
+ true ->
+ spawn_request(Node, <<"erlang">>, F2, Args2, [{reply_tag, T2}, monitor, link])
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ if Local ->
+ spawn_request(<<"erlang">>, F2, Args2, [monitor, link]);
+ true ->
+ spawn_request(Node, <<"erlang">>, F2, Args2, [monitor, link])
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ if Local ->
+ spawn_request(<<"erlang">>, F2, Args2);
+ true ->
+ spawn_request(Node, <<"erlang">>, F2, Args2)
+ end,
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ spawn_request(<<"node">>, M2, F2, Args2, [{reply_tag, T2}, monitor, link]),
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ spawn_request(<<"node">>, M2, F2, Args2, [monitor, link]),
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok = try
+ spawn_request(<<"node">>, M2, F2, Args2),
+ nok
+ catch
+ error:badarg -> ok
+ end,
+ ok.
+
+
+spawn_request_monitor_demonitor(Config) when is_list(Config) ->
+ {ok, Node} = start_node(Config),
+ BlockFun = fun () ->
+ erts_debug:set_internal_state(available_internal_state, true),
+ erts_debug:set_internal_state(block, 1000),
+ ok
+ end,
+
+ %% Block receiver node...
+ spawn_request(Node, BlockFun, [{priority,max}, link]),
+ receive after 100 -> ok end,
+
+ erlang:display(spawning),
+ erlang:yield(),
+ R = spawn_request(Node, timer, sleep, [10000], [monitor]),
+ %% Should not be possible to demonitor
+ %% before operation has succeeded...
+ erlang:display(premature_demonitor),
+ {monitors, []} = process_info(self(), monitors),
+ false = erlang:demonitor(R, [info]), %% Should be ignored by VM...
+ erlang:display(wait_success),
+ receive
+ {spawn_reply, R, ok, P} ->
+ erlang:display(demonitor),
+ {monitors, [{process,P}]} = process_info(self(), monitors),
+ true = erlang:demonitor(R, [info]),
+ {monitors, []} = process_info(self(), monitors),
+ exit(P, kill)
+ end,
+ erlang:display(done),
+ stop_node(Node),
+ ok.
+
+spawn_request_monitor_child_exit(Config) when is_list(Config) ->
+ %% Early child exit...
+ Tag = {a, tag},
+ R1 = spawn_request(nonexisting_module, nonexisting_function, [], [monitor, {reply_tag, Tag}]),
+ receive
+ {Tag, R1, ok, P1} ->
+ receive
+ {'DOWN', R1, process, P1, Reason1} ->
+ {undef, _} = Reason1
+ end
+ end,
+ {ok, Node} = start_node(Config),
+ R2 = spawn_request(Node, nonexisting_module, nonexisting_function, [], [{reply_tag, Tag}, monitor]),
+ receive
+ {Tag, R2, ok, P2} ->
+ receive
+ {'DOWN', R2, process, P2, Reason2} ->
+ {undef, _} = Reason2
+ end
+ end,
+ stop_node(Node),
+ ok.
+
+spawn_request_link_child_exit(Config) when is_list(Config) ->
+ %% Early child exit...
+ process_flag(trap_exit, true),
+ Tag = {a, tag},
+ R1 = spawn_request(nonexisting_module, nonexisting_function, [], [{reply_tag, Tag}, link]),
+ receive
+ {Tag, R1, ok, P1} ->
+ receive
+ {'EXIT', P1, Reason1} ->
+ {undef, _} = Reason1
+ end
+ end,
+ {ok, Node} = start_node(Config),
+ R2 = spawn_request(Node, nonexisting_module, nonexisting_function, [], [link, {reply_tag, Tag}]),
+ receive
+ {Tag, R2, ok, P2} ->
+ receive
+ {'EXIT', P2, Reason2} ->
+ {undef, _} = Reason2
+ end
+ end,
+ stop_node(Node),
+ ok.
+
+spawn_request_link_parent_exit(Config) when is_list(Config) ->
+ C1 = spawn_request_link_parent_exit_test(node()),
+ {ok, Node} = start_node(Config),
+ C2 = spawn_request_link_parent_exit_test(Node),
+ stop_node(Node),
+ {comment, C1 ++ " " ++ C2}.
+
+spawn_request_link_parent_exit_test(Node) ->
+ %% Early parent exit...
+ Tester = self(),
+
+ verify_nc(node()),
+
+ %% Ensure code loaded on other node...
+ _ = rpc:call(Node, ?MODULE, module_info, []),
+
+ ChildFun = fun () ->
+ Child = self(),
+ spawn_opt(fun () ->
+ process_flag(trap_exit, true),
+ receive
+ {'EXIT', Child, Reason} ->
+ Tester ! {parent_exit, Reason}
+ end
+ end, [link,{priority,max}]),
+ receive after infinity -> ok end
+ end,
+ ParentFun = case node() == Node of
+ true ->
+ fun (Wait) ->
+ spawn_request(ChildFun, [link,{priority,max}]),
+ receive after Wait -> ok end,
+ exit(kaboom)
+ end;
+ false ->
+ fun (Wait) ->
+ spawn_request(Node, ChildFun, [link,{priority,max}]),
+ receive after Wait -> ok end,
+ exit(kaboom)
+ end
+ end,
+ lists:foreach(fun (N) ->
+ spawn(fun () -> ParentFun(N rem 10) end)
+ end,
+ lists:seq(1, 1000)),
+ N = gather_parent_exits(kaboom, false),
+ Comment = case node() == Node of
+ true ->
+ C = "Got " ++ integer_to_list(N) ++ " node local kabooms!",
+ erlang:display(C),
+ C;
+ false ->
+ C = "Got " ++ integer_to_list(N) ++ " node remote kabooms!",
+ erlang:display(C),
+ true = N /= 0,
+ C
+ end,
+ Comment.
+
+spawn_request_abandon_bif(Config) when is_list(Config) ->
+ {ok, Node} = start_node(Config),
+ false = spawn_request_abandon(make_ref()),
+ false = spawn_request_abandon(spawn_request(fun () -> ok end)),
+ false = spawn_request_abandon(rpc:call(Node, erlang, make_ref, [])),
+ try
+ noreturn = spawn_request_abandon(self())
+ catch
+ error:badarg ->
+ ok
+ end,
+ try
+ noreturn = spawn_request_abandon(4711)
+ catch
+ error:badarg ->
+ ok
+ end,
+
+ verify_nc(node()),
+
+ %% Ensure code loaded on other node...
+ _ = rpc:call(Node, ?MODULE, module_info, []),
+
+
+ TotOps = 1000,
+ Tester = self(),
+
+ ChildFun = fun () ->
+ Child = self(),
+ spawn_opt(fun () ->
+ process_flag(trap_exit, true),
+ receive
+ {'EXIT', Child, Reason} ->
+ Tester ! {parent_exit, Reason}
+ end
+ end, [link,{priority,max}]),
+ receive after infinity -> ok end
+ end,
+ ParentFun = fun (Wait, Opts) ->
+ ReqId = spawn_request(Node, ChildFun, Opts),
+ receive after Wait -> ok end,
+ case spawn_request_abandon(ReqId) of
+ true ->
+ ok;
+ false ->
+ receive
+ {spawn_reply, ReqId, error, _} ->
+ exit(spawn_failed);
+ {spawn_reply, ReqId, ok, Pid} ->
+ unlink(Pid),
+ exit(Pid, bye)
+ after
+ 0 ->
+ exit(missing_spawn_reply)
+ end
+ end
+ end,
+ %% Parent exit early...
+ lists:foreach(fun (N) ->
+ spawn_opt(fun () ->
+ ParentFun(N rem 50, [link])
+ end, [link,{priority,max}])
+ end,
+ lists:seq(1, TotOps)),
+ NoA1 = gather_parent_exits(abandoned, true),
+ %% Parent exit late...
+ lists:foreach(fun (N) ->
+ spawn_opt(fun () ->
+ ParentFun(N rem 50, [link]),
+ receive
+ {spawn_reply, _, _, _} ->
+ exit(unexpected_spawn_reply)
+ after
+ 1000 -> ok
+ end
+ end, [link,{priority,max}])
+ end,
+ lists:seq(1, TotOps)),
+ NoA2 = gather_parent_exits(abandoned, true),
+ %% Parent exit early...
+ lists:foreach(fun (N) ->
+ spawn_opt(fun () ->
+ ParentFun(N rem 50, [])
+ end, [link,{priority,max}])
+ end,
+ lists:seq(1, TotOps)),
+ 0 = gather_parent_exits(abandoned, true),
+ %% Parent exit late...
+ lists:foreach(fun (N) ->
+ spawn_opt(fun () ->
+ ParentFun(N rem 50, []),
+ receive
+ {spawn_reply, _, _, _} ->
+ exit(unexpected_spawn_reply)
+ after
+ 1000 -> ok
+ end
+ end, [link,{priority,max}])
+ end,
+ lists:seq(1, TotOps)),
+ 0 = gather_parent_exits(abandoned, true),
+ stop_node(Node),
+ C = "Got " ++ integer_to_list(NoA1) ++ " and "
+ ++ integer_to_list(NoA2) ++ " abandoneds of 2*"
+ ++ integer_to_list(TotOps) ++ " ops!",
+ erlang:display(C),
+ true = NoA1 /= 0,
+ true = NoA1 /= TotOps,
+ true = NoA2 /= 0,
+ true = NoA2 /= TotOps,
+ {comment, C}.
+
+gather_parent_exits(Reason, AllowOther) ->
+ receive after 2000 -> ok end,
+ gather_parent_exits(Reason, AllowOther, 0).
+
+gather_parent_exits(Reason, AllowOther, N) ->
+ receive
+ {parent_exit, Reason} ->
+ gather_parent_exits(Reason, AllowOther, N+1);
+ {parent_exit, _} = ParentExit ->
+ case AllowOther of
+ false ->
+ ct:fail(ParentExit);
+ true ->
+ gather_parent_exits(Reason, AllowOther, N)
+ end
+ after 0 ->
+ N
+ end.
+dist_spawn_monitor(Config) when is_list(Config) ->
+ {ok, Node} = start_node(Config),
+ R1 = spawn_request(Node, erlang, exit, [hej], [monitor]),
+ receive
+ {spawn_reply, R1, ok, P1} ->
+ receive
+ {'DOWN', R1, process, P1, Reason1} ->
+ hej = Reason1
+ end
+ end,
+ {P2, Mon2} = spawn_monitor(Node, erlang, exit, [hej]),
+ receive
+ {'DOWN', Mon2, process, P2, Reason2} ->
+ hej = Reason2
+ end,
+ {P3, Mon3} = spawn_opt(Node, erlang, exit, [hej], [monitor]),
+ receive
+ {'DOWN', Mon3, process, P3, Reason3} ->
+ hej = Reason3
+ end,
+ stop_node(Node),
+ ok.
+
+spawn_old_node(Config) when is_list(Config) ->
+ Cookie = atom_to_list(erlang:get_cookie()),
+ Rel = "22_latest",
+ case test_server:is_release_available(Rel) of
+ false ->
+ {skipped, "No OTP 22 available"};
+ true ->
+ {ok, OldNode} = test_server:start_node(make_nodename(Config),
+ peer,
+ [{args, " -setcookie "++Cookie},
+ {erl, [{release, Rel}]}]),
+ try
+ %% Spawns triggering a new connection; which
+ %% will trigger hopeful data transcoding
+ %% of spawn requests...
+ io:format("~n~nDoing initial connect tests...~n", []),
+ spawn_old_node_test(OldNode, true),
+ %% Spawns on an already existing connection...
+ io:format("~n~nDoing already connected tests...~n", []),
+ spawn_old_node_test(OldNode, false)
+ after
+ test_server:stop_node(OldNode)
+ end,
+ ok
+ end.
+
+spawn_new_node(Config) when is_list(Config) ->
+ Cookie = atom_to_list(erlang:get_cookie()),
+ %% Test that the same operations as in spawn_old_node test
+ %% works as expected on current OTP...
+ {ok, CurrNode} = test_server:start_node(make_nodename(Config),
+ peer,
+ [{args, " -setcookie "++Cookie}]),
+ try
+ %% Spawns triggering a new connection; which
+ %% will trigger hopeful data transcoding
+ %% of spawn requests...
+ io:format("~n~nDoing initial connect tests...~n", []),
+ spawn_current_node_test(CurrNode, true),
+ io:format("~n~nDoing already connected tests...~n", []),
+ %% Spawns on an already existing connection...
+ spawn_current_node_test(CurrNode, false)
+ after
+ test_server:stop_node(CurrNode)
+ end.
+
+disconnect_node(Node, Disconnect) ->
+ case Disconnect of
+ false ->
+ ok;
+ true ->
+ monitor_node(Node, true),
+ erlang:disconnect_node(Node),
+ receive {nodedown, Node} -> ok end
+ end.
+
+spawn_old_node_test(Node, Disconnect) ->
+ io:format("Testing spawn_request() on old node...", []),
+ disconnect_node(Node, Disconnect),
+ R1 = spawn_request(Node, erlang, exit, [hej], [monitor, {reply_tag, a_tag}]),
+ receive
+ {a_tag, R1, Err, Notsup} ->
+ error = Err,
+ notsup = Notsup,
+ ok
+ end,
+ io:format("Testing spawn_monitor() on old node...", []),
+ disconnect_node(Node, Disconnect),
+ try
+ spawn_monitor(Node, erlang, exit, [hej])
+ catch
+ error:notsup ->
+ ok
+ end,
+ io:format("Testing spawn_opt() with monitor on old node...", []),
+ disconnect_node(Node, Disconnect),
+ try
+ spawn_opt(Node, erlang, exit, [hej], [monitor])
+ catch
+ error:badarg ->
+ ok
+ end,
+ io:format("Testing spawn_opt() with link on old node...", []),
+ disconnect_node(Node, Disconnect),
+ process_flag(trap_exit, true),
+ P1 = spawn_opt(Node, erlang, exit, [hej], [link]),
+ Node = node(P1),
+ receive
+ {'EXIT', P1, hej} ->
+ ok
+ end,
+ io:format("Testing spawn_link() on old node...", []),
+ disconnect_node(Node, Disconnect),
+ P2 = spawn_link(Node, erlang, exit, [hej]),
+ Node = node(P2),
+ receive
+ {'EXIT', P2, hej} ->
+ ok
+ end.
+
+spawn_current_node_test(Node, Disconnect) ->
+ io:format("Testing spawn_request() on new node...", []),
+ disconnect_node(Node, Disconnect),
+ R1 = spawn_request(Node, erlang, exit, [hej], [monitor, {reply_tag, a_tag}]),
+ receive
+ {a_tag, R1, ok, P1} ->
+ Node = node(P1),
+ receive
+ {'DOWN', R1, process, P1, hej} -> ok
+ end
+ end,
+ io:format("Testing spawn_monitor() on new node...", []),
+ disconnect_node(Node, Disconnect),
+ {P2, M2} = spawn_monitor(Node, erlang, exit, [hej]),
+ receive
+ {'DOWN', M2, process, P2, hej} -> ok
+ end,
+ Node = node(P2),
+ io:format("Testing spawn_opt() with monitor on new node...", []),
+ disconnect_node(Node, Disconnect),
+ {P3, M3} = spawn_opt(Node, erlang, exit, [hej], [monitor]),
+ receive
+ {'DOWN', M3, process, P3, hej} -> ok
+ end,
+ Node = node(P3),
+ io:format("Testing spawn_opt() with link on new node...", []),
+ disconnect_node(Node, Disconnect),
+ process_flag(trap_exit, true),
+ P4 = spawn_opt(Node, erlang, exit, [hej], [link]),
+ Node = node(P4),
+ receive
+ {'EXIT', P4, hej} ->
+ ok
+ end,
+ io:format("Testing spawn_link() on new node...", []),
+ disconnect_node(Node, Disconnect),
+ P5 = spawn_link(Node, erlang, exit, [hej]),
+ Node = node(P5),
+ receive
+ {'EXIT', P5, hej} ->
+ ok
+ end.
+
+spawn_request_reply_option(Config) when is_list(Config) ->
+ spawn_request_reply_option_test(node()),
+ {ok, Node} = start_node(Config),
+ spawn_request_reply_option_test(Node).
+
+spawn_request_reply_option_test(Node) ->
+ io:format("Testing on node: ~p~n", [Node]),
+ Parent = self(),
+ Done1 = make_ref(),
+ RID1 = spawn_request(Node, fun () -> Parent ! Done1 end, [{reply, yes}]),
+ receive Done1 -> ok end,
+ receive
+ {spawn_reply, RID1, ok, _} -> ok
+ after 0 ->
+ ct:fail(missing_spawn_reply)
+ end,
+ Done2 = make_ref(),
+ RID2 = spawn_request(Node, fun () -> Parent ! Done2 end, [{reply, success_only}]),
+ receive Done2 -> ok end,
+ receive
+ {spawn_reply, RID2, ok, _} -> ok
+ after 0 ->
+ ct:fail(missing_spawn_reply)
+ end,
+ Done3 = make_ref(),
+ RID3 = spawn_request(Node, fun () -> Parent ! Done3 end, [{reply, error_only}]),
+ receive Done3 -> ok end,
+ receive
+ {spawn_reply, RID3, _, _} ->
+ ct:fail(unexpected_spawn_reply)
+ after 0 ->
+ ok
+ end,
+ Done4 = make_ref(),
+ RID4 = spawn_request(Node, fun () -> Parent ! Done4 end, [{reply, no}]),
+ receive Done4 -> ok end,
+ receive
+ {spawn_reply, RID4, _, _} ->
+ ct:fail(unexpected_spawn_reply)
+ after 0 ->
+ ok
+ end,
+ RID5 = spawn_request(Node, fun () -> ok end, [{reply, yes}, bad_option]),
+ receive
+ {spawn_reply, RID5, error, badopt} -> ok
+ end,
+ RID6 = spawn_request(Node, fun () -> ok end, [{reply, success_only}, bad_option]),
+ receive
+ {spawn_reply, RID6, error, badopt} -> ct:fail(unexpected_spawn_reply)
+ after 1000 -> ok
+ end,
+ RID7 = spawn_request(Node, fun () -> ok end, [{reply, error_only}, bad_option]),
+ receive
+ {spawn_reply, RID7, error, badopt} -> ok
+ end,
+ RID8 = spawn_request(Node, fun () -> ok end, [{reply, no}, bad_option]),
+ receive
+ {spawn_reply, RID8, error, badopt} -> ct:fail(unexpected_spawn_reply)
+ after 1000 -> ok
+ end,
+ RID8_1 = spawn_request(Node, fun () -> ok end, [{reply, nahh}]),
+ receive
+ {spawn_reply, RID8_1, error, badopt} -> ok
+ end,
+ case Node == node() of
+ true ->
+ ok;
+ false ->
+ stop_node(Node),
+ RID9 = spawn_request(Node, fun () -> ok end, [{reply, yes}]),
+ receive
+ {spawn_reply, RID9, error, noconnection} -> ok
+ end,
+ RID10 = spawn_request(Node, fun () -> ok end, [{reply, success_only}]),
+ receive
+ {spawn_reply, RID10, error, noconnection} -> ct:fail(unexpected_spawn_reply)
+ after 1000 -> ok
+ end,
+ RID11 = spawn_request(Node, fun () -> ok end, [{reply, error_only}]),
+ receive
+ {spawn_reply, RID11, error, noconnection} -> ok
+ end,
+ RID12 = spawn_request(Node, fun () -> ok end, [{reply, no}]),
+ receive
+ {spawn_reply, RID12, error, noconnection} -> ct:fail(unexpected_spawn_reply)
+ after 1000 -> ok
+ end,
+ ok
+ end.
+
processes_term_proc_list(Config) when is_list(Config) ->
Tester = self(),
@@ -3011,24 +4001,50 @@ tok_loop(hopp) ->
tok_loop(hej).
id(I) -> I.
+
+make_nodename(Config) when is_list(Config) ->
+ list_to_atom(atom_to_list(?MODULE)
+ ++ "-"
+ ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(second))
+ ++ "-"
+ ++ integer_to_list(erlang:unique_integer([positive]))).
start_node(Config) ->
start_node(Config, "").
start_node(Config, Args) when is_list(Config) ->
Pa = filename:dirname(code:which(?MODULE)),
- Name = list_to_atom(atom_to_list(?MODULE)
- ++ "-"
- ++ atom_to_list(proplists:get_value(testcase, Config))
- ++ "-"
- ++ integer_to_list(erlang:system_time(second))
- ++ "-"
- ++ integer_to_list(erlang:unique_integer([positive]))),
+ Name = make_nodename(Config),
test_server:start_node(Name, slave, [{args, "-pa "++Pa++" "++Args}]).
stop_node(Node) ->
+ verify_nc(node()),
+ verify_nc(Node),
test_server:stop_node(Node).
+verify_nc(Node) ->
+ P = self(),
+ Ref = make_ref(),
+ Pid = spawn(Node,
+ fun() ->
+ R = erts_test_utils:check_node_dist(fun(E) -> E end),
+ P ! {Ref, R}
+ end),
+ MonRef = monitor(process, Pid),
+ receive
+ {Ref, ok} ->
+ demonitor(MonRef,[flush]),
+ ok;
+ {Ref, Error} ->
+ ct:log("~s",[Error]),
+ ct:fail(failed_nc_refc_check);
+ {'DOWN', MonRef, _, _, _} = Down ->
+ ct:log("~p",[Down]),
+ ct:fail(crashed_nc_refc_check)
+ end.
+
enable_internal_state() ->
case catch erts_debug:get_internal_state(available_internal_state) of
true -> true;
diff --git a/erts/emulator/test/property_test/phash2_properties.erl b/erts/emulator/test/property_test/phash2_properties.erl
new file mode 100644
index 0000000000..b1f3207c56
--- /dev/null
+++ b/erts/emulator/test/property_test/phash2_properties.erl
@@ -0,0 +1,63 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019-2019. 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(phash2_properties).
+
+-ifdef(PROPER).
+
+-include_lib("proper/include/proper.hrl").
+-export([prop_phash2_same_with_same_input/0,
+ prop_phash2_same_with_same_long_input/0,
+ prop_phash2_same_in_different_versions/1,
+ prop_phash2_same_in_different_versions_with_long_input/1]).
+-proptest([proper]).
+
+%%--------------------------------------------------------------------
+%% Properties --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+prop_phash2_same_with_same_input() ->
+ ?FORALL(T, any(), erlang:phash2(T) =:= erlang:phash2(T)).
+
+prop_phash2_same_with_same_long_input() ->
+ ?FORALL(T, any(),
+ begin
+ BigTerm = lists:duplicate(10000, T),
+ erlang:phash2(BigTerm) =:= erlang:phash2(BigTerm)
+ end).
+
+prop_phash2_same_in_different_versions(DifferntVersionNode) ->
+ ?FORALL(T, any(),
+ erlang:phash2(T) =:= rpc:call(DifferntVersionNode,erlang,phash2,[T])).
+
+prop_phash2_same_in_different_versions_with_long_input(DifferntVersionNode) ->
+ ?FORALL(T, any(),
+ begin
+ BigTerm = lists:duplicate(10000, T),
+ RpcRes = rpc:call(DifferntVersionNode,erlang,phash2,[BigTerm]),
+ LocalRes = erlang:phash2(BigTerm),
+ RpcRes =:= LocalRes
+ end).
+
+%%--------------------------------------------------------------------
+%% Generators -------------------------------------------------------
+%%--------------------------------------------------------------------
+
+-endif.
diff --git a/erts/emulator/test/receive_SUITE.erl b/erts/emulator/test/receive_SUITE.erl
index 339507c9d8..333ca6a02a 100644
--- a/erts/emulator/test/receive_SUITE.erl
+++ b/erts/emulator/test/receive_SUITE.erl
@@ -43,7 +43,7 @@ all() ->
erl_1199].
init_per_testcase(receive_opt_deferred_save, Config) ->
- case erlang:system_info(schedulers) of
+ case erlang:system_info(schedulers_online) of
1 ->
{skip, "Needs more schedulers to run"};
_ ->
diff --git a/erts/emulator/test/scheduler_SUITE.erl b/erts/emulator/test/scheduler_SUITE.erl
index c863a69a43..7a4e644cfa 100644
--- a/erts/emulator/test/scheduler_SUITE.erl
+++ b/erts/emulator/test/scheduler_SUITE.erl
@@ -995,62 +995,81 @@ sct_cmd(Config) when is_list(Config) ->
{"db", thread_no_node_processor_spread}]).
sbt_cmd(Config) when is_list(Config) ->
- Bind = try
- OldVal = erlang:system_flag(scheduler_bind_type, default_bind),
- erlang:system_flag(scheduler_bind_type, OldVal),
- go_for_it
- catch
- error:notsup -> notsup;
- error:_ -> go_for_it
- end,
- case Bind of
- notsup ->
- {skipped, "Binding of schedulers not supported"};
- go_for_it ->
- CpuTCmd = case erlang:system_info({cpu_topology,detected}) of
- undefined ->
- case os:type() of
- linux ->
- case erlang:system_info(logical_processors) of
- 1 ->
- "+sctL0";
- N when is_integer(N) ->
- NS = integer_to_list(N-1),
- "+sctL0-"++NS++"p0-"++NS;
- _ ->
- false
- end;
- _ ->
- false
- end;
- _ ->
- ""
- end,
- case CpuTCmd of
- false ->
- {skipped, "Don't know how to create cpu topology"};
- _ ->
- case erlang:system_info(logical_processors) of
- LP when is_integer(LP) ->
- OldRelFlags = clear_erl_rel_flags(),
- try
- lists:foreach(fun ({ClBt, Bt}) ->
- sbt_test(Config,
- CpuTCmd,
- ClBt,
- Bt,
- LP)
- end,
- ?BIND_TYPES)
- after
- restore_erl_rel_flags(OldRelFlags)
- end,
- ok;
- _ ->
- {skipped,
- "Don't know the amount of logical processors"}
- end
- end
+ case sbt_check_prereqs() of
+ {skipped, _Reason}=Skipped ->
+ Skipped;
+ ok ->
+ case sbt_make_topology_args() of
+ false ->
+ {skipped, "Don't know how to create cpu topology"};
+ CpuTCmd ->
+ LP = erlang:system_info(logical_processors),
+ OldRelFlags = clear_erl_rel_flags(),
+ try
+ lists:foreach(fun ({ClBt, Bt}) ->
+ sbt_test(Config, CpuTCmd,
+ ClBt, Bt, LP)
+ end,
+ ?BIND_TYPES)
+ after
+ restore_erl_rel_flags(OldRelFlags)
+ end,
+ ok
+ end
+ end.
+
+sbt_make_topology_args() ->
+ case erlang:system_info({cpu_topology,detected}) of
+ undefined ->
+ case os:type() of
+ linux ->
+ case erlang:system_info(logical_processors) of
+ 1 ->
+ "+sctL0";
+ N ->
+ NS = integer_to_list(N - 1),
+ "+sctL0-"++NS++"p0-"++NS
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ ""
+ end.
+
+sbt_check_prereqs() ->
+ try
+ Available = erlang:system_info(logical_processors_available),
+ Quota = erlang:system_info(cpu_quota),
+ if
+ Quota =:= unknown; Quota >= Available ->
+ ok;
+ Quota < Available ->
+ throw({skipped, "Test requires that CPU quota is greater than "
+ "the number of available processors."})
+ end,
+
+ try
+ OldVal = erlang:system_flag(scheduler_bind_type, default_bind),
+ erlang:system_flag(scheduler_bind_type, OldVal)
+ catch
+ error:notsup ->
+ throw({skipped, "Scheduler binding not supported."});
+ error:_ ->
+ %% ?!
+ ok
+ end,
+
+ case erlang:system_info(logical_processors) of
+ Count when is_integer(Count) ->
+ ok;
+ unknown ->
+ throw({skipped, "Can't detect number of logical processors."})
+ end,
+
+ ok
+ catch
+ throw:{skip,_Reason}=Skip -> Skip
end.
sbt_test(Config, CpuTCmd, ClBt, Bt, LP) ->
@@ -1112,28 +1131,48 @@ scheduler_threads(Config) when is_list(Config) ->
{Sched, HalfSchedOnln, _} = get_sstate(Config, "+SP:50"),
%% Configure 2x scheduler threads only
{TwiceSched, SchedOnln, _} = get_sstate(Config, "+SP 200"),
- case {erlang:system_info(logical_processors),
- erlang:system_info(logical_processors_available)} of
- {LProc, LProcAvail} when is_integer(LProc), is_integer(LProcAvail) ->
- %% Test resetting the scheduler counts
- ResetCmd = "+S "++FourSched++":"++FourSchedOnln++" +S 0:0",
- {LProc, LProcAvail, _} = get_sstate(Config, ResetCmd),
- %% Test negative +S settings, but only for SMP-enabled emulators
- case {LProc > 1, LProcAvail > 1} of
- {true, true} ->
- SchedMinus1 = LProc-1,
- SchedOnlnMinus1 = LProcAvail-1,
- {SchedMinus1, SchedOnlnMinus1, _} = get_sstate(Config, "+S -1"),
- {LProc, SchedOnlnMinus1, _} = get_sstate(Config, "+S :-1"),
- {SchedMinus1, SchedOnlnMinus1, _} = get_sstate(Config, "+S -1:-1"),
- ok;
- _ ->
- {comment, "Skipped reduced amount of schedulers test due to too few logical processors"}
- end;
- _ -> %% Skipped when missing info about logical processors...
- {comment, "Skipped reset amount of schedulers test, and reduced amount of schedulers test due to too unknown amount of logical processors"}
+
+ LProc = erlang:system_info(logical_processors),
+ LProcAvail = erlang:system_info(logical_processors_available),
+ Quota = erlang:system_info(cpu_quota),
+
+ if
+ not is_integer(LProc); not is_integer(LProcAvail) ->
+ {comment, "Skipped reset amount of schedulers test, and reduced "
+ "amount of schedulers test due to too unknown amount of "
+ "logical processors"};
+ is_integer(LProc); is_integer(LProcAvail) ->
+ ExpectedOnln = st_expected_onln(LProcAvail, Quota),
+
+ st_reset(Config, LProc, ExpectedOnln, FourSched, FourSchedOnln),
+
+ if
+ LProc =:= 1; LProcAvail =:= 1 ->
+ {comment, "Skipped reduced amount of schedulers test due "
+ "to too few logical processors"};
+ LProc > 1, LProcAvail > 1 ->
+ st_reduced(Config, LProc, ExpectedOnln)
+ end
end.
+st_reset(Config, LProc, ExpectedOnln, FourSched, FourSchedOnln) ->
+ %% Test resetting # of schedulers.
+ ResetCmd = "+S "++FourSched++":"++FourSchedOnln++" +S 0:0",
+ {LProc, ExpectedOnln, _} = get_sstate(Config, ResetCmd),
+ ok.
+
+st_reduced(Config, LProc, ExpectedOnln) ->
+ %% Test negative +S settings
+ SchedMinus1 = LProc-1,
+ SchedOnlnMinus1 = ExpectedOnln-1,
+ {SchedMinus1, SchedOnlnMinus1, _} = get_sstate(Config, "+S -1"),
+ {LProc, SchedOnlnMinus1, _} = get_sstate(Config, "+S :-1"),
+ {SchedMinus1, SchedOnlnMinus1, _} = get_sstate(Config, "+S -1:-1"),
+ ok.
+
+st_expected_onln(LProcAvail, unknown) -> LProcAvail;
+st_expected_onln(LProcAvail, Quota) -> min(LProcAvail, Quota).
+
dirty_scheduler_threads(Config) when is_list(Config) ->
case erlang:system_info(dirty_cpu_schedulers) of
0 -> {skipped, "No dirty scheduler support"};
diff --git a/erts/emulator/test/send_term_SUITE.erl b/erts/emulator/test/send_term_SUITE.erl
index 8afe4e4ac1..6fa19c47d4 100644
--- a/erts/emulator/test/send_term_SUITE.erl
+++ b/erts/emulator/test/send_term_SUITE.erl
@@ -156,27 +156,11 @@ receive_any() ->
end.
chk_temp_alloc() ->
- case erlang:system_info({allocator,temp_alloc}) of
- false ->
- %% Temp alloc is not enabled
- ok;
- TIL ->
- %% Verify that we havn't got anything allocated by temp_alloc
- lists:foreach(
- fun ({instance, _, TI}) ->
- {value, {mbcs, MBCInfo}}
- = lists:keysearch(mbcs, 1, TI),
- {value, {blocks, 0, _, _}}
- = lists:keysearch(blocks, 1, MBCInfo),
- {value, {sbcs, SBCInfo}}
- = lists:keysearch(sbcs, 1, TI),
- {value, {blocks, 0, _, _}}
- = lists:keysearch(blocks, 1, SBCInfo)
- end,
- TIL),
- ok
+ %% Verify that we haven't got any outstanding temp_alloc allocations.
+ case erts_debug:alloc_blocks_size(temp_alloc) of
+ undefined -> ok;
+ 0 -> ok
end.
-
%% Start/stop drivers.
start_driver(Config, Name) ->
diff --git a/erts/emulator/test/small_SUITE.erl b/erts/emulator/test/small_SUITE.erl
index 00a02e5560..7dbe1fb4f4 100644
--- a/erts/emulator/test/small_SUITE.erl
+++ b/erts/emulator/test/small_SUITE.erl
@@ -78,6 +78,15 @@ sp2_1(N, MinS, MaxS) when N > 0 ->
[N | sp2_1(N bsl 1, MinS, MaxS)].
arith_test(A, B, MinS, MaxS) ->
+ try arith_test_1(A, B, MinS, MaxS) of
+ ok -> ok
+ catch
+ error:Reason:Stk ->
+ ct:fail("arith_test failed with ~p~n\tA = ~p~n\tB = ~p\n\t~p",
+ [Reason, A, B, Stk])
+ end.
+
+arith_test_1(A, B, MinS, MaxS) ->
verify_kind(A + B, MinS, MaxS),
verify_kind(B + A, MinS, MaxS),
verify_kind(A - B, MinS, MaxS),
@@ -97,6 +106,9 @@ arith_test(A, B, MinS, MaxS) ->
true = B =:= 0 orelse ((A * B) div id(B) =:= A),
true = A =:= 0 orelse ((B * A) div id(A) =:= B),
+ true = B =:= 0 orelse (((A div id(B)) * id(B) + A rem id(B)) =:= A),
+ true = A =:= 0 orelse (((B div id(A)) * id(A) + B rem id(A)) =:= B),
+
ok.
%% Verifies that N is a small when it should be
diff --git a/erts/emulator/test/statistics_SUITE.erl b/erts/emulator/test/statistics_SUITE.erl
index 81505902c3..90fbedb94a 100644
--- a/erts/emulator/test/statistics_SUITE.erl
+++ b/erts/emulator/test/statistics_SUITE.erl
@@ -340,7 +340,22 @@ run_scheduler_wall_time_test(Type) ->
scheduler_wall_time ->
Schedulers + DirtyCPUSchedulers
end,
-
+
+ Env = [io_lib:format("~s~n",[KV]) || KV <- os:getenv()],
+
+ ct:log("Env:~n~s",[Env]),
+
+ ct:log("Schedulers: ~p~n"
+ "SchedulersOnline: ~p~n"
+ "DirtyCPUSchedulers: ~p~n"
+ "DirtyCPUSchedulersOnline: ~p~n"
+ "DirtyIOSchedulersOnline: ~p~n",
+ [erlang:system_info(schedulers),
+ Schedulers,
+ erlang:system_info(dirty_cpu_schedulers),
+ DirtyCPUSchedulers,
+ DirtyIOSchedulers]),
+
%% Let testserver and everyone else finish their work
timer:sleep(1500),
%% Empty load
@@ -376,20 +391,28 @@ run_scheduler_wall_time_test(Type) ->
|| _ <- lists:seq(1, lists:max([1,DirtyCPUSchedulers div 2]))],
HalfDirtyIOHogs = [StartDirtyHog(dirty_io)
|| _ <- lists:seq(1, lists:max([1,DirtyIOSchedulers div 2]))],
- HalfLoad = lists:sum(get_load(Type)) div TotLoadSchedulers,
- if Schedulers < 2, HalfLoad > 80 -> ok; %% Ok only one scheduler online and one hog
+ HalfScheds = get_load(Type),
+ ct:log("HalfScheds: ~w",[HalfScheds]),
+ HalfLoad = lists:sum(HalfScheds) div TotLoadSchedulers,
+ if Schedulers =:= 1, HalfLoad > 80 -> ok; %% Ok only one scheduler online and one hog
%% We want roughly 50% load
HalfLoad > 40, HalfLoad < 60 -> ok;
true -> exit({halfload, HalfLoad})
end,
- %% 100% load
- LastHogs = [StartHog() || _ <- lists:seq(1, Schedulers div 2)],
+ %% 100% load. Need to take into consideration an odd number of
+ %% schedulers and also special consideration for when there is
+ %% only 1 scheduler
+ LastHogs = [StartHog() || _ <- lists:seq(1, (Schedulers+1) div 2),
+ Schedulers =/= 1],
LastDirtyCPUHogs = [StartDirtyHog(dirty_cpu)
- || _ <- lists:seq(1, DirtyCPUSchedulers div 2)],
+ || _ <- lists:seq(1, (DirtyCPUSchedulers+1) div 2),
+ DirtyCPUSchedulers =/= 1],
LastDirtyIOHogs = [StartDirtyHog(dirty_io)
- || _ <- lists:seq(1, DirtyIOSchedulers div 2)],
+ || _ <- lists:seq(1, (DirtyIOSchedulers+1) div 2),
+ DirtyIOSchedulers =/= 1],
FullScheds = get_load(Type),
+ ct:log("FullScheds: ~w",[FullScheds]),
{false,_} = {lists:any(fun(Load) -> Load < 80 end, FullScheds),FullScheds},
FullLoad = lists:sum(FullScheds) div TotLoadSchedulers,
if FullLoad > 90 -> ok;
@@ -420,7 +443,27 @@ get_load(Type) ->
Start = erlang:statistics(Type),
timer:sleep(1500),
End = erlang:statistics(Type),
- lists:reverse(lists:sort(load_percentage(lists:sort(Start),lists:sort(End)))).
+
+ lists:reverse(
+ lists:sort(load_percentage(online_statistics(Start),online_statistics(End)))).
+
+%% We are only interested in schedulers that are online to remove all
+%% offline normal and dirty cpu schedulers (dirty io cannot be offline)
+online_statistics(Stats) ->
+ Schedulers = erlang:system_info(schedulers),
+ SchedulersOnline = erlang:system_info(schedulers_online),
+ DirtyCPUSchedulers = erlang:system_info(dirty_cpu_schedulers),
+ DirtyCPUSchedulersOnline = erlang:system_info(dirty_cpu_schedulers_online),
+ DirtyIOSchedulersOnline = erlang:system_info(dirty_io_schedulers),
+ SortedStats = lists:sort(Stats),
+ ct:pal("Stats: ~p~n", [SortedStats]),
+ SchedulersStats =
+ lists:sublist(SortedStats, 1, SchedulersOnline),
+ DirtyCPUSchedulersStats =
+ lists:sublist(SortedStats, Schedulers+1, DirtyCPUSchedulersOnline),
+ DirtyIOSchedulersStats =
+ lists:sublist(SortedStats, Schedulers + DirtyCPUSchedulers+1, DirtyIOSchedulersOnline),
+ SchedulersStats ++ DirtyCPUSchedulersStats ++ DirtyIOSchedulersStats.
load_percentage([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) ->
[100*(WN-WP) div (TN-TP)|load_percentage(Ss, Ps)];
@@ -693,9 +736,9 @@ msacc_test(TmpFile) ->
%% Check some IO
{ok, L} = gen_tcp:listen(0, [{active, true},{reuseaddr,true}]),
{ok, Port} = inet:port(L),
- Pid = spawn(fun() ->
- {ok, S} = gen_tcp:accept(L),
- (fun F() -> receive M -> F() end end)()
+ _Pid = spawn(fun() ->
+ {ok, _S} = gen_tcp:accept(L),
+ (fun F() -> receive _M -> F() end end)()
end),
{ok, C} = gen_tcp:connect("localhost", Port, []),
[begin gen_tcp:send(C,"hello"),timer:sleep(1) end || _ <- lists:seq(1,100)],
diff --git a/erts/emulator/test/system_info_SUITE.erl b/erts/emulator/test/system_info_SUITE.erl
index 5426414069..7c547fde7a 100644
--- a/erts/emulator/test/system_info_SUITE.erl
+++ b/erts/emulator/test/system_info_SUITE.erl
@@ -451,41 +451,52 @@ cmp_memory(MWs, Str) ->
garbage_collect(),
erts_debug:set_internal_state(wait, deallocations),
- EDM = erts_debug:get_internal_state(memory),
- EM = erlang:memory(),
+ retry(3, fun() ->
+ EDM = erts_debug:get_internal_state(memory),
+ EM = erlang:memory(),
- io:format("~s:~n"
- "erlang:memory() = ~p~n"
- "crash dump memory = ~p~n",
- [Str, EM, EDM]),
+ io:format("~s:~n"
+ "erlang:memory() = ~p~n"
+ "crash dump memory = ~p~n",
+ [Str, EM, EDM]),
- check_sane_memory(EM),
- check_sane_memory(EDM),
+ check_sane_memory(EM),
+ check_sane_memory(EDM),
- %% We expect these to always give us exactly the same result
+ %% We expect these to always give us exactly the same result
- cmp_memory(atom, EM, EDM, 1),
- cmp_memory(atom_used, EM, EDM, 1),
- cmp_memory(binary, EM, EDM, 1),
- cmp_memory(code, EM, EDM, 1),
- cmp_memory(ets, EM, EDM, 1),
+ cmp_memory(atom, EM, EDM, 1),
+ cmp_memory(atom_used, EM, EDM, 1),
+ cmp_memory(binary, EM, EDM, 1),
+ cmp_memory(code, EM, EDM, 1),
+ cmp_memory(ets, EM, EDM, 1),
- %% Total, processes, processes_used, and system will seldom
- %% give us exactly the same result since the two readings
- %% aren't taken atomically.
- %%
- %% Torerance is scaled according to the number of schedulers
- %% to match spawn_mem_workers.
+ %% Total, processes, processes_used, and system will seldom
+ %% give us exactly the same result since the two readings
+ %% aren't taken atomically.
+ %%
+ %% Torerance is scaled according to the number of schedulers
+ %% to match spawn_mem_workers.
- Tolerance = 1.05 + 0.01 * erlang:system_info(schedulers_online),
+ Tolerance = 1.05 + 0.01 * erlang:system_info(schedulers_online),
- cmp_memory(total, EM, EDM, Tolerance),
- cmp_memory(processes, EM, EDM, Tolerance),
- cmp_memory(processes_used, EM, EDM, Tolerance),
- cmp_memory(system, EM, EDM, Tolerance),
+ cmp_memory(total, EM, EDM, Tolerance),
+ cmp_memory(processes, EM, EDM, Tolerance),
+ cmp_memory(processes_used, EM, EDM, Tolerance),
+ cmp_memory(system, EM, EDM, Tolerance)
+ end),
ok.
+retry(N, Fun) ->
+ try Fun()
+ catch
+ error:Error:Stack when N > 1 ->
+ io:format("Test failed: ~p\nat: ~p\nRetry max ~p more times\n",
+ [Error, Stack, N-1]),
+ retry(N-1, Fun)
+ end.
+
mapn(_Fun, 0) ->
[];
mapn(Fun, N) ->
diff --git a/erts/emulator/test/timer_bif_SUITE.erl b/erts/emulator/test/timer_bif_SUITE.erl
index 7d682897f8..68048b9771 100644
--- a/erts/emulator/test/timer_bif_SUITE.erl
+++ b/erts/emulator/test/timer_bif_SUITE.erl
@@ -31,7 +31,8 @@
same_time_yielding_with_cancel/1, same_time_yielding_with_cancel_other/1,
% same_time_yielding_with_cancel_other_accessor/1,
auto_cancel_yielding/1,
- suspended_scheduler_timeout/1]).
+ suspended_scheduler_timeout/1,
+ multizero_timeout_in_timeout/1]).
-include_lib("common_test/include/ct.hrl").
@@ -70,7 +71,8 @@ all() ->
same_time_yielding_with_cancel_other,
% same_time_yielding_with_cancel_other_accessor,
auto_cancel_yielding,
- suspended_scheduler_timeout].
+ suspended_scheduler_timeout,
+ multizero_timeout_in_timeout].
%% Basic start_timer/3 functionality
@@ -492,7 +494,7 @@ same_time_yielding(Config) when is_list(Config) ->
Mem = mem(),
Ref = make_ref(),
SchdlrsOnln = erlang:system_info(schedulers_online),
- Tmo = erlang:monotonic_time(millisecond) + 3000,
+ Tmo = erlang:monotonic_time(millisecond) + 6000,
Tmrs = lists:map(fun (I) ->
process_flag(scheduler, (I rem SchdlrsOnln) + 1),
erlang:start_timer(Tmo, self(), Ref, [{abs, true}])
@@ -536,7 +538,7 @@ same_time_yielding_with_cancel_other(Config) when is_list(Config) ->
do_cancel_tmrs(Tmo, Tmrs, Tester) ->
BeginCancel = erlang:convert_time_unit(Tmo,
millisecond,
- microsecond) - 100,
+ microsecond) - 500,
busy_wait_until(fun () ->
erlang:monotonic_time(microsecond) >= BeginCancel
end),
@@ -553,7 +555,7 @@ do_cancel_tmrs(Tmo, Tmrs, Tester) ->
same_time_yielding_with_cancel_test(Other, Accessor) ->
Mem = mem(),
SchdlrsOnln = erlang:system_info(schedulers_online),
- Tmo = erlang:monotonic_time(millisecond) + 3000,
+ Tmo = erlang:monotonic_time(millisecond) + 6000,
Tester = self(),
Cancelor = case Other of
false ->
@@ -657,6 +659,26 @@ suspended_scheduler_timeout(Config) when is_list(Config) ->
end,
ok.
+multizero_timeout_in_timeout(Config) when is_list(Config) ->
+ Timeout = 500,
+ MaxTimeoutDiff = 1000,
+
+ %% We want to operate on the same timer wheel all the time...
+ process_flag(scheduler, erlang:system_info(schedulers_online)),
+
+ erlang:send_after(5*(Timeout+MaxTimeoutDiff), self(), pling),
+ erlang:yield(),
+ Start = erlang:monotonic_time(),
+ erts_debug:set_internal_state(multizero_timeout_in_timeout, Timeout),
+ receive multizero_timeout_in_timeout_done -> ok end,
+ End = erlang:monotonic_time(),
+ Time = erlang:convert_time_unit(End-Start, native, millisecond),
+ io:format("Time=~p~n", [Time]),
+ true = Time < Timeout + MaxTimeoutDiff,
+ ok.
+
+
+
process_is_cleaned_up(P) when is_pid(P) ->
undefined == erts_debug:get_internal_state({process_status, P}).
diff --git a/erts/emulator/test/trace_SUITE.erl b/erts/emulator/test/trace_SUITE.erl
index c2d5cd7023..951502cb61 100644
--- a/erts/emulator/test/trace_SUITE.erl
+++ b/erts/emulator/test/trace_SUITE.erl
@@ -39,7 +39,7 @@
system_monitor_large_heap_1/1, system_monitor_large_heap_2/1,
system_monitor_long_schedule/1,
bad_flag/1, trace_delivered/1, trap_exit_self_receive/1,
- trace_info_badarg/1, erl_704/1]).
+ trace_info_badarg/1, erl_704/1, ms_excessive_nesting/1]).
-include_lib("common_test/include/ct.hrl").
@@ -63,7 +63,8 @@ all() ->
system_monitor_long_gc_2, system_monitor_large_heap_1,
system_monitor_long_schedule,
system_monitor_large_heap_2, bad_flag, trace_delivered,
- trap_exit_self_receive, trace_info_badarg, erl_704].
+ trap_exit_self_receive, trace_info_badarg, erl_704,
+ ms_excessive_nesting].
init_per_testcase(_Case, Config) ->
[{receiver,spawn(fun receiver/0)}|Config].
@@ -664,7 +665,7 @@ dist_procs_trace(Config) when is_list(Config) ->
Proc1 ! {trap_exit_please, true},
Proc3 = receive {spawned, Proc1, P3} -> P3 end,
io:format("Proc3 = ~p ~n", [Proc3]),
- {trace, Proc1, getting_linked, Proc3} = receive_first_trace(),
+ {trace, Proc1, link, Proc3} = receive_first_trace(),
Reason3 = make_ref(),
Proc1 ! {send_please, Proc3, {exit_please, Reason3}},
receive {Proc1, {'EXIT', Proc3, Reason3}} -> ok end,
@@ -958,15 +959,14 @@ do_system_monitor_long_schedule() ->
{Self,L} when is_list(L) ->
ok
after 1000 ->
- ct:fail(no_trace_of_pid)
+ ct:fail(no_trace_of_pid)
end,
"ok" = erlang:port_control(Port,1,[]),
- "ok" = erlang:port_control(Port,2,[]),
receive
{Port,LL} when is_list(LL) ->
ok
after 1000 ->
- ct:fail(no_trace_of_port)
+ ct:fail(no_trace_of_port)
end,
port_close(Port),
erlang:system_monitor(undefined),
@@ -1684,6 +1684,18 @@ bad_flag(Config) when is_list(Config) ->
{'EXIT', {badarg, _}} = (catch erlang:trace(new,
true,
[not_a_valid_flag])),
+
+ %% Leaks of {tracer,_} in OTP 23.2
+ Pid = spawn(fun() -> receive die -> ok end end),
+ 1 = erlang:trace(Pid, true, [{tracer, self()},
+ {tracer, self()}]),
+ Pid ! die,
+ {'EXIT', {badarg, _}} =
+ (catch erlang:trace(new, true, [{tracer, self()}
+ | improper])),
+ {'EXIT', {badarg, _}} =
+ (catch erlang:trace(new, true, [{tracer, self()},
+ not_a_valid_flag])),
ok.
%% Test erlang:trace_delivered/1
@@ -1754,6 +1766,33 @@ erl_704_test(N) ->
(catch erlang:resume_process(P)),
erl_704_test(N-1).
+ms_excessive_nesting(Config) when is_list(Config) ->
+ MkMSCond = fun (_Fun, N) when N < 0 -> true;
+ (Fun, N) -> {'or', {'=:=', N, '$1'}, Fun(Fun, N-1)}
+ end,
+ %% Ensure it compiles with substantial but reasonable
+ %% (hmm...) nesting
+ MS = [{['$1'], [MkMSCond(MkMSCond, 100)], []}],
+ io:format("~p~n", [erlang:match_spec_test([1], MS, trace)]),
+ _ = erlang:trace_pattern({?MODULE, '_', '_'}, MS, []),
+ %% Now test a match spec using excessive nesting. This
+ %% used to seg-fault the emulator due to recursion
+ %% beyond the end of the C-stack.
+ %%
+ %% We expect to get a system_limit error, but don't
+ %% fail if it compiles (someone must have rewritten
+ %% compilation of match specs to use an explicit
+ %% stack instead of using recursion).
+ ENMS = [{['$1'], [MkMSCond(MkMSCond, 1000000)], []}],
+ io:format("~p~n", [erlang:match_spec_test([1], ENMS, trace)]),
+ try
+ _ = erlang:trace_pattern({?MODULE, '_', '_'}, ENMS, []),
+ {comment, "compiled"}
+ catch
+ error:system_limit ->
+ {comment, "got system_limit"}
+ end.
+
drop_trace_until_down(Proc, Mon) ->
drop_trace_until_down(Proc, Mon, false, 0, 0).
diff --git a/erts/emulator/test/trace_SUITE_data/Makefile.src b/erts/emulator/test/trace_SUITE_data/Makefile.src
new file mode 100644
index 0000000000..645107d7b0
--- /dev/null
+++ b/erts/emulator/test/trace_SUITE_data/Makefile.src
@@ -0,0 +1,3 @@
+all: slow_drv@dll@
+
+@SHLIB_RULES@
diff --git a/erts/emulator/test/trace_SUITE_data/slow_drv.c b/erts/emulator/test/trace_SUITE_data/slow_drv.c
new file mode 100644
index 0000000000..e7c1eb2125
--- /dev/null
+++ b/erts/emulator/test/trace_SUITE_data/slow_drv.c
@@ -0,0 +1,108 @@
+#ifdef __WIN32__
+#include <windows.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "erl_driver.h"
+
+typedef struct _erl_drv_data {
+ ErlDrvPort erlang_port;
+} EchoDrvData;
+
+static EchoDrvData slow_drv_data, *slow_drv_data_p;
+
+static EchoDrvData *slow_drv_start(ErlDrvPort port, char *command);
+static void slow_drv_stop(EchoDrvData *data_p);
+static void slow_drv_output(ErlDrvData drv_data, char *buf,
+ ErlDrvSizeT len);
+static ErlDrvSSizeT slow_drv_control(ErlDrvData drv_data, unsigned int command,
+ char *buf, ErlDrvSizeT len,
+ char **rbuf, ErlDrvSizeT rlen);
+static void slow_drv_timeout(ErlDrvData drv_data);
+static void slow_drv_finish(void);
+
+static ErlDrvEntry slow_drv_entry = {
+ NULL, /* init */
+ slow_drv_start,
+ slow_drv_stop,
+ slow_drv_output,
+ NULL, /* ready_input */
+ NULL, /* ready_output */
+ "slow_drv",
+ slow_drv_finish,
+ NULL, /* handle */
+ slow_drv_control, /* control */
+ slow_drv_timeout, /* timeout */
+ NULL, /* outputv */
+ NULL, /* ready_async */
+ NULL,
+ NULL,
+ NULL,
+ ERL_DRV_EXTENDED_MARKER,
+ ERL_DRV_EXTENDED_MAJOR_VERSION,
+ ERL_DRV_EXTENDED_MINOR_VERSION,
+ 0,
+ NULL,
+ NULL,
+ NULL
+
+};
+
+DRIVER_INIT(slow_drv)
+{
+ slow_drv_data_p = NULL;
+ return &slow_drv_entry;
+}
+
+static EchoDrvData *slow_drv_start(ErlDrvPort port, char *command)
+{
+ if (slow_drv_data_p != NULL) {
+ return ERL_DRV_ERROR_GENERAL;
+ }
+ slow_drv_data_p = &slow_drv_data;
+ slow_drv_data_p->erlang_port = port;
+ return slow_drv_data_p;
+}
+
+static void slow_drv_stop(EchoDrvData *data_p) {
+ slow_drv_data_p = NULL;
+}
+
+static void slow_drv_output(ErlDrvData drv_data, char *buf, ErlDrvSizeT len) {
+ EchoDrvData* data_p = (EchoDrvData *) drv_data;
+ driver_output(data_p->erlang_port, buf, len);
+}
+
+static ErlDrvSSizeT slow_drv_control(ErlDrvData drv_data, unsigned int command,
+ char *buf, ErlDrvSizeT len,
+ char **rbuf, ErlDrvSizeT rlen)
+{
+ EchoDrvData* data_p = (EchoDrvData *) drv_data;
+ memcpy(*rbuf,"ok",2);
+ if (command == 1) {
+ driver_set_timer(data_p->erlang_port, 0);
+ } else {
+ slow_drv_timeout(drv_data);
+ }
+ return 2;
+}
+
+static void slow_drv_timeout(ErlDrvData drv_data)
+{
+ /* Sleep for 150 msec */
+#ifdef __WIN32__
+ Sleep(150);
+#else
+ usleep(150000);
+#endif
+}
+
+static void slow_drv_finish() {
+ slow_drv_data_p = NULL;
+}
diff --git a/erts/emulator/test/trace_call_time_SUITE.erl b/erts/emulator/test/trace_call_time_SUITE.erl
index a126c48af9..00235bffa5 100644
--- a/erts/emulator/test/trace_call_time_SUITE.erl
+++ b/erts/emulator/test/trace_call_time_SUITE.erl
@@ -35,7 +35,7 @@
-export([seq/3, seq_r/3]).
-export([loaded/1, a_function/1, a_called_function/1, dec/1, nif_dec/1, dead_tracer/1,
- return_stop/1]).
+ return_stop/1,catch_crash/1]).
-define(US_ERROR, 10000).
-define(R_ERROR, 0.8).
@@ -91,7 +91,8 @@ all() ->
false ->
[basic, on_and_off, info, pause_and_restart, scheduling,
disable_ongoing,
- combo, bif, nif, called_function, dead_tracer, return_stop]
+ combo, bif, nif, called_function, dead_tracer, return_stop,
+ catch_crash]
end.
not_run(Config) when is_list(Config) ->
@@ -307,8 +308,7 @@ combo(Config) when is_list(Config) ->
2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, [], [local]),
2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_time]),
2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, MetaMs, [{meta,MetaTracer}]),
- %% not implemented
- %2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_count]),
+ 2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_count]),
1 = erlang:trace(Self, true, [{tracer,LocalTracer} | Flags]),
%%
@@ -337,9 +337,7 @@ combo(Config) when is_list(Config) ->
{value,{match_spec,[]}} = lists:keysearch(match_spec, 1, TraceInfoBif),
{value,{meta, MetaTracer}} = lists:keysearch(meta, 1, TraceInfoBif),
{value,{meta_match_spec,MetaMs}} = lists:keysearch(meta_match_spec, 1, TraceInfoBif),
- %% not implemented
- {value,{call_count,false}} = lists:keysearch(call_count, 1, TraceInfoBif),
- %{value,{call_count,0}} = lists:keysearch(call_count, 1, TraceInfoBif),
+ {value,{call_count,0}} = lists:keysearch(call_count, 1, TraceInfoBif),
{value,{call_time,[]}} = lists:keysearch(call_time, 1, TraceInfoBif),
%%
@@ -408,8 +406,12 @@ bif(Config) when is_list(Config) ->
%%
2 = erlang:trace_pattern({erlang, binary_to_term, '_'}, true, [call_time]),
2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, true, [call_time]),
+ 1 = erlang:trace_pattern({?MODULE, with_bif, 1}, true, [call_time]),
Pid = setup(),
- {L, T1} = execute(Pid, fun() -> with_bif(M) end),
+ {L, Tot1} = execute(Pid, fun() -> with_bif(M) end),
+
+ {call_time,[{Pid,_,S,Us}]} = erlang:trace_info({?MODULE,with_bif,1}, call_time),
+ T1 = Tot1 - (S*1000000 + Us),
ok = check_trace_info({erlang, binary_to_term, 1}, [{Pid, M - 1, 0, 0}], T1/2),
ok = check_trace_info({erlang, term_to_binary, 1}, [{Pid, M - 1, 0, 0}], T1/2),
@@ -418,9 +420,9 @@ bif(Config) when is_list(Config) ->
2 = erlang:trace_pattern({erlang, term_to_binary, '_'}, false, [call_time]),
- {L, T2} = execute(Pid, fun() -> with_bif(M) end),
+ {L, _T2} = execute(Pid, fun() -> with_bif(M) end),
- ok = check_trace_info({erlang, binary_to_term, 1}, [{Pid, M*2 - 2, 0, 0}], T1/2 + T2),
+ ok = check_trace_info({erlang, binary_to_term, 1}, [{Pid, M*2 - 2, 0, 0}], T1),
ok = check_trace_info({erlang, term_to_binary, 1}, false, none),
%%
@@ -439,12 +441,14 @@ nif(Config) when is_list(Config) ->
1 = erlang:trace_pattern({?MODULE, nif_dec, '_'}, true, [call_time]),
1 = erlang:trace_pattern({?MODULE, with_nif, '_'}, true, [call_time]),
Pid = setup(),
- {_, T1} = execute(Pid, fun() -> with_nif(M) end),
+ {_, Tot1} = execute(Pid, fun() -> with_nif(M) end),
+
+ {call_time,[{Pid,_,S,Us}]} = erlang:trace_info({?MODULE,with_nif,1}, call_time),
+ T1 = Tot1 - (S*1000000 + Us),
% the nif is called M - 1 times, the last time the function with 'with_nif'
% returns ok and does not call the nif.
- ok = check_trace_info({?MODULE, nif_dec, 1}, [{Pid, M-1, 0, 0}], T1/2),
- ok = check_trace_info({?MODULE, with_nif, 1}, [{Pid, M, 0, 0}], T1/2),
+ ok = check_trace_info({?MODULE, nif_dec, 1}, [{Pid, M-1, 0, 0}], T1),
%%
P = erlang:trace_pattern({'_','_','_'}, false, [call_time]),
@@ -482,6 +486,8 @@ called_function(Config) when is_list(Config) ->
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
dead_tracer(Config) when is_list(Config) ->
+ TracedMFAs = dead_tracer_mfas(),
+
Self = self(),
FirstTracer = tracer(),
StartTracing = fun() -> turn_on_tracing(Self) end,
@@ -496,14 +502,14 @@ dead_tracer(Config) when is_list(Config) ->
erlang:yield(),
%% Collect and check that we only get call_time info for the current process.
- Info1 = collect_all_info(),
+ Info1 = collect_all_info(TracedMFAs),
[] = other_than_self(Info1),
io:format("~p\n", [Info1]),
%% Note that we have not turned off tracing for the current process,
%% but that the tracer has terminated. No more call_time information should be recorded.
[1,2,3] = seq(1, 3, fun(I) -> I + 1 end),
- [] = collect_all_info(),
+ [] = collect_all_info(TracedMFAs),
%% When we start a second tracer process, that tracer process must
%% not inherit the tracing flags and the dead tracer (even though
@@ -512,7 +518,7 @@ dead_tracer(Config) when is_list(Config) ->
tell_tracer(SecondTracer, StartTracing),
Seq20 = lists:seq(1, 20),
Seq20 = seq(1, 20, fun(I) -> I + 1 end),
- Info2 = collect_all_info(),
+ Info2 = collect_all_info(TracedMFAs),
io:format("~p\n", [Info2]),
[] = other_than_self(Info2),
SecondTracer ! quit,
@@ -548,9 +554,21 @@ turn_on_tracing(Pid) ->
_ = now(),
ok.
-collect_all_info() ->
- collect_all_info([{?MODULE,F,A} || {F,A} <- module_info(functions)] ++
- erlang:system_info(snifs)).
+%% We want to trace functions local to this module as well as all BIFs, and for
+%% the latter we need to ensure that their modules are loaded.
+dead_tracer_mfas() ->
+ Modules = [M || {M,_F,_A} <- erlang:system_info(snifs)],
+ Whitelist0 = gb_sets:from_list(Modules),
+ Whitelist = case code:ensure_modules_loaded(Modules) of
+ {error, Reasons} ->
+ Blacklist = gb_sets:from_list([M || {M, _} <- Reasons]),
+ gb_sets:subtract(Whitelist0, Blacklist);
+ ok ->
+ Whitelist0
+ end,
+ EligibleSNIFs = [MFA || {M,_F,_A}=MFA <- erlang:system_info(snifs),
+ gb_sets:is_element(M, Whitelist)],
+ [{?MODULE,F,A} || {F,A} <- module_info(functions)] ++ EligibleSNIFs.
collect_all_info([MFA|T]) ->
CallTime = erlang:trace_info(MFA, call_time),
@@ -617,6 +635,25 @@ spinner(N) ->
quicky() ->
done.
+%% OTP-16994: next_catch returned a bogus stack pointer when call_time tracing
+%% was enabled, crashing the emulator.
+catch_crash(_Config) ->
+ Fun = id(fun() -> catch_crash_1() end),
+
+ _ = erlang:trace_pattern({?MODULE,'_','_'}, true, [call_time]),
+ _ = erlang:trace(self(), true, [call]),
+
+ Res = (catch Fun()),
+
+ _ = erlang:trace_pattern({'_','_','_'}, false, [call_time]),
+ _ = erlang:trace(self(), false, [call]),
+
+ id(Res),
+
+ ok.
+
+catch_crash_1() ->
+ error(crash).
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -669,21 +706,29 @@ seq_r(Start, Stop, Succ, R) ->
seq_r(Succ(Start), Stop, Succ, [Start | R]).
% Check call time tracing data and print mismatches
-check_trace_info(Mfa, [{Pid, C,_,_}] = Expect, Time) ->
- case erlang:trace_info(Mfa, call_time) of
- % Time tests are somewhat problematic. We want to know if Time (EXPECTED_TIME) and S*1000000 + Us (ACTUAL_TIME)
- % is the same.
- % If the ratio EXPECTED_TIME/ACTUAL_TIME is ~ 1 or if EXPECTED_TIME - ACTUAL_TIME is near zero, the test is ok.
- {call_time,[{Pid,C,S,Us}]} when S >= 0, Us >= 0, abs(1 - Time/(S*1000000 + Us)) < ?R_ERROR; abs(Time - S*1000000 - Us) < ?US_ERROR ->
+check_trace_info(Mfa, [{Pid, ExpectedC,_,_}] = Expect, Time) ->
+ {call_time,[{Pid,C,S,Us}]} = erlang:trace_info(Mfa, call_time),
+ {Mod, Name, Arity} = Mfa,
+ IsBuiltin = erlang:is_builtin(Mod, Name, Arity),
+ if
+ %% Call count on BIFs may exceed number of calls as they often trap to
+ %% themselves.
+ IsBuiltin, C >= ExpectedC, S >= 0, Us >= 0,
+ abs(1 - Time/(S*1000000 + Us)) < ?R_ERROR;
+ abs(Time - S*1000000 - Us) < ?US_ERROR ->
+ ok;
+ not IsBuiltin, C =:= ExpectedC, S >= 0, Us >= 0,
+ abs(1 - Time/(S*1000000 + Us)) < ?R_ERROR;
+ abs(Time - S*1000000 - Us) < ?US_ERROR ->
ok;
- {call_time,[{Pid,C,S,Us}]} ->
+ true ->
Sum = S*1000000 + Us,
- io:format("Expected ~p -> {call_time, ~p (Time ~p us)}~n - got ~w s. ~w us. = ~w us. - ~w -> delta ~w (ratio ~.2f, should be 1.0)~n",
- [Mfa, Expect, Time, S, Us, Sum, Time, Sum - Time, Time/Sum]),
- time_error;
- Other ->
- io:format("Expected ~p -> {call_time, ~p (Time ~p us)}~n - got ~p~n", [ Mfa, Expect, Time, Other]),
- time_count_error
+ io:format("Expected ~p -> {call_time, ~p (Time ~p us)}~n - got ~w "
+ "s. ~w us. = ~w us. - ~w -> delta ~w (ratio ~.2f, "
+ "should be 1.0)~n",
+ [Mfa, Expect, Time,
+ S, Us, Sum, Time, Sum - Time, Time/Sum]),
+ time_error
end;
check_trace_info(Mfa, Expect, _) ->
case erlang:trace_info(Mfa, call_time) of
@@ -748,7 +793,8 @@ setup() ->
setup([]).
setup(Opts) ->
- Pid = spawn_link(fun() -> loop() end),
+ Pid = spawn_opt(fun() -> loop() end,
+ [link, {max_heap_size, 10000}]),
1 = erlang:trace(Pid, true, [call|Opts]),
Pid.
@@ -772,9 +818,12 @@ loop() ->
quit ->
ok;
{Pid, execute, Fun } when is_function(Fun) ->
+ %% Make sure we always run with the same amount of reductions.
+ erlang:yield(),
Pid ! {self(), answer, erlang:apply(Fun, [])},
loop();
{Pid, execute, {M, F, A}} ->
+ erlang:yield(),
Pid ! {self(), answer, erlang:apply(M, F, A)},
loop()
end.
diff --git a/erts/emulator/test/trace_local_SUITE.erl b/erts/emulator/test/trace_local_SUITE.erl
index 6d87f390e6..505d38dc67 100644
--- a/erts/emulator/test/trace_local_SUITE.erl
+++ b/erts/emulator/test/trace_local_SUITE.erl
@@ -1111,12 +1111,14 @@ x_exc_body(ExcOpts, {M,F}=Func, Args, Apply) ->
end,
{value,Value}
catch
- Thrown when Nocatch ->
+ throw:Thrown:Stk when Nocatch ->
+ put(get_stacktrace, Stk),
CR = {error,{nocatch,Thrown}},
x_exc_exception(Rtt, M, F, Args, Arity, CR),
expect({eft,{?MODULE,exc,2},CR}),
CR;
- Class:Reason ->
+ Class:Reason:Stk ->
+ put(get_stacktrace, Stk),
CR = {Class,Reason},
x_exc_exception(Rtt, M, F, Args, Arity, CR),
expect({eft,{?MODULE,exc,2},CR}),
@@ -1157,7 +1159,8 @@ x_exc_exception(_Rtt, M, F, _, Arity, CR) ->
expect({eft,{M,F,Arity},CR}).
x_exc_stacktrace() ->
- x_exc_stacktrace(erlang:get_stacktrace()).
+ x_exc_stacktrace(get(get_stacktrace)).
+
%% Truncate stacktrace to below exc/2
x_exc_stacktrace([{?MODULE,x_exc,4,_}|_]) -> [];
x_exc_stacktrace([{?MODULE,x_exc_func,4,_}|_]) -> [];
diff --git a/erts/emulator/test/z_SUITE.erl b/erts/emulator/test/z_SUITE.erl
index d5675c5217..5b2e77dfa2 100644
--- a/erts/emulator/test/z_SUITE.erl
+++ b/erts/emulator/test/z_SUITE.erl
@@ -34,7 +34,8 @@
-export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2]).
--export([schedulers_alive/1, node_container_refc_check/1,
+-export([used_thread_specific_events/1, schedulers_alive/1,
+ node_container_refc_check/1,
long_timers/1, pollset_size/1,
check_io_debug/1, get_check_io_info/0,
lc_graph/1,
@@ -46,7 +47,8 @@ suite() ->
{timetrap, {minutes, 5}}].
all() ->
- [schedulers_alive, node_container_refc_check,
+ [used_thread_specific_events, schedulers_alive,
+ node_container_refc_check,
long_timers, pollset_size, check_io_debug,
lc_graph,
%% Make sure that the leaked_processes/1 is always
@@ -72,6 +74,36 @@ end_per_testcase(_Name, Config) ->
%%% The test cases -------------------------------------------------------------
%%%
+used_thread_specific_events(Config) when is_list(Config) ->
+ Pid = whereis(used_thread_specific_events_holder),
+ Mon = monitor(process, Pid),
+ Pid ! {get_used_tse, self()},
+ UsedTSE = erlang:system_info(ethread_used_tse),
+ receive
+ {used_tse, InitialUsedTSE} ->
+ io:format("InitialUsedTSE=~p UsedTSE=~p~n", [InitialUsedTSE, UsedTSE]),
+ case os:type() of
+ {win32,_} ->
+ %% The windows poll implementation creates threads on demand
+ %% which in turn will get thread specific events allocated.
+ %% We don't know how many such threads are created, so we
+ %% just have to guess and test that the amount of events is
+ %% not huge.
+ Extra = 100, %% Value take out of the blue...
+ if UsedTSE =< InitialUsedTSE+Extra -> ok;
+ true -> ct:fail("An unexpected large amount of thread specific events used!")
+ end;
+ _ ->
+ if UsedTSE =< InitialUsedTSE -> ok;
+ true -> ct:fail("An increased amount of thread specific events used!")
+ end
+ end,
+ exit(Pid, kill),
+ receive {'DOWN', Mon, process, Pid, _} -> ok end;
+ {'DOWN', Mon, process, Pid, Reason} ->
+ ct:fail({used_thread_specific_events_holder, Reason})
+ end.
+
%% Tests that all schedulers are actually used
schedulers_alive(Config) when is_list(Config) ->
Master = self(),
diff --git a/erts/emulator/utils/beam_makeops b/erts/emulator/utils/beam_makeops
index c56ded6af2..f951a33567 100755
--- a/erts/emulator/utils/beam_makeops
+++ b/erts/emulator/utils/beam_makeops
@@ -186,7 +186,7 @@ foreach my $type (keys %pattern_type) {
$construction_type{$type} = 1
if index($genop_types, $type) >= 0;
}
-foreach my $makes_no_sense ('f', 'j', 'o', 'p', 'q') {
+foreach my $makes_no_sense ('f', 'j', 'o', 'q') {
delete $construction_type{$makes_no_sense};
}
@@ -719,7 +719,7 @@ sub emulator_output {
print "const GenOpEntry gen_opc[] = {\n";
for ($i = 0; $i < @gen_opname; $i++) {
if ($i == $num_file_opcodes) {
- print "\n/*\n * Internal generic instructions.\n */\n\n";
+ print starred_comment("Internal generic instructions.");
}
my($name) = $gen_opname[$i];
my($arity) = $gen_arity[$i];
@@ -1315,7 +1315,7 @@ sub combine_instruction_group {
my $inc = 0;
unless ($i == $#slots) {
- $flags = "-no_next";
+ $flags = "-micro_instruction";
my $next_offset = $label_to_offset{$next};
$inc = ($offset + $size) - $next_offset;
$transfer_to_next = "I += $inc;\n" if $inc;
@@ -1553,8 +1553,10 @@ sub code_gen {
my $dispatch_next;
my $instr_offset = $group_size + $offset + 1;
- if ($flags =~ /-no_next/) {
+ if ($flags =~ /-micro_instruction/) {
$dispatch_next = "";
+ } elsif ($flags =~ /-no_next/) {
+ $dispatch_next = "ASSERT(!\"Fell through '$name' (-no_next)\");";
} elsif ($flags =~ /-no_prefetch/) {
$dispatch_next = "\nI += $instr_offset;\n" .
"ASSERT(VALID_INSTR(*I));\n" .
@@ -2238,6 +2240,7 @@ sub parse_transformation {
(undef,$_) = compile_transform(0, $rest_var, @op);
}
}
+ $orig =~ tr/ \t/ /s;
push(@transformations, [$., $orig, [@from], [reverse @to]]);
}
@@ -2396,6 +2399,13 @@ sub tr_gen {
}
#
+ # Group instructions.
+ #
+ foreach $key (sort keys %gen_transform) {
+ $gen_transform{$key} = group_tr($gen_transform{$key});
+ }
+
+ #
# Print the generated transformation engine.
#
my($offset) = 0;
@@ -2404,29 +2414,18 @@ sub tr_gen {
$gen_transform_offset{$key} = $offset;
my @instr = @{$gen_transform{$key}};
- #
- # If the last instruction is 'fail', remove it and
- # convert the previous 'try_me_else' to 'try_me_else_fail'.
- #
- if (is_instr($instr[$#instr], 'fail')) {
- pop(@instr);
- my $i = $#instr;
- $i-- while !is_instr($instr[$i], 'try_me_else');
- $instr[$i] = make_op('', 'try_me_else_fail');
- }
-
foreach $instr (@instr) {
my($size, $instr_ref, $comment) = @$instr;
my($op, @args) = @$instr_ref;
- print " ";
if (!defined $op) {
$comment =~ s/\n(.)/\n $1/g;
- print "\n", $comment;
+ print $comment;
} else {
+ print " ";
$op = "TOP_$op";
$match_engine_ops{$op} = 1;
if ($comment ne '') {
- printf "%-24s /* %s */\n", (join(", ", ($op, @args)) . ","),
+ printf "%-30s /* %s */\n", (join(", ", ($op, @args)) . ","),
$comment;
} else {
print join(", ", ($op, @args)), ",\n";
@@ -2436,9 +2435,7 @@ sub tr_gen {
}
print "\n";
}
- print "/*\n";
- print " * Total number of words: $offset\n";
- print " */\n";
+ print starred_comment("Total number of words: $offset");
print "};\n\n";
}
@@ -2452,6 +2449,7 @@ sub tr_gen_from {
my $where = "left side of transformation in line $line: ";
my $may_fail = 0;
my $is_first = 1;
+ my @instrs;
foreach $ref (@tr) {
my($name, $arity, @ops) = @$ref;
@@ -2469,20 +2467,26 @@ sub tr_gen_from {
$name = $1;
$may_fail = 1;
my $var;
- my(@args);
+ my @args;
+ my @vars;
foreach $var (@ops) {
- error($where, "variable '$var' unbound")
+ if ($var =~ /^-?\d+$/) {
+ push @args, $var;
+ next;
+ }
+ error($where, "'$var' unbound")
unless defined $var{$var};
+ push @vars, $var;
if ($var_type{$var} eq 'scalar') {
push(@args, "var[$var{$var}]");
} else {
push(@args, "rest_args");
}
}
- my $pi = tr_next_index(\@pred_table, \%pred_table, $name, @args);
- my $op = make_op("$name()", 'pred', $pi);
- my @slots = grep(/^\d+/, map { $var{$_} } @ops);
+ my $pi = tr_next_index(\@{pred_table}, \%pred_table, $name, @args);
+ my $op = make_op("$name()", 'pred', $pi);
+ my @slots = grep(/^\d+/, map { $var{$_} } @vars);
op_slot_usage($op, @slots);
push(@code, $op);
next;
@@ -2497,6 +2501,7 @@ sub tr_gen_from {
$opnum = $gen_opnum{$name,$arity};
push(@code, make_op("$name/$arity", 'next_instr', $opnum));
+ push @instrs, "$name/$arity";
foreach $op (@ops) {
my($var, $type, $type_val, $cond, $val) = @$op;
my $ignored_var = "$var (ignored)";
@@ -2587,14 +2592,47 @@ sub tr_gen_from {
#
push(@code, make_op($may_fail ? '' : 'always reached', 'commit'));
+ #
+ # Peephole optimization: combine instructions.
+ #
+ for (my $i = 0; $i < @code; $i++) {
+ if (is_instr($code[$i], 'is_type')) {
+ my(undef, $is_type_ref, $type_comment) = @{$code[$i]};
+ if (is_instr($code[$i+1], 'set_var_next_arg')) {
+ my(undef, $next_ref, $next_comment) = @{$code[$i+1]};
+ my $comment = "$type_comment $next_comment";
+ my $op = make_op($comment, 'is_type_set_var_next_arg',
+ $is_type_ref->[1], $next_ref->[1]);
+ splice @code, $i, 2, ($op);
+ } elsif (is_instr($code[$i+1], 'next_arg')) {
+ my $op = make_op($type_comment, 'is_type_next_arg', $is_type_ref->[1]);
+ splice @code, $i, 2, ($op);
+ }
+ } elsif (is_instr($code[$i], 'is_type_eq')) {
+ my(undef, $is_type_ref, $type_comment) = @{$code[$i]};
+ if (is_instr($code[$i+1], 'set_var_next_arg')) {
+ my(undef, $next_ref, $next_comment) = @{$code[$i+1]};
+ my $comment = "$type_comment $next_comment";
+ my $op = make_op($comment, 'is_type_eq_set_var_next_arg',
+ $is_type_ref->[1], $is_type_ref->[2],
+ $next_ref->[1]);
+ splice @code, $i, 2, ($op);
+ } elsif (is_instr($code[$i+1], 'next_arg')) {
+ my $op = make_op($type_comment, 'is_type_eq_next_arg',
+ $is_type_ref->[1], $is_type_ref->[2]);
+ splice @code, $i, 2, ($op);
+ }
+ }
+ }
+
$te_max_vars = $var_num
if $te_max_vars < $var_num;
- [\%var, \%var_type, \@code];
+ [\%var, \%var_type, \@instrs, \@code];
}
sub tr_gen_to {
my($line, $orig_transform, $so_far, @tr) = @_;
- my($var_ref, $var_type_ref, $code_ref) = @$so_far;
+ my($var_ref, $var_type_ref, $instrs_ref, $code_ref) = @$so_far;
my(%var) = %$var_ref;
my(%var_type) = %$var_type_ref;
my(@code) = @$code_ref;
@@ -2662,11 +2700,10 @@ sub tr_gen_to {
op_slot_usage($op, $var{$var});
push(@code, $op);
} elsif ($type ne '') {
- push(@code, make_op('', 'store_type', "TAG_$type"));
- if ($type_val) {
- push(@code, make_op('', 'store_val', $type_val));
- }
- push(@code, make_op('', 'next_arg'));
+ my $val = $type_val || 0;
+ my $comment = "$type=$val";
+ my $op = make_op($comment, 'store_val_next_arg', "TAG_$type", $val);
+ push @code, $op;
}
}
pop(@code) if is_instr($code[$#code], 'next_arg');
@@ -2677,31 +2714,133 @@ sub tr_gen_to {
tr_maybe_keep(\@code);
tr_maybe_rename(\@code);
+ combine_commit(\@code);
tr_remove_unused(\@code);
+ chain_instructions($instrs_ref, $line, $orig_transform,
+ $cannot_fail, @code);
+}
+
+#
+# Chain together all codes segments having the same first instruction.
+#
+sub chain_instructions() {
+ my($instrs_ref, $line, $orig_transform, $cannot_fail, @code) = @_;
+ my($first,$second) = @$instrs_ref;
+
+ my $tr_ref = $gen_transform{$first};
+
+ if ($tr_ref) {
+ my (undef, $cant_fail) = $$tr_ref[$#{$tr_ref}];
+ if ($cant_fail) {
+ error("Line $line: A previous transformation shadows '$orig_transform'");
+ }
+ }
+ shift @code;
+ my $comment = starred_comment("Line $line:", " $orig_transform");
+ push @$tr_ref, [$first,$second,$cannot_fail,$comment,@code];
+
+ $gen_transform{$first} = $tr_ref;
+}
+
+#
+# Optimize the code for transformations matching the same first
+# instruction.
+#
+
+sub group_tr {
+ my($lref) = @_;
+
#
- # Chain together all codes segments having the same first operation.
+ # Group tranformations (while keeping the order) that all match the same
+ # second instruction.
#
- my($first_ref) = shift(@code);
- my($size, $first, $key) = @$first_ref;
- my($dummy, $arity);
- ($dummy, $op, $arity) = @$first;
- my($comment) = "\n/*\n * Line $line:\n * $orig_transform\n */\n\n";
+ for (my $i = 0; $i < @$lref; $i++) {
+ my(undef,$current) = @{${$lref}[$i]};
+ next unless defined $current;
+
+ # Find the next instruction that as the same second instruction.
+ for (my $j = $i + 1; $j < @$lref; $j++) {
+ my(undef,$other) = @{${$lref}[$j]};
- my $prev_last;
- $prev_last = pop(@{$gen_transform{$key}})
- if defined $gen_transform{$key}; # Fail
+ # If this instruction does not match a second instruction,
+ # we must not continue the search.
+ last unless defined $other;
- if ($prev_last && !is_instr($prev_last, 'fail')) {
- error("Line $line: A previous transformation shadows '$orig_transform'");
+ if ($other eq $current) {
+ # Found an instruction. If it is already the very next
+ # instruction, place it directly after the current instruction.
+ if ($j > $i + 1) {
+ my($el) = splice @$lref, $j, 1;
+ splice @$lref, $i, 0, ($el);
+ }
+ last;
+ }
+ }
}
- unless ($cannot_fail) {
- unshift(@code, make_op('', 'try_me_else',
- tr_code_len(@code)));
- push(@code, make_op("$key", 'fail'));
+
+ #
+ # Add 'try_me_else' instructions to try the next transformation
+ # when the current transformation fails.
+ #
+ for (my $i = 0; $i < @$lref; $i++) {
+ my($first,$second,$cannot_fail,$comment,@c) = @{${$lref}[$i]};
+ unless ($cannot_fail) {
+ if ($i == $#{$lref}) {
+ unshift @c, make_op('', 'try_me_else_fail');
+ } else {
+ unshift @c, make_op('', 'try_me_else', code_len(@c));
+ }
+ }
+ ${$lref}[$i] = [$first,$second,$cannot_fail,$comment,@c];
+ }
+
+ #
+ # Find consecutive runs of at least two transformation matching
+ # the same second instruction. When a run is found, add a
+ # 'skip_unless' instruction that will skip all of the instructions in the run
+ # when the second instruction is wrong.
+ #
+ for (my $i = 0; $i < @$lref; $i++) {
+ my(undef,$current) = @{${$lref}[$i]};
+ next unless defined $current;
+ my $j;
+ my $skip_len = 0;
+
+ for ($j = $i; $j < @$lref; $j++) {
+ my(undef,$other,undef,undef,@c) = @{${$lref}[$j]};
+ last unless defined $other and $other eq $current;
+ $skip_len += code_len(@c);
+ }
+
+ if ($j > $i + 1) {
+ my $num_rules_skipped = $j - $i;
+ my $comment = "Skip $num_rules_skipped rules" .
+ " unless the second instruction is $current.";
+ $comment = starred_comment($comment);
+ my($name, $arity) = split('/', $current);
+ my $op = make_op('', 'skip_unless',
+ $gen_opnum{$name,$arity}, $skip_len);
+ splice @$lref, $i, 0, (['','',1,$comment,$op]);
+ $i = $j + 1;
+ if ($j == $#{$lref}) {
+ my($first,$second,$cannot_fail,$comment,@c) = @{${$lref}[$j]};
+ push @c, make_op('wrong second instruction', 'fail');
+ ${$lref}[$j] = [$first,$second,$cannot_fail,$comment,@c];
+ }
+ }
}
- unshift(@code, make_op($comment));
- push(@{$gen_transform{$key}}, @code),
+
+ #
+ # Flatten the code to a one-dimensional sequence of instructions.
+ #
+ my @code;
+ for (my $i = 0; $i < @$lref; $i++) {
+ my($first,$second,$cannot_fail,$comment,@c) = @{${$lref}[$i]};
+ push @code, make_op($comment);
+ push @code, @c;
+ }
+ \@code;
}
sub tr_maybe_keep {
@@ -2718,6 +2857,10 @@ sub tr_maybe_keep {
@last_instr = ($args[0]);
} elsif ($op eq 'set_var_next_arg') {
push @last_instr, $args[0];
+ } elsif ($op eq 'is_type_set_var_next_arg') {
+ push @last_instr, $args[1];
+ } elsif ($op eq 'is_type_eq_set_var_next_arg') {
+ push @last_instr, $args[2];
} elsif ($op eq 'next_arg') {
push @last_instr, 'ignored';
} elsif ($op eq 'new_instr') {
@@ -2744,6 +2887,22 @@ sub tr_maybe_keep {
}
}
+sub combine_commit {
+ my($ref) = @_;
+
+ for (my $i = 1; $i < @$ref; $i++) {
+ my $instr = $$ref[$i];
+ my($size, $instr_ref, $comment) = @$instr;
+ my($op, @args) = @$instr_ref;
+ if ($op eq 'rest_args') {
+ return;
+ } elsif ($op eq 'new_instr' and is_instr($$ref[$i-1], 'commit')) {
+ my $op = make_op($comment, 'commit_new_instr', @args);
+ splice @$ref, $i - 1, 2, ($op);
+ }
+ }
+}
+
sub tr_maybe_rename {
my($ref) = @_;
my $s = 'left';
@@ -2764,8 +2923,22 @@ sub tr_maybe_rename {
$num_args++;
}
$a++;
+ } elsif ($op eq 'is_type_set_var_next_arg') {
+ if ($num_args == $a and $args[1] == $a) {
+ $num_args++;
+ }
+ $a++;
+ } elsif ($op eq 'is_type_eq_set_var_next_arg') {
+ if ($num_args == $a and $args[2] == $a) {
+ $num_args++;
+ }
+ $a++;
} elsif ($op eq 'next_arg') {
$a++;
+ } elsif ($op eq 'is_type_next_arg') {
+ $a++;
+ } elsif ($op eq 'is_type_eq_next_arg') {
+ $a++;
} elsif ($op eq 'commit') {
$a = 0;
$first = $i;
@@ -2809,8 +2982,7 @@ sub tr_remove_unused {
}
}
- # Replace 'set_var_next_arg' with 'next_arg' if the variable
- # is never used.
+ # If a variable is not used, don't store the variable.
for my $instr (@$ref) {
my($size, $instr_ref, $comment) = @$instr;
my($op, @args) = @$instr_ref;
@@ -2818,7 +2990,16 @@ sub tr_remove_unused {
my $var = $args[0];
next if $used{$var};
$instr = make_op("$comment (ignored)", 'next_arg');
- }
+ } elsif ($op eq 'is_type_set_var_next_arg') {
+ my($type,$var) = @args;
+ next if $used{$var};
+ $instr = make_op("$comment (ignored)", 'is_type_next_arg', $type);
+ } elsif ($op eq 'is_type_eq_set_var_next_arg') {
+ my($type,$val,$var) = @args;
+ next if $used{$var};
+ $instr = make_op("$comment (ignored)", 'is_type_eq_next_arg',
+ $type, $val);
+ }
}
# Delete a sequence of 'next_arg' instructions when they are
@@ -2846,7 +3027,7 @@ sub tr_remove_unused {
}
}
-sub tr_code_len {
+sub code_len {
my($sum) = 0;
my($ref);
@@ -2878,6 +3059,10 @@ sub get_comment {
$ref->[2];
}
+sub starred_comment {
+ "\n/*" . join("\n * ", '', @_) . "\n */\n\n";
+}
+
sub tr_next_index {
my($lref,$href,$name,@args) = @_;
my $code = "RVAL = $name(" . join(', ', 'st', @args) . "); break;\n";
diff --git a/erts/emulator/utils/find_cross_ycf b/erts/emulator/utils/find_cross_ycf
new file mode 100755
index 0000000000..2dcc339f68
--- /dev/null
+++ b/erts/emulator/utils/find_cross_ycf
@@ -0,0 +1,55 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-define(YCF, "yielding_c_fun").
+
+main([]) ->
+ YCF = case os:type() of
+ {win32, _} ->
+ ?YCF ++ ".exe";
+ _ ->
+ ?YCF
+ end,
+ RootDir = code:root_dir(),
+ PureBootstrapYCF = filename:join([RootDir, "bin", YCF]),
+ case file:read_file_info(PureBootstrapYCF) of
+ {ok, _} ->
+ io:format("~s~n", [PureBootstrapYCF]),
+ halt(0);
+ {error, _} ->
+ ok
+ end,
+ InstalledYCF = filename:join([RootDir,
+ "erts-" ++ erlang:system_info(version),
+ "bin",
+ YCF]),
+ case file:read_file_info(InstalledYCF) of
+ {ok, _} ->
+ io:format("~s~n", [InstalledYCF]),
+ halt(0);
+ {error, _} ->
+ io:format("No '~s' found in used bootstrap system under '~s'~n",
+ [YCF, RootDir]),
+ halt(1)
+ end,
+ ok.
diff --git a/erts/emulator/utils/make_alloc_types b/erts/emulator/utils/make_alloc_types
index 33afe139a2..ddb8713a72 100755
--- a/erts/emulator/utils/make_alloc_types
+++ b/erts/emulator/utils/make_alloc_types
@@ -233,6 +233,7 @@ foreach my $a (@a_order) {
$a_no--;
print DST "\n#define ERTS_ALC_A_MAX ($a_no)\n";
+print DST "\n#define ERTS_ALC_A_COUNT (ERTS_ALC_A_MAX - ERTS_ALC_A_MIN + 1)\n";
# Print class numbers -----------------------------------------------------
@@ -254,6 +255,7 @@ foreach my $c (sort keys(%c_tab)) {
}
$c_no--;
print DST "\n#define ERTS_ALC_C_MAX ($c_no)\n";
+print DST "\n#define ERTS_ALC_C_COUNT (ERTS_ALC_C_MAX - ERTS_ALC_C_MIN + 1)\n";
# Print type number intervals ---------------------------------------------
@@ -291,6 +293,7 @@ foreach my $a (@a_order) {
}
$t_no--;
print DST "#define ERTS_ALC_N_MAX ($t_no)\n";
+print DST "\n#define ERTS_ALC_N_COUNT (ERTS_ALC_N_MAX - ERTS_ALC_N_MIN + 1)\n";
# Print multi thread use of allocators -------------------------------------
diff --git a/erts/emulator/utils/make_driver_tab b/erts/emulator/utils/make_driver_tab
index a000b9d415..78b6fba254 100755
--- a/erts/emulator/utils/make_driver_tab
+++ b/erts/emulator/utils/make_driver_tab
@@ -52,6 +52,7 @@ while (@ARGV) {
if ( $d =~ /^.*\.a$/ ) {
$d = basename $d;
$d =~ s/\.a$//; # strip .a
+ $d =~ s/\.gprof$//; # strip .gprof
if ($mode == 1) {
push(@static_drivers, $d);
}
diff --git a/erts/emulator/utils/make_preload b/erts/emulator/utils/make_preload
index 0cd3509b62..04163bc63b 100755
--- a/erts/emulator/utils/make_preload
+++ b/erts/emulator/utils/make_preload
@@ -40,7 +40,6 @@ use File::Basename;
my $gen_rc = 0;
my $gen_old = 0;
my $windres = 0;
-my $msys = 0;
my $file;
my $progname = basename($0);
@@ -51,8 +50,6 @@ while (@ARGV && $ARGV[0] =~ /^-(\w+)/) {
$gen_rc = 1;
} elsif ($opt eq '-windres') {
$windres = 1;
- } elsif ($opt eq '-msys') {
- $msys = 1;
} elsif ($opt eq '-old') {
$gen_old = 1;
} else {
@@ -73,12 +70,7 @@ foreach $file (@ARGV) {
my $module = basename($file, ".beam");
if ($gen_rc) {
my $win_file;
- if ($msys) {
- ($win_file) = split("\n", `(msys2win_path.sh $file)`);
- } else {
- ($win_file) = split("\n", `(cygpath -d $file 2>/dev/null || cygpath -w $file)`);
- }
- $win_file =~ s&\\&\\\\&g;
+ ($win_file) = split("\n", `(w32_path.sh -d $file)`);
print "$num ERLANG_CODE \"$win_file\"\n";
push(@modules, " ", -s $file, "L, $num, ",
length($module), ",\"$module\",\n");
diff --git a/erts/emulator/utils/make_tables b/erts/emulator/utils/make_tables
index deee5c2344..f87472111f 100755
--- a/erts/emulator/utils/make_tables
+++ b/erts/emulator/utils/make_tables
@@ -35,7 +35,6 @@ use File::Basename;
# Output:
# <-src>/erl_am.c
# <-src>/erl_bif_table.c
-# <-src>/erl_bif_wrap.c
# <-src>/erl_dirty_bif_wrap.c
# <-src>/erl_guard_bifs.c
# <-src>/hipe_nbif_impl.c
@@ -92,7 +91,7 @@ while (<>) {
my($type, @args) = split;
if ($type eq 'atom') {
save_atoms(@args);
- } elsif ($type eq 'bif' or $type eq 'ubif' or $type eq 'gcbif') {
+ } elsif ($type eq 'bif' or $type eq 'ubif' or $type eq 'hbif') {
if (@args > 2) {
error("$type only allows two arguments");
}
@@ -124,14 +123,22 @@ while (<>) {
error("invalid sched_type: $sched_type");
}
- my $wrapper;
- if ($type eq 'bif') {
- $wrapper = "wrap_$alias";
- } else {
- $wrapper = $alias;
- }
+ my $kind;
+ if ($type eq 'bif') {
+ $kind = 'BIF_KIND_REGULAR';
+ }
+ elsif ($type eq 'hbif') {
+ $kind = 'BIF_KIND_HEAVY';
+ }
+ elsif ($type eq 'ubif') {
+ $kind = 'BIF_KIND_GUARD';
+ }
+ else {
+ error("invalid bif_type: $type");
+ }
+
push(@bif, ["am_$atom_alias{$mod}","am_$atom_alias{$name}",$arity,
- $alias3,$wrapper,$alias]);
+ $alias3,$alias,$kind]);
push(@bif_info, [$type, $sched_type, $alias3, $alias]);
} elsif ($type eq 'dirty-cpu' or $type eq 'dirty-io'
or $type eq 'dirty-cpu-test' or $type eq 'dirty-io-test') {
@@ -196,7 +203,7 @@ open_file("$include/erl_bif_list.h");
my $i;
for ($i = 0; $i < @bif; $i++) {
# module atom, function atom, arity, C function, table index
- print "BIF_LIST($bif[$i]->[0],$bif[$i]->[1],$bif[$i]->[2],$bif[$i]->[3],$bif[$i]->[5],$i)\n";
+ print "BIF_LIST($bif[$i]->[0],$bif[$i]->[1],$bif[$i]->[2],$bif[$i]->[3],$bif[$i]->[4],$i)\n";
}
#
@@ -208,15 +215,24 @@ my $bif_size = @bif;
print <<EOF;
#ifndef __ERL_BIF_TABLE_H__
#define __ERL_BIF_TABLE_H__
+
+#include "sys.h"
+
typedef void *BifFunction;
+typedef enum {
+ BIF_KIND_REGULAR,
+ BIF_KIND_HEAVY,
+ BIF_KIND_GUARD
+} BifKind;
+
typedef struct bif_entry {
Eterm module;
Eterm name;
int arity;
BifFunction f;
- BifFunction traced;
BifFunction impl;
+ BifKind kind;
} BifEntry;
typedef struct erts_gc_bif {
@@ -231,8 +247,7 @@ typedef struct erts_u_bif {
} ErtsUBif;
extern BifEntry bif_table[];
-extern Export* bif_export[];
-extern const ErtsGcBif erts_gc_bifs[];
+extern Export bif_trap_export[];
extern const ErtsUBif erts_u_bifs[];
#define BIF_SIZE $bif_size
@@ -250,7 +265,6 @@ for ($i = 0; $i < @bif; $i++) {
my $args = join(', ', 'Process*', 'Eterm*', 'UWord*');
my $name = $bif_info[$i]->[3];
print "Eterm $name($args);\n";
- print "Eterm wrap_$name($args);\n";
print "Eterm erts_gc_$name(Process* p, Eterm* reg, Uint live);\n"
if $bif_info[$i]->[0] eq 'gcbif';
print "Eterm $bif_info[$i]->[2]($args);\n"
@@ -273,7 +287,7 @@ my $i;
includes("export.h", "sys.h", "erl_vm.h", "erl_process.h", "bif.h",
"erl_bif_table.h", "erl_atom_table.h");
-print "\nExport* bif_export[BIF_SIZE];\n";
+print "\nExport bif_trap_export[BIF_SIZE];\n";
print "BifEntry bif_table[] = {\n";
for ($i = 0; $i < @bif; $i++) {
@@ -283,25 +297,6 @@ for ($i = 0; $i < @bif; $i++) {
print "};\n\n";
#
-# Generate the bif wrappers file.
-#
-
-open_file("$src/erl_bif_wrap.c");
-my $i;
-includes("export.h", "sys.h", "erl_vm.h", "global.h", "erl_process.h", "bif.h",
- "erl_bif_table.h", "erl_atom_table.h");
-for ($i = 0; $i < @bif; $i++) {
- next if $bif[$i]->[3] eq $bif[$i]->[4]; # Skip unwrapped bifs
- my $arity = $bif[$i]->[2];
- my $func = $bif_info[$i]->[3];
- print "Eterm\n";
- print "wrap_$func(Process* p, Eterm* args, UWord* I)\n";
- print "{\n";
- print " return erts_bif_trace($i, p, args, I);\n";
- print "}\n\n";
-}
-
-#
# Generate erl_gc_bifs.c.
#
@@ -309,18 +304,11 @@ open_file("$src/erl_guard_bifs.c");
my $i;
includes("export.h", "sys.h", "erl_vm.h", "global.h", "erl_process.h", "bif.h",
"erl_bif_table.h");
-print "const ErtsGcBif erts_gc_bifs[] = {\n";
-for ($i = 0; $i < @bif; $i++) {
- next unless $bif_info[$i]->[0] eq 'gcbif';
- print " {$bif[$i]->[3], erts_gc_$bif[$i]->[3], BIF_$bif[$i]->[5]},\n";
-}
-print " {NULL, NULL, -1}\n";
-print "};\n";
print "const ErtsUBif erts_u_bifs[] = {\n";
for ($i = 0; $i < @bif; $i++) {
next unless $bif_info[$i]->[0] eq 'ubif';
- print " {$bif[$i]->[3], BIF_$bif[$i]->[5]},\n";
+ print " {$bif[$i]->[3], BIF_$bif[$i]->[4]},\n";
}
print " {NULL, -1}\n";
print "};\n";
@@ -368,7 +356,7 @@ EOF
my $i;
for ($i = 0; $i < @bif; $i++) {
print <<EOF;
-Eterm nbif_impl_$bif[$i]->[5](Process *c_p, Eterm *regs);
+Eterm nbif_impl_$bif[$i]->[4](Process *c_p, Eterm *regs);
EOF
}
@@ -388,9 +376,9 @@ EOF
for ($i = 0; $i < @bif; $i++) {
print <<EOF;
-Eterm nbif_impl_$bif[$i]->[5](Process *c_p, Eterm *regs)
+Eterm nbif_impl_$bif[$i]->[4](Process *c_p, Eterm *regs)
{
- return $bif[$i]->[3](c_p, regs, (UWord *) bif_export\[BIF_$bif[$i]->[5]\]);
+ return $bif[$i]->[3](c_p, regs, (UWord *)&bif_trap_export\[BIF_$bif[$i]->[4]\]);
}
EOF
diff --git a/erts/emulator/valgrind/suppress.standard b/erts/emulator/valgrind/suppress.standard
index 99a3ee4048..a029ea7d37 100644
--- a/erts/emulator/valgrind/suppress.standard
+++ b/erts/emulator/valgrind/suppress.standard
@@ -252,7 +252,19 @@ obj:*/ssleay.*
fun:start_thread
fun:clone
}
-
+{
+ Crypto engine internal...
+ Memcheck:Leak
+ fun:malloc
+ fun:CRYPTO_malloc
+ fun:ENGINE_new
+ fun:ENGINE_by_id
+ fun:engine_by_id_nif
+ fun:process_main
+ fun:sched_thread_func
+ fun:thr_wrapper
+ fun:start_thread
+}
{
Harmless assembler bug in openssl
Memcheck:Addr8
@@ -265,7 +277,36 @@ obj:*/ssleay.*
Memcheck:Addr8
fun:RC4
}
-
+{
+Crypto internal... loading gives expected errors when curves are tried. But including <openssl/err.h> and removing them triggers compiler errors on Windows
+Memcheck:Leak
+fun:malloc
+...
+fun:valid_curve
+fun:init_curves
+fun:init_curve_types
+fun:init_algorithms_types
+fun:initialize
+fun:load
+fun:erts_load_nif
+fun:process_main
+fun:sched_thread_func
+}
+{
+Crypto internal.. loading pecularities revisited
+Memcheck:Leak
+fun:malloc
+fun:CRYPTO_malloc
+fun:lh_new
+...
+fun:ecdh_check
+fun:ECDH_compute_key
+fun:ecdh_compute_key_nif
+fun:process_main
+fun:sched_thread_func
+fun:thr_wrapper
+fun:start_thread
+}
{
Prebuilt constant terms in os_info_init (PossiblyLost)
Memcheck:Leak
diff --git a/erts/epmd/Makefile b/erts/epmd/Makefile
index d3308ddedc..e4b201bd88 100644
--- a/erts/epmd/Makefile
+++ b/erts/epmd/Makefile
@@ -31,3 +31,5 @@ SPECIAL_TARGETS =
# Default Subdir Targets
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/erts/epmd/epmd.mk b/erts/epmd/epmd.mk
index cdac1ca037..a55cfa3b86 100644
--- a/erts/epmd/epmd.mk
+++ b/erts/epmd/epmd.mk
@@ -67,5 +67,5 @@ EPMD_NODE_TYPE=110
# Distribution format 5 contains the new md5 based handshake.
EPMD_DIST_LOW=5
-EPMD_DIST_HIGH=5
+EPMD_DIST_HIGH=6
diff --git a/erts/epmd/src/Makefile.in b/erts/epmd/src/Makefile.in
index da4370d5f9..f9f65cec5e 100644
--- a/erts/epmd/src/Makefile.in
+++ b/erts/epmd/src/Makefile.in
@@ -53,12 +53,8 @@ ERTS_INCL = -I$(ERL_TOP)/erts/include \
ifeq ($(TARGET),win32)
ERTS_INTERNAL_LIBS=-L../../lib/internal/$(TARGET) -lerts_internal_r$(ERTS_LIB_TYPEMARKER) @ERTS_INTERNAL_X_LIBS@
else
-ifeq ($(findstring vxworks,$(TARGET)),vxworks)
-ERTS_INTERNAL_LIBS=-L../../lib/internal/$(TARGET) -lerts_internal$(ERTS_LIB_TYPEMARKER) @ERTS_INTERNAL_X_LIBS@
-else
ERTS_INTERNAL_LIBS=-L../../lib/internal/$(TARGET) -lerts_internal$(ERTS_LIB_TYPEMARKER) @ERTS_INTERNAL_X_LIBS@ -lm
endif
-endif
ERTS_LIB = $(ERL_TOP)/erts/lib_src/obj/$(TARGET)/$(TYPE)/MADE
diff --git a/erts/epmd/src/epmd.c b/erts/epmd/src/epmd.c
index 07aedeb834..c76f32d3cd 100644
--- a/erts/epmd/src/epmd.c
+++ b/erts/epmd/src/epmd.c
@@ -81,48 +81,6 @@ static char *mystrdup(char *s)
return r;
}
-#ifdef VXWORKS
-int start_epmd(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9)
-char *a0, *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8, *a9;
-{
- char* argarr[] = {a0,a1,a2,a3,a4,a5,a6,a7,a8,a9};
- int i;
- char** argv = malloc(sizeof(char *)*10);
- int argvsiz = 10;
- int argc = 1;
- char* tmp = malloc(100);
- int tmpsiz = 100;
- char* pplast;
- char* token;
-
- argv[0] = mystrdup("epmd");
- argv[1] = NULL;
-
- for(i=0;i<10;++i)
- {
- if(argarr[i] == NULL || *argarr[i] == '\0')
- continue;
- if(strlen(argarr[i]) >= tmpsiz)
- tmp = realloc(tmp, tmpsiz = (strlen(argarr[i])+1));
- strcpy(tmp,argarr[i]);
- for(token = strtok_r(tmp," ",&pplast);
- token != NULL;
- token = strtok_r(NULL," ",&pplast))
- {
- if(argc >= argvsiz - 1)
- argv = realloc(argv,sizeof(char *) * (argvsiz += 10));
- argv[argc++] = mystrdup(token);
- argv[argc] = NULL;
- }
- }
- free(tmp);
- return taskSpawn("epmd",100,VX_FP_TASK,20000,epmd_main,
- argc,(int) argv,1,
- 0,0,0,0,0,0,0);
-}
-#endif /* WxWorks */
-
-
int epmd(int argc, char **argv)
{
return epmd_main(argc,argv,0);
@@ -397,13 +355,6 @@ static void run_daemon(EpmdVars *g)
}
#endif
-#if defined(VXWORKS)
-static void run_daemon(EpmdVars *g)
-{
- run(g);
-}
-#endif
-
/***************************************************************************
* Misc support routines
@@ -474,7 +425,7 @@ static void usage(EpmdVars *g)
* Error reporting - dbg_printf() & dbg_tty_printf & dbg_perror()
*
* The first form will print out on tty or syslog depending on
- * if it runs as deamon or not. The second form will never print
+ * if it runs as daemon or not. The second form will never print
* out on syslog.
*
* The arguments are
diff --git a/erts/epmd/src/epmd.h b/erts/epmd/src/epmd.h
index 618d6748fb..3f5587a72e 100644
--- a/erts/epmd/src/epmd.h
+++ b/erts/epmd/src/epmd.h
@@ -26,6 +26,7 @@
#define EPMD_ALIVE2_REQ 'x'
#define EPMD_PORT2_REQ 'z'
#define EPMD_ALIVE2_RESP 'y'
+#define EPMD_ALIVE2_X_RESP 'v'
#define EPMD_PORT2_RESP 'w'
#define EPMD_NAMES_REQ 'n'
diff --git a/erts/epmd/src/epmd_int.h b/erts/epmd/src/epmd_int.h
index 124a09ae6b..f5d17aff55 100644
--- a/erts/epmd/src/epmd_int.h
+++ b/erts/epmd/src/epmd_int.h
@@ -30,13 +30,6 @@
#define NO_DAEMON
#endif
-#ifdef VXWORKS
-#define NO_SYSCONF
-#define NO_DAEMON
-#define NO_FCNTL
-#define DONT_USE_MAIN
-#endif
-
/* ************************************************************************ */
/* Standard includes */
@@ -56,16 +49,6 @@
#include <sys/types.h>
#include <fcntl.h>
-#ifdef VXWORKS
-# include <sys/times.h>
-# include <time.h>
-# include <selectLib.h>
-# include <sysLib.h>
-# include <sockLib.h>
-# include <ioLib.h>
-# include <taskLib.h>
-# include <rpc/rpc.h>
-#else /* ! VXWORKS */
#ifndef __WIN32__
# ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
@@ -78,7 +61,6 @@
# endif
# endif
#endif
-#endif /* ! VXWORKS */
#if !defined(__WIN32__)
# include <netinet/in.h>
@@ -130,10 +112,6 @@
# define ioctl(s,r,o) ioctlsocket((s),(r),(o))
#endif /* WIN32 */
-#ifdef VXWORKS
-#define sleep(n) taskDelay((n) * sysClkRateGet())
-#endif /* VXWORKS */
-
#ifdef USE_BCOPY
# define memcpy(a, b, c) bcopy((b), (a), (c))
# define memcmp(a, b, c) bcmp((a), (b), (c))
@@ -277,6 +255,12 @@ static const struct in6_addr in6addr_loopback =
#define put_int16(i, s) {((unsigned char*)(s))[0] = ((i) >> 8) & 0xff; \
((unsigned char*)(s))[1] = (i) & 0xff;}
+#define put_int32(i, s) do {((char*)(s))[0] = (char)((i) >> 24) & 0xff; \
+ ((char*)(s))[1] = (char)((i) >> 16) & 0xff; \
+ ((char*)(s))[2] = (char)((i) >> 8) & 0xff; \
+ ((char*)(s))[3] = (char)(i) & 0xff;} \
+ while (0)
+
#if defined(__GNUC__)
# define EPMD_INLINE __inline__
#elif defined(__WIN32__)
@@ -287,7 +271,7 @@ static const struct in6_addr in6addr_loopback =
/* ************************************************************************ */
-/* Stuctures used by server */
+/* Structures used by server */
typedef struct {
int fd; /* File descriptor */
@@ -307,10 +291,10 @@ struct enode {
int fd; /* The socket in use */
unsigned short port; /* Port number of Erlang node */
char symname[MAXSYMLEN+1]; /* Name of the Erlang node */
- short creation; /* Started as a random number 1..3 */
+ unsigned int cr_counter; /* Used to generate 'creation' numbers */
char nodetype; /* 77 = normal erlang node 72 = hidden (c-node */
char protocol; /* 0 = tcp/ipv4 */
- unsigned short highvsn; /* 0 = OTP-R3 erts-4.6.x, 1 = OTP-R4 erts-4.7.x*/
+ unsigned short highvsn; /* 5: creation=1..3, 6: creation=1..(2^32-1)*/
unsigned short lowvsn;
int extralen;
char extra[MAXSYMLEN+1];
diff --git a/erts/epmd/src/epmd_srv.c b/erts/epmd/src/epmd_srv.c
index 2b40e3675c..ad264585c6 100644
--- a/erts/epmd/src/epmd_srv.c
+++ b/erts/epmd/src/epmd_srv.c
@@ -38,7 +38,7 @@
* register their names with this server.
*
* To be accessible to all Erlang nodes this server listens to a well
- * known port EPMD_PORT_NO (curently port 4369) where requests
+ * known port EPMD_PORT_NO (currently port 4369) where requests
* for connections can be sent.
*
* To keep track of when registered Erlang nodes are terminated this
@@ -59,7 +59,7 @@
/* We use separate data structures for node names and connections
so that a request will not use a slot with a name that we
- want to resuse later incrementing the "creation" */
+ want to reuse later incrementing the "creation" */
/* forward declarations */
@@ -198,10 +198,46 @@ static EPMD_INLINE void select_fd_set(EpmdVars* g, int fd)
}
}
+#ifdef HAVE_SOCKLEN_T
+static const char *epmd_ntop(struct sockaddr_storage *sa, char *buff, socklen_t len) {
+#else
+static const char *epmd_ntop(struct sockaddr_storage *sa, char *buff, size_t len) {
+#endif
+ /* Save errno so that it is not changed by inet_ntop */
+ int myerrno = errno;
+ const char *res;
+#if !defined(EPMD6)
+ if (sa->ss_family == AF_INET6) {
+ struct sockaddr_in6 *addr = (struct sockaddr_in6 *)sa;
+ res = inet_ntop(
+ addr->sin6_family,
+ (void*)&addr->sin6_addr,
+ buff, len);
+ } else {
+ struct sockaddr_in *addr = (struct sockaddr_in *)sa;
+ res = inet_ntop(
+ addr->sin_family, &addr->sin_addr.s_addr,
+ buff, len);
+ }
+#else
+ struct sockaddr_in *addr = (struct sockaddr_in *)sa;
+ res = inet_ntoa(addr->sin_addr);
+ erts_snprintf(buff,len,"%s", res);
+ res = buff;
+#endif
+ errno = myerrno;
+ return res;
+}
+
void run(EpmdVars *g)
{
struct EPMD_SOCKADDR_IN iserv_addr[MAX_LISTEN_SOCKETS];
int listensock[MAX_LISTEN_SOCKETS];
+#if defined(EPMD6)
+ char socknamebuf[INET6_ADDRSTRLEN];
+#else
+ char socknamebuf[INET_ADDRSTRLEN];
+#endif
int num_sockets = 0;
int i;
int opt;
@@ -410,32 +446,37 @@ void run(EpmdVars *g)
in accept() waiting for the next request. */
#if (defined(__WIN32__) || defined(NO_FCNTL))
opt = 1;
- /* Gives warning in VxWorks */
if (ioctl(listensock[i], FIONBIO, &opt) != 0)
#else
opt = fcntl(listensock[i], F_GETFL, 0);
if (fcntl(listensock[i], F_SETFL, opt | O_NONBLOCK) == -1)
-#endif /* __WIN32__ || VXWORKS */
- dbg_perror(g,"failed to set non-blocking mode of listening socket %d",
- listensock[i]);
+#endif /* __WIN32__ */
+ dbg_perror(g,"failed to set non-blocking mode of listening socket %d on ipaddr %s",
+ listensock[i], epmd_ntop(&iserv_addr[num_sockets],
+ socknamebuf, sizeof(socknamebuf)));
- if (bind(listensock[i], (struct sockaddr*) &iserv_addr[i], salen) < 0)
+ if (bind(listensock[i], sa, salen) < 0)
{
if (errno == EADDRINUSE)
{
- dbg_tty_printf(g,1,"there is already a epmd running at port %d",
- g->port);
+ dbg_tty_printf(g,1,"there is already a epmd running at port %d on ipaddr %s",
+ g->port, epmd_ntop(&iserv_addr[num_sockets],
+ socknamebuf, sizeof(socknamebuf)));
epmd_cleanup_exit(g,0);
}
else
{
- dbg_perror(g,"failed to bind socket");
- epmd_cleanup_exit(g,1);
+ dbg_perror(g,"failed to bind on ipaddr %s",
+ epmd_ntop(&iserv_addr[num_sockets],
+ socknamebuf, sizeof(socknamebuf)));
+ epmd_cleanup_exit(g,1);
}
}
if(listen(listensock[i], SOMAXCONN) < 0) {
- dbg_perror(g,"failed to listen on socket");
+ dbg_perror(g,"failed to listen on ipaddr %s",
+ epmd_ntop(&iserv_addr[num_sockets],
+ socknamebuf, sizeof(socknamebuf)));
epmd_cleanup_exit(g,1);
}
select_fd_set(g, listensock[i]);
@@ -525,7 +566,7 @@ void run(EpmdVars *g)
/*
* This routine read as much of the packet as possible and
- * if completed calls "do_request()" to fullfill the request.
+ * if completed calls "do_request()" to fulfill the request.
*
*/
@@ -665,6 +706,21 @@ static int do_accept(EpmdVars *g,int listensock)
return conn_open(g,msgsock);
}
+static void bump_creation(Node* node)
+{
+ if (++node->cr_counter < 4)
+ node->cr_counter = 4;
+}
+static unsigned int get_creation(Node* node)
+{
+ if (node->highvsn >= 6) {
+ return node->cr_counter; /* 4..(2^32-1)*/
+ }
+ else {
+ return node->cr_counter % 3 + 1; /* 1..3 */
+ }
+}
+
/* buf is actually one byte larger than bsize,
giving place for null termination */
static void do_request(g, fd, s, buf, bsize)
@@ -706,8 +762,10 @@ static void do_request(g, fd, s, buf, bsize)
unsigned char protocol;
unsigned short highvsn;
unsigned short lowvsn;
+ unsigned int creation;
int namelen;
int extralen;
+ int replylen;
char *name;
char *extra;
eport = get_int16(&buf[1]);
@@ -737,17 +795,22 @@ static void do_request(g, fd, s, buf, bsize)
extra = &buf[11+namelen+2];
extra[extralen]='\000';
- wbuf[0] = EPMD_ALIVE2_RESP;
- if ((node = node_reg2(g, namelen, name, fd, eport, nodetype, protocol,
- highvsn, lowvsn, extralen, extra)) == NULL) {
- wbuf[1] = 1; /* error */
- put_int16(99, wbuf+2);
- } else {
- wbuf[1] = 0; /* ok */
- put_int16(node->creation, wbuf+2);
- }
+ node = node_reg2(g, namelen, name, fd, eport, nodetype, protocol,
+ highvsn, lowvsn, extralen, extra);
+ creation = node ? get_creation(node) : 99;
+ wbuf[1] = node ? 0 : 1; /* ok | error */
+ if (highvsn >= 6) {
+ wbuf[0] = EPMD_ALIVE2_X_RESP;
+ put_int32(creation, wbuf+2);
+ replylen = 6;
+ }
+ else {
+ wbuf[0] = EPMD_ALIVE2_RESP;
+ put_int16(creation, wbuf+2);
+ replylen = 4;
+ }
- if (!reply(g, fd, wbuf, 4))
+ if (!reply(g, fd, wbuf, replylen))
{
node_unreg(g, name);
dbg_tty_printf(g,1,"** failed to send ALIVE2_RESP for \"%s\"",
@@ -1033,18 +1096,9 @@ static int conn_open(EpmdVars *g,int fd)
int i;
Connection *s;
-#ifdef VXWORKS
- /*
- * Since file descriptors are global on VxWorks, we might get an fd that
- * does not fit in the FD_SET.
- *
- * Note: This test would be harmless on Unix, but would fail on Windows
- * because socket are numbered differently and FD_SETs are implemented
- * differently.
- */
+#if !defined(__WIN32__)
if (fd >= FD_SETSIZE) {
- dbg_tty_printf(g,0,"file descriptor %d: too high for FD_SETSIZE=%d",
- fd,FD_SETSIZE);
+ dbg_tty_printf(g,0,"fd does not fit in fd_set fd=%d, FD_SETSIZE=%d",fd, FD_SETSIZE);
close(fd);
return EPMD_FALSE;
}
@@ -1200,8 +1254,8 @@ static int node_unreg(EpmdVars *g,char *name)
for (; node; prev = &node->next, node = node->next)
if (is_same_str(node->symname, name))
{
- dbg_tty_printf(g,1,"unregistering '%s:%d', port %d",
- node->symname, node->creation, node->port);
+ dbg_tty_printf(g,1,"unregistering '%s:%u', port %d",
+ node->symname, get_creation(node), node->port);
*prev = node->next; /* Link out from "reg" list */
@@ -1235,8 +1289,8 @@ static int node_unreg_sock(EpmdVars *g,int fd)
for (; node; prev = &node->next, node = node->next)
if (node->fd == fd)
{
- dbg_tty_printf(g,1,"unregistering '%s:%d', port %d",
- node->symname, node->creation, node->port);
+ dbg_tty_printf(g,1,"unregistering '%s:%u', port %d",
+ node->symname, get_creation(node), node->port);
*prev = node->next; /* Link out from "reg" list */
@@ -1264,19 +1318,8 @@ static int node_unreg_sock(EpmdVars *g,int fd)
}
/*
- * Finding a node slot and a (name,creation) name is a bit tricky.
- * We try in order
- *
- * 1. If the name was used before and we can reuse that slot but use
- * a new "creation" digit in the range 1..3.
- *
- * 2. We try to find a new unused slot.
- *
- * 3. We try to use an used slot this isn't used any longer.
- * FIXME: The criteria for *what* slot to steal should be improved.
- * Perhaps use the oldest or something.
+ * Register a new node
*/
-
static Node *node_reg2(EpmdVars *g,
int namelen,
char* name,
@@ -1346,7 +1389,7 @@ static Node *node_reg2(EpmdVars *g,
}
/* Try to find the name in the used queue so that we
- can change "creation" number 1..3 */
+ can change "creation" number */
prev = NULL;
@@ -1375,9 +1418,8 @@ static Node *node_reg2(EpmdVars *g,
g->nodes.unreg_count--;
- /* When reusing we change the "creation" number 1..3 */
-
- node->creation = node->creation % 3 + 1;
+ /* When reusing we change the "creation" number */
+ bump_creation(node);
break;
}
@@ -1404,7 +1446,8 @@ static Node *node_reg2(EpmdVars *g,
exit(1);
}
- node->creation = (current_time(g) % 3) + 1; /* "random" 1-3 */
+ node->cr_counter = current_time(g); /* "random" */
+ bump_creation(node);
}
}
@@ -1423,11 +1466,11 @@ static Node *node_reg2(EpmdVars *g,
select_fd_set(g, fd);
if (highvsn == 0) {
- dbg_tty_printf(g,1,"registering '%s:%d', port %d",
- node->symname, node->creation, node->port);
+ dbg_tty_printf(g,1,"registering '%s:%u', port %d",
+ node->symname, get_creation(node), node->port);
} else {
- dbg_tty_printf(g,1,"registering '%s:%d', port %d",
- node->symname, node->creation, node->port);
+ dbg_tty_printf(g,1,"registering '%s:%u', port %d",
+ node->symname, get_creation(node), node->port);
dbg_tty_printf(g,1,"type %d proto %d highvsn %d lowvsn %d",
nodetype, protocol, highvsn, lowvsn);
}
@@ -1561,8 +1604,8 @@ static void print_names(EpmdVars *g)
for (node = g->nodes.reg; node; node = node->next)
{
- fprintf(stderr,"***** active name \"%s#%d\" at port %d, fd = %d\r\n",
- node->symname, node->creation, node->port, node->fd);
+ fprintf(stderr,"***** active name \"%s#%u\" at port %d, fd = %d\r\n",
+ node->symname, get_creation(node), node->port, node->fd);
count ++;
}
@@ -1572,8 +1615,8 @@ static void print_names(EpmdVars *g)
for (node = g->nodes.unreg; node; node = node->next)
{
- fprintf(stderr,"***** old/unused name \"%s#%d\"\r\n",
- node->symname, node->creation);
+ fprintf(stderr,"***** old/unused name \"%s#%u\"\r\n",
+ node->symname, get_creation(node));
count ++;
}
diff --git a/erts/epmd/test/Makefile b/erts/epmd/test/Makefile
index ad1315440f..d55ccabfb2 100644
--- a/erts/epmd/test/Makefile
+++ b/erts/epmd/test/Makefile
@@ -73,7 +73,7 @@ release_spec:
release_tests_spec: opt
$(INSTALL_DIR) "$(RELEPMDDIR)"
- $(INSTALL_DATA) epmd.spec epmd.spec.vxworks $(ERL_FILES) \
+ $(INSTALL_DATA) epmd.spec $(ERL_FILES) \
$(EMAKEFILE) "$(RELEPMDDIR)"
chmod -R u+w "$(RELEPMDDIR)"
diff --git a/erts/etc/common/Makefile.in b/erts/etc/common/Makefile.in
index 05d7b5679f..7a2b821c46 100644
--- a/erts/etc/common/Makefile.in
+++ b/erts/etc/common/Makefile.in
@@ -23,7 +23,6 @@ include $(ERL_TOP)/make/target.mk
ERTS_LIB_TYPEMARKER=.$(TYPE)
-USING_MINGW=@MIXED_CYGWIN_MINGW@
USING_VC=@MIXED_VC@
ifeq ($(TYPE),debug)
@@ -155,7 +154,14 @@ MC_OUTPUTS=$(OBJDIR)/erlsrv_logmess.h $(OBJDIR)/erlsrv_logmess.res
MT_FLAG="-MD"
endif
INET_GETHOST = $(BINDIR)/inet_gethost.exe
-INSTALL_EMBEDDED_PROGS += $(BINDIR)/typer.exe $(BINDIR)/dialyzer.exe $(BINDIR)/erlc.exe $(BINDIR)/start_erl.exe $(BINDIR)/escript.exe $(BINDIR)/ct_run.exe
+INSTALL_EMBEDDED_PROGS += \
+ $(BINDIR)/typer.exe \
+ $(BINDIR)/dialyzer.exe \
+ $(BINDIR)/erlc.exe \
+ $(BINDIR)/start_erl.exe \
+ $(BINDIR)/escript.exe \
+ $(BINDIR)/ct_run.exe \
+ $(BINDIR)/erl_call.exe
INSTALL_SRC = $(WINETC)/start_erl.c $(WINETC)/Nmakefile.start_erl
ERLEXECDIR=.
INSTALL_LIBS =
@@ -166,9 +172,11 @@ INSTALL_TOP = Install.ini
INSTALL_TOP_BIN = $(BINDIR)/Install.exe
INSTALL_PROGS = \
$(INET_GETHOST) \
- $(BINDIR)/heart.exe $(BINDIR)/erlsrv.exe \
- $(BINDIR)/erl.exe $(BINDIR)/erl_log.exe \
- $(BINDIR)/werl.exe \
+ $(BINDIR)/heart.exe \
+ $(BINDIR)/erlsrv.exe \
+ $(BINDIR)/erl.exe \
+ $(BINDIR)/erl_log.exe\
+ $(BINDIR)/werl.exe \
$(BINDIR)/$(ERLEXEC) \
$(INSTALL_EMBEDDED_PROGS)
@@ -188,9 +196,16 @@ ENTRY_OBJ=
ERLSRV_OBJECTS=
MC_OUTPUTS=
INET_GETHOST = $(BINDIR)/inet_gethost@EXEEXT@
-INSTALL_EMBEDDED_PROGS += $(BINDIR)/typer@EXEEXT@ $(BINDIR)/dialyzer@EXEEXT@ \
- $(BINDIR)/erlc@EXEEXT@ $(BINDIR)/escript@EXEEXT@ $(BINDIR)/ct_run@EXEEXT@ \
- $(BINDIR)/run_erl $(BINDIR)/to_erl $(BINDIR)/dyn_erl
+INSTALL_EMBEDDED_PROGS += \
+ $(BINDIR)/typer@EXEEXT@ \
+ $(BINDIR)/dialyzer@EXEEXT@ \
+ $(BINDIR)/erlc@EXEEXT@ \
+ $(BINDIR)/escript@EXEEXT@ \
+ $(BINDIR)/ct_run@EXEEXT@ \
+ $(BINDIR)/run_erl@EXEEXT@ \
+ $(BINDIR)/to_erl@EXEEXT@ \
+ $(BINDIR)/dyn_erl@EXEEXT@ \
+ $(BINDIR)/erl_call@EXEEXT@
INSTALL_EMBEDDED_DATA = $(UXETC)/start.src $(UXETC)/start_erl.src
INSTALL_TOP = Install
INSTALL_TOP_BIN =
@@ -425,6 +440,10 @@ $(OBJDIR)/dyn_erl.o: $(UXETC)/dyn_erl.c $(RC_GENERATED)
$(OBJDIR)/safe_string.o: $(ETC)/safe_string.c $(RC_GENERATED)
$(V_CC) $(CFLAGS) -o $@ -c $(ETC)/safe_string.c
+# erl_call
+$(BINDIR)/erl_call@EXEEXT@: $(ERL_TOP)/lib/erl_interface/bin/$(TARGET)/erl_call@EXEEXT@
+ $(ld_verbose)cp $< $@
+
ifneq ($(TARGET),win32)
$(BINDIR)/$(ERLEXEC): $(OBJDIR)/$(ERLEXEC).o $(ERTS_LIB)
$(ld_verbose)$(PURIFY) $(LD) $(LDFLAGS) -o $@ $(OBJDIR)/$(ERLEXEC).o $(ERTS_INTERNAL_LIBS)
@@ -491,9 +510,14 @@ ifneq ($(INSTALL_OBJS),)
endif
$(INSTALL_DIR) "$(RELEASE_PATH)/erts-$(VSN)/bin"
ifneq ($(TARGET), win32)
-ifneq ($(findstring vxworks,$(TARGET)), vxworks)
$(INSTALL_SCRIPT) erl.src "$(RELEASE_PATH)/erts-$(VSN)/bin"
-endif
+# On Android, the shell is found in /system/bin/sh instead of /bin/sh
+ case "$(TARGET)" in \
+ *-android*) \
+ sed -i'' -e "s;/bin/sh;/system/bin/sh;" \
+ "$(RELEASE_PATH)/erts-$(VSN)/bin/erl.src" \
+ ;; \
+ esac
endif
ifneq ($(INSTALL_PROGS),)
$(INSTALL_PROGRAM) $(INSTALL_PROGS) "$(RELEASE_PATH)/erts-$(VSN)/bin"
@@ -514,6 +538,15 @@ ifneq ($(INSTALL_SRC),)
endif
ifneq ($(INSTALL_EMBEDDED_DATA),)
$(INSTALL_SCRIPT) $(INSTALL_EMBEDDED_DATA) "$(RELEASE_PATH)/erts-$(VSN)/bin"
+# On Android, the shell is found in /system/bin/sh instead of /bin/sh
+ case "$(TARGET)" in \
+ *-android*) \
+ sed -i'' -e "s;/bin/sh;/system/bin/sh;" \
+ "$(RELEASE_PATH)/erts-$(VSN)/bin/start_erl.src"; \
+ sed -i'' -e "s;/bin/sh;/system/bin/sh;" \
+ "$(RELEASE_PATH)/erts-$(VSN)/bin/start.src"; \
+ ;; \
+ esac
endif
ifneq ($(INSTALL_LIBS),)
$(INSTALL_DATA) $(INSTALL_LIBS) "$(RELEASE_PATH)/erts-$(VSN)/bin"
diff --git a/erts/etc/common/erlc.c b/erts/etc/common/erlc.c
index b76f1bb3e0..2d2b463675 100644
--- a/erts/etc/common/erlc.c
+++ b/erts/etc/common/erlc.c
@@ -205,6 +205,7 @@ int wmain(int argc, wchar_t **wcargv)
int main(int argc, char** argv)
{
#endif
+ int single_scheduler;
int eargv_size;
int eargc_base; /* How many arguments in the base of eargv. */
char* emulator;
@@ -299,6 +300,8 @@ int main(int argc, char** argv)
* Parse all command line switches.
*/
+ single_scheduler = 1;
+
while (argc > 1) {
/*
@@ -308,6 +311,11 @@ int main(int argc, char** argv)
source_file = "<no source>";
switch (argv[1][0]) {
case '+':
+ if (strcmp(argv[1], "+native") == 0) {
+ /* HiPE makes good use of multiple schedulers, so we'll let it
+ * use the default number. */
+ single_scheduler = 0;
+ }
PUSH(argv[1]);
break;
case '-':
@@ -370,6 +378,16 @@ int main(int argc, char** argv)
argc--, argv++;
}
+ /* The compile server benefits from multiple schedulers. */
+ single_scheduler = single_scheduler && !use_server;
+
+ if (single_scheduler) {
+ /* Limit ourselves to a single scheduler to save memory and avoid
+ * starving the system of threads when used in parallel builds (e.g.
+ * make -j64 on a 64-core system). */
+ UNSHIFT("+S1");
+ }
+
/*
* Move up the commands for invoking the emulator and adjust eargv
* accordingly.
diff --git a/erts/etc/common/erlexec.c b/erts/etc/common/erlexec.c
index 681a4c1299..9dba684cbb 100644
--- a/erts/etc/common/erlexec.c
+++ b/erts/etc/common/erlexec.c
@@ -83,6 +83,7 @@ static char *plusM_au_alloc_switches[] = {
"acul",
"acnl",
"acfml",
+ "cp",
"e",
"t",
"lmbcs",
@@ -199,7 +200,7 @@ void error(char* format, ...);
static void usage_notsup(const char *switchname, const char *alt);
static char **build_args_from_env(char *env_var);
-static char **build_args_from_string(char *env_var);
+static char **build_args_from_string(char *env_var, int allow_comments);
static void initial_argv_massage(int *argc, char ***argv);
static void get_parameters(int argc, char** argv);
static void add_arg(char *new_arg);
@@ -219,7 +220,7 @@ static void get_start_erl_data(char *);
static char* get_value(HKEY key, char* value_name, BOOL mustExit);
static char* possibly_quote(char* arg);
-/*
+/*
* Functions from win_erlexec.c
*/
int start_win_emulator(char* emu, char *startprog,char** argv, int start_detached);
@@ -249,7 +250,7 @@ static int start_smp_emu = 1; /* Start the smp emulator. */
static const char* emu_type = 0; /* Type of emulator (lcnt, valgrind, etc) */
#ifdef __WIN32__
-static char *start_emulator_program = NULL; /* For detachec mode -
+static char *start_emulator_program = NULL; /* For detached mode -
erl.exe/werl.exe */
static char* key_val_name = ERLANG_VERSION; /* Used by the registry
* access functions.
@@ -267,7 +268,7 @@ static WCHAR *latin1_to_utf16(char *str);
#endif
/*
- * Needed parameters to be fetched from the environment (Unix)
+ * Parameters to be fetched from the environment (Unix)
* or the ini file (Win32).
*/
@@ -275,7 +276,7 @@ static char* bindir; /* Location of executables. */
static char* rootdir; /* Root location of Erlang installation. */
static char* emu; /* Emulator to run. */
static char* progname; /* Name of this program. */
-static char* home; /* Path of user's home directory. */
+static char* home; /* Path of user's home directory, if any. */
static void
set_env(char *key, char *value)
@@ -415,7 +416,7 @@ __declspec(dllexport) int win_erlexec(int argc, char **argv, HANDLE module, int
int main(int argc, char **argv)
#endif
{
- int haltAfterwards = 0; /* If true, put 's erlang halt' at the end
+ int haltAfterwards = 0; /* If true, put '-s erlang halt' at the end
* of the arguments. */
int isdistributed = 0;
int no_epmd = 0;
@@ -431,7 +432,7 @@ int main(int argc, char **argv)
#ifdef __WIN32__
this_module_handle = module;
run_werl = windowed;
- /* if we started this erl just to get a detached emulator,
+ /* if we started this erl just to get a detached emulator,
* the arguments are already prepared for beam, so we skip
* directly to start_emulator */
s = get_env("ERL_CONSOLE_MODE");
@@ -449,7 +450,7 @@ int main(int argc, char **argv)
emu = argv[0];
start_emulator_program = strsave(argv[0]);
goto skip_arg_massage;
- }
+ }
free_env_val(s);
#else
int reset_cerl_detached = 0;
@@ -480,7 +481,7 @@ int main(int argc, char **argv)
#endif
get_parameters(argc, argv);
-
+
/*
* Construct the path of the executable.
*/
@@ -596,11 +597,16 @@ int main(int argc, char **argv)
free_env_val(s);
set_env("PATH", tmpStr);
-
+
i = 1;
get_home();
- add_args("-home", home, NULL);
+ /* Add the home parameter when available. This is optional to support
+ systems that don't have the notion of a home directory and setups
+ that don't have the HOME environment variable set (ERL-476). */
+ if (home != NULL) {
+ add_args("-home", home, NULL);
+ }
add_epmd_port();
@@ -634,7 +640,7 @@ int main(int argc, char **argv)
case 'c':
if (strcmp(argv[i], "-compile") == 0) {
/*
- * Note that the shell script erl.exec does an recursive call
+ * Note that the shell script erl.exec does a recursive call
* on itself here. We'll avoid doing that.
*/
add_args("-noshell", "-noinput", "-s", "c", "lc_batch",
@@ -648,11 +654,13 @@ int main(int argc, char **argv)
error("Conflicting -start_erl and -config options");
if (i+1 >= argc)
usage("-config");
- config_script_cnt++;
- config_scripts = erealloc(config_scripts,
- config_script_cnt * sizeof(char*));
- config_scripts[config_script_cnt-1] = strsave(argv[i+1]);
- i++;
+ do {
+ config_script_cnt++;
+ config_scripts = erealloc(config_scripts,
+ config_script_cnt * sizeof(char*));
+ config_scripts[config_script_cnt-1] = strsave(argv[i+1]);
+ i++;
+ } while ((i+1) < argc && argv[i+1][0] != '-' && argv[i+1][0] != '+');
}
#endif
else {
@@ -688,7 +696,7 @@ int main(int argc, char **argv)
usage("-env");
set_env(argv[i+1], argv[i+2]);
i += 2;
- } else if (strcmp(argv[i], "-epmd") == 0) {
+ } else if (strcmp(argv[i], "-epmd") == 0) {
if (i+1 >= argc)
usage("-epmd");
epmd_prog = argv[i+1];
@@ -706,7 +714,7 @@ int main(int argc, char **argv)
case 'm':
/*
- * Note that the shell script erl.exec does an recursive call
+ * Note that the shell script erl.exec does a recursive call
* on itself here. We'll avoid doing that.
*/
if (strcmp(argv[i], "-make") == 0) {
@@ -732,7 +740,7 @@ int main(int argc, char **argv)
if (strcmp(argv[i], "-name") == 0) { /* -name NAME */
if (i+1 >= argc)
usage("-name");
-
+
/*
* Note: Cannot use add_args() here, due to non-defined
* evaluation order.
@@ -769,7 +777,7 @@ int main(int argc, char **argv)
add_arg(argv[i]);
add_arg(argv[i+1]);
i++;
- }
+ }
else if (strcmp(argv[i], "-start_erl") == 0) {
if (i+1 < argc && argv[i+1][0] != '-') {
get_start_erl_data(argv[i+1]);
@@ -891,7 +899,7 @@ int main(int argc, char **argv)
if (argv[i][2] != '\0') {
if ((argv[i][2] != 'i') &&
(argv[i][2] != 'c') &&
- (argv[i][2] != 'd')) {
+ (argv[i][2] != 'd')) {
usage(argv[i]);
} else {
add_Eargs(argv[i]);
@@ -1023,7 +1031,7 @@ int main(int argc, char **argv)
add_Eargs(argv[i]);
}
break;
-
+
default:
add_arg(argv[i]);
} /* switch(argv[i][0] */
@@ -1041,7 +1049,7 @@ int main(int argc, char **argv)
if (haltAfterwards) {
add_args("-s", "erlang", "halt", NULL);
}
-
+
if (isdistributed && !no_epmd)
start_epmd(epmd_prog);
@@ -1050,8 +1058,8 @@ int main(int argc, char **argv)
/* Start the emulator within an xterm.
* Move up all arguments and insert
* "xterm -e " first.
- * The path must be searched for this
- * to work, i.e execvp() must be used.
+ * The path must be searched for this
+ * to work, i.e execvp() must be used.
*/
ensure_EargsSz(EargsCnt+2);
for (i = EargsCnt; i > 0; i--)
@@ -1059,9 +1067,9 @@ int main(int argc, char **argv)
EargsCnt += 2; /* Two args to insert */
Eargsp[0] = emu = "xterm";
Eargsp[1] = "-e";
- }
+ }
#endif
-
+
add_Eargs("--");
add_Eargs("-root");
add_Eargs(rootdir);
@@ -1072,7 +1080,7 @@ int main(int argc, char **argv)
for (i = 0; i < argsCnt; i++)
Eargsp[EargsCnt++] = argsp[i];
Eargsp[EargsCnt] = NULL;
-
+
if (print_qouted_cmd_exit) {
printf("\"%s\" ", emu);
for (i = 1; i < EargsCnt; i++)
@@ -1097,7 +1105,7 @@ int main(int argc, char **argv)
#ifdef __WIN32__
if (EargsSz != EargsCnt + 1)
- Eargsp = (char **) erealloc((void *) Eargsp, (EargsCnt + 1) *
+ Eargsp = (char **) erealloc((void *) Eargsp, (EargsCnt + 1) *
sizeof(char *));
efree((void *) argsp);
@@ -1163,7 +1171,7 @@ int main(int argc, char **argv)
#ifdef DEBUG
execvp(emu, Eargsp); /* "xterm ..." needs to search the path */
#endif
- }
+ }
#ifdef DEBUG
else
#endif
@@ -1191,10 +1199,10 @@ usage_aux(void)
#endif
"[-make] [-man [manopts] MANPAGE] [-x] [-emu_args] [-start_epmd BOOLEAN] "
"[-args_file FILENAME] [+A THREADS] [+a SIZE] [+B[c|d|i]] [+c [BOOLEAN]] "
- "[+C MODE] [+h HEAP_SIZE_OPTION] [+K BOOLEAN] "
- "[+l] [+M<SUBSWITCH> <ARGUMENT>] [+P MAX_PROCS] [+Q MAX_PORTS] "
+ "[+C MODE] [+dcg DECENTRALIZED_COUNTER_GROUPS_LIMIT] [+h HEAP_SIZE_OPTION] "
+ "[+M<SUBSWITCH> <ARGUMENT>] [+P MAX_PROCS] [+Q MAX_PORTS] "
"[+R COMPAT_REL] "
- "[+r] [+rg READER_GROUPS_LIMIT] [+s SCHEDULER_OPTION] "
+ "[+r] [+rg READER_GROUPS_LIMIT] [+s<SUBSWITCH> SCHEDULER_OPTION] "
"[+S NO_SCHEDULERS:NO_SCHEDULERS_ONLINE] "
"[+SP PERCENTAGE_SCHEDULERS:PERCENTAGE_SCHEDULERS_ONLINE] "
"[+T LEVEL] [+V] [+v] "
@@ -1238,12 +1246,12 @@ start_epmd(char *epmd)
if (!epmd) {
epmd = epmd_cmd;
#ifdef __WIN32__
- erts_snprintf(epmd_cmd, sizeof(epmd_cmd), "%s" DIRSEP "epmd", bindir);
+ erts_snprintf(epmd_cmd, sizeof(epmd_cmd), "\"%s" DIRSEP "epmd\"", bindir);
arg1 = "-daemon";
#else
erts_snprintf(epmd_cmd, sizeof(epmd_cmd), "\"%s" DIRSEP "epmd\" -daemon", bindir);
#endif
- }
+ }
#ifdef __WIN32__
if (arg1 != NULL) {
strcat(epmd, " ");
@@ -1257,7 +1265,7 @@ start_epmd(char *epmd)
start.cb = sizeof (start);
MultiByteToWideChar(CP_UTF8, 0, epmd, -1, wcepmd, MAXPATHLEN+100);
- if (!CreateProcessW(NULL, wcepmd, NULL, NULL, FALSE,
+ if (!CreateProcessW(NULL, wcepmd, NULL, NULL, FALSE,
CREATE_DEFAULT_ERROR_MODE | DETACHED_PROCESS,
NULL, NULL, &start, &pi))
result = -1;
@@ -1287,7 +1295,7 @@ add_args(char *first_arg, ...)
{
va_list ap;
char* arg;
-
+
add_arg(first_arg);
va_start(ap, first_arg);
while ((arg = va_arg(ap, char *)) != NULL) {
@@ -1346,7 +1354,7 @@ erealloc(void *p, size_t size)
}
static void
-efree(void *p)
+efree(void *p)
{
free(p);
}
@@ -1393,7 +1401,7 @@ static void get_start_erl_data(char *file)
char* reldir;
char* otpstring;
char* tprogname;
- if (boot_script)
+ if (boot_script)
error("Conflicting -start_erl and -boot options");
if (config_scripts)
error("Conflicting -start_erl and -config options");
@@ -1450,12 +1458,12 @@ static void get_start_erl_data(char *file)
erts_snprintf(a_config_script, 512, "%s/%s/sys", reldir, otpstring);
config_scripts = &a_config_script;
config_script_cnt = 1;
-
+
got_start_erl = 1;
}
-static wchar_t *replace_filename(wchar_t *path, wchar_t *new_base)
+static wchar_t *replace_filename(wchar_t *path, wchar_t *new_base)
{
int plen = wcslen(path);
wchar_t *res = (wchar_t *) emalloc((plen+wcslen(new_base)+1)*sizeof(wchar_t));
@@ -1478,8 +1486,8 @@ static char *path_massage(wchar_t *long_path)
WideCharToMultiByte(CP_UTF8, 0, long_path, -1, p, len, NULL, NULL);
return p;
}
-
-static char *do_lookup_in_section(InitSection *inis, char *name,
+
+static char *do_lookup_in_section(InitSection *inis, char *name,
char *section, wchar_t *filename, int is_path)
{
char *p = lookup_init_entry(inis, name);
@@ -1498,8 +1506,8 @@ static void get_parameters(int argc, char** argv)
wchar_t *p;
wchar_t buffer[MAX_PATH];
wchar_t *ini_filename;
- HANDLE module = GetModuleHandle(NULL); /* This might look strange, but we want the erl.ini
- that resides in the same dir as erl.exe, not
+ HANDLE module = GetModuleHandle(NULL); /* This might look strange, but we want the erl.ini
+ that resides in the same dir as erl.exe, not
an erl.ini in our directory */
InitFile *inif;
InitSection *inis;
@@ -1548,9 +1556,9 @@ static void get_parameters(int argc, char** argv)
}
bindir = do_lookup_in_section(inis, "Bindir", INI_SECTION, ini_filename,1);
- rootdir = do_lookup_in_section(inis, "Rootdir", INI_SECTION,
+ rootdir = do_lookup_in_section(inis, "Rootdir", INI_SECTION,
ini_filename,1);
- progname = do_lookup_in_section(inis, "Progname", INI_SECTION,
+ progname = do_lookup_in_section(inis, "Progname", INI_SECTION,
ini_filename,0);
free_init_file(inif);
}
@@ -1575,7 +1583,7 @@ get_home(void)
home = utf16_to_utf8(profile);
/* CoTaskMemFree(profile); */
} else
- error("HOMEDRIVE or HOMEPATH is not set and GetWindowsDir failed");
+ error("HOMEDRIVE or HOMEPATH not set and getting USERPROFILE failed");
} else {
home = emalloc(strlen(homedrive)+strlen(homepath)+1);
strcpy(home, homedrive);
@@ -1640,8 +1648,6 @@ static void
get_home(void)
{
home = get_env("HOME");
- if (home == NULL)
- error("HOME must be set");
}
#endif
@@ -1657,12 +1663,12 @@ static void add_epmd_port(void)
static char **build_args_from_env(char *env_var)
{
char *value = get_env(env_var);
- char **res = build_args_from_string(value);
+ char **res = build_args_from_string(value, 0);
free_env_val(value);
return res;
}
-static char **build_args_from_string(char *string)
+static char **build_args_from_string(char *string, int allow_comments)
{
int argc = 0;
char **argv = NULL;
@@ -1671,7 +1677,7 @@ static char **build_args_from_string(char *string)
int s_alloced = 0;
int s_pos = 0;
char *p = string;
- enum {Start, Build, Build0, BuildSQuoted, BuildDQuoted, AcceptNext} state;
+ enum {Start, Build, Build0, BuildSQuoted, BuildDQuoted, AcceptNext, BuildComment} state;
#define ENSURE() \
if (s_pos >= s_alloced) { \
@@ -1703,12 +1709,24 @@ static char **build_args_from_string(char *string)
break;
case Build0:
switch (*p) {
+ case '\n':
+ case '\f':
+ case '\r':
+ case '\t':
+ case '\v':
case ' ':
++p;
break;
case '\0':
state = Start;
break;
+ case '#':
+ if (allow_comments) {
+ ++p;
+ state = BuildComment;
+ break;
+ }
+ /* fall-through */
default:
state = Build;
break;
@@ -1716,6 +1734,15 @@ static char **build_args_from_string(char *string)
break;
case Build:
switch (*p) {
+ case '#':
+ if (!allow_comments)
+ goto build_default;
+ /* fall-through */
+ case '\n':
+ case '\f':
+ case '\r':
+ case '\t':
+ case '\v':
case ' ':
case '\0':
ENSURE();
@@ -1736,6 +1763,7 @@ static char **build_args_from_string(char *string)
state = AcceptNext;
break;
default:
+ build_default:
ENSURE();
(*cur_s)[s_pos++] = *p++;
break;
@@ -1770,14 +1798,19 @@ static char **build_args_from_string(char *string)
}
break;
case AcceptNext:
- if (!*p) {
- state = Build;
- } else {
+ if (*p) {
ENSURE();
(*cur_s)[s_pos++] = *p++;
}
state = Build;
break;
+ case BuildComment:
+ if (*p == '\n' || *p == '\0') {
+ state = Build0;
+ } else {
+ p++;
+ }
+ break;
}
}
done:
@@ -1786,8 +1819,8 @@ done:
return NULL;
}
argv[argc++] = "--"; /* Add a -- separator in order
- for different from different environments
- to effect each other */
+ for flags from different environments
+ to not effect each other */
argv[argc++] = NULL; /* Sure to be large enough */
return argv;
#undef ENSURE
@@ -1802,30 +1835,16 @@ errno_string(void)
return str;
}
+#define FILE_BUFF_SIZE 1024
+
static char **
read_args_file(char *filename)
{
- int c, aix = 0, quote = 0, cmnt = 0, asize = 0;
- char **res, *astr = NULL;
FILE *file;
-
-#undef EAF_CMNT
-#undef EAF_QUOTE
-#undef SAVE_CHAR
-
-#define EAF_CMNT (1 << 8)
-#define EAF_QUOTE (1 << 9)
-#define SAVE_CHAR(C) \
- do { \
- if (!astr) \
- astr = emalloc(sizeof(char)*(asize = 20)); \
- if (aix == asize) \
- astr = erealloc(astr, sizeof(char)*(asize += 20)); \
- if (' ' != (char) (C)) \
- astr[aix++] = (char) (C); \
- else if (aix > 0 && astr[aix-1] != ' ') \
- astr[aix++] = ' '; \
- } while (0)
+ char buff[FILE_BUFF_SIZE+1];
+ size_t astr_sz = 0, sz;
+ char *astr = buff;
+ char **res;
do {
errno = 0;
@@ -1837,63 +1856,41 @@ read_args_file(char *filename)
errno_string());
}
- while (1) {
- c = getc(file);
- if (c == EOF) {
- if (ferror(file)) {
- if (errno == EINTR) {
- clearerr(file);
- continue;
- }
- usage_format("Failed to read arguments file \"%s\": %s\n",
- filename,
- errno_string());
- }
- break;
- }
+ sz = fread(astr, 1, FILE_BUFF_SIZE, file);
- switch (quote | cmnt | c) {
- case '\\':
- quote = EAF_QUOTE;
- break;
- case '#':
- cmnt = EAF_CMNT;
- break;
- case EAF_CMNT|'\n':
- cmnt = 0;
- /* Fall through... */
- case '\n':
- case '\f':
- case '\r':
- case '\t':
- case '\v':
- if (!quote)
- c = ' ';
- /* Fall through... */
- default:
- if (!cmnt)
- SAVE_CHAR(c);
- quote = 0;
- break;
- }
+ while (!feof(file) && sz == FILE_BUFF_SIZE) {
+ if (astr == buff) {
+ astr = emalloc(FILE_BUFF_SIZE*2+1);
+ astr_sz = FILE_BUFF_SIZE;
+ memcpy(astr, buff, sizeof(buff));
+ } else {
+ astr_sz += FILE_BUFF_SIZE;
+ astr = erealloc(astr,astr_sz+FILE_BUFF_SIZE+1);
+ }
+ sz = fread(astr+astr_sz, 1, FILE_BUFF_SIZE, file);
+ }
+
+ if (ferror(file)) {
+ usage_format("Failed to read arguments file \"%s\": %s\n",
+ filename,
+ errno_string());
}
- SAVE_CHAR('\0');
+ astr[astr_sz + sz] = '\0';
fclose(file);
if (astr[0] == '\0')
res = NULL;
else
- res = build_args_from_string(astr);
+ res = build_args_from_string(astr, !0);
- efree(astr);
+ if (astr != buff)
+ efree(astr);
return res;
-#undef EAF_CMNT
-#undef EAF_QUOTE
-#undef SAVE_CHAR
+#undef FILE_BUFF_SIZE
}
@@ -1996,7 +1993,7 @@ get_file_args(char *filename, argv_buf *abp, argv_buf *xabp)
i = 0;
argv = read_args_file(filename);
-
+
while (argv) {
while (argv[i]) {
@@ -2077,7 +2074,7 @@ initial_argv_massage(int *argc, char ***argv)
if (av)
avv[vix++].argv = av;
- if (vix == (*argc > 1 ? 1 : 0)) {
+ if (vix == (*argc > 1 ? 2 : 0)) {
/* Only command line argv; check if we can use argv as it is... */
ac = *argc;
av = *argv;
@@ -2099,7 +2096,7 @@ initial_argv_massage(int *argc, char ***argv)
build_new_argv:
save_arg(&ab, (*argv)[0]);
-
+
vix = 0;
while (avv[vix].argv) {
ac = avv[vix].argc;
@@ -2191,12 +2188,12 @@ possibly_quote(char* arg)
* Unicode helpers to handle environment and command line parameters on
* Windows. We internally handle all environment variables in UTF8,
* but put and get the environment using the WCHAR (limited UTF16) interface
- *
- * These are simplified to only handle Unicode characters that can fit in
+ *
+ * These are simplified to only handle Unicode characters that can fit in
* Windows simplified UTF16, i.e. characters that fit in 16 bits.
*/
-static int utf8_len(unsigned char first)
+static int utf8_len(unsigned char first)
{
if ((first & ((unsigned char) 0x80)) == 0) {
return 1;
@@ -2206,7 +2203,7 @@ static int utf8_len(unsigned char first)
return 3;
} else if ((first & ((unsigned char) 0xF8)) == 0xF0) {
return 4;
- }
+ }
return 1; /* will be a '?' */
}
@@ -2216,7 +2213,7 @@ static WCHAR *utf8_to_utf16(unsigned char *bytes)
unsigned char *tmp = bytes;
WCHAR *target, *res;
int num = 0;
-
+
while (*tmp) {
num++;
tmp += utf8_len(*tmp);
@@ -2227,12 +2224,12 @@ static WCHAR *utf8_to_utf16(unsigned char *bytes)
unipoint = (unsigned int) *bytes;
++bytes;
} else if (((*bytes) & ((unsigned char) 0xE0)) == 0xC0) {
- unipoint =
+ unipoint =
(((unsigned int) ((*bytes) & ((unsigned char) 0x1F))) << 6) |
((unsigned int) (bytes[1] & ((unsigned char) 0x3F)));
bytes += 2;
} else if (((*bytes) & ((unsigned char) 0xF0)) == 0xE0) {
- unipoint =
+ unipoint =
(((unsigned int) ((*bytes) & ((unsigned char) 0xF))) << 12) |
(((unsigned int) (bytes[1] & ((unsigned char) 0x3F))) << 6) |
((unsigned int) (bytes[2] & ((unsigned char) 0x3F)));
@@ -2265,9 +2262,9 @@ static int put_utf8(WCHAR ch, unsigned char *target, int sz, int *pos)
if (((*pos) + 1) >= sz) {
return -1;
}
- target[(*pos)++] = (((unsigned char) (x >> 6)) |
+ target[(*pos)++] = (((unsigned char) (x >> 6)) |
((unsigned char) 0xC0));
- target[(*pos)++] = (((unsigned char) (x & 0x3F)) |
+ target[(*pos)++] = (((unsigned char) (x & 0x3F)) |
((unsigned char) 0x80));
} else {
if ((x >= 0xD800 && x <= 0xDFFF) ||
@@ -2279,11 +2276,11 @@ static int put_utf8(WCHAR ch, unsigned char *target, int sz, int *pos)
return -1;
}
- target[(*pos)++] = (((unsigned char) (x >> 12)) |
+ target[(*pos)++] = (((unsigned char) (x >> 12)) |
((unsigned char) 0xE0));
- target[(*pos)++] = ((((unsigned char) (x >> 6)) & 0x3F) |
+ target[(*pos)++] = ((((unsigned char) (x >> 6)) & 0x3F) |
((unsigned char) 0x80));
- target[(*pos)++] = (((unsigned char) (x & 0x3F)) |
+ target[(*pos)++] = (((unsigned char) (x & 0x3F)) |
((unsigned char) 0x80));
}
return 0;
@@ -2295,7 +2292,7 @@ static int need_bytes_for_utf8(WCHAR x)
return 1;
else if (x < 0x800)
return 2;
- else
+ else
return 3;
}
@@ -2310,7 +2307,7 @@ static WCHAR *latin1_to_utf16(char *str)
return wstr;
}
-static char *utf16_to_utf8(WCHAR *wstr)
+static char *utf16_to_utf8(WCHAR *wstr)
{
int len = wcslen(wstr);
char *result;
@@ -2329,5 +2326,5 @@ static char *utf16_to_utf8(WCHAR *wstr)
result[pos] = '\0';
return result;
}
-
+
#endif
diff --git a/erts/etc/common/heart.c b/erts/etc/common/heart.c
index a37bb4cfef..215c152008 100644
--- a/erts/etc/common/heart.c
+++ b/erts/etc/common/heart.c
@@ -977,7 +977,7 @@ debugf(const char *format,...)
#ifdef __WIN32__
void print_last_error() {
- LPVOID lpMsgBuf;
+ LPTSTR lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
diff --git a/erts/etc/common/inet_gethost.c b/erts/etc/common/inet_gethost.c
index 4fc7a93348..12012a056e 100644
--- a/erts/etc/common/inet_gethost.c
+++ b/erts/etc/common/inet_gethost.c
@@ -1578,8 +1578,7 @@ static int create_worker(Worker *pworker, int save_que)
pworker->que_first = pworker->que_last = NULL;
pworker->que_size = 0;
}
- DEBUGF(3,("Created worker[%ld] with fd %d",
- (long) pworker->pid, (int) pworker->readfrom));
+ DEBUGF(3,("Created worker[%ld]", (long) pworker->pid));
return 0;
}
diff --git a/erts/etc/unix/Install.src b/erts/etc/unix/Install.src
index e4b842877c..2dbf628972 100644
--- a/erts/etc/unix/Install.src
+++ b/erts/etc/unix/Install.src
@@ -88,6 +88,7 @@ cd "$ERL_ROOT/bin"
cp -p "$ERL_ROOT/erts-%I_VSN%/bin/erl" .
cp -p "$ERL_ROOT/erts-%I_VSN%/bin/erlc" .
+cp -p "$ERL_ROOT/erts-%I_VSN%/bin/erl_call" .
cp -p "$ERL_ROOT/erts-%I_VSN%/bin/dialyzer" .
cp -p "$ERL_ROOT/erts-%I_VSN%/bin/typer" .
cp -p "$ERL_ROOT/erts-%I_VSN%/bin/ct_run" .
diff --git a/erts/etc/unix/cerl.src b/erts/etc/unix/cerl.src
index 1b451f7fa1..3889308a67 100644
--- a/erts/etc/unix/cerl.src
+++ b/erts/etc/unix/cerl.src
@@ -43,6 +43,7 @@
# -purecov Run emulator compiled for purecov
# -gcov Run emulator compiled for gcov
# -valgrind Run emulator compiled for valgrind
+# -asan Run emulator compiled for address-sanitizer
# -lcnt Run emulator compiled for lock counting
# -icount Run emulator compiled for instruction counting
# -rr Run emulator under "rr record"
@@ -75,8 +76,8 @@ GDB=
GDBBP=
GDBARGS=
TYPE=
-debug=
run_valgrind=no
+run_asan=no
run_rr=no
skip_erlexec=no
@@ -220,6 +221,18 @@ while [ $# -gt 0 ]; do
run_valgrind=yes
skip_erlexec=yes
;;
+ "-asan")
+ shift
+ cargs="$cargs -asan"
+ run_asan=yes
+ TYPE=.asan
+ ;;
+ "-emu_type")
+ shift
+ cargs="$cargs -$1"
+ TYPE=.$1
+ shift
+ ;;
"-rr")
shift
cargs="$cargs -rr"
@@ -265,6 +278,28 @@ if [ $skip_erlexec = yes ]; then
set -- $beam_args
IFS="$SAVE_IFS"
fi
+if [ $run_asan = yes ]; then
+ # Leak sanitizer options
+ if [ "x${LSAN_OPTIONS#*suppressions=}" = "x$LSAN_OPTIONS" ]; then
+ export LSAN_OPTIONS
+ if [ "x$ERL_TOP" != "x" ]; then
+ LSAN_OPTIONS="$LSAN_OPTIONS:suppressions=$ERL_TOP/erts/emulator/asan/suppress"
+ else
+ echo "No leak-sanitizer suppression file found in \$LSAN_OPTIONS"
+ echo "and \$ERL_TOP not set."
+ fi
+ fi
+ # Address sanitizer options
+ export ASAN_OPTIONS
+ if [ "x$ASAN_LOG_DIR" != "x" ]; then
+ if [ "x${ASAN_OPTIONS#*log_path=}" = "x$ASAN_OPTIONS" ]; then
+ ASAN_OPTIONS="$ASAN_OPTIONS:log_path=$ASAN_LOG_DIR/$EMU_NAME-$ASAN_LOGFILE_PREFIX-0"
+ fi
+ fi
+ if [ "x${ASAN_OPTIONS#*halt_on_error=}" = "x$ASAN_OPTIONS" ]; then
+ ASAN_OPTIONS="$ASAN_OPTIONS:halt_on_error=false"
+ fi
+fi
if [ "x$GDB" = "x" ]; then
if [ $run_valgrind = yes ]; then
valversion=`valgrind --version`
diff --git a/erts/etc/unix/etp-commands.in b/erts/etc/unix/etp-commands.in
index 9104d47148..f440b6a882 100644
--- a/erts/etc/unix/etp-commands.in
+++ b/erts/etc/unix/etp-commands.in
@@ -646,6 +646,67 @@ define etp-atom-1
end
+define etp-string-to-atom
+# Args: (char*) null-terminated
+#
+# Non-reentrant
+
+ set $etp_i = 0
+ set $etp_h = ((UWord)0)
+ while (($arg0)[$etp_i]) != 0
+ set $etp_c = (unsigned char)(($arg0)[$etp_i])
+
+ if $etp_c & 0x80
+ printf "Non ASCII atoms not implemented\n"
+ loop_break
+ end
+
+ set $etp_h = ($etp_h << 4) + $etp_c
+ set $etp_g = $etp_h & 0xf0000000
+ if $etp_g != 0
+ set $etp_h ^= ($etp_g >> 24)
+ set $etp_h ^= $etp_g
+ end
+ set $etp_i++
+ end
+
+ # hash_get_slot
+ set $etp_h ^= $etp_h >> erts_atom_table.htable.shift
+ if $etp_arch64
+ set $etp_h = (11400714819323198485UL * $etp_h) >> erts_atom_table.htable.shift
+ else
+ set $etp_h = (2654435769UL * $etp_h) >> erts_atom_table.htable.shift
+ end
+ set $etp_p = (Atom*)erts_atom_table.htable.bucket[$etp_h]
+
+ # search hash bucket list
+ while $etp_p
+ set $etp_i = 0
+ while $etp_i < $etp_p->len && ($arg0)[$etp_i]
+ if $etp_p->name[$etp_i] != ($arg0)[$etp_i]
+ loop_break
+ end
+ set $etp_i++
+ end
+ if $etp_i == $etp_p->len && ($arg0)[$etp_i] == 0
+ loop_break
+ end
+ set $etp_p = (Atom*)$etp_p->slot.bucket.next
+ end
+ if $etp_p
+ print ($etp_p->slot.index << 6) | (2 << 2) | 3
+ else
+ printf "Can't find atom\n"
+ end
+end
+
+document etp-string-to-atom
+%----------------------------------------
+% etp-string-to-atom (char*)
+%
+% Ex: etp-string-to-atom "erlang"
+%----------------------------------------
+end
define etp-char-1
# Args: int char, int quote_char
@@ -1093,6 +1154,92 @@ document etp-mfa
%---------------------------------------------------------------------------
end
+define etp-export-get
+ # Args: Eterm Eterm Uint
+
+ set $etp_h = (((Eterm)$arg0 >> 6) * ((Eterm)$arg1 >> 6)) ^ (Uint)$arg2
+
+ #hash_get_slot
+ set $etp_t = &export_tables[the_active_code_index.counter].htable
+ set $etp_h ^= $etp_h >> $etp_t->shift
+ if $etp_arch64
+ set $etp_h = (11400714819323198485UL * $etp_h) >> $etp_t->shift
+ else
+ set $etp_h = (2654435769UL * $etp_h) >> $etp_t->shift
+ end
+
+ set $etp_p = (struct export_entry*) $etp_t->bucket[$etp_h]
+ while $etp_p
+ if $etp_p->ep->info.mfa.module == $arg0 && $etp_p->ep->info.mfa.function == $arg1 && $etp_p->ep->info.mfa.arity == $arg2
+ loop_break
+ end
+ set $etp_p = (struct export_entry*) $etp_p->slot.bucket.next
+ end
+ if $etp_p
+ print $etp_p->ep
+ else
+ printf "Can't find export entry\n"
+ end
+end
+
+document etp-export-get
+%---------------------------------------------------------
+% etp-export-get module function arity
+%
+% Lookup and print pointer to Export entry.
+% Example:
+% (gdb) etp-string-to-atom "erlang"
+% $1 = 13323
+% (gdb) etp-string-to-atom "self"
+% $2 = 47115
+% (gdb) etp-export-get 13323 47115 0
+% $3 = (Export *) 0x7f53caf1f358
+%---------------------------------------------------------
+end
+
+define etp-module-get
+ # Args: Eterm
+
+ set $etp_ix = ((Eterm)$arg0 >> 6)
+ set $etp_h = $etp_ix
+
+ #hash_get_slot
+ set $etp_t = &module_tables[the_active_code_index.counter].htable
+ set $etp_h ^= $etp_h >> $etp_t->shift
+ if $etp_arch64
+ set $etp_h = (11400714819323198485UL * $etp_h) >> $etp_t->shift
+ else
+ set $etp_h = (2654435769UL * $etp_h) >> $etp_t->shift
+ end
+
+ set $etp_p = (Module*) $etp_t->bucket[$etp_h]
+ while $etp_p
+ if $etp_p->module == $etp_ix
+ loop_break
+ end
+ set $etp_p = (Module*) $etp_p->slot.bucket.next
+ end
+ if $etp_p
+ print $etp_p
+ else
+ printf "Can't find module entry\n"
+ end
+end
+
+document etp-module-get
+%---------------------------------------------------------
+% etp-module-get module
+%
+% Lookup and print pointer to Module entry.
+% Example:
+% (gdb) etp-string-to-atom "erlang"
+% $1 = 13323
+% (gdb) etp-module-get 13323
+% $2 = (Module *) 0x7f53caf1f358
+%---------------------------------------------------------
+end
+
+
define etp-cp-func-info-1
# Args: Eterm cp
#
@@ -1187,12 +1334,14 @@ document etp-cp
%---------------------------------------------------------------------------
% etp-cp Eterm
%
-% Take a code continuation pointer and print
+% Take a code continuation pointer or instruction pointer and print
% module, function, arity and offset.
%
-% Code continuation pointers can be found in the process structure e.g
-% process_tab[i]->cp and process_tab[i]->i, the second is the
-% program counter, which is the same thing as a continuation pointer.
+% Code continuation pointers can be found on the stack. The instruction
+% pointer is found in the process struct. For example:
+%
+% c_p->i
+% process_tab[i]->i
%---------------------------------------------------------------------------
end
@@ -1509,17 +1658,13 @@ define etp-stack-preamble
set $etp_stack_end = ($arg0)->hend
if ($arg0)->state.counter & 0x8000
printf "%%%%%% WARNING: The process is currently running, so c_p->stop will not be correct\r\n"
- printf "%%%%%% Consider using %s-emu instead\r\n", $arg1
+ printf "%%%%%% Consider using -emu variant instead\r\n"
end
printf "%% Stacktrace (%u)\n", $etp_stack_end-$etp_stack_p
if ($arg0)->i != 0
printf "I: "
etp ((Eterm)($arg0)->i)
end
- if ($arg0)->cp != 0
- printf "cp:"
- etp ((Eterm)($arg0)->cp)
- end
end
define etp-stack-preamble-emu
@@ -1528,10 +1673,6 @@ define etp-stack-preamble-emu
printf "%% Stacktrace (%u)\n", $etp_stack_end-$etp_stack_p
printf "I: "
etp ((BeamInstr)I)
- if ($arg0)->cp != 0
- printf "cp: "
- etp ((Eterm)($arg0)->cp)
- end
end
define etp-stacktrace-1
@@ -2224,13 +2365,6 @@ define etp-process-info-int
else
printf "unknown\n"
end
- printf " CP: "
- if ($etp_proc->cp)
- etp-cp-1 $etp_proc->cp
- printf "\n"
- else
- printf "unknown\n"
- end
printf " I: "
if ($etp_proc->i)
etp-cp-1 $etp_proc->i
@@ -2456,11 +2590,6 @@ define etp-process-memory-info
end
end
- if ($etp_pmem_proc->cp)
- printf " CP: "
- etp-cp-1 $etp_pmem_proc->cp
- printf " "
- end
printf "\n"
end
end
diff --git a/erts/etc/unix/run_erl.c b/erts/etc/unix/run_erl.c
index a29c7e54d2..f8d48a8b9d 100644
--- a/erts/etc/unix/run_erl.c
+++ b/erts/etc/unix/run_erl.c
@@ -46,7 +46,7 @@
/* On OS X, BSD and Solaris, we must leave _XOPEN_SOURCE undefined in order
* for the prototype of vsyslog() to be included.
*/
-# if !(defined(__APPLE__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__sun))
+# if !(defined(__APPLE__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__sun))
# define _XOPEN_SOURCE 600
# endif
# endif
@@ -1201,7 +1201,19 @@ static void error_logf(int priority, int line, const char *format, ...)
#ifdef HAVE_SYSLOG_H
if (run_daemon) {
+#ifdef HAVE_VSYSLOG
vsyslog(priority,format,args);
+#else
+ /* Some OSes like AIX lack vsyslog. */
+ va_list ap;
+ char message[900]; /* AIX man page says truncation past this */
+
+ va_start (ap, format);
+ vsnprintf(message, 900, format, ap);
+ va_end(ap);
+
+ syslog(priority, message);
+#endif
}
else
#endif
diff --git a/erts/etc/unix/to_erl.c b/erts/etc/unix/to_erl.c
index 0e2c4f266b..a1a0fde0a2 100644
--- a/erts/etc/unix/to_erl.c
+++ b/erts/etc/unix/to_erl.c
@@ -244,8 +244,8 @@ int main(int argc, char **argv)
#endif
tty_smode.c_iflag =
1*BRKINT |/*Signal interrupt on break.*/
- 1*IGNPAR |/*Ignore characters with parity errors.*/
- 0;
+ 1*IGNPAR |/*Ignore characters with parity errors.*/
+ 0;
#if 0
0*IGNBRK |/*Ignore break condition.*/
@@ -262,31 +262,31 @@ int main(int argc, char **argv)
#endif
tty_smode.c_oflag =
- 1*OPOST |/*Post-process output.*/
- 1*ONLCR |/*Map NL to CR-NL on output.*/
+ OPOST |/*Post-process output.*/
+ 0*ONLCR |/*Map NL to CR-NL on output.*/
#ifdef XTABS
- 1*XTABS |/*Expand tabs to spaces. (Linux)*/
+ 1*XTABS |/*Expand tabs to spaces. (Linux)*/
#endif
#ifdef OXTABS
- 1*OXTABS |/*Expand tabs to spaces. (FreeBSD)*/
+ 1*OXTABS |/*Expand tabs to spaces. (FreeBSD)*/
#endif
#ifdef NL0
- 1*NL0 |/*Select newline delays*/
+ 1*NL0 |/*Select newline delays*/
#endif
#ifdef CR0
- 1*CR0 |/*Select carriage-return delays*/
+ 1*CR0 |/*Select carriage-return delays*/
#endif
#ifdef TAB0
- 1*TAB0 |/*Select horizontal tab delays*/
+ 1*TAB0 |/*Select horizontal tab delays*/
#endif
#ifdef BS0
- 1*BS0 |/*Select backspace delays*/
+ 1*BS0 |/*Select backspace delays*/
#endif
#ifdef VT0
- 1*VT0 |/*Select vertical tab delays*/
+ 1*VT0 |/*Select vertical tab delays*/
#endif
#ifdef FF0
- 1*FF0 |/*Select form feed delays*/
+ 1*FF0 |/*Select form feed delays*/
#endif
0;
diff --git a/erts/etc/win32/Install.c b/erts/etc/win32/Install.c
index 06594a107f..c7bf96c124 100644
--- a/erts/etc/win32/Install.c
+++ b/erts/etc/win32/Install.c
@@ -47,7 +47,7 @@ int wmain(int argc, wchar_t **argv)
InitFile *ini_file;
InitSection *ini_section;
HANDLE module = GetModuleHandle(NULL);
- wchar_t *binaries[] = { L"erl.exe", L"werl.exe", L"erlc.exe",
+ wchar_t *binaries[] = { L"erl.exe", L"werl.exe", L"erlc.exe", L"erl_call.exe",
L"dialyzer.exe",
L"typer.exe",
L"escript.exe", L"ct_run.exe", NULL };
diff --git a/erts/etc/win32/beam.rc b/erts/etc/win32/beam.rc
index 0aaabf1097..d7e7aa0877 100644
--- a/erts/etc/win32/beam.rc
+++ b/erts/etc/win32/beam.rc
@@ -68,13 +68,14 @@ END
AboutBox DIALOG DISCARDABLE 0, 0, 217, 55
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About Erlang Shell"
-FONT 8, "MS Sans Serif"
+FONT 10, "Microsoft Sans Serif"
BEGIN
ICON 1,-1,11,17,18,20
- LTEXT "Erlang Shell Version 1.0",ID_VERSIONSTRING,40,10,119,8,
+ LTEXT "Erlang OTP Version X.X",ID_OTP_VERSIONSTRING,40,10,200,20,
SS_NOPREFIX
- LTEXT "Copyright Ericsson Telecom AB",-1,40,25,
- 119,8
+ LTEXT "Erlang Shell Version 1.0",ID_ERTS_VERSIONSTRING,40,20,200,20,
+ SS_NOPREFIX
+ LTEXT "Copyright Ericsson AB",-1,40,35,200,20
DEFPUSHBUTTON "OK",IDOK,178,7,32,14,WS_GROUP
END
diff --git a/erts/etc/win32/cygwin_tools/reg_query.sh b/erts/etc/win32/cygwin_tools/reg_query.sh
new file mode 100644
index 0000000000..c5c2566b7a
--- /dev/null
+++ b/erts/etc/win32/cygwin_tools/reg_query.sh
@@ -0,0 +1,24 @@
+#! /bin/sh
+BAT_FILE=/tmp/w$$.bat
+if [ -z "$1" -o -z "$2" ]; then
+ echo "Usage:" "$0" '<key> <valuename>'
+ exit 1
+fi
+BACKED=`echo "$1" | sed 's,/,\\\\,g'`
+# We need to get the 64bit part of the registry, hence we need to execute
+# a 64bit reg.exe, but c:\windows\system32 is redirected to 32bit versions
+# if we are in the 32bit virtual environment, so we need to use the
+# sysnative trick to get to the 64bit executable of reg.exe (ouch!)
+if [ -d $WINDIR/sysnative ]; then
+ REG_CMD="$WINDIR\\sysnative\\reg.exe"
+else
+ REG_CMD="reg.exe"
+fi
+cat > $BAT_FILE <<EOF
+@echo off
+$REG_CMD query "$BACKED" /v "$2"
+EOF
+#echo $BAT_FILE
+#cat $BAT_FILE
+RESULT=`cmd.exe //C $BAT_FILE`
+echo $RESULT | sed "s,.*$2 REG_[^ ]* ,,"
diff --git a/erts/etc/win32/cygwin_tools/w32_path.sh b/erts/etc/win32/cygwin_tools/w32_path.sh
new file mode 100755
index 0000000000..3b67ec853a
--- /dev/null
+++ b/erts/etc/win32/cygwin_tools/w32_path.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+WIN32=false
+SEPARATOR=""
+ABSOLUTE=""
+UNIX=false
+done=false
+while [ $done = false ]; do
+ case "$1" in
+ -w)
+ WIN32=true;
+ SEPARATOR=backslash;
+ shift;;
+ -d)
+ WIN32=true;
+ SEPARATOR=double;
+ shift;;
+ -m)
+ WIN32=true;
+ SEPARATOR=slash;
+ shift;;
+ -u)
+ UNIX=true;
+ shift;;
+ -a)
+ ABSOLUTE="-a";
+ shift;;
+
+ *)
+ done=true;;
+ esac
+done
+
+if [ $WIN32 = false -a $UNIX = false ]; then
+ echo "Usage: $0 -m|-w|-d|-u [-a] <path>" >&2
+ exit 1;
+fi
+
+if [ -z "$1" ]; then
+ echo "Usage: $0 -m|-w|-u [-a] <path>" >&2
+ exit 1;
+fi
+
+if [ $UNIX = true ]; then
+ echo `cygpath $ABSOLUTE -u $1`
+else
+ case "$SEPARATOR" in
+ slash)
+ echo `cygpath $ABSOLUTE -m $1`;
+ ;;
+ backslash)
+ echo `cygpath $ABSOLUTE -w $1`;
+ ;;
+ double)
+ DOUBLE=`cygpath $ABSOLUTE -w $1 | sed 's,\\\\,\\\\\\\\,g'`;
+ echo $DOUBLE
+ ;;
+ esac
+fi
diff --git a/erts/etc/win32/erl.c b/erts/etc/win32/erl.c
index b230aa6a40..ae58cf04be 100644
--- a/erts/etc/win32/erl.c
+++ b/erts/etc/win32/erl.c
@@ -17,10 +17,7 @@
*
* %CopyrightEnd%
*/
-#pragma comment(linker,"/manifestdependency:\"type='win32' "\
- "name='Microsoft.Windows.Common-Controls' "\
- "version='6.0.0.0' processorArchitecture='*' "\
- "publicKeyToken='6595b64144ccf1df' language='*'\"")
+
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
@@ -53,8 +50,9 @@ int wmain(int argc, wchar_t **argv)
HANDLE erlexec_handle; /* Instance */
ErlexecFunction *win_erlexec;
wchar_t *path = malloc(100*sizeof(wchar_t));
+ wchar_t *wslpath = malloc(100*sizeof(wchar_t));
wchar_t *npath;
- int pathlen;
+ int pathlen, wslpathlen;
char ** utf8argv;
int i, len;
@@ -66,9 +64,24 @@ int wmain(int argc, wchar_t **argv)
path = realloc(path,pathlen*sizeof(wchar_t));
GetEnvironmentVariableW(L"PATH",path,pathlen);
}
- pathlen = (wcslen(path) + wcslen(erlexec_dir) + 2);
+
+ if ((wslpathlen = GetEnvironmentVariableW(L"WSLENV",wslpath,100)) > 0) {
+ if ((wslpathlen = GetEnvironmentVariableW(L"WSLPATH",wslpath,100)) > 0) {
+ if (wslpathlen > 100) {
+ wslpath = realloc(wslpath,wslpathlen*sizeof(wchar_t));
+ GetEnvironmentVariableW(L"WSLPATH",wslpath,wslpathlen);
+ }
+ wslpathlen = wcslen(wslpath);
+ }
+ }
+ /* Add size for path delimiters and eos */
+ pathlen = (wcslen(path) + wslpathlen + wcslen(erlexec_dir) + 3);
npath = (wchar_t *) malloc(pathlen*sizeof(wchar_t));
- swprintf(npath,pathlen,L"%s;%s",erlexec_dir,path);
+ if(wslpathlen > 0) {
+ swprintf(npath,pathlen,L"%s;%s;%s",erlexec_dir,path,wslpath);
+ } else {
+ swprintf(npath,pathlen,L"%s;%s",erlexec_dir,path);
+ }
SetEnvironmentVariableW(L"PATH",npath);
if ((erlexec_handle = LoadLibraryW(erlexec_name)) == NULL) {
diff --git a/erts/etc/win32/erl_log.c b/erts/etc/win32/erl_log.c
index de0d8e39f0..ed2fb294de 100644
--- a/erts/etc/win32/erl_log.c
+++ b/erts/etc/win32/erl_log.c
@@ -57,7 +57,7 @@ main()
static void print_last_error(char* message)
{
- LPTSTR* lpBufPtr;
+ LPTSTR lpBufPtr;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
diff --git a/erts/etc/win32/erlsrv/erlsrv_interactive.c b/erts/etc/win32/erlsrv/erlsrv_interactive.c
index c616ef86e3..9f1312b5e9 100644
--- a/erts/etc/win32/erlsrv/erlsrv_interactive.c
+++ b/erts/etc/win32/erlsrv/erlsrv_interactive.c
@@ -1273,7 +1273,7 @@ int interactive_main(int argc, wchar_t **argv){
_setmode(_fileno(stderr), _O_U8TEXT); /* set stderr to UTF8 */
if (take_lock() != 0) {
- fwprintf(stderr,L"%s: unable to acquire global lock (%s).\n",argv[0],
+ fwprintf(stderr,L"%s: unable to acquire global lock (%S).\n",argv[0],
ERLSRV_INTERACTIVE_GLOBAL_SEMAPHORE);
return 1;
}
diff --git a/erts/etc/win32/manifest.xml b/erts/etc/win32/manifest.xml
new file mode 100644
index 0000000000..eea364c9e9
--- /dev/null
+++ b/erts/etc/win32/manifest.xml
@@ -0,0 +1,17 @@
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0"
+ processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*">
+ </assemblyIdentity>
+ </dependentAssembly>
+ </dependency>
+ <ms_asmv2:trustInfo xmlns:ms_asmv2="urn:schemas-microsoft-com:asm.v2">
+ <ms_asmv2:security>
+ <ms_asmv2:requestedPrivileges>
+ <ms_asmv2:requestedExecutionLevel level="AsInvoker" uiAccess="false"></ms_asmv2:requestedExecutionLevel>
+ </ms_asmv2:requestedPrivileges>
+ </ms_asmv2:security>
+ </ms_asmv2:trustInfo>
+</assembly>
diff --git a/erts/etc/win32/msys_tools/reg_query.sh b/erts/etc/win32/msys_tools/reg_query.sh
index ae6d5c3218..70a7bf0d17 100644
--- a/erts/etc/win32/msys_tools/reg_query.sh
+++ b/erts/etc/win32/msys_tools/reg_query.sh
@@ -7,7 +7,7 @@ fi
BACKED=`echo "$1" | sed 's,/,\\\\,g'`
# We need to get the 64bit part of the registry, hence we need to execute
# a 64bit reg.exe, but c:\windows\system32 is redirected to 32bit versions
-# if we ate in the 32bit virtual environment, why we need to use the
+# if we are in the 32bit virtual environment, so we need to use the
# sysnative trick to get to the 64bit executable of reg.exe (ouch!)
if [ -d $WINDIR/sysnative ]; then
REG_CMD="$WINDIR\\sysnative\\reg.exe"
diff --git a/erts/etc/win32/msys_tools/w32_path.sh b/erts/etc/win32/msys_tools/w32_path.sh
new file mode 100755
index 0000000000..9a5089391d
--- /dev/null
+++ b/erts/etc/win32/msys_tools/w32_path.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+WIN32=false
+SEPARATOR=""
+ABSOLUTE=""
+UNIX=false
+done=false
+while [ $done = false ]; do
+ case "$1" in
+ -w)
+ WIN32=true;
+ SEPARATOR=backslash;
+ shift;;
+ -d)
+ WIN32=true;
+ SEPARATOR=double;
+ shift;;
+ -m)
+ WIN32=true;
+ SEPARATOR=slash;
+ shift;;
+ -u)
+ UNIX=true;
+ shift;;
+ -a)
+ ABSOLUTE="-a";
+ shift;;
+
+ *)
+ done=true;;
+ esac
+done
+
+if [ $WIN32 = false -a $UNIX = false ]; then
+ echo "Usage: $0 -m|-w|-d|-u [-a] <path>" >&2
+ exit 1;
+fi
+
+if [ -z "$1" ]; then
+ echo "Usage: $0 -m|-w|-u [-a] <path>" >&2
+ exit 1;
+fi
+
+if [ $UNIX = true ]; then
+ echo `win2msys_path.sh $ABSOLUTE $1`
+else
+ case "$SEPARATOR" in
+ slash)
+ echo `msys2win_path.sh -m $ABSOLUTE -m $1`;
+ ;;
+ backslash)
+ echo `msys2win_path.sh $ABSOLUTE $1`;
+ ;;
+ double)
+ DOUBLE=`msys2win_path.sh $ABSOLUTE $1 | sed 's,\\\\,\\\\\\\\,g'`;
+ echo $DOUBLE
+ ;;
+ esac
+fi
diff --git a/erts/etc/win32/nsis/Makefile b/erts/etc/win32/nsis/Makefile
index 0b4e0d0359..6273181a9d 100644
--- a/erts/etc/win32/nsis/Makefile
+++ b/erts/etc/win32/nsis/Makefile
@@ -23,11 +23,11 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
include $(ERL_TOP)/erts/vsn.mk
VERSION_HEADER = erlang.nsh
-MAKENSIS = makensis
+MAKENSIS = makensis.exe
MAKENSISFLAGS = /V2
CUSTOM_MODERN=custom_modern.exe
-# This is not the way we usually do in our makefiles,
+# This is not the way we usually do in our makefiles,
# but making release is the ONLY thing we do with this one,
# Its not called during ordinary recursive make.
all: release
@@ -44,56 +44,38 @@ TARGET_DIR = $(RELEASE_PATH)
ifdef MSYSTEM
ifeq ($(MSYSTEM),$(filter $(MSYSTEM),MSYS MINGW32 MINGW64))
- USEMSYS := true
+ MAKENSISFLAGS = //V2
endif
endif
-ifeq ($(USEMSYS),true)
-
- MAKENSISFLAGS = //V2
- WTESTROOT=$(shell (msys2win_path.sh "$(RELEASE_PATH)"))
- WTARGET_DIR=$(shell (msys2win_path.sh "$(TARGET_DIR)"))
-
-else
-
- MAKENSISFLAGS = /V2
- WTESTROOT=$(shell (cygpath -d "$(RELEASE_PATH)" 2>/dev/null || cygpath -w "$(RELEASE_PATH)"))
- WTARGET_DIR=$(shell (cygpath -d "$(TARGET_DIR)" 2>/dev/null || cygpath -d "$(TARGET_DIR)"))
-
-endif
+WTESTROOT=$(shell (w32_path.sh -d "$(RELEASE_PATH)"))
+WTARGET_DIR=$(shell (w32_path.sh -d "$(TARGET_DIR)"))
ifeq ($(CONFIG_SUBTYPE),win64)
WINTYPE=win64
+ REDIST_TARGET=vcredist_x64.exe
else
WINTYPE=win32
+ REDIST_TARGET=vcredist_x86.exe
endif
-REDIST_FILE=$(shell (sh ./find_redist.sh || echo ""))
-ifeq ($(USEMSYS),true)
- NICEREDISTFILE=$(shell (msys2win_path.sh -m "$(REDIST_FILE)" 2>/dev/null || echo ""))
-else
- NICEREDISTFILE=$(shell (cygpath -d -m "$(REDIST_FILE)" 2>/dev/null || echo ""))
-endif
-
-REDIST_TARGET=$(shell (sh ./find_redist.sh -n || echo ""))
-REDIST_DLL_VERSION=$(shell (sh ./dll_version_helper.sh $(NICEREDISTFILE) || echo ""))
-REDIST_DLL_NAME=$(shell (sh ./dll_version_helper.sh -n $(NICEREDISTFILE) || echo ""))
+REDIST_FILE=$(shell (sh ./find_redist.sh $(WINTYPE) || echo ""))
+NICEREDISTFILE=$(shell (w32_path.sh -m "$(REDIST_FILE)" 2>/dev/null || echo "NOTFOUND"))
+# $(info $$NICEREDISTFILE = [${NICEREDISTFILE}])
+REDIST_DLL_VERSION=$(shell (sh ./dll_version_helper.sh "$(NICEREDISTFILE)" || echo ""))
+REDIST_DLL_NAME=$(shell (sh ./dll_version_helper.sh -n "$(NICEREDISTFILE)" || echo ""))
+# $(info $$REDIST_DLL_VERSION = [${REDIST_DLL_VERSION}])
+# $(info $$REDIST_DLL_NAME = [${REDIST_DLL_NAME}])
+# $(info $$REDIST_FILE = [${REDIST_FILE}])
release_spec:
- @NSIS_VER=`makensis /hdrinfo | head -1 | awk '{print $$2}'`; \
+ @NSIS_VER=`makensis.exe -version`; \
case $$NSIS_VER in \
v2.0b*) \
- echo '!define MUI_VERSION "$(SYSTEM_VSN)"' > $(VERSION_HEADER);\
- echo '!define ERTS_VERSION "$(VSN)"' >> $(VERSION_HEADER);\
- echo '!define TESTROOT "$(WTESTROOT)"' >> $(VERSION_HEADER);\
- echo '!define OUTFILEDIR "$(WTARGET_DIR)"' >> $(VERSION_HEADER);\
- if [ -f "$(RELEASE_PATH)/docs/doc/index.html" ];\
- then\
- echo '!define HAVE_DOCS 1' >> $(VERSION_HEADER); \
- fi;\
- $(MAKENSIS) erlang.nsi;;\
- v2.*) \
+ echo "Unsupported NSIS version: $$NSIS_VER";;\
+ v2.* | v3.*) \
echo '!define OTP_VERSION "$(SYSTEM_VSN)"' > $(VERSION_HEADER);\
+ echo '!define OTP_RELEASE_VERSION "$(OTP_VERSION)"' >> $(VERSION_HEADER);\
echo '!define ERTS_VERSION "$(VSN)"' >> $(VERSION_HEADER);\
echo '!define TESTROOT "$(WTESTROOT)"' >> $(VERSION_HEADER);\
echo '!define OUTFILEDIR "$(WTARGET_DIR)"' >> $(VERSION_HEADER);\
@@ -104,7 +86,7 @@ release_spec:
fi;\
if [ '!' -z "$(REDIST_FILE)" -a '!' -z "$(REDIST_DLL_VERSION)" ];\
then \
- cp $(REDIST_FILE) "$(RELEASE_PATH)/$(REDIST_TARGET)";\
+ cp "$(REDIST_FILE)" "$(RELEASE_PATH)/$(REDIST_TARGET)";\
echo '!define HAVE_REDIST_FILE 1' >> $(VERSION_HEADER); \
echo '!define REDIST_DLL_VERSION "$(REDIST_DLL_VERSION)"' >> $(VERSION_HEADER);\
echo '!define REDIST_DLL_NAME "$(REDIST_DLL_NAME)"' >> $(VERSION_HEADER);\
@@ -117,7 +99,7 @@ release_spec:
echo "Running $(MAKENSIS) $(MAKENSISFLAGS) erlang20.nsi";\
$(MAKENSIS) $(MAKENSISFLAGS) erlang20.nsi;;\
*) \
- echo 'Unsupported NSIS version';;\
+ echo "Unsupported NSIS version: $$NSIS_VER";;\
esac
release_docs release_docs_spec docs:
diff --git a/erts/etc/win32/nsis/dll_version_helper.sh b/erts/etc/win32/nsis/dll_version_helper.sh
index 9eafb6ce0e..1b295aa89a 100755
--- a/erts/etc/win32/nsis/dll_version_helper.sh
+++ b/erts/etc/win32/nsis/dll_version_helper.sh
@@ -44,12 +44,16 @@ int main(void)
}
EOF
-cl -MD hello.c > /dev/null 2>&1
+cl.exe -MD hello.c > /dev/null 2>&1
if [ '!' -f hello.exe.manifest ]; then
# Gah - VC 2010 changes the way it handles DLL's and manifests... Again...
# need another way of getting the version
DLLNAME=`dumpbin.exe -imports hello.exe | egrep MSVCR.*dll`
DLLNAME=`echo $DLLNAME`
+ if [ -z "$DLLNAME" ]; then
+ DLLNAME=`dumpbin.exe -imports hello.exe | egrep VCRUNTIME.*dll`
+ DLLNAME=`echo $DLLNAME`
+ fi
if [ '!' -z "$1" ]; then
FILETOLOOKIN=$1
else
@@ -92,22 +96,25 @@ int main(void)
for(i=0; i < n; ++i) {
sprintf(buff,"\\\\StringFileInfo\\\\%04x%04x\\\\FileVersion",
translate[i*2],translate[i*2+1]);
- if (VerQueryValue(versinfo,buff,&vs_verinfo,&vs_ver_size)) {
- printf("%s\n",(char *) vs_verinfo);
- return 0;
+ if (VerQueryValue(versinfo,buff,&vs_verinfo,&vs_ver_size) && vs_ver_size > 2) {
+ if(vs_verinfo[1] == 0) // Wide char (depends on compiler version!!)
+ printf("%S\n",(unsigned short *) vs_verinfo);
+ else
+ printf("%s\n",(char *) vs_verinfo);
+ return 0;
}
}
fprintf(stderr,"Failed to find file version of %s\n",REQ_MODULE);
return 0;
}
EOF
- cl -MD helper.c version.lib > /dev/null 2>&1
+ cl.exe -MD helper.c version.lib > /dev/null 2>&1
if [ '!' -f helper.exe ]; then
echo "Failed to build helper program." >&2
exit 1
fi
NAME=$DLLNAME
- VERSION=`./helper`
+ VERSION=`./helper.exe`
else
VERSION=`grep '<assemblyIdentity' hello.exe.manifest | sed 's,.*version=.\([0-9\.]*\).*,\1,g' | grep -v '<'`
NAME=`grep '<assemblyIdentity' hello.exe.manifest | sed 's,.*name=.[A-Za-z\.]*\([0-9]*\).*,msvcr\1.dll,g' | grep -v '<'`
diff --git a/erts/etc/win32/nsis/erlang.nsi b/erts/etc/win32/nsis/erlang.nsi
deleted file mode 100644
index f4fd2b4cdb..0000000000
--- a/erts/etc/win32/nsis/erlang.nsi
+++ /dev/null
@@ -1,386 +0,0 @@
-; NSIS Modern User Interface version 1.63
-; Erlang OTP installation script based on "Start Menu Folder Selection
-; Example Script"
-; Original example written by Joost Verburg
-; Modified for Erlang by Patrik
-
-; Verbosity does not come naturally with MUI, have to set it back now and then.
- !verbose 1
- !define MUI_MANUALVERBOSE 1
-
- !define MUI_PRODUCT "Erlang OTP"
-
- !include "erlang.nsh" ; All release specific parameters come from this
-
-
- !include "MUI.nsh"
-
-;--------------------------------
-;Configuration
-
- ;SetCompressor bzip2
-
-;General
- OutFile "${OUTFILEDIR}\otp_win32_${MUI_VERSION}.exe"
-
-;Folder selection page
- InstallDir "$PROGRAMFILES\erl${ERTS_VERSION}"
-
-;Remember install folder
- InstallDirRegKey HKLM "SOFTWARE\Ericsson\Erlang\${ERTS_VERSION}" ""
-
-;$9 is being used to store the Start Menu Folder.
-;Do not use this variable in your script (or Push/Pop it)!
-
-;To change this variable, use MUI_STARTMENUPAGE_VARIABLE.
-;Have a look at the Readme for info about other options (default folder,
-;registry).
-
-; Set the default start menu folder
-
- !define MUI_STARTMENUPAGE_DEFAULTFOLDER "${MUI_PRODUCT} ${MUI_VERSION}"
-
-; Registry keys where start menu folder is stored
- !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM"
- !define MUI_STARTMENUPAGE_REGISTRY_KEY \
- "SOFTWARE\Ericsson\Erlang\${ERTS_VERSION}"
- !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
-
-; Temporary variable used here and there...
- !define TEMP $R0
-
-;--------------------------------
-;Modern UI Configuration
- !define MUI_ICON "erlang_inst.ico"
- !define MUI_UNICON "erlang_uninst.ico"
- !define MUI_WELCOMEPAGE
- !define MUI_COMPONENTSPAGE
- !define MUI_DIRECTORYPAGE
- !define MUI_STARTMENUPAGE
-
- !define MUI_ABORTWARNING
-
- !define MUI_UNINSTALLER
- !define MUI_UNCONFIRMPAGE
-
-;--------------------------------
-;Languages
-
- !insertmacro MUI_LANGUAGE "English"
-
-;--------------------------------
-;Language Strings
-
-;Description
- LangString DESC_SecErlang ${LANG_ENGLISH} "Erlang OTP."
- LangString DESC_SecErlangDev ${LANG_ENGLISH} \
- "Erlang OTP development environment (required)."
- LangString DESC_SecErlangAssoc ${LANG_ENGLISH} \
- "Erlang filetype associations (.erl, .hrl, .beam)."
-!ifdef HAVE_DOCS
- LangString DESC_SecErlangDoc ${LANG_ENGLISH} "Documentation."
-!endif
-;--------------------------------
-;Installer Sections
-
-SubSection /e "Erlang" SecErlang
-Section "Development" SecErlangDev
-SectionIn 1 RO
-
- StrCmp ${MUI_STARTMENUPAGE_VARIABLE} "" 0 skip_silent_mode
- StrCpy ${MUI_STARTMENUPAGE_VARIABLE} \
- "${MUI_STARTMENUPAGE_DEFAULTFOLDER}"
-skip_silent_mode:
-
- SetOutPath "$INSTDIR"
- File "${TESTROOT}\Install.ini"
- File "${TESTROOT}\Install.exe"
- File /r "${TESTROOT}\releases"
- File /r "${TESTROOT}\lib"
- File /r "${TESTROOT}\erts-${ERTS_VERSION}"
- File /r "${TESTROOT}\usr"
-
-;Store install folder
- WriteRegStr HKLM "SOFTWARE\Ericsson\Erlang\${ERTS_VERSION}" "" $INSTDIR
-
-; Run the setup program
- ExecWait '"$INSTDIR\Install.exe" -s'
-
-; The startmenu stuff
- !insertmacro MUI_STARTMENU_WRITE_BEGIN
-; Set back verbosity...
- !verbose 1
-; Try to use the Common startmenu...
- SetShellVarContext All
- ClearErrors
- CreateDirectory "$SMPROGRAMS\${MUI_STARTMENUPAGE_VARIABLE}"
- IfErrors 0 continue_create
- ;MessageBox MB_OK "Error creating file"
- SetShellVarContext current
- CreateDirectory "$SMPROGRAMS\${MUI_STARTMENUPAGE_VARIABLE}"
-continue_create:
- WriteUninstaller "$INSTDIR\Uninstall.exe"
- CreateShortCut "$SMPROGRAMS\${MUI_STARTMENUPAGE_VARIABLE}\Erlang.lnk" \
- "$INSTDIR\bin\werl.exe"
- CreateShortCut \
- "$SMPROGRAMS\${MUI_STARTMENUPAGE_VARIABLE}\Uninstall.lnk" \
- "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
-
- !insertmacro MUI_STARTMENU_WRITE_END
-; And once again, the verbosity...
- !verbose 1
-;Create uninstaller
-; WriteUninstaller "$INSTDIR\Uninstall.exe"
-
- WriteRegStr HKLM \
- "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})" \
- "DisplayName" "Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})"
- WriteRegStr HKLM \
- "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})" \
- "UninstallString" "$INSTDIR\Uninstall.exe"
- WriteRegDWORD HKLM \
- "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})" \
- "NoModify" 1
- WriteRegDWORD HKLM \
- "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})" \
- "NoRepair" 1
-
-; Check that the registry could be written, we only check one key,
-; but it should be sufficient...
- ReadRegStr ${TEMP} "${MUI_STARTMENUPAGE_REGISTRY_ROOT}" \
- "${MUI_STARTMENUPAGE_REGISTRY_KEY}" \
- "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}"
-
- StrCmp ${TEMP} "" 0 done
-
-; Now we're done if we are a superuser. If the registry stuff failed, we
-; do the things below...
-
- WriteRegStr HKCU "Software\Ericsson\Erlang\${ERTS_VERSION}" \
- "" $INSTDIR
- WriteRegStr HKCU "${MUI_STARTMENUPAGE_REGISTRY_KEY}" \
- "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}" \
- "${MUI_STARTMENUPAGE_VARIABLE}"
- WriteRegStr HKCU \
- "Software\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})" \
- "DisplayName" "Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})"
- WriteRegStr HKCU \
- "Software\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})" \
- "UninstallString" "$INSTDIR\Uninstall.exe"
- WriteRegDWORD HKCU \
- "Software\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})" \
- "NoModify" 1
- WriteRegDWORD HKCU \
- "Software\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})" \
- "NoRepair" 1
-
-done:
-SectionEnd ; SecErlangDev
-
-Section "Associations" SecErlangAssoc
-
- ;File /r "${TESTROOT}\usr\lib\icons"
-
-; .erl
- ; back up old value of .erl
- ReadRegStr $1 HKCR ".erl" ""
- StrCmp $1 "" OwnErl
- StrCmp $1 "ErlangSource" OwnErl
- WriteRegStr HKCR ".erl" "backup_val" $1
-OwnErl:
- WriteRegStr HKCR ".erl" "" "ErlangSource"
- ReadRegStr $0 HKCR "ErlangSource" ""
- StrCmp $0 "" 0 skipErlAssoc
- WriteRegStr HKCR "ErlangSource" "" "Erlang source file"
- WriteRegStr HKCR "ErlangSource\shell" "" "open"
- WriteRegStr HKCR "ErlangSource\shell\compile" "" "Compile"
- WriteRegStr HKCR "ErlangSource\shell\compile\command" "" \
- '"$INSTDIR\bin\erlc.exe" "%1"'
- WriteRegStr HKCR "ErlangSource\DefaultIcon" "" \
- $INSTDIR\usr\lib\icons\erl_icon.ico
- WriteRegStr HKCR "ErlangSource\shell\open\command" \
- "" 'write.exe "%1"'
-skipErlAssoc:
-
-; .hrl
- ; back up old value of .hrl
- ReadRegStr $1 HKCR ".hrl" ""
- StrCmp $1 "" OwnHrl
- StrCmp $1 "ErlangHeader" OwnHrl
- WriteRegStr HKCR ".hrl" "backup_val" $1
-OwnHrl:
- WriteRegStr HKCR ".hrl" "" "ErlangHeader"
- ReadRegStr $0 HKCR "ErlangHeader" ""
- StrCmp $0 "" 0 skipHrlAssoc
- WriteRegStr HKCR "ErlangHeader" "" "Erlang header file"
- WriteRegStr HKCR "ErlangHeader\shell" "" "open"
- WriteRegStr HKCR "ErlangHeader\DefaultIcon" "" \
- $INSTDIR\usr\lib\icons\hrl_icon.ico
- WriteRegStr HKCR "ErlangHeader\shell\open\command" \
- "" 'write.exe "%1"'
-skipHrlAssoc:
-
-; .beam
- ; back up old value of .beam
- ReadRegStr $1 HKCR ".beam" ""
- StrCmp $1 "" OwnBeam
- StrCmp $1 "ErlangBeam" OwnBeam
- WriteRegStr HKCR ".beam" "backup_val" $1
-OwnBeam:
- WriteRegStr HKCR ".beam" "" "ErlangBeam"
- ReadRegStr $0 HKCR "ErlangBeam" ""
- StrCmp $0 "" 0 skipBeamAssoc
- WriteRegStr HKCR "ErlangBeam" "" "Erlang beam code"
- WriteRegStr HKCR "ErlangBeam\DefaultIcon" "" \
- $INSTDIR\usr\lib\icons\beam_icon.ico
-skipBeamAssoc:
-
-SectionEnd ; SecErlangAssoc
-SubSectionEnd
-
-!ifdef HAVE_DOCS
-Section "Erlang Documentation" SecErlangDoc
-
- SetOutPath "$INSTDIR"
- File /r "${TESTROOT}\docs\*"
-
-; The startmenu stuff
- !insertmacro MUI_STARTMENU_WRITE_BEGIN
-; Set back verbosity...
- !verbose 1
-; Try to use the Common startmenu...
- SetShellVarContext All
- ClearErrors
- CreateShortCut "$SMPROGRAMS\${MUI_STARTMENUPAGE_VARIABLE}\Erlang Documentation.lnk" \
- "$INSTDIR\doc\index.html"
- IfErrors 0 continue_create
- ;MessageBox MB_OK "Error creating file"
- SetShellVarContext current
- CreateShortCut \
- "$SMPROGRAMS\${MUI_STARTMENUPAGE_VARIABLE}\Erlang Documentation.lnk" \
- "$INSTDIR\doc\index.html"
-continue_create:
-
- !insertmacro MUI_STARTMENU_WRITE_END
-; And once again, the verbosity...
- !verbose 1
-SectionEnd ; ErlangDoc
-!endif
-
-
-;Display the Finish header
-;Insert this macro after the sections if you are not using a finish page
- !insertmacro MUI_SECTIONS_FINISHHEADER
-
-;--------------------------------
-;Descriptions
-
- !insertmacro MUI_FUNCTIONS_DESCRIPTION_BEGIN
- !insertmacro MUI_DESCRIPTION_TEXT ${SecErlang} $(DESC_SecErlang)
- !insertmacro MUI_DESCRIPTION_TEXT ${SecErlangDev} $(DESC_SecErlangDev)
- !insertmacro MUI_DESCRIPTION_TEXT ${SecErlangAssoc} \
- $(DESC_SecErlangAssoc)
-!ifdef HAVE_DOCS
- !insertmacro MUI_DESCRIPTION_TEXT ${SecErlangDoc} $(DESC_SecErlangDoc)
-!endif
- !insertmacro MUI_FUNCTIONS_DESCRIPTION_END
-
-;--------------------------------
-;Uninstaller Section
-
-Section "Uninstall"
-
- RMDir /r "$INSTDIR"
-
-;Remove shortcut
- ReadRegStr ${TEMP} "${MUI_STARTMENUPAGE_REGISTRY_ROOT}" \
- "${MUI_STARTMENUPAGE_REGISTRY_KEY}" \
- "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}"
- StrCmp ${TEMP} "" 0 end_try
-; Try HKCU instead...
- ReadRegStr ${TEMP} HKCU \
- "${MUI_STARTMENUPAGE_REGISTRY_KEY}" \
- "${MUI_STARTMENUPAGE_REGISTRY_VALUENAME}"
-; If this failed to, we have no shortcuts (eh?)
- StrCmp ${TEMP} "" noshortcuts
-end_try:
- SetShellVarContext All
- ClearErrors
-; If we cannot find the shortcut, switch to current user context
- GetFileTime "$SMPROGRAMS\${TEMP}\Erlang.lnk" $R1 $R2
- IfErrors 0 continue_delete
- ;MessageBox MB_OK "Error removing file"
- SetShellVarContext current
-continue_delete:
- Delete "$SMPROGRAMS\${TEMP}\Erlang.lnk"
- Delete "$SMPROGRAMS\${TEMP}\Uninstall.lnk"
- Delete "$SMPROGRAMS\${TEMP}\Erlang Documentation.lnk"
- RMDir "$SMPROGRAMS\${TEMP}" ;Only if empty
-
-noshortcuts:
-; We delete both in HKCU and HKLM, we don't really know were they might be...
- DeleteRegKey /ifempty HKLM "SOFTWARE\Ericsson\Erlang\${ERTS_VERSION}"
- DeleteRegKey /ifempty HKCU "SOFTWARE\Ericsson\Erlang\${ERTS_VERSION}"
- DeleteRegKey HKLM \
- "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})"
- DeleteRegKey HKCU \
- "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${MUI_VERSION} (${ERTS_VERSION})"
-
-
-; Now remove shell/file associations we'we made...
-; .erl
- ReadRegStr $1 HKCR ".erl" ""
- StrCmp $1 "ErlangSource" 0 NoOwnSourceExt
- ReadRegStr $1 HKCR ".erl" "backup_val"
- StrCmp $1 "" 0 RestoreBackupSource
- DeleteRegKey HKCR ".erl"
- Goto NoOwnSourceExt
-RestoreBackupSource:
- WriteRegStr HKCR ".erl" "" $1
- DeleteRegValue HKCR ".erl" "backup_val"
-NoOwnSourceExt:
-
- ReadRegStr $1 HKCR "ErlangSource\DefaultIcon" ""
- StrCmp $1 "$INSTDIR\usr\lib\icons\erl_icon.ico" 0 NoOwnSource
- DeleteRegKey HKCR "ErlangSource"
-NoOwnSource:
-
-;.hrl
- ReadRegStr $1 HKCR ".hrl" ""
- StrCmp $1 "ErlangHeader" 0 NoOwnHeaderExt
- ReadRegStr $1 HKCR ".hrl" "backup_val"
- StrCmp $1 "" 0 RestoreBackupHeader
- DeleteRegKey HKCR ".hrl"
- Goto NoOwnHeaderExt
-RestoreBackupHeader:
- WriteRegStr HKCR ".hrl" "" $1
- DeleteRegValue HKCR ".hrl" "backup_val"
-NoOwnHeaderExt:
-
- ReadRegStr $1 HKCR "ErlangHeader\DefaultIcon" ""
- StrCmp $1 "$INSTDIR\usr\lib\icons\hrl_icon.ico" 0 NoOwnHeader
- DeleteRegKey HKCR "ErlangHeader"
-NoOwnHeader:
-
-;.beam
- ReadRegStr $1 HKCR ".beam" ""
- StrCmp $1 "ErlangBeam" 0 NoOwnBeamExt
- ReadRegStr $1 HKCR ".beam" "backup_val"
- StrCmp $1 "" 0 RestoreBackupBeam
- DeleteRegKey HKCR ".beam"
- Goto NoOwnBeamExt
-RestoreBackupBeam:
- WriteRegStr HKCR ".beam" "" $1
- DeleteRegValue HKCR ".beam" "backup_val"
-NoOwnBeamExt:
-
- ReadRegStr $1 HKCR "ErlangBeam\DefaultIcon" ""
- StrCmp $1 "$INSTDIR\usr\lib\icons\beam_icon.ico" 0 NoOwnBeam
- DeleteRegKey HKCR "ErlangBeam"
-NoOwnBeam:
-
-;Display the Finish header
- !insertmacro MUI_UNFINISHHEADER
-
-SectionEnd
- !verbose 3
diff --git a/erts/etc/win32/nsis/erlang20.nsi b/erts/etc/win32/nsis/erlang20.nsi
index 66746b684d..907a64b89c 100644
--- a/erts/etc/win32/nsis/erlang20.nsi
+++ b/erts/etc/win32/nsis/erlang20.nsi
@@ -52,13 +52,13 @@ Var STARTMENU_FOLDER
!define MY_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
;General
- OutFile "${OUTFILEDIR}\otp_${WINTYPE}_${OTP_VERSION}.exe"
+ OutFile "${OUTFILEDIR}\otp_${WINTYPE}_${OTP_RELEASE_VERSION}.exe"
;Folder selection page
!if ${WINTYPE} == "win64"
- InstallDir "$PROGRAMFILES64\erl${ERTS_VERSION}"
+ InstallDir "$PROGRAMFILES64\erl-${OTP_RELEASE_VERSION}"
!else
- InstallDir "$PROGRAMFILES\erl${ERTS_VERSION}"
+ InstallDir "$PROGRAMFILES\erl-${OTP_RELEASE_VERSION}"
!endif
;Remember install folder
InstallDirRegKey HKLM "SOFTWARE\Ericsson\Erlang\${ERTS_VERSION}" ""
@@ -68,7 +68,7 @@ Var STARTMENU_FOLDER
!if ${WINTYPE} == "win64"
!define MUI_STARTMENUPAGE_DEFAULTFOLDER "${OTP_PRODUCT} ${OTP_VERSION} (x64)"
!else
- !define MUI_STARTMENUPAGE_DEFAULTFOLDER "${OTP_PRODUCT} ${OTP_VERSION}"
+ !define MUI_STARTMENUPAGE_DEFAULTFOLDER "${OTP_PRODUCT} ${OTP_VERSION} (i386)"
!endif
;--------------------------------
@@ -134,7 +134,7 @@ Section "Microsoft redistributable libraries." SecMSRedist
IfSilent +3
ExecWait '"$INSTDIR\${REDIST_EXECUTABLE}"'
Goto +2
- ExecWait '"$INSTDIR\${REDIST_EXECUTABLE}" /q'
+ ExecWait '"$INSTDIR\${REDIST_EXECUTABLE}" /q /norestart'
!verbose 1
SectionEnd ; MSRedist
@@ -144,7 +144,21 @@ SubSection /e "Erlang" SecErlang
Section "Development" SecErlangDev
SectionIn 1 RO
+
SetOutPath "$INSTDIR"
+
+; Don't let Users nor Autenticated Users group create new files
+; Avoid dll injection when installing to non /Program Files/ dirs
+
+ StrCmp $INSTDIR $InstallDir cp_files
+ ; Remove ANY inherited access control
+ ExecShellWait "open" "$SYSDIR\icacls.exe" '"$INSTDIR" /inheritance:r' SW_HIDE
+ ; Grant Admin full control
+ ExecShellWait "open" "$SYSDIR\icacls.exe" '"$INSTDIR" /grant:r *S-1-5-32-544:(OI)(CI)F' SW_HIDE
+ ; Grant Normal Users read+execute control
+ ExecShellWait "open" "$SYSDIR\icacls.exe" '"$INSTDIR" /grant:r *S-1-1-0:(OI)(CI)RX' SW_HIDE
+
+cp_files:
File "${TESTROOT}\Install.ini"
File "${TESTROOT}\Install.exe"
SetOutPath "$INSTDIR\releases"
@@ -201,7 +215,7 @@ done_startmenu:
WriteRegStr HKLM \
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${OTP_VERSION} (${ERTS_VERSION})" \
- "DisplayName" "Erlang OTP ${OTP_VERSION} (${ERTS_VERSION})"
+ "DisplayName" "Erlang OTP ${OTP_RELEASE_VERSION} (${ERTS_VERSION})"
WriteRegStr HKLM \
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${OTP_VERSION} (${ERTS_VERSION})" \
"UninstallString" "$INSTDIR\Uninstall.exe"
@@ -225,7 +239,7 @@ done_startmenu:
WriteRegStr HKCU \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${OTP_VERSION} (${ERTS_VERSION})" \
- "DisplayName" "Erlang OTP ${OTP_VERSION} (${ERTS_VERSION})"
+ "DisplayName" "Erlang OTP ${OTP_RELEASE_VERSION} (${ERTS_VERSION})"
WriteRegStr HKCU \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\Erlang OTP ${OTP_VERSION} (${ERTS_VERSION})" \
"UninstallString" "$INSTDIR\Uninstall.exe"
diff --git a/erts/etc/win32/nsis/find_redist.sh b/erts/etc/win32/nsis/find_redist.sh
index c070ad469a..deb5480e1a 100755
--- a/erts/etc/win32/nsis/find_redist.sh
+++ b/erts/etc/win32/nsis/find_redist.sh
@@ -26,17 +26,9 @@ lookup_prog_in_path ()
save_ifs=$IFS
IFS=:
for p in $PATH; do
- # In cygwin the programs are not always executable and have .exe suffix...
- if [ "X$TARGET" = "Xwin32" ]; then
- if [ -f $p/$PROG.exe ]; then
- echo $p/$PROG
- break;
- fi
- else
- if [ -x $p/$PROG ]; then
- echo $p/$PROG
- break;
- fi
+ if [ -f $p/$PROG.exe ]; then
+ echo $p/$PROG
+ break;
fi
done
IFS=$save_ifs
@@ -89,36 +81,38 @@ add_path_element()
echo "$PA"
}
-
-CLPATH=`lookup_prog_in_path cl`
-
-if [ -z "$CLPATH" ]; then
- echo "Can not locate cl.exe and vcredist_x86/x64.exe - OK if using mingw" >&2
- exit 1
-fi
-
# Look to see if it's 64bit
-XX=`remove_path_element cl "$CLPATH"`
-YY=`remove_path_element amd64 "$XX"`
-if [ "$YY" != "$XX" ]; then
+if [ "$1" = "win64" ]; then
AMD64DIR=true
VCREDIST=vcredist_x64
COMPONENTS="cl amd64 bin vc"
-else
+elif [ "$1" = "win32" ]; then
AMD64DIR=false
VCREDIST=vcredist_x86
COMPONENTS="cl bin vc"
+else
+ echo "TARGET argument should win32 or win64"
+ exit 2
fi
-if [ X"$1" = X"-n" ]; then
- echo $VCREDIST.exe
- exit 0
+if [ x"$VCToolsRedistDir" != x"" ]; then
+ File="$VCToolsRedistDir/$VCREDIST.exe"
+ if [ -r "$File" ]; then
+ echo "$File"
+ exit 0
+ fi
+fi
+
+CLPATH=`lookup_prog_in_path cl`
+if [ -z "$CLPATH" ]; then
+ echo "Can not locate cl.exe and vcredist_x86/x64.exe - OK if using mingw" >&2
+ exit 1
fi
-# echo $CLPATH
+echo $CLPATH
BPATH=$CLPATH
for x in $COMPONENTS; do
- # echo $x
+ #echo $x
NBPATH=`remove_path_element $x "$BPATH"`
if [ "$NBPATH" = "$BPATH" ]; then
echo "Failed to locate $VCREDIST.exe because cl.exe was in an unexpected location" >&2
diff --git a/erts/etc/win32/resource.h b/erts/etc/win32/resource.h
index e59baadb50..3ce4540b1b 100644
--- a/erts/etc/win32/resource.h
+++ b/erts/etc/win32/resource.h
@@ -19,7 +19,7 @@
*/
#define IDR_CONMENU 101
#define IDMENU_STARTLOG 40001
-#define IDMENU_STOPLOG 40002
+#define IDMENU_STOPLOG 40002
#define IDMENU_EXIT 40003
#define IDMENU_COPY 40004
#define IDMENU_PASTE 40005
@@ -28,7 +28,8 @@
#define IDMENU_ABOUT 40008
#define IDMENU_SELALL 40009
#define IDMENU_SELECTBKG 40010
-#define ID_BREAK 40011
-#define ID_VERSIONSTRING 40000
+#define ID_BREAK 40011
+#define ID_ERTS_VERSIONSTRING 40000
+#define ID_OTP_VERSIONSTRING 40012
#define ID_COMBOBOX 3
diff --git a/erts/etc/win32/win_erlexec.c b/erts/etc/win32/win_erlexec.c
index 39dac358e9..defa654ad8 100644
--- a/erts/etc/win32/win_erlexec.c
+++ b/erts/etc/win32/win_erlexec.c
@@ -22,11 +22,6 @@
* Most of this only used when beam is run as a separate process.
*/
-#pragma comment(linker,"/manifestdependency:\"type='win32' "\
- "name='Microsoft.Windows.Common-Controls' "\
- "version='6.0.0.0' processorArchitecture='*' "\
- "publicKeyToken='6595b64144ccf1df' language='*'\"")
-
#include <windows.h>
#include <winuser.h>
#include <wincon.h>
@@ -272,7 +267,7 @@ start_emulator(char* utf8emu, char *utf8start_prog, char** utf8argv, int start_d
*/
if (start_detached) {
- int result;
+ HANDLE result;
int i;
wchar_t *start_prog=NULL;
wchar_t **argv;
@@ -311,17 +306,17 @@ start_emulator(char* utf8emu, char *utf8start_prog, char** utf8argv, int start_d
MessageBoxW(NULL, buffer, L"Start detached",MB_OK);
}
#endif
- result = _wspawnv(_P_DETACH, start_prog, argv);
+ result = (HANDLE) _wspawnv(_P_DETACH, start_prog, argv);
free_fnuttified(utf8argv);
free(start_prog);
- if (result == -1) {
+ if (result == (HANDLE)-1) {
#ifdef ARGS_HARDDEBUG
MessageBox(NULL, "_wspawnv failed","Start detached",MB_OK);
#endif
return 1;
}
- SetPriorityClass((HANDLE) result, GetPriorityClass(GetCurrentProcess()));
+ SetPriorityClass(result, GetPriorityClass(GetCurrentProcess()));
} else {
wchar_t *emu=NULL;
#ifdef LOAD_BEAM_DYNAMICALLY
diff --git a/erts/etc/win32/wsl_tools/SetupWSLcross.bat b/erts/etc/win32/wsl_tools/SetupWSLcross.bat
new file mode 100644
index 0000000000..b6ff78c1e0
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/SetupWSLcross.bat
@@ -0,0 +1,71 @@
+@echo off
+rem Setup MCL and echo the environment
+rem Usage: eval `cmd.exe /c SetupWSLcross.bat x64`
+
+IF "%~1"=="x86" GOTO search
+IF "%~1"=="x64" GOTO search
+
+GOTO badarg
+
+:search
+IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat". (
+ call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" %~1 > nul
+ goto continue
+)
+
+IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvarsall.bat". (
+ call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvarsall.bat" %~1 > nul
+ goto continue
+)
+
+IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat". (
+ call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" %~1 > nul
+ goto continue
+)
+
+IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat". (
+ call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" %~1 > nul
+ goto continue
+)
+
+IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat". (
+ call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %~1 > nul
+ goto continue
+)
+
+IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat". (
+ call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" %~1 > nul
+ goto continue
+)
+
+GOTO no_vcvars
+
+:continue
+
+FOR /F "delims==" %%F IN ('where cl.exe') DO SET _cl_exec_=%%F
+FOR %%F IN ("%_cl_exec_%") DO SET CL_PATH=%%~dpF
+
+FOR /F "delims==" %%F IN ('where rc.exe') DO SET _rc_exec_=%%F
+FOR %%F IN ("%_rc_exec_%") DO SET RC_PATH=%%~dpF
+
+rem Order is important for some unknown reason
+set WSLENV=VCToolsRedistDir/up:CL_PATH/up:RC_PATH/up:LIBPATH/ul:LIB/ul:INCLUDE/ul
+wsl.exe echo INCLUDE=\"$INCLUDE\";
+wsl.exe echo LIB=\"$LIB\";
+wsl.exe echo LIBPATH=\"$LIBPATH\";
+wsl.exe echo VCToolsRedistDir=\"$VCToolsRedistDir\";
+wsl.exe echo PATH=\"$CL_PATH\":\"$RC_PATH\":'$PATH';
+wsl.exe echo WSLENV='$WSLENV:LIBPATH/l:LIB/l:INCLUDE/l';
+rem wsl.exe echo export 'INCLUDE LIB LIBPATH VCToolsRedistDir WSLENV PATH';
+wsl.exe echo "# Eval this file eval \`cmd.exe /c SetupWSLcross.bat\`"
+
+exit
+
+:badarg
+echo "Bad TARGET or not specified: %~1 expected x86 or x64"
+exit
+
+:no_vcvars
+echo "Error: SetupWSLcross.bat: Could not find vcvarsall.bat"
+echo " edit erts/etc/win32/wsl_tools/SetupWSLcross.bat"
+exit
diff --git a/erts/etc/win32/wsl_tools/erl b/erts/etc/win32/wsl_tools/erl
new file mode 100755
index 0000000000..db24f6b4fe
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/erl
@@ -0,0 +1,45 @@
+#! /bin/sh
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+# Note! This shellscript expects to be run in a wsl environment,
+# it converts erlc command lines to native windows erlc commands, which
+# basically means running the command wslpath on whatever is a path...
+
+CMD=""
+for x in "$@"; do
+ case "$x" in
+ -I/*|-o/*)
+ y=`echo $x | sed 's,^-[Io]\(/.*\),\1,g'`;
+ z=`echo $x | sed 's,^-\([Io]\)\(/.*\),\1,g'`;
+ MPATH=`wslpath -m $y`;
+ CMD="$CMD -$z\"$MPATH\"";;
+ /*)
+ MPATH=`wslpath -m $x`;
+ CMD="$CMD \"$MPATH\"";;
+ *)
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD \"$y\"";;
+ esac
+done
+ERL_TOP=`wslpath -m $ERL_TOP`
+WSLENV="ERL_TOP/w:$WSLENV"
+export WSLENV
+export ERL_TOP
+eval erl.exe $CMD
diff --git a/erts/etc/win32/wsl_tools/erlc b/erts/etc/win32/wsl_tools/erlc
new file mode 100755
index 0000000000..bca537f751
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/erlc
@@ -0,0 +1,60 @@
+#! /bin/sh
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+
+
+CMD=""
+ECHO_ONLY=false
+for x in "$@"; do
+ case "$x" in
+ --echo_only)
+ ECHO_ONLY=true;;
+ -I/*|-o/*)
+ y=`echo $x | sed 's,^-[Io]\(/.*\),\1,g'`;
+ z=`echo $x | sed 's,^-\([Io]\)\(/.*\),\1,g'`;
+ MPATH=`w32_path.sh -m $y`;
+ CMD="$CMD -$z$MPATH";;
+ -pa/*)
+ y=`echo $x | sed 's,^-pa\(/.*\),\1,g'`;
+ MPATH=`w32_path.sh -m $y`;
+ CMD="$CMD -pa $MPATH";;
+ /*)
+ MPATH=`w32_path.sh -m $x`;
+ CMD="$CMD \"$MPATH\"";;
+# Needed for +'{preproc_flags,whatever}'
+ +{preproc_flags,*})
+ y=`echo $x | sed 's,^+{preproc_flags\,"\(.*\)"},\1,g'`;
+ z=`eval $0 --echo_only $y`;
+ case "$z" in # Dont "doubledoublequote"
+ \"*\")
+ CMD="$CMD +'{preproc_flags,$z}'";;
+ *)
+ CMD="$CMD +'{preproc_flags,\"$z\"}'";;
+ esac;;
+ *)
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD \"$y\"";;
+ esac
+done
+if [ $ECHO_ONLY = true ]; then
+ echo $CMD
+else
+ eval erlc.exe $CMD
+fi
diff --git a/erts/etc/win32/wsl_tools/javac.sh b/erts/etc/win32/wsl_tools/javac.sh
new file mode 100755
index 0000000000..b1f142adfd
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/javac.sh
@@ -0,0 +1,53 @@
+#! /bin/sh
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+# Note! This shellscript expects to be run in a WSL environment,
+# it converts erlc command lines to native windows erlc commands, which
+# basically means running the command wslpath on whatever is a path...
+
+CMD=""
+
+SAVE="$@"
+while test -n "$1" ; do
+ x="$1"
+ case "$x" in
+ -I/*|-o/*|-d/*)
+ y=`echo $x | sed 's,^-[Iod]\(/.*\),\1,g'`;
+ z=`echo $x | sed 's,^-\([Iod]\)\(/.*\),\1,g'`;
+ #echo "Foooo:$z"
+ MPATH=`wslpath -m $y`;
+ CMD="$CMD -$z\"$MPATH\"";;
+ -d|-I|-o)
+ shift;
+ MPATH=`wslpath -m $1`;
+ CMD="$CMD $x $MPATH";;
+ /*)
+ #echo "absolute:"$x;
+ MPATH=`wslpath -m $x`;
+ CMD="$CMD \"$MPATH\"";;
+ *)
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD \"$y\"";;
+ esac
+ shift
+done
+#echo javac.exe "$CMD"
+export WSLENV=CLASSPATH/l
+eval javac.exe "$CMD"
diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.first b/erts/etc/win32/wsl_tools/make_bootstrap_ini.sh
index 0ea872ef49..c33d328ea0 100644..100755
--- a/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.first
+++ b/erts/etc/win32/wsl_tools/make_bootstrap_ini.sh
@@ -1,8 +1,9 @@
+#! /bin/bash
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2000-2016. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2003-2016. All Rights Reserved.
+#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@@ -14,9 +15,29 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
+#
# %CopyrightEnd%
#
+# Create a local init-file for erlang in the build environment.
+if [ -z "$1" ]; then
+ echo "error: $0: No rootdir given"
+ exit 1
+else
+ RDIR=$1
+fi
+if [ -z "$2" ]; then
+ echo "error: $0: No bindir given"
+ exit 1
+else
+ BDIR=$2
+fi
+
+DRDIR=`w32_path.sh -d $RDIR`
+DBDIR=`w32_path.sh -d $BDIR`
-eterm_test_decl.c: eterm_test.c
- erl -noinput -pa ../all_SUITE_data -s init_tc run eterm_test -s erlang halt
+cat > $RDIR/bin/erl.ini <<EOF
+[erlang]
+Bindir=$DBDIR
+Progname=erl
+Rootdir=$DRDIR
+EOF
diff --git a/erts/doc/Makefile b/erts/etc/win32/wsl_tools/make_local_ini.sh
index f26a43592e..85ec4e40aa 100644..100755
--- a/erts/doc/Makefile
+++ b/erts/etc/win32/wsl_tools/make_local_ini.sh
@@ -1,8 +1,9 @@
-#
+#! /bin/bash
+#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1996-2016. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2003-2016. All Rights Reserved.
+#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@@ -14,24 +15,26 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-# %CopyrightEnd%
-#
-
-#
-# Default Rules
#
-OTP_MAKE_ROOT=/home/super/otp/otp_make
-include $(OTP_MAKE_ROOT)/otp.mk
-
-#
-# Macros
+# %CopyrightEnd%
#
-SUB_DIRECTORIES = src
+# Create a local init-file for erlang in the build environment.
+if [ -z "$1" ]; then
+ if [ -z $ERL_TOP ]; then
+ echo "error: $0: No rootdir available"
+ exit 1
+ else
+ RDIR=$ERL_TOP
+ fi
+else
+ RDIR=$1
+fi
-SPECIAL_TARGETS =
+DDIR=`w32_path.sh -d $RDIR`
-#
-# Default Subdir Targets
-#
-include $(OTP_MAKE_ROOT)/otp_subdir.mk
+cat > $RDIR/bin/erl.ini <<EOF
+[erlang]
+Bindir=$DDIR\\\\bin\\\\win32
+Progname=erl
+Rootdir=$DDIR
+EOF
diff --git a/erts/etc/win32/wsl_tools/reg_query.sh b/erts/etc/win32/wsl_tools/reg_query.sh
new file mode 100755
index 0000000000..c05f00dfa1
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/reg_query.sh
@@ -0,0 +1,19 @@
+#! /bin/sh
+
+mkdir -p $ERL_TOP/tmp
+BAT_FILE=$ERL_TOP/tmp/w$$.bat
+if [ -z "$1" -o -z "$2" ]; then
+ echo "Usage:" "$0" '<key> <valuename>'
+ exit 1
+fi
+BACKED=`echo "$1" | sed 's,/,\\\\,g'`
+
+if [ $CONFIG_SUBTYPE = "win64" ]; then
+ REG_OPT=" /reg:64"
+else
+ REG_OPT=" /reg:32"
+fi
+
+WIN_BAT_FILE=`w32_path.sh -w $BAT_FILE`
+RESULT=`reg.exe query "$BACKED" /v "$2" $REG_OPT | sed 's@\\\@/@g' | tr -d '\r\n'`
+echo "$RESULT" | sed "s,.*REG_[^ ]* *,,g"
diff --git a/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.first b/erts/etc/win32/wsl_tools/vc/ar.sh
index 9ca8f7f8a3..4d3b8ffdb5 100644..100755
--- a/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.first
+++ b/erts/etc/win32/wsl_tools/vc/ar.sh
@@ -1,8 +1,9 @@
+#! /bin/sh
#
# %CopyrightBegin%
-#
+#
# Copyright Ericsson AB 2002-2016. All Rights Reserved.
-#
+#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@@ -14,9 +15,34 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
+#
# %CopyrightEnd%
#
+CMD=""
+while test -n "$1" ; do
+ x="$1"
+ case "$x" in
+ -out:)
+ shift
+ case "$1" in
+ /*)
+ MPATH=`w32_path.sh -d $1`;;
+ *)
+ MPATH=$1;;
+ esac
+ CMD="$CMD -out:\"$MPATH\"";;
+ -out:/*)
+ y=`echo $x | sed 's,^-out:\(/.*\),\1,g'`;
+ MPATH=`w32_path.sh -d $y`;
+ CMD="$CMD -out:\"$MPATH\"";;
+ /*)
+ MPATH=`w32_path.sh -d $x`;
+ CMD="$CMD \"$MPATH\"";;
+ *)
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD \"$y\"";;
+ esac
+ shift
+done
-ext_test_decl.c: ext_test.c
- erl -noinput -pa ../all_SUITE_data -s init_tc run ext_test -s erlang halt
+eval lib.exe /nologo $CMD
diff --git a/erts/etc/win32/wsl_tools/vc/cc.sh b/erts/etc/win32/wsl_tools/vc/cc.sh
new file mode 100755
index 0000000000..036e00681c
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/vc/cc.sh
@@ -0,0 +1,382 @@
+#! /bin/sh
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+# Icky cl wrapper that does it's best to behave like a Unixish cc.
+# Made to work for Erlang builds and to make configure happy, not really
+# general I suspect.
+# set -x
+# Save the command line for debug outputs
+
+SAVE="$@"
+
+# Constants
+COMMON_CFLAGS="-nologo -D__WIN32__ -DWIN32 -DWINDOWS -D_WIN32 -DNT -D_CRT_SECURE_NO_DEPRECATE"
+
+# Variables
+# The stdout and stderr for the compiler
+MSG_FILE=/tmp/cl.exe.$$.1
+ERR_FILE=/tmp/cl.exe.$$.2
+
+# "Booleans" determined during "command line parsing"
+# If the stdlib option is explicitly passed to this program
+MD_FORCED=false
+# If we're preprocession (only) i.e. -E
+PREPROCESSING=false
+# If we're generating dependencies (implies preprocesing)
+DEPENDENCIES=false
+# If this is supposed to be a debug build
+DEBUG_BUILD=false
+# If this is supposed to be an optimized build (there can only be one...)
+OPTIMIZED_BUILD=false
+# If we're linking or only compiling
+LINKING=true
+
+# This data is accumulated during command line "parsing"
+# The stdlibrary option, default multithreaded dynamic
+MD=-MD
+# Flags for debug compilation
+DEBUG_FLAGS=""
+# Flags for optimization
+OPTIMIZE_FLAGS=""
+# The specified output filename (if any), may be either object or exe.
+OUTFILE=""
+# Unspecified command line options for the compiler
+CMD=""
+# All the c source files, in unix style
+SOURCES=""
+# All the options to pass to the linker, kept in Unix style
+LINKCMD=""
+
+
+# Loop through the parameters and set the above variables accordingly
+# Also convert some filenames to "windows style"
+# except for anything passed to the linker, that script
+# handles those and the sources, which are also kept unixish for now
+
+# If we are in "unix" directory we can't use relative paths
+# since cl.exe can't find that path
+WINCHECK=`w32_path.sh -m $PWD`
+case $WINCHECK in
+ //wsl$/*)
+ USEABSPATH=true
+ ;;
+ *)
+ USEABSPATH=false
+ ;;
+esac
+
+while test -n "$1" ; do
+ x="$1"
+ case "$x" in
+ -Wall)
+ ;;
+ -c)
+ LINKING=false;;
+ #CMD="$CMD -c";;
+ -MM)
+ PREPROCESSING=true;
+ LINKING=false;
+ DEPENDENCIES=true;;
+ -E)
+ PREPROCESSING=true;
+ LINKING=false;; # Obviously...
+ #CMD="$CMD -E";;
+ -Owx)
+ # Optimization hardcoded of wxErlang
+ OPTIMIZE_FLAGS="-Ob2ity -Gs -Z7";
+ DEBUG_FLAGS="";
+ DEBUG_BUILD=false;
+ if [ $MD_FORCED = false ]; then
+ MD=-MD;
+ fi
+ OPTIMIZED_BUILD=true;;
+ -O*)
+ # Optimization hardcoded
+ OPTIMIZE_FLAGS="-Ox -Z7";
+ DEBUG_FLAGS="";
+ DEBUG_BUILD=false;
+ if [ $MD_FORCED = false ]; then
+ MD=-MD;
+ fi
+ OPTIMIZED_BUILD=true;;
+ -g|-ggdb)
+ if [ $OPTIMIZED_BUILD = false ];then
+ # Hardcoded windows debug flags
+ DEBUG_FLAGS="-Z7";
+ if [ $MD_FORCED = false ]; then
+ MD=-MDd;
+ fi
+ LINKCMD="$LINKCMD -g";
+ DEBUG_BUILD=true;
+ fi;;
+ # Allow forcing of stdlib
+ -mt|-MT)
+ MD="-MT";
+ MD_FORCED=true;;
+ -md|-MD)
+ MD="-MD";
+ MD_FORCED=true;;
+ -ml|-ML)
+ MD="-ML";
+ MD_FORCED=true;;
+ -mdd|-MDD|-MDd)
+ MD="-MDd";
+ MD_FORCED=true;;
+ -mtd|-MTD|-MTd)
+ MD="-MTd";
+ MD_FORCED=true;;
+ -mld|-MLD|-MLd)
+ MD="-MLd";
+ MD_FORCED=true;;
+ -o)
+ shift;
+ OUTFILE="$1";;
+ -o*)
+ y=`echo $x | sed 's,^-[Io]\(.*\),\1,g'`;
+ OUTFILE="$y";;
+ -I/*)
+ y=`echo $x | sed 's,^-[Io]\(/.*\),\1,g'`;
+ z=`echo $x | sed 's,^-\([Io]\)\(/.*\),\1,g'`;
+ MPATH=`w32_path.sh -d "$y"`;
+ CMD="$CMD -$z\"$MPATH\"";;
+ -I\"/*)
+ y=`echo $x | sed 's,^\"-[Io]\(/.*\)\",\1,g'`;
+ z=`echo $x | sed 's,^\"-\([Io]\)\(/.*\)\",\1,g'`;
+ MPATH=`w32_path.sh -d "$y"`;
+ CMD="$CMD -$z\"$MPATH\"";;
+ -I*)
+ if [ $USEABSPATH = true ]; then
+ y=`echo $x | sed 's,^-[Io]\(.*\),\1,g'`;
+ z=`echo $x | sed 's,^-\([Io]\)\(.*\),\1,g'`;
+ MPATH=`w32_path.sh -a -d "$y"`;
+ CMD="$CMD -$z$MPATH";
+ else
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD $y"
+ fi;;
+ -D*)
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD $y";;
+ -EH*)
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD $y";;
+ -TP|-Tp)
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD $y";;
+ -l*)
+ y=`echo $x | sed 's,^-l\(.*\),\1,g'`;
+ LINKCMD="$LINKCMD $x";;
+ /*.c)
+ SOURCES="$SOURCES $x";;
+ *.c)
+ SOURCES="$SOURCES $x";;
+ /*.cc)
+ SOURCES="$SOURCES $x";;
+ *.cc)
+ SOURCES="$SOURCES $x";;
+ /*.cpp)
+ SOURCES="$SOURCES $x";;
+ *.cpp)
+ SOURCES="$SOURCES $x";;
+ /*.o)
+ LINKCMD="$LINKCMD $x";;
+ *.o)
+ LINKCMD="$LINKCMD $x";;
+ *)
+ # Try to quote uninterpreted options
+ y=`echo $x | sed 's,",\\\",g'`;
+ LINKCMD="$LINKCMD $y";;
+ esac
+ shift
+done
+
+#Return code from compiler, linker.sh and finally this script...
+RES=0
+
+# Accumulated object names
+ACCUM_OBJECTS=""
+
+# A temporary object file location
+TMPOBJDIR=$ERL_TOP/tmpobj$$
+mkdir $TMPOBJDIR
+
+WINTMPDIR=`w32_path.sh -w $TMPOBJDIR`
+
+# Sometimes the file server doesn't keep up (paralell file creation)
+while true ; do
+ DIR_EXISTS=$(cd /mnt/c; cmd.exe /C "IF EXIST $WINTMPDIR (echo yes) ELSE (echo no)")
+ case $DIR_EXISTS in # Contains trash in the end of string
+ yes*)
+ break
+ ;;
+ *)
+ if [ "X$CC_SH_DEBUG_LOG" != "X" ]; then
+ echo "sleep 1" >> $CC_SH_DEBUG_LOG
+ fi;
+ echo sleep $WINTMPDIR does not exist >&2
+ sleep 1
+ esac
+done
+
+# Compile
+for x in $SOURCES; do
+ # Compile each source
+ if [ $LINKING = false ]; then
+ # We should have an output defined, which is a directory
+ # or an object file
+ case $OUTFILE in
+ /*.o)
+ # Simple output, SOURCES should be one single
+ n=`echo $SOURCES | wc -w`;
+ if [ $n -gt 1 ]; then
+ echo "cc.sh:Error, multiple sources, one object output.";
+ exit 1;
+ else
+ output_filename=`echo $OUTFILE`;
+ fi;;
+ *.o)
+ # Relative path needs no translation
+ n=`echo $SOURCES | wc -w`
+ if [ $n -gt 1 ]; then
+ echo "cc.sh:Error, multiple sources, one object output."
+ exit 1
+ else
+ output_filename=$OUTFILE
+ fi;;
+ /*)
+ # Absolute directory
+ o=`echo $x | sed 's,.*/,,' | sed 's,\.c$,.o,'`
+ output_filename=`echo $OUTFILE`
+ output_filename="$output_filename/${o}";;
+ *)
+ # Relative_directory or empty string (.//x.o is valid)
+ o=`echo $x | sed 's,.*/,,' | sed 's,\.cp*$,.o,'`
+ output_filename="./${OUTFILE}/${o}";;
+ esac
+ else
+ # We are linking, which means we build objects in a temporary
+ # directory and link from there. We should retain the basename
+ # of each source to make examining the exe easier...
+ o=`echo $x | sed 's,.*/,,' | sed 's,\.c$,.o,'`
+ output_filename=$TMPOBJDIR/$o
+ ACCUM_OBJECTS="$ACCUM_OBJECTS $output_filename"
+ fi
+ # Now we know enough, lets try a compilation...
+ if [ $USEABSPATH = true ]; then
+ MPATH=`w32_path.sh -a -d $x`
+ else
+ MPATH=`w32_path.sh -d $x`
+ fi
+ if [ $PREPROCESSING = true ]; then
+ output_flag="-E"
+ else
+ output_flag="/FS -c -Fo`w32_path.sh -a -d ${output_filename}`"
+ fi
+ params="$COMMON_CFLAGS $MD $DEBUG_FLAGS $OPTIMIZE_FLAGS \
+ $CMD ${output_flag} $MPATH"
+ if [ "X$CC_SH_DEBUG_LOG" != "X" ]; then
+ echo cc.sh "$SAVE" >>$CC_SH_DEBUG_LOG
+ echo cl.exe $params >>$CC_SH_DEBUG_LOG
+ fi
+ eval cl.exe $params >$MSG_FILE 2>$ERR_FILE
+ RES=$?
+ if test $PREPROCESSING = false; then
+ cat $ERR_FILE >&2
+ tail -n +2 $MSG_FILE
+ else
+ tail -n +2 $ERR_FILE >&2
+ if test $DEPENDENCIES = true; then
+ perl -e '
+my $file = "'$x'";
+while (<>) {
+ next unless /^#line/;
+ next if /$file/o;
+ (undef,$_) = split(/\"/);
+ next if / /;
+ $all{$_} = 1;
+}
+foreach (sort keys %all) {
+ my $w_file;
+ ($w_file) = split("\n",`(w32_path.sh -u $_)`);
+ push @f, "\\\n $w_file ";
+}
+if (@f) {
+ my $oname = $file;
+ $oname =~ s@.*/@@;
+ $oname =~ s@[.]cp*@.o@;
+ print $oname, ":", @f;
+ print "\n\n";
+ print STDERR "Made dependencies for $file\n";
+}' $MSG_FILE
+ else
+ cat $MSG_FILE
+ fi
+ fi
+ rm -f $ERR_FILE $MSG_FILE
+ if [ $RES != 0 ]; then
+ echo Failed: cl.exe $params
+ rm -rf $TMPOBJDIR
+ exit $RES
+ fi
+done
+
+# If we got here, we succeeded in compiling (if there were anything to compile)
+# The output filename should name an executable if we're linking
+if [ $LINKING = true ]; then
+ case $OUTFILE in
+ "")
+ # Use the first source name to name the executable
+ first_source=""
+ for x in $SOURCES; do first_source=$x; break; done;
+ if [ -n "$first_source" ]; then
+ e=`echo $x | sed 's,.*/,,' | sed 's,\.c$,.exe,'`;
+ out_spec="-o $e";
+ else
+ out_spec="";
+ fi;;
+ *)
+ out_spec="-o $OUTFILE";;
+ esac
+ # Descide which standard library to link against
+ case $MD in
+ -ML)
+ stdlib="-lLIBC";;
+ -MLd)
+ stdlib="-lLIBCD";;
+ -MD)
+ stdlib="-lMSVCRT";;
+ -MDd)
+ stdlib="-lMSVCRTD";;
+ -MT)
+ stdlib="-lLIBCMT";;
+ -MTd)
+ stdlib="-lLIBMTD";;
+ esac
+ # And finally call the next script to do the linking...
+ params="$out_spec $LINKCMD $stdlib"
+ if [ "X$CC_SH_DEBUG_LOG" != "X" ]; then
+ echo ld.sh $ACCUM_OBJECTS $params >>$CC_SH_DEBUG_LOG
+ fi
+ eval ld.sh $ACCUM_OBJECTS $params
+ RES=$?
+fi
+rm -rf $TMPOBJDIR
+
+exit $RES
diff --git a/erts/etc/win32/wsl_tools/vc/coffix.c b/erts/etc/win32/wsl_tools/vc/coffix.c
new file mode 100755
index 0000000000..7428f9cd41
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/vc/coffix.c
@@ -0,0 +1,161 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1999-2016. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+/*
+** This mini tool fixes an incompatibility between
+** Microsoft's tools, who dont like the virtual size being put in
+** the physical address field, but rely on the raw size field for
+** sizing the ".bss" section.
+** This fixes some of the problems with linking gcc compiled objects
+** together with MSVC dito.
+**
+** Courtesy DJ Delorie for describing the COFF file format on
+** http://www.delorie.com/djgpp/doc/coff/
+** The coff structures are fetched from Microsofts headers though.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <windows.h>
+#include <winnt.h> /* Structure definitions for PE (COFF) */
+
+static int dump_edit(char *filename, int edit);
+static int v_printf(char *format, ...);
+
+
+char *progname;
+int verbouse = 0;
+
+int main(int argc, char **argv)
+{
+ int findex = 1;
+ int edit = 0;
+ int ret;
+
+ progname = argv[0];
+ if (argc == 1) {
+ fprintf(stderr,"Format : %s [-e] [-v] <filename>\n", progname);
+ return 1;
+ }
+ for (findex = 1;
+ findex < argc && (*argv[findex] == '-' || *argv[findex] == '/');
+ ++findex)
+ switch (argv[findex][1]) {
+ case 'e':
+ case 'E':
+ edit = 1;
+ break;
+ case 'v':
+ case 'V':
+ verbouse = 1;
+ default:
+ fprintf(stderr, "%s: unknown option %s\n", progname, argv[findex]);
+ break;
+ }
+ if (findex == argc) {
+ fprintf(stderr,"%s: No filenames given.\n", progname);
+ return 1;
+ }
+ for(; findex < argc; ++findex)
+ if ((ret = dump_edit(argv[findex],edit)) != 0)
+ return ret;
+ return 0;
+}
+
+int dump_edit(char *filename, int edit)
+{
+ FILE *f = fopen(filename, (edit) ? "r+b" : "rb");
+ IMAGE_FILE_HEADER filhdr;
+ IMAGE_SECTION_HEADER scnhdr;
+ int i;
+
+ if (f == NULL) {
+ fprintf(stderr, "%s: cannot open %s.\n", progname, filename);
+ return 1;
+ }
+
+ if (fread(&filhdr, sizeof(filhdr), 1, f) == 0) {
+ fprintf(stderr,"%s: Could not read COFF header from %s,"
+ " is this a PE (COFF) file?\n", progname, filename);
+ fclose(f);
+ return 1;
+ }
+ v_printf("File: %s\n", filename);
+ v_printf("Magic number: 0x%08x\n", filhdr.Machine);
+ v_printf("Number of sections: %d\n",filhdr.NumberOfSections);
+
+ if (fseek(f, (long) filhdr.SizeOfOptionalHeader, SEEK_CUR) != 0) {
+ fprintf(stderr,"%s: Could not read COFF optional header from %s,"
+ " is this a PE (COFF) file?\n", progname, filename);
+ fclose(f);
+ return 1;
+ }
+
+ for (i = 0; i < filhdr.NumberOfSections; ++i) {
+ if (fread(&scnhdr, sizeof(scnhdr), 1, f) == 0) {
+ fprintf(stderr,"%s: Could not read section header from %s,"
+ " is this a PE (COFF) file?\n", progname, filename);
+ fclose(f);
+ return 1;
+ }
+ v_printf("Section %s:\n", scnhdr.Name);
+ v_printf("Physical address: 0x%08x\n", scnhdr.Misc.PhysicalAddress);
+ v_printf("Size: 0x%08x\n", scnhdr.SizeOfRawData);
+ if (scnhdr.Misc.PhysicalAddress != 0 &&
+ scnhdr.SizeOfRawData == 0) {
+ printf("Section header %s in file %s will confuse MSC linker, "
+ "virtual size is 0x%08x and raw size is 0\n",
+ scnhdr.Name, filename, scnhdr.Misc.PhysicalAddress,
+ scnhdr.SizeOfRawData);
+ if (edit) {
+ scnhdr.SizeOfRawData = scnhdr.Misc.PhysicalAddress;
+ scnhdr.Misc.PhysicalAddress = 0;
+ if (fseek(f, (long) -((long)sizeof(scnhdr)), SEEK_CUR) != 0 ||
+ fwrite(&scnhdr, sizeof(scnhdr), 1, f) == 0) {
+ fprintf(stderr,"%s: could not edit file %s.\n",
+ progname, filename);
+ fclose(f);
+ return 1;
+ }
+ printf("Edited object, virtual size is now 0, and "
+ "raw size is 0x%08x.\n", scnhdr.SizeOfRawData);
+ } else {
+ printf("Specify option '-e' to correct the problem.\n");
+ }
+ }
+ }
+ fclose(f);
+ return 0;
+}
+
+
+static int v_printf(char *format, ...)
+{
+ va_list ap;
+ int ret = 0;
+ if (verbouse) {
+ va_start(ap, format);
+ ret = vfprintf(stdout, format, ap);
+ va_end(ap);
+ }
+ return ret;
+}
diff --git a/erts/etc/win32/wsl_tools/vc/emu_cc.sh b/erts/etc/win32/wsl_tools/vc/emu_cc.sh
new file mode 100755
index 0000000000..00b8555d2b
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/vc/emu_cc.sh
@@ -0,0 +1,100 @@
+#! /bin/sh
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+
+# sudo apt-get install gcc-mingw-w64 on (wsl ubuntu)
+
+if [ X"$CONFIG_SUBTYPE" = X"win64" ]; then
+ GCC="x86_64-w64-mingw32-gcc -m64"
+else
+ GCC="x86_64-w64-mingw32-gcc -m32"
+fi
+TOOLDIR=$ERL_TOP/erts/etc/win32/wsl_tools/vc
+COFFIX=$TOOLDIR/coffix
+WTOOLDIR=`w32_path.sh -d "$TOOLDIR"`
+# Do primitive 'make'
+newer_exe=`find $TOOLDIR -newer $COFFIX.c -name coffix.exe -print`
+
+if [ -z $newer_exe ]; then
+ echo recompiling $COFFIX.exe
+ cl.exe -Fe${WTOOLDIR}/coffix.exe ${WTOOLDIR}/coffix.c
+ rm -f $COFFIX.obj coffix.obj $COFFIX.pdb coffix.pdb
+fi
+
+# Try to find out the output filename and remove it from command line
+CMD=""
+OUTFILE=""
+INFILE=""
+SKIP_COFFIX=false
+while test -n "$1" ; do
+ x="$1"
+ case "$x" in
+ -o/*)
+ OUTFILE=`echo $x | sed 's,^-[Io]\(/.*\),\1,g'`;;
+ -o)
+ shift
+ OUTFILE=$1;;
+ -MM)
+ SKIP_COFFIX=true
+ CMD="$CMD \"$x\"";;
+ *.c)
+ INFILE="$INFILE $x";
+ CMD="$CMD \"$x\"";;
+ *)
+ CMD="$CMD \"$x\"";;
+ esac
+ shift
+done
+if [ -z "$INFILE" ]; then
+ echo 'emu_cc.sh: please give an input filename for the compiler' >&2
+ exit 1
+fi
+if [ -z "$OUTFILE" ]; then
+ OUTFILE=`echo $INFILE | sed 's,\.c$,.o,'`
+fi
+
+if [ $SKIP_COFFIX = false ]; then
+ n=`echo $INFILE | wc -w`;
+ if [ $n -gt 1 ]; then
+ echo "emu_cc.sh:Error, multiple sources, one object output.";
+ exit 1;
+ fi
+ mkdir -p $ERL_TOP/tmp
+ TEMPFILE=$ERL_TOP/tmp/tmp_emu_cc$$.o
+ if [ "X$EMU_CC_SH_DEBUG_LOG" != "X" ]; then
+ echo "$GCC -o $TEMPFILE -D__WIN32__ -DWIN32 -DWINDOWS -fomit-frame-pointer $CMD" >> $EMU_CC_SH_DEBUG_LOG 2>&1
+ fi
+ eval $GCC -o $TEMPFILE -D__WIN32__ -DWIN32 -DWINDOWS -fomit-frame-pointer $CMD
+ RES=$?
+ if [ $RES = 0 ]; then
+ $COFFIX.exe -e `w32_path.sh -w $TEMPFILE`
+ RES=$?
+ if [ $RES = 0 ]; then
+ cp $TEMPFILE $OUTFILE
+ else
+ echo "emu_cc.sh: fatal: coffix failed!" >&2
+ fi
+ fi
+ rm -f $TEMPFILE
+ exit $RES
+else
+ eval $GCC -D__WIN32__ -DWIN32 -DWINDOWS -fomit-frame-pointer -fno-tree-copyrename $CMD 2>/dev/null
+ exit $?
+fi
diff --git a/erts/etc/win32/wsl_tools/vc/ld.sh b/erts/etc/win32/wsl_tools/vc/ld.sh
new file mode 100755
index 0000000000..a16c502cea
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/vc/ld.sh
@@ -0,0 +1,214 @@
+#! /bin/sh
+# set -x
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+# Save the command line for debug outputs
+
+SAVE="$@"
+kernel_libs="kernel32.lib advapi32.lib"
+gdi_libs="gdi32.lib user32.lib comctl32.lib comdlg32.lib shell32.lib"
+DEFAULT_LIBRARIES="$kernel_libs $gdi_libs"
+
+CMD=""
+STDLIB=MSVCRT.LIB
+DEBUG_BUILD=false
+STDLIB_FORCED=false
+BUILD_DLL=false
+OUTPUT_FILENAME=""
+
+while test -n "$1" ; do
+ x="$1"
+ case "$x" in
+ -dll| -DLL)
+ BUILD_DLL=true;;
+ -L/*|-L.*)
+ y=`echo $x | sed 's,^-L\(.*\),\1,g'`;
+ MPATH=`w32_path.sh -d $y`;
+ CMD="$CMD -libpath:\"$MPATH\"";;
+ -lMSVCRT|-lmsvcrt)
+ STDLIB_FORCED=true;
+ STDLIB=MSVCRT.LIB;;
+ -lMSVCRTD|-lmsvcrtd)
+ STDLIB_FORCED=true;
+ STDLIB=MSVCRTD.LIB;;
+ -lLIBCMT|-llibcmt)
+ STDLIB_FORCED=true;
+ STDLIB=LIBCMT.LIB;;
+ -lLIBCMTD|-llibcmtd)
+ STDLIB_FORCED=true;
+ STDLIB=LIBCMTD.LIB;;
+ -lsocket)
+ DEFAULT_LIBRARIES="$DEFAULT_LIBRARIES WS2_32.LIB IPHLPAPI.LIB";;
+ -l*)
+ y=`echo $x | sed 's,^-l\(.*\),\1,g'`;
+ MPATH=`w32_path.sh -d $y`;
+ CMD="$CMD \"${MPATH}.lib\"";;
+ -g)
+ DEBUG_BUILD=true;;
+ -pdb:none|-incremental:no)
+ ;;
+ -implib:*)
+ y=`echo $x | sed 's,^-implib:\(.*\),\1,g'`;
+ MPATH=`w32_path.sh -d $y`;
+ CMD="$CMD -implib:\"${MPATH}\"";;
+ -def:*)
+ y=`echo $x | sed 's,^-def:\(.*\),\1,g'`;
+ MPATH=`w32_path.sh -d $y`;
+ CMD="$CMD -def:\"${MPATH}\"";;
+ -o)
+ shift
+ MPATH=`w32_path.sh -a -d $1`;
+ OUTPUT_FILENAME="$MPATH";;
+ -o/*)
+ y=`echo $x | sed 's,^-[Io]\(/.*\),\1,g'`;
+ MPATH=`w32_path.sh -a -d $y`;
+ OUTPUT_FILENAME="$MPATH";;
+ /*)
+ MPATH=`w32_path.sh -d $x`;
+ CMD="$CMD \"$MPATH\"";;
+ *)
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD \"$y\"";;
+ esac
+ shift
+done
+if [ $DEBUG_BUILD = true ]; then
+ linktype="-debug -pdb:none"
+ if [ $STDLIB_FORCED = false ]; then
+ STDLIB=MSVCRTD.LIB
+ fi
+fi
+# Generate a PDB
+linkadd_pdb=""
+case "$OUTPUT_FILENAME" in
+ *.exe|*.EXE)
+ fn=`echo "$OUTPUT_FILENAME" | sed 's,[eE][xX][eE]$,,g'`;
+ # fn=`w32_path.sh -a -d $fn0`
+ # echo EXE "$OUTPUT_FILENAME" $fn
+ linkadd_pdb="-pdb:\"${fn}pdb\"";;
+ *.dll|*.DLL)
+ fn=`echo "$OUTPUT_FILENAME" | sed 's,[dD][lL][lL]$,,g'`;
+ # fn=`w32_path.sh -a -d $fn0`
+ # echo DLL "$OUTPUT_FILENAME" $fn
+ linkadd_pdb="-pdb:\"${fn}pdb\"";;
+ "")
+ linkadd_pdb="-pdb:\"a.pdb\"";;
+ *)
+ fn="$OUTPUT_FILENAME"
+ # fn=`w32_path.sh -a -d $OUTPUT_FILENAME`
+ # echo * "$OUTPUT_FILENAME" $fn
+ linkadd_pdb="-pdb:\"${fn}.pdb\"";;
+esac
+
+linktype="-debug $linkadd_pdb"
+
+CHMOD_FILE=""
+
+if [ $BUILD_DLL = true ];then
+ case "$OUTPUT_FILENAME" in
+ *.exe|*.EXE)
+ echo "Warning, output set to .exe when building DLL" >&2
+ CHMOD_FILE="$OUTPUT_FILENAME";
+ CMD="-dll -out:\"$OUTPUT_FILENAME\" $CMD";
+ OUTPUTRES="${OUTPUT_FILENAME}\;2";
+ MANIFEST="${OUTPUT_FILENAME}.manifest";;
+ *.dll|*.DLL)
+ CMD="-dll -out:\"$OUTPUT_FILENAME\" $CMD";
+ OUTPUTRES="${OUTPUT_FILENAME}\;2";
+ MANIFEST="${OUTPUT_FILENAME}.manifest";;
+ "")
+ CMD="-dll -out:\"a.dll\" $CMD";
+ OUTPUTRES="a.dll\;2";
+ MANIFEST="a.dll.manifest";;
+ *)
+ CMD="-dll -out:\"${OUTPUT_FILENAME}.dll\" $CMD";
+ OUTPUTRES="${OUTPUT_FILENAME}.dll\;2";
+ MANIFEST="${OUTPUT_FILENAME}.dll.manifest";;
+ esac
+else
+ case "$OUTPUT_FILENAME" in
+ *.exe|*.EXE)
+ CHMOD_FILE="$OUTPUT_FILENAME";
+ CMD="-out:\"$OUTPUT_FILENAME\" $CMD";
+ OUTPUTRES="${OUTPUT_FILENAME}\;1"
+ MANIFEST="${OUTPUT_FILENAME}.manifest";;
+ *.dll|*.DLL)
+ echo "Warning, output set to .dll when building EXE" >&2
+ CMD="-out:\"$OUTPUT_FILENAME\" $CMD";
+ OUTPUTRES="${OUTPUT_FILENAME}\;1";
+ MANIFEST="${OUTPUT_FILENAME}.manifest";;
+ "")
+ CHMOD_FILE="a.exe";
+ CMD="-out:\"a.exe\" $CMD";
+ OUTPUTRES="a.exe\;1";
+ MANIFEST="a.exe.manifest";;
+ *)
+ CMD="-out:\"${OUTPUT_FILENAME}.exe\" $CMD";
+ OUTPUTRES="${OUTPUT_FILENAME}.exe\;1";
+ MANIFEST="${OUTPUT_FILENAME}.exe.manifest";;
+ esac
+fi
+
+p=$$
+CMD="$linktype -nologo -incremental:no $CMD $STDLIB $DEFAULT_LIBRARIES"
+if [ "X$LD_SH_DEBUG_LOG" != "X" ]; then
+ echo ld.sh "$SAVE" >>$LD_SH_DEBUG_LOG
+ echo link.exe $CMD >>$LD_SH_DEBUG_LOG
+fi
+eval link.exe "$CMD" >/tmp/link.exe.${p}.1 2>/tmp/link.exe.${p}.2
+RES=$?
+
+CMANIFEST=`w32_path.sh -u $MANIFEST`
+
+if [ -f "$CMANIFEST" ]; then
+ ## Add stuff to manifest to turn off "virtualization"
+ sed -n -i '1h;1!H;${;g;s,<trustInfo.*</trustInfo>.,,g;p;}' $CMANIFEST 2>/dev/null
+ sed -i "s/<\/assembly>/ <ms_asmv2:trustInfo xmlns:ms_asmv2=\"urn:schemas-microsoft-com:asm.v2\">\n <ms_asmv2:security>\n <ms_asmv2:requestedPrivileges>\n <ms_asmv2:requestedExecutionLevel level=\"AsInvoker\" uiAccess=\"false\"\/>\n <\/ms_asmv2:requestedPrivileges>\n <\/ms_asmv2:security>\n <\/ms_asmv2:trustInfo>\n<\/assembly>/" $CMANIFEST 2>/dev/null
+else
+ CMANIFEST=$ERL_TOP/erts/etc/win32/manifest.xml
+ MANIFEST=`w32_path.sh -d $CMANIFEST`
+fi
+
+if [ "$RES" = "0" ]; then
+ eval mt.exe -nologo -manifest "$MANIFEST" -outputresource:"$OUTPUTRES" >>/tmp/link.exe.${p}.1 2>>/tmp/link.exe.${p}.2
+ RES=$?
+ if [ "$RES" != "0" ]; then
+ REMOVE=`echo "$OUTPUTRES" | sed 's,\\\;[12]$,,g'`
+ CREMOVE=`wslpath -u $REMOVE`
+ ## If Defender or Search are enabled, they will lock just created files
+ ## and then mt will fail :/
+ echo "If you get this error, make sure Windows Defender AND Windows Search is disabled">>/tmp/link.exe.${p}.1
+ rm -f "$CREMOVE"
+ fi
+fi
+
+# This works around some strange behaviour
+# in cygwin 1.7 Beta on Windows 7 with samba drive.
+# Configure will think the compiler failed if test -x fails,
+# which it might do as we might not be the owner of the
+# file.
+if [ '!' -z "$CHMOD_FILE" -a -s "$CHMOD_FILE" -a '!' -x "$CHMOD_FILE" ]; then
+ chmod +x $CHMOD_FILE
+fi
+
+tail -n +2 /tmp/link.exe.${p}.2 >&2
+cat /tmp/link.exe.${p}.1
+rm -f /tmp/link.exe.${p}.2 /tmp/link.exe.${p}.1
+exit $RES
diff --git a/erts/etc/win32/wsl_tools/vc/mc.sh b/erts/etc/win32/wsl_tools/vc/mc.sh
new file mode 100755
index 0000000000..4ed7e7e2a3
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/vc/mc.sh
@@ -0,0 +1,96 @@
+#! /bin/sh
+# set -x
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+# Save the command line for debug outputs
+SAVE="$@"
+CMD=""
+OUTPUT_DIRNAME=""
+
+# Find the correct mc.exe. This could be done by the configure script,
+# But as we seldom use the message compiler, it might as well be done here...
+MCC=""
+save_ifs=$IFS
+IFS=:
+for p in $PATH; do
+ if [ -f $p/mc.exe ]; then
+ if [ -n "`$p/mc.exe -? 2>&1 >/dev/null </dev/null \
+ | grep -i \"message compiler\"`" ]; then
+ MCC=`echo "mc.exe" | sed 's/ /\\\\ /g'`
+ break
+ else
+ echo "Bad mc.exe in path" >&2
+ exit 1
+ fi
+ fi
+done
+IFS=$save_ifs
+
+if [ -z "$MCC" ]; then
+ echo 'mc.exe not found!' >&2
+ exit 1
+fi
+
+while test -n "$1" ; do
+ x="$1"
+ case "$x" in
+ -o)
+ shift
+ OUTPUT_DIRNAME="$1";;
+ -o/*)
+ y=`echo $x | sed 's,^-[Io]\(/.*\),\1,g'`;
+ OUTPUT_DIRNAME="$y";;
+ -I)
+ shift
+ MPATH=`w32_path.sh -d $1`;
+ CMD="$CMD -I\"$MPATH\"";;
+ -I/*)
+ y=`echo $x | sed 's,^-[Io]\(/.*\),\1,g'`;
+ MPATH=`w32_path.sh -d $y`;
+ CMD="$CMD -I\"$MPATH\"";;
+ *)
+ MPATH=`w32_path.sh -d -a $x`;
+ CMD="$CMD \"$MPATH\"";;
+ esac
+ shift
+done
+p=$$
+if [ "X$MC_SH_DEBUG_LOG" != "X" ]; then
+ echo mc.sh "$SAVE" >>$MC_SH_DEBUG_LOG
+ echo mc.exe $CMD >>$MC_SH_DEBUG_LOG
+fi
+if [ -n "$OUTPUT_DIRNAME" ]; then
+ cd $OUTPUT_DIRNAME
+ RES=$?
+ if [ "$RES" != "0" ]; then
+ echo "mc.sh: Error: could not cd to $OUTPUT_DIRNAME">&2
+ exit $RES
+ fi
+fi
+
+eval $MCC "$CMD" >/tmp/mc.exe.${p}.1 2>/tmp/mc.exe.${p}.2
+RES=$?
+if [ $RES != 0 ]; then
+ echo Failed: $MCC "$CMD"
+fi
+tail -n +2 /tmp/mc.exe.${p}.2 >&2
+cat /tmp/mc.exe.${p}.1
+rm -f /tmp/mc.exe.${p}.2 /tmp/mc.exe.${p}.1
+exit $RES
diff --git a/erts/etc/win32/wsl_tools/vc/rc.sh b/erts/etc/win32/wsl_tools/vc/rc.sh
new file mode 100755
index 0000000000..bbd5c9a773
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/vc/rc.sh
@@ -0,0 +1,94 @@
+#! /bin/sh
+# set -x
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2002-2016. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+# Save the command line for debug outputs
+SAVE="$@"
+CMD=""
+OUTPUT_FILENAME=""
+
+# Find the correct rc.exe. This could be done by the configure script,
+# But as we seldom use the resource compiler, it might as well be done here...
+RCC=""
+save_ifs=$IFS
+IFS=:
+for p in $PATH; do
+ if [ -f $p/rc.exe ]; then
+ if [ -n "`$p/rc.exe -? 2>&1 | grep -i "resource compiler"`" ]; then
+ RCC="rc.exe /nologo"
+ break
+ else
+ echo "Bad rc.exe in path" >&2
+ exit 1
+ fi
+ fi
+done
+IFS=$save_ifs
+
+if [ -z "$RCC" ]; then
+ echo 'rc.exe not found!' >&2
+ exit 1
+fi
+
+while test -n "$1" ; do
+ x="$1"
+ case "$x" in
+ -o)
+ shift
+ MPATH=`w32_path.sh -d $1`;
+ OUTPUT_FILENAME="$MPATH";;
+ -o/*)
+ y=`echo $x | sed 's,^-[Io]\(/.*\),\1,g'`;
+ MPATH=`w32_path.sh -d $y`;
+ OUTPUT_FILENAME="$MPATH";;
+ -I)
+ shift
+ MPATH=`w32_path.sh -d $1`;
+ CMD="$CMD -I\"$MPATH\"";;
+ -I/*)
+ y=`echo $x | sed 's,^-[Io]\(/.*\),\1,g'`;
+ MPATH=`w32_path.sh -d $y`;
+ CMD="$CMD -I\"$MPATH\"";;
+ /*)
+ MPATH=`w32_path.sh -d $x`;
+ CMD="$CMD \"$MPATH\"";;
+ *)
+ y=`echo $x | sed 's,",\\\",g'`;
+ CMD="$CMD \"$y\"";;
+ esac
+ shift
+done
+p=$$
+if [ -n "$OUTPUT_FILENAME" ]; then
+ CMD="-Fo$OUTPUT_FILENAME $CMD"
+fi
+if [ "X$RC_SH_DEBUG_LOG" != "X" ]; then
+ echo rc.sh "$SAVE" >>$RC_SH_DEBUG_LOG
+ echo rc.exe /nologo $CMD >>$RC_SH_DEBUG_LOG
+fi
+eval $RCC "$CMD" >/tmp/rc.exe.${p}.1 2>/tmp/rc.exe.${p}.2
+RES=$?
+if [ $RES != 0 ]; then
+ echo Failed: $RCC "$CMD"
+fi
+tail -n +2 /tmp/rc.exe.${p}.2 >&2
+cat /tmp/rc.exe.${p}.1
+rm -f /tmp/rc.exe.${p}.2 /tmp/rc.exe.${p}.1
+exit $RES
diff --git a/erts/etc/win32/wsl_tools/w32_path.sh b/erts/etc/win32/wsl_tools/w32_path.sh
new file mode 100755
index 0000000000..44e35a6eb9
--- /dev/null
+++ b/erts/etc/win32/wsl_tools/w32_path.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+
+WIN32=false
+SEPARATOR=""
+ABSOLUTE=""
+UNIX=false
+done=false
+while [ $done = false ]; do
+ case "$1" in
+ -w)
+ WIN32=true;
+ SEPARATOR=backslash;
+ shift;;
+ -d)
+ WIN32=true;
+ SEPARATOR=double;
+ shift;;
+ -m)
+ WIN32=true;
+ SEPARATOR=slash;
+ shift;;
+ -u)
+ UNIX=true;
+ shift;;
+ -a)
+ ABSOLUTE="-a";
+ shift;;
+
+ *)
+ done=true;;
+ esac
+done
+
+if [ $WIN32 = false -a $UNIX = false ]; then
+ echo "Usage: $0 -m|-w|-d|-u [-a] <path>" >&2
+ exit 1;
+fi
+
+if [ -z "$1" ]; then
+ echo "Usage: $0 -m|-w|-u [-a] <path>" >&2
+ exit 1;
+fi
+
+case "$1" in
+ /*)
+ rel_input=false
+ ;;
+ *)
+ rel_input=true
+ ;;
+esac
+
+if [ $UNIX = true ]; then
+ # cl.exe loses //// in the beginning which make dependencies fail
+ # and sometimes lowercases the path
+ case $1 in
+ \\*wsl$\\*)
+ y=`echo $1 | sed 's,\\\\\+,/,g'`;
+ z=`echo $y | sed 's,^/wsl$/[^/]*\(.*\),\1,g' | sed 's, ,\\ ,g'`;
+ echo "$z";
+ ;;
+ *)
+ echo `wslpath -u $ABSOLUTE "$1" | sed 's, ,\\ ,g'`
+ ;;
+ esac
+else
+ # wslpath have changed to always return absolute paths and
+ # ensure the file/dir exists before translation
+
+ if [ $rel_input = true -a "$ABSOLUTE" = "" ]; then
+ case "$SEPARATOR" in
+ slash)
+ echo $1
+ ;;
+ backslash)
+ echo "$1" | sed 's,/,\\,g'
+ ;;
+ double)
+ echo "$1" | sed 's,/,\\\\,g'
+ ;;
+ esac
+ exit 0
+ fi
+
+ # absolute input and/or absolute output
+
+ if [ -d "$1" ]; then
+ dir=$1;
+ case "$SEPARATOR" in
+ slash)
+ echo `wslpath -m $ABSOLUTE "$dir"`;
+ ;;
+ backslash)
+ echo `wslpath -w $ABSOLUTE "$dir"`;
+ ;;
+ double)
+ DOUBLE=`wslpath -w $ABSOLUTE "$dir" | sed 's,\\\\,\\\\\\\\,g'`;
+ echo $DOUBLE
+ ;;
+ esac
+ exit 0
+ else
+ dir=`dirname "$1"`
+ file=`basename "$1"`
+
+ case "$SEPARATOR" in
+ slash)
+ echo `wslpath -m $ABSOLUTE "$dir"`/$file;
+ ;;
+ backslash)
+ echo `wslpath -w $ABSOLUTE "$dir"`\\$file;
+ ;;
+ double)
+ DOUBLE=`wslpath -w $ABSOLUTE "$dir" | sed 's,\\\\,\\\\\\\\,g'`;
+ echo $DOUBLE\\\\$file
+ ;;
+ esac
+ fi
+fi
diff --git a/erts/include/internal/erl_misc_utils.h b/erts/include/internal/erl_misc_utils.h
index 55566ddf74..59933ade4b 100644
--- a/erts/include/internal/erl_misc_utils.h
+++ b/erts/include/internal/erl_misc_utils.h
@@ -39,6 +39,7 @@ int erts_cpu_info_update(erts_cpu_info_t *cpuinfo);
int erts_get_cpu_configured(erts_cpu_info_t *cpuinfo);
int erts_get_cpu_online(erts_cpu_info_t *cpuinfo);
int erts_get_cpu_available(erts_cpu_info_t *cpuinfo);
+int erts_get_cpu_quota(erts_cpu_info_t *cpuinfo);
char *erts_get_unbind_from_cpu_str(erts_cpu_info_t *cpuinfo);
int erts_get_available_cpu(erts_cpu_info_t *cpuinfo, int no);
int erts_get_cpu_topology_size(erts_cpu_info_t *cpuinfo);
diff --git a/erts/include/internal/erl_printf_format.h b/erts/include/internal/erl_printf_format.h
index 56ec032bd1..27fe914caa 100644
--- a/erts/include/internal/erl_printf_format.h
+++ b/erts/include/internal/erl_printf_format.h
@@ -21,10 +21,6 @@
#ifndef ERL_PRINTF_FORMAT_H__
#define ERL_PRINTF_FORMAT_H__
-#ifdef VXWORKS
-#include <vxWorks.h>
-#endif
-
#include <sys/types.h>
#include <stdarg.h>
#include <stdlib.h>
diff --git a/erts/include/internal/ethread.h b/erts/include/internal/ethread.h
index fa35bf3d0b..d6644185f9 100644
--- a/erts/include/internal/ethread.h
+++ b/erts/include/internal/ethread.h
@@ -54,8 +54,7 @@
#endif
#if defined(ETHR_DEBUG) || !defined(ETHR_INLINE) || ETHR_XCHK \
- || (defined(__GNUC__) && defined(ERTS_MIXED_CYGWIN_VC)) \
- || (defined(__GNUC__) && defined(ERTS_MIXED_MSYS_VC))
+ || (defined(__GNUC__) && defined(ERTS_MIXED_VC))
# undef ETHR_INLINE
# define ETHR_INLINE
# undef ETHR_FORCE_INLINE
@@ -630,12 +629,18 @@ struct ethr_ts_event_ {
#define ETHR_TS_EV_INITED (((unsigned) 1) << 1)
#define ETHR_TS_EV_TMP (((unsigned) 1) << 2)
#define ETHR_TS_EV_MAIN_THR (((unsigned) 1) << 3)
+#define ETHR_TS_EV_BUSY (((unsigned) 1) << 4)
+#define ETHR_TS_EV_PEEK (((unsigned) 1) << 5)
-int ethr_get_tmp_ts_event__(ethr_ts_event **tsepp);
+ethr_sint64_t ethr_no_used_tse(void);
int ethr_free_ts_event__(ethr_ts_event *tsep);
-int ethr_make_ts_event__(ethr_ts_event **tsepp);
+int ethr_make_ts_event__(ethr_ts_event **tsepp, int tmp);
#if !defined(ETHR_TRY_INLINE_FUNCS) || defined(ETHREAD_IMPL__)
+ethr_ts_event *ethr_lookup_ts_event__(int busy_dup);
+ethr_ts_event *ethr_peek_ts_event(void);
+void ethr_unpeek_ts_event(ethr_ts_event *);
+ethr_ts_event *ethr_use_ts_event(ethr_ts_event *tsep);
ethr_ts_event *ethr_get_ts_event(void);
void ethr_leave_ts_event(ethr_ts_event *);
#endif
@@ -647,11 +652,11 @@ void ethr_leave_ts_event(ethr_ts_event *);
extern pthread_key_t ethr_ts_event_key__;
static ETHR_INLINE ethr_ts_event *
-ETHR_INLINE_FUNC_NAME_(ethr_get_ts_event)(void)
+ETHR_INLINE_FUNC_NAME_(ethr_lookup_ts_event__)(int busy_dup)
{
ethr_ts_event *tsep = pthread_getspecific(ethr_ts_event_key__);
- if (!tsep) {
- int res = ethr_make_ts_event__(&tsep);
+ if (!tsep || (busy_dup && (tsep->iflgs & ETHR_TS_EV_BUSY))) {
+ int res = ethr_make_ts_event__(&tsep, 0);
if (res != 0)
ETHR_FATAL_ERROR__(res);
ETHR_ASSERT(tsep);
@@ -659,12 +664,6 @@ ETHR_INLINE_FUNC_NAME_(ethr_get_ts_event)(void)
return tsep;
}
-static ETHR_INLINE void
-ETHR_INLINE_FUNC_NAME_(ethr_leave_ts_event)(ethr_ts_event *tsep)
-{
-
-}
-
#endif
#elif defined(ETHR_WIN32_THREADS)
@@ -674,11 +673,11 @@ ETHR_INLINE_FUNC_NAME_(ethr_leave_ts_event)(ethr_ts_event *tsep)
extern DWORD ethr_ts_event_key__;
static ETHR_INLINE ethr_ts_event *
-ETHR_INLINE_FUNC_NAME_(ethr_get_ts_event)(void)
+ETHR_INLINE_FUNC_NAME_(ethr_lookup_ts_event__)(int busy_dup)
{
ethr_ts_event *tsep = TlsGetValue(ethr_ts_event_key__);
- if (!tsep) {
- int res = ethr_get_tmp_ts_event__(&tsep);
+ if (!tsep || (busy_dup && (tsep->iflgs & ETHR_TS_EV_BUSY))) {
+ int res = ethr_make_ts_event__(&tsep, !0);
if (res != 0)
ETHR_FATAL_ERROR__(res);
ETHR_ASSERT(tsep);
@@ -686,17 +685,68 @@ ETHR_INLINE_FUNC_NAME_(ethr_get_ts_event)(void)
return tsep;
}
+#endif
+
+#endif
+
+#if defined(ETHR_TRY_INLINE_FUNCS) || defined(ETHREAD_IMPL__)
+
+static ETHR_INLINE ethr_ts_event *
+ETHR_INLINE_FUNC_NAME_(ethr_get_ts_event)(void)
+{
+ ethr_ts_event *tsep = ethr_lookup_ts_event__(!0);
+ ETHR_ASSERT(!(tsep->iflgs & ETHR_TS_EV_BUSY));
+ tsep->iflgs |= ETHR_TS_EV_BUSY;
+ return tsep;
+}
+
+static ETHR_INLINE ethr_ts_event *
+ETHR_INLINE_FUNC_NAME_(ethr_peek_ts_event)(void)
+{
+ ethr_ts_event *tsep = ethr_lookup_ts_event__(0);
+ ETHR_ASSERT(!(tsep->iflgs & ETHR_TS_EV_PEEK));
+ tsep->iflgs |= ETHR_TS_EV_PEEK;
+ return tsep;
+}
+
static ETHR_INLINE void
-ETHR_INLINE_FUNC_NAME_(ethr_leave_ts_event)(ethr_ts_event *tsep)
+ETHR_INLINE_FUNC_NAME_(ethr_unpeek_ts_event)(ethr_ts_event *tsep)
{
- if (tsep->iflgs & ETHR_TS_EV_TMP) {
+ ETHR_ASSERT(tsep->iflgs & ETHR_TS_EV_PEEK);
+ tsep->iflgs &= ~ETHR_TS_EV_PEEK;
+ if ((tsep->iflgs & (ETHR_TS_EV_TMP|ETHR_TS_EV_BUSY)) == ETHR_TS_EV_TMP) {
int res = ethr_free_ts_event__(tsep);
if (res != 0)
ETHR_FATAL_ERROR__(res);
}
}
-#endif
+static ETHR_INLINE ethr_ts_event *
+ETHR_INLINE_FUNC_NAME_(ethr_use_ts_event)(ethr_ts_event *tsep)
+{
+ ethr_ts_event *tmp_tsep = tsep;
+ if (tmp_tsep->iflgs & ETHR_TS_EV_BUSY) {
+ int res = ethr_make_ts_event__(&tmp_tsep, !0);
+ if (res != 0)
+ ETHR_FATAL_ERROR__(res);
+ ETHR_ASSERT(tmp_tsep && tsep != tmp_tsep);
+ }
+ ETHR_ASSERT(!(tmp_tsep->iflgs & ETHR_TS_EV_BUSY));
+ tmp_tsep->iflgs |= ETHR_TS_EV_BUSY;
+ return tmp_tsep;
+}
+
+static ETHR_INLINE void
+ETHR_INLINE_FUNC_NAME_(ethr_leave_ts_event)(ethr_ts_event *tsep)
+{
+ ETHR_ASSERT(tsep->iflgs & ETHR_TS_EV_BUSY);
+ tsep->iflgs &= ~ETHR_TS_EV_BUSY;
+ if ((tsep->iflgs & (ETHR_TS_EV_TMP|ETHR_TS_EV_PEEK)) == ETHR_TS_EV_TMP) {
+ int res = ethr_free_ts_event__(tsep);
+ if (res != 0)
+ ETHR_FATAL_ERROR__(res);
+ }
+}
#endif
diff --git a/erts/include/internal/ethread_header_config.h.in b/erts/include/internal/ethread_header_config.h.in
index 6309f10439..300068b952 100644
--- a/erts/include/internal/ethread_header_config.h.in
+++ b/erts/include/internal/ethread_header_config.h.in
@@ -76,6 +76,12 @@
/* Define if x86/x86_64 out of order instructions should be synchronized */
#undef ETHR_X86_OUT_OF_ORDER
+/* Define if you have the powerpc lwsync instruction */
+#undef ETHR_PPC_HAVE_LWSYNC
+
+/* Define if you do not have the powerpc lwsync instruction */
+#undef ETHR_PPC_HAVE_NO_LWSYNC
+
/* Define if only run in Sparc TSO mode */
#undef ETHR_SPARC_TSO
@@ -86,10 +92,20 @@
#undef ETHR_SPARC_RMO
/* Define as a boolean indicating whether you have a gcc compatible compiler
- capable of generating the ARM DMB instruction, and are compiling for an ARM
- processor with ARM DMB instruction support, or not */
+ capable of generating the ARM 'dmb sy' instruction, and are compiling for
+ an ARM processor with ARM DMB instruction support, or not */
#undef ETHR_HAVE_GCC_ASM_ARM_DMB_INSTRUCTION
+/* Define as a boolean indicating whether you have a gcc compatible compiler
+ capable of generating the ARM 'dmb ld' instruction, and are compiling for
+ an ARM processor with ARM DMB instruction support, or not */
+#undef ETHR_HAVE_GCC_ASM_ARM_DMB_LD_INSTRUCTION
+
+/* Define as a boolean indicating whether you have a gcc compatible compiler
+ capable of generating the ARM 'dmb st' instruction, and are compiling for
+ an ARM processor with ARM DMB instruction support, or not */
+#undef ETHR_HAVE_GCC_ASM_ARM_DMB_ST_INSTRUCTION
+
/* Define as a bitmask corresponding to the word sizes that
__sync_synchronize() can handle on your system */
#undef ETHR_HAVE___sync_synchronize
diff --git a/erts/include/internal/gcc/ethr_membar.h b/erts/include/internal/gcc/ethr_membar.h
index 643b243683..aeef8115a3 100644
--- a/erts/include/internal/gcc/ethr_membar.h
+++ b/erts/include/internal/gcc/ethr_membar.h
@@ -149,14 +149,51 @@ ethr_full_fence__(void)
__asm__ __volatile__("dmb sy" : : : "memory");
}
+#if ETHR_HAVE_GCC_ASM_ARM_DMB_ST_INSTRUCTION
static __inline__ __attribute__((__always_inline__)) void
ethr_store_fence__(void)
{
+ /* StoreStore */
__asm__ __volatile__("dmb st" : : : "memory");
}
+#endif
+
+#if ETHR_HAVE_GCC_ASM_ARM_DMB_LD_INSTRUCTION
+static __inline__ __attribute__((__always_inline__)) void
+ethr_load_fence__(void)
+{
+ /* LoadLoad and LoadStore */
+ __asm__ __volatile__("dmb ld" : : : "memory");
+}
+#endif
-#define ETHR_MEMBAR(B) \
- ETHR_CHOOSE_EXPR((B) == ETHR_StoreStore, ethr_store_fence__(), ethr_full_fence__())
+#if ETHR_HAVE_GCC_ASM_ARM_DMB_ST_INSTRUCTION && ETHR_HAVE_GCC_ASM_ARM_DMB_LD_INSTRUCTION
+/* sy, st & ld */
+#define ETHR_MEMBAR(B) \
+ ETHR_CHOOSE_EXPR((B) == ETHR_StoreStore, \
+ ethr_store_fence__(), \
+ ETHR_CHOOSE_EXPR((B) & (ETHR_StoreStore \
+ | ETHR_StoreLoad), \
+ ethr_full_fence__(), \
+ ethr_load_fence__()))
+#elif ETHR_HAVE_GCC_ASM_ARM_DMB_ST_INSTRUCTION
+/* sy & st */
+#define ETHR_MEMBAR(B) \
+ ETHR_CHOOSE_EXPR((B) == ETHR_StoreStore, \
+ ethr_store_fence__(), \
+ ethr_full_fence__())
+#elif ETHR_HAVE_GCC_ASM_ARM_DMB_LD_INSTRUCTION
+/* sy & ld */
+#define ETHR_MEMBAR(B) \
+ ETHR_CHOOSE_EXPR((B) & (ETHR_StoreStore \
+ | ETHR_StoreLoad), \
+ ethr_full_fence__(), \
+ ethr_load_fence__())
+#else
+/* sy */
+#define ETHR_MEMBAR(B) \
+ ethr_full_fence__()
+#endif
#elif ETHR_HAVE___sync_synchronize
@@ -205,9 +242,13 @@ ethr_full_fence__(void)
/*
* Define ETHR_READ_DEPEND_MEMORY_BARRIER for all architechtures
* not known to order data dependent loads
+ *
+ * This is a bit too conservative, but better safe than sorry...
+ * Add more archs as needed...
*/
-#if !defined(__ia64__) && !defined(__arm__)
+#if !defined(__ia64__) && !defined(__arm__) && !defined(__arm64__) \
+ && !defined(__aarch32__) && !defined(__aarch64__)
# define ETHR_READ_DEPEND_MEMORY_BARRIER ETHR_MEMBAR(ETHR_LoadLoad)
#endif
diff --git a/erts/include/internal/gcc/ethread.h b/erts/include/internal/gcc/ethread.h
index 12b41f8704..5584648614 100644
--- a/erts/include/internal/gcc/ethread.h
+++ b/erts/include/internal/gcc/ethread.h
@@ -44,6 +44,10 @@
#undef ETHR_GCC_RELB_VERSIONS__
#undef ETHR_GCC_RELB_MOD_VERSIONS__
#undef ETHR_GCC_MB_MOD_VERSIONS__
+#undef ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS__
+
+#define ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS__ \
+ ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS
/*
* True GNU GCCs before version 4.8 do not emit a memory barrier
@@ -52,18 +56,46 @@
*/
#undef ETHR___atomic_load_ACQUIRE_barrier_bug
#if ETHR_GCC_COMPILER != ETHR_GCC_COMPILER_TRUE
+
+#if ETHR_GCC_COMPILER == ETHR_GCC_COMPILER_CLANG \
+ && defined(__apple_build_version__) \
+ && __clang_major__ >= 12
+/* Apples clang verified not to have this bug */
+# define ETHR___atomic_load_ACQUIRE_barrier_bug 0
+/* Also trust builtin barriers */
+# undef ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS__
+# define ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS__ 1
+# else
/*
- * A gcc compatible compiler. We have no information
+ * Another gcc compatible compiler. We have no information
* about the existence of this bug, but we assume
* that it is not impossible that it could have
* been "inherited". Therefore, until we are certain
* that the bug does not exist, we assume that it
* does.
*/
-# define ETHR___atomic_load_ACQUIRE_barrier_bug ETHR_GCC_VERSIONS_MASK__
+# define ETHR___atomic_load_ACQUIRE_barrier_bug ETHR_GCC_VERSIONS_MASK__
+# endif
+
#elif !ETHR_AT_LEAST_GCC_VSN__(4, 8, 0)
/* True gcc of version < 4.8, i.e., bug exist... */
# define ETHR___atomic_load_ACQUIRE_barrier_bug ETHR_GCC_VERSIONS_MASK__
+#elif ETHR_AT_LEAST_GCC_VSN__(8, 3, 0) \
+ && (defined(__arm64__) || defined(__aarch64__) || defined(__arm__)) \
+ && ETHR_SIZEOF_PTR == 8
+/* Verified not to have this bug */
+# define ETHR___atomic_load_ACQUIRE_barrier_bug 0
+/* Also trust builtin barriers */
+# undef ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS__
+# define ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS__ 1
+#elif ETHR_AT_LEAST_GCC_VSN__(9, 3, 0) \
+ && (defined(__powerpc__) || defined(__ppc__) || defined(__powerpc64__)) \
+ && ETHR_SIZEOF_PTR == 8
+/* Verified not to have this bug */
+# define ETHR___atomic_load_ACQUIRE_barrier_bug 0
+/* Also trust builtin barriers */
+# undef ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS__
+# define ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS__ 1
#else /* True gcc of version >= 4.8 */
/*
* Sizes less than or equal to word size have been fixed,
@@ -87,7 +119,7 @@
#define ETHR_GCC_RELAXED_VERSIONS__ ETHR_GCC_VERSIONS_MASK__
#define ETHR_GCC_RELAXED_MOD_VERSIONS__ ETHR_GCC_VERSIONS_MASK__
-#if ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS
+#if ETHR_TRUST_GCC_ATOMIC_BUILTINS_MEMORY_BARRIERS__
# define ETHR_GCC_ACQB_VERSIONS__ ETHR_GCC_VERSIONS_MASK__
# define ETHR_GCC_ACQB_MOD_VERSIONS__ ETHR_GCC_VERSIONS_MASK__
# define ETHR_GCC_RELB_VERSIONS__ ETHR_GCC_VERSIONS_MASK__
diff --git a/erts/lib_src/Makefile.in b/erts/lib_src/Makefile.in
index 7277019ece..8fddd3479e 100644
--- a/erts/lib_src/Makefile.in
+++ b/erts/lib_src/Makefile.in
@@ -22,8 +22,13 @@ include $(ERL_TOP)/make/output.mk
include $(ERL_TOP)/make/target.mk
include ../include/internal/$(TARGET)/ethread.mk
-OMIT_OMIT_FP=no
+ifeq ($(TARGET),win32)
+EXE_SUFFIX=.exe
+else
+EXE_SUFFIX=
+endif
+OMIT_OMIT_FP=no
CC=@CC@
LD=@LD@
@@ -39,6 +44,7 @@ ERLANG_OSTYPE=@ERLANG_OSTYPE@
OMIT_FP=false
CFLAGS=$(subst O2,O3, @CFLAGS@)
+LDFLAGS=@LDFLAGS@
ifeq ($(TYPE),debug)
CFLAGS=@DEBUG_CFLAGS@ -DDEBUG
@@ -75,6 +81,11 @@ CFLAGS=@DEBUG_CFLAGS@ -DVALGRIND
TYPE_SUFFIX=.valgrind
PRE_LD=
else
+ifeq ($(TYPE),asan)
+CFLAGS=@DEBUG_CFLAGS@
+TYPE_SUFFIX=.asan
+PRE_LD=
+else
ifeq ($(TYPE),gprof)
CFLAGS += -DGPROF -pg
TYPE_SUFFIX=.gprof
@@ -111,6 +122,7 @@ endif
endif
endif
endif
+endif
OPSYS=@OPSYS@
sol2CFLAGS=
@@ -152,7 +164,6 @@ ERTS_INCL_INT=../include/internal
INCLUDES=-I$(ERTS_INCL) -I$(ERTS_INCL)/$(TARGET) -I$(ERTS_INCL_INT) -I$(ERTS_INCL_INT)/$(TARGET)
INCLUDES += -I../emulator/beam -I../emulator/sys/$(ERLANG_OSTYPE)
-USING_MINGW=@MIXED_CYGWIN_MINGW@
USING_VC=@MIXED_VC@
ifeq ($(USING_VC),yes)
@@ -343,7 +354,11 @@ endif
.PHONY: all
all: $(OBJ_DIR)/MADE
-$(OBJ_DIR)/MADE: $(ETHREAD_LIB) $(ERTS_LIBS) $(ERTS_INTERNAL_LIBS)
+YCF_SOURCE_DIR=$(ERL_TOP)/erts/lib_src/yielding_c_fun
+
+include $(YCF_SOURCE_DIR)/main_target.mk
+
+$(OBJ_DIR)/MADE: $(YCF_EXECUTABLE) $(ETHREAD_LIB) $(ERTS_LIBS) $(ERTS_INTERNAL_LIBS)
$(gen_verbose)
ifeq ($(OMIT_OMIT_FP),yes)
@echo '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *'
@@ -496,6 +511,7 @@ INTERNAL_RELEASE_LIBS= \
.PHONY: release_spec
release_spec: all
+ $(INSTALL_PROGRAM) $(YCF_EXECUTABLE) "$(RELSYSDIR)/bin"
ifneq ($(strip $(RELEASE_INCLUDES)),)
$(INSTALL_DIR) "$(RELSYSDIR)/include"
$(INSTALL_DIR) "$(RELEASE_PATH)/usr/include"
@@ -538,6 +554,8 @@ clean:
$(RM) -r ../lib/internal/$(TARGET)/*
$(RM) -r ../lib/$(TARGET)/*
$(RM) -r obj/$(TARGET)/*
+ $(RM) yielding_c_fun/bin/$(TARGET)/*
+ $(RM) yielding_c_fun/$(TARGET)/*
#
# Make dependencies
@@ -657,8 +675,10 @@ endif
endif
@echo "# EOF" >> $(DEPEND_MK);
+ifneq ($(ERTS_SKIP_DEPEND),true)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPEND_MK)
endif
+endif
# eof
diff --git a/erts/lib_src/common/erl_misc_utils.c b/erts/lib_src/common/erl_misc_utils.c
index f50d709e77..b35d53be7d 100644
--- a/erts/lib_src/common/erl_misc_utils.c
+++ b/erts/lib_src/common/erl_misc_utils.c
@@ -29,10 +29,8 @@
#include "ethread_inline.h"
#include "erl_misc_utils.h"
-#if defined(__WIN32__)
-#elif defined(VXWORKS)
-# include <selectLib.h>
-#else /* UNIX */
+#if !defined(__WIN32__) /* UNIX */
+# include <stdarg.h>
# include <stdio.h>
# include <sys/types.h>
# include <sys/param.h>
@@ -54,6 +52,7 @@
# endif
# endif
# include <string.h>
+# include <stdio.h>
# ifdef HAVE_UNISTD_H
# include <unistd.h>
# endif
@@ -133,6 +132,7 @@
#endif
static int read_topology(erts_cpu_info_t *cpuinfo);
+static int read_cpu_quota(int limit);
#if defined(ERTS_HAVE_MISC_UTIL_AFFINITY_MASK__)
static int
@@ -176,6 +176,7 @@ struct erts_cpu_info_t_ {
int online;
int available;
int topology_size;
+ int quota;
erts_cpu_topology_t *topology;
#if defined(ERTS_HAVE_MISC_UTIL_AFFINITY_MASK__)
char *affinity_str;
@@ -238,6 +239,7 @@ erts_cpu_info_create(void)
cpuinfo->configured = -1;
cpuinfo->online = -1;
cpuinfo->available = -1;
+ cpuinfo->quota = -1;
erts_cpu_info_update(cpuinfo);
return cpuinfo;
}
@@ -269,6 +271,7 @@ erts_cpu_info_update(erts_cpu_info_t *cpuinfo)
int configured = 0;
int online = 0;
int available = 0;
+ int quota = 0;
erts_cpu_topology_t *old_topology;
int old_topology_size;
#if defined(ERTS_HAVE_MISC_UTIL_AFFINITY_MASK__)
@@ -408,13 +411,20 @@ erts_cpu_info_update(erts_cpu_info_t *cpuinfo)
if (available > online)
available = online;
+ else if (available == 0) /* shouldn't happen */
+ available = online;
if (cpuinfo->available != available)
changed = 1;
+ quota = read_cpu_quota(online);
+ if (cpuinfo->quota != quota)
+ changed = 1;
+
cpuinfo->configured = configured;
cpuinfo->online = online;
cpuinfo->available = available;
+ cpuinfo->quota = quota;
old_topology = cpuinfo->topology;
old_topology_size = cpuinfo->topology_size;
@@ -471,6 +481,16 @@ erts_get_cpu_available(erts_cpu_info_t *cpuinfo)
return cpuinfo->available;
}
+int
+erts_get_cpu_quota(erts_cpu_info_t *cpuinfo)
+{
+ if (!cpuinfo)
+ return -EINVAL;
+ if (cpuinfo->quota <= 0)
+ return -ENOTSUP;
+ return cpuinfo->quota;
+}
+
char *
erts_get_unbind_from_cpu_str(erts_cpu_info_t *cpuinfo)
{
@@ -775,7 +795,7 @@ adjust_processor_nodes(erts_cpu_info_t *cpuinfo, int no_nodes)
#ifdef __linux__
static int
-read_file(char *path, char *buf, int size)
+read_file(const char *path, char *buf, int size)
{
int ix = 0;
ssize_t sz = size-1;
@@ -1004,6 +1024,240 @@ read_topology(erts_cpu_info_t *cpuinfo)
return res;
}
+static int
+csv_contains(const char *haystack,
+ const char *element,
+ char separator) {
+ size_t element_len;
+ const char *ptr;
+
+ element_len = strlen(element);
+ ptr = strstr(haystack, element);
+
+ while (ptr) {
+ if (!ptr[element_len] || ptr[element_len] == separator) {
+ if (ptr == haystack || ptr[-1] == separator) {
+ return 1;
+ }
+ }
+
+ ptr = strstr(&ptr[1], element);
+ }
+
+ return 0;
+}
+
+static const char*
+str_combine(const char *a, const char *b) {
+ size_t a_len, b_len;
+ char *result;
+
+ a_len = strlen(a);
+ b_len = strlen(b);
+
+ result = malloc(a_len + b_len + 1);
+
+ memcpy(&result[0], a, a_len);
+ memcpy(&result[a_len], b, b_len + 1);
+
+ return result;
+}
+
+static const char*
+get_cgroup_v1_base_dir(const char *controller) {
+ char line_buf[5 << 10];
+ FILE *var_file;
+
+ var_file = fopen("/proc/self/cgroup", "r");
+
+ if (var_file == NULL) {
+ return NULL;
+ }
+
+ while (fgets(line_buf, sizeof(line_buf), var_file)) {
+ /* sscanf_s requires C11, so we use hardcoded sizes (rather than rely
+ * on macros like MAXPATHLEN) so we can specify them directly in the
+ * format string. */
+ char base_dir[4 << 10];
+ char controllers[256];
+
+ if (sscanf(line_buf, "%*d:%255[^:]:%4095s\n",
+ controllers, base_dir) != 2) {
+ continue;
+ }
+
+ if (csv_contains(controllers, controller, ',')) {
+ fclose(var_file);
+ return strdup(base_dir);
+ }
+ }
+
+ fclose(var_file);
+ return NULL;
+}
+
+enum cgroup_version_t {
+ ERTS_CGROUP_NONE,
+ ERTS_CGROUP_V1,
+ ERTS_CGROUP_V2
+};
+
+static enum cgroup_version_t
+get_cgroup_path(const char *controller, const char **path) {
+ char line_buf[10 << 10];
+ FILE *var_file;
+
+ var_file = fopen("/proc/self/mountinfo", "r");
+
+ if (var_file == NULL) {
+ return ERTS_CGROUP_NONE;
+ }
+
+ while (fgets(line_buf, sizeof(line_buf), var_file)) {
+ char mount_path[4 << 10];
+ char root_path[4 << 10];
+ char fs_flags[512];
+ char fs_type[64];
+
+ /* Format:
+ * [Mount id] [Parent id] [Major] [Minor] [Root] [Mounted at] \
+ * [Mount flags] ... (options terminated by a single hyphen) ... \
+ * [FS type] [Mount source] [Flags]
+ *
+ * (See proc(5) for a more complete description.)
+ *
+ * This fails if any of the fs options contain a hyphen, but this is
+ * not likely to happen on a cgroup, so we just skip such lines. */
+ if (sscanf(line_buf,
+ "%*d %*d %*d:%*d %4095s %4095s %*s %*[^-]- "
+ "%63s %*s %511[^\n]\n",
+ root_path, mount_path,
+ fs_type, fs_flags) != 4) {
+ continue;
+ }
+
+ if (!strcmp(fs_type, "cgroup2")) {
+ char controllers[256];
+ const char *cgc_path;
+
+ cgc_path = str_combine(mount_path, "/cgroup.controllers");
+ if (read_file(cgc_path, controllers, sizeof(controllers)) > 0) {
+ if (csv_contains(controllers, controller, ' ')) {
+ free((void*)cgc_path);
+ fclose(var_file);
+
+ *path = strdup(mount_path);
+ return ERTS_CGROUP_V2;
+ }
+ }
+ free((void*)cgc_path);
+ } else if (!strcmp(fs_type, "cgroup")) {
+ if (csv_contains(fs_flags, controller, ',')) {
+ const char *base_dir = get_cgroup_v1_base_dir(controller);
+
+ if (base_dir) {
+ if (strcmp(root_path, base_dir)) {
+ *path = str_combine(mount_path, base_dir);
+ } else {
+ *path = strdup(mount_path);
+ }
+
+ free((void*)base_dir);
+ fclose(var_file);
+
+ return ERTS_CGROUP_V1;
+ }
+ }
+ }
+ }
+
+ fclose(var_file);
+
+ return ERTS_CGROUP_NONE;
+}
+
+static int read_cgroup_interface(const char *group_path, const char *if_name,
+ int arg_count, const char *format, ...) {
+ const char *var_path;
+ int res;
+
+ var_path = str_combine(group_path, if_name);
+ res = 0;
+
+ if (var_path) {
+ FILE *var_file;
+
+ var_file = fopen(var_path, "r");
+ free((void*)var_path);
+
+ if (var_file) {
+ va_list va_args;
+
+ va_start(va_args, format);
+
+ if (vfscanf(var_file, format, va_args) == arg_count) {
+ res = 1;
+ }
+
+ va_end(va_args);
+
+ fclose(var_file);
+ }
+ }
+
+ return res;
+}
+
+/* CPU quotas are read from the cgroup configuration, which can be pretty hairy
+ * as we need to support both v1 and v2, and it's possible for both versions to
+ * be active at the same time. */
+
+static int
+read_cpu_quota(int limit)
+{
+ ssize_t cfs_period_us, cfs_quota_us;
+ const char *cgroup_path;
+ int succeeded;
+
+ switch (get_cgroup_path("cpu", &cgroup_path)) {
+ case ERTS_CGROUP_V1:
+ succeeded = read_cgroup_interface(cgroup_path, "/cpu.cfs_quota_us",
+ 1, "%zi", &cfs_quota_us) &&
+ read_cgroup_interface(cgroup_path, "/cpu.cfs_period_us",
+ 1, "%zi", &cfs_period_us);
+
+ free((void*)cgroup_path);
+ break;
+ case ERTS_CGROUP_V2:
+ succeeded = read_cgroup_interface(cgroup_path, "/cpu.max",
+ 2, "%zi %zi", &cfs_quota_us, &cfs_period_us);
+
+ free((void*)cgroup_path);
+ break;
+ default:
+ succeeded = 0;
+ break;
+ }
+
+ if (succeeded) {
+ if (cfs_period_us > 0 && cfs_quota_us > 0) {
+ size_t quota = cfs_quota_us / cfs_period_us;
+
+ if (quota == 0) {
+ quota = 1;
+ }
+
+ if (quota > 0 && quota <= (size_t)limit) {
+ return quota;
+ }
+ }
+
+ return limit;
+ }
+
+ return 0;
+}
+
#elif defined(HAVE_KSTAT) /* SunOS kstat */
#include <kstat.h>
@@ -1157,6 +1411,13 @@ read_topology(erts_cpu_info_t *cpuinfo)
}
+static int
+read_cpu_quota(int limit)
+{
+ (void)limit;
+ return 0;
+}
+
#elif defined(__WIN32__)
/*
@@ -1431,6 +1692,13 @@ read_topology(erts_cpu_info_t *cpuinfo)
return res;
}
+static int
+read_cpu_quota(int limit)
+{
+ (void)limit;
+ return 0;
+}
+
#elif defined(__FreeBSD__)
/**
@@ -1670,9 +1938,23 @@ error:
return res;
}
+static int
+read_cpu_quota(int limit)
+{
+ (void)limit;
+ return 0;
+}
+
#else
static int
+read_cpu_quota(int limit)
+{
+ (void)limit;
+ return 0;
+}
+
+static int
read_topology(erts_cpu_info_t *cpuinfo)
{
return -ENOTSUP;
diff --git a/erts/lib_src/common/erl_printf.c b/erts/lib_src/common/erl_printf.c
index 38412ee36f..07d662a004 100644
--- a/erts/lib_src/common/erl_printf.c
+++ b/erts/lib_src/common/erl_printf.c
@@ -18,11 +18,6 @@
* %CopyrightEnd%
*/
-/* Without this, variable argument lists break on VxWorks */
-#ifdef VXWORKS
-#include <vxWorks.h>
-#endif
-
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
diff --git a/erts/lib_src/common/erl_printf_format.c b/erts/lib_src/common/erl_printf_format.c
index 8f9e0b4a90..c8256532b5 100644
--- a/erts/lib_src/common/erl_printf_format.c
+++ b/erts/lib_src/common/erl_printf_format.c
@@ -31,11 +31,6 @@
* sz: 8 | 16 | 32 | 64 | p | e
*/
-/* Without this, variable argument lists break on VxWorks */
-#ifdef VXWORKS
-#include <vxWorks.h>
-#endif
-
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
diff --git a/erts/lib_src/common/ethr_aux.c b/erts/lib_src/common/ethr_aux.c
index 7b156fe01a..b844960854 100644
--- a/erts/lib_src/common/ethr_aux.c
+++ b/erts/lib_src/common/ethr_aux.c
@@ -109,7 +109,8 @@ x86_init(void)
if (eax > 0
&& (ETHR_IS_X86_VENDOR("GenuineIntel", ebx, ecx, edx)
- || ETHR_IS_X86_VENDOR("AuthenticAMD", ebx, ecx, edx))) {
+ || ETHR_IS_X86_VENDOR("AuthenticAMD", ebx, ecx, edx)
+ || ETHR_IS_X86_VENDOR("HygonGenuine", ebx, ecx, edx))) {
eax = 1;
ethr_x86_cpuid__(&eax, &ebx, &ecx, &edx);
}
@@ -292,7 +293,8 @@ ethr_late_init_common__(ethr_late_init_data *lid)
res = init_ts_event_alloc();
if (res != 0)
return res;
- res = ethr_make_ts_event__(&tsep);
+
+ res = ethr_make_ts_event__(&tsep, 0);
if (res == 0)
tsep->iflgs |= ETHR_TS_EV_ETHREAD;
if (!lid) {
@@ -434,8 +436,9 @@ typedef union {
char align[ETHR_CACHE_LINE_ALIGN_SIZE(sizeof(ethr_ts_event))];
} ethr_aligned_ts_event;
-static ethr_spinlock_t ts_ev_alloc_lock;
+static ethr_mutex ts_ev_alloc_lock;
static ethr_ts_event *free_ts_ev;
+static ethr_sint64_t used_events;
static ethr_ts_event *ts_event_pool(int size, ethr_ts_event **endpp)
{
@@ -464,34 +467,38 @@ static ethr_ts_event *ts_event_pool(int size, ethr_ts_event **endpp)
static int init_ts_event_alloc(void)
{
+ used_events = 0;
free_ts_ev = ts_event_pool(ERTS_TS_EV_ALLOC_DEFAULT_POOL_SIZE,
NULL);
if (!free_ts_ev)
return ENOMEM;
- return ethr_spinlock_init(&ts_ev_alloc_lock);
+ return ethr_mutex_init(&ts_ev_alloc_lock);
}
static ethr_ts_event *ts_event_alloc(void)
{
ethr_ts_event *ts_ev;
- ethr_spin_lock(&ts_ev_alloc_lock);
+ ethr_mutex_lock(&ts_ev_alloc_lock);
+ ETHR_ASSERT(used_events >= 0);
if (free_ts_ev) {
ts_ev = free_ts_ev;
free_ts_ev = ts_ev->next;
- ethr_spin_unlock(&ts_ev_alloc_lock);
+ used_events++;
+ ethr_mutex_unlock(&ts_ev_alloc_lock);
}
else {
ethr_ts_event *ts_ev_pool_end;
- ethr_spin_unlock(&ts_ev_alloc_lock);
+ ethr_mutex_unlock(&ts_ev_alloc_lock);
ts_ev = ts_event_pool(ERTS_TS_EV_ALLOC_POOL_SIZE, &ts_ev_pool_end);
if (!ts_ev)
return NULL;
- ethr_spin_lock(&ts_ev_alloc_lock);
+ ethr_mutex_lock(&ts_ev_alloc_lock);
ts_ev_pool_end->next = free_ts_ev;
free_ts_ev = ts_ev->next;
- ethr_spin_unlock(&ts_ev_alloc_lock);
+ used_events++;
+ ethr_mutex_unlock(&ts_ev_alloc_lock);
}
return ts_ev;
}
@@ -499,69 +506,71 @@ static ethr_ts_event *ts_event_alloc(void)
static void ts_event_free(ethr_ts_event *ts_ev)
{
ETHR_ASSERT(!ts_ev->udata);
- ethr_spin_lock(&ts_ev_alloc_lock);
+ ethr_mutex_lock(&ts_ev_alloc_lock);
+ ETHR_ASSERT(used_events > 0);
+ used_events--;
ts_ev->next = free_ts_ev;
free_ts_ev = ts_ev;
- ethr_spin_unlock(&ts_ev_alloc_lock);
+ ethr_mutex_unlock(&ts_ev_alloc_lock);
}
-int ethr_make_ts_event__(ethr_ts_event **tsepp)
+static void
+init_ts_event_flags(ethr_ts_event **tsepp)
{
- int res;
ethr_ts_event *tsep = *tsepp;
-
- if (!tsep) {
- tsep = ts_event_alloc();
- if (!tsep)
- return ENOMEM;
- }
-
- if ((tsep->iflgs & ETHR_TS_EV_INITED) == 0) {
- res = ethr_event_init(&tsep->event);
- if (res != 0) {
- ts_event_free(tsep);
- return res;
- }
- }
-
tsep->iflgs = ETHR_TS_EV_INITED;
tsep->udata = NULL;
tsep->rgix = 0;
tsep->mtix = 0;
+}
- res = ethr_set_tse__(tsep);
- if (res != 0 && tsepp && *tsepp) {
- ts_event_free(tsep);
- return res;
- }
-
- if (tsepp)
- *tsepp = tsep;
-
- return 0;
+ethr_sint64_t
+ethr_no_used_tse(void)
+{
+ ethr_sint64_t res;
+ ethr_mutex_lock(&ts_ev_alloc_lock);
+ ETHR_ASSERT(used_events >= 0);
+ res = used_events;
+ ethr_mutex_unlock(&ts_ev_alloc_lock);
+ return res;
}
-int ethr_get_tmp_ts_event__(ethr_ts_event **tsepp)
+
+int ethr_make_ts_event__(ethr_ts_event **tsepp, int tmp)
{
int res;
- ethr_ts_event *tsep = *tsepp;
+ ethr_ts_event *tsep;
- if (!tsep) {
- tsep = ts_event_alloc();
- if (!tsep)
- return ENOMEM;
- }
+ tsep = ts_event_alloc();
+ if (!tsep)
+ return ENOMEM;
- if ((tsep->iflgs & ETHR_TS_EV_INITED) == 0) {
- res = ethr_event_init(&tsep->event);
- if (res != 0) {
- ts_event_free(tsep);
- return res;
- }
+ if (*tsepp) {
+ *tsep = **tsepp;
+ ETHR_ASSERT(tsep->iflgs & ETHR_TS_EV_BUSY);
+ tsep->iflgs &= ~ETHR_TS_EV_BUSY;
+ tsep->iflgs |= ETHR_TS_EV_TMP;
}
+ else {
+ init_ts_event_flags(&tsep);
- tsep->iflgs = ETHR_TS_EV_INITED|ETHR_TS_EV_TMP;
- tsep->udata = NULL;
+ if (tmp) {
+ tsep->iflgs |= ETHR_TS_EV_TMP;
+ }
+ else {
+ res = ethr_set_tse__(tsep);
+ if (res != 0 && tsepp && *tsepp) {
+ ts_event_free(tsep);
+ return res;
+ }
+ }
+ }
+
+ res = ethr_event_init(&tsep->event);
+ if (res != 0) {
+ ts_event_free(tsep);
+ return res;
+ }
if (tsepp)
*tsepp = tsep;
@@ -571,8 +580,10 @@ int ethr_get_tmp_ts_event__(ethr_ts_event **tsepp)
int ethr_free_ts_event__(ethr_ts_event *tsep)
{
- ts_event_free(tsep);
- return 0;
+ int res = ethr_event_destroy(&tsep->event);
+ if (res == 0)
+ ts_event_free(tsep);
+ return res;
}
void ethr_ts_event_destructor__(void *vtsep)
diff --git a/erts/lib_src/common/ethr_mutex.c b/erts/lib_src/common/ethr_mutex.c
index 5e7e7b2f32..7be83b9117 100644
--- a/erts/lib_src/common/ethr_mutex.c
+++ b/erts/lib_src/common/ethr_mutex.c
@@ -184,7 +184,7 @@ ethr_rwmutex_set_reader_group(int ix)
tse = ethr_get_ts_event();
- if ((tse->iflgs & ETHR_TS_EV_ETHREAD) == 0) {
+ if ((tse->iflgs & (ETHR_TS_EV_ETHREAD|ETHR_TS_EV_TMP)) == 0) {
ethr_leave_ts_event(tse);
return EINVAL;
}
@@ -419,6 +419,8 @@ event_wait(struct ethr_mutex_base_ *mtxb,
int need_try_complete_runlock = 0;
int transfer_read_lock = 0;
+ ETHR_ASSERT(tse->iflgs & ETHR_TS_EV_BUSY);
+
/* Need to enqueue and wait... */
tse->uflgs = type;
@@ -683,7 +685,7 @@ write_lock_wait(struct ethr_mutex_base_ *mtxb,
ethr_rwmutex *rwmtx = (ethr_rwmutex *) mtxb;
scnt--;
if (!tse)
- tse = ethr_get_ts_event();
+ tse = ethr_peek_ts_event();
res = rwmutex_try_complete_runlock(rwmtx, act,
tse, 0, 0,
1);
@@ -702,10 +704,12 @@ write_lock_wait(struct ethr_mutex_base_ *mtxb,
scnt = 0;
if (!tse)
- tse = ethr_get_ts_event();
+ tse = ethr_peek_ts_event();
if (update_spincount(mtxb, tse, &start_scnt, &scnt)) {
- event_wait(mtxb, tse, scnt, ETHR_RWMTX_W_WAIT_FLG__,
+ ethr_ts_event *tmp_tse = ethr_use_ts_event(tse);
+ event_wait(mtxb, tmp_tse, scnt, ETHR_RWMTX_W_WAIT_FLG__,
is_rwmtx, is_freq_read);
+ ethr_leave_ts_event(tmp_tse);
goto done;
}
}
@@ -727,7 +731,7 @@ write_lock_wait(struct ethr_mutex_base_ *mtxb,
done:
if (tse)
- ethr_leave_ts_event(tse);
+ ethr_unpeek_ts_event(tse);
}
static int
@@ -1236,6 +1240,7 @@ ethr_cond_wait(ethr_cond *cnd, ethr_mutex *mtx)
}
ETHR_ASSERT(act == ETHR_RWMTX_W_WAIT_FLG__);
}
+ ETHR_ASSERT(tse->iflgs & ETHR_TS_EV_BUSY);
ethr_event_swait(&tse->event, scnt);
/* swait result: 0 || EINTR */
woken = 1;
@@ -1976,7 +1981,7 @@ rwmutex_try_complete_runlock(ethr_rwmutex *rwmtx,
tse_tmp = tse;
if (!tse_tmp)
- tse_tmp = ethr_get_ts_event();
+ tse_tmp = ethr_peek_ts_event();
if ((act & ETHR_RWMTX_WAIT_FLGS__) && (act & ~ETHR_RWMTX_WAIT_FLGS__) == 0)
goto check_waiters;
@@ -1996,7 +2001,7 @@ rwmutex_try_complete_runlock(ethr_rwmutex *rwmtx,
}
if (!tse)
- ethr_leave_ts_event(tse_tmp);
+ ethr_unpeek_ts_event(tse_tmp);
if (check_before_try) {
res = check_readers_array(rwmtx, six, length);
@@ -2135,7 +2140,7 @@ rwmutex_normal_rlock_wait(ethr_rwmutex *rwmtx, ethr_sint32_t initial)
{
ethr_sint32_t act = initial, exp;
int scnt, start_scnt;
- ethr_ts_event *tse = NULL;
+ ethr_ts_event *tse = ethr_get_ts_event();
int until_yield = ETHR_YIELD_AFTER_BUSY_LOOPS;
start_scnt = scnt = initial_spincount(&rwmtx->mtxb);
@@ -2154,7 +2159,6 @@ rwmutex_normal_rlock_wait(ethr_rwmutex *rwmtx, ethr_sint32_t initial)
while (act & (ETHR_RWMTX_W_FLG__|ETHR_RWMTX_W_WAIT_FLG__)) {
if (scnt <= 0) {
- tse = ethr_get_ts_event();
if (update_spincount(&rwmtx->mtxb, tse, &start_scnt, &scnt)) {
event_wait(&rwmtx->mtxb, tse, scnt,
ETHR_RWMTX_R_WAIT_FLG__, 1, 0);
@@ -2183,8 +2187,7 @@ rwmutex_normal_rlock_wait(ethr_rwmutex *rwmtx, ethr_sint32_t initial)
}
done:
- if (tse)
- ethr_leave_ts_event(tse);
+ ethr_leave_ts_event(tse);
}
static void
@@ -2283,8 +2286,10 @@ rwmutex_freqread_rlock_wait(ethr_rwmutex *rwmtx,
while (act & (ETHR_RWMTX_W_FLG__|ETHR_RWMTX_W_WAIT_FLG__)) {
if (scnt <= 0) {
if (update_spincount(&rwmtx->mtxb, tse, &start_scnt, &scnt)) {
- event_wait(&rwmtx->mtxb, tse, scnt,
+ ethr_ts_event *tmp_tse = ethr_use_ts_event(tse);
+ event_wait(&rwmtx->mtxb, tmp_tse, scnt,
ETHR_RWMTX_R_WAIT_FLG__, 1, 1);
+ ethr_leave_ts_event(tmp_tse);
return; /* Got it */
}
}
@@ -2799,9 +2804,9 @@ ethr_rwmutex_tryrlock(ethr_rwmutex *rwmtx)
case ETHR_RWMUTEX_TYPE_FREQUENT_READ:
case ETHR_RWMUTEX_TYPE_EXTREMELY_FREQUENT_READ: {
- ethr_ts_event *tse = ethr_get_ts_event();
+ ethr_ts_event *tse = ethr_peek_ts_event();
res = rwmutex_freqread_rlock(rwmtx, tse, 1);
- ethr_leave_ts_event(tse);
+ ethr_unpeek_ts_event(tse);
break;
}
}
@@ -2859,9 +2864,9 @@ ethr_rwmutex_rlock(ethr_rwmutex *rwmtx)
case ETHR_RWMUTEX_TYPE_FREQUENT_READ:
case ETHR_RWMUTEX_TYPE_EXTREMELY_FREQUENT_READ: {
- ethr_ts_event *tse = ethr_get_ts_event();
+ ethr_ts_event *tse = ethr_peek_ts_event();
rwmutex_freqread_rlock(rwmtx, tse, 0);
- ethr_leave_ts_event(tse);
+ ethr_unpeek_ts_event(tse);
break;
}
}
@@ -2901,7 +2906,7 @@ ethr_rwmutex_runlock(ethr_rwmutex *rwmtx)
case ETHR_RWMUTEX_TYPE_FREQUENT_READ:
case ETHR_RWMUTEX_TYPE_EXTREMELY_FREQUENT_READ: {
- ethr_ts_event *tse = ethr_get_ts_event();
+ ethr_ts_event *tse = ethr_peek_ts_event();
act = rwmutex_freqread_rdrs_dec_read_relb(rwmtx, tse);
@@ -2915,7 +2920,7 @@ ethr_rwmutex_runlock(ethr_rwmutex *rwmtx)
rwmutex_freqread_rdrs_dec_chk_wakeup(rwmtx, tse, act);
}
- ethr_leave_ts_event(tse);
+ ethr_unpeek_ts_event(tse);
break;
}
}
diff --git a/erts/lib_src/pthread/ethr_event.c b/erts/lib_src/pthread/ethr_event.c
index 464875570a..9d6e26fd81 100644
--- a/erts/lib_src/pthread/ethr_event.c
+++ b/erts/lib_src/pthread/ethr_event.c
@@ -361,7 +361,7 @@ wait__(ethr_event *e, int spincount, ethr_sint64_t timeout)
if (spincount == 0)
goto set_event_off_waiter;
}
- if (timeout == 0)
+ else if (timeout == 0)
return ETIMEDOUT;
else {
#ifdef ETHR_HAVE_ETHR_GET_MONOTONIC_TIME
diff --git a/erts/lib_src/pthread/ethread.c b/erts/lib_src/pthread/ethread.c
index dbf1b56683..f7262c02b1 100644
--- a/erts/lib_src/pthread/ethread.c
+++ b/erts/lib_src/pthread/ethread.c
@@ -47,6 +47,10 @@
#include <limits.h>
+#if defined (__HAIKU__)
+#include <os/kernel/OS.h>
+#endif
+
#define ETHR_INLINE_FUNC_NAME_(X) X ## __
#define ETHREAD_IMPL__
@@ -83,7 +87,7 @@ typedef struct {
void *prep_func_res;
size_t stacksize;
char *name;
- char name_buff[16];
+ char name_buff[32];
} ethr_thr_wrap_data__;
static void *thr_wrapper(void *vtwd)
@@ -98,7 +102,7 @@ static void *thr_wrapper(void *vtwd)
ethr_set_stacklimit__(&c, twd->stacksize);
- result = (ethr_sint32_t) ethr_make_ts_event__(&tsep);
+ result = (ethr_sint32_t) ethr_make_ts_event__(&tsep, 0);
if (result == 0) {
tsep->iflgs |= ETHR_TS_EV_ETHREAD;
@@ -331,13 +335,26 @@ ethr_thr_create(ethr_tid *tid, void * (*func)(void *), void *arg,
#endif
ethr_atomic32_init(&twd.result, (ethr_sint32_t) -1);
- twd.tse = ethr_get_ts_event();
twd.thr_func = func;
twd.arg = arg;
twd.stacksize = 0;
if (opts && opts->name) {
- snprintf(twd.name_buff, 16, "%s", opts->name);
+ size_t nlen = sizeof(twd.name_buff);
+#ifdef __HAIKU__
+ if (nlen > B_OS_NAME_LENGTH)
+ nlen = B_OS_NAME_LENGTH;
+#else
+ /*
+ * Length of 16 is known to work. At least pthread_setname_np()
+ * is documented to fail on too long name string, but documentation
+ * does not say what the limit is. Do not have the time to dig
+ * further into that now...
+ */
+ if (nlen > 16)
+ nlen = 16;
+#endif
+ snprintf(twd.name_buff, nlen, "%s", opts->name);
twd.name = twd.name_buff;
} else
twd.name = NULL;
@@ -346,6 +363,8 @@ ethr_thr_create(ethr_tid *tid, void * (*func)(void *), void *arg,
if (res != 0)
return res;
+ twd.tse = ethr_get_ts_event();
+
/* Error cleanup needed after this point */
/* Schedule child thread in system scope (if possible) ... */
@@ -426,6 +445,7 @@ ethr_thr_create(ethr_tid *tid, void * (*func)(void *), void *arg,
/* Cleanup... */
error:
+ ethr_leave_ts_event(twd.tse);
dres = pthread_attr_destroy(&attr);
if (res == 0)
res = dres;
@@ -498,6 +518,14 @@ ethr_setname(char *name)
pthread_set_name_np(ethr_self(), name);
#elif defined(ETHR_HAVE_PTHREAD_SETNAME_NP_1)
pthread_setname_np(name);
+#elif defined(__HAIKU__)
+ thread_id haiku_tid;
+ haiku_tid = get_pthread_thread_id(ethr_self());
+ if (!name) {
+ rename_thread (haiku_tid, "");
+ } else {
+ rename_thread (haiku_tid, name);
+ }
#endif
}
@@ -507,12 +535,35 @@ ethr_equal_tids(ethr_tid tid1, ethr_tid tid2)
return pthread_equal((pthread_t) tid1, (pthread_t) tid2);
}
-
/*
* Thread specific events
*/
ethr_ts_event *
+ethr_lookup_ts_event__(int busy_dup)
+{
+ return ethr_lookup_ts_event____(busy_dup);
+}
+
+ethr_ts_event *
+ethr_peek_ts_event(void)
+{
+ return ethr_peek_ts_event__();
+}
+
+void
+ethr_unpeek_ts_event(ethr_ts_event *tsep)
+{
+ ethr_unpeek_ts_event__(tsep);
+}
+
+ethr_ts_event *
+ethr_use_ts_event(ethr_ts_event *tsep)
+{
+ return ethr_use_ts_event__(tsep);
+}
+
+ethr_ts_event *
ethr_get_ts_event(void)
{
return ethr_get_ts_event__();
diff --git a/erts/lib_src/win/ethread.c b/erts/lib_src/win/ethread.c
index aa43e03435..dbe7ae6f44 100644
--- a/erts/lib_src/win/ethread.c
+++ b/erts/lib_src/win/ethread.c
@@ -106,7 +106,7 @@ static unsigned __stdcall thr_wrapper(LPVOID vtwd)
ethr_set_stacklimit__(&c, twd->stacksize);
- result = (ethr_sint32_t) ethr_make_ts_event__(&tsep);
+ result = (ethr_sint32_t) ethr_make_ts_event__(&tsep, 0);
if (result == 0) {
tsep->iflgs |= ETHR_TS_EV_ETHREAD;
@@ -607,6 +607,30 @@ ethr_tsd_get(ethr_tsd_key key)
*/
ethr_ts_event *
+ethr_lookup_ts_event__(int busy_dup)
+{
+ return ethr_lookup_ts_event____(busy_dup);
+}
+
+ethr_ts_event *
+ethr_peek_ts_event(void)
+{
+ return ethr_peek_ts_event__();
+}
+
+void
+ethr_unpeek_ts_event(ethr_ts_event *tsep)
+{
+ ethr_unpeek_ts_event__(tsep);
+}
+
+ethr_ts_event *
+ethr_use_ts_event(ethr_ts_event *tsep)
+{
+ return ethr_use_ts_event__(tsep);
+}
+
+ethr_ts_event *
ethr_get_ts_event(void)
{
return ethr_get_ts_event__();
@@ -618,10 +642,3 @@ ethr_leave_ts_event(ethr_ts_event *tsep)
ethr_leave_ts_event__(tsep);
}
-ethr_ts_event *
-ethr_create_ts_event__(void)
-{
- ethr_ts_event *tsep;
- ethr_make_ts_event__(&tsep);
- return tsep;
-}
diff --git a/erts/lib_src/yielding_c_fun/.gitignore b/erts/lib_src/yielding_c_fun/.gitignore
new file mode 100644
index 0000000000..eb74315e49
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/.gitignore
@@ -0,0 +1,16 @@
+*.o
+*~
+bin/yielding_c_fun.bin
+core
+test/test_trap_out.c
+GPATH
+GRTAGS
+GTAGS
+test/tmp_dir
+compile_commands.json
+compile_commands_old.json
+CMakeLists.txt
+cmake_mkdir/
+.idea/
+cmake-build-debug/
+ycf_malloc_log.txt \ No newline at end of file
diff --git a/erts/lib_src/yielding_c_fun/GNUmakefile b/erts/lib_src/yielding_c_fun/GNUmakefile
new file mode 100644
index 0000000000..c87a945cf6
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/GNUmakefile
@@ -0,0 +1,212 @@
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+
+#
+# Author: Kjell Winblad
+#
+
+#
+# Note: The main target of this makefile generates another makefile
+# which is named "Makefile". The generated file "Makefile" should be
+# compatible with other make implementations than GNU make while this
+# file is only compatible with GNU make. GNU make will automatically
+# use this file as makefile when executed in the directory where this
+# file is located.
+#
+# Erlang/OTP note: The build system for Erlang/OTP does not use this
+# file to build Yielding C Fun (YCF). The file "main_target.mk"
+# contains the necessary rules to build YCF and is included both by
+# this file and the Erlang/OTP file
+# $(ERL_TOP)/erts/lib_src/Makefile.in. This file is intended for
+# building YCF independently of Erlang/OTP. This file also contains
+# several special make targets (e.g., to invoke tests).
+#
+
+
+ifdef MODERN_CC
+ EXTRA_C_FLAGS = -g -O02 -std=c99 -pedantic -Wall
+endif
+
+ifdef CC_32_BIT
+ EXTRA_C_FLAGS = -m32 -g -O03 -std=c99 -pedantic -Wall
+endif
+
+ifdef USE_GC
+ USE_GC_STRING = -use_gc
+endif
+
+ifdef ADD_SAN
+ V_CC = clang
+ EXTRA_C_FLAGS = -std=c99 -Wall -pedantic -g -O00 -fsanitize-blacklist=lib/simple_c_gc/.misc/clang_blacklist.txt -fsanitize=address -fno-omit-frame-pointer
+ USE_GC_STRING = -use_gc
+endif
+
+ifdef MEM_SAN
+ V_CC = clang
+ EXTRA_C_FLAGS = -std=c99 -Wall -pedantic -g -O00 -fsanitize-blacklist=lib/simple_c_gc/.misc/clang_blacklist.txt -fsanitize=memory -fno-omit-frame-pointer
+endif
+
+ifdef UB_SAN
+ V_CC = clang
+ EXTRA_C_FLAGS = -std=c99 -Wall -pedantic -g -O00 -fsanitize-blacklist=lib/simple_c_gc/.misc/clang_blacklist.txt -fsanitize=undefined -fno-omit-frame-pointer
+endif
+
+
+YCF_SOURCE_DIR = .
+
+ifndef V_CC
+ V_CC = cc
+endif
+
+ifndef V_LD
+ V_LD = $(CC)
+endif
+
+CFLAGS = $(EXTRA_C_FLAGS)
+
+all: ./bin/yielding_c_fun.bin Makefile
+
+include main_target.mk
+
+##############################################################
+# Generate a simple Makefile intended to work with other make
+# implementations than GNU make
+##############################################################
+
+Makefile: $(YCF_SOURCES)
+ echo '#' > Makefile
+ echo '# !!!!!!!!!!! OBS Generated by GNUmakefile !!!!!!!!!!!!' >> Makefile
+ echo '# This file is supposed to be compatible with as many make implementations as possible' >> Makefile
+ echo '# Do not modify this file manually unless you cannot use GNU make.' >> Makefile
+ echo '# Instead, just run GNU make in the directory where this file is located' >> Makefile
+ echo '# and a new version of this file will be generated when needed.' >> Makefile
+ echo '# GNU make will use the file GNUmakefile which is located in the same' >> Makefile
+ echo '# directory.' >> Makefile
+ echo '#' >> Makefile
+ echo '#' >> Makefile
+ echo '# Erlang/OTP note: The build system for Erlang/OTP does not use this' >> Makefile
+ echo '# file to build Yielding C Fun (YCF). The file "main_target.mk"' >> Makefile
+ echo '# contains the necessary rules to build YCF and is included both by' >> Makefile
+ echo '# this file and the Erlang/OTP file' >> Makefile
+ echo '# $$(ERL_TOP)/erts/lib_src/Makefile.in. This file is intended for' >> Makefile
+ echo '# building YCF independently of Erlang/OTP.' >> Makefile
+ echo '#' >> Makefile
+ echo >> Makefile
+ O_FILES="";\
+ for FILE in $(YCF_SOURCES);\
+ do\
+ OFILE_FULL=`echo $$FILE | sed 's/\.c/.o/'` ; \
+ OFILE=`basename $$OFILE_FULL` ; \
+ O_FILES="$$O_FILES $$OFILE" ; \
+ done ; \
+ echo $(YCF_EXECUTABLE)':' $$O_FILES >> Makefile ; \
+ echo ' $$(CC) $(YCF_INCLUDE_DIRS) $$(CFLAGS) -o '$(YCF_EXECUTABLE)' ' $$O_FILES >> Makefile
+ for FILE in $(YCF_SOURCES);\
+ do\
+ echo >> Makefile ; \
+ OFILE_FULL=`echo $$FILE | sed 's/\.c/.o/'` ; \
+ OFILE=`basename $$OFILE_FULL` ; \
+ echo "$$OFILE: $$FILE" >> Makefile ;\
+ echo ' $$(CC) $(YCF_INCLUDE_DIRS) $$(CFLAGS) ' -c -o $$OFILE $$FILE >> Makefile ;\
+ done
+ echo >> Makefile
+
+##############################################
+# Special targets for testing etc:
+##############################################
+
+.PHONY: all clean test run_test_continusly CMakeLists.txt cmake_compile clang_format test_add_san test_mem_san test_modern_cc test_sanitizers test_gcc_clang_tcc clang_tidy test_bmake test_all
+
+test: $(YCF_EXECUTABLE)
+ ./test/test.sh $(USE_GC_STRING) ;\
+ RESULT=$$? &&\
+ (exit $$RESULT) &&\
+ printf "\n\n\033[0;32mALL TESTS PASSED!\033[0m\n\n\n" ||\
+ printf "\n\n\033[0;31mTEST FAILED!\033[0m\n\n\n" &&\
+ exit $$RESULT
+
+test_add_san:
+ make clean && \
+ make V_CC=clang V_LD=clang ADD_SAN=1 test
+
+test_mem_san:
+ make clean && \
+ make V_CC=clang V_LD=clang MEM_SAN=1 test
+
+test_ub_san:
+ make clean && \
+ make V_CC=clang V_LD=clang UB_SAN=1 test
+
+test_modern_cc:
+ make clean && \
+ make V_CC=clang V_LD=clang MODERN_CC=1 test
+
+test_sanitizers:
+ make test_add_san && \
+ make test_mem_san && \
+ make test_ub_san
+
+test_gcc_clang_tcc:
+ make V_CC=gcc V_LD=gcc EXTRA_C_FLAGS="-g -O01 -std=c99 -pedantic -Wall -Werror" clean $(YCF_EXECUTABLE) && \
+ make V_CC=clang V_LD=clang EXTRA_C_FLAGS="-g -O01 -std=c99 -pedantic -Wall -Werror" clean $(YCF_EXECUTABLE) && \
+ make V_CC=tcc V_LD=tcc EXTRA_C_FLAGS="-g -O01 -std=c99 -pedantic -Wall -Werror" clean $(YCF_EXECUTABLE)
+
+test_32_bit:
+ make clean && \
+ make CC_32_BIT=1 test && \
+ make clean
+
+# sudo apt-get install bmake
+# test that something else than GNU make can compile the tool
+test_bmake:
+ make clean && \
+ make Makefile && \
+ bmake
+
+test_all:
+ make test_gcc_clang_tcc && \
+ make clang_tidy && \
+ make test_sanitizers && \
+ make test_modern_cc && \
+ make test_32_bit && \
+ make test_bmake
+
+run_test_continusly:
+ inotifywait -e close_write,moved_to,create -m ./*.c ./*.h -m test -m test/examples | while read -r directory events filename; do gtags ; make test_all ; done
+
+cmake_compile: CMakeLists.txt
+ mkdir cmake_mkdir || true
+ cd cmake_mkdir && cmake ..
+
+clang_tidy:
+ (ls *.c ; echo lib/tiny_regex_c/re.c ; echo lib/simple_c_gc/simple_c_gc.c) | xargs -I{} -n1 clang-tidy -warnings-as-errors=* {} -- $(YCF_INCLUDE_DIRS) $(YCF_CFLAGS)
+
+clang_format:
+ clang-format -style="{BasedOnStyle: LLVM}" -i *.c *.h
+
+clean:
+ rm -f lib/simple_c_gc/*.o lib/tiny_regex_c/*.o ./*.o ./*~ core trap parse $(YCF_EXECUTABLE) CMakeLists.txt
+
+# Produce a CMakeLists.txt to build with cmake
+CMakeLists.txt: $(YCF_SOURCES)
+ echo "cmake_minimum_required (VERSION 2.6)" > CMakeLists.txt
+ echo "project (YIELDING_C_FUN C)" >> CMakeLists.txt
+ echo "add_executable(cmake.out " >> CMakeLists.txt
+ echo $(YCF_SOURCES) >> CMakeLists.txt
+ echo ")" >> CMakeLists.txt
+
diff --git a/erts/lib_src/yielding_c_fun/Makefile b/erts/lib_src/yielding_c_fun/Makefile
new file mode 100644
index 0000000000..71cef1b320
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/Makefile
@@ -0,0 +1,54 @@
+#
+# !!!!!!!!!!! OBS Generated by GNUmakefile !!!!!!!!!!!!
+# This file is supposed to be compatible with as many make implementations as possible
+# Do not modify this file manually unless you cannot use GNU make.
+# Instead, just run GNU make in the directory where this file is located
+# and a new version of this file will be generated when needed.
+# GNU make will use the file GNUmakefile which is located in the same
+# directory.
+#
+#
+# Erlang/OTP note: The build system for Erlang/OTP does not use this
+# file to build Yielding C Fun (YCF). The file "main_target.mk"
+# contains the necessary rules to build YCF and is included both by
+# this file and the Erlang/OTP file
+# $(ERL_TOP)/erts/lib_src/Makefile.in. This file is intended for
+# building YCF independently of Erlang/OTP.
+#
+
+./bin/yielding_c_fun.bin: simple_c_gc.o re.o ycf_lexer.o ycf_main.o ycf_node.o ycf_parser.o ycf_printers.o ycf_string.o ycf_symbol.o ycf_utils.o ycf_yield_fun.o
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -o ./bin/yielding_c_fun.bin simple_c_gc.o re.o ycf_lexer.o ycf_main.o ycf_node.o ycf_parser.o ycf_printers.o ycf_string.o ycf_symbol.o ycf_utils.o ycf_yield_fun.o
+
+simple_c_gc.o: ./lib/simple_c_gc/simple_c_gc.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o simple_c_gc.o ./lib/simple_c_gc/simple_c_gc.c
+
+re.o: ./lib/tiny_regex_c/re.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o re.o ./lib/tiny_regex_c/re.c
+
+ycf_lexer.o: ./ycf_lexer.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o ycf_lexer.o ./ycf_lexer.c
+
+ycf_main.o: ./ycf_main.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o ycf_main.o ./ycf_main.c
+
+ycf_node.o: ./ycf_node.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o ycf_node.o ./ycf_node.c
+
+ycf_parser.o: ./ycf_parser.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o ycf_parser.o ./ycf_parser.c
+
+ycf_printers.o: ./ycf_printers.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o ycf_printers.o ./ycf_printers.c
+
+ycf_string.o: ./ycf_string.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o ycf_string.o ./ycf_string.c
+
+ycf_symbol.o: ./ycf_symbol.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o ycf_symbol.o ./ycf_symbol.c
+
+ycf_utils.o: ./ycf_utils.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o ycf_utils.o ./ycf_utils.c
+
+ycf_yield_fun.o: ./ycf_yield_fun.c
+ $(CC) -I. -I./lib/tiny_regex_c -I./lib/simple_c_gc $(CFLAGS) -c -o ycf_yield_fun.o ./ycf_yield_fun.c
+
diff --git a/erts/lib_src/yielding_c_fun/README.md b/erts/lib_src/yielding_c_fun/README.md
new file mode 100644
index 0000000000..f63475e6a8
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/README.md
@@ -0,0 +1,610 @@
+Yielding C Fun
+==============
+
+Introduction
+------------
+
+Yielding C Fun (YCF) is a tool that transforms functions written in a
+subset of the C programming language so that they become yieldable. A
+yieldable function can be suspended/yielded/paused/trapped (either
+automatically or where the user has inserted a particular statement)
+and then be resumed at a later point. Yileldable functions are also
+called [coroutines](https://en.wikipedia.org/wiki/Coroutine).
+
+Difference Between Yielding C Fun and Coroutine Libraries
+---------------------------------------------------------
+
+Several libraries implement [coroutine support for the C programming
+language](https://en.wikipedia.org/wiki/Coroutine#Implementations_for_C)
+(e.g., \[[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11],
+[12], [13]\]). These libraries either rely on platform-specific code
+or do not save call stack variables. Yielding C Fun (YCF) does not
+have any of these two limitations. YCF can accomplish this as it is a
+source-to-source transformation tool and not only a library.
+
+YCF has been created to make it easier to implement yielding Erlang
+[NIFs](http://erlang.org/doc/tutorial/nif.html) and
+[BIFs](http://erlang.org/pipermail/erlang-questions/2009-October/046899.html)
+(i.e., Erlang functions that are written in C). Below are examples of
+YCF features that are useful when implementing yielding Erlang NIFs
+and BIFs:
+
+ * YCF automatically generates a destroy function for each yieldable
+ function. The destroy function frees resources that are used by a
+ suspended function. The destroy function is useful when a suspended
+ function needs to abort (e.g., when the Erlang process that invoked
+ the function has died).
+
+ * YCF can automatically insert code that yields functions after a
+ user specifiable number of loop iterations and goto statements.
+
+ * YCF has a hook system that lets the user insert code that is
+ triggered when certain events happen (e.g., when a function
+ yields).
+
+The main limitations of YCF are that it cannot handle all valid C code
+and that it cannot make library functions without source code
+yieldable. Pointers to stack-allocated data are also not allowed (YCF
+has a memory allocation function called `YCF_STACK_ALLOC` to work
+around this issue).
+
+Requirements
+------------
+
+* A C99 compatible C compiler
+* make (optional but useful for building)
+
+Compile and Test
+----------------
+
+Build the executable `$YCF_ROOT/bin/yielding_c_fun.bin`:
+
+ make
+
+Build the executable and run all tests:
+
+ make test
+
+Getting Started
+---------------
+
+A brief introduction tutorial can be found
+[here](doc/thread_tutorial.md). This tutorial is a perfect place to
+start!
+
+The "[test/examples/](test/examples/)" folder in this repository
+contains many small examples that are useful when learning about
+YCF. YCF's automatic tests use these examples as well. The driver for
+these tests is located in `test/test.sh`.
+
+[This Erlang NIF example](test/examples/sha256_erlang_nif/) shows how
+one can use YCF to write a yielding Erlang NIF library.
+
+
+Command Line Parameters
+-----------------------
+
+```
+Usage: yielding_c_fun [-h]
+ yielding_c_fun [-use_gc [-print_gc_info]]
+ [-log_max_mem_usage log_file]
+ [(( -f | -frec | -fnoauto ) function_name)...
+ [-output_file_name output_file]
+ [-header_file_name header_file]
+ [-debug]
+ [-only_yielding_funs]
+ [-static_aux_funs]
+ input_c_file]]
+```
+
+* `-h`
+
+ Print help text
+
+* `-use_gc`
+
+ Use garbage collection. The garbage collection system assumes that
+ the C call stack consists of a continuous memory block and is
+ therefore not enabled by default even though this assumption is
+ valid on all major platforms. YCF does not reclaim any allocated
+ memory if the `-use_gc` flag is not set.
+
+* `-print_gc_info`
+
+ (For debugging) Print garbage collection information to `stderr`
+
+* `-log_max_mem_usage log_file`
+
+ (For debugging) Print the peak memory usage of the tool to the file
+ `log_file`
+
+* `-fnoauto function_name`
+
+ Generate a yieldable version of the function named
+ function_name. The user can use `YCF_YIELD()`,
+ `YCF_YIELD_NO_REDS()`, and `YCF_CONSUME_REDS(N)` to control
+ when and where the function should yield. See the section titled
+ "Special Statements and Macros" for more information.
+
+* `-f function_name`
+
+ Generate a yieldable version of the function named
+ `function_name`. The generated function automatically decrements the
+ reduction counter by one at the beginning of loop bodies and before
+ goto statements. The function yields automatically if the reduction
+ counter reaches a value that is zero or smaller after it has been
+ decremented.
+
+* `-frec function_name`
+
+ Same as the -f option with the exception that the generated function
+ also decrements one reduction before calls to other yieldable
+ functions and before returning. The function yields automatically if
+ the reduction counter reaches a value that is zero or smaller after
+ it has been decremented.
+
+* `-output_file_name output_file`
+
+ Output the generated code to a file named output_file. The output
+ is printed to standard output if this parameter is unspecified.
+
+* `-header_file_name header_file`
+
+ Generate a header file containing only declarations for the generated
+ functions and write the header file to the file named header_file.
+
+* `-debug`
+
+ Generate debug code that executes when a function yields. The debug
+ code searches the call stack of the yielding functions for pointers
+ to data that is allocated on the call stack. The program crashes
+ with an error message if any such pointer is found.
+
+* `-only_yielding_funs`
+
+ Print only the generated functions and struct declarations. The
+ default behavior is to insert the generated functions into a copy of
+ the input source file.
+
+* `-static_aux_funs`
+
+ Make the generated auxiliary functions static (i.e., local to the C
+ compilation unit)
+
+* `input_c_file`
+
+ The source file containing the functions that YCF shall create
+ yieldable versions of. YCF does not do any macro expansions. There
+ are several restrictions on the code that YCF can handle that are
+ described in the "Code Restrictions" section below.
+
+Generated Functions
+-------------------
+
+YCF generates three functions for each function name that it is
+given. These functions have the original function name as prefix and
+different suffixes. Descriptions of the functions that YCF generates
+follows below:
+
+
+```c
+
+/* Shall return a pointer to a memory block of size size bytes. */
+typedef void* (*ycf_yield_alloc_type) (size_t size ,void* ctx);
+/* Shall free the memory block which block points to. */
+typedef void (*ycf_yield_free_type) (void* block,void* ctx);
+
+return_type_of_orginal_fun
+original_fun_name_ycf_gen_yielding(
+ long * ycf_nr_of_reductions,
+ void ** ycf_yield_state,
+ void * ycf_extra_context,
+ ycf_yield_alloc_type ycf_yield_alloc,
+ ycf_yield_free_type ycf_yield_free,
+ void * ycf_yield_alloc_free_context,
+ size_t ycf_stack_alloc_size_or_max_size,
+ void* ycf_stack_alloc_data
+ paremeters_of_orginal_function);
+```
+
+The generated function with suffix `_ycf_gen_yielding` initiates the
+call of a yieldable function. Its parameters and return types are
+described below:
+
+* `return_type_of_orginal_fun`
+
+ The return type is the same as the return type of the original
+ function. The return value is the return value of the function if
+ the `_ycf_gen_yielding` function returns without yielding and is
+ uninitialized otherwise.
+
+* `long * ycf_nr_of_reductions`
+
+ (input/output parameter) Gives the yieldable function the number of
+ reductions it can consume before yielding and is also used to write
+ back the number of reductions that are left when the function
+ returns or yields.
+
+* `void ** ycf_yield_state`
+
+ (input/output parameter) Should be a pointer to a pointer to NULL
+ when the `_ycf_gen_yielding` function is called. The value pointed
+ to by ycf_yield_state is NULL when the `_ycf_gen_yielding` function
+ has returned if it did not yield and points to the yield state
+ otherwise.
+
+* `void * ycf_extra_context`
+
+ This parameter is useful if the yieldable function needs to access
+ data that may change when it resumes after having been yielded. The
+ extra context can be accessed from within the yieldable function
+ with the `YCF_GET_EXTRA_CONTEXT()` function.
+
+* `ycf_yield_alloc_type ycf_yield_alloc`
+
+ A memory allocator function that is used by the yieldable function
+ to allocate memory (e.g., to save the state when the function
+ yields).
+
+* `ycf_yield_free ycf_yield_free`
+
+ A memory free function that should free a memory block that has been
+ allocated with ycf_yield_alloc.
+
+* `void * ycf_yield_alloc_free_context`
+
+ A context that is passed as the second argument to `ycf_yield_alloc`
+ and `ycf_yield_free`.
+
+* `size_t ycf_stack_alloc_size_or_max_size`
+
+ The max number of total bytes that can be allocated with the special
+ allocator `YCF_STACK_ALLOC(n)`. This can be set to 0 if
+ `YCF_STACK_ALLOC(n)` is unused. See the documentation of
+ `YCF_STACK_ALLOC(n)` below for more information.
+
+* `void* ycf_stack_alloc_data`
+
+ A pointer to a data block that will be used by
+ `YCF_STACK_ALLOC(n)`. The value of `ycf_stack_alloc_data` should be
+ `NULL` or a pointer to a data block that is least
+ `ycf_stack_alloc_size_or_max_size` bytes large if
+ `YCF_STACK_ALLOC(n)` is used within the yieldable function or any
+ yieldable function that is called by the yieldable function. The
+ `ycf_yield_alloc` and `ycf_yield_free` functions will be used to
+ automatically alloc and free a data block when needed, if
+ `ycf_stack_alloc_data` is set to `NULL`. The value of
+ `ycf_stack_alloc_data` does not matter if `YCF_STACK_ALLOC(n)` is
+ unused.
+
+* `paremeters_of_orginal_function`
+
+ Parameters that the original function takes will be placed in the
+ end of the parameter list of the `ycf_gen_yielding` function.
+
+
+```c
+return_type_of_orginal_fun
+original_fun_name_ycf_gen_continue(
+ long * ycf_nr_of_reduction,
+ void ** ycf_yield_state,
+ void * ycf_extra_context);
+```
+
+The generated function with the suffix `_ycf_gen_continue` is used to
+resume a yielded function. The descriptions of the parameters and
+return type for the `_ycf_gen_yielding` function above are valid for
+the `_ycf_gen_continue` function as well, with the exception that the
+parameter `ycf_yield_state` should point to a pointer to a yield state
+(created in the previous call to `_ycf_gen_yielding` or
+`_ycf_gen_continue`).
+
+```c
+void original_fun_name_ycf_gen_destroy(void * ycf_yield_state);
+```
+
+The `_gen_destroy` function frees the state of a yieldable function
+that has been suspended. Note that the parameter `ycf_yield_state`
+points directly to the yield state, unlike the parameter of the
+`_ycf_gen_yielding` and `_ycf_gen_continue` functions with the same
+name.
+
+
+
+The `YCF_YIELD_CODE_GENERATED` Macro
+------------------------------------
+
+YCF also generates code that defines the macro
+`YCF_YIELD_CODE_GENERATED`. This macro may be useful if one wants to
+compile one version of a program with yieldable functions and another
+without yieldable functions.
+
+Special Statements and Macros
+-----------------------------
+
+Some special statements and macros can be used from within a yieldable
+function. Descriptions of those follow below:
+
+* `YCF_YIELD();`
+
+ The `YCF_YIELD();` statement sets the reduction counter to zero
+ and yields the function when it is executed.
+
+* `YCF_YIELD_NO_REDS();`
+
+ The `YCF_YIELD_NO_REDS();` statement yields the function
+ without changing the reduction counter when it is executed.
+
+* `YCF_CONSUME_REDS(N);`
+
+ The `YCF_CONSUME_REDS(N);` statement decrements the
+ reductions counter by N and yields if the reduction counter is less
+ than or equal to zero after the decrement.
+
+* `YCF_STACK_ALLOC(N)`
+
+ The `YCF_STACK_ALLOC(N)` macro uses an allocator that is included in
+ the code generated by YCF to allocate a block with `N` bytes and
+ return a pointer to these bytes. A block that has been allocated
+ with `YCF_STACK_ALLOC(N)` is automatically freed when the function
+ that allocated the block returns. Memory blocks that are allocated
+ with `YCF_STACK_ALLOC(N)` do not move when a yieldable function
+ yields and then resumes again. In contrast, data that is allocated
+ directly on the call stack may move when a function yields and
+ resumes. `YCF_STACK_ALLOC(N)` can thus be useful if one wants to
+ port C code that has variables that point to data that is allocated
+ on the call stack. The parameters `ycf_stack_alloc_size_or_max_size`
+ and `ycf_stack_alloc_data` of the `_ycf_gen_yielding` function need
+ to be set correctly if `YCF_STACK_ALLOC(N)` is used. Please see the
+ description of the `_ycf_gen_yielding` function in the "Generated
+ Functions" section above for details about those parameters. Notice
+ also that the `-debug` flag that is described in the "Command Line
+ Parameters" section above can be useful when one wants to find out
+ if a function points to data that is allocated on the call stack of
+ a yieldable function.
+
+* `YCF_GET_EXTRA_CONTEXT()`
+
+ The `YCF_GET_EXTRA_CONTEXT()` macro returns the value of the
+ `ycf_extra_context` parameter that was passed to the latest call of
+ one of the corresponding `_ycf_gen_yielding` or `_ycf_gen_continue`
+ functions. See the "Generated Functions" section above for
+ information about the parameters of `_ycf_gen_yielding` and
+ `_ycf_gen_continue` functions.
+
+* `YCF_NR_OF_REDS_LEFT()`
+
+ The `YCF_NR_OF_REDS_LEFT()` macro returns the current value of
+ the reduction counter (a value of type `long`).
+
+* `YCF_SET_NR_OF_REDS_LEFT(NEW_NR_OF_REDS_LEFT)`
+
+ The `YCF_SET_NR_OF_REDS_LEFT(NEW_NR_OF_REDS_LEFT)` macro sets
+ the value that the reduction counter (which stores a value of type
+ `long`) to `NEW_NR_OF_REDS_LEFT`.
+
+* `YCF_MAX_NR_OF_REDS`
+
+ The `YCF_MAX_NR_OF_REDS` macro returns the maximum value that the
+ reduction counter may have.
+
+Code Restrictions
+-----------------
+
+YCF cannot parse all valid C code. The code restrictions that
+yieldable functions need to follow are described below. It is
+recommended to check that the generated code is correct.
+
+* **Declarations**
+
+ Variable declarations and parameters of yieldable functions need to
+ be in the following form:
+
+ ```
+ "(optional) type descriptor (i.e., struct, union or enum)"
+
+ "type name"
+
+ "(optional) one or more star characters (i.e., *)"
+
+ "variable name"
+
+ "(optional) one or more square brackets with a number inside (e.g, [3])"
+
+ "(optional) one or more empty square brackets (e.g, [])"
+
+ "(optional) equal sign followed by an expression (automatic array
+ initialization and struct initialization of the form
+ {.filed_name=value...} are not allowed)"
+
+ "semicolon"
+ ```
+
+ Here are some examples of declarations that are **correct**:
+
+ ```c
+ int var1;
+ int var2 = 1;
+ int var3 = var2 + 1;
+ int var4 = function(var3);
+ int * var5 = malloc(sizeof(int*));
+ int ** var6 = malloc(sizeof(int*)*10);
+ int ***** var7;
+ struct struct_name var8;
+ struct struct_name var9 = function2();
+ double var10[128];
+ double var11[128][];
+ double * var12[128];
+ ```
+
+ Here are examples of declarations that are **incorrect**:
+
+ ```c
+ int var1, var2;
+ int var1 = 1, var2 = 10;
+ void (*printer_t)(int);
+ ```
+
+ Note that one has to use a `typedef` to be able to declare a
+ function pointer variable.
+
+* **Pointers**
+
+ Pointers to call-stack-allocated data are not allowed. The
+ `YCF_YIELD_ALLOC(N)` function, which is described in the "Special
+ Statements and Macros" section above, can be used to work around
+ this limitation. The `-debug` flag that is described in the "Command
+ Line Parameters" section above, can be useful when one wants to find
+ out if a yieldable function points to call-stack-allocated data.
+
+
+* **Macros**
+
+ YCF does not expand macros so macros in functions that YCF
+ transforms should not "hide" variables or any other code that is
+ relevant for yielding.
+
+* **Calls to a Yieldable Function from Another Yieldable Function**
+
+ Calls to a yieldable function from another yieldable function need
+ to be in a form that YCF recognizes. Such calls need to be in one of
+ the following forms:
+
+ * As a separate statement:
+
+ Examples:
+ ```c
+ my_fun(my_param_expression + 1, 10);
+ my_fun2();
+ ```
+
+ * A separate assignment statement to a variable. The function call
+ expression may be negated but is not allowed to be nested in other
+ types of expressions.
+
+ Examples of **correct** ways of calling yieldable functions:
+ ```c
+ int var_name_1 = my_fun();
+ int var_name_2 = !my_fun();
+ var_name_3 = my_fun();
+ var_name_4 = !my_fun();
+ ```
+
+ Examples of **incorrect** ways of calling yieldable functions:
+ ```c
+ int var_name_1 = (my_fun());
+ var_name_2 = 1 + my_fun();
+ t->name = my_fun();
+ *ptr = my_fun();
+ ```
+
+ * As the expression of `while`-statements, `do-while`-statements or
+ 'if`-statements:
+
+ Examples of **correct** ways of calling yieldable functions:
+ ```c
+ if(my_fun()) printf("hej\n");
+ if(0) else if(my_fun()) printf("hej\n");
+ while(!my_fun()) printf("hej\n");
+ do { printf("hej\n"); } while(my_fun());
+ var_name_3 = my_fun();
+ var_name_4 = !my_fun();
+ ```
+
+ Examples of **incorrect** ways of calling yieldable functions:
+ ```
+ if(3+my_fun()) printf("hej\n");
+ if(hej=my_fun()) printf("hej\n");
+ ```
+
+ YCF ignores calls to yieldable functions that are not in any of
+ the forms described above, so it is important to check the
+ generated source code.
+
+Hooks
+-----
+
+It is possible to insert special hooks in yieldable functions. Hooks
+execute when certain events are happening. Hooks may read and write to
+variables (changes to variables are visible after the code block has
+executed). Hooks can be placed anywhere one can place a normal
+statement within the function body. There are two ways to write hooks:
+
+**Hook Style 1:**
+
+```c
+ YCF_SPECIAL_CODE_START(ON_EVENT_NAME);
+ printf("This will be printed when EVENT_NAME is happening\n");
+ YCF_SPECIAL_CODE_END();
+```
+
+**Hook Style 2:**
+
+```c
+ /*special_code_start:ON_EVENT_NAME*/
+ if(0){
+ printf("This will be printed when EVENT_NAME is happening\n");
+ }
+ /*special_code_end*/
+```
+
+The following hook events are currently available:
+
+* `ON_SAVE_YIELD_STATE`
+
+ Triggered when the function yields.
+
+* `ON_RESTORE_YIELD_STATE`
+
+ Triggered before a function resumes after a yield.
+
+* `ON_DESTROY_STATE`
+
+ Triggered if and when the corresponding `_ycf_gen_destroy` function
+ is executing.
+
+* `ON_DESTROY_STATE_OR_RETURN`
+
+ Triggered if and when the corresponding `_ycf_gen_destroy` function
+ for the function is executing or when the function is returning.
+
+* `ON_RETURN`
+
+ Triggered when the function is returning.
+
+License
+-------
+
+Yielding C Fun is released under the [Apache License
+2.0](http://www.apache.org/licenses/LICENSE-2.0).
+
+
+> Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+>
+> Licensed under the Apache License, Version 2.0 (the "License");
+> you may not use this file except in compliance with the License.
+> You may obtain a copy of the License at
+>
+> http://www.apache.org/licenses/LICENSE-2.0
+>
+> Unless required by applicable law or agreed to in writing, software
+> distributed under the License is distributed on an "AS IS" BASIS,
+> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+> See the License for the specific language governing permissions and
+> limitations under the License.
+
+
+
+[1]: http://swtch.com/libtask/ "libtask"
+[2]: http://xmailserver.org/libpcl.html
+[3]: https://web.archive.org/web/20060110123338/http://www.goron.de/~froese/coro/
+[4]: https://github.com/halayli/lthread
+[5]: http://dekorte.com/projects/opensource/libcoroutine/
+[6]: http://code.google.com/p/libconcurrency/libconcurrency
+[7]: http://software.schmorp.de/pkg/libcoro.html
+[8]: https://github.com/Adaptv/ribs2
+[9]: http://libdill.org/
+[10]: https://github.com/hnes/libaco
+[11]: https://byuu.org/library/libco/
+[12]: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
+[13]: https://github.com/jsseldenthuis/coroutine
diff --git a/erts/lib_src/yielding_c_fun/TODO b/erts/lib_src/yielding_c_fun/TODO
new file mode 100644
index 0000000000..f3ded2dd22
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/TODO
@@ -0,0 +1,4 @@
+* Refactor and beautify the code so it does not look like spaghetti
+* Handle "automatic" array size initialization
+* Handle struct initialization (e.g., struct t my_struct = {.field = hej});
+* Print warning or error message when calling yieldable function from a position that YCF can't handle
diff --git a/erts/lib_src/yielding_c_fun/bin/yielding_c_fun b/erts/lib_src/yielding_c_fun/bin/yielding_c_fun
new file mode 100755
index 0000000000..87745e7aad
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/bin/yielding_c_fun
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+
+#Code to find directory of this file from https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself
+SOURCE="${BASH_SOURCE[0]}"
+while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
+ DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
+ SOURCE="$(readlink "$SOURCE")"
+ [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
+done
+DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
+
+
+FOUND="no"
+
+for FILE in "$@"
+do
+ if [ "${FILE: -2}" == ".c" ]
+ then
+ if ! cc -c $FILE -o $DIR/a.out ; then
+ echo "$0: error: Could not compile file with cc"
+ exit 1
+ fi
+ rm $DIR/a.out
+ if [ $1 == "-rr" ]
+ then
+ rr record $DIR/yielding_c_fun.bin "${@:2}" > $DIR/TMP_OUT_WITH_RR
+ # rr does not print when stdout is redirected
+ cat $DIR/TMP_OUT_WITH_RR
+ rm $DIR/TMP_OUT_WITH_RR
+ else
+ $DIR/yielding_c_fun.bin $@
+ fi
+ fi
+ FOUND="yes"
+done
+
+if [ $FOUND == "no" ]
+then
+ echo "$0: error: Expected a file name with .c ending"
+ exit 1
+fi
diff --git a/erts/lib_src/yielding_c_fun/doc/thread_tutorial.md b/erts/lib_src/yielding_c_fun/doc/thread_tutorial.md
new file mode 100644
index 0000000000..c818032bc5
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/doc/thread_tutorial.md
@@ -0,0 +1,222 @@
+Yielding C Fun Thread Example Tutorial
+======================================
+
+This tutorial goes through how Yielding C Fun can be used to simulate
+multi-threading in a single thread. You can find the source code that
+we use in the tutorial below or in
+[../test/examples/thread_example.c](../test/examples/thread_example.c).
+
+```c
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD_NO_REDS()
+
+static int f_2(char* name, int n) {
+ for(int y = 0; y < 2; y++) {
+ printf("%s f_2: y=%d\n", name, y);
+ }
+ return n*2;
+}
+
+static void f_1(char* name, int x) {
+ YCF_YIELD_NO_REDS();
+ while (x > 0) {
+ int f_2_ret = f_2(name, x);
+ printf("%s f_1: x=%d f_2_ret=%d\n", name, x, f_2_ret);
+ x--;
+ }
+ printf("%s f_1: DONE\n", name);
+}
+
+static void* ycf_alloc(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+static void ycf_free(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] ) {
+#ifdef YCF_YIELD_CODE_GENERATED
+ long t1_nr_of_reds = 1;
+ void* t1_state = NULL;
+ long t2_nr_of_reds = 2;
+ void* t2_state = NULL;
+ long t3_nr_of_reds = 1000;
+ void* t3_state = NULL;
+ /* Start t1, t2 and t3*/
+ f_1_ycf_gen_yielding(&t1_nr_of_reds, &t1_state, NULL,
+ ycf_alloc, ycf_free, NULL, 0, NULL,
+ "t1", 2);
+ f_1_ycf_gen_yielding(&t2_nr_of_reds, &t2_state, NULL,
+ ycf_alloc, ycf_free, NULL, 0, NULL,
+ "t2", 4);
+ f_1_ycf_gen_yielding(&t3_nr_of_reds, &t3_state, NULL,
+ ycf_alloc, ycf_free, NULL, 0, NULL,
+ "t3", 2);
+ printf("THREADS STARTED\n");
+ /* Execute t1, t2 and t3*/
+ while (t1_state != NULL ||
+ t2_state != NULL ||
+ t3_state != NULL) {
+ t1_nr_of_reds = 1;
+ t2_nr_of_reds = 2;
+ if (t1_state != NULL) {
+ printf("SCHEDULING THREAD: t1\n");
+ f_1_ycf_gen_continue(&t1_nr_of_reds, &t1_state, NULL);
+ if (t1_state == NULL) {
+ printf("THREAD t1 DONE (number of reductions left = %ld)\n",
+ t1_nr_of_reds);
+ }
+ }
+ if (t2_state != NULL) {
+ printf("SCHEDULING THREAD: t2\n");
+ f_1_ycf_gen_continue(&t2_nr_of_reds, &t2_state, NULL);
+ if (t2_state == NULL) {
+ printf("THREAD t2 DONE (number of reductions left = %ld)\n",
+ t2_nr_of_reds);
+ }
+ }
+ if (t3_state != NULL) {
+ printf("SCHEDULING THREAD: t3\n");
+ f_1_ycf_gen_continue(&t3_nr_of_reds, &t3_state, NULL);
+ if (t3_state == NULL) {
+ printf("THREAD t3 DONE (number of reductions left = %ld)\n",
+ t3_nr_of_reds);
+ }
+ }
+ }
+#endif
+ (void)f_1;
+ printf("DONE\n");
+ return 0;
+}
+```
+
+To run this example, you need to compile YCF itself, if you have not
+done so already:
+
+ cd directory_where_the_ycf_source_code_is_located
+ YCF_ROOT=`pwd`
+ make
+
+Now, you can transform `test/examples/thread_example.c` by executing
+the following command:
+
+ "$YCF_ROOT"/bin/yielding_c_fun.bin \
+ -f f_1 -f f_2 \
+ -output_file_name modified_thread_example.c \
+ "$YCF_ROOT"/test/examples/thread_example.c
+
+A new file should now exist in your current directory called
+`modified_thread_example.c`. This file contains a transformed
+version of `test/examples/thread_example.c`. The parameters `-f
+f_1` and `-f f_2` tells YCF to generate yieldable versions of the
+functions named `f_1` and `f_2`.
+
+Before you inspect the generated file to see what the tool has done,
+it is smart to format the generated source code with `clang-format` or
+some other tool to make the code more readable:
+
+ clang-format -i modified_thread_example.c
+
+You can now open the generated source code in your favorite editor:
+
+ emacs modified_thread_example.c &
+
+The yieldable functions get the suffix `_ycf_gen_yielding`. For
+example, the yielding version of `f_1` is called
+`f_1_ycf_gen_yielding`. As you can see, several parameters have been
+added to the yieldable versions of `f_1`. We explain the first two of
+these parameters here. [The main documentation of YCF](../README.md)
+explains the rest of the parameters. The first parameter is an
+input/output parameter that tells the yieldable function how many
+reductions that can be "consumed" before it yields. The first
+parameter can also be used by the caller to get the number of
+reductions that have been consumed. The second parameter is also an
+input/output parameter that should be a pointer to a `NULL` pointer
+when the yieldable function is first started. The pointer that is
+pointed to by the second parameter is set to a value which is
+different from `NULL` when the function yields.
+
+Let us now compile and run the generated source code so we can see
+what is happening:
+
+
+ cc -g modified_thread_example.c
+
+```
+$ ./a.out
+THREADS STARTED
+SCHEDULING THREAD: t1
+SCHEDULING THREAD: t2
+SCHEDULING THREAD: t3
+t3 f_2: y=0
+t3 f_2: y=1
+t3 f_1: x=2 f_2_ret=4
+t3 f_2: y=0
+t3 f_2: y=1
+t3 f_1: x=1 f_2_ret=2
+t3 f_1: DONE
+THREAD t3 DONE (number of reductions left = 994)
+SCHEDULING THREAD: t1
+SCHEDULING THREAD: t2
+t2 f_2: y=0
+t2 f_2: y=1
+t2 f_1: x=4 f_2_ret=8
+SCHEDULING THREAD: t1
+t1 f_2: y=0
+SCHEDULING THREAD: t2
+t2 f_2: y=0
+SCHEDULING THREAD: t1
+t1 f_2: y=1
+t1 f_1: x=2 f_2_ret=4
+SCHEDULING THREAD: t2
+t2 f_2: y=1
+t2 f_1: x=3 f_2_ret=6
+SCHEDULING THREAD: t1
+SCHEDULING THREAD: t2
+t2 f_2: y=0
+t2 f_2: y=1
+t2 f_1: x=2 f_2_ret=4
+SCHEDULING THREAD: t1
+t1 f_2: y=0
+SCHEDULING THREAD: t2
+t2 f_2: y=0
+SCHEDULING THREAD: t1
+t1 f_2: y=1
+t1 f_1: x=1 f_2_ret=2
+t1 f_1: DONE
+THREAD t1 DONE (number of reductions left = 1)
+SCHEDULING THREAD: t2
+t2 f_2: y=1
+t2 f_1: x=1 f_2_ret=2
+t2 f_1: DONE
+THREAD t2 DONE (number of reductions left = 2)
+DONE
+$
+```
+
+We can see that all three calls to the yieldable version of `f_1`
+yield before printing anything. This is due to the
+`YCF_YIELD_NO_REDS()` statement at the beginning of `f_1`'s
+body. We can also see that the "thread" named `t3` runs to completion
+without yielding when we call `f_1_ycf_gen_continue` with `t3_state`
+in the while loop and that `t3` consumed (1000-994=6) reductions. This
+is expected as we gave `t3` 1000 reductions when we started it with
+the `f_1_ycf_gen_yielding` function. The "threads" `t1` and `t2` get
+much fewer reductions (i.e., 1 and 2) each time they get to execute
+and they are therefore interleaved.
+
+You can now experiment with other transformation options (e.g.,
+`-frec` and `-fnoauto`) and the special statements that you can use
+inside functions that can yield. All these options and statements are
+documented [here](../README.md).
+
+The best way to figure out how YCF works under the hood is probably to
+step through the transformed program in a debugger.
+
+Good Luck! \ No newline at end of file
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/.gitignore b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/.gitignore
new file mode 100644
index 0000000000..e72b2fe033
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/.gitignore
@@ -0,0 +1,11 @@
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+*~
+GPATH
+GRTAGS
+GTAGS
+test.bin
+.tmp_out \ No newline at end of file
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/.misc/clang_blacklist.txt b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/.misc/clang_blacklist.txt
new file mode 100644
index 0000000000..d212057d10
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/.misc/clang_blacklist.txt
@@ -0,0 +1,10 @@
+
+[memory]
+
+fun:scgc_mark_reachable_objects_in_region
+fun:reverse_bits
+
+[address]
+
+fun:scgc_mark_reachable_objects_in_region
+fun:reverse_bits
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/GIT_VERSION b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/GIT_VERSION
new file mode 100644
index 0000000000..8cd6ceac63
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/GIT_VERSION
@@ -0,0 +1,73 @@
+origin https://github.com/kjellwinblad/simple_c_gc.git (fetch)
+origin https://github.com/kjellwinblad/simple_c_gc.git (push)
+commit 76577516b6e9bd8e8f647d869fb19361f42f9f9f
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Wed Sep 25 16:04:40 2019 +0200
+
+ Fix more Microsoft VS C/C++ compiler errors
+
+commit 4b2734f051ca1386bb970af6b0308e5f2d338403
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Tue Sep 24 16:36:48 2019 +0200
+
+ Make compatible with Microsoft VS C/C++ compiler and fix make test
+
+commit 41e6f3558756d9b6da44ca5247049c23bceadad9
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Mon Sep 23 16:25:55 2019 +0200
+
+ Small fix
+
+commit 8fbd03f880a1eb741462c395e689d844f8d58e06
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Mon Sep 23 16:13:56 2019 +0200
+
+ Fix compile warnings when compiling on a 32-bit system
+
+commit fc051d92bbef9ad1a22f855bc24b46d4efadda93
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Tue Sep 10 15:14:20 2019 +0200
+
+ Add clang-tidy Makefile target
+
+commit c6c3c1adc24f500472c22aad7f467d0a925108f0
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Tue Sep 10 15:07:39 2019 +0200
+
+ Add undefined behavior sanitizer and fix undefined behavior
+
+commit c734e684030a24ae694ed1fe7477c0eb5719bc12
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Mon Sep 9 09:06:19 2019 +0200
+
+ Improve Makefile and add info support
+
+commit 286c6ace1a71f2b021b76b602d5bfe4813f0b519
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Thu Sep 5 12:08:19 2019 +0200
+
+ Make clang_format target .PHONY
+
+commit eacdda4f9c71b5d917111bb9285671739d1cb671
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Thu Sep 5 11:33:03 2019 +0200
+
+ Add clang-format make target and reformat files
+
+commit c617afa556535620633117bdd6cc232ecc8c62f2
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Thu Sep 5 11:07:17 2019 +0200
+
+ Small fixes
+
+commit fb043e17d6e63100ca89ec41c34f667d35da7fb8
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Thu Sep 5 10:55:18 2019 +0200
+
+ Fix compiler warning and make c99 compatible
+
+commit d4b8333a49e62d8ffdbf60ab8382de39209891e2
+Author: Kjell Winblad <kjellwinblad@gmail.com>
+Date: Sat Aug 17 05:59:27 2019 +0200
+
+ First version of Simple C GC
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/LICENSE b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/LICENSE
new file mode 100644
index 0000000000..261eeb9e9f
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/Makefile b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/Makefile
new file mode 100644
index 0000000000..1b326a9988
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/Makefile
@@ -0,0 +1,121 @@
+IDIR = .
+CC = cc
+
+ifdef MODERN_CC
+ EXTRA_C_FLAGS = -g -O03 -std=c99 -pedantic -Wall
+endif
+
+ifdef CC_32_BIT
+ EXTRA_C_FLAGS = -m32 -g -O03 -std=c99 -pedantic -Wall
+endif
+
+ifdef ADD_SAN
+ CC = clang
+ EXTRA_C_FLAGS = -std=c99 -Wall -pedantic -g -O00 -fsanitize-blacklist=.misc/clang_blacklist.txt -fsanitize=address -fno-omit-frame-pointer
+ USE_GC_STRING = -use_gc
+endif
+
+ifdef MEM_SAN
+ CC = clang
+ EXTRA_C_FLAGS = -std=c99 -Wall -pedantic -g -O00 -fsanitize-blacklist=.misc/clang_blacklist.txt -fsanitize=memory -fno-omit-frame-pointer
+endif
+
+ifdef UB_SAN
+ CC = clang
+ EXTRA_C_FLAGS = -std=c99 -Wall -pedantic -g -O00 -fsanitize-blacklist=.misc/clang_blacklist.txt -fsanitize=undefined -fno-omit-frame-pointer
+endif
+
+CFLAGS = -I$(IDIR) $(EXTRA_C_FLAGS)
+
+ODIR = .
+LDIR =
+
+_DEPS = bitreversal.h simple_c_gc.h chained_hash_set.h sorted_list_set.h
+DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
+
+_OBJ = simple_c_gc.o test.o
+OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
+C_FILES = $(patsubst %.o,%.c,$(_OBJ))
+
+$(ODIR)/%.o: %.c $(DEPS)
+ $(CC) -c -o $@ $< $(CFLAGS)
+
+test.bin: $(OBJ)
+ $(CC) -o $@ $^ $(CFLAGS)
+
+
+.PHONY: clean test run_test_continusly CMakeLists.txt cmake_compile clang_format test_add_san test_ub_san test_mem_san test_sanitizers test_modern_cc test_valgrind
+
+test: test.bin
+ ./test.bin ;\
+ RESULT=$$? &&\
+ (exit $$RESULT) &&\
+ printf "\n\n\033[0;32mALL TESTS PASSED!\033[0m\n\n\n" ||\
+ printf "\n\n\033[0;31mTEST FAILED!\033[0m\n\n\n" &&\
+ exit $$RESULT
+
+test_valgrind:
+ make clean && \
+ make EXTRA_C_FLAGS="-g -O01" && \
+ valgrind --undef-value-errors=no ./test.bin ;\
+ RESULT=$$? &&\
+ (exit $$RESULT) &&\
+ printf "\n\n\033[0;32mALL TESTS PASSED!\033[0m\n\n\n" ||\
+ printf "\n\n\033[0;31mTEST FAILED!\033[0m\n\n\n" &&\
+ exit $$RESULT
+
+test_add_san:
+ make clean && \
+ make ADD_SAN=1 test
+
+test_mem_san:
+ make clean && \
+ make MEM_SAN=1 test
+
+test_ub_san:
+ make clean && \
+ make UB_SAN=1 test
+
+test_sanitizers:
+ make test_add_san && \
+ make test_mem_san && \
+ make test_ub_san
+
+test_modern_cc:
+ make clean && \
+ make MODERN_CC=1 test
+
+test_32_bit:
+ make clean && \
+ make CC_32_BIT=1 test && \
+ make clean
+
+test_all:
+ make test_valgrind && \
+ make test_sanitizers && \
+ make test_modern_cc && \
+ make test_32_bit
+
+run_test_continusly:
+ inotifywait -e close_write,moved_to,create -m ./*.c ./*.h | while read -r directory events filename; do gtags ; make test ; done
+
+CMakeLists.txt: $(C_FILES)
+ echo "cmake_minimum_required (VERSION 2.6)" > CMakeLists.txt
+ echo "project (SIMPLE_C_GC)" >> CMakeLists.txt
+ echo "add_executable(cmake.out" >> CMakeLists.txt
+ echo $(C_FILES) >> CMakeLists.txt
+ echo ")" >> CMakeLists.txt
+
+
+cmake_compile: CMakeLists.txt
+ mkdir cmake_mkdir || true
+ cd cmake_mkdir && cmake ..
+
+clang_tidy:
+ ls *.c | xargs -I{} -n1 clang-tidy -warnings-as-errors=* {} -- $(CFLAGS)
+
+clang_format:
+ clang-format -style="{BasedOnStyle: LLVM}" -i *.c *.h
+
+clean:
+ rm -f $(ODIR)/*.o *~ core $(IDIR)/*~ test.bin CMakeLists.txt
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/README.md b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/README.md
new file mode 100644
index 0000000000..c1f6834505
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/README.md
@@ -0,0 +1,69 @@
+Simple C GC
+===========
+
+This is the readme file for Simple C GC. Simple C GC is a simple
+garbage collection system for the C programming language inspired by
+[Daniel Holden's article about Cello's garbage collection
+system](http://libcello.org/learn/garbage-collection).
+
+Usage
+-----
+
+The interface of Simple C GC consists of only two functions:
+
+```C
+/**
+ * This function starts code that will be garbage collected
+ *
+ * @param main The function that will be started and garbage collected
+ * @param argc Passed to main
+ * @param argv Passed to main
+ * @param my_malloc Simple C GC will use this to allocated memory
+ * @param my_free Simple C GC will use this to free memory
+ */
+int scgc_start_gced_code(int (*main)(int, char *[]),
+ int argc,
+ char *argv[],
+ void* (*my_malloc)( size_t ),
+ void (*my_free)( void* ));
+
+/**
+ * Allocate a new memory block that will be garbage collected
+ *
+ * @param size The size of the new memory block
+ */
+void* scgc_new(size_t size);
+```
+
+An example that illustrates how these functions can be used in
+practice is located in the `test.c` file.
+
+
+Notes
+-----
+
+The garbage collector assumes that all data that should be garbage
+collected is pointed to directly or indirectly from the "C stack" and
+that the "C stack" is implemented as a continuous block of memory.
+
+Compile and Test
+----------------
+
+ make test
+
+License
+-------
+
+ Copyright 2019 Kjell Winblad (kjellwinblad@gmail.com, http://winsh.me)
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/chained_hash_set.h b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/chained_hash_set.h
new file mode 100644
index 0000000000..ee508e5ccc
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/chained_hash_set.h
@@ -0,0 +1,307 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright 2019 Kjell Winblad (kjellwinblad@gmail.com, http://winsh.me).
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#ifndef CHAINED_HASH_SET_H
+#define CHAINED_HASH_SET_H
+
+
+#if defined(_MSC_VER)
+#define inline __inline
+#endif
+
+#include "sorted_list_set.h"
+#include <stdint.h>
+
+#define CHAIN_LENGHT_EXPAND_THRESHOLD 2
+#define CHAIN_LENGHT_SHRINK_THRESHOLD 0.5
+/*Must be power of two*/
+#define INITIAL_NUMBER_OF_BUCKETS 4
+
+typedef struct {
+ unsigned int keyPosition;
+ void *(*extract_key)(void *v, int keyPos);
+ unsigned int (*hash_key)(void *key);
+ bool (*are_equal)(void *v1, void *v2);
+ char *(*to_string)(void *v1);
+ void *(*malloc)(size_t size);
+ void (*free)(void *ptr);
+ unsigned int numberOfBuckets;
+ unsigned int expandTreshold;
+ unsigned int shrinkTreshold;
+ unsigned int size;
+ SortedListSetNode **buckets;
+} ChainedHashSet;
+
+/*
+ * The reverse_bits function is inspired by:
+ * https://stackoverflow.com/questions/746171/efficient-algorithm-for-bit-reversal-from-msb-lsb-to-lsb-msb-in-c
+ */
+static const unsigned char BitReverseTable256[] = {
+ 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0,
+ 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
+ 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4,
+ 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
+ 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC,
+ 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
+ 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 0x0A, 0x8A, 0x4A, 0xCA,
+ 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
+ 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6,
+ 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
+ 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1,
+ 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
+ 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9,
+ 0x39, 0xB9, 0x79, 0xF9, 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
+ 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD,
+ 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
+ 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3,
+ 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
+ 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7,
+ 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
+ 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF,
+ 0x3F, 0xBF, 0x7F, 0xFF};
+
+static inline uint32_t reverse_bits(uint32_t v) {
+ return (((uint32_t)BitReverseTable256[v & 0xff]) << 24) |
+ (((uint32_t)BitReverseTable256[(v >> 8) & 0xff]) << 16) |
+ (((uint32_t)BitReverseTable256[(v >> 16) & 0xff]) << 8) |
+ (((uint32_t)BitReverseTable256[(v >> 24) & 0xff]));
+}
+
+static inline void ch_set_increase_size(ChainedHashSet *set) {
+ set->size = set->size + 1;
+ if (set->size > set->expandTreshold) {
+ unsigned int oldNumberOfBuckets = set->numberOfBuckets;
+ unsigned int newNumberOfBuckets = oldNumberOfBuckets * 2;
+ unsigned int splitUpMask = reverse_bits(newNumberOfBuckets - 1) ^
+ reverse_bits(oldNumberOfBuckets - 1);
+ SortedListSetNode **newBuckets =
+ set->malloc(sizeof(SortedListSetNode *) * newNumberOfBuckets);
+ SortedListSetNode **oldBuckets = set->buckets;
+ SortedListSetNode *moveTemp;
+ for (unsigned int i = 0; i < oldNumberOfBuckets; i++) {
+ moveTemp = sl_set_split_opt(&oldBuckets[i], splitUpMask);
+ newBuckets[i] = oldBuckets[i];
+ newBuckets[i + oldNumberOfBuckets] = moveTemp;
+ }
+ set->free(oldBuckets);
+ set->buckets = newBuckets;
+ set->numberOfBuckets = newNumberOfBuckets;
+ set->expandTreshold = newNumberOfBuckets * CHAIN_LENGHT_EXPAND_THRESHOLD;
+ set->shrinkTreshold = newNumberOfBuckets * CHAIN_LENGHT_SHRINK_THRESHOLD;
+ }
+}
+
+static inline void ch_set_decrease_size(ChainedHashSet *set) {
+ set->size = set->size - 1;
+ if (set->size < set->shrinkTreshold) {
+ unsigned int oldNumberOfBuckets = set->numberOfBuckets;
+ unsigned int newNumberOfBuckets = oldNumberOfBuckets / 2;
+ SortedListSetNode **newBuckets =
+ set->malloc(sizeof(SortedListSetNode *) * newNumberOfBuckets);
+ SortedListSetNode **oldBuckets = set->buckets;
+ for (unsigned int i = 0; i < newNumberOfBuckets; i++) {
+ newBuckets[i] = oldBuckets[i];
+ sl_set_concat_opt(&newBuckets[i], oldBuckets[i + newNumberOfBuckets]);
+ }
+ set->free(oldBuckets);
+ set->buckets = newBuckets;
+ set->numberOfBuckets = newNumberOfBuckets;
+ set->expandTreshold = newNumberOfBuckets * CHAIN_LENGHT_EXPAND_THRESHOLD;
+ if (set->numberOfBuckets == INITIAL_NUMBER_OF_BUCKETS) {
+ set->shrinkTreshold = 0;
+ } else {
+ set->shrinkTreshold = newNumberOfBuckets * CHAIN_LENGHT_SHRINK_THRESHOLD;
+ }
+ }
+}
+
+static inline void ch_set_initialize(ChainedHashSet *set,
+ unsigned int keyPosition,
+ void *(*extract_key)(void *v, int keyPos),
+ unsigned int (*hash_key)(void *k),
+ bool (*are_equal)(void *v1, void *v2),
+ char *(*to_string)(void *v1),
+ void *(*my_malloc)(size_t size),
+ void (*my_free)(void *ptr)) {
+ set->keyPosition = keyPosition;
+ set->extract_key = extract_key;
+ set->hash_key = hash_key;
+ set->are_equal = are_equal;
+ set->to_string = to_string;
+ set->malloc = my_malloc;
+ set->free = my_free;
+ set->numberOfBuckets = INITIAL_NUMBER_OF_BUCKETS;
+ set->expandTreshold = set->numberOfBuckets * CHAIN_LENGHT_EXPAND_THRESHOLD;
+ set->shrinkTreshold = set->numberOfBuckets * CHAIN_LENGHT_EXPAND_THRESHOLD;
+ set->size = 0;
+ set->buckets =
+ set->malloc(sizeof(SortedListSetNode *) * INITIAL_NUMBER_OF_BUCKETS);
+ for (int i = 0; i < INITIAL_NUMBER_OF_BUCKETS; i++) {
+ set->buckets[i] = NULL;
+ }
+}
+
+static inline ChainedHashSet *ch_set_create(
+ unsigned int keyPosition, void *(*extract_key)(void *v, int keyPos),
+ unsigned int (*hash_key)(void *k), bool (*are_equal)(void *v1, void *v2),
+ char *(*to_string)(void *v1), void *(*my_malloc)(size_t size),
+ void (*my_free)(void *ptr)) {
+ ChainedHashSet *set = my_malloc(sizeof(ChainedHashSet));
+ ch_set_initialize(set, keyPosition, extract_key, hash_key, are_equal,
+ to_string, my_malloc, my_free);
+ return set;
+}
+
+static inline bool ch_set_insert_opt(ChainedHashSet *set, void *value,
+ unsigned int valueSize,
+ unsigned int hashValue, bool overwrite) {
+ unsigned int bucketIndex = hashValue & (set->numberOfBuckets - 1);
+ SortedListSetNode **bucket = &set->buckets[bucketIndex];
+ bool oneAdded = sl_set_insert_opt(
+ bucket, value, valueSize, reverse_bits(hashValue), set->keyPosition,
+ overwrite, set->extract_key, set->are_equal, set->malloc, set->free);
+ if (oneAdded) {
+ ch_set_increase_size(set);
+ }
+ return oneAdded;
+}
+
+static inline void ch_set_insert(void *setParam, void *value,
+ unsigned int valueSize) {
+ ChainedHashSet *set = (ChainedHashSet *)setParam;
+ void *key = set->extract_key(value, set->keyPosition);
+ unsigned int hashValue = set->hash_key(key);
+ ch_set_insert_opt(set, value, valueSize, hashValue, true);
+}
+
+static inline bool ch_set_insert_new(void *setParam, void *value,
+ unsigned int valueSize) {
+ ChainedHashSet *set = (ChainedHashSet *)setParam;
+ void *key = set->extract_key(value, set->keyPosition);
+ unsigned int hashValue = set->hash_key(key);
+ return ch_set_insert_opt(set, value, valueSize, hashValue, false);
+}
+
+static inline void *ch_set_lookup(void *setParam, void *key) {
+ ChainedHashSet *set = (ChainedHashSet *)setParam;
+ unsigned int hashValue = set->hash_key(key);
+ unsigned int bucketIndex = hashValue & (set->numberOfBuckets - 1);
+ SortedListSetNode **bucket = &set->buckets[bucketIndex];
+ return sl_set_lookup_opt(bucket, key, reverse_bits(hashValue),
+ set->keyPosition, set->extract_key, set->are_equal,
+ false, set->malloc);
+}
+
+static inline void ch_set_delete(void *setParam, void *key,
+ unsigned int keySize) {
+ (void)keySize;
+ ChainedHashSet *set = (ChainedHashSet *)setParam;
+ unsigned int hashValue = set->hash_key(key);
+ unsigned int bucketIndex = hashValue & (set->numberOfBuckets - 1);
+ SortedListSetNode **bucket = &set->buckets[bucketIndex];
+ bool oneRemoved =
+ sl_set_delete_opt(bucket, key, reverse_bits(hashValue), set->keyPosition,
+ set->extract_key, set->are_equal, set->free);
+ if (oneRemoved) {
+ ch_set_decrease_size(set);
+ }
+}
+
+static inline void ch_set_traverse(void *setParam,
+ void (*traverser)(size_t index, void *v,
+ void *context),
+ void *context) {
+ ChainedHashSet *set = (ChainedHashSet *)setParam;
+ unsigned int numberOfBuckets = set->numberOfBuckets;
+ SortedListSetNode *itemNode;
+ size_t index = 0;
+ for (unsigned int i = 0; i < numberOfBuckets; i++) {
+ itemNode = set->buckets[i];
+ while (itemNode != NULL) {
+ traverser(index, (void *)itemNode->value, context);
+ index++;
+ itemNode = itemNode->next;
+ }
+ }
+}
+
+static inline void ch_set_free(void *setParam) {
+ ChainedHashSet *set = (ChainedHashSet *)setParam;
+ unsigned int numberOfBuckets = set->numberOfBuckets;
+ for (unsigned int i = 0; i < numberOfBuckets; i++) {
+ sl_set_free_opt(&set->buckets[i], set->free);
+ }
+ set->free(set->buckets);
+ set->free(set);
+}
+
+static inline char *ch_set_to_string(void *setParam) {
+ ChainedHashSet *set = (ChainedHashSet *)setParam;
+ unsigned int numberOfBuckets = set->numberOfBuckets;
+ char **bucketStrings = malloc(sizeof(char*)*numberOfBuckets);
+ unsigned int *bucketStringSizes = malloc(sizeof(unsigned int)*numberOfBuckets);
+ unsigned int totalBucketsStringSize = 0;
+ SortedListSet *tempListSet = plain_sl_set_create(
+ set->keyPosition, set->extract_key, set->hash_key, set->are_equal,
+ set->to_string, set->malloc, set->free);
+ for (unsigned int i = 0; i < numberOfBuckets; i++) {
+ tempListSet->head = set->buckets[i];
+ bucketStrings[i] = sl_set_to_string(tempListSet);
+ bucketStringSizes[i] = strlen(bucketStrings[i]);
+ totalBucketsStringSize = totalBucketsStringSize + bucketStringSizes[i];
+ }
+ tempListSet->head = NULL;
+ sl_set_free(tempListSet);
+ char *resultString =
+ set->malloc(totalBucketsStringSize + numberOfBuckets * 3 - 3 + 3);
+ resultString[0] = '[';
+ unsigned int currentPosition = 1;
+ for (unsigned int i = 0; i < numberOfBuckets; i++) {
+ sprintf(&resultString[currentPosition], "%s", bucketStrings[i]);
+ set->free(bucketStrings[i]);
+ currentPosition = currentPosition + bucketStringSizes[i];
+ if (i != (numberOfBuckets - 1)) {
+ sprintf(&resultString[currentPosition], ",\n ");
+ currentPosition = currentPosition + 3;
+ }
+ }
+ sprintf(&resultString[currentPosition], "]");
+ free(bucketStrings);
+ free(bucketStringSizes);
+ return resultString;
+}
+
+static inline void ch_set_print(void *setParam) {
+ ChainedHashSet *set = (ChainedHashSet *)setParam;
+ char *str = ch_set_to_string(set);
+ printf("%s\n", str);
+ set->free(str);
+}
+
+static inline bool ch_set_is_concurrent() { return false; }
+
+#endif
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/simple_c_gc.c b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/simple_c_gc.c
new file mode 100644
index 0000000000..0eeb050282
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/simple_c_gc.c
@@ -0,0 +1,310 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright 2019 Kjell Winblad (kjellwinblad@gmail.com, http://winsh.me).
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include "simple_c_gc.h"
+#include "chained_hash_set.h"
+
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool scgc_print_gc_info = false;
+static ChainedHashSet *scgc_objects;
+static void *scgc_stack_top;
+
+#define SCGC_MIN_ALLOCS_UNTIL_FREE 100
+static int scgc_allocs_until_gc = SCGC_MIN_ALLOCS_UNTIL_FREE;
+static void *(*scgc_user_malloc)(size_t size);
+static void (*scgc_user_free)(void *ptr);
+
+typedef enum { scgc_blank_state, scgc_marked } scgc_object_state;
+
+typedef struct {
+ unsigned long magic_number;
+ size_t size;
+ scgc_object_state state;
+ void *data[1];
+} scgc_object;
+
+typedef struct {
+ void *address;
+ scgc_object *base;
+ unsigned long magic_number;
+} scgc_object_ref;
+
+static void *scgc_malloc(size_t size) {
+ void *(*my_malloc)(size_t size);
+ if (scgc_user_malloc == NULL) {
+ my_malloc = malloc;
+ } else {
+ my_malloc = scgc_user_malloc;
+ }
+ void *res = my_malloc(size);
+ if (res == NULL) {
+ printf("GCGC: Allocator returned NULL.\n");
+ exit(1);
+ }
+ return res;
+}
+
+static void scgc_free(void *ptr) {
+ void (*my_free)(void *ptr);
+ if (scgc_user_free == NULL) {
+ my_free = free;
+ } else {
+ my_free = scgc_user_free;
+ }
+ my_free(ptr);
+}
+
+static void *scgc_extract_key(void *v, int keyPos) {
+ (void)keyPos;
+ return v;
+}
+
+static unsigned int scgc_hash_key(void *keyPtr) {
+ /* From
+ * https://lemire.me/blog/2018/08/15/fast-strongly-universal-64-bit-hashing-everywhere*/
+ int64_t x = (intptr_t)*((void **)keyPtr);
+ int64_t a = 2348923;
+ int64_t b = 3292;
+ int64_t c = 9893487421;
+ int32_t low = (int)x;
+ int32_t high = (int)(x >> 32);
+ return (unsigned int)((a * low + b * high + c) >> 32);
+}
+
+static bool scgc_are_equal(void *v1p, void *v2p) {
+ scgc_object_ref *v1 = ((scgc_object_ref *)v1p);
+ scgc_object_ref *v2 = ((scgc_object_ref *)v2p);
+ return v1->address == v2->address;
+}
+
+static char *scgc_to_string(void *vp) {
+ scgc_object_ref *v = ((scgc_object_ref *)vp);
+ char *str = scgc_malloc(200);
+ sprintf(str, "{.address=%p, .base_address=%p, magic_number=%lu}", v->address,
+ (void *)v->base, v->magic_number);
+ return str;
+}
+
+static void scgc_initialize_global_state() {
+ scgc_objects =
+ ch_set_create(0, scgc_extract_key, scgc_hash_key, scgc_are_equal,
+ scgc_to_string, scgc_malloc, scgc_free);
+ srand((int)(intptr_t)scgc_objects * 2654435761);
+}
+
+static void scgc_do_gc(bool no_stack);
+
+static void scgc_destroy_global_state() {
+ scgc_do_gc(true);
+ ch_set_free(scgc_objects);
+}
+
+static int scgc_start_gced_code_2(int (*main)(int, char *[]), int argc,
+ char **argv[]) {
+ volatile int noinline = 1;
+ volatile char **my_argv = (volatile char **)*argv;
+ scgc_stack_top = (void *)my_argv;
+ int (*next)(int, char *[]) = noinline ? main : (int (*)(int, char *[]))(NULL);
+ {
+ int to_return;
+ scgc_initialize_global_state();
+ to_return = next(argc, (char **)my_argv);
+ scgc_destroy_global_state();
+ return to_return;
+ }
+}
+
+static void scgc_global_set_put(scgc_object_ref ref) {
+ ch_set_insert(scgc_objects, &ref, sizeof(scgc_object_ref));
+}
+
+static void scgc_global_set_del(void *key) {
+ ch_set_delete(scgc_objects, &key, sizeof(void *));
+}
+
+static scgc_object_ref *scgc_global_set_get(void *key) {
+ scgc_object_ref *ret = ch_set_lookup(scgc_objects, &key);
+ return ret;
+}
+
+static void scgc_mark_reachable_objects_in_region(void *start, void *end);
+
+static void *scgc_min(void *a, void *b) { return a <= b ? a : b; }
+
+static void *scgc_max(void *a, void *b) { return a > b ? a : b; }
+
+static void ycf_find_stack_bottom_and_mark_conservative_helper(void) {
+ volatile void *p = NULL;
+ volatile intptr_t stack_bottom = (intptr_t)&p;
+ scgc_mark_reachable_objects_in_region(
+ scgc_min(scgc_stack_top, (void *)&stack_bottom),
+ scgc_max((void *)&stack_bottom, scgc_stack_top));
+}
+
+static void scgc_get_stack_bottom_and_mark() {
+ jmp_buf env;
+ setjmp(env);
+
+ volatile int noinline = 1;
+
+ void (*bottom)(void) =
+ noinline ? ycf_find_stack_bottom_and_mark_conservative_helper
+ : (void (*)(void))(NULL);
+
+ bottom();
+}
+
+static void scgc_mark_reachable_objects_in_region(void *start, void *end) {
+ void **iter = start;
+ void **iter_end = end;
+ scgc_object *object;
+ while (iter <= iter_end) {
+ scgc_object_ref *ref = scgc_global_set_get(*iter);
+ if (ref != NULL && ref->base->data == *iter &&
+ ref->magic_number == ref->base->magic_number &&
+ ref->base->state == scgc_blank_state) {
+ object = ref->base;
+ object->state = scgc_marked;
+ scgc_mark_reachable_objects_in_region(
+ &object->data[0], &((char *)object->data)[object->size - 1]);
+ }
+ iter = iter + 1;
+ }
+}
+
+static void scgc_mark_reachable_objects(bool no_stack) {
+ void *tmp = NULL;
+ if (no_stack) {
+ scgc_mark_reachable_objects_in_region(&tmp, &tmp);
+ } else {
+ scgc_get_stack_bottom_and_mark();
+ }
+}
+
+static void scgc_collect_unmarked_traverser(size_t index, void *v,
+ void *context) {
+ void **objects_to_remove = context;
+ scgc_object_ref *ref = v;
+ if (ref->base->state == scgc_blank_state) {
+ scgc_free(ref->base);
+ objects_to_remove[index] = ref->address;
+ } else {
+ objects_to_remove[index] = NULL;
+ }
+}
+
+static void scgc_remove_unmarked_objects() {
+ size_t nr_of_candidates = scgc_objects->size;
+ void **objects_to_remove = scgc_malloc(sizeof(void *) * nr_of_candidates);
+ for (size_t i = 0; i < nr_of_candidates; i++) {
+ objects_to_remove[i] = NULL;
+ }
+ ch_set_traverse(scgc_objects, scgc_collect_unmarked_traverser,
+ objects_to_remove);
+ for (size_t i = 0; i < nr_of_candidates; i++) {
+ if (objects_to_remove[i] != NULL) {
+ scgc_global_set_del(objects_to_remove[i]);
+ }
+ }
+ scgc_free(objects_to_remove);
+}
+
+static void scgc_unmark_traverser(size_t index, void *v, void *context) {
+ scgc_object_ref *ref = v;
+ ref->base->state = scgc_blank_state;
+}
+
+static void scgc_unmark_objects() {
+ ch_set_traverse(scgc_objects, scgc_unmark_traverser, NULL);
+}
+
+static void scgc_do_gc(bool no_stack) {
+ unsigned int objects_before = scgc_objects->size;
+ scgc_mark_reachable_objects(no_stack);
+ scgc_remove_unmarked_objects();
+ scgc_unmark_objects();
+ if (scgc_print_gc_info) {
+ unsigned int objects_after = scgc_objects->size;
+ unsigned int objects_removed = objects_before - objects_after;
+ fprintf(stderr, "GC: before=%u, after=%u, removed=%u\n", objects_before,
+ objects_after, objects_removed);
+ }
+}
+
+static void scgc_gc() {
+ scgc_allocs_until_gc--;
+ if (scgc_allocs_until_gc <= 0) {
+ scgc_do_gc(false);
+ unsigned int objects_after = scgc_objects->size;
+ scgc_allocs_until_gc = objects_after * 2;
+ if (scgc_allocs_until_gc < SCGC_MIN_ALLOCS_UNTIL_FREE) {
+ scgc_allocs_until_gc = SCGC_MIN_ALLOCS_UNTIL_FREE;
+ }
+ }
+}
+
+/* Public interface */
+
+int scgc_start_gced_code(int (*main)(int, char *[]), int argc, char *argv[],
+ void *(*my_malloc)(size_t), void (*my_free)(void *)) {
+ volatile int noinline = 1;
+ int (*next)(int (*)(int, char *[]), int, char **[]) =
+ (noinline ? scgc_start_gced_code_2
+ : (int (*)(int (*)(int, char *[]), int, char **[]))(NULL));
+ volatile char **my_argv = (volatile char **)argv;
+ int res;
+ scgc_user_malloc = my_malloc;
+ scgc_user_free = my_free;
+ if (my_argv == NULL) {
+ fprintf(stderr,
+ "scgc_start_gced_code: the argv parameter should not be NULL!");
+ exit(1);
+ }
+ res = next(main, argc, (char ***)&my_argv);
+ return res;
+}
+
+void *scgc_new(size_t size) {
+ scgc_gc();
+ scgc_object *new = scgc_malloc(size + sizeof(scgc_object));
+ scgc_object_ref new_ref;
+ unsigned long magic_number = (unsigned long)rand();
+ new->state = scgc_blank_state;
+ new->magic_number = magic_number;
+ new->size = size;
+ new_ref.address = new->data;
+ new_ref.base = new;
+ new_ref.magic_number = magic_number;
+ scgc_global_set_put(new_ref);
+ return new->data;
+}
+
+void scgc_enable_print_gc_info() { scgc_print_gc_info = true; }
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/simple_c_gc.h b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/simple_c_gc.h
new file mode 100644
index 0000000000..0e9aff84b8
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/simple_c_gc.h
@@ -0,0 +1,57 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright 2019 Kjell Winblad (kjellwinblad@gmail.com, http://winsh.me).
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/**
+ * Pulic interface for Simple C GC.
+ *
+ * @author Kjell Winblad
+ *
+ */
+
+#ifndef SIMPLE_C_GC_H
+#define SIMPLE_C_GC_H
+
+#include <stddef.h>
+
+/**
+ * This function starts code that will be garbage collected
+ *
+ * @param main The function that will be started and garbage collected
+ * @param argc Passed to main
+ * @param argv Passed to main
+ * @param my_malloc Simple C GC will use this to allocated memory
+ * @param my_free Simple C GC will use this to free memory
+ */
+int scgc_start_gced_code(int (*main)(int, char *[]), int argc, char *argv[],
+ void *(*my_malloc)(size_t), void (*my_free)(void *));
+
+/**
+ * Allocate a new memory block that will be garbage collected
+ *
+ * @param size The size of the new memory block
+ */
+void *scgc_new(size_t size);
+
+/**
+ * Enables printing of garbage collection information
+ */
+void scgc_enable_print_gc_info(void);
+#endif
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/sorted_list_set.h b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/sorted_list_set.h
new file mode 100644
index 0000000000..1c216d2ed9
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/sorted_list_set.h
@@ -0,0 +1,370 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright 2019 Kjell Winblad (kjellwinblad@gmail.com, http://winsh.me).
+ * All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#ifndef SORTED_LIST_SET_H
+#define SORTED_LIST_SET_H
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct SortedListSetNodeImpl {
+ struct SortedListSetNodeImpl *next;
+ unsigned int key_hash_value;
+ unsigned int valueSize;
+ char value[]; /* Flexible size array member */
+} SortedListSetNode;
+
+typedef struct {
+ SortedListSetNode *head;
+ unsigned int keyPosition;
+ void *(*extract_key)(void *v, int keyPos);
+ unsigned int (*hash_key)(void *k);
+ bool (*are_equal)(void *v1, void *v2);
+ char *(*to_string)(void *v1);
+ void *(*malloc)(size_t size);
+ void (*free)(void *ptr);
+} SortedListSet;
+
+static inline SortedListSet *plain_sl_set_create(
+ unsigned int keyPosition, void *(*extract_key)(void *v, int keyPos),
+ unsigned int (*hash_key)(void *k), bool (*are_equal)(void *v1, void *v2),
+ char *(*to_string)(void *v1), void *(*my_malloc)(size_t size),
+ void (*my_free)(void *ptr)) {
+ SortedListSet *set = my_malloc(sizeof(SortedListSet));
+ set->head = NULL;
+ set->keyPosition = keyPosition;
+ set->extract_key = extract_key;
+ set->hash_key = hash_key;
+ set->are_equal = are_equal;
+ set->to_string = to_string;
+ set->malloc = my_malloc;
+ set->free = my_free;
+ return set;
+}
+
+static inline int compare_hash_codes(unsigned int code1, unsigned int code2) {
+ return code1 - code2;
+}
+
+static inline bool sl_set_insert_opt(SortedListSetNode **root, void *valuePtr,
+ unsigned int valueSize,
+ unsigned int keyHashValue, int keyPosition,
+ bool overwrite,
+ void *(*extract_key)(void *v, int keyPos),
+ bool (*are_equal)(void *v1, void *v2),
+ void *(*my_malloc)(size_t size),
+ void (*my_free)(void *ptr)) {
+ void *key = extract_key(valuePtr, keyPosition);
+ SortedListSetNode *previous = (SortedListSetNode *)root;
+ SortedListSetNode *current = previous->next;
+ bool oneMore = true;
+ int compareResult;
+ while (current != NULL) {
+ compareResult = compare_hash_codes(current->key_hash_value, keyHashValue);
+ if (compareResult < 0) {
+ previous = current;
+ current = previous->next;
+ } else if (compareResult > 0) {
+ break;
+ } else {
+ if (are_equal(extract_key(current->value, keyPosition), key)) {
+ if (overwrite) {
+ SortedListSetNode *oldCurrent = current;
+ current = current->next;
+ previous->next = current;
+ my_free(oldCurrent);
+ oneMore = false;
+ break;
+ } else {
+ return false;
+ }
+ } else {
+ previous = current;
+ current = previous->next;
+ }
+ }
+ }
+ SortedListSetNode *newNode = my_malloc(sizeof(SortedListSetNode) + valueSize);
+ previous->next = newNode;
+ newNode->next = current;
+ newNode->key_hash_value = keyHashValue;
+ memcpy(newNode->value, valuePtr, valueSize);
+ newNode->valueSize = valueSize;
+ return oneMore;
+}
+
+static inline void sl_set_insert(void *setParam, void *valuePtr,
+ unsigned int valueSize) {
+ SortedListSet *set = (SortedListSet *)setParam;
+ sl_set_insert_opt(&set->head, valuePtr, valueSize,
+ set->hash_key(set->extract_key(valuePtr, set->keyPosition)),
+ set->keyPosition, true, set->extract_key, set->are_equal,
+ set->malloc, set->free);
+}
+
+static inline bool sl_set_insert_new(void *setParam, void *valuePtr,
+ unsigned int valueSize) {
+ SortedListSet *set = (SortedListSet *)setParam;
+ return sl_set_insert_opt(
+ &set->head, valuePtr, valueSize,
+ set->hash_key(set->extract_key(valuePtr, set->keyPosition)),
+ set->keyPosition, false, set->extract_key, set->are_equal, set->malloc,
+ set->free);
+}
+
+static inline void *sl_set_lookup_opt(SortedListSetNode **root, void *key,
+ unsigned int keyHashValue,
+ int keyPosition,
+ void *(*extract_key)(void *v, int keyPos),
+ bool (*are_equal)(void *v1, void *v2),
+ bool copyOut,
+ void *(*my_malloc)(size_t size)) {
+ SortedListSetNode *previous = (SortedListSetNode *)root;
+ SortedListSetNode *current = previous->next;
+ int compareResult;
+ while (current != NULL) {
+ compareResult = compare_hash_codes(current->key_hash_value, keyHashValue);
+ if (compareResult < 0) {
+ previous = current;
+ current = previous->next;
+ } else if (compareResult > 0) {
+ return NULL;
+ } else if (are_equal(extract_key(current->value, keyPosition), key)) {
+ if (copyOut) {
+ void *toReturn = my_malloc(current->valueSize);
+ memcpy(toReturn, current->value, current->valueSize);
+ return toReturn;
+ } else {
+ return current->value;
+ }
+ } else {
+ previous = current;
+ current = previous->next;
+ }
+ }
+ return NULL;
+}
+
+static inline void *sl_set_lookup(void *setParam, void *key) {
+ SortedListSet *set = (SortedListSet *)setParam;
+ return sl_set_lookup_opt(&set->head, key, set->hash_key(key),
+ set->keyPosition, set->extract_key, set->are_equal,
+ false, set->malloc);
+}
+
+static inline bool sl_set_delete_opt(SortedListSetNode **root, void *key,
+ unsigned int keyHashValue, int keyPosition,
+ void *(*extract_key)(void *v, int keyPos),
+ bool (*are_equal)(void *v1, void *v2),
+ void (*my_free)(void *ptr)) {
+ SortedListSetNode *previous = (SortedListSetNode *)root;
+ SortedListSetNode *current = previous->next;
+ int compareResult;
+ while (current != NULL) {
+ compareResult = compare_hash_codes(current->key_hash_value, keyHashValue);
+ if (compareResult < 0) {
+ previous = current;
+ current = previous->next;
+ } else if (compareResult > 0) {
+ return false;
+ } else if (are_equal(extract_key(current->value, keyPosition), key)) {
+ previous->next = current->next;
+ my_free(current);
+ return true;
+ } else {
+ previous = current;
+ current = previous->next;
+ }
+ }
+ return false;
+}
+
+static inline void sl_set_delete(void *setParam, void *key,
+ unsigned int keySize) {
+ (void)keySize;
+ SortedListSet *set = (SortedListSet *)setParam;
+ sl_set_delete_opt(&set->head, key, set->hash_key(key), set->keyPosition,
+ set->extract_key, set->are_equal, set->free);
+}
+
+static inline void sl_set_free_opt(SortedListSetNode **root,
+ void (*my_free)(void *ptr)) {
+ SortedListSetNode *previous = (SortedListSetNode *)root;
+ SortedListSetNode *current = previous->next;
+ while (current != NULL) {
+ previous = current;
+ current = current->next;
+ my_free(previous);
+ }
+}
+
+static inline void sl_set_free(void *setParam) {
+ SortedListSet *set = (SortedListSet *)setParam;
+ sl_set_free_opt(&set->head, set->free);
+ set->free(set);
+}
+
+static inline SortedListSetNode *sl_set_split_opt(SortedListSetNode **root,
+ unsigned int splitPattern) {
+ SortedListSetNode *previous = (SortedListSetNode *)root;
+ SortedListSetNode *current = previous->next;
+ while (current != NULL) {
+ if (current->key_hash_value & splitPattern) {
+ previous->next = NULL;
+ return current;
+ }
+ previous = current;
+ current = previous->next;
+ }
+ return NULL;
+}
+
+static inline void sl_set_concat_opt(SortedListSetNode **root,
+ SortedListSetNode *list) {
+ SortedListSetNode *previous = (SortedListSetNode *)root;
+ SortedListSetNode *current = previous->next;
+ while (current != NULL) {
+ previous = current;
+ current = previous->next;
+ }
+ previous->next = list;
+}
+
+static inline void sl_set_append_opt(SortedListSetNode **root,
+ SortedListSetNode *list) {
+ if (list == NULL) {
+ return;
+ }
+ SortedListSetNode *previous = (SortedListSetNode *)root;
+ SortedListSetNode *current = previous->next;
+ while (current != NULL) {
+ previous = current;
+ current = previous->next;
+ }
+ previous->next = list;
+}
+
+static inline void *
+sl_set_fold_opt(SortedListSetNode **root, void *initialValue,
+ void *(*f)(void *soFar, void *currentValue)) {
+ SortedListSetNode *previous = (SortedListSetNode *)root;
+ SortedListSetNode *current = previous->next;
+ void *soFar = initialValue;
+ while (current != NULL) {
+ soFar = f(soFar, current->value);
+ current = current->next;
+ }
+ return soFar;
+}
+
+static inline void *sl_set_fold(SortedListSet *set, void *initialValue,
+ void *(*f)(void *soFar, void *currentValue)) {
+ return sl_set_fold_opt(&set->head, initialValue, f);
+}
+
+static inline void *_______size_helper(void *soFarParam, void *currentValue) {
+ unsigned int *soFar = (unsigned int *)soFarParam;
+ (void)currentValue;
+ unsigned int prev = *soFar;
+ *soFar = prev + 1;
+ return soFar;
+}
+
+static inline unsigned int sl_set_size_opt(SortedListSetNode **root) {
+ unsigned int size = 0;
+ sl_set_fold_opt(root, &size, _______size_helper);
+ return size;
+}
+
+static inline unsigned int sl_set_size(SortedListSet *set) {
+ return sl_set_size_opt(&set->head);
+}
+
+static inline void sl_set_print(void *setParam) {
+ SortedListSet *set = (SortedListSet *)setParam;
+ SortedListSetNode *previous = (SortedListSetNode *)set;
+ SortedListSetNode *current = previous->next;
+ printf("[");
+ while (current != NULL) {
+ char *string = set->to_string(current->value);
+ printf("%s", string);
+ set->free(string);
+ previous = current;
+ current = previous->next;
+ if (current != NULL) {
+ printf(",");
+ }
+ }
+ printf("]\n");
+}
+
+static inline char *sl_set_to_string(void *setParam) {
+ SortedListSet *set = (SortedListSet *)setParam;
+ unsigned int numberOfElements = sl_set_size(set);
+ char **stringArray = malloc(sizeof(char*)*numberOfElements);
+ unsigned int *stringLengths = malloc(sizeof(unsigned int)*numberOfElements);
+ SortedListSetNode *previous = (SortedListSetNode *)set;
+ SortedListSetNode *current = previous->next;
+ unsigned int elementNumber = 0;
+ unsigned int totalCharCount = 2;
+ if (numberOfElements == 0) {
+ char *buffer = (char *)set->malloc(3);
+ sprintf(buffer, "[]");
+ free(stringArray);
+ free(stringLengths);
+ return buffer;
+ }
+ while (current != NULL) {
+ stringArray[elementNumber] = set->to_string(current->value);
+ stringLengths[elementNumber] = strlen(stringArray[elementNumber]);
+ totalCharCount = totalCharCount + stringLengths[elementNumber];
+ current = current->next;
+ elementNumber++;
+ }
+ totalCharCount = totalCharCount + numberOfElements;
+ char *stringBuffer = set->malloc(totalCharCount);
+ stringBuffer[0] = '[';
+ unsigned int currentPosition = 1;
+ for (unsigned int i = 0; i < numberOfElements; i++) {
+ sprintf(&stringBuffer[currentPosition], "%s", stringArray[i]);
+ set->free(stringArray[i]);
+ currentPosition = currentPosition + stringLengths[i];
+ if (i != (numberOfElements - 1)) {
+ sprintf(&stringBuffer[currentPosition], ",");
+ currentPosition = currentPosition + 1;
+ }
+ }
+ sprintf(&stringBuffer[currentPosition], "]");
+ free(stringArray);
+ free(stringLengths);
+ return stringBuffer;
+}
+
+#endif
diff --git a/erts/lib_src/yielding_c_fun/lib/simple_c_gc/test.c b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/test.c
new file mode 100644
index 0000000000..f118db4361
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/simple_c_gc/test.c
@@ -0,0 +1,53 @@
+#include "simple_c_gc.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static size_t nr_of_objects_created = 0;
+static size_t nr_of_objects = 0;
+static size_t peek_nr_of_objects = 0;
+
+static void *my_malloc(size_t size) {
+ nr_of_objects++;
+ nr_of_objects_created++;
+ if (nr_of_objects > peek_nr_of_objects) {
+ peek_nr_of_objects = nr_of_objects;
+ }
+ return calloc(1, size);
+}
+
+static void my_free(void *ptr) {
+ nr_of_objects--;
+ free(ptr);
+}
+
+int my_main(int argc, char *argv[]) {
+ if (argc == 2 && strcmp(argv[1], "-enable_gc_info") == 0) {
+ scgc_enable_print_gc_info();
+ }
+ void **my = scgc_new(100);
+ FILE *fp = fopen(".tmp_out", "w");
+ fprintf(fp, "my %p %p\n", (void *)my, (void *)&my);
+ for (int i = 0; i < 100000; i++) {
+ my[0] = scgc_new(32);
+ my[1] = scgc_new(32);
+ my[2] = scgc_new(32);
+ ((void **)my[2])[0] = scgc_new(32);
+ ((void **)my[2])[1] = scgc_new(32);
+ fprintf(fp, "test1 %p\n", ((void **)my[0])[0]);
+ fprintf(fp, "test2 %p\n", ((void **)my[1])[0]);
+ fprintf(fp, "test3 %p\n", ((void ***)my[2])[0][0]);
+ fprintf(fp, "test4 %p\n", ((void ***)my[2])[1][0]);
+ }
+ fclose(fp);
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ /*Test the gc*/
+ scgc_start_gced_code(my_main, argc, argv, my_malloc, my_free);
+ fprintf(stderr, "Peek nr of live objects: %zu\n", peek_nr_of_objects);
+ fprintf(stderr, "Nr of objects after: %zu\n", nr_of_objects);
+ fprintf(stderr, "Nr of objects created: %zu\n", nr_of_objects_created);
+ return 0;
+}
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/GIT_VERSION b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/GIT_VERSION
new file mode 100644
index 0000000000..0bbde9c80d
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/GIT_VERSION
@@ -0,0 +1,468 @@
+origin https://github.com/kokke/tiny-regex-c.git (fetch)
+origin https://github.com/kokke/tiny-regex-c.git (push)
+commit d3058f271f7a06ff298dff0a6a9a1e0753a5fa17
+Merge: 28882c4 c2ed772
+Author: kokke <spam@rowdy.dk>
+Date: Fri Oct 26 23:20:07 2018 +0200
+
+ Merge pull request #22 from monolifed/master
+
+ Update re.c for #20
+
+commit c2ed77267c86e30aa342f48528e5ffce6ab4d103
+Author: monolifed <6624464+monolifed@users.noreply.github.com>
+Date: Thu Oct 25 19:19:24 2018 +0300
+
+ Update re.c
+
+commit 28882c4a39fbc9ddd44e69caa22aac4c4a208934
+Author: kokke <spam@rowdy.dk>
+Date: Tue Oct 23 11:27:23 2018 +0200
+
+ Update README.md
+
+commit 4583018febd2d28277b512f09427e2ba01b4cbd5
+Author: kokke <spam@rowdy.dk>
+Date: Tue Oct 23 11:26:47 2018 +0200
+
+ Update README.md
+
+commit 2211111107da75f5574d5f0140e4396ae701d947
+Author: kokke <spam@rowdy.dk>
+Date: Mon Oct 22 16:04:02 2018 +0200
+
+ Update test1.c
+
+ Adding failing test-case for question-mark '?', brought to my attention by @tobermory in https://github.com/kokke/tiny-regex-c/issues/20
+
+commit 679aebd38a245afb9f9d107d066b68765b94865b
+Author: kokke <spam@rowdy.dk>
+Date: Mon Oct 22 15:41:33 2018 +0200
+
+ Update re.c
+
+ fixing typo, noticed by @tobermory -> https://github.com/kokke/tiny-regex-c/issues/19
+
+commit 2f225fa5e355ad3a99cdd5e953768399fe0b6607
+Author: kokke <spam@rowdy.dk>
+Date: Wed Jun 6 18:15:48 2018 +0200
+
+ Update test1.c
+
+commit b587a65abf0f1347a3b7c7050b73c5dbb94d9cb7
+Merge: 89a479f 96a8f77
+Author: kokke <spam@rowdy.dk>
+Date: Wed Jun 6 18:10:57 2018 +0200
+
+ Merge pull request #17 from monolifed/patch-1
+
+ Update re.c
+
+commit 96a8f770c2922505699c9a4d6ba9b9584be5ee29
+Author: monolifed <6624464+monolifed@users.noreply.github.com>
+Date: Thu May 31 02:06:49 2018 +0300
+
+ Update re.c
+
+ hopefully fixes #12
+
+commit 89a479f985cb25284c4e11870c1531c299255790
+Merge: bf9b2f0 e5f3564
+Author: kokke <spam@rowdy.dk>
+Date: Tue May 15 11:25:53 2018 +0200
+
+ Merge pull request #16 from TermoSINteZ/master
+
+ Fix pattern ".?" issues
+
+commit e5f3564a1de7230cec207cb6aec3866b0b7931e0
+Author: TermoSINteZ <termo.sintez@gmail.com>
+Date: Tue May 15 10:41:17 2018 +0300
+
+ Remove tabs
+
+commit acb0a441470808c99a08ecc8d8716d258866835c
+Author: TermoSINteZ <termo.sintez@gmail.com>
+Date: Tue May 15 00:10:55 2018 +0300
+
+ Fix pattern ".?" issues
+
+commit bf9b2f0c5e91dd12e1fea8cbc7ae7a6193e7b4ed
+Merge: cb80dee 84af23d
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 17 14:09:03 2018 +0200
+
+ Merge pull request #14 from roflcopter4/master
+
+ Check for correct python2 binary in Makefile
+
+commit 84af23dde1c6785ca680d5aced93e20e484efa8d
+Author: roflcopter4 <brendan.leason.4@gmail.com>
+Date: Mon Apr 16 14:45:34 2018 -0600
+
+ Fix dumb typos
+
+commit 0cb0b1348392b795971be9472d8dd2854403a2cb
+Author: roflcopter4 <brendan.leason.4@gmail.com>
+Date: Mon Apr 16 14:42:13 2018 -0600
+
+ Add back '@' signs I accidentally removed
+
+commit 81d12dfd3de805d0969e66650731e4df9158169c
+Author: roflcopter4 <brendan.leason.4@gmail.com>
+Date: Mon Apr 16 14:26:28 2018 -0600
+
+ Check for correct python2 binry in Makefile
+
+commit cb80dee0644f41df67b1740fefd8573d18d84a53
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 15:39:17 2018 +0100
+
+ Update README.md
+
+commit 005de160fa2d8796eb2bce75b52eeaac3ac13d8d
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 15:32:37 2018 +0100
+
+ Update Makefile
+
+commit 9ec0029e83e7cba718f6b6f8b107a0133d22b4a7
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 15:31:40 2018 +0100
+
+ Create regex_test_neg.py
+
+commit 960dd3ebec78d2ac61969840d4e23c97d443bdc9
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 15:31:11 2018 +0100
+
+ Create test_rand_neg.c
+
+commit 3d472f3d78d9702ffcdf4439ac842e3250da7c49
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 14:02:23 2018 +0100
+
+ Update README.md
+
+commit cdf61829adc1c94a9f2d019b2683c34c7732ca60
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 13:06:49 2018 +0100
+
+ Update Makefile
+
+commit 98812bdcafa58c7f35fc40fae9ba64d6d2a9eac1
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 12:52:34 2018 +0100
+
+ Update README.md
+
+commit 9c192d4199e4e1e6a764f1b1699deb5b159b161e
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 12:47:27 2018 +0100
+
+ Update re.c
+
+commit fb677f315fd159e13c2c2fc5b40f199148c0fb0f
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 12:45:20 2018 +0100
+
+ Update test1.c
+
+commit dc1b3ee8fc4354e5dfa1846ee6b778452609d50e
+Author: kokke <spam@rowdy.dk>
+Date: Fri Mar 23 12:08:34 2018 +0100
+
+ Update README.md
+
+commit eac0cef080a32c4b606bd10994ddcf8a72249d78
+Author: kokke <spam@rowdy.dk>
+Date: Mon Dec 11 23:26:53 2017 +0100
+
+ Update README.md
+
+commit dc9f34d74b6ac80cd0bed17fca76ef35fca3b101
+Author: kokke <spam@rowdy.dk>
+Date: Mon Dec 11 23:23:40 2017 +0100
+
+ Update re.c
+
+commit d76301fa18f3575cca94816cff291a01fda58ad7
+Author: kokke <spam@rowdy.dk>
+Date: Mon Dec 11 23:23:22 2017 +0100
+
+ Update test1.c
+
+commit b72898ef7a67a0650c7d00b7a09f36abc1fa57a1
+Author: kokke <spam@rowdy.dk>
+Date: Mon Dec 11 21:35:55 2017 +0100
+
+ Update re.c
+
+commit 3cd275c9c55ec51a01ad01c317232247aeec9bab
+Merge: baf3a15 881f634
+Author: kokke <spam@rowdy.dk>
+Date: Mon Dec 11 21:32:26 2017 +0100
+
+ Merge pull request #9 from mrigger/out-of-bounds-fixes
+
+ Out of bounds fixes.
+
+commit 881f634e9a905933d1889a4e0b9b09920337478a
+Author: Manuel Rigger <manuel.rigger@jku.at>
+Date: Sun Dec 10 11:42:17 2017 +0100
+
+ Fix out-of-bunds access found by AFL (input: [00000000000000000000000000000000000000][).
+
+commit e6c91ab986f1ed7d7e39d58e6fdfae44d3110c99
+Author: Manuel Rigger <manuel.rigger@jku.at>
+Date: Sun Dec 10 10:30:06 2017 +0100
+
+ Fix out-of-bounds accesses found by AFL (input: [00000000000000000000000000000000000000).
+
+commit 619a9c654df6a471d7e043081b3fb4bf0e1cd642
+Author: Manuel Rigger <manuel.rigger@jku.at>
+Date: Sat Dec 9 22:56:12 2017 +0100
+
+ Fix out-of-bounds access found by AFL.
+
+commit 43051e257141740e99c32f1cffea78d2a72fe10e
+Author: Manuel Rigger <manuel.rigger@jku.at>
+Date: Fri Dec 8 21:25:01 2017 +0100
+
+ Fix out-of-bounds access found by AFL.
+
+commit baf3a15d7b99b856e82d3dd69555d3bac6750049
+Author: kokke <spam@rowdy.dk>
+Date: Thu Oct 12 00:54:16 2017 +0200
+
+ Update re.h
+
+ To make the C++ crowd and their compilers happy ;)
+
+commit 107352174172b05f25d153c651b327890ab3b574
+Author: kokke <spam@rowdy.dk>
+Date: Sat Jul 8 03:32:29 2017 +0200
+
+ Update re.c
+
+commit ef6b2416b17388da413d3b9cce95ef6897255970
+Author: kokke <spam@rowdy.dk>
+Date: Sat Jul 8 03:26:59 2017 +0200
+
+ Update test1.c
+
+commit b8446ecba1c59b7a9f29e7d8174661deb38a74a5
+Author: kokke <spam@rowdy.dk>
+Date: Wed May 3 22:55:11 2017 +0200
+
+ Update README.md
+
+commit 1600de0a66b11610183c02c4f778866dce6927af
+Author: kokke <spam@rowdy.dk>
+Date: Mon May 1 21:43:47 2017 +0200
+
+ Update regex_test.py
+
+commit e5dafc83fe1672de45ab1bc40e7f34876e2d1d15
+Author: kokke <spam@rowdy.dk>
+Date: Mon May 1 21:34:22 2017 +0200
+
+ Update test_rand.c
+
+commit a39262a534a628d7c07bbb753a66d031a4b4a43a
+Author: kokke <spam@rowdy.dk>
+Date: Mon May 1 21:27:17 2017 +0200
+
+ Update test_print.c
+
+commit 96b9af356129a263ef60355426de0c339308cd26
+Author: kokke <spam@rowdy.dk>
+Date: Mon May 1 21:25:11 2017 +0200
+
+ Update re.c
+
+commit 529889acae614c91697af87feba5abe5334a4274
+Author: kokke <spam@rowdy.dk>
+Date: Mon May 1 21:18:40 2017 +0200
+
+ Update README.md
+
+commit 1afc07dc2936f17dc0df5178ef61caeb19d2c5b1
+Author: kokke <spam@rowdy.dk>
+Date: Mon May 1 21:17:57 2017 +0200
+
+ Update README.md
+
+commit 407f4fe08fb219d68026817d47d1a453a0b6c1ba
+Author: kokke <spam@rowdy.dk>
+Date: Fri Apr 28 17:27:12 2017 +0200
+
+ Update README.md
+
+commit 2d2ebe66d55349ba6add2a4335b24b0a13fb26d2
+Author: kokke <spam@rowdy.dk>
+Date: Mon Apr 24 23:48:18 2017 +0200
+
+ Update README.md
+
+commit 5c76b8cc4e77af9c4a3cb1bbd1cfda3e73e1be56
+Author: kokke <spam@rowdy.dk>
+Date: Mon Apr 24 23:33:34 2017 +0200
+
+ Update README.md
+
+commit e4c7cb9d63d1b284f92053f535402738ee2c3a6a
+Author: kokke <spam@rowdy.dk>
+Date: Mon Apr 24 09:28:24 2017 +0200
+
+ Update README.md
+
+commit 1e694fe184be1261e9f684ea3556103e45649c3b
+Author: kokke <spam@rowdy.dk>
+Date: Thu Apr 20 17:58:33 2017 +0200
+
+ Update README.md
+
+commit 52810460dd877f337e23d58e627b5ae3fbbee430
+Author: kokke <spam@rowdy.dk>
+Date: Wed Apr 19 02:38:00 2017 +0200
+
+ Update Makefile
+
+commit d44fb891007d56e0344f7372521f3ff583630d61
+Author: kokke <spam@rowdy.dk>
+Date: Wed Apr 19 01:19:38 2017 +0200
+
+ Update README.md
+
+commit af6e7b642b75255bc2a608383a1b0969816bbd83
+Author: kokke <spam@rowdy.dk>
+Date: Wed Apr 19 01:16:23 2017 +0200
+
+ Update README.md
+
+commit 858d217db95390b6e7bd1fd1ba7be950142852c9
+Author: kokke <spam@rowdy.dk>
+Date: Wed Apr 19 00:32:20 2017 +0200
+
+ Update README.md
+
+commit 28fffd4d32b56cd9b318091cacc8a5c71fbfffed
+Author: kokke <spam@rowdy.dk>
+Date: Wed Apr 19 00:31:44 2017 +0200
+
+ Update README.md
+
+commit bd874728041ad0444744a9e739dd5d078f249150
+Author: kokke <spam@rowdy.dk>
+Date: Wed Apr 19 00:28:26 2017 +0200
+
+ Update README.md
+
+commit 02110e89252e76c1a2b2a44430f0f402e523f50a
+Author: kokke <spam@rowdy.dk>
+Date: Wed Apr 19 00:26:41 2017 +0200
+
+ Update test1.c
+
+commit 939bc7572148c77d398b22892857b769ec63ac54
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 23:31:14 2017 +0200
+
+ Update README.md
+
+commit 5fef3901aabb6100b28460dccf3154d3e1cc18e7
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 23:09:12 2017 +0200
+
+ Update README.md
+
+commit cae4b96ced1679ea703890eff00deefb0b80cc16
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 23:08:34 2017 +0200
+
+ Update README.md
+
+commit 72398075ee66c49e3e804be94146956dc8bafff6
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:58:10 2017 +0200
+
+ Update Makefile
+
+commit 2994559b99506b35e71c8e18aa99dd706f3b7e38
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:53:05 2017 +0200
+
+ Update re.c
+
+commit 124052b32f302c5a570e79089f951a7d1d56cb38
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:52:01 2017 +0200
+
+ Update Makefile
+
+commit ba8caa931b36b6c274c82214b636770857f7872d
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:50:11 2017 +0200
+
+ Create regex_test.py
+
+commit 9630b56d1faffea799575bb9a81f9c0278906a16
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:49:39 2017 +0200
+
+ Create exrex.py
+
+commit 7da49b443622deaaa472d233a80c58a378fb8646
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:47:37 2017 +0200
+
+ Create test_rand.c
+
+commit 29384927296062ecf0fec81a1c7e35d315b5d84a
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:47:09 2017 +0200
+
+ Create test_print.c
+
+commit 475c199a1b1af4fdd8422b8639117ec97f53b350
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:46:34 2017 +0200
+
+ Create test2.c
+
+commit 2e6e69622061daa740fa03c726f4cc66c5fbc38c
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:45:08 2017 +0200
+
+ Create test1.c
+
+commit e9e4ce1b91609d839a2996bb918397b502f908b7
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:44:33 2017 +0200
+
+ Create Makefile
+
+commit 5a8f0e60718fbfdca7c1fc079a77051af68bdc87
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:41:01 2017 +0200
+
+ Create re.c
+
+commit 52b0aeb459cf1682b636fabe01a97de17579b036
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:38:55 2017 +0200
+
+ Create re.h
+
+commit d061985f504096a59e320a4760784a4090eaf1e4
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:38:01 2017 +0200
+
+ Update README.md
+
+commit 32cbf08728415efb96a3f562f31b780c024502ff
+Author: kokke <spam@rowdy.dk>
+Date: Tue Apr 18 22:37:02 2017 +0200
+
+ Initial commit
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/LICENSE b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/LICENSE
new file mode 100644
index 0000000000..cf1ab25da0
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/Makefile b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/Makefile
new file mode 100644
index 0000000000..deb9fea33a
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/Makefile
@@ -0,0 +1,106 @@
+# Compiler to use - can be replaced by clang for instance
+CC := gcc
+
+# Number of random text expressions to generate, for random testing
+NRAND_TESTS := 1000
+
+PYTHON != if (python --version 2>&1 | grep -q 'Python 2\..*'); then \
+ echo 'python'; \
+ elif command -v python2 >/dev/null 2>&1; then \
+ echo 'python2'; \
+ else \
+ echo 'Error: no compatible python version found.' >&2; \
+ exit 1; \
+ fi
+
+# Flags to pass to compiler
+CFLAGS := -O3 -Wall -Wextra -std=c99 -I.
+
+all:
+ @$(CC) $(CFLAGS) re.c tests/test1.c -o tests/test1
+ @$(CC) $(CFLAGS) re.c tests/test2.c -o tests/test2
+ @$(CC) $(CFLAGS) re.c tests/test_rand.c -o tests/test_rand
+ @$(CC) $(CFLAGS) re.c tests/test_rand_neg.c -o tests/test_rand_neg
+
+clean:
+ @rm -f tests/test1 tests/test2 tests/test_rand
+ @#@$(foreach test_bin,$(TEST_BINS), rm -f $(test_bin) ; )
+ @rm -f a.out
+ @rm -f *.o
+
+
+test: all
+ @$(test $(PYTHON))
+ @echo
+ @echo Testing hand-picked regex\'s:
+ @./tests/test1
+ @echo Testing patterns against $(NRAND_TESTS) random strings matching the Python implementation and comparing:
+ @echo
+ @$(PYTHON) ./scripts/regex_test.py \\d+\\w?\\D\\d $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py \\s+[a-zA-Z0-9?]* $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py \\w*\\d?\\w\\? $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [^\\d]+\\\\?\\s $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [^\\w][^-1-4] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [^\\w] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [^1-4] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [^-1-4] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [^\\d]+\\s?[\\w]* $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py a+b*[ac]*.+.*.[\\.]. $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py a?b[ac*]*.?[\\]+[?]? $(NRAND_TESTS)
+ @#python ./scripts/regex_test.py [1-5-]+[-1-2]-[-] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [-1-3]-[-]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [1-5]+[-1-2]-[\\-] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [-1-2]* $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py \\s?[a-fKL098]+-? $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [\\-]* $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [\\\\]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [0-9a-fA-F]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [1379][2468][abcdef] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [012345-9]?[0123-789] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [012345-9] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [0-56789] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [abc-zABC-Z] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [a\d]?1234 $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py .*123faerdig $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py .?\\w+jsj$ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [?to][+to][?ta][*ta] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py \\d+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [a-z]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py \\s+[a-zA-Z0-9?]* $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py \\w $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py \\d $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [\\d] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test.py [^\\d] $(NRAND_TESTS)
+ @#python ./scripts/regex_test.py [^-1-4] $(NRAND_TESTS)
+ @echo
+ @echo
+ @echo
+ @echo Testing rejection of patterns against $(NRAND_TESTS) random strings also rejected by the Python implementation:
+ @echo
+ @$(PYTHON) ./scripts/regex_test_neg.py \\d+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [a-z]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py \\s+[a-zA-Z0-9?]* $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py ^\\w $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py ^\\d $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [\\d] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py ^[^\\d] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [^\\w]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py ^[\\w]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py ^[^0-9] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [a-z].[A-Z] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [-1-3]-[-]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [1-5]+[-1-2]-[\\-] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [-0-9]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [\\-]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [\\\\]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [0-9a-fA-F]+ $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [1379][2468][abcdef] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [012345-9] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py [0-56789] $(NRAND_TESTS)
+ @$(PYTHON) ./scripts/regex_test_neg.py .*123faerdig $(NRAND_TESTS)
+ @echo
+ @echo
+ @./tests/test2
+ @echo
+ @echo
+
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/README.md b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/README.md
new file mode 100644
index 0000000000..ab89576f08
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/README.md
@@ -0,0 +1,138 @@
+# tiny-regex-c
+# A small regex implementation in C
+### Description
+Small and portable [Regular Expression](https://en.wikipedia.org/wiki/Regular_expression) (regex) library written in C.
+
+Design is inspired by Rob Pike's regex-code for the book *"Beautiful Code"* [available online here](http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html).
+
+Supports a subset of the syntax and semantics of the Python standard library implementation (the `re`-module).
+
+### Current status
+All supported regex-operators seem to work properly according to the test-set, with the following exception:
+
+There is a problem with ranges (e.g. `[0-9]` for a digit 0-9) combined with inverted character-cases, e.g. `[^ab]` for anything but 'a' or 'b' - like `[^-0-9]` for anything not '-' or a digit 0-9. I think the code mathces too broadly in that case.
+
+I think you should test the patterns you are going to use. You can easily modify the test-harness to generate tests for your intended patterns to check for compliance.
+
+**I will gladly accept patches correcting bugs.**
+
+### Design goals
+The main design goal of this library is to be small, correct, self contained and use few resources while retaining acceptable performance and feature completeness. Clarity of the code is also highly valued.
+
+### Notable features and omissions
+- Small code and binary size: <500 SLOC, ~3kb binary for x86. Statically #define'd memory usage / allocation.
+- No use of dynamic memory allocation (i.e. no calls to `malloc` / `free`).
+- To avoid call-stack exhaustion, iterative searching is preferred over recursive by default (can be changed with a pre-processor flag).
+- No support for capturing groups or named capture: `(^P<name>group)` etc.
+- Thorough testing : [exrex](https://github.com/asciimoo/exrex) is used to randomly generate test-cases from regex patterns, which are fed into the regex code for verification. Try `make test` to generate a few thousand tests cases yourself.
+- Compiled for x86 using GCC 4.7.4 and optimizing for size, the binary takes up ~2-3kb code space and allocates ~0.5kb RAM :
+ ```
+ > gcc -Os -c re.c
+ > size re.o
+ text data bss dec hex filename
+ 2319 0 544 2863 b2f re.o
+
+ ```
+ For ARM/Thumb using GCC 4.8.1 it's around 1.5kb code and less RAM :
+ ```
+ > arm-none-eabi-gcc -Os -mthumb -c re.c
+ > size re.o
+ text data bss dec hex filename
+ 1418 0 280 1698 6a2 re.o
+
+ ```
+ For 8-bit AVR using AVR-GCC 4.8.1 it's around 2kb code and less RAM :
+ ```
+ > avr-gcc -Os -c re.c
+ > size re.o
+ text data bss dec hex filename
+ 2128 0 130 2258 8d2 re.o
+ ```
+
+
+
+### API
+This is the public / exported API:
+```C
+/* Typedef'd pointer to hide implementation details. */
+typedef struct regex_t* re_t;
+
+/* Compiles regex string pattern to a regex_t-array. */
+re_t re_compile(const char* pattern);
+
+/* Finds matches of the compiled pattern inside text. */
+int re_matchp(re_t pattern, const char* text);
+
+/* Finds matches of pattern inside text (compiles first automatically). */
+int re_match(const char* pattern, const char* text);
+```
+
+### Supported regex-operators
+The following features / regex-operators are supported by this library.
+
+NOTE: inverted character classes are buggy - see the test harness for concrete examples.
+
+
+ - `.` Dot, matches any character
+ - `^` Start anchor, matches beginning of string
+ - `$` End anchor, matches end of string
+ - `*` Asterisk, match zero or more (greedy)
+ - `+` Plus, match one or more (greedy)
+ - `?` Question, match zero or one (non-greedy)
+ - `[abc]` Character class, match if one of {'a', 'b', 'c'}
+ - `[^abc]` Inverted class, match if NOT one of {'a', 'b', 'c'}
+ **`NOTE: This feature is currently broken for some usage of character ranges!`**
+ - `[a-zA-Z]` Character ranges, the character set of the ranges { a-z | A-Z }
+ - `\s` Whitespace, \t \f \r \n \v and spaces
+ - `\S` Non-whitespace
+ - `\w` Alphanumeric, [a-zA-Z0-9_]
+ - `\W` Non-alphanumeric
+ - `\d` Digits, [0-9]
+ - `\D` Non-digits
+
+### Usage
+Compile a regex from ASCII-string (char-array) to a custom pattern structure using `re_compile()`.
+
+Search a text-string for a regex and get an index into the string, using `re_match()` or `re_matchp()`.
+
+The returned index points to the first place in the string, where the regex pattern matches.
+
+If the regular expression doesn't match, the matching function returns an index of -1 to indicate failure.
+
+### Examples
+Example of usage:
+```C
+/* Standard null-terminated C-string to search: */
+const char* string_to_search = "ahem.. 'hello world !' ..";
+
+/* Compile a simple regular expression using character classes, meta-char and greedy + non-greedy quantifiers: */
+re_t pattern = re_compile("[Hh]ello [Ww]orld\\s*[!]?");
+
+/* Check if the regex matches the text: */
+int match_idx = re_matchp(pattern, string_to_search);
+if (match_idx != -1)
+{
+ printf("match at idx %d.\n", match_idx);
+}
+```
+
+For more usage examples I encourage you to look at the code in the `tests`-folder.
+
+### TODO
+- Fix the implementation of inverted character classes.
+- Fix implementation of branches (`|`), and see if that can lead us closer to groups as well, e.g. `(a|b)+`.
+- Add `example.c` that demonstrates usage.
+- Add `tests/test_perf.c` for performance and time measurements.
+- Testing: Improve pattern rejection testing.
+
+### FAQ
+- *Q: What differentiates this library from other C regex implementations?*
+
+ A: Well, the small size for one. <500 lines of C-code compiling to 2-3kb ROM, using very little RAM.
+
+### License
+All material in this repository is in the public domain.
+
+
+
+
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/re.c b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/re.c
new file mode 100644
index 0000000000..76d4066247
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/re.c
@@ -0,0 +1,470 @@
+/*
+ *
+ * Mini regex-module inspired by Rob Pike's regex code described in:
+ *
+ * http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html
+ *
+ *
+ *
+ * Supports:
+ * ---------
+ * '.' Dot, matches any character
+ * '^' Start anchor, matches beginning of string
+ * '$' End anchor, matches end of string
+ * '*' Asterisk, match zero or more (greedy)
+ * '+' Plus, match one or more (greedy)
+ * '?' Question, match zero or one (non-greedy)
+ * '[abc]' Character class, match if one of {'a', 'b', 'c'}
+ * '[^abc]' Inverted class, match if NOT one of {'a', 'b', 'c'} -- NOTE: feature is currently broken!
+ * '[a-zA-Z]' Character ranges, the character set of the ranges { a-z | A-Z }
+ * '\s' Whitespace, \t \f \r \n \v and spaces
+ * '\S' Non-whitespace
+ * '\w' Alphanumeric, [a-zA-Z0-9_]
+ * '\W' Non-alphanumeric
+ * '\d' Digits, [0-9]
+ * '\D' Non-digits
+ *
+ *
+ */
+
+
+
+#include "re.h"
+#include <stdio.h>
+
+/* Definitions: */
+
+#define MAX_REGEXP_OBJECTS 30 /* Max number of regex symbols in expression. */
+#define MAX_CHAR_CLASS_LEN 40 /* Max length of character-class buffer in. */
+
+
+enum { UNUSED, DOT, BEGIN, END, QUESTIONMARK, STAR, PLUS, CHAR, CHAR_CLASS, INV_CHAR_CLASS, DIGIT, NOT_DIGIT, ALPHA, NOT_ALPHA, WHITESPACE, NOT_WHITESPACE, /* BRANCH */ };
+
+typedef struct regex_t
+{
+ unsigned char type; /* CHAR, STAR, etc. */
+ union
+ {
+ unsigned char ch; /* the character itself */
+ unsigned char* ccl; /* OR a pointer to characters in class */
+ } u;
+} regex_t;
+
+
+
+/* Private function declarations: */
+static int matchpattern(regex_t* pattern, const char* text);
+static int matchcharclass(char c, const char* str);
+static int matchstar(regex_t p, regex_t* pattern, const char* text);
+static int matchplus(regex_t p, regex_t* pattern, const char* text);
+static int matchone(regex_t p, char c);
+static int matchdigit(char c);
+static int matchalpha(char c);
+static int matchwhitespace(char c);
+static int matchmetachar(char c, const char* str);
+static int matchrange(char c, const char* str);
+static int ismetachar(char c);
+
+
+
+/* Public functions: */
+int re_match(const char* pattern, const char* text)
+{
+ return re_matchp(re_compile(pattern), text);
+}
+
+int re_matchp(re_t pattern, const char* text)
+{
+ if (pattern != 0)
+ {
+ if (pattern[0].type == BEGIN)
+ {
+ return ((matchpattern(&pattern[1], text)) ? 0 : -1);
+ }
+ else
+ {
+ int idx = -1;
+
+ do
+ {
+ idx += 1;
+
+ if (matchpattern(pattern, text))
+ {
+ if (text[0] == '\0')
+ return -1;
+
+ return idx;
+ }
+ }
+ while (*text++ != '\0');
+ }
+ }
+ return -1;
+}
+
+re_t re_compile(const char* pattern)
+{
+ /* The sizes of the two static arrays below substantiates the static RAM usage of this module.
+ MAX_REGEXP_OBJECTS is the max number of symbols in the expression.
+ MAX_CHAR_CLASS_LEN determines the size of buffer for chars in all char-classes in the expression. */
+ static regex_t re_compiled[MAX_REGEXP_OBJECTS];
+ static unsigned char ccl_buf[MAX_CHAR_CLASS_LEN];
+ int ccl_bufidx = 1;
+
+ char c; /* current char in pattern */
+ int i = 0; /* index into pattern */
+ int j = 0; /* index into re_compiled */
+
+ while (pattern[i] != '\0' && (j+1 < MAX_REGEXP_OBJECTS))
+ {
+ c = pattern[i];
+
+ switch (c)
+ {
+ /* Meta-characters: */
+ case '^': { re_compiled[j].type = BEGIN; } break;
+ case '$': { re_compiled[j].type = END; } break;
+ case '.': { re_compiled[j].type = DOT; } break;
+ case '*': { re_compiled[j].type = STAR; } break;
+ case '+': { re_compiled[j].type = PLUS; } break;
+ case '?': { re_compiled[j].type = QUESTIONMARK; } break;
+/* case '|': { re_compiled[j].type = BRANCH; } break; <-- not working properly */
+
+ /* Escaped character-classes (\s \w ...): */
+ case '\\':
+ {
+ if (pattern[i+1] != '\0')
+ {
+ /* Skip the escape-char '\\' */
+ i += 1;
+ /* ... and check the next */
+ switch (pattern[i])
+ {
+ /* Meta-character: */
+ case 'd': { re_compiled[j].type = DIGIT; } break;
+ case 'D': { re_compiled[j].type = NOT_DIGIT; } break;
+ case 'w': { re_compiled[j].type = ALPHA; } break;
+ case 'W': { re_compiled[j].type = NOT_ALPHA; } break;
+ case 's': { re_compiled[j].type = WHITESPACE; } break;
+ case 'S': { re_compiled[j].type = NOT_WHITESPACE; } break;
+
+ /* Escaped character, e.g. '.' or '$' */
+ default:
+ {
+ re_compiled[j].type = CHAR;
+ re_compiled[j].u.ch = pattern[i];
+ } break;
+ }
+ }
+ /* '\\' as last char in pattern -> invalid regular expression. */
+/*
+ else
+ {
+ re_compiled[j].type = CHAR;
+ re_compiled[j].ch = pattern[i];
+ }
+*/
+ } break;
+
+ /* Character class: */
+ case '[':
+ {
+ /* Remember where the char-buffer starts. */
+ int buf_begin = ccl_bufidx;
+
+ /* Look-ahead to determine if negated */
+ if (pattern[i+1] == '^')
+ {
+ re_compiled[j].type = INV_CHAR_CLASS;
+ i += 1; /* Increment i to avoid including '^' in the char-buffer */
+ }
+ else
+ {
+ re_compiled[j].type = CHAR_CLASS;
+ }
+
+ /* Copy characters inside [..] to buffer */
+ while ( (pattern[++i] != ']')
+ && (pattern[i] != '\0')) /* Missing ] */
+ {
+ if (pattern[i] == '\\')
+ {
+ if (ccl_bufidx >= MAX_CHAR_CLASS_LEN - 1)
+ {
+ //fputs("exceeded internal buffer!\n", stderr);
+ return 0;
+ }
+ ccl_buf[ccl_bufidx++] = pattern[i++];
+ }
+ else if (ccl_bufidx >= MAX_CHAR_CLASS_LEN)
+ {
+ //fputs("exceeded internal buffer!\n", stderr);
+ return 0;
+ }
+ ccl_buf[ccl_bufidx++] = pattern[i];
+ }
+ if (ccl_bufidx >= MAX_CHAR_CLASS_LEN)
+ {
+ /* Catches cases such as [00000000000000000000000000000000000000][ */
+ //fputs("exceeded internal buffer!\n", stderr);
+ return 0;
+ }
+ /* Null-terminate string end */
+ ccl_buf[ccl_bufidx++] = 0;
+ re_compiled[j].u.ccl = &ccl_buf[buf_begin];
+ } break;
+
+ /* Other characters: */
+ default:
+ {
+ re_compiled[j].type = CHAR;
+ re_compiled[j].u.ch = c;
+ } break;
+ }
+ i += 1;
+ j += 1;
+ }
+ /* 'UNUSED' is a sentinel used to indicate end-of-pattern */
+ re_compiled[j].type = UNUSED;
+
+ return (re_t) re_compiled;
+}
+
+void re_print(regex_t* pattern)
+{
+ const char* types[] = { "UNUSED", "DOT", "BEGIN", "END", "QUESTIONMARK", "STAR", "PLUS", "CHAR", "CHAR_CLASS", "INV_CHAR_CLASS", "DIGIT", "NOT_DIGIT", "ALPHA", "NOT_ALPHA", "WHITESPACE", "NOT_WHITESPACE", "BRANCH" };
+
+ int i;
+ for (i = 0; i < MAX_REGEXP_OBJECTS; ++i)
+ {
+ if (pattern[i].type == UNUSED)
+ {
+ break;
+ }
+
+ printf("type: %s", types[pattern[i].type]);
+ if (pattern[i].type == CHAR_CLASS || pattern[i].type == INV_CHAR_CLASS)
+ {
+ printf(" [");
+ int j;
+ char c;
+ for (j = 0; j < MAX_CHAR_CLASS_LEN; ++j)
+ {
+ c = pattern[i].u.ccl[j];
+ if ((c == '\0') || (c == ']'))
+ {
+ break;
+ }
+ printf("%c", c);
+ }
+ printf("]");
+ }
+ else if (pattern[i].type == CHAR)
+ {
+ printf(" '%c'", pattern[i].u.ch);
+ }
+ printf("\n");
+ }
+}
+
+
+
+/* Private functions: */
+static int matchdigit(char c)
+{
+ return ((c >= '0') && (c <= '9'));
+}
+static int matchalpha(char c)
+{
+ return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
+}
+static int matchwhitespace(char c)
+{
+ return ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r') || (c == '\f') || (c == '\v'));
+}
+static int matchalphanum(char c)
+{
+ return ((c == '_') || matchalpha(c) || matchdigit(c));
+}
+static int matchrange(char c, const char* str)
+{
+ return ((c != '-') && (str[0] != '\0') && (str[0] != '-') &&
+ (str[1] == '-') && (str[1] != '\0') &&
+ (str[2] != '\0') && ((c >= str[0]) && (c <= str[2])));
+}
+static int ismetachar(char c)
+{
+ return ((c == 's') || (c == 'S') || (c == 'w') || (c == 'W') || (c == 'd') || (c == 'D'));
+}
+
+static int matchmetachar(char c, const char* str)
+{
+ switch (str[0])
+ {
+ case 'd': return matchdigit(c);
+ case 'D': return !matchdigit(c);
+ case 'w': return matchalphanum(c);
+ case 'W': return !matchalphanum(c);
+ case 's': return matchwhitespace(c);
+ case 'S': return !matchwhitespace(c);
+ default: return (c == str[0]);
+ }
+}
+
+static int matchcharclass(char c, const char* str)
+{
+ do
+ {
+ if (matchrange(c, str))
+ {
+ return 1;
+ }
+ else if (str[0] == '\\')
+ {
+ /* Escape-char: increment str-ptr and match on next char */
+ str += 1;
+ if (matchmetachar(c, str))
+ {
+ return 1;
+ }
+ else if ((c == str[0]) && !ismetachar(c))
+ {
+ return 1;
+ }
+ }
+ else if (c == str[0])
+ {
+ if (c == '-')
+ {
+ return ((str[-1] == '\0') || (str[1] == '\0'));
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+ while (*str++ != '\0');
+
+ return 0;
+}
+
+static int matchone(regex_t p, char c)
+{
+ switch (p.type)
+ {
+ case DOT: return 1;
+ case CHAR_CLASS: return matchcharclass(c, (const char*)p.u.ccl);
+ case INV_CHAR_CLASS: return !matchcharclass(c, (const char*)p.u.ccl);
+ case DIGIT: return matchdigit(c);
+ case NOT_DIGIT: return !matchdigit(c);
+ case ALPHA: return matchalphanum(c);
+ case NOT_ALPHA: return !matchalphanum(c);
+ case WHITESPACE: return matchwhitespace(c);
+ case NOT_WHITESPACE: return !matchwhitespace(c);
+ default: return (p.u.ch == c);
+ }
+}
+
+static int matchstar(regex_t p, regex_t* pattern, const char* text)
+{
+ do
+ {
+ if (matchpattern(pattern, text))
+ return 1;
+ }
+ while ((text[0] != '\0') && matchone(p, *text++));
+
+ return 0;
+}
+
+static int matchplus(regex_t p, regex_t* pattern, const char* text)
+{
+ while ((text[0] != '\0') && matchone(p, *text++))
+ {
+ if (matchpattern(pattern, text))
+ return 1;
+ }
+ return 0;
+}
+
+static int matchquestion(regex_t p, regex_t* pattern, const char* text)
+{
+ if (p.type == UNUSED)
+ return 1;
+ if (matchpattern(pattern, text))
+ return 1;
+ if (*text && matchone(p, *text++))
+ return matchpattern(pattern, text);
+ return 0;
+}
+
+
+#if 0
+
+/* Recursive matching */
+static int matchpattern(regex_t* pattern, const char* text)
+{
+ if ((pattern[0].type == UNUSED) || (pattern[1].type == QUESTIONMARK))
+ {
+ return matchquestion(pattern[1], &pattern[2], text);
+ }
+ else if (pattern[1].type == STAR)
+ {
+ return matchstar(pattern[0], &pattern[2], text);
+ }
+ else if (pattern[1].type == PLUS)
+ {
+ return matchplus(pattern[0], &pattern[2], text);
+ }
+ else if ((pattern[0].type == END) && pattern[1].type == UNUSED)
+ {
+ return text[0] == '\0';
+ }
+ else if ((text[0] != '\0') && matchone(pattern[0], text[0]))
+ {
+ return matchpattern(&pattern[1], text+1);
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+#else
+
+/* Iterative matching */
+static int matchpattern(regex_t* pattern, const char* text)
+{
+ do
+ {
+ if ((pattern[0].type == UNUSED) || (pattern[1].type == QUESTIONMARK))
+ {
+ return matchquestion(pattern[0], &pattern[2], text);
+ }
+ else if (pattern[1].type == STAR)
+ {
+ return matchstar(pattern[0], &pattern[2], text);
+ }
+ else if (pattern[1].type == PLUS)
+ {
+ return matchplus(pattern[0], &pattern[2], text);
+ }
+ else if ((pattern[0].type == END) && pattern[1].type == UNUSED)
+ {
+ return (text[0] == '\0');
+ }
+/* Branching is not working properly
+ else if (pattern[1].type == BRANCH)
+ {
+ return (matchpattern(pattern, text) || matchpattern(&pattern[2], text));
+ }
+*/
+ }
+ while ((text[0] != '\0') && matchone(*pattern++, *text++));
+
+ return 0;
+}
+
+#endif
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/re.h b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/re.h
new file mode 100644
index 0000000000..fd36412100
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/re.h
@@ -0,0 +1,54 @@
+/*
+ *
+ * Mini regex-module inspired by Rob Pike's regex code described in:
+ *
+ * http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html
+ *
+ *
+ *
+ * Supports:
+ * ---------
+ * '.' Dot, matches any character
+ * '^' Start anchor, matches beginning of string
+ * '$' End anchor, matches end of string
+ * '*' Asterisk, match zero or more (greedy)
+ * '+' Plus, match one or more (greedy)
+ * '?' Question, match zero or one (non-greedy)
+ * '[abc]' Character class, match if one of {'a', 'b', 'c'}
+ * '[^abc]' Inverted class, match if NOT one of {'a', 'b', 'c'} -- NOTE: feature is currently broken!
+ * '[a-zA-Z]' Character ranges, the character set of the ranges { a-z | A-Z }
+ * '\s' Whitespace, \t \f \r \n \v and spaces
+ * '\S' Non-whitespace
+ * '\w' Alphanumeric, [a-zA-Z0-9_]
+ * '\W' Non-alphanumeric
+ * '\d' Digits, [0-9]
+ * '\D' Non-digits
+ *
+ *
+ */
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+
+
+/* Typedef'd pointer to get abstract datatype. */
+typedef struct regex_t* re_t;
+
+
+/* Compile regex string pattern to a regex_t-array. */
+re_t re_compile(const char* pattern);
+
+
+/* Find matches of the compiled pattern inside text. */
+int re_matchp(re_t pattern, const char* text);
+
+
+/* Find matches of the txt pattern inside text (will compile automatically first). */
+int re_match(const char* pattern, const char* text);
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/exrex.py b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/exrex.py
new file mode 100644
index 0000000000..fc6fa7d5cc
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/exrex.py
@@ -0,0 +1,307 @@
+#!/usr/bin/env python
+
+# This file is part of exrex.
+#
+# exrex is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# exrex is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with exrex. If not, see < http://www.gnu.org/licenses/ >.
+#
+# (C) 2012- by Adam Tauber, <asciimoo@gmail.com>
+
+try:
+ from future_builtins import map, range
+except:
+ pass
+from re import sre_parse
+from itertools import product, chain, tee
+from random import choice,randint
+import string
+
+__all__ = ('generate', 'CATEGORIES', 'count', 'parse', 'getone')
+
+CATEGORIES = {'category_space' : sorted(sre_parse.WHITESPACE)
+ ,'category_digit' : sorted(sre_parse.DIGITS)
+ ,'category_any' : [chr(x) for x in range(32, 123)]
+ ,'category_word' : sorted( frozenset(string.ascii_letters + string.digits + "_") )
+ }
+
+def comb(g, i):
+ for c in g:
+ g2,i = tee(i)
+ for c2 in g2:
+ yield c+c2
+
+def mappend(g, c):
+ for cc in g:
+ yield cc+c
+
+def _in(d):
+ ret = []
+ neg = False
+ for i in d:
+ if i[0] == 'range':
+ subs = map(chr, range(i[1][0], i[1][1]+1))
+ if neg:
+ for char in subs:
+ try:
+ ret.remove(char)
+ except:
+ pass
+ else:
+ ret.extend(subs)
+ elif i[0] == 'literal':
+ if neg:
+ try:
+ ret.remove(chr(i[1]))
+ except:
+ pass
+ else:
+ ret.append(chr(i[1]))
+ elif i[0] == 'category':
+ subs = CATEGORIES.get(i[1], [''])
+ if neg:
+ for char in subs:
+ try:
+ ret.remove(char)
+ except:
+ pass
+ else:
+ ret.extend(subs)
+ elif i[0] == 'negate':
+ ret = list(CATEGORIES['category_any'])
+ neg = True
+ return ret
+
+
+def prods(orig, ran, items):
+ for o in orig:
+ for r in ran:
+ for s in product(items, repeat=r):
+ yield o+''.join(s)
+
+def ggen(g1, f, *args, **kwargs):
+ for a in g1:
+ g2 = f(*args, **kwargs)
+ if isinstance(g2, int):
+ yield g2
+ else:
+ for b in g2:
+ yield a+b
+
+def _gen(d, limit=20, count=False):
+ """docstring for _gen"""
+ ret = ['']
+ strings = 0
+ for i in d:
+ if i[0] == 'in':
+ subs = _in(i[1])
+ if count:
+ strings = (strings or 1) * len(subs)
+ ret = comb(ret, subs)
+ elif i[0] == 'literal':
+ ret = mappend(ret, chr(i[1]))
+ elif i[0] == 'category':
+ subs = CATEGORIES.get(i[1], [''])
+ if count:
+ strings = (strings or 1) * len(subs)
+ ret = comb(ret, subs)
+ elif i[0] == 'any':
+ subs = CATEGORIES['category_any']
+ if count:
+ strings = (strings or 1) * len(subs)
+ ret = comb(ret, subs)
+ elif i[0] == 'max_repeat':
+ chars = filter(None, _gen(list(i[1][2]), limit))
+ if i[1][1]+1 - i[1][0] >= limit:
+ ran = range(i[1][0], i[1][0]+limit)
+ else:
+ ran = range(i[1][0], i[1][1]+1)
+ if count:
+ for i in ran:
+ strings += pow(len(chars), i)
+ ret = prods(ret, ran, chars)
+ elif i[0] == 'branch':
+ subs = list(chain.from_iterable(_gen(list(x), limit) for x in i[1][1]))
+ if count:
+ strings = (strings or 1) * (len(subs) or 1)
+ ret = comb(ret, subs)
+ elif i[0] == 'subpattern':
+ if count:
+ strings = (strings or 1) * (sum(ggen([0], _gen, i[1][1], limit=limit, count=True)) or 1)
+ ret = ggen(ret, _gen, i[1][1], limit=limit, count=False)
+ # ignore ^ and $
+ elif i[0] == 'at':
+ continue
+ elif i[0] == 'not_literal':
+ subs = list(CATEGORIES['category_any'])
+ subs.remove(chr(i[1]))
+ if count:
+ strings = (strings or 1) * len(subs)
+ ret = comb(ret, subs)
+ elif i[0] == 'assert':
+ print i[1][1]
+ continue
+ else:
+ #print('[!] cannot handle expression ' + repr(i))
+ raise Exception('[!] cannot handle expression ' + repr(i))
+
+ if count:
+ return strings
+
+ return ret
+
+def _randone(d, limit=20):
+ """docstring for _randone"""
+ ret = ''
+ for i in d:
+ if i[0] == 'in':
+ ret += choice(_in(i[1]))
+ elif i[0] == 'literal':
+ ret += chr(i[1])
+ elif i[0] == 'category':
+ ret += choice(CATEGORIES.get(i[1], ['']))
+ elif i[0] == 'any':
+ ret += choice(CATEGORIES['category_any'])
+ elif i[0] == 'max_repeat':
+ chars = filter(None, _gen(list(i[1][2]), limit))
+ if i[1][1]+1 - i[1][0] >= limit:
+ min,max = i[1][0], i[1][0]+limit
+ else:
+ min,max = i[1][0], i[1][1]
+ for _ in range(randint(min, max)):
+ ret += choice(chars)
+ elif i[0] == 'branch':
+ ret += choice(list(chain.from_iterable(_gen(list(x), limit) for x in i[1][1])))
+ elif i[0] == 'subpattern':
+ ret += _randone(i[1][1], limit)
+ elif i[0] == 'at':
+ continue
+ elif i[0] == 'not_literal':
+ c=list(CATEGORIES['category_any'])
+ c.remove(chr(i[1]))
+ ret += choice(c)
+ else:
+ #print('[!] cannot handle expression "%s"' % str(i))
+ raise Exception('[!] cannot handle expression "%s"' % str(i))
+
+ return ret
+
+
+def parse(s):
+ """Regular expression parser
+ :param s: Regular expression
+ :type s: str
+ :rtype: list
+ """
+ r = sre_parse.parse(s)
+ return list(r)
+
+def generate(s, limit=20):
+ """Creates a generator that generates all matching strings to a given regular expression
+ :param s: Regular expression
+ :type s: str
+ :param limit: Range limit
+ :type limit: int
+ :returns: string generator object
+ """
+ return _gen(parse(s), limit)
+
+def count(s, limit=20):
+ """Counts all matching strings to a given regular expression
+ :param s: Regular expression
+ :type s: str
+ :param limit: Range limit
+ :type limit: int
+ :rtype: int
+ :returns: number of matching strings
+ """
+ return _gen(parse(s), limit, count=True)
+
+def getone(regex_string, limit=20):
+ """Returns a random matching string to a given regular expression
+ """
+ return _randone(parse(regex_string), limit)
+
+def argparser():
+ import argparse
+ from sys import stdout
+ argp = argparse.ArgumentParser(description='exrex - regular expression string generator')
+ argp.add_argument('-o', '--output'
+ ,help = 'Output file - default is STDOUT'
+ ,metavar = 'FILE'
+ ,default = stdout
+ ,type = argparse.FileType('w')
+ )
+ argp.add_argument('-l', '--limit'
+ ,help = 'Max limit for range size - default is 20'
+ ,default = 20
+ ,action = 'store'
+ ,type = int
+ ,metavar = 'N'
+ )
+ argp.add_argument('-c', '--count'
+ ,help = 'Count matching strings'
+ ,default = False
+ ,action = 'store_true'
+ )
+ argp.add_argument('-r', '--random'
+ ,help = 'Returns a random string that matches to the regex'
+ ,default = False
+ ,action = 'store_true'
+ )
+ argp.add_argument('-d', '--delimiter'
+ ,help = 'Delimiter - default is \\n'
+ ,default = '\n'
+ )
+ argp.add_argument('-v', '--verbose'
+ ,action = 'store_true'
+ ,help = 'Verbose mode'
+ ,default = False
+ )
+ argp.add_argument('regex'
+ ,metavar = 'REGEX'
+ ,help = 'REGEX string'
+ )
+ return vars(argp.parse_args())
+
+def __main__():
+ from sys import exit, stderr
+ # 'as(d|f)qw(e|r|s)[a-zA-Z]{2,3}'
+ # 'as(QWE|Z([XC]|Y|U)V){2,3}asdf'
+ # '.?'
+ # '.+'
+ # 'asdf.{1,4}qwer{2,5}'
+ # 'a(b)?(c)?(d)?'
+ # 'a[b][c][d]?[e]?
+ args = argparser()
+ if args['verbose']:
+ args['output'].write('%r%s' % (parse(args['regex'], limit=args['limit']), args['delimiter']))
+ if args['count']:
+ args['output'].write('%d%s' % (count(args['regex'], limit=args['limit']), args['delimiter']))
+ exit(0)
+ if args['random']:
+ args['output'].write('%s%s' % (getone(args['regex'], limit=args['limit']), args['delimiter']))
+ exit(0)
+ try:
+ g = generate(args['regex'], args['limit'])
+ except Exception, e:
+ print >> stderr, '[!] Error: ', e
+ exit(1)
+ for s in g:
+ try:
+ args['output'].write(s+args['delimiter'])
+ except:
+ break
+
+if __name__ == '__main__':
+ __main__()
+
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/regex_test.py b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/regex_test.py
new file mode 100644
index 0000000000..0d3d00702c
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/regex_test.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+
+"""
+ This program generates random text that matches a given regex-pattern.
+ The pattern is given via sys.argv and the generated text is passed to
+ the binary 'tests/test_rand' to check if the generated text also matches
+ the regex-pattern in the C implementation.
+ The exit-code of the testing program, is used to determine test success.
+
+ This script is called by the Makefile when doing 'make test'
+"""
+
+
+import re
+import sys
+import exrex
+from subprocess import call
+
+
+prog = "./tests/test_rand"
+
+if len(sys.argv) < 2:
+ print("")
+ print("usage: %s pattern [nrepeat]" % sys.argv[0])
+ print(" where [nrepeat] is optional")
+ print("")
+ sys.exit(-1)
+
+own_prog = sys.argv[0]
+pattern = sys.argv[1]
+if len(sys.argv) > 2:
+ ntests = int(sys.argv[2])
+else:
+ ntests = 10
+nfails = 0
+repeats = ntests
+
+
+try:
+ repeats = int(sys.argv[2])
+except:
+ pass
+
+r = 50
+while r < 0:
+ try:
+ g = exrex.generate(pattern)
+ break
+ except:
+ pass
+
+
+sys.stdout.write("%-35s" % (" pattern '%s': " % pattern))
+
+
+while repeats >= 0:
+ try:
+ repeats -= 1
+ example = exrex.getone(pattern)
+ #print("%s %s %s" % (prog, pattern, example))
+ ret = call([prog, "\"%s\"" % pattern, "\"%s\"" % example])
+ if ret != 0:
+ escaped = repr(example) # escapes special chars for better printing
+ print(" FAIL : doesn't match %s as expected [%s]." % (escaped, ", ".join([("0x%02x" % ord(e)) for e in example]) ))
+ nfails += 1
+
+ except:
+ #import traceback
+ #print("EXCEPTION!")
+ #raw_input(traceback.format_exc())
+ ntests -= 1
+ repeats += 1
+ #nfails += 1
+
+sys.stdout.write("%4d/%d tests succeeded \n" % (ntests - nfails, ntests))
+#print("")
+
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/regex_test_neg.py b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/regex_test_neg.py
new file mode 100644
index 0000000000..c3daad62c0
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/scripts/regex_test_neg.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+"""
+ This program generates random text that matches a given regex-pattern.
+ The pattern is given via sys.argv and the generated text is passed to
+ the binary 'tests/test_rand' to check if the generated text also matches
+ the regex-pattern in the C implementation.
+ The exit-code of the testing program, is used to determine test success.
+
+ This script is called by the Makefile when doing 'make test'
+"""
+
+
+import re
+import sys
+import string
+import random
+from subprocess import call
+
+
+prog = "./tests/test_rand_neg"
+
+if len(sys.argv) < 2:
+ print("")
+ print("usage: %s pattern [nrepeat]" % sys.argv[0])
+ print(" where [nrepeat] is optional")
+ print("")
+ sys.exit(-1)
+
+own_prog = sys.argv[0]
+pattern = sys.argv[1]
+if len(sys.argv) > 2:
+ ntests = int(sys.argv[2])
+else:
+ ntests = 10
+nfails = 0
+repeats = ntests
+
+
+try:
+ repeats = int(sys.argv[2])
+except:
+ pass
+
+sys.stdout.write("%-35s" % (" pattern '%s': " % pattern))
+
+
+
+
+def gen_no_match(pattern, minlen=1, maxlen=50, maxattempts=500):
+ nattempts = 0
+ while True:
+ nattempts += 1
+ ret = "".join([random.choice(string.printable) for i in range(random.Random().randint(minlen, maxlen))])
+ if re.findall(pattern, ret) == []:
+ return ret
+ if nattempts >= maxattempts:
+ raise Exception("Could not generate string that did not match the regex pattern '%s' after %d attempts" % (pattern, nattempts))
+
+
+
+while repeats >= 0:
+ try:
+ repeats -= 1
+ example = gen_no_match(pattern)
+ #print("%s %s %s" % (prog, pattern, example))
+ ret = call([prog, "\"%s\"" % pattern, "\"%s\"" % example])
+ if ret != 0:
+ escaped = repr(example) # escapes special chars for better printing
+ print(" FAIL : matches %s unexpectedly [%s]." % (escaped, ", ".join([("0x%02x" % ord(e)) for e in example]) ))
+ nfails += 1
+
+ except:
+ #import traceback
+ #print("EXCEPTION!")
+ #raw_input(traceback.format_exc())
+ ntests -= 1
+ repeats += 1
+ #nfails += 1
+
+sys.stdout.write("%4d/%d tests succeeded \n" % (ntests - nfails, ntests))
+#print("")
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test1.c b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test1.c
new file mode 100644
index 0000000000..ab54cc2ebe
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test1.c
@@ -0,0 +1,141 @@
+/*
+ * Testing various regex-patterns
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "re.h"
+
+
+#define OK ((char*) 1)
+#define NOK ((char*) 0)
+
+
+char* test_vector[][3] =
+{
+ { OK, "\\d", "5" },
+ { OK, "\\w+", "hej" },
+ { OK, "\\s", "\t \n" },
+ { NOK, "\\S", "\t \n" },
+ { OK, "[\\s]", "\t \n" },
+ { NOK, "[\\S]", "\t \n" },
+ { NOK, "\\D", "5" },
+ { NOK, "\\W+", "hej" },
+ { OK, "[0-9]+", "12345" },
+ { OK, "\\D", "hej" },
+ { NOK, "\\d", "hej" },
+ { OK, "[^\\w]", "\\" },
+ { OK, "[\\W]", "\\" },
+ { NOK, "[\\w]", "\\" },
+ { OK, "[^\\d]", "d" },
+ { NOK, "[\\d]", "d" },
+ { NOK, "[^\\D]", "d" },
+ { OK, "[\\D]", "d" },
+ { OK, "^.*\\\\.*$", "c:\\Tools" },
+ { OK, "^[\\+-]*[\\d]+$", "+27" },
+ { OK, "[abc]", "1c2" },
+ { NOK, "[abc]", "1C2" },
+ { OK, "[1-5]+", "0123456789" },
+ { OK, "[.2]", "1C2" },
+ { OK, "a*$", "Xaa" },
+ { OK, "a*$", "Xaa" },
+ { OK, "[a-h]+", "abcdefghxxx" },
+ { NOK, "[a-h]+", "ABCDEFGH" },
+ { OK, "[A-H]+", "ABCDEFGH" },
+ { NOK, "[A-H]+", "abcdefgh" },
+ { OK, "[^\\s]+", "abc def" },
+ { OK, "[^fc]+", "abc def" },
+ { OK, "[^d\\sf]+", "abc def" },
+ { OK, "\n", "abc\ndef" },
+ { OK, "b.\\s*\n", "aa\r\nbb\r\ncc\r\n\r\n" },
+ { OK, ".*c", "abcabc" },
+ { OK, ".+c", "abcabc" },
+ { OK, "[b-z].*", "ab" },
+ { OK, "b[k-z]*", "ab" },
+ { NOK, "[0-9]", " - " },
+ { OK, "[^0-9]", " - " },
+ { OK, "0|", "0|" },
+ { NOK, "\\d\\d:\\d\\d:\\d\\d", "0s:00:00" },
+ { NOK, "\\d\\d:\\d\\d:\\d\\d", "000:00" },
+ { NOK, "\\d\\d:\\d\\d:\\d\\d", "00:0000" },
+ { NOK, "\\d\\d:\\d\\d:\\d\\d", "100:0:00" },
+ { NOK, "\\d\\d:\\d\\d:\\d\\d", "00:100:00" },
+ { NOK, "\\d\\d:\\d\\d:\\d\\d", "0:00:100" },
+ { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "0:0:0" },
+ { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "0:00:0" },
+ { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "0:0:00" },
+ { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "00:0:0" },
+ { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "00:00:0" },
+ { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "00:0:00" },
+ { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "0:00:00" },
+ { OK, "\\d\\d?:\\d\\d?:\\d\\d?", "00:00:00" },
+ { OK, "[Hh]ello [Ww]orld\\s*[!]?", "Hello world !" },
+ { OK, "[Hh]ello [Ww]orld\\s*[!]?", "hello world !" },
+ { OK, "[Hh]ello [Ww]orld\\s*[!]?", "Hello World !" },
+ { OK, "[Hh]ello [Ww]orld\\s*[!]?", "Hello world! " },
+ { OK, "[Hh]ello [Ww]orld\\s*[!]?", "Hello world !" },
+ { OK, "[Hh]ello [Ww]orld\\s*[!]?", "hello World !" },
+ { NOK, "\\d\\d?:\\d\\d?:\\d\\d?", "a:0" }, /* Failing test case reported in https://github.com/kokke/tiny-regex-c/issues/12 */
+/*
+ { OK, "[^\\w][^-1-4]", ")T" },
+ { OK, "[^\\w][^-1-4]", ")^" },
+ { OK, "[^\\w][^-1-4]", "*)" },
+ { OK, "[^\\w][^-1-4]", "!." },
+ { OK, "[^\\w][^-1-4]", " x" },
+ { OK, "[^\\w][^-1-4]", "$b" },
+*/
+ { OK, ".?bar", "real_bar" },
+ { NOK, ".?bar", "real_foo" },
+ { NOK, "X?Y", "Z" },
+};
+
+
+void re_print(re_t);
+
+int main()
+{
+ char* text;
+ char* pattern;
+ int should_fail;
+ size_t ntests = sizeof(test_vector) / sizeof(*test_vector);
+ size_t nfailed = 0;
+ size_t i;
+
+ for (i = 0; i < ntests; ++i)
+ {
+ pattern = test_vector[i][1];
+ text = test_vector[i][2];
+ should_fail = (test_vector[i][0] == NOK);
+
+ int m = re_match(pattern, text);
+
+ if (should_fail)
+ {
+ if (m != (-1))
+ {
+ printf("\n");
+ re_print(re_compile(pattern));
+ fprintf(stderr, "[%lu/%lu]: pattern '%s' matched '%s' unexpectedly. \n", (i+1), ntests, pattern, text);
+ nfailed += 1;
+ }
+ }
+ else
+ {
+ if (m == (-1))
+ {
+ printf("\n");
+ re_print(re_compile(pattern));
+ fprintf(stderr, "[%lu/%lu]: pattern '%s' didn't match '%s' as expected. \n", (i+1), ntests, pattern, text);
+ nfailed += 1;
+ }
+ }
+ }
+
+ // printf("\n");
+ printf("%lu/%lu tests succeeded.\n", ntests - nfailed, ntests);
+ printf("\n");
+ printf("\n");
+ printf("\n");
+
+ return 0;
+}
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test2.c b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test2.c
new file mode 100644
index 0000000000..f234f2d49a
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test2.c
@@ -0,0 +1,2097 @@
+/*
+ * A small performance test, to see how the library performs on a few MBs of text.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "re.h"
+
+
+char buf[] = {
+ 0x79, 0x66, 0x75, 0x66, 0x63, 0x6f, 0x75, 0x62, 0x74, 0x77, 0x66, 0x6f, 0x69, 0x72, 0x71, 0x78,
+ 0x66, 0x69, 0x63, 0x72, 0x6c, 0x61, 0x6a, 0x79, 0x68, 0x6d, 0x67, 0x66, 0x67, 0x75, 0x69, 0x65,
+ 0x62, 0x6d, 0x72, 0x63, 0x67, 0x74, 0x64, 0x62, 0x67, 0x64, 0x6c, 0x66, 0x78, 0x68, 0x75, 0x6f,
+ 0x73, 0x6b, 0x68, 0x68, 0x64, 0x68, 0x71, 0x66, 0x79, 0x6a, 0x6a, 0x67, 0x70, 0x6e, 0x6f, 0x77,
+ 0x6e, 0x68, 0x79, 0x65, 0x76, 0x61, 0x73, 0x6e, 0x77, 0x71, 0x74, 0x64, 0x77, 0x76, 0x74, 0x6d,
+ 0x6e, 0x76, 0x79, 0x71, 0x79, 0x79, 0x6a, 0x63, 0x74, 0x75, 0x6f, 0x74, 0x6d, 0x66, 0x67, 0x64,
+ 0x77, 0x61, 0x75, 0x79, 0x72, 0x79, 0x69, 0x73, 0x6f, 0x69, 0x67, 0x65, 0x6a, 0x6b, 0x66, 0x69,
+ 0x63, 0x79, 0x64, 0x71, 0x68, 0x6c, 0x67, 0x66, 0x6c, 0x6e, 0x79, 0x64, 0x67, 0x76, 0x64, 0x6a,
+ 0x73, 0x64, 0x6a, 0x61, 0x79, 0x6d, 0x71, 0x72, 0x68, 0x67, 0x68, 0x67, 0x66, 0x64, 0x63, 0x77,
+ 0x6f, 0x73, 0x67, 0x6d, 0x63, 0x74, 0x77, 0x72, 0x6b, 0x6f, 0x6f, 0x63, 0x68, 0x6f, 0x66, 0x67,
+ 0x73, 0x66, 0x6f, 0x75, 0x77, 0x62, 0x71, 0x6b, 0x77, 0x72, 0x68, 0x6d, 0x66, 0x71, 0x78, 0x75,
+ 0x6a, 0x74, 0x75, 0x6c, 0x69, 0x62, 0x64, 0x61, 0x78, 0x78, 0x66, 0x71, 0x61, 0x6d, 0x76, 0x76,
+ 0x6f, 0x65, 0x68, 0x78, 0x70, 0x79, 0x65, 0x68, 0x76, 0x62, 0x73, 0x6f, 0x64, 0x76, 0x63, 0x64,
+ 0x74, 0x73, 0x6e, 0x6a, 0x6a, 0x61, 0x72, 0x62, 0x66, 0x76, 0x63, 0x71, 0x6d, 0x74, 0x67, 0x75,
+ 0x6a, 0x6b, 0x6a, 0x6e, 0x75, 0x72, 0x73, 0x6d, 0x65, 0x69, 0x79, 0x62, 0x6d, 0x67, 0x63, 0x63,
+ 0x6c, 0x6c, 0x67, 0x6e, 0x6b, 0x76, 0x6e, 0x61, 0x79, 0x6a, 0x62, 0x6d, 0x70, 0x69, 0x67, 0x68,
+ 0x70, 0x74, 0x6b, 0x70, 0x74, 0x6e, 0x62, 0x6f, 0x6e, 0x6e, 0x61, 0x76, 0x68, 0x78, 0x77, 0x6f,
+ 0x77, 0x76, 0x6d, 0x6d, 0x75, 0x6b, 0x73, 0x6e, 0x79, 0x69, 0x6f, 0x72, 0x6a, 0x78, 0x67, 0x65,
+ 0x6b, 0x71, 0x61, 0x6b, 0x72, 0x75, 0x69, 0x65, 0x6b, 0x70, 0x69, 0x72, 0x70, 0x76, 0x79, 0x76,
+ 0x72, 0x76, 0x69, 0x76, 0x67, 0x66, 0x61, 0x72, 0x6c, 0x71, 0x66, 0x79, 0x74, 0x78, 0x6c, 0x6e,
+ 0x64, 0x68, 0x61, 0x66, 0x6f, 0x76, 0x6e, 0x67, 0x63, 0x78, 0x61, 0x75, 0x73, 0x65, 0x69, 0x77,
+ 0x67, 0x75, 0x77, 0x62, 0x75, 0x6b, 0x70, 0x77, 0x75, 0x69, 0x70, 0x62, 0x76, 0x73, 0x71, 0x70,
+ 0x78, 0x71, 0x77, 0x78, 0x66, 0x6e, 0x75, 0x65, 0x76, 0x6b, 0x79, 0x6d, 0x74, 0x61, 0x68, 0x61,
+ 0x78, 0x6e, 0x74, 0x77, 0x63, 0x79, 0x6e, 0x6d, 0x77, 0x71, 0x6f, 0x66, 0x6f, 0x65, 0x65, 0x72,
+ 0x61, 0x76, 0x64, 0x64, 0x67, 0x77, 0x69, 0x78, 0x6a, 0x75, 0x67, 0x66, 0x63, 0x79, 0x65, 0x6d,
+ 0x62, 0x68, 0x72, 0x6e, 0x78, 0x6d, 0x71, 0x6c, 0x6e, 0x6a, 0x62, 0x62, 0x64, 0x75, 0x6c, 0x6d,
+ 0x61, 0x71, 0x67, 0x6d, 0x68, 0x6f, 0x67, 0x71, 0x69, 0x74, 0x75, 0x78, 0x69, 0x6c, 0x65, 0x79,
+ 0x6e, 0x65, 0x76, 0x61, 0x75, 0x6a, 0x65, 0x73, 0x68, 0x63, 0x62, 0x6d, 0x6b, 0x6d, 0x6f, 0x6d,
+ 0x6c, 0x69, 0x79, 0x69, 0x6e, 0x76, 0x74, 0x66, 0x6f, 0x63, 0x6c, 0x6e, 0x6a, 0x75, 0x70, 0x70,
+ 0x62, 0x78, 0x71, 0x63, 0x6d, 0x6e, 0x67, 0x6e, 0x66, 0x65, 0x72, 0x62, 0x62, 0x74, 0x78, 0x77,
+ 0x6f, 0x69, 0x66, 0x75, 0x76, 0x79, 0x6c, 0x73, 0x62, 0x6e, 0x6c, 0x70, 0x6a, 0x76, 0x6c, 0x78,
+ 0x6e, 0x66, 0x6f, 0x68, 0x65, 0x6c, 0x75, 0x70, 0x62, 0x62, 0x78, 0x61, 0x62, 0x6b, 0x61, 0x6e,
+ 0x61, 0x78, 0x71, 0x71, 0x73, 0x6e, 0x6c, 0x6f, 0x6a, 0x67, 0x61, 0x65, 0x61, 0x6b, 0x74, 0x78,
+ 0x70, 0x67, 0x73, 0x65, 0x78, 0x79, 0x74, 0x76, 0x6f, 0x74, 0x73, 0x6f, 0x63, 0x76, 0x63, 0x70,
+ 0x66, 0x6d, 0x6a, 0x71, 0x62, 0x78, 0x68, 0x74, 0x71, 0x70, 0x69, 0x65, 0x77, 0x65, 0x77, 0x65,
+ 0x78, 0x6a, 0x72, 0x71, 0x6e, 0x65, 0x79, 0x64, 0x6b, 0x73, 0x79, 0x63, 0x64, 0x6b, 0x78, 0x64,
+ 0x66, 0x69, 0x77, 0x62, 0x6f, 0x71, 0x6c, 0x73, 0x6c, 0x68, 0x78, 0x70, 0x71, 0x79, 0x72, 0x79,
+ 0x6f, 0x6b, 0x6c, 0x77, 0x63, 0x6a, 0x68, 0x67, 0x69, 0x70, 0x68, 0x6e, 0x61, 0x71, 0x77, 0x61,
+ 0x70, 0x65, 0x76, 0x6c, 0x72, 0x71, 0x66, 0x66, 0x6e, 0x67, 0x71, 0x6c, 0x77, 0x63, 0x6c, 0x65,
+ 0x6b, 0x72, 0x66, 0x63, 0x73, 0x61, 0x70, 0x71, 0x64, 0x69, 0x6e, 0x71, 0x78, 0x6c, 0x74, 0x61,
+ 0x77, 0x65, 0x71, 0x61, 0x64, 0x66, 0x68, 0x67, 0x6f, 0x75, 0x66, 0x69, 0x73, 0x6e, 0x76, 0x6e,
+ 0x66, 0x63, 0x66, 0x74, 0x6c, 0x6b, 0x68, 0x62, 0x6e, 0x6c, 0x78, 0x65, 0x73, 0x70, 0x76, 0x78,
+ 0x61, 0x70, 0x66, 0x70, 0x79, 0x67, 0x69, 0x67, 0x68, 0x78, 0x70, 0x76, 0x73, 0x63, 0x6b, 0x68,
+ 0x71, 0x67, 0x66, 0x6c, 0x78, 0x77, 0x6c, 0x72, 0x76, 0x6c, 0x73, 0x68, 0x68, 0x61, 0x68, 0x6c,
+ 0x6c, 0x79, 0x6a, 0x66, 0x74, 0x77, 0x65, 0x62, 0x6e, 0x71, 0x6b, 0x61, 0x71, 0x75, 0x67, 0x62,
+ 0x76, 0x62, 0x6b, 0x71, 0x71, 0x6b, 0x65, 0x69, 0x74, 0x75, 0x79, 0x77, 0x6c, 0x65, 0x64, 0x67,
+ 0x70, 0x6a, 0x71, 0x73, 0x6c, 0x73, 0x61, 0x69, 0x70, 0x68, 0x73, 0x76, 0x74, 0x69, 0x77, 0x69,
+ 0x6b, 0x6b, 0x70, 0x68, 0x78, 0x79, 0x67, 0x79, 0x62, 0x63, 0x74, 0x68, 0x69, 0x63, 0x77, 0x70,
+ 0x6b, 0x6a, 0x74, 0x6a, 0x66, 0x75, 0x75, 0x73, 0x65, 0x68, 0x63, 0x6a, 0x64, 0x76, 0x64, 0x72,
+ 0x79, 0x77, 0x6d, 0x74, 0x69, 0x6a, 0x66, 0x65, 0x67, 0x77, 0x67, 0x73, 0x73, 0x74, 0x6a, 0x75,
+ 0x65, 0x6e, 0x72, 0x61, 0x63, 0x6e, 0x67, 0x74, 0x6d, 0x6d, 0x65, 0x76, 0x76, 0x65, 0x61, 0x64,
+ 0x70, 0x77, 0x78, 0x72, 0x77, 0x70, 0x6f, 0x69, 0x66, 0x71, 0x6e, 0x6c, 0x75, 0x68, 0x68, 0x6f,
+ 0x63, 0x6f, 0x6d, 0x62, 0x61, 0x70, 0x72, 0x77, 0x6c, 0x76, 0x6b, 0x70, 0x68, 0x6e, 0x6d, 0x78,
+ 0x68, 0x65, 0x6c, 0x6f, 0x65, 0x73, 0x63, 0x69, 0x65, 0x74, 0x77, 0x66, 0x64, 0x74, 0x68, 0x66,
+ 0x71, 0x62, 0x67, 0x72, 0x6a, 0x64, 0x68, 0x77, 0x6d, 0x72, 0x6b, 0x78, 0x72, 0x6b, 0x72, 0x78,
+ 0x70, 0x65, 0x69, 0x65, 0x72, 0x63, 0x6a, 0x69, 0x62, 0x75, 0x72, 0x6b, 0x69, 0x6a, 0x78, 0x65,
+ 0x68, 0x69, 0x6d, 0x6a, 0x75, 0x72, 0x66, 0x76, 0x79, 0x66, 0x67, 0x74, 0x79, 0x69, 0x76, 0x6d,
+ 0x77, 0x77, 0x65, 0x6b, 0x73, 0x61, 0x73, 0x6c, 0x70, 0x74, 0x6f, 0x76, 0x79, 0x6a, 0x66, 0x64,
+ 0x67, 0x6d, 0x72, 0x66, 0x66, 0x6e, 0x68, 0x63, 0x64, 0x73, 0x6c, 0x63, 0x6c, 0x71, 0x6d, 0x6c,
+ 0x63, 0x69, 0x65, 0x6b, 0x73, 0x64, 0x6b, 0x76, 0x68, 0x73, 0x71, 0x61, 0x72, 0x67, 0x6a, 0x79,
+ 0x78, 0x6a, 0x6f, 0x65, 0x71, 0x65, 0x72, 0x66, 0x62, 0x6d, 0x63, 0x73, 0x64, 0x77, 0x78, 0x77,
+ 0x6f, 0x67, 0x70, 0x6d, 0x6f, 0x66, 0x79, 0x79, 0x74, 0x75, 0x65, 0x75, 0x6e, 0x72, 0x76, 0x72,
+ 0x6a, 0x77, 0x78, 0x6e, 0x6e, 0x67, 0x74, 0x6d, 0x6f, 0x66, 0x6f, 0x6a, 0x67, 0x65, 0x6c, 0x75,
+ 0x63, 0x78, 0x6c, 0x67, 0x78, 0x79, 0x72, 0x61, 0x6b, 0x71, 0x76, 0x77, 0x6d, 0x6d, 0x69, 0x76,
+ 0x6f, 0x6e, 0x66, 0x61, 0x61, 0x62, 0x62, 0x79, 0x61, 0x66, 0x6d, 0x78, 0x74, 0x78, 0x69, 0x6f,
+ 0x63, 0x79, 0x70, 0x74, 0x75, 0x79, 0x63, 0x63, 0x70, 0x6d, 0x70, 0x66, 0x67, 0x65, 0x61, 0x70,
+ 0x79, 0x79, 0x6d, 0x6a, 0x6d, 0x77, 0x6a, 0x75, 0x62, 0x6b, 0x69, 0x64, 0x71, 0x67, 0x64, 0x63,
+ 0x62, 0x68, 0x6c, 0x68, 0x72, 0x6a, 0x67, 0x6d, 0x72, 0x73, 0x78, 0x72, 0x64, 0x65, 0x77, 0x72,
+ 0x72, 0x71, 0x6a, 0x64, 0x70, 0x71, 0x76, 0x73, 0x65, 0x75, 0x6d, 0x76, 0x6f, 0x6b, 0x66, 0x73,
+ 0x6b, 0x69, 0x6b, 0x66, 0x68, 0x79, 0x64, 0x78, 0x6b, 0x67, 0x76, 0x6e, 0x74, 0x70, 0x67, 0x69,
+ 0x62, 0x64, 0x72, 0x6a, 0x6e, 0x72, 0x72, 0x6b, 0x73, 0x6f, 0x6d, 0x6c, 0x6c, 0x66, 0x78, 0x75,
+ 0x66, 0x66, 0x6e, 0x6b, 0x69, 0x74, 0x6c, 0x79, 0x69, 0x70, 0x6c, 0x72, 0x6e, 0x74, 0x6e, 0x6e,
+ 0x67, 0x6f, 0x62, 0x70, 0x65, 0x62, 0x62, 0x6a, 0x68, 0x66, 0x76, 0x6a, 0x74, 0x6a, 0x69, 0x71,
+ 0x6c, 0x76, 0x77, 0x6b, 0x6b, 0x73, 0x6f, 0x64, 0x62, 0x67, 0x6a, 0x6b, 0x68, 0x65, 0x61, 0x69,
+ 0x77, 0x78, 0x74, 0x62, 0x70, 0x73, 0x70, 0x68, 0x6b, 0x75, 0x72, 0x70, 0x68, 0x6b, 0x75, 0x6d,
+ 0x62, 0x74, 0x77, 0x65, 0x71, 0x77, 0x6d, 0x65, 0x70, 0x66, 0x70, 0x6c, 0x69, 0x68, 0x75, 0x75,
+ 0x62, 0x62, 0x75, 0x69, 0x6d, 0x68, 0x63, 0x6e, 0x6d, 0x6b, 0x78, 0x70, 0x77, 0x61, 0x77, 0x69,
+ 0x78, 0x69, 0x6a, 0x68, 0x64, 0x74, 0x6e, 0x72, 0x6c, 0x6d, 0x72, 0x75, 0x6e, 0x66, 0x66, 0x67,
+ 0x6f, 0x73, 0x6e, 0x6f, 0x78, 0x72, 0x72, 0x6c, 0x66, 0x70, 0x66, 0x69, 0x6a, 0x71, 0x6e, 0x67,
+ 0x6c, 0x74, 0x76, 0x78, 0x74, 0x77, 0x73, 0x66, 0x73, 0x75, 0x70, 0x67, 0x67, 0x62, 0x75, 0x77,
+ 0x67, 0x78, 0x76, 0x6d, 0x6a, 0x78, 0x69, 0x75, 0x63, 0x6a, 0x62, 0x6e, 0x6d, 0x66, 0x61, 0x6b,
+ 0x73, 0x68, 0x72, 0x78, 0x79, 0x6d, 0x69, 0x68, 0x6f, 0x73, 0x6d, 0x62, 0x68, 0x70, 0x78, 0x6f,
+ 0x6e, 0x63, 0x63, 0x6f, 0x78, 0x65, 0x74, 0x6a, 0x77, 0x78, 0x79, 0x6a, 0x62, 0x78, 0x70, 0x69,
+ 0x65, 0x79, 0x77, 0x6d, 0x6b, 0x6c, 0x6b, 0x62, 0x6e, 0x75, 0x73, 0x6e, 0x63, 0x67, 0x6c, 0x63,
+ 0x63, 0x6a, 0x71, 0x62, 0x61, 0x78, 0x6c, 0x6d, 0x69, 0x64, 0x6f, 0x71, 0x62, 0x6f, 0x62, 0x72,
+ 0x6b, 0x61, 0x65, 0x77, 0x62, 0x75, 0x79, 0x6e, 0x73, 0x79, 0x67, 0x76, 0x63, 0x70, 0x69, 0x6a,
+ 0x6c, 0x62, 0x70, 0x62, 0x61, 0x77, 0x69, 0x6e, 0x62, 0x71, 0x6e, 0x6b, 0x72, 0x76, 0x76, 0x64,
+ 0x77, 0x68, 0x66, 0x78, 0x77, 0x64, 0x6b, 0x6a, 0x74, 0x66, 0x67, 0x69, 0x70, 0x65, 0x6a, 0x68,
+ 0x71, 0x76, 0x71, 0x69, 0x75, 0x64, 0x6b, 0x71, 0x71, 0x61, 0x67, 0x6d, 0x67, 0x62, 0x69, 0x6b,
+ 0x61, 0x66, 0x62, 0x63, 0x76, 0x64, 0x66, 0x73, 0x63, 0x78, 0x74, 0x77, 0x6e, 0x63, 0x68, 0x61,
+ 0x6c, 0x71, 0x70, 0x69, 0x65, 0x77, 0x6d, 0x69, 0x72, 0x6b, 0x6e, 0x6f, 0x75, 0x78, 0x6d, 0x74,
+ 0x6a, 0x68, 0x72, 0x6b, 0x69, 0x72, 0x6b, 0x78, 0x72, 0x68, 0x65, 0x68, 0x76, 0x6d, 0x67, 0x71,
+ 0x65, 0x6f, 0x61, 0x72, 0x6f, 0x63, 0x70, 0x67, 0x78, 0x73, 0x6f, 0x62, 0x6b, 0x65, 0x64, 0x6b,
+ 0x62, 0x76, 0x76, 0x77, 0x63, 0x6a, 0x77, 0x78, 0x6e, 0x79, 0x63, 0x75, 0x62, 0x75, 0x67, 0x67,
+ 0x62, 0x6a, 0x6a, 0x74, 0x6f, 0x6a, 0x6c, 0x75, 0x70, 0x65, 0x61, 0x6f, 0x68, 0x6b, 0x67, 0x6e,
+ 0x6a, 0x6c, 0x69, 0x71, 0x63, 0x75, 0x6a, 0x72, 0x71, 0x6d, 0x75, 0x73, 0x78, 0x62, 0x78, 0x66,
+ 0x79, 0x79, 0x70, 0x6e, 0x75, 0x6c, 0x66, 0x6e, 0x6b, 0x6b, 0x73, 0x71, 0x6d, 0x77, 0x68, 0x6d,
+ 0x76, 0x75, 0x6c, 0x77, 0x6f, 0x71, 0x6c, 0x67, 0x70, 0x6f, 0x76, 0x70, 0x72, 0x64, 0x74, 0x77,
+ 0x63, 0x78, 0x69, 0x73, 0x6f, 0x77, 0x6f, 0x6a, 0x64, 0x64, 0x77, 0x6b, 0x79, 0x72, 0x65, 0x6f,
+ 0x74, 0x61, 0x71, 0x71, 0x6d, 0x6d, 0x68, 0x66, 0x63, 0x6d, 0x73, 0x76, 0x6f, 0x76, 0x68, 0x6c,
+ 0x61, 0x6b, 0x76, 0x6a, 0x69, 0x73, 0x76, 0x72, 0x6c, 0x63, 0x78, 0x74, 0x73, 0x78, 0x62, 0x74,
+ 0x70, 0x68, 0x62, 0x6a, 0x70, 0x66, 0x74, 0x66, 0x61, 0x66, 0x72, 0x74, 0x6f, 0x67, 0x76, 0x67,
+ 0x6c, 0x64, 0x73, 0x69, 0x6d, 0x69, 0x6a, 0x74, 0x67, 0x73, 0x78, 0x69, 0x75, 0x72, 0x6e, 0x61,
+ 0x76, 0x6a, 0x69, 0x73, 0x6a, 0x77, 0x71, 0x67, 0x66, 0x6e, 0x66, 0x74, 0x71, 0x6a, 0x6f, 0x64,
+ 0x63, 0x6c, 0x67, 0x70, 0x77, 0x78, 0x64, 0x6f, 0x6b, 0x69, 0x76, 0x75, 0x66, 0x6c, 0x6d, 0x6d,
+ 0x65, 0x75, 0x65, 0x75, 0x77, 0x6a, 0x72, 0x6b, 0x77, 0x67, 0x65, 0x63, 0x6f, 0x6e, 0x6d, 0x6a,
+ 0x6d, 0x66, 0x78, 0x66, 0x66, 0x76, 0x61, 0x6f, 0x74, 0x6f, 0x63, 0x75, 0x79, 0x74, 0x61, 0x6a,
+ 0x64, 0x62, 0x75, 0x79, 0x77, 0x6a, 0x68, 0x76, 0x67, 0x61, 0x74, 0x64, 0x6d, 0x64, 0x75, 0x65,
+ 0x64, 0x6c, 0x70, 0x75, 0x68, 0x6c, 0x70, 0x74, 0x70, 0x71, 0x6a, 0x6d, 0x61, 0x79, 0x67, 0x76,
+ 0x69, 0x6c, 0x6a, 0x63, 0x6b, 0x69, 0x77, 0x63, 0x77, 0x63, 0x61, 0x72, 0x66, 0x76, 0x69, 0x78,
+ 0x72, 0x68, 0x72, 0x75, 0x74, 0x63, 0x64, 0x6a, 0x73, 0x79, 0x70, 0x6f, 0x69, 0x65, 0x6b, 0x73,
+ 0x71, 0x70, 0x6b, 0x68, 0x61, 0x68, 0x79, 0x62, 0x75, 0x65, 0x79, 0x68, 0x65, 0x6f, 0x67, 0x70,
+ 0x77, 0x78, 0x6f, 0x77, 0x72, 0x65, 0x75, 0x73, 0x6a, 0x64, 0x6c, 0x75, 0x6e, 0x65, 0x76, 0x78,
+ 0x73, 0x6a, 0x69, 0x6d, 0x61, 0x6d, 0x6a, 0x6d, 0x71, 0x6d, 0x71, 0x6b, 0x64, 0x6c, 0x65, 0x61,
+ 0x67, 0x6a, 0x6c, 0x64, 0x78, 0x64, 0x64, 0x78, 0x75, 0x73, 0x70, 0x76, 0x69, 0x66, 0x6e, 0x74,
+ 0x72, 0x73, 0x79, 0x78, 0x72, 0x74, 0x6b, 0x78, 0x71, 0x63, 0x62, 0x72, 0x75, 0x6d, 0x68, 0x63,
+ 0x69, 0x78, 0x6d, 0x71, 0x68, 0x6d, 0x64, 0x65, 0x75, 0x70, 0x6b, 0x73, 0x63, 0x72, 0x69, 0x75,
+ 0x77, 0x76, 0x67, 0x72, 0x75, 0x6c, 0x76, 0x65, 0x6e, 0x70, 0x63, 0x6f, 0x66, 0x71, 0x65, 0x62,
+ 0x73, 0x64, 0x70, 0x63, 0x78, 0x74, 0x6f, 0x73, 0x75, 0x6f, 0x78, 0x73, 0x71, 0x6e, 0x77, 0x75,
+ 0x70, 0x76, 0x74, 0x75, 0x61, 0x70, 0x68, 0x72, 0x71, 0x79, 0x6d, 0x72, 0x69, 0x79, 0x69, 0x74,
+ 0x61, 0x72, 0x75, 0x68, 0x72, 0x79, 0x62, 0x65, 0x6d, 0x61, 0x71, 0x6c, 0x77, 0x67, 0x73, 0x66,
+ 0x67, 0x69, 0x6e, 0x62, 0x6c, 0x74, 0x76, 0x70, 0x71, 0x64, 0x71, 0x79, 0x70, 0x73, 0x63, 0x6c,
+ 0x67, 0x63, 0x73, 0x6f, 0x68, 0x78, 0x73, 0x66, 0x6c, 0x6b, 0x62, 0x78, 0x70, 0x68, 0x62, 0x6c,
+ 0x66, 0x67, 0x74, 0x77, 0x61, 0x6c, 0x71, 0x70, 0x6a, 0x6f, 0x6c, 0x75, 0x6e, 0x61, 0x79, 0x68,
+ 0x73, 0x61, 0x79, 0x69, 0x71, 0x6e, 0x6d, 0x66, 0x6c, 0x68, 0x74, 0x72, 0x64, 0x64, 0x6a, 0x75,
+ 0x70, 0x6c, 0x69, 0x69, 0x68, 0x72, 0x66, 0x75, 0x61, 0x61, 0x69, 0x64, 0x66, 0x69, 0x68, 0x63,
+ 0x67, 0x6d, 0x76, 0x71, 0x6a, 0x6e, 0x6a, 0x78, 0x61, 0x77, 0x67, 0x77, 0x69, 0x77, 0x71, 0x6d,
+ 0x6b, 0x6a, 0x72, 0x71, 0x72, 0x6e, 0x61, 0x65, 0x6f, 0x71, 0x6f, 0x64, 0x6d, 0x79, 0x61, 0x66,
+ 0x6b, 0x77, 0x76, 0x64, 0x67, 0x6f, 0x66, 0x6e, 0x79, 0x64, 0x77, 0x75, 0x6d, 0x76, 0x64, 0x69,
+ 0x78, 0x6b, 0x72, 0x6f, 0x75, 0x74, 0x71, 0x6c, 0x77, 0x61, 0x6d, 0x69, 0x61, 0x75, 0x72, 0x61,
+ 0x6d, 0x78, 0x6c, 0x71, 0x6e, 0x74, 0x6c, 0x6e, 0x71, 0x78, 0x77, 0x6f, 0x64, 0x76, 0x66, 0x62,
+ 0x62, 0x75, 0x6b, 0x75, 0x66, 0x75, 0x73, 0x6a, 0x76, 0x75, 0x70, 0x73, 0x68, 0x6c, 0x77, 0x6d,
+ 0x75, 0x71, 0x78, 0x73, 0x64, 0x73, 0x78, 0x69, 0x76, 0x64, 0x61, 0x6e, 0x62, 0x6a, 0x79, 0x6f,
+ 0x70, 0x6a, 0x6d, 0x75, 0x6e, 0x75, 0x63, 0x66, 0x65, 0x75, 0x6a, 0x64, 0x6b, 0x65, 0x66, 0x64,
+ 0x62, 0x66, 0x78, 0x67, 0x77, 0x75, 0x74, 0x62, 0x67, 0x61, 0x6e, 0x67, 0x68, 0x6b, 0x6c, 0x73,
+ 0x6d, 0x69, 0x69, 0x75, 0x6e, 0x6c, 0x69, 0x73, 0x77, 0x69, 0x63, 0x64, 0x75, 0x6c, 0x6b, 0x78,
+ 0x69, 0x78, 0x67, 0x73, 0x72, 0x6a, 0x64, 0x78, 0x6a, 0x72, 0x78, 0x6f, 0x61, 0x76, 0x71, 0x66,
+ 0x76, 0x63, 0x77, 0x67, 0x75, 0x65, 0x73, 0x64, 0x64, 0x6a, 0x77, 0x68, 0x6f, 0x61, 0x6f, 0x78,
+ 0x6e, 0x6d, 0x78, 0x70, 0x76, 0x6b, 0x67, 0x67, 0x75, 0x6d, 0x6e, 0x62, 0x70, 0x74, 0x66, 0x6a,
+ 0x70, 0x68, 0x70, 0x6c, 0x74, 0x6d, 0x62, 0x66, 0x69, 0x73, 0x6a, 0x70, 0x66, 0x69, 0x6b, 0x73,
+ 0x62, 0x70, 0x6f, 0x61, 0x63, 0x63, 0x71, 0x6f, 0x70, 0x6a, 0x75, 0x64, 0x6d, 0x74, 0x68, 0x76,
+ 0x61, 0x6e, 0x72, 0x71, 0x62, 0x70, 0x64, 0x66, 0x70, 0x71, 0x61, 0x69, 0x68, 0x64, 0x61, 0x65,
+ 0x79, 0x78, 0x6b, 0x72, 0x64, 0x63, 0x6d, 0x6c, 0x67, 0x61, 0x6a, 0x67, 0x64, 0x73, 0x70, 0x76,
+ 0x74, 0x70, 0x6c, 0x66, 0x77, 0x71, 0x66, 0x72, 0x6c, 0x67, 0x69, 0x66, 0x70, 0x68, 0x67, 0x6e,
+ 0x70, 0x69, 0x67, 0x71, 0x73, 0x72, 0x6d, 0x71, 0x74, 0x64, 0x6b, 0x71, 0x73, 0x70, 0x78, 0x70,
+ 0x75, 0x76, 0x74, 0x6e, 0x73, 0x63, 0x6d, 0x64, 0x6e, 0x73, 0x79, 0x68, 0x6b, 0x6e, 0x6a, 0x77,
+ 0x6e, 0x62, 0x67, 0x6d, 0x72, 0x68, 0x6c, 0x76, 0x64, 0x6f, 0x73, 0x6d, 0x62, 0x67, 0x69, 0x68,
+ 0x64, 0x65, 0x72, 0x67, 0x75, 0x66, 0x68, 0x64, 0x61, 0x67, 0x6a, 0x75, 0x75, 0x6c, 0x72, 0x71,
+ 0x75, 0x74, 0x73, 0x78, 0x74, 0x6d, 0x62, 0x79, 0x6f, 0x63, 0x6f, 0x62, 0x67, 0x73, 0x70, 0x70,
+ 0x72, 0x6e, 0x68, 0x79, 0x76, 0x77, 0x76, 0x6e, 0x6e, 0x6e, 0x78, 0x65, 0x65, 0x63, 0x64, 0x61,
+ 0x62, 0x78, 0x71, 0x72, 0x63, 0x68, 0x71, 0x63, 0x73, 0x70, 0x70, 0x61, 0x71, 0x76, 0x64, 0x67,
+ 0x74, 0x71, 0x74, 0x67, 0x74, 0x77, 0x6e, 0x79, 0x73, 0x67, 0x79, 0x70, 0x77, 0x6f, 0x6b, 0x79,
+ 0x74, 0x79, 0x67, 0x6a, 0x6e, 0x6a, 0x70, 0x76, 0x62, 0x77, 0x74, 0x70, 0x72, 0x63, 0x6c, 0x74,
+ 0x65, 0x64, 0x63, 0x78, 0x64, 0x6d, 0x73, 0x69, 0x65, 0x70, 0x6d, 0x71, 0x6e, 0x6c, 0x62, 0x64,
+ 0x67, 0x6c, 0x72, 0x65, 0x6b, 0x63, 0x77, 0x6d, 0x63, 0x70, 0x72, 0x65, 0x6d, 0x74, 0x6d, 0x63,
+ 0x78, 0x75, 0x68, 0x70, 0x73, 0x63, 0x6e, 0x77, 0x71, 0x61, 0x73, 0x6b, 0x75, 0x6e, 0x76, 0x74,
+ 0x63, 0x70, 0x72, 0x65, 0x62, 0x75, 0x61, 0x6a, 0x76, 0x66, 0x75, 0x73, 0x64, 0x67, 0x6d, 0x77,
+ 0x64, 0x6a, 0x66, 0x6b, 0x6b, 0x6c, 0x66, 0x64, 0x69, 0x72, 0x70, 0x76, 0x67, 0x74, 0x6c, 0x6a,
+ 0x72, 0x67, 0x74, 0x66, 0x6a, 0x62, 0x66, 0x73, 0x79, 0x77, 0x6b, 0x62, 0x77, 0x6a, 0x75, 0x73,
+ 0x61, 0x69, 0x78, 0x6b, 0x66, 0x67, 0x62, 0x70, 0x65, 0x64, 0x76, 0x79, 0x77, 0x6c, 0x75, 0x74,
+ 0x77, 0x63, 0x73, 0x62, 0x63, 0x73, 0x6f, 0x6c, 0x64, 0x65, 0x6e, 0x6c, 0x6a, 0x72, 0x77, 0x6b,
+ 0x61, 0x78, 0x75, 0x77, 0x6c, 0x61, 0x64, 0x63, 0x64, 0x6a, 0x61, 0x68, 0x6e, 0x65, 0x68, 0x66,
+ 0x71, 0x6b, 0x73, 0x6f, 0x62, 0x6e, 0x69, 0x72, 0x79, 0x63, 0x6f, 0x79, 0x79, 0x75, 0x70, 0x77,
+ 0x75, 0x76, 0x63, 0x77, 0x76, 0x75, 0x70, 0x68, 0x65, 0x68, 0x63, 0x6c, 0x6d, 0x62, 0x73, 0x68,
+ 0x70, 0x68, 0x6b, 0x70, 0x63, 0x70, 0x79, 0x66, 0x68, 0x70, 0x72, 0x77, 0x65, 0x6e, 0x65, 0x74,
+ 0x62, 0x64, 0x74, 0x78, 0x6d, 0x70, 0x79, 0x74, 0x74, 0x6f, 0x6e, 0x6d, 0x78, 0x73, 0x6d, 0x6a,
+ 0x74, 0x6d, 0x75, 0x6f, 0x79, 0x66, 0x6c, 0x6b, 0x62, 0x68, 0x77, 0x67, 0x75, 0x71, 0x6e, 0x6d,
+ 0x6b, 0x63, 0x6a, 0x6a, 0x78, 0x78, 0x70, 0x68, 0x74, 0x72, 0x74, 0x65, 0x6f, 0x77, 0x6c, 0x72,
+ 0x74, 0x6c, 0x66, 0x78, 0x77, 0x78, 0x72, 0x73, 0x6f, 0x78, 0x6d, 0x66, 0x63, 0x61, 0x63, 0x6e,
+ 0x69, 0x73, 0x79, 0x72, 0x6f, 0x72, 0x62, 0x76, 0x75, 0x71, 0x70, 0x61, 0x78, 0x77, 0x76, 0x70,
+ 0x78, 0x69, 0x77, 0x65, 0x75, 0x63, 0x71, 0x6f, 0x6b, 0x6a, 0x74, 0x61, 0x63, 0x61, 0x64, 0x72,
+ 0x64, 0x62, 0x67, 0x72, 0x79, 0x6b, 0x74, 0x70, 0x6e, 0x6b, 0x63, 0x79, 0x66, 0x74, 0x6c, 0x6e,
+ 0x78, 0x6c, 0x70, 0x73, 0x6c, 0x69, 0x66, 0x6d, 0x71, 0x77, 0x79, 0x76, 0x71, 0x6e, 0x67, 0x78,
+ 0x74, 0x6b, 0x6b, 0x64, 0x6f, 0x63, 0x77, 0x61, 0x75, 0x6c, 0x6a, 0x6d, 0x6e, 0x72, 0x66, 0x6a,
+ 0x76, 0x75, 0x71, 0x6f, 0x6d, 0x71, 0x77, 0x75, 0x70, 0x77, 0x76, 0x74, 0x70, 0x71, 0x6b, 0x63,
+ 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x68, 0x61, 0x71, 0x6a, 0x71, 0x75, 0x65, 0x69, 0x6b, 0x63, 0x76,
+ 0x79, 0x6b, 0x76, 0x62, 0x77, 0x76, 0x73, 0x78, 0x6d, 0x76, 0x66, 0x6f, 0x69, 0x64, 0x72, 0x6d,
+ 0x6c, 0x62, 0x71, 0x61, 0x72, 0x6c, 0x6c, 0x70, 0x64, 0x69, 0x76, 0x6e, 0x62, 0x62, 0x69, 0x75,
+ 0x64, 0x73, 0x6b, 0x76, 0x76, 0x67, 0x6b, 0x78, 0x76, 0x70, 0x78, 0x70, 0x68, 0x67, 0x72, 0x6e,
+ 0x6b, 0x68, 0x6f, 0x73, 0x69, 0x6d, 0x71, 0x6b, 0x74, 0x6f, 0x79, 0x73, 0x65, 0x78, 0x6c, 0x67,
+ 0x74, 0x73, 0x6f, 0x62, 0x61, 0x73, 0x61, 0x63, 0x62, 0x74, 0x74, 0x6c, 0x68, 0x70, 0x76, 0x77,
+ 0x6a, 0x6a, 0x6a, 0x6f, 0x77, 0x6f, 0x66, 0x68, 0x78, 0x67, 0x64, 0x73, 0x75, 0x69, 0x6d, 0x69,
+ 0x62, 0x6b, 0x77, 0x63, 0x69, 0x6f, 0x61, 0x72, 0x61, 0x6f, 0x61, 0x77, 0x6a, 0x63, 0x69, 0x70,
+ 0x62, 0x79, 0x61, 0x61, 0x66, 0x71, 0x75, 0x72, 0x75, 0x61, 0x69, 0x6c, 0x78, 0x63, 0x78, 0x6c,
+ 0x74, 0x78, 0x6f, 0x78, 0x6a, 0x76, 0x72, 0x61, 0x69, 0x68, 0x6c, 0x67, 0x65, 0x79, 0x75, 0x6d,
+ 0x76, 0x65, 0x62, 0x67, 0x6b, 0x74, 0x6a, 0x62, 0x74, 0x6a, 0x63, 0x66, 0x62, 0x6c, 0x75, 0x6a,
+ 0x78, 0x61, 0x6f, 0x79, 0x62, 0x64, 0x61, 0x64, 0x77, 0x69, 0x77, 0x77, 0x75, 0x78, 0x77, 0x69,
+ 0x72, 0x69, 0x67, 0x65, 0x73, 0x65, 0x71, 0x6d, 0x6c, 0x65, 0x73, 0x68, 0x6e, 0x68, 0x69, 0x6b,
+ 0x72, 0x74, 0x76, 0x78, 0x62, 0x6a, 0x78, 0x72, 0x77, 0x76, 0x78, 0x68, 0x74, 0x74, 0x62, 0x6a,
+ 0x6c, 0x73, 0x76, 0x73, 0x79, 0x71, 0x72, 0x77, 0x65, 0x6f, 0x6a, 0x6e, 0x63, 0x64, 0x73, 0x65,
+ 0x69, 0x74, 0x72, 0x76, 0x68, 0x79, 0x67, 0x67, 0x78, 0x77, 0x75, 0x78, 0x72, 0x72, 0x68, 0x64,
+ 0x76, 0x73, 0x64, 0x61, 0x64, 0x78, 0x6f, 0x67, 0x6c, 0x6e, 0x61, 0x6a, 0x6b, 0x79, 0x71, 0x6b,
+ 0x62, 0x74, 0x71, 0x6b, 0x76, 0x6e, 0x6e, 0x6e, 0x6e, 0x74, 0x6b, 0x76, 0x61, 0x6e, 0x67, 0x65,
+ 0x73, 0x72, 0x79, 0x66, 0x61, 0x65, 0x65, 0x74, 0x6e, 0x61, 0x66, 0x74, 0x70, 0x69, 0x65, 0x67,
+ 0x67, 0x76, 0x62, 0x61, 0x6c, 0x66, 0x67, 0x76, 0x65, 0x76, 0x6a, 0x62, 0x64, 0x64, 0x6b, 0x6d,
+ 0x70, 0x70, 0x63, 0x74, 0x63, 0x67, 0x78, 0x73, 0x72, 0x6d, 0x63, 0x78, 0x6d, 0x70, 0x6e, 0x75,
+ 0x76, 0x74, 0x79, 0x6b, 0x61, 0x69, 0x6d, 0x73, 0x73, 0x69, 0x64, 0x66, 0x6e, 0x76, 0x67, 0x67,
+ 0x62, 0x77, 0x74, 0x68, 0x74, 0x68, 0x65, 0x64, 0x75, 0x68, 0x70, 0x69, 0x73, 0x6f, 0x67, 0x6b,
+ 0x74, 0x79, 0x68, 0x6c, 0x77, 0x73, 0x68, 0x61, 0x64, 0x68, 0x76, 0x61, 0x62, 0x74, 0x71, 0x6f,
+ 0x79, 0x6b, 0x6d, 0x61, 0x63, 0x65, 0x64, 0x74, 0x67, 0x75, 0x73, 0x6d, 0x65, 0x6a, 0x76, 0x69,
+ 0x75, 0x63, 0x72, 0x6c, 0x69, 0x70, 0x73, 0x70, 0x62, 0x77, 0x61, 0x76, 0x65, 0x69, 0x62, 0x70,
+ 0x6f, 0x6d, 0x70, 0x77, 0x78, 0x64, 0x78, 0x62, 0x77, 0x6e, 0x65, 0x71, 0x63, 0x6d, 0x79, 0x74,
+ 0x72, 0x78, 0x6e, 0x6b, 0x74, 0x62, 0x6f, 0x68, 0x74, 0x71, 0x66, 0x6e, 0x6d, 0x73, 0x76, 0x76,
+ 0x6f, 0x76, 0x73, 0x66, 0x66, 0x62, 0x62, 0x68, 0x66, 0x6e, 0x72, 0x72, 0x73, 0x6a, 0x73, 0x72,
+ 0x69, 0x63, 0x73, 0x79, 0x69, 0x6b, 0x66, 0x6e, 0x6c, 0x71, 0x66, 0x66, 0x74, 0x78, 0x62, 0x6e,
+ 0x75, 0x61, 0x74, 0x62, 0x68, 0x6f, 0x76, 0x6c, 0x75, 0x6c, 0x6a, 0x69, 0x67, 0x77, 0x61, 0x6b,
+ 0x76, 0x76, 0x6f, 0x66, 0x62, 0x6f, 0x66, 0x62, 0x67, 0x62, 0x75, 0x6e, 0x72, 0x6d, 0x71, 0x6d,
+ 0x6f, 0x73, 0x69, 0x6c, 0x70, 0x69, 0x69, 0x6a, 0x6d, 0x6e, 0x68, 0x70, 0x63, 0x63, 0x69, 0x68,
+ 0x68, 0x65, 0x73, 0x6a, 0x67, 0x65, 0x74, 0x79, 0x77, 0x67, 0x6d, 0x76, 0x75, 0x69, 0x71, 0x68,
+ 0x62, 0x6c, 0x72, 0x66, 0x76, 0x65, 0x76, 0x71, 0x78, 0x74, 0x68, 0x62, 0x6c, 0x68, 0x68, 0x66,
+ 0x64, 0x62, 0x6d, 0x6f, 0x77, 0x68, 0x69, 0x78, 0x65, 0x71, 0x68, 0x79, 0x6d, 0x67, 0x77, 0x6b,
+ 0x64, 0x6a, 0x79, 0x6b, 0x68, 0x71, 0x65, 0x68, 0x65, 0x6d, 0x74, 0x63, 0x72, 0x67, 0x66, 0x76,
+ 0x6c, 0x61, 0x78, 0x74, 0x6c, 0x67, 0x62, 0x73, 0x75, 0x73, 0x70, 0x66, 0x69, 0x64, 0x61, 0x72,
+ 0x6b, 0x6e, 0x66, 0x73, 0x6d, 0x71, 0x76, 0x75, 0x6a, 0x61, 0x6a, 0x70, 0x78, 0x61, 0x6c, 0x63,
+ 0x75, 0x6d, 0x79, 0x65, 0x66, 0x61, 0x79, 0x68, 0x74, 0x72, 0x6b, 0x77, 0x74, 0x63, 0x77, 0x67,
+ 0x61, 0x73, 0x6b, 0x79, 0x67, 0x63, 0x73, 0x6d, 0x67, 0x79, 0x65, 0x73, 0x64, 0x72, 0x71, 0x65,
+ 0x68, 0x66, 0x6b, 0x64, 0x73, 0x76, 0x63, 0x70, 0x78, 0x70, 0x76, 0x78, 0x6d, 0x66, 0x79, 0x63,
+ 0x6a, 0x6a, 0x6c, 0x75, 0x77, 0x64, 0x62, 0x67, 0x77, 0x64, 0x74, 0x76, 0x69, 0x6d, 0x72, 0x64,
+ 0x73, 0x75, 0x71, 0x6e, 0x74, 0x66, 0x65, 0x6e, 0x76, 0x6b, 0x6c, 0x70, 0x70, 0x6e, 0x68, 0x78,
+ 0x75, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x76, 0x63, 0x72, 0x70, 0x71, 0x67, 0x67, 0x70, 0x64, 0x66,
+ 0x67, 0x68, 0x79, 0x70, 0x63, 0x67, 0x6f, 0x68, 0x70, 0x6f, 0x68, 0x6f, 0x6b, 0x71, 0x79, 0x62,
+ 0x76, 0x6a, 0x75, 0x6e, 0x77, 0x65, 0x70, 0x6f, 0x72, 0x6f, 0x61, 0x66, 0x65, 0x6b, 0x73, 0x63,
+ 0x61, 0x62, 0x66, 0x65, 0x67, 0x77, 0x77, 0x79, 0x72, 0x72, 0x6b, 0x6e, 0x6d, 0x75, 0x6b, 0x65,
+ 0x68, 0x6e, 0x6a, 0x6e, 0x65, 0x65, 0x61, 0x78, 0x66, 0x68, 0x64, 0x62, 0x79, 0x66, 0x63, 0x72,
+ 0x64, 0x74, 0x71, 0x6a, 0x72, 0x78, 0x76, 0x64, 0x6c, 0x66, 0x6d, 0x75, 0x6b, 0x64, 0x6d, 0x78,
+ 0x68, 0x74, 0x63, 0x69, 0x6c, 0x6d, 0x65, 0x63, 0x63, 0x69, 0x70, 0x76, 0x6d, 0x6e, 0x70, 0x67,
+ 0x6d, 0x64, 0x6d, 0x6f, 0x69, 0x6d, 0x76, 0x75, 0x65, 0x6b, 0x6f, 0x73, 0x78, 0x70, 0x63, 0x6c,
+ 0x73, 0x6e, 0x77, 0x6f, 0x6d, 0x79, 0x70, 0x77, 0x63, 0x77, 0x69, 0x65, 0x71, 0x6d, 0x74, 0x6d,
+ 0x78, 0x78, 0x61, 0x70, 0x65, 0x65, 0x64, 0x65, 0x75, 0x71, 0x67, 0x63, 0x64, 0x74, 0x6c, 0x68,
+ 0x78, 0x65, 0x6d, 0x6f, 0x68, 0x72, 0x65, 0x72, 0x68, 0x6b, 0x65, 0x74, 0x68, 0x78, 0x66, 0x64,
+ 0x69, 0x77, 0x79, 0x72, 0x77, 0x73, 0x71, 0x78, 0x71, 0x76, 0x67, 0x74, 0x79, 0x74, 0x6b, 0x77,
+ 0x63, 0x6d, 0x75, 0x76, 0x6f, 0x67, 0x62, 0x76, 0x67, 0x72, 0x72, 0x72, 0x61, 0x6d, 0x73, 0x74,
+ 0x78, 0x65, 0x64, 0x64, 0x67, 0x77, 0x67, 0x6f, 0x78, 0x74, 0x76, 0x6b, 0x75, 0x69, 0x75, 0x68,
+ 0x62, 0x72, 0x67, 0x73, 0x6f, 0x75, 0x73, 0x79, 0x6a, 0x6b, 0x70, 0x79, 0x76, 0x64, 0x6b, 0x71,
+ 0x64, 0x79, 0x68, 0x76, 0x6a, 0x71, 0x63, 0x65, 0x70, 0x78, 0x61, 0x74, 0x72, 0x65, 0x6f, 0x68,
+ 0x76, 0x75, 0x6a, 0x75, 0x6a, 0x62, 0x65, 0x61, 0x62, 0x6b, 0x74, 0x64, 0x70, 0x6d, 0x6e, 0x74,
+ 0x74, 0x72, 0x6b, 0x72, 0x71, 0x66, 0x72, 0x72, 0x75, 0x6b, 0x6b, 0x6a, 0x63, 0x73, 0x6c, 0x76,
+ 0x77, 0x71, 0x6e, 0x70, 0x65, 0x71, 0x61, 0x72, 0x68, 0x79, 0x79, 0x74, 0x78, 0x62, 0x6a, 0x63,
+ 0x72, 0x78, 0x6d, 0x61, 0x72, 0x73, 0x69, 0x61, 0x61, 0x78, 0x69, 0x63, 0x6b, 0x64, 0x79, 0x75,
+ 0x66, 0x72, 0x63, 0x67, 0x77, 0x6f, 0x77, 0x65, 0x75, 0x6f, 0x6a, 0x72, 0x61, 0x6a, 0x6c, 0x66,
+ 0x72, 0x6e, 0x70, 0x69, 0x68, 0x6a, 0x62, 0x6c, 0x6c, 0x70, 0x71, 0x6e, 0x75, 0x69, 0x76, 0x76,
+ 0x66, 0x6e, 0x77, 0x76, 0x66, 0x69, 0x78, 0x74, 0x68, 0x78, 0x64, 0x79, 0x66, 0x75, 0x74, 0x68,
+ 0x6f, 0x67, 0x79, 0x78, 0x73, 0x6d, 0x78, 0x79, 0x66, 0x63, 0x74, 0x75, 0x6f, 0x62, 0x62, 0x62,
+ 0x68, 0x78, 0x6c, 0x64, 0x76, 0x64, 0x72, 0x6e, 0x65, 0x71, 0x66, 0x69, 0x77, 0x76, 0x62, 0x66,
+ 0x71, 0x63, 0x74, 0x61, 0x78, 0x62, 0x74, 0x72, 0x6b, 0x67, 0x69, 0x78, 0x63, 0x6a, 0x6e, 0x76,
+ 0x62, 0x61, 0x6f, 0x68, 0x6a, 0x67, 0x72, 0x72, 0x79, 0x74, 0x78, 0x67, 0x65, 0x72, 0x6a, 0x68,
+ 0x6d, 0x6f, 0x61, 0x71, 0x65, 0x72, 0x64, 0x61, 0x79, 0x73, 0x6a, 0x64, 0x76, 0x77, 0x6a, 0x6b,
+ 0x71, 0x6c, 0x75, 0x75, 0x6c, 0x78, 0x67, 0x77, 0x76, 0x78, 0x69, 0x74, 0x62, 0x74, 0x75, 0x71,
+ 0x77, 0x77, 0x70, 0x69, 0x6c, 0x61, 0x71, 0x66, 0x6c, 0x78, 0x6a, 0x67, 0x77, 0x68, 0x65, 0x63,
+ 0x77, 0x72, 0x79, 0x6c, 0x6f, 0x64, 0x78, 0x77, 0x68, 0x78, 0x63, 0x68, 0x66, 0x70, 0x78, 0x6f,
+ 0x70, 0x78, 0x66, 0x79, 0x76, 0x63, 0x6a, 0x65, 0x79, 0x66, 0x78, 0x63, 0x6e, 0x6b, 0x75, 0x61,
+ 0x70, 0x6c, 0x62, 0x68, 0x79, 0x63, 0x6b, 0x6d, 0x62, 0x77, 0x68, 0x76, 0x62, 0x6f, 0x68, 0x62,
+ 0x78, 0x78, 0x72, 0x72, 0x6c, 0x74, 0x79, 0x74, 0x6b, 0x68, 0x6e, 0x74, 0x74, 0x70, 0x73, 0x67,
+ 0x73, 0x71, 0x6b, 0x6f, 0x69, 0x6a, 0x71, 0x78, 0x6a, 0x77, 0x70, 0x76, 0x72, 0x64, 0x6a, 0x68,
+ 0x6e, 0x6a, 0x69, 0x79, 0x73, 0x78, 0x71, 0x77, 0x71, 0x68, 0x6d, 0x6b, 0x68, 0x79, 0x63, 0x62,
+ 0x63, 0x6b, 0x67, 0x6f, 0x69, 0x68, 0x6e, 0x6d, 0x67, 0x65, 0x6a, 0x74, 0x67, 0x66, 0x73, 0x62,
+ 0x67, 0x78, 0x6d, 0x69, 0x62, 0x73, 0x67, 0x76, 0x71, 0x73, 0x79, 0x6d, 0x6a, 0x77, 0x65, 0x67,
+ 0x70, 0x6e, 0x64, 0x76, 0x66, 0x6b, 0x70, 0x73, 0x6e, 0x61, 0x69, 0x71, 0x71, 0x62, 0x75, 0x70,
+ 0x72, 0x63, 0x69, 0x68, 0x6e, 0x6d, 0x65, 0x6e, 0x6a, 0x73, 0x62, 0x70, 0x6a, 0x69, 0x67, 0x69,
+ 0x6f, 0x76, 0x6f, 0x70, 0x74, 0x6f, 0x66, 0x6f, 0x64, 0x64, 0x76, 0x66, 0x6b, 0x62, 0x73, 0x6c,
+ 0x6a, 0x6e, 0x73, 0x6b, 0x75, 0x6f, 0x66, 0x6e, 0x78, 0x6b, 0x64, 0x6c, 0x6d, 0x73, 0x6e, 0x69,
+ 0x6a, 0x6f, 0x78, 0x62, 0x6b, 0x78, 0x74, 0x65, 0x67, 0x72, 0x66, 0x66, 0x69, 0x69, 0x63, 0x73,
+ 0x75, 0x79, 0x75, 0x73, 0x77, 0x72, 0x77, 0x77, 0x74, 0x74, 0x68, 0x78, 0x76, 0x71, 0x79, 0x78,
+ 0x61, 0x6c, 0x6b, 0x6e, 0x61, 0x76, 0x6b, 0x6d, 0x76, 0x63, 0x70, 0x79, 0x6e, 0x78, 0x72, 0x61,
+ 0x75, 0x6c, 0x64, 0x69, 0x6f, 0x78, 0x61, 0x71, 0x68, 0x6c, 0x61, 0x77, 0x69, 0x69, 0x74, 0x75,
+ 0x6f, 0x66, 0x75, 0x6e, 0x62, 0x6c, 0x6d, 0x67, 0x79, 0x6f, 0x69, 0x71, 0x76, 0x71, 0x78, 0x67,
+ 0x75, 0x66, 0x6d, 0x69, 0x67, 0x75, 0x6f, 0x6b, 0x63, 0x61, 0x6f, 0x63, 0x70, 0x63, 0x76, 0x75,
+ 0x63, 0x74, 0x70, 0x78, 0x64, 0x77, 0x6e, 0x69, 0x78, 0x6c, 0x63, 0x70, 0x66, 0x6d, 0x68, 0x64,
+ 0x6a, 0x75, 0x64, 0x71, 0x6b, 0x64, 0x67, 0x78, 0x73, 0x63, 0x6e, 0x75, 0x74, 0x77, 0x76, 0x6a,
+ 0x62, 0x74, 0x73, 0x76, 0x67, 0x70, 0x6f, 0x78, 0x6f, 0x63, 0x75, 0x64, 0x61, 0x79, 0x6b, 0x70,
+ 0x73, 0x75, 0x65, 0x64, 0x74, 0x70, 0x61, 0x6f, 0x77, 0x6d, 0x74, 0x6e, 0x78, 0x65, 0x72, 0x75,
+ 0x63, 0x71, 0x69, 0x66, 0x79, 0x65, 0x74, 0x77, 0x6f, 0x6d, 0x66, 0x6a, 0x77, 0x6f, 0x78, 0x76,
+ 0x66, 0x67, 0x65, 0x64, 0x66, 0x61, 0x62, 0x61, 0x71, 0x79, 0x64, 0x6c, 0x79, 0x6b, 0x64, 0x75,
+ 0x79, 0x6c, 0x67, 0x6d, 0x76, 0x67, 0x77, 0x71, 0x69, 0x65, 0x66, 0x71, 0x6b, 0x70, 0x62, 0x64,
+ 0x73, 0x6e, 0x6d, 0x67, 0x67, 0x6f, 0x75, 0x70, 0x65, 0x79, 0x63, 0x62, 0x61, 0x6b, 0x62, 0x6a,
+ 0x66, 0x68, 0x67, 0x75, 0x78, 0x68, 0x6b, 0x77, 0x66, 0x79, 0x67, 0x6c, 0x6f, 0x6a, 0x6a, 0x73,
+ 0x6f, 0x73, 0x63, 0x78, 0x6a, 0x67, 0x74, 0x6d, 0x6f, 0x68, 0x6b, 0x62, 0x70, 0x66, 0x6a, 0x6e,
+ 0x6e, 0x67, 0x6c, 0x65, 0x74, 0x6f, 0x6c, 0x67, 0x68, 0x6d, 0x67, 0x79, 0x61, 0x6a, 0x6e, 0x70,
+ 0x64, 0x6f, 0x68, 0x6b, 0x6a, 0x64, 0x64, 0x6f, 0x6a, 0x64, 0x66, 0x67, 0x73, 0x6a, 0x76, 0x78,
+ 0x6c, 0x67, 0x66, 0x75, 0x6c, 0x79, 0x69, 0x65, 0x71, 0x6c, 0x6b, 0x63, 0x61, 0x6d, 0x61, 0x78,
+ 0x6a, 0x74, 0x6a, 0x78, 0x65, 0x75, 0x62, 0x6b, 0x6b, 0x75, 0x77, 0x78, 0x73, 0x77, 0x64, 0x61,
+ 0x61, 0x6b, 0x71, 0x6f, 0x6f, 0x6c, 0x67, 0x6e, 0x6d, 0x77, 0x64, 0x6d, 0x64, 0x6d, 0x64, 0x76,
+ 0x77, 0x6b, 0x69, 0x76, 0x72, 0x70, 0x77, 0x62, 0x75, 0x69, 0x73, 0x68, 0x65, 0x76, 0x70, 0x76,
+ 0x76, 0x6c, 0x6a, 0x69, 0x77, 0x73, 0x69, 0x66, 0x67, 0x6a, 0x74, 0x6a, 0x70, 0x6f, 0x6c, 0x77,
+ 0x70, 0x6a, 0x76, 0x6a, 0x61, 0x68, 0x78, 0x73, 0x70, 0x72, 0x78, 0x62, 0x76, 0x6b, 0x66, 0x6b,
+ 0x64, 0x71, 0x6d, 0x62, 0x6d, 0x70, 0x79, 0x6a, 0x66, 0x6c, 0x79, 0x76, 0x66, 0x75, 0x69, 0x71,
+ 0x66, 0x79, 0x78, 0x77, 0x75, 0x75, 0x6b, 0x77, 0x6d, 0x67, 0x67, 0x6b, 0x75, 0x75, 0x64, 0x75,
+ 0x62, 0x73, 0x6c, 0x6a, 0x6c, 0x64, 0x69, 0x74, 0x6e, 0x65, 0x63, 0x79, 0x6e, 0x6d, 0x6c, 0x77,
+ 0x6a, 0x69, 0x6d, 0x78, 0x62, 0x74, 0x69, 0x6a, 0x66, 0x75, 0x6e, 0x6f, 0x6e, 0x78, 0x73, 0x6d,
+ 0x74, 0x74, 0x62, 0x6c, 0x68, 0x6b, 0x70, 0x61, 0x6c, 0x6c, 0x63, 0x64, 0x67, 0x65, 0x68, 0x73,
+ 0x74, 0x6d, 0x64, 0x78, 0x68, 0x71, 0x61, 0x66, 0x77, 0x66, 0x6a, 0x66, 0x6f, 0x61, 0x65, 0x6f,
+ 0x6f, 0x74, 0x6a, 0x69, 0x6a, 0x75, 0x68, 0x6c, 0x73, 0x6b, 0x72, 0x75, 0x69, 0x63, 0x63, 0x62,
+ 0x6e, 0x64, 0x76, 0x75, 0x79, 0x71, 0x64, 0x61, 0x6a, 0x65, 0x65, 0x61, 0x61, 0x6e, 0x66, 0x65,
+ 0x74, 0x77, 0x61, 0x62, 0x68, 0x68, 0x67, 0x71, 0x69, 0x68, 0x6b, 0x73, 0x6b, 0x76, 0x68, 0x73,
+ 0x62, 0x63, 0x65, 0x65, 0x73, 0x72, 0x6a, 0x71, 0x6e, 0x61, 0x6a, 0x63, 0x61, 0x62, 0x73, 0x6e,
+ 0x6e, 0x70, 0x67, 0x6f, 0x63, 0x70, 0x64, 0x66, 0x62, 0x61, 0x64, 0x6a, 0x78, 0x70, 0x61, 0x6f,
+ 0x69, 0x79, 0x66, 0x77, 0x74, 0x69, 0x62, 0x6a, 0x73, 0x75, 0x6d, 0x76, 0x74, 0x6d, 0x61, 0x77,
+ 0x67, 0x67, 0x72, 0x6f, 0x79, 0x75, 0x61, 0x72, 0x65, 0x6c, 0x67, 0x69, 0x63, 0x77, 0x71, 0x61,
+ 0x62, 0x63, 0x61, 0x71, 0x6d, 0x6d, 0x64, 0x6a, 0x6a, 0x70, 0x63, 0x71, 0x72, 0x6b, 0x6a, 0x66,
+ 0x62, 0x76, 0x6c, 0x61, 0x72, 0x6f, 0x75, 0x72, 0x75, 0x65, 0x6b, 0x75, 0x73, 0x64, 0x76, 0x6b,
+ 0x6c, 0x6d, 0x73, 0x74, 0x6a, 0x70, 0x61, 0x64, 0x74, 0x71, 0x6c, 0x63, 0x6f, 0x6c, 0x68, 0x75,
+ 0x69, 0x70, 0x73, 0x73, 0x76, 0x78, 0x6b, 0x67, 0x73, 0x74, 0x6f, 0x70, 0x72, 0x61, 0x75, 0x6c,
+ 0x66, 0x66, 0x69, 0x6f, 0x75, 0x6b, 0x6d, 0x64, 0x74, 0x79, 0x6b, 0x66, 0x6b, 0x65, 0x65, 0x6c,
+ 0x6b, 0x61, 0x6c, 0x73, 0x63, 0x66, 0x67, 0x76, 0x6d, 0x74, 0x61, 0x65, 0x6e, 0x62, 0x61, 0x72,
+ 0x63, 0x6d, 0x6c, 0x6a, 0x75, 0x67, 0x6d, 0x62, 0x6e, 0x70, 0x76, 0x6b, 0x64, 0x77, 0x6b, 0x68,
+ 0x75, 0x74, 0x77, 0x61, 0x73, 0x62, 0x6c, 0x79, 0x79, 0x77, 0x6f, 0x66, 0x63, 0x73, 0x67, 0x64,
+ 0x63, 0x62, 0x66, 0x71, 0x73, 0x79, 0x66, 0x73, 0x69, 0x72, 0x71, 0x70, 0x72, 0x67, 0x68, 0x72,
+ 0x6d, 0x6d, 0x79, 0x68, 0x77, 0x6f, 0x68, 0x62, 0x64, 0x70, 0x64, 0x70, 0x6e, 0x72, 0x69, 0x66,
+ 0x6b, 0x72, 0x64, 0x65, 0x63, 0x77, 0x61, 0x61, 0x6b, 0x73, 0x73, 0x77, 0x78, 0x77, 0x6c, 0x64,
+ 0x61, 0x69, 0x62, 0x72, 0x72, 0x6d, 0x6e, 0x76, 0x70, 0x75, 0x72, 0x63, 0x77, 0x69, 0x65, 0x62,
+ 0x73, 0x73, 0x6b, 0x70, 0x70, 0x64, 0x67, 0x71, 0x72, 0x6a, 0x6f, 0x79, 0x75, 0x75, 0x64, 0x75,
+ 0x6f, 0x73, 0x78, 0x67, 0x79, 0x79, 0x78, 0x6e, 0x76, 0x63, 0x6c, 0x73, 0x6a, 0x6c, 0x65, 0x6e,
+ 0x6c, 0x70, 0x69, 0x76, 0x63, 0x6a, 0x6f, 0x6a, 0x6a, 0x6f, 0x6b, 0x66, 0x75, 0x73, 0x66, 0x71,
+ 0x61, 0x75, 0x78, 0x68, 0x6b, 0x67, 0x71, 0x72, 0x6b, 0x65, 0x67, 0x6d, 0x68, 0x74, 0x62, 0x78,
+ 0x65, 0x6f, 0x69, 0x74, 0x71, 0x79, 0x63, 0x67, 0x79, 0x77, 0x6b, 0x69, 0x61, 0x6e, 0x6b, 0x69,
+ 0x70, 0x78, 0x74, 0x75, 0x72, 0x66, 0x6f, 0x64, 0x77, 0x62, 0x79, 0x73, 0x79, 0x73, 0x78, 0x65,
+ 0x62, 0x6b, 0x6f, 0x6b, 0x76, 0x62, 0x6e, 0x68, 0x6a, 0x72, 0x61, 0x61, 0x73, 0x6f, 0x78, 0x61,
+ 0x68, 0x76, 0x63, 0x6c, 0x77, 0x6c, 0x63, 0x6e, 0x6d, 0x6c, 0x71, 0x6f, 0x70, 0x72, 0x63, 0x75,
+ 0x68, 0x61, 0x67, 0x69, 0x6f, 0x6a, 0x6e, 0x61, 0x71, 0x72, 0x6b, 0x66, 0x73, 0x6c, 0x64, 0x79,
+ 0x79, 0x75, 0x79, 0x6d, 0x6e, 0x6c, 0x67, 0x65, 0x6a, 0x76, 0x66, 0x76, 0x77, 0x6c, 0x6b, 0x77,
+ 0x75, 0x64, 0x72, 0x73, 0x77, 0x69, 0x64, 0x74, 0x72, 0x69, 0x66, 0x6b, 0x6f, 0x6d, 0x76, 0x74,
+ 0x64, 0x64, 0x73, 0x74, 0x69, 0x6f, 0x71, 0x79, 0x65, 0x64, 0x71, 0x69, 0x6d, 0x62, 0x6d, 0x70,
+ 0x6c, 0x76, 0x61, 0x72, 0x6e, 0x65, 0x64, 0x77, 0x6a, 0x67, 0x72, 0x78, 0x74, 0x72, 0x69, 0x62,
+ 0x6f, 0x71, 0x71, 0x6a, 0x69, 0x70, 0x73, 0x79, 0x6a, 0x70, 0x70, 0x6d, 0x6f, 0x6f, 0x6d, 0x79,
+ 0x6f, 0x6e, 0x6a, 0x6e, 0x75, 0x67, 0x76, 0x78, 0x65, 0x72, 0x77, 0x64, 0x69, 0x6f, 0x63, 0x69,
+ 0x73, 0x68, 0x72, 0x79, 0x6c, 0x6a, 0x61, 0x77, 0x6b, 0x72, 0x78, 0x63, 0x72, 0x63, 0x79, 0x69,
+ 0x6a, 0x72, 0x6e, 0x79, 0x62, 0x67, 0x6f, 0x72, 0x63, 0x6b, 0x6d, 0x61, 0x76, 0x6e, 0x73, 0x64,
+ 0x62, 0x6a, 0x78, 0x74, 0x72, 0x74, 0x64, 0x79, 0x6b, 0x71, 0x72, 0x68, 0x67, 0x63, 0x63, 0x76,
+ 0x6f, 0x77, 0x76, 0x61, 0x64, 0x65, 0x70, 0x71, 0x72, 0x61, 0x63, 0x63, 0x66, 0x75, 0x73, 0x62,
+ 0x70, 0x70, 0x67, 0x68, 0x70, 0x63, 0x6c, 0x64, 0x6d, 0x76, 0x6d, 0x67, 0x73, 0x72, 0x6a, 0x6a,
+ 0x78, 0x6b, 0x64, 0x78, 0x71, 0x71, 0x66, 0x68, 0x79, 0x6b, 0x6b, 0x69, 0x67, 0x70, 0x6e, 0x66,
+ 0x73, 0x73, 0x78, 0x72, 0x63, 0x76, 0x79, 0x65, 0x63, 0x67, 0x79, 0x64, 0x69, 0x71, 0x75, 0x68,
+ 0x79, 0x6c, 0x6b, 0x61, 0x78, 0x6a, 0x6c, 0x76, 0x63, 0x61, 0x70, 0x79, 0x71, 0x6c, 0x77, 0x6c,
+ 0x6f, 0x66, 0x63, 0x6a, 0x69, 0x64, 0x6f, 0x6f, 0x75, 0x64, 0x76, 0x70, 0x65, 0x63, 0x72, 0x70,
+ 0x62, 0x71, 0x6c, 0x73, 0x74, 0x74, 0x6b, 0x6f, 0x6e, 0x79, 0x72, 0x64, 0x66, 0x6d, 0x79, 0x73,
+ 0x6d, 0x64, 0x67, 0x6b, 0x69, 0x6d, 0x76, 0x65, 0x63, 0x73, 0x6c, 0x6a, 0x66, 0x64, 0x64, 0x70,
+ 0x72, 0x67, 0x69, 0x6c, 0x63, 0x63, 0x71, 0x63, 0x69, 0x73, 0x78, 0x67, 0x77, 0x6a, 0x6d, 0x74,
+ 0x63, 0x63, 0x68, 0x78, 0x61, 0x73, 0x6b, 0x6b, 0x73, 0x69, 0x73, 0x72, 0x73, 0x71, 0x67, 0x68,
+ 0x65, 0x76, 0x77, 0x65, 0x6e, 0x68, 0x72, 0x6a, 0x63, 0x6d, 0x67, 0x6e, 0x65, 0x73, 0x78, 0x73,
+ 0x6a, 0x6d, 0x74, 0x61, 0x61, 0x73, 0x74, 0x62, 0x6a, 0x6a, 0x62, 0x63, 0x65, 0x63, 0x70, 0x69,
+ 0x66, 0x71, 0x70, 0x68, 0x6a, 0x66, 0x67, 0x78, 0x61, 0x63, 0x6c, 0x75, 0x79, 0x6a, 0x63, 0x6b,
+ 0x72, 0x78, 0x65, 0x65, 0x77, 0x77, 0x73, 0x71, 0x68, 0x6f, 0x6b, 0x64, 0x6e, 0x66, 0x64, 0x6e,
+ 0x6b, 0x78, 0x67, 0x73, 0x6c, 0x75, 0x64, 0x6d, 0x66, 0x6b, 0x77, 0x73, 0x74, 0x70, 0x68, 0x66,
+ 0x6c, 0x70, 0x6a, 0x62, 0x6b, 0x78, 0x6e, 0x65, 0x61, 0x6a, 0x68, 0x63, 0x6a, 0x64, 0x75, 0x69,
+ 0x71, 0x77, 0x6e, 0x67, 0x72, 0x70, 0x70, 0x76, 0x70, 0x6e, 0x6b, 0x74, 0x75, 0x76, 0x6b, 0x62,
+ 0x79, 0x63, 0x78, 0x6c, 0x63, 0x6f, 0x69, 0x68, 0x76, 0x6f, 0x62, 0x6a, 0x61, 0x66, 0x6b, 0x72,
+ 0x75, 0x76, 0x67, 0x74, 0x78, 0x73, 0x69, 0x63, 0x63, 0x72, 0x75, 0x61, 0x64, 0x75, 0x69, 0x70,
+ 0x63, 0x68, 0x72, 0x67, 0x69, 0x6f, 0x6d, 0x62, 0x61, 0x68, 0x62, 0x76, 0x6e, 0x6f, 0x62, 0x76,
+ 0x70, 0x6a, 0x67, 0x6b, 0x74, 0x75, 0x75, 0x70, 0x70, 0x6c, 0x76, 0x6d, 0x71, 0x6b, 0x6f, 0x72,
+ 0x70, 0x67, 0x63, 0x79, 0x72, 0x64, 0x67, 0x6f, 0x6d, 0x77, 0x64, 0x61, 0x6f, 0x68, 0x63, 0x61,
+ 0x64, 0x61, 0x67, 0x62, 0x6e, 0x65, 0x6a, 0x6e, 0x77, 0x66, 0x6e, 0x75, 0x79, 0x64, 0x75, 0x79,
+ 0x79, 0x61, 0x63, 0x6f, 0x63, 0x79, 0x77, 0x64, 0x73, 0x74, 0x6a, 0x71, 0x63, 0x61, 0x6b, 0x76,
+ 0x63, 0x6e, 0x73, 0x72, 0x75, 0x62, 0x75, 0x75, 0x67, 0x68, 0x71, 0x6f, 0x6e, 0x61, 0x65, 0x75,
+ 0x6e, 0x6a, 0x76, 0x71, 0x68, 0x74, 0x79, 0x68, 0x65, 0x70, 0x65, 0x6e, 0x6c, 0x62, 0x78, 0x6e,
+ 0x66, 0x64, 0x63, 0x77, 0x68, 0x75, 0x78, 0x6e, 0x71, 0x6d, 0x79, 0x71, 0x68, 0x68, 0x75, 0x73,
+ 0x6c, 0x67, 0x73, 0x75, 0x6d, 0x74, 0x72, 0x6d, 0x77, 0x65, 0x67, 0x67, 0x6f, 0x67, 0x66, 0x75,
+ 0x6d, 0x63, 0x75, 0x6d, 0x6d, 0x74, 0x72, 0x6f, 0x64, 0x74, 0x6f, 0x76, 0x73, 0x75, 0x67, 0x65,
+ 0x73, 0x78, 0x77, 0x67, 0x66, 0x77, 0x66, 0x69, 0x73, 0x75, 0x61, 0x65, 0x6a, 0x61, 0x6b, 0x72,
+ 0x6c, 0x6a, 0x71, 0x71, 0x73, 0x6e, 0x6f, 0x70, 0x65, 0x6e, 0x72, 0x6a, 0x74, 0x6f, 0x72, 0x76,
+ 0x61, 0x69, 0x74, 0x67, 0x72, 0x6f, 0x6d, 0x74, 0x75, 0x74, 0x72, 0x63, 0x65, 0x78, 0x71, 0x6b,
+ 0x79, 0x73, 0x63, 0x6c, 0x6f, 0x75, 0x73, 0x62, 0x70, 0x70, 0x73, 0x65, 0x79, 0x6d, 0x6f, 0x76,
+ 0x64, 0x76, 0x67, 0x69, 0x64, 0x6a, 0x72, 0x69, 0x75, 0x69, 0x79, 0x6e, 0x6d, 0x78, 0x69, 0x62,
+ 0x71, 0x6e, 0x6b, 0x66, 0x75, 0x73, 0x6b, 0x74, 0x6e, 0x61, 0x6c, 0x69, 0x76, 0x63, 0x66, 0x6d,
+ 0x69, 0x61, 0x62, 0x73, 0x74, 0x6a, 0x79, 0x64, 0x6f, 0x67, 0x77, 0x68, 0x6b, 0x63, 0x75, 0x63,
+ 0x74, 0x6d, 0x76, 0x68, 0x72, 0x6e, 0x69, 0x6d, 0x74, 0x6f, 0x79, 0x78, 0x62, 0x76, 0x62, 0x6f,
+ 0x70, 0x6c, 0x6f, 0x62, 0x65, 0x78, 0x63, 0x65, 0x72, 0x77, 0x64, 0x76, 0x65, 0x66, 0x74, 0x63,
+ 0x63, 0x6a, 0x6d, 0x6b, 0x6e, 0x64, 0x73, 0x62, 0x66, 0x6d, 0x62, 0x66, 0x6b, 0x76, 0x63, 0x68,
+ 0x6c, 0x65, 0x69, 0x72, 0x75, 0x62, 0x6e, 0x64, 0x6a, 0x6a, 0x6d, 0x75, 0x74, 0x79, 0x6d, 0x67,
+ 0x65, 0x6e, 0x6b, 0x67, 0x6a, 0x6e, 0x63, 0x63, 0x63, 0x6d, 0x6b, 0x64, 0x6f, 0x6e, 0x6a, 0x75,
+ 0x77, 0x71, 0x74, 0x65, 0x73, 0x71, 0x62, 0x61, 0x76, 0x67, 0x6d, 0x6b, 0x74, 0x78, 0x6c, 0x66,
+ 0x65, 0x74, 0x6f, 0x68, 0x61, 0x73, 0x66, 0x63, 0x79, 0x64, 0x6a, 0x79, 0x74, 0x67, 0x67, 0x6b,
+ 0x78, 0x70, 0x6f, 0x6c, 0x64, 0x68, 0x69, 0x76, 0x6b, 0x77, 0x67, 0x6d, 0x62, 0x72, 0x71, 0x76,
+ 0x69, 0x76, 0x6e, 0x76, 0x71, 0x6c, 0x74, 0x74, 0x71, 0x75, 0x6b, 0x6c, 0x70, 0x6e, 0x69, 0x78,
+ 0x71, 0x6d, 0x62, 0x6d, 0x69, 0x65, 0x6c, 0x77, 0x68, 0x68, 0x6d, 0x6d, 0x62, 0x70, 0x75, 0x74,
+ 0x77, 0x6f, 0x65, 0x69, 0x78, 0x62, 0x78, 0x74, 0x6b, 0x76, 0x72, 0x76, 0x65, 0x78, 0x77, 0x74,
+ 0x63, 0x75, 0x71, 0x66, 0x6e, 0x6c, 0x6d, 0x62, 0x76, 0x6b, 0x77, 0x70, 0x76, 0x79, 0x6b, 0x78,
+ 0x76, 0x6e, 0x6d, 0x66, 0x6d, 0x6b, 0x69, 0x6b, 0x64, 0x79, 0x77, 0x62, 0x65, 0x61, 0x67, 0x6c,
+ 0x66, 0x6f, 0x70, 0x73, 0x73, 0x66, 0x68, 0x71, 0x68, 0x70, 0x65, 0x65, 0x74, 0x69, 0x65, 0x6e,
+ 0x65, 0x68, 0x76, 0x79, 0x66, 0x76, 0x67, 0x66, 0x68, 0x68, 0x6d, 0x73, 0x78, 0x6d, 0x69, 0x79,
+ 0x63, 0x6f, 0x64, 0x64, 0x79, 0x68, 0x71, 0x75, 0x73, 0x6d, 0x63, 0x67, 0x75, 0x64, 0x6b, 0x65,
+ 0x69, 0x76, 0x70, 0x65, 0x6a, 0x63, 0x70, 0x68, 0x61, 0x66, 0x73, 0x73, 0x68, 0x74, 0x71, 0x6c,
+ 0x63, 0x61, 0x62, 0x73, 0x75, 0x73, 0x78, 0x6d, 0x66, 0x74, 0x74, 0x73, 0x63, 0x74, 0x64, 0x75,
+ 0x6b, 0x6c, 0x6a, 0x72, 0x72, 0x61, 0x64, 0x74, 0x71, 0x70, 0x6b, 0x69, 0x70, 0x75, 0x61, 0x6e,
+ 0x71, 0x77, 0x73, 0x6f, 0x72, 0x77, 0x67, 0x67, 0x67, 0x61, 0x73, 0x73, 0x61, 0x6e, 0x63, 0x70,
+ 0x6c, 0x69, 0x68, 0x67, 0x62, 0x76, 0x71, 0x72, 0x72, 0x72, 0x72, 0x65, 0x65, 0x6d, 0x70, 0x6d,
+ 0x73, 0x73, 0x69, 0x68, 0x74, 0x76, 0x71, 0x66, 0x67, 0x67, 0x70, 0x66, 0x69, 0x78, 0x62, 0x78,
+ 0x72, 0x6d, 0x78, 0x76, 0x79, 0x6a, 0x6f, 0x79, 0x76, 0x65, 0x61, 0x61, 0x6f, 0x6c, 0x6c, 0x79,
+ 0x6e, 0x62, 0x63, 0x76, 0x69, 0x69, 0x77, 0x76, 0x6d, 0x66, 0x69, 0x79, 0x79, 0x6f, 0x75, 0x71,
+ 0x64, 0x75, 0x6b, 0x6c, 0x6d, 0x73, 0x6c, 0x71, 0x78, 0x6e, 0x6d, 0x74, 0x6e, 0x71, 0x6b, 0x66,
+ 0x6d, 0x67, 0x69, 0x6b, 0x77, 0x66, 0x69, 0x6d, 0x6d, 0x6d, 0x6a, 0x74, 0x77, 0x6f, 0x63, 0x76,
+ 0x6d, 0x67, 0x65, 0x65, 0x64, 0x76, 0x68, 0x67, 0x68, 0x66, 0x68, 0x6f, 0x61, 0x63, 0x78, 0x6d,
+ 0x76, 0x64, 0x74, 0x6e, 0x6d, 0x69, 0x66, 0x71, 0x62, 0x70, 0x71, 0x66, 0x77, 0x77, 0x63, 0x6b,
+ 0x76, 0x78, 0x72, 0x66, 0x70, 0x76, 0x78, 0x65, 0x72, 0x6f, 0x62, 0x62, 0x67, 0x65, 0x62, 0x76,
+ 0x66, 0x6e, 0x79, 0x69, 0x64, 0x69, 0x68, 0x67, 0x66, 0x70, 0x77, 0x6e, 0x76, 0x64, 0x75, 0x6c,
+ 0x71, 0x68, 0x6a, 0x71, 0x71, 0x70, 0x65, 0x68, 0x61, 0x71, 0x78, 0x75, 0x6c, 0x76, 0x68, 0x6d,
+ 0x64, 0x65, 0x65, 0x79, 0x6f, 0x78, 0x66, 0x77, 0x70, 0x66, 0x66, 0x6f, 0x74, 0x73, 0x71, 0x6e,
+ 0x65, 0x65, 0x75, 0x68, 0x6e, 0x64, 0x6e, 0x67, 0x72, 0x76, 0x71, 0x6b, 0x65, 0x71, 0x6d, 0x6f,
+ 0x63, 0x73, 0x63, 0x6e, 0x66, 0x73, 0x68, 0x6d, 0x6b, 0x72, 0x67, 0x6d, 0x6f, 0x6a, 0x77, 0x70,
+ 0x64, 0x77, 0x62, 0x79, 0x6e, 0x65, 0x6c, 0x62, 0x66, 0x77, 0x6e, 0x73, 0x77, 0x64, 0x74, 0x74,
+ 0x77, 0x64, 0x65, 0x66, 0x6e, 0x73, 0x78, 0x6d, 0x79, 0x76, 0x73, 0x71, 0x76, 0x61, 0x66, 0x6e,
+ 0x76, 0x77, 0x6f, 0x73, 0x6c, 0x6e, 0x6d, 0x78, 0x73, 0x71, 0x64, 0x66, 0x78, 0x65, 0x73, 0x63,
+ 0x69, 0x73, 0x69, 0x62, 0x64, 0x63, 0x74, 0x6f, 0x6e, 0x69, 0x79, 0x66, 0x75, 0x71, 0x70, 0x79,
+ 0x6c, 0x6d, 0x67, 0x72, 0x67, 0x65, 0x72, 0x75, 0x78, 0x68, 0x73, 0x6f, 0x76, 0x62, 0x62, 0x78,
+ 0x68, 0x78, 0x79, 0x72, 0x65, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x67, 0x79, 0x6d, 0x78, 0x73, 0x78,
+ 0x68, 0x67, 0x77, 0x71, 0x76, 0x78, 0x74, 0x61, 0x6c, 0x76, 0x78, 0x65, 0x78, 0x6a, 0x76, 0x64,
+ 0x6f, 0x6d, 0x71, 0x6f, 0x6a, 0x6f, 0x77, 0x79, 0x69, 0x6e, 0x66, 0x71, 0x6e, 0x62, 0x66, 0x6f,
+ 0x61, 0x68, 0x73, 0x72, 0x69, 0x68, 0x68, 0x73, 0x63, 0x67, 0x69, 0x74, 0x6c, 0x76, 0x79, 0x66,
+ 0x6f, 0x75, 0x6c, 0x69, 0x73, 0x72, 0x74, 0x6c, 0x6f, 0x62, 0x78, 0x68, 0x6b, 0x6d, 0x77, 0x63,
+ 0x6d, 0x67, 0x64, 0x77, 0x6f, 0x64, 0x72, 0x77, 0x6b, 0x6f, 0x6c, 0x6c, 0x6e, 0x6c, 0x63, 0x77,
+ 0x73, 0x77, 0x6c, 0x71, 0x73, 0x73, 0x6f, 0x61, 0x64, 0x65, 0x69, 0x78, 0x63, 0x66, 0x6f, 0x66,
+ 0x72, 0x77, 0x69, 0x64, 0x6c, 0x72, 0x6b, 0x6d, 0x72, 0x6c, 0x6d, 0x64, 0x71, 0x6b, 0x69, 0x61,
+ 0x75, 0x66, 0x65, 0x62, 0x6e, 0x6f, 0x68, 0x75, 0x6a, 0x6d, 0x71, 0x74, 0x78, 0x71, 0x75, 0x6a,
+ 0x75, 0x6f, 0x68, 0x6d, 0x68, 0x68, 0x78, 0x61, 0x75, 0x71, 0x75, 0x62, 0x79, 0x6c, 0x75, 0x73,
+ 0x72, 0x6b, 0x66, 0x62, 0x71, 0x71, 0x79, 0x69, 0x6f, 0x6d, 0x69, 0x6d, 0x79, 0x6a, 0x66, 0x69,
+ 0x6a, 0x66, 0x79, 0x61, 0x63, 0x65, 0x69, 0x67, 0x61, 0x73, 0x71, 0x65, 0x72, 0x73, 0x6b, 0x6b,
+ 0x70, 0x6f, 0x78, 0x69, 0x78, 0x6c, 0x78, 0x61, 0x66, 0x6b, 0x69, 0x69, 0x64, 0x6c, 0x74, 0x70,
+ 0x78, 0x75, 0x6f, 0x73, 0x79, 0x6f, 0x6a, 0x6e, 0x74, 0x70, 0x70, 0x66, 0x6c, 0x64, 0x79, 0x67,
+ 0x78, 0x6e, 0x6c, 0x78, 0x71, 0x6c, 0x74, 0x62, 0x6e, 0x77, 0x6c, 0x66, 0x71, 0x6f, 0x76, 0x79,
+ 0x70, 0x71, 0x76, 0x67, 0x73, 0x76, 0x76, 0x77, 0x64, 0x61, 0x6c, 0x68, 0x61, 0x61, 0x78, 0x74,
+ 0x72, 0x78, 0x6d, 0x6d, 0x64, 0x6d, 0x72, 0x65, 0x6a, 0x6a, 0x79, 0x63, 0x6c, 0x62, 0x66, 0x75,
+ 0x79, 0x72, 0x68, 0x71, 0x71, 0x64, 0x6f, 0x79, 0x63, 0x72, 0x6c, 0x6b, 0x64, 0x63, 0x75, 0x68,
+ 0x71, 0x72, 0x77, 0x72, 0x6c, 0x6b, 0x75, 0x6d, 0x6e, 0x6c, 0x67, 0x75, 0x66, 0x65, 0x68, 0x6d,
+ 0x79, 0x70, 0x6c, 0x73, 0x79, 0x6b, 0x6c, 0x6d, 0x72, 0x6f, 0x70, 0x76, 0x78, 0x6d, 0x6f, 0x61,
+ 0x68, 0x76, 0x6f, 0x6a, 0x66, 0x77, 0x72, 0x6e, 0x62, 0x71, 0x6b, 0x71, 0x63, 0x65, 0x71, 0x65,
+ 0x6d, 0x70, 0x65, 0x6e, 0x6d, 0x77, 0x61, 0x66, 0x76, 0x6e, 0x79, 0x71, 0x76, 0x63, 0x70, 0x76,
+ 0x75, 0x6b, 0x74, 0x65, 0x6f, 0x67, 0x6e, 0x79, 0x6f, 0x61, 0x72, 0x6e, 0x6a, 0x61, 0x6d, 0x63,
+ 0x66, 0x6c, 0x63, 0x6a, 0x6e, 0x62, 0x62, 0x77, 0x63, 0x75, 0x71, 0x78, 0x65, 0x79, 0x6e, 0x79,
+ 0x6a, 0x65, 0x78, 0x73, 0x73, 0x63, 0x69, 0x74, 0x79, 0x66, 0x73, 0x71, 0x74, 0x63, 0x62, 0x76,
+ 0x73, 0x6a, 0x67, 0x78, 0x64, 0x77, 0x69, 0x78, 0x6b, 0x62, 0x79, 0x78, 0x67, 0x67, 0x76, 0x72,
+ 0x75, 0x70, 0x66, 0x68, 0x76, 0x6e, 0x6e, 0x6e, 0x75, 0x64, 0x77, 0x6a, 0x65, 0x6a, 0x70, 0x77,
+ 0x65, 0x79, 0x67, 0x6e, 0x61, 0x79, 0x6b, 0x77, 0x77, 0x78, 0x6e, 0x63, 0x72, 0x68, 0x79, 0x67,
+ 0x76, 0x77, 0x68, 0x6c, 0x78, 0x72, 0x65, 0x73, 0x71, 0x6f, 0x78, 0x78, 0x77, 0x71, 0x70, 0x64,
+ 0x6a, 0x63, 0x76, 0x6f, 0x68, 0x70, 0x72, 0x6f, 0x63, 0x72, 0x67, 0x67, 0x76, 0x77, 0x65, 0x72,
+ 0x6d, 0x75, 0x74, 0x63, 0x6b, 0x73, 0x6b, 0x73, 0x6c, 0x6b, 0x6e, 0x70, 0x75, 0x76, 0x67, 0x6e,
+ 0x68, 0x66, 0x75, 0x70, 0x78, 0x69, 0x69, 0x6c, 0x6b, 0x66, 0x77, 0x67, 0x78, 0x66, 0x69, 0x6b,
+ 0x72, 0x61, 0x64, 0x64, 0x78, 0x62, 0x68, 0x72, 0x76, 0x73, 0x78, 0x71, 0x65, 0x6f, 0x63, 0x79,
+ 0x70, 0x74, 0x77, 0x78, 0x75, 0x79, 0x75, 0x66, 0x65, 0x72, 0x70, 0x78, 0x6c, 0x79, 0x70, 0x76,
+ 0x61, 0x73, 0x64, 0x79, 0x76, 0x74, 0x79, 0x77, 0x66, 0x65, 0x61, 0x63, 0x76, 0x75, 0x64, 0x75,
+ 0x6e, 0x70, 0x63, 0x6b, 0x6c, 0x79, 0x62, 0x67, 0x67, 0x74, 0x73, 0x72, 0x68, 0x6f, 0x67, 0x62,
+ 0x6e, 0x6b, 0x68, 0x62, 0x64, 0x62, 0x70, 0x71, 0x61, 0x74, 0x6d, 0x6c, 0x78, 0x6b, 0x6b, 0x75,
+ 0x68, 0x69, 0x6e, 0x6d, 0x66, 0x76, 0x6a, 0x63, 0x68, 0x72, 0x6d, 0x63, 0x6d, 0x6b, 0x6c, 0x78,
+ 0x73, 0x72, 0x6b, 0x6c, 0x64, 0x73, 0x71, 0x6e, 0x76, 0x73, 0x69, 0x6c, 0x77, 0x72, 0x73, 0x72,
+ 0x74, 0x78, 0x61, 0x65, 0x6b, 0x6e, 0x6f, 0x62, 0x75, 0x6c, 0x69, 0x63, 0x77, 0x6d, 0x78, 0x76,
+ 0x72, 0x65, 0x75, 0x6d, 0x6f, 0x70, 0x63, 0x6b, 0x62, 0x64, 0x75, 0x6d, 0x62, 0x64, 0x77, 0x6d,
+ 0x6b, 0x70, 0x70, 0x72, 0x77, 0x64, 0x65, 0x74, 0x62, 0x69, 0x63, 0x65, 0x69, 0x71, 0x71, 0x6f,
+ 0x62, 0x67, 0x67, 0x6a, 0x77, 0x79, 0x75, 0x69, 0x77, 0x6e, 0x62, 0x77, 0x61, 0x67, 0x62, 0x74,
+ 0x79, 0x76, 0x78, 0x71, 0x78, 0x70, 0x65, 0x61, 0x65, 0x76, 0x76, 0x70, 0x69, 0x73, 0x6d, 0x75,
+ 0x6a, 0x76, 0x6a, 0x72, 0x6a, 0x73, 0x74, 0x6a, 0x76, 0x74, 0x6d, 0x70, 0x79, 0x74, 0x6b, 0x76,
+ 0x64, 0x68, 0x62, 0x76, 0x6c, 0x79, 0x77, 0x71, 0x64, 0x75, 0x6a, 0x77, 0x76, 0x6e, 0x6d, 0x74,
+ 0x65, 0x71, 0x76, 0x6a, 0x67, 0x62, 0x70, 0x71, 0x64, 0x6b, 0x71, 0x63, 0x6b, 0x72, 0x71, 0x70,
+ 0x68, 0x64, 0x72, 0x70, 0x70, 0x6d, 0x6e, 0x61, 0x77, 0x64, 0x78, 0x67, 0x70, 0x6b, 0x71, 0x63,
+ 0x66, 0x74, 0x64, 0x75, 0x72, 0x72, 0x67, 0x66, 0x75, 0x74, 0x63, 0x62, 0x71, 0x76, 0x6f, 0x69,
+ 0x65, 0x6e, 0x6f, 0x62, 0x70, 0x72, 0x74, 0x6a, 0x6c, 0x72, 0x78, 0x72, 0x66, 0x78, 0x6d, 0x75,
+ 0x79, 0x63, 0x67, 0x6c, 0x74, 0x66, 0x79, 0x71, 0x67, 0x68, 0x75, 0x67, 0x78, 0x78, 0x71, 0x6a,
+ 0x74, 0x6f, 0x62, 0x76, 0x63, 0x63, 0x65, 0x76, 0x64, 0x68, 0x71, 0x6f, 0x65, 0x61, 0x6b, 0x72,
+ 0x67, 0x64, 0x66, 0x63, 0x6e, 0x68, 0x74, 0x66, 0x6d, 0x67, 0x70, 0x6d, 0x6c, 0x65, 0x68, 0x6f,
+ 0x62, 0x65, 0x63, 0x68, 0x62, 0x66, 0x76, 0x61, 0x67, 0x61, 0x62, 0x6f, 0x74, 0x71, 0x78, 0x65,
+ 0x68, 0x6a, 0x6c, 0x66, 0x79, 0x61, 0x6b, 0x6c, 0x6c, 0x6d, 0x64, 0x62, 0x72, 0x67, 0x77, 0x75,
+ 0x6d, 0x69, 0x70, 0x6b, 0x62, 0x6b, 0x70, 0x62, 0x75, 0x79, 0x6e, 0x78, 0x6f, 0x75, 0x6a, 0x66,
+ 0x73, 0x68, 0x71, 0x6b, 0x77, 0x76, 0x66, 0x72, 0x6e, 0x6c, 0x72, 0x6d, 0x68, 0x6e, 0x6b, 0x6c,
+ 0x72, 0x78, 0x70, 0x62, 0x6b, 0x67, 0x65, 0x6b, 0x6a, 0x61, 0x68, 0x6a, 0x61, 0x67, 0x63, 0x73,
+ 0x62, 0x73, 0x6d, 0x62, 0x70, 0x72, 0x77, 0x67, 0x6c, 0x68, 0x67, 0x6b, 0x74, 0x79, 0x65, 0x79,
+ 0x78, 0x72, 0x6a, 0x75, 0x76, 0x6b, 0x66, 0x6e, 0x6e, 0x70, 0x71, 0x61, 0x6b, 0x64, 0x61, 0x77,
+ 0x72, 0x63, 0x63, 0x61, 0x64, 0x68, 0x65, 0x75, 0x6c, 0x61, 0x70, 0x75, 0x76, 0x68, 0x69, 0x64,
+ 0x71, 0x68, 0x73, 0x78, 0x71, 0x71, 0x6a, 0x6d, 0x79, 0x74, 0x71, 0x63, 0x78, 0x74, 0x6e, 0x6f,
+ 0x70, 0x6f, 0x74, 0x73, 0x70, 0x6e, 0x74, 0x73, 0x76, 0x75, 0x69, 0x76, 0x63, 0x6b, 0x69, 0x67,
+ 0x70, 0x78, 0x63, 0x67, 0x67, 0x6f, 0x6f, 0x74, 0x65, 0x72, 0x68, 0x69, 0x71, 0x6f, 0x6d, 0x6c,
+ 0x72, 0x68, 0x76, 0x78, 0x76, 0x73, 0x6d, 0x65, 0x6a, 0x69, 0x76, 0x66, 0x62, 0x63, 0x71, 0x73,
+ 0x73, 0x62, 0x6d, 0x66, 0x67, 0x68, 0x61, 0x75, 0x67, 0x6b, 0x70, 0x6e, 0x6d, 0x66, 0x61, 0x74,
+ 0x76, 0x67, 0x6a, 0x6d, 0x69, 0x6b, 0x75, 0x68, 0x6a, 0x65, 0x6f, 0x6f, 0x64, 0x70, 0x76, 0x67,
+ 0x69, 0x63, 0x70, 0x63, 0x64, 0x75, 0x6c, 0x67, 0x6d, 0x76, 0x67, 0x6a, 0x64, 0x62, 0x6b, 0x65,
+ 0x72, 0x71, 0x6c, 0x68, 0x6d, 0x78, 0x66, 0x73, 0x75, 0x6f, 0x72, 0x63, 0x77, 0x6b, 0x77, 0x71,
+ 0x68, 0x62, 0x6f, 0x6c, 0x77, 0x6c, 0x69, 0x6b, 0x6b, 0x6b, 0x76, 0x66, 0x76, 0x72, 0x6f, 0x67,
+ 0x63, 0x70, 0x66, 0x78, 0x6b, 0x76, 0x6a, 0x74, 0x62, 0x67, 0x61, 0x6c, 0x75, 0x63, 0x68, 0x76,
+ 0x6c, 0x6e, 0x75, 0x76, 0x63, 0x6a, 0x6f, 0x64, 0x75, 0x65, 0x61, 0x6d, 0x72, 0x63, 0x62, 0x69,
+ 0x76, 0x66, 0x6e, 0x6a, 0x6c, 0x63, 0x68, 0x68, 0x71, 0x69, 0x6b, 0x62, 0x66, 0x69, 0x63, 0x6f,
+ 0x6d, 0x6f, 0x70, 0x76, 0x77, 0x63, 0x69, 0x62, 0x76, 0x70, 0x6e, 0x76, 0x78, 0x69, 0x71, 0x73,
+ 0x75, 0x6b, 0x6d, 0x78, 0x61, 0x64, 0x78, 0x64, 0x6e, 0x76, 0x72, 0x66, 0x64, 0x72, 0x66, 0x67,
+ 0x6e, 0x66, 0x67, 0x78, 0x69, 0x6d, 0x62, 0x6e, 0x72, 0x63, 0x71, 0x6c, 0x67, 0x62, 0x6a, 0x63,
+ 0x70, 0x71, 0x6c, 0x71, 0x66, 0x66, 0x67, 0x76, 0x69, 0x6a, 0x65, 0x72, 0x64, 0x6e, 0x67, 0x6c,
+ 0x68, 0x6b, 0x6c, 0x65, 0x6b, 0x70, 0x6b, 0x6a, 0x66, 0x73, 0x70, 0x77, 0x71, 0x61, 0x78, 0x74,
+ 0x63, 0x6c, 0x75, 0x6a, 0x62, 0x77, 0x69, 0x6d, 0x6d, 0x6e, 0x65, 0x75, 0x71, 0x76, 0x66, 0x77,
+ 0x78, 0x78, 0x72, 0x72, 0x68, 0x79, 0x66, 0x68, 0x65, 0x6e, 0x6d, 0x68, 0x6b, 0x65, 0x70, 0x66,
+ 0x72, 0x65, 0x79, 0x65, 0x79, 0x73, 0x78, 0x62, 0x62, 0x69, 0x75, 0x63, 0x74, 0x6c, 0x72, 0x74,
+ 0x73, 0x71, 0x64, 0x6c, 0x75, 0x77, 0x6a, 0x6a, 0x68, 0x64, 0x68, 0x76, 0x71, 0x6d, 0x74, 0x6f,
+ 0x66, 0x75, 0x70, 0x71, 0x62, 0x64, 0x64, 0x74, 0x6b, 0x72, 0x70, 0x71, 0x6d, 0x79, 0x68, 0x61,
+ 0x64, 0x6c, 0x61, 0x75, 0x72, 0x79, 0x77, 0x69, 0x6d, 0x67, 0x64, 0x78, 0x6c, 0x6b, 0x63, 0x78,
+ 0x6a, 0x71, 0x63, 0x67, 0x6d, 0x68, 0x64, 0x61, 0x73, 0x6a, 0x73, 0x68, 0x74, 0x69, 0x6a, 0x72,
+ 0x64, 0x6c, 0x77, 0x70, 0x72, 0x6b, 0x68, 0x6c, 0x6d, 0x6d, 0x63, 0x63, 0x6c, 0x6f, 0x78, 0x63,
+ 0x61, 0x79, 0x6a, 0x64, 0x6a, 0x70, 0x72, 0x71, 0x70, 0x74, 0x66, 0x73, 0x6d, 0x79, 0x6d, 0x6a,
+ 0x62, 0x71, 0x6f, 0x79, 0x66, 0x69, 0x65, 0x78, 0x61, 0x73, 0x61, 0x6b, 0x78, 0x70, 0x78, 0x64,
+ 0x6e, 0x6d, 0x79, 0x6b, 0x77, 0x75, 0x78, 0x78, 0x68, 0x61, 0x6f, 0x6d, 0x78, 0x79, 0x6a, 0x77,
+ 0x6b, 0x77, 0x69, 0x64, 0x74, 0x6e, 0x63, 0x76, 0x67, 0x65, 0x73, 0x61, 0x72, 0x73, 0x78, 0x66,
+ 0x6b, 0x6c, 0x64, 0x6e, 0x78, 0x73, 0x73, 0x68, 0x6e, 0x73, 0x76, 0x62, 0x6e, 0x6d, 0x66, 0x6d,
+ 0x64, 0x79, 0x62, 0x6a, 0x6e, 0x72, 0x71, 0x64, 0x70, 0x79, 0x6a, 0x62, 0x64, 0x65, 0x61, 0x73,
+ 0x74, 0x6e, 0x63, 0x6b, 0x6a, 0x6f, 0x66, 0x68, 0x6e, 0x67, 0x68, 0x77, 0x73, 0x68, 0x61, 0x63,
+ 0x74, 0x73, 0x62, 0x6a, 0x69, 0x75, 0x72, 0x6d, 0x77, 0x6c, 0x6e, 0x72, 0x6f, 0x70, 0x6d, 0x76,
+ 0x71, 0x6e, 0x76, 0x66, 0x6e, 0x6e, 0x75, 0x70, 0x69, 0x72, 0x66, 0x61, 0x6f, 0x6d, 0x66, 0x75,
+ 0x65, 0x68, 0x69, 0x64, 0x78, 0x6c, 0x6f, 0x6b, 0x66, 0x72, 0x6a, 0x77, 0x64, 0x72, 0x72, 0x61,
+ 0x6f, 0x67, 0x61, 0x64, 0x6c, 0x76, 0x70, 0x6c, 0x66, 0x76, 0x79, 0x77, 0x6b, 0x6e, 0x6e, 0x67,
+ 0x76, 0x76, 0x75, 0x6a, 0x64, 0x70, 0x71, 0x63, 0x67, 0x6a, 0x6e, 0x63, 0x73, 0x76, 0x6e, 0x78,
+ 0x62, 0x6e, 0x63, 0x6f, 0x74, 0x65, 0x64, 0x6f, 0x6f, 0x62, 0x79, 0x75, 0x72, 0x69, 0x61, 0x78,
+ 0x72, 0x75, 0x6e, 0x6b, 0x64, 0x67, 0x6e, 0x72, 0x6f, 0x73, 0x68, 0x79, 0x68, 0x61, 0x6b, 0x6d,
+ 0x72, 0x64, 0x6f, 0x71, 0x72, 0x64, 0x6b, 0x71, 0x6c, 0x62, 0x68, 0x6b, 0x6a, 0x6d, 0x64, 0x6b,
+ 0x71, 0x76, 0x6c, 0x63, 0x72, 0x61, 0x73, 0x62, 0x6f, 0x6f, 0x6c, 0x69, 0x75, 0x64, 0x63, 0x70,
+ 0x6b, 0x6e, 0x6f, 0x6f, 0x6c, 0x65, 0x6f, 0x78, 0x6a, 0x74, 0x64, 0x79, 0x62, 0x64, 0x69, 0x79,
+ 0x63, 0x6c, 0x77, 0x6f, 0x74, 0x6a, 0x68, 0x61, 0x6a, 0x68, 0x74, 0x6a, 0x70, 0x68, 0x72, 0x68,
+ 0x63, 0x6f, 0x71, 0x77, 0x78, 0x76, 0x6a, 0x6e, 0x76, 0x73, 0x67, 0x69, 0x6f, 0x73, 0x68, 0x71,
+ 0x69, 0x6c, 0x73, 0x73, 0x79, 0x67, 0x79, 0x61, 0x64, 0x70, 0x61, 0x71, 0x68, 0x73, 0x6b, 0x65,
+ 0x64, 0x75, 0x76, 0x69, 0x73, 0x6e, 0x67, 0x77, 0x6a, 0x72, 0x62, 0x74, 0x76, 0x63, 0x6f, 0x73,
+ 0x64, 0x64, 0x62, 0x69, 0x72, 0x6b, 0x71, 0x63, 0x73, 0x73, 0x64, 0x69, 0x6e, 0x73, 0x64, 0x6d,
+ 0x63, 0x70, 0x67, 0x76, 0x61, 0x79, 0x6d, 0x73, 0x74, 0x6c, 0x79, 0x6d, 0x74, 0x68, 0x65, 0x77,
+ 0x63, 0x66, 0x77, 0x6e, 0x64, 0x69, 0x6a, 0x62, 0x65, 0x6e, 0x79, 0x6b, 0x6c, 0x6e, 0x79, 0x6e,
+ 0x65, 0x76, 0x6b, 0x6e, 0x62, 0x6a, 0x72, 0x73, 0x72, 0x76, 0x69, 0x71, 0x62, 0x76, 0x63, 0x69,
+ 0x6c, 0x63, 0x64, 0x70, 0x6d, 0x6b, 0x63, 0x6c, 0x61, 0x77, 0x71, 0x74, 0x61, 0x71, 0x6c, 0x65,
+ 0x65, 0x70, 0x70, 0x67, 0x6c, 0x77, 0x6e, 0x74, 0x64, 0x79, 0x62, 0x64, 0x63, 0x77, 0x79, 0x73,
+ 0x73, 0x66, 0x68, 0x70, 0x65, 0x61, 0x67, 0x72, 0x61, 0x6a, 0x68, 0x66, 0x62, 0x72, 0x6d, 0x6e,
+ 0x65, 0x78, 0x6b, 0x6b, 0x78, 0x74, 0x72, 0x6b, 0x71, 0x67, 0x6a, 0x69, 0x71, 0x65, 0x6d, 0x76,
+ 0x79, 0x69, 0x61, 0x65, 0x70, 0x68, 0x6e, 0x64, 0x6a, 0x77, 0x69, 0x6c, 0x6b, 0x6b, 0x63, 0x62,
+ 0x6d, 0x63, 0x76, 0x77, 0x67, 0x6e, 0x6b, 0x6d, 0x67, 0x71, 0x69, 0x78, 0x6e, 0x6d, 0x6b, 0x78,
+ 0x65, 0x6c, 0x61, 0x77, 0x6f, 0x64, 0x79, 0x62, 0x63, 0x61, 0x6b, 0x66, 0x72, 0x79, 0x69, 0x65,
+ 0x76, 0x77, 0x65, 0x6d, 0x6c, 0x69, 0x62, 0x6f, 0x79, 0x77, 0x6d, 0x78, 0x6d, 0x62, 0x6f, 0x78,
+ 0x68, 0x77, 0x6d, 0x70, 0x72, 0x6d, 0x63, 0x71, 0x79, 0x65, 0x61, 0x66, 0x78, 0x66, 0x63, 0x6e,
+ 0x69, 0x68, 0x73, 0x74, 0x6d, 0x6f, 0x76, 0x71, 0x64, 0x75, 0x77, 0x63, 0x6c, 0x63, 0x70, 0x61,
+ 0x6d, 0x77, 0x76, 0x78, 0x72, 0x71, 0x6f, 0x61, 0x74, 0x62, 0x64, 0x68, 0x64, 0x6a, 0x67, 0x75,
+ 0x71, 0x79, 0x66, 0x70, 0x64, 0x72, 0x62, 0x6b, 0x65, 0x65, 0x66, 0x79, 0x6d, 0x75, 0x70, 0x6a,
+ 0x61, 0x75, 0x67, 0x65, 0x70, 0x6c, 0x65, 0x61, 0x77, 0x72, 0x67, 0x73, 0x74, 0x68, 0x77, 0x70,
+ 0x65, 0x6c, 0x74, 0x75, 0x73, 0x74, 0x6d, 0x61, 0x64, 0x6f, 0x79, 0x78, 0x79, 0x62, 0x75, 0x6b,
+ 0x66, 0x79, 0x78, 0x75, 0x73, 0x71, 0x75, 0x76, 0x6a, 0x63, 0x73, 0x68, 0x65, 0x71, 0x6e, 0x62,
+ 0x75, 0x77, 0x74, 0x68, 0x70, 0x77, 0x6c, 0x66, 0x6d, 0x79, 0x65, 0x64, 0x66, 0x69, 0x70, 0x66,
+ 0x74, 0x66, 0x6c, 0x77, 0x71, 0x6f, 0x6d, 0x78, 0x6d, 0x6d, 0x70, 0x73, 0x77, 0x75, 0x79, 0x65,
+ 0x63, 0x6e, 0x76, 0x66, 0x63, 0x6d, 0x71, 0x65, 0x67, 0x74, 0x73, 0x6b, 0x6f, 0x70, 0x62, 0x74,
+ 0x78, 0x68, 0x6d, 0x67, 0x72, 0x66, 0x75, 0x77, 0x69, 0x68, 0x62, 0x70, 0x74, 0x75, 0x6f, 0x63,
+ 0x68, 0x70, 0x79, 0x6e, 0x77, 0x61, 0x68, 0x6d, 0x6d, 0x68, 0x74, 0x78, 0x76, 0x78, 0x76, 0x74,
+ 0x65, 0x75, 0x6f, 0x70, 0x65, 0x79, 0x6f, 0x73, 0x63, 0x61, 0x6b, 0x76, 0x6c, 0x74, 0x75, 0x75,
+ 0x68, 0x6f, 0x65, 0x6d, 0x74, 0x68, 0x63, 0x71, 0x67, 0x64, 0x72, 0x63, 0x67, 0x6b, 0x68, 0x74,
+ 0x69, 0x65, 0x6c, 0x73, 0x74, 0x63, 0x75, 0x69, 0x71, 0x65, 0x75, 0x67, 0x6b, 0x79, 0x79, 0x6f,
+ 0x75, 0x63, 0x6d, 0x66, 0x75, 0x6f, 0x6d, 0x6d, 0x73, 0x63, 0x6d, 0x73, 0x70, 0x71, 0x76, 0x74,
+ 0x71, 0x72, 0x77, 0x71, 0x70, 0x76, 0x65, 0x79, 0x72, 0x6d, 0x6d, 0x76, 0x78, 0x65, 0x77, 0x72,
+ 0x78, 0x72, 0x6c, 0x75, 0x76, 0x76, 0x72, 0x72, 0x77, 0x6b, 0x68, 0x6a, 0x73, 0x6c, 0x6f, 0x6d,
+ 0x79, 0x68, 0x75, 0x62, 0x6c, 0x6d, 0x69, 0x68, 0x78, 0x62, 0x65, 0x79, 0x72, 0x78, 0x6d, 0x6e,
+ 0x68, 0x69, 0x6d, 0x74, 0x63, 0x65, 0x66, 0x70, 0x77, 0x62, 0x6b, 0x64, 0x75, 0x6e, 0x72, 0x6a,
+ 0x76, 0x6e, 0x75, 0x6f, 0x6e, 0x61, 0x71, 0x72, 0x74, 0x76, 0x61, 0x61, 0x74, 0x66, 0x66, 0x61,
+ 0x78, 0x72, 0x70, 0x62, 0x65, 0x73, 0x79, 0x64, 0x75, 0x67, 0x75, 0x66, 0x71, 0x73, 0x73, 0x70,
+ 0x69, 0x73, 0x6d, 0x6c, 0x70, 0x64, 0x69, 0x6f, 0x74, 0x78, 0x70, 0x72, 0x78, 0x71, 0x73, 0x63,
+ 0x62, 0x70, 0x76, 0x6c, 0x6b, 0x62, 0x61, 0x76, 0x73, 0x67, 0x77, 0x75, 0x6a, 0x72, 0x65, 0x64,
+ 0x62, 0x79, 0x6e, 0x76, 0x6d, 0x65, 0x6c, 0x62, 0x67, 0x63, 0x62, 0x77, 0x72, 0x78, 0x65, 0x77,
+ 0x71, 0x72, 0x64, 0x75, 0x69, 0x77, 0x6c, 0x6f, 0x77, 0x61, 0x69, 0x6d, 0x69, 0x76, 0x63, 0x72,
+ 0x79, 0x79, 0x6a, 0x6f, 0x63, 0x72, 0x74, 0x62, 0x78, 0x77, 0x6d, 0x75, 0x62, 0x6c, 0x6b, 0x6f,
+ 0x74, 0x63, 0x73, 0x70, 0x74, 0x6d, 0x6a, 0x70, 0x6f, 0x6a, 0x6f, 0x66, 0x61, 0x66, 0x71, 0x75,
+ 0x74, 0x73, 0x6a, 0x6c, 0x6d, 0x79, 0x68, 0x71, 0x78, 0x75, 0x6d, 0x6f, 0x70, 0x74, 0x70, 0x76,
+ 0x71, 0x6a, 0x61, 0x62, 0x67, 0x69, 0x6d, 0x62, 0x74, 0x6b, 0x67, 0x73, 0x66, 0x6f, 0x6d, 0x6c,
+ 0x79, 0x69, 0x69, 0x6d, 0x67, 0x6d, 0x71, 0x66, 0x73, 0x66, 0x6e, 0x65, 0x6c, 0x75, 0x79, 0x68,
+ 0x6a, 0x74, 0x79, 0x70, 0x72, 0x73, 0x6b, 0x65, 0x6d, 0x6a, 0x6e, 0x6a, 0x76, 0x6d, 0x6d, 0x6a,
+ 0x6b, 0x68, 0x65, 0x6a, 0x68, 0x71, 0x6b, 0x63, 0x73, 0x6a, 0x78, 0x68, 0x6f, 0x76, 0x70, 0x75,
+ 0x73, 0x72, 0x78, 0x6f, 0x74, 0x6c, 0x6f, 0x6d, 0x6c, 0x74, 0x6b, 0x73, 0x71, 0x66, 0x6a, 0x64,
+ 0x6a, 0x68, 0x6b, 0x67, 0x72, 0x78, 0x76, 0x68, 0x70, 0x69, 0x6e, 0x75, 0x6d, 0x76, 0x6b, 0x62,
+ 0x6b, 0x61, 0x78, 0x6f, 0x77, 0x72, 0x75, 0x76, 0x6e, 0x69, 0x6f, 0x6c, 0x6d, 0x68, 0x6e, 0x6b,
+ 0x6d, 0x75, 0x6c, 0x75, 0x72, 0x75, 0x78, 0x6b, 0x73, 0x71, 0x6b, 0x76, 0x63, 0x63, 0x6f, 0x6b,
+ 0x75, 0x6a, 0x6a, 0x74, 0x6c, 0x78, 0x73, 0x74, 0x75, 0x71, 0x6e, 0x76, 0x77, 0x66, 0x6e, 0x61,
+ 0x66, 0x62, 0x63, 0x65, 0x72, 0x63, 0x67, 0x77, 0x62, 0x6a, 0x71, 0x75, 0x6a, 0x6f, 0x63, 0x71,
+ 0x77, 0x72, 0x74, 0x6e, 0x71, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x66, 0x77, 0x61, 0x68, 0x74, 0x70,
+ 0x64, 0x76, 0x62, 0x65, 0x64, 0x68, 0x63, 0x68, 0x77, 0x75, 0x6b, 0x79, 0x77, 0x70, 0x61, 0x63,
+ 0x6b, 0x64, 0x6a, 0x79, 0x72, 0x6f, 0x73, 0x62, 0x65, 0x69, 0x71, 0x6d, 0x66, 0x61, 0x73, 0x6e,
+ 0x6b, 0x63, 0x65, 0x77, 0x73, 0x72, 0x77, 0x62, 0x63, 0x75, 0x67, 0x62, 0x65, 0x62, 0x6b, 0x76,
+ 0x61, 0x71, 0x78, 0x76, 0x61, 0x6d, 0x70, 0x65, 0x6f, 0x68, 0x77, 0x64, 0x61, 0x6a, 0x6f, 0x67,
+ 0x6b, 0x67, 0x68, 0x75, 0x71, 0x66, 0x73, 0x69, 0x75, 0x62, 0x6a, 0x6c, 0x72, 0x71, 0x68, 0x69,
+ 0x61, 0x6a, 0x6a, 0x79, 0x71, 0x6c, 0x6d, 0x76, 0x6f, 0x68, 0x74, 0x77, 0x6a, 0x63, 0x62, 0x6d,
+ 0x6a, 0x6f, 0x74, 0x61, 0x6f, 0x6b, 0x69, 0x64, 0x72, 0x6b, 0x67, 0x75, 0x76, 0x77, 0x78, 0x61,
+ 0x6c, 0x70, 0x69, 0x6f, 0x69, 0x62, 0x61, 0x67, 0x72, 0x62, 0x67, 0x65, 0x67, 0x6e, 0x62, 0x6c,
+ 0x66, 0x69, 0x69, 0x6b, 0x6a, 0x6d, 0x6a, 0x70, 0x6d, 0x73, 0x6f, 0x77, 0x63, 0x75, 0x6c, 0x69,
+ 0x65, 0x6c, 0x79, 0x6d, 0x79, 0x71, 0x6d, 0x61, 0x74, 0x6b, 0x78, 0x75, 0x6b, 0x6c, 0x73, 0x63,
+ 0x6d, 0x6c, 0x6c, 0x78, 0x74, 0x6f, 0x6f, 0x67, 0x71, 0x67, 0x78, 0x71, 0x64, 0x65, 0x63, 0x6b,
+ 0x69, 0x74, 0x6d, 0x74, 0x6b, 0x6b, 0x78, 0x61, 0x74, 0x66, 0x75, 0x77, 0x66, 0x68, 0x69, 0x6a,
+ 0x66, 0x65, 0x79, 0x78, 0x65, 0x6a, 0x6e, 0x71, 0x76, 0x79, 0x69, 0x71, 0x64, 0x76, 0x77, 0x67,
+ 0x61, 0x6d, 0x6c, 0x71, 0x66, 0x6c, 0x67, 0x64, 0x65, 0x62, 0x66, 0x77, 0x6c, 0x65, 0x74, 0x72,
+ 0x62, 0x64, 0x61, 0x6c, 0x6e, 0x6c, 0x75, 0x6e, 0x64, 0x74, 0x78, 0x63, 0x62, 0x62, 0x68, 0x73,
+ 0x61, 0x67, 0x79, 0x6f, 0x67, 0x6c, 0x66, 0x62, 0x75, 0x72, 0x76, 0x6f, 0x62, 0x61, 0x66, 0x73,
+ 0x74, 0x75, 0x6c, 0x62, 0x69, 0x6a, 0x68, 0x6b, 0x75, 0x77, 0x6d, 0x72, 0x6e, 0x62, 0x77, 0x6a,
+ 0x64, 0x72, 0x74, 0x67, 0x76, 0x79, 0x6c, 0x65, 0x79, 0x78, 0x79, 0x68, 0x76, 0x75, 0x78, 0x69,
+ 0x61, 0x78, 0x6e, 0x77, 0x64, 0x69, 0x61, 0x69, 0x74, 0x70, 0x66, 0x78, 0x75, 0x64, 0x6a, 0x68,
+ 0x6f, 0x67, 0x6c, 0x6e, 0x74, 0x70, 0x75, 0x74, 0x75, 0x6a, 0x61, 0x6d, 0x78, 0x76, 0x63, 0x67,
+ 0x6f, 0x66, 0x6b, 0x71, 0x6d, 0x74, 0x6c, 0x66, 0x63, 0x62, 0x73, 0x6c, 0x74, 0x63, 0x6c, 0x6d,
+ 0x72, 0x73, 0x6c, 0x77, 0x75, 0x6a, 0x77, 0x66, 0x75, 0x75, 0x73, 0x71, 0x68, 0x6d, 0x6d, 0x61,
+ 0x6d, 0x6b, 0x63, 0x63, 0x6c, 0x6d, 0x77, 0x74, 0x6c, 0x69, 0x65, 0x68, 0x6d, 0x67, 0x63, 0x70,
+ 0x63, 0x61, 0x6a, 0x71, 0x72, 0x67, 0x6f, 0x78, 0x73, 0x62, 0x70, 0x6f, 0x6f, 0x65, 0x6f, 0x6f,
+ 0x73, 0x75, 0x6d, 0x6a, 0x75, 0x70, 0x71, 0x61, 0x61, 0x72, 0x6a, 0x66, 0x72, 0x72, 0x62, 0x61,
+ 0x67, 0x63, 0x73, 0x79, 0x6e, 0x71, 0x62, 0x6a, 0x63, 0x66, 0x76, 0x6c, 0x64, 0x64, 0x68, 0x77,
+ 0x66, 0x6b, 0x6c, 0x6e, 0x6b, 0x61, 0x73, 0x6e, 0x73, 0x63, 0x78, 0x72, 0x62, 0x75, 0x6b, 0x63,
+ 0x6e, 0x6b, 0x6e, 0x65, 0x6a, 0x62, 0x62, 0x77, 0x6a, 0x77, 0x6c, 0x62, 0x70, 0x67, 0x68, 0x72,
+ 0x73, 0x6d, 0x71, 0x63, 0x69, 0x74, 0x68, 0x6c, 0x78, 0x69, 0x78, 0x65, 0x75, 0x6d, 0x63, 0x6b,
+ 0x64, 0x75, 0x65, 0x76, 0x6c, 0x72, 0x6e, 0x63, 0x79, 0x6d, 0x63, 0x6d, 0x67, 0x70, 0x66, 0x6b,
+ 0x76, 0x6a, 0x6b, 0x62, 0x75, 0x69, 0x76, 0x6c, 0x62, 0x76, 0x66, 0x72, 0x61, 0x6a, 0x67, 0x71,
+ 0x64, 0x74, 0x74, 0x70, 0x6c, 0x63, 0x73, 0x6e, 0x71, 0x74, 0x6b, 0x6a, 0x64, 0x62, 0x69, 0x68,
+ 0x65, 0x71, 0x6a, 0x6b, 0x62, 0x73, 0x6c, 0x67, 0x6d, 0x68, 0x78, 0x6d, 0x73, 0x69, 0x65, 0x74,
+ 0x6c, 0x61, 0x66, 0x74, 0x65, 0x65, 0x62, 0x6a, 0x6a, 0x6d, 0x71, 0x61, 0x69, 0x74, 0x70, 0x6c,
+ 0x68, 0x65, 0x65, 0x6b, 0x69, 0x79, 0x72, 0x71, 0x71, 0x74, 0x6f, 0x6e, 0x6c, 0x62, 0x74, 0x63,
+ 0x64, 0x69, 0x66, 0x70, 0x76, 0x6d, 0x6b, 0x72, 0x6c, 0x6a, 0x77, 0x61, 0x74, 0x72, 0x62, 0x65,
+ 0x68, 0x6e, 0x75, 0x62, 0x71, 0x6e, 0x63, 0x63, 0x64, 0x64, 0x62, 0x69, 0x74, 0x6c, 0x6e, 0x70,
+ 0x78, 0x69, 0x63, 0x65, 0x6a, 0x6f, 0x78, 0x6c, 0x6a, 0x65, 0x6e, 0x6c, 0x6c, 0x65, 0x67, 0x6e,
+ 0x68, 0x62, 0x63, 0x65, 0x6c, 0x62, 0x6f, 0x6e, 0x6d, 0x61, 0x6c, 0x61, 0x76, 0x69, 0x71, 0x77,
+ 0x74, 0x6a, 0x74, 0x74, 0x6d, 0x6a, 0x6a, 0x77, 0x70, 0x76, 0x70, 0x75, 0x6c, 0x6f, 0x6c, 0x75,
+ 0x69, 0x61, 0x6c, 0x75, 0x62, 0x6f, 0x68, 0x67, 0x69, 0x67, 0x61, 0x6d, 0x72, 0x72, 0x63, 0x6b,
+ 0x63, 0x70, 0x6c, 0x70, 0x69, 0x71, 0x6e, 0x76, 0x6f, 0x77, 0x74, 0x64, 0x76, 0x67, 0x6f, 0x77,
+ 0x76, 0x77, 0x6e, 0x61, 0x72, 0x76, 0x74, 0x65, 0x63, 0x63, 0x73, 0x65, 0x6d, 0x67, 0x69, 0x65,
+ 0x74, 0x6e, 0x77, 0x63, 0x78, 0x77, 0x63, 0x76, 0x68, 0x65, 0x76, 0x73, 0x64, 0x6a, 0x68, 0x6a,
+ 0x65, 0x72, 0x75, 0x70, 0x61, 0x6b, 0x71, 0x68, 0x74, 0x6f, 0x78, 0x6d, 0x74, 0x62, 0x61, 0x71,
+ 0x64, 0x6b, 0x6a, 0x76, 0x79, 0x61, 0x6c, 0x77, 0x6a, 0x68, 0x78, 0x62, 0x70, 0x78, 0x78, 0x65,
+ 0x64, 0x74, 0x6f, 0x71, 0x69, 0x75, 0x70, 0x78, 0x71, 0x78, 0x66, 0x65, 0x74, 0x6d, 0x6a, 0x75,
+ 0x66, 0x76, 0x70, 0x75, 0x69, 0x6c, 0x73, 0x76, 0x79, 0x63, 0x62, 0x68, 0x74, 0x6e, 0x69, 0x72,
+ 0x76, 0x6b, 0x62, 0x6d, 0x67, 0x65, 0x79, 0x70, 0x79, 0x6d, 0x65, 0x70, 0x79, 0x6a, 0x62, 0x61,
+ 0x76, 0x64, 0x65, 0x75, 0x69, 0x6a, 0x76, 0x70, 0x6c, 0x69, 0x74, 0x6b, 0x65, 0x6d, 0x69, 0x75,
+ 0x77, 0x68, 0x6b, 0x6f, 0x72, 0x66, 0x68, 0x75, 0x73, 0x63, 0x77, 0x69, 0x69, 0x6b, 0x68, 0x67,
+ 0x66, 0x6f, 0x71, 0x69, 0x63, 0x77, 0x74, 0x61, 0x6b, 0x70, 0x62, 0x6b, 0x6e, 0x6f, 0x77, 0x6e,
+ 0x78, 0x6e, 0x66, 0x69, 0x78, 0x6f, 0x64, 0x77, 0x61, 0x6c, 0x70, 0x75, 0x62, 0x70, 0x6d, 0x76,
+ 0x79, 0x72, 0x66, 0x64, 0x76, 0x75, 0x6f, 0x6f, 0x71, 0x73, 0x75, 0x73, 0x77, 0x79, 0x71, 0x63,
+ 0x76, 0x78, 0x73, 0x6e, 0x6d, 0x70, 0x69, 0x78, 0x75, 0x6c, 0x6e, 0x76, 0x76, 0x79, 0x6d, 0x68,
+ 0x71, 0x76, 0x63, 0x77, 0x67, 0x6d, 0x6a, 0x64, 0x6f, 0x73, 0x64, 0x70, 0x68, 0x67, 0x6f, 0x61,
+ 0x67, 0x6d, 0x76, 0x6f, 0x6c, 0x74, 0x72, 0x74, 0x76, 0x62, 0x70, 0x61, 0x64, 0x6f, 0x73, 0x63,
+ 0x64, 0x74, 0x68, 0x68, 0x76, 0x79, 0x6a, 0x66, 0x78, 0x6a, 0x63, 0x68, 0x6a, 0x61, 0x73, 0x65,
+ 0x65, 0x6c, 0x6f, 0x61, 0x61, 0x71, 0x6b, 0x72, 0x6e, 0x71, 0x62, 0x6f, 0x66, 0x77, 0x72, 0x66,
+ 0x63, 0x75, 0x73, 0x65, 0x62, 0x66, 0x74, 0x69, 0x71, 0x66, 0x72, 0x67, 0x61, 0x6c, 0x68, 0x64,
+ 0x63, 0x66, 0x73, 0x78, 0x74, 0x63, 0x64, 0x71, 0x71, 0x75, 0x77, 0x6a, 0x71, 0x6a, 0x6e, 0x6b,
+ 0x77, 0x64, 0x66, 0x68, 0x69, 0x77, 0x77, 0x71, 0x62, 0x73, 0x64, 0x70, 0x71, 0x65, 0x63, 0x71,
+ 0x71, 0x70, 0x6e, 0x6a, 0x75, 0x6f, 0x70, 0x63, 0x67, 0x65, 0x72, 0x76, 0x77, 0x79, 0x72, 0x6b,
+ 0x68, 0x77, 0x75, 0x68, 0x6a, 0x66, 0x6f, 0x6c, 0x79, 0x76, 0x66, 0x65, 0x62, 0x72, 0x75, 0x62,
+ 0x69, 0x65, 0x77, 0x72, 0x77, 0x74, 0x69, 0x6f, 0x76, 0x6e, 0x76, 0x6a, 0x68, 0x76, 0x6f, 0x75,
+ 0x77, 0x6f, 0x75, 0x6c, 0x67, 0x74, 0x6d, 0x75, 0x79, 0x62, 0x6c, 0x6c, 0x66, 0x6e, 0x76, 0x62,
+ 0x6a, 0x71, 0x65, 0x66, 0x77, 0x62, 0x6b, 0x71, 0x61, 0x66, 0x71, 0x76, 0x6b, 0x6c, 0x72, 0x6c,
+ 0x63, 0x63, 0x68, 0x68, 0x6a, 0x69, 0x6a, 0x63, 0x75, 0x71, 0x68, 0x6b, 0x6d, 0x61, 0x76, 0x76,
+ 0x6e, 0x6a, 0x6a, 0x67, 0x68, 0x6f, 0x65, 0x67, 0x72, 0x6f, 0x70, 0x70, 0x6b, 0x63, 0x64, 0x75,
+ 0x63, 0x67, 0x65, 0x71, 0x69, 0x64, 0x66, 0x65, 0x6c, 0x72, 0x72, 0x66, 0x72, 0x74, 0x66, 0x75,
+ 0x65, 0x77, 0x6d, 0x69, 0x68, 0x6e, 0x72, 0x77, 0x6a, 0x61, 0x72, 0x67, 0x73, 0x76, 0x6b, 0x67,
+ 0x67, 0x6c, 0x75, 0x67, 0x61, 0x73, 0x78, 0x68, 0x72, 0x6b, 0x64, 0x69, 0x6d, 0x6c, 0x6e, 0x68,
+ 0x78, 0x76, 0x70, 0x67, 0x6c, 0x6f, 0x68, 0x63, 0x72, 0x76, 0x61, 0x69, 0x68, 0x71, 0x69, 0x6f,
+ 0x67, 0x6b, 0x61, 0x69, 0x6d, 0x73, 0x66, 0x6c, 0x64, 0x76, 0x71, 0x78, 0x79, 0x74, 0x62, 0x71,
+ 0x6b, 0x62, 0x71, 0x63, 0x69, 0x74, 0x61, 0x6a, 0x63, 0x75, 0x6f, 0x6a, 0x6b, 0x64, 0x61, 0x77,
+ 0x66, 0x61, 0x6c, 0x74, 0x71, 0x75, 0x61, 0x69, 0x6e, 0x69, 0x6a, 0x6b, 0x61, 0x65, 0x78, 0x68,
+ 0x62, 0x78, 0x75, 0x6c, 0x6b, 0x68, 0x79, 0x77, 0x6a, 0x76, 0x6f, 0x61, 0x63, 0x71, 0x65, 0x66,
+ 0x6e, 0x67, 0x73, 0x74, 0x62, 0x73, 0x73, 0x76, 0x78, 0x6b, 0x63, 0x78, 0x72, 0x6c, 0x6a, 0x67,
+ 0x69, 0x63, 0x78, 0x64, 0x63, 0x6d, 0x6a, 0x6f, 0x6d, 0x6d, 0x63, 0x74, 0x70, 0x64, 0x6d, 0x74,
+ 0x72, 0x77, 0x6a, 0x75, 0x73, 0x6c, 0x63, 0x68, 0x71, 0x71, 0x62, 0x75, 0x6d, 0x79, 0x70, 0x6f,
+ 0x77, 0x72, 0x62, 0x6a, 0x69, 0x6e, 0x76, 0x77, 0x61, 0x73, 0x71, 0x79, 0x78, 0x70, 0x78, 0x6e,
+ 0x64, 0x79, 0x78, 0x6e, 0x6e, 0x65, 0x78, 0x73, 0x68, 0x76, 0x79, 0x75, 0x65, 0x73, 0x65, 0x6c,
+ 0x72, 0x6d, 0x64, 0x76, 0x73, 0x76, 0x6e, 0x67, 0x73, 0x74, 0x6a, 0x62, 0x79, 0x79, 0x70, 0x64,
+ 0x71, 0x73, 0x6d, 0x64, 0x6a, 0x62, 0x66, 0x67, 0x70, 0x67, 0x74, 0x66, 0x6e, 0x78, 0x78, 0x6e,
+ 0x64, 0x6e, 0x64, 0x6f, 0x64, 0x6f, 0x73, 0x6d, 0x68, 0x79, 0x68, 0x67, 0x6f, 0x61, 0x66, 0x68,
+ 0x78, 0x73, 0x62, 0x6b, 0x61, 0x63, 0x6b, 0x6a, 0x76, 0x62, 0x66, 0x78, 0x79, 0x61, 0x76, 0x6c,
+ 0x64, 0x71, 0x72, 0x75, 0x66, 0x69, 0x6c, 0x64, 0x62, 0x6b, 0x63, 0x6d, 0x73, 0x72, 0x61, 0x77,
+ 0x69, 0x71, 0x73, 0x78, 0x63, 0x74, 0x74, 0x66, 0x71, 0x73, 0x63, 0x6c, 0x70, 0x66, 0x65, 0x68,
+ 0x6a, 0x72, 0x62, 0x70, 0x75, 0x65, 0x66, 0x70, 0x73, 0x72, 0x62, 0x69, 0x70, 0x6f, 0x78, 0x78,
+ 0x66, 0x78, 0x71, 0x63, 0x73, 0x6a, 0x70, 0x70, 0x6a, 0x74, 0x67, 0x6e, 0x79, 0x67, 0x75, 0x72,
+ 0x69, 0x74, 0x61, 0x73, 0x74, 0x68, 0x75, 0x70, 0x6f, 0x72, 0x63, 0x75, 0x70, 0x73, 0x64, 0x69,
+ 0x72, 0x75, 0x74, 0x63, 0x71, 0x75, 0x6f, 0x64, 0x74, 0x66, 0x63, 0x65, 0x68, 0x76, 0x74, 0x77,
+ 0x75, 0x69, 0x75, 0x63, 0x65, 0x61, 0x79, 0x77, 0x67, 0x77, 0x69, 0x6f, 0x6e, 0x74, 0x75, 0x75,
+ 0x74, 0x6c, 0x62, 0x6f, 0x6b, 0x78, 0x71, 0x66, 0x78, 0x62, 0x64, 0x64, 0x6b, 0x69, 0x77, 0x64,
+ 0x66, 0x70, 0x74, 0x73, 0x6a, 0x68, 0x74, 0x75, 0x65, 0x6e, 0x73, 0x70, 0x78, 0x6f, 0x74, 0x69,
+ 0x72, 0x76, 0x70, 0x72, 0x69, 0x68, 0x70, 0x6d, 0x66, 0x67, 0x74, 0x73, 0x6d, 0x66, 0x68, 0x73,
+ 0x6b, 0x6c, 0x66, 0x62, 0x66, 0x76, 0x66, 0x6a, 0x70, 0x76, 0x67, 0x72, 0x65, 0x78, 0x61, 0x6f,
+ 0x78, 0x68, 0x62, 0x61, 0x73, 0x64, 0x65, 0x6b, 0x73, 0x6a, 0x64, 0x61, 0x68, 0x67, 0x64, 0x78,
+ 0x6f, 0x6f, 0x61, 0x65, 0x78, 0x68, 0x64, 0x6f, 0x71, 0x72, 0x69, 0x66, 0x64, 0x71, 0x73, 0x61,
+ 0x78, 0x74, 0x69, 0x77, 0x6a, 0x74, 0x74, 0x79, 0x6f, 0x70, 0x78, 0x6b, 0x76, 0x6d, 0x79, 0x72,
+ 0x6e, 0x65, 0x74, 0x62, 0x6f, 0x67, 0x67, 0x6e, 0x6b, 0x74, 0x77, 0x73, 0x69, 0x75, 0x61, 0x64,
+ 0x6a, 0x79, 0x70, 0x70, 0x62, 0x78, 0x6c, 0x71, 0x61, 0x72, 0x6c, 0x65, 0x79, 0x61, 0x77, 0x6a,
+ 0x79, 0x6b, 0x6f, 0x66, 0x66, 0x79, 0x6a, 0x79, 0x6c, 0x62, 0x67, 0x6f, 0x64, 0x6a, 0x67, 0x61,
+ 0x72, 0x6a, 0x64, 0x72, 0x72, 0x77, 0x79, 0x61, 0x78, 0x77, 0x71, 0x70, 0x76, 0x67, 0x78, 0x78,
+ 0x63, 0x6b, 0x78, 0x64, 0x77, 0x6c, 0x73, 0x66, 0x6c, 0x63, 0x74, 0x6e, 0x6c, 0x65, 0x63, 0x74,
+ 0x65, 0x68, 0x64, 0x6c, 0x65, 0x6d, 0x64, 0x76, 0x71, 0x72, 0x6b, 0x66, 0x74, 0x71, 0x6d, 0x6b,
+ 0x74, 0x69, 0x75, 0x73, 0x67, 0x65, 0x6c, 0x73, 0x63, 0x6c, 0x72, 0x66, 0x78, 0x69, 0x67, 0x73,
+ 0x75, 0x6f, 0x6a, 0x75, 0x6b, 0x6e, 0x6f, 0x64, 0x6a, 0x66, 0x73, 0x69, 0x63, 0x66, 0x6e, 0x6b,
+ 0x68, 0x71, 0x69, 0x61, 0x6a, 0x63, 0x78, 0x6e, 0x6c, 0x6a, 0x78, 0x66, 0x71, 0x69, 0x6e, 0x62,
+ 0x78, 0x75, 0x72, 0x64, 0x73, 0x6f, 0x65, 0x78, 0x70, 0x73, 0x72, 0x72, 0x70, 0x78, 0x76, 0x6a,
+ 0x6b, 0x63, 0x75, 0x78, 0x76, 0x67, 0x6c, 0x66, 0x61, 0x6a, 0x71, 0x71, 0x6d, 0x6a, 0x6a, 0x62,
+ 0x65, 0x76, 0x73, 0x71, 0x77, 0x69, 0x63, 0x65, 0x74, 0x64, 0x79, 0x66, 0x6d, 0x66, 0x74, 0x68,
+ 0x62, 0x6d, 0x6d, 0x64, 0x78, 0x74, 0x76, 0x75, 0x78, 0x79, 0x66, 0x71, 0x6d, 0x6d, 0x6f, 0x78,
+ 0x61, 0x63, 0x64, 0x74, 0x71, 0x63, 0x65, 0x63, 0x68, 0x64, 0x78, 0x6b, 0x79, 0x6d, 0x71, 0x6f,
+ 0x61, 0x79, 0x72, 0x6e, 0x64, 0x71, 0x63, 0x63, 0x6f, 0x71, 0x73, 0x6d, 0x74, 0x71, 0x63, 0x64,
+ 0x74, 0x6f, 0x71, 0x78, 0x78, 0x6b, 0x68, 0x74, 0x6f, 0x63, 0x62, 0x6f, 0x78, 0x6e, 0x6f, 0x76,
+ 0x6a, 0x6b, 0x6c, 0x68, 0x68, 0x62, 0x62, 0x6a, 0x71, 0x67, 0x6f, 0x63, 0x66, 0x72, 0x63, 0x63,
+ 0x6f, 0x74, 0x6d, 0x72, 0x77, 0x71, 0x72, 0x6d, 0x65, 0x6c, 0x6b, 0x76, 0x70, 0x74, 0x72, 0x61,
+ 0x74, 0x72, 0x71, 0x76, 0x79, 0x6a, 0x77, 0x72, 0x65, 0x61, 0x6b, 0x74, 0x6d, 0x61, 0x67, 0x75,
+ 0x73, 0x73, 0x69, 0x67, 0x77, 0x66, 0x73, 0x72, 0x63, 0x6b, 0x66, 0x76, 0x6a, 0x69, 0x67, 0x61,
+ 0x74, 0x64, 0x66, 0x78, 0x64, 0x72, 0x62, 0x6a, 0x64, 0x65, 0x61, 0x62, 0x63, 0x72, 0x61, 0x66,
+ 0x68, 0x72, 0x78, 0x77, 0x73, 0x6a, 0x72, 0x66, 0x79, 0x6d, 0x70, 0x77, 0x78, 0x64, 0x66, 0x63,
+ 0x73, 0x68, 0x61, 0x75, 0x73, 0x77, 0x63, 0x79, 0x79, 0x78, 0x66, 0x6f, 0x6d, 0x79, 0x67, 0x6f,
+ 0x74, 0x61, 0x66, 0x78, 0x6f, 0x77, 0x6b, 0x77, 0x66, 0x6a, 0x63, 0x61, 0x75, 0x69, 0x77, 0x62,
+ 0x72, 0x77, 0x74, 0x66, 0x67, 0x6b, 0x63, 0x61, 0x71, 0x77, 0x63, 0x75, 0x67, 0x72, 0x67, 0x67,
+ 0x76, 0x73, 0x61, 0x72, 0x72, 0x61, 0x75, 0x6a, 0x68, 0x67, 0x75, 0x64, 0x79, 0x78, 0x6a, 0x61,
+ 0x73, 0x6c, 0x6f, 0x62, 0x64, 0x73, 0x69, 0x79, 0x71, 0x79, 0x65, 0x72, 0x79, 0x6c, 0x6c, 0x6f,
+ 0x71, 0x70, 0x66, 0x72, 0x77, 0x73, 0x62, 0x66, 0x66, 0x78, 0x79, 0x72, 0x71, 0x73, 0x64, 0x68,
+ 0x6b, 0x6e, 0x6f, 0x6e, 0x67, 0x73, 0x6c, 0x6a, 0x74, 0x74, 0x6a, 0x6a, 0x74, 0x64, 0x70, 0x62,
+ 0x79, 0x75, 0x61, 0x6d, 0x6b, 0x70, 0x70, 0x6c, 0x6a, 0x73, 0x62, 0x72, 0x72, 0x76, 0x72, 0x66,
+ 0x78, 0x66, 0x78, 0x78, 0x75, 0x70, 0x6d, 0x6a, 0x70, 0x66, 0x65, 0x6f, 0x72, 0x76, 0x6c, 0x67,
+ 0x76, 0x69, 0x73, 0x74, 0x76, 0x63, 0x76, 0x75, 0x70, 0x76, 0x69, 0x6f, 0x65, 0x70, 0x72, 0x6a,
+ 0x6b, 0x68, 0x6d, 0x6a, 0x71, 0x79, 0x72, 0x6a, 0x74, 0x68, 0x64, 0x72, 0x76, 0x65, 0x6e, 0x63,
+ 0x66, 0x6a, 0x61, 0x66, 0x71, 0x69, 0x62, 0x69, 0x76, 0x66, 0x71, 0x67, 0x73, 0x6f, 0x6c, 0x61,
+ 0x62, 0x63, 0x69, 0x6f, 0x64, 0x70, 0x6e, 0x66, 0x70, 0x68, 0x70, 0x6d, 0x77, 0x66, 0x74, 0x73,
+ 0x64, 0x64, 0x6f, 0x64, 0x68, 0x63, 0x79, 0x72, 0x70, 0x6c, 0x76, 0x64, 0x66, 0x74, 0x70, 0x6b,
+ 0x78, 0x6a, 0x63, 0x6a, 0x61, 0x69, 0x74, 0x61, 0x6b, 0x6a, 0x62, 0x63, 0x71, 0x6b, 0x6e, 0x6f,
+ 0x67, 0x77, 0x61, 0x64, 0x79, 0x73, 0x6c, 0x64, 0x64, 0x64, 0x74, 0x71, 0x6a, 0x73, 0x64, 0x6b,
+ 0x65, 0x6a, 0x79, 0x78, 0x70, 0x6e, 0x72, 0x71, 0x66, 0x79, 0x78, 0x77, 0x76, 0x61, 0x78, 0x69,
+ 0x6f, 0x76, 0x71, 0x70, 0x6b, 0x75, 0x69, 0x75, 0x72, 0x6f, 0x66, 0x65, 0x66, 0x69, 0x79, 0x67,
+ 0x71, 0x76, 0x76, 0x79, 0x71, 0x6b, 0x74, 0x6f, 0x77, 0x61, 0x77, 0x63, 0x71, 0x75, 0x72, 0x73,
+ 0x63, 0x6d, 0x72, 0x6e, 0x73, 0x66, 0x62, 0x70, 0x66, 0x79, 0x77, 0x73, 0x61, 0x69, 0x6a, 0x6a,
+ 0x6c, 0x69, 0x69, 0x69, 0x62, 0x63, 0x63, 0x76, 0x74, 0x6d, 0x6e, 0x72, 0x69, 0x6d, 0x67, 0x76,
+ 0x6a, 0x72, 0x73, 0x61, 0x61, 0x6f, 0x6f, 0x67, 0x78, 0x74, 0x76, 0x77, 0x62, 0x73, 0x64, 0x74,
+ 0x69, 0x76, 0x61, 0x69, 0x65, 0x70, 0x76, 0x73, 0x6f, 0x70, 0x77, 0x73, 0x75, 0x72, 0x68, 0x76,
+ 0x73, 0x62, 0x78, 0x74, 0x78, 0x74, 0x69, 0x71, 0x69, 0x77, 0x6c, 0x68, 0x6d, 0x6a, 0x65, 0x75,
+ 0x70, 0x79, 0x6c, 0x76, 0x62, 0x64, 0x68, 0x62, 0x70, 0x72, 0x6d, 0x65, 0x70, 0x67, 0x65, 0x73,
+ 0x6f, 0x77, 0x66, 0x63, 0x74, 0x77, 0x68, 0x6e, 0x74, 0x6a, 0x76, 0x70, 0x79, 0x77, 0x72, 0x67,
+ 0x65, 0x68, 0x71, 0x66, 0x70, 0x76, 0x67, 0x6e, 0x65, 0x68, 0x77, 0x61, 0x6c, 0x76, 0x76, 0x75,
+ 0x6f, 0x78, 0x77, 0x77, 0x6c, 0x65, 0x73, 0x76, 0x6b, 0x64, 0x70, 0x6f, 0x6a, 0x79, 0x6d, 0x73,
+ 0x6e, 0x64, 0x62, 0x6d, 0x79, 0x63, 0x72, 0x71, 0x68, 0x61, 0x74, 0x62, 0x64, 0x63, 0x72, 0x73,
+ 0x62, 0x79, 0x73, 0x66, 0x66, 0x6e, 0x79, 0x61, 0x70, 0x6b, 0x6a, 0x6e, 0x62, 0x79, 0x6b, 0x64,
+ 0x68, 0x65, 0x66, 0x62, 0x74, 0x63, 0x76, 0x71, 0x73, 0x77, 0x6d, 0x73, 0x71, 0x66, 0x64, 0x6d,
+ 0x71, 0x68, 0x68, 0x77, 0x74, 0x61, 0x79, 0x70, 0x74, 0x66, 0x65, 0x71, 0x74, 0x66, 0x63, 0x73,
+ 0x6c, 0x68, 0x64, 0x75, 0x72, 0x62, 0x72, 0x72, 0x6e, 0x71, 0x6e, 0x69, 0x75, 0x72, 0x71, 0x63,
+ 0x75, 0x65, 0x79, 0x73, 0x65, 0x62, 0x67, 0x62, 0x70, 0x61, 0x62, 0x74, 0x69, 0x6b, 0x6d, 0x6c,
+ 0x65, 0x6a, 0x78, 0x63, 0x72, 0x6f, 0x66, 0x62, 0x77, 0x62, 0x75, 0x69, 0x6f, 0x70, 0x6c, 0x68,
+ 0x6a, 0x78, 0x73, 0x61, 0x66, 0x79, 0x6f, 0x6e, 0x73, 0x67, 0x76, 0x79, 0x64, 0x62, 0x6f, 0x66,
+ 0x63, 0x6b, 0x6c, 0x75, 0x74, 0x77, 0x6a, 0x66, 0x75, 0x63, 0x77, 0x76, 0x6b, 0x6d, 0x69, 0x77,
+ 0x71, 0x61, 0x65, 0x72, 0x66, 0x6d, 0x70, 0x73, 0x68, 0x73, 0x74, 0x77, 0x6e, 0x73, 0x74, 0x76,
+ 0x63, 0x75, 0x72, 0x63, 0x66, 0x6d, 0x67, 0x72, 0x61, 0x69, 0x61, 0x6b, 0x69, 0x74, 0x77, 0x68,
+ 0x74, 0x6e, 0x73, 0x77, 0x66, 0x65, 0x62, 0x6a, 0x6d, 0x66, 0x71, 0x6c, 0x78, 0x79, 0x65, 0x66,
+ 0x62, 0x68, 0x72, 0x61, 0x6e, 0x6a, 0x79, 0x6e, 0x70, 0x79, 0x70, 0x68, 0x71, 0x77, 0x69, 0x6f,
+ 0x63, 0x66, 0x67, 0x77, 0x71, 0x6b, 0x6b, 0x62, 0x71, 0x67, 0x66, 0x6b, 0x65, 0x65, 0x78, 0x75,
+ 0x75, 0x74, 0x75, 0x71, 0x68, 0x72, 0x64, 0x6b, 0x6e, 0x6d, 0x69, 0x62, 0x64, 0x68, 0x73, 0x63,
+ 0x61, 0x74, 0x6e, 0x77, 0x6a, 0x6f, 0x77, 0x74, 0x63, 0x75, 0x6f, 0x70, 0x75, 0x61, 0x67, 0x68,
+ 0x68, 0x72, 0x6b, 0x73, 0x73, 0x66, 0x6f, 0x79, 0x64, 0x77, 0x68, 0x74, 0x71, 0x6d, 0x69, 0x73,
+ 0x71, 0x62, 0x76, 0x77, 0x68, 0x68, 0x79, 0x75, 0x6c, 0x75, 0x79, 0x6e, 0x78, 0x6f, 0x66, 0x71,
+ 0x65, 0x6a, 0x62, 0x67, 0x75, 0x74, 0x6f, 0x64, 0x78, 0x6a, 0x63, 0x73, 0x72, 0x63, 0x68, 0x6b,
+ 0x69, 0x70, 0x6c, 0x73, 0x65, 0x6d, 0x69, 0x63, 0x74, 0x77, 0x6c, 0x6b, 0x78, 0x65, 0x75, 0x6b,
+ 0x6a, 0x77, 0x73, 0x77, 0x77, 0x64, 0x61, 0x69, 0x75, 0x76, 0x77, 0x78, 0x61, 0x6e, 0x71, 0x78,
+ 0x65, 0x77, 0x76, 0x71, 0x70, 0x6d, 0x6d, 0x65, 0x69, 0x71, 0x65, 0x66, 0x74, 0x6c, 0x61, 0x66,
+ 0x6c, 0x62, 0x76, 0x73, 0x77, 0x66, 0x6a, 0x65, 0x79, 0x71, 0x6b, 0x67, 0x68, 0x6e, 0x72, 0x71,
+ 0x73, 0x71, 0x62, 0x71, 0x74, 0x6d, 0x75, 0x6e, 0x6e, 0x6f, 0x75, 0x73, 0x76, 0x6c, 0x68, 0x6e,
+ 0x79, 0x6e, 0x79, 0x74, 0x79, 0x79, 0x79, 0x74, 0x66, 0x77, 0x6d, 0x75, 0x6e, 0x67, 0x68, 0x68,
+ 0x6c, 0x76, 0x68, 0x65, 0x68, 0x6f, 0x76, 0x71, 0x69, 0x68, 0x6f, 0x6c, 0x66, 0x74, 0x6d, 0x73,
+ 0x70, 0x76, 0x61, 0x65, 0x73, 0x62, 0x79, 0x76, 0x6c, 0x69, 0x76, 0x76, 0x79, 0x6a, 0x77, 0x65,
+ 0x68, 0x6d, 0x61, 0x75, 0x6f, 0x6c, 0x66, 0x69, 0x70, 0x73, 0x62, 0x73, 0x6e, 0x72, 0x77, 0x6d,
+ 0x68, 0x67, 0x6d, 0x75, 0x74, 0x67, 0x69, 0x6e, 0x69, 0x65, 0x72, 0x63, 0x64, 0x75, 0x63, 0x63,
+ 0x6a, 0x62, 0x67, 0x74, 0x6b, 0x74, 0x77, 0x73, 0x77, 0x73, 0x65, 0x69, 0x71, 0x63, 0x63, 0x6f,
+ 0x64, 0x63, 0x79, 0x6c, 0x75, 0x74, 0x6b, 0x70, 0x78, 0x62, 0x69, 0x69, 0x6a, 0x72, 0x63, 0x64,
+ 0x62, 0x78, 0x6e, 0x74, 0x6d, 0x66, 0x73, 0x75, 0x77, 0x6c, 0x6a, 0x77, 0x78, 0x62, 0x67, 0x78,
+ 0x70, 0x64, 0x77, 0x6b, 0x6b, 0x77, 0x68, 0x75, 0x62, 0x67, 0x73, 0x76, 0x61, 0x61, 0x71, 0x63,
+ 0x75, 0x70, 0x6a, 0x78, 0x6d, 0x6a, 0x76, 0x72, 0x76, 0x63, 0x66, 0x6c, 0x6e, 0x77, 0x79, 0x6f,
+ 0x69, 0x65, 0x65, 0x65, 0x69, 0x6e, 0x71, 0x79, 0x64, 0x71, 0x72, 0x75, 0x6f, 0x70, 0x61, 0x65,
+ 0x75, 0x6f, 0x70, 0x6b, 0x78, 0x79, 0x6a, 0x6c, 0x78, 0x62, 0x6d, 0x6d, 0x64, 0x62, 0x6e, 0x68,
+ 0x79, 0x64, 0x79, 0x74, 0x61, 0x65, 0x6a, 0x68, 0x6f, 0x62, 0x62, 0x65, 0x73, 0x71, 0x6e, 0x77,
+ 0x76, 0x63, 0x62, 0x74, 0x74, 0x74, 0x6b, 0x6d, 0x79, 0x78, 0x69, 0x75, 0x69, 0x6f, 0x62, 0x6b,
+ 0x6a, 0x75, 0x77, 0x67, 0x71, 0x6d, 0x67, 0x70, 0x6d, 0x65, 0x6b, 0x70, 0x6d, 0x70, 0x74, 0x78,
+ 0x61, 0x68, 0x72, 0x6d, 0x78, 0x79, 0x6d, 0x77, 0x66, 0x68, 0x73, 0x73, 0x64, 0x71, 0x64, 0x64,
+ 0x6e, 0x72, 0x66, 0x74, 0x72, 0x65, 0x75, 0x79, 0x6c, 0x6c, 0x68, 0x72, 0x6c, 0x71, 0x6b, 0x6c,
+ 0x70, 0x61, 0x79, 0x76, 0x72, 0x6a, 0x65, 0x71, 0x72, 0x76, 0x71, 0x6e, 0x74, 0x75, 0x62, 0x64,
+ 0x67, 0x6c, 0x65, 0x78, 0x75, 0x67, 0x6f, 0x79, 0x64, 0x69, 0x66, 0x67, 0x6f, 0x64, 0x62, 0x72,
+ 0x73, 0x79, 0x70, 0x68, 0x63, 0x61, 0x64, 0x6a, 0x6f, 0x6d, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x6e,
+ 0x6f, 0x74, 0x61, 0x6e, 0x6c, 0x62, 0x6f, 0x64, 0x62, 0x76, 0x6c, 0x74, 0x68, 0x79, 0x70, 0x6a,
+ 0x68, 0x71, 0x71, 0x6b, 0x6c, 0x66, 0x67, 0x6e, 0x61, 0x68, 0x6a, 0x6f, 0x65, 0x77, 0x64, 0x70,
+ 0x6c, 0x65, 0x63, 0x66, 0x6b, 0x65, 0x63, 0x62, 0x79, 0x6f, 0x79, 0x63, 0x77, 0x70, 0x61, 0x67,
+ 0x65, 0x73, 0x67, 0x6e, 0x74, 0x6c, 0x67, 0x6e, 0x64, 0x71, 0x75, 0x61, 0x70, 0x70, 0x6c, 0x68,
+ 0x64, 0x65, 0x63, 0x6c, 0x6e, 0x68, 0x6c, 0x67, 0x70, 0x64, 0x63, 0x65, 0x76, 0x78, 0x78, 0x78,
+ 0x68, 0x65, 0x61, 0x79, 0x77, 0x6d, 0x72, 0x72, 0x6f, 0x79, 0x67, 0x78, 0x6d, 0x6d, 0x67, 0x71,
+ 0x75, 0x70, 0x72, 0x69, 0x68, 0x75, 0x6f, 0x70, 0x63, 0x66, 0x65, 0x69, 0x75, 0x64, 0x75, 0x72,
+ 0x6b, 0x67, 0x75, 0x6b, 0x67, 0x6f, 0x6d, 0x65, 0x6d, 0x63, 0x73, 0x79, 0x61, 0x6e, 0x65, 0x69,
+ 0x64, 0x6c, 0x73, 0x75, 0x69, 0x66, 0x79, 0x65, 0x61, 0x68, 0x76, 0x70, 0x62, 0x76, 0x78, 0x72,
+ 0x6b, 0x74, 0x73, 0x6b, 0x66, 0x63, 0x6c, 0x79, 0x68, 0x72, 0x78, 0x6a, 0x62, 0x70, 0x73, 0x6a,
+ 0x68, 0x73, 0x67, 0x67, 0x64, 0x70, 0x77, 0x69, 0x71, 0x65, 0x6f, 0x6c, 0x68, 0x61, 0x6f, 0x64,
+ 0x6c, 0x68, 0x6d, 0x64, 0x79, 0x74, 0x78, 0x63, 0x68, 0x62, 0x64, 0x61, 0x70, 0x78, 0x6e, 0x74,
+ 0x6b, 0x71, 0x67, 0x73, 0x71, 0x73, 0x62, 0x61, 0x64, 0x6a, 0x6c, 0x6e, 0x77, 0x70, 0x66, 0x65,
+ 0x6b, 0x64, 0x66, 0x6d, 0x6d, 0x6a, 0x69, 0x65, 0x65, 0x71, 0x66, 0x62, 0x6f, 0x74, 0x6c, 0x6e,
+ 0x65, 0x73, 0x75, 0x6f, 0x78, 0x62, 0x68, 0x72, 0x65, 0x6c, 0x6f, 0x6a, 0x70, 0x77, 0x64, 0x6b,
+ 0x6d, 0x73, 0x71, 0x68, 0x6b, 0x61, 0x6b, 0x69, 0x73, 0x65, 0x6d, 0x61, 0x70, 0x79, 0x6f, 0x76,
+ 0x62, 0x67, 0x78, 0x61, 0x6d, 0x63, 0x6f, 0x70, 0x66, 0x6e, 0x65, 0x65, 0x62, 0x62, 0x6b, 0x73,
+ 0x64, 0x6c, 0x78, 0x71, 0x62, 0x76, 0x65, 0x6e, 0x71, 0x73, 0x6e, 0x75, 0x6d, 0x71, 0x6e, 0x75,
+ 0x73, 0x6a, 0x74, 0x75, 0x6d, 0x68, 0x6f, 0x69, 0x6a, 0x73, 0x6b, 0x75, 0x70, 0x75, 0x69, 0x6a,
+ 0x71, 0x6a, 0x73, 0x71, 0x6c, 0x66, 0x70, 0x63, 0x75, 0x6d, 0x6f, 0x65, 0x71, 0x75, 0x77, 0x61,
+ 0x63, 0x78, 0x6f, 0x78, 0x6c, 0x69, 0x76, 0x6b, 0x65, 0x79, 0x67, 0x74, 0x77, 0x65, 0x64, 0x6f,
+ 0x74, 0x79, 0x6f, 0x74, 0x6e, 0x74, 0x66, 0x6e, 0x69, 0x70, 0x77, 0x68, 0x78, 0x73, 0x66, 0x6a,
+ 0x65, 0x6f, 0x6b, 0x64, 0x6c, 0x73, 0x75, 0x64, 0x64, 0x6b, 0x68, 0x62, 0x77, 0x72, 0x75, 0x76,
+ 0x75, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x64, 0x77, 0x62, 0x65, 0x66, 0x65, 0x6a, 0x71, 0x69, 0x71,
+ 0x78, 0x65, 0x77, 0x77, 0x79, 0x66, 0x67, 0x72, 0x6d, 0x6c, 0x68, 0x6a, 0x63, 0x79, 0x79, 0x72,
+ 0x73, 0x62, 0x62, 0x77, 0x6e, 0x6c, 0x79, 0x75, 0x6f, 0x78, 0x68, 0x6a, 0x64, 0x6c, 0x62, 0x67,
+ 0x65, 0x6c, 0x65, 0x64, 0x65, 0x70, 0x75, 0x68, 0x64, 0x72, 0x71, 0x62, 0x66, 0x74, 0x70, 0x77,
+ 0x63, 0x6d, 0x76, 0x63, 0x78, 0x64, 0x76, 0x75, 0x72, 0x6e, 0x65, 0x68, 0x71, 0x78, 0x73, 0x62,
+ 0x77, 0x6c, 0x6f, 0x63, 0x74, 0x64, 0x71, 0x62, 0x79, 0x78, 0x70, 0x6a, 0x69, 0x6c, 0x73, 0x68,
+ 0x6c, 0x6a, 0x6b, 0x6f, 0x73, 0x6a, 0x6e, 0x62, 0x73, 0x66, 0x6f, 0x67, 0x76, 0x74, 0x6c, 0x78,
+ 0x63, 0x6c, 0x76, 0x77, 0x6c, 0x6c, 0x74, 0x71, 0x6b, 0x66, 0x66, 0x63, 0x61, 0x68, 0x6c, 0x76,
+ 0x68, 0x72, 0x72, 0x6c, 0x6b, 0x61, 0x6f, 0x6c, 0x72, 0x6b, 0x6b, 0x78, 0x77, 0x78, 0x61, 0x68,
+ 0x63, 0x66, 0x6a, 0x68, 0x78, 0x74, 0x71, 0x68, 0x64, 0x69, 0x6d, 0x75, 0x72, 0x6d, 0x78, 0x6c,
+ 0x6a, 0x68, 0x61, 0x74, 0x69, 0x64, 0x67, 0x71, 0x62, 0x67, 0x6b, 0x6f, 0x6e, 0x63, 0x64, 0x73,
+ 0x72, 0x6c, 0x6c, 0x78, 0x64, 0x70, 0x76, 0x79, 0x69, 0x61, 0x76, 0x6d, 0x72, 0x65, 0x64, 0x75,
+ 0x64, 0x77, 0x70, 0x64, 0x71, 0x73, 0x77, 0x75, 0x61, 0x6b, 0x76, 0x6e, 0x62, 0x70, 0x75, 0x69,
+ 0x6e, 0x73, 0x74, 0x66, 0x6d, 0x6c, 0x6b, 0x70, 0x67, 0x66, 0x6f, 0x75, 0x65, 0x69, 0x78, 0x67,
+ 0x68, 0x78, 0x67, 0x6b, 0x6a, 0x68, 0x72, 0x71, 0x72, 0x71, 0x77, 0x6c, 0x74, 0x64, 0x79, 0x74,
+ 0x73, 0x6d, 0x73, 0x6f, 0x79, 0x62, 0x79, 0x71, 0x6c, 0x73, 0x73, 0x6a, 0x67, 0x6e, 0x72, 0x69,
+ 0x75, 0x71, 0x62, 0x72, 0x65, 0x63, 0x6b, 0x63, 0x79, 0x76, 0x74, 0x79, 0x79, 0x67, 0x77, 0x79,
+ 0x66, 0x64, 0x72, 0x73, 0x61, 0x6f, 0x62, 0x72, 0x76, 0x66, 0x62, 0x76, 0x73, 0x68, 0x67, 0x63,
+ 0x72, 0x64, 0x6b, 0x74, 0x6c, 0x6b, 0x78, 0x6f, 0x64, 0x6a, 0x69, 0x76, 0x67, 0x75, 0x73, 0x70,
+ 0x78, 0x73, 0x75, 0x67, 0x71, 0x71, 0x74, 0x65, 0x65, 0x6d, 0x70, 0x6e, 0x74, 0x64, 0x77, 0x68,
+ 0x78, 0x72, 0x6e, 0x71, 0x73, 0x70, 0x6f, 0x6a, 0x6a, 0x6f, 0x69, 0x70, 0x63, 0x6b, 0x71, 0x6e,
+ 0x77, 0x79, 0x6c, 0x6e, 0x73, 0x6f, 0x63, 0x71, 0x67, 0x65, 0x77, 0x6d, 0x75, 0x6d, 0x64, 0x67,
+ 0x64, 0x65, 0x73, 0x73, 0x64, 0x74, 0x61, 0x66, 0x73, 0x69, 0x69, 0x65, 0x68, 0x66, 0x6e, 0x67,
+ 0x69, 0x6d, 0x66, 0x65, 0x78, 0x79, 0x72, 0x61, 0x6b, 0x68, 0x64, 0x75, 0x67, 0x67, 0x71, 0x76,
+ 0x76, 0x75, 0x79, 0x71, 0x62, 0x76, 0x61, 0x77, 0x71, 0x76, 0x76, 0x67, 0x70, 0x69, 0x62, 0x6e,
+ 0x6e, 0x65, 0x65, 0x67, 0x75, 0x6a, 0x70, 0x70, 0x71, 0x68, 0x71, 0x6b, 0x61, 0x6c, 0x74, 0x6a,
+ 0x6b, 0x63, 0x70, 0x72, 0x6c, 0x73, 0x79, 0x77, 0x74, 0x67, 0x71, 0x72, 0x65, 0x61, 0x72, 0x67,
+ 0x66, 0x6d, 0x69, 0x65, 0x75, 0x61, 0x6d, 0x69, 0x77, 0x6c, 0x76, 0x71, 0x64, 0x6f, 0x79, 0x69,
+ 0x77, 0x6a, 0x68, 0x73, 0x65, 0x6a, 0x75, 0x6f, 0x75, 0x6a, 0x63, 0x67, 0x6c, 0x6d, 0x78, 0x74,
+ 0x74, 0x75, 0x72, 0x6a, 0x64, 0x74, 0x72, 0x75, 0x63, 0x6f, 0x72, 0x6f, 0x6b, 0x65, 0x64, 0x76,
+ 0x79, 0x6d, 0x78, 0x71, 0x61, 0x61, 0x79, 0x6d, 0x77, 0x79, 0x68, 0x74, 0x69, 0x6e, 0x67, 0x72,
+ 0x74, 0x70, 0x64, 0x72, 0x6a, 0x78, 0x6c, 0x61, 0x6d, 0x71, 0x6b, 0x6a, 0x64, 0x70, 0x62, 0x6a,
+ 0x67, 0x69, 0x64, 0x6e, 0x6b, 0x62, 0x61, 0x6a, 0x74, 0x61, 0x69, 0x78, 0x75, 0x78, 0x72, 0x69,
+ 0x6d, 0x6e, 0x79, 0x63, 0x70, 0x68, 0x78, 0x6d, 0x79, 0x64, 0x63, 0x6e, 0x62, 0x6e, 0x65, 0x6f,
+ 0x6c, 0x79, 0x71, 0x70, 0x6a, 0x68, 0x70, 0x75, 0x6e, 0x64, 0x72, 0x79, 0x67, 0x77, 0x63, 0x68,
+ 0x72, 0x79, 0x65, 0x78, 0x64, 0x77, 0x6e, 0x6d, 0x62, 0x71, 0x79, 0x75, 0x6b, 0x63, 0x72, 0x71,
+ 0x65, 0x69, 0x6c, 0x68, 0x66, 0x62, 0x61, 0x79, 0x6c, 0x67, 0x61, 0x78, 0x76, 0x6c, 0x6d, 0x75,
+ 0x73, 0x6d, 0x6a, 0x6c, 0x6e, 0x66, 0x70, 0x61, 0x6f, 0x6e, 0x6b, 0x70, 0x69, 0x6b, 0x61, 0x75,
+ 0x6d, 0x73, 0x6b, 0x61, 0x63, 0x72, 0x73, 0x65, 0x6a, 0x77, 0x6b, 0x78, 0x68, 0x6e, 0x6f, 0x78,
+ 0x69, 0x6b, 0x67, 0x63, 0x73, 0x76, 0x6f, 0x67, 0x63, 0x71, 0x6d, 0x67, 0x62, 0x6e, 0x76, 0x71,
+ 0x71, 0x78, 0x78, 0x68, 0x78, 0x63, 0x65, 0x72, 0x6f, 0x6b, 0x75, 0x6a, 0x76, 0x6d, 0x79, 0x68,
+ 0x75, 0x78, 0x77, 0x75, 0x6d, 0x70, 0x6e, 0x71, 0x76, 0x77, 0x76, 0x76, 0x70, 0x77, 0x72, 0x61,
+ 0x6a, 0x62, 0x71, 0x67, 0x67, 0x73, 0x71, 0x75, 0x6a, 0x73, 0x66, 0x6e, 0x64, 0x76, 0x6a, 0x65,
+ 0x76, 0x66, 0x6e, 0x68, 0x67, 0x78, 0x79, 0x66, 0x65, 0x6d, 0x6d, 0x71, 0x73, 0x66, 0x63, 0x79,
+ 0x6c, 0x72, 0x6a, 0x6f, 0x78, 0x76, 0x6d, 0x78, 0x63, 0x6b, 0x67, 0x70, 0x78, 0x76, 0x61, 0x62,
+ 0x6e, 0x76, 0x75, 0x76, 0x74, 0x70, 0x75, 0x61, 0x77, 0x65, 0x75, 0x6b, 0x65, 0x74, 0x76, 0x78,
+ 0x66, 0x6f, 0x6a, 0x72, 0x72, 0x70, 0x66, 0x70, 0x63, 0x73, 0x78, 0x6e, 0x74, 0x77, 0x78, 0x68,
+ 0x61, 0x6a, 0x61, 0x75, 0x74, 0x75, 0x6b, 0x62, 0x6c, 0x6a, 0x72, 0x79, 0x73, 0x79, 0x6b, 0x6f,
+ 0x77, 0x6b, 0x75, 0x74, 0x76, 0x74, 0x64, 0x79, 0x64, 0x74, 0x64, 0x77, 0x71, 0x6b, 0x75, 0x72,
+ 0x66, 0x75, 0x61, 0x79, 0x78, 0x6a, 0x69, 0x6a, 0x68, 0x6a, 0x6c, 0x77, 0x68, 0x67, 0x77, 0x73,
+ 0x76, 0x6e, 0x65, 0x6d, 0x6f, 0x6a, 0x6e, 0x61, 0x6e, 0x6c, 0x74, 0x6b, 0x70, 0x70, 0x75, 0x66,
+ 0x70, 0x72, 0x76, 0x69, 0x77, 0x6b, 0x76, 0x68, 0x6e, 0x6b, 0x77, 0x64, 0x76, 0x61, 0x6b, 0x6d,
+ 0x77, 0x6f, 0x6a, 0x6a, 0x62, 0x6b, 0x79, 0x73, 0x6a, 0x6a, 0x76, 0x62, 0x64, 0x69, 0x6e, 0x6a,
+ 0x6e, 0x6a, 0x6b, 0x75, 0x6f, 0x61, 0x72, 0x6d, 0x79, 0x66, 0x71, 0x77, 0x6e, 0x72, 0x68, 0x74,
+ 0x69, 0x6d, 0x6b, 0x70, 0x65, 0x77, 0x65, 0x64, 0x66, 0x77, 0x6b, 0x71, 0x6c, 0x66, 0x70, 0x75,
+ 0x63, 0x63, 0x62, 0x65, 0x64, 0x65, 0x75, 0x6a, 0x6b, 0x67, 0x69, 0x64, 0x77, 0x71, 0x65, 0x70,
+ 0x78, 0x67, 0x63, 0x62, 0x79, 0x74, 0x75, 0x73, 0x72, 0x70, 0x63, 0x72, 0x77, 0x66, 0x6f, 0x73,
+ 0x66, 0x6e, 0x61, 0x69, 0x62, 0x75, 0x79, 0x71, 0x68, 0x77, 0x6f, 0x79, 0x67, 0x76, 0x62, 0x67,
+ 0x78, 0x63, 0x70, 0x67, 0x62, 0x69, 0x6b, 0x75, 0x78, 0x77, 0x6d, 0x71, 0x78, 0x63, 0x77, 0x78,
+ 0x61, 0x61, 0x75, 0x74, 0x6b, 0x77, 0x62, 0x71, 0x70, 0x74, 0x6d, 0x6e, 0x62, 0x71, 0x73, 0x71,
+ 0x78, 0x76, 0x79, 0x68, 0x61, 0x73, 0x78, 0x61, 0x77, 0x77, 0x62, 0x67, 0x77, 0x6b, 0x6d, 0x69,
+ 0x77, 0x6a, 0x6a, 0x74, 0x69, 0x64, 0x62, 0x70, 0x6c, 0x6e, 0x75, 0x71, 0x6e, 0x66, 0x72, 0x76,
+ 0x63, 0x63, 0x61, 0x6b, 0x74, 0x62, 0x68, 0x61, 0x70, 0x69, 0x75, 0x64, 0x6a, 0x6b, 0x68, 0x73,
+ 0x6f, 0x6b, 0x67, 0x63, 0x6a, 0x79, 0x79, 0x65, 0x78, 0x78, 0x78, 0x67, 0x73, 0x6d, 0x63, 0x79,
+ 0x6c, 0x75, 0x6e, 0x65, 0x79, 0x66, 0x6d, 0x63, 0x76, 0x6a, 0x6b, 0x67, 0x71, 0x67, 0x69, 0x61,
+ 0x77, 0x71, 0x75, 0x76, 0x76, 0x70, 0x74, 0x6d, 0x69, 0x67, 0x61, 0x66, 0x64, 0x6b, 0x73, 0x66,
+ 0x66, 0x70, 0x65, 0x77, 0x6d, 0x70, 0x6f, 0x61, 0x71, 0x6e, 0x76, 0x69, 0x6a, 0x68, 0x70, 0x71,
+ 0x70, 0x66, 0x6e, 0x6f, 0x70, 0x72, 0x64, 0x78, 0x6f, 0x75, 0x66, 0x69, 0x73, 0x68, 0x71, 0x77,
+ 0x6b, 0x69, 0x73, 0x66, 0x79, 0x6d, 0x64, 0x69, 0x74, 0x66, 0x73, 0x64, 0x6e, 0x63, 0x6b, 0x75,
+ 0x70, 0x70, 0x64, 0x72, 0x71, 0x79, 0x74, 0x71, 0x73, 0x6f, 0x6a, 0x72, 0x6d, 0x72, 0x78, 0x63,
+ 0x73, 0x61, 0x70, 0x6d, 0x69, 0x76, 0x73, 0x64, 0x6c, 0x73, 0x69, 0x61, 0x78, 0x61, 0x78, 0x78,
+ 0x67, 0x75, 0x6a, 0x78, 0x75, 0x6e, 0x6f, 0x61, 0x6e, 0x62, 0x75, 0x74, 0x77, 0x6a, 0x68, 0x70,
+ 0x67, 0x65, 0x72, 0x6c, 0x6e, 0x75, 0x79, 0x6a, 0x72, 0x6b, 0x6b, 0x63, 0x79, 0x77, 0x74, 0x73,
+ 0x6a, 0x6c, 0x6a, 0x69, 0x6b, 0x66, 0x6b, 0x61, 0x6a, 0x72, 0x66, 0x67, 0x73, 0x6c, 0x67, 0x6d,
+ 0x6c, 0x69, 0x6a, 0x6d, 0x6f, 0x64, 0x78, 0x6f, 0x6c, 0x75, 0x74, 0x6c, 0x74, 0x74, 0x64, 0x72,
+ 0x76, 0x6f, 0x62, 0x63, 0x77, 0x66, 0x69, 0x74, 0x70, 0x6e, 0x72, 0x75, 0x6c, 0x77, 0x61, 0x79,
+ 0x78, 0x72, 0x62, 0x75, 0x75, 0x6c, 0x6a, 0x6c, 0x69, 0x70, 0x76, 0x63, 0x6a, 0x74, 0x75, 0x6c,
+ 0x77, 0x6c, 0x75, 0x72, 0x68, 0x79, 0x6b, 0x62, 0x6a, 0x64, 0x63, 0x6b, 0x70, 0x6e, 0x6d, 0x6b,
+ 0x78, 0x6e, 0x77, 0x6c, 0x61, 0x6d, 0x66, 0x6e, 0x62, 0x77, 0x75, 0x76, 0x6c, 0x72, 0x73, 0x6c,
+ 0x61, 0x63, 0x77, 0x75, 0x64, 0x70, 0x76, 0x72, 0x6a, 0x63, 0x62, 0x6d, 0x72, 0x71, 0x62, 0x6e,
+ 0x76, 0x70, 0x66, 0x68, 0x63, 0x68, 0x69, 0x66, 0x6c, 0x72, 0x74, 0x6c, 0x78, 0x74, 0x64, 0x64,
+ 0x74, 0x63, 0x6a, 0x6d, 0x69, 0x6a, 0x74, 0x78, 0x66, 0x68, 0x63, 0x66, 0x76, 0x63, 0x74, 0x67,
+ 0x6e, 0x74, 0x69, 0x66, 0x69, 0x75, 0x63, 0x65, 0x64, 0x62, 0x6f, 0x71, 0x6c, 0x62, 0x79, 0x61,
+ 0x71, 0x6d, 0x76, 0x6e, 0x63, 0x62, 0x66, 0x61, 0x6d, 0x65, 0x74, 0x6e, 0x6a, 0x78, 0x61, 0x73,
+ 0x6e, 0x62, 0x78, 0x72, 0x65, 0x6b, 0x6a, 0x76, 0x6b, 0x6f, 0x70, 0x6a, 0x65, 0x72, 0x6c, 0x61,
+ 0x64, 0x61, 0x64, 0x69, 0x61, 0x6f, 0x6f, 0x66, 0x6a, 0x70, 0x63, 0x6e, 0x77, 0x6b, 0x77, 0x63,
+ 0x74, 0x62, 0x79, 0x6e, 0x77, 0x6b, 0x70, 0x6d, 0x67, 0x72, 0x66, 0x71, 0x72, 0x6a, 0x6f, 0x73,
+ 0x77, 0x71, 0x77, 0x62, 0x75, 0x78, 0x6b, 0x6a, 0x63, 0x6b, 0x78, 0x67, 0x66, 0x66, 0x61, 0x77,
+ 0x79, 0x65, 0x66, 0x64, 0x73, 0x71, 0x72, 0x77, 0x73, 0x77, 0x77, 0x75, 0x79, 0x73, 0x75, 0x65,
+ 0x75, 0x65, 0x6d, 0x74, 0x75, 0x65, 0x74, 0x67, 0x69, 0x70, 0x6c, 0x6d, 0x74, 0x73, 0x68, 0x6f,
+ 0x61, 0x6c, 0x76, 0x65, 0x6d, 0x6c, 0x6d, 0x64, 0x64, 0x61, 0x73, 0x6f, 0x68, 0x77, 0x6e, 0x6c,
+ 0x6a, 0x77, 0x73, 0x77, 0x64, 0x69, 0x79, 0x6d, 0x75, 0x65, 0x6f, 0x6a, 0x72, 0x68, 0x75, 0x6f,
+ 0x6a, 0x77, 0x69, 0x6c, 0x61, 0x70, 0x69, 0x64, 0x78, 0x73, 0x6e, 0x70, 0x74, 0x75, 0x63, 0x71,
+ 0x77, 0x78, 0x73, 0x69, 0x66, 0x73, 0x73, 0x6a, 0x6e, 0x66, 0x65, 0x67, 0x70, 0x63, 0x6b, 0x6f,
+ 0x76, 0x77, 0x75, 0x74, 0x6d, 0x77, 0x74, 0x6c, 0x74, 0x69, 0x78, 0x6d, 0x6c, 0x75, 0x75, 0x66,
+ 0x76, 0x69, 0x71, 0x69, 0x73, 0x66, 0x78, 0x6e, 0x69, 0x78, 0x69, 0x69, 0x62, 0x71, 0x70, 0x69,
+ 0x70, 0x78, 0x64, 0x6d, 0x67, 0x70, 0x62, 0x67, 0x64, 0x65, 0x71, 0x77, 0x6b, 0x72, 0x6a, 0x71,
+ 0x70, 0x68, 0x70, 0x62, 0x71, 0x71, 0x74, 0x70, 0x73, 0x6d, 0x70, 0x64, 0x67, 0x76, 0x69, 0x74,
+ 0x70, 0x64, 0x72, 0x6a, 0x70, 0x6d, 0x66, 0x69, 0x74, 0x62, 0x66, 0x6a, 0x72, 0x69, 0x62, 0x74,
+ 0x71, 0x77, 0x6e, 0x72, 0x6b, 0x73, 0x66, 0x71, 0x74, 0x6a, 0x6c, 0x68, 0x6b, 0x6a, 0x68, 0x6c,
+ 0x6c, 0x69, 0x6d, 0x67, 0x76, 0x78, 0x61, 0x78, 0x6b, 0x6a, 0x65, 0x74, 0x75, 0x6d, 0x74, 0x71,
+ 0x79, 0x61, 0x6a, 0x75, 0x75, 0x6c, 0x74, 0x6f, 0x72, 0x6f, 0x74, 0x65, 0x75, 0x76, 0x62, 0x74,
+ 0x6f, 0x64, 0x62, 0x6a, 0x67, 0x62, 0x68, 0x6f, 0x6b, 0x75, 0x6a, 0x6c, 0x77, 0x69, 0x78, 0x66,
+ 0x6e, 0x68, 0x70, 0x66, 0x68, 0x77, 0x74, 0x67, 0x6b, 0x68, 0x63, 0x69, 0x76, 0x62, 0x6e, 0x69,
+ 0x64, 0x6d, 0x70, 0x63, 0x6a, 0x78, 0x67, 0x64, 0x69, 0x70, 0x65, 0x6c, 0x61, 0x67, 0x6a, 0x64,
+ 0x67, 0x61, 0x62, 0x76, 0x69, 0x77, 0x70, 0x70, 0x6a, 0x78, 0x70, 0x68, 0x78, 0x65, 0x64, 0x61,
+ 0x69, 0x6e, 0x76, 0x62, 0x64, 0x66, 0x6a, 0x79, 0x63, 0x78, 0x6b, 0x71, 0x78, 0x77, 0x6d, 0x6b,
+ 0x75, 0x72, 0x6f, 0x67, 0x75, 0x69, 0x72, 0x76, 0x66, 0x79, 0x63, 0x77, 0x74, 0x6d, 0x67, 0x6c,
+ 0x70, 0x75, 0x71, 0x78, 0x6f, 0x6d, 0x73, 0x75, 0x64, 0x78, 0x6b, 0x77, 0x6b, 0x77, 0x6f, 0x79,
+ 0x78, 0x6f, 0x73, 0x65, 0x62, 0x61, 0x79, 0x75, 0x74, 0x66, 0x71, 0x74, 0x6e, 0x6b, 0x68, 0x61,
+ 0x74, 0x62, 0x75, 0x65, 0x76, 0x78, 0x6b, 0x75, 0x6b, 0x69, 0x70, 0x74, 0x75, 0x74, 0x6e, 0x6b,
+ 0x68, 0x62, 0x61, 0x65, 0x6b, 0x77, 0x63, 0x66, 0x61, 0x62, 0x6a, 0x73, 0x68, 0x63, 0x61, 0x79,
+ 0x68, 0x70, 0x74, 0x70, 0x73, 0x6d, 0x67, 0x71, 0x6b, 0x73, 0x63, 0x6d, 0x61, 0x6b, 0x69, 0x69,
+ 0x72, 0x65, 0x61, 0x6e, 0x61, 0x78, 0x67, 0x6a, 0x73, 0x67, 0x6a, 0x6c, 0x66, 0x74, 0x6b, 0x75,
+ 0x6a, 0x6b, 0x66, 0x69, 0x68, 0x63, 0x73, 0x65, 0x6b, 0x62, 0x77, 0x68, 0x73, 0x72, 0x6a, 0x67,
+ 0x6a, 0x63, 0x6a, 0x70, 0x77, 0x72, 0x69, 0x69, 0x6d, 0x6b, 0x70, 0x62, 0x79, 0x74, 0x63, 0x74,
+ 0x6d, 0x6c, 0x79, 0x79, 0x76, 0x6e, 0x74, 0x79, 0x6b, 0x6a, 0x70, 0x6b, 0x6f, 0x78, 0x6f, 0x72,
+ 0x77, 0x69, 0x71, 0x62, 0x61, 0x6a, 0x72, 0x6b, 0x6e, 0x78, 0x6e, 0x65, 0x75, 0x61, 0x70, 0x70,
+ 0x71, 0x61, 0x66, 0x6d, 0x65, 0x6c, 0x75, 0x71, 0x70, 0x6e, 0x63, 0x6a, 0x69, 0x61, 0x62, 0x72,
+ 0x77, 0x79, 0x6c, 0x65, 0x70, 0x6d, 0x75, 0x78, 0x6f, 0x78, 0x6f, 0x73, 0x6c, 0x78, 0x61, 0x75,
+ 0x73, 0x63, 0x65, 0x6b, 0x78, 0x61, 0x6d, 0x67, 0x69, 0x71, 0x6e, 0x76, 0x6a, 0x70, 0x70, 0x6b,
+ 0x68, 0x78, 0x73, 0x75, 0x74, 0x70, 0x72, 0x70, 0x61, 0x67, 0x63, 0x78, 0x78, 0x61, 0x68, 0x76,
+ 0x77, 0x64, 0x6d, 0x66, 0x71, 0x69, 0x71, 0x61, 0x6a, 0x6f, 0x6c, 0x6c, 0x62, 0x6a, 0x77, 0x77,
+ 0x77, 0x70, 0x6b, 0x78, 0x68, 0x61, 0x67, 0x6d, 0x72, 0x6b, 0x73, 0x75, 0x70, 0x67, 0x70, 0x73,
+ 0x76, 0x6a, 0x74, 0x76, 0x79, 0x67, 0x6d, 0x61, 0x67, 0x73, 0x6c, 0x6a, 0x79, 0x64, 0x61, 0x76,
+ 0x68, 0x75, 0x76, 0x69, 0x75, 0x72, 0x6e, 0x63, 0x62, 0x63, 0x6c, 0x76, 0x78, 0x6b, 0x79, 0x78,
+ 0x79, 0x79, 0x68, 0x74, 0x6a, 0x71, 0x6c, 0x73, 0x69, 0x69, 0x6c, 0x61, 0x70, 0x74, 0x66, 0x65,
+ 0x71, 0x67, 0x75, 0x6b, 0x66, 0x69, 0x61, 0x72, 0x72, 0x6e, 0x6e, 0x6c, 0x64, 0x65, 0x79, 0x68,
+ 0x6d, 0x66, 0x63, 0x71, 0x6b, 0x73, 0x62, 0x6d, 0x68, 0x72, 0x70, 0x6c, 0x68, 0x64, 0x76, 0x61,
+ 0x77, 0x6a, 0x61, 0x6c, 0x6b, 0x69, 0x63, 0x77, 0x6f, 0x78, 0x63, 0x64, 0x62, 0x63, 0x74, 0x62,
+ 0x69, 0x79, 0x6f, 0x75, 0x6a, 0x6f, 0x73, 0x6a, 0x66, 0x62, 0x6a, 0x6a, 0x76, 0x65, 0x69, 0x6d,
+ 0x78, 0x68, 0x6d, 0x67, 0x78, 0x76, 0x78, 0x73, 0x6e, 0x6f, 0x72, 0x73, 0x72, 0x77, 0x6c, 0x64,
+ 0x6e, 0x74, 0x62, 0x65, 0x61, 0x64, 0x72, 0x75, 0x6a, 0x64, 0x63, 0x67, 0x61, 0x6f, 0x76, 0x76,
+ 0x68, 0x69, 0x73, 0x75, 0x6b, 0x6d, 0x65, 0x69, 0x6d, 0x6a, 0x77, 0x6e, 0x77, 0x67, 0x6a, 0x6b,
+ 0x6d, 0x77, 0x73, 0x62, 0x63, 0x73, 0x6c, 0x62, 0x71, 0x6c, 0x6f, 0x73, 0x6d, 0x61, 0x77, 0x76,
+ 0x64, 0x66, 0x79, 0x6a, 0x6f, 0x76, 0x67, 0x6c, 0x6c, 0x6f, 0x76, 0x72, 0x77, 0x72, 0x6d, 0x64,
+ 0x6b, 0x76, 0x74, 0x69, 0x62, 0x79, 0x63, 0x6b, 0x64, 0x6a, 0x63, 0x75, 0x6d, 0x76, 0x76, 0x62,
+ 0x69, 0x6b, 0x71, 0x65, 0x6c, 0x79, 0x6c, 0x76, 0x64, 0x70, 0x68, 0x6a, 0x6d, 0x75, 0x6e, 0x73,
+ 0x68, 0x77, 0x66, 0x6a, 0x73, 0x61, 0x72, 0x67, 0x6d, 0x63, 0x6a, 0x77, 0x64, 0x61, 0x6b, 0x68,
+ 0x6c, 0x6a, 0x64, 0x73, 0x68, 0x77, 0x67, 0x71, 0x74, 0x72, 0x72, 0x6a, 0x67, 0x66, 0x6d, 0x69,
+ 0x77, 0x72, 0x67, 0x75, 0x76, 0x65, 0x6e, 0x79, 0x6b, 0x71, 0x6c, 0x79, 0x6a, 0x64, 0x66, 0x6a,
+ 0x6f, 0x6c, 0x75, 0x71, 0x71, 0x6e, 0x75, 0x79, 0x68, 0x79, 0x63, 0x71, 0x69, 0x67, 0x69, 0x70,
+ 0x70, 0x67, 0x68, 0x71, 0x68, 0x76, 0x68, 0x78, 0x62, 0x6e, 0x79, 0x73, 0x77, 0x73, 0x66, 0x6c,
+ 0x63, 0x74, 0x71, 0x66, 0x70, 0x6b, 0x75, 0x6d, 0x79, 0x64, 0x77, 0x64, 0x6c, 0x68, 0x62, 0x79,
+ 0x78, 0x62, 0x6f, 0x76, 0x64, 0x62, 0x69, 0x62, 0x77, 0x61, 0x70, 0x67, 0x79, 0x73, 0x66, 0x6d,
+ 0x6d, 0x62, 0x77, 0x70, 0x66, 0x61, 0x79, 0x66, 0x67, 0x65, 0x78, 0x73, 0x65, 0x70, 0x79, 0x71,
+ 0x75, 0x71, 0x77, 0x77, 0x68, 0x6e, 0x6a, 0x6b, 0x72, 0x64, 0x68, 0x67, 0x64, 0x66, 0x6c, 0x62,
+ 0x75, 0x68, 0x64, 0x6f, 0x68, 0x73, 0x76, 0x61, 0x6e, 0x61, 0x77, 0x6f, 0x71, 0x62, 0x62, 0x65,
+ 0x6f, 0x68, 0x6b, 0x76, 0x6c, 0x67, 0x65, 0x6c, 0x74, 0x65, 0x79, 0x66, 0x72, 0x76, 0x74, 0x6f,
+ 0x63, 0x69, 0x6d, 0x6d, 0x78, 0x6c, 0x6d, 0x65, 0x6a, 0x63, 0x72, 0x65, 0x62, 0x6b, 0x66, 0x64,
+ 0x6d, 0x79, 0x72, 0x76, 0x74, 0x6d, 0x6a, 0x66, 0x67, 0x67, 0x61, 0x67, 0x6f, 0x64, 0x76, 0x79,
+ 0x76, 0x69, 0x6e, 0x66, 0x79, 0x73, 0x69, 0x65, 0x76, 0x6a, 0x6f, 0x66, 0x68, 0x64, 0x72, 0x6c,
+ 0x67, 0x6c, 0x79, 0x70, 0x6e, 0x72, 0x61, 0x69, 0x73, 0x6e, 0x65, 0x79, 0x67, 0x63, 0x6d, 0x74,
+ 0x67, 0x75, 0x6c, 0x6c, 0x71, 0x70, 0x62, 0x74, 0x61, 0x61, 0x64, 0x76, 0x69, 0x74, 0x6b, 0x76,
+ 0x76, 0x68, 0x62, 0x62, 0x66, 0x71, 0x6a, 0x72, 0x6a, 0x6b, 0x6c, 0x72, 0x6e, 0x74, 0x74, 0x77,
+ 0x61, 0x77, 0x69, 0x63, 0x6e, 0x6e, 0x68, 0x6c, 0x68, 0x61, 0x76, 0x61, 0x61, 0x75, 0x75, 0x6e,
+ 0x69, 0x70, 0x6d, 0x6c, 0x68, 0x73, 0x71, 0x63, 0x73, 0x61, 0x72, 0x75, 0x6f, 0x75, 0x67, 0x70,
+ 0x65, 0x69, 0x65, 0x77, 0x72, 0x79, 0x77, 0x73, 0x76, 0x6f, 0x77, 0x6a, 0x69, 0x72, 0x69, 0x71,
+ 0x79, 0x63, 0x63, 0x75, 0x6b, 0x69, 0x75, 0x62, 0x63, 0x75, 0x6d, 0x61, 0x6b, 0x6a, 0x63, 0x6f,
+ 0x77, 0x71, 0x69, 0x6b, 0x6e, 0x77, 0x75, 0x68, 0x78, 0x74, 0x70, 0x77, 0x68, 0x67, 0x73, 0x72,
+ 0x76, 0x61, 0x66, 0x68, 0x62, 0x61, 0x6a, 0x6e, 0x73, 0x77, 0x79, 0x61, 0x66, 0x74, 0x6a, 0x61,
+ 0x70, 0x6f, 0x66, 0x75, 0x65, 0x6a, 0x76, 0x66, 0x67, 0x75, 0x6c, 0x79, 0x6d, 0x79, 0x73, 0x79,
+ 0x66, 0x64, 0x61, 0x75, 0x79, 0x6d, 0x66, 0x6a, 0x78, 0x6a, 0x73, 0x76, 0x6a, 0x6a, 0x6d, 0x6f,
+ 0x67, 0x63, 0x66, 0x71, 0x67, 0x69, 0x73, 0x68, 0x6b, 0x6e, 0x6e, 0x6a, 0x70, 0x61, 0x68, 0x6a,
+ 0x77, 0x6d, 0x65, 0x69, 0x6b, 0x67, 0x6e, 0x6f, 0x63, 0x72, 0x79, 0x70, 0x73, 0x6d, 0x75, 0x76,
+ 0x77, 0x6b, 0x6b, 0x72, 0x70, 0x61, 0x74, 0x6c, 0x76, 0x76, 0x76, 0x75, 0x70, 0x72, 0x77, 0x6c,
+ 0x6b, 0x64, 0x66, 0x6f, 0x64, 0x6e, 0x6f, 0x75, 0x64, 0x67, 0x63, 0x71, 0x64, 0x70, 0x79, 0x67,
+ 0x61, 0x70, 0x62, 0x72, 0x71, 0x6d, 0x66, 0x65, 0x6f, 0x66, 0x63, 0x77, 0x6b, 0x78, 0x70, 0x77,
+ 0x65, 0x71, 0x78, 0x66, 0x70, 0x64, 0x76, 0x63, 0x74, 0x6d, 0x62, 0x77, 0x6b, 0x70, 0x6d, 0x6a,
+ 0x64, 0x72, 0x65, 0x61, 0x6e, 0x75, 0x62, 0x6a, 0x66, 0x6d, 0x6f, 0x6e, 0x74, 0x6a, 0x79, 0x67,
+ 0x73, 0x6e, 0x64, 0x74, 0x72, 0x79, 0x64, 0x6a, 0x79, 0x61, 0x61, 0x64, 0x62, 0x66, 0x6f, 0x72,
+ 0x70, 0x72, 0x75, 0x66, 0x77, 0x6e, 0x62, 0x6c, 0x73, 0x64, 0x66, 0x71, 0x69, 0x79, 0x66, 0x74,
+ 0x64, 0x61, 0x70, 0x67, 0x62, 0x69, 0x72, 0x71, 0x61, 0x78, 0x74, 0x77, 0x6b, 0x6f, 0x62, 0x77,
+ 0x6c, 0x75, 0x67, 0x78, 0x67, 0x61, 0x64, 0x76, 0x68, 0x72, 0x68, 0x71, 0x64, 0x63, 0x70, 0x69,
+ 0x6f, 0x75, 0x6e, 0x76, 0x78, 0x6d, 0x69, 0x68, 0x78, 0x62, 0x63, 0x72, 0x65, 0x69, 0x63, 0x72,
+ 0x71, 0x75, 0x71, 0x69, 0x74, 0x65, 0x6b, 0x71, 0x79, 0x6e, 0x6e, 0x74, 0x63, 0x70, 0x71, 0x6a,
+ 0x6a, 0x6e, 0x73, 0x6a, 0x6e, 0x66, 0x63, 0x66, 0x66, 0x63, 0x6f, 0x76, 0x69, 0x78, 0x75, 0x68,
+ 0x65, 0x74, 0x65, 0x6b, 0x68, 0x68, 0x6c, 0x66, 0x79, 0x6d, 0x64, 0x78, 0x6a, 0x69, 0x71, 0x6b,
+ 0x6b, 0x61, 0x69, 0x73, 0x67, 0x78, 0x6d, 0x73, 0x78, 0x6b, 0x62, 0x75, 0x71, 0x76, 0x79, 0x6b,
+ 0x61, 0x73, 0x6d, 0x75, 0x66, 0x63, 0x6f, 0x6c, 0x65, 0x6a, 0x6f, 0x78, 0x63, 0x78, 0x75, 0x6b,
+ 0x78, 0x6e, 0x6c, 0x71, 0x70, 0x75, 0x68, 0x68, 0x6b, 0x78, 0x77, 0x69, 0x73, 0x6c, 0x67, 0x6b,
+ 0x6d, 0x6f, 0x6b, 0x72, 0x77, 0x78, 0x73, 0x68, 0x63, 0x68, 0x69, 0x62, 0x77, 0x72, 0x77, 0x77,
+ 0x73, 0x66, 0x77, 0x6e, 0x68, 0x6a, 0x68, 0x73, 0x6c, 0x6c, 0x6d, 0x73, 0x67, 0x63, 0x64, 0x6d,
+ 0x68, 0x73, 0x69, 0x66, 0x78, 0x64, 0x6e, 0x74, 0x69, 0x71, 0x6f, 0x62, 0x68, 0x6f, 0x76, 0x71,
+ 0x65, 0x63, 0x70, 0x61, 0x68, 0x67, 0x72, 0x79, 0x71, 0x6e, 0x69, 0x68, 0x74, 0x61, 0x6c, 0x6b,
+ 0x6c, 0x6f, 0x63, 0x70, 0x69, 0x71, 0x6d, 0x63, 0x64, 0x6b, 0x79, 0x67, 0x72, 0x75, 0x65, 0x6b,
+ 0x75, 0x61, 0x76, 0x63, 0x61, 0x6d, 0x6e, 0x6d, 0x70, 0x6a, 0x77, 0x66, 0x75, 0x66, 0x68, 0x6d,
+ 0x71, 0x66, 0x6d, 0x6e, 0x64, 0x63, 0x65, 0x6c, 0x72, 0x6f, 0x6f, 0x62, 0x78, 0x6d, 0x67, 0x62,
+ 0x6a, 0x6d, 0x75, 0x67, 0x63, 0x63, 0x61, 0x78, 0x66, 0x78, 0x66, 0x78, 0x6d, 0x66, 0x79, 0x78,
+ 0x72, 0x65, 0x65, 0x79, 0x61, 0x79, 0x70, 0x73, 0x72, 0x73, 0x77, 0x69, 0x62, 0x62, 0x71, 0x61,
+ 0x76, 0x66, 0x73, 0x66, 0x66, 0x62, 0x63, 0x62, 0x6f, 0x63, 0x64, 0x63, 0x6d, 0x6d, 0x75, 0x6d,
+ 0x6f, 0x68, 0x62, 0x62, 0x6e, 0x76, 0x65, 0x6b, 0x6a, 0x69, 0x70, 0x63, 0x75, 0x68, 0x6f, 0x6e,
+ 0x6d, 0x71, 0x66, 0x75, 0x64, 0x79, 0x74, 0x69, 0x6d, 0x6e, 0x78, 0x6c, 0x73, 0x69, 0x73, 0x79,
+ 0x67, 0x6b, 0x6a, 0x6e, 0x63, 0x6f, 0x79, 0x66, 0x62, 0x69, 0x63, 0x71, 0x6c, 0x73, 0x73, 0x63,
+ 0x6c, 0x77, 0x64, 0x6d, 0x67, 0x72, 0x64, 0x6b, 0x75, 0x72, 0x69, 0x70, 0x68, 0x70, 0x62, 0x62,
+ 0x64, 0x78, 0x69, 0x78, 0x6f, 0x67, 0x6e, 0x6c, 0x77, 0x6f, 0x78, 0x65, 0x70, 0x70, 0x6a, 0x68,
+ 0x6e, 0x71, 0x64, 0x61, 0x6d, 0x63, 0x75, 0x75, 0x6e, 0x79, 0x66, 0x63, 0x6e, 0x70, 0x6f, 0x74,
+ 0x77, 0x75, 0x63, 0x6e, 0x71, 0x78, 0x67, 0x74, 0x79, 0x66, 0x71, 0x6b, 0x64, 0x74, 0x6c, 0x79,
+ 0x62, 0x78, 0x6d, 0x67, 0x6b, 0x6a, 0x67, 0x61, 0x63, 0x61, 0x6e, 0x73, 0x61, 0x6b, 0x66, 0x69,
+ 0x74, 0x6e, 0x70, 0x66, 0x6a, 0x66, 0x64, 0x73, 0x66, 0x74, 0x6a, 0x74, 0x63, 0x72, 0x63, 0x73,
+ 0x71, 0x76, 0x6c, 0x64, 0x69, 0x74, 0x6b, 0x73, 0x6c, 0x6d, 0x75, 0x66, 0x62, 0x79, 0x6d, 0x62,
+ 0x70, 0x77, 0x6f, 0x63, 0x6b, 0x69, 0x6c, 0x78, 0x69, 0x78, 0x66, 0x65, 0x68, 0x62, 0x6f, 0x67,
+ 0x65, 0x61, 0x71, 0x6f, 0x69, 0x71, 0x75, 0x74, 0x75, 0x62, 0x69, 0x79, 0x76, 0x78, 0x6a, 0x66,
+ 0x79, 0x6e, 0x6e, 0x73, 0x62, 0x63, 0x77, 0x74, 0x75, 0x71, 0x6d, 0x6d, 0x6d, 0x74, 0x73, 0x6a,
+ 0x78, 0x74, 0x64, 0x6d, 0x79, 0x6c, 0x6f, 0x66, 0x6d, 0x61, 0x6f, 0x70, 0x71, 0x76, 0x79, 0x70,
+ 0x67, 0x71, 0x6d, 0x69, 0x62, 0x66, 0x75, 0x79, 0x71, 0x61, 0x69, 0x6b, 0x68, 0x66, 0x62, 0x66,
+ 0x6c, 0x73, 0x6e, 0x71, 0x62, 0x6c, 0x6e, 0x6d, 0x63, 0x66, 0x73, 0x6d, 0x61, 0x69, 0x75, 0x6a,
+ 0x64, 0x61, 0x72, 0x71, 0x75, 0x73, 0x6d, 0x71, 0x79, 0x68, 0x61, 0x77, 0x74, 0x64, 0x69, 0x6d,
+ 0x6d, 0x61, 0x72, 0x72, 0x76, 0x64, 0x66, 0x70, 0x64, 0x6c, 0x73, 0x6c, 0x75, 0x74, 0x73, 0x66,
+ 0x75, 0x67, 0x77, 0x6d, 0x6f, 0x77, 0x6e, 0x68, 0x67, 0x66, 0x68, 0x6f, 0x6b, 0x68, 0x62, 0x73,
+ 0x64, 0x79, 0x73, 0x75, 0x69, 0x6f, 0x77, 0x71, 0x68, 0x6e, 0x66, 0x78, 0x6f, 0x66, 0x73, 0x68,
+ 0x74, 0x76, 0x6d, 0x6d, 0x67, 0x6d, 0x6d, 0x73, 0x65, 0x66, 0x6b, 0x6d, 0x69, 0x70, 0x72, 0x70,
+ 0x70, 0x70, 0x64, 0x71, 0x75, 0x74, 0x61, 0x78, 0x77, 0x64, 0x6f, 0x79, 0x63, 0x69, 0x6b, 0x64,
+ 0x67, 0x78, 0x6d, 0x65, 0x63, 0x66, 0x73, 0x6d, 0x70, 0x64, 0x77, 0x75, 0x66, 0x61, 0x77, 0x70,
+ 0x63, 0x66, 0x75, 0x65, 0x78, 0x6c, 0x77, 0x73, 0x62, 0x77, 0x75, 0x67, 0x64, 0x72, 0x61, 0x6b,
+ 0x6c, 0x6a, 0x68, 0x79, 0x6a, 0x6b, 0x6b, 0x70, 0x67, 0x74, 0x6b, 0x71, 0x72, 0x65, 0x78, 0x71,
+ 0x74, 0x64, 0x79, 0x79, 0x61, 0x69, 0x68, 0x6c, 0x68, 0x64, 0x67, 0x74, 0x64, 0x67, 0x64, 0x73,
+ 0x6d, 0x6a, 0x6c, 0x61, 0x70, 0x6e, 0x72, 0x62, 0x6d, 0x66, 0x62, 0x77, 0x6e, 0x6e, 0x74, 0x79,
+ 0x62, 0x6d, 0x77, 0x76, 0x65, 0x6c, 0x72, 0x76, 0x79, 0x75, 0x76, 0x78, 0x6f, 0x77, 0x61, 0x75,
+ 0x6c, 0x75, 0x63, 0x65, 0x79, 0x77, 0x69, 0x73, 0x62, 0x71, 0x6a, 0x6e, 0x6b, 0x72, 0x6c, 0x76,
+ 0x6e, 0x62, 0x64, 0x75, 0x62, 0x6e, 0x75, 0x6c, 0x65, 0x78, 0x72, 0x6c, 0x6a, 0x66, 0x6f, 0x6e,
+ 0x74, 0x68, 0x77, 0x64, 0x68, 0x6d, 0x6b, 0x6b, 0x6b, 0x6e, 0x71, 0x76, 0x6f, 0x71, 0x66, 0x75,
+ 0x68, 0x70, 0x73, 0x75, 0x6d, 0x6c, 0x6e, 0x6b, 0x74, 0x75, 0x79, 0x66, 0x69, 0x61, 0x79, 0x61,
+ 0x6d, 0x66, 0x71, 0x67, 0x63, 0x73, 0x63, 0x75, 0x76, 0x68, 0x70, 0x67, 0x78, 0x6f, 0x6c, 0x68,
+ 0x6e, 0x75, 0x69, 0x69, 0x75, 0x78, 0x6f, 0x6b, 0x67, 0x76, 0x79, 0x73, 0x61, 0x72, 0x74, 0x62,
+ 0x64, 0x61, 0x63, 0x64, 0x63, 0x77, 0x6e, 0x6b, 0x78, 0x65, 0x70, 0x73, 0x6b, 0x64, 0x6a, 0x64,
+ 0x76, 0x6b, 0x74, 0x6e, 0x6b, 0x67, 0x64, 0x78, 0x65, 0x71, 0x68, 0x68, 0x74, 0x62, 0x79, 0x75,
+ 0x73, 0x6a, 0x69, 0x72, 0x64, 0x75, 0x61, 0x66, 0x70, 0x66, 0x71, 0x6d, 0x6b, 0x63, 0x62, 0x71,
+ 0x66, 0x65, 0x68, 0x78, 0x77, 0x65, 0x62, 0x78, 0x66, 0x72, 0x67, 0x78, 0x6d, 0x75, 0x6d, 0x75,
+ 0x6a, 0x72, 0x68, 0x6d, 0x71, 0x68, 0x63, 0x6e, 0x76, 0x63, 0x79, 0x66, 0x73, 0x69, 0x6e, 0x6f,
+ 0x70, 0x66, 0x70, 0x62, 0x66, 0x63, 0x6a, 0x61, 0x72, 0x69, 0x79, 0x69, 0x69, 0x74, 0x76, 0x61,
+ 0x61, 0x67, 0x68, 0x72, 0x62, 0x61, 0x64, 0x6b, 0x66, 0x6d, 0x6e, 0x79, 0x79, 0x67, 0x68, 0x79,
+ 0x76, 0x64, 0x65, 0x65, 0x61, 0x6b, 0x6a, 0x6f, 0x6d, 0x6e, 0x63, 0x68, 0x6e, 0x76, 0x71, 0x67,
+ 0x6b, 0x74, 0x70, 0x68, 0x73, 0x77, 0x6c, 0x76, 0x67, 0x6a, 0x77, 0x79, 0x77, 0x74, 0x68, 0x6a,
+ 0x70, 0x6e, 0x6f, 0x62, 0x6f, 0x62, 0x6c, 0x6d, 0x63, 0x6f, 0x77, 0x6e, 0x75, 0x6c, 0x6a, 0x61,
+ 0x68, 0x71, 0x76, 0x64, 0x78, 0x66, 0x66, 0x67, 0x65, 0x6e, 0x74, 0x6e, 0x77, 0x65, 0x6c, 0x67,
+ 0x62, 0x6e, 0x62, 0x6d, 0x6b, 0x69, 0x6c, 0x67, 0x74, 0x6e, 0x63, 0x65, 0x70, 0x74, 0x6d, 0x71,
+ 0x6c, 0x65, 0x6d, 0x74, 0x62, 0x62, 0x6f, 0x64, 0x61, 0x61, 0x70, 0x6a, 0x79, 0x62, 0x62, 0x77,
+ 0x6d, 0x70, 0x79, 0x70, 0x67, 0x69, 0x76, 0x75, 0x66, 0x70, 0x6a, 0x6c, 0x62, 0x71, 0x61, 0x6b,
+ 0x77, 0x68, 0x6d, 0x6b, 0x73, 0x73, 0x68, 0x62, 0x70, 0x71, 0x71, 0x6b, 0x70, 0x68, 0x75, 0x63,
+ 0x77, 0x63, 0x76, 0x62, 0x6a, 0x66, 0x61, 0x77, 0x6f, 0x76, 0x64, 0x75, 0x72, 0x64, 0x6a, 0x70,
+ 0x75, 0x76, 0x65, 0x68, 0x68, 0x63, 0x6c, 0x6f, 0x78, 0x6b, 0x66, 0x68, 0x68, 0x71, 0x79, 0x74,
+ 0x76, 0x68, 0x77, 0x76, 0x79, 0x75, 0x6b, 0x79, 0x61, 0x64, 0x69, 0x6f, 0x6e, 0x68, 0x6d, 0x75,
+ 0x63, 0x71, 0x6b, 0x77, 0x65, 0x68, 0x72, 0x63, 0x64, 0x64, 0x6a, 0x62, 0x6e, 0x6d, 0x72, 0x77,
+ 0x74, 0x71, 0x67, 0x6d, 0x68, 0x61, 0x63, 0x65, 0x6c, 0x64, 0x64, 0x71, 0x75, 0x62, 0x67, 0x69,
+ 0x6d, 0x76, 0x68, 0x62, 0x71, 0x74, 0x65, 0x62, 0x61, 0x6f, 0x70, 0x78, 0x6f, 0x65, 0x78, 0x71,
+ 0x67, 0x77, 0x70, 0x66, 0x65, 0x72, 0x69, 0x79, 0x72, 0x66, 0x62, 0x79, 0x66, 0x62, 0x6d, 0x6b,
+ 0x6b, 0x6b, 0x78, 0x6a, 0x68, 0x66, 0x6f, 0x77, 0x70, 0x75, 0x70, 0x75, 0x78, 0x6d, 0x63, 0x74,
+ 0x61, 0x78, 0x77, 0x65, 0x63, 0x68, 0x6f, 0x6f, 0x74, 0x79, 0x73, 0x6a, 0x6b, 0x6b, 0x67, 0x70,
+ 0x74, 0x77, 0x76, 0x73, 0x75, 0x6a, 0x74, 0x66, 0x6a, 0x73, 0x64, 0x6f, 0x6c, 0x73, 0x6e, 0x75,
+ 0x77, 0x6d, 0x71, 0x71, 0x63, 0x6f, 0x62, 0x75, 0x62, 0x72, 0x72, 0x66, 0x71, 0x63, 0x71, 0x6f,
+ 0x67, 0x71, 0x65, 0x76, 0x71, 0x70, 0x6f, 0x79, 0x73, 0x69, 0x67, 0x64, 0x71, 0x68, 0x73, 0x79,
+ 0x74, 0x68, 0x70, 0x6d, 0x6e, 0x74, 0x6e, 0x79, 0x79, 0x62, 0x65, 0x6f, 0x74, 0x75, 0x61, 0x6b,
+ 0x69, 0x6e, 0x70, 0x6e, 0x70, 0x6b, 0x70, 0x72, 0x78, 0x63, 0x6c, 0x66, 0x67, 0x71, 0x6e, 0x72,
+ 0x65, 0x69, 0x72, 0x6e, 0x75, 0x71, 0x61, 0x6e, 0x64, 0x6b, 0x6a, 0x69, 0x63, 0x62, 0x76, 0x62,
+ 0x6f, 0x61, 0x74, 0x72, 0x66, 0x6b, 0x67, 0x63, 0x6d, 0x66, 0x73, 0x64, 0x65, 0x72, 0x65, 0x73,
+ 0x76, 0x70, 0x62, 0x62, 0x70, 0x71, 0x70, 0x66, 0x78, 0x62, 0x6b, 0x6f, 0x63, 0x6e, 0x74, 0x63,
+ 0x61, 0x63, 0x78, 0x74, 0x79, 0x6e, 0x77, 0x64, 0x6a, 0x73, 0x64, 0x72, 0x6a, 0x68, 0x6a, 0x68,
+ 0x64, 0x62, 0x67, 0x68, 0x6a, 0x75, 0x78, 0x74, 0x73, 0x70, 0x6d, 0x65, 0x75, 0x71, 0x61, 0x6f,
+ 0x6c, 0x78, 0x67, 0x79, 0x66, 0x71, 0x64, 0x75, 0x79, 0x75, 0x77, 0x67, 0x78, 0x69, 0x70, 0x70,
+ 0x74, 0x6d, 0x62, 0x70, 0x68, 0x71, 0x75, 0x6d, 0x67, 0x73, 0x79, 0x64, 0x74, 0x72, 0x67, 0x74,
+ 0x70, 0x6b, 0x67, 0x78, 0x66, 0x6c, 0x77, 0x71, 0x78, 0x6c, 0x75, 0x66, 0x69, 0x67, 0x69, 0x67,
+ 0x79, 0x6c, 0x70, 0x71, 0x6b, 0x6c, 0x67, 0x74, 0x79, 0x79, 0x78, 0x79, 0x63, 0x73, 0x79, 0x6c,
+ 0x62, 0x62, 0x78, 0x62, 0x74, 0x75, 0x61, 0x6d, 0x73, 0x6d, 0x61, 0x64, 0x78, 0x6d, 0x61, 0x6b,
+ 0x70, 0x77, 0x67, 0x63, 0x65, 0x67, 0x62, 0x78, 0x70, 0x6c, 0x6b, 0x63, 0x78, 0x63, 0x66, 0x79,
+ 0x62, 0x6c, 0x73, 0x65, 0x6c, 0x6d, 0x70, 0x67, 0x69, 0x67, 0x6d, 0x61, 0x79, 0x6b, 0x76, 0x66,
+ 0x66, 0x76, 0x6e, 0x62, 0x66, 0x61, 0x77, 0x67, 0x6c, 0x61, 0x6f, 0x61, 0x62, 0x75, 0x64, 0x72,
+ 0x77, 0x76, 0x6d, 0x65, 0x77, 0x65, 0x67, 0x73, 0x64, 0x73, 0x77, 0x68, 0x73, 0x69, 0x74, 0x6a,
+ 0x65, 0x62, 0x77, 0x73, 0x6a, 0x61, 0x64, 0x73, 0x6f, 0x65, 0x75, 0x76, 0x61, 0x63, 0x78, 0x64,
+ 0x69, 0x79, 0x6d, 0x68, 0x69, 0x65, 0x6b, 0x69, 0x6b, 0x62, 0x6d, 0x6d, 0x73, 0x6f, 0x61, 0x70,
+ 0x79, 0x67, 0x6d, 0x6c, 0x61, 0x72, 0x70, 0x79, 0x66, 0x78, 0x61, 0x66, 0x71, 0x74, 0x6c, 0x73,
+ 0x62, 0x70, 0x61, 0x72, 0x6e, 0x78, 0x77, 0x69, 0x77, 0x6a, 0x62, 0x75, 0x6f, 0x6d, 0x67, 0x79,
+ 0x71, 0x63, 0x61, 0x76, 0x77, 0x75, 0x6b, 0x65, 0x72, 0x72, 0x63, 0x65, 0x65, 0x64, 0x71, 0x74,
+ 0x78, 0x77, 0x6f, 0x70, 0x71, 0x63, 0x77, 0x63, 0x75, 0x66, 0x66, 0x6d, 0x67, 0x61, 0x76, 0x77,
+ 0x69, 0x64, 0x72, 0x6f, 0x73, 0x69, 0x6d, 0x69, 0x71, 0x74, 0x64, 0x71, 0x63, 0x72, 0x6d, 0x75,
+ 0x77, 0x63, 0x77, 0x67, 0x77, 0x64, 0x6d, 0x6f, 0x72, 0x6f, 0x6d, 0x6b, 0x62, 0x63, 0x70, 0x6f,
+ 0x72, 0x62, 0x61, 0x71, 0x65, 0x67, 0x64, 0x76, 0x78, 0x63, 0x66, 0x66, 0x71, 0x6f, 0x69, 0x73,
+ 0x6b, 0x72, 0x67, 0x6e, 0x77, 0x77, 0x79, 0x73, 0x69, 0x79, 0x6a, 0x6d, 0x72, 0x74, 0x79, 0x68,
+ 0x72, 0x6e, 0x70, 0x61, 0x6d, 0x67, 0x66, 0x74, 0x67, 0x6d, 0x73, 0x72, 0x79, 0x62, 0x6d, 0x6f,
+ 0x64, 0x63, 0x6f, 0x67, 0x70, 0x69, 0x79, 0x71, 0x68, 0x72, 0x79, 0x63, 0x6e, 0x77, 0x6b, 0x66,
+ 0x67, 0x73, 0x65, 0x71, 0x72, 0x68, 0x64, 0x73, 0x78, 0x6f, 0x64, 0x66, 0x78, 0x68, 0x66, 0x65,
+ 0x64, 0x78, 0x72, 0x61, 0x68, 0x74, 0x79, 0x6f, 0x6f, 0x6a, 0x63, 0x6a, 0x66, 0x6b, 0x64, 0x71,
+ 0x6f, 0x66, 0x6b, 0x67, 0x6c, 0x6c, 0x6c, 0x73, 0x61, 0x6f, 0x78, 0x73, 0x71, 0x62, 0x6e, 0x65,
+ 0x74, 0x67, 0x66, 0x6f, 0x6c, 0x77, 0x6a, 0x67, 0x6d, 0x61, 0x77, 0x77, 0x73, 0x73, 0x61, 0x6c,
+ 0x79, 0x6a, 0x78, 0x62, 0x74, 0x62, 0x62, 0x62, 0x70, 0x64, 0x6e, 0x6e, 0x62, 0x63, 0x79, 0x62,
+ 0x61, 0x6c, 0x74, 0x6e, 0x67, 0x77, 0x63, 0x75, 0x71, 0x61, 0x63, 0x67, 0x6f, 0x71, 0x63, 0x67,
+ 0x76, 0x73, 0x63, 0x66, 0x74, 0x68, 0x63, 0x6c, 0x66, 0x6a, 0x71, 0x69, 0x76, 0x6c, 0x72, 0x62,
+ 0x72, 0x67, 0x61, 0x6f, 0x72, 0x71, 0x64, 0x74, 0x64, 0x71, 0x6b, 0x68, 0x63, 0x6b, 0x67, 0x62,
+ 0x68, 0x6b, 0x79, 0x76, 0x70, 0x71, 0x76, 0x62, 0x72, 0x72, 0x74, 0x61, 0x6f, 0x67, 0x6f, 0x79,
+ 0x74, 0x69, 0x62, 0x72, 0x65, 0x61, 0x73, 0x61, 0x6b, 0x72, 0x6d, 0x73, 0x79, 0x78, 0x63, 0x62,
+ 0x70, 0x68, 0x63, 0x73, 0x77, 0x65, 0x61, 0x71, 0x79, 0x70, 0x71, 0x6a, 0x67, 0x70, 0x6c, 0x62,
+ 0x6f, 0x79, 0x62, 0x72, 0x73, 0x61, 0x75, 0x77, 0x74, 0x79, 0x70, 0x76, 0x64, 0x6c, 0x71, 0x61,
+ 0x67, 0x68, 0x76, 0x66, 0x71, 0x75, 0x72, 0x69, 0x71, 0x66, 0x6a, 0x78, 0x66, 0x6d, 0x6f, 0x75,
+ 0x6d, 0x6a, 0x61, 0x75, 0x6c, 0x6f, 0x71, 0x75, 0x6d, 0x70, 0x67, 0x79, 0x69, 0x61, 0x65, 0x72,
+ 0x6c, 0x6c, 0x71, 0x68, 0x72, 0x75, 0x65, 0x61, 0x77, 0x69, 0x73, 0x63, 0x6d, 0x75, 0x67, 0x69,
+ 0x76, 0x6b, 0x6e, 0x78, 0x70, 0x64, 0x6a, 0x6c, 0x67, 0x6e, 0x6e, 0x69, 0x77, 0x67, 0x67, 0x6e,
+ 0x66, 0x76, 0x61, 0x72, 0x6b, 0x61, 0x78, 0x74, 0x66, 0x76, 0x6e, 0x68, 0x6b, 0x66, 0x64, 0x66,
+ 0x72, 0x6b, 0x68, 0x74, 0x6f, 0x6b, 0x77, 0x73, 0x77, 0x6f, 0x6d, 0x70, 0x61, 0x76, 0x78, 0x76,
+ 0x6c, 0x71, 0x6f, 0x75, 0x79, 0x67, 0x68, 0x68, 0x68, 0x77, 0x68, 0x68, 0x62, 0x6d, 0x6d, 0x74,
+ 0x6f, 0x78, 0x6e, 0x62, 0x6e, 0x65, 0x77, 0x74, 0x79, 0x62, 0x67, 0x79, 0x65, 0x61, 0x72, 0x78,
+ 0x61, 0x73, 0x6e, 0x6e, 0x77, 0x6c, 0x6a, 0x77, 0x62, 0x70, 0x68, 0x64, 0x62, 0x79, 0x65, 0x73,
+ 0x6d, 0x77, 0x77, 0x78, 0x78, 0x69, 0x65, 0x6e, 0x75, 0x71, 0x79, 0x6b, 0x73, 0x6e, 0x75, 0x69,
+ 0x77, 0x69, 0x65, 0x75, 0x6f, 0x79, 0x79, 0x6c, 0x6b, 0x74, 0x74, 0x63, 0x67, 0x67, 0x75, 0x71,
+ 0x74, 0x73, 0x76, 0x69, 0x68, 0x6d, 0x67, 0x67, 0x74, 0x62, 0x69, 0x6d, 0x6b, 0x78, 0x77, 0x68,
+ 0x66, 0x61, 0x6a, 0x67, 0x6f, 0x65, 0x62, 0x69, 0x67, 0x64, 0x78, 0x6b, 0x75, 0x6a, 0x67, 0x72,
+ 0x76, 0x64, 0x6f, 0x72, 0x63, 0x63, 0x76, 0x66, 0x6b, 0x6c, 0x62, 0x64, 0x70, 0x63, 0x68, 0x70,
+ 0x74, 0x71, 0x61, 0x77, 0x6b, 0x78, 0x68, 0x6c, 0x77, 0x63, 0x76, 0x71, 0x6c, 0x69, 0x6a, 0x6c,
+ 0x70, 0x75, 0x78, 0x68, 0x62, 0x77, 0x61, 0x63, 0x77, 0x6c, 0x66, 0x77, 0x79, 0x64, 0x79, 0x79,
+ 0x76, 0x75, 0x77, 0x6e, 0x6d, 0x72, 0x66, 0x6b, 0x74, 0x6b, 0x62, 0x67, 0x76, 0x66, 0x74, 0x74,
+ 0x6d, 0x68, 0x6e, 0x6a, 0x77, 0x6c, 0x6c, 0x75, 0x6b, 0x65, 0x74, 0x6d, 0x62, 0x6c, 0x61, 0x70,
+ 0x75, 0x67, 0x6f, 0x67, 0x6d, 0x64, 0x76, 0x72, 0x6b, 0x6a, 0x65, 0x6e, 0x6c, 0x63, 0x71, 0x6b,
+ 0x6a, 0x64, 0x6f, 0x74, 0x67, 0x70, 0x69, 0x6e, 0x62, 0x72, 0x66, 0x65, 0x66, 0x65, 0x6e, 0x74,
+ 0x64, 0x6a, 0x78, 0x71, 0x61, 0x62, 0x61, 0x66, 0x6a, 0x63, 0x73, 0x66, 0x66, 0x79, 0x69, 0x6c,
+ 0x74, 0x6e, 0x78, 0x69, 0x6b, 0x6c, 0x62, 0x6f, 0x6c, 0x65, 0x61, 0x74, 0x65, 0x65, 0x66, 0x70,
+ 0x74, 0x71, 0x64, 0x67, 0x76, 0x69, 0x79, 0x6f, 0x6a, 0x74, 0x63, 0x62, 0x76, 0x71, 0x6a, 0x6e,
+ 0x78, 0x6b, 0x75, 0x67, 0x6c, 0x68, 0x63, 0x76, 0x63, 0x73, 0x6b, 0x6d, 0x69, 0x62, 0x72, 0x72,
+ 0x70, 0x64, 0x79, 0x79, 0x63, 0x6c, 0x70, 0x72, 0x6a, 0x79, 0x76, 0x74, 0x67, 0x6f, 0x6a, 0x67,
+ 0x68, 0x63, 0x69, 0x69, 0x78, 0x65, 0x77, 0x64, 0x6e, 0x6f, 0x78, 0x72, 0x68, 0x79, 0x68, 0x6f,
+ 0x6a, 0x62, 0x72, 0x75, 0x70, 0x6b, 0x75, 0x76, 0x6c, 0x69, 0x6b, 0x6d, 0x6d, 0x65, 0x73, 0x62,
+ 0x69, 0x68, 0x76, 0x78, 0x61, 0x6c, 0x6e, 0x61, 0x72, 0x72, 0x75, 0x75, 0x70, 0x78, 0x71, 0x79,
+ 0x74, 0x6e, 0x73, 0x6a, 0x75, 0x73, 0x76, 0x67, 0x6d, 0x6b, 0x68, 0x75, 0x77, 0x72, 0x79, 0x62,
+ 0x68, 0x6d, 0x77, 0x6b, 0x6b, 0x61, 0x73, 0x75, 0x6a, 0x74, 0x72, 0x66, 0x68, 0x63, 0x70, 0x79,
+ 0x69, 0x66, 0x6c, 0x67, 0x71, 0x72, 0x67, 0x76, 0x73, 0x63, 0x67, 0x76, 0x68, 0x6e, 0x63, 0x6e,
+ 0x75, 0x63, 0x69, 0x70, 0x6e, 0x78, 0x66, 0x66, 0x75, 0x6b, 0x67, 0x79, 0x6d, 0x6d, 0x70, 0x6e,
+ 0x6e, 0x6e, 0x6a, 0x69, 0x76, 0x63, 0x6c, 0x76, 0x75, 0x79, 0x61, 0x79, 0x74, 0x65, 0x71, 0x67,
+ 0x62, 0x64, 0x62, 0x67, 0x6f, 0x6b, 0x6c, 0x68, 0x61, 0x6d, 0x73, 0x74, 0x70, 0x6b, 0x62, 0x63,
+ 0x75, 0x66, 0x6a, 0x6a, 0x6a, 0x6a, 0x64, 0x76, 0x6b, 0x67, 0x73, 0x75, 0x69, 0x73, 0x6a, 0x6e,
+ 0x6a, 0x68, 0x6d, 0x72, 0x73, 0x71, 0x73, 0x75, 0x6a, 0x6d, 0x69, 0x75, 0x66, 0x71, 0x67, 0x75,
+ 0x73, 0x70, 0x71, 0x61, 0x69, 0x71, 0x74, 0x71, 0x65, 0x6b, 0x71, 0x6f, 0x77, 0x68, 0x6a, 0x77,
+ 0x74, 0x79, 0x6f, 0x74, 0x69, 0x62, 0x70, 0x70, 0x65, 0x6d, 0x6d, 0x77, 0x6d, 0x72, 0x62, 0x67,
+ 0x62, 0x74, 0x61, 0x76, 0x79, 0x6b, 0x79, 0x6d, 0x75, 0x71, 0x6e, 0x6e, 0x76, 0x78, 0x72, 0x70,
+ 0x62, 0x64, 0x70, 0x6c, 0x6e, 0x79, 0x6c, 0x68, 0x70, 0x65, 0x67, 0x6d, 0x65, 0x75, 0x76, 0x62,
+ 0x63, 0x70, 0x6b, 0x75, 0x66, 0x68, 0x68, 0x6f, 0x70, 0x66, 0x68, 0x73, 0x6e, 0x78, 0x75, 0x68,
+ 0x6a, 0x6e, 0x61, 0x78, 0x6e, 0x6b, 0x78, 0x6e, 0x71, 0x66, 0x75, 0x68, 0x62, 0x79, 0x6f, 0x6c,
+ 0x66, 0x67, 0x67, 0x6c, 0x6c, 0x75, 0x62, 0x79, 0x6b, 0x69, 0x71, 0x69, 0x75, 0x72, 0x71, 0x6a,
+ 0x70, 0x76, 0x70, 0x6d, 0x73, 0x70, 0x72, 0x61, 0x6b, 0x77, 0x78, 0x78, 0x6e, 0x73, 0x75, 0x75,
+ 0x6f, 0x68, 0x63, 0x6c, 0x75, 0x75, 0x6d, 0x72, 0x70, 0x75, 0x63, 0x63, 0x63, 0x6b, 0x6a, 0x79,
+ 0x6d, 0x61, 0x73, 0x62, 0x79, 0x75, 0x6b, 0x6d, 0x6a, 0x6f, 0x67, 0x61, 0x62, 0x6a, 0x6e, 0x71,
+ 0x69, 0x71, 0x6a, 0x6a, 0x61, 0x68, 0x68, 0x76, 0x61, 0x6a, 0x72, 0x6b, 0x76, 0x74, 0x6f, 0x61,
+ 0x70, 0x77, 0x6b, 0x67, 0x73, 0x75, 0x70, 0x62, 0x6d, 0x65, 0x67, 0x6e, 0x62, 0x79, 0x74, 0x74,
+ 0x72, 0x61, 0x6b, 0x76, 0x64, 0x69, 0x69, 0x69, 0x6c, 0x68, 0x78, 0x65, 0x70, 0x68, 0x79, 0x70,
+ 0x65, 0x72, 0x64, 0x68, 0x68, 0x6c, 0x61, 0x6f, 0x74, 0x62, 0x78, 0x6c, 0x72, 0x6c, 0x62, 0x75,
+ 0x6c, 0x64, 0x72, 0x64, 0x79, 0x6b, 0x72, 0x72, 0x62, 0x6d, 0x74, 0x72, 0x6b, 0x78, 0x6c, 0x75,
+ 0x78, 0x6a, 0x71, 0x67, 0x72, 0x79, 0x76, 0x71, 0x75, 0x6d, 0x75, 0x67, 0x6a, 0x63, 0x66, 0x6f,
+ 0x63, 0x66, 0x79, 0x74, 0x74, 0x70, 0x75, 0x6a, 0x6e, 0x64, 0x64, 0x6e, 0x68, 0x6e, 0x6e, 0x78,
+ 0x6b, 0x68, 0x72, 0x74, 0x6c, 0x6d, 0x73, 0x74, 0x65, 0x72, 0x6c, 0x68, 0x78, 0x72, 0x66, 0x70,
+ 0x79, 0x6d, 0x61, 0x61, 0x76, 0x77, 0x64, 0x6e, 0x77, 0x74, 0x67, 0x6f, 0x74, 0x6f, 0x6b, 0x77,
+ 0x69, 0x79, 0x79, 0x73, 0x72, 0x71, 0x63, 0x62, 0x69, 0x78, 0x75, 0x72, 0x62, 0x71, 0x6f, 0x6e,
+ 0x66, 0x67, 0x6c, 0x72, 0x66, 0x6b, 0x61, 0x6e, 0x70, 0x70, 0x71, 0x65, 0x68, 0x71, 0x6e, 0x79,
+ 0x75, 0x75, 0x75, 0x67, 0x64, 0x67, 0x75, 0x74, 0x6a, 0x6b, 0x6a, 0x65, 0x71, 0x75, 0x73, 0x6d,
+ 0x77, 0x78, 0x72, 0x6e, 0x71, 0x61, 0x73, 0x63, 0x75, 0x78, 0x6c, 0x6c, 0x78, 0x6a, 0x75, 0x68,
+ 0x65, 0x71, 0x77, 0x6a, 0x76, 0x6d, 0x6d, 0x6e, 0x6a, 0x6e, 0x61, 0x65, 0x64, 0x72, 0x6c, 0x74,
+ 0x76, 0x63, 0x69, 0x65, 0x77, 0x70, 0x61, 0x69, 0x65, 0x74, 0x6d, 0x74, 0x68, 0x67, 0x6f, 0x65,
+ 0x62, 0x69, 0x63, 0x77, 0x77, 0x61, 0x75, 0x76, 0x69, 0x69, 0x77, 0x6f, 0x66, 0x6b, 0x77, 0x65,
+ 0x77, 0x6c, 0x6b, 0x69, 0x6a, 0x6a, 0x6c, 0x67, 0x76, 0x66, 0x74, 0x75, 0x72, 0x6c, 0x66, 0x6a,
+ 0x64, 0x67, 0x62, 0x73, 0x65, 0x75, 0x6e, 0x75, 0x6a, 0x6a, 0x69, 0x6a, 0x70, 0x63, 0x6f, 0x6c,
+ 0x69, 0x6b, 0x69, 0x6e, 0x79, 0x69, 0x70, 0x71, 0x66, 0x6b, 0x71, 0x71, 0x6e, 0x73, 0x66, 0x77,
+ 0x66, 0x73, 0x72, 0x77, 0x70, 0x74, 0x66, 0x71, 0x69, 0x6b, 0x74, 0x64, 0x61, 0x79, 0x65, 0x6e,
+ 0x6d, 0x6f, 0x6b, 0x78, 0x61, 0x70, 0x76, 0x74, 0x63, 0x78, 0x76, 0x63, 0x75, 0x67, 0x77, 0x62,
+ 0x63, 0x6a, 0x62, 0x79, 0x70, 0x61, 0x6e, 0x6c, 0x72, 0x6f, 0x6c, 0x6a, 0x63, 0x79, 0x75, 0x65,
+ 0x72, 0x72, 0x6e, 0x76, 0x64, 0x73, 0x77, 0x6b, 0x6d, 0x75, 0x67, 0x74, 0x75, 0x6f, 0x73, 0x67,
+ 0x6b, 0x61, 0x6a, 0x68, 0x69, 0x66, 0x6f, 0x64, 0x6f, 0x6e, 0x6a, 0x68, 0x61, 0x75, 0x79, 0x76,
+ 0x69, 0x70, 0x69, 0x6b, 0x6f, 0x71, 0x76, 0x6a, 0x77, 0x63, 0x68, 0x6f, 0x6e, 0x64, 0x71, 0x62,
+ 0x6d, 0x67, 0x76, 0x78, 0x70, 0x67, 0x6f, 0x67, 0x79, 0x78, 0x68, 0x73, 0x6a, 0x6f, 0x6b, 0x67,
+ 0x74, 0x6a, 0x77, 0x71, 0x77, 0x72, 0x70, 0x6c, 0x69, 0x78, 0x73, 0x72, 0x72, 0x71, 0x67, 0x6d,
+ 0x63, 0x6c, 0x6a, 0x72, 0x77, 0x61, 0x6b, 0x66, 0x63, 0x6c, 0x71, 0x78, 0x70, 0x6f, 0x70, 0x76,
+ 0x79, 0x61, 0x79, 0x6f, 0x6d, 0x61, 0x6b, 0x73, 0x6f, 0x6a, 0x64, 0x78, 0x6d, 0x6e, 0x67, 0x6e,
+ 0x74, 0x67, 0x74, 0x6a, 0x74, 0x6e, 0x79, 0x79, 0x6c, 0x63, 0x79, 0x6b, 0x6f, 0x6c, 0x69, 0x71,
+ 0x67, 0x75, 0x61, 0x62, 0x6b, 0x6d, 0x66, 0x71, 0x65, 0x79, 0x63, 0x6b, 0x79, 0x6e, 0x76, 0x6c,
+ 0x77, 0x75, 0x61, 0x66, 0x63, 0x65, 0x77, 0x75, 0x6a, 0x78, 0x75, 0x6f, 0x6a, 0x61, 0x61, 0x6d,
+ 0x69, 0x68, 0x77, 0x73, 0x79, 0x64, 0x77, 0x6f, 0x77, 0x76, 0x76, 0x6d, 0x69, 0x67, 0x78, 0x6f,
+ 0x76, 0x6a, 0x73, 0x65, 0x77, 0x6e, 0x70, 0x6a, 0x78, 0x75, 0x77, 0x79, 0x62, 0x6e, 0x76, 0x63,
+ 0x61, 0x76, 0x69, 0x6a, 0x68, 0x74, 0x78, 0x69, 0x78, 0x71, 0x6f, 0x6d, 0x76, 0x6f, 0x6d, 0x64,
+ 0x6d, 0x73, 0x6c, 0x65, 0x68, 0x6c, 0x71, 0x6d, 0x61, 0x66, 0x69, 0x78, 0x6e, 0x6b, 0x64, 0x79,
+ 0x62, 0x70, 0x6d, 0x66, 0x6d, 0x74, 0x6e, 0x70, 0x67, 0x6c, 0x6e, 0x68, 0x6c, 0x6a, 0x71, 0x79,
+ 0x66, 0x67, 0x68, 0x73, 0x6f, 0x72, 0x73, 0x6a, 0x77, 0x73, 0x65, 0x67, 0x62, 0x75, 0x69, 0x6d,
+ 0x71, 0x67, 0x73, 0x73, 0x72, 0x6b, 0x6e, 0x70, 0x6f, 0x68, 0x62, 0x72, 0x6c, 0x74, 0x66, 0x72,
+ 0x77, 0x72, 0x6d, 0x67, 0x75, 0x74, 0x77, 0x62, 0x72, 0x73, 0x68, 0x6e, 0x70, 0x65, 0x64, 0x6b,
+ 0x6f, 0x65, 0x76, 0x6c, 0x6f, 0x69, 0x72, 0x72, 0x77, 0x62, 0x63, 0x61, 0x6e, 0x6e, 0x72, 0x73,
+ 0x62, 0x6d, 0x6b, 0x69, 0x76, 0x6d, 0x71, 0x62, 0x61, 0x6b, 0x6a, 0x62, 0x6d, 0x79, 0x6d, 0x78,
+ 0x6a, 0x70, 0x68, 0x61, 0x65, 0x76, 0x66, 0x71, 0x64, 0x6c, 0x65, 0x77, 0x72, 0x66, 0x71, 0x6f,
+ 0x69, 0x71, 0x79, 0x70, 0x72, 0x6d, 0x6c, 0x69, 0x70, 0x69, 0x72, 0x64, 0x61, 0x79, 0x74, 0x6a,
+ 0x61, 0x79, 0x72, 0x6c, 0x74, 0x68, 0x66, 0x72, 0x72, 0x67, 0x63, 0x68, 0x6d, 0x77, 0x64, 0x71,
+ 0x71, 0x75, 0x69, 0x6d, 0x6e, 0x69, 0x72, 0x76, 0x6e, 0x6d, 0x72, 0x78, 0x75, 0x73, 0x66, 0x72,
+ 0x6b, 0x77, 0x6b, 0x77, 0x77, 0x72, 0x64, 0x77, 0x74, 0x76, 0x6e, 0x66, 0x62, 0x6e, 0x69, 0x77,
+ 0x62, 0x6f, 0x62, 0x73, 0x67, 0x79, 0x69, 0x63, 0x79, 0x63, 0x73, 0x64, 0x78, 0x76, 0x70, 0x6b,
+ 0x6f, 0x77, 0x72, 0x61, 0x6f, 0x78, 0x79, 0x67, 0x68, 0x76, 0x61, 0x6f, 0x63, 0x78, 0x6e, 0x6c,
+ 0x69, 0x73, 0x6c, 0x79, 0x74, 0x79, 0x67, 0x79, 0x79, 0x77, 0x77, 0x78, 0x65, 0x75, 0x6b, 0x65,
+ 0x65, 0x69, 0x77, 0x6c, 0x66, 0x65, 0x75, 0x68, 0x78, 0x66, 0x62, 0x6c, 0x75, 0x67, 0x67, 0x70,
+ 0x69, 0x6c, 0x79, 0x71, 0x77, 0x69, 0x6f, 0x79, 0x6d, 0x69, 0x6d, 0x79, 0x71, 0x66, 0x63, 0x75,
+ 0x69, 0x69, 0x76, 0x72, 0x75, 0x69, 0x6b, 0x64, 0x62, 0x70, 0x64, 0x78, 0x79, 0x69, 0x64, 0x72,
+ 0x68, 0x73, 0x6d, 0x68, 0x6c, 0x72, 0x66, 0x6a, 0x6a, 0x6f, 0x66, 0x67, 0x6d, 0x6f, 0x66, 0x74,
+ 0x62, 0x74, 0x70, 0x61, 0x71, 0x70, 0x70, 0x68, 0x77, 0x73, 0x78, 0x65, 0x61, 0x6f, 0x6e, 0x6c,
+ 0x61, 0x71, 0x6f, 0x66, 0x68, 0x76, 0x76, 0x66, 0x79, 0x70, 0x6c, 0x6d, 0x64, 0x6d, 0x76, 0x75,
+ 0x6d, 0x6e, 0x73, 0x6d, 0x67, 0x62, 0x78, 0x75, 0x6c, 0x6a, 0x6b, 0x74, 0x78, 0x63, 0x67, 0x65,
+ 0x62, 0x63, 0x66, 0x67, 0x6d, 0x77, 0x79, 0x66, 0x77, 0x72, 0x79, 0x6a, 0x64, 0x76, 0x78, 0x78,
+ 0x6b, 0x66, 0x6f, 0x6c, 0x6e, 0x6d, 0x6b, 0x73, 0x71, 0x6d, 0x68, 0x68, 0x74, 0x6a, 0x75, 0x6b,
+ 0x63, 0x77, 0x66, 0x6b, 0x6e, 0x69, 0x66, 0x75, 0x6e, 0x75, 0x6c, 0x67, 0x66, 0x6e, 0x6f, 0x73,
+ 0x6e, 0x73, 0x73, 0x77, 0x75, 0x71, 0x72, 0x76, 0x68, 0x64, 0x6a, 0x6c, 0x61, 0x66, 0x6b, 0x6d,
+ 0x6e, 0x63, 0x79, 0x69, 0x73, 0x6f, 0x64, 0x72, 0x61, 0x65, 0x6a, 0x67, 0x79, 0x6c, 0x6d, 0x61,
+ 0x62, 0x6e, 0x67, 0x6d, 0x77, 0x6a, 0x77, 0x72, 0x68, 0x68, 0x6f, 0x76, 0x77, 0x73, 0x76, 0x6f,
+ 0x6c, 0x66, 0x69, 0x75, 0x63, 0x74, 0x70, 0x74, 0x75, 0x6e, 0x66, 0x6d, 0x66, 0x69, 0x6b, 0x70,
+ 0x70, 0x74, 0x66, 0x6a, 0x6c, 0x6e, 0x65, 0x6b, 0x67, 0x71, 0x79, 0x76, 0x65, 0x64, 0x6a, 0x70,
+ 0x63, 0x71, 0x6f, 0x68, 0x76, 0x77, 0x65, 0x77, 0x78, 0x78, 0x65, 0x68, 0x69, 0x63, 0x66, 0x74,
+ 0x70, 0x65, 0x6e, 0x6a, 0x68, 0x6c, 0x70, 0x67, 0x6f, 0x62, 0x66, 0x79, 0x75, 0x71, 0x63, 0x6b,
+ 0x64, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x63, 0x6b, 0x78, 0x66, 0x63, 0x6f, 0x6f, 0x73, 0x67, 0x73,
+ 0x72, 0x67, 0x61, 0x66, 0x77, 0x6f, 0x78, 0x63, 0x6f, 0x64, 0x6d, 0x68, 0x70, 0x74, 0x70, 0x70,
+ 0x6c, 0x61, 0x63, 0x63, 0x78, 0x72, 0x68, 0x69, 0x76, 0x79, 0x74, 0x6c, 0x6c, 0x64, 0x72, 0x62,
+ 0x65, 0x73, 0x6e, 0x63, 0x70, 0x74, 0x6a, 0x74, 0x68, 0x6d, 0x62, 0x63, 0x74, 0x6b, 0x62, 0x6f,
+ 0x61, 0x72, 0x68, 0x62, 0x6c, 0x6e, 0x72, 0x71, 0x79, 0x76, 0x74, 0x67, 0x74, 0x70, 0x74, 0x67,
+ 0x77, 0x6e, 0x79, 0x70, 0x6f, 0x66, 0x72, 0x6b, 0x75, 0x6f, 0x64, 0x63, 0x65, 0x6f, 0x67, 0x66,
+ 0x68, 0x65, 0x75, 0x77, 0x64, 0x76, 0x61, 0x66, 0x69, 0x6a, 0x67, 0x62, 0x72, 0x71, 0x64, 0x79,
+ 0x72, 0x61, 0x73, 0x72, 0x75, 0x61, 0x65, 0x63, 0x77, 0x77, 0x77, 0x70, 0x65, 0x65, 0x62, 0x76,
+ 0x65, 0x68, 0x70, 0x77, 0x6c, 0x63, 0x63, 0x62, 0x6a, 0x68, 0x79, 0x64, 0x63, 0x73, 0x6f, 0x66,
+ 0x78, 0x66, 0x78, 0x63, 0x6f, 0x67, 0x74, 0x75, 0x69, 0x73, 0x77, 0x69, 0x6b, 0x78, 0x65, 0x70,
+ 0x6c, 0x66, 0x70, 0x77, 0x62, 0x69, 0x66, 0x74, 0x76, 0x75, 0x6e, 0x64, 0x71, 0x6f, 0x79, 0x62,
+ 0x67, 0x78, 0x77, 0x79, 0x74, 0x62, 0x73, 0x61, 0x6a, 0x67, 0x76, 0x6a, 0x6f, 0x62, 0x65, 0x75,
+ 0x75, 0x74, 0x68, 0x6a, 0x68, 0x6b, 0x65, 0x79, 0x73, 0x75, 0x6c, 0x69, 0x66, 0x68, 0x6c, 0x79,
+ 0x6f, 0x78, 0x75, 0x72, 0x72, 0x61, 0x6c, 0x68, 0x76, 0x64, 0x79, 0x6a, 0x6f, 0x69, 0x62, 0x75,
+ 0x6b, 0x73, 0x71, 0x74, 0x74, 0x79, 0x6d, 0x6d, 0x61, 0x6d, 0x72, 0x78, 0x72, 0x6e, 0x65, 0x61,
+ 0x75, 0x65, 0x65, 0x77, 0x62, 0x62, 0x69, 0x6c, 0x6d, 0x6a, 0x76, 0x78, 0x64, 0x76, 0x61, 0x63,
+ 0x69, 0x71, 0x6b, 0x6d, 0x62, 0x78, 0x6a, 0x6d, 0x6a, 0x78, 0x67, 0x68, 0x79, 0x79, 0x66, 0x71,
+ 0x6b, 0x73, 0x72, 0x76, 0x78, 0x74, 0x63, 0x72, 0x74, 0x76, 0x70, 0x79, 0x68, 0x79, 0x69, 0x62,
+ 0x61, 0x74, 0x6b, 0x79, 0x79, 0x6c, 0x78, 0x62, 0x65, 0x78, 0x71, 0x64, 0x75, 0x73, 0x68, 0x62,
+ 0x68, 0x74, 0x71, 0x70, 0x77, 0x61, 0x6a, 0x65, 0x79, 0x6d, 0x61, 0x68, 0x61, 0x75, 0x6e, 0x6e,
+ 0x6d, 0x73, 0x73, 0x65, 0x77, 0x66, 0x70, 0x78, 0x69, 0x65, 0x64, 0x71, 0x6f, 0x65, 0x62, 0x6e,
+ 0x72, 0x69, 0x61, 0x6e, 0x75, 0x76, 0x79, 0x68, 0x69, 0x64, 0x6e, 0x69, 0x63, 0x6d, 0x68, 0x65,
+ 0x62, 0x63, 0x68, 0x67, 0x79, 0x67, 0x67, 0x66, 0x63, 0x71, 0x69, 0x79, 0x69, 0x6a, 0x77, 0x76,
+ 0x64, 0x6c, 0x73, 0x71, 0x6b, 0x6b, 0x77, 0x63, 0x79, 0x78, 0x62, 0x76, 0x75, 0x73, 0x68, 0x65,
+ 0x64, 0x6b, 0x70, 0x78, 0x75, 0x71, 0x6e, 0x64, 0x70, 0x64, 0x6b, 0x6f, 0x77, 0x63, 0x77, 0x67,
+ 0x74, 0x66, 0x78, 0x73, 0x6c, 0x77, 0x62, 0x68, 0x76, 0x66, 0x70, 0x6e, 0x71, 0x66, 0x73, 0x6f,
+ 0x70, 0x65, 0x77, 0x63, 0x61, 0x71, 0x64, 0x6c, 0x72, 0x6a, 0x71, 0x68, 0x73, 0x66, 0x67, 0x62,
+ 0x70, 0x77, 0x69, 0x67, 0x6e, 0x78, 0x63, 0x75, 0x6a, 0x6b, 0x70, 0x69, 0x6c, 0x76, 0x64, 0x6f,
+ 0x76, 0x63, 0x75, 0x62, 0x77, 0x67, 0x6e, 0x67, 0x67, 0x6d, 0x63, 0x66, 0x6d, 0x67, 0x65, 0x6b,
+ 0x66, 0x73, 0x66, 0x75, 0x66, 0x73, 0x73, 0x6b, 0x6b, 0x78, 0x75, 0x65, 0x75, 0x76, 0x70, 0x76,
+ 0x61, 0x6e, 0x78, 0x66, 0x6c, 0x71, 0x66, 0x62, 0x71, 0x75, 0x79, 0x70, 0x74, 0x79, 0x65, 0x70,
+ 0x66, 0x79, 0x6f, 0x75, 0x70, 0x63, 0x76, 0x75, 0x62, 0x6c, 0x78, 0x68, 0x6f, 0x77, 0x75, 0x6e,
+ 0x79, 0x6d, 0x70, 0x64, 0x71, 0x63, 0x69, 0x75, 0x76, 0x68, 0x71, 0x75, 0x67, 0x79, 0x69, 0x69,
+ 0x77, 0x6d, 0x69, 0x6a, 0x68, 0x6a, 0x6d, 0x6a, 0x70, 0x6c, 0x73, 0x6d, 0x6c, 0x6c, 0x6f, 0x6c,
+ 0x69, 0x71, 0x73, 0x78, 0x6d, 0x61, 0x61, 0x73, 0x6f, 0x66, 0x66, 0x63, 0x71, 0x6b, 0x6d, 0x6f,
+ 0x75, 0x67, 0x71, 0x77, 0x67, 0x73, 0x76, 0x76, 0x6a, 0x76, 0x6c, 0x61, 0x67, 0x66, 0x6a, 0x69,
+ 0x73, 0x71, 0x69, 0x72, 0x72, 0x78, 0x63, 0x6b, 0x76, 0x6f, 0x6b, 0x78, 0x6b, 0x72, 0x6f, 0x6a,
+ 0x64, 0x64, 0x73, 0x74, 0x75, 0x64, 0x6f, 0x75, 0x77, 0x62, 0x64, 0x6a, 0x67, 0x6a, 0x73, 0x69,
+ 0x6a, 0x79, 0x63, 0x77, 0x75, 0x72, 0x75, 0x62, 0x68, 0x73, 0x6c, 0x69, 0x74, 0x64, 0x6d, 0x71,
+ 0x76, 0x76, 0x6e, 0x67, 0x67, 0x65, 0x71, 0x70, 0x6a, 0x79, 0x74, 0x75, 0x76, 0x74, 0x67, 0x79,
+ 0x62, 0x63, 0x68, 0x6c, 0x6c, 0x70, 0x67, 0x65, 0x64, 0x76, 0x72, 0x65, 0x76, 0x6b, 0x69, 0x71,
+ 0x73, 0x79, 0x78, 0x64, 0x70, 0x79, 0x61, 0x76, 0x61, 0x66, 0x74, 0x79, 0x6a, 0x6a, 0x6a, 0x78,
+ 0x6b, 0x6e, 0x74, 0x6f, 0x66, 0x67, 0x62, 0x76, 0x71, 0x65, 0x62, 0x64, 0x73, 0x6f, 0x63, 0x66,
+ 0x66, 0x6c, 0x63, 0x77, 0x6e, 0x72, 0x66, 0x72, 0x74, 0x70, 0x70, 0x6c, 0x6f, 0x72, 0x6b, 0x79,
+ 0x68, 0x67, 0x63, 0x62, 0x79, 0x6f, 0x73, 0x65, 0x62, 0x76, 0x67, 0x65, 0x77, 0x64, 0x6c, 0x61,
+ 0x62, 0x61, 0x64, 0x68, 0x79, 0x72, 0x70, 0x61, 0x77, 0x6d, 0x6a, 0x69, 0x70, 0x6f, 0x77, 0x77,
+ 0x77, 0x6f, 0x6d, 0x78, 0x76, 0x69, 0x69, 0x78, 0x74, 0x68, 0x68, 0x74, 0x6c, 0x6e, 0x66, 0x67,
+ 0x67, 0x64, 0x62, 0x6d, 0x63, 0x71, 0x75, 0x65, 0x75, 0x71, 0x6c, 0x75, 0x62, 0x66, 0x66, 0x61,
+ 0x69, 0x67, 0x79, 0x70, 0x6e, 0x6b, 0x6f, 0x71, 0x62, 0x77, 0x6c, 0x75, 0x67, 0x77, 0x78, 0x79,
+ 0x6e, 0x77, 0x6a, 0x71, 0x71, 0x77, 0x68, 0x72, 0x73, 0x77, 0x69, 0x75, 0x61, 0x62, 0x65, 0x71,
+ 0x6a, 0x78, 0x6e, 0x6c, 0x6c, 0x70, 0x76, 0x74, 0x69, 0x76, 0x61, 0x78, 0x66, 0x76, 0x6e, 0x62,
+ 0x64, 0x71, 0x71, 0x6c, 0x79, 0x65, 0x65, 0x6b, 0x71, 0x6a, 0x73, 0x6e, 0x6b, 0x76, 0x70, 0x79,
+ 0x77, 0x63, 0x63, 0x67, 0x75, 0x6e, 0x66, 0x74, 0x63, 0x76, 0x75, 0x6d, 0x66, 0x6c, 0x63, 0x6b,
+ 0x79, 0x6d, 0x72, 0x69, 0x67, 0x75, 0x65, 0x78, 0x70, 0x6c, 0x71, 0x69, 0x69, 0x76, 0x70, 0x75,
+ 0x6b, 0x6a, 0x64, 0x66, 0x71, 0x71, 0x6b, 0x6a, 0x66, 0x64, 0x79, 0x69, 0x6f, 0x74, 0x62, 0x6c,
+ 0x78, 0x61, 0x77, 0x65, 0x6e, 0x6f, 0x62, 0x70, 0x76, 0x6e, 0x73, 0x73, 0x74, 0x6d, 0x6d, 0x6a,
+ 0x68, 0x66, 0x6f, 0x78, 0x70, 0x76, 0x66, 0x6f, 0x6a, 0x76, 0x6a, 0x6b, 0x6c, 0x6f, 0x64, 0x77,
+ 0x78, 0x61, 0x68, 0x63, 0x72, 0x61, 0x69, 0x78, 0x69, 0x74, 0x78, 0x6c, 0x79, 0x61, 0x61, 0x67,
+ 0x72, 0x6a, 0x6f, 0x76, 0x6d, 0x6d, 0x6c, 0x78, 0x63, 0x63, 0x65, 0x69, 0x67, 0x78, 0x73, 0x76,
+ 0x78, 0x69, 0x6d, 0x70, 0x73, 0x76, 0x78, 0x71, 0x75, 0x65, 0x77, 0x69, 0x73, 0x77, 0x6f, 0x66,
+ 0x65, 0x6b, 0x61, 0x6a, 0x78, 0x70, 0x70, 0x71, 0x71, 0x79, 0x67, 0x79, 0x79, 0x6e, 0x64, 0x6c,
+ 0x6c, 0x6a, 0x6d, 0x6a, 0x79, 0x73, 0x63, 0x72, 0x6a, 0x72, 0x77, 0x6d, 0x74, 0x70, 0x77, 0x6e,
+ 0x77, 0x63, 0x6c, 0x71, 0x74, 0x75, 0x61, 0x79, 0x69, 0x71, 0x6a, 0x64, 0x62, 0x64, 0x68, 0x74,
+ 0x75, 0x75, 0x64, 0x6d, 0x6f, 0x66, 0x64, 0x69, 0x74, 0x73, 0x6c, 0x66, 0x77, 0x64, 0x73, 0x6e,
+ 0x63, 0x6e, 0x73, 0x6d, 0x67, 0x73, 0x76, 0x6b, 0x73, 0x6b, 0x77, 0x6c, 0x6f, 0x70, 0x69, 0x6f,
+ 0x6f, 0x62, 0x63, 0x6c, 0x70, 0x70, 0x6e, 0x61, 0x78, 0x6d, 0x63, 0x74, 0x67, 0x66, 0x79, 0x69,
+ 0x6c, 0x68, 0x71, 0x68, 0x68, 0x75, 0x79, 0x61, 0x62, 0x6b, 0x6b, 0x70, 0x6a, 0x69, 0x75, 0x6a,
+ 0x78, 0x65, 0x74, 0x62, 0x6d, 0x72, 0x6c, 0x74, 0x79, 0x74, 0x66, 0x63, 0x6d, 0x71, 0x68, 0x70,
+ 0x6c, 0x74, 0x79, 0x6e, 0x69, 0x75, 0x66, 0x6d, 0x69, 0x6f, 0x74, 0x68, 0x6c, 0x6b, 0x65, 0x72,
+ 0x69, 0x70, 0x68, 0x72, 0x65, 0x66, 0x6e, 0x72, 0x71, 0x66, 0x6f, 0x6e, 0x68, 0x62, 0x70, 0x6d,
+ 0x6e, 0x75, 0x77, 0x68, 0x74, 0x77, 0x74, 0x61, 0x76, 0x74, 0x79, 0x63, 0x6f, 0x63, 0x6c, 0x64,
+ 0x78, 0x69, 0x6f, 0x6d, 0x66, 0x78, 0x6c, 0x6c, 0x6f, 0x6a, 0x77, 0x6d, 0x74, 0x67, 0x63, 0x64,
+ 0x79, 0x64, 0x6d, 0x61, 0x66, 0x64, 0x79, 0x6a, 0x61, 0x68, 0x63, 0x6d, 0x61, 0x69, 0x70, 0x6e,
+ 0x61, 0x77, 0x6a, 0x64, 0x63, 0x64, 0x70, 0x6d, 0x75, 0x62, 0x6d, 0x74, 0x64, 0x6d, 0x74, 0x67,
+ 0x6d, 0x72, 0x77, 0x79, 0x6f, 0x76, 0x6f, 0x75, 0x62, 0x6b, 0x72, 0x77, 0x66, 0x6b, 0x6c, 0x73,
+ 0x6a, 0x76, 0x69, 0x68, 0x6f, 0x77, 0x62, 0x74, 0x71, 0x66, 0x74, 0x64, 0x63, 0x79, 0x6d, 0x72,
+ 0x76, 0x65, 0x69, 0x6f, 0x74, 0x73, 0x73, 0x6b, 0x68, 0x66, 0x64, 0x72, 0x70, 0x62, 0x73, 0x76,
+ 0x64, 0x6d, 0x70, 0x6c, 0x6d, 0x63, 0x73, 0x73, 0x72, 0x76, 0x69, 0x76, 0x62, 0x64, 0x72, 0x75,
+ 0x76, 0x6a, 0x62, 0x78, 0x69, 0x61, 0x73, 0x68, 0x6a, 0x67, 0x62, 0x6a, 0x64, 0x67, 0x77, 0x76,
+ 0x6c, 0x69, 0x77, 0x74, 0x72, 0x6f, 0x77, 0x66, 0x67, 0x62, 0x6e, 0x6e, 0x66, 0x70, 0x72, 0x64,
+ 0x71, 0x79, 0x66, 0x6f, 0x72, 0x65, 0x75, 0x64, 0x6b, 0x6f, 0x77, 0x73, 0x79, 0x75, 0x78, 0x6a,
+ 0x64, 0x6d, 0x79, 0x77, 0x6b, 0x62, 0x77, 0x62, 0x66, 0x67, 0x73, 0x68, 0x72, 0x62, 0x62, 0x67,
+ 0x6d, 0x6d, 0x6c, 0x78, 0x69, 0x6c, 0x75, 0x77, 0x6d, 0x69, 0x6c, 0x6b, 0x71, 0x64, 0x67, 0x65,
+ 0x73, 0x63, 0x6d, 0x6d, 0x63, 0x65, 0x6a, 0x71, 0x79, 0x6d, 0x63, 0x6e, 0x77, 0x64, 0x63, 0x71,
+ 0x67, 0x77, 0x68, 0x6d, 0x63, 0x61, 0x67, 0x6e, 0x63, 0x71, 0x6a, 0x72, 0x75, 0x61, 0x74, 0x69,
+ 0x6c, 0x76, 0x62, 0x65, 0x78, 0x74, 0x63, 0x67, 0x75, 0x6e, 0x71, 0x69, 0x68, 0x77, 0x69, 0x6b,
+ 0x69, 0x67, 0x70, 0x6c, 0x77, 0x6d, 0x6d, 0x6c, 0x77, 0x77, 0x6d, 0x66, 0x61, 0x6d, 0x66, 0x76,
+ 0x69, 0x62, 0x66, 0x6b, 0x64, 0x68, 0x75, 0x78, 0x67, 0x64, 0x61, 0x6b, 0x78, 0x6c, 0x78, 0x70,
+ 0x77, 0x61, 0x68, 0x65, 0x6d, 0x66, 0x6f, 0x77, 0x78, 0x6f, 0x61, 0x6e, 0x6e, 0x77, 0x67, 0x6b,
+ 0x62, 0x70, 0x6b, 0x69, 0x61, 0x67, 0x6a, 0x76, 0x68, 0x74, 0x61, 0x6d, 0x70, 0x69, 0x70, 0x6a,
+ 0x68, 0x71, 0x69, 0x63, 0x74, 0x6a, 0x6a, 0x73, 0x69, 0x64, 0x64, 0x75, 0x79, 0x6a, 0x65, 0x6d,
+ 0x71, 0x62, 0x68, 0x77, 0x74, 0x65, 0x68, 0x6b, 0x78, 0x69, 0x6e, 0x73, 0x65, 0x66, 0x6e, 0x64,
+ 0x6b, 0x79, 0x66, 0x61, 0x78, 0x63, 0x6a, 0x6b, 0x68, 0x76, 0x74, 0x6f, 0x72, 0x6d, 0x73, 0x6c,
+ 0x67, 0x6a, 0x72, 0x78, 0x75, 0x6f, 0x6d, 0x6e, 0x61, 0x6d, 0x66, 0x6b, 0x65, 0x72, 0x70, 0x62,
+ 0x67, 0x78, 0x64, 0x74, 0x77, 0x6c, 0x67, 0x78, 0x71, 0x75, 0x71, 0x62, 0x61, 0x6d, 0x79, 0x70,
+ 0x74, 0x71, 0x6e, 0x6a, 0x70, 0x61, 0x74, 0x6a, 0x6f, 0x6a, 0x78, 0x6d, 0x72, 0x61, 0x67, 0x76,
+ 0x71, 0x71, 0x75, 0x61, 0x62, 0x6d, 0x62, 0x67, 0x6a, 0x61, 0x73, 0x73, 0x70, 0x64, 0x69, 0x74,
+ 0x6e, 0x62, 0x72, 0x72, 0x6d, 0x78, 0x72, 0x76, 0x6a, 0x6b, 0x70, 0x6d, 0x72, 0x77, 0x61, 0x63,
+ 0x79, 0x64, 0x6b, 0x6e, 0x72, 0x6b, 0x61, 0x6e, 0x77, 0x70, 0x6f, 0x6e, 0x68, 0x6c, 0x71, 0x62,
+ 0x74, 0x64, 0x63, 0x67, 0x65, 0x67, 0x67, 0x6e, 0x6d, 0x66, 0x62, 0x72, 0x71, 0x74, 0x76, 0x79,
+ 0x70, 0x72, 0x6f, 0x6f, 0x63, 0x79, 0x72, 0x63, 0x73, 0x64, 0x68, 0x63, 0x73, 0x6c, 0x6f, 0x6c,
+ 0x66, 0x71, 0x6a, 0x75, 0x69, 0x6e, 0x67, 0x72, 0x74, 0x6a, 0x61, 0x73, 0x68, 0x69, 0x6d, 0x71,
+ 0x76, 0x73, 0x65, 0x6f, 0x63, 0x62, 0x71, 0x6d, 0x66, 0x71, 0x69, 0x61, 0x76, 0x6d, 0x76, 0x6f,
+ 0x64, 0x6c, 0x6c, 0x74, 0x6c, 0x74, 0x76, 0x6b, 0x63, 0x62, 0x79, 0x70, 0x6e, 0x64, 0x64, 0x6e,
+ 0x71, 0x73, 0x71, 0x6d, 0x71, 0x67, 0x6a, 0x69, 0x66, 0x76, 0x6e, 0x73, 0x6f, 0x77, 0x67, 0x6f,
+ 0x65, 0x71, 0x73, 0x70, 0x61, 0x6b, 0x67, 0x69, 0x6b, 0x75, 0x68, 0x6d, 0x6a, 0x6c, 0x61, 0x70,
+ 0x71, 0x74, 0x66, 0x63, 0x61, 0x67, 0x77, 0x77, 0x68, 0x61, 0x61, 0x6a, 0x74, 0x72, 0x6d, 0x75,
+ 0x75, 0x64, 0x69, 0x70, 0x64, 0x62, 0x6b, 0x75, 0x6c, 0x73, 0x68, 0x65, 0x6b, 0x74, 0x74, 0x6e,
+ 0x6c, 0x61, 0x6c, 0x74, 0x71, 0x6c, 0x79, 0x69, 0x65, 0x76, 0x74, 0x77, 0x76, 0x69, 0x66, 0x78,
+ 0x68, 0x79, 0x64, 0x79, 0x61, 0x76, 0x68, 0x70, 0x78, 0x73, 0x68, 0x6d, 0x6a, 0x67, 0x65, 0x76,
+ 0x73, 0x66, 0x6b, 0x6b, 0x70, 0x68, 0x64, 0x79, 0x76, 0x71, 0x77, 0x63, 0x65, 0x6a, 0x6e, 0x6f,
+ 0x6f, 0x75, 0x6e, 0x65, 0x6a, 0x64, 0x6c, 0x69, 0x6e, 0x70, 0x6f, 0x6d, 0x66, 0x76, 0x76, 0x66,
+ 0x62, 0x6c, 0x78, 0x71, 0x71, 0x73, 0x63, 0x6e, 0x72, 0x6d, 0x6a, 0x74, 0x67, 0x77, 0x78, 0x74,
+ 0x65, 0x66, 0x6d, 0x72, 0x73, 0x6f, 0x64, 0x6e, 0x66, 0x71, 0x61, 0x6c, 0x72, 0x77, 0x67, 0x71,
+ 0x6d, 0x74, 0x69, 0x72, 0x61, 0x6b, 0x78, 0x67, 0x78, 0x73, 0x70, 0x6b, 0x63, 0x76, 0x6f, 0x65,
+ 0x6b, 0x70, 0x74, 0x71, 0x75, 0x67, 0x78, 0x6c, 0x6b, 0x79, 0x76, 0x71, 0x63, 0x6b, 0x6a, 0x6e,
+ 0x61, 0x66, 0x6f, 0x69, 0x6f, 0x64, 0x6d, 0x6e, 0x61, 0x76, 0x69, 0x6f, 0x78, 0x68, 0x61, 0x74,
+ 0x6d, 0x68, 0x72, 0x64, 0x63, 0x79, 0x78, 0x76, 0x6c, 0x6e, 0x6f, 0x6a, 0x72, 0x6b, 0x6b, 0x70,
+ 0x65, 0x65, 0x6c, 0x63, 0x66, 0x73, 0x66, 0x68, 0x6e, 0x74, 0x6d, 0x64, 0x62, 0x6f, 0x6f, 0x67,
+ 0x79, 0x6d, 0x69, 0x61, 0x78, 0x6b, 0x6b, 0x61, 0x6e, 0x76, 0x6b, 0x70, 0x6e, 0x6d, 0x68, 0x67,
+ 0x72, 0x72, 0x69, 0x74, 0x78, 0x6a, 0x78, 0x75, 0x68, 0x71, 0x70, 0x6b, 0x6b, 0x64, 0x71, 0x63,
+ 0x65, 0x6c, 0x6c, 0x69, 0x72, 0x76, 0x70, 0x6f, 0x70, 0x6e, 0x73, 0x74, 0x6a, 0x61, 0x75, 0x63,
+ 0x61, 0x78, 0x6f, 0x79, 0x67, 0x65, 0x61, 0x69, 0x6d, 0x68, 0x6c, 0x62, 0x75, 0x79, 0x68, 0x79,
+ 0x65, 0x6a, 0x67, 0x6f, 0x68, 0x78, 0x70, 0x69, 0x62, 0x6b, 0x6b, 0x64, 0x77, 0x78, 0x6b, 0x76,
+ 0x69, 0x64, 0x75, 0x70, 0x72, 0x6e, 0x67, 0x73, 0x65, 0x73, 0x71, 0x73, 0x70, 0x78, 0x66, 0x6b,
+ 0x6e, 0x6b, 0x6a, 0x6f, 0x73, 0x6b, 0x70, 0x71, 0x69, 0x63, 0x6f, 0x73, 0x69, 0x70, 0x6f, 0x6b,
+ 0x69, 0x75, 0x61, 0x70, 0x72, 0x65, 0x6a, 0x75, 0x61, 0x67, 0x75, 0x76, 0x65, 0x6d, 0x6c, 0x73,
+ 0x71, 0x6b, 0x73, 0x65, 0x79, 0x68, 0x70, 0x69, 0x6f, 0x69, 0x6c, 0x6b, 0x72, 0x64, 0x6c, 0x79,
+ 0x6b, 0x6a, 0x61, 0x6e, 0x6b, 0x6b, 0x74, 0x6f, 0x79, 0x64, 0x68, 0x68, 0x6c, 0x6f, 0x62, 0x70,
+ 0x6e, 0x79, 0x62, 0x62, 0x6b, 0x66, 0x6e, 0x6f, 0x70, 0x64, 0x66, 0x75, 0x73, 0x79, 0x62, 0x75,
+ 0x6d, 0x67, 0x73, 0x73, 0x6b, 0x71, 0x6e, 0x6d, 0x75, 0x62, 0x72, 0x6a, 0x6b, 0x63, 0x6d, 0x6c,
+ 0x6f, 0x6c, 0x74, 0x65, 0x71, 0x65, 0x6b, 0x77, 0x61, 0x66, 0x78, 0x6c, 0x78, 0x6d, 0x6e, 0x6d,
+ 0x6a, 0x71, 0x62, 0x64, 0x68, 0x67, 0x61, 0x6a, 0x68, 0x72, 0x72, 0x62, 0x6e, 0x6b, 0x71, 0x6d,
+ 0x66, 0x75, 0x6a, 0x78, 0x64, 0x66, 0x75, 0x74, 0x6f, 0x6a, 0x74, 0x73, 0x75, 0x62, 0x71, 0x6b,
+ 0x6d, 0x66, 0x65, 0x62, 0x64, 0x68, 0x79, 0x74, 0x6f, 0x75, 0x76, 0x66, 0x79, 0x70, 0x77, 0x65,
+ 0x78, 0x72, 0x65, 0x6e, 0x6a, 0x62, 0x61, 0x74, 0x65, 0x73, 0x68, 0x6e, 0x72, 0x75, 0x62, 0x64,
+ 0x69, 0x73, 0x65, 0x6b, 0x61, 0x6f, 0x6e, 0x71, 0x61, 0x6e, 0x64, 0x6e, 0x76, 0x73, 0x65, 0x63,
+ 0x6c, 0x6b, 0x6d, 0x77, 0x66, 0x6b, 0x72, 0x72, 0x78, 0x72, 0x6b, 0x6c, 0x69, 0x69, 0x79, 0x76,
+ 0x6c, 0x78, 0x75, 0x73, 0x65, 0x6d, 0x6e, 0x74, 0x68, 0x65, 0x76, 0x70, 0x64, 0x6b, 0x77, 0x6c,
+ 0x77, 0x6d, 0x66, 0x79, 0x64, 0x64, 0x6f, 0x74, 0x62, 0x6d, 0x6b, 0x61, 0x78, 0x6a, 0x73, 0x63,
+ 0x6e, 0x6a, 0x62, 0x73, 0x72, 0x6e, 0x6c, 0x62, 0x75, 0x79, 0x62, 0x66, 0x69, 0x72, 0x6c, 0x67,
+ 0x78, 0x61, 0x76, 0x62, 0x6e, 0x66, 0x63, 0x69, 0x68, 0x73, 0x78, 0x78, 0x74, 0x61, 0x78, 0x71,
+ 0x6e, 0x70, 0x75, 0x77, 0x63, 0x64, 0x61, 0x6f, 0x75, 0x68, 0x76, 0x71, 0x64, 0x72, 0x74, 0x75,
+ 0x6c, 0x73, 0x72, 0x75, 0x63, 0x61, 0x74, 0x61, 0x77, 0x74, 0x64, 0x65, 0x74, 0x68, 0x74, 0x78,
+ 0x63, 0x65, 0x79, 0x75, 0x6e, 0x6d, 0x78, 0x77, 0x67, 0x65, 0x6e, 0x74, 0x66, 0x76, 0x6c, 0x78,
+ 0x66, 0x76, 0x62, 0x76, 0x68, 0x65, 0x6f, 0x73, 0x78, 0x76, 0x69, 0x6c, 0x63, 0x76, 0x70, 0x79,
+ 0x6d, 0x75, 0x6e, 0x69, 0x61, 0x66, 0x65, 0x78, 0x6d, 0x68, 0x62, 0x73, 0x66, 0x73, 0x6b, 0x6c,
+ 0x77, 0x64, 0x6c, 0x72, 0x78, 0x75, 0x6c, 0x63, 0x6a, 0x64, 0x68, 0x79, 0x6f, 0x74, 0x65, 0x6d,
+ 0x65, 0x70, 0x6d, 0x70, 0x68, 0x65, 0x65, 0x65, 0x6c, 0x74, 0x6b, 0x69, 0x74, 0x6a, 0x71, 0x64,
+ 0x72, 0x74, 0x6e, 0x74, 0x75, 0x6a, 0x72, 0x73, 0x6b, 0x69, 0x6c, 0x71, 0x78, 0x77, 0x75, 0x6c,
+ 0x69, 0x62, 0x6e, 0x6a, 0x68, 0x73, 0x77, 0x77, 0x63, 0x66, 0x6a, 0x78, 0x6d, 0x77, 0x6e, 0x62,
+ 0x6c, 0x76, 0x66, 0x66, 0x75, 0x65, 0x6e, 0x69, 0x79, 0x75, 0x72, 0x6d, 0x70, 0x71, 0x65, 0x67,
+ 0x74, 0x75, 0x6a, 0x6d, 0x66, 0x64, 0x72, 0x66, 0x77, 0x70, 0x76, 0x75, 0x73, 0x75, 0x6c, 0x73,
+ 0x75, 0x6b, 0x71, 0x6f, 0x79, 0x6a, 0x62, 0x78, 0x69, 0x62, 0x72, 0x61, 0x6a, 0x69, 0x67, 0x71,
+ 0x66, 0x71, 0x6a, 0x63, 0x62, 0x79, 0x78, 0x69, 0x61, 0x63, 0x6a, 0x62, 0x6a, 0x63, 0x68, 0x79,
+ 0x68, 0x69, 0x63, 0x73, 0x65, 0x67, 0x70, 0x74, 0x72, 0x6f, 0x79, 0x75, 0x65, 0x73, 0x6a, 0x76,
+ 0x6f, 0x64, 0x66, 0x72, 0x6d, 0x71, 0x66, 0x6d, 0x62, 0x6a, 0x68, 0x74, 0x67, 0x68, 0x6e, 0x6b,
+ 0x67, 0x6c, 0x6e, 0x72, 0x79, 0x6e, 0x6b, 0x6a, 0x66, 0x6a, 0x65, 0x69, 0x75, 0x6a, 0x6e, 0x6a,
+ 0x61, 0x62, 0x79, 0x6c, 0x65, 0x74, 0x63, 0x76, 0x63, 0x6a, 0x68, 0x76, 0x6d, 0x69, 0x66, 0x68,
+ 0x67, 0x71, 0x66, 0x68, 0x66, 0x75, 0x76, 0x6e, 0x74, 0x70, 0x63, 0x67, 0x73, 0x68, 0x67, 0x61,
+ 0x74, 0x66, 0x65, 0x6e, 0x6b, 0x6c, 0x64, 0x6b, 0x70, 0x70, 0x75, 0x78, 0x76, 0x68, 0x74, 0x69,
+ 0x76, 0x77, 0x6e, 0x6c, 0x77, 0x76, 0x66, 0x6a, 0x61, 0x6a, 0x63, 0x6a, 0x6e, 0x73, 0x67, 0x73,
+ 0x6b, 0x6e, 0x75, 0x6a, 0x6a, 0x70, 0x73, 0x66, 0x78, 0x79, 0x61, 0x79, 0x6a, 0x6e, 0x76, 0x74,
+ 0x65, 0x74, 0x79, 0x6d, 0x6e, 0x79, 0x75, 0x76, 0x72, 0x6f, 0x6a, 0x67, 0x61, 0x66, 0x6a, 0x72,
+ 0x65, 0x79, 0x61, 0x6b, 0x65, 0x6b, 0x67, 0x77, 0x6a, 0x69, 0x73, 0x64, 0x6e, 0x6a, 0x68, 0x68,
+ 0x73, 0x76, 0x70, 0x78, 0x79, 0x6c, 0x71, 0x78, 0x6b, 0x77, 0x6b, 0x72, 0x70, 0x64, 0x75, 0x64,
+ 0x6a, 0x63, 0x69, 0x79, 0x70, 0x64, 0x72, 0x78, 0x72, 0x70, 0x79, 0x6a, 0x61, 0x6f, 0x73, 0x64,
+ 0x76, 0x79, 0x77, 0x71, 0x69, 0x75, 0x69, 0x6a, 0x77, 0x65, 0x77, 0x72, 0x6b, 0x61, 0x65, 0x78,
+ 0x65, 0x64, 0x66, 0x62, 0x6f, 0x72, 0x74, 0x72, 0x73, 0x71, 0x64, 0x77, 0x76, 0x70, 0x6c, 0x73,
+ 0x6e, 0x68, 0x6a, 0x62, 0x6d, 0x6f, 0x70, 0x78, 0x64, 0x6e, 0x65, 0x6f, 0x6d, 0x65, 0x64, 0x71,
+ 0x70, 0x6c, 0x6c, 0x6d, 0x6e, 0x71, 0x65, 0x66, 0x63, 0x74, 0x62, 0x71, 0x6a, 0x6c, 0x6b, 0x70,
+ 0x6a, 0x65, 0x6e, 0x6c, 0x6c, 0x69, 0x6e, 0x61, 0x79, 0x73, 0x78, 0x72, 0x65, 0x69, 0x78, 0x62,
+ 0x6f, 0x79, 0x6e, 0x63, 0x74, 0x6c, 0x65, 0x66, 0x72, 0x6d, 0x6a, 0x6c, 0x6b, 0x62, 0x6b, 0x6c,
+ 0x70, 0x65, 0x64, 0x70, 0x67, 0x71, 0x6c, 0x77, 0x6b, 0x66, 0x62, 0x77, 0x72, 0x6d, 0x6a, 0x62,
+ 0x61, 0x62, 0x65, 0x71, 0x78, 0x67, 0x62, 0x66, 0x69, 0x70, 0x79, 0x6b, 0x66, 0x6f, 0x62, 0x6c,
+ 0x61, 0x73, 0x61, 0x6c, 0x64, 0x62, 0x61, 0x70, 0x61, 0x63, 0x73, 0x6d, 0x76, 0x79, 0x68, 0x66,
+ 0x66, 0x6f, 0x66, 0x6f, 0x6a, 0x63, 0x61, 0x66, 0x75, 0x75, 0x61, 0x72, 0x61, 0x6a, 0x71, 0x6b,
+ 0x62, 0x65, 0x6c, 0x62, 0x62, 0x74, 0x69, 0x75, 0x69, 0x6f, 0x77, 0x65, 0x73, 0x72, 0x63, 0x72,
+ 0x62, 0x78, 0x75, 0x75, 0x6d, 0x70, 0x6c, 0x75, 0x6e, 0x72, 0x6f, 0x6c, 0x61, 0x61, 0x6d, 0x79,
+ 0x65, 0x71, 0x68, 0x6a, 0x6e, 0x6e, 0x6d, 0x69, 0x68, 0x75, 0x65, 0x6a, 0x72, 0x61, 0x77, 0x75,
+ 0x69, 0x73, 0x76, 0x6b, 0x64, 0x77, 0x75, 0x61, 0x68, 0x74, 0x65, 0x63, 0x69, 0x6e, 0x62, 0x6f,
+ 0x63, 0x79, 0x65, 0x65, 0x6e, 0x66, 0x6f, 0x6c, 0x61, 0x78, 0x69, 0x68, 0x73, 0x62, 0x61, 0x6f,
+ 0x65, 0x75, 0x70, 0x6a, 0x68, 0x72, 0x6e, 0x66, 0x62, 0x6a, 0x74, 0x61, 0x63, 0x72, 0x6e, 0x65,
+ 0x65, 0x67, 0x71, 0x79, 0x61, 0x63, 0x72, 0x6f, 0x77, 0x77, 0x77, 0x74, 0x67, 0x77, 0x68, 0x6c,
+ 0x72, 0x64, 0x61, 0x77, 0x64, 0x62, 0x6f, 0x63, 0x67, 0x77, 0x6a, 0x6b, 0x71, 0x78, 0x75, 0x6b,
+ 0x6a, 0x77, 0x76, 0x75, 0x6e, 0x6d, 0x61, 0x67, 0x74, 0x6c, 0x62, 0x75, 0x78, 0x63, 0x6a, 0x71,
+ 0x6f, 0x6f, 0x77, 0x6f, 0x74, 0x6c, 0x66, 0x73, 0x78, 0x75, 0x78, 0x62, 0x66, 0x62, 0x70, 0x78,
+ 0x65, 0x6a, 0x77, 0x71, 0x6c, 0x71, 0x65, 0x79, 0x79, 0x70, 0x6b, 0x70, 0x79, 0x6b, 0x78, 0x69,
+ 0x6d, 0x65, 0x6c, 0x66, 0x78, 0x73, 0x6e, 0x6a, 0x72, 0x77, 0x69, 0x76, 0x68, 0x78, 0x6b, 0x71,
+ 0x70, 0x76, 0x63, 0x61, 0x62, 0x79, 0x6f, 0x64, 0x6e, 0x72, 0x65, 0x72, 0x69, 0x69, 0x64, 0x65,
+ 0x6b, 0x71, 0x68, 0x6b, 0x6d, 0x6c, 0x6c, 0x62, 0x72, 0x71, 0x72, 0x65, 0x61, 0x69, 0x65, 0x71,
+ 0x6c, 0x6d, 0x66, 0x69, 0x67, 0x71, 0x6d, 0x6d, 0x71, 0x69, 0x6f, 0x69, 0x68, 0x6d, 0x61, 0x74,
+ 0x6f, 0x65, 0x6c, 0x68, 0x67, 0x79, 0x6b, 0x63, 0x69, 0x78, 0x61, 0x6f, 0x65, 0x6c, 0x79, 0x64,
+ 0x68, 0x66, 0x72, 0x6a, 0x6d, 0x64, 0x6d, 0x71, 0x6c, 0x64, 0x6b, 0x6d, 0x64, 0x74, 0x64, 0x62,
+ 0x72, 0x64, 0x67, 0x71, 0x68, 0x61, 0x76, 0x70, 0x69, 0x61, 0x73, 0x6a, 0x72, 0x62, 0x66, 0x75,
+ 0x64, 0x77, 0x70, 0x6e, 0x72, 0x6f, 0x76, 0x6f, 0x68, 0x73, 0x6c, 0x6a, 0x61, 0x78, 0x72, 0x73,
+ 0x70, 0x76, 0x76, 0x6b, 0x75, 0x71, 0x65, 0x6d, 0x6a, 0x67, 0x73, 0x64, 0x62, 0x68, 0x79, 0x74,
+ 0x71, 0x70, 0x66, 0x77, 0x6e, 0x72, 0x65, 0x75, 0x72, 0x74, 0x76, 0x72, 0x78, 0x64, 0x73, 0x68,
+ 0x71, 0x65, 0x74, 0x67, 0x6b, 0x67, 0x64, 0x61, 0x64, 0x63, 0x6e, 0x71, 0x6e, 0x74, 0x76, 0x67,
+ 0x6b, 0x6c, 0x6c, 0x63, 0x6e, 0x70, 0x72, 0x76, 0x65, 0x72, 0x6c, 0x6f, 0x64, 0x71, 0x66, 0x78,
+ 0x66, 0x6c, 0x62, 0x76, 0x75, 0x69, 0x65, 0x70, 0x75, 0x76, 0x77, 0x6e, 0x78, 0x6a, 0x6f, 0x6e,
+ 0x67, 0x73, 0x6b, 0x61, 0x6b, 0x6a, 0x6a, 0x72, 0x6f, 0x69, 0x77, 0x61, 0x61, 0x75, 0x6f, 0x71,
+ 0x76, 0x76, 0x70, 0x71, 0x75, 0x63, 0x75, 0x6d, 0x6e, 0x74, 0x68, 0x63, 0x64, 0x6c, 0x65, 0x74,
+ 0x77, 0x64, 0x72, 0x67, 0x73, 0x72, 0x65, 0x61, 0x64, 0x70, 0x71, 0x76, 0x6a, 0x79, 0x6f, 0x6e,
+ 0x61, 0x71, 0x63, 0x76, 0x78, 0x71, 0x63, 0x77, 0x66, 0x75, 0x78, 0x67, 0x6e, 0x66, 0x6c, 0x61,
+ 0x6d, 0x78, 0x79, 0x61, 0x67, 0x6c, 0x79, 0x6e, 0x68, 0x6d, 0x66, 0x67, 0x61, 0x75, 0x76, 0x73,
+ 0x6c, 0x6c, 0x76, 0x6c, 0x6f, 0x61, 0x71, 0x61, 0x73, 0x72, 0x69, 0x6e, 0x64, 0x78, 0x73, 0x79,
+ 0x64, 0x63, 0x69, 0x61, 0x71, 0x65, 0x74, 0x65, 0x6b, 0x6d, 0x75, 0x74, 0x61, 0x64, 0x77, 0x65,
+ 0x78, 0x71, 0x64, 0x65, 0x6b, 0x71, 0x6d, 0x6b, 0x70, 0x6c, 0x62, 0x68, 0x64, 0x62, 0x63, 0x62,
+ 0x6c, 0x70, 0x6b, 0x72, 0x78, 0x62, 0x79, 0x68, 0x6e, 0x79, 0x6a, 0x65, 0x6d, 0x69, 0x64, 0x79,
+ 0x6d, 0x6a, 0x69, 0x6d, 0x79, 0x69, 0x6e, 0x76, 0x6d, 0x74, 0x71, 0x79, 0x74, 0x6a, 0x70, 0x6f,
+ 0x79, 0x6c, 0x6e, 0x71, 0x63, 0x77, 0x76, 0x64, 0x66, 0x6c, 0x74, 0x64, 0x68, 0x65, 0x75, 0x72,
+ 0x73, 0x6a, 0x65, 0x67, 0x73, 0x69, 0x6b, 0x6f, 0x64, 0x62, 0x61, 0x74, 0x70, 0x73, 0x6f, 0x6a,
+ 0x6a, 0x6b, 0x6a, 0x67, 0x6a, 0x66, 0x72, 0x6e, 0x73, 0x62, 0x65, 0x77, 0x6a, 0x70, 0x65, 0x69,
+ 0x6a, 0x70, 0x61, 0x62, 0x6f, 0x63, 0x62, 0x77, 0x77, 0x6b, 0x65, 0x75, 0x63, 0x65, 0x61, 0x65,
+ 0x68, 0x66, 0x67, 0x75, 0x61, 0x67, 0x79, 0x71, 0x6a, 0x66, 0x65, 0x73, 0x6c, 0x64, 0x70, 0x65,
+ 0x68, 0x6e, 0x71, 0x6b, 0x74, 0x67, 0x77, 0x75, 0x6a, 0x79, 0x74, 0x70, 0x79, 0x6d, 0x62, 0x6e,
+ 0x71, 0x75, 0x72, 0x62, 0x6b, 0x6d, 0x68, 0x71, 0x78, 0x66, 0x67, 0x75, 0x6a, 0x6f, 0x6f, 0x67,
+ 0x6b, 0x62, 0x67, 0x63, 0x63, 0x71, 0x62, 0x79, 0x68, 0x6e, 0x77, 0x70, 0x68, 0x73, 0x64, 0x76,
+ 0x65, 0x75, 0x6e, 0x66, 0x6a, 0x75, 0x61, 0x63, 0x6b, 0x73, 0x62, 0x61, 0x71, 0x69, 0x6b, 0x70,
+ 0x6c, 0x76, 0x79, 0x77, 0x6c, 0x6c, 0x64, 0x6e, 0x6d, 0x79, 0x71, 0x62, 0x70, 0x6f, 0x6e, 0x6d,
+ 0x73, 0x74, 0x69, 0x76, 0x6f, 0x74, 0x71, 0x73, 0x78, 0x6c, 0x61, 0x70, 0x78, 0x78, 0x6f, 0x67,
+ 0x68, 0x70, 0x72, 0x64, 0x71, 0x6d, 0x6a, 0x67, 0x75, 0x61, 0x78, 0x71, 0x74, 0x73, 0x63, 0x6c,
+ 0x65, 0x66, 0x6c, 0x72, 0x6d, 0x77, 0x6f, 0x62, 0x77, 0x70, 0x68, 0x68, 0x72, 0x72, 0x66, 0x73,
+ 0x74, 0x79, 0x69, 0x79, 0x65, 0x79, 0x68, 0x67, 0x69, 0x71, 0x74, 0x75, 0x65, 0x6c, 0x62, 0x61,
+ 0x63, 0x64, 0x74, 0x63, 0x76, 0x77, 0x79, 0x65, 0x73, 0x63, 0x73, 0x6c, 0x6d, 0x6b, 0x6a, 0x78,
+ 0x6c, 0x67, 0x76, 0x64, 0x68, 0x73, 0x63, 0x78, 0x66, 0x73, 0x78, 0x67, 0x75, 0x70, 0x63, 0x63,
+ 0x61, 0x77, 0x6f, 0x68, 0x6d, 0x6d, 0x74, 0x6a, 0x66, 0x62, 0x61, 0x76, 0x62, 0x63, 0x6c, 0x6f,
+ 0x68, 0x6f, 0x74, 0x69, 0x6a, 0x6b, 0x6c, 0x70, 0x78, 0x6a, 0x75, 0x63, 0x67, 0x6d, 0x6e, 0x6e,
+ 0x6f, 0x63, 0x69, 0x64, 0x66, 0x72, 0x69, 0x6d, 0x6d, 0x6a, 0x74, 0x74, 0x62, 0x6a, 0x61, 0x67,
+ 0x79, 0x6f, 0x78, 0x62, 0x6e, 0x63, 0x64, 0x74, 0x71, 0x75, 0x6d, 0x73, 0x72, 0x67, 0x6f, 0x72,
+ 0x6d, 0x70, 0x6f, 0x6c, 0x6b, 0x66, 0x69, 0x79, 0x63, 0x79, 0x79, 0x63, 0x66, 0x70, 0x6c, 0x61,
+ 0x6a, 0x61, 0x62, 0x76, 0x70, 0x71, 0x73, 0x71, 0x72, 0x63, 0x77, 0x78, 0x62, 0x6b, 0x75, 0x68,
+ 0x69, 0x75, 0x6b, 0x77, 0x71, 0x6e, 0x61, 0x6e, 0x62, 0x79, 0x6d, 0x61, 0x6e, 0x63, 0x6a, 0x74,
+ 0x77, 0x6d, 0x78, 0x64, 0x75, 0x6b, 0x79, 0x79, 0x73, 0x62, 0x67, 0x62, 0x75, 0x6f, 0x72, 0x67,
+ 0x6f, 0x76, 0x73, 0x68, 0x66, 0x6f, 0x78, 0x72, 0x69, 0x64, 0x61, 0x66, 0x69, 0x6c, 0x6a, 0x71,
+ 0x73, 0x75, 0x6a, 0x69, 0x71, 0x64, 0x6c, 0x61, 0x72, 0x67, 0x71, 0x76, 0x6d, 0x64, 0x74, 0x72,
+ 0x78, 0x67, 0x6d, 0x70, 0x65, 0x79, 0x61, 0x6d, 0x79, 0x6a, 0x68, 0x67, 0x6c, 0x64, 0x70, 0x74,
+ 0x62, 0x70, 0x6c, 0x6d, 0x72, 0x6e, 0x72, 0x68, 0x70, 0x78, 0x6f, 0x6e, 0x6b, 0x72, 0x66, 0x77,
+ 0x72, 0x61, 0x64, 0x68, 0x6b, 0x61, 0x6a, 0x6d, 0x77, 0x6c, 0x63, 0x78, 0x6c, 0x65, 0x63, 0x74,
+ 0x78, 0x76, 0x68, 0x61, 0x6f, 0x6b, 0x64, 0x66, 0x73, 0x72, 0x73, 0x66, 0x65, 0x67, 0x6d, 0x70,
+ 0x69, 0x63, 0x75, 0x6a, 0x79, 0x6e, 0x6d, 0x79, 0x63, 0x71, 0x61, 0x75, 0x76, 0x79, 0x73, 0x66,
+ 0x76, 0x61, 0x6c, 0x71, 0x69, 0x71, 0x6b, 0x66, 0x65, 0x78, 0x78, 0x63, 0x6d, 0x78, 0x6c, 0x6e,
+ 0x6e, 0x79, 0x69, 0x6a, 0x6f, 0x75, 0x64, 0x6c, 0x65, 0x71, 0x68, 0x78, 0x67, 0x73, 0x76, 0x75,
+ 0x70, 0x6c, 0x6d, 0x6f, 0x6c, 0x64, 0x67, 0x73, 0x74, 0x73, 0x6b, 0x6b, 0x78, 0x68, 0x77, 0x62,
+ 0x74, 0x79, 0x72, 0x6f, 0x6f, 0x76, 0x73, 0x74, 0x76, 0x67, 0x6d, 0x6c, 0x71, 0x75, 0x61, 0x6a,
+ 0x69, 0x77, 0x6d, 0x68, 0x72, 0x72, 0x6b, 0x6b, 0x69, 0x6c, 0x6c, 0x63, 0x68, 0x62, 0x76, 0x6c,
+ 0x78, 0x69, 0x79, 0x62, 0x79, 0x68, 0x71, 0x62, 0x70, 0x72, 0x6e, 0x66, 0x71, 0x71, 0x67, 0x63,
+ 0x69, 0x67, 0x70, 0x6b, 0x76, 0x69, 0x79, 0x78, 0x73, 0x74, 0x69, 0x67, 0x6a, 0x78, 0x63, 0x66,
+ 0x65, 0x65, 0x64, 0x65, 0x72, 0x6d, 0x67, 0x72, 0x64, 0x70, 0x73, 0x66, 0x6e, 0x73, 0x70, 0x65,
+ 0x6e, 0x61, 0x68, 0x66, 0x66, 0x6a, 0x75, 0x6a, 0x65, 0x66, 0x70, 0x65, 0x66, 0x6b, 0x77, 0x63,
+ 0x62, 0x6c, 0x61, 0x73, 0x77, 0x70, 0x65, 0x72, 0x61, 0x69, 0x76, 0x62, 0x6b, 0x72, 0x64, 0x69,
+ 0x61, 0x62, 0x6d, 0x79, 0x64, 0x76, 0x61, 0x76, 0x69, 0x72, 0x64, 0x73, 0x76, 0x66, 0x67, 0x6b,
+ 0x71, 0x75, 0x6b, 0x75, 0x76, 0x68, 0x77, 0x63, 0x79, 0x76, 0x6c, 0x69, 0x67, 0x69, 0x76, 0x6d,
+ 0x79, 0x6c, 0x62, 0x65, 0x66, 0x76, 0x73, 0x78, 0x73, 0x75, 0x6e, 0x68, 0x6e, 0x61, 0x79, 0x75,
+ 0x6f, 0x77, 0x71, 0x6d, 0x62, 0x74, 0x63, 0x67, 0x66, 0x68, 0x63, 0x79, 0x72, 0x75, 0x6a, 0x78,
+ 0x79, 0x75, 0x66, 0x75, 0x6e, 0x61, 0x6a, 0x6b, 0x72, 0x75, 0x6f, 0x6f, 0x6f, 0x75, 0x78, 0x70,
+ 0x71, 0x68, 0x76, 0x67, 0x65, 0x63, 0x78, 0x78, 0x6d, 0x73, 0x63, 0x63, 0x74, 0x67, 0x71, 0x6c,
+ 0x75, 0x78, 0x74, 0x6e, 0x66, 0x62, 0x6e, 0x63, 0x78, 0x75, 0x64, 0x78, 0x69, 0x67, 0x6b, 0x64,
+ 0x6d, 0x6f, 0x79, 0x74, 0x61, 0x6f, 0x64, 0x67, 0x71, 0x77, 0x62, 0x63, 0x6d, 0x64, 0x76, 0x67,
+ 0x63, 0x79, 0x75, 0x74, 0x6f, 0x68, 0x73, 0x77, 0x69, 0x76, 0x6e, 0x68, 0x6a, 0x6a, 0x79, 0x75,
+ 0x65, 0x68, 0x69, 0x6e, 0x6d, 0x6a, 0x69, 0x68, 0x65, 0x6c, 0x77, 0x62, 0x61, 0x6b, 0x6d, 0x77,
+ 0x78, 0x70, 0x6a, 0x73, 0x79, 0x6b, 0x63, 0x6e, 0x79, 0x6d, 0x65, 0x72, 0x75, 0x66, 0x6c, 0x79,
+ 0x6f, 0x6e, 0x77, 0x69, 0x6d, 0x72, 0x70, 0x74, 0x75, 0x62, 0x75, 0x72, 0x6c, 0x65, 0x77, 0x66,
+ 0x78, 0x62, 0x70, 0x70, 0x6c, 0x6e, 0x69, 0x78, 0x73, 0x79, 0x71, 0x6b, 0x6d, 0x75, 0x62, 0x76,
+ 0x63, 0x65, 0x6d, 0x67, 0x6c, 0x66, 0x74, 0x6d, 0x6b, 0x72, 0x66, 0x6e, 0x61, 0x6f, 0x6d, 0x61,
+ 0x67, 0x6b, 0x66, 0x71, 0x71, 0x6a, 0x6e, 0x72, 0x78, 0x64, 0x71, 0x73, 0x63, 0x79, 0x74, 0x72,
+ 0x74, 0x70, 0x77, 0x66, 0x73, 0x79, 0x71, 0x65, 0x6a, 0x73, 0x66, 0x66, 0x67, 0x63, 0x65, 0x6f,
+ 0x71, 0x6c, 0x73, 0x79, 0x73, 0x67, 0x78, 0x74, 0x61, 0x6f, 0x6a, 0x75, 0x74, 0x70, 0x76, 0x78,
+ 0x69, 0x6b, 0x75, 0x78, 0x6f, 0x68, 0x6c, 0x65, 0x65, 0x61, 0x71, 0x65, 0x66, 0x67, 0x66, 0x74,
+ 0x79, 0x6c, 0x62, 0x74, 0x69, 0x66, 0x79, 0x61, 0x79, 0x77, 0x64, 0x69, 0x70, 0x67, 0x73, 0x6b,
+ 0x75, 0x74, 0x6c, 0x68, 0x74, 0x62, 0x64, 0x66, 0x76, 0x6a, 0x69, 0x72, 0x68, 0x73, 0x65, 0x63,
+ 0x74, 0x61, 0x6e, 0x6c, 0x63, 0x64, 0x67, 0x6d, 0x68, 0x75, 0x63, 0x6a, 0x68, 0x64, 0x79, 0x61,
+ 0x6b, 0x77, 0x6a, 0x66, 0x75, 0x79, 0x6b, 0x65, 0x6f, 0x77, 0x75, 0x62, 0x72, 0x77, 0x75, 0x73,
+ 0x64, 0x69, 0x79, 0x61, 0x73, 0x67, 0x6b, 0x68, 0x62, 0x75, 0x69, 0x6f, 0x6a, 0x64, 0x77, 0x68,
+ 0x65, 0x65, 0x66, 0x61, 0x61, 0x68, 0x72, 0x6a, 0x69, 0x6b, 0x77, 0x71, 0x78, 0x6c, 0x6b, 0x74,
+ 0x76, 0x6c, 0x6a, 0x70, 0x63, 0x6f, 0x71, 0x65, 0x65, 0x6f, 0x69, 0x71, 0x62, 0x73, 0x6c, 0x75,
+ 0x6a, 0x74, 0x61, 0x78, 0x6d, 0x62, 0x6e, 0x6e, 0x79, 0x69, 0x68, 0x69, 0x77, 0x6c, 0x6a, 0x6c,
+ 0x75, 0x62, 0x6e, 0x66, 0x67, 0x66, 0x62, 0x71, 0x75, 0x69, 0x66, 0x6c, 0x68, 0x75, 0x71, 0x61,
+ 0x78, 0x62, 0x69, 0x64, 0x6c, 0x66, 0x70, 0x61, 0x64, 0x61, 0x70, 0x63, 0x6b, 0x6d, 0x78, 0x62,
+ 0x6f, 0x65, 0x71, 0x73, 0x75, 0x66, 0x6a, 0x69, 0x6b, 0x64, 0x63, 0x6f, 0x63, 0x6d, 0x69, 0x73,
+ 0x79, 0x77, 0x69, 0x68, 0x66, 0x70, 0x76, 0x6e, 0x6f, 0x64, 0x72, 0x79, 0x70, 0x66, 0x6f, 0x63,
+ 0x79, 0x64, 0x76, 0x69, 0x70, 0x6e, 0x65, 0x67, 0x73, 0x6c, 0x63, 0x6f, 0x77, 0x67, 0x63, 0x6a,
+ 0x69, 0x6a, 0x6f, 0x74, 0x61, 0x68, 0x6b, 0x63, 0x6c, 0x63, 0x71, 0x68, 0x61, 0x70, 0x70, 0x64,
+ 0x62, 0x6b, 0x68, 0x75, 0x63, 0x6e, 0x78, 0x68, 0x61, 0x71, 0x6e, 0x77, 0x6f, 0x6b, 0x78, 0x78,
+ 0x74, 0x73, 0x70, 0x77, 0x66, 0x61, 0x6b, 0x77, 0x6c, 0x63, 0x76, 0x65, 0x79, 0x73, 0x76, 0x79,
+ 0x68, 0x78, 0x6b, 0x73, 0x61, 0x71, 0x77, 0x70, 0x61, 0x6b, 0x79, 0x73, 0x6b, 0x6c, 0x77, 0x63,
+ 0x69, 0x62, 0x77, 0x64, 0x68, 0x6c, 0x68, 0x79, 0x62, 0x72, 0x6a, 0x62, 0x72, 0x62, 0x61, 0x6b,
+ 0x71, 0x63, 0x63, 0x66, 0x62, 0x6a, 0x75, 0x65, 0x79, 0x6e, 0x65, 0x63, 0x6a, 0x75, 0x63, 0x79,
+ 0x74, 0x69, 0x69, 0x76, 0x78, 0x66, 0x68, 0x6c, 0x67, 0x77, 0x65, 0x6f, 0x76, 0x75, 0x61, 0x63,
+ 0x71, 0x73, 0x76, 0x64, 0x69, 0x64, 0x75, 0x64, 0x71, 0x62, 0x74, 0x6f, 0x74, 0x63, 0x66, 0x63,
+ 0x74, 0x70, 0x6d, 0x66, 0x6f, 0x6f, 0x6b, 0x79, 0x74, 0x61, 0x70, 0x76, 0x6d, 0x6f, 0x70, 0x64,
+ 0x74, 0x68, 0x6a, 0x6e, 0x6b, 0x70, 0x68, 0x64, 0x72, 0x72, 0x75, 0x63, 0x6e, 0x69, 0x69, 0x61,
+ 0x6f, 0x64, 0x76, 0x6a, 0x69, 0x6e, 0x79, 0x70, 0x61, 0x64, 0x76, 0x69, 0x6d, 0x6b, 0x67, 0x6a,
+ 0x6e, 0x67, 0x63, 0x6f, 0x78, 0x79, 0x73, 0x64, 0x77, 0x77, 0x72, 0x73, 0x61, 0x65, 0x6a, 0x65,
+ 0x6d, 0x62, 0x6b, 0x69, 0x78, 0x6d, 0x78, 0x74, 0x62, 0x75, 0x79, 0x6b, 0x66, 0x65, 0x73, 0x64,
+ 0x67, 0x72, 0x61, 0x64, 0x6c, 0x64, 0x61, 0x6f, 0x74, 0x64, 0x68, 0x73, 0x69, 0x6d, 0x69, 0x6f,
+ 0x6f, 0x71, 0x70, 0x77, 0x75, 0x77, 0x71, 0x73, 0x75, 0x63, 0x79, 0x74, 0x6f, 0x79, 0x6f, 0x63,
+ 0x64, 0x72, 0x78, 0x70, 0x63, 0x65, 0x62, 0x6e, 0x65, 0x64, 0x70, 0x6a, 0x6f, 0x63, 0x70, 0x6b,
+ 0x67, 0x73, 0x78, 0x61, 0x77, 0x70, 0x6e, 0x70, 0x78, 0x71, 0x61, 0x63, 0x6d, 0x68, 0x6a, 0x63,
+ 0x74, 0x73, 0x62, 0x75, 0x74, 0x75, 0x6c, 0x6b, 0x6c, 0x76, 0x6b, 0x65, 0x6a, 0x77, 0x77, 0x64,
+ 0x77, 0x6f, 0x6f, 0x70, 0x61, 0x74, 0x65, 0x74, 0x6d, 0x6f, 0x68, 0x77, 0x6b, 0x63, 0x65, 0x62,
+ 0x6a, 0x74, 0x6b, 0x78, 0x6d, 0x76, 0x63, 0x73, 0x6c, 0x68, 0x6f, 0x6d, 0x6c, 0x78, 0x79, 0x6b,
+ 0x74, 0x74, 0x66, 0x69, 0x76, 0x70, 0x6f, 0x6f, 0x78, 0x61, 0x6d, 0x68, 0x77, 0x75, 0x74, 0x79,
+ 0x68, 0x6f, 0x63, 0x64, 0x6b, 0x72, 0x65, 0x68, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x6d, 0x72, 0x75,
+ 0x74, 0x75, 0x6d, 0x6f, 0x6f, 0x65, 0x68, 0x64, 0x6a, 0x74, 0x64, 0x68, 0x62, 0x70, 0x6d, 0x68,
+ 0x73, 0x6d, 0x65, 0x76, 0x75, 0x64, 0x65, 0x66, 0x77, 0x69, 0x71, 0x6a, 0x64, 0x6b, 0x73, 0x6f,
+ 0x70, 0x6c, 0x6e, 0x6c, 0x6a, 0x66, 0x67, 0x78, 0x61, 0x79, 0x68, 0x63, 0x70, 0x79, 0x68, 0x6c,
+ 0x70, 0x6c, 0x76, 0x64, 0x68, 0x76, 0x67, 0x73, 0x65, 0x62, 0x65, 0x71, 0x71, 0x79, 0x72, 0x77,
+ 0x65, 0x77, 0x79, 0x78, 0x72, 0x75, 0x6e, 0x71, 0x68, 0x72, 0x64, 0x66, 0x64, 0x63, 0x68, 0x79,
+ 0x66, 0x70, 0x67, 0x64, 0x75, 0x62, 0x79, 0x66, 0x62, 0x74, 0x6b, 0x72, 0x6a, 0x70, 0x78, 0x61,
+ 0x70, 0x6e, 0x76, 0x63, 0x6a, 0x62, 0x76, 0x69, 0x68, 0x77, 0x6c, 0x73, 0x62, 0x68, 0x65, 0x6f,
+ 0x6f, 0x6c, 0x65, 0x6e, 0x66, 0x6a, 0x71, 0x69, 0x71, 0x66, 0x63, 0x6e, 0x63, 0x6d, 0x67, 0x78,
+ 0x72, 0x72, 0x66, 0x79, 0x66, 0x75, 0x72, 0x73, 0x69, 0x76, 0x75, 0x75, 0x69, 0x64, 0x6f, 0x6e,
+ 0x67, 0x67, 0x78, 0x72, 0x72, 0x71, 0x71, 0x69, 0x61, 0x66, 0x6f, 0x6d, 0x67, 0x68, 0x63, 0x62,
+ 0x6b, 0x6b, 0x6c, 0x61, 0x63, 0x65, 0x62, 0x76, 0x72, 0x74, 0x6c, 0x78, 0x68, 0x64, 0x71, 0x78,
+ 0x76, 0x6c, 0x72, 0x71, 0x73, 0x79, 0x66, 0x67, 0x72, 0x76, 0x6c, 0x75, 0x68, 0x6b, 0x70, 0x6e,
+ 0x63, 0x62, 0x6d, 0x67, 0x64, 0x70, 0x68, 0x68, 0x6c, 0x65, 0x77, 0x61, 0x75, 0x71, 0x61, 0x6d,
+ 0x6d, 0x67, 0x6c, 0x69, 0x63, 0x6a, 0x6d, 0x62, 0x6a, 0x77, 0x73, 0x6d, 0x65, 0x6f, 0x73, 0x76,
+ 0x69, 0x6f, 0x70, 0x66, 0x64, 0x73, 0x69, 0x72, 0x71, 0x6a, 0x79, 0x74, 0x6e, 0x65, 0x6a, 0x6b,
+ 0x63, 0x76, 0x66, 0x66, 0x72, 0x6e, 0x79, 0x6d, 0x6b, 0x6d, 0x75, 0x76, 0x68, 0x70, 0x6a, 0x78,
+ 0x6a, 0x6c, 0x78, 0x68, 0x77, 0x66, 0x75, 0x62, 0x6c, 0x75, 0x79, 0x69, 0x6a, 0x6e, 0x78, 0x74,
+ 0x6c, 0x6e, 0x65, 0x78, 0x64, 0x6f, 0x78, 0x68, 0x76, 0x6c, 0x75, 0x73, 0x62, 0x62, 0x79, 0x68,
+ 0x6d, 0x77, 0x75, 0x6d, 0x67, 0x6c, 0x68, 0x72, 0x72, 0x65, 0x62, 0x68, 0x67, 0x61, 0x74, 0x63,
+ 0x72, 0x6c, 0x63, 0x65, 0x74, 0x78, 0x6a, 0x68, 0x71, 0x71, 0x6b, 0x64, 0x79, 0x77, 0x73, 0x68,
+ 0x69, 0x76, 0x64, 0x62, 0x77, 0x75, 0x78, 0x76, 0x67, 0x65, 0x75, 0x67, 0x75, 0x68, 0x6e, 0x64,
+ 0x79, 0x67, 0x77, 0x6f, 0x68, 0x70, 0x69, 0x64, 0x61, 0x62, 0x69, 0x76, 0x64, 0x78, 0x71, 0x6e,
+ 0x6e, 0x69, 0x61, 0x68, 0x75, 0x76, 0x6c, 0x74, 0x68, 0x63, 0x6d, 0x79, 0x73, 0x69, 0x61, 0x64,
+ 0x6c, 0x69, 0x74, 0x75, 0x69, 0x70, 0x69, 0x73, 0x74, 0x6c, 0x62, 0x6c, 0x6e, 0x6c, 0x6f, 0x73,
+ 0x61, 0x63, 0x6e, 0x66, 0x72, 0x63, 0x65, 0x71, 0x62, 0x68, 0x77, 0x74, 0x6d, 0x62, 0x62, 0x75,
+ 0x69, 0x75, 0x63, 0x63, 0x6b, 0x68, 0x70, 0x71, 0x63, 0x6f, 0x65, 0x70, 0x6a, 0x78, 0x73, 0x61,
+ 0x68, 0x75, 0x71, 0x66, 0x6a, 0x77, 0x6b, 0x6a, 0x76, 0x79, 0x61, 0x64, 0x6a, 0x6f, 0x68, 0x72,
+ 0x78, 0x64, 0x75, 0x6f, 0x64, 0x64, 0x6d, 0x65, 0x75, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x65, 0x6d,
+ 0x6d, 0x72, 0x65, 0x62, 0x70, 0x64, 0x64, 0x69, 0x6b, 0x6a, 0x63, 0x78, 0x6c, 0x74, 0x61, 0x68,
+ 0x61, 0x6e, 0x6c, 0x75, 0x73, 0x62, 0x62, 0x6e, 0x71, 0x62, 0x6a, 0x78, 0x65, 0x63, 0x61, 0x65,
+ 0x75, 0x73, 0x66, 0x61, 0x6b, 0x74, 0x6b, 0x65, 0x6f, 0x69, 0x63, 0x6e, 0x6f, 0x69, 0x6b, 0x62,
+ 0x65, 0x73, 0x64, 0x64, 0x6d, 0x72, 0x76, 0x66, 0x76, 0x76, 0x72, 0x6d, 0x67, 0x6a, 0x65, 0x6d,
+ 0x73, 0x65, 0x67, 0x79, 0x79, 0x63, 0x62, 0x6a, 0x6e, 0x68, 0x71, 0x65, 0x69, 0x6a, 0x6e, 0x63,
+ 0x76, 0x72, 0x6c, 0x67, 0x6f, 0x6d, 0x64, 0x74, 0x61, 0x76, 0x6d, 0x76, 0x6d, 0x75, 0x64, 0x6a,
+ 0x64, 0x63, 0x75, 0x70, 0x64, 0x6a, 0x68, 0x72, 0x65, 0x64, 0x79, 0x79, 0x72, 0x6a, 0x6d, 0x77,
+ 0x75, 0x64, 0x6f, 0x72, 0x63, 0x65, 0x74, 0x72, 0x6e, 0x67, 0x66, 0x70, 0x63, 0x6d, 0x64, 0x65,
+ 0x63, 0x72, 0x69, 0x6a, 0x74, 0x68, 0x77, 0x74, 0x72, 0x79, 0x77, 0x71, 0x6c, 0x6d, 0x79, 0x6f,
+ 0x62, 0x79, 0x72, 0x61, 0x62, 0x6a, 0x77, 0x76, 0x61, 0x6d, 0x74, 0x65, 0x73, 0x69, 0x6f, 0x74,
+ 0x76, 0x68, 0x71, 0x76, 0x67, 0x76, 0x62, 0x6f, 0x63, 0x6f, 0x6c, 0x77, 0x69, 0x6a, 0x62, 0x64,
+ 0x62, 0x74, 0x6a, 0x69, 0x71, 0x6a, 0x6c, 0x78, 0x67, 0x78, 0x67, 0x65, 0x65, 0x70, 0x79, 0x65,
+ 0x71, 0x75, 0x6c, 0x6e, 0x6b, 0x68, 0x6e, 0x62, 0x78, 0x65, 0x76, 0x69, 0x6d, 0x66, 0x6a, 0x70,
+ 0x6c, 0x63, 0x68, 0x75, 0x6a, 0x6c, 0x73, 0x75, 0x68, 0x6d, 0x63, 0x73, 0x68, 0x6e, 0x64, 0x6f,
+ 0x61, 0x65, 0x75, 0x6a, 0x75, 0x62, 0x76, 0x79, 0x6d, 0x64, 0x71, 0x76, 0x78, 0x62, 0x6a, 0x75,
+ 0x6c, 0x6b, 0x77, 0x79, 0x6a, 0x6a, 0x77, 0x74, 0x6a, 0x72, 0x73, 0x66, 0x72, 0x63, 0x69, 0x75,
+ 0x62, 0x70, 0x77, 0x6e, 0x62, 0x65, 0x71, 0x71, 0x6c, 0x76, 0x6b, 0x73, 0x6e, 0x64, 0x74, 0x75,
+ 0x62, 0x79, 0x71, 0x77, 0x75, 0x73, 0x6f, 0x76, 0x6d, 0x6a, 0x73, 0x72, 0x62, 0x6d, 0x69, 0x73,
+ 0x6a, 0x6a, 0x6d, 0x6f, 0x73, 0x63, 0x72, 0x75, 0x6c, 0x6c, 0x68, 0x67, 0x70, 0x74, 0x61, 0x73,
+ 0x78, 0x6d, 0x63, 0x6c, 0x6d, 0x74, 0x71, 0x65, 0x67, 0x74, 0x75, 0x6a, 0x6f, 0x6a, 0x6e, 0x64,
+ 0x6d, 0x68, 0x63, 0x77, 0x79, 0x6a, 0x65, 0x6b, 0x62, 0x6a, 0x66, 0x6b, 0x76, 0x73, 0x74, 0x75,
+ 0x6b, 0x6a, 0x79, 0x69, 0x6d, 0x6c, 0x79, 0x6a, 0x79, 0x79, 0x79, 0x78, 0x71, 0x75, 0x76, 0x67,
+ 0x70, 0x64, 0x69, 0x74, 0x70, 0x61, 0x62, 0x79, 0x65, 0x6e, 0x65, 0x6e, 0x77, 0x63, 0x67, 0x73,
+ 0x79, 0x77, 0x6a, 0x73, 0x78, 0x61, 0x62, 0x76, 0x74, 0x6f, 0x6b, 0x6f, 0x75, 0x6b, 0x6f, 0x67,
+ 0x6b, 0x67, 0x76, 0x65, 0x6b, 0x70, 0x6d, 0x6a, 0x61, 0x70, 0x6c, 0x67, 0x61, 0x64, 0x74, 0x63,
+ 0x63, 0x6b, 0x73, 0x74, 0x68, 0x6d, 0x6d, 0x69, 0x79, 0x65, 0x67, 0x71, 0x6e, 0x66, 0x6b, 0x72,
+ 0x69, 0x75, 0x6f, 0x65, 0x61, 0x75, 0x71, 0x67, 0x63, 0x70, 0x6b, 0x63, 0x76, 0x73, 0x64, 0x6a,
+ 0x6e, 0x61, 0x75, 0x72, 0x68, 0x6d, 0x62, 0x68, 0x65, 0x67, 0x79, 0x63, 0x6f, 0x66, 0x68, 0x68,
+ 0x66, 0x61, 0x79, 0x68, 0x62, 0x74, 0x70, 0x69, 0x6e, 0x6a, 0x78, 0x65, 0x6c, 0x77, 0x79, 0x73,
+ 0x69, 0x62, 0x71, 0x63, 0x62, 0x73, 0x65, 0x65, 0x6b, 0x6f, 0x6f, 0x71, 0x6b, 0x62, 0x6f, 0x68,
+ 0x75, 0x66, 0x78, 0x62, 0x73, 0x6d, 0x62, 0x71, 0x64, 0x6a, 0x6c, 0x63, 0x76, 0x66, 0x66, 0x69,
+ 0x62, 0x6c, 0x6a, 0x69, 0x78, 0x78, 0x65, 0x68, 0x73, 0x70, 0x65, 0x6f, 0x67, 0x73, 0x79, 0x69,
+ 0x67, 0x71, 0x70, 0x62, 0x6e, 0x65, 0x63, 0x62, 0x69, 0x70, 0x6e, 0x6f, 0x70, 0x77, 0x64, 0x62,
+ 0x6e, 0x6e, 0x69, 0x66, 0x61, 0x61, 0x79, 0x62, 0x68, 0x72, 0x6a, 0x75, 0x6c, 0x6f, 0x73, 0x75,
+ 0x6a, 0x73, 0x62, 0x6e, 0x6f, 0x65, 0x65, 0x62, 0x6d, 0x6a, 0x71, 0x6c, 0x6c, 0x77, 0x71, 0x6c,
+ 0x69, 0x6a, 0x6c, 0x74, 0x6a, 0x6b, 0x73, 0x78, 0x72, 0x6e, 0x68, 0x64, 0x69, 0x63, 0x73, 0x78,
+ 0x77, 0x6b, 0x6a, 0x6b, 0x78, 0x72, 0x61, 0x6d, 0x6b, 0x64, 0x71, 0x75, 0x6d, 0x65, 0x71, 0x71,
+ 0x6b, 0x61, 0x72, 0x71, 0x6e, 0x76, 0x77, 0x6e, 0x61, 0x66, 0x6d, 0x66, 0x68, 0x6a, 0x64, 0x6a,
+ 0x66, 0x75, 0x66, 0x6c, 0x76, 0x64, 0x73, 0x6a, 0x6c, 0x63, 0x6d, 0x71, 0x67, 0x73, 0x74, 0x76,
+ 0x74, 0x66, 0x70, 0x75, 0x6c, 0x6c, 0x63, 0x74, 0x70, 0x68, 0x6e, 0x78, 0x61, 0x73, 0x6a, 0x6f,
+ 0x67, 0x78, 0x6d, 0x79, 0x62, 0x68, 0x6f, 0x66, 0x64, 0x78, 0x61, 0x64, 0x6d, 0x61, 0x74, 0x72,
+ 0x61, 0x77, 0x6a, 0x79, 0x75, 0x66, 0x6f, 0x6c, 0x79, 0x78, 0x76, 0x76, 0x72, 0x69, 0x6f, 0x79,
+ 0x65, 0x62, 0x65, 0x73, 0x6e, 0x61, 0x6e, 0x71, 0x63, 0x68, 0x6e, 0x76, 0x64, 0x6f, 0x69, 0x62,
+ 0x6e, 0x6c, 0x6e, 0x79, 0x71, 0x79, 0x73, 0x72, 0x71, 0x65, 0x6b, 0x65, 0x65, 0x76, 0x65, 0x6d,
+ 0x72, 0x69, 0x77, 0x71, 0x70, 0x78, 0x78, 0x6b, 0x64, 0x70, 0x77, 0x79, 0x73, 0x75, 0x74, 0x62,
+ 0x79, 0x6b, 0x79, 0x70, 0x71, 0x70, 0x76, 0x6f, 0x71, 0x71, 0x66, 0x62, 0x6d, 0x6b, 0x74, 0x73,
+ 0x6c, 0x78, 0x77, 0x68, 0x6d, 0x6d, 0x65, 0x75, 0x74, 0x64, 0x71, 0x72, 0x6c, 0x64, 0x6f, 0x68,
+ 0x63, 0x75, 0x78, 0x68, 0x6f, 0x71, 0x61, 0x68, 0x78, 0x78, 0x73, 0x62, 0x74, 0x70, 0x61, 0x6e,
+ 0x68, 0x72, 0x71, 0x6c, 0x72, 0x71, 0x70, 0x77, 0x68, 0x6b, 0x64, 0x63, 0x6c, 0x76, 0x79, 0x70,
+ 0x76, 0x6f, 0x6a, 0x65, 0x67, 0x74, 0x73, 0x6a, 0x72, 0x64, 0x6a, 0x77, 0x6b, 0x61, 0x62, 0x69,
+ 0x70, 0x75, 0x6c, 0x6f, 0x6e, 0x6e, 0x6b, 0x76, 0x66, 0x71, 0x74, 0x62, 0x71, 0x75, 0x6c, 0x70,
+ 0x71, 0x70, 0x73, 0x61, 0x6a, 0x61, 0x79, 0x78, 0x74, 0x77, 0x77, 0x61, 0x76, 0x6f, 0x6c, 0x75,
+ 0x68, 0x63, 0x72, 0x77, 0x64, 0x64, 0x77, 0x6e, 0x67, 0x6a, 0x6c, 0x74, 0x73, 0x78, 0x72, 0x6a,
+ 0x6e, 0x69, 0x70, 0x79, 0x64, 0x62, 0x63, 0x76, 0x65, 0x76, 0x6d, 0x6f, 0x6b, 0x79, 0x63, 0x78,
+ 0x68, 0x70, 0x6e, 0x75, 0x64, 0x77, 0x64, 0x6e, 0x6c, 0x6e, 0x64, 0x78, 0x72, 0x73, 0x70, 0x74,
+ 0x72, 0x69, 0x76, 0x74, 0x61, 0x77, 0x64, 0x72, 0x65, 0x67, 0x6e, 0x68, 0x68, 0x67, 0x64, 0x6c,
+ 0x6f, 0x6e, 0x6f, 0x76, 0x71, 0x6c, 0x6f, 0x67, 0x75, 0x77, 0x6a, 0x72, 0x6d, 0x72, 0x70, 0x79,
+ 0x75, 0x72, 0x70, 0x78, 0x6f, 0x6e, 0x65, 0x6d, 0x74, 0x72, 0x6a, 0x62, 0x63, 0x76, 0x69, 0x68,
+ 0x77, 0x78, 0x6c, 0x72, 0x77, 0x63, 0x75, 0x6a, 0x6b, 0x74, 0x73, 0x64, 0x64, 0x70, 0x74, 0x61,
+ 0x6e, 0x6e, 0x62, 0x77, 0x76, 0x71, 0x78, 0x6a, 0x71, 0x76, 0x74, 0x61, 0x64, 0x75, 0x75, 0x63,
+ 0x61, 0x71, 0x78, 0x67, 0x77, 0x71, 0x66, 0x6e, 0x6c, 0x73, 0x61, 0x6e, 0x77, 0x62, 0x71, 0x63,
+ 0x6b, 0x6f, 0x6c, 0x6c, 0x6d, 0x77, 0x6a, 0x78, 0x77, 0x64, 0x6d, 0x70, 0x6a, 0x6c, 0x73, 0x78,
+ 0x68, 0x76, 0x6e, 0x6d, 0x6c, 0x70, 0x6f, 0x65, 0x64, 0x74, 0x61, 0x61, 0x73, 0x66, 0x76, 0x6a,
+ 0x62, 0x71, 0x6f, 0x6f, 0x73, 0x68, 0x79, 0x6b, 0x6b, 0x70, 0x73, 0x62, 0x75, 0x6a, 0x74, 0x73,
+ 0x75, 0x65, 0x6b, 0x6b, 0x6e, 0x74, 0x75, 0x75, 0x70, 0x6b, 0x70, 0x78, 0x77, 0x64, 0x65, 0x75,
+ 0x6f, 0x75, 0x73, 0x64, 0x66, 0x72, 0x67, 0x65, 0x77, 0x68, 0x66, 0x6b, 0x62, 0x6d, 0x62, 0x67,
+ 0x6d, 0x70, 0x66, 0x67, 0x62, 0x71, 0x68, 0x6f, 0x78, 0x68, 0x76, 0x67, 0x6d, 0x61, 0x63, 0x67,
+ 0x6b, 0x75, 0x65, 0x65, 0x67, 0x63, 0x78, 0x61, 0x6e, 0x78, 0x66, 0x71, 0x75, 0x6d, 0x66, 0x73,
+ 0x69, 0x75, 0x62, 0x78, 0x70, 0x70, 0x73, 0x75, 0x67, 0x67, 0x79, 0x63, 0x63, 0x62, 0x67, 0x72,
+ 0x69, 0x62, 0x6e, 0x78, 0x73, 0x77, 0x78, 0x79, 0x64, 0x70, 0x64, 0x66, 0x72, 0x66, 0x63, 0x6e,
+ 0x74, 0x70, 0x62, 0x77, 0x6e, 0x61, 0x77, 0x64, 0x79, 0x69, 0x74, 0x70, 0x63, 0x62, 0x6e, 0x6d,
+ 0x72, 0x71, 0x6c, 0x6b, 0x6f, 0x6d, 0x67, 0x69, 0x68, 0x6f, 0x79, 0x6a, 0x73, 0x67, 0x70, 0x65,
+ 0x62, 0x6a, 0x75, 0x6f, 0x67, 0x67, 0x68, 0x6d, 0x6f, 0x64, 0x64, 0x70, 0x69, 0x70, 0x64, 0x62,
+ 0x67, 0x76, 0x64, 0x62, 0x6e, 0x71, 0x63, 0x66, 0x65, 0x70, 0x68, 0x78, 0x6a, 0x66, 0x6f, 0x68,
+ 0x70, 0x78, 0x67, 0x70, 0x6e, 0x63, 0x6c, 0x67, 0x61, 0x62, 0x67, 0x69, 0x77, 0x73, 0x61, 0x77,
+ 0x6f, 0x6d, 0x6b, 0x6f, 0x67, 0x68, 0x66, 0x77, 0x6c, 0x71, 0x63, 0x72, 0x74, 0x79, 0x77, 0x65,
+ 0x66, 0x6d, 0x77, 0x77, 0x74, 0x71, 0x63, 0x78, 0x64, 0x69, 0x79, 0x74, 0x71, 0x74, 0x74, 0x71,
+ 0x75, 0x78, 0x62, 0x6b, 0x78, 0x6d, 0x6c, 0x70, 0x71, 0x71, 0x78, 0x70, 0x69, 0x75, 0x6b, 0x6b,
+ 0x63, 0x6e, 0x72, 0x68, 0x67, 0x6c, 0x71, 0x69, 0x6c, 0x75, 0x70, 0x6a, 0x61, 0x6d, 0x66, 0x76,
+ 0x73, 0x6a, 0x74, 0x65, 0x67, 0x66, 0x71, 0x6f, 0x68, 0x6f, 0x6e, 0x71, 0x78, 0x72, 0x6b, 0x75,
+ 0x79, 0x77, 0x6c, 0x66, 0x61, 0x6b, 0x75, 0x65, 0x6d, 0x62, 0x68, 0x6c, 0x70, 0x72, 0x73, 0x72,
+ 0x63, 0x64, 0x67, 0x69, 0x67, 0x72, 0x64, 0x6b, 0x61, 0x77, 0x62, 0x6f, 0x79, 0x6a, 0x68, 0x64,
+ 0x65, 0x6e, 0x76, 0x72, 0x68, 0x6e, 0x6e, 0x77, 0x62, 0x68, 0x71, 0x62, 0x66, 0x69, 0x73, 0x64,
+ 0x6f, 0x78, 0x6c, 0x6a, 0x77, 0x6b, 0x62, 0x76, 0x71, 0x70, 0x71, 0x70, 0x6e, 0x6e, 0x76, 0x68,
+ 0x6a, 0x6c, 0x6b, 0x63, 0x71, 0x79, 0x68, 0x6a, 0x78, 0x6e, 0x74, 0x66, 0x73, 0x65, 0x6d, 0x77,
+ 0x6a, 0x67, 0x79, 0x67, 0x64, 0x71, 0x79, 0x62, 0x75, 0x68, 0x65, 0x74, 0x6c, 0x6a, 0x72, 0x6b,
+ 0x79, 0x73, 0x71, 0x74, 0x6b, 0x63, 0x70, 0x70, 0x69, 0x65, 0x69, 0x76, 0x79, 0x69, 0x6b, 0x70,
+ 0x6a, 0x76, 0x6a, 0x65, 0x63, 0x73, 0x70, 0x69, 0x6a, 0x6c, 0x71, 0x63, 0x72, 0x6a, 0x73, 0x79,
+ 0x62, 0x65, 0x69, 0x79, 0x63, 0x6c, 0x75, 0x65, 0x6d, 0x6e, 0x73, 0x63, 0x66, 0x71, 0x75, 0x61,
+ 0x72, 0x79, 0x75, 0x6f, 0x61, 0x61, 0x6c, 0x68, 0x6e, 0x77, 0x66, 0x74, 0x6f, 0x70, 0x75, 0x65,
+ 0x66, 0x66, 0x65, 0x78, 0x72, 0x62, 0x67, 0x6d, 0x70, 0x69, 0x64, 0x65, 0x68, 0x63, 0x67, 0x72,
+ 0x79, 0x6c, 0x67, 0x64, 0x6d, 0x68, 0x70, 0x6a, 0x73, 0x6b, 0x62, 0x6e, 0x62, 0x66, 0x76, 0x77,
+ 0x69, 0x6a, 0x6e, 0x61, 0x74, 0x6d, 0x73, 0x72, 0x6f, 0x79, 0x6b, 0x68, 0x71, 0x62, 0x79, 0x6e,
+ 0x62, 0x6b, 0x67, 0x79, 0x77, 0x6e, 0x64, 0x6d, 0x66, 0x64, 0x73, 0x6f, 0x77, 0x70, 0x65, 0x6b,
+ 0x65, 0x71, 0x65, 0x6d, 0x69, 0x67, 0x6c, 0x75, 0x72, 0x66, 0x77, 0x61, 0x76, 0x6e, 0x69, 0x74,
+ 0x61, 0x72, 0x74, 0x62, 0x68, 0x67, 0x72, 0x6a, 0x65, 0x77, 0x6e, 0x75, 0x75, 0x79, 0x6a, 0x6d,
+ 0x6f, 0x75, 0x65, 0x6c, 0x6f, 0x6a, 0x71, 0x74, 0x74, 0x6f, 0x6e, 0x71, 0x71, 0x79, 0x62, 0x74,
+ 0x77, 0x6a, 0x61, 0x75, 0x74, 0x74, 0x6d, 0x61, 0x6f, 0x77, 0x77, 0x76, 0x71, 0x64, 0x79, 0x73,
+ 0x61, 0x69, 0x66, 0x6a, 0x61, 0x61, 0x64, 0x62, 0x74, 0x77, 0x6a, 0x6d, 0x6e, 0x6f, 0x68, 0x78,
+ 0x75, 0x6d, 0x6c, 0x70, 0x77, 0x74, 0x71, 0x78, 0x77, 0x75, 0x6c, 0x62, 0x72, 0x73, 0x63, 0x6c,
+ 0x74, 0x63, 0x64, 0x64, 0x73, 0x68, 0x74, 0x64, 0x6b, 0x63, 0x72, 0x76, 0x6d, 0x68, 0x74, 0x6c,
+ 0x6a, 0x79, 0x70, 0x68, 0x6b, 0x6c, 0x68, 0x61, 0x77, 0x69, 0x71, 0x64, 0x66, 0x64, 0x61, 0x76,
+ 0x61, 0x6a, 0x68, 0x75, 0x73, 0x6e, 0x70, 0x68, 0x77, 0x69, 0x67, 0x6d, 0x67, 0x6e, 0x73, 0x79,
+ 0x6a, 0x70, 0x77, 0x65, 0x6f, 0x68, 0x67, 0x6a, 0x66, 0x64, 0x6c, 0x6e, 0x6e, 0x72, 0x67, 0x70,
+ 0x76, 0x78, 0x68, 0x6d, 0x69, 0x66, 0x76, 0x77, 0x77, 0x66, 0x63, 0x74, 0x74, 0x68, 0x62, 0x65,
+ 0x74, 0x65, 0x74, 0x65, 0x6f, 0x73, 0x73, 0x69, 0x62, 0x64, 0x6a, 0x72, 0x75, 0x72, 0x6c, 0x79,
+ 0x61, 0x77, 0x66, 0x62, 0x71, 0x63, 0x71, 0x66, 0x6f, 0x65, 0x6d, 0x62, 0x61, 0x66, 0x73, 0x71,
+ 0x77, 0x75, 0x62, 0x75, 0x63, 0x75, 0x68, 0x6a, 0x79, 0x68, 0x68, 0x77, 0x64, 0x79, 0x67, 0x67,
+ 0x64, 0x76, 0x69, 0x78, 0x6b, 0x6b, 0x64, 0x61, 0x64, 0x61, 0x76, 0x64, 0x6f, 0x75, 0x71, 0x6b,
+ 0x72, 0x70, 0x72, 0x6b, 0x66, 0x6c, 0x68, 0x73, 0x66, 0x62, 0x73, 0x75, 0x66, 0x79, 0x79, 0x71,
+ 0x69, 0x73, 0x6b, 0x70, 0x65, 0x67, 0x79, 0x66, 0x65, 0x69, 0x65, 0x6a, 0x64, 0x73, 0x79, 0x74,
+ 0x65, 0x64, 0x76, 0x77, 0x68, 0x74, 0x72, 0x79, 0x68, 0x62, 0x66, 0x74, 0x67, 0x78, 0x70, 0x6d,
+ 0x68, 0x70, 0x6d, 0x6b, 0x73, 0x74, 0x75, 0x76, 0x64, 0x6f, 0x6f, 0x79, 0x76, 0x68, 0x67, 0x6c,
+ 0x78, 0x72, 0x64, 0x67, 0x63, 0x65, 0x70, 0x6f, 0x61, 0x77, 0x62, 0x6b, 0x61, 0x64, 0x77, 0x6c,
+ 0x70, 0x74, 0x64, 0x70, 0x6d, 0x6e, 0x71, 0x68, 0x79, 0x73, 0x72, 0x6c, 0x72, 0x70, 0x77, 0x62,
+ 0x68, 0x61, 0x6d, 0x68, 0x6a, 0x73, 0x75, 0x6e, 0x74, 0x61, 0x61, 0x6f, 0x75, 0x67, 0x79, 0x77,
+ 0x67, 0x6d, 0x61, 0x70, 0x64, 0x76, 0x69, 0x67, 0x70, 0x79, 0x6f, 0x78, 0x70, 0x66, 0x76, 0x6a,
+ 0x6e, 0x69, 0x63, 0x66, 0x73, 0x69, 0x78, 0x6e, 0x66, 0x64, 0x62, 0x62, 0x66, 0x75, 0x61, 0x61,
+ 0x6c, 0x63, 0x79, 0x6b, 0x63, 0x6c, 0x6e, 0x64, 0x6e, 0x6a, 0x68, 0x66, 0x64, 0x63, 0x61, 0x75,
+ 0x79, 0x65, 0x67, 0x6b, 0x6a, 0x76, 0x75, 0x74, 0x6c, 0x63, 0x78, 0x63, 0x78, 0x61, 0x70, 0x66,
+ 0x64, 0x76, 0x71, 0x73, 0x73, 0x6f, 0x65, 0x64, 0x6f, 0x73, 0x75, 0x79, 0x72, 0x70, 0x65, 0x6f,
+ 0x65, 0x64, 0x70, 0x62, 0x6f, 0x73, 0x69, 0x72, 0x62, 0x76, 0x67, 0x67, 0x62, 0x77, 0x70, 0x67,
+ 0x6a, 0x78, 0x65, 0x70, 0x64, 0x73, 0x6d, 0x79, 0x77, 0x71, 0x68, 0x6b, 0x79, 0x76, 0x69, 0x65,
+ 0x75, 0x66, 0x75, 0x76, 0x6b, 0x6b, 0x71, 0x6d, 0x6b, 0x6e, 0x67, 0x6b, 0x6f, 0x71, 0x77, 0x6f,
+ 0x61, 0x70, 0x61, 0x62, 0x64, 0x75, 0x63, 0x6c, 0x77, 0x65, 0x6a, 0x77, 0x62, 0x69, 0x64, 0x6a,
+ 0x71, 0x77, 0x65, 0x66, 0x6c, 0x6f, 0x67, 0x6c, 0x62, 0x74, 0x79, 0x69, 0x62, 0x79, 0x6c, 0x6f,
+ 0x71, 0x6e, 0x6c, 0x68, 0x66, 0x6e, 0x66, 0x76, 0x62, 0x6a, 0x75, 0x73, 0x6c, 0x67, 0x68, 0x73,
+ 0x77, 0x73, 0x76, 0x77, 0x6f, 0x79, 0x74, 0x6c, 0x75, 0x71, 0x78, 0x6e, 0x63, 0x62, 0x72, 0x64,
+ 0x74, 0x74, 0x73, 0x70, 0x68, 0x66, 0x76, 0x75, 0x77, 0x6e, 0x6b, 0x76, 0x73, 0x73, 0x70, 0x63,
+ 0x6a, 0x78, 0x77, 0x77, 0x70, 0x6e, 0x63, 0x6c, 0x77, 0x78, 0x6e, 0x72, 0x6a, 0x6d, 0x62, 0x6e,
+ 0x6d, 0x75, 0x72, 0x63, 0x64, 0x78, 0x62, 0x66, 0x71, 0x67, 0x72, 0x6b, 0x62, 0x73, 0x75, 0x70,
+ 0x74, 0x6d, 0x67, 0x70, 0x63, 0x69, 0x69, 0x76, 0x68, 0x74, 0x70, 0x62, 0x75, 0x6a, 0x72, 0x75,
+ 0x79, 0x6e, 0x77, 0x68, 0x63, 0x65, 0x71, 0x68, 0x66, 0x66, 0x79, 0x6b, 0x6b, 0x6b, 0x62, 0x74,
+ 0x68, 0x63, 0x69, 0x6d, 0x6f, 0x71, 0x78, 0x66, 0x6e, 0x64, 0x70, 0x78, 0x6e, 0x67, 0x66, 0x62,
+ 0x66, 0x6f, 0x79, 0x6d, 0x6a, 0x77, 0x6e, 0x6b, 0x78, 0x64, 0x65, 0x64, 0x6e, 0x64, 0x68, 0x73,
+ 0x75, 0x72, 0x70, 0x71, 0x67, 0x63, 0x76, 0x61, 0x6a, 0x61, 0x66, 0x6f, 0x79, 0x74, 0x68, 0x6a,
+ 0x70, 0x77, 0x75, 0x70, 0x69, 0x76, 0x70, 0x72, 0x76, 0x61, 0x78, 0x6b, 0x62, 0x69, 0x68, 0x77,
+ 0x6c, 0x6a, 0x78, 0x65, 0x71, 0x66, 0x61, 0x6a, 0x74, 0x72, 0x66, 0x72, 0x6e, 0x6c, 0x74, 0x73,
+ 0x69, 0x68, 0x6f, 0x75, 0x71, 0x6b, 0x6b, 0x69, 0x70, 0x71, 0x73, 0x74, 0x61, 0x71, 0x69, 0x77,
+ 0x64, 0x6d, 0x6c, 0x77, 0x6f, 0x76, 0x76, 0x6f, 0x62, 0x63, 0x74, 0x73, 0x73, 0x74, 0x62, 0x67,
+ 0x6f, 0x71, 0x6f, 0x70, 0x73, 0x65, 0x6a, 0x71, 0x75, 0x62, 0x63, 0x79, 0x65, 0x61, 0x64, 0x75,
+ 0x79, 0x69, 0x74, 0x64, 0x6a, 0x61, 0x6a, 0x77, 0x63, 0x6c, 0x67, 0x69, 0x6e, 0x62, 0x73, 0x68,
+ 0x65, 0x64, 0x72, 0x62, 0x6f, 0x68, 0x73, 0x66, 0x6b, 0x76, 0x79, 0x6a, 0x73, 0x6b, 0x70, 0x69,
+ 0x62, 0x62, 0x71, 0x76, 0x6f, 0x6d, 0x6e, 0x6f, 0x71, 0x66, 0x6f, 0x70, 0x75, 0x73, 0x67, 0x72,
+ 0x64, 0x78, 0x6a, 0x79, 0x76, 0x68, 0x73, 0x65, 0x65, 0x71, 0x6f, 0x66, 0x6e, 0x61, 0x62, 0x72,
+ 0x70, 0x62, 0x65, 0x6b, 0x66, 0x73, 0x73, 0x76, 0x6e, 0x69, 0x79, 0x66, 0x69, 0x6f, 0x73, 0x6e,
+ 0x77, 0x72, 0x79, 0x65, 0x69, 0x73, 0x78, 0x61, 0x68, 0x6e, 0x6c, 0x79, 0x70, 0x64, 0x75, 0x6b,
+ 0x70, 0x73, 0x77, 0x75, 0x65, 0x79, 0x77, 0x73, 0x61, 0x72, 0x6b, 0x69, 0x79, 0x74, 0x63, 0x6a,
+ 0x64, 0x68, 0x63, 0x69, 0x61, 0x6a, 0x76, 0x74, 0x63, 0x6a, 0x79, 0x75, 0x6d, 0x70, 0x67, 0x6d,
+ 0x64, 0x64, 0x78, 0x61, 0x6d, 0x6d, 0x72, 0x78, 0x74, 0x6e, 0x6e, 0x65, 0x77, 0x65, 0x63, 0x72,
+ 0x74, 0x70, 0x70, 0x71, 0x6a, 0x72, 0x67, 0x69, 0x75, 0x6e, 0x6c, 0x61, 0x74, 0x66, 0x79, 0x63,
+ 0x66, 0x67, 0x75, 0x75, 0x79, 0x75, 0x79, 0x63, 0x72, 0x6c, 0x6f, 0x73, 0x77, 0x71, 0x78, 0x74,
+ 0x72, 0x75, 0x6f, 0x6b, 0x71, 0x67, 0x65, 0x63, 0x77, 0x78, 0x6b, 0x6c, 0x69, 0x6d, 0x6a, 0x78,
+ 0x63, 0x6c, 0x69, 0x6d, 0x6a, 0x6f, 0x61, 0x62, 0x64, 0x67, 0x61, 0x6b, 0x6b, 0x6d, 0x71, 0x61,
+ 0x64, 0x6b, 0x6f, 0x6d, 0x72, 0x65, 0x6d, 0x66, 0x65, 0x77, 0x78, 0x78, 0x6c, 0x63, 0x65, 0x76,
+ 0x71, 0x67, 0x73, 0x61, 0x69, 0x68, 0x71, 0x76, 0x6c, 0x72, 0x72, 0x63, 0x69, 0x6b, 0x78, 0x6a,
+ 0x6e, 0x79, 0x62, 0x64, 0x6c, 0x63, 0x6c, 0x72, 0x62, 0x6f, 0x72, 0x68, 0x79, 0x62, 0x70, 0x67,
+ 0x64, 0x73, 0x6e, 0x67, 0x6c, 0x69, 0x77, 0x76, 0x68, 0x70, 0x6f, 0x66, 0x63, 0x67, 0x72, 0x72,
+ 0x6c, 0x69, 0x61, 0x75, 0x61, 0x62, 0x6e, 0x75, 0x69, 0x61, 0x77, 0x77, 0x65, 0x6a, 0x6b, 0x74,
+ 0x61, 0x61, 0x6d, 0x6f, 0x77, 0x79, 0x72, 0x78, 0x73, 0x77, 0x6b, 0x63, 0x6d, 0x6b, 0x66, 0x71,
+ 0x78, 0x64, 0x64, 0x76, 0x79, 0x6f, 0x63, 0x62, 0x74, 0x69, 0x65, 0x6f, 0x65, 0x62, 0x74, 0x6f,
+ 0x74, 0x77, 0x78, 0x64, 0x64, 0x63, 0x61, 0x65, 0x76, 0x70, 0x63, 0x71, 0x62, 0x66, 0x66, 0x75,
+ 0x76, 0x78, 0x63, 0x65, 0x68, 0x6d, 0x6e, 0x61, 0x72, 0x74, 0x67, 0x77, 0x71, 0x71, 0x67, 0x66,
+ 0x6c, 0x67, 0x73, 0x65, 0x76, 0x72, 0x74, 0x67, 0x68, 0x75, 0x76, 0x70, 0x66, 0x68, 0x78, 0x6e,
+ 0x6d, 0x62, 0x6b, 0x77, 0x6c, 0x61, 0x65, 0x6a, 0x6d, 0x6c, 0x6f, 0x6c, 0x66, 0x6f, 0x76, 0x73,
+ 0x76, 0x6e, 0x72, 0x63, 0x62, 0x63, 0x72, 0x74, 0x6f, 0x63, 0x6a, 0x72, 0x6b, 0x63, 0x6d, 0x6d,
+ 0x62, 0x78, 0x72, 0x71, 0x71, 0x70, 0x6a, 0x6d, 0x65, 0x6e, 0x6e, 0x79, 0x74, 0x66, 0x74, 0x64,
+ 0x73, 0x64, 0x73, 0x74, 0x79, 0x66, 0x77, 0x61, 0x6a, 0x71, 0x68, 0x72, 0x73, 0x73, 0x77, 0x71,
+ 0x67, 0x61, 0x6e, 0x68, 0x75, 0x69, 0x61, 0x66, 0x67, 0x73, 0x73, 0x6e, 0x6b, 0x70, 0x61, 0x69,
+ 0x61, 0x63, 0x6e, 0x67, 0x62, 0x78, 0x6e, 0x6f, 0x74, 0x67, 0x61, 0x74, 0x68, 0x74, 0x63, 0x68,
+ 0x64, 0x68, 0x6a, 0x63, 0x79, 0x65, 0x78, 0x6c, 0x78, 0x6a, 0x62, 0x73, 0x71, 0x65, 0x65, 0x66,
+ 0x6a, 0x62, 0x75, 0x6b, 0x70, 0x66, 0x68, 0x73, 0x72, 0x6d, 0x71, 0x79, 0x6e, 0x71, 0x6f, 0x65,
+ 0x66, 0x6b, 0x72, 0x78, 0x75, 0x6c, 0x74, 0x6a, 0x62, 0x62, 0x74, 0x78, 0x61, 0x62, 0x75, 0x65,
+ 0x6d, 0x67, 0x64, 0x67, 0x78, 0x65, 0x68, 0x6e, 0x75, 0x6d, 0x78, 0x77, 0x61, 0x6a, 0x79, 0x72,
+ 0x63, 0x6a, 0x6b, 0x66, 0x6b, 0x75, 0x69, 0x73, 0x64, 0x73, 0x65, 0x64, 0x6a, 0x6c, 0x62, 0x6e,
+ 0x66, 0x6f, 0x63, 0x78, 0x67, 0x63, 0x76, 0x76, 0x6f, 0x78, 0x61, 0x67, 0x77, 0x73, 0x72, 0x77,
+ 0x63, 0x6e, 0x62, 0x79, 0x79, 0x73, 0x6c, 0x65, 0x66, 0x75, 0x6d, 0x68, 0x72, 0x61, 0x70, 0x6f,
+ 0x76, 0x6f, 0x68, 0x62, 0x65, 0x6f, 0x70, 0x6b, 0x6c, 0x65, 0x69, 0x66, 0x73, 0x71, 0x66, 0x71,
+ 0x74, 0x78, 0x76, 0x65, 0x75, 0x66, 0x78, 0x6b, 0x63, 0x78, 0x70, 0x6b, 0x74, 0x67, 0x78, 0x75,
+ 0x65, 0x6c, 0x6c, 0x75, 0x75, 0x75, 0x6b, 0x68, 0x6f, 0x63, 0x64, 0x64, 0x67, 0x61, 0x63, 0x61,
+ 0x6d, 0x65, 0x79, 0x76, 0x69, 0x70, 0x74, 0x71, 0x62, 0x68, 0x67, 0x65, 0x6f, 0x77, 0x74, 0x63,
+ 0x64, 0x65, 0x67, 0x78, 0x62, 0x66, 0x65, 0x6d, 0x6a, 0x6c, 0x62, 0x6c, 0x6a, 0x75, 0x61, 0x67,
+ 0x71, 0x6e, 0x6e, 0x70, 0x72, 0x6c, 0x78, 0x77, 0x69, 0x6d, 0x66, 0x69, 0x6e, 0x6b, 0x63, 0x76,
+ 0x72, 0x70, 0x67, 0x79, 0x64, 0x6e, 0x68, 0x73, 0x75, 0x73, 0x6d, 0x62, 0x6b, 0x6d, 0x69, 0x70,
+ 0x6b, 0x6f, 0x65, 0x6f, 0x78, 0x6d, 0x69, 0x79, 0x63, 0x71, 0x68, 0x72, 0x75, 0x6d, 0x67, 0x71,
+ 0x6d, 0x76, 0x6f, 0x6f, 0x69, 0x62, 0x78, 0x64, 0x69, 0x6f, 0x65, 0x66, 0x66, 0x63, 0x62, 0x6c,
+ 0x6b, 0x74, 0x79, 0x6f, 0x6f, 0x79, 0x70, 0x73, 0x79, 0x75, 0x67, 0x72, 0x79, 0x72, 0x63, 0x73,
+ 0x6c, 0x64, 0x72, 0x6f, 0x62, 0x6b, 0x62, 0x6f, 0x77, 0x76, 0x6c, 0x62, 0x6c, 0x70, 0x66, 0x67,
+ 0x6f, 0x63, 0x79, 0x66, 0x69, 0x67, 0x65, 0x79, 0x6e, 0x64, 0x6e, 0x69, 0x6d, 0x71, 0x67, 0x6f,
+ 0x69, 0x74, 0x67, 0x65, 0x72, 0x63, 0x6f, 0x64, 0x68, 0x73, 0x79, 0x79, 0x6d, 0x6d, 0x69, 0x67,
+ 0x77, 0x6d, 0x64, 0x70, 0x74, 0x6c, 0x75, 0x79, 0x74, 0x62, 0x62, 0x6a, 0x63, 0x65, 0x61, 0x79,
+ 0x62, 0x65, 0x68, 0x65, 0x6b, 0x73, 0x78, 0x73, 0x68, 0x6a, 0x79, 0x71, 0x71, 0x6e, 0x62, 0x61,
+ 0x6b, 0x73, 0x6a, 0x65, 0x6b, 0x74, 0x68, 0x74, 0x66, 0x6b, 0x6f, 0x76, 0x73, 0x6e, 0x65, 0x6d,
+ 0x6e, 0x78, 0x72, 0x69, 0x69, 0x76, 0x6e, 0x72, 0x71, 0x61, 0x61, 0x6c, 0x73, 0x63, 0x74, 0x6e,
+ 0x64, 0x71, 0x6d, 0x6b, 0x62, 0x62, 0x63, 0x6c, 0x61, 0x61, 0x65, 0x78, 0x64, 0x71, 0x78, 0x68,
+ 0x6b, 0x76, 0x72, 0x79, 0x70, 0x65, 0x6e, 0x6a, 0x65, 0x77, 0x63, 0x61, 0x64, 0x66, 0x71, 0x61,
+ 0x76, 0x72, 0x75, 0x68, 0x69, 0x63, 0x75, 0x71, 0x71, 0x6f, 0x75, 0x77, 0x76, 0x61, 0x6b, 0x73,
+ 0x6e, 0x77, 0x70, 0x71, 0x64, 0x62, 0x61, 0x76, 0x65, 0x65, 0x6f, 0x71, 0x76, 0x77, 0x72, 0x73,
+ 0x64, 0x73, 0x61, 0x74, 0x75, 0x75, 0x6c, 0x6f, 0x74, 0x64, 0x6d, 0x6c, 0x6a, 0x72, 0x66, 0x68,
+ 0x6f, 0x75, 0x63, 0x71, 0x73, 0x73, 0x6e, 0x6b, 0x75, 0x67, 0x74, 0x6e, 0x6b, 0x6f, 0x73, 0x73,
+ 0x6a, 0x61, 0x70, 0x66, 0x66, 0x61, 0x68, 0x70, 0x76, 0x6c, 0x70, 0x64, 0x72, 0x63, 0x61, 0x6e,
+ 0x77, 0x74, 0x73, 0x6d, 0x73, 0x67, 0x6e, 0x63, 0x61, 0x73, 0x63, 0x73, 0x6d, 0x76, 0x6a, 0x6d,
+ 0x6e, 0x6d, 0x6a, 0x66, 0x71, 0x6e, 0x66, 0x6c, 0x66, 0x72, 0x77, 0x6a, 0x79, 0x67, 0x67, 0x62,
+ 0x69, 0x72, 0x71, 0x66, 0x66, 0x69, 0x61, 0x6d, 0x75, 0x77, 0x66, 0x74, 0x6e, 0x6d, 0x67, 0x75,
+ 0x74, 0x63, 0x65, 0x75, 0x6d, 0x6f, 0x73, 0x61, 0x74, 0x6a, 0x66, 0x64, 0x70, 0x69, 0x61, 0x6e,
+ 0x61, 0x64, 0x77, 0x75, 0x63, 0x76, 0x6c, 0x66, 0x79, 0x77, 0x74, 0x79, 0x64, 0x6f, 0x65, 0x66,
+ 0x62, 0x71, 0x6a, 0x6c, 0x6a, 0x67, 0x69, 0x68, 0x61, 0x66, 0x75, 0x78, 0x73, 0x65, 0x6c, 0x61,
+ 0x6b, 0x74, 0x68, 0x75, 0x70, 0x70, 0x6a, 0x67, 0x73, 0x71, 0x63, 0x68, 0x65, 0x64, 0x77, 0x66,
+ 0x66, 0x6a, 0x71, 0x77, 0x72, 0x62, 0x73, 0x6d, 0x62, 0x73, 0x65, 0x6c, 0x75, 0x6f, 0x67, 0x6a,
+ 0x61, 0x79, 0x6b, 0x70, 0x61, 0x71, 0x61, 0x64, 0x6f, 0x63, 0x6a, 0x75, 0x63, 0x6e, 0x79, 0x78,
+ 0x71, 0x69, 0x73, 0x68, 0x6a, 0x6b, 0x66, 0x6f, 0x68, 0x66, 0x62, 0x65, 0x75, 0x70, 0x70, 0x68,
+ 0x61, 0x68, 0x76, 0x78, 0x6d, 0x64, 0x6c, 0x64, 0x63, 0x73, 0x72, 0x76, 0x65, 0x62, 0x66, 0x6b,
+ 0x66, 0x78, 0x62, 0x75, 0x6b, 0x69, 0x77, 0x76, 0x64, 0x77, 0x71, 0x72, 0x71, 0x6d, 0x78, 0x6a,
+ 0x6b, 0x73, 0x79, 0x64, 0x6b, 0x6a, 0x76, 0x73, 0x76, 0x6c, 0x78, 0x6d, 0x6c, 0x6a, 0x78, 0x79,
+ 0x75, 0x76, 0x75, 0x79, 0x67, 0x77, 0x6d, 0x76, 0x72, 0x79, 0x6a, 0x62, 0x6f, 0x78, 0x69, 0x73,
+ 0x73, 0x61, 0x70, 0x66, 0x67, 0x79, 0x61, 0x67, 0x62, 0x6d, 0x65, 0x6a, 0x72, 0x77, 0x66, 0x65,
+ 0x62, 0x63, 0x71, 0x6d, 0x6d, 0x62, 0x62, 0x6d, 0x75, 0x6a, 0x6e, 0x64, 0x79, 0x70, 0x67, 0x68,
+ 0x70, 0x65, 0x71, 0x77, 0x75, 0x77, 0x6e, 0x67, 0x6e, 0x61, 0x6c, 0x6e, 0x64, 0x64, 0x70, 0x70,
+ 0x6f, 0x6a, 0x62, 0x70, 0x62, 0x69, 0x6d, 0x62, 0x70, 0x6b, 0x63, 0x6c, 0x68, 0x79, 0x6b, 0x68,
+ 0x71, 0x6e, 0x71, 0x63, 0x6e, 0x62, 0x6c, 0x71, 0x76, 0x6a, 0x6b, 0x67, 0x66, 0x6a, 0x73, 0x79,
+ 0x6a, 0x73, 0x67, 0x77, 0x6c, 0x61, 0x6c, 0x6c, 0x6a, 0x63, 0x76, 0x6b, 0x64, 0x78, 0x61, 0x70,
+ 0x63, 0x6d, 0x6a, 0x75, 0x6f, 0x74, 0x65, 0x63, 0x6b, 0x68, 0x67, 0x73, 0x73, 0x63, 0x71, 0x63,
+ 0x73, 0x68, 0x68, 0x74, 0x76, 0x6e, 0x6e, 0x6c, 0x66, 0x61, 0x68, 0x71, 0x6c, 0x6e, 0x76, 0x79,
+ 0x77, 0x76, 0x61, 0x66, 0x69, 0x6d, 0x65, 0x6a, 0x70, 0x6a, 0x79, 0x79, 0x6a, 0x74, 0x65, 0x65,
+ 0x66, 0x6b, 0x77, 0x77, 0x68, 0x72, 0x62, 0x79, 0x6d, 0x78, 0x71, 0x67, 0x78, 0x74, 0x63, 0x79,
+ 0x74, 0x65, 0x69, 0x64, 0x64, 0x71, 0x68, 0x74, 0x65, 0x6d, 0x6a, 0x64, 0x78, 0x6c, 0x61, 0x76,
+ 0x73, 0x6c, 0x75, 0x73, 0x63, 0x64, 0x68, 0x76, 0x67, 0x6e, 0x78, 0x78, 0x68, 0x75, 0x69, 0x65,
+ 0x6a, 0x65, 0x75, 0x78, 0x77, 0x69, 0x71, 0x6a, 0x70, 0x6a, 0x70, 0x6b, 0x70, 0x72, 0x72, 0x63,
+ 0x64, 0x74, 0x79, 0x65, 0x63, 0x68, 0x79, 0x62, 0x63, 0x61, 0x79, 0x6e, 0x77, 0x79, 0x61, 0x65,
+ 0x66, 0x71, 0x6e, 0x79, 0x78, 0x69, 0x73, 0x6c, 0x67, 0x6f, 0x66, 0x75, 0x63, 0x63, 0x65, 0x78,
+ 0x66, 0x6a, 0x70, 0x67, 0x62, 0x63, 0x69, 0x77, 0x63, 0x76, 0x6d, 0x79, 0x68, 0x71, 0x77, 0x74,
+ 0x6f, 0x6c, 0x6b, 0x71, 0x6c, 0x67, 0x6e, 0x70, 0x71, 0x78, 0x66, 0x69, 0x73, 0x6e, 0x72, 0x69,
+ 0x77, 0x71, 0x65, 0x6f, 0x6f, 0x6b, 0x69, 0x66, 0x6e, 0x61, 0x6d, 0x73, 0x61, 0x70, 0x70, 0x6b,
+ 0x67, 0x76, 0x72, 0x66, 0x64, 0x71, 0x65, 0x71, 0x64, 0x61, 0x79, 0x64, 0x6b, 0x6a, 0x6c, 0x6c,
+ 0x69, 0x65, 0x62, 0x69, 0x61, 0x6e, 0x6e, 0x74, 0x79, 0x63, 0x63, 0x6d, 0x65, 0x6b, 0x68, 0x6c,
+ 0x71, 0x76, 0x62, 0x78, 0x63, 0x77, 0x75, 0x69, 0x79, 0x6f, 0x76, 0x66, 0x77, 0x6e, 0x6c, 0x78,
+ 0x6e, 0x78, 0x73, 0x6a, 0x74, 0x62, 0x68, 0x66, 0x65, 0x68, 0x61, 0x6d, 0x77, 0x72, 0x69, 0x6f,
+ 0x6b, 0x6f, 0x65, 0x71, 0x6b, 0x79, 0x74, 0x77, 0x61, 0x74, 0x69, 0x74, 0x6a, 0x75, 0x70, 0x76,
+ 0x6a, 0x77, 0x70, 0x73, 0x77, 0x72, 0x75, 0x71, 0x76, 0x68, 0x73, 0x73, 0x6b, 0x6c, 0x63, 0x69,
+ 0x6b, 0x75, 0x67, 0x74, 0x6e, 0x79, 0x76, 0x67, 0x71, 0x78, 0x64, 0x6a, 0x79, 0x68, 0x6e, 0x65,
+ 0x65, 0x62, 0x76, 0x6f, 0x6d, 0x75, 0x74, 0x73, 0x6a, 0x68, 0x71, 0x67, 0x6b, 0x64, 0x62, 0x74,
+ 0x64, 0x62, 0x77, 0x65, 0x69, 0x61, 0x78, 0x68, 0x72, 0x70, 0x6d, 0x69, 0x6b, 0x70, 0x6e, 0x62,
+ 0x69, 0x74, 0x62, 0x6b, 0x6d, 0x64, 0x78, 0x70, 0x76, 0x74, 0x6a, 0x6a, 0x63, 0x65, 0x77, 0x78,
+ 0x72, 0x71, 0x63, 0x6d, 0x6c, 0x69, 0x63, 0x68, 0x71, 0x62, 0x62, 0x6f, 0x79, 0x75, 0x75, 0x73,
+ 0x71, 0x6c, 0x77, 0x6d, 0x62, 0x75, 0x67, 0x66, 0x61, 0x6e, 0x73, 0x73, 0x62, 0x67, 0x63, 0x72,
+ 0x66, 0x6e, 0x63, 0x71, 0x78, 0x6d, 0x68, 0x67, 0x79, 0x79, 0x76, 0x74, 0x6d, 0x74, 0x74, 0x73,
+ 0x79, 0x76, 0x6d, 0x79, 0x71, 0x72, 0x70, 0x65, 0x69, 0x66, 0x73, 0x74, 0x77, 0x6d, 0x74, 0x6e,
+ 0x6f, 0x72, 0x71, 0x62, 0x67, 0x74, 0x62, 0x77, 0x6f, 0x6b, 0x6c, 0x66, 0x65, 0x63, 0x75, 0x6e,
+ 0x66, 0x6b, 0x75, 0x69, 0x61, 0x79, 0x6c, 0x74, 0x77, 0x6b, 0x73, 0x78, 0x70, 0x6f, 0x69, 0x72,
+ 0x72, 0x74, 0x69, 0x6f, 0x62, 0x66, 0x61, 0x6c, 0x72, 0x6b, 0x63, 0x6c, 0x73, 0x67, 0x6e, 0x6b,
+ 0x74, 0x70, 0x70, 0x73, 0x6d, 0x6a, 0x73, 0x67, 0x71, 0x70, 0x77, 0x71, 0x69, 0x6a, 0x79, 0x71,
+ 0x75, 0x70, 0x67, 0x62, 0x72, 0x61, 0x79, 0x64, 0x73, 0x73, 0x66, 0x78, 0x64, 0x76, 0x69, 0x79,
+ 0x6f, 0x62, 0x6a, 0x6d, 0x76, 0x69, 0x66, 0x6b, 0x69, 0x6c, 0x6a, 0x69, 0x75, 0x71, 0x66, 0x77,
+ 0x69, 0x75, 0x76, 0x6b, 0x73, 0x69, 0x6d, 0x66, 0x61, 0x6b, 0x79, 0x74, 0x61, 0x66, 0x64, 0x64,
+ 0x65, 0x66, 0x73, 0x64, 0x76, 0x74, 0x6f, 0x63, 0x6c, 0x6c, 0x66, 0x75, 0x69, 0x6a, 0x79, 0x72,
+ 0x70, 0x64, 0x61, 0x74, 0x75, 0x74, 0x62, 0x61, 0x72, 0x6a, 0x73, 0x6f, 0x77, 0x65, 0x67, 0x71,
+ 0x64, 0x66, 0x69, 0x69, 0x63, 0x6a, 0x66, 0x73, 0x76, 0x70, 0x63, 0x69, 0x75, 0x64, 0x78, 0x62,
+ 0x79, 0x67, 0x79, 0x6b, 0x78, 0x6f, 0x63, 0x6d, 0x69, 0x61, 0x67, 0x6f, 0x77, 0x64, 0x70, 0x66,
+ 0x6b, 0x73, 0x76, 0x6c, 0x6a, 0x64, 0x69, 0x6f, 0x68, 0x75, 0x72, 0x67, 0x6c, 0x64, 0x73, 0x71,
+ 0x67, 0x6f, 0x69, 0x6c, 0x63, 0x6a, 0x62, 0x71, 0x70, 0x78, 0x73, 0x66, 0x66, 0x6e, 0x79, 0x62,
+ 0x6c, 0x77, 0x65, 0x6f, 0x6e, 0x70, 0x61, 0x76, 0x67, 0x77, 0x6d, 0x71, 0x73, 0x70, 0x75, 0x62,
+ 0x62, 0x68, 0x78, 0x6f, 0x62, 0x76, 0x61, 0x65, 0x61, 0x6a, 0x70, 0x65, 0x70, 0x70, 0x73, 0x75,
+ 0x62, 0x73, 0x61, 0x72, 0x64, 0x6c, 0x76, 0x6e, 0x6b, 0x74, 0x67, 0x6a, 0x6b, 0x66, 0x65, 0x6c,
+ 0x62, 0x65, 0x79, 0x6b, 0x69, 0x78, 0x61, 0x72, 0x73, 0x62, 0x6c, 0x70, 0x64, 0x78, 0x71, 0x78,
+ 0x68, 0x6c, 0x63, 0x72, 0x66, 0x78, 0x73, 0x79, 0x68, 0x73, 0x62, 0x68, 0x68, 0x6f, 0x67, 0x63,
+ 0x73, 0x6d, 0x6d, 0x75, 0x64, 0x63, 0x79, 0x74, 0x68, 0x74, 0x79, 0x69, 0x6b, 0x63, 0x63, 0x67,
+ 0x6a, 0x76, 0x75, 0x6b, 0x63, 0x77, 0x79, 0x64, 0x76, 0x74, 0x68, 0x6c, 0x76, 0x77, 0x68, 0x70,
+ 0x69, 0x6f, 0x73, 0x66, 0x76, 0x62, 0x76, 0x64, 0x73, 0x65, 0x64, 0x61, 0x70, 0x67, 0x6e, 0x6b,
+ 0x72, 0x70, 0x6e, 0x70, 0x79, 0x75, 0x6e, 0x6f, 0x6c, 0x67, 0x6d, 0x78, 0x61, 0x62, 0x6c, 0x6e,
+ 0x79, 0x75, 0x75, 0x66, 0x74, 0x65, 0x61, 0x74, 0x69, 0x6d, 0x78, 0x64, 0x63, 0x68, 0x70, 0x61,
+ 0x73, 0x67, 0x65, 0x6e, 0x70, 0x74, 0x67, 0x66, 0x69, 0x62, 0x68, 0x6d, 0x73, 0x64, 0x6f, 0x67,
+ 0x6c, 0x6a, 0x67, 0x71, 0x6c, 0x72, 0x6e, 0x6a, 0x6b, 0x6f, 0x73, 0x73, 0x70, 0x77, 0x66, 0x61,
+ 0x6b, 0x70, 0x65, 0x6e, 0x6e, 0x76, 0x6a, 0x6e, 0x63, 0x75, 0x77, 0x6f, 0x72, 0x62, 0x78, 0x6a,
+ 0x6a, 0x6e, 0x6e, 0x66, 0x69, 0x66, 0x68, 0x76, 0x61, 0x74, 0x6f, 0x76, 0x6c, 0x63, 0x68, 0x66,
+ 0x65, 0x72, 0x71, 0x65, 0x63, 0x69, 0x75, 0x73, 0x6b, 0x64, 0x6b, 0x79, 0x68, 0x62, 0x79, 0x65,
+ 0x74, 0x61, 0x70, 0x76, 0x79, 0x70, 0x74, 0x75, 0x79, 0x62, 0x6e, 0x6c, 0x70, 0x6d, 0x69, 0x72,
+ 0x77, 0x71, 0x6b, 0x63, 0x71, 0x61, 0x6c, 0x72, 0x67, 0x6d, 0x6b, 0x74, 0x6e, 0x71, 0x75, 0x66,
+ 0x6b, 0x6b, 0x65, 0x77, 0x65, 0x73, 0x72, 0x63, 0x67, 0x6b, 0x6a, 0x61, 0x69, 0x73, 0x61, 0x64,
+ 0x6d, 0x77, 0x74, 0x76, 0x65, 0x63, 0x61, 0x6e, 0x62, 0x68, 0x6e, 0x70, 0x62, 0x64, 0x6e, 0x79,
+ 0x69, 0x79, 0x68, 0x75, 0x6c, 0x79, 0x71, 0x78, 0x62, 0x64, 0x62, 0x78, 0x79, 0x66, 0x70, 0x65,
+ 0x70, 0x68, 0x67, 0x6d, 0x67, 0x6b, 0x62, 0x65, 0x63, 0x68, 0x67, 0x63, 0x6b, 0x78, 0x6a, 0x63,
+ 0x73, 0x63, 0x62, 0x6c, 0x61, 0x6b, 0x68, 0x68, 0x6e, 0x61, 0x70, 0x73, 0x69, 0x74, 0x63, 0x68,
+ 0x64, 0x66, 0x69, 0x62, 0x6e, 0x6c, 0x63, 0x72, 0x63, 0x62, 0x62, 0x74, 0x67, 0x67, 0x61, 0x62,
+ 0x67, 0x72, 0x75, 0x6a, 0x74, 0x78, 0x62, 0x66, 0x67, 0x63, 0x69, 0x73, 0x75, 0x68, 0x76, 0x6c,
+ 0x66, 0x65, 0x69, 0x66, 0x6e, 0x6e, 0x68, 0x72, 0x72, 0x65, 0x62, 0x71, 0x63, 0x72, 0x71, 0x69,
+ 0x63, 0x66, 0x74, 0x77, 0x65, 0x67, 0x6b, 0x70, 0x6c, 0x63, 0x6d, 0x68, 0x68, 0x71, 0x70, 0x72,
+ 0x76, 0x72, 0x77, 0x61, 0x72, 0x73, 0x75, 0x68, 0x6c, 0x73, 0x6f, 0x61, 0x6e, 0x76, 0x6e, 0x66,
+ 0x70, 0x61, 0x67, 0x6c, 0x6b, 0x61, 0x75, 0x67, 0x64, 0x76, 0x6d, 0x6f, 0x77, 0x78, 0x72, 0x70,
+ 0x6e, 0x6e, 0x6d, 0x77, 0x67, 0x71, 0x75, 0x78, 0x74, 0x71, 0x73, 0x76, 0x67, 0x61, 0x79, 0x70,
+ 0x6d, 0x71, 0x73, 0x76, 0x6c, 0x6f, 0x6d, 0x77, 0x65, 0x65, 0x76, 0x68, 0x66, 0x6a, 0x76, 0x72,
+ 0x76, 0x71, 0x64, 0x67, 0x74, 0x6d, 0x62, 0x6b, 0x64, 0x6d, 0x74, 0x78, 0x6f, 0x73, 0x62, 0x76,
+ 0x78, 0x79, 0x74, 0x6b, 0x75, 0x77, 0x6a, 0x77, 0x66, 0x65, 0x74, 0x74, 0x79, 0x6e, 0x64, 0x6d,
+ 0x6e, 0x67, 0x64, 0x62, 0x6b, 0x77, 0x72, 0x69, 0x61, 0x70, 0x67, 0x64, 0x64, 0x77, 0x6b, 0x63,
+ 0x70, 0x74, 0x67, 0x73, 0x69, 0x72, 0x79, 0x65, 0x64, 0x6e, 0x71, 0x68, 0x71, 0x79, 0x6e, 0x6e,
+ 0x63, 0x64, 0x6a, 0x70, 0x62, 0x73, 0x76, 0x72, 0x71, 0x71, 0x74, 0x67, 0x6a, 0x62, 0x70, 0x63,
+ 0x69, 0x61, 0x6f, 0x6d, 0x63, 0x67, 0x74, 0x74, 0x70, 0x72, 0x6c, 0x6d, 0x6a, 0x69, 0x64, 0x79,
+ 0x77, 0x6d, 0x77, 0x75, 0x6b, 0x68, 0x6e, 0x70, 0x71, 0x64, 0x66, 0x6f, 0x72, 0x78, 0x78, 0x69,
+ 0x6e, 0x75, 0x6b, 0x74, 0x65, 0x6c, 0x65, 0x79, 0x73, 0x73, 0x65, 0x66, 0x6f, 0x72, 0x75, 0x69,
+ 0x77, 0x61, 0x75, 0x74, 0x63, 0x68, 0x73, 0x6c, 0x73, 0x78, 0x6d, 0x6b, 0x66, 0x64, 0x75, 0x76,
+ 0x6f, 0x6f, 0x79, 0x78, 0x68, 0x6d, 0x6b, 0x6f, 0x6a, 0x72, 0x6e, 0x62, 0x63, 0x67, 0x79, 0x78,
+ 0x6b, 0x77, 0x75, 0x71, 0x69, 0x6e, 0x70, 0x73, 0x61, 0x68, 0x66, 0x63, 0x6a, 0x66, 0x75, 0x74,
+ 0x63, 0x6d, 0x72, 0x71, 0x64, 0x77, 0x77, 0x69, 0x68, 0x70, 0x79, 0x6c, 0x72, 0x62, 0x78, 0x69,
+ 0x6a, 0x61, 0x6d, 0x75, 0x6d, 0x67, 0x61, 0x78, 0x61, 0x6e, 0x73, 0x6b, 0x6c, 0x74, 0x67, 0x61,
+ 0x71, 0x68, 0x62, 0x6c, 0x62, 0x77, 0x75, 0x65, 0x6c, 0x76, 0x73, 0x64, 0x68, 0x73, 0x68, 0x79,
+ 0x78, 0x6a, 0x73, 0x6a, 0x72, 0x65, 0x77, 0x68, 0x64, 0x76, 0x75, 0x6d, 0x6d, 0x64, 0x75, 0x78,
+ 0x78, 0x76, 0x61, 0x63, 0x6f, 0x78, 0x69, 0x76, 0x61, 0x66, 0x62, 0x64, 0x72, 0x6c, 0x70, 0x79,
+ 0x6e, 0x63, 0x6a, 0x70, 0x6f, 0x73, 0x63, 0x76, 0x6d, 0x76, 0x77, 0x6f, 0x78, 0x63, 0x67, 0x6c,
+ 0x6b, 0x77, 0x71, 0x79, 0x70, 0x6a, 0x78, 0x71, 0x69, 0x69, 0x76, 0x6c, 0x6d, 0x66, 0x6f, 0x66,
+ 0x68, 0x64, 0x61, 0x71, 0x61, 0x74, 0x68, 0x72, 0x75, 0x6e, 0x6d, 0x73, 0x69, 0x71, 0x71, 0x6b,
+ 0x79, 0x76, 0x74, 0x67, 0x63, 0x64, 0x70, 0x69, 0x65, 0x75, 0x72, 0x6e, 0x6c, 0x76, 0x63, 0x72,
+ 0x6d, 0x6e, 0x73, 0x6d, 0x75, 0x6c, 0x73, 0x75, 0x61, 0x64, 0x69, 0x72, 0x75, 0x66, 0x71, 0x6b,
+ 0x77, 0x6b, 0x64, 0x62, 0x68, 0x61, 0x6e, 0x67, 0x6d, 0x68, 0x66, 0x72, 0x69, 0x6b, 0x61, 0x78,
+ 0x70, 0x6d, 0x74, 0x6d, 0x6c, 0x75, 0x63, 0x72, 0x71, 0x71, 0x70, 0x78, 0x62, 0x6a, 0x69, 0x70,
+ 0x70, 0x68, 0x6a, 0x6e, 0x6e, 0x74, 0x69, 0x62, 0x64, 0x69, 0x66, 0x62, 0x78, 0x6b, 0x6c, 0x64,
+ 0x66, 0x79, 0x6a, 0x77, 0x77, 0x79, 0x70, 0x6e, 0x76, 0x78, 0x6a, 0x70, 0x69, 0x63, 0x62, 0x72,
+ 0x6f, 0x64, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x67, 0x74, 0x65, 0x77, 0x6c, 0x6e, 0x72, 0x64, 0x6d,
+ 0x62, 0x68, 0x72, 0x66, 0x67, 0x63, 0x69, 0x74, 0x69, 0x6c, 0x75, 0x72, 0x6c, 0x63, 0x73, 0x71,
+ 0x66, 0x6f, 0x79, 0x67, 0x73, 0x6b, 0x6f, 0x6c, 0x65, 0x72, 0x78, 0x72, 0x76, 0x6a, 0x6f, 0x6e,
+ 0x66, 0x6e, 0x75, 0x6c, 0x6b, 0x75, 0x72, 0x74, 0x6a, 0x6d, 0x67, 0x67, 0x67, 0x74, 0x61, 0x6c,
+ 0x64, 0x64, 0x71, 0x79, 0x62, 0x72, 0x65, 0x6d, 0x62, 0x6c, 0x6f, 0x76, 0x63, 0x71, 0x68, 0x79,
+ 0x6e, 0x78, 0x6c, 0x63, 0x79, 0x61, 0x69, 0x65, 0x68, 0x68, 0x64, 0x66, 0x66, 0x6d, 0x6a, 0x79,
+ 0x62, 0x71, 0x78, 0x70, 0x77, 0x78, 0x73, 0x69, 0x61, 0x6e, 0x66, 0x6e, 0x77, 0x72, 0x63, 0x68,
+ 0x75, 0x6e, 0x75, 0x64, 0x67, 0x6e, 0x69, 0x6a, 0x73, 0x71, 0x6f, 0x71, 0x62, 0x73, 0x6b, 0x65,
+ 0x74, 0x73, 0x73, 0x70, 0x66, 0x66, 0x75, 0x6e, 0x70, 0x76, 0x77, 0x71, 0x78, 0x6d, 0x71, 0x78,
+ 0x6b, 0x6e, 0x62, 0x6d, 0x6f, 0x69, 0x66, 0x61, 0x6f, 0x63, 0x6e, 0x6d, 0x79, 0x77, 0x65, 0x73,
+ 0x62, 0x73, 0x77, 0x61, 0x65, 0x61, 0x79, 0x76, 0x61, 0x78, 0x62, 0x77, 0x77, 0x70, 0x77, 0x77,
+ 0x77, 0x6b, 0x63, 0x6e, 0x70, 0x76, 0x6e, 0x79, 0x72, 0x68, 0x70, 0x6f, 0x62, 0x74, 0x65, 0x78,
+ 0x6d, 0x76, 0x6a, 0x63, 0x6a, 0x66, 0x6d, 0x77, 0x6c, 0x73, 0x66, 0x6c, 0x78, 0x6e, 0x6d, 0x6b,
+ 0x6f, 0x67, 0x6a, 0x67, 0x71, 0x79, 0x75, 0x77, 0x76, 0x74, 0x6d, 0x6e, 0x68, 0x66, 0x79, 0x69,
+ 0x68, 0x75, 0x64, 0x70, 0x6c, 0x74, 0x77, 0x70, 0x6f, 0x74, 0x71, 0x6f, 0x67, 0x6e, 0x6d, 0x6f,
+ 0x76, 0x62, 0x64, 0x74, 0x65, 0x74, 0x63, 0x66, 0x67, 0x6d, 0x75, 0x63, 0x79, 0x69, 0x65, 0x73,
+ 0x6c, 0x73, 0x65, 0x62, 0x6d, 0x78, 0x68, 0x66, 0x77, 0x66, 0x74, 0x71, 0x6b, 0x65, 0x6d, 0x72,
+ 0x63, 0x73, 0x61, 0x66, 0x6e, 0x71, 0x67, 0x64, 0x73, 0x64, 0x77, 0x6a, 0x78, 0x6f, 0x66, 0x63,
+ 0x61, 0x65, 0x63, 0x76, 0x63, 0x6d, 0x71, 0x6d, 0x79, 0x79, 0x65, 0x61, 0x79, 0x66, 0x6f, 0x64,
+ 0x6b, 0x74, 0x6c, 0x75, 0x70, 0x63, 0x66, 0x77, 0x74, 0x65, 0x70, 0x75, 0x78, 0x63, 0x66, 0x6f,
+ 0x6e, 0x62, 0x6f, 0x62, 0x79, 0x73, 0x65, 0x72, 0x66, 0x6a, 0x63, 0x79, 0x66, 0x73, 0x73, 0x63,
+ 0x62, 0x69, 0x76, 0x63, 0x6e, 0x6b, 0x74, 0x76, 0x75, 0x73, 0x71, 0x74, 0x6e, 0x6e, 0x73, 0x68,
+ 0x65, 0x61, 0x6c, 0x68, 0x67, 0x65, 0x79, 0x6c, 0x79, 0x63, 0x6b, 0x77, 0x62, 0x75, 0x6d, 0x73,
+ 0x6f, 0x70, 0x69, 0x68, 0x68, 0x77, 0x6c, 0x63, 0x63, 0x6e, 0x78, 0x76, 0x69, 0x76, 0x75, 0x74,
+ 0x70, 0x64, 0x6a, 0x70, 0x6c, 0x67, 0x6f, 0x62, 0x6c, 0x73, 0x6e, 0x6e, 0x78, 0x66, 0x74, 0x65,
+ 0x6b, 0x70, 0x79, 0x72, 0x73, 0x69, 0x6c, 0x72, 0x61, 0x6c, 0x78, 0x6e, 0x63, 0x72, 0x72, 0x76,
+ 0x64, 0x68, 0x78, 0x78, 0x70, 0x79, 0x61, 0x76, 0x70, 0x6f, 0x73, 0x6c, 0x69, 0x72, 0x63, 0x72,
+ 0x72, 0x78, 0x62, 0x69, 0x61, 0x73, 0x67, 0x66, 0x64, 0x6b, 0x67, 0x66, 0x79, 0x6c, 0x61, 0x6b,
+ 0x72, 0x6d, 0x6a, 0x74, 0x70, 0x72, 0x69, 0x6b, 0x71, 0x74, 0x6e, 0x6a, 0x79, 0x69, 0x78, 0x73,
+ 0x73, 0x77, 0x68, 0x68, 0x73, 0x6b, 0x78, 0x72, 0x66, 0x6a, 0x68, 0x61, 0x67, 0x67, 0x68, 0x76,
+ 0x6a, 0x71, 0x62, 0x62, 0x68, 0x6b, 0x68, 0x66, 0x76, 0x6b, 0x6e, 0x78, 0x65, 0x77, 0x62, 0x70,
+ 0x72, 0x76, 0x76, 0x6b, 0x76, 0x64, 0x6c, 0x68, 0x79, 0x6f, 0x68, 0x69, 0x6a, 0x66, 0x67, 0x73,
+ 0x63, 0x61, 0x6b, 0x69, 0x74, 0x64, 0x62, 0x6a, 0x76, 0x74, 0x6b, 0x79, 0x77, 0x65, 0x69, 0x65,
+ 0x6b, 0x77, 0x72, 0x74, 0x6f, 0x74, 0x62, 0x64, 0x78, 0x6f, 0x62, 0x73, 0x6b, 0x69, 0x79, 0x64,
+ 0x76, 0x78, 0x71, 0x6a, 0x79, 0x61, 0x77, 0x64, 0x66, 0x77, 0x63, 0x77, 0x77, 0x6b, 0x69, 0x68,
+ 0x6e, 0x6d, 0x67, 0x6a, 0x74, 0x6c, 0x76, 0x68, 0x68, 0x72, 0x61, 0x77, 0x71, 0x64, 0x61, 0x65,
+ 0x6b, 0x72, 0x65, 0x61, 0x6f, 0x64, 0x6d, 0x71, 0x76, 0x68, 0x66, 0x78, 0x71, 0x6c, 0x6e, 0x68,
+ 0x75, 0x74, 0x75, 0x6f, 0x73, 0x70, 0x70, 0x66, 0x76, 0x6d, 0x73, 0x66, 0x6a, 0x75, 0x63, 0x77,
+ 0x69, 0x66, 0x67, 0x76, 0x6b, 0x68, 0x65, 0x63, 0x68, 0x73, 0x6f, 0x75, 0x64, 0x79, 0x6d, 0x6f,
+ 0x6e, 0x79, 0x6a, 0x78, 0x64, 0x69, 0x71, 0x70, 0x6d, 0x78, 0x70, 0x61, 0x63, 0x77, 0x66, 0x79,
+ 0x6c, 0x79, 0x66, 0x70, 0x73, 0x77, 0x65, 0x6b, 0x72, 0x6d, 0x70, 0x65, 0x74, 0x61, 0x68, 0x66,
+ 0x63, 0x79, 0x79, 0x6b, 0x76, 0x76, 0x79, 0x78, 0x6f, 0x76, 0x67, 0x79, 0x77, 0x77, 0x62, 0x77,
+ 0x69, 0x78, 0x74, 0x65, 0x65, 0x6f, 0x6a, 0x6d, 0x76, 0x6d, 0x75, 0x66, 0x6d, 0x6e, 0x70, 0x79,
+ 0x6e, 0x6c, 0x79, 0x67, 0x72, 0x6b, 0x6c, 0x64, 0x76, 0x6c, 0x6e, 0x65, 0x69, 0x64, 0x76, 0x6d,
+ 0x68, 0x79, 0x6b, 0x74, 0x63, 0x6a, 0x63, 0x66, 0x65, 0x74, 0x69, 0x74, 0x71, 0x6e, 0x62, 0x66,
+ 0x78, 0x75, 0x62, 0x74, 0x73, 0x66, 0x61, 0x77, 0x6d, 0x6c, 0x62, 0x77, 0x61, 0x68, 0x6e, 0x76,
+ 0x66, 0x6a, 0x68, 0x73, 0x63, 0x6e, 0x74, 0x64, 0x6a, 0x75, 0x68, 0x63, 0x6f, 0x73, 0x77, 0x6a,
+ 0x68, 0x75, 0x72, 0x69, 0x79, 0x69, 0x65, 0x64, 0x6d, 0x6a, 0x67, 0x70, 0x79, 0x68, 0x6f, 0x6c,
+ 0x6d, 0x75, 0x6f, 0x73, 0x68, 0x68, 0x6f, 0x73, 0x70, 0x6c, 0x65, 0x67, 0x6b, 0x6e, 0x74, 0x69,
+ 0x63, 0x72, 0x6f, 0x63, 0x68, 0x6b, 0x79, 0x74, 0x77, 0x79, 0x76, 0x63, 0x71, 0x6b, 0x6d, 0x6d,
+ 0x6f, 0x69, 0x6c, 0x77, 0x6c, 0x6c, 0x62, 0x6b, 0x71, 0x6c, 0x74, 0x67, 0x6c, 0x64, 0x6b, 0x69,
+ 0x62, 0x73, 0x78, 0x66, 0x68, 0x77, 0x64, 0x74, 0x73, 0x73, 0x71, 0x74, 0x64, 0x6b, 0x6a, 0x69,
+ 0x74, 0x6c, 0x61, 0x72, 0x74, 0x6e, 0x6a, 0x6c, 0x68, 0x78, 0x77, 0x6b, 0x67, 0x6f, 0x6f, 0x6a,
+ 0x61, 0x65, 0x6a, 0x78, 0x67, 0x61, 0x77, 0x72, 0x6e, 0x62, 0x6d, 0x6c, 0x6e, 0x67, 0x69, 0x66,
+ 0x6e, 0x6a, 0x76, 0x69, 0x77, 0x72, 0x79, 0x6f, 0x63, 0x69, 0x6e, 0x67, 0x72, 0x6a, 0x76, 0x69,
+ 0x75, 0x78, 0x6d, 0x6e, 0x65, 0x64, 0x67, 0x70, 0x6a, 0x6d, 0x74, 0x78, 0x79, 0x73, 0x72, 0x70,
+ 0x72, 0x61, 0x6f, 0x77, 0x61, 0x79, 0x6f, 0x72, 0x6b, 0x61, 0x61, 0x75, 0x77, 0x69, 0x63, 0x6d,
+ 0x68, 0x78, 0x74, 0x65, 0x74, 0x67, 0x63, 0x77, 0x63, 0x71, 0x72, 0x72, 0x71, 0x6c, 0x64, 0x79,
+ 0x78, 0x6b, 0x63, 0x75, 0x70, 0x64, 0x6f, 0x6f, 0x6c, 0x76, 0x6f, 0x71, 0x75, 0x6d, 0x63, 0x74,
+ 0x71, 0x63, 0x76, 0x6e, 0x75, 0x63, 0x65, 0x69, 0x67, 0x76, 0x64, 0x61, 0x6d, 0x72, 0x6a, 0x6b,
+ 0x64, 0x70, 0x68, 0x75, 0x67, 0x6c, 0x77, 0x71, 0x61, 0x70, 0x6e, 0x67, 0x73, 0x70, 0x6e, 0x68,
+ 0x73, 0x73, 0x69, 0x65, 0x6a, 0x66, 0x68, 0x67, 0x72, 0x73, 0x72, 0x69, 0x6a, 0x77, 0x6b, 0x78,
+ 0x6a, 0x61, 0x73, 0x66, 0x66, 0x65, 0x6b, 0x65, 0x6b, 0x61, 0x68, 0x6b, 0x6a, 0x73, 0x79, 0x6f,
+ 0x64, 0x78, 0x72, 0x76, 0x6c, 0x67, 0x61, 0x70, 0x79, 0x77, 0x77, 0x6c, 0x69, 0x67, 0x71, 0x76,
+ 0x79, 0x61, 0x72, 0x62, 0x74, 0x64, 0x69, 0x64, 0x6f, 0x78, 0x6d, 0x6f, 0x67, 0x67, 0x6c, 0x78,
+ 0x74, 0x62, 0x64, 0x72, 0x63, 0x70, 0x64, 0x71, 0x79, 0x6f, 0x63, 0x74, 0x6c, 0x69, 0x6c, 0x6d,
+ 0x77, 0x6b, 0x69, 0x63, 0x72, 0x70, 0x70, 0x71, 0x75, 0x73, 0x68, 0x68, 0x6c, 0x79, 0x6c, 0x67,
+ 0x69, 0x65, 0x79, 0x76, 0x6b, 0x74, 0x6f, 0x6d, 0x61, 0x79, 0x79, 0x6a, 0x78, 0x6e, 0x78, 0x62,
+ 0x78, 0x65, 0x61, 0x70, 0x68, 0x75, 0x78, 0x6d, 0x77, 0x77, 0x70, 0x6e, 0x75, 0x67, 0x65, 0x66,
+ 0x6f, 0x6c, 0x65, 0x77, 0x61, 0x6b, 0x69, 0x68, 0x73, 0x61, 0x74, 0x62, 0x6c, 0x6a, 0x65, 0x6d,
+ 0x74, 0x77, 0x77, 0x64, 0x67, 0x6f, 0x76, 0x78, 0x62, 0x6b, 0x71, 0x6d, 0x67, 0x62, 0x75, 0x61,
+ 0x79, 0x78, 0x64, 0x65, 0x65, 0x72, 0x62, 0x64, 0x75, 0x61, 0x66, 0x6e, 0x66, 0x6c, 0x70, 0x73,
+ 0x70, 0x68, 0x6d, 0x79, 0x63, 0x71, 0x64, 0x70, 0x6d, 0x75, 0x78, 0x61, 0x6b, 0x61, 0x62, 0x67,
+ 0x65, 0x62, 0x64, 0x66, 0x64, 0x6a, 0x76, 0x67, 0x68, 0x78, 0x6e, 0x6d, 0x65, 0x79, 0x73, 0x64,
+ 0x63, 0x68, 0x72, 0x71, 0x6a, 0x6f, 0x71, 0x61, 0x77, 0x6a, 0x6b, 0x69, 0x62, 0x73, 0x63, 0x69,
+ 0x73, 0x63, 0x64, 0x6c, 0x67, 0x71, 0x69, 0x75, 0x71, 0x63, 0x64, 0x6b, 0x61, 0x6e, 0x73, 0x6c,
+ 0x65, 0x66, 0x64, 0x6e, 0x6a, 0x74, 0x6b, 0x78, 0x69, 0x6a, 0x64, 0x6a, 0x77, 0x70, 0x70, 0x67,
+ 0x6d, 0x72, 0x6b, 0x65, 0x79, 0x64, 0x61, 0x6f, 0x64, 0x70, 0x73, 0x63, 0x72, 0x6d, 0x68, 0x73,
+ 0x74, 0x6e, 0x6f, 0x72, 0x61, 0x73, 0x61, 0x72, 0x71, 0x72, 0x61, 0x75, 0x77, 0x67, 0x70, 0x68,
+ 0x70, 0x66, 0x61, 0x72, 0x67, 0x73, 0x69, 0x67, 0x6b, 0x71, 0x73, 0x64, 0x77, 0x61, 0x67, 0x70,
+ 0x69, 0x71, 0x72, 0x77, 0x77, 0x68, 0x71, 0x63, 0x6a, 0x61, 0x70, 0x63, 0x70, 0x62, 0x71, 0x68,
+ 0x63, 0x78, 0x71, 0x62, 0x70, 0x79, 0x74, 0x76, 0x76, 0x62, 0x74, 0x63, 0x76, 0x68, 0x62, 0x73,
+ 0x72, 0x64, 0x64, 0x66, 0x71, 0x65, 0x6d, 0x72, 0x63, 0x77, 0x6a, 0x77, 0x6a, 0x76, 0x62, 0x6c,
+ 0x77, 0x65, 0x61, 0x63, 0x6b, 0x62, 0x79, 0x71, 0x75, 0x6b, 0x68, 0x74, 0x6f, 0x66, 0x74, 0x65,
+ 0x72, 0x76, 0x74, 0x6f, 0x71, 0x71, 0x6e, 0x65, 0x63, 0x78, 0x6d, 0x74, 0x74, 0x66, 0x71, 0x78,
+ 0x77, 0x68, 0x75, 0x74, 0x72, 0x62, 0x73, 0x6c, 0x65, 0x71, 0x66, 0x6c, 0x6d, 0x75, 0x66, 0x62,
+ 0x71, 0x78, 0x68, 0x63, 0x65, 0x6f, 0x72, 0x63, 0x69, 0x74, 0x68, 0x74, 0x72, 0x64, 0x62, 0x68,
+ 0x63, 0x65, 0x74, 0x78, 0x67, 0x74, 0x61, 0x74, 0x75, 0x69, 0x6f, 0x6b, 0x78, 0x6c, 0x72, 0x79,
+ 0x77, 0x69, 0x63, 0x6a, 0x6c, 0x75, 0x62, 0x62, 0x77, 0x77, 0x64, 0x77, 0x72, 0x62, 0x79, 0x74,
+ 0x73, 0x6e, 0x75, 0x69, 0x68, 0x64, 0x71, 0x68, 0x6f, 0x65, 0x64, 0x77, 0x63, 0x6f, 0x75, 0x74,
+ 0x6a, 0x74, 0x75, 0x64, 0x62, 0x68, 0x62, 0x76, 0x75, 0x75, 0x66, 0x63, 0x69, 0x62, 0x79, 0x62,
+ 0x6d, 0x70, 0x75, 0x71, 0x74, 0x76, 0x71, 0x63, 0x68, 0x6f, 0x69, 0x77, 0x6b, 0x6b, 0x61, 0x6b,
+ 0x79, 0x74, 0x77, 0x6c, 0x65, 0x67, 0x6b, 0x71, 0x61, 0x76, 0x66, 0x77, 0x69, 0x72, 0x79, 0x66,
+ 0x6e, 0x63, 0x75, 0x70, 0x73, 0x6f, 0x6c, 0x72, 0x70, 0x69, 0x6e, 0x71, 0x61, 0x77, 0x6b, 0x61,
+ 0x6c, 0x70, 0x69, 0x74, 0x72, 0x6a, 0x75, 0x74, 0x78, 0x6e, 0x6a, 0x71, 0x6b, 0x6a, 0x67, 0x71,
+ 0x78, 0x61, 0x6d, 0x77, 0x6b, 0x6d, 0x68, 0x77, 0x79, 0x66, 0x77, 0x6a, 0x75, 0x76, 0x70, 0x62,
+ 0x67, 0x64, 0x64, 0x73, 0x6e, 0x72, 0x71, 0x69, 0x65, 0x6e, 0x66, 0x64, 0x70, 0x65, 0x73, 0x67,
+ 0x6e, 0x74, 0x6e, 0x6e, 0x62, 0x78, 0x68, 0x71, 0x64, 0x62, 0x68, 0x6e, 0x77, 0x6c, 0x6c, 0x74,
+ 0x66, 0x76, 0x72, 0x65, 0x62, 0x79, 0x6d, 0x72, 0x79, 0x78, 0x63, 0x62, 0x64, 0x77, 0x6a, 0x67,
+ 0x6b, 0x6b, 0x79, 0x65, 0x6d, 0x67, 0x6a, 0x72, 0x79, 0x78, 0x63, 0x64, 0x68, 0x66, 0x75, 0x6e,
+ 0x63, 0x6d, 0x70, 0x66, 0x69, 0x64, 0x64, 0x76, 0x74, 0x62, 0x69, 0x66, 0x6f, 0x75, 0x6d, 0x67,
+ 0x63, 0x79, 0x74, 0x61, 0x72, 0x70, 0x6a, 0x73, 0x65, 0x71, 0x71, 0x62, 0x75, 0x75, 0x65, 0x69,
+ 0x6d, 0x6d, 0x72, 0x64, 0x62, 0x64, 0x72, 0x63, 0x74, 0x6e, 0x74, 0x72, 0x6f, 0x79, 0x67, 0x79,
+ 0x78, 0x6c, 0x64, 0x63, 0x6b, 0x74, 0x76, 0x78, 0x70, 0x64, 0x6c, 0x64, 0x72, 0x66, 0x62, 0x71,
+ 0x69, 0x62, 0x61, 0x6a, 0x6f, 0x73, 0x6d, 0x77, 0x6d, 0x65, 0x6b, 0x71, 0x64, 0x6c, 0x63, 0x61,
+ 0x6c, 0x71, 0x65, 0x6c, 0x78, 0x74, 0x76, 0x67, 0x74, 0x69, 0x64, 0x76, 0x72, 0x74, 0x75, 0x68,
+ 0x68, 0x63, 0x77, 0x65, 0x64, 0x67, 0x70, 0x70, 0x70, 0x61, 0x6b, 0x65, 0x68, 0x67, 0x6d, 0x72,
+ 0x6e, 0x6a, 0x64, 0x68, 0x62, 0x69, 0x61, 0x76, 0x6b, 0x71, 0x64, 0x74, 0x66, 0x67, 0x6c, 0x71,
+ 0x6b, 0x76, 0x61, 0x77, 0x79, 0x75, 0x76, 0x6c, 0x71, 0x62, 0x6e, 0x61, 0x6b, 0x6d, 0x6e, 0x6d,
+ 0x77, 0x72, 0x75, 0x66, 0x72, 0x6b, 0x62, 0x6b, 0x62, 0x73, 0x75, 0x75, 0x71, 0x61, 0x67, 0x66,
+ 0x79, 0x70, 0x6c, 0x6c, 0x6b, 0x62, 0x63, 0x66, 0x6a, 0x74, 0x76, 0x6d, 0x70, 0x71, 0x6a, 0x62,
+ 0x68, 0x75, 0x72, 0x75, 0x62, 0x74, 0x70, 0x6b, 0x66, 0x61, 0x67, 0x6e, 0x61, 0x6f, 0x6f, 0x68,
+ 0x77, 0x6a, 0x72, 0x78, 0x79, 0x73, 0x71, 0x78, 0x75, 0x61, 0x78, 0x63, 0x6a, 0x75, 0x73, 0x73,
+ 0x62, 0x70, 0x71, 0x75, 0x63, 0x6e, 0x66, 0x72, 0x74, 0x79, 0x6f, 0x6f, 0x73, 0x6a, 0x75, 0x62,
+ 0x65, 0x79, 0x79, 0x79, 0x75, 0x6f, 0x79, 0x78, 0x6e, 0x77, 0x75, 0x62, 0x66, 0x6e, 0x67, 0x73,
+ 0x63, 0x68, 0x65, 0x74, 0x68, 0x75, 0x6a, 0x78, 0x69, 0x6f, 0x70, 0x62, 0x78, 0x65, 0x68, 0x63,
+ 0x75, 0x78, 0x62, 0x6d, 0x73, 0x6a, 0x68, 0x6a, 0x76, 0x6f, 0x76, 0x6a, 0x6c, 0x73, 0x62, 0x6e,
+ 0x64, 0x68, 0x6f, 0x63, 0x63, 0x67, 0x72, 0x78, 0x64, 0x71, 0x74, 0x76, 0x71, 0x62, 0x66, 0x79,
+ 0x70, 0x61, 0x78, 0x61, 0x77, 0x64, 0x69, 0x79, 0x6e, 0x64, 0x78, 0x66, 0x68, 0x75, 0x65, 0x76,
+ 0x70, 0x6a, 0x67, 0x6a, 0x63, 0x67, 0x61, 0x74, 0x67, 0x77, 0x6c, 0x78, 0x78, 0x75, 0x73, 0x64,
+ 0x69, 0x64, 0x69, 0x6f, 0x72, 0x62, 0x70, 0x65, 0x77, 0x74, 0x74, 0x66, 0x76, 0x6e, 0x74, 0x6b,
+ 0x74, 0x77, 0x76, 0x67, 0x62, 0x75, 0x68, 0x62, 0x66, 0x78, 0x77, 0x72, 0x6a, 0x66, 0x6a, 0x64,
+ 0x6b, 0x6c, 0x70, 0x78, 0x63, 0x77, 0x6b, 0x63, 0x62, 0x77, 0x77, 0x77, 0x65, 0x66, 0x73, 0x64,
+ 0x62, 0x76, 0x6b, 0x73, 0x75, 0x6a, 0x70, 0x63, 0x72, 0x6b, 0x71, 0x73, 0x66, 0x61, 0x70, 0x6c,
+ 0x76, 0x64, 0x61, 0x74, 0x69, 0x76, 0x79, 0x6a, 0x76, 0x75, 0x65, 0x6e, 0x67, 0x63, 0x6e, 0x75,
+ 0x65, 0x74, 0x6c, 0x75, 0x78, 0x6a, 0x6b, 0x78, 0x66, 0x68, 0x71, 0x6e, 0x76, 0x68, 0x68, 0x6c,
+ 0x70, 0x6c, 0x66, 0x64, 0x65, 0x72, 0x6f, 0x73, 0x74, 0x75, 0x6f, 0x6b, 0x6c, 0x6b, 0x6b, 0x6a,
+ 0x74, 0x71, 0x66, 0x63, 0x70, 0x69, 0x6e, 0x61, 0x6d, 0x64, 0x6b, 0x73, 0x74, 0x75, 0x6f, 0x6f,
+ 0x61, 0x61, 0x65, 0x6d, 0x73, 0x67, 0x71, 0x77, 0x65, 0x70, 0x73, 0x67, 0x6d, 0x6d, 0x6d, 0x62,
+ 0x74, 0x6c, 0x68, 0x65, 0x68, 0x74, 0x65, 0x76, 0x62, 0x6f, 0x70, 0x6c, 0x74, 0x68, 0x76, 0x6e,
+ 0x6c, 0x68, 0x6b, 0x76, 0x68, 0x68, 0x78, 0x72, 0x79, 0x6a, 0x67, 0x64, 0x71, 0x71, 0x6a, 0x77,
+ 0x6a, 0x74, 0x74, 0x64, 0x72, 0x69, 0x74, 0x74, 0x66, 0x61, 0x71, 0x70, 0x64, 0x68, 0x69, 0x6b,
+ 0x6b, 0x6e, 0x6b, 0x6a, 0x61, 0x70, 0x73, 0x68, 0x70, 0x62, 0x65, 0x77, 0x77, 0x62, 0x64, 0x73,
+ 0x6a, 0x6e, 0x79, 0x76, 0x78, 0x68, 0x6d, 0x6a, 0x6c, 0x76, 0x61, 0x71, 0x73, 0x73, 0x69, 0x78,
+ 0x69, 0x6c, 0x65, 0x73, 0x6f, 0x6d, 0x67, 0x6a, 0x64, 0x6b, 0x6e, 0x73, 0x69, 0x72, 0x64, 0x65,
+ 0x77, 0x71, 0x79, 0x72, 0x67, 0x71, 0x68, 0x6a, 0x63, 0x74, 0x75, 0x70, 0x79, 0x75, 0x73, 0x78,
+ 0x6d, 0x64, 0x76, 0x63, 0x67, 0x64, 0x69, 0x6b, 0x77, 0x73, 0x6a, 0x6d, 0x6b, 0x6d, 0x77, 0x68,
+ 0x62, 0x6c, 0x67, 0x76, 0x6a, 0x6f, 0x64, 0x6a, 0x76, 0x64, 0x61, 0x6a, 0x74, 0x77, 0x68, 0x69,
+ 0x6a, 0x6d, 0x6d, 0x6b, 0x67, 0x6f, 0x76, 0x73, 0x6e, 0x6f, 0x70, 0x67, 0x65, 0x77, 0x68, 0x63,
+ 0x64, 0x75, 0x64, 0x63, 0x79, 0x62, 0x77, 0x78, 0x64, 0x70, 0x6b, 0x70, 0x70, 0x69, 0x75, 0x62,
+ 0x73, 0x76, 0x76, 0x72, 0x67, 0x6f, 0x73, 0x6e, 0x70, 0x74, 0x70, 0x6d, 0x78, 0x79, 0x77, 0x6e,
+ 0x75, 0x77, 0x70, 0x76, 0x6f, 0x62, 0x73, 0x64, 0x78, 0x6f, 0x66, 0x64, 0x67, 0x69, 0x68, 0x79,
+ 0x70, 0x71, 0x72, 0x72, 0x6a, 0x73, 0x6d, 0x78, 0x61, 0x64, 0x67, 0x70, 0x72, 0x77, 0x79, 0x65,
+ 0x6e, 0x72, 0x62, 0x64, 0x74, 0x62, 0x72, 0x72, 0x78, 0x76, 0x79, 0x76, 0x68, 0x63, 0x64, 0x62,
+ 0x67, 0x70, 0x6e, 0x76, 0x6d, 0x6f, 0x74, 0x72, 0x76, 0x61, 0x64, 0x72, 0x74, 0x79, 0x67, 0x64,
+ 0x6f, 0x74, 0x69, 0x68, 0x78, 0x79, 0x65, 0x69, 0x69, 0x64, 0x6c, 0x63, 0x69, 0x62, 0x63, 0x61,
+ 0x64, 0x68, 0x78, 0x65, 0x64, 0x67, 0x6d, 0x6e, 0x6e, 0x67, 0x71, 0x6e, 0x65, 0x76, 0x76, 0x64,
+ 0x6a, 0x66, 0x65, 0x70, 0x77, 0x79, 0x67, 0x74, 0x74, 0x67, 0x6c, 0x69, 0x65, 0x72, 0x6e, 0x70,
+ 0x62, 0x6b, 0x78, 0x65, 0x68, 0x74, 0x70, 0x6a, 0x64, 0x6d, 0x6e, 0x79, 0x76, 0x73, 0x70, 0x72,
+ 0x6f, 0x67, 0x62, 0x75, 0x63, 0x64, 0x74, 0x74, 0x71, 0x78, 0x73, 0x79, 0x69, 0x79, 0x62, 0x66,
+ 0x64, 0x75, 0x6f, 0x76, 0x68, 0x75, 0x72, 0x75, 0x66, 0x71, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x67,
+ 0x61, 0x69, 0x6b, 0x69, 0x77, 0x68, 0x72, 0x69, 0x6b, 0x70, 0x72, 0x6b, 0x75, 0x66, 0x64, 0x79,
+ 0x6b, 0x65, 0x71, 0x69, 0x61, 0x67, 0x6c, 0x75, 0x64, 0x70, 0x75, 0x72, 0x68, 0x65, 0x73, 0x69,
+ 0x67, 0x65, 0x6d, 0x71, 0x66, 0x79, 0x61, 0x64, 0x70, 0x65, 0x73, 0x6a, 0x76, 0x76, 0x79, 0x71,
+ 0x71, 0x6e, 0x76, 0x64, 0x63, 0x6b, 0x73, 0x70, 0x6a, 0x6d, 0x65, 0x73, 0x74, 0x63, 0x6a, 0x6f,
+ 0x73, 0x75, 0x76, 0x72, 0x67, 0x67, 0x61, 0x72, 0x66, 0x70, 0x77, 0x79, 0x78, 0x6a, 0x76, 0x64,
+ 0x61, 0x6c, 0x6f, 0x79, 0x73, 0x62, 0x69, 0x62, 0x64, 0x6e, 0x62, 0x69, 0x67, 0x6d, 0x6e, 0x6b,
+ 0x78, 0x66, 0x64, 0x61, 0x6d, 0x79, 0x63, 0x77, 0x66, 0x61, 0x64, 0x72, 0x6a, 0x6e, 0x74, 0x78,
+ 0x62, 0x63, 0x6f, 0x6f, 0x66, 0x6d, 0x6b, 0x63, 0x64, 0x76, 0x6d, 0x67, 0x75, 0x65, 0x68, 0x6b,
+ 0x68, 0x63, 0x77, 0x6a, 0x70, 0x77, 0x6b, 0x67, 0x61, 0x79, 0x6b, 0x62, 0x79, 0x74, 0x68, 0x73,
+ 0x71, 0x78, 0x75, 0x75, 0x63, 0x64, 0x6f, 0x6a, 0x6b, 0x61, 0x73, 0x75, 0x6e, 0x71, 0x63, 0x68,
+ 0x71, 0x6a, 0x78, 0x6b, 0x68, 0x70, 0x68, 0x73, 0x72, 0x65, 0x73, 0x64, 0x70, 0x75, 0x64, 0x78,
+ 0x78, 0x78, 0x66, 0x76, 0x6f, 0x71, 0x65, 0x63, 0x61, 0x76, 0x74, 0x64, 0x78, 0x6b, 0x72, 0x61,
+ 0x6e, 0x64, 0x6e, 0x65, 0x78, 0x77, 0x63, 0x65, 0x6c, 0x6d, 0x77, 0x6c, 0x79, 0x6b, 0x65, 0x74,
+ 0x78, 0x62, 0x73, 0x66, 0x76, 0x62, 0x79, 0x76, 0x61, 0x79, 0x70, 0x69, 0x64, 0x66, 0x6b, 0x68,
+ 0x72, 0x62, 0x74, 0x6d, 0x6b, 0x75, 0x73, 0x67, 0x68, 0x78, 0x61, 0x6e, 0x62, 0x6f, 0x75, 0x65,
+ 0x72, 0x65, 0x78, 0x75, 0x69, 0x67, 0x70, 0x6e, 0x73, 0x61, 0x71, 0x61, 0x65, 0x77, 0x69, 0x6c,
+ 0x6a, 0x73, 0x62, 0x73, 0x66, 0x6b, 0x6f, 0x71, 0x73, 0x71, 0x62, 0x73, 0x78, 0x6d, 0x77, 0x67,
+ 0x78, 0x75, 0x66, 0x6c, 0x77, 0x6e, 0x65, 0x6c, 0x63, 0x79, 0x77, 0x6f, 0x70, 0x6f, 0x65, 0x75,
+ 0x72, 0x6f, 0x67, 0x63, 0x6c, 0x69, 0x75, 0x70, 0x69, 0x70, 0x6e, 0x67, 0x74, 0x72, 0x72, 0x72,
+ 0x6e, 0x69, 0x70, 0x74, 0x71, 0x68, 0x70, 0x76, 0x6b, 0x62, 0x68, 0x6f, 0x77, 0x66, 0x6b, 0x64,
+ 0x75, 0x66, 0x67, 0x79, 0x65, 0x65, 0x78, 0x61, 0x70, 0x67, 0x77, 0x76, 0x67, 0x6d, 0x64, 0x65,
+ 0x69, 0x62, 0x73, 0x65, 0x67, 0x70, 0x74, 0x6e, 0x67, 0x78, 0x75, 0x69, 0x6a, 0x68, 0x6d, 0x65,
+ 0x78, 0x62, 0x6d, 0x6d, 0x62, 0x68, 0x65, 0x6c, 0x6e, 0x67, 0x6b, 0x61, 0x78, 0x6f, 0x62, 0x79,
+ 0x64, 0x78, 0x6f, 0x65, 0x74, 0x72, 0x73, 0x6d, 0x6c, 0x61, 0x76, 0x77, 0x68, 0x64, 0x71, 0x77,
+ 0x71, 0x79, 0x77, 0x69, 0x6c, 0x66, 0x75, 0x65, 0x79, 0x72, 0x70, 0x75, 0x6b, 0x61, 0x64, 0x6c,
+ 0x67, 0x6e, 0x6e, 0x72, 0x75, 0x68, 0x68, 0x71, 0x62, 0x65, 0x72, 0x72, 0x73, 0x68, 0x71, 0x6a,
+ 0x78, 0x74, 0x74, 0x6a, 0x6a, 0x6f, 0x6d, 0x67, 0x62, 0x61, 0x6b, 0x6a, 0x66, 0x6a, 0x66, 0x6e,
+ 0x75, 0x64, 0x64, 0x74, 0x6e, 0x64, 0x62, 0x6f, 0x71, 0x6d, 0x74, 0x6c, 0x6b, 0x72, 0x6e, 0x68,
+ 0x6a, 0x70, 0x6d, 0x72, 0x66, 0x64, 0x6d, 0x71, 0x6b, 0x66, 0x65, 0x6e, 0x69, 0x74, 0x69, 0x68,
+ 0x78, 0x6c, 0x61, 0x66, 0x79, 0x6e, 0x71, 0x62, 0x75, 0x63, 0x6f, 0x79, 0x6b, 0x78, 0x65, 0x6f,
+ 0x73, 0x70, 0x64, 0x69, 0x74, 0x6d, 0x6d, 0x67, 0x6a, 0x75, 0x79, 0x71, 0x72, 0x67, 0x6f, 0x72,
+ 0x61, 0x6c, 0x79, 0x77, 0x72, 0x67, 0x66, 0x76, 0x77, 0x66, 0x71, 0x79, 0x79, 0x77, 0x74, 0x73,
+ 0x61, 0x63, 0x77, 0x79, 0x6f, 0x6b, 0x70, 0x78, 0x79, 0x62, 0x72, 0x63, 0x78, 0x66, 0x74, 0x65,
+ 0x79, 0x66, 0x63, 0x6a, 0x69, 0x6c, 0x64, 0x62, 0x62, 0x78, 0x64, 0x68, 0x72, 0x73, 0x64, 0x62,
+ 0x67, 0x76, 0x6a, 0x75, 0x74, 0x70, 0x79, 0x6e, 0x6e, 0x64, 0x63, 0x79, 0x63, 0x75, 0x73, 0x67,
+ 0x77, 0x77, 0x6a, 0x69, 0x76, 0x74, 0x75, 0x69, 0x76, 0x71, 0x62, 0x6f, 0x68, 0x70, 0x78, 0x69,
+ 0x75, 0x74, 0x76, 0x66, 0x6c, 0x77, 0x69, 0x66, 0x6b, 0x6b, 0x70, 0x6b, 0x6d, 0x6b, 0x79, 0x76,
+ 0x69, 0x73, 0x64, 0x69, 0x74, 0x64, 0x6b, 0x72, 0x6c, 0x78, 0x64, 0x68, 0x66, 0x63, 0x75, 0x72,
+ 0x6d, 0x6f, 0x68, 0x6e, 0x61, 0x76, 0x72, 0x79, 0x69, 0x67, 0x76, 0x69, 0x70, 0x6e, 0x6a, 0x6c,
+ 0x64, 0x79, 0x68, 0x65, 0x79, 0x63, 0x74, 0x6b, 0x64, 0x72, 0x62, 0x6e, 0x77, 0x6e, 0x6d, 0x62,
+ 0x78, 0x72, 0x77, 0x6c, 0x67, 0x74, 0x6a, 0x75, 0x78, 0x69, 0x65, 0x63, 0x6e, 0x72, 0x70, 0x70,
+ 0x69, 0x69, 0x62, 0x77, 0x61, 0x63, 0x77, 0x62, 0x6e, 0x61, 0x79, 0x66, 0x77, 0x73, 0x75, 0x71,
+ 0x6b, 0x6d, 0x68, 0x74, 0x69, 0x65, 0x6a, 0x67, 0x6c, 0x71, 0x77, 0x6d, 0x68, 0x78, 0x66, 0x68,
+ 0x68, 0x75, 0x6c, 0x65, 0x6e, 0x6e, 0x77, 0x6d, 0x79, 0x62, 0x6c, 0x76, 0x62, 0x6c, 0x69, 0x61,
+ 0x76, 0x68, 0x68, 0x78, 0x6d, 0x68, 0x6d, 0x75, 0x67, 0x6d, 0x68, 0x64, 0x63, 0x68, 0x6e, 0x6d,
+ 0x68, 0x63, 0x76, 0x74, 0x78, 0x78, 0x66, 0x61, 0x64, 0x6a, 0x69, 0x6e, 0x74, 0x66, 0x63, 0x62,
+ 0x68, 0x78, 0x74, 0x73, 0x72, 0x78, 0x6b, 0x76, 0x79, 0x61, 0x65, 0x61, 0x68, 0x6d, 0x73, 0x76,
+ 0x74, 0x69, 0x6c, 0x70, 0x6a, 0x6b, 0x77, 0x72, 0x67, 0x79, 0x65, 0x6c, 0x6a, 0x77, 0x66, 0x73,
+ 0x66, 0x67, 0x71, 0x6f, 0x67, 0x61, 0x68, 0x6c, 0x73, 0x6e, 0x72, 0x67, 0x75, 0x6e, 0x78, 0x79,
+ 0x72, 0x62, 0x78, 0x63, 0x6d, 0x69, 0x79, 0x62, 0x63, 0x70, 0x64, 0x70, 0x67, 0x78, 0x79, 0x6d,
+ 0x76, 0x65, 0x6e, 0x68, 0x70, 0x65, 0x6c, 0x73, 0x71, 0x63, 0x6a, 0x75, 0x75, 0x6f, 0x66, 0x77,
+ 0x72, 0x6d, 0x6b, 0x71, 0x72, 0x6a, 0x6b, 0x71, 0x74, 0x75, 0x64, 0x64, 0x6e, 0x6e, 0x6f, 0x63,
+ 0x74, 0x72, 0x76, 0x76, 0x77, 0x67, 0x65, 0x6a, 0x69, 0x78, 0x63, 0x6a, 0x68, 0x68, 0x63, 0x74,
+ 0x6f, 0x70, 0x61, 0x73, 0x6e, 0x61, 0x64, 0x62, 0x6b, 0x65, 0x61, 0x74, 0x77, 0x62, 0x74, 0x6d,
+ 0x66, 0x62, 0x6e, 0x76, 0x6e, 0x6c, 0x67, 0x78, 0x67, 0x6b, 0x77, 0x64, 0x67, 0x69, 0x73, 0x79,
+ 0x67, 0x77, 0x6e, 0x6f, 0x68, 0x73, 0x6c, 0x69, 0x68, 0x75, 0x65, 0x6c, 0x77, 0x74, 0x77, 0x78,
+ 0x62, 0x62, 0x68, 0x6d, 0x6f, 0x77, 0x74, 0x68, 0x78, 0x67, 0x71, 0x79, 0x62, 0x74, 0x6d, 0x66,
+ 0x78, 0x64, 0x69, 0x6f, 0x65, 0x70, 0x6b, 0x62, 0x68, 0x6c, 0x62, 0x69, 0x63, 0x75, 0x6e, 0x68,
+ 0x79, 0x73, 0x78, 0x6f, 0x6b, 0x78, 0x65, 0x79, 0x71, 0x6e, 0x6a, 0x67, 0x73, 0x62, 0x73, 0x6b,
+ 0x6f, 0x68, 0x6e, 0x65, 0x6e, 0x78, 0x62, 0x61, 0x62, 0x78, 0x6e, 0x71, 0x72, 0x6a, 0x71, 0x67,
+ 0x70, 0x79, 0x67, 0x6e, 0x69, 0x75, 0x74, 0x71, 0x70, 0x78, 0x66, 0x70, 0x61, 0x6f, 0x6a, 0x70,
+ 0x61, 0x76, 0x74, 0x76, 0x67, 0x74, 0x79, 0x65, 0x67, 0x6b, 0x64, 0x78, 0x70, 0x63, 0x66, 0x79,
+ 0x74, 0x65, 0x74, 0x6f, 0x62, 0x79, 0x77, 0x74, 0x77, 0x71, 0x63, 0x61, 0x73, 0x78, 0x6c, 0x77,
+ 0x6c, 0x71, 0x67, 0x62, 0x79, 0x69, 0x67, 0x6a, 0x6f, 0x6c, 0x68, 0x67, 0x64, 0x76, 0x66, 0x73,
+ 0x77, 0x6d, 0x76, 0x65, 0x62, 0x68, 0x68, 0x76, 0x61, 0x68, 0x6d, 0x75, 0x6e, 0x61, 0x76, 0x00, 0x00
+};
+
+
+int main()
+{
+ const int ntests = 10;
+ size_t bufsize = sizeof(buf) - 1;
+ int i;
+ size_t bufsizes[ntests];
+ char old;
+
+ for (i = ntests-1; i >= 0; --i)
+ {
+ bufsizes[i] = bufsize;
+ bufsize /= 2;
+ }
+
+ printf("\n\n");
+ printf("Testing pathological pattern '.+nonexisting.+' to force worst-case asymptotic performance: \n");
+
+ for (i = 0; i < ntests; ++i)
+ {
+ old = buf[bufsizes[i]];
+ buf[bufsizes[i]] = 0;
+
+ printf(" matching on %lu bytes of test input: ", bufsizes[i]);
+ fflush(stdout);
+ printf("%d \n", re_match(".+nonexisting.+", buf));
+
+ buf[bufsizes[i]] = old;
+ }
+
+ printf("\n\n");
+
+ return 0;
+}
+
+
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_print.c b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_print.c
new file mode 100644
index 0000000000..42eddbe704
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_print.c
@@ -0,0 +1,21 @@
+/*
+ This program prints out a verbose explanation of a given regular expression.
+*/
+
+#include <stdio.h>
+#include "re.h"
+
+
+int main(int argc, char** argv)
+{
+ if (argc == 2)
+ {
+ re_print(re_compile(argv[1]));
+ }
+ else
+ {
+ printf("\nUsage: %s <PATTERN> \n", argv[0]);
+ }
+ return -2;
+}
+
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_rand.c b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_rand.c
new file mode 100644
index 0000000000..5a8baede6c
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_rand.c
@@ -0,0 +1,28 @@
+/*
+ This program tries to match a given regular expression with text given as input to stdin.
+ If the text is a match for the pattern, the program returns 0.
+ If the text doesn't match the pattern, the program returns -2.
+
+ This program is used in random testing to test a lot of random text and regex together.
+ See ./scripts/regex_test.py and the Makefile for this project for the gritty details.
+*/
+
+#include <stdio.h>
+#include "re.h"
+
+
+int main(int argc, char** argv)
+{
+ if (argc == 3)
+ {
+ int m = re_match(argv[1], argv[2]);
+ if (m != -1)
+ return 0;
+ }
+ else
+ {
+ printf("\nUsage: %s <PATTERN> <TEXT> \n", argv[0]);
+ }
+ return -2;
+}
+
diff --git a/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_rand_neg.c b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_rand_neg.c
new file mode 100644
index 0000000000..192ce5c608
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/lib/tiny_regex_c/tests/test_rand_neg.c
@@ -0,0 +1,29 @@
+/*
+ Negative version of test_rand.c -- returns true if no match
+
+ This program tries to match a given regular expression with text given as input to stdin.
+ If the text is NOT a match for the pattern, the program returns 0.
+ If the text does match the pattern, the program returns -2.
+
+ This program is used in random testing to test a lot of random text and regex together.
+ See ./scripts/regex_test_neg.py and the Makefile for this project for the gritty details.
+*/
+
+#include <stdio.h>
+#include "re.h"
+
+
+int main(int argc, char** argv)
+{
+ if (argc == 3)
+ {
+ int m = re_match(argv[1], argv[2]);
+ if (m == -1)
+ return 0;
+ }
+ else
+ {
+ printf("\nUsage: %s <PATTERN> <TEXT> \n", argv[0]);
+ }
+ return -2;
+}
diff --git a/erts/lib_src/yielding_c_fun/main_target.mk b/erts/lib_src/yielding_c_fun/main_target.mk
new file mode 100644
index 0000000000..4c97d4d9cf
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/main_target.mk
@@ -0,0 +1,41 @@
+# This file is included by GNUmakefile
+
+ifndef TARGET
+YCF_OBJ_DIR=$(YCF_SOURCE_DIR)
+YCF_BIN_DIR=$(YCF_SOURCE_DIR)/bin
+YCF_EXECUTABLE=$(YCF_BIN_DIR)/yielding_c_fun.bin$(EXE_SUFFIX)
+else
+YCF_OBJ_DIR=$(YCF_SOURCE_DIR)/$(TARGET)
+YCF_BIN_DIR=$(YCF_SOURCE_DIR)/bin/$(TARGET)
+YCF_EXECUTABLE=$(YCF_BIN_DIR)/yielding_c_fun$(EXE_SUFFIX)
+_create_dirs := $(shell mkdir -p $(YCF_OBJ_DIR) $(YCF_BIN_DIR))
+endif
+
+YCF_INCLUDE_DIRS = \
+ -I$(YCF_SOURCE_DIR) \
+ -I$(YCF_SOURCE_DIR)/lib/tiny_regex_c \
+ -I$(YCF_SOURCE_DIR)/lib/simple_c_gc
+
+YCF_HEADERS = $(sort $(shell find $(YCF_SOURCE_DIR) -name '*.h'))
+
+YCF_EXTRA_SOURCES = \
+ $(YCF_SOURCE_DIR)/lib/tiny_regex_c/re.c \
+ $(YCF_SOURCE_DIR)/lib/simple_c_gc/simple_c_gc.c
+
+YCF_SOURCES = $(sort $(wildcard $(YCF_SOURCE_DIR)/*.c) $(YCF_EXTRA_SOURCES))
+
+YCF_OBJECTS = $(addprefix $(YCF_OBJ_DIR)/,$(notdir $(YCF_SOURCES:.c=.o)))
+
+YCF_CFLAGS = $(filter-out -Wstrict-prototypes -Wdeclaration-after-statement -Wmissing-prototypes,$(CFLAGS))
+
+$(YCF_EXECUTABLE): $(YCF_OBJECTS)
+ $(V_LD) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_OBJECTS) -o $@
+
+$(YCF_OBJ_DIR)/%.o: $(YCF_SOURCE_DIR)/lib/tiny_regex_c/%.c $(YCF_HEADERS)
+ $(V_CC) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@
+
+$(YCF_OBJ_DIR)/%.o: $(YCF_SOURCE_DIR)/lib/simple_c_gc/%.c $(YCF_HEADERS)
+ $(V_CC) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@
+
+$(YCF_OBJ_DIR)/%.o: $(YCF_SOURCE_DIR)/%.c $(YCF_HEADERS)
+ $(V_CC) $(YCF_CFLAGS) $(LDFLAGS) $(YCF_INCLUDE_DIRS) -c $< -o $@
diff --git a/erts/lib_src/yielding_c_fun/test/examples/auto_yield.c b/erts/lib_src/yielding_c_fun/test/examples/auto_yield.c
new file mode 100644
index 0000000000..3fa85ba061
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/auto_yield.c
@@ -0,0 +1,124 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+int rec_inc(int to_inc, int nr_of_incs){
+ int res;
+ printf("nr_of_incs %d\n", nr_of_incs);
+ if(nr_of_incs == 0) {
+ return to_inc;
+ } else {
+ res = rec_inc(to_inc + 1, nr_of_incs -1);
+ return res;
+ }
+}
+
+
+int fun(){
+ int i;
+ int v1;
+ int v2 = 13;
+ int v3 = 7;
+ int v4 = 5;
+ int v5 = 2;
+ printf("REC CALL START\n");
+ v1 = rec_inc(42, 100); /* v1 == 142 */
+ printf("REC CALL END\n");
+ printf("FOR LOOP START\n");
+ for(i = 0; i < 100; i++){
+ v2 = v2 + 1;
+ } /* v2 == 113 */
+ printf("FOR LOOP END\n");
+ printf("WHILE LOOP START\n");
+ i = 0;
+ while(i < 100){
+ v3 = v3 + 1;
+ i++;
+ } /* v3 == 107 */
+ printf("WHILE LOOP END\n");
+ printf("DO WHILE LOOP START\n");
+ i = 0;
+ do {
+ v4 = v4 + 1;
+ i++;
+ } while (i < 100); /* v4 == 105 */
+ printf("DO WHILE LOOP END\n");
+ printf("GOTO START\n");
+ i = 0;
+ my_label:
+ v5 = v5 + 1;
+ i++;
+ if (i < 100) goto my_label; /* v5 == 102 */
+ printf("GOTO END\n");
+ return v1 + v2 + v3 + v4 + v5; /* 142 + 113 + 107 + 105 + 102 == 569 */
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ nr_of_reductions = 10;
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL);
+ if(wb != NULL){
+ printf("TRAPPED %ld\n", nr_of_reductions);
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+ printf("REDS LEFT WHEN READY %ld\n", nr_of_reductions);
+#else
+ ret = fun();
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 569){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/auto_yield.c.out b/erts/lib_src/yielding_c_fun/test/examples/auto_yield.c.out
new file mode 100644
index 0000000000..d6962791d0
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/auto_yield.c.out
@@ -0,0 +1,173 @@
+REC CALL START
+nr_of_incs 100
+nr_of_incs 99
+nr_of_incs 98
+nr_of_incs 97
+nr_of_incs 96
+nr_of_incs 95
+nr_of_incs 94
+nr_of_incs 93
+nr_of_incs 92
+TRAPPED 0
+nr_of_incs 91
+nr_of_incs 90
+nr_of_incs 89
+nr_of_incs 88
+nr_of_incs 87
+nr_of_incs 86
+nr_of_incs 85
+nr_of_incs 84
+nr_of_incs 83
+nr_of_incs 82
+TRAPPED 0
+nr_of_incs 81
+nr_of_incs 80
+nr_of_incs 79
+nr_of_incs 78
+nr_of_incs 77
+nr_of_incs 76
+nr_of_incs 75
+nr_of_incs 74
+nr_of_incs 73
+nr_of_incs 72
+TRAPPED 0
+nr_of_incs 71
+nr_of_incs 70
+nr_of_incs 69
+nr_of_incs 68
+nr_of_incs 67
+nr_of_incs 66
+nr_of_incs 65
+nr_of_incs 64
+nr_of_incs 63
+nr_of_incs 62
+TRAPPED 0
+nr_of_incs 61
+nr_of_incs 60
+nr_of_incs 59
+nr_of_incs 58
+nr_of_incs 57
+nr_of_incs 56
+nr_of_incs 55
+nr_of_incs 54
+nr_of_incs 53
+nr_of_incs 52
+TRAPPED 0
+nr_of_incs 51
+nr_of_incs 50
+nr_of_incs 49
+nr_of_incs 48
+nr_of_incs 47
+nr_of_incs 46
+nr_of_incs 45
+nr_of_incs 44
+nr_of_incs 43
+nr_of_incs 42
+TRAPPED 0
+nr_of_incs 41
+nr_of_incs 40
+nr_of_incs 39
+nr_of_incs 38
+nr_of_incs 37
+nr_of_incs 36
+nr_of_incs 35
+nr_of_incs 34
+nr_of_incs 33
+nr_of_incs 32
+TRAPPED 0
+nr_of_incs 31
+nr_of_incs 30
+nr_of_incs 29
+nr_of_incs 28
+nr_of_incs 27
+nr_of_incs 26
+nr_of_incs 25
+nr_of_incs 24
+nr_of_incs 23
+nr_of_incs 22
+TRAPPED 0
+nr_of_incs 21
+nr_of_incs 20
+nr_of_incs 19
+nr_of_incs 18
+nr_of_incs 17
+nr_of_incs 16
+nr_of_incs 15
+nr_of_incs 14
+nr_of_incs 13
+nr_of_incs 12
+TRAPPED 0
+nr_of_incs 11
+nr_of_incs 10
+nr_of_incs 9
+nr_of_incs 8
+nr_of_incs 7
+nr_of_incs 6
+nr_of_incs 5
+nr_of_incs 4
+nr_of_incs 3
+nr_of_incs 2
+TRAPPED 0
+nr_of_incs 1
+nr_of_incs 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+REC CALL END
+FOR LOOP START
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+FOR LOOP END
+WHILE LOOP START
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+WHILE LOOP END
+DO WHILE LOOP START
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+DO WHILE LOOP END
+GOTO START
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+TRAPPED 0
+GOTO END
+REDS LEFT WHEN READY 8
+RETURNED 569
diff --git a/erts/lib_src/yielding_c_fun/test/examples/const_defenition.c b/erts/lib_src/yielding_c_fun/test/examples/const_defenition.c
new file mode 100644
index 0000000000..683acda063
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/const_defenition.c
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+int fun(const char x){
+ const int y = x + 1; /* y == 2*/
+ int const *ptr = NULL;
+ YCF_YIELD();
+ if(ptr != NULL){
+ printf("ERROR\n");
+ }
+ return y;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 1;
+#endif
+ int ret = 0;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 2){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/const_defenition.c.out b/erts/lib_src/yielding_c_fun/test/examples/const_defenition.c.out
new file mode 100644
index 0000000000..37d51cb983
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/const_defenition.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 2
diff --git a/erts/lib_src/yielding_c_fun/test/examples/consume_reds.c b/erts/lib_src/yielding_c_fun/test/examples/consume_reds.c
new file mode 100644
index 0000000000..fcab3dbe5e
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/consume_reds.c
@@ -0,0 +1,85 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+#define YCF_CONSUME_REDS(X)
+
+int fun(int x){
+ int i;
+ int count = 0;
+ for(i = 0; i < 100; i++){
+ count = count + 2;
+ YCF_CONSUME_REDS(10);
+ }
+ return count + x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ int nr_of_yields = 0;
+ long nr_of_reductions;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ nr_of_reductions = 101;
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED %ld\n", nr_of_reductions);
+ nr_of_yields++;
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun(1);
+#endif
+ printf("Number of yields %d\n", nr_of_yields);
+ printf("RETURNED %d\n", ret);
+ if(ret != 201){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/consume_reds.c.out b/erts/lib_src/yielding_c_fun/test/examples/consume_reds.c.out
new file mode 100644
index 0000000000..32b93f33f6
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/consume_reds.c.out
@@ -0,0 +1,11 @@
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+Number of yields 9
+RETURNED 201
diff --git a/erts/lib_src/yielding_c_fun/test/examples/control_statements.c b/erts/lib_src/yielding_c_fun/test/examples/control_statements.c
new file mode 100644
index 0000000000..3abac5e57d
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/control_statements.c
@@ -0,0 +1,289 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+int fun(char x){
+ YCF_YIELD();
+ if(x == 1){
+ /*empty control statement*/
+ } else {
+ /* another empty*/
+ }
+ if(x == 1){
+
+ } else {
+
+ }
+ if(x == 1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_1 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ if(x == 1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_2 %d %d\n", x, y);
+ }else printf("Hej\n");
+ YCF_YIELD();
+ if(x == 1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_3 %d %d\n", x, y);
+ }else if(x == 1){
+ int y = 4;
+ printf("Hej %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ if(x == 2){
+ int y = 4;
+ printf("Hej %d %d\n", x, y);
+ }else if(x == 1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_4 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ if(x == 2){
+ int y = 4;
+ printf("Hej %d %d\n", x, y);
+ }else{
+ int y = 4;
+ YCF_YIELD();
+ printf("s_5 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ while(x == 1) {
+ int y = 4;
+ x = 2;
+ YCF_YIELD();
+ printf("s_6 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ x = 1;
+ YCF_YIELD();
+ do x++; while(x == 1);
+ YCF_YIELD();
+ printf("s_7 %d\n", x);
+ YCF_YIELD();
+ x = 1;
+ do {
+ int y = 4;
+ YCF_YIELD();
+ printf("s_8 %d %d\n", x, y);
+ YCF_YIELD();
+ x++;
+ } while(x == 2);
+ YCF_YIELD();
+ x = 1;
+ YCF_YIELD();
+ while(x==1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_9 %d %d\n", x, y);
+ YCF_YIELD();
+ x++;
+ }
+ YCF_YIELD();
+ x = 2;
+ for(;;){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_10 %d %d\n", x, y);
+ YCF_YIELD();
+ break;
+ }
+ YCF_YIELD();
+ for(x = 0;x < 10;x++){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_11 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ x = 42;
+ YCF_YIELD();
+ switch(x){
+ int y;
+ case 42:
+ y = 4;
+ YCF_YIELD();
+ printf("s_12 %d %d\n", x, y);
+ YCF_YIELD();
+ }
+ YCF_YIELD();
+ {
+ x = 1;
+ YCF_YIELD();
+ if(x == 1) if(1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_1 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ if(x == 1) switch(1){
+ int y;
+ case 1:;
+ y = 4;
+ YCF_YIELD();
+ printf("s_2 %d %d\n", x, y);
+ }else printf("Hej\n");
+ YCF_YIELD();
+ if(x == 1) switch(1){
+ int y;
+ case 1:;
+ y = 4;
+ YCF_YIELD();
+ printf("s_3 %d %d\n", x, y);
+ }else if(x == 1){
+ int y = 4;
+ printf("Hej %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ if(x == 2){
+ int y = 4;
+ printf("Hej %d %d\n", x, y);
+ }else if(x == 1) if(1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_4 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ if(x == 2){
+ int y = 4;
+ printf("Hej %d %d\n", x, y);
+ }else if(1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_5 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ while(x == 1) if(1) {
+ int y = 4;
+ x = 2;
+ YCF_YIELD();
+ printf("s_6 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ x = 1;
+ YCF_YIELD();
+ do if(1) x++; while(x == 1);
+ YCF_YIELD();
+ printf("s_7 %d\n", x);
+ YCF_YIELD();
+ x = 1;
+ do if(1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_8 %d %d\n", x, y);
+ YCF_YIELD();
+ x++;
+ } while(x == 2);
+ YCF_YIELD();
+ x = 1;
+ YCF_YIELD();
+ while(x==1) if(1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_9 %d %d\n", x, y);
+ YCF_YIELD();
+ x++;
+ }
+ YCF_YIELD();
+ x = 2;
+ for(;;) if(1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_10 %d %d\n", x, y);
+ YCF_YIELD();
+ break;
+ }
+ YCF_YIELD();
+ for(x = 0;x < 10;x++) if(1){
+ int y = 4;
+ YCF_YIELD();
+ printf("s_11 %d %d\n", x, y);
+ }
+ YCF_YIELD();
+ x = 42;
+ YCF_YIELD();
+ switch(x){
+ int y;
+ case 42:
+ y = 4;
+ YCF_YIELD();
+ printf("s_12 %d %d\n", x, y);
+ YCF_YIELD();
+ }
+ YCF_YIELD();
+ }
+ return x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ int nr_of_trapps = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ nr_of_trapps++;
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun(1);
+ nr_of_trapps = 86;
+#endif
+ printf("RETURNED %d %d\n", ret, nr_of_trapps);
+ if(ret != 42){
+ return 1;
+ }else{
+ return 0;
+ }
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/control_statements.c.out b/erts/lib_src/yielding_c_fun/test/examples/control_statements.c.out
new file mode 100644
index 0000000000..1beeae592e
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/control_statements.c.out
@@ -0,0 +1,45 @@
+s_1 1 4
+s_2 1 4
+s_3 1 4
+s_4 1 4
+s_5 1 4
+s_6 2 4
+s_7 2
+s_8 1 4
+s_8 2 4
+s_9 1 4
+s_10 2 4
+s_11 0 4
+s_11 1 4
+s_11 2 4
+s_11 3 4
+s_11 4 4
+s_11 5 4
+s_11 6 4
+s_11 7 4
+s_11 8 4
+s_11 9 4
+s_12 42 4
+s_1 1 4
+s_2 1 4
+s_3 1 4
+s_4 1 4
+s_5 1 4
+s_6 2 4
+s_7 2
+s_8 1 4
+s_8 2 4
+s_9 1 4
+s_10 2 4
+s_11 0 4
+s_11 1 4
+s_11 2 4
+s_11 3 4
+s_11 4 4
+s_11 5 4
+s_11 6 4
+s_11 7 4
+s_11 8 4
+s_11 9 4
+s_12 42 4
+RETURNED 42 86
diff --git a/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state.c b/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state.c
new file mode 100644
index 0000000000..739cd9702a
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state.c
@@ -0,0 +1,129 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+int fun(char x){
+ int y = 10;
+ /*special_code_start:ON_SAVE_YIELD_STATE*/
+ if(0){
+ printf("y=%d\n", y);
+ y = 42;
+ }
+ /*special_code_end*/
+ /*special_code_start:ON_RETURN*/
+ if(0){
+ printf("I returned y=%d\n", y);
+ }
+ /*special_code_end*/
+ /*special_code_start:ON_DESTROY_STATE_OR_RETURN*/
+ if(0){
+ printf("I got destroyed or returned y=%d\n", y);
+ }
+ /*special_code_end*/
+ /*special_code_start:ON_RESTORE_YIELD_STATE*/
+ if(0){
+ x = 9;
+ }
+ /*special_code_end*/
+ if(0){
+ /*special_code_start:ON_SAVE_YIELD_STATE*/
+ if(0){
+ int z = 10;
+ printf("y=%d z=%d\n", y, z);
+ }
+ /*special_code_end*/
+ }
+ if(y != 10 || x != 1){
+ /*special_code_start:ON_RESTORE_YIELD_STATE*/
+ if(0){
+ printf("y=%d x=%d\n", y, x);
+ x = x*2;
+ printf("y=%d x=%d\n", y, x);
+ }
+ /*special_code_end*/
+ /*special_code_start:ON_RESTORE_YIELD_STATE*/
+ if(0){
+ printf("y=%d x=%d\n", y, x);
+ x = x/2;
+ printf("y=%d x=%d\n", y, x);
+ }
+ /*special_code_end*/
+ printf("ERROR BEFORE YIELD\n");
+ exit(1);
+ }
+ YCF_YIELD();
+ if(y != 42 || x != 9){
+ printf("ERROR AFTER YIELD\n");
+ exit(1);
+ }
+ printf("SUCCESS\n");
+ return x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 9){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state.c.out b/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state.c.out
new file mode 100644
index 0000000000..639fd64b46
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state.c.out
@@ -0,0 +1,11 @@
+y=10
+y=42 z=10
+TRAPPED
+y=42 x=9
+y=42 x=18
+y=42 x=18
+y=42 x=9
+SUCCESS
+I returned y=42
+I got destroyed or returned y=42
+RETURNED 9
diff --git a/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state_alt_syntax.c b/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state_alt_syntax.c
new file mode 100644
index 0000000000..28e1512ece
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state_alt_syntax.c
@@ -0,0 +1,131 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+#ifndef YCF_YIELDING_C_FUN_HELPERS
+
+#define ON_SAVE_YIELD_STATE
+#define ON_RESTORE_YIELD_STATE
+#define ON_DESTROY_STATE
+#define ON_RETURN
+#define ON_DESTROY_STATE_OR_RETURN
+#define YCF_SPECIAL_CODE_START(PARAM) \
+ /*special_code_start:PARAM*/ \
+ if(0){
+#define YCF_SPECIAL_CODE_END() \
+ } \
+ /*special_code_end*/
+
+#endif
+
+int fun(char x){
+ int y = 10;
+ YCF_SPECIAL_CODE_START(ON_SAVE_YIELD_STATE);
+ printf("y=%d\n", y);
+ y = 42;
+ YCF_SPECIAL_CODE_END();
+ YCF_SPECIAL_CODE_START(ON_RETURN);
+ printf("I returned y=%d\n", y);
+ YCF_SPECIAL_CODE_END();
+ YCF_SPECIAL_CODE_START(ON_DESTROY_STATE_OR_RETURN);
+ printf("I got destroyed or returned y=%d\n", y);
+ YCF_SPECIAL_CODE_END();
+ YCF_SPECIAL_CODE_START(ON_RESTORE_YIELD_STATE);
+ x = 9;
+ YCF_SPECIAL_CODE_END();
+ if(0){
+ YCF_SPECIAL_CODE_START(ON_SAVE_YIELD_STATE); {
+ int z = 10;
+ printf("y=%d z=%d\n", y, z);
+ } YCF_SPECIAL_CODE_END();
+ }
+ if(y != 10 || x != 1){
+ YCF_SPECIAL_CODE_START(ON_RESTORE_YIELD_STATE);
+ printf("y=%d x=%d\n", y, x);
+ x = x*2;
+ printf("y=%d x=%d\n", y, x);
+ YCF_SPECIAL_CODE_END();
+ YCF_SPECIAL_CODE_START(ON_RESTORE_YIELD_STATE);
+ printf("y=%d x=%d\n", y, x);
+ x = x/2;
+ printf("y=%d x=%d\n", y, x);
+ YCF_SPECIAL_CODE_END();
+ printf("ERROR BEFORE YIELD\n");
+ exit(1);
+ }
+ YCF_YIELD();
+ if(y != 42 || x != 9){
+ printf("ERROR AFTER YIELD\n");
+ exit(1);
+ }
+ printf("SUCCESS\n");
+ return x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 9){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state_alt_syntax.c.out b/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state_alt_syntax.c.out
new file mode 100644
index 0000000000..639fd64b46
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/custom_code_save_restore_yield_state_alt_syntax.c.out
@@ -0,0 +1,11 @@
+y=10
+y=42 z=10
+TRAPPED
+y=42 x=9
+y=42 x=18
+y=42 x=18
+y=42 x=9
+SUCCESS
+I returned y=42
+I got destroyed or returned y=42
+RETURNED 9
diff --git a/erts/lib_src/yielding_c_fun/test/examples/debug_ptr_to_stack.c b/erts/lib_src/yielding_c_fun/test/examples/debug_ptr_to_stack.c
new file mode 100644
index 0000000000..76d500de24
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/debug_ptr_to_stack.c
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+int fun(){
+ int array[1];
+ int* ptr;
+ array[0] = 1;
+ ptr = &array[0];
+ (void)ptr;
+ YCF_YIELD();
+ array[0] = 1;
+ return array[0];
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 1;
+#endif
+ int ret = 0;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun();
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 1){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/debug_ptr_to_stack.c.out b/erts/lib_src/yielding_c_fun/test/examples/debug_ptr_to_stack.c.out
new file mode 100644
index 0000000000..7c3cfb5078
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/debug_ptr_to_stack.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 1
diff --git a/erts/lib_src/yielding_c_fun/test/examples/declarations.c b/erts/lib_src/yielding_c_fun/test/examples/declarations.c
new file mode 100644
index 0000000000..718471c73f
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/declarations.c
@@ -0,0 +1,91 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+unsigned long fun(char x, char* y, char **z, unsigned long a, unsigned long* b, unsigned long** c){
+ unsigned int f = 3 + 3;
+ x = x + 1;
+ y = y + 1;
+ z = z + 1;
+ a = a + 1;
+ b = b + 1;
+ c = c + 1;
+ YCF_YIELD();
+ f = f - 6;
+ x = x + 1;
+ y = y + 1;
+ z = z + 1;
+ a = a + 1;
+ b = b + 1;
+ c = c + 1;
+ return (unsigned long)((long)(x + y) + ((z + a) + ((long)b + (long)c))) + f;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1,(void*)1,(void*)1,1,(void*)1,(void*)1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun(1,(void*)1,(void*)1,1,(void*)1,(void*)1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != fun(1,(void*)1,(void*)1,1,(void*)1,(void*)1)){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/declarations.c.out b/erts/lib_src/yielding_c_fun/test/examples/declarations.c.out
new file mode 100644
index 0000000000..d96b566da5
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/declarations.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 361
diff --git a/erts/lib_src/yielding_c_fun/test/examples/declarations_in_for_loops.c b/erts/lib_src/yielding_c_fun/test/examples/declarations_in_for_loops.c
new file mode 100644
index 0000000000..a8d2b02f02
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/declarations_in_for_loops.c
@@ -0,0 +1,83 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+int fun(){
+ int y = 0;
+ for(int x = 0; x < 10; x++){
+ y++;
+ for(int x = 0; x < 10; x++){
+ y++;
+ }
+ }
+ return y;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ nr_of_reductions = 1;
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 110){
+ return 1;
+ }else{
+ return 0;
+ }
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/declarations_in_for_loops.c.out b/erts/lib_src/yielding_c_fun/test/examples/declarations_in_for_loops.c.out
new file mode 100644
index 0000000000..88a9c3bbd1
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/declarations_in_for_loops.c.out
@@ -0,0 +1,211 @@
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+RETURNED 110
diff --git a/erts/lib_src/yielding_c_fun/test/examples/destroy_while_yielded.c b/erts/lib_src/yielding_c_fun/test/examples/destroy_while_yielded.c
new file mode 100644
index 0000000000..aedaf74fb0
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/destroy_while_yielded.c
@@ -0,0 +1,92 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+void fun(int level){
+ void* my_mem = malloc(1000);
+ /*special_code_start:ON_DESTROY_STATE*/
+ if(0){
+ printf("FREE AT LEVEL %d\n", level);
+ free(my_mem);
+ }
+ /*special_code_end*/
+ /*special_code_start:ON_DESTROY_STATE_OR_RETURN*/
+ if(0){
+ printf("I got destroyed or returned %d\n", level);
+ }
+ /*special_code_end*/
+ printf("LEVEL %d\n", level);
+ if (level == 10) {
+ YCF_YIELD();
+ printf("SHOULD NOT BE PRINTED 1\n");
+ return;
+ }else {
+ fun(level + 1);
+ printf("SHOULD NOT BE PRINTED 2\n");
+ }
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 1;
+ do {
+ fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ fun_ycf_gen_destroy(wb);
+ printf("DESTROYED\n");
+ wb = NULL;
+ break;
+ }
+ } while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED\n");
+ return 0;
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/destroy_while_yielded.c.out b/erts/lib_src/yielding_c_fun/test/examples/destroy_while_yielded.c.out
new file mode 100644
index 0000000000..bdf7893e32
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/destroy_while_yielded.c.out
@@ -0,0 +1,33 @@
+LEVEL 1
+LEVEL 2
+LEVEL 3
+LEVEL 4
+LEVEL 5
+LEVEL 6
+LEVEL 7
+LEVEL 8
+LEVEL 9
+LEVEL 10
+TRAPPED
+I got destroyed or returned 1
+FREE AT LEVEL 1
+I got destroyed or returned 2
+FREE AT LEVEL 2
+I got destroyed or returned 3
+FREE AT LEVEL 3
+I got destroyed or returned 4
+FREE AT LEVEL 4
+I got destroyed or returned 5
+FREE AT LEVEL 5
+I got destroyed or returned 6
+FREE AT LEVEL 6
+I got destroyed or returned 7
+FREE AT LEVEL 7
+I got destroyed or returned 8
+FREE AT LEVEL 8
+I got destroyed or returned 9
+FREE AT LEVEL 9
+I got destroyed or returned 10
+FREE AT LEVEL 10
+DESTROYED
+RETURNED
diff --git a/erts/lib_src/yielding_c_fun/test/examples/in_code_var_declaration.c b/erts/lib_src/yielding_c_fun/test/examples/in_code_var_declaration.c
new file mode 100644
index 0000000000..a99fed5753
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/in_code_var_declaration.c
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+int fun(char x){
+ x = x + 1; /* x == 2*/
+ int y;
+ y = 1;
+ int z = 1;
+ YCF_YIELD();
+ x = x + y + z; /* x == 4*/
+ return x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 1;
+#endif
+ int ret = 0;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 4){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/in_code_var_declaration.c.out b/erts/lib_src/yielding_c_fun/test/examples/in_code_var_declaration.c.out
new file mode 100644
index 0000000000..cf0b7ff2c6
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/in_code_var_declaration.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 4
diff --git a/erts/lib_src/yielding_c_fun/test/examples/multi_scope_yield.c b/erts/lib_src/yielding_c_fun/test/examples/multi_scope_yield.c
new file mode 100644
index 0000000000..87024abe39
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/multi_scope_yield.c
@@ -0,0 +1,93 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+int fun(char x){
+ long y = x + 1; /* y == 2*/
+ YCF_YIELD();
+ {
+ int x = 10;
+ YCF_YIELD();
+ y = y + x; /* y == 12*/
+ YCF_YIELD();
+ {
+ long x = 30;
+ YCF_YIELD();
+ y = y + x; /* y == 42*/
+ YCF_YIELD();
+ }
+ YCF_YIELD();
+ y = y + x; /* y == 52*/
+ }
+ YCF_YIELD();
+ y = y + x; /* y == 53*/
+ YCF_YIELD();
+ return y;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 53){
+ return 1;
+ }else{
+ return 0;
+ }
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/multi_scope_yield.c.out b/erts/lib_src/yielding_c_fun/test/examples/multi_scope_yield.c.out
new file mode 100644
index 0000000000..bf653730af
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/multi_scope_yield.c.out
@@ -0,0 +1,9 @@
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+RETURNED 53
diff --git a/erts/lib_src/yielding_c_fun/test/examples/nested_call_consume_reds.c b/erts/lib_src/yielding_c_fun/test/examples/nested_call_consume_reds.c
new file mode 100644
index 0000000000..239de1a334
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/nested_call_consume_reds.c
@@ -0,0 +1,91 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+#define YCF_CONSUME_REDS(X)
+
+int sub_fun(int x){
+ int i;
+ int count = 0;
+ for(i = 0; i < 100; i++){
+ count = count + 2;
+ YCF_CONSUME_REDS(10);
+ }
+ return count + x;
+}
+
+int fun(int x){
+ int ret;
+ ret = sub_fun(x);
+ return ret;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ int nr_of_yields = 0;
+ long nr_of_reductions;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ nr_of_reductions = 101;
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED %ld\n", nr_of_reductions);
+ nr_of_yields++;
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun(1);
+#endif
+ printf("Number of yields %d\n", nr_of_yields);
+ printf("RETURNED %d\n", ret);
+ if(ret != 201){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/nested_call_consume_reds.c.out b/erts/lib_src/yielding_c_fun/test/examples/nested_call_consume_reds.c.out
new file mode 100644
index 0000000000..32b93f33f6
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/nested_call_consume_reds.c.out
@@ -0,0 +1,11 @@
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+TRAPPED -9
+Number of yields 9
+RETURNED 201
diff --git a/erts/lib_src/yielding_c_fun/test/examples/nested_loop_yield.c b/erts/lib_src/yielding_c_fun/test/examples/nested_loop_yield.c
new file mode 100644
index 0000000000..b87685e23d
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/nested_loop_yield.c
@@ -0,0 +1,104 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+int fun(){
+ int x;
+ int y;
+ int z;
+ int outer = 0;
+ int inner = 0;
+ for(x = 0; x < 2; x++){
+ for(y = 0; y < 2; y++){ /* 2 times */
+ for(z = 0; z < 2; z++){ /* 4 times */
+ YCF_YIELD(); /* 8 times */
+ outer++;
+ printf("outer %d: x=%d y=%d z=%d\n", outer, x, y, z);
+ {
+ int x;
+ int y;
+ int z;
+ for(x = 0; x < 2; x++){ /* 8 times */
+ for(y = 0; y < 2; y++){ /* 16 times */
+ for(z = 0; z < 2; z++){ /* 32 times */
+ YCF_YIELD(); /* 64 times */
+ inner++;
+ printf("inner %d: x=%d y=%d z=%d\n", inner, x, y, z);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return inner + outer;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ int nr_of_traps = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL);
+ if(wb != NULL){
+ nr_of_traps++;
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun();
+#endif
+ printf("NR OF TRAPS %d\n", nr_of_traps);
+ printf("RETURNED %d\n", ret);
+ if(ret != 72 || nr_of_traps != ret){
+ return 1;
+ }else{
+ return 0;
+ }
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/nested_loop_yield.c.out b/erts/lib_src/yielding_c_fun/test/examples/nested_loop_yield.c.out
new file mode 100644
index 0000000000..b48664e112
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/nested_loop_yield.c.out
@@ -0,0 +1,74 @@
+outer 1: x=0 y=0 z=0
+inner 1: x=0 y=0 z=0
+inner 2: x=0 y=0 z=1
+inner 3: x=0 y=1 z=0
+inner 4: x=0 y=1 z=1
+inner 5: x=1 y=0 z=0
+inner 6: x=1 y=0 z=1
+inner 7: x=1 y=1 z=0
+inner 8: x=1 y=1 z=1
+outer 2: x=0 y=0 z=1
+inner 9: x=0 y=0 z=0
+inner 10: x=0 y=0 z=1
+inner 11: x=0 y=1 z=0
+inner 12: x=0 y=1 z=1
+inner 13: x=1 y=0 z=0
+inner 14: x=1 y=0 z=1
+inner 15: x=1 y=1 z=0
+inner 16: x=1 y=1 z=1
+outer 3: x=0 y=1 z=0
+inner 17: x=0 y=0 z=0
+inner 18: x=0 y=0 z=1
+inner 19: x=0 y=1 z=0
+inner 20: x=0 y=1 z=1
+inner 21: x=1 y=0 z=0
+inner 22: x=1 y=0 z=1
+inner 23: x=1 y=1 z=0
+inner 24: x=1 y=1 z=1
+outer 4: x=0 y=1 z=1
+inner 25: x=0 y=0 z=0
+inner 26: x=0 y=0 z=1
+inner 27: x=0 y=1 z=0
+inner 28: x=0 y=1 z=1
+inner 29: x=1 y=0 z=0
+inner 30: x=1 y=0 z=1
+inner 31: x=1 y=1 z=0
+inner 32: x=1 y=1 z=1
+outer 5: x=1 y=0 z=0
+inner 33: x=0 y=0 z=0
+inner 34: x=0 y=0 z=1
+inner 35: x=0 y=1 z=0
+inner 36: x=0 y=1 z=1
+inner 37: x=1 y=0 z=0
+inner 38: x=1 y=0 z=1
+inner 39: x=1 y=1 z=0
+inner 40: x=1 y=1 z=1
+outer 6: x=1 y=0 z=1
+inner 41: x=0 y=0 z=0
+inner 42: x=0 y=0 z=1
+inner 43: x=0 y=1 z=0
+inner 44: x=0 y=1 z=1
+inner 45: x=1 y=0 z=0
+inner 46: x=1 y=0 z=1
+inner 47: x=1 y=1 z=0
+inner 48: x=1 y=1 z=1
+outer 7: x=1 y=1 z=0
+inner 49: x=0 y=0 z=0
+inner 50: x=0 y=0 z=1
+inner 51: x=0 y=1 z=0
+inner 52: x=0 y=1 z=1
+inner 53: x=1 y=0 z=0
+inner 54: x=1 y=0 z=1
+inner 55: x=1 y=1 z=0
+inner 56: x=1 y=1 z=1
+outer 8: x=1 y=1 z=1
+inner 57: x=0 y=0 z=0
+inner 58: x=0 y=0 z=1
+inner 59: x=0 y=1 z=0
+inner 60: x=0 y=1 z=1
+inner 61: x=1 y=0 z=0
+inner 62: x=1 y=0 z=1
+inner 63: x=1 y=1 z=0
+inner 64: x=1 y=1 z=1
+NR OF TRAPS 72
+RETURNED 72
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/.gitignore b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/.gitignore
new file mode 100644
index 0000000000..b5e8b97bac
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/.gitignore
@@ -0,0 +1,25 @@
+.rebar3
+_*
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+logs
+_build
+.idea
+*.iml
+rebar3.crashdump
+*~
+c_src/compile_commands.json
+c_src/compile_commands_old.json
+c_src/gen_yielding_sha_256.c
+c_src/gen_yielding_sha_256.h
+priv/
+rebar.lock
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/LICENSE b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/LICENSE
new file mode 100644
index 0000000000..66232832b9
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/LICENSE
@@ -0,0 +1,191 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2019, Kjell Winblad <kjellwinblad@gmail.com>.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/Makefile b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/Makefile
new file mode 100644
index 0000000000..8bae3dc26a
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/Makefile
@@ -0,0 +1,34 @@
+
+ifeq ($(wildcard rebar3),rebar3)
+REBAR3 = $(CURDIR)/rebar3
+endif
+
+REBAR3 ?= $(shell test -e `which rebar3` 2>/dev/null && which rebar3 || echo "./rebar3")
+
+
+.PHONY: deps test build
+
+all: build test docs
+
+build: $(REBAR3)
+ @$(REBAR3) compile
+
+deps:
+ @$(REBAR3) get-deps
+
+clean:
+ @$(REBAR3) clean
+
+distclean: clean
+ @$(REBAR3) delete-deps
+
+docs:
+ @$(REBAR3) edoc
+
+
+test:
+ @$(REBAR3) ct
+
+
+release: test
+ @$(REBAR3) release
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/README.md b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/README.md
new file mode 100644
index 0000000000..ba579880b3
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/README.md
@@ -0,0 +1,23 @@
+sha256_nif
+=====
+
+An OTP library that illustrates how one can implement a yielding
+Erlang NIF API with the help of Yielding C Fun.
+
+Build
+-----
+
+ $ rebar3 compile
+
+or
+
+ $ make
+
+Test
+----
+
+ $ rebar3 ct
+
+or
+
+ $ make test \ No newline at end of file
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/Makefile b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/Makefile
new file mode 100644
index 0000000000..dde621c591
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/Makefile
@@ -0,0 +1,83 @@
+# Based on c_src.mk from erlang.mk by Loic Hoguin <essen@ninenines.eu>
+
+CURDIR := $(shell pwd)
+BASEDIR := $(abspath $(CURDIR)/..)
+
+PROJECT ?= $(notdir $(BASEDIR))
+PROJECT := $(strip $(PROJECT))
+
+ERTS_INCLUDE_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~ts/erts-~ts/include/\", [code:root_dir(), erlang:system_info(version)]).")
+ERL_INTERFACE_INCLUDE_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~ts\", [code:lib_dir(erl_interface, include)]).")
+ERL_INTERFACE_LIB_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~ts\", [code:lib_dir(erl_interface, lib)]).")
+
+C_SRC_DIR = $(CURDIR)
+C_SRC_OUTPUT ?= $(CURDIR)/../priv/$(PROJECT).so
+
+# System type and C compiler/flags.
+
+UNAME_SYS := $(shell uname -s)
+ifeq ($(UNAME_SYS), Darwin)
+ CC ?= cc
+ CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall
+ CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall
+ LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress
+else ifeq ($(UNAME_SYS), FreeBSD)
+ CC ?= cc
+ CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
+ CXXFLAGS ?= -O3 -finline-functions -Wall
+else ifeq ($(UNAME_SYS), Linux)
+ CC ?= gcc
+ CFLAGS ?= -O3 -std=c99 -g -Wall -Wno-missing-prototypes -Wno-unused-function
+#-O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
+ CXXFLAGS ?= -O3 -finline-functions -Wall
+endif
+
+EXTRACFLAGSYCFCODE=-Wno-unused-function
+
+CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
+CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
+
+LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lerl_interface -lei
+LDFLAGS += -shared
+
+# Verbosity.
+
+c_verbose_0 = @echo " C " $(?F);
+c_verbose = $(c_verbose_$(V))
+
+cpp_verbose_0 = @echo " CPP " $(?F);
+cpp_verbose = $(cpp_verbose_$(V))
+
+link_verbose_0 = @echo " LD " $(@F);
+link_verbose = $(link_verbose_$(V))
+
+SOURCES := $(shell find $(C_SRC_DIR) -type f \( -name "*.c" -o -name "*.C" -o -name "*.cc" -o -name "*.cpp" \) | grep -v "./sha-2/sha-256_orginal.c" | grep -v "./sha-2/sha-256.c" | grep -v "./gen_yielding_sha_256.c" | grep -v "./sha-2/test.c")
+OBJECTS = gen_yielding_sha_256.o $(addsuffix .o, $(basename $(SOURCES)))
+
+COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c
+COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c
+
+$(C_SRC_OUTPUT): $(OBJECTS)
+ @mkdir -p $(BASEDIR)/priv/
+ $(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $(C_SRC_OUTPUT)
+
+gen_yielding_sha_256.c: sha-2/sha-256.c
+ ../../../../bin/yielding_c_fun -use_gc -yield -debug -f calc_sha_256 -f calc_chunk -header_file_name gen_yielding_sha_256.h "sha-2/sha-256.c" > gen_yielding_sha_256.c
+
+gen_yielding_sha_256.o: gen_yielding_sha_256.c
+ $(CC) $(CFLAGS) $(EXTRACFLAGSYCFCODE) $(CPPFLAGS) -c gen_yielding_sha_256.c
+
+%.o: %.c
+ $(COMPILE_C) $(OUTPUT_OPTION) $<
+
+%.o: %.cc
+ $(COMPILE_CPP) $(OUTPUT_OPTION) $<
+
+%.o: %.C
+ $(COMPILE_CPP) $(OUTPUT_OPTION) $<
+
+%.o: %.cpp
+ $(COMPILE_CPP) $(OUTPUT_OPTION) $<
+
+clean:
+ @rm -f gen_yielding_sha_256.c gen_yielding_sha_256.h $(C_SRC_OUTPUT) $(OBJECTS)
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/.gitignore b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/.gitignore
new file mode 100644
index 0000000000..6587b2f0fc
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/.gitignore
@@ -0,0 +1,3 @@
+*~
+*.o
+test
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/.travis.yml b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/.travis.yml
new file mode 100644
index 0000000000..ffec9e605d
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/.travis.yml
@@ -0,0 +1,2 @@
+language: c
+script: make
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/GIT_VERSION b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/GIT_VERSION
new file mode 100644
index 0000000000..28e48c7b6f
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/GIT_VERSION
@@ -0,0 +1,265 @@
+origin https://github.com/amosnier/sha-2.git (fetch)
+origin https://github.com/amosnier/sha-2.git (push)
+commit ff76937294694ec347819d81a1f580187fb34246
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Sat Jul 28 23:24:58 2018 +0200
+
+ Update README.md
+
+commit ad933ac6959f8507c349b2c60f02b9336f5ade10
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Sat Jul 28 23:24:18 2018 +0200
+
+ Update README.md
+
+commit 92e7db3c188a2c7a971564b2fdac2d38c7b329e3
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Sat Jul 28 23:22:45 2018 +0200
+
+ Update README.md
+
+commit 6d500e221aa29f9a3701833f1f31ccabba9c58f7
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Sat Jul 28 23:22:00 2018 +0200
+
+ Update README.md
+
+ Added section about reference implementation.
+
+commit 924fba5c17abac8e6b90eef99b509f2cf759d223
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Sat Jul 28 22:02:09 2018 +0200
+
+ Replaced some for loops by memset
+
+commit 9583456b329abe8bfbaaedd0aca76fc3eb31e0b8
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Sat Jul 28 19:16:41 2018 +0200
+
+ Implemented workaround for ld issue with large BSS
+
+ The large arrays are now allocated on heap instead of in BSS. This
+ actually feels like a pretty stupid linker limitation, and the
+ workaround leads to code that is less readable. But it only impacts
+ the test program.
+
+ The issue was reported with GCC 7.3.0 and compiling for 64-bit arch on
+ MSYS2's MinGW compiler. GCC 7.3.0 for 64-bit on Linux (my own machine
+ and Travis) seems perfectly OK with a large BSS segment (around 3 GB
+ is what the test program uses for test data).
+
+ Reported issue:
+
+ $ make
+ cc -Wall -Wextra -Wpedantic -c -o test.o test.c
+ cc -Wall -Wextra -Wpedantic -c -o sha-256.o sha-256.c
+ cc test.o sha-256.o -o test
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/crt2.o: in function `__tmainCRTStartup':
+ C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:251:(.text+0x1cd): relocation truncated to fit: R_X86_64_PC32 against symbol `__imp_Sleep' defined in .idata$5 section in C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/libkernel32.a(dkhchs01360.o)
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:278:(.text+0x255): relocation truncated to fit: R_X86_64_PC32 against symbol `__imp_SetUnhandledExceptionFilter' defined in .idata$5 section in C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/libkernel32.a(dkhchs01346.o)
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:286:(.text+0x283): relocation truncated to fit: R_X86_64_PC32 against symbol `__mingw_winmain_hInstance' defined in COMMON section in C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/crt2.o
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:312:(.text+0x2f3): relocation truncated to fit: R_X86_64_PC32 against symbol `__mingw_winmain_lpCmdLine' defined in COMMON section in C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/crt2.o
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:238:(.text+0x475): relocation truncated to fit: R_X86_64_PC32 against symbol `__imp_GetStartupInfoA' defined in .idata$5 section in C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/libkernel32.a(dkhchs00721.o)
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-gccmain.o): in function `__main':
+ C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/gccmain.c:74:(.text+0xb2): relocation truncated to fit: R_X86_64_PC32 against `.bss'
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/gccmain.c:76:(.text+0xc2): relocation truncated to fit: R_X86_64_PC32 against `.bss'
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-charmax.o): in function `my_lconv_init':
+ C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/charmax.c:19:(.text+0x3): relocation truncated to fit: R_X86_64_PC32 against symbol `__imp___lconv_init' defined in .idata$5 section in C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/libmsvcrt.a(dkxcbs00090.o)
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64_libmingw32_a-gs_support.o): in function `__security_init_cookie':
+ C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/gs_support.c:62:(.text+0x47): relocation truncated to fit: R_X86_64_PC32 against symbol `__imp_GetSystemTimeAsFileTime' defined in .idata$5 section in C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/libkernel32.a(dkhchs00746.o)
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/gs_support.c:70:(.text+0x52): relocation truncated to fit: R_X86_64_PC32 against symbol `__imp_GetCurrentProcessId' defined in .idata$5 section in C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/lib/../lib/libkernel32.a(dkhchs00536.o)
+ C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/7.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/gs_support.c:71:(.text+0x5b): additional relocation overflows omitted from the output
+ collect2.exe: error: ld returned 1 exit status
+ make: *** [<builtin>: test] Error 1```
+
+commit 23b53726f3c5c283112110468ff1c9b4981260e9
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Sat Jul 28 16:53:38 2018 +0200
+
+ Added explicit cast to correct printf() related bug
+
+commit f03474231fc0e86c66cbe2a74a9d9f6589dd96f9
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 23:41:17 2017 +0100
+
+ Activated testing of long messages
+
+commit a222d72880c8b9b2b22155d34adfc72e49e511c5
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 23:11:41 2017 +0100
+
+ Minor README change
+
+commit 98a0bf165c558dde77211ff09cc6b0e1c30badc7
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 23:08:48 2017 +0100
+
+ Minor README change
+
+commit 688b14926c42b61dbfb61f3046594afb058dad31
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 23:07:54 2017 +0100
+
+ Added Travis comment
+
+commit d6b49c65bdaa54120d20401e15368cb462f8a122
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 23:00:29 2017 +0100
+
+ Fixed minor warnings
+
+commit a6c25e2cdf96eeb522199c67e895f0791e297fc9
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 22:53:19 2017 +0100
+
+ Included test in make
+
+commit 538cfe97fa7ad217875196b4e5ce8ddfc1d88f7a
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 22:34:57 2017 +0100
+
+ Travis icon again
+
+commit 903e46163b6d6258b3d5e5b5de60f134749e2f10
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 22:33:50 2017 +0100
+
+ Work on Travis icon
+
+commit d87b78db4bfa08ddceecb3837943b7886d2f4f88
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 22:31:02 2017 +0100
+
+ Added Travis link
+
+commit 19d1e8a568af53c9788ed6fc2ecc9fcb34b17037
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 22:24:03 2017 +0100
+
+ Started adding Travis
+
+commit 8f83e8dedf169858efd366fe2237ec31e8f3c3ec
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 21:54:31 2017 +0100
+
+ Minor README change
+
+commit 369c47af630d6f3dc6cf817e402ef070a588d5a9
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 21:52:36 2017 +0100
+
+ Minor README change
+
+commit b177afbe06965bec656f0cf437f449f747f6575e
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 21:45:25 2017 +0100
+
+ Improved formatting
+
+commit 8fd2b04fed6b5bcaad04e66cb2a3158b61b5ce1e
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 21:44:04 2017 +0100
+
+ Improved formatting
+
+commit 96988951cc63d2305ce7612ebad5456bfca444c9
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 21:40:56 2017 +0100
+
+ Improved formatting
+
+commit 37500f461fbc5e0553ec4ea2f5ec449126ac63c0
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 21:37:39 2017 +0100
+
+ Formatting improvement
+
+commit 6f35ae7007f0f9b54e5798d6a63ffbd1f9f9d59c
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 21:34:20 2017 +0100
+
+ Added extensive testing
+
+commit cba84edcb52079236c0c32c591a74ec89155fc62
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Fri Dec 15 19:37:47 2017 +0100
+
+ Made changes according to review comments from StackExchange CODE REVIEW
+
+commit d7214be72713f020f2dffbac01a13442921cabce
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Thu Dec 14 22:45:59 2017 +0100
+
+ Added comment about code review
+
+commit da12ae4e94725e87052da5c0191103de0d817ed5
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Thu Dec 14 20:11:09 2017 +0100
+
+ Tests at compile time instead of runtime
+
+commit b21a7b552969cbf8a2c702f617e15750fe1b2b2b
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 12:03:01 2017 +0100
+
+ Added comment
+
+commit 85e93b4a79b7ed9f20077021b489c3a1cbd6a251
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 12:01:19 2017 +0100
+
+ Typo
+
+commit ccc3d23b29eeb4c1ace83ae93f415d7a3d57ed45
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 12:00:03 2017 +0100
+
+ Added comment.
+
+commit 047f309134372f826027ac383667c18ce7a2062f
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 11:29:58 2017 +0100
+
+ Added comment about testing
+
+commit 5833df68d9de2f1cb1cc3db57608eba25559a9bf
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 11:27:39 2017 +0100
+
+ Removed temporary code
+
+commit f3f5e1b4532a40e37fc9b880f348a525c4a4165a
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 11:23:44 2017 +0100
+
+ Added some notes
+
+commit 097f4b536db64d29fbc5f54d052ba795db68f00f
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 11:18:17 2017 +0100
+
+ Typo
+
+commit 73684be1627a05d4dbd585d2cc926e6b91275dc4
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 11:14:32 2017 +0100
+
+ Corrected typo
+
+commit 489f5e66b63ed90ed3de197237e608e101253efb
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 11:12:47 2017 +0100
+
+ Completed the implementation, including testing, and fixed bugs
+
+commit d676207600a94c5d94ef6f6188cc1c907f06b42a
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Wed Dec 13 00:52:28 2017 +0100
+
+ Started a SHA-256 implementation. Buggy so far.
+
+commit 3433d4ef5ccb136f0b889610cac3f752776ea525
+Author: Alain Mosnier <alain@wanamoon.net>
+Date: Tue Dec 12 20:48:09 2017 +0100
+
+ Initial commit
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/LICENSE b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/LICENSE
new file mode 100644
index 0000000000..cf1ab25da0
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/Makefile b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/Makefile
new file mode 100644
index 0000000000..53bcbab93e
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/Makefile
@@ -0,0 +1,11 @@
+CFLAGS = -Wall -Wextra -Wpedantic
+
+.PHONY: all
+all: test
+ ./test
+
+test: test.o sha-256.o
+
+.PHONY: clean
+clean:
+ rm test *.o
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/README.md b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/README.md
new file mode 100644
index 0000000000..167a69d08c
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/README.md
@@ -0,0 +1,97 @@
+# sha-2 [![Build Status](https://travis-ci.org/amosnier/sha-2.svg?branch=master)](https://travis-ci.org/amosnier/sha-2)
+
+## Contents
+
+SHA-2 algorithm implementations.
+
+At the moment, only SHA-256 is implemented.
+
+## Design criteria
+
+- Easy to test, include in any project, compile and link.
+
+- ANSI C with as little specific C99 as possible (e.g. extended
+ integer types are used, but not bool).
+
+- Portable. Makes no assumptions on the target system's endianess or
+ word size.
+
+- The SHA-256 implementation is a straightforward implementation of
+ the algorithm specified on
+ [Wikipedia](https://en.wikipedia.org/wiki/SHA-2).
+
+## Notes
+
+The Makefile is as minimal as possible. No effort was put into making
+it general. Its purpose is mainly to ease testing for the developer's
+host machine. The actual implementation is however extremely easy to
+include in any project, may it use GNU make or any other build tool.
+
+## Code review
+
+This code has been reviewed at [Stack Exchange CODE
+REVIEW](https://codereview.stackexchange.com/questions/182812/self-contained-sha-256-implementation-in-c),
+and the implementation has been improved accordingly.
+
+## Testing
+
+Testing is continuously performed on Travis CI (see above).
+
+Apart from that, the implementation has been successfully tested on an x86-64 machine
+under Linux as well as on a 16-bit DSP. On the x86-64 machine, all the
+available NIST test vectors where successfully tested ([SHA-256
+examples](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/SHA256.pdf)
+and [SHA-2 Additional
+examples](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/SHA2_Additional.pdf),
+plus a few others).
+
+In particular:
+
+```
+Input Message: "abc"
+Message Digest is BA7816BF 8F01CFEA 414140DE 5DAE2223 B00361A3 96177A9C B410FF61 F20015AD
+```
+
+```
+Input Message: "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+Message Digest is 248D6A61 D20638B8 E5C02693 0C3E6039 A33CE459 64FF2167 F6ECEDD4 19DB06C1
+```
+
+```
+SHA-256 Test Data
+#1) 1 byte 0xbd
+68325720 aabd7c82 f30f554b 313d0570 c95accbb 7dc4b5aa e11204c0 8ffe732b
+#2) 4 bytes 0xc98c8e55
+7abc22c0 ae5af26c e93dbb94 433a0e0b 2e119d01 4f8e7f65 bd56c61c cccd9504
+#3) 55 bytes of zeros
+02779466 cdec1638 11d07881 5c633f21 90141308 1449002f 24aa3e80 f0b88ef7
+#4) 56 bytes of zeros
+d4817aa5 497628e7 c77e6b60 6107042b bba31308 88c5f47a 375e6179 be789fbb
+#5) 57 bytes of zeros
+65a16cb7 861335d5 ace3c607 18b5052e 44660726 da4cd13b b745381b 235a1785
+#6) 64 bytes of zeros
+f5a5fd42 d16a2030 2798ef6e d309979b 43003d23 20d9f0e8 ea9831a9 2759fb4b
+#7) 1000 bytes of zeros
+541b3e9d aa09b20b f85fa273 e5cbd3e8 0185aa4e c298e765 db87742b 70138a53
+#8) 1000 bytes of 0x41 ‘A’
+c2e68682 3489ced2 017f6059 b8b23931 8b6364f6 dcd835d0 a519105a 1eadd6e4
+#9) 1005 bytes of 0x55 ‘U’
+f4d62dde c0f3dd90 ea1380fa 16a5ff8d c4c54b21 740650f2 4afc4120 903552b0
+#10) 1000000 bytes of zeros
+d29751f2 649b32ff 572b5e0a 9f541ea6 60a50f94 ff0beedf b0b692b9 24cc8025
+#11) 0x20000000 (536870912) bytes of 0x5a ‘Z’
+15a1868c 12cc5395 1e182344 277447cd 0979536b adcc512a d24c67e9 b2d4f3dd
+#12) 0x41000000 (1090519040) bytes of zeros
+461c19a9 3bd4344f 9215f5ec 64357090 342bc66b 15a14831 7d276e31 cbc20b53
+#13) 0x6000003e (1610612798) bytes of 0x42 ‘B’
+c23ce8a7 895f4b21 ec0daf37 920ac0a2 62a22004 5a03eb2d fed48ef9 b05aabea
+```
+
+## License
+
+This repository is made available in the public domain. See [LICENSE
+FILE](LICENSE).
+
+## Reference implementation
+
+I had missed that when I made this implementation but [RFC 6234, chapter 8](https://tools.ietf.org/html/rfc6234#section-8) actually includes a reference implementation in C that is (at least in ambition) broader in scope than this one. I have however neither compiled nor tested it.
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256.c b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256.c
new file mode 100644
index 0000000000..4338531d7e
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256.c
@@ -0,0 +1,224 @@
+#include <stdint.h>
+#include <string.h>
+
+#ifdef YCF_YIELD_CODE_GENERATED
+#include "sha-2/sha-256.h"
+#else
+#include "sha-256.h"
+#endif
+
+#define CHUNK_SIZE 64
+#define TOTAL_LEN_LEN 8
+
+/*
+ * ABOUT bool: this file does not use bool in order to be as pre-C99 compatible as possible.
+ */
+
+/*
+ * Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here.
+ * When useful for clarification, portions of the pseudo-code are reproduced here too.
+ */
+
+/*
+ * Initialize array of round constants:
+ * (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):
+ */
+static const uint32_t k[] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+struct buffer_state {
+ const uint8_t * p;
+ size_t len;
+ size_t total_len;
+ int single_one_delivered; /* bool */
+ int total_len_delivered; /* bool */
+};
+
+static inline uint32_t right_rot(uint32_t value, unsigned int count)
+{
+ /*
+ * Defined behaviour in standard C for all count where 0 < count < 32,
+ * which is what we need here.
+ */
+ return value >> count | value << (32 - count);
+}
+
+static void init_buf_state(struct buffer_state * state, const void * input, size_t len)
+{
+ state->p = input;
+ state->len = len;
+ state->total_len = len;
+ state->single_one_delivered = 0;
+ state->total_len_delivered = 0;
+}
+
+/* Return value: bool */
+static int calc_chunk(uint8_t chunk[CHUNK_SIZE], struct buffer_state * state)
+{
+ size_t space_in_chunk;
+
+ if (state->total_len_delivered) {
+ return 0;
+ }
+
+ if (state->len >= CHUNK_SIZE) {
+ memcpy(chunk, state->p, CHUNK_SIZE);
+ state->p += CHUNK_SIZE;
+ state->len -= CHUNK_SIZE;
+ return 1;
+ }
+
+ memcpy(chunk, state->p, state->len);
+ chunk += state->len;
+ space_in_chunk = CHUNK_SIZE - state->len;
+ state->p += state->len;
+ state->len = 0;
+
+ /* If we are here, space_in_chunk is one at minimum. */
+ if (!state->single_one_delivered) {
+ *chunk++ = 0x80;
+ space_in_chunk -= 1;
+ state->single_one_delivered = 1;
+ }
+
+ /*
+ * Now:
+ * - either there is enough space left for the total length, and we can conclude,
+ * - or there is too little space left, and we have to pad the rest of this chunk with zeroes.
+ * In the latter case, we will conclude at the next invokation of this function.
+ */
+ if (space_in_chunk >= TOTAL_LEN_LEN) {
+ const size_t left = space_in_chunk - TOTAL_LEN_LEN;
+ size_t len = state->total_len;
+ int i;
+ memset(chunk, 0x00, left);
+ chunk += left;
+
+ /* Storing of len * 8 as a big endian 64-bit without overflow. */
+ chunk[7] = (uint8_t) (len << 3);
+ len >>= 5;
+ for (i = 6; i >= 0; i--) {
+ chunk[i] = (uint8_t) len;
+ len >>= 8;
+ }
+ state->total_len_delivered = 1;
+ } else {
+ memset(chunk, 0x00, space_in_chunk);
+ }
+
+ return 1;
+}
+
+#define YCF_STACK_ALLOC(X) malloc(X)
+
+
+/*
+ * Limitations:
+ * - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem
+ * for large data sizes.
+ * - SHA algorithms theoretically operate on bit strings. However, this implementation has no support
+ * for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes.
+ * In particular, the len parameter is a number of bytes.
+ */
+void calc_sha_256(uint8_t hash[32], const void * input, size_t len)
+{
+ /*
+ * Note 1: All integers (expect indexes) are 32-bit unsigned integers and addition is calculated modulo 2^32.
+ * Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 = i = 63
+ * Note 3: The compression function uses 8 working variables, a through h
+ * Note 4: Big-endian convention is used when expressing the constants in this pseudocode,
+ * and when parsing message block data from bytes to words, for example,
+ * the first word of the input message "abc" after padding is 0x61626380
+ */
+
+ /*
+ * Initialize hash values:
+ * (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
+ */
+ uint32_t h[8];
+ int i;
+ int j;
+
+ /* 512-bit chunks is what we will operate on. */
+ uint8_t* chunk = YCF_STACK_ALLOC(64);
+ struct buffer_state state;
+ h[0] = 0x6a09e667;
+ h[1] = 0xbb67ae85;
+ h[2] = 0x3c6ef372;
+ h[3] = 0xa54ff53a;
+ h[4] = 0x510e527f;
+ h[5] = 0x9b05688c;
+ h[6] = 0x1f83d9ab;
+ h[7] = 0x5be0cd19;
+ init_buf_state(&state, input, len);
+
+ while (calc_chunk(chunk, &state)) {
+ uint32_t ah[8];
+
+ /*
+ * create a 64-entry message schedule array w[0..63] of 32-bit words
+ * (The initial values in w[0..63] don't matter, so many implementations zero them here)
+ * copy chunk into first 16 words w[0..15] of the message schedule array
+ */
+ uint32_t w[64];
+ const uint8_t *p = chunk;
+
+ memset(w, 0x00, sizeof w);
+ for (i = 0; i < 16; i++) {
+ w[i] = (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 |
+ (uint32_t) p[2] << 8 | (uint32_t) p[3];
+ p += 4;
+ }
+
+ /* Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: */
+ for (i = 16; i < 64; i++) {
+ const uint32_t s0 = right_rot(w[i - 15], 7) ^ right_rot(w[i - 15], 18) ^ (w[i - 15] >> 3);
+ const uint32_t s1 = right_rot(w[i - 2], 17) ^ right_rot(w[i - 2], 19) ^ (w[i - 2] >> 10);
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1;
+ }
+
+ /* Initialize working variables to current hash value: */
+ for (i = 0; i < 8; i++)
+ ah[i] = h[i];
+
+ /* Compression function main loop: */
+ for (i = 0; i < 64; i++) {
+ const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
+ const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
+ const uint32_t temp1 = ah[7] + s1 + ch + k[i] + w[i];
+ const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
+ const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
+ const uint32_t temp2 = s0 + maj;
+
+ ah[7] = ah[6];
+ ah[6] = ah[5];
+ ah[5] = ah[4];
+ ah[4] = ah[3] + temp1;
+ ah[3] = ah[2];
+ ah[2] = ah[1];
+ ah[1] = ah[0];
+ ah[0] = temp1 + temp2;
+ }
+
+ /* Add the compressed chunk to the current hash value: */
+ for (i = 0; i < 8; i++)
+ h[i] += ah[i];
+ }
+
+ /* Produce the final hash value (big-endian): */
+ for (i = 0, j = 0; i < 8; i++)
+ {
+ hash[j++] = (uint8_t) (h[i] >> 24);
+ hash[j++] = (uint8_t) (h[i] >> 16);
+ hash[j++] = (uint8_t) (h[i] >> 8);
+ hash[j++] = (uint8_t) h[i];
+ }
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256.h b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256.h
new file mode 100644
index 0000000000..2cce60bb7b
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256.h
@@ -0,0 +1,4 @@
+#include <stdint.h>
+#include <stdlib.h>
+
+void calc_sha_256(uint8_t hash[], const void *input, size_t len);
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256_orginal.c b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256_orginal.c
new file mode 100644
index 0000000000..53d6ff2e2a
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/sha-256_orginal.c
@@ -0,0 +1,210 @@
+#include <stdint.h>
+#include <string.h>
+
+#include "sha-256.h"
+
+#define CHUNK_SIZE 64
+#define TOTAL_LEN_LEN 8
+
+/*
+ * ABOUT bool: this file does not use bool in order to be as pre-C99 compatible as possible.
+ */
+
+/*
+ * Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here.
+ * When useful for clarification, portions of the pseudo-code are reproduced here too.
+ */
+
+/*
+ * Initialize array of round constants:
+ * (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):
+ */
+static const uint32_t k[] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+struct buffer_state {
+ const uint8_t * p;
+ size_t len;
+ size_t total_len;
+ int single_one_delivered; /* bool */
+ int total_len_delivered; /* bool */
+};
+
+static inline uint32_t right_rot(uint32_t value, unsigned int count)
+{
+ /*
+ * Defined behaviour in standard C for all count where 0 < count < 32,
+ * which is what we need here.
+ */
+ return value >> count | value << (32 - count);
+}
+
+static void init_buf_state(struct buffer_state * state, const void * input, size_t len)
+{
+ state->p = input;
+ state->len = len;
+ state->total_len = len;
+ state->single_one_delivered = 0;
+ state->total_len_delivered = 0;
+}
+
+/* Return value: bool */
+static int calc_chunk(uint8_t chunk[CHUNK_SIZE], struct buffer_state * state)
+{
+ size_t space_in_chunk;
+
+ if (state->total_len_delivered) {
+ return 0;
+ }
+
+ if (state->len >= CHUNK_SIZE) {
+ memcpy(chunk, state->p, CHUNK_SIZE);
+ state->p += CHUNK_SIZE;
+ state->len -= CHUNK_SIZE;
+ return 1;
+ }
+
+ memcpy(chunk, state->p, state->len);
+ chunk += state->len;
+ space_in_chunk = CHUNK_SIZE - state->len;
+ state->p += state->len;
+ state->len = 0;
+
+ /* If we are here, space_in_chunk is one at minimum. */
+ if (!state->single_one_delivered) {
+ *chunk++ = 0x80;
+ space_in_chunk -= 1;
+ state->single_one_delivered = 1;
+ }
+
+ /*
+ * Now:
+ * - either there is enough space left for the total length, and we can conclude,
+ * - or there is too little space left, and we have to pad the rest of this chunk with zeroes.
+ * In the latter case, we will conclude at the next invokation of this function.
+ */
+ if (space_in_chunk >= TOTAL_LEN_LEN) {
+ const size_t left = space_in_chunk - TOTAL_LEN_LEN;
+ size_t len = state->total_len;
+ int i;
+ memset(chunk, 0x00, left);
+ chunk += left;
+
+ /* Storing of len * 8 as a big endian 64-bit without overflow. */
+ chunk[7] = (uint8_t) (len << 3);
+ len >>= 5;
+ for (i = 6; i >= 0; i--) {
+ chunk[i] = (uint8_t) len;
+ len >>= 8;
+ }
+ state->total_len_delivered = 1;
+ } else {
+ memset(chunk, 0x00, space_in_chunk);
+ }
+
+ return 1;
+}
+
+/*
+ * Limitations:
+ * - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem
+ * for large data sizes.
+ * - SHA algorithms theoretically operate on bit strings. However, this implementation has no support
+ * for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes.
+ * In particular, the len parameter is a number of bytes.
+ */
+void calc_sha_256(uint8_t hash[32], const void * input, size_t len)
+{
+ /*
+ * Note 1: All integers (expect indexes) are 32-bit unsigned integers and addition is calculated modulo 2^32.
+ * Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 = i = 63
+ * Note 3: The compression function uses 8 working variables, a through h
+ * Note 4: Big-endian convention is used when expressing the constants in this pseudocode,
+ * and when parsing message block data from bytes to words, for example,
+ * the first word of the input message "abc" after padding is 0x61626380
+ */
+
+ /*
+ * Initialize hash values:
+ * (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
+ */
+ uint32_t h[] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
+ int i, j;
+
+ /* 512-bit chunks is what we will operate on. */
+ uint8_t chunk[64];
+
+ struct buffer_state state;
+
+ init_buf_state(&state, input, len);
+
+ while (calc_chunk(chunk, &state)) {
+ uint32_t ah[8];
+
+ /*
+ * create a 64-entry message schedule array w[0..63] of 32-bit words
+ * (The initial values in w[0..63] don't matter, so many implementations zero them here)
+ * copy chunk into first 16 words w[0..15] of the message schedule array
+ */
+ uint32_t w[64];
+ const uint8_t *p = chunk;
+
+ memset(w, 0x00, sizeof w);
+ for (i = 0; i < 16; i++) {
+ w[i] = (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 |
+ (uint32_t) p[2] << 8 | (uint32_t) p[3];
+ p += 4;
+ }
+
+ /* Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: */
+ for (i = 16; i < 64; i++) {
+ const uint32_t s0 = right_rot(w[i - 15], 7) ^ right_rot(w[i - 15], 18) ^ (w[i - 15] >> 3);
+ const uint32_t s1 = right_rot(w[i - 2], 17) ^ right_rot(w[i - 2], 19) ^ (w[i - 2] >> 10);
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1;
+ }
+
+ /* Initialize working variables to current hash value: */
+ for (i = 0; i < 8; i++)
+ ah[i] = h[i];
+
+ /* Compression function main loop: */
+ for (i = 0; i < 64; i++) {
+ const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
+ const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
+ const uint32_t temp1 = ah[7] + s1 + ch + k[i] + w[i];
+ const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
+ const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
+ const uint32_t temp2 = s0 + maj;
+
+ ah[7] = ah[6];
+ ah[6] = ah[5];
+ ah[5] = ah[4];
+ ah[4] = ah[3] + temp1;
+ ah[3] = ah[2];
+ ah[2] = ah[1];
+ ah[1] = ah[0];
+ ah[0] = temp1 + temp2;
+ }
+
+ /* Add the compressed chunk to the current hash value: */
+ for (i = 0; i < 8; i++)
+ h[i] += ah[i];
+ }
+
+ /* Produce the final hash value (big-endian): */
+ for (i = 0, j = 0; i < 8; i++)
+ {
+ hash[j++] = (uint8_t) (h[i] >> 24);
+ hash[j++] = (uint8_t) (h[i] >> 16);
+ hash[j++] = (uint8_t) (h[i] >> 8);
+ hash[j++] = (uint8_t) h[i];
+ }
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/test.c b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/test.c
new file mode 100644
index 0000000000..5b5031c374
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/test.c
@@ -0,0 +1,238 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "sha-256.h"
+
+struct string_vector {
+ const char *input;
+ const char *output;
+};
+
+static const struct string_vector STRING_VECTORS[] = {
+ {
+ "",
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "abc",
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
+ },
+ {
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
+ "a8ae6e6ee929abea3afcfc5258c8ccd6f85273e0d4626d26c7279f3250f77c8e"
+ },
+ {
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde",
+ "057ee79ece0b9a849552ab8d3c335fe9a5f1c46ef5f1d9b190c295728628299c"
+ },
+ {
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0",
+ "2a6ad82f3620d3ebe9d678c812ae12312699d673240d5be8fac0910a70000d93"
+ },
+ {
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
+ },
+ {
+ "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno"
+ "ijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1"
+ }
+};
+
+#define LARGE_MESSAGES 1
+
+static uint8_t data1[] = { 0xbd };
+static uint8_t data2[] = { 0xc9, 0x8c, 0x8e, 0x55 };
+static uint8_t data7[1000];
+static uint8_t data8[1000];
+static uint8_t data9[1005];
+#if LARGE_MESSAGES
+#define SIZEOF_DATA11 536870912
+#define SIZEOF_DATA12 1090519040
+#define SIZEOF_DATA13 1610612798
+static uint8_t * data11;
+static uint8_t * data12;
+static uint8_t * data13;
+#endif
+
+struct vector {
+ const uint8_t *input;
+ size_t input_len;
+ const char *output;
+};
+
+static struct vector vectors[] = {
+ {
+ data1,
+ sizeof data1,
+ "68325720aabd7c82f30f554b313d0570c95accbb7dc4b5aae11204c08ffe732b"
+ },
+ {
+ data2,
+ sizeof data2,
+ "7abc22c0ae5af26ce93dbb94433a0e0b2e119d014f8e7f65bd56c61ccccd9504"
+ },
+ {
+ data7,
+ 55,
+ "02779466cdec163811d078815c633f21901413081449002f24aa3e80f0b88ef7"
+ },
+ {
+ data7,
+ 56,
+ "d4817aa5497628e7c77e6b606107042bbba3130888c5f47a375e6179be789fbb"
+ },
+ {
+ data7,
+ 57,
+ "65a16cb7861335d5ace3c60718b5052e44660726da4cd13bb745381b235a1785"
+ },
+ {
+ data7,
+ 64,
+ "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"
+ },
+ {
+ data7,
+ sizeof data7,
+ "541b3e9daa09b20bf85fa273e5cbd3e80185aa4ec298e765db87742b70138a53"
+ },
+ {
+ data8,
+ sizeof data8,
+ "c2e686823489ced2017f6059b8b239318b6364f6dcd835d0a519105a1eadd6e4"
+ },
+ {
+ data9,
+ sizeof data9,
+ "f4d62ddec0f3dd90ea1380fa16a5ff8dc4c54b21740650f24afc4120903552b0"
+ }
+#if LARGE_MESSAGES
+ ,
+ {
+ NULL,
+ 1000000,
+ "d29751f2649b32ff572b5e0a9f541ea660a50f94ff0beedfb0b692b924cc8025"
+ },
+ {
+ NULL,
+ SIZEOF_DATA11,
+ "15a1868c12cc53951e182344277447cd0979536badcc512ad24c67e9b2d4f3dd"
+ },
+ {
+ NULL,
+ SIZEOF_DATA12,
+ "461c19a93bd4344f9215f5ec64357090342bc66b15a148317d276e31cbc20b53"
+ },
+ {
+ NULL,
+ SIZEOF_DATA13,
+ "c23ce8a7895f4b21ec0daf37920ac0a262a220045a03eb2dfed48ef9b05aabea"
+ }
+#endif
+};
+
+static void construct_binary_messages(void)
+{
+ memset(data7, 0x00, sizeof data7);
+ memset(data8, 0x41, sizeof data8);
+ memset(data9, 0x55, sizeof data9);
+#if LARGE_MESSAGES
+ /*
+ * Heap allocation as a workaround for some linkers not liking
+ * large BSS segments.
+ */
+ data11 = malloc(SIZEOF_DATA11);
+ data12 = malloc(SIZEOF_DATA12);
+ data13 = malloc(SIZEOF_DATA13);
+ memset(data11, 0x5a, SIZEOF_DATA11);
+ memset(data12, 0x00, SIZEOF_DATA12);
+ memset(data13, 0x42, SIZEOF_DATA13);
+ vectors[9].input = data12;
+ vectors[10].input = data11;
+ vectors[11].input = data12;
+ vectors[12].input = data13;
+#endif
+}
+
+static void destruct_binary_messages(void)
+{
+#if LARGE_MESSAGES
+ free(data11);
+ free(data12);
+ free(data13);
+#endif
+}
+
+static void hash_to_string(char string[65], const uint8_t hash[32])
+{
+ size_t i;
+ for (i = 0; i < 32; i++) {
+ string += sprintf(string, "%02x", hash[i]);
+ }
+}
+
+static int string_test(const char input[], const char output[])
+{
+ uint8_t hash[32];
+ char hash_string[65];
+ calc_sha_256(hash, input, strlen(input));
+ hash_to_string(hash_string, hash);
+ printf("input: %s\n", input);
+ printf("hash : %s\n", hash_string);
+ if (strcmp(output, hash_string)) {
+ printf("FAILURE!\n\n");
+ return 1;
+ } else {
+ printf("SUCCESS!\n\n");
+ return 0;
+ }
+}
+
+/*
+ * Limitation:
+ * - The variable input_len will be truncated to its LONG_BIT least
+ * significant bits in the print output. This will never be a problem
+ * for values that in practice are less than 2^32 - 1. Rationale: ANSI
+ * C-compatibility and keeping it simple.
+ */
+static int test(const uint8_t * input, size_t input_len, const char output[])
+{
+ uint8_t hash[32];
+ char hash_string[65];
+ calc_sha_256(hash, input, input_len);
+ hash_to_string(hash_string, hash);
+ printf("input starts with 0x%02x, length %lu\n", *input, (unsigned long) input_len);
+ printf("hash : %s\n", hash_string);
+ if (strcmp(output, hash_string)) {
+ printf("FAILURE!\n\n");
+ return 1;
+ } else {
+ printf("SUCCESS!\n\n");
+ return 0;
+ }
+}
+
+int main(void)
+{
+ size_t i;
+ for (i = 0; i < (sizeof STRING_VECTORS / sizeof (struct string_vector)); i++) {
+ const struct string_vector *vector = &STRING_VECTORS[i];
+ if (string_test(vector->input, vector->output))
+ return 1;
+ }
+ construct_binary_messages();
+ for (i = 0; i < (sizeof vectors / sizeof (struct vector)); i++) {
+ const struct vector *vector = &vectors[i];
+ if (test(vector->input, vector->input_len, vector->output))
+ {
+ destruct_binary_messages();
+ return 1;
+ }
+ }
+ destruct_binary_messages();
+ return 0;
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha256_nif.c b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha256_nif.c
new file mode 100644
index 0000000000..6ad22c1dcd
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha256_nif.c
@@ -0,0 +1,173 @@
+
+#include "erl_nif.h"
+
+#include "sha-2/sha-256.h"
+struct buffer_state;
+#include "gen_yielding_sha_256.h"
+
+
+
+
+ERL_NIF_TERM
+mk_atom(ErlNifEnv* env, const char* atom)
+{
+ ERL_NIF_TERM ret;
+
+ if(!enif_make_existing_atom(env, atom, &ret, ERL_NIF_LATIN1))
+ {
+ return enif_make_atom(env, atom);
+ }
+
+ return ret;
+}
+
+ERL_NIF_TERM
+mk_error(ErlNifEnv* env, const char* mesg)
+{
+ return enif_make_tuple2(env, mk_atom(env, "error"), mk_atom(env, mesg));
+}
+
+
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return enif_alloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ enif_free(data);
+}
+
+typedef struct {
+ void* continuation;
+ ErlNifEnv* work_env;
+ ErlNifBinary out_bin;
+} sha256continuation;
+
+/* #define DEBUG */
+#ifdef DEBUG
+# define DEBUG_PRINT(x) printf x
+long nr_of_yields = 0;
+#else
+# define DEBUG_PRINT(x) do {} while (0)
+#endif
+
+void sha256continuation_ErlNifResourceDtor(ErlNifEnv* caller_env, void* obj){
+ sha256continuation* continuation = obj;
+ if(continuation->continuation != NULL){
+ calc_sha_256_ycf_gen_destroy(continuation->continuation);
+ enif_release_binary(&continuation->out_bin);
+ enif_free_env(continuation->work_env);
+ continuation->continuation = NULL;
+ DEBUG_PRINT(("DESTOY\n"));
+ }
+ DEBUG_PRINT(("DEALLOC\n"));
+}
+
+#define REDUCTIONS_UNTIL_YCF_YIELD() (200*300*8)
+
+static ERL_NIF_TERM
+sha256_nif_cont_after_yield(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifResourceType* res_type = (ErlNifResourceType*)enif_priv_data(env);
+ long nr_of_reductions = REDUCTIONS_UNTIL_YCF_YIELD();
+ sha256continuation* continuation;
+ enif_get_resource(env,
+ argv[0],
+ res_type,
+ (void**)&continuation);
+ calc_sha_256_ycf_gen_continue(&nr_of_reductions,
+ &continuation->continuation,
+ NULL);
+ if(YCF_IS_YIELDED(continuation->continuation)){
+#ifdef DEBUG
+ nr_of_yields++;
+#endif
+ return enif_schedule_nif(env, "sha256_nif_cont_after_yield", 0, sha256_nif_cont_after_yield, 1, argv);
+ }
+ DEBUG_PRINT(("NUMBER OF YCF_YIELD()S %ld \n", nr_of_yields));
+ enif_free_env(continuation->work_env);
+ ErlNifBinary out_bin = continuation->out_bin;
+ return enif_make_binary(env, &out_bin);
+}
+
+static ERL_NIF_TERM
+sha256_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ /* ErlNifEnv* msg_env; */
+ ErlNifResourceType* res_type = (ErlNifResourceType*)enif_priv_data(env);
+ ErlNifEnv *work_env = enif_alloc_env();
+ void* wb = NULL;
+ long nr_of_reductions;
+ ERL_NIF_TERM newargv[1];
+ if(argc != 1 || !enif_is_binary(env, argv[0]))
+ {
+ return enif_make_badarg(env);
+ }
+ /* Copy the input binary to the work environemnt so it will be kept when we are yielding */
+ ERL_NIF_TERM input_bin_term = enif_make_copy(work_env, argv[0]);
+ ErlNifBinary input_bin;
+ enif_inspect_binary(work_env, input_bin_term, &input_bin);
+ ErlNifBinary out_bin;
+ enif_alloc_binary(256/8, &out_bin);
+ nr_of_reductions = REDUCTIONS_UNTIL_YCF_YIELD();
+#ifdef DEBUG
+ nr_of_yields = 0;
+#endif
+ calc_sha_256_ycf_gen_yielding(&nr_of_reductions,
+ &wb,
+ NULL,
+ allocator,
+ freer,
+ NULL,
+ 64,
+ NULL,
+ out_bin.data,
+ input_bin.data,
+ input_bin.size);
+ if(YCF_IS_YIELDED(wb)){
+ DEBUG_PRINT(("TRAPPED FIRST CALL %ld \n", nr_of_reductions));
+ sha256continuation* continuation =
+ enif_alloc_resource(res_type,
+ sizeof(sha256continuation));
+ continuation->work_env = work_env;
+ continuation->continuation = wb;
+ continuation->out_bin = out_bin;
+ newargv[0] = enif_make_resource(env, continuation);
+ enif_release_resource(continuation);
+ return enif_schedule_nif(env, "sha256_nif_cont_after_yield", 0, sha256_nif_cont_after_yield, 1, newargv);
+ }
+ enif_free_env(work_env);
+ return enif_make_binary(env, &out_bin);
+}
+
+static ErlNifFunc nif_funcs[] = {
+ {"sha256", 1, sha256_nif}
+};
+
+static int
+nifload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ *priv_data = enif_open_resource_type(env,
+ NULL,
+ "sha256_nif",
+ sha256continuation_ErlNifResourceDtor,
+ ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
+ NULL);
+ return 0;
+}
+
+static int
+nifupgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
+{
+ *priv_data = enif_open_resource_type(env,
+ NULL,
+ "sha256_nif",
+ sha256continuation_ErlNifResourceDtor,
+ ERL_NIF_RT_TAKEOVER,
+ NULL);
+ return 0;
+}
+
+ERL_NIF_INIT(sha256_nif, nif_funcs, nifload, NULL, nifupgrade, NULL);
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/rebar.config b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/rebar.config
new file mode 100644
index 0000000000..d20b1db59d
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/rebar.config
@@ -0,0 +1,12 @@
+{erl_opts, [debug_info]}.
+{deps, []}.
+
+{erl_opts, [debug_info]}.
+{deps, []}.
+
+{pre_hooks,
+ [{"(linux|darwin|solaris)", compile, "make -C c_src"},
+ {"(freebsd)", compile, "gmake -C c_src"}]}.
+{post_hooks,
+ [{"(linux|darwin|solaris)", clean, "make -C c_src clean"},
+ {"(freebsd)", clean, "gmake -C c_src clean"}]}. \ No newline at end of file
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/src/sha256_nif.app.src b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/src/sha256_nif.app.src
new file mode 100644
index 0000000000..603a5092bb
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/src/sha256_nif.app.src
@@ -0,0 +1,14 @@
+{application, sha256_nif,
+ [{description, "An OTP library"},
+ {vsn, "0.1.0"},
+ {registered, []},
+ {applications,
+ [kernel,
+ stdlib
+ ]},
+ {env,[]},
+ {modules, []},
+
+ {licenses, ["Apache 2.0"]},
+ {links, []}
+ ]}.
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/src/sha256_nif.erl b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/src/sha256_nif.erl
new file mode 100644
index 0000000000..d613f2c5aa
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/src/sha256_nif.erl
@@ -0,0 +1,27 @@
+-module(sha256_nif).
+
+-export([sha256/1]).
+-on_load(init/0).
+
+-define(APPNAME, sha256_nif).
+-define(LIBNAME, sha256_erlang_nif).
+
+sha256(_) ->
+ not_loaded(?LINE).
+
+init() ->
+ SoName = case code:priv_dir(?APPNAME) of
+ {error, bad_name} ->
+ case filelib:is_dir(filename:join(["..", priv])) of
+ true ->
+ filename:join(["..", priv, ?LIBNAME]);
+ _ ->
+ filename:join([priv, ?LIBNAME])
+ end;
+ Dir ->
+ filename:join(Dir, ?LIBNAME)
+ end,
+ erlang:load_nif(SoName, 0).
+
+not_loaded(Line) ->
+ exit({not_loaded, [{module, ?MODULE}, {line, Line}]}).
diff --git a/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/test/basic_SUITE.erl b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/test/basic_SUITE.erl
new file mode 100644
index 0000000000..7d0a42ce84
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/test/basic_SUITE.erl
@@ -0,0 +1,32 @@
+-module(basic_SUITE).
+-include_lib("common_test/include/ct.hrl").
+-export([all/0]).
+-export([test1/1, test2/1]).
+
+all() ->
+ [test1, test2].
+
+bin_to_hex_string(Bin)->
+ lists:flatten(io_lib:format("~s", [lists:flatten([io_lib:format("~2.16.0B",[X]) || <<X:8>> <= Bin ])])).
+
+foreach_up_to_helper(UpTo, UpTo, Fun) ->
+ ok;
+foreach_up_to_helper(UpTo, Current, Fun) ->
+ Fun(Current),
+ foreach_up_to_helper(UpTo, Current + 1, Fun).
+
+foreach_up_to(UpTo, Fun) ->
+ foreach_up_to_helper(UpTo, 1, Fun).
+
+test1(_Config) ->
+ foreach_up_to(15, fun(V) ->
+ In = erlang:list_to_binary(lists:flatten(lists:duplicate(1024 bsl V, "h"))),
+ {CryptoHashTime, Expect} = timer:tc(crypto, hash, [sha256,In]),
+ {MyTime, Expect} = timer:tc(sha256_nif, sha256, [In]),
+ io:format("Size: ~p My Time: ~p Crypto Hash Time: ~p", [1024 bsl V, MyTime/1000000, CryptoHashTime/1000000])
+ end),
+ ok.
+
+test2(_Config) ->
+ Expect = crypto:hash(sha256, "hej"),
+ Expect = sha256_nif:sha256(<<"hej">>).
diff --git a/erts/lib_src/yielding_c_fun/test/examples/simple_fun_call.c b/erts/lib_src/yielding_c_fun/test/examples/simple_fun_call.c
new file mode 100644
index 0000000000..0ac86d0b7d
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/simple_fun_call.c
@@ -0,0 +1,92 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+void sub_fun(char* x){
+ *x = *x + 1; /* x == 3*/
+ *x = *x + 1; /* x == 4*/
+}
+
+int sub_fun2(int x, int y){
+ return x+y;
+}
+
+int fun(char x){
+ int y;
+ (void)y;
+ x = x + 1; /* x == 2*/
+ sub_fun(((&x)));
+ sub_fun2(10, 20);
+ y = sub_fun2(10, 20);
+ YCF_YIELD();
+ x = x + 1; /* x == 5*/
+ return x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 5){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/simple_fun_call.c.out b/erts/lib_src/yielding_c_fun/test/examples/simple_fun_call.c.out
new file mode 100644
index 0000000000..20db8bc359
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/simple_fun_call.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 5
diff --git a/erts/lib_src/yielding_c_fun/test/examples/simple_yield.c b/erts/lib_src/yielding_c_fun/test/examples/simple_yield.c
new file mode 100644
index 0000000000..49316af598
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/simple_yield.c
@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+int empty_fun(){
+ return 1;
+}
+
+int fun(char x){
+ x = x + 1; /* x == 2*/
+ YCF_YIELD();
+ x = x + 1; /* x == 3*/
+ {
+ int hej = !!empty_fun();
+ (void)hej;
+ }
+ return x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 1;
+#endif
+ int ret = 0;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 3){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/simple_yield.c.out b/erts/lib_src/yielding_c_fun/test/examples/simple_yield.c.out
new file mode 100644
index 0000000000..96f574d048
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/simple_yield.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 3
diff --git a/erts/lib_src/yielding_c_fun/test/examples/simple_yield_no_reds.c b/erts/lib_src/yielding_c_fun/test/examples/simple_yield_no_reds.c
new file mode 100644
index 0000000000..5c3778e203
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/simple_yield_no_reds.c
@@ -0,0 +1,61 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD_NO_REDS()
+
+int empty_fun(){
+ return 1;
+}
+
+int fun(char x){
+ x = x + 1; /* x == 2*/
+ YCF_YIELD_NO_REDS();
+ x = x + 1; /* x == 3*/
+ {
+ int hej = !!empty_fun();
+ (void)hej;
+ return x;
+ }
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 777;
+#endif
+ int ret = 0;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ if(nr_of_reductions != 777){
+ printf("SHOULD NOT HAPPEN\n");
+ }
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 3){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/simple_yield_no_reds.c.out b/erts/lib_src/yielding_c_fun/test/examples/simple_yield_no_reds.c.out
new file mode 100644
index 0000000000..96f574d048
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/simple_yield_no_reds.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 3
diff --git a/erts/lib_src/yielding_c_fun/test/examples/simple_yielding_fun_call.c b/erts/lib_src/yielding_c_fun/test/examples/simple_yielding_fun_call.c
new file mode 100644
index 0000000000..99fcabaf99
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/simple_yielding_fun_call.c
@@ -0,0 +1,111 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+void sub_fun_1(char* res, char x, char y){
+ printf("sub_fun_1_1\n");
+ YCF_YIELD();
+ printf("sub_fun_1_2\n");
+ *res = x + y;
+ YCF_YIELD();
+ printf("sub_fun_1_3\n");
+ YCF_YIELD();
+ printf("sub_fun_1_4\n");
+}
+
+void sub_fun_2(){
+ printf("sub_fun_2_1\n");
+ YCF_YIELD();
+ printf("sub_fun_2_2\n");
+}
+
+char sub_fun_3(char x, char y){
+ char res;
+ printf("sub_fun_2_1\n");
+ YCF_YIELD();
+ res = x + y;
+ YCF_YIELD();
+ printf("sub_fun_2_2\n");
+ return res;
+}
+
+int fun(char x){
+ char y;
+ char z = 10
+ YCF_YIELD();
+ sub_fun_1(&y,x,1); /* y == 2 */
+ YCF_YIELD();
+ sub_fun_2();
+ YCF_YIELD();
+ y = sub_fun_3(y, 1); /* y == 3 */
+ YCF_YIELD();
+ return y + z;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 13){
+ return 1;
+ }else{
+ return 0;
+ }
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/simple_yielding_fun_call.c.out b/erts/lib_src/yielding_c_fun/test/examples/simple_yielding_fun_call.c.out
new file mode 100644
index 0000000000..cc807db6c3
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/simple_yielding_fun_call.c.out
@@ -0,0 +1,18 @@
+sub_fun_1_1
+TRAPPED
+sub_fun_1_2
+TRAPPED
+sub_fun_1_3
+TRAPPED
+sub_fun_1_4
+TRAPPED
+sub_fun_2_1
+TRAPPED
+sub_fun_2_2
+TRAPPED
+sub_fun_2_1
+TRAPPED
+TRAPPED
+sub_fun_2_2
+TRAPPED
+RETURNED 13
diff --git a/erts/lib_src/yielding_c_fun/test/examples/stack_array.c b/erts/lib_src/yielding_c_fun/test/examples/stack_array.c
new file mode 100644
index 0000000000..9f251749d6
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/stack_array.c
@@ -0,0 +1,135 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#define YCF_YIELD()
+
+
+void fun(int x3content[]){
+ int x1[2];
+ int x2[2][2];
+ int *x3[2];
+ int z = 42;
+ (void)z;
+ x1[0] = 1;
+ x1[1] = 2;
+ x2[0][0] = 3;
+ x2[0][1] = 4;
+ x2[1][0] = 5;
+ x2[1][1] = 6;
+ x3[0] = x3content;
+ x3[1] = x3content;
+ x3[0][0] = 7;
+ x3[0][1] = 8;
+ x3[1][0] = 9;
+ x3[1][1] = 10;
+ printf("%d %d %d %d %d %d %d %d %d %d\n",
+ x1[0],
+ x1[1],
+ x2[0][0],
+ x2[0][1],
+ x2[1][0],
+ x2[1][1],
+ x3[0][0],
+ x3[0][1],
+ x3[1][0],
+ x3[1][1]);
+ YCF_YIELD();
+ printf("%d %d %d %d %d %d %d %d %d %d\n",
+ x1[0],
+ x1[1],
+ x2[0][0],
+ x2[0][1],
+ x2[1][0],
+ x2[1][1],
+ x3[0][0],
+ x3[0][1],
+ x3[1][0],
+ x3[1][1]);
+ return;
+}
+
+void fun_reset(int x3content[]){
+ int x1[2];
+ int x2[2][2];
+ int *x3[2];
+ (void)x2;
+ (void)x1;
+ x1[0] = 42;
+ x1[1] = 42;
+ x2[0][0] = 42;
+ x2[0][1] = 42;
+ x2[1][0] = 42;
+ x2[1][1] = 42;
+ x3[0] = x3content;
+ x3[1] = x3content;
+ x3[0][0] = 42;
+ x3[0][1] = 42;
+ x3[1][0] = 42;
+ x3[1][1] = 42;
+ return;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int x3content[2];
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL, x3content);
+ fun_reset(x3content);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(x3content);
+#endif
+ printf("RETURNED\n");
+ return 0;
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/stack_array.c.out b/erts/lib_src/yielding_c_fun/test/examples/stack_array.c.out
new file mode 100644
index 0000000000..c5cd129186
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/stack_array.c.out
@@ -0,0 +1,4 @@
+1 2 3 4 5 6 9 10 9 10
+TRAPPED
+1 2 3 4 5 6 42 42 42 42
+RETURNED
diff --git a/erts/lib_src/yielding_c_fun/test/examples/static_inline_function.c b/erts/lib_src/yielding_c_fun/test/examples/static_inline_function.c
new file mode 100644
index 0000000000..146364d936
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/static_inline_function.c
@@ -0,0 +1,71 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#if __STDC_VERSION__ >= 199901L
+
+#else
+#define inline
+#endif
+
+#define YCF_YIELD()
+
+static
+inline
+int fun(char x);
+
+
+static
+inline
+int fun(char x){
+ x = x + 1; /* x == 2*/
+ {
+ int y;
+ y = 1;
+ {
+ int z = 1;
+ YCF_YIELD();
+ x = x + y + z; /* x == 4*/
+ return x;
+ }
+ }
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 1;
+#endif
+ int ret = 0;
+ (void)fun;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 4){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/static_inline_function.c.out b/erts/lib_src/yielding_c_fun/test/examples/static_inline_function.c.out
new file mode 100644
index 0000000000..cf0b7ff2c6
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/static_inline_function.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 4
diff --git a/lib/erl_interface/src/legacy/erl_internal.h b/erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_code.c
index 25cf3e4f42..fd1240c180 100644
--- a/lib/erl_interface/src/legacy/erl_internal.h
+++ b/erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_code.c
@@ -1,8 +1,8 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -14,35 +14,53 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
+ *
* %CopyrightEnd%
*/
-#ifndef _ERL_INTERNAL_H
-#define _ERL_INTERNAL_H
-/*
- * Function: Some useful stuff not to be exported to users.
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
*/
-#define HEAD(ep) ep->uval.lval.head
-#define TAIL(ep) ep->uval.lval.tail
-#define ERL_NO_REF(x) (ERL_COUNT(x) == 0)
-
-#ifdef DEBUG
-#define ASSERT(e) \
- if (e) { \
- ; \
- } else { \
- erl_assert_error(#e, __FILE__, __LINE__); \
- }
-extern void erl_assert_error(char* expr, char* file, int line)
- __attribute__ ((__noreturn__));
+#include <stdio.h>
+#include <stdlib.h>
-#else
+#define YCF_YIELD()
-#define ASSERT(e)
+int A(int depth);
+int B(int depth);
-#endif
+int A(int depth){
+ int b;
+ YCF_YIELD();
+ depth++;
+ printf("A ");
+ YCF_YIELD();
+ if(depth == 100){
+ return 1;
+ } else {
+ b = B(depth);
+ }
+ YCF_YIELD();
+ return b + 1;
+}
-#endif /* _ERL_INTERNAL_H */
+int B(int depth){
+ int a;
+ YCF_YIELD();
+ depth++;
+ printf("B ");
+ YCF_YIELD();
+ if(depth == 100){
+ YCF_YIELD();
+ return 1;
+ } else {
+ a = A(depth);
+ }
+ YCF_YIELD();
+ return a + 1;
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_main.c b/erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_main.c
new file mode 100644
index 0000000000..b8d377f724
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_main.c
@@ -0,0 +1,71 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#ifdef YCF_YIELD_CODE_GENERATED
+#include "tmp_dir/tmp.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int A(int depth);
+int B(int depth);
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = A_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,0);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+#else
+ ret = A(0);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != A(0)){
+ return 1;
+ }else{
+ return 0;
+ }
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_main.c.out b/erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_main.c.out
new file mode 100644
index 0000000000..c41fe372ae
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/test_generated_header_file_main.c.out
@@ -0,0 +1,302 @@
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+RETURNED 100
+A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B \ No newline at end of file
diff --git a/erts/lib_src/yielding_c_fun/test/examples/test_only_output_yielding_funs.c b/erts/lib_src/yielding_c_fun/test/examples/test_only_output_yielding_funs.c
new file mode 100644
index 0000000000..04e071e94b
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/test_only_output_yielding_funs.c
@@ -0,0 +1,60 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+int empty_fun(){
+ return 1;
+}
+
+#include "tmp.inc"
+
+static int fun(char x){
+ x = x + 1; /* x == 2*/
+ YCF_YIELD();
+ x = x + 1; /* x == 3*/
+ {
+ int hej = !!empty_fun();
+ return x + hej;
+ }
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 1;
+#endif
+ int ret = 0;
+ (void)fun;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 4){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/test_only_output_yielding_funs.c.out b/erts/lib_src/yielding_c_fun/test/examples/test_only_output_yielding_funs.c.out
new file mode 100644
index 0000000000..cf0b7ff2c6
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/test_only_output_yielding_funs.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 4
diff --git a/erts/lib_src/yielding_c_fun/test/examples/test_trap.c b/erts/lib_src/yielding_c_fun/test/examples/test_trap.c
new file mode 100644
index 0000000000..7799de618e
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/test_trap.c
@@ -0,0 +1,42 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+int fun(char x){
+ int y = 10;
+ int i = 0;
+ printf("BEFORE YCF_YIELD()\n");
+ y = y + x;/*y == 11*/
+ for (i = 0; i < 100; i++){
+ printf("ITER %d\n", i);
+ y = y + x;
+ YCF_YIELD();
+ }
+ printf("AFTER YCF_YIELD()\n");/*y == 111*/
+ y = y*3;
+ return y;/*y == 333*/
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun(1,&wb,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ return 0;
+}
+
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/thread_example.c b/erts/lib_src/yielding_c_fun/test/examples/thread_example.c
new file mode 100644
index 0000000000..875e9fe990
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/thread_example.c
@@ -0,0 +1,88 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD_NO_REDS()
+
+static int f_2(char* name, int n) {
+ for(int y = 0; y < 2; y++) {
+ printf("%s f_2: y=%d\n", name, y);
+ }
+ return n*2;
+}
+
+static void f_1(char* name, int x) {
+ YCF_YIELD_NO_REDS();
+ while (x > 0) {
+ int f_2_ret = f_2(name, x);
+ printf("%s f_1: x=%d f_2_ret=%d\n", name, x, f_2_ret);
+ x--;
+ }
+ printf("%s f_1: DONE\n", name);
+}
+
+static void* ycf_alloc(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+static void ycf_free(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] ) {
+#ifdef YCF_YIELD_CODE_GENERATED
+ long t1_nr_of_reds = 1;
+ void* t1_state = NULL;
+ long t2_nr_of_reds = 2;
+ void* t2_state = NULL;
+ long t3_nr_of_reds = 1000;
+ void* t3_state = NULL;
+ /* Start t1, t2 and t3*/
+ f_1_ycf_gen_yielding(&t1_nr_of_reds, &t1_state, NULL,
+ ycf_alloc, ycf_free, NULL, 0, NULL,
+ "t1", 2);
+ f_1_ycf_gen_yielding(&t2_nr_of_reds, &t2_state, NULL,
+ ycf_alloc, ycf_free, NULL, 0, NULL,
+ "t2", 4);
+ f_1_ycf_gen_yielding(&t3_nr_of_reds, &t3_state, NULL,
+ ycf_alloc, ycf_free, NULL, 0, NULL,
+ "t3", 2);
+ printf("THREADS STARTED\n");
+ /* Execute t1, t2 and t3*/
+ while (t1_state != NULL ||
+ t2_state != NULL ||
+ t3_state != NULL) {
+ t1_nr_of_reds = 1;
+ t2_nr_of_reds = 2;
+ if (t1_state != NULL) {
+ printf("SCHEDULING THREAD: t1\n");
+ f_1_ycf_gen_continue(&t1_nr_of_reds, &t1_state, NULL);
+ if (t1_state == NULL) {
+ printf("THREAD t1 DONE (number of reductions left = %ld)\n",
+ t1_nr_of_reds);
+ }
+ }
+ if (t2_state != NULL) {
+ printf("SCHEDULING THREAD: t2\n");
+ f_1_ycf_gen_continue(&t2_nr_of_reds, &t2_state, NULL);
+ if (t2_state == NULL) {
+ printf("THREAD t2 DONE (number of reductions left = %ld)\n",
+ t2_nr_of_reds);
+ }
+ }
+ if (t3_state != NULL) {
+ printf("SCHEDULING THREAD: t3\n");
+ f_1_ycf_gen_continue(&t3_nr_of_reds, &t3_state, NULL);
+ if (t3_state == NULL) {
+ printf("THREAD t3 DONE (number of reductions left = %ld)\n",
+ t3_nr_of_reds);
+ }
+ }
+ }
+#endif
+ (void)f_1;
+ printf("DONE\n");
+ return 0;
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/thread_example.c.out b/erts/lib_src/yielding_c_fun/test/examples/thread_example.c.out
new file mode 100644
index 0000000000..015f2204ea
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/thread_example.c.out
@@ -0,0 +1,47 @@
+THREADS STARTED
+SCHEDULING THREAD: t1
+SCHEDULING THREAD: t2
+SCHEDULING THREAD: t3
+t3 f_2: y=0
+t3 f_2: y=1
+t3 f_1: x=2 f_2_ret=4
+t3 f_2: y=0
+t3 f_2: y=1
+t3 f_1: x=1 f_2_ret=2
+t3 f_1: DONE
+THREAD t3 DONE (number of reductions left = 994)
+SCHEDULING THREAD: t1
+SCHEDULING THREAD: t2
+t2 f_2: y=0
+t2 f_2: y=1
+t2 f_1: x=4 f_2_ret=8
+SCHEDULING THREAD: t1
+t1 f_2: y=0
+SCHEDULING THREAD: t2
+t2 f_2: y=0
+SCHEDULING THREAD: t1
+t1 f_2: y=1
+t1 f_1: x=2 f_2_ret=4
+SCHEDULING THREAD: t2
+t2 f_2: y=1
+t2 f_1: x=3 f_2_ret=6
+SCHEDULING THREAD: t1
+SCHEDULING THREAD: t2
+t2 f_2: y=0
+t2 f_2: y=1
+t2 f_1: x=2 f_2_ret=4
+SCHEDULING THREAD: t1
+t1 f_2: y=0
+SCHEDULING THREAD: t2
+t2 f_2: y=0
+SCHEDULING THREAD: t1
+t1 f_2: y=1
+t1 f_1: x=1 f_2_ret=2
+t1 f_1: DONE
+THREAD t1 DONE (number of reductions left = 1)
+SCHEDULING THREAD: t2
+t2 f_2: y=1
+t2 f_1: x=1 f_2_ret=2
+t2 f_1: DONE
+THREAD t2 DONE (number of reductions left = 2)
+DONE
diff --git a/erts/lib_src/yielding_c_fun/test/examples/void_param.c b/erts/lib_src/yielding_c_fun/test/examples/void_param.c
new file mode 100644
index 0000000000..f5e089b27a
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/void_param.c
@@ -0,0 +1,79 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+int fun(void){
+ int z = 1;
+ char x = z + 1; /* x == 2*/
+ YCF_YIELD();
+ x = x + 1; /* x == 3*/
+ return x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun();
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 3){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/void_param.c.out b/erts/lib_src/yielding_c_fun/test/examples/void_param.c.out
new file mode 100644
index 0000000000..96f574d048
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/void_param.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 3
diff --git a/erts/lib_src/yielding_c_fun/test/examples/void_ret_fun.c b/erts/lib_src/yielding_c_fun/test/examples/void_ret_fun.c
new file mode 100644
index 0000000000..10dd51bfae
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/void_ret_fun.c
@@ -0,0 +1,85 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+
+void fun(int* y){
+ int x = 1;
+ x = x + 1; /* x == 2*/
+ YCF_YIELD();
+ x = x + 1; /* x == 3*/
+ *y = x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int nr_of_trapps = 0;
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,&ret);
+ if(wb != NULL){
+ printf("TRAP\n");
+ nr_of_trapps++;
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+ if(nr_of_trapps != 1){
+ printf("ERROR\n");
+ exit(1);
+ }
+#else
+ fun(&ret);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 3){
+ printf("ERROR\n");
+ exit(1);
+ }
+ return 0;
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/void_ret_fun.c.out b/erts/lib_src/yielding_c_fun/test/examples/void_ret_fun.c.out
new file mode 100644
index 0000000000..4b85bbb30e
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/void_ret_fun.c.out
@@ -0,0 +1,2 @@
+TRAP
+RETURNED 3
diff --git a/erts/lib_src/yielding_c_fun/test/examples/ycf_stack_alloc.c b/erts/lib_src/yielding_c_fun/test/examples/ycf_stack_alloc.c
new file mode 100644
index 0000000000..20fe2a444d
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/ycf_stack_alloc.c
@@ -0,0 +1,126 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+#define YCF_CONSUME_REDS(X)
+#ifndef YCF_YIELD_CODE_GENERATED
+#define YCF_STACK_ALLOC(X) malloc(1)
+#endif
+
+int sub_fun(int x){
+ int i;
+ int count = 0;
+ char* my_data = YCF_STACK_ALLOC(5);
+ my_data[0] = 's';
+ my_data[1] = 'u';
+ my_data[2] = 'b';
+ my_data[3] = '\n';
+ my_data[4] = '\0';
+ for(i = 0; i < 2; i++){
+ count = count + 2;
+ YCF_YIELD();
+ printf("%s", my_data);
+ }
+ return count + x;
+}
+
+int fun(int x){
+ int ret;
+ char* my_data = YCF_STACK_ALLOC(5);
+ char* my_data2 = YCF_STACK_ALLOC(5);
+ char* my_data3;
+ my_data[0] = 'h';
+ my_data[1] = 'e';
+ my_data[2] = 'j';
+ my_data[3] = '\n';
+ my_data[4] = '\0';
+ my_data2[0] = 's';
+ my_data2[1] = 'o';
+ my_data2[2] = 's';
+ my_data2[3] = '\n';
+ my_data2[4] = '\0';
+ YCF_YIELD();
+ printf("%s", my_data);
+ printf("%s", my_data2);
+ ret = sub_fun(x);
+ ret = sub_fun(x);
+ my_data3 = YCF_STACK_ALLOC(5);
+ printf("BEFORE OVERWRITE %s", my_data3);
+ my_data3[0] = 'e';
+ my_data3[1] = 'r';
+ my_data3[2] = 'l';
+ my_data3[3] = '\n';
+ my_data3[4] = '\0';
+ printf("AFTER OVERWRITE %s", my_data3);
+ return ret;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ int nr_of_yields = 0;
+ long nr_of_reductions;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ nr_of_reductions = 101;
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,15,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED %ld\n", nr_of_reductions);
+ nr_of_yields++;
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = fun(1);
+#endif
+ printf("Number of yields %d\n", nr_of_yields);
+ printf("RETURNED %d\n", ret);
+ if(ret != 5){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/ycf_stack_alloc.c.out b/erts/lib_src/yielding_c_fun/test/examples/ycf_stack_alloc.c.out
new file mode 100644
index 0000000000..8b3c31c9e5
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/ycf_stack_alloc.c.out
@@ -0,0 +1,15 @@
+TRAPPED 0
+hej
+sos
+TRAPPED 0
+sub
+TRAPPED 0
+sub
+TRAPPED 0
+sub
+TRAPPED 0
+sub
+BEFORE OVERWRITE sub
+AFTER OVERWRITE erl
+Number of yields 5
+RETURNED 5
diff --git a/erts/lib_src/yielding_c_fun/test/examples/yield_with_struct.c b/erts/lib_src/yielding_c_fun/test/examples/yield_with_struct.c
new file mode 100644
index 0000000000..3506d83499
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/yield_with_struct.c
@@ -0,0 +1,67 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+typedef struct my_struct {
+ struct my_struct* my_struct;
+ int len;
+} my_struct;
+
+
+#define YCF_YIELD()
+
+int fun(char x){
+ my_struct s;
+ my_struct* sp;
+ int len = 55;
+ s.len = 10;
+ s.my_struct = NULL;
+ sp = malloc(sizeof(my_struct));
+ sp->len = 10;
+ sp->my_struct = NULL;
+ YCF_YIELD();
+ return
+ s.len == 10 &&
+ s.my_struct == NULL &&
+ sp->len == 10 &&
+ sp->my_struct == NULL &&
+ len == 55;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 1;
+#endif
+ int ret = 0;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != 1){
+ return 1;
+ }else{
+ return 0;
+ }
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/yield_with_struct.c.out b/erts/lib_src/yielding_c_fun/test/examples/yield_with_struct.c.out
new file mode 100644
index 0000000000..7c3cfb5078
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/yield_with_struct.c.out
@@ -0,0 +1,2 @@
+TRAPPED
+RETURNED 1
diff --git a/erts/lib_src/yielding_c_fun/test/examples/yielding_fun_in_control.c b/erts/lib_src/yielding_c_fun/test/examples/yielding_fun_in_control.c
new file mode 100644
index 0000000000..c0d7ac6c13
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/yielding_fun_in_control.c
@@ -0,0 +1,68 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+int sub_fun(char x){
+ YCF_YIELD();
+ x = x + 1; /* x == 1*/
+ return (x == 10 ? 0 : 1);
+}
+
+
+int fun(char x){
+ if(sub_fun(0)){
+ printf("hej\n");
+ }
+ if(0) printf("NO");
+ else if(sub_fun(0)){
+ printf("hej 2\n");
+ }
+ if(sub_fun(10)){
+ printf("hej 3\n");
+ } else {
+ printf("HEJ\n");
+ }
+ while(sub_fun(9)){
+ printf("hoo\n");
+ }
+ do{
+ printf("haa\n");
+ }while(sub_fun(9));
+ return x;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+ long nr_of_reductions = 1;
+#endif
+ int ret = 0;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = fun_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,1);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ fun(1);
+#endif
+ printf("RETURNED %d\n", ret);
+ return 0;
+}
+
diff --git a/erts/lib_src/yielding_c_fun/test/examples/yielding_fun_in_control.c.out b/erts/lib_src/yielding_c_fun/test/examples/yielding_fun_in_control.c.out
new file mode 100644
index 0000000000..11b01b7d63
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/yielding_fun_in_control.c.out
@@ -0,0 +1,10 @@
+TRAPPED
+hej
+TRAPPED
+hej 2
+TRAPPED
+hej 3
+TRAPPED
+haa
+TRAPPED
+RETURNED 1
diff --git a/erts/lib_src/yielding_c_fun/test/examples/yielding_mutual_recursion.c b/erts/lib_src/yielding_c_fun/test/examples/yielding_mutual_recursion.c
new file mode 100644
index 0000000000..655c94e956
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/yielding_mutual_recursion.c
@@ -0,0 +1,104 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Description:
+ *
+ * Author: Kjell Winblad
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define YCF_YIELD()
+
+int A(int depth);
+int B(int depth);
+
+int A(int depth){
+ int b;
+ YCF_YIELD();
+ depth++;
+ printf("A ");
+ YCF_YIELD();
+ if(depth == 100){
+ return 1;
+ } else {
+ b = B(depth);
+ }
+ YCF_YIELD();
+ return b + 1;
+}
+
+int B(int depth){
+ int a;
+ YCF_YIELD();
+ depth++;
+ printf("B ");
+ YCF_YIELD();
+ if(depth == 100){
+ YCF_YIELD();
+ return 1;
+ } else {
+ a = A(depth);
+ }
+ YCF_YIELD();
+ return a + 1;
+}
+
+void* allocator(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
+
+void freer(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+int main( int argc, const char* argv[] )
+{
+#ifdef YCF_YIELD_CODE_GENERATED
+ void* wb = NULL;
+#endif
+ int ret = 0;
+ long nr_of_reductions = 1;
+#ifdef YCF_YIELD_CODE_GENERATED
+ do{
+ ret = A_ycf_gen_yielding(&nr_of_reductions,&wb,NULL,allocator,freer,NULL,0,NULL,0);
+ if(wb != NULL){
+ printf("TRAPPED\n");
+ }
+ }while(wb != NULL);
+ if(wb != NULL){
+ free(wb);
+ }
+#else
+ ret = A(0);
+#endif
+ printf("RETURNED %d\n", ret);
+ if(ret != A(0)){
+ return 1;
+ }else{
+ return 0;
+ }
+}
diff --git a/erts/lib_src/yielding_c_fun/test/examples/yielding_mutual_recursion.c.out b/erts/lib_src/yielding_c_fun/test/examples/yielding_mutual_recursion.c.out
new file mode 100644
index 0000000000..c41fe372ae
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/examples/yielding_mutual_recursion.c.out
@@ -0,0 +1,302 @@
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+A TRAPPED
+TRAPPED
+B TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+TRAPPED
+RETURNED 100
+A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B A B \ No newline at end of file
diff --git a/erts/lib_src/yielding_c_fun/test/test.sh b/erts/lib_src/yielding_c_fun/test/test.sh
new file mode 100755
index 0000000000..a0270475fb
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/test/test.sh
@@ -0,0 +1,176 @@
+#!/bin/bash
+
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+
+
+#
+# Description:
+#
+# Author: Kjell Winblad
+#
+
+#Code to find directory of this file from https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself
+SOURCE="${BASH_SOURCE[0]}"
+while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
+ DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
+ SOURCE="$(readlink "$SOURCE")"
+ [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
+done
+DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
+
+GC=$1
+
+RR=-rr
+RR=""
+
+PATH=$PATH:$DIR/../bin
+
+set -e
+set -x
+
+TMP_DIR=$DIR/tmp_dir
+
+mkdir -p $TMP_DIR
+
+TMP_FILE=$TMP_DIR/tmp
+
+TMP_C_FILE=$TMP_DIR/tmp.c
+TMP_INC_FILE=$TMP_DIR/tmp.inc
+TMP_C_FILE2=$TMP_DIR/tmp2.c
+TMP_O_FILE1=$TMP_DIR/tmp1.o
+TMP_O_FILE2=$TMP_DIR/tmp2.o
+TMP_H_FILE=$TMP_DIR/tmp.h
+TMP_CC_OUT=$TMP_DIR/a.out
+
+CC_ARGS="-std=c99 -pedantic -Wall"
+
+CC=clang
+
+SIMPLE_TEST_FILES=("$DIR/examples/simple_yield.c"
+ "$DIR/examples/multi_scope_yield.c"
+ "$DIR/examples/nested_loop_yield.c"
+ "$DIR/examples/void_ret_fun.c"
+ "$DIR/examples/declarations.c"
+ "$DIR/examples/void_param.c"
+ "$DIR/examples/simple_fun_call.c"
+ "$DIR/examples/control_statements.c"
+ "$DIR/examples/simple_yielding_fun_call.c"
+ "$DIR/examples/yielding_mutual_recursion.c"
+ "$DIR/examples/stack_array.c"
+ "$DIR/examples/custom_code_save_restore_yield_state.c"
+ "$DIR/examples/custom_code_save_restore_yield_state_alt_syntax.c"
+ "$DIR/examples/destroy_while_yielded.c"
+ "$DIR/examples/consume_reds.c"
+ "$DIR/examples/nested_call_consume_reds.c"
+ "$DIR/examples/auto_yield.c"
+ "$DIR/examples/yield_with_struct.c"
+ "$DIR/examples/const_defenition.c"
+ "$DIR/examples/ycf_stack_alloc.c"
+ "$DIR/examples/declarations_in_for_loops.c"
+ "$DIR/examples/in_code_var_declaration.c"
+ "$DIR/examples/static_inline_function.c"
+ "$DIR/examples/yielding_fun_in_control.c"
+ "$DIR/examples/debug_ptr_to_stack.c"
+ "$DIR/examples/simple_yield_no_reds.c"
+ "$DIR/examples/thread_example.c")
+
+SIMPLE_TEST_FILES_YIELD_FUNS=("-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun -fnoauto sub_fun_1 -fnoauto sub_fun_2 -fnoauto sub_fun_3"
+ "-fnoauto A -fnoauto B"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun -fnoauto sub_fun"
+ "-frec fun -frec rec_inc"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun -fnoauto sub_fun"
+ "-f fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-fnoauto fun -fnoauto sub_fun"
+ "-fnoauto fun"
+ "-fnoauto fun"
+ "-f f_1 -f f_2")
+
+# Check that yielding_c_fun can repeat files correctly
+for C_FILE in $SIMPLE_TEST_FILES
+do
+ yielding_c_fun $GC $RR -repeat $C_FILE > $TMP_C_FILE
+ cmp $C_FILE $TMP_C_FILE
+done
+
+# Check that the default action is to generate correct yield code
+yielding_c_fun $GC -yield $DIR/examples/simple_yield.c > $TMP_C_FILE
+yielding_c_fun $GC $DIR/examples/simple_yield.c > $TMP_C_FILE2
+cmp $TMP_C_FILE $TMP_C_FILE2
+
+# Check yielding
+for ((i = 0; i < ${#SIMPLE_TEST_FILES[@]}; i++))
+do
+ yielding_c_fun $GC $RR -yield ${SIMPLE_TEST_FILES_YIELD_FUNS[$i]} "${SIMPLE_TEST_FILES[$i]}" > $TMP_C_FILE
+ $CC $CC_ARGS -g $TMP_C_FILE -o $TMP_CC_OUT
+ $TMP_CC_OUT > $TMP_FILE
+ cmp $TMP_FILE "${SIMPLE_TEST_FILES[$i]}.out"
+done
+
+
+# Check generated header file and output file
+yielding_c_fun $GC $RR -yield -fnoauto A -fnoauto B -header_file_name $TMP_H_FILE -output_file_name $TMP_C_FILE "$DIR/examples/test_generated_header_file_code.c"
+$CC $CC_ARGS -c $TMP_C_FILE -o $TMP_O_FILE1
+$CC $CC_ARGS -I$DIR -DYCF_YIELD_CODE_GENERATED=1 -c "$DIR/examples/test_generated_header_file_main.c" -o $TMP_O_FILE2
+$CC $CC_ARGS $TMP_O_FILE1 $TMP_O_FILE2 -o $TMP_CC_OUT
+$TMP_CC_OUT > $TMP_FILE
+cmp $TMP_FILE "$DIR/examples/test_generated_header_file_main.c.out"
+
+
+# Check generation of only yielding fun
+echo $TMP_INC_FILE
+yielding_c_fun.bin $GC $RR -yield -static_aux_funs -only_yielding_funs -fnoauto fun -output_file_name $TMP_INC_FILE "$DIR/examples/test_only_output_yielding_funs.c"
+$CC $CC_ARGS -I "$TMP_DIR" "$DIR/examples/test_only_output_yielding_funs.c" -o $TMP_CC_OUT
+$TMP_CC_OUT > $TMP_FILE
+cmp $TMP_FILE "$DIR/examples/test_only_output_yielding_funs.c.out"
+
+# Check that debug mode can detect pointers to stack
+yielding_c_fun $GC $RR -yield -debug -fnoauto fun "$DIR/examples/debug_ptr_to_stack.c" > $TMP_C_FILE
+$CC $CC_ARGS $TMP_C_FILE -o $TMP_CC_OUT
+(set +e ; $TMP_CC_OUT ; [ $? -ne 0 ])
+
+# Check that memory usage of the tool can be logged
+MEM_LOG_FILE="$TMP_DIR/my_mem_log_file.txt"
+yielding_c_fun $GC $RR -log_max_mem_usage "$MEM_LOG_FILE" -yield -debug -fnoauto fun "$DIR/examples/multi_scope_yield.c" > $TMP_C_FILE
+test -f "$MEM_LOG_FILE"
+#rm "$MEM_LOG_FILE"
+
+# Uncomment to the test the Erlang NIF example
+# if [ `rebar3 > /dev/null 2>&1 ; echo $?` = 0 ]
+# then
+# (cd "$DIR/examples/sha256_erlang_nif/" && make clean && make test)
+# fi
+
+
diff --git a/lib/erl_interface/src/legacy/portability.h b/erts/lib_src/yielding_c_fun/ycf_helpers.h
index 42a78662d5..ae32f60f10 100644
--- a/lib/erl_interface/src/legacy/portability.h
+++ b/erts/lib_src/yielding_c_fun/ycf_helpers.h
@@ -1,8 +1,8 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2000-2016. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -14,21 +14,27 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
+ *
* %CopyrightEnd%
*/
-#ifndef _PORTABILITY_H
-#define _PORTABILITY_H
-
-#if !defined(__GNUC__) || __GNUC__ < 2
-
/*
- * GCC's attributes are too useful to not use. Other compilers
- * just lose opportunities to optimize and warn.
+ * Author: Kjell Winblad
*/
-# define __attribute__(foo) /* nothing */
-#endif
+#ifndef YIELDING_C_FUN_HELPERS_H
+#define YIELDING_C_FUN_HELPERS_H
+
+#include <stdlib.h>
+
+static void* ycf_alloc(size_t size, void* context){
+ (void)context;
+ return malloc(size);
+}
-#endif /* _PORTABILITY_H */
+void ycf_free(void* data, void* context){
+ (void)context;
+ free(data);
+}
+
+#endif
diff --git a/erts/lib_src/yielding_c_fun/ycf_lexer.c b/erts/lib_src/yielding_c_fun/ycf_lexer.c
new file mode 100644
index 0000000000..8fcebcc6b0
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_lexer.c
@@ -0,0 +1,483 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+
+#include "lib/tiny_regex_c/re.h"
+#include "ycf_yield_fun.h"
+#include "ycf_utils.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+
+
+int ycf_symbol_is_text_eq(ycf_symbol* symbol, char* str){
+ unsigned long symbol_length = symbol->stop - symbol->start;
+ return
+ symbol_length == strlen(str) &&
+ strncmp(str, &symbol->source[symbol->start], symbol_length) == 0;
+}
+
+char* ycf_symbol_text_between(ycf_symbol* s1, ycf_symbol* s2){
+ int size = s2->stop - s1->start;
+ char* str = ycf_malloc(size+1);
+ strncpy(str, &s1->source[s1->start], size);
+ str[size] = 0;
+ return str;
+}
+
+char* get_symbol_type_text(ycf_symbol_type type){
+ switch(type) {
+ case ycf_symbol_type_comment: return "ycf_symbol_type_comment";
+ case ycf_symbol_type_string_literal: return "ycf_symbol_type_string_literal";
+ case ycf_symbol_type_macro_define: return "ycf_symbol_type_macro_define";
+ case ycf_symbol_type_macro_command: return "ycf_symbol_type_macro_command";
+ case ycf_symbol_type_whitespace: return "ycf_symbol_type_whitespace";
+ case ycf_symbol_type_identifier: return "ycf_symbol_type_identifier";
+ case ycf_symbol_type_number: return "ycf_symbol_type_number";
+ case ycf_symbol_type_star: return "ycf_symbol_type_star";
+ case ycf_symbol_type_neg: return "ycf_symbol_type_neg";
+ case ycf_symbol_type_equal_equal_sign: return "ycf_symbol_type_equal_equal_sign";
+ case ycf_symbol_type_not_equal_sign: return "ycf_symbol_type_not_equal_sign";
+ case ycf_symbol_type_open_parenthesis: return "ycf_symbol_type_open_parenthesis";
+ case ycf_symbol_type_end_parenthesis: return "ycf_symbol_type_end_parenthesis";
+ case ycf_symbol_type_open_curly_brace: return "ycf_symbol_type_open_curly_brace";
+ case ycf_symbol_type_end_curly_brace: return "ycf_symbol_type_end_curly_brace";
+ case ycf_symbol_type_open_square_bracket: return "ycf_symbol_type_open_square_bracket";
+ case ycf_symbol_type_end_square_bracket: return "ycf_symbol_type_end_square_bracket";
+ case ycf_symbol_type_equal_sign: return "ycf_symbol_type_equal_sign";
+ case ycf_symbol_type_semicolon: return "ycf_symbol_type_semicolon";
+ case ycf_symbol_type_comma: return "ycf_symbol_type_comma";
+ case ycf_symbol_type_consume_reds: return "ycf_symbol_type_consume_reds";
+ case ycf_symbol_type_pointer_field_access: return "ycf_symbol_type_pointer_field_access";
+ case ycf_symbol_type_period: return "ycf_symbol_type_period";
+ case ycf_symbol_type_const: return "ycf_symbol_type_const";
+ case ycf_symbol_type_void: return "ycf_symbol_type_void";
+ case ycf_symbol_type_volatile: return "ycf_symbol_type_volatile";
+ case ycf_symbol_type_static: return "ycf_symbol_type_static";
+ case ycf_symbol_type_inline: return "ycf_symbol_type_inline";
+ case ycf_symbol_type_return: return "ycf_symbol_type_return";
+ case ycf_symbol_type_if: return "ycf_symbol_type_if";
+ case ycf_symbol_type_else: return "ycf_symbol_type_else";
+ case ycf_symbol_type_goto: return "ycf_symbol_type_goto";
+ case ycf_symbol_type_break: return "ycf_symbol_type_break";
+ case ycf_symbol_type_while: return "ycf_symbol_type_while";
+ case ycf_symbol_type_do: return "ycf_symbol_type_do";
+ case ycf_symbol_type_for: return "ycf_symbol_type_for";
+ case ycf_symbol_type_switch: return "ycf_symbol_type_switch";
+ case ycf_symbol_type_continue: return "ycf_symbol_type_continue";
+ case ycf_symbol_type_something_else: return "ycf_symbol_type_something_else";
+ case ycf_symbol_type_special_code_start: return "ycf_symbol_type_special_code_start";
+ case ycf_symbol_type_special_code_end: return "ycf_symbol_type_special_code_end";
+ }
+ return "non_existing_symbol?";
+}
+
+typedef struct symbol_finder {
+ int (*finder)(struct symbol_finder*,char*);
+ ycf_symbol_type type;
+ int length;
+ char *str_1;
+ char *str_2;
+} symbol_finder;
+
+int starts_with(char *str, char *prefix)
+{
+ return strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+int until_no_match(symbol_finder* f, char* text){
+ int pos = 0;
+ while(re_match(f->str_1, &(text[pos])) == 0){
+ pos++;
+ }
+ return pos;
+}
+
+int string_litteral_finder(symbol_finder* f, char* text){
+ int pos = 0;
+ if (starts_with(text, "\"")){
+ pos++;
+ //\"(\\.|[^"\\])*\"
+ while(re_match("\\.", &(text[pos])) == 0 ||
+ re_match("[^\"]", &(text[pos])) == 0){
+ pos++;
+ }
+ if(starts_with(&(text[pos]), "\"")){
+ return pos + 1;
+ }else {
+ printf("Broken string litteral\n");
+ exit(1);
+ }
+ }
+ return pos;
+}
+
+int macro_define_finder(symbol_finder* f, char* text){
+ int pos = 0;
+ if (starts_with(text, "#define")){
+ pos = pos + strlen("#define");
+ while(1){
+ if(starts_with(&(text[pos]), "\\\n")){
+ pos = pos + 2;
+ } else if (starts_with(&(text[pos]), "\n")){
+ break;
+ } else {
+ pos++;
+ }
+ }
+ }
+ return pos;
+}
+
+
+int starts_with_until_no_match(symbol_finder* f, char* text){
+ int pos = 0;
+ if(re_match(f->str_1, text) == 0){
+ while(re_match(f->str_2, &(text[pos])) == 0){
+ pos++;
+ }
+ }
+ return pos;
+}
+
+int starts_with_ends_with(symbol_finder* f, char* text){
+ if(starts_with(text, f->str_1)){
+ int pos = 1;
+ while(!starts_with(&(text[pos]), f->str_2)){
+ pos++;
+ }
+ return pos+strlen(f->str_2);
+ }
+ return 0;
+}
+
+int fixed_string(symbol_finder* f, char* text){
+ if(starts_with(text, f->str_1)){
+ return strlen(f->str_1);
+ }
+ return 0;
+}
+
+int fixed_alpha_string(symbol_finder* f, char* text){
+ if(starts_with(text, f->str_1) &&
+ re_match("[^\\W]", &text[strlen(f->str_1)])){
+ return strlen(f->str_1);
+ }
+ return 0;
+}
+
+int regex_char(symbol_finder* f, char* text){
+ if(re_match(f->str_1, text) == 0){
+ return 1;
+ }
+ return 0;
+}
+
+void fold_whitespace_and_comments(ycf_symbol_list* symbols){
+ ycf_symbol* prev = NULL;
+ ycf_symbol* current = symbols->head;
+ ycf_symbol* dummy = ycf_malloc(sizeof(ycf_symbol));
+ while(current != NULL){
+ current->whitespace_or_comment_before = NULL;
+ if(prev != NULL && (prev->type == ycf_symbol_type_whitespace ||
+ prev->type == ycf_symbol_type_comment)){
+ current->whitespace_or_comment_before = prev;
+ }
+ prev = current;
+ current = current->next;
+ }
+ // remove ycf_symbol_type_whitespace and comments from list
+ dummy->type = ycf_symbol_type_void;
+ dummy->next = symbols->head;
+ prev = dummy;
+ current = prev->next;
+ while(current != NULL &&
+ current != symbols->last){
+ if(current->type == ycf_symbol_type_whitespace ||
+ current->type == ycf_symbol_type_comment){
+ prev->next = current->next;
+ current = current->next;
+ }else {
+ prev = current;
+ current = current->next;
+ }
+ }
+ symbols->head = dummy->next;
+}
+
+ycf_symbol_list ycf_symbol_list_from_text(char* text){
+ int pos = 0;
+ int nr_of_finders = 41;
+ int i;
+ ycf_symbol_list ret = ycf_symbol_list_empty();
+ symbol_finder symbol_finders[] =
+ {
+ {
+ .type = ycf_symbol_type_special_code_start,
+ .str_1 = "/*special_code_start:",
+ .str_2 = "*/",
+ .finder = starts_with_ends_with
+ },
+ {
+ .type = ycf_symbol_type_special_code_end,
+ .str_1 = "/*special_code_end*/",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_comment,
+ .str_1 = "/*",
+ .str_2 = "*/",
+ .finder = starts_with_ends_with
+ },
+ {
+ .type = ycf_symbol_type_string_literal,
+ .finder = string_litteral_finder
+ },
+ {
+ .type = ycf_symbol_type_macro_define,
+ .finder = macro_define_finder
+ },
+ {
+ .type = ycf_symbol_type_macro_command,
+ .str_1 = "#",
+ .str_2 = "\n",
+ .finder = starts_with_ends_with
+ },
+ {
+ .type = ycf_symbol_type_whitespace,
+ .str_1 = "\\s",
+ .finder = until_no_match
+ },
+ {
+ .type = ycf_symbol_type_void,
+ .str_1 = "void",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_static,
+ .str_1 = "static",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_inline,
+ .str_1 = "inline",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_const,
+ .str_1 = "const",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_volatile,
+ .str_1 = "volatile",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_consume_reds,
+ .str_1 = "YCF_CONSUME_REDS",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_return,
+ .str_1 = "return",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_if,
+ .str_1 = "if",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_else,
+ .str_1 = "else",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_goto,
+ .str_1 = "goto",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_break,
+ .str_1 = "break",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_continue,
+ .str_1 = "continue",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_while,
+ .str_1 = "while",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_do,
+ .str_1 = "do",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_for,
+ .str_1 = "for",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_switch,
+ .str_1 = "switch",
+ .finder = fixed_alpha_string
+ },
+ {
+ .type = ycf_symbol_type_identifier,
+ .str_1 = "[a-zA-Z]",
+ .str_2 = "\\w",
+ .finder = starts_with_until_no_match
+ },
+ {
+ .type = ycf_symbol_type_number,
+ .str_1 = "\\d",
+ .finder = until_no_match
+ },
+ {
+ .type = ycf_symbol_type_open_parenthesis,
+ .str_1 = "(",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_end_parenthesis,
+ .str_1 = ")",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_open_curly_brace,
+ .str_1 = "{",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_end_curly_brace,
+ .str_1 = "}",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_open_square_bracket,
+ .str_1 = "[",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_end_square_bracket,
+ .str_1 = "]",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_equal_sign,
+ .str_1 = "=",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_not_equal_sign,
+ .str_1 = "!=",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_equal_sign,
+ .str_1 = "==",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_star,
+ .str_1 = "*",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_neg,
+ .str_1 = "!",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_semicolon,
+ .str_1 = ";",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_comma,
+ .str_1 = ",",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_period,
+ .str_1 = ".",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_pointer_field_access,
+ .str_1 = "->",
+ .finder = fixed_string
+ },
+ {
+ .type = ycf_symbol_type_something_else,
+ .str_1 = ".",
+ .finder = regex_char
+ }
+ };
+ while(text[pos] != 0){
+ int last_pos = pos;
+ for(i = 0; i < nr_of_finders; i++) {
+ symbol_finder f = symbol_finders[i];
+ int stop = f.finder(&f, &text[pos]);
+ if(stop){
+ ycf_symbol* s = ycf_malloc(sizeof(ycf_symbol));
+ s->type = f.type;
+ s->source = text;
+ s->start = pos;
+ s->stop = pos + stop;
+ s->next = NULL;
+ ycf_symbol_list_append(&ret, s);
+ pos = s->stop;
+ break;
+ }
+ }
+ if (last_pos == pos){
+ printf("Lexer: NOTHING MATCH Stuck at: \n%s\n", &text[pos]);
+ exit(1);
+ }
+ }
+ fold_whitespace_and_comments(&ret);
+ return ret;
+}
+
+void ycf_symbol_list_print(char* text){
+ ycf_symbol_list symbols = ycf_symbol_list_from_text(text);
+ ycf_symbol* s = symbols.head;
+ while(s != NULL){
+ printf("TYPE %s, START=%d, STOP=%d\n",
+ get_symbol_type_text(s->type),
+ s->start,
+ s->stop);
+ s = s->next;
+ }
+ printf("||||| END OF SYMBOLS\n");
+}
diff --git a/erts/lib_src/yielding_c_fun/ycf_lists.h b/erts/lib_src/yielding_c_fun/ycf_lists.h
new file mode 100644
index 0000000000..7bb3487ecf
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_lists.h
@@ -0,0 +1,316 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+#ifndef YIELDING_C_FUN_LISTS_H
+#define YIELDING_C_FUN_LISTS_H
+
+#include <stdlib.h>
+#include <stdio.h>
+
+
+#define INIT_LIST(L) \
+ do{ \
+ (L)->head = NULL; \
+ (L)->last = NULL; \
+ }while(0)
+
+#define APPEND_LIST(L,E) \
+ do{ \
+ void* e = E; \
+ if((L)->head == NULL){ \
+ (L)->head = e; \
+ } else { \
+ (L)->last->next = e; \
+ } \
+ (L)->last = e; \
+ }while(0)
+
+#define PREPEND_LIST(T,L,E) \
+ do{ \
+ T* e = E; \
+ if((L)->head == NULL){ \
+ (L)->last = e; \
+ } else { \
+ e->next = (L)->head; \
+ } \
+ (L)->head = e; \
+ }while(0)
+
+#define CONCAT_LIST(T,L1,L2) \
+ do{ \
+ T* current = (L2)->head; \
+ while(current != NULL){ \
+ APPEND_LIST(L1, current); \
+ current = current->next; \
+ } \
+ }while(0)
+
+#define PRINT_LIST(T,L, NAME) \
+ do{ \
+ printf("NAME %s\n", NAME); \
+ printf("HEAD %p\n", (L)->head); \
+ printf("LAST %p\n", (L)->last); \
+ printf("ELEMS:\n"); \
+ T* current = (L)->head; \
+ while(current != NULL){ \
+ printf("E: %p\n", current); \
+ current = current->next; \
+ } \
+ }while(0)
+
+#define REMOVE_LIST(T,L,E) \
+ do{ \
+ T* prev = NULL; \
+ T* rl_current = (L)->head; \
+ T* e = E; \
+ while(rl_current != e && rl_current != NULL){ \
+ prev = rl_current ; \
+ rl_current = rl_current ->next; \
+ } \
+ if(rl_current == NULL){ \
+ exit(1); \
+ } \
+ if(prev == NULL){ \
+ if((L)->head == (L)->last){ \
+ (L)->last = NULL; \
+ } \
+ (L)->head = (L)->head->next; \
+ }else{ \
+ if(rl_current == (L)->last){ \
+ (L)->last = prev; \
+ } \
+ prev->next = rl_current ->next; \
+ } \
+ }while(0)
+
+#define INSERT_AFTER_LIST(T,L,E,TO_INSERT) \
+ do{ \
+ T* current_y = (L)->head; \
+ T* elem_x = E; \
+ T* to_insert2 = TO_INSERT; \
+ if(elem_x == NULL){ \
+ PREPEND_LIST(T,L,to_insert2); \
+ break; \
+ }else if(elem_x == (L)->last){ \
+ APPEND_LIST(L,to_insert2); \
+ break; \
+ } \
+ while(current_y != elem_x && current_y != NULL){ \
+ current_y = current_y->next; \
+ } \
+ if(current_y == NULL){ \
+ printf("CANNOT INSERT AFTER NONEXISTING\n"); \
+ exit(1); \
+ } \
+ to_insert2->next = current_y->next; \
+ current_y->next = to_insert2; \
+ }while(0)
+
+#define INSERT_BEFORE_LIST(T,L,E_BEFORE,TO_INSERT) \
+ do{ \
+ T* prev_x = NULL; \
+ T* current_x = (L)->head; \
+ T* to_insert_x = TO_INSERT; \
+ T* e_before_x = E_BEFORE; \
+ while(current_x != e_before_x && current_x != NULL){ \
+ prev_x = current_x; \
+ current_x = current_x->next; \
+ } \
+ if(current_x == NULL){ \
+ printf("CANNOT INSERT AFTER NONEXISTING\n"); \
+ exit(1); \
+ } \
+ INSERT_AFTER_LIST(T,L,prev_x,to_insert_x); \
+ }while(0)
+
+
+#define REPLACE_LIST(T,L,OLD,NEW) \
+ do{ \
+ T* old = OLD; \
+ T* new = NEW; \
+ T* prev_old_next = old->next; \
+ INSERT_AFTER_LIST(T,L,old,new); \
+ REMOVE_LIST(T,L,old); \
+ old->next = prev_old_next; \
+ }while(0)
+
+
+/* list functions */
+
+#define GENERATE_LIST_FUNCTIONS(NODE_TYPE) \
+ \
+ NODE_TYPE##_list NODE_TYPE##_list_empty(){ \
+ NODE_TYPE##_list list; \
+ INIT_LIST(&list); \
+ return list; \
+ } \
+ \
+ NODE_TYPE* NODE_TYPE##_shallow_copy(NODE_TYPE* n){ \
+ NODE_TYPE* new = ycf_malloc(sizeof(NODE_TYPE)); \
+ *new = *n; \
+ new->next = NULL; \
+ return new; \
+ } \
+ \
+ NODE_TYPE##_list NODE_TYPE##_list_shallow_copy(NODE_TYPE##_list n){ \
+ NODE_TYPE##_list new; \
+ NODE_TYPE* current = n.head; \
+ INIT_LIST(&new); \
+ while(current != NULL){ \
+ APPEND_LIST(&new, NODE_TYPE##_shallow_copy(current)); \
+ current = current->next; \
+ } \
+ return new; \
+ } \
+ \
+ int NODE_TYPE##_list_get_item_position(NODE_TYPE##_list* list, NODE_TYPE* node){ \
+ NODE_TYPE* current = list->head; \
+ int pos = 0; \
+ while(current != NULL){ \
+ if(current == node){ \
+ return pos; \
+ } \
+ pos = pos + 1; \
+ current = current->next; \
+ } \
+ return -1; \
+ } \
+ \
+ NODE_TYPE* NODE_TYPE##_list_get_item_at_position(NODE_TYPE##_list* list, int pos){ \
+ NODE_TYPE* current = list->head; \
+ int current_pos = 0; \
+ while(current != NULL){ \
+ if(current_pos == pos){ \
+ return current; \
+ } \
+ current_pos = current_pos + 1; \
+ current = current->next; \
+ } \
+ return NULL; \
+ } \
+ \
+ void NODE_TYPE##_list_append(NODE_TYPE##_list* list, NODE_TYPE* node){ \
+ APPEND_LIST(list, node); \
+ } \
+ \
+ NODE_TYPE##_list NODE_TYPE##_list_copy_append(NODE_TYPE##_list list, NODE_TYPE* node){ \
+ NODE_TYPE##_list list_copy = NODE_TYPE##_list_shallow_copy(list); \
+ NODE_TYPE* node_copy = NODE_TYPE##_shallow_copy(node); \
+ NODE_TYPE##_list_append(&list_copy, node_copy); \
+ return list_copy; \
+ } \
+ \
+ void NODE_TYPE##_list_prepend(NODE_TYPE##_list* list, NODE_TYPE* node){ \
+ PREPEND_LIST(NODE_TYPE, list, node); \
+ } \
+ \
+ NODE_TYPE##_list NODE_TYPE##_list_copy_prepend(NODE_TYPE##_list list, NODE_TYPE* node){ \
+ NODE_TYPE##_list list_copy = NODE_TYPE##_list_shallow_copy(list); \
+ NODE_TYPE* node_copy = NODE_TYPE##_shallow_copy(node); \
+ NODE_TYPE##_list_prepend(&list_copy, node_copy); \
+ return list_copy; \
+ } \
+ \
+ void NODE_TYPE##_list_insert_before(NODE_TYPE##_list* list, NODE_TYPE* before_this, NODE_TYPE* to_insert_z){ \
+ INSERT_BEFORE_LIST(NODE_TYPE, list, before_this, to_insert_z); \
+ } \
+ \
+ NODE_TYPE##_list NODE_TYPE##_list_copy_insert_before(NODE_TYPE##_list list, NODE_TYPE* before_this, NODE_TYPE* to_insert){ \
+ int pos = NODE_TYPE##_list_get_item_position(&list, before_this); \
+ NODE_TYPE##_list list_copy = NODE_TYPE##_list_shallow_copy(list);; \
+ NODE_TYPE* actual_this = NODE_TYPE##_list_get_item_at_position(&list_copy, pos); \
+ NODE_TYPE* to_insert_copy = NODE_TYPE##_shallow_copy(to_insert); \
+ NODE_TYPE##_list_insert_before(&list_copy, actual_this, to_insert_copy); \
+ return list_copy; \
+ } \
+ \
+ void NODE_TYPE##_list_insert_after(NODE_TYPE##_list* list, NODE_TYPE* after_this, NODE_TYPE* to_insert_z){ \
+ INSERT_AFTER_LIST(NODE_TYPE, list, after_this, to_insert_z); \
+ } \
+ \
+ NODE_TYPE##_list NODE_TYPE##_list_copy_insert_after(NODE_TYPE##_list list, NODE_TYPE* after_this, NODE_TYPE* to_insert){ \
+ int pos = NODE_TYPE##_list_get_item_position(&list, after_this); \
+ NODE_TYPE##_list list_copy = NODE_TYPE##_list_shallow_copy(list); \
+ NODE_TYPE* actual_this = NODE_TYPE##_list_get_item_at_position(&list_copy, pos); \
+ NODE_TYPE* to_insert_copy = NODE_TYPE##_shallow_copy(to_insert); \
+ NODE_TYPE##_list_insert_after(&list_copy, actual_this, to_insert_copy); \
+ return list_copy; \
+ } \
+ \
+ void NODE_TYPE##_list_remove(NODE_TYPE##_list* list, NODE_TYPE* to_remove){ \
+ REMOVE_LIST(NODE_TYPE, list, to_remove); \
+ } \
+ \
+ NODE_TYPE##_list NODE_TYPE##_list_copy_remove(NODE_TYPE##_list list, NODE_TYPE* to_remove){ \
+ int pos = NODE_TYPE##_list_get_item_position(&list, to_remove); \
+ NODE_TYPE##_list list_copy = NODE_TYPE##_list_shallow_copy(list); \
+ NODE_TYPE* actual_this = NODE_TYPE##_list_get_item_at_position(&list_copy, pos); \
+ NODE_TYPE##_list_remove(&list_copy, actual_this); \
+ return list_copy; \
+ } \
+ \
+ void NODE_TYPE##_list_replace(NODE_TYPE##_list* list, NODE_TYPE* to_replace, NODE_TYPE* replace_with){ \
+ REPLACE_LIST(NODE_TYPE, list, to_replace, replace_with); \
+ } \
+ \
+ NODE_TYPE##_list NODE_TYPE##_list_copy_replace(NODE_TYPE##_list list, NODE_TYPE* to_replace, NODE_TYPE* replace_with){ \
+ int pos = NODE_TYPE##_list_get_item_position(&list, to_replace); \
+ NODE_TYPE##_list list_copy = NODE_TYPE##_list_shallow_copy(list); \
+ NODE_TYPE* actual_this = NODE_TYPE##_list_get_item_at_position(&list_copy, pos); \
+ NODE_TYPE* replace_with_copy = NODE_TYPE##_shallow_copy(replace_with); \
+ NODE_TYPE##_list_replace(&list_copy, actual_this, replace_with_copy); \
+ return list_copy; \
+ } \
+ \
+ void NODE_TYPE##_list_concat(NODE_TYPE##_list* list1, NODE_TYPE##_list* list2){ \
+ CONCAT_LIST(NODE_TYPE, list1, list2); \
+ } \
+ NODE_TYPE##_list NODE_TYPE##_list_copy_concat(NODE_TYPE##_list list1, NODE_TYPE##_list list2){ \
+ NODE_TYPE##_list list1_copy = NODE_TYPE##_list_shallow_copy(list1); \
+ NODE_TYPE##_list list2_copy = NODE_TYPE##_list_shallow_copy(list2); \
+ CONCAT_LIST(NODE_TYPE, &list1_copy, &list2_copy); \
+ return list1_copy; \
+ } \
+ \
+ size_t NODE_TYPE##_list_length(NODE_TYPE##_list list){ \
+ NODE_TYPE* current = list.head; \
+ size_t count = 0; \
+ while(current != NULL){ \
+ count = count + 1; \
+ current = current->next; \
+ } \
+ return count; \
+ }
+
+/* void print_string_list(string_list n){ */
+/* string_list_item* current = n.head; */
+/* printf("START\n"); */
+/* while(current != NULL){ */
+/* printf("%s\n", current->str); */
+/* current = current->next; */
+/* } */
+/* printf("END\n"); */
+/* } */
+
+#endif //YIELDING_C_FUN_LISTS_H
diff --git a/erts/lib_src/yielding_c_fun/ycf_main.c b/erts/lib_src/yielding_c_fun/ycf_main.c
new file mode 100644
index 0000000000..2287db8161
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_main.c
@@ -0,0 +1,330 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+
+#include "ycf_utils.h"
+#include "ycf_yield_fun.h"
+#include "ycf_node.h"
+#include "lib/simple_c_gc/simple_c_gc.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+
+char* file_to_str(const char* filename) {
+ char * buffer;
+ long length;
+ FILE * f = fopen (filename, "rb");
+
+ if (f) {
+ fseek (f, 0, SEEK_END);
+ length = ftell (f);
+ fseek (f, 0, SEEK_SET);
+ buffer = ycf_malloc (length+1);
+ size_t nr_of_read_bytes =
+ fread (buffer, 1, length, f);
+ if (nr_of_read_bytes != length) {
+ printf("error: while reading file %s\n", filename);
+ exit (1);
+ }
+ fclose (f);
+ buffer[length] = 0;
+ } else {
+ printf("error: could not open file %s\n", filename);
+ exit(1);
+ }
+
+ return buffer;
+}
+
+void str_to_file(const char *filepath, const char *data)
+{
+ FILE *fp = fopen(filepath, "w");
+ if (fp != NULL)
+ {
+ fputs(data, fp);
+ fclose(fp);
+ }
+}
+
+void parse_and_repeat_src_from_ast(const char* file_name){
+ char* content = file_to_str(file_name);
+ ycf_node* tree = ycf_node_from_string(content);
+ ycf_node_print(tree, NULL);
+}
+
+void parse_and_print_symbols(const char* file_name){
+ char* content = file_to_str(file_name);
+ ycf_symbol_list_print(content);
+}
+
+void parse_and_print_ast(const char* file_name){
+ char* content = file_to_str(file_name);
+ ycf_node* tree = ycf_node_from_string(content);
+ print_abstract_syntax_tree(tree);
+}
+
+void print_help_text_and_exit(char* program_name, int error_code) {
+ printf("Usage: %s [-h]\n"
+ " %s [-use_gc [-print_gc_info]]\n"
+ " [-log_max_mem_usage log_file]\n"
+ " [(( -f | -frec | -fnoauto ) function_name)...\n"
+ " [-output_file_name output_file]\n"
+ " [-header_file_name header_file]\n"
+ " [-debug]\n"
+ " [-only_yielding_funs]\n"
+ " [-static_aux_funs]\n"
+ " input_c_file]]\n"
+ "\n"
+ "Please see the README.md file for more details.\n",
+ program_name,
+ program_name);
+ exit(0);
+}
+
+int ycf_main( int argc, char* argv[] )
+{
+ int i = 1;
+ for(i = 1; i < argc; i++ ){
+ bool repeat = ycf_string_is_equal("-repeat", argv[i]);
+ bool print_symbols = ycf_string_is_equal("-print_symbols", argv[i]);
+ bool print_ast = ycf_string_is_equal("-print_ast", argv[i]);;
+ bool yield = ycf_string_is_equal("-yield", argv[i]);
+ if(repeat || print_symbols || print_ast || yield){
+ i++;
+ if(i >= argc){
+ printf("ERROR: Expected at least one more argument\n\n");
+ print_help_text_and_exit(argv[0], 1);
+ }
+ }
+ if (print_ast) {
+ parse_and_print_ast(argv[i]);
+ } else if (repeat) {
+ parse_and_repeat_src_from_ast(argv[i]);
+ } else if (print_symbols) {
+ parse_and_print_symbols(argv[i]);
+ } else /* Default is yield */ {
+ ycf_string_item_list funs_to_yield = ycf_string_item_list_empty();
+ ycf_string_item_list funs_to_yield_no_auto = ycf_string_item_list_empty();
+ ycf_string_item_list funs_to_yield_frec = ycf_string_item_list_empty();
+ ycf_string_item_list all_yield_funs = ycf_string_item_list_empty();
+ char* header_file_name = NULL;
+ char* output_file_name = NULL;
+ bool debug = false;
+ bool only_yielding_funs = false;
+ bool static_aux_funs = false;
+ while(i < argc && (ycf_string_is_equal(argv[i], "-f") ||
+ ycf_string_is_equal(argv[i], "-frec") ||
+ ycf_string_is_equal(argv[i], "-fnoauto") ||
+ ycf_string_is_equal(argv[i], "-header_file_name") ||
+ ycf_string_is_equal(argv[i], "-output_file_name") ||
+ ycf_string_is_equal(argv[i], "-debug") ||
+ ycf_string_is_equal(argv[i], "-only_yielding_funs") ||
+ ycf_string_is_equal(argv[i], "-static_aux_funs"))){
+ bool frec = ycf_string_is_equal(argv[i], "-frec");
+ bool noauto = ycf_string_is_equal(argv[i], "-fnoauto");
+ bool header = ycf_string_is_equal(argv[i], "-header_file_name");
+ bool output = ycf_string_is_equal(argv[i], "-output_file_name");
+ i++;
+ if(ycf_string_is_equal(argv[i-1], "-debug")){
+ debug = true;
+ continue;
+ }
+ if(ycf_string_is_equal(argv[i-1], "-only_yielding_funs")){
+ only_yielding_funs = true;
+ continue;
+ }
+ if(ycf_string_is_equal(argv[i-1], "-static_aux_funs")){
+ static_aux_funs = true;
+ continue;
+ }
+ if(i >= argc){
+ fprintf(stderr, "ERROR: Expected a name after %s\n\n", argv[i-1]);
+ print_help_text_and_exit(argv[0], 1);
+ }
+ if(header){
+ if(header_file_name != NULL){
+ fprintf(stderr, "ERROR: Can only print a single header file\n\n");
+ print_help_text_and_exit(argv[0], 1);
+ }
+ header_file_name = (char*)argv[i];
+ } else if(output){
+ if(output_file_name != NULL){
+ fprintf(stderr, "ERROR: Can only print a single output file\n\n");
+ print_help_text_and_exit(argv[0], 1);
+ }
+ output_file_name = (char*)argv[i];
+ } else if(frec){
+ ycf_string_item_list_append(&funs_to_yield_frec, ycf_string_item_new((char *) argv[i]));
+ } else if(noauto){
+ ycf_string_item_list_append(&funs_to_yield_no_auto, ycf_string_item_new((char *) argv[i]));
+ } else{
+ ycf_string_item_list_append(&funs_to_yield, ycf_string_item_new((char *) argv[i]));
+ }
+ i++;
+ }
+ ycf_string_item_list funs_to_yield_copy = ycf_string_item_list_shallow_copy(funs_to_yield);
+ ycf_string_item_list funs_to_yield_no_auto_copy = ycf_string_item_list_shallow_copy(funs_to_yield_no_auto);
+ ycf_string_item_list funs_to_yield_frec_copy = ycf_string_item_list_shallow_copy(funs_to_yield_frec);
+ ycf_string_item_list_concat(&all_yield_funs, &funs_to_yield_copy);
+ ycf_string_item_list_concat(&all_yield_funs, &funs_to_yield_no_auto_copy);
+ ycf_string_item_list_concat(&all_yield_funs, &funs_to_yield_frec_copy);
+ if(funs_to_yield.head == NULL && funs_to_yield_no_auto.head == NULL && funs_to_yield_frec.head == NULL){
+ fprintf(stderr, "ERROR: Expected at least one \"(-f|-frec|-fnoauto) function_name\" argument\n\n");
+ print_help_text_and_exit(argv[0], 1);
+ }
+ if(i >= argc){
+ fprintf(stderr, "ERROR: Expected an input file name as the last parameter\n\n");
+ print_help_text_and_exit(argv[0], 1);
+ }
+ {
+ char* file_name = (char*)argv[i];
+ char* src = file_to_str(file_name);
+ ycf_string_item* current_fun = funs_to_yield.head;
+ ycf_node* header_file = ycf_node_from_string("");
+ ycf_node* tree = ycf_node_from_string(src);
+ ycf_node* only_yielding_funs_tree = NULL;
+ while(current_fun != NULL){
+ tree = ast_get_ast_with_yieldified_function(tree,
+ header_file,
+ current_fun->str,
+ &all_yield_funs,
+ true,
+ false,
+ debug,
+ only_yielding_funs,
+ &only_yielding_funs_tree,
+ static_aux_funs);
+ current_fun = current_fun->next;
+ }
+ current_fun = funs_to_yield_no_auto.head;
+ while(current_fun != NULL){
+ tree = ast_get_ast_with_yieldified_function(tree,
+ header_file,
+ current_fun->str,
+ &all_yield_funs,
+ false,
+ false,
+ debug,
+ only_yielding_funs,
+ &only_yielding_funs_tree,
+ static_aux_funs);
+ current_fun = current_fun->next;
+ }
+ current_fun = funs_to_yield_frec.head;
+ while(current_fun != NULL){
+ tree = ast_get_ast_with_yieldified_function(tree,
+ header_file,
+ current_fun->str,
+ &all_yield_funs,
+ true,
+ true,
+ debug,
+ only_yielding_funs,
+ &only_yielding_funs_tree,
+ static_aux_funs);
+ current_fun = current_fun->next;
+ }
+ ast_add_yield_code_generated_define(tree, debug);
+ ast_add_yield_code_generated_define(header_file, debug);
+ if(only_yielding_funs){
+ ast_add_yield_code_generated_define(only_yielding_funs_tree, debug);
+ }
+ if(header_file_name != NULL){
+ ycf_string_printable_buffer* b = ycf_string_printable_buffer_new();
+ ycf_node_print(header_file, b);
+ str_to_file(header_file_name, b->buffer);
+ }
+ if(only_yielding_funs){
+ tree = only_yielding_funs_tree;
+ }
+ if(output_file_name != NULL){
+ ycf_string_printable_buffer* b = ycf_string_printable_buffer_new();
+ ycf_node_print(tree, b);
+ str_to_file(output_file_name, b->buffer);
+ } else {
+ ycf_node_print(tree, NULL);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+bool is_help_option(char* string) {
+return
+ ycf_string_is_equal("-h", string) ||
+ ycf_string_is_equal("--h", string) ||
+ ycf_string_is_equal("-help", string) ||
+ ycf_string_is_equal("--help", string);
+}
+
+int main( int argc, char* argv[] )
+{
+ bool log_max_mem_usage = false;
+ bool use_gc = false;
+ char * log_max_mem_usage_file = NULL;
+ int i = 1;
+ if (argc == 1) {
+ print_help_text_and_exit(argv[0], 0);
+ }
+ while(i < argc &&
+ (ycf_string_is_equal("-use_gc", argv[i]) ||
+ ycf_string_is_equal("-log_max_mem_usage", argv[i]) ||
+ ycf_string_is_equal("-print_gc_info", argv[i]) ||
+ is_help_option(argv[i]))) {
+ if (is_help_option(argv[i])) {
+ print_help_text_and_exit(argv[0], 0);
+ } else if (ycf_string_is_equal("-use_gc", argv[i])) {
+ use_gc = true;
+ i++;
+ } else if(ycf_string_is_equal("-print_gc_info", argv[i])) {
+ scgc_enable_print_gc_info();
+ i++;
+ } else if(ycf_string_is_equal(argv[i], "-log_max_mem_usage")) {
+ ycf_enable_memory_tracking();
+ log_max_mem_usage = true;
+ i++;
+ log_max_mem_usage_file = (char*)argv[i];
+ i++;
+ }
+ }
+ int nr_of_params_to_remove = i - 1;
+ int ret;
+ if (!use_gc) {
+ ret = ycf_main(argc - nr_of_params_to_remove ,
+ argv + nr_of_params_to_remove);
+ } else {
+ ycf_enable_gc();
+ ret = scgc_start_gced_code(ycf_main,
+ argc - nr_of_params_to_remove,
+ argv + nr_of_params_to_remove,
+ ycf_raw_malloc,
+ ycf_free);
+ }
+ if(log_max_mem_usage){
+ ycf_malloc_log(log_max_mem_usage_file, "all");
+ }
+ return ret;
+}
diff --git a/erts/lib_src/yielding_c_fun/ycf_node.c b/erts/lib_src/yielding_c_fun/ycf_node.c
new file mode 100644
index 0000000000..517c7e991d
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_node.c
@@ -0,0 +1,915 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include "ycf_symbol.h"
+#include "ycf_node.h"
+#include "ycf_string.h"
+#include "ycf_lists.h"
+#include "ycf_yield_fun.h"
+#include "ycf_parser.h"
+
+GENERATE_LIST_FUNCTIONS(ycf_node)
+
+#define YCF_UNIQ_VARIABLE_NAME_NUMBER_PREFIX "_N"
+
+static int ycf_uniq_variable_name_number_counter = 0;
+
+static void uniqify_local_vars_in_node_rename_var_helper(ycf_node* n,
+ char* old_name,
+ char* new_name);
+static void uniqify_local_vars_in_scope_rename_var_helper(ycf_node_code_scope* s,
+ char* old_name,
+ char* new_name);
+
+
+static void uniqify_local_vars_in_expression_rename_var_helper(ycf_node_expression* n,
+ char* old_name,
+ char* new_name){
+ ycf_node* current = n->content.head;
+ while(current != NULL){
+ uniqify_local_vars_in_node_rename_var_helper(current,
+ old_name,
+ new_name);
+ current = current->next;
+ }
+}
+
+static void uniqify_local_vars_in_paran_expression_rename_var_helper(ycf_node_parentheses_expression* n,
+ char* old_name,
+ char* new_name){
+ uniqify_local_vars_in_expression_rename_var_helper(&n->content,
+ old_name,
+ new_name);
+}
+
+static void uniqify_local_vars_in_fun_call_rename_var_helper(ycf_node_function_call* n,
+ char* old_name,
+ char* new_name)
+{
+ ycf_node* e = n->parameter_expressions.head;
+ while(e != NULL){
+ uniqify_local_vars_in_node_rename_var_helper(e, old_name, new_name);
+ e = e->next;
+ }
+ if(ycf_symbol_is_text_eq(n->identifier, old_name)){
+ n->identifier = ycf_symbol_copy_change_text(n->identifier, new_name);
+ }
+}
+
+static void uniqify_local_vars_in_assignment_fun_call_rename_var_helper(ycf_node_function_call_assignment* n,
+ char* old_name,
+ char* new_name)
+{
+ uniqify_local_vars_in_expression_rename_var_helper(&n->left_side, old_name, new_name);
+ uniqify_local_vars_in_fun_call_rename_var_helper(&n->fun_call, old_name, new_name);
+}
+
+static void uniqify_local_vars_in_node_rename_var_helper(ycf_node* n,
+ char* old_name,
+ char* new_name){
+ if(n == NULL){
+ return;
+ } else if(n->type == ycf_node_type_code_scope){
+ uniqify_local_vars_in_scope_rename_var_helper(&n->u.code_scope, old_name, new_name);
+ } else if(n->type == ycf_node_type_on_restore_yield_state_code ||
+ n->type == ycf_node_type_on_save_yield_state_code ||
+ n->type == ycf_node_type_on_destroy_state_code ||
+ n->type == ycf_node_type_on_return_code ||
+ n->type == ycf_node_type_on_destroy_state_or_return_code){
+ uniqify_local_vars_in_scope_rename_var_helper(&n->u.special_code_block.code.if_statement->u.code_scope, old_name, new_name);
+ } else if(n->type == ycf_node_type_other &&
+ n->u.other.what->type == ycf_symbol_type_identifier &&
+ ycf_symbol_is_text_eq(n->u.other.what, old_name)){
+ n->u.other.what =
+ ycf_symbol_copy_change_text(n->u.other.what, new_name);
+ } else if(n->type == ycf_node_type_assignment){
+ uniqify_local_vars_in_expression_rename_var_helper(&n->u.a.left_side, old_name, new_name);
+ uniqify_local_vars_in_expression_rename_var_helper(&n->u.a.right_side, old_name, new_name);
+ } else if (n->type == ycf_node_type_function_call){
+ uniqify_local_vars_in_fun_call_rename_var_helper(&n->u.function_call, old_name, new_name);
+ } else if (n->type == ycf_node_type_statement){
+ ycf_node* e = n->u.statement.expression;
+ uniqify_local_vars_in_node_rename_var_helper(e, old_name, new_name);
+ } else if (n->type == ycf_node_type_return_statement && n->u.return_n.return_expression != NULL){
+ ycf_node* e = n->u.return_n.return_expression;
+ uniqify_local_vars_in_node_rename_var_helper(e, old_name, new_name);
+ } else if (n->type == ycf_node_type_expression){
+ uniqify_local_vars_in_expression_rename_var_helper(&n->u.expression, old_name, new_name);
+ } else if (n->type == ycf_node_type_assignment_function_call){
+ uniqify_local_vars_in_assignment_fun_call_rename_var_helper(&n->u.function_call_assignment, old_name, new_name);
+ }else if (n->type == ycf_node_type_parentheses_expression){
+ uniqify_local_vars_in_paran_expression_rename_var_helper(&n->u.parentheses_expression, old_name, new_name);
+ } else if (n->type == ycf_node_type_if){
+ uniqify_local_vars_in_paran_expression_rename_var_helper(&n->u.if_n.expression, old_name, new_name);
+ uniqify_local_vars_in_node_rename_var_helper(n->u.if_n.if_statement, old_name, new_name);
+ } else if (n->type == ycf_node_type_if_else){
+ uniqify_local_vars_in_paran_expression_rename_var_helper(&n->u.if_else.if_part.expression, old_name, new_name);
+ uniqify_local_vars_in_node_rename_var_helper(n->u.if_else.if_part.if_statement, old_name, new_name);
+ uniqify_local_vars_in_node_rename_var_helper(n->u.if_else.else_statement, old_name, new_name);
+ } else if (n->type == ycf_node_type_while){
+ uniqify_local_vars_in_paran_expression_rename_var_helper(&n->u.while_n.expression, old_name, new_name);
+ uniqify_local_vars_in_node_rename_var_helper(n->u.while_n.statement, old_name, new_name);
+ } else if (n->type == ycf_node_type_do_while){
+ uniqify_local_vars_in_paran_expression_rename_var_helper(&n->u.do_while.expression, old_name, new_name);
+ uniqify_local_vars_in_node_rename_var_helper(n->u.do_while.statement, old_name, new_name);
+ } else if (n->type == ycf_node_type_switch){
+ uniqify_local_vars_in_paran_expression_rename_var_helper(&n->u.switch_n.expression, old_name, new_name);
+ uniqify_local_vars_in_scope_rename_var_helper(&n->u.switch_n.scope, old_name, new_name);
+ } else if (n->type == ycf_node_type_for){
+ uniqify_local_vars_in_node_rename_var_helper(n->u.for_n.init, old_name, new_name);
+ uniqify_local_vars_in_node_rename_var_helper(n->u.for_n.stop_cond, old_name, new_name);
+ uniqify_local_vars_in_node_rename_var_helper(n->u.for_n.end_exp, old_name, new_name);
+ uniqify_local_vars_in_node_rename_var_helper(n->u.for_n.statement, old_name, new_name);
+ }
+}
+
+static void uniqify_local_vars_in_scope_rename_var_helper(ycf_node_code_scope* s,
+ char* old_name,
+ char* new_name){
+ ycf_node* current = s->other_nodes.head;
+ while(current != NULL){
+ uniqify_local_vars_in_node_rename_var_helper(current, old_name, new_name);
+ current = current->next;
+ }
+}
+
+
+
+static void uniqify_definitions_in_scope(ycf_node_list* definition_nodes, ycf_node_code_scope* s){
+ ycf_node* current = definition_nodes->head;
+ while(current != NULL){
+ ycf_symbol * old_symbol = NULL;
+ if(current->type == ycf_node_type_variable_definition){
+ old_symbol = current->u.definition.identifier;
+ }else if(current->type == ycf_node_type_variable_definition_init){
+ old_symbol = current->u.definition_init.definition.identifier;
+ }
+ ycf_uniq_variable_name_number_counter++;
+ {
+ char* old_name = ycf_symbol_get_text(old_symbol);
+ char* new_name = ycf_string_new("%s%s%lu", old_name, YCF_UNIQ_VARIABLE_NAME_NUMBER_PREFIX,
+ ycf_uniq_variable_name_number_counter);
+ if(current->type == ycf_node_type_variable_definition){
+ current->u.definition.identifier = ycf_symbol_copy_change_text(old_symbol, new_name);
+ }else if(current->type == ycf_node_type_variable_definition_init){
+ current->u.definition_init.definition.identifier = ycf_symbol_copy_change_text(old_symbol, new_name);
+ }
+ uniqify_local_vars_in_scope_rename_var_helper(s, old_name, new_name);
+ current = current->next;
+ }
+ }
+}
+
+static void uniqify_local_vars_in_scope(ycf_node_code_scope* s);
+
+static void uniqify_local_vars_in_node(ycf_node* n){
+ if(n->type == ycf_node_type_code_scope){
+ uniqify_local_vars_in_scope(&n->u.code_scope);
+ } else if(n->type == ycf_node_type_on_restore_yield_state_code){
+ uniqify_local_vars_in_scope(&n->u.special_code_block.code.if_statement->u.code_scope);
+ } else if(n->type == ycf_node_type_on_save_yield_state_code ||
+ n->type == ycf_node_type_on_save_yield_state_code ||
+ n->type == ycf_node_type_on_destroy_state_code ||
+ n->type == ycf_node_type_on_return_code ||
+ n->type == ycf_node_type_on_destroy_state_or_return_code){
+ uniqify_local_vars_in_scope(&n->u.special_code_block.code.if_statement->u.code_scope);
+ } else if (n->type == ycf_node_type_if){
+ uniqify_local_vars_in_node(n->u.if_n.if_statement);
+ } else if (n->type == ycf_node_type_if_else){
+ uniqify_local_vars_in_node(n->u.if_else.if_part.if_statement);
+ uniqify_local_vars_in_node(n->u.if_else.else_statement);
+ } else if (n->type == ycf_node_type_while){
+ uniqify_local_vars_in_node(n->u.while_n.statement);
+ } else if (n->type == ycf_node_type_do_while){
+ uniqify_local_vars_in_node(n->u.do_while.statement);
+ } else if (n->type == ycf_node_type_switch){
+ uniqify_local_vars_in_scope(&n->u.switch_n.scope);
+ } else if (n->type == ycf_node_type_for){
+ uniqify_local_vars_in_node(n->u.for_n.statement);
+ }
+}
+
+static void uniqify_local_vars_in_scope(ycf_node_code_scope* s){
+ ycf_node* current = s->other_nodes.head;
+ while(current != NULL){
+ uniqify_local_vars_in_node(current);
+ current = current->next;
+ }
+ uniqify_definitions_in_scope(&s->definition_nodes, s);
+}
+
+void ycf_uniqify_local_vars_in_function(ycf_node* function){
+ uniqify_local_vars_in_scope(&function->u.function.body);
+ uniqify_definitions_in_scope(&function->u.function.definition.parameters,
+ &function->u.function.body);
+}
+
+
+ycf_node* ycf_node_new_assignment_from_definition_init(ycf_node_definition_init* n){
+ ycf_node* res = ycf_malloc(sizeof(ycf_node));
+ res->type = ycf_node_type_assignment;
+ res->next = NULL;
+ res->u.a.assignment_symbol = n->definition.end;
+ {
+ ycf_symbol* ident = ycf_symbol_copy(n->definition.identifier);
+ ident->whitespace_or_comment_before =
+ n->definition.type_specifiers.head->whitespace_or_comment_before;
+ res->u.a.left_side = parse_expression(ident).result->u.expression;
+ }
+ res->u.a.right_side = parse_expression(n->initializer_expression.head).result->u.expression;
+ res->u.a.end = ycf_symbol_new_semicolon();
+ return res;
+}
+
+ycf_node* ycf_node_new_definition_from_definition_init(ycf_node_definition_init* n){
+ ycf_node* res = ycf_malloc(sizeof(ycf_node));
+ res->type = ycf_node_type_variable_definition;
+ res->next = NULL;
+ res->u.definition = n->definition;
+ res->u.definition.end = ycf_symbol_new_semicolon();
+ return res;
+}
+
+ycf_node* ycf_node_code_scope_shallow_copy(ycf_node* n){
+ ycf_node* new_scope = ycf_node_shallow_copy(n);
+ new_scope->u.code_scope.definition_nodes = ycf_node_list_shallow_copy(new_scope->u.code_scope.definition_nodes);
+ new_scope->u.code_scope.other_nodes = ycf_node_list_shallow_copy(new_scope->u.code_scope.other_nodes);
+ return new_scope;
+}
+
+
+void ycf_node_search_and_replace_statements_in_node(ycf_node* n, ycf_node* (*replacer) (ycf_node*,
+ ycf_node_code_scope*,
+ void*), void* context,
+ ycf_node_code_scope* s){
+ if(n->type == ycf_node_type_code_scope){
+ ycf_node_search_and_replace_statements_in_scope(&n->u.code_scope, replacer, context);
+ } else if(n->type == ycf_node_type_on_destroy_state_code ||
+ n->type == ycf_node_type_on_restore_yield_state_code ||
+ n->type == ycf_node_type_on_save_yield_state_code ||
+ n->type == ycf_node_type_on_destroy_state_or_return_code ||
+ n->type == ycf_node_type_on_return_code) {
+ ycf_node* replace_candidate = n->u.special_code_block.code.if_statement;
+ ycf_node* possible_replacement = replacer(replace_candidate, s, context);
+ if(replace_candidate != possible_replacement){
+ n->u.special_code_block.code.if_statement = possible_replacement;
+ } else {
+ ycf_node_search_and_replace_statements_in_node(n->u.special_code_block.code.if_statement,
+ replacer,
+ context,
+ s);
+ }
+ } else if (n->type == ycf_node_type_if){
+ ycf_node_search_and_replace_statements_in_node(n->u.if_n.if_statement, replacer, context, s);
+ } else if (n->type == ycf_node_type_if_else){
+ ycf_node_search_and_replace_statements_in_node(n->u.if_else.if_part.if_statement, replacer, context, s);
+ ycf_node_search_and_replace_statements_in_node(n->u.if_else.else_statement, replacer, context, s);
+ } else if (n->type == ycf_node_type_while){
+ ycf_node_search_and_replace_statements_in_node(n->u.while_n.statement, replacer, context, s);
+ } else if (n->type == ycf_node_type_do_while){
+ ycf_node_search_and_replace_statements_in_node(n->u.do_while.statement, replacer, context, s);
+ } else if (n->type == ycf_node_type_switch){
+ ycf_node_search_and_replace_statements_in_scope(&n->u.switch_n.scope, replacer, context);
+ } else if (n->type == ycf_node_type_for){
+ ycf_node_search_and_replace_statements_in_node(n->u.for_n.statement, replacer, context, s);
+ }
+}
+
+void ycf_node_search_and_replace_statements_in_scope(ycf_node_code_scope* s,
+ ycf_node* (*replacer) (ycf_node* canditate,
+ ycf_node_code_scope* candidates_scope,
+ void* context),
+ void* context){
+ ycf_node* n = s->other_nodes.head;
+ while(n != NULL){
+ ycf_node* replace_candidate = n;
+ ycf_node* possible_replacement = replacer(replace_candidate, s, context);
+ if(replace_candidate != possible_replacement){
+ ycf_node_list_replace(&s->other_nodes, replace_candidate, possible_replacement);
+ } else {
+ ycf_node_search_and_replace_statements_in_node(n, replacer, context, s);
+ }
+ n = n->next;
+ }
+ return;
+}
+
+ycf_node_code_scope ycf_node_copy_search_and_replace_statements_in_scope(ycf_node_code_scope s,
+ ycf_node* (*replacer) (ycf_node*,
+ ycf_node_code_scope*,
+ void*),
+ void* context){
+ ycf_node_search_and_replace_statements_in_scope(&s, replacer, context);
+ return s;
+}
+
+char* ycf_node_to_string(ycf_node* n){
+ ycf_string_printable_buffer* b = ycf_string_printable_buffer_new();
+ ycf_node_print(n, b);
+ return b->buffer;
+}
+
+char* ycf_node_list_to_string(ycf_node_list* l){
+ ycf_string_printable_buffer* b = ycf_string_printable_buffer_new();
+ ycf_node* current = l->head;
+ while(current != NULL){
+ ycf_node_print(current, b);
+ current = current->next;
+ }
+ return b->buffer;
+}
+
+ycf_node* ycf_node_new_text_node(char* text){
+ ycf_node* n = ycf_node_new();
+ n->type = ycf_node_type_other;
+ n->u.other.what = ycf_symbol_new_something_else(text);
+ return n;
+}
+
+ycf_node* ycf_node_new(){
+ ycf_node* new = ycf_malloc(sizeof(ycf_node));
+ new->next = NULL;
+ return new;
+}
+
+ycf_node* ycf_node_find_function(ycf_node* c_file_node, char* fun_name){
+ ycf_node* current = c_file_node->u.c_file.content.head;
+ while(current != NULL){
+ if(current->type == ycf_node_type_function_definition &&
+ ycf_symbol_is_text_eq(current->u.function.definition.definition.identifier,
+ fun_name)){
+ return current;
+ }
+ current = current->next;
+ }
+ return NULL;
+}
+
+ycf_node* ycf_node_find_function_declaration(ycf_node* c_file_node, char* fun_name){
+ ycf_node* current = c_file_node->u.c_file.content.head;
+ while(current != NULL){
+ if(current->type == ycf_node_type_function_declaration &&
+ ycf_symbol_is_text_eq(current->u.function_definition.definition.identifier,
+ fun_name)){
+ return current;
+ }
+ current = current->next;
+ }
+ return NULL;
+}
+
+ycf_node* ycf_node_find_define_node(ycf_node* c_file_node, char* define_name){
+ ycf_node* current = c_file_node->u.c_file.content.head;
+ while(current != NULL){
+ if(current->type == ycf_node_type_other &&
+ ycf_symbol_is_text_eq(current->u.other.what,
+ ycf_string_new("#define %s", define_name))){
+ return current;
+ }
+ current = current->next;
+ }
+ return NULL;
+}
+
+bool ycf_node_is_void_ret_fun(ycf_node* f_node){
+ ycf_symbol_list type = f_node->u.function.definition.definition.type_specifiers;
+ return type.last->type == ycf_symbol_type_void;
+}
+
+ycf_node* ycf_node_get_from_code_scope_text(char* code){
+ char* f_code = ycf_string_new("void f(){\n"
+ "{\n"
+ "%s\n"
+ "}\n"
+ "}\n",
+ code);
+ ycf_symbol_list symbols = ycf_symbol_list_from_text(f_code);
+ ycf_node* tree = get_abstract_syntax_tree_root(&symbols);
+ if(tree->u.c_file.content.head->type != ycf_node_type_function_definition){
+ printf("NOT A FUNCTION\n");
+ exit(1);
+ }
+ return tree->u.c_file.content.head->u.function.body.other_nodes.head;
+}
+
+ycf_node* ycf_node_get_function_from_text(char* code){
+ char* f_code = ycf_string_new("%s",
+ code);
+ ycf_symbol_list symbols = ycf_symbol_list_from_text(f_code);
+ ycf_node* tree = get_abstract_syntax_tree_root(&symbols);
+ if(tree->u.c_file.content.head->type != ycf_node_type_function_definition){
+ printf("ycf_get_function_from_text: NOT A FUNCTION\n");
+ exit(1);
+ }
+ return tree->u.c_file.content.head;
+}
+
+ycf_node* ycf_node_from_string(char* src){
+ ycf_symbol_list symbols = ycf_symbol_list_from_text(src);
+ ycf_node* tree = get_abstract_syntax_tree_root(&symbols);
+ return tree;
+}
+
+ycf_node* mk_scope_wrapper(ycf_node* statement){
+ return ycf_node_get_from_code_scope_text(ycf_string_new("{\n"
+ " %s\n"
+ "}\n",
+ ycf_node_to_string(statement)));
+}
+
+static ycf_node* scope_inserter(ycf_node* candidate, ycf_node_code_scope* s, void* context){
+ (void)context;
+ (void)s;
+ if (candidate->type == ycf_node_type_while){
+ if(candidate->u.while_n.statement->type == ycf_node_type_code_scope){
+ ycf_node_insert_scopes_in_complex_statements(&candidate->u.while_n.statement->u.code_scope);
+ }
+ candidate->u.while_n.statement = mk_scope_wrapper(candidate->u.while_n.statement);
+ return candidate;
+ } else if (candidate->type == ycf_node_type_do_while){
+ if(candidate->u.do_while.statement->type == ycf_node_type_code_scope){
+ ycf_node_insert_scopes_in_complex_statements(&candidate->u.do_while.statement->u.code_scope);
+ }
+ candidate->u.do_while.statement = mk_scope_wrapper(candidate->u.do_while.statement);
+ return candidate;
+ } else if (candidate->type == ycf_node_type_for){
+ if(candidate->u.for_n.statement->type == ycf_node_type_code_scope){
+ ycf_node_insert_scopes_in_complex_statements(&candidate->u.for_n.statement->u.code_scope);
+ }
+ candidate->u.for_n.statement = mk_scope_wrapper(candidate->u.for_n.statement);
+ return candidate;
+ } else if (candidate->type == ycf_node_type_if){
+ if(candidate->u.if_n.if_statement->type == ycf_node_type_code_scope){
+ ycf_node_insert_scopes_in_complex_statements(&candidate->u.if_n.if_statement->u.code_scope);
+ }
+ candidate->u.if_n.if_statement = mk_scope_wrapper(candidate->u.if_n.if_statement);
+ return candidate;
+ } else if (candidate->type == ycf_node_type_if_else){
+ if(candidate->u.if_else.if_part.if_statement->type == ycf_node_type_code_scope){
+ ycf_node_insert_scopes_in_complex_statements(&candidate->u.if_else.if_part.if_statement->u.code_scope);
+ }
+ candidate->u.if_else.if_part.if_statement = mk_scope_wrapper(candidate->u.if_else.if_part.if_statement);
+ if(candidate->u.if_else.else_statement->type == ycf_node_type_code_scope){
+ ycf_node_insert_scopes_in_complex_statements(&candidate->u.if_else.else_statement->u.code_scope);
+ }
+ candidate->u.if_else.else_statement = mk_scope_wrapper(candidate->u.if_else.else_statement);
+ return candidate;
+ } else {
+ return candidate;
+ }
+}
+
+void ycf_node_insert_scopes_in_complex_statements(ycf_node_code_scope* s){
+ ycf_node_search_and_replace_statements_in_scope(s,
+ scope_inserter,
+ NULL);
+}
+
+
+static ycf_node* scope_remover(ycf_node* candidate, ycf_node_code_scope* s, void* context){
+ uintptr_t* removed_scopes = context;
+ if(candidate->type == ycf_node_type_code_scope &&
+ ycf_node_list_length(candidate->u.code_scope.definition_nodes) == 0 &&
+ ycf_node_list_length(candidate->u.code_scope.other_nodes) == 1 &&
+ candidate->u.code_scope.other_nodes.head->type == ycf_node_type_code_scope) {
+ *removed_scopes = *removed_scopes + 1;
+ return candidate->u.code_scope.other_nodes.head;
+ }
+ return candidate;
+}
+
+void ycf_node_remove_unecessary_scopes(ycf_node_code_scope* s){
+ uintptr_t removed_scopes;
+ do{
+ removed_scopes = 0;
+ ycf_node_search_and_replace_statements_in_scope(s,
+ scope_remover,
+ &removed_scopes);
+ }while(removed_scopes > 0);
+}
+
+static ycf_node* for_declaration_normalizer(ycf_node* candidate,
+ ycf_node_code_scope* s,
+ void* context){
+ (void)context;
+ (void)s;
+ if (candidate->type == ycf_node_type_for){
+ if(candidate->u.for_n.init != NULL &&
+ (candidate->u.for_n.init->type == ycf_node_type_variable_definition ||
+ candidate->u.for_n.init->type == ycf_node_type_variable_definition_init)){
+ if(candidate->u.for_n.statement->type == ycf_node_type_code_scope){
+ ycf_node_normalize_for_var_declarations(&candidate->u.for_n.statement->u.code_scope);
+ }
+ ycf_string_printable_buffer * b = ycf_string_printable_buffer_new();
+ ycf_node_print(candidate->u.for_n.init, b);
+ candidate->u.for_n.init = ycf_node_statement_new(ycf_node_expression_new(ycf_node_list_empty()),
+ ycf_symbol_new_semicolon());
+ ycf_node_print(candidate, b);
+ return ycf_node_get_from_code_scope_text(b->buffer);
+ }else {
+ return candidate;
+ }
+
+ } else {
+ return candidate;
+ }
+}
+
+void ycf_node_normalize_for_var_declarations(ycf_node_code_scope* s){
+ ycf_node_search_and_replace_statements_in_scope(s,
+ for_declaration_normalizer,
+ NULL);
+}
+
+
+
+static ycf_node* ycf_node_move_in_code_var_declarations_to_top_mover(ycf_node* candidate,
+ ycf_node_code_scope* candidates_scope,
+ void* context){
+ (void)context;
+ if (candidate->type == ycf_node_type_variable_definition) {
+ ycf_node_list_append(&candidates_scope->definition_nodes,
+ ycf_node_shallow_copy(candidate));
+ return ycf_node_get_from_code_scope_text(ycf_string_new("\n/* moved declaration (%s)*/\n",
+ ycf_symbol_get_text(candidate->u.definition.identifier)));
+ } else if (candidate->type == ycf_node_type_variable_definition_init) {
+ ycf_node_list_append(&candidates_scope->definition_nodes,
+ ycf_node_new_definition_from_definition_init(&candidate->u.definition_init));
+ return ycf_node_new_assignment_from_definition_init(&candidate->u.definition_init);
+ } else {
+ return candidate;
+ }
+}
+
+void ycf_node_move_in_code_var_declarations_to_top(ycf_node_code_scope* s){
+ ycf_node_search_and_replace_statements_in_scope(s,
+ ycf_node_move_in_code_var_declarations_to_top_mover,
+ NULL);
+}
+
+static ycf_node* ycf_node_remove_declarations_in_scope_helper(ycf_node* candidate,
+ ycf_node_code_scope* candidates_scope,
+ void* context){
+ (void)context;
+ candidates_scope->definition_nodes = ycf_node_list_empty();
+ return candidate;
+}
+
+void ycf_node_remove_declarations_in_scope(ycf_node_code_scope* s){
+ ycf_node_search_and_replace_statements_in_scope(s,
+ ycf_node_remove_declarations_in_scope_helper,
+ NULL);
+}
+
+static void normalize_init_definitions_in_scope_helper_insert_assignments(ycf_node* current_def,
+ ycf_node_list* other_nodes){
+ if(current_def == NULL){
+ return;
+ }
+ normalize_init_definitions_in_scope_helper_insert_assignments(current_def->next,
+ other_nodes);
+ if(current_def->type == ycf_node_type_variable_definition_init){
+ ycf_node* assignment = ycf_node_new_assignment_from_definition_init(&current_def->u.definition_init);
+ ycf_node_list_prepend(other_nodes, assignment);
+ return;
+ }else{
+ return;
+ }
+}
+
+static ycf_node* ycf_node_normalize_init_definitions_in_scope_helper(ycf_node* candidate,
+ ycf_node_code_scope* candidates_scope,
+ void* context){
+ (void)context;
+ (void)candidates_scope;
+ ycf_node_code_scope* s = NULL;
+ if(candidate == NULL){
+ s = context;
+ } else if(candidate->type == ycf_node_type_code_scope){
+ s = &candidate->u.code_scope;
+
+ } else if(candidate->type == ycf_node_type_switch){
+ s = &candidate->u.switch_n.scope;
+ }
+ if(s != NULL) {
+ ycf_node * current = s->definition_nodes.head;
+ normalize_init_definitions_in_scope_helper_insert_assignments(s->definition_nodes.head,
+ &s->other_nodes);
+ while (current != NULL) {
+ if (current->type == ycf_node_type_variable_definition_init) {
+ ycf_node *def_node = ycf_node_new_definition_from_definition_init(&current->u.definition_init);
+ ycf_node_list_replace(&s->definition_nodes, current, def_node);
+ }
+ current = current->next;
+ }
+ }
+ return candidate;
+}
+
+void ycf_node_normalize_init_definitions_in_scope(ycf_node_code_scope* s){
+ ycf_node_normalize_init_definitions_in_scope_helper(NULL,
+ NULL,
+ s);
+ ycf_node_search_and_replace_statements_in_scope(s,
+ ycf_node_normalize_init_definitions_in_scope_helper,
+ NULL);
+}
+
+
+static ycf_node* ycf_node_get_declarations_in_scope_helper(ycf_node* candidate,
+ ycf_node_code_scope* candidates_scope,
+ void* context){
+ ycf_node_list* list = (ycf_node_list*)context;
+ if(candidate->type == ycf_node_type_code_scope){
+ ycf_node_list to_append = ycf_node_list_shallow_copy(candidate->u.code_scope.definition_nodes);
+ ycf_node_list_concat(list, &to_append);
+ } else if(candidate->type == ycf_node_type_switch){
+ ycf_node_list to_append = ycf_node_list_shallow_copy(candidate->u.switch_n.scope.definition_nodes);
+ ycf_node_list_concat(list, &to_append);
+ }
+ return candidate;
+}
+
+ycf_node_list ycf_node_get_declarations_in_scope(ycf_node_code_scope* s){
+ ycf_node_list res = s->definition_nodes;
+ ycf_node_search_and_replace_statements_in_scope(s,
+ ycf_node_get_declarations_in_scope_helper,
+ &res);
+ return res;
+}
+
+ycf_node_list ycf_node_definition_list_from_string(char* str){
+ return ycf_node_get_from_code_scope_text(str)->u.code_scope.definition_nodes;
+}
+
+void ycf_node_rename_function(ycf_node_function* f, char* new_name){
+ f->definition.definition.identifier =
+ ycf_symbol_copy_change_text(f->definition.definition.identifier,
+ new_name);
+}
+
+static bool is_scope_ends_with_void_ret(ycf_node_code_scope* scope_node){
+ if(scope_node->other_nodes.last->type == ycf_node_type_code_scope){
+ return is_scope_ends_with_void_ret(&scope_node->other_nodes.last->u.code_scope);
+ }else{
+ ycf_node* prev = NULL;
+ ycf_node* current = scope_node->other_nodes.head;
+ while(current != NULL){
+ prev = current;
+ current = current->next;
+ }
+ return
+ prev != NULL &&
+ current != NULL &&
+ prev->type == ycf_node_type_other &&
+ prev->u.other.what->type == ycf_symbol_type_return &&
+ current->type == ycf_node_type_other &&
+ current->u.other.what->type == ycf_symbol_type_semicolon;
+ }
+}
+
+bool ycf_node_is_void_ret_ending_fun(ycf_node* f_node){
+ return is_scope_ends_with_void_ret(&f_node->u.function.body);
+}
+
+ycf_node_list ycf_node_get_all_definitions_in_function(ycf_node_function* f){
+ ycf_node_list all = ycf_node_get_declarations_in_scope(&f->body);
+ ycf_node_list all_copy = ycf_node_list_shallow_copy(all);
+ ycf_node_list params = f->definition.parameters;
+ ycf_node_list_concat(&all_copy, &params);
+ return all_copy;
+}
+
+void ycf_node_remove_const_specifiers_from_declaration(ycf_node* declaration){
+ ycf_symbol* current = declaration->u.definition.type_specifiers.head;
+ while(current != NULL) {
+ if(current->type == ycf_symbol_type_const){
+ ycf_symbol_list_remove(&declaration->u.definition.type_specifiers,
+ current);
+ }
+ current = current->next;
+ }
+}
+
+void ycf_node_remove_static_specifiers_from_declaration(ycf_node* declaration){
+ ycf_symbol* current = declaration->u.definition.type_specifiers.head;
+ while(current != NULL) {
+ if(current->type == ycf_symbol_type_static){
+ ycf_symbol_list_remove(&declaration->u.definition.type_specifiers,
+ current);
+ }
+ current = current->next;
+ }
+}
+
+void ycf_node_remove_inline_specifiers_from_declaration(ycf_node* declaration){
+ ycf_symbol* current = declaration->u.definition.type_specifiers.head;
+ while(current != NULL) {
+ if(current->type == ycf_symbol_type_inline){
+ ycf_symbol_list_remove(&declaration->u.definition.type_specifiers,
+ current);
+ }
+ current = current->next;
+ }
+}
+
+void ycf_node_remove_array_size_info_from_declaration(ycf_node* declaration){
+ ycf_node* current = declaration->u.definition.array_brackets.head;
+ while(current != NULL) {
+ current->u.array_bracket.empty = true;
+ current = current->next;
+ }
+}
+
+void ycf_node_modify_declarations(ycf_node_list declarations, void (*modifer)(ycf_node*)){
+ ycf_node* current = declarations.head;
+ while(current != NULL) {
+ modifer(current);
+ current = current->next;
+ }
+}
+
+ycf_symbol_list ycf_node_get_return_type(ycf_node* f_node_or_f_dec_node){
+ ycf_node_function_definition def;
+ ycf_node * tmp;
+ if (f_node_or_f_dec_node->type == ycf_node_type_function_declaration) {
+ def = f_node_or_f_dec_node->u.function_definition;
+ } else {
+ def = f_node_or_f_dec_node->u.function.definition;
+ }
+ tmp =
+ ycf_node_defenition_new(ycf_symbol_list_shallow_copy(def.definition.type_specifiers),
+ ycf_symbol_new_identifier(ycf_string_new("tmp")),
+ ycf_node_list_empty(),
+ ycf_symbol_new_semicolon());
+ ycf_node_remove_inline_specifiers_from_declaration(tmp);
+ ycf_node_remove_static_specifiers_from_declaration(tmp);
+ return tmp->u.definition.type_specifiers;
+}
+
+ycf_symbol_list ycf_node_find_function_return_type(ycf_node* c_file_node, char* fun_name)
+{
+ ycf_node* f_node_or_f_dec_node = ycf_node_find_function(c_file_node, fun_name);
+ if (f_node_or_f_dec_node == NULL) {
+ f_node_or_f_dec_node = ycf_node_find_function_declaration(c_file_node, fun_name);
+ }
+ if (f_node_or_f_dec_node == NULL) {
+ fprintf(stderr, "ycf_node_find_function_return_type: Could not find function declaration or definition: %s\n", fun_name);
+ exit(1);
+ }
+ return ycf_node_get_return_type(f_node_or_f_dec_node);
+}
+
+char* ycf_node_get_node_type_string(ycf_node_type t){
+ switch (t){
+ case ycf_node_type_c_file:
+ return "ycf_node_type_c_file";
+ case ycf_node_type_variable_definition:
+ return "ycf_node_type_variable_definition";
+ case ycf_node_type_variable_definition_init:
+ return "ycf_node_type_variable_definition_init";
+ case ycf_node_type_function_definition:
+ return "ycf_node_type_function_definition";
+ case ycf_node_type_function_declaration:
+ return "ycf_node_type_function_declaration";
+ case ycf_node_type_code_scope:
+ return "ycf_node_type_code_scope";
+ case ycf_node_type_other:
+ return "ycf_node_type_other";
+ case ycf_node_type_gen_typedef_struct:
+ return "ycf_node_type_gen_typedef_struct";
+ case ycf_node_type_expression:
+ return "ycf_node_type_expression";
+ case ycf_node_type_statement:
+ return "ycf_node_type_statement";
+ case ycf_node_type_return_statement:
+ return "ycf_node_type_return_statement";
+ case ycf_node_type_assignment:
+ return "ycf_node_type_assignment";
+ case ycf_node_type_yield:
+ return "ycf_node_type_yield";
+ case ycf_node_type_consume_reds:
+ return "ycf_node_type_consume_reds";
+ case ycf_node_type_goto:
+ return "ycf_node_type_goto";
+ case ycf_node_type_parentheses_expression:
+ return "ycf_node_type_parentheses_expression";
+ case ycf_node_type_function_call:
+ return "ycf_node_type_function_call";
+ case ycf_node_type_assignment_function_call:
+ return "ycf_node_type_assignment_function_call";
+ case ycf_node_type_while:
+ return "ycf_node_type_while";
+ case ycf_node_type_do_while:
+ return "ycf_node_type_do_while";
+ case ycf_node_type_for:
+ return "ycf_node_type_for";
+ case ycf_node_type_switch:
+ return "ycf_node_type_switch";
+ case ycf_node_type_if:
+ return "ycf_node_type_if";
+ case ycf_node_type_if_else:
+ return "ycf_node_type_if_else";
+ case ycf_node_type_comma:
+ return "ycf_node_type_comma";
+ case ycf_node_type_array_bracket:
+ return "ycf_node_type_array_bracket";
+ case ycf_node_type_macro_cmd:
+ return "ycf_node_type_macro_cmd";
+ case ycf_node_type_period_field_access:
+ return "ycf_node_type_period_field_access";
+ case ycf_node_type_pointer_field_access:
+ return "ycf_node_type_pointer_field_access";
+ case ycf_node_type_on_save_yield_state_code:
+ return "ycf_node_type_on_save_yield_state_code";
+ case ycf_node_type_on_restore_yield_state_code:
+ return "ycf_node_type_on_restore_yield_state_code";
+ case ycf_node_type_on_destroy_state_code:
+ return "ycf_node_type_on_destroy_state_code";
+ case ycf_node_type_on_return_code:
+ return "ycf_node_type_on_return_code";
+ case ycf_node_type_on_destroy_state_or_return_code:
+ return "ycf_node_type_on_destroy_state_or_return_code";
+ }
+ return "unrecognized type";
+}
+
+void ycf_node_print_node_type(ycf_node_type t){
+ printf("%s\n",ycf_node_get_node_type_string(t));
+}
+
+ycf_node_assignment* ycf_node_get_assignment(ycf_node* n) {
+ if(n->type == ycf_node_type_assignment){
+ return &n->u.a;
+ } else if (n->type == ycf_node_type_statement &&
+ n->u.statement.expression->u.expression.content.head->type == ycf_node_type_assignment){
+ return &n->u.statement.expression->u.expression.content.head->u.a;
+ } else {
+ fprintf(stderr,
+ "Trying to get %s from a node of type %s\n",
+ "ycf_node_type_assignment",
+ ycf_node_get_node_type_string(n->type));
+ exit(1);
+ }
+}
+
+void ycf_node_normalize_function(ycf_node* fun){
+ /* Remove array size info from parameters */
+ ycf_node_modify_declarations(fun->u.function.definition.parameters,
+ ycf_node_remove_array_size_info_from_declaration);
+ /* Insert scope in nest statements (e.g., if(1) print(); -> if(1) {print();}) */
+ ycf_node_insert_scopes_in_complex_statements(&fun->u.function.body);
+ /* Move out declarations from for loops */
+ ycf_node_normalize_for_var_declarations(&fun->u.function.body);
+ /* Move in code declations to top of scope */
+ ycf_node_move_in_code_var_declarations_to_top(&fun->u.function.body);
+ /* Normalize declarations */
+ ycf_node_normalize_init_definitions_in_scope(&fun->u.function.body);
+ /* Uniqify local variables */
+ ycf_uniqify_local_vars_in_function(fun);
+ /* Save scope declarations and function declarations (includes
+ parameters) */
+ ycf_node_list scope_defs = ycf_node_get_declarations_in_scope(&fun->u.function.body);
+ scope_defs = ycf_node_list_shallow_copy(scope_defs);
+ ycf_node_list defs = ycf_node_get_all_definitions_in_function(&fun->u.function);
+ defs = ycf_node_list_shallow_copy(defs);
+ /* Remove const from const declarations */
+ ycf_node_modify_declarations(defs, ycf_node_remove_const_specifiers_from_declaration);
+ ycf_node_modify_declarations(scope_defs, ycf_node_remove_const_specifiers_from_declaration);
+ ycf_node_modify_declarations(fun->u.function.definition.parameters,
+ ycf_node_remove_const_specifiers_from_declaration);
+ /* Move all declarations to top scope */
+ ycf_node_remove_declarations_in_scope(&fun->u.function.body);
+ fun->u.function.body.definition_nodes = scope_defs;
+ /* Add return statement if function is missing return statement */
+ if(ycf_node_is_void_ret_fun(fun) &&
+ !ycf_node_is_void_ret_ending_fun(fun)){
+ ycf_node_list_append(&fun->u.function.body.other_nodes,
+ ycf_node_get_from_code_scope_text("return;"));
+ }
+ ycf_node_remove_unecessary_scopes(&fun->u.function.body);
+}
diff --git a/erts/lib_src/yielding_c_fun/ycf_node.h b/erts/lib_src/yielding_c_fun/ycf_node.h
new file mode 100644
index 0000000000..d230a1a2c2
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_node.h
@@ -0,0 +1,455 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+#ifndef YIELDING_C_FUN_YCF_NODE_FUNS_H
+#define YIELDING_C_FUN_YCF_NODE_FUNS_H
+
+#include "ycf_utils.h"
+#include "ycf_string.h"
+#include "ycf_symbol.h"
+
+/* Types for nodes */
+typedef enum {
+ ycf_node_type_c_file,
+ ycf_node_type_variable_definition,
+ ycf_node_type_variable_definition_init,
+ ycf_node_type_function_definition,
+ ycf_node_type_function_declaration,
+ ycf_node_type_code_scope,
+ ycf_node_type_other,
+ ycf_node_type_gen_typedef_struct,
+ ycf_node_type_expression,
+ ycf_node_type_statement,
+ ycf_node_type_return_statement,
+ ycf_node_type_assignment,
+ ycf_node_type_yield,
+ ycf_node_type_consume_reds,
+ ycf_node_type_goto,
+ ycf_node_type_parentheses_expression,
+ ycf_node_type_function_call,
+ ycf_node_type_assignment_function_call,
+ ycf_node_type_while,
+ ycf_node_type_do_while,
+ ycf_node_type_for,
+ ycf_node_type_switch,
+ ycf_node_type_if,
+ ycf_node_type_if_else,
+ ycf_node_type_comma,
+ ycf_node_type_array_bracket,
+ ycf_node_type_macro_cmd,
+ ycf_node_type_period_field_access,
+ ycf_node_type_pointer_field_access,
+ ycf_node_type_on_save_yield_state_code,
+ ycf_node_type_on_restore_yield_state_code,
+ ycf_node_type_on_destroy_state_code,
+ ycf_node_type_on_return_code,
+ ycf_node_type_on_destroy_state_or_return_code
+} ycf_node_type;
+
+struct ycf_node;
+
+typedef struct {
+ struct ycf_node* head;
+ struct ycf_node* last;
+} ycf_node_list;
+
+typedef struct {
+ ycf_node_list content;
+} ycf_node_c_file;
+
+typedef struct {
+ ycf_node_list content;
+ ycf_symbol* end_symbol;
+} ycf_node_expression;
+
+typedef struct {
+ ycf_symbol* start_symbol;
+ ycf_node_expression content;
+ ycf_symbol* end_symbol;
+} ycf_node_parentheses_expression;
+
+typedef struct {
+ ycf_symbol_list neg_symbols;
+ ycf_symbol* identifier;
+ ycf_symbol* start_symbol;
+ ycf_node_list parameter_expressions;
+ ycf_symbol* end_symbol;
+} ycf_node_function_call;
+
+typedef struct array_bracket {
+ ycf_symbol* start;
+ bool empty;
+ ycf_node_expression content;
+ ycf_symbol* end;
+} ycf_node_array_bracket;
+
+
+typedef struct {
+ ycf_symbol_list type_specifiers;
+ ycf_symbol* identifier;
+ ycf_node_list array_brackets;
+ ycf_symbol* end;
+} ycf_node_definition;
+
+typedef struct {
+ ycf_node_definition definition;
+ ycf_symbol_list initializer_expression;
+ ycf_symbol* end;
+} ycf_node_definition_init;
+
+typedef struct {
+ struct ycf_node* expression;
+ ycf_symbol* end_symbol;
+} ycf_node_statement;
+
+typedef struct {
+ ycf_node_list definition_nodes;
+ ycf_node_list other_nodes;
+ ycf_symbol* start;
+ ycf_symbol* end;
+} ycf_node_code_scope;
+
+typedef struct {
+ ycf_symbol* while_word;
+ ycf_node_parentheses_expression expression;
+ struct ycf_node* statement;
+} ycf_node_while;
+
+typedef struct {
+ ycf_symbol* do_word;
+ struct ycf_node* statement;
+ ycf_symbol* while_word;
+ ycf_node_parentheses_expression expression;
+ ycf_symbol* end;
+} ycf_node_do_while;
+
+typedef struct {
+ ycf_symbol* for_word;
+ ycf_symbol* start_parentheses;
+ struct ycf_node* init;
+ struct ycf_node* stop_cond;
+ ycf_symbol* stop_cond_end;
+ struct ycf_node* end_exp;
+ ycf_symbol* end_parentheses;
+ struct ycf_node* statement;
+} ycf_node_for;
+
+typedef struct {
+ ycf_symbol* switch_word;
+ ycf_node_parentheses_expression expression;
+ ycf_node_code_scope scope;
+} ycf_node_switch;
+
+typedef struct {
+ ycf_symbol* if_word;
+ ycf_node_parentheses_expression expression;
+ struct ycf_node* if_statement;
+} ycf_node_if;
+
+typedef struct {
+ ycf_node_if if_part;
+ ycf_symbol* else_word;
+ struct ycf_node* else_statement;
+} ycf_node_if_else;
+
+typedef struct {
+ ycf_node_expression left_side;
+ ycf_symbol* assignment_symbol;
+ ycf_node_expression right_side;
+ ycf_symbol* end;
+} ycf_node_assignment;
+
+/*typedef struct {
+ ycf_symbol* id;
+} ycf_node_identifier;*/
+
+typedef struct {
+ ycf_symbol* yield_symbol;
+ ycf_symbol* end_symbol;
+} ycf_node_yield;
+
+typedef struct {
+ ycf_symbol* consume_reds_symbol;
+ ycf_node_parentheses_expression nr_of_reds_expression;
+ ycf_symbol* end_symbol;
+} ycf_node_consume_reds;
+
+typedef struct {
+ ycf_symbol* what;
+} ycf_node_other;
+
+typedef struct {
+ ycf_node_definition definition;
+ int ignore_param_ending;
+ ycf_node_list parameters;
+ ycf_symbol_list end;
+} ycf_node_function_definition;
+
+typedef struct {
+ ycf_node_function_definition definition;
+ ycf_node_code_scope body;
+} ycf_node_function;
+
+typedef struct {
+ ycf_node_expression left_side;
+ ycf_symbol* assignment_symbol;
+ ycf_node_function_call fun_call;
+} ycf_node_function_call_assignment;
+
+typedef struct {
+ ycf_node_list definition_nodes;
+ char* name;
+} ycf_node_gen_typedef_struct;
+
+typedef struct {
+ ycf_symbol* comma_symbol;
+} ycf_node_comma;
+
+typedef struct {
+ ycf_symbol* symbol;
+} ycf_node_macro_cmd;
+
+typedef struct {
+ struct ycf_symbol* start;
+ ycf_node_if code;
+ struct ycf_symbol* end;
+} ycf_node_special_code_block;
+
+
+typedef struct {
+ struct ycf_symbol* goto_symbol;
+ struct ycf_symbol* label_symbol;
+ struct ycf_symbol* end_symbol;
+} ycf_node_goto;
+
+typedef struct {
+ struct ycf_symbol* return_symbol;
+ struct ycf_node* return_expression;
+ struct ycf_symbol* end_symbol;
+} ycf_node_return;
+
+typedef struct {
+ struct ycf_symbol* period;
+ struct ycf_symbol* field_name;
+} ycf_node_period_field_access;
+
+typedef struct {
+ struct ycf_symbol* pointer;
+ struct ycf_symbol* field_name;
+} ycf_pointer_field_access;
+
+typedef struct ycf_node {
+ ycf_node_type type;
+ struct ycf_node* next;
+ union {
+ ycf_node_c_file c_file;
+ ycf_node_definition definition;
+ ycf_node_definition_init definition_init;
+ ycf_node_code_scope code_scope;
+ ycf_node_other other;
+ ycf_node_function function;
+ ycf_node_function_definition function_definition;
+ ycf_node_assignment a;
+ ycf_node_gen_typedef_struct gen_typedef_struct;
+ ycf_node_yield yield;
+ ycf_node_consume_reds consume_reds;
+ ycf_node_expression expression;
+ ycf_node_parentheses_expression parentheses_expression;
+ ycf_node_function_call function_call;
+ ycf_node_statement statement;
+ ycf_node_while while_n;
+ ycf_node_do_while do_while;
+ ycf_node_for for_n;
+ ycf_node_switch switch_n;
+ ycf_node_if if_n;
+ ycf_node_if_else if_else;
+ ycf_node_function_call_assignment function_call_assignment;
+ ycf_node_comma comma;
+ ycf_node_array_bracket array_bracket;
+ ycf_node_macro_cmd macro_cmd;
+ ycf_node_special_code_block special_code_block;
+ ycf_node_goto goto_n;
+ ycf_node_return return_n;
+ ycf_node_period_field_access period_field_access;
+ ycf_pointer_field_access pointer_field_access;
+ } u;
+} ycf_node;
+
+void ycf_node_search_and_replace_statements_in_scope(ycf_node_code_scope* s,
+ ycf_node* (*replacer) (ycf_node* canditate,
+ ycf_node_code_scope* candidates_scope,
+ void* context),
+ void* context);
+
+char* ycf_node_to_string(ycf_node* n);
+ycf_node* ycf_node_new_text_node(char* text);
+ycf_node* ycf_node_new();
+ycf_node* ycf_node_find_function(ycf_node* c_file_node, char* fun_name);
+ycf_node* ycf_node_find_function_declaration(ycf_node* c_file_node, char* fun_name);
+ycf_node* ycf_node_find_define_node(ycf_node* c_file_node, char* define_name);
+bool ycf_node_is_void_ret_fun(ycf_node* f_node);
+ycf_node* ycf_node_get_from_code_scope_text(char* code);
+ycf_node* ycf_node_get_function_from_text(char* code);
+void ycf_node_print(ycf_node* node, ycf_string_printable_buffer* b);
+ycf_node* ycf_node_deep_copy(ycf_node *n);
+ycf_node* ycf_node_from_string(char* src);
+void ycf_node_insert_scopes_in_complex_statements(ycf_node_code_scope* s);
+void ycf_node_normalize_for_var_declarations(ycf_node_code_scope* s);
+void ycf_node_move_in_code_var_declarations_to_top(ycf_node_code_scope* s);
+void ycf_node_remove_declarations_in_scope(ycf_node_code_scope* s);
+ycf_node_list ycf_node_get_declarations_in_scope(ycf_node_code_scope* s);
+void ycf_node_normalize_init_definitions_in_scope(ycf_node_code_scope* s);
+ycf_node_list ycf_node_get_all_definitions_in_function(ycf_node_function* f);
+void ycf_uniqify_local_vars_in_function(ycf_node* function);
+
+ycf_node* ycf_node_new_assignment_from_definition_init(ycf_node_definition_init* n);
+ycf_node* ycf_node_new_definition_from_definition_init(ycf_node_definition_init* n);
+ycf_node* ycf_node_defenition_new(ycf_symbol_list type_spec,
+ ycf_symbol* id,
+ ycf_node_list array_brackets,
+ ycf_symbol* end);
+ycf_node* ycf_node_defenition_with_init_new(ycf_node_definition def,
+ ycf_symbol_list expression,
+ ycf_symbol* end);
+ycf_node* ycf_node_c_file_new(ycf_node_list content);
+ycf_node* ycf_node_function_def_new(ycf_node_definition def_node,
+ ycf_node_list parameters,
+ bool ignore_param_ending,
+ ycf_symbol_list end);
+ycf_node* ycf_node_yield_new(ycf_symbol* yield_symbol,
+ ycf_symbol* end);
+ycf_node* ycf_node_consume_reds_new(ycf_symbol* consume_reds_symbol,
+ ycf_node_parentheses_expression nr_of_reds_expression,
+ ycf_symbol* end_symbol);
+ycf_node* ycf_node_other_new(ycf_symbol* other_symbol);
+ycf_node* ycf_node_scope_new(ycf_symbol* start,
+ ycf_node_list declaration_nodes,
+ ycf_node_list other_nodes,
+ ycf_symbol* end);
+ycf_node* ycf_node_function_new(ycf_node_function_definition fun_def,
+ ycf_node_code_scope body);
+ycf_node* ycf_node_function_call_new(ycf_symbol_list neg_symbols,
+ ycf_symbol* ident,
+ ycf_symbol* paran_start,
+ ycf_node_list parameters,
+ ycf_symbol* paran_end);
+ycf_node* ycf_node_paran_expression_new(ycf_symbol* start, ycf_node_expression expr, ycf_symbol* end);
+ycf_node* ycf_node_expression_new(ycf_node_list expr);
+ycf_node* ycf_node_statement_new(ycf_node* expression,
+ ycf_symbol* end_symbol);
+ycf_node* ycf_node_while_new(ycf_symbol* while_word,
+ ycf_node_parentheses_expression expression,
+ ycf_node* statement);
+ycf_node* ycf_node_do_while_new(ycf_symbol* do_word,
+ ycf_node* statm,
+ ycf_symbol* while_word,
+ ycf_node_parentheses_expression expression,
+ ycf_symbol* end);
+ycf_node* ycf_node_for_new(ycf_symbol* for_word,
+ ycf_symbol* start_paran,
+ struct ycf_node* init,
+ ycf_node* stop_cond,
+ ycf_symbol* stop_cond_end,
+ ycf_node* end_exp,
+ ycf_symbol* end_paran,
+ ycf_node* statem);
+ycf_node* ycf_node_switch_new(ycf_symbol* switch_word,
+ ycf_node_parentheses_expression expression,
+ ycf_node_code_scope scope);
+ycf_node* ycf_node_if_new(ycf_symbol* if_word,
+ ycf_node_parentheses_expression expression,
+ struct ycf_node* if_statem);
+ycf_node* ycf_node_if_else_new(ycf_node_if if_n,
+ ycf_symbol* else_word,
+ struct ycf_node* else_statement);
+ycf_node* ycf_node_fun_call_assignment_new(ycf_node_expression left_side,
+ ycf_symbol* assignment_symbol,
+ ycf_node_function_call fun_call);
+ycf_node* ycf_node_comma_new(ycf_symbol* comma_symbol);
+ycf_node* ycf_node_array_bracket_new(ycf_symbol* start,
+ bool empty,
+ ycf_node_expression content,
+ ycf_symbol* end);
+ycf_node* ycf_node_macro_cmd_new(ycf_symbol* macro_symbol);
+ycf_node* ycf_node_special_code_block_new(ycf_node_type type,
+ ycf_symbol* start,
+ ycf_node_if code,
+ ycf_symbol* end);
+ycf_node* ycf_node_goto_new(ycf_symbol* goto_symbol,
+ ycf_symbol* label_symbol,
+ ycf_symbol* end_symbol);
+ycf_node* ycf_node_return_new(ycf_symbol* return_symbol,
+ ycf_node* return_expression,
+ ycf_symbol* end_symbol);
+ycf_node* ycf_node_period_field_access_new(ycf_symbol* period,
+ ycf_symbol* field_name);
+ycf_node* ycf_pointer_field_access_new(ycf_symbol* pointer,
+ ycf_symbol* field_name);
+
+ycf_node_list ycf_node_definition_list_from_string(char* str);
+void ycf_node_rename_function(ycf_node_function* f, char* new_name);
+bool ycf_node_is_void_ret_ending_fun(ycf_node* f_node);
+void ycf_node_remove_const_specifiers_from_declaration(ycf_node* declaration);
+void ycf_node_remove_static_specifiers_from_declaration(ycf_node* declaration);
+void ycf_node_remove_inline_specifiers_from_declaration(ycf_node* declaration);
+void ycf_node_remove_array_size_info_from_declaration(ycf_node* declaration);
+void ycf_node_modify_declarations(ycf_node_list declarations, void (*modifer)(ycf_node*));
+ycf_symbol_list ycf_node_get_return_type(ycf_node* f_node);
+ycf_symbol_list ycf_node_find_function_return_type(ycf_node* c_file_node, char* fun_name);
+
+
+/* Functions for node lists */
+
+int ycf_node_list_get_item_position(ycf_node_list* list, ycf_node* node);
+
+ycf_node* ycf_node_shallow_copy(ycf_node* n);
+ycf_node* ycf_node_list_get_item_at_position(ycf_node_list* list, int pos);
+
+void ycf_node_list_append(ycf_node_list* list, ycf_node* node);
+void ycf_node_list_prepend(ycf_node_list* list, ycf_node* node);
+void ycf_node_list_insert_before(ycf_node_list* list, ycf_node* before_this, ycf_node* to_insert);
+void ycf_node_list_insert_after(ycf_node_list* list, ycf_node* after_this, ycf_node* to_insert);
+void ycf_node_list_remove(ycf_node_list* list, ycf_node* to_remove);
+void ycf_node_list_replace(ycf_node_list* list, ycf_node* to_replace, ycf_node* replace_with);
+void ycf_node_list_concat(ycf_node_list* list1, ycf_node_list* list2);
+
+ycf_node_list ycf_node_list_empty();
+ycf_node_list ycf_node_list_shallow_copy(ycf_node_list n);
+ycf_node_list ycf_node_list_copy_append(ycf_node_list list, ycf_node* node);
+ycf_node_list ycf_node_list_copy_prepend(ycf_node_list list, ycf_node* node);
+ycf_node_list ycf_node_list_copy_insert_before(ycf_node_list list, ycf_node* before_this, ycf_node* to_insert);
+ycf_node_list ycf_node_list_copy_insert_after(ycf_node_list list, ycf_node* after_this, ycf_node* to_insert);
+ycf_node_list ycf_node_list_copy_remove(ycf_node_list list, ycf_node* to_remove);
+ycf_node_list ycf_node_list_copy_replace(ycf_node_list list, ycf_node* to_replace, ycf_node* replace_with);
+ycf_node_list ycf_node_list_copy_concat(ycf_node_list list1, ycf_node_list list2);
+size_t ycf_node_list_length(ycf_node_list list);
+char* ycf_node_list_to_string(ycf_node_list* l);
+char* ycf_node_get_node_type_string(ycf_node_type t);
+
+ycf_node_assignment* ycf_node_get_assignment(ycf_node* n);
+void ycf_node_print_node_type(ycf_node_type t);
+void ycf_node_normalize_function(ycf_node* fun);
+void ycf_node_remove_unecessary_scopes(ycf_node_code_scope* s);
+
+#endif //YIELDING_C_FUN_YCF_NODE_FUNS_H
diff --git a/erts/lib_src/yielding_c_fun/ycf_parser.c b/erts/lib_src/yielding_c_fun/ycf_parser.c
new file mode 100644
index 0000000000..ed2747c955
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_parser.c
@@ -0,0 +1,1498 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+#include "ycf_yield_fun.h"
+#include "ycf_utils.h"
+#include "ycf_node.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+void print_symbol_code(ycf_symbol* s);
+ycf_parse_result parse_exp_statement(ycf_symbol* symbols);
+
+ycf_parse_result fail_parse_result(){
+ ycf_parse_result r;
+ r.success = false;
+ r.next_symbol = NULL;
+ r.result = NULL;
+ return r;
+}
+
+ycf_parse_result success_parse_result(ycf_symbol* next,
+ ycf_node* result){
+ ycf_parse_result r;
+ r.success = true;
+ r.next_symbol = next;
+ r.result = result;
+ return r;
+}
+
+typedef struct {
+ ycf_node_list list;
+ ycf_symbol* next_symbol;
+} parse_list_res;
+
+typedef struct {
+ ycf_symbol_list list;
+ ycf_symbol* next_symbol;
+} parse_symbol_list_res;
+
+
+parse_list_res parse_list_generic(int number_of_parsers,
+ ycf_parse_result (*parsers[])(ycf_symbol*),
+ ycf_symbol* symbols,
+ bool use_break_symbol,
+ int nr_of_break_symbols,
+ ycf_symbol_type break_symbols[]){
+ parse_list_res r;
+ ycf_symbol* current = symbols;
+ int i;
+ r.list = ycf_node_list_empty();
+ while(current != NULL){
+ if(use_break_symbol){
+ bool is_break = false;
+ for(int i = 0; i < nr_of_break_symbols; i++){
+ if(break_symbols[i] == current->type){
+ is_break = true;
+ }
+ }
+ if(is_break){
+ break;
+ }
+ }
+ ycf_symbol* prev_current_symbol = current;
+ for(i = 0; i < number_of_parsers; i++){
+ ycf_parse_result res = parsers[i](current);
+ if(res.success){
+ ycf_node_list_append(&r.list, res.result);
+ current = res.next_symbol;
+ break;
+ }
+ }
+ if(prev_current_symbol == current){
+ break;
+ }
+ }
+ r.next_symbol = current;
+ return r;
+}
+
+parse_list_res parse_list(int number_of_parsers,
+ ycf_parse_result (*parsers[])(ycf_symbol*),
+ ycf_symbol* symbols){
+ return parse_list_generic(number_of_parsers,
+ parsers,
+ symbols,
+ false,
+ 0,
+ NULL);
+
+}
+
+parse_list_res parse_list_break_symbol(int number_of_parsers,
+ ycf_parse_result (*parsers[])(ycf_symbol*),
+ ycf_symbol* symbols,
+ ycf_symbol_type break_symbol){
+ ycf_symbol_type break_s[1] = {break_symbol};
+ return parse_list_generic(number_of_parsers,
+ parsers,
+ symbols,
+ true,
+ 1,
+ break_s);
+
+}
+
+parse_list_res parse_list_break_symbols(int number_of_parsers,
+ ycf_parse_result (*parsers[])(ycf_symbol*),
+ ycf_symbol* symbols,
+ int nr_of_break_symbols,
+ ycf_symbol_type break_symbols[]){
+ return parse_list_generic(number_of_parsers,
+ parsers,
+ symbols,
+ true,
+ nr_of_break_symbols,
+ break_symbols);
+
+}
+
+
+ycf_node* ycf_node_defenition_new(ycf_symbol_list type_spec,
+ ycf_symbol* id,
+ ycf_node_list array_brackets,
+ ycf_symbol* end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_variable_definition;
+ n->next = NULL;
+ n->u.definition.identifier = ycf_symbol_copy(id);
+ n->u.definition.type_specifiers = type_spec;
+ n->u.definition.array_brackets = array_brackets;
+ n->u.definition.end = ycf_symbol_copy(end);
+ return n;
+}
+
+ycf_node* ycf_node_defenition_with_init_new(ycf_node_definition def,
+ ycf_symbol_list expression,
+ ycf_symbol* end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_variable_definition_init;
+ n->next = NULL;
+ n->u.definition_init.definition = def;
+ n->u.definition_init.initializer_expression = expression;
+ n->u.definition_init.end = ycf_symbol_copy(end);
+ return n;
+}
+
+ycf_node* ycf_node_c_file_new(ycf_node_list content){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_c_file;
+ n->next = NULL;
+ n->u.c_file.content = content;
+ return n;
+}
+
+
+ycf_node* ycf_node_function_def_new(ycf_node_definition def_node,
+ ycf_node_list parameters,
+ bool ignore_param_ending,
+ ycf_symbol_list end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_function_declaration;
+ n->next = NULL;
+ n->u.function_definition.definition = def_node;
+ n->u.function_definition.parameters = parameters;
+ n->u.function_definition.ignore_param_ending = ignore_param_ending;
+ n->u.function_definition.end = end;
+ return n;
+}
+
+ycf_node* ycf_node_yield_new(ycf_symbol* yield_symbol,
+ ycf_symbol* end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_yield;
+ n->next = NULL;
+ n->u.yield.yield_symbol = ycf_symbol_copy(yield_symbol);
+ n->u.yield.end_symbol = ycf_symbol_copy(end);
+ return n;
+}
+
+
+
+ycf_node* ycf_node_consume_reds_new(ycf_symbol* consume_reds_symbol,
+ ycf_node_parentheses_expression nr_of_reds_expression,
+ ycf_symbol* end_symbol){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_consume_reds;
+ n->next = NULL;
+ n->u.consume_reds.consume_reds_symbol = ycf_symbol_copy(consume_reds_symbol);
+ n->u.consume_reds.nr_of_reds_expression = nr_of_reds_expression;
+ n->u.consume_reds.end_symbol = ycf_symbol_copy(end_symbol);
+ return n;
+}
+
+
+ycf_node* ycf_node_other_new(ycf_symbol* other_symbol){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_other;
+ n->next = NULL;
+ n->u.other.what = ycf_symbol_copy(other_symbol);
+ return n;
+}
+
+ycf_node* ycf_node_scope_new(ycf_symbol* start,
+ ycf_node_list declaration_nodes,
+ ycf_node_list other_nodes,
+ ycf_symbol* end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_code_scope;
+ n->next = NULL;
+ n->u.code_scope.start = ycf_symbol_copy(start);
+ n->u.code_scope.definition_nodes = declaration_nodes;
+ n->u.code_scope.other_nodes = other_nodes;
+ n->u.code_scope.end = ycf_symbol_copy(end);
+ return n;
+}
+
+ycf_node* ycf_node_function_new(ycf_node_function_definition fun_def,
+ ycf_node_code_scope body){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_function_definition;
+ n->next = NULL;
+ n->u.function.definition = fun_def;
+ n->u.function.body = body;
+ return n;
+}
+
+ycf_node* ycf_node_function_call_new(ycf_symbol_list neg_symbols,
+ ycf_symbol* ident,
+ ycf_symbol* paran_start,
+ ycf_node_list parameters,
+ ycf_symbol* paran_end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_function_call;
+ n->next = NULL;
+ n->u.function_call.neg_symbols = neg_symbols;
+ n->u.function_call.identifier = ident;
+ n->u.function_call.start_symbol = paran_start;
+ n->u.function_call.parameter_expressions = parameters;
+ n->u.function_call.end_symbol = paran_end;
+ return n;
+}
+
+ycf_node* ycf_node_paran_expression_new(ycf_symbol* start, ycf_node_expression expr, ycf_symbol* end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_parentheses_expression;
+ n->next = NULL;
+ n->u.parentheses_expression.start_symbol = start;
+ n->u.parentheses_expression.content = expr;
+ n->u.parentheses_expression.end_symbol = end;
+ return n;
+}
+
+ycf_node* ycf_node_expression_new(ycf_node_list expr){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_expression;
+ n->next = NULL;
+ n->u.expression.content = expr;
+ n->u.expression.end_symbol = NULL;
+ return n;
+}
+
+ycf_node* ycf_node_statement_new(ycf_node* expression,
+ ycf_symbol* end_symbol){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->type = ycf_node_type_statement;
+ n->next = NULL;
+ n->u.statement.expression = expression;
+ n->u.statement.end_symbol = end_symbol;
+ return n;
+}
+
+ycf_node* ycf_node_while_new(ycf_symbol* while_word,
+ ycf_node_parentheses_expression expression,
+ ycf_node* statement){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_while;
+ n->u.while_n.while_word = while_word;
+ n->u.while_n.expression = expression;
+ n->u.while_n.statement = statement;
+ return n;
+}
+
+
+
+ycf_node* ycf_node_do_while_new(ycf_symbol* do_word,
+ ycf_node* statm,
+ ycf_symbol* while_word,
+ ycf_node_parentheses_expression expression,
+ ycf_symbol* end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_do_while;
+ n->u.do_while.do_word = do_word;
+ n->u.do_while.statement = statm;
+ n->u.do_while.while_word = while_word;
+ n->u.do_while.expression = expression;
+ n->u.do_while.end = end;
+ return n;
+}
+
+ycf_node* ycf_node_for_new(ycf_symbol* for_word,
+ ycf_symbol* start_paran,
+ struct ycf_node* init,
+ ycf_node* stop_cond,
+ ycf_symbol* stop_cond_end,
+ ycf_node* end_exp,
+ ycf_symbol* end_paran,
+ ycf_node* statem){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_for;
+ n->u.for_n.for_word = for_word;
+ n->u.for_n.start_parentheses = start_paran;
+ n->u.for_n.init = init;
+ n->u.for_n.stop_cond = stop_cond;
+ n->u.for_n.stop_cond_end = stop_cond_end;
+ n->u.for_n.end_exp = end_exp;
+ n->u.for_n.end_parentheses = end_paran;
+ n->u.for_n.statement = statem;
+ return n;
+}
+
+ycf_node* ycf_node_switch_new(ycf_symbol* switch_word,
+ ycf_node_parentheses_expression expression,
+ ycf_node_code_scope scope){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_switch;
+ n->u.switch_n.switch_word = switch_word;
+ n->u.switch_n.expression = expression;
+ n->u.switch_n.scope = scope;
+ return n;
+}
+
+ycf_node* ycf_node_if_new(ycf_symbol* if_word,
+ ycf_node_parentheses_expression expression,
+ struct ycf_node* if_statem){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_if;
+ n->u.if_n.if_word = if_word;
+ n->u.if_n.expression = expression;
+ n->u.if_n.if_statement = if_statem;
+ return n;
+}
+
+ycf_node* ycf_node_if_else_new(ycf_node_if if_n,
+ ycf_symbol* else_word,
+ struct ycf_node* else_statement){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_if_else;
+ n->u.if_else.if_part = if_n;
+ n->u.if_else.else_word = else_word;
+ n->u.if_else.else_statement = else_statement;
+ return n;
+}
+
+
+ycf_node* ycf_node_fun_call_assignment_new(ycf_node_expression left_side,
+ ycf_symbol* assignment_symbol,
+ ycf_node_function_call fun_call){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_assignment_function_call;
+ n->u.function_call_assignment.left_side = left_side;
+ n->u.function_call_assignment.assignment_symbol = assignment_symbol;
+ n->u.function_call_assignment.fun_call = fun_call;
+ return n;
+}
+
+ycf_node* ycf_node_assignment_new(ycf_node_expression left_side,
+ ycf_symbol* assignment_symbol,
+ ycf_node_expression right_side,
+ ycf_symbol* end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_assignment;
+ n->u.a.left_side = left_side;
+ n->u.a.right_side = right_side;
+ n->u.a.assignment_symbol = assignment_symbol;
+ n->u.a.end = end;
+ return n;
+}
+
+ycf_node* ycf_node_comma_new(ycf_symbol* comma_symbol){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_comma;
+ n->u.comma.comma_symbol = comma_symbol;
+ return n;
+}
+
+ycf_node* ycf_node_array_bracket_new(ycf_symbol* start,
+ bool empty,
+ ycf_node_expression content,
+ ycf_symbol* end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_array_bracket;
+ n->u.array_bracket.start = start;
+ n->u.array_bracket.empty = empty;
+ n->u.array_bracket.content = content;
+ n->u.array_bracket.end = end;
+ return n;
+}
+
+ycf_node* ycf_node_macro_cmd_new(ycf_symbol* macro_symbol){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_macro_cmd;
+ n->u.macro_cmd.symbol = macro_symbol;
+ return n;
+}
+
+ycf_node* ycf_node_special_code_block_new(ycf_node_type type,
+ ycf_symbol* start,
+ ycf_node_if code,
+ ycf_symbol* end){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = type;
+ n->u.special_code_block.start = start;
+ n->u.special_code_block.code = code;
+ n->u.special_code_block.end = end;
+ return n;
+}
+
+ycf_node* ycf_node_goto_new(ycf_symbol* goto_symbol,
+ ycf_symbol* label_symbol,
+ ycf_symbol* end_symbol){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_goto;
+ n->u.goto_n.goto_symbol = goto_symbol;
+ n->u.goto_n.label_symbol = label_symbol;
+ n->u.goto_n.end_symbol = end_symbol;
+ return n;
+}
+
+ycf_node* ycf_node_return_new(ycf_symbol* return_symbol,
+ ycf_node* return_expression,
+ ycf_symbol* end_symbol){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_return_statement;
+ n->u.return_n.return_symbol = return_symbol;
+ n->u.return_n.return_expression = return_expression;
+ n->u.return_n.end_symbol = end_symbol;
+ return n;
+}
+
+
+ycf_node* ycf_node_period_field_access_new(ycf_symbol* period,
+ ycf_symbol* field_name){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_period_field_access;
+ n->u.period_field_access.period = period;
+ n->u.period_field_access.field_name = field_name;
+ return n;
+}
+
+ycf_node* ycf_pointer_field_access_new(ycf_symbol* pointer,
+ ycf_symbol* field_name){
+ ycf_node* n = ycf_malloc(sizeof(ycf_node));
+ n->next = NULL;
+ n->type = ycf_node_type_period_field_access;
+ n->u.pointer_field_access.pointer = pointer;
+ n->u.pointer_field_access.field_name = field_name;
+ return n;
+}
+
+parse_symbol_list_res parse_symbol_list(int nr_of_aloved_symbols,
+ ycf_symbol_type accept_symbols[],
+ ycf_symbol* symbols){
+ int i;
+ parse_symbol_list_res r;
+ ycf_symbol* current = symbols;
+ r.list = ycf_symbol_list_empty();
+ while (current != NULL){
+ ycf_symbol* prev_current = current;
+ for(i = 0; i < nr_of_aloved_symbols; i++){
+ if(current->type == accept_symbols[i]){
+ ycf_symbol_list_append(&r.list, ycf_symbol_copy(current));
+ current = current->next;
+ }
+ }
+ if(prev_current == current){
+ break;
+ }
+ }
+ r.next_symbol = current;
+ return r;
+}
+
+parse_symbol_list_res
+parse_symbol_list_until(ycf_symbol* symbols, ycf_symbol_type end_symbol){
+ parse_symbol_list_res r;
+ ycf_symbol* current = symbols;
+ r.list = ycf_symbol_list_empty();
+ while (current->type != end_symbol){
+ ycf_symbol_list_append(&r.list, ycf_symbol_copy(current));
+ current = current->next;
+ }
+ r.next_symbol = current;
+ return r;
+}
+
+ycf_parse_result parse_expression_end_end_squarebracket(ycf_symbol* symbols);
+
+ycf_parse_result parse_array_bracket(ycf_symbol* symbols){
+ if(symbols->type != ycf_symbol_type_open_square_bracket){
+ return fail_parse_result();
+ }
+ if(symbols->next->type == ycf_symbol_type_end_square_bracket){
+ ycf_node_expression empty = {.content = ycf_node_list_empty(), .end_symbol = NULL};
+ return success_parse_result(symbols->next->next,
+ ycf_node_array_bracket_new(symbols, true, empty, symbols->next));
+ }
+ ycf_parse_result square_bracket_content =
+ parse_expression_end_end_squarebracket(symbols->next);
+ if(!square_bracket_content.success){
+ return fail_parse_result();
+ }
+ return success_parse_result(square_bracket_content.next_symbol->next,
+ ycf_node_array_bracket_new(symbols,
+ false,
+ square_bracket_content.result->u.expression,
+ square_bracket_content.next_symbol));
+}
+
+ycf_parse_result parse_defenition_until_identifier(ycf_symbol* symbols,
+ ycf_symbol_type end_symbol_type){
+ ycf_symbol* current = symbols;
+ ycf_symbol_list type_specifier = ycf_symbol_list_empty();
+ ycf_symbol* ident = NULL;
+ ycf_node_list array_brackets;
+ /* Parse modifiers */
+ {
+ int nr_of_aloved_symbols = 4;
+ ycf_symbol_type accept_symbols[4];
+ accept_symbols[0] = ycf_symbol_type_static;
+ accept_symbols[1] = ycf_symbol_type_const;
+ accept_symbols[2] = ycf_symbol_type_inline;
+ accept_symbols[3] = ycf_symbol_type_volatile;
+ parse_symbol_list_res more =
+ parse_symbol_list(nr_of_aloved_symbols,
+ accept_symbols,
+ current);
+ ycf_symbol_list_concat(&type_specifier, &more.list);
+ current = more.next_symbol;
+ }
+ /* Parse first symbol */
+ if(current->type == ycf_symbol_type_identifier ||
+ current->type == ycf_symbol_type_void){
+ ycf_symbol_list_append(&type_specifier, ycf_symbol_copy(current));
+ current = current->next;
+ } else {
+ return fail_parse_result();
+ }
+ if(type_specifier.last->type == ycf_symbol_type_identifier){
+ /* Parse remaining identifiers if first symbol is ycf_symbol_type_identifier */
+ int nr_of_aloved_symbols = 2;
+ ycf_symbol_type accept_symbols[2];
+ accept_symbols[0] = ycf_symbol_type_identifier;
+ accept_symbols[1] = ycf_symbol_type_const;
+ parse_symbol_list_res more =
+ parse_symbol_list(nr_of_aloved_symbols,
+ accept_symbols,
+ current);
+ ycf_symbol_list_concat(&type_specifier, &more.list);
+ current = more.next_symbol;
+ }
+ {
+ /* Parse stars */
+ int nr_of_aloved_symbols = 1;
+ ycf_symbol_type accept_symbols[1];
+ accept_symbols[0] = ycf_symbol_type_star;
+ parse_symbol_list_res more =
+ parse_symbol_list(nr_of_aloved_symbols,
+ accept_symbols,
+ current);
+ ycf_symbol_list_concat(&type_specifier, &more.list);
+ current = more.next_symbol;
+ }
+ /* Handle ycf_symbol_type_identifier */
+ if(type_specifier.last->type == ycf_symbol_type_identifier){
+ ident = type_specifier.last;
+ ycf_symbol_list_remove(&type_specifier, type_specifier.last);
+ if (type_specifier.head == NULL) {
+ return fail_parse_result();
+ }
+ } else if (current->type != ycf_symbol_type_identifier){
+ return fail_parse_result();
+ } else {
+ ident = current;
+ current = current->next;
+ }
+ /* Handle array brackets */
+ {
+ ycf_parse_result (*parsers[])(ycf_symbol *) = {
+ parse_array_bracket
+ };
+ parse_list_res res = parse_list(1, parsers, current);
+ array_brackets = res.list;
+ current = res.next_symbol;
+ }
+ /* Parse end symbol */
+ if (current == NULL ||
+ current->type != end_symbol_type){
+ return fail_parse_result();
+ } else {
+ return success_parse_result(current->next,
+ ycf_node_defenition_new(type_specifier,
+ ident,
+ array_brackets,
+ current));
+ }
+}
+
+ycf_parse_result parse_defenition_no_init(ycf_symbol* symbols){
+ ycf_parse_result res = parse_defenition_until_identifier(symbols, ycf_symbol_type_semicolon);
+ return res;
+}
+
+ycf_parse_result parse_defenition_with_init(ycf_symbol* symbols){
+ ycf_parse_result dec = parse_defenition_until_identifier(symbols, ycf_symbol_type_equal_sign);
+ parse_symbol_list_res expression_list_res;
+ if(!dec.success){
+ return fail_parse_result();
+ }
+ expression_list_res =
+ parse_symbol_list_until(dec.next_symbol, ycf_symbol_type_semicolon);
+ return success_parse_result(expression_list_res.next_symbol->next,
+ ycf_node_defenition_with_init_new(dec.result->u.definition,
+ expression_list_res.list,
+ expression_list_res.next_symbol));
+}
+
+ycf_parse_result parse_defenition_comma(ycf_symbol* symbols){
+ ycf_parse_result res = parse_defenition_until_identifier(symbols, ycf_symbol_type_comma);
+ return res;
+}
+
+ycf_parse_result parse_defenition_end_paran(ycf_symbol* symbols){
+ ycf_parse_result res = parse_defenition_until_identifier(symbols, ycf_symbol_type_end_parenthesis);
+ return res;
+}
+
+ycf_parse_result parse_function_head(ycf_symbol* symbols,
+ ycf_symbol_type end_symbol,
+ bool exclude_end_symbol){
+ ycf_symbol* current = symbols;
+ ycf_parse_result def_res =
+ parse_defenition_until_identifier(current,
+ ycf_symbol_type_open_parenthesis);
+ if(!def_res.success){
+ return fail_parse_result();
+ }
+ current = def_res.next_symbol;
+
+ /* Parse function without parameters */
+
+ if(current->type == ycf_symbol_type_end_parenthesis &&
+ current->next->type == end_symbol){
+ ycf_symbol_list end = ycf_symbol_list_empty();
+ ycf_symbol* next_symbol;
+ ycf_node_list parameter_list = ycf_node_list_empty();
+ ycf_symbol_list_append(&end, ycf_symbol_copy(current));
+ if(!exclude_end_symbol){
+ next_symbol = current->next->next;
+ ycf_symbol_list_append(&end, ycf_symbol_copy(current->next));
+ } else {
+ next_symbol = current->next;
+ }
+ return success_parse_result(next_symbol,
+ ycf_node_function_def_new(def_res.result->u.definition,
+ parameter_list,
+ false,
+ end));
+ } else if(current->type == ycf_symbol_type_void &&
+ current->next->type == ycf_symbol_type_end_parenthesis &&
+ current->next->next->type == end_symbol){
+ ycf_symbol_list end = ycf_symbol_list_empty();
+ ycf_symbol* next_symbol;
+ ycf_node_list parameter_list = ycf_node_list_empty();
+ ycf_symbol_list_append(&end, ycf_symbol_copy(current));
+ ycf_symbol_list_append(&end, ycf_symbol_copy(current->next));
+ if(!exclude_end_symbol){
+ next_symbol = current->next->next->next;
+ ycf_symbol_list_append(&end, ycf_symbol_copy(current->next->next));
+ }else{
+ next_symbol = current->next->next;
+ }
+ return success_parse_result(next_symbol,
+ ycf_node_function_def_new(def_res.result->u.definition,
+ parameter_list,
+ false,
+ end));
+ }
+
+ /* Parse parameters */
+
+ int number_of_parsers = 2;
+ ycf_parse_result (*parsers[])(ycf_symbol *) = {
+ parse_defenition_comma,
+ parse_defenition_end_paran
+ };
+ parse_list_res param_list =
+ parse_list(number_of_parsers, parsers, current);
+ if(param_list.next_symbol == NULL ||
+ param_list.next_symbol->type != end_symbol){
+ return fail_parse_result();
+ }
+
+ /* Parse ending */
+
+ {
+ ycf_symbol_list end = ycf_symbol_list_empty();
+ ycf_symbol* next_symbol;
+ if(!exclude_end_symbol){
+ next_symbol = param_list.next_symbol->next;
+ ycf_symbol_list_append(&end, ycf_symbol_copy(param_list.next_symbol));
+ }else{
+ next_symbol = param_list.next_symbol;
+ }
+ return success_parse_result(next_symbol,
+ ycf_node_function_def_new(def_res.result->u.definition,
+ param_list.list,
+ false,
+ end));
+ }
+}
+
+ycf_parse_result parse_function_def(ycf_symbol* symbols){
+ return parse_function_head(symbols, ycf_symbol_type_semicolon, false);
+}
+
+ycf_parse_result parse_other(ycf_symbol* symbols){
+ ycf_symbol* what = symbols;
+ return success_parse_result(symbols->next,
+ ycf_node_other_new(what));
+}
+
+
+ycf_parse_result parse_goto(ycf_symbol* symbols){
+ ycf_symbol* goto_symbol = symbols;
+ if(goto_symbol->type != ycf_symbol_type_goto){
+ return fail_parse_result();
+ }
+ ycf_symbol* label_symbol = goto_symbol->next;
+ if(label_symbol->type != ycf_symbol_type_identifier){
+ return fail_parse_result();
+ }
+ ycf_symbol* end_symbol = label_symbol->next;
+ if(end_symbol->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ return success_parse_result(end_symbol->next,
+ ycf_node_goto_new(goto_symbol, label_symbol, end_symbol));
+}
+
+
+ycf_parse_result parse_comma(ycf_symbol* symbols){
+ if(symbols->type == ycf_symbol_type_comma){
+ return success_parse_result(symbols->next, ycf_node_comma_new(symbols));
+ } else {
+ return fail_parse_result();
+ }
+}
+
+
+ycf_parse_result parse_pointer_field_access_node(ycf_symbol* symbols){
+ ycf_symbol* pointer = symbols;
+ if(pointer->type != ycf_symbol_type_pointer_field_access){
+ return fail_parse_result();
+ }
+ ycf_symbol* field_name = pointer->next;
+ if(field_name->type != ycf_symbol_type_identifier){
+ return fail_parse_result();
+ }
+ return success_parse_result(field_name->next,
+ ycf_pointer_field_access_new(pointer, field_name));
+}
+
+ycf_parse_result parse_period_field_access_node(ycf_symbol* symbols){
+ ycf_symbol* p = symbols;
+ if(p->type != ycf_symbol_type_period){
+ return fail_parse_result();
+ }
+ ycf_symbol* field_name = p->next;
+ if(field_name->type != ycf_symbol_type_identifier){
+ return fail_parse_result();
+ }
+ return success_parse_result(field_name->next,
+ ycf_node_period_field_access_new(p, field_name));
+}
+
+ycf_parse_result parse_function_call(ycf_symbol* symbols);
+ycf_parse_result parse_expression_generic(ycf_symbol* symbols,
+ bool use_end_symbol,
+ bool no_function_call_assignment,
+ int nr_of_end_symbols,
+ ycf_symbol_type end_symbols[]);
+
+ycf_parse_result parse_paran_expression(ycf_symbol* symbols){
+ if(symbols->type != ycf_symbol_type_open_parenthesis){
+ return fail_parse_result();
+ }
+ ycf_symbol* start = symbols;
+ ycf_parse_result exp = parse_expression_generic(start->next,
+ true,
+ false,
+ 1,
+ (ycf_symbol_type[]){ycf_symbol_type_end_parenthesis});
+ if(!exp.success || exp.next_symbol->type != ycf_symbol_type_end_parenthesis ){
+ return fail_parse_result();
+ }
+ return success_parse_result(exp.next_symbol->next,
+ ycf_node_paran_expression_new(start,
+ exp.result->u.expression,
+ exp.next_symbol));
+}
+
+ycf_parse_result parse_function_call_assignment(ycf_symbol* symbols);
+ycf_parse_result parse_assignment(ycf_symbol* symbols);
+
+ycf_parse_result parse_expression_generic(ycf_symbol* symbols,
+ bool use_end_symbol,
+ bool no_function_call_assignment,
+ int nr_of_end_symbols,
+ ycf_symbol_type end_symbols[]){
+ int number_of_parsers = (!no_function_call_assignment ? 1 : 0) + 5;
+ ycf_parse_result (*parsers[7])(ycf_symbol *);
+ if(!no_function_call_assignment){
+ parsers[0] = parse_function_call;
+ parsers[1] = parse_function_call_assignment;
+ parsers[2] = parse_paran_expression;
+ parsers[3] = parse_period_field_access_node;
+ parsers[4] = parse_pointer_field_access_node;
+ parsers[5] = parse_other;
+ }else{
+ parsers[0] = parse_function_call;
+ parsers[1] = parse_paran_expression;
+ parsers[2] = parse_period_field_access_node;
+ parsers[3] = parse_pointer_field_access_node;
+ parsers[4] = parse_other;
+ }
+ parse_list_res res = parse_list_break_symbols(number_of_parsers, parsers, symbols, nr_of_end_symbols,end_symbols);
+ if(res.list.head == NULL || (use_end_symbol &&
+ (res.next_symbol == NULL ||res.next_symbol->type != end_symbols[0]))){
+ return fail_parse_result();
+ }
+ return success_parse_result(res.next_symbol,
+ ycf_node_expression_new(res.list));
+}
+
+ycf_parse_result parse_expression(ycf_symbol* symbols){
+ return parse_expression_generic(symbols,
+ false,
+ false,
+ 0 /* ignored */,
+ NULL);
+}
+
+ycf_parse_result parse_expression_end_comma(ycf_symbol* symbols){
+ return parse_expression_generic(symbols,
+ true,
+ false,
+ 2,
+ (ycf_symbol_type[]){ycf_symbol_type_comma, ycf_symbol_type_end_parenthesis});
+}
+
+ycf_parse_result parse_expression_end_end_paren(ycf_symbol* symbols){
+ return parse_expression_generic(symbols,
+ true,
+ false,
+ 1,
+ (ycf_symbol_type[]){ycf_symbol_type_end_parenthesis});
+}
+
+ycf_parse_result parse_expression_end_end_semicolon(ycf_symbol* symbols){
+ return parse_expression_generic(symbols,
+ true,
+ false,
+ 1,
+ (ycf_symbol_type[]){ycf_symbol_type_semicolon});
+}
+
+ycf_parse_result parse_expression_end_end_squarebracket(ycf_symbol* symbols){
+ return parse_expression_generic(symbols,
+ true,
+ false,
+ 1,
+ (ycf_symbol_type[]){ycf_symbol_type_end_square_bracket});
+}
+
+
+ycf_parse_result parse_function_call(ycf_symbol* symbols){
+ parse_symbol_list_res negs;
+ {
+ /* Parse neg symbols */
+ int nr_of_aloved_symbols = 1;
+ ycf_symbol_type accept_symbols[1];
+ accept_symbols[0] = ycf_symbol_type_neg;
+ negs =
+ parse_symbol_list(nr_of_aloved_symbols,
+ accept_symbols,
+ symbols);
+ }
+ ycf_symbol* ident = negs.next_symbol;
+ if(ident == NULL ||
+ ident->type != ycf_symbol_type_identifier){
+ return fail_parse_result();
+ }
+ ycf_symbol* paran_start = ident->next;
+ if(paran_start == NULL || paran_start->type != ycf_symbol_type_open_parenthesis){
+ return fail_parse_result();
+ }
+ int number_of_parsers = 3;
+ ycf_parse_result (*parsers[])(ycf_symbol *) = {
+ parse_comma,
+ parse_expression_end_comma,
+ parse_expression_end_end_paren
+ };
+ parse_list_res param_list_res =
+ parse_list_break_symbol(number_of_parsers,
+ parsers,
+ paran_start->next,
+ ycf_symbol_type_end_parenthesis);
+ if(param_list_res.next_symbol == NULL ||
+ param_list_res.next_symbol->type != ycf_symbol_type_end_parenthesis){
+ return fail_parse_result();
+ }
+ return success_parse_result(param_list_res.next_symbol->next,
+ ycf_node_function_call_new(negs.list,
+ ident,
+ paran_start,
+ param_list_res.list,
+ param_list_res.next_symbol));
+}
+
+ycf_parse_result parse_assignment(ycf_symbol* symbols) {
+ if(symbols->type != ycf_symbol_type_identifier){
+ return fail_parse_result();
+ }
+ if(symbols->next == NULL || symbols->next->type != ycf_symbol_type_equal_sign){
+ return fail_parse_result();
+ }
+ ycf_parse_result left_expression = parse_expression_generic(symbols,
+ false,
+ true,
+ 3,
+ (ycf_symbol_type[]){
+ ycf_symbol_type_equal_sign,
+ ycf_symbol_type_semicolon,
+ ycf_symbol_type_end_parenthesis});
+ if(!left_expression.success){
+ return fail_parse_result();
+ }
+ ycf_symbol* equal_sign = left_expression.next_symbol;
+ if(equal_sign == NULL || equal_sign->type != ycf_symbol_type_equal_sign) {
+ return fail_parse_result();
+ }
+ if(equal_sign->next == NULL || equal_sign->next->type != ycf_symbol_type_identifier){
+ return fail_parse_result();
+ }
+ if(equal_sign->next->next == NULL || equal_sign->next->next->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ ycf_parse_result right_expression = parse_expression_generic(equal_sign->next,
+ false,
+ true,
+ 3,
+ (ycf_symbol_type[]){
+ ycf_symbol_type_semicolon,
+ ycf_symbol_type_equal_sign,
+ ycf_symbol_type_end_parenthesis});
+ if(!right_expression.success ||
+ right_expression.next_symbol == NULL ||
+ right_expression.next_symbol->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ ycf_node* node = ycf_node_assignment_new(left_expression.result->u.expression,
+ ycf_symbol_copy(equal_sign),
+ right_expression.result->u.expression,
+ ycf_symbol_copy(right_expression.next_symbol));
+ return success_parse_result(right_expression.next_symbol->next,
+ node);
+}
+
+ycf_parse_result parse_function_call_assignment(ycf_symbol* symbols){
+ ycf_parse_result expression = parse_expression_generic(symbols,
+ true,
+ true,
+ 3,
+ (ycf_symbol_type[]){ycf_symbol_type_equal_sign,
+ ycf_symbol_type_semicolon,
+ ycf_symbol_type_end_parenthesis});
+ if(!expression.success){
+ return fail_parse_result();
+ }
+ ycf_symbol* equal_sign = expression.next_symbol;
+ ycf_parse_result fun_call = parse_function_call(equal_sign->next);
+ if(!fun_call.success){
+ return fail_parse_result();
+ }
+ return success_parse_result(fun_call.next_symbol,
+ ycf_node_fun_call_assignment_new(expression.result->u.expression,
+ equal_sign,
+ fun_call.result->u.function_call));
+}
+
+
+ycf_parse_result parse_function_call_assignment_statement(ycf_symbol* symbols){
+ ycf_parse_result fun_call_ass = parse_function_call_assignment(symbols);
+ if(!fun_call_ass.success || fun_call_ass.next_symbol->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ return success_parse_result(fun_call_ass.next_symbol->next,
+ ycf_node_statement_new(fun_call_ass.result,
+ fun_call_ass.next_symbol));
+}
+
+
+ycf_parse_result parse_function_call_statement(ycf_symbol* symbols){
+ ycf_parse_result fun_call = parse_function_call(symbols);
+ if(!fun_call.success || fun_call.next_symbol->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ return success_parse_result(fun_call.next_symbol->next,
+ ycf_node_statement_new(fun_call.result,
+ fun_call.next_symbol));
+}
+
+ycf_parse_result parse_statement(ycf_symbol* symbols);
+
+
+ycf_parse_result parse_cond_statement(ycf_symbol* symbols, ycf_symbol_type cond_keyword){
+ if(symbols->type != cond_keyword){
+ return fail_parse_result();
+ }
+ ycf_symbol* cond_word = symbols;
+ ycf_parse_result cond_exp = parse_paran_expression(cond_word->next);
+ if(!cond_exp.success){
+ return fail_parse_result();
+ }
+ ycf_parse_result cond_statement = parse_statement(cond_exp.next_symbol);
+ if(!cond_statement.success){
+ return fail_parse_result();
+ }
+ /* Return if node even though it might be something else */
+ return success_parse_result(cond_statement.next_symbol,
+ ycf_node_if_new(cond_word,
+ cond_exp.result->u.parentheses_expression,
+ cond_statement.result));
+}
+
+ycf_parse_result parse_if_statement(ycf_symbol* symbols){
+ return parse_cond_statement(symbols, ycf_symbol_type_if);
+}
+
+ycf_parse_result parse_if_else_statement(ycf_symbol* symbols){
+ ycf_parse_result if_statem = parse_if_statement(symbols);
+ if(!if_statem.success){
+ return fail_parse_result();
+ }
+ if(if_statem.next_symbol->type != ycf_symbol_type_else){
+ return fail_parse_result();
+ }
+ ycf_symbol* else_w = if_statem.next_symbol;
+ ycf_parse_result else_statement = parse_statement(else_w->next);
+ if(!else_statement.success){
+ return fail_parse_result();
+ }
+ return success_parse_result(else_statement.next_symbol,
+ ycf_node_if_else_new(if_statem.result->u.if_n,
+ else_w,
+ else_statement.result));
+}
+
+ycf_parse_result parse_while_statement(ycf_symbol* symbols){
+ ycf_parse_result cond_statem = parse_cond_statement(symbols, ycf_symbol_type_while);
+ if(!cond_statem.success){
+ return fail_parse_result();
+ }
+ return success_parse_result(cond_statem.next_symbol,
+ ycf_node_while_new(cond_statem.result->u.if_n.if_word,
+ cond_statem.result->u.if_n.expression,
+ cond_statem.result->u.if_n.if_statement));
+}
+
+ycf_parse_result parse_do_while_statement(ycf_symbol* symbols){
+ if(symbols->type != ycf_symbol_type_do){
+ return fail_parse_result();
+ }
+ ycf_symbol* do_word = symbols;
+ ycf_parse_result statement = parse_statement(do_word->next);
+ if(!statement.success || statement.next_symbol->type != ycf_symbol_type_while){
+ return fail_parse_result();
+ }
+ ycf_symbol* while_word = statement.next_symbol;
+ ycf_parse_result cond = parse_paran_expression(while_word->next);
+ if(!cond.success){
+ return fail_parse_result();
+ }
+ if(cond.next_symbol->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ return success_parse_result(cond.next_symbol->next,
+ ycf_node_do_while_new(do_word, statement.result, while_word,
+ cond.result->u.parentheses_expression, cond.next_symbol));
+}
+
+ycf_parse_result parse_for_statement(ycf_symbol* symbols){
+ if(symbols->type != ycf_symbol_type_for){
+ return fail_parse_result();
+ }
+ ycf_symbol* for_word = symbols;
+ if(for_word->next->type != ycf_symbol_type_open_parenthesis){
+ return fail_parse_result();
+ }
+ ycf_symbol* start_paran = for_word->next;
+ ycf_node* init = NULL;
+ ycf_parse_result init_res = parse_defenition_with_init(start_paran->next);
+ if(!init_res.success){
+ init_res = parse_defenition_no_init(start_paran->next);
+ }
+ if(!init_res.success){
+ init_res = parse_exp_statement(start_paran->next);
+ }
+ if(!init_res.success){
+ return fail_parse_result();
+ }
+ init = init_res.result;
+ ycf_node* stop_cond = NULL;
+ ycf_symbol* stop_cond_end = NULL;
+ if(init_res.next_symbol->type == ycf_symbol_type_semicolon){
+ stop_cond_end = init_res.next_symbol;
+ }else{
+ ycf_parse_result stop_cond_res = parse_expression_end_end_semicolon(init_res.next_symbol);
+ if(!stop_cond_res.success || stop_cond_res.next_symbol->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ stop_cond = stop_cond_res.result;
+ stop_cond_end = stop_cond_res.next_symbol;
+ }
+ ycf_node* end_exp = NULL;
+ ycf_symbol* end_exp_end = NULL;
+ if(stop_cond_end->next->type == ycf_symbol_type_end_parenthesis){
+ end_exp_end = stop_cond_end->next;
+ }else{
+ ycf_parse_result end_exp_res = parse_expression_end_end_paren(stop_cond_end->next);
+ if(!end_exp_res.success || end_exp_res.next_symbol->type != ycf_symbol_type_end_parenthesis){
+ return fail_parse_result();
+ }
+ end_exp = end_exp_res.result;
+ end_exp_end = end_exp_res.next_symbol;
+ }
+ ycf_parse_result for_statement = parse_statement(end_exp_end->next);
+ if(!for_statement.success){
+ return fail_parse_result();
+ }
+ return success_parse_result(for_statement.next_symbol,
+ ycf_node_for_new(for_word,
+ start_paran,
+ init,
+ stop_cond,
+ stop_cond_end,
+ end_exp,
+ end_exp_end,
+ for_statement.result));
+}
+
+
+ycf_parse_result parse_switch_statement(ycf_symbol* symbols){
+ ycf_parse_result cond_statem = parse_cond_statement(symbols, ycf_symbol_type_switch);
+ if(!cond_statem.success){
+ return fail_parse_result();
+ }
+ if(cond_statem.result->u.if_n.if_statement->type != ycf_node_type_code_scope){
+ return fail_parse_result();
+ }
+ return success_parse_result(cond_statem.next_symbol,
+ ycf_node_switch_new(cond_statem.result->u.if_n.if_word,
+ cond_statem.result->u.if_n.expression,
+ cond_statem.result->u.if_n.if_statement->u.code_scope));
+}
+
+ycf_parse_result parse_exp_statement(ycf_symbol* symbols){
+ if(symbols->type == ycf_symbol_type_semicolon){
+ /*Empty statement*/
+ return success_parse_result(symbols->next,
+ ycf_node_statement_new(ycf_node_expression_new(ycf_node_list_empty()),
+ symbols));
+ }
+ ycf_parse_result expression = parse_expression_end_end_semicolon(symbols);
+ if(!expression.success || expression.next_symbol->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ return success_parse_result(expression.next_symbol->next,
+ ycf_node_statement_new(expression.result,
+ expression.next_symbol));
+}
+
+ycf_parse_result parse_return_statement(ycf_symbol* symbols){
+ ycf_symbol* return_symbol = symbols;
+ if(return_symbol->type != ycf_symbol_type_return){
+ return fail_parse_result();
+ }
+ if(return_symbol->next->type == ycf_symbol_type_semicolon){
+ return success_parse_result(return_symbol->next->next,
+ ycf_node_return_new(return_symbol,
+ NULL,
+ return_symbol->next));
+ }
+ ycf_parse_result expression = parse_expression_end_end_semicolon(return_symbol->next);
+ if(!expression.success || expression.next_symbol->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ return success_parse_result(expression.next_symbol->next,
+ ycf_node_return_new(return_symbol,
+ expression.result,
+ expression.next_symbol));
+}
+
+ycf_parse_result parse_macro_command(ycf_symbol* symbols){
+ if(symbols->type == ycf_symbol_type_macro_command || symbols->type == ycf_symbol_type_macro_define){
+ return success_parse_result(symbols->next, ycf_node_macro_cmd_new(symbols));
+ } else {
+ return fail_parse_result();
+ }
+}
+
+ycf_parse_result parse_scope(ycf_symbol* symbols);
+
+ycf_parse_result parse_special_code_block(ycf_symbol* symbols, char* code_block_name, ycf_node_type type){
+ ycf_symbol* start = symbols;
+ if(symbols->type != ycf_symbol_type_special_code_start ||
+ !ycf_symbol_is_text_eq(start,
+ ycf_string_new("/*special_code_start:%s*/", code_block_name))){
+ return fail_parse_result();
+ }
+ ycf_parse_result code = parse_if_statement(start->next);
+ if(!code.success){
+ return fail_parse_result();
+ }
+ ycf_symbol* end = code.next_symbol;
+ if(end->type != ycf_symbol_type_special_code_end){
+ return fail_parse_result();
+ }
+ return success_parse_result(end->next,
+ ycf_node_special_code_block_new(type,
+ start,
+ code.result->u.if_n,
+ end));
+}
+
+ycf_parse_result parse_on_save_yield_state(ycf_symbol* symbols){
+ return parse_special_code_block(symbols, "ON_SAVE_YIELD_STATE", ycf_node_type_on_save_yield_state_code);
+}
+
+ycf_parse_result parse_on_restore_yield_state(ycf_symbol* symbols){
+ return parse_special_code_block(symbols, "ON_RESTORE_YIELD_STATE", ycf_node_type_on_restore_yield_state_code);
+}
+
+ycf_parse_result parse_destroy_state_code(ycf_symbol* symbols){
+ ycf_parse_result res = parse_special_code_block(symbols, "ON_DESTROY_STATE", ycf_node_type_on_destroy_state_code);
+ return res;
+}
+
+ycf_parse_result parse_on_return_code(ycf_symbol* symbols){
+ ycf_parse_result res = parse_special_code_block(symbols, "ON_RETURN", ycf_node_type_on_return_code);
+ return res;
+}
+
+ycf_parse_result parse_on_destroy_state_or_return_code(ycf_symbol* symbols){
+ ycf_parse_result res = parse_special_code_block(symbols, "ON_DESTROY_STATE_OR_RETURN", ycf_node_type_on_destroy_state_or_return_code);
+ return res;
+}
+
+ycf_parse_result parse_consume_reds(ycf_symbol* symbols){
+ ycf_symbol* consume_reds_symbol = symbols;
+ if(consume_reds_symbol->type != ycf_symbol_type_consume_reds){
+ return fail_parse_result();
+ }
+ ycf_parse_result paran_expression =
+ parse_paran_expression(consume_reds_symbol->next);
+ if(!paran_expression.success){
+ return fail_parse_result();
+ }
+ if(paran_expression.next_symbol->type != ycf_symbol_type_semicolon){
+ return fail_parse_result();
+ }
+ return success_parse_result(paran_expression.next_symbol->next,
+ ycf_node_consume_reds_new(consume_reds_symbol,
+ paran_expression.result->u.parentheses_expression,
+ paran_expression.next_symbol));
+}
+
+ycf_parse_result parse_statement(ycf_symbol* symbols){
+ int number_of_parsers = 22;
+ ycf_parse_result (*parsers[])(ycf_symbol *) = {
+ parse_defenition_no_init,
+ parse_defenition_with_init,
+ parse_if_else_statement,
+ parse_if_statement,
+ parse_while_statement,
+ parse_do_while_statement,
+ parse_for_statement,
+ parse_switch_statement,
+ parse_scope,
+ parse_goto,
+ parse_return_statement,
+ parse_consume_reds,
+ parse_function_call_statement,
+ parse_function_call_assignment_statement,
+ parse_assignment,
+ parse_on_save_yield_state,
+ parse_on_restore_yield_state,
+ parse_on_return_code,
+ parse_on_destroy_state_or_return_code,
+ parse_destroy_state_code,
+ parse_macro_command,
+ parse_exp_statement
+ };
+ for(int i = 0; i < number_of_parsers; i++){
+ ycf_parse_result res = parsers[i](symbols);
+ if(res.success){
+ return res;
+ }
+ }
+ return fail_parse_result();
+}
+
+ycf_parse_result parse_scope(ycf_symbol* symbols){
+ ycf_symbol* current = symbols;
+ ycf_symbol* start;
+ if(current->type != ycf_symbol_type_open_curly_brace){
+ return fail_parse_result();
+ }
+ start = current;
+ current = current->next;
+ int number_of_parsers = 2;
+ ycf_parse_result (*parsers[])(ycf_symbol *) = {
+ parse_defenition_no_init,
+ parse_defenition_with_init
+ };
+ parse_list_res decs = parse_list(number_of_parsers, parsers, current);
+ current = decs.next_symbol;
+ /* parse rest */
+ number_of_parsers = 1;
+ ycf_parse_result (*rest_parsers[])(ycf_symbol *) = {
+ parse_statement
+ };
+ parse_list_res others =
+ parse_list_break_symbol(number_of_parsers,
+ rest_parsers,
+ current,
+ ycf_symbol_type_end_curly_brace);
+ current = others.next_symbol;
+ if (current == NULL) {
+ return fail_parse_result();
+ }
+ return success_parse_result(current->next, ycf_node_scope_new(start,
+ decs.list,
+ others.list,
+ current));
+}
+
+ycf_parse_result parse_function(ycf_symbol* symbols){
+ ycf_parse_result fun_head = parse_function_head(symbols, ycf_symbol_type_open_curly_brace, true);
+ if(!fun_head.success){
+ return fail_parse_result();
+ }
+ ycf_parse_result scope = parse_scope(fun_head.next_symbol);
+ if(!scope.success){
+ return fail_parse_result();
+ }
+ return success_parse_result(scope.next_symbol,
+ ycf_node_function_new(fun_head.result->u.function_definition,
+ scope.result->u.code_scope));
+}
+
+ycf_parse_result parse_c_file(ycf_symbol* symbols){
+ int number_of_parsers = 5;
+ ycf_parse_result (*parsers[])(ycf_symbol *) = {
+ parse_defenition_no_init,
+ parse_defenition_with_init,
+ parse_function_def,
+ parse_function,
+ parse_other
+ };
+ parse_list_res program_list =
+ parse_list(number_of_parsers, parsers, symbols);
+ if(program_list.next_symbol != NULL){
+ return fail_parse_result();
+ } else {
+ return success_parse_result(NULL,
+ ycf_node_c_file_new(program_list.list));
+ }
+}
+
+
+ycf_node* get_abstract_syntax_tree_root(ycf_symbol_list* symbols){
+ ycf_parse_result res = parse_c_file(symbols->head);
+ if(!res.success){
+ printf("ERROR: Could not parse file\n");
+ exit(1);
+ }
+ return res.result;
+}
+
+ycf_node* ycf_node_deep_copy(ycf_node *n) {
+ ycf_string_printable_buffer *b = ycf_string_printable_buffer_new();
+ ycf_node_print(n, b);
+ ycf_symbol_list symbols = ycf_symbol_list_from_text(b->buffer);
+ ycf_parse_result res;
+ if (n->type == ycf_node_type_c_file) {
+ res = parse_c_file(symbols.head);
+ } else if (n->type == ycf_node_type_variable_definition ||
+ n->type == ycf_node_type_variable_definition_init ||
+ n->type == ycf_node_type_other ||
+ n->type == ycf_node_type_code_scope ||
+ n->type == ycf_node_type_assignment ||
+ n->type == ycf_node_type_yield ||
+ n->type == ycf_node_type_statement ||
+ n->type == ycf_node_type_if ||
+ n->type == ycf_node_type_if_else ||
+ n->type == ycf_node_type_while ||
+ n->type == ycf_node_type_do_while ||
+ n->type == ycf_node_type_switch ||
+ n->type == ycf_node_type_for ||
+ n->type == ycf_node_type_assignment_function_call ||
+ n->type == ycf_node_type_on_save_yield_state_code ||
+ n->type == ycf_node_type_on_restore_yield_state_code ||
+ n->type == ycf_node_type_on_destroy_state_code ||
+ n->type == ycf_node_type_on_return_code ||
+ n->type == ycf_node_type_on_destroy_state_or_return_code ||
+ n->type == ycf_node_type_goto ||
+ n->type == ycf_node_type_return_statement ||
+ n->type == ycf_node_type_consume_reds) {
+ res = parse_statement(symbols.head);
+ } else if (n->type == ycf_node_type_function_declaration) {
+ res = parse_function_def(symbols.head);
+ } else if (n->type == ycf_node_type_function_definition) {
+ res = parse_function(symbols.head);
+ } else if (n->type == ycf_node_type_function_call) {
+ res = parse_function_call(symbols.head);
+ } else if (n->type == ycf_node_type_expression) {
+ res = parse_expression(symbols.head);
+ } else if (n->type == ycf_node_type_parentheses_expression) {
+ res = parse_paran_expression(symbols.head);
+ } else if (n->type == ycf_node_type_comma) {
+ res = parse_comma(symbols.head);
+ } else if (n->type == ycf_node_type_array_bracket) {
+ res = parse_array_bracket(symbols.head);
+ } else if (n->type == ycf_node_type_macro_cmd) {
+ res = parse_macro_command(symbols.head);
+ } else if (n->type == ycf_node_type_period_field_access) {
+ res = parse_period_field_access_node(symbols.head);
+ } else if (n->type == ycf_node_type_pointer_field_access) {
+ res = parse_pointer_field_access_node(symbols.head);
+ } else {
+ fprintf(stderr, "Unknown node type %d\n", n->type);
+ exit(1);
+ }
+ if(!res.success) {
+ fprintf(stderr, "An error has been detected in the function ycf_node_deep_copy\n");
+ exit(1);
+ }
+ return res.result;
+}
+
diff --git a/lib/erl_interface/src/legacy/erl_connect.h b/erts/lib_src/yielding_c_fun/ycf_parser.h
index 6cb5d5cd1b..566c7ec503 100644
--- a/lib/erl_interface/src/legacy/erl_connect.h
+++ b/erts/lib_src/yielding_c_fun/ycf_parser.h
@@ -1,8 +1,8 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -14,12 +14,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
+ *
* %CopyrightEnd%
*/
-#ifndef _ERL_CONNECT_H
-#define _ERL_CONNECT_H
-erlang_pid *erl_self(void);
+/*
+ * Author: Kjell Winblad
+ */
+
+#include "ycf_node.h"
-#endif /* _ERL_CONNECT_H */
+ycf_node* get_abstract_syntax_tree_root(ycf_symbol_list* symbols);
diff --git a/erts/lib_src/yielding_c_fun/ycf_printers.c b/erts/lib_src/yielding_c_fun/ycf_printers.c
new file mode 100644
index 0000000000..0cebfc5ebc
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_printers.c
@@ -0,0 +1,493 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ycf_yield_fun.h"
+#include "ycf_node.h"
+
+void print_node_code_expression(ycf_node_expression e, ycf_string_printable_buffer* b);
+
+void print_symbol_code(ycf_symbol* s, ycf_string_printable_buffer* b){
+ if(s == NULL){
+ return;
+ }
+ print_symbol_code(s->whitespace_or_comment_before, b);
+ ycf_string_printable_buffer_printf(b, "%s", ycf_symbol_get_text(s));
+}
+
+void print_symbol_list(ycf_symbol_list* l, ycf_string_printable_buffer* b){
+ ycf_symbol* s = l->head;
+ while(s != NULL){
+ print_symbol_code(s, b);
+ s = s->next;
+ }
+}
+
+void print_node_code_array_bracket(ycf_node_array_bracket n, ycf_string_printable_buffer* b){
+ print_symbol_code(n.start, b);
+ if(!n.empty){
+ print_node_code_expression(n.content, b);
+ }
+ print_symbol_code(n.end, b);
+}
+
+void print_node_code_definition_custom_end(ycf_node_definition d, bool dyn_array_to_ptr, ycf_symbol* end, ycf_string_printable_buffer* b){
+ int nr_of_empty_brackets = 0;
+ {
+ ycf_node* current = d.array_brackets.head;
+ while(current != NULL){
+ if(current->u.array_bracket.empty){
+ nr_of_empty_brackets++;
+ }
+ current = current->next;
+ }
+ }
+ print_symbol_list(&d.type_specifiers, b);
+ if(dyn_array_to_ptr){
+ for(int i = 0; i < nr_of_empty_brackets; i++){
+ ycf_string_printable_buffer_printf(b, "*");
+ }
+ }
+ print_symbol_code(d.identifier, b);
+ if (!dyn_array_to_ptr || nr_of_empty_brackets == 0){
+ print_node_list_code(d.array_brackets.head, b);
+ }
+ print_symbol_code(end, b);
+}
+
+void print_node_code_definition(ycf_node_definition d, ycf_string_printable_buffer* b){
+ print_node_code_definition_custom_end(d, false, d.end, b);
+}
+
+void print_node_code_definition_init(ycf_node_definition_init d, ycf_string_printable_buffer* b){
+ print_node_code_definition(d.definition, b);
+ print_symbol_list(&d.initializer_expression, b);
+ print_symbol_code(d.end, b);
+}
+
+void print_node_list_code(ycf_node* n, ycf_string_printable_buffer* b){
+ while(n != NULL){
+ ycf_node_print(n, b);
+ n = n->next;
+ }
+}
+
+void print_node_code_function_def_parameters(ycf_node_function_definition f_def, ycf_string_printable_buffer* b){
+ if(f_def.ignore_param_ending){
+ ycf_node* n = f_def.parameters.head;
+ while(n != NULL){
+ if(n->next == NULL){
+ print_node_code_definition_custom_end(n->u.definition, false, ycf_symbol_new_parenthesis(), b);
+ }else{
+ print_node_code_definition_custom_end(n->u.definition, false, ycf_symbol_new_comma(), b);
+ }
+ n = n->next;
+ }
+ if(f_def.end.head != NULL && f_def.end.head->type == ycf_symbol_type_end_parenthesis){
+ ycf_symbol_list end = f_def.end;
+ end.head = end.head->next;
+ print_symbol_list(&end, b);
+ }else if(f_def.end.head != NULL && f_def.end.head->type != ycf_symbol_type_void){
+ print_symbol_list(&f_def.end, b);
+ }
+ }else {
+ print_node_list_code(f_def.parameters.head, b);
+ print_symbol_list(&f_def.end, b);
+ }
+}
+void print_node_code_function_def(ycf_node_function_definition f_def, ycf_string_printable_buffer* b){
+ print_node_code_definition(f_def.definition, b);
+ print_node_code_function_def_parameters(f_def, b);
+}
+
+void print_node_code_scope(ycf_node_code_scope s, ycf_string_printable_buffer* b){
+ print_symbol_code(s.start, b);
+ print_node_list_code(s.definition_nodes.head, b);
+ print_node_list_code(s.other_nodes.head, b);
+ print_symbol_code(s.end, b);
+}
+
+void print_node_code_function(ycf_node_function f, ycf_string_printable_buffer* b){
+ print_node_code_function_def(f.definition, b);
+ print_node_code_scope(f.body, b);
+}
+
+void print_node_code_assignment(ycf_node_assignment a, ycf_string_printable_buffer* b){
+ print_node_code_expression(a.left_side, b);
+ print_symbol_code(a.assignment_symbol, b);
+ print_node_code_expression(a.right_side, b);
+ print_symbol_code(a.end, b);
+}
+
+void print_node_code_gen_struct(ycf_node_gen_typedef_struct n, ycf_string_printable_buffer* b){
+ ycf_string_printable_buffer_printf(b, "\n\n\nstruct %s {", n.name);
+ ycf_node* current = n.definition_nodes.head;
+ while(current != NULL){
+ print_node_code_definition_custom_end(current->u.definition, true, ycf_symbol_new_semicolon(), b);
+ current = current->next;
+ }
+ ycf_string_printable_buffer_printf(b, "\n};\n");
+}
+
+void print_node_code_yield(ycf_node_yield n, ycf_string_printable_buffer* b){
+ print_symbol_code(n.yield_symbol,b);
+ print_symbol_code(n.end_symbol,b);
+
+}
+
+void print_node_code_function_call(ycf_node_function_call n, ycf_string_printable_buffer* b){
+ print_symbol_list(&n.neg_symbols, b);
+ print_symbol_code(n.identifier, b);
+ print_symbol_code(n.start_symbol, b);
+ print_node_list_code(n.parameter_expressions.head, b);
+ print_symbol_code(n.end_symbol, b);
+}
+
+void print_node_code_expression(ycf_node_expression e, ycf_string_printable_buffer* b){
+ print_node_list_code(e.content.head, b);
+}
+
+
+void print_node_code_paran_expression(ycf_node_parentheses_expression e, ycf_string_printable_buffer* b){
+ print_symbol_code(e.start_symbol, b);
+ print_node_code_expression(e.content, b);
+ print_symbol_code(e.end_symbol, b);
+}
+
+void print_node_code_consume_reds(ycf_node_consume_reds e, ycf_string_printable_buffer* b){
+ print_symbol_code(e.consume_reds_symbol, b);
+ print_node_code_paran_expression(e.nr_of_reds_expression, b);
+ print_symbol_code(e.end_symbol, b);
+}
+
+void print_node_code_if_statement(ycf_node_if e, ycf_string_printable_buffer* b){
+ print_symbol_code(e.if_word, b);
+ print_node_code_paran_expression(e.expression, b);
+ ycf_node_print(e.if_statement, b);
+}
+
+void print_node_code_while_statement(ycf_node_while e, ycf_string_printable_buffer* b){
+ print_symbol_code(e.while_word, b);
+ print_node_code_paran_expression(e.expression, b);
+ ycf_node_print(e.statement, b);
+}
+
+void print_node_code_do_while_statement(ycf_node_do_while e, ycf_string_printable_buffer* b){
+ print_symbol_code(e.do_word, b);
+ ycf_node_print(e.statement, b);
+ print_symbol_code(e.while_word, b);
+ print_node_code_paran_expression(e.expression, b);
+ print_symbol_code(e.end, b);
+}
+
+void print_node_code_switch_statement(ycf_node_switch e, ycf_string_printable_buffer* b){
+ print_symbol_code(e.switch_word, b);
+ print_node_code_paran_expression(e.expression, b);
+ print_node_code_scope(e.scope, b);
+}
+
+void print_node_code_for_statement(ycf_node_for e, ycf_string_printable_buffer* b){
+ print_symbol_code(e.for_word, b);
+ print_symbol_code(e.start_parentheses, b);
+ ycf_node_print(e.init, b);
+ ycf_node_print(e.stop_cond, b);
+ print_symbol_code(e.stop_cond_end, b);
+ ycf_node_print(e.end_exp, b);
+ print_symbol_code(e.end_parentheses, b);
+ ycf_node_print(e.statement, b);
+}
+
+void print_node_code_if_else_statement(ycf_node_if_else e, ycf_string_printable_buffer* b){
+ print_node_code_if_statement(e.if_part, b);
+ print_symbol_code(e.else_word, b);
+ ycf_node_print(e.else_statement, b);
+}
+
+void print_node_code_for_assignment_fun_call(ycf_node_function_call_assignment e, ycf_string_printable_buffer* b){
+ print_node_code_expression(e.left_side, b);
+ print_symbol_code(e.assignment_symbol, b);
+ print_node_code_function_call(e.fun_call, b);
+}
+
+void print_node_code_special_code_block(ycf_node_special_code_block block, ycf_string_printable_buffer* b){
+ print_symbol_code(block.start, b);
+ print_node_code_if_statement(block.code, b);
+ print_symbol_code(block.end, b);
+
+}
+
+void print_node_code_goto(ycf_node_goto n, ycf_string_printable_buffer* b){
+ print_symbol_code(n.goto_symbol, b);
+ print_symbol_code(n.label_symbol, b);
+ print_symbol_code(n.end_symbol, b);
+}
+
+void print_node_code_period_field_access(ycf_node_period_field_access n, ycf_string_printable_buffer* b){
+ print_symbol_code(n.period, b);
+ print_symbol_code(n.field_name, b);
+}
+
+void print_node_code_pointer_field_access(ycf_pointer_field_access n, ycf_string_printable_buffer* b){
+ print_symbol_code(n.pointer, b);
+ print_symbol_code(n.field_name, b);
+}
+
+void print_node_code_return(ycf_node_return n, ycf_string_printable_buffer* b){
+ print_symbol_code(n.return_symbol, b);
+ if (n.return_expression != NULL) {
+ ycf_node_print(n.return_expression, b);
+ }
+ print_symbol_code(n.end_symbol, b);
+}
+
+void ycf_node_print(ycf_node* node, ycf_string_printable_buffer* b){
+ if(node == NULL){
+ return;
+ } else if(node->type == ycf_node_type_c_file){
+ print_node_list_code(node->u.c_file.content.head, b);
+ } else if(node->type == ycf_node_type_variable_definition){
+ print_node_code_definition(node->u.definition, b);
+ } else if(node->type == ycf_node_type_variable_definition_init){
+ print_node_code_definition_init(node->u.definition_init, b);
+ } else if(node->type == ycf_node_type_function_declaration){
+ print_node_code_function_def(node->u.function_definition, b);
+ } else if(node->type == ycf_node_type_other){
+ print_symbol_code(node->u.other.what, b);
+ } else if(node->type == ycf_node_type_code_scope){
+ print_node_code_scope(node->u.code_scope, b);
+ }else if(node->type == ycf_node_type_function_definition){
+ print_node_code_function(node->u.function, b);
+ }else if(node->type == ycf_node_type_assignment){
+ print_node_code_assignment(node->u.a, b);
+ }else if(node->type == ycf_node_type_gen_typedef_struct){
+ print_node_code_gen_struct(node->u.gen_typedef_struct, b);
+ }else if(node->type == ycf_node_type_yield){
+ print_node_code_yield(node->u.yield, b);
+ } else if(node->type == ycf_node_type_statement){
+ ycf_node_print(node->u.statement.expression, b);
+ print_symbol_code(node->u.statement.end_symbol, b);
+ } else if(node->type == ycf_node_type_function_call){
+ print_node_code_function_call(node->u.function_call, b);
+ } else if(node->type == ycf_node_type_expression){
+ print_node_code_expression(node->u.expression, b);
+ } else if(node->type == ycf_node_type_parentheses_expression){
+ print_node_code_paran_expression(node->u.parentheses_expression, b);
+ } else if(node->type == ycf_node_type_if){
+ print_node_code_if_statement(node->u.if_n, b);
+ }else if(node->type == ycf_node_type_if_else){
+ print_node_code_if_else_statement(node->u.if_else, b);
+ }else if(node->type == ycf_node_type_while){
+ print_node_code_while_statement(node->u.while_n, b);
+ }else if(node->type == ycf_node_type_do_while){
+ print_node_code_do_while_statement(node->u.do_while, b);
+ }else if(node->type == ycf_node_type_switch){
+ print_node_code_switch_statement(node->u.switch_n, b);
+ }else if(node->type == ycf_node_type_for){
+ print_node_code_for_statement(node->u.for_n, b);
+ }else if(node->type == ycf_node_type_assignment_function_call){
+ print_node_code_for_assignment_fun_call(node->u.function_call_assignment, b);
+ } else if(node->type == ycf_node_type_comma){
+ print_symbol_code(node->u.comma.comma_symbol, b);
+ } else if(node->type == ycf_node_type_array_bracket){
+ print_node_code_array_bracket(node->u.array_bracket, b);
+ } else if(node->type == ycf_node_type_macro_cmd){
+ print_symbol_code(node->u.macro_cmd.symbol, b);
+ } else if(node->type == ycf_node_type_on_save_yield_state_code ||
+ node->type == ycf_node_type_on_restore_yield_state_code ||
+ node->type == ycf_node_type_on_destroy_state_code ||
+ node->type == ycf_node_type_on_return_code ||
+ node->type == ycf_node_type_on_destroy_state_or_return_code){
+ print_node_code_special_code_block(node->u.special_code_block, b);
+ } else if(node->type == ycf_node_type_consume_reds){
+ print_node_code_consume_reds(node->u.consume_reds, b);
+ } else if(node->type == ycf_node_type_goto){
+ print_node_code_goto(node->u.goto_n, b);
+ } else if(node->type == ycf_node_type_return_statement){
+ print_node_code_return(node->u.return_n, b);
+ } else if(node->type == ycf_node_type_period_field_access){
+ print_node_code_period_field_access(node->u.period_field_access, b);
+ } else if(node->type == ycf_node_type_pointer_field_access){
+ print_node_code_pointer_field_access(node->u.pointer_field_access, b);
+ } else {
+ fprintf(stderr, "Unknown node type %d\n", node->type);
+ exit(1);
+ }
+}
+
+
+void print_definition(ycf_node_definition d){
+ printf("NODE: definition (type=%s,%s=%s)\n",
+ ycf_symbol_text_between(d.type_specifiers.head,
+ d.type_specifiers.last),
+ get_symbol_type_text(d.identifier->type),
+ ycf_symbol_get_text(d.identifier) );
+}
+
+void print_scope(ycf_node_code_scope node){
+ printf("NODE: scope\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ printf("Defenition Nodes:\n");
+ struct ycf_node* n = node.definition_nodes.head;
+ while(n != NULL){
+ print_abstract_syntax_tree(n);
+ n = n->next;
+ }
+ printf("Other Nodes:\n");
+ n = node.other_nodes.head;
+ while(n != NULL){
+ print_abstract_syntax_tree(n);
+ n = n->next;
+ }
+ printf("END SCOPE\n");
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+}
+
+void print_function_def(ycf_node_function_definition node){
+ printf("NODE: function def:\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ printf("definition:\n");
+ print_definition(node.definition);
+ printf("parameters:\n");
+ struct ycf_node* n = node.parameters.head;
+ while(n != NULL){
+ print_abstract_syntax_tree(n);
+ n = n->next;
+ }
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+}
+
+void print_fun_call(ycf_node_function_call f_call){
+ printf("NODE: fun_call %s parameters:\n", ycf_symbol_get_text(f_call.identifier));
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ struct ycf_node* current = f_call.parameter_expressions.head;
+ int i = 1;
+ while(current != NULL){
+ printf("param %d: \n", i);
+ ycf_node_print(current, NULL);
+ current = current->next;
+ i++;
+ }
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+}
+
+void print_abstract_syntax_tree(ycf_node* node){
+ printf("%p\n",(void*)node);
+ if(node == NULL){
+ return;
+ } else if(node->type == ycf_node_type_c_file){
+ printf("NODE: c_file\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ struct ycf_node* n = node->u.c_file.content.head;
+ while(n != NULL){
+ print_abstract_syntax_tree(n);
+ n = n->next;
+ }
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ } else if(node->type == ycf_node_type_variable_definition){
+ print_definition(node->u.definition);
+ } else if(node->type == ycf_node_type_variable_definition_init){
+ printf("NODE: definition_init (type=%s,%s=%s,init=%s)\n",
+ ycf_symbol_text_between(node->u.definition_init.definition.type_specifiers.head,
+ node->u.definition_init.definition.type_specifiers.last),
+ get_symbol_type_text(node->u.definition_init.definition.identifier->type),
+ ycf_symbol_get_text(node->u.definition_init.definition.identifier),
+ ycf_symbol_text_between(node->u.definition_init.initializer_expression.head,
+ node->u.definition_init.initializer_expression.last));
+ } else if(node->type == ycf_node_type_function_declaration){
+ print_function_def(node->u.function_definition);
+ } else if(node->type == ycf_node_type_other){
+ printf("NODE: other (%s)\n", get_symbol_type_text(node->u.other.what->type));
+ } else if(node->type == ycf_node_type_code_scope){
+ print_scope(node->u.code_scope);
+ }else if(node->type == ycf_node_type_function_definition){
+ printf("NODE: function\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ print_function_def(node->u.function.definition);
+ print_scope(node->u.function.body);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }else if(node->type == ycf_node_type_assignment){
+ printf("NODE: assignment\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ print_node_code_assignment(node->u.a, NULL);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }else if(node->type == ycf_node_type_yield){
+ printf("NODE: yield\n");
+ }else if(node->type == ycf_node_type_statement){
+ printf("NODE: statement\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ print_abstract_syntax_tree(node->u.statement.expression);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }else if(node->type == ycf_node_type_if){
+ printf("NODE: if_statement\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ ycf_node_print(node, NULL);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }else if(node->type == ycf_node_type_if_else){
+ printf("NODE: if_else_statement\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ ycf_node_print(node, NULL);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }else if(node->type == ycf_node_type_while){
+ printf("NODE: while_statement\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ ycf_node_print(node, NULL);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }else if(node->type == ycf_node_type_do_while){
+ printf("NODE: do_while_statement\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ ycf_node_print(node, NULL);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }else if(node->type == ycf_node_type_for){
+ printf("NODE: for_statement\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ ycf_node_print(node, NULL);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }else if(node->type == ycf_node_type_switch){
+ printf("NODE: switch_statement\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ ycf_node_print(node, NULL);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ } else if(node->type == ycf_node_type_function_call) {
+ print_fun_call(node->u.function_call);
+ } else if(node->type == ycf_node_type_assignment_function_call) {
+ printf("NODE: assignment fun call\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ printf("Assignment expression:\n");
+ print_node_code_expression(node->u.function_call_assignment.left_side, NULL);
+ print_fun_call(node->u.function_call_assignment.fun_call);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+
+ } else {
+ printf("NODE: OTHER???\n");
+ printf(">>>>>>>>>>>>>>>>>>>>>>>\n");
+ ycf_node_print(node, NULL);
+ printf("<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+
+}
+
diff --git a/lib/erl_interface/src/legacy/erl_fix_alloc.h b/erts/lib_src/yielding_c_fun/ycf_printers.h
index 50d1368e34..1cc23165c8 100644
--- a/lib/erl_interface/src/legacy/erl_fix_alloc.h
+++ b/erts/lib_src/yielding_c_fun/ycf_printers.h
@@ -1,8 +1,8 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -14,14 +14,17 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
+ *
* %CopyrightEnd%
*/
-#ifndef _ERL_FIX_ALLOC_H
-#define _ERL_FIX_ALLOC_H
-int erl_init_eterm_alloc(void);
-void erl_eterm_free(void*);
-void *erl_eterm_alloc(void);
+/*
+ * Author: Kjell Winblad
+ */
+
+#include "ycf_node.h"
+
+void print_node_code_function_def_parameters(ycf_node_function_definition f_def, ycf_string_printable_buffer* b);
+void print_node_code_function_def(ycf_node_function_definition f_def, ycf_string_printable_buffer* b);
+void print_node_list_code(ycf_node* n, ycf_string_printable_buffer* b);
-#endif /* _ERL_FIX_ALLOC_H */
diff --git a/erts/lib_src/yielding_c_fun/ycf_string.c b/erts/lib_src/yielding_c_fun/ycf_string.c
new file mode 100644
index 0000000000..aabb0c3c61
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_string.c
@@ -0,0 +1,111 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "ycf_string.h"
+#include "ycf_lists.h"
+#include "ycf_utils.h"
+
+bool ycf_string_is_equal(const char* str1, const char* str2)
+{
+ size_t str1_length = strlen(str1);
+ size_t str2_length = strlen(str2);
+ return
+ str1_length == str2_length &&
+ strncmp(str1, str2, str1_length) == 0;
+}
+
+
+ycf_string_item* ycf_string_item_new(char* str){
+ ycf_string_item* item = ycf_malloc(sizeof(ycf_string_item));
+ item->str = str;
+ item->next = NULL;
+ return item;
+}
+
+char* ycf_string_new(char* format, ...){
+ va_list args;
+ va_start (args, format);
+ int n = vsnprintf(NULL, 0, format, args);
+ va_end (args);
+ char* new = ycf_malloc(n +1);
+ va_start (args, format);
+ vsnprintf(new, n +1, format, args);
+ va_end (args);
+ return new;
+}
+
+
+ycf_string_printable_buffer* ycf_string_printable_buffer_new(){
+ const size_t init_size = 128;
+ ycf_string_printable_buffer* b = ycf_malloc(sizeof(ycf_string_printable_buffer));
+ b->current_pos = 0;
+ b->buffer = ycf_malloc(init_size);
+ b->size = init_size;
+ return b;
+}
+
+void ycf_string_printable_buffer_printf(ycf_string_printable_buffer* buf, char* format, ...){
+ va_list args;
+ if(buf == NULL){
+ va_start (args, format);
+ vprintf(format, args);
+ va_end (args);
+ return;
+ }
+ va_start (args, format);
+ int n = vsnprintf(NULL, 0, format, args) + 1;
+ va_end (args);
+ while(buf->current_pos + n + 64 > buf->size){
+ char* new_buf = ycf_malloc(buf->size * 2);
+ for(int i = 0; i < buf->size; i++){
+ new_buf[i] = buf->buffer[i];
+ }
+ buf->buffer = new_buf;
+ buf->size = buf->size * 2;
+ }
+ va_start (args, format);
+ vsnprintf(&buf->buffer[buf->current_pos], n, format, args);
+ va_end (args);
+ buf->current_pos = buf->current_pos + n - 1;
+}
+
+bool ycf_string_item_list_contains(ycf_string_item_list* l, char* str){
+ ycf_string_item* current = l->head;
+ while(current != NULL){
+ if(strcmp(current->str, str) == 0){
+ return true;
+ }
+ current = current->next;
+ }
+ return false;
+}
+
+
+GENERATE_LIST_FUNCTIONS(ycf_string_item)
diff --git a/erts/lib_src/yielding_c_fun/ycf_string.h b/erts/lib_src/yielding_c_fun/ycf_string.h
new file mode 100644
index 0000000000..0c19e08572
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_string.h
@@ -0,0 +1,89 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+#ifndef YIELDING_C_FUN_YCF_STRING_H
+#define YIELDING_C_FUN_YCF_STRING_H
+
+#include <stdlib.h>
+#include "ycf_utils.h"
+
+/* Types for printable buffers */
+
+typedef struct {
+ char* buffer;
+ size_t current_pos;
+ size_t size;
+} ycf_string_printable_buffer;
+
+/* Types for string lists */
+
+typedef struct ycf_string_item {
+ char* str;
+ struct ycf_string_item* next;
+} ycf_string_item;
+
+typedef struct string_item_list {
+ ycf_string_item* head;
+ ycf_string_item* last;
+} ycf_string_item_list;
+
+
+bool ycf_string_is_equal(const char* str1, const char* str2);
+ycf_string_item* ycf_string_item_new(char* str);
+char* ycf_string_new(char* format, ...);
+
+/* Functions for printable buffers */
+
+ycf_string_printable_buffer* ycf_string_printable_buffer_new(void);
+void ycf_string_printable_buffer_printf(ycf_string_printable_buffer* buf, char* format, ...);
+
+/* Functions for string lists */
+
+int ycf_string_item_list_get_item_position(ycf_string_item_list* list, ycf_string_item* node);
+
+bool ycf_string_item_list_contains(ycf_string_item_list* l, char* str);
+
+ycf_string_item* ycf_string_item_shallow_copy(ycf_string_item* n);
+ycf_string_item* ycf_string_item_list_get_item_at_position(ycf_string_item_list* list, int pos);
+
+void ycf_string_item_list_append(ycf_string_item_list* list, ycf_string_item* node);
+void ycf_string_item_list_prepend(ycf_string_item_list* list, ycf_string_item* node);
+void ycf_string_item_list_insert_before(ycf_string_item_list* list, ycf_string_item* before_this, ycf_string_item* to_insert);
+void ycf_string_item_list_insert_after(ycf_string_item_list* list, ycf_string_item* after_this, ycf_string_item* to_insert);
+void ycf_string_item_list_remove(ycf_string_item_list* list, ycf_string_item* to_remove);
+void ycf_string_item_list_replace(ycf_string_item_list* list, ycf_string_item* to_replace, ycf_string_item* replace_with);
+void ycf_string_item_list_concat(ycf_string_item_list* list1, ycf_string_item_list* list2);
+
+ycf_string_item_list ycf_string_item_list_empty();
+ycf_string_item_list ycf_string_item_list_shallow_copy(ycf_string_item_list n);
+ycf_string_item_list ycf_string_item_list_copy_append(ycf_string_item_list list, ycf_string_item* node);
+ycf_string_item_list ycf_string_item_list_copy_prepend(ycf_string_item_list list, ycf_string_item* node);
+ycf_string_item_list ycf_string_item_list_copy_insert_before(ycf_string_item_list list, ycf_string_item* before_this, ycf_string_item* to_insert);
+ycf_string_item_list ycf_string_item_list_copy_insert_after(ycf_string_item_list list, ycf_string_item* after_this, ycf_string_item* to_insert);
+ycf_string_item_list ycf_string_item_list_copy_remove(ycf_string_item_list list, ycf_string_item* to_remove);
+ycf_string_item_list ycf_string_item_list_copy_replace(ycf_string_item_list list, ycf_string_item* to_replace, ycf_string_item* replace_with);
+ycf_string_item_list ycf_string_item_list_copy_concat(ycf_string_item_list list1, ycf_string_item_list list2);
+
+
+#endif //YIELDING_C_FUN_YCF_STRING_H
diff --git a/erts/lib_src/yielding_c_fun/ycf_symbol.c b/erts/lib_src/yielding_c_fun/ycf_symbol.c
new file mode 100644
index 0000000000..826aac3afd
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_symbol.c
@@ -0,0 +1,166 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "ycf_symbol.h"
+#include "ycf_string.h"
+#include "ycf_utils.h"
+#include "ycf_lists.h"
+
+
+
+ycf_symbol* ycf_symbol_copy(ycf_symbol* symbol){
+ ycf_symbol* new_symbol = ycf_malloc(sizeof(ycf_symbol));
+ new_symbol->next = NULL;
+ new_symbol->source = symbol->source;
+ new_symbol->start = symbol->start;
+ new_symbol->stop = symbol->stop;
+ new_symbol->type = symbol->type;
+ new_symbol->whitespace_or_comment_before = symbol->whitespace_or_comment_before;
+ return new_symbol;
+}
+
+ycf_symbol* ycf_symbol_copy_change_text(ycf_symbol* to_copy, char* new_text){
+ ycf_symbol* new_symbol = ycf_malloc(sizeof(ycf_symbol));
+ new_symbol->next = NULL;
+ new_symbol->source = new_text;
+ new_symbol->start = 0;
+ new_symbol->stop = strlen(new_text);
+ new_symbol->type = to_copy->type;
+ new_symbol->whitespace_or_comment_before = to_copy->whitespace_or_comment_before;
+ return new_symbol;
+}
+
+ycf_symbol* ycf_symbol_new_something_else(char* text){
+ ycf_symbol* new = ycf_malloc(sizeof(ycf_symbol));
+ new->type = ycf_symbol_type_something_else;
+ new->next = NULL;
+ new->source = text;
+ new->whitespace_or_comment_before = NULL;
+ new->start = 0;
+ new->stop = strlen(text);
+ return new;
+}
+
+ycf_symbol* ycf_symbol_new_semicolon(){
+ ycf_symbol* new_symbol = ycf_malloc(sizeof(ycf_symbol));
+ new_symbol->next = NULL;
+ new_symbol->source = ";";
+ new_symbol->start = 0;
+ new_symbol->stop = strlen(";");
+ new_symbol->type = ycf_symbol_type_semicolon;
+ new_symbol->whitespace_or_comment_before = NULL;
+ return new_symbol;
+}
+
+ycf_symbol* ycf_symbol_new_identifier(char* name){
+ ycf_symbol* new_symbol = ycf_malloc(sizeof(ycf_symbol));
+ new_symbol->next = NULL;
+ new_symbol->source = name;
+ new_symbol->start = 0;
+ new_symbol->stop = strlen(new_symbol->source);
+ new_symbol->type = ycf_symbol_type_identifier;
+ new_symbol->whitespace_or_comment_before = NULL;
+ return new_symbol;
+}
+
+ycf_symbol* ycf_symbol_new_star(){
+ ycf_symbol* new_symbol = ycf_malloc(sizeof(ycf_symbol));
+ new_symbol->next = NULL;
+ new_symbol->source = "*";
+ new_symbol->start = 0;
+ new_symbol->stop = strlen("*");
+ new_symbol->type = ycf_symbol_type_star;
+ new_symbol->whitespace_or_comment_before = NULL;
+ return new_symbol;
+}
+
+ycf_symbol* ycf_symbol_new_parenthesis(){
+ ycf_symbol* new_symbol = ycf_malloc(sizeof(ycf_symbol));
+ new_symbol->next = NULL;
+ new_symbol->source = ")";
+ new_symbol->start = 0;
+ new_symbol->stop = strlen(")");
+ new_symbol->type = ycf_symbol_type_semicolon;
+ new_symbol->whitespace_or_comment_before = NULL;
+ return new_symbol;
+}
+
+ycf_symbol* ycf_symbol_new_open_curly_brace(){
+ ycf_symbol* new_symbol = ycf_malloc(sizeof(ycf_symbol));
+ new_symbol->next = NULL;
+ new_symbol->source = "\n{\n";
+ new_symbol->start = 0;
+ new_symbol->stop = strlen(new_symbol->source);
+ new_symbol->type = ycf_symbol_type_open_curly_brace;
+ new_symbol->whitespace_or_comment_before = NULL;
+ return new_symbol;
+}
+
+ycf_symbol* ycf_symbol_new_end_curly_brace(){
+ ycf_symbol* new_symbol = ycf_malloc(sizeof(ycf_symbol));
+ new_symbol->next = NULL;
+ new_symbol->source = "\n}\n";
+ new_symbol->start = 0;
+ new_symbol->stop = strlen(new_symbol->source);
+ new_symbol->type = ycf_symbol_type_end_curly_brace;
+ new_symbol->whitespace_or_comment_before = NULL;
+ return new_symbol;
+}
+
+
+ycf_symbol* ycf_symbol_new_comma(){
+ ycf_symbol* new_symbol = ycf_malloc(sizeof(ycf_symbol));
+ new_symbol->next = NULL;
+ new_symbol->source = ",";
+ new_symbol->start = 0;
+ new_symbol->stop = strlen(",");
+ new_symbol->type = ycf_symbol_type_semicolon;
+ new_symbol->whitespace_or_comment_before = NULL;
+ return new_symbol;
+}
+
+
+char* ycf_symbol_get_text(ycf_symbol* symbol){
+ int size = symbol->stop - symbol->start;
+ char* str = ycf_malloc(size+1);
+ strncpy(str, &symbol->source[symbol->start], size);
+ str[size] = 0;
+ return str;
+}
+
+char* ycf_symbol_list_to_str(ycf_symbol_list* l){
+ ycf_symbol* s = l->head;
+ char* str = "";
+ while(s != NULL){
+ str = ycf_string_new(" %s %s ", str, ycf_symbol_get_text(s));
+ s = s->next;
+ }
+ return str;
+}
+
+GENERATE_LIST_FUNCTIONS(ycf_symbol)
diff --git a/erts/lib_src/yielding_c_fun/ycf_symbol.h b/erts/lib_src/yielding_c_fun/ycf_symbol.h
new file mode 100644
index 0000000000..b6d69cc5cb
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_symbol.h
@@ -0,0 +1,110 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+#ifndef YIELDING_C_FUN_YCF_SYMBOL_H
+#define YIELDING_C_FUN_YCF_SYMBOL_H
+
+/* Types for symbols */
+
+typedef enum {
+ ycf_symbol_type_comment,
+ ycf_symbol_type_string_literal,
+ ycf_symbol_type_macro_define,
+ ycf_symbol_type_macro_command,
+ ycf_symbol_type_whitespace,
+ ycf_symbol_type_identifier,
+ ycf_symbol_type_number,
+ ycf_symbol_type_open_parenthesis,
+ ycf_symbol_type_end_parenthesis,
+ ycf_symbol_type_open_curly_brace,
+ ycf_symbol_type_end_curly_brace,
+ ycf_symbol_type_open_square_bracket,
+ ycf_symbol_type_end_square_bracket,
+ ycf_symbol_type_not_equal_sign,
+ ycf_symbol_type_equal_sign,
+ ycf_symbol_type_equal_equal_sign,
+ ycf_symbol_type_star,
+ ycf_symbol_type_neg,
+ ycf_symbol_type_semicolon,
+ ycf_symbol_type_comma,
+ ycf_symbol_type_pointer_field_access,
+ ycf_symbol_type_period,
+ ycf_symbol_type_special_code_start,
+ ycf_symbol_type_special_code_end,
+ ycf_symbol_type_const,
+ ycf_symbol_type_void,
+ ycf_symbol_type_static,
+ ycf_symbol_type_inline,
+ ycf_symbol_type_volatile,
+ ycf_symbol_type_consume_reds,
+ ycf_symbol_type_return,
+ ycf_symbol_type_if,
+ ycf_symbol_type_else,
+ ycf_symbol_type_goto,
+ ycf_symbol_type_while,
+ ycf_symbol_type_do,
+ ycf_symbol_type_for,
+ ycf_symbol_type_switch,
+ ycf_symbol_type_break,
+ ycf_symbol_type_continue,
+ ycf_symbol_type_something_else
+} ycf_symbol_type;
+
+
+typedef struct ycf_symbol {
+ ycf_symbol_type type;
+ int start;
+ int stop;
+ char* source;
+ struct ycf_symbol* whitespace_or_comment_before;
+ struct ycf_symbol* next;
+} ycf_symbol;
+
+typedef struct symbol_list {
+ struct ycf_symbol* head;
+ struct ycf_symbol* last;
+} ycf_symbol_list;
+
+/* Functions for symbols */
+
+ycf_symbol_list ycf_symbol_list_shallow_copy(ycf_symbol_list n);
+
+ycf_symbol* ycf_symbol_new_something_else(char* text);
+ycf_symbol* ycf_symbol_copy_change_text(ycf_symbol* to_copy, char* new_text);
+ycf_symbol* ycf_symbol_new_semicolon(void);
+ycf_symbol* ycf_symbol_new_star();
+ycf_symbol* ycf_symbol_new_parenthesis(void);
+ycf_symbol* ycf_symbol_new_comma(void);
+ycf_symbol* ycf_symbol_new_open_curly_brace(void);
+ycf_symbol* ycf_symbol_new_end_curly_brace(void);
+ycf_symbol* ycf_symbol_new_identifier(char* name);
+char* ycf_symbol_get_text(ycf_symbol* symbol);
+ycf_symbol_list ycf_symbol_list_from_text(char* text);
+void ycf_symbol_list_print(char* text);
+char* get_symbol_type_text(ycf_symbol_type type);
+ycf_symbol* ycf_symbol_copy(ycf_symbol* symbol);
+char* ycf_symbol_text_between(ycf_symbol* s1, ycf_symbol* s2);
+int ycf_symbol_is_text_eq(ycf_symbol* symbol, char* str);
+char* ycf_symbol_list_to_str(ycf_symbol_list* l);
+#endif //YIELDING_C_FUN_YCF_SYMBOL_H
diff --git a/erts/lib_src/yielding_c_fun/ycf_utils.c b/erts/lib_src/yielding_c_fun/ycf_utils.c
new file mode 100644
index 0000000000..1c51043b41
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_utils.c
@@ -0,0 +1,106 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "ycf_utils.h"
+#include "lib/simple_c_gc/simple_c_gc.h"
+#include <stdint.h>
+
+bool ycf_use_gc = false;
+bool ycf_track_memory = false;
+
+size_t ycf_memory_usage = 0;
+size_t ycf_max_memory_usage = 0;
+
+void ycf_enable_gc(){
+ ycf_use_gc = true;
+}
+
+void ycf_enable_memory_tracking(){
+ ycf_track_memory = true;
+}
+
+void ycf_malloc_log(char* log_file, char* log_entry_id) {
+ FILE* out = fopen(log_file, "a");
+ fprintf(out,
+ "(%s)\nMax memory consumption %zu bytes ~ %zu kilo bytes ~ %zu mega bytes (after=%zu)\n",
+ log_entry_id,
+ ycf_max_memory_usage,
+ ycf_max_memory_usage / 1000,
+ ycf_max_memory_usage / 1000000,
+ ycf_memory_usage);
+ fclose(out);
+}
+
+void* ycf_raw_malloc(size_t size) {
+ if (ycf_track_memory) {
+ void* block = malloc(size + sizeof(intptr_t));
+ intptr_t* size_ptr = block;
+ *size_ptr = size + sizeof(intptr_t);
+ ycf_memory_usage = ycf_memory_usage + size + sizeof(intptr_t);
+ if (ycf_memory_usage > ycf_max_memory_usage) {
+ ycf_max_memory_usage = ycf_memory_usage;
+ }
+ if(block == NULL) {
+ fprintf(stderr, "ycf_malloc failed: is there enough memory in the machine?\n");
+ exit(1);
+ }
+ return (void*)(((char*)block) + sizeof(intptr_t));
+ } else {
+ void* block = malloc(size);
+ if(block == NULL) {
+ fprintf(stderr, "ycf_malloc failed: is there enough memory in the machine?\n");
+ exit(1);
+ }
+ return block;
+ }
+}
+
+void* ycf_malloc(size_t size) {
+ if (ycf_use_gc) {
+ return scgc_new(size);
+ } else {
+ return ycf_raw_malloc(size);
+ }
+}
+
+
+void ycf_free(void* to_free) {
+ if (ycf_track_memory) {
+ char* to_free_cp = to_free;
+ char* start = to_free_cp - sizeof(intptr_t);
+ intptr_t* size_ptr = (intptr_t*)start;
+ ycf_memory_usage = ycf_memory_usage - *size_ptr;
+ free(start);
+ } else {
+ free(to_free);
+ }
+}
+
+
+
diff --git a/lib/erl_interface/src/legacy/erl_marshal.h b/erts/lib_src/yielding_c_fun/ycf_utils.h
index c1963b832d..d713fafc09 100644
--- a/lib/erl_interface/src/legacy/erl_marshal.h
+++ b/erts/lib_src/yielding_c_fun/ycf_utils.h
@@ -1,8 +1,8 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -14,17 +14,27 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
+ *
* %CopyrightEnd%
*/
-#ifndef _ERL_MARSHALL_H
-#define _ERL_MARSHALL_H
-#include "erl_eterm.h" /* FIXME don't want to include this here */
+/*
+ * Author: Kjell Winblad
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <stddef.h>
+#include <stdbool.h>
+
+
+void ycf_enable_gc(void);
+void ycf_enable_memory_tracking(void);
+void* ycf_malloc(size_t size);
+void ycf_malloc_log(char* log_file, char* log_entry_id);
+void* ycf_raw_malloc(size_t size);
+void ycf_free(void* to_free);
-/* FIXME: not documented, may be internal */
-int erl_verify_magic(unsigned char*);
-void erl_init_marshal(void);
-int erl_encode_it(ETERM *ep, unsigned char **ext, int dist);
-#endif /* _ERL_MARSHALL_H */
+#endif
diff --git a/erts/lib_src/yielding_c_fun/ycf_yield_fun.c b/erts/lib_src/yielding_c_fun/ycf_yield_fun.c
new file mode 100644
index 0000000000..a597df87c0
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_yield_fun.c
@@ -0,0 +1,1625 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+
+#include "ycf_utils.h"
+#include "ycf_yield_fun.h"
+#include "ycf_node.h"
+#include "ycf_symbol.h"
+#include "ycf_string.h"
+#include "ycf_printers.h"
+#include "ycf_parser.h"
+
+static int ycf_yield_location_id_counter = 0;
+
+static
+ycf_node* mk_typedef_struct_node(ycf_node_list definitions, char* name){
+ ycf_node* res = ycf_malloc(sizeof(ycf_node));
+ res->next = NULL;
+ res->type = ycf_node_type_gen_typedef_struct;
+ res->u.gen_typedef_struct.definition_nodes = definitions;
+ res->u.gen_typedef_struct.name = name;
+ return res;
+}
+
+static
+ycf_node_list mk_ycf_trap_state_params(){
+ return ycf_node_definition_list_from_string(ycf_string_new(
+ "long* ycf_nr_of_reductions_param;\n"
+ "void** ycf_trap_state;\n"
+ "void* ycf_extra_context;\n"));
+}
+
+static
+ycf_node_list mk_saved_ycf_trap_state_params(){
+ return ycf_node_definition_list_from_string(
+ "ycf_yield_alloc_type ycf_yield_alloc;\n"
+ "ycf_yield_free_type ycf_yield_free;\n"
+ "void* ycf_yield_alloc_free_context;\n"
+ "size_t ycf_stack_alloc_size_or_max_size;\n"
+ "void* ycf_stack_alloc_data;\n");
+}
+
+static
+ycf_node_list mk_trap_extra_state(){
+ return ycf_node_definition_list_from_string(
+ "int ycf_trap_location;\n"
+ "long ycf_nr_of_reductions;\n"
+ "struct ycf_alloc_data ycf_frame_alloc_data;\n");;
+}
+
+static
+char* get_ycf_trap_state_assignments(ycf_node_list ycf_trap_state_defines){
+ ycf_node* current = ycf_trap_state_defines.head;
+ char* str = "";
+ while(current != NULL){
+ char* ident = ycf_symbol_get_text(current->u.definition.identifier);
+ if(current->u.definition.array_brackets.head != NULL && !current->u.definition.array_brackets.head->u.array_bracket.empty){
+ ycf_string_printable_buffer* array_size_exp = ycf_string_printable_buffer_new();
+ ycf_node* bracket = current->u.definition.array_brackets.head;
+ while(bracket != NULL){
+ print_node_code_expression(bracket->u.array_bracket.content,array_size_exp);
+ ycf_string_printable_buffer_printf(array_size_exp, " * ");
+ bracket = bracket->next;
+ }
+ ycf_string_printable_buffer_printf(array_size_exp, " sizeof(");
+ print_symbol_list(&current->u.definition.type_specifiers, array_size_exp);
+ ycf_string_printable_buffer_printf(array_size_exp, ")");
+ str = ycf_string_new("%s"
+ " memcpy(ycf_my_trap_state->%s, %s, %s);\n",
+ str,
+ ident,
+ ident,
+ array_size_exp->buffer);
+ }else{
+ str = ycf_string_new("%s"
+ " ycf_my_trap_state->%s = %s;\n",
+ str,
+ ident,
+ ident);
+ }
+ current = current->next;
+ }
+ return str;
+}
+
+static
+char* mk_code_from_special_code_list(ycf_node_list special_code_list){
+ ycf_string_printable_buffer* b = ycf_string_printable_buffer_new();
+ ycf_node* current = special_code_list.head;
+ ycf_string_printable_buffer_printf(b, "\n/* YCF SPECIAL CUSTOM CODE START */\n");
+ while(current != NULL) {
+ ycf_node_print(current->u.special_code_block.code.if_statement, b);
+ ycf_string_printable_buffer_printf(b, "\n");
+ current = current->next;
+ }
+ ycf_string_printable_buffer_printf(b, "\n/* YCF SPECIAL CUSTOM CODE END */\n");
+ return b->buffer;
+}
+
+static
+ycf_node* mk_yield_code(ycf_node* f_node,
+ char* ycf_trap_state_name,
+ ycf_node_list ycf_trap_state_defines,
+ ycf_node_list on_save_yield_state_code_list,
+ bool debug){
+ char* ret_code;
+ if(ycf_node_is_void_ret_fun(f_node)){
+ ret_code = "return;\n";
+ } else{
+ ycf_symbol_list ret_type = ycf_node_get_return_type(f_node);
+ ret_code =
+ ycf_string_new(" {\n"
+ " static %s const ycf_unused_ret_value;\n"
+ " return ycf_unused_ret_value;\n"
+ " }\n",
+ ycf_symbol_list_to_str(&ret_type));
+ }
+ char *debug_check_for_stack_ptrs = "";
+ if(debug){
+ debug_check_for_stack_ptrs =
+ ycf_string_new("ycf_debug_check_block(\"%s\",\n"
+ " ycf_find_stack_bottom_conservative(),\n"
+ " ycf_trap_state,\n"
+ " ycf_my_trap_state,\n"
+ " sizeof(struct %s));\n",
+ ycf_trap_state_name,
+ ycf_trap_state_name);
+ }
+ char* code = ycf_string_new("\n"
+ "{\n"
+ " struct %s* ycf_my_trap_state;\n"
+ " ycf_do_yield_label_%s:;\n"
+ " %s"
+ " if (*ycf_trap_state == NULL) {\n"
+ " ycf_my_trap_state = ycf_yield_alloc(sizeof(struct %s), ycf_yield_alloc_free_context);\n"
+ " } else {\n"
+ " ycf_my_trap_state = *ycf_trap_state;\n"
+ " }\n"
+ " %s\n"
+ " *ycf_nr_of_reductions_param = ycf_nr_of_reductions;\n"
+ " *ycf_trap_state = ycf_my_trap_state;\n"
+ " %s"
+ " %s\n"
+ "}\n"
+ "\n",
+ ycf_trap_state_name,
+ ycf_symbol_get_text(f_node->u.function.definition.definition.identifier),
+ mk_code_from_special_code_list(on_save_yield_state_code_list),
+ ycf_trap_state_name,
+ get_ycf_trap_state_assignments(ycf_trap_state_defines),
+ debug_check_for_stack_ptrs,
+ ret_code
+ );
+ return ycf_node_get_from_code_scope_text(code);
+}
+
+ycf_node* mk_goto_yield_code(ycf_node* f_node,
+ char* ycf_trap_state_name,
+ ycf_node_list ycf_trap_state_defines){
+ ycf_yield_location_id_counter++;
+ char* code = ycf_string_new("\n"
+ "{\n"
+ " ycf_nr_of_reductions = 0;\n"
+ " ycf_trap_location = %d;\n"
+ " goto ycf_do_yield_label_%s;\n"
+ " ycf_yield_location_label_%d:;\n"
+ "}\n",
+ ycf_yield_location_id_counter,
+ ycf_symbol_get_text(f_node->u.function.definition.definition.identifier),
+ ycf_yield_location_id_counter
+ );
+ return ycf_node_get_from_code_scope_text(code);
+}
+
+static
+ycf_node* mk_goto_yield_no_reds_code(ycf_node* f_node,
+ char* ycf_trap_state_name,
+ ycf_node_list ycf_trap_state_defines){
+ ycf_yield_location_id_counter++;
+ char* code = ycf_string_new("\n"
+ "{\n"
+ " ycf_trap_location = %d;\n"
+ " goto ycf_do_yield_label_%s;\n"
+ " ycf_yield_location_label_%d:;\n"
+ "}\n",
+ ycf_yield_location_id_counter,
+ ycf_symbol_get_text(f_node->u.function.definition.definition.identifier),
+ ycf_yield_location_id_counter
+ );
+ return ycf_node_get_from_code_scope_text(code);
+}
+
+
+static
+ycf_node* mk_yield_fun_call_state_var(ycf_node* f){
+ ycf_node_list code = ycf_node_list_empty();
+ char* fun_name = NULL;
+ if(f->u.statement.expression->type == ycf_node_type_function_call){
+ fun_name = ycf_symbol_get_text(f->u.statement.expression->u.function_call.identifier);
+ }else{
+ fun_name = ycf_symbol_get_text(f->u.statement.expression->u.function_call_assignment.fun_call.identifier);
+ }
+ ycf_node * scope_with_dec =
+ ycf_node_get_from_code_scope_text(ycf_string_new("void* ycf_sub_fun_trap_state_wb = NULL;\n"
+ "/*special_code_start:ON_DESTROY_STATE*/\n"
+ "if(0){\n"
+ " if(ycf_sub_fun_trap_state_wb != NULL)\n{"
+ " %s_ycf_gen_destroy(ycf_sub_fun_trap_state_wb);\n"
+ " }\n"
+ "}\n"
+ "/*special_code_end*/\n", fun_name));
+ ycf_node_list_append(&code, ycf_node_shallow_copy(f));
+ ycf_node_list_append(&code, scope_with_dec->u.code_scope.other_nodes.head);
+ ycf_node* ret = ycf_node_scope_new(ycf_symbol_new_open_curly_brace(),
+ scope_with_dec->u.code_scope.definition_nodes,
+ code,
+ ycf_symbol_new_end_curly_brace());
+ return ret;
+}
+
+static
+ycf_node* mk_yield_fun_call(ycf_node* c_file_node,
+ ycf_node* f,
+ char* ycf_trap_state_var_name,
+ char* calling_fun_name,
+ bool auto_yield){
+ ycf_yield_location_id_counter++;
+ ycf_node_function_call f_node;
+ char* assignment_code = "";
+ char* tmp_assignment_var_name = "ycf_tmp_fun_call_tmp_var";
+ char* tmp_assignment_var_declaration = "";
+ char* parameters = "";
+ char* finalize_call_code = "";
+ if(f->u.statement.expression->type == ycf_node_type_function_call){
+ f_node = f->u.statement.expression->u.function_call;
+ }else{
+ ycf_symbol_list called_fun_ret_type;
+ ycf_string_printable_buffer* assign_to_b = ycf_string_printable_buffer_new();
+ f_node = f->u.statement.expression->u.function_call_assignment.fun_call;
+ called_fun_ret_type =
+ ycf_node_find_function_return_type(c_file_node,
+ ycf_symbol_get_text(f_node.identifier));
+ tmp_assignment_var_declaration =
+ ycf_string_new("%s %s;\n",
+ ycf_symbol_list_to_str(&called_fun_ret_type),
+ tmp_assignment_var_name);
+ print_node_code_expression(f->u.statement.expression->u.function_call_assignment.left_side, assign_to_b);
+ assignment_code = ycf_string_new("%s = ", tmp_assignment_var_name);
+ finalize_call_code = ycf_string_new("%s = %s;\n",
+ assign_to_b->buffer,
+ tmp_assignment_var_name);
+ }
+ if (f_node.parameter_expressions.head != NULL){
+ ycf_string_printable_buffer* b = ycf_string_printable_buffer_new();
+ print_node_list_code(f_node.parameter_expressions.head, b);
+ parameters = ycf_string_new(",%s", b->buffer);
+ }
+
+ char* code = ycf_string_new("%s"
+ "%s"
+ "%s %s%s_ycf_gen_yielding(&ycf_nr_of_reductions,\n"
+ " &%s,ycf_extra_context,\n"
+ " ycf_yield_alloc,ycf_yield_free,\n"
+ " ycf_yield_alloc_free_context,\n"
+ " YCF_ALLOC_NEXT_MAX_SIZE(),\n"
+ " YCF_ALLOC_NEXT_BLOCK()\n"
+ " %s);\n"
+ "while(YCF_IS_YIELDED(%s)){\n"
+ " ycf_trap_location = %d;\n"
+ " goto ycf_do_yield_label_%s;\n"
+ " ycf_yield_location_label_%d:;\n"
+ " %s %s%s_ycf_gen_continue(&ycf_nr_of_reductions,\n"
+ " &%s,\n"
+ " ycf_extra_context);\n"
+ "}\n"
+ "%s\n",
+ tmp_assignment_var_declaration,
+ auto_yield ? "YCF_CONSUME_REDS(1);" : "",
+ assignment_code,
+ ycf_symbol_list_to_str(&f_node.neg_symbols),
+ ycf_symbol_get_text(f_node.identifier),
+ ycf_trap_state_var_name,
+ parameters,
+ ycf_trap_state_var_name,
+ ycf_yield_location_id_counter,
+ calling_fun_name,
+ ycf_yield_location_id_counter,
+ assignment_code,
+ ycf_symbol_list_to_str(&f_node.neg_symbols),
+ ycf_symbol_get_text(f_node.identifier),
+ ycf_trap_state_var_name,
+ finalize_call_code
+ );
+
+ return ycf_node_get_from_code_scope_text(code);
+}
+
+
+static
+char* get_trap_restore_assignments(ycf_node_list ycf_trap_state_defines){
+ ycf_node* current = ycf_trap_state_defines.head;
+ char* str = "\n";
+ while(current != NULL){
+ char* ident = ycf_symbol_get_text(current->u.definition.identifier);
+ if(current->u.definition.array_brackets.head != NULL && !current->u.definition.array_brackets.head->u.array_bracket.empty){
+ ycf_string_printable_buffer* array_size_exp = ycf_string_printable_buffer_new();
+ ycf_node* bracket = current->u.definition.array_brackets.head;
+ while(bracket != NULL){
+ print_node_code_expression(bracket->u.array_bracket.content,array_size_exp);
+ ycf_string_printable_buffer_printf(array_size_exp, " * ");
+ bracket = bracket->next;
+ }
+ ycf_string_printable_buffer_printf(array_size_exp, " sizeof(");
+ print_symbol_list(&current->u.definition.type_specifiers, array_size_exp);
+ ycf_string_printable_buffer_printf(array_size_exp, ")");
+ str = ycf_string_new("%s"
+ " memcpy(%s, ycf_my_trap_state->%s, %s);\n",
+ str,
+ ident,
+ ident,
+ array_size_exp->buffer);
+ }else{
+ str = ycf_string_new("%s"
+ " %s = ycf_my_trap_state->%s;\n",
+ str,
+ ident,
+ ident);
+ }
+ current = current->next;
+ }
+ return str;
+}
+
+static
+char* get_goto_ycf_trap_location_switch(int nr_of_ycf_trap_locations){
+ int i;
+ char* str = "switch(ycf_trap_location){\n";
+ for(i = 1; i <= nr_of_ycf_trap_locations; i++){
+ str = ycf_string_new("%s"
+ "case %d: goto ycf_yield_location_label_%d;\n", str, i, i);
+ }
+ str = ycf_string_new("%s\n}", str, i, i);
+ return str;
+}
+
+typedef struct {
+ ycf_node* f_node;
+ char* ycf_trap_state_name;
+ ycf_node_list ycf_trap_state_defines;
+} yield_code_replacer_context;
+
+ycf_node* yield_code_replacer(ycf_node* candidate, ycf_node_code_scope* s,void* context){
+ yield_code_replacer_context* c = context;
+ (void)s;
+ if(candidate->type == ycf_node_type_statement &&
+ candidate->u.statement.expression != NULL &&
+ candidate->u.statement.expression->type == ycf_node_type_function_call &&
+ ycf_symbol_is_text_eq(candidate->u.statement.expression->u.function_call.identifier,
+ "YCF_YIELD") &&
+ candidate->u.statement.expression->u.function_call.parameter_expressions.head == NULL){
+ ycf_node* yield_code = mk_goto_yield_code(c->f_node, c->ycf_trap_state_name, c->ycf_trap_state_defines);
+ return yield_code;
+ } else {
+ return candidate;
+ }
+}
+
+static
+void insert_yield_code(ycf_node* f_node, char* ycf_trap_state_name, ycf_node_list ycf_trap_state_defines, ycf_node_code_scope* s){
+ ycf_node_search_and_replace_statements_in_scope(s,
+ yield_code_replacer,
+ &(yield_code_replacer_context) {f_node, ycf_trap_state_name,
+ ycf_trap_state_defines});
+}
+
+static
+ycf_node* special_code_section_replacer(ycf_node* candidate, ycf_node_code_scope* s,void* context){
+ (void)context;
+ (void)s;
+ if(candidate->type == ycf_node_type_statement &&
+ candidate->u.statement.expression != NULL &&
+ candidate->u.statement.expression->type == ycf_node_type_function_call) {
+ ycf_node_function_call fun_call = candidate->u.statement.expression->u.function_call;
+ ycf_symbol* fun_name = fun_call.identifier;
+ if(ycf_symbol_is_text_eq(fun_name, "YCF_SPECIAL_CODE_START") &&
+ ycf_node_list_length(fun_call.parameter_expressions) == 1) {
+ char* parameter = ycf_node_to_string(fun_call.parameter_expressions.head);
+ ycf_node* special_code_start =
+ ycf_node_new_text_node(ycf_string_new("/*special_code_start:%s*/\n"
+ "if(0){\n",
+ parameter));
+ return special_code_start;
+ } else if (ycf_symbol_is_text_eq(candidate->u.statement.expression->u.function_call.identifier,
+ "YCF_SPECIAL_CODE_END") &&
+ ycf_node_list_length(fun_call.parameter_expressions) == 0) {
+ return ycf_node_new_text_node("}/*special_code_end*/\n");
+ }
+ }
+ return candidate;
+}
+
+static
+ycf_node* replace_alt_syntax_special_code_section_code(ycf_node* f_node){
+ ycf_node_search_and_replace_statements_in_scope(&f_node->u.function.body,
+ special_code_section_replacer,
+ NULL);
+ return ycf_node_get_function_from_text(ycf_node_to_string(f_node));
+}
+
+
+static
+ycf_node* yield_no_reds_code_replacer(ycf_node* candidate, ycf_node_code_scope* s,void* context){
+ yield_code_replacer_context* c = context;
+ (void)s;
+ if(candidate->type == ycf_node_type_statement &&
+ candidate->u.statement.expression != NULL &&
+ candidate->u.statement.expression->type == ycf_node_type_function_call &&
+ ycf_symbol_is_text_eq(candidate->u.statement.expression->u.function_call.identifier,
+ "YCF_YIELD_NO_REDS") &&
+ candidate->u.statement.expression->u.function_call.parameter_expressions.head == NULL){
+ ycf_node* yield_code = mk_goto_yield_no_reds_code(c->f_node, c->ycf_trap_state_name, c->ycf_trap_state_defines);
+ return yield_code;
+ } else {
+ return candidate;
+ }
+}
+
+static
+void insert_yield_no_reds_code(ycf_node* f_node, char* ycf_trap_state_name, ycf_node_list ycf_trap_state_defines, ycf_node_code_scope* s){
+ ycf_node_search_and_replace_statements_in_scope(s,
+ yield_no_reds_code_replacer,
+ &(yield_code_replacer_context){f_node, ycf_trap_state_name,
+ ycf_trap_state_defines});
+}
+
+typedef struct {
+ ycf_node_list on_return_code_list;
+ ycf_node_list on_return_or_destroy_code_list;
+ bool auto_yield;
+} replace_return_ctx;
+
+static
+ycf_node* save_nr_of_reductions_before_return_replacer(ycf_node* candidate,
+ ycf_node_code_scope* s,
+ void* context){
+ replace_return_ctx* ctx = context;
+ (void)s;
+ char* consume_reds_code = ctx->auto_yield ? "YCF_CONSUME_REDS(1);\n" : "";
+ if(candidate->type == ycf_node_type_return_statement){
+ char* code =
+ ycf_string_new("\n"
+ "%s"
+ "if (*ycf_trap_state != NULL) {\n"
+ " ycf_yield_free(*ycf_trap_state, ycf_yield_alloc_free_context);\n"
+ " *ycf_trap_state = NULL;\n"
+ "}\n"
+ "ycf_destroy_stack_allocator(&ycf_frame_alloc_data,\n"
+ " ycf_yield_free,\n"
+ " ycf_yield_alloc_free_context);\n"
+ "*ycf_nr_of_reductions_param = ycf_nr_of_reductions;"
+ "%s"
+ "%s"
+ "%s",
+ consume_reds_code,
+ mk_code_from_special_code_list(
+ ctx->on_return_or_destroy_code_list),
+ mk_code_from_special_code_list(ctx->on_return_code_list),
+ ycf_node_to_string(candidate));
+ return ycf_node_get_from_code_scope_text(code);
+ } else {
+ return candidate;
+ }
+}
+
+static
+void save_nr_of_reductions_before_return(ycf_node_code_scope* s,
+ ycf_node_list on_return_code_list,
+ ycf_node_list on_return_or_destroy_code_list,
+ bool auto_yield){
+ replace_return_ctx ctx;
+ ctx.on_return_code_list = on_return_code_list;
+ ctx.on_return_or_destroy_code_list = on_return_or_destroy_code_list;
+ ctx.auto_yield = auto_yield;
+ ycf_node_search_and_replace_statements_in_scope(s,
+ save_nr_of_reductions_before_return_replacer,
+ &ctx);
+}
+
+ycf_node* mk_consume_reds_code(char* function_name, ycf_node* consume_reds_node){
+ ycf_string_printable_buffer* b = ycf_string_printable_buffer_new();
+ ycf_yield_location_id_counter++;
+ print_node_code_paran_expression(consume_reds_node->u.consume_reds.nr_of_reds_expression, b);
+ return ycf_node_get_from_code_scope_text(ycf_string_new("ycf_nr_of_reductions = ycf_nr_of_reductions - %s;\n"
+ "if (ycf_nr_of_reductions <= 0) {\n"
+ " ycf_trap_location = %d;\n"
+ " goto ycf_do_yield_label_%s;\n"
+ " ycf_yield_location_label_%d:;\n"
+ "}\n",
+ b->buffer,
+ ycf_yield_location_id_counter,
+ function_name,
+ ycf_yield_location_id_counter));
+}
+
+static
+ycf_node* consume_reds_replacer(ycf_node* candidate, ycf_node_code_scope* s, void* context){
+ char* function_name = context;
+ (void)s;
+ if(candidate->type == ycf_node_type_consume_reds){
+ return mk_consume_reds_code(function_name, candidate);
+ } else {
+ return candidate;
+ }
+}
+
+static
+void insert_consume_reds_code(char* function_name, ycf_node_code_scope* s){
+ ycf_node_search_and_replace_statements_in_scope(s,
+ consume_reds_replacer,
+ function_name);
+}
+
+static
+ycf_node* mk_consume_reds_wrapped_statement(ycf_node* statement){
+ return ycf_node_get_from_code_scope_text(ycf_string_new("YCF_CONSUME_REDS(1);\n"
+ "%s\n", ycf_node_to_string(statement)));
+}
+
+static
+void insert_consume_reds_calls(ycf_node_code_scope* s);
+
+static
+ycf_node* consume_reds_calls_inserter(ycf_node* candidate, ycf_node_code_scope* s, void* context){
+ if(candidate->type == ycf_node_type_goto){
+ return mk_consume_reds_wrapped_statement(candidate);
+ } else if (candidate->type == ycf_node_type_while){
+ if(candidate->u.while_n.statement->type == ycf_node_type_code_scope){
+ insert_consume_reds_calls(&candidate->u.while_n.statement->u.code_scope);
+ }
+ candidate->u.while_n.statement = mk_consume_reds_wrapped_statement(candidate->u.while_n.statement);
+ return candidate;
+ } else if (candidate->type == ycf_node_type_do_while){
+ if(candidate->u.do_while.statement->type == ycf_node_type_code_scope){
+ insert_consume_reds_calls(&candidate->u.do_while.statement->u.code_scope);
+ }
+ candidate->u.do_while.statement = mk_consume_reds_wrapped_statement(candidate->u.do_while.statement);
+ return candidate;
+ } else if (candidate->type == ycf_node_type_for){
+ if(candidate->u.for_n.statement->type == ycf_node_type_code_scope){
+ insert_consume_reds_calls(&candidate->u.for_n.statement->u.code_scope);
+ }
+ candidate->u.for_n.statement = mk_consume_reds_wrapped_statement(candidate->u.for_n.statement);
+ return candidate;
+ } else {
+ return candidate;
+ }
+}
+
+static void insert_consume_reds_calls(ycf_node_code_scope* s){
+ ycf_node_search_and_replace_statements_in_scope(s,
+ consume_reds_calls_inserter,
+ NULL);
+}
+
+typedef struct {
+ ycf_node_list code;
+ ycf_node_type code_type;
+} special_code_context;
+
+ycf_node* do_special_code_replace(ycf_node* candidate, ycf_node_code_scope* s, void* context){
+ special_code_context* c = context;
+ (void)s;
+ if(candidate->type == c->code_type){
+ ycf_node_list_append(&c->code, ycf_node_shallow_copy(candidate));
+ return ycf_node_new_text_node("\n/* YCF Replaced special code */\n");
+ } else {
+ return candidate;
+ }
+}
+
+static
+ycf_node_list save_and_replace_special_code(ycf_node_code_scope* s, ycf_node_type code_type){
+ special_code_context context;
+ context.code = ycf_node_list_empty();
+ context.code_type = code_type;
+ ycf_node_search_and_replace_statements_in_scope(s,
+ do_special_code_replace,
+ &context);
+ return context.code;
+}
+
+static
+ycf_node* insert_fun_call_state_var_replacer(ycf_node* candidate, ycf_node_code_scope* s, void* context){
+ ycf_string_item_list* yielding_funs = context;
+ (void)s;
+ if(candidate->type == ycf_node_type_statement &&
+ (candidate->u.statement.expression->type == ycf_node_type_function_call ||
+ candidate->u.statement.expression->type == ycf_node_type_assignment_function_call)){
+ ycf_string_item* current = yielding_funs->head;
+ while(current != NULL){
+ char* fun_name = NULL;
+ if(candidate->u.statement.expression->type == ycf_node_type_function_call){
+ fun_name = ycf_symbol_get_text(candidate->u.statement.expression->u.function_call.identifier);
+ } else if(candidate->u.statement.expression->type == ycf_node_type_assignment_function_call){
+ fun_name = ycf_symbol_get_text(
+ candidate->u.statement.expression->u.function_call_assignment.fun_call.identifier);
+ } else {
+ current = current->next;
+ continue;
+ }
+ if(ycf_string_is_equal(current->str, fun_name)){
+ return mk_yield_fun_call_state_var(candidate);
+ }
+ current = current->next;
+ }
+ }
+ return candidate;
+}
+
+static
+void insert_fun_call_state_var(ycf_node_code_scope* s, ycf_string_item_list* yielding_funs){
+ ycf_node_search_and_replace_statements_in_scope(s,
+ insert_fun_call_state_var_replacer,
+ yielding_funs);
+}
+
+typedef struct {
+ ycf_node* c_file_node;
+ char* calling_fun_name;
+ ycf_string_item_list* yielding_funs;
+ ycf_string_item_list* starte_vars_wb;
+ bool auto_yield;
+} yielding_fun_call_code_context;
+
+static
+ycf_node* insert_yielding_fun_call_code_replacer(ycf_node* candidate,
+ ycf_node_code_scope* s,
+ void* context){
+ yielding_fun_call_code_context* c = context;
+ ycf_node* c_file_node = c->c_file_node;
+ ycf_string_item_list* yielding_funs = c->yielding_funs;
+ (void)s;
+ if (candidate->type == ycf_node_type_code_scope &&
+ candidate->u.code_scope.other_nodes.head != candidate->u.code_scope.other_nodes.last){
+ ycf_node* call_candidate = candidate->u.code_scope.other_nodes.head->next;
+ if (call_candidate->type == ycf_node_type_statement &&
+ (call_candidate->u.statement.expression->type == ycf_node_type_function_call ||
+ call_candidate->u.statement.expression->type == ycf_node_type_assignment_function_call)) {
+ ycf_string_item* current = yielding_funs->head;
+ while(current != NULL){
+ char* fun_name = NULL;
+ if (call_candidate->u.statement.expression->type == ycf_node_type_function_call) {
+ fun_name = ycf_symbol_get_text(call_candidate->u.statement.expression->u.function_call.identifier);
+ } else if (call_candidate->u.statement.expression->type == ycf_node_type_assignment_function_call) {
+ fun_name = ycf_symbol_get_text(
+ call_candidate->u.statement.expression->u.function_call_assignment.fun_call.identifier);
+ } else {
+ current = current->next;
+ continue;
+ }
+ if(ycf_string_is_equal(current->str, fun_name)){
+ char* ycf_trap_state_var_name =
+ ycf_symbol_get_text(ycf_node_get_assignment(candidate->u.code_scope.other_nodes.head)->left_side.content.head->u.other.what);
+ ycf_string_item_list_append(c->starte_vars_wb, ycf_string_item_new(ycf_trap_state_var_name));
+ ycf_node* new_call_code = mk_yield_fun_call(c_file_node,
+ call_candidate,
+ ycf_trap_state_var_name,
+ c->calling_fun_name,
+ c->auto_yield);
+ ycf_node_list_replace(&candidate->u.code_scope.other_nodes, call_candidate, new_call_code);
+ return ycf_node_scope_new(ycf_symbol_new_open_curly_brace(),
+ candidate->u.code_scope.definition_nodes,
+ candidate->u.code_scope.other_nodes,
+ ycf_symbol_new_end_curly_brace());;
+ }
+ current = current->next;
+ }
+ }
+ }
+ return candidate;
+}
+
+ycf_string_item_list insert_yielding_fun_call_code(ycf_node* c_file_node,
+ ycf_node_code_scope* s,
+ ycf_string_item_list* yielding_funs,
+ char* calling_fun_name,
+ bool auto_yield){
+ yielding_fun_call_code_context context;
+ ycf_string_item_list state_vars = ycf_string_item_list_empty();
+ context.c_file_node = c_file_node;
+ context.calling_fun_name = calling_fun_name;
+ context.yielding_funs = yielding_funs;
+ context.starte_vars_wb = &state_vars;
+ context.auto_yield = auto_yield;
+ ycf_node_search_and_replace_statements_in_scope(s,
+ insert_yielding_fun_call_code_replacer,
+ &context);
+ return state_vars;
+}
+
+static
+ycf_node* mk_yield_init_code(char* ycf_trap_state_name, ycf_node_list ycf_trap_state_defines, ycf_node_list on_restore_yield_state_code_list, bool auto_yield, char *function_name){
+ char* code = ycf_string_new("\n"
+ "{\n"
+ " ycf_nr_of_reductions = *ycf_nr_of_reductions_param;\n"
+ " ycf_frame_alloc_data.size = 0;\n"
+ " ycf_frame_alloc_data.max_size = ycf_stack_alloc_size_or_max_size;\n"
+ " ycf_frame_alloc_data.data = ycf_stack_alloc_data;\n"
+ " ycf_frame_alloc_data.needs_freeing = 0;\n"
+ " if(*ycf_trap_state != NULL){\n"
+ " struct %s* ycf_my_trap_state = *ycf_trap_state;\n"
+ " %s\n"
+ " %s\n"
+ " ycf_nr_of_reductions = *ycf_nr_of_reductions_param;\n"
+ " %s\n"
+ " }\n"
+ "}\n",
+ ycf_trap_state_name,
+ get_trap_restore_assignments(ycf_trap_state_defines),
+ mk_code_from_special_code_list(on_restore_yield_state_code_list),
+ get_goto_ycf_trap_location_switch(ycf_yield_location_id_counter));
+ return ycf_node_get_from_code_scope_text(code);
+}
+
+static
+ycf_node* mk_destroy_state_function_node(char* yielding_function_name,
+ ycf_node_list defs,
+ char* ycf_trap_state_struct_name,
+ ycf_node_list destroy_code,
+ ycf_node_list on_destroy_state_or_return_code_list,
+ bool static_aux_funs){
+ ycf_node_list my_defs = ycf_node_list_shallow_copy(defs);
+ ycf_node* current = my_defs.head;
+ while(current != NULL){
+ current->u.definition.end = ycf_symbol_new_semicolon();
+ int empty_array_brackets = 0;
+ {
+ current->u.definition.array_brackets = ycf_node_list_shallow_copy(current->u.definition.array_brackets);
+ ycf_node* b = current->u.definition.array_brackets.head;
+ while(b != NULL && b->u.array_bracket.empty){
+ empty_array_brackets++;
+ ycf_node_list_remove(&current->u.definition.array_brackets, b);
+ b = b->next;
+ }
+ }
+ current->u.definition.type_specifiers = ycf_symbol_list_shallow_copy(current->u.definition.type_specifiers);
+ if(empty_array_brackets > 0){
+ for(int i = 0; i < empty_array_brackets; i++){
+ ycf_symbol_list_append(&current->u.definition.type_specifiers, ycf_symbol_new_star());
+ }
+ }
+ current = current->next;
+ }
+ char* code =
+ ycf_string_new("\n"
+ "%s void %s_ycf_gen_destroy(struct %s* ycf_my_trap_state){\n"
+ " {\n"
+ " %s\n"
+ " %s\n"
+ " %s\n"
+ " %s\n"
+ " ycf_destroy_stack_allocator(&ycf_frame_alloc_data, ycf_yield_free, ycf_yield_alloc_free_context);\n"
+ " ycf_yield_free(ycf_my_trap_state, ycf_yield_alloc_free_context);\n"
+ " }\n"
+ "}\n",
+ static_aux_funs ? "static" : "",
+ yielding_function_name,
+ ycf_trap_state_struct_name,
+ ycf_node_list_to_string(&my_defs),
+ get_trap_restore_assignments(my_defs),
+ mk_code_from_special_code_list(on_destroy_state_or_return_code_list),
+ mk_code_from_special_code_list(destroy_code));
+ return ycf_node_get_function_from_text(code);
+}
+
+void ast_add_yield_code_generated_define(ycf_node* source_out_tree/*Will be changed*/, bool debug_mode)
+{
+ char *debug_mode_code =
+ (debug_mode ?
+ "\n"
+ "#include <setjmp.h>\n"
+ "#include <stdint.h>\n"
+ "#include <string.h>\n"
+ "static void* ycf_find_stack_bottom_conservative_helper() {\n"
+ " void* p = NULL;\n"
+ " volatile intptr_t tmp = (intptr_t)&p;\n"
+ " return (void*)tmp;\n"
+ "}\n"
+ "static void* ycf_find_stack_bottom_conservative() {\n"
+ " jmp_buf env;\n"
+ " setjmp(env);\n"
+ "\n"
+ " {\n"
+ " volatile int noinline = 1;\n"
+ " void* (*bottom)(void) = noinline\n"
+ " ? ycf_find_stack_bottom_conservative_helper\n"
+ " : (void*(*)(void))(NULL);\n"
+ "\n"
+ " return bottom();\n"
+ " }\n"
+ "}\n"
+ "static void ycf_debug_check_block(char* struct_name, void* stack_start, void* stack_end, void* block, size_t block_size) {\n"
+ " char* p;\n"
+ " for (p = block; p < (((char*)block) + block_size); p += sizeof(void*)) {\n"
+ " if(*((char**)p) > (char*)stack_start && *((char**)p) <= (char*)stack_end){\n"
+ " fprintf(stderr, \"Pointer to stack in yielded functions state!!!!! (pointer_address=%p, struct %s,offset=%lu)\\n\", (void*)p, struct_name, (unsigned long)(p-(size_t)block));\n"
+ " exit(1);\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ "\n"
+ :
+ "");
+ char* ycf_yielding_c_fun_helpers_code =
+ ycf_string_new("#ifndef YCF_YIELDING_C_FUN_HELPERS\n"
+ "#define YCF_YIELDING_C_FUN_HELPERS 1\n"
+ "#include <string.h>\n"
+ "#include <stdio.h>\n"
+ "#include <stdlib.h>\n"
+ "\n"
+ "/*\n"
+ " * YCF_GCC_DIAG_ON and YCF_GCC_DIAG_OFF can be used to temporarly\n"
+ " * disable a gcc or clang warning in a file.\n"
+ " *\n"
+ " * Example:\n"
+ " * YCF_GCC_DIAG_OFF(unused-function)\n"
+ " * static int test(){ return 0;}\n"
+ " * YCF_GCC_DIAG_ON(unused-function)\n"
+ " *\n"
+ " * These macros were orginally authored by Jonathan Wakely and has\n"
+ " * been modified by Patrick Horgan.\n"
+ " *\n"
+ " * Source: http://dbp-consulting.com/tutorials/SuppressingGCCWarnings.html\n"
+ " *\n"
+ " */\n"
+ "#if defined(_MSC_VER)\n"
+ "# define YCF_GCC_DIAG_OFF(x) __pragma(warning(push, 0))\n"
+ "# define YCF_GCC_DIAG_ON(x) __pragma(warning(pop))\n"
+ "#elif ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402\n"
+ "#define YCF_GCC_DIAG_STR(s) #s\n"
+ "#define YCF_GCC_DIAG_JOINSTR(x,y) YCF_GCC_DIAG_STR(x ## y)\n"
+ "# define YCF_GCC_DIAG_DO_PRAGMA(x) _Pragma (#x)\n"
+ "# define YCF_GCC_DIAG_PRAGMA(x) YCF_GCC_DIAG_DO_PRAGMA(GCC diagnostic x)\n"
+ "# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406\n"
+ "# define YCF_GCC_DIAG_OFF(x) YCF_GCC_DIAG_PRAGMA(push) \\\n"
+ " YCF_GCC_DIAG_PRAGMA(ignored YCF_GCC_DIAG_JOINSTR(-W,x))\n"
+ "# define YCF_GCC_DIAG_ON(x) YCF_GCC_DIAG_PRAGMA(pop)\n"
+ "# else\n"
+ "# define YCF_GCC_DIAG_OFF(x) YCF_GCC_DIAG_PRAGMA(ignored YCF_GCC_DIAG_JOINSTR(-W,x))\n"
+ "# define YCF_GCC_DIAG_ON(x) YCF_GCC_DIAG_PRAGMA(warning YCF_GCC_DIAG_JOINSTR(-W,x))\n"
+ "# endif\n"
+ "#else\n"
+ "# define YCF_GCC_DIAG_OFF(x)\n"
+ "# define YCF_GCC_DIAG_ON(x)\n"
+ "#endif\n"
+ "#ifdef __GNUC__\n"
+ "# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5) || defined(__clang__)\n"
+ "# define YCF_GCC_ATTRIBUTE_UNUSED __attribute__ ((unused))\n"
+ "# else\n"
+ "# define YCF_GCC_ATTRIBUTE_UNUSED\n"
+ "# endif\n"
+ "#else\n"
+ "# define YCF_GCC_ATTRIBUTE_UNUSED\n"
+ "#endif\n"
+ "\n"
+ "typedef void* (*ycf_yield_alloc_type) (size_t,void*);\n"
+ "typedef void (*ycf_yield_free_type) (void*,void*);\n"
+ "\n"
+ "struct ycf_alloc_data {\n"
+ " size_t size;\n"
+ " size_t max_size;\n"
+ " int needs_freeing;\n"
+ " char* data;\n"
+ "};\n"
+ "\n"
+ "#define YCF_ALLOC_NEXT_BLOCK() (\\\n"
+ " ycf_frame_alloc_data.data == NULL \\\n"
+ " ? NULL \\\n"
+ " : ((void*)(&ycf_frame_alloc_data.data[ycf_frame_alloc_data.size]))\\\n"
+ ")\n"
+ "#define YCF_ALLOC_NEXT_MAX_SIZE() (\\\n"
+ " ycf_frame_alloc_data.data == NULL \\\n"
+ " ? 0 \\\n"
+ " : (ycf_frame_alloc_data.max_size - ycf_frame_alloc_data.size)\\\n"
+ ")\n"
+ "\n"
+ "/* Macros for special code sections */\n"
+ "#define ON_SAVE_YIELD_STATE ON_SAVE_YIELD_STATE\n"
+ "#define ON_RESTORE_YIELD_STATE ON_RESTORE_YIELD_STATE\n"
+ "#define ON_DESTROY_STATE ON_DESTROY_STATE\n"
+ "#define ON_RETURN ON_RETURN\n"
+ "#define ON_DESTROY_STATE_OR_RETURN ON_DESTROY_STATE_OR_RETURN\n"
+ "#define YCF_SPECIAL_CODE_START(PARAM) \\\n"
+ " /*special_code_start:PARAM*/ \\\n"
+ " if(0){\n"
+ "#define YCF_SPECIAL_CODE_END() \\\n"
+ " }\\\n"
+ " /*special_code_end*/\n"
+ "\n"
+ "YCF_GCC_ATTRIBUTE_UNUSED\n"
+ "static void* ycf_stack_alloc(size_t size,\n"
+ " struct ycf_alloc_data* data,\n"
+ " ycf_yield_alloc_type allocator,\n"
+ " void* ycf_yield_alloc_free_context,\n"
+ " size_t default_stack_size){\n"
+ " void * ret = NULL;"
+ " if (data->data == NULL) {\n"
+ " if (default_stack_size == 0) {\n"
+ " fprintf(stderr, \"ycf_alloc: not enough stack!!\\n\");\n"
+ " exit(1);\n"
+ " }\n"
+ " data->data = allocator(default_stack_size, ycf_yield_alloc_free_context);\n"
+ " data->needs_freeing = 1;"
+ " data->max_size = default_stack_size;"
+ " data->size = 0;"
+ " }\n"
+ " if (data->size + size > data->max_size) {\n"
+ " fprintf(stderr, \"ycf_alloc: not enough stack!\\n\");\n"
+ " exit(1);\n"
+ " }\n"
+ " ret = &data->data[data->size];\n"
+ " data->size = data->size + size;\n"
+ " return ret;\n"
+ "}\n"
+ "static void ycf_destroy_stack_allocator(struct ycf_alloc_data* data,\n"
+ " ycf_yield_free_type freer,\n"
+ " void* ycf_yield_alloc_free_context){\n"
+ " if(data->needs_freeing){\n"
+ " freer(data->data, ycf_yield_alloc_free_context);\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "#include <limits.h>\n"
+ "#define YCF_MAX_NR_OF_REDS LONG_MAX\n"
+ "#define YCF_NR_OF_REDS_LEFT() ycf_nr_of_reductions\n"
+ "#define YCF_SET_NR_OF_REDS_LEFT(NEW_NR_OF_REDS_LEFT) \\\n"
+ " do{ycf_nr_of_reductions = (NEW_NR_OF_REDS_LEFT);}while(0)\n"
+ "\n"
+ "#define YCF_GET_EXTRA_CONTEXT() ycf_extra_context\n"
+ "\n"
+ "#define YCF_IS_YIELDED(CTX) (CTX != NULL)\n"
+ "\n"
+ "#define YCF_YIELD_CODE_GENERATED 1\n"
+ "\n"
+ "%s"
+ "\n"
+ "/* end of YCF_YIELDING_C_FUN_HELPERS guard */\n"
+ "#endif\n", debug_mode_code);
+ ycf_node_list_prepend(&source_out_tree->u.c_file.content,
+ ycf_node_new_text_node(ycf_yielding_c_fun_helpers_code));
+}
+
+static
+ycf_node* mk_fun_def(ycf_node* fun_node){
+ ycf_node* fun_def = ycf_malloc(sizeof(ycf_node));
+ fun_def->next = NULL;
+ fun_def->type = ycf_node_type_function_declaration;
+ fun_def->u.function_definition = fun_node->u.function.definition;
+ ycf_symbol_list fun_node_def_end = ycf_symbol_list_empty();
+ ycf_symbol_list_append(&fun_node_def_end, ycf_symbol_new_semicolon());
+ fun_def->u.function_definition.end = fun_node_def_end;
+ return fun_def;
+}
+
+
+static
+ycf_node* mk_null_vars_code(ycf_string_item_list fun_call_state_vars){
+ ycf_string_item* current = fun_call_state_vars.head;
+ char* assignments = "";
+ while(current != NULL){
+ assignments = ycf_string_new("%s\n"
+ "%s = NULL;\n",
+ assignments,
+ current->str);
+ current = current->next;
+ }
+ ycf_node* code = ycf_node_get_from_code_scope_text(assignments);
+ return code;
+}
+
+static
+ycf_node* mk_continue_function_node(char* yielding_function_name,
+ char* ycf_trap_state_struct_name,
+ ycf_node* yielding_fun,
+ ycf_node_list uniqified_parameters){
+ char* parameters = "";
+ ycf_node* current = uniqified_parameters.head;
+ while(current != NULL){
+ parameters = ycf_string_new("%s,ycf_my_trap_state->%s", parameters, ycf_symbol_get_text(current->u.definition.identifier));
+ current = current->next;
+ }
+ char* fun_call_str =
+ ycf_string_new("%s_ycf_gen_yielding(ycf_number_of_reduction_param,\n"
+ " ycf_trap_state,\n"
+ " ycf_extra_context,\n"
+ " ycf_my_trap_state->ycf_yield_alloc,\n"
+ " ycf_my_trap_state->ycf_yield_free,\n"
+ " ycf_my_trap_state->ycf_yield_alloc_free_context,\n"
+ " ycf_my_trap_state->ycf_stack_alloc_size_or_max_size,\n"
+ " ycf_my_trap_state->ycf_stack_alloc_data\n"
+ " %s)\n",
+ yielding_function_name,
+ parameters);
+ char* code = ycf_string_new("\n"
+ "%s %s_ycf_gen_continue(long* ycf_number_of_reduction_param,\n"
+ " void** ycf_trap_state,\n"
+ " void* ycf_extra_context){\n"
+ " struct %s* ycf_my_trap_state = *ycf_trap_state;\n"
+ "%s"
+ "}\n",
+ ycf_symbol_list_to_str(&yielding_fun->u.function.definition.definition.type_specifiers),
+ yielding_function_name,
+ ycf_trap_state_struct_name,
+ (ycf_node_is_void_ret_fun(yielding_fun) ?
+ ycf_string_new("%s;\n"
+ "return;\n",
+ fun_call_str):
+ ycf_string_new("return %s;\n",
+ fun_call_str)));
+ ycf_symbol_list symbols = ycf_symbol_list_from_text(code);
+ ycf_node* tree = get_abstract_syntax_tree_root(&symbols);
+ if(tree->u.c_file.content.head->type != ycf_node_type_function_definition){
+ printf("NOT A FUNCTION\n");
+ exit(1);
+ }
+ return tree->u.c_file.content.head;
+}
+
+static
+ycf_node* mk_debug_yielding_fun_call_wrapper(char* yielding_function_name,
+ char* ycf_trap_state_struct_name,
+ ycf_node* yielding_fun){
+ char* parameters_2 = "";
+ {
+ ycf_node* current = yielding_fun->u.function.definition.parameters.head;
+ bool first = true;
+ char* identifier;
+ while(current != NULL){
+ identifier = ycf_symbol_get_text(current->u.definition.identifier);
+ if(strcmp(identifier, "ycf_trap_state") == 0){
+ identifier = ycf_string_new("(void**)(&ycf_my_trap_state)");
+ }
+ parameters_2 = ycf_string_new(first ? "%s%s" :"%s,%s",
+ parameters_2,
+ identifier);
+ current = current->next;
+ first = false;
+ }
+ }
+ char* parameters = "";
+ {
+ ycf_node* current = yielding_fun->u.function.definition.parameters.head;
+ bool first = true;
+ while(current != NULL){
+ parameters = ycf_string_new(first ? "%s%s" :"%s,%s",
+ parameters,
+ ycf_symbol_get_text(current->u.definition.identifier));
+ current = current->next;
+ first = false;
+ }
+ }
+ ycf_string_printable_buffer* header_2 = ycf_string_printable_buffer_new();
+ ycf_node_function_definition def_2 = yielding_fun->u.function.definition;
+ def_2.definition.identifier =
+ ycf_symbol_copy_change_text(def_2.definition.identifier,
+ ycf_string_new("%s_2",
+ ycf_symbol_get_text(def_2.definition.identifier)));
+ print_node_code_function_def(def_2, header_2);
+ ycf_string_printable_buffer* header = ycf_string_printable_buffer_new();
+ print_node_code_function_def(yielding_fun->u.function.definition, header);
+ ycf_string_printable_buffer* parameter_types = ycf_string_printable_buffer_new();
+ print_node_code_function_def_parameters(yielding_fun->u.function.definition, parameter_types);
+ ycf_symbol_list ret_type = ycf_node_get_return_type(yielding_fun);
+ char* wrapper_assignment = NULL;
+ char* wrapper_return = NULL;
+ if(ycf_node_is_void_ret_fun(yielding_fun)){
+ wrapper_assignment = ycf_string_new("");
+ wrapper_return = ycf_string_new("return;");
+ }else{
+ wrapper_assignment = ycf_string_new("%s ycf_to_return = ", ycf_symbol_list_to_str(&ret_type));
+ wrapper_return = ycf_string_new("return ycf_to_return;");
+ }
+ char* code = ycf_string_new("\n"
+ "%s{\n"
+ " volatile int noinline = 1;\n"
+ " volatile void* ycf_my_trap_state = *ycf_trap_state;\n"
+ " %s (*next)(%s = noinline\n"
+ " ? %s_ycf_gen_yielding_3\n"
+ " : (%s(*)(%s)(NULL);\n"
+ " {\n"
+ " %s next(%s);\n"
+ " *ycf_trap_state = (struct %s*)ycf_my_trap_state;\n"
+ " %s\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "\n"
+ "%s{\n"
+ " volatile int noinline = 1;\n"
+ " %s (*next)(%s = noinline\n"
+ " ? %s_ycf_gen_yielding_2\n"
+ " : (%s(*)(%s)(NULL);\n"
+ "\n"
+ " {\n"
+ " %s next(%s);\n"
+ " %s;\n"
+ " }\n"
+ "}\n",
+ header_2->buffer,
+ ycf_symbol_list_to_str(&ret_type),
+ parameter_types->buffer,
+ yielding_function_name,
+ ycf_symbol_list_to_str(&ret_type),
+ parameter_types->buffer,
+ wrapper_assignment,
+ parameters_2,
+ ycf_trap_state_struct_name,
+ wrapper_return,
+ /* Second function*/
+ header->buffer,
+ ycf_symbol_list_to_str(&ret_type),
+ parameter_types->buffer,
+ yielding_function_name,
+ ycf_symbol_list_to_str(&ret_type),
+ parameter_types->buffer,
+ wrapper_assignment,
+ parameters,
+ wrapper_return);
+
+ return ycf_node_new_text_node(code);
+}
+
+
+static
+void break_up_control_expressions(ycf_node_code_scope* s,
+ ycf_string_item_list* yielding_function_names);
+
+
+static ycf_node* break_up_control_expressions_helper(ycf_node* candidate,
+ ycf_node_code_scope* candidates_scope,
+ void* context){
+ ycf_string_item_list* list = (ycf_string_item_list*)context;
+ if(candidate->type == ycf_node_type_if &&
+ ycf_node_list_length(candidate->u.if_n.expression.content.content) == 1 &&
+ candidate->u.if_n.expression.content.content.head->type == ycf_node_type_function_call &&
+ ycf_string_item_list_contains(list,
+ ycf_symbol_get_text(candidate->u.if_n.expression.content.content.head->u.function_call.identifier))){
+ break_up_control_expressions(&candidate->u.if_n.if_statement->u.code_scope, list);
+ ycf_node * wrapper =
+ ycf_node_get_from_code_scope_text(ycf_string_new("int ycf_gen_control_tmp;\n"
+ "ycf_gen_control_tmp = !!%s;\n"
+ "if(ycf_gen_control_tmp)\n"
+ "%s",
+ ycf_node_to_string(candidate->u.if_n.expression.content.content.head),
+ ycf_node_to_string(candidate->u.if_n.if_statement)));
+ return wrapper;
+ } else if(candidate->type == ycf_node_type_if_else &&
+ ycf_node_list_length(candidate->u.if_else.if_part.expression.content.content) == 1 &&
+ candidate->u.if_else.if_part.expression.content.content.head->type == ycf_node_type_function_call &&
+ ycf_string_item_list_contains(list,
+ ycf_symbol_get_text(candidate->u.if_else.if_part.expression.content.content.head->u.function_call.identifier))){
+ break_up_control_expressions(&candidate->u.if_else.if_part.if_statement->u.code_scope, list);
+ ycf_node * wrapper =
+ ycf_node_get_from_code_scope_text(ycf_string_new("int ycf_gen_control_tmp;\n"
+ "ycf_gen_control_tmp = !!%s;\n"
+ "if(ycf_gen_control_tmp)\n"
+ "%s\n"
+ "%s\n"
+ "%s\n",
+ ycf_node_to_string(candidate->u.if_else.if_part.expression.content.content.head),
+ ycf_node_to_string(candidate->u.if_else.if_part.if_statement),
+ ycf_symbol_get_text(candidate->u.if_else.else_word),
+ ycf_node_to_string(candidate->u.if_else.else_statement)));
+ return wrapper;
+ } else if(candidate->type == ycf_node_type_do_while &&
+ ycf_node_list_length(candidate->u.do_while.expression.content.content) == 1 &&
+ candidate->u.do_while.expression.content.content.head->type == ycf_node_type_function_call &&
+ ycf_string_item_list_contains(list,
+ ycf_symbol_get_text(candidate->u.do_while.expression.content.content.head->u.function_call.identifier))){
+ break_up_control_expressions(&candidate->u.do_while.statement->u.code_scope, list);
+ ycf_node * wrapper =
+ ycf_node_get_from_code_scope_text(ycf_string_new("int ycf_gen_control_tmp;\n"
+ "do {\n"
+ " %s\n"
+ " ycf_gen_control_tmp = !!%s;\n"
+ "}while(ycf_gen_control_tmp);\n",
+ ycf_node_to_string(candidate->u.do_while.statement),
+ ycf_node_to_string(candidate->u.do_while.expression.content.content.head)));
+ return wrapper;
+ } else if(candidate->type == ycf_node_type_while &&
+ ycf_node_list_length(candidate->u.while_n.expression.content.content) == 1 &&
+ candidate->u.while_n.expression.content.content.head->type == ycf_node_type_function_call &&
+ ycf_string_item_list_contains(list,
+ ycf_symbol_get_text(candidate->u.while_n.expression.content.content.head->u.function_call.identifier))){
+ break_up_control_expressions(&candidate->u.while_n.statement->u.code_scope, list);
+ ycf_node * wrapper =
+ ycf_node_get_from_code_scope_text(ycf_string_new("int ycf_gen_control_tmp;\n"
+ "ycf_gen_control_tmp = !!%s;\n"
+ "while(ycf_gen_control_tmp){\n"
+ " %s\n"
+ " ycf_gen_control_tmp = !!%s;\n"
+ "}\n",
+ ycf_node_to_string(candidate->u.while_n.expression.content.content.head),
+ ycf_node_to_string(candidate->u.while_n.statement),
+ ycf_node_to_string(candidate->u.while_n.expression.content.content.head)));
+ return wrapper;
+ }
+ return candidate;
+}
+
+static
+void break_up_control_expressions(ycf_node_code_scope* s,
+ ycf_string_item_list* yielding_function_names){
+ ycf_node_insert_scopes_in_complex_statements(s);
+ ycf_node_search_and_replace_statements_in_scope(s,
+ break_up_control_expressions_helper,
+ yielding_function_names);
+}
+
+static
+ycf_node* mk_wrap_in_surpress_warn(char* warning, ycf_node* to_wrap) {
+ ycf_string_printable_buffer* buf = ycf_string_printable_buffer_new();
+ ycf_string_printable_buffer_printf(buf,
+ "\n"
+ "/* clang-format off */\n"
+ "YCF_GCC_DIAG_OFF(%s)\n"
+ "/* clang-format on */\n", warning);
+ ycf_node_print(to_wrap, buf);
+ ycf_string_printable_buffer_printf(buf,
+ "\n"
+ "/* clang-format off */\n"
+ "YCF_GCC_DIAG_ON(%s)\n"
+ "/* clang-format on */\n", warning);
+ return ycf_node_new_text_node(buf->buffer);
+
+}
+
+static
+ycf_node* supress_warnings_wrap_yielding_fun(ycf_node* yielding_fun){
+ ycf_node* ret = yielding_fun;
+ ret = mk_wrap_in_surpress_warn("uninitialized", ret);
+ ret = mk_wrap_in_surpress_warn("maybe-uninitialized", ret);
+ ret = mk_wrap_in_surpress_warn("sometimes-uninitialized", ret);
+ ret = mk_wrap_in_surpress_warn("unknown-warning-option", ret);
+ ret = mk_wrap_in_surpress_warn("pragmas", ret);
+ return ret;
+}
+
+static
+ycf_node* supress_warnings_wrap_destroy_fun(ycf_node* fun){
+ ycf_node* ret = fun;
+ ret = mk_wrap_in_surpress_warn("unused-function", ret);
+ ret = mk_wrap_in_surpress_warn("unused-but-set-variable", ret);
+ ret = mk_wrap_in_surpress_warn("unknown-warning-option", ret);
+ ret = mk_wrap_in_surpress_warn("pragmas", ret);
+ return ret;
+}
+
+ycf_node* insert_yielding_function_with_prefix_suffix(ycf_node* tree_to_insert_to,
+ ycf_node* insert_before,
+ ycf_node* yielding_fun,
+ bool debug_mode,
+ char* yielding_function_name,
+ char* ycf_trap_state_struct_name){
+ ycf_node* prefix = ycf_node_new_text_node("\n"
+ "#define YCF_IN_YIELDING_FUN 1\n"
+ "#undef YCF_STACK_ALLOC\n"
+ "#define YCF_STACK_ALLOC(SIZE) \\\n"
+ " ycf_stack_alloc(SIZE,\\\n"
+ " &ycf_frame_alloc_data,\\\n"
+ " ycf_yield_alloc, ycf_yield_alloc_free_context,\\\n"
+ " ycf_stack_alloc_size_or_max_size)\n");
+ if(insert_before == NULL){
+ ycf_node_list_append(&tree_to_insert_to->u.c_file.content, prefix);
+ }else{
+ ycf_node_list_insert_before(&tree_to_insert_to->u.c_file.content,
+ insert_before,
+ prefix);
+ }
+ if(debug_mode){
+ ycf_node* wrapper_funs =
+ mk_debug_yielding_fun_call_wrapper(yielding_function_name,
+ ycf_trap_state_struct_name,
+ yielding_fun);
+ ycf_node_rename_function(&yielding_fun->u.function,
+ ycf_string_new("%s_ycf_gen_yielding_3",
+ yielding_function_name));
+
+ if(insert_before == NULL){
+ yielding_fun = supress_warnings_wrap_yielding_fun(yielding_fun);
+ ycf_node_list_append(&tree_to_insert_to->u.c_file.content,
+ yielding_fun);
+ ycf_node_list_append(&tree_to_insert_to->u.c_file.content, wrapper_funs);
+ } else {
+ yielding_fun = supress_warnings_wrap_yielding_fun(yielding_fun);
+ ycf_node_list_insert_before(&tree_to_insert_to->u.c_file.content,
+ insert_before,
+ yielding_fun);
+ ycf_node_list_insert_before(&tree_to_insert_to->u.c_file.content,
+ insert_before,
+ wrapper_funs);
+ }
+ }else {
+ if(insert_before == NULL){
+ yielding_fun = supress_warnings_wrap_yielding_fun(yielding_fun);
+ ycf_node_list_append(&tree_to_insert_to->u.c_file.content,
+ yielding_fun);
+ }else{
+ yielding_fun = supress_warnings_wrap_yielding_fun(yielding_fun);
+ ycf_node_list_insert_before(&tree_to_insert_to->u.c_file.content,
+ insert_before,
+ yielding_fun);
+ }
+ }
+ if(insert_before == NULL){
+ ycf_node_list_append(&tree_to_insert_to->u.c_file.content,
+ ycf_node_new_text_node("\n"
+ "#undef YCF_STACK_ALLOC\n"
+ "#undef YCF_IN_YIELDING_FUN\n"));
+ }else{
+ ycf_node_list_insert_after(&tree_to_insert_to->u.c_file.content,
+ yielding_fun,
+ ycf_node_new_text_node("\n"
+ "#undef YCF_STACK_ALLOC\n"
+ "#undef YCF_IN_YIELDING_FUN\n"
+ "#define YCF_STACK_ALLOC(SIZE) malloc(SIZE)\n"));
+ ycf_node_list_insert_after(&tree_to_insert_to->u.c_file.content,
+ insert_before,
+ ycf_node_new_text_node("\n#undef YCF_STACK_ALLOC\n"));
+ }
+ return yielding_fun;
+}
+
+ycf_node* ast_get_ast_with_yieldified_function(ycf_node* source_tree,
+ ycf_node* header_tree,
+ char* yielding_function_name,
+ ycf_string_item_list* all_yielding_function_names,
+ bool auto_yield,
+ bool recusive_auto_yield,
+ bool debug_mode,
+ bool only_yielding_funs,
+ ycf_node** only_yielding_funs_tree,
+ bool static_aux_funs)
+{
+ ycf_yield_location_id_counter = 0;
+ ycf_node* tree_ret = ycf_node_deep_copy(source_tree);
+ if (*only_yielding_funs_tree == NULL){
+ *only_yielding_funs_tree = ycf_node_c_file_new(ycf_node_list_empty());
+ }
+ /* Find function */
+ ycf_node* fun = ycf_node_find_function(tree_ret, yielding_function_name);
+ if(fun == NULL){
+ fprintf(stderr, "Could not find function %s\n", yielding_function_name);
+ exit(1);
+ }
+ ycf_node* fun_change = ycf_node_deep_copy(fun);
+ /* Replace alternative syntax for special code sections */
+ fun_change = replace_alt_syntax_special_code_section_code(fun_change);
+ ycf_node_normalize_function(fun_change);
+ /* Brake up control expressions with simple calls to yielding functions */
+ break_up_control_expressions(&fun_change->u.function.body, all_yielding_function_names);
+ /* Insert trap state var for calls to yielding functions */
+ insert_fun_call_state_var(&fun_change->u.function.body, all_yielding_function_names);
+ /* Normalize the function to make transformation easier (move all declarations to the top etc) */
+ ycf_node_normalize_function(fun_change);
+ /* Insert YCF_CONSUME_REDS(1) code if auto yielding is on */
+ if(auto_yield){
+ insert_consume_reds_calls(&fun_change->u.function.body);
+ }
+ /* Save variable declaraions */
+ ycf_node_list uniqified_parameters =
+ ycf_node_list_shallow_copy(fun_change->u.function.definition.parameters);
+ ycf_node_list scope_defs =
+ ycf_node_list_shallow_copy(ycf_node_get_declarations_in_scope(&fun_change->u.function.body));
+ ycf_node_list defs =
+ ycf_node_list_shallow_copy(ycf_node_get_all_definitions_in_function(&fun_change->u.function));
+ /* Add extra vaiables that are needed for yielding */
+ ycf_node_list extra_ycf_trap_state = mk_trap_extra_state();
+ ycf_node_list_concat(&scope_defs, &extra_ycf_trap_state);
+ fun_change->u.function.body.definition_nodes = scope_defs;
+ /* Generate trap state struct */
+ ycf_node_list trap_state_struct_var_declarations =
+ ycf_node_list_shallow_copy(extra_ycf_trap_state);
+ ycf_node_list_concat(&trap_state_struct_var_declarations, &defs);
+ char* ycf_trap_state_struct_name =
+ ycf_string_new("gen_ycf_trap_state_for_%s",
+ yielding_function_name);
+ trap_state_struct_var_declarations =
+ ycf_node_list_copy_concat(mk_saved_ycf_trap_state_params(),
+ trap_state_struct_var_declarations);
+ ycf_node* ycf_trap_state_struct =
+ mk_typedef_struct_node(trap_state_struct_var_declarations,
+ ycf_trap_state_struct_name);
+ /* Add extra parameters for trapping */
+ {
+ ycf_node_list trap_params = mk_ycf_trap_state_params();
+ ycf_node_list saved_params = mk_saved_ycf_trap_state_params();
+ ycf_node_list_concat(&trap_params, &saved_params);
+ ycf_node_list_concat(&trap_params, &fun_change->u.function.definition.parameters);
+ fun_change->u.function.definition.parameters = trap_params;
+ fun_change->u.function.definition.ignore_param_ending = 1;
+ }
+ /* Collect and replace special code */
+ ycf_node_list on_save_yield_state_code_list =
+ save_and_replace_special_code(&fun_change->u.function.body,
+ ycf_node_type_on_save_yield_state_code);
+ ycf_node_list on_restore_yield_state_code_list =
+ save_and_replace_special_code(&fun_change->u.function.body,
+ ycf_node_type_on_restore_yield_state_code);
+ ycf_node_list on_destroy_state_code_list =
+ save_and_replace_special_code(&fun_change->u.function.body,
+ ycf_node_type_on_destroy_state_code);
+ ycf_node_list on_destroy_state_or_return_code_list =
+ save_and_replace_special_code(&fun_change->u.function.body,
+ ycf_node_type_on_destroy_state_or_return_code);
+ ycf_node_list on_return_code_list =
+ save_and_replace_special_code(&fun_change->u.function.body, ycf_node_type_on_return_code);
+ save_nr_of_reductions_before_return(&fun_change->u.function.body,
+ on_destroy_state_or_return_code_list,
+ on_return_code_list,
+ recusive_auto_yield);
+ /* Insert goto yield code in function */
+ insert_yield_code(fun_change,
+ ycf_trap_state_struct_name,
+ trap_state_struct_var_declarations,
+ &fun_change->u.function.body);
+ insert_yield_no_reds_code(fun_change,
+ ycf_trap_state_struct_name,
+ trap_state_struct_var_declarations,
+ &fun_change->u.function.body);
+ {
+ ycf_string_item_list fun_call_state_vars =
+ insert_yielding_fun_call_code(source_tree,
+ &fun_change->u.function.body,
+ all_yielding_function_names,
+ yielding_function_name,
+ recusive_auto_yield);
+ /* Null all fun_call_state_vars */
+ ycf_node_list_prepend(&fun_change->u.function.body.other_nodes, mk_null_vars_code(fun_call_state_vars));
+ }
+ /* Replace YCF_CONSUME_REDS calls */
+ insert_consume_reds_code(yielding_function_name, &fun_change->u.function.body);
+ /* Insert yield initialization in the beginning of the function */
+ {
+ ycf_node* trap_init = mk_yield_init_code(ycf_trap_state_struct_name,
+ trap_state_struct_var_declarations,
+ on_restore_yield_state_code_list,
+ auto_yield,
+ yielding_function_name);
+ ycf_node_list_prepend(&fun_change->u.function.body.other_nodes, trap_init);
+ }
+ /* Add code that saves the state and yields to the end of the function */
+ ycf_node_list_append(&fun_change->u.function.body.other_nodes,
+ mk_yield_code(fun_change,
+ ycf_trap_state_struct_name,
+ trap_state_struct_var_declarations,
+ on_save_yield_state_code_list,
+ debug_mode));
+ /* Change name of function */
+ ycf_node_rename_function(&fun_change->u.function,
+ ycf_string_new("%s_ycf_gen_yielding",
+ yielding_function_name));
+ /* Remove unecessary scopes */
+ ycf_node_remove_unecessary_scopes(&fun_change->u.function.body);
+ /* Make continue function */
+ ycf_node* continue_function =
+ mk_continue_function_node(yielding_function_name,
+ ycf_trap_state_struct_name,
+ fun,
+ uniqified_parameters);
+ /* Make destroy state function */
+ ycf_node* destroy_state_function =
+ mk_destroy_state_function_node(yielding_function_name,
+ trap_state_struct_var_declarations,
+ ycf_trap_state_struct_name,
+ on_destroy_state_code_list,
+ on_destroy_state_or_return_code_list,
+ static_aux_funs);
+ ycf_node* fun_change_dec = mk_fun_def(fun_change);
+ /****************************************************************
+ *
+ * The following code inserts the changed function and its helper
+ * function into the output AST(s)
+ *
+ ****************************************************************/
+ /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+ /* Insert changed function into tree that will be printed */
+ /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+ ycf_node* fun_ret = ycf_node_find_function(tree_ret, yielding_function_name);
+ ycf_node* fun_change_wrapper =
+ insert_yielding_function_with_prefix_suffix(tree_ret,
+ fun_ret,
+ fun_change,
+ debug_mode,
+ yielding_function_name,
+ ycf_trap_state_struct_name);
+ /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+ /* END: Insert changed function into tree that will be printed */
+ /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+ /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+ /* Insert declarations in the top */
+ /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+ ycf_node* fun_dec_node = ycf_node_find_function_declaration(tree_ret, yielding_function_name);
+ ycf_node_list_prepend(&tree_ret->u.c_file.content, ycf_node_new_text_node("\n"));
+ if(fun_dec_node == NULL){
+ ycf_node_list_prepend(&tree_ret->u.c_file.content,
+ mk_wrap_in_surpress_warn("unused-function",
+ mk_fun_def(destroy_state_function)));
+ ycf_node_list_prepend(&tree_ret->u.c_file.content,
+ mk_wrap_in_surpress_warn("unused-function",
+ mk_fun_def(continue_function)));
+ } else {
+ ycf_node_list_insert_before(&tree_ret->u.c_file.content,
+ fun_dec_node,
+ mk_wrap_in_surpress_warn("unused-function",
+ mk_fun_def(destroy_state_function)));
+ ycf_node_list_insert_before(&tree_ret->u.c_file.content,
+ fun_dec_node,
+ mk_wrap_in_surpress_warn("unused-function",
+ mk_fun_def(continue_function)));
+
+ }
+ ycf_node_list_prepend(&header_tree->u.c_file.content, ycf_node_shallow_copy(fun_change_dec));
+ ycf_node_list_prepend(&header_tree->u.c_file.content, ycf_node_new_text_node("\n"));
+ ycf_node_list_prepend(&header_tree->u.c_file.content,
+ mk_wrap_in_surpress_warn("unused-function",
+ mk_fun_def(destroy_state_function)));
+ ycf_node_list_prepend(&header_tree->u.c_file.content,
+ mk_wrap_in_surpress_warn("unused-function",
+ mk_fun_def(continue_function)));
+ ycf_node* ycf_trap_state_struct_dec =
+ ycf_node_new_text_node(ycf_string_new("\n\nstruct %s;",
+ ycf_trap_state_struct->u.gen_typedef_struct.name));
+ ycf_node_list_prepend(&tree_ret->u.c_file.content,
+ ycf_trap_state_struct_dec);
+ ycf_node_list_prepend(&header_tree->u.c_file.content,
+ ycf_node_shallow_copy(ycf_trap_state_struct_dec));
+ ycf_node_list_append(&header_tree->u.c_file.content,
+ ycf_node_new_text_node("\n"));
+ /* Insert definition of changed function */
+ if(fun_dec_node != NULL){
+ ycf_node_list_insert_before(&tree_ret->u.c_file.content,
+ fun_dec_node,
+ ycf_node_shallow_copy(ycf_node_shallow_copy(fun_change_dec)));
+ }else{
+ ycf_node_list_insert_before(&tree_ret->u.c_file.content,
+ fun_change_wrapper,
+ ycf_node_shallow_copy(ycf_node_shallow_copy(fun_change_dec)));
+ }
+ ycf_node_list_insert_before(&tree_ret->u.c_file.content, fun_change_wrapper, ycf_trap_state_struct);
+ ycf_node_list_insert_after(&tree_ret->u.c_file.content,
+ ycf_trap_state_struct,
+ supress_warnings_wrap_destroy_fun(destroy_state_function));
+ ycf_node_list_insert_after(&tree_ret->u.c_file.content,
+ ycf_trap_state_struct,
+ mk_wrap_in_surpress_warn("unused-function",
+ continue_function));
+ /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+ /* END: Insert declarations in the top */
+ /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+ if(only_yielding_funs){
+ ycf_node_list_prepend(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_new_text_node("\n"));
+ ycf_node_list_prepend(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_shallow_copy(fun_change_dec));
+ ycf_node_list_prepend(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_new_text_node("\n"));
+ ycf_node_list_prepend(&(*only_yielding_funs_tree)->u.c_file.content,
+ mk_wrap_in_surpress_warn("unused-function",mk_fun_def(destroy_state_function)));
+ ycf_node_list_prepend(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_new_text_node("\n"));
+ ycf_node_list_prepend(&(*only_yielding_funs_tree)->u.c_file.content,
+ mk_wrap_in_surpress_warn("unused-function",
+ mk_fun_def(continue_function)));
+ ycf_node_list_prepend(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_new_text_node("\n"));
+ ycf_node_list_prepend(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_shallow_copy(ycf_trap_state_struct_dec));
+ ycf_node_list_prepend(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_new_text_node("\n"));
+
+ ycf_node_list_append(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_shallow_copy(ycf_trap_state_struct));
+ ycf_node_list_append(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_new_text_node("\n"));
+ ycf_node_list_append(&(*only_yielding_funs_tree)->u.c_file.content,
+ supress_warnings_wrap_destroy_fun(destroy_state_function));
+ ycf_node_list_append(&(*only_yielding_funs_tree)->u.c_file.content,
+ ycf_node_new_text_node("\n"));
+ ycf_node_list_append(&(*only_yielding_funs_tree)->u.c_file.content,
+ mk_wrap_in_surpress_warn("unused-function",continue_function));
+ insert_yielding_function_with_prefix_suffix((*only_yielding_funs_tree),
+ NULL,
+ fun_change,
+ debug_mode,
+ yielding_function_name,
+ ycf_trap_state_struct_name);
+ }
+ return tree_ret;
+}
diff --git a/erts/lib_src/yielding_c_fun/ycf_yield_fun.h b/erts/lib_src/yielding_c_fun/ycf_yield_fun.h
new file mode 100644
index 0000000000..410256e71c
--- /dev/null
+++ b/erts/lib_src/yielding_c_fun/ycf_yield_fun.h
@@ -0,0 +1,116 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Author: Kjell Winblad
+ */
+
+
+#ifndef YCF_AST_H
+#define YCF_AST_H
+
+#include "ycf_utils.h"
+#include "ycf_symbol.h"
+#include "ycf_string.h"
+#include "ycf_node.h"
+
+
+typedef struct {
+ bool success;
+ ycf_symbol* next_symbol;
+ ycf_node* result;
+} ycf_parse_result;
+
+
+/* Functions for strings */
+
+void string_item_list_print(ycf_string_item_list n);
+
+
+/* Functions for symbol lists */
+
+int ycf_symbol_list_get_item_position(ycf_symbol_list* list, ycf_symbol* node);
+
+ycf_symbol* ycf_symbol_shallow_copy(ycf_symbol* n);
+ycf_symbol* ycf_symbol_list_get_item_at_position(ycf_symbol_list* list, int pos);
+
+void ycf_symbol_list_append(ycf_symbol_list* list, ycf_symbol* node);
+void ycf_symbol_list_prepend(ycf_symbol_list* list, ycf_symbol* node);
+void ycf_symbol_list_insert_before(ycf_symbol_list* list, ycf_symbol* before_this, ycf_symbol* to_insert);
+void ycf_symbol_list_insert_after(ycf_symbol_list* list, ycf_symbol* after_this, ycf_symbol* to_insert);
+void ycf_symbol_list_remove(ycf_symbol_list* list, ycf_symbol* to_remove);
+void ycf_symbol_list_replace(ycf_symbol_list* list, ycf_symbol* to_replace, ycf_symbol* replace_with);
+void ycf_symbol_list_concat(ycf_symbol_list* list1, ycf_symbol_list* list2);
+
+ycf_symbol_list ycf_symbol_list_empty();
+ycf_symbol_list ycf_symbol_list_shallow_copy(ycf_symbol_list n);
+ycf_symbol_list ycf_symbol_list_copy_append(ycf_symbol_list list, ycf_symbol* node);
+ycf_symbol_list ycf_symbol_list_copy_prepend(ycf_symbol_list list, ycf_symbol* node);
+ycf_symbol_list ycf_symbol_list_copy_insert_before(ycf_symbol_list list, ycf_symbol* before_this, ycf_symbol* to_insert);
+ycf_symbol_list ycf_symbol_list_copy_insert_after(ycf_symbol_list list, ycf_symbol* after_this, ycf_symbol* to_insert);
+ycf_symbol_list ycf_symbol_list_copy_remove(ycf_symbol_list list, ycf_symbol* to_remove);
+ycf_symbol_list ycf_symbol_list_copy_replace(ycf_symbol_list list, ycf_symbol* to_replace, ycf_symbol* replace_with);
+ycf_symbol_list ycf_symbol_list_copy_concat(ycf_symbol_list list1, ycf_symbol_list list2);
+
+
+
+
+/* Functions for parsing text to AST */
+
+ycf_node* ycf_node_scope_new(ycf_symbol* start,
+ ycf_node_list declaration_nodes,
+ ycf_node_list other_nodes,
+ ycf_symbol* end);
+ycf_parse_result parse_expression(ycf_symbol* symbols);
+
+
+void print_abstract_syntax_tree(ycf_node* node);
+void print_node_code_paran_expression(ycf_node_parentheses_expression e, ycf_string_printable_buffer* b);
+void print_node_code_expression(ycf_node_expression e, ycf_string_printable_buffer* b);
+
+void ast_add_yield_code_generated_define(ycf_node* source_out_tree/*Will be changed*/, bool debug_mode);
+void print_symbol_list(ycf_symbol_list* l, ycf_string_printable_buffer* b);
+
+
+/* Abstract syntax tree functions */
+
+
+
+ycf_node* ycf_node_find_function(ycf_node* c_file_node, char* fun_name);
+ycf_node_list ycf_node_get_all_definitions_in_function(ycf_node_function* f);
+
+
+ycf_node* ast_get_ast_with_yieldified_function(ycf_node* source_tree,
+ ycf_node* header_tree, /*Will be changed*/
+ char* yielding_function_name,
+ ycf_string_item_list* all_yielding_function_names,
+ bool auto_yield,
+ bool recusive_auto_yield,
+ bool debug_mode,
+ bool only_yielding_funs,
+ ycf_node** only_yielding_funs_tree,
+ bool static_aux_funs);
+
+void print_abstract_syntax_tree(ycf_node* node);
+void print_node_code_expression(ycf_node_expression e, ycf_string_printable_buffer* b);
+void print_node_list_code(ycf_node* n, ycf_string_printable_buffer* b);
+void ast_add_yield_code_generated_define(ycf_node* source_out_tree, bool debug_mode);
+
+#endif
diff --git a/erts/preloaded/ebin/atomics.beam b/erts/preloaded/ebin/atomics.beam
index 91d7fc2ad4..11f86ee9bd 100644
--- a/erts/preloaded/ebin/atomics.beam
+++ b/erts/preloaded/ebin/atomics.beam
Binary files differ
diff --git a/erts/preloaded/ebin/counters.beam b/erts/preloaded/ebin/counters.beam
index 133bf87fe0..4b8cb80ddc 100644
--- a/erts/preloaded/ebin/counters.beam
+++ b/erts/preloaded/ebin/counters.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erl_init.beam b/erts/preloaded/ebin/erl_init.beam
index fc8b1f95ac..4f22f77a27 100644
--- a/erts/preloaded/ebin/erl_init.beam
+++ b/erts/preloaded/ebin/erl_init.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erl_prim_loader.beam b/erts/preloaded/ebin/erl_prim_loader.beam
index 4c0ccccea1..111d93b11e 100644
--- a/erts/preloaded/ebin/erl_prim_loader.beam
+++ b/erts/preloaded/ebin/erl_prim_loader.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erl_tracer.beam b/erts/preloaded/ebin/erl_tracer.beam
index 3553a9b744..8e490aad52 100644
--- a/erts/preloaded/ebin/erl_tracer.beam
+++ b/erts/preloaded/ebin/erl_tracer.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam
index 050db2b8b2..61e841b703 100644
--- a/erts/preloaded/ebin/erlang.beam
+++ b/erts/preloaded/ebin/erlang.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erts_code_purger.beam b/erts/preloaded/ebin/erts_code_purger.beam
index 468e5fa9ed..b42aa2f77d 100644
--- a/erts/preloaded/ebin/erts_code_purger.beam
+++ b/erts/preloaded/ebin/erts_code_purger.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam b/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam
index b92fb1f18f..00b4270384 100644
--- a/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam
+++ b/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erts_internal.beam b/erts/preloaded/ebin/erts_internal.beam
index 0815644727..f7650c1564 100644
--- a/erts/preloaded/ebin/erts_internal.beam
+++ b/erts/preloaded/ebin/erts_internal.beam
Binary files differ
diff --git a/erts/preloaded/ebin/erts_literal_area_collector.beam b/erts/preloaded/ebin/erts_literal_area_collector.beam
index 6a3ec567da..f10723e575 100644
--- a/erts/preloaded/ebin/erts_literal_area_collector.beam
+++ b/erts/preloaded/ebin/erts_literal_area_collector.beam
Binary files differ
diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam
index 6f4d981219..6ff224c4f4 100644
--- a/erts/preloaded/ebin/init.beam
+++ b/erts/preloaded/ebin/init.beam
Binary files differ
diff --git a/erts/preloaded/ebin/persistent_term.beam b/erts/preloaded/ebin/persistent_term.beam
index a54d92ace5..6908d741c9 100644
--- a/erts/preloaded/ebin/persistent_term.beam
+++ b/erts/preloaded/ebin/persistent_term.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_buffer.beam b/erts/preloaded/ebin/prim_buffer.beam
index 4beee4d55f..3c0ee0ab01 100644
--- a/erts/preloaded/ebin/prim_buffer.beam
+++ b/erts/preloaded/ebin/prim_buffer.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_eval.beam b/erts/preloaded/ebin/prim_eval.beam
index 377f5c6ad9..4378d33a25 100644
--- a/erts/preloaded/ebin/prim_eval.beam
+++ b/erts/preloaded/ebin/prim_eval.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam
index 475fa36382..8356308267 100644
--- a/erts/preloaded/ebin/prim_file.beam
+++ b/erts/preloaded/ebin/prim_file.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam
index ffb9fcac11..97cb7565b2 100644
--- a/erts/preloaded/ebin/prim_inet.beam
+++ b/erts/preloaded/ebin/prim_inet.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_net.beam b/erts/preloaded/ebin/prim_net.beam
index 3c8f4d04fe..510856e5c0 100644
--- a/erts/preloaded/ebin/prim_net.beam
+++ b/erts/preloaded/ebin/prim_net.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_socket.beam b/erts/preloaded/ebin/prim_socket.beam
new file mode 100644
index 0000000000..c42f68512a
--- /dev/null
+++ b/erts/preloaded/ebin/prim_socket.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_zip.beam b/erts/preloaded/ebin/prim_zip.beam
index effe8e96d8..cc9224ead4 100644
--- a/erts/preloaded/ebin/prim_zip.beam
+++ b/erts/preloaded/ebin/prim_zip.beam
Binary files differ
diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam
deleted file mode 100644
index da5cd1734b..0000000000
--- a/erts/preloaded/ebin/socket.beam
+++ /dev/null
Binary files differ
diff --git a/erts/preloaded/ebin/socket_registry.beam b/erts/preloaded/ebin/socket_registry.beam
index 08fe74594a..1ac302f5f0 100644
--- a/erts/preloaded/ebin/socket_registry.beam
+++ b/erts/preloaded/ebin/socket_registry.beam
Binary files differ
diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam
index 956726359e..38eb604124 100644
--- a/erts/preloaded/ebin/zlib.beam
+++ b/erts/preloaded/ebin/zlib.beam
Binary files differ
diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile
index d2153f23e1..b0c205cec8 100644
--- a/erts/preloaded/src/Makefile
+++ b/erts/preloaded/src/Makefile
@@ -36,7 +36,7 @@ include $(ERL_TOP)/lib/kernel/vsn.mk
ifeq ($(USE_ESOCK), yes)
PRE_LOADED_ERL_ESOCK_MODULES = \
socket_registry \
- socket \
+ prim_socket \
prim_net
else
PRE_LOADED_ERL_ESOCK_MODULES =
@@ -82,7 +82,7 @@ APP_FILE= erts.app
APP_SRC= $(APP_FILE).src
APP_TARGET= $(STATIC_EBIN)/$(APP_FILE)
ifeq ($(USE_ESOCK), yes)
-APP_ESOCK_MODS= prim_net, socket, socket_registry
+APP_ESOCK_MODS= prim_net, prim_socket, socket_registry,
else
APP_ESOCK_MODS=
endif
diff --git a/erts/preloaded/src/erl_init.erl b/erts/preloaded/src/erl_init.erl
index dadf7dda6f..c19bebdd20 100644
--- a/erts/preloaded/src/erl_init.erl
+++ b/erts/preloaded/src/erl_init.erl
@@ -35,8 +35,8 @@ start(Mod, BootArgs) ->
erl_tracer:on_load(),
prim_buffer:on_load(),
prim_file:on_load(),
- %% socket:on_load(), prim_net:on_load(),
- conditional_load(socket, [socket, prim_net]),
+ %% prim_socket:on_load(), prim_net:on_load(),
+ conditional_load(prim_socket, [prim_socket, prim_net]),
%% Proceed to the specified boot module
run(Mod, boot, BootArgs).
diff --git a/erts/preloaded/src/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl
index c46b5e706a..20fda619b8 100644
--- a/erts/preloaded/src/erl_prim_loader.erl
+++ b/erts/preloaded/src/erl_prim_loader.erl
@@ -1194,7 +1194,7 @@ ensure_virtual_dirs(Components, Fun, FakeFI, Includes, Dirs, Acc) ->
{I, F, Acc3};
true ->
%% The directory element does already exist
- %% Recursivly ensure dir elements on all levels
+ %% Recursively ensure dir elements on all levels
ensure_virtual_dirs(Dir,Fun,FakeFI,Includes,Dirs,Acc)
end
end.
diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl
index f78a79203c..c8c3b36c8d 100644
--- a/erts/preloaded/src/erlang.erl
+++ b/erts/preloaded/src/erlang.erl
@@ -20,12 +20,14 @@
-module(erlang).
-export([apply/2,apply/3,spawn/4,spawn_link/4,
- spawn_monitor/1,spawn_monitor/3,
+ spawn_monitor/1,spawn_monitor/2,
+ spawn_monitor/3,spawn_monitor/4,
spawn_opt/2,spawn_opt/3,spawn_opt/4,spawn_opt/5,
- disconnect_node/1]).
+ spawn_request/1, spawn_request/2,
+ spawn_request/3, spawn_request/4, spawn_request/5,
+ spawn_request_abandon/1, disconnect_node/1]).
-export([spawn/1, spawn_link/1, spawn/2, spawn_link/2]).
-export([yield/0]).
--export([crasher/6]).
-export([fun_info/1]).
-export([send_nosuspend/2, send_nosuspend/3]).
-export([localtime_to_universaltime/1]).
@@ -52,7 +54,13 @@
dist_ctrl_set_opt/3,
dist_get_stat/1]).
--deprecated([get_stacktrace/0,now/0]).
+-deprecated([{get_stacktrace,0,
+ "use the new try/catch syntax for retrieving the "
+ "stack backtrace"}]).
+-deprecated([{now,0,
+ "see the \"Time and Time Correction in Erlang\" "
+ "chapter of the ERTS User's Guide for more information"}]).
+-removed([{hash,2,"use erlang:phash2/2 instead"}]).
%% Get rid of autoimports of spawn to avoid clashes with ourselves.
-compile({no_auto_import,[spawn_link/1]}).
@@ -64,8 +72,13 @@
-export_type([timestamp/0]).
-export_type([time_unit/0]).
-export_type([deprecated_time_unit/0]).
+-export_type([spawn_opt_option/0]).
+-export_type([priority_level/0]).
+-export_type([max_heap_size/0]).
+-export_type([message_queue_data/0]).
-type ext_binary() :: binary().
+-type ext_iovec() :: iovec().
-type timestamp() :: {MegaSecs :: non_neg_integer(),
Secs :: non_neg_integer(),
MicroSecs :: non_neg_integer()}.
@@ -109,8 +122,11 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-export([adler32/1, adler32/2, adler32_combine/3, append_element/2]).
--export([atom_to_binary/2, atom_to_list/1, binary_part/2, binary_part/3]).
--export([binary_to_atom/2, binary_to_existing_atom/2, binary_to_float/1]).
+-export([atom_to_binary/1, atom_to_binary/2]).
+-export([atom_to_list/1, binary_part/2, binary_part/3]).
+-export([binary_to_atom/1, binary_to_atom/2]).
+-export([binary_to_existing_atom/1, binary_to_existing_atom/2]).
+-export([binary_to_float/1]).
-export([binary_to_integer/1,binary_to_integer/2]).
-export([binary_to_list/1]).
-export([binary_to_list/3, binary_to_term/1, binary_to_term/2]).
@@ -178,14 +194,37 @@
make_tuple/2, make_tuple/3, nodes/1, open_port/2,
port_call/2, port_call/3, port_info/1, port_info/2, process_flag/2,
process_info/2, send/2, send/3, seq_trace_info/1,
- setelement/3, spawn_opt/1,
+ setelement/3,
statistics/1, subtract/2, system_flag/2,
- term_to_binary/1, term_to_binary/2, tl/1, trace_pattern/2,
+ term_to_binary/1, term_to_binary/2,
+ term_to_iovec/1, term_to_iovec/2,
+ tl/1, trace_pattern/2,
trace_pattern/3, tuple_to_list/1, system_info/1,
universaltime_to_localtime/1]).
-export([dt_get_tag/0, dt_get_tag_data/0, dt_prepend_vm_tag_data/1, dt_append_vm_tag_data/1,
dt_put_tag/1, dt_restore_tag/1, dt_spread_tag/1]).
+%% Operators
+
+-export(['=='/2, '=:='/2,
+ '/='/2, '=/='/2,
+ '=<'/2, '>='/2,
+ '<'/2, '>'/2]).
+
+-export(['-'/1, '+'/1,
+ '-'/2, '+'/2,
+ '/'/2, '*'/2,
+ 'div'/2, 'rem'/2,
+ 'bsl'/2, 'bsr'/2,
+ 'bor'/2, 'band'/2,
+ 'bxor'/2, 'bnot'/1]).
+
+-export(['and'/2, 'or'/2,
+ 'xor'/2, 'not'/1]).
+
+-export(['--'/2, '++'/2]).
+
+-export(['!'/2]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Simple native code BIFs
@@ -209,20 +248,10 @@
type |
uniq.
--type seq_trace_info() ::
- 'send' |
- 'receive' |
- 'print' |
- 'timestamp' |
- 'monotonic_timestamp' |
- 'strict_monotonic_timestamp' |
- 'label' |
- 'serial'.
-
-type seq_trace_info_returns() ::
- { seq_trace_info(), non_neg_integer() |
- boolean() |
- { non_neg_integer(), non_neg_integer() } } |
+ { 'send' | 'receive' | 'print' | 'timestamp' | 'monotonic_timestamp' | 'strict_monotonic_timestamp', boolean() } |
+ { 'label', term() } |
+ { 'serial', { non_neg_integer(), non_neg_integer() } } |
[].
-type system_profile_option() ::
@@ -341,6 +370,12 @@ adler32_combine(_FirstAdler, _SecondAdler, _SecondSize) ->
append_element(_Tuple1, _Term) ->
erlang:nif_error(undefined).
+%% atom_to_binary/1
+-spec atom_to_binary(Atom) -> binary() when
+ Atom :: atom().
+atom_to_binary(Atom) ->
+ erlang:atom_to_binary(Atom, utf8).
+
%% atom_to_binary/2
-spec atom_to_binary(Atom, Encoding) -> binary() when
Atom :: atom(),
@@ -371,6 +406,12 @@ binary_part(_Subject, _PosLen) ->
binary_part(_Subject, _Start, _Length) ->
erlang:nif_error(undefined).
+%% binary_to_atom/1
+-spec binary_to_atom(Binary) -> atom() when
+ Binary :: binary().
+binary_to_atom(Binary) ->
+ erlang:binary_to_atom(Binary, utf8).
+
%% binary_to_atom/2
-spec binary_to_atom(Binary, Encoding) -> atom() when
Binary :: binary(),
@@ -378,6 +419,12 @@ binary_part(_Subject, _Start, _Length) ->
binary_to_atom(_Binary, _Encoding) ->
erlang:nif_error(undefined).
+%% binary_to_existing_atom/1
+-spec binary_to_existing_atom(Binary) -> atom() when
+ Binary :: binary().
+binary_to_existing_atom(Binary) ->
+ erlang:binary_to_existing_atom(Binary, utf8).
+
%% binary_to_existing_atom/2
-spec binary_to_existing_atom(Binary, Encoding) -> atom() when
Binary :: binary(),
@@ -585,7 +632,7 @@ date() ->
HttpHeader :: {'http_header',
integer(),
HttpField,
- Reserved :: term(),
+ UnmodifiedField :: HttpString,
Value :: HttpString},
HttpError :: {'http_error', HttpString},
HttpMethod :: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE'
@@ -1710,11 +1757,9 @@ setnode(_P1, _P2) ->
-spec erlang:setnode(Node, DistCtrlr, Opts) -> dist_handle() when
Node :: atom(),
DistCtrlr :: port() | pid(),
- Opts :: {integer(), integer(), atom(), atom()}.
-setnode(Node, DistCtrlr, {Flags, Ver, IC, OC} = Opts) when erlang:is_atom(IC),
- erlang:is_atom(OC) ->
- case case erts_internal:create_dist_channel(Node, DistCtrlr,
- Flags, Ver) of
+ Opts :: {integer(), integer(), pos_integer()}.
+setnode(Node, DistCtrlr, {_Flags, _Ver, _Creation} = Opts) ->
+ case case erts_internal:create_dist_channel(Node, DistCtrlr, Opts) of
{ok, DH} -> DH;
{message, Ref} -> receive {Ref, Res} -> Res end;
Err -> Err
@@ -2186,7 +2231,7 @@ nodes(_Arg) ->
-spec open_port(PortName, PortSettings) -> port() when
PortName :: {spawn, Command :: string() | binary()} |
{spawn_driver, Command :: string() | binary()} |
- {spawn_executable, FileName :: file:name() } |
+ {spawn_executable, FileName :: file:name_all() } |
{fd, In :: non_neg_integer(), Out :: non_neg_integer()},
PortSettings :: [Opt],
Opt :: {packet, N :: 1 | 2 | 4}
@@ -2381,7 +2426,7 @@ send(_Dest,_Msg,_Options) ->
(timestamp) -> {timestamp, boolean()};
(monotonic_timestamp) -> {timestamp, boolean()};
(strict_monotonic_timestamp) -> {strict_monotonic_timestamp, boolean()};
- (label) -> [] | {label, non_neg_integer()};
+ (label) -> [] | {label, term()};
(serial) -> [] | {serial, {non_neg_integer(), non_neg_integer()}}.
seq_trace_info(_What) ->
erlang:nif_error(undefined).
@@ -2395,20 +2440,6 @@ seq_trace_info(_What) ->
setelement(_Index, _Tuple1, _Value) ->
erlang:nif_error(undefined).
--spec erlang:spawn_opt({Module, Function, Args, Options}) -> pid() | {pid(), reference()} when
- Module :: module(),
- Function :: atom(),
- Args :: [term()],
- Options :: [Option],
- Option :: link | monitor
- | {priority, Level :: priority_level()}
- | {fullsweep_after, Number :: non_neg_integer()}
- | {min_heap_size, Size :: non_neg_integer()}
- | {max_heap_size, Size :: max_heap_size()}
- | {min_bin_vheap_size, VSize :: non_neg_integer()}.
-spawn_opt(_Tuple) ->
- erlang:nif_error(undefined).
-
-spec statistics(active_tasks) -> [ActiveTasks] when
ActiveTasks :: non_neg_integer();
(active_tasks_all) -> [ActiveTasks] when
@@ -2556,11 +2587,24 @@ term_to_binary(_Term) ->
-spec term_to_binary(Term, Options) -> ext_binary() when
Term :: term(),
Options :: [compressed |
- {compressed, Level :: 0..9} |
- {minor_version, Version :: 0..2} ].
+ {compressed, Level :: 0..9} |
+ {minor_version, Version :: 0..2} ].
term_to_binary(_Term, _Options) ->
erlang:nif_error(undefined).
+-spec term_to_iovec(Term) -> ext_iovec() when
+ Term :: term().
+term_to_iovec(_Term) ->
+ erlang:nif_error(undefined).
+
+-spec term_to_iovec(Term, Options) -> ext_iovec() when
+ Term :: term(),
+ Options :: [compressed |
+ {compressed, Level :: 0..9} |
+ {minor_version, Version :: 0..2} ].
+term_to_iovec(_Term, _Options) ->
+ erlang:nif_error(undefined).
+
%% Shadowed by erl_bif_types: erlang:tl/1
-spec tl(List) -> term() when
List :: [term(), ...].
@@ -2836,10 +2880,24 @@ spawn_link(N, F) ->
-spec spawn_monitor(Fun) -> {pid(), reference()} when
Fun :: function().
spawn_monitor(F) when erlang:is_function(F, 0) ->
- erlang:spawn_opt({erlang,apply,[F,[]],[monitor]});
+ erlang:spawn_opt(erlang,apply,[F,[]],[monitor]);
spawn_monitor(F) ->
erlang:error(badarg, [F]).
+-spec spawn_monitor(Node, Fun) -> {pid(), reference()} when
+ Node :: node(),
+ Fun :: function().
+
+spawn_monitor(Node, F) when erlang:is_atom(Node), erlang:is_function(F, 0) ->
+ try
+ erlang:spawn_monitor(Node,erlang,apply,[F,[]])
+ catch
+ error:Err ->
+ erlang:error(Err, [Node, F])
+ end;
+spawn_monitor(Node, F) ->
+ erlang:error(badarg, [Node, F]).
+
-spec spawn_monitor(Module, Function, Args) -> {pid(), reference()} when
Module :: module(),
Function :: atom(),
@@ -2847,7 +2905,7 @@ spawn_monitor(F) ->
spawn_monitor(M, F, A) when erlang:is_atom(M),
erlang:is_atom(F),
erlang:is_list(A) ->
- erlang:spawn_opt({M,F,A,[monitor]});
+ erlang:spawn_opt(M,F,A,[monitor]);
spawn_monitor(M, F, A) ->
erlang:error(badarg, [M,F,A]).
@@ -2873,24 +2931,23 @@ spawn_monitor(M, F, A) ->
Fun :: function(),
Options :: [spawn_opt_option()].
spawn_opt(F, O) when erlang:is_function(F) ->
- spawn_opt(erlang, apply, [F, []], O);
+ erlang:spawn_opt(erlang, apply, [F, []], O);
spawn_opt({M,F}=MF, O) when erlang:is_atom(M), erlang:is_atom(F) ->
- spawn_opt(erlang, apply, [MF, []], O);
-spawn_opt({M,F,A}, O) -> % For (undocumented) backward compatibility
- spawn_opt(M, F, A, O);
+ erlang:spawn_opt(erlang, apply, [MF, []], O);
spawn_opt(F, O) ->
erlang:error(badarg, [F, O]).
-spec spawn_opt(Node, Fun, Options) -> pid() | {pid(), reference()} when
Node :: node(),
Fun :: function(),
- Options :: [spawn_opt_option()].
+ Options :: [monitor | link | OtherOption],
+ OtherOption :: term().
spawn_opt(N, F, O) when N =:= erlang:node() ->
- spawn_opt(F, O);
-spawn_opt(N, F, O) when erlang:is_function(F) ->
- spawn_opt(N, erlang, apply, [F, []], O);
+ erlang:spawn_opt(F, O);
+spawn_opt(N, F, O) when erlang:is_function(F, 0) ->
+ erlang:spawn_opt(N, erlang, apply, [F, []], O);
spawn_opt(N, {M,F}=MF, O) when erlang:is_atom(M), erlang:is_atom(F) ->
- spawn_opt(N, erlang, apply, [MF, []], O);
+ erlang:spawn_opt(N, erlang, apply, [MF, []], O);
spawn_opt(N, F, O) ->
erlang:error(badarg, [N, F, O]).
@@ -2909,24 +2966,11 @@ spawn(N,M,F,A) when N =:= erlang:node(),
spawn(N,M,F,A) when erlang:is_atom(N),
erlang:is_atom(M),
erlang:is_atom(F) ->
- case is_well_formed_list(A) of
- true ->
- ok;
- false ->
- erlang:error(badarg, [N, M, F, A])
- end,
- case catch gen_server:call({net_kernel,N},
- {spawn,M,F,A,erlang:group_leader()},
- infinity) of
- Pid when erlang:is_pid(Pid) ->
- Pid;
- Error ->
- case remote_spawn_error(Error, {no_link, N, M, F, A, []}) of
- {fault, Fault} ->
- erlang:error(Fault, [N, M, F, A]);
- Pid ->
- Pid
- end
+ try
+ erlang:spawn_opt(N, M, F, A, [])
+ catch
+ _:Reason ->
+ erlang:error(Reason, [N, M, F, A])
end;
spawn(N,M,F,A) ->
erlang:error(badarg, [N, M, F, A]).
@@ -2944,41 +2988,69 @@ spawn_link(N,M,F,A) when N =:= erlang:node(),
spawn_link(N,M,F,A) when erlang:is_atom(N),
erlang:is_atom(M),
erlang:is_atom(F) ->
- case is_well_formed_list(A) of
- true ->
- ok;
- _ ->
- erlang:error(badarg, [N, M, F, A])
- end,
- case catch gen_server:call({net_kernel,N},
- {spawn_link,M,F,A,erlang:group_leader()},
- infinity) of
- Pid when erlang:is_pid(Pid) ->
- Pid;
- Error ->
- case remote_spawn_error(Error, {link, N, M, F, A, []}) of
- {fault, Fault} ->
- erlang:error(Fault, [N, M, F, A]);
- Pid ->
- Pid
- end
+ try
+ erlang:spawn_opt(N, M, F, A, [link])
+ catch
+ _:Reason ->
+ erlang:error(Reason, [N, M, F, A])
end;
spawn_link(N,M,F,A) ->
erlang:error(badarg, [N, M, F, A]).
+-spec spawn_monitor(Node, Module, Function, Args) -> {pid(), reference()} when
+ Node :: node(),
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()].
+spawn_monitor(N,M,F,A) when N =:= erlang:node(),
+ erlang:is_atom(M),
+ erlang:is_atom(F),
+ erlang:is_list(A) ->
+ try
+ erlang:spawn_monitor(M,F,A)
+ catch
+ error:Err ->
+ erlang:error(Err, [N, M, F, A])
+ end;
+spawn_monitor(N,M,F,A) when erlang:is_atom(N),
+ erlang:is_atom(M),
+ erlang:is_atom(F) ->
+ Ref = try
+ erlang:spawn_request(N, M, F, A, [monitor])
+ catch
+ error:Err0 ->
+ erlang:error(Err0, [N, M, F, A])
+ end,
+ receive
+ {spawn_reply, Ref, ok, Pid} when erlang:is_pid(Pid) ->
+ {Pid, Ref};
+ {spawn_reply, Ref, error, badopt} ->
+ erlang:error(badarg, [N, M, F, A]);
+ {spawn_reply, Ref, error, noconnection} ->
+ try
+ erlang:spawn_opt(erts_internal,crasher,
+ [N,M,F,A,[monitor],
+ noconnection],
+ [monitor])
+ catch
+ _:Err1 ->
+ erlang:error(Err1, [N, M, F, A])
+ end;
+ {spawn_reply, Ref, error, Err2} ->
+ erlang:error(Err2, [N, M, F, A])
+ end;
+spawn_monitor(N,M,F,A) ->
+ erlang:error(badarg, [N, M, F, A]).
+
-spec spawn_opt(Module, Function, Args, Options) ->
pid() | {pid(), reference()} when
Module :: module(),
Function :: atom(),
Args :: [term()],
Options :: [spawn_opt_option()].
-spawn_opt(M, F, A, Opts) ->
- case catch erlang:spawn_opt({M,F,A,Opts}) of
- {'EXIT',{Reason,_}} ->
- erlang:error(Reason, [M,F,A,Opts]);
- Res ->
- Res
- end.
+spawn_opt(_Module, _Function, _Args, _Options) ->
+ erlang:nif_error(undefined).
+
-spec spawn_opt(Node, Module, Function, Args, Options) ->
pid() | {pid(), reference()} when
@@ -2986,47 +3058,76 @@ spawn_opt(M, F, A, Opts) ->
Module :: module(),
Function :: atom(),
Args :: [term()],
- Options :: [spawn_opt_option()].
+ Options :: [monitor | link | OtherOption],
+ OtherOption :: term().
+
spawn_opt(N, M, F, A, O) when N =:= erlang:node(),
erlang:is_atom(M), erlang:is_atom(F),
erlang:is_list(A), erlang:is_list(O) ->
- spawn_opt(M, F, A, O);
+ erlang:spawn_opt(M, F, A, O);
spawn_opt(N, M, F, A, O) when erlang:is_atom(N),
erlang:is_atom(M),
erlang:is_atom(F) ->
- case {is_well_formed_list(A), is_well_formed_list(O)} of
- {true, true} ->
- ok;
- _ ->
- erlang:error(badarg, [N, M, F, A, O])
- end,
- case lists:member(monitor, O) of
- false -> ok;
- true -> erlang:error(badarg, [N, M, F, A, O])
- end,
- {L,NO} = lists:foldl(fun (link, {_, NewOpts}) ->
- {link, NewOpts};
- (Opt, {LO, NewOpts}) ->
- {LO, [Opt|NewOpts]}
- end,
- {no_link,[]},
- O),
- case catch gen_server:call({net_kernel,N},
- {spawn_opt,M,F,A,NO,L,erlang:group_leader()},
- infinity) of
- Pid when erlang:is_pid(Pid) ->
- Pid;
- Error ->
- case remote_spawn_error(Error, {L, N, M, F, A, NO}) of
- {fault, Fault} ->
- erlang:error(Fault, [N, M, F, A, O]);
- Pid ->
- Pid
- end
+ {Ref, MonOpt} = case erts_internal:dist_spawn_request(N, {M, F, A}, O, spawn_opt) of
+ {R, MO} when erlang:is_reference(R) -> {R, MO};
+ badarg -> erlang:error(badarg, [N, M, F, A, O])
+ end,
+ receive
+ {spawn_reply, Ref, ok, Pid} when erlang:is_pid(Pid) ->
+ case MonOpt of
+ true -> {Pid, Ref};
+ false -> Pid
+ end;
+ {spawn_reply, Ref, error, badopt} ->
+ erlang:error(badarg, [N, M, F, A, O]);
+ {spawn_reply, Ref, error, noconnection} ->
+ try
+ erlang:spawn_opt(erts_internal,crasher,
+ [N,M,F,A,O,noconnection], O)
+ catch
+ _:Err1 ->
+ erlang:error(Err1, [N, M, F, A, O])
+ end;
+ {spawn_reply, Ref, error, notsup} ->
+ case old_remote_spawn_opt(N, M, F, A, O) of
+ Pid when erlang:is_pid(Pid) ->
+ Pid;
+ Err2 ->
+ erlang:error(Err2, [N, M, F, A, O])
+ end;
+ {spawn_reply, Ref, error, Err3} ->
+ erlang:error(Err3, [N, M, F, A, O])
end;
spawn_opt(N,M,F,A,O) ->
erlang:error(badarg, [N,M,F,A,O]).
+old_remote_spawn_opt(N, M, F, A, O) ->
+ case lists:member(monitor, O) of
+ true ->
+ badarg;
+ _ ->
+ {L,NO} = lists:foldl(fun (link, {_, NewOpts}) ->
+ {link, NewOpts};
+ (Opt, {LO, NewOpts}) ->
+ {LO, [Opt|NewOpts]}
+ end,
+ {no_link,[]},
+ O),
+ case catch gen_server:call({net_kernel,N},
+ {spawn_opt,M,F,A,NO,L,erlang:group_leader()},
+ infinity) of
+ Pid when erlang:is_pid(Pid) ->
+ Pid;
+ Error ->
+ case remote_spawn_error(Error, {L, N, M, F, A, NO}) of
+ {fault, Fault} ->
+ Fault;
+ Pid ->
+ Pid
+ end
+ end
+ end.
+
remote_spawn_error({'EXIT', {{nodedown,N}, _}}, {L, N, M, F, A, O}) ->
{Opts, LL} = case L =:= link of
true ->
@@ -3034,7 +3135,7 @@ remote_spawn_error({'EXIT', {{nodedown,N}, _}}, {L, N, M, F, A, O}) ->
false ->
{O, []}
end,
- spawn_opt(erlang,crasher,[N,M,F,A,Opts,noconnection], LL);
+ erlang:spawn_opt(erts_internal,crasher,[N,M,F,A,Opts,noconnection], LL);
remote_spawn_error({'EXIT', {Reason, _}}, _) ->
{fault, Reason};
remote_spawn_error({'EXIT', Reason}, _) ->
@@ -3042,21 +3143,177 @@ remote_spawn_error({'EXIT', Reason}, _) ->
remote_spawn_error(Other, _) ->
{fault, Other}.
-is_well_formed_list([]) ->
- true;
-is_well_formed_list([_|Rest]) ->
- is_well_formed_list(Rest);
-is_well_formed_list(_) ->
- false.
-
-crasher(Node,Mod,Fun,Args,[],Reason) ->
- error_logger:warning_msg("** Can not start ~w:~w,~w on ~w **~n",
- [Mod,Fun,Args,Node]),
- erlang:exit(Reason);
-crasher(Node,Mod,Fun,Args,Opts,Reason) ->
- error_logger:warning_msg("** Can not start ~w:~w,~w (~w) on ~w **~n",
- [Mod,Fun,Args,Opts,Node]),
- erlang:exit(Reason).
+%%
+%% spawn_request/1
+%%
+
+-spec spawn_request(Fun) -> ReqId when
+ Fun :: function(),
+ ReqId :: reference().
+
+spawn_request(F) when erlang:is_function(F, 0) ->
+ try
+ erlang:spawn_request(erlang, apply, [F, []], [])
+ catch
+ error:Err ->
+ erlang:error(Err, [F])
+ end;
+spawn_request(F) ->
+ erlang:error(badarg, [F]).
+
+%%
+%% spawn_request/2
+%%
+
+-spec spawn_request(Fun, Options) -> ReqId when
+ Fun :: function(),
+ Option :: {reply_tag, ReplyTag}
+ | {reply, Reply}
+ | spawn_opt_option(),
+ ReplyTag :: term(),
+ Reply :: yes | no | error_only | success_only,
+ Options :: [Option],
+ ReqId :: reference();
+ (Node, Fun) -> ReqId when
+ Node :: node(),
+ Fun :: function(),
+ ReqId :: reference().
+
+spawn_request(F, O) when erlang:is_function(F, 0) ->
+ try
+ erlang:spawn_request(erlang, apply, [F, []], O)
+ catch
+ error:Err ->
+ erlang:error(Err, [F, O])
+ end;
+spawn_request(N, F) when erlang:is_function(F, 0) ->
+ try
+ erlang:spawn_request(N, erlang, apply, [F, []], [])
+ catch
+ error:Err ->
+ erlang:error(Err, [N, F])
+ end;
+spawn_request(A1, A2) ->
+ erlang:error(badarg, [A1, A2]).
+
+%%
+%% spawn_request/3
+%%
+
+-spec spawn_request(Node, Fun, Options) -> ReqId when
+ Node :: node(),
+ Fun :: function(),
+ Options :: [Option],
+ Option :: monitor
+ | link
+ | {reply_tag, ReplyTag}
+ | {reply, Reply}
+ | OtherOption,
+ ReplyTag :: term(),
+ Reply :: yes | no | error_only | success_only,
+ OtherOption :: term(),
+ ReqId :: reference();
+ (Module, Function, Args) ->
+ ReqId when
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ ReqId :: reference().
+
+spawn_request(N, F, O) when erlang:is_function(F, 0) ->
+ try
+ erlang:spawn_request(N, erlang, apply, [F, []], O)
+ catch
+ error:Err ->
+ erlang:error(Err, [N, F, O])
+ end;
+spawn_request(M, F, A) ->
+ try
+ erlang:spawn_request(M, F, A, [])
+ catch
+ error:Err ->
+ erlang:error(Err, [M, F, A])
+ end.
+
+%%
+%% spawn_request/4
+%%
+
+-spec spawn_request(Node, Module, Function, Args) ->
+ ReqId when
+ Node :: node(),
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ ReqId :: reference();
+ (Module, Function, Args, Options) ->
+ ReqId when
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Option :: {reply_tag, ReplyTag}
+ | {reply, Reply}
+ | spawn_opt_option(),
+ ReplyTag :: term(),
+ Reply :: yes | no | error_only | success_only,
+ Options :: [Option],
+ ReqId :: reference().
+
+spawn_request(N, M, F, A) when erlang:is_atom(F) ->
+ try
+ erlang:spawn_request(N, M, F, A, [])
+ catch
+ error:Err ->
+ erlang:error(Err, [N, M, F, A])
+ end;
+spawn_request(M, F, A, O) ->
+ case erts_internal:spawn_request(M, F, A, O) of
+ Ref when erlang:is_reference(Ref) ->
+ Ref;
+ badarg ->
+ erlang:error(badarg, [M, F, A, O])
+ end.
+
+%%
+%% spawn_request/5
+%%
+
+-spec spawn_request(Node, Module, Function, Args, Options) ->
+ ReqId when
+ Node :: node(),
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Options :: [Option],
+ Option :: monitor
+ | link
+ | {reply_tag, ReplyTag}
+ | {reply, Reply}
+ | OtherOption,
+ ReplyTag :: term(),
+ Reply :: yes | no | error_only | success_only,
+ OtherOption :: term(),
+ ReqId :: reference().
+
+spawn_request(N, M, F, A, O) when N =:= erlang:node() ->
+ try
+ erlang:spawn_request(M, F, A, O)
+ catch
+ error:Err ->
+ erlang:error(Err, [N, M, F, A, O])
+ end;
+spawn_request(N, M, F, A, O) ->
+ case erts_internal:dist_spawn_request(N, {M, F, A}, O, spawn_request) of
+ Ref when erlang:is_reference(Ref) ->
+ Ref;
+ badarg ->
+ erlang:error(badarg, [N, M, F, A, O])
+ end.
+
+-spec spawn_request_abandon(ReqId :: reference()) -> boolean().
+
+spawn_request_abandon(_ReqId) ->
+ erlang:nif_error(undefined).
-spec erlang:yield() -> 'true'.
yield() ->
@@ -3337,7 +3594,7 @@ dist_ctrl_put_data(_DHandle, _Data) ->
-spec erlang:dist_ctrl_get_data(DHandle) -> {Size, Data} | Data | 'none' when
Size :: non_neg_integer(),
DHandle :: dist_handle(),
- Data :: iodata().
+ Data :: iovec().
dist_ctrl_get_data(_DHandle) ->
erlang:nif_error(undefined).
@@ -3671,15 +3928,6 @@ get_memval(code, #memory{code = V}) -> V;
get_memval(ets, #memory{ets = V}) -> V;
get_memval(_, #memory{}) -> erlang:error(badarg).
-get_blocks_size([{blocks_size, Sz, _, _} | Rest], Acc) ->
- get_blocks_size(Rest, Acc+Sz);
-get_blocks_size([{blocks_size, Sz} | Rest], Acc) ->
- get_blocks_size(Rest, Acc+Sz);
-get_blocks_size([_ | Rest], Acc) ->
- get_blocks_size(Rest, Acc);
-get_blocks_size([], Acc) ->
- Acc.
-
get_fix_proc([{ProcType, A1, U1}| Rest], {A0, U0}) when ProcType == proc;
ProcType == monitor;
ProcType == link;
@@ -3722,14 +3970,15 @@ au_mem_acc(#memory{ total = Tot,
processes = Proc,
processes_used = ProcU } = Mem,
eheap_alloc, Data) ->
- Sz = get_blocks_size(Data, 0),
+ Sz = acc_blocks_size(Data, 0),
Mem#memory{ total = Tot+Sz,
processes = Proc+Sz,
processes_used = ProcU+Sz};
au_mem_acc(#memory{ total = Tot,
system = Sys,
- ets = Ets } = Mem, ets_alloc, Data) ->
- Sz = get_blocks_size(Data, 0),
+ ets = Ets } = Mem,
+ ets_alloc, Data) ->
+ Sz = acc_blocks_size(Data, 0),
Mem#memory{ total = Tot+Sz,
system = Sys+Sz,
ets = Ets+Sz };
@@ -3737,31 +3986,45 @@ au_mem_acc(#memory{total = Tot,
system = Sys,
binary = Bin } = Mem,
binary_alloc, Data) ->
- Sz = get_blocks_size(Data, 0),
+ Sz = acc_blocks_size(Data, 0),
Mem#memory{ total = Tot+Sz,
system = Sys+Sz,
binary = Bin+Sz};
au_mem_acc(#memory{ total = Tot,
system = Sys } = Mem,
_Type, Data) ->
- Sz = get_blocks_size(Data, 0),
+ Sz = acc_blocks_size(Data, 0),
Mem#memory{ total = Tot+Sz,
system = Sys+Sz }.
-au_mem_foreign(Mem, [{Type, SizeList} | Rest]) ->
- au_mem_foreign(au_mem_acc(Mem, Type, SizeList), Rest);
-au_mem_foreign(Mem, []) ->
+acc_blocks_size([{size, Sz, _, _} | Rest], Acc) ->
+ acc_blocks_size(Rest, Acc+Sz);
+acc_blocks_size([{size, Sz} | Rest], Acc) ->
+ acc_blocks_size(Rest, Acc+Sz);
+acc_blocks_size([_ | Rest], Acc) ->
+ acc_blocks_size(Rest, Acc);
+acc_blocks_size([], Acc) ->
+ Acc.
+
+au_mem_blocks([{blocks, L} | Rest], Mem0) ->
+ Mem = au_mem_blocks_1(L, Mem0),
+ au_mem_blocks(Rest, Mem);
+au_mem_blocks([_ | Rest], Mem) ->
+ au_mem_blocks(Rest, Mem);
+au_mem_blocks([], Mem) ->
Mem.
-au_mem_current(Mem0, Type, [{mbcs_pool, MBCS} | Rest]) ->
- [Foreign] = [Foreign || {foreign_blocks, Foreign} <- MBCS],
- SizeList = MBCS -- [Foreign],
- Mem = au_mem_foreign(Mem0, Foreign),
- au_mem_current(au_mem_acc(Mem, Type, SizeList), Type, Rest);
-au_mem_current(Mem, Type, [{mbcs, SizeList} | Rest]) ->
- au_mem_current(au_mem_acc(Mem, Type, SizeList), Type, Rest);
-au_mem_current(Mem, Type, [{sbcs, SizeList} | Rest]) ->
- au_mem_current(au_mem_acc(Mem, Type, SizeList), Type, Rest);
+au_mem_blocks_1([{Type, SizeList} | Rest], Mem) ->
+ au_mem_blocks_1(Rest, au_mem_acc(Mem, Type, SizeList));
+au_mem_blocks_1([], Mem) ->
+ Mem.
+
+au_mem_current(Mem, Type, [{mbcs_pool, Stats} | Rest]) ->
+ au_mem_current(au_mem_blocks(Stats, Mem), Type, Rest);
+au_mem_current(Mem, Type, [{mbcs, Stats} | Rest]) ->
+ au_mem_current(au_mem_blocks(Stats, Mem), Type, Rest);
+au_mem_current(Mem, Type, [{sbcs, Stats} | Rest]) ->
+ au_mem_current(au_mem_blocks(Stats, Mem), Type, Rest);
au_mem_current(Mem, Type, [_ | Rest]) ->
au_mem_current(Mem, Type, Rest);
au_mem_current(Mem, _Type, []) ->
@@ -3917,3 +4180,98 @@ gc_info(Ref, N, {OrigColls,OrigRecl}) ->
{Ref, {_,Colls, Recl}} ->
gc_info(Ref, N-1, {Colls+OrigColls,Recl+OrigRecl})
end.
+
+%% Operators
+
+-spec erlang:'=='(term(), term()) -> boolean().
+'=='(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'=:='(term(), term()) -> boolean().
+'=:='(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'/='(term(), term()) -> boolean().
+'/='(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'=/='(term(), term()) -> boolean().
+'=/='(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'=<'(term(), term()) -> boolean().
+'=<'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'>='(term(), term()) -> boolean().
+'>='(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'<'(term(), term()) -> boolean().
+'<'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'>'(term(), term()) -> boolean().
+'>'(_A, _B) ->
+ erlang:nif_error(undefined).
+
+-spec erlang:'-'(number()) -> number().
+'-'(_A) ->
+ erlang:nif_error(undefined).
+-spec erlang:'+'(number()) -> number().
+'+'(_A) ->
+ erlang:nif_error(undefined).
+-spec erlang:'-'(number(), number()) -> number().
+'-'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'+'(number(), number()) -> number().
+'+'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'/'(number(), number()) -> float().
+'/'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'*'(number(), number()) -> number().
+'*'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'div'(integer(), integer()) -> integer().
+'div'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'rem'(integer(), integer()) -> integer().
+'rem'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'bsl'(integer(), integer()) -> integer().
+'bsl'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'bsr'(integer(), integer()) -> integer().
+'bsr'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'bor'(integer(), integer()) -> integer().
+'bor'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'band'(integer(), integer()) -> integer().
+'band'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'bxor'(integer(), integer()) -> integer().
+'bxor'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'bnot'(integer()) -> integer().
+'bnot'(_A) ->
+ erlang:nif_error(undefined).
+
+-spec erlang:'--'(list(), list()) -> list().
+'--'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'++'(list(), term()) -> term().
+'++'(_A, _B) ->
+ erlang:nif_error(undefined).
+
+-spec erlang:'and'(boolean(), boolean()) -> boolean().
+'and'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'or'(boolean(), boolean()) -> boolean().
+'or'(_A, _B) ->
+ erlang:nif_error(undefined).
+
+-spec erlang:'xor'(boolean(), boolean()) -> boolean().
+'xor'(_A, _B) ->
+ erlang:nif_error(undefined).
+-spec erlang:'not'(boolean()) -> boolean().
+'not'(_A) ->
+ erlang:nif_error(undefined).
+
+-spec erlang:'!'(dst(), term()) -> term().
+'!'(_Dst, _Msg) ->
+ erlang:nif_error(undefined).
diff --git a/erts/preloaded/src/erts.app.src b/erts/preloaded/src/erts.app.src
index d3dbe0f2d1..3a98f6f0f1 100644
--- a/erts/preloaded/src/erts.app.src
+++ b/erts/preloaded/src/erts.app.src
@@ -36,13 +36,13 @@
atomics,
counters,
persistent_term,
- zlib,
%ESOCK_MODS%
+ zlib
]},
{registered, []},
{applications, []},
{env, []},
- {runtime_dependencies, ["stdlib-3.5", "kernel-6.5.1", "sasl-3.3"]}
+ {runtime_dependencies, ["stdlib-3.13", "kernel-7.0", "sasl-3.3"]}
]}.
%% vim: ft=erlang
diff --git a/erts/preloaded/src/erts_internal.erl b/erts/preloaded/src/erts_internal.erl
index 48443ebdf7..aa3b6b753c 100644
--- a/erts/preloaded/src/erts_internal.erl
+++ b/erts/preloaded/src/erts_internal.erl
@@ -67,6 +67,7 @@
-export([dist_ctrl_put_data/2]).
-export([get_dflags/0]).
+-export([get_creation/0]).
-export([new_connection/1]).
-export([abort_pending_connection/2]).
@@ -89,7 +90,7 @@
-export([process_flag/3]).
--export([create_dist_channel/4]).
+-export([create_dist_channel/3]).
-export([erase_persistent_terms/0]).
@@ -105,6 +106,10 @@
-export([get_internal_state_blocked/1]).
+-export([spawn_request/4, spawn_init/1, dist_spawn_request/4, dist_spawn_init/1]).
+
+-export([crasher/6]).
+
%%
%% Await result of send to port
%%
@@ -564,6 +569,10 @@ dist_ctrl_put_data(DHandle, IoList) ->
get_dflags() ->
erlang:nif_error(undefined).
+-spec erts_internal:get_creation() -> pos_integer().
+get_creation() ->
+ erlang:nif_error(undefined).
+
-spec erts_internal:new_connection(Node) -> ConnId when
Node :: atom(),
ConnId :: {integer(), erlang:dist_handle()}.
@@ -703,17 +712,18 @@ process_display(_Pid, _Type) ->
process_flag(_Pid, _Flag, _Value) ->
erlang:nif_error(undefined).
--spec create_dist_channel(Node, DistCtrlr, Flags, Ver) -> Result when
+-spec create_dist_channel(Node, DistCtrlr, {Flags, Ver, Cr}) -> Result when
Node :: atom(),
DistCtrlr :: port() | pid(),
Flags :: integer(),
Ver :: integer(),
+ Cr :: pos_integer(),
Result :: {'ok', erlang:dist_handle()}
| {'message', reference()}
| 'badarg'
| 'system_limit'.
-create_dist_channel(_Node, _DistCtrlr, _Flags, _Ver) ->
+create_dist_channel(_Node, _DistCtrlr, _Tpl) ->
erlang:nif_error(undefined).
-spec erase_persistent_terms() -> 'ok'.
@@ -830,3 +840,85 @@ get_internal_state_blocked(Arg) ->
erlang:system_flag(multi_scheduling, unblock)
end,
Result.
+
+-spec spawn_request(Module, Function, Args, Opts) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Opts :: [term()],
+ Res :: reference() | 'badarg'.
+
+spawn_request(_Module, _Function, _Args, _Opts) ->
+ erlang:nif_error(undef).
+
+-spec spawn_init({Module, Function, Args}) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Res :: term().
+
+spawn_init({M, F, A}) ->
+ apply(M, F, A).
+
+-spec dist_spawn_request(Node, MFA, Opts, spawn_request) -> Res when
+ Node :: node(),
+ MFA :: {Module, Function, Args},
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Opts :: [term()],
+ Res :: reference() | 'badarg';
+ (Node, MFA, Opts, spawn_opt) -> Res when
+ Node :: node(),
+ MFA :: {Module, Function, Args},
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Opts :: [term()],
+ Res :: {reference(), boolean()} | 'badarg'.
+
+dist_spawn_request(_Node, _MFA, _Opts, _Type) ->
+ erlang:nif_error(undef).
+
+-spec dist_spawn_init(MFA) -> Res when
+ MFA :: {Module, Function, non_neg_integer()},
+ Module :: module(),
+ Function :: atom(),
+ Res :: term().
+
+dist_spawn_init(MFA) ->
+ %%
+ %% The argument list is passed as a message
+ %% to the newly created process. This since
+ %% it might be large and require a substantial
+ %% amount of work to decode. This way we put
+ %% this work on the newly created process
+ %% (which can execute in parallel with all
+ %% other tasks) instead of on the distribution
+ %% channel code which is a bottleneck in the
+ %% system.
+ %%
+ %% erl_create_process() ensures that the
+ %% argument list to use in apply is
+ %% guaranteed to be the first message in the
+ %% message queue.
+ %%
+ {M, F, _NoA} = MFA,
+ receive
+ A ->
+ erlang:apply(M, F, A)
+ end.
+
+%%
+%% Failed distributed spawn(), spawn_link(), spawn_monitor(), spawn_opt()
+%% spawns a dummy process executing the crasher/6 function...
+%%
+
+crasher(Node,Mod,Fun,Args,[],Reason) ->
+ error_logger:warning_msg("** Can not start ~w:~w,~w on ~w **~n",
+ [Mod,Fun,Args,Node]),
+ erlang:exit(Reason);
+crasher(Node,Mod,Fun,Args,Opts,Reason) ->
+ error_logger:warning_msg("** Can not start ~w:~w,~w (~w) on ~w **~n",
+ [Mod,Fun,Args,Opts,Node]),
+ erlang:exit(Reason).
diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl
index b36b4b5599..e39c13b63d 100644
--- a/erts/preloaded/src/init.erl
+++ b/erts/preloaded/src/init.erl
@@ -48,7 +48,7 @@
-module(init).
--export([restart/0,reboot/0,stop/0,stop/1,
+-export([restart/1,restart/0,reboot/0,stop/0,stop/1,
get_status/0,boot/1,get_arguments/0,get_plain_arguments/0,
get_argument/1,script_id/0]).
@@ -63,6 +63,7 @@
-include_lib("kernel/include/file.hrl").
-type internal_status() :: 'starting' | 'started' | 'stopping'.
+-type mode() :: 'embedded' | 'interactive'.
-record(state, {flags = [],
args = [],
@@ -164,7 +165,15 @@ request(Req) ->
end.
-spec restart() -> 'ok'.
-restart() -> init ! {stop,restart}, ok.
+restart() -> restart([]).
+
+-spec restart([{mode, mode()}]) -> 'ok'.
+restart([]) ->
+ init ! {stop,restart}, ok;
+restart([{mode, Mode}]) when Mode =:= embedded; Mode =:= interactive ->
+ init ! {stop,{restart,Mode}}, ok;
+restart(Opts) when is_list(Opts) ->
+ erlang:error(badarg, [Opts]).
-spec reboot() -> 'ok'.
reboot() -> init ! {stop,reboot}, ok.
@@ -546,11 +555,11 @@ stop(Reason,State) ->
clear_system(BootPid,State1),
do_stop(Reason,State1).
-do_stop(restart,#state{start = Start, flags = Flags, args = Args}) ->
- %% Make sure we don't have any outstanding messages before doing the restart.
- flush(),
- erts_internal:erase_persistent_terms(),
- boot(Start,Flags,Args);
+do_stop({restart,Mode},#state{start=Start, flags=Flags0, args=Args}) ->
+ Flags = update_flag(mode, Flags0, atom_to_binary(Mode)),
+ do_restart(Start,Flags,Args);
+do_stop(restart,#state{start=Start, flags=Flags, args=Args}) ->
+ do_restart(Start,Flags,Args);
do_stop(reboot,_) ->
halt();
do_stop(stop,State) ->
@@ -560,6 +569,11 @@ do_stop({stop,Status},State) ->
stop_heart(State),
halt(Status).
+do_restart(Start,Flags,Args) ->
+ flush(),
+ erts_internal:erase_persistent_terms(),
+ boot(Start,Flags,Args).
+
clear_system(BootPid,State) ->
Heart = get_heart(State#state.kernel),
Logger = get_logger(State#state.kernel),
@@ -798,7 +812,7 @@ do_boot(Init,Flags,Start) ->
start_prim_loader(Init, bs2ss(Path), PathFls),
BootFile = bootfile(Flags,Root),
BootList = get_boot(BootFile,Root),
- LoadMode = b2a(get_flag(mode, Flags, false)),
+ LoadMode = b2a(get_flag(mode, Flags, interactive)),
Deb = b2a(get_flag(init_debug, Flags, false)),
catch ?ON_LOAD_HANDLER ! {init_debug_flag,Deb},
BootVars = get_boot_vars(Root, Flags),
@@ -1233,6 +1247,13 @@ get_args([B|Bs], As) ->
end;
get_args([], As) -> {reverse(As),[]}.
+update_flag(Flag, [{Flag, _} | Flags], Value) ->
+ [{Flag, [Value]} | Flags];
+update_flag(Flag, [Head | Flags], Value) ->
+ [Head | update_flag(Flag, Flags, Value)];
+update_flag(Flag, [], Value) ->
+ [{Flag, [Value]}].
+
%%
%% Internal get_flag function, with default value.
%% Return: true if flag given without args
diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl
index 32b2ab3fd1..046d6ffbad 100644
--- a/erts/preloaded/src/prim_file.erl
+++ b/erts/preloaded/src/prim_file.erl
@@ -34,6 +34,7 @@
-export([read_link/1, read_link_all/1,
read_link_info/1, read_link_info/2,
read_file_info/1, read_file_info/2,
+ read_handle_info/1, read_handle_info/2,
write_file_info/2, write_file_info/3]).
-export([list_dir/1, list_dir_all/1]).
@@ -497,6 +498,8 @@ get_handle_nif(_FileRef) ->
erlang:nif_error(undef).
delayed_close_nif(_FileRef) ->
erlang:nif_error(undef).
+read_handle_info_nif(_FileRef) ->
+ erlang:nif_error(undef).
%%
%% Quality-of-life helpers
@@ -598,20 +601,37 @@ read_link_info(Name, Opts) ->
read_info_1(Name, FollowLinks, TimeType) ->
try
case read_info_nif(encode_path(Name), FollowLinks) of
- {error, Reason} ->
- {error, Reason};
- FileInfo ->
- CTime = from_posix_seconds(FileInfo#file_info.ctime, TimeType),
- MTime = from_posix_seconds(FileInfo#file_info.mtime, TimeType),
- ATime = from_posix_seconds(FileInfo#file_info.atime, TimeType),
- {ok, FileInfo#file_info{ ctime = CTime,
- mtime = MTime,
- atime = ATime }}
+ {error, Reason} -> {error, Reason};
+ FileInfo -> {ok, adjust_times(FileInfo, TimeType)}
+ end
+ catch
+ error:_ -> {error, badarg}
+ end.
+
+read_handle_info(Fd) ->
+ read_handle_info_1(Fd, local).
+read_handle_info(Fd, Opts) ->
+ read_handle_info_1(Fd, proplist_get_value(time, Opts, local)).
+
+read_handle_info_1(Fd, TimeType) ->
+ try
+ #{ handle := FRef } = get_fd_data(Fd),
+ case read_handle_info_nif(FRef) of
+ {error, Reason} -> {error, Reason};
+ FileInfo -> {ok, adjust_times(FileInfo, TimeType)}
end
catch
error:_ -> {error, badarg}
end.
+adjust_times(FileInfo, TimeType) ->
+ CTime = from_posix_seconds(FileInfo#file_info.ctime, TimeType),
+ MTime = from_posix_seconds(FileInfo#file_info.mtime, TimeType),
+ ATime = from_posix_seconds(FileInfo#file_info.atime, TimeType),
+ FileInfo#file_info{ ctime = CTime,
+ mtime = MTime,
+ atime = ATime }.
+
write_file_info(Filename, Info) ->
write_file_info_1(Filename, Info, local).
write_file_info(Filename, Info, Opts) ->
diff --git a/erts/preloaded/src/prim_net.erl b/erts/preloaded/src/prim_net.erl
index fcd528bdaa..e82ae33b8a 100644
--- a/erts/preloaded/src/prim_net.erl
+++ b/erts/preloaded/src/prim_net.erl
@@ -157,15 +157,14 @@ gethostname() ->
Info :: name_info(),
Reason :: term().
-getnameinfo(SockAddr, [] = _Flags) ->
- getnameinfo(SockAddr, undefined);
-getnameinfo(#{family := Fam, addr := _Addr} = SockAddr, Flags)
- when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso
- (is_list(Flags) orelse (Flags =:= undefined)) ->
- nif_getnameinfo(socket:ensure_sockaddr(SockAddr), Flags);
-getnameinfo(#{family := Fam, path := _Path} = SockAddr, Flags)
- when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) ->
- nif_getnameinfo(SockAddr, Flags).
+getnameinfo(SockAddr, Flags) ->
+ try
+ ESockAddr = prim_socket:encode_sockaddr(SockAddr),
+ nif_getnameinfo(ESockAddr, Flags)
+ catch
+ throw : ERROR ->
+ ERROR
+ end.
%% ===========================================================================
diff --git a/erts/preloaded/src/prim_socket.erl b/erts/preloaded/src/prim_socket.erl
new file mode 100644
index 0000000000..efa99cc989
--- /dev/null
+++ b/erts/preloaded/src/prim_socket.erl
@@ -0,0 +1,1378 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(prim_socket).
+
+-compile(no_native).
+
+-export([on_load/0, on_load/1]).
+
+-export(
+ [encode_path/1, encode_sockaddr/1,
+ info/0, info/1,
+ debug/1, socket_debug/1, use_registry/1,
+ supports/0, supports/1, supports/2,
+ open/2, open/4,
+ bind/2, bind/3,
+ connect/1, connect/2,
+ listen/2,
+ accept/2,
+ send/4, sendto/5, sendmsg/4,
+ recv/4, recvfrom/4, recvmsg/5,
+ close/1, finalize_close/1,
+ shutdown/2, setopt/4, getopt/3,
+ sockname/1, peername/1,
+ cancel/3
+ ]).
+
+%% Also in socket
+-define(REGISTRY, socket_registry).
+
+
+%% ===========================================================================
+%%
+%% Defaults
+%%
+
+-define(ESOCK_SEND_FLAGS_DEFAULT, []).
+-define(ESOCK_SEND_TIMEOUT_DEFAULT, infinity).
+-define(ESOCK_SENDTO_FLAGS_DEFAULT, []).
+-define(ESOCK_SENDTO_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
+-define(ESOCK_SENDMSG_FLAGS_DEFAULT, []).
+-define(ESOCK_SENDMSG_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
+
+-define(ESOCK_RECV_FLAGS_DEFAULT, []).
+-define(ESOCK_RECV_TIMEOUT_DEFAULT, infinity).
+
+-define(ESOCK_SOCKADDR_IN4_DEFAULTS,
+ (#{port => 0, addr => any})).
+-define(ESOCK_SOCKADDR_IN6_DEFAULTS,
+ (#{port => 0, addr => any,
+ flowinfo => 0, scope_id => 0})).
+
+
+%% ===========================================================================
+%%
+%% Constants common to prim_socket_nif.c - has to be "identical"
+%%
+
+-define(ESOCK_SEND_FLAG_CONFIRM, (1 bsl 0)).
+-define(ESOCK_SEND_FLAG_DONTROUTE, (1 bsl 1)).
+-define(ESOCK_SEND_FLAG_EOR, (1 bsl 2)).
+-define(ESOCK_SEND_FLAG_MORE, (1 bsl 3)).
+-define(ESOCK_SEND_FLAG_NOSIGNAL, (1 bsl 4)).
+-define(ESOCK_SEND_FLAG_OOB, (1 bsl 5)).
+
+-define(ESOCK_RECV_FLAG_CMSG_CLOEXEC, (1 bsl 0)).
+-define(ESOCK_RECV_FLAG_ERRQUEUE, (1 bsl 1)).
+-define(ESOCK_RECV_FLAG_OOB, (1 bsl 2)).
+-define(ESOCK_RECV_FLAG_PEEK, (1 bsl 3)).
+-define(ESOCK_RECV_FLAG_TRUNC, (1 bsl 4)).
+
+
+%% shutdown/2
+-define(ESOCK_SHUTDOWN_HOW_READ, 0).
+-define(ESOCK_SHUTDOWN_HOW_WRITE, 1).
+-define(ESOCK_SHUTDOWN_HOW_READ_WRITE, 2).
+
+
+
+%% ----------------------------------
+%% Address domain / protcol family
+
+-define(ESOCK_DOMAIN_LOCAL, 1).
+-define(ESOCK_DOMAIN_UNIX, ?ESOCK_DOMAIN_LOCAL).
+-define(ESOCK_DOMAIN_INET, 2).
+-define(ESOCK_DOMAIN_INET6, 3).
+
+%% ----------------------------------
+%% Protocol type
+
+-define(ESOCK_TYPE_STREAM, 101).
+-define(ESOCK_TYPE_DGRAM, 102).
+-define(ESOCK_TYPE_RAW, 103).
+%% -define(ESOCK_TYPE_RDM, 104).
+-define(ESOCK_TYPE_SEQPACKET, 105).
+
+%% ----------------------------------
+%% Protocol
+
+-define(ESOCK_PROTOCOL_DEFAULT, 200).
+-define(ESOCK_PROTOCOL_IP, 201).
+-define(ESOCK_PROTOCOL_TCP, 202).
+-define(ESOCK_PROTOCOL_UDP, 203).
+-define(ESOCK_PROTOCOL_SCTP, 204).
+-define(ESOCK_PROTOCOL_ICMP, 205).
+-define(ESOCK_PROTOCOL_IGMP, 206).
+
+%% ----------------------------------
+%% Option level
+
+-define(ESOCK_OPT_LEVEL_OTP, 301).
+-define(ESOCK_OPT_LEVEL_SOCKET, 302).
+-define(ESOCK_OPT_LEVEL_IP, 303).
+-define(ESOCK_OPT_LEVEL_IPV6, 304).
+-define(ESOCK_OPT_LEVEL_TCP, 305).
+-define(ESOCK_OPT_LEVEL_UDP, 306).
+-define(ESOCK_OPT_LEVEL_SCTP, 307).
+
+%% ----------------------------------
+%% *** OTP (socket) options
+
+-define(ESOCK_OPT_OTP_DEBUG, 1001).
+-define(ESOCK_OPT_OTP_IOW, 1002).
+-define(ESOCK_OPT_OTP_CTRL_PROC, 1003).
+-define(ESOCK_OPT_OTP_RCVBUF, 1004).
+%%-define(ESOCK_OPT_OTP_SNDBUF, 1005).
+-define(ESOCK_OPT_OTP_RCVCTRLBUF, 1006).
+-define(ESOCK_OPT_OTP_SNDCTRLBUF, 1007).
+-define(ESOCK_OPT_OTP_FD, 1008).
+-define(ESOCK_OPT_OTP_META, 1009).
+-define(ESOCK_OPT_OTP_USE_REGISTRY, 1010).
+%%
+-define(ESOCK_OPT_OTP_DOMAIN, 1999). % INTERNAL
+%%-define(ESOCK_OPT_OTP_TYPE, 1998). % INTERNAL
+%%-define(ESOCK_OPT_OTP_PROTOCOL, 1997). % INTERNAL
+%%-define(ESOCK_OPT_OTP_DTP, 1996). % INTERNAL
+
+%% ----------------------------------
+%% *** SOCKET (socket) options
+
+-define(ESOCK_OPT_SOCK_ACCEPTCONN, 2001).
+-define(ESOCK_OPT_SOCK_ACCEPTFILTER, 2002). % FreeBSD
+-define(ESOCK_OPT_SOCK_BINDTODEVICE, 2003).
+-define(ESOCK_OPT_SOCK_BROADCAST, 2004).
+-define(ESOCK_OPT_SOCK_BUSY_POLL, 2005).
+-define(ESOCK_OPT_SOCK_DEBUG, 2006).
+-define(ESOCK_OPT_SOCK_DOMAIN, 2007).
+-define(ESOCK_OPT_SOCK_DONTROUTE, 2008).
+-define(ESOCK_OPT_SOCK_ERROR, 2009).
+-define(ESOCK_OPT_SOCK_KEEPALIVE, 2010).
+-define(ESOCK_OPT_SOCK_LINGER, 2011).
+-define(ESOCK_OPT_SOCK_MARK, 2012).
+-define(ESOCK_OPT_SOCK_OOBINLINE, 2013).
+-define(ESOCK_OPT_SOCK_PASSCRED, 2014).
+-define(ESOCK_OPT_SOCK_PEEK_OFF, 2015).
+-define(ESOCK_OPT_SOCK_PEERCRED, 2016).
+-define(ESOCK_OPT_SOCK_PRIORITY, 2017).
+-define(ESOCK_OPT_SOCK_PROTOCOL, 2018).
+-define(ESOCK_OPT_SOCK_RCVBUF, 2019).
+-define(ESOCK_OPT_SOCK_RCVBUFFORCE, 2020).
+-define(ESOCK_OPT_SOCK_RCVLOWAT, 2021).
+-define(ESOCK_OPT_SOCK_RCVTIMEO, 2022).
+-define(ESOCK_OPT_SOCK_REUSEADDR, 2023).
+-define(ESOCK_OPT_SOCK_REUSEPORT, 2024).
+-define(ESOCK_OPT_SOCK_RXQ_OVFL, 2025).
+-define(ESOCK_OPT_SOCK_SETFIB, 2026). % FreeBSD
+-define(ESOCK_OPT_SOCK_SNDBUF, 2027).
+-define(ESOCK_OPT_SOCK_SNDBUFFORCE, 2028).
+-define(ESOCK_OPT_SOCK_SNDLOWAT, 2029).
+-define(ESOCK_OPT_SOCK_SNDTIMEO, 2030).
+-define(ESOCK_OPT_SOCK_TIMESTAMP, 2031).
+-define(ESOCK_OPT_SOCK_TYPE, 2032).
+
+%% ----------------------------------
+%% *** IP (socket) options
+
+-define(ESOCK_OPT_IP_ADD_MEMBERSHIP, 3001).
+-define(ESOCK_OPT_IP_ADD_SOURCE_MEMBERSHIP, 3002).
+-define(ESOCK_OPT_IP_BLOCK_SOURCE, 3003).
+-define(ESOCK_OPT_IP_DONTFRAG, 3004). % FreeBSD
+-define(ESOCK_OPT_IP_DROP_MEMBERSHIP, 3005).
+-define(ESOCK_OPT_IP_DROP_SOURCE_MEMBERSHIP,3006).
+-define(ESOCK_OPT_IP_FREEBIND, 3007).
+-define(ESOCK_OPT_IP_HDRINCL, 3008).
+-define(ESOCK_OPT_IP_MINTTL, 3009).
+-define(ESOCK_OPT_IP_MSFILTER, 3010).
+-define(ESOCK_OPT_IP_MTU, 3011).
+-define(ESOCK_OPT_IP_MTU_DISCOVER, 3012).
+-define(ESOCK_OPT_IP_MULTICAST_ALL, 3013).
+-define(ESOCK_OPT_IP_MULTICAST_IF, 3014).
+-define(ESOCK_OPT_IP_MULTICAST_LOOP, 3015).
+-define(ESOCK_OPT_IP_MULTICAST_TTL, 3016).
+-define(ESOCK_OPT_IP_NODEFRAG, 3017).
+-define(ESOCK_OPT_IP_OPTIONS, 3018). % FreeBSD
+-define(ESOCK_OPT_IP_PKTINFO, 3019).
+-define(ESOCK_OPT_IP_RECVDSTADDR, 3020). % FreeBSD
+-define(ESOCK_OPT_IP_RECVERR, 3021).
+-define(ESOCK_OPT_IP_RECVIF, 3022).
+-define(ESOCK_OPT_IP_RECVOPTS, 3023).
+-define(ESOCK_OPT_IP_RECVORIGDSTADDR, 3024).
+-define(ESOCK_OPT_IP_RECVTOS, 3025).
+-define(ESOCK_OPT_IP_RECVTTL, 3026).
+-define(ESOCK_OPT_IP_RETOPTS, 3027).
+-define(ESOCK_OPT_IP_ROUTER_ALERT, 3028).
+-define(ESOCK_OPT_IP_SENDSRCADDR, 3029). % FreeBSD
+-define(ESOCK_OPT_IP_TOS, 3030).
+-define(ESOCK_OPT_IP_TRANSPARENT, 3031).
+-define(ESOCK_OPT_IP_TTL, 3032).
+-define(ESOCK_OPT_IP_UNBLOCK_SOURCE, 3033).
+
+%% ----------------------------------
+%% *** IPv6 (socket) options
+
+-define(ESOCK_OPT_IPV6_ADDRFORM, 4001).
+-define(ESOCK_OPT_IPV6_ADD_MEMBERSHIP, 4002).
+-define(ESOCK_OPT_IPV6_AUTHHDR, 4003). % Obsolete?
+-define(ESOCK_OPT_IPV6_AUTH_LEVEL, 4004). % FreeBSD
+-define(ESOCK_OPT_IPV6_CHECKSUM, 4005). % FreeBSD
+-define(ESOCK_OPT_IPV6_DROP_MEMBERSHIP, 4006).
+-define(ESOCK_OPT_IPV6_DSTOPTS, 4007).
+-define(ESOCK_OPT_IPV6_ESP_NETWORK_LEVEL, 4008). % FreeBSD
+-define(ESOCK_OPT_IPV6_ESP_TRANS_LEVEL, 4009). % FreeBSD
+-define(ESOCK_OPT_IPV6_FAITH, 4010). % FreeBSD
+-define(ESOCK_OPT_IPV6_FLOWINFO, 4011).
+-define(ESOCK_OPT_IPV6_HOPLIMIT, 4012).
+-define(ESOCK_OPT_IPV6_HOPOPTS, 4013).
+-define(ESOCK_OPT_IPV6_IPCOMP_LEVEL, 4014). % FreeBSD
+-define(ESOCK_OPT_IPV6_JOIN_GROUP, 4015). % FreeBSD
+-define(ESOCK_OPT_IPV6_LEAVE_GROUP, 4016). % FreeBSD
+-define(ESOCK_OPT_IPV6_MTU, 4017).
+-define(ESOCK_OPT_IPV6_MTU_DISCOVER, 4018).
+-define(ESOCK_OPT_IPV6_MULTICAST_HOPS, 4019).
+-define(ESOCK_OPT_IPV6_MULTICAST_IF, 4020).
+-define(ESOCK_OPT_IPV6_MULTICAST_LOOP, 4021).
+-define(ESOCK_OPT_IPV6_PORTRANGE, 4022). % FreeBSD
+-define(ESOCK_OPT_IPV6_PKTOPTIONS, 4023). % FreeBSD
+-define(ESOCK_OPT_IPV6_RECVERR, 4024).
+-define(ESOCK_OPT_IPV6_RECVHOPLIMIT, 4025).
+-define(ESOCK_OPT_IPV6_RECVPKTINFO, 4026). % On FreeBSD: PKTINFO
+-define(ESOCK_OPT_IPV6_RECVTCLASS, 4027).
+-define(ESOCK_OPT_IPV6_ROUTER_ALERT, 4028).
+-define(ESOCK_OPT_IPV6_RTHDR, 4029).
+-define(ESOCK_OPT_IPV6_TCLASS, 4030). % FreeBSD
+-define(ESOCK_OPT_IPV6_UNICAST_HOPS, 4031).
+-define(ESOCK_OPT_IPV6_USE_MIN_MTU, 4032). % FreeBSD
+-define(ESOCK_OPT_IPV6_V6ONLY, 4033).
+
+%% ----------------------------------
+%% *** TCP (socket) options
+
+-define(ESOCK_OPT_TCP_CONGESTION, 5001).
+-define(ESOCK_OPT_TCP_CORK, 5002).
+-define(ESOCK_OPT_TCP_INFO, 5003).
+-define(ESOCK_OPT_TCP_KEEPCNT, 5004).
+-define(ESOCK_OPT_TCP_KEEPIDLE, 5005).
+-define(ESOCK_OPT_TCP_KEEPINTVL, 5006).
+-define(ESOCK_OPT_TCP_MAXSEG, 5007).
+-define(ESOCK_OPT_TCP_MD5SIG, 5008).
+-define(ESOCK_OPT_TCP_NODELAY, 5009).
+-define(ESOCK_OPT_TCP_NOOPT, 5010).
+-define(ESOCK_OPT_TCP_NOPUSH, 5011).
+-define(ESOCK_OPT_TCP_SYNCNT, 5012).
+-define(ESOCK_OPT_TCP_USER_TIMEOUT, 5013).
+
+%% ----------------------------------
+%% *** UDP (socket) options
+
+-define(ESOCK_OPT_UDP_CORK, 6001).
+
+%% ----------------------------------
+%% *** SCTP (socket) options
+
+-define(ESOCK_OPT_SCTP_ADAPTION_LAYER, 7001).
+-define(ESOCK_OPT_SCTP_ASSOCINFO, 7002).
+-define(ESOCK_OPT_SCTP_AUTH_ACTIVE_KEY, 7003).
+-define(ESOCK_OPT_SCTP_AUTH_ASCONF, 7004).
+-define(ESOCK_OPT_SCTP_AUTH_CHUNK, 7005).
+-define(ESOCK_OPT_SCTP_AUTH_KEY, 7006).
+-define(ESOCK_OPT_SCTP_AUTH_DELETE_KEY, 7007).
+-define(ESOCK_OPT_SCTP_AUTOCLOSE, 7008).
+-define(ESOCK_OPT_SCTP_CONTEXT, 7009).
+-define(ESOCK_OPT_SCTP_DEFAULT_SEND_PARAMS, 7010).
+-define(ESOCK_OPT_SCTP_DELAYED_ACK_TIME, 7011).
+-define(ESOCK_OPT_SCTP_DISABLE_FRAGMENTS, 7012).
+-define(ESOCK_OPT_SCTP_HMAC_IDENT, 7013).
+-define(ESOCK_OPT_SCTP_EVENTS, 7014).
+-define(ESOCK_OPT_SCTP_EXPLICIT_EOR, 7015).
+-define(ESOCK_OPT_SCTP_FRAGMENT_INTERLEAVE, 7016).
+-define(ESOCK_OPT_SCTP_GET_PEER_ADDR_INFO, 7017).
+-define(ESOCK_OPT_SCTP_INITMSG, 7018).
+-define(ESOCK_OPT_SCTP_I_WANT_MAPPED_V4_ADDR, 7019).
+-define(ESOCK_OPT_SCTP_LOCAL_AUTH_CHUNKS, 7020).
+-define(ESOCK_OPT_SCTP_MAXSEG, 7021).
+-define(ESOCK_OPT_SCTP_MAXBURST, 7022).
+-define(ESOCK_OPT_SCTP_NODELAY, 7023).
+-define(ESOCK_OPT_SCTP_PARTIAL_DELIVERY_POINT, 7024).
+-define(ESOCK_OPT_SCTP_PEER_ADDR_PARAMS, 7025).
+-define(ESOCK_OPT_SCTP_PEER_AUTH_CHUNKS, 7026).
+-define(ESOCK_OPT_SCTP_PRIMARY_ADDR, 7027).
+-define(ESOCK_OPT_SCTP_RESET_STREAMS, 7028).
+-define(ESOCK_OPT_SCTP_RTOINFO, 7029).
+-define(ESOCK_OPT_SCTP_SET_PEER_PRIMARY_ADDR, 7030).
+-define(ESOCK_OPT_SCTP_STATUS, 7031).
+-define(ESOCK_OPT_SCTP_USE_EXT_RECVINFO, 7032).
+
+
+%% ===========================================================================
+%%
+%% Guard macros
+%%
+
+%% Check that there are 1:s just in the lowest 8 bits of all 4 elements
+-define(
+ IS_IPV4_ADDR(A),
+ (is_tuple(A) andalso tuple_size(A) =:= 4
+ andalso
+ ((element(1, (A)) bor element(2, (A))
+ bor element(3, (A)) bor element(4, (A))
+ ) band (bnot 16#FF)
+ ) =:= 0)).
+
+%% Check that there are 1:s just in the lowest 16 bits of all 8 elements
+-define(
+ IS_IPV6_ADDR(A),
+ (is_tuple(A) andalso tuple_size(A) =:= 8
+ andalso
+ ((element(1, (A)) bor element(2, (A))
+ bor element(3, (A)) bor element(4, (A))
+ bor element(5, (A)) bor element(6, (A))
+ bor element(7, (A)) bor element(8, (A))
+ ) band (bnot 16#FFFF)
+ ) =:= 0)).
+
+
+%% ===========================================================================
+%% API for 'erl_init'
+%%
+
+on_load() ->
+ on_load(#{}).
+
+on_load(Extra) when is_map(Extra) ->
+ %% This is spawned as a system process to prevent init:restart/0 from
+ %% killing it.
+ Pid = erts_internal:spawn_system_process(?REGISTRY, start, []),
+ DebugFilename =
+ case os:get_env_var("ESOCK_DEBUG_FILENAME") of
+ "*" ->
+ "/tmp/esock-dbg-??????";
+ F ->
+ F
+ end,
+ ok =
+ erlang:load_nif(
+ atom_to_list(?MODULE),
+ case DebugFilename of
+ false ->
+ case os:get_env_var("ESOCK_USE_SOCKET_REGISTRY") of
+ "true" ->
+ Extra#{registry => Pid,
+ use_registry => true};
+ "false" ->
+ Extra#{registry => Pid,
+ use_registry => false};
+ _ ->
+ Extra#{registry => Pid}
+ end;
+ _ ->
+ case os:get_env_var("ESOCK_USE_SOCKET_REGISTRY") of
+ "true" ->
+ Extra#{registry => Pid,
+ use_registry => true,
+ debug => true,
+ socket_debug => true,
+ debug_filename => encode_path(DebugFilename)};
+ "false" ->
+ Extra#{registry => Pid,
+ use_registry => false,
+ debug => true,
+ socket_debug => true,
+ debug_filename => encode_path(DebugFilename)};
+ _ ->
+ Extra#{registry => Pid,
+ debug => true,
+ socket_debug => true,
+ debug_filename => encode_path(DebugFilename)}
+ end
+ end).
+
+
+%% ===========================================================================
+%% API for 'socket'
+%%
+
+%% File names has to be encoded according to
+%% the native file encoding
+%%
+encode_path(Path) ->
+ %% These are all BIFs - will not cause code loading
+ unicode:characters_to_binary(Path, file:native_name_encoding()).
+
+encode_sockaddr(SockAddr) ->
+ enc_sockaddr(SockAddr).
+
+
+%% ----------------------------------
+
+info() ->
+ nif_info().
+
+info(SockRef) ->
+ nif_info(SockRef).
+
+%% ----------------------------------
+
+debug(D) ->
+ nif_command(#{command => ?FUNCTION_NAME, data => D}).
+
+
+socket_debug(D) ->
+ nif_command(#{command => ?FUNCTION_NAME, data => D}).
+
+
+use_registry(D) ->
+ nif_command(#{command => ?FUNCTION_NAME, data => D}).
+
+
+%% ----------------------------------
+
+supports() ->
+ nif_supports().
+
+supports(Key) ->
+ nif_supports(Key).
+
+supports(options = Key, Level) ->
+ case Level of
+ socket ->
+ nif_supports(Key, ?ESOCK_OPT_LEVEL_SOCKET);
+ ip ->
+ nif_supports(Key, ?ESOCK_OPT_LEVEL_IP);
+ ipv6 ->
+ nif_supports(Key, ?ESOCK_OPT_LEVEL_IPV6);
+ tcp ->
+ nif_supports(Key, ?ESOCK_OPT_LEVEL_TCP);
+ udp ->
+ nif_supports(Key, ?ESOCK_OPT_LEVEL_UDP);
+ sctp ->
+ nif_supports(Key, ?ESOCK_OPT_LEVEL_SCTP);
+ _ ->
+ []
+ end;
+supports(_Key1, _Key2) ->
+ [].
+
+%% ----------------------------------
+
+open(FD, Opts) when is_map(Opts) ->
+ EOpts =
+ maps:map(
+ fun (Key, Val) ->
+ case Key of
+ domain ->
+ enc_domain(Val);
+ type ->
+ enc_type(Val);
+ protocol ->
+ enc_protocol(Val);
+ _ ->
+ Val
+ end
+ end, Opts),
+ nif_open(FD, EOpts).
+
+open(Domain, Type, Protocol, Opts) when is_map(Opts) ->
+ EDomain = enc_domain(Domain),
+ EType = enc_type(Type),
+ EProtocol = enc_protocol(Protocol),
+ EOpts =
+ case Opts of
+ #{netns := Path} when is_list(Path) ->
+ Opts#{netns := encode_path(Path)};
+ _ ->
+ Opts
+ end,
+ nif_open(EDomain, EType, EProtocol, EOpts).
+
+%% ----------------------------------
+
+bind(SockRef, Addr) ->
+ nif_bind(SockRef, enc_sockaddr(Addr)).
+
+bind(SockRef, Addrs, Action) when is_list(Addrs) ->
+ EAddrs = [enc_sockaddr(Addr) || Addr <- Addrs],
+ nif_bind(SockRef, EAddrs, Action).
+
+%% ----------------------------------
+
+connect(SockRef, SockAddr) ->
+ nif_connect(SockRef, enc_sockaddr(SockAddr)).
+
+connect(SockRef) ->
+ nif_connect(SockRef).
+
+%% ----------------------------------
+
+listen(SockRef, Backlog) ->
+ nif_listen(SockRef, Backlog).
+
+%% ----------------------------------
+
+accept(ListenSockRef, AccRef) ->
+ case nif_accept(ListenSockRef, AccRef) of
+ {ok, _SockRef} = Result ->
+ Result;
+ {error, eagain} ->
+ select;
+ {error, _} = Result ->
+ Result
+ end.
+
+%% ----------------------------------
+
+send(SockRef, SendRef, Data, Flags) ->
+ EFlags = enc_send_flags(Flags),
+ send_result(
+ nif_send(SockRef, SendRef, Data, EFlags)).
+
+sendto(SockRef, SendRef, Data, To, Flags) ->
+ ETo = enc_sockaddr(To),
+ EFlags = enc_send_flags(Flags),
+ send_result(
+ nif_sendto(SockRef, SendRef, Data, ETo, EFlags)).
+
+sendmsg(SockRef, SendRef, MsgHdr, Flags) ->
+ EMsgHdr = enc_msghdr(MsgHdr),
+ EFlags = enc_send_flags(Flags),
+ send_result(
+ nif_sendmsg(SockRef, SendRef, EMsgHdr, EFlags)).
+
+send_result(Result) ->
+ case Result of
+ ok -> ok;
+ {ok, _Written} = OK -> OK;
+ {error, eagain} -> select;
+ {error, _} = ERROR -> ERROR
+ end.
+
+%% ----------------------------------
+
+recv(SockRef, RecvRef, Length, Flags) ->
+ EFlags = enc_recv_flags(Flags),
+ case nif_recv(SockRef, RecvRef, Length, EFlags) of
+ {ok, true, Bin} ->
+ {ok, Bin};
+ %%
+ {ok, false, Bin} ->
+ %% Depending on the number of bytes we tried to read:
+ if
+ Length =:= 0 ->
+ %% 0 - Read everything available
+ %% We got something, but there may be more
+ %% - keep reading.
+ {more, Bin};
+ true ->
+ %% > 0 - We got a part of the message
+ %% and we will be notified when there is more to read
+ %% (a select message)
+ {select, Bin}
+ end;
+ %%
+ {error, eagain} ->
+ select;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+recvfrom(SockRef, RecvRef, Length, Flags) ->
+ EFlags = enc_recv_flags(Flags),
+ recvfromsg_result(
+ nif_recvfrom(SockRef, RecvRef, Length, EFlags)).
+
+recvmsg(SockRef, RecvRef, BufSz, CtrlSz, Flags) ->
+ EFlags = enc_recv_flags(Flags),
+ recvfromsg_result(
+ nif_recvmsg(SockRef, RecvRef, BufSz, CtrlSz, EFlags)).
+
+recvfromsg_result(Result) ->
+ case Result of
+ {ok, _} = OK -> OK;
+ {error, eagain} -> select;
+ {error, _} = ERROR -> ERROR
+ end.
+
+%% ----------------------------------
+
+close(SockRef) ->
+ nif_close(SockRef).
+
+finalize_close(SockRef) ->
+ nif_finalize_close(SockRef).
+
+%% ----------------------------------
+
+shutdown(SockRef, How) ->
+ nif_shutdown(SockRef, enc_shutdown_how(How)).
+
+%% ----------------------------------
+
+setopt(SockRef, Level, Opt, Val)
+ when is_integer(Level), is_integer(Opt), is_binary(Val) ->
+ nif_setopt(SockRef, false, Level, Opt, Val);
+setopt(SockRef, Level, Opt, Val)
+ when is_integer(Opt), is_binary(Val) ->
+ ELevel = enc_sockopt_type(Level, []),
+ nif_setopt(SockRef, true, ELevel, Opt, Val);
+setopt(SockRef, Level, Opt, Value) when Opt =/= [] ->
+ case enc_sockopt_type(Level, Opt) of
+ {undefined, _ELevel, _EOpt} ->
+ {error, einval};
+ {Type, ELevel, EOpt} ->
+ EValue = enc_setopt_value(Level, Opt, Value, Type),
+ nif_setopt(SockRef, true, ELevel, EOpt, EValue)
+ end.
+
+getopt(SockRef, Level, Opt)
+ when is_integer(Level) ->
+ nif_getopt(SockRef, false, Level, Opt);
+getopt(SockRef, Level, Opt) when is_atom(Opt) ->
+ {_Type, ELevel, EOpt} = enc_sockopt_type(Level, Opt),
+ case nif_getopt(SockRef, true, ELevel, EOpt) of
+ {ok, Value} ->
+ {ok, dec_getopt_value(Value, Level, Opt)};
+ {error, _} = Error ->
+ Error
+ end;
+getopt(SockRef, Level, Opt) ->
+ ELevel = enc_sockopt_type(Level, []),
+ nif_getopt(SockRef, true, ELevel, Opt).
+
+%% ----------------------------------
+
+sockname(Ref) ->
+ nif_sockname(Ref).
+
+peername(Ref) ->
+ nif_peername(Ref).
+
+%% ----------------------------------
+
+cancel(SRef, Op, Ref) ->
+ nif_cancel(SRef, Op, Ref).
+
+%% ===========================================================================
+%% Encode / decode
+%%
+
+enc_domain(local) -> ?ESOCK_DOMAIN_LOCAL;
+enc_domain(inet) -> ?ESOCK_DOMAIN_INET;
+enc_domain(inet6) -> ?ESOCK_DOMAIN_INET6;
+enc_domain(Domain) -> invalid(domain, Domain).
+
+enc_type(stream) -> ?ESOCK_TYPE_STREAM;
+enc_type(dgram) -> ?ESOCK_TYPE_DGRAM;
+enc_type(raw) -> ?ESOCK_TYPE_RAW;
+enc_type(seqpacket) -> ?ESOCK_TYPE_SEQPACKET;
+enc_type(Type) -> invalid(type, Type).
+
+enc_protocol(default) -> ?ESOCK_PROTOCOL_DEFAULT;
+enc_protocol(ip) -> ?ESOCK_PROTOCOL_IP;
+enc_protocol(tcp) -> ?ESOCK_PROTOCOL_TCP;
+enc_protocol(udp) -> ?ESOCK_PROTOCOL_UDP;
+enc_protocol(sctp) -> ?ESOCK_PROTOCOL_SCTP;
+enc_protocol(icmp) -> ?ESOCK_PROTOCOL_ICMP;
+enc_protocol(igmp) -> ?ESOCK_PROTOCOL_IGMP;
+enc_protocol({raw, P} = RAW) when is_integer(P) -> RAW;
+enc_protocol(Proto) ->
+ invalid(protocol, Proto).
+
+
+enc_sockaddr(#{family := inet} = SockAddr) ->
+ maps:merge(?ESOCK_SOCKADDR_IN4_DEFAULTS, SockAddr);
+enc_sockaddr(#{family := inet6} = SockAddr) ->
+ maps:merge(?ESOCK_SOCKADDR_IN6_DEFAULTS, SockAddr);
+enc_sockaddr(#{family := local, path := Path} = SockAddr)
+ when is_list(Path) andalso
+ (length(Path) > 0) andalso
+ (length(Path) =< 255) ->
+ BinPath = encode_path(Path),
+ enc_sockaddr(SockAddr#{path => BinPath});
+enc_sockaddr(#{family := local, path := Path} = SockAddr)
+ when is_binary(Path) andalso
+ (byte_size(Path) > 0) andalso
+ (byte_size(Path) =< 255) ->
+ SockAddr;
+enc_sockaddr(SockAddr) ->
+ invalid(address, SockAddr).
+
+
+enc_send_flags([]) -> 0;
+enc_send_flags([Flag | Flags]) ->
+ case Flag of
+ confirm -> ?ESOCK_SEND_FLAG_CONFIRM;
+ dontroute -> ?ESOCK_SEND_FLAG_DONTROUTE;
+ eor -> ?ESOCK_SEND_FLAG_EOR;
+ more -> ?ESOCK_SEND_FLAG_MORE;
+ nosignal -> ?ESOCK_SEND_FLAG_NOSIGNAL;
+ oob -> ?ESOCK_SEND_FLAG_OOB;
+ _ -> invalid(flag, Flag)
+ end bor enc_send_flags(Flags).
+
+
+enc_recv_flags([]) -> 0;
+enc_recv_flags([Flag | Flags]) ->
+ case Flag of
+ cmsg_cloexec -> ?ESOCK_RECV_FLAG_CMSG_CLOEXEC;
+ errqueue -> ?ESOCK_RECV_FLAG_ERRQUEUE;
+ oob -> ?ESOCK_RECV_FLAG_OOB;
+ peek -> ?ESOCK_RECV_FLAG_PEEK;
+ trunc -> ?ESOCK_RECV_FLAG_TRUNC;
+ _ -> invalid(flag, Flag)
+ end bor enc_recv_flags(Flags).
+
+
+enc_msghdr(#{ctrl := []} = M) ->
+ enc_msghdr(maps:remove(ctrl, M));
+enc_msghdr(#{iov := IOV, addr := Addr} = M)
+ when is_list(IOV), IOV =/= [] ->
+ M#{iov => erlang:iolist_to_iovec(IOV),
+ addr => enc_sockaddr(Addr)};
+enc_msghdr(#{iov := IOV} = M)
+ when is_list(IOV), IOV =/= [] ->
+ M#{iov => erlang:iolist_to_iovec(IOV)};
+enc_msghdr(#{} = M) ->
+ not_supported(M).
+
+
+%% Common to setopt and getopt
+%% - the returned first tuple element is a type tag
+%% that is used by setopt through enc_setopt_value/4
+%% and ignored by getopt;
+%% 'undefined' means that encoding for setopt/4
+%% cannot be done and setopt/4 returns {error, einval}.
+%%
+%% Opt values not handled here will be passed to not_supported/1,2
+%% which causes a runtime error.
+%%
+%% Values that fails encoding in enc_setopt_value/4
+%% will be passed to not_supported/3.
+%%
+%% So, all combinations of Level and Opt enumerated here
+%% will either return {error, einval} because they can not
+%% be encoded for setopt/4, or they are passed to
+%% the setopt or getopt NIF. The NIF will return
+%% {error, einval} for unknown Level and Opt.
+%%
+%% Therefore all Level and Opt enumerated here will produce
+%% a return value from setopt/4 and getopt/3 and should
+%% be in the type spec for socket:setopt() and socket:getopt().
+%%
+%% Opt =:= [] just encodes the Level
+%%
+enc_sockopt_type(otp = Level, Opt) ->
+ L = ?ESOCK_OPT_LEVEL_OTP,
+ case Opt of
+ [] -> L;
+ %%
+ debug -> {boolean, L, ?ESOCK_OPT_OTP_DEBUG};
+ iow -> {boolean, L, ?ESOCK_OPT_OTP_IOW};
+ controlling_process -> {pid, L, ?ESOCK_OPT_OTP_CTRL_PROC};
+ rcvbuf -> {Opt, L, ?ESOCK_OPT_OTP_RCVBUF};
+ rcvctrlbuf -> {buf, L, ?ESOCK_OPT_OTP_RCVCTRLBUF};
+ sndctrlbuf -> {buf, L, ?ESOCK_OPT_OTP_SNDCTRLBUF};
+ fd -> {undefined, L, ?ESOCK_OPT_OTP_FD};
+ meta -> {term, L, ?ESOCK_OPT_OTP_META};
+ use_registry -> {boolean, L, ?ESOCK_OPT_OTP_USE_REGISTRY};
+ domain -> {undefined, L, ?ESOCK_OPT_OTP_DOMAIN};
+ _ ->
+ not_supported(Level, Opt)
+ end;
+enc_sockopt_type(socket = Level, Opt) ->
+ L = ?ESOCK_OPT_LEVEL_SOCKET,
+ case Opt of
+ [] -> L;
+ %%
+ acceptconn -> {undefined, L, ?ESOCK_OPT_SOCK_ACCEPTCONN};
+ acceptfilter -> {undefined, L, ?ESOCK_OPT_SOCK_ACCEPTFILTER};
+ bindtodevice ->
+ %% Before linux 3.8, this socket option
+ %% could be set but not get.
+ %% Maximum size of buffer for name: IFNAMSIZ
+ %% So, we let the implementation decide.
+ {list, L, ?ESOCK_OPT_SOCK_BINDTODEVICE};
+ broadcast -> {boolean, L, ?ESOCK_OPT_SOCK_BROADCAST};
+ busy_poll -> {undefined, L, ?ESOCK_OPT_SOCK_BUSY_POLL};
+ debug -> {integer, L, ?ESOCK_OPT_SOCK_DEBUG};
+ domain -> {undefined, L, ?ESOCK_OPT_SOCK_DOMAIN};
+ dontroute -> {boolean, L, ?ESOCK_OPT_SOCK_DONTROUTE};
+ error -> {undefined, L, ?ESOCK_OPT_SOCK_ERROR};
+ keepalive -> {boolean, L, ?ESOCK_OPT_SOCK_KEEPALIVE};
+ linger -> {linger, L, ?ESOCK_OPT_SOCK_LINGER};
+ mark -> {undefined, L, ?ESOCK_OPT_SOCK_MARK};
+ oobinline -> {boolean, L, ?ESOCK_OPT_SOCK_OOBINLINE};
+ passcred -> {boolean, L, ?ESOCK_OPT_SOCK_PASSCRED};
+ peek_off -> {integer, L, ?ESOCK_OPT_SOCK_PEEK_OFF};
+ peercred -> {undefined, L, ?ESOCK_OPT_SOCK_PEERCRED};
+ priority -> {integer, L, ?ESOCK_OPT_SOCK_PRIORITY};
+ protocol -> {undefined, L, ?ESOCK_OPT_SOCK_PROTOCOL};
+ rcvbuf -> {integer, L, ?ESOCK_OPT_SOCK_RCVBUF};
+ rcvbufforce -> {undefined, L, ?ESOCK_OPT_SOCK_RCVBUFFORCE};
+ rcvlowat ->
+ %% May not work on Linux
+ {integer, L, ?ESOCK_OPT_SOCK_RCVLOWAT};
+ rcvtimeo -> {timeval, L, ?ESOCK_OPT_SOCK_RCVTIMEO};
+ reuseaddr -> {boolean, L, ?ESOCK_OPT_SOCK_REUSEADDR};
+ reuseport -> {boolean, L, ?ESOCK_OPT_SOCK_REUSEPORT};
+ rxq_ovfl -> {undefined, L, ?ESOCK_OPT_SOCK_RXQ_OVFL};
+ setfib -> {undefined, L, ?ESOCK_OPT_SOCK_SETFIB};
+ sndbuf -> {integer, L, ?ESOCK_OPT_SOCK_SNDBUF};
+ sndbufforce -> {undefined, L, ?ESOCK_OPT_SOCK_SNDBUFFORCE};
+ sndlowat ->
+ %% Not changeable on Linux
+ {integer, L, ?ESOCK_OPT_SOCK_SNDLOWAT};
+ sndtimeo -> {timeval, L, ?ESOCK_OPT_SOCK_SNDTIMEO};
+ timestamp -> {boolean, L, ?ESOCK_OPT_SOCK_TIMESTAMP};
+ type -> {undefined, L, ?ESOCK_OPT_SOCK_TYPE};
+ _ ->
+ not_supported(Level, Opt)
+ end;
+enc_sockopt_type(ip = Level, Opt) ->
+ L = ?ESOCK_OPT_LEVEL_IP,
+ case Opt of
+ [] -> L;
+ %%
+ add_membership ->
+ {addr_if, L, ?ESOCK_OPT_IP_ADD_MEMBERSHIP};
+ add_source_membership ->
+ {addr_if_src, L, ?ESOCK_OPT_IP_ADD_SOURCE_MEMBERSHIP};
+ block_source ->
+ {addr_if_src, L, ?ESOCK_OPT_IP_BLOCK_SOURCE};
+ dontfrag ->
+ %% FreeBSD only?
+ %% Only respected on udp and raw ip
+ %% (unless the hdrincl option has been set)
+ {boolean, L, ?ESOCK_OPT_IP_DONTFRAG};
+ drop_membership ->
+ {addr_if, L, ?ESOCK_OPT_IP_DROP_MEMBERSHIP};
+ drop_source_membership ->
+ {addr_if_src, L, ?ESOCK_OPT_IP_DROP_SOURCE_MEMBERSHIP};
+ freebind ->
+ %% Linux only?
+ {boolean, L, ?ESOCK_OPT_IP_FREEBIND};
+ hdrincl ->
+ {boolean, L, ?ESOCK_OPT_IP_HDRINCL};
+ minttl ->
+ {integer, L, ?ESOCK_OPT_IP_MINTTL};
+ msfilter -> {Opt, L, ?ESOCK_OPT_IP_MSFILTER};
+ mtu -> {undefined, L, ?ESOCK_OPT_IP_MTU};
+ mtu_discover -> {Opt, L, ?ESOCK_OPT_IP_MTU_DISCOVER};
+ multicast_all ->
+ {boolean, L, ?ESOCK_OPT_IP_MULTICAST_ALL};
+ multicast_if ->
+ {boolean, L, ?ESOCK_OPT_IP_MULTICAST_ALL};
+ multicast_loop ->
+ {boolean, L, ?ESOCK_OPT_IP_MULTICAST_LOOP};
+ multicast_ttl ->
+ {byte, L, ?ESOCK_OPT_IP_MULTICAST_TTL};
+ nodefrag ->
+ {boolean, L, ?ESOCK_OPT_IP_NODEFRAG};
+ options ->
+ {undefined, L, ?ESOCK_OPT_IP_OPTIONS};
+ pktinfo ->
+ {boolean, L, ?ESOCK_OPT_IP_PKTINFO};
+ recvdstaddr ->
+ {boolean, L, ?ESOCK_OPT_IP_RECVDSTADDR};
+ recverr ->
+ {boolean, L, ?ESOCK_OPT_IP_RECVERR};
+ recvif ->
+ {boolean, L, ?ESOCK_OPT_IP_RECVIF};
+ recvopts ->
+ {boolean, L, ?ESOCK_OPT_IP_RECVOPTS};
+ recvorigdstaddr ->
+ {boolean, L, ?ESOCK_OPT_IP_RECVORIGDSTADDR};
+ recvtos ->
+ {boolean, L, ?ESOCK_OPT_IP_RECVTOS};
+ recvttl ->
+ {boolean, L, ?ESOCK_OPT_IP_RECVTTL};
+ retopts ->
+ {boolean, L, ?ESOCK_OPT_IP_RETOPTS};
+ router_alert ->
+ {integer, L, ?ESOCK_OPT_IP_ROUTER_ALERT};
+ sendsrcaddr ->
+ {boolean, L, ?ESOCK_OPT_IP_SENDSRCADDR};
+ tos ->
+ %% On FreeBSD it specifies that this option is only valid
+ %% for stream, dgram and "some" raw sockets...
+ %% No such condition on linux (in the man page)...
+ {Opt, L, ?ESOCK_OPT_IP_TOS};
+ transparent ->
+ {boolean, L, ?ESOCK_OPT_IP_TRANSPARENT};
+ ttl ->
+ {integer, L, ?ESOCK_OPT_IP_TTL};
+ unblock_source ->
+ {addr_if_src, L, ?ESOCK_OPT_IP_UNBLOCK_SOURCE};
+ _ ->
+ not_supported(Level, Opt)
+ end;
+enc_sockopt_type(ipv6 = Level, Opt) ->
+ L = ?ESOCK_OPT_LEVEL_IPV6,
+ case Opt of
+ [] -> L;
+ %%
+ addrform -> {Opt, L, ?ESOCK_OPT_IPV6_ADDRFORM};
+ add_membership ->
+ {addr_if, L, ?ESOCK_OPT_IPV6_ADD_MEMBERSHIP};
+ authhdr ->
+ %% Is this obsolete? When get, the result is enoprotoopt
+ %% and in the header file it says 'obsolete'...
+ %% But there might be (old?) versions of linux
+ %% where it still works...
+ {boolean, L, ?ESOCK_OPT_IPV6_AUTHHDR};
+ auth_level ->
+ {undefined, L, ?ESOCK_OPT_IPV6_AUTH_LEVEL};
+ checksum ->
+ {undefined, L, ?ESOCK_OPT_IPV6_CHECKSUM};
+ drop_membership ->
+ {addr_if, L, ?ESOCK_OPT_IPV6_DROP_MEMBERSHIP};
+ dstopts ->
+ {boolean, L, ?ESOCK_OPT_IPV6_DSTOPTS};
+ esp_network_level ->
+ {undefined, L, ?ESOCK_OPT_IPV6_ESP_NETWORK_LEVEL};
+ esp_trans_level ->
+ {undefined, L, ?ESOCK_OPT_IPV6_ESP_TRANS_LEVEL};
+ flowinfo ->
+ {boolean, L, ?ESOCK_OPT_IPV6_FLOWINFO};
+ hoplimit ->
+ {boolean, L, ?ESOCK_OPT_IPV6_HOPLIMIT};
+ hopopts ->
+ {boolean, L, ?ESOCK_OPT_IPV6_HOPOPTS};
+ ipcomp_level ->
+ {undefined, L, ?ESOCK_OPT_IPV6_IPCOMP_LEVEL};
+ join_group ->
+ {undefined, L, ?ESOCK_OPT_IPV6_JOIN_GROUP};
+ leave_group ->
+ {undefined, L, ?ESOCK_OPT_IPV6_LEAVE_GROUP};
+ mtu ->
+ {integer, L, ?ESOCK_OPT_IPV6_MTU};
+ mtu_discover -> {Opt, L, ?ESOCK_OPT_IPV6_MTU_DISCOVER};
+ multicast_hops ->
+ {hops, L, ?ESOCK_OPT_IPV6_MULTICAST_HOPS};
+ multicast_if ->
+ {integer, L, ?ESOCK_OPT_IPV6_MULTICAST_IF};
+ multicast_loop ->
+ {boolean, L, ?ESOCK_OPT_IPV6_MULTICAST_LOOP};
+ portrange ->
+ {undefined, L, ?ESOCK_OPT_IPV6_PORTRANGE};
+ pktoptions ->
+ {undefined, L, ?ESOCK_OPT_IPV6_PKTOPTIONS};
+ recverr ->
+ {boolean, L, ?ESOCK_OPT_IPV6_RECVERR};
+ recvhoplimit ->
+ {boolean, L, ?ESOCK_OPT_IPV6_RECVHOPLIMIT};
+ recvpktinfo ->
+ {boolean, L, ?ESOCK_OPT_IPV6_RECVPKTINFO};
+ pktinfo -> % alias on FreeBSD
+ {boolean, L, ?ESOCK_OPT_IPV6_RECVPKTINFO};
+ recvtclass ->
+ {boolean, L, ?ESOCK_OPT_IPV6_RECVTCLASS};
+ router_alert ->
+ {integer, L, ?ESOCK_OPT_IPV6_ROUTER_ALERT};
+ rthdr ->
+ {boolean, L, ?ESOCK_OPT_IPV6_RTHDR};
+ tclass ->
+ {boolean, L, ?ESOCK_OPT_IPV6_TCLASS};
+ unicast_hops ->
+ {hops, L, ?ESOCK_OPT_IPV6_UNICAST_HOPS};
+ use_min_mtu ->
+ {undefined, L, ?ESOCK_OPT_IPV6_USE_MIN_MTU};
+ v6only ->
+ {boolean, L, ?ESOCK_OPT_IPV6_V6ONLY};
+ _ ->
+ not_supported(Level, Opt)
+ end;
+enc_sockopt_type(tcp = Level, Opt) ->
+ L = ?ESOCK_OPT_LEVEL_TCP,
+ case Opt of
+ [] -> L;
+ %%
+ congestion ->
+ {list, L, ?ESOCK_OPT_TCP_CONGESTION};
+ cork ->
+ {boolean, L, ?ESOCK_OPT_TCP_CORK};
+ info ->
+ {undefined, L, ?ESOCK_OPT_TCP_INFO};
+ keepcnt ->
+ {undefined, L, ?ESOCK_OPT_TCP_KEEPCNT};
+ keepidle ->
+ {undefined, L, ?ESOCK_OPT_TCP_KEEPIDLE};
+ keepintvl ->
+ {undefined, L, ?ESOCK_OPT_TCP_KEEPINTVL};
+ maxseg ->
+ {integer, L, ?ESOCK_OPT_TCP_MAXSEG};
+ md5seg ->
+ {undefined, L, ?ESOCK_OPT_TCP_MD5SIG};
+ nodelay ->
+ {boolean, L, ?ESOCK_OPT_TCP_NODELAY};
+ noopt ->
+ {undefined, L, ?ESOCK_OPT_TCP_NOOPT};
+ nopush ->
+ {undefined, L, ?ESOCK_OPT_TCP_NOPUSH};
+ syncnt ->
+ %% Only set? 1..255
+ {undefined, L, ?ESOCK_OPT_TCP_SYNCNT};
+ user_timeout ->
+ {undefined, L, ?ESOCK_OPT_TCP_USER_TIMEOUT};
+ _ ->
+ not_supported(Level, Opt)
+ end;
+enc_sockopt_type(udp = Level, Opt) ->
+ L = ?ESOCK_OPT_LEVEL_UDP,
+ case Opt of
+ [] -> L;
+ %%
+ cork ->
+ {boolean, L, ?ESOCK_OPT_UDP_CORK};
+ _ ->
+ not_supported(Level, Opt)
+ end;
+enc_sockopt_type(sctp = Level, Opt) ->
+ L = ?ESOCK_OPT_LEVEL_SCTP,
+ case Opt of
+ [] -> L;
+ %%
+ adaption_layer ->
+ {undefined, L, ?ESOCK_OPT_SCTP_ADAPTION_LAYER};
+ associnfo -> {Opt, L, ?ESOCK_OPT_SCTP_ASSOCINFO};
+ auth_active_key ->
+ {undefined, L, ?ESOCK_OPT_SCTP_AUTH_ACTIVE_KEY};
+ auth_asconf ->
+ {undefined, L, ?ESOCK_OPT_SCTP_AUTH_ASCONF};
+ auth_chunk ->
+ {undefined, L, ?ESOCK_OPT_SCTP_AUTH_CHUNK};
+ auth_key ->
+ {undefined, L, ?ESOCK_OPT_SCTP_AUTH_KEY};
+ auth_delete_key ->
+ {undefined, L, ?ESOCK_OPT_SCTP_AUTH_DELETE_KEY};
+ autoclose ->
+ {integer, L, ?ESOCK_OPT_SCTP_AUTOCLOSE};
+ context ->
+ {undefined, L, ?ESOCK_OPT_SCTP_CONTEXT};
+ default_send_params ->
+ {undefined, L, ?ESOCK_OPT_SCTP_DEFAULT_SEND_PARAMS};
+ delayed_ack_time ->
+ {undefined, L, ?ESOCK_OPT_SCTP_DELAYED_ACK_TIME};
+ disable_fragments ->
+ {boolean, L, ?ESOCK_OPT_SCTP_DISABLE_FRAGMENTS};
+ hmac_ident ->
+ {undefined, L, ?ESOCK_OPT_SCTP_HMAC_IDENT};
+ events -> {Opt, L, ?ESOCK_OPT_SCTP_EVENTS};
+ explicit_eor ->
+ {undefined, L, ?ESOCK_OPT_SCTP_EXPLICIT_EOR};
+ fragment_intreleave ->
+ {undefined, L, ?ESOCK_OPT_SCTP_FRAGMENT_INTERLEAVE};
+ get_peer_addr_info ->
+ {undefined, L, ?ESOCK_OPT_SCTP_GET_PEER_ADDR_INFO};
+ initmsg -> {Opt, L, ?ESOCK_OPT_SCTP_INITMSG};
+ i_want_mapped_v4_addr ->
+ {undefined, L, ?ESOCK_OPT_SCTP_I_WANT_MAPPED_V4_ADDR};
+ local_auth_chunks ->
+ {undefined, L, ?ESOCK_OPT_SCTP_LOCAL_AUTH_CHUNKS};
+ maxseg ->
+ {integer, L, ?ESOCK_OPT_SCTP_MAXSEG};
+ maxburst ->
+ {undefined, L, ?ESOCK_OPT_SCTP_MAXBURST};
+ nodelay ->
+ {boolean, L, ?ESOCK_OPT_SCTP_NODELAY};
+ partial_delivery_point ->
+ {undefined, L, ?ESOCK_OPT_SCTP_PARTIAL_DELIVERY_POINT};
+ peer_addr_params ->
+ {undefined, L, ?ESOCK_OPT_SCTP_PEER_ADDR_PARAMS};
+ peer_auth_chunks ->
+ {undefined, L, ?ESOCK_OPT_SCTP_PEER_AUTH_CHUNKS};
+ primary_addr ->
+ {undefined, L, ?ESOCK_OPT_SCTP_PRIMARY_ADDR};
+ reset_streams ->
+ {undefined, L, ?ESOCK_OPT_SCTP_RESET_STREAMS};
+ rtoinfo -> {Opt, L, ?ESOCK_OPT_SCTP_RTOINFO};
+ set_peer_primary_addr ->
+ {undefined, L, ?ESOCK_OPT_SCTP_SET_PEER_PRIMARY_ADDR};
+ status -> % ?ESOCK_OPT_SCTP_RTOINFO;
+ {undefined, L, ?ESOCK_OPT_SCTP_STATUS};
+ use_ext_recvinfo ->
+ {undefined, L, ?ESOCK_OPT_SCTP_USE_EXT_RECVINFO};
+ _ ->
+ not_supported(Level, Opt)
+ end;
+enc_sockopt_type(Level, _Opt) ->
+ not_supported(Level).
+
+%% Validate and possibly encode the setopt value
+%%
+enc_setopt_value(Level, Opt, Value, Type) ->
+ %%
+ %% When guards are enough
+ %%
+ case Type of
+ boolean when is_boolean(Value) ->
+ Value;
+ integer when is_integer(Value) ->
+ Value;
+ non_neg_integer when is_integer(Value), 0 =< Value ->
+ Value;
+ byte when is_integer(Value), 0 =< Value, Value =< 255 ->
+ Value;
+ pid when is_pid(Value) ->
+ Value;
+ list when is_list(Value) ->
+ Value;
+ term ->
+ Value;
+ %%
+ mtu_discover
+ when Value =:= want;
+ Value =:= dont;
+ Value =:= do;
+ Value =:= probe;
+ is_integer(Value) ->
+ Value;
+ tos
+ when Value =:= lowdelay;
+ Value =:= throughput;
+ Value =:= reliability;
+ Value =:= mincost;
+ is_integer(Value) ->
+ Value;
+ addrform when Value =:= inet ->
+ enc_domain(Value);
+ _ ->
+ enc_setopt_value(Level, Opt, {Type, Value})
+ end.
+%%
+enc_setopt_value(Level, Opt, TypeValue) ->
+ %%
+ %% When matching is needed
+ %%
+ case TypeValue of
+ {rcvbuf, {N, BufSz} = BufSpec}
+ when is_integer(N), 0 < N, is_integer(BufSz), 0 < BufSz ->
+ %% N: Number of reads (when specifying length = 0)
+ %% BufSz: Size of the "read" buffer
+ BufSpec;
+ {rcvbuf, Value} ->
+ enc_setopt_value(Level, Opt, Value, buf);
+ %%
+ {buf, default} ->
+ 0; % This will cause the nif-code to choose the default value
+ {buf, Value} ->
+ enc_setopt_value(Level, Opt, Value, non_neg_integer);
+ %%
+ {linger, abort} ->
+ {true, 0};
+ {linger, {OnOff, Secs} = Linger}
+ when is_boolean(OnOff), is_integer(Secs), 0 =< Secs ->
+ Linger;
+ %%
+ {timeval,
+ #{sec := Sec, usec := USec} = Timeval}
+ when is_integer(Sec), is_integer(USec) ->
+ Timeval;
+ %%
+ {addr_if,
+ #{multiaddr := MA, interface := IF} = AddrIf}
+ when Level =:= ip
+ andalso ?IS_IPV4_ADDR(MA)
+ andalso (IF =:= any orelse ?IS_IPV4_ADDR(IF)) ->
+ AddrIf;
+ {addr_if,
+ #{multiaddr := MA, interface := IF} = AddrIf}
+ when Level =:= ipv6
+ andalso ?IS_IPV6_ADDR(MA)
+ andalso (is_integer(IF) andalso (0 =< IF)) ->
+ AddrIf;
+ %%
+ {addr_if_src,
+ #{multiaddr := MA, interface := IF, sourceaddr := SA} = AddrIfSrc}
+ when ?IS_IPV4_ADDR(MA)
+ andalso ?IS_IPV4_ADDR(IF)
+ andalso ?IS_IPV4_ADDR(SA) ->
+ AddrIfSrc;
+ %%
+ {msfilter, null = Value} ->
+ Value;
+ {addr_msfilter,
+ #{multiaddr := MA, interface := IF, fmode := FMode, slist := SL} =
+ AddrMsfilter}
+ when ?IS_IPV4_ADDR(MA)
+ andalso ?IS_IPV4_ADDR(IF)
+ andalso (FMode =:= include orelse FMode =:= exclude)
+ andalso is_list(SL) ->
+ AddrMsfilter;
+ {hops, default} ->
+ -1;
+ {hops, Value} ->
+ enc_setopt_value(Level, Opt, Value, byte);
+ %%
+ {associnfo,
+ #{assoc_id := AssocId,
+ asocmaxrxt := MaxRxt,
+ num_peer_dests := NumPeerDests,
+ peer_rwnd := PeerRWND,
+ local_rwnd := LocalRWND,
+ cookie_life := CLife} = AssocInfo}
+ when is_integer(AssocId),
+ is_integer(MaxRxt), (MaxRxt >= 0),
+ is_integer(NumPeerDests), (NumPeerDests >= 0),
+ is_integer(PeerRWND), (PeerRWND >= 0),
+ is_integer(LocalRWND), (LocalRWND >= 0),
+ is_integer(CLife), (CLife >= 0) ->
+ AssocInfo;
+ {events,
+ #{data_in := DataIn,
+ association := Assoc,
+ address := Addr,
+ send_failure := SndFailure,
+ peer_error := PeerError,
+ shutdown := Shutdown,
+ partial_delivery := PartialDelivery,
+ adaptation_layer := AdaptLayer,
+ authentication := Auth,
+ sender_dry := SndDry} = Events}
+ when is_boolean(DataIn),
+ is_boolean(Assoc),
+ is_boolean(Addr),
+ is_boolean(SndFailure),
+ is_boolean(PeerError),
+ is_boolean(Shutdown),
+ is_boolean(PartialDelivery),
+ is_boolean(AdaptLayer),
+ is_boolean(Auth),
+ is_boolean(SndDry) ->
+ Events;
+ {initmsg,
+ #{num_outstreams := NumOut,
+ max_instreams := MaxIn,
+ max_attempts := MaxAttempts,
+ max_init_timeo := MaxInitTO} = InitMsg}
+ when is_integer(NumOut), (NumOut >= 0),
+ is_integer(MaxIn), (MaxIn >= 0),
+ is_integer(MaxAttempts), (MaxAttempts >= 0),
+ is_integer(MaxInitTO), (MaxInitTO >= 0) ->
+ InitMsg;
+ {rtoinfo,
+ #{assoc_id := AssocId,
+ initial := Init,
+ max := Max,
+ min := Min} = RTOInfo}
+ when is_integer(AssocId),
+ is_integer(Init), (Init >= 0),
+ is_integer(Max), (Max >= 0),
+ is_integer(Min), (Min >= 0) ->
+ RTOInfo;
+ {Type, Value} when Type =/= undefined ->
+ %% No match
+ not_supported(Level, Opt, Value)
+ end.
+
+
+enc_shutdown_how(How) ->
+ case How of
+ read -> ?ESOCK_SHUTDOWN_HOW_READ;
+ write -> ?ESOCK_SHUTDOWN_HOW_WRITE;
+ read_write -> ?ESOCK_SHUTDOWN_HOW_READ_WRITE;
+ _ ->
+ invalid(how, How)
+ end.
+
+
+%% +++ Decode getopt value +++
+%%
+%% For the most part, we simply let the value pass through, but for some
+%% values we may need to do an actual decode.
+%%
+dec_getopt_value(Alg, tcp, congestion) ->
+ %% This string is NULL-terminated, but the general function we use
+ %% in the nif code does not know that. So, deal with it here.
+ {Str, _} = lists:splitwith(fun(0) -> false; (_) -> true end, Alg),
+ Str;
+dec_getopt_value(Value, _Level, _Opt) ->
+ Value.
+
+%% ===========================================================================
+%% Error functions
+%%
+
+-dialyzer({no_return, not_supported/3}).
+not_supported(Level, Opt, Value) ->
+ not_supported({Level, Opt, Value}).
+%%
+-dialyzer({no_return, not_supported/2}).
+not_supported(Level, Opt) ->
+ not_supported({Level, Opt}).
+%%
+-dialyzer({no_return, not_supported/1}).
+not_supported(What) ->
+ invalid(not_supported, What).
+
+-dialyzer({no_return, invalid/2}).
+invalid(What, Info) ->
+ err({invalid, {What, Info}}).
+
+err(Reason) ->
+ erlang:error(Reason).
+
+
+%% ===========================================================================
+%% NIF functions
+%%
+
+nif_info() -> erlang:nif_error(undef).
+nif_info(_SRef) -> erlang:nif_error(undef).
+
+nif_command(_Command) -> erlang:nif_error(undef).
+
+nif_supports() -> erlang:nif_error(undef).
+nif_supports(_Key) -> erlang:nif_error(undef).
+nif_supports(_Key1, _Key2) -> erlang:nif_error(undef).
+
+nif_open(_FD, _Opts) -> erlang:nif_error(undef).
+nif_open(_Domain, _Type, _Protocol, _Opts) -> erlang:nif_error(undef).
+
+nif_bind(_SRef, _SockAddr) -> erlang:nif_error(undef).
+nif_bind(_SRef, _SockAddrs, _Action) -> erlang:nif_error(undef).
+
+nif_connect(_SRef) -> erlang:nif_error(undef).
+nif_connect(_SRef, _SockAddr) -> erlang:nif_error(undef).
+
+nif_listen(_SRef, _Backlog) -> erlang:nif_error(undef).
+
+nif_accept(_SRef, _Ref) -> erlang:nif_error(undef).
+
+nif_send(_SockRef, _SendRef, _Data, _Flags) -> erlang:nif_error(undef).
+nif_sendto(_SRef, _SendRef, _Data, _Dest, _Flags) -> erlang:nif_error(undef).
+nif_sendmsg(_SRef, _SendRef, _MsgHdr, _Flags) -> erlang:nif_error(undef).
+
+nif_recv(_SRef, _RecvRef, _Length, _Flags) -> erlang:nif_error(undef).
+nif_recvfrom(_SRef, _RecvRef, _Length, _Flags) -> erlang:nif_error(undef).
+nif_recvmsg(_SRef, _RecvRef, _BufSz, _CtrlSz, _Flags) -> erlang:nif_error(undef).
+
+nif_close(_SRef) -> erlang:nif_error(undef).
+nif_finalize_close(_SRef) -> erlang:nif_error(undef).
+nif_shutdown(_SRef, _How) -> erlang:nif_error(undef).
+
+nif_setopt(_Ref, _IsEnc, _Lev, _Opt, _Val) -> erlang:nif_error(undef).
+nif_getopt(_Ref, _IsEnc, _Lev, _Opt) -> erlang:nif_error(undef).
+
+nif_sockname(_Ref) -> erlang:nif_error(undef).
+nif_peername(_Ref) -> erlang:nif_error(undef).
+
+nif_cancel(_SRef, _Op, _Ref) -> erlang:nif_error(undef).
+
+%% ===========================================================================
diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl
deleted file mode 100644
index eb1bcdce66..0000000000
--- a/erts/preloaded/src/socket.erl
+++ /dev/null
@@ -1,4145 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2018-2020. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
--module(socket).
-
--compile(no_native).
--compile({no_auto_import,[error/1]}).
-
-%% Administrative and "global" utility functions
--export([
- on_load/0, on_load/1,
-
- number_of/0,
- which_sockets/0, which_sockets/1,
-
- ensure_sockaddr/1,
-
- debug/1,
- %% command/1,
- info/0, info/1,
- supports/0, supports/1, supports/2, supports/3
- ]).
-
--export([
- open/2, open/3, open/4,
- bind/2, bind/3,
- connect/2, connect/3,
- listen/1, listen/2,
- accept/1, accept/2,
-
- send/2, send/3, send/4,
- sendto/3, sendto/4, sendto/5,
- sendmsg/2, sendmsg/3, sendmsg/4,
-
- recv/1, recv/2, recv/3, recv/4,
- recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4,
- recvmsg/1, recvmsg/2, recvmsg/3, recvmsg/5,
-
- close/1,
- shutdown/2,
-
- setopt/4,
- getopt/3,
-
- sockname/1,
- peername/1,
-
- cancel/2
- ]).
-
--export_type([
- select_tag/0,
- select_ref/0,
- select_info/0,
-
- socket_counters/0,
- socket_counter/0,
- socket_info/0,
-
- %% command/0,
-
- domain/0,
- type/0,
- protocol/0,
- socket/0,
-
- port_number/0,
- ip_address/0,
- ip4_address/0,
- ip6_address/0,
- sockaddr/0,
- sockaddr_in4/0,
- sockaddr_in6/0,
- sockaddr_un/0,
- sockaddr_ll/0,
-
- send_flags/0,
- send_flag/0,
-
- recv_flags/0,
- recv_flag/0,
-
- shutdown_how/0,
-
- sockopt_level/0,
- otp_socket_option/0,
- socket_option/0,
- ip_socket_option/0,
- ipv6_socket_option/0,
- tcp_socket_option/0,
- udp_socket_option/0,
- sctp_socket_option/0,
- raw_socket_option/0,
-
- timeval/0,
- ip_tos/0,
- ip_mreq/0,
- ip_mreq_source/0,
- ip_pmtudisc/0,
- ip_msfilter_mode/0,
- ip_msfilter/0,
- ip_pktinfo/0,
- ipv6_mreq/0,
- ipv6_pmtudisc/0,
- ipv6_pktinfo/0,
- in6_flow_info/0,
- in6_scope_id/0,
- sctp_assoc_id/0,
- sctp_sndrcvinfo/0,
- sctp_event_subscribe/0,
- sctp_assocparams/0,
- sctp_initmsg/0,
- sctp_rtoinfo/0,
-
-
- msghdr_flag/0,
- msghdr_flags/0,
- msghdr/0,
- cmsghdr_level/0,
- cmsghdr_type/0,
- %% cmsghdr_data/0,
- cmsghdr_recv/0, cmsghdr_send/0,
-
- ee_origin/0,
- icmp_dest_unreach/0,
- icmpv6_dest_unreach/0,
- extended_err/0,
-
- uint8/0,
- uint16/0,
- uint20/0,
- uint32/0,
- int32/0
- ]).
-
-
--define(REGISTRY, socket_registry).
-
-
-%% The command type has the general form:
-%% #{
-%% command := atom(),
-%% data := term()
-%% }
-%% But only certain values are actually valid, so the type gets the form:
--type debug_command() :: #{
- command := debug,
- data := boolean()
- }.
-%% -type command() :: debug_command().
-
--type socket_counters() :: [{socket_counter(), non_neg_integer()}].
--type socket_counter() :: read_byte | read_fails | read_pkg | read_pkg_max |
- read_tries | read_waits |
- write_byte | write_fails | write_pkg | write_pkg_max |
- write_tries | write_waits |
- acc_success | acc_fails | acc_tries | acc_waits.
--type socket_info() :: #{domain := domain(),
- type := type(),
- protocol := protocol(),
- ctrl := pid(),
- counters := socket_counters(),
- num_readers := non_neg_integer(),
- num_writers := non_neg_integer(),
- num_acceptors := non_neg_integer(),
- writable := boolean(),
- readable := boolean()}.
-
--type uint8() :: 0..16#FF.
--type uint16() :: 0..16#FFFF.
--type uint20() :: 0..16#FFFFF.
--type uint32() :: 0..16#FFFFFFFF.
--type int32() :: -2147483648..2147483647.
-
-
-%% We support only a subset of all domains.
--type domain() :: local | inet | inet6.
-
-%% We support only a subset of all types.
-%% RDM - Reliably Delivered Messages
--type type() :: stream | dgram | raw | rdm | seqpacket.
-
-%% We support only a subset of all protocols:
-%% Note that the '{raw, integer()}' construct is intended
-%% to be used with type = raw.
-%% Note also that only the "superuser" can create a raw socket.
--type protocol() :: ip | tcp | udp | sctp | icmp | igmp | {raw, integer()}.
-
--type port_number() :: 0..65535.
-
--type ip_address() :: ip4_address() | ip6_address().
-
--type ip4_address() :: {0..255, 0..255, 0..255, 0..255}.
-
--type in6_flow_info() :: uint20().
--type in6_scope_id() :: uint32().
-
--type ip6_address() ::
- {0..65535,
- 0..65535,
- 0..65535,
- 0..65535,
- 0..65535,
- 0..65535,
- 0..65535,
- 0..65535}.
-
--type timeval() :: #{sec := integer(),
- usec := integer()}.
-
--type ip_pktinfo() :: #{
- ifindex := non_neg_integer(), % Interface Index
- spec_dst := ip4_address(), % Local Address
- addr := ip4_address() % Header Destination address
- }.
-
-%% If the integer value is used, its up to the caller to ensure its valid!
--type ip_tos() :: lowdelay |
- throughput |
- reliability |
- mincost |
- integer().
-
-%% This type is used when requesting to become member of a multicast
-%% group with a call to setopt. Example:
-%%
-%% socket:setopt(Socket, ip, add_membership, #{multiaddr => Addr,
-%% interface => any}).
-%%
-%% Its also used when removing from a multicast group. Example:
-%%
-%% socket:setopt(Socket, ip, drop_membership, #{multiaddr => Addr,
-%% interface => any}).
-%%
-
--type ip_mreq() :: #{multiaddr := ip4_address(),
- interface := any | ip4_address()}.
-%% -type ip_mreqn() :: #{multiaddr := ip4_address(),
-%% address := any | ip4_address(),
-%% ifindex := integer()}.
-
--type ip_mreq_source() :: #{multiaddr := ip4_address(),
- interface := ip4_address(),
- sourceaddr := ip4_address()}.
-
--type ip_pmtudisc() :: want | dont | do | probe.
-
-%% multiaddr: Multicast group address
-%% interface: Address of local interface
-%% mode: Filter mode
-%% slist: List of source addresses
--type ip_msfilter_mode() :: include | exclude.
-
--type ip_msfilter() :: #{multiaddr := ip4_address(),
- interface := ip4_address(),
- mode := ip_msfilter_mode(),
- slist := [ip4_address()]}.
-
--type ipv6_mreq() :: #{multiaddr := ip6_address(),
- interface := non_neg_integer()}.
-
--type ipv6_pmtudisc() :: ip_pmtudisc().
-
--type ipv6_pktinfo() :: #{
- addr := ip6_address(),
- ifindex := integer()
- }.
-
-
--type sctp_assoc_id() :: int32().
--type sctp_sndrcvinfo() :: #{
- stream := uint16(),
- ssn := uint16(),
- flags := uint16(),
- ppid := uint16(),
- context := uint16(),
- timetolive := uint16(),
- tsn := uint16(),
- cumtsn := uint16(),
- assoc_id := sctp_assoc_id()
- }.
-
--type sctp_event_subscribe() :: #{data_in := boolean(),
- association := boolean(),
- address := boolean(),
- send_failure := boolean(),
- peer_error := boolean(),
- shutdown := boolean(),
- partial_delivery := boolean(),
- adaptation_layer := boolean(),
- authentication := boolean(),
- sender_dry := boolean()}.
-
--type sctp_assocparams() :: #{assoc_id := sctp_assoc_id(),
- max_rxt := uint16(),
- num_peer_dests := uint16(),
- peer_rwnd := uint32(),
- local_rwnd := uint32(),
- cookie_life := uint32()}.
-
--type sctp_initmsg() :: #{num_outstreams := uint16(),
- max_instreams := uint16(),
- max_attempts := uint16(),
- max_init_timeo := uint16()
- }.
-
--type sctp_rtoinfo() :: #{assoc_id := sctp_assoc_id(),
- initial := uint32(),
- max := uint32(),
- min := uint32()}.
-
--type sockaddr_un() :: #{family := local,
- path := binary() | string()}.
--type sockaddr_in4() :: #{family := inet,
- port := port_number(),
- %% The 'broadcast' here is the "limited broadcast"
- addr := any | broadcast | loopback | ip4_address()}.
--type sockaddr_in6() :: #{family := inet6,
- port := port_number(),
- addr := any | loopback | ip6_address(),
- flowinfo := in6_flow_info(),
- scope_id := in6_scope_id()}.
--type sockaddr_ll() :: #{family := packet,
- protocol := non_neg_integer(),
- ifindex := integer(),
- pkttype := packet_type(),
- hatype := non_neg_integer(),
- addr := binary()}.
--type packet_type() :: host | broadcast | multicast | otherhost |
- outgoing | loopback | user | kernel | fastroute |
- non_neg_integer().
--type sockaddr() :: sockaddr_in4() |
- sockaddr_in6() |
- sockaddr_un() |
- sockaddr_ll().
-
--define(SOCKADDR_IN4_DEFAULTS(A), #{port => 0,
- addr => A}).
--define(SOCKADDR_IN4_DEFAULTS, ?SOCKADDR_IN4_DEFAULTS(any)).
--define(SOCKADDR_IN4_DEFAULT(A), (?SOCKADDR_IN4_DEFAULTS(A))#{family => inet}).
--define(SOCKADDR_IN6_DEFAULTS(A), #{port => 0,
- addr => A,
- flowinfo => 0,
- scope_id => 0}).
--define(SOCKADDR_IN6_DEFAULTS, ?SOCKADDR_IN6_DEFAULTS(any)).
--define(SOCKADDR_IN6_DEFAULT(A), (?SOCKADDR_IN6_DEFAULTS(A))#{family => inet6}).
-
-%% otp - This option is internal to our (OTP) implementation.
-%% socket - The socket layer (SOL_SOCKET).
-%% ip - The IP layer (SOL_IP or is it IPPROTO_IP?).
-%% ipv6 - The IPv6 layer (SOL_IPV6).
-%% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP).
-%% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP).
-%% sctp - The SCTP (Stream Control Transmission Protocol) layer (IPPROTO_SCTP).
-%% Int - Raw level, sent down and used "as is".
-%% Its up to the caller to make sure this is correct!
--type sockopt_level() :: otp |
- socket |
- ip | ipv6 | tcp | udp | sctp |
- non_neg_integer().
-
-%% There are some options that are 'read-only'.
-%% Should those be included here or in a special list?
-%% Should we just document it and leave it to the user?
-%% Or catch it in the encode functions?
-%% A setopt for a readonly option leads to einval?
-%% Do we really need a sndbuf?
-
--type otp_socket_option() :: debug |
- iow |
- controlling_process |
- rcvbuf | % sndbuf |
- rcvctrlbuf |
- sndctrlbuf |
- fd.
-%% Shall we have special treatment of linger??
-%% read-only options:
-%% domain | protocol | type.
-%% FreeBSD (only?): acceptfilter
--type socket_option() :: acceptconn |
- acceptfilter |
- bindtodevice |
- broadcast |
- busy_poll |
- debug |
- domain |
- dontroute |
- error |
- keepalive |
- linger |
- mark |
- oobinline |
- passcred |
- peek_off |
- peercred |
- priority |
- protocol |
- rcvbuf |
- rcvbufforce |
- rcvlowat |
- rcvtimeo |
- reuseaddr |
- reuseport |
- rxq_ovfl |
- setfib |
- sndbuf |
- sndbufforce |
- sndlowat |
- sndtimeo |
- timestamp |
- type.
-
-%% Read-only options:
-%% mtu
-%%
-%% Options only valid on FreeBSD?:
-%% dontfrag
-%% Options only valid for RAW sockets:
-%% nodefrag (linux only?)
--type ip_socket_option() :: add_membership |
- add_source_membership |
- block_source |
- dontfrag |
- drop_membership |
- drop_source_membership |
- freebind |
- hdrincl |
- minttl |
- msfilter |
- mtu |
- mtu_discover |
- multicast_all |
- multicast_if |
- multicast_loop |
- multicast_ttl |
- nodefrag |
- options |
- pktinfo |
- recverr |
- recvif |
- recvdstaddr |
- recvopts |
- recvorigdstaddr |
- recvtos |
- recvttl |
- retopts |
- router_alert |
- sndsrcaddr |
- tos |
- transparent |
- ttl |
- unblock_source.
--type ipv6_socket_option() ::
- addrform |
- add_membership |
- authhdr |
- auth_level |
- checksum |
- drop_membership |
- dstopts |
- esp_trans_level |
- esp_network_level |
- faith |
- flowinfo |
- hopopts |
- ipcomp_level |
- join_group |
- leave_group |
- mtu |
- mtu_discover |
- multicast_hops |
- multicast_if |
- multicast_loop |
- portrange |
- pktoptions |
- recverr |
- recvhoplimit | hoplimit |
- recvpktinfo | pktinfo |
- recvtclass |
- router_alert |
- rthdr |
- tclass |
- unicast_hops |
- use_min_mtu |
- v6only.
-
--type tcp_socket_option() :: congestion |
- cork |
- info |
- keepcnt |
- keepidle |
- keepintvl |
- maxseg |
- md5sig |
- nodelay |
- noopt |
- nopush |
- syncnt |
- user_timeout.
-
--type udp_socket_option() :: cork.
-
--type sctp_socket_option() ::
- adaption_layer |
- associnfo |
- auth_active_key |
- auth_asconf |
- auth_chunk |
- auth_key |
- auth_delete_key |
- autoclose |
- context |
- default_send_params |
- delayed_ack_time |
- disable_fragments |
- hmac_ident |
- events |
- explicit_eor |
- fragment_interleave |
- get_peer_addr_info |
- initmsg |
- i_want_mapped_v4_addr |
- local_auth_chunks |
- maxseg |
- maxburst |
- nodelay |
- partial_delivery_point |
- peer_addr_params |
- peer_auth_chunks |
- primary_addr |
- reset_streams |
- rtoinfo |
- set_peer_primary_addr |
- status |
- use_ext_recvinfo.
-
--type raw_socket_option() :: filter.
-
-%% -type plain_socket_option() :: integer().
-%% -type sockopt() :: otp_socket_option() |
-%% socket_option() |
-%% ip_socket_option() |
-%% ipv6_socket_option() |
-%% tcp_socket_option() |
-%% udp_socket_option() |
-%% sctp_socket_option() |
-%% raw_socket_option() |
-%% plain_socket_option().
-
--record(socket, {ref :: reference()}).
-
--opaque socket() :: #socket{}.
-
--type send_flags() :: [send_flag()].
--type send_flag() :: confirm |
- dontroute |
- eor |
- more |
- nosignal |
- oob.
-
-%% Note that not all of these flags are useful for every recv function!
-%%
--type recv_flags() :: [recv_flag()].
--type recv_flag() :: cmsg_cloexec |
- errqueue |
- oob |
- peek |
- trunc.
-
--type shutdown_how() :: read | write | read_write.
-
--type msghdr_flag() :: ctrunc | eor | errqueue | oob | trunc.
--type msghdr_flags() :: [msghdr_flag()].
--type msghdr() :: #{
- %% *Optional* target address
- %% Used on an unconnected socket to specify the
- %% target address for a datagram.
- addr := sockaddr(),
-
- iov := [binary()],
-
- %% The maximum size of the control buffer is platform
- %% specific. It is the users responsibility to ensure
- %% that its not exceeded.
- ctrl := [cmsghdr_recv()] | [cmsghdr_send()],
-
- %% Only valid with recvmsg
- flags := msghdr_flags()
- }.
-%% We are able to (completely) decode *some* control message headers.
-%% Even if we are able to decode both level and type, we may not be
-%% able to decode the data, in which case it will be a binary.
-
--type cmsghdr_level() :: socket | ip | ipv6 | integer().
--type cmsghdr_type() :: credentials |
- hoplevel |
- origdstaddr |
- pktinfo |
- recvtos |
- rights |
- timestamp |
- tos |
- ttl |
- integer().
--type cmsghdr_recv() ::
- #{level := socket, type := timestamp, data := timeval()} |
- #{level := socket, type := rights, data := binary()} |
- #{level := socket, type := credentials, data := binary()} |
- #{level := socket, type := integer(), data := binary()} |
- #{level := ip, type := tos, data := ip_tos()} |
- #{level := ip, type := recvtos, data := ip_tos()} |
- #{level := ip, type := ttl, data := integer()} |
- #{level := ip, type := recvttl, data := integer()} |
- #{level := ip, type := pktinfo, data := ip_pktinfo()} |
- #{level := ip, type := origdstaddr, data := sockaddr_in4()} |
- #{level := ip, type := recverr, data := extended_err() | binary()} |
- #{level := ip, type := integer(), data := binary()} |
- #{level := ipv6, type := hoplevel, data := integer()} |
- #{level := ipv6, type := pktinfo, data := ipv6_pktinfo()} |
- #{level := ipv6, type := recverr, data := extended_err() | binary()} |
- #{level := ipv6, type := tclass, data := integer()} |
- #{level := ipv6, type := integer(), data := binary()} |
- #{level := integer(), type := integer(), data := binary()}.
--type cmsghdr_send() ::
- #{level := socket, type := timestamp, data := binary()} |
- #{level := socket, type := rights, data := binary()} |
- #{level := socket, type := credentials, data := binary()} |
- #{level := socket, type := integer(), data := binary()} |
- #{level := ip, type := tos, data := ip_tos() | binary()} |
- #{level := ip, type := ttl, data := integer() | binary()} |
- #{level := ip, type := integer(), data := binary()} |
- #{level := ipv6, type := tclass, data := integer()} |
- #{level := ipv6, type := integer(), data := binary()} |
- #{level := udp, type := integer(), data := binary()} |
- #{level := integer(), type := integer(), data := binary()}.
-
--type ee_origin() :: none | local | icmp | icmp6 | uint8().
--type icmp_dest_unreach() :: net_unreach | host_unreach | port_unreach | frag_needed |
- net_unknown | host_unknown | uint8().
--type icmpv6_dest_unreach() :: noroute | adm_prohibited | not_neighbour | addr_unreach |
- port_unreach | policy_fail | reject_route | uint8().
--type extended_err() ::
- #{error := term(),
- origin := icmp,
- type := dest_unreach,
- code := icmp_dest_unreach(),
- info := uint32(),
- data := uint32(),
- offender := undefined | sockaddr()} |
- #{error := term(),
- origin := icmp,
- type := time_exceeded | uint8(),
- code := uint8(),
- info := uint32(),
- data := uint32(),
- offender := undefined | sockaddr()} |
- #{error := term(),
- origin := icmp6,
- type := dest_unreach,
- code := icmpv6_dest_unreach(),
- info := uint32(),
- data := uint32(),
- offender := undefined | sockaddr()} |
- #{error := term(),
- origin := icmp6,
- type := pkt_toobig | time_exceeded | uint8(),
- code := uint8(),
- info := uint32(),
- data := uint32(),
- offender := undefined | sockaddr()} |
- #{error := term(),
- origin := ee_origin(),
- type := uint8(),
- code := uint8(),
- info := uint32(),
- data := uint32(),
- offender := undefined | sockaddr()}.
-
--opaque select_tag() :: atom().
--opaque select_ref() :: reference().
-
-%% -record(select_info, {tag :: select_tag(), ref :: select_ref()}).
-
--type select_info() :: {select_info, select_tag(), select_ref()}.
-
--define(SELECT_INFO(T, R), {select_info, T, R}).
--define(SELECT(T, R), {select, ?SELECT_INFO(T, R)}).
-
-
-%% This is used in messages sent from the nif-code to erlang processes:
-%%
-%% {?ESOCK_TAG, Socket :: socket(), Tag :: atom(), Info :: term()}
-%%
--define(ESOCK_TAG, '$socket').
-
--define(ESOCK_DOMAIN_LOCAL, 1).
--define(ESOCK_DOMAIN_UNIX, ?ESOCK_DOMAIN_LOCAL).
--define(ESOCK_DOMAIN_INET, 2).
--define(ESOCK_DOMAIN_INET6, 3).
-
--define(ESOCK_TYPE_STREAM, 1).
--define(ESOCK_TYPE_DGRAM, 2).
--define(ESOCK_TYPE_RAW, 3).
-%% -define(ESOCK_TYPE_RDM, 4).
--define(ESOCK_TYPE_SEQPACKET, 5).
-
--define(ESOCK_PROTOCOL_DEFAULT, 0).
--define(ESOCK_PROTOCOL_IP, 1).
--define(ESOCK_PROTOCOL_TCP, 2).
--define(ESOCK_PROTOCOL_UDP, 3).
--define(ESOCK_PROTOCOL_SCTP, 4).
--define(ESOCK_PROTOCOL_ICMP, 5).
--define(ESOCK_PROTOCOL_IGMP, 6).
-
--define(ESOCK_LISTEN_BACKLOG_DEFAULT, 5).
-
--define(ESOCK_ACCEPT_TIMEOUT_DEFAULT, infinity).
-
--define(ESOCK_SEND_FLAG_CONFIRM, 0).
--define(ESOCK_SEND_FLAG_DONTROUTE, 1).
--define(ESOCK_SEND_FLAG_EOR, 2).
--define(ESOCK_SEND_FLAG_MORE, 3).
--define(ESOCK_SEND_FLAG_NOSIGNAL, 4).
--define(ESOCK_SEND_FLAG_OOB, 5).
-
--define(ESOCK_SEND_FLAGS_DEFAULT, []).
--define(ESOCK_SEND_TIMEOUT_DEFAULT, infinity).
--define(ESOCK_SENDTO_FLAGS_DEFAULT, []).
--define(ESOCK_SENDTO_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
--define(ESOCK_SENDMSG_FLAGS_DEFAULT, []).
--define(ESOCK_SENDMSG_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
-
--define(ESOCK_RECV_FLAG_CMSG_CLOEXEC, 0).
--define(ESOCK_RECV_FLAG_ERRQUEUE, 1).
--define(ESOCK_RECV_FLAG_OOB, 2).
--define(ESOCK_RECV_FLAG_PEEK, 3).
--define(ESOCK_RECV_FLAG_TRUNC, 4).
-
--define(ESOCK_RECV_FLAGS_DEFAULT, []).
--define(ESOCK_RECV_TIMEOUT_DEFAULT, infinity).
-
--define(ESOCK_OPT_LEVEL_OTP, 0).
--define(ESOCK_OPT_LEVEL_SOCKET, 1).
--define(ESOCK_OPT_LEVEL_IP, 2).
--define(ESOCK_OPT_LEVEL_IPV6, 3).
--define(ESOCK_OPT_LEVEL_TCP, 4).
--define(ESOCK_OPT_LEVEL_UDP, 5).
--define(ESOCK_OPT_LEVEL_SCTP, 6).
-
-%% *** OTP (socket) options
--define(ESOCK_OPT_OTP_DEBUG, 1).
--define(ESOCK_OPT_OTP_IOW, 2).
--define(ESOCK_OPT_OTP_CTRL_PROC, 3).
--define(ESOCK_OPT_OTP_RCVBUF, 4).
-%%-define(ESOCK_OPT_OTP_SNDBUF, 5).
--define(ESOCK_OPT_OTP_RCVCTRLBUF, 6).
--define(ESOCK_OPT_OTP_SNDCTRLBUF, 7).
--define(ESOCK_OPT_OTP_FD, 8).
--define(ESOCK_OPT_OTP_META, 9).
--define(ESOCK_OPT_OTP_DOMAIN, 16#FF01). % INTERNAL
--define(ESOCK_OPT_OTP_TYPE, 16#FF02). % INTERNAL
--define(ESOCK_OPT_OTP_PROTOCOL, 16#FF03). % INTERNAL
--define(ESOCK_OPT_OTP_DTP, 16#FF04). % INTERNAL
-
-%% *** SOCKET (socket) options
--define(ESOCK_OPT_SOCK_ACCEPTCONN, 1).
-%% -define(ESOCK_OPT_SOCK_ACCEPTFILTER, 2). % FreeBSD
--define(ESOCK_OPT_SOCK_BINDTODEVICE, 3).
--define(ESOCK_OPT_SOCK_BROADCAST, 4).
-%% -define(ESOCK_OPT_SOCK_BUSY_POLL, 5).
--define(ESOCK_OPT_SOCK_DEBUG, 6).
--define(ESOCK_OPT_SOCK_DOMAIN, 7).
--define(ESOCK_OPT_SOCK_DONTROUTE, 8).
-%% -define(ESOCK_OPT_SOCK_ERROR, 9).
--define(ESOCK_OPT_SOCK_KEEPALIVE, 10).
--define(ESOCK_OPT_SOCK_LINGER, 11).
-%% -define(ESOCK_OPT_SOCK_MARK, 12).
--define(ESOCK_OPT_SOCK_OOBINLINE, 13).
--define(ESOCK_OPT_SOCK_PASSCRED, 14).
--define(ESOCK_OPT_SOCK_PEEK_OFF, 15).
-%% -define(ESOCK_OPT_SOCK_PEERCRED, 16).
--define(ESOCK_OPT_SOCK_PRIORITY, 17).
--define(ESOCK_OPT_SOCK_PROTOCOL, 18).
--define(ESOCK_OPT_SOCK_RCVBUF, 19).
-%% -define(ESOCK_OPT_SOCK_RCVBUFFORCE, 20).
--define(ESOCK_OPT_SOCK_RCVLOWAT, 21).
--define(ESOCK_OPT_SOCK_RCVTIMEO, 22).
--define(ESOCK_OPT_SOCK_REUSEADDR, 23).
--define(ESOCK_OPT_SOCK_REUSEPORT, 24).
-%% -define(ESOCK_OPT_SOCK_RXQ_OVFL, 25).
-%% -define(ESOCK_OPT_SOCK_SETFIB, 26). % FreeBSD
--define(ESOCK_OPT_SOCK_SNDBUF, 27).
-%% -define(ESOCK_OPT_SOCK_SNDBUFFORCE, 28).
--define(ESOCK_OPT_SOCK_SNDLOWAT, 29).
--define(ESOCK_OPT_SOCK_SNDTIMEO, 30).
--define(ESOCK_OPT_SOCK_TIMESTAMP, 31).
--define(ESOCK_OPT_SOCK_TYPE, 32).
-
-%% *** IP (socket) options
--define(ESOCK_OPT_IP_ADD_MEMBERSHIP, 1).
--define(ESOCK_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2).
--define(ESOCK_OPT_IP_BLOCK_SOURCE, 3).
-%% -define(ESOCK_OPT_IP_DONTFRAG, 4). % FreeBSD
--define(ESOCK_OPT_IP_DROP_MEMBERSHIP, 5).
--define(ESOCK_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6).
--define(ESOCK_OPT_IP_FREEBIND, 7).
--define(ESOCK_OPT_IP_HDRINCL, 8).
--define(ESOCK_OPT_IP_MINTTL, 9).
--define(ESOCK_OPT_IP_MSFILTER, 10).
--define(ESOCK_OPT_IP_MTU, 11).
--define(ESOCK_OPT_IP_MTU_DISCOVER, 12).
--define(ESOCK_OPT_IP_MULTICAST_ALL, 13).
--define(ESOCK_OPT_IP_MULTICAST_IF, 14).
--define(ESOCK_OPT_IP_MULTICAST_LOOP, 15).
--define(ESOCK_OPT_IP_MULTICAST_TTL, 16).
--define(ESOCK_OPT_IP_NODEFRAG, 17).
-%% -define(ESOCK_OPT_IP_OPTIONS, 18). % FreeBSD
--define(ESOCK_OPT_IP_PKTINFO, 19).
--define(ESOCK_OPT_IP_RECVDSTADDR, 20). % FreeBSD
--define(ESOCK_OPT_IP_RECVERR, 21).
--define(ESOCK_OPT_IP_RECVIF, 22).
--define(ESOCK_OPT_IP_RECVOPTS, 23).
--define(ESOCK_OPT_IP_RECVORIGDSTADDR, 24).
--define(ESOCK_OPT_IP_RECVTOS, 25).
--define(ESOCK_OPT_IP_RECVTTL, 26).
--define(ESOCK_OPT_IP_RETOPTS, 27).
--define(ESOCK_OPT_IP_ROUTER_ALERT, 28).
--define(ESOCK_OPT_IP_SENDSRCADDR, 29). % FreeBSD
--define(ESOCK_OPT_IP_TOS, 30).
--define(ESOCK_OPT_IP_TRANSPARENT, 31).
--define(ESOCK_OPT_IP_TTL, 32).
--define(ESOCK_OPT_IP_UNBLOCK_SOURCE, 33).
-
-%% *** IPv6 (socket) options
--define(ESOCK_OPT_IPV6_ADDRFORM, 1).
--define(ESOCK_OPT_IPV6_ADD_MEMBERSHIP, 2).
--define(ESOCK_OPT_IPV6_AUTHHDR, 3). % Obsolete?
-%% -define(ESOCK_OPT_IPV6_AUTH_LEVEL, 4). % FreeBSD
-%% -define(ESOCK_OPT_IPV6_CHECKSUM, 5). % FreeBSD
--define(ESOCK_OPT_IPV6_DROP_MEMBERSHIP, 6).
--define(ESOCK_OPT_IPV6_DSTOPTS, 7).
-%% -define(ESOCK_OPT_IPV6_ESP_NETWORK_LEVEL, 8). % FreeBSD
-%% -define(ESOCK_OPT_IPV6_ESP_TRANS_LEVEL, 9). % FreeBSD
-%% -define(ESOCK_OPT_IPV6_FAITH, 10). % FreeBSD
--define(ESOCK_OPT_IPV6_FLOWINFO, 11).
--define(ESOCK_OPT_IPV6_HOPLIMIT, 12).
--define(ESOCK_OPT_IPV6_HOPOPTS, 13).
-%% -define(ESOCK_OPT_IPV6_IPCOMP_LEVEL, 14). % FreeBSD
-%% -define(ESOCK_OPT_IPV6_JOIN_GROUP, 15). % FreeBSD
-%% -define(ESOCK_OPT_IPV6_LEAVE_GROUP, 16). % FreeBSD
--define(ESOCK_OPT_IPV6_MTU, 17).
--define(ESOCK_OPT_IPV6_MTU_DISCOVER, 18).
--define(ESOCK_OPT_IPV6_MULTICAST_HOPS, 19).
--define(ESOCK_OPT_IPV6_MULTICAST_IF, 20).
--define(ESOCK_OPT_IPV6_MULTICAST_LOOP, 21).
-%% -define(ESOCK_OPT_IPV6_PORTRANGE, 22). % FreeBSD
-%% -define(ESOCK_OPT_IPV6_PKTOPTIONS, 23). % FreeBSD
--define(ESOCK_OPT_IPV6_RECVERR, 24).
--define(ESOCK_OPT_IPV6_RECVHOPLIMIT, 25).
--define(ESOCK_OPT_IPV6_RECVPKTINFO, 26). % On FreeBSD: PKTINFO
--define(ESOCK_OPT_IPV6_RECVTCLASS, 27).
--define(ESOCK_OPT_IPV6_ROUTER_ALERT, 28).
--define(ESOCK_OPT_IPV6_RTHDR, 29).
--define(ESOCK_OPT_IPV6_TCLASS, 30). % FreeBSD
--define(ESOCK_OPT_IPV6_UNICAST_HOPS, 31).
-%% -define(ESOCK_OPT_IPV6_USE_MIN_MTU, 32). % FreeBSD
--define(ESOCK_OPT_IPV6_V6ONLY, 33).
-
-%% *** TCP (socket) options
--define(ESOCK_OPT_TCP_CONGESTION, 1).
--define(ESOCK_OPT_TCP_CORK, 2).
-%% -define(ESOCK_OPT_TCP_INFO, 3).
-%% -define(ESOCK_OPT_TCP_KEEPCNT, 4).
-%% -define(ESOCK_OPT_TCP_KEEPIDLE, 5).
-%% -define(ESOCK_OPT_TCP_KEEPINTVL, 6).
--define(ESOCK_OPT_TCP_MAXSEG, 7).
-%% -define(ESOCK_OPT_TCP_MD5SIG, 8).
--define(ESOCK_OPT_TCP_NODELAY, 9).
-%% -define(ESOCK_OPT_TCP_NOOPT, 10).
-%% -define(ESOCK_OPT_TCP_NOPUSH, 11).
-%% -define(ESOCK_OPT_TCP_SYNCNT, 12).
-%% -define(ESOCK_OPT_TCP_USER_TIMEOUT, 13).
-
-%% *** UDP (socket) options
--define(ESOCK_OPT_UDP_CORK, 1).
-
-%% *** SCTP (socket) options
-%% -define(ESOCK_OPT_SCTP_ADAPTION_LAYER, 1).
--define(ESOCK_OPT_SCTP_ASSOCINFO, 2).
-%% -define(ESOCK_OPT_SCTP_AUTH_ACTIVE_KEY, 3).
-%% -define(ESOCK_OPT_SCTP_AUTH_ASCONF, 4).
-%% -define(ESOCK_OPT_SCTP_AUTH_CHUNK, 5).
-%% -define(ESOCK_OPT_SCTP_AUTH_KEY, 6).
-%% -define(ESOCK_OPT_SCTP_AUTH_DELETE_KEY, 7).
--define(ESOCK_OPT_SCTP_AUTOCLOSE, 8).
-%% -define(ESOCK_OPT_SCTP_CONTEXT, 9).
-%% -define(ESOCK_OPT_SCTP_DEFAULT_SEND_PARAMS, 10).
-%% -define(ESOCK_OPT_SCTP_DELAYED_ACK_TIME, 11).
--define(ESOCK_OPT_SCTP_DISABLE_FRAGMENTS, 12).
-%% -define(ESOCK_OPT_SCTP_HMAC_IDENT, 13).
--define(ESOCK_OPT_SCTP_EVENTS, 14).
-%% -define(ESOCK_OPT_SCTP_EXPLICIT_EOR, 15).
-%% -define(ESOCK_OPT_SCTP_FRAGMENT_INTERLEAVE, 16).
-%% -define(ESOCK_OPT_SCTP_GET_PEER_ADDR_INFO, 17).
--define(ESOCK_OPT_SCTP_INITMSG, 18).
-%% -define(ESOCK_OPT_SCTP_I_WANT_MAPPED_V4_ADDR, 19).
-%% -define(ESOCK_OPT_SCTP_LOCAL_AUTH_CHUNKS, 20).
--define(ESOCK_OPT_SCTP_MAXSEG, 21).
-%% -define(ESOCK_OPT_SCTP_MAXBURST, 22).
--define(ESOCK_OPT_SCTP_NODELAY, 23).
-%% -define(ESOCK_OPT_SCTP_PARTIAL_DELIVERY_POINT, 24).
-%% -define(ESOCK_OPT_SCTP_PEER_ADDR_PARAMS, 25).
-%% -define(ESOCK_OPT_SCTP_PEER_AUTH_CHUNKS, 26).
-%% -define(ESOCK_OPT_SCTP_PRIMARY_ADDR, 27).
-%% -define(ESOCK_OPT_SCTP_RESET_STREAMS, 28).
--define(ESOCK_OPT_SCTP_RTOINFO, 29).
-%% -define(ESOCK_OPT_SCTP_SET_PEER_PRIMARY_ADDR, 30).
-%% -define(ESOCK_OPT_SCTP_STATUS, 31).
-%% -define(ESOCK_OPT_SCTP_USE_EXT_RECVINFO, 32).
-
--define(ESOCK_SHUTDOWN_HOW_READ, 0).
--define(ESOCK_SHUTDOWN_HOW_WRITE, 1).
--define(ESOCK_SHUTDOWN_HOW_READ_WRITE, 2).
-
-
--define(ESOCK_SUPPORTS_OPTIONS, 16#0001).
--define(ESOCK_SUPPORTS_SCTP, 16#0002).
--define(ESOCK_SUPPORTS_IPV6, 16#0003).
--define(ESOCK_SUPPORTS_LOCAL, 16#0004).
--define(ESOCK_SUPPORTS_SEND_FLAGS, 16#0005).
--define(ESOCK_SUPPORTS_RECV_FLAGS, 16#0006).
-
-
-%% ===========================================================================
-%%
-%% Administrative and utility API
-%%
-%% ===========================================================================
-
--spec on_load() -> ok.
-
-%% Should we require that the Extra arg is a map?
-on_load() ->
- on_load(#{}).
-
--spec on_load(Extra) -> ok when
- Extra :: map().
-
-on_load(Extra) ->
- %% This is spawned as a system process to prevent init:restart/0 from
- %% killing it.
- Pid = erts_internal:spawn_system_process(?REGISTRY, start, []),
- ok = erlang:load_nif(atom_to_list(?MODULE), Extra#{registry => Pid}).
-
-
-%% *** number_of ***
-%%
-%% Interface function to the socket registry
-%% returns the number of existing (and "alive") sockets.
-%%
--spec number_of() -> non_neg_integer().
-
-number_of() ->
- ?REGISTRY:number_of().
-
-
-%% *** which_sockets/0,1 ***
-%%
-%% Interface function to the socket registry
-%% Returns a list of all the sockets, accoring to the filter rule.
-%%
--spec which_sockets() -> [socket()].
-
-which_sockets() ->
- ?REGISTRY:which_sockets(fun(_) -> true end).
-
--spec which_sockets(FilterRule) -> [socket()] when
- FilterRule :: inet | inet6 |
- stream | dgram | seqpacket |
- sctp | tcp | udp |
- pid() |
- fun((socket_info()) -> boolean()).
-
-which_sockets(Domain)
- when ((Domain =:= inet) orelse (Domain =:= inet6)) ->
- ?REGISTRY:which_sockets(fun(#{domain := D}) when (D =:= Domain) -> true;
- (_) -> false end);
-which_sockets(Type)
- when ((Type =:= stream) orelse (Type =:= dgram) orelse (Type =:= seqpacket)) ->
- ?REGISTRY:which_sockets(fun(#{type := T}) when (T =:= Type) -> true;
- (_) -> false end);
-which_sockets(Proto)
- when ((Proto =:= sctp) orelse (Proto =:= tcp) orelse (Proto =:= udp)) ->
- ?REGISTRY:which_sockets(fun(#{protocol := P}) when (P =:= Proto) -> true;
- (_) -> false end);
-which_sockets(CTRL)
- when is_pid(CTRL) ->
- ?REGISTRY:which_sockets(fun(#{ctrl := C}) when (C =:= CTRL) -> true;
- (_) -> false end);
-which_sockets(Filter) when is_function(Filter, 1) ->
- ?REGISTRY:which_sockets(Filter).
-
-
-
-
-
--spec info() -> map().
-
-info() ->
- nif_info().
-
-
--spec debug(D) -> ok when
- D :: boolean().
-
-debug(D) when is_boolean(D) ->
- command(#{command => debug,
- data => D}).
-
-
--spec command(Command) -> ok when
- Command :: debug_command().
-
-command(#{command := debug,
- data := Dbg} = Command) when is_boolean(Dbg) ->
- nif_command(Command).
-
-
-
-%% ===========================================================================
-%%
-%% info - Get miscellaneous information about a socket.
-%%
-%% Generates a list of various info about the socket, such as counter values.
-%%
-%% Do *not* call this function often.
-%%
-%% ===========================================================================
-
--spec info(Socket) -> socket_info() when
- Socket :: socket().
-
-info(#socket{ref = SockRef}) ->
- nif_info(SockRef).
-
-
-
-%% ===========================================================================
-%%
-%% supports - get information about what the platform "supports".
-%%
-%% Generates a list of various info about what the plaform can support.
-%% The most obvious case is 'options'.
-%%
-%% Each item in a 'supports'-list will appear only *one* time.
-%%
-%% ===========================================================================
-
--type supports_options_socket() :: [{socket_option(), boolean()}].
--type supports_options_ip() :: [{ip_socket_option(), boolean()}].
--type supports_options_ipv6() :: [{ipv6_socket_option(), boolean()}].
--type supports_options_tcp() :: [{tcp_socket_option(), boolean()}].
--type supports_options_udp() :: [{udp_socket_option(), boolean()}].
--type supports_options_sctp() :: [{sctp_socket_option(), boolean()}].
--type supports_options() :: [{socket, supports_options_socket()} |
- {ip, supports_options_ip()} |
- {ipv6, supports_options_ipv6()} |
- {tcp, supports_options_tcp()} |
- {udp, supports_options_udp()} |
- {sctp, supports_options_sctp()}].
--type supports_send_flags() :: [{send_flag(), boolean()}].
--type supports_recv_flags() :: [{recv_flag(), boolean()}].
-
--spec supports() -> [{options, supports_options()} |
- {sctp, boolean()} |
- {ipv6, boolean()} |
- {local, boolean()} |
- {send_flags, supports_send_flags()} |
- {recv_flags, supports_recv_flags()}].
-
-supports() ->
- [{options, supports(options)},
- {sctp, supports(sctp)},
- {ipv6, supports(ipv6)},
- {local, supports(local)},
- {send_flags, supports(send_flags)},
- {recv_flags, supports(recv_flags)}].
-
-
--dialyzer({no_contracts, supports/1}).
--spec supports(options) -> supports_options();
- (sctp) -> boolean();
- (ipv6) -> boolean();
- (local) -> boolean();
- (send_flags) -> supports_send_flags();
- (recv_flags) -> supports_recv_flags();
- (Key1) -> false when
- Key1 :: term().
-
-supports(options) ->
- nif_supports(?ESOCK_SUPPORTS_OPTIONS);
-supports(sctp) ->
- nif_supports(?ESOCK_SUPPORTS_SCTP);
-supports(ipv6) ->
- nif_supports(?ESOCK_SUPPORTS_IPV6);
-supports(local) ->
- nif_supports(?ESOCK_SUPPORTS_LOCAL);
-supports(send_flags) ->
- nif_supports(?ESOCK_SUPPORTS_SEND_FLAGS);
-supports(recv_flags) ->
- nif_supports(?ESOCK_SUPPORTS_RECV_FLAGS);
-supports(_Key1) ->
- false.
-
--dialyzer({no_contracts, supports/2}).
--spec supports(options, socket) -> supports_options_socket();
- (options, ip) -> supports_options_ip();
- (options, ipv6) -> supports_options_ipv6();
- (options, tcp) -> supports_options_tcp();
- (options, udp) -> supports_options_udp();
- (options, sctp) -> supports_options_sctp();
- (send_flags, SendFlag :: send_flag()) -> boolean();
- (recv_flags, RecvFlag :: recv_flag()) -> boolean();
- (Key1, Key2) -> false when
- Key1 :: term(),
- Key2 :: term().
-
-supports(options, Level) ->
- proplists:get_value(Level, supports(options), false);
-supports(send_flags, Flag) ->
- proplists:get_value(Flag, supports(send_flags), false);
-supports(recv_flags, Flag) ->
- proplists:get_value(Flag, supports(recv_flags), false);
-supports(_Key1, _Level) ->
- false.
-
-
--spec supports(options, socket, Opt :: socket_option()) -> boolean();
- (options, ip, Opt :: ip_socket_option()) -> boolean();
- (options, ipv6, Opt :: ipv6_socket_option()) -> boolean();
- (options, tcp, Opt :: tcp_socket_option()) -> boolean();
- (options, udp, Opt :: udp_socket_option()) -> boolean();
- (options, sctp, Opt :: sctp_socket_option()) -> boolean();
- (Key1, Key2, Key3) -> false when
- Key1 :: term(),
- Key2 :: term(),
- Key3 :: term().
-
--dialyzer({no_contracts, supports/3}).
-supports(options, Level, Opt) ->
- case supports(options, Level) of
- S when is_list(S) ->
- proplists:get_value(Opt, S, false);
- _ ->
- false
- end;
-supports(_Key1, _Key2, _Key3) ->
- false.
-
-
-
-%% ===========================================================================
-%%
-%% The proper socket API
-%%
-%% ===========================================================================
-
-%% ===========================================================================
-%%
-%% <KOLLA>
-%%
-%% How do we handle the case when an fd has been created (somehow)
-%% and we shall create a socket "from it".
-%% Can we figure out Domain, Type and Protocol from fd?
-%% No we can't: For instance, its not possible to 'get' domain on FreeBSD.
-%%
-%% Instead, require: open(Domain, Stream, Proto, #{fd => FD}).
-%% The last argument, Extra, is used to provide the fd.
-%%
-%% </KOLLA>
-%%
-%%
-%% <KOLLA>
-%%
-%% Possibly add a "registry" in the nif, allowing the user processes to
-%% "register" themselves.
-%% The point of this would be to ensure that these processes are
-%% informed if the socket "terminates". Could possibly be used for
-%% other things? If gen_tcp implements the active feature using
-%% a reader process, the nif may need to know about this process,
-%% since its probably "hidden" from the socket "owner" (someone
-%% needs to handle it if it dies).
-%% Register under a name?
-%%
-%% The nif sets up a monitor to this process, and if it dies the socket
-%% is closed. It is also used if someone wants to monitor the socket.
-%%
-%% We may therefor need monitor function(s):
-%%
-%% socket:monitor(Socket)
-%% socket:demonitor(Socket)
-%%
-%% </KOLLA>
-%%
-
-
-
-%% ===========================================================================
-%%
-%% open - create an endpoint for communication
-%%
-%% Extra: Currently only used for netns
-%%
-
--spec open(Domain, Type) -> {ok, Socket} | {error, Reason} when
- Domain :: domain(),
- Type :: type(),
- Socket :: socket(),
- Reason :: term().
-
-open(Domain, Type) ->
- open(Domain, Type, default).
-
--spec open(Domain, Type, Protocol) -> {ok, Socket} | {error, Reason} when
- Domain :: domain(),
- Type :: type(),
- Protocol :: default | protocol(),
- Socket :: socket(),
- Reason :: term().
-
-open(Domain, Type, Protocol) ->
- open(Domain, Type, Protocol, #{}).
-
--spec open(Domain, Type, Protocol, Extra) -> {ok, Socket} | {error, Reason} when
- Domain :: domain(),
- Type :: type(),
- Protocol :: default | protocol(),
- Extra :: map(),
- Socket :: socket(),
- Reason :: term().
-
-open(Domain, Type, Protocol, Extra) when is_map(Extra) ->
- try
- begin
- EDomain = enc_domain(Domain),
- EType = enc_type(Type),
- EProtocol = enc_protocol(Protocol),
- nif_open(EDomain, EType, EProtocol, Extra)
- end
- of
- {ok, SockRef} ->
- Socket = #socket{ref = SockRef},
- {ok, Socket};
- {error, _} = ERROR ->
- ERROR
- catch
- throw:ERROR ->
- ERROR
- end.
-
-
-
-%% ===========================================================================
-%%
-%% bind - bind a name to a socket
-%%
-%% Note that Addr can only have the value of broadcast *if* Domain =:= inet!
-%%
-
--spec bind(Socket, Addr) -> {ok, Port} | {error, Reason} when
- Socket :: socket(),
- Addr :: any | broadcast | loopback | sockaddr(),
- Port :: port_number(),
- Reason :: term().
-
-bind(#socket{ref = SockRef}, Addr)
- when ((Addr =:= any) orelse
- (Addr =:= broadcast) orelse
- (Addr =:= loopback)) ->
- try
- case which_domain(SockRef) of
- inet ->
- nif_bind(SockRef, ?SOCKADDR_IN4_DEFAULT(Addr));
- inet6 when (Addr =:= any) orelse (Addr =:= loopback) ->
- nif_bind(SockRef, ?SOCKADDR_IN6_DEFAULT(Addr));
- Domain ->
- invalid_domain(Domain)
- end
- catch
- throw:ERROR ->
- ERROR
- end;
-bind(#socket{ref = SockRef} = _Socket, Addr) when is_map(Addr) ->
- try
- nif_bind(SockRef, ensure_sockaddr(Addr))
- catch
- throw:ERROR ->
- ERROR
- end.
-
-
-
-%% ===========================================================================
-%%
-%% bind - Add or remove a bind addresses on a socket
-%%
-%% Calling this function is only valid if the socket is:
-%% type = seqpacket
-%% protocol = sctp
-%%
-%% If the domain is inet, then all addresses *must* be IPv4.
-%% If the domain is inet6, the addresses can be aither IPv4 or IPv6.
-%%
-
--spec bind(Socket, Addrs, Action) -> ok | {error, Reason} when
- Socket :: socket(),
- Addrs :: [sockaddr()],
- Action :: add | remove,
- Reason :: term().
-
-bind(#socket{ref = SockRef}, Addrs, Action)
- when is_list(Addrs) andalso ((Action =:= add) orelse (Action =:= remove)) ->
- try
- begin
- {Domain, Type, Proto} = which_dtp(SockRef),
- ensure_domain(Domain, [inet, inet6]),
- ensure_type(Type, seqpacket),
- ensure_protocol(Proto, sctp),
- validate_addrs(Domain, Addrs),
- nif_bind(SockRef, Addrs, Action)
- end
- catch
- throw:ERROR ->
- ERROR
- end.
-
-ensure_domain(Domain, [Domain | _]) -> ok;
-ensure_domain(Domain, [_ | Domains]) -> ensure_domain(Domain, Domains);
-ensure_domain(Domain, []) -> invalid_domain(Domain).
-
-ensure_type(Type, Type) -> ok;
-ensure_type(Type, _) -> invalid_type(Type).
-
-ensure_protocol(Proto, Proto) -> ok;
-ensure_protocol(Proto, _) -> invalid_protocol(Proto).
-
-
-validate_addrs(inet = _Domain, Addrs) ->
- validate_inet_addrs(Addrs);
-validate_addrs(inet6 = _Domain, Addrs) ->
- validate_inet6_addrs(Addrs).
-
-validate_inet_addrs(Addrs) ->
- Validator = fun(#{family := inet,
- addrs := Addr}) when is_tuple(Addr) andalso
- (size(Addr) =:= 4) ->
- ok;
- (X) ->
- throw({error, {invalid_address, X}})
- end,
- lists:foreach(Validator, Addrs).
-
-validate_inet6_addrs(Addrs) ->
- Validator = fun(#{family := inet,
- addrs := Addr}) when is_tuple(Addr) andalso
- (size(Addr) =:= 4) ->
- ok;
- (#{family := inet6,
- addrs := Addr}) when is_tuple(Addr) andalso
- (size(Addr) =:= 8) ->
- ok;
- (X) ->
- throw({error, {invalid_address, X}})
- end,
- lists:foreach(Validator, Addrs).
-
-
-%% ===========================================================================
-%%
-%% connect - initiate a connection on a socket
-%%
-
--spec connect(Socket, SockAddr) -> ok | {error, Reason} when
- Socket :: socket(),
- SockAddr :: sockaddr(),
- Reason :: term().
-
-connect(Socket, SockAddr) ->
- connect(Socket, SockAddr, infinity).
-
--spec connect(Socket, SockAddr, nowait) ->
- ok | {select, SelectInfo} | {error, Reason} when
- Socket :: socket(),
- SockAddr :: sockaddr(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, SockAddr, Timeout) -> ok | {error, Reason} when
- Socket :: socket(),
- SockAddr :: sockaddr(),
- Timeout :: timeout(),
- Reason :: term().
-
-%% <KOLLA>
-%% Is it possible to connect with family = local for the (dest) sockaddr?
-%% </KOLLA>
-connect(#socket{ref = SockRef}, SockAddr, Timeout) ->
- try
- do_connect(SockRef, ensure_sockaddr(SockAddr), deadline(Timeout))
- catch
- throw:ERROR ->
- ERROR
- end.
-
-
-do_connect(SockRef, SockAddr, Deadline) ->
- case nif_connect(SockRef, ensure_sockaddr(SockAddr)) of
-
- ok ->
- %% Connected!
- ok;
-
-
- {ok, Ref} when (Deadline =:= nowait) ->
- %% Connecting, but the caller does not want to wait...
- ?SELECT(connect, Ref);
-
- {ok, Ref} ->
- %% Connecting...
- Timeout = timeout(Deadline),
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, select, Ref} ->
- nif_finalize_connection(SockRef)
- after Timeout ->
- cancel(SockRef, connect, Ref),
- {error, timeout}
- end;
-
-
- {error, _} = ERROR ->
- ERROR
- end.
-
-
-
-%% ===========================================================================
-%%
-%% listen - listen for connections on a socket
-%%
-
--spec listen(Socket) -> ok | {error, Reason} when
- Socket :: socket(),
- Reason :: term().
-
-listen(Socket) ->
- listen(Socket, ?ESOCK_LISTEN_BACKLOG_DEFAULT).
-
--spec listen(Socket, Backlog) -> ok | {error, Reason} when
- Socket :: socket(),
- Backlog :: pos_integer(),
- Reason :: term().
-
-listen(#socket{ref = SockRef}, Backlog)
- when (is_integer(Backlog) andalso (Backlog >= 0)) ->
- nif_listen(SockRef, Backlog).
-
-
-
-
-%% ===========================================================================
-%%
-%% accept, accept4 - accept a connection on a socket
-%%
-
--spec accept(LSocket) -> {ok, Socket} | {error, Reason} when
- LSocket :: socket(),
- Socket :: socket(),
- Reason :: term().
-
-accept(Socket) ->
- accept(Socket, ?ESOCK_ACCEPT_TIMEOUT_DEFAULT).
-
--spec accept(LSocket, nowait) ->
- {ok, Socket} |
- {select, SelectInfo} |
- {error, Reason} when
- LSocket :: socket(),
- Socket :: socket(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (LSocket, Timeout) -> {ok, Socket} | {error, Reason} when
- LSocket :: socket(),
- Timeout :: timeout(),
- Socket :: socket(),
- Reason :: term().
-
-accept(#socket{ref = LSockRef}, Timeout) ->
- try
- do_accept(LSockRef, deadline(Timeout))
- catch
- throw:ERROR ->
- ERROR
- end.
-
-do_accept(LSockRef, Deadline) ->
- AccRef = make_ref(),
- case nif_accept(LSockRef, AccRef) of
-
- {ok, SockRef} ->
- Socket = #socket{ref = SockRef},
- {ok, Socket};
-
-
- {error, eagain} when (Deadline =:= nowait) ->
- ?SELECT(accept, AccRef);
-
- {error, eagain} ->
- %% Each call is non-blocking, but even then it takes
- %% *some* time, so just to be sure, recalculate before
- %% the receive.
- Timeout = timeout(Deadline),
- receive
- {?ESOCK_TAG, #socket{ref = LSockRef}, select, AccRef} ->
- do_accept(LSockRef, Deadline);
-
- {?ESOCK_TAG, _Socket, abort, {AccRef, Reason}} ->
- {error, Reason}
-
- after Timeout ->
- cancel(LSockRef, accept, AccRef),
- {error, timeout}
- end;
-
-
- {error, _} = ERROR ->
- cancel(LSockRef, accept, AccRef), % Just to be on the safe side...
- ERROR
- end.
-
-
-
-%% ===========================================================================
-%%
-%% send, sendto, sendmsg - send a message on a socket
-%%
-
--spec send(Socket, Data) -> ok | {error, Reason} when
- Socket :: socket(),
- Data :: iodata(),
- Reason :: term().
-
-send(Socket, Data) ->
- send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
-
--spec send(Socket, Data, Flags) -> ok | {error, Reason} when
- Socket :: socket(),
- Data :: iodata(),
- Flags :: send_flags(),
- Reason :: term();
- (Socket, Data, Timeout :: nowait) ->
- ok |
- {ok, {binary(), SelectInfo}} |
- {select, SelectInfo} |
- {ok, {RestData, SelectInfo}} |
- {error, Reason} when
- Socket :: socket(),
- Data :: iodata(),
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: term();
- (Socket, Data, Timeout) -> ok | {error, Reason} when
- Socket :: socket(),
- Data :: iodata(),
- Timeout :: timeout(),
- Reason :: term().
-
-send(Socket, Data, Flags) when is_list(Flags) ->
- send(Socket, Data, Flags, ?ESOCK_SEND_TIMEOUT_DEFAULT);
-send(Socket, Data, Timeout) ->
- send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, Timeout).
-
--spec send(Socket, Data, Flags, nowait) -> ok |
- {select, SelectInfo} |
- {ok, {RestData, SelectInfo}} |
- {error, Reason} when
- Socket :: socket(),
- Data :: iodata(),
- Flags :: send_flags(),
- RestData :: binary(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, Data, Flags, Timeout) -> ok | {error, Reason} when
- Socket :: socket(),
- Data :: iodata(),
- Flags :: send_flags(),
- Timeout :: timeout(),
- Reason :: term().
-
-send(Socket, Data, Flags, Timeout) when is_list(Data) ->
- Bin = erlang:list_to_binary(Data),
- send(Socket, Bin, Flags, Timeout);
-send(#socket{ref = SockRef}, Data, Flags, Timeout)
- when is_binary(Data), is_list(Flags) ->
- To = undefined,
- try
- begin
- EFlags = enc_send_flags(Flags),
- Deadline = deadline(Timeout),
- send_common(SockRef, Data, To, EFlags, Deadline, send)
- end
- catch
- throw:ERROR ->
- ERROR
- end.
-
-send_common(SockRef, Data, To, EFlags, Deadline, SendName) ->
-
- SendRef = make_ref(),
-
- case
- case SendName of
- send ->
- nif_send(SockRef, SendRef, Data, EFlags);
- sendto ->
- nif_sendto(SockRef, SendRef, Data, To, EFlags)
- end
- of
-
- ok -> ok;
-
-
- {ok, Written} when (Deadline =:= nowait) ->
- %% We are partially done, but the user don't want to wait (here)
- %% for completion
- <<_:Written/binary, Rest/binary>> = Data,
- {ok, {Rest, ?SELECT_INFO(SendName, SendRef)}};
-
- {ok, Written} ->
- %% We are partially done, wait for continuation
- Timeout = timeout(Deadline),
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, select, SendRef}
- when (Written > 0) ->
- <<_:Written/binary, Rest/binary>> = Data,
- send_common(
- SockRef, Rest, To, EFlags, Deadline, SendName);
-
- {?ESOCK_TAG, #socket{ref = SockRef}, select, SendRef} ->
- send_common(
- SockRef, Data, To, EFlags, Deadline, SendName);
-
- {?ESOCK_TAG, _Socket, abort, {SendRef, Reason}} ->
- {error, {Reason, size(Data)}}
-
- after Timeout ->
- _ = cancel(SockRef, SendName, SendRef),
- {error, {timeout, size(Data)}}
- end;
-
-
- {error, exbusy} = Error when Deadline =:= nowait -> Error;
-
- {error, exbusy = Reason} ->
- %% Internal error:
- %% we called send, got eagain, and called send again
- %% - without waiting for select message
- erlang:error(Reason);
-
-
- {error, eagain} when (Deadline =:= nowait) ->
- ?SELECT(SendName, SendRef);
-
- {error, eagain} ->
- Timeout = timeout(Deadline),
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, select, SendRef} ->
- send_common(
- SockRef, Data, To, EFlags, Deadline, SendName);
-
- {?ESOCK_TAG, _Socket, abort, {SendRef, Reason}} ->
- {error, {Reason, size(Data)}}
-
- after Timeout ->
- _ = cancel(SockRef, SendName, SendRef),
- {error, {timeout, size(Data)}}
- end;
-
-
- {error, Reason} ->
- {error, {Reason, size(Data)}}
- end.
-
-
-%% ---------------------------------------------------------------------------
-%%
-
--spec sendto(Socket, Data, Dest) ->
- ok | {error, Reason} when
- Socket :: socket(),
- Data :: binary(),
- Dest :: sockaddr(),
- Reason :: term().
-
-sendto(Socket, Data, Dest) ->
- sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT).
-
--spec sendto(Socket, Data, Dest, Flags) -> ok | {error, Reason} when
- Socket :: socket(),
- Data :: binary(),
- Dest :: sockaddr(),
- Flags :: send_flags(),
- Reason :: term()
- ; (Socket, Data, Dest, Timeout :: nowait) -> ok |
- {select, SelectInfo} |
- {error, Reason} when
- Socket :: socket(),
- Data :: iodata(),
- Dest :: sockaddr(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, Data, Dest, Timeout) -> ok | {error, Reason} when
- Socket :: socket(),
- Data :: iodata(),
- Dest :: sockaddr(),
- Timeout :: timeout(),
- Reason :: term().
-
-sendto(Socket, Data, Dest, Flags) when is_list(Flags) ->
- sendto(Socket, Data, Dest, Flags, ?ESOCK_SENDTO_TIMEOUT_DEFAULT);
-sendto(Socket, Data, Dest, Timeout) ->
- sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT, Timeout).
-
-
--spec sendto(Socket, Data, Dest, Flags, nowait) ->
- ok |
- {ok, {binary(), SelectInfo}} |
- {select, SelectInfo} |
- {error, Reason} when
- Socket :: socket(),
- Data :: binary(),
- Dest :: sockaddr(),
- Flags :: send_flags(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when
- Socket :: socket(),
- Data :: binary(),
- Dest :: sockaddr(),
- Flags :: send_flags(),
- Timeout :: timeout(),
- Reason :: term().
-
-sendto(Socket, Data, Dest, Flags, Timeout) when is_list(Data) ->
- Bin = erlang:list_to_binary(Data),
- sendto(Socket, Bin, Dest, Flags, Timeout);
-sendto(#socket{ref = SockRef}, Data, Dest, Flags, Timeout)
- when is_binary(Data), is_list(Flags) ->
- try
- begin
- To = ensure_sockaddr(Dest),
- EFlags = enc_send_flags(Flags),
- Deadline = deadline(Timeout),
- send_common(SockRef, Data, To, EFlags, Deadline, sendto)
- end
- catch
- throw:ERROR ->
- ERROR
- end.
-
-
-%% ---------------------------------------------------------------------------
-%%
-%% The only part of the msghdr() that *must* exist (a connected
-%% socket need not specify the addr field) is the iov.
-%% The ctrl field is optional, and the addr and flags are not
-%% used when sending.
-%%
-
--spec sendmsg(Socket, MsgHdr) ->
- ok |
- {ok, Remaining} |
- {error, Reason} when
- Socket :: socket(),
- MsgHdr :: msghdr(),
- Remaining :: erlang:iovec(),
- Reason :: term().
-
-sendmsg(Socket, MsgHdr) ->
- sendmsg(Socket, MsgHdr,
- ?ESOCK_SENDMSG_FLAGS_DEFAULT, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT).
-
-
--spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when
- Socket :: socket(),
- MsgHdr :: msghdr(),
- Flags :: send_flags(),
- Reason :: term();
- (Socket, MsgHdr, Timeout :: nowait) ->
- ok |
- {ok, Remaining} |
- {error, Reason} when
- Socket :: socket(),
- MsgHdr :: msghdr(),
- Remaining :: erlang:iovec(),
- Reason :: term();
- (Socket, MsgHdr, Timeout) -> ok | {error, Reason} when
- Socket :: socket(),
- MsgHdr :: msghdr(),
- Timeout :: timeout(),
- Reason :: term().
-
-sendmsg(Socket, MsgHdr, Flags) when is_list(Flags) ->
- sendmsg(Socket, MsgHdr, Flags, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT);
-sendmsg(Socket, MsgHdr, Timeout) ->
- sendmsg(Socket, MsgHdr, ?ESOCK_SENDMSG_FLAGS_DEFAULT, Timeout).
-
-
--spec sendmsg(Socket, MsgHdr, Flags, nowait) ->
- ok |
- {ok, Remaining} |
- {error, Reason} when
- Socket :: socket(),
- MsgHdr :: msghdr(),
- Flags :: send_flags(),
- Remaining :: erlang:iovec(),
- Reason :: term()
- ; (Socket, MsgHdr, Flags, Timeout) ->
- ok |
- {ok, Remaining} |
- {error, Reason} when
- Socket :: socket(),
- MsgHdr :: msghdr(),
- Flags :: send_flags(),
- Timeout :: timeout(),
- Remaining :: erlang:iovec(),
- Reason :: term().
-
-sendmsg(#socket{ref = SockRef}, #{iov := IOV} = MsgHdr, Flags, Timeout)
- when is_list(IOV), is_list(Flags) ->
- try
- begin
- M = ensure_msghdr(MsgHdr),
- EFlags = enc_send_flags(Flags),
- Deadline = deadline(Timeout),
- do_sendmsg(SockRef, M, EFlags, Deadline)
- end
- catch
- throw:ERROR ->
- ERROR
- end.
-
-
-do_sendmsg(SockRef, MsgHdr, EFlags, Deadline) ->
-
- SendRef = make_ref(),
-
- case nif_sendmsg(SockRef, SendRef, MsgHdr, EFlags) of
- ok ->
- %% We are done
- ok;
-
-
- {ok, Written} when is_integer(Written) andalso (Written > 0) ->
- %% We should not retry here since the protocol may not
- %% be able to handle a message being split. Leave it to
- %% the caller to figure out (call again with the rest).
- %%
- %% We need to cancel this partial write.
- %%
- _ = cancel(SockRef, sendmsg, SendRef),
- {ok, do_sendmsg_rest(maps:get(iov, MsgHdr), Written)};
-
-
- {error, exbusy} = Error when Deadline =:= nowait -> Error;
-
- {error, exbusy = Reason} ->
- %% Internal error:
- %% we called send, got eagain, and called send again
- %% - without waiting for select message
- erlang:error(Reason);
-
-
- {error, eagain} when (Deadline =:= nowait) ->
- ?SELECT(sendmsg, SendRef);
-
- {error, eagain} ->
- Timeout = timeout(Deadline),
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, select, SendRef} ->
- do_sendmsg(SockRef, MsgHdr, EFlags, Deadline);
-
- {?ESOCK_TAG, _Socket, abort, {SendRef, Reason}} ->
- {error, Reason}
-
- after Timeout ->
- _ = cancel(SockRef, sendmsg, SendRef),
- {error, timeout}
- end;
-
-
- {error, _} = ERROR ->
- ERROR
- end.
-
-do_sendmsg_rest([B|IOVec], Written) when (Written >= size(B)) ->
- do_sendmsg_rest(IOVec, Written - size(B));
-do_sendmsg_rest([B|IOVec], Written) ->
- <<_:Written/binary, Rest/binary>> = B,
- [Rest|IOVec].
-
-ensure_msghdr(#{ctrl := []} = M) ->
- ensure_msghdr(maps:remove(ctrl, M));
-ensure_msghdr(#{iov := IOV, addr := Addr} = M)
- when is_list(IOV) andalso (IOV =/= []) ->
- M#{iov => erlang:iolist_to_iovec(IOV), addr => ensure_sockaddr(Addr)};
-ensure_msghdr(#{iov := IOV} = M)
- when is_list(IOV) andalso (IOV =/= []) ->
- M#{iov => erlang:iolist_to_iovec(IOV)};
-ensure_msghdr(_) ->
- einval().
-
-
-
-%% ===========================================================================
-%%
-%% recv, recvfrom, recvmsg - receive a message from a socket
-%%
-%% Description:
-%% There is a special case for the argument Length. If its set to zero (0),
-%% it means "give me everything you have".
-%%
-%% Returns: {ok, Binary} | {error, Reason}
-%% Binary - The received data as a binary
-%% Reason - The error reason:
-%% timeout | {timeout, AccData} |
-%% posix() | {posix(), AccData} |
-%% atom() | {atom(), AccData}
-%% AccData - The data (as a binary) that we did manage to receive
-%% before the timeout.
-%%
-%% Arguments:
-%% Socket - The socket to read from.
-%% Length - The number of bytes to read.
-%% Flags - A list of "options" for the read.
-%% Timeout - Time-out in milliseconds.
-
--spec recv(Socket) -> {ok, Data} | {error, Reason} when
- Socket :: socket(),
- Data :: binary(),
- Reason :: term().
-
-recv(Socket) ->
- recv(Socket, 0).
-
--spec recv(Socket, Length) -> {ok, Data} | {error, Reason} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Data :: binary(),
- Reason :: term().
-
-recv(Socket, Length) ->
- recv(Socket, Length,
- ?ESOCK_RECV_FLAGS_DEFAULT,
- ?ESOCK_RECV_TIMEOUT_DEFAULT).
-
--spec recv(Socket, Length, Flags) -> {ok, Data} |
- {error, Reason} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Flags :: recv_flags(),
- Data :: binary(),
- Reason :: term()
- ; (Socket, Length, Timeout :: nowait) -> {ok, Data} |
- {select, SelectInfo} |
- {ok, {Data, SelectInfo}} |
- {error, Reason} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, Length, Timeout) -> {ok, Data} |
- {error, Reason} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Timeout :: timeout(),
- Data :: binary(),
- Reason :: term().
-
-recv(Socket, Length, Flags) when is_list(Flags) ->
- recv(Socket, Length, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
-recv(Socket, Length, Timeout) ->
- recv(Socket, Length, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
-
--spec recv(Socket, Length, Flags, nowait) -> {ok, Data} |
- {select, SelectInfo} |
- {ok, {Data, SelectInfo}} |
- {error, Reason} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Flags :: recv_flags(),
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, Length, Flags, Timeout) -> {ok, Data} |
- {error, Reason} when
- Socket :: socket(),
- Length :: non_neg_integer(),
- Flags :: recv_flags(),
- Timeout :: timeout(),
- Data :: binary(),
- Reason :: term().
-
-recv(#socket{ref = SockRef}, Length, Flags, Timeout)
- when is_integer(Length), Length >= 0, is_list(Flags) ->
- try
- EFlags = enc_recv_flags(Flags),
- Deadline = deadline(Timeout),
- do_recv(SockRef, Length, EFlags, Deadline, <<>>)
- catch
- throw:ERROR ->
- ERROR
- end.
-
-%% We will only recurse with Length == 0 if Length is 0,
-%% so Length == 0 means to return all available data also when recursing
-%%
-%% Note that the Deadline value of 'nowait' has a special meaning. It means
-%% that we will either return with data or with the with {error, NNNN}. In
-%% wich case the caller will receive a select message at some later time.
-%%
-do_recv(SockRef, Length, EFlags, Deadline, Acc) ->
-
- RecvRef = make_ref(),
- case nif_recv(SockRef, RecvRef, Length, EFlags) of
-
- {ok, true = _Complete, Bin} ->
- {ok, bincat(Acc, Bin)};
-
-
- %% It depends on the amount of bytes we tried to read:
- %% 0 - Read everything available
- %% We got something, but there may be more - keep reading.
- %% > 0 - We got a part of the message and we will be notified
- %% when there is more to read (a select message)
- {ok, false = _Complete, Bin} when Length =:= 0 ->
- Timeout = timeout(Deadline),
- if
- 0 < Timeout ->
- do_recv(
- SockRef, Length, EFlags, Deadline, bincat(Acc, Bin));
- true ->
- {ok, bincat(Acc, Bin)}
- end;
-
- %% Did not get all the user asked for, but the user also
- %% specified 'nowait', so deliver what we got and the
- %% select info.
- {ok, false = _Completed, Bin} when Deadline =:= nowait ->
- {ok, {bincat(Acc, Bin), ?SELECT_INFO(recv, RecvRef)}};
-
- {ok, false = _Completed, Bin} ->
- %% We got a chunk of it!
- Timeout = timeout(Deadline),
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, select, RecvRef} ->
- if
- 0 < Timeout ->
- do_recv(
- SockRef, Length - byte_size(Bin), EFlags,
- Deadline, bincat(Acc, Bin));
- true ->
- {error, {timeout, bincat(Acc, Bin)}}
- end;
-
- {?ESOCK_TAG, _Socket, abort, {RecvRef, Reason}} ->
- {error, Reason}
-
- after Timeout ->
- cancel(SockRef, recv, RecvRef),
- {error, {timeout, bincat(Acc, Bin)}}
- end;
-
-
- {error, exbusy} = Error when Deadline =:= nowait -> Error;
-
- {error, exbusy = Reason} ->
- %% Internal error:
- %% we called recv, got eagain, and called recv again
- %% - without waiting for select message
- erlang:error(Reason);
-
-
- %% The user does not want to wait!
- %% The user will be informed that there is something to read
- %% via the select socket message (see below).
- {error, eagain} when Deadline =:= nowait ->
- if
- byte_size(Acc) =:= 0 ->
- ?SELECT(recv, RecvRef);
- true ->
- {ok, {Acc, ?SELECT_INFO(recv, RecvRef)}}
- end;
-
-
- %% We return with the accumulated binary (if its non-empty)
- {error, eagain} when Length =:= 0, 0 < byte_size(Acc) ->
- cancel(SockRef, recv, RecvRef),
- {ok, Acc};
-
- {error, eagain} ->
- %% There is nothing just now, but we will be notified when there
- %% is something to read (a select message).
- Timeout = timeout(Deadline),
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, select, RecvRef} ->
- if
- 0 < Timeout ->
- do_recv(
- SockRef, Length, EFlags, Deadline, Acc);
- 0 < byte_size(Acc) ->
- {error, {timeout, Acc}};
- true ->
- {error, timeout}
- end;
-
- {?ESOCK_TAG, _Socket, abort, {RecvRef, Reason}} ->
- {error, Reason}
-
- after Timeout ->
- cancel(SockRef, recv, RecvRef),
- {error, timeout}
- end;
-
-
- {error, _} = ERROR when byte_size(Acc) =:= 0 ->
- ERROR;
-
- {error, Reason} ->
- {error, {Reason, Acc}}
-
- end.
-
-
-
-%% ---------------------------------------------------------------------------
-%%
-%% With recvfrom we get messages, which means that regardless of how
-%% much we want to read, we return when we get a message.
-%% The MaxSize argument basically defines the size of our receive
-%% buffer. By setting the size to zero (0), we use the configured
-%% size (see setopt).
-%% It may be impossible to know what (buffer) size is appropriate
-%% "in advance", and in those cases it may be convenient to use the
-%% (recv) 'peek' flag. When this flag is provided the message is *not*
-%% "consumed" from the underlying (OS) buffers, so another recvfrom call
-%% is needed, possibly with a then adjusted buffer size.
-%%
-
--spec recvfrom(Socket) -> {ok, {Source, Data}} | {error, Reason} when
- Socket :: socket(),
- Source :: sockaddr() | undefined,
- Data :: binary(),
- Reason :: term().
-
-recvfrom(Socket) ->
- recvfrom(Socket, 0).
-
--spec recvfrom(Socket, BufSz) -> {ok, {Source, Data}} | {error, Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Source :: sockaddr() | undefined,
- Data :: binary(),
- Reason :: term().
-
-recvfrom(Socket, BufSz) ->
- recvfrom(Socket, BufSz,
- ?ESOCK_RECV_FLAGS_DEFAULT,
- ?ESOCK_RECV_TIMEOUT_DEFAULT).
-
--spec recvfrom(Socket, Flags, nowait) ->
- {ok, {Source, Data}} |
- {select, SelectInfo} |
- {error, Reason} when
- Socket :: socket(),
- Flags :: recv_flags(),
- Source :: sockaddr() | undefined,
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, Flags, Timeout) ->
- {ok, {Source, Data}} |
- {error, Reason} when
- Socket :: socket(),
- Flags :: recv_flags(),
- Timeout :: timeout(),
- Source :: sockaddr() | undefined,
- Data :: binary(),
- Reason :: term()
- ; (Socket, BufSz, Flags) ->
- {ok, {Source, Data}} | {error, Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Flags :: recv_flags(),
- Source :: sockaddr() | undefined,
- Data :: binary(),
- Reason :: term()
- ; (Socket, BufSz, nowait) ->
- {ok, {Source, Data}} |
- {select, SelectInfo} |
- {error, Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Source :: sockaddr() | undefined,
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, BufSz, Timeout) ->
- {ok, {Source, Data}} | {error, Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Timeout :: timeout(),
- Source :: sockaddr() | undefined,
- Data :: binary(),
- Reason :: term().
-
-recvfrom(Socket, Flags, Timeout) when is_list(Flags) ->
- recvfrom(Socket, 0, Flags, Timeout);
-recvfrom(Socket, BufSz, Flags) when is_list(Flags) ->
- recvfrom(Socket, BufSz, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
-recvfrom(Socket, BufSz, Timeout) ->
- recvfrom(Socket, BufSz, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
-
--spec recvfrom(Socket, BufSz, Flags, nowait) ->
- {ok, {Source, Data}} |
- {select, SelectInfo} |
- {error, Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Flags :: recv_flags(),
- Source :: sockaddr() | undefined,
- Data :: binary(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, BufSz, Flags, Timeout) ->
- {ok, {Source, Data}} |
- {error, Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- Flags :: recv_flags(),
- Timeout :: timeout(),
- Source :: sockaddr() | undefined,
- Data :: binary(),
- Reason :: term().
-
-recvfrom(#socket{ref = SockRef}, BufSz, Flags, Timeout)
- when is_integer(BufSz), 0 =< BufSz, is_list(Flags) ->
- try
- EFlags = enc_recv_flags(Flags),
- Deadline = deadline(Timeout),
- do_recvfrom(SockRef, BufSz, EFlags, Deadline)
- catch
- throw:ERROR ->
- ERROR
- end.
-
-do_recvfrom(SockRef, BufSz, EFlags, Deadline) ->
-
- RecvRef = make_ref(),
- case nif_recvfrom(SockRef, RecvRef, BufSz, EFlags) of
-
- {ok, {_Source, _NewData}} = OK ->
- OK;
-
-
- {error, exbusy} = Error when Deadline =:= nowait -> Error;
-
- {error, exbusy = Reason} ->
- %% Internal error:
- %% we called recvfrom, got eagain, and called recvfrom again
- %% - without waiting for select message
- erlang:error(Reason);
-
-
- {error, eagain} when Deadline =:= nowait ->
- ?SELECT(recvfrom, RecvRef);
-
- {error, eagain} ->
- %% There is nothing just now, but we will be notified when there
- %% is something to read (a select message).
- Timeout = timeout(Deadline),
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, select, RecvRef} ->
- do_recvfrom(SockRef, BufSz, EFlags, Deadline);
-
- {?ESOCK_TAG, _Socket, abort, {RecvRef, Reason}} ->
- {error, Reason}
-
- after Timeout ->
- cancel(SockRef, recvfrom, RecvRef),
- {error, timeout}
- end;
-
-
- {error, _Reason} = ERROR ->
- ERROR
-
- end.
-
-
-%% ---------------------------------------------------------------------------
-%%
-
--spec recvmsg(Socket) -> {ok, MsgHdr} | {error, Reason} when
- Socket :: socket(),
- MsgHdr :: msghdr(),
- Reason :: term().
-
-recvmsg(Socket) ->
- recvmsg(Socket, 0, 0,
- ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
-
--spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when
- Socket :: socket(),
- Flags :: recv_flags(),
- MsgHdr :: msghdr(),
- Reason :: term()
- ; (Socket, Timeout :: nowait) -> {ok, MsgHdr} |
- {select, SelectInfo} |
- {error, Reason} when
- Socket :: socket(),
- MsgHdr :: msghdr(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, Timeout) -> {ok, MsgHdr} | {error, Reason} when
- Socket :: socket(),
- Timeout :: timeout(),
- MsgHdr :: msghdr(),
- Reason :: term().
-
-recvmsg(Socket, Flags) when is_list(Flags) ->
- recvmsg(Socket, 0, 0, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
-recvmsg(Socket, Timeout) ->
- recvmsg(Socket, 0, 0, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
-
--spec recvmsg(Socket, Flags, nowait) -> {ok, MsgHdr} |
- {select, SelectInfo} |
- {error, Reason} when
- Socket :: socket(),
- Flags :: recv_flags(),
- MsgHdr :: msghdr(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket, Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when
- Socket :: socket(),
- Flags :: recv_flags(),
- Timeout :: timeout(),
- MsgHdr :: msghdr(),
- Reason :: term()
- ; (Socket, BufSz, CtrlSz) -> {ok, MsgHdr} | {error, Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- CtrlSz :: non_neg_integer(),
- MsgHdr :: msghdr(),
- Reason :: term().
-
-recvmsg(Socket, Flags, Timeout) when is_list(Flags) ->
- recvmsg(Socket, 0, 0, Flags, Timeout);
-recvmsg(Socket, BufSz, CtrlSz) when is_integer(BufSz), is_integer(CtrlSz) ->
- recvmsg(Socket, BufSz, CtrlSz,
- ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
-
-
--spec recvmsg(Socket,
- BufSz, CtrlSz,
- Flags, nowait) -> {ok, MsgHdr} |
- {select, SelectInfo} |
- {error, Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- CtrlSz :: non_neg_integer(),
- Flags :: recv_flags(),
- MsgHdr :: msghdr(),
- SelectInfo :: select_info(),
- Reason :: term()
- ; (Socket,
- BufSz, CtrlSz,
- Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when
- Socket :: socket(),
- BufSz :: non_neg_integer(),
- CtrlSz :: non_neg_integer(),
- Flags :: recv_flags(),
- Timeout :: timeout(),
- MsgHdr :: msghdr(),
- Reason :: term().
-
-recvmsg(#socket{ref = SockRef}, BufSz, CtrlSz, Flags, Timeout)
- when is_integer(BufSz), 0 =< BufSz,
- is_integer(CtrlSz), 0 =< CtrlSz,
- is_list(Flags) ->
- try
- EFlags = enc_recv_flags(Flags),
- Deadline = deadline(Timeout),
- do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Deadline)
- catch
- throw:ERROR ->
- ERROR
- end.
-
-do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Deadline) ->
-
- RecvRef = make_ref(),
- case nif_recvmsg(SockRef, RecvRef, BufSz, CtrlSz, EFlags) of
-
- {ok, _MsgHdr} = OK ->
- OK;
-
-
- {error, exbusy} = Error when Deadline =:= nowait -> Error;
-
- {error, exbusy = Reason} ->
- %% Internal error:
- %% we called recvmsg, got eagain, and called recvmsg again
- %% - without waiting for select message
- erlang:error(Reason);
-
-
- {error, eagain} when Deadline =:= nowait ->
- ?SELECT(recvmsg, RecvRef);
-
- {error, eagain} ->
- %% There is nothing just now, but we will be notified when there
- %% is something to read (a select message).
- Timeout = timeout(Deadline),
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, select, RecvRef} ->
- do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Deadline);
-
- {?ESOCK_TAG, _Socket, abort, {RecvRef, Reason}} ->
- {error, Reason}
-
- after Timeout ->
- cancel(SockRef, recvmsg, RecvRef),
- {error, timeout}
- end;
-
-
- {error, _Reason} = ERROR ->
- ERROR
-
- end.
-
-
-
-%% ===========================================================================
-%%
-%% close - close a file descriptor
-%%
-%% Closing a socket is a two stage rocket (because of linger).
-%% We need to perform the actual socket close while in BLOCKING mode.
-%% But that would hang the entire VM, so what we do is divide the
-%% close in two steps:
-%% 1) nif_close + the socket_stop (nif) callback function
-%% This is for everything that can be done safely NON-BLOCKING.
-%% 2) nif_finalize_close which is executed by a *dirty* scheduler
-%% Before we call the socket close function, we set the socket
-%% BLOCKING. Thereby linger is handled properly.
-
--spec close(Socket) -> ok | {error, Reason} when
- Socket :: socket(),
- Reason :: term().
-
-close(#socket{ref = SockRef}) ->
- case nif_close(SockRef) of
- ok ->
- nif_finalize_close(SockRef);
- {ok, CloseRef} ->
- %% We must wait for the socket_stop callback function to
- %% complete its work
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, close, CloseRef} ->
- nif_finalize_close(SockRef)
- end;
- {error, _} = ERROR ->
- ERROR
- end.
-
-
-
-
-%% ===========================================================================
-%%
-%% shutdown - shut down part of a full-duplex connection
-%%
-
--spec shutdown(Socket, How) -> ok | {error, Reason} when
- Socket :: socket(),
- How :: shutdown_how(),
- Reason :: term().
-
-shutdown(#socket{ref = SockRef}, How) ->
- try
- nif_shutdown(SockRef, enc_shutdown_how(How))
- catch
- throw:T ->
- T;
- %% <WIN32-TEMPORARY>
- error:notsup:S ->
- erlang:raise(error, notsup, S);
- %% </WIN32-TEMPORARY>
- error:Reason ->
- {error, Reason}
- end.
-
-
-
-
-%% ===========================================================================
-%%
-%% setopt - manipulate individual properties of a socket
-%%
-%% What properties are valid depend on what kind of socket it is
-%% (domain, type and protocol)
-%% If its an "invalid" option (or value), we should not crash but return some
-%% useful error...
-%%
-%% <KOLLA>
-%%
-%% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING
-%% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!!
-%%
-%% </KOLLA>
-
--spec setopt(Socket, otp, otp_socket_option(), Value) -> ok | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, socket, socket_option(), Value) -> ok | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, ip, ip_socket_option(), Value) -> ok | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, ipv6, ipv6_socket_option(), Value) -> ok | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, tcp, tcp_socket_option(), Value) -> ok | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, udp, udp_socket_option(), Value) -> ok | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, sctp, sctp_socket_option(), Value) -> ok | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, Level, Key, Value) -> ok | {error, Reason} when
- Socket :: socket(),
- Level :: non_neg_integer(),
- Key :: non_neg_integer(),
- Value :: binary(),
- Reason :: term().
-
-setopt(#socket{ref = SockRef}, Level, Key, Value) ->
- try
- begin
- {Domain, Type, Proto} = which_dtp(SockRef),
- {EIsEncoded, ELevel} = enc_setopt_level(Level),
- EKey = enc_setopt_key(Level, Key, Domain, Type, Proto),
- EVal = enc_setopt_value(Level, Key, Value, Domain, Type, Proto),
- nif_setopt(SockRef, EIsEncoded, ELevel, EKey, EVal)
- end
- catch
- throw:ERROR ->
- ERROR
- end.
-
-
-
-
-%% ===========================================================================
-%%
-%% getopt - retrieve individual properties of a socket
-%%
-%% What properties are valid depend on what kind of socket it is
-%% (domain, type and protocol).
-%% If its an "invalid" option, we should not crash but return some
-%% useful error...
-%%
-%% When specifying level as an integer, and therefor using "native mode",
-%% we should make it possible to specify common types instead of the
-%% value size. Example: int | bool | {string, pos_integer()} | non_neg_integer()
-%%
-
--spec getopt(Socket, otp, otp_socket_option()) -> {ok, Value} | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, socket, socket_option()) -> {ok, Value} | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, ip, ip_socket_option()) -> {ok, Value} | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, ipv6, ipv6_socket_option()) -> {ok, Value} | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, tcp, tcp_socket_option()) -> {ok, Value} | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, udp, udp_socket_option()) -> {ok, Value} | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, sctp, sctp_socket_option()) -> {ok, Value} | {error, Reason} when
- Socket :: socket(),
- Value :: term(),
- Reason :: term()
- ; (Socket, Level, Key) -> ok | {ok, Value} | {error, Reason} when
- Socket :: socket(),
- Level :: integer(),
- Key :: {NativeOpt, ValueSize},
- NativeOpt :: integer(),
- ValueSize :: int | bool | non_neg_integer(),
- Value :: term(),
- Reason :: term().
-
-getopt(#socket{ref = SockRef}, Level, Key) ->
- try
- begin
- {Domain, Type, Proto} = which_dtp(SockRef),
- {EIsEncoded, ELevel} = enc_getopt_level(Level),
- EKey = enc_getopt_key(Level, Key, Domain, Type, Proto),
- %% We may need to decode the value (for the same reason
- %% we (may have) needed to encode the value for setopt).
- case nif_getopt(SockRef, EIsEncoded, ELevel, EKey) of
- ok ->
- ok;
- {ok, EVal} ->
- Val =
- dec_getopt_value(
- Level, Key, EVal, Domain, Type, Proto),
- {ok, Val};
- {error, _} = E ->
- E
- end
- end
- catch
- throw:ERROR ->
- ERROR
- end.
-
-
-%% These are internal "shortcut" functions for the options
-%% domain, type and protocol.
-
--spec which_domain(SockRef) -> Domain when
- SockRef :: reference(),
- Domain :: domain().
-
-which_domain(SockRef) ->
- case nif_getopt(SockRef, true,
- ?ESOCK_OPT_LEVEL_OTP, ?ESOCK_OPT_OTP_DOMAIN) of
- {ok, Domain} ->
- if
- is_atom(Domain) ->
- Domain;
- is_integer(Domain) ->
- invalid_domain(Domain)
- end;
- {error, _} = ERROR ->
- throw(ERROR)
- end.
-
-
-%%%-spec which_type(SockRef) -> Type when
-%%% SockRef :: reference(),
-%%% Type :: type().
-%%%
-%%%which_type(SockRef) ->
-%%% case nif_getopt(SockRef, true,
-%%% ?ESOCK_OPT_LEVEL_OTP, ?ESOCK_OPT_OTP_TYPE) of
-%%% {ok, Type} ->
-%%% if
-%%% is_atom(Type) ->
-%%% Type;
-%%% is_integer(Type) ->
-%%% invalid_type(Type)
-%%% end;
-%%% {error, _} = ERROR ->
-%%% throw(ERROR)
-%%% end.
-%%%
-%%%-spec which_protocol(SockRef) -> Protocol when
-%%% SockRef :: reference(),
-%%% Protocol :: protocol().
-%%%
-%%%which_protocol(SockRef) ->
-%%% case nif_getopt(SockRef, true,
-%%% ?ESOCK_OPT_LEVEL_OTP, ?ESOCK_OPT_OTP_PROTOCOL) of
-%%% {ok, Proto} ->
-%%% if
-%%% is_atom(Proto) ->
-%%% Proto;
-%%% is_integer(Proto) ->
-%%% invalid_protocol(Proto)
-%%% end;
-%%% {error, _} = ERROR ->
-%%% throw(ERROR)
-%%% end.
-
-which_dtp(SockRef) ->
- case
- nif_getopt(
- SockRef, true, ?ESOCK_OPT_LEVEL_OTP, ?ESOCK_OPT_OTP_DTP)
- of
- {ok, {Domain, Type, Proto} = DTP} ->
- if
- is_integer(Domain) ->
- invalid_domain(Domain);
- is_integer(Type) ->
- invalid_type(Type);
- is_integer(Proto) ->
- invalid_protocol(Proto);
- is_atom(Domain), is_atom(Type), is_atom(Proto) ->
- DTP
- end;
- {error, _} = ERROR ->
- throw(ERROR)
- end.
-
-
-
-%% ===========================================================================
-%%
-%% sockname - return the current address of the socket.
-%%
-%%
-
--spec sockname(Socket) -> {ok, SockAddr} | {error, Reason} when
- Socket :: socket(),
- SockAddr :: sockaddr(),
- Reason :: term().
-
-sockname(#socket{ref = SockRef}) ->
- nif_sockname(SockRef).
-
-
-
-%% ===========================================================================
-%%
-%% peername - return the address of the peer *connected* to the socket.
-%%
-%%
-
--spec peername(Socket) -> {ok, SockAddr} | {error, Reason} when
- Socket :: socket(),
- SockAddr :: sockaddr(),
- Reason :: term().
-
-peername(#socket{ref = SockRef}) ->
- nif_peername(SockRef).
-
-
-%% ===========================================================================
-%%
-%% cancel - cancel an operation resulting in a select
-%%
-%% A call to accept, recv/recvfrom/recvmsg and send/sendto/sendmsg
-%% can result in a select if they are called with the Timeout argument
-%% set to nowait. This is indicated by the return of the select-info.
-%% Such a operation can be cancelled by calling this function.
-%%
-
--spec cancel(Socket, SelectInfo) -> ok | {error, Reason} when
- Socket :: socket(),
- SelectInfo :: select_info(),
- Reason :: term().
-
-cancel(#socket{ref = SockRef}, ?SELECT_INFO(Tag, Ref)) ->
- cancel(SockRef, Tag, Ref).
-
-
-
-%% ===========================================================================
-%%
-%% Encode / decode
-%%
-%% ===========================================================================
-
-%% -spec enc_domain(Domain) -> non_neg_integer() when
-%% Domain :: domain().
-
-enc_domain(local) -> ?ESOCK_DOMAIN_LOCAL;
-enc_domain(inet) -> ?ESOCK_DOMAIN_INET;
-enc_domain(inet6) -> ?ESOCK_DOMAIN_INET6;
-enc_domain(Domain) -> invalid_domain(Domain).
-
-%% -spec enc_type(Type) -> non_neg_integer() when
-%% Type :: type().
-
-enc_type(stream) -> ?ESOCK_TYPE_STREAM;
-enc_type(dgram) -> ?ESOCK_TYPE_DGRAM;
-enc_type(raw) -> ?ESOCK_TYPE_RAW;
-enc_type(seqpacket) -> ?ESOCK_TYPE_SEQPACKET;
-enc_type(Type) -> invalid_type(Type).
-
--spec enc_protocol(Protocol) -> non_neg_integer() |
- {raw, non_neg_integer()} when
- Protocol :: protocol().
-
-enc_protocol(default) -> ?ESOCK_PROTOCOL_DEFAULT;
-enc_protocol(ip) -> ?ESOCK_PROTOCOL_IP;
-enc_protocol(tcp) -> ?ESOCK_PROTOCOL_TCP;
-enc_protocol(udp) -> ?ESOCK_PROTOCOL_UDP;
-enc_protocol(sctp) -> ?ESOCK_PROTOCOL_SCTP;
-enc_protocol(icmp) -> ?ESOCK_PROTOCOL_ICMP;
-enc_protocol(igmp) -> ?ESOCK_PROTOCOL_IGMP;
-enc_protocol({raw, P} = RAW) when is_integer(P) -> RAW;
-enc_protocol(Proto) ->
- invalid_protocol(Proto).
-
-
--spec enc_send_flags(Flags) -> non_neg_integer() when
- Flags :: send_flags().
-
-enc_send_flags(Flags) ->
- EFlags = [{confirm, ?ESOCK_SEND_FLAG_CONFIRM},
- {dontroute, ?ESOCK_SEND_FLAG_DONTROUTE},
- {eor, ?ESOCK_SEND_FLAG_EOR},
- {more, ?ESOCK_SEND_FLAG_MORE},
- {nosignal, ?ESOCK_SEND_FLAG_NOSIGNAL},
- {oob, ?ESOCK_SEND_FLAG_OOB}],
- enc_flags(Flags, EFlags).
-
--spec enc_recv_flags(Flags) -> non_neg_integer() when
- Flags :: recv_flags().
-
-enc_recv_flags(Flags) ->
- EFlags = [{cmsg_cloexec, ?ESOCK_RECV_FLAG_CMSG_CLOEXEC},
- {errqueue, ?ESOCK_RECV_FLAG_ERRQUEUE},
- {oob, ?ESOCK_RECV_FLAG_OOB},
- {peek, ?ESOCK_RECV_FLAG_PEEK},
- {trunc, ?ESOCK_RECV_FLAG_TRUNC}],
- enc_flags(Flags, EFlags).
-
-
-enc_flags([], _) ->
- 0;
-enc_flags(Flags, EFlags) ->
- F = fun(Flag, Acc) ->
- case lists:keysearch(Flag, 1, EFlags) of
- {value, {Flag, EFlag}} ->
- Acc bor (1 bsl EFlag);
- false ->
- throw({error, {unknown_flag, Flag}})
- end
- end,
- lists:foldl(F, 0, Flags).
-
-
-%% +++ Encode setopt level +++
-
--spec enc_setopt_level(Level) -> {IsEncoded, EncodedLevel} when
- Level :: sockopt_level(),
- IsEncoded :: boolean(),
- EncodedLevel :: integer().
-
-enc_setopt_level(otp) ->
- {true, ?ESOCK_OPT_LEVEL_OTP};
-enc_setopt_level(socket) ->
- {true, ?ESOCK_OPT_LEVEL_SOCKET};
-enc_setopt_level(ip) ->
- {true, ?ESOCK_OPT_LEVEL_IP};
-enc_setopt_level(ipv6) ->
- {true, ?ESOCK_OPT_LEVEL_IPV6};
-enc_setopt_level(tcp) ->
- {true, ?ESOCK_OPT_LEVEL_TCP};
-enc_setopt_level(udp) ->
- {true, ?ESOCK_OPT_LEVEL_UDP};
-enc_setopt_level(sctp) ->
- {true, ?ESOCK_OPT_LEVEL_SCTP};
-%% Any option that is of an plain level must be provided as a binary
-%% already fully encoded!
-enc_setopt_level(L) when is_integer(L) ->
- {false, L}.
-
-
-%% +++ Encode setopt key +++
-
-%% We should ...really... do something with the domain, type and protocol args...
-%% Also, any option (key) which has an integer level (plain) must also be provided
-%% in a plain mode, that is, as an integer.
-%% Also, not all options are available on all platforms. That is something we
-%% don't check here, but in the nif-code.
-
-enc_setopt_key(Level, Opt, Domain, Type, Protocol) ->
- enc_sockopt_key(Level, Opt, set, Domain, Type, Protocol).
-
-
-%% +++ Encode setopt value +++
-%%
-%% For the most part this function does *not* do an actual encode,
-%% it simply validates the value type. But in some cases it will
-%% encode the value into an more "manageable" type.
-%% It also handles "aliases" (see linger).
-
--spec enc_setopt_value(otp, otp_socket_option(),
- Value, Domain, Type, Protocol) -> term() when
- Value :: term(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol()
- ; (socket, socket_option(),
- Value, Domain, Type, Protocol) -> term() when
- Value :: term(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol()
- ; (ip, ip_socket_option(),
- Value, Domain, Type, Protocol) -> term() when
- Value :: term(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol()
- ; (ipv6, ipv6_socket_option(),
- Value, Domain, Type, Protocol) -> term() when
- Value :: term(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol()
- ; (tcp, tcp_socket_option(),
- Value, Domain, Type, Protocol) -> term() when
- Value :: term(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol()
- ; (udp, udp_socket_option(),
- Value, Domain, Type, Protocol) -> term() when
- Value :: term(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol()
- ; (sctp, sctp_socket_option(),
- Value, Domain, Type, Protocol) -> term() when
- Value :: term(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol()
- ; (Level, Opt,
- Value, Domain, Type, Protocol) -> term() when
- Level :: integer(),
- Opt :: integer(),
- Value :: binary(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol().
-
-enc_setopt_value(otp, debug, V, _, _, _) when is_boolean(V) ->
- V;
-enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) ->
- V;
-enc_setopt_value(otp, controlling_process, V, _, _, _) when is_pid(V) ->
- V;
-enc_setopt_value(otp, rcvbuf, V, _, _, _) when (V =:= default) ->
- 0; % This will cause the nif-code to choose the default value
-enc_setopt_value(otp, rcvbuf, V, _, _, _) when is_integer(V) andalso (V > 0) ->
- V;
-%% N: Number of reads (when specifying length = 0)
-%% V: Size of the "read" buffer
-enc_setopt_value(otp, rcvbuf, {N, BufSz} = V, _, stream = _T, _P)
- when (is_integer(N) andalso (N > 0)) andalso
- (is_integer(BufSz) andalso (BufSz > 0)) ->
- V;
-enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when (V =:= default) ->
- 0;
-enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) ->
- V;
-enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when (V =:= default) ->
- 0;
-enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) ->
- V;
-enc_setopt_value(otp, meta, V, _, _, _) ->
- V;
-enc_setopt_value(otp = L, Opt, V, _D, _T, _P) ->
- not_supported({L, Opt, V});
-
-enc_setopt_value(socket, bindtodevice, V, _D, _T, _P) when is_list(V) ->
- V;
-enc_setopt_value(socket, broadcast, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(socket, debug, V, _D, _T, _P) when is_integer(V) ->
- V;
-enc_setopt_value(socket, dontroute, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(socket, keepalive, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(socket, linger, abort, D, T, P) ->
- enc_setopt_value(socket, linger, {true, 0}, D, T, P);
-enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P)
- when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) ->
- V;
-enc_setopt_value(socket, oobinline, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(socket, passcred, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(socket, peek_off = Opt, V, _D, _T, _P) when is_integer(V) ->
- %% V;
- not_supported(Opt);
-enc_setopt_value(socket, priority, V, _D, _T, _P) when is_integer(V) ->
- V;
-enc_setopt_value(socket, rcvbuf, V, _D, _T, _P) when is_integer(V) ->
- V;
-enc_setopt_value(socket, rcvlowat, V, _D, _T, _P) when is_integer(V) ->
- V;
-enc_setopt_value(socket, rcvtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P)
- when is_integer(Sec) andalso is_integer(USec) ->
- V;
-enc_setopt_value(socket, reuseaddr, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(socket, reuseport, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) ->
- V;
-enc_setopt_value(socket, sndlowat, V, _D, _T, _P) when is_integer(V) ->
- V;
-enc_setopt_value(socket, sndtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P)
- when is_integer(Sec) andalso is_integer(USec) ->
- V;
-enc_setopt_value(socket, timestamp, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(socket = L, Opt, V, _D, _T, _P) ->
- not_supported({L, Opt, V});
-
-enc_setopt_value(ip, add_membership, #{multiaddr := MA,
- interface := IF} = V, _D, _T, _P)
- when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
- ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) ->
- V;
-enc_setopt_value(ip, add_source_membership, #{multiaddr := MA,
- interface := IF,
- sourceaddr := SA} = V, _D, _T, _P)
- when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
- (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
- (is_tuple(SA) andalso (size(SA) =:= 4)) ->
- V;
-enc_setopt_value(ip, block_source, #{multiaddr := MA,
- interface := IF,
- sourceaddr := SA} = V, _D, _T, _P)
- when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
- (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
- (is_tuple(SA) andalso (size(SA) =:= 4)) ->
- V;
-enc_setopt_value(ip, drop_membership, #{multiaddr := MA,
- interface := IF} = V, _D, _T, _P)
- when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
- ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) ->
- V;
-enc_setopt_value(ip, drop_source_membership, #{multiaddr := MA,
- interface := IF,
- sourceaddr := SA} = V, _D, _T, _P)
- when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
- (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
- (is_tuple(SA) andalso (size(SA) =:= 4)) ->
- V;
-enc_setopt_value(ip, freebind, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(ip, hdrincl, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(ip, minttl, V, _D, _T, _P) when is_integer(V) ->
- V;
-enc_setopt_value(ip, msfilter, null = V, _D, _T, _P) ->
- V;
-enc_setopt_value(ip, msfilter, #{multiaddr := MA,
- interface := IF,
- fmode := FMode,
- slist := SL} = V, _D, _T, _P)
- when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
- (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
- ((FMode =:= include) orelse (FMode =:= exclude)) andalso
- is_list(SL) ->
- ensure_ip_msfilter_slist(SL),
- V;
-enc_setopt_value(ip, mtu_discover, V, _D, _T, _P)
- when (V =:= want) orelse
- (V =:= dont) orelse
- (V =:= do) orelse
- (V =:= probe) orelse
- is_integer(V) ->
- V;
-enc_setopt_value(ip, multicast_all, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, multicast_if, V, _D, _T, _P)
- when (V =:= any) orelse (is_tuple(V) andalso (size(V) =:= 4)) ->
- V;
-enc_setopt_value(ip, multicast_loop, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, multicast_ttl, V, _D, _T, _P)
- when is_integer(V) andalso (0 =< V) andalso (V =< 255) ->
- V;
-enc_setopt_value(ip, nodefrag, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, pktinfo, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, recvdstaddr, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, recverr, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, recvif, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, recvopts, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, recvorigdstaddr, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, recvtos, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, recvttl, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, retopts, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, router_alert, V, _D, _T, _P)
- when is_integer(V) ->
- V;
-enc_setopt_value(ip, sendsrcaddr, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, tos, V, _D, _T, _P)
- when (V =:= lowdelay) orelse
- (V =:= throughput) orelse
- (V =:= reliability) orelse
- (V =:= mincost) orelse
- is_integer(V) ->
- V;
-enc_setopt_value(ip, transparent, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ip, ttl, V, _D, _T, _P)
- when is_integer(V) ->
- V;
-enc_setopt_value(ip, unblock_source, #{multiaddr := MA,
- interface := IF,
- sourceaddr := SA} = V, _D, _T, _P)
- when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
- (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
- (is_tuple(SA) andalso (size(SA) =:= 4)) ->
- V;
-enc_setopt_value(ip = L, Opt, V, _D, _T, _P) ->
- not_supported({L, Opt, V});
-
-enc_setopt_value(ipv6, addrform, inet = V, _D, _T, _P) ->
- enc_domain(V);
-enc_setopt_value(ipv6, add_membership, #{multiaddr := MA,
- interface := IF} = V, _D, _T, _P)
- when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso
- (is_integer(IF) andalso (IF >= 0))) ->
- V;
-%% Is this obsolete? When get, the result is enoprotoopt and in the
-%% header file it says 'obsolete'...
-%% But there might be (old?) versions of linux where it still works...
-enc_setopt_value(ipv6, authhdr, V, _D, T, _P)
- when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
- V;
-enc_setopt_value(ipv6, dstopts, V, _D, T, _P)
- when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
- V;
-enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA,
- interface := IF} = V, _D, _T, _P)
- when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso
- (is_integer(IF) andalso (IF >= 0))) ->
- V;
-enc_setopt_value(ipv6, flowinfo, V, _D, T, _P)
- when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
- V;
-enc_setopt_value(ipv6, hoplimit, V, _D, T, _P)
- when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
- V;
-enc_setopt_value(ipv6, hopopts, V, _D, T, _P)
- when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
- V;
-enc_setopt_value(ipv6, mtu, V, _D, _T, _P) when is_integer(V) ->
- V;
-enc_setopt_value(ipv6, mtu_discover, V, _D, _T, _P)
- when (V =:= want) orelse
- (V =:= dont) orelse
- (V =:= do) orelse
- (V =:= probe) orelse
- is_integer(V) ->
- V;
-enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P)
- when (V =:= default) ->
- -1;
-enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P)
- when is_integer(V) andalso (V >= 0) andalso (V =< 255) ->
- V;
-enc_setopt_value(ipv6, multicast_if, V, _D, _T, _P)
- when is_integer(V) ->
- V;
-enc_setopt_value(ipv6, multicast_loop, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ipv6, recverr, V, _D, _T, _P)
- when is_boolean(V) ->
- V;
-enc_setopt_value(ipv6, recvhoplimit, V, _D, T, _P)
- when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
- V;
-enc_setopt_value(ipv6, Opt, V, _D, _T, _P)
- when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso
- is_boolean(V) ->
- V;
-enc_setopt_value(ipv6, recvtclass, V, _D, T, _P)
- when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
- V;
-enc_setopt_value(ipv6, router_alert, V, _D, T, _P)
- when is_integer(V) andalso (T =:= raw) ->
- V;
-enc_setopt_value(ipv6, rthdr, V, _D, T, _P)
- when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
- V;
-enc_setopt_value(ipv6, tclass, V, _D, T, _P)
- when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
- V;
-enc_setopt_value(ipv6, unicast_hops, V, _D, _T, _P)
- when (V =:= default) ->
- -1;
-enc_setopt_value(ipv6, unicast_hops, V, _D, _T, _P)
- when is_integer(V) andalso (V >= 0) andalso (V =< 255) ->
- V;
-enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) ->
- V;
-enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) ->
- not_supported({L, Opt, V});
-
-enc_setopt_value(tcp, congestion, V, _D, T, P)
- when is_list(V) andalso
- (T =:= stream) andalso
- (P =:= tcp) ->
- V;
-enc_setopt_value(tcp, cork, V, _D, T, P)
- when is_boolean(V) andalso (T =:= stream) andalso (P =:= tcp) ->
- V;
-enc_setopt_value(tcp, maxseg, V, _D, T, P)
- when is_integer(V) andalso
- (T =:= stream) andalso
- (P =:= tcp) ->
- V;
-enc_setopt_value(tcp, nodelay, V, _D, T, P)
- when is_boolean(V) andalso
- (T =:= stream) andalso
- (P =:= tcp) ->
- V;
-enc_setopt_value(tcp = L, Opt, V, _D, _T, _P) ->
- not_supported({L, Opt, V});
-
-enc_setopt_value(udp, cork, V, _D, T, P)
- when is_boolean(V) andalso (T =:= dgram) andalso (P =:= udp) ->
- V;
-enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) ->
- not_supported({L, Opt});
-
-enc_setopt_value(sctp, associnfo, #{assoc_id := AssocId,
- asocmaxrxt := MaxRxt,
- num_peer_dests := NumPeerDests,
- peer_rwnd := PeerRWND,
- local_rwnd := LocalRWND,
- cookie_life := CLife} = V,
- _D, _T, P)
- when is_integer(AssocId) andalso
- is_integer(MaxRxt) andalso (MaxRxt >= 0) andalso
- is_integer(NumPeerDests) andalso (NumPeerDests >= 0) andalso
- is_integer(PeerRWND) andalso (PeerRWND >= 0) andalso
- is_integer(LocalRWND) andalso (LocalRWND >= 0) andalso
- is_integer(CLife) andalso (CLife >= 0) andalso
- (P =:= sctp) ->
- V;
-enc_setopt_value(sctp, autoclose, V, _D, _T, P)
- when is_integer(V) andalso (V >= 0) andalso (P =:= sctp) ->
- V;
-enc_setopt_value(sctp, disable_fragments, V, _D, _T, P)
- when is_boolean(V) andalso (P =:= sctp) ->
- V;
-enc_setopt_value(sctp, events, #{data_in := DataIn,
- association := Assoc,
- address := Addr,
- send_failure := SndFailure,
- peer_error := PeerError,
- shutdown := Shutdown,
- partial_delivery := PartialDelivery,
- adaptation_layer := AdaptLayer,
- authentication := Auth,
- sender_dry := SndDry} = V, _D, _T, P)
- when is_boolean(DataIn) andalso
- is_boolean(Assoc) andalso
- is_boolean(Addr) andalso
- is_boolean(SndFailure) andalso
- is_boolean(PeerError) andalso
- is_boolean(Shutdown) andalso
- is_boolean(PartialDelivery) andalso
- is_boolean(AdaptLayer) andalso
- is_boolean(Auth) andalso
- is_boolean(SndDry) andalso
- (P =:= sctp) ->
- V;
-enc_setopt_value(sctp, initmsg, #{num_outstreams := NumOut,
- max_instreams := MaxIn,
- max_attempts := MaxAttempts,
- max_init_timeo := MaxInitTO} = V,
- _D, _T, P)
- when is_integer(NumOut) andalso (NumOut >= 0) andalso
- is_integer(MaxIn) andalso (MaxIn >= 0) andalso
- is_integer(MaxAttempts) andalso (MaxAttempts >= 0) andalso
- is_integer(MaxInitTO) andalso (MaxInitTO >= 0) andalso
- (P =:= sctp) ->
- V;
-enc_setopt_value(sctp, maxseg, V, _D, _T, P)
- when is_integer(V) andalso (V >= 0) andalso (P =:= sctp) ->
- V;
-enc_setopt_value(sctp, nodelay, V, _D, _T, P)
- when is_boolean(V) andalso (P =:= sctp) ->
- V;
-enc_setopt_value(sctp, rtoinfo, #{assoc_id := AssocId,
- initial := Init,
- max := Max,
- min := Min} = V,
- _D, _T, P)
- when is_integer(AssocId) andalso
- is_integer(Init) andalso (Init >= 0) andalso
- is_integer(Max) andalso (Max >= 0) andalso
- is_integer(Min) andalso (Min >= 0) andalso
- (P =:= sctp) ->
- V;
-enc_setopt_value(sctp = L, Opt, V, _D, _T, _P) ->
- not_supported({L, Opt, V});
-
-%% enc_setopt_value(raw = L, Opt, _V, _D, _T, _P) ->
-%% not_supported({L, Opt});
-
-%% Is this correct? What about getopt?
-enc_setopt_value(L, Opt, V, _, _, _)
- when is_integer(L) andalso is_integer(Opt) andalso is_binary(V) ->
- V.
-
-
-
-
-%% +++ Encode getopt value +++
-
-enc_getopt_level(Level) ->
- enc_setopt_level(Level).
-
-
-%% +++ Encode getopt key +++
-
-enc_getopt_key(Level, Opt, Domain, Type, Protocol) ->
- enc_sockopt_key(Level, Opt, get, Domain, Type, Protocol).
-
-
-%% +++ Decode getopt value +++
-%%
-%% For the most part, we simply let the value pass through, but for some
-%% values we may need to do an actual decode.
-%%
-
-%% This string is NULL-terminated, but the general function we use
-%% in the nif code does not know that. So, deal with it here.
-dec_getopt_value(tcp = _L, congestion = _Opt, Alg, _D, _T, _P) when is_list(Alg) ->
- {Str, _} = lists:splitwith(fun(0) -> false; (_) -> true end, Alg),
- Str;
-%% Let the user deal with the rest for now...
-dec_getopt_value(_L, _Opt, V, _D, _T, _P) ->
- V.
-
-
-
-%% +++ Encode socket option key +++
-
-%% Most options are usable both for set and get, but some are
-%% are only available for e.g. get.
--spec enc_sockopt_key(Level, Opt, Direction,
- Domain, Type, Protocol) -> non_neg_integer() when
- Level :: otp,
- Direction :: set | get,
- Opt :: otp_socket_option(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol();
- (Level, Opt, Direction,
- Domain, Type, Protocol) -> non_neg_integer() when
- Level :: socket,
- Direction :: set | get,
- Opt :: socket_option(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol();
- (Level, Opt, Direction,
- Domain, Type, Protocol) -> non_neg_integer() when
- Level :: ip,
- Direction :: set | get,
- Opt :: ip_socket_option(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol();
- (Level, Opt, Direction,
- Domain, Type, Protocol) -> non_neg_integer() when
- Level :: ipv6,
- Direction :: set | get,
- Opt :: ipv6_socket_option(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol();
- (Level, Opt, Direction,
- Domain, Type, Protocol) -> non_neg_integer() when
- Level :: tcp,
- Direction :: set | get,
- Opt :: tcp_socket_option(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol();
- (Level, Opt, Direction,
- Domain, Type, Protocol) -> non_neg_integer() when
- Level :: udp,
- Direction :: set | get,
- Opt :: udp_socket_option(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol();
- (Level, Opt, Direction,
- Domain, Type, Protocol) -> non_neg_integer() when
- Level :: sctp,
- Direction :: set | get,
- Opt :: sctp_socket_option(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol();
- (Level, Opt, Direction,
- Domain, Type, Protocol) -> non_neg_integer() when
- Level :: integer(),
- Direction :: set,
- Opt :: integer(),
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol();
- (Level, Opt, Direction,
- Domain, Type, Protocol) -> {NativeOpt, ValueSize} when
- Level :: integer(),
- Direction :: get,
- Opt :: {NativeOpt, ValueSize},
- NativeOpt :: integer(),
- ValueSize :: non_neg_integer() | 'int' | 'bool',
- Domain :: domain(),
- Type :: type(),
- Protocol :: protocol().
-
-
-%% +++ OTP socket options +++
-enc_sockopt_key(otp, debug, _, _, _, _) ->
- ?ESOCK_OPT_OTP_DEBUG;
-enc_sockopt_key(otp, iow, _, _, _, _) ->
- ?ESOCK_OPT_OTP_IOW;
-enc_sockopt_key(otp, controlling_process, _, _, _, _) ->
- ?ESOCK_OPT_OTP_CTRL_PROC;
-enc_sockopt_key(otp, rcvbuf, _, _, _, _) ->
- ?ESOCK_OPT_OTP_RCVBUF;
-enc_sockopt_key(otp, rcvctrlbuf, _, _, _, _) ->
- ?ESOCK_OPT_OTP_RCVCTRLBUF;
-enc_sockopt_key(otp, sndctrlbuf, _, _, _, _) ->
- ?ESOCK_OPT_OTP_SNDCTRLBUF;
-enc_sockopt_key(otp, fd, get = _Dir, _, _, _) ->
- ?ESOCK_OPT_OTP_FD;
-enc_sockopt_key(otp, meta, _, _, _, _) ->
- ?ESOCK_OPT_OTP_META;
-enc_sockopt_key(otp = L, Opt, _, _, _, _) ->
- not_supported({L, Opt});
-
-%% +++ SOCKET socket options +++
-enc_sockopt_key(socket = _L, acceptconn = _Opt, get = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_ACCEPTCONN;
-enc_sockopt_key(socket = L, acceptconn = Opt, Dir, _D, _T, _P) ->
- not_supported({L, Opt, Dir});
-enc_sockopt_key(socket = L, acceptfilter = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-%% Before linux 3.8, this socket option could be set but not get.
-%% Maximum size of buffer for name: IFNAMSIZ
-%% So, we let the implementation decide.
-enc_sockopt_key(socket = _L, bindtodevice = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_BINDTODEVICE;
-enc_sockopt_key(socket, broadcast = _Opt, _Dir, _D, dgram = _T, _P) ->
- ?ESOCK_OPT_SOCK_BROADCAST;
-enc_sockopt_key(socket = L, busy_poll = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(socket = _L, debug = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_DEBUG;
-enc_sockopt_key(socket, domain = _Opt, get = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_DOMAIN;
-enc_sockopt_key(socket, dontroute = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_DONTROUTE;
-enc_sockopt_key(socket = L, error = Opt, get = _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-%% This is only for connection-oriented sockets, but who are those?
-%% Type = stream or Protocol = tcp?
-%% For now, we just let is pass and it will fail later if not ok...
-enc_sockopt_key(socket, keepalive = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_KEEPALIVE;
-enc_sockopt_key(socket, linger = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_LINGER;
-enc_sockopt_key(socket = L, mark = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(socket = _L, oobinline = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_OOBINLINE;
-enc_sockopt_key(socket, passcred, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_PASSCRED;
-enc_sockopt_key(socket = L, peek_off = Opt, _Dir, local = _D, _T, _P) ->
- %% ?ESOCK_OPT_SOCK_PEEK_OFF;
- not_supported({L, Opt});
-enc_sockopt_key(socket = L, peercred = Opt, get = _Dir, local = _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(socket, priority = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_PRIORITY;
-enc_sockopt_key(socket, protocol = _Opt, get = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_PROTOCOL;
-enc_sockopt_key(socket, rcvbuf = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_RCVBUF;
-enc_sockopt_key(socket = L, rcvbufforce = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-%% May not work on linux.
-enc_sockopt_key(socket = _L, rcvlowat = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_RCVLOWAT;
-enc_sockopt_key(socket = _L, rcvtimeo = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_RCVTIMEO;
-enc_sockopt_key(socket = _L, reuseaddr = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_REUSEADDR;
-enc_sockopt_key(socket = _L, reuseport = _Opt, _Dir, D, _T, _P)
- when ((D =:= inet) orelse (D =:= inet6)) ->
- ?ESOCK_OPT_SOCK_REUSEPORT;
-enc_sockopt_key(socket = L, rxq_ovfl = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(socket = L, setfib = Opt, set = _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(socket = _L, sndbuf = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_SNDBUF;
-enc_sockopt_key(socket = L, sndbufforce = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-%% Not changeable on linux.
-enc_sockopt_key(socket = _L, sndlowat = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_SNDLOWAT;
-enc_sockopt_key(socket = _L, sndtimeo = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_SNDTIMEO;
-enc_sockopt_key(socket = _L, timestamp = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_TIMESTAMP;
-enc_sockopt_key(socket = _L, type = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SOCK_TYPE;
-enc_sockopt_key(socket = L, UnknownOpt, _Dir, _D, _T, _P) ->
- unknown({L, UnknownOpt});
-
-%% +++ IP socket options +++
-enc_sockopt_key(ip = _L, add_membership = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_ADD_MEMBERSHIP;
-enc_sockopt_key(ip = _L, add_source_membership = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_ADD_SOURCE_MEMBERSHIP;
-enc_sockopt_key(ip = _L, block_source = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_BLOCK_SOURCE;
-%% FreeBSD only?
-%% Only respected on udp and raw ip (unless the hdrincl option has been set).
-enc_sockopt_key(ip = L, dontfrag = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ip = _L, drop_membership = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_DROP_MEMBERSHIP;
-enc_sockopt_key(ip = _L, drop_source_membership = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_DROP_SOURCE_MEMBERSHIP;
-%% Linux only?
-enc_sockopt_key(ip = _L, freebind = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_FREEBIND;
-enc_sockopt_key(ip = _L, hdrincl = _Opt, _Dir, _D, raw = _T, _P) ->
- ?ESOCK_OPT_IP_HDRINCL;
-enc_sockopt_key(ip = _L, minttl = _Opt, _Dir, _D, raw = _T, _P) ->
- ?ESOCK_OPT_IP_MINTTL;
-enc_sockopt_key(ip = _L, msfilter = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_MSFILTER;
-enc_sockopt_key(ip = _L, mtu = _Opt, get = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_MTU;
-enc_sockopt_key(ip = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_MTU_DISCOVER;
-enc_sockopt_key(ip = _L, multicast_all = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_MULTICAST_ALL;
-enc_sockopt_key(ip = _L, multicast_if = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_MULTICAST_IF;
-enc_sockopt_key(ip = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_MULTICAST_LOOP;
-enc_sockopt_key(ip = _L, multicast_ttl = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_MULTICAST_TTL;
-enc_sockopt_key(ip = _L, nodefrag = _Opt, _Dir, _D, raw = _T, _P) ->
- ?ESOCK_OPT_IP_NODEFRAG;
-enc_sockopt_key(ip = L, options = Opt, _Dir, _D, _T, _P) ->
- not_supported({Opt, L});
-enc_sockopt_key(ip = _L, pktinfo = _Opt, _Dir, _D, dgram = _T, _P) ->
- ?ESOCK_OPT_IP_PKTINFO;
-enc_sockopt_key(ip = _L, recvdstaddr = _Opt, _Dir, _D, T, _P) when (T =:= dgram) ->
- ?ESOCK_OPT_IP_RECVDSTADDR;
-enc_sockopt_key(ip = _L, recverr = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_RECVERR;
-enc_sockopt_key(ip = _L, recvif = _Opt, _Dir, _D, T, _P)
- when (T =:= dgram) orelse (T =:= raw) ->
- ?ESOCK_OPT_IP_RECVIF;
-enc_sockopt_key(ip = _L, recvopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) ->
- ?ESOCK_OPT_IP_RECVOPTS;
-enc_sockopt_key(ip = _L, recvorigdstaddr = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_RECVORIGDSTADDR;
-enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_RECVTOS;
-enc_sockopt_key(ip = _L, recvttl = _Opt, _Dir, _D, T, _P) when (T =/= stream) ->
- ?ESOCK_OPT_IP_RECVTTL;
-enc_sockopt_key(ip = _L, retopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) ->
- ?ESOCK_OPT_IP_RETOPTS;
-enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) ->
- ?ESOCK_OPT_IP_ROUTER_ALERT;
-enc_sockopt_key(ip, sendsrcaddr = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_SENDSRCADDR;
-%% On FreeBSD it specifies that this option is only valid
-%% for stream, dgram and "some" raw sockets...
-%% No such condition on linux (in the man page)...
-enc_sockopt_key(ip, tos = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_TOS;
-enc_sockopt_key(ip = _L, transparent = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_TRANSPARENT;
-enc_sockopt_key(ip, ttl = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_TTL;
-enc_sockopt_key(ip = _L, unblock_source = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IP_UNBLOCK_SOURCE;
-enc_sockopt_key(ip = L, UnknownOpt, _Dir, _D, _T, _P) ->
- unknown({L, UnknownOpt});
-
-%% IPv6 socket options
-enc_sockopt_key(ipv6 = _L, addrform = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_ADDRFORM;
-enc_sockopt_key(ipv6, add_membership = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_ADD_MEMBERSHIP;
-enc_sockopt_key(ipv6 = _L, authhdr = _Opt, _Dir, _D, T, _P)
- when ((T =:= dgram) orelse (T =:= raw)) ->
- ?ESOCK_OPT_IPV6_AUTHHDR;
-enc_sockopt_key(ipv6 = L, auth_level = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6 = L, checksum = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6, drop_membership = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_DROP_MEMBERSHIP;
-enc_sockopt_key(ipv6 = _L, dstopts = _Opt, _Dir, _D, T, _P)
- when (T =:= dgram) orelse (T =:= raw) ->
- ?ESOCK_OPT_IPV6_DSTOPTS;
-enc_sockopt_key(ipv6 = L, esp_trans_level = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6 = L, esp_network_level = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6 = _L, flowinfo = _Opt, _Dir, _D, T, _P)
- when (T =:= dgram) orelse (T =:= raw) ->
- ?ESOCK_OPT_IPV6_FLOWINFO;
-enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P)
- when (T =:= dgram) orelse (T =:= raw) ->
- ?ESOCK_OPT_IPV6_HOPLIMIT;
-enc_sockopt_key(ipv6 = _L, hopopts = _Opt, _Dir, _D, T, _P)
- when ((T =:= dgram) orelse (T =:= raw)) ->
- ?ESOCK_OPT_IPV6_HOPOPTS;
-enc_sockopt_key(ipv6 = L, ipcomp_level = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6 = L, join_group = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6 = L, leave_group = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6 = _L, mtu = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_MTU;
-enc_sockopt_key(ipv6 = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_MTU_DISCOVER;
-enc_sockopt_key(ipv6 = _L, multicast_hops = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_MULTICAST_HOPS;
-enc_sockopt_key(ipv6 = _L, multicast_if = _Opt, _Dir, _D, T, _P)
- when (T =:= dgram) orelse (T =:= raw) ->
- ?ESOCK_OPT_IPV6_MULTICAST_IF;
-enc_sockopt_key(ipv6 = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_MULTICAST_LOOP;
-enc_sockopt_key(ipv6 = L, portrange = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6 = L, pktoptions = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6 = _L, recverr = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_RECVERR;
-enc_sockopt_key(ipv6, recvhoplimit = _Opt, _Dir, _D, T, _P)
- when (T =:= dgram) orelse (T =:= raw) ->
- ?ESOCK_OPT_IPV6_RECVHOPLIMIT;
-enc_sockopt_key(ipv6 = _L, Opt, _Dir, _D, T, _P)
- when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso
- ((T =:= dgram) orelse (T =:= raw)) ->
- ?ESOCK_OPT_IPV6_RECVPKTINFO;
-enc_sockopt_key(ipv6 = _L, recvtclass = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_RECVTCLASS;
-enc_sockopt_key(ipv6 = _L, router_alert = _Opt, _Dir, _D, T, _P) when (T =:= raw) ->
- ?ESOCK_OPT_IPV6_ROUTER_ALERT;
-enc_sockopt_key(ipv6 = _L, rthdr = _Opt, _Dir, _D, T, _P)
- when ((T =:= dgram) orelse (T =:= raw)) ->
- ?ESOCK_OPT_IPV6_RTHDR;
-enc_sockopt_key(ipv6 = _L, tclass = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_TCLASS;
-enc_sockopt_key(ipv6 = _L, unicast_hops = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_UNICAST_HOPS;
-enc_sockopt_key(ipv6 = L, use_min_mtu = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(ipv6 = _L, v6only = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_IPV6_V6ONLY;
-enc_sockopt_key(ipv6 = L, UnknownOpt, _Dir, _D, _T, _P) ->
- unknown({L, UnknownOpt});
-
-%% TCP socket options
-%% There are other options that would be useful; info,
-%% but they are difficult to get portable...
-enc_sockopt_key(tcp, congestion = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_TCP_CONGESTION;
-enc_sockopt_key(tcp = _L, cork = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_TCP_CORK;
-enc_sockopt_key(tcp = L, keepidle = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(tcp = L, keepintvl = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(tcp = L, keepcnt = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(tcp, maxseg = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_TCP_MAXSEG;
-enc_sockopt_key(tcp = L, md5sig = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(tcp, nodelay = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_TCP_NODELAY;
-enc_sockopt_key(tcp = L, noopt = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(tcp = L, nopush = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(tcp = L, syncnt = Opt, _Dir, _D, _T, _P) -> % Only set? 1..255
- not_supported({L, Opt});
-enc_sockopt_key(tcp = L, user_timeout = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(tcp = L, UnknownOpt, _Dir, _D, _T, _P) ->
- unknown({L, UnknownOpt});
-
-%% UDP socket options
-enc_sockopt_key(udp, cork = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_UDP_CORK;
-enc_sockopt_key(udp = L, UnknownOpt, _Dir, _D, _T, _P) ->
- unknown({L, UnknownOpt});
-
-%% SCTP socket options
-enc_sockopt_key(sctp = L, adaption_layer = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = _L, associnfo = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SCTP_ASSOCINFO;
-enc_sockopt_key(sctp = L, auth_active_key = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, auth_asconf = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, auth_chunk = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, auth_key = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, auth_delete_key = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp, autoclose = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SCTP_AUTOCLOSE;
-enc_sockopt_key(sctp = L, context = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, default_send_params = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, delayed_ack_time = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = _L, disable_fragments = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SCTP_DISABLE_FRAGMENTS;
-enc_sockopt_key(sctp = L, hmac_ident = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = _L, events = _Opt, set = _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SCTP_EVENTS;
-enc_sockopt_key(sctp = L, explicit_eor = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, fragment_interleave = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, get_peer_addr_info = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = _L, initmsg = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SCTP_INITMSG;
-enc_sockopt_key(sctp = L, i_want_mapped_v4_addr = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, local_auth_chunks = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = _L, maxseg = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SCTP_MAXSEG;
-enc_sockopt_key(sctp = L, maxburst = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp, nodelay = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SCTP_NODELAY;
-enc_sockopt_key(sctp = L, partial_delivery_point = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, peer_addr_params = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, peer_auth_chunks = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, primary_addr = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, reset_streams = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = _L, rtoinfo = _Opt, _Dir, _D, _T, _P) ->
- ?ESOCK_OPT_SCTP_RTOINFO;
-enc_sockopt_key(sctp = L, set_peer_primary_addr = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, status = Opt, get = _Dir, _D, _T, _P) ->
- not_supported({L, Opt}); % ?ESOCK_OPT_SCTP_RTOINFO;
-enc_sockopt_key(sctp = L, use_exp_recvinfo = Opt, _Dir, _D, _T, _P) ->
- not_supported({L, Opt});
-enc_sockopt_key(sctp = L, UnknownOpt, _Dir, _D, _T, _P) ->
- unknown({L, UnknownOpt});
-
-%% +++ "Native" socket options +++
-enc_sockopt_key(Level, Opt, set = _Dir, _D, _T, _P)
- when is_integer(Level) andalso is_integer(Opt) ->
- Opt;
-enc_sockopt_key(Level, {NativeOpt, ValueSize} = Opt, get = _Dir, _D, _T, _P)
- when is_integer(Level) andalso
- is_integer(NativeOpt) andalso
- ((is_integer(ValueSize) andalso (ValueSize >= 0)) orelse
- ((ValueSize =:= int) orelse (ValueSize =:= bool))) ->
- Opt;
-
-enc_sockopt_key(Level, Opt, _Dir, _Domain, _Type, _Protocol) ->
- unknown({Level, Opt}).
-
-
-
-enc_shutdown_how(read) ->
- ?ESOCK_SHUTDOWN_HOW_READ;
-enc_shutdown_how(write) ->
- ?ESOCK_SHUTDOWN_HOW_WRITE;
-enc_shutdown_how(read_write) ->
- ?ESOCK_SHUTDOWN_HOW_READ_WRITE.
-
-
-
-
-%% ===========================================================================
-%%
-%% Misc utility functions
-%%
-%% ===========================================================================
-
--dialyzer({nowarn_function, ensure_ip_msfilter_slist/1}).
-ensure_ip_msfilter_slist(SL) ->
- EnsureSA = fun(SA) when is_tuple(SA) andalso (size(SA) =:= 4) -> ok;
- (_) -> einval()
- end,
- lists:foreach(EnsureSA, SL).
-
-
-ensure_sockaddr(#{family := inet} = SockAddr) ->
- maps:merge(?SOCKADDR_IN4_DEFAULTS, SockAddr);
-ensure_sockaddr(#{family := inet6} = SockAddr) ->
- maps:merge(?SOCKADDR_IN6_DEFAULTS, SockAddr);
-ensure_sockaddr(#{family := local, path := Path} = SockAddr)
- when is_list(Path) andalso
- (length(Path) > 0) andalso
- (length(Path) =< 255) ->
- BinPath = unicode:characters_to_binary(Path, file:native_name_encoding()),
- ensure_sockaddr(SockAddr#{path => BinPath});
-ensure_sockaddr(#{family := local, path := Path} = SockAddr)
- when is_binary(Path) andalso
- (byte_size(Path) > 0) andalso
- (byte_size(Path) =< 255) ->
- SockAddr;
-ensure_sockaddr(SockAddr) ->
- invalid_address(SockAddr).
-
-
-
-cancel(SockRef, Op, OpRef) ->
- case nif_cancel(SockRef, Op, OpRef) of
- %% The select has already completed
- {error, select_sent} ->
- flush_select_msg(SockRef, OpRef),
- _ = flush_abort_msg(SockRef, OpRef),
- ok;
- Other ->
- _ = flush_abort_msg(SockRef, OpRef),
- Other
- end.
-
-flush_select_msg(SockRef, Ref) ->
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, select, Ref} ->
- ok
- after 0 ->
- ok
- end.
-
-flush_abort_msg(SockRef, Ref) ->
- receive
- {?ESOCK_TAG, #socket{ref = SockRef}, abort, {Ref, Reason}} ->
- Reason
- after 0 ->
- ok
- end.
-
-%% formated_timestamp() ->
-%% format_timestamp(os:timestamp()).
-
-%% format_timestamp(Now) ->
-%% N2T = fun(N) -> calendar:now_to_local_time(N) end,
-%% format_timestamp(Now, N2T, true).
-
-%% format_timestamp({_N1, _N2, N3} = N, N2T, true) ->
-%% FormatExtra = ".~.2.0w",
-%% ArgsExtra = [N3 div 10000],
-%% format_timestamp(N, N2T, FormatExtra, ArgsExtra);
-%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) ->
-%% FormatExtra = "",
-%% ArgsExtra = [],
-%% format_timestamp(N, N2T, FormatExtra, ArgsExtra).
-
-%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) ->
-%% {Date, Time} = N2T(N),
-%% {YYYY,MM,DD} = Date,
-%% {Hour,Min,Sec} = Time,
-%% FormatDate =
-%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra,
-%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra),
-%% lists:flatten(FormatDate).
-
-
-deadline(Timeout) ->
- case Timeout of
- nowait -> Timeout;
- infinity -> Timeout;
- _ when is_integer(Timeout), 0 =< Timeout ->
- timestamp() + Timeout;
- _ ->
- invalid_timeout(Timeout)
- end.
-
-timeout(Deadline) ->
- case Deadline of
- nowait -> 0;
- infinity -> infinity;
- _ ->
- Now = timestamp(),
- if
- Now < Deadline -> Deadline - Now;
- true -> 0
- end
- end.
-
-timestamp() ->
- erlang:monotonic_time(milli_seconds).
-
-
--compile({inline, [bincat/2]}).
-bincat(<<>>, <<_/binary>> = B) -> B;
-bincat(<<_/binary>> = A, <<>>) -> A;
-bincat(<<_/binary>> = A, <<_/binary>> = B) ->
- <<A/binary, B/binary>>.
-
-
-%% p(F) ->
-%% p(F, []).
-
-%% p(F, A) ->
-%% p(get(sname), F, A).
-
-%% p(undefined, F, A) ->
-%% p("***", F, A);
-%% p(SName, F, A) ->
-%% TS = formated_timestamp(),
-%% io:format(user,"[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]),
-%% io:format("[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]).
-
-
-
-%% ===========================================================================
-%%
-%% Error functions
-%%
-%% ===========================================================================
-
--spec invalid_domain(Domain) -> no_return() when
- Domain :: term().
-invalid_domain(Domain) ->
- error({invalid_domain, Domain}).
-
--spec invalid_type(Type) -> no_return() when
- Type :: term().
-invalid_type(Type) ->
- error({invalid_type, Type}).
-
--spec invalid_protocol(Proto) -> no_return() when
- Proto :: term().
-invalid_protocol(Proto) ->
- error({invalid_protocol, Proto}).
-
--spec invalid_address(SockAddr) -> no_return() when
- SockAddr :: term().
-invalid_address(SockAddr) ->
- error({invalid_address, SockAddr}).
-
--spec invalid_timeout(Timeout) -> no_return() when
- Timeout :: term().
-invalid_timeout(Timeout) ->
- error({invalid_timeout, Timeout}).
-
--spec not_supported(What) -> no_return() when
- What :: term().
-not_supported(What) ->
- error({not_supported, What}).
-
--spec unknown(What) -> no_return() when
- What :: term().
-unknown(What) ->
- error({unknown, What}).
-
--spec einval() -> no_return().
-einval() ->
- error(einval).
-
--spec error(Reason) -> no_return() when
- Reason :: term().
-error(Reason) ->
- throw({error, Reason}).
-
-
-%% ===========================================================================
-%%
-%% Below follows the actual NIF-functions.
-%%
-%% ===========================================================================
-
-nif_info() ->
- erlang:nif_error(undef).
-
-nif_info(_SRef) ->
- erlang:nif_error(undef).
-
-nif_command(_Command) ->
- erlang:nif_error(undef).
-
-nif_supports(_Key) ->
- erlang:nif_error(undef).
-
-nif_open(_Domain, _Type, _Protocol, _Extra) ->
- erlang:nif_error(undef).
-
-nif_bind(_SRef, _SockAddr) ->
- erlang:nif_error(undef).
-
-nif_bind(_SRef, _SockAddrs, _Action) ->
- erlang:nif_error(undef).
-
-nif_connect(_SRef, _SockAddr) ->
- erlang:nif_error(undef).
-
-nif_finalize_connection(_SRef) ->
- erlang:nif_error(undef).
-
-nif_listen(_SRef, _Backlog) ->
- erlang:nif_error(undef).
-
-nif_accept(_SRef, _Ref) ->
- erlang:nif_error(undef).
-
-nif_send(_SockRef, _SendRef, _Data, _Flags) ->
- erlang:nif_error(undef).
-
-nif_sendto(_SRef, _SendRef, _Data, _Dest, _Flags) ->
- erlang:nif_error(undef).
-
-nif_sendmsg(_SRef, _SendRef, _MsgHdr, _Flags) ->
- erlang:nif_error(undef).
-
-nif_recv(_SRef, _RecvRef, _Length, _Flags) ->
- erlang:nif_error(undef).
-
-nif_recvfrom(_SRef, _RecvRef, _Length, _Flags) ->
- erlang:nif_error(undef).
-
-nif_recvmsg(_SRef, _RecvRef, _BufSz, _CtrlSz, _Flags) ->
- erlang:nif_error(undef).
-
-nif_cancel(_SRef, _Op, _Ref) ->
- erlang:nif_error(undef).
-
-nif_close(_SRef) ->
- erlang:nif_error(undef).
-
-nif_shutdown(_SRef, _How) ->
- erlang:nif_error(undef).
-
-nif_finalize_close(_SRef) ->
- erlang:nif_error(undef).
-
-nif_setopt(_Ref, _IsEnc, _Lev, _Key, _Val) ->
- erlang:nif_error(undef).
-
-nif_getopt(_Ref, _IsEnc, _Lev, _Key) ->
- erlang:nif_error(undef).
-
-nif_sockname(_Ref) ->
- erlang:nif_error(undef).
-
-nif_peername(_Ref) ->
- erlang:nif_error(undef).
-
diff --git a/erts/preloaded/src/zlib.erl b/erts/preloaded/src/zlib.erl
index 6f53e67901..36946a3406 100644
--- a/erts/preloaded/src/zlib.erl
+++ b/erts/preloaded/src/zlib.erl
@@ -58,7 +58,7 @@
-define(Z_BEST_COMPRESSION, 9).
-define(Z_DEFAULT_COMPRESSION, (-1)).
-%% compresssion strategy
+%% compression strategy
-define(Z_FILTERED, 1).
-define(Z_HUFFMAN_ONLY, 2).
-define(Z_RLE, 3).
diff --git a/erts/test/erl_print_SUITE.erl b/erts/test/erl_print_SUITE.erl
index 8735d3c427..544d1a8dcb 100644
--- a/erts/test/erl_print_SUITE.erl
+++ b/erts/test/erl_print_SUITE.erl
@@ -324,6 +324,9 @@ run_case(Config, TestArgs, Fun) ->
-define(PORT_EXT, 102).
-define(PID_EXT, 103).
-define(NEW_REFERENCE_EXT, 114).
+-define(NEW_PID_EXT, $X).
+-define(NEW_PORT_EXT, $Y).
+-define(NEWER_REFERENCE_EXT, $Z).
uint32_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 32 ->
[(Uint bsr 24) band 16#ff,
@@ -351,13 +354,13 @@ mk_pid({NodeName, Creation}, Number, Serial) when is_atom(NodeName) ->
mk_pid({atom_to_list(NodeName), Creation}, Number, Serial);
mk_pid({NodeName, Creation}, Number, Serial) ->
case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ?PID_EXT,
+ ?NEW_PID_EXT,
?ATOM_EXT,
uint16_be(length(NodeName)),
NodeName,
uint32_be(Number),
uint32_be(Serial),
- uint8(Creation)])) of
+ uint32_be(Creation)])) of
Pid when is_pid(Pid) ->
Pid;
{'EXIT', {badarg, _}} ->
@@ -370,12 +373,12 @@ mk_port({NodeName, Creation}, Number) when is_atom(NodeName) ->
mk_port({atom_to_list(NodeName), Creation}, Number);
mk_port({NodeName, Creation}, Number) ->
case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ?PORT_EXT,
+ ?NEW_PORT_EXT,
?ATOM_EXT,
uint16_be(length(NodeName)),
NodeName,
uint32_be(Number),
- uint8(Creation)])) of
+ uint32_be(Creation)])) of
Port when is_port(Port) ->
Port;
{'EXIT', {badarg, _}} ->
@@ -388,33 +391,16 @@ mk_ref({NodeName, Creation}, Numbers) when is_atom(NodeName),
is_integer(Creation),
is_list(Numbers) ->
mk_ref({atom_to_list(NodeName), Creation}, Numbers);
-mk_ref({NodeName, Creation}, [Number]) when is_list(NodeName),
- is_integer(Creation),
- is_integer(Number) ->
- case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ?REFERENCE_EXT,
- ?ATOM_EXT,
- uint16_be(length(NodeName)),
- NodeName,
- uint32_be(Number),
- uint8(Creation)])) of
- Ref when is_reference(Ref) ->
- Ref;
- {'EXIT', {badarg, _}} ->
- exit({badarg, mk_ref, [{NodeName, Creation}, [Number]]});
- Other ->
- exit({unexpected_binary_to_term_result, Other})
- end;
mk_ref({NodeName, Creation}, Numbers) when is_list(NodeName),
is_integer(Creation),
is_list(Numbers) ->
case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ?NEW_REFERENCE_EXT,
+ ?NEWER_REFERENCE_EXT,
uint16_be(length(Numbers)),
?ATOM_EXT,
uint16_be(length(NodeName)),
NodeName,
- uint8(Creation),
+ uint32_be(Creation),
lists:map(fun (N) ->
uint32_be(N)
end,
@@ -429,11 +415,10 @@ mk_ref({NodeName, Creation}, Numbers) when is_list(NodeName),
my_cre() -> erlang:system_info(creation).
-oth_cre(0) -> 1;
-oth_cre(1) -> 2;
-oth_cre(2) -> 3;
-oth_cre(3) -> 1;
-oth_cre(N) -> exit({invalid_creation, N}).
+oth_cre(N) when N >= 0, N < (1 bsl 32) ->
+ (N rem ((1 bsl 32) - 1)) + 1;
+oth_cre(N) ->
+ exit({invalid_creation, N}).
str_1_bsl_10000() ->
"19950631168807583848837421626835850838234968318861924548520089498529438830221946631919961684036194597899331129423209124271556491349413781117593785932096323957855730046793794526765246551266059895520550086918193311542508608460618104685509074866089624888090489894838009253941633257850621568309473902556912388065225096643874441046759871626985453222868538161694315775629640762836880760732228535091641476183956381458969463899410840960536267821064621427333394036525565649530603142680234969400335934316651459297773279665775606172582031407994198179607378245683762280037302885487251900834464581454650557929601414833921615734588139257095379769119277800826957735674444123062018757836325502728323789270710373802866393031428133241401624195671690574061419654342324638801248856147305207431992259611796250130992860241708340807605932320161268492288496255841312844061536738951487114256315111089745514203313820202931640957596464756010405845841566072044962867016515061920631004186422275908670900574606417856951911456055068251250406007519842261898059237118054444788072906395242548339221982707404473162376760846613033778706039803413197133493654622700563169937455508241780972810983291314403571877524768509857276937926433221599399876886660808368837838027643282775172273657572744784112294389733810861607423253291974813120197604178281965697475898164531258434135959862784130128185406283476649088690521047580882615823961985770122407044330583075869039319604603404973156583208672105913300903752823415539745394397715257455290510212310947321610753474825740775273986348298498340756937955646638621874569499279016572103701364433135817214311791398222983845847334440270964182851005072927748364550578634501100852987812389473928699540834346158807043959118985815145779177143619698728131459483783202081474982171858011389071228250905826817436220577475921417653715687725614904582904992461028630081535583308130101987675856234343538955409175623400844887526162643568648833519463720377293240094456246923254350400678027273837755376406726898636241037491410966718557050759098100246789880178271925953381282421954028302759408448955014676668389697996886241636313376393903373455801407636741877711055384225739499110186468219696581651485130494222369947714763069155468217682876200362777257723781365331611196811280792669481887201298643660768551639860534602297871557517947385246369446923087894265948217008051120322365496288169035739121368338393591756418733850510970271613915439590991598154654417336311656936031122249937969999226781732358023111862644575299135758175008199839236284615249881088960232244362173771618086357015468484058622329792853875623486556440536962622018963571028812361567512543338303270029097668650568557157505516727518899194129711337690149916181315171544007728650573189557450920330185304847113818315407324053319038462084036421763703911550639789000742853672196280903477974533320468368795868580237952218629120080742819551317948157624448298518461509704888027274721574688131594750409732115080498190455803416826949787141316063210686391511681774304792596709376".
diff --git a/erts/test/erlexec_SUITE.erl b/erts/test/erlexec_SUITE.erl
index 628166f7a4..c210c5479b 100644
--- a/erts/test/erlexec_SUITE.erl
+++ b/erts/test/erlexec_SUITE.erl
@@ -129,21 +129,26 @@ args_file(Config) when is_list(Config) ->
" -args_file ~s#acomment~n"
"~n"
"-MiscArg7~n"
+ "-MiscArg8 'val with double space'~n"
+ "-MiscArg9 '~n'~n"
+ "-MiscArg10 \\~n\\\t~n"
"#~n"
"+\\#700~n"
+ "#~s~n"
"-extra +XtraArg6~n",
- [AFN2]),
+ [AFN2,lists:duplicate(1024*1024, $a)]),
write_file(AFN2,
- "-MiscArg3~n"
+ "-MiscArg3 \t\v\f\r\n ~n"
"+\\#300~n"
"-args_file ~s~n"
- "-MiscArg5~n"
- "+\\#500#anothercomment -MiscArg10~n"
+ "-MiscArg5 ' '~n"
+ "+\\#500#anothercomment -MiscArg11~n"
"-args_file ~s~n"
"-args_file ~s~n"
"-args_file ~s~n"
+ "# ~s~n"
"-extra +XtraArg5~n",
- [AFN3, AFN4, AFN5, AFN6]),
+ [AFN3, AFN4, AFN5, AFN6,lists:duplicate(1758, $a)]),
write_file(AFN3,
"# comment again~n"
" -MiscArg4 +\\#400 -extra +XtraArg1"),
@@ -156,33 +161,37 @@ args_file(Config) when is_list(Config) ->
write_file(AFN6, "-extra # +XtraArg10~n"),
CmdLine = "+#100 -MiscArg1 "
++ "-args_file " ++ AFN1
- ++ " +#800 -MiscArg8 -extra +XtraArg7 +XtraArg8",
+ ++ " +#800 -MiscArgCLI \\\t -extra +XtraArg7 +XtraArg8",
{Emu, Misc, Extra} = emu_args(CmdLine),
verify_args(["-#100", "-#200", "-#300", "-#400",
"-#500", "-#600", "-#700", "-#800"], Emu),
verify_args(["-MiscArg1", "-MiscArg2", "-MiscArg3", "-MiscArg4",
- "-MiscArg5", "-MiscArg6", "-MiscArg7", "-MiscArg8"],
+ "-MiscArg5", " ", "-MiscArg6", "-MiscArg7", "-MiscArg8",
+ "val with double space",
+ "-MiscArg9","\n","-MiscArg10","\n\t","-MiscArgCLI"],
Misc),
verify_args(["+XtraArg1", "+XtraArg2", "+XtraArg3", "+XtraArg4",
"+XtraArg5", "+XtraArg6", "+XtraArg7", "+XtraArg8"],
Extra),
- verify_not_args(["-MiscArg10", "-#1000", "+XtraArg10",
+ verify_not_args(["","-MiscArg11", "-#1000", "+XtraArg10",
"-MiscArg1", "-MiscArg2", "-MiscArg3", "-MiscArg4",
"-MiscArg5", "-MiscArg6", "-MiscArg7", "-MiscArg8",
+ "-MiscArg9", "-MiscArg10","-MiscArgCLI",
"+XtraArg1", "+XtraArg2", "+XtraArg3", "+XtraArg4",
"+XtraArg5", "+XtraArg6", "+XtraArg7", "+XtraArg8"],
Emu),
- verify_not_args(["-MiscArg10", "-#1000", "+XtraArg10",
+ verify_not_args(["","-MiscArg11", "-#1000", "+XtraArg10",
"-#100", "-#200", "-#300", "-#400",
"-#500", "-#600", "-#700", "-#800",
"+XtraArg1", "+XtraArg2", "+XtraArg3", "+XtraArg4",
"+XtraArg5", "+XtraArg6", "+XtraArg7", "+XtraArg8"],
Misc),
- verify_not_args(["-MiscArg10", "-#1000", "+XtraArg10",
+ verify_not_args(["","-MiscArg11", "-#1000", "+XtraArg10",
"-#100", "-#200", "-#300", "-#400",
"-#500", "-#600", "-#700", "-#800",
"-MiscArg1", "-MiscArg2", "-MiscArg3", "-MiscArg4",
- "-MiscArg5", "-MiscArg6", "-MiscArg7", "-MiscArg8"],
+ "-MiscArg5", "-MiscArg6", "-MiscArg7", "-MiscArg8",
+ "-MiscArg9","-MiscArg10","-MiscArgCLI"],
Extra),
ok.
@@ -220,8 +229,7 @@ evil_args_file(Config) when is_list(Config) ->
ANums),
Misc),
ok.
-
-
+
env(Config) when is_list(Config) ->
os:putenv("ERL_AFLAGS", "-MiscArg1 +#100 -extra +XtraArg1 +XtraArg2"),
@@ -398,10 +406,13 @@ verify_args([], _Ys) ->
ok;
verify_args(Xs, []) ->
exit({args_not_found_in_order, Xs});
-verify_args([X|Xs], [X|Ys]) ->
- verify_args(Xs, Ys);
-verify_args(Xs, [_Y|Ys]) ->
- verify_args(Xs, Ys).
+verify_args([X|Xs], [Y|Ys]) ->
+ case string:equal(string:replace(X,"\r\n","\n"),string:replace(Y,"\r\n","\n")) of
+ true ->
+ verify_args(Xs,Ys);
+ false ->
+ verify_args(Xs,[Y|Ys])
+ end.
verify_not_args(Xs, Ys) ->
lists:foreach(fun (X) ->
@@ -414,9 +425,9 @@ verify_not_args(Xs, Ys) ->
emu_args(CmdLineArgs) ->
io:format("CmdLineArgs = ~ts~n", [CmdLineArgs]),
{ok,[[Erl]]} = init:get_argument(progname),
- EmuCL = os:cmd(Erl ++ " -emu_args_exit " ++ CmdLineArgs),
- io:format("EmuCL = ~ts", [EmuCL]),
- split_emu_clt(string:lexemes(EmuCL, [$ ,$\t,$\n,[$\r,$\n]])).
+ EmuCL = os:cmd(Erl ++ " -emu_qouted_cmd_exit " ++ CmdLineArgs),
+ ct:pal("EmuCL = ~ts", [EmuCL]),
+ split_emu_clt(string:split(string:trim(EmuCL,both,"\n \""), "\" \"", all)).
split_emu_clt(EmuCLT) ->
split_emu_clt(EmuCLT, [], [], [], emu).
diff --git a/erts/test/erlexec_SUITE_data/erlexec_tests.c b/erts/test/erlexec_SUITE_data/erlexec_tests.c
index bd28d2900c..057d674a8f 100644
--- a/erts/test/erlexec_SUITE_data/erlexec_tests.c
+++ b/erts/test/erlexec_SUITE_data/erlexec_tests.c
@@ -23,7 +23,7 @@
* Author: Sverker Eriksson
*/
-#if defined (__WIN32__) || defined(VXWORKS)
+#if defined (__WIN32__)
int main() {return 0;}
#else /* UNIX only */
diff --git a/erts/test/ethread_SUITE_data/ethread_tests.c b/erts/test/ethread_SUITE_data/ethread_tests.c
index fe7f92b012..87f59f1adc 100644
--- a/erts/test/ethread_SUITE_data/ethread_tests.c
+++ b/erts/test/ethread_SUITE_data/ethread_tests.c
@@ -217,16 +217,23 @@ create_join_thread_test(void)
* Tests ethr_equal_tids.
*/
-#define ETT_THREADS 100000
+#define ETT_THREADS 1000
static ethr_tid ett_tids[3];
static ethr_mutex ett_mutex;
static ethr_cond ett_cond;
static int ett_terminate;
+static int ett_thread_go;
static void *
ett_thread(void *my_tid)
{
+ ethr_mutex_lock(&ett_mutex);
+ while (!ett_thread_go) {
+ int res = ethr_cond_wait(&ett_cond, &ett_mutex);
+ ASSERT(res == 0);
+ }
+ ethr_mutex_unlock(&ett_mutex);
ASSERT(!ethr_equal_tids(ethr_self(), ett_tids[0]));
ASSERT(ethr_equal_tids(ethr_self(), *((ethr_tid *) my_tid)));
@@ -257,18 +264,36 @@ equal_tids_test(void)
res = ethr_cond_init(&ett_cond);
ASSERT(res == 0);
ett_tids[0] = ethr_self();
+
+ ethr_mutex_lock(&ett_mutex);
+ ett_thread_go = 0;
+ ethr_mutex_unlock(&ett_mutex);
res = ethr_thr_create(&ett_tids[1], ett_thread, (void *) &ett_tids[1], NULL);
ASSERT(res == 0);
+ ethr_mutex_lock(&ett_mutex);
+ ett_thread_go = 1;
+ ethr_cond_signal(&ett_cond);
+ ethr_mutex_unlock(&ett_mutex);
+
ASSERT(ethr_equal_tids(ethr_self(), ett_tids[0]));
ASSERT(!ethr_equal_tids(ethr_self(), ett_tids[1]));
res = ethr_thr_join(ett_tids[1], NULL);
+ ethr_mutex_lock(&ett_mutex);
+ ett_thread_go = 0;
+ ethr_mutex_unlock(&ett_mutex);
+
res = ethr_thr_create(&ett_tids[2], ett_thread, (void *) &ett_tids[2], NULL);
ASSERT(res == 0);
+ ethr_mutex_lock(&ett_mutex);
+ ett_thread_go = 1;
+ ethr_cond_signal(&ett_cond);
+ ethr_mutex_unlock(&ett_mutex);
+
ASSERT(ethr_equal_tids(ethr_self(), ett_tids[0]));
ASSERT(!ethr_equal_tids(ethr_self(), ett_tids[1]));
ASSERT(!ethr_equal_tids(ethr_self(), ett_tids[2]));
@@ -294,9 +319,18 @@ equal_tids_test(void)
ASSERT(!ethr_equal_tids(ett_tids[0], ett_tids[1]));
for (i = 0; i < ETT_THREADS; i++) {
+ ethr_mutex_lock(&ett_mutex);
+ ett_thread_go = 0;
+ ethr_mutex_unlock(&ett_mutex);
+
res = ethr_thr_create(&ett_tids[2], ett_thread, (void*)&ett_tids[2], NULL);
ASSERT(res == 0);
+ ethr_mutex_lock(&ett_mutex);
+ ett_thread_go = 1;
+ ethr_cond_broadcast(&ett_cond);
+ ethr_mutex_unlock(&ett_mutex);
+
ASSERT(!ethr_equal_tids(ett_tids[0], ett_tids[2]));
ASSERT(!ethr_equal_tids(ett_tids[1], ett_tids[2]));
diff --git a/erts/test/nt_SUITE_data/nt_info.c b/erts/test/nt_SUITE_data/nt_info.c
index 8ef52cad2d..87917a1559 100644
--- a/erts/test/nt_SUITE_data/nt_info.c
+++ b/erts/test/nt_SUITE_data/nt_info.c
@@ -27,12 +27,7 @@
#include <stdlib.h>
#include <string.h>
-#if defined(VXWORKS)
-int nt_info(int argc, char **argv){
- printf("Hello lvsj!\n");
- return 0;
-}
-#elif !defined(__WIN32__)
+#if !defined(__WIN32__)
int main(int argc, char **argv){
printf("Hello lvsj!\n");
return 0;
diff --git a/erts/test/otp_SUITE.erl b/erts/test/otp_SUITE.erl
index 2372e8b9ac..3f4f3f9574 100644
--- a/erts/test/otp_SUITE.erl
+++ b/erts/test/otp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -57,8 +57,14 @@ init_per_suite(Config) ->
{error,bad_name} ->
Erts = filename:join([code:root_dir(),"erts","preloaded","ebin"]),
{ok,_} = xref:add_directory(Server, Erts, []);
- _ ->
- ok
+ LibDir ->
+ case file:read_file_info(filename:join([LibDir,"ebin"])) of
+ {error,enoent} ->
+ Erts = filename:join([LibDir, "preloaded","ebin"]),
+ {ok,_} = xref:add_directory(Server, Erts, []);
+ _ ->
+ ok
+ end
end,
[{xref_server,Server}|Config].
@@ -79,8 +85,7 @@ undefined_functions(Config) when is_list(Config) ->
[UndefS,ExcludeFrom]),
{ok,Undef0} = xref:q(Server, lists:flatten(Q)),
Undef1 = hipe_filter(Undef0),
- Undef2 = ssl_crypto_filter(Undef1),
- Undef3 = edoc_filter(Undef2),
+ Undef3 = ssl_crypto_filter(Undef1),
Undef4 = eunit_filter(Undef3),
Undef5 = dialyzer_filter(Undef4),
Undef6 = wx_filter(Undef5),
@@ -92,9 +97,9 @@ undefined_functions(Config) when is_list(Config) ->
_ ->
Fd = open_log(Config, "undefined_functions"),
foreach(fun ({MFA1,MFA2}) ->
- io:format("~s calls undefined ~s",
- [format_mfa(Server, MFA1),
- format_mfa(MFA2)]),
+ ct:pal("~s calls undefined ~s",
+ [format_mfa(Server, MFA1),
+ format_mfa(MFA2)]),
io:format(Fd, "~s ~s\n",
[format_mfa(Server, MFA1),
format_mfa(MFA2)])
@@ -157,12 +162,6 @@ ssl_crypto_filter(Undef) ->
{_,_} -> Undef
end.
-edoc_filter(Undef) ->
- %% Filter away function call that is catched.
- filter(fun({{edoc_lib,uri_get_http,1},{http,request_sync,2}}) -> false;
- (_) -> true
- end, Undef).
-
eunit_filter(Undef) ->
filter(fun({{eunit_test,wrapper_test_exported_,0},
{eunit_test,nonexisting_function,0}}) -> false;
diff --git a/erts/test/upgrade_SUITE.erl b/erts/test/upgrade_SUITE.erl
index f92c25bdb4..f4c8aae810 100644
--- a/erts/test/upgrade_SUITE.erl
+++ b/erts/test/upgrade_SUITE.erl
@@ -20,6 +20,8 @@
-compile(export_all).
+-compile(r21).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
@@ -80,7 +82,7 @@ end_per_testcase(_Case,Config) ->
ok.
all() ->
- [minor,major].
+ [minor,major,ancient_major].
%% If this is major release X, then this test performs an upgrade from
%% major release X-1 to the current release.
@@ -89,6 +91,13 @@ major(Config) ->
PreviousMajor = previous_major(Current),
upgrade_test(PreviousMajor,Current,Config).
+%% If this is major release X, then this test performs an upgrade from
+%% major release X-2 to the current release.
+ancient_major(Config) ->
+ Current = erlang:system_info(otp_release),
+ PreviousPreviousMajor = previous_major(previous_major(Current)),
+ upgrade_test(PreviousPreviousMajor,Current,Config).
+
%% If this is a patched version of major release X, then this test
%% performs an upgrade from major release X to the current release.
minor(Config) ->
diff --git a/erts/test/z_SUITE.erl b/erts/test/z_SUITE.erl
index 3097ec22e6..18a42f7c28 100644
--- a/erts/test/z_SUITE.erl
+++ b/erts/test/z_SUITE.erl
@@ -48,7 +48,7 @@ all() ->
core_files(Config) when is_list(Config) ->
case os:type() of
{win32, _} ->
- {skipped, "No idea searching for core-files on windows"};
+ win32_search(true, os:getenv("OTP_DAILY_BUILD_TOP_DIR"));
{unix, darwin} ->
core_file_search(
core_search_conf(true,
@@ -63,7 +63,7 @@ core_files(Config) when is_list(Config) ->
search_for_core_files(Dir) ->
case os:type() of
{win32, _} ->
- io:format("No idea searching for core-files on windows");
+ win32_search(false, Dir);
{unix, darwin} ->
core_file_search(core_search_conf(false, Dir, "/cores"));
_ ->
@@ -103,18 +103,7 @@ core_search_conf(RunByTS, DBTop) ->
core_search_conf(RunByTS, DBTop, false).
core_search_conf(RunByTS, DBTop, XDir) ->
- SearchDir = case is_dir(DBTop) of
- false ->
- case code:which(test_server) of
- non_existing ->
- {ok, CWD} = file:get_cwd(),
- CWD;
- TS ->
- filename:dirname(filename:dirname(TS))
- end;
- true ->
- DBTop
- end,
+ SearchDir = search_dir(DBTop),
XSearchDir = case is_dir(XDir) of
false ->
false;
@@ -130,6 +119,20 @@ core_search_conf(RunByTS, DBTop, XDir) ->
file = os:find_executable("file"),
run_by_ts = RunByTS}.
+search_dir(DBTop) ->
+ case is_dir(DBTop) of
+ false ->
+ case code:which(test_server) of
+ non_existing ->
+ {ok, CWD} = file:get_cwd(),
+ CWD;
+ TS ->
+ filename:dirname(filename:dirname(TS))
+ end;
+ true ->
+ DBTop
+ end.
+
file_inspect(#core_search_conf{file = File}, Core) ->
FRes0 = os:cmd(File ++ " " ++ Core),
FRes = case string:split(FRes0, Core) of
@@ -186,15 +189,14 @@ dump_core(#core_search_conf{ cerl = false }, _) ->
dump_core(_, {ignore, _Core}) ->
ok;
dump_core(#core_search_conf{ cerl = Cerl }, Core) ->
- Dump = case test_server:is_debug() of
- true ->
- os:cmd(Cerl ++ " -debug -dump " ++ Core);
- _ ->
- os:cmd(Cerl ++ " -dump " ++ Core)
- end,
+ Dump = case erlang:system_info(build_type) of
+ opt ->
+ os:cmd(Cerl ++ " -dump " ++ Core);
+ Type ->
+ os:cmd(lists:concat([Cerl," -",Type," -dump ",Core]))
+ end,
ct:log("~ts~n~n~ts",[Core,Dump]).
-
format_core(Conf, {ignore, Core}) ->
format_core(Conf, Core, "[ignored] ");
format_core(Conf, Core) ->
@@ -230,17 +232,24 @@ core_file_search(#core_search_conf{search_dir = Base,
extra_search_dir = XBase,
cerl = Cerl,
run_by_ts = RunByTS} = Conf) ->
- case {Cerl,test_server:is_debug()} of
+ case {Cerl,erlang:system_info(build_type)} of
{false,_} -> ok;
- {_,true} ->
+ {_,opt} ->
catch io:format("A cerl script that probably can be used for "
- "inspection of emulator cores:~n ~s -debug~n",
+ "inspection of emulator cores:~n ~s~n",
[Cerl]);
- _ ->
+ {_,Type} ->
catch io:format("A cerl script that probably can be used for "
- "inspection of emulator cores:~n ~s~n",
- [Cerl])
+ "inspection of emulator cores:~n ~s -emu_type ~p~n",
+ [Cerl,Type])
end,
+
+ case os:getenv("DOCKER_BUILD_INFO") of
+ false -> ok;
+ Info ->
+ io:format(Info)
+ end,
+
io:format("Searching for core-files in: ~s~s~n",
[case XBase of
false -> "";
@@ -321,3 +330,39 @@ core_file_search(#core_search_conf{search_dir = Base,
_ -> Res
end
end.
+
+win32_search(RunByTS, DBTop) ->
+ case os:getenv("WSLENV") of
+ false when RunByTS ->
+ {skipped, "No idea searching for core-files on old windows"};
+ false ->
+ io:format("No idea searching for core-files on old windows");
+ _ ->
+ win32_search_2(RunByTS, DBTop)
+ end.
+
+win32_search_2(true, DBTop0) ->
+ DBTop = search_dir(DBTop0),
+ Dir = "c:/ldisk/daily_build",
+ io:format("Find and move 'dmp' files in: ~s to ~s~n",[Dir, DBTop]),
+ case filelib:wildcard("*.dmp", Dir) of
+ [] -> ok;
+ Dumps ->
+ %% We move the "daily" dmp files to this test-run
+ Str = lists:flatten(["Core-files found:", lists:join($\s, lists:reverse(Dumps))]),
+ Rename = fun(File) ->
+ FP = filename:join(Dir, File),
+ _ = file:rename(FP, filename:join(DBTop, File))
+ end,
+ [Rename(File) || File <- Dumps],
+ ct:fail(Str)
+ end;
+win32_search_2(false, _DBTop0) ->
+ DBTop = search_dir("c:/ldisk/daily_build"),
+ io:format("Search for 'dmp' files in: ~s~n",[DBTop]),
+ case filelib:wildcard("*.dmp", DBTop) of
+ [] -> "Core-files found: Ignored core-files found:";
+ Dumps ->
+ io:format("The dmp files must be removed manually\n", []),
+ lists:flatten(["Core-files found:", lists:join($\s, lists:reverse(Dumps))])
+ end.
diff --git a/erts/vsn.mk b/erts/vsn.mk
index cba7ace509..ef6d146974 100644
--- a/erts/vsn.mk
+++ b/erts/vsn.mk
@@ -18,7 +18,7 @@
# %CopyrightEnd%
#
-VSN = 10.7.2
+VSN = 11.2
# Port number 4365 in 4.2
# Port number 4366 in 4.3
diff --git a/lib/asn1/Makefile b/lib/asn1/Makefile
index 26e7e37924..10a8795703 100644
--- a/lib/asn1/Makefile
+++ b/lib/asn1/Makefile
@@ -54,12 +54,7 @@ SPECIAL_TARGETS =
include $(ERL_TOP)/make/otp_subdir.mk
-.PHONY: info version
-
-info:
- @echo "APP_RELEASE_DIR: $(APP_RELEASE_DIR)"
- @echo "APP_DIR: $(APP_DIR)"
- @echo "APP_TAR_FILE: $(APP_TAR_FILE)"
+.PHONY: version
version:
@echo "$(VSN)"
@@ -100,3 +95,5 @@ tar: $(APP_TAR_FILE)
$(APP_TAR_FILE): $(APP_DIR)
(cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME))
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/asn1/c_src/Makefile b/lib/asn1/c_src/Makefile
index 1f714df357..cb606fd74e 100644
--- a/lib/asn1/c_src/Makefile
+++ b/lib/asn1/c_src/Makefile
@@ -71,7 +71,7 @@ LN=cp
else
NIF_SHARED_OBJ_FILE = $(LIBDIR)/asn1rt_nif.so
NIF_LIB_FILE = $(LIBDIR)/asn1rt_nif.a
-CLIB_FLAGS = -lc
+CLIB_FLAGS =
LN= ln -s
endif
diff --git a/lib/asn1/doc/src/Makefile b/lib/asn1/doc/src/Makefile
index 9c0d865884..c18f567240 100644
--- a/lib/asn1/doc/src/Makefile
+++ b/lib/asn1/doc/src/Makefile
@@ -21,6 +21,7 @@
include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
# ----------------------------------------------------
# Application version
# ----------------------------------------------------
@@ -29,11 +30,6 @@ VSN=$(ASN1_VSN)
APPLICATION=asn1
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -47,6 +43,7 @@ XML_PART_FILES = part.xml
XML_HTML_FILE = \
notes_history.xml
+
XML_CHAPTER_FILES = \
asn1_introduction.xml \
asn1_getting_started.xml \
@@ -60,77 +57,11 @@ XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) \
XML_GEN_FILES = $(GEN_XML:%=$(XMLDIR)/%)
-GIF_FILES = \
+IMAGE_FILES = \
exclusive_Win_But.gif \
selective_Window2.gif \
selective_TypeList.gif
-# ----------------------------------------------------
-
-ASN1_FILES = \
- Seq.asn \
- Seq.asn1config
-
-INFO_FILE = ../../info
-EXTRA_FILES = \
- $(DEFAULT_HTML_FILES) \
- $(ASN1_FILES) \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(GEN_XML:%.xml=$(HTMLDIR)/%.html) \
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(GEN_XML) errs core *~
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
+include $(ERL_TOP)/make/doc.mk
-release_spec:
+.SECONDARY: $(XML_GEN_FILES)
diff --git a/lib/asn1/doc/src/asn1_getting_started.xml b/lib/asn1/doc/src/asn1_getting_started.xml
index 69d2b93356..1a7f3d9566 100644
--- a/lib/asn1/doc/src/asn1_getting_started.xml
+++ b/lib/asn1/doc/src/asn1_getting_started.xml
@@ -199,15 +199,15 @@ erlc -o ../asnfiles -I ../asnfiles -I /usr/local/standards/asn1 Person.asn</pre>
<item>
<p>Use maps instead of records to represent the <c>SEQUENCE</c> and
<c>SET</c> types. No <c>.hrl</c> files will be generated.
- See the Section <seealso marker="asn1_getting_started#MAP_SEQ_SET">
- Map representation for SEQUENCE and SET</seealso>
+ See the Section <seeguide marker="asn1_getting_started#MAP_SEQ_SET">
+ Map representation for SEQUENCE and SET</seeguide>
for more information.</p>
</item>
<tag><c>+asn1config</c></tag>
<item>
<p>This functionality works together with option
<c>ber</c>. It enables the specialized decodes, see Section
- <seealso marker="asn1_spec">Specialized Decode</seealso>.</p>
+ <seeguide marker="asn1_spec">Specialized Decode</seeguide>.</p>
</item>
<tag><c>+undec_rest</c></tag>
<item>
@@ -233,7 +233,7 @@ erlc -o ../asnfiles -I ../asnfiles -I /usr/local/standards/asn1 Person.asn</pre>
from the Erlang shell. Here follows a brief
description of the primary functions. For a
complete description of each function, see module <c>asn1ct</c> in
- the <seealso marker="asn1ct">ASN.1 Reference Manual</seealso>.</p>
+ the <seeerl marker="asn1ct">ASN.1 Reference Manual</seeerl>.</p>
<p>The compiler is started by <c>asn1ct:compile/1</c> with
default options, or <c>asn1ct:compile/2</c> if explicit options
are given.</p>
@@ -354,47 +354,47 @@ END </pre>
<cell align="left" valign="middle"><em>Constructed Types</em></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#BOOLEAN">BOOLEAN</seealso></cell>
- <cell align="left" valign="middle"><seealso marker="#SEQUENCE">SEQUENCE</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#BOOLEAN">BOOLEAN</seeguide></cell>
+ <cell align="left" valign="middle"><seeguide marker="#SEQUENCE">SEQUENCE</seeguide></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#INTEGER">INTEGER</seealso></cell>
- <cell align="left" valign="middle"><seealso marker="#SET">SET</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#INTEGER">INTEGER</seeguide></cell>
+ <cell align="left" valign="middle"><seeguide marker="#SET">SET</seeguide></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#REAL">REAL</seealso></cell>
- <cell align="left" valign="middle"><seealso marker="#CHOICE">CHOICE</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#REAL">REAL</seeguide></cell>
+ <cell align="left" valign="middle"><seeguide marker="#CHOICE">CHOICE</seeguide></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#NULL">NULL</seealso></cell>
- <cell align="left" valign="middle"><seealso marker="#SOF">SET OF and SEQUENCE OF</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#NULL">NULL</seeguide></cell>
+ <cell align="left" valign="middle"><seeguide marker="#SOF">SET OF and SEQUENCE OF</seeguide></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#ENUMERATED">ENUMERATED</seealso></cell>
- <cell align="left" valign="middle"><seealso marker="#ANY">ANY</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#ENUMERATED">ENUMERATED</seeguide></cell>
+ <cell align="left" valign="middle"><seeguide marker="#ANY">ANY</seeguide></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#BIT STRING">BIT STRING</seealso></cell>
- <cell align="left" valign="middle"><seealso marker="#ANY">ANY DEFINED BY</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#BIT STRING">BIT STRING</seeguide></cell>
+ <cell align="left" valign="middle"><seeguide marker="#ANY">ANY DEFINED BY</seeguide></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#OCTET STRING">OCTET STRING</seealso></cell>
- <cell align="left" valign="middle"><seealso marker="#NegotiationTypes">EXTERNAL</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#OCTET STRING">OCTET STRING</seeguide></cell>
+ <cell align="left" valign="middle"><seeguide marker="#NegotiationTypes">EXTERNAL</seeguide></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#Character Strings">Character Strings</seealso></cell>
- <cell align="left" valign="middle"><seealso marker="#NegotiationTypes">EMBEDDED PDV</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#Character Strings">Character Strings</seeguide></cell>
+ <cell align="left" valign="middle"><seeguide marker="#NegotiationTypes">EMBEDDED PDV</seeguide></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#OBJECT IDENTIFIER">OBJECT IDENTIFIER</seealso></cell>
- <cell align="left" valign="middle"><seealso marker="#NegotiationTypes">CHARACTER STRING</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#OBJECT IDENTIFIER">OBJECT IDENTIFIER</seeguide></cell>
+ <cell align="left" valign="middle"><seeguide marker="#NegotiationTypes">CHARACTER STRING</seeguide></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#Object Descriptor">Object Descriptor</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#Object Descriptor">Object Descriptor</seeguide></cell>
<cell align="left" valign="middle"></cell>
</row>
<row>
- <cell align="left" valign="middle"><seealso marker="#The TIME types">TIME Types</seealso></cell>
+ <cell align="left" valign="middle"><seeguide marker="#The TIME types">TIME Types</seeguide></cell>
<cell align="left" valign="middle"></cell>
</row>
<tcaption>Supported ASN.1 Types</tcaption>
@@ -697,7 +697,7 @@ ok
[1052,1086,1081,32,1084,1072,1083,1077,1085,1100,1082,1080,
1081,32,1043,1085,1086,1084]</pre>
- <p>For details, see the <seealso marker="stdlib:unicode">unicode</seealso>
+ <p>For details, see the <seeerl marker="stdlib:unicode">unicode</seeerl>
module in STDLIB.</p>
<p>In the following example, this ASN.1 specification is used:</p>
@@ -789,8 +789,8 @@ Pdu ::= SEQUENCE {
<p>This is a 4-component structure called <c>Pdu</c>. By default,
a <c>SEQUENCE</c> is represented by a record in Erlang.
It can also be represented as a map; see
- <seealso marker="asn1_getting_started#MAP_SEQ_SET">
- Map representation for SEQUENCE and SET</seealso>.
+ <seeguide marker="asn1_getting_started#MAP_SEQ_SET">
+ Map representation for SEQUENCE and SET</seeguide>.
For each <c>SEQUENCE</c> and <c>SET</c> in an ASN.1 module an Erlang
record declaration is generated. For <c>Pdu</c>, a record
like the following is defined:</p>
@@ -1020,7 +1020,7 @@ Arr2Val = ["abc",[14,34,54],"Octets"], </pre>
<c>TYPE-IDENTIFIER.@Type</c> accomplish the same as the
deprecated <c>ANY</c>.</p>
<p>See also
- <seealso marker="#Information Object">Information object</seealso>.</p>
+ <seeguide marker="#Information Object">Information object</seeguide>.</p>
</section>
<section>
diff --git a/lib/asn1/doc/src/asn1_spec.xmlsrc b/lib/asn1/doc/src/asn1_spec.xmlsrc
index 6367376f50..85481bdb2a 100644
--- a/lib/asn1/doc/src/asn1_spec.xmlsrc
+++ b/lib/asn1/doc/src/asn1_spec.xmlsrc
@@ -83,8 +83,8 @@
file with the same name as the ASN.1 specification but with
extension <c>.asn1config</c>. This configuration file is not
the same as used for compilation of a set of files. See Section
- <seealso marker="#UndecodedPart">Writing an Exclusive Decode
- Instruction.</seealso></item>
+ <seeguide marker="#UndecodedPart">Writing an Exclusive Decode
+ Instruction.</seeguide></item>
</list>
</section>
@@ -401,15 +401,15 @@ List_Selector = [integer()]</pre>
<p>In the example, component <c>number</c> of the first of the encoded
elements in the <c>SEQUENCE OF</c> <c>buttonList</c> is selected.
This applies on the ASN.1 specification in Section
- <seealso marker="#Asn1spec">Writing an Exclusive Decode
- Instruction</seealso>.
+ <seeguide marker="#Asn1spec">Writing an Exclusive Decode
+ Instruction</seeguide>.
</p>
</section>
<section>
<title>Another Example</title>
<p>In this example, the same ASN.1 specification as in Section
- <seealso marker="#Asn1spec">Writing an Exclusive Decode Instruction</seealso>
+ <seeguide marker="#Asn1spec">Writing an Exclusive Decode Instruction</seeguide>
is used. The following is a valid selective decode instruction:</p>
<pre>
{selective_decode,
@@ -517,7 +517,7 @@ ValAction = {'Action',17,{'Button',4711,false}}.
<section>
<title>ASN.1 Specifications, Messages, and Configuration</title>
- <p>The specifications <seealso marker="#Asn1spec">GUI</seealso> and
+ <p>The specifications <seeguide marker="#Asn1spec">GUI</seeguide> and
<url href="http://www.itu.int/ITU-T/asn1/database/itu-t/h/h248/2002/MEDIA-GATEWAY-CONTROL.html">MEDIA-GATEWAY-CONTROL</url>
were used in the test.
</p>
diff --git a/lib/asn1/doc/src/asn1ct.xml b/lib/asn1/doc/src/asn1ct.xml
index 585c4c1031..e4c5a2a3ee 100644
--- a/lib/asn1/doc/src/asn1ct.xml
+++ b/lib/asn1/doc/src/asn1ct.xml
@@ -48,10 +48,10 @@
and <c>OCTET STRING</c> types as Erlang terms were changed. <c>BIT
STRING</c> values are now Erlang bit strings and <c>OCTET STRING</c>
values are binaries. Also, an undecoded open type is now wrapped in
- an <c>asn1_OPENTYPE</c> tuple. For details, see <seealso
- marker="asn1_getting_started#BIT STRING">BIT STRING</seealso>, <seealso
- marker="asn1_getting_started#OCTET STRING">OCTET STRING</seealso>, and
- <seealso marker="asn1_getting_started#Information Object">ASN.1 Information Objects</seealso> in the User's Guide.</p>
+ an <c>asn1_OPENTYPE</c> tuple. For details, see <seeguide
+ marker="asn1_getting_started#BIT STRING">BIT STRING</seeguide>, <seeguide
+ marker="asn1_getting_started#OCTET STRING">OCTET STRING</seeguide>, and
+ <seeguide marker="asn1_getting_started#Information Object">ASN.1 Information Objects</seeguide> in the User's Guide.</p>
<p>To revert to the old representation of the types, use option
<c>legacy_erlang_types</c>.</p>
</note>
@@ -207,8 +207,8 @@ File3.asn</pre>
records). This option also suppresses the generation of
<c>.hrl</c> files.</p>
<p>For details, see Section
- <seealso marker="asn1_getting_started#MAP_SEQ_SET">
- Map representation for SEQUENCE and SET</seealso>
+ <seeguide marker="asn1_getting_started#MAP_SEQ_SET">
+ Map representation for SEQUENCE and SET</seeguide>
in the User's Guide.
</p>
</item>
@@ -220,8 +220,8 @@ File3.asn</pre>
This option cannot be combined with the option <c>maps</c>.
</p>
<p>For details, see Section
- <seealso marker="asn1_getting_started#BIT STRING">
- BIT STRING</seealso> in the User's Guide.
+ <seeguide marker="asn1_getting_started#BIT STRING">
+ BIT STRING</seeguide> in the User's Guide.
</p>
<p>This option implies option <c>legacy_erlang_types</c>.</p>
</item>
@@ -234,7 +234,7 @@ File3.asn</pre>
This option cannot be combined with the option <c>maps</c>.
</p>
<p>For details, see Section
- <seealso marker="asn1_getting_started#BIT STRING">BIT STRING</seealso>
+ <seeguide marker="asn1_getting_started#BIT STRING">BIT STRING</seeguide>
in the User's Guide</p>
<p>This option implies option <c>legacy_erlang_types</c>.</p>
</item>
@@ -242,10 +242,10 @@ File3.asn</pre>
<item>
<p>Use the same Erlang types to represent <c>BIT STRING</c> and
<c>OCTET STRING</c> as in OTP R16.</p>
- <p>For details, see Section <seealso
- marker="asn1_getting_started#BIT STRING">BIT STRING</seealso> and Section
- <seealso marker="asn1_getting_started#OCTET STRING">OCTET
- STRING</seealso> in the User's Guide.</p>
+ <p>For details, see Section <seeguide
+ marker="asn1_getting_started#BIT STRING">BIT STRING</seeguide> and Section
+ <seeguide marker="asn1_getting_started#OCTET STRING">OCTET
+ STRING</seeguide> in the User's Guide.</p>
<p><em>This option is not recommended for new code.</em>
This option cannot be combined with the option <c>maps</c>.</p>
</item>
@@ -300,12 +300,12 @@ File3.asn</pre>
<c>.asn1config</c>.
</p>
<p>For instructions for exclusive decode, see Section
- <seealso marker="asn1_spec#Exclusive Instruction">Exclusive
- Decode</seealso> in the User's Guide.
+ <seeguide marker="asn1_spec#Exclusive Instruction">Exclusive
+ Decode</seeguide> in the User's Guide.
</p>
<p>For instructions for selective decode, see Section
- <seealso marker="asn1_spec#Selective Instruction">Selective
- Decode</seealso> in the User's Guide.
+ <seeguide marker="asn1_spec#Selective Instruction">Selective
+ Decode</seeguide> in the User's Guide.
</p>
</item>
<tag><c>undec_rest</c></tag>
diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml
index 3351d8f195..3f4bfddd06 100644
--- a/lib/asn1/doc/src/notes.xml
+++ b/lib/asn1/doc/src/notes.xml
@@ -32,6 +32,58 @@
<p>This document describes the changes made to the asn1 application.</p>
+<section><title>Asn1 5.0.14</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Changes in order to build on the Haiku operating system.</p>
+ <p>
+ Thanks to Calvin Buckley</p>
+ <p>
+ Own Id: OTP-16707 Aux Id: PR-2638 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Asn1 5.0.13</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Adhere to the ASN.1 specification for hstring &amp;
+ bstring lexical items. That is they may include white
+ space.</p>
+ <p>
+ Own Id: OTP-16490</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ Improve handling of ellipsis in a CHOICE</p>
+ <p>
+ Own Id: OTP-16554 Aux Id: ERL-1189 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Asn1 5.0.12</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/asn1/doc/users_guide/Makefile b/lib/asn1/doc/users_guide/Makefile
index 38196f5e1c..079af7ac1f 100644
--- a/lib/asn1/doc/users_guide/Makefile
+++ b/lib/asn1/doc/users_guide/Makefile
@@ -40,8 +40,7 @@ PSFIG_FILES=
USERS_GUIDE = users_guide.sgml
EXTRA_GEN_FILES= $(SGML_FILES:.sgml=.html) \
- users_guide_frame.html users_guide_first.html \
- min_head.gif
+ users_guide_frame.html users_guide_first.html
HTML_FILES= $(USERS_GUIDE:.sgml=.html)
diff --git a/lib/asn1/src/asn1ct.erl b/lib/asn1/src/asn1ct.erl
index 5f59abc093..e3ea96bb2f 100644
--- a/lib/asn1/src/asn1ct.erl
+++ b/lib/asn1/src/asn1ct.erl
@@ -76,6 +76,11 @@
-define(ALTERNATIVE_UNDECODED,alt_undec).
-define(ALTERNATIVE_PARTS,alt_parts).
+%% Removed functions
+
+-removed({decode,'_',"use Mod:decode/2 instead"}).
+-removed({encode,'_',"use Mod:encode/2 instead"}).
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This is the interface to the compiler
@@ -1689,6 +1694,9 @@ create_pdec_command(ModName,{'CHOICE',[Comp=#'ComponentType'{name=C1}|_]},TNL=[C
create_pdec_command(ModName,[Comp],TNL,Acc);
create_pdec_command(ModName,{'CHOICE',[#'ComponentType'{}|Comps]},TNL,Acc) ->
create_pdec_command(ModName,{'CHOICE',Comps},TNL,Acc);
+create_pdec_command(ModName,{'CHOICE',{Cs1,Cs2}},TNL,Acc)
+ when is_list(Cs1),is_list(Cs2) ->
+ create_pdec_command(ModName,{'CHOICE',Cs1 ++ Cs2},TNL,Acc);
create_pdec_command(ModName,#'Externaltypereference'{module=M,type=C1},
TypeNameList,Acc) ->
#type{def=Def} = get_referenced_type(M,C1),
diff --git a/lib/asn1/src/asn1ct_tok.erl b/lib/asn1/src/asn1ct_tok.erl
index 8235b689f8..76ff60f9cf 100644
--- a/lib/asn1/src/asn1ct_tok.erl
+++ b/lib/asn1/src/asn1ct_tok.erl
@@ -239,16 +239,16 @@ skip_multiline_comment(Stream, [_|T], Lno, Level) ->
skip_multiline_comment(Stream, T, Lno, Level).
collect_quoted("'B"++T, Lno, L) ->
- case check_bin(L) of
- true ->
- {{bstring,Lno,lists:reverse(L)}, T};
+ case validate_bin(L) of
+ {ok, Bin} ->
+ {{bstring,Lno,Bin}, T};
false ->
throw({error,{invalid_binary_number,lists:reverse(L)}})
end;
collect_quoted("'H"++T, Lno, L) ->
- case check_hex(L) of
- true ->
- {{hstring,Lno,lists:reverse(L)}, T};
+ case validate_hex(L) of
+ {ok, Hex} ->
+ {{hstring,Lno,Hex}, T};
false ->
throw({error,{invalid_hex_number,lists:reverse(L)}})
end;
@@ -257,24 +257,31 @@ collect_quoted([H|T], Lno, L) ->
collect_quoted([], _, _) -> % This should be allowed FIX later
throw({error,eol_in_token}).
-check_bin([$0|T]) ->
- check_bin(T);
-check_bin([$1|T]) ->
- check_bin(T);
-check_bin([]) ->
- true;
-check_bin(_) ->
- false.
-
-check_hex([H|T]) when $0 =< H , H =< $9 ->
- check_hex(T);
-check_hex([H|T]) when $A =< H , H =< $F ->
- check_hex(T);
-check_hex([]) ->
- true;
-check_hex(_) ->
- false.
-
+validate_bin(L) ->
+ validate_bin(L,[]).
+
+validate_bin([H|T], A) when H =:= $0; H =:= $1 ->
+ validate_bin(T, [H|A]);
+validate_bin([$\s|T], A) ->
+ validate_bin(T, A);
+validate_bin([_|_], _) ->
+ false;
+validate_bin([], A) ->
+ {ok, A}.
+
+validate_hex(L) ->
+ validate_hex(L,[]).
+
+validate_hex([H|T], A) when $0 =< H , H =< $9 ->
+ validate_hex(T, [H|A]);
+validate_hex([H|T], A) when $A =< H , H =< $F ->
+ validate_hex(T, [H|A]);
+validate_hex([$\s|T], A) ->
+ validate_hex(T, A);
+validate_hex([_|_], _) ->
+ false;
+validate_hex([], A) ->
+ {ok, A}.
%% reserved_word(A) -> true|false|rstrtype
%% A = atom()
diff --git a/lib/asn1/src/asn1rt.erl b/lib/asn1/src/asn1rt.erl
new file mode 100644
index 0000000000..fc2e2c2d2a
--- /dev/null
+++ b/lib/asn1/src/asn1rt.erl
@@ -0,0 +1,34 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% This module was removed entirely in OTP 20.0, it's only retained for its
+%% removal warning attribute.
+%%
+
+-module(asn1rt).
+
+-removed({decode,'_',"use Mod:decode/2 instead"}).
+-removed({encode,'_',"use Mod:encode/2 instead"}).
+
+-removed({utf8_binary_to_list,'_',
+ "use unicode:characters_to_list/1 instead"}).
+-removed({utf8_list_to_binary,'_',
+ "use unicode:characters_to_binary/1 instead"}).
diff --git a/lib/asn1/test/asn1_SUITE.erl b/lib/asn1/test/asn1_SUITE.erl
index f148dfc5ce..f75a385cf5 100644
--- a/lib/asn1/test/asn1_SUITE.erl
+++ b/lib/asn1/test/asn1_SUITE.erl
@@ -45,10 +45,112 @@ all() ->
[xref,
xref_export_all,
- {group, compile},
- {group, parallel},
+ c_string,
+ constraint_equivalence,
+
+ ber_decode_invalid_length,
+ ber_choiceinseq,
+ ber_optional,
+ tagdefault_automatic,
+
+ cover,
+
+ parse,
+ test_undecoded_rest,
+ specialized_decodes,
+ special_decode_performance,
+
+ testMegaco,
+ testConstraints,
+ testCompactBitString,
+ default,
+ testPrim,
+ rtUI,
+ testPrimStrings,
+
+ per,
+ ber,
+ der,
+
+ h323test,
+ testExtensibilityImplied,
+ testChoice,
+ testDefaultOctetString,
+ testMultipleLevels,
+ testOpt,
+ testSeqDefault,
+ testMaps,
+
+ testTypeValueNotation,
+
+ testExternal,
+
+ testSeqExtension,
+ testSeqOptional,
+ testSeqPrim,
+ testSeqTypeRefCho,
+ testSeqTypeRefPrim,
+ testSeqTypeRefSeq,
+ testSeqTypeRefSet,
+
+ testSeqOf,
+ testSeqOfIndefinite,
+ testSeqOfCho,
+ testSeqOfChoExt,
+
+ testExtensionAdditionGroup,
+
+ testSet,
+ testSetOf,
+
+ testEnumExt,
+ value_test,
+ testSeq2738,
+ constructed,
+ ber_decode_error,
+ otp_14440,
+ testSeqSetIndefinite,
+ testChoiceIndefinite,
+ per_open_type,
+ testInfObjectClass,
+ testUniqueObjectSets,
+ testInfObjExtract,
+ testParam,
+ testFragmented,
+ testMergeCompile,
+ testobj,
+ testDeepTConstr,
+ testImport,
+ testDER,
+ testDEFAULT,
+ testExtensionDefault,
+ testMvrasn6,
+ testContextSwitchingTypes,
+ testOpenTypeImplicitTag,
+ testROSE,
+ testINSTANCE_OF,
+ testTCAP,
+ test_ParamTypeInfObj,
+ test_Defed_ObjectIdentifier,
+ testSelectionType,
+ testSSLspecs,
+ testNortel,
+ test_WS_ParamClass,
+ test_modified_x420,
+
+ %% Some heavy tests.
+ testTcapsystem,
+ testNBAPsystem,
+ testS1AP,
+ testRfcs,
+
+ test_compile_options,
+ testDoubleEllipses,
+ test_x691,
+ ticket_6143,
+ test_OTP_9688,
+ testValueTest,
- % TODO: Investigate parallel running of these:
testComment,
testName2Number,
ticket_7407,
@@ -57,129 +159,7 @@ all() ->
{group, performance}].
groups() ->
- Parallel = asn1_test_lib:parallel(),
- [{compile, Parallel,
- [c_string,
- constraint_equivalence]},
-
- {ber, Parallel,
- [ber_decode_invalid_length,
- ber_choiceinseq,
- % Uses 'SOpttest'
- ber_optional,
- tagdefault_automatic]},
-
- {parallel, Parallel,
- [cover,
- {group, ber},
- % Uses 'P-Record', 'Constraints', 'MEDIA-GATEWAY-CONTROL'...
- {group, [], [parse,
- test_undecoded_rest,
- specialized_decodes,
- special_decode_performance,
- testMegaco,
- testConstraints,
- testCompactBitString]},
- default,
- % Uses 'Def', 'MULTIMEDIA-SYSTEM-CONTROL', 'H323-MESSAGES', 'Prim',
- % 'Real'
- {group, [], [testPrim,
- rtUI,
- testPrimStrings,
- per,
- ber_other,
- der,
- h323test]},
- testExtensibilityImplied,
- testChoPrim,
- testChoExtension,
- testChoOptional,
- testChoRecursive,
- testChoTypeRefCho,
- testChoTypeRefPrim,
- testChoTypeRefSeq,
- testChoTypeRefSet,
- testDefaultOctetString,
- testMultipleLevels,
- testOpt,
- testSeqDefault,
- testMaps,
- % Uses 'External'
- {group, [], [testExternal,
- testSeqExtension]},
- testSeqOptional,
- testSeqPrim,
- testSeqTypeRefCho,
- % Uses 'SeqTypeRefPrim'
- {group, [], [testSeqTypeRefPrim,
- testTypeValueNotation]},
- testSeqTypeRefSeq,
- testSeqTypeRefSet,
- % Uses 'SeqOf'
- {group, [], [testSeqOf,
- testSeqOfIndefinite]}, % Uses 'Mvrasn*'
- testSeqOfCho,
- testSeqOfChoExt,
- testSetDefault,
- testExtensionAdditionGroup,
- testSetOptional,
- testSetPrim,
- testSetTypeRefCho,
- testSetTypeRefPrim,
- testSetTypeRefSeq,
- testSetTypeRefSet,
- testSetOf,
- testSetOfCho,
- testEnumExt,
- value_test,
- testSeq2738,
- % Uses 'Constructed'
- {group, [], [constructed,
- ber_decode_error,
- otp_14440]},
- testSeqSetIndefinite,
- testChoiceIndefinite,
- per_open_type,
- testInfObjectClass,
- testUniqueObjectSets,
- testInfObjExtract,
- testParam,
- testFragmented,
- testMergeCompile,
- testobj,
- testDeepTConstr,
- testImport,
- testDER,
- testDEFAULT,
- testExtensionDefault,
- testMvrasn6,
- testContextSwitchingTypes,
- testOpenTypeImplicitTag,
- testROSE,
- testINSTANCE_OF,
- testTCAP,
- test_ParamTypeInfObj,
- test_Defed_ObjectIdentifier,
- testSelectionType,
- testSSLspecs,
- testNortel,
- % Uses 'PKCS7', 'InformationFramework'
- {group, [], [test_WS_ParamClass,
- test_modified_x420]},
- %% Don't run all these at the same time.
- {group, [],
- [testTcapsystem,
- testNBAPsystem,
- testS1AP,
- testRfcs]},
- test_compile_options,
- testDoubleEllipses,
- test_x691,
- ticket_6143,
- test_OTP_9688,
- testValueTest]},
-
- {performance, [],
+ [{performance, [],
[testTimer_ber,
testTimer_ber_maps,
testTimer_per,
@@ -326,30 +306,26 @@ do_test_prim(Rule, NoOkWrapper) ->
testCompactBitString(Config) -> test(Config, fun testCompactBitString/3).
testCompactBitString(Config, Rule, Opts) ->
- asn1_test_lib:compile("PrimStrings", Config,
- [Rule, compact_bit_string|Opts]),
+ Files = ["PrimStrings", "Constraints"],
+ asn1_test_lib:compile_all(Files, Config, [Rule, compact_bit_string|Opts]),
testCompactBitString:compact_bit_string(Rule),
testCompactBitString:bit_string_unnamed(Rule),
testCompactBitString:bit_string_unnamed(Rule),
testCompactBitString:ticket_7734(Rule),
- asn1_test_lib:compile("Constraints", Config,
- [Rule, compact_bit_string|Opts]),
testCompactBitString:otp_4869(Rule).
testPrimStrings(Config) ->
test(Config, fun testPrimStrings/3, [ber,{ber,[der]},per,uper]).
testPrimStrings(Config, Rule, Opts) ->
LegacyOpts = [legacy_erlang_types|Opts],
- asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config,
- [Rule|LegacyOpts]),
+ Files = ["PrimStrings", "BitStr"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|LegacyOpts]),
testPrimStrings_cases(Rule, LegacyOpts),
- asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config, [Rule|Opts]),
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
testPrimStrings_cases(Rule, Opts),
- asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config,
- [legacy_bit_string,Rule|Opts]),
+ asn1_test_lib:compile_all(Files, Config, [legacy_bit_string,Rule|Opts]),
testPrimStrings:bit_string(Rule, Opts),
- asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config,
- [compact_bit_string,Rule|Opts]),
+ asn1_test_lib:compile_all(Files, Config, [compact_bit_string,Rule|Opts]),
testPrimStrings:bit_string(Rule, Opts),
testPrimStrings:more_strings(Rule).
@@ -398,46 +374,26 @@ testExtensibilityImplied(Config, Rule, Opts) ->
[Rule,no_ok_wrapper|Opts]),
testExtensibilityImplied:main(Rule).
-testChoPrim(Config) -> test(Config, fun testChoPrim/3).
-testChoPrim(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoPrim", Config, [Rule|Opts]),
+testChoice(Config) -> test(Config, fun testChoice/3).
+testChoice(Config, Rule, Opts) ->
+ Files = ["ChoPrim",
+ "ChoExtension",
+ "ChoOptional",
+ "ChoOptionalImplicitTag",
+ "ChoRecursive",
+ "ChoTypeRefCho",
+ "ChoTypeRefPrim",
+ "ChoTypeRefSeq",
+ "ChoTypeRefSet"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
testChoPrim:bool(Rule),
- testChoPrim:int(Rule).
-
-testChoExtension(Config) -> test(Config, fun testChoExtension/3).
-testChoExtension(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoExtension", Config, [Rule|Opts]),
- testChoExtension:extension(Rule).
-
-testChoOptional(Config) -> test(Config, fun testChoOptional/3).
-testChoOptional(Config, Rule, Opts) ->
- asn1_test_lib:compile_all(["ChoOptional",
- "ChoOptionalImplicitTag"], Config, [Rule|Opts]),
- testChoOptional:run().
-
-testChoRecursive(Config) -> test(Config, fun testChoRecursive/3).
-testChoRecursive(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoRecursive", Config, [Rule|Opts]),
- testChoRecursive:recursive(Rule).
-
-testChoTypeRefCho(Config) -> test(Config, fun testChoTypeRefCho/3).
-testChoTypeRefCho(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoTypeRefCho", Config, [Rule|Opts]),
- testChoTypeRefCho:choice(Rule).
-
-testChoTypeRefPrim(Config) -> test(Config, fun testChoTypeRefPrim/3).
-testChoTypeRefPrim(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoTypeRefPrim", Config, [Rule|Opts]),
- testChoTypeRefPrim:prim(Rule).
-
-testChoTypeRefSeq(Config) -> test(Config, fun testChoTypeRefSeq/3).
-testChoTypeRefSeq(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoTypeRefSeq", Config, [Rule|Opts]),
- testChoTypeRefSeq:seq(Rule).
-
-testChoTypeRefSet(Config) -> test(Config, fun testChoTypeRefSet/3).
-testChoTypeRefSet(Config, Rule, Opts) ->
- asn1_test_lib:compile("ChoTypeRefSet", Config, [Rule|Opts]),
+ testChoPrim:int(Rule),
+ testChoExtension:extension(Rule),
+ testChoOptional:run(),
+ testChoRecursive:recursive(Rule),
+ testChoTypeRefCho:choice(Rule),
+ testChoTypeRefPrim:prim(Rule),
+ testChoTypeRefSeq:seq(Rule),
testChoTypeRefSet:set(Rule).
testDefaultOctetString(Config) -> test(Config, fun testDefaultOctetString/3).
@@ -564,50 +520,33 @@ testSeqOfIndefinite(Config, Rule, Opts) ->
asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
testSeqOfIndefinite:main().
-testSetDefault(Config) -> test(Config, fun testSetDefault/3).
-testSetDefault(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetDefault", Config, [Rule|Opts]),
- testSetDefault:main(Rule).
+testSet(Config) -> test(Config, fun testSet/3).
+testSet(Config, Rule, Opts) ->
+ Files = ["SetDefault",
+ "SetOptional",
+ "SetPrim",
+ "SetTypeRefCho",
+ "SetTypeRefPrim",
+ "SetTypeRefSeq",
+ "SetTypeRefSet"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
-testSetOptional(Config) -> test(Config, fun testSetOptional/3).
-testSetOptional(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetOptional", Config, [Rule|Opts]),
+ testSetDefault:main(Rule),
testSetOptional:ticket_7533(Rule),
- testSetOptional:main(Rule).
-
-testSetPrim(Config) -> test(Config, fun testSetPrim/3).
-testSetPrim(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetPrim", Config, [Rule|Opts]),
- testSetPrim:main(Rule).
-
-testSetTypeRefCho(Config) -> test(Config, fun testSetTypeRefCho/3).
-testSetTypeRefCho(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetTypeRefCho", Config, [Rule|Opts]),
- testSetTypeRefCho:main(Rule).
-
-testSetTypeRefPrim(Config) -> test(Config, fun testSetTypeRefPrim/3).
-testSetTypeRefPrim(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetTypeRefPrim", Config, [Rule|Opts]),
- testSetTypeRefPrim:main(Rule).
-
-testSetTypeRefSeq(Config) -> test(Config, fun testSetTypeRefSeq/3).
-testSetTypeRefSeq(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetTypeRefSeq", Config, [Rule|Opts]),
- testSetTypeRefSeq:main(Rule).
-
-testSetTypeRefSet(Config) -> test(Config, fun testSetTypeRefSet/3).
-testSetTypeRefSet(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetTypeRefSet", Config, [Rule|Opts]),
+ testSetOptional:main(Rule),
+
+ testSetPrim:main(Rule),
+ testSetTypeRefCho:main(Rule),
+ testSetTypeRefPrim:main(Rule),
+ testSetTypeRefSeq:main(Rule),
testSetTypeRefSet:main(Rule).
testSetOf(Config) -> test(Config, fun testSetOf/3).
testSetOf(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetOf", Config, [Rule|Opts]),
- testSetOf:main(Rule).
-
-testSetOfCho(Config) -> test(Config, fun testSetOfCho/3).
-testSetOfCho(Config, Rule, Opts) ->
- asn1_test_lib:compile("SetOfCho", Config, [Rule|Opts]),
+ Files = ["SetOf",
+ "SetOfCho"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
+ testSetOf:main(Rule),
testSetOfCho:main(Rule).
c_string(Config) ->
@@ -657,19 +596,23 @@ parse(Config) ->
per(Config) ->
test(Config, fun per/3, [per,uper,{per,[maps]},{uper,[maps]}]).
per(Config, Rule, Opts) ->
- [module_test(M, Config, Rule, Opts) || M <- per_modules()].
+ module_test(per_modules(), Config, Rule, Opts).
-ber_other(Config) ->
- test(Config, fun ber_other/3, [ber,{ber,[maps]}]).
+ber(Config) ->
+ test(Config, fun ber/3, [ber,{ber,[maps]}]).
-ber_other(Config, Rule, Opts) ->
- [module_test(M, Config, Rule, Opts) || M <- ber_modules()].
+ber(Config, Rule, Opts) ->
+ module_test(ber_modules(), Config, Rule, Opts).
der(Config) ->
asn1_test_lib:compile_all(ber_modules(), Config, [der]).
-module_test(M0, Config, Rule, Opts) ->
- asn1_test_lib:compile(M0, Config, [Rule,?NO_MAPS_MODULE|Opts]),
+module_test(Modules, Config, Rule, Opts) ->
+ asn1_test_lib:compile_all(Modules, Config, [Rule,?NO_MAPS_MODULE|Opts]),
+ _ = [do_module_test(M, Config, Opts) || M <- Modules],
+ ok.
+
+do_module_test(M0, Config, Opts) ->
case list_to_atom(M0) of
'LDAP' ->
%% Because of the recursive definition of 'Filter' in
@@ -815,8 +758,8 @@ per_open_type(Config, Rule, Opts) ->
testConstraints(Config) -> test(Config, fun testConstraints/3).
testConstraints(Config, Rule, Opts) ->
- asn1_test_lib:compile("Constraints", Config, [Rule|Opts]),
- asn1_test_lib:compile("LargeConstraints", Config, [Rule|Opts]),
+ Files = ["Constraints", "LargeConstraints"],
+ asn1_test_lib:compile_all(Files, Config, [Rule|Opts]),
testConstraints:int_constraints(Rule),
case Rule of
ber -> ok;
@@ -989,7 +932,8 @@ specialized_decodes(Config, Rule, Opts) ->
"PartialDecSeq3.asn",
"PartialDecMyHTTP.asn",
"MEDIA-GATEWAY-CONTROL.asn",
- "P-Record"],
+ "P-Record",
+ "PartialDecChoExtension.asn"],
Config,
[Rule,legacy_erlang_types,asn1config|Opts]),
test_partial_incomplete_decode:test(Config),
@@ -1162,9 +1106,6 @@ testExtensionAdditionGroup(Config, Rule, Opts) ->
[Rule,{record_name_prefix,"RRC-"}|Opts]),
extensionAdditionGroup:run(Rule).
-% parse_modules() ->
-% ["ImportsFrom"].
-
per_modules() ->
[X || X <- test_modules()].
diff --git a/lib/asn1/test/asn1_SUITE_data/PartialDecChoExtension.asn b/lib/asn1/test/asn1_SUITE_data/PartialDecChoExtension.asn
new file mode 100644
index 0000000000..128e5966d8
--- /dev/null
+++ b/lib/asn1/test/asn1_SUITE_data/PartialDecChoExtension.asn
@@ -0,0 +1,12 @@
+PartialDecChoExtension DEFINITIONS IMPLICIT TAGS ::=
+
+BEGIN
+
+ChoExt ::= CHOICE
+{
+ i BOOLEAN,
+ ...,
+ j INTEGER
+}
+
+END
diff --git a/lib/asn1/test/asn1_SUITE_data/PartialDecChoExtension.asn1config b/lib/asn1/test/asn1_SUITE_data/PartialDecChoExtension.asn1config
new file mode 100644
index 0000000000..32265b03e4
--- /dev/null
+++ b/lib/asn1/test/asn1_SUITE_data/PartialDecChoExtension.asn1config
@@ -0,0 +1,2 @@
+{selective_decode, {'PartialDecChoExtension',
+ [{selected_decode_ChoExt, ['ChoExt', j]}]}}.
diff --git a/lib/asn1/test/asn1_SUITE_data/ValueTest.asn b/lib/asn1/test/asn1_SUITE_data/ValueTest.asn
index b2c59d686a..0474386061 100644
--- a/lib/asn1/test/asn1_SUITE_data/ValueTest.asn
+++ b/lib/asn1/test/asn1_SUITE_data/ValueTest.asn
@@ -100,6 +100,7 @@ os-holder-1 OS-HOLDER ::= { ID 1 OS '4041FF'H }
OctetStringSeq ::= ParamSeq{OCTET STRING}
someOctetString OCTET STRING ::= '404142'H
+someOctetStringWhiteSpace OCTET STRING ::= '40 41 42'H
octetStringSeq1 OctetStringSeq ::= { a someOctetString }
octetStringSeq2 OctetStringSeq ::= { a otherOctetString }
@@ -128,6 +129,7 @@ BsSeq ::= SEQUENCE {
}
someBitString BIT STRING ::= '101101'B
+someBitStringWhiteSpace BIT STRING ::= '101 101'B
bsSeq1 BsSeq ::= { a someBitString, b someNamedBs }
bsSeq2 BsSeq ::= { a otherBitString, b someOtherNamedBs }
diff --git a/lib/asn1/test/asn1_test_lib.erl b/lib/asn1/test/asn1_test_lib.erl
index af8462f0c9..2c91256d6d 100644
--- a/lib/asn1/test/asn1_test_lib.erl
+++ b/lib/asn1/test/asn1_test_lib.erl
@@ -24,7 +24,6 @@
rm_dirs/1,
hex_to_bin/1,
match_value/2,
- parallel/0,
roundtrip/3,roundtrip/4,roundtrip_enc/3,roundtrip_enc/4,
map_roundtrip/3]).
@@ -48,12 +47,6 @@ compile_all(Files, Config, Options0) ->
dialyze(Files, Options),
ok.
-parallel() ->
- case erlang:system_info(schedulers) > 1 andalso not run_dialyzer() of
- true -> [parallel];
- false -> []
- end.
-
dialyze(Files, Options) ->
case not run_dialyzer() orelse lists:member(abs, Options) of
true -> ok;
diff --git a/lib/asn1/test/testMegaco.erl b/lib/asn1/test/testMegaco.erl
index 0be798b962..6a88117896 100644
--- a/lib/asn1/test/testMegaco.erl
+++ b/lib/asn1/test/testMegaco.erl
@@ -26,8 +26,8 @@
-include_lib("common_test/include/ct.hrl").
compile(Config, Erule, Options) ->
- asn1_test_lib:compile("MEDIA-GATEWAY-CONTROL.asn", Config, [Erule|Options]),
- asn1_test_lib:compile("OLD-MEDIA-GATEWAY-CONTROL.asn", Config, [Erule|Options]),
+ Files = ["MEDIA-GATEWAY-CONTROL.asn","OLD-MEDIA-GATEWAY-CONTROL.asn"],
+ asn1_test_lib:compile_all(Files, Config, [Erule|Options]),
{ok,'OLD-MEDIA-GATEWAY-CONTROL','MEDIA-GATEWAY-CONTROL'}.
main(no_module,_) -> ok;
diff --git a/lib/asn1/test/testValueTest.erl b/lib/asn1/test/testValueTest.erl
index 6699c0094a..f09a9ee307 100644
--- a/lib/asn1/test/testValueTest.erl
+++ b/lib/asn1/test/testValueTest.erl
@@ -84,6 +84,7 @@ main() ->
{'OctetStringSeq',<<16#40,16#41,16#42>>} = M:octetStringSeq1(),
<<16#40,16#41,16#42>> = M:otherOctetString(),
<<16#40,16#41,16#42>> = M:someOctetString(),
+ <<16#40,16#41,16#42>> = M:someOctetStringWhiteSpace(),
{'OctetStringSeq',<<16#40,16#41,16#42>>} = M:octetStringSeq2(),
{'OctetStringSeq',<<16#40,16#41,16#FF>>} = M:octetStringSeq3(),
<<16#40,16#41,16#FF>> = M:'os-1'(),
@@ -94,6 +95,7 @@ main() ->
{'BsSeq',<<2#101101:6>>,[c]} = M:bsSeq2(),
{'BsSeq',<<2#101:3>>,[a,c]} = M:bsSeq3(),
<<2#101101:6>> = M:someBitString(),
+ <<2#101101:6>> = M:someBitStringWhiteSpace(),
<<2#101101:6>> = M:otherBitString(),
<<2#101:3>> = M:bsFromObject(),
<<2#101:3>> = M:bsFromObjectInd(),
diff --git a/lib/asn1/test/test_selective_decode.erl b/lib/asn1/test/test_selective_decode.erl
index c264e919a6..df48b5e65c 100644
--- a/lib/asn1/test/test_selective_decode.erl
+++ b/lib/asn1/test/test_selective_decode.erl
@@ -51,6 +51,10 @@ test() ->
Bytes4 = roundtrip('P-Record', 'PersonnelRecord', PRecMsg, PRecMsgDec),
{ok,_} = 'P-Record':sel_dec(Bytes4),
+ ChoExtMsg = msg('ChoExt'),
+ Bytes5 = roundtrip('PartialDecChoExtension', 'ChoExt', ChoExtMsg),
+ {ok, 42} = 'PartialDecChoExtension':selected_decode_ChoExt(Bytes5),
+
ok.
msg('F') ->
@@ -60,8 +64,10 @@ msg('E') ->
{'E',10,[{'D',11,true},{'D',12,false}],false,{dc,{'E_d_dc',13,true,{'E_d_dc_dcc',14,15}}}};
msg('M-G-C') ->
- {'MegacoMessage',asn1_NOVALUE,{'Message',1,{ip4Address,{'IP4Address',[125,125,125,111],55555}},{transactions,[{transactionReply,{'TransactionReply',50007,asn1_NOVALUE,{actionReplies,[{'ActionReply',0,asn1_NOVALUE,asn1_NOVALUE,[{auditValueReply,{auditResult,{'AuditResult',{'TerminationID',[],[255,255,255]},[{mediaDescriptor,{'MediaDescriptor',asn1_NOVALUE,{multiStream,[{'StreamDescriptor',1,{'StreamParms',{'LocalControlDescriptor',sendRecv,asn1_NOVALUE,asn1_NOVALUE,[{'PropertyParm',[0,11,0,7],[[52,48]],asn1_NOVALUE}]},{'LocalRemoteDescriptor',[[{'PropertyParm',[0,0,176,1],[[48]],asn1_NOVALUE},{'PropertyParm',[0,0,176,8],[[73,78,32,73,80,52,32,49,50,53,46,49,50,53,46,49,50,53,46,49,49,49]],asn1_NOVALUE},{'PropertyParm',[0,0,176,15],[[97,117,100,105,111,32,49,49,49,49,32,82,84,80,47,65,86,80,32,32,52]],asn1_NOVALUE},{'PropertyParm',[0,0,176,12],[[112,116,105,109,101,58,51,48]],asn1_NOVALUE}]]},{'LocalRemoteDescriptor',[[{'PropertyParm',[0,0,176,1],[[48]],asn1_NOVALUE},{'PropertyParm',[0,0,176,8],[[73,78,32,73,80,52,32,49,50,52,46,49,50,52,46,49,50,52,46,50,50,50]],asn1_NOVALUE},{'PropertyParm',[0,0,176,15],[[97,117,100,105,111,32,50,50,50,50,32,82,84,80,47,65,86,80,32,32,52]],asn1_NOVALUE},{'PropertyParm',[0,0,176,12],[[112,116,105,109,101,58,51,48]],asn1_NOVALUE}]]}}}]}}},{packagesDescriptor,[{'PackagesItem',[0,11],1},{'PackagesItem',[0,11],1}]},{statisticsDescriptor,[{'StatisticsParameter',[0,12,0,4],[[49,50,48,48]]},{'StatisticsParameter',[0,11,0,2],[[54,50,51,48,48]]},{'StatisticsParameter',[0,12,0,5],[[55,48,48]]},{'StatisticsParameter',[0,11,0,3],[[52,53,49,48,48]]},{'StatisticsParameter',[0,12,0,6],[[48,46,50]]},{'StatisticsParameter',[0,12,0,7],[[50,48]]},{'StatisticsParameter',[0,12,0,8],[[52,48]]}]}]}}}]}]}}}]}}}.
+ {'MegacoMessage',asn1_NOVALUE,{'Message',1,{ip4Address,{'IP4Address',[125,125,125,111],55555}},{transactions,[{transactionReply,{'TransactionReply',50007,asn1_NOVALUE,{actionReplies,[{'ActionReply',0,asn1_NOVALUE,asn1_NOVALUE,[{auditValueReply,{auditResult,{'AuditResult',{'TerminationID',[],[255,255,255]},[{mediaDescriptor,{'MediaDescriptor',asn1_NOVALUE,{multiStream,[{'StreamDescriptor',1,{'StreamParms',{'LocalControlDescriptor',sendRecv,asn1_NOVALUE,asn1_NOVALUE,[{'PropertyParm',[0,11,0,7],[[52,48]],asn1_NOVALUE}]},{'LocalRemoteDescriptor',[[{'PropertyParm',[0,0,176,1],[[48]],asn1_NOVALUE},{'PropertyParm',[0,0,176,8],[[73,78,32,73,80,52,32,49,50,53,46,49,50,53,46,49,50,53,46,49,49,49]],asn1_NOVALUE},{'PropertyParm',[0,0,176,15],[[97,117,100,105,111,32,49,49,49,49,32,82,84,80,47,65,86,80,32,32,52]],asn1_NOVALUE},{'PropertyParm',[0,0,176,12],[[112,116,105,109,101,58,51,48]],asn1_NOVALUE}]]},{'LocalRemoteDescriptor',[[{'PropertyParm',[0,0,176,1],[[48]],asn1_NOVALUE},{'PropertyParm',[0,0,176,8],[[73,78,32,73,80,52,32,49,50,52,46,49,50,52,46,49,50,52,46,50,50,50]],asn1_NOVALUE},{'PropertyParm',[0,0,176,15],[[97,117,100,105,111,32,50,50,50,50,32,82,84,80,47,65,86,80,32,32,52]],asn1_NOVALUE},{'PropertyParm',[0,0,176,12],[[112,116,105,109,101,58,51,48]],asn1_NOVALUE}]]}}}]}}},{packagesDescriptor,[{'PackagesItem',[0,11],1},{'PackagesItem',[0,11],1}]},{statisticsDescriptor,[{'StatisticsParameter',[0,12,0,4],[[49,50,48,48]]},{'StatisticsParameter',[0,11,0,2],[[54,50,51,48,48]]},{'StatisticsParameter',[0,12,0,5],[[55,48,48]]},{'StatisticsParameter',[0,11,0,3],[[52,53,49,48,48]]},{'StatisticsParameter',[0,12,0,6],[[48,46,50]]},{'StatisticsParameter',[0,12,0,7],[[50,48]]},{'StatisticsParameter',[0,12,0,8],[[52,48]]}]}]}}}]}]}}}]}}};
+msg('ChoExt') ->
+ {j, 42}.
roundtrip(M, T, V) ->
asn1_test_lib:roundtrip_enc(M, T, V).
diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk
index 4451489b68..b5dfc871bd 100644
--- a/lib/asn1/vsn.mk
+++ b/lib/asn1/vsn.mk
@@ -1 +1 @@
-ASN1_VSN = 5.0.12
+ASN1_VSN = 5.0.14
diff --git a/lib/common_test/Makefile b/lib/common_test/Makefile
index f2065b8a0d..5ac76f0044 100644
--- a/lib/common_test/Makefile
+++ b/lib/common_test/Makefile
@@ -45,3 +45,7 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=compiler tools crypto runtime_tools syntax_tools ftp inets \
+ debugger sasl snmp ssh reltool observer xmerl
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/common_test/doc/src/Makefile b/lib/common_test/doc/src/Makefile
index 55f978a2d1..a5f2f0975e 100644
--- a/lib/common_test/doc/src/Makefile
+++ b/lib/common_test/doc/src/Makefile
@@ -27,10 +27,6 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
include ../../vsn.mk
VSN=$(COMMON_TEST_VSN)
APPLICATION=common_test
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
# ----------------------------------------------------
# Target Specs
@@ -39,7 +35,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
CT_XML_FILES = $(CT_MODULES:=.xml)
XML_APPLICATION_FILES = ref_man.xml
-XML_REF1_FILES = ct_run.xml
+XML_REF1_FILES = ct_run_cmd.xml
# REMEMBER: links to HTML files for these modules in ref_man.xml
XML_REF3_FILES = ct.xml \
ct_master.xml \
@@ -54,7 +50,8 @@ XML_REF3_FILES = ct.xml \
ct_property_test.xml \
ct_netconfc.xml \
ct_hooks.xml \
- ct_testspec.xml
+ ct_testspec.xml \
+ ct_suite.xml
XML_REF6_FILES = common_test_app.xml
XML_PART_FILES = part.xml
@@ -80,95 +77,18 @@ XML_CHAPTER_FILES = \
BOOK_FILES = book.xml
-GIF_FILES = \
+IMAGE_FILES = \
tc_execution.gif \
config.gif \
html_logs.gif
-INSTALL_NOTES = ../../notes.html
-
XML_FILES=$(XML_APPLICATION_FILES) $(XML_REF1_FILES) $(XML_REF3_FILES) $(XML_REF6_FILES) \
$(XML_PART_FILES) $(XML_CHAPTER_FILES) $(BOOK_FILES)
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN1_FILES = $(XML_REF1_FILES:%.xml=$(MAN1DIR)/%.1)
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
-
TOP_SPECS_FILE = specs.xml
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-SPECS_FLAGS = -I../../include -I../../../snmp/include \
- -I../../../kernel/include
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: man pdf html
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-man: $(MAN6_FILES) $(MAN3_FILES) $(MAN1_FILES)
+NO_CHUNKS = ct_hooks.xml ct_suite.xml
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN1DIR)/*
- rm -f $(MAN3DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECDIR)/*
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DATA) $(MAN1DIR)/* "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6DIR)/* "$(RELEASE_PATH)/man/man6"
-
-release_spec:
-release_tests_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/common_test/doc/src/basics_chapter.xml b/lib/common_test/doc/src/basics_chapter.xml
index b18dfcc085..8f0a5acf45 100644
--- a/lib/common_test/doc/src/basics_chapter.xml
+++ b/lib/common_test/doc/src/basics_chapter.xml
@@ -143,9 +143,9 @@
</p>
<p>
The test suite module must conform to a
- <seealso marker="common_test">callback interface</seealso>
+ <seeerl marker="common_test">callback interface</seeerl>
specified by the <c>Common Test</c> test server. For details, see section
- <seealso marker="write_test_chapter#intro">Writing Test Suites</seealso>.
+ <seeguide marker="write_test_chapter#intro">Writing Test Suites</seeguide>.
</p>
<p>
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index eb42a4e5f5..07a2a3e2cd 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -52,563 +52,7 @@
<item>Step-by-step execution of test cases</item>
</list>
- <p>The following section describes the mandatory and optional test suite
- functions that <c>Common Test</c> calls during test execution.
- For more details, see section
- <seealso marker="write_test_chapter">Writing Test Suites</seealso>
- in the User's Guide.</p>
-
</description>
- <section>
- <title>Test Case Callback Functions</title>
- <p>The following functions define the callback interface
- for a test suite.</p>
- </section>
-
- <funcs>
- <func>
- <name since="">Module:all() -> Tests | {skip,Reason} </name>
- <fsummary>Returns the list of all test case groups and test cases
- in the module.</fsummary>
- <type>
- <v>Tests = [TestCase | {testcase,TestCase,TCRepeatProps} | {group,GroupName} | {group,GroupName,Properties} | {group,GroupName,Properties,SubGroups}]</v>
- <v>TestCase = atom()</v>
- <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v>
- <v>GroupName = atom()</v>
- <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}] | default</v>
- <v>SubGroups = [{GroupName,Properties} | {GroupName,Properties,SubGroups}]</v>
- <v>Shuffle = shuffle | {shuffle,Seed}</v>
- <v>Seed = {integer(),integer(),integer()}</v>
- <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
- <v>N = integer() | forever</v>
- <v>Reason = term()</v>
- </type>
-
- <desc>
- <p>MANDATORY</p>
-
- <p>Returns the list of all test cases and test case groups in the
- test suite module to be executed. This list also specifies the
- order the cases and groups are executed by <c>Common Test</c>.
- A test case is represented by an atom,
- the name of the test case function, or a <c>testcase</c> tuple
- indicating that the test case shall be repeated. A test case group is
- represented by a <c>group</c> tuple, where <c>GroupName</c>,
- an atom, is the name of the group (defined in
- <seealso marker="#Module:groups-0"><c>groups/0</c></seealso>).
- Execution properties for groups can also be specified, both
- for a top-level group and for any of its subgroups.
- Group execution properties specified here override
- properties in the group definition (see
- <seealso marker="#Module:groups-0"><c>groups/0</c></seealso>).
- (With value <c>default</c>, the group definition properties
- are used).</p>
-
- <p>If <c>{skip,Reason}</c> is returned, all test cases
- in the module are skipped and <c>Reason</c>
- is printed on the HTML result page.</p>
-
- <p>For details on groups, see section
- <seealso marker="write_test_chapter#test_case_groups">Test Case
- Groups</seealso> in the User's Guide.</p>
-
- </desc>
- </func>
-
- <func>
- <name since="">Module:groups() -> GroupDefs</name>
- <fsummary>Returns a list of test case group definitions.</fsummary>
- <type>
- <v>GroupDefs = [Group]</v>
- <v>Group = {GroupName,Properties,GroupsAndTestCases}</v>
- <v>GroupName = atom()</v>
- <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}]</v>
- <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase | {testcase,TestCase,TCRepeatProps}]</v>
- <v>TestCase = atom()</v>
- <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v>
- <v>Shuffle = shuffle | {shuffle,Seed}</v>
- <v>Seed = {integer(),integer(),integer()}</v>
- <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
- <v>N = integer() | forever</v>
- </type>
-
- <desc>
- <p>OPTIONAL</p>
-
- <p>Defines test case groups. For details, see section
- <seealso marker="write_test_chapter#test_case_groups">Test Case
- Groups</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:suite() -> [Info] </name>
- <fsummary>Test suite info function (providing default data
- for the suite).</fsummary>
- <type>
- <v>Info = {timetrap,Time} | {require,Required} | {require,Name,Required} | {userdata,UserData} | {silent_connections,Conns} | {stylesheet,CSSFile} | {ct_hooks, CTHs}</v>
- <v>Time = TimeVal | TimeFunc</v>
- <v>TimeVal = MilliSec | {seconds,integer()} | {minutes,integer()} | {hours,integer()}</v>
- <v>TimeFunc = {Mod,Func,Args} | Fun</v>
- <v>MilliSec = integer()</v>
- <v>Mod = atom()</v>
- <v>Func = atom()</v>
- <v>Args = list()</v>
- <v>Fun = fun()</v>
- <v>Required = Key | {Key,SubKeys} | {Key,SubKey} | {Key,SubKey,SubKeys}</v>
- <v>Key = atom()</v>
- <v>SubKeys = SubKey | [SubKey]</v>
- <v>SubKey = atom()</v>
- <v>Name = atom()</v>
- <v>UserData = term()</v>
- <v>Conns = [atom()]</v>
- <v>CSSFile = string()</v>
- <v>CTHs = [CTHModule |</v>
- <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
- <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
- <v>CTHModule = atom()</v>
- <v>CTHInitArgs = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL</p>
-
- <p>The test suite information function. Returns a list of tagged
- tuples specifying various properties related to the execution of
- this test suite (common for all test cases in the suite).</p>
-
- <p>Tag <c>timetrap</c> sets the maximum time that each
- test case is allowed to execute (including
- <seealso marker="#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso>
- and
- <seealso marker="#Module:end_per_testcase-2"><c>end_per_testcase/2</c></seealso>).
- If the timetrap time is exceeded, the test case fails with reason
- <c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
- set a new timetrap by returning a <c>TimeVal</c>. It can also be
- used to trigger a timetrap time-out by, at some point, returning a
- value other than a <c>TimeVal</c>. For details, see section
- <seealso marker="write_test_chapter#timetraps">Timetrap Time-Outs</seealso>
- in the User's Guide.</p>
-
- <p>Tag <c>require</c> specifies configuration variables
- required by test cases (or configuration functions)
- in the suite. If the required configuration variables are not found
- in any of the configuration files, all test cases are skipped.
- For details about the <c>require</c> functionality, see funtion
- <seealso marker="ct#require-1"><c>ct:require/1,2</c></seealso>.</p>
-
- <p>With <c>userdata</c>, the user can
- specify any test suite-related information, which can be
- read by calling
- <seealso marker="ct#userdata-2"><c>ct:userdata/2</c></seealso>.</p>
-
- <p>Tag <c>ct_hooks</c> specifies the
- <seealso marker="ct_hooks_chapter">Common Test Hooks</seealso>
- to be run with this suite.</p>
-
- <p>Other tuples than the ones defined are ignored.</p>
-
- <p>For details about the test suite information function, see section
- <seealso marker="write_test_chapter#suite">Test
- Suite Information Function</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:init_per_suite(Config) -> NewConfig | {skip,Reason} |
- {skip_and_save,Reason,SaveConfig}</name>
- <fsummary>Test suite initializations.</fsummary>
- <type>
- <v>Config = NewConfig = SaveConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL; if this function is defined, then <seealso
- marker="#Module:end_per_suite-1"><c>end_per_suite/1</c></seealso>
- must also be defined.</p>
-
- <p>This configuration function is called as the first function in the
- suite. It typically contains initializations that are common for
- all test cases in the suite, and that must only be done
- once. Parameter <c>Config</c> is the configuration data
- that can be modified. Whatever is returned from this
- function is specified as <c>Config</c> to all configuration functions
- and test cases in the suite.</p>
-
- <p>If <c>{skip,Reason}</c>
- is returned, all test cases in the suite are skipped
- and <c>Reason</c> is printed in the overview log for the suite.</p>
-
- <p>For information on <c>save_config</c> and <c>skip_and_save</c>,
- see section
- <seealso marker="dependencies_chapter#save_config">Saving
- Configuration Data</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:end_per_suite(Config) -> term() |
- {save_config,SaveConfig}</name>
- <fsummary>Test suite finalization.</fsummary>
- <type>
- <v>Config = SaveConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- </type>
-
- <desc>
- <p>OPTIONAL; if this function is defined, then <seealso
- marker="#Module:init_per_suite-1"><c>init_per_suite/1</c></seealso>
- must also be defined.</p>
-
- <p>This function is called as the last test case in the
- suite. It is meant to be used for cleaning up after
- <seealso marker="#Module:init_per_suite-1"><c>init_per_suite/1</c></seealso>.</p>
- <p>For information on <c>save_config</c>, see section
- <seealso marker="dependencies_chapter#save_config">Saving
- Configuration Data</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="OTP R15B">Module:group(GroupName) -> [Info] </name>
- <fsummary>Test case group information function (providing default data
- for a test case group, that is, its test cases and
- subgroups).</fsummary>
- <type>
- <v>Info = {timetrap,Time} | {require,Required} | {require,Name,Required} | {userdata,UserData} | {silent_connections,Conns} | {stylesheet,CSSFile} | {ct_hooks, CTHs}</v>
- <v>Time = TimeVal | TimeFunc</v>
- <v>TimeVal = MilliSec | {seconds,integer()} | {minutes,integer()} | {hours,integer()}</v>
- <v>TimeFunc = {Mod,Func,Args} | Fun</v>
- <v>MilliSec = integer()</v>
- <v>Mod = atom()</v>
- <v>Func = atom()</v>
- <v>Args = list()</v>
- <v>Fun = fun()</v>
- <v>Required = Key | {Key,SubKeys} | {Key,Subkey} | {Key,Subkey,SubKeys}</v>
- <v>Key = atom()</v>
- <v>SubKeys = SubKey | [SubKey]</v>
- <v>SubKey = atom()</v>
- <v>Name = atom()</v>
- <v>UserData = term()</v>
- <v>Conns = [atom()]</v>
- <v>CSSFile = string()</v>
- <v>CTHs = [CTHModule |</v>
- <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
- <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
- <v>CTHModule = atom()</v>
- <v>CTHInitArgs = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL</p>
-
- <p>The test case group information function. It is supposed to
- return a list of tagged tuples that specify various properties
- related to the execution of a test case group (that is, its test
- cases and subgroups). Properties set by
- <seealso marker="#Module:group-1"><c>group/1</c></seealso> override
- properties with the same key that have been set previously by
- <seealso marker="#Module:suite-0"><c>suite/0</c></seealso>.</p>
-
- <p>Tag <c>timetrap</c> sets the maximum time that each
- test case is allowed to execute (including
- <seealso marker="#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso>
- and
- <seealso marker="#Module:end_per_testcase-2"><c>end_per_testcase/2</c></seealso>).
- If the timetrap time is
- exceeded, the test case fails with reason
- <c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
- set a new timetrap by returning a <c>TimeVal</c>. It can also be
- used to trigger a timetrap time-out by, at some point, returning a
- value other than a <c>TimeVal</c>. For details, see section
- <seealso marker="write_test_chapter#timetraps">Timetrap
- Time-Outs</seealso> in the User's Guide.</p>
-
- <p>Tag <c>require</c> specifies configuration variables
- required by test cases (or configuration functions)
- in the suite. If the required configuration variables are not found
- in any of the configuration files, all test cases in this group are
- skipped. For details about the <c>require</c> functionality, see
- function
- <seealso marker="ct#require-1"><c>ct:require/1,2</c></seealso>.</p>
-
- <p>With <c>userdata</c>, the user can
- specify any test case group related information that can be
- read by calling
- <seealso marker="ct#userdata-2"><c>ct:userdata/2</c></seealso>.</p>
-
- <p>Tag <c>ct_hooks</c> specifies the
- <seealso marker="ct_hooks_chapter">Common Test Hooks</seealso>
- to be run with this suite.</p>
-
- <p>Other tuples than the ones defined are ignored.</p>
-
- <p>For details about the test case group information function,
- see section <seealso marker="write_test_chapter#group_info">Group
- Information Function</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:init_per_group(GroupName, Config) -> NewConfig |
- {skip,Reason}</name>
- <fsummary>Test case group initializations.</fsummary>
- <type>
- <v>GroupName = atom()</v>
- <v>Config = NewConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL; if this function is defined, then <seealso
- marker="#Module:end_per_group-2"><c>end_per_group/2</c></seealso>
- must also be defined.</p>
-
- <p>This configuration function is called before execution of a
- test case group. It typically contains initializations that are
- common for all test cases and subgroups in the group, and that
- must only be performed once. <c>GroupName</c> is the name of the
- group, as specified in the group definition (see
- <seealso marker="#Module:groups-0"><c>groups/0</c></seealso>).
- Parameter <c>Config</c> is the configuration data that can be
- modified.
- The return value of this function is given as <c>Config</c>
- to all test cases and subgroups in the group.</p>
-
- <p>If <c>{skip,Reason}</c>
- is returned, all test cases in the group are skipped and
- <c>Reason</c> is printed in the overview log for the group.</p>
-
- <p>For information about test case groups, see section
- <seealso marker="write_test_chapter#test_case_groups">Test Case
- Groups</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:end_per_group(GroupName, Config) -> term() |
- {return_group_result,Status}</name>
- <fsummary>Test case group finalization.</fsummary>
- <type>
- <v>GroupName = atom()</v>
- <v>Config = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Status = ok | skipped | failed</v>
- </type>
-
- <desc>
- <p>OPTIONAL; if this function is defined, then <seealso
- marker="#Module:init_per_group-2"><c>init_per_group/2</c></seealso>
- must also be defined.</p>
-
- <p>This function is called after the execution of a test case group
- is finished. It is meant to be used for cleaning up after
- <seealso marker="#Module:init_per_group-2"><c>init_per_group/2</c></seealso>.
- A status value for a nested subgroup can be returned with
- <c>{return_group_result,Status}</c>. The status can be retrieved in
- <seealso marker="#Module:end_per_group-2"><c>end_per_group/2</c></seealso>
- for the group on the level above. The status is also used by
- <c>Common Test</c> for deciding if execution of a group is to
- proceed if property <c>sequence</c> or <c>repeat_until_*</c>
- is set.</p>
-
- <p>For details about test case groups, see section
- <seealso marker="write_test_chapter#test_case_groups">Test Case
- Groups</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail,Reason} | {skip,Reason}</name>
- <fsummary>Test case initializations.</fsummary>
- <type>
- <v> TestCase = atom()</v>
- <v> Config = NewConfig = [{Key,Value}]</v>
- <v> Key = atom()</v>
- <v> Value = term()</v>
- <v> Reason = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL; if this function is defined,
- then <seealso marker="#Module:end_per_testcase-2">
- <c>end_per_testcase/2</c></seealso> must also be
- defined.</p>
-
- <p>This function is called before each test case. Argument
- <c>TestCase</c> is the test case name, and
- <c>Config</c> (list of key-value tuples) is the configuration
- data that can be modified. The <c>NewConfig</c> list returned
- from this function is given as <c>Config</c> to the test case.
- If <c>{fail,Reason}</c> is returned, the test case is
- marked as failed without being executed.</p>
-
- <p>If <c>{skip,Reason}</c> is returned, the test case is skipped
- and <c>Reason</c> is printed in the overview log for the suite.</p>
- </desc>
- </func>
-
- <func>
- <name since="">Module:end_per_testcase(TestCase, Config) -> term() | {fail,Reason} | {save_config,SaveConfig}</name>
- <fsummary>Test case finalization.</fsummary>
- <type>
- <v>TestCase = atom()</v>
- <v>Config = SaveConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
-
- <p>OPTIONAL; if this function is defined,
- then <seealso marker="#Module:init_per_testcase-2">
- <c>init_per_testcase/2</c></seealso> must also be
- defined.</p>
-
- <p>This function is called after each test case, and can be used
- to clean up after
- <seealso marker="#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso>
- and the test case. Any return value (besides <c>{fail,Reason}</c>
- and <c>{save_config,SaveConfig}</c>) is ignored. By returning
- <c>{fail,Reason}</c>, <c>TestCase</c> is marked as faulty (even
- though it was successful in the sense that it returned
- a value instead of terminating).</p>
-
- <p>For information on <c>save_config</c>, see section
- <seealso marker="dependencies_chapter#save_config">Saving
- Configuration Data</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="OTP R14B">Module:Testcase() -> [Info] </name>
- <fsummary>Test case information function.</fsummary>
- <type>
- <v>Info = {timetrap,Time} | {require,Required} | {require,Name,Required} | {userdata,UserData} | {silent_connections,Conns}</v>
- <v>Time = TimeVal | TimeFunc</v>
- <v>TimeVal = MilliSec | {seconds,integer()} | {minutes,integer()} | {hours,integer()}</v>
- <v>TimeFunc = {Mod,Func,Args} | Fun</v>
- <v>MilliSec = integer()</v>
- <v>Mod = atom()</v>
- <v>Func = atom()</v>
- <v>Args = list()</v>
- <v>Fun = fun()</v>
- <v>Required = Key | {Key,SubKeys} | {Key,Subkey} | {Key,Subkey,SubKeys}</v>
- <v>Key = atom()</v>
- <v>SubKeys = SubKey | [SubKey]</v>
- <v>SubKey = atom()</v>
- <v>Name = atom()</v>
- <v>UserData = term()</v>
- <v>Conns = [atom()]</v>
- </type>
-
- <desc>
-
- <p>OPTIONAL</p>
-
- <p>The test case information function. It is supposed to
- return a list of tagged tuples that specify various properties
- related to the execution of this particular test case.
- Properties set by
- <seealso marker="#Module:Testcase-0"><c>Testcase/0</c></seealso>
- override properties set previously for the test case by
- <seealso marker="#Module:group-1"><c>group/1</c></seealso> or
- <seealso marker="#Module:suite-0"><c>suite/0</c></seealso>.</p>
-
- <p>Tag <c>timetrap</c> sets the maximum time that the
- test case is allowed to execute. If the timetrap time is
- exceeded, the test case fails with reason <c>timetrap_timeout</c>.
- <seealso marker="#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso>
- and
- <seealso marker="#Module:end_per_testcase-2"><c>end_per_testcase/2</c></seealso>
- are included in the timetrap time.
- A <c>TimeFunc</c> function can be used to
- set a new timetrap by returning a <c>TimeVal</c>. It can also be
- used to trigger a timetrap time-out by, at some point, returning a
- value other than a <c>TimeVal</c>. For details, see section
- <seealso marker="write_test_chapter#timetraps">Timetrap
- Time-Outs</seealso> in the User's Guide.</p>
-
- <p>Tag <c>require</c> specifies configuration variables
- that are required by the test case (or <c>init_per_testcase/2</c>
- or <c>end_per_testcase/2</c>).
- If the required configuration variables are not found in any of the
- configuration files, the test case is skipped. For details about
- the <c>require</c> functionality, see function
- <seealso marker="ct#require-1"><c>ct:require/1,2</c></seealso>.</p>
-
- <p>If <c>timetrap</c> or <c>require</c> is not set, the
- default values specified by
- <seealso marker="#Module:suite-0"><c>suite/0</c></seealso> (or
- <seealso marker="#Module:group-1"><c>group/1</c></seealso>) are used.</p>
-
- <p>With <c>userdata</c>, the user can specify any test case-related
- information that can be read by calling
- <seealso marker="ct#userdata-3"><c>ct:userdata/3</c></seealso>.</p>
-
- <p>Other tuples than the ones defined are ignored.</p>
-
- <p>For details about the test case information function, see section
- <seealso marker="write_test_chapter#info_function">Test
- Case Information Function</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since="OTP R14B">Module:Testcase(Config) -> term() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name>
- <fsummary>A test case.</fsummary>
- <type>
- <v>Config = SaveConfig = [{Key,Value}]</v>
- <v>Key = atom()</v>
- <v>Value = term()</v>
- <v>Reason = term()</v>
- <v>Comment = string()</v>
- </type>
-
- <desc>
- <p>MANDATORY</p>
-
- <p>The implementation of a test case. Call the functions to test and
- check the result. If something fails, ensure the
- function causes a runtime error or call
- <seealso marker="ct#fail-1"><c>ct:fail/1,2</c></seealso>
- (which also causes the test case process to terminate).</p>
-
- <p>Elements from the <c>Config</c> list can, for example, be read
- with <c>proplists:get_value/2</c> in STDLIB
- (or the macro <c>?config</c> defined in <c>ct.hrl</c>).</p>
-
- <p>If you decide not to run the test case after all, return
- <c>{skip,Reason}</c>. <c>Reason</c> is then
- printed in field <c>Comment</c> on the HTML result page.</p>
-
- <p>To print some information in field <c>Comment</c> on the HTML
- result page, return <c>{comment,Comment}</c>.</p>
-
- <p>If the function returns anything else, the test case is
- considered successful. The return value always gets printed
- in the test case log file.</p>
-
- <p>For details about test case implementation, see section
- <seealso marker="write_test_chapter#test_cases">Test Cases</seealso>
- in the User's Guide.</p>
-
- <p>For information on <c>save_config</c> and <c>skip_and_save</c>,
- see section
- <seealso marker="dependencies_chapter#save_config">Saving
- Configuration Data</seealso> in the User's Guide.</p>
- </desc>
- </func>
-
- </funcs>
-
</erlref>
diff --git a/lib/common_test/doc/src/config_file_chapter.xml b/lib/common_test/doc/src/config_file_chapter.xml
index db93744214..c055a31577 100644
--- a/lib/common_test/doc/src/config_file_chapter.xml
+++ b/lib/common_test/doc/src/config_file_chapter.xml
@@ -74,14 +74,14 @@
variable (<c>CfgVarName</c> in the previous definition) exists before
attempting to read the associated value in a test case or configuration function.</p>
- <p><c>require</c> is an assert statement, which can be part of the <seealso
- marker="write_test_chapter#suite">Test Suite Information Function</seealso> or
- <seealso marker="write_test_chapter#info_function">Test Case Information
- Function</seealso>. If the required variable is unavailable, the
+ <p><c>require</c> is an assert statement, which can be part of the <seeguide
+ marker="write_test_chapter#suite">Test Suite Information Function</seeguide> or
+ <seeguide marker="write_test_chapter#info_function">Test Case Information
+ Function</seeguide>. If the required variable is unavailable, the
test is skipped (unless a default value has been specified, see section
- <seealso marker="write_test_chapter#info_function">Test Case Information
- Function</seealso> for details). Also, function
- <seealso marker="ct#require-1"><c>ct:require/1/2</c></seealso> can be called
+ <seeguide marker="write_test_chapter#info_function">Test Case Information
+ Function</seeguide> for details). Also, function
+ <seemfa marker="ct#require/1"><c>ct:require/1/2</c></seemfa> can be called
from a test case to check if a specific variable is available. The return
value from this function must be checked explicitly and appropriate
action be taken depending on the result (for example, to skip the test case
@@ -91,7 +91,7 @@
information-list is to look like
<c>{require,CfgVarName}</c> or <c>{require,AliasName,CfgVarName}</c>.
The arguments <c>AliasName</c> and <c>CfgVarName</c> are the same as the
- arguments to <seealso marker="ct#require-1"><c>ct:require/1,2</c></seealso>.
+ arguments to <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.
<c>AliasName</c> becomes an alias for the configuration variable,
and can be used as reference to the configuration data value.
The configuration variable can be associated with any
@@ -103,7 +103,7 @@
(or test case) and improve readability.</item>
</list>
<p>To read the value of a configuration variable, use function
- <seealso marker="ct#get_config-1"><c>get_config/1,2,3</c></seealso>.
+ <seemfa marker="ct#get_config/1"><c>get_config/1,2,3</c></seemfa>.
</p>
<p><em>Example:</em></p>
<pre>
@@ -121,7 +121,7 @@
<title>Using Configuration Variables Defined in Multiple Files</title>
<p>If a configuration variable is defined in multiple files and you
want to access all possible values, use function
- <seealso marker="ct#get_config-3"><c>ct:get_config/3</c></seealso>
+ <seemfa marker="ct#get_config/3"><c>ct:get_config/3</c></seemfa>
and specify <c>all</c> in the options list. The values are then
returned in a list and the order of the elements corresponds to the order
that the configuration files were specified at startup.</p>
@@ -134,7 +134,7 @@
if they must be stored in open and shared directories.</p>
<p>To have <c>Common Test</c> encrypt a
specified file using function <c>DES3</c> in application <c>Crypto</c>,
- call <seealso marker="ct#encrypt_config_file-2"><c>ct:encrypt_config_file/2,3</c></seealso>
+ call <seemfa marker="ct#encrypt_config_file/2"><c>ct:encrypt_config_file/2,3</c></seemfa>
The encrypted file can then be used as a regular configuration file
in combination with other encrypted files or normal text files. However, the
key for decrypting the configuration file must be provided when running the test.
@@ -142,16 +142,16 @@
<c>decrypt_file</c>, or a key file in a predefined location.</p>
<p><c>Common Test</c> also provides decryption functions,
- <seealso marker="ct#decrypt_config_file-2"><c>ct:decrypt_config_file/2,3</c></seealso>,
+ <seemfa marker="ct#decrypt_config_file/2"><c>ct:decrypt_config_file/2,3</c></seemfa>,
for recreating the original text files.</p>
</section>
<section>
<title>Opening Connections Using Configuration Data</title>
<p>Two different methods for opening a connection using the support functions
- in, for example, <seealso marker="ct_ssh"><c>ct_ssh</c></seealso>,
- <seealso marker="ct_ftp"><c>ct_ftp</c></seealso>, and
- <seealso marker="ct_telnet"><c>ct_telnet</c></seealso> follows:</p>
+ in, for example, <seeerl marker="ct_ssh"><c>ct_ssh</c></seeerl>,
+ <seeerl marker="ct_ftp"><c>ct_ftp</c></seeerl>, and
+ <seeerl marker="ct_telnet"><c>ct_telnet</c></seeerl> follows:</p>
<list type="bulleted">
<item>Using a configuration target name (an alias) as reference.</item>
<item>Using the configuration variable as reference.</item>
@@ -457,7 +457,7 @@
<p>Here, the handler also provides for dynamically reloading of
configuration variables. If
- <seealso marker="ct#reload_config-1"><c>ct:reload_config(localtime)</c></seealso> is called from
+ <seemfa marker="ct#reload_config/1"><c>ct:reload_config(localtime)</c></seemfa> is called from
the test case function, all variables loaded with <c>config_driver:read_config/1</c>
are updated with their latest values, and the new value for variable
<c>localtime</c> is returned.</p>
diff --git a/lib/common_test/doc/src/cover_chapter.xml b/lib/common_test/doc/src/cover_chapter.xml
index 6d063e8b2f..12db7226c4 100644
--- a/lib/common_test/doc/src/cover_chapter.xml
+++ b/lib/common_test/doc/src/cover_chapter.xml
@@ -62,7 +62,7 @@
is running on. If so, you must specify these other nodes in the
cover specification file or add them dynamically to the code coverage
set of nodes. For details on the latter, see module
- <seealso marker="ct_cover"><c>ct_cover</c></seealso>.</p>
+ <seeerl marker="ct_cover"><c>ct_cover</c></seeerl>.</p>
<p>In the cover specification file you can also specify your
required level of the code coverage analysis; <c>details</c> or
@@ -85,16 +85,16 @@
<p>To activate the code coverage support, specify the name of the cover
specification file as you start <c>Common Test</c>.
Do this by using flag <c>-cover</c> with
- <seealso marker="ct_run"><c>ct_run</c></seealso>,
+ <seecom marker="ct_run"><c>ct_run</c></seecom>,
for example:</p>
<pre>
$ ct_run -dir $TESTOBJS/db -cover $TESTOBJS/db/config/db.coverspec</pre>
<p>You can also pass the cover specification file name in a
- call to <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>,
+ call to <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>,
by adding a <c>{cover,CoverSpec}</c> tuple to argument <c>Opts</c>.</p>
<p>You can also enable code coverage in your test specifications (see section
- <seealso marker="run_test_chapter#test_specifications">Test Specifications</seealso>
+ <seeguide marker="run_test_chapter#test_specifications">Test Specifications</seeguide>
in section Running Tests and Analyzing Results).</p>
</section>
@@ -116,9 +116,9 @@
<p>The option can be set by using flag <c>-cover_stop</c> with
<c>ct_run</c>, by adding <c>{cover_stop,true|false}</c> to argument
<c>Opts</c> to
- <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>,
+ <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>,
or by adding a <c>cover_stop</c> term in the test specification (see section
- <seealso marker="run_test_chapter#test_specifications">Test Specifications</seealso>
+ <seeguide marker="run_test_chapter#test_specifications">Test Specifications</seeguide>
in section Running Tests and Analyzing Results).</p>
</section>
@@ -257,7 +257,7 @@
<p>Then <c>m1</c> is cover compiled in test run <c>s2</c>,
but not shown in the coverage log. Instead, if
- <seealso marker="ct_cover#cross_cover_analyse-2"><c>ct_cover:cross_cover_analyse/2</c></seealso>
+ <seemfa marker="ct_cover#cross_cover_analyse/2"><c>ct_cover:cross_cover_analyse/2</c></seemfa>
is called after both <c>s1</c> and <c>s2</c> test runs are completed,
the accumulated result for <c>m1</c> is available in the cross cover
log for test run <c>s1</c>.</p>
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml
index 83c0ecb309..366d856e08 100644
--- a/lib/common_test/doc/src/ct.xml
+++ b/lib/common_test/doc/src/ct.xml
@@ -57,85 +57,58 @@
<item><p><c>data_dir</c> - Data file directory</p></item>
<item><p><c>priv_dir</c> - Scratch file directory</p></item>
<item><p>Whatever added by
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite/1</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>
or
- <seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
in the test suite.</p></item>
</list>
</description>
- <section>
- <title>Data Types</title>
- <marker id="types"/>
- <taglist>
-
- <tag>
- <marker id="type-handle"/>
- <c>handle() = pid()</c>
- </tag>
- <item>
+ <datatypes>
+ <datatype>
+ <name>handle() = pid()</name>
+ <desc>
<p>The identity (handle) of a connection.</p>
- </item>
+ </desc>
+ </datatype>
- <tag>
- <marker id="type-config_key"/>
- <c>config_key() = atom()</c>
- </tag>
- <item>
+ <datatype>
+ <name>config_key() = atom()</name>
+ <desc>
<p>A configuration key which exists in a configuration file</p>
- </item>
-
- <tag>
- <marker id="type-target_name"/><c>target_name() = atom()</c>
- </tag>
- <item>
+ </desc>
+ </datatype>
+ <datatype>
+ <name>target_name() = atom()</name>
+ <desc>
<p>A name and association to configuration data introduced
- through a require statement, or a call to
- <seealso marker="#require-2"><c>ct:require/2</c></seealso>,
- for example,
- <c>ct:require(mynodename,{node,[telnet]})</c>.</p>
- </item>
-
- <tag>
- <marker id="type-key_or_name"/>
- <c>key_or_name() = config_key() | target_name()</c>
- </tag>
- <item/>
-
- <tag>
- <marker id="type-conn_log_options"/>
- <c>conn_log_options() = [conn_log_option()]</c>
- </tag>
- <item>
+ through a require statement, or a call to
+ <seemfa marker="#require/2"><c>ct:require/2</c></seemfa>,
+ for example,
+ <c>ct:require(mynodename,{node,[telnet]})</c>.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name>key_or_name() = config_key() | target_name()</name>
+ <name>conn_log_options() = [conn_log_option()]</name>
+ <desc>
<p>Options that can be given to the <c>cth_conn_log</c> hook,
- which is used for logging of NETCONF and Telnet
- connections. See
- <seealso marker="ct_netconfc#Logging">ct_netconfc</seealso>
- or <seealso marker="ct_telnet#Logging">ct_telnet</seealso>
- for description and examples of how to use this hook.</p>
- </item>
-
- <tag>
- <marker id="type-conn_log_option"/>
- <c>conn_log_option() = {log_type,conn_log_type()} | {hosts,[key_or_name()]}</c>
- </tag>
- <item/>
-
- <tag>
- <marker id="type-conn_log_type"/>
- <c>conn_log_type() = raw | pretty | html | silent</c>
- </tag>
- <item/>
-
- <tag>
- <marker id="type-conn_log_mod"/>
- <c>conn_log_mod() = ct_netconfc | ct_telnet</c>
- </tag>
- <item/>
-
- </taglist>
- </section>
+ which is used for logging of NETCONF and Telnet
+ connections. See
+ <seeerl marker="ct_netconfc#Logging">ct_netconfc</seeerl>
+ or <seeerl marker="ct_telnet#Logging">ct_telnet</seeerl>
+ for description and examples of how to use this hook.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name>conn_log_option() = {log_type,conn_log_type()} | {hosts,[key_or_name()]}</name>
+ <name>conn_log_type() = raw | pretty | html | silent</name>
+ <name>conn_log_mod() = ct_netconfc | ct_telnet</name>
+ </datatype>
+
+ </datatypes>
<funcs>
<func>
@@ -168,9 +141,9 @@
<desc><marker id="add_config-2"/>
<p>Loads configuration variables using the specified callback module and
configuration string. The callback module is to be either loaded or
- present in the code part. Loaded configuration variables can later
+ present in the code path. Loaded configuration variables can later
be removed using function
- <seealso marker="#remove_config-2"><c>ct:remove_config/2</c></seealso>.
+ <seemfa marker="#remove_config/2"><c>ct:remove_config/2</c></seemfa>.
</p>
</desc>
</func>
@@ -190,17 +163,17 @@
The user can then interact with the Erlang node running the tests,
for example, for debugging purposes or for manually executing a
part of the test case. If a parallel group is executing,
- <seealso marker="#break-2"><c>ct:break/2</c></seealso> is to be
+ <seemfa marker="#break/2"><c>ct:break/2</c></seemfa> is to be
called instead.</p>
<p>A cancelled timetrap is not automatically reactivated after the
- break, but must be started exlicitly with
- <seealso marker="#timetrap-1"><c>ct:timetrap/1</c></seealso>.</p>
+ break, but must be started explicitly with
+ <seemfa marker="#timetrap/1"><c>ct:timetrap/1</c></seemfa>.</p>
<p>In order for the break/continue functionality to work, <c>Common
Test</c> must release the shell process controlling <c>stdin</c>.
This is done by setting start option <c>release_shell</c>
to <c>true</c>. For details, see section
- <seealso marker="run_test_chapter#erlang_shell_or_program">Running
- Tests from the Erlang Shell or from an Erlang Program</seealso>
+ <seeguide marker="run_test_chapter#erlang_shell_or_program">Running
+ Tests from the Erlang Shell or from an Erlang Program</seeguide>
in the User's Guide.</p>
</desc>
</func>
@@ -216,14 +189,14 @@
</type>
<desc><marker id="break-2"/>
<p>Works the same way as
- <seealso marker="#break-1"><c>ct:break/1</c></seealso>, only
+ <seemfa marker="#break/1"><c>ct:break/1</c></seemfa>, only
argument <c>TestCase</c> makes it possible to pause a test case
executing in a parallel group. Function
- <seealso marker="#continue-1"><c>ct:continue/1</c></seealso> is to
+ <seemfa marker="#continue/1"><c>ct:continue/1</c></seemfa> is to
be used to resume execution of <c>TestCase</c>.</p>
<p>For details, see
- <seealso marker="#break/1"><c>ct:break/1</c></seealso>.</p>
+ <seemfa marker="#break/1"><c>ct:break/1</c></seemfa>.</p>
</desc>
</func>
@@ -235,7 +208,7 @@
</type>
<desc><marker id="capture_get-0"/>
<p>Equivalent to
- <seealso marker="#capture_get-1">ct:capture_get([default])</seealso>.</p>
+ <seemfa marker="#capture_get/1">ct:capture_get([default])</seemfa>.</p>
</desc>
</func>
@@ -255,9 +228,9 @@
If <c>ExclCategories = []</c>, no filtering takes place.</p>
<p>See also
- <seealso marker="#capture_start-0"><c>ct:capture_start/0</c></seealso>,
- <seealso marker="#capture_stop-0"><c>ct:capture_stop/0</c></seealso>,
- <seealso marker="#log-3"><c>ct:log/3</c></seealso>.</p>
+ <seemfa marker="#capture_start/0"><c>ct:capture_start/0</c></seemfa>,
+ <seemfa marker="#capture_stop/0"><c>ct:capture_stop/0</c></seemfa>,
+ <seemfa marker="#log/3"><c>ct:log/3</c></seemfa>.</p>
</desc>
</func>
@@ -270,8 +243,8 @@
during execution of the test case.</p>
<p>See also
- <seealso marker="#capture_get-1"><c>ct:capture_get/1</c></seealso>,
- <seealso marker="#capture_stop-0"><c>ct:capture_stop/0</c></seealso>.</p>
+ <seemfa marker="#capture_get/1"><c>ct:capture_get/1</c></seemfa>,
+ <seemfa marker="#capture_stop/0"><c>ct:capture_stop/0</c></seemfa>.</p>
</desc>
</func>
@@ -284,8 +257,8 @@
<c>capture_start/0</c>).</p>
<p>See also
- <seealso marker="#capture_get-1"><c>ct:capture_get/1</c></seealso>,
- <seealso marker="#capture_start-0"><c>ct:capture_start/0</c></seealso>.</p>
+ <seemfa marker="#capture_get/1"><c>ct:capture_get/1</c></seemfa>,
+ <seemfa marker="#capture_start/0"><c>ct:capture_start/0</c></seemfa>.</p>
</desc>
</func>
@@ -321,7 +294,7 @@
<p>Arguments <c>Format</c> and <c>Args</c> are used in a call to
<c>io_lib:format/2</c> to create the comment string. The behavior
of <c>comment/2</c> is otherwise the same as function
- <seealso marker="#comment-1"><c>ct:comment/1</c></seealso>.</p>
+ <seemfa marker="#comment/1"><c>ct:comment/1</c></seemfa>.</p>
</desc>
</func>
@@ -332,7 +305,7 @@
<desc><marker id="continue-0"/>
<p>This function must be called to continue after a test case
(not executing in a parallel group) has called function
- <seealso marker="#break-1"><c>ct:break/1</c></seealso>.</p>
+ <seemfa marker="#break/1"><c>ct:break/1</c></seemfa>.</p>
</desc>
</func>
@@ -345,7 +318,7 @@
</type>
<desc><marker id="continue-1"/>
<p>This function must be called to continue after a test case has
- called <seealso marker="#break-2"><c>ct:break/2</c></seealso>.
+ called <seemfa marker="#break/2"><c>ct:break/2</c></seemfa>.
If the paused test case, <c>TestCase</c>, executes in a parallel
group, this function, rather than <c>continue/0</c>, must be used
to let the test case proceed.</p>
@@ -363,7 +336,7 @@
</type>
<desc><marker id="decrypt_config_file-2"/>
<p>Decrypts <c>EncryptFileName</c>, previously generated with
- <seealso marker="#encrypt_config_file-2"><c>ct:encrypt_config_file/2,3</c></seealso>.
+ <seemfa marker="#encrypt_config_file/2"><c>ct:encrypt_config_file/2,3</c></seemfa>.
The original file contents is saved in the target file. The
encryption key, a string, must be available in a text file named
<c>.ct_config.crypt</c>, either in the current directory, or the
@@ -383,7 +356,7 @@
</type>
<desc><marker id="decrypt_config_file-3"/>
<p>Decrypts <c>EncryptFileName</c>, previously generated with
- <seealso marker="#encrypt_config_file-2"><c>ct:encrypt_config_file/2,3</c></seealso>.
+ <seemfa marker="#encrypt_config_file/2"><c>ct:encrypt_config_file/2,3</c></seemfa>.
The original file contents is saved in the target file. The key
must have the same value as that used for encryption.</p>
</desc>
@@ -407,11 +380,11 @@
<p>For information about using encrypted configuration files when
running tests, see section
- <seealso marker="config_file_chapter#encrypted_config_files">Encrypted
- Configuration Files</seealso> in the User's Guide.</p>
+ <seeguide marker="config_file_chapter#encrypted_config_files">Encrypted
+ Configuration Files</seeguide> in the User's Guide.</p>
<p>For details on DES3 encryption/decryption, see application
- <seealso marker="crypto:index"><c>Crypto</c></seealso>.</p>
+ <seeapp marker="crypto:index"><c>Crypto</c></seeapp>.</p>
</desc>
</func>
@@ -433,11 +406,11 @@
<p>For information about using encrypted configuration files when
running tests, see section
- <seealso marker="config_file_chapter#encrypted_config_files">Encrypted
- Configuration Files</seealso> in the User's Guide.</p>
+ <seeguide marker="config_file_chapter#encrypted_config_files">Encrypted
+ Configuration Files</seeguide> in the User's Guide.</p>
<p>For details on DES3 encryption/decryption, see application
- <seealso marker="crypto:index"><c>Crypto</c></seealso>.</p>
+ <seeapp marker="crypto:index"><c>Crypto</c></seeapp>.</p>
</desc>
</func>
@@ -473,8 +446,8 @@
<name since="">get_config(Required) -&gt; Value</name>
<fsummary>Equivalent to get_config(Required, undefined, []).</fsummary>
<desc><marker id="get_config-1"/>
- <p>Equivalent to <seealso marker="#get_config-3"><c>ct:get_config(Required,
- undefined, [])</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#get_config/3"><c>ct:get_config(Required,
+ undefined, [])</c></seemfa>.</p>
</desc>
</func>
@@ -482,8 +455,8 @@
<name since="">get_config(Required, Default) -&gt; Value</name>
<fsummary>Equivalent to get_config(Required, Default, []).</fsummary>
<desc><marker id="get_config-2"/>
- <p>Equivalent to <seealso marker="#get_config-3"><c>ct:get_config(Required,
- Default, [])</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#get_config/3"><c>ct:get_config(Required,
+ Default, [])</c></seemfa>.</p>
</desc>
</func>
@@ -505,7 +478,7 @@
<p>Returns the matching values or configuration elements, given a
configuration variable key or its associated name (if one has been
specified with
- <seealso marker="#require-2"><c>ct:require/2</c></seealso>
+ <seemfa marker="#require/2"><c>ct:require/2</c></seemfa>
or a <c>require</c> statement).</p>
<p><em>Example:</em></p>
@@ -528,7 +501,7 @@
ct:get_config(unknownkey,Default) -&gt; Default</pre>
<p>If a configuration variable key has been associated with a name (by
- <seealso marker="#require-2"><c>ct:require/2</c></seealso>
+ <seemfa marker="#require/2"><c>ct:require/2</c></seemfa>
or a <c>require</c> statement), the name can be used instead
of the key to read the value:</p>
@@ -546,10 +519,10 @@
returned elements are then on the form <c>{Required,Value}</c>.</p>
<p>See also
- <seealso marker="#get_config-1"><c>ct:get_config/1</c></seealso>,
- <seealso marker="#get_config-2"><c>ct:get_config/2</c></seealso>,
- <seealso marker="#require-1"><c>ct:require/1</c></seealso>,
- <seealso marker="#require-2"><c>ct:require/2</c></seealso>.</p>
+ <seemfa marker="#get_config/1"><c>ct:get_config/1</c></seemfa>,
+ <seemfa marker="#get_config/2"><c>ct:get_config/2</c></seemfa>,
+ <seemfa marker="#require/1"><c>ct:require/1</c></seemfa>,
+ <seemfa marker="#require/2"><c>ct:require/2</c></seemfa>.</p>
</desc>
</func>
@@ -690,8 +663,8 @@
</type>
<desc><marker id="get_verbosity-1"/>
<p>This function returns the verbosity level for the specified logging
- category. See the <seealso marker="write_test_chapter#logging">
- User's Guide</seealso> for details. Use the value <c>default</c> to read
+ category. See the <seeguide marker="write_test_chapter#logging">
+ User's Guide</seeguide> for details. Use the value <c>default</c> to read
the general verbosity level.</p>
</desc>
</func>
@@ -744,7 +717,7 @@
<fsummary>Equivalent to log(default, 50, Format, [], []).</fsummary>
<desc><marker id="log-1"/>
<p>Equivalent to
- <seealso marker="#log-5"><c>ct:log(default, 50, Format, [], [])</c></seealso>.</p>
+ <seemfa marker="#log/5"><c>ct:log(default, 50, Format, [], [])</c></seemfa>.</p>
</desc>
</func>
@@ -757,8 +730,8 @@
<v>X2 = Format | FormatArgs</v>
</type>
<desc><marker id="log-2"/>
- <p>Equivalent to <seealso marker="#log-5"><c>ct:log(Category,
- Importance, Format, FormatArgs, [])</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#log/5"><c>ct:log(Category,
+ Importance, Format, FormatArgs, [])</c></seemfa>.</p>
</desc>
</func>
@@ -772,8 +745,8 @@
<v>X3 = Format | FormatArgs | Opts</v>
</type>
<desc><marker id="log-3"/>
- <p>Equivalent to <seealso marker="#log-5"><c>ct:log(Category,
- Importance, Format, FormatArgs, Opts)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#log/5"><c>ct:log(Category,
+ Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
</desc>
</func>
@@ -788,8 +761,8 @@
<v>X4 = FormatArgs | Opts</v>
</type>
<desc><marker id="log-4"/>
- <p>Equivalent to <seealso marker="#log-5"><c>ct:log(Category,
- Importance, Format, FormatArgs, Opts)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#log/5"><c>ct:log(Category,
+ Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
</desc>
</func>
@@ -815,8 +788,8 @@
and default value for <c>FormatArgs</c> is <c>[]</c>.</p>
<p>For details on <c>Category</c>, <c>Importance</c> and the <c>no_css</c>
- option, see section <seealso marker="write_test_chapter#logging">
- Logging - Categories and Verbosity Levels</seealso> in the User's Guide.</p>
+ option, see section <seeguide marker="write_test_chapter#logging">
+ Logging - Categories and Verbosity Levels</seeguide> in the User's Guide.</p>
<p>Common Test will not escape special HTML characters (&lt;, &gt; and &amp;)
in the text printed with this function, unless the <c>esc_chars</c>
@@ -854,7 +827,7 @@
caught by any installed event manager.</p>
<p>See also
- <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:gen_event"><c>gen_event(3)</c></seeerl>.</p>
</desc>
</func>
@@ -863,8 +836,8 @@
<fsummary>Equivalent to pal(default, 50, Format, [], []).</fsummary>
<desc><marker id="pal-1"/>
<p>Equivalent to
- <seealso marker="#pal-5"><c>ct:pal(default, 50, Format,
- [], [])</c></seealso>.</p>
+ <seemfa marker="#pal/5"><c>ct:pal(default, 50, Format,
+ [], [])</c></seemfa>.</p>
</desc>
</func>
@@ -877,8 +850,8 @@
<v>X2 = Format | FormatArgs</v>
</type>
<desc><marker id="pal-2"/>
- <p>Equivalent to <seealso marker="#pal-5"><c>ct:pal(Category,
- Importance, Format, FormatArgs, [])</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#pal/5"><c>ct:pal(Category,
+ Importance, Format, FormatArgs, [])</c></seemfa>.</p>
</desc>
</func>
@@ -892,8 +865,8 @@
<v>X3 = Format | FormatArgs | Opts</v>
</type>
<desc><marker id="pal-3"/>
- <p>Equivalent to <seealso marker="#pal-5"><c>ct:pal(Category,
- Importance, Format, FormatArgs, Opts)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#pal/5"><c>ct:pal(Category,
+ Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
</desc>
</func>
@@ -908,8 +881,8 @@
<v>X4 = FormatArgs | Opts</v>
</type>
<desc><marker id="pal-4"/>
- <p>Equivalent to <seealso marker="#pal-5"><c>ct:pal(Category,
- Importance, Format, FormatArgs, Opts)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#pal/5"><c>ct:pal(Category,
+ Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
</desc>
</func>
@@ -935,8 +908,8 @@
and default value for <c>FormatArgs</c> is <c>[]</c>.</p>
<p>For details on <c>Category</c> and <c>Importance</c>, see section
- <seealso marker="write_test_chapter#logging">Logging - Categories
- and Verbosity Levels</seealso> in the User's Guide.</p>
+ <seeguide marker="write_test_chapter#logging">Logging - Categories
+ and Verbosity Levels</seeguide> in the User's Guide.</p>
<p>Note that special characters in the text (&lt;, &gt; and &amp;) will
be escaped by Common Test before the text is printed to the log
@@ -970,8 +943,8 @@
<name since="">print(Format) -&gt; ok</name>
<fsummary>Equivalent to print(default, 50, Format, [], []).</fsummary>
<desc><marker id="print-1"/>
- <p>Equivalent to <seealso marker="#print-5"><c>ct:print(default,
- 50, Format, [], [])</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#print/5"><c>ct:print(default,
+ 50, Format, [], [])</c></seemfa>.</p>
</desc>
</func>
@@ -984,8 +957,8 @@
<v>X2 = Format | FormatArgs</v>
</type>
<desc><marker id="print-2"/>
- <p>Equivalent to <seealso marker="#print-5"><c>ct:print(Category,
- Importance, Format, FormatArgs, [])</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#print/5"><c>ct:print(Category,
+ Importance, Format, FormatArgs, [])</c></seemfa>.</p>
</desc>
</func>
@@ -999,8 +972,8 @@
<v>X3 = Format | FormatArgs | Opts</v>
</type>
<desc><marker id="print-3"/>
- <p>Equivalent to <seealso marker="#print-5"><c>ct:print(Category,
- Importance, Format, FormatArgs, Opts)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#print/5"><c>ct:print(Category,
+ Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
</desc>
</func>
@@ -1015,8 +988,8 @@
<v>X4 = FormatArgs | Opts</v>
</type>
<desc><marker id="print-4"/>
- <p>Equivalent to <seealso marker="#print-5"><c>ct:print(Category,
- Importance, Format, FormatArgs, Opts)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#print/5"><c>ct:print(Category,
+ Importance, Format, FormatArgs, Opts)</c></seemfa>.</p>
</desc>
</func>
@@ -1042,8 +1015,8 @@
and default value for <c>FormatArgs</c> is <c>[]</c>.</p>
<p>For details on <c>Category</c> and <c>Importance</c>, see section
- <seealso marker="write_test_chapter#logging">Logging - Categories
- and Verbosity Levels</seealso> in the User's Guide.</p>
+ <seeguide marker="write_test_chapter#logging">Logging - Categories
+ and Verbosity Levels</seeguide> in the User's Guide.</p>
</desc>
</func>
@@ -1117,7 +1090,7 @@
<v>Reason = term()</v>
</type>
<desc><marker id="remove_config-2"/>
- <p>Removes configuration variables (together wih their aliases)
+ <p>Removes configuration variables (together with their aliases)
that were loaded with specified callback module and configuration
string.</p>
</desc>
@@ -1170,10 +1143,10 @@
{myvar,[{sub1,[{sub2,Value}]}]}.</pre>
<p>See also
- <seealso marker="#get_config-1"><c>ct:get_config/1</c></seealso>,
- <seealso marker="#get_config-2"><c>ct:get_config/2</c></seealso>,
- <seealso marker="#get_config-3"><c>ct:get_config/3</c></seealso>,
- <seealso marker="#require-2"><c>ct:require/2</c></seealso>.</p>
+ <seemfa marker="#get_config/1"><c>ct:get_config/1</c></seemfa>,
+ <seemfa marker="#get_config/2"><c>ct:get_config/2</c></seemfa>,
+ <seemfa marker="#get_config/3"><c>ct:get_config/3</c></seemfa>,
+ <seemfa marker="#require/2"><c>ct:require/2</c></seemfa>.</p>
</desc>
</func>
@@ -1190,12 +1163,12 @@
<desc><marker id="require-2"/>
<p>Checks if the required configuration is available and gives it a
name. The semantics for <c>Required</c> is the same as in
- <seealso marker="#require-1"><c>ct:require/1</c></seealso> except
+ <seemfa marker="#require/1"><c>ct:require/1</c></seemfa> except
that a list of <c>SubKey</c>s cannot be specified.</p>
<p>If the requested data is available, the subentry is associated
with <c>Name</c> so that the value of the element can be read with
- <seealso marker="#get_config-1"><c>ct:get_config/1,2</c></seealso>
+ <seemfa marker="#get_config/1"><c>ct:get_config/1,2</c></seemfa>
provided <c>Name</c> is used instead of the whole <c>Required</c>
term.</p>
@@ -1229,10 +1202,10 @@
</note>
<p>See also
- <seealso marker="#get_config-1"><c>ct:get_config/1</c></seealso>,
- <seealso marker="#get_config-2"><c>ct:get_config/2</c></seealso>,
- <seealso marker="#get_config-3"><c>ct:get_config/3</c></seealso>,
- <seealso marker="#require-1"><c>ct:require/1</c></seealso>.</p>
+ <seemfa marker="#get_config/1"><c>ct:get_config/1</c></seemfa>,
+ <seemfa marker="#get_config/2"><c>ct:get_config/2</c></seemfa>,
+ <seemfa marker="#get_config/3"><c>ct:get_config/3</c></seemfa>,
+ <seemfa marker="#require/1"><c>ct:require/1</c></seemfa>.</p>
</desc>
</func>
@@ -1246,7 +1219,7 @@
<desc><marker id="run-1"/>
<p>Runs all test cases in all suites in the specified directories.</p>
- <p>See also <seealso marker="#run-3"><c>ct:run/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#run/3"><c>ct:run/3</c></seemfa>.</p>
</desc>
</func>
@@ -1256,7 +1229,7 @@
<desc><marker id="run-2"/>
<p>Runs all test cases in the specified suite.</p>
- <p>See also <seealso marker="#run-3"><c>ct:run/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#run/3"><c>ct:run/3</c></seemfa>.</p>
</desc>
</func>
@@ -1273,7 +1246,7 @@
<p>Runs the specified test cases.</p>
<p>Requires that
- <seealso marker="#install-1"><c>ct:install/1</c></seealso> has been
+ <seemfa marker="#install/1"><c>ct:install/1</c></seemfa> has been
run first.</p>
<p>Suites (<c>*_SUITE.erl</c>) files must be stored in <c>TestDir</c>
@@ -1339,8 +1312,8 @@
<desc><marker id="run_test-1"/>
<p>Runs tests as specified by the combination of options in
<c>Opts</c>. The options are the same as those used with program
- <c>ct_run</c>, see <seealso marker="ct_run#ct_run">Run Tests from
- Command Line</seealso> in the <c>ct_run</c> manual page.</p>
+ <c>ct_run</c>, see <seecom marker="ct_run#ct_run">Run Tests from
+ Command Line</seecom> in the <c>ct_run</c> manual page.</p>
<p>Here a <c>TestDir</c> can be used to point out the path to a
<c>Suite</c>. Option <c>testcase</c> corresponds to option
<c>-case</c> in program <c>ct_run</c>. Configuration files
@@ -1348,7 +1321,7 @@
<p><c>TestRunnerPid</c> is returned if <c>release_shell == true</c>.
For details, see
- <seealso marker="#break-1"><c>ct:break/1</c></seealso>.</p>
+ <seemfa marker="#break/1"><c>ct:break/1</c></seemfa>.</p>
<p><c>Reason</c> indicates the type of error encountered.</p>
</desc>
@@ -1383,8 +1356,8 @@
</type>
<desc><marker id="set_verbosity-2"/>
<p>Use this function to set, or modify, the verbosity level for a logging
- category. See the <seealso marker="write_test_chapter#logging">
- User's Guide</seealso> for details. Use the value <c>default</c> to set the
+ category. See the <seeguide marker="write_test_chapter#logging">
+ User's Guide</seeguide> for details. Use the value <c>default</c> to set the
general verbosity level.</p>
</desc>
</func>
@@ -1425,7 +1398,7 @@
<p>If any functions (for example, Telnet or FTP) using
"required configuration data" are to be called from the Erlang shell,
configuration data must first be required with
- <seealso marker="#require-2"><c>ct:require/2</c></seealso>.</p>
+ <seemfa marker="#require/2"><c>ct:require/2</c></seemfa>.</p>
<p><em>Example:</em></p>
@@ -1448,7 +1421,7 @@
<desc><marker id="step-3"/>
<p>Steps through a test case with the debugger.</p>
- <p>See also <seealso marker="#run-3"><c>ct:run/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#run/3"><c>ct:run/3</c></seemfa>.</p>
</desc>
</func>
@@ -1462,10 +1435,10 @@
</type>
<desc><marker id="step-4"/>
<p>Steps through a test case with the debugger. If option
- <c>config</c> has been specifed, breakpoints are also set on
+ <c>config</c> has been specified, breakpoints are also set on
the configuration functions in <c>Suite</c>.</p>
- <p>See also <seealso marker="#run-3"><c>ct:run/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#run/3"><c>ct:run/3</c></seemfa>.</p>
</desc>
</func>
@@ -1476,7 +1449,7 @@
<p>Exits the interactive mode.</p>
<p>See also
- <seealso marker="#start_interactive-0"><c>ct:start_interactive/0</c></seealso>.
+ <seemfa marker="#start_interactive/0"><c>ct:start_interactive/0</c></seemfa>.
</p>
</desc>
</func>
@@ -1495,7 +1468,7 @@
caught by any installed event manager.</p>
<p>See also
- <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>.
+ <seeerl marker="stdlib:gen_event"><c>gen_event(3)</c></seeerl>.
</p>
</desc>
</func>
@@ -1551,7 +1524,7 @@
<desc><marker id="userdata-2"/>
<p>Returns any data specified with tag <c>userdata</c> in the list
of tuples returned from
- <seealso marker="common_test#Module:suite-0"><c>suite/0</c></seealso>.</p>
+ <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>.</p>
</desc>
</func>
diff --git a/lib/common_test/doc/src/ct_cover.xml b/lib/common_test/doc/src/ct_cover.xml
index 70ea9b65ed..836a651db6 100644
--- a/lib/common_test/doc/src/ct_cover.xml
+++ b/lib/common_test/doc/src/ct_cover.xml
@@ -61,7 +61,7 @@
<p>To have effect, this function is to be called from
<c>init_per_suite/1</c> (see
- <seealso marker="common_test"><c>common_test</c></seealso>)
+ <seeerl marker="common_test"><c>common_test</c></seeerl>)
before any tests are performed.</p>
</desc>
</func>
@@ -77,8 +77,8 @@
</type>
<desc><marker id="cross_cover_analyse-2"/>
<p>Accumulates cover results over multiple tests. See section
- <seealso marker="cover_chapter#cross_cover">Cross Cover
- Analysis</seealso> in the Users's Guide.</p>
+ <seeguide marker="cover_chapter#cross_cover">Cross Cover
+ Analysis</seeguide> in the Users's Guide.</p>
</desc>
</func>
@@ -94,7 +94,7 @@
<p>Call this function to stop cover test on nodes previously
added with
- <seealso marker="#add_nodes-1"><c>ct_cover:add_nodes/1</c></seealso>.
+ <seemfa marker="#add_nodes/1"><c>ct_cover:add_nodes/1</c></seemfa>.
Results on the remote node are transferred to the <c>Common Test</c>
node.</p>
</desc>
diff --git a/lib/common_test/doc/src/ct_ftp.xml b/lib/common_test/doc/src/ct_ftp.xml
index 7ee6049486..b1e1eba6ec 100644
--- a/lib/common_test/doc/src/ct_ftp.xml
+++ b/lib/common_test/doc/src/ct_ftp.xml
@@ -41,21 +41,21 @@
</description>
- <section>
- <title>Data Types</title>
- <marker id="types"/>
- <taglist>
- <tag><c>connection() = handle() | target_name()</c></tag>
- <item><marker id="type-connection"/>
+ <datatypes>
+ <datatype>
+ <name>connection() = handle() | target_name()</name>
+ <desc>
<p>For <c>target_name</c>, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p></item>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p></desc>
+ </datatype>
+ <datatype>
- <tag><c>handle() = handle()</c></tag>
- <item><marker id="type-handle"/>
+ <name>handle() = handle()</name>
+ <desc>
<p>Handle for a specific FTP connection, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p></item>
- </taglist>
- </section>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p></desc>
+ </datatype>
+ </datatypes>
<funcs>
<func>
@@ -111,13 +111,13 @@
<p><c>RemoteFile</c> and <c>LocalFile</c> must be absolute paths.</p>
<p>The configuration file must be as for
- <seealso marker="#put-3"><c>ct_ftp:put/3</c></seealso>.</p>
+ <seemfa marker="#put/3"><c>ct_ftp:put/3</c></seemfa>.</p>
<p>For <c>target_name</c>, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p>
<p>See also
- <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>.</p>
+ <seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>.</p>
</desc>
</func>
@@ -156,10 +156,10 @@
with the handle value.</p>
<p>For information on how to create a new <c>Name</c>, see
- <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>.</p>
+ <seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>.</p>
<p>For <c>target_name</c>, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p>
</desc>
</func>
@@ -180,7 +180,7 @@
<p><c>LocalFile</c> and <c>RemoteFile</c> must be absolute paths.</p>
<p>For <c>target_name</c>, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p>
<p>If the target host is a "special" node, the FTP address must be
specified in the configuration file as follows:</p>
@@ -198,7 +198,7 @@
{password,Password}]}.</pre>
<p>See also
- <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>.</p>
+ <seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>.</p>
</desc>
</func>
@@ -210,7 +210,7 @@
<p>The file gets the same name on the local host.</p>
- <p>See also <seealso marker="#recv-3"><c>ct_ftp:recv/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#recv/3"><c>ct_ftp:recv/3</c></seemfa>.</p>
</desc>
</func>
@@ -238,7 +238,7 @@
<p>The file gets the same name on the remote host.</p>
<p>See also
- <seealso marker="#send-3"><c>ct_ftp:send/3</c></seealso>.</p>
+ <seemfa marker="#send/3"><c>ct_ftp:send/3</c></seemfa>.</p>
</desc>
</func>
diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml
index cd9323a5ff..875efec305 100644
--- a/lib/common_test/doc/src/ct_hooks.xml
+++ b/lib/common_test/doc/src/ct_hooks.xml
@@ -55,25 +55,26 @@
<p>The following sections describe the mandatory and optional CTH
functions that <c>Common Test</c> calls during test execution.
For more details, see section
- <seealso marker="ct_hooks_chapter">Common Test Hooks</seealso> in the
+ <seeguide marker="ct_hooks_chapter">Common Test Hooks</seeguide> in the
User's Guide.</p>
<p>For information about how to add a CTH to your suite, see section
- <seealso marker="ct_hooks_chapter#installing">Installing a CTH</seealso>
+ <seeguide marker="ct_hooks_chapter#installing">Installing a CTH</seeguide>
in the User's Guide.</p>
<note><p>For a minimal example of a CTH, see section
- <seealso marker="ct_hooks_chapter#example">Example CTH</seealso>
+ <seeguide marker="ct_hooks_chapter#example">Example CTH</seeguide>
in the User's Guide.</p></note>
</description>
- <section>
- <title>Callback Functions</title>
- <p>The following functions define the callback interface for a CTH.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>The following functions define the callback interface for a CTH.</p>
+ </fsdescription>
<func>
<name since="OTP R14B02">Module:init(Id, Opts) -&gt; {ok, State} | {ok, State, Priority}</name>
<fsummary>Initiates the Common Test Hook.</fsummary>
@@ -91,11 +92,11 @@
this CTH.</p>
<p><c>Id</c> is either the return value of
- <seealso marker="#Module:id-1"><c>ct_hooks:id/1</c></seealso>,
+ <seemfa marker="#Module:id/1"><c>ct_hooks:id/1</c></seemfa>,
or a <c>reference</c> (created using
- <seealso marker="erts:erlang#make_ref-0">erlang:make_ref/0</seealso>
+ <seemfa marker="erts:erlang#make_ref/0">erlang:make_ref/0</seemfa>
in ERTS) if
- <seealso marker="#Module:id-1"><c>ct_hooks:id/1</c></seealso>
+ <seemfa marker="#Module:id/1"><c>ct_hooks:id/1</c></seemfa>
is not implemented.</p>
<p><c>Priority</c> is the relative priority of this hook. Hooks with a
@@ -103,7 +104,7 @@
is set to <c>0</c>.</p>
<p>For details about when <c>init</c> is called, see section
- <seealso marker="ct_hooks_chapter#scope">CTH Scope</seealso>
+ <seeguide marker="ct_hooks_chapter#scope">CTH Scope</seeguide>
in the User's Guide.</p>
</desc>
</func>
@@ -129,28 +130,28 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso>.
+ <seemfa marker="ct_suite#Module:groups/0"><c>groups/0</c></seemfa>.
It is used to modify the test group definitions, for
instance to add or remove groups or change group properties.</p>
<p><c>GroupDefs</c> is what
- <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso>
+ <seemfa marker="ct_suite#Module:groups/0"><c>groups/0</c></seemfa>
returned, that is, a list of group definitions.</p>
<p><c>NewGroupDefs</c> is the possibly modified version of this list.</p>
<p>This function is called only if the CTH is added before
<c>init_per_suite</c> is run. For details, see section
- <seealso marker="ct_hooks_chapter#scope">CTH Scope</seealso>
+ <seeguide marker="ct_hooks_chapter#scope">CTH Scope</seeguide>
in the User's Guide.</p>
<p>Notice that for CTHs that are installed by means of the
- <seealso marker="common_test#Module:suite-0"><c>suite/0</c></seealso>
+ <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>
function, <c>post_groups/2</c> is called before
- the <seealso marker="#Module:init-2"><c>init/2</c></seealso>
+ the <seemfa marker="#Module:init/2"><c>init/2</c></seemfa>
hook function. However, for CTHs that are installed by means
of the CT start flag,
- the <seealso marker="#Module:init-2"><c>init/2</c></seealso>
+ the <seemfa marker="#Module:init/2"><c>init/2</c></seemfa>
function is called first.</p>
<note>
@@ -190,19 +191,19 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seealso marker="common_test#Module:all-0"><c>all/0</c></seealso>.
+ <seemfa marker="ct_suite#Module:all/0"><c>all/0</c></seemfa>.
It is used to modify the set of test cases and test group to
be executed, for instance to add or remove test cases and
groups, change group properties, or even skip all tests in
the suite.</p>
<p><c>Return</c> is what
- <seealso marker="common_test#Module:all-0"><c>all/0</c></seealso>
+ <seemfa marker="ct_suite#Module:all/0"><c>all/0</c></seemfa>
returned, that is, a list of test cases and groups to be
executed, or a tuple <c>{skip,Reason}</c>.</p>
<p><c>GroupDefs</c> is what
- <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso>
+ <seemfa marker="ct_suite#Module:groups/0"><c>groups/0</c></seemfa>
or the <c>post_groups/2</c> hook returned, that is, a list
of group definitions.</p>
@@ -210,16 +211,16 @@
<p>This function is called only if the CTH is added before
<c>init_per_suite</c> is run. For details, see section
- <seealso marker="ct_hooks_chapter#scope">CTH Scope</seealso>
+ <seeguide marker="ct_hooks_chapter#scope">CTH Scope</seeguide>
in the User's Guide.</p>
<p>Notice that for CTHs that are installed by means of the
- <seealso marker="common_test#Module:suite-0"><c>suite/0</c></seealso>
+ <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>
function, <c>post_all/2</c> is called before
- the <seealso marker="#Module:init-2"><c>init/2</c></seealso>
+ the <seemfa marker="#Module:init/2"><c>init/2</c></seemfa>
hook function. However, for CTHs that are installed by means
of the CT start flag,
- the <seealso marker="#Module:init-2"><c>init/2</c></seealso>
+ the <seemfa marker="#Module:init/2"><c>init/2</c></seemfa>
function is called first.</p>
<note>
@@ -252,7 +253,7 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
if it exists. It typically contains initialization/logging that must
be done before <c>init_per_suite</c> is called. If
<c>{skip,Reason}</c> or <c>{fail,Reason}</c> is returned,
@@ -269,18 +270,18 @@
<p><c>Return</c> is the result of the <c>init_per_suite</c> function.
If it is <c>{skip,Reason}</c> or <c>{fail,Reason}</c>,
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
is never called, instead the initiation is considered to be
skipped or failed, respectively. If a <c>NewConfig</c> list is
returned,
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
is called with that <c>NewConfig</c> list. For more details, see
- section <seealso marker="ct_hooks_chapter#pre">Pre Hooks</seealso>
+ section <seeguide marker="ct_hooks_chapter#pre">Pre Hooks</seeguide>
in the User's Guide.</p>
<p>This function is called only if the CTH is added before
<c>init_per_suite is run</c>. For details, see section
- <seealso marker="ct_hooks_chapter#scope">CTH Scope</seealso>
+ <seeguide marker="ct_hooks_chapter#scope">CTH Scope</seeguide>
in the User's Guide.</p>
</desc>
</func>
@@ -303,31 +304,31 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
if it exists. It typically contains extra checks to ensure that all
the correct dependencies are started correctly.</p>
<p><c>Return</c> is what
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
returned, that is, <c>{fail,Reason}</c>, <c>{skip,Reason}</c>, a
<c>Config</c> list, or a term describing how
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
failed.</p>
<p><c>NewReturn</c> is the possibly modified return value of
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso>.
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>.
To recover from a failure in
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso>,
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>,
return <c>ConfigList</c> with the <c>tc_status</c> element removed.
For more details, see
- <seealso marker="ct_hooks_chapter#post"> Post Hooks</seealso> in
+ <seeguide marker="ct_hooks_chapter#post"> Post Hooks</seeguide> in
section "Manipulating Tests" in the User's Guide.</p>
<p><c>CTHState</c> is the current internal state of the CTH.</p>
<p>This function is called only if the CTH is added before or in
<c>init_per_suite</c>. For details, see section
- <seealso marker="ct_hooks_chapter#scope">CTH Scope</seealso>
+ <seeguide marker="ct_hooks_chapter#scope">CTH Scope</seeguide>
in the User's Guide.</p>
</desc>
</func>
@@ -351,11 +352,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seealso marker="common_test#Module:init_per_group-2"><c>init_per_group</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:pre_init_per_suite-3"><c>pre_init_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:pre_init_per_suite/3"><c>pre_init_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:init_per_group-2"><c>init_per_group</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa>
instead.</p>
<p>If <c>Module:pre_init_per_group/4</c> is not exported, common_test
@@ -384,11 +385,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seealso marker="common_test#Module:init_per_group-2"><c>init_per_group</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:post_init_per_suite-4"><c>post_init_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:post_init_per_suite/4"><c>post_init_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:init_per_group-2"><c>init_per_group</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa>
instead.</p>
<p>If <c>Module:post_init_per_group/5</c> is not exported, common_test
@@ -417,11 +418,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:pre_init_per_suite-3"><c>pre_init_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:pre_init_per_suite/3"><c>pre_init_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
instead.</p>
<p>If <c>Module:pre_init_per_testcase/4</c> is not exported, common_test
@@ -454,11 +455,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:post_init_per_suite-4"><c>post_init_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:post_init_per_suite/4"><c>post_init_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa>
instead.</p>
<p>If <c>Module:post_init_per_testcase/5</c> is not exported, common_test
@@ -486,11 +487,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:pre_end_per_suite-3"><c>pre_end_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:pre_end_per_suite/3"><c>pre_end_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
instead.</p>
<p>This function cannot change the result of the test case by returning skip or fail
@@ -523,11 +524,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:post_end_per_suite-4"><c>post_end_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:post_end_per_suite/4"><c>post_end_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
instead.</p>
<p>If <c>Module:post_end_per_testcase/5</c> is not exported, common_test
@@ -556,11 +557,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seealso marker="common_test#Module:end_per_group-2"><c>end_per_group</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:pre_init_per_suite-3"><c>pre_init_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:pre_init_per_suite/3"><c>pre_init_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:end_per_group-2"><c>end_per_group</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa>
instead.</p>
<p>If <c>Module:pre_end_per_group/4</c> is not exported, common_test
@@ -589,11 +590,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seealso marker="common_test#Module:end_per_group-2"><c>end_per_group</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:post_init_per_suite-4"><c>post_init_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:post_init_per_suite/4"><c>post_init_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:end_per_group-2">end_per_group</seealso>
+ <seemfa marker="ct_suite#Module:end_per_group/2">end_per_group</seemfa>
instead.</p>
<p>If <c>Module:post_end_per_group/5</c> is not exported, common_test
@@ -621,11 +622,11 @@
<p>OPTIONAL</p>
<p>This function is called before
- <seealso marker="common_test#Module:end_per_suite-1"><c>end_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:pre_init_per_suite-3"><c>pre_init_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:pre_init_per_suite/3"><c>pre_init_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:end_per_suite-1"><c>end_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
instead.</p>
</desc>
</func>
@@ -648,11 +649,11 @@
<p>OPTIONAL</p>
<p>This function is called after
- <seealso marker="common_test#Module:end_per_suite-1"><c>end_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
if it exists. It behaves the same way as
- <seealso marker="ct_hooks#Module:post_init_per_suite-4"><c>post_init_per_suite</c></seealso>,
+ <seemfa marker="ct_hooks#Module:post_init_per_suite/4"><c>post_init_per_suite</c></seemfa>,
but for function
- <seealso marker="common_test#Module:end_per_suite-1"><c>end_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
instead.</p>
</desc>
</func>
@@ -677,9 +678,9 @@
<list type="bulleted">
<item><p>If <c>init_per_suite</c> fails, this function is called after
- <seealso marker="#Module:post_init_per_suite-4"><c>post_init_per_suite</c></seealso>.</p></item>
+ <seemfa marker="#Module:post_init_per_suite/4"><c>post_init_per_suite</c></seemfa>.</p></item>
<item><p>If a test case fails, this funcion is called after
- <seealso marker="#Module:post_end_per_testcase-5"><c>post_end_per_testcase</c></seealso>.</p></item>
+ <seemfa marker="#Module:post_end_per_testcase/5"><c>post_end_per_testcase</c></seemfa>.</p></item>
</list>
<p>If the failed test case belongs to a test case group, the first
@@ -687,11 +688,11 @@
the function name.</p>
<p>The data that comes with <c>Reason</c> follows the same format as
- <seealso marker="event_handler_chapter#failreason"><c>FailReason</c></seealso>
+ <seeguide marker="event_handler_chapter#failreason"><c>FailReason</c></seeguide>
in event
- <seealso marker="event_handler_chapter#tc_done"><c>tc_done</c></seealso>.
+ <seeguide marker="event_handler_chapter#tc_done"><c>tc_done</c></seeguide>.
For details, see section
- <seealso marker="event_handler_chapter#events">Event Handling</seealso>
+ <seeguide marker="event_handler_chapter#events">Event Handling</seeguide>
in the User's Guide.</p>
<p>If <c>Module:on_tc_fail/4</c> is not exported, common_test
@@ -722,9 +723,9 @@
<list type="bulleted">
<item><p>If <c>init_per_group</c> is skipped, this function is
called after
- <seealso marker="#Module:post_init_per_group-5"><c>post_init_per_group</c></seealso>.</p></item>
+ <seemfa marker="#Module:post_init_per_group/5"><c>post_init_per_group</c></seemfa>.</p></item>
<item><p>If a test case is skipped, this function is called after
- <seealso marker="#Module:post_end_per_testcase-5"><c>post_end_per_testcase</c></seealso>.</p></item>
+ <seemfa marker="#Module:post_end_per_testcase/5"><c>post_end_per_testcase</c></seemfa>.</p></item>
</list>
<p>If the skipped test case belongs to a test case group, the first
@@ -733,11 +734,11 @@
<p>The data that comes with <c>Reason</c> follows the same format as
events
- <seealso marker="event_handler_chapter#tc_auto_skip"><c>tc_auto_skip</c></seealso>
+ <seeguide marker="event_handler_chapter#tc_auto_skip"><c>tc_auto_skip</c></seeguide>
and
- <seealso marker="event_handler_chapter#tc_user_skip"><c>tc_user_skip</c></seealso>
+ <seeguide marker="event_handler_chapter#tc_user_skip"><c>tc_user_skip</c></seeguide>
For details, see section
- <seealso marker="event_handler_chapter#events">Event Handling</seealso>
+ <seeguide marker="event_handler_chapter#events">Event Handling</seeguide>
in the User's Guide.</p>
<p>If <c>Module:on_tc_skip/4</c> is not exported, common_test
@@ -757,7 +758,7 @@
<p>OPTIONAL</p>
<p>This function is called at the end of a CTH
- <seealso marker="ct_hooks_chapter#scope">scope</seealso>.</p>
+ <seeguide marker="ct_hooks_chapter#scope">scope</seeguide>.</p>
</desc>
</func>
@@ -774,7 +775,7 @@
<p>The <c>Id</c> identifies a CTH instance uniquely. If two CTHs return
the same <c>Id</c>, the second CTH is ignored and subsequent calls to
the CTH are only made to the first instance. For details, see section
- <seealso marker="ct_hooks_chapter#installing">Installing a CTH</seealso>
+ <seeguide marker="ct_hooks_chapter#installing">Installing a CTH</seeguide>
in the User's Guide.</p>
<p>This function is <em>not</em> to have any side effects, as it can
diff --git a/lib/common_test/doc/src/ct_hooks_chapter.xml b/lib/common_test/doc/src/ct_hooks_chapter.xml
index ea4b67be08..a4c72c1fa4 100644
--- a/lib/common_test/doc/src/ct_hooks_chapter.xml
+++ b/lib/common_test/doc/src/ct_hooks_chapter.xml
@@ -71,24 +71,24 @@
<list type="bulleted">
<item>Add <c>-ct_hooks</c> as an argument to
- <seealso marker="run_test_chapter#ct_run">ct_run</seealso>.
+ <seeguide marker="run_test_chapter#ct_run">ct_run</seeguide>.
To add multiple CTHs using this method, append them to each other
using the keyword <c>and</c>, that is,
<c>ct_run -ct_hooks cth1 [{debug,true}] and cth2 ...</c>.</item>
<item>Add tag <c>ct_hooks</c> to your
- <seealso marker="run_test_chapter#test_specifications">
- Test Specification</seealso>.</item>
+ <seeguide marker="run_test_chapter#test_specifications">
+ Test Specification</seeguide>.</item>
<item>Add tag <c>ct_hooks</c> to your call to
- <seealso marker="ct#run_test-1">ct:run_test/1</seealso>.</item>
+ <seemfa marker="ct#run_test/1">ct:run_test/1</seemfa>.</item>
</list>
<p>CTHs can also be added within a test suite. This is done by returning
<c>{ct_hooks,[CTH]}</c> in the configuration list from
- <seealso marker="common_test#Module:suite-0">suite/0</seealso>,
- <seealso marker="common_test#Module:init_per_suite-1">
- init_per_suite/1</seealso>, or
- <seealso marker="common_test#Module:init_per_group-2">
- init_per_group/2</seealso>.</p>
+ <seemfa marker="ct_suite#Module:suite/0">suite/0</seemfa>,
+ <seemfa marker="ct_suite#Module:init_per_suite/1">
+ init_per_suite/1</seemfa>, or
+ <seemfa marker="ct_suite#Module:init_per_group/2">
+ init_per_group/2</seemfa>.</p>
<p>In this case, <c>CTH</c> can either be only the module name of the CTH
or a tuple with the module name and the initial arguments, and optionally
@@ -105,7 +105,7 @@
to be activated. This can cause problems if you want to override
CTHs in test specifications while still having them in the
suite information function. The
- <seealso marker="ct_hooks#Module:id-1">id/1</seealso>
+ <seemfa marker="ct_hooks#Module:id/1">id/1</seemfa>
callback exists to address this problem. By returning the same
<c>id</c> in both places, <c>Common Test</c> knows that this CTH
is already installed and does not try to install it again.</p>
@@ -118,7 +118,7 @@
This is not always desired, so <c>Common Test</c> allows
the user to specify a priority for each hook. The priority can either
be specified in the CTH function
- <seealso marker="ct_hooks#Module:init-2">init/2</seealso> or when
+ <seemfa marker="ct_hooks#Module:init/2">init/2</seemfa> or when
installing the hook. The priority specified at installation overrides the
priority returned by the CTH.</p>
</section>
@@ -130,9 +130,9 @@
<p>Once the CTH is installed into a certain test run it remains there until
its scope is expired. The scope of a CTH depends on when it is
installed, see the following table.
- Function <seealso marker="ct_hooks#Module:init-2">init/2</seealso> is
+ Function <seemfa marker="ct_hooks#Module:init/2">init/2</seemfa> is
called at the beginning of the scope and function
- <seealso marker="ct_hooks#Module:terminate-1">terminate/1</seealso>
+ <seemfa marker="ct_hooks#Module:terminate/1">terminate/1</seemfa>
is called when the scope ends.</p>
<table>
<row>
@@ -141,44 +141,44 @@
<cell><em>CTH scope ends after</em></cell>
</row>
<row>
- <cell><seealso marker="run_test_chapter#ct_run">ct_run</seealso></cell>
+ <cell><seeguide marker="run_test_chapter#ct_run">ct_run</seeguide></cell>
<cell>the first test suite is to be run</cell>
<cell>the last test suite has been run</cell>
</row>
<row>
- <cell><seealso marker="ct#run_test-1">ct:run_test</seealso></cell>
+ <cell><seemfa marker="ct#run_test/1">ct:run_test</seemfa></cell>
<cell>the first test suite is run</cell>
<cell>the last test suite has been run</cell>
</row>
<row>
- <cell><seealso marker="run_test_chapter#test_specifications">
- Test Specification</seealso></cell>
+ <cell><seeguide marker="run_test_chapter#test_specifications">
+ Test Specification</seeguide></cell>
<cell>the first test suite is run</cell>
<cell>the last test suite has been run</cell>
</row>
<row>
- <cell><seealso marker="common_test#Module:suite-0">suite/0
- </seealso></cell>
- <cell><seealso marker="ct_hooks#Module:pre_init_per_suite-3">
- pre_init_per_suite/3</seealso> is called</cell>
- <cell><seealso marker="ct_hooks#Module:post_end_per_suite-4">
- post_end_per_suite/4</seealso> has been called for that test suite</cell>
+ <cell><seemfa marker="ct_suite#Module:suite/0">suite/0
+ </seemfa></cell>
+ <cell><seemfa marker="ct_hooks#Module:pre_init_per_suite/3">
+ pre_init_per_suite/3</seemfa> is called</cell>
+ <cell><seemfa marker="ct_hooks#Module:post_end_per_suite/4">
+ post_end_per_suite/4</seemfa> has been called for that test suite</cell>
</row>
<row>
- <cell><seealso marker="common_test#Module:init_per_suite-1">
- init_per_suite/1</seealso></cell>
- <cell><seealso marker="ct_hooks#Module:post_init_per_suite-4">
- post_init_per_suite/4</seealso> is called</cell>
- <cell><seealso marker="ct_hooks#Module:post_end_per_suite-4">
- post_end_per_suite/4</seealso> has been called for that test suite</cell>
+ <cell><seemfa marker="ct_suite#Module:init_per_suite/1">
+ init_per_suite/1</seemfa></cell>
+ <cell><seemfa marker="ct_hooks#Module:post_init_per_suite/4">
+ post_init_per_suite/4</seemfa> is called</cell>
+ <cell><seemfa marker="ct_hooks#Module:post_end_per_suite/4">
+ post_end_per_suite/4</seemfa> has been called for that test suite</cell>
</row>
<row>
- <cell><seealso marker="common_test#Module:init_per_group-2">
- init_per_group/2</seealso></cell>
- <cell><seealso marker="ct_hooks#Module:post_init_per_group-5">
- post_init_per_group/5</seealso> is called</cell>
- <cell><seealso marker="ct_hooks#Module:post_end_per_group-5">
- post_end_per_group/5</seealso> has been called for that group</cell>
+ <cell><seemfa marker="ct_suite#Module:init_per_group/2">
+ init_per_group/2</seemfa></cell>
+ <cell><seemfa marker="ct_hooks#Module:post_init_per_group/5">
+ post_init_per_group/5</seemfa> is called</cell>
+ <cell><seemfa marker="ct_hooks#Module:post_end_per_group/5">
+ post_end_per_group/5</seemfa> has been called for that group</cell>
</row>
<tcaption>Scope of a CTH</tcaption>
</table>
@@ -197,12 +197,12 @@
<title>External Configuration Data and Logging</title>
<p>Configuration data values in the CTH can be read
by calling
- <seealso marker="ct#get_config-1"><c>ct:get_config/1,2,3</c></seealso>
+ <seemfa marker="ct#get_config/1"><c>ct:get_config/1,2,3</c></seemfa>
(as explained in section
- <seealso marker="config_file_chapter#require_config_data">Requiring and Reading Configuration Data</seealso>).
+ <seeguide marker="config_file_chapter#require_config_data">Requiring and Reading Configuration Data</seeguide>).
The configuration variables in question must, as always, first have been
required by a suite-, group-, or test case information function,
- or by function <seealso marker="ct#require-1"><c>ct:require/1/2</c></seealso>.
+ or by function <seemfa marker="ct#require/1"><c>ct:require/1/2</c></seemfa>.
The latter can also be used in CT hook functions.</p>
<p>The CT hook functions can call any logging function
in the <c>ct</c> interface to print information to the log files, or to
@@ -236,12 +236,12 @@
In a CTH, the behavior can be hooked in before the following functions:</p>
<list type="bulleted">
- <item><seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso></item>
- <item><seealso marker="common_test#Module:init_per_group-2"><c>init_per_group</c></seealso></item>
- <item><seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase</c></seealso></item>
- <item><seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase</c></seealso></item>
- <item><seealso marker="common_test#Module:end_per_group-2"><c>end_per_group</c></seealso></item>
- <item><seealso marker="common_test#Module:end_per_suite-1"><c>end_per_suite</c></seealso></item>
+ <item><seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa></item>
</list>
<p>
@@ -282,12 +282,12 @@
<title>Post Hooks</title>
<p>In a CTH, behavior can be hooked in after the following functions:</p>
<list type="bulleted">
- <item><seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso></item>
- <item><seealso marker="common_test#Module:init_per_group-2"><c>init_per_group</c></seealso></item>
- <item><seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase</c></seealso></item>
- <item><seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase</c></seealso></item>
- <item><seealso marker="common_test#Module:end_per_group-2"><c>end_per_group</c></seealso></item>
- <item><seealso marker="common_test#Module:end_per_suite-1"><c>end_per_suite</c></seealso></item>
+ <item><seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group</c></seemfa></item>
+ <item><seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa></item>
</list>
<p>
@@ -333,8 +333,8 @@
<title>Skip and Fail Hooks</title>
<p>
After any post hook has been executed for all installed CTHs,
- <seealso marker="ct_hooks#Module:on_tc_fail-4">on_tc_fail</seealso>
- or <seealso marker="ct_hooks#Module:on_tc_skip-4">on_tc_skip</seealso>
+ <seemfa marker="ct_hooks#Module:on_tc_fail/4">on_tc_fail</seemfa>
+ or <seemfa marker="ct_hooks#Module:on_tc_skip/4">on_tc_skip</seemfa>
is called if the testcase failed or was skipped, respectively.
You cannot affect the outcome of the tests any further at this point.
</p>
@@ -354,7 +354,7 @@
and terminate.
Any system error- or progress reports generated during the init- or
termination stage are saved in the
- <seealso marker="run_test_chapter#pre_post_test_io_log">Pre- and Post Test I/O Log</seealso>.
+ <seeguide marker="run_test_chapter#pre_post_test_io_log">Pre- and Post Test I/O Log</seeguide>.
(This is also true for any printouts made
with <c>ct:log/2</c> and <c>ct:pal/2</c>).</p>
@@ -375,7 +375,7 @@
<marker id="example"/>
<title>Example CTH</title>
<p>The following CTH logs information about a test run into a format
- parseable by <seealso marker="kernel:file#consult-1">file:consult/1</seealso>
+ parseable by <seemfa marker="kernel:file#consult/1">file:consult/1</seemfa>
(in Kernel):
</p>
<code>
@@ -506,9 +506,13 @@
If an event cannot be associated with a test case, it is printed in
the <c>Common Test</c> framework log.
This happens for test cases running in parallel and events occuring
- in-between test cases. You can configure the level of
- <seealso marker="sasl:sasl_app">SASL</seealso> reports
- using the normal SASL mechanisms.</p>
+ in-between test cases.</p>
+ <p>The log events are handled using a <seeerl marker="kernel:logger">Logger</seeerl>
+ handler called cth_log_redirect. The formatting and level is copied from
+ the current <c>default</c> handler when the cth is started. If you want to
+ use another level either change the <c>default</c> handler level before
+ starting common_test, or use the <seemfa marker="kernel:logger#set_handler_config/3">
+ <c>logger:set_handler_config/3</c></seemfa> API.</p>
</item>
<tag><c>cth_surefire</c></tag>
<item>
@@ -541,7 +545,3 @@ x86_64-unknown-linux-gnu.my_test.logs/run.2012-12-12_11.19.39/suite.log.html"</c
</section>
</chapter>
-
-
-
-
diff --git a/lib/common_test/doc/src/ct_master.xml b/lib/common_test/doc/src/ct_master.xml
index e1fa2958a3..ca08fb61ea 100644
--- a/lib/common_test/doc/src/ct_master.xml
+++ b/lib/common_test/doc/src/ct_master.xml
@@ -118,8 +118,8 @@
<v>TestSpecs = string() | [SeparateOrMerged]</v>
</type>
<desc><marker id="run-1"/>
- <p>Equivalent to <seealso marker="#run-4"><c>ct_master:run(TestSpecs,
- false, [], [])</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#run/4"><c>ct_master:run(TestSpecs,
+ false, [], [])</c></seemfa>.</p>
</desc>
</func>
@@ -134,8 +134,8 @@
<v>ExclNodes = [atom()]</v>
</type>
<desc><marker id="run-3"/>
- <p>Equivalent to <seealso marker="#run-4"><c>ct_master:run(TestSpecs,
- false, InclNodes, ExclNodes)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#run/4"><c>ct_master:run(TestSpecs,
+ false, InclNodes, ExclNodes)</c></seemfa>.</p>
</desc>
</func>
@@ -171,8 +171,8 @@
</type>
<desc><marker id="run_on_node-2"/>
<p>Equivalent to
- <seealso marker="#run_on_node-3"><c>ct_master:run_on_node(TestSpecs,
- false, Node)</c></seealso>.</p>
+ <seemfa marker="#run_on_node/3"><c>ct_master:run_on_node(TestSpecs,
+ false, Node)</c></seemfa>.</p>
</desc>
</func>
@@ -210,7 +210,7 @@
</type>
<desc><marker id="run_test-2"/>
<p>Tests are spawned on <c>Node</c> using
- <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso></p>
+ <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa></p>
</desc>
</func>
</funcs>
diff --git a/lib/common_test/doc/src/ct_master_chapter.xml b/lib/common_test/doc/src/ct_master_chapter.xml
index 7b5aae7ad8..990d38e55a 100644
--- a/lib/common_test/doc/src/ct_master_chapter.xml
+++ b/lib/common_test/doc/src/ct_master_chapter.xml
@@ -52,7 +52,7 @@
log files written by each independent <c>Common Test</c> server.</p>
<p>The <c>Common Test</c> Master API is exported by module
- <seealso marker="ct_master"><c>ct_master</c></seealso>.</p>
+ <seeerl marker="ct_master"><c>ct_master</c></seeerl>.</p>
</section>
<section>
<title>Use</title>
@@ -62,8 +62,8 @@
able to start test sessions on them.</p>
<p>Tests are started by calling
- <seealso marker="ct_master#run-1"><c>ct_master:run(TestSpecs)</c></seealso> or
- <seealso marker="ct_master#run-3"><c>ct_master:run(TestSpecs, InclNodes, ExclNodes)</c></seealso></p>
+ <seemfa marker="ct_master#run/1"><c>ct_master:run(TestSpecs)</c></seemfa> or
+ <seemfa marker="ct_master#run/3"><c>ct_master:run(TestSpecs, InclNodes, ExclNodes)</c></seemfa></p>
<p><c>TestSpecs</c> is either the name of a test specification file (string) or a list
of test specifications. If it is a list, the specifications are handled (and
@@ -101,21 +101,21 @@
does not attempt to re-establish contact with the failing node.</p>
<p>At any time, to get the current status of the test nodes, call function
- <seealso marker="ct_master#progress-0"><c>ct_master:progress()</c></seealso>.</p>
+ <seemfa marker="ct_master#progress/0"><c>ct_master:progress()</c></seemfa>.</p>
<p>To stop one or more tests, use function
- <seealso marker="ct_master#abort-0"><c>ct_master:abort()</c></seealso> (to stop all) or
- <seealso marker="ct_master#abort-1"><c>ct_master:abort(Nodes)</c></seealso>.</p>
+ <seemfa marker="ct_master#abort/0"><c>ct_master:abort()</c></seemfa> (to stop all) or
+ <seemfa marker="ct_master#abort/1"><c>ct_master:abort(Nodes)</c></seemfa>.</p>
<p>For details about the <c>Common Test</c> Master API, see module
- <seealso marker="ct_master"><c>ct_master</c></seealso>.</p>
+ <seeerl marker="ct_master"><c>ct_master</c></seeerl>.</p>
</section>
<section>
<marker id="test_specifications"></marker>
<title>Test Specifications</title>
<p>The test specifications used as input to <c>Common Test</c> Master are fully compatible with the
specifications used as input to the regular <c>Common Test</c> server. The syntax is described in section
- <seealso marker="run_test_chapter#test_specifications">Test Specifications</seealso>
+ <seeguide marker="run_test_chapter#test_specifications">Test Specifications</seeguide>
in section Running Tests and Analyzing Results.</p>
<p>All test specification terms can have a <c>NodeRefs</c> element. This element
@@ -140,7 +140,7 @@
the log directory or install an event handler).</p>
<p>Consider the example in section
- <seealso marker="run_test_chapter#test_specifications">Test Specifications</seealso>
+ <seeguide marker="run_test_chapter#test_specifications">Test Specifications</seeguide>
in section Running Tests and Analysing Results,
now extended with node information and intended to be executed by
<c>Common Test</c> Master:</p>
@@ -192,7 +192,7 @@
<p>A nice feature is that a test specification that includes node
information can still be used as input to the regular <c>Common Test</c> server
(as described in section
- <seealso marker="run_test_chapter#test_specifications">Test Specifications</seealso>).
+ <seeguide marker="run_test_chapter#test_specifications">Test Specifications</seeguide>).
The result is that any test specified to run on a node with the same
name as the <c>Common Test</c> node in question (typically <c>ct@somehost</c> if started
with the <c>ct_run</c> program), is performed. Tests without explicit
@@ -222,7 +222,7 @@
host <c>host2</c>. Also, function <c>module:function/0</c> is evaluated on
<c>node1@host3</c>, and the result of this call is printed to the log.</p>
- <p>The default callback module <seealso marker="ct_slave">ct_slave</seealso>,
+ <p>The default callback module <seeerl marker="ct_slave">ct_slave</seeerl>,
has the following features:
</p>
<list type="bulleted">
diff --git a/lib/common_test/doc/src/ct_netconfc.xml b/lib/common_test/doc/src/ct_netconfc.xml
index f4d98b611b..9b4d55b810 100644
--- a/lib/common_test/doc/src/ct_netconfc.xml
+++ b/lib/common_test/doc/src/ct_netconfc.xml
@@ -45,21 +45,21 @@
<marker id="Connecting"/>
<p><em>Connecting to a NETCONF server</em></p>
- <p>Call <seealso marker="#connect-1"><c>connect/1,2</c></seealso>
+ <p>Call <seemfa marker="#connect/1"><c>connect/1,2</c></seemfa>
to establish a connection to a server, then pass the returned
- handle to <seealso marker="#session-1"><c>session/1-3</c></seealso> to
+ handle to <seemfa marker="#session/1"><c>session/1-3</c></seemfa> to
establish a NETCONF session on a new SSH channel.
Each call to
- <seealso marker="#session-1"><c>session/1-3</c></seealso> establishes a
+ <seemfa marker="#session/1"><c>session/1-3</c></seemfa> establishes a
new session on the same connection, and results in a hello message
to the server.</p>
<p>Alternately,
- <seealso marker="#open-1"><c>open/1,2</c></seealso> can be used to
+ <seemfa marker="#open/1"><c>open/1,2</c></seemfa> can be used to
establish a single session on a dedicated connection.
(Or, equivalently,
- <seealso marker="#only_open-1"><c>only_open/1,2</c></seealso>
- followed by <seealso marker="#hello-1"><c>hello/1-3</c></seealso>.)</p>
+ <seemfa marker="#only_open/1"><c>only_open/1,2</c></seemfa>
+ followed by <seemfa marker="#hello/1"><c>hello/1-3</c></seemfa>.)</p>
<p>Connect/session options can be specified in a configuration
file with entries like the following.</p>
@@ -67,9 +67,9 @@
<pre>
{server_id(), [option()]}.</pre>
- <p>The <seealso marker="#type-server_id"><c>server_id()</c></seealso>
+ <p>The <seetype marker="#server_id"><c>server_id()</c></seetype>
or an associated
- <seealso marker="ct#type-target_name"><c>ct:target_name()</c></seealso>
+ <seetype marker="ct#target_name"><c>ct:target_name()</c></seetype>
can then be passed to the aforementioned functions to use the
referenced configuration.</p>
@@ -80,12 +80,12 @@
procedure calls (RPCs) from client to server and a corresponding
reply from server to client.
RPCs are sent using like-named functions (eg.
- <seealso marker="#edit_config-3"><c>edit_config/3-5</c></seealso>
+ <seemfa marker="#edit_config/3"><c>edit_config/3-5</c></seemfa>
to send an edit-config RPC), with the server reply
as return value.
There are functions for each RPC defined in RFC 6241 and
the create-subscription RPC from RFC 5277, all of which are
- wrappers on <seealso marker="#send_rpc-2"><c>send_rpc/2,3</c></seealso>,
+ wrappers on <seemfa marker="#send_rpc/2"><c>send_rpc/2,3</c></seemfa>,
that can be used to send an arbitrary RPC
not defined in RFC 6241 or RFC 5277.</p>
@@ -106,7 +106,7 @@
<pre>
suite() -&gt;
- [{ct_hooks, [{cth_conn_log, [{<seealso marker="ct#type-conn_log_mod"><c>ct:conn_log_mod()</c></seealso>, <seealso marker="ct#type-conn_log_options"><c>ct:conn_log_options()</c></seealso>}]}]}].</pre>
+ [{ct_hooks, [{cth_conn_log, [{<seetype marker="ct#conn_log_mod"><c>ct:conn_log_mod()</c></seetype>, <seetype marker="ct#conn_log_options"><c>ct:conn_log_options()</c></seetype>}]}]}].</pre>
<p><c>conn_log_mod()</c> is the name of the <c>Common Test</c> module
implementing the connection protocol, for example, <c>ct_netconfc</c>.</p>
@@ -137,7 +137,7 @@
To do this, use hook option <c>hosts</c> and list the names of the
servers/connections to be used in the suite. The connections
must be named for this to work, that is, they must be opened with
- <seealso marker="#open-2"><c>open/2</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
<p>Option <c>hosts</c> has no effect if <c>log_type</c> is set to
<c>html</c> or <c>silent</c>.</p>
@@ -146,13 +146,13 @@
configuration variable <c>ct_conn_log</c>:</p>
<pre>
- {ct_conn_log,[{<seealso marker="ct#type-conn_log_mod"><c>ct:conn_log_mod()</c></seealso>, <seealso marker="ct#type-conn_log_options"><c>ct:conn_log_options()</c></seealso>}]}.</pre>
+ {ct_conn_log,[{<seetype marker="ct#conn_log_mod"><c>ct:conn_log_mod()</c></seetype>, <seetype marker="ct#conn_log_options"><c>ct:conn_log_options()</c></seetype>}]}.</pre>
<p>For example:</p>
<pre>
{ct_conn_log,[{ct_netconfc,[{log_type,pretty},
- {hosts,[<seealso marker="ct#type-key_or_name"><c>ct:key_or_name()</c></seealso>]}]}]}</pre>
+ {hosts,[<seetype marker="ct#key_or_name"><c>ct:key_or_name()</c></seetype>]}]}]}</pre>
<note>
<p>Hook options specified in a configuration file overwrite the
@@ -215,11 +215,11 @@
<desc>
<p>Handle to a connection to a NETCONF server as
returned by
- <seealso marker="#connect-1"><c>connect/1,2</c></seealso>,
+ <seemfa marker="#connect/1"><c>connect/1,2</c></seemfa>,
or to a session as returned by
- <seealso marker="#session-1"><c>session/1-3</c></seealso>,
- <seealso marker="#open-1"><c>open/1,2</c></seealso>,
- or <seealso marker="#only_open-1"><c>only_open/1,2</c></seealso>.</p>
+ <seemfa marker="#session/1"><c>session/1-3</c></seemfa>,
+ <seemfa marker="#open/1"><c>open/1,2</c></seemfa>,
+ or <seemfa marker="#only_open/1"><c>only_open/1,2</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -244,7 +244,7 @@
<name name="notification"/>
<desc>
<p>Event notification messages sent as a result of calls to
- <seealso marker="#create_subscription-2"><c>create_subscription/2,3</c></seealso>.</p>
+ <seemfa marker="#create_subscription/2"><c>create_subscription/2,3</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -252,8 +252,8 @@
<desc>
<p>Options <c>host</c> and <c>port</c> specify the
server endpoint to which to connect, and are passed directly
- to <seealso
- marker="ssh:ssh#connect-3"><c>ssh:connect/4</c></seealso>,
+ to <seemfa
+ marker="ssh:ssh#connect/3"><c>ssh:connect/4</c></seemfa>,
as are arbitrary ssh options. Common options are <c>user</c>,
<c>password</c> and <c>user_dir</c>.</p>
@@ -299,7 +299,7 @@
<name name="streams"/>
<desc>
<p>Stream information as returned by
- <seealso marker="#get_event_streams-1"><c>get_event_streams/1-3</c></seealso>.
+ <seemfa marker="#get_event_streams/1"><c>get_event_streams/1-3</c></seemfa>.
See RFC 5277, "XML Schema for Event Notifications", for detail
on the format of the string values.</p>
</desc>
@@ -323,7 +323,7 @@
<name name="simple_xml"/>
<desc>
<p>Representation of XML, as described in application
- <seealso marker="xmerl:index"><c>xmerl</c></seealso>.</p>
+ <seeapp marker="xmerl:index"><c>xmerl</c></seeapp>.</p>
</desc>
</datatype>
<datatype>
@@ -377,14 +377,14 @@
<p>Opens an SSH connection to a NETCONF server.</p>
<p>If the server options are specified in a configuration file, use
- <seealso marker="#connect-2"><c>connect/2</c></seealso>
+ <seemfa marker="#connect/2"><c>connect/2</c></seemfa>
instead.</p>
- <p>The opaque <seealso marker="#type-handle"><c>handle()</c></seealso>
+ <p>The opaque <seetype marker="#handle"><c>handle()</c></seetype>
reference returned from this
function is required as connection identifier when opening
sessions over this connection, see
- <seealso marker="#session-1"><c>session/1-3</c></seealso>.</p>
+ <seemfa marker="#session/1"><c>session/1-3</c></seemfa>.</p>
</desc>
</func>
@@ -404,20 +404,20 @@
the configuration file takes precedence.</p>
<p>If the server is not specified in a configuration file, use
- <seealso marker="#connect-1"><c>connect/1</c></seealso>
+ <seemfa marker="#connect/1"><c>connect/1</c></seemfa>
instead.</p>
- <p>The opaque <seealso marker="#type-handle"><c>handle()</c></seealso>
+ <p>The opaque <seetype marker="#handle"><c>handle()</c></seetype>
reference returned from this
function can be used as connection identifier when opening
sessions over this connection, see
- <seealso marker="#session-1"><c>session/1-3</c></seealso>.
+ <seemfa marker="#session/1"><c>session/1-3</c></seemfa>.
However, if <c><anno>KeyOrName</anno></c> is a
<c>target_name()</c>, that is, if the server is named through a
- call to <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>
+ call to <seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>
or a <c>require</c> statement in the test suite, then this name can
be used instead of
- <seealso marker="#type-handle"><c>handle()</c></seealso>.</p>
+ <seetype marker="#handle"><c>handle()</c></seetype>.</p>
</desc>
</func>
@@ -442,7 +442,7 @@
<p>Creates a subscription for event notifications by sending
an RFC 5277 create-subscription RPC to the server.
The calling process receives events as messages of
- type <seealso marker="#type-notification"><c>notification()</c></seealso>.</p>
+ type <seetype marker="#notification"><c>notification()</c></seetype>.</p>
<p>From RFC 5722, 2.1 Subscribing to Receive Event Notifications:</p>
@@ -513,7 +513,7 @@
<p>If there are open NETCONF sessions on the connection, these
will be brutally aborted. To avoid this, close each session
- with <seealso marker="#close_session-1"><c>close_session/1,2</c></seealso></p>
+ with <seemfa marker="#close_session/1"><c>close_session/1,2</c></seemfa></p>
</desc>
</func>
@@ -618,7 +618,7 @@
&lt;/netconf&gt;</pre>
<p>If more complex filtering is needed, use
- <seealso marker="#get-2"><c>ct_netconfc:get/2,3</c></seealso> and
+ <seemfa marker="#get/2"><c>ct_netconfc:get/2,3</c></seemfa> and
specify the exact filter according to "XML Schema for Event
Notifications" in RFC 5277.</p>
</desc>
@@ -645,7 +645,7 @@
specified timeout.</p>
<p>Note that capabilities for an outgoing hello can be passed
- directly to <seealso marker="#open-2"><c>open/2</c></seealso>.</p>
+ directly to <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
</desc>
</func>
@@ -685,7 +685,7 @@
Locks are intended to be short-lived.</p>
<p>Operation
- <seealso marker="#kill_session-2"><c>kill_session/2,3</c></seealso>
+ <seemfa marker="#kill_session/2"><c>kill_session/2,3</c></seemfa>
can be used to force the release of a lock owned by another NETCONF
session. How this is achieved by the server side is
implementation-specific.</p>
@@ -698,7 +698,7 @@
<desc>
<p>Opens a NETCONF session, but does not send <c>hello</c>.</p>
- <p>As <seealso marker="#open-1"><c>open/1</c></seealso>, but
+ <p>As <seemfa marker="#open/1"><c>open/1</c></seemfa>, but
does not send a <c>hello</c> message.</p>
</desc>
</func>
@@ -709,7 +709,7 @@
<desc>
<p>Opens a named NETCONF session, but does not send <c>hello</c>.</p>
- <p>As <seealso marker="#open-2"><c>open/2</c></seealso>, but
+ <p>As <seemfa marker="#open/2"><c>open/2</c></seemfa>, but
does not send a <c>hello</c> message.</p>
</desc>
</func>
@@ -722,11 +722,11 @@
<p>If the server options are specified in a configuration file,
or if a named client is needed for logging purposes (see section
- <seealso marker="#Logging">Logging</seealso> in this module), use
- <seealso marker="#open-2"><c>open/2</c></seealso>
+ <seeerl marker="#Logging">Logging</seeerl> in this module), use
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>
instead.</p>
- <p>The opaque <seealso marker="#type-handle"><c>handle()</c></seealso>
+ <p>The opaque <seetype marker="#handle"><c>handle()</c></seetype>
reference returned from this
function is required as client identifier when calling any other
function in this module.</p>
@@ -751,21 +751,21 @@
the configuration file take precedence.</p>
<p>If the server is not specified in a configuration file, use
- <seealso marker="#open-1"><c>open/1</c></seealso>
+ <seemfa marker="#open/1"><c>open/1</c></seemfa>
instead.</p>
- <p>The opaque <seealso marker="#type-handle"><c>handle()</c></seealso>
+ <p>The opaque <seetype marker="#handle"><c>handle()</c></seetype>
reference returned from this
function can be used as client identifier when calling any other
function in this module. However, if <c><anno>KeyOrName</anno></c> is a
<c>target_name()</c>, that is, if the server is named through a
- call to <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>
+ call to <seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>
or a <c>require</c> statement in the test suite, then this name can
be used instead of
- <seealso marker="#type-handle"><c>handle()</c></seealso>.</p>
+ <seetype marker="#handle"><c>handle()</c></seetype>.</p>
<p>See also
- <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>.</p>
+ <seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>.</p>
</desc>
</func>
@@ -811,16 +811,16 @@
<p>Opens a NETCONF session as a channel on the given SSH
connection, and exchanges hello messages with the server.</p>
- <p>The opaque <seealso marker="#type-handle"><c>handle()</c></seealso>
+ <p>The opaque <seetype marker="#handle"><c>handle()</c></seetype>
reference returned from this
function can be used as client identifier when calling any
other function in this module. However, if <c><anno>KeyOrName</anno></c>
is used and it is a <c>target_name()</c>, that is, if the
server is named through a call
- to <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>
+ to <seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>
or a <c>require</c> statement in the test suite, then this
name can be used instead of
- <seealso marker="#type-handle"><c>handle()</c></seealso>.</p>
+ <seetype marker="#handle"><c>handle()</c></seetype>.</p>
</desc>
</func>
@@ -833,7 +833,7 @@
<p>Unlocks the configuration target.</p>
<p>If the client earlier has acquired a lock through
- <seealso marker="#lock-2"><c>lock/2,3</c></seealso>, this
+ <seemfa marker="#lock/2"><c>lock/2,3</c></seemfa>, this
operation releases the associated lock. To access another target
than <c>running</c>, the server must support <c>:candidate</c>
and/or <c>:startup</c>.</p>
diff --git a/lib/common_test/doc/src/ct_property_test.xml b/lib/common_test/doc/src/ct_property_test.xml
index 841d1d9ecb..4c5eede758 100644
--- a/lib/common_test/doc/src/ct_property_test.xml
+++ b/lib/common_test/doc/src/ct_property_test.xml
@@ -80,7 +80,7 @@
).</code>
<p>and the the property test module (in this example <c>ftp_simple_client_server.erl</c>)
as almost a usual property testing module
- (More examples are in <seealso marker="ct_property_test_chapter">the User's Guide</seealso>):</p>
+ (More examples are in <seeguide marker="ct_property_test_chapter">the User's Guide</seeguide>):</p>
<code>
-module(ftp_simple_client_server).
-export([prop_ftp/0...]).
@@ -165,7 +165,7 @@ prop_ftp() -&gt;
<name since="">present_result(Module, Cmds, Triple, Config) -> Result</name>
<fsummary>Presents the result of statem property testing</fsummary>
<desc>
- <p>Same as <seealso marker="#present_result/5"><c>present_result(Module, Cmds, Triple, Config, [])</c></seealso>
+ <p>Same as <seemfa marker="#present_result/5"><c>present_result(Module, Cmds, Triple, Config, [])</c></seemfa>
</p>
</desc>
</func>
@@ -186,15 +186,15 @@ prop_ftp() -&gt;
<d>the output from for example proper:run_commands/2 or proper:run_parallel_commands/2</d>
<v>Config =</v>
- <d>the Common Test <seealso marker="common_test#Module:Testcase/1">Config</seealso> in test cases.</d>
+ <d>the Common Test <seemfa marker="ct_suite#Module:Testcase/1">Config</seemfa> in test cases.</d>
<v>Options = [present_option()]</v>
<v>present_option() = {print_fun, fun(Format,Args)}</v>
<v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| {spec, StatisticsSpec}</v>
<d>The <c>print_fun</c> defines which function to do the actual printout. The default is
- <seealso marker="ct#log/2">ct:log/2</seealso>.
+ <seemfa marker="ct#log/2">ct:log/2</seemfa>.
The <c>spec</c> defines what statistics are to be printed<!--, see the
- <seealso marker="ct_property_test_chapter#spec_present_result">User's Guide</seealso>-->
+ <seeguide marker="ct_property_test_chapter#spec_present_result">User's Guide</seeguide>-->
</d>
<v>Result = boolean()</v>
@@ -205,14 +205,14 @@ prop_ftp() -&gt;
PropEr, QuickCheck or other similar property testing tool.
</p>
<p>It is assumed to be called inside the property called by
- <seealso marker="#quickcheck/2">quickcheck/2</seealso>:</p>
+ <seemfa marker="#quickcheck/2">quickcheck/2</seemfa>:</p>
<code>
...
RunResult = run_parallel_commands(?MODULE, Cmds),
ct_property_test:present_result(?MODULE, Cmds, RunResult, Config)
...
</code>
- <p>See the <seealso marker="ct_property_test_chapter#stateful1">User's Guide</seealso> for
+ <p>See the <seeguide marker="ct_property_test_chapter#stateful1">User's Guide</seeguide> for
an example of the usage and of the default printout.
</p>
<p>The <c>StatisticsSpec</c> is a list of the tuples:</p>
diff --git a/lib/common_test/doc/src/ct_property_test_chapter.xml b/lib/common_test/doc/src/ct_property_test_chapter.xml
index 456f57b626..eb833ceefb 100644
--- a/lib/common_test/doc/src/ct_property_test_chapter.xml
+++ b/lib/common_test/doc/src/ct_property_test_chapter.xml
@@ -52,7 +52,7 @@
<section>
<marker id="supported"></marker>
<title>What Is Supported?</title>
- <p>The <seealso marker="ct_property_test#">ct_property_test</seealso> module
+ <p>The <seeerl marker="ct_property_test#">ct_property_test</seeerl> module
does the following:
</p>
<list type="bulleted">
@@ -173,7 +173,7 @@ prop_parallel(Config) ->
end)).
</code>
<p>The
- <seealso marker="ct_property_test#present_result/4">ct_property_test:present_result/4</seealso>
+ <seemfa marker="ct_property_test#present_result/4">ct_property_test:present_result/4</seemfa>
is a help function for printing some statistics in the CommonTest log file.</p>
<p>Our example test could for example be a simple test of an ftp server, where we perform get, put
and delete requests, some of them in parallel. Per default, the result has three sections:
@@ -243,7 +243,7 @@ Range : Number in range
<marker id="spec_present_result"></marker>
<title>The spec for present_result/5</title>
<p>To be written...
- <seealso marker="ct_property_test#present_result/5">present_result/5</seealso>
+ <seemfa marker="ct_property_test#present_result/5">present_result/5</seemfa>
</p>
</section-->
</chapter>
diff --git a/lib/common_test/doc/src/ct_rpc.xml b/lib/common_test/doc/src/ct_rpc.xml
index 69b735217c..28b8011861 100644
--- a/lib/common_test/doc/src/ct_rpc.xml
+++ b/lib/common_test/doc/src/ct_rpc.xml
@@ -73,7 +73,7 @@
</type>
<desc><marker id="app_node-3"/>
<p>Same as
- <seealso marker="#app_node-2"><c>ct_rpc:app_node/2</c></seealso>,
+ <seemfa marker="#app_node/2"><c>ct_rpc:app_node/2</c></seemfa>,
except that argument <c>FailOnBadRPC</c> determines if the search
for a candidate node is to stop if <c>badrpc</c> is received at
some point.</p>
@@ -94,7 +94,7 @@
</type>
<desc><marker id="app_node-4"/>
<p>Same as
- <seealso marker="#app_node-2"><c>ct_rpc:app_node/2</c></seealso>,
+ <seemfa marker="#app_node/2"><c>ct_rpc:app_node/2</c></seemfa>,
except that argument <c>FailOnBadRPC</c> determines if the search
for a candidate node is to stop if <c>badrpc</c> is received at
some point.</p>
diff --git a/lib/common_test/doc/src/ct_run.xml b/lib/common_test/doc/src/ct_run_cmd.xml
index 5b962ed5c7..3c27ce1779 100644
--- a/lib/common_test/doc/src/ct_run.xml
+++ b/lib/common_test/doc/src/ct_run_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -39,17 +39,17 @@
<description>
<p>The <c>ct_run</c> program is automatically installed with Erlang/OTP
and the <c>Common Test</c> application (for more information, see
- section <seealso marker="install_chapter">Installation</seealso>
+ section <seeguide marker="install_chapter">Installation</seeguide>
in the User's Guide). The program accepts different start flags.
Some flags trigger <c>ct_run</c> to start <c>Common Test</c> and
pass on data to it. Some flags start an Erlang node prepared for
running <c>Common Test</c> in a particular mode.</p>
<p>The interface function
- <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>,
+ <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>,
corresponding to the <c>ct_run</c> program, is used for starting
<c>Common Test</c> from the Erlang shell (or an Erlang program).
- For details, see the <seealso marker="ct"><c>ct</c></seealso>
+ For details, see the <seeerl marker="ct"><c>ct</c></seeerl>
manual page.</p>
<p><c>ct_run</c> also accepts Erlang emulator flags. These are used
@@ -221,9 +221,8 @@
<section>
<title>See Also</title>
<p>For information about the start flags, see section
- <seealso marker="run_test_chapter">Running Tests and Analyzing
- Results</seealso> in the User's Guide.</p>
+ <seeguide marker="run_test_chapter">Running Tests and Analyzing
+ Results</seeguide> in the User's Guide.</p>
</section>
</comref>
-
diff --git a/lib/common_test/doc/src/ct_slave.xml b/lib/common_test/doc/src/ct_slave.xml
index d84d17d0b1..55f7573c63 100644
--- a/lib/common_test/doc/src/ct_slave.xml
+++ b/lib/common_test/doc/src/ct_slave.xml
@@ -63,7 +63,7 @@
<p>Starts an Erlang node with name <c>Node</c> on the local host.</p>
<p>See also
- <seealso marker="#start-3"><c>ct_slave:start/3</c></seealso>.</p>
+ <seemfa marker="#start/3"><c>ct_slave:start/3</c></seemfa>.</p>
</desc>
</func>
@@ -85,7 +85,7 @@
atom-valued and <c>start(Node, Opts)</c> when it is list-valued.</p>
<p>See also
- <seealso marker="#start-3"><c>ct_slave:start/3</c></seealso>.</p>
+ <seemfa marker="#start/3"><c>ct_slave:start/3</c></seemfa>.</p>
</desc>
</func>
@@ -176,7 +176,7 @@
<item><p><c>{error, started_not_connected, NodeName}</c> if the
node is started, but not connected to the master node.</p></item>
<item><p><c>{error, not_alive, NodeName}</c> if the node on which
- <seealso marker="#start-3"><c>ct_slave:start/3</c></seealso> is
+ <seemfa marker="#start/3"><c>ct_slave:start/3</c></seemfa> is
called, is not alive. Notice that <c>NodeName</c> is the name of
the current node in this case.</p></item>
</list>
diff --git a/lib/common_test/doc/src/ct_snmp.xml b/lib/common_test/doc/src/ct_snmp.xml
index bedf8f3c8a..abbda41154 100644
--- a/lib/common_test/doc/src/ct_snmp.xml
+++ b/lib/common_test/doc/src/ct_snmp.xml
@@ -148,7 +148,7 @@
<p>SNMP traps, inform, and report messages are handled by the user
callback module. For details, see the
- <seealso marker="snmp:index"><c>SNMP</c></seealso> application.</p>
+ <seeapp marker="snmp:index"><c>SNMP</c></seeapp> application.</p>
<p>It is recommended to use the <c>.hrl</c> files created by the
Erlang/OTP MIB compiler to define the Object Identifiers (OIDs).
@@ -160,7 +160,7 @@
<p>Furthermore, values can be set for <c>SNMP</c> application configuration
parameters, <c>config</c>, <c>server</c>, <c>net_if</c>, and so on (for
- a list of valid parameters and types, see the <seealso marker="snmp:users_guide"><c>User's Guide for the SNMP application</c></seealso>). This is
+ a list of valid parameters and types, see the <seeguide marker="snmp:index"><c>User's Guide for the SNMP application</c></seeguide>). This is
done by defining a configuration data variable on the following form:</p>
<pre>
@@ -170,7 +170,7 @@
<p>A name for the data must be allocated in the suite using
<c>require</c> (see the example above). Pass this name as argument
<c>SnmpAppConfName</c> to
- <seealso marker="#start-3"><c>ct_snmp:start/3</c></seealso>.
+ <seemfa marker="#start/3"><c>ct_snmp:start/3</c></seemfa>.
<c>ct_snmp</c> specifies default values for some <c>SNMP</c> application
configuration parameters (such as <c>{verbosity,trace}</c> for parameter
<c>config</c>). This set of defaults is merged with the parameters
@@ -179,64 +179,39 @@
</description>
- <section>
- <title>Data Types</title>
- <marker id="types"/>
- <taglist>
- <tag><c>agent_config() = {Item, Value}</c></tag>
- <item><marker id="type-agent_config"/> </item>
- <tag><c>agent_ip() = ip()</c></tag>
- <item><marker id="type-agent_ip"/> </item>
- <tag><c>agent_name() = atom()</c></tag>
- <item><marker id="type-agent_name"/> </item>
- <tag><c>agent_port() = integer()</c></tag>
- <item><marker id="type-agent_port"/> </item>
- <tag><c>call_back_module() = atom()</c></tag>
- <item><marker id="type-call_back_module"/> </item>
- <tag><c>error_index() = integer()</c></tag>
- <item><marker id="type-error_index"/> </item>
- <tag><c>error_status() = noError | atom()</c></tag>
- <item><marker id="type-error_status"/> </item>
- <tag><c>ip() = string() | {integer(), integer(), integer(), integer()}</c></tag>
- <item><marker id="type-ip"/> </item>
- <tag><c>manager_ip() = ip()</c></tag>
- <item><marker id="type-manager_ip"/> </item>
- <tag><c>oid() = [byte()]</c></tag>
- <item><marker id="type-oid"/> </item>
- <tag><c>oids() = [oid()]</c></tag>
- <item><marker id="type-oids"/> </item>
- <tag><c>rel_path() = string()</c></tag>
- <item><marker id="type-rel_path"/> </item>
- <tag><c>sec_type() = none | minimum | semi</c></tag>
- <item><marker id="type-sec_type"/> </item>
- <tag><c>snmp_app_agent_params() = term()</c></tag>
- <item><marker id="type-snmp_app_agent_params"/> </item>
- <tag><c>snmp_app_manager_params() = term()</c></tag>
- <item><marker id="type-snmp_app_manager_params"/> </item>
- <tag><c>snmpreply() = {error_status(), error_index(), varbinds()}</c></tag>
- <item><marker id="type-snmpreply"/> </item>
- <tag><c>user_data() = term()</c></tag>
- <item><marker id="type-user_data"/> </item>
- <tag><c>user_name() = atom()</c></tag>
- <item><marker id="type-user_name"/> </item>
- <tag><c>usm_config() = {Item, Value}</c></tag>
- <item><marker id="type-usm_config"/> </item>
- <tag><c>usm_user_name() = string()</c></tag>
- <item><marker id="type-usm_user_name"/> </item>
- <tag><c>value_type() = o('OBJECT IDENTIFIER') | i('INTEGER') | u('Unsigned32') | g('Unsigned32') | s('OCTET STRING')</c></tag>
- <item><marker id="type-value_type"/> </item>
- <tag><c>var_and_val() = {oid(), value_type(), value()}</c></tag>
- <item><marker id="type-var_and_val"/> </item>
- <tag><c>varbind() = term()</c></tag>
- <item><marker id="type-varbind"/> </item>
- <tag><c>varbinds() = [varbind()]</c></tag>
- <item><marker id="type-varbinds"/> </item>
- <tag><c>varsandvals() = [var_and_val()]</c></tag>
- <item><marker id="type-varsandvals"/> </item>
- </taglist>
- <p>These data types are described in the documentation for
- the <seealso marker="snmp:index"><c>SNMP</c></seealso> application.</p>
- </section>
+ <datatypes>
+ <datatype>
+ <name>agent_config() = {Item, Value}</name>
+ <name>agent_ip() = ip()</name>
+ <name>agent_name() = atom()</name>
+ <name>agent_port() = integer()</name>
+ <name>call_back_module() = atom()</name>
+ <name>error_index() = integer()</name>
+ <name>error_status() = noError | atom()</name>
+ <name>ip() = string() | {integer(), integer(), integer(), integer()}</name>
+ <name>manager_ip() = ip()</name>
+ <name>oid() = [byte()]</name>
+ <name>oids() = [oid()]</name>
+ <name>rel_path() = string()</name>
+ <name>sec_type() = none | minimum | semi</name>
+ <name>snmp_app_agent_params() = term()</name>
+ <name>snmp_app_manager_params() = term()</name>
+ <name>snmpreply() = {error_status(), error_index(), varbinds()}</name>
+ <name>user_data() = term()</name>
+ <name>user_name() = atom()</name>
+ <name>usm_config() = {Item, Value}</name>
+ <name>usm_user_name() = string()</name>
+ <name>value_type() = o('OBJECT IDENTIFIER') | i('INTEGER') | u('Unsigned32') | g('Unsigned32') | s('OCTET STRING')</name>
+ <name>var_and_val() = {oid(), value_type(), value()}</name>
+ <name>varbind() = term()</name>
+ <name>varbinds() = [varbind()]</name>
+ <name>varsandvals() = [var_and_val()]</name>
+ <desc>
+ <p>These data types are described in the documentation for
+ the <seeapp marker="snmp:index"><c>SNMP</c></seeapp> application.</p>
+ </desc>
+ </datatype>
+ </datatypes>
<funcs>
<func>
@@ -377,8 +352,8 @@
undefined).</fsummary>
<desc><marker id="start-2"/>
<p>Equivalent to
- <seealso marker="#start-3"><c>ct_snmp:start(Config, MgrAgentConfName,
- undefined)</c></seealso>.</p>
+ <seemfa marker="#start/3"><c>ct_snmp:start(Config, MgrAgentConfName,
+ undefined)</c></seemfa>.</p>
</desc>
</func>
@@ -398,13 +373,13 @@
<c>MgrAgentConfName</c>, are performed. When using SNMPv3, called
USM users are also registered. Users, <c>usm_users</c>, and
managed agents can also be registered later using
- <seealso marker="#register_users-2"><c>ct_snmp:register_users/2</c></seealso>,
- <seealso marker="#register_agents-2"><c>ct_snmp:register_agents/2</c></seealso>,
+ <seemfa marker="#register_users/2"><c>ct_snmp:register_users/2</c></seemfa>,
+ <seemfa marker="#register_agents/2"><c>ct_snmp:register_agents/2</c></seemfa>,
and
- <seealso marker="#register_usm_users-2"><c>ct_snmp:register_usm_users/2</c></seealso>.</p>
+ <seemfa marker="#register_usm_users/2"><c>ct_snmp:register_usm_users/2</c></seemfa>.</p>
<p>The agent started is called <c>snmp_master_agent</c>. Use
- <seealso marker="#load_mibs-1"><c>ct_snmp:load_mibs/1</c></seealso>
+ <seemfa marker="#load_mibs/1"><c>ct_snmp:load_mibs/1</c></seemfa>
to load MIBs into the agent.</p>
<p>With <c>SnmpAppConfName</c> SNMP applications can be configured
diff --git a/lib/common_test/doc/src/ct_ssh.xml b/lib/common_test/doc/src/ct_ssh.xml
index c4fb977354..7659dc986d 100644
--- a/lib/common_test/doc/src/ct_ssh.xml
+++ b/lib/common_test/doc/src/ct_ssh.xml
@@ -64,34 +64,39 @@
<p><c>ConnType = ssh | sftp</c>.</p>
<p>For other types, see
- <seealso marker="ssh:ssh"><c>ssh(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh"><c>ssh(3)</c></seeerl>.</p>
<p>All time-out parameters in <c>ct_ssh</c> functions are values in
milliseconds.</p>
</description>
- <section>
- <title>Data Types</title>
- <marker id="types"/>
- <taglist>
- <tag><c>connection() = handle() | target_name()</c></tag>
- <item><marker id="type-connection"/>
+ <datatypes>
+ <datatype>
+ <name>connection() = handle() | target_name()</name>
+ <desc>
<p>For <c>target_name</c>, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p></item>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p>
+ </desc>
+ </datatype>
- <tag><c>handle() = handle()</c></tag>
- <item><marker id="type-handle"/>
+ <datatype>
+ <name>handle() = handle()</name>
+ <desc>
<p>Handle for a specific SSH/SFTP connection, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p></item>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p>
+ </desc>
+ </datatype>
- <tag><c>ssh_sftp_return() = term()</c></tag>
- <item><marker id="type-ssh_sftp_return"/>
+ <datatype>
+ <name>ssh_sftp_return() = term()</name>
+ <desc>
<p>Return value from an
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp</c></seealso>
- function.</p></item>
- </taglist>
- </section>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp</c></seeerl>
+ function.</p>
+ </desc>
+ </datatype>
+ </datatypes>
<funcs>
<func>
@@ -104,7 +109,7 @@
</type>
<desc><marker id="apread-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -118,7 +123,7 @@
</type>
<desc><marker id="apread-5"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -132,7 +137,7 @@
</type>
<desc><marker id="apwrite-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -146,7 +151,7 @@
</type>
<desc><marker id="apwrite-5"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -160,7 +165,7 @@
</type>
<desc><marker id="aread-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -174,7 +179,7 @@
</type>
<desc><marker id="aread-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -188,7 +193,7 @@
</type>
<desc><marker id="awrite-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -202,7 +207,7 @@
</type>
<desc><marker id="awrite-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -216,7 +221,7 @@
</type>
<desc><marker id="close-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -230,7 +235,7 @@
</type>
<desc><marker id="close-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -239,8 +244,8 @@
<fsummary>Equivalent to connect(KeyOrName, host, []).</fsummary>
<desc><marker id="connect-1"/>
<p>Equivalent to
- <seealso marker="#connect-3"><c>ct_ssh:connect(KeyOrName, host,
- [])</c></seealso>.</p>
+ <seemfa marker="#connect/3"><c>ct_ssh:connect(KeyOrName, host,
+ [])</c></seemfa>.</p>
</desc>
</func>
@@ -249,8 +254,8 @@
<fsummary>Equivalent to connect(KeyOrName, ConnType, []).</fsummary>
<desc><marker id="connect-2"/>
<p>Equivalent to
- <seealso marker="#connect-3"><c>ct_ssh:connect(KeyOrName, ConnType,
- [])</c></seealso>.</p>
+ <seemfa marker="#connect/3"><c>ct_ssh:connect(KeyOrName, ConnType,
+ [])</c></seemfa>.</p>
</desc>
</func>
@@ -279,10 +284,10 @@
be opened using the configuration data specified by <c>Key</c>).</p>
<p>For information on how to create a new <c>Name</c>, see
- <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>.</p>
+ <seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>.</p>
<p>For <c>target_name</c>, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p>
<p><c>ConnType</c> always overrides the type specified in the
address tuple in the configuration data (and in <c>ExtraOpts</c>).
@@ -296,7 +301,7 @@
the configuration data for <c>KeyOrName</c>. The extra options
override any existing options with the same key in the
configuration data. For details on valid SSH options, see
- application <seealso marker="ssh:index"><c>SSH</c></seealso>.</p>
+ application <seeapp marker="ssh:index"><c>SSH</c></seeapp>.</p>
</desc>
</func>
@@ -310,7 +315,7 @@
</type>
<desc><marker id="del_dir-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -324,7 +329,7 @@
</type>
<desc><marker id="del_dir-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -338,7 +343,7 @@
</type>
<desc><marker id="delete-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -352,7 +357,7 @@
</type>
<desc><marker id="delete-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -373,8 +378,8 @@
<fsummary>Equivalent to exec(SSH, Command, DefaultTimeout).</fsummary>
<desc><marker id="exec-2"/>
<p>Equivalent to
- <seealso marker="#exec-3"><c>ct_ssh:exec(SSH, Command,
- DefaultTimeout)</c></seealso>.</p>
+ <seemfa marker="#exec/3"><c>ct_ssh:exec(SSH, Command,
+ DefaultTimeout)</c></seemfa>.</p>
</desc>
</func>
@@ -423,7 +428,7 @@
</type>
<desc><marker id="get_file_info-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -437,7 +442,7 @@
</type>
<desc><marker id="get_file_info-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -451,7 +456,7 @@
</type>
<desc><marker id="list_dir-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -465,7 +470,7 @@
</type>
<desc><marker id="list_dir-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -479,7 +484,7 @@
</type>
<desc><marker id="make_dir-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -493,7 +498,7 @@
</type>
<desc><marker id="make_dir-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -507,7 +512,7 @@
</type>
<desc><marker id="make_symlink-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -521,7 +526,7 @@
</type>
<desc><marker id="make_symlink-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -535,7 +540,7 @@
</type>
<desc><marker id="open-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -549,7 +554,7 @@
</type>
<desc><marker id="open-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -563,7 +568,7 @@
</type>
<desc><marker id="opendir-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -577,7 +582,7 @@
</type>
<desc><marker id="opendir-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -591,7 +596,7 @@
</type>
<desc><marker id="position-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -605,7 +610,7 @@
</type>
<desc><marker id="position-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -619,7 +624,7 @@
</type>
<desc><marker id="pread-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -633,7 +638,7 @@
</type>
<desc><marker id="pread-5"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -647,7 +652,7 @@
</type>
<desc><marker id="pwrite-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -661,7 +666,7 @@
</type>
<desc><marker id="pwrite-5"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -675,7 +680,7 @@
</type>
<desc><marker id="read-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -689,7 +694,7 @@
</type>
<desc><marker id="read-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -703,7 +708,7 @@
</type>
<desc><marker id="read_file-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -717,7 +722,7 @@
</type>
<desc><marker id="read_file-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -731,7 +736,7 @@
</type>
<desc><marker id="read_file_info-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -745,7 +750,7 @@
</type>
<desc><marker id="read_file_info-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -759,7 +764,7 @@
</type>
<desc><marker id="read_link-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -773,7 +778,7 @@
</type>
<desc><marker id="read_link-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -787,7 +792,7 @@
</type>
<desc><marker id="read_link_info-2"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -801,7 +806,7 @@
</type>
<desc><marker id="read_link_info-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -811,8 +816,8 @@
close).</fsummary>
<desc><marker id="receive_response-2"/>
<p>Equivalent to
- <seealso marker="#receive_response-3"><c>ct_ssh:receive_response(SSH,
-ChannelId, close)</c></seealso>.</p>
+ <seemfa marker="#receive_response/3"><c>ct_ssh:receive_response(SSH,
+ChannelId, close)</c></seemfa>.</p>
</desc>
</func>
@@ -822,8 +827,8 @@ ChannelId, close)</c></seealso>.</p>
DefaultTimeout).</fsummary>
<desc><marker id="receive_response-3"/>
<p>Equivalent to
- <seealso marker="#receive_response-4"><c>ct_ssh:receive_response(SSH,
-ChannelId, End, DefaultTimeout)</c></seealso>.</p>
+ <seemfa marker="#receive_response/4"><c>ct_ssh:receive_response(SSH,
+ChannelId, End, DefaultTimeout)</c></seemfa>.</p>
</desc>
</func>
@@ -853,7 +858,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
<p>If <c>End</c> is a fun, this fun is called with one argument, the
data value in a received <c>ssh_cm</c> message (see
- <seealso marker="ssh:ssh_connection"><c>ssh_connection(3)</c></seealso>.
+ <seeerl marker="ssh:ssh_connection"><c>ssh_connection(3)</c></seeerl>.
The fun is to return either <c>true</c> to end the receiving
operation (and have the so far collected data returned) or
<c>false</c> to wait for more data from the server. Even if a fun
@@ -872,7 +877,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</type>
<desc><marker id="rename-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -886,7 +891,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</type>
<desc><marker id="rename-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -895,8 +900,8 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
<fsummary>Equivalent to send(SSH, ChannelId, 0, Data,
DefaultTimeout).</fsummary>
<desc><marker id="send-3"/>
- <p>Equivalent to <seealso marker="#send-5"><c>ct_ssh:send(SSH,
- ChannelId, 0, Data, DefaultTimeout)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#send/5"><c>ct_ssh:send(SSH,
+ ChannelId, 0, Data, DefaultTimeout)</c></seemfa>.</p>
</desc>
</func>
@@ -904,8 +909,8 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
<name since="">send(SSH, ChannelId, Data, Timeout) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to send(SSH, ChannelId, 0, Data, Timeout).</fsummary>
<desc><marker id="send-4"/>
- <p>Equivalent to <seealso marker="#send-5"><c>ct_ssh:send(SSH,
- ChannelId, 0, Data, Timeout)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#send/5"><c>ct_ssh:send(SSH,
+ ChannelId, 0, Data, Timeout)</c></seemfa>.</p>
</desc>
</func>
@@ -931,8 +936,8 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
close).</fsummary>
<desc><marker id="send_and_receive-3"/>
<p>Equivalent to
- <seealso marker="#send_and_receive-4"><c>ct_ssh:send_and_receive(SSH,
- ChannelId, Data, close)</c></seealso>.</p>
+ <seemfa marker="#send_and_receive/4"><c>ct_ssh:send_and_receive(SSH,
+ ChannelId, Data, close)</c></seemfa>.</p>
</desc>
</func>
@@ -942,8 +947,8 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
DefaultTimeout).</fsummary>
<desc><marker id="send_and_receive-4"/>
<p>Equivalent to
- <seealso marker="#send_and_receive-6"><c>ct_ssh;send_and_receive(SSH,
-ChannelId, 0, Data, End, DefaultTimeout)</c></seealso>.</p>
+ <seemfa marker="#send_and_receive/6"><c>ct_ssh;send_and_receive(SSH,
+ChannelId, 0, Data, End, DefaultTimeout)</c></seemfa>.</p>
</desc>
</func>
@@ -953,8 +958,8 @@ ChannelId, 0, Data, End, DefaultTimeout)</c></seealso>.</p>
Timeout).</fsummary>
<desc><marker id="send_and_receive-5"/>
<p>Equivalent to
- <seealso marker="#send_and_receive-6"><c>ct_ssh:send_and_receive(SSH,
-ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
+ <seemfa marker="#send_and_receive/6"><c>ct_ssh:send_and_receive(SSH,
+ChannelId, 0, Data, End, Timeout)</c></seemfa>.</p>
</desc>
</func>
@@ -976,7 +981,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
receive the server response.</p>
<p>For details on argument <c>End</c>, see
- <seealso marker="#receive_response-4"><c>ct_ssh:receive_response/4</c></seealso>.</p>
+ <seemfa marker="#receive_response/4"><c>ct_ssh:receive_response/4</c></seemfa>.</p>
</desc>
</func>
@@ -998,8 +1003,8 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
<fsummary>Equivalent to session_open(SSH, DefaultTimeout).</fsummary>
<desc><marker id="session_open-1"/>
<p>Equivalent to
- <seealso marker="#session_open-2"><c>ct_ssh:session_open(SSH,
- DefaultTimeout)</c></seealso>.</p>
+ <seemfa marker="#session_open/2"><c>ct_ssh:session_open(SSH,
+ DefaultTimeout)</c></seemfa>.</p>
</desc>
</func>
@@ -1038,8 +1043,8 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
<fsummary>Equivalent to shell(SSH, ChannelId, DefaultTimeout).</fsummary>
<desc><marker id="shell-2"/>
<p>Equivalent to
- <seealso marker="#shell-3"><c>ct_ssh:shell(SSH, ChannelId,
- DefaultTimeout)</c></seealso>.</p>
+ <seemfa marker="#shell/3"><c>ct_ssh:shell(SSH, ChannelId,
+ DefaultTimeout)</c></seemfa>.</p>
</desc>
</func>
@@ -1066,8 +1071,8 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
DefaultTimeout).</fsummary>
<desc><marker id="subsystem-3"/>
<p>Equivalent to
- <seealso marker="#subsystem-4"><c>ct_ssh:subsystem(SSH, ChannelId,
- Subsystem, DefaultTimeout)</c></seealso>.</p>
+ <seemfa marker="#subsystem/4"><c>ct_ssh:subsystem(SSH, ChannelId,
+ Subsystem, DefaultTimeout)</c></seemfa>.</p>
</desc>
</func>
@@ -1097,7 +1102,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</type>
<desc><marker id="write-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -1111,7 +1116,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</type>
<desc><marker id="write-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -1125,7 +1130,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</type>
<desc><marker id="write_file-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -1139,7 +1144,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</type>
<desc><marker id="write_file-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -1153,7 +1158,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</type>
<desc><marker id="write_file_info-3"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
@@ -1167,7 +1172,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</type>
<desc><marker id="write_file_info-4"/>
<p>For information and other types, see
- <seealso marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seealso>.</p>
+ <seeerl marker="ssh:ssh_sftp"><c>ssh_sftp(3)</c></seeerl>.</p>
</desc>
</func>
</funcs>
diff --git a/lib/common_test/doc/src/ct_suite.xml b/lib/common_test/doc/src/ct_suite.xml
new file mode 100644
index 0000000000..0289a569f0
--- /dev/null
+++ b/lib/common_test/doc/src/ct_suite.xml
@@ -0,0 +1,619 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2020</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+ <title>ct_suite</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module since="">ct_suite</module>
+ <modulesummary>-behaviour(ct_suite).
+ </modulesummary>
+ <description>
+ <p>The following section describes the mandatory and optional test suite
+ functions that <c>Common Test</c> calls during test execution.
+ For more details, see section
+ <seeguide marker="write_test_chapter">Writing Test Suites</seeguide>
+ in the User's Guide.</p>
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="ct_testname" n_vars="0"/>
+ <desc><p>The name of the testcase function.</p></desc>
+ </datatype>
+ <datatype>
+ <name name="ct_groupname" n_vars="0"/>
+ <desc><p>The name of the test group.</p></desc>
+ </datatype>
+ <datatype>
+ <name name="ct_config" n_vars="0"/>
+ <desc><p>The configuration data that can be modified.</p></desc>
+ </datatype>
+ <datatype>
+ <name name="ct_status" n_vars="0"/>
+ <desc><p>The status value for a nested subgroup.</p></desc>
+ </datatype>
+ <datatype>
+ <name>ct_group_def()</name>
+ <desc><p>The test group definition, as returned by <seemfa marker="#Module:groups/0"><c>Module:groups/0</c></seemfa>.</p></desc>
+ </datatype>
+ <datatype>
+ <name>ct_test_def()</name>
+ <desc><p>The test suite definition, as returned by <seemfa marker="#Module:all/0"><c>Module:all/0</c></seemfa>.</p></desc>
+ </datatype>
+ <datatype>
+ <name>ct_info()</name>
+ <desc><p>The test suite information, as returned by <seemfa marker="#Module:suite/0"><c>Module:suite/0</c></seemfa>, <seemfa marker="#Module:group/1"><c>Module:group/1</c></seemfa> and <seemfa marker="#Module:Testcase/0"><c>Module:Testcase/0</c></seemfa>.</p></desc>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>
+ The following functions are to be exported from a
+ <c>ct_suite</c> callback module in order to define
+ the callback interface for a test suite.
+ </p>
+ </fsdescription>
+
+ <func>
+ <name since="">Module:all() -> [ct_test_def()] | {skip, Reason}</name>
+ <fsummary>Returns the list of all test case groups and test cases
+ in the module.</fsummary>
+ <type>
+ <v><seetype marker="#ct_test_def">ct_test_def()</seetype> = TestCase | {group, GroupName} | {group, GroupName, Properties} | {group, GroupName, Properties, SubGroups}</v>
+ <v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v>Properties = [parallel | sequence | Shuffle | {RepeatType, N}] | default</v>
+ <v>SubGroups = [{GroupName, Properties} | {GroupName, Properties, SubGroups}]</v>
+ <v>Shuffle = shuffle | {shuffle, Seed}</v>
+ <v>Seed = {integer(), integer(), integer()}</v>
+ <v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>N = integer() | forever</v>
+ <v>Reason = term()</v>
+ </type>
+
+ <desc>
+ <p>MANDATORY</p>
+
+ <p>Returns the list of all test cases and test case groups in the
+ test suite module to be executed. This list also specifies the
+ order the cases and groups are executed by <c>Common Test</c>.
+ A test case is represented by an atom,
+ the name of the test case function, or a <c>testcase</c> tuple
+ indicating that the test case shall be repeated. A test case group is
+ represented by a <c>group</c> tuple, where <c>GroupName</c>,
+ an atom, is the name of the group (defined in
+ <seemfa marker="#Module:groups/0"><c>Module:groups/0</c></seemfa>).
+ Execution properties for groups can also be specified, both
+ for a top-level group and for any of its subgroups.
+ Group execution properties specified here override
+ properties in the group definition (see
+ <seemfa marker="#Module:groups/0"><c>Module:groups/0</c></seemfa>).
+ (With value <c>default</c>, the group definition properties
+ are used).</p>
+
+ <p>If <c>{skip, Reason}</c> is returned, all test cases
+ in the module are skipped and <c>Reason</c>
+ is printed on the HTML result page.</p>
+
+ <p>For details on groups, see section
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case
+ Groups</seeguide> in the User's Guide.</p>
+
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:groups() -> [ct_group_def()]</name>
+ <fsummary>Returns a list of test case group definitions.</fsummary>
+ <type>
+ <v><seetype marker="#ct_group_def">ct_group_def()</seetype> = {GroupName, Properties, GroupsAndTestCases}</v>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v>Properties = [parallel | sequence | Shuffle | {RepeatType, N}]</v>
+ <v>GroupsAndTestCases = [Group | {group, GroupName} | TestCase]</v>
+ <v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
+ <v>Shuffle = shuffle | {shuffle, Seed}</v>
+ <v>Seed = {integer(), integer(), integer()}</v>
+ <v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v>
+ <v>N = integer() | forever</v>
+ </type>
+
+ <desc>
+ <p>OPTIONAL</p>
+
+ <p>Defines test case groups. For details, see section
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case
+ Groups</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:suite() -> [ct_info()]</name>
+ <fsummary>Test suite info function (providing default data
+ for the suite).</fsummary>
+ <type>
+ <v><seetype marker="#ct_info">ct_info()</seetype> = {timetrap, Time} | {require, Required} | {require, Name, Required} | {userdata, UserData} | {silent_connections, Conns} | {stylesheet, CSSFile} | {ct_hooks, CTHs}</v>
+ <v>Time = TimeVal | TimeFunc</v>
+ <v>TimeVal = MilliSec | {seconds, integer()} | {minutes, integer()} | {hours, integer()}</v>
+ <v>TimeFunc = {Mod, Func, Args} | Fun</v>
+ <v>MilliSec = integer()</v>
+ <v>Mod = atom()</v>
+ <v>Func = atom()</v>
+ <v>Args = list()</v>
+ <v>Fun = fun()</v>
+ <v>Required = Key | {Key, SubKeys} | {Key, SubKey} | {Key, SubKey, SubKeys}</v>
+ <v>Key = atom()</v>
+ <v>SubKeys = SubKey | [SubKey]</v>
+ <v>SubKey = atom()</v>
+ <v>Name = atom()</v>
+ <v>UserData = term()</v>
+ <v>Conns = [atom()]</v>
+ <v>CSSFile = string()</v>
+ <v>CTHs = [CTHModule |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
+ <v>CTHModule = atom()</v>
+ <v>CTHInitArgs = term()</v>
+ <v>CTHPriority = integer()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL</p>
+
+ <p>The test suite information function. Returns a list of tagged
+ tuples specifying various properties related to the execution of
+ this test suite (common for all test cases in the suite).</p>
+
+ <p>Tag <c>timetrap</c> sets the maximum time that each
+ test case is allowed to execute (including
+ <seemfa marker="#Module:init_per_testcase/2"><c>Module:init_per_testcase/2</c></seemfa>
+ and
+ <seemfa marker="#Module:end_per_testcase/2"><c>Module:end_per_testcase/2</c></seemfa>).
+ If the timetrap time is exceeded, the test case fails with reason
+ <c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
+ set a new timetrap by returning a <c>TimeVal</c>. It can also be
+ used to trigger a timetrap time-out by, at some point, returning a
+ value other than a <c>TimeVal</c>. For details, see section
+ <seeguide marker="write_test_chapter#timetraps">Timetrap Time-Outs</seeguide>
+ in the User's Guide.</p>
+
+ <p>Tag <c>require</c> specifies configuration variables
+ required by test cases (or configuration functions)
+ in the suite. If the required configuration variables are not found
+ in any of the configuration files, all test cases are skipped.
+ For details about the <c>require</c> functionality, see funtion
+ <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.</p>
+
+ <p>With <c>userdata</c>, the user can
+ specify any test suite-related information, which can be
+ read by calling
+ <seemfa marker="ct#userdata/2"><c>ct:userdata/2</c></seemfa>.</p>
+
+ <p>Tag <c>ct_hooks</c> specifies the
+ <seeguide marker="ct_hooks_chapter">Common Test Hooks</seeguide>
+ to be run with this suite.</p>
+
+ <p>Other tuples than the ones defined are ignored.</p>
+
+ <p>For details about the test suite information function, see section
+ <seeguide marker="write_test_chapter#suite">Test
+ Suite Information Function</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:init_per_suite(Config) -> NewConfig | {skip, Reason} |
+ {skip_and_save, Reason, SaveConfig}</name>
+ <fsummary>Test suite initializations.</fsummary>
+ <type>
+ <v>Config = NewConfig = SaveConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL; if this function is defined, then <seemfa
+ marker="#Module:end_per_suite/1"><c>Module:end_per_suite/1</c></seemfa>
+ must also be defined.</p>
+
+ <p>This configuration function is called as the first function in the
+ suite. It typically contains initializations that are common for
+ all test cases in the suite, and that must only be done
+ once. Parameter <c>Config</c> is the configuration data
+ that can be modified. Whatever is returned from this
+ function is specified as <c>Config</c> to all configuration functions
+ and test cases in the suite.</p>
+
+ <p>If <c>{skip, Reason}</c>
+ is returned, all test cases in the suite are skipped
+ and <c>Reason</c> is printed in the overview log for the suite.</p>
+
+ <p>For information on <c>save_config</c> and <c>skip_and_save</c>,
+ see section
+ <seeguide marker="dependencies_chapter#save_config">Saving
+ Configuration Data</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:end_per_suite(Config) -> term() |
+ {save_config, SaveConfig}</name>
+ <fsummary>Test suite finalization.</fsummary>
+ <type>
+ <v>Config = SaveConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ </type>
+
+ <desc>
+ <p>OPTIONAL; if this function is defined, then <seemfa
+ marker="#Module:init_per_suite/1"><c>Module:init_per_suite/1</c></seemfa>
+ must also be defined.</p>
+
+ <p>This function is called as the last test case in the
+ suite. It is meant to be used for cleaning up after
+ <seemfa marker="#Module:init_per_suite/1"><c>Module:init_per_suite/1</c></seemfa>.</p>
+ <p>For information on <c>save_config</c>, see section
+ <seeguide marker="dependencies_chapter#save_config">Saving
+ Configuration Data</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R15B">Module:group(GroupName) -> [ct_info()]</name>
+ <fsummary>Test case group information function (providing default data
+ for a test case group, that is, its test cases and
+ subgroups).</fsummary>
+ <type>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v><seetype marker="#ct_info">ct_info()</seetype> = {timetrap, Time} | {require, Required} | {require, Name, Required} | {userdata, UserData} | {silent_connections, Conns} | {stylesheet, CSSFile} | {ct_hooks, CTHs}</v>
+ <v>Time = TimeVal | TimeFunc</v>
+ <v>TimeVal = MilliSec | {seconds, integer()} | {minutes, integer()} | {hours, integer()}</v>
+ <v>TimeFunc = {Mod, Func, Args} | Fun</v>
+ <v>MilliSec = integer()</v>
+ <v>Mod = atom()</v>
+ <v>Func = atom()</v>
+ <v>Args = list()</v>
+ <v>Fun = fun()</v>
+ <v>Required = Key | {Key, SubKeys} | {Key, SubKey} | {Key, SubKey, SubKeys}</v>
+ <v>Key = atom()</v>
+ <v>SubKeys = SubKey | [SubKey]</v>
+ <v>SubKey = atom()</v>
+ <v>Name = atom()</v>
+ <v>UserData = term()</v>
+ <v>Conns = [atom()]</v>
+ <v>CSSFile = string()</v>
+ <v>CTHs = [CTHModule |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
+ <v>CTHModule = atom()</v>
+ <v>CTHInitArgs = term()</v>
+ <v>CTHPriority = integer()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL</p>
+
+ <p>The test case group information function. It is supposed to
+ return a list of tagged tuples that specify various properties
+ related to the execution of a test case group (that is, its test
+ cases and subgroups). Properties set by
+ <seemfa marker="#Module:group/1"><c>Module:group/1</c></seemfa> override
+ properties with the same key that have been set previously by
+ <seemfa marker="#Module:suite/0"><c>Module:suite/0</c></seemfa>.</p>
+
+ <p>Tag <c>timetrap</c> sets the maximum time that each
+ test case is allowed to execute (including
+ <seemfa marker="#Module:init_per_testcase/2"><c>Module:init_per_testcase/2</c></seemfa>
+ and
+ <seemfa marker="#Module:end_per_testcase/2"><c>Module:end_per_testcase/2</c></seemfa>).
+ If the timetrap time is
+ exceeded, the test case fails with reason
+ <c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
+ set a new timetrap by returning a <c>TimeVal</c>. It can also be
+ used to trigger a timetrap time-out by, at some point, returning a
+ value other than a <c>TimeVal</c>. For details, see section
+ <seeguide marker="write_test_chapter#timetraps">Timetrap
+ Time-Outs</seeguide> in the User's Guide.</p>
+
+ <p>Tag <c>require</c> specifies configuration variables
+ required by test cases (or configuration functions)
+ in the suite. If the required configuration variables are not found
+ in any of the configuration files, all test cases in this group are
+ skipped. For details about the <c>require</c> functionality, see
+ function
+ <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.</p>
+
+ <p>With <c>userdata</c>, the user can
+ specify any test case group related information that can be
+ read by calling
+ <seemfa marker="ct#userdata/2"><c>ct:userdata/2</c></seemfa>.</p>
+
+ <p>Tag <c>ct_hooks</c> specifies the
+ <seeguide marker="ct_hooks_chapter">Common Test Hooks</seeguide>
+ to be run with this suite.</p>
+
+ <p>Other tuples than the ones defined are ignored.</p>
+
+ <p>For details about the test case group information function,
+ see section <seeguide marker="write_test_chapter#group_info">Group
+ Information Function</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:init_per_group(GroupName, Config) -> NewConfig |
+ {skip, Reason}</name>
+ <fsummary>Test case group initializations.</fsummary>
+ <type>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v>Config = NewConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL; if this function is defined, then <seemfa
+ marker="#Module:end_per_group/2"><c>Module:end_per_group/2</c></seemfa>
+ must also be defined.</p>
+
+ <p>This configuration function is called before execution of a
+ test case group. It typically contains initializations that are
+ common for all test cases and subgroups in the group, and that
+ must only be performed once. <c>GroupName</c> is the name of the
+ group, as specified in the group definition (see
+ <seemfa marker="#Module:groups/0"><c>Module:groups/0</c></seemfa>).
+ Parameter <c>Config</c> is the configuration data that can be
+ modified.
+ The return value of this function is given as <c>Config</c>
+ to all test cases and subgroups in the group.</p>
+
+ <p>If <c>{skip, Reason}</c>
+ is returned, all test cases in the group are skipped and
+ <c>Reason</c> is printed in the overview log for the group.</p>
+
+ <p>For information about test case groups, see section
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case
+ Groups</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:end_per_group(GroupName, Config) -> term() |
+ {return_group_result, Status}</name>
+ <fsummary>Test case group finalization.</fsummary>
+ <type>
+ <v>GroupName = <seetype marker="#ct_groupname">ct_groupname()</seetype></v>
+ <v>Config = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Status = <seetype marker="#ct_status">ct_status()</seetype></v>
+ </type>
+
+ <desc>
+ <p>OPTIONAL; if this function is defined, then <seemfa
+ marker="#Module:init_per_group/2"><c>Module:init_per_group/2</c></seemfa>
+ must also be defined.</p>
+
+ <p>This function is called after the execution of a test case group
+ is finished. It is meant to be used for cleaning up after
+ <seemfa marker="#Module:init_per_group/2"><c>Module:init_per_group/2</c></seemfa>.
+ A status value for a nested subgroup can be returned with
+ <c>{return_group_result, Status}</c>. The status can be retrieved in
+ <seemfa marker="#Module:end_per_group/2"><c>Module:end_per_group/2</c></seemfa>
+ for the group on the level above. The status is also used by
+ <c>Common Test</c> for deciding if execution of a group is to
+ proceed if property <c>sequence</c> or <c>repeat_until_*</c>
+ is set.</p>
+
+ <p>For details about test case groups, see section
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case
+ Groups</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail, Reason} | {skip, Reason}</name>
+ <fsummary>Test case initializations.</fsummary>
+ <type>
+ <v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
+ <v>Config = NewConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL; if this function is defined,
+ then <seemfa marker="#Module:end_per_testcase/2">
+ <c>Module:end_per_testcase/2</c></seemfa> must also be
+ defined.</p>
+
+ <p>This function is called before each test case. Argument
+ <c>TestCase</c> is the test case name, and
+ <c>Config</c> (list of key-value tuples) is the configuration
+ data that can be modified. The <c>NewConfig</c> list returned
+ from this function is given as <c>Config</c> to the test case.
+ If <c>{fail, Reason}</c> is returned, the test case is
+ marked as failed without being executed.</p>
+
+ <p>If <c>{skip, Reason}</c> is returned, the test case is skipped
+ and <c>Reason</c> is printed in the overview log for the suite.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:end_per_testcase(TestCase, Config) -> term() | {fail, Reason} | {save_config, SaveConfig}</name>
+ <fsummary>Test case finalization.</fsummary>
+ <type>
+ <v>TestCase = <seetype marker="#ct_testname">ct_testname()</seetype></v>
+ <v>Config = SaveConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+
+ <p>OPTIONAL; if this function is defined,
+ then <seemfa marker="#Module:init_per_testcase/2">
+ <c>Module:init_per_testcase/2</c></seemfa> must also be
+ defined.</p>
+
+ <p>This function is called after each test case, and can be used
+ to clean up after
+ <seemfa marker="#Module:init_per_testcase/2"><c>Module:init_per_testcase/2</c></seemfa>
+ and the test case. Any return value (besides <c>{fail, Reason}</c>
+ and <c>{save_config, SaveConfig}</c>) is ignored. By returning
+ <c>{fail, Reason}</c>, <c>TestCase</c> is marked as faulty (even
+ though it was successful in the sense that it returned
+ a value instead of terminating).</p>
+
+ <p>For information on <c>save_config</c>, see section
+ <seeguide marker="dependencies_chapter#save_config">Saving
+ Configuration Data</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R14B">Module:Testcase() -> [ct_info()] </name>
+ <fsummary>Test case information function.</fsummary>
+ <type>
+ <v><seetype marker="#ct_info">ct_info()</seetype> = {timetrap, Time} | {require, Required} | {require, Name, Required} | {userdata, UserData} | {silent_connections, Conns} | {stylesheet, CSSFile} | {ct_hooks, CTHs}</v>
+ <v>Time = TimeVal | TimeFunc</v>
+ <v>TimeVal = MilliSec | {seconds, integer()} | {minutes, integer()} | {hours, integer()}</v>
+ <v>TimeFunc = {Mod, Func, Args} | Fun</v>
+ <v>MilliSec = integer()</v>
+ <v>Mod = atom()</v>
+ <v>Func = atom()</v>
+ <v>Args = list()</v>
+ <v>Fun = fun()</v>
+ <v>Required = Key | {Key, SubKeys} | {Key, SubKey} | {Key, SubKey, SubKeys}</v>
+ <v>Key = atom()</v>
+ <v>SubKeys = SubKey | [SubKey]</v>
+ <v>SubKey = atom()</v>
+ <v>Name = atom()</v>
+ <v>UserData = term()</v>
+ <v>Conns = [atom()]</v>
+ <v>CSSFile = string()</v>
+ <v>CTHs = [CTHModule |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs} |</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{CTHModule, CTHInitArgs, CTHPriority}]</v>
+ <v>CTHModule = atom()</v>
+ <v>CTHInitArgs = term()</v>
+ <v>CTHPriority = integer()</v>
+ </type>
+
+ <desc>
+
+ <p>OPTIONAL</p>
+
+ <p>The test case information function. It is supposed to
+ return a list of tagged tuples that specify various properties
+ related to the execution of this particular test case.
+ Properties set by
+ <seemfa marker="#Module:Testcase/0"><c>Module:Testcase/0</c></seemfa>
+ override properties set previously for the test case by
+ <seemfa marker="#Module:group/1"><c>Module:group/1</c></seemfa> or
+ <seemfa marker="#Module:suite/0"><c>Module:suite/0</c></seemfa>.</p>
+
+ <p>Tag <c>timetrap</c> sets the maximum time that the
+ test case is allowed to execute. If the timetrap time is
+ exceeded, the test case fails with reason <c>timetrap_timeout</c>.
+ <seemfa marker="#Module:init_per_testcase/2"><c>Module:init_per_testcase/2</c></seemfa>
+ and
+ <seemfa marker="#Module:end_per_testcase/2"><c>Module:end_per_testcase/2</c></seemfa>
+ are included in the timetrap time.
+ A <c>TimeFunc</c> function can be used to
+ set a new timetrap by returning a <c>TimeVal</c>. It can also be
+ used to trigger a timetrap time-out by, at some point, returning a
+ value other than a <c>TimeVal</c>. For details, see section
+ <seeguide marker="write_test_chapter#timetraps">Timetrap
+ Time-Outs</seeguide> in the User's Guide.</p>
+
+ <p>Tag <c>require</c> specifies configuration variables
+ that are required by the test case (or <c>init_per_testcase/2</c>
+ or <c>end_per_testcase/2</c>).
+ If the required configuration variables are not found in any of the
+ configuration files, the test case is skipped. For details about
+ the <c>require</c> functionality, see function
+ <seemfa marker="ct#require/1"><c>ct:require/1,2</c></seemfa>.</p>
+
+ <p>If <c>timetrap</c> or <c>require</c> is not set, the
+ default values specified by
+ <seemfa marker="#Module:suite/0"><c>Module:suite/0</c></seemfa> (or
+ <seemfa marker="#Module:group/1"><c>Module:group/1</c></seemfa>) are used.</p>
+
+ <p>With <c>userdata</c>, the user can specify any test case-related
+ information that can be read by calling
+ <seemfa marker="ct#userdata/3"><c>ct:userdata/3</c></seemfa>.</p>
+
+ <p>Other tuples than the ones defined are ignored.</p>
+
+ <p>For details about the test case information function, see section
+ <seeguide marker="write_test_chapter#info_function">Test
+ Case Information Function</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R14B">Module:Testcase(Config) -> term() | {skip, Reason} | {comment, Comment} | {save_config, SaveConfig} | {skip_and_save, Reason, SaveConfig} | exit()</name>
+ <fsummary>A test case.</fsummary>
+ <type>
+ <v>Config = SaveConfig = <seetype marker="#ct_config">ct_config()</seetype></v>
+ <v>Reason = term()</v>
+ <v>Comment = string()</v>
+ </type>
+
+ <desc>
+ <p>MANDATORY</p>
+
+ <p>The implementation of a test case. Call the functions to test and
+ check the result. If something fails, ensure the
+ function causes a runtime error or call
+ <seemfa marker="ct#fail/1"><c>ct:fail/1,2</c></seemfa>
+ (which also causes the test case process to terminate).</p>
+
+ <p>Elements from the <c>Config</c> list can, for example, be read
+ with <c>proplists:get_value/2</c> in STDLIB
+ (or the macro <c>?config</c> defined in <c>ct.hrl</c>).</p>
+
+ <p>If you decide not to run the test case after all, return
+ <c>{skip, Reason}</c>. <c>Reason</c> is then
+ printed in field <c>Comment</c> on the HTML result page.</p>
+
+ <p>To print some information in field <c>Comment</c> on the HTML
+ result page, return <c>{comment, Comment}</c>.</p>
+
+ <p>If the function returns anything else, the test case is
+ considered successful. The return value always gets printed
+ in the test case log file.</p>
+
+ <p>For details about test case implementation, see section
+ <seeguide marker="write_test_chapter#test_cases">Test Cases</seeguide>
+ in the User's Guide.</p>
+
+ <p>For information on <c>save_config</c> and <c>skip_and_save</c>,
+ see section
+ <seeguide marker="dependencies_chapter#save_config">Saving
+ Configuration Data</seeguide> in the User's Guide.</p>
+ </desc>
+ </func>
+
+ </funcs>
+
+</erlref>
diff --git a/lib/common_test/doc/src/ct_telnet.xml b/lib/common_test/doc/src/ct_telnet.xml
index 2774ab3f3c..0a0dadbeb3 100644
--- a/lib/common_test/doc/src/ct_telnet.xml
+++ b/lib/common_test/doc/src/ct_telnet.xml
@@ -44,7 +44,7 @@
perform string matching on the result. For information about how to use
<c>ct_telnet</c> and configure connections, specifically for UNIX hosts,
see the
- <seealso marker="unix_telnet"><c>unix_telnet</c></seealso> manual page.
+ <seeerl marker="unix_telnet"><c>unix_telnet</c></seeerl> manual page.
</p>
<p>Default values defined in <c>ct_telnet</c>:</p>
@@ -89,7 +89,7 @@
<p><c>keep_alive</c> can be specified per connection, if necessary. For
details, see
- <seealso marker="unix_telnet"><c>unix_telnet</c></seealso>.</p>
+ <seeerl marker="unix_telnet"><c>unix_telnet</c></seeerl>.</p>
</description>
@@ -127,7 +127,7 @@
option <c>hosts</c> and list the names of the servers/connections
to be used in the suite. The connections must be named for this to
work (see
- <seealso marker="#open-1"><c>ct_telnet:open/1,2,3,4</c></seealso>).</p>
+ <seemfa marker="#open/1"><c>ct_telnet:open/1,2,3,4</c></seemfa>).</p>
<p>Hook option <c>log_type</c> can be used to change the
<c>cth_conn_log</c> behavior. The default value of this option is
@@ -177,31 +177,37 @@
[{ct_hooks, [{cth_conn_log, []}]}].</pre>
</section>
- <section>
- <title>Data Types</title>
- <marker id="types"/>
- <taglist>
- <tag><c>connection() = handle() | {target_name(), connection_type()} | target_name()</c></tag>
- <item><marker id="type-connection"/>
+ <datatypes>
+ <datatype>
+ <name>connection() = handle() | {target_name(), connection_type()} | target_name()</name>
+ <desc>
<p>For <c>target_name()</c>, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p></item>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p>
+ </desc>
+ </datatype>
- <tag><c>connection_type() = telnet | ts1 | ts2</c></tag>
- <item><marker id="type-connection_type"/> </item>
+ <datatype>
+ <name>connection_type() = telnet | ts1 | ts2</name>
+ </datatype>
- <tag><c>handle() = handle()</c></tag>
- <item><marker id="type-handle"/>
+ <datatype>
+ <name>handle() = handle()</name>
+ <desc>
<p>Handle for a specific Telnet connection, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p></item>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p>
+ </desc>
+ </datatype>
- <tag><c>prompt_regexp() = string()</c></tag>
- <item><marker id="type-prompt_regexp"/>
+ <datatype>
+ <name>prompt_regexp() = string()</name>
+ <desc>
<p>Regular expression matching all possible prompts for a specific
- target type. <c>regexp</c> must not have any groups, that is, when
- matching, <c>re:run/3</c> (in STDLIB) must return a list with
- one single element.</p></item>
- </taglist>
- </section>
+ target type. <c>regexp</c> must not have any groups, that is, when
+ matching, <c>re:run/3</c> (in STDLIB) must return a list with
+ one single element.</p>
+ </desc>
+ </datatype>
+ </datatypes>
<funcs>
<func>
@@ -218,7 +224,7 @@
<p>A connection can be associated with a target name and/or a handle.
If <c>Connection</c> has no associated target name, it can only
be closed with the handle value (see
- <seealso marker="#open-4"><c>ct_telnet:open/4</c></seealso>).</p>
+ <seemfa marker="#open/4"><c>ct_telnet:open/4</c></seemfa>).</p>
</desc>
</func>
@@ -227,8 +233,8 @@
<fsummary>Equivalent to cmd(Connection, Cmd, []).</fsummary>
<desc><marker id="cmd-2"/>
<p>Equivalent to
- <seealso marker="#cmd-3"><c>ct_telnet:cmd(Connection, Cmd,
- [])</c></seealso>.</p>
+ <seemfa marker="#cmd/3"><c>ct_telnet:cmd(Connection, Cmd,
+ [])</c></seemfa>.</p>
</desc>
</func>
@@ -259,7 +265,7 @@
for prompt. If the time expires, the function returns
<c>{error,timeout}</c>. For information about the default value
for the command timeout, see the
- <seealso marker="#Default_values">list of default values</seealso>
+ <seeerl marker="#Default_values">list of default values</seeerl>
in the beginning of this module.</p>
</desc>
</func>
@@ -269,8 +275,8 @@
<fsummary>Equivalent to cmdf(Connection, CmdFormat, Args, []).</fsummary>
<desc><marker id="cmdf-3"/>
<p>Equivalent to
- <seealso marker="#cmdf-4"><c>ct_telnet:cmdf(Connection, CmdFormat,
- Args, [])</c></seealso>.</p>
+ <seemfa marker="#cmdf/4"><c>ct_telnet:cmdf(Connection, CmdFormat,
+ Args, [])</c></seemfa>.</p>
</desc>
</func>
@@ -292,7 +298,7 @@
and a list of arguments to build the command).</p>
<p>For details, see
- <seealso marker="#cmd-3"><c>ct_telnet:cmd/3</c></seealso>.</p>
+ <seemfa marker="#cmd/3"><c>ct_telnet:cmd/3</c></seemfa>.</p>
</desc>
</func>
@@ -301,8 +307,8 @@
<fsummary>Equivalent to expect(Connections, Patterns, []).</fsummary>
<desc><marker id="expect-2"/>
<p>Equivalent to
- <seealso marker="#expect-3"><c>ct_telnet:expect(Connections,
- Patterns, [])</c></seealso>.</p>
+ <seemfa marker="#expect/3"><c>ct_telnet:expect(Connections,
+ Patterns, [])</c></seemfa>.</p>
</desc>
</func>
@@ -453,8 +459,8 @@
<fsummary>Equivalent to open(Name, telnet).</fsummary>
<desc><marker id="open-1"/>
<p>Equivalent to
- <seealso marker="#open-2"><c>ct_telnet:open(Name,
- telnet)</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>ct_telnet:open(Name,
+ telnet)</c></seemfa>.</p>
</desc>
</func>
@@ -478,8 +484,8 @@
<fsummary>Equivalent to open(KeyOrName, ConnType, TargetMod, []).</fsummary>
<desc><marker id="open-3"/>
<p>Equivalent to
- <seealso marker="#open-4"><c>ct_telnet:ct_telnet:open(KeyOrName,
- ConnType, TargetMod, [])</c></seealso>.</p>
+ <seemfa marker="#open/4"><c>ct_telnet:ct_telnet:open(KeyOrName,
+ ConnType, TargetMod, [])</c></seemfa>.</p>
</desc>
</func>
@@ -506,7 +512,7 @@
alternatives:</p>
<list type="bulleted">
- <item><p><seealso marker="ct#require-2"><c>ct:require/2</c></seealso>
+ <item><p><seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>
in a test case</p></item>
<item><p>A <c>require</c> statement in the suite information
function (<c>suite/0</c>)</p></item>
@@ -526,10 +532,10 @@
(for example, <c>unix_telnet</c>).</p>
<p>For <c>target_name()</c>, see module
- <seealso marker="ct"><c>ct</c></seealso>.</p>
+ <seeerl marker="ct"><c>ct</c></seeerl>.</p>
<p>See also
- <seealso marker="ct#require-2"><c>ct:require/2</c></seealso>.</p>
+ <seemfa marker="ct#require/2"><c>ct:require/2</c></seemfa>.</p>
</desc>
</func>
@@ -538,8 +544,8 @@
<fsummary>Equivalent to send(Connection, Cmd, []).</fsummary>
<desc><marker id="send-2"/>
<p>Equivalent to
- <seealso marker="#send-3"><c>ct_telnet:send(Connection, Cmd,
- [])</c></seealso>.</p>
+ <seemfa marker="#send/3"><c>ct_telnet:send(Connection, Cmd,
+ [])</c></seemfa>.</p>
</desc>
</func>
@@ -566,8 +572,8 @@
carriage return and newline characters.</p>
<p>The resulting output from the command can be read with
- <seealso marker="#get_data-1"><c>ct_telnet:get_data/2</c></seealso> or
- <seealso marker="#expect-2"><c>ct_telnet:expect/2,3</c></seealso>.</p>
+ <seemfa marker="#get_data/1"><c>ct_telnet:get_data/2</c></seemfa> or
+ <seemfa marker="#expect/2"><c>ct_telnet:expect/2,3</c></seemfa>.</p>
</desc>
</func>
@@ -576,8 +582,8 @@
<fsummary>Equivalent to sendf(Connection, CmdFormat, Args, []).</fsummary>
<desc><marker id="sendf-3"/>
<p>Equivalent to
- <seealso marker="#sendf-4"><c>ct_telnet:sendf(Connection, CmdFormat,
- Args, [])</c></seealso>.</p>
+ <seemfa marker="#sendf/4"><c>ct_telnet:sendf(Connection, CmdFormat,
+ Args, [])</c></seemfa>.</p>
</desc>
</func>
@@ -598,14 +604,14 @@
string and a list of arguments to build the command).</p>
<p>For details, see
- <seealso marker="#send-3"><c>ct_telnet:send/3</c></seealso>.</p>
+ <seemfa marker="#send/3"><c>ct_telnet:send/3</c></seemfa>.</p>
</desc>
</func>
</funcs>
<section>
<title>See Also</title>
- <p><seealso marker="unix_telnet"><c>unix_telnet</c></seealso></p>
+ <p><seeerl marker="unix_telnet"><c>unix_telnet</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/common_test/doc/src/dependencies_chapter.xml b/lib/common_test/doc/src/dependencies_chapter.xml
index 8ede822ae5..0a0f4c562f 100644
--- a/lib/common_test/doc/src/dependencies_chapter.xml
+++ b/lib/common_test/doc/src/dependencies_chapter.xml
@@ -87,8 +87,8 @@
<p>To avoid this, we can consider starting and stopping the server for every test.
We can thus implement the start and stop action as common functions to be
called from
- <seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase</c></seealso> and
- <seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase</c></seealso>.
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase</c></seemfa> and
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>.
(Remember to test the start and stop functionality separately.)
The configuration can also be implemented as a common function, maybe grouped
with the start function. Finally, the testing of connecting and disconnecting a
@@ -194,9 +194,9 @@
<p>To pass data from one test suite to another, the same mechanism is used. The data
is to be saved by finction
- <seealso marker="common_test#Module:end_per_suite-1"><c>end_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite</c></seemfa>
and read by function
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite</c></seemfa>
in the suite that follows. When passing data between suites, <c>Saver</c> carries the
name of the test suite.</p>
@@ -264,7 +264,7 @@
<p>A sequence of test cases is defined as a test case group
with a <c>sequence</c> property. Test case groups are defined
through function <c>groups/0</c> in the test suite (for details, see section
- <seealso marker="write_test_chapter#test_case_groups">Test Case Groups</seealso>.</p>
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case Groups</seeguide>.</p>
<p>For example, to ensure that if <c>allocate</c>
in <c>server_b_SUITE</c> crashes, <c>deallocate</c> is skipped,
@@ -306,9 +306,9 @@
any property, that is, they are not required to also be sequences. If you want the
status of the subgroup to affect the sequence on the level above, return
<c>{return_group_result,Status}</c> from
- <seealso marker="common_test#Module:end_per_group-2"><c>end_per_group/2</c></seealso>,
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group/2</c></seemfa>,
as described in section
- <seealso marker="write_test_chapter#repeated_groups">Repeated Groups</seealso>
+ <seeguide marker="write_test_chapter#repeated_groups">Repeated Groups</seeguide>
in Writing Test Suites.
A failed subgroup (<c>Status == failed</c>) causes the execution of a
sequence to fail in the same way a test case does.</p>
diff --git a/lib/common_test/doc/src/event_handler_chapter.xml b/lib/common_test/doc/src/event_handler_chapter.xml
index 89272891ed..ee758c3199 100644
--- a/lib/common_test/doc/src/event_handler_chapter.xml
+++ b/lib/common_test/doc/src/event_handler_chapter.xml
@@ -50,9 +50,9 @@
pass the information on. The event handlers are Erlang modules
implemented by the <c>Common Test</c> user according to the <c>gen_event</c>
behavior (for details, see module
- <seealso marker="stdlib:gen_event"><c>gen_event</c></seealso> and
+ <seeerl marker="stdlib:gen_event"><c>gen_event</c></seeerl> and
section
- <seealso marker="doc/design_principles:events"><c>gen_event Behaviour</c></seealso>
+ <seeguide marker="system/design_principles:events"><c>gen_event Behaviour</c></seeguide>
in OTP Design Principles in the System Documentation).
</p>
@@ -69,20 +69,20 @@
manager, either by telling <c>Common Test</c> to install them before the test
run (described later), or by adding the handlers dynamically during the test
run using
- <seealso marker="stdlib:gen_event#add_handler-3"><c>gen_event:add_handler/3</c></seealso> or
- <seealso marker="stdlib:gen_event#add_sup_handler-3"><c>gen_event:add_sup_handler/3</c></seealso>.
+ <seemfa marker="stdlib:gen_event#add_handler/3"><c>gen_event:add_handler/3</c></seemfa> or
+ <seemfa marker="stdlib:gen_event#add_sup_handler/3"><c>gen_event:add_sup_handler/3</c></seemfa>.
In the latter scenario, the reference of the <c>Common Test</c> event manager is
required. To get it, call
- <seealso marker="ct#get_event_mgr_ref-0"><c>ct:get_event_mgr_ref/0</c></seealso>
+ <seemfa marker="ct#get_event_mgr_ref/0"><c>ct:get_event_mgr_ref/0</c></seemfa>
or (on the <c>Common Test</c> Master node)
- <seealso marker="ct_master#get_event_mgr_ref-0"><c>ct_master:get_event_mgr_ref/0</c></seealso>.</p>
+ <seemfa marker="ct_master#get_event_mgr_ref/0"><c>ct_master:get_event_mgr_ref/0</c></seemfa>.</p>
</section>
<section>
<marker id="usage"></marker>
<title>Use</title>
<p>Event handlers can be installed by an <c>event_handler</c> start flag
- (<seealso marker="ct_run"><c>ct_run</c></seealso>) or option
- <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>, where the
+ (<seecom marker="ct_run"><c>ct_run</c></seecom>) or option
+ <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>, where the
argument specifies the names of one or more event handler modules.</p>
<p><em>Example:</em></p>
@@ -99,7 +99,7 @@
example).</p></note>
<p>An event_handler tuple in argument <c>Opts</c> has the following definition
- (see <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>):</p>
+ (see <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>):</p>
<pre>
{event_handler,EventHandlers}
@@ -115,7 +115,7 @@
Event handler <c>my_evh2</c> is started with the name of the current node in the init argument list.</p>
<p>Event handlers can also be plugged in using one of the following
- <seealso marker="run_test_chapter#test_specifications">test specification</seealso>
+ <seeguide marker="run_test_chapter#test_specifications">test specification</seeguide>
terms:</p>
<list type="bulleted">
<item><c>{event_handler, EventHandlers}</c></item>
@@ -377,8 +377,8 @@
manager can look like.</p>
<note><p>To ensure that printouts to <c>stdout</c> (or printouts made with
- <seealso marker="ct#log-2"><c>ct:log/2,3</c></seealso> or
- <seealso marker="ct#pal-2"><c>ct:pal,2,3</c></seealso>) get written to the test case log
+ <seemfa marker="ct#log/2"><c>ct:log/2,3</c></seemfa> or
+ <seemfa marker="ct#pal/2"><c>ct:pal,2,3</c></seemfa>) get written to the test case log
file, and not to the <c>Common Test</c> framework log, you can synchronize
with the <c>Common Test</c> server by matching on evvents <c>tc_start</c> and <c>tc_done</c>.
In the period between these events, all I/O is directed to the
diff --git a/lib/common_test/doc/src/getting_started_chapter.xml b/lib/common_test/doc/src/getting_started_chapter.xml
index 9b021058e8..171a40b3a0 100644
--- a/lib/common_test/doc/src/getting_started_chapter.xml
+++ b/lib/common_test/doc/src/getting_started_chapter.xml
@@ -49,7 +49,7 @@
<p>
To understand what is discussed and examplified here, we recommended
you to first read section
- <seealso marker="basics_chapter#basics">Common Test Basics</seealso>.
+ <seeguide marker="basics_chapter#basics">Common Test Basics</seeguide>.
</p>
</note>
</section>
@@ -85,7 +85,7 @@
a test case to generate a runtime error to indicate failure (for example,
by causing a bad match error or by calling <c>exit/1</c>, preferably
through the help function
- <seealso marker="ct#fail-1"><c>ct:fail/1,2</c></seealso>). A successful
+ <seemfa marker="ct#fail/1"><c>ct:fail/1,2</c></seemfa>). A successful
execution is indicated by a normal return from the test case function.
</p>
</section>
@@ -93,9 +93,9 @@
<section>
<title>A Simple Test Suite</title>
<p>As shown in section
- <seealso marker="basics_chapter#External_Interfaces">Common Test Basics</seealso>,
+ <seeguide marker="basics_chapter#External_Interfaces">Common Test Basics</seeguide>,
the test suite module implements
- <seealso marker="common_test">callback functions</seealso>
+ <seeerl marker="common_test">callback functions</seeerl>
(mandatory or optional) for various purposes, for example:
</p>
<list type="bulleted">
@@ -219,52 +219,52 @@
be hard-coded in the test suites (such as hostnames, addresses, and
user login data)?"</p>
<p><em>Answer:</em>
- See section <seealso marker="config_file_chapter#top">External Configuration Data</seealso>.</p>
+ See section <seeguide marker="config_file_chapter#top">External Configuration Data</seeguide>.</p>
</item>
<item><p><em>Question:</em> "Is there a way to declare different tests and run them
in one session without having to write my own scripts? Also, can such
declarations be used for regression testing?"</p>
<p><em>Answer:</em> See section
- <seealso marker="run_test_chapter#test_specifications">Test Specifications</seealso>
+ <seeguide marker="run_test_chapter#test_specifications">Test Specifications</seeguide>
in section Running Tests and Analyzing Results.
</p>
</item>
<item><p><em>Question:</em> "Can test cases and/or test runs be automatically repeated?"</p>
<p><em>Answer:</em> Learn more about
- <seealso marker="write_test_chapter#test_case_groups">Test Case Groups</seealso>
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case Groups</seeguide>
and read about start flags/options in section
- <seealso marker="run_test_chapter#ct_run">Running Tests</seealso> and in
+ <seeguide marker="run_test_chapter#ct_run">Running Tests</seeguide> and in
the Reference Manual.</p>
</item>
<item><p><em>Question:</em> "Does <c>Common Test</c> execute my test cases in sequence or in parallel?"</p>
<p><em>Answer:</em> See
- <seealso marker="write_test_chapter#test_case_groups">Test Case Groups</seealso>
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case Groups</seeguide>
in section Writing Test Suites.</p>
</item>
<item><p><em>Question:</em> "What is the syntax for timetraps (mentioned earlier), and how do I set them?"</p>
<p><em>Answer:</em> This is explained in the
- <seealso marker="write_test_chapter#timetraps">Timetrap Time-Outs</seealso>
+ <seeguide marker="write_test_chapter#timetraps">Timetrap Time-Outs</seeguide>
part of section Writing Test Suites.</p>
</item>
<item><p><em>Question:</em> "What functions are available for logging and printing?"</p>
<p><em>Answer:</em> See
- <seealso marker="write_test_chapter#logging">Logging</seealso>
+ <seeguide marker="write_test_chapter#logging">Logging</seeguide>
in section Writing Test Suites.</p>
</item>
<item><p><em>Question:</em> "I need data files for my tests. Where do I store them preferably?"</p>
<p><em>Answer:</em> See
- <seealso marker="write_test_chapter#data_priv_dir">Data and Private
- Directories</seealso>.</p>
+ <seeguide marker="write_test_chapter#data_priv_dir">Data and Private
+ Directories</seeguide>.</p>
</item>
<item><p><em>Question:</em> "Can I start with a test suite example, please?"</p>
- <p><em>Answer:</em> <seealso marker="example_chapter#top">Welcome!</seealso></p>
+ <p><em>Answer:</em> <seeguide marker="example_chapter#top">Welcome!</seeguide></p>
</item>
</list>
<p>You probably want to get started on your own first test suites now, while
diff --git a/lib/common_test/doc/src/install_chapter.xml b/lib/common_test/doc/src/install_chapter.xml
index b29906d381..17ef6c8dc1 100644
--- a/lib/common_test/doc/src/install_chapter.xml
+++ b/lib/common_test/doc/src/install_chapter.xml
@@ -36,8 +36,8 @@
<p>The two main interfaces for running tests with <c>Common Test</c>
are an executable program named
- <seealso marker="ct_run"><c>ct_run</c></seealso> and the
- Erlang module <seealso marker="ct"><c>ct</c></seealso>.
+ <seecom marker="ct_run"><c>ct_run</c></seecom> and the
+ Erlang module <seeerl marker="ct"><c>ct</c></seeerl>.
<c>ct_run</c> is compiled for the underlying operating system (for example,
Unix/Linux or Windows) during the build of the Erlang/OTP system,
and is installed automatically with other executable programs in
@@ -51,11 +51,3 @@
and/or the interface functions in the <c>ct</c> module.</p>
</section>
</chapter>
-
-
-
-
-
-
-
-
diff --git a/lib/common_test/doc/src/introduction.xml b/lib/common_test/doc/src/introduction.xml
index df12bea6dd..a7ee0b0840 100644
--- a/lib/common_test/doc/src/introduction.xml
+++ b/lib/common_test/doc/src/introduction.xml
@@ -44,7 +44,7 @@
case functions.</p></item>
</list>
<p><c>Common Test</c> also integrates use of the OTP
- <seealso marker="tools:cover">cover</seealso> tool in application
+ <seeerl marker="tools:cover">cover</seeerl> tool in application
Tools for code coverage analysis of Erlang/OTP programs.</p>
<p><c>Common Test</c> executes test suite programs automatically,
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index 70653c0711..1ba1765347 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -33,6 +33,74 @@
<file>notes.xml</file>
</header>
+<section><title>Common_Test 1.20</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Various address sanitizer support.</p>
+ <p>
+ Own Id: OTP-16959 Aux Id: PR-2965 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Common_Test 1.19.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add behaviour for test suites</p>
+ <p>
+ Own Id: OTP-17070</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Common_Test 1.19</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The function <c>ct_property_test:init_tool/1</c> is added
+ for the cases when the user does not want
+ ct_property_test to compile properties. init_tool/1 can
+ be used to set the property_test_tool config value.</p>
+ <p>
+ Own Id: OTP-16029 Aux Id: PR-2145 </p>
+ </item>
+ <item>
+ <p>
+ The built-in Common Test Hook, <c>cth_log_redirect</c>,
+ has been updated to use the system <c>default</c> Logger
+ handler's configuration instead of its own. See the
+ section on <seeguide
+ marker="common_test:ct_hooks_chapter#built-in-cths">Built-in
+ Hooks</seeguide> in the Common Test User's Guide.</p>
+ <p>
+ Own Id: OTP-16273</p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.18.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/common_test/doc/src/ref_man.xml b/lib/common_test/doc/src/ref_man.xml
index 1ac20db5c2..b8184ed7f8 100644
--- a/lib/common_test/doc/src/ref_man.xml
+++ b/lib/common_test/doc/src/ref_man.xml
@@ -33,7 +33,7 @@
</description>
<xi:include href="common_test_app.xml"/>
- <xi:include href="ct_run.xml"/>
+ <xi:include href="ct_run_cmd.xml"/>
<xi:include href="ct.xml"/>
<xi:include href="ct_master.xml"/>
<xi:include href="ct_cover.xml"/>
@@ -48,6 +48,7 @@
<xi:include href="ct_hooks.xml"/>
<xi:include href="ct_property_test.xml"/>
<xi:include href="ct_testspec.xml"/>
+ <xi:include href="ct_suite.xml"/>
</application>
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index 700becf8ab..0b8657ced3 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -64,7 +64,7 @@
control <c>stdin</c>), the test run proceeds automatically without the missing
suites. This behavior can however be modified with the
<c><![CDATA[ct_run]]></c> flag <c><![CDATA[-abort_if_missing_suites]]></c>,
- or the <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso> option
+ or the <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa> option
<c><![CDATA[{abort_if_missing_suites,TrueOrFalse}]]></c>. If
<c><![CDATA[abort_if_missing_suites]]></c> is set to <c>true</c>, the test run
stops immediately if some suites fail to compile.</p>
@@ -80,7 +80,7 @@
<p>If test suites or help modules include header files stored in other
locations than the test directory, these include directories can be specified
by using flag <c><![CDATA[-include]]></c> with
- <seealso marker="ct_run"><c>ct_run</c></seealso>,
+ <seecom marker="ct_run"><c>ct_run</c></seecom>,
or option <c><![CDATA[include]]></c> with <c><![CDATA[ct:run_test/1]]></c>.
Also, an include path can be specified with an OS
environment variable, <c><![CDATA[CT_INCLUDE_PATH]]></c>.</p>
@@ -93,7 +93,7 @@
, or both, to the compiler.</p>
<p>Include directories can also be specified in test specifications,
- see <seealso marker="#test_specifications">Test Specifications</seealso>.</p>
+ see <seeguide marker="#test_specifications">Test Specifications</seeguide>.</p>
<p>If the user wants to run all test suites for a test object (or an OTP application)
by specifying only the top directory (for example, with start flag/option <c>dir</c>),
@@ -119,7 +119,7 @@
<marker id="ct_run"></marker>
<title>Running Tests from the OS Command Line</title>
- <p>The <seealso marker="ct_run"><c>ct_run</c></seealso> program can be used
+ <p>The <seecom marker="ct_run"><c>ct_run</c></seecom> program can be used
for running tests from the OS command line, for example, as follows:
</p>
<list type="bulleted">
@@ -150,10 +150,10 @@
$ ct_run -suite ./testdir/x_SUITE ./testdir/y_SUITE</pre>
<p>For details, see
- <seealso marker="run_test_chapter#group_execution">Test Case Group Execution</seealso>.</p>
+ <seeguide marker="run_test_chapter#group_execution">Test Case Group Execution</seeguide>.</p>
<p>The following flags can also be used with
- <seealso marker="ct_run"><c>ct_run</c></seealso>:</p>
+ <seecom marker="ct_run"><c>ct_run</c></seecom>:</p>
<taglist>
<tag><c><![CDATA[-help]]></c></tag>
<item><p>Lists all available start flags.</p></item>
@@ -189,30 +189,30 @@
<tag><c><![CDATA[-cover <cover_cfg_file>]]></c></tag>
<item><p>To perform code coverage test (see
- <seealso marker="cover_chapter#cover">Code Coverage Analysis</seealso>).</p></item>
+ <seeguide marker="cover_chapter#cover">Code Coverage Analysis</seeguide>).</p></item>
<tag><c><![CDATA[-cover_stop <bool>]]></c></tag>
<item><p>To specify if the <c>cover</c> tool is to be stopped
after the test is completed (see
- <seealso marker="cover_chapter#cover_stop">Code Coverage Analysis</seealso>).</p></item>
+ <seeguide marker="cover_chapter#cover_stop">Code Coverage Analysis</seeguide>).</p></item>
<tag><c><![CDATA[-event_handler <event_handlers>]]></c></tag>
<item><p>To install
- <seealso marker="event_handler_chapter#event_handling">event handlers</seealso>.</p></item>
+ <seeguide marker="event_handler_chapter#event_handling">event handlers</seeguide>.</p></item>
<tag><c><![CDATA[-event_handler_init <event_handlers>]]></c></tag>
<item><p>To install
- <seealso marker="event_handler_chapter#event_handling">event handlers</seealso>
+ <seeguide marker="event_handler_chapter#event_handling">event handlers</seeguide>
including start arguments.</p></item>
<tag><c><![CDATA[-ct_hooks <ct_hooks>]]></c></tag>
<item><p>To install
- <seealso marker="ct_hooks_chapter#installing">Common Test Hooks</seealso>
+ <seeguide marker="ct_hooks_chapter#installing">Common Test Hooks</seeguide>
including start arguments.</p></item>
<tag><c><![CDATA[-enable_builtin_hooks <bool>]]></c></tag>
<item><p>To enable or disable
- <seealso marker="ct_hooks_chapter#builtin_cths">Built-in Common Test Hooks</seealso>.
+ <seeguide marker="ct_hooks_chapter#builtin_cths">Built-in Common Test Hooks</seeguide>.
Default is <c>true</c>.</p></item>
<tag><c><![CDATA[-include]]></c></tag>
@@ -225,12 +225,12 @@
<item><p>Aborts the test run if one or more suites fail to compile (described earlier).</p></item>
<tag><c><![CDATA[-multiply_timetraps <n>]]></c></tag>
- <item><p>Extends <seealso marker="write_test_chapter#timetraps">timetrap
- time-out</seealso> values.</p></item>
+ <item><p>Extends <seeguide marker="write_test_chapter#timetraps">timetrap
+ time-out</seeguide> values.</p></item>
<tag><c><![CDATA[-scale_timetraps <bool>]]></c></tag>
- <item><p>Enables automatic <seealso marker="write_test_chapter#timetraps">timetrap
- time-out</seealso> scaling.</p></item>
+ <item><p>Enables automatic <seeguide marker="write_test_chapter#timetraps">timetrap
+ time-out</seeguide> scaling.</p></item>
<tag><c><![CDATA[-repeat <n>]]></c></tag>
<item><p>Tells <c>Common Test</c> to repeat the tests <c>n</c> times (described later).</p></item>
@@ -247,26 +247,26 @@
<tag><c><![CDATA[-decrypt_key <key>]]></c></tag>
<item><p>Provides a decryption key for
- <seealso marker="config_file_chapter#encrypted_config_files">encrypted configuration files</seealso>.</p></item>
+ <seeguide marker="config_file_chapter#encrypted_config_files">encrypted configuration files</seeguide>.</p></item>
<tag><c><![CDATA[-decrypt_file <key_file>]]></c></tag>
<item><p>Points out a file containing a decryption key for
- <seealso marker="config_file_chapter#encrypted_config_files">encrypted configuration files</seealso>.</p></item>
+ <seeguide marker="config_file_chapter#encrypted_config_files">encrypted configuration files</seeguide>.</p></item>
<tag><c><![CDATA[-basic_html]]></c></tag>
<item><p>Switches off HTML enhancements that can be incompatible with older browsers.</p></item>
<tag><c><![CDATA[-logopts <opts>]]></c></tag>
<item><p>Enables modification of the logging behavior, see
- <seealso marker="run_test_chapter#logopts">Log options</seealso>.</p></item>
+ <seeguide marker="run_test_chapter#logopts">Log options</seeguide>.</p></item>
<tag><c><![CDATA[-verbosity <levels>]]></c></tag>
- <item><p>Sets <seealso marker="write_test_chapter#logging">verbosity levels
- for printouts</seealso>.</p></item>
+ <item><p>Sets <seeguide marker="write_test_chapter#logging">verbosity levels
+ for printouts</seeguide>.</p></item>
<tag><c><![CDATA[-no_esc_chars]]></c></tag>
<item><p>Disables automatic escaping of special HTML characters.
- See the <seealso marker="write_test_chapter#logging">Logging chapter</seealso>.</p></item>
+ See the <seeguide marker="write_test_chapter#logging">Logging chapter</seeguide>.</p></item>
</taglist>
<note><p>Directories passed to <c>Common Test</c> can have either relative or absolute paths.</p></note>
@@ -303,8 +303,8 @@
<c>ct_run -dir ./</c></p></note>
<p>For more information about the <c>ct_run</c> program, see module
- <seealso marker="ct_run"><c>ct_run</c></seealso> and section
- <seealso marker="install_chapter#general">Installation</seealso>.
+ <seecom marker="ct_run"><c>ct_run</c></seecom> and section
+ <seeguide marker="install_chapter#general">Installation</seeguide>.
</p>
</section>
@@ -314,14 +314,14 @@
<p><c>Common Test</c> provides an Erlang API for running tests. The main
(and most flexible) function for specifying and executing tests is
- <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>.
+ <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>.
It takes the same start parameters as
- <seealso marker="run_test_chapter#ct_run"><c>ct_run</c></seealso>,
+ <seeguide marker="run_test_chapter#ct_run"><c>ct_run</c></seeguide>,
but the flags are instead specified as options in a list of key-value tuples.
For example, a test specified with <c>ct_run</c> as follows:</p>
<p><c>$ ct_run -suite ./my_SUITE -logdir ./results</c></p>
- <p>is with <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso> specified as:</p>
+ <p>is with <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa> specified as:</p>
<p><c>1> ct:run_test([{suite,"./my_SUITE"},{logdir,"./results"}]).</c></p>
<p>The function returns the test result, represented by the tuple
@@ -337,7 +337,7 @@
<section>
<title>Releasing the Erlang Shell</title>
<p>During execution of tests started with
- <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>,
+ <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>,
the Erlang shell process, controlling <c>stdin</c>, remains the top-level
process of the <c>Common Test</c> system of processes. Consequently,
the Erlang shell is not available for interaction during
@@ -352,13 +352,13 @@
<c>ct:run_test/1</c> returns the pid of this process rather than the
test result, which instead is printed to tty at the end of the test run.</p>
<note><p>To use the functions
- <seealso marker="ct#break-1"><c>ct:break/1,2</c></seealso> and
- <seealso marker="ct#continue-0"><c>ct:continue/0,1</c></seealso>,
+ <seemfa marker="ct#break/1"><c>ct:break/1,2</c></seemfa> and
+ <seemfa marker="ct#continue/0"><c>ct:continue/0,1</c></seemfa>,
<c>release_shell</c> <em>must</em> be set to <c>true</c>.</p></note>
</section>
<p>For details, see
- <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso> manual page.</p>
+ <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa> manual page.</p>
</section>
<section>
@@ -380,7 +380,7 @@
or more group names and/or one or more group paths. At startup,
<c>Common Test</c> searches for matching groups in the group definitions
tree (that is, the list returned from <c>Suite:groups/0</c>; for details, see section
- <seealso marker="write_test_chapter#test_case_groups">Test Case Groups</seealso>.
+ <seeguide marker="write_test_chapter#test_case_groups">Test Case Groups</seeguide>.
</p>
<p>Given a group name, say <c>g</c>, <c>Common Test</c> searches for all paths
@@ -523,8 +523,8 @@
in the scope of the test suite only (no group configuration functions are called).</p>
<p>The group specification feature, as presented in this section, can also
- be used in <seealso marker="run_test_chapter#test_specifications">Test
- Specifications</seealso> (with some extra features added).</p>
+ be used in <seeguide marker="run_test_chapter#test_specifications">Test
+ Specifications</seeguide> (with some extra features added).</p>
</section>
@@ -542,9 +542,9 @@
trying out various operations during test suite development.</p>
<p>To start the interactive shell mode, start an Erlang shell
- manually and call <seealso marker="ct#install-1"><c>ct:install/1</c></seealso>
+ manually and call <seemfa marker="ct#install/1"><c>ct:install/1</c></seemfa>
to install any configuration data you might need (use <c>[]</c> as argument otherwise).
- Then call <seealso marker="ct#start_interactive-0"><c>ct:start_interactive/0</c></seealso>
+ Then call <seemfa marker="ct#start_interactive/0"><c>ct:start_interactive/0</c></seemfa>
to start <c>Common Test</c>.</p>
<p>If you use the <c>ct_run</c> program, you can start
@@ -564,10 +564,10 @@
<p>If any functions using "required configuration data" (for example, functions
<c>ct_telnet</c> or <c>ct_ftp</c>) are to be called from the Erlang shell, first require
- configuration data with <seealso marker="ct#require-1"><c>
- ct:require/1,2</c></seealso>. This is equivalent to a <c>require</c> statement
- in the <seealso marker="write_test_chapter#suite">Test Suite Information Function</seealso>
- or in the <seealso marker="write_test_chapter#info_function">Test Case Information Function</seealso>.</p>
+ configuration data with <seemfa marker="ct#require/1"><c>
+ ct:require/1,2</c></seemfa>. This is equivalent to a <c>require</c> statement
+ in the <seeguide marker="write_test_chapter#suite">Test Suite Information Function</seeguide>
+ or in the <seeguide marker="write_test_chapter#info_function">Test Case Information Function</seeguide>.</p>
<p><em>Example:</em></p>
<pre>
@@ -587,13 +587,13 @@
is not supported.</p>
<p>If you wish to exit the interactive mode (for example, to start an automated
- test run with <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>),
+ test run with <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>),
call function
- <seealso marker="ct#stop_interactive-0"><c>ct:stop_interactive/0</c></seealso>.
+ <seemfa marker="ct#stop_interactive/0"><c>ct:stop_interactive/0</c></seemfa>.
This shuts down the running <c>ct</c> application. Associations between
configuration names and data created with <c>require</c> are
consequently deleted. Function
- <seealso marker="ct#start_interactive-0"><c>ct:start_interactive/0</c></seealso>
+ <seemfa marker="ct#start_interactive/0"><c>ct:start_interactive/0</c></seemfa>
takes you back into interactive mode, but the previous state is not restored.</p>
</section>
@@ -601,7 +601,7 @@
<title>Step-by-Step Execution of Test Cases with the Erlang Debugger</title>
<p>Using <c>ct_run -step [opts]</c>, or by passing option <c>{step,Opts}</c>
- to <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>,
+ to <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>,
the following is possible:</p>
<list type="bulleted">
<item>Get the Erlang Debugger started automatically.</item>
@@ -635,9 +635,9 @@
<p>The most flexible way to specify what to test, is to use a
test specification, which is a sequence of
Erlang terms. The terms are normally declared in one or more text files
- (see <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>), but
+ (see <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa>), but
can also be passed to <c>Common Test</c> on the form of a list (see
- <seealso marker="ct#run_testspec-1"><c>ct:run_testspec/1</c></seealso>).
+ <seemfa marker="ct#run_testspec/1"><c>ct:run_testspec/1</c></seemfa>).
There are two general types of terms: configuration terms and test
specification terms.</p>
@@ -773,7 +773,7 @@
<p>The test specification uses the same mechanism for specifying
test case groups through names and paths, as explained in section
- <seealso marker="run_test_chapter#group_execution">Test Case Group Execution</seealso>,
+ <seeguide marker="run_test_chapter#group_execution">Test Case Group Execution</seeguide>,
with the addition of element <c>GroupSpec</c>.</p>
<p>Element <c>GroupSpec</c> makes it possible to specify
@@ -783,8 +783,8 @@
change properties of groups at the time of execution,
without having to edit the test suite. The same feature is available for
<c>group</c> elements in the <c>Suite:all/0</c> list. For details and examples,
- see section <seealso marker="write_test_chapter#test_case_groups">
- Test Case Groups</seealso>.</p>
+ see section <seeguide marker="write_test_chapter#test_case_groups">
+ Test Case Groups</seeguide>.</p>
</section>
<section>
@@ -794,21 +794,21 @@
test host environment and in a distributed <c>Common Test</c> environment
(Large Scale Testing). The node parameters in term <c>init</c> are only
relevant in the latter (see section
- <seealso marker="ct_master_chapter#test_specifications">Test Specifications</seealso>
+ <seeguide marker="ct_master_chapter#test_specifications">Test Specifications</seeguide>
in Large Scale Testing). For details about the various terms, see the
corresponding sections in the User's Guide, for example, the following:
</p>
<list type="bulleted">
- <item>The <seealso marker="run_test_chapter#ct_run"><c>ct_run</c>
- program</seealso> for an overview of available start flags
+ <item>The <seeguide marker="run_test_chapter#ct_run"><c>ct_run</c>
+ program</seeguide> for an overview of available start flags
(as most flags have a corresponding configuration term)</item>
- <item><seealso marker="write_test_chapter#logging">Logging</seealso>
+ <item><seeguide marker="write_test_chapter#logging">Logging</seeguide>
(for terms <c>verbosity</c>, <c>stylesheet</c>, <c>basic_html</c> and <c>esc_chars</c>)</item>
- <item><seealso marker="config_file_chapter#top">External Configuration Data</seealso>
+ <item><seeguide marker="config_file_chapter#top">External Configuration Data</seeguide>
(for terms <c>config</c> and <c>userconfig</c>)</item>
- <item><seealso marker="event_handler_chapter#event_handling">Event
- Handling</seealso> (for the <c>event_handler</c> term)</item>
- <item><seealso marker="ct_hooks_chapter#installing">Common Test Hooks</seealso>
+ <item><seeguide marker="event_handler_chapter#event_handling">Event
+ Handling</seeguide> (for the <c>event_handler</c> term)</item>
+ <item><seeguide marker="ct_hooks_chapter#installing">Common Test Hooks</seeguide>
(for term <c>ct_hooks</c>)</item>
</list>
@@ -1045,7 +1045,7 @@
<p>Constants can well replace term <c>node</c> also, but
this still has a declarative value, mainly when used in combination
with <c>NodeRefs == all_nodes</c>
- (see <seealso marker="#types">Types</seealso>).</p>
+ (see <seeguide marker="#types">Types</seeguide>).</p>
</section>
<section>
@@ -1097,8 +1097,8 @@
<p>With term <c>init</c> it is possible to specify initialization options
for nodes defined in the test specification. There are options
to start the node and to evaluate any function on the node.
- For details, see section <seealso marker="ct_master_chapter#ct_slave">Automatic Startup of
- Test Target Nodes</seealso> in section Using Common Test for Large Scale Testing.</p>
+ For details, see section <seeguide marker="ct_master_chapter#ct_slave">Automatic Startup of
+ Test Target Nodes</seeguide> in section Using Common Test for Large Scale Testing.</p>
</section>
<section>
<title>User-Specific Terms</title>
@@ -1107,7 +1107,7 @@
when starting tests with <c>ct_run</c>. This forces <c>Common Test</c> to ignore
unrecognizable terms. In this mode, <c>Common Test</c> is not able to check the
specification for errors as efficiently as if the scanner runs in default mode.
- If <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso> is used
+ If <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa> is used
for starting the tests, the relaxed scanner mode is enabled by tuple
<c>{allow_user_terms,true}</c>.</p>
</section>
@@ -1116,7 +1116,7 @@
<p>Terms in the current test specification
(that is, the specification that has been used to configure and run the current test)
can be looked up.
- The function <seealso marker="ct#get_testspec_terms-0"><c>get_testspec_terms()</c></seealso>
+ The function <seemfa marker="ct#get_testspec_terms/0"><c>get_testspec_terms()</c></seemfa>
returns a list of all test specification terms (both configuration terms and test terms),
and <c>get_testspec_terms(Tags)</c> returns the term (or a list of terms) matching the
tag (or tags) in <c>Tags</c>.</p>
@@ -1218,8 +1218,8 @@
straightforward to compare the latest results to that of previous
test runs, even if the set of test cases changes. If application SASL
is running, its logs are also printed to the current minor log file by the
- <seealso marker="common_test:ct_hooks_chapter#builtin_cths">
- cth_log_redirect built-in hook</seealso>.
+ <seeguide marker="common_test:ct_hooks_chapter#builtin_cths">
+ cth_log_redirect built-in hook</seeguide>.
</p>
<p>The full name of the minor log file (that is, the name of the file
@@ -1229,7 +1229,7 @@
can also be read by a pre- or post <c>Common Test Hook</c> function). Also,
at the start of a test case, this data is sent with an event
to any installed event handler. For details, see section
- <seealso marker="event_handler_chapter#event_handling">Event Handling</seealso>.
+ <seeguide marker="event_handler_chapter#event_handling">Event Handling</seeguide>.
</p>
<p>The log files are written continuously during a test run and links are
@@ -1280,8 +1280,8 @@
<title>The Unexpected I/O Log</title>
<p>The test suites overview page includes a link to the Unexpected I/O Log.
In this log, <c>Common Test</c> saves printouts made with
- <seealso marker="ct#log-2"><c>ct:log/1,2,3,4,5</c></seealso> and
- <seealso marker="ct#pal-2"><c>ct:pal/1,2,3,4,5</c></seealso>, as well as captured system
+ <seemfa marker="ct#log/2"><c>ct:log/1,2,3,4,5</c></seemfa> and
+ <seemfa marker="ct#pal/2"><c>ct:pal/1,2,3,4,5</c></seemfa>, as well as captured system
error- and progress reports, which cannot be associated with particular test cases and
therefore cannot be written to individual test case log files. This occurs,
for example, if a log printout is made from an external process (not a test
@@ -1305,7 +1305,7 @@
All information in these examples ends up in the Pre- and Post Test I/O Log.
For more information on how to synchronize test runs with external user
applications, see section
- <seealso marker="ct_hooks_chapter#synchronizing">Synchronizing</seealso>
+ <seeguide marker="ct_hooks_chapter#synchronizing">Synchronizing</seeguide>
in section Common Test Hooks.</p>
<note><p>Logging to file with <c>ct:log/1,2,3,4,5</c> or <c>ct:pal/1,2,3,4,5</c>
only works when <c>Common Test</c> is running. Printouts with <c>ct:pal/1,2,3,4,5</c>
@@ -1336,7 +1336,7 @@
<pre>
basic_html</pre>
<p>This disables the use of style sheets and JavaScripts (see
- <seealso marker="#table_sorting">Sorting HTML Table Columns</seealso>).</p>
+ <seeguide marker="#table_sorting">Sorting HTML Table Columns</seeguide>).</p>
<p><c>Common Test</c> includes an <em>optional</em> feature to allow
user HTML style sheets for customizing printouts. The
@@ -1523,7 +1523,7 @@ div.error pre { color:white }</pre>
case group. The options described here are used to repeat execution of entire test runs,
while the <c>repeat</c> property of a test case group makes it possible to repeat
execution of sets of test cases within a suite. For more information about the latter,
- see section <seealso marker="write_test_chapter#test_case_groups">Test Case Groups </seealso>
+ see section <seeguide marker="write_test_chapter#test_case_groups">Test Case Groups </seeguide>
in section Writing Test Suites.</p></note>
</section>
@@ -1592,8 +1592,8 @@ div.error pre { color:white }</pre>
<p><c>silent_connections</c> can also be specified with a term
in a test specification
- (see section <seealso marker="run_test_chapter#test_specifications">Test
- Specifications</seealso> in section Running Tests and Analyzing Results).
+ (see section <seeguide marker="run_test_chapter#test_specifications">Test
+ Specifications</seeguide> in section Running Tests and Analyzing Results).
Connections provided with start flag/option <c>silent_connections</c>
are merged with any connections listed in the test specification.</p>
diff --git a/lib/common_test/doc/src/specs.xml b/lib/common_test/doc/src/specs.xml
index 7e40e8351d..def8f761a5 100644
--- a/lib/common_test/doc/src/specs.xml
+++ b/lib/common_test/doc/src/specs.xml
@@ -2,4 +2,5 @@
<specs xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="../specs/specs_ct_netconfc.xml"/>
<xi:include href="../specs/specs_ct.xml"/>
+ <xi:include href="../specs/specs_ct_suite.xml"/>
</specs>
diff --git a/lib/common_test/doc/src/test_structure_chapter.xml b/lib/common_test/doc/src/test_structure_chapter.xml
index 3ffaa623c3..e041376d7f 100644
--- a/lib/common_test/doc/src/test_structure_chapter.xml
+++ b/lib/common_test/doc/src/test_structure_chapter.xml
@@ -56,12 +56,12 @@
<item>Using <c>skip_suites</c> and <c>skip_cases</c>
terms in
- <seealso marker="run_test_chapter#test_specifications">test specifications</seealso>.
+ <seeguide marker="run_test_chapter#test_specifications">test specifications</seeguide>.
</item>
<item>Returning <c>{skip,Reason}</c> from function
- <seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso> or
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite/1</c></seealso>.</item>
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa> or
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>.</item>
<item>Returning <c>{skip,Reason}</c> from the execution clause
of the test case. The execution clause is called, so the author
@@ -190,7 +190,7 @@
<item>
<p>The status of a test case explicitly skipped in any of
the ways described in section
- <seealso marker="#skipping_test_cases">Skipping Test Cases</seealso>.
+ <seeguide marker="#skipping_test_cases">Skipping Test Cases</seeguide>.
</p>
</item>
diff --git a/lib/common_test/doc/src/unix_telnet.xml b/lib/common_test/doc/src/unix_telnet.xml
index 2441e44f0a..d9905aec32 100644
--- a/lib/common_test/doc/src/unix_telnet.xml
+++ b/lib/common_test/doc/src/unix_telnet.xml
@@ -39,7 +39,7 @@
<description>
<p>Callback module for
- <seealso marker="ct_telnet"><c>ct_telnet</c></seealso>,
+ <seeerl marker="ct_telnet"><c>ct_telnet</c></seeerl>,
for connecting to a Telnet server on a UNIX host.</p>
<p>It requires the following entry in the configuration file:</p>
@@ -53,7 +53,7 @@
<p>To communicate through Telnet to the host specified by
<c>HostNameOrIpAddress</c>, use the interface functions in
- <seealso marker="ct_telnet"><c>ct_telnet</c></seealso>, for example,
+ <seeerl marker="ct_telnet"><c>ct_telnet</c></seeerl>, for example,
<c>open(Name)</c> and <c>cmd(Name,Cmd)</c>.</p>
<p><c>Name</c> is the name you allocated to the Unix host in your
@@ -71,7 +71,7 @@
to the server every 10 seconds if the connection is idle) can be
enabled or disabled for one particular connection as described here.
It can be disabled for all connections using <c>telnet_settings</c>
- (see <seealso marker="ct_telnet"><c>ct_telnet</c></seealso>).</p>
+ (see <seeerl marker="ct_telnet"><c>ct_telnet</c></seeerl>).</p>
<p>The <c>{port,PortNum}</c> tuple is optional and if omitted, default
Telnet port 23 is used. Also the <c>keep_alive</c> tuple is optional,
@@ -101,8 +101,8 @@
<p>Setup Telnet connection to a Unix host.</p>
<p>For <c>target_name()</c>, see
- <seealso marker="ct"><c>ct</c></seealso>. For <c>handle()</c>, see
- <seealso marker="ct_telnet"><c>ct_telnet</c></seealso>.</p>
+ <seeerl marker="ct"><c>ct</c></seeerl>. For <c>handle()</c>, see
+ <seeerl marker="ct_telnet"><c>ct_telnet</c></seeerl>.</p>
</desc>
</func>
@@ -119,15 +119,15 @@
for users on Unix hosts.</p>
<p>For <c>prompt_regexp()</c>, see
- <seealso marker="ct_telnet"><c>ct_telnet</c></seealso>.</p>
+ <seeerl marker="ct_telnet"><c>ct_telnet</c></seeerl>.</p>
</desc>
</func>
</funcs>
<section>
<title>See Also</title>
- <p><seealso marker="ct"><c>ct</c></seealso>,
- <seealso marker="ct_telnet"><c>ct_telnet</c></seealso></p>
+ <p><seeerl marker="ct"><c>ct</c></seeerl>,
+ <seeerl marker="ct_telnet"><c>ct_telnet</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index 8de2377fe6..661ba04fe8 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -34,7 +34,7 @@
<marker id="intro"></marker>
<title>Support for Test Suite Authors</title>
- <p>The <seealso marker="ct"><c>ct</c></seealso> module provides the main
+ <p>The <seeerl marker="ct"><c>ct</c></seeerl> module provides the main
interface for writing test cases. This includes for example, the following:</p>
<list type="bulleted">
@@ -44,7 +44,7 @@
<item>Function for adding comments to the HTML overview page</item>
</list>
- <p>For details about these functions, see module <seealso marker="ct"><c>ct</c></seealso>.</p>
+ <p>For details about these functions, see module <seeerl marker="ct"><c>ct</c></seeerl>.</p>
<p>The <c>Common Test</c> application also includes other modules named
<c><![CDATA[ct_<component>]]></c>, which
@@ -67,14 +67,14 @@
</p>
<p>Each test suite module must export function
- <seealso marker="common_test#Module:all-0"><c>all/0</c></seealso>,
+ <seemfa marker="ct_suite#Module:all/0"><c>all/0</c></seemfa>,
which returns the list of all test case groups and test cases
to be executed in that module.
</p>
<p>The callback functions to be implemented by the test suite are
- all listed in module <seealso marker="common_test">common_test
- </seealso>. They are also described in more detail later in this User's Guide.
+ all listed in module <seeerl marker="common_test">common_test
+ </seeerl>. They are also described in more detail later in this User's Guide.
</p>
</section>
@@ -83,8 +83,8 @@
<title>Init and End per Suite</title>
<p>Each test suite module can contain the optional configuration functions
- <seealso marker="common_test#Module:init_per_suite-1"><c>init_per_suite/1</c></seealso>
- and <seealso marker="common_test#Module:end_per_suite-1"><c>end_per_suite/1</c></seealso>.
+ <seemfa marker="ct_suite#Module:init_per_suite/1"><c>init_per_suite/1</c></seemfa>
+ and <seemfa marker="ct_suite#Module:end_per_suite/1"><c>end_per_suite/1</c></seemfa>.
If the init function is defined, so must the end function be.
</p>
@@ -129,7 +129,7 @@
in the suite, <c>Common Test</c> calls dummy functions (with the same names)
instead, so that output generated by hook functions can be saved to the log
files for these dummies. For details, see
- <seealso marker="ct_hooks_chapter#manipulating">Common Test Hooks</seealso>.
+ <seeguide marker="ct_hooks_chapter#manipulating">Common Test Hooks</seeguide>.
</p>
</section>
@@ -138,8 +138,8 @@
<title>Init and End per Test Case</title>
<p>Each test suite module can contain the optional configuration functions
- <seealso marker="common_test#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso>
- and <seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase/2</c></seealso>.
+ <seemfa marker="ct_suite#Module:init_per_testcase/2"><c>init_per_testcase/2</c></seemfa>
+ and <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase/2</c></seemfa>.
If the init function is defined, so must the end function be.</p>
<p>If <c>init_per_testcase</c> exists, it is called before each
@@ -164,7 +164,7 @@
<p>The return value of <c>end_per_testcase/2</c> is ignored by the
test server, with exception of the
- <seealso marker="dependencies_chapter#save_config"><c>save_config</c></seealso>
+ <seeguide marker="dependencies_chapter#save_config"><c>save_config</c></seeguide>
and <c>fail</c> tuple.</p>
<p><c>end_per_testcase</c> can check if the test case was successful.
@@ -187,7 +187,7 @@
<p>Function <c>end_per_testcase/2</c> is even called if a
test case terminates because of a call to
- <seealso marker="ct#abort_current_testcase-1"><c>ct:abort_current_testcase/1</c></seealso>,
+ <seemfa marker="ct#abort_current_testcase/1"><c>ct:abort_current_testcase/1</c></seemfa>,
or after a timetrap time-out. However, <c>end_per_testcase</c>
then executes on a different process than the test case
function. In this situation, <c>end_per_testcase</c> cannot
@@ -253,15 +253,15 @@
<p>The test case function takes one argument, <c>Config</c>, which
contains configuration information such as <c>data_dir</c> and
<c>priv_dir</c>. (For details about these, see section
- <seealso marker="#data_priv_dir">Data and Private Directories</seealso>.
+ <seeguide marker="#data_priv_dir">Data and Private Directories</seeguide>.
The value of <c>Config</c> at the time of the call, is the same
as the return value from <c>init_per_testcase</c>, mentioned earlier.
</p>
<note><p>The test case function argument <c>Config</c> is not to be
confused with the information that can be retrieved from the
- configuration files (using <seealso marker="ct#get_config-1"><c>
- ct:get_config/1/2</c></seealso>). The test case argument <c>Config</c>
+ configuration files (using <seemfa marker="ct#get_config/1"><c>
+ ct:get_config/1/2</c></seemfa>). The test case argument <c>Config</c>
is to be used for runtime configuration of the test suite and the
test cases, while configuration files are to contain data
related to the SUT. These two types of configuration data are handled
@@ -269,10 +269,10 @@
<p>As parameter <c>Config</c> is a list of key-value tuples, that is,
a data type called a property list, it can be handled by the
- <seealso marker="stdlib:proplists"><c>proplists</c></seealso> module.
+ <seeerl marker="stdlib:proplists"><c>proplists</c></seeerl> module.
A value can, for example, be searched for and returned with function
- <seealso marker="stdlib:proplists#get_value-2"><c>proplists:get_value/2</c></seealso>.
- Also, or alternatively, the general <seealso marker="stdlib:lists"><c>lists</c></seealso>
+ <seemfa marker="stdlib:proplists#get_value/2"><c>proplists:get_value/2</c></seemfa>.
+ Also, or alternatively, the general <seeerl marker="stdlib:lists"><c>lists</c></seeerl>
module contains useful functions. Normally, the only operations
performed on <c>Config</c> is insert (adding a tuple to the head of the list)
and lookup. <c>Common Test</c> provides a simple macro named <c>?config</c>,
@@ -289,7 +289,7 @@
<p>If the test case returns the tuple <c>{comment,Comment}</c>, the case
is considered successful and <c>Comment</c> is printed in the overview
log file. This is equal to calling
- <seealso marker="ct#comment-1"><c>ct:comment(Comment)</c></seealso>.
+ <seemfa marker="ct#comment/1"><c>ct:comment(Comment)</c></seemfa>.
</p>
</section>
@@ -314,7 +314,7 @@
reason <c>timetrap_timeout</c>. Notice that <c>init_per_testcase</c>
and <c>end_per_testcase</c> are included in the timetrap time.
For details, see section
- <seealso marker="write_test_chapter#timetraps">Timetrap Time-Outs</seealso>.
+ <seeguide marker="write_test_chapter#timetraps">Timetrap Time-Outs</seeguide>.
</p>
</item>
<tag><c>userdata</c></tag>
@@ -322,7 +322,7 @@
<p>
Specifies any data related to the test case. This
data can be retrieved at any time using the
- <seealso marker="ct#userdata-3"><c>ct:userdata/3</c></seealso>
+ <seemfa marker="ct#userdata/3"><c>ct:userdata/3</c></seemfa>
utility function.
</p>
</item>
@@ -330,7 +330,7 @@
<item>
<p>
For details, see section
- <seealso marker="run_test_chapter#silent_connections">Silent Connections</seealso>.
+ <seeguide marker="run_test_chapter#silent_connections">Silent Connections</seeguide>.
</p>
</item>
<tag><c>require</c></tag>
@@ -367,10 +367,10 @@
</taglist>
<p>For more information about <c>require</c>, see section
- <seealso marker="config_file_chapter#require_config_data">
- Requiring and Reading Configuration Data</seealso>
+ <seeguide marker="config_file_chapter#require_config_data">
+ Requiring and Reading Configuration Data</seeguide>
in section External Configuration Data and function
- <seealso marker="ct#require-1"><c>ct:require/1/2</c></seealso>.</p>
+ <seemfa marker="ct#require/1"><c>ct:require/1/2</c></seemfa>.</p>
<note><p>Specifying a default value for a required variable can result
in a test case always getting executed. This might not be a desired behavior.</p>
@@ -378,7 +378,7 @@
<p>If <c>timetrap</c> or <c>require</c>, or both, is not set specifically for
a particular test case, default values specified by function
- <seealso marker="common_test#Module:suite-0"><c>suite/0</c></seealso>
+ <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>
are used.
</p>
@@ -404,24 +404,24 @@
<marker id="suite"></marker>
<title>Test Suite Information Function</title>
- <p>Function <seealso marker="common_test#Module:suite-0"><c>suite/0</c></seealso>
+ <p>Function <seemfa marker="ct_suite#Module:suite/0"><c>suite/0</c></seemfa>
can, for example, be used in a test suite module to set a default
<c>timetrap</c> value and to <c>require</c> external configuration data.
If a test case, or a group information function also specifies any of the information tags, it
overrides the default values set by <c>suite/0</c>. For details,
see
- <seealso marker="#info_function">Test Case Information Function</seealso> and
- <seealso marker="#test_case_groups">Test Case Groups</seealso>.
+ <seeguide marker="#info_function">Test Case Information Function</seeguide> and
+ <seeguide marker="#test_case_groups">Test Case Groups</seeguide>.
</p>
<p>The following options can also be specified with the suite information list:</p>
<list type="bulleted">
<item><c>stylesheet</c>,
- see <seealso marker="run_test_chapter#html_stylesheet">HTML Style Sheets</seealso></item>
+ see <seeguide marker="run_test_chapter#html_stylesheet">HTML Style Sheets</seeguide></item>
<item><c>userdata</c>,
- see <seealso marker="#info_function">Test Case Information Function</seealso></item>
+ see <seeguide marker="#info_function">Test Case Information Function</seeguide></item>
<item><c>silent_connections</c>,
- see <seealso marker="run_test_chapter#silent_connections">Silent Connections</seealso></item>
+ see <seeguide marker="run_test_chapter#silent_connections">Silent Connections</seeguide></item>
</list>
<p>
@@ -445,7 +445,7 @@
<p>A test case group is a set of test cases sharing configuration
functions and execution properties. Test case groups are defined by
function
- <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso>
+ <seemfa marker="ct_suite#Module:groups/0"><c>groups/0</c></seemfa>
according to the following syntax:</p>
<pre>
groups() -> GroupDefs
@@ -479,7 +479,7 @@
<item><p><c>Common Test</c> executes all test cases in the group in parallel.</p></item>
<tag><c>sequence</c></tag>
<item><p>The cases are executed in a sequence as described in section
- <seealso marker="dependencies_chapter#sequences">Sequences</seealso> in section
+ <seeguide marker="dependencies_chapter#sequences">Sequences</seeguide> in section
Dependencies Between Test Cases and Suites.</p></item>
<tag><c>shuffle</c></tag>
<item><p>The cases in the group are executed in random order.</p></item>
@@ -551,8 +551,8 @@
<p>The described syntax can also be used in test specifications
to change group properties at the time of execution,
without having to edit the test suite. For more information, see
- section <seealso marker="run_test_chapter#test_specifications">Test
- Specifications</seealso> in section Running Tests and Analyzing Results.</p>
+ section <seeguide marker="run_test_chapter#test_specifications">Test
+ Specifications</seeguide> in section Running Tests and Analyzing Results.</p>
<p>As illustrated, properties can be combined. If, for example,
<c>shuffle</c>, <c>repeat_until_any_fail</c>, and <c>sequence</c>
@@ -561,12 +561,12 @@
execution is immediately stopped and the remaining cases are skipped.</p>
<p>Before execution of a group begins, the configuration function
- <seealso marker="common_test#Module:init_per_group-2"><c>init_per_group(GroupName, Config)</c></seealso>
+ <seemfa marker="ct_suite#Module:init_per_group/2"><c>init_per_group(GroupName, Config)</c></seemfa>
is called. The list of tuples returned from this function is passed to the
test cases in the usual manner by argument <c>Config</c>.
<c>init_per_group/2</c> is meant to be used for initializations common
for the test cases in the group. After execution of the group is finished, function
- <seealso marker="common_test#Module:end_per_group-2"><c>end_per_group(GroupName, Config)</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_group/2"><c>end_per_group(GroupName, Config)</c></seemfa>
is called. This function is meant to be used for cleaning up after
<c>init_per_group/2</c>. If the init function is defined, so must the end function be.</p>
@@ -575,7 +575,7 @@
dummy functions (with the same names) instead. Output generated by
hook functions are saved to the log files for these dummies.
For more information, see section
- <seealso marker="ct_hooks_chapter#manipulating">Manipulating Tests</seealso>
+ <seeguide marker="ct_hooks_chapter#manipulating">Manipulating Tests</seeguide>
in section Common Test Hooks.
</p>
@@ -654,7 +654,7 @@
<title>Parallel Test Cases and I/O</title>
<p>A parallel test case has a private I/O server as its group leader.
(For a description of the group leader concept, see
- <seealso marker="erts:index">ERTS</seealso>).
+ <seeapp marker="erts:index">ERTS</seeapp>).
The central I/O server process, which handles the output from
regular test cases and configuration functions, does not respond to I/O messages
during execution of parallel groups. This is important to understand
@@ -775,7 +775,7 @@
suite information function, and can in turn be overridden by test
case information properties. For a list of valid information properties
and more general information, see the
- <seealso marker="#info_function">Test Case Information Function</seealso>.
+ <seeguide marker="#info_function">Test Case Information Function</seeguide>.
</p>
</section>
@@ -784,7 +784,7 @@
<p>Information functions can also be used for functions <c>init_per_suite</c>,
<c>end_per_suite</c>, <c>init_per_group</c>, and <c>end_per_group</c>,
and they work the same way as with the
- <seealso marker="#info_function">Test Case Information Function</seealso>.
+ <seeguide marker="#info_function">Test Case Information Function</seeguide>.
This is useful, for example, for setting timetraps and requiring
external configuration data relevant only for the configuration
function in question (without affecting properties set for groups
@@ -799,7 +799,7 @@
and use the same properties as the test case (that is, the properties
set by the test case information function, <c>TestCase()</c>). For a list
of valid information properties and more general information, see the
- <seealso marker="#info_function">Test Case Information Function</seealso>.
+ <seeguide marker="#info_function">Test Case Information Function</seeguide>.
</p>
</section>
@@ -833,8 +833,8 @@
configured to create one dedicated private directory per
test case and execution instead. This is accomplished with
the flag/option <c>create_priv_dir</c> (to be used with the
- <seealso marker="ct_run"><c>ct_run</c></seealso> program, the
- <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso> function, or
+ <seecom marker="ct_run"><c>ct_run</c></seecom> program, the
+ <seemfa marker="ct#run_test/1"><c>ct:run_test/1</c></seemfa> function, or
as test specification term). There are three possible values
for this option as follows:
</p>
@@ -853,7 +853,7 @@
Therefore, if the manual version is used instead, the test case must tell
<c>Common Test</c> to create <c>priv_dir</c> when it needs it.
It does this by calling the function
- <seealso marker="ct#make_priv_dir-0"><c>ct:make_priv_dir/0</c></seealso>.
+ <seemfa marker="ct#make_priv_dir/0"><c>ct:make_priv_dir/0</c></seemfa>.
</p>
<note><p>Do not depend on the current working directory for
@@ -902,7 +902,7 @@
<p>A timetrap can also be set or reset dynamically during the
execution of a test case, or configuration function.
This is done by calling
- <seealso marker="ct#timetrap-1"><c>ct:timetrap/1</c></seealso>.
+ <seemfa marker="ct#timetrap/1"><c>ct:timetrap/1</c></seemfa>.
This function cancels the current timetrap and starts a new one
(that stays active until time-out, or end of the current function).</p>
@@ -916,13 +916,13 @@
<p>If a test case needs to suspend itself for a time that also gets
multipled by <c>multiply_timetraps</c> (and possibly also scaled up if
<c>scale_timetraps</c> is enabled), the function
- <seealso marker="ct#sleep-1"><c>ct:sleep/1</c></seealso>
+ <seemfa marker="ct#sleep/1"><c>ct:sleep/1</c></seemfa>
can be used (instead of, for example, <c>timer:sleep/1</c>).</p>
<p>A function (<c>fun/0</c> or <c>{Mod,Func,Args}</c> (MFA) tuple) can be
specified as timetrap value in the suite-, group- and test case information
function, and as argument to function
- <seealso marker="ct#timetrap-1"><c>ct:timetrap/1</c></seealso>.</p>
+ <seemfa marker="ct#timetrap/1"><c>ct:timetrap/1</c></seemfa>.</p>
<p><em>Examples:</em></p>
<p><c>{timetrap,{my_test_utils,timetrap,[?MODULE,system_start]}}</c></p>
@@ -940,7 +940,7 @@
When the timetrap function returns, the time-out is triggered, <em>unless</em>
the return value is a valid timetrap time, such as an integer,
or a <c>{SecMinOrHourTag,Time}</c> tuple (for details, see module
- <seealso marker="common_test">common_test</seealso>). If a time value
+ <seeerl marker="common_test">common_test</seeerl>). If a time value
is returned, a new timetrap is started to generate a time-out after
the specified time.</p>
@@ -959,13 +959,13 @@
<item><c>ct:print(Category, Importance, Format, FormatArgs)</c></item>
<item><c>ct:pal(Category, Importance, Format, FormatArgs)</c></item>
</list>
- <p>The <seealso marker="ct#log-1"><c>log/1,2,3,4,5</c></seealso> function
+ <p>The <seemfa marker="ct#log/1"><c>log/1,2,3,4,5</c></seemfa> function
prints a string to the test case log file.
- The <seealso marker="ct#print-1"><c>print/1,2,3,4</c></seealso> function
+ The <seemfa marker="ct#print/1"><c>print/1,2,3,4</c></seemfa> function
prints the string to screen.
- The <seealso marker="ct#pal-1"><c>pal/1,2,3,4</c></seealso> function
+ The <seemfa marker="ct#pal/1"><c>pal/1,2,3,4</c></seemfa> function
prints the same string both to file and screen. The functions are described
- in module <seealso marker="ct">ct</seealso>.
+ in module <seeerl marker="ct">ct</seeerl>.
</p>
<p>The optional <c>Category</c> argument can be used to categorize the
@@ -1047,13 +1047,13 @@ ct:pal(?LOW_IMPORTANCE, "Info report: ~p", [Info])</pre>
ct:log(?INFO, "Info report: ~p", [Info])
ct:pal(?ERROR, "Error report: ~p", [Error])</pre>
- <p>The functions <seealso marker="ct#set_verbosity-2"><c>ct:set_verbosity/2</c></seealso>
- and <seealso marker="ct#get_verbosity-1"><c>ct:get_verbosity/1</c></seealso> may be used
+ <p>The functions <seemfa marker="ct#set_verbosity/2"><c>ct:set_verbosity/2</c></seemfa>
+ and <seemfa marker="ct#get_verbosity/1"><c>ct:get_verbosity/1</c></seemfa> may be used
to modify and read verbosity levels during test execution.</p>
<p>The arguments <c>Format</c> and <c>FormatArgs</c> in <c>ct:log/print/pal</c> are
always passed on to the STDLIB function <c>io:format/3</c> (For details,
- see the <seealso marker="stdlib:io"><c>io</c></seealso> manual page).</p>
+ see the <seeerl marker="stdlib:io"><c>io</c></seeerl> manual page).</p>
<p><c>ct:pal/4</c> and <c>ct:log/5</c> add headers to strings being printed to the
log file. The strings are also wrapped in div tags with a CSS class
@@ -1062,15 +1062,15 @@ ct:pal(?ERROR, "Error report: ~p", [Error])</pre>
call <c>ct:log/5</c> with the <c>no_css</c> option.</p>
<p>How categories can be mapped to CSS tags is documented in section
- <seealso marker="run_test_chapter#html_stylesheet">HTML Style Sheets</seealso>
+ <seeguide marker="run_test_chapter#html_stylesheet">HTML Style Sheets</seeguide>
in section Running Tests and Analyzing Results.</p>
<p>Common Test will escape special HTML characters (&lt;, &gt; and &amp;) in printouts
to the log file made with <c>ct:pal/4</c> and <c>io:format/2</c>. In order to print
strings with HTML tags to the log, use the <c>ct:log/3,4,5</c> function. The character
escaping feature is per default disabled for <c>ct:log/3,4,5</c> but can be enabled with
- the <c>esc_chars</c> option in the <c>Opts</c> list, see <seealso marker="ct#log-5">
- <c>ct:log/3,4,5</c></seealso>.</p>
+ the <c>esc_chars</c> option in the <c>Opts</c> list, see <seemfa marker="ct#log/5">
+ <c>ct:log/3,4,5</c></seemfa>.</p>
<p>If the character escaping feature needs to be disabled (typically for backwards
compatibility reasons), use the <c>ct_run</c> start flag <c>-no_esc_chars</c>, or the
@@ -1078,7 +1078,7 @@ ct:pal(?ERROR, "Error report: ~p", [Error])</pre>
supported in test specifications).</p>
<p>For more information about log files, see section
- <seealso marker="run_test_chapter#log_files">Log Files</seealso>
+ <seeguide marker="run_test_chapter#log_files">Log Files</seeguide>
in section Running Tests and Analyzing Results.</p>
</section>
@@ -1163,7 +1163,7 @@ ct:pal(?ERROR, "Error report: ~p", [Error])</pre>
environment as possible, so that subsequent test cases
do not crash because of their execution order.
The function
- <seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase</c></seealso>
+ <seemfa marker="ct_suite#Module:end_per_testcase/2"><c>end_per_testcase</c></seemfa>
is suitable for this.
</p>
</item>
diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile
index a20de14e0e..7d7b5ed203 100644
--- a/lib/common_test/src/Makefile
+++ b/lib/common_test/src/Makefile
@@ -40,6 +40,9 @@ RELSYSDIR = $(RELEASE_PATH)/lib/common_test-$(VSN)
# Target Specs
# ----------------------------------------------------
+BEHAVIOUR_MODULES= \
+ ct_suite
+
MODULES= \
ct \
ct_logs \
@@ -88,9 +91,13 @@ MODULES= \
TARGET_MODULES= $(MODULES:%=$(EBIN)/%)
-BEAM_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+BEAM_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) \
+ $(BEHAVIOUR_MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+ERL_FILES= \
+ $(MODULES:=.erl) \
+ $(BEHAVIOUR_MODULES:%=%.erl)
-ERL_FILES= $(MODULES:=.erl)
HRL_FILES = \
ct_util.hrl \
ct_netconfc.hrl
diff --git a/lib/common_test/src/common_test.app.src b/lib/common_test/src/common_test.app.src
index 07b35b7180..8aa15efa7e 100644
--- a/lib/common_test/src/common_test.app.src
+++ b/lib/common_test/src/common_test.app.src
@@ -63,7 +63,8 @@
test_server_gl,
test_server_io,
test_server_node,
- test_server_sup
+ test_server_sup,
+ ct_suite
]},
{registered, [ct_logs,
ct_util_server,
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index a07e61199b..adb90c0869 100644
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -24,6 +24,8 @@
%% Created : 15 February 2010
%%----------------------------------------------------------------------
-module(ct_config).
+-compile([{nowarn_deprecated_function,{crypto,block_decrypt,4}},
+ {nowarn_deprecated_function,{crypto,block_encrypt,4}}]).
-export([start/1, stop/0]).
@@ -600,7 +602,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
0 -> Bin1;
N -> list_to_binary([Bin1,random_bytes(8-N)])
end,
- EncBin = crypto:block_encrypt(des3_cbc, CryptoKey, IVec, Bin2),
+ EncBin = crypto:crypto_one_time(des_ede3_cbc, CryptoKey, IVec, Bin2, true),
case file:write_file(EncryptFileName, EncBin) of
ok ->
io:format("~ts --(encrypt)--> ~ts~n",
@@ -634,7 +636,7 @@ decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) ->
{CryptoKey,IVec} = make_crypto_key(Key),
case file:read_file(EncryptFileName) of
{ok,Bin} ->
- DecBin = crypto:block_decrypt(des3_cbc, CryptoKey, IVec, Bin),
+ DecBin = crypto:crypto_one_time(des_ede3_cbc, CryptoKey, IVec, Bin, false),
case catch binary_to_term(DecBin) of
{'EXIT',_} ->
{error,bad_file};
@@ -706,7 +708,8 @@ get_crypt_key_from_file() ->
make_crypto_key(String) ->
<<K1:8/binary,K2:8/binary>> = First = erlang:md5(String),
<<K3:8/binary,IVec:8/binary>> = erlang:md5([First|lists:reverse(String)]),
- {[K1,K2,K3],IVec}.
+ Key = <<K1/binary,K2/binary,K3/binary>>,
+ {Key,IVec}.
random_bytes(N) ->
random_bytes_1(N, []).
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index 403edd8f4d..41e9734731 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -816,7 +816,7 @@ end_tc(Mod,Func00,TCPid,Result,Args,Return) ->
lists:keydelete(Func,2,Running);
({_,{suite0_failed,_}}) ->
undefined;
- ([{_,CurrTC}]) when CurrTC == Func ->
+ ([{_,_}]) ->
undefined;
(undefined) ->
undefined;
diff --git a/lib/common_test/src/ct_property_test.erl b/lib/common_test/src/ct_property_test.erl
index 0d1025e08c..b94cc15374 100644
--- a/lib/common_test/src/ct_property_test.erl
+++ b/lib/common_test/src/ct_property_test.erl
@@ -23,8 +23,8 @@
%%% API
%% Main functions
-export([init_per_suite/1,
- quickcheck/2
- ]).
+ init_tool/1,
+ quickcheck/2]).
%% Result presentation
-export([present_result/4, present_result/5,
@@ -50,34 +50,40 @@
%%% the property tests
%%%
init_per_suite(Config) ->
+ case init_tool(Config) of
+ {skip, _}=Skip ->
+ Skip;
+ Config1 ->
+ Path = property_tests_path("property_test", Config1),
+ case compile_tests(Path, Config1) of
+ error ->
+ {fail, "Property test compilation failed in "++Path};
+ {skip,Reason} ->
+ {skip,Reason};
+ up_to_date ->
+ add_code_pathz(Path),
+ [{property_dir, Path} | Config1]
+ end
+ end.
+
+init_tool(Config) ->
ToolsToCheck = proplists:get_value(prop_tools, Config, [eqc,proper,triq]),
case which_module_exists(ToolsToCheck) of
{ok,ToolModule} ->
case code:where_is_file(lists:concat([ToolModule,".beam"])) of
non_existing ->
- ct:log("Found ~p, but ~tp is not found",
+ ct:log("Found ~p, but ~tp~n is not found",
[ToolModule, lists:concat([ToolModule,".beam"])]),
{skip, "Strange Property testing tool installation"};
ToolPath ->
ct:pal("Found property tester ~p~n"
"at ~tp",
[ToolModule, ToolPath]),
- Path = property_tests_path("property_test", Config),
- case compile_tests(Path,ToolModule) of
- error ->
- {fail, "Property test compilation failed in "++Path};
- {skip,Reason} ->
- {skip,Reason};
- up_to_date ->
- add_code_pathz(Path),
- [{property_dir,Path},
- {property_test_tool,ToolModule} | Config]
- end
+ [{property_test_tool, ToolModule} | Config]
end;
-
- not_found ->
- ct:pal("No property tester found",[]),
- {skip, "No property testing tool found"}
+ not_found ->
+ ct:pal("No property tester found",[]),
+ {skip, "No property testing tool found"}
end.
%%%----------------------------------------------------------------
@@ -189,7 +195,8 @@ add_code_pathz(Dir) ->
ok
end.
-compile_tests(Path, ToolModule) ->
+compile_tests(Path, Config) ->
+ ToolModule = proplists:get_value(property_test_tool, Config),
MacroDefs = macro_def(ToolModule),
{ok,Cwd} = file:get_cwd(),
case file:set_cwd(Path) of
diff --git a/lib/common_test/src/ct_suite.erl b/lib/common_test/src/ct_suite.erl
new file mode 100644
index 0000000000..a2d23e15ef
--- /dev/null
+++ b/lib/common_test/src/ct_suite.erl
@@ -0,0 +1,130 @@
+-module(ct_suite).
+
+%%------------------------------------------------------------------
+%% Test Suite Behaviour
+%% ------------------------------------------------------------------
+-export_type([ct_testname/0,
+ ct_groupname/0,
+ ct_config/0,
+ ct_status/0,
+ ct_group_def/0,
+ ct_test_def/0,
+ ct_info/0
+ ]).
+
+-type ct_testname() :: atom().
+-type ct_groupname() :: atom().
+-type ct_config() :: [{Key :: atom(), Value :: term()}].
+-type ct_status() :: ok |
+ skipped |
+ failed.
+-type ct_group_props() :: [
+ parallel |
+ sequence |
+ shuffle |
+ {shuffle, Seed :: {integer(), integer(), integer()}} |
+ {ct_group_repeat_type(), ct_test_repeat()}
+ ].
+-type ct_group_props_ref() ::
+ ct_group_props() |
+ default.
+-type ct_group_repeat_type() :: repeat |
+ repeat_until_all_ok |
+ repeat_until_all_fail |
+ repeat_until_any_ok |
+ repeat_until_any_fail.
+-type ct_test_repeat() :: integer() |
+ forever.
+-type ct_group_def() :: {ct_groupname(), ct_group_props(), [
+ ct_testname() |
+ ct_group_def() |
+ {group, ct_groupname()} |
+ ct_testcase_ref()
+ ]}.
+-type ct_subgroups_def() :: {ct_groupname(), ct_group_props_ref()} |
+ {ct_groupname(), ct_group_props_ref(), ct_subgroups_def()}.
+-type ct_group_ref() :: {group, ct_groupname()} |
+ {group, ct_groupname(), ct_group_props_ref()} |
+ {group, ct_groupname(), ct_group_props_ref(), ct_subgroups_def()}.
+-type ct_testcase_ref() :: {testcase, ct_testname(), ct_testcase_repeat_prop()}.
+-type ct_testcase_repeat_prop() :: {repeat, ct_test_repeat()} |
+ {repeat_until_ok, ct_test_repeat()} |
+ {repeat_until_fail, ct_test_repeat()}.
+-type ct_info() :: {timetrap, ct_info_timetrap()} |
+ {require, ct_info_required()} |
+ {require, Name :: atom(), ct_info_required()} |
+ {userdata, UserData :: term()} |
+ {silent_connections, Conns :: [atom()]} |
+ {stylesheet, CSSFile :: string()} |
+ {ct_hooks, CTHs :: ct_hooks()}.
+-type ct_info_timetrap() :: MilliSec :: integer() |
+ {seconds, integer()} |
+ {minutes, integer()} |
+ {hours, integer()} |
+ {Mod :: atom(), Func :: atom(), Args :: list()} |
+ ct_info_timetrap_fun().
+-type ct_info_timetrap_fun() :: fun().
+-type ct_info_required() :: Key :: atom() |
+ {Key :: atom(), SubKeys :: ct_info_required_subkeys()} |
+ {Key :: atom(), SubKey :: atom()} |
+ {Key :: atom(), SubKey :: atom(), SubKeys :: ct_info_required_subkeys()}.
+-type ct_info_required_subkeys() :: SubKey :: atom() |
+ [SubKey :: atom()].
+-type ct_hooks() :: [
+ CTHModule :: atom() |
+ {CTHModule :: atom(), CTHInitArgs :: term()} |
+ {CTHModule :: atom(), CTHInitArgs :: term(), CTHPriority :: integer()}
+ ].
+-type ct_test_def() :: ct_testname() | ct_group_ref() | ct_testcase_ref().
+
+-callback all() ->
+ [TestDef :: ct_test_def()] |
+ {skip, Reason :: term()}.
+
+-callback groups() ->
+ [GroupDef :: ct_group_def()].
+
+-callback suite() ->
+ [Info :: ct_info()].
+
+-callback init_per_suite(Config :: ct_config()) ->
+ NewConfig :: ct_config() |
+ {skip, Reason :: term()} |
+ {skip_and_save, Reason :: term(), SaveConfig :: ct_config()}.
+
+-callback end_per_suite(Config :: ct_config()) ->
+ term() |
+ {save_config, SaveConfig :: ct_config()}.
+
+-callback group(GroupName :: ct_groupname()) ->
+ [Info :: ct_info()].
+
+-callback init_per_group(GroupName :: ct_groupname(), Config :: ct_config()) ->
+ NewConfig :: ct_config() |
+ {skip, Reason :: term()}.
+
+-callback end_per_group(GroupName :: ct_groupname(), Config :: ct_config()) ->
+ term() |
+ {return_group_result, Status :: ct_status()}.
+
+-callback init_per_testcase(TestCase :: ct_testname(), Config :: ct_config()) ->
+ NewConfig :: ct_config() |
+ {fail, Reason :: term()} |
+ {skip, Reason :: term()}.
+
+-callback end_per_testcase(TestCase :: ct_testname(), Config :: ct_config()) ->
+ term() |
+ {fail, Reason :: term()} |
+ {save_config, SaveConfig :: ct_config()}.
+
+%% only all/0 is mandatory
+-optional_callbacks([groups/0,
+ suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ group/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl
index fe869a4373..9743611425 100644
--- a/lib/common_test/src/cth_log_redirect.erl
+++ b/lib/common_test/src/cth_log_redirect.erl
@@ -129,10 +129,16 @@ start_log_handler() ->
_Pid ->
ok
end,
+ {DefaultFormatter, DefaultLevel} =
+ case logger:get_handler_config(default) of
+ {ok, Default} ->
+ {maps:get(formatter, Default), maps:get(level, Default)};
+ _Else ->
+ {{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG},info}
+ end,
ok = logger:add_handler(?MODULE,?MODULE,
- #{level=>info,
- formatter=>{?DEFAULT_FORMATTER,
- ?DEFAULT_FORMAT_CONFIG}}).
+ #{level=>DefaultLevel,
+ formatter=>DefaultFormatter}).
init([]) ->
{ok, #eh_state{log_func = tc_log_async}}.
diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl
index 20c229dde4..b3cc2af145 100644
--- a/lib/common_test/src/test_server.erl
+++ b/lib/common_test/src/test_server.erl
@@ -21,7 +21,7 @@
-define(DEFAULT_TIMETRAP_SECS, 60).
%%% TEST_SERVER_CTRL INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--export([run_test_case_apply/1,init_target_info/0,init_valgrind/0]).
+-export([run_test_case_apply/1,init_target_info/0,init_memory_checker/0]).
-export([cover_compile/1,cover_analyse/2]).
%%% TEST_SERVER_SUP INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -48,10 +48,8 @@
-export([is_cover/0,is_debug/0,is_commercial/0]).
-export([break/1,break/2,break/3,continue/0,continue/1]).
+-export([memory_checker/0, is_valgrind/0, is_asan/0]).
-%%% DEBUGGER INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--export([valgrind_new_leaks/0, valgrind_format/2,
- is_valgrind/0]).
%%% PRIVATE EXPORTED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-export([]).
@@ -60,6 +58,7 @@
-include("test_server_internal.hrl").
-include_lib("kernel/include/file.hrl").
+
init_target_info() ->
[$.|Emu] = code:objfile_extension(),
{_, OTPRel} = init:script_id(),
@@ -73,8 +72,8 @@ init_target_info() ->
username=test_server_sup:get_username(),
cookie=atom_to_list(erlang:get_cookie())}.
-init_valgrind() ->
- valgrind_new_leaks().
+init_memory_checker() ->
+ check_memory_leaks().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -367,19 +366,50 @@ stick_all_sticky(Node,Sticky) ->
%% cover.
run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,TimetrapData}) ->
- case is_valgrind() of
- false ->
- ok;
- true ->
- valgrind_format("Test case #~w ~w:~w/1", [CaseNum, Mod, Func]),
- os:putenv("VALGRIND_LOGFILE_INFIX",atom_to_list(Mod)++"."++
- atom_to_list(Func)++"-")
- end,
+ MC = case {Func, memory_checker()} of
+ {init_per_suite, _} -> none; % skip init/end_per_suite/group
+ {init_per_group, _} -> none; % as CaseNum is always 0
+ {end_per_group, _} -> none;
+ {end_per_suite, _} -> none;
+ {_, valgrind} ->
+ valgrind_format("Test case #~w ~w:~w/1", [CaseNum, Mod, Func]),
+ os:putenv("VALGRIND_LOGFILE_INFIX",atom_to_list(Mod)++"."++
+ atom_to_list(Func)++"-"),
+ valgrind;
+ {_, asan} ->
+ %% Address sanitizer does not support printf in log file
+ %% but it lets us change the log file on the fly. So we use
+ %% that to give each test case its own log file.
+ case asan_take_logpath() of
+ false -> false;
+ {LogPath, OtherOpts} ->
+ LogDir = filename:dirname(LogPath),
+ LogFile = filename:basename(LogPath),
+ [Exe, App | _ ] = string:lexemes(LogFile, "-"),
+ NewLogFile = io_lib:format("~s-~s-tc-~4..0w-~w-~w",
+ [Exe,App,CaseNum, Mod, Func]),
+ NewLogPath = filename:join(LogDir, NewLogFile),
+
+ %% Do leak check and then change asan log file
+ %% for this running beam executable.
+ erlang:system_info({memory_checker, check_leaks}),
+ _PrevLog = erlang:system_info({memory_checker, log, NewLogPath}),
+
+ %% Set log file name for subnodes
+ %% that may be created by this test case
+ NewOpts = asan_make_opts(["log_path="++NewLogPath++".subnode"
+ | OtherOpts]),
+ os:putenv("ASAN_OPTIONS", NewOpts)
+ end,
+ asan;
+ {_, none} ->
+ node
+ end,
ProcBef = erlang:system_info(process_count),
Result = run_test_case_apply(Mod, Func, Args, Name, RunInit,
TimetrapData),
ProcAft = erlang:system_info(process_count),
- valgrind_new_leaks(),
+ check_memory_leaks(MC),
DetFail = get(test_server_detected_fail),
{Result,DetFail,ProcBef,ProcAft}.
@@ -2053,7 +2083,8 @@ timetrap_scale_factor() ->
{ 3, fun() -> has_superfluous_schedulers() end},
{ 6, fun() -> is_debug() end},
{10, fun() -> is_cover() end},
- {10, fun() -> is_valgrind() end}
+ {10, fun() -> is_valgrind() end},
+ {2, fun() -> is_asan() end}
]).
timetrap_scale_factor(Scales) ->
@@ -2962,22 +2993,36 @@ is_commercial() ->
%%
%% Returns true if valgrind is running, else false
is_valgrind() ->
- case catch erlang:system_info({valgrind, running}) of
- {'EXIT', _} -> false;
- Res -> Res
+ memory_checker() =:= valgrind.
+
+%% Returns true if address-sanitizer is running, else false
+is_asan() ->
+ memory_checker() =:= asan.
+
+%% Returns the error checker running (valgrind | asan | none).
+memory_checker() ->
+ case catch erlang:system_info({memory_checker, running}) of
+ {'EXIT', _} -> none;
+ EC -> EC
end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% DEBUGGER INTERFACE %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% valgrind_new_leaks() -> ok
+%% check_memory_leaks() -> ok
%%
-%% Checks for new memory leaks if Valgrind is active.
-valgrind_new_leaks() ->
- catch erlang:system_info({valgrind, memory}),
+%% Checks for memory leaks if Valgrind or Address-sanitizer is active.
+check_memory_leaks() ->
+ check_memory_leaks(memory_checker()).
+
+check_memory_leaks(valgrind) ->
+ catch erlang:system_info({memory_checker, check_leaks}),
+ ok;
+check_memory_leaks(_) ->
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -2987,9 +3032,31 @@ valgrind_new_leaks() ->
%%
%% Outputs the formatted string to Valgrind's logfile,if Valgrind is active.
valgrind_format(Format, Args) ->
- (catch erlang:system_info({valgrind, io_lib:format(Format, Args)})),
+ (catch erlang:system_info({memory_checker, print, io_lib:format(Format, Args)})),
ok.
+asan_take_logpath() ->
+ case os:getenv("ASAN_OPTIONS") of
+ false -> false;
+ S ->
+ Opts = string:lexemes(S, ":"),
+ asan_take_logpath_loop(Opts, [])
+ end.
+
+asan_take_logpath_loop(["log_path="++LogPath | T], Acc) ->
+ {LogPath, T ++ Acc};
+asan_take_logpath_loop([Opt | T], Acc) ->
+ asan_take_logpath_loop(T, [Opt | Acc]);
+asan_take_logpath_loop([], _) ->
+ false.
+
+asan_make_opts([A|T]) ->
+ asan_make_opts(T, A).
+
+asan_make_opts([], Acc) ->
+ Acc;
+asan_make_opts([A|T], Acc) ->
+ asan_make_opts(T, A ++ [$: | Acc]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl
index 995594dd59..dbd5537206 100644
--- a/lib/common_test/src/test_server_ctrl.erl
+++ b/lib/common_test/src/test_server_ctrl.erl
@@ -2195,7 +2195,7 @@ do_add_end_per_suite_and_skip(LastMod, LastRef, Mod, FwMod) ->
%% Runs the specified tests, then displays/logs the summary.
run_test_cases(TestSpec, Config, TimetrapData) ->
- test_server:init_valgrind(),
+ test_server:init_memory_checker(),
case lists:member(no_src, get(test_server_logopts)) of
true ->
ok;
diff --git a/lib/common_test/src/test_server_node.erl b/lib/common_test/src/test_server_node.erl
index 8deea353d7..edfb1fbd92 100644
--- a/lib/common_test/src/test_server_node.erl
+++ b/lib/common_test/src/test_server_node.erl
@@ -645,7 +645,38 @@ find_release(latest) ->
find_release(previous) ->
"kaka";
find_release(Rel) ->
- find_release(os:type(), Rel).
+ case find_release(os:type(), Rel) of
+ none ->
+ find_release_path(Rel);
+ Else ->
+ Else
+ end.
+
+find_release_path(Rel) ->
+ Paths = string:lexemes(os:getenv("PATH"), ":"),
+ find_release_path(Paths, Rel).
+find_release_path([Path|T], Rel) ->
+ case os:find_executable("erl", Path) of
+ false ->
+ find_release_path(T, Rel);
+ ErlExec ->
+ Pattern = filename:join([Path,"..","releases","*","OTP_VERSION"]),
+ case filelib:wildcard(Pattern) of
+ [VersionFile] ->
+ {ok, VsnBin} = file:read_file(VersionFile),
+ [MajorVsn|_] = string:lexemes(VsnBin, "."),
+ case unicode:characters_to_list(MajorVsn) of
+ Rel ->
+ ErlExec;
+ _Else ->
+ find_release_path(T, Rel)
+ end;
+ _Else ->
+ find_release_path(T, Rel)
+ end
+ end;
+find_release_path([], _) ->
+ none.
find_release({unix,sunos}, Rel) ->
case os:cmd("uname -p") of
diff --git a/lib/common_test/test/ct_hooks_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE.erl
index 83fbb78628..08f0c751dd 100644
--- a/lib/common_test/test/ct_hooks_SUITE.erl
+++ b/lib/common_test/test/ct_hooks_SUITE.erl
@@ -32,6 +32,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("common_test/include/ct_event.hrl").
+-include_lib("kernel/src/logger_internal.hrl").
-define(eh, ct_test_support_eh).
@@ -60,6 +61,13 @@ end_per_suite(Config) ->
init_per_testcase(TestCase, Config) ->
ct_test_support:init_per_testcase(TestCase, Config).
+
+end_per_testcase(cth_log_formatter = TestCase, Config) ->
+ ct_test_support:ct_rpc(
+ {logger,set_handler_config,
+ [default, formatter,
+ {?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}]}, Config),
+ ct_test_support:end_per_testcase(TestCase, Config);
end_per_testcase(TestCase, Config) ->
ct_test_support:end_per_testcase(TestCase, Config).
@@ -92,7 +100,8 @@ all(suite) ->
fail_n_skip_with_minimal_cth, prio_cth, no_config,
no_init_suite_config, no_init_config, no_end_config,
failed_sequence, repeat_force_stop, config_clash,
- callbacks_on_skip, fallback, data_dir, cth_log
+ callbacks_on_skip, fallback, data_dir,
+ cth_log, cth_log_formatter, cth_log_unexpect
]
).
@@ -263,10 +272,79 @@ data_dir(Config) when is_list(Config) ->
cth_log(Config) when is_list(Config) ->
%% test that cth_log_redirect writes properly to
- %% unexpected I/O log
+ %% html I/O log
ct:timetrap({minutes,10}),
StartOpts = do_test(cth_log, "cth_log_SUITE.erl", [], Config),
Logdir = proplists:get_value(logdir, StartOpts),
+ TCLogs =
+ filelib:wildcard(
+ filename:join(Logdir,
+ "ct_run*/cth.tests*/run*/cth_log_suite.tc*.html")),
+ lists:foreach(
+ fun(TCLog) ->
+ {ok,Bin} = file:read_file(TCLog),
+ Ts = string:lexemes(binary_to_list(Bin),[$\n]),
+ Matches = lists:foldl(fun("=ERROR"++_, {E,I,N,L}) ->
+ {E+1,I,N,L};
+ ("=INFO"++_, {E,I,N,L}) ->
+ {E,I+1,N,L};
+ ("=NOTICE"++_, {E,I,N,L}) ->
+ {E,I,N+1,L};
+ ("Logger"++_, {E,I,N,L}) ->
+ {E,I,N,L+1};
+ (_, N) -> N
+ end, {0,0,0,0}, Ts),
+ ct:pal("~p ({Error,Info,Notice,Log}) matches in ~tp",
+ [Matches,TCLog]),
+ MatchList = tuple_to_list(Matches),
+ case [N || N <- MatchList, N<1] of
+ [] -> ok;
+ _ -> exit({missing_io,TCLog})
+ end
+ end, TCLogs),
+ ok.
+
+cth_log_formatter(Config) when is_list(Config) ->
+ %% test that cth_log_redirect writes properly to
+ %% html I/O log using the formatter
+ ct:timetrap({minutes,10}),
+ ct_test_support:ct_rpc({logger,set_handler_config,[default, formatter,
+ {logger_formatter,#{ template => [level,":",msg,"\n"] }}]},
+ Config),
+ StartOpts = do_test(cth_log_formatter, "cth_log_formatter_SUITE.erl", [], Config),
+ Logdir = proplists:get_value(logdir, StartOpts),
+ TCLogs =
+ filelib:wildcard(
+ filename:join(Logdir,
+ "ct_run*/cth.tests*/run*/cth_log_formatter_suite.tc*.html")),
+ lists:foreach(
+ fun(TCLog) ->
+ {ok,Bin} = file:read_file(TCLog),
+ Ts = string:lexemes(binary_to_list(Bin),[$\n]),
+ Matches = lists:foldl(fun("error:"++_, {E,N,L}) ->
+ {E+1,N,L};
+ ("notice:"++_, {E,N,L}) ->
+ {E,N+1,L};
+ ("Logger"++_, {E,N,L}) ->
+ {E,N,L+1};
+ (_, N) -> N
+ end, {0,0,0}, Ts),
+ ct:pal("~p ({Error,Notice,Log}) matches in ~tp",
+ [Matches,TCLog]),
+ MatchList = tuple_to_list(Matches),
+ case [N || N <- MatchList, N<1] of
+ [] -> ok;
+ _ -> exit({missing_io,TCLog})
+ end
+ end, TCLogs),
+ ok.
+
+cth_log_unexpect(Config) when is_list(Config) ->
+ %% test that cth_log_redirect writes properly to
+ %% unexpected I/O log
+ ct:timetrap({minutes,10}),
+ StartOpts = do_test(cth_log_unexpect, "cth_log_unexpect_SUITE.erl", [], Config),
+ Logdir = proplists:get_value(logdir, StartOpts),
UnexpIoLogs =
filelib:wildcard(
filename:join(Logdir,
@@ -275,11 +353,11 @@ cth_log(Config) when is_list(Config) ->
fun(UnexpIoLog) ->
{ok,Bin} = file:read_file(UnexpIoLog),
Ts = string:lexemes(binary_to_list(Bin),[$\n]),
- Matches = lists:foldl(fun([$=,$E,$R,$R,$O,$R|_], {E,I,L}) ->
+ Matches = lists:foldl(fun("=ERROR"++_, {E,I,L}) ->
{E+1,I,L};
- ([$=,$I,$N,$F,$O|_], {E,I,L}) ->
+ ("=INFO"++_, {E,I,L}) ->
{E,I+1,L};
- ([$L,$o,$g,$g,$e,$r|_], {E,I,L}) ->
+ ("Logger"++_, {E,I,L}) ->
{E,I,L+1};
(_, N) -> N
end, {0,0,0}, Ts),
@@ -1824,18 +1902,58 @@ test_events(cth_log) ->
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
{?eh,tc_start,{cth_log_SUITE,init_per_suite}},
+ {?eh,tc_start,{ct_framework,{init_per_group,g1,
+ [{suite,cth_log_SUITE}]}}},
+ {?eh,tc_done,{ct_framework,{init_per_group,g1,
+ [{suite,cth_log_SUITE}]},ok}},
+ {?eh,test_stats,{30,0,{0,0}}},
+ {?eh,tc_start,{ct_framework,{end_per_group,g1,
+ [{suite,cth_log_SUITE}]}}},
+ {?eh,tc_done,{ct_framework,{end_per_group,g1,
+ [{suite,cth_log_SUITE}]},ok}},
+
+ {?eh,tc_done,{cth_log_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(cth_log_formatter) ->
+ [{?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,tc_start,{cth_log_formatter_SUITE,init_per_suite}},
+
+ {?eh,tc_start,{ct_framework,{init_per_group,g1,
+ [{suite,cth_log_formatter_SUITE}]}}},
+ {?eh,tc_done,{ct_framework,{init_per_group,g1,
+ [{suite,cth_log_formatter_SUITE}]},ok}},
+ {?eh,test_stats,{30,0,{0,0}}},
+ {?eh,tc_start,{ct_framework,{end_per_group,g1,
+ [{suite,cth_log_formatter_SUITE}]}}},
+ {?eh,tc_done,{ct_framework,{end_per_group,g1,
+ [{suite,cth_log_formatter_SUITE}]},ok}},
+
+ {?eh,tc_done,{cth_log_formatter_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(cth_log_unexpect) ->
+ [{?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,tc_start,{cth_log_unexpect_SUITE,init_per_suite}},
+
{parallel,
[{?eh,tc_start,{ct_framework,{init_per_group,g1,
- [{suite,cth_log_SUITE},parallel]}}},
+ [{suite,cth_log_unexpect_SUITE},parallel]}}},
{?eh,tc_done,{ct_framework,{init_per_group,g1,
- [{suite,cth_log_SUITE},parallel]},ok}},
+ [{suite,cth_log_unexpect_SUITE},parallel]},ok}},
{?eh,test_stats,{30,0,{0,0}}},
{?eh,tc_start,{ct_framework,{end_per_group,g1,
- [{suite,cth_log_SUITE},parallel]}}},
+ [{suite,cth_log_unexpect_SUITE},parallel]}}},
{?eh,tc_done,{ct_framework,{end_per_group,g1,
- [{suite,cth_log_SUITE},parallel]},ok}}]},
+ [{suite,cth_log_unexpect_SUITE},parallel]},ok}}]},
- {?eh,tc_done,{cth_log_SUITE,end_per_suite,ok}},
+ {?eh,tc_done,{cth_log_unexpect_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl
index eda190b682..0de234f77e 100644
--- a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl
@@ -41,8 +41,8 @@ suite() ->
%%--------------------------------------------------------------------
init_per_suite(Config) ->
application:start(sasl),
- Gen = spawn(fun() -> gen() end),
- [{gen,Gen}|Config].
+ do_log(?FUNCTION_NAME),
+ Config.
%%--------------------------------------------------------------------
%% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
@@ -50,9 +50,7 @@ init_per_suite(Config) ->
%% @end
%%--------------------------------------------------------------------
end_per_suite(Config) ->
- Gen = proplists:get_value(gen, Config),
- exit(Gen, kill),
- ct:sleep(100),
+ do_log(?FUNCTION_NAME),
application:stop(sasl),
ok.
@@ -92,8 +90,7 @@ end_per_testcase(_TestCase, _Config) ->
%% @end
%%--------------------------------------------------------------------
groups() ->
- [{g1,[parallel,{repeat,10}],[tc1,tc2,tc3]},
- {g2,[{repeat,10}],[tc1,tc2,tc3]}].
+ [{g1,[{repeat,10}],[tc1,tc2,tc3]}].
%%--------------------------------------------------------------------
%% @spec all() -> GroupsAndTestCases | {skip,Reason}
@@ -104,26 +101,24 @@ groups() ->
%% @end
%%--------------------------------------------------------------------
all() ->
- [{group,g1},{group,g2}].
+ [{group,g1}].
tc1(_) ->
- ct:sleep(100),
+ do_log(?FUNCTION_NAME),
ok.
tc2(_) ->
- ct:sleep(100),
+ do_log(?FUNCTION_NAME),
ok.
tc3(_) ->
- ct:sleep(100),
+ do_log(?FUNCTION_NAME),
ok.
%%%-----------------------------------------------------------------
-gen() ->
- gen_loop(1).
-gen_loop(N) ->
- ct:log("Logger iteration: ~p", [N]),
- error_logger:error_report(N),
- error_logger:info_report(N),
- ct:sleep(150),
- gen_loop(N+1).
+do_log(What) ->
+ ct:log("Logger ~p", [What]),
+ error_logger:error_report(What),
+ error_logger:info_report(What),
+ logger:notice("~p",[What]),
+ timer:sleep(100).
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_formatter_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_formatter_SUITE.erl
new file mode 100644
index 0000000000..26097f5eae
--- /dev/null
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_formatter_SUITE.erl
@@ -0,0 +1,124 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(cth_log_formatter_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% @spec suite() -> Info
+%% Info = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,30}}].
+
+%%--------------------------------------------------------------------
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ application:start(sasl),
+ do_log(?FUNCTION_NAME),
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ do_log(?FUNCTION_NAME),
+ application:stop(sasl),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1} | {fail,Reason}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%% @end
+%%--------------------------------------------------------------------
+groups() ->
+ [{g1,[{repeat,10}],[tc1,tc2,tc3]}].
+
+%%--------------------------------------------------------------------
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [{group,g1}].
+
+tc1(_) ->
+ do_log(?FUNCTION_NAME),
+ ok.
+tc2(_) ->
+ do_log(?FUNCTION_NAME),
+ ok.
+tc3(_) ->
+ do_log(?FUNCTION_NAME),
+ ok.
+
+%%%-----------------------------------------------------------------
+
+
+do_log(What) ->
+ ct:log("Logger ~p", [What]),
+ error_logger:error_report(What),
+ error_logger:info_report(What),
+ logger:notice("~p",[What]),
+ timer:sleep(100).
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_unexpect_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_unexpect_SUITE.erl
new file mode 100644
index 0000000000..af482b8f27
--- /dev/null
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_unexpect_SUITE.erl
@@ -0,0 +1,129 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(cth_log_unexpect_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% @spec suite() -> Info
+%% Info = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,30}}].
+
+%%--------------------------------------------------------------------
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ application:start(sasl),
+ Gen = spawn(fun() -> gen() end),
+ [{gen,Gen}|Config].
+
+%%--------------------------------------------------------------------
+%% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ Gen = proplists:get_value(gen, Config),
+ exit(Gen, kill),
+ ct:sleep(100),
+ application:stop(sasl),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1} | {fail,Reason}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%% @end
+%%--------------------------------------------------------------------
+groups() ->
+ [{g1,[parallel,{repeat,10}],[tc1,tc2,tc3]},
+ {g2,[{repeat,10}],[tc1,tc2,tc3]}].
+
+%%--------------------------------------------------------------------
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [{group,g1},{group,g2}].
+
+tc1(_) ->
+ ct:sleep(100),
+ ok.
+tc2(_) ->
+ ct:sleep(100),
+ ok.
+tc3(_) ->
+ ct:sleep(100),
+ ok.
+
+%%%-----------------------------------------------------------------
+
+gen() ->
+ gen_loop(1).
+
+gen_loop(N) ->
+ ct:log("Logger iteration: ~p", [N]),
+ error_logger:error_report(N),
+ error_logger:info_report(N),
+ ct:sleep(150),
+ gen_loop(N+1).
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ecdsa_key b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ecdsa_key
new file mode 100644
index 0000000000..3c4f2511e3
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ecdsa_key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIGOpCH9PXImAb4f6KaRMmk+qu48TxeYRyE0OwjLAF6nsoAoGCCqGSM49
+AwEHoUQDQgAE/hLY0r6Jm2305buEMWohEO0tvuf3pvVQ/DmJ1P2Mjyh9MTZJjb7q
+ciLVXS565yiuZZ5hBDun4kjPk2Uk50PbPA==
+-----END EC PRIVATE KEY-----
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ecdsa_key.pub b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ecdsa_key.pub
new file mode 100644
index 0000000000..83745f2f70
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ecdsa_key.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP4S2NK+iZtt9OW7hDFqIRDtLb7n96b1UPw5idT9jI8ofTE2SY2+6nIi1V0ueucormWeYQQ7p+JIz5NlJOdD2zw= uabhnil@elxadlj3q32
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ed25519_key b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ed25519_key
new file mode 100644
index 0000000000..b5d1caac6e
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDulNEQ8e5932nxfsjvAqamGARz4kCimQoEDoXMvXwMbwAAAJjL1zxIy9c8
+SAAAAAtzc2gtZWQyNTUxOQAAACDulNEQ8e5932nxfsjvAqamGARz4kCimQoEDoXMvXwMbw
+AAAEBduLyMCbEq12IBJyO/tUAhFuV62Upb7Z4i2cjLmzm6nO6U0RDx7n3fafF+yO8CpqYY
+BHPiQKKZCgQOhcy9fAxvAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ed25519_key.pub b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..433cf5c3dc
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO6U0RDx7n3fafF+yO8CpqYYBHPiQKKZCgQOhcy9fAxv uabhnil@elxadlj3q32
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_rsa_key b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_rsa_key
new file mode 100644
index 0000000000..4154ede882
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_rsa_key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA11eaLZ1RQ7RU8Snmm5ruA4v1sn/PRBuBqOTZK6IPSDoQeMjO
+QD/pk4fG5VZMouSEZQFmp1qgRQH+kPd316DR8c1Mf0sEMm0Y15EOzQDp22pXIhx0
+SxL0KJfnhb2zcPUX21vBJwYgmG9rX52LGYLBB6SteYdmNUjEjtPnTpd/4RPrQ1am
+/M3sQogpncWHDfm27OJSWIn6HSRW8YmIfzqacN00gs6LdkdqrMUjOE47MO8w5E5L
+8ddefQ6DkCiFCqjYqngeH18dYDHkt3iUw7DKnf8AmhI1HmMPnsa+SwU81eOVBij4
+VhBxjHS287kbKaH/DdE2zY2gcub1Gn6O5JZaowIDAQABAoIBAAjvIn9n+nojni6P
+PXkROXS+NshcEx5RQdTfAb+UrquhdRN9yUjTHf638GGNbgqIDpIeN8MTuHiEd6pO
+ChPRIUS17smNNDRfYFt5MjMBKbKnu8u3gH+o3qZcfweGck5qbL32FZJn56xLWxoR
+cDbzvMxzYZscKfpl9XmzgvI4yN5uoKFEWE4mOOkhX4thPVCM4tdshjPp0GPF7QXQ
+/HZ6cLgHI7SF2InLyD54i4mDmGyFMs5LnJ1WwIk5iio9Xo7BTOhHspY7Kt1j0gNz
+JGVM/Sw0IDGScXqBD8fCFfG3m/mrtSEsfpbsTmID+hWDV6Lpbu9leoP892DGm7sy
+RQuHPGECgYEA9M0rRxUiMEVjA8HZdmeK2YW3pfMl5aQSGXfrNE9JLjtKCZDB78/k
+EROLABJRVGUEFFgr3PCNY7nenwDoSxtyel+RJYEVp7KICEx0PVjVYzsxgKwb8sTz
+oDKhhI64QVd0YhNkkiratAXGfb4zqZZ9v7TwwHvEB4QQz99Pxs8KmrMCgYEA4TFw
+zhxYr7dB5RsGRKRXwrqNGQGunrox5P8ICRqsfBOxgon1iPKG748sM7av/CiBcSTP
+ZlkBHhJcrC7h/7ySYwoppLxAISvbcPcBuEIlpTnRgshV0advyPpsDlmTuD4ogmyK
+nKTFMxeMCW2AcIPh4m9XGjHTEoH/bjey8tkq+FECgYAoE4KXQ31yW5mnHtAkEzVn
+AP4cu96jZqXT547o5GX9nJU1Va56PRWAOivSWi1YXKU7U2fy5qqu/dnVPAfLa/Xk
+1MplUmmiJ5cfjKym8mkUsrca8pIsv1OHEWLh2Z+Oiuuxtq0LRNrDCAMDyOSaSdnS
+TaPwTxK7jEJYVZ2w1WpOAwKBgHBatIeBSRZSfoQKWbRBsq+rV96UIkV7bI1uWNoB
+/vDg+n3Ay0qKhpxbeJhMm71ZkEudLAVKfu48BxS5R3TK5taXKXPRoMVsFk5kTu1Z
+w2KOWGPCBF9YBMPnfaYjNzqDeL6p3PaOlv0I0IuboKuCgV7yYijllwPy7DhXfecA
+RxohAoGAfur4AI+mZWO7IfCnD6f/3GiwX565NZwhdZ3A0YFZp7lg3JYMrZ7pi/a6
+I7ywEHiKDxZ+4+5y8v69cP6hHKZbC+liaENEmYzfKz73SFwf/GSeUiKUEAiITEmH
+N+Cj0PcHEc1iy/EGGX7lxi/5MHJcsNjAD0lZDohTiz0SnwQiXXg=
+-----END RSA PRIVATE KEY-----
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_rsa_key.pub b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..f24e046209
--- /dev/null
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ssh_dir/ssh_host_rsa_key.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDXV5otnVFDtFTxKeabmu4Di/Wyf89EG4Go5Nkrog9IOhB4yM5AP+mTh8blVkyi5IRlAWanWqBFAf6Q93fXoNHxzUx/SwQybRjXkQ7NAOnbalciHHRLEvQol+eFvbNw9RfbW8EnBiCYb2tfnYsZgsEHpK15h2Y1SMSO0+dOl3/hE+tDVqb8zexCiCmdxYcN+bbs4lJYifodJFbxiYh/Oppw3TSCzot2R2qsxSM4Tjsw7zDkTkvx1159DoOQKIUKqNiqeB4fXx1gMeS3eJTDsMqd/wCaEjUeYw+exr5LBTzV45UGKPhWEHGMdLbzuRspof8N0TbNjaBy5vUafo7kllqj uabhnil@elxadlj3q32
diff --git a/lib/common_test/test/ct_repeat_testrun_SUITE.erl b/lib/common_test/test/ct_repeat_testrun_SUITE.erl
index 9382e4c011..79b9c4d549 100644
--- a/lib/common_test/test/ct_repeat_testrun_SUITE.erl
+++ b/lib/common_test/test/ct_repeat_testrun_SUITE.erl
@@ -94,9 +94,9 @@ init_per_suite(Config0) ->
[Opts3],Config)
end),
%% The time to compare with here must match the timeout value
- %% in the test suite. Accept 30% logging overhead (26 sec total).
- if T > 26000000 ->
- ct:pal("Timing test took ~w sec (< 27 sec expected). "
+ %% in the test suite. Accept some overhead
+ if T > 5000 ->
+ ct:pal("Timing test took ~w sec (< 5 sec expected). "
"Skipping the suite!",
[trunc(T/1000000)]),
ct_test_support:end_per_suite(Config),
diff --git a/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl b/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl
index 1a305b1516..de521a5622 100644
--- a/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl
+++ b/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl
@@ -61,7 +61,7 @@ end_per_testcase(_Case, Config) ->
%%%-----------------------------------------------------------------
%%% Test cases
tc1(_Config) ->
- ct:sleep(10000),
+ timer:sleep(1000),
ok.
tc2(_Config) ->
diff --git a/lib/common_test/test_server/conf_vars.in b/lib/common_test/test_server/conf_vars.in
index 7c55d7b9ed..2d1e78fc69 100644
--- a/lib/common_test/test_server/conf_vars.in
+++ b/lib/common_test/test_server/conf_vars.in
@@ -23,3 +23,4 @@ SSLEAY_ROOT:@SSLEAY_ROOT@
JAVAC:@JAVAC@
make_command:@make_command@
test_c_compiler:@test_c_compiler@
+WSL:@wsl@
diff --git a/lib/common_test/test_server/configure.in b/lib/common_test/test_server/configure.in
index 220c1d47df..d58b4fc51b 100644
--- a/lib/common_test/test_server/configure.in
+++ b/lib/common_test/test_server/configure.in
@@ -371,7 +371,7 @@ HCC='$(CC)'
AC_SUBST(HCC)
#--------------------------------------------------------------------
-# ld is used for linking on vxworks
+# ld
#--------------------------------------------------------------------
LD='$(CC) $(CFLAGS)'
AC_SUBST(LD)
@@ -389,6 +389,12 @@ exe=''
AC_SUBST(exe)
#--------------------------------------------------------------------
+# wsl command (if on windows using wsl)
+#--------------------------------------------------------------------
+wsl=''
+AC_SUBST(wsl)
+
+#--------------------------------------------------------------------
# flags when linking for cross platform targets (yet 'tis useful with
# native builds)
#--------------------------------------------------------------------
diff --git a/lib/common_test/test_server/ts_autoconf_win32.erl b/lib/common_test/test_server/ts_autoconf_win32.erl
index 1179dfe0e5..33492a18fd 100644
--- a/lib/common_test/test_server/ts_autoconf_win32.erl
+++ b/lib/common_test/test_server/ts_autoconf_win32.erl
@@ -57,7 +57,9 @@ tests() ->
{"for C compiler", fun c_compiler/1},
{"for make program", fun make/1},
{"for location of SSL libraries", fun ssl/1},
- {"for location of Java compiler", fun javac/1}].
+ {"for location of Java compiler", fun javac/1},
+ {"if wsl is to used as shell", fun wsl/1}
+ ].
system_type(Vars) ->
Os = case os:type() of
@@ -168,7 +170,7 @@ visual_cxx(Vars) ->
{'DEFS', common_c_defs()},
{'SHLIB_SUFFIX', ".dll"},
{'ERTS_LIBS', ERTS_THR_LIB ++ LIBS},
- {'LIBS', DEFAULT_THR_LIB ++ DBG_LINK ++ LIBS},
+ {'LIBS', DBG_LINK ++ LIBS},
{obj,".obj"},
{exe, ".exe"},
{test_c_compiler, "{msc, undefined}"}
@@ -247,6 +249,14 @@ javac(Vars) ->
{Path, [{'JAVAC', "javac"} | Vars]}
end.
+wsl(Vars) ->
+ case os:getenv("WSLENV") of
+ false ->
+ {no, [{'wsl', ""} | Vars]};
+ _ ->
+ {"wsl.exe", [{'wsl', "wsl.exe"} | Vars]}
+ end.
+
is_debug_build() ->
case catch string:find(erlang:system_info(system_version), "debug") of
nomatch ->
diff --git a/lib/common_test/test_server/ts_run.erl b/lib/common_test/test_server/ts_run.erl
index 7e12b9652c..ce454dce9c 100644
--- a/lib/common_test/test_server/ts_run.erl
+++ b/lib/common_test/test_server/ts_run.erl
@@ -197,17 +197,25 @@ make_command(Vars, Spec, State) ->
{ok,Cwd} = file:get_cwd(),
TestDir = State#state.test_dir,
TestPath = filename:nativename(TestDir),
- Erl = case os:getenv("TS_RUN_VALGRIND") of
+ Erl = case os:getenv("TS_RUN_EMU") of
false ->
ct:get_progname();
- _ ->
+ "valgrind" ->
case State#state.file of
Dir when is_list(Dir) ->
os:putenv("VALGRIND_LOGFILE_PREFIX", Dir++"-");
_ ->
ok
end,
- "cerl -valgrind"
+ "cerl -valgrind";
+ "asan" ->
+ case State#state.file of
+ App when is_list(App) ->
+ os:putenv("ASAN_LOGFILE_PREFIX", App);
+ _ ->
+ ok
+ end,
+ "cerl -asan"
end,
Naming =
case ts_lib:var(longnames, Vars) of
@@ -261,9 +269,10 @@ run_batch(Vars, _Spec, State) ->
ts_lib:progress(Vars, 1, "Command: ~ts~n", [Command]),
io:format(user, "Command: ~ts~n",[Command]),
Port = open_port({spawn, Command}, [stream, in, eof, exit_status]),
- Timeout = 30000 * case os:getenv("TS_RUN_VALGRIND") of
+ Timeout = 30000 * case os:getenv("TS_RUN_EMU") of
false -> 1;
- _ -> 100
+ "valgrind" -> 100;
+ "asan" -> 2
end,
tricky_print_data(Port, Timeout).
diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk
index 1e6991d73a..d1e8e2e8ba 100644
--- a/lib/common_test/vsn.mk
+++ b/lib/common_test/vsn.mk
@@ -1 +1 @@
-COMMON_TEST_VSN = 1.18.2
+COMMON_TEST_VSN = 1.20
diff --git a/lib/compiler/Makefile b/lib/compiler/Makefile
index b8b2f562a2..f18df11e9f 100644
--- a/lib/compiler/Makefile
+++ b/lib/compiler/Makefile
@@ -36,3 +36,6 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=crypto hipe
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/compiler/doc/src/Makefile b/lib/compiler/doc/src/Makefile
index 2fb163b9e7..e1c445662c 100644
--- a/lib/compiler/doc/src/Makefile
+++ b/lib/compiler/doc/src/Makefile
@@ -28,94 +28,19 @@ VSN=$(COMPILER_VSN)
APPLICATION=compiler
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-COMPILER_DIR = $(ERL_TOP)/lib/compiler/src
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
XML_REF3_FILES = compile.xml
+EDOC_REF3_FILES = cerl.xml cerl_trees.xml cerl_clauses.xml
XML_PART_FILES = internal.xml
-XML_CHAPTER_FILES = notes.xml
+XML_NOTES_FILES = notes.xml
BOOK_FILES = book.xml
-GIF_FILES =
-
XML_FILES = \
- $(BOOK_FILES) $(XML_CHAPTER_FILES) \
+ $(BOOK_FILES) $(XML_NOTES_FILES) \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES)
-XML_INTERNAL_FILES = \
- cerl.xml cerl_trees.xml cerl_clauses.xml
-
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-XML_GEN_FILES = $(XML_INTERNAL_FILES:%=$(XMLDIR)/%)
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-$(XML_INTERNAL_FILES:%=$(XMLDIR)/%): $(COMPILER_DIR)/$(@:$(XMLDIR)/%.xml=%.erl)
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -def vsn $(COMPILER_VSN) -dir $(XMLDIR) $(COMPILER_DIR)/$(@:$(XMLDIR)/%.xml=%.erl)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml
index 0d9feb09da..3465141de0 100644
--- a/lib/compiler/doc/src/compile.xml
+++ b/lib/compiler/doc/src/compile.xml
@@ -38,6 +38,13 @@
</p>
</description>
+ <datatypes>
+ <datatype>
+ <name>option() = term()</name>
+ <desc><p>See <seemfa marker="#file/2">file/2</seemfa> for detailed description</p></desc>
+ </datatype>
+ </datatypes>
+
<funcs>
<func>
<name since="OTP 19.0">env_compiler_options()</name>
@@ -82,7 +89,7 @@
to be an error if the module name in the source code is
not the same as the basename of the output file.</p>
- <p><marker id="type-option"/>Available options:</p>
+ <p>Available options:</p>
<taglist>
<tag><c>basic_validation</c></tag>
<item>
@@ -119,7 +126,7 @@
<p>The compiler will emit informational warnings about binary
matching optimizations (both successful and unsuccessful).
For more information, see the section about
- <seealso marker="doc/efficiency_guide:binaryhandling#bin_opt_info">bin_opt_info</seealso>
+ <seeguide marker="system/efficiency_guide:binaryhandling#bin_opt_info">bin_opt_info</seeguide>
in the Efficiency Guide.</p>
</item>
@@ -143,8 +150,8 @@
<tag><c>debug_info</c></tag>
<item>
<marker id="debug_info"></marker>
- <p>Includes debug information in the form of <seealso marker="erts:absform">
- Erlang Abstract Format</seealso> in the <c>debug_info</c>
+ <p>Includes debug information in the form of <seeguide marker="erts:absform">
+ Erlang Abstract Format</seeguide> in the <c>debug_info</c>
chunk of the compiled beam module. Tools such as Debugger,
Xref, and Cover require the debug information to be included.</p>
@@ -153,7 +160,7 @@
(<c>encrypt_debug_info</c>) to prevent this.</p>
<p>For details, see
- <seealso marker="stdlib:beam_lib#debug_info">beam_lib(3)</seealso>.</p>
+ <seeerl marker="stdlib:beam_lib#debug_info">beam_lib(3)</seeerl>.</p>
</item>
<tag><c>{debug_info, {Backend, Data}}</c></tag>
@@ -164,7 +171,7 @@
The given module must implement a <c>debug_info/4</c> function
and is responsible for generating different code representations,
as described in the <c>debug_info</c> under
- <seealso marker="stdlib:beam_lib#debug_info">beam_lib(3)</seealso>.</p>
+ <seeerl marker="stdlib:beam_lib#debug_info">beam_lib(3)</seeerl>.</p>
<p><em>Warning</em>: Source code can be reconstructed from
the debug information. Use encrypted debug information
@@ -186,7 +193,7 @@
for encrypting the debug information. The default
(and currently the only) type is <c>des3_cbc</c>.</p>
<p>For details, see
- <seealso marker="stdlib:beam_lib#debug_info">beam_lib(3)</seealso>.</p>
+ <seeerl marker="stdlib:beam_lib#debug_info">beam_lib(3)</seeerl>.</p>
</item>
<tag><c>encrypt_debug_info</c></tag>
@@ -197,7 +204,7 @@
</p>
<p>For details, see
- <seealso marker="stdlib:beam_lib#debug_info">beam_lib(3)</seealso>.</p>
+ <seeerl marker="stdlib:beam_lib#debug_info">beam_lib(3)</seeerl>.</p>
</item>
<tag><c>deterministic</c></tag>
@@ -489,7 +496,7 @@ module.beam: module.erl \
<p>Pass extra chunks to be stored in the <c>.beam</c> file.
The extra chunks must be a list of tuples with a four byte
binary as chunk name followed by a binary with the chunk contents.
- See <seealso marker="stdlib:beam_lib">beam_lib</seealso> for
+ See <seeerl marker="stdlib:beam_lib">beam_lib</seeerl> for
more information.
</p>
</item>
@@ -610,11 +617,11 @@ module.beam: module.erl \
assembled list of deprecated functions in Erlang/OTP. To
do a more general check, the Xref tool can be used.
See also
- <seealso marker="tools:xref#deprecated_function">xref(3)</seealso>
+ <seeerl marker="tools:xref#deprecated_function">xref(3)</seeerl>
and the function
- <seealso marker="tools:xref#m/1">xref:m/1</seealso>, also
+ <seemfa marker="tools:xref#m/1">xref:m/1</seemfa>, also
accessible through the function
- <seealso marker="stdlib:c#xm/1">c:xm/1</seealso>.</p>
+ <seemfa marker="stdlib:c#xm/1">c:xm/1</seemfa>.</p>
</item>
<tag><c>{nowarn_deprecated_function, MFAs}</c></tag>
@@ -652,7 +659,7 @@ module.beam: module.erl \
<item>
<p>Turns off warnings for calls to old type testing BIFs,
such as <c>pid/1</c> and <c>list/1</c>. See the
- <seealso marker="doc/reference_manual:expressions#guards">Erlang Reference Manual</seealso>
+ <seeguide marker="system/reference_manual:expressions#guards">Erlang Reference Manual</seeguide>
for a complete list of type testing BIFs and their old
equivalents. Default is to emit warnings for calls to
old type testing BIFs.</p>
@@ -677,6 +684,14 @@ module.beam: module.erl \
<p>Turns off warnings for unused record types. Default is to
emit warnings for unused locally defined record types.</p>
</item>
+
+ <tag><c>nowarn_nif_inline</c></tag>
+ <item>
+ <p>By default, warnings are emitted when inlining is enabled in
+ a module that may load NIFs, as the compiler may inline NIF
+ fallbacks by accident. Use this option to turn off this kind of
+ warnings.</p>
+ </item>
</taglist>
<p>Another class of warnings is generated by the compiler
@@ -787,7 +802,7 @@ module.beam: module.erl \
characters that describes the error. This function is
usually called implicitly when an <c>ErrorInfo</c> structure
(described in section
- <seealso marker="#error_information">Error Information</seealso>) is processed.</p>
+ <seeerl marker="#error_information">Error Information</seeerl>) is processed.</p>
</desc>
</func>
@@ -810,7 +825,7 @@ module.beam: module.erl \
<name since="">noenv_file(File, Options) -> CompRet</name>
<fsummary>Compiles a file (ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary>
<desc>
- <p>Works like <seealso marker="#file/2">file/2</seealso>,
+ <p>Works like <seemfa marker="#file/2">file/2</seemfa>,
except that the environment variable <c>ERL_COMPILER_OPTIONS</c>
is not consulted.</p>
</desc>
@@ -820,7 +835,7 @@ module.beam: module.erl \
<name since="">noenv_forms(Forms, Options) -> CompRet</name>
<fsummary>Compiles a list of forms (ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary>
<desc>
- <p>Works like <seealso marker="#forms/2">forms/2</seealso>,
+ <p>Works like <seemfa marker="#forms/2">forms/2</seemfa>,
except that the environment variable <c>ERL_COMPILER_OPTIONS</c>
is not consulted.</p>
</desc>
@@ -835,7 +850,7 @@ module.beam: module.erl \
</type>
<desc>
<p>Works like
- <seealso marker="#output_generated/1">output_generated/1</seealso>,
+ <seemfa marker="#output_generated/1">output_generated/1</seemfa>,
except that the environment variable <c>ERL_COMPILER_OPTIONS</c>
is not consulted.</p>
</desc>
@@ -852,19 +867,19 @@ module.beam: module.erl \
into a list.</p>
<p>The list is appended to any options given to
- <seealso marker="#file/2">file/2</seealso>,
- <seealso marker="#forms/2">forms/2</seealso>, and
- <seealso marker="#output_generated/1">output_generated/2</seealso>.
+ <seemfa marker="#file/2">file/2</seemfa>,
+ <seemfa marker="#forms/2">forms/2</seemfa>, and
+ <seemfa marker="#output_generated/1">output_generated/2</seemfa>.
Use the alternative functions
- <seealso marker="#noenv_file/2">noenv_file/2</seealso>,
- <seealso marker="#noenv_forms/2">noenv_forms/2</seealso>, or
- <seealso marker="#noenv_output_generated/1">noenv_output_generated/2</seealso>
+ <seemfa marker="#noenv_file/2">noenv_file/2</seemfa>,
+ <seemfa marker="#noenv_forms/2">noenv_forms/2</seemfa>, or
+ <seemfa marker="#noenv_output_generated/1">noenv_output_generated/2</seemfa>
if you do not want the environment variable to be consulted,
for example, if you are calling the compiler recursively from
inside a parse transform.</p>
<p>The list can be retrieved with
- <seealso marker="#env_compiler_options/0">env_compiler_options/0</seealso>.</p>
+ <seemfa marker="#env_compiler_options/0">env_compiler_options/0</seemfa>.</p>
</section>
<section>
@@ -936,16 +951,16 @@ pi() -> 3.1416.
<p>The following functions are inlined:</p>
<list type="bulleted">
- <item><seealso marker="stdlib:lists#all/2">lists:all/2</seealso></item>
- <item><seealso marker="stdlib:lists#any/2">lists:any/2</seealso></item>
- <item><seealso marker="stdlib:lists#foreach/2">lists:foreach/2</seealso></item>
- <item><seealso marker="stdlib:lists#map/2">lists:map/2</seealso></item>
- <item><seealso marker="stdlib:lists#flatmap/2">lists:flatmap/2</seealso></item>
- <item><seealso marker="stdlib:lists#filter/2">lists:filter/2</seealso></item>
- <item><seealso marker="stdlib:lists#foldl/3">lists:foldl/3</seealso></item>
- <item><seealso marker="stdlib:lists#foldr/3">lists:foldr/3</seealso></item>
- <item><seealso marker="stdlib:lists#mapfoldl/3">lists:mapfoldl/3</seealso></item>
- <item><seealso marker="stdlib:lists#mapfoldr/3">lists:mapfoldr/3</seealso></item>
+ <item><seemfa marker="stdlib:lists#all/2">lists:all/2</seemfa></item>
+ <item><seemfa marker="stdlib:lists#any/2">lists:any/2</seemfa></item>
+ <item><seemfa marker="stdlib:lists#foreach/2">lists:foreach/2</seemfa></item>
+ <item><seemfa marker="stdlib:lists#map/2">lists:map/2</seemfa></item>
+ <item><seemfa marker="stdlib:lists#flatmap/2">lists:flatmap/2</seemfa></item>
+ <item><seemfa marker="stdlib:lists#filter/2">lists:filter/2</seemfa></item>
+ <item><seemfa marker="stdlib:lists#foldl/3">lists:foldl/3</seemfa></item>
+ <item><seemfa marker="stdlib:lists#foldr/3">lists:foldr/3</seemfa></item>
+ <item><seemfa marker="stdlib:lists#mapfoldl/3">lists:mapfoldl/3</seemfa></item>
+ <item><seemfa marker="stdlib:lists#mapfoldr/3">lists:mapfoldr/3</seemfa></item>
</list>
</section>
@@ -980,10 +995,10 @@ Module:format_error(ErrorDescriptor)</code>
<section>
<title>See Also</title>
<p>
- <seealso marker="stdlib:epp">epp(3)</seealso>,
- <seealso marker="stdlib:erl_id_trans">erl_id_trans(3)</seealso>,
- <seealso marker="stdlib:erl_lint">erl_lint(3)</seealso>,
- <seealso marker="stdlib:beam_lib">beam_lib(3)</seealso>
+ <seeerl marker="stdlib:epp">epp(3)</seeerl>,
+ <seeerl marker="stdlib:erl_id_trans">erl_id_trans(3)</seeerl>,
+ <seeerl marker="stdlib:erl_lint">erl_lint(3)</seeerl>,
+ <seeerl marker="stdlib:beam_lib">beam_lib(3)</seeerl>
</p>
</section>
</erlref>
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 43c2f82932..0d8fafb523 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,329 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 7.6.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug in the type optimization pass that could
+ yield incorrect values or cause the wrong clauses to be
+ executed.</p>
+ <p>
+ Own Id: OTP-17073</p>
+ </item>
+ <item>
+ <p>Fixed a bug in the validator that could cause it to
+ reject valid code.</p>
+ <p>
+ Own Id: OTP-17126 Aux Id: ERL-1471 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.6.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Several minor compiler bugs have been fixed:</p>
+ <p>Constructing a binary with a list as a size of a
+ binary segment could generate a BEAM file that could not
+ be loaded.</p>
+ <p>When matching a binary segment of type <c>float</c>
+ and ignoring the matched out value, the match would
+ always succeed, even if the size was invalid or the value
+ of the float was NaN or some other non-numeric float
+ value.</p>
+ <p>Attempting to construct an invalid external fun (e.g.
+ <c>fun m:f:bad</c>) is supposed to raise a
+ '<c>badarg</c>' exception, but if the value was never
+ used, no exception would be raised.</p>
+ <p>
+ Own Id: OTP-16932</p>
+ </item>
+ <item>
+ <p>Fixed multiple bugs in the validator that could cause
+ it to reject valid code.</p>
+ <p>
+ Own Id: OTP-17039 Aux Id: ERL-1426 </p>
+ </item>
+ <item>
+ <p>The compiler could crash when a binary comprehension
+ had a generator that depended on another generator.</p>
+ <p>
+ Own Id: OTP-17045 Aux Id: ERL-1427 </p>
+ </item>
+ <item>
+ <p>Fixed a bug in the type optimization pass that could
+ yield incorrect values or cause the wrong clauses to be
+ executed.</p>
+ <p>
+ Own Id: OTP-17072 Aux Id: ERL-1440 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.6.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug in the boolean optimization pass that
+ caused the compiler to confuse different clauses.</p>
+ <p>
+ Own Id: OTP-16951 Aux Id: ERL-1384 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.6.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a performance bug that could be triggered by
+ tuple matching in very large functions.</p>
+ <p>
+ Own Id: OTP-16895 Aux Id: ERL-1359 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.6.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>If the update of a map with the '<c>Map#{Key :=
+ Value}</c>' syntax failed, the line number in the stack
+ backtrace could be incorrect.</p>
+ <p>
+ Own Id: OTP-16701 Aux Id: ERL-1271 </p>
+ </item>
+ <item>
+ <p>Fixed a performance bug that slowed down compilation
+ of modules with deeply nested terms.</p>
+ <p>
+ Own Id: OTP-16755 Aux Id: ERL-1297 </p>
+ </item>
+ <item>
+ <p>The compiler could in rare circumstances do an an
+ unsafe optimization that would result in a matching of a
+ nested map pattern would fail to match.</p>
+ <p>
+ Own Id: OTP-16820</p>
+ </item>
+ <item>
+ <p>Fixed a bug in the validator that caused it to reject
+ valid code.</p>
+ <p>
+ Own Id: OTP-16838 Aux Id: ERL-1340 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.6.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>When calls to <c>is_map_key</c> were repeated, the
+ compiler could terminate with an internal consistency
+ failure.</p>
+ <p>
+ Own Id: OTP-16708 Aux Id: ERL-1276 </p>
+ </item>
+ <item>
+ <p>Fixed a bug in the type inference pass that could
+ cause the compiler to hang.</p>
+ <p>
+ Own Id: OTP-16745 Aux Id: ERL-1289 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.6.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ In rare circumstances, a guard using 'not' could evaluate
+ to the wrong boolean value.</p>
+ <p>
+ Own Id: OTP-16652 Aux Id: ERL-1246 </p>
+ </item>
+ <item>
+ <p>A guard expression that referenced a variable bound to
+ a boolean expression could evaluate to the wrong
+ value.</p>
+ <p>
+ Own Id: OTP-16657 Aux Id: ERL-1253 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p><c>erlang:fun_info(fun foo/1, name/1)</c> used to
+ return a function name based on the name of the function
+ that <c>fun foo/1</c> was used in. The name returned is
+ now <c>-fun.foo/1-</c>.</p>
+ <p>
+ Own Id: OTP-15837</p>
+ </item>
+ <item>
+ <p> Initialization of record fields using <c>_</c> is no
+ longer allowed if the number of affected fields is zero.
+ </p>
+ <p>
+ Own Id: OTP-16516</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>EEP-52 has been implemented.</p>
+ <p>In binary matching, the size of the segment to be
+ matched is now allowed to be a guard expression, and
+ similarly in map matching the keys can now be guard
+ expressions. See the Erlang Reference Manual and
+ Programming Examples for more details.</p>
+ <p>Language compilers or code generators that generate
+ Core Erlang code may need to be updated to be compatible
+ with the compiler in OTP 23. For more details, see the
+ section Backwards Compatibility in <url
+ href="http://erlang.org/eeps/eep-0052.html">EEP
+ 52</url>.</p>
+ <p>
+ Own Id: OTP-14708</p>
+ </item>
+ <item>
+ <p> Allow underscores in numeric literals to improve
+ readability. Examples: <c>123_456_789</c>,
+ <c>16#1234_ABCD</c>. </p>
+ <p>
+ Own Id: OTP-16007 Aux Id: PR-2324 </p>
+ </item>
+ <item>
+ <p>Improved the type optimization pass' inference of
+ types that depend on themselves, giving us more accurate
+ types and letting us track the content types of
+ lists.</p>
+ <p>
+ Own Id: OTP-16214 Aux Id: PR-2460 </p>
+ </item>
+ <item>
+ <p>
+ Support message queue optimization also for references
+ returned from the new <seemfa
+ marker="erts:erlang#spawn_request/5"><c>spawn_request()</c></seemfa>
+ BIFs.</p>
+ <p>
+ Own Id: OTP-16367 Aux Id: OTP-15251 </p>
+ </item>
+ <item>
+ <p>The compiler will now raise a warning when inlining is
+ used in modules that load NIFs.</p>
+ <p>
+ Own Id: OTP-16429 Aux Id: ERL-303 </p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>Line information was sometimes incorrect for
+ floating-point math exceptions.</p>
+ <p>
+ Own Id: OTP-16505 Aux Id: ERL-1178 </p>
+ </item>
+ <item>
+ <p>The <c>debug_info</c> option can now be specified in
+ <c>-compile()</c> attributes.</p>
+ <p>
+ Own Id: OTP-16523 Aux Id: ERL-1058 </p>
+ </item>
+ <item>
+ <p>Reduced the resource usage of <c>erlc</c> in parallel
+ builds (e.g. <c>make -j128</c>).</p>
+ <p>
+ Own Id: OTP-16543 Aux Id: ERL-1186 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.5.4.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Fixed a bug in the type optimization pass that could
+ yield incorrect values or cause the wrong clauses to be
+ executed.</p>
+ <p>
+ Own Id: OTP-17073</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.5.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug in the validator that could cause it to
+ reject valid code</p>
+ <p>
+ Own Id: OTP-17039 Aux Id: ERL-1426 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.5.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a bug that could cause the compiler to crash on
+ code that constructed binaries.</p>
+ <p>
+ Own Id: OTP-16747 Aux Id: ERL-1290 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.5.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1214,9 +1537,9 @@
Own Id: OTP-13794</p>
</item>
<item>
- <p>Replaced usage of deprecated symbolic <seealso
- marker="erts:erlang#type-time_unit"><c>time
- unit</c></seealso> representations.</p>
+ <p>Replaced usage of deprecated symbolic <seetype
+ marker="erts:erlang#time_unit"><c>time
+ unit</c></seetype> representations.</p>
<p>
Own Id: OTP-13831 Aux Id: OTP-13735 </p>
</item>
@@ -2089,8 +2412,8 @@
</taglist>
<p>
For information on how to use Maps please see Map Expressions in the
- <seealso marker="doc/reference_manual:expressions#map_expressions">
- Reference Manual</seealso>.</p>
+ <seeguide marker="system/reference_manual:expressions#map_expressions">
+ Reference Manual</seeguide>.</p>
<p>
The current implementation is without the following
features:</p>
@@ -4235,4 +4558,3 @@
</section>
</section>
</chapter>
-
diff --git a/lib/compiler/scripts/.gitignore b/lib/compiler/scripts/.gitignore
index 4e4eba766d..444b0cea1e 100644
--- a/lib/compiler/scripts/.gitignore
+++ b/lib/compiler/scripts/.gitignore
@@ -1 +1,5 @@
-/smoke-build
+/smoke-build/*
+
+# The dependency lock file must be kept to ensure that the smoke
+# test won't be broken as time passes.
+!/smoke-build/mix.lock
diff --git a/lib/compiler/scripts/smoke-build/mix.lock b/lib/compiler/scripts/smoke-build/mix.lock
new file mode 100644
index 0000000000..9fb83798c4
--- /dev/null
+++ b/lib/compiler/scripts/smoke-build/mix.lock
@@ -0,0 +1,9 @@
+%{
+ "credentials_obfuscation": {:hex, :credentials_obfuscation, "1.1.0", "513793cc20c18afc9e03e584b436192a751a8344890e03a8741c65c8d6866fab", [:rebar3], [], "hexpm"},
+ "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"},
+ "jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm"},
+ "lager": {:hex, :lager, "3.8.0", "3402b9a7e473680ca179fc2f1d827cab88dd37dd1e6113090c6f45ef05228a1c", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
+ "rabbit_common": {:hex, :rabbit_common, "3.7.18", "4249efdf1fd96a81739ffad675582f980cc55aa0a02217e4907b4cd719c44822", [:make, :rebar3], [{:credentials_obfuscation, "1.1.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:jsx, "2.9.0", [hex: :jsx, repo: "hexpm", optional: false]}, {:lager, "3.8.0", [hex: :lager, repo: "hexpm", optional: false]}, {:ranch, "1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}, {:recon, "2.5.0", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm"},
+ "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
+ "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm"},
+}
diff --git a/lib/compiler/scripts/smoke-mix.exs b/lib/compiler/scripts/smoke-mix.exs
index ba0815e465..0bfb80b53c 100644
--- a/lib/compiler/scripts/smoke-mix.exs
+++ b/lib/compiler/scripts/smoke-mix.exs
@@ -45,6 +45,7 @@ defmodule Smoke.MixProject do
{:gpb, "~> 4.6"},
{:gproc, "~> 0.8.0"},
{:graphql, "~> 0.15.0", hex: :graphql_erl},
+ {:hut, "~> 1.3"},
{:hackney, "~> 1.15.0"},
{:ibrowse, "~> 4.4.1"},
{:jose, "~> 1.9.0"},
@@ -89,12 +90,13 @@ defmodule Smoke.MixProject do
defp build_wings do
# If the Erlang system is not installed, the build will
- # crash in plugins_src/accel when attempting to build
- # the accel driver. Since there is very little Erlang code in
- # the directory, skip the entire directory.
+ # crash in c_src or plugins_src/accel when attempting to
+ # build native code. Since there is very little Erlang
+ # code in these directories, skip them both.
"""
echo "all:\n\t" >plugins_src/accel/Makefile
+ echo "all:\n\t" >c_src/Makefile
git commit -a -m'Disable for smoke testing'
git tag -a -m'Smoke test' vsmoke_test
make
diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile
index e42032c3ae..b1531ac985 100644
--- a/lib/compiler/src/Makefile
+++ b/lib/compiler/src/Makefile
@@ -49,16 +49,18 @@ MODULES = \
beam_a \
beam_asm \
beam_block \
+ beam_call_types \
beam_clean \
beam_dict \
+ beam_digraph \
beam_disasm \
- beam_except \
beam_flatten \
beam_jump \
beam_listing \
beam_opcodes \
beam_peep \
beam_ssa \
+ beam_ssa_bool \
beam_ssa_bsm \
beam_ssa_codegen \
beam_ssa_dead \
@@ -72,6 +74,7 @@ MODULES = \
beam_ssa_type \
beam_kernel_to_ssa \
beam_trim \
+ beam_types \
beam_utils \
beam_validator \
beam_z \
@@ -93,6 +96,7 @@ MODULES = \
sys_core_fold \
sys_core_fold_lists \
sys_core_inline \
+ sys_core_prepare \
sys_pre_attributes \
v3_core \
v3_kernel \
@@ -104,6 +108,7 @@ HRL_FILES= \
beam_disasm.hrl \
beam_ssa_opt.hrl \
beam_ssa.hrl \
+ beam_types.hrl \
core_parse.hrl \
v3_kernel.hrl
@@ -190,6 +195,7 @@ release_docs_spec:
# Dependencies -- alphabetically, please
# ----------------------------------------------------
+$(EBIN)/beam_call_types.beam: beam_types.hrl
$(EBIN)/beam_disasm.beam: $(EGEN)/beam_opcodes.hrl beam_disasm.hrl
$(EBIN)/beam_listing.beam: core_parse.hrl v3_kernel.hrl beam_ssa.hrl
$(EBIN)/beam_kernel_to_ssa.beam: v3_kernel.hrl beam_ssa.hrl
@@ -204,7 +210,9 @@ $(EBIN)/beam_ssa_pp.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_pre_codegen.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_recv.beam: beam_ssa.hrl
$(EBIN)/beam_ssa_share.beam: beam_ssa.hrl
-$(EBIN)/beam_ssa_type.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_type.beam: beam_ssa.hrl beam_types.hrl
+$(EBIN)/beam_types.beam: beam_types.hrl
+$(EBIN)/beam_validator.beam: beam_types.hrl
$(EBIN)/cerl.beam: core_parse.hrl
$(EBIN)/compile.beam: core_parse.hrl ../../stdlib/include/erl_compile.hrl
$(EBIN)/core_lib.beam: core_parse.hrl
diff --git a/lib/compiler/src/beam_a.erl b/lib/compiler/src/beam_a.erl
index b6193f2aa4..44bcf8663c 100644
--- a/lib/compiler/src/beam_a.erl
+++ b/lib/compiler/src/beam_a.erl
@@ -59,16 +59,6 @@ rename_instrs([{test,is_eq_exact,_,[Dst,Src]}=Test,
rename_instrs([{test,is_eq_exact,_,[Same,Same]}|Is]) ->
%% Same literal or same register. Will always succeed.
rename_instrs(Is);
-rename_instrs([{recv_set,_},
- {label,Lbl},
- {loop_rec,{f,Fail},{x,0}},
- {loop_rec_end,_},{label,Fail}|Is]) ->
- %% This instruction sequence does nothing. All we need to
- %% keep is the first label.
- [{label,Lbl}|rename_instrs(Is)];
-rename_instrs([{loop_rec,{f,Fail},{x,0}},{loop_rec_end,_},{label,Fail}|Is]) ->
- %% This instruction sequence does nothing.
- rename_instrs(Is);
rename_instrs([{apply_last,A,N}|Is]) ->
[{apply,A},{deallocate,N},return|rename_instrs(Is)];
rename_instrs([{call_last,A,F,N}|Is]) ->
diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl
index 67757001c9..1cfb33db04 100644
--- a/lib/compiler/src/beam_asm.erl
+++ b/lib/compiler/src/beam_asm.erl
@@ -64,11 +64,30 @@ module(Code, ExtraChunks, CompileInfo, CompilerOpts) ->
assemble({Mod,Exp0,Attr0,Asm0,NumLabels}, ExtraChunks, CompileInfo, CompilerOpts) ->
{1,Dict0} = beam_dict:atom(Mod, beam_dict:new()),
{0,Dict1} = beam_dict:fname(atom_to_list(Mod) ++ ".erl", Dict0),
+ Dict2 = shared_fun_wrappers(CompilerOpts, Dict1),
NumFuncs = length(Asm0),
{Asm,Attr} = on_load(Asm0, Attr0),
Exp = cerl_sets:from_list(Exp0),
- {Code,Dict2} = assemble_1(Asm, Exp, Dict1, []),
- build_file(Code, Attr, Dict2, NumLabels, NumFuncs, ExtraChunks, CompileInfo, CompilerOpts).
+ {Code,Dict} = assemble_1(Asm, Exp, Dict2, []),
+ build_file(Code, Attr, Dict, NumLabels, NumFuncs,
+ ExtraChunks, CompileInfo, CompilerOpts).
+
+shared_fun_wrappers(Opts, Dict) ->
+ case proplists:get_bool(no_shared_fun_wrappers, Opts) of
+ false ->
+ %% The compiler in OTP 23 depends on the on the loader
+ %% using the new indices in funs and being able to have
+ %% multiple make_fun2 instructions referring to the same
+ %% fun entry. Artificially set the highest opcode for the
+ %% module to ensure that it cannot be loaded in OTP 22
+ %% and earlier.
+ Swap = beam_opcodes:opcode(swap, 2),
+ beam_dict:opcode(Swap, Dict);
+ true ->
+ %% Fun wrappers are not shared for compatibility with a
+ %% previous OTP release.
+ Dict
+ end.
on_load(Fs0, Attr0) ->
case proplists:get_value(on_load, Attr0) of
diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl
index 707974b2c1..a734ca3a10 100644
--- a/lib/compiler/src/beam_block.erl
+++ b/lib/compiler/src/beam_block.erl
@@ -33,8 +33,9 @@ module({Mod,Exp,Attr,Fs0,Lc}, _Opts) ->
function({function,Name,Arity,CLabel,Is0}) ->
try
- Is1 = blockify(Is0),
- Is = embed_lines(Is1),
+ Is1 = swap_opt(Is0),
+ Is2 = blockify(Is1),
+ Is = embed_lines(Is2),
{function,Name,Arity,CLabel,Is}
catch
Class:Error:Stack ->
@@ -42,6 +43,40 @@ function({function,Name,Arity,CLabel,Is0}) ->
erlang:raise(Class, Error, Stack)
end.
+%%%
+%%% Try to use a `swap` instruction instead of a sequence of moves.
+%%%
+%%% Note that beam_ssa_codegen generates `swap` instructions only for
+%%% the moves within a single SSA instruction (such as `call`), not
+%%% for the moves generated by a sequence of SSA instructions.
+%%% Therefore, this optimization is needed.
+%%%
+
+swap_opt([{move,Reg1,{x,X}=Temp}=Move1,
+ {move,Reg2,Reg1}=Move2,
+ {move,Temp,Reg2}=Move3|Is]) when Reg1 =/= Temp ->
+ case is_unused(X, Is) of
+ true ->
+ [{swap,Reg1,Reg2}|swap_opt(Is)];
+ false ->
+ [Move1|swap_opt([Move2,Move3|Is])]
+ end;
+swap_opt([I|Is]) ->
+ [I|swap_opt(Is)];
+swap_opt([]) -> [].
+
+is_unused(X, [{call,A,_}|_]) when A =< X -> true;
+is_unused(X, [{call_ext,A,_}|_]) when A =< X -> true;
+is_unused(X, [{make_fun2,_,_,_,A}|_]) when A =< X -> true;
+is_unused(X, [{move,Src,Dst}|Is]) ->
+ case {Src,Dst} of
+ {{x,X},_} -> false;
+ {_,{x,X}} -> true;
+ {_,_} -> is_unused(X, Is)
+ end;
+is_unused(X, [{line,_}|Is]) -> is_unused(X, Is);
+is_unused(_, _) -> false.
+
%% blockify(Instructions0) -> Instructions
%% Collect sequences of instructions to basic blocks.
%% Also do some simple optimations on instructions outside the blocks.
diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl
new file mode 100644
index 0000000000..f4065b2e15
--- /dev/null
+++ b/lib/compiler/src/beam_call_types.erl
@@ -0,0 +1,992 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(beam_call_types).
+
+-include("beam_types.hrl").
+
+-import(lists, [duplicate/2,foldl/3]).
+
+-export([will_succeed/3, types/3]).
+
+%%
+%% Returns whether a call will succeed or not.
+%%
+%% Note that it only answers 'yes' for functions in the 'erlang' module as
+%% calls to other modules may fail due to not being loaded, even if we consider
+%% the module to be known.
+%%
+
+-spec will_succeed(Mod, Func, ArgTypes) -> Result when
+ Mod :: atom(),
+ Func :: atom(),
+ ArgTypes :: [normal_type()],
+ Result :: yes | no | maybe.
+
+will_succeed(erlang, '++', [LHS, _RHS]) ->
+ succeeds_if_type(LHS, proper_list());
+will_succeed(erlang, '--', [LHS, RHS]) ->
+ case {succeeds_if_type(LHS, proper_list()),
+ succeeds_if_type(RHS, proper_list())} of
+ {yes, yes} -> yes;
+ {no, _} -> no;
+ {_, no} -> no;
+ {_, _} -> maybe
+ end;
+will_succeed(erlang, BoolOp, [LHS, RHS]) when BoolOp =:= 'and';
+ BoolOp =:= 'or' ->
+ case {succeeds_if_type(LHS, beam_types:make_boolean()),
+ succeeds_if_type(RHS, beam_types:make_boolean())} of
+ {yes, yes} -> yes;
+ {no, _} -> no;
+ {_, no} -> no;
+ {_, _} -> maybe
+ end;
+will_succeed(erlang, bit_size, [Arg]) ->
+ succeeds_if_type(Arg, #t_bitstring{});
+will_succeed(erlang, byte_size, [Arg]) ->
+ succeeds_if_type(Arg, #t_bitstring{});
+will_succeed(erlang, hd, [Arg]) ->
+ succeeds_if_type(Arg, #t_cons{});
+will_succeed(erlang, is_function, [_,#t_integer{elements={Min,_}}])
+ when Min >= 0 ->
+ yes;
+will_succeed(erlang, is_map_key, [_Key, Map]) ->
+ succeeds_if_type(Map, #t_map{});
+will_succeed(erlang, length, [Arg]) ->
+ succeeds_if_type(Arg, proper_list());
+will_succeed(erlang, map_size, [Arg]) ->
+ succeeds_if_type(Arg, #t_map{});
+will_succeed(erlang, 'not', [Arg]) ->
+ succeeds_if_type(Arg, beam_types:make_boolean());
+will_succeed(erlang, setelement, [#t_integer{elements={Min,Max}},
+ #t_tuple{exact=Exact,size=Size}, _]) ->
+ case Min >= 1 andalso Max =< Size of
+ true -> yes;
+ false when Exact -> no;
+ false -> maybe
+ end;
+will_succeed(erlang, size, [Arg]) ->
+ ArgType = beam_types:join(#t_tuple{}, #t_bitstring{}),
+ succeeds_if_type(Arg, ArgType);
+will_succeed(erlang, tuple_size, [Arg]) ->
+ succeeds_if_type(Arg, #t_tuple{});
+will_succeed(erlang, tl, [Arg]) ->
+ succeeds_if_type(Arg, #t_cons{});
+will_succeed(Mod, Func, Args) ->
+ Arity = length(Args),
+ case erl_bifs:is_safe(Mod, Func, Arity) of
+ true ->
+ yes;
+ false ->
+ case erl_bifs:is_exit_bif(Mod, Func, Arity) of
+ true ->
+ no;
+ false ->
+ %% While we can't infer success for functions outside the
+ %% 'erlang' module (see above comment), it's safe to infer
+ %% failure when we know they return none or if the
+ %% arguments must have certain types.
+ case types(Mod, Func, Args) of
+ {none, _, _} -> no;
+ {_, ArgTypes, _} -> fails_on_conflict(Args, ArgTypes)
+ end
+ end
+ end.
+
+fails_on_conflict([ArgType | Args], [Required | Types]) ->
+ case beam_types:meet(ArgType, Required) of
+ none -> no;
+ _ -> fails_on_conflict(Args, Types)
+ end;
+fails_on_conflict([], []) ->
+ maybe.
+
+succeeds_if_type(ArgType, Required) ->
+ case beam_types:meet(ArgType, Required) of
+ ArgType -> yes;
+ none -> no;
+ _ -> maybe
+ end.
+
+%%
+%% Returns the inferred return and argument types for known functions, and
+%% whether it's safe to subtract argument types on failure.
+%%
+%% Note that the return type will be 'none' if we can statically determine that
+%% the function will fail at runtime.
+%%
+
+-spec types(Mod, Func, ArgTypes) -> {RetType, ArgTypes, CanSubtract} when
+ Mod :: atom(),
+ Func :: atom(),
+ ArgTypes :: [normal_type()],
+ RetType :: type(),
+ CanSubtract :: boolean().
+
+%% Functions that only fail due to bad argument *types*, meaning it's safe to
+%% subtract argument types on failure.
+%%
+%% Note that these are all from the erlang module; suitable functions in other
+%% modules could fail due to the module not being loaded.
+types(erlang, 'map_size', [_]) ->
+ sub_safe(#t_integer{}, [#t_map{}]);
+types(erlang, 'tuple_size', [_]) ->
+ sub_safe(#t_integer{}, [#t_tuple{}]);
+types(erlang, 'bit_size', [_]) ->
+ sub_safe(#t_integer{}, [#t_bitstring{}]);
+types(erlang, 'byte_size', [_]) ->
+ sub_safe(#t_integer{}, [#t_bitstring{}]);
+types(erlang, hd, [Src]) ->
+ RetType = erlang_hd_type(Src),
+ sub_safe(RetType, [#t_cons{}]);
+types(erlang, tl, [Src]) ->
+ RetType = erlang_tl_type(Src),
+ sub_safe(RetType, [#t_cons{}]);
+types(erlang, 'not', [_]) ->
+ Bool = beam_types:make_boolean(),
+ sub_safe(Bool, [Bool]);
+types(erlang, 'length', [_]) ->
+ sub_safe(#t_integer{}, [proper_list()]);
+
+%% Boolean ops
+types(erlang, 'and', [_,_]) ->
+ Bool = beam_types:make_boolean(),
+ sub_unsafe(Bool, [Bool, Bool]);
+types(erlang, 'or', [_,_]) ->
+ Bool = beam_types:make_boolean(),
+ sub_unsafe(Bool, [Bool, Bool]);
+types(erlang, 'xor', [_,_]) ->
+ Bool = beam_types:make_boolean(),
+ sub_unsafe(Bool, [Bool, Bool]);
+
+%% Bitwise ops
+types(erlang, 'band', [_,_]=Args) ->
+ sub_unsafe(erlang_band_type(Args), [#t_integer{}, #t_integer{}]);
+types(erlang, 'bor', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'bxor', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'bsl', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'bsr', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'bnot', [_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}]);
+
+%% Fixed-type arithmetic
+types(erlang, 'float', [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(erlang, 'round', [_]) ->
+ sub_unsafe(#t_integer{}, [number]);
+types(erlang, 'floor', [_]) ->
+ sub_unsafe(#t_integer{}, [number]);
+types(erlang, 'ceil', [_]) ->
+ sub_unsafe(#t_integer{}, [number]);
+types(erlang, 'trunc', [_]) ->
+ sub_unsafe(#t_integer{}, [number]);
+types(erlang, '/', [_,_]) ->
+ sub_unsafe(#t_float{}, [number, number]);
+types(erlang, 'div', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+types(erlang, 'rem', [_,_]) ->
+ sub_unsafe(#t_integer{}, [#t_integer{}, #t_integer{}]);
+
+%% Mixed-type arithmetic; '+'/2 and friends are handled in the catch-all
+%% clause for the 'erlang' module.
+types(erlang, 'abs', [_]=Args) ->
+ mixed_arith_types(Args);
+
+%% List operations
+types(erlang, '++', [LHS, RHS]) ->
+ %% `[] ++ RHS` yields RHS, even if RHS is not a list.
+ ListType = copy_list(LHS, same_length, proper),
+ RetType = beam_types:join(ListType, RHS),
+ sub_unsafe(RetType, [proper_list(), any]);
+types(erlang, '--', [LHS, _]) ->
+ RetType = copy_list(LHS, new_length, proper),
+ sub_unsafe(RetType, [proper_list(), proper_list()]);
+
+types(erlang, 'iolist_to_binary', [_]) ->
+ %% Arg is an iodata(), despite its name.
+ ArgType = beam_types:join(#t_list{}, #t_bitstring{size_unit=8}),
+ sub_unsafe(#t_bitstring{size_unit=8}, [ArgType]);
+types(erlang, 'list_to_binary', [_]) ->
+ %% Arg is an iolist(), despite its name.
+ sub_unsafe(#t_bitstring{size_unit=8}, [#t_list{}]);
+types(erlang, 'list_to_bitstring', [_]) ->
+ %% As list_to_binary but with bitstrings rather than binaries.
+ sub_unsafe(#t_bitstring{}, [proper_list()]);
+
+%% Misc ops.
+types(erlang, 'binary_part', [_, _]) ->
+ PosLen = make_two_tuple(#t_integer{}, #t_integer{}),
+ Binary = #t_bitstring{size_unit=8},
+ sub_unsafe(Binary, [Binary, PosLen]);
+types(erlang, 'binary_part', [_, _, _]) ->
+ Binary = #t_bitstring{size_unit=8},
+ sub_unsafe(Binary, [Binary, #t_integer{}, #t_integer{}]);
+types(erlang, 'is_map_key', [Key, Map]) ->
+ RetType = case erlang_map_get_type(Key, Map) of
+ none -> beam_types:make_atom(false);
+ _ -> beam_types:make_boolean()
+ end,
+ sub_unsafe(RetType, [any, #t_map{}]);
+types(erlang, 'map_get', [Key, Map]) ->
+ RetType = erlang_map_get_type(Key, Map),
+ sub_unsafe(RetType, [any, #t_map{}]);
+types(erlang, 'node', [_]) ->
+ sub_unsafe(#t_atom{}, [any]);
+types(erlang, 'node', []) ->
+ sub_unsafe(#t_atom{}, []);
+types(erlang, 'size', [_]) ->
+ ArgType = beam_types:join(#t_tuple{}, #t_bitstring{}),
+ sub_unsafe(#t_integer{}, [ArgType]);
+
+%% Tuple element ops
+types(erlang, element, [PosType, TupleType]) ->
+ Index = case PosType of
+ #t_integer{elements={Same,Same}} when is_integer(Same) ->
+ Same;
+ _ ->
+ 0
+ end,
+
+ RetType = case TupleType of
+ #t_tuple{size=Sz,elements=Es} when Index =< Sz,
+ Index >= 1 ->
+ beam_types:get_tuple_element(Index, Es);
+ _ ->
+ any
+ end,
+
+ sub_unsafe(RetType, [#t_integer{}, #t_tuple{size=Index}]);
+types(erlang, setelement, [PosType, TupleType, ArgType]) ->
+ RetType = case {PosType,TupleType} of
+ {#t_integer{elements={Index,Index}},
+ #t_tuple{elements=Es0,size=Size}=T} when Index >= 1 ->
+ %% This is an exact index, update the type of said
+ %% element or return 'none' if it's known to be out of
+ %% bounds.
+ Es = beam_types:set_tuple_element(Index, ArgType, Es0),
+ case T#t_tuple.exact of
+ false ->
+ T#t_tuple{size=max(Index, Size),elements=Es};
+ true when Index =< Size ->
+ T#t_tuple{elements=Es};
+ true ->
+ none
+ end;
+ {#t_integer{elements={Min,Max}},
+ #t_tuple{elements=Es0,size=Size}=T} when Min >= 1 ->
+ %% We know this will land between Min and Max, so kill
+ %% the types for those indexes.
+ Es = discard_tuple_element_info(Min, Max, Es0),
+ case T#t_tuple.exact of
+ false ->
+ T#t_tuple{elements=Es,size=max(Min, Size)};
+ true when Min =< Size ->
+ T#t_tuple{elements=Es,size=Size};
+ true ->
+ none
+ end;
+ {_,#t_tuple{}=T} ->
+ %% Position unknown, so we have to discard all element
+ %% information.
+ T#t_tuple{elements=#{}};
+ {#t_integer{elements={Min,_Max}},_} ->
+ #t_tuple{size=Min};
+ {_,_} ->
+ #t_tuple{}
+ end,
+ sub_unsafe(RetType, [#t_integer{}, #t_tuple{}, any]);
+
+types(erlang, make_fun, [_,_,Arity0]) ->
+ Type = case Arity0 of
+ #t_integer{elements={Arity,Arity}} when Arity >= 0 ->
+ #t_fun{arity=Arity};
+ _ ->
+ #t_fun{}
+ end,
+ sub_unsafe(Type, [#t_atom{}, #t_atom{}, #t_integer{}]);
+
+types(erlang, Name, Args) ->
+ Arity = length(Args),
+
+ case erl_bifs:is_exit_bif(erlang, Name, Arity) of
+ true ->
+ {none, Args, false};
+ false ->
+ case erl_internal:arith_op(Name, Arity) of
+ true ->
+ mixed_arith_types(Args);
+ false ->
+ IsTest =
+ erl_internal:new_type_test(Name, Arity) orelse
+ erl_internal:comp_op(Name, Arity),
+
+ RetType = case IsTest of
+ true -> beam_types:make_boolean();
+ false -> any
+ end,
+
+ sub_unsafe(RetType, duplicate(Arity, any))
+ end
+ end;
+
+%%
+%% Math BIFs
+%%
+
+types(math, cos, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, cosh, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, sin, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, sinh, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, tan, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, tanh, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, acos, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, acosh, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, asin, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, asinh, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, atan, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, atanh, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, erf, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, erfc, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, exp, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, log, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, log2, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, log10, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, sqrt, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, atan2, [_,_]) ->
+ sub_unsafe(#t_float{}, [number, number]);
+types(math, pow, [_,_]) ->
+ sub_unsafe(#t_float{}, [number, number]);
+types(math, ceil, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, floor, [_]) ->
+ sub_unsafe(#t_float{}, [number]);
+types(math, fmod, [_,_]) ->
+ sub_unsafe(#t_float{}, [number, number]);
+types(math, pi, []) ->
+ sub_unsafe(#t_float{}, []);
+
+%%
+%% List functions
+%%
+%% These tend to have tricky edge cases around nil and proper lists, be very
+%% careful and try not to narrow the types needlessly. Keep in mind that they
+%% need to be safe regardless of how the function is implemented, so it's best
+%% not to say that a list is proper unless every element must be visited to
+%% succeed.
+%%
+
+%% Operator aliases.
+types(lists, append, [_,_]=Args) ->
+ types(erlang, '++', Args);
+types(lists, append, [_]) ->
+ %% This is implemented through folding the list over erlang:'++'/2, so it
+ %% can hypothetically return anything, but we can infer that its argument
+ %% is a proper list on success.
+ sub_unsafe(any, [proper_list()]);
+types(lists, subtract, [_,_]=Args) ->
+ types(erlang, '--', Args);
+
+%% Functions returning booleans.
+types(lists, all, [_,_]) ->
+ %% This can succeed on improper lists if the fun returns 'false' for an
+ %% element before reaching the end.
+ sub_unsafe(beam_types:make_boolean(), [#t_fun{arity=1}, #t_list{}]);
+types(lists, any, [_,_]) ->
+ %% Doesn't imply that the argument is a proper list; see lists:all/2
+ sub_unsafe(beam_types:make_boolean(), [#t_fun{arity=1}, #t_list{}]);
+types(lists, keymember, [_,_,_]) ->
+ %% Doesn't imply that the argument is a proper list; see lists:all/2
+ sub_unsafe(beam_types:make_boolean(), [any, #t_integer{}, #t_list{}]);
+types(lists, member, [_,_]) ->
+ %% Doesn't imply that the argument is a proper list; see lists:all/2
+ sub_unsafe(beam_types:make_boolean(), [any, #t_list{}]);
+types(lists, prefix, [_,_]) ->
+ %% This function doesn't need to reach the end of either list to return
+ %% false, so we can succeed even when both are improper lists.
+ sub_unsafe(beam_types:make_boolean(), [#t_list{}, #t_list{}]);
+types(lists, suffix, [_,_]) ->
+ %% A different implementation could return true when the first list is nil,
+ %% so we can't tell if either is proper.
+ sub_unsafe(beam_types:make_boolean(), [#t_list{}, #t_list{}]);
+
+%% Simple folds
+types(lists, foldl, [Fun, Init, List]) ->
+ RetType = lists_fold_type(Fun, Init, List),
+ sub_unsafe(RetType, [#t_fun{arity=2}, any, proper_list()]);
+types(lists, foldr, [Fun, Init, List]) ->
+ RetType = lists_fold_type(Fun, Init, List),
+ sub_unsafe(RetType, [#t_fun{arity=2}, any, proper_list()]);
+
+%% Functions returning plain lists.
+types(lists, droplast, [List]) ->
+ RetType = copy_list(List, new_length, proper),
+ sub_unsafe(RetType, [proper_list()]);
+types(lists, dropwhile, [_Fun, List]) ->
+ %% If the element is found before the end of the list, we could return an
+ %% improper list.
+ RetType = copy_list(List, new_length, maybe_improper),
+ sub_unsafe(RetType, [#t_fun{arity=1}, #t_list{}]);
+types(lists, duplicate, [_Count, Element]) ->
+ sub_unsafe(proper_list(Element), [#t_integer{}, any]);
+types(lists, filter, [_Fun, List]) ->
+ RetType = copy_list(List, new_length, proper),
+ sub_unsafe(RetType, [#t_fun{arity=1}, proper_list()]);
+types(lists, flatten, [_]) ->
+ sub_unsafe(proper_list(), [proper_list()]);
+types(lists, map, [Fun, List]) ->
+ RetType = lists_map_type(Fun, List),
+ sub_unsafe(RetType, [#t_fun{arity=1}, proper_list()]);
+types(lists, reverse, [List]) ->
+ RetType = copy_list(List, same_length, proper),
+ sub_unsafe(RetType, [proper_list()]);
+types(lists, sort, [List]) ->
+ RetType = copy_list(List, same_length, proper),
+ sub_unsafe(RetType, [proper_list()]);
+types(lists, takewhile, [_Fun, List]) ->
+ %% Doesn't imply that the argument is a proper list; see lists:all/2
+ RetType = copy_list(List, new_length, proper),
+ sub_unsafe(RetType, [#t_fun{arity=1}, #t_list{}]);
+types(lists, usort, [List]) ->
+ %% The result is not quite the same length, but a non-empty list will stay
+ %% non-empty.
+ RetType = copy_list(List, same_length, proper),
+ sub_unsafe(RetType, [proper_list()]);
+types(lists, zip, [_,_]=Lists) ->
+ {RetType, ArgType} = lists_zip_types(Lists),
+ sub_unsafe(RetType, [ArgType, ArgType]);
+types(lists, zipwith, [Fun | [_,_]=Lists]) ->
+ {RetType, ArgType} = lists_zipwith_types(Fun, Lists),
+ sub_unsafe(RetType, [#t_fun{arity=2}, ArgType, ArgType]);
+
+%% Functions with complex return values.
+types(lists, keyfind, [KeyType,PosType,_]) ->
+ %% Doesn't imply that the argument is a proper list; see lists:all/2
+ TupleType = case PosType of
+ #t_integer{elements={Index,Index}} when is_integer(Index),
+ Index >= 1 ->
+ Es = beam_types:set_tuple_element(Index, KeyType, #{}),
+ #t_tuple{size=Index,elements=Es};
+ _ ->
+ #t_tuple{}
+ end,
+ RetType = beam_types:join(TupleType, beam_types:make_atom(false)),
+ sub_unsafe(RetType, [any, #t_integer{}, #t_list{}]);
+types(lists, MapFold, [Fun, Init, List])
+ when MapFold =:= mapfoldl; MapFold =:= mapfoldr ->
+ RetType = lists_mapfold_type(Fun, Init, List),
+ sub_unsafe(RetType, [#t_fun{arity=2}, any, proper_list()]);
+types(lists, partition, [_Fun, List]) ->
+ ListType = copy_list(List, new_length, proper),
+ RetType = make_two_tuple(ListType, ListType),
+ sub_unsafe(RetType, [#t_fun{arity=1}, proper_list()]);
+types(lists, search, [_,_]) ->
+ %% Doesn't imply that the argument is a proper list; see lists:all/2
+ TupleType = make_two_tuple(beam_types:make_atom(value), any),
+ RetType = beam_types:join(TupleType, beam_types:make_atom(false)),
+ sub_unsafe(RetType, [#t_fun{arity=1}, #t_list{}]);
+types(lists, splitwith, [_Fun, List]) ->
+ %% Only the elements in the left list are guaranteed to be visited, so both
+ %% the argument and the right list may be improper.
+ Left = copy_list(List, new_length, proper),
+ Right = copy_list(List, new_length, maybe_improper),
+ sub_unsafe(make_two_tuple(Left, Right), [#t_fun{arity=1}, #t_list{}]);
+types(lists, unzip, [List]) ->
+ RetType = lists_unzip_type(2, List),
+ sub_unsafe(RetType, [proper_list()]);
+
+%%
+%% Map functions
+%%
+
+types(maps, filter, [_Fun, Map]) ->
+ %% Conservatively assume that key/value types are unchanged.
+ RetType = case Map of
+ #t_map{}=T -> T;
+ _ -> #t_map{}
+ end,
+ sub_unsafe(RetType, [#t_fun{arity=2}, #t_map{}]);
+types(maps, find, [Key, Map]) ->
+ TupleType = case erlang_map_get_type(Key, Map) of
+ none ->
+ none;
+ ValueType ->
+ make_two_tuple(beam_types:make_atom(ok), ValueType)
+ end,
+ %% error | {ok, Value}
+ RetType = beam_types:join(beam_types:make_atom(error), TupleType),
+ sub_unsafe(RetType, [any, #t_map{}]);
+types(maps, fold, [Fun, Init, _Map]) ->
+ RetType = case Fun of
+ #t_fun{type=Type} ->
+ %% The map is potentially empty, so we have to assume it
+ %% can return the initial value.
+ beam_types:join(Type, Init);
+ _ ->
+ any
+ end,
+ sub_unsafe(RetType, [#t_fun{arity=3}, any, #t_map{}]);
+types(maps, from_list, [Pairs]) ->
+ PairType = erlang_hd_type(Pairs),
+ RetType = case beam_types:normalize(PairType) of
+ #t_tuple{elements=Es} ->
+ SKey = beam_types:get_tuple_element(1, Es),
+ SValue = beam_types:get_tuple_element(2, Es),
+ #t_map{super_key=SKey,super_value=SValue};
+ _ ->
+ #t_map{}
+ end,
+ sub_unsafe(RetType, [proper_list()]);
+types(maps, get, [_Key, _Map]=Args) ->
+ types(erlang, map_get, Args);
+types(maps, get, [Key, Map, Default]) ->
+ RetType = case erlang_map_get_type(Key, Map) of
+ none -> Default;
+ ValueType -> beam_types:join(ValueType, Default)
+ end,
+ sub_unsafe(RetType, [any, #t_map{}, any]);
+types(maps, is_key, [_Key, _Map]=Args) ->
+ types(erlang, is_map_key, Args);
+types(maps, keys, [Map]) ->
+ RetType = case Map of
+ #t_map{super_key=none} -> nil;
+ #t_map{super_key=SKey} -> proper_list(SKey);
+ _ -> proper_list()
+ end,
+ sub_unsafe(RetType, [#t_map{}]);
+types(maps, map, [Fun, Map]) ->
+ RetType = case {Fun, Map} of
+ {#t_fun{type=FunRet}, #t_map{super_value=SValue0}} ->
+ SValue = beam_types:join(FunRet, SValue0),
+ Map#t_map{super_value=SValue};
+ _ ->
+ #t_map{}
+ end,
+ sub_unsafe(RetType, [#t_fun{arity=2}, #t_map{}]);
+types(maps, merge, [A, B]) ->
+ RetType = case {A, B} of
+ {#t_map{super_key=SKeyA,super_value=SValueA},
+ #t_map{super_key=SKeyB,super_value=SValueB}} ->
+ SKey = beam_types:join(SKeyA, SKeyB),
+ SValue = beam_types:join(SValueA, SValueB),
+ #t_map{super_key=SKey,super_value=SValue};
+ _ ->
+ #t_map{}
+ end,
+ sub_unsafe(RetType, [#t_map{}, #t_map{}]);
+types(maps, new, []) ->
+ RetType = #t_map{super_key=none,super_value=none},
+ sub_unsafe(RetType, []);
+types(maps, put, [Key, Value, Map]) ->
+ RetType = case Map of
+ #t_map{super_key=SKey0,super_value=SValue0} ->
+ SKey = beam_types:join(Key, SKey0),
+ SValue = beam_types:join(Value, SValue0),
+ #t_map{super_key=SKey,super_value=SValue};
+ _ ->
+ #t_map{}
+ end,
+ sub_unsafe(RetType, [any, any, #t_map{}]);
+types(maps, remove, [Key, Map]) ->
+ RetType = maps_remove_type(Key, Map),
+ sub_unsafe(RetType, [any, #t_map{}]);
+types(maps, take, [Key, Map]) ->
+ TupleType = case erlang_map_get_type(Key, Map) of
+ none ->
+ none;
+ ValueType ->
+ MapType = beam_types:meet(Map, #t_map{}),
+ make_two_tuple(ValueType, MapType)
+ end,
+ %% error | {Value, Map}
+ RetType = beam_types:join(beam_types:make_atom(error), TupleType),
+ sub_unsafe(RetType, [any, #t_map{}]);
+types(maps, to_list, [Map]) ->
+ RetType = case Map of
+ #t_map{super_key=SKey,super_value=SValue} ->
+ proper_list(make_two_tuple(SKey, SValue));
+ _ ->
+ proper_list()
+ end,
+ sub_unsafe(RetType, [#t_map{}]);
+types(maps, update_with, [_Key, Fun, Map]) ->
+ RetType = case {Fun, Map} of
+ {#t_fun{type=FunRet}, #t_map{super_value=SValue0}} ->
+ SValue = beam_types:join(FunRet, SValue0),
+ Map#t_map{super_value=SValue};
+ _ ->
+ #t_map{}
+ end,
+ sub_unsafe(RetType, [any, #t_fun{arity=1}, #t_map{}]);
+types(maps, values, [Map]) ->
+ RetType = case Map of
+ #t_map{super_value=none} -> nil;
+ #t_map{super_value=SValue} -> proper_list(SValue);
+ _ -> proper_list()
+ end,
+ sub_unsafe(RetType, [#t_map{}]);
+types(maps, with, [Keys, Map]) ->
+ RetType = case Map of
+ #t_map{super_key=SKey0} ->
+ %% Since we know that the Map will only contain the pairs
+ %% pointed out by Keys, we can restrict the types to
+ %% those in the list.
+ SKey = beam_types:meet(erlang_hd_type(Keys), SKey0),
+ Map#t_map{super_key=SKey};
+ _ ->
+ #t_map{}
+ end,
+ sub_unsafe(RetType, [proper_list(), #t_map{}]);
+types(maps, without, [Keys, Map]) ->
+ RetType = maps_remove_type(erlang_hd_type(Keys), Map),
+ sub_unsafe(RetType, [proper_list(), #t_map{}]);
+
+%% Catch-all clause for unknown functions.
+
+types(_, _, Args) ->
+ sub_unsafe(any, [any || _ <- Args]).
+
+%%
+%% Function-specific helpers.
+%%
+
+mixed_arith_types([FirstType | _]=Args0) ->
+ RetType = foldl(fun(#t_integer{}, #t_integer{}) -> #t_integer{};
+ (#t_integer{}, number) -> number;
+ (#t_integer{}, #t_float{}) -> #t_float{};
+ (#t_float{}, #t_integer{}) -> #t_float{};
+ (#t_float{}, number) -> #t_float{};
+ (#t_float{}, #t_float{}) -> #t_float{};
+ (number, #t_integer{}) -> number;
+ (number, #t_float{}) -> #t_float{};
+ (number, number) -> number;
+ (any, _) -> number;
+ (_, _) -> none
+ end, FirstType, Args0),
+ sub_unsafe(RetType, [number || _ <- Args0]).
+
+erlang_hd_type(Src) ->
+ case beam_types:meet(Src, #t_cons{}) of
+ #t_cons{type=Type} -> Type;
+ _ -> any
+ end.
+
+erlang_tl_type(Src) ->
+ case beam_types:meet(Src, #t_cons{}) of
+ #t_cons{terminator=Term}=Cons -> beam_types:join(Cons, Term);
+ _ -> any
+ end.
+
+erlang_band_type([#t_integer{elements={Int,Int}}, RHS]) when is_integer(Int) ->
+ erlang_band_type_1(RHS, Int);
+erlang_band_type([LHS, #t_integer{elements={Int,Int}}]) when is_integer(Int) ->
+ erlang_band_type_1(LHS, Int);
+erlang_band_type(_) ->
+ #t_integer{}.
+
+erlang_band_type_1(LHS, Int) ->
+ case LHS of
+ #t_integer{elements={Min0,Max0}} when Max0 - Min0 < 1 bsl 256 ->
+ {Intersection, Union} = range_masks(Min0, Max0),
+
+ Min = Intersection band Int,
+ Max = min(Max0, Union band Int),
+
+ #t_integer{elements={Min,Max}};
+ #t_integer{} when Int >= 0 ->
+ %% The range is either unknown or too wide, conservatively assume
+ %% that the new range is 0 .. Int.
+ %%
+ %% NOTE: We must not produce a singleton type unless we are sure
+ %% that the operation can't fail. Therefore, we only do this
+ %% inference if LHS is known to be an integer.
+ beam_types:meet(LHS, #t_integer{elements={0,Int}});
+ _ ->
+ %% We can't infer boundaries when LHS is not an integer or
+ %% the range is unknown and the other operand is a
+ %% negative number, as the latter sign-extends to infinity
+ %% and we can't express an inverted range at the moment
+ %% (cf. X band -8; either less than -7 or greater than 7).
+ beam_types:meet(LHS, #t_integer{})
+ end.
+
+erlang_map_get_type(Key, Map) ->
+ case Map of
+ #t_map{super_key=SKey,super_value=SValue} ->
+ case beam_types:meet(SKey, Key) of
+ none -> none;
+ _ -> SValue
+ end;
+ _ ->
+ any
+ end.
+
+lists_fold_type(_Fun, Init, nil) ->
+ Init;
+lists_fold_type(#t_fun{type=Type}, _Init, #t_cons{}) ->
+ %% The list is non-empty so it's safe to ignore Init.
+ Type;
+lists_fold_type(#t_fun{type=Type}, Init, #t_list{}) ->
+ %% The list is possibly empty so we have to assume it can return the
+ %% initial value, whose type can differ significantly from the fun's
+ %% return value.
+ beam_types:join(Type, Init);
+lists_fold_type(_Fun, _Init, _List) ->
+ any.
+
+lists_map_type(#t_fun{type=Type}, Types) ->
+ lists_map_type_1(Types, Type);
+lists_map_type(_Fun, Types) ->
+ lists_map_type_1(Types, any).
+
+lists_map_type_1(nil, _ElementType) ->
+ nil;
+lists_map_type_1(#t_cons{}, none) ->
+ %% The list is non-empty and the fun never returns.
+ none;
+lists_map_type_1(#t_cons{}, ElementType) ->
+ proper_cons(ElementType);
+lists_map_type_1(_, none) ->
+ %% The fun never returns, so the only way we could return normally is
+ %% if the list is empty.
+ nil;
+lists_map_type_1(_, ElementType) ->
+ proper_list(ElementType).
+
+lists_mapfold_type(#t_fun{type=#t_tuple{size=2,elements=Es}}, Init, List) ->
+ ElementType = beam_types:get_tuple_element(1, Es),
+ AccType = beam_types:get_tuple_element(2, Es),
+ lists_mapfold_type_1(List, ElementType, Init, AccType);
+lists_mapfold_type(#t_fun{type=none}, _Init, #t_cons{}) ->
+ %% The list is non-empty and the fun never returns.
+ none;
+lists_mapfold_type(#t_fun{type=none}, Init, _List) ->
+ %% The fun never returns, so the only way we could return normally is
+ %% if the list is empty, in which case we'll return [] and the initial
+ %% value.
+ make_two_tuple(nil, Init);
+lists_mapfold_type(_Fun, Init, List) ->
+ lists_mapfold_type_1(List, any, Init, any).
+
+lists_mapfold_type_1(nil, _ElementType, Init, _AccType) ->
+ make_two_tuple(nil, Init);
+lists_mapfold_type_1(#t_cons{}, ElementType, _Init, AccType) ->
+ %% The list has at least one element, so it's safe to ignore Init.
+ make_two_tuple(proper_cons(ElementType), AccType);
+lists_mapfold_type_1(_, ElementType, Init, AccType0) ->
+ %% We can only rely on AccType when we know the list is non-empty, so we
+ %% have to join it with the initial value in case the list is empty.
+ AccType = beam_types:join(AccType0, Init),
+ make_two_tuple(proper_list(ElementType), AccType).
+
+lists_unzip_type(Size, List) ->
+ Es = lut_make_elements(lut_list_types(Size, List), 1, #{}),
+ #t_tuple{size=Size,exact=true,elements=Es}.
+
+lut_make_elements([Type | Types], Index, Es0) ->
+ Es = beam_types:set_tuple_element(Index, Type, Es0),
+ lut_make_elements(Types, Index + 1, Es);
+lut_make_elements([], _Index, Es) ->
+ Es.
+
+lut_list_types(Size, #t_cons{type=#t_tuple{size=Size,elements=Es}}) ->
+ Types = lut_element_types(1, Size, Es),
+ [proper_cons(T) || T <- Types];
+lut_list_types(Size, #t_list{type=#t_tuple{size=Size,elements=Es}}) ->
+ Types = lut_element_types(1, Size, Es),
+ [proper_list(T) || T <- Types];
+lut_list_types(Size, nil) ->
+ lists:duplicate(Size, nil);
+lut_list_types(Size, _) ->
+ lists:duplicate(Size, proper_list()).
+
+lut_element_types(Index, Max, #{}) when Index > Max ->
+ [];
+lut_element_types(Index, Max, Es) ->
+ ElementType = beam_types:get_tuple_element(Index, Es),
+ [ElementType | lut_element_types(Index + 1, Max, Es)].
+
+%% lists:zip/2 and friends only succeed when all arguments have the same
+%% length, so if one of them is #t_cons{}, we can infer that all of them are
+%% #t_cons{} on success.
+
+lists_zip_types(Types) ->
+ lists_zip_types_1(Types, false, #{}, 1).
+
+lists_zip_types_1([nil | _], _AnyCons, _Es, _N) ->
+ %% Early exit; we know the result is [] on success.
+ {nil, nil};
+lists_zip_types_1([#t_cons{type=Type,terminator=nil} | Lists],
+ _AnyCons, Es0, N) ->
+ Es = beam_types:set_tuple_element(N, Type, Es0),
+ lists_zip_types_1(Lists, true, Es, N + 1);
+lists_zip_types_1([#t_list{type=Type,terminator=nil} | Lists],
+ AnyCons, Es0, N) ->
+ Es = beam_types:set_tuple_element(N, Type, Es0),
+ lists_zip_types_1(Lists, AnyCons, Es, N + 1);
+lists_zip_types_1([_ | Lists], AnyCons, Es, N) ->
+ lists_zip_types_1(Lists, AnyCons, Es, N + 1);
+lists_zip_types_1([], true, Es, N) ->
+ %% At least one element was cons, so we know it's non-empty on success.
+ ElementType = #t_tuple{exact=true,size=(N - 1),elements=Es},
+ RetType = proper_cons(ElementType),
+ ArgType = proper_cons(),
+ {RetType, ArgType};
+lists_zip_types_1([], false, Es, N) ->
+ ElementType = #t_tuple{exact=true,size=(N - 1),elements=Es},
+ RetType = proper_list(ElementType),
+ ArgType = proper_list(),
+ {RetType, ArgType}.
+
+lists_zipwith_types(#t_fun{type=Type}, Types) ->
+ lists_zipwith_type_1(Types, Type);
+lists_zipwith_types(_Fun, Types) ->
+ lists_zipwith_type_1(Types, any).
+
+lists_zipwith_type_1([nil | _], _ElementType) ->
+ %% Early exit; we know the result is [] on success.
+ {nil, nil};
+lists_zipwith_type_1([#t_cons{} | _Lists], none) ->
+ %% Early exit; the list is non-empty and we know the fun never
+ %% returns.
+ {none, any};
+lists_zipwith_type_1([#t_cons{} | _Lists], ElementType) ->
+ %% Early exit; we know the result is cons on success.
+ RetType = proper_cons(ElementType),
+ ArgType = proper_cons(),
+ {RetType, ArgType};
+lists_zipwith_type_1([_ | Lists], ElementType) ->
+ lists_zipwith_type_1(Lists, ElementType);
+lists_zipwith_type_1([], none) ->
+ %% Since we know the fun won't return, the only way we could return
+ %% normally is if all lists are empty.
+ {nil, nil};
+lists_zipwith_type_1([], ElementType) ->
+ RetType = proper_list(ElementType),
+ ArgType = proper_list(),
+ {RetType, ArgType}.
+
+maps_remove_type(Key, #t_map{super_key=SKey0}=Map) ->
+ case beam_types:is_singleton_type(Key) of
+ true ->
+ SKey = beam_types:subtract(SKey0, Key),
+ Map#t_map{super_key=SKey};
+ false ->
+ Map
+ end;
+maps_remove_type(_Key, _Map) ->
+ #t_map{}.
+
+%%%
+%%% Generic helpers
+%%%
+
+sub_unsafe(RetType, ArgTypes) ->
+ {RetType, ArgTypes, false}.
+
+sub_safe(RetType, ArgTypes) ->
+ {RetType, ArgTypes, true}.
+
+discard_tuple_element_info(Min, Max, Es) ->
+ foldl(fun(El, Acc) when Min =< El, El =< Max ->
+ maps:remove(El, Acc);
+ (_El, Acc) -> Acc
+ end, Es, maps:keys(Es)).
+
+%% Returns two bitmasks describing all possible values between From and To.
+%%
+%% The first contains the bits that are common to all values, and the second
+%% contains the bits that are set by any value in the range.
+range_masks(From, To) when From =< To ->
+ range_masks_1(From, To, 0, -1, 0).
+
+range_masks_1(From, To, BitPos, Intersection, Union) when From < To ->
+ range_masks_1(From + (1 bsl BitPos), To, BitPos + 1,
+ Intersection band From, Union bor From);
+range_masks_1(_From, To, _BitPos, Intersection0, Union0) ->
+ Intersection = To band Intersection0,
+ Union = To bor Union0,
+ {Intersection, Union}.
+
+proper_cons() ->
+ #t_cons{terminator=nil}.
+
+proper_cons(ElementType) ->
+ #t_cons{type=ElementType,terminator=nil}.
+
+proper_list() ->
+ #t_list{terminator=nil}.
+
+proper_list(ElementType) ->
+ #t_list{type=ElementType,terminator=nil}.
+
+%% Constructs a new list type based on another, optionally keeping the same
+%% length and/or making it proper.
+-spec copy_list(List, Length, Proper) -> type() when
+ List :: type(),
+ Length :: same_length | new_length,
+ Proper :: proper | maybe_improper.
+copy_list(#t_cons{terminator=Term}=T, Length, maybe_improper) ->
+ copy_list_1(T, Length, Term);
+copy_list(#t_list{terminator=Term}=T, Length, maybe_improper) ->
+ copy_list_1(T, Length, Term);
+copy_list(T, Length, proper) ->
+ copy_list_1(T, Length, nil);
+copy_list(T, Length, _Proper) ->
+ copy_list_1(T, Length, any).
+
+copy_list_1(#t_cons{}=T, same_length, Terminator) ->
+ T#t_cons{terminator=Terminator};
+copy_list_1(#t_cons{type=Type}, new_length, Terminator) ->
+ #t_list{type=Type,terminator=Terminator};
+copy_list_1(#t_list{}=T, _Length, Terminator) ->
+ T#t_list{terminator=Terminator};
+copy_list_1(nil, _Length, _Terminator) ->
+ nil;
+copy_list_1(_, _Length, Terminator) ->
+ #t_list{terminator=Terminator}.
+
+make_two_tuple(Type1, Type2) ->
+ Es0 = beam_types:set_tuple_element(1, Type1, #{}),
+ Es = beam_types:set_tuple_element(2, Type2, Es0),
+ #t_tuple{size=2,exact=true,elements=Es}.
diff --git a/lib/compiler/src/beam_clean.erl b/lib/compiler/src/beam_clean.erl
index 7299654476..6b2b2ce085 100644
--- a/lib/compiler/src/beam_clean.erl
+++ b/lib/compiler/src/beam_clean.erl
@@ -34,7 +34,8 @@ module({Mod,Exp,Attr,Fs0,_}, Opts) ->
Used = find_all_used(WorkList, All, cerl_sets:from_list(WorkList)),
Fs1 = remove_unused(Order, Used, All),
{Fs2,Lc} = clean_labels(Fs1),
- Fs = maybe_remove_lines(Fs2, Opts),
+ Fs3 = fix_swap(Fs2, Opts),
+ Fs = maybe_remove_lines(Fs3, Opts),
{ok,{Mod,Exp,Attr,Fs,Lc}}.
%% Determine the rootset, i.e. exported functions and
@@ -137,31 +138,54 @@ function_replace([{function,Name,Arity,Entry,Asm0}|Fs], Dict, Acc) ->
function_replace([], _, Acc) -> Acc.
%%%
+%%% If compatibility with a previous release (OTP 22 or earlier) has
+%%% been requested, replace swap instructions with a sequence of moves.
+%%%
+
+fix_swap(Fs, Opts) ->
+ case proplists:get_bool(no_swap, Opts) of
+ false -> Fs;
+ true -> fold_functions(fun swap_moves/1, Fs)
+ end.
+
+swap_moves([{swap,Reg1,Reg2}|Is]) ->
+ Temp = {x,1022},
+ [{move,Reg1,Temp},{move,Reg2,Reg1},{move,Temp,Reg2}|swap_moves(Is)];
+swap_moves([I|Is]) ->
+ [I|swap_moves(Is)];
+swap_moves([]) -> [].
+
+%%%
%%% Remove line instructions if requested.
%%%
maybe_remove_lines(Fs, Opts) ->
case proplists:get_bool(no_line_info, Opts) of
false -> Fs;
- true -> remove_lines(Fs)
+ true -> fold_functions(fun remove_lines/1, Fs)
end.
-remove_lines([{function,N,A,Lbl,Is0}|T]) ->
- Is = remove_lines_fun(Is0),
- [{function,N,A,Lbl,Is}|remove_lines(T)];
-remove_lines([]) -> [].
-
-remove_lines_fun([{line,_}|Is]) ->
- remove_lines_fun(Is);
-remove_lines_fun([{block,Bl0}|Is]) ->
+remove_lines([{line,_}|Is]) ->
+ remove_lines(Is);
+remove_lines([{block,Bl0}|Is]) ->
Bl = remove_lines_block(Bl0),
- [{block,Bl}|remove_lines_fun(Is)];
-remove_lines_fun([I|Is]) ->
- [I|remove_lines_fun(Is)];
-remove_lines_fun([]) -> [].
+ [{block,Bl}|remove_lines(Is)];
+remove_lines([I|Is]) ->
+ [I|remove_lines(Is)];
+remove_lines([]) -> [].
remove_lines_block([{set,_,_,{line,_}}|Is]) ->
remove_lines_block(Is);
remove_lines_block([I|Is]) ->
[I|remove_lines_block(Is)];
remove_lines_block([]) -> [].
+
+
+%%%
+%%% Helpers.
+%%%
+
+fold_functions(F, [{function,N,A,Lbl,Is0}|T]) ->
+ Is = F(Is0),
+ [{function,N,A,Lbl,Is}|fold_functions(F, T)];
+fold_functions(_F, []) -> [].
diff --git a/lib/compiler/src/beam_dict.erl b/lib/compiler/src/beam_dict.erl
index 1b477d3baf..76d4e62f6b 100644
--- a/lib/compiler/src/beam_dict.erl
+++ b/lib/compiler/src/beam_dict.erl
@@ -36,10 +36,11 @@
-type import_tab() :: gb_trees:tree(mfa(), index()).
-type fname_tab() :: #{Name :: term() => index()}.
-type line_tab() :: #{{Fname :: index(), Line :: term()} => index()}.
--type literal_tab() :: dict:dict(Literal :: term(), index()).
+-type literal_tab() :: #{Literal :: term() => index()}.
-type lambda_info() :: {label(),{index(),label(),non_neg_integer()}}.
-type lambda_tab() :: {non_neg_integer(),[lambda_info()]}.
+-type wrapper() :: #{label() => index()}.
-record(asm,
{atoms = #{} :: atom_tab(),
@@ -48,7 +49,8 @@
imports = gb_trees:empty() :: import_tab(),
strings = <<>> :: binary(), %String pool
lambdas = {0,[]} :: lambda_tab(),
- literals = dict:new() :: literal_tab(),
+ wrappers = #{} :: wrapper(),
+ literals = #{} :: literal_tab(),
fnames = #{} :: fname_tab(),
lines = #{} :: line_tab(),
num_lines = 0 :: non_neg_integer(), %Number of line instructions
@@ -147,22 +149,32 @@ string(BinString, Dict) when is_binary(BinString) ->
-spec lambda(label(), non_neg_integer(), bdict()) ->
{non_neg_integer(), bdict()}.
-lambda(Lbl, NumFree, #asm{lambdas={OldIndex,Lambdas0}}=Dict) ->
- %% Set Index the same as OldIndex.
- Index = OldIndex,
- Lambdas = [{Lbl,{Index,Lbl,NumFree}}|Lambdas0],
- {OldIndex,Dict#asm{lambdas={OldIndex+1,Lambdas}}}.
+lambda(Lbl, NumFree, #asm{wrappers=Wrappers0,
+ lambdas={OldIndex,Lambdas0}}=Dict) ->
+ case Wrappers0 of
+ #{Lbl:=Index} ->
+ %% OTP 23: There old is a fun entry for this wrapper function.
+ %% Share the fun entry.
+ {Index,Dict};
+ #{} ->
+ %% Set Index the same as OldIndex.
+ Index = OldIndex,
+ Wrappers = Wrappers0#{Lbl=>Index},
+ Lambdas = [{Lbl,{Index,Lbl,NumFree}}|Lambdas0],
+ {OldIndex,Dict#asm{wrappers=Wrappers,
+ lambdas={OldIndex+1,Lambdas}}}
+ end.
%% Returns the index for a literal (adding it to the literal table if necessary).
%% literal(Literal, Dict) -> {Index,Dict'}
-spec literal(term(), bdict()) -> {non_neg_integer(), bdict()}.
literal(Lit, #asm{literals=Tab0,next_literal=NextIndex}=Dict) ->
- case dict:find(Lit, Tab0) of
- {ok,Index} ->
+ case Tab0 of
+ #{Lit:=Index} ->
{Index,Dict};
- error ->
- Tab = dict:store(Lit, NextIndex, Tab0),
+ #{} ->
+ Tab = Tab0#{Lit=>NextIndex},
{NextIndex,Dict#asm{literals=Tab,next_literal=NextIndex+1}}
end.
@@ -174,7 +186,7 @@ line([], #asm{num_lines=N}=Dict) ->
%% No location available. Return the special pre-defined
%% index 0.
{0,Dict#asm{num_lines=N+1}};
-line([{location,Name,Line}], #asm{lines=Lines,num_lines=N}=Dict0) ->
+line([{location,Name,Line}|_], #asm{lines=Lines,num_lines=N}=Dict0) ->
{FnameIndex,Dict1} = fname(Name, Dict0),
Key = {FnameIndex,Line},
case Lines of
@@ -182,7 +194,9 @@ line([{location,Name,Line}], #asm{lines=Lines,num_lines=N}=Dict0) ->
_ ->
Index = maps:size(Lines) + 1,
{Index, Dict1#asm{lines=Lines#{Key=>Index},num_lines=N+1}}
- end.
+ end;
+line([_|T], #asm{}=Dict) ->
+ line(T, Dict).
-spec fname(nonempty_string(), bdict()) ->
{non_neg_integer(), bdict()}.
@@ -253,7 +267,7 @@ lambda_table(#asm{locals=Loc0,lambdas={NumLambdas,Lambdas0}}) ->
-spec literal_table(bdict()) -> {non_neg_integer(), [[binary(),...]]}.
literal_table(#asm{literals=Tab,next_literal=NumLiterals}) ->
- L0 = dict:fold(fun(Lit, Num, Acc) ->
+ L0 = maps:fold(fun(Lit, Num, Acc) ->
[{Num,my_term_to_binary(Lit)}|Acc]
end, [], Tab),
L1 = lists:sort(L0),
@@ -261,7 +275,12 @@ literal_table(#asm{literals=Tab,next_literal=NumLiterals}) ->
{NumLiterals,L}.
my_term_to_binary(Term) ->
- term_to_binary(Term, [{minor_version,1}]).
+ %% Use the latest possible minor version. Minor version 2 can be
+ %% be decoded by OTP 16, which is as far back as we have compatibility
+ %% options for the compiler. (When this comment was written, some time
+ %% after the release of OTP 22, the default minor version was 1.)
+
+ term_to_binary(Term, [{minor_version,2}]).
%% Return the line table.
-spec line_table(bdict()) ->
diff --git a/lib/compiler/src/beam_digraph.erl b/lib/compiler/src/beam_digraph.erl
new file mode 100644
index 0000000000..800fcf4c22
--- /dev/null
+++ b/lib/compiler/src/beam_digraph.erl
@@ -0,0 +1,308 @@
+
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Digraph data type. Similar to the digraph module, but provides a
+%% functional API. The functional API allows us to revert to a
+%% previous version of the digraph when an optimization that may have
+%% damaged the digraph has failed.
+%%
+
+-module(beam_digraph).
+
+-export([new/0,
+ add_vertex/2, add_vertex/3, add_edge/3, add_edge/4,
+ del_edge/2, del_edges/2,
+ has_vertex/2,
+ is_path/3,
+ in_degree/2, in_edges/2, in_neighbours/2,
+ out_degree/2, out_edges/2, out_neighbours/2,
+ vertex/2, vertices/1,
+ reverse_postorder/2,
+ roots/1,
+ topsort/1,
+ strong_components/2]).
+
+%% Debugging.
+-define(DEBUG, false).
+-if(?DEBUG).
+-export([dump/1,dump/2,dump/3]).
+-endif.
+
+-import(lists, [foldl/3, reverse/1]).
+
+-type edge_map() :: #{ vertex() => ordsets:ordset(vertex()) }.
+-type vertice_map() :: #{ vertex() => label() }.
+
+-record(dg, {vs = #{} :: vertice_map(),
+ in_es = #{} :: edge_map(),
+ out_es = #{} :: edge_map()}).
+
+-type graph() :: #dg{}.
+
+-type vertex() :: term().
+-type label() :: term().
+-type edge() :: {vertex(), vertex(), label()}.
+
+-spec new() -> graph().
+new() -> #dg{}.
+
+-spec add_vertex(graph(), vertex()) -> graph().
+add_vertex(Dg, V) ->
+ add_vertex(Dg, V, vertex).
+
+-spec add_vertex(graph(), vertex(), label()) -> graph().
+add_vertex(Dg, V, Label) ->
+ #dg{in_es=InEsMap0,out_es=OutEsMap0,vs=Vs0} = Dg,
+ InEsMap = init_edge_map(V, InEsMap0),
+ OutEsMap = init_edge_map(V, OutEsMap0),
+ Vs = Vs0#{V=>Label},
+ Dg#dg{vs=Vs,in_es=InEsMap,out_es=OutEsMap}.
+
+init_edge_map(V, EsMap) ->
+ case is_map_key(V, EsMap) of
+ true ->
+ EsMap;
+ false ->
+ EsMap#{V=>ordsets:new()}
+ end.
+
+-spec add_edge(graph(), vertex(), vertex()) -> graph().
+add_edge(Dg, From, To) ->
+ add_edge(Dg, From, To, edge).
+
+-spec add_edge(graph(), vertex(), vertex(), label()) -> graph().
+add_edge(Dg, From, To, Label) ->
+ #dg{in_es=InEsMap0,out_es=OutEsMap0} = Dg,
+ Name = {From,To,Label},
+ InEsMap = edge_map_add(To, Name, InEsMap0),
+ OutEsMap = edge_map_add(From, Name, OutEsMap0),
+ Dg#dg{in_es=InEsMap,out_es=OutEsMap}.
+
+edge_map_add(V, E, EsMap) ->
+ Es0 = map_get(V, EsMap),
+ Es = ordsets:add_element(E, Es0),
+ EsMap#{V:=Es}.
+
+-spec del_edge(graph(), edge()) -> graph().
+del_edge(Dg, {From,To,_}=E) ->
+ #dg{in_es=InEsMap0,out_es=OutEsMap0} = Dg,
+ InEsMap = edge_map_del(To, E, InEsMap0),
+ OutEsMap = edge_map_del(From, E, OutEsMap0),
+ Dg#dg{in_es=InEsMap,out_es=OutEsMap}.
+
+edge_map_del(V, E, EsMap) ->
+ Es0 = map_get(V, EsMap),
+ Es = Es0 -- [E],
+ EsMap#{V:=Es}.
+
+-spec del_edges(graph(), [edge()]) -> graph().
+del_edges(G, Es) when is_list(Es) ->
+ foldl(fun(E, A) -> del_edge(A, E) end, G, Es).
+
+-spec has_vertex(graph(), vertex()) -> boolean().
+has_vertex(#dg{vs=Vs}, V) ->
+ is_map_key(V, Vs).
+
+-spec in_degree(graph(), vertex()) -> non_neg_integer().
+in_degree(#dg{in_es=InEsMap}, V) ->
+ length(map_get(V, InEsMap)).
+
+-spec in_edges(graph(), vertex()) -> [edge()].
+in_edges(#dg{in_es=InEsMap}, V) ->
+ map_get(V, InEsMap).
+
+-spec in_neighbours(graph(), vertex()) -> [vertex()].
+in_neighbours(#dg{in_es=InEsMap}, V) ->
+ [From || {From,_,_} <- map_get(V, InEsMap)].
+
+-spec is_path(graph(), vertex(), vertex()) -> boolean().
+is_path(G, From, To) ->
+ Seen = cerl_sets:new(),
+ try
+ _ = is_path_1([From], To, G, Seen),
+ false
+ catch
+ throw:true ->
+ true
+ end.
+
+is_path_1([To|_], To, _G, _Seen) ->
+ throw(true);
+is_path_1([V|Vs], To, G, Seen0) ->
+ case cerl_sets:is_element(V, Seen0) of
+ true ->
+ is_path_1(Vs, To, G, Seen0);
+ false ->
+ Seen1 = cerl_sets:add_element(V, Seen0),
+ Successors = out_neighbours(G, V),
+ Seen = is_path_1(Successors, To, G, Seen1),
+ is_path_1(Vs, To, G, Seen)
+ end;
+is_path_1([], _To, _G, Seen) ->
+ Seen.
+
+-spec out_degree(graph(), vertex()) -> non_neg_integer().
+out_degree(#dg{out_es=OutEsMap}, V) ->
+ length(map_get(V, OutEsMap)).
+
+-spec out_edges(graph(), vertex()) -> [edge()].
+out_edges(#dg{out_es=OutEsMap}, V) ->
+ map_get(V, OutEsMap).
+
+-spec out_neighbours(graph(), vertex()) -> [vertex()].
+out_neighbours(#dg{out_es=OutEsMap}, V) ->
+ [To || {_,To,_} <- map_get(V, OutEsMap)].
+
+-spec vertex(graph(), vertex()) -> label().
+vertex(#dg{vs=Vs}, V) ->
+ map_get(V, Vs).
+
+-spec vertices(graph()) -> [{vertex(), label()}].
+vertices(#dg{vs=Vs}) ->
+ maps:to_list(Vs).
+
+-spec reverse_postorder(graph(), [vertex()]) -> [vertex()].
+reverse_postorder(G, Vs) ->
+ Seen = cerl_sets:new(),
+ {RPO, _} = reverse_postorder_1(Vs, G, Seen, []),
+ RPO.
+
+reverse_postorder_1([V|Vs], G, Seen0, Acc0) ->
+ case cerl_sets:is_element(V, Seen0) of
+ true ->
+ reverse_postorder_1(Vs, G, Seen0, Acc0);
+ false ->
+ Seen1 = cerl_sets:add_element(V, Seen0),
+ Successors = out_neighbours(G, V),
+ {Acc,Seen} = reverse_postorder_1(Successors, G, Seen1, Acc0),
+ reverse_postorder_1(Vs, G, Seen, [V|Acc])
+ end;
+reverse_postorder_1([], _, Seen, Acc) ->
+ {Acc, Seen}.
+
+-spec roots(graph()) -> [vertex()].
+roots(G) ->
+ roots_1(vertices(G), G).
+
+roots_1([{V,_}|Vs], G) ->
+ case in_degree(G, V) of
+ 0 ->
+ [V|roots_1(Vs, G)];
+ _ ->
+ roots_1(Vs, G)
+ end;
+roots_1([], _G) -> [].
+
+-spec topsort(graph()) -> [vertex()].
+topsort(G) ->
+ Seen = roots(G),
+ reverse_postorder(G, Seen).
+
+%%
+%% Kosaraju's algorithm
+%%
+%% Visit each node in reverse post order. If the node has not been assigned to
+%% a component yet, start a new component and add all of its in-neighbors to it
+%% if they don't yet belong to one. Keep going until all nodes have been
+%% visited.
+%%
+%% https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
+%%
+
+-spec strong_components(graph(), [vertex()]) -> ComponentMap when
+ %% Vertices together with their components.
+ ComponentMap :: #{ vertex() => [vertex()] }.
+strong_components(G, Vs) ->
+ sc_1(Vs, G, #{}, #{}).
+
+sc_1([V | Vs], G, Roots0, Components) when not is_map_key(V, Roots0) ->
+ %% V has not been assigned to a component, start a new one with this one as
+ %% the root.
+ {Roots, Component} = sc_2([V], G, V, Roots0, []),
+ sc_1(Vs, G, Roots, Components#{ V => Component });
+sc_1([V | Vs], G, Roots, Components0) ->
+ %% V is already part of a component, copy it over.
+ Root = map_get(V, Roots),
+ Components = Components0#{ V => map_get(Root, Components0) },
+
+ sc_1(Vs, G, Roots, Components);
+sc_1([], _G, _Roots, Components) ->
+ Components.
+
+sc_2([V | Vs], G, Root, Roots, Acc) when not is_map_key(V, Roots) ->
+ %% V has not been assigned to a component, so assign it to the current one.
+ sc_2(in_neighbours(G, V) ++ Vs, G, Root, Roots#{ V => Root }, [V | Acc]);
+sc_2([_V | Vs], G, Root, Roots, Acc) ->
+ %% V is already part of a component, skip it.
+ sc_2(Vs, G, Root, Roots, Acc);
+sc_2([], _G, _Root, Roots, Acc) ->
+ {Roots, reverse(Acc)}.
+
+-if(?DEBUG).
+
+%%
+%% Dumps the graph as a string in dot (graphviz) format.
+%%
+%% Use dot(1) to convert to an image:
+%%
+%% dot [input] -T[format]
+%% dot graph_file -Tsvg > graph.svg
+
+-spec dump(any()) -> any().
+dump(G) ->
+ Formatter = fun(Node) -> io_lib:format("~p", [Node]) end,
+ io:format("~s", [dump_1(G, Formatter)]).
+
+-spec dump(any(), any()) -> any().
+dump(G, FileName) ->
+ Formatter = fun(Node) -> io_lib:format("~p", [Node]) end,
+ dump(G, FileName, Formatter).
+
+-spec dump(any(), any(), any()) -> any().
+dump(G, FileName, Formatter) ->
+ {ok, Fd} = file:open(FileName, [write]),
+ io:fwrite(Fd, "~s", [dump_1(G, Formatter)]),
+ file:close(Fd).
+
+dump_1(G, Formatter) ->
+ Vs = maps:keys(G#dg.vs),
+
+ {Map, Vertices} = dump_vertices(Vs, 0, Formatter,#{}, []),
+ Edges = dump_edges(Vs, G, Map, []),
+
+ io_lib:format("digraph g {~n~s~n~s~n}~n", [Vertices, Edges]).
+
+dump_vertices([V | Vs], Counter, Formatter, Map, Acc) ->
+ VerticeSlug = io_lib:format(" ~p [label=\"~s\"]~n",
+ [Counter, Formatter(V)]),
+ dump_vertices(Vs, Counter + 1, Formatter,
+ Map#{ V => Counter }, [VerticeSlug | Acc]);
+dump_vertices([], _Counter, _Formatter, Map, Acc) ->
+ {Map, Acc}.
+
+dump_edges([V | Vs], G, Map, Acc) ->
+ SelfId = map_get(V, Map),
+ EdgeSlug = [io_lib:format(" ~p -> ~p~n", [SelfId, map_get(To, Map)]) ||
+ {_, To, _} <- out_edges(G, V)],
+ dump_edges(Vs, G, Map, [EdgeSlug | Acc]);
+dump_edges([], _G, _Map, Acc) ->
+ Acc.
+
+-endif.
diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl
index 7d048716e4..c52edd6635 100644
--- a/lib/compiler/src/beam_disasm.erl
+++ b/lib/compiler/src/beam_disasm.erl
@@ -1123,6 +1123,15 @@ resolve_inst({put_tuple2,[Dst,{{z,1},{u,_},List0}]},_,_,_) ->
{put_tuple2,Dst,{list,List}};
%%
+%% OTP 23.
+%%
+resolve_inst({bs_start_match4,[Fail,Live,Src,Dst]},_,_,_) ->
+ {bs_start_match4,Fail,Live,Src,Dst};
+resolve_inst({swap,[_,_]=List},_,_,_) ->
+ [R1,R2] = resolve_args(List),
+ {swap,R1,R2};
+
+%%
%% Catches instructions that are not yet handled.
%%
resolve_inst(X,_,_,_) -> ?exit({resolve_inst,X}).
diff --git a/lib/compiler/src/beam_except.erl b/lib/compiler/src/beam_except.erl
deleted file mode 100644
index aef34e38a6..0000000000
--- a/lib/compiler/src/beam_except.erl
+++ /dev/null
@@ -1,256 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2011-2020. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
--module(beam_except).
--export([module/2]).
-
-%%% Rewrite certain calls to erlang:error/{1,2} to specialized
-%%% instructions:
-%%%
-%%% erlang:error({badmatch,Value}) => badmatch Value
-%%% erlang:error({case_clause,Value}) => case_end Value
-%%% erlang:error({try_clause,Value}) => try_case_end Value
-%%% erlang:error(if_clause) => if_end
-%%% erlang:error(function_clause, Args) => jump FuncInfoLabel
-%%%
-
--import(lists, [reverse/1,reverse/2,seq/2,splitwith/2]).
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc}, _Opt) ->
- Fs = [function(F) || F <- Fs0],
- {ok,{Mod,Exp,Attr,Fs,Lc}}.
-
-function({function,Name,Arity,CLabel,Is0}) ->
- try
- Is = function_1(Is0),
- {function,Name,Arity,CLabel,Is}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
--record(st,
- {lbl :: beam_asm:label(), %func_info label
- loc :: [_], %location for func_info
- arity :: arity() %arity for function
- }).
-
-function_1(Is0) ->
- case Is0 of
- [{label,Lbl},{line,Loc},{func_info,_,_,Arity}|_] ->
- St = #st{lbl=Lbl,loc=Loc,arity=Arity},
- translate(Is0, St, []);
- [{label,_}|_] ->
- %% No line numbers. The source must be a .S file.
- %% There is no need to do anything.
- Is0
- end.
-
-translate([{call_ext,Ar,{extfunc,erlang,error,Ar}}=I|Is], St, Acc) ->
- translate_1(Ar, I, Is, St, Acc);
-translate([I|Is], St, Acc) ->
- translate(Is, St, [I|Acc]);
-translate([], _, Acc) ->
- reverse(Acc).
-
-translate_1(Ar, I, Is, #st{arity=Arity}=St, [{line,_}=Line|Acc1]=Acc0) ->
- case dig_out(Ar, Arity, Acc1) of
- no ->
- translate(Is, St, [I|Acc0]);
- {yes,function_clause,Acc2} ->
- case {Is,Line,St} of
- {[return|_],{line,Loc},#st{lbl=Fi,loc=Loc}} ->
- Instr = {jump,{f,Fi}},
- translate(Is, St, [Instr|Acc2]);
- {_,_,_} ->
- %% Not a call_only instruction, or not the same
- %% location information as in in the line instruction
- %% before the func_info instruction. Not safe
- %% to translate to a jump.
- translate(Is, St, [I|Acc0])
- end;
- {yes,Instr,Acc2} ->
- translate(Is, St, [Instr,Line|Acc2])
- end.
-
-dig_out(1, _Arity, Is) ->
- dig_out(Is);
-dig_out(2, Arity, Is) ->
- dig_out_fc(Arity, Is);
-dig_out(_, _, _) -> no.
-
-dig_out([{block,Bl0}|Is]) ->
- case dig_out_block(reverse(Bl0)) of
- no -> no;
- {yes,What,[]} ->
- {yes,What,Is};
- {yes,What,Bl} ->
- {yes,What,[{block,Bl}|Is]}
- end;
-dig_out(_) -> no.
-
-dig_out_block([{set,[{x,0}],[{atom,if_clause}],move}]) ->
- {yes,if_end,[]};
-dig_out_block([{set,[{x,0}],[{literal,{Exc,Value}}],move}|Is]) ->
- translate_exception(Exc, {literal,Value}, Is, 0);
-dig_out_block([{set,[{x,0}],[{atom,Exc},Value],put_tuple2}|Is]) ->
- translate_exception(Exc, Value, Is, 3);
-dig_out_block(_) -> no.
-
-translate_exception(badmatch, Val, Is, Words) ->
- {yes,{badmatch,Val},fix_block(Is, Words)};
-translate_exception(case_clause, Val, Is, Words) ->
- {yes,{case_end,Val},fix_block(Is, Words)};
-translate_exception(try_clause, Val, Is, Words) ->
- {yes,{try_case_end,Val},fix_block(Is, Words)};
-translate_exception(_, _, _, _) -> no.
-
-fix_block(Is, 0) ->
- reverse(Is);
-fix_block(Is, Words) ->
- reverse(fix_block_1(Is, Words)).
-
-fix_block_1([{set,[],[],{alloc,Live,{F1,F2,Needed0,F3}}}|Is], Words)
- when is_integer(Needed0) ->
- case Needed0 - Words of
- 0 ->
- Is;
- Needed ->
- true = Needed >= 0, %Assertion.
- [{set,[],[],{alloc,Live,{F1,F2,Needed,F3}}}|Is]
- end;
-fix_block_1([I|Is], Words) ->
- [I|fix_block_1(Is, Words)];
-fix_block_1([], _Words) ->
- %% Rare. The heap allocation was probably done by a binary
- %% construction instruction.
- [].
-
-dig_out_fc(Arity, Is0) ->
- Regs0 = maps:from_list([{{x,X},{arg,X}} || X <- seq(0, Arity-1)]),
- {Is,Acc0} = splitwith(fun({label,_}) -> false;
- ({test,_,_,_}) -> false;
- (_) -> true
- end, Is0),
- {Regs,Acc} = dig_out_fc_1(reverse(Is), Arity, Regs0, Acc0),
- case Regs of
- #{{x,0}:={atom,function_clause},{x,1}:=Args} ->
- case moves_from_stack(Args, 0, []) of
- {Moves,Arity} ->
- {yes,function_clause,reverse(Moves, Acc)};
- {_,_} ->
- no
- end;
- #{} ->
- no
- end.
-
-dig_out_fc_1([{block,Bl}|Is], Arity, Regs0, Acc) ->
- Regs = dig_out_fc_block(Bl, Regs0),
- dig_out_fc_1(Is, Arity, Regs, Acc);
-dig_out_fc_1([{bs_set_position,_,_}=I|Is], Arity, Regs, Acc) ->
- dig_out_fc_1(Is, Arity, Regs, [I|Acc]);
-dig_out_fc_1([{bs_get_tail,Src,Dst,Live0}|Is], Arity, Regs0, Acc) ->
- case Src of
- {x,X} when X < Arity ->
- %% The heuristic for determining the number of live
- %% registers is likely to give an incorrect result.
- %% Give up.
- {#{},[]};
- _ ->
- Regs = prune_xregs(Live0, Regs0),
- Live = dig_out_stack_live(Regs, Live0),
- I = {bs_get_tail,Src,Dst,Live},
- dig_out_fc_1(Is, Arity, Regs, [I|Acc])
- end;
-dig_out_fc_1([_|_], _Arity, _Regs, _Acc) ->
- {#{},[]};
-dig_out_fc_1([], _Arity, Regs, Acc) ->
- {Regs,Acc}.
-
-dig_out_fc_block([{set,[],[],{alloc,Live,_}}|Is], Regs0) ->
- Regs = prune_xregs(Live, Regs0),
- dig_out_fc_block(Is, Regs);
-dig_out_fc_block([{set,[Dst],[Hd,Tl],put_list}|Is], Regs0) ->
- Regs = Regs0#{Dst=>{cons,get_reg(Hd, Regs0),get_reg(Tl, Regs0)}},
- dig_out_fc_block(Is, Regs);
-dig_out_fc_block([{set,[Dst],[Src],move}|Is], Regs0) ->
- Regs = Regs0#{Dst=>get_reg(Src, Regs0)},
- dig_out_fc_block(Is, Regs);
-dig_out_fc_block([{set,_,_,_}|_], _Regs) ->
- %% Unknown instruction. Fail.
- #{};
-dig_out_fc_block([], Regs) -> Regs.
-
-dig_out_stack_live(Regs, Default) ->
- Reg = {x,2},
- case Regs of
- #{Reg:=List} ->
- dig_out_stack_live_1(List, Default);
- #{} ->
- Default
- end.
-
-dig_out_stack_live_1({cons,{arg,N},T}, Live) ->
- dig_out_stack_live_1(T, max(N + 1, Live));
-dig_out_stack_live_1({cons,_,T}, Live) ->
- dig_out_stack_live_1(T, Live);
-dig_out_stack_live_1(nil, Live) ->
- Live;
-dig_out_stack_live_1(_, Live) -> Live.
-
-prune_xregs(Live, Regs) ->
- maps:filter(fun({x,X}, _) -> X < Live end, Regs).
-
-moves_from_stack({cons,{arg,N},_}, I, _Acc) when N =/= I ->
- %% Wrong argument. Give up.
- {[],-1};
-moves_from_stack({cons,H,T}, I, Acc) ->
- case H of
- {arg,I} ->
- moves_from_stack(T, I+1, Acc);
- _ ->
- moves_from_stack(T, I+1, [{move,H,{x,I}}|Acc])
- end;
-moves_from_stack(nil, I, Acc) ->
- {reverse(Acc),I};
-moves_from_stack({literal,[H|T]}, I, Acc) ->
- Cons = {cons,tag_literal(H),tag_literal(T)},
- moves_from_stack(Cons, I, Acc);
-moves_from_stack(_, _, _) ->
- %% Not understood. Give up.
- {[],-1}.
-
-
-get_reg(R, Regs) ->
- case Regs of
- #{R:=Val} -> Val;
- #{} -> R
- end.
-
-tag_literal([]) -> nil;
-tag_literal(T) when is_atom(T) -> {atom,T};
-tag_literal(T) when is_float(T) -> {float,T};
-tag_literal(T) when is_integer(T) -> {integer,T};
-tag_literal(T) -> {literal,T}.
diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl
index 35e085c6d2..0cb9c12f12 100644
--- a/lib/compiler/src/beam_jump.erl
+++ b/lib/compiler/src/beam_jump.erl
@@ -132,10 +132,12 @@
%%% on the program state.
%%%
--import(lists, [foldl/3,mapfoldl/3,reverse/1,reverse/2]).
+-import(lists, [foldl/3,keymember/3,mapfoldl/3,reverse/1,reverse/2]).
-type instruction() :: beam_utils:instruction().
+-include("beam_types.hrl").
+
-spec module(beam_utils:module_code(), [compile:option()]) ->
{'ok',beam_utils:module_code()}.
@@ -189,6 +191,15 @@ eliminate_moves([{test,is_eq_exact,_,[Reg,Val]}=I,
RegVal = {Reg,Val},
BlkIs = eliminate_moves_blk(BlkIs0, RegVal),
eliminate_moves([{block,BlkIs}|Is], D, [I|Acc]);
+eliminate_moves([{test,is_nonempty_list,Fail,[Reg]}=I|Is], D0, Acc) ->
+ case is_proper_list(Reg, Acc) of
+ true ->
+ D = update_value_dict([nil,Fail], Reg, D0),
+ eliminate_moves(Is, D, [I|Acc]);
+ false ->
+ D = update_unsafe_labels(I, D0),
+ eliminate_moves(Is, D, [I|Acc])
+ end;
eliminate_moves([{label,Lbl},{block,BlkIs0}=Blk|Is], D, Acc0) ->
Acc = [{label,Lbl}|Acc0],
case {no_fallthrough(Acc0),D} of
@@ -198,6 +209,10 @@ eliminate_moves([{label,Lbl},{block,BlkIs0}=Blk|Is], D, Acc0) ->
{_,_} ->
eliminate_moves([Blk|Is], D, Acc)
end;
+eliminate_moves([{call,_,_}=I|Is], D, Acc) ->
+ eliminate_moves_call(Is, D, [I | Acc]);
+eliminate_moves([{call_ext,_,_}=I|Is], D, Acc) ->
+ eliminate_moves_call(Is, D, [I | Acc]);
eliminate_moves([{block,[]}|Is], D, Acc) ->
%% Empty blocks can prevent further jump optimizations.
eliminate_moves(Is, D, Acc);
@@ -206,6 +221,21 @@ eliminate_moves([I|Is], D0, Acc) ->
eliminate_moves(Is, D, [I|Acc]);
eliminate_moves([], _, Acc) -> reverse(Acc).
+eliminate_moves_call([{'%',{var_info,{x,0},Info}}=Anno,
+ {block,BlkIs0}=Blk | Is], D, Acc0) ->
+ Acc = [Anno | Acc0],
+ RetType = proplists:get_value(type, Info, none),
+ case beam_types:get_singleton_value(RetType) of
+ {ok, Value} ->
+ RegVal = {{x,0}, value_to_literal(Value)},
+ BlkIs = eliminate_moves_blk(BlkIs0, RegVal),
+ eliminate_moves([{block,BlkIs}|Is], D, Acc);
+ error ->
+ eliminate_moves(Is, D, [Blk | Acc])
+ end;
+eliminate_moves_call(Is, D, Acc) ->
+ eliminate_moves(Is, D, Acc).
+
eliminate_moves_blk([{set,[Dst],[_],move}|_]=Is, {_,Dst}) ->
Is;
eliminate_moves_blk([{set,[Dst],[Lit],move}|Is], {Dst,Lit}) ->
@@ -217,9 +247,29 @@ eliminate_moves_blk([{set,[_],[_],move}=I|Is], {_,_}=RegVal) ->
[I|eliminate_moves_blk(Is, RegVal)];
eliminate_moves_blk(Is, _) -> Is.
+no_fallthrough([{'%',_} | Is]) ->
+ no_fallthrough(Is);
no_fallthrough([I|_]) ->
is_unreachable_after(I).
+is_proper_list(Reg, [{'%',{var_info,Reg,Info}}|_]) ->
+ case proplists:get_value(type, Info) of
+ #t_list{terminator=nil} ->
+ true;
+ _ ->
+ %% Unknown type or not a proper list.
+ false
+ end;
+is_proper_list(Reg, [{'%',{var_info,_,_}}|Is]) ->
+ is_proper_list(Reg, Is);
+is_proper_list(_, _) -> false.
+
+value_to_literal([]) -> nil;
+value_to_literal(A) when is_atom(A) -> {atom,A};
+value_to_literal(F) when is_float(F) -> {float,F};
+value_to_literal(I) when is_integer(I) -> {integer,I};
+value_to_literal(Other) -> {literal,Other}.
+
update_value_dict([Lit,{f,Lbl}|T], Reg, D0) ->
D = case D0 of
#{Lbl:=unsafe} -> D0;
@@ -267,92 +317,171 @@ insert_labels([], Lc, Acc) ->
%%% (1) We try to share the code for identical code segments by replacing all
%%% occurrences except the last with jumps to the last occurrence.
%%%
+%%% We must not share code that raises an exception from outside a
+%%% try/catch block with code inside a try/catch block and vice versa,
+%%% because beam_validator will probably flag it as unsafe
+%%% (ambiguous_catch_try_state). The same goes for a plain catch.
+%%%
share(Is0) ->
Is1 = eliminate_fallthroughs(Is0, []),
Is2 = find_fixpoint(fun(Is) ->
- share_1(Is, #{}, #{}, [], [])
+ share_1(Is)
end, Is1),
reverse(Is2).
-share_1([{label,L}=Lbl|Is], Dict0, Lbls0, [_|_]=Seq, Acc) ->
- case maps:find(Seq, Dict0) of
- error ->
- Dict = case is_shareable(Seq) of
- true ->
- maps:put(Seq, L, Dict0);
- false ->
- Dict0
- end,
- share_1(Is, Dict, Lbls0, [], [[Lbl|Seq]|Acc]);
- {ok,Label} ->
- Lbls = maps:put(L, Label, Lbls0),
- share_1(Is, Dict0, Lbls, [], [[Lbl,{jump,{f,Label}}]|Acc])
+share_1(Is) ->
+ Safe = classify_labels(Is),
+ share_1(Is, Safe, #{}, #{}, [], []).
+
+%% Note that we examine the instructions in reverse execution order.
+share_1([{label,L}=Lbl|Is], Safe, Dict0, Lbls0, [_|_]=Seq0, Acc) ->
+ Seq = maybe_add_scope(Seq0, L, Safe),
+
+ %% If there are try/catch or catch instructions in this function,
+ %% any line instructions in the sequence now include a scope.
+ case Dict0 of
+ #{Seq := Label} ->
+ %% This sequence of instructions has been seen previously.
+ %% The scope identifiers added to line instructions ensure
+ %% that two sequence will not be equal unless sharing is
+ %% also safe.
+ Lbls = Lbls0#{L => Label},
+ share_1(Is, Safe, Dict0, Lbls, [],
+ [[Lbl,{jump,{f,Label}}]|Acc]);
+ #{} ->
+ %% This is first time we have seen this sequence of instructions.
+ %% Find out whether it is safe to share the sequence.
+ case map_size(Safe) =:= 0 orelse is_shareable(Seq) of
+ true ->
+ %% Either this function does not contain any try/catch
+ %% instructions, in which case it is always safe to share
+ %% exception-raising instructions such as if_end and
+ %% case_end, or it this sequence does not include
+ %% any of the problematic instructions.
+ Dict = Dict0#{Seq => L},
+ share_1(Is, Safe, Dict, Lbls0, [], [[Lbl|Seq]|Acc]);
+ false ->
+ %% The sequence includes an inappropriate instruction
+ %% that may case beam_validator to complain about
+ %% an ambiguous try/catch state.
+ share_1(Is, Safe, Dict0, Lbls0, [], [[Lbl|Seq]|Acc])
+ end
end;
-share_1([{func_info,_,_,_}|_]=Is0, _, Lbls, [], Acc0) when Lbls =/= #{} ->
- lists:foldl(fun(Is, Acc) ->
- beam_utils:replace_labels(Is, Acc, Lbls, fun(Old) -> Old end)
- end, Is0, Acc0);
-share_1([{func_info,_,_,_}|_]=Is, _, Lbls, [], Acc) when Lbls =:= #{} ->
- lists:foldl(fun lists:reverse/2, Is, Acc);
-share_1([{'catch',_,_}=I|Is], Dict0, Lbls0, Seq, Acc) ->
- {Dict,Lbls} = clean_non_sharable(Dict0, Lbls0),
- share_1(Is, Dict, Lbls, [I|Seq], Acc);
-share_1([{'try',_,_}=I|Is], Dict0, Lbls0, Seq, Acc) ->
- {Dict,Lbls} = clean_non_sharable(Dict0, Lbls0),
- share_1(Is, Dict, Lbls, [I|Seq], Acc);
-share_1([{try_case,_}=I|Is], Dict0, Lbls0, Seq, Acc) ->
- {Dict,Lbls} = clean_non_sharable(Dict0, Lbls0),
- share_1(Is, Dict, Lbls, [I|Seq], Acc);
-share_1([{catch_end,_}=I|Is], Dict0, Lbls0, Seq, Acc) ->
- {Dict,Lbls} = clean_non_sharable(Dict0, Lbls0),
- share_1(Is, Dict, Lbls, [I|Seq], Acc);
-share_1([{jump,{f,To}}=I,{label,L}=Lbl|Is], Dict0, Lbls0, _Seq, Acc) ->
- Lbls = maps:put(L, To, Lbls0),
- share_1(Is, Dict0, Lbls, [], [[Lbl,I]|Acc]);
-share_1([I|Is], Dict, Lbls, Seq, Acc) ->
+share_1([{func_info,_,_,_}|_]=Is0, _Safe, _, Lbls, [], Acc0) ->
+ %% Replace jumps to jumps with a jump to the final destination
+ %% (jump threading). This optimization is done in the main
+ %% optimization pass of this module, but we do it here too because
+ %% it can give more opportunities for sharing code.
+ F = case Lbls =:= #{} of
+ true ->
+ fun lists:reverse/2;
+ false ->
+ fun(Is, Acc) ->
+ beam_utils:replace_labels(Is, Acc, Lbls,
+ fun(Old) -> Old end)
+ end
+ end,
+ foldl(F, Is0, Acc0);
+share_1([{'catch',_,_}=I|Is], Safe, Dict, _Lbls0, Seq, Acc) ->
+ %% Disable the jump threading optimization because it may be unsafe.
+ share_1(Is, Safe, Dict, #{}, [I|Seq], Acc);
+share_1([{'try',_,_}=I|Is], Safe, Dict, _Lbls, Seq, Acc) ->
+ %% Disable the jump threading optimization because it may be unsafe.
+ share_1(Is, Safe, Dict, #{}, [I|Seq], Acc);
+share_1([{jump,{f,To}}=I,{label,From}=Lbl|Is], Safe, Dict0, Lbls0, _Seq, Acc) ->
+ Lbls = Lbls0#{From => To},
+ share_1(Is, Safe, Dict0, Lbls, [], [[Lbl,I]|Acc]);
+share_1([I|Is], Safe, Dict, Lbls, Seq, Acc) ->
case is_unreachable_after(I) of
false ->
- share_1(Is, Dict, Lbls, [I|Seq], Acc);
+ share_1(Is, Safe, Dict, Lbls, [I|Seq], Acc);
true ->
- share_1(Is, Dict, Lbls, [I], Acc)
+ share_1(Is, Safe, Dict, Lbls, [I], Acc)
+ end.
+
+%% If the label has a scope set, assign it to any line instruction
+%% in the sequence.
+maybe_add_scope(Seq, L, Safe) ->
+ case Safe of
+ #{L := Scope} -> add_scope(Seq, Scope);
+ #{} -> Seq
end.
+add_scope([{line,Loc}=I|Is], Scope) ->
+ case keymember(scope, 1, Loc) of
+ false ->
+ [{line,[{scope,Scope}|Loc]}|add_scope(Is, Scope)];
+ true ->
+ [I|add_scope(Is, Scope)]
+ end;
+add_scope([I|Is], Scope) ->
+ [I|add_scope(Is, Scope)];
+add_scope([], _Scope) -> [].
+
+is_shareable([build_stacktrace|_]) -> false;
+is_shareable([{case_end,_}|_]) -> false;
is_shareable([{'catch',_,_}|_]) -> false;
is_shareable([{catch_end,_}|_]) -> false;
+is_shareable([if_end|_]) -> false;
is_shareable([{'try',_,_}|_]) -> false;
is_shareable([{try_case,_}|_]) -> false;
is_shareable([{try_end,_}|_]) -> false;
-is_shareable(_) -> true.
-
-clean_non_sharable(Dict0, Lbls0) ->
- %% We are passing in or out of a 'catch' or 'try' block. Remove
- %% sequences that should not be shared over the boundaries of the
- %% block. Since the end of the sequence must match, the only
- %% possible match between a sequence outside and a sequence inside
- %% the 'catch'/'try' block is a sequence that ends with an
- %% instruction that causes an exception. Any sequence that causes
- %% an exception must contain a line/1 instruction.
- Dict1 = maps:to_list(Dict0),
- Lbls1 = maps:to_list(Lbls0),
- {Dict2,Lbls2} = foldl(fun({K, V}, {Dict,Lbls}) ->
- case sharable_with_try(K) of
- true ->
- {[{K,V}|Dict],lists:keydelete(V, 2, Lbls)};
- false ->
- {Dict,Lbls}
- end
- end, {[],Lbls1}, Dict1),
- {maps:from_list(Dict2),maps:from_list(Lbls2)}.
-
-sharable_with_try([{line,_}|_]) ->
- %% This sequence may cause an exception and may potentially
- %% match a sequence on the other side of the 'catch'/'try' block
- %% boundary.
- false;
-sharable_with_try([_|Is]) ->
- sharable_with_try(Is);
-sharable_with_try([]) -> true.
+is_shareable([_|Is]) -> is_shareable(Is);
+is_shareable([]) -> true.
+
+%%
+%% Classify labels according to where the instructions that branch to
+%% the labels are located. Each label is assigned a set of scope
+%% identifers (but usually just one). If two labels have different
+%% scope identfiers, sharing a sequence that raises an exception
+%% between the labels may not be safe, because one label is inside a
+%% try/catch, and the other label is outside. Note that we don't care
+%% where the labels themselves are located, only from where the
+%% branches to them are located.
+%%
+%% If there is more than one scope in the function (that is, if there
+%% try/catch or catch in the function), the scope identifiers will be
+%% added to the line instructions. Recording the scope in the line
+%% instructions makes beam_jump idempotent, ensuring that beam_jump
+%% will not do any unsafe optimizations when when compiling from a .S
+%% file.
+%%
+
+classify_labels(Is) ->
+ classify_labels(Is, 0, #{}).
+
+classify_labels([{'catch',_,_}|Is], Scope, Safe) ->
+ classify_labels(Is, Scope+1, Safe);
+classify_labels([{catch_end,_}|Is], Scope, Safe) ->
+ classify_labels(Is, Scope+1, Safe);
+classify_labels([{'try',_,_}|Is], Scope, Safe) ->
+ classify_labels(Is, Scope+1, Safe);
+classify_labels([{'try_end',_}|Is], Scope, Safe) ->
+ classify_labels(Is, Scope+1, Safe);
+classify_labels([{'try_case',_}|Is], Scope, Safe) ->
+ classify_labels(Is, Scope+1, Safe);
+classify_labels([{'try_case_end',_}|Is], Scope, Safe) ->
+ classify_labels(Is, Scope+1, Safe);
+classify_labels([I|Is], Scope, Safe0) ->
+ Labels = instr_labels(I),
+ Safe = foldl(fun(L, A) ->
+ case A of
+ #{L := [Scope]} -> A;
+ #{L := Other} -> A#{L => ordsets:add_element(Scope, Other)};
+ #{} -> A#{L => [Scope]}
+ end
+ end, Safe0, Labels),
+ classify_labels(Is, Scope, Safe);
+classify_labels([], Scope, Safe) ->
+ case Scope of
+ 0 ->
+ %% No try/catch or catch in this function. We don't
+ %% need the collected information.
+ #{};
+ _ ->
+ Safe
+ end.
%% Eliminate all fallthroughs. Return the result reversed.
@@ -592,8 +721,6 @@ is_unreachable_after(I) -> is_exit_instruction(I).
-spec is_exit_instruction(instruction()) -> boolean().
-is_exit_instruction({call_ext,_,{extfunc,M,F,A}}) ->
- erl_bifs:is_exit_bif(M, F, A);
is_exit_instruction(if_end) -> true;
is_exit_instruction({case_end,_}) -> true;
is_exit_instruction({try_case_end,_}) -> true;
@@ -736,7 +863,13 @@ instr_labels({recv_set,Lbl}) ->
do_instr_labels(Lbl);
instr_labels({fcheckerror,Lbl}) ->
do_instr_labels(Lbl);
-instr_labels(_) -> [].
+instr_labels({bs_start_match4,Fail,_,_,_}) ->
+ case Fail of
+ {f,L} -> [L];
+ {atom,_} -> []
+ end;
+instr_labels(_) ->
+ [].
do_instr_labels({f,0}) -> [];
do_instr_labels({f,F}) -> [F].
diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl
index 018b94ea57..0131e42f45 100644
--- a/lib/compiler/src/beam_kernel_to_ssa.erl
+++ b/lib/compiler/src/beam_kernel_to_ssa.erl
@@ -24,7 +24,7 @@
%% The main interface.
-export([module/2]).
--import(lists, [append/1,duplicate/2,flatmap/2,foldl/3,
+-import(lists, [all/2,append/1,flatmap/2,foldl/3,
keysort/2,mapfoldl/3,map/2,member/2,
reverse/1,reverse/2,sort/1]).
@@ -34,13 +34,14 @@
-type label() :: beam_ssa:label().
%% Main codegen structure.
--record(cg, {lcount=1 :: label(), %Label counter
+-record(cg, {lcount=1 :: label(), %Label counter
bfail=1 :: label(),
catch_label=none :: 'none' | label(),
vars=#{} :: map(), %Defined variables.
break=0 :: label(), %Break label
recv=0 :: label(), %Receive label
- ultimate_failure=0 :: label() %Label for ultimate match failure.
+ ultimate_failure=0 :: label(), %Label for ultimate match failure.
+ labels=#{} :: #{atom() => label()}
}).
%% Internal records.
@@ -83,6 +84,7 @@ function(#k_fdef{anno=Anno0,func=Name,arity=Arity,
cg_fun(Ke, St0) ->
{UltimateFail,FailIs,St1} = make_failure(badarg, St0),
+ ?EXCEPTION_BLOCK = UltimateFail, %Assertion.
St2 = St1#cg{bfail=UltimateFail,ultimate_failure=UltimateFail},
{B,St} = cg(Ke, St2),
Asm = [{label,0}|B++FailIs],
@@ -105,8 +107,6 @@ make_failure(Reason, St0) ->
cg(#k_match{body=M,ret=Rs}, St) ->
do_match_cg(M, Rs, St);
-cg(#k_guard_match{body=M,ret=Rs}, St) ->
- do_match_cg(M, Rs, St);
cg(#k_seq{arg=Arg,body=Body}, St0) ->
{ArgIs,St1} = cg(Arg, St0),
{BodyIs,St} = cg(Body, St1),
@@ -123,14 +123,6 @@ cg(#k_try_enter{arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th}, St) ->
try_enter_cg(Ta, Vs, Tb, Evs, Th, St);
cg(#k_catch{body=Cb,ret=[R]}, St) ->
do_catch_cg(Cb, R, St);
-cg(#k_receive{anno=Le,timeout=Te,var=Rvar,body=Rm,action=Tes,ret=Rs}, St) ->
- recv_loop_cg(Te, Rvar, Rm, Tes, Rs, Le, St);
-cg(#k_receive_next{}, #cg{recv=Recv}=St) ->
- Is = [#b_set{op=recv_next},make_uncond_branch(Recv)],
- {Is,St};
-cg(#k_receive_accept{}, St) ->
- Remove = #b_set{op=remove_message},
- {[Remove],St};
cg(#k_put{anno=Le,arg=Con,ret=Var}, St) ->
put_cg(Var, Con, Le, St);
cg(#k_return{args=[Ret0]}, St) ->
@@ -139,18 +131,30 @@ cg(#k_return{args=[Ret0]}, St) ->
cg(#k_break{args=Bs}, #cg{break=Br}=St) ->
Args = ssa_args(Bs, St),
{[#cg_break{args=Args,phi=Br}],St};
-cg(#k_guard_break{args=Bs}, St) ->
- cg(#k_break{args=Bs}, St).
+cg(#k_letrec_goto{label=Label,first=First,then=Then,ret=Rs},
+ #cg{break=OldBreak,labels=Labels0}=St0) ->
+ {Tf,St1} = new_label(St0),
+ {B,St2} = new_label(St1),
+ Labels = Labels0#{Label=>Tf},
+ {Fis,St3} = cg(First, St2#cg{labels=Labels,break=B}),
+ {Sis,St4} = cg(Then, St3),
+ St5 = St4#cg{labels=Labels0},
+ {BreakVars,St} = new_ssa_vars(Rs, St5),
+ Phi = #cg_phi{vars=BreakVars},
+ {Fis ++ [{label,Tf}] ++ Sis ++ [{label,B},Phi],St#cg{break=OldBreak}};
+cg(#k_goto{label=Label}, #cg{labels=Labels}=St) ->
+ Branch = map_get(Label, Labels),
+ {[make_uncond_branch(Branch)],St}.
%% match_cg(Matc, [Ret], State) -> {[Ainstr],State}.
%% Generate code for a match.
-do_match_cg(M, Rs, St0) ->
+do_match_cg(M, Rs, #cg{bfail=Bfail,break=OldBreak}=St0) ->
{B,St1} = new_label(St0),
- {Mis,St2} = match_cg(M, St1#cg.bfail, St1#cg{break=B}),
- {BreakVars,St} = new_ssa_vars(Rs, St2),
- {Mis ++ [{label,B},#cg_phi{vars=BreakVars}],
- St#cg{bfail=St0#cg.bfail,break=St1#cg.break}}.
+ {Mis,St2} = match_cg(M, Bfail, St1#cg{break=B}),
+ St3 = St2#cg{break=OldBreak},
+ {BreakVars,St} = new_ssa_vars(Rs, St3),
+ {Mis ++ [{label,B},#cg_phi{vars=BreakVars}],St}.
%% match_cg(Match, Fail, State) -> {[Ainstr],State}.
%% Generate code for a match tree.
@@ -206,6 +210,28 @@ select_cg(#k_type_clause{type=Type,values=Scs}, Var, Tf, Vf, St0) ->
{Is,St} = select_val_cg(Type, Arg, Vls, Tf, Vf, Sis, St2),
{Is,St}.
+select_val_cg(k_atom, {bool,Dst}, Vls, _Tf, _Vf, Sis, St) ->
+ %% Generate a br instruction for a known boolean value from
+ %% the `wait_timeout` instruction.
+ [{#b_literal{val=false},Fail},{#b_literal{val=true},Succ}] = sort(Vls),
+ case Dst of
+ #b_var{} ->
+ Br = #b_br{bool=Dst,succ=Succ,fail=Fail},
+ {[Br|Sis],St};
+ #b_literal{val=true}=Bool ->
+ %% A `wait_timeout 0` instruction was optimized away.
+ Br = #b_br{bool=Bool,succ=Succ,fail=Succ},
+ {[Br|Sis],St}
+ end;
+select_val_cg(k_atom, {succeeded,Dst}, Vls, _Tf, _Vf, Sis, St0) ->
+ [{#b_literal{val=false},Fail},{#b_literal{val=true},Succ}] = sort(Vls),
+ #b_var{} = Dst, %Assertion.
+ %% Generate a `succeeded` instruction and two-way branch
+ %% following the `peek_message` instruction.
+ {Bool,St} = new_ssa_var('@ssa_bool', St0),
+ Succeeded = #b_set{op={succeeded,guard},dst=Bool,args=[Dst]},
+ Br = #b_br{bool=Bool,succ=Succ,fail=Fail},
+ {[Succeeded,Br|Sis],St};
select_val_cg(k_tuple, Tuple, Vls, Tf, Vf, Sis, St0) ->
{Is0,St1} = make_cond_branch({bif,is_tuple}, [Tuple], Tf, St0),
{Arity,St2} = new_ssa_var('@ssa_arity', St1),
@@ -269,7 +295,7 @@ select_cons(#k_val_clause{val=#k_cons{hd=Hd,tl=Tl},body=B},
{Is,St} = make_cond_branch(is_nonempty_list, [Src], Tf, St2),
{Is ++ Eis ++ Bis,St}.
-select_nil(#k_val_clause{val=#k_nil{},body=B}, V, Tf, Vf, St0) ->
+select_nil(#k_val_clause{val=#k_literal{val=[]},body=B}, V, Tf, Vf, St0) ->
{Bis,St1} = match_cg(B, Vf, St0),
Src = ssa_arg(V, St1),
{Is,St} = make_cond_branch({bif,'=:='}, [Src,#b_literal{val=[]}], Tf, St1),
@@ -279,9 +305,10 @@ select_binary(#k_val_clause{val=#k_binary{segs=#k_var{name=Ctx0}},body=B},
#k_var{}=Src, Tf, Vf, St0) ->
{Ctx,St1} = new_ssa_var(Ctx0, St0),
{Bis0,St2} = match_cg(B, Vf, St1),
- {TestIs,St} = make_cond_branch(succeeded, [Ctx], Tf, St2),
+ {TestIs,St} = make_succeeded(Ctx, {guard, Tf}, St2),
Bis1 = [#b_set{op=bs_start_match,dst=Ctx,
- args=[ssa_arg(Src, St)]}] ++ TestIs ++ Bis0,
+ args=[#b_literal{val=new},
+ ssa_arg(Src, St)]}] ++ TestIs ++ Bis0,
Bis = finish_bs_matching(Bis1),
{Bis,St}.
@@ -311,6 +338,39 @@ make_cond_branch(Cond, Args, Fail, St0) ->
make_uncond_branch(Fail) ->
#b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail}.
+%%
+%% Success checks need to be treated differently in bodies and guards; a check
+%% in a guard can be safely removed when we know it fails because we know
+%% there's never any side-effects, but in bodies the checked instruction may
+%% throw an exception and we need to ensure it isn't optimized away.
+%%
+%% Checks are expressed as {succeeded,guard} and {succeeded,body} respectively,
+%% where the latter has a side-effect (see beam_ssa:no_side_effect/1) and the
+%% former does not. This ensures that passes like ssa_opt_dead and ssa_opt_live
+%% won't optimize away pure operations that may throw an exception, since their
+%% result is used in {succeeded,body}.
+%%
+%% Other than the above details, the two variants are equivalent and most
+%% passes that care about them can simply match {succeeded,_}.
+%%
+
+make_succeeded(Var, {guard, Fail}, St) ->
+ make_succeeded_1(Var, guard, Fail, St);
+make_succeeded(Var, {in_catch, CatchLbl}, St) ->
+ make_succeeded_1(Var, body, CatchLbl, St);
+make_succeeded(Var, {no_catch, Fail}, St) ->
+ #cg{ultimate_failure=Fail} = St, %Assertion
+ make_succeeded_1(Var, body, Fail, St).
+
+make_succeeded_1(Var, Kind, Fail, St0) ->
+ {Bool,St1} = new_ssa_var('@ssa_bool', St0),
+ {Succ,St} = new_label(St1),
+
+ Check = [#b_set{op={succeeded,Kind},dst=Bool,args=[Var]},
+ #b_br{bool=Bool,succ=Succ,fail=Fail}],
+
+ {Check ++ [{label,Succ}], St}.
+
%% Instructions for selection of binary segments.
select_bin_segs(Scs, Ivar, Tf, St) ->
@@ -341,11 +401,11 @@ select_bin_seg(#k_val_clause{val=#k_bin_int{size=Sz,unit=U,flags=Fs,
{Bis,St} = match_cg(B, Fail, St1),
Is = case Mis ++ Bis of
[#b_set{op=bs_match,args=[#b_literal{val=string},OtherCtx1,Bin1]},
- #b_set{op=succeeded,dst=Bool1},
+ #b_set{op={succeeded,guard},dst=Bool1},
#b_br{bool=Bool1,succ=Succ,fail=Fail},
{label,Succ},
#b_set{op=bs_match,dst=Dst,args=[#b_literal{val=string},_OtherCtx2,Bin2]}|
- [#b_set{op=succeeded,dst=Bool2},
+ [#b_set{op={succeeded,guard},dst=Bool2},
#b_br{bool=Bool2,fail=Fail}|_]=Is0] ->
%% We used to do this optimization later, but it
%% turns out that in huge functions with many
@@ -375,15 +435,23 @@ select_bin_end(#k_val_clause{val=#k_bin_end{},body=B}, Src, Tf, St0) ->
select_extract_bin(#k_var{name=Hd}, Size0, Unit, Type, Flags, Vf,
Ctx, Anno, St0) ->
{Dst,St1} = new_ssa_var(Hd, St0),
- Size = ssa_arg(Size0, St0),
+ Size = case {Size0,ssa_arg(Size0, St0)} of
+ {#k_var{},#b_literal{val=all}} ->
+ %% The size `all` is used for the size of the final binary
+ %% segment in a pattern. Using `all` explicitly is not allowed,
+ %% so we convert it to an obvious invalid size.
+ #b_literal{val=bad_size};
+ {_,Size1} ->
+ Size1
+ end,
build_bs_instr(Anno, Type, Vf, Ctx, Size, Unit, Flags, Dst, St1).
-select_extract_int(#k_var{name=Tl}, 0, #k_int{val=0}, _U, _Fs, _Vf,
+select_extract_int(#k_var{name=Tl}, 0, #k_literal{val=0}, _U, _Fs, _Vf,
Ctx, St0) ->
St = set_ssa_var(Tl, Ctx, St0),
{[],St};
-select_extract_int(#k_var{name=Tl}, Val, #k_int{val=Sz}, U, Fs, Vf,
- Ctx, St0) ->
+select_extract_int(#k_var{name=Tl}, Val, #k_literal{val=Sz}, U, Fs, Vf,
+ Ctx, St0) when is_integer(Sz) ->
{Dst,St1} = new_ssa_var(Tl, St0),
Bits = U*Sz,
Bin = case member(big, Fs) of
@@ -394,7 +462,7 @@ select_extract_int(#k_var{name=Tl}, Val, #k_int{val=Sz}, U, Fs, Vf,
<<Val:Bits/little>>
end,
Bits = bit_size(Bin), %Assertion.
- {TestIs,St} = make_cond_branch(succeeded, [Dst], Vf, St1),
+ {TestIs,St} = make_succeeded(Dst, {guard, Vf}, St1),
Set = #b_set{op=bs_match,dst=Dst,
args=[#b_literal{val=string},Ctx,#b_literal{val=Bin}]},
{[Set|TestIs],St}.
@@ -412,21 +480,14 @@ build_bs_instr(Anno, Type, Fail, Ctx, Size, Unit0, Flags0, Dst, St0) ->
#b_set{anno=Anno,op=bs_match,dst=Dst,
args=[TypeArg,Ctx,Flags]}
end,
- {Is,St} = make_cond_branch(succeeded, [Dst], Fail, St0),
+ {Is,St} = make_succeeded(Dst, {guard, Fail}, St0),
{[Get|Is],St}.
select_val(#k_val_clause{val=#k_tuple{es=Es},body=B}, V, Vf, St0) ->
- #k{us=Used} = k_get_anno(B),
- {Eis,St1} = select_extract_tuple(V, Es, Used, St0),
+ {Eis,St1} = select_extract_tuple(V, Es, St0),
{Bis,St2} = match_cg(B, Vf, St1),
{length(Es),Eis ++ Bis,St2};
-select_val(#k_val_clause{val=Val0,body=B}, _V, Vf, St0) ->
- Val = case Val0 of
- #k_atom{val=Lit} -> Lit;
- #k_float{val=Lit} -> Lit;
- #k_int{val=Lit} -> Lit;
- #k_literal{val=Lit} -> Lit
- end,
+select_val(#k_val_clause{val=#k_literal{val=Val},body=B}, _V, Vf, St0) ->
{Bis,St1} = match_cg(B, Vf, St0),
{Val,Bis,St1}.
@@ -438,17 +499,18 @@ select_val(#k_val_clause{val=Val0,body=B}, _V, Vf, St0) ->
%% It is probably worthwhile because it is common to extract only a
%% few elements from a huge record.
-select_extract_tuple(Src, Vs, Used, St0) ->
+select_extract_tuple(Src, Vs, St0) ->
Tuple = ssa_arg(Src, St0),
- F = fun (#k_var{name=V}, {Elem,S0}) ->
- case member(V, Used) of
+ F = fun (#k_var{anno=Anno,name=V}, {Elem,S0}) ->
+ case member(unused, Anno) of
true ->
+ {[],{Elem+1,S0}};
+ false ->
Args = [Tuple,#b_literal{val=Elem}],
{Dst,S} = new_ssa_var(V, S0),
- Get = #b_set{op=get_tuple_element,dst=Dst,args=Args},
- {[Get],{Elem+1,S}};
- false ->
- {[],{Elem+1,S0}}
+ Get = #b_set{op=get_tuple_element,
+ dst=Dst,args=Args},
+ {[Get],{Elem+1,S}}
end
end,
{Es,{_,St}} = flatmapfoldl(F, {0,St0}, Vs),
@@ -475,7 +537,7 @@ select_extract_map([P|Ps], Src, Fail, St0) ->
Key = ssa_arg(Key0, St0),
{Dst,St1} = new_ssa_var(Dst0, St0),
Set = #b_set{op=get_map_element,dst=Dst,args=[MapSrc,Key]},
- {TestIs,St2} = make_cond_branch(succeeded, [Dst], Fail, St1),
+ {TestIs,St2} = make_succeeded(Dst, {guard, Fail}, St1),
{Is,St} = select_extract_map(Ps, Src, Fail, St2),
{[Set|TestIs]++Is,St};
select_extract_map([], _, _, St) ->
@@ -501,11 +563,20 @@ guard_clause_cg(#k_guard_clause{guard=G,body=B}, Fail, St0) ->
%% the correct exit point. Primops and tests all go to the next
%% instruction on success or jump to a failure label.
-guard_cg(#k_protected{arg=Ts,ret=Rs,inner=Inner}, Fail, St) ->
- protected_cg(Ts, Rs, Inner, Fail, St);
-guard_cg(#k_test{op=Test0,args=As,inverted=Inverted}, Fail, St0) ->
- #k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=Test}} = Test0,
- test_cg(Test, Inverted, As, Fail, St0);
+guard_cg(#k_try{arg=Ts,vars=[],body=#k_break{args=[]},
+ evars=[],handler=#k_break{args=[]}},
+ Fail,
+ #cg{bfail=OldBfail,break=OldBreak}=St0) ->
+ %% Do a try/catch without return value for effect. The return
+ %% value is not checked; success passes on to the next instruction
+ %% and failure jumps to Fail.
+ {Next,St1} = new_label(St0),
+ {Tis,St2} = guard_cg(Ts, Fail, St1#cg{bfail=Fail,break=Next}),
+ Is = Tis ++ [{label,Next},#cg_phi{vars=[]}],
+ {Is,St2#cg{bfail=OldBfail,break=OldBreak}};
+guard_cg(#k_test{op=Test0,args=As}, Fail, St0) ->
+ #k_remote{mod=#k_literal{val=erlang},name=#k_literal{val=Test}} = Test0,
+ test_cg(Test, false, As, Fail, St0);
guard_cg(#k_seq{arg=Arg,body=Body}, Fail, St0) ->
{ArgIs,St1} = guard_cg(Arg, Fail, St0),
{BodyIs,St} = guard_cg(Body, Fail, St1),
@@ -522,7 +593,8 @@ test_cg(Test, Inverted, As0, Fail, St0) ->
case {Test,ssa_args(As0, St0)} of
{is_record,[Tuple,#b_literal{val=Atom}=Tag,#b_literal{val=Int}=Arity]}
when is_atom(Atom), is_integer(Int) ->
- test_is_record_cg(Inverted, Fail, Tuple, Tag, Arity, St0);
+ false = Inverted, %Assertion.
+ test_is_record_cg(Fail, Tuple, Tag, Arity, St0);
{_,As} ->
{Bool,St1} = new_ssa_var('@ssa_bool', St0),
{Succ,St} = new_label(St1),
@@ -534,7 +606,7 @@ test_cg(Test, Inverted, As0, Fail, St0) ->
{[Bif,Br,{label,Succ}],St}
end.
-test_is_record_cg(false, Fail, Tuple, TagVal, ArityVal, St0) ->
+test_is_record_cg(Fail, Tuple, TagVal, ArityVal, St0) ->
{Arity,St1} = new_ssa_var('@ssa_arity', St0),
{Tag,St2} = new_ssa_var('@ssa_tag', St1),
{Is0,St3} = make_cond_branch({bif,is_tuple}, [Tuple], Fail, St2),
@@ -544,44 +616,8 @@ test_is_record_cg(false, Fail, Tuple, TagVal, ArityVal, St0) ->
args=[Tuple,#b_literal{val=0}]},
{Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], Fail, St4),
Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2,
- {Is,St};
-test_is_record_cg(true, Fail, Tuple, TagVal, ArityVal, St0) ->
- {Succ,St1} = new_label(St0),
- {Arity,St2} = new_ssa_var('@ssa_arity', St1),
- {Tag,St3} = new_ssa_var('@ssa_tag', St2),
- {Is0,St4} = make_cond_branch({bif,is_tuple}, [Tuple], Succ, St3),
- GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]},
- {Is1,St5} = make_cond_branch({bif,'=:='}, [Arity,ArityVal], Succ, St4),
- GetTag = #b_set{op=get_tuple_element,dst=Tag,
- args=[Tuple,#b_literal{val=0}]},
- {Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], Succ, St5),
- Is3 = [make_uncond_branch(Fail),{label,Succ}],
- Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2 ++ Is3,
{Is,St}.
-%% protected_cg([Kexpr], [Ret], Fail, St) -> {[Ainstr],St}.
-%% Do a protected. Protecteds without return values are just done
-%% for effect, the return value is not checked, success passes on to
-%% the next instruction and failure jumps to Fail. If there are
-%% return values then these must be set to 'false' on failure,
-%% control always passes to the next instruction.
-
-protected_cg(Ts, [], _, Fail, St0) ->
- %% Protect these calls, revert when done.
- {Tis,St1} = guard_cg(Ts, Fail, St0#cg{bfail=Fail}),
- {Tis,St1#cg{bfail=St0#cg.bfail}};
-protected_cg(Ts, Rs, Inner0, _Fail, St0) ->
- {Pfail,St1} = new_label(St0),
- {Br,St2} = new_label(St1),
- Prot = duplicate(length(Rs), #b_literal{val=false}),
- {Tis,St3} = guard_cg(Ts, Pfail, St2#cg{break=Pfail,bfail=Pfail}),
- Inner = ssa_args(Inner0, St3),
- {BreakVars,St} = new_ssa_vars(Rs, St3),
- Is = Tis ++ [#cg_break{args=Inner,phi=Br},
- {label,Pfail},#cg_break{args=Prot,phi=Br},
- {label,Br},#cg_phi{vars=BreakVars}],
- {Is,St#cg{break=St0#cg.break,bfail=St0#cg.bfail}}.
-
%% match_fmf(Fun, LastFail, State, [Clause]) -> {Is,State}.
%% This is a special flatmapfoldl for match code gen where we
%% generate a "failure" label for each clause. The last clause uses
@@ -596,7 +632,7 @@ match_fmf(F, LastFail, St0, [H|T]) ->
{Rs,St3} = match_fmf(F, LastFail, St2, T),
{R ++ [{label,Fail}] ++ Rs,St3}.
-%% fail_label(State) -> {Where,FailureLabel}.
+%% fail_context(State) -> {Where,FailureLabel}.
%% Where = guard | no_catch | in_catch
%% Return an indication of which part of a function code is
%% being generated for and the appropriate failure label to
@@ -609,7 +645,7 @@ match_fmf(F, LastFail, St0, [H|T]) ->
%% a try/catch or catch.
%% in_catch - In the scope of a try/catch or catch.
-fail_label(#cg{catch_label=Catch,bfail=Fail,ultimate_failure=Ult}) ->
+fail_context(#cg{catch_label=Catch,bfail=Fail,ultimate_failure=Ult}) ->
if
Fail =/= Ult ->
{guard,Fail};
@@ -619,14 +655,6 @@ fail_label(#cg{catch_label=Catch,bfail=Fail,ultimate_failure=Ult}) ->
{in_catch,Catch}
end.
-%% bif_fail_label(State) -> FailureLabel.
-%% Return the appropriate failure label for a guard BIF call or
-%% primop that fails.
-
-bif_fail_label(St) ->
- {_,Fail} = fail_label(St),
- Fail.
-
%% call_cg(Func, [Arg], [Ret], Le, State) ->
%% {[Ainstr],State}.
%% enter_cg(Func, [Arg], Le, St) -> {[Ainstr],St}.
@@ -634,111 +662,124 @@ bif_fail_label(St) ->
call_cg(Func, As, [], Le, St) ->
call_cg(Func, As, [#k_var{name='@ssa_ignored'}], Le, St);
-call_cg(Func0, As, [#k_var{name=R}|MoreRs]=Rs, Le, St0) ->
- case fail_label(St0) of
+call_cg(Func, As, [#k_var{name=R}|MoreRs]=Rs, Le, St0) ->
+ case fail_context(St0) of
{guard,Fail} ->
%% Inside a guard. The only allowed function call is to
%% erlang:error/1,2. We will generate a branch to the
%% failure branch.
- #k_remote{mod=#k_atom{val=erlang},
- name=#k_atom{val=error}} = Func0, %Assertion.
- [#k_var{name=DestVar}] = Rs,
- St = set_ssa_var(DestVar, #b_literal{val=unused}, St0),
+ #k_remote{mod=#k_literal{val=erlang},
+ name=#k_literal{val=error}} = Func, %Assertion.
+ St = set_unused_ssa_vars(Rs, St0),
{[make_uncond_branch(Fail),#cg_unreachable{}],St};
- {Catch,Fail} ->
+ FailCtx ->
%% Ordinary function call in a function body.
- Args = ssa_args(As, St0),
+ Args = ssa_args([Func|As], St0),
{Ret,St1} = new_ssa_var(R, St0),
- Func = call_target(Func0, Args, St0),
- Call = #b_set{anno=line_anno(Le),op=call,dst=Ret,args=[Func|Args]},
+ Call = #b_set{anno=line_anno(Le),op=call,dst=Ret,args=Args},
%% If this is a call to erlang:error(), MoreRs could be a
%% nonempty list of variables that each need a value.
- St2 = foldl(fun(#k_var{name=Dummy}, S) ->
- set_ssa_var(Dummy, #b_literal{val=unused}, S)
- end, St1, MoreRs),
- case Catch of
- no_catch ->
- {[Call],St2};
- in_catch ->
- {TestIs,St} = make_cond_branch(succeeded, [Ret], Fail, St2),
- {[Call|TestIs],St}
- end
+ St2 = set_unused_ssa_vars(MoreRs, St1),
+
+ {TestIs,St} = make_succeeded(Ret, FailCtx, St2),
+ {[Call|TestIs],St}
end.
-enter_cg(Func0, As0, Le, St0) ->
- Anno = line_anno(Le),
- Func = call_target(Func0, As0, St0),
- As = ssa_args(As0, St0),
+enter_cg(Func, As0, Le, St0) ->
+ As = ssa_args([Func|As0], St0),
{Ret,St} = new_ssa_var('@ssa_ret', St0),
- Call = #b_set{anno=Anno,op=call,dst=Ret,args=[Func|As]},
+ Call = #b_set{anno=line_anno(Le),op=call,dst=Ret,args=As},
{[Call,#b_ret{arg=Ret}],St}.
-call_target(Func, As, St) ->
- Arity = length(As),
- case Func of
- #k_remote{mod=Mod0,name=Name0} ->
- Mod = ssa_arg(Mod0, St),
- Name = ssa_arg(Name0, St),
- #b_remote{mod=Mod,name=Name,arity=Arity};
- #k_local{name=Name} when is_atom(Name) ->
- #b_local{name=#b_literal{val=Name},arity=Arity};
- #k_var{}=Var ->
- ssa_arg(Var, St)
- end.
-
%% bif_cg(#k_bif{}, Le,State) -> {[Ainstr],State}.
%% Generate code for a guard BIF or primop.
-bif_cg(#k_bif{op=#k_internal{name=Name},args=As,ret=Rs}, Le, St) ->
- internal_cg(Name, As, Rs, Le, St);
-bif_cg(#k_bif{op=#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=Name}},
+bif_cg(#k_bif{op=#k_internal{name=Name},args=As,ret=Rs}, _Le, St) ->
+ internal_cg(Name, As, Rs, St);
+bif_cg(#k_bif{op=#k_remote{mod=#k_literal{val=erlang},name=#k_literal{val=Name}},
args=As,ret=Rs}, Le, St) ->
bif_cg(Name, As, Rs, Le, St).
%% internal_cg(Bif, [Arg], [Ret], Le, State) ->
%% {[Ainstr],State}.
-internal_cg(make_fun, [Name0,Arity0|As], Rs, _Le, St0) ->
- #k_atom{val=Name} = Name0,
- #k_int{val=Arity} = Arity0,
- case Rs of
- [#k_var{name=Dst0}] ->
- {Dst,St} = new_ssa_var(Dst0, St0),
- Args = ssa_args(As, St),
- Local = #b_local{name=#b_literal{val=Name},arity=Arity},
- MakeFun = #b_set{op=make_fun,dst=Dst,args=[Local|Args]},
- {[MakeFun],St};
- [] ->
- {[],St0}
- end;
-internal_cg(bs_init_writable=I, As, [#k_var{name=Dst0}], _Le, St0) ->
- %% This behaves like a function call.
- {Dst,St} = new_ssa_var(Dst0, St0),
- Args = ssa_args(As, St),
- Set = #b_set{op=I,dst=Dst,args=Args},
- {[Set],St};
-internal_cg(build_stacktrace=I, As, [#k_var{name=Dst0}], _Le, St0) ->
- {Dst,St} = new_ssa_var(Dst0, St0),
- Args = ssa_args(As, St),
- Set = #b_set{op=I,dst=Dst,args=Args},
- {[Set],St};
-internal_cg(raise, As, [#k_var{name=Dst0}], _Le, St0) ->
+internal_cg(raise, As, [#k_var{name=Dst0}], St0) ->
Args = ssa_args(As, St0),
{Dst,St} = new_ssa_var(Dst0, St0),
Resume = #b_set{op=resume,dst=Dst,args=Args},
- case St of
- #cg{catch_label=none} ->
- {[Resume],St};
- #cg{catch_label=Catch} when is_integer(Catch) ->
- Is = [Resume,make_uncond_branch(Catch),#cg_unreachable{}],
+ case fail_context(St) of
+ {no_catch,_Fail} ->
+ %% No current catch in this function. Follow the resume
+ %% instruction by a return (instead of a branch to
+ %% ?EXCEPTION_MARKER) to ensure that the trim optimization
+ %% can be applied. (Allowing control to pass through to
+ %% the next instruction would mean that the type for the
+ %% try/catch construct would be `any`.)
+ Is = [Resume,#b_ret{arg=Dst},#cg_unreachable{}],
+ {Is,St};
+ {in_catch,Fail} ->
+ Is = [Resume,make_uncond_branch(Fail),#cg_unreachable{}],
{Is,St}
end;
-internal_cg(raw_raise=I, As, [#k_var{name=Dst0}], _Le, St0) ->
+internal_cg(recv_peek_message, [], [#k_var{name=Succeeded0},
+ #k_var{name=Dst0}], St0) ->
+ {Dst,St1} = new_ssa_var(Dst0, St0),
+ St = new_succeeded_value(Succeeded0, Dst, St1),
+ Set = #b_set{op=peek_message,dst=Dst,args=[]},
+ {[Set],St};
+internal_cg(recv_wait_timeout, As, [#k_var{name=Succeeded0}], St0) ->
+ case ssa_args(As, St0) of
+ [#b_literal{val=0}] ->
+ %% If beam_ssa_opt is run (which is default), the
+ %% `wait_timeout` instruction will be removed if the
+ %% operand is a literal 0. However, if optimizations have
+ %% been turned off, we must not not generate a
+ %% `wait_timeout` instruction with a literal 0 timeout,
+ %% because the BEAM instruction will not handle it
+ %% correctly.
+ St = new_bool_value(Succeeded0, #b_literal{val=true}, St0),
+ {[],St};
+ Args ->
+ %% Note that the `wait_timeout` instruction can
+ %% potentially branch in three different directions:
+ %%
+ %% * A new message is available in the message queue.
+ %% wait_timeout branches to the given label.
+ %%
+ %% * The timeout expired. wait_timeout transfers control
+ %% to the next instruction.
+ %%
+ %% * The value for timeout duration is invalid (either not
+ %% an integer or negative or too large). A timeout_value
+ %% exception will be raised.
+ %%
+ %% wait_timeout will be represented like this in SSA code:
+ %%
+ %% WaitBool = wait_timeout TimeoutValue
+ %% Succeeded = succeeded:body WaitBool
+ %% br Succeeded, ^good_timeout_value, ^bad_timeout_value
+ %%
+ %% good_timeout_value:
+ %% br WaitBool, ^timeout_expired, ^new_message_received
+ %%
+ {Wait,St1} = new_ssa_var('@ssa_wait', St0),
+ {Succ,St2} = make_succeeded(Wait, fail_context(St1), St1),
+ St = new_bool_value(Succeeded0, Wait, St2),
+ Set = #b_set{op=wait_timeout,dst=Wait,args=Args},
+ {[Set|Succ],St}
+ end;
+internal_cg(Op, As, [#k_var{name=Dst0}], St0) when is_atom(Op) ->
%% This behaves like a function call.
{Dst,St} = new_ssa_var(Dst0, St0),
Args = ssa_args(As, St),
- Set = #b_set{op=I,dst=Dst,args=Args},
+ Set = #b_set{op=Op,dst=Dst,args=Args},
+ {[Set],St};
+internal_cg(Op, As, [], St0) when is_atom(Op) ->
+ %% This behaves like a function call.
+ {Dst,St} = new_ssa_var('@ssa_ignored', St0),
+ Args = ssa_args(As, St),
+ Set = #b_set{op=Op,dst=Dst,args=Args},
{[Set],St}.
bif_cg(Bif, As0, [#k_var{name=Dst0}], Le, St0) ->
@@ -752,8 +793,8 @@ bif_cg(Bif, As0, [#k_var{name=Dst0}], Le, St0) ->
I = #b_set{anno=line_anno(Le),op={bif,Bif},dst=Dst,args=As},
case erl_bifs:is_safe(erlang, Bif, length(As)) of
false ->
- Fail = bif_fail_label(St1),
- {Is,St} = make_cond_branch(succeeded, [Dst], Fail, St1),
+ FailCtx = fail_context(St1),
+ {Is,St} = make_succeeded(Dst, FailCtx, St1),
{[I|Is],St};
true->
{[I],St1}
@@ -779,50 +820,6 @@ bif_is_record_cg(Dst, Tuple, TagVal, ArityVal, St0) ->
Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2 ++ Is3,
{Is,St}.
-%% recv_loop_cg(TimeOut, ReceiveVar, ReceiveMatch, TimeOutExprs,
-%% [Ret], Le, St) -> {[Ainstr],St}.
-
-recv_loop_cg(Te, Rvar, Rm, Tes, Rs, Le, St0) ->
- %% Get labels.
- {Rl,St1} = new_label(St0),
- {Tl,St2} = new_label(St1),
- {Bl,St3} = new_label(St2),
- St4 = St3#cg{break=Bl,recv=Rl},
- {Ris,St5} = cg_recv_mesg(Rvar, Rm, Tl, Le, St4),
- {Wis,St6} = cg_recv_wait(Te, Tes, St5),
- {BreakVars,St} = new_ssa_vars(Rs, St6),
- {Ris ++ [{label,Tl}] ++ Wis ++
- [{label,Bl},#cg_phi{vars=BreakVars}],
- St#cg{break=St0#cg.break,recv=St0#cg.recv}}.
-
-%% cg_recv_mesg( ) -> {[Ainstr],St}.
-
-cg_recv_mesg(#k_var{name=R}, Rm, Tl, Le, St0) ->
- {Dst,St1} = new_ssa_var(R, St0),
- {Mis,St2} = match_cg(Rm, none, St1),
- RecvLbl = St1#cg.recv,
- {TestIs,St} = make_cond_branch(succeeded, [Dst], Tl, St2),
- Is = [#b_br{anno=line_anno(Le),bool=#b_literal{val=true},
- succ=RecvLbl,fail=RecvLbl},
- {label,RecvLbl},
- #b_set{op=peek_message,dst=Dst}|TestIs],
- {Is++Mis,St}.
-
-%% cg_recv_wait(Te, Tes, St) -> {[Ainstr],St}.
-
-cg_recv_wait(#k_int{val=0}, Es, St0) ->
- {Tis,St} = cg(Es, St0),
- {[#b_set{op=timeout}|Tis],St};
-cg_recv_wait(Te, Es, St0) ->
- {Tis,St1} = cg(Es, St0),
- Args = [ssa_arg(Te, St1)],
- {WaitDst,St2} = new_ssa_var('@ssa_wait', St1),
- {WaitIs,St} = make_cond_branch(succeeded, [WaitDst], St1#cg.recv, St2),
- %% Infinite timeout will be optimized later.
- Is = [#b_set{op=wait_timeout,dst=WaitDst,args=Args}] ++ WaitIs ++
- [#b_set{op=timeout}] ++ Tis,
- {Is,St}.
-
%% try_cg(TryBlock, [BodyVar], TryBody, [ExcpVar], TryHandler, [Ret], St) ->
%% {[Ainstr],St}.
@@ -835,24 +832,106 @@ try_cg(Ta, Vs, Tb, Evs, Th, Rs, St0) ->
{SsaVs,St6} = new_ssa_vars(Vs, St5),
{SsaEvs,St7} = new_ssa_vars(Evs, St6),
{Ais,St8} = cg(Ta, St7#cg{break=B,catch_label=H}),
- St9 = St8#cg{break=E,catch_label=St7#cg.catch_label},
- {Bis,St10} = cg(Tb, St9),
- {His,St11} = cg(Th, St10),
- {BreakVars,St12} = new_ssa_vars(Rs, St11),
- {CatchedAgg,St} = new_ssa_var('@ssa_agg', St12),
- ExtractVs = extract_vars(SsaEvs, CatchedAgg, 0),
- KillTryTag = #b_set{op=kill_try_tag,args=[TryTag]},
- Args = [#b_literal{val='try'},TryTag],
- Handler = [{label,H},
- #b_set{op=landingpad,dst=CatchedAgg,args=Args}] ++
- ExtractVs ++ [KillTryTag],
- {[#b_set{op=new_try_tag,dst=TryTag,args=[#b_literal{val='try'}]},
- #b_br{bool=TryTag,succ=Next,fail=H},
- {label,Next}] ++ Ais ++
- [{label,B},#cg_phi{vars=SsaVs},KillTryTag] ++ Bis ++
- Handler ++ His ++
- [{label,E},#cg_phi{vars=BreakVars}],
- St#cg{break=St0#cg.break}}.
+
+ %% We try to avoid constructing a try/catch if the expression to
+ %% be evaluated don't have any side effects and if the error
+ %% reason is not explicitly matched.
+ %%
+ %% Starting in OTP 23, segment sizes in binary matching and keys
+ %% in map matching are allowed to be arbitrary guard
+ %% expressions. Those expressions are evaluated in a try/catch
+ %% so that matching can continue with the next clause if the evaluation
+ %% of such expression fails.
+ %%
+ %% It is not allowed to use try/catch during matching in a receive
+ %% (the try/catch would force the saving of fragile message references
+ %% to the stack frame). Therefore, avoiding creating try/catch is
+ %% not merely an optimization but necessary for correctness.
+
+ case {Vs,Tb,Th,is_guard_cg_safe_list(Ais)} of
+ {[#k_var{name=X}],#k_break{args=[#k_var{name=X}]},
+ #k_break{args=[#k_literal{}]},true} ->
+ %% There are no instructions that will clobber X registers
+ %% and the exception is not matched. Therefore, a
+ %% try/catch is not needed. This code is probably located
+ %% in a guard.
+ {ProtIs,St9} = guard_cg(Ta, H, St7#cg{break=B,bfail=H}),
+ {His,St10} = cg(Th, St9),
+ {RetVars,St} = new_ssa_vars(Rs, St10),
+ Is = ProtIs ++ [{label,H}] ++ His ++
+ [{label,B},#cg_phi{vars=RetVars}],
+ {Is,St#cg{break=St0#cg.break,bfail=St7#cg.bfail}};
+ {[#k_var{name=X}],#k_break{args=[#k_literal{}=SuccLit0,#k_var{name=X}]},
+ #k_break{args=[#k_literal{val=false},#k_literal{}]},true} ->
+ %% There are no instructions that will clobber X registers
+ %% and the exception is not matched. Therefore, a
+ %% try/catch is not needed. This code probably evaluates
+ %% a key expression in map matching.
+ {FinalLabel,St9} = new_label(St7),
+ {ProtIs,St10} = guard_cg(Ta, H, St9#cg{break=B,bfail=H}),
+ {His,St11} = cg(Th, St10#cg{break=FinalLabel}),
+ {RetVars,St12} = new_ssa_vars(Rs, St11),
+ {Result,St} = new_ssa_var('@ssa_result', St12),
+ SuccLit = ssa_arg(SuccLit0, St),
+ Is = ProtIs ++ [{label,H}] ++ His ++
+ [{label,B},
+ #cg_phi{vars=[Result]},
+ #cg_break{args=[SuccLit,Result],phi=FinalLabel},
+ {label,FinalLabel},
+ #cg_phi{vars=RetVars}],
+ {Is,St#cg{break=St0#cg.break,bfail=St7#cg.bfail}};
+ {_,#k_break{args=[]},#k_break{args=[]},true} ->
+ %% There are no instructions that will clobber X registers
+ %% and the exception is not matched. Therefore, a
+ %% try/catch is not needed. This code probably does the
+ %% size calculation for a segment in binary matching.
+ {ProtIs,St9} = guard_cg(Ta, H, St7#cg{break=B,bfail=H}),
+ {His,St10} = cg(Th, St9),
+ {RetVars,St} = new_ssa_vars(Rs, St10),
+ Is = ProtIs ++ [{label,H}] ++ His ++
+ [{label,B},#cg_phi{vars=RetVars}],
+ {Is,St#cg{break=St0#cg.break,bfail=St7#cg.bfail}};
+ {_,_,_,_} ->
+ %% The general try/catch (not in a guard).
+ St9 = St8#cg{break=E,catch_label=St7#cg.catch_label},
+ {Bis,St10} = cg(Tb, St9),
+ {His,St11} = cg(Th, St10),
+ {BreakVars,St12} = new_ssa_vars(Rs, St11),
+ {CatchedAgg,St13} = new_ssa_var('@ssa_agg', St12),
+ ExtractVs = extract_vars(SsaEvs, CatchedAgg, 0),
+ KillTryTag = #b_set{op=kill_try_tag,args=[TryTag]},
+ Args = [#b_literal{val='try'},TryTag],
+ Handler = [{label,H},
+ #b_set{op=landingpad,dst=CatchedAgg,args=Args}] ++
+ ExtractVs ++ [KillTryTag],
+ {[#b_set{op=new_try_tag,dst=TryTag,args=[#b_literal{val='try'}]},
+ #b_br{bool=TryTag,succ=Next,fail=H},
+ {label,Next}] ++ Ais ++
+ [{label,B},#cg_phi{vars=SsaVs},KillTryTag] ++ Bis ++
+ Handler ++ His ++
+ [{label,E},#cg_phi{vars=BreakVars}],
+ St13#cg{break=St0#cg.break}}
+ end.
+
+is_guard_cg_safe_list(Is) ->
+ all(fun is_guard_cg_safe/1, Is).
+
+is_guard_cg_safe(#b_set{op=call,args=Args}) ->
+ case Args of
+ [#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error},
+ arity=1}|_] ->
+ true;
+ _ ->
+ false
+ end;
+is_guard_cg_safe(#b_set{}=I) -> not beam_ssa:clobbers_xregs(I);
+is_guard_cg_safe(#b_br{}) -> true;
+is_guard_cg_safe(#b_switch{}) -> true;
+is_guard_cg_safe(#cg_break{}) -> true;
+is_guard_cg_safe(#cg_phi{}) -> true;
+is_guard_cg_safe({label,_}) -> true;
+is_guard_cg_safe(#cg_unreachable{}) -> false.
try_enter_cg(Ta, Vs, Tb, Evs, Th, St0) ->
{B,St1} = new_label(St0), %Body label
@@ -928,9 +1007,9 @@ put_cg([#k_var{name=R}], #k_tuple{es=Es}, _Le, St0) ->
PutTuple = #b_set{op=put_tuple,dst=Ret,args=Args},
{[PutTuple],St};
put_cg([#k_var{name=R}], #k_binary{segs=Segs}, Le, St0) ->
- Fail = bif_fail_label(St0),
+ FailCtx = fail_context(St0),
{Dst,St1} = new_ssa_var(R, St0),
- cg_binary(Dst, Segs, Fail, Le, St1);
+ cg_binary(Dst, Segs, FailCtx, Le, St1);
put_cg([#k_var{name=R}], #k_map{op=Op,var=Map,
es=[#k_map_pair{key=#k_var{}=K,val=V}]},
Le, St0) ->
@@ -959,14 +1038,14 @@ put_cg([#k_var{name=R}], Con0, _Le, St0) ->
{[],St}.
put_cg_map(LineAnno, Op, SrcMap, Dst, List, St0) ->
- Fail = bif_fail_label(St0),
Args = [#b_literal{val=Op},SrcMap|List],
PutMap = #b_set{anno=LineAnno,op=put_map,dst=Dst,args=Args},
if
Op =:= assoc ->
{[PutMap],St0};
true ->
- {Is,St} = make_cond_branch(succeeded, [Dst], Fail, St0),
+ FailCtx = fail_context(St0),
+ {Is,St} = make_succeeded(Dst, FailCtx, St0),
{[PutMap|Is],St}
end.
@@ -974,18 +1053,18 @@ put_cg_map(LineAnno, Op, SrcMap, Dst, List, St0) ->
%%% Code generation for constructing binaries.
%%%
-cg_binary(Dst, Segs0, Fail, Le, St0) ->
- {PutCode0,SzCalc0,St1} = cg_bin_put(Segs0, Fail, St0),
+cg_binary(Dst, Segs0, FailCtx, Le, St0) ->
+ {PutCode0,SzCalc0,St1} = cg_bin_put(Segs0, FailCtx, St0),
LineAnno = line_anno(Le),
- Anno = Le#k.a,
+ Anno = Le,
case PutCode0 of
[#b_set{op=bs_put,dst=Bool,args=[_,_,Src,#b_literal{val=all}|_]},
#b_br{bool=Bool},
{label,_}|_] ->
#k_bin_seg{unit=Unit0,next=Segs} = Segs0,
Unit = #b_literal{val=Unit0},
- {PutCode,SzCalc1,St2} = cg_bin_put(Segs, Fail, St1),
- {_,SzVar,SzCode0,St3} = cg_size_calc(1, SzCalc1, Fail, St2),
+ {PutCode,SzCalc1,St2} = cg_bin_put(Segs, FailCtx, St1),
+ {_,SzVar,SzCode0,St3} = cg_size_calc(1, SzCalc1, FailCtx, St2),
SzCode = cg_bin_anno(SzCode0, LineAnno),
Args = case member(single_use, Anno) of
true ->
@@ -994,14 +1073,14 @@ cg_binary(Dst, Segs0, Fail, Le, St0) ->
[#b_literal{val=append},Src,SzVar,Unit]
end,
BsInit = #b_set{anno=LineAnno,op=bs_init,dst=Dst,args=Args},
- {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St3),
+ {TestIs,St} = make_succeeded(Dst, FailCtx, St3),
{SzCode ++ [BsInit] ++ TestIs ++ PutCode,St};
[#b_set{op=bs_put}|_] ->
- {Unit,SzVar,SzCode0,St2} = cg_size_calc(8, SzCalc0, Fail, St1),
+ {Unit,SzVar,SzCode0,St2} = cg_size_calc(8, SzCalc0, FailCtx, St1),
SzCode = cg_bin_anno(SzCode0, LineAnno),
Args = [#b_literal{val=new},SzVar,Unit],
BsInit = #b_set{anno=LineAnno,op=bs_init,dst=Dst,args=Args},
- {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St2),
+ {TestIs,St} = make_succeeded(Dst, FailCtx, St2),
{SzCode ++ [BsInit] ++ TestIs ++ PutCode0,St}
end.
@@ -1009,18 +1088,18 @@ cg_bin_anno([Set|Sets], Anno) ->
[Set#b_set{anno=Anno}|Sets];
cg_bin_anno([], _) -> [].
-%% cg_size_calc(PreferredUnit, SzCalc, Fail, St0) ->
+%% cg_size_calc(PreferredUnit, SzCalc, FailCtx, St0) ->
%% {ActualUnit,SizeVariable,SizeCode,St}.
%% Generate size calculation code.
-cg_size_calc(Unit, error, _Fail, St) ->
+cg_size_calc(Unit, error, _FailCtx, St) ->
{#b_literal{val=Unit},#b_literal{val=badarg},[],St};
-cg_size_calc(8, [{1,_}|_]=SzCalc, Fail, St) ->
- cg_size_calc(1, SzCalc, Fail, St);
-cg_size_calc(8, SzCalc, Fail, St0) ->
- {Var,Pre,St} = cg_size_calc_1(SzCalc, Fail, St0),
+cg_size_calc(8, [{1,_}|_]=SzCalc, FailCtx, St) ->
+ cg_size_calc(1, SzCalc, FailCtx, St);
+cg_size_calc(8, SzCalc, FailCtx, St0) ->
+ {Var,Pre,St} = cg_size_calc_1(SzCalc, FailCtx, St0),
{#b_literal{val=8},Var,Pre,St};
-cg_size_calc(1, SzCalc0, Fail, St0) ->
+cg_size_calc(1, SzCalc0, FailCtx, St0) ->
SzCalc = map(fun({8,#b_literal{val=Size}}) ->
{1,#b_literal{val=8*Size}};
({8,{{bif,byte_size},Src}}) ->
@@ -1030,54 +1109,54 @@ cg_size_calc(1, SzCalc0, Fail, St0) ->
({_,_}=Pair) ->
Pair
end, SzCalc0),
- {Var,Pre,St} = cg_size_calc_1(SzCalc, Fail, St0),
+ {Var,Pre,St} = cg_size_calc_1(SzCalc, FailCtx, St0),
{#b_literal{val=1},Var,Pre,St}.
-cg_size_calc_1(SzCalc, Fail, St0) ->
- cg_size_calc_2(SzCalc, #b_literal{val=0}, Fail, St0).
+cg_size_calc_1(SzCalc, FailCtx, St0) ->
+ cg_size_calc_2(SzCalc, #b_literal{val=0}, FailCtx, St0).
-cg_size_calc_2([{_,{'*',Unit,{_,_}=Bif}}|T], Sum0, Fail, St0) ->
- {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0),
- {BifDst,Pre1,St2} = cg_size_bif(Bif, Fail, St1),
- {Sum,Pre2,St} = cg_size_add(Sum1, BifDst, Unit, Fail, St2),
+cg_size_calc_2([{_,{'*',Unit,{_,_}=Bif}}|T], Sum0, FailCtx, St0) ->
+ {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, FailCtx, St0),
+ {BifDst,Pre1,St2} = cg_size_bif(Bif, FailCtx, St1),
+ {Sum,Pre2,St} = cg_size_add(Sum1, BifDst, Unit, FailCtx, St2),
{Sum,Pre0++Pre1++Pre2,St};
-cg_size_calc_2([{_,#b_literal{}=Sz}|T], Sum0, Fail, St0) ->
- {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0),
- {Sum,Pre,St} = cg_size_add(Sum1, Sz, #b_literal{val=1}, Fail, St1),
+cg_size_calc_2([{_,#b_literal{}=Sz}|T], Sum0, FailCtx, St0) ->
+ {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, FailCtx, St0),
+ {Sum,Pre,St} = cg_size_add(Sum1, Sz, #b_literal{val=1}, FailCtx, St1),
{Sum,Pre0++Pre,St};
-cg_size_calc_2([{_,#b_var{}=Sz}|T], Sum0, Fail, St0) ->
- {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0),
- {Sum,Pre,St} = cg_size_add(Sum1, Sz, #b_literal{val=1}, Fail, St1),
+cg_size_calc_2([{_,#b_var{}=Sz}|T], Sum0, FailCtx, St0) ->
+ {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, FailCtx, St0),
+ {Sum,Pre,St} = cg_size_add(Sum1, Sz, #b_literal{val=1}, FailCtx, St1),
{Sum,Pre0++Pre,St};
-cg_size_calc_2([{_,{_,_}=Bif}|T], Sum0, Fail, St0) ->
- {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0),
- {BifDst,Pre1,St2} = cg_size_bif(Bif, Fail, St1),
- {Sum,Pre2,St} = cg_size_add(Sum1, BifDst, #b_literal{val=1}, Fail, St2),
+cg_size_calc_2([{_,{_,_}=Bif}|T], Sum0, FailCtx, St0) ->
+ {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, FailCtx, St0),
+ {BifDst,Pre1,St2} = cg_size_bif(Bif, FailCtx, St1),
+ {Sum,Pre2,St} = cg_size_add(Sum1, BifDst, #b_literal{val=1}, FailCtx, St2),
{Sum,Pre0++Pre1++Pre2,St};
-cg_size_calc_2([], Sum, _Fail, St) ->
+cg_size_calc_2([], Sum, _FailCtx, St) ->
{Sum,[],St}.
-cg_size_bif(#b_var{}=Var, _Fail, St) ->
+cg_size_bif(#b_var{}=Var, _FailCtx, St) ->
{Var,[],St};
-cg_size_bif({Name,Src}, Fail, St0) ->
+cg_size_bif({Name,Src}, FailCtx, St0) ->
{Dst,St1} = new_ssa_var('@ssa_bif', St0),
Bif = #b_set{op=Name,dst=Dst,args=[Src]},
- {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St1),
+ {TestIs,St} = make_succeeded(Dst, FailCtx, St1),
{Dst,[Bif|TestIs],St}.
-cg_size_add(#b_literal{val=0}, Val, #b_literal{val=1}, _Fail, St) ->
+cg_size_add(#b_literal{val=0}, Val, #b_literal{val=1}, _FailCtx, St) ->
{Val,[],St};
-cg_size_add(A, B, Unit, Fail, St0) ->
+cg_size_add(A, B, Unit, FailCtx, St0) ->
{Dst,St1} = new_ssa_var('@ssa_sum', St0),
- {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St1),
+ {TestIs,St} = make_succeeded(Dst, FailCtx, St1),
BsAdd = #b_set{op=bs_add,dst=Dst,args=[A,B,Unit]},
{Dst,[BsAdd|TestIs],St}.
-cg_bin_put(Seg, Fail, St) ->
- cg_bin_put_1(Seg, Fail, [], [], St).
+cg_bin_put(Seg, FailCtx, St) ->
+ cg_bin_put_1(Seg, FailCtx, [], [], St).
cg_bin_put_1(#k_bin_seg{size=Size0,unit=U,type=T,flags=Fs,seg=Src0,next=Next},
- Fail, Acc, SzCalcAcc, St0) ->
+ FailCtx, Acc, SzCalcAcc, St0) ->
[Src,Size] = ssa_args([Src0,Size0], St0),
NeedSize = bs_need_size(T),
TypeArg = #b_literal{val=T},
@@ -1087,9 +1166,12 @@ cg_bin_put_1(#k_bin_seg{size=Size0,unit=U,type=T,flags=Fs,seg=Src0,next=Next},
true -> [TypeArg,Flags,Src,Size,Unit];
false -> [TypeArg,Flags,Src]
end,
- {Is,St} = make_cond_branch(bs_put, Args, Fail, St0),
+ %% bs_put has its own 'succeeded' logic, and should always jump directly to
+ %% the fail label regardless of whether it's in a catch or not.
+ {_, FailLbl} = FailCtx,
+ {Is,St} = make_cond_branch(bs_put, Args, FailLbl, St0),
SzCalc = bin_size_calc(T, Src, Size, U),
- cg_bin_put_1(Next, Fail, reverse(Is, Acc), [SzCalc|SzCalcAcc], St);
+ cg_bin_put_1(Next, FailCtx, reverse(Is, Acc), [SzCalc|SzCalcAcc], St);
cg_bin_put_1(#k_bin_end{}, _, Acc, SzCalcAcc, St) ->
SzCalc = fold_size_calc(SzCalcAcc, 0, []),
{reverse(Acc),SzCalc,St}.
@@ -1139,12 +1221,22 @@ fold_size_calc([], Bits, Acc) ->
ssa_args(As, St) ->
[ssa_arg(A, St) || A <- As].
-ssa_arg(#k_var{name=V}, #cg{vars=Vars}) -> maps:get(V, Vars);
+ssa_arg(#k_var{name=V}, #cg{vars=Vars}) -> map_get(V, Vars);
ssa_arg(#k_literal{val=V}, _) -> #b_literal{val=V};
-ssa_arg(#k_atom{val=V}, _) -> #b_literal{val=V};
-ssa_arg(#k_float{val=V}, _) -> #b_literal{val=V};
-ssa_arg(#k_int{val=V}, _) -> #b_literal{val=V};
-ssa_arg(#k_nil{}, _) -> #b_literal{val=[]}.
+ssa_arg(#k_remote{mod=Mod0,name=Name0,arity=Arity}, St) ->
+ Mod = ssa_arg(Mod0, St),
+ Name = ssa_arg(Name0, St),
+ #b_remote{mod=Mod,name=Name,arity=Arity};
+ssa_arg(#k_local{name=Name,arity=Arity}, _) when is_atom(Name) ->
+ #b_local{name=#b_literal{val=Name},arity=Arity}.
+
+new_succeeded_value(VarBase, Var, #cg{vars=Vars0}=St) ->
+ Vars = Vars0#{VarBase=>{succeeded,Var}},
+ St#cg{vars=Vars}.
+
+new_bool_value(VarBase, Var, #cg{vars=Vars0}=St) ->
+ Vars = Vars0#{VarBase=>{bool,Var}},
+ St#cg{vars=Vars}.
new_ssa_vars(Vs, St) ->
mapfoldl(fun(#k_var{name=V}, S) ->
@@ -1164,6 +1256,11 @@ new_ssa_var(VarBase, #cg{lcount=Uniq,vars=Vars}=St0)
{Var,St}
end.
+set_unused_ssa_vars(Vars, St) ->
+ foldl(fun(#k_var{name=V}, S) ->
+ set_ssa_var(V, #b_literal{val=unused}, S)
+ end, St, Vars).
+
set_ssa_var(VarBase, Val, #cg{vars=Vars}=St)
when is_atom(VarBase); is_integer(VarBase) ->
St#cg{vars=Vars#{VarBase=>Val}}.
@@ -1178,23 +1275,20 @@ new_label(#cg{lcount=Next}=St) ->
%% current filename and line number. The annotation should be
%% included in any operation that could cause an exception.
-line_anno(#k{a=Anno}) ->
- line_anno_1(Anno).
-
-line_anno_1([Line,{file,Name}]) when is_integer(Line) ->
- line_anno_2(Name, Line);
-line_anno_1([_|_]=A) ->
+line_anno([Line,{file,Name}]) when is_integer(Line) ->
+ line_anno_1(Name, Line);
+line_anno([_|_]=A) ->
{Name,Line} = find_loc(A, no_file, 0),
- line_anno_2(Name, Line);
-line_anno_1([]) ->
+ line_anno_1(Name, Line);
+line_anno([]) ->
#{}.
-line_anno_2(no_file, _) ->
+line_anno_1(no_file, _) ->
#{};
-line_anno_2(_, 0) ->
+line_anno_1(_, 0) ->
%% Missing line number or line number 0.
#{};
-line_anno_2(Name, Line) ->
+line_anno_1(Name, Line) ->
#{location=>{Name,Line}}.
find_loc([Line|T], File, _) when is_integer(Line) ->
@@ -1220,27 +1314,37 @@ finalize(Asm0, St0) ->
{Asm,St} = fix_sets(Asm1, [], St0),
{build_map(Asm),St}.
+%% fix_phis(Is0) -> Is.
+%% Rewrite #cg_break{} and #cg_phi{} records to #b_set{} records.
+%% A #cg_break{} is rewritten to an unconditional branch, and
+%% and a #cg_phi{} is rewritten to one or more phi nodes.
+
fix_phis(Is) ->
fix_phis_1(Is, none, #{}).
-fix_phis_1([{label,L},#cg_phi{vars=[]}=Phi|Is0], _Lbl, Map0) ->
- case maps:is_key(L, Map0) of
- false ->
- %% No #cg_break{} references this label. Nothing else can
- %% reference it, so it can be safely be removed.
- {Is,Map} = drop_upto_label(Is0, Map0),
- fix_phis_1(Is, none, Map);
- true ->
- %% There is a break referencing this label; probably caused
- %% by a try/catch whose return value is ignored.
- [{label,L}|fix_phis_1([Phi|Is0], L, Map0)]
+fix_phis_1([{label,Lbl},#cg_phi{vars=Vars}|Is0], _Lbl, Map0) ->
+ case Map0 of
+ #{Lbl:=Pairs} ->
+ %% This phi node was referenced by at least one #cg_break{}.
+ %% Create the phi nodes.
+ Phis = gen_phis(Vars, Pairs),
+ Map = maps:remove(Lbl, Map0),
+ [{label,Lbl}] ++ Phis ++ fix_phis_1(Is0, Lbl, Map);
+ #{} ->
+ %% No #cg_break{} instructions reference this label.
+ %% #cg_break{} instructions must reference the labels for
+ %% #cg_phi{} instructions; therefore this label is
+ %% unreachable and can be dropped.
+ Is = drop_upto_label(Is0),
+ fix_phis_1(Is, none, Map0)
end;
fix_phis_1([{label,L}=I|Is], _Lbl, Map) ->
[I|fix_phis_1(Is, L, Map)];
-fix_phis_1([#cg_unreachable{}|Is0], _Lbl, Map0) ->
- {Is,Map} = drop_upto_label(Is0, Map0),
+fix_phis_1([#cg_unreachable{}|Is0], _Lbl, Map) ->
+ Is = drop_upto_label(Is0),
fix_phis_1(Is, none, Map);
fix_phis_1([#cg_break{args=Args,phi=Target}|Is], Lbl, Map) when is_integer(Lbl) ->
+ %% Pair each argument with the label for this block and save in the map.
Pairs1 = case Map of
#{Target:=Pairs0} -> Pairs0;
#{} -> []
@@ -1248,17 +1352,6 @@ fix_phis_1([#cg_break{args=Args,phi=Target}|Is], Lbl, Map) when is_integer(Lbl)
Pairs = [[{Arg,Lbl} || Arg <- Args]|Pairs1],
I = make_uncond_branch(Target),
[I|fix_phis_1(Is, none, Map#{Target=>Pairs})];
-fix_phis_1([#cg_phi{vars=Vars}|Is0], Lbl, Map0) ->
- Pairs = maps:get(Lbl, Map0),
- Map1 = maps:remove(Lbl, Map0),
- case gen_phis(Vars, Pairs) of
- [#b_set{op=phi,args=[]}] ->
- {Is,Map} = drop_upto_label(Is0, Map1),
- Ret = #b_ret{arg=#b_literal{val=unreachable}},
- [Ret|fix_phis_1(Is, none, Map)];
- Phis ->
- Phis ++ fix_phis_1(Is0, Lbl, Map1)
- end;
fix_phis_1([I|Is], Lbl, Map) ->
[I|fix_phis_1(Is, Lbl, Map)];
fix_phis_1([], _, Map) ->
@@ -1267,6 +1360,7 @@ fix_phis_1([], _, Map) ->
gen_phis([V|Vs], Preds0) ->
{Pairs,Preds} = collect_preds(Preds0, [], []),
+ [_|_] = Pairs, %Assertion.
[#b_set{op=phi,dst=V,args=Pairs}|gen_phis(Vs, Preds)];
gen_phis([], _) -> [].
@@ -1275,6 +1369,36 @@ collect_preds([[First|Rest]|T], ColAcc, RestAcc) ->
collect_preds([], ColAcc, RestAcc) ->
{keysort(2, ColAcc),RestAcc}.
+drop_upto_label([{label,_}|_]=Is) -> Is;
+drop_upto_label([_|Is]) -> drop_upto_label(Is).
+
+%% fix_sets(Is0, Acc, St0) -> {Is,St}.
+%% Ensure that #b_set.dst is filled in with a proper variable.
+%% (For convenience, for instructions that don't have a useful return value,
+%% the code generator would set #b_set.dst to `none`.)
+
+fix_sets([#b_set{op=Op,dst=Dst}=Set,#b_ret{arg=Dst}=Ret|Is], Acc, St) ->
+ NoValue = case Op of
+ remove_message -> true;
+ timeout -> true;
+ _ -> false
+ end,
+ case NoValue of
+ true ->
+ %% An instruction without value was used in effect
+ %% context in `after` block. Example:
+ %%
+ %% try
+ %% ...
+ %% after
+ %% receive _ -> ignored end
+ %% end,
+ %% ok.
+ %%
+ fix_sets(Is, [Ret#b_ret{arg=#b_literal{val=ok}},Set|Acc], St);
+ false ->
+ fix_sets(Is, [Ret,Set|Acc], St)
+ end;
fix_sets([#b_set{dst=none}=Set|Is], Acc, St0) ->
{Dst,St} = new_ssa_var('@ssa_ignored', St0),
I = Set#b_set{dst=Dst},
@@ -1284,9 +1408,14 @@ fix_sets([I|Is], Acc, St) ->
fix_sets([], Acc, St) ->
{reverse(Acc),St}.
+%% build_map(Is) -> #{}.
+%% Split up the sequential instruction stream into blocks and
+%% store them in a map.
+
build_map(Is) ->
- Blocks = build_graph_1(Is, [], []),
- maps:from_list(Blocks).
+ Linear0 = build_graph_1(Is, [], []),
+ Linear = beam_ssa:trim_unreachable(Linear0),
+ maps:from_list(Linear).
build_graph_1([{label,L}|Is], Lbls, []) ->
build_graph_1(Is, [L|Lbls], []);
@@ -1297,20 +1426,8 @@ build_graph_1([I|Is], Lbls, BlockAcc) ->
build_graph_1([], Lbls, BlockAcc) ->
make_blocks(Lbls, BlockAcc).
-make_blocks(Lbls, [Last|Is0]) ->
+make_blocks(Lbls, [Last0|Is0]) ->
Is = reverse(Is0),
+ Last = beam_ssa:normalize(Last0),
Block = #b_blk{is=Is,last=Last},
[{L,Block} || L <- Lbls].
-
-drop_upto_label([{label,_}|_]=Is, Map) ->
- {Is,Map};
-drop_upto_label([#cg_break{phi=Target}|Is], Map) ->
- Pairs = case Map of
- #{Target:=Pairs0} -> Pairs0;
- #{} -> []
- end,
- drop_upto_label(Is, Map#{Target=>Pairs});
-drop_upto_label([_|Is], Map) ->
- drop_upto_label(Is, Map).
-
-k_get_anno(Thing) -> element(2, Thing).
diff --git a/lib/compiler/src/beam_peep.erl b/lib/compiler/src/beam_peep.erl
index eed0b19037..da59aea2bd 100644
--- a/lib/compiler/src/beam_peep.erl
+++ b/lib/compiler/src/beam_peep.erl
@@ -184,7 +184,8 @@ prune_redundant_values([], _) -> [].
simplify_get_map_elements(Fail, Src, {list,[Key,Dst]},
[{get_map_elements,Fail,Src,{list,List1}}|Acc]) ->
- case are_keys_literals([Key]) andalso are_keys_literals(List1) of
+ case are_keys_literals([Key]) andalso are_keys_literals(List1) andalso
+ not is_source_overwritten(Src, List1) of
true ->
case member(Key, List1) of
true ->
@@ -217,3 +218,6 @@ simplify_has_map_fields(_, _, _) -> error.
are_keys_literals([{x,_}|_]) -> false;
are_keys_literals([{y,_}|_]) -> false;
are_keys_literals([_|_]) -> true.
+
+is_source_overwritten(Src, [_Key,Src]) -> true;
+is_source_overwritten(_, _) -> false.
diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl
index d0c5a8433f..08dbd9e648 100644
--- a/lib/compiler/src/beam_ssa.erl
+++ b/lib/compiler/src/beam_ssa.erl
@@ -21,15 +21,18 @@
-module(beam_ssa).
-export([add_anno/3,get_anno/2,get_anno/3,
- clobbers_xregs/1,def/2,def_used/2,
+ between/4,
+ clobbers_xregs/1,def/2,def_unused/3,
definitions/1,
- dominators/1,common_dominators/3,
+ dominators/1,dominators/2,common_dominators/3,
flatmapfold_instrs_rpo/4,
fold_po/3,fold_po/4,fold_rpo/3,fold_rpo/4,
fold_instrs_rpo/4,
+ is_loop_header/1,
linearize/1,
mapfold_blocks_rpo/4,
mapfold_instrs_rpo/4,
+ merge_blocks/1,
normalize/1,
no_side_effect/1,
predecessors/1,
@@ -38,8 +41,7 @@
split_blocks/3,
successors/1,successors/2,
trim_unreachable/1,
- update_phi_labels/4,used/1,
- uses/1,uses/2]).
+ used/1,uses/1,uses/2]).
-export_type([b_module/0,b_function/0,b_blk/0,b_set/0,
b_ret/0,b_br/0,b_switch/0,terminator/0,
@@ -79,9 +81,14 @@
-type var_base() :: atom() | non_neg_integer().
-type literal_value() :: atom() | integer() | float() | list() |
- nil() | tuple() | map() | binary().
+ nil() | tuple() | map() | binary() | fun().
+
+-type op() :: {'bif',atom()} |
+ {'float',float_op()} |
+ {'succeeded', 'guard' | 'body'} |
+ prim_op() |
+ cg_prim_op().
--type op() :: {'bif',atom()} | {'float',float_op()} | prim_op() | cg_prim_op().
-type anno() :: #{atom() := any()}.
-type block_map() :: #{label():=b_blk()}.
@@ -89,6 +96,7 @@
-type numbering_map() :: #{label():=non_neg_integer()}.
-type usage_map() :: #{b_var():=[{label(),b_set() | terminator()}]}.
-type definition_map() :: #{b_var():=b_set()}.
+-type predecessor_map() :: #{label():=[label()]}.
-type rename_map() :: #{b_var():=value()}.
-type rename_proplist() :: [{b_var(),value()}].
@@ -110,7 +118,6 @@
'make_fun' | 'new_try_tag' |
'peek_message' | 'phi' | 'put_list' | 'put_map' | 'put_tuple' |
'raw_raise' | 'recv_next' | 'remove_message' | 'resume' |
- 'succeeded' |
'timeout' |
'wait' | 'wait_timeout'.
@@ -120,10 +127,11 @@
%% Primops only used internally during code generation.
-type cg_prim_op() :: 'bs_get' | 'bs_get_position' | 'bs_match_string' |
'bs_restore' | 'bs_save' | 'bs_set_position' | 'bs_skip' |
- 'copy' | 'put_tuple_arity' | 'put_tuple_element' |
- 'put_tuple_elements' | 'set_tuple_element'.
+ 'copy' | 'match_fail' | 'put_tuple_arity' |
+ 'put_tuple_element' | 'put_tuple_elements' |
+ 'set_tuple_element' | 'succeeded'.
--import(lists, [foldl/3,keyfind/3,mapfoldl/3,member/2,reverse/1,umerge/1]).
+-import(lists, [foldl/3,mapfoldl/3,member/2,reverse/1,sort/1]).
-spec add_anno(Key, Value, Construct) -> Construct when
Key :: atom(),
@@ -174,6 +182,8 @@ clobbers_xregs(#b_set{op=Op}) ->
make_fun -> true;
peek_message -> true;
raw_raise -> true;
+ timeout -> true;
+ wait_timeout -> true;
_ -> false
end.
@@ -188,13 +198,18 @@ no_side_effect(#b_set{op=Op}) ->
case Op of
{bif,_} -> true;
{float,get} -> true;
+ bs_add -> true;
bs_init -> true;
+ bs_init_writable -> true;
bs_extract -> true;
bs_match -> true;
bs_start_match -> true;
bs_test_tail -> true;
bs_get_tail -> true;
bs_put -> true;
+ bs_utf16_size -> true;
+ bs_utf8_size -> true;
+ build_stacktrace -> true;
extract -> true;
get_hd -> true;
get_tl -> true;
@@ -207,14 +222,26 @@ no_side_effect(#b_set{op=Op}) ->
put_map -> true;
put_list -> true;
put_tuple -> true;
- succeeded -> true;
+ {succeeded,guard} -> true;
_ -> false
end.
--spec predecessors(Blocks) -> #{BlockNumber:=[Predecessor]} when
+%% is_loop_header(#b_set{}) -> true|false.
+%% Test whether this instruction is a loop header.
+
+-spec is_loop_header(b_set()) -> boolean().
+
+is_loop_header(#b_set{op=Op}) ->
+ case Op of
+ peek_message -> true;
+ wait -> true;
+ wait_timeout -> true;
+ _ -> false
+ end.
+
+-spec predecessors(Blocks) -> Result when
Blocks :: block_map(),
- BlockNumber :: label(),
- Predecessor :: label().
+ Result :: predecessor_map().
predecessors(Blocks) ->
P0 = [{S,L} || {L,Blk} <- maps:to_list(Blocks),
@@ -290,20 +317,22 @@ normalize(#b_br{}=Br) ->
normalize(#b_switch{arg=Arg,fail=Fail,list=List}=Sw) ->
case Arg of
#b_literal{} ->
- case keyfind(Arg, 1, List) of
- false ->
- #b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail};
- {Arg,L} ->
- #b_br{bool=#b_literal{val=true},succ=L,fail=L}
- end;
+ normalize_switch(Arg, List, Fail);
#b_var{} when List =:= [] ->
#b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail};
#b_var{} ->
- Sw
+ Sw#b_switch{list=sort(List)}
end;
normalize(#b_ret{}=Ret) ->
Ret.
+normalize_switch(Val, [{Val,L}|_], _Fail) ->
+ #b_br{bool=#b_literal{val=true},succ=L,fail=L};
+normalize_switch(Val, [_|T], Fail) ->
+ normalize_switch(Val, T, Fail);
+normalize_switch(_Val, [], Fail) ->
+ #b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail}.
+
-spec successors(label(), block_map()) -> [label()].
successors(L, Blocks) ->
@@ -319,17 +348,18 @@ def(Ls, Blocks) ->
Blks = [map_get(L, Blocks) || L <- Top],
def_1(Blks, []).
--spec def_used(Ls, Blocks) -> {Def,Used} when
+-spec def_unused(Ls, Used, Blocks) -> {Def,Unused} when
Ls :: [label()],
+ Used :: ordsets:ordset(var_name()),
Blocks :: block_map(),
Def :: ordsets:ordset(var_name()),
- Used :: ordsets:ordset(var_name()).
+ Unused :: ordsets:ordset(var_name()).
-def_used(Ls, Blocks) ->
+def_unused(Ls, Unused, Blocks) ->
Top = rpo(Ls, Blocks),
Blks = [map_get(L, Blocks) || L <- Top],
Preds = cerl_sets:from_list(Top),
- def_used_1(Blks, Preds, [], []).
+ def_unused_1(Blks, Preds, [], Unused).
%% dominators(BlockMap) -> {Dominators,Numbering}.
%% Calculate the dominator tree, returning a map where each entry
@@ -349,6 +379,13 @@ def_used(Ls, Blocks) ->
Result :: {dominator_map(), numbering_map()}.
dominators(Blocks) ->
Preds = predecessors(Blocks),
+ dominators(Blocks, Preds).
+
+-spec dominators(Blocks, Preds) -> Result when
+ Blocks :: block_map(),
+ Preds :: predecessor_map(),
+ Result :: {dominator_map(), numbering_map()}.
+dominators(Blocks, Preds) ->
Top0 = rpo(Blocks),
Df = maps:from_list(number(Top0, 0)),
[{0,[]}|Top] = [{L,map_get(L, Preds)} || L <- Top0],
@@ -511,6 +548,31 @@ rpo(From, Blocks) ->
{Ls,_} = rpo_1(From, Blocks, Seen, []),
Ls.
+%% between(From, To, Preds, Blocks) -> RPO
+%% Returns all the blocks between `From` and `To` in reverse post-order. This
+%% is most efficient when `From` dominates `To`, as it won't visit any
+%% unnecessary blocks in that case.
+
+-spec between(From, To, Preds, Blocks) -> Labels when
+ From :: label(),
+ To :: label(),
+ Preds :: predecessor_map(),
+ Blocks :: block_map(),
+ Labels :: [label()].
+
+between(From, To, Preds, Blocks) ->
+ %% Gather the predecessors of `To` and then walk forward from `From`,
+ %% skipping the blocks that don't precede `To`.
+ %%
+ %% As an optimization we initialize the predecessor set with `From` to stop
+ %% gathering once seen since we're only interested in the blocks inbetween.
+ %% Uninteresting blocks can still be added if `From` doesn't dominate `To`,
+ %% but that has no effect on the final result.
+ Filter = between_make_filter([To], Preds, cerl_sets:from_list([From])),
+ {Paths, _} = between_rpo([From], Blocks, Filter, []),
+
+ Paths.
+
-spec rename_vars(Rename, [label()], block_map()) -> block_map() when
Rename :: rename_map() | rename_proplist().
@@ -521,16 +583,16 @@ rename_vars(Rename, From, Blocks) when is_map(Rename)->
Preds = cerl_sets:from_list(Top),
F = fun(#b_set{op=phi,args=Args0}=Set) ->
Args = rename_phi_vars(Args0, Preds, Rename),
- Set#b_set{args=Args};
+ normalize(Set#b_set{args=Args});
(#b_set{args=Args0}=Set) ->
Args = [rename_var(A, Rename) || A <- Args0],
- Set#b_set{args=Args};
+ normalize(Set#b_set{args=Args});
(#b_switch{arg=Bool}=Sw) ->
- Sw#b_switch{arg=rename_var(Bool, Rename)};
+ normalize(Sw#b_switch{arg=rename_var(Bool, Rename)});
(#b_br{bool=Bool}=Br) ->
- Br#b_br{bool=rename_var(Bool, Rename)};
+ normalize(Br#b_br{bool=rename_var(Bool, Rename)});
(#b_ret{arg=Arg}=Ret) ->
- Ret#b_ret{arg=rename_var(Arg, Rename)}
+ normalize(Ret#b_ret{arg=rename_var(Arg, Rename)})
end,
map_instrs_1(Top, F, Blocks).
@@ -551,43 +613,20 @@ split_blocks(P, Blocks, Count) ->
Ls = beam_ssa:rpo(Blocks),
split_blocks_1(Ls, P, Blocks, Count).
--spec trim_unreachable(Blocks0) -> Blocks when
- Blocks0 :: block_map(),
- Blocks :: block_map().
+-spec trim_unreachable(SSA0) -> SSA when
+ SSA0 :: block_map() | [{label(),b_blk()}],
+ SSA :: block_map() | [{label(),b_blk()}].
%% trim_unreachable(Blocks0) -> Blocks.
%% Remove all unreachable blocks. Adjust all phi nodes so
%% they don't refer to blocks that has been removed or no
%% no longer branch to the phi node in question.
-trim_unreachable(Blocks) ->
+trim_unreachable(Blocks) when is_map(Blocks) ->
%% Could perhaps be optimized if there is any need.
- maps:from_list(linearize(Blocks)).
-
-%% update_phi_labels([BlockLabel], Old, New, Blocks0) -> Blocks.
-%% In the given blocks, replace label Old in with New in all
-%% phi nodes. This is useful after merging or splitting
-%% blocks.
-
--spec update_phi_labels(From, Old, New, Blocks0) -> Blocks when
- From :: [label()],
- Old :: label(),
- New :: label(),
- Blocks0 :: block_map(),
- Blocks :: block_map().
-
-update_phi_labels([L|Ls], Old, New, Blocks0) ->
- case Blocks0 of
- #{L:=#b_blk{is=[#b_set{op=phi}|_]=Is0}=Blk0} ->
- Is = update_phi_labels_is(Is0, Old, New),
- Blk = Blk0#b_blk{is=Is},
- Blocks = Blocks0#{L:=Blk},
- update_phi_labels(Ls, Old, New, Blocks);
- #{L:=#b_blk{}} ->
- %% No phi nodes in this block.
- update_phi_labels(Ls, Old, New, Blocks0)
- end;
-update_phi_labels([], _, _, Blocks) -> Blocks.
+ maps:from_list(linearize(Blocks));
+trim_unreachable([_|_]=Blocks) ->
+ trim_unreachable_1(Blocks, cerl_sets:from_list([0])).
-spec used(b_blk() | b_set() | terminator()) -> [var_name()].
@@ -633,6 +672,12 @@ fold_uses_block(Lbl, #b_blk{is=Is,last=Last}, UseMap0) ->
end,
F(Last, foldl(F, UseMap0, Is)).
+-spec merge_blocks(block_map()) -> block_map().
+
+merge_blocks(Blocks) ->
+ Preds = predecessors(Blocks),
+ merge_blocks_1(rpo(Blocks), Preds, Blocks).
+
%%%
%%% Internal functions.
%%%
@@ -651,34 +696,28 @@ is_commutative('=/=') -> true;
is_commutative('/=') -> true;
is_commutative(_) -> false.
-def_used_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, UsedAcc) ->
- {Def,Used} = def_used_is(Is, Preds, Def0, used(Last)),
- case Used of
- [] ->
- def_used_1(Bs, Preds, Def, UsedAcc);
- [_|_] ->
- def_used_1(Bs, Preds, Def, [Used|UsedAcc])
- end;
-def_used_1([], _Preds, Def0, UsedAcc) ->
- Def = ordsets:from_list(Def0),
- Used = umerge(UsedAcc),
- {Def,Used}.
+def_unused_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, Unused0) ->
+ Unused1 = ordsets:subtract(Unused0, used(Last)),
+ {Def,Unused} = def_unused_is(Is, Preds, Def0, Unused1),
+ def_unused_1(Bs, Preds, Def, Unused);
+def_unused_1([], _Preds, Def, Unused) ->
+ {ordsets:from_list(Def), Unused}.
-def_used_is([#b_set{op=phi,dst=Dst,args=Args}|Is],
- Preds, Def0, Used0) ->
+def_unused_is([#b_set{op=phi,dst=Dst,args=Args}|Is],
+ Preds, Def0, Unused0) ->
Def = [Dst|Def0],
%% We must be careful to only include variables that will
%% be used when arriving from one of the predecessor blocks
%% in Preds.
- Used1 = [V || {#b_var{}=V,L} <- Args, cerl_sets:is_element(L, Preds)],
- Used = ordsets:union(ordsets:from_list(Used1), Used0),
- def_used_is(Is, Preds, Def, Used);
-def_used_is([#b_set{dst=Dst}=I|Is], Preds, Def0, Used0) ->
+ Unused1 = [V || {#b_var{}=V,L} <- Args, cerl_sets:is_element(L, Preds)],
+ Unused = ordsets:subtract(Unused0, ordsets:from_list(Unused1)),
+ def_unused_is(Is, Preds, Def, Unused);
+def_unused_is([#b_set{dst=Dst}=I|Is], Preds, Def0, Unused0) ->
Def = [Dst|Def0],
- Used = ordsets:union(used(I), Used0),
- def_used_is(Is, Preds, Def, Used);
-def_used_is([], _Preds, Def, Used) ->
- {Def,Used}.
+ Unused = ordsets:subtract(Unused0, used(I)),
+ def_unused_is(Is, Preds, Def, Unused);
+def_unused_is([], _Preds, Def, Unused) ->
+ {Def,Unused}.
def_1([#b_blk{is=Is}|Bs], Def0) ->
Def = def_is(Is, Def0),
@@ -796,6 +835,61 @@ is_successor(L, Pred, S) ->
false
end.
+trim_unreachable_1([{L,Blk0}|Bs], Seen0) ->
+ Blk = trim_phis(Blk0, Seen0),
+ case cerl_sets:is_element(L, Seen0) of
+ false ->
+ trim_unreachable_1(Bs, Seen0);
+ true ->
+ case successors(Blk) of
+ [] ->
+ [{L,Blk}|trim_unreachable_1(Bs, Seen0)];
+ [_|_]=Successors ->
+ Seen = cerl_sets:union(Seen0, cerl_sets:from_list(Successors)),
+ [{L,Blk}|trim_unreachable_1(Bs, Seen)]
+ end
+ end;
+trim_unreachable_1([], _) -> [].
+
+trim_phis(#b_blk{is=[#b_set{op=phi}|_]=Is0}=Blk, Seen) ->
+ Is = trim_phis_1(Is0, Seen),
+ Blk#b_blk{is=Is};
+trim_phis(Blk, _Seen) -> Blk.
+
+trim_phis_1([#b_set{op=phi,args=Args0}=I|Is], Seen) ->
+ Args = [P || {_,L}=P <- Args0, cerl_sets:is_element(L, Seen)],
+ [I#b_set{args=Args}|trim_phis_1(Is, Seen)];
+trim_phis_1(Is, _Seen) -> Is.
+
+between_make_filter([L | Ls], Preds, Acc0) ->
+ case cerl_sets:is_element(L, Acc0) of
+ true ->
+ between_make_filter(Ls, Preds, Acc0);
+ false ->
+ Next = map_get(L, Preds),
+ Acc1 = cerl_sets:add_element(L, Acc0),
+
+ Acc = between_make_filter(Next, Preds, Acc1),
+ between_make_filter(Ls, Preds, Acc)
+ end;
+between_make_filter([], _Preds, Acc) ->
+ Acc.
+
+between_rpo([L | Ls], Blocks, Filter0, Acc0) ->
+ case cerl_sets:is_element(L, Filter0) of
+ true ->
+ Block = map_get(L, Blocks),
+ Filter1 = cerl_sets:del_element(L, Filter0),
+
+ Successors = successors(Block),
+ {Acc, Filter} = between_rpo(Successors, Blocks, Filter1, Acc0),
+ between_rpo(Ls, Blocks, Filter, [L | Acc]);
+ false ->
+ between_rpo(Ls, Blocks, Filter0, Acc0)
+ end;
+between_rpo([], _, Filter, Acc) ->
+ {Acc, Filter}.
+
rpo_1([L|Ls], Blocks, Seen0, Acc0) ->
case cerl_sets:is_element(L, Seen0) of
true ->
@@ -896,3 +990,110 @@ used_1([H|T], Used0) ->
Used = ordsets:union(used(H), Used0),
used_1(T, Used);
used_1([], Used) -> Used.
+
+
+%%% Merge blocks.
+
+merge_blocks_1([L|Ls], Preds0, Blocks0) ->
+ case Preds0 of
+ #{L:=[P]} ->
+ #{P:=Blk0,L:=Blk1} = Blocks0,
+ case is_merge_allowed(L, Blk0, Blk1) of
+ true ->
+ #b_blk{is=Is0} = Blk0,
+ #b_blk{is=Is1} = Blk1,
+ verify_merge_is(Is1),
+ Is = Is0 ++ Is1,
+ Blk2 = Blk1#b_blk{is=Is},
+ Blk = merge_fix_succeeded(Blk2),
+ Blocks1 = maps:remove(L, Blocks0),
+ Blocks2 = Blocks1#{P:=Blk},
+ Successors = successors(Blk),
+ Blocks = update_phi_labels(Successors, L, P, Blocks2),
+ Preds = merge_update_preds(Successors, L, P, Preds0),
+ merge_blocks_1(Ls, Preds, Blocks);
+ false ->
+ merge_blocks_1(Ls, Preds0, Blocks0)
+ end;
+ #{} ->
+ merge_blocks_1(Ls, Preds0, Blocks0)
+ end;
+merge_blocks_1([], _Preds, Blocks) -> Blocks.
+
+merge_update_preds([L|Ls], From, To, Preds0) ->
+ Ps = [rename_label(P, From, To) || P <- map_get(L, Preds0)],
+ Preds = Preds0#{L:=Ps},
+ merge_update_preds(Ls, From, To, Preds);
+merge_update_preds([], _, _, Preds) -> Preds.
+
+merge_fix_succeeded(#b_blk{is=[_|_]=Is0,last=#b_br{succ=To,fail=To}}=Blk) ->
+ case reverse(Is0) of
+ [#b_set{op={succeeded,guard},args=[Dst]},#b_set{dst=Dst}|Is] ->
+ %% A succeeded:guard instruction must not be followed by a one-way
+ %% branch. Eliminate it. (We do this mainly for the benefit of the
+ %% beam_ssa_bool pass. When called from beam_ssa_opt there should
+ %% be no such instructions left.)
+ Blk#b_blk{is=reverse(Is)};
+ _ ->
+ Blk
+ end;
+merge_fix_succeeded(Blk) -> Blk.
+
+verify_merge_is([#b_set{op=Op}|_]) ->
+ %% The merged block has only one predecessor, so it should not have any phi
+ %% nodes.
+ true = Op =/= phi; %Assertion.
+verify_merge_is(_) ->
+ ok.
+
+is_merge_allowed(?EXCEPTION_BLOCK, #b_blk{}, #b_blk{}) ->
+ false;
+is_merge_allowed(_L, #b_blk{is=[#b_set{op=landingpad} | _]}, #b_blk{}) ->
+ false;
+is_merge_allowed(_L, #b_blk{}, #b_blk{is=[#b_set{op=landingpad} | _]}) ->
+ false;
+is_merge_allowed(L, #b_blk{}=Blk1, #b_blk{is=[#b_set{}=I|_]}=Blk2) ->
+ not is_loop_header(I) andalso
+ is_merge_allowed_1(L, Blk1, Blk2);
+is_merge_allowed(L, Blk1, Blk2) ->
+ is_merge_allowed_1(L, Blk1, Blk2).
+
+is_merge_allowed_1(L, #b_blk{last=#b_br{}}=Blk, #b_blk{is=Is}) ->
+ %% The predecessor block must have exactly one successor (L) for
+ %% the merge to be safe.
+ case successors(Blk) of
+ [L] ->
+ case Is of
+ [#b_set{op=phi,args=[_]}|_] ->
+ %% The type optimizer pass must have been
+ %% turned off, since it would have removed this
+ %% redundant phi node. Refuse to merge the blocks
+ %% to ensure that this phi node remains at the
+ %% beginning of a block.
+ false;
+ _ ->
+ true
+ end;
+ [_|_] ->
+ false
+ end;
+is_merge_allowed_1(_, #b_blk{last=#b_switch{}}, #b_blk{}) ->
+ false.
+
+%% update_phi_labels([BlockLabel], Old, New, Blocks0) -> Blocks.
+%% In the given blocks, replace label Old in with New in all
+%% phi nodes. This is useful after merging or splitting
+%% blocks.
+
+update_phi_labels([L|Ls], Old, New, Blocks0) ->
+ case Blocks0 of
+ #{L:=#b_blk{is=[#b_set{op=phi}|_]=Is0}=Blk0} ->
+ Is = update_phi_labels_is(Is0, Old, New),
+ Blk = Blk0#b_blk{is=Is},
+ Blocks = Blocks0#{L:=Blk},
+ update_phi_labels(Ls, Old, New, Blocks);
+ #{L:=#b_blk{}} ->
+ %% No phi nodes in this block.
+ update_phi_labels(Ls, Old, New, Blocks0)
+ end;
+update_phi_labels([], _, _, Blocks) -> Blocks.
diff --git a/lib/compiler/src/beam_ssa.hrl b/lib/compiler/src/beam_ssa.hrl
index fa76b08453..509a94135e 100644
--- a/lib/compiler/src/beam_ssa.hrl
+++ b/lib/compiler/src/beam_ssa.hrl
@@ -62,5 +62,13 @@
-record(b_local, {name :: beam_ssa:b_literal(),
arity :: non_neg_integer()}).
-%% If this block exists, it calls erlang:error(badarg).
--define(BADARG_BLOCK, 1).
+%% This is a psuedo-block used to express that certain instructions and BIFs
+%% throw exceptions on failure. The code generator rewrites all branches to
+%% this block to {f,0} which causes the instruction to throw an exception
+%% instead of branching.
+%%
+%% Since this is not an ordinary block, it's illegal to merge it with other
+%% blocks, and jumps are only valid when we know that an exception will be
+%% thrown by the operation that branches here; the *block itself* does not
+%% throw an exception.
+-define(EXCEPTION_BLOCK, 1).
diff --git a/lib/compiler/src/beam_ssa_bool.erl b/lib/compiler/src/beam_ssa_bool.erl
new file mode 100644
index 0000000000..46066731bd
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_bool.erl
@@ -0,0 +1,1703 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% The purpose of this pass is to optimize boolean expressions in
+%% guards. Instead of evaluating a boolean expression and finally
+%% comparing it to 'true', evaluate the expression using control flow.
+%%
+%% This pass is run directly after conversion to SSA code because some
+%% optimizations in beam_ssa_opt (especially sinking of
+%% get_tuple_element instructions) would prevent these optimizations
+%% or at least make them much more difficult to perform.
+%%
+%% As an example, take the guard:
+%%
+%% when is_integer(V0), is_atom(V1) ->
+%%
+%% The unoptimized SSA code translated to pseudo BEAM code would look
+%% like:
+%%
+%% bif is_integer V0 => Bool0
+%% bif is_atom V1 => Bool1
+%% bif and Bool0 Bool1 => Bool
+%% test Bool =:= true else goto Fail
+%% ...
+%% Fail:
+%% ...
+%%
+%% The optimized code would look like:
+%%
+%% test is_integer V0 else goto Fail
+%% test is_atom V1 else goto Fail
+%% ...
+%% Fail:
+%% ...
+%%
+%% An 'or' operation is only slightly more complicated:
+%%
+%% test is_integer V0 else goto NotFailedYet
+%% goto Success
+%%
+%% NotFailedYet:
+%% test is_atom V1 else goto Fail
+%%
+%% Success:
+%% ...
+%% Fail:
+%% ...
+%%
+%% The unoptimized SSA code for the first example looks like:
+%%
+%% 0:
+%% _2 = bif:is_integer _0
+%% _3 = bif:is_atom _1
+%% _7 = bif:'and' _2, _3
+%% @ssa_bool = succeeded:guard _7
+%% br @ssa_bool, label 4, label 3
+%%
+%% 4:
+%% @ssa_bool:5 = bif:'=:=' _7, literal true
+%% br @ssa_bool:5, label 6, label 3
+%%
+%% 6:
+%% ret literal ok
+%%
+%% 3: Error.
+%% ...
+%%
+%% The optimized SSA code looks like:
+%%
+%% 0:
+%% _2 = bif:is_integer _0
+%% br _2, label 11, label 3
+%%
+%% 11:
+%% _3 = bif:is_atom _1
+%% br _3, label 6, label 3
+%%
+%% 6:
+%% ret literal ok
+%%
+%% 3: Error.
+%% ...
+
+-module(beam_ssa_bool).
+-export([module/2]).
+
+-import(lists, [all/2,foldl/3,keyfind/3,last/1,partition/2,
+ reverse/1,reverse/2,sort/1]).
+
+-include("beam_ssa.hrl").
+
+-record(st, {defs=#{},
+ ldefs=#{},
+ count :: beam_ssa:label(),
+ dom,
+ uses}).
+
+-spec module(beam_ssa:b_module(), [compile:option()]) ->
+ {'ok',beam_ssa:b_module()}.
+
+module(#b_module{body=Fs0}=Module, _Opts) ->
+ Fs = [function(F) || F <- Fs0],
+ {ok,Module#b_module{body=Fs}}.
+
+function(#b_function{anno=Anno}=F) ->
+ try
+ opt_function(F)
+ catch
+ Class:Error:Stack ->
+ #{func_info:={_,Name,Arity}} = Anno,
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+opt_function(#b_function{bs=Blocks0,cnt=Count0}=F) ->
+ {Blocks1,Count1} = pre_opt(Blocks0, Count0),
+ DefVars = interesting_defs(Blocks1),
+ if
+ map_size(DefVars) > 1 ->
+ Dom = beam_ssa:dominators(Blocks1),
+ Uses = beam_ssa:uses(Blocks1),
+ St0 = #st{defs=DefVars,count=Count1,dom=Dom,uses=Uses},
+ {Blocks2,St} = bool_opt(Blocks1, St0),
+ Count = St#st.count,
+
+ %% When merging blocks, phi nodes must have the same
+ %% number of arguments as the number of predecessors.
+ %% To ensure that, trim before merging.
+
+ Blocks3 = beam_ssa:trim_unreachable(Blocks2),
+ Blocks = beam_ssa:merge_blocks(Blocks3),
+ F#b_function{bs=Blocks,cnt=Count};
+ true ->
+ %% There are no boolean operators that can be optimized in
+ %% this function.
+ F#b_function{bs=Blocks1,cnt=Count1}
+ end.
+
+%%%
+%%% Do some optimizations to help the main boolean optimization pass:
+%%%
+%%% * Remove `succeeded` instructions that can't fail after `and`,
+%%% `or`, and `not`. The main optimization pass can only optimize
+%%% boolean operators that are known not to fail.
+%%%
+%%% * Rewrite a boolean #b_switch{} to a #b_br{} if the fail label
+%%% can't be reached or is not important. (The main optimization
+%%% can't handle #b_switch{}.)
+%%%
+%%% * Simplify phi nodes, eliminating them if they only have one
+%%% value. Also annotate phi nodes that are known to evaluate
+%%% to a boolean.
+%%%
+
+-type var() :: beam_ssa:b_var().
+
+%% Note: We use the substitution map for both substitutions and type
+%% information. If the associated value for a variable is a #b_set{},
+%% it means that the value is a boolean.
+-type pre_sub_val() ::
+ beam_ssa:value() | %Value to be substituted.
+ beam_ssa:b_set() | %This variable is a boolean.
+ {'true_or_any',beam_ssa:label()} |
+ '=:='.
+
+-type pre_sub_map() :: #{'uses' => {'uses',beam_ssa:block_map() | list()},
+ var() => pre_sub_val()}.
+
+pre_opt(Blocks, Count) ->
+ Top = beam_ssa:rpo(Blocks),
+
+ %% Collect information to help the pre_opt pass to optimize
+ %% `switch` instructions.
+ Sub0 = #{uses => {uses,Blocks}},
+ Sub1 = get_phi_info(Top, Blocks, Sub0),
+ Sub = maps:remove(uses, Sub1),
+
+ %% Now do the actual optimizations.
+ Reached = cerl_sets:from_list([hd(Top)]),
+ pre_opt(Top, Sub, Reached, Count, Blocks).
+
+-spec get_phi_info(Ls, Blocks, Sub0) -> Sub when
+ Ls :: [beam_ssa:label()],
+ Blocks :: beam_ssa:block_map(),
+ Sub0 :: pre_sub_map(),
+ Sub :: pre_sub_map().
+
+%% get_phi_info([Label], Blocks, Sub0) -> Sub.
+%% Collect information to help us optimize `switch` instructions
+%% such as:
+%%
+%% switch SomeVar, label _, [ {literal false, _ }, {literal true, _ } ]
+%% .
+%% .
+%% .
+%% PhiVar = phi { SomeVar, _ }, { literal fail, _ }, { literal false, _}
+%% EqBool = bif:'=:=' PhiVar, literal true
+%%
+%% Here it can be seen that `SomeVar` is compared to `true`. If
+%% `SomeVar` is not `true`, it does not matter whether its value is
+%% `false` or some other value. That means that the `switch` can be
+%% replaced with a two-way `br`:
+%%
+%% NewBoolVar = bif:'=:=' SomeVar, literal true
+%% br NewBoolVar, label _, label _
+%%
+%% For this example, the value {true_or_any,LabelOfPhiBlock} will be
+%% added for the key `SomeVar` in the substitution map.
+
+get_phi_info([L|Ls], Blocks, Sub0) ->
+ Sub = get_phi_info(Ls, Blocks, Sub0),
+ #b_blk{is=Is} = map_get(L, Blocks),
+ get_phi_info_is(Is, L, Sub);
+get_phi_info([], _, Sub) -> Sub.
+
+get_phi_info_is([I|Is], From, Sub0) ->
+ Sub = get_phi_info_is(Is, From, Sub0),
+ get_phi_info_instr(I, From, Sub);
+get_phi_info_is([], _, Sub) -> Sub.
+
+get_phi_info_instr(#b_set{op={bif,'=:='},
+ args=[#b_var{}=Bool,#b_literal{val=true}]},
+ _From, Sub) ->
+ Sub#{Bool=>'=:='};
+get_phi_info_instr(#b_set{op=phi,dst=Dst,args=Args}, From, Sub0) ->
+ {Safe,Sub} =
+ case Sub0 of
+ #{Dst:='=:='} ->
+ get_phi_info_single_use(Dst, Sub0);
+ #{Dst:={true_or_any,_}} ->
+ get_phi_info_single_use(Dst, Sub0);
+ #{} ->
+ {false,Sub0}
+ end,
+ case Safe of
+ true ->
+ foldl(fun({#b_var{}=V,_}, A) ->
+ A#{V => {true_or_any,From}};
+ (_, A) -> A
+ end, Sub, Args);
+ false -> Sub
+ end;
+get_phi_info_instr(_, _, Sub) -> Sub.
+
+get_phi_info_single_use(Var, Sub) ->
+ case map_get(uses, Sub) of
+ Uses when is_map(Uses) ->
+ {case Uses of
+ #{Var:=[_]} -> true;
+ #{Var:=[_|_]} -> false
+ end,Sub};
+ {uses,Blocks} ->
+ Uses = beam_ssa:uses(Blocks),
+ get_phi_info_single_use(Var, Sub#{uses => Uses})
+ end.
+
+-spec pre_opt(Ls, Sub, Reached, Count0, Blocks0) -> {Blocks,Count} when
+ Ls :: [beam_ssa:label()],
+ Reached :: cerl_sets:set(beam_ssa:label()),
+ Count0 :: beam_ssa:label(),
+ Blocks0 :: beam_ssa:block_map(),
+ Sub :: pre_sub_map(),
+ Count :: beam_ssa:label(),
+ Blocks :: beam_ssa:block_map().
+
+pre_opt([L|Ls], Sub0, Reached0, Count0, Blocks) ->
+ case cerl_sets:is_element(L, Reached0) of
+ false ->
+ %% This block will never be reached.
+ pre_opt(Ls, Sub0, Reached0, Count0, maps:remove(L, Blocks));
+ true ->
+ #b_blk{is=Is0,last=Last0} = Blk0 = map_get(L, Blocks),
+ {Is,Sub} = pre_opt_is(Is0, Reached0, Sub0, []),
+ case pre_opt_terminator(Last0, Sub, Blocks) of
+ {#b_set{}=Test0,#b_br{}=Br0} ->
+ %% Here is a #b_switch{} that has been reduced to
+ %% a '=:=' followed by a two-way `br`.
+ Bool = #b_var{name={'@ssa_bool',Count0}},
+ Count = Count0 + 1,
+ Test = Test0#b_set{dst=Bool},
+ Br = beam_ssa:normalize(Br0#b_br{bool=Bool}),
+ Blk = Blk0#b_blk{is=Is++[Test],last=Br},
+ Successors = beam_ssa:successors(Blk),
+ Reached = cerl_sets:union(Reached0,
+ cerl_sets:from_list(Successors)),
+ pre_opt(Ls, Sub, Reached, Count, Blocks#{L:=Blk});
+ Last ->
+ Blk = Blk0#b_blk{is=Is,last=Last},
+ Successors = beam_ssa:successors(Blk),
+ Reached = cerl_sets:union(Reached0,
+ cerl_sets:from_list(Successors)),
+ pre_opt(Ls, Sub, Reached, Count0, Blocks#{L:=Blk})
+ end
+ end;
+pre_opt([], _, _, Count, Blocks) ->
+ {Blocks,Count}.
+
+pre_opt_is([#b_set{op=phi,dst=Dst,args=Args0}=I0|Is], Reached, Sub0, Acc) ->
+ Args1 = [{Val,From} || {Val,From} <- Args0,
+ cerl_sets:is_element(From, Reached)],
+ Args = sub_args(Args1, Sub0),
+ case all_same(Args) of
+ true ->
+ %% Single value or all values are the same. We can remove
+ %% the phi node.
+ {Arg,_} = hd(Args),
+ Sub = Sub0#{Dst=>Arg},
+ pre_opt_is(Is, Reached, Sub, Acc);
+ false ->
+ case pre_is_phi_bool(Args, Sub0) of
+ true ->
+ %% The value of the phi node is always a
+ %% boolean. Update type information in the sub map
+ %% and add an annotation.
+ Anno = I0#b_set.anno,
+ I = I0#b_set{args=Args,anno=Anno#{boolean_phi=>true}},
+ Sub = Sub0#{Dst=>I},
+ pre_opt_is(Is, Reached, Sub, [I|Acc]);
+ false ->
+ I = I0#b_set{args=Args},
+ pre_opt_is(Is, Reached, Sub0, [I|Acc])
+ end
+ end;
+pre_opt_is([#b_set{op={succeeded,_},dst=Dst,args=Args0}=I0|Is],
+ Reached, Sub0, Acc) ->
+ [Arg] = Args = sub_args(Args0, Sub0),
+ I = I0#b_set{args=Args},
+ case pre_is_safe_bool(Arg, Sub0) of
+ true ->
+ %% The preceding boolean operation can't fail. Get rid
+ %% of this `succeeded` instruction.
+ Sub = Sub0#{Dst=>#b_literal{val=true}},
+ pre_opt_is(Is, Reached, Sub, Acc);
+ false ->
+ pre_opt_is(Is, Reached, Sub0, [I|Acc])
+ end;
+pre_opt_is([#b_set{dst=Dst,args=Args0}=I0|Is], Reached, Sub0, Acc) ->
+ Args = sub_args(Args0, Sub0),
+ I = I0#b_set{args=Args},
+ case is_bool_expr(I) of
+ true ->
+ case pre_eval_op(I, Sub0) of
+ none ->
+ Sub = Sub0#{Dst=>I},
+ pre_opt_is(Is, Reached, Sub, [I|Acc]);
+ #b_var{}=Var ->
+ %% We must remove the 'succeeded' instruction that
+ %% follows since the variable it checks is gone.
+ [#b_set{op={succeeded,_},dst=SuccDst,args=[Dst]}] = Is,
+ Sub = Sub0#{Dst=>Var,SuccDst=>#b_literal{val=true}},
+ pre_opt_is([], Reached, Sub, Acc);
+ #b_literal{}=Lit ->
+ Sub = Sub0#{Dst=>Lit},
+ pre_opt_is(Is, Reached, Sub, Acc)
+ end;
+ false ->
+ pre_opt_is(Is, Reached, Sub0, [I|Acc])
+ end;
+pre_opt_is([], _Reached, Sub, Acc) ->
+ {reverse(Acc),Sub}.
+
+pre_opt_terminator(#b_br{bool=#b_literal{}}=Br, _Sub, _Blocks) ->
+ Br;
+pre_opt_terminator(#b_br{bool=Bool}=Br0, Sub, Blocks) ->
+ case beam_ssa:normalize(Br0#b_br{bool=sub_arg(Bool, Sub)}) of
+ Br0 ->
+ Br0;
+ #b_br{bool=#b_literal{val=true},succ=Next}=Br ->
+ %% See if the terminator from the successor block
+ %% can be incorporated into this block to give
+ %% more opportunities for optimization.
+ #b_blk{is=Is,last=Last} = map_get(Next, Blocks),
+ case {Is,Last} of
+ {[],#b_switch{}} ->
+ pre_opt_terminator(Last, Sub, Blocks);
+ {_,_} ->
+ Br
+ end
+ end;
+pre_opt_terminator(#b_ret{arg=Arg}=Ret, Sub, _Blocks) ->
+ beam_ssa:normalize(Ret#b_ret{arg=sub_arg(Arg, Sub)});
+pre_opt_terminator(#b_switch{arg=Arg0}=Sw0, Sub, Blocks) ->
+ case beam_ssa:normalize(Sw0#b_switch{arg=sub_arg(Arg0, Sub)}) of
+ #b_switch{arg=Arg,list=List}=Sw ->
+ case sort(List) of
+ [{#b_literal{val=false},Fail},
+ {#b_literal{val=true},Succ}] ->
+ case pre_is_arg_bool(Arg, Sub) of
+ false ->
+ pre_opt_sw(Sw, Fail, Succ, Sub, Blocks);
+ true ->
+ beam_ssa:normalize(#b_br{bool=Arg,succ=Succ,fail=Fail})
+ end;
+ _ ->
+ Sw
+ end;
+ Other ->
+ pre_opt_terminator(Other, Sub, Blocks)
+ end.
+
+pre_opt_sw(#b_switch{arg=Arg,fail=Fail}=Sw, False, True, Sub, Blocks) ->
+ case Sub of
+ #{Arg:={true_or_any,PhiL}} ->
+ #{Fail:=FailBlk,False:=FalseBlk,PhiL:=PhiBlk} = Blocks,
+ case {FailBlk,FalseBlk,PhiBlk} of
+ {#b_blk{is=[],last=#b_br{succ=PhiL,fail=PhiL}},
+ #b_blk{is=[],last=#b_br{succ=PhiL,fail=PhiL}},
+ #b_blk{is=[#b_set{op=phi,args=PhiArgs}|_]}} ->
+ case keyfind(False, 2, PhiArgs) of
+ {#b_literal{val=Bool},False} when Bool =/= true ->
+ %% This is an `andalso` in a guard. The code
+ %% can be simplified to a two-way `br` because
+ %% the actual value of the variable does not
+ %% matter if it is not equal to `true`.
+ DummyDst = #b_var{name=0},
+ Br0 = #b_br{bool=DummyDst,succ=True,fail=False},
+ Br = beam_ssa:normalize(Br0),
+ {#b_set{op={bif,'=:='},dst=DummyDst,
+ args=[Arg,#b_literal{val=true}]},
+ Br};
+ {_,_} ->
+ Sw
+ end;
+ {_,_,_} ->
+ Sw
+ end;
+ #{} ->
+ Sw
+ end.
+
+pre_eval_op(#b_set{op={bif,Op},args=Args}, Sub) ->
+ case pre_are_args_bool(Args, Sub) of
+ true ->
+ case {Op,Args} of
+ {'and',[#b_literal{val=true},#b_var{}=Res]} -> Res;
+ {'and',[#b_literal{val=false}=Res,#b_var{}]} -> Res;
+ {'and',[#b_var{}=Res,#b_literal{val=true}]} -> Res;
+ {'and',[#b_var{},#b_literal{val=false}=Res]} -> Res;
+ {'or',[#b_literal{val=true}=Res,#b_var{}]} -> Res;
+ {'or',[#b_literal{val=false},#b_var{}=Res]} -> Res;
+ {'or',[#b_var{},#b_literal{val=true}=Res]} -> Res;
+ {'or',[#b_var{}=Res,#b_literal{val=false}]} -> Res;
+ _ -> none
+ end;
+ false ->
+ none
+ end.
+
+all_same([{H,_}|T]) ->
+ all(fun({E,_}) -> E =:= H end, T).
+
+pre_is_phi_bool([{#b_literal{val=Lit},_}|As], Sub) ->
+ is_boolean(Lit) andalso pre_is_phi_bool(As, Sub);
+pre_is_phi_bool([{#b_var{}=A,_}|As], Sub) ->
+ case Sub of
+ #{A:=#b_set{}} ->
+ pre_is_phi_bool(As, Sub);
+ #{} ->
+ false
+ end;
+pre_is_phi_bool([], _Sub) -> true.
+
+pre_is_safe_bool(#b_literal{}, _Sub) ->
+ true;
+pre_is_safe_bool(Var, Sub) ->
+ case Sub of
+ #{Var:=#b_set{op={bif,is_function},
+ args=[_,Arity]}} ->
+ case Arity of
+ #b_literal{val=Lit} ->
+ is_integer(Lit) andalso Lit >= 0;
+ #b_var{} ->
+ false
+ end;
+ #{Var:=#b_set{op={bif,Op},args=Args}} ->
+ Arity = length(Args),
+ erl_internal:bool_op(Op, Arity) andalso
+ pre_are_args_bool(Args, Sub);
+ #{} ->
+ false
+ end.
+
+pre_are_args_bool([A|As], Sub) ->
+ pre_is_arg_bool(A, Sub) andalso pre_are_args_bool(As, Sub);
+pre_are_args_bool([], _Sub) -> true.
+
+pre_is_arg_bool(#b_literal{val=Lit}, _Sub) ->
+ is_boolean(Lit);
+pre_is_arg_bool(#b_var{}=A, Sub) ->
+ case Sub of
+ #{A:=#b_set{}} ->
+ true;
+ #{} ->
+ false
+ end.
+
+%%%
+%%% Build a map from variable to definitions for boolean expressions
+%%% phi nodes. This map will be used by collect_bool_vars() and by
+%%% shortcut_branches().
+%%%
+
+interesting_defs(Blocks) ->
+ interesting_defs(maps:to_list(Blocks), []).
+
+interesting_defs([{L,#b_blk{is=Is}}|Bs], Acc) ->
+ interesting_defs(Bs, interesting_defs_is(Is, L, Acc));
+interesting_defs([], Acc) ->
+ maps:from_list(Acc).
+
+interesting_defs_is([#b_set{op={bif,_},dst=V}=I|Is], L, Acc) ->
+ case is_bool_expr(I) of
+ true ->
+ interesting_defs_is(Is, L, [{V,{L,I}}|Acc]);
+ false ->
+ interesting_defs_is(Is, L, Acc)
+ end;
+interesting_defs_is([#b_set{op=phi,dst=V}=Set|Is], L, Acc) ->
+ interesting_defs_is(Is, L, [{V,{L,Set}}|Acc]);
+interesting_defs_is([#b_set{}|Is], L, Acc) ->
+ interesting_defs_is(Is, L, Acc);
+interesting_defs_is([], _L, Acc) -> Acc.
+
+%%%
+%%% Search for boolean expressions to optimize.
+%%%
+%%% The main purpose of this module is to optimize guards. A guard ends in the
+%%% following instructions:
+%%%
+%%% Bool = bif:'=:=' Var, literal true
+%%% br BoolVar, label Success, label Failure
+%%%
+%%% To make sure that we'll find the end of the guard instead of some
+%%% interior '=:=' instruction we will visit the blocks in postorder.
+%%%
+
+bool_opt(Blocks, St) ->
+ bool_opt(beam_ssa:rpo(Blocks), Blocks, St).
+
+bool_opt([L|Ls], Blocks0, St0) ->
+ {Blocks,St1} = bool_opt(Ls, Blocks0, St0),
+ case Blocks of
+ #{L:=#b_blk{is=[_|_]=Is,last=#b_br{bool=#b_var{}=Bool}=Br}} ->
+ case last(Is) of
+ #b_set{op={bif,'=:='},dst=Bool,
+ args=[#b_var{},#b_literal{val=true}]} ->
+ try
+ bool_opt_rewrite(Bool, L, Br, Blocks, St1)
+ catch
+ throw:not_possible ->
+ {Blocks,St1}
+ end;
+ #b_set{} ->
+ {Blocks,St1}
+ end;
+ #{} ->
+ %% Either this block was removed by a previous successful
+ %% optimization, it is empty, or its terminator is not a
+ %% two-way `br` instruction.
+ {Blocks,St1}
+ end;
+bool_opt([], Blocks, St) ->
+ {Blocks,St}.
+
+bool_opt_rewrite(Bool, From, Br, Blocks0, St0) ->
+ TreeVars = collect_bool_vars(Bool, St0),
+ case TreeVars of
+ [Bool] ->
+ %% Only one variable means that there is nothing to
+ %% optimize. (The variable is either a function argument,
+ %% or has been defined by an instruction such as `call` or
+ %% `get_tuple_element`.)
+ not_possible();
+ [_|_] ->
+ ok
+ end,
+
+ %% Find the common dominator block for all the blocks with boolean
+ %% variables.
+ Dom = bool_opt_dom(TreeVars, St0),
+
+ %% Split out non-boolean instruction from the block that dominates
+ %% all the boolean operators. Splitting will save some work, and
+ %% it could also make more optimizations possible since phi nodes
+ %% could be difficult to handle later when they have been included
+ %% in the graph.
+ {DomPreIs,Blocks1} = split_dom_block(Dom, Blocks0),
+
+ %% Collect all blocks from the Dom block up to and including
+ %% the From block.
+ Bs = collect_digraph_blocks(Dom, From, Br, Blocks1),
+
+ %% Build a digraph from the collected blocks.
+ {Root,G0,St1} = build_digraph(Bs, Br, St0),
+
+ %% Optimize the digraph.
+ LDefs = digraph_bool_def(G0),
+ St = St1#st{ldefs=LDefs},
+ G1 = opt_digraph_top(Bool, G0, St),
+ G = shortcut_branches(Root, G1, St),
+
+ %% Make sure that every variable that is used will be defined
+ %% on every path to its use.
+ ensure_init(Root, G, G0),
+
+ %% Delete the original blocks. This is important so that we will not
+ %% try optimize the already optimized code. That would not work
+ %% because the map of definitions in St#st.defs would not be updated
+ %% to include the newly optimized blocks.
+ DomBlk0 = map_get(Dom, Blocks1),
+ Blocks2 = maps:without([L || {L,#b_blk{}} <- Bs], Blocks1),
+
+ %% Convert the optimized digraph back to SSA code.
+ Blocks3 = digraph_to_ssa([Root], G, Blocks2),
+
+ %% Add a branch from the pre-sequence in the dominating block to
+ %% the first block of the optimized code.
+ DomBlk = DomBlk0#b_blk{is=DomPreIs,last=oneway_br(Root)},
+ Blocks = Blocks3#{Dom => DomBlk},
+ {Blocks,St#st{ldefs=#{}}}.
+
+%%%
+%%% Collect boolean variables recursively reachable from the root
+%%% boolean variable.
+%%%
+
+collect_bool_vars(RootBool, St) ->
+ #b_set{args=[#b_var{}=Var,#b_literal{}]} = get_def(RootBool, St),
+ collect_bool_vars([Var], St, [RootBool]).
+
+collect_bool_vars([V|Vs], St, Acc) ->
+ case get_def(V, St) of
+ #b_set{op=phi,anno=Anno,args=Args} ->
+ {Vars,Ls} = collect_phi_args(Args, Anno),
+ collect_bool_vars(Vars ++ Vs, St, Ls ++ Vars ++ Acc);
+ #b_set{args=Args}=I ->
+ %% This is a boolean expression.
+ Vars = [Arg || #b_var{}=Arg <- Args],
+ case is_rewritable_bool_op(I) of
+ true ->
+ %% This is a bool op ('and', 'or', or
+ %% 'not'). Recursively collect more boolean
+ %% variables from its arguments.
+ collect_bool_vars(Vars ++ Vs, St, [V|Acc]);
+ false ->
+ %% This is a comparison operator (such as `<`) or
+ %% type test. Don't visit its arguments
+ %% recursively.
+ collect_bool_vars(Vs, St, [V|Acc])
+ end;
+ none ->
+ collect_bool_vars(Vs, St, Acc)
+ end;
+collect_bool_vars([], _St, Acc) ->
+ ordsets:from_list(Acc).
+
+is_rewritable_bool_op(#b_set{op={bif,Bif}}) ->
+ %% `xor` is a bool op, but it is not practical to rewrite it.
+ case Bif of
+ 'and' -> true;
+ 'or' -> true;
+ 'not' -> true;
+ _ -> false
+ end.
+
+collect_phi_args(Args, Anno) ->
+ case is_map_key(boolean_phi, Anno) of
+ true ->
+ Vars = [V || {#b_var{}=V,_} <- Args],
+ case Vars of
+ [_|_] ->
+ {Vars,[]};
+ [] ->
+ %% This phi node only contains literal values.
+ %% Force the inclusion of referenced blocks.
+ Ls = [{block,L} || {_,L} <- Args],
+ {[],Ls}
+ end;
+ false ->
+ %% We can't rewrite phi nodes that don't return
+ %% a boolean value.
+ {[],[]}
+ end.
+
+%%%
+%%% Dominator utility functions.
+%%%
+
+bool_opt_dom(TreeVars, #st{defs=Defs,dom={DomBy,Num}}) ->
+ Ls0 = foldl(fun({block,L}, A) ->
+ [L|A];
+ (V, A) ->
+ {L,_} = map_get(V, Defs),
+ [L|A]
+ end, [], TreeVars),
+ Ls = ordsets:from_list(Ls0),
+ [Common|_] = beam_ssa:common_dominators(Ls, DomBy, Num),
+ Common.
+
+split_dom_block(L, Blocks0) ->
+ #b_blk{is=Is} = Blk0 = map_get(L, Blocks0),
+ {PreIs,TailIs} = split_dom_block_is(Is, []),
+ Blk = Blk0#b_blk{is=TailIs},
+ Blocks = Blocks0#{L:=Blk},
+ {PreIs,Blocks}.
+
+split_dom_block_is([#b_set{},#b_set{op={succeeded,_}}]=Is, PreAcc) ->
+ {reverse(PreAcc),Is};
+split_dom_block_is([#b_set{}=I|Is]=Is0, PreAcc) ->
+ case is_bool_expr(I) of
+ true ->
+ {reverse(PreAcc),Is0};
+ false ->
+ split_dom_block_is(Is, [I|PreAcc])
+ end;
+split_dom_block_is([], PreAcc) ->
+ {reverse(PreAcc),[]}.
+
+%%%
+%%% Find and collect the blocks that should be converted to a digraph.
+%%%
+
+collect_digraph_blocks(FirstL, LastL, #b_br{succ=Succ,fail=Fail}, Blocks) ->
+ Ws = gb_sets:singleton(FirstL),
+ Seen = cerl_sets:from_list([Succ,Fail]),
+ collect_digraph_blocks(Ws, LastL, Blocks, Seen, []).
+
+collect_digraph_blocks(Ws0, LastL, Blocks, Seen0, Acc0) ->
+ case gb_sets:is_empty(Ws0) of
+ true ->
+ Acc0;
+ false ->
+ {L,Ws1} = gb_sets:take_smallest(Ws0),
+ Seen = cerl_sets:add_element(L, Seen0),
+ Blk = map_get(L, Blocks),
+ Acc = [{L,Blk}|Acc0],
+ Ws = cdb_update_workset(L, Blk, LastL, Seen, Ws1),
+ collect_digraph_blocks(Ws, LastL, Blocks, Seen, Acc)
+ end.
+
+cdb_update_workset(LastL, _Blk, LastL, _Seen, Ws) ->
+ Ws;
+cdb_update_workset(_L, Blk, _LastL, Seen, Ws) ->
+ Successors = beam_ssa:successors(Blk),
+ cdb_update_workset(Successors, Seen, Ws).
+
+cdb_update_workset([L|Ls], Seen, Ws) ->
+ case cerl_sets:is_element(L, Seen) of
+ true ->
+ cdb_update_workset(Ls, Seen, Ws);
+ false ->
+ cdb_update_workset(Ls, Seen, gb_sets:add_element(L, Ws))
+ end;
+cdb_update_workset([], _Seen, Ws) -> Ws.
+
+%%%
+%%% For the blocks from the dominating block up to the last block,
+%%% build a digraph where each vertex is an instruction. This is just
+%%% a more convenient way to represent the code, more suitable for
+%%% the optimizations we are about to do.
+%%%
+
+build_digraph(Bs, #b_br{succ=Succ,fail=Fail}, St0) ->
+ Ignore = ordsets:from_list([Succ,Fail]),
+ G0 = beam_digraph:new(),
+ {Map0,G1,St1} = build_mapping(Bs, #{}, G0, St0),
+ {Map,G2} = add_external_vertices(Ignore, Map0, G1),
+ {G,St} = build_digraph_1(Bs, G2, Map, St1),
+
+ %% Find the root node now. After we have done optimizations,
+ %% there may be more than one root node (that is, nodes without
+ %% any incident vertices).
+ [Root] = digraph_roots(G),
+ {Root,G,St}.
+
+build_mapping([{L,Blk}|Bs], Map0, G0, St0) ->
+ {Vtx,St} = new_label(St0),
+ Map = Map0#{L=>Vtx},
+ Label = case Blk of
+ #b_blk{is=[]} -> br;
+ #b_blk{} -> initial
+ end,
+ G = beam_digraph:add_vertex(G0, Vtx, Label),
+ build_mapping(Bs, Map, G, St);
+build_mapping([], Map, G, St) ->
+ {Map,G,St}.
+
+add_external_vertices([V|Vs], Map0, G0) ->
+ G = beam_digraph:add_vertex(G0, V, {external,#{}}),
+ Map = Map0#{V=>V},
+ add_external_vertices(Vs, Map, G);
+add_external_vertices([], Map, G) ->
+ {Map,G}.
+
+build_digraph_1([{L,Blk}|Bs], G0, Map, St0) ->
+ #b_blk{is=Is,last=Last} = Blk,
+ Vtx = map_get(L, Map),
+ {G,St} = build_digraph_is(Is, Last, Vtx, Map, G0, St0),
+ build_digraph_1(Bs, G, Map, St);
+build_digraph_1([], G, _Map, St) ->
+ {G,St}.
+
+build_digraph_is([#b_set{op=phi,args=Args0}=I0|Is], Last, Vtx, Map, G, St) ->
+ case Is of
+ [#b_set{op=phi}|_] -> not_possible();
+ _ -> ok
+ end,
+ Args = [{V,case Map of
+ #{L:=Other} -> Other;
+ #{} -> not_possible()
+ end} || {V,L} <- Args0],
+ I = I0#b_set{args=Args},
+ build_digraph_is_1(I, Is, Last, Vtx, Map, G, St);
+build_digraph_is([#b_set{}=I|Is], Last, Vtx, Map, G, St) ->
+ case beam_ssa:no_side_effect(I) of
+ true ->
+ build_digraph_is_1(I, Is, Last, Vtx, Map, G, St);
+ false ->
+ not_possible()
+ end;
+build_digraph_is([], Last, From, Map, G0, St) ->
+ case Last of
+ #b_br{bool=#b_literal{val=true},succ=To0,fail=To0} ->
+ To = map_get(To0, Map),
+ G = beam_digraph:add_edge(G0, From, To, next),
+ {G,St};
+ #b_br{bool=#b_var{}=Bool,succ=Succ0,fail=Fail0} ->
+ #{Succ0:=Succ,Fail0:=Fail} = Map,
+ case beam_digraph:vertex(G0, From) of
+ #b_set{dst=Bool} ->
+ G = add_succ_fail_edges(From, Succ, Fail, G0),
+ {G,St};
+ #b_set{} ->
+ %% Wrong variable being tested. This is rare.
+ not_possible();
+ br ->
+ G1 = add_succ_fail_edges(From, Succ, Fail, G0),
+ G = beam_digraph:add_vertex(G1, From, {br,Bool}),
+ {G,St}
+ end;
+ _ ->
+ not_possible()
+ end.
+
+build_digraph_is_1(I, Is, Last, Vtx, Map, G0, St0) ->
+ G1 = beam_digraph:add_vertex(G0, Vtx, I),
+ case Is of
+ [] ->
+ build_digraph_is(Is, Last, Vtx, Map, G1, St0);
+ [_|_] ->
+ {NextVtx,St} = new_label(St0),
+ G2 = beam_digraph:add_vertex(G1, NextVtx, initial),
+ G = beam_digraph:add_edge(G2, Vtx, NextVtx, next),
+ build_digraph_is(Is, Last, NextVtx, Map, G, St)
+ end.
+
+%%%
+%%% Optimize the graph, attempting to eliminating 'and', 'or', and 'not'
+%%% instructions.
+%%%
+
+opt_digraph_top(Arg, G0, St) ->
+ I = get_def(Arg, G0, St),
+ #b_set{op={bif,'=:='},dst=Dst,
+ args=[#b_var{}=Bool,#b_literal{val=true}]} = I,
+ {br,Succ,Fail} = get_targets(Dst, G0, St),
+ G1 = ensure_single_use(Dst, G0, St),
+ G = convert_to_br_node(I, Succ, G1, St),
+ redirect_test(Bool, {fail,Fail}, G, St).
+
+do_opt_digraph([A|As], G0, St) ->
+ I = get_def(A, G0, St),
+ try opt_digraph_instr(I, G0, St) of
+ G ->
+ do_opt_digraph(As, G, St)
+ catch
+ throw:not_possible ->
+ do_opt_digraph(As, G0, St)
+ end;
+do_opt_digraph([], G, _St) -> G.
+
+opt_digraph_instr(#b_set{dst=Dst}=I, G0, St) ->
+ %% We KNOW that this node has two outgoing edges (one labeled
+ %% `succ` and one `fail`).
+ {br,Succ,Fail} = get_targets(Dst, G0, St),
+ G1 = ensure_single_use(Dst, G0, St),
+ case I of
+ #b_set{op={bif,'and'},args=Args} ->
+ G2 = convert_to_br_node(I, Succ, G1, St),
+ {First,Second} = order_args(Args, G2, St),
+ G = redirect_test(First, {fail,Fail}, G2, St),
+ redirect_test(Second, {fail,Fail}, G, St);
+ #b_set{op={bif,'or'},args=Args} ->
+ {First,Second} = order_args(Args, G1, St),
+
+ %% Here we give up the optimization if the optimization
+ %% would skip instructions that may fail. A possible
+ %% future improvement would be to hoist the failing
+ %% instructions so that they would always be executed.
+ ensure_no_failing_instructions(First, Second, G1, St),
+
+ G2 = convert_to_br_node(I, Succ, G1, St),
+ G = redirect_test(First, {succ,Succ}, G2, St),
+ redirect_test(Second, {fail,Fail}, G, St);
+ #b_set{op={bif,'xor'}} ->
+ %% Rewriting 'xor' is not practical. Fortunately,
+ %% 'xor' is almost never used in practice.
+ not_possible();
+ #b_set{op={bif,'not'}} ->
+ %% This is suprisingly rare. The previous attempt to
+ %% optimize it was broken, which wasn't noticed because
+ %% very few test cases triggered this code.
+ not_possible();
+ #b_set{op=phi,dst=Bool} ->
+ Vtx = get_vertex(Bool, St),
+ G2 = del_out_edges(Vtx, G1),
+ G = beam_digraph:add_edge(G2, Vtx, Succ, next),
+ redirect_test(Bool, {fail,Fail}, G, St);
+ #b_set{} ->
+ G1
+ end.
+
+ensure_single_use(Bool, G, #st{uses=U}=St) ->
+ case map_get(Bool, U) of
+ [_] ->
+ G;
+ Uses ->
+ Vtx = get_vertex(Bool, St),
+ ensure_single_use_1(Bool, Vtx, Uses, G)
+ end.
+
+ensure_single_use_1(Bool, Vtx, Uses, G) ->
+ Fail = case get_targets(Vtx, G) of
+ {br,_,Fail0} -> Fail0;
+ _ -> not_possible()
+ end,
+ case partition(fun({L,#b_set{}}) when L =:= Fail -> true;
+ (_) -> false
+ end, Uses) of
+ {[_],[_]} ->
+ case {beam_digraph:vertex(G, Fail),
+ beam_digraph:in_edges(G, Fail)} of
+ {{external,Bs0}, [_]} ->
+ %% The only other use of the variable Bool
+ %% is in the failure block and it can only
+ %% be reached through this test, so we can
+ %% replace it with the literal `false`
+ %% in that block.
+ Bs = Bs0#{Bool => #b_literal{val=false}},
+ beam_digraph:add_vertex(G, Fail, {external,Bs});
+ _ ->
+ not_possible()
+ end;
+ {_,_} ->
+ not_possible()
+ end.
+
+convert_to_br_node(I, Target, G0, St) ->
+ Vtx = get_vertex(I, St),
+ G1 = del_out_edges(Vtx, G0),
+ G = beam_digraph:add_vertex(G1, Vtx, br),
+ beam_digraph:add_edge(G, Vtx, Target, next).
+
+
+%% ensure_no_failing_instructions(First, Second, G, St) -> ok.
+%% Ensure that there are no instructions that can fail that would not
+%% be executed if right-hand side of the `or` would be skipped. That
+%% means that the `or` could succeed when it was supposed to
+%% fail. Example:
+%%
+%% (element(1, T) =:= tag) or
+%% (element(10, T) =:= y)
+
+ensure_no_failing_instructions(First, Second, G, St) ->
+ Vs0 = covered(get_vertex(First, St), get_vertex(Second, St), G),
+ Vs = [{V,beam_digraph:vertex(G, V)} || V <- Vs0],
+ Failing = [P || {V,#b_set{op={succeeded,_}}}=P <- Vs,
+ not eaten_by_phi(V, G)],
+ case Failing of
+ [] -> ok;
+ [_|_] -> not_possible()
+ end.
+
+eaten_by_phi(V, G) ->
+ {br,_,Fail} = get_targets(V, G),
+ case beam_digraph:vertex(G, Fail) of
+ br ->
+ [To] = beam_digraph:out_neighbours(G, Fail),
+ case beam_digraph:vertex(G, To) of
+ #b_set{op=phi} ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end.
+
+%% order_args([Arg1,Arg2], G, St) -> {First,Second}.
+%% Order arguments for a boolean operator so that there is path in the
+%% digraph from the instruction referered to by the first operand to
+%% the instruction refered to by the second operand.
+
+order_args([#b_var{}=VarA,#b_var{}=VarB], G, St) ->
+ {VA,VB} = {get_vertex(VarA, St),get_vertex(VarB, St)},
+ case beam_digraph:is_path(G, VA, VB) of
+ true ->
+ %% Core Erlang code generated by v3_core always
+ %% has operands already in correct order.
+ {VarA,VarB};
+ false ->
+ %% Core Erlang code generated by other frontends
+ %% such as LFE may have the operands swapped.
+ true = beam_digraph:is_path(G, VB, VA), %Assertion.
+ {VarB,VarB}
+ end;
+order_args(_Args, _G, _St) ->
+ %% Literal operands. Can only happen if the Core Erlang optimization
+ %% passes have been turned off.
+ not_possible().
+
+redirect_test(Bool, SuccFail, G0, St) ->
+ V = get_vertex(Bool, St),
+ I = get_def(Bool, G0, St),
+ case I of
+ #b_set{op=phi,args=Args} ->
+ G = ensure_single_use(Bool, G0, St),
+ redirect_phi(Bool, Args, SuccFail, G, St);
+ #b_set{} ->
+ G1 = redirect_test_1(V, SuccFail, G0),
+ G = ensure_single_use(Bool, G1, St),
+ do_opt_digraph([Bool], G, St)
+ end.
+
+redirect_test_1(V, SuccFail, G) ->
+ case get_targets(V, G) of
+ {br,_Succ,Fail} ->
+ %% I have only seen this happen in code generated by LFE
+ %% (in lfe_andor_SUITE.core and lfe_guard_SUITE.core)
+ case SuccFail of
+ {fail,Fail} -> G;
+ {succ,_} -> not_possible()
+ end;
+ {br,Next} ->
+ case SuccFail of
+ {succ,Succ} ->
+ add_succ_fail_edges(V, Succ, Next, G);
+ {fail,Fail} ->
+ add_succ_fail_edges(V, Next, Fail, G)
+ end
+ end.
+
+redirect_phi(Phi, Args, SuccFail, G0, St) ->
+ PhiVtx = get_vertex(Phi, St),
+ G = beam_digraph:add_vertex(G0, PhiVtx, br),
+ redirect_phi_1(PhiVtx, sort(Args), SuccFail, G, St).
+
+redirect_phi_1(PhiVtx, [{#b_literal{val=false},FalseExit},
+ {#b_var{}=SuccBool,_BoolExit}],
+ SuccFail, G0, St) ->
+ %% This was most likely an `andalso` in the source code.
+ BoolVtx = get_vertex(SuccBool, St),
+
+ %% We must be careful when rewriting guards that reference boolean
+ %% expressions defined before the guard. Here is an example:
+ %%
+ %% Bool = Z =:= false,
+ %% if
+ %% X =:= Y andalso Bool -> ok;
+ %% true -> error
+ %% end.
+ %%
+ %% Slightly simplified, the SSA code will look like this:
+ %%
+ %% 10: Bool = bif:'=:=' _2, `false`
+ %% br ^11
+ %%
+ %% 11: B = bif:'=:=' X, Y
+ %% br B, ^20, ^30
+ %%
+ %% 20: br ^40
+ %% 30: br ^40
+ %%
+ %% 40: Phi = phi { `true`, ^20 }, { Bool, ^30 }
+ %% br Phi, ^100, ^200
+ %%
+ %% 100: ret `ok`
+ %% 200: ret `error'
+ %%
+ %% The usual rewriting of the phi node will result in the following
+ %% SSA code:
+ %%
+ %% 10: Bool = bif:'=:=' _2, `false`
+ %% br Bool, ^100, ^200
+ %%
+ %% 11: B = bif:'=:=' X, Y
+ %% br B, ^100, ^200
+ %%
+ %% 20: br ^40
+ %% 30: br ^40
+ %%
+ %% 40: Phi = phi { `true`, ^20 }, { Bool, ^30 }
+ %% br Phi, ^100, ^200
+ %%
+ %% 100: ret `ok`
+ %% 200: ret `error'
+ %%
+ %% Block 11 is no longer reachable; thus, the X =:= Y test has been dropped.
+ %% To avoid dropping tests, we should check whether if there is a path from
+ %% 10 to block 20. If there is, the optimization in its current form is not
+ %% safe.
+ %%
+ ensure_disjoint_paths(G0, BoolVtx, FalseExit),
+
+ [FalseOut] = beam_digraph:out_edges(G0, FalseExit),
+ G1 = beam_digraph:del_edge(G0, FalseOut),
+ case SuccFail of
+ {fail,Fail} ->
+ G2 = beam_digraph:add_edge(G1, FalseExit, Fail, next),
+ G = add_succ_fail_edges(BoolVtx, PhiVtx, FalseExit, G2),
+ do_opt_digraph([SuccBool], G, St);
+ {succ,Succ} ->
+ G2 = beam_digraph:add_edge(G1, FalseExit, PhiVtx, next),
+ G = add_succ_fail_edges(BoolVtx, Succ, PhiVtx, G2),
+ do_opt_digraph([SuccBool], G, St)
+ end;
+redirect_phi_1(PhiVtx, [{#b_literal{val=true},TrueExit},
+ {#b_var{}=SuccBool,_BoolExit}],
+ {fail,Fail}, G0, St) ->
+ %% This was probably an `orelse` in the source code.
+ BoolVtx = get_vertex(SuccBool, St),
+
+ %% See the previous clause for an explanation of why we
+ %% must ensure that paths are disjoint.
+ ensure_disjoint_paths(G0, BoolVtx, TrueExit),
+
+ [TrueOut] = beam_digraph:out_edges(G0, TrueExit),
+ G1 = beam_digraph:del_edge(G0, TrueOut),
+ G2 = beam_digraph:add_edge(G1, TrueExit, PhiVtx, next),
+ G = add_succ_fail_edges(BoolVtx, PhiVtx, Fail, G2),
+ %% As as future improvement, we could follow TrueExit
+ %% back to its originating boolean expression and
+ %% optimize that too.
+ do_opt_digraph([SuccBool], G, St);
+redirect_phi_1(_PhiVtx, [{#b_literal{val=false},FalseExit},
+ {#b_literal{val=true},TrueExit}],
+ SuccFail, G0, _St) ->
+ case SuccFail of
+ {fail,Fail} ->
+ [FalseOut] = beam_digraph:out_edges(G0, FalseExit),
+ G = beam_digraph:del_edge(G0, FalseOut),
+ beam_digraph:add_edge(G, FalseExit, Fail, next);
+ {succ,Succ} ->
+ [TrueOut] = beam_digraph:out_edges(G0, TrueExit),
+ G = beam_digraph:del_edge(G0, TrueOut),
+ beam_digraph:add_edge(G, TrueExit, Succ, next)
+ end;
+redirect_phi_1(_PhiVtx, _Args, _SuccFail, _G, _St) ->
+ not_possible().
+
+digraph_bool_def(G) ->
+ Vs = beam_digraph:vertices(G),
+ Ds = [{Dst,Vtx} || {Vtx,#b_set{dst=Dst}} <- Vs],
+ maps:from_list(Ds).
+
+
+%% ensure_disjoint_paths(G, Vertex1, Vertex2) -> ok.
+%% Ensure that there is no path from Vertex1 to Vertex2 in
+%% either direction. (It is probably overkill to test both
+%% directions, but better safe than sorry.)
+
+ensure_disjoint_paths(G, V1, V2) ->
+ case beam_digraph:is_path(G, V1, V2) orelse beam_digraph:is_path(G, V2, V1) of
+ true -> not_possible();
+ false -> ok
+ end.
+
+%%%
+%%% Shortcut branches that branch to other branches.
+%%%
+%%% Shortcutting may eliminate problems with variables that
+%%% are not defined on all paths to their use. For example,
+%%% code such as the following can be made safe again:
+%%%
+%%% ensure_written(Head, false) when not Head#head.ram_file -> ...
+%%%
+%%% Shortcutting also simplifies the conversion from the digraph
+%%% back to the standard SSA format.
+%%%
+
+shortcut_branches(Vtx, G, St) ->
+ Vs = reverse(beam_digraph:reverse_postorder(G, [Vtx])),
+ do_shortcut_branches(Vs, G, St).
+
+do_shortcut_branches([V|Vs], G0, St) ->
+ case get_targets(V, G0) of
+ {br,Succ0,Fail0} ->
+ {SuccBs,FailBs} = eval_bs(V, G0, St),
+ Succ = eval_instr(Succ0, G0, SuccBs),
+ G1 = redirect_edge(V, Succ0, {succ,Succ}, G0),
+ Fail = eval_instr(Fail0, G1, FailBs),
+ G = redirect_edge(V, Fail0, {fail,Fail}, G1),
+ do_shortcut_branches(Vs, G, St);
+ {br,Next0} ->
+ Next = eval_instr(Next0, G0, #{}),
+ G = redirect_edge(V, Next0, {next,Next}, G0),
+ do_shortcut_branches(Vs, G, St);
+ none ->
+ %% This is an external vertex.
+ do_shortcut_branches(Vs, G0, St)
+ end;
+do_shortcut_branches([], G, _St) -> G.
+
+redirect_edge(_From, To, {_Label,To}, G) ->
+ G;
+redirect_edge(From, To0, {Label,To}, G0) ->
+ G = beam_digraph:del_edge(G0, {From,To0,Label}),
+ beam_digraph:add_edge(G, From, To, Label).
+
+eval_bs(Vtx, G, St) ->
+ case beam_digraph:vertex(G, Vtx) of
+ #b_set{op={bif,'=:='},args=[#b_var{}=Bool,#b_literal{val=true}]} ->
+ case get_def(Bool, G, St) of
+ #b_set{op=phi}=Phi ->
+ phi_bs(Phi);
+ _ ->
+ {#{},#{}}
+ end;
+ _ ->
+ {#{},#{}}
+ end.
+
+phi_bs(#b_set{op=phi,dst=PhiDst,args=PhiArgs}) ->
+ Literals0 = [Lit || {#b_literal{}=Lit,_} <- PhiArgs],
+ case length(Literals0) =:= length(PhiArgs) of
+ true ->
+ %% The values in the phi node are all literals.
+ Literals = ordsets:from_list(Literals0),
+ case partition(fun(#b_literal{val=Val}) ->
+ Val =:= true
+ end, Literals) of
+ {[True],[FailVal]} ->
+ %% As there is only two possible values, we can
+ %% predict the value of the phi node on both
+ %% branches.
+ SuccBs = #{PhiDst => True},
+ FailBs = #{PhiDst => FailVal},
+ {SuccBs,FailBs};
+ {_,_} ->
+ {#{},#{}}
+ end;
+ false ->
+ {#{},#{}}
+ end.
+
+eval_instr(Vtx, G, Bs) ->
+ case beam_digraph:vertex(G, Vtx) of
+ #b_set{} when map_size(Bs) =:= 0 ->
+ %% With no bindings, eval_safe_bool_expr() is
+ %% unlikely to do anything useful. If we would
+ %% call it anyway, the time complexity would be
+ %% quadratic, which would be slow for large
+ %% graphs.
+ Vtx;
+ #b_set{}=I ->
+ case is_safe_bool_expr(I) of
+ true -> eval_safe_bool_expr(I, Vtx, G, Bs);
+ false -> Vtx
+ end;
+ br ->
+ %% We can shortcut this branch unless its
+ %% target is a phi node.
+ [Next] = beam_digraph:out_neighbours(G, Vtx),
+ case beam_digraph:vertex(G, Next) of
+ #b_set{op=phi} -> Vtx;
+ _ -> eval_instr(Next, G, Bs)
+ end;
+ {br,#b_var{}} ->
+ Vtx;
+ {external,_} ->
+ Vtx
+ end.
+
+eval_safe_bool_expr(#b_set{op={bif,Bif},dst=Dst,args=Args0}, Vtx, G, Bs) ->
+ case get_targets(Vtx, G) of
+ {br,Succ,Fail} ->
+ True = #b_literal{val=true},
+ False = #b_literal{val=false},
+ Args = sub_args(Args0, Bs),
+ case eval_bif(Bif, Args) of
+ none ->
+ case {eval_instr(Succ, G, Bs#{Dst=>True}),
+ eval_instr(Fail, G, Bs#{Dst=>False})} of
+ {Same,Same} -> Same;
+ {_,_} -> Vtx
+ end;
+ true ->
+ eval_instr(Succ, G, Bs#{Dst=>True});
+ false ->
+ eval_instr(Fail, G, Bs#{Dst=>False})
+ end;
+ {br,_} ->
+ Vtx
+ end.
+
+eval_bif(Bif, Args0) ->
+ case eval_literal_args(Args0, []) of
+ none ->
+ none;
+ Args ->
+ %% We have already made sure that this expression can't
+ %% fail; thus there is no need for a `try`.
+ apply(erlang, Bif, Args)
+ end.
+
+eval_literal_args([#b_literal{val=Val}|As], Acc) ->
+ eval_literal_args(As, [Val|Acc]);
+eval_literal_args([_|_], _) ->
+ none;
+eval_literal_args([], Acc) ->
+ reverse(Acc).
+
+%%%
+%%% Check that variables are initialized on all paths and abort
+%%% the optimization if not.
+%%%
+%%% Expressions that use `or` and `not` may have added
+%%% `bif:is_boolean` instructions at the end of the boolean
+%%% expression. It can happen that the variables tested by
+%%% `bif:is_boolean` are not initialized on all paths.
+%%%
+
+ensure_init(Root, G, G0) ->
+ Vs = beam_digraph:vertices(G),
+
+ %% Build an ordset of a all variables used by the code
+ %% before the optimization.
+ Used = ensure_init_used(G0),
+
+ %% Build a map of all variables that are set by instructions in
+ %% the digraph. Variables not included in this map have been
+ %% defined by code before the code in the digraph.
+ Vars = maps:from_list([{Dst,unset} ||
+ {_,#b_set{dst=Dst}} <- Vs]),
+ RPO = beam_digraph:reverse_postorder(G, [Root]),
+ ensure_init_1(RPO, Used, G, #{Root=>Vars}).
+
+ensure_init_1([V|Vs], Used, G, InitMaps0) ->
+ InitMaps = ensure_init_instr(V, Used, G, InitMaps0),
+ ensure_init_1(Vs, Used, G, InitMaps);
+ensure_init_1([], _, _, _) -> ok.
+
+ensure_init_instr(Vtx, Used, G, InitMaps0) ->
+ VarMap0 = map_get(Vtx, InitMaps0),
+ case beam_digraph:vertex(G, Vtx) of
+ #b_set{dst=Dst}=I ->
+ do_ensure_init_instr(I, VarMap0, InitMaps0),
+ OutVs = beam_digraph:out_neighbours(G, Vtx),
+ VarMap = VarMap0#{Dst=>set},
+ InitMaps = InitMaps0#{Vtx:=VarMap},
+ ensure_init_successors(OutVs, G, VarMap, InitMaps);
+ {external,_} ->
+ %% We have reached the success or failure node.
+ %% If the code we have been optimizing does not
+ %% originate from a guard, it is possible that a
+ %% variable set in the optimized code will be used
+ %% here.
+ case [V || {V,unset} <- maps:to_list(VarMap0)] of
+ [] ->
+ InitMaps0;
+ [_|_]=Unset0 ->
+ %% There are some variables that are not always
+ %% set when this node is reached. We must make
+ %% sure that they are not used at this node or
+ %% one of its successors.
+ Unset = ordsets:from_list(Unset0),
+ case ordsets:is_subset(Unset, Used) of
+ true ->
+ %% Note that all of the potentially unset
+ %% variables are only used once (otherwise
+ %% the optimization would have been
+ %% aborted earlier). Therefore, since all
+ %% variables are used in the optimized code,
+ %% they cannot be used in this node or in one
+ %% of its successors.
+ InitMaps0;
+ false ->
+ %% The original code probably did not
+ %% originate from a guard. One of the
+ %% potentially unset variables are not
+ %% used in the optimized code. That means
+ %% that it must be used at this node or in
+ %% one of its successors. (Or that it was
+ %% not used at all in the original code,
+ %% but that basically only happens in test
+ %% cases.)
+ not_possible()
+ end
+ end;
+ _ ->
+ OutVs = beam_digraph:out_neighbours(G, Vtx),
+ ensure_init_successors(OutVs, G, VarMap0, InitMaps0)
+ end.
+
+ensure_init_used(G) ->
+ Vs = beam_digraph:vertices(G),
+ ensure_init_used_1(Vs, G, []).
+
+ensure_init_used_1([{Vtx,#b_set{dst=Dst}=I}|Vs], G, Acc0) ->
+ Acc1 = [beam_ssa:used(I)|Acc0],
+ case beam_digraph:out_degree(G, Vtx) of
+ 2 ->
+ Acc = [[Dst]|Acc1],
+ ensure_init_used_1(Vs, G, Acc);
+ _ ->
+ ensure_init_used_1(Vs, G, Acc1)
+ end;
+ensure_init_used_1([{_Vtx,{br,Bool}}|Vs], G, Acc) ->
+ ensure_init_used_1(Vs, G, [[Bool]|Acc]);
+ensure_init_used_1([_|Vs], G, Acc) ->
+ ensure_init_used_1(Vs, G, Acc);
+ensure_init_used_1([], _G, Acc) ->
+ ordsets:union(Acc).
+
+do_ensure_init_instr(#b_set{op=phi,args=Args},
+ _VarMap, InitMaps) ->
+ _ = [ensure_init_used(Var, map_get(From, InitMaps)) ||
+ {#b_var{}=Var,From} <- Args],
+ ok;
+do_ensure_init_instr(#b_set{}=I, VarMap, _InitMaps) ->
+ Used = beam_ssa:used(I),
+ _ = [ensure_init_used(Var, VarMap) || Var <- Used],
+ ok.
+
+ensure_init_used(Var, VarMap) ->
+ case VarMap of
+ #{Var:=unset} -> not_possible();
+ #{Var:=set} -> ok;
+ #{} -> ok
+ end.
+
+ensure_init_successors([To|Vs], G, Vars0, InitMaps0) ->
+ case InitMaps0 of
+ #{To:=Vars1} ->
+ Vars = join_inits(Vars0, Vars1),
+ InitMaps = InitMaps0#{To:=Vars},
+ ensure_init_successors(Vs, G, Vars0, InitMaps);
+ #{} ->
+ InitMaps = InitMaps0#{To=>Vars0},
+ ensure_init_successors(Vs, G, Vars0, InitMaps)
+ end;
+ensure_init_successors([], _, _, InitMaps) ->
+ InitMaps.
+
+join_inits(VarMap0, VarMap1) ->
+ join_inits_1(maps:to_list(VarMap0), VarMap1).
+
+join_inits_1([{V,State0}|Vs], VarMap) ->
+ State1 = map_get(V, VarMap),
+ State = case {State0,State1} of
+ {set,set} -> set;
+ {_,_} -> unset
+ end,
+ case State =:= State1 of
+ true ->
+ join_inits_1(Vs, VarMap);
+ false ->
+ join_inits_1(Vs, VarMap#{V:=State})
+ end;
+join_inits_1([], VarMap) ->
+ VarMap.
+
+%%%
+%%% Transform the digraph back to standard SSA code.
+%%%
+%%% We don't try merge blocks during the conversion because it would
+%%% be difficult to keep phi nodes up to date. We will call
+%%% beam_ssa:merge_blocks/1 before returning from this pass to do all
+%%% block merging.
+%%%
+
+digraph_to_ssa(Ls, G, Blocks0) ->
+ Seen = cerl_sets:new(),
+ {Blocks,_} = digraph_to_ssa(Ls, G, Blocks0, Seen),
+ Blocks.
+
+digraph_to_ssa([L|Ls], G, Blocks0, Seen0) ->
+ Seen1 = cerl_sets:add_element(L, Seen0),
+ {Blk,Successors0} = digraph_to_ssa_blk(L, G, Blocks0, []),
+ Blocks1 = Blocks0#{L=>Blk},
+ Successors = [S || S <- Successors0,
+ not cerl_sets:is_element(S, Seen1)],
+ {Blocks,Seen} = digraph_to_ssa(Successors, G, Blocks1, Seen1),
+ digraph_to_ssa(Ls, G, Blocks, Seen);
+digraph_to_ssa([], _G, Blocks, Seen) ->
+ {Blocks,Seen}.
+
+digraph_to_ssa_blk(From, G, Blocks, Acc0) ->
+ case beam_digraph:vertex(G, From) of
+ #b_set{dst=Dst}=I ->
+ case get_targets(From, G) of
+ {br,Succ,Fail} ->
+ %% This is a two-way branch that ends the current block.
+ Br = beam_ssa:normalize(#b_br{bool=Dst,succ=Succ,fail=Fail}),
+ Is = reverse(Acc0, [I]),
+ Blk = #b_blk{is=Is,last=Br},
+ {Blk,beam_ssa:successors(Blk)};
+ {br,Next} ->
+ %% This is a two-way branch that ends the current block.
+ Br = oneway_br(Next),
+ Is = reverse(Acc0, [I]),
+ Blk = #b_blk{is=Is,last=Br},
+ {Blk,beam_ssa:successors(Blk)}
+ end;
+ br ->
+ %% Create an empty block.
+ {br,Next} = get_targets(From, G),
+ Blk = #b_blk{is=[],last=oneway_br(Next)},
+ {Blk,beam_ssa:successors(Blk)};
+ {br,Bool} ->
+ %% This is a two-way `br` instruction. The most common
+ %% reason for its existence in the graph is that the root
+ %% node only contained a phi instruction (which was taken
+ %% out of the block before building the graph).
+ [] = Acc0, %Assertion.
+ {br,Succ,Fail} = get_targets(From, G),
+ Br = beam_ssa:normalize(#b_br{bool=Bool,succ=Succ,fail=Fail}),
+ Blk = #b_blk{is=[],last=Br},
+ {Blk,beam_ssa:successors(Blk)};
+ {external,Sub} ->
+ #b_blk{is=Is0} = Blk = map_get(From, Blocks),
+ Is = [I#b_set{args=sub_args(Args0, Sub)} ||
+ #b_set{args=Args0}=I <- Is0],
+ {Blk#b_blk{is=Is},[]}
+ end.
+
+%%%
+%%% Helper functions follow.
+%%%
+
+%% get_def(Var, #st{}) -> #b_set{} | none.
+%% Find the definition for a variable. Only boolean
+%% expressions and phi nodes can be found.
+
+get_def(#b_var{}=Bool, #st{defs=Defs}) ->
+ case Defs of
+ #{Bool:={_,Def}} ->
+ Def;
+ #{} ->
+ none
+ end.
+
+%% get_def(Var, Graph, #st{}) -> #b_set{} | none.
+%% Find the definition for a variable, looking first in the digraph
+%% Graph. If it is not found there, look in the global map of
+%% interesting definitions from the entire functions.
+
+get_def(Var, G, #st{ldefs=LDefs,defs=Defs}) ->
+ case LDefs of
+ #{Var:=Vtx} ->
+ beam_digraph:vertex(G, Vtx);
+ #{} ->
+ %% Not in the graph. Returning definitions for phi nodes
+ %% outside the graph is useful for shortcut_branches().
+ case Defs of
+ #{Var:={_,Def}} -> Def;
+ #{} -> none
+ end
+ end.
+
+add_succ_fail_edges(From, Succ, Fail, G0) ->
+ G1 = beam_digraph:add_edge(G0, From, Succ, succ),
+ G = beam_digraph:add_edge(G1, From, Fail, fail),
+ case beam_digraph:out_edges(G0, From) of
+ [{From,_,next}=E] -> beam_digraph:del_edge(G, E);
+ [] -> G
+ end.
+
+get_vertex(#b_set{dst=Dst}, St) ->
+ get_vertex(Dst, St);
+get_vertex(#b_var{}=Var, #st{ldefs=LDefs}) ->
+ map_get(Var, LDefs).
+
+get_targets(Vtx, G) when is_integer(Vtx) ->
+ case beam_digraph:out_edges(G, Vtx) of
+ [{_,To,next}] ->
+ {br,To};
+ [{_,Succ,succ},{_,Fail,fail}] ->
+ {br,Succ,Fail};
+ [{_,Fail,fail},{_,Succ,succ}] ->
+ {br,Succ,Fail};
+ [] ->
+ none
+ end.
+
+get_targets(#b_var{}=Var, G, #st{ldefs=LDefs}) ->
+ get_targets(map_get(Var, LDefs), G).
+
+del_out_edges(V, G) ->
+ beam_digraph:del_edges(G, beam_digraph:out_edges(G, V)).
+
+covered(From, To, G) ->
+ Seen0 = cerl_sets:new(),
+ {yes,Seen} = covered_1(From, To, G, Seen0),
+ cerl_sets:to_list(Seen).
+
+covered_1(To, To, _G, Seen) ->
+ {yes,Seen};
+covered_1(From, To, G, Seen0) ->
+ Vs0 = beam_digraph:out_neighbours(G, From),
+ Vs = [V || V <- Vs0, not cerl_sets:is_element(V, Seen0)],
+ Seen = cerl_sets:union(cerl_sets:from_list(Vs), Seen0),
+ case Vs of
+ [] ->
+ no;
+ [_|_] ->
+ covered_list(Vs, To, G, Seen, false)
+ end.
+
+covered_list([V|Vs], To, G, Seen0, AnyFound) ->
+ case covered_1(V, To, G, Seen0) of
+ {yes,Seen} ->
+ covered_list(Vs, To, G, Seen, true);
+ no ->
+ covered_list(Vs, To, G, Seen0, AnyFound)
+ end;
+covered_list([], _, _, Seen, AnyFound) ->
+ case AnyFound of
+ true -> {yes,Seen};
+ false -> no
+ end.
+
+digraph_roots(G) ->
+ digraph_roots_1(beam_digraph:vertices(G), G).
+
+digraph_roots_1([{V,_}|Vs], G) ->
+ case beam_digraph:in_degree(G, V) of
+ 0 ->
+ [V|digraph_roots_1(Vs, G)];
+ _ ->
+ digraph_roots_1(Vs, G)
+ end;
+digraph_roots_1([], _G) -> [].
+
+not_possible() ->
+ throw(not_possible).
+
+new_label(#st{count=Count}=St) ->
+ {Count,St#st{count=Count+1}}.
+
+sub_args(Args, Sub) ->
+ [sub_arg(Arg, Sub) || Arg <- Args].
+
+sub_arg({#b_var{}=Arg,From}, Sub) when is_integer(From) ->
+ {do_sub_arg(Arg, Sub),From};
+sub_arg(#b_var{}=Arg, Sub) ->
+ do_sub_arg(Arg, Sub);
+sub_arg(#b_remote{mod=Mod,name=Name}=Rem, Sub) ->
+ Rem#b_remote{mod=do_sub_arg(Mod, Sub),
+ name=do_sub_arg(Name, Sub)};
+sub_arg(Arg, _Sub) -> Arg.
+
+do_sub_arg(#b_var{}=Old, Sub) ->
+ case Sub of
+ #{Old:=#b_literal{}=New} -> New;
+ #{Old:=#b_var{}=New} -> New;
+ #{} -> Old
+ end;
+do_sub_arg(#b_literal{}=Old, _Sub) -> Old.
+
+is_bool_expr(#b_set{op={bif,Op},args=Args}) ->
+ Arity = length(Args),
+ erl_internal:comp_op(Op, Arity) orelse
+ erl_internal:new_type_test(Op, Arity) orelse
+ erl_internal:bool_op(Op, Arity);
+is_bool_expr(_) -> false.
+
+%% Test whether the expression always succeeds and
+%% always returns a boolean.
+is_safe_bool_expr(#b_set{op={bif,Op},args=Args}) ->
+ Arity = length(Args),
+ erl_internal:comp_op(Op, Arity) orelse
+ erl_internal:new_type_test(Op, Arity);
+is_safe_bool_expr(#b_set{}) -> false.
+
+oneway_br(To) ->
+ #b_br{bool=#b_literal{val=true},succ=To,fail=To}.
diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl
index 4d48e16221..f33b6bc6f2 100644
--- a/lib/compiler/src/beam_ssa_bsm.erl
+++ b/lib/compiler/src/beam_ssa_bsm.erl
@@ -57,8 +57,9 @@
-export([module/2, format_error/1]).
-include("beam_ssa.hrl").
+-include("beam_types.hrl").
--import(lists, [member/2, reverse/1, reverse/2, splitwith/2, map/2, foldl/3,
+-import(lists, [member/2, reverse/1, reverse/2, splitwith/2, foldl/3,
mapfoldl/3, nth/2, max/1, unzip/1]).
-spec format_error(term()) -> nonempty_string().
@@ -169,12 +170,14 @@ ccc_1([#b_local{}=Call | Args], Ctx, Aliases, ModInfo) ->
Parameters = funcinfo_get(Callee, parameters, ModInfo),
Parameter = nth(1 + arg_index(Ctx, Args), Parameters),
- case maps:find(Parameter, ParamInfo) of
- {ok, suitable_for_reuse} ->
+ case ParamInfo of
+ #{ Parameter := suitable_for_reuse } ->
suitable_for_reuse;
- {ok, Other} ->
- {unsuitable_call, {Call, Other}};
- error ->
+ #{ Parameter := {unsuitable_call, {Call, _}}=Info } ->
+ Info;
+ #{ Parameter := Info } ->
+ {unsuitable_call, {Call, Info}};
+ #{} ->
{no_match_on_entry, Call}
end;
UseCount > 1 ->
@@ -314,7 +317,8 @@ amb_1(Lbl, #b_blk{is=Is0,last=Last0}=Block, State0) ->
{Is, State1} = mapfoldl(fun(I, State) ->
amb_assign_set(I, Lbl, State)
end, State0, Is0),
- {Last, State} = amb_assign_last(Last0, Lbl, State1),
+ {Last1, State} = amb_assign_last(Last0, Lbl, State1),
+ Last = beam_ssa:normalize(Last1),
{Block#b_blk{is=Is,last=Last}, State}.
amb_assign_set(#b_set{op=phi,args=Args0}=I, _Lbl, State0) ->
@@ -439,7 +443,7 @@ is_var_in_args(_Var, []) -> false.
renames = #{} :: beam_ssa:rename_map() }).
combine_matches({Fs0, ModInfo}) ->
- Fs = map(fun(F) -> combine_matches(F, ModInfo) end, Fs0),
+ Fs = [combine_matches(F, ModInfo) || F <- Fs0],
{Fs, ModInfo}.
combine_matches(#b_function{bs=Blocks0,cnt=Counter0}=F, ModInfo) ->
@@ -462,15 +466,16 @@ combine_matches(#b_function{bs=Blocks0,cnt=Counter0}=F, ModInfo) ->
{Blocks, Counter} = alias_matched_binaries(Blocks2, Counter0,
State#cm.match_aliases),
- F#b_function{ bs=Blocks, cnt=Counter };
+ F#b_function{ bs=beam_ssa:trim_unreachable(Blocks),
+ cnt=Counter };
false ->
F
end.
cm_1([#b_set{ op=bs_start_match,
dst=Ctx,
- args=[Src] },
- #b_set{ op=succeeded,
+ args=[_,Src] },
+ #b_set{ op={succeeded,guard},
dst=Bool,
args=[Ctx] }]=MatchSeq, Acc0, Lbl, State0) ->
Acc = reverse(Acc0),
@@ -583,14 +588,15 @@ aca_1(Blocks, State) ->
EntryBlock = maps:get(0, Blocks),
aca_enable_reuse(EntryBlock#b_blk.is, EntryBlock, Blocks, [], State).
-aca_enable_reuse([#b_set{op=bs_start_match,args=[Src]}=I0 | Rest],
+aca_enable_reuse([#b_set{op=bs_start_match,args=[_,Src]}=I0 | Rest],
EntryBlock, Blocks0, Acc, State0) ->
case aca_is_reuse_safe(Src, State0) of
true ->
- {I, Last, Blocks1, State} =
+ {I, Last0, Blocks1, State} =
aca_reuse_context(I0, EntryBlock, Blocks0, State0),
Is = reverse([I | Acc], Rest),
+ Last = beam_ssa:normalize(Last0),
Blocks = maps:put(0, EntryBlock#b_blk{is=Is,last=Last}, Blocks1),
%% Copying (and thus renaming) the successors of a block may cause
@@ -627,7 +633,8 @@ aca_is_reuse_safe(Src, State) ->
%% they're unused so far.
ordsets:is_element(Src, State#aca.unused_parameters).
-aca_reuse_context(#b_set{dst=Dst, args=[Src]}=I0, Block, Blocks0, State0) ->
+aca_reuse_context(#b_set{op=bs_start_match,dst=Dst,args=[_,Src]}=I0,
+ Block, Blocks0, State0) ->
%% When matching fails on a reused context it needs to be converted back
%% to a binary. We only need to do this on the success path since it can't
%% be a context on the type failure path, but it's very common for these
@@ -669,12 +676,14 @@ aca_handle_convergence(Src, State0, Last0, Blocks0) ->
{Succ, Blocks, Counter} =
aca_copy_successors(Succ0, Blocks0, State0#aca.counter),
State = State0#aca{ counter = Counter },
- {State, Last0#b_br{succ=Succ}, Blocks};
+ Last = beam_ssa:normalize(Last0#b_br{succ=Succ}),
+ {State, Last, Blocks};
right ->
{Fail, Blocks, Counter} =
aca_copy_successors(Fail0, Blocks0, State0#aca.counter),
State = State0#aca{ counter = Counter },
- {State, Last0#b_br{fail=Fail}, Blocks}
+ Last = beam_ssa:normalize(Last0#b_br{fail=Fail}),
+ {State, Last, Blocks}
end;
false ->
{State0, Last0, Blocks0}
@@ -696,9 +705,9 @@ aca_copy_successors(Lbl0, Blocks0, Counter0) ->
Lbl = maps:get(Lbl0, BRs),
{Lbl, Blocks, Counter}.
-aca_cs_build_brs([?BADARG_BLOCK=Lbl | Path], Counter, Acc) ->
- %% ?BADARG_BLOCK is a marker and not an actual block, so renaming it will
- %% break exception handling.
+aca_cs_build_brs([?EXCEPTION_BLOCK=Lbl | Path], Counter, Acc) ->
+ %% ?EXCEPTION_BLOCK is a marker and not an actual block, so renaming it
+ %% will break exception handling.
aca_cs_build_brs(Path, Counter, Acc#{ Lbl => Lbl });
aca_cs_build_brs([Lbl | Path], Counter0, Acc) ->
aca_cs_build_brs(Path, Counter0 + 1, Acc#{ Lbl => Counter0 });
@@ -716,7 +725,8 @@ aca_cs_1([], Blocks, Counter, _VRs, _BRs, Acc) ->
aca_cs_block(#b_blk{is=Is0,last=Last0}=Block0, Counter0, VRs0, BRs) ->
{VRs, Is, Counter} = aca_cs_is(Is0, Counter0, VRs0, BRs, []),
- Last = aca_cs_last(Last0, VRs, BRs),
+ Last1 = aca_cs_last(Last0, VRs, BRs),
+ Last = beam_ssa:normalize(Last1),
Block = Block0#b_blk{is=Is,last=Last},
{VRs, Block, Counter}.
@@ -782,9 +792,8 @@ aca_cs_arg(Arg, VRs) ->
%% contexts to us.
allow_context_passthrough({Fs, ModInfo0}) ->
- ModInfo =
- acp_forward_params([{F, beam_ssa:uses(F#b_function.bs)} || F <- Fs],
- ModInfo0),
+ FsUses = [{F, beam_ssa:uses(F#b_function.bs)} || F <- Fs],
+ ModInfo = acp_forward_params(FsUses, ModInfo0),
{Fs, ModInfo}.
acp_forward_params(FsUses, ModInfo0) ->
@@ -833,7 +842,7 @@ acp_1(_Param, _Uses, _ModInfo, ParamInfo) ->
match_aliases = #{} :: match_alias_map() }).
skip_outgoing_tail_extraction({Fs0, ModInfo}) ->
- Fs = map(fun(F) -> skip_outgoing_tail_extraction(F, ModInfo) end, Fs0),
+ Fs = [skip_outgoing_tail_extraction(F, ModInfo) || F <- Fs0],
{Fs, ModInfo}.
skip_outgoing_tail_extraction(#b_function{bs=Blocks0}=F, ModInfo) ->
@@ -883,25 +892,25 @@ sote_rewrite_call(Call0, [Arg | ArgsIn], ArgsOut, State0) ->
sote_rewrite_call(Call0, ArgsIn, [Arg | ArgsOut], State0)
end.
-%% Adds parameter_type_info annotations to help the validator determine whether
-%% our optimizations were safe.
+%% Adds parameter annotations to help the validator determine whether our
+%% optimizations were safe.
annotate_context_parameters({Fs, ModInfo}) ->
mapfoldl(fun annotate_context_parameters/2, ModInfo, Fs).
annotate_context_parameters(F, ModInfo) ->
ParamInfo = funcinfo_get(F, parameter_info, ModInfo),
- TypeAnno0 = beam_ssa:get_anno(parameter_type_info, F, #{}),
- TypeAnno = maps:fold(fun(K, _V, Acc) when is_map_key(K, Acc) ->
- %% Assertion.
- error(conflicting_parameter_types);
- (K, suitable_for_reuse, Acc) ->
- T = beam_validator:type_anno(match_context),
- Acc#{ K => T };
- (_K, _V, Acc) ->
- Acc
- end, TypeAnno0, ParamInfo),
- {beam_ssa:add_anno(parameter_type_info, TypeAnno, F), ModInfo}.
+ ParamAnno0 = beam_ssa:get_anno(parameter_info, F, #{}),
+ ParamAnno = maps:fold(fun(K, _V, Acc) when is_map_key(K, Acc) ->
+ %% Assertion.
+ error(conflicting_parameter_types);
+ (K, suitable_for_reuse, Acc) ->
+ Info = maps:get(K, Acc, []),
+ Acc#{ K => [accepts_match_context | Info] };
+ (_K, _V, Acc) ->
+ Acc
+ end, ParamAnno0, ParamInfo),
+ {beam_ssa:add_anno(parameter_info, ParamAnno, F), ModInfo}.
%%%
%%% +bin_opt_info
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
index f40f005d50..8529cf7439 100644
--- a/lib/compiler/src/beam_ssa_codegen.erl
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -28,7 +28,7 @@
-include("beam_ssa.hrl").
--import(lists, [foldl/3,keymember/3,keysort/2,last/1,map/2,mapfoldl/3,
+-import(lists, [foldl/3,keymember/3,keysort/2,map/2,mapfoldl/3,
reverse/1,reverse/2,sort/1,splitwith/2,takewhile/2]).
-record(cg, {lcount=1 :: beam_label(), %Label counter
@@ -37,7 +37,8 @@
used_labels=gb_sets:empty() :: gb_sets:set(ssa_label()),
regs=#{} :: #{beam_ssa:var_name()=>ssa_register()},
ultimate_fail=1 :: beam_label(),
- catches=gb_sets:empty() :: gb_sets:set(ssa_label())
+ catches=gb_sets:empty() :: gb_sets:set(ssa_label()),
+ fc_label=1 :: beam_label()
}).
-spec module(beam_ssa:b_module(), [compile:option()]) ->
@@ -56,7 +57,7 @@ module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, _Opts) ->
-record(cg_set, {anno=#{} :: anno(),
dst :: b_var(),
- op :: beam_ssa:op(),
+ op :: beam_ssa:op() | 'nop',
args :: [beam_ssa:argument() | xreg()]}).
-record(cg_alloc, {anno=#{} :: anno(),
@@ -114,17 +115,17 @@ functions(Forms, AtomMod) ->
function(#b_function{anno=Anno,bs=Blocks}, AtomMod, St0) ->
#{func_info:={_,Name,Arity}} = Anno,
try
- assert_badarg_block(Blocks), %Assertion.
+ assert_exception_block(Blocks), %Assertion.
Regs = maps:get(registers, Anno),
St1 = St0#cg{labels=#{},used_labels=gb_sets:empty(),
regs=Regs},
{Fi,St2} = new_label(St1), %FuncInfo label
{Entry,St3} = local_func_label(Name, Arity, St2),
{Ult,St4} = new_label(St3), %Ultimate failure
- Labels = (St4#cg.labels)#{0=>Entry,?BADARG_BLOCK=>0},
+ Labels = (St4#cg.labels)#{0=>Entry,?EXCEPTION_BLOCK=>0},
St5 = St4#cg{labels=Labels,used_labels=gb_sets:singleton(Entry),
ultimate_fail=Ult},
- {Body,St} = cg_fun(Blocks, St5),
+ {Body,St} = cg_fun(Blocks, St5#cg{fc_label=Fi}),
Asm = [{label,Fi},line(Anno),
{func_info,AtomMod,{atom,Name},Arity}] ++
add_parameter_annos(Body, Anno) ++
@@ -137,10 +138,10 @@ function(#b_function{anno=Anno,bs=Blocks}, AtomMod, St0) ->
erlang:raise(Class, Error, Stack)
end.
-assert_badarg_block(Blocks) ->
- %% Assertion: ?BADARG_BLOCK must be the call erlang:error(badarg).
+assert_exception_block(Blocks) ->
+ %% Assertion: ?EXCEPTION_BLOCK must be a call erlang:error(badarg).
case Blocks of
- #{?BADARG_BLOCK:=Blk} ->
+ #{?EXCEPTION_BLOCK:=Blk} ->
#b_blk{is=[#b_set{op=call,dst=Ret,
args=[#b_remote{mod=#b_literal{val=erlang},
name=#b_literal{val=error}},
@@ -148,19 +149,21 @@ assert_badarg_block(Blocks) ->
last=#b_ret{arg=Ret}} = Blk,
ok;
#{} ->
- %% ?BADARG_BLOCK has been removed because it was never used.
+ %% ?EXCEPTION_BLOCK has been removed because it was never used.
ok
end.
add_parameter_annos([{label, _}=Entry | Body], Anno) ->
- ParamInfo = maps:get(parameter_type_info, Anno, #{}),
+ ParamTypes = maps:get(parameter_info, Anno, #{}),
+
Annos = maps:fold(
- fun(K, V, Acc) when is_map_key(K, ParamInfo) ->
- TypeInfo = maps:get(K, ParamInfo),
- [{'%', {type_info, V, TypeInfo}} | Acc];
+ fun(K, V, Acc) when is_map_key(K, ParamTypes) ->
+ Info = map_get(K, ParamTypes),
+ [{'%', {var_info, V, Info}} | Acc];
(_K, _V, Acc) ->
Acc
end, [], maps:get(registers, Anno)),
+
[Entry | sort(Annos)] ++ Body.
cg_fun(Blocks, St0) ->
@@ -170,7 +173,8 @@ cg_fun(Blocks, St0) ->
Linear2 = prefer_xregs(Linear1, St),
Linear3 = liveness(Linear2, St),
Linear4 = defined(Linear3, St),
- Linear = opt_allocate(Linear4, St),
+ Linear5 = opt_allocate(Linear4, St),
+ Linear = fix_wait_timeout(Linear5),
cg_linear(Linear, St).
%% collect_catch_labels(Linear, St) -> St.
@@ -365,7 +369,7 @@ classify_heap_need(bs_save) -> neutral;
classify_heap_need(bs_get_position) -> gc;
classify_heap_need(bs_set_position) -> neutral;
classify_heap_need(bs_skip) -> gc;
-classify_heap_need(bs_start_match) -> neutral;
+classify_heap_need(bs_start_match) -> gc;
classify_heap_need(bs_test_tail) -> neutral;
classify_heap_need(bs_utf16_size) -> neutral;
classify_heap_need(bs_utf8_size) -> neutral;
@@ -384,6 +388,8 @@ classify_heap_need(is_tagged_tuple) -> neutral;
classify_heap_need(kill_try_tag) -> gc;
classify_heap_need(landingpad) -> gc;
classify_heap_need(make_fun) -> gc;
+classify_heap_need(match_fail) -> gc;
+classify_heap_need(nop) -> neutral;
classify_heap_need(new_try_tag) -> gc;
classify_heap_need(peek_message) -> gc;
classify_heap_need(put_map) -> gc;
@@ -409,44 +415,13 @@ classify_heap_need(wait_timeout) -> gc.
%%% since the BEAM interpreter have more optimized instructions
%%% operating on X registers than on Y registers.
%%%
-%%% 'call' and 'make_fun' are handled somewhat specially. If a value
-%%% already is in the correct X register, the X register will always
-%%% be used instead of the Y register. However, if there are one or more
-%%% values in the wrong X registers, the X registers variables will be
-%%% used only if that does not cause more 'move' instructions to be
-%%% be emitted than if the Y register variables were used.
-%%%
-%%% Here are some examples. The first example shows how a 'move' from
-%%% an Y register is eliminated:
-%%%
-%%% move x0 y1
-%%% move y1 x0 %%Will be eliminated.
-%%%
-%%% call f/1
-%%%
-%%% Here is an example when x0 and x1 must be swapped to load the argument
-%%% registers. Here the 'call' instruction will use the Y registers to
-%%% avoid introducing an extra 'move' insruction:
-%%%
-%%% move x0 y0
-%%% move x1 y1
-%%%
-%%% move y0 x1
-%%% move y1 x0
-%%%
-%%% call f/2
+%%% In call and 'call' and 'make_fun' instructions there is also the
+%%% possibility that a 'move' instruction can be eliminated because
+%%% a value is already in the correct X register.
%%%
-%%% Using the X register to load the argument registers would need
-%%% an extra 'move' instruction like this:
-%%%
-%%% move x0 y0
-%%% move x1 y1
-%%%
-%%% move x1 x2
-%%% move x0 x1
-%%% move x2 x0
-%%%
-%%% call f/2
+%%% Because of the new 'swap' instruction introduced in OTP 23, it
+%%% is always beneficial to prefer X register over Y registers. That
+%%% was not the case in OTP 22, which lacks the 'swap' instruction.
%%%
prefer_xregs(Linear, St) ->
@@ -530,60 +505,22 @@ prefer_xregs_prune(#cg_set{dst=Dst}, Copies, St) ->
maps:filter(F, Copies).
%% prefer_xregs_call(Instruction, Copies, St) -> Instruction.
-%% Given a 'call' or 'make_fun' instruction, minimize the number
-%% of 'move' instructions to set up the argument registers.
-%% Prefer using X registers over Y registers, unless that will
-%% result in more 'move' instructions.
-
-prefer_xregs_call(#cg_set{args=[_]}=I, _Copies, _St) ->
- I;
-prefer_xregs_call(#cg_set{args=[F|Args0]}=I, Copies, St) ->
- case Args0 of
- [A0] ->
- %% Only one argument. Always prefer the X register
- %% if available.
- A = do_prefer_xreg(A0, Copies, St),
- I#cg_set{args=[F,A]};
- [_|_] ->
- %% Two or more arguments. Try rewriting arguments in
- %% two ways and see which way produces the least
- %% number of 'move' instructions.
- Args1 = prefer_xregs_call_1(Args0, Copies, 0, St),
- Args2 = [do_prefer_xreg(A, Copies, St) || A <- Args0],
- case {count_moves(Args1, St),count_moves(Args2, St)} of
- {N1,N2} when N1 < N2 ->
- %% There will be fewer 'move' instructions if
- %% we keep using Y registers.
- I#cg_set{args=[F|Args1]};
- {_,_} ->
- %% Always use the values in X registers.
- I#cg_set{args=[F|Args2]}
- end
- end.
-
-count_moves(Args, St) ->
- length(setup_args(beam_args(Args, St))).
-
-prefer_xregs_call_1([#b_var{}=A|As], Copies, X, St) ->
- case {beam_arg(A, St),Copies} of
- {{y,_},#{A:=Other}} ->
- case beam_arg(Other, St) of
- {x,X} ->
- %% This value is already in the correct X register.
- %% It is always benefical to use the X register variable.
- [Other|prefer_xregs_call_1(As, Copies, X+1, St)];
- _ ->
- %% This value is another X register. Keep using
- %% the Y register variable.
- [A|prefer_xregs_call_1(As, Copies, X+1, St)]
- end;
- {_,_} ->
- %% The value is not available in an X register.
- [A|prefer_xregs_call_1(As, Copies, X+1, St)]
- end;
-prefer_xregs_call_1([A|As], Copies, X, St) ->
- [A|prefer_xregs_call_1(As, Copies, X+1, St)];
-prefer_xregs_call_1([], _, _, _) -> [].
+%% Given a 'call' or 'make_fun' instruction rewrite the arguments
+%% to use an X register instead of a Y register if a value is
+%% is available in both.
+
+prefer_xregs_call(#cg_set{args=[F0|Args0]}=I, Copies, St) ->
+ F = case F0 of
+ #b_var{} ->
+ do_prefer_xreg(F0, Copies, St);
+ #b_remote{mod=Mod,name=Name} ->
+ F0#b_remote{mod=do_prefer_xreg(Mod, Copies, St),
+ name=do_prefer_xreg(Name, Copies, St)};
+ _ ->
+ F0
+ end,
+ Args = [do_prefer_xreg(A, Copies, St) || A <- Args0],
+ I#cg_set{args=[F|Args]}.
do_prefer_xreg(#b_var{}=A, Copies, St) ->
case {beam_arg(A, St),Copies} of
@@ -629,7 +566,7 @@ liveness_get(S, LiveMap) ->
end.
liveness_successors(Terminator) ->
- successors(Terminator) -- [?BADARG_BLOCK].
+ successors(Terminator) -- [?EXCEPTION_BLOCK].
liveness_is([#cg_alloc{}=I0|Is], Regs, Live, Acc) ->
I = I0#cg_alloc{live=num_live(Live, Regs)},
@@ -942,6 +879,44 @@ opt_allocate_is([#cg_alloc{}|Is]) ->
opt_allocate_is(Is);
opt_allocate_is([]) -> none.
+%% fix_wait_timeout([Block]) -> [Block].
+%% In SSA code, the `wait_timeout` instruction is a three-way branch
+%% (because there will be an exception for a bad timeout value). In
+%% BEAM code, the potential rasing of an exception for a bad timeout
+%% duration is not explicitly represented. Thus we will need to
+%% rewrite the following code:
+%%
+%% WaitBool = wait_timeout TimeoutValue
+%% Succeeded = succeeded:body WaitBool
+%% br Succeeded, ^good_timeout_value, ^bad_timeout_value
+%%
+%% good_timeout_value:
+%% br WaitBool, ^timeout_expired, ^new_message_received
+%%
+%% To this code:
+%%
+%% WaitBool = wait_timeout TimeoutValue
+%% br WaitBool, ^timeout_expired, ^new_message_received
+%%
+fix_wait_timeout([{L1,#cg_blk{is=Is0,last=#cg_br{bool=#b_var{},succ=L2}}=Blk1},
+ {L2,#cg_blk{is=[],last=#cg_br{bool=#b_var{}}=Br}=Blk2}|Bs]) ->
+ case fix_wait_timeout_is(Is0, []) of
+ no ->
+ [{L1,Blk1},{L2,Blk2}|fix_wait_timeout(Bs)];
+ {yes,Is} ->
+ [{L1,Blk1#cg_blk{is=Is,last=Br}}|fix_wait_timeout(Bs)]
+ end;
+fix_wait_timeout([B|Bs]) ->
+ [B|fix_wait_timeout(Bs)];
+fix_wait_timeout([]) -> [].
+
+fix_wait_timeout_is([#cg_set{op=wait_timeout,dst=WaitBool}=WT,
+ #cg_set{op=succeeded,args=[WaitBool]}], Acc) ->
+ {yes,reverse(Acc, [WT])};
+fix_wait_timeout_is([I|Is], Acc) ->
+ fix_wait_timeout_is(Is, [I|Acc]);
+fix_wait_timeout_is([], _Acc) -> no.
+
%%%
%%% Here follows the main code generation functions.
%%%
@@ -973,6 +948,12 @@ cg_block(Is0, Last, Next, St0) ->
case Last of
#cg_br{succ=Next,fail=Next} ->
cg_block(Is0, none, St0);
+ #cg_br{succ=Same,fail=Same} when Same =:= ?EXCEPTION_BLOCK ->
+ %% An expression in this block *always* throws an exception, so we
+ %% terminate it with an 'if_end' to make sure the validator knows
+ %% that the following instructions won't actually be reached.
+ {Is,St} = cg_block(Is0, none, St0),
+ {Is++[if_end],St};
#cg_br{succ=Same,fail=Same} ->
{Fail,St1} = use_block_label(Same, St0),
{Is,St} = cg_block(Is0, none, St1),
@@ -1119,24 +1100,28 @@ cg_block([#cg_set{op=bs_init,dst=Dst0,args=Args0,anno=Anno}=I,
Line = line(Anno),
Alloc = map_get(alloc, Anno),
[#b_literal{val=Kind}|Args1] = Args0,
+ Live = get_live(I),
case Kind of
new ->
[Dst,Size,{integer,Unit}] = beam_args([Dst0|Args1], St),
- Live = get_live(I),
{[Line|cg_bs_init(Dst, Size, Alloc, Unit, Live, Fail)],St};
private_append ->
[Dst,Src,Bits,{integer,Unit}] = beam_args([Dst0|Args1], St),
Flags = {field_flags,[]},
- Is = [Line,{bs_private_append,Fail,Bits,Unit,Src,Flags,Dst}],
+ TestHeap = {test_heap,Alloc,Live},
+ BsPrivateAppend = {bs_private_append,Fail,Bits,Unit,Src,Flags,Dst},
+ Is = [TestHeap,Line,BsPrivateAppend],
{Is,St};
append ->
[Dst,Src,Bits,{integer,Unit}] = beam_args([Dst0|Args1], St),
Flags = {field_flags,[]},
- Live = get_live(I),
Is = [Line,{bs_append,Fail,Bits,Alloc,Live,Unit,Src,Flags,Dst}],
{Is,St}
end;
-cg_block([#cg_set{anno=Anno,op=bs_start_match,dst=Ctx0,args=[Bin0]}=I,
+cg_block([#cg_set{anno=Anno,
+ op=bs_start_match,
+ dst=Ctx0,
+ args=[#b_literal{val=new},Bin0]}=I,
#cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
[Dst,Bin1] = beam_args([Ctx0,Bin0], St),
{Bin,Pre} = force_reg(Bin1, Dst),
@@ -1153,10 +1138,17 @@ cg_block([#cg_set{anno=Anno,op=bs_start_match,dst=Ctx0,args=[Bin0]}=I,
cg_block([#cg_set{op=bs_get}=Set,
#cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
{cg_bs_get(Fail, Set, St),St};
-cg_block([#cg_set{op=bs_match_string,args=[CtxVar,#b_literal{val=String}]},
+cg_block([#cg_set{op=bs_match_string,args=[CtxVar,#b_literal{val=String0}]},
#cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
CtxReg = beam_arg(CtxVar, St),
- Is = [{test,bs_match_string,Fail,[CtxReg,String]}],
+
+ Bits = bit_size(String0),
+ String = case Bits rem 8 of
+ 0 -> String0;
+ Rem -> <<String0/bitstring,0:(8-Rem)>>
+ end,
+
+ Is = [{test,bs_match_string,Fail,[CtxReg,Bits,{string,String}]}],
{Is,St};
cg_block([#cg_set{dst=Dst0,op=landingpad,args=Args0}|T], Context, St0) ->
[Dst,{atom,Kind},Tag] = beam_args([Dst0|Args0], St0),
@@ -1178,6 +1170,10 @@ cg_block([#cg_set{op=call}=I,
#cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) ->
%% A call in try/catch block.
cg_block([I], none, St);
+cg_block([#cg_set{op=match_fail}=I,
+ #cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) ->
+ %% A match_fail instruction in a try/catch block.
+ cg_block([I], none, St);
cg_block([#cg_set{op=get_map_element,dst=Dst0,args=Args0},
#cg_set{op=succeeded,dst=Bool}], {Bool,Fail0}, St) ->
[Dst,Map,Key] = beam_args([Dst0|Args0], St),
@@ -1201,9 +1197,17 @@ cg_block([#cg_set{op=is_tagged_tuple,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
cg_block([#cg_set{op=is_nonempty_list,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
Args = beam_args(Args0, St),
{[{test,is_nonempty_list,ensure_label(Fail, St),Args}],St};
-cg_block([#cg_set{op=has_map_field,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
- [Src,Key] = beam_args(Args0, St),
- {[{test,has_map_fields,Fail,Src,{list,[Key]}}],St};
+cg_block([#cg_set{op=has_map_field,dst=Dst0,args=Args0}], {Dst0,Fail0}, St) ->
+ Fail = ensure_label(Fail0, St),
+ case beam_args([Dst0|Args0], St) of
+ [{z,0},Src,Key] ->
+ {[{test,has_map_fields,Fail,Src,{list,[Key]}}],St};
+ [Dst,Src,Key] ->
+ %% The result is used more than once. Must rewrite to bif:is_map_key
+ %% to set the destination register.
+ {[{bif,is_map_key,Fail0,[Key,Src],Dst},
+ {test,is_eq_exact,Fail,[Dst,{atom,true}]}],St}
+ end;
cg_block([#cg_set{op=call}=Call], {_Bool,_Fail}=Context, St0) ->
{Is0,St1} = cg_call(Call, body, none, St0),
{Is1,St} = cg_block([], Context, St1),
@@ -1220,15 +1224,24 @@ cg_block([#cg_set{op=call}=Call|T], Context, St0) ->
{Is0,St1} = cg_call(Call, body, none, St0),
{Is1,St} = cg_block(T, Context, St1),
{Is0++Is1,St};
-cg_block([#cg_set{op=make_fun,dst=Dst0,args=[Local|Args0]}|T],
+cg_block([#cg_set{anno=Anno,op=make_fun,dst=Dst0,args=[Local|Args0]}|T],
Context, St0) ->
#b_local{name=#b_literal{val=Func},arity=Arity} = Local,
[Dst|Args] = beam_args([Dst0|Args0], St0),
{FuncLbl,St1} = local_func_label(Func, Arity, St0),
Is0 = setup_args(Args) ++
[{make_fun2,{f,FuncLbl},0,0,length(Args)}|copy({x,0}, Dst)],
- {Is1,St} = cg_block(T, Context, St1),
- {Is0++Is1,St};
+
+ Is1 = case Anno of
+ #{ result_type := Type } ->
+ Info = {var_info, Dst, [{fun_type, Type}]},
+ Is0 ++ [{'%', Info}];
+ #{} ->
+ Is0
+ end,
+
+ {Is2,St} = cg_block(T, Context, St1),
+ {Is1++Is2,St};
cg_block([#cg_set{op=copy}|_]=T0, Context, St0) ->
{Is0,T} = cg_copy(T0, St0),
{Is1,St} = cg_block(T, Context, St0),
@@ -1239,6 +1252,35 @@ cg_block([#cg_set{op=copy}|_]=T0, Context, St0) ->
no ->
{Is,St}
end;
+cg_block([#cg_set{op=match_fail,args=Args0,anno=Anno}], none, St) ->
+ Args = beam_args(Args0, St),
+ Is = cg_match_fail(Args, line(Anno), none),
+ {Is,St};
+cg_block([#cg_set{op=match_fail,args=Args0,anno=Anno}|T], Context, St0) ->
+ FcLabel = case Context of
+ {return,_,none} ->
+ %% There is no stack frame. If this is a function_clause
+ %% exception, it is safe to jump to the label of the
+ %% func_info instruction.
+ St0#cg.fc_label;
+ _ ->
+ %% This is most probably not a function_clause.
+ %% If this is a function_clause exception
+ %% (rare), it is not safe to jump to the
+ %% func_info label.
+ none
+ end,
+ Args = beam_args(Args0, St0),
+ Is0 = cg_match_fail(Args, line(Anno), FcLabel),
+ {Is1,St} = cg_block(T, Context, St0),
+ {Is0++Is1,St};
+cg_block([#cg_set{op=wait_timeout,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
+ case beam_args(Args0, St) of
+ [{atom,infinity}] ->
+ {[{wait,Fail}],St};
+ [Timeout] ->
+ {[{wait_timeout,Fail,Timeout}],St}
+ end;
cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=Set], none, St) ->
[Dst|Args] = beam_args([Dst0|Args0], St),
Is = cg_instr(Op, Args, Dst, Set),
@@ -1270,8 +1312,7 @@ cg_copy(T0, St) ->
end, T0),
Moves0 = cg_copy_1(Copies, St),
Moves1 = [Move || {move,Src,Dst}=Move <- Moves0, Src =/= Dst],
- Scratch = {x,1022},
- Moves = order_moves(Moves1, Scratch),
+ Moves = order_moves(Moves1),
{Moves,T}.
cg_copy_1([#cg_set{dst=Dst0,args=Args}|T], St) ->
@@ -1473,8 +1514,9 @@ cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=[#b_local{}=Func0|Args0]},
Call = build_call(call, Arity, {f,FuncLbl}, Context, Dst),
Is = setup_args(Args, Anno, Context, St) ++ Line ++ Call,
case Anno of
- #{ result_type := Info } ->
- {Is ++ [{'%', {type_info, Dst, Info}}], St};
+ #{ result_type := Type } ->
+ Info = {var_info, Dst, [{type,Type}]},
+ {Is ++ [{'%', Info}], St};
#{} ->
{Is, St}
end;
@@ -1510,7 +1552,49 @@ cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=Args0},
Arity = length(Args),
Call = build_call(call_fun, Arity, Func, Context, Dst),
Is = setup_args(Args++[Func], Anno, Context, St) ++ Line ++ Call,
- {Is,St}.
+ case Anno of
+ #{ result_type := Type } ->
+ Info = {var_info, Dst, [{type,Type}]},
+ {Is ++ [{'%', Info}], St};
+ #{} ->
+ {Is, St}
+ end.
+
+cg_match_fail([{atom,function_clause}|Args], Line, Fc) ->
+ case Fc of
+ none ->
+ %% There is a stack frame (probably because of inlining).
+ %% Jumping to the func_info label is not allowed by
+ %% beam_validator. Rewrite the instruction as a call to
+ %% erlang:error/2.
+ make_fc(Args, Line);
+ _ ->
+ setup_args(Args) ++ [{jump,{f,Fc}}]
+ end;
+cg_match_fail([{atom,Op}], Line, _Fc) ->
+ [Line,Op];
+cg_match_fail([{atom,Op},Val], Line, _Fc) ->
+ [Line,{Op,Val}].
+
+make_fc(Args, Line) ->
+ %% Recreate the original call to erlang:error/2.
+ Live = foldl(fun({x,X}, A) -> max(X+1, A);
+ (_, A) -> A
+ end, 0, Args),
+ TmpReg = {x,Live},
+ StkMoves = build_stk(reverse(Args), TmpReg, nil),
+ [{test_heap,2*length(Args),Live}|StkMoves] ++
+ [{move,{atom,function_clause},{x,0}},
+ Line,
+ {call_ext,2,{extfunc,erlang,error,2}}].
+
+build_stk([V], _TmpReg, Tail) ->
+ [{put_list,V,Tail,{x,1}}];
+build_stk([V|Vs], TmpReg, Tail) ->
+ I = {put_list,V,Tail,TmpReg},
+ [I|build_stk(Vs, TmpReg, TmpReg)];
+build_stk([], _TmpReg, nil) ->
+ [{move,nil,{x,1}}].
build_call(call_fun, Arity, _Func, none, Dst) ->
[{call_fun,Arity}|copy({x,0}, Dst)];
@@ -1550,15 +1634,22 @@ build_apply(Arity, {return,Val,N}, _Dst) when is_integer(N) ->
build_apply(Arity, none, Dst) ->
[{apply,Arity}|copy({x,0}, Dst)].
-cg_instr(put_map, [{atom,assoc},SrcMap|Ss], Dst, Set) ->
+cg_instr(bs_start_match, [{atom,resume}, Src], Dst, Set) ->
Live = get_live(Set),
- [{put_map_assoc,{f,0},SrcMap,Dst,Live,{list,Ss}}];
+ [{bs_start_match4,{atom,resume},Live,Src,Dst}];
+cg_instr(bs_start_match, [{atom,new}, Src0], Dst, Set) ->
+ {Src, Pre} = force_reg(Src0, Dst),
+ Live = get_live(Set),
+ Pre ++ [{bs_start_match4,{atom,no_fail},Live,Src,Dst}];
cg_instr(bs_get_tail, [Src], Dst, Set) ->
Live = get_live(Set),
[{bs_get_tail,Src,Dst,Live}];
cg_instr(bs_get_position, [Ctx], Dst, Set) ->
Live = get_live(Set),
[{bs_get_position,Ctx,Dst,Live}];
+cg_instr(put_map, [{atom,assoc},SrcMap|Ss], Dst, Set) ->
+ Live = get_live(Set),
+ [{put_map_assoc,{f,0},SrcMap,Dst,Live,{list,Ss}}];
cg_instr(Op, Args, Dst, _Set) ->
cg_instr(Op, Args, Dst).
@@ -1592,8 +1683,12 @@ cg_instr(get_tl=Op, [Src], Dst) ->
[{Op,Src,Dst}];
cg_instr(get_tuple_element=Op, [Src,{integer,N}], Dst) ->
[{Op,Src,N,Dst}];
+cg_instr(has_map_field, [Map,Key], Dst) ->
+ [{bif,is_map_key,{f,0},[Key,Map],Dst}];
cg_instr(put_list=Op, [Hd,Tl], Dst) ->
[{Op,Hd,Tl,Dst}];
+cg_instr(nop, [], _Dst) ->
+ [];
cg_instr(put_tuple, Elements, Dst) ->
[{put_tuple2,Dst,{list,Elements}}];
cg_instr(put_tuple_arity, [{integer,Arity}], Dst) ->
@@ -1617,9 +1712,9 @@ cg_test(bs_utf8_size=Op, Fail, [Src], Dst, _I) ->
[{Op,Fail,Src,Dst}];
cg_test(bs_utf16_size=Op, Fail, [Src], Dst, _I) ->
[{Op,Fail,Src,Dst}];
-cg_test({float,convert}, Fail, [Src], Dst, _I) ->
+cg_test({float,convert}, Fail, [Src], Dst, #cg_set{anno=Anno}) ->
{f,0} = Fail, %Assertion.
- [{fconv,Src,Dst}];
+ [line(Anno),{fconv,Src,Dst}];
cg_test({float,Op0}, Fail, Args, Dst, #cg_set{anno=Anno}) ->
Op = case Op0 of
'+' -> fadd;
@@ -1631,16 +1726,9 @@ cg_test({float,Op0}, Fail, Args, Dst, #cg_set{anno=Anno}) ->
[line(Anno),{bif,Op,Fail,Args,Dst}];
cg_test(peek_message, Fail, [], Dst, _I) ->
[{loop_rec,Fail,{x,0}}|copy({x,0}, Dst)];
-cg_test(put_map, Fail, [{atom,exact},SrcMap|Ss], Dst, Set) ->
+cg_test(put_map, Fail, [{atom,exact},SrcMap|Ss], Dst, #cg_set{anno=Anno}=Set) ->
Live = get_live(Set),
- [{put_map_exact,Fail,SrcMap,Dst,Live,{list,Ss}}];
-cg_test(wait_timeout, Fail, [Timeout], _Dst, _) ->
- case Timeout of
- {atom,infinity} ->
- [{wait,Fail}];
- _ ->
- [{wait_timeout,Fail,Timeout}]
- end.
+ [line(Anno),{put_map_exact,Fail,SrcMap,Dst,Live,{list,Ss}}].
cg_bs_get(Fail, #cg_set{dst=Dst0,args=[#b_literal{val=Type}|Ss0]}=Set, St) ->
Op = case Type of
@@ -1728,7 +1816,7 @@ cg_catch(Agg, T0, Context, St0) ->
cg_try(Agg, Tag, T0, Context, St0) ->
{Moves0,T1} = cg_extract(T0, Agg, St0),
- Moves = order_moves(Moves0, {x,3}),
+ Moves = order_moves(Moves0),
[#cg_set{op=kill_try_tag}|T2] = T1,
{T,St} = cg_block(T2, Context, St0),
{[{try_case,Tag}|Moves++T],St}.
@@ -1780,7 +1868,7 @@ linearize(Blocks) ->
Linear = beam_ssa:linearize(Blocks),
linearize_1(Linear, Blocks).
-linearize_1([{?BADARG_BLOCK,_}|Ls], Blocks) ->
+linearize_1([{?EXCEPTION_BLOCK,_}|Ls], Blocks) ->
linearize_1(Ls, Blocks);
linearize_1([{L,Block0}|Ls], Blocks) ->
Block = translate_block(L, Block0, Blocks),
@@ -1822,8 +1910,6 @@ translate_terminator(#b_ret{anno=Anno,arg=Arg}) ->
#cg_ret{arg=Arg,dealloc=Dealloc};
translate_terminator(#b_br{bool=#b_literal{val=true},succ=Succ}) ->
#cg_br{bool=#b_literal{val=true},succ=Succ,fail=Succ};
-translate_terminator(#b_br{bool=#b_literal{val=false},fail=Fail}) ->
- #cg_br{bool=#b_literal{val=true},succ=Fail,fail=Fail};
translate_terminator(#b_br{bool=Bool,succ=Succ,fail=Fail}) ->
#cg_br{bool=Bool,succ=Succ,fail=Fail};
translate_terminator(#b_switch{arg=Bool,fail=Fail,list=List}) ->
@@ -1834,7 +1920,28 @@ translate_phis(L, #cg_br{succ=Target,fail=Target}, Blocks) ->
Phis = takewhile(fun(#b_set{op=phi}) -> true;
(#b_set{}) -> false
end, Is),
- phi_copies(Phis, L);
+ case Phis of
+ [] ->
+ [];
+ [#b_set{op=phi,dst=NopDst}|_]=Phis ->
+ %% In rare cases (so far only seen in unoptimized code),
+ %% copy instructions can be combined like this:
+ %%
+ %% y0/yreg_0 = copy x0/xreg_0
+ %% x0/xreg_1 = copy y0/yreg_0
+ %%
+ %% This will result in a swap instruction instead of
+ %% two move instructions. To avoid that, insert a
+ %% dummy instruction before the copy instructions
+ %% resulting from the phi node:
+ %%
+ %% y0/yreg_0 = copy x0/xreg_0
+ %% _ = nop
+ %% x0/xreg_1 = copy y0/yreg_0
+ %%
+ Nop = #cg_set{op=nop,dst=NopDst,args=[]},
+ [Nop|phi_copies(Phis, L)]
+ end;
translate_phis(_, _, _) -> [].
phi_copies([#b_set{dst=Dst,args=PhiArgs}|Sets], L) ->
@@ -1884,8 +1991,7 @@ setup_args([]) ->
[];
setup_args([_|_]=Args) ->
Moves = gen_moves(Args, 0, []),
- Scratch = {x,1+last(sort([length(Args)-1|[X || {x,X} <- Args]]))},
- order_moves(Moves, Scratch).
+ order_moves(Moves).
%% kill_yregs(Anno, #cg{}) -> [{kill,{y,Y}}].
%% Kill Y registers that will not be used again.
@@ -1905,47 +2011,48 @@ gen_moves([A|As], I, Acc) ->
gen_moves([], _, Acc) ->
keysort(3, Acc).
-%% order_moves([Move], ScratchReg) -> [Move]
+%% order_moves([Move]) -> [Move]
%% Orders move instruction so that source registers are not
%% destroyed before they are used. If there are cycles
%% (such as {move,{x,0},{x,1}}, {move,{x,1},{x,1}}),
-%% the scratch register is used to break up the cycle.
-%% If possible, the first move of the input list is placed
+%% swap instructions will be used to break up the cycle.
+%%
+%% If possible, the first move of the input list is placed
%% last in the result list (to make the move to {x,0} occur
%% just before the call to allow the Beam loader to coalesce
%% the instructions).
-order_moves(Ms, Scr) -> order_moves(Ms, Scr, []).
+order_moves(Ms) -> order_moves(Ms, []).
-order_moves([{move,_,_}=M|Ms0], ScrReg, Acc0) ->
- {Chain,Ms} = collect_chain(Ms0, [M], ScrReg),
+order_moves([{move,_,_}=M|Ms0], Acc0) ->
+ {Chain,Ms} = collect_chain(Ms0, [M]),
Acc = reverse(Chain, Acc0),
- order_moves(Ms, ScrReg, Acc);
-order_moves([], _, Acc) -> Acc.
+ order_moves(Ms, Acc);
+order_moves([], Acc) -> Acc.
-collect_chain(Ms, Path, ScrReg) ->
- collect_chain(Ms, Path, [], ScrReg).
+collect_chain(Ms, Path) ->
+ collect_chain(Ms, Path, []).
-collect_chain([{move,Src,Same}=M|Ms0], [{move,Same,_}|_]=Path, Others, ScrReg) ->
+collect_chain([{move,Src,Same}=M|Ms0], [{move,Same,_}|_]=Path, Others) ->
case keymember(Src, 3, Path) of
false ->
- collect_chain(reverse(Others, Ms0), [M|Path], [], ScrReg);
+ collect_chain(reverse(Others, Ms0), [M|Path], []);
true ->
- %% There is a cycle, which we must break up.
- {break_up_cycle(M, Path, ScrReg),reverse(Others, Ms0)}
+ %% There is a cycle.
+ {break_up_cycle(M, Path),reverse(Others, Ms0)}
end;
-collect_chain([M|Ms], Path, Others, ScrReg) ->
- collect_chain(Ms, Path, [M|Others], ScrReg);
-collect_chain([], Path, Others, _) ->
+collect_chain([M|Ms], Path, Others) ->
+ collect_chain(Ms, Path, [M|Others]);
+collect_chain([], Path, Others) ->
{Path,Others}.
-break_up_cycle({move,Src,_}=M, Path, ScrReg) ->
- [{move,ScrReg,Src},M|break_up_cycle1(Src, Path, ScrReg)].
+break_up_cycle({move,Src,_Dst}=M, Path) ->
+ break_up_cycle_1(Src, [M|Path], []).
-break_up_cycle1(Dst, [{move,Src,Dst}|Path], ScrReg) ->
- [{move,Src,ScrReg}|Path];
-break_up_cycle1(Dst, [M|Path], LastMove) ->
- [M|break_up_cycle1(Dst, Path, LastMove)].
+break_up_cycle_1(Dst, [{move,_Src,Dst}|Path], Acc) ->
+ reverse(Acc, Path);
+break_up_cycle_1(Dst, [{move,S,D}|Path], Acc) ->
+ break_up_cycle_1(Dst, Path, [{swap,S,D}|Acc]).
%%%
%%% General utility functions.
diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl
index 05273b1617..b4710d6797 100644
--- a/lib/compiler/src/beam_ssa_dead.erl
+++ b/lib/compiler/src/beam_ssa_dead.erl
@@ -28,7 +28,7 @@
-include("beam_ssa.hrl").
-import(lists, [append/1,keymember/3,last/1,member/2,
- takewhile/2,reverse/1]).
+ reverse/1,sort/1,takewhile/2]).
-type used_vars() :: #{beam_ssa:label():=cerl_sets:set(beam_ssa:var_name())}.
@@ -97,38 +97,38 @@ shortcut_opt(#st{bs=Blocks}=St) ->
%% in the first clause of shortcut_2/5).
Ls = beam_ssa:rpo(Blocks),
- shortcut_opt(Ls, #{}, St).
+ shortcut_opt(Ls, St).
-shortcut_opt([L|Ls], Bs, #st{bs=Blocks0}=St) ->
+shortcut_opt([L|Ls], #st{bs=Blocks0}=St) ->
#b_blk{is=Is,last=Last0} = Blk0 = get_block(L, St),
- case shortcut_terminator(Last0, Is, L, Bs, St) of
+ case shortcut_terminator(Last0, Is, L, St) of
Last0 ->
%% No change. No need to update the block.
- shortcut_opt(Ls, Bs, St);
+ shortcut_opt(Ls, St);
Last ->
%% The terminator was simplified in some way.
%% Update the block.
Blk = Blk0#b_blk{last=Last},
Blocks = Blocks0#{L=>Blk},
- shortcut_opt(Ls, Bs, St#st{bs=Blocks})
+ shortcut_opt(Ls, St#st{bs=Blocks})
end;
-shortcut_opt([], _, St) -> St.
+shortcut_opt([], St) -> St.
shortcut_terminator(#b_br{bool=#b_literal{val=true},succ=Succ0},
- _Is, From, Bs, St0) ->
+ _Is, From, St0) ->
St = St0#st{rel_op=none},
- shortcut(Succ0, From, Bs, St);
+ shortcut(Succ0, From, #{}, St);
shortcut_terminator(#b_br{bool=#b_var{}=Bool,succ=Succ0,fail=Fail0}=Br,
- Is, From, Bs, St0) ->
+ Is, From, St0) ->
St = St0#st{target=one_way},
RelOp = get_rel_op(Bool, Is),
%% The boolean in a `br` is seldom used by the successors. By
%% not binding its value unless it is actually used we might be able
%% to skip some work in shortcut/4 and sub/2.
- SuccBs = bind_var_if_used(Succ0, Bool, #b_literal{val=true}, Bs, St),
+ SuccBs = bind_var_if_used(Succ0, Bool, #b_literal{val=true}, St),
BrSucc = shortcut(Succ0, From, SuccBs, St#st{rel_op=RelOp}),
- FailBs = bind_var_if_used(Fail0, Bool, #b_literal{val=false}, Bs, St),
+ FailBs = bind_var_if_used(Fail0, Bool, #b_literal{val=false}, St),
BrFail = shortcut(Fail0, From, FailBs, St#st{rel_op=invert_op(RelOp)}),
case {BrSucc,BrFail} of
@@ -141,19 +141,34 @@ shortcut_terminator(#b_br{bool=#b_var{}=Bool,succ=Succ0,fail=Fail0}=Br,
%% No change.
Br
end;
-shortcut_terminator(#b_switch{arg=Bool,list=List0}=Sw, _Is, From, Bs, St) ->
- List = shortcut_switch(List0, Bool, From, Bs, St),
- beam_ssa:normalize(Sw#b_switch{list=List});
-shortcut_terminator(Last, _Is, _Bs, _From, _St) ->
+shortcut_terminator(#b_switch{arg=Bool,fail=Fail0,list=List0}=Sw,
+ _Is, From, St) ->
+ Fail = shortcut_sw_fail(Fail0, List0, Bool, From, St),
+ List = shortcut_sw_list(List0, Bool, From, St),
+ beam_ssa:normalize(Sw#b_switch{fail=Fail,list=List});
+shortcut_terminator(Last, _Is, _From, _St) ->
Last.
-shortcut_switch([{Lit,L0}|T], Bool, From, Bs, St0) ->
+shortcut_sw_fail(Fail0, List, Bool, From, St0) ->
+ case sort(List) of
+ [{#b_literal{val=false},_},
+ {#b_literal{val=true},_}] ->
+ RelOp = {{'not',is_boolean},Bool},
+ St = St0#st{rel_op=RelOp,target=one_way},
+ #b_br{bool=#b_literal{val=true},succ=Fail} =
+ shortcut(Fail0, From, #{}, St),
+ Fail;
+ _ ->
+ Fail0
+ end.
+
+shortcut_sw_list([{Lit,L0}|T], Bool, From, St0) ->
RelOp = {'=:=',Bool,Lit},
St = St0#st{rel_op=RelOp},
#b_br{bool=#b_literal{val=true},succ=L} =
- shortcut(L0, From, bind_var(Bool, Lit, Bs), St#st{target=one_way}),
- [{Lit,L}|shortcut_switch(T, Bool, From, Bs, St0)];
-shortcut_switch([], _, _, _, _) -> [].
+ shortcut(L0, From, bind_var(Bool, Lit, #{}), St#st{target=one_way}),
+ [{Lit,L}|shortcut_sw_list(T, Bool, From, St0)];
+shortcut_sw_list([], _, _, _) -> [].
shortcut(L, _From, Bs, #st{rel_op=none,target=one_way}) when map_size(Bs) =:= 0 ->
%% There is no way that we can find a suitable branch, because there is no
@@ -409,8 +424,10 @@ is_br_safe(UnsetVars, Br, #st{us=Us}=St) ->
is_forbidden(L, St) ->
case get_block(L, St) of
- #b_blk{is=[#b_set{op=phi}|_]} -> true;
- #b_blk{is=[#b_set{op=peek_message}|_]} -> true;
+ #b_blk{is=[#b_set{op=phi}|_]} ->
+ true;
+ #b_blk{is=[#b_set{}=I|_]} ->
+ beam_ssa:is_loop_header(I);
#b_blk{} -> false
end.
@@ -423,6 +440,13 @@ eval_is([#b_set{op=phi,dst=Dst,args=Args}|Is], From, Bs0, St) ->
Val = get_phi_arg(Args, From),
Bs = bind_var(Dst, Val, Bs0),
eval_is(Is, From, Bs, St);
+eval_is([#b_set{op={succeeded,guard},dst=Dst,args=[Var]}], _From, Bs, _St) ->
+ case Bs of
+ #{Var:=#b_literal{}} ->
+ bind_var(Dst, #b_literal{val=true}, Bs);
+ #{} ->
+ Bs
+ end;
eval_is([#b_set{op={bif,_},dst=Dst}=I0|Is], From, Bs, St) ->
I = sub(I0, Bs),
case eval_bif(I, St) of
@@ -521,13 +545,10 @@ eval_switch_1([], _Arg, _PrevOp, Fail) ->
%% Fail is now either the failure label or 'none'.
Fail.
-bind_var_if_used(L, Var, Val0, Bs, #st{us=Us}) ->
+bind_var_if_used(L, Var, Val, #st{us=Us}) ->
case cerl_sets:is_element(Var, map_get(L, Us)) of
- true ->
- Val = get_value(Val0, Bs),
- Bs#{Var=>Val};
- false ->
- Bs
+ true -> #{Var=>Val};
+ false -> #{}
end.
bind_var(Var, Val0, Bs) ->
@@ -688,7 +709,7 @@ eval_rel_op(Bif, Args, #st{rel_op=Prev}) ->
%% PrevCondition is a condition known to be true. This function
%% will tell whether Condition will succeed.
-will_succeed({_Op,_Var,_Value}=Same, {_Op,_Var,_Value}=Same) ->
+will_succeed({_,_,_}=Same, {_,_,_}=Same) ->
%% Repeated test.
yes;
will_succeed({Op1,Var,#b_literal{val=A}}, {Op2,Var,#b_literal{val=B}}) ->
@@ -702,6 +723,9 @@ will_succeed({_,_}=Same, {_,_}=Same) ->
yes;
will_succeed({Test1,Var}, {Test2,Var}) ->
will_succeed_test(Test1, Test2);
+will_succeed({{'not',is_boolean},Var}, {'=:=',Var,#b_literal{val=Lit}})
+ when is_boolean(Lit) ->
+ no;
will_succeed({_,_}, {_,_}) ->
maybe;
will_succeed({_,_}, {_,_,_}) ->
@@ -758,32 +782,28 @@ will_succeed_1('=:=', A, '>', B) ->
will_succeed_1('=/=', A, '=:=', B) when A =:= B -> no;
will_succeed_1('<', A, '=:=', B) when B >= A -> no;
-will_succeed_1('<', A, '=/=', B) when B >= A -> yes;
will_succeed_1('<', A, '<', B) when B >= A -> yes;
-will_succeed_1('<', A, '=<', B) when B > A -> yes;
-will_succeed_1('<', A, '>=', B) when B > A -> no;
+will_succeed_1('<', A, '=<', B) when B >= A -> yes;
+will_succeed_1('<', A, '>=', B) when B >= A -> no;
will_succeed_1('<', A, '>', B) when B >= A -> no;
will_succeed_1('=<', A, '=:=', B) when B > A -> no;
-will_succeed_1('=<', A, '=/=', B) when B > A -> yes;
will_succeed_1('=<', A, '<', B) when B > A -> yes;
will_succeed_1('=<', A, '=<', B) when B >= A -> yes;
will_succeed_1('=<', A, '>=', B) when B > A -> no;
will_succeed_1('=<', A, '>', B) when B >= A -> no;
will_succeed_1('>=', A, '=:=', B) when B < A -> no;
-will_succeed_1('>=', A, '=/=', B) when B < A -> yes;
will_succeed_1('>=', A, '<', B) when B =< A -> no;
will_succeed_1('>=', A, '=<', B) when B < A -> no;
will_succeed_1('>=', A, '>=', B) when B =< A -> yes;
will_succeed_1('>=', A, '>', B) when B < A -> yes;
will_succeed_1('>', A, '=:=', B) when B =< A -> no;
-will_succeed_1('>', A, '=/=', B) when B =< A -> yes;
will_succeed_1('>', A, '<', B) when B =< A -> no;
-will_succeed_1('>', A, '=<', B) when B < A -> no;
+will_succeed_1('>', A, '=<', B) when B =< A -> no;
will_succeed_1('>', A, '>=', B) when B =< A -> yes;
-will_succeed_1('>', A, '>', B) when B < A -> yes;
+will_succeed_1('>', A, '>', B) when B =< A -> yes;
will_succeed_1('==', A, '==', B) ->
if
@@ -920,17 +940,14 @@ combine_eqs_1([L|Ls], #st{bs=Blocks0}=St0) ->
end;
combine_eqs_1([], St) -> St.
-comb_get_sw(L, Blocks) ->
- comb_get_sw(L, true, Blocks).
-
-comb_get_sw(L, Safe0, #st{bs=Blocks,skippable=Skippable}) ->
+comb_get_sw(L, #st{bs=Blocks,skippable=Skippable}) ->
#b_blk{is=Is,last=Last} = map_get(L, Blocks),
- Safe1 = Safe0 andalso is_map_key(L, Skippable),
+ Safe0 = is_map_key(L, Skippable),
case Last of
#b_ret{} ->
none;
#b_br{bool=#b_var{}=Bool,succ=Succ,fail=Fail} ->
- case comb_is(Is, Bool, Safe1) of
+ case comb_is(Is, Bool, Safe0) of
{none,_} ->
none;
{#b_set{op={bif,'=:='},args=[#b_var{}=Arg,#b_literal{}=Lit]},Safe} ->
@@ -941,7 +958,7 @@ comb_get_sw(L, Safe0, #st{bs=Blocks,skippable=Skippable}) ->
#b_br{} ->
none;
#b_switch{arg=#b_var{}=Arg,fail=Fail,list=List} ->
- {none,Safe} = comb_is(Is, none, Safe1),
+ {none,Safe} = comb_is(Is, none, Safe0),
{Safe,Arg,L,Fail,List}
end.
diff --git a/lib/compiler/src/beam_ssa_funs.erl b/lib/compiler/src/beam_ssa_funs.erl
index 41a123515f..2d9bfc4db9 100644
--- a/lib/compiler/src/beam_ssa_funs.erl
+++ b/lib/compiler/src/beam_ssa_funs.erl
@@ -143,7 +143,19 @@ lfo_optimize_is([], _LFuns, _Trampolines) ->
[].
lfo_short_circuit(Call, Trampolines) ->
- case maps:find(Call, Trampolines) of
- {ok, Other} -> lfo_short_circuit(Other, Trampolines);
- error -> Call
+ lfo_short_circuit(Call, Trampolines, cerl_sets:new()).
+
+lfo_short_circuit(Call, Trampolines, Seen0) ->
+ %% Beware of infinite loops! Get out if this call has been seen before.
+ case cerl_sets:is_element(Call, Seen0) of
+ true ->
+ Call;
+ false ->
+ case Trampolines of
+ #{Call := Other} ->
+ Seen = cerl_sets:add_element(Call, Seen0),
+ lfo_short_circuit(Other, Trampolines, Seen);
+ #{} ->
+ Call
+ end
end.
diff --git a/lib/compiler/src/beam_ssa_lint.erl b/lib/compiler/src/beam_ssa_lint.erl
index a003607dab..f6a1945d19 100644
--- a/lib/compiler/src/beam_ssa_lint.erl
+++ b/lib/compiler/src/beam_ssa_lint.erl
@@ -39,39 +39,14 @@ module(#b_module{body=Fs,name=Name}=Mod0, _Options) ->
end.
-spec format_error(term()) -> iolist().
-format_error({{_M,F,A},{redefined_variable, Name, Old, I}}) ->
- io_lib:format("~p/~p: Variable ~ts (~ts) redefined by ~ts",
- [F, A, format_var(Name), format_instr(Old), format_instr(I)]);
-format_error({{_M,F,A},{missing_phi_paths, Paths, I}}) ->
- io_lib:format("~p/~p: Phi node ~ts doesn't define a value for these "
- "branches: ~w",
- [F, A, format_instr(I), Paths]);
-format_error({{_M,F,A},{garbage_phi_paths, Paths, I}}) ->
- io_lib:format("~p/~p: Phi node ~ts defines a value for these unreachable "
- "or non-existent branches: ~w",
- [F, A, format_instr(I), Paths]);
-format_error({{_M,F,A},{unknown_phi_variable, Name, {From, _To}, I}}) ->
- io_lib:format("~p/~p: Variable ~ts used in phi node ~ts is undefined on "
- "branch ~w",
- [F, A, format_var(Name), format_instr(I), From]);
-format_error({{_M,F,A},{unknown_block, Label, I}}) ->
- io_lib:format("~p/~p: Unknown block ~p referenced in ~ts",
- [F, A, Label, I]);
-format_error({{_M,F,A},{unknown_variable, Name, I}}) ->
- io_lib:format("~p/~p: Unbound variable ~ts used in ~ts",
- [F, A, format_var(Name), format_instr(I)]);
-format_error({{_M,F,A},{phi_inside_block, Name, Id}}) ->
- io_lib:format("~p/~p: Phi node defining ~ts is not at start of block ~p",
- [F, A, format_var(Name), Id]);
-format_error({{_M,F,A},{undefined_label_in_phi, Label, I}}) ->
- io_lib:format("~p/~p: Unknown block label ~p in phi node ~ts",
- [F, A, Label, format_instr(I)]).
+format_error({{_M,F,A},Error}) ->
+ [io_lib:format("~p/~p: ", [F,A]),format_error_1(Error)].
format_instr(I) ->
[$',beam_ssa_pp:format_instr(I),$'].
format_var(V) ->
- beam_ssa_pp:format_var(#b_var{name=V}).
+ beam_ssa_pp:format_var(V).
validate_function(F) ->
try
@@ -86,34 +61,36 @@ validate_function(F) ->
erlang:raise(Class, Error, Stack)
end.
--type defined_vars() :: gb_sets:set(beam_ssa:var_name()).
+-type defined_vars() :: gb_sets:set(beam_ssa:argument()).
-record(vvars,
{blocks :: #{ beam_ssa:label() => beam_ssa:b_blk() },
branch_def_vars :: #{
- %% Describes the variable state at the time of this exact branch (phi
- %% node validation).
- {From :: beam_ssa:label(), To :: beam_ssa:label()} => defined_vars(),
- %% Describes the variable state common to all branches leading to this
- %% label (un/redefined variable validation).
- beam_ssa:label() => defined_vars() },
+ %% Describes the variable state at the time of
+ %% this exact branch (phi node validation).
+ {From :: beam_ssa:label(),
+ To :: beam_ssa:label()} => defined_vars(),
+ %% Describes the variable state common to all
+ %% branches leading to this label (un/redefined
+ %% variable validation).
+ beam_ssa:label() => defined_vars() },
defined_vars :: defined_vars()}).
-spec validate_variables(beam_ssa:b_function()) -> ok.
validate_variables(#b_function{ args = Args, bs = Blocks }) ->
%% Prefill the mapping with function arguments.
- ArgNames = vvars_get_varnames(Args),
- DefVars = gb_sets:from_list(ArgNames),
+ Args = vvars_get_variables(Args),
+ DefVars = gb_sets:from_list(Args),
Entry = 0,
State = #vvars{blocks = Blocks,
branch_def_vars = #{ Entry => DefVars },
defined_vars = DefVars},
- ok = vvars_assert_unique(Blocks, ArgNames),
+ ok = vvars_assert_unique(Blocks, Args),
vvars_phi_nodes(vvars_block(Entry, State)).
%% Checks the uniqueness of all variables across all blocks.
--spec vvars_assert_unique(Blocks, [beam_ssa:var_name()]) -> ok when
+-spec vvars_assert_unique(Blocks, [beam_ssa:b_var()]) -> ok when
Blocks :: #{ beam_ssa:label() => beam_ssa:b_blk() }.
vvars_assert_unique(Blocks, Args) ->
BlockIs = [Is || #b_blk{is=Is} <- maps:values(Blocks)],
@@ -124,12 +101,12 @@ vvars_assert_unique(Blocks, Args) ->
ok.
-spec vvars_assert_unique_1(Is, Defined) -> ok when
- Is :: list(beam_ssa:b_set()),
- Defined :: #{ beam_ssa:var_name() => beam_ssa:b_set() }.
-vvars_assert_unique_1([#b_set{dst=#b_var{name=DstName}}=I|Is], Defined) ->
+ Is :: list(beam_ssa:b_set()),
+ Defined :: #{ beam_ssa:b_var() => beam_ssa:b_set() }.
+vvars_assert_unique_1([#b_set{dst=Dst}=I|Is], Defined) ->
case Defined of
- #{DstName:=Old} -> throw({redefined_variable, DstName, Old, I});
- _ -> vvars_assert_unique_1(Is, Defined#{DstName=>I})
+ #{Dst:=Old} -> throw({redefined_variable, Dst, Old, I});
+ _ -> vvars_assert_unique_1(Is, Defined#{Dst=>I})
end;
vvars_assert_unique_1([], Defined) ->
Defined.
@@ -141,17 +118,17 @@ vvars_phi_nodes(#vvars{ blocks = Blocks }=State) ->
ok.
-spec vvars_phi_nodes_1(Is, Id, State) -> ok when
- Is :: list(beam_ssa:b_set()),
- Id :: beam_ssa:label(),
- State :: #vvars{}.
+ Is :: list(beam_ssa:b_set()),
+ Id :: beam_ssa:label(),
+ State :: #vvars{}.
vvars_phi_nodes_1([#b_set{ op = phi, args = Phis }=I | Is], Id, State) ->
ok = vvars_assert_phi_paths(Phis, I, Id, State),
ok = vvars_assert_phi_vars(Phis, I, Id, State),
vvars_phi_nodes_1(Is, Id, State);
vvars_phi_nodes_1([_ | Is], Id, _State) ->
- case [Dst || #b_set{op=phi,dst=#b_var{name=Dst}} <- Is] of
- [Name|_] ->
- throw({phi_inside_block, Name, Id});
+ case [Dst || #b_set{op=phi,dst=Dst} <- Is] of
+ [Var|_] ->
+ throw({phi_inside_block, Var, Id});
[] ->
ok
end;
@@ -161,10 +138,10 @@ vvars_phi_nodes_1([], _Id, _State) ->
%% Checks whether all paths leading to this phi node are represented, and that
%% it doesn't reference any non-existent paths.
-spec vvars_assert_phi_paths(Phis, I, Id, State) -> ok when
- Phis :: list({beam_ssa:argument(), beam_ssa:label()}),
- Id :: beam_ssa:label(),
- I :: beam_ssa:b_set(),
- State :: #vvars{}.
+ Phis :: list({beam_ssa:argument(), beam_ssa:label()}),
+ Id :: beam_ssa:label(),
+ I :: beam_ssa:b_set(),
+ State :: #vvars{}.
vvars_assert_phi_paths(Phis, I, Id, State) ->
BranchKeys = maps:keys(State#vvars.branch_def_vars),
RequiredPaths = ordsets:from_list([From || {From, To} <- BranchKeys, To =:= Id]),
@@ -173,34 +150,34 @@ vvars_assert_phi_paths(Phis, I, Id, State) ->
[_|_]=MissingPaths -> throw({missing_phi_paths, MissingPaths, I});
[] -> ok
end.
- %% %% The following test is sometimes useful to find missing optimizations.
- %% %% It is commented out, though, because it can be triggered by
- %% %% by weird but legal code.
- %% case ordsets:subtract(ProvidedPaths, RequiredPaths) of
- %% [_|_]=GarbagePaths -> throw({garbage_phi_paths, GarbagePaths, I});
- %% [] -> ok
- %% end.
+%% %% The following test is sometimes useful to find missing optimizations.
+%% %% It is commented out, though, because it can be triggered by
+%% %% by weird but legal code.
+%% case ordsets:subtract(ProvidedPaths, RequiredPaths) of
+%% [_|_]=GarbagePaths -> throw({garbage_phi_paths, GarbagePaths, I});
+%% [] -> ok
+%% end.
%% Checks whether all variables used in this phi node are defined in the branch
%% they arrived on.
-spec vvars_assert_phi_vars(Phis, I, Id, State) -> ok when
- Phis :: list({beam_ssa:argument(), beam_ssa:label()}),
- Id :: beam_ssa:label(),
- I :: beam_ssa:b_set(),
- State :: #vvars{}.
+ Phis :: list({beam_ssa:argument(), beam_ssa:label()}),
+ Id :: beam_ssa:label(),
+ I :: beam_ssa:b_set(),
+ State :: #vvars{}.
vvars_assert_phi_vars(Phis, I, Id, #vvars{blocks=Blocks,
branch_def_vars=BranchDefVars}) ->
Vars = [{Var, From} || {#b_var{}=Var, From} <- Phis],
- foreach(fun({#b_var{name=VarName}, From}) ->
+ foreach(fun({Var, From}) ->
BranchKey = {From, Id},
case BranchDefVars of
#{BranchKey:=DefVars} ->
- case gb_sets:is_member(VarName, DefVars) of
+ case gb_sets:is_member(Var, DefVars) of
true -> ok;
- false -> throw({unknown_variable, VarName, I})
+ false -> throw({unknown_variable, Var, I})
end;
#{} ->
- throw({unknown_phi_variable, VarName, BranchKey, I})
+ throw({unknown_phi_variable, Var, BranchKey, I})
end
end, Vars),
Labels = [From || {#b_literal{},From} <- Phis],
@@ -214,32 +191,73 @@ vvars_assert_phi_vars(Phis, I, Id, #vvars{blocks=Blocks,
end, Labels).
-spec vvars_block(Id, State) -> #vvars{} when
- Id :: beam_ssa:label(),
- State :: #vvars{}.
+ Id :: beam_ssa:label(),
+ State :: #vvars{}.
vvars_block(Id, State0) ->
#{ Id := #b_blk{ is = Is, last = Terminator} } = State0#vvars.blocks,
#{ Id := DefVars } = State0#vvars.branch_def_vars,
+ validate_normalized(Terminator),
State = State0#vvars{ defined_vars = DefVars },
- vvars_terminator(Terminator, Id, vvars_block_1(Is, State)).
+ vvars_terminator(Terminator, Id, vvars_block_1(Is, Terminator, State)).
--spec vvars_block_1(Blocks, State) -> #vvars{} when
- Blocks :: list(beam_ssa:b_blk()),
- State :: #vvars{}.
-vvars_block_1([], State) ->
- State;
-vvars_block_1([#b_set{ dst = #b_var{ name = DstName }, op = phi } | Is], State0) ->
+validate_normalized(I) ->
+ case beam_ssa:normalize(I) of
+ I ->
+ ok;
+ _ ->
+ %% Some denormalized forms of #b_br{} can confuse
+ %% beam_ssa_pre_codegen and/or beam_ssa_codegen and cause
+ %% the compiler to crash. Here is an example of a denormalized
+ %% br that may cause the compiler to crash:
+ %%
+ %% br bool, ^99, ^99
+ throw({not_normalized, I})
+ end.
+
+-spec vvars_block_1(Is, Terminator, State) -> #vvars{} when
+ Is :: list(#b_set{}),
+ Terminator :: beam_ssa:terminator(),
+ State :: #vvars{}.
+vvars_block_1([#b_set{dst=OpVar,args=OpArgs}=I,
+ #b_set{op={succeeded,Kind},args=[OpVar],dst=SuccVar}],
+ Terminator, State) ->
+ true = Kind =:= guard orelse Kind =:= body, %Assertion.
+ case Terminator of
+ #b_br{bool=#b_var{}} ->
+ ok = vvars_assert_args(OpArgs, I, State),
+ vvars_save_var(SuccVar, vvars_save_var(OpVar, State));
+ _ when Kind =:= body ->
+ ok = vvars_assert_args(OpArgs, I, State),
+ vvars_save_var(SuccVar, vvars_save_var(OpVar, State));
+ _ ->
+ %% A succeeded:guard instruction must be followed by a
+ %% two-way branch; otherwise beam_ssa_codegen will crash.
+ throw({succeeded_not_followed_by_two_way_br, I})
+ end;
+vvars_block_1([#b_set{op={succeeded,guard},args=Args}=I | [_|_]],
+ _Terminator, State) ->
+ ok = vvars_assert_args(Args, I, State),
+ %% 'succeeded' must be the last instruction in its block.
+ throw({succeeded_not_last, I});
+vvars_block_1([#b_set{op={succeeded,_},args=Args}=I], _Terminator, State) ->
+ ok = vvars_assert_args(Args, I, State),
+ %% 'succeeded' must be directly preceded by the operation it checks.
+ throw({succeeded_not_preceded, I});
+vvars_block_1([#b_set{ dst = Dst, op = phi } | Is], Terminator, State) ->
%% We don't check phi node arguments at this point since we may not have
%% visited their definition yet. They'll be handled later on in
%% vvars_phi_nodes/1 after all blocks are processed.
- vvars_block_1(Is, vvars_save_var(DstName, State0));
-vvars_block_1([#b_set{ dst = #b_var{ name = DstName }, args = Args }=I | Is], State0) ->
- ok = vvars_assert_args(Args, I, State0),
- vvars_block_1(Is, vvars_save_var(DstName, State0)).
+ vvars_block_1(Is, Terminator, vvars_save_var(Dst, State));
+vvars_block_1([#b_set{ dst = Dst, args = Args }=I | Is], Terminator, State) ->
+ ok = vvars_assert_args(Args, I, State),
+ vvars_block_1(Is, Terminator, vvars_save_var(Dst, State));
+vvars_block_1([], _Terminator, State) ->
+ State.
-spec vvars_terminator(Terminator, From, State) -> #vvars{} when
- Terminator :: beam_ssa:terminator(),
- From :: beam_ssa:label(),
- State :: #vvars{}.
+ Terminator :: beam_ssa:terminator(),
+ From :: beam_ssa:label(),
+ State :: #vvars{}.
vvars_terminator(#b_ret{ arg = Arg }=I, _From, State) ->
ok = vvars_assert_args([Arg], I, State),
State;
@@ -264,62 +282,62 @@ vvars_terminator(#b_br{ bool = Arg, succ = Succ, fail = Fail }=I, From, State) -
vvars_terminator_1(Labels, From, State).
-spec vvars_terminator_1(Labels, From, State) -> #vvars{} when
- Labels :: list(beam_ssa:label()),
- From :: beam_ssa:label(),
- State :: #vvars{}.
+ Labels :: list(beam_ssa:label()),
+ From :: beam_ssa:label(),
+ State :: #vvars{}.
vvars_terminator_1(Labels0, From, State0) ->
%% Filter out all branches that have already been taken. This should result
%% in either all of Labels0 or an empty list.
Labels = [To || To <- Labels0,
- not maps:is_key({From, To}, State0#vvars.branch_def_vars)],
+ not maps:is_key({From, To}, State0#vvars.branch_def_vars)],
true = Labels =:= Labels0 orelse Labels =:= [], %Assertion
State1 = foldl(fun(To, State) ->
- vvars_save_branch(From, To, State)
+ vvars_save_branch(From, To, State)
end, State0, Labels),
foldl(fun(To, State) ->
- vvars_block(To, State)
+ vvars_block(To, State)
end, State1, Labels).
%% Gets all variable names in args, ignoring literals etc
--spec vvars_get_varnames(Args) -> list(beam_ssa:var_name()) when
- Args :: list(beam_ssa:argument()).
-vvars_get_varnames(Args) ->
- [Name || #b_var{ name = Name } <- Args].
+-spec vvars_get_variables(Args) -> list(beam_ssa:b_var()) when
+ Args :: list(beam_ssa:argument()).
+vvars_get_variables(Args) ->
+ [Var || #b_var{}=Var <- Args].
%% Checks that all variables in Args are defined in all paths leading to the
%% current State.
-spec vvars_assert_args(Args, I, State) -> ok when
- Args :: list(beam_ssa:argument()),
- I :: beam_ssa:terminator() | beam_ssa:b_set(),
- State :: #vvars{}.
+ Args :: list(beam_ssa:argument()),
+ I :: beam_ssa:terminator() | beam_ssa:b_set(),
+ State :: #vvars{}.
vvars_assert_args(Args, I, #vvars{defined_vars=DefVars}=State) ->
foreach(fun(#b_remote{mod=Mod,name=Name}) ->
vvars_assert_args([Mod,Name], I, State);
- (#b_var{name=Name}) ->
- case gb_sets:is_member(Name, DefVars) of
+ (#b_var{}=Var) ->
+ case gb_sets:is_member(Var, DefVars) of
true -> ok;
- false -> throw({unknown_variable,Name,I})
+ false -> throw({unknown_variable,Var,I})
end;
(_) -> ok
end, Args).
%% Checks that all given labels are defined in State.
-spec vvars_assert_labels(Labels, I, State) -> ok when
- Labels :: list(beam_ssa:label()),
- I :: beam_ssa:terminator(),
- State :: #vvars{}.
+ Labels :: list(beam_ssa:label()),
+ I :: beam_ssa:terminator(),
+ State :: #vvars{}.
vvars_assert_labels(Labels, I, #vvars{blocks=Blocks}) ->
foreach(fun(Label) ->
- case maps:is_key(Label, Blocks) of
- false -> throw({unknown_block, Label, I});
- true -> ok
- end
+ case maps:is_key(Label, Blocks) of
+ false -> throw({unknown_block, Label, I});
+ true -> ok
+ end
end, Labels).
-spec vvars_save_branch(From, To, State) -> #vvars{} when
- From :: beam_ssa:label(),
- To :: beam_ssa:label(),
- State :: #vvars{}.
+ From :: beam_ssa:label(),
+ To :: beam_ssa:label(),
+ State :: #vvars{}.
vvars_save_branch(From, To, State) ->
DefVars = State#vvars.defined_vars,
Branches0 = State#vvars.branch_def_vars,
@@ -335,15 +353,56 @@ vvars_save_branch(From, To, State) ->
end.
-spec vvars_merge_branches(New, Existing) -> defined_vars() when
- New :: defined_vars(),
- Existing :: defined_vars().
+ New :: defined_vars(),
+ Existing :: defined_vars().
vvars_merge_branches(New, Existing) ->
gb_sets:intersection(New, Existing).
--spec vvars_save_var(VarName, State) -> #vvars{} when
- VarName :: beam_ssa:var_name(),
- State :: #vvars{}.
-vvars_save_var(VarName, State0) ->
+-spec vvars_save_var(Var, State) -> #vvars{} when
+ Var :: #b_var{},
+ State :: #vvars{}.
+vvars_save_var(Var, State0) ->
%% vvars_assert_unique guarantees that variables are never set twice.
- DefVars = gb_sets:insert(VarName, State0#vvars.defined_vars),
+ DefVars = gb_sets:insert(Var, State0#vvars.defined_vars),
State0#vvars{ defined_vars = DefVars }.
+
+
+format_error_1({redefined_variable, Name, Old, I}) ->
+ io_lib:format("Variable ~ts (~ts) redefined by ~ts",
+ [format_var(Name), format_instr(Old), format_instr(I)]);
+format_error_1({missing_phi_paths, Paths, I}) ->
+ io_lib:format("Phi node ~ts doesn't define a value for these "
+ "branches: ~w",
+ [format_instr(I), Paths]);
+format_error_1({garbage_phi_paths, Paths, I}) ->
+ io_lib:format("Phi node ~ts defines a value for these unreachable "
+ "or non-existent branches: ~w",
+ [format_instr(I), Paths]);
+format_error_1({unknown_phi_variable, Name, {From, _To}, I}) ->
+ io_lib:format("Variable ~ts used in phi node ~ts is undefined on "
+ "branch ~w",
+ [format_var(Name), format_instr(I), From]);
+format_error_1({unknown_block, Label, I}) ->
+ io_lib:format("Unknown block ~p referenced in ~ts",
+ [Label, I]);
+format_error_1({unknown_variable, Name, I}) ->
+ io_lib:format("Unbound variable ~ts used in ~ts",
+ [format_var(Name), format_instr(I)]);
+format_error_1({phi_inside_block, Name, Id}) ->
+ io_lib:format("Phi node defining ~ts is not at start of block ~p",
+ [format_var(Name), Id]);
+format_error_1({undefined_label_in_phi, Label, I}) ->
+ io_lib:format("Unknown block label ~p in phi node ~ts",
+ [Label, format_instr(I)]);
+format_error_1({succeeded_not_preceded, I}) ->
+ io_lib:format("~ts does not reference the preceding instruction",
+ [format_instr(I)]);
+format_error_1({succeeded_not_last, I}) ->
+ io_lib:format("~ts is not the last instruction in its block",
+ [format_instr(I)]);
+format_error_1({not_normalized, I}) ->
+ io_lib:format("~ts is not normalized by beam_ssa:normalize/1",
+ [format_instr(I)]);
+format_error_1({succeeded_not_followed_by_two_way_br, I}) ->
+ io_lib:format("~ts not followed by a two-way branch",
+ [format_instr(I)]).
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index 047e4b0c59..787408a894 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -28,9 +28,9 @@
%%% in one phase and then apply it in the next without having to risk working
%%% with incomplete information.
%%%
-%%% Each sub-pass operates on a #st{} record and a func_info_db(), where the
-%%% former is just a #b_function{} whose blocks can be represented either in
-%%% linear or map form, and the latter is a map with information about all
+%%% Each sub-pass operates on a #opt_st{} record and a func_info_db(), where
+%%% the former is just a #b_function{} whose blocks can be represented either
+%%% in linear or map form, and the latter is a map with information about all
%%% functions in the module (see beam_ssa_opt.hrl for more details).
%%%
@@ -39,45 +39,77 @@
-include("beam_ssa_opt.hrl").
--import(lists, [all/2,append/1,duplicate/2,foldl/3,keyfind/3,member/2,
- reverse/1,reverse/2,
+-import(lists, [all/2,append/1,duplicate/2,flatten/1,foldl/3,
+ keyfind/3,last/1,mapfoldl/3,member/2,
+ partition/2,reverse/1,reverse/2,
splitwith/2,sort/1,takewhile/2,unzip/1]).
--define(DEFAULT_REPETITIONS, 2).
+-define(MAX_REPETITIONS, 16).
-spec module(beam_ssa:b_module(), [compile:option()]) ->
{'ok',beam_ssa:b_module()}.
--record(st, {ssa :: [{beam_ssa:label(),beam_ssa:b_blk()}] |
- beam_ssa:block_map(),
- args :: [beam_ssa:b_var()],
- cnt :: beam_ssa:label(),
- anno :: beam_ssa:anno()}).
--type st_map() :: #{ func_id() => #st{} }.
-
module(Module, Opts) ->
- FuncDb0 = case proplists:get_value(no_module_opt, Opts, false) of
- false -> build_func_db(Module);
- true -> #{}
- end,
+ FuncDb = case proplists:get_value(no_module_opt, Opts, false) of
+ false -> build_func_db(Module);
+ true -> #{}
+ end,
%% Passes that perform module-level optimizations are often aided by
%% optimizing callers before callees and vice versa, so we optimize all
- %% functions in call order, flipping it as required.
+ %% functions in call order, alternating the order every time.
StMap0 = build_st_map(Module),
- Order = get_call_order_po(StMap0, FuncDb0),
-
- Phases =
- [{Order, prologue_passes(Opts)}] ++
- repeat(Opts, repeated_passes(Opts), Order) ++
- [{Order, epilogue_passes(Opts)}],
+ Order = get_call_order_po(StMap0, FuncDb),
- {StMap, _FuncDb} = foldl(fun({FuncIds, Ps}, {StMap, FuncDb}) ->
- phase(FuncIds, Ps, StMap, FuncDb)
- end, {StMap0, FuncDb0}, Phases),
+ Phases = [{once, Order, prologue_passes(Opts)},
+ {module, module_passes(Opts)},
+ {fixpoint, Order, repeated_passes(Opts)},
+ {once, Order, epilogue_passes(Opts)}],
+ StMap = run_phases(Phases, StMap0, FuncDb),
{ok, finish(Module, StMap)}.
+run_phases([{module, Passes} | Phases], StMap0, FuncDb0) ->
+ {StMap, FuncDb} = compile:run_sub_passes(Passes, {StMap0, FuncDb0}),
+ run_phases(Phases, StMap, FuncDb);
+run_phases([{once, FuncIds0, Passes} | Phases], StMap0, FuncDb0) ->
+ FuncIds = skip_removed(FuncIds0, StMap0),
+ {StMap, FuncDb} = phase(FuncIds, Passes, StMap0, FuncDb0),
+ run_phases(Phases, StMap, FuncDb);
+run_phases([{fixpoint, FuncIds0, Passes} | Phases], StMap0, FuncDb0) ->
+ FuncIds = skip_removed(FuncIds0, StMap0),
+ RevFuncIds = reverse(FuncIds),
+ Order = {FuncIds, RevFuncIds},
+ {StMap, FuncDb} = fixpoint(RevFuncIds, Order, Passes,
+ StMap0, FuncDb0, ?MAX_REPETITIONS),
+ run_phases(Phases, StMap, FuncDb);
+run_phases([], StMap, _FuncDb) ->
+ StMap.
+
+skip_removed(FuncIds, StMap) ->
+ [F || F <- FuncIds, is_map_key(F, StMap)].
+
+%% Run the given passes until a fixpoint is reached.
+fixpoint(_FuncIds, _Order, _Passes, StMap, FuncDb, 0) ->
+ %% Too many repetitions. Give up and return what we have.
+ {StMap, FuncDb};
+fixpoint(FuncIds0, Order0, Passes, StMap0, FuncDb0, N) ->
+ {StMap, FuncDb} = phase(FuncIds0, Passes, StMap0, FuncDb0),
+ Repeat = changed(FuncIds0, FuncDb0, FuncDb, StMap0, StMap),
+ case cerl_sets:size(Repeat) of
+ 0 ->
+ %% No change. Fixpoint reached.
+ {StMap, FuncDb};
+ _ ->
+ %% Repeat the optimizations for functions whose code has
+ %% changed or for which there is potentially updated type
+ %% information.
+ {OrderA, OrderB} = Order0,
+ Order = {OrderB, OrderA},
+ FuncIds = [Id || Id <- OrderA, cerl_sets:is_element(Id, Repeat)],
+ fixpoint(FuncIds, Order, Passes, StMap, FuncDb, N - 1)
+ end.
+
phase([FuncId | Ids], Ps, StMap, FuncDb0) ->
try compile:run_sub_passes(Ps, {map_get(FuncId, StMap), FuncDb0}) of
{St, FuncDb} ->
@@ -91,19 +123,94 @@ phase([FuncId | Ids], Ps, StMap, FuncDb0) ->
phase([], _Ps, StMap, FuncDb) ->
{StMap, FuncDb}.
-%% Repeats the given passes, alternating the order between runs to make the
-%% type pass more efficient.
-repeat(Opts, Ps, OrderA) ->
- Repeat = proplists:get_value(ssa_opt_repeat, Opts, ?DEFAULT_REPETITIONS),
- OrderB = reverse(OrderA),
- repeat_1(Repeat, Ps, OrderA, OrderB).
-
-repeat_1(0, _Opts, _OrderA, _OrderB) ->
- [];
-repeat_1(N, Ps, OrderA, OrderB) when N > 0, N rem 2 =:= 0 ->
- [{OrderA, Ps} | repeat_1(N - 1, Ps, OrderA, OrderB)];
-repeat_1(N, Ps, OrderA, OrderB) when N > 0, N rem 2 =:= 1 ->
- [{OrderB, Ps} | repeat_1(N - 1, Ps, OrderA, OrderB)].
+changed(PrevIds, FuncDb0, FuncDb, StMap0, StMap) ->
+ %% Find all functions in FuncDb that can be reached by changes
+ %% of argument and/or return types. Those are the functions that
+ %% may gain from running the optimization passes again.
+ %%
+ %% Note that we examine all functions in FuncDb, not only functions
+ %% optimized in the previous run, because the argument types can
+ %% have been updated for functions not included in the previous run.
+
+ F = fun(Id, A) ->
+ case cerl_sets:is_element(Id, A) of
+ true ->
+ A;
+ false ->
+ {#func_info{arg_types=ATs0,succ_types=ST0},
+ #func_info{arg_types=ATs1,succ_types=ST1}} =
+ {map_get(Id, FuncDb0),map_get(Id, FuncDb)},
+
+ %% If the argument types have changed for this
+ %% function, re-optimize this function and all
+ %% functions it calls directly or indirectly.
+ %%
+ %% If the return type has changed, re-optimize
+ %% this function and all functions that call
+ %% this function directly or indirectly.
+ Opts = case ATs0 =:= ATs1 of
+ true -> [];
+ false -> [called]
+ end ++
+ case ST0 =:= ST1 of
+ true -> [];
+ false -> [callers]
+ end,
+ case Opts of
+ [] -> A;
+ [_|_] -> add_changed([Id], Opts, FuncDb, A)
+ end
+ end
+ end,
+ Ids = foldl(F, cerl_sets:new(), maps:keys(FuncDb)),
+
+ %% From all functions that were optimized in the previous run,
+ %% find the functions that had any change in the SSA code. Those
+ %% functions might gain from being optimized again. (For example,
+ %% when beam_ssa_dead has shortcut branches, the types for some
+ %% variables could become narrower, giving beam_ssa_type new
+ %% opportunities for optimization.)
+ %%
+ %% Note that the functions examined could be functions with module-level
+ %% optimization turned off (and thus not included in FuncDb).
+
+ foldl(fun(Id, A) ->
+ case cerl_sets:is_element(Id, A) of
+ true ->
+ %% Already scheduled for another optimization.
+ %% No need to compare the SSA code.
+ A;
+ false ->
+ %% Compare the SSA code before and after optimization.
+ case {map_get(Id, StMap0),map_get(Id, StMap)} of
+ {Same,Same} -> A;
+ {_,_} -> cerl_sets:add_element(Id, A)
+ end
+ end
+ end, Ids, PrevIds).
+
+add_changed([Id|Ids], Opts, FuncDb, S0) when is_map_key(Id, FuncDb) ->
+ case cerl_sets:is_element(Id, S0) of
+ true ->
+ add_changed(Ids, Opts, FuncDb, S0);
+ false ->
+ S1 = cerl_sets:add_element(Id, S0),
+ #func_info{in=In,out=Out} = map_get(Id, FuncDb),
+ S2 = case member(callers, Opts) of
+ true -> add_changed(In, Opts, FuncDb, S1);
+ false -> S1
+ end,
+ S = case member(called, Opts) of
+ true -> add_changed(Out, Opts, FuncDb, S2);
+ false -> S2
+ end,
+ add_changed(Ids, Opts, FuncDb, S)
+ end;
+add_changed([_|Ids], Opts, FuncDb, S) ->
+ %% This function is exempt from module-level optimization and will not
+ %% provide any more information.
+ add_changed(Ids, Opts, FuncDb, S);
+add_changed([], _, _, S) -> S.
%%
@@ -117,7 +224,7 @@ build_st_map(#b_module{body=Fs}) ->
build_st_map_1([F | Fs], Map) ->
#b_function{anno=Anno,args=Args,cnt=Counter,bs=Bs} = F,
- St = #st{anno=Anno,args=Args,cnt=Counter,ssa=Bs},
+ St = #opt_st{anno=Anno,args=Args,cnt=Counter,ssa=Bs},
build_st_map_1(Fs, Map#{ get_func_id(F) => St });
build_st_map_1([], Map) ->
Map.
@@ -127,9 +234,14 @@ finish(#b_module{body=Fs0}=Module, StMap) ->
Module#b_module{body=finish_1(Fs0, StMap)}.
finish_1([F0 | Fs], StMap) ->
- #st{anno=Anno,cnt=Counter,ssa=Blocks} = map_get(get_func_id(F0), StMap),
- F = F0#b_function{anno=Anno,bs=Blocks,cnt=Counter},
- [F | finish_1(Fs, StMap)];
+ FuncId = get_func_id(F0),
+ case StMap of
+ #{ FuncId := #opt_st{anno=Anno,cnt=Counter,ssa=Blocks} } ->
+ F = F0#b_function{anno=Anno,bs=Blocks,cnt=Counter},
+ [F | finish_1(Fs, StMap)];
+ #{} ->
+ finish_1(Fs, StMap)
+ end;
finish_1([], _StMap) ->
[].
@@ -145,18 +257,30 @@ prologue_passes(Opts) ->
?PASS(ssa_opt_linearize),
?PASS(ssa_opt_tuple_size),
?PASS(ssa_opt_record),
- ?PASS(ssa_opt_cse), %Helps the first type pass.
- ?PASS(ssa_opt_type_start)],
+ ?PASS(ssa_opt_cse), % Helps the first type pass.
+ ?PASS(ssa_opt_live), % ...
+ ?PASS(ssa_opt_receive_after)],
passes_1(Ps, Opts).
+module_passes(Opts) ->
+ Ps0 = [{ssa_opt_type_start,
+ fun({StMap, FuncDb}) ->
+ beam_ssa_type:opt_start(StMap, FuncDb)
+ end}],
+ passes_1(Ps0, Opts).
+
%% These passes all benefit from each other (in roughly this order), so they
%% are repeated as required.
repeated_passes(Opts) ->
Ps = [?PASS(ssa_opt_live),
+ ?PASS(ssa_opt_ne),
?PASS(ssa_opt_bs_puts),
?PASS(ssa_opt_dead),
?PASS(ssa_opt_cse),
?PASS(ssa_opt_tail_phis),
+ ?PASS(ssa_opt_sink),
+ ?PASS(ssa_opt_tuple_size),
+ ?PASS(ssa_opt_record),
?PASS(ssa_opt_type_continue)], %Must run after ssa_opt_dead to
%clean up phi nodes.
passes_1(Ps, Opts).
@@ -165,18 +289,20 @@ epilogue_passes(Opts) ->
Ps = [?PASS(ssa_opt_type_finish),
?PASS(ssa_opt_float),
?PASS(ssa_opt_sw),
+ ?PASS(ssa_opt_try),
- %% Run live one more time to clean up after the float and sw
- %% passes.
+ %% Run live one more time to clean up after the previous
+ %% epilogue passes.
?PASS(ssa_opt_live),
?PASS(ssa_opt_bsm),
- ?PASS(ssa_opt_bsm_units),
?PASS(ssa_opt_bsm_shortcut),
- ?PASS(ssa_opt_blockify),
?PASS(ssa_opt_sink),
+ ?PASS(ssa_opt_blockify),
?PASS(ssa_opt_merge_blocks),
?PASS(ssa_opt_get_tuple_element),
- ?PASS(ssa_opt_trim_unreachable)],
+ ?PASS(ssa_opt_tail_calls),
+ ?PASS(ssa_opt_trim_unreachable),
+ ?PASS(ssa_opt_unfold_literals)],
passes_1(Ps, Opts).
passes_1(Ps, Opts0) ->
@@ -194,16 +320,26 @@ passes_1(Ps, Opts0) ->
%% Builds a function information map with basic information about incoming and
%% outgoing local calls, as well as whether the function is exported.
-spec build_func_db(#b_module{}) -> func_info_db().
-build_func_db(#b_module{body=Fs,exports=Exports}) ->
+build_func_db(#b_module{body=Fs,attributes=Attr,exports=Exports0}) ->
+ Exports = fdb_exports(Attr, Exports0),
try
- fdb_1(Fs, gb_sets:from_list(Exports), #{})
+ fdb_fs(Fs, Exports, #{})
catch
%% All module-level optimizations are invalid when a NIF can override a
%% function, so we have to bail out.
throw:load_nif -> #{}
end.
-fdb_1([#b_function{ args=Args,bs=Bs }=F | Fs], Exports, FuncDb0) ->
+fdb_exports([{on_load, L} | Attrs], Exports) ->
+ %% Functions marked with on_load must be treated as exported to prevent
+ %% them from being optimized away when unused.
+ fdb_exports(Attrs, flatten(L) ++ Exports);
+fdb_exports([_Attr | Attrs], Exports) ->
+ fdb_exports(Attrs, Exports);
+fdb_exports([], Exports) ->
+ gb_sets:from_list(Exports).
+
+fdb_fs([#b_function{ args=Args,bs=Bs }=F | Fs], Exports, FuncDb0) ->
Id = get_func_id(F),
#b_local{name=#b_literal{val=Name}, arity=Arity} = Id,
@@ -224,8 +360,8 @@ fdb_1([#b_function{ args=Args,bs=Bs }=F | Fs], Exports, FuncDb0) ->
fdb_is(Is, Id, FuncDb)
end, FuncDb1, Bs),
- fdb_1(Fs, Exports, FuncDb);
-fdb_1([], _Exports, FuncDb) ->
+ fdb_fs(Fs, Exports, FuncDb);
+fdb_fs([], _Exports, FuncDb) ->
FuncDb.
fdb_is([#b_set{op=call,
@@ -237,6 +373,12 @@ fdb_is([#b_set{op=call,
name=#b_literal{val=load_nif}},
_Path, _LoadInfo]} | _Is], _Caller, _FuncDb) ->
throw(load_nif);
+fdb_is([#b_set{op=make_fun,
+ args=[#b_local{}=Callee | _]} | Is],
+ Caller, FuncDb) ->
+ %% The make_fun instruction's type depends on the return type of the
+ %% function in question, so we treat this as a function call.
+ fdb_is(Is, Caller, fdb_update(Caller, Callee, FuncDb));
fdb_is([_ | Is], Caller, FuncDb) ->
fdb_is(Is, Caller, FuncDb);
fdb_is([], _Caller, FuncDb) ->
@@ -289,29 +431,29 @@ gco_rpo([], _, Seen, Acc) ->
%%% Trivial sub passes.
%%%
-ssa_opt_dead({#st{ssa=Linear}=St, FuncDb}) ->
- {St#st{ssa=beam_ssa_dead:opt(Linear)}, FuncDb}.
+ssa_opt_dead({#opt_st{ssa=Linear}=St, FuncDb}) ->
+ {St#opt_st{ssa=beam_ssa_dead:opt(Linear)}, FuncDb}.
-ssa_opt_linearize({#st{ssa=Blocks}=St, FuncDb}) ->
- {St#st{ssa=beam_ssa:linearize(Blocks)}, FuncDb}.
+ssa_opt_linearize({#opt_st{ssa=Blocks}=St, FuncDb}) ->
+ {St#opt_st{ssa=beam_ssa:linearize(Blocks)}, FuncDb}.
-ssa_opt_type_start({#st{ssa=Linear0,args=Args,anno=Anno}=St0, FuncDb0}) ->
- {Linear, FuncDb} = beam_ssa_type:opt_start(Linear0, Args, Anno, FuncDb0),
- {St0#st{ssa=Linear}, FuncDb}.
-
-ssa_opt_type_continue({#st{ssa=Linear0,args=Args,anno=Anno}=St0, FuncDb0}) ->
+ssa_opt_type_continue({#opt_st{ssa=Linear0,args=Args,anno=Anno}=St0, FuncDb0}) ->
{Linear, FuncDb} = beam_ssa_type:opt_continue(Linear0, Args, Anno, FuncDb0),
- {St0#st{ssa=Linear}, FuncDb}.
+ {St0#opt_st{ssa=Linear}, FuncDb}.
-ssa_opt_type_finish({#st{args=Args,anno=Anno0}=St0, FuncDb0}) ->
+ssa_opt_type_finish({#opt_st{args=Args,anno=Anno0}=St0, FuncDb0}) ->
{Anno, FuncDb} = beam_ssa_type:opt_finish(Args, Anno0, FuncDb0),
- {St0#st{anno=Anno}, FuncDb}.
+ {St0#opt_st{anno=Anno}, FuncDb}.
+
+ssa_opt_blockify({#opt_st{ssa=Linear}=St, FuncDb}) ->
+ {St#opt_st{ssa=maps:from_list(Linear)}, FuncDb}.
-ssa_opt_blockify({#st{ssa=Linear}=St, FuncDb}) ->
- {St#st{ssa=maps:from_list(Linear)}, FuncDb}.
+ssa_opt_trim_unreachable({#opt_st{ssa=Blocks}=St, FuncDb}) ->
+ {St#opt_st{ssa=beam_ssa:trim_unreachable(Blocks)}, FuncDb}.
-ssa_opt_trim_unreachable({#st{ssa=Blocks}=St, FuncDb}) ->
- {St#st{ssa=beam_ssa:trim_unreachable(Blocks)}, FuncDb}.
+ssa_opt_merge_blocks({#opt_st{ssa=Blocks0}=St, FuncDb}) ->
+ Blocks = beam_ssa:merge_blocks(Blocks0),
+ {St#opt_st{ssa=Blocks}, FuncDb}.
%%%
%%% Split blocks before certain instructions to enable more optimizations.
@@ -323,14 +465,14 @@ ssa_opt_trim_unreachable({#st{ssa=Blocks}=St, FuncDb}) ->
%%% for sinking get_tuple_element instructions.
%%%
-ssa_opt_split_blocks({#st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) ->
+ssa_opt_split_blocks({#opt_st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) ->
P = fun(#b_set{op={bif,element}}) -> true;
(#b_set{op=call}) -> true;
(#b_set{op=make_fun}) -> true;
(_) -> false
end,
{Blocks,Count} = beam_ssa:split_blocks(P, Blocks0, Count0),
- {St#st{ssa=Blocks,cnt=Count}, FuncDb}.
+ {St#opt_st{ssa=Blocks,cnt=Count}, FuncDb}.
%%%
%%% Coalesce phi nodes.
@@ -354,10 +496,10 @@ ssa_opt_split_blocks({#st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) ->
%%% different registers).
%%%
-ssa_opt_coalesce_phis({#st{ssa=Blocks0}=St, FuncDb}) ->
+ssa_opt_coalesce_phis({#opt_st{ssa=Blocks0}=St, FuncDb}) ->
Ls = beam_ssa:rpo(Blocks0),
Blocks = c_phis_1(Ls, Blocks0),
- {St#st{ssa=Blocks}, FuncDb}.
+ {St#opt_st{ssa=Blocks}, FuncDb}.
c_phis_1([L|Ls], Blocks0) ->
case map_get(L, Blocks0) of
@@ -460,9 +602,9 @@ c_fix_branches([], _, Blocks) -> Blocks.
%%% - Smaller stack frames
%%%
-ssa_opt_tail_phis({#st{ssa=SSA0,cnt=Count0}=St, FuncDb}) ->
+ssa_opt_tail_phis({#opt_st{ssa=SSA0,cnt=Count0}=St, FuncDb}) ->
{SSA,Count} = opt_tail_phis(SSA0, Count0),
- {St#st{ssa=SSA,cnt=Count}, FuncDb}.
+ {St#opt_st{ssa=SSA,cnt=Count}, FuncDb}.
opt_tail_phis(Blocks, Count) when is_map(Blocks) ->
opt_tail_phis(maps:values(Blocks), Blocks, Count);
@@ -514,28 +656,15 @@ reduce_phis([]) -> [].
opt_tail_phi_arg({PredL,Sub0}, Is0, Ret0, {Blocks0,Count0,Cost0}) ->
Blk0 = map_get(PredL, Blocks0),
#b_blk{is=IsPrefix,last=#b_br{succ=Next,fail=Next}} = Blk0,
- case is_exit_bif(IsPrefix) of
- false ->
- Sub1 = maps:from_list(Sub0),
- {Is1,Count,Sub} = new_names(Is0, Sub1, Count0, []),
- Is2 = [sub(I, Sub) || I <- Is1],
- Cost = build_cost(Is2, Cost0),
- Is = IsPrefix ++ Is2,
- Ret = sub(Ret0, Sub),
- Blk = Blk0#b_blk{is=Is,last=Ret},
- Blocks = Blocks0#{PredL:=Blk},
- {Blocks,Count,Cost};
- true ->
- %% The block ends in a call to a function that
- %% will cause an exception.
- {Blocks0,Count0,Cost0+3}
- end.
-
-is_exit_bif([#b_set{op=call,
- args=[#b_remote{mod=#b_literal{val=Mod},
- name=#b_literal{val=Name}}|Args]}]) ->
- erl_bifs:is_exit_bif(Mod, Name, length(Args));
-is_exit_bif(_) -> false.
+ Sub1 = maps:from_list(Sub0),
+ {Is1,Count,Sub} = new_names(Is0, Sub1, Count0, []),
+ Is2 = [sub(I, Sub) || I <- Is1],
+ Cost = build_cost(Is2, Cost0),
+ Is = IsPrefix ++ Is2,
+ Ret = sub(Ret0, Sub),
+ Blk = Blk0#b_blk{is=Is,last=Ret},
+ Blocks = Blocks0#{PredL:=Blk},
+ {Blocks,Count,Cost}.
new_names([#b_set{dst=Dst}=I|Is], Sub0, Count0, Acc) ->
{NewDst,Count} = new_var(Dst, Count0),
@@ -591,7 +720,7 @@ are_all_literals(Args) ->
%%% be replaced with get_tuple_element/3 instructions.
%%%
-ssa_opt_element({#st{ssa=Blocks}=St, FuncDb}) ->
+ssa_opt_element({#opt_st{ssa=Blocks}=St, FuncDb}) ->
%% Collect the information about element instructions in this
%% function.
GetEls = collect_element_calls(beam_ssa:linearize(Blocks)),
@@ -603,13 +732,13 @@ ssa_opt_element({#st{ssa=Blocks}=St, FuncDb}) ->
%% For each chain, swap the first element call with the
%% element call with the highest index.
- {St#st{ssa=swap_element_calls(Chains, Blocks)}, FuncDb}.
+ {St#opt_st{ssa=swap_element_calls(Chains, Blocks)}, FuncDb}.
collect_element_calls([{L,#b_blk{is=Is0,last=Last}}|Bs]) ->
case {Is0,Last} of
{[#b_set{op={bif,element},dst=Element,
args=[#b_literal{val=N},#b_var{}=Tuple]},
- #b_set{op=succeeded,dst=Bool,args=[Element]}],
+ #b_set{op={succeeded,guard},dst=Bool,args=[Element]}],
#b_br{bool=Bool,succ=Succ,fail=Fail}} ->
Info = {L,Succ,{Tuple,Fail},N},
[Info|collect_element_calls(Bs)];
@@ -664,9 +793,9 @@ swap_element_calls_1([], _, Blocks) ->
%%% when applicable.
%%%
-ssa_opt_record({#st{ssa=Linear}=St, FuncDb}) ->
+ssa_opt_record({#opt_st{ssa=Linear}=St, FuncDb}) ->
Blocks = maps:from_list(Linear),
- {St#st{ssa=record_opt(Linear, Blocks)}, FuncDb}.
+ {St#opt_st{ssa=record_opt(Linear, Blocks)}, FuncDb}.
record_opt([{L,#b_blk{is=Is0,last=Last}=Blk0}|Bs], Blocks) ->
Is = record_opt_is(Is0, Last, Blocks),
@@ -759,9 +888,9 @@ is_tagged_tuple_4([], _, _) -> no.
%%% subexpressions across instructions that clobber the X registers.
%%%
-ssa_opt_cse({#st{ssa=Linear}=St, FuncDb}) ->
+ssa_opt_cse({#opt_st{ssa=Linear}=St, FuncDb}) ->
M = #{0=>#{}},
- {St#st{ssa=cse(Linear, #{}, M)}, FuncDb}.
+ {St#opt_st{ssa=cse(Linear, #{}, M)}, FuncDb}.
cse([{L,#b_blk{is=Is0,last=Last0}=Blk}|Bs], Sub0, M0) ->
Es0 = map_get(L, M0),
@@ -772,7 +901,7 @@ cse([{L,#b_blk{is=Is0,last=Last0}=Blk}|Bs], Sub0, M0) ->
[{L,Blk#b_blk{is=Is,last=Last}}|cse(Bs, Sub, M)];
cse([], _, _) -> [].
-cse_successors([#b_set{op=succeeded,args=[Src]},Bif|_], Blk, EsSucc, M0) ->
+cse_successors([#b_set{op={succeeded,_},args=[Src]},Bif|_], Blk, EsSucc, M0) ->
case cse_suitable(Bif) of
true ->
%% The previous instruction only has a valid value at the success branch.
@@ -810,7 +939,7 @@ cse_successors_1([L|Ls], Es0, M) ->
end;
cse_successors_1([], _, M) -> M.
-cse_is([#b_set{op=succeeded,dst=Bool,args=[Src]}=I0|Is], Es, Sub0, Acc) ->
+cse_is([#b_set{op={succeeded,_},dst=Bool,args=[Src]}=I0|Is], Es, Sub0, Acc) ->
I = sub(I0, Sub0),
case I of
#b_set{args=[Src]} ->
@@ -898,34 +1027,37 @@ cse_suitable(#b_set{}) -> false.
-record(fs,
{s=undefined :: 'undefined' | 'cleared',
regs=#{} :: #{beam_ssa:b_var():=beam_ssa:b_var()},
+ vars=cerl_sets:new() :: cerl_sets:set(),
fail=none :: 'none' | beam_ssa:label(),
non_guards :: gb_sets:set(beam_ssa:label()),
bs :: beam_ssa:block_map()
}).
-ssa_opt_float({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
+ssa_opt_float({#opt_st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
NonGuards = non_guards(Linear0),
Blocks = maps:from_list(Linear0),
Fs = #fs{non_guards=NonGuards,bs=Blocks},
{Linear,Count} = float_opt(Linear0, Count0, Fs),
- {St#st{ssa=Linear,cnt=Count}, FuncDb}.
+ {St#opt_st{ssa=Linear,cnt=Count}, FuncDb}.
-float_blk_is_in_guard(#b_blk{last=#b_br{fail=F}}, #fs{non_guards=NonGuards}) ->
- not gb_sets:is_member(F, NonGuards);
-float_blk_is_in_guard(#b_blk{}, #fs{}) ->
+%% The fconv instruction doesn't support jumping to a fail label, so we have to
+%% skip this optimization if the fail block is a guard.
+%%
+%% We also skip the optimization in blocks that always fail, as it's both
+%% difficult and pointless to rewrite them to use float ops.
+float_can_optimize_blk(#b_blk{last=#b_br{bool=#b_var{},fail=F}},
+ #fs{non_guards=NonGuards}) ->
+ gb_sets:is_member(F, NonGuards);
+float_can_optimize_blk(#b_blk{}, #fs{}) ->
false.
float_opt([{L,Blk}|Bs0], Count0, Fs) ->
- case float_blk_is_in_guard(Blk, Fs) of
+ case float_can_optimize_blk(Blk, Fs) of
true ->
- %% This block is inside a guard. Don't do
- %% any floating point optimizations.
- {Bs,Count} = float_opt(Bs0, Count0, Fs),
- {[{L,Blk}|Bs],Count};
+ float_opt_1(L, Blk, Bs0, Count0, Fs);
false ->
- %% This block is not inside a guard.
- %% We can do the optimization.
- float_opt_1(L, Blk, Bs0, Count0, Fs)
+ {Bs,Count} = float_opt(Bs0, Count0, Fs),
+ {[{L,Blk}|Bs],Count}
end;
float_opt([], Count, _Fs) ->
{[],Count}.
@@ -979,7 +1111,7 @@ float_conv([{L,#b_blk{is=Is0}=Blk0}|Bs0], Fail, Count0) ->
[#b_set{op={float,convert}}=Conv] ->
{Bool0,Count1} = new_reg('@ssa_bool', Count0),
Bool = #b_var{name=Bool0},
- Succeeded = #b_set{op=succeeded,dst=Bool,
+ Succeeded = #b_set{op={succeeded,body},dst=Bool,
args=[Conv#b_set.dst]},
Is = [Conv,Succeeded],
[{NextL,_}|_] = Bs0,
@@ -1004,12 +1136,12 @@ float_maybe_flush(Blk0, #fs{s=cleared,fail=Fail,bs=Blocks}=Fs0, Count0) ->
#b_blk{last=#b_br{bool=#b_var{},succ=Succ}=Br} = Blk0,
%% If the success block starts with a floating point operation, we can
- %% defer flushing to that block as long as it isn't a guard.
+ %% defer flushing to that block as long as it's suitable for optimization.
#b_blk{is=Is} = SuccBlk = map_get(Succ, Blocks),
- SuccIsGuard = float_blk_is_in_guard(SuccBlk, Fs0),
+ CanOptimizeSucc = float_can_optimize_blk(SuccBlk, Fs0),
case Is of
- [#b_set{anno=#{float_op:=_}}|_] when not SuccIsGuard ->
+ [#b_set{anno=#{float_op:=_}}|_] when CanOptimizeSucc ->
%% No flush needed.
{[],Blk0,Fs0,Count0};
_ ->
@@ -1041,7 +1173,7 @@ float_maybe_flush(Blk0, #fs{s=cleared,fail=Fail,bs=Blocks}=Fs0, Count0) ->
float_maybe_flush(Blk, Fs, Count) ->
{[],Blk,Fs,Count}.
-float_opt_is([#b_set{op=succeeded,args=[Src]}=I0],
+float_opt_is([#b_set{op={succeeded,_},args=[Src]}=I0],
#fs{regs=Rs}=Fs, Count, Acc) ->
case Rs of
#{Src:=Fr} ->
@@ -1064,38 +1196,40 @@ float_opt_is([], Fs, _Count, _Acc) ->
#fs{s=undefined} = Fs, %Assertion.
none.
-float_make_op(#b_set{op={bif,Op},dst=Dst,args=As0}=I0,
- Ts, #fs{s=S,regs=Rs0}=Fs, Count0) ->
- {As1,Rs1,Count1} = float_load(As0, Ts, Rs0, Count0, []),
+float_make_op(#b_set{op={bif,Op},dst=Dst,args=As0,anno=Anno}=I0,
+ Ts, #fs{s=S,regs=Rs0,vars=Vs0}=Fs, Count0) ->
+ {As1,Rs1,Count1} = float_load(As0, Ts, Anno, Rs0, Count0, []),
{As,Is0} = unzip(As1),
{Fr,Count2} = new_reg('@fr', Count1),
FrDst = #b_var{name=Fr},
I = I0#b_set{op={float,Op},dst=FrDst,args=As},
+ Vs = cerl_sets:add_element(Dst, Vs0),
Rs = Rs1#{Dst=>FrDst},
Is = append(Is0) ++ [I],
case S of
undefined ->
{Ignore,Count} = new_reg('@ssa_ignore', Count2),
C = #b_set{op={float,clearerror},dst=#b_var{name=Ignore}},
- {[C|Is],Fs#fs{s=cleared,regs=Rs},Count};
+ {[C|Is],Fs#fs{s=cleared,regs=Rs,vars=Vs},Count};
cleared ->
- {Is,Fs#fs{regs=Rs},Count2}
+ {Is,Fs#fs{regs=Rs,vars=Vs},Count2}
end.
-float_load([A|As], [T|Ts], Rs0, Count0, Acc) ->
- {Load,Rs,Count} = float_reg_arg(A, T, Rs0, Count0),
- float_load(As, Ts, Rs, Count, [Load|Acc]);
-float_load([], [], Rs, Count, Acc) ->
+float_load([A|As], [T|Ts], Anno, Rs0, Count0, Acc) ->
+ {Load,Rs,Count} = float_reg_arg(A, T, Anno, Rs0, Count0),
+ float_load(As, Ts, Anno, Rs, Count, [Load|Acc]);
+float_load([], [], _Anno, Rs, Count, Acc) ->
{reverse(Acc),Rs,Count}.
-float_reg_arg(A, T, Rs, Count0) ->
+float_reg_arg(A, T, Anno, Rs, Count0) ->
case Rs of
#{A:=Fr} ->
{{Fr,[]},Rs,Count0};
#{} ->
{Fr,Count} = new_float_copy_reg(Count0),
Dst = #b_var{name=Fr},
- I = float_load_reg(T, A, Dst),
+ I0 = float_load_reg(T, A, Dst),
+ I = I0#b_set{anno=Anno},
{{Dst,[I]},Rs#{A=>Dst},Count}
end.
@@ -1143,12 +1277,12 @@ float_flush_regs(#fs{regs=Rs}) ->
%%% with a cheaper instructions
%%%
-ssa_opt_live({#st{ssa=Linear0}=St, FuncDb}) ->
+ssa_opt_live({#opt_st{ssa=Linear0}=St, FuncDb}) ->
RevLinear = reverse(Linear0),
Blocks0 = maps:from_list(RevLinear),
Blocks = live_opt(RevLinear, #{}, Blocks0),
Linear = beam_ssa:linearize(Blocks),
- {St#st{ssa=Linear}, FuncDb}.
+ {St#opt_st{ssa=Linear}, FuncDb}.
live_opt([{L,Blk0}|Bs], LiveMap0, Blocks) ->
Blk1 = beam_ssa_share:block(Blk0, Blocks),
@@ -1208,34 +1342,31 @@ live_opt_is([#b_set{op=phi,dst=Dst}=I|Is], Live, Acc) ->
false ->
live_opt_is(Is, Live, Acc)
end;
-live_opt_is([#b_set{op=succeeded,dst=SuccDst=SuccDstVar,
- args=[Dst]}=SuccI,
- #b_set{dst=Dst}=I|Is], Live0, Acc) ->
- case gb_sets:is_member(Dst, Live0) of
- true ->
- Live1 = gb_sets:add(Dst, Live0),
- Live = gb_sets:delete_any(SuccDst, Live1),
- live_opt_is([I|Is], Live, [SuccI|Acc]);
- false ->
- case live_opt_unused(I) of
- {replace,NewI0} ->
- NewI = NewI0#b_set{dst=SuccDstVar},
- live_opt_is([NewI|Is], Live0, Acc);
- keep ->
- case gb_sets:is_member(SuccDst, Live0) of
- true ->
- Live1 = gb_sets:add(Dst, Live0),
- Live = gb_sets:delete(SuccDst, Live1),
- live_opt_is([I|Is], Live, [SuccI|Acc]);
- false ->
- live_opt_is([I|Is], Live0, Acc)
- end
- end
+live_opt_is([#b_set{op={succeeded,_},dst=SuccDst,args=[MapDst]}=SuccI,
+ #b_set{op=get_map_element,dst=MapDst}=MapI | Is],
+ Live0, Acc) ->
+ case {gb_sets:is_member(SuccDst, Live0),
+ gb_sets:is_member(MapDst, Live0)} of
+ {true, true} ->
+ Live = gb_sets:delete(SuccDst, Live0),
+ live_opt_is([MapI | Is], Live, [SuccI | Acc]);
+ {true, false} ->
+ %% 'get_map_element' is unused; replace 'succeeded' with
+ %% 'has_map_field'
+ NewI = MapI#b_set{op=has_map_field,dst=SuccDst},
+ live_opt_is([NewI | Is], Live0, Acc);
+ {false, true} ->
+ %% 'succeeded' is unused (we know it will succeed); discard it and
+ %% keep 'get_map_element'
+ live_opt_is([MapI | Is], Live0, Acc);
+ {false, false} ->
+ live_opt_is(Is, Live0, Acc)
end;
live_opt_is([#b_set{dst=Dst}=I|Is], Live0, Acc) ->
case gb_sets:is_member(Dst, Live0) of
true ->
- Live1 = gb_sets:union(Live0, gb_sets:from_ordset(beam_ssa:used(I))),
+ LiveUsed = gb_sets:from_ordset(beam_ssa:used(I)),
+ Live1 = gb_sets:union(Live0, LiveUsed),
Live = gb_sets:delete(Dst, Live1),
live_opt_is(Is, Live, [I|Acc]);
false ->
@@ -1243,16 +1374,120 @@ live_opt_is([#b_set{dst=Dst}=I|Is], Live0, Acc) ->
true ->
live_opt_is(Is, Live0, Acc);
false ->
- Live = gb_sets:union(Live0, gb_sets:from_ordset(beam_ssa:used(I))),
+ LiveUsed = gb_sets:from_ordset(beam_ssa:used(I)),
+ Live = gb_sets:union(Live0, LiveUsed),
live_opt_is(Is, Live, [I|Acc])
end
end;
live_opt_is([], Live, Acc) ->
{Acc,Live}.
-live_opt_unused(#b_set{op=get_map_element}=Set) ->
- {replace,Set#b_set{op=has_map_field}};
-live_opt_unused(_) -> keep.
+%%%
+%%% Do a strength reduction of try/catch and catch.
+%%%
+%%% In try/catch constructs where the expression is restricted
+%%% (essentially a guard expression) and the error reason is ignored
+%%% in the catch part, such as:
+%%%
+%%% try
+%%% <RestrictedExpression>
+%%% catch
+%%% _:_ ->
+%%% ...
+%%% end
+%%%
+%%% the try/catch can be eliminated by simply removing the `new_try_tag`,
+%%% `landingpad`, and `kill_try_tag` instructions.
+
+ssa_opt_try({#opt_st{ssa=Linear0}=St, FuncDb}) ->
+ Linear1 = opt_try(Linear0),
+ %% Unreachable blocks with tuple extractions will cause problems
+ %% for ssa_opt_sink.
+ Linear = beam_ssa:trim_unreachable(Linear1),
+ {St#opt_st{ssa=Linear}, FuncDb}.
+
+opt_try([{L,#b_blk{is=[#b_set{op=new_try_tag}],
+ last=Last}=Blk0}|Bs0]) ->
+ #b_br{succ=Succ,fail=Fail} = Last,
+ Ws = cerl_sets:from_list([Succ,Fail]),
+ try do_opt_try(Bs0, Ws) of
+ Bs ->
+ Blk = Blk0#b_blk{is=[],
+ last=#b_br{bool=#b_literal{val=true},
+ succ=Succ,fail=Succ}},
+ [{L,Blk}|opt_try(Bs)]
+ catch
+ throw:not_possible ->
+ [{L,Blk0}|opt_try(Bs0)]
+ end;
+opt_try([{L,Blk}|Bs]) ->
+ [{L,Blk}|opt_try(Bs)];
+opt_try([]) -> [].
+
+do_opt_try([{L,Blk}|Bs]=Bs0, Ws0) ->
+ case cerl_sets:is_element(L, Ws0) of
+ false ->
+ %% This block is not reachable from the block with the
+ %% `new_try_tag` instruction. Retain it. There is no
+ %% need to check it for safety.
+ case cerl_sets:size(Ws0) of
+ 0 -> Bs0;
+ _ -> [{L,Blk}|do_opt_try(Bs, Ws0)]
+ end;
+ true ->
+ Ws1 = cerl_sets:del_element(L, Ws0),
+ #b_blk{is=Is0} = Blk,
+ case is_safe_without_try(Is0, []) of
+ {safe,Is} ->
+ %% This block does not execute any instructions
+ %% that would require a try. Analyze successors.
+ Successors = beam_ssa:successors(Blk),
+ Ws = cerl_sets:union(cerl_sets:from_list(Successors),
+ Ws1),
+ [{L,Blk#b_blk{is=Is}}|do_opt_try(Bs, Ws)];
+ unsafe ->
+ %% There is something unsafe in the block, for
+ %% example a `call` instruction or an `extract`
+ %% instruction.
+ throw(not_possible);
+ {done,Is} ->
+ %% This block kills the try tag (either after successful
+ %% execution or at the landing pad). Don't analyze
+ %% successors.
+ [{L,Blk#b_blk{is=Is}}|do_opt_try(Bs, Ws1)]
+ end
+ end;
+do_opt_try([], Ws) ->
+ 0 = cerl_sets:size(Ws), %Assertion.
+ [].
+
+is_safe_without_try([#b_set{op=kill_try_tag}|Is], Acc) ->
+ %% Remove this kill_try_tag instruction. If there was a landingpad
+ %% instruction in this block, it has already been removed. Preserve
+ %% all other instructions in the block.
+ {done,reverse(Acc, Is)};
+is_safe_without_try([#b_set{op=extract}|_], _Acc) ->
+ %% The error reason is accessed.
+ unsafe;
+is_safe_without_try([#b_set{op=landingpad}|Is], Acc) ->
+ is_safe_without_try(Is, Acc);
+is_safe_without_try([#b_set{op={succeeded,body}}=I0|Is], Acc) ->
+ %% If we reached this point, it means that the previous instruction
+ %% has no side effects. We must now convert the flavor of the
+ %% succeeded to the `guard`, since the try/catch will be removed.
+ I = I0#b_set{op={succeeded,guard}},
+ is_safe_without_try(Is, [I|Acc]);
+is_safe_without_try([#b_set{op=Op}=I|Is], Acc) ->
+ IsSafe = case Op of
+ phi -> true;
+ _ -> beam_ssa:no_side_effect(I)
+ end,
+ case IsSafe of
+ true -> is_safe_without_try(Is, [I|Acc]);
+ false -> unsafe
+ end;
+is_safe_without_try([], Acc) ->
+ {safe,reverse(Acc)}.
%%%
%%% Optimize binary matching.
@@ -1264,10 +1499,10 @@ live_opt_unused(_) -> keep.
%%% with bs_test_tail.
%%%
-ssa_opt_bsm({#st{ssa=Linear}=St, FuncDb}) ->
+ssa_opt_bsm({#opt_st{ssa=Linear}=St, FuncDb}) ->
Extracted0 = bsm_extracted(Linear),
Extracted = cerl_sets:from_list(Extracted0),
- {St#st{ssa=bsm_skip(Linear, Extracted)}, FuncDb}.
+ {St#opt_st{ssa=bsm_skip(Linear, Extracted)}, FuncDb}.
bsm_skip([{L,#b_blk{is=Is0}=Blk}|Bs0], Extracted) ->
Bs = bsm_skip(Bs0, Extracted),
@@ -1280,7 +1515,9 @@ bsm_skip_is([I0|Is], Extracted) ->
#b_set{op=bs_match,
dst=Ctx,
args=[#b_literal{val=T}=Type,PrevCtx|Args0]}
- when T =/= string, T =/= skip ->
+ when T =/= float, T =/= string, T =/= skip ->
+ %% Note that it is never safe to skip matching
+ %% of floats, even if the size is known to be correct.
I = case cerl_sets:is_element(Ctx, Extracted) of
true ->
I0;
@@ -1327,7 +1564,7 @@ coalesce_skips_is([#b_set{op=bs_match,
Ctx0,Type,Flags,
#b_literal{val=Size0},
#b_literal{val=Unit0}]}=Skip0,
- #b_set{op=succeeded}],
+ #b_set{op={succeeded,guard}}],
#b_br{succ=L2,fail=Fail}=Br0,
Bs0) when is_integer(Size0) ->
case Bs0 of
@@ -1336,7 +1573,7 @@ coalesce_skips_is([#b_set{op=bs_match,
args=[#b_literal{val=skip},_,_,_,
#b_literal{val=Size1},
#b_literal{val=Unit1}]},
- #b_set{op=succeeded}=Succeeded],
+ #b_set{op={succeeded,guard}}=Succeeded],
last=#b_br{fail=Fail}=Br}}|Bs] when is_integer(Size1) ->
SkipBits = Size0 * Unit0 + Size1 * Unit1,
Skip = Skip0#b_set{dst=SkipDst,
@@ -1365,14 +1602,14 @@ coalesce_skips_is(_, _, _) ->
%%% Short-cutting binary matching instructions.
%%%
-ssa_opt_bsm_shortcut({#st{ssa=Linear}=St, FuncDb}) ->
+ssa_opt_bsm_shortcut({#opt_st{ssa=Linear}=St, FuncDb}) ->
Positions = bsm_positions(Linear, #{}),
case map_size(Positions) of
0 ->
%% No binary matching instructions.
{St, FuncDb};
_ ->
- {St#st{ssa=bsm_shortcut(Linear, Positions)}, FuncDb}
+ {St#opt_st{ssa=bsm_shortcut(Linear, Positions)}, FuncDb}
end.
bsm_positions([{L,#b_blk{is=Is,last=Last}}|Bs], PosMap0) ->
@@ -1416,7 +1653,7 @@ bsm_update_bits(_, Bits) -> Bits.
bsm_shortcut([{L,#b_blk{is=Is,last=Last0}=Blk}|Bs], PosMap) ->
case {Is,Last0} of
{[#b_set{op=bs_match,dst=New,args=[_,Old|_]},
- #b_set{op=succeeded,dst=Bool,args=[New]}],
+ #b_set{op={succeeded,guard},dst=Bool,args=[New]}],
#b_br{bool=Bool,fail=Fail}} ->
case PosMap of
#{Old:=Bits,Fail:={TailBits,NextFail}} when Bits > TailBits ->
@@ -1431,110 +1668,6 @@ bsm_shortcut([{L,#b_blk{is=Is,last=Last0}=Blk}|Bs], PosMap) ->
bsm_shortcut([], _PosMap) -> [].
%%%
-%%% Eliminate redundant bs_test_unit2 instructions.
-%%%
-
-ssa_opt_bsm_units({#st{ssa=Linear}=St, FuncDb}) ->
- {St#st{ssa=bsm_units(Linear, #{})}, FuncDb}.
-
-bsm_units([{L,#b_blk{last=#b_br{succ=Succ,fail=Fail}}=Block0} | Bs], UnitMaps0) ->
- UnitsIn = maps:get(L, UnitMaps0, #{}),
- {Block, UnitsOut} = bsm_units_skip(Block0, UnitsIn),
- UnitMaps1 = bsm_units_join(Succ, UnitsOut, UnitMaps0),
- UnitMaps = bsm_units_join(Fail, UnitsIn, UnitMaps1),
- [{L, Block} | bsm_units(Bs, UnitMaps)];
-bsm_units([{L,#b_blk{last=#b_switch{fail=Fail,list=Switch}}=Block} | Bs], UnitMaps0) ->
- UnitsIn = maps:get(L, UnitMaps0, #{}),
- Labels = [Fail | [Lbl || {_Arg, Lbl} <- Switch]],
- UnitMaps = foldl(fun(Lbl, UnitMaps) ->
- bsm_units_join(Lbl, UnitsIn, UnitMaps)
- end, UnitMaps0, Labels),
- [{L, Block} | bsm_units(Bs, UnitMaps)];
-bsm_units([{L, Block} | Bs], UnitMaps) ->
- [{L, Block} | bsm_units(Bs, UnitMaps)];
-bsm_units([], _UnitMaps) ->
- [].
-
-bsm_units_skip(Block, Units) ->
- bsm_units_skip_1(Block#b_blk.is, Block, Units).
-
-bsm_units_skip_1([#b_set{op=bs_start_match,dst=New}|_], Block, Units) ->
- %% We bail early since there can't be more than one match per block.
- {Block, Units#{ New => 1 }};
-bsm_units_skip_1([#b_set{op=bs_match,
- dst=New,
- args=[#b_literal{val=skip},
- Ctx,
- #b_literal{val=binary},
- _Flags,
- #b_literal{val=all},
- #b_literal{val=OpUnit}]}=Skip | Test],
- Block0, Units) ->
- [#b_set{op=succeeded,dst=Bool,args=[New]}] = Test, %Assertion.
- #b_br{bool=Bool} = Last0 = Block0#b_blk.last, %Assertion.
- CtxUnit = map_get(Ctx, Units),
- if
- CtxUnit rem OpUnit =:= 0 ->
- Is = takewhile(fun(I) -> I =/= Skip end, Block0#b_blk.is),
- Last = Last0#b_br{bool=#b_literal{val=true}},
- Block = Block0#b_blk{is=Is,last=Last},
- {Block, Units#{ New => CtxUnit }};
- CtxUnit rem OpUnit =/= 0 ->
- {Block0, Units#{ New => OpUnit, Ctx => OpUnit }}
- end;
-bsm_units_skip_1([#b_set{op=bs_match,dst=New,args=Args}|_], Block, Units) ->
- [_,Ctx|_] = Args,
- CtxUnit = map_get(Ctx, Units),
- OpUnit = bsm_op_unit(Args),
- {Block, Units#{ New => gcd(OpUnit, CtxUnit) }};
-bsm_units_skip_1([_I | Is], Block, Units) ->
- bsm_units_skip_1(Is, Block, Units);
-bsm_units_skip_1([], Block, Units) ->
- {Block, Units}.
-
-bsm_op_unit([_,_,_,Size,#b_literal{val=U}]) ->
- case Size of
- #b_literal{val=Sz} when is_integer(Sz) -> Sz*U;
- _ -> U
- end;
-bsm_op_unit([#b_literal{val=string},_,#b_literal{val=String}]) ->
- bit_size(String);
-bsm_op_unit([#b_literal{val=utf8}|_]) ->
- 8;
-bsm_op_unit([#b_literal{val=utf16}|_]) ->
- 16;
-bsm_op_unit([#b_literal{val=utf32}|_]) ->
- 32;
-bsm_op_unit(_) ->
- 1.
-
-%% Several paths can lead to the same match instruction and the inferred units
-%% may differ between them, so we can only keep the information that is common
-%% to all paths.
-bsm_units_join(Lbl, MapA, UnitMaps0) when is_map_key(Lbl, UnitMaps0) ->
- MapB = map_get(Lbl, UnitMaps0),
- Merged = if
- map_size(MapB) =< map_size(MapA) ->
- bsm_units_join_1(maps:keys(MapB), MapA, MapB);
- map_size(MapB) > map_size(MapA) ->
- bsm_units_join_1(maps:keys(MapA), MapB, MapA)
- end,
- UnitMaps0#{Lbl := Merged};
-bsm_units_join(Lbl, MapA, UnitMaps0) when MapA =/= #{} ->
- UnitMaps0#{Lbl => MapA};
-bsm_units_join(_Lbl, _MapA, UnitMaps0) ->
- UnitMaps0.
-
-bsm_units_join_1([Key | Keys], Left, Right) when is_map_key(Key, Left) ->
- UnitA = map_get(Key, Left),
- UnitB = map_get(Key, Right),
- bsm_units_join_1(Keys, Left, Right#{Key := gcd(UnitA, UnitB)});
-bsm_units_join_1([Key | Keys], Left, Right) ->
- bsm_units_join_1(Keys, Left, maps:remove(Key, Right));
-bsm_units_join_1([], _MapA, Right) ->
- Right.
-
-%%%
%%% Optimize binary construction.
%%%
%%% If an integer segment or a float segment has a literal size and
@@ -1543,9 +1676,9 @@ bsm_units_join_1([], _MapA, Right) ->
%%% to bs_put_string instructions in later pass.
%%%
-ssa_opt_bs_puts({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
+ssa_opt_bs_puts({#opt_st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
{Linear,Count} = opt_bs_puts(Linear0, Count0, []),
- {St#st{ssa=Linear,cnt=Count}, FuncDb}.
+ {St#opt_st{ssa=Linear,cnt=Count}, FuncDb}.
opt_bs_puts([{L,#b_blk{is=Is}=Blk0}|Bs], Count0, Acc0) ->
case Is of
@@ -1641,8 +1774,13 @@ opt_bs_put(#b_set{args=[#b_literal{val=Type},#b_literal{val=Flags},
I = I0#b_set{args=Args},
opt_bs_put(I);
{binary,_} when is_bitstring(Val) ->
- <<Bitstring:EffectiveSize/bits,_/bits>> = Val,
- [Bitstring];
+ case Val of
+ <<Bitstring:EffectiveSize/bits,_/bits>> ->
+ [Bitstring];
+ _ ->
+ %% Specified size exceeds size of bitstring.
+ not_possible
+ end;
{float,Endian} ->
try
[opt_bs_put_float(Val, EffectiveSize, Endian)]
@@ -1713,7 +1851,7 @@ opt_bs_put_split_int_1(Int, L, R) ->
%%% .
%%% .
%%% Size = bif:tuple_size Var
-%%% BoolVar1 = succeeded Size
+%%% BoolVar1 = succeeded:guard Size
%%% br BoolVar1, label 4, label 3
%%%
%%% 4:
@@ -1763,12 +1901,12 @@ opt_bs_put_split_int_1(Int, L, R) ->
%%% is_tuple_of_arity instruction by the loader.
%%%
-ssa_opt_tuple_size({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
+ssa_opt_tuple_size({#opt_st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
%% This optimization is only safe in guards, as prefixing tuple_size with
%% an is_tuple check prevents it from throwing an exception.
NonGuards = non_guards(Linear0),
{Linear,Count} = opt_tup_size(Linear0, NonGuards, Count0, []),
- {St#st{ssa=Linear,cnt=Count}, FuncDb}.
+ {St#opt_st{ssa=Linear,cnt=Count}, FuncDb}.
opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], NonGuards, Count0, Acc0) ->
case {Is,Last} of
@@ -1825,7 +1963,7 @@ opt_tup_size_2(PreIs, TupleSizeIs, PreL, EqL, Tuple, Fail, Count0, Acc) ->
{PreL,PreBlk}|Acc],Count}.
opt_tup_size_is([#b_set{op={bif,tuple_size},dst=Size,args=[Tuple]}=I,
- #b_set{op=succeeded,dst=Bool,args=[Size]}],
+ #b_set{op={succeeded,_},dst=Bool,args=[Size]}],
Bool, Size, Acc) ->
{reverse(Acc),[I],Tuple};
opt_tup_size_is([I|Is], Bool, Size, Acc) ->
@@ -1835,33 +1973,20 @@ opt_tup_size_is([], _, _, _Acc) -> none.
%%%
%%% Optimize #b_switch{} instructions.
%%%
-%%% If the argument for a #b_switch{} comes from a phi node with all
-%%% literals, any values in the switch list which are not in the phi
-%%% node can be removed.
-%%%
-%%% If the values in the phi node and switch list are the same,
-%%% the failure label can't be reached and be eliminated.
-%%%
%%% A #b_switch{} with only one value can be rewritten to
%%% a #b_br{}. A switch that only verifies that the argument
-%%% is 'true' or 'false' can be rewritten to a is_boolean test.
-%%%
+%%% is 'true' or 'false' can be rewritten to an is_boolean test.
+%%%b
-ssa_opt_sw({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
+ssa_opt_sw({#opt_st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
{Linear,Count} = opt_sw(Linear0, Count0, []),
- {St#st{ssa=Linear,cnt=Count}, FuncDb}.
+ {St#opt_st{ssa=Linear,cnt=Count}, FuncDb}.
opt_sw([{L,#b_blk{is=Is,last=#b_switch{}=Sw0}=Blk0}|Bs], Count0, Acc) ->
- %% Ensure that no label in the switch list is the same
- %% as the failure label.
- #b_switch{fail=Fail,list=List0} = Sw0,
- List = [{Val,Lbl} || {Val,Lbl} <- List0, Lbl =/= Fail],
- Sw1 = beam_ssa:normalize(Sw0#b_switch{list=List}),
- case Sw1 of
+ case Sw0 of
#b_switch{arg=Arg,fail=Fail,list=[{Lit,Lbl}]} ->
%% Rewrite a single value switch to a br.
- Bool = #b_var{name={'@ssa_bool',Count0}},
- Count = Count0 + 1,
+ {Bool,Count} = new_var('@ssa_bool', Count0),
IsEq = #b_set{op={bif,'=:='},dst=Bool,args=[Arg,Lit]},
Br = #b_br{bool=Bool,succ=Lbl,fail=Fail},
Blk = Blk0#b_blk{is=Is++[IsEq],last=Br},
@@ -1870,17 +1995,13 @@ opt_sw([{L,#b_blk{is=Is,last=#b_switch{}=Sw0}=Blk0}|Bs], Count0, Acc) ->
list=[{#b_literal{val=B1},Lbl},{#b_literal{val=B2},Lbl}]}
when B1 =:= not B2 ->
%% Replace with is_boolean test.
- Bool = #b_var{name={'@ssa_bool',Count0}},
- Count = Count0 + 1,
+ {Bool,Count} = new_var('@ssa_bool', Count0),
IsBool = #b_set{op={bif,is_boolean},dst=Bool,args=[Arg]},
Br = #b_br{bool=Bool,succ=Lbl,fail=Fail},
Blk = Blk0#b_blk{is=Is++[IsBool],last=Br},
opt_sw(Bs, Count, [{L,Blk}|Acc]);
- Sw0 ->
- opt_sw(Bs, Count0, [{L,Blk0}|Acc]);
- Sw ->
- Blk = Blk0#b_blk{last=Sw},
- opt_sw(Bs, Count0, [{L,Blk}|Acc])
+ _ ->
+ opt_sw(Bs, Count0, [{L,Blk0}|Acc])
end;
opt_sw([{L,#b_blk{}=Blk}|Bs], Count, Acc) ->
opt_sw(Bs, Count, [{L,Blk}|Acc]);
@@ -1888,78 +2009,170 @@ opt_sw([], Count, Acc) ->
{reverse(Acc),Count}.
%%%
-%%% Merge blocks.
+%%% Replace `wait_timeout infinity` with `wait`, but only when safe to
+%%% do so.
%%%
-
-ssa_opt_merge_blocks({#st{ssa=Blocks}=St, FuncDb}) ->
- Preds = beam_ssa:predecessors(Blocks),
- Merged = merge_blocks_1(beam_ssa:rpo(Blocks), Preds, Blocks),
- {St#st{ssa=Merged}, FuncDb}.
-
-merge_blocks_1([L|Ls], Preds0, Blocks0) ->
- case Preds0 of
- #{L:=[P]} ->
- #{P:=Blk0,L:=Blk1} = Blocks0,
- case is_merge_allowed(L, Blk0, Blk1) of
- true ->
- #b_blk{is=Is0} = Blk0,
- #b_blk{is=Is1} = Blk1,
- verify_merge_is(Is1),
- Is = Is0 ++ Is1,
- Blk = Blk1#b_blk{is=Is},
- Blocks1 = maps:remove(L, Blocks0),
- Blocks2 = Blocks1#{P:=Blk},
- Successors = beam_ssa:successors(Blk),
- Blocks = beam_ssa:update_phi_labels(Successors, L, P, Blocks2),
- Preds = merge_update_preds(Successors, L, P, Preds0),
- merge_blocks_1(Ls, Preds, Blocks);
- false ->
- merge_blocks_1(Ls, Preds0, Blocks0)
- end;
- #{} ->
- merge_blocks_1(Ls, Preds0, Blocks0)
+%%% Consider this code:
+%%%
+%%% 0:
+%%% @tag = new_try_tag `'try'`
+%%% br @tag, ^2, ^99
+%%%
+%%% 2:
+%%% .
+%%% .
+%%% .
+%%% br ^50
+%%%
+%%% 50:
+%%% @wait_bool = wait_timeout `infinity`
+%%% @succ_bool = succeeded @bool
+%%% br @succ_bool ^51, ^99
+%%%
+%%% 51:
+%%% br @wait_bool ^75, ^50
+%%%
+%%% 75:
+%%% timeout
+%%% kill_try_tag @tag
+%%% ret `ok`
+%%%
+%%% 99:
+%%% @ssa_agg = landingpad `'try'`, @tag
+%%% @ssa_ignored = kill_try_tag @tag
+%%% ret `error`
+%%%
+%%%
+%%% The liveness range of @tag will be from block 0 to block 99.
+%%% That will ensure that the Y register reserved for @tag can't
+%%% be reused or killed inside the try/block.
+%%%
+%%% It would not be safe (in general) to replace the `wait_timeout`
+%%% instruction with `wait` in this code. That is, the following
+%%% code is potentially UNSAFE (depending on the exact code in
+%%% block 2):
+%%%
+%%% 0:
+%%% @tag = new_try_tag `'try'`
+%%% br @tag, ^2, ^99
+%%%
+%%% 2:
+%%% .
+%%% .
+%%% .
+%%% br ^50
+%%%
+%%% 50:
+%%% wait
+%%% br ^50
+%%%
+%%% 99:
+%%% @ssa_agg = landingpad `'try'`, @tag
+%%% @ssa_ignored = kill_try_tag @tag
+%%% ret `error`
+%%%
+%%% The try tag variable @tag will not be live in block 2 and 50
+%%% (because from those blocks, there is no way to reach an
+%%% instruction that uses @tag). Because @tag is not live, the
+%%% register allocator could reuse the register for @tag, or the
+%%% code generator could kill the register that holds @tag.
+%%%
+
+ssa_opt_receive_after({#opt_st{ssa=Linear}=St, FuncDb}) ->
+ {St#opt_st{ssa=recv_after_opt(Linear)}, FuncDb}.
+
+recv_after_opt([{L1,#b_blk{is=Is0,last=#b_br{bool=#b_var{},
+ succ=L2,
+ fail=?EXCEPTION_BLOCK}}=Blk1},
+ {L2,#b_blk{is=[],last=#b_br{bool=#b_var{}=WaitBool,
+ fail=Fail}=Br0}=Blk2}|Bs]) ->
+ case recv_after_opt_is(Is0, WaitBool, []) of
+ {yes,Is} ->
+ Br = Br0#b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail},
+ [{L1,Blk1#b_blk{is=Is,last=Br}}|recv_after_opt(Bs)];
+ no ->
+ [{L1,Blk1},{L2,Blk2}|recv_after_opt(Bs)]
end;
-merge_blocks_1([], _Preds, Blocks) -> Blocks.
-
-merge_update_preds([L|Ls], From, To, Preds0) ->
- Ps = [rename_label(P, From, To) || P <- map_get(L, Preds0)],
- Preds = Preds0#{L:=Ps},
- merge_update_preds(Ls, From, To, Preds);
-merge_update_preds([], _, _, Preds) -> Preds.
-
-rename_label(From, From, To) -> To;
-rename_label(Lbl, _, _) -> Lbl.
+recv_after_opt([B|Bs]) ->
+ [B|recv_after_opt(Bs)];
+recv_after_opt([]) -> [].
+
+recv_after_opt_is([#b_set{op=wait_timeout,
+ args=[#b_literal{val=infinity}],
+ dst=WaitBool}=I0,
+ #b_set{op={succeeded,body},
+ args=[WaitBool]}],
+ WaitBool, Acc) ->
+ I = I0#b_set{op=wait,args=[]},
+ {yes,reverse(Acc, [I])};
+recv_after_opt_is([I|Is], WaitBool, Acc) ->
+ recv_after_opt_is(Is, WaitBool, [I|Acc]);
+recv_after_opt_is([], _WaitBool, _Acc) -> no.
+
+%%% Try to replace `=/=` with `=:=` and `/=` with `==`. For example,
+%%% this code:
+%%%
+%%% Bool = bif:'=/=' Anything, AnyValue
+%%% br Bool, ^Succ, ^Fail
+%%%
+%%% can be rewritten like this:
+%%%
+%%% Bool = bif:'=:=' Anything, AnyValue
+%%% br Bool, ^Fail, ^Succ
+%%%
+%%% The transformation is only safe if the only used of Bool is in the
+%%% terminator. We therefore need to verify that there is only one
+%%% use.
+%%%
+%%% This transformation is not an optimization in itself, but it opens
+%%% up for other optimizations in beam_ssa_type and beam_ssa_dead.
+%%%
+
+ssa_opt_ne({#opt_st{ssa=Linear0}=St, FuncDb}) ->
+ Linear = opt_ne(Linear0, {uses,Linear0}),
+ {St#opt_st{ssa=Linear}, FuncDb}.
+
+opt_ne([{L,#b_blk{is=[_|_]=Is0,last=#b_br{bool=#b_var{}=Bool}}=Blk0}|Bs], Uses0) ->
+ case last(Is0) of
+ #b_set{op={bif,'=/='},dst=Bool}=I0 ->
+ I = I0#b_set{op={bif,'=:='}},
+ {Blk,Uses} = opt_ne_replace(I, Blk0, Uses0),
+ [{L,Blk}|opt_ne(Bs, Uses)];
+ #b_set{op={bif,'/='},dst=Bool}=I0 ->
+ I = I0#b_set{op={bif,'=='}},
+ {Blk,Uses} = opt_ne_replace(I, Blk0, Uses0),
+ [{L,Blk}|opt_ne(Bs, Uses)];
+ _ ->
+ [{L,Blk0}|opt_ne(Bs, Uses0)]
+ end;
+opt_ne([{L,Blk}|Bs], Uses) ->
+ [{L,Blk}|opt_ne(Bs, Uses)];
+opt_ne([], _Uses) -> [].
+
+opt_ne_replace(#b_set{dst=Bool}=I,
+ #b_blk{is=Is0,last=#b_br{succ=Succ,fail=Fail}=Br0}=Blk,
+ Uses0) ->
+ case opt_ne_single_use(Bool, Uses0) of
+ {true,Uses} ->
+ Is = replace_last(Is0, I),
+ Br = Br0#b_br{succ=Fail,fail=Succ},
+ {Blk#b_blk{is=Is,last=Br},Uses};
+ {false,Uses} ->
+ %% The variable is used more than once. Not safe.
+ {Blk,Uses}
+ end.
-verify_merge_is([#b_set{op=Op}|_]) ->
- %% The merged block has only one predecessor, so it should not have any phi
- %% nodes.
- true = Op =/= phi; %Assertion.
-verify_merge_is(_) ->
- ok.
+replace_last([_], Repl) -> [Repl];
+replace_last([I|Is], Repl) -> [I|replace_last(Is, Repl)].
-is_merge_allowed(_, #b_blk{}, #b_blk{is=[#b_set{op=peek_message}|_]}) ->
- false;
-is_merge_allowed(L, #b_blk{last=#b_br{}}=Blk, #b_blk{is=Is}) ->
- %% The predecessor block must have exactly one successor (L) for
- %% the merge to be safe.
- case beam_ssa:successors(Blk) of
- [L] ->
- case Is of
- [#b_set{op=phi,args=[_]}|_] ->
- %% The type optimizer pass must have been
- %% turned off, since it would have removed this
- %% redundant phi node. Refuse to merge the blocks
- %% to ensure that this phi node remains at the
- %% beginning of a block.
- false;
- _ ->
- true
- end;
- [_|_] ->
- false
- end;
-is_merge_allowed(_, #b_blk{last=#b_switch{}}, #b_blk{}) ->
- false.
+opt_ne_single_use(Var, {uses,Linear}) ->
+ Uses = beam_ssa:uses(maps:from_list(Linear)),
+ opt_ne_single_use(Var, Uses);
+opt_ne_single_use(Var, Uses) when is_map(Uses) ->
+ {case Uses of
+ #{Var:=[_]} -> true;
+ #{Var:=[_|_]} -> false
+ end,Uses}.
%%%
%%% When a tuple is matched, the pattern matching compiler generates a
@@ -1976,54 +2189,105 @@ is_merge_allowed(_, #b_blk{last=#b_switch{}}, #b_blk{}) ->
%%% extracting tuple elements on execution paths that never use the
%%% extracted values.
%%%
-
-ssa_opt_sink({#st{ssa=Blocks0}=St, FuncDb}) ->
- Linear = beam_ssa:linearize(Blocks0),
-
+%%% However, there is one caveat to be aware of. Sinking tuple elements
+%%% will keep the entire tuple alive longer. In rare circumstance, that
+%%% can be a problem. Here is an example:
+%%%
+%%% mapfoldl(F, Acc0, [Hd|Tail]) ->
+%%% {R,Acc1} = F(Hd, Acc0),
+%%% {Rs,Acc2} = mapfoldl(F, Acc1, Tail),
+%%% {[R|Rs],Acc2};
+%%% mapfoldl(F, Acc, []) ->
+%%% {[],Acc}.
+%%%
+%%% Sinking get_tuple_element instructions will generate code similar
+%%% to this example:
+%%%
+%%% slow_mapfoldl(F, Acc0, [Hd|Tail]) ->
+%%% Res1 = F(Hd, Acc0),
+%%% Res2 = slow_mapfoldl(F, element(2, Res1), Tail),
+%%% {[element(1, Res1)|element(1, Res2)],element(2, Res2)};
+%%% slow_mapfoldl(F, Acc, []) ->
+%%% {[],Acc}.
+%%%
+%%% Here the entire tuple bound to `Res1` will be kept alive until
+%%% `slow_mapfoldl/3` returns. That is, every intermediate accumulator
+%%% will be kept alive.
+%%%
+%%% In this case, not sinking is clearly superior:
+%%%
+%%% fast_mapfoldl(F, Acc0, [Hd|Tail]) ->
+%%% Res1 = F(Hd, Acc0),
+%%% R = element(1, Res1),
+%%% Res2 = fast_mapfoldl(F, element(2, Res1), Tail),
+%%% {[R|element(1, Res2)],element(2, Res2)};
+%%% fast_mapfoldl(F, Acc, []) ->
+%%% {[],Acc}.
+%%%
+%%% The `slow_mapfoldl/3` and `fast_mapfoldl/3` use the same number of
+%%% stack slots.
+%%%
+%%% To avoid producing code similar to `slow_mapfoldl/3`, use an
+%%% heuristic to only sink when sinking would reduce the number of
+%%% stack slots, or if there can't possibly be a recursive call
+%%% involved. This is a heuristic because it is difficult to exactly
+%%% predict the number of stack slots that will be needed for a given
+%%% piece of code.
+
+ssa_opt_sink({#opt_st{ssa=Linear}=St, FuncDb}) ->
%% Create a map with all variables that define get_tuple_element
- %% instructions. The variable name map to the block it is defined in.
+ %% instructions. The variable name maps to the block it is defined
+ %% in and the source tuple.
case def_blocks(Linear) of
[] ->
%% No get_tuple_element instructions, so there is nothing to do.
{St, FuncDb};
[_|_]=Defs0 ->
Defs = maps:from_list(Defs0),
- {do_ssa_opt_sink(Linear, Defs, St), FuncDb}
+ {do_ssa_opt_sink(Defs, St), FuncDb}
end.
-do_ssa_opt_sink(Linear, Defs, #st{ssa=Blocks0}=St) ->
- %% Now find all the blocks that use variables defined by get_tuple_element
- %% instructions.
+do_ssa_opt_sink(Defs, #opt_st{ssa=Linear}=St) ->
+ %% Find all the blocks that use variables defined by
+ %% get_tuple_element instructions.
Used = used_blocks(Linear, Defs, []),
%% Calculate dominators.
- {Dom,Numbering} = beam_ssa:dominators(Blocks0),
+ Blocks0 = maps:from_list(Linear),
+ Preds = beam_ssa:predecessors(Blocks0),
+ {Dom, Numbering} = beam_ssa:dominators(Blocks0, Preds),
%% It is not safe to move get_tuple_element instructions to blocks
%% that begin with certain instructions. It is also unsafe to move
- %% the instructions into any part of a receive. To avoid such
- %% unsafe moves, pretend that the unsuitable blocks are not
- %% dominators.
+ %% the instructions into any part of a receive.
Unsuitable = unsuitable(Linear, Blocks0),
%% Calculate new positions for get_tuple_element instructions. The new
%% position is a block that dominates all uses of the variable.
- DefLoc = new_def_locations(Used, Defs, Dom, Numbering, Unsuitable),
+ DefLocs0 = new_def_locations(Used, Defs, Dom, Numbering, Unsuitable),
+
+ %% Avoid keeping tuples alive if only one element is accessed later and
+ %% if there is the chance of a recursive call being made. This is an
+ %% important precaution to avoid that lists:mapfoldl/3 keeps all previous
+ %% versions of the accumulator alive until the end of the input list.
+ Ps = partition_deflocs(DefLocs0, Defs, Blocks0),
+ DefLocs1 = filter_deflocs(Ps, Preds, Blocks0),
+ DefLocs = sort(DefLocs1),
%% Now move all suitable get_tuple_element instructions to their
%% new blocks.
- Blocks = foldl(fun({V,To}, A) ->
- From = map_get(V, Defs),
+ Blocks = foldl(fun({V,{From,To}}, A) ->
move_defs(V, From, To, A)
- end, Blocks0, DefLoc),
- St#st{ssa=Blocks}.
+ end, Blocks0, DefLocs),
+
+ St#opt_st{ssa=beam_ssa:linearize(Blocks)}.
def_blocks([{L,#b_blk{is=Is}}|Bs]) ->
def_blocks_is(Is, L, def_blocks(Bs));
def_blocks([]) -> [].
-def_blocks_is([#b_set{op=get_tuple_element,dst=Dst}|Is], L, Acc) ->
- def_blocks_is(Is, L, [{Dst,L}|Acc]);
+def_blocks_is([#b_set{op=get_tuple_element,args=[Tuple,_],dst=Dst}|Is], L, Acc) ->
+ def_blocks_is(Is, L, [{Dst,{L,Tuple}}|Acc]);
def_blocks_is([_|Is], L, Acc) ->
def_blocks_is(Is, L, Acc);
def_blocks_is([], _, Acc) -> Acc.
@@ -2035,6 +2299,180 @@ used_blocks([{L,Blk}|Bs], Def, Acc0) ->
used_blocks([], _Def, Acc) ->
rel2fam(Acc).
+%% Partition sinks for get_tuple_element instructions in the same
+%% clause extracting from the same tuple. Sort each partition in
+%% execution order.
+partition_deflocs(DefLoc, _Defs, Blocks) ->
+ {BlkNums0,_} = mapfoldl(fun(L, N) -> {{L,N},N+1} end, 0, beam_ssa:rpo(Blocks)),
+ BlkNums = maps:from_list(BlkNums0),
+ S = [{Tuple,{map_get(To, BlkNums),{V,{From,To}}}} ||
+ {V,Tuple,{From,To}} <- DefLoc],
+ F = rel2fam(S),
+ partition_deflocs_1(F, Blocks).
+
+partition_deflocs_1([{Tuple,DefLocs0}|T], Blocks) ->
+ DefLocs1 = [DL || {_,DL} <- DefLocs0],
+ DefLocs = partition_dl(DefLocs1, Blocks),
+ [{Tuple,DL} || DL <- DefLocs] ++ partition_deflocs_1(T, Blocks);
+partition_deflocs_1([], _) -> [].
+
+partition_dl([_]=DefLoc, _Blocks) ->
+ [DefLoc];
+partition_dl([{_,{_,First}}|_]=DefLoc0, Blocks) ->
+ RPO = beam_ssa:rpo([First], Blocks),
+ {P,DefLoc} = partition_dl_1(DefLoc0, RPO, []),
+ [P|partition_dl(DefLoc, Blocks)];
+partition_dl([], _Blocks) -> [].
+
+partition_dl_1([{_,{_,L}}=DL|DLs], [L|_]=Ls, Acc) ->
+ partition_dl_1(DLs, Ls, [DL|Acc]);
+partition_dl_1([_|_]=DLs, [_|Ls], Acc) ->
+ partition_dl_1(DLs, Ls, Acc);
+partition_dl_1([], _, Acc) ->
+ {reverse(Acc),[]};
+partition_dl_1([_|_]=DLs, [], Acc) ->
+ {reverse(Acc),DLs}.
+
+filter_deflocs([{Tuple,DefLoc0}|DLs], Preds, Blocks) ->
+ %% Input is a list of sinks of get_tuple_element instructions in
+ %% execution order from the same tuple in the same clause.
+ [{_,{_,First}}|_] = DefLoc0,
+ Paths = find_paths_to_check(DefLoc0, First),
+ WillGC0 = ordsets:from_list([FromTo || {{_,_}=FromTo,_} <- Paths]),
+ WillGC1 = [{{From,To},will_gc(From, To, Preds, Blocks, true)} ||
+ {From,To} <- WillGC0],
+ WillGC = maps:from_list(WillGC1),
+
+ %% Separate sinks that will force the reference to the tuple to be
+ %% saved on the stack from sinks that don't force.
+ {DefLocGC0,DefLocNoGC} =
+ partition(fun({{_,_}=FromTo,_}) ->
+ map_get(FromTo, WillGC)
+ end, Paths),
+
+ %% Avoid potentially harmful sinks.
+ DefLocGC = filter_gc_deflocs(DefLocGC0, Tuple, First, Preds, Blocks),
+
+ %% Construct the complete list of sink operations.
+ DefLoc1 = DefLocGC ++ DefLocNoGC,
+ [DL || {_,{_,{From,To}}=DL} <- DefLoc1, From =/= To] ++
+ filter_deflocs(DLs, Preds, Blocks);
+filter_deflocs([], _, _) -> [].
+
+%% Use an heuristic to avoid harmful sinking in lists:mapfold/3 and
+%% similar functions.
+filter_gc_deflocs(DefLocGC, Tuple, First, Preds, Blocks) ->
+ case DefLocGC of
+ [] ->
+ [];
+ [{_,{_,{From,To}}}] ->
+ %% There is only one get_tuple_element instruction that
+ %% can be sunk. That means that we may not gain any slots
+ %% by sinking it.
+ case is_on_stack(First, Tuple, Blocks) of
+ true ->
+ %% The tuple itself must be stored in a stack slot
+ %% (because it will be used later) in addition to
+ %% the tuple element being extracted. It is
+ %% probably a win to sink this instruction.
+ DefLocGC;
+ false ->
+ case will_gc(From, To, Preds, Blocks, false) of
+ false ->
+ %% There is no risk for recursive calls,
+ %% so it should be safe to
+ %% sink. Theoretically, we shouldn't win
+ %% any stack slots, but in practice it
+ %% seems that sinking makes it more likely
+ %% that the stack slot for a dying value
+ %% can be immediately reused for another
+ %% value.
+ DefLocGC;
+ true ->
+ %% This function could be involved in a
+ %% recursive call. Since there is no
+ %% obvious reduction in the number of
+ %% stack slots, we will not sink.
+ []
+ end
+ end;
+ [_,_|_] ->
+ %% More than one get_tuple_element instruction can be
+ %% sunk. Sinking will almost certainly reduce the number
+ %% of stack slots.
+ DefLocGC
+ end.
+
+find_paths_to_check([{_,{_,To}}=Move|T], First) ->
+ [{{First,To},Move}|find_paths_to_check(T, First)];
+find_paths_to_check([], _First) -> [].
+
+will_gc(From, To, Preds, Blocks, All) ->
+ Between = beam_ssa:between(From, To, Preds, Blocks),
+ will_gc_1(Between, To, Blocks, All, #{From => false}).
+
+will_gc_1([To|_], To, _Blocks, _All, WillGC) ->
+ map_get(To, WillGC);
+will_gc_1([L|Ls], To, Blocks, All, WillGC0) ->
+ #b_blk{is=Is} = Blk = map_get(L, Blocks),
+ GC = map_get(L, WillGC0) orelse will_gc_is(Is, All),
+ WillGC = gc_update_successors(Blk, GC, WillGC0),
+ will_gc_1(Ls, To, Blocks, All, WillGC).
+
+will_gc_is([#b_set{op=call,args=Args}|Is], false) ->
+ case Args of
+ [#b_remote{mod=#b_literal{val=erlang}}|_] ->
+ %% Assume that remote calls to the erlang module can't cause a recursive
+ %% call.
+ will_gc_is(Is, false);
+ [_|_] ->
+ true
+ end;
+will_gc_is([_|Is], false) ->
+ %% Instructions that clobber X registers may cause a GC, but will not cause
+ %% a recursive call.
+ will_gc_is(Is, false);
+will_gc_is([I|Is], All) ->
+ beam_ssa:clobbers_xregs(I) orelse will_gc_is(Is, All);
+will_gc_is([], _All) -> false.
+
+is_on_stack(From, Var, Blocks) ->
+ is_on_stack(beam_ssa:rpo([From], Blocks), Var, Blocks, #{From => false}).
+
+is_on_stack([L|Ls], Var, Blocks, WillGC0) ->
+ #b_blk{is=Is} = Blk = map_get(L, Blocks),
+ GC0 = map_get(L, WillGC0),
+ try is_on_stack_is(Is, Var, GC0) of
+ GC ->
+ WillGC = gc_update_successors(Blk, GC, WillGC0),
+ is_on_stack(Ls, Var, Blocks, WillGC)
+ catch
+ throw:{done,GC} ->
+ GC
+ end;
+is_on_stack([], _Var, _, _) -> false.
+
+is_on_stack_is([#b_set{op=get_tuple_element}|Is], Var, GC) ->
+ is_on_stack_is(Is, Var, GC);
+is_on_stack_is([I|Is], Var, GC0) ->
+ case GC0 andalso member(Var, beam_ssa:used(I)) of
+ true ->
+ throw({done,GC0});
+ false ->
+ GC = GC0 orelse beam_ssa:clobbers_xregs(I),
+ is_on_stack_is(Is, Var, GC)
+ end;
+is_on_stack_is([], _, GC) -> GC.
+
+gc_update_successors(Blk, GC, WillGC) ->
+ foldl(fun(L, Acc) ->
+ case Acc of
+ #{L := true} -> Acc;
+ #{L := false} when GC =:= false -> Acc;
+ #{} -> Acc#{L => GC}
+ end
+ end, WillGC, beam_ssa:successors(Blk)).
+
%% unsuitable(Linear, Blocks) -> Unsuitable.
%% Return an ordset of block labels for the blocks that are not
%% suitable for sinking of get_tuple_element instructions.
@@ -2045,15 +2483,13 @@ unsuitable(Linear, Blocks) ->
Unsuitable1 = unsuitable_recv(Linear, Blocks, Predecessors),
gb_sets:from_list(Unsuitable0 ++ Unsuitable1).
-unsuitable_1([{L,#b_blk{is=[#b_set{op=Op}|_]}}|Bs]) ->
+unsuitable_1([{L,#b_blk{is=[#b_set{op=Op}=I|_]}}|Bs]) ->
Unsuitable = case Op of
bs_extract -> true;
bs_put -> true;
{float,_} -> true;
landingpad -> true;
- peek_message -> true;
- wait_timeout -> true;
- _ -> false
+ _ -> beam_ssa:is_loop_header(I)
end,
case Unsuitable of
true ->
@@ -2087,10 +2523,10 @@ unsuitable_loop(L, Blocks, Predecessors, Acc) ->
unsuitable_loop_1(Ps, Blocks, Predecessors, Acc).
unsuitable_loop_1([P|Ps], Blocks, Predecessors, Acc0) ->
- case map_get(P, Blocks) of
- #b_blk{is=[#b_set{op=peek_message}|_]} ->
+ case is_loop_header(P, Blocks) of
+ true ->
unsuitable_loop_1(Ps, Blocks, Predecessors, Acc0);
- #b_blk{} ->
+ false ->
case ordsets:is_element(P, Acc0) of
false ->
Acc1 = ordsets:add_element(P, Acc0),
@@ -2102,6 +2538,14 @@ unsuitable_loop_1([P|Ps], Blocks, Predecessors, Acc0) ->
end;
unsuitable_loop_1([], _, _, Acc) -> Acc.
+is_loop_header(L, Blocks) ->
+ case map_get(L, Blocks) of
+ #b_blk{is=[I|_]} ->
+ beam_ssa:is_loop_header(I);
+ #b_blk{} ->
+ false
+ end.
+
%% new_def_locations([{Variable,[UsedInBlock]}|Vs], Defs,
%% Dominators, Numbering, Unsuitable) ->
%% [{Variable,NewDefinitionBlock}]
@@ -2111,19 +2555,22 @@ unsuitable_loop_1([], _, _, Acc) -> Acc.
%% of the variable and as near to the uses of as possible.
new_def_locations([{V,UsedIn}|Vs], Defs, Dom, Numbering, Unsuitable) ->
- DefIn = map_get(V, Defs),
+ {DefIn,Tuple} = map_get(V, Defs),
Common = common_dominator(UsedIn, Dom, Numbering, Unsuitable),
- case member(Common, map_get(DefIn, Dom)) of
- true ->
- %% The common dominator is either DefIn or an
- %% ancestor of DefIn.
- new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable);
- false ->
- %% We have found a suitable descendant of DefIn,
- %% to which the get_tuple_element instruction can
- %% be sunk.
- [{V,Common}|new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable)]
- end;
+ Sink = case member(Common, map_get(DefIn, Dom)) of
+ true ->
+ %% The common dominator is either DefIn or an
+ %% ancestor of DefIn. We'll need to consider all
+ %% get_tuple_element instructions so we will add
+ %% a dummy sink.
+ {V,Tuple,{DefIn,DefIn}};
+ false ->
+ %% We have found a suitable descendant of DefIn,
+ %% to which the get_tuple_element instruction can
+ %% be sunk.
+ {V,Tuple,{DefIn,Common}}
+ end,
+ [Sink|new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable)];
new_def_locations([], _, _, _, _) -> [].
common_dominator(Ls0, Dom, Numbering, Unsuitable) ->
@@ -2146,7 +2593,6 @@ move_defs(V, From, To, Blocks) ->
{Def,FromBlk} = remove_def(V, FromBlk0),
try insert_def(V, Def, ToBlk0) of
ToBlk ->
- %%io:format("~p: ~p => ~p\n", [V,From,To]),
Blocks#{From:=FromBlk,To:=ToBlk}
catch
throw:not_possible ->
@@ -2181,7 +2627,7 @@ insert_def_is([#b_set{op=Op}=I|Is]=Is0, V, Def) ->
_ -> here
end,
Action = case Is of
- [#b_set{op=succeeded}|_] -> here;
+ [#b_set{op={succeeded,_}}|_] -> here;
_ -> Action0
end,
case Action of
@@ -2207,9 +2653,9 @@ insert_def_is([], _V, Def) ->
%%% for combining get_tuple_element instructions.
%%%
-ssa_opt_get_tuple_element({#st{ssa=Blocks0}=St, FuncDb}) ->
+ssa_opt_get_tuple_element({#opt_st{ssa=Blocks0}=St, FuncDb}) ->
Blocks = opt_get_tuple_element(maps:to_list(Blocks0), Blocks0),
- {St#st{ssa=Blocks}, FuncDb}.
+ {St#opt_st{ssa=Blocks}, FuncDb}.
opt_get_tuple_element([{L,#b_blk{is=Is0}=Blk0}|Bs], Blocks) ->
case opt_get_tuple_element_is(Is0, false, []) of
@@ -2243,15 +2689,397 @@ collect_get_tuple_element(Is, _Src, Acc) ->
{Acc,Is}.
%%%
-%%% Common utilities.
+%%% Unfold literals to avoid unnecessary move instructions in call
+%%% instructions.
+%%%
+%%% Consider the following example:
+%%%
+%%% -module(whatever).
+%%% -export([foo/0]).
+%%% foo() ->
+%%% foobar(1, 2, 3).
+%%% foobar(A, B, C) ->
+%%% foobar(A, B, C, []).
+%%% foobar(A, B, C, D) -> ...
+%%%
+%%% The type optimization pass will find out that A, B, and C have constant
+%%% values and do constant folding, rewriting foobar/3 to:
+%%%
+%%% foobar(A, B, C) ->
+%%% foobar(1, 2, 3, []).
+%%%
+%%% That will result in three extra `move` instructions.
+%%%
+%%% This optimization sub pass will undo the constant folding
+%%% optimization, rewriting code to use the original variable instead
+%%% of the constant if the original variable is known to be in an x
+%%% register.
+%%%
+%%% This optimization sub pass will also undo constant folding of the
+%%% list of arguments in the call to error/2 in the last clause of a
+%%% function. For example:
+%%%
+%%% bar(X, Y) ->
+%%% error(function_clause, [X,42]).
+%%%
+%%% will be rewritten to:
+%%%
+%%% bar(X, Y) ->
+%%% error(function_clause, [X,Y]).
%%%
-gcd(A, B) ->
- case A rem B of
- 0 -> B;
- X -> gcd(B, X)
+ssa_opt_unfold_literals({St,FuncDb}) ->
+ #opt_st{ssa=Blocks0,args=Args,anno=Anno,cnt=Count0} = St,
+ ParamInfo = maps:get(parameter_info, Anno, #{}),
+ LitMap = collect_arg_literals(Args, ParamInfo, 0, #{}),
+ case map_size(LitMap) of
+ 0 ->
+ %% None of the arguments for this function are known
+ %% literals. Nothing to do.
+ {St,FuncDb};
+ _ ->
+ SafeMap = #{0 => true},
+ {Blocks,Count} = unfold_literals(beam_ssa:rpo(Blocks0),
+ LitMap, SafeMap, Count0, Blocks0),
+ {St#opt_st{ssa=Blocks,cnt=Count},FuncDb}
end.
+collect_arg_literals([V|Vs], Info, X, Acc0) ->
+ case Info of
+ #{V:=VarInfo} ->
+ Type = proplists:get_value(type, VarInfo, any),
+ case beam_types:get_singleton_value(Type) of
+ {ok,Val} ->
+ F = fun(Vars) -> [{X,V}|Vars] end,
+ Acc = maps:update_with(Val, F, [{X,V}], Acc0),
+ collect_arg_literals(Vs, Info, X + 1, Acc);
+ error ->
+ collect_arg_literals(Vs, Info, X + 1, Acc0)
+ end;
+ #{} ->
+ collect_arg_literals(Vs, Info, X + 1, Acc0)
+ end;
+collect_arg_literals([], _Info, _X, Acc) -> Acc.
+
+unfold_literals([L|Ls], LitMap, SafeMap0, Count0, Blocks0) ->
+ {Blocks,Safe,Count} =
+ case map_get(L, SafeMap0) of
+ false ->
+ %% Before reaching this block, an instruction that
+ %% clobbers x registers has been executed. *If* we
+ %% would use an argument variable instead of literal,
+ %% it would force the value to be saved to a y
+ %% register. This is not what we want.
+ {Blocks0,false,Count0};
+ true ->
+ %% All x registers live when entering the function
+ %% are still live. Using the variable instead of
+ %% the substituted value will eliminate a `move`
+ %% instruction.
+ #b_blk{is=Is0} = Blk = map_get(L, Blocks0),
+ {Is,Safe0,Count1} = unfold_lit_is(Is0, LitMap, Count0, []),
+ {Blocks0#{L:=Blk#b_blk{is=Is}},Safe0,Count1}
+ end,
+ %% Propagate safeness to successors.
+ Successors = beam_ssa:successors(L, Blocks),
+ SafeMap = unfold_update_succ(Successors, Safe, SafeMap0),
+ unfold_literals(Ls, LitMap, SafeMap, Count,Blocks);
+unfold_literals([], _, _, Count, Blocks) ->
+ {Blocks,Count}.
+
+unfold_update_succ([S|Ss], Safe, SafeMap0) ->
+ F = fun(Prev) -> Prev and Safe end,
+ SafeMap = maps:update_with(S, F, Safe, SafeMap0),
+ unfold_update_succ(Ss, Safe, SafeMap);
+unfold_update_succ([], _, SafeMap) -> SafeMap.
+
+unfold_lit_is([#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error},
+ arity=2},
+ #b_literal{val=function_clause},
+ ArgumentList]}=I0|Is], LitMap, Count0, Acc0) ->
+ %% This is a call to error/2 that raises a function_clause
+ %% exception in the final clause of a function. Try to undo
+ %% constant folding in the list of arguments (the second argument
+ %% for error/2).
+ case unfold_arg_list(Acc0, ArgumentList, LitMap, Count0, 0, []) of
+ {[FinalPutList|_]=Acc,Count} ->
+ %% Acc now contains the possibly rewritten code that
+ %% creates the argument list. All that remains is to
+ %% rewrite the call to error/2 itself so that is will
+ %% refer to rewritten argument list. This is essential
+ %% when all arguments have known literal values as in this
+ %% example:
+ %%
+ %% foo(X, Y) -> error(function_clause, [0,1]).
+ %%
+ #b_set{op=put_list,dst=ListVar} = FinalPutList,
+ #b_set{args=[ErlangError,Fc,_]} = I0,
+ I = I0#b_set{args=[ErlangError,Fc,ListVar]},
+ {reverse(Acc, [I|Is]),false,Count};
+ {[],_} ->
+ %% Handle code such as:
+ %%
+ %% bar(KnownValue, Stk) -> error(function_clause, Stk).
+ {reverse(Acc0, [I0|Is]),false,Count0}
+ end;
+unfold_lit_is([#b_set{op=Op,args=Args0}=I0|Is], LitMap, Count, Acc) ->
+ %% Using a register instead of a literal is a clear win only for
+ %% `call` and `make_fun` instructions. Substituting into other
+ %% instructions is unlikely to be an improvement.
+ Unfold = case Op of
+ call -> true;
+ make_fun -> true;
+ _ -> false
+ end,
+ I = case Unfold of
+ true ->
+ Args = unfold_call_args(Args0, LitMap, -1),
+ I0#b_set{args=Args};
+ false ->
+ I0
+ end,
+ case beam_ssa:clobbers_xregs(I) of
+ true ->
+ %% This instruction clobbers x register. Don't do
+ %% any substitutions in rest of this block or in any
+ %% of its successors.
+ {reverse(Acc, [I|Is]),false,Count};
+ false ->
+ unfold_lit_is(Is, LitMap, Count, [I|Acc])
+ end;
+unfold_lit_is([], _LitMap, Count, Acc) ->
+ {reverse(Acc),true,Count}.
+
+%% unfold_arg_list(Is, ArgumentList, LitMap, Count0, X, Acc) ->
+%% {UpdatedAcc, Count}.
+%%
+%% Unfold the arguments in the argument list (second argument for error/2).
+%%
+%% Note that Is is the reversed list of instructions before the
+%% call to error/2. Because of the way the list is built in reverse,
+%% it means that the first put_list instruction found will add the first
+%% argument (x0) to the list, the second the second argument (x1), and
+%% so on.
+
+unfold_arg_list(Is, #b_literal{val=[Hd|Tl]}, LitMap, Count0, X, Acc) ->
+ %% Handle the case that the entire argument list (the second argument
+ %% for error/2) is a literal.
+ {PutListDst,Count} = new_var('@put_list', Count0),
+ PutList = #b_set{op=put_list,dst=PutListDst,
+ args=[#b_literal{val=Hd},#b_literal{val=Tl}]},
+ unfold_arg_list([PutList|Is], PutListDst, LitMap, Count, X, Acc);
+unfold_arg_list([#b_set{op=put_list,dst=List,
+ args=[Hd0,#b_literal{val=[Hd|Tl]}]}=I0|Is0],
+ List, LitMap, Count0, X, Acc) ->
+ %% The rest of the argument list is a literal list.
+ {PutListDst,Count} = new_var('@put_list', Count0),
+ PutList = #b_set{op=put_list,dst=PutListDst,
+ args=[#b_literal{val=Hd},#b_literal{val=Tl}]},
+ I = I0#b_set{args=[Hd0,PutListDst]},
+ unfold_arg_list([I,PutList|Is0], List, LitMap, Count, X, Acc);
+unfold_arg_list([#b_set{op=put_list,dst=List,args=[Hd0,Tl]}=I0|Is],
+ List, LitMap, Count, X, Acc) ->
+ %% Unfold the head of the list.
+ Hd = unfold_arg(Hd0, LitMap, X),
+ I = I0#b_set{args=[Hd,Tl]},
+ unfold_arg_list(Is, Tl, LitMap, Count, X + 1, [I|Acc]);
+unfold_arg_list([I|Is], List, LitMap, Count, X, Acc) ->
+ %% Some other instruction, such as bs_get_tail.
+ unfold_arg_list(Is, List, LitMap, Count, X, [I|Acc]);
+unfold_arg_list([], _, _, Count, _, Acc) ->
+ {reverse(Acc),Count}.
+
+unfold_call_args([A0|As], LitMap, X) ->
+ A = unfold_arg(A0, LitMap, X),
+ [A|unfold_call_args(As, LitMap, X + 1)];
+unfold_call_args([], _, _) -> [].
+
+unfold_arg(#b_literal{val=Val}=Lit, LitMap, X) ->
+ case LitMap of
+ #{Val:=Vars} ->
+ %% This literal is available in an x register.
+ %% If it is in the correct x register, use
+ %% the register. Don't bother if it is in the
+ %% wrong register, because that would still result
+ %% in a `move` instruction.
+ case keyfind(X, 1, Vars) of
+ false -> Lit;
+ {X,Var} -> Var
+ end;
+ #{} -> Lit
+ end;
+unfold_arg(Expr, _LitMap, _X) -> Expr.
+
+%%%
+%%% Optimize tail calls created as the result of optimizations.
+%%%
+%%% Consider the following example of a tail call in Erlang code:
+%%%
+%%% bar() ->
+%%% foo().
+%%%
+%%% The SSA code for the call will look like this:
+%%%
+%%% @ssa_ret = call (`foo`/0)
+%%% ret @ssa_ret
+%%%
+%%% Sometimes optimizations create new tail calls. Consider this
+%%% slight variation of the example:
+%%%
+%%% bar() ->
+%%% {_,_} = foo().
+%%%
+%%% foo() -> {a,b}.
+%%%
+%%% If beam_ssa_type can figure out that `foo/0` always returns a tuple
+%%% of size two, the test for a tuple is no longer needed and the call
+%%% to `foo/0` will become a tail call. However, the resulting SSA
+%%% code will look like this:
+%%%
+%%% @ssa_ret = call (`foo`/0)
+%%% @ssa_bool = succeeded:body @ssa_ret
+%%% br @ssa_bool, ^999, ^1
+%%%
+%%% 999:
+%%% ret @ssa_ret
+%%%
+%%% The beam_ssa_codegen pass will not recognize this code as a tail
+%%% call and will generate an unncessary stack frame. It may also
+%%% generate unecessary `kill` instructions.
+%%%
+%%% To avoid those extra instructions, this optimization will
+%%% eliminate the `succeeded:body` and `br` instructions and insert
+%%% the `ret` in the same block as the call:
+%%%
+%%% @ssa_ret = call (`foo`/0)
+%%% ret @ssa_ret
+%%%
+%%% Finally, consider this example:
+%%%
+%%% bar() ->
+%%% foo_ok(),
+%%% ok.
+%%%
+%%% foo_ok() -> ok.
+%%%
+%%% The SSA code for the call to `foo_ok/0` will look like:
+%%%
+%%% %% Result type: `ok`
+%%% @ssa_ignored = call (`foo_ok`/0)
+%%% @ssa_bool = succeeded:body @ssa_ignored
+%%% br @ssa_bool, ^999, ^1
+%%%
+%%% 999:
+%%% ret `ok`
+%%%
+%%% Since the call to `foo_ok/0` has an annotation indicating that the
+%%% call will always return the atom `ok`, the code can be simplified
+%%% like this:
+%%%
+%%% @ssa_ignored = call (`foo_ok`/0)
+%%% ret @ssa_ignored
+%%%
+%%% The beam_jump pass does the same optimization, but it does it too
+%%% late to avoid creating an uncessary stack frame or unnecessary
+%%% `kill` instructions.
+%%%
+
+ssa_opt_tail_calls({St,FuncDb}) ->
+ #opt_st{ssa=Blocks0} = St,
+ Blocks = opt_tail_calls(beam_ssa:rpo(Blocks0), Blocks0),
+ {St#opt_st{ssa=Blocks},FuncDb}.
+
+opt_tail_calls([L|Ls], Blocks0) ->
+ #b_blk{is=Is0,last=Last} = Blk0 = map_get(L, Blocks0),
+
+ %% Does this block end with a two-way branch whose success
+ %% label targets an empty block with a `ret` terminator?
+ case is_potential_tail_call(Last, Blocks0) of
+ {yes,Bool,Ret} ->
+ %% Yes, `Ret` is the value returned from that block
+ %% (either a variable or literal). Do the instructions
+ %% in this block end with a `call` instruction that
+ %% returns the same value as `Ret`, followed by a
+ %% `succeeded:body` instruction?
+ case is_tail_call_is(Is0, Bool, Ret, []) of
+ {yes,Is,Var} ->
+ %% Yes, this is a tail call. `Is` is the instructions
+ %% in the block with `succeeded:body` removed, and
+ %% `Var` is the destination variable for the return
+ %% value of the call. Rewrite this block to directly
+ %% return `Var`.
+ Blk = Blk0#b_blk{is=Is,last=#b_ret{arg=Var}},
+ Blocks = Blocks0#{L:=Blk},
+ opt_tail_calls(Ls, Blocks);
+ no ->
+ %% No, the block does not end with a call, or the
+ %% the call instruction has not the same value
+ %% as `Ret`.
+ opt_tail_calls(Ls, Blocks0)
+ end;
+ no ->
+ opt_tail_calls(Ls, Blocks0)
+ end;
+opt_tail_calls([], Blocks) -> Blocks.
+
+is_potential_tail_call(#b_br{bool=#b_var{}=Bool,succ=Succ}, Blocks) ->
+ case Blocks of
+ #{Succ := #b_blk{is=[],last=#b_ret{arg=Arg}}} ->
+ %% This could be a tail call.
+ {yes,Bool,Arg};
+ #{} ->
+ %% The block is not empty or does not have a `ret` terminator.
+ no
+ end;
+is_potential_tail_call(_, _) ->
+ %% Not a two-way branch (a `succeeded:body` instruction must be
+ %% followed by a two-way branch).
+ no.
+
+is_tail_call_is([#b_set{op=call,dst=Dst}=Call,
+ #b_set{op={succeeded,body},dst=Bool}],
+ Bool, Ret, Acc) ->
+ IsTailCall =
+ case Ret of
+ #b_literal{val=Val} ->
+ %% The return value for this function is a literal.
+ %% Now test whether it is the same literal that the
+ %% `call` instruction returns.
+ Type = beam_ssa:get_anno(result_type, Call, any),
+ case beam_types:get_singleton_value(Type) of
+ {ok,Val} ->
+ %% Same value.
+ true;
+ {ok,_} ->
+ %% Wrong value.
+ false;
+ error ->
+ %% The type for the return value is not a singleton value.
+ false
+ end;
+ #b_var{} ->
+ %% It is a tail call if the variable set by the `call` instruction
+ %% is the same variable as the argument for the `ret` terminator.
+ Ret =:= Dst
+ end,
+ case IsTailCall of
+ true ->
+ %% Return the instructions in the block with `succeeded:body` removed.
+ Is = reverse(Acc, [Call]),
+ {yes,Is,Dst};
+ false ->
+ no
+ end;
+is_tail_call_is([I|Is], Bool, Ret, Acc) ->
+ is_tail_call_is(Is, Bool, Ret, [I|Acc]);
+is_tail_call_is([], _Bool, _Ret, _Acc) -> no.
+
+%%%
+%%% Common utilities.
+%%%
+
non_guards(Linear) ->
gb_sets:from_list(non_guards_1(Linear)).
@@ -2263,7 +3091,7 @@ non_guards_1([{L,#b_blk{is=Is}}|Bs]) ->
non_guards_1(Bs)
end;
non_guards_1([]) ->
- [?BADARG_BLOCK].
+ [?EXCEPTION_BLOCK].
rel2fam(S0) ->
S1 = sofs:relation(S0),
@@ -2300,4 +3128,6 @@ new_var(#b_var{name={Base,N}}, Count) ->
true = is_integer(N), %Assertion.
{#b_var{name={Base,Count}},Count+1};
new_var(#b_var{name=Base}, Count) ->
+ {#b_var{name={Base,Count}},Count+1};
+new_var(Base, Count) when is_atom(Base) ->
{#b_var{name={Base,Count}},Count+1}.
diff --git a/lib/compiler/src/beam_ssa_opt.hrl b/lib/compiler/src/beam_ssa_opt.hrl
index 37711a6f48..800096dce2 100644
--- a/lib/compiler/src/beam_ssa_opt.hrl
+++ b/lib/compiler/src/beam_ssa_opt.hrl
@@ -38,16 +38,34 @@
%% when dealing with co-recursive functions.
arg_types = [] :: list(arg_type_map()),
- %% The inferred return type of this function, this is either [type()]
- %% or [] to note absence.
- ret_type = [] :: list()}).
+ %% The success types of this function, grouping return values by their
+ %% argument types at the time of return.
+ %%
+ %% This gives us more precise types than a naive join of all returned
+ %% values, as we can rule out the cases where the arguments are
+ %% incompatible with the ones we're passing.
+ %%
+ %% Note that the argument types are those seen on successful return,
+ %% they do not cover all types that are provided to the function.
+ succ_types = [] :: success_type_set()}).
-type arg_key() :: {CallerId :: func_id(),
CallDst :: beam_ssa:b_var()}.
-type arg_type_map() :: #{ arg_key() => term() }.
+-type call_self() :: {call_self, ArgTypes :: [term()]}.
+-type success_type_set() :: [{ArgTypes :: [term()],
+ RetType :: call_self() | term()}].
+
%% Per-function metadata used by various optimization passes to perform
%% module-level optimization. If a function is absent it means that
%% module-level optimization has been turned off for said function.
-type func_id() :: beam_ssa:b_local().
-type func_info_db() :: #{ func_id() => #func_info{} }.
+
+-record(opt_st, {ssa :: [{beam_ssa:label(),beam_ssa:b_blk()}] |
+ beam_ssa:block_map(),
+ args :: [beam_ssa:b_var()],
+ cnt :: beam_ssa:label(),
+ anno :: beam_ssa:anno()}).
+-type st_map() :: #{ func_id() => #opt_st{} }.
diff --git a/lib/compiler/src/beam_ssa_pp.erl b/lib/compiler/src/beam_ssa_pp.erl
index 34ac08b32e..adb8533a81 100644
--- a/lib/compiler/src/beam_ssa_pp.erl
+++ b/lib/compiler/src/beam_ssa_pp.erl
@@ -35,10 +35,10 @@ format_function(#b_function{anno=Anno0,args=Args,
#{} ->
Anno0
end,
- ReachableBlocks = beam_ssa:rpo(Blocks),
- All = maps:keys(Blocks),
- Unreachable = ordsets:subtract(ordsets:from_list(All),
- ordsets:from_list(ReachableBlocks)),
+ ReachableBlocks = beam_ssa:rpo(Blocks),
+ All = maps:keys(Blocks),
+ Unreachable = ordsets:subtract(ordsets:from_list(All),
+ ordsets:from_list(ReachableBlocks)),
[case Anno0 of
#{location:={Filename,Line}} ->
io_lib:format("%% ~ts:~p\n", [Filename,Line]);
@@ -48,7 +48,8 @@ format_function(#b_function{anno=Anno0,args=Args,
io_lib:format("%% Counter = ~p\n", [Counter]),
[format_anno(Key, Value) ||
{Key,Value} <- lists:sort(maps:to_list(Anno))],
- io_lib:format("function ~p:~p(~ts) {\n", [M,F,format_args(Args, FuncAnno)]),
+ io_lib:format("function `~p`:`~p`(~ts) {\n",
+ [M, F, format_args(Args, FuncAnno)]),
[format_live_interval(Var, FuncAnno) || Var <- Args],
format_blocks(ReachableBlocks, Blocks, FuncAnno),
case Unreachable of
@@ -82,6 +83,20 @@ format_var(V) ->
%%% Local functions.
%%%
+format_anno(parameter_info, Map) when is_map(Map) ->
+ case map_size(Map) of
+ 0 ->
+ [];
+ _ ->
+ Params = lists:sort(maps:to_list(Map)),
+ Break = "\n%% ",
+ [io_lib:format("%% Parameters\n", []),
+ [io_lib:format("%% ~s =>~s~s\n",
+ [format_var(V),
+ Break,
+ format_param_info(I, Break)]) ||
+ {V,I} <- Params]]
+ end;
format_anno(Key, Map) when is_map(Map) ->
Sorted = lists:sort(maps:to_list(Map)),
[io_lib:format("%% ~s:\n", [Key]),
@@ -89,6 +104,20 @@ format_anno(Key, Map) when is_map(Map) ->
format_anno(Key, Value) ->
io_lib:format("%% ~s: ~p\n", [Key,Value]).
+format_param_info([{type, T} | Infos], Break) ->
+ [format_type(T, Break) |
+ format_param_info(Infos, Break)];
+format_param_info([Info | Infos], Break) ->
+ [io_lib:format("~s~p", [Break, Info]) |
+ format_param_info(Infos, Break)];
+format_param_info([], _Break) ->
+ [].
+
+format_type(T, Break) ->
+ %% Gross hack, but it's short and simple.
+ Indented = lists:flatten(io_lib:format("~p", [T])),
+ string:replace(Indented, [$\n], Break, all).
+
format_blocks(Ls, Blocks, Anno) ->
PP = [format_block(L, Blocks, Anno) || L <- Ls],
lists:join($\n, PP).
@@ -139,17 +168,20 @@ format_i_number(#{n:=N}) ->
io_lib:format("[~p] ", [N]);
format_i_number(#{}) -> [].
-format_terminator(#b_br{anno=A,bool=#b_literal{val=true},succ=Lbl}, _) ->
- io_lib:format(" ~sbr label ~p\n", [format_i_number(A),Lbl]);
-format_terminator(#b_br{anno=A,bool=#b_literal{val=false},fail=Lbl}, _) ->
- io_lib:format(" ~sbr label ~p\n", [format_i_number(A),Lbl]);
+format_terminator(#b_br{anno=A,bool=#b_literal{val=true},
+ succ=Same,fail=Same}, _) ->
+ io_lib:format(" ~sbr ~ts\n", [format_i_number(A),format_label(Same)]);
format_terminator(#b_br{anno=A,bool=Bool,succ=Succ,fail=Fail}, FuncAnno) ->
- io_lib:format(" ~sbr ~ts, label ~p, label ~p\n",
- [format_i_number(A),format_arg(Bool, FuncAnno),Succ,Fail]);
+ io_lib:format(" ~sbr ~ts, ~ts, ~ts\n",
+ [format_i_number(A),
+ format_arg(Bool, FuncAnno),
+ format_label(Succ),
+ format_label(Fail)]);
format_terminator(#b_switch{anno=A,arg=Arg,fail=Fail,list=List}, FuncAnno) ->
- io_lib:format(" ~sswitch ~ts, label ~p, ~ts\n",
- [format_i_number(A),format_arg(Arg, FuncAnno),Fail,
- format_list(List,FuncAnno)]);
+ io_lib:format(" ~sswitch ~ts, ~ts, ~ts\n",
+ [format_i_number(A),format_arg(Arg, FuncAnno),
+ format_label(Fail),
+ format_switch_list(List, FuncAnno)]);
format_terminator(#b_ret{anno=A,arg=Arg}, FuncAnno) ->
io_lib:format(" ~sret ~ts\n", [format_i_number(A),format_arg(Arg, FuncAnno)]).
@@ -189,30 +221,36 @@ format_args(Args, FuncAnno) ->
format_arg(#b_var{}=Arg, FuncAnno) ->
format_var(Arg, FuncAnno);
format_arg(#b_literal{val=Val}, _FuncAnno) ->
- io_lib:format("literal ~p", [Val]);
+ io_lib:format("`~p`", [Val]);
format_arg(#b_remote{mod=Mod,name=Name,arity=Arity}, FuncAnno) ->
- io_lib:format("remote (~ts):(~ts)/~p",
+ io_lib:format("(~ts:~ts/~p)",
[format_arg(Mod, FuncAnno),format_arg(Name, FuncAnno),Arity]);
format_arg(#b_local{name=Name,arity=Arity}, FuncAnno) ->
- io_lib:format("local ~ts/~p", [format_arg(Name, FuncAnno),Arity]);
+ io_lib:format("(~ts/~p)", [format_arg(Name, FuncAnno),Arity]);
format_arg({Value,Label}, FuncAnno) when is_integer(Label) ->
- io_lib:format("{ ~ts, ~p }", [format_arg(Value, FuncAnno),Label]);
+ io_lib:format("{ ~ts, ~ts }", [format_arg(Value, FuncAnno),
+ format_label(Label)]);
format_arg(Other, _) ->
io_lib:format("*** ~p ***", [Other]).
-format_list(List, FuncAnno) ->
- Ss = [io_lib:format("{ ~ts, ~ts }", [format_arg(Val, FuncAnno),format_label(L)]) ||
- {Val,L} <- List],
- io_lib:format("[ ~ts ]", [lists:join(", ", Ss)]).
+format_switch_list(List, FuncAnno) ->
+ Ss = [io_lib:format("{ ~ts, ~ts }", [format_arg(Val, FuncAnno),
+ format_label(L)]) || {Val,L} <- List],
+ io_lib:format("[\n ~ts\n ]", [lists:join(",\n ", Ss)]).
format_label(L) ->
- ["label ",integer_to_list(L)].
+ io_lib:format("^~w", [L]).
format_anno(#{n:=_}=Anno) ->
format_anno(maps:remove(n, Anno));
format_anno(#{location:={File,Line}}=Anno0) ->
Anno = maps:remove(location, Anno0),
- [io_lib:format(" %% ~ts:~p\n", [File,Line])|format_anno_1(Anno)];
+ [io_lib:format(" %% ~ts:~p\n", [File,Line])|format_anno(Anno)];
+format_anno(#{result_type:=T}=Anno0) ->
+ Anno = maps:remove(result_type, Anno0),
+ Break = "\n %% ",
+ [io_lib:format(" %% Result type:~s~s\n",
+ [Break, format_type(T, Break)]) | format_anno(Anno)];
format_anno(Anno) ->
format_anno_1(Anno).
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index 366f37d4d8..efab700dbb 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -71,7 +71,7 @@
-include("beam_ssa.hrl").
-import(lists, [all/2,any/2,append/1,duplicate/2,
- foldl/3,last/1,map/2,member/2,partition/2,
+ foldl/3,last/1,member/2,partition/2,
reverse/1,reverse/2,sort/1,splitwith/2,zip/2]).
-spec module(beam_ssa:b_module(), [compile:option()]) ->
@@ -108,7 +108,8 @@ functions([], _Ps, _UseBSM3) -> [].
intervals=[] :: [{b_var(),[range()]}],
res=[] :: [{b_var(),reservation()}] | #{b_var():=reservation()},
regs=#{} :: #{b_var():=ssa_register()},
- extra_annos=[] :: [{atom(),term()}]
+ extra_annos=[] :: [{atom(),term()}],
+ location :: term()
}).
-define(PASS(N), {N,fun N/1}).
@@ -120,6 +121,7 @@ passes(Opts) ->
%% Preliminaries.
?PASS(fix_bs),
?PASS(sanitize),
+ ?PASS(match_fail_instructions),
case FixTuples of
false -> ignore;
true -> ?PASS(fix_tuples)
@@ -164,7 +166,9 @@ passes(Opts) ->
function(#b_function{anno=Anno,args=Args,bs=Blocks0,cnt=Count0}=F0,
Ps, UseBSM3) ->
try
- St0 = #st{ssa=Blocks0,args=Args,use_bsm3=UseBSM3,cnt=Count0},
+ Location = maps:get(location, Anno, none),
+ St0 = #st{ssa=Blocks0,args=Args,use_bsm3=UseBSM3,
+ cnt=Count0,location=Location},
St = compile:run_sub_passes(Ps, St0),
#st{ssa=Blocks,cnt=Count,regs=Regs,extra_annos=ExtraAnnos} = St,
F1 = add_extra_annos(F0, ExtraAnnos),
@@ -253,25 +257,26 @@ bs_pos_bsm3(Linear0, CtxChain, Count0) ->
S = sofs:to_external(S1),
{SavePoints,Count1} = make_bs_pos_dict(S, Count0, []),
- {Gets,Count2} = make_bs_setpos_map(Rs, SavePoints, Count1, []),
- {Sets,Count} = make_bs_getpos_map(maps:to_list(Rs0), SavePoints, Count2, []),
+
+ {Gets,Count2} = make_bs_getpos_map(Rs, SavePoints, Count1, []),
+ {Sets,Count} = make_bs_setpos_map(maps:to_list(Rs0), SavePoints, Count2, []),
%% Now insert all saves and restores.
- {bs_insert_bsm3(Linear0, Gets, Sets, SavePoints),Count}.
+ {bs_insert_bsm3(Linear0, Gets, Sets), Count}.
-make_bs_setpos_map([{Ctx,Save}=Ps|T], SavePoints, Count, Acc) ->
+make_bs_getpos_map([{Ctx,Save}=Ps|T], SavePoints, Count, Acc) ->
SavePoint = get_savepoint(Ps, SavePoints),
I = #b_set{op=bs_get_position,dst=SavePoint,args=[Ctx]},
- make_bs_setpos_map(T, SavePoints, Count+1, [{Save,I}|Acc]);
-make_bs_setpos_map([], _, Count, Acc) ->
+ make_bs_getpos_map(T, SavePoints, Count+1, [{Save,I}|Acc]);
+make_bs_getpos_map([], _, Count, Acc) ->
{maps:from_list(Acc),Count}.
-make_bs_getpos_map([{Bef,{Ctx,_}=Ps}|T], SavePoints, Count, Acc) ->
+make_bs_setpos_map([{Bef,{Ctx,_}=Ps}|T], SavePoints, Count, Acc) ->
Ignored = #b_var{name={'@ssa_ignored',Count}},
Args = [Ctx, get_savepoint(Ps, SavePoints)],
I = #b_set{op=bs_set_position,dst=Ignored,args=Args},
- make_bs_getpos_map(T, SavePoints, Count+1, [{Bef,I}|Acc]);
-make_bs_getpos_map([], _, Count, Acc) ->
+ make_bs_setpos_map(T, SavePoints, Count+1, [{Bef,I}|Acc]);
+make_bs_setpos_map([], _, Count, Acc) ->
{maps:from_list(Acc),Count}.
get_savepoint({_,_}=Ps, SavePoints) ->
@@ -375,15 +380,25 @@ join_positions([{L,MapPos0}|T], D) ->
end;
join_positions([], D) -> D.
-join_positions_1(MapPos0, MapPos1) ->
- MapPos2 = maps:map(fun(Start, Pos) ->
- case MapPos0 of
- #{Start:=Pos} -> Pos;
- #{Start:=_} -> unknown;
- #{} -> Pos
- end
- end, MapPos1),
- maps:merge(MapPos0, MapPos2).
+join_positions_1(LHS, RHS) ->
+ if
+ map_size(LHS) < map_size(RHS) ->
+ join_positions_2(maps:keys(LHS), RHS, LHS);
+ true ->
+ join_positions_2(maps:keys(RHS), LHS, RHS)
+ end.
+
+join_positions_2([V | Vs], Bigger, Smaller) ->
+ case {Bigger, Smaller} of
+ {#{ V := Same }, #{ V := Same }} ->
+ join_positions_2(Vs, Bigger, Smaller);
+ {#{ V := _ }, #{ V := _ }} ->
+ join_positions_2(Vs, Bigger, Smaller#{ V := unknown });
+ {#{}, #{ V := _ }} ->
+ join_positions_2(Vs, Bigger, maps:remove(V, Smaller))
+ end;
+join_positions_2([], _Bigger, Smaller) ->
+ Smaller.
%%
%% Updates the restore and position maps according to the given instructions.
@@ -394,30 +409,37 @@ join_positions_1(MapPos0, MapPos1) ->
%%
bs_restores_is([#b_set{op=bs_start_match,dst=Start}|Is],
- CtxChain, SPos0, FPos, Rs) ->
- %% We only allow one match per block.
- SPos0 = FPos, %Assertion.
+ CtxChain, SPos0, _FPos, Rs) ->
+ %% Match instructions leave the position unchanged on failure, so
+ %% FPos must be the SPos we entered the *instruction* with, and not the
+ %% *block*.
+ %%
+ %% This is important when we have multiple matches in a single block where
+ %% all but the last are guaranteed to succeed; the upcoming fail block must
+ %% restore to the position of the next-to-last match, not the position we
+ %% entered the current block with.
+ FPos = SPos0,
SPos = SPos0#{Start=>Start},
bs_restores_is(Is, CtxChain, SPos, FPos, Rs);
bs_restores_is([#b_set{op=bs_match,dst=NewPos,args=Args}=I|Is],
- CtxChain, SPos0, FPos0, Rs0) ->
- SPos0 = FPos0, %Assertion.
+ CtxChain, SPos0, _FPos, Rs0) ->
Start = bs_subst_ctx(NewPos, CtxChain),
[_,FromPos|_] = Args,
case SPos0 of
#{Start:=FromPos} ->
%% Same position, no restore needed.
SPos = case bs_match_type(I) of
- plain ->
- %% Update position to new position.
- SPos0#{Start:=NewPos};
- _ ->
- %% Position will not change (test_unit
- %% instruction or no instruction at
- %% all).
- SPos0#{Start:=FromPos}
- end,
- bs_restores_is(Is, CtxChain, SPos, FPos0, Rs0);
+ plain ->
+ %% Update position to new position.
+ SPos0#{Start:=NewPos};
+ _ ->
+ %% Position will not change (test_unit
+ %% instruction or no instruction at
+ %% all).
+ SPos0
+ end,
+ FPos = SPos0,
+ bs_restores_is(Is, CtxChain, SPos, FPos, Rs0);
#{Start:=_} ->
%% Different positions, might need a restore instruction.
case bs_match_type(I) of
@@ -425,50 +447,64 @@ bs_restores_is([#b_set{op=bs_match,dst=NewPos,args=Args}=I|Is],
%% This is a tail test that will be optimized away.
%% There's no need to do a restore, and all
%% positions are unchanged.
- bs_restores_is(Is, CtxChain, SPos0, FPos0, Rs0);
+ FPos = SPos0,
+ bs_restores_is(Is, CtxChain, SPos0, FPos, Rs0);
test_unit ->
%% This match instruction will be replaced by
%% a test_unit instruction. We will need a
%% restore. The new position will be the position
%% restored to (NOT NewPos).
SPos = SPos0#{Start:=FromPos},
- FPos = FPos0#{Start:=FromPos},
+ FPos = SPos,
Rs = Rs0#{NewPos=>{Start,FromPos}},
bs_restores_is(Is, CtxChain, SPos, FPos, Rs);
plain ->
%% Match or skip. Position will be changed.
SPos = SPos0#{Start:=NewPos},
- FPos = FPos0#{Start:=FromPos},
+ FPos = SPos0#{Start:=FromPos},
Rs = Rs0#{NewPos=>{Start,FromPos}},
bs_restores_is(Is, CtxChain, SPos, FPos, Rs)
end
end;
bs_restores_is([#b_set{op=bs_extract,args=[FromPos|_]}|Is],
- CtxChain, SPos, FPos, Rs) ->
+ CtxChain, SPos, _FPos, Rs) ->
Start = bs_subst_ctx(FromPos, CtxChain),
+
#{Start:=FromPos} = SPos, %Assertion.
- #{Start:=FromPos} = FPos, %Assertion.
+ FPos = SPos,
+
bs_restores_is(Is, CtxChain, SPos, FPos, Rs);
bs_restores_is([#b_set{op=call,dst=Dst,args=Args}|Is],
- CtxChain, SPos0, FPos0, Rs0) ->
- {Rs, SPos1, FPos1} = bs_restore_args(Args, SPos0, FPos0, CtxChain, Dst, Rs0),
- {SPos, FPos} = bs_invalidate_pos(Args, SPos1, FPos1, CtxChain),
- bs_restores_is(Is, CtxChain, SPos, FPos, Rs);
-bs_restores_is([#b_set{op=landingpad}|Is], CtxChain, SPos0, FPos0, Rs) ->
- %% We can land here from any point, so all positions are invalid.
- Invalidate = fun(_Start,_Pos) -> unknown end,
- SPos = maps:map(Invalidate, SPos0),
- FPos = maps:map(Invalidate, FPos0),
+ CtxChain, SPos0, _FPos, Rs0) ->
+ {SPos1, Rs} = bs_restore_args(Args, SPos0, CtxChain, Dst, Rs0),
+
+ SPos = bs_invalidate_pos(Args, SPos1, CtxChain),
+ FPos = SPos,
+
bs_restores_is(Is, CtxChain, SPos, FPos, Rs);
bs_restores_is([#b_set{op=Op,dst=Dst,args=Args}|Is],
- CtxChain, SPos0, FPos0, Rs0)
+ CtxChain, SPos0, _FPos, Rs0)
when Op =:= bs_test_tail;
Op =:= bs_get_tail ->
- {Rs, SPos, FPos} = bs_restore_args(Args, SPos0, FPos0, CtxChain, Dst, Rs0),
+ {SPos, Rs} = bs_restore_args(Args, SPos0, CtxChain, Dst, Rs0),
+ FPos = SPos,
+
bs_restores_is(Is, CtxChain, SPos, FPos, Rs);
-bs_restores_is([_|Is], CtxChain, SPos, FPos, Rs) ->
+bs_restores_is([#b_set{op={succeeded,guard},args=[Arg]}],
+ CtxChain, SPos, FPos0, Rs) ->
+ %% If we're branching on a match operation, the positions will be different
+ %% depending on whether it succeeds.
+ Ctx = bs_subst_ctx(Arg, CtxChain),
+ FPos = case SPos of
+ #{ Ctx := _ } -> FPos0;
+ #{} -> SPos
+ end,
+ {SPos, FPos, Rs};
+bs_restores_is([_ | Is], CtxChain, SPos, _FPos, Rs) ->
+ FPos = SPos,
bs_restores_is(Is, CtxChain, SPos, FPos, Rs);
-bs_restores_is([], _CtxChain, SPos, FPos, Rs) ->
+bs_restores_is([], _CtxChain, SPos, _FPos, Rs) ->
+ FPos = SPos,
{SPos, FPos, Rs}.
bs_match_type(#b_set{args=[#b_literal{val=skip},_Ctx,
@@ -483,54 +519,52 @@ bs_match_type(_) ->
%% Call instructions leave the match position in an undefined state,
%% requiring us to invalidate each affected argument.
-bs_invalidate_pos([#b_var{}=Arg|Args], SPos0, FPos0, CtxChain) ->
+bs_invalidate_pos([#b_var{}=Arg|Args], Pos0, CtxChain) ->
Start = bs_subst_ctx(Arg, CtxChain),
- case SPos0 of
+ case Pos0 of
#{Start:=_} ->
- SPos = SPos0#{Start:=unknown},
- FPos = FPos0#{Start:=unknown},
- bs_invalidate_pos(Args, SPos, FPos, CtxChain);
+ Pos = Pos0#{Start:=unknown},
+ bs_invalidate_pos(Args, Pos, CtxChain);
#{} ->
%% Not a match context.
- bs_invalidate_pos(Args, SPos0, FPos0, CtxChain)
+ bs_invalidate_pos(Args, Pos0, CtxChain)
end;
-bs_invalidate_pos([_|Args], SPos, FPos, CtxChain) ->
- bs_invalidate_pos(Args, SPos, FPos, CtxChain);
-bs_invalidate_pos([], SPos, FPos, _CtxChain) ->
- {SPos, FPos}.
+bs_invalidate_pos([_|Args], Pos, CtxChain) ->
+ bs_invalidate_pos(Args, Pos, CtxChain);
+bs_invalidate_pos([], Pos, _CtxChain) ->
+ Pos.
-bs_restore_args([#b_var{}=Arg|Args], SPos0, FPos0, CtxChain, Dst, Rs0) ->
+bs_restore_args([#b_var{}=Arg|Args], Pos0, CtxChain, Dst, Rs0) ->
Start = bs_subst_ctx(Arg, CtxChain),
- case SPos0 of
+ case Pos0 of
#{Start:=Arg} ->
%% Same position, no restore needed.
- bs_restore_args(Args, SPos0, FPos0, CtxChain, Dst, Rs0);
+ bs_restore_args(Args, Pos0, CtxChain, Dst, Rs0);
#{Start:=_} ->
%% Different positions, need a restore instruction.
- SPos = SPos0#{Start:=Arg},
- FPos = FPos0#{Start:=Arg},
+ Pos = Pos0#{Start:=Arg},
Rs = Rs0#{Dst=>{Start,Arg}},
- bs_restore_args(Args, SPos, FPos, CtxChain, Dst, Rs);
+ bs_restore_args(Args, Pos, CtxChain, Dst, Rs);
#{} ->
%% Not a match context.
- bs_restore_args(Args, SPos0, FPos0, CtxChain, Dst, Rs0)
+ bs_restore_args(Args, Pos0, CtxChain, Dst, Rs0)
end;
-bs_restore_args([_|Args], SPos, FPos, CtxChain, Dst, Rs) ->
- bs_restore_args(Args, SPos, FPos, CtxChain, Dst, Rs);
-bs_restore_args([], SPos, FPos, _CtxChain, _Dst, Rs) ->
- {Rs,SPos,FPos}.
+bs_restore_args([_|Args], Pos, CtxChain, Dst, Rs) ->
+ bs_restore_args(Args, Pos, CtxChain, Dst, Rs);
+bs_restore_args([], Pos, _CtxChain, _Dst, Rs) ->
+ {Pos, Rs}.
%% Insert all bs_save and bs_restore instructions.
-bs_insert_bsm3(Blocks, Saves, Restores, SavePoints) ->
- bs_insert_1(Blocks, Saves, Restores, SavePoints, fun(I) -> I end).
+bs_insert_bsm3(Blocks, Saves, Restores) ->
+ bs_insert_1(Blocks, [], Saves, Restores, fun(I) -> I end).
-bs_insert_bsm2(Blocks, Saves, Restores, SavePoints) ->
+bs_insert_bsm2(Blocks, Saves, Restores, Slots) ->
%% The old instructions require bs_start_match to be annotated with the
%% number of position slots it needs.
- bs_insert_1(Blocks, Saves, Restores, SavePoints,
+ bs_insert_1(Blocks, [], Saves, Restores,
fun(#b_set{op=bs_start_match,dst=Dst}=I0) ->
- NumSlots = case SavePoints of
+ NumSlots = case Slots of
#{Dst:=NumSlots0} -> NumSlots0;
#{} -> 0
end,
@@ -539,46 +573,38 @@ bs_insert_bsm2(Blocks, Saves, Restores, SavePoints) ->
I
end).
-bs_insert_1([{L,#b_blk{is=Is0}=Blk}|Bs0], Saves, Restores, Slots, XFrm) ->
- Is = bs_insert_is_1(Is0, Restores, Slots, XFrm),
- Bs = bs_insert_saves(Is, Bs0, Saves),
- [{L,Blk#b_blk{is=Is}}|bs_insert_1(Bs, Saves, Restores, Slots, XFrm)];
-bs_insert_1([], _, _, _, _) -> [].
+bs_insert_1([{L,#b_blk{is=Is0}=Blk} | Bs], Deferred0, Saves, Restores, XFrm) ->
+ Is1 = bs_insert_deferred(Is0, Deferred0),
+ {Is, Deferred} = bs_insert_is(Is1, Saves, Restores, XFrm, []),
+ [{L,Blk#b_blk{is=Is}} | bs_insert_1(Bs, Deferred, Saves, Restores, XFrm)];
+bs_insert_1([], [], _, _, _) ->
+ [].
-bs_insert_is_1([#b_set{op=Op,dst=Dst}=I0|Is], Restores, SavePoints, XFrm) ->
- I = XFrm(I0),
- if
- Op =:= bs_test_tail;
- Op =:= bs_get_tail;
- Op =:= bs_match;
- Op =:= call ->
- Rs = case Restores of
- #{Dst:=R} -> [R];
- #{} -> []
- end,
- Rs ++ [I|bs_insert_is_1(Is, Restores, SavePoints, XFrm)];
- true ->
- [I|bs_insert_is_1(Is, Restores, SavePoints, XFrm)]
- end;
-bs_insert_is_1([], _, _, _) -> [].
+bs_insert_deferred([#b_set{op=bs_extract}=I | Is], Deferred) ->
+ [I | bs_insert_deferred(Is, Deferred)];
+bs_insert_deferred(Is, Deferred) ->
+ Deferred ++ Is.
-bs_insert_saves([#b_set{dst=Dst}|Is], Bs, Saves) ->
- case Saves of
- #{Dst:=S} ->
- bs_insert_save(S, Bs);
- #{} ->
- bs_insert_saves(Is, Bs, Saves)
+bs_insert_is([#b_set{dst=Dst}=I0|Is], Saves, Restores, XFrm, Acc0) ->
+ I = XFrm(I0),
+ Pre = case Restores of
+ #{Dst:=R} -> [R];
+ #{} -> []
+ end,
+ Post = case Saves of
+ #{Dst:=S} -> [S];
+ #{} -> []
+ end,
+ Acc = [I | Pre] ++ Acc0,
+ case Is of
+ [#b_set{op={succeeded,_},args=[Dst]}] ->
+ %% Defer the save sequence to the success block.
+ {reverse(Acc, Is), Post};
+ _ ->
+ bs_insert_is(Is, Saves, Restores, XFrm, Post ++ Acc)
end;
-bs_insert_saves([], Bs, _) -> Bs.
-
-bs_insert_save(Save, [{L,#b_blk{is=Is0}=Blk}|Bs]) ->
- Is = case Is0 of
- [#b_set{op=bs_extract}=Ex|Is1] ->
- [Ex,Save|Is1];
- _ ->
- [Save|Is0]
- end,
- [{L,Blk#b_blk{is=Is}}|Bs].
+bs_insert_is([], _, _, _, Acc) ->
+ {reverse(Acc), []}.
%% Translate bs_match instructions to bs_get, bs_match_string,
%% or bs_skip. Also rename match context variables to use the
@@ -598,6 +624,10 @@ bs_instrs([{L,#b_blk{is=Is0}=Blk}|Bs], CtxChain, Acc0) ->
bs_instrs([], _, Acc) ->
reverse(Acc).
+bs_instrs_is([#b_set{op={succeeded,_}}=I|Is], CtxChain, Acc) ->
+ %% This instruction refers to a specific operation, so we must not
+ %% substitute the context argument.
+ bs_instrs_is(Is, CtxChain, [I | Acc]);
bs_instrs_is([#b_set{op=Op,args=Args0}=I0|Is], CtxChain, Acc) ->
Args = [bs_subst_ctx(A, CtxChain) || A <- Args0],
I1 = I0#b_set{args=Args},
@@ -606,8 +636,6 @@ bs_instrs_is([#b_set{op=Op,args=Args0}=I0|Is], CtxChain, Acc) ->
I1#b_set{op=bs_skip,args=[Type,Ctx|As]};
{bs_match,[#b_literal{val=string},Ctx|As]} ->
I1#b_set{op=bs_match_string,args=[Ctx|As]};
- {bs_get_tail,[Ctx|As]} ->
- I1#b_set{op=bs_get_tail,args=[Ctx|As]};
{_,_} ->
I1
end,
@@ -706,12 +734,12 @@ sanitize(#st{ssa=Blocks0,cnt=Count0}=St) ->
St#st{ssa=Blocks,cnt=Count}.
sanitize([L|Ls], Count0, Blocks0, Values0) ->
- #b_blk{is=Is0} = Blk0 = map_get(L, Blocks0),
- case sanitize_is(Is0, Count0, Values0, false, []) of
+ #b_blk{is=Is0,last=Last0} = Blk0 = map_get(L, Blocks0),
+ case sanitize_is(Is0, Last0, Count0, Values0, false, []) of
no_change ->
sanitize(Ls, Count0, Blocks0, Values0);
- {Is,Count,Values} ->
- Blk = Blk0#b_blk{is=Is},
+ {Is,Last,Count,Values} ->
+ Blk = Blk0#b_blk{is=Is,last=Last},
Blocks = Blocks0#{L:=Blk},
sanitize(Ls, Count, Blocks, Values)
end;
@@ -732,50 +760,147 @@ sanitize([], Count, Blocks0, Values) ->
end,Count}.
sanitize_is([#b_set{op=get_map_element,args=Args0}=I0|Is],
- Count0, Values, Changed, Acc) ->
+ Last, Count0, Values, Changed, Acc) ->
case sanitize_args(Args0, Values) of
[#b_literal{}=Map,Key] ->
%% Bind the literal map to a variable.
{MapVar,Count} = new_var('@ssa_map', Count0),
I = I0#b_set{args=[MapVar,Key]},
Copy = #b_set{op=copy,dst=MapVar,args=[Map]},
- sanitize_is(Is, Count, Values, true, [I,Copy|Acc]);
+ sanitize_is(Is, Last, Count, Values, true, [I,Copy|Acc]);
[_,_]=Args0 ->
- sanitize_is(Is, Count0, Values, Changed, [I0|Acc]);
+ sanitize_is(Is, Last, Count0, Values, Changed, [I0|Acc]);
[_,_]=Args ->
I = I0#b_set{args=Args},
- sanitize_is(Is, Count0, Values, Changed, [I|Acc])
+ sanitize_is(Is, Last, Count0, Values, true, [I|Acc])
end;
-sanitize_is([#b_set{op=Op,dst=Dst,args=Args0}=I0|Is0],
- Count, Values, Changed0, Acc) ->
+sanitize_is([#b_set{op={succeeded,guard},dst=Dst,args=[Arg0]}=I0],
+ #b_br{bool=Dst}=Last, Count, Values, _Changed, Acc0) ->
+ %% We no longer need to distinguish between guard and body checks, so we'll
+ %% rewrite this as a plain 'succeeded'.
+ case sanitize_arg(Arg0, Values) of
+ #b_var{}=Arg ->
+ case Acc0 of
+ [#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error},
+ arity=1},_],
+ dst=Arg0}|Acc] ->
+ %% This erlang:error/1 is the result from a
+ %% sanitized bs_add or bs_init instruction. Calls
+ %% to erlang:error/1 in receive is not allowed, so
+ %% we will have to rewrite this instruction
+ %% sequence to an unconditional branch to the
+ %% failure label.
+ Fail = Last#b_br.fail,
+ Br = #b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail},
+ {reverse(Acc), Br, Count, Values};
+ _ ->
+ I = I0#b_set{op=succeeded,args=[Arg]},
+ {reverse(Acc0, [I]), Last, Count, Values}
+ end;
+ #b_literal{} ->
+ Value = #b_literal{val=true},
+ {reverse(Acc0), Last, Count, Values#{ Dst => Value }}
+ end;
+sanitize_is([#b_set{op={succeeded,body},dst=Dst,args=[Arg0]}=I0],
+ #b_br{bool=Dst}=Last, Count, Values, _Changed, Acc) ->
+ %% We no longer need to distinguish between guard and body checks, so we'll
+ %% rewrite this as a plain 'succeeded'.
+ case sanitize_arg(Arg0, Values) of
+ #b_var{}=Arg ->
+ I = I0#b_set{op=succeeded,args=[Arg]},
+ {reverse(Acc, [I]), Last, Count, Values};
+ #b_literal{} ->
+ Value = #b_literal{val=true},
+ {reverse(Acc), Last, Count, Values#{ Dst => Value }}
+ end;
+sanitize_is([#b_set{op={succeeded,Kind},args=[Arg0]} | Is],
+ Last, Count, Values, _Changed, Acc) ->
+ %% We're no longer branching on this instruction and can safely remove it.
+ [] = Is, #b_br{succ=Same,fail=Same} = Last, %Assertion.
+ if
+ Same =:= ?EXCEPTION_BLOCK ->
+ %% The checked instruction always fails at runtime and we're not
+ %% in a try/catch; rewrite the terminator to a return.
+ body = Kind, %Assertion.
+ Arg = sanitize_arg(Arg0, Values),
+ sanitize_is(Is, #b_ret{arg=Arg}, Count, Values, true, Acc);
+ Same =/= ?EXCEPTION_BLOCK ->
+ %% We either always succeed, or always fail to somewhere other than
+ %% the exception block.
+ true = Kind =:= guard orelse Kind =:= body, %Assertion.
+ sanitize_is(Is, Last, Count, Values, true, Acc)
+ end;
+sanitize_is([#b_set{op=Op}=I|Is], Last, Count, Values, Changed, Acc) ->
+ case Last of
+ #b_br{succ=Same,fail=Same} ->
+ case is_test_op(Op) of
+ true ->
+ sanitize_is(Is, Last, Count, Values, true, Acc);
+ false ->
+ do_sanitize_is(I, Is, Last, Count, Values, Changed, Acc)
+ end;
+ _ ->
+ do_sanitize_is(I, Is, Last, Count, Values, Changed, Acc)
+ end;
+sanitize_is([], Last, Count, Values, Changed, Acc) ->
+ case Changed of
+ true ->
+ {reverse(Acc), Last, Count, Values};
+ false ->
+ no_change
+ end.
+
+is_test_op(bs_put) -> true;
+is_test_op(bs_test_tail) -> true;
+is_test_op(_) -> false.
+
+do_sanitize_is(#b_set{op=Op,dst=Dst,args=Args0}=I0,
+ Is, Last, Count, Values, Changed0, Acc) ->
Args = sanitize_args(Args0, Values),
case sanitize_instr(Op, Args, I0) of
{value,Value0} ->
Value = #b_literal{val=Value0},
- sanitize_is(Is0, Count, Values#{Dst=>Value}, true, Acc);
+ sanitize_is(Is, Last, Count, Values#{Dst=>Value}, true, Acc);
{ok,I} ->
- sanitize_is(Is0, Count, Values, true, [I|Acc]);
+ sanitize_is(Is, Last, Count, Values, true, [I|Acc]);
ok ->
I = I0#b_set{args=Args},
Changed = Changed0 orelse Args =/= Args0,
- sanitize_is(Is0, Count, Values, Changed, [I|Acc])
- end;
-sanitize_is([], Count, Values, Changed, Acc) ->
- case Changed of
- true ->
- {reverse(Acc),Count,Values};
- false ->
- no_change
+ sanitize_is(Is, Last, Count, Values, Changed, [I|Acc])
end.
sanitize_args(Args, Values) ->
- map(fun(Var) ->
- case Values of
- #{Var:=New} -> New;
- #{} -> Var
- end
- end, Args).
+ [sanitize_arg(Arg, Values) || Arg <- Args].
+
+sanitize_arg(#b_var{}=Var, Values) ->
+ case Values of
+ #{Var:=New} -> New;
+ #{} -> Var
+ end;
+sanitize_arg(Arg, _Values) ->
+ Arg.
+
+sanitize_instr(phi, PhiArgs, _I) ->
+ case phi_all_same_literal(PhiArgs) of
+ true ->
+ %% (Can only happen when some optimizations have been
+ %% turned off.)
+ %%
+ %% This phi node always produces the same literal value.
+ %% We must do constant progation of the value to ensure
+ %% that we can sanitize any instructions that don't accept
+ %% literals (such as `get_hd`). This is necessary for
+ %% correctness, because beam_ssa_codegen:prefer_xregs/2
+ %% does constant propagation and could propagate a literal
+ %% into an instruction that don't accept literals.
+ [{#b_literal{val=Val},_}|_] = PhiArgs,
+ {value,Val};
+ false ->
+ ok
+ end;
sanitize_instr({bif,Bif}, [#b_literal{val=Lit}], _I) ->
case erl_bifs:is_pure(erlang, Bif, 1) of
false ->
@@ -796,6 +921,12 @@ sanitize_instr({bif,Bif}, [#b_literal{val=Lit1},#b_literal{val=Lit2}], _I) ->
error:_ ->
ok
end;
+sanitize_instr(bs_match, Args, I) ->
+ %% Matching of floats are never changed to a bs_skip even when the
+ %% value is never used, because the match can always fail (for example,
+ %% if it is a NaN).
+ [#b_literal{val=float}|_] = Args, %Assertion.
+ {ok,I#b_set{op=bs_get}};
sanitize_instr(get_hd, [#b_literal{val=[Hd|_]}], _I) ->
{value,Hd};
sanitize_instr(get_tl, [#b_literal{val=[_|Tl]}], _I) ->
@@ -819,19 +950,25 @@ sanitize_instr(is_tagged_tuple, [#b_literal{val=Tuple},
true ->
{value,false}
end;
+sanitize_instr(bs_add, [Arg1,Arg2,_|_], I0) ->
+ case all(fun(#b_literal{val=Size}) -> is_integer(Size) andalso Size >= 0;
+ (#b_var{}) -> true
+ end, [Arg1,Arg2]) of
+ true -> ok;
+ false -> {ok,sanitize_badarg(I0)}
+ end;
sanitize_instr(bs_init, [#b_literal{val=new},#b_literal{val=Sz}|_], I0) ->
if
is_integer(Sz), Sz >= 0 -> ok;
true -> {ok,sanitize_badarg(I0)}
end;
-sanitize_instr(bs_init, [#b_literal{val=append},_,#b_literal{val=Sz}|_], I0) ->
+sanitize_instr(bs_init, [#b_literal{},_,#b_literal{val=Sz}|_], I0) ->
if
is_integer(Sz), Sz >= 0 -> ok;
true -> {ok,sanitize_badarg(I0)}
end;
-sanitize_instr(succeeded, [#b_literal{}], _I) ->
- {value,true};
-sanitize_instr(_, _, _) -> ok.
+sanitize_instr(_, _, _) ->
+ ok.
sanitize_badarg(I) ->
Func = #b_remote{mod=#b_literal{val=erlang},
@@ -856,6 +993,126 @@ prune_phi(#b_set{args=Args0}=Phi, Reachable) ->
gb_sets:is_element(Pred, Reachable)],
Phi#b_set{args=Args}.
+phi_all_same_literal([{#b_literal{}=Arg, _From} | Phis]) ->
+ phi_all_same_literal_1(Phis, Arg);
+phi_all_same_literal([_|_]) ->
+ false.
+
+phi_all_same_literal_1([{Arg, _From} | Phis], Arg) ->
+ phi_all_same_literal_1(Phis, Arg);
+phi_all_same_literal_1([], _Arg) ->
+ true;
+phi_all_same_literal_1(_Phis, _Arg) ->
+ false.
+
+%%% Rewrite certain calls to erlang:error/{1,2} to specialized
+%%% instructions:
+%%%
+%%% erlang:error({badmatch,Value}) => badmatch Value
+%%% erlang:error({case_clause,Value}) => case_end Value
+%%% erlang:error({try_clause,Value}) => try_case_end Value
+%%% erlang:error(if_clause) => if_end
+%%% erlang:error(function_clause, Args) => jump FuncInfoLabel
+%%%
+%%% In SSA code, we represent those instructions as a 'match_fail'
+%%% instruction with the name of the BEAM instruction as the first
+%%% argument.
+
+match_fail_instructions(#st{ssa=Blocks0,args=Args,location=Location}=St) ->
+ Ls = maps:to_list(Blocks0),
+ Info = {length(Args),Location},
+ Blocks = match_fail_instrs_1(Ls, Info, Blocks0),
+ St#st{ssa=Blocks}.
+
+match_fail_instrs_1([{L,#b_blk{is=Is0}=Blk}|Bs], Arity, Blocks0) ->
+ case match_fail_instrs_blk(Is0, Arity, []) of
+ none ->
+ match_fail_instrs_1(Bs, Arity, Blocks0);
+ Is ->
+ Blocks = Blocks0#{L:=Blk#b_blk{is=Is}},
+ match_fail_instrs_1(Bs, Arity, Blocks)
+ end;
+match_fail_instrs_1([], _Arity, Blocks) -> Blocks.
+
+match_fail_instrs_blk([#b_set{op=put_tuple,dst=Dst,
+ args=[#b_literal{val=Tag},Val]},
+ #b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}},
+ Dst]}=Call|Is],
+ _Arity, Acc) ->
+ match_fail_instr(Call, Tag, Val, Is, Acc);
+match_fail_instrs_blk([#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}},
+ #b_literal{val={Tag,Val}}]}=Call|Is],
+ _Arity, Acc) ->
+ match_fail_instr(Call, Tag, #b_literal{val=Val}, Is, Acc);
+match_fail_instrs_blk([#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}},
+ #b_literal{val=if_clause}]}=Call|Is],
+ _Arity, Acc) ->
+ I = Call#b_set{op=match_fail,args=[#b_literal{val=if_end}]},
+ reverse(Acc, [I|Is]);
+match_fail_instrs_blk([#b_set{op=call,anno=Anno,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}},
+ #b_literal{val=function_clause},
+ Stk]}=Call],
+ {Arity,Location}, Acc) ->
+ case match_fail_stk(Stk, Acc, [], []) of
+ {[_|_]=Vars,Is} when length(Vars) =:= Arity ->
+ case maps:get(location, Anno, none) of
+ Location ->
+ I = Call#b_set{op=match_fail,
+ args=[#b_literal{val=function_clause}|Vars]},
+ Is ++ [I];
+ _ ->
+ %% erlang:error/2 has a different location than the
+ %% func_info instruction at the beginning of the function
+ %% (probably because of inlining). Keep the original call.
+ reverse(Acc, [Call])
+ end;
+ _ ->
+ %% Either the stacktrace could not be picked apart (for example,
+ %% if the call to erlang:error/2 was handwritten) or the number
+ %% of arguments in the stacktrace was different from the arity
+ %% of the host function (because it is the implementation of a
+ %% fun). Keep the original call.
+ reverse(Acc, [Call])
+ end;
+match_fail_instrs_blk([I|Is], Arity, Acc) ->
+ match_fail_instrs_blk(Is, Arity, [I|Acc]);
+match_fail_instrs_blk(_, _, _) ->
+ none.
+
+match_fail_instr(Call, Tag, Val, Is, Acc) ->
+ Op = case Tag of
+ badmatch -> Tag;
+ case_clause -> case_end;
+ try_clause -> try_case_end;
+ _ -> none
+ end,
+ case Op of
+ none ->
+ none;
+ _ ->
+ I = Call#b_set{op=match_fail,args=[#b_literal{val=Op},Val]},
+ reverse(Acc, [I|Is])
+ end.
+
+match_fail_stk(#b_var{}=V, [#b_set{op=put_list,dst=V,args=[H,T]}|Is], IAcc, VAcc) ->
+ match_fail_stk(T, Is, IAcc, [H|VAcc]);
+match_fail_stk(#b_literal{val=[H|T]}, Is, IAcc, VAcc) ->
+ match_fail_stk(#b_literal{val=T}, Is, IAcc, [#b_literal{val=H}|VAcc]);
+match_fail_stk(#b_literal{val=[]}, [], IAcc, VAcc) ->
+ {reverse(VAcc),IAcc};
+match_fail_stk(T, [#b_set{op=Op}=I|Is], IAcc, VAcc)
+ when Op =:= bs_get_tail; Op =:= bs_set_position ->
+ match_fail_stk(T, Is, [I|IAcc], VAcc);
+match_fail_stk(_, _, _, _) -> none.
+
%%%
%%% Fix tuples.
%%%
@@ -911,9 +1168,8 @@ use_set_tuple_element(#st{ssa=Blocks0}=St) ->
Blocks = use_ste_1(RPO, Uses, Blocks0),
St#st{ssa=Blocks}.
-use_ste_1([L|Ls], Uses, Blocks0) ->
- {Blk0,Blocks} = use_ste_across(L, Uses, Blocks0),
- #b_blk{is=Is0} = Blk0,
+use_ste_1([L|Ls], Uses, Blocks) ->
+ #b_blk{is=Is0} = Blk0 = map_get(L, Blocks),
case use_ste_is(Is0, Uses) of
Is0 ->
use_ste_1(Ls, Uses, Blocks);
@@ -976,69 +1232,6 @@ extract_ste(#b_set{op=call,dst=Dst,
end;
extract_ste(#b_set{}) -> none.
-%%% Optimize accross blocks within a try/catch block.
-
-use_ste_across(L, Uses, Blocks) ->
- case map_get(L, Blocks) of
- #b_blk{last=#b_br{bool=#b_var{}}}=Blk ->
- try
- use_ste_across_1(L, Blk, Uses, Blocks)
- catch
- throw:not_possible ->
- {Blk,Blocks}
- end;
- #b_blk{}=Blk ->
- {Blk,Blocks}
- end.
-
-use_ste_across_1(L, Blk0, Uses, Blocks0) ->
- #b_blk{is=IsThis,last=#b_br{bool=Bool,succ=Next}} = Blk0,
- case reverse(IsThis) of
- [#b_set{op=succeeded,dst=Bool,args=[Result]}=Succ0,
- #b_set{op=call,args=[#b_remote{}|_],dst=Result}=Call1|Prefix] ->
- case is_single_use(Bool, Uses) andalso
- is_n_uses(2, Result, Uses) of
- true -> ok;
- false -> throw(not_possible)
- end,
- Call2 = use_ste_across_next(Next, Uses, Blocks0),
- Is = [Call1,Call2],
- case use_ste_is(Is, decrement_uses(Result, Uses)) of
- [#b_set{}=Call,#b_set{op=set_tuple_element}=Ste] ->
- Blocks1 = use_ste_fix_next(Ste, Next, Blocks0),
- Succ = Succ0#b_set{args=[Call#b_set.dst]},
- Blk = Blk0#b_blk{is=reverse(Prefix, [Call,Succ])},
- Blocks = Blocks1#{L:=Blk},
- {Blk,Blocks};
- _ ->
- throw(not_possible)
- end;
- _ ->
- throw(not_possible)
- end.
-
-use_ste_across_next(Next, Uses, Blocks) ->
- case map_get(Next, Blocks) of
- #b_blk{is=[#b_set{op=call,dst=Result,args=[#b_remote{}|_]}=Call,
- #b_set{op=succeeded,dst=Bool,args=[Result]}],
- last=#b_br{bool=Bool}} ->
- case is_single_use(Bool, Uses) andalso
- is_n_uses(2, Result, Uses) of
- true -> ok;
- false -> throw(not_possible)
- end,
- Call;
- #b_blk{} ->
- throw(not_possible)
- end.
-
-use_ste_fix_next(Ste, Next, Blocks) ->
- Blk0 = map_get(Next, Blocks),
- #b_blk{is=[#b_set{op=call},#b_set{op=succeeded}],last=Br0} = Blk0,
- Br = beam_ssa:normalize(Br0#b_br{bool=#b_literal{val=true}}),
- Blk = Blk0#b_blk{is=[Ste],last=Br},
- Blocks#{Next:=Blk}.
-
%% Count how many times each variable is used.
count_uses(Blocks) ->
@@ -1048,7 +1241,7 @@ count_uses_blk([#b_blk{is=Is,last=Last}|Bs], CountMap0) ->
F = fun(I, CountMap) ->
foldl(fun(Var, Acc) ->
case Acc of
- #{Var:=3} -> Acc;
+ #{Var:=2} -> Acc;
#{Var:=C} -> Acc#{Var:=C+1};
#{} -> Acc#{Var=>1}
end
@@ -1058,16 +1251,6 @@ count_uses_blk([#b_blk{is=Is,last=Last}|Bs], CountMap0) ->
count_uses_blk(Bs, CountMap);
count_uses_blk([], CountMap) -> CountMap.
-decrement_uses(V, Uses) ->
- #{V:=C} = Uses,
- Uses#{V:=C-1}.
-
-is_n_uses(N, V, Uses) ->
- case Uses of
- #{V:=N} -> true;
- #{} -> false
- end.
-
is_single_use(V, Uses) ->
case Uses of
#{V:=1} -> true;
@@ -1189,10 +1372,10 @@ place_frame_here(L, Blocks, Doms, Frames) ->
Descendants = beam_ssa:rpo([L], Blocks),
PhiPredecessors = phi_predecessors(L, Blocks),
MustDominate = ordsets:from_list(PhiPredecessors ++ Descendants),
- Dominates = all(fun(?BADARG_BLOCK) ->
+ Dominates = all(fun(?EXCEPTION_BLOCK) ->
%% This block defines no variables and calls
%% erlang:error(badarg). It does not matter
- %% whether L dominates ?BADARG_BLOCK or not;
+ %% whether L dominates ?EXCEPTION_BLOCK or not;
%% it is still safe to put the frame in L.
true;
(Bl) ->
@@ -1229,14 +1412,23 @@ need_frame(#b_blk{is=Is,last=#b_ret{arg=Ret}}) ->
need_frame(#b_blk{is=Is}) ->
need_frame_1(Is, body).
-need_frame_1([#b_set{op=make_fun,dst=Fun}|Is], {return,_}=Context) ->
- %% Since make_fun clobbers X registers, a stack frame is needed if
- %% any of the following instructions use any other variable than
- %% the one holding the reference to the created fun.
- need_frame_1(Is, Context) orelse
- case beam_ssa:used(#b_blk{is=Is,last=#b_ret{arg=Fun}}) of
- [Fun] -> false;
- [_|_] -> true
+need_frame_1([#b_set{op=make_fun,dst=Fun}|Is], {return,Ret}=Context) ->
+ case need_frame_1(Is, Context) of
+ true ->
+ true;
+ false ->
+ %% Since make_fun clobbers X registers, a stack frame is
+ %% needed if any of the following instructions use any
+ %% other variable than the one holding the reference to
+ %% the created fun.
+ Defs = ordsets:from_list([Dst || #b_set{dst=Dst} <- Is]),
+ Blk = #b_blk{is=Is,last=#b_ret{arg=Ret}},
+ Used = ordsets:subtract(beam_ssa:used(Blk), Defs),
+ case Used of
+ [] -> false;
+ [Fun] -> false;
+ [_|_] -> true
+ end
end;
need_frame_1([#b_set{op=new_try_tag}|_], _) ->
true;
@@ -1250,14 +1442,9 @@ need_frame_1([#b_set{op=call,args=[Func|_]}|Is], Context) ->
#b_remote{mod=#b_literal{val=Mod},
name=#b_literal{val=Name},
arity=Arity} when is_atom(Mod), is_atom(Name) ->
- case erl_bifs:is_exit_bif(Mod, Name, Arity) of
- true ->
- false;
- false ->
- Context =:= body orelse
- Is =/= [] orelse
- is_trap_bif(Mod, Name, Arity)
- end;
+ Context =:= body orelse
+ Is =/= [] orelse
+ is_trap_bif(Mod, Name, Arity);
#b_remote{} ->
%% This is an apply(), which always needs a frame.
true;
@@ -1340,9 +1527,9 @@ recv_common(_Defs, none, _Blocks) ->
%% in the tail position of a function.
[];
recv_common(Defs, Exit, Blocks) ->
- {ExitDefs,ExitUsed} = beam_ssa:def_used([Exit], Blocks),
+ {ExitDefs,ExitUnused} = beam_ssa:def_unused([Exit], Defs, Blocks),
Def = ordsets:subtract(Defs, ExitDefs),
- ordsets:intersection(Def, ExitUsed).
+ ordsets:subtract(Def, ExitUnused).
%% recv_crit_edges([RemoveMessageLabel], LoopExit,
%% Blocks0, Count0) -> {Blocks,Count}.
@@ -1447,9 +1634,9 @@ exit_predecessors([], _Exit, _Blocks) -> [].
%% later used within a clause of the receive.
fix_receive([L|Ls], Defs, Blocks0, Count0) ->
- {RmDefs,Used0} = beam_ssa:def_used([L], Blocks0),
+ {RmDefs,Unused} = beam_ssa:def_unused([L], Defs, Blocks0),
Def = ordsets:subtract(Defs, RmDefs),
- Used = ordsets:intersection(Def, Used0),
+ Used = ordsets:subtract(Def, Unused),
{NewVars,Count} = new_vars([Base || #b_var{name=Base} <- Used], Count0),
Ren = zip(Used, NewVars),
Blocks1 = beam_ssa:rename_vars(Ren, [L], Blocks0),
@@ -1483,8 +1670,8 @@ find_loop_exit(_, _) ->
%% loop exit block.
none.
-find_loop_exit_1([?BADARG_BLOCK|Ls], RmSet, Dominators, Blocks) ->
- %% ?BADARG_BLOCK is a marker and not an actual block, so it is not
+find_loop_exit_1([?EXCEPTION_BLOCK|Ls], RmSet, Dominators, Blocks) ->
+ %% ?EXCEPTION_BLOCK is a marker and not an actual block, so it is not
%% the block we are looking for.
find_loop_exit_1(Ls, RmSet, Dominators, Blocks);
find_loop_exit_1([L|Ls0], RmSet, Dominators, Blocks) ->
@@ -1697,11 +1884,17 @@ find_yregs_terminator(Terminator, #dk{k=Killed}, Yregs0) ->
%%%
%%% 0:
%%% Res = call local literal foo/1, literal 42
+%%% @ssa_bool:1 = succeeded:body Res
+%%% br @ssa_bool:1, label 2, label 1
+%%% 2:
%%% _1 = bif:node Pid
-%%% @ssa_bool = succeeded _1
-%%% br @ssa_bool, label 3, label 1
+%%% @ssa_bool:2 = succeeded:body _1
+%%% br @ssa_bool:2, label 3, label 1
%%% 3:
-%%% @ssa_ignored = call local literal bar/0
+%%% _2 = call local literal bar/0
+%%% @ssa_bool:3 = succeeded:body _2
+%%% br @ssa_bool:3, label 4, label 1
+%%% 4:
%%% ret Res
%%%
%%% It can be seen that the variables Pid and Res must be saved in Y
@@ -1712,12 +1905,17 @@ find_yregs_terminator(Terminator, #dk{k=Killed}, Yregs0) ->
%%% 0:
%%% Pid:4 = copy Pid
%%% Res = call local literal foo/1, literal 42
+%%% @ssa_bool:1 = succeeded:body Res
+%%% br @ssa_bool:1, label 2, label 1
+%%% 2:
%%% _1 = bif:node Pid:4
-%%% @ssa_bool = succeeded _1
-%%% br @ssa_bool, label 3, label 1
-%%%
+%%% @ssa_bool:2 = succeeded:body _1
+%%% br @ssa_bool:2, label 3, label 1
%%% 3:
-%%% @ssa_ignored = call local literal bar/0
+%%% _2 = call local literal bar/0
+%%% @ssa_bool:3 = succeeded:body _2
+%%% br @ssa_bool:3, label 4, label 1
+%%% 4:
%%% ret Res
%%%
%%% The Res and Pid:4 variables must be assigned to different Y registers
@@ -1727,13 +1925,18 @@ find_yregs_terminator(Terminator, #dk{k=Killed}, Yregs0) ->
%%% 0:
%%% Pid:4 = copy Pid
%%% Res:6 = call local literal foo/1, literal 42
+%%% @ssa_bool:1 = succeeded:body Res:6
+%%% br @ssa_bool:1, label 2, label 1
+%%% 2:
%%% _1 = bif:node Pid:4
-%%% @ssa_bool = succeeded _1
-%%% br @ssa_bool, label 3, label 1
-%%%
+%%% @ssa_bool:2 = succeeded:body _1
+%%% br @ssa_bool:2, label 3, label 1
%%% 3:
%%% Res = copy Res:6
-%%% @ssa_ignored = call local literal bar/0
+%%% _2 = call local literal bar/0
+%%% @ssa_bool:3 = succeeded:body _2
+%%% br @ssa_bool:3, label 4, label 1
+%%% 4:
%%% ret Res
%%%
%%% The new variable Res:6 is used to capture the return value from the call.
@@ -1767,7 +1970,7 @@ collect_yregs([], Yregs) -> Yregs.
copy_retval_2([L|Ls], Yregs, Copy0, Blocks0, Count0) ->
#b_blk{is=Is0,last=Last} = Blk = map_get(L, Blocks0),
RC = case {Last,Ls} of
- {#b_br{succ=Succ,fail=?BADARG_BLOCK},[Succ|_]} ->
+ {#b_br{succ=Succ,fail=?EXCEPTION_BLOCK},[Succ|_]} ->
true;
{_,_} ->
false
@@ -1984,11 +2187,9 @@ number_is_2([], N, Acc) ->
live_intervals(#st{args=Args,ssa=Blocks}=St) ->
Vars0 = [{V,{0,1}} || #b_var{}=V <- Args],
- F = fun(L, _, A) -> live_interval_blk(L, Blocks, A) end,
- LiveMap0 = #{},
- Acc0 = {[],LiveMap0},
- {Vars,_} = beam_ssa:fold_po(F, Acc0, Blocks),
- Intervals = merge_ranges(rel2fam(Vars0++Vars)),
+ PO = reverse(beam_ssa:rpo(Blocks)),
+ Vars = live_interval_blk(PO, Blocks, Vars0, #{}),
+ Intervals = merge_ranges(rel2fam(Vars)),
St#st{intervals=Intervals}.
merge_ranges([{V,Rs}|T]) ->
@@ -2001,32 +2202,51 @@ merge_ranges_1([R|Rs]) ->
[R|merge_ranges_1(Rs)];
merge_ranges_1([]) -> [].
-live_interval_blk(L, Blocks, {Vars0,LiveMap0}) ->
+live_interval_blk([L|Ls], Blocks, Vars0, LiveMap0) ->
Live0 = [],
- Successors = beam_ssa:successors(L, Blocks),
+ Blk = map_get(L, Blocks),
+ Successors = beam_ssa:successors(Blk),
Live1 = update_successors(Successors, L, Blocks, LiveMap0, Live0),
%% Add ranges for all variables that are live in the successors.
- #b_blk{is=Is,last=Last} = map_get(L, Blocks),
+ #b_blk{is=Is,last=Last} = Blk,
End = beam_ssa:get_anno(n, Last),
- Use = [{V,{use,End+1}} || V <- Live1],
+ EndUse = {use,End+1},
+ Use = [{V,EndUse} || V <- Live1],
%% Determine used and defined variables in this block.
FirstNumber = first_number(Is, Last),
- UseDef0 = live_interval_blk_1([Last|reverse(Is)], FirstNumber, Use),
- UseDef = rel2fam(UseDef0),
+ UseDef0 = live_interval_last(Last, Use),
+ UseDef1 = live_interval_blk_is(Is, FirstNumber, UseDef0),
+ UseDef = rel2fam(UseDef1),
%% Update what is live at the beginning of this block and
%% store it.
- Used = [V || {V,[{use,_}|_]} <- UseDef],
- Live2 = ordsets:union(Live1, Used),
- Killed = [V || {V,[{def,_}|_]} <- UseDef],
- Live = ordsets:subtract(Live2, Killed),
+ Live = [V || {V,[{use,_}|_]} <- UseDef],
LiveMap = LiveMap0#{L=>Live},
%% Construct the ranges for this block.
Vars = make_block_ranges(UseDef, FirstNumber, Vars0),
- {Vars,LiveMap}.
+ live_interval_blk(Ls, Blocks, Vars, LiveMap);
+live_interval_blk([], _Blocks, Vars, _LiveMap) ->
+ Vars.
+
+live_interval_last(I, Acc) ->
+ N = beam_ssa:get_anno(n, I),
+ Used = beam_ssa:used(I),
+ [{V,{use,N}} || V <- Used] ++ Acc.
+
+live_interval_blk_is([#b_set{op=phi,dst=Dst}|Is], FirstNumber, Acc0) ->
+ Acc = [{Dst,{def,FirstNumber}}|Acc0],
+ live_interval_blk_is(Is, FirstNumber, Acc);
+live_interval_blk_is([#b_set{dst=Dst}=I|Is], FirstNumber, Acc0) ->
+ N = beam_ssa:get_anno(n, I),
+ Acc1 = [{Dst,{def,N}}|Acc0],
+ Used = beam_ssa:used(I),
+ Acc = [{V,{use,N}} || V <- Used] ++ Acc1,
+ live_interval_blk_is(Is, FirstNumber, Acc);
+live_interval_blk_is([], _FirstNumber, Acc) ->
+ Acc.
make_block_ranges([{V,[{def,Def}]}|Vs], First, Acc) ->
make_block_ranges(Vs, First, [{V,{Def,Def}}|Acc]);
@@ -2038,30 +2258,6 @@ make_block_ranges([{V,[{use,_}|_]=Uses}|Vs], First, Acc) ->
make_block_ranges(Vs, First, [{V,{First,Last}}|Acc]);
make_block_ranges([], _, Acc) -> Acc.
-live_interval_blk_1([#b_set{op=phi,dst=Dst}|Is], FirstNumber, Acc0) ->
- Acc = [{Dst,{def,FirstNumber}}|Acc0],
- live_interval_blk_1(Is, FirstNumber, Acc);
-live_interval_blk_1([#b_set{op=bs_start_match}=I|Is],
- FirstNumber, Acc0) ->
- N = beam_ssa:get_anno(n, I),
- #b_set{dst=Dst} = I,
- Acc1 = [{Dst,{def,N}}|Acc0],
- Acc = [{V,{use,N}} || V <- beam_ssa:used(I)] ++ Acc1,
- live_interval_blk_1(Is, FirstNumber, Acc);
-live_interval_blk_1([I|Is], FirstNumber, Acc0) ->
- N = beam_ssa:get_anno(n, I),
- Acc1 = case I of
- #b_set{dst=Dst} ->
- [{Dst,{def,N}}|Acc0];
- _ ->
- Acc0
- end,
- Used = beam_ssa:used(I),
- Acc = [{V,{use,N}} || V <- Used] ++ Acc1,
- live_interval_blk_1(Is, FirstNumber, Acc);
-live_interval_blk_1([], _FirstNumber, Acc) ->
- Acc.
-
%% first_number([#b_set{}]) -> InstructionNumber.
%% Return the number for the first instruction for the block.
%% Note that this number is one less than the first
@@ -2116,8 +2312,8 @@ reserve_yregs(#st{frames=Frames}=St0) ->
reserve_yregs_1(L, #st{ssa=Blocks0,cnt=Count0,res=Res0}=St) ->
Blk = map_get(L, Blocks0),
Yregs = beam_ssa:get_anno(yregs, Blk),
- {Def,Used} = beam_ssa:def_used([L], Blocks0),
- UsedYregs = ordsets:intersection(Yregs, Used),
+ {Def,Unused} = beam_ssa:def_unused([L], Yregs, Blocks0),
+ UsedYregs = ordsets:subtract(Yregs, Unused),
DefBefore = ordsets:subtract(UsedYregs, Def),
{BeforeVars,Blocks,Count} = rename_vars(DefBefore, L, Blocks0, Count0),
InsideVars = ordsets:subtract(UsedYregs, DefBefore),
@@ -2307,68 +2503,69 @@ reserve_zregs(Blocks, Intervals, Res) ->
end,
beam_ssa:fold_rpo(F, [0], Res, Blocks).
-reserve_zreg([#b_set{op=Op,dst=Dst}],
- #b_br{bool=Dst}, _ShortLived, A) when Op =:= call;
- Op =:= get_tuple_element ->
- %% If type optimization has determined that the result of these
- %% instructions can be used directly in a branch, we must avoid reserving a
- %% z register or code generation will fail.
- A;
reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst},
- #b_set{op={bif,'=:='},args=[Dst,Val]}], Last, ShortLived, A0) ->
+ #b_set{op={bif,'=:='},args=[Dst,Val],dst=Bool}],
+ Last, ShortLived, A) ->
case {Val,Last} of
- {#b_literal{val=Arity},#b_br{bool=#b_var{}}} when Arity bsr 32 =:= 0 ->
+ {#b_literal{val=Arity},#b_br{bool=Bool}} when Arity bsr 32 =:= 0 ->
%% These two instructions can be combined to a test_arity
%% instruction provided that the arity variable is short-lived.
- reserve_zreg_1(Dst, ShortLived, A0);
+ reserve_test_zreg(Dst, ShortLived, A);
{_,_} ->
%% Either the arity is too big, or the boolean value is not
%% used in a conditional branch.
- A0
+ A
end;
reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst}],
- #b_switch{}, ShortLived, A) ->
- reserve_zreg_1(Dst, ShortLived, A);
-reserve_zreg([#b_set{op={bif,'xor'}}], _Last, _ShortLived, A) ->
- %% There is no short, easy way to rewrite 'xor' to a series of
- %% test instructions.
- A;
-reserve_zreg([#b_set{op={bif,is_record}}], _Last, _ShortLived, A) ->
- %% There is no short, easy way to rewrite is_record/2 to a series of
- %% test instructions.
- A;
-reserve_zreg([#b_set{op=Op,dst=Dst}|Is], Last, ShortLived, A0) ->
- IsZReg = case Op of
- bs_match_string -> true;
- bs_save -> true;
- bs_restore -> true;
- bs_set_position -> true;
- {float,clearerror} -> true;
- kill_try_tag -> true;
- landingpad -> true;
- put_tuple_elements -> true;
- remove_message -> true;
- set_tuple_element -> true;
- succeeded -> true;
- timeout -> true;
- wait_timeout -> true;
- _ -> false
- end,
- A = case IsZReg of
- true -> [{Dst,z}|A0];
- false -> A0
- end,
- reserve_zreg(Is, Last, ShortLived, A);
-reserve_zreg([], #b_br{bool=Bool}, ShortLived, A) ->
- reserve_zreg_1(Bool, ShortLived, A);
+ #b_switch{arg=Dst}, ShortLived, A) ->
+ reserve_test_zreg(Dst, ShortLived, A);
+reserve_zreg([#b_set{op=Op,dst=Dst}], #b_br{bool=Dst}, ShortLived, A) ->
+ case use_zreg(Op) of
+ yes -> [{Dst,z} | A];
+ no -> A;
+ maybe -> reserve_test_zreg(Dst, ShortLived, A)
+ end;
+reserve_zreg([#b_set{op=Op,dst=Dst} | Is], Last, ShortLived, A) ->
+ case use_zreg(Op) of
+ yes -> reserve_zreg(Is, Last, ShortLived, [{Dst,z} | A]);
+ _Other -> reserve_zreg(Is, Last, ShortLived, A)
+ end;
reserve_zreg([], _, _, A) -> A.
-reserve_zreg_1(#b_var{}=V, ShortLived, A) ->
+use_zreg(bs_match_string) -> yes;
+use_zreg(bs_save) -> yes;
+use_zreg(bs_restore) -> yes;
+use_zreg(bs_set_position) -> yes;
+use_zreg({float,clearerror}) -> yes;
+use_zreg(kill_try_tag) -> yes;
+use_zreg(landingpad) -> yes;
+use_zreg(put_tuple_elements) -> yes;
+use_zreg(remove_message) -> yes;
+use_zreg(set_tuple_element) -> yes;
+use_zreg(succeeded) -> yes;
+use_zreg(timeout) -> yes;
+use_zreg(wait_timeout) -> yes;
+%% There's no way we can combine these into a test instruction, so we must
+%% avoid using a z register if their result is used directly in a branch.
+use_zreg(call) -> no;
+use_zreg({bif,is_map_key}) -> no;
+use_zreg({bif,is_record}) -> no;
+use_zreg({bif,map_get}) -> no;
+use_zreg({bif,'xor'}) -> no;
+use_zreg(get_hd) -> no;
+use_zreg(get_tl) -> no;
+use_zreg(get_tuple_element) -> no;
+%% Assume the instruction can use a z register, provided it's the last in its
+%% block and that the result is only used in the terminator.
+use_zreg(_) -> maybe.
+
+%% If V is defined just before a branch, we may be able to combine it into a
+%% test instruction.
+reserve_test_zreg(#b_var{}=V, ShortLived, A) ->
case cerl_sets:is_element(V, ShortLived) of
true -> [{V,z}|A];
false -> A
- end;
-reserve_zreg_1(#b_literal{}, _, A) -> A.
+ end.
reserve_fregs(Blocks, Res) ->
F = fun(_, #b_blk{is=Is}, A) ->
@@ -2506,13 +2703,9 @@ reserve_xregs_is([], Res, Xs, _Used) ->
{Res,Xs}.
%% Pick up register hints from the successors of this blocks.
-reserve_terminator(_L, _Is, #b_br{bool=#b_var{},succ=Succ,fail=?BADARG_BLOCK},
- _Blocks, XsMap, _Res) ->
- %% We know that no variables are used at ?BADARG_BLOCK, so
- %% any register hints from the success blocks are safe to use.
- map_get(Succ, XsMap);
reserve_terminator(L, Is, #b_br{bool=#b_var{},succ=Succ,fail=Fail},
- Blocks, XsMap, Res) when Succ =/= Fail ->
+ Blocks, XsMap, Res) when Succ =/= Fail,
+ Fail =/= ?EXCEPTION_BLOCK ->
#{Succ:=SuccBlk,Fail:=FailBlk} = Blocks,
case {SuccBlk,FailBlk} of
{#b_blk{is=[],last=#b_br{succ=PhiL,fail=PhiL}},
@@ -2545,15 +2738,33 @@ reserve_terminator(L, Is, #b_br{bool=#b_var{},succ=Succ,fail=Fail},
%% be safe at the failure block, and vice versa.
#{}
end;
-reserve_terminator(L, Is, #b_br{bool=#b_literal{val=true},succ=Succ},
+reserve_terminator(L, Is, #b_br{bool=Bool,succ=Succ,fail=Fail},
Blocks, XsMap, Res) ->
- case map_get(Succ, Blocks) of
- #b_blk{is=[],last=Last} ->
- reserve_terminator(Succ, Is, Last, Blocks, XsMap, Res);
- #b_blk{is=[_|_]=PhiIs} ->
- res_xregs_from_phi(PhiIs, L, Res, #{})
+ case {Bool, Fail} of
+ {_, ?EXCEPTION_BLOCK} ->
+ %% We know that no variables are used from ?EXCEPTION_BLOCK, so any
+ %% register hints from the successor block are safe to use.
+ reserve_terminator_1(L, Succ, Is, Blocks, XsMap, Res);
+ {#b_literal{val=true}, _} ->
+ %% We only have one successor, so its hints are safe to use.
+ reserve_terminator_1(L, Succ, Is, Blocks, XsMap, Res);
+ {_, _} ->
+ %% Register hints from the success block may not
+ %% be safe at the failure block, and vice versa.
+ #{}
end;
-reserve_terminator(_, _, _, _, _, _) -> #{}.
+reserve_terminator(_, _, _, _, _, _) ->
+ #{}.
+
+reserve_terminator_1(L, Succ, _Is, Blocks, XsMap, Res) ->
+ case {Blocks, XsMap} of
+ {#{ Succ := #b_blk{is=[#b_set{op=phi}|_]=PhiIs}}, #{}} ->
+ res_xregs_from_phi(PhiIs, L, Res, #{});
+ {#{}, #{ Succ := Xs }}->
+ Xs;
+ {#{}, #{}} ->
+ #{}
+ end.
%% Pick up a reservation from a phi node.
res_xregs_from_phi([#b_set{op=phi,dst=Dst,args=Args}|Is],
@@ -2915,11 +3126,9 @@ are_overlapping_1({_,_}, []) -> false.
%% Check whether the block is a loop header.
is_loop_header(L, Blocks) ->
- %% We KNOW that a loop header must start with a peek_message
- %% instruction.
case map_get(L, Blocks) of
- #b_blk{is=[#b_set{op=peek_message}|_]} -> true;
- _ -> false
+ #b_blk{is=[I|_]} -> beam_ssa:is_loop_header(I);
+ #b_blk{} -> false
end.
rel2fam(S0) ->
diff --git a/lib/compiler/src/beam_ssa_recv.erl b/lib/compiler/src/beam_ssa_recv.erl
index ac42d17b5f..117526ecfc 100644
--- a/lib/compiler/src/beam_ssa_recv.erl
+++ b/lib/compiler/src/beam_ssa_recv.erl
@@ -165,19 +165,21 @@ recv_opt_makes_ref([I|Is], RecvLbl, Blocks, Acc) ->
recv_opt_makes_ref([], _, _, _) -> no.
makes_ref(#b_set{dst=Dst,args=[Func0|_]}, Blocks) ->
- Func = case Func0 of
- #b_remote{mod=#b_literal{val=erlang},
- name=#b_literal{val=Name},arity=A0} ->
- {Name,A0};
+ MFA = case Func0 of
+ #b_remote{mod=#b_literal{val=Mod},
+ name=#b_literal{val=Func},arity=A0} ->
+ {Mod,Func,A0};
_ ->
none
end,
- case Func of
- {make_ref,0} ->
+ case MFA of
+ {erlang,make_ref,0} ->
{yes,Dst};
- {monitor,2} ->
+ {erlang,monitor,2} ->
{yes,Dst};
- {spawn_monitor,A} when A =:= 1; A =:= 3 ->
+ {erlang,spawn_request,A} when 1 =< A, A =< 5 ->
+ {yes,Dst};
+ {erlang,spawn_monitor,A} when 1 =< A, A =< 4 ->
ref_in_tuple(Dst, Blocks);
_ ->
no
diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl
index 72ce6f19c2..ae8f582430 100644
--- a/lib/compiler/src/beam_ssa_share.erl
+++ b/lib/compiler/src/beam_ssa_share.erl
@@ -51,10 +51,25 @@ module(#b_module{body=Fs0}=Module, _Opts) ->
Blocks0 :: beam_ssa:block_map(),
Blk :: beam_ssa:b_blk().
-block(#b_blk{last=Last0}=Blk, Blocks) ->
+block(#b_blk{is=Is0,last=Last0}=Blk, Blocks) ->
case share_terminator(Last0, Blocks) of
- none -> Blk;
- Last -> Blk#b_blk{last=beam_ssa:normalize(Last)}
+ none ->
+ Blk;
+ #b_br{succ=Same,fail=Same}=Last ->
+ %% The terminator was reduced from a two-way branch to a
+ %% one-way branch.
+ case reverse(Is0) of
+ [#b_set{op={succeeded,Kind},args=[Dst]},#b_set{dst=Dst}|Is] ->
+ %% A succeeded instruction must not be followed by a
+ %% one-way branch. We must remove both the succeeded
+ %% instruction and the instruction preceding it.
+ guard = Kind, %Assertion.
+ Blk#b_blk{is=reverse(Is),last=beam_ssa:normalize(Last)};
+ _ ->
+ Blk#b_blk{last=beam_ssa:normalize(Last)}
+ end;
+ Last ->
+ Blk#b_blk{last=beam_ssa:normalize(Last)}
end.
%%%
@@ -117,8 +132,8 @@ share_terminator(_Last, _Blocks) -> none.
%% possible if the blocks are not equivalent, as that is the common
%% case.
-are_equivalent(_Succ, _, ?BADARG_BLOCK, _, _Blocks) ->
- %% ?BADARG_BLOCK is special. Sharing could be incorrect.
+are_equivalent(_Succ, _, ?EXCEPTION_BLOCK, _, _Blocks) ->
+ %% ?EXCEPTION_BLOCK is special. Sharing could be incorrect.
false;
are_equivalent(_Succ, #b_blk{is=Is1,last=#b_ret{arg=RetVal1}=Ret1},
_Fail, #b_blk{is=Is2,last=#b_ret{arg=RetVal2}=Ret2}, _Blocks) ->
@@ -240,6 +255,12 @@ share_switch_2([[{_,_}|_]=Prep|T], Blocks, Acc0) ->
share_switch_2(T, Blocks, Acc);
share_switch_2([], _, Acc) -> Acc.
+canonical_block({?EXCEPTION_BLOCK,_VarMap}, _Blocks) ->
+ %% Never ever share the ?EXCEPTION_BLOCK with another block.
+ %% Unless the entire switch or br is optimized away, a
+ %% {f,0} can be emitted where it is not allowed and a later
+ %% pass will crash.
+ {{none,?EXCEPTION_BLOCK},done};
canonical_block({L,VarMap0}, Blocks) ->
#b_blk{is=Is,last=Last0} = map_get(L, Blocks),
case canonical_terminator(L, Last0, Blocks) of
@@ -331,11 +352,16 @@ canonical_terminator(_, _, _) -> none.
canonical_terminator_phis([#b_set{op=phi,args=PhiArgs}=Phi|Is], L) ->
{Value,L} = keyfind(L, 2, PhiArgs),
[Phi#b_set{op=copy,args=[Value]}|canonical_terminator_phis(Is, L)];
-canonical_terminator_phis([#b_set{op=peek_message}=I|_], L) ->
- %% We could get stuck into an infinite loop if we allowed the
- %% comparisons to continue into this block. Force an unequal
- %% compare with all other predecessors of this block.
- [I#b_set{op=copy,args=[#b_literal{val=L}]}];
+canonical_terminator_phis([#b_set{}=I|_], L) ->
+ case beam_ssa:is_loop_header(I) of
+ true ->
+ %% We could get stuck into an infinite loop if we allowed the
+ %% comparisons to continue into this loop. Force an unequal
+ %% compare with all other predecessors of this block.
+ [I#b_set{op=copy,args=[#b_literal{val=L}]}];
+ false ->
+ []
+ end;
canonical_terminator_phis(_, _) -> [].
canonical_arg(#b_var{}=Var, VarMap) ->
@@ -368,7 +394,9 @@ shortcut_nonempty_block(L, Blocks) ->
is_forbidden(L, Blocks) ->
case map_get(L, Blocks) of
- #b_blk{is=[#b_set{op=phi}|_]} -> true;
- #b_blk{is=[#b_set{op=peek_message}|_]} -> true;
+ #b_blk{is=[#b_set{op=phi}|_]} ->
+ true;
+ #b_blk{is=[#b_set{}=I|_]} ->
+ beam_ssa:is_loop_header(I);
#b_blk{} -> false
end.
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
index 2be8ad2b76..304c74c2f3 100644
--- a/lib/compiler/src/beam_ssa_type.erl
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -17,126 +17,572 @@
%%
%% %CopyrightEnd%
%%
+%% This pass infers types from expressions and attempts to simplify or remove
+%% subsequent instructions based on that information.
+%%
+%% This is divided into two subpasses; the first figures out function type
+%% signatures for the whole module without optimizing anything, and the second
+%% optimizes based on that information, further refining the type signatures as
+%% it goes.
+%%
-module(beam_ssa_type).
--export([opt_start/4, opt_continue/4, opt_finish/3]).
+-export([opt_start/2, opt_continue/4, opt_finish/3]).
-include("beam_ssa_opt.hrl").
--import(lists, [all/2,any/2,droplast/1,foldl/3,last/1,member/2,
- keyfind/3,reverse/1,reverse/2,
- sort/1,split/2]).
-
--define(UNICODE_INT, #t_integer{elements={0,16#10FFFF}}).
-
--record(d,
- {ds :: #{beam_ssa:b_var():=beam_ssa:b_set()},
- ls :: #{beam_ssa:label():=type_db()},
- once :: cerl_sets:set(beam_ssa:b_var()),
- func_id :: func_id(),
- func_db :: func_info_db(),
- sub = #{} :: #{beam_ssa:b_var():=beam_ssa:value()},
- ret_type = [] :: [type()]}).
-
--define(ATOM_SET_SIZE, 5).
-
-%% Records that represent type information.
--record(t_atom, {elements=any :: 'any' | [atom()]}).
--record(t_integer, {elements=any :: 'any' | {integer(),integer()}}).
--record(t_bs_match, {type :: type()}).
--record(t_tuple, {size=0 :: integer(),
- exact=false :: boolean(),
- %% Known element types (1-based index), unknown elements are
- %% are assumed to be 'any'.
- elements=#{} :: #{ non_neg_integer() => type() }}).
-
--type type() :: 'any' | 'none' |
- #t_atom{} | #t_integer{} | #t_bs_match{} | #t_tuple{} |
- {'binary',pos_integer()} | 'cons' | 'float' | 'list' | 'map' | 'nil' | 'number'.
--type type_db() :: #{beam_ssa:var_name():=type()}.
-
--spec opt_start(Linear, Args, Anno, FuncDb) -> {Linear, FuncDb} when
- Linear :: [{non_neg_integer(), beam_ssa:b_blk()}],
- Args :: [beam_ssa:b_var()],
- Anno :: beam_ssa:anno(),
- FuncDb :: func_info_db().
-opt_start(Linear, Args, Anno, FuncDb) ->
- %% This is the first run through the module, so our arg_types can be
- %% incomplete as we may not have visited all call sites at least once.
- Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]),
- opt_continue_1(Linear, Args, get_func_id(Anno), Ts, FuncDb).
+-include("beam_types.hrl").
+
+-import(lists, [all/2,any/2,duplicate/2,foldl/3,member/2,
+ keyfind/3,reverse/1,split/2,zip/2]).
+
+%% The maximum number of #b_ret{} terminators a function can have before
+%% collapsing success types into a single entry. Consider the following code:
+%%
+%% f(0) -> 1;
+%% f(...) -> ...;
+%% f(500000) -> 500000.
+%%
+%% Since success types are grouped by return type and each clause returns a
+%% distinct type (singleton #t_integer{}s), we'll add 500000 entries which
+%% makes progress glacial since every call needs to examine them all to
+%% determine the return type.
+%%
+%% The entries are collapsed unconditionally if the number of returns in a
+%% function exceeds this threshold. This is necessary because collapsing as we
+%% go might widen a type; if we're at (?RETURN_LIMIT - 1) entries and suddenly
+%% narrow a type down, it could push us over the edge and collapse all entries,
+%% possibly widening the return type and breaking optimizations that were based
+%% on the earlier (narrower) types.
+-define(RETURN_LIMIT, 30).
+
+%% Constants common to all subpasses.
+-record(metadata,
+ { func_id :: func_id(),
+ limit_return :: boolean(),
+ params :: [beam_ssa:b_var()],
+ used_once :: cerl_sets:set(beam_ssa:b_var()) }).
+
+-type type_db() :: #{ beam_ssa:var_name() := type() }.
+
+%%
+
+-spec opt_start(term(), term()) -> term().
+opt_start(StMap, FuncDb0) when FuncDb0 =/= #{} ->
+ {ArgDb, FuncDb} = signatures(StMap, FuncDb0),
+
+ opt_start_1(maps:keys(StMap), ArgDb, StMap, FuncDb);
+opt_start(StMap, FuncDb) ->
+ %% Module-level analysis is disabled, likely because of a call to
+ %% load_nif/2 or similar. opt_continue/4 will assume that all arguments and
+ %% return types are 'any'.
+ {StMap, FuncDb}.
+
+opt_start_1([Id | Ids], ArgDb, StMap0, FuncDb0) ->
+ case ArgDb of
+ #{ Id := ArgTypes } ->
+ #opt_st{ssa=Linear0,args=Args} = St0 = map_get(Id, StMap0),
+
+ Ts = maps:from_list(zip(Args, ArgTypes)),
+ {Linear, FuncDb} = opt_function(Linear0, Args, Id, Ts, FuncDb0),
+
+ St = St0#opt_st{ssa=Linear},
+ StMap = StMap0#{ Id := St },
+
+ opt_start_1(Ids, ArgDb, StMap, FuncDb);
+ #{} ->
+ %% Unreachable functions must be removed so that opt_continue/4
+ %% won't process them and potentially taint the argument types of
+ %% other functions.
+ StMap = maps:remove(Id, StMap0),
+ FuncDb = maps:remove(Id, FuncDb0),
+
+ opt_start_1(Ids, ArgDb, StMap, FuncDb)
+ end;
+opt_start_1([], _CommittedArgs, StMap, FuncDb) ->
+ {StMap, FuncDb}.
+
+%%
+%% The initial signature analysis is based on the paper "Practical Type
+%% Inference Based on Success Typings" [1] by `Tobias Lindahl` and
+%% `Konstantinos Sagonas`, mainly section 6.1 and onwards.
+%%
+%% The general idea is to start out at the module's entry points and propagate
+%% types to the functions we call. The argument types of all exported functions
+%% start out a 'any', whereas local functions start at 'none'. Every time a
+%% function call widens the argument types, we analyze the callee again and
+%% propagate its return types to the callers, analyzing them again, and
+%% continuing this process until all arguments and return types have been
+%% widened as far as they can be.
+%%
+%% Note that we do not "jump-start the analysis" by first determining success
+%% types as in the paper because we need to know all possible inputs including
+%% those that will not return.
+%%
+%% [1] http://www.it.uu.se/research/group/hipe/papers/succ_types.pdf
+%%
+
+-record(sig_st,
+ { wl = wl_new() :: worklist(),
+ committed = #{} :: #{ func_id() => [type()] },
+ updates = #{} :: #{ func_id() => [type()] }}).
+
+signatures(StMap, FuncDb0) ->
+ State0 = init_sig_st(StMap, FuncDb0),
+ {State, FuncDb} = signatures_1(StMap, FuncDb0, State0),
+ {State#sig_st.committed, FuncDb}.
+
+signatures_1(StMap, FuncDb0, State0) ->
+ case wl_next(State0#sig_st.wl) of
+ {ok, FuncId} ->
+ {State, FuncDb} = sig_function(FuncId, StMap, State0, FuncDb0),
+ signatures_1(StMap, FuncDb, State);
+ empty ->
+ %% No more work to do, assert that we don't have any outstanding
+ %% updates.
+ #sig_st{updates=Same,committed=Same} = State0, %Assertion.
+
+ {State0, FuncDb0}
+ end.
+
+sig_function(Id, StMap, State0, FuncDb0) ->
+ case sig_function_1(Id, StMap, State0, FuncDb0) of
+ {false, false, State, FuncDb} ->
+ %% No added work and the types are identical. Pop ourselves from
+ %% the work list and move on to the next function.
+ Wl = wl_pop(Id, State#sig_st.wl),
+ {State#sig_st{wl=Wl}, FuncDb};
+ {false, true, State, FuncDb} ->
+ %% We've added some work and our return type is unchanged. Keep
+ %% following the work list without popping ourselves; we're very
+ %% likely to need to return here later and can avoid a lot of
+ %% redundant work by keeping our place in line.
+ {State, FuncDb};
+ {true, WlChanged, State, FuncDb} ->
+ %% Our return type has changed so all of our (previously analyzed)
+ %% callers need to be analyzed again.
+ %%
+ %% If our worklist is unchanged we'll pop ourselves since our
+ %% callers will add us back if we need to analyzed again, and
+ %% it's wasteful to stay in the worklist when we don't.
+ Wl0 = case WlChanged of
+ true -> State#sig_st.wl;
+ false -> wl_pop(Id, State#sig_st.wl)
+ end,
+
+ #func_info{in=Cs0} = map_get(Id, FuncDb0),
+ Callers = [C || C <- Cs0, is_map_key(C, State#sig_st.updates)],
+ Wl = wl_defer_list(Callers, Wl0),
+
+ {State#sig_st{wl=Wl}, FuncDb}
+ end.
+
+sig_function_1(Id, StMap, State0, FuncDb) ->
+ #opt_st{ssa=Linear,args=Args} = map_get(Id, StMap),
+
+ {ArgTypes, State1} = sig_commit_args(Id, State0),
+ Ts = maps:from_list(zip(Args, ArgTypes)),
+
+ FakeCall = #b_set{op=call,args=[#b_remote{mod=#b_literal{val=unknown},
+ name=#b_literal{val=unknown},
+ arity=0}]},
+
+ Ds = maps:from_list([{Var, FakeCall#b_set{dst=Var}} ||
+ #b_var{}=Var <- Args]),
+
+ Ls = #{ ?EXCEPTION_BLOCK => {incoming, Ts},
+ 0 => {incoming, Ts} },
+
+ Meta = init_metadata(Id, Linear, Args),
+
+ Wl0 = State1#sig_st.wl,
+
+ {State, SuccTypes} = sig_bs(Linear, Ds, Ls, FuncDb, #{}, [], Meta, State1),
+
+ WlChanged = wl_changed(Wl0, State#sig_st.wl),
+ #{ Id := #func_info{succ_types=SuccTypes0}=Entry0 } = FuncDb,
+
+ if
+ SuccTypes0 =:= SuccTypes ->
+ {false, WlChanged, State, FuncDb};
+ SuccTypes0 =/= SuccTypes ->
+ Entry = Entry0#func_info{succ_types=SuccTypes},
+ {true, WlChanged, State, FuncDb#{ Id := Entry }}
+ end.
+
+sig_bs([{L, #b_blk{is=Is,last=Last0}} | Bs],
+ Ds0, Ls0, Fdb, Sub0, SuccTypes0, Meta, State0) ->
+ case Ls0 of
+ #{ L := Incoming } ->
+ {incoming, Ts0} = Incoming, %Assertion.
+
+ {Ts, Ds, Sub, State} =
+ sig_is(Is, Ts0, Ds0, Ls0, Fdb, Sub0, State0),
+
+ Last = simplify_terminator(Last0, Ts, Ds, Sub),
+ SuccTypes = update_success_types(Last, Ts, Ds, Meta, SuccTypes0),
+
+ UsedOnce = Meta#metadata.used_once,
+ {_, Ls1} = update_successors(Last, Ts, Ds, Ls0, UsedOnce),
+
+ %% In the future there may be a point to storing outgoing types on
+ %% a per-edge basis as it would give us more precision in phi
+ %% nodes, but there's nothing to gain from that at the moment so
+ %% we'll store the current Ts to save memory.
+ Ls = Ls1#{ L := {outgoing, Ts} },
+ sig_bs(Bs, Ds, Ls, Fdb, Sub, SuccTypes, Meta, State);
+ #{} ->
+ %% This block is never reached. Ignore it.
+ sig_bs(Bs, Ds0, Ls0, Fdb, Sub0, SuccTypes0, Meta, State0)
+ end;
+sig_bs([], _Ds, _Ls, _Fdb, _Sub, SuccTypes, _Meta, State) ->
+ {State, SuccTypes}.
+
+sig_is([#b_set{op=call,
+ args=[#b_local{}=Callee | _]=Args0,
+ dst=Dst}=I0 | Is],
+ Ts0, Ds0, Ls, Fdb, Sub, State0) ->
+ Args = simplify_args(Args0, Ts0, Sub),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+
+ [_ | CallArgs] = Args,
+ {I, State} = sig_local_call(I1, Callee, CallArgs, Ts0, Fdb, State0),
+
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ sig_is(Is, Ts, Ds, Ls, Fdb, Sub, State);
+sig_is([#b_set{op=call,
+ args=[#b_var{} | _]=Args0,
+ dst=Dst}=I0 | Is],
+ Ts0, Ds0, Ls, Fdb, Sub, State) ->
+ Args = simplify_args(Args0, Ts0, Sub),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+
+ [Fun | _] = Args,
+ I = case normalized_type(Fun, Ts0) of
+ #t_fun{type=Type} -> beam_ssa:add_anno(result_type, Type, I1);
+ _ -> I1
+ end,
+
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ sig_is(Is, Ts, Ds, Ls, Fdb, Sub, State);
+sig_is([#b_set{op=make_fun,args=Args0,dst=Dst}=I0|Is],
+ Ts0, Ds0, Ls, Fdb, Sub0, State0) ->
+ Args = simplify_args(Args0, Ts0, Sub0),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+
+ {I, State} = sig_make_fun(I1, Ts0, Fdb, State0),
+
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ sig_is(Is, Ts, Ds, Ls, Fdb, Sub0, State);
+sig_is([I0 | Is], Ts0, Ds0, Ls, Fdb, Sub0, State) ->
+ case simplify(I0, Ts0, Ds0, Ls, Sub0) of
+ {#b_set{}, Ts, Ds} ->
+ sig_is(Is, Ts, Ds, Ls, Fdb, Sub0, State);
+ Sub when is_map(Sub) ->
+ sig_is(Is, Ts0, Ds0, Ls, Fdb, Sub, State)
+ end;
+sig_is([], Ts, Ds, _Ls, _Fdb, Sub, State) ->
+ {Ts, Ds, Sub, State}.
+
+sig_local_call(I0, Callee, Args, Ts, Fdb, State) ->
+ ArgTypes = argument_types(Args, Ts),
+ I = sig_local_return(I0, Callee, ArgTypes, Fdb),
+ {I, sig_update_args(Callee, ArgTypes, State)}.
+
+%% While it's impossible to tell which arguments a fun will be called with
+%% (someone could steal it through tracing and call it), we do know its free
+%% variables and can update their types as if this were a local call.
+sig_make_fun(#b_set{op=make_fun,
+ args=[#b_local{}=Callee | FreeVars]}=I0,
+ Ts, Fdb, State) ->
+ ArgCount = Callee#b_local.arity - length(FreeVars),
+
+ FVTypes = [raw_type(FreeVar, Ts) || FreeVar <- FreeVars],
+ ArgTypes = duplicate(ArgCount, any) ++ FVTypes,
+
+ I = sig_local_return(I0, Callee, ArgTypes, Fdb),
+ {I, sig_update_args(Callee, ArgTypes, State)}.
+
+sig_local_return(I, Callee, ArgTypes, Fdb) ->
+ #func_info{succ_types=SuccTypes} = map_get(Callee, Fdb),
+ case return_type(SuccTypes, ArgTypes) of
+ any -> I;
+ Type -> beam_ssa:add_anno(result_type, Type, I)
+ end.
+
+init_sig_st(StMap, FuncDb) ->
+ %% Start out as if all the roots have been called with 'any' for all
+ %% arguments.
+ Roots = init_sig_roots(FuncDb),
+ #sig_st{ committed=#{},
+ updates=init_sig_args(Roots, StMap, #{}),
+ wl=wl_defer_list(Roots, wl_new()) }.
+
+init_sig_roots(FuncDb) ->
+ maps:fold(fun(Id, #func_info{exported=true}, Acc) ->
+ [Id | Acc];
+ (_, _, Acc) ->
+ Acc
+ end, [], FuncDb).
+
+init_sig_args([Root | Roots], StMap, Acc) ->
+ #opt_st{args=Args0} = map_get(Root, StMap),
+ ArgTypes = lists:duplicate(length(Args0), any),
+ init_sig_args(Roots, StMap, Acc#{ Root => ArgTypes });
+init_sig_args([], _StMap, Acc) ->
+ Acc.
+
+sig_commit_args(Id, #sig_st{updates=Us,committed=Committed0}=State0) ->
+ Types = map_get(Id, Us),
+ Committed = Committed0#{ Id => Types },
+ State = State0#sig_st{committed=Committed},
+ {Types, State}.
+
+sig_update_args(Callee, Types, #sig_st{committed=Committed}=State) ->
+ case Committed of
+ #{ Callee := Current } ->
+ case parallel_join(Current, Types) of
+ Current ->
+ %% We've already processed this function with these
+ %% arguments, so there's no need to visit it again.
+ State;
+ Widened ->
+ sig_update_args_1(Callee, Widened, State)
+ end;
+ #{} ->
+ sig_update_args_1(Callee, Types, State)
+ end.
+
+sig_update_args_1(Callee, Types, #sig_st{updates=Us0,wl=Wl0}=State) ->
+ Us = case Us0 of
+ #{ Callee := Current } ->
+ Us0#{ Callee => parallel_join(Current, Types) };
+ #{} ->
+ Us0#{ Callee => Types }
+ end,
+ State#sig_st{updates=Us,wl=wl_add(Callee, Wl0)}.
-spec opt_continue(Linear, Args, Anno, FuncDb) -> {Linear, FuncDb} when
Linear :: [{non_neg_integer(), beam_ssa:b_blk()}],
Args :: [beam_ssa:b_var()],
Anno :: beam_ssa:anno(),
FuncDb :: func_info_db().
-opt_continue(Linear, Args, Anno, FuncDb) ->
+opt_continue(Linear0, Args, Anno, FuncDb) when FuncDb =/= #{} ->
Id = get_func_id(Anno),
case FuncDb of
#{ Id := #func_info{exported=false,arg_types=ArgTypes} } ->
%% This is a local function and we're guaranteed to have visited
%% every call site at least once, so we know that the parameter
%% types are at least as narrow as the join of all argument types.
- Ts = join_arg_types(Args, ArgTypes, Anno),
- opt_continue_1(Linear, Args, Id, Ts, FuncDb);
- #{} ->
- %% We can't infer the parameter types of exported functions, nor
- %% the ones where module-level optimization is disabled, but
+ Ts = join_arg_types(Args, ArgTypes, #{}),
+ opt_function(Linear0, Args, Id, Ts, FuncDb);
+ #{ Id := #func_info{exported=true} } ->
+ %% We can't infer the parameter types of exported functions, but
%% running the pass again could still help other functions.
Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]),
- opt_continue_1(Linear, Args, Id, Ts, FuncDb)
- end.
+ opt_function(Linear0, Args, Id, Ts, FuncDb)
+ end;
+opt_continue(Linear0, Args, Anno, _FuncDb) ->
+ %% Module-level optimization is disabled, pass an empty function database
+ %% so we only perform local optimizations.
+ Id = get_func_id(Anno),
+ Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]),
+ {Linear, _} = opt_function(Linear0, Args, Id, Ts, #{}),
+ {Linear, #{}}.
-join_arg_types(Args, ArgTypes, Anno) ->
- %% We suppress type optimization for parameters that have already been
- %% optimized by another pass, as they may have done things we have no idea
- %% how to interpret and running them over could generate incorrect code.
- ParamTypes = maps:get(parameter_type_info, Anno, #{}),
- Ts0 = join_arg_types_1(Args, ArgTypes, #{}),
- maps:fold(fun(Arg, _V, Ts) ->
- maps:put(Arg, any, Ts)
- end, Ts0, ParamTypes).
-
-join_arg_types_1([Arg | Args], [TM | TMs], Ts) when map_size(TM) =/= 0 ->
- join_arg_types_1(Args, TMs, Ts#{ Arg => join(maps:values(TM))});
-join_arg_types_1([Arg | Args], [_TM | TMs], Ts) ->
- join_arg_types_1(Args, TMs, Ts#{ Arg => any });
-join_arg_types_1([], [], Ts) ->
+join_arg_types([Arg | Args], [TypeMap | TMs], Ts) ->
+ Type = beam_types:join(maps:values(TypeMap)),
+ join_arg_types(Args, TMs, Ts#{ Arg => Type });
+join_arg_types([], [], Ts) ->
Ts.
--spec opt_continue_1(Linear, Args, Id, Ts, FuncDb) -> Result when
+%%
+%% Optimizes a function based on the type information inferred by signatures/2
+%% and earlier runs of opt_function/5.
+%%
+%% This is pretty straightforward as it only walks through each function once,
+%% and because it only makes types narrower it's safe to optimize the functions
+%% in any order or not at all.
+%%
+
+-spec opt_function(Linear, Args, Id, Ts, FuncDb) -> Result when
Linear :: [{non_neg_integer(), beam_ssa:b_blk()}],
Args :: [beam_ssa:b_var()],
Id :: func_id(),
Ts :: type_db(),
FuncDb :: func_info_db(),
Result :: {Linear, FuncDb}.
-opt_continue_1(Linear0, Args, Id, Ts, FuncDb0) ->
- UsedOnce = used_once(Linear0, Args),
+opt_function(Linear0, Args, Id, Ts, FuncDb0) ->
FakeCall = #b_set{op=call,args=[#b_remote{mod=#b_literal{val=unknown},
name=#b_literal{val=unknown},
arity=0}]},
- Defs = maps:from_list([{Var,FakeCall#b_set{dst=Var}} ||
- #b_var{}=Var <- Args]),
- D = #d{ func_db=FuncDb0,
- func_id=Id,
- ds=Defs,
- ls=#{0=>Ts,?BADARG_BLOCK=>#{}},
- once=UsedOnce },
+ Ds = maps:from_list([{Var, FakeCall#b_set{dst=Var}} ||
+ #b_var{}=Var <- Args]),
- {Linear, FuncDb, NewRet} = opt(Linear0, D, []),
+ Ls = #{ ?EXCEPTION_BLOCK => {incoming, Ts},
+ 0 => {incoming, Ts} },
+
+ Meta = init_metadata(Id, Linear0, Args),
+
+ {Linear, FuncDb, SuccTypes} =
+ opt_bs(Linear0, Ds, Ls, FuncDb0, #{}, [], Meta, []),
case FuncDb of
#{ Id := Entry0 } ->
- Entry = Entry0#func_info{ret_type=NewRet},
+ Entry = Entry0#func_info{succ_types=SuccTypes},
{Linear, FuncDb#{ Id := Entry }};
#{} ->
- %% Module-level optimizations have been turned off for this
- %% function.
+ %% Module-level optimizations have been turned off.
{Linear, FuncDb}
end.
+get_func_id(Anno) ->
+ #{func_info:={_Mod, Name, Arity}} = Anno,
+ #b_local{name=#b_literal{val=Name}, arity=Arity}.
+
+opt_bs([{L, #b_blk{is=Is0,last=Last0}=Blk0} | Bs],
+ Ds0, Ls0, Fdb0, Sub0, SuccTypes0, Meta, Acc) ->
+ case Ls0 of
+ #{ L := Incoming } ->
+ {incoming, Ts0} = Incoming, %Assertion.
+
+ {Is, Ts, Ds, Fdb, Sub} =
+ opt_is(Is0, Ts0, Ds0, Ls0, Fdb0, Sub0, Meta, []),
+
+ Last1 = simplify_terminator(Last0, Ts, Ds, Sub),
+ SuccTypes = update_success_types(Last1, Ts, Ds, Meta, SuccTypes0),
+
+ UsedOnce = Meta#metadata.used_once,
+ {Last, Ls1} = update_successors(Last1, Ts, Ds, Ls0, UsedOnce),
+
+ Ls = Ls1#{ L := {outgoing, Ts} }, %Assertion.
+
+ Blk = Blk0#b_blk{is=Is,last=Last},
+ opt_bs(Bs, Ds, Ls, Fdb, Sub, SuccTypes, Meta, [{L,Blk} | Acc]);
+ #{} ->
+ %% This block is never reached. Discard it.
+ opt_bs(Bs, Ds0, Ls0, Fdb0, Sub0, SuccTypes0, Meta, Acc)
+ end;
+opt_bs([], _Ds, _Ls, Fdb, _Sub, SuccTypes, _Meta, Acc) ->
+ {reverse(Acc), Fdb, SuccTypes}.
+
+opt_is([#b_set{op=call,
+ args=[#b_local{}=Callee | _]=Args0,
+ dst=Dst}=I0 | Is],
+ Ts0, Ds0, Ls, Fdb0, Sub, Meta, Acc) ->
+ Args = simplify_args(Args0, Ts0, Sub),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+
+ [_ | CallArgs] = Args,
+ {I, Fdb} = opt_local_call(I1, Callee, CallArgs, Dst, Ts0, Fdb0, Meta),
+
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ opt_is(Is, Ts, Ds, Ls, Fdb, Sub, Meta, [I | Acc]);
+opt_is([#b_set{op=call,
+ args=[#b_var{} | _]=Args0,
+ dst=Dst}=I0 | Is],
+ Ts0, Ds0, Ls, Fdb, Sub, Meta, Acc) ->
+ Args = simplify_args(Args0, Ts0, Sub),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+
+ [Fun | _] = Args,
+ I = case normalized_type(Fun, Ts0) of
+ #t_fun{type=Type} when Type =/= any ->
+ beam_ssa:add_anno(result_type, Type, I1);
+ _ ->
+ I1
+ end,
+
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ opt_is(Is, Ts, Ds, Ls, Fdb, Sub, Meta, [I | Acc]);
+opt_is([#b_set{op=make_fun,args=Args0,dst=Dst}=I0|Is],
+ Ts0, Ds0, Ls, Fdb0, Sub0, Meta, Acc) ->
+ Args = simplify_args(Args0, Ts0, Sub0),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+
+ {I, Fdb} = opt_make_fun(I1, Ts0, Fdb0, Meta),
+
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ opt_is(Is, Ts, Ds, Ls, Fdb, Sub0, Meta, [I|Acc]);
+opt_is([I0 | Is], Ts0, Ds0, Ls, Fdb, Sub0, Meta, Acc) ->
+ case simplify(I0, Ts0, Ds0, Ls, Sub0) of
+ {#b_set{}=I, Ts, Ds} ->
+ opt_is(Is, Ts, Ds, Ls, Fdb, Sub0, Meta, [I | Acc]);
+ Sub when is_map(Sub) ->
+ opt_is(Is, Ts0, Ds0, Ls, Fdb, Sub, Meta, Acc)
+ end;
+opt_is([], Ts, Ds, _Ls, Fdb, Sub, _Meta, Acc) ->
+ {reverse(Acc), Ts, Ds, Fdb, Sub}.
+
+opt_local_call(I0, Callee, Args, Dst, Ts, Fdb, Meta) ->
+ ArgTypes = argument_types(Args, Ts),
+ I = opt_local_return(I0, Callee, ArgTypes, Fdb),
+ case Fdb of
+ #{ Callee := #func_info{exported=false,arg_types=AT0}=Info0 } ->
+ %% Update the argument types of *this exact call*, the types
+ %% will be joined later when the callee is optimized.
+ CallId = {Meta#metadata.func_id, Dst},
+
+ AT = update_arg_types(ArgTypes, AT0, CallId),
+ Info = Info0#func_info{arg_types=AT},
+
+ {I, Fdb#{ Callee := Info }};
+ #{} ->
+ %% We can't narrow the argument types of exported functions as they
+ %% can receive anything as part of an external call. We can still
+ %% rely on their return types however.
+ {I, Fdb}
+ end.
+
+%% See sig_make_fun/4
+opt_make_fun(#b_set{op=make_fun,
+ dst=Dst,
+ args=[#b_local{}=Callee | FreeVars]}=I0,
+ Ts, Fdb, Meta) ->
+ ArgCount = Callee#b_local.arity - length(FreeVars),
+ FVTypes = [raw_type(FreeVar, Ts) || FreeVar <- FreeVars],
+ ArgTypes = duplicate(ArgCount, any) ++ FVTypes,
+
+ I = opt_local_return(I0, Callee, ArgTypes, Fdb),
+
+ case Fdb of
+ #{ Callee := #func_info{exported=false,arg_types=AT0}=Info0 } ->
+ CallId = {Meta#metadata.func_id, Dst},
+
+ AT = update_arg_types(ArgTypes, AT0, CallId),
+ Info = Info0#func_info{arg_types=AT},
+
+ {I, Fdb#{ Callee := Info }};
+ #{} ->
+ %% We can't narrow the argument types of exported functions as they
+ %% can receive anything as part of an external call.
+ {I, Fdb}
+ end.
+
+opt_local_return(I, Callee, ArgTypes, Fdb) when Fdb =/= #{} ->
+ #func_info{succ_types=SuccTypes} = map_get(Callee, Fdb),
+ case return_type(SuccTypes, ArgTypes) of
+ any -> I;
+ Type -> beam_ssa:add_anno(result_type, Type, I)
+ end;
+opt_local_return(I, _Callee, _ArgTyps, _Fdb) ->
+ %% Module-level optimization is disabled, assume it returns anything.
+ I.
+
+update_arg_types([ArgType | ArgTypes], [TypeMap0 | TypeMaps], CallId) ->
+ TypeMap = TypeMap0#{ CallId => ArgType },
+ [TypeMap | update_arg_types(ArgTypes, TypeMaps, CallId)];
+update_arg_types([], [], _CallId) ->
+ [].
+
+%%
+
-spec opt_finish(Args, Anno, FuncDb) -> {Anno, FuncDb} when
Args :: [beam_ssa:b_var()],
Anno :: beam_ssa:anno(),
@@ -145,325 +591,140 @@ opt_finish(Args, Anno, FuncDb) ->
Id = get_func_id(Anno),
case FuncDb of
#{ Id := #func_info{exported=false,arg_types=ArgTypes} } ->
- ParamInfo0 = maps:get(parameter_type_info, Anno, #{}),
+ ParamInfo0 = maps:get(parameter_info, Anno, #{}),
ParamInfo = opt_finish_1(Args, ArgTypes, ParamInfo0),
- {Anno#{ parameter_type_info => ParamInfo }, FuncDb};
+ {Anno#{ parameter_info => ParamInfo }, FuncDb};
#{} ->
{Anno, FuncDb}
end.
-opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo)
- when is_map_key(Arg, ParamInfo); %% See join_arg_types/3
- map_size(TypeMap) =:= 0 ->
- opt_finish_1(Args, TypeMaps, ParamInfo);
-opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo0) ->
- case join(maps:values(TypeMap)) of
+opt_finish_1([Arg | Args], [TypeMap | TypeMaps], Acc0) ->
+ case beam_types:join(maps:values(TypeMap)) of
any ->
- opt_finish_1(Args, TypeMaps, ParamInfo0);
- none ->
- %% This function will never be called. Pretend that we don't
- %% know the type for this argument.
- opt_finish_1(Args, TypeMaps, ParamInfo0);
+ opt_finish_1(Args, TypeMaps, Acc0);
JoinedType ->
- JoinedType = verified_type(JoinedType),
- ParamInfo = ParamInfo0#{ Arg => validator_anno(JoinedType) },
- opt_finish_1(Args, TypeMaps, ParamInfo)
- end;
-opt_finish_1([], [], ParamInfo) ->
- ParamInfo.
-
-validator_anno(#t_tuple{size=Size,exact=Exact,elements=Elements0}) ->
- Elements = maps:fold(fun(Index, Type, Acc) ->
- Key = beam_validator:type_anno(integer, Index),
- Acc#{ Key => validator_anno(Type) }
- end, #{}, Elements0),
- beam_validator:type_anno(tuple, Size, Exact, Elements);
-validator_anno(#t_integer{elements={Same,Same}}) ->
- beam_validator:type_anno(integer, Same);
-validator_anno(#t_integer{}) ->
- beam_validator:type_anno(integer);
-validator_anno(float) ->
- beam_validator:type_anno(float);
-validator_anno(#t_atom{elements=[Val]}) ->
- beam_validator:type_anno(atom, Val);
-validator_anno(#t_atom{}=A) ->
- case t_is_boolean(A) of
- true -> beam_validator:type_anno(bool);
- false -> beam_validator:type_anno(atom)
+ Info = maps:get(Arg, Acc0, []),
+ Acc = Acc0#{ Arg => [{type, JoinedType} | Info] },
+ opt_finish_1(Args, TypeMaps, Acc)
end;
-validator_anno(T) ->
- beam_validator:type_anno(T).
+opt_finish_1([], [], Acc) ->
+ Acc.
-get_func_id(Anno) ->
- #{func_info:={_Mod, Name, Arity}} = Anno,
- #b_local{name=#b_literal{val=Name}, arity=Arity}.
+%%%
+%%% Optimization helpers
+%%%
-opt([{L,Blk}|Bs], #d{ls=Ls}=D, Acc) ->
- case Ls of
- #{L:=Ts} ->
- opt_1(L, Blk, Bs, Ts, D, Acc);
- #{} ->
- %% This block is never reached. Discard it.
- opt(Bs, D, Acc)
+simplify_terminator(#b_br{bool=Bool}=Br0, Ts, Ds, Sub) ->
+ Br = beam_ssa:normalize(Br0#b_br{bool=simplify_arg(Bool, Ts, Sub)}),
+ simplify_not(Br, Ts, Ds, Sub);
+simplify_terminator(#b_switch{arg=Arg0,fail=Fail,list=List0}=Sw0,
+ Ts, Ds, Sub) ->
+ Arg = simplify_arg(Arg0, Ts, Sub),
+ %% Ensure that no label in the switch list is the same as the
+ %% failure label.
+ List = [{Val,Lbl} || {Val,Lbl} <- List0, Lbl =/= Fail],
+ case beam_ssa:normalize(Sw0#b_switch{arg=Arg,list=List}) of
+ #b_switch{}=Sw ->
+ case beam_types:is_boolean_type(raw_type(Arg, Ts)) of
+ true -> simplify_switch_bool(Sw, Ts, Ds, Sub);
+ false -> Sw
+ end;
+ #b_br{}=Br ->
+ simplify_terminator(Br, Ts, Ds, Sub)
end;
-opt([], D, Acc) ->
- #d{func_db=FuncDb,ret_type=NewRet} = D,
- {reverse(Acc), FuncDb, NewRet}.
-
-opt_1(L, #b_blk{is=Is0,last=Last0}=Blk0, Bs, Ts0,
- #d{ds=Ds0,sub=Sub0,func_db=Fdb0}=D0, Acc) ->
- case opt_is(Is0, Ts0, Ds0, Fdb0, D0, Sub0, []) of
- {Is,Ts,Ds,Fdb,Sub} ->
- D1 = D0#d{ds=Ds,sub=Sub,func_db=Fdb},
- Last1 = simplify_terminator(Last0, Sub, Ts, Ds),
- Last = opt_terminator(Last1, Ts, Ds),
- D = update_successors(Last, Ts, D1),
- Blk = Blk0#b_blk{is=Is,last=Last},
- opt(Bs, D, [{L,Blk}|Acc]);
- {no_return,Ret,Is,Ds,Fdb,Sub} ->
- %% This call will never reach the successor block.
- %% Rewrite the terminator to a 'ret', and remove
- %% all type information for this label. That can
- %% potentially narrow the type of the phi node
- %% in the former successor.
- Ls = maps:remove(L, D0#d.ls),
- RetType = join([none|D0#d.ret_type]),
- D = D0#d{ds=Ds,ls=Ls,sub=Sub,
- func_db=Fdb,ret_type=[RetType]},
- Blk = Blk0#b_blk{is=Is,last=Ret},
- opt(Bs, D, [{L,Blk}|Acc])
- end.
-
-simplify_terminator(#b_br{bool=Bool}=Br, Sub, Ts, _Ds) ->
- Br#b_br{bool=simplify_arg(Bool, Sub, Ts)};
-simplify_terminator(#b_switch{arg=Arg}=Sw, Sub, Ts, _Ds) ->
- Sw#b_switch{arg=simplify_arg(Arg, Sub, Ts)};
-simplify_terminator(#b_ret{arg=Arg}=Ret, Sub, Ts, Ds) ->
+simplify_terminator(#b_ret{arg=Arg}=Ret, Ts, Ds, Sub) ->
%% Reducing the result of a call to a literal (fairly common for 'ok')
%% breaks tail call optimization.
case Ds of
#{ Arg := #b_set{op=call}} -> Ret;
- #{} -> Ret#b_ret{arg=simplify_arg(Arg, Sub, Ts)}
+ #{} -> Ret#b_ret{arg=simplify_arg(Arg, Ts, Sub)}
end.
-opt_is([#b_set{op=phi,dst=Dst,args=Args0}=I0|Is],
- Ts0, Ds0, Fdb, #d{ls=Ls}=D, Sub0, Acc) ->
+%%
+%% Simplifies an instruction, returning either a new instruction (with updated
+%% type and definition maps), or an updated substitution map if the instruction
+%% was redundant.
+%%
+
+simplify(#b_set{op=phi,dst=Dst,args=Args0}=I0, Ts0, Ds0, Ls, Sub) ->
%% Simplify the phi node by removing all predecessor blocks that no
%% longer exists or no longer branches to this block.
- Args = [{simplify_arg(Arg, Sub0, Ts0),From} ||
- {Arg,From} <- Args0, maps:is_key(From, Ls)],
- case all_same(Args) of
+ {Type, Args} = simplify_phi_args(Args0, Ls, Sub, none, []),
+ case phi_all_same(Args) of
true ->
%% Eliminate the phi node if there is just one source
%% value or if the values are identical.
- [{Val,_}|_] = Args,
- Sub = Sub0#{Dst=>Val},
- opt_is(Is, Ts0, Ds0, Fdb, D, Sub, Acc);
+ [{Val, _} | _] = Args,
+ Sub#{ Dst => Val };
false ->
I = I0#b_set{args=Args},
- Ts = update_types(I, Ts0, Ds0),
- Ds = Ds0#{Dst=>I},
- opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I|Acc])
- end;
-opt_is([#b_set{op=call,args=Args0,dst=Dst}=I0|Is],
- Ts0, Ds0, Fdb0, D, Sub0, Acc) ->
- Args = simplify_args(Args0, Sub0, Ts0),
- I1 = beam_ssa:normalize(I0#b_set{args=Args}),
- {Ts1,Ds,Fdb,I2} = opt_call(I1, D, Ts0, Ds0, Fdb0),
- case {map_get(Dst, Ts1),Is} of
- {Type,[#b_set{op=succeeded}]} when Type =/= none ->
- %% This call instruction is inside a try/catch
- %% block. Don't attempt to simplify it.
- opt_is(Is, Ts1, Ds, Fdb, D, Sub0, [I2|Acc]);
- {none,[#b_set{op=succeeded}]} ->
- %% This call instruction is inside a try/catch
- %% block, but we know it will never return and
- %% later optimizations may try to exploit that.
- %%
- %% For example, if we have an expression that
- %% either returns this call or a tuple, we know
- %% that the expression always returns a tuple
- %% and can turn a later element/3 into
- %% get_tuple_element.
- %%
- %% This is sound but difficult to validate in a
- %% meaningful way as try/catch currently forces
- %% us to maintain the illusion that the success
- %% block is reachable even when its not, so we
- %% disable the optimization to keep things
- %% simple.
- Ts = Ts1#{ Dst := any },
- opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I2|Acc]);
- {none,_} ->
- %% This call never returns. The rest of the
- %% instructions will not be executed.
- Ret = #b_ret{arg=Dst},
- {no_return,Ret,reverse(Acc, [I2]),Ds,Fdb,Sub0};
- {_,_} ->
- case simplify_call(I2) of
- #b_set{}=I ->
- opt_is(Is, Ts1, Ds, Fdb, D, Sub0, [I|Acc]);
- #b_literal{}=Lit ->
- Sub = Sub0#{Dst=>Lit},
- Ts = maps:remove(Dst, Ts1),
- opt_is(Is, Ts, Ds0, Fdb, D, Sub, Acc);
- #b_var{}=Var ->
- Ts = maps:remove(Dst, Ts1),
- Sub = Sub0#{Dst=>Var},
- opt_is(Is, Ts, Ds0, Fdb, D, Sub, Acc)
- end
- end;
-opt_is([#b_set{op=succeeded,args=[Arg],dst=Dst}=I],
- Ts0, Ds0, Fdb, D, Sub0, Acc) ->
- case Ds0 of
- #{ Arg := #b_set{op=call} } ->
- %% The success check of a call is part of exception handling and
- %% must not be optimized away. We still have to update its type
- %% though.
- Ts = update_types(I, Ts0, Ds0),
- Ds = Ds0#{Dst=>I},
- opt_is([], Ts, Ds, Fdb, D, Sub0, [I|Acc]);
- #{} ->
- Args = simplify_args([Arg], Sub0, Ts0),
- Type = type(succeeded, Args, Ts0, Ds0),
- case get_literal_from_type(Type) of
- #b_literal{}=Lit ->
- Sub = Sub0#{Dst=>Lit},
- opt_is([], Ts0, Ds0, Fdb, D, Sub, Acc);
- none ->
- Ts = Ts0#{Dst=>Type},
- Ds = Ds0#{Dst=>I},
- opt_is([], Ts, Ds, Fdb, D, Sub0, [I|Acc])
- end
+ Ts = Ts0#{ Dst => Type },
+ Ds = Ds0#{ Dst => I },
+ {I, Ts, Ds}
+ end;
+simplify(#b_set{op={succeeded,Kind},args=[Arg],dst=Dst}=I0,
+ Ts0, Ds0, _Ls, Sub) ->
+ Type = case will_succeed(I0, Ts0, Ds0, Sub) of
+ yes -> beam_types:make_atom(true);
+ no -> beam_types:make_atom(false);
+ maybe -> beam_types:make_boolean()
+ end,
+ case Type of
+ #t_atom{elements=[true]} ->
+ %% The checked operation always succeeds, so it's safe to remove
+ %% this instruction regardless of whether we're in a guard or not.
+ Lit = #b_literal{val=true},
+ Sub#{ Dst => Lit };
+ #t_atom{elements=[false]} when Kind =:= guard ->
+ %% Failing operations are only safe to remove in guards.
+ Lit = #b_literal{val=false},
+ Sub#{ Dst => Lit };
+ _ ->
+ true = is_map_key(Arg, Ds0), %Assertion.
+
+ %% Note that we never simplify args; this instruction is specific
+ %% to the operation being checked, and simplifying could break that
+ %% connection.
+ I = beam_ssa:normalize(I0),
+ Ts = Ts0#{ Dst => Type },
+ Ds = Ds0#{ Dst => I },
+ {I, Ts, Ds}
end;
-opt_is([#b_set{args=Args0,dst=Dst}=I0|Is],
- Ts0, Ds0, Fdb, D, Sub0, Acc) ->
- Args = simplify_args(Args0, Sub0, Ts0),
+simplify(#b_set{op=bs_match,dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) ->
+ Args = simplify_args(Args0, Ts0, Sub),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+ I2 = case {Args0,Args} of
+ {[_,_,_,#b_var{},_],[Type,Val,Flags,#b_literal{val=all},Unit]} ->
+ %% The size `all` is used for the size of the final binary
+ %% segment in a pattern. Using `all` explicitly is not allowed,
+ %% so we convert it to an obvious invalid size.
+ I1#b_set{args=[Type,Val,Flags,#b_literal{val=bad_size},Unit]};
+ {_,_} ->
+ I1
+ end,
+ %% We KNOW that simplify/2 will return a #b_set{} record when called with
+ %% a bs_match instruction.
+ #b_set{} = I3 = simplify(I2, Ts0),
+ I = beam_ssa:normalize(I3),
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ {I, Ts, Ds};
+simplify(#b_set{dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) ->
+ Args = simplify_args(Args0, Ts0, Sub),
I1 = beam_ssa:normalize(I0#b_set{args=Args}),
case simplify(I1, Ts0) of
#b_set{}=I2 ->
I = beam_ssa:normalize(I2),
Ts = update_types(I, Ts0, Ds0),
- Ds = Ds0#{Dst=>I},
- opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I|Acc]);
+ Ds = Ds0#{ Dst => I },
+ {I, Ts, Ds};
#b_literal{}=Lit ->
- Sub = Sub0#{Dst=>Lit},
- opt_is(Is, Ts0, Ds0, Fdb, D, Sub, Acc);
+ Sub#{ Dst => Lit };
#b_var{}=Var ->
- case Is of
- [#b_set{op=succeeded,dst=SuccDst,args=[Dst]}] ->
- %% We must remove this 'succeeded' instruction.
- Sub = Sub0#{Dst=>Var,SuccDst=>#b_literal{val=true}},
- opt_is([], Ts0, Ds0, Fdb, D, Sub, Acc);
- _ ->
- Sub = Sub0#{Dst=>Var},
- opt_is(Is, Ts0, Ds0, Fdb, D, Sub, Acc)
- end
- end;
-opt_is([], Ts, Ds, Fdb, _D, Sub, Acc) ->
- {reverse(Acc), Ts, Ds, Fdb, Sub}.
-
-simplify_call(#b_set{op=call,args=[#b_remote{}=Rem|Args]}=I) ->
- case Rem of
- #b_remote{mod=#b_literal{val=Mod},
- name=#b_literal{val=Name}} ->
- case erl_bifs:is_pure(Mod, Name, length(Args)) of
- true ->
- simplify_remote_call(Mod, Name, Args, I);
- false ->
- I
- end;
- #b_remote{} ->
- I
- end;
-simplify_call(I) -> I.
-
-%% Simplify a remote call to a pure BIF.
-simplify_remote_call(erlang, '++', [#b_literal{val=[]},Tl], _I) ->
- Tl;
-simplify_remote_call(erlang, setelement,
- [#b_literal{val=Pos},
- #b_literal{val=Tuple},
- #b_var{}=Value], I)
- when is_integer(Pos), 1 =< Pos, Pos =< tuple_size(Tuple) ->
- %% Position is a literal integer and the shape of the
- %% tuple is known.
- Els0 = [#b_literal{val=El} || El <- tuple_to_list(Tuple)],
- {Bef,[_|Aft]} = split(Pos - 1, Els0),
- Els = Bef ++ [Value|Aft],
- I#b_set{op=put_tuple,args=Els};
-simplify_remote_call(Mod, Name, Args0, I) ->
- case make_literal_list(Args0) of
- none ->
- I;
- Args ->
- %% The arguments are literals. Try to evaluate the BIF.
- try apply(Mod, Name, Args) of
- Val ->
- case cerl:is_literal_term(Val) of
- true ->
- #b_literal{val=Val};
- false ->
- %% The value can't be expressed as a literal
- %% (e.g. a pid).
- I
- end
- catch
- _:_ ->
- %% Failed. Don't bother trying to optimize
- %% the call.
- I
- end
+ Sub#{ Dst => Var }
end.
-opt_call(#b_set{dst=Dst,args=[#b_local{}=Callee|Args]}=I0, D, Ts0, Ds0, Fdb0) ->
- {Ts, Ds, I} = opt_local_call(I0, Ts0, Ds0, Fdb0),
- case Fdb0 of
- #{ Callee := #func_info{exported=false,arg_types=ArgTypes0}=Info } ->
- %% Update the argument types of *this exact call*, the types
- %% will be joined later when the callee is optimized.
- CallId = {D#d.func_id, Dst},
- ArgTypes = update_arg_types(Args, ArgTypes0, CallId, Ts0),
-
- Fdb = Fdb0#{ Callee => Info#func_info{arg_types=ArgTypes} },
- {Ts, Ds, Fdb, I};
- #{} ->
- %% We can't narrow the argument types of exported functions as they
- %% can receive anything as part of an external call.
- {Ts, Ds, Fdb0, I}
- end;
-opt_call(#b_set{dst=Dst}=I, _D, Ts0, Ds0, Fdb) ->
- Ts = update_types(I, Ts0, Ds0),
- Ds = Ds0#{ Dst => I },
- {Ts, Ds, Fdb, I}.
-
-opt_local_call(#b_set{dst=Dst,args=[Id|_]}=I0, Ts0, Ds0, Fdb) ->
- Type = case Fdb of
- #{ Id := #func_info{ret_type=[T]} } -> T;
- #{} -> any
- end,
- I = case Type of
- any -> I0;
- none -> I0;
- _ -> beam_ssa:add_anno(result_type, validator_anno(Type), I0)
- end,
- Ts = Ts0#{ Dst => Type },
- Ds = Ds0#{ Dst => I },
- {Ts, Ds, I}.
-
-update_arg_types([Arg | Args], [TypeMap0 | TypeMaps], CallId, Ts) ->
- %% Match contexts are treated as bitstrings when optimizing arguments, as
- %% we don't yet support removing the "bs_start_match3" instruction.
- NewType = case get_type(Arg, Ts) of
- #t_bs_match{} -> {binary, 1};
- Type -> Type
- end,
- TypeMap = TypeMap0#{ CallId => NewType },
- [TypeMap | update_arg_types(Args, TypeMaps, CallId, Ts)];
-update_arg_types([], [], _CallId, _Ts) ->
- [].
-
simplify(#b_set{op={bif,'and'},args=Args}=I, Ts) ->
case is_safe_bool_op(Args, Ts) of
true ->
@@ -487,8 +748,10 @@ simplify(#b_set{op={bif,'or'},args=Args}=I, Ts) ->
I
end;
simplify(#b_set{op={bif,element},args=[#b_literal{val=Index},Tuple]}=I0, Ts) ->
- case t_tuple_size(get_type(Tuple, Ts)) of
- {_,Size} when is_integer(Index), 1 =< Index, Index =< Size ->
+ case normalized_type(Tuple, Ts) of
+ #t_tuple{size=Size} when is_integer(Index),
+ 1 =< Index,
+ Index =< Size ->
I = I0#b_set{op=get_tuple_element,
args=[Tuple,#b_literal{val=Index-1}]},
simplify(I, Ts);
@@ -496,67 +759,110 @@ simplify(#b_set{op={bif,element},args=[#b_literal{val=Index},Tuple]}=I0, Ts) ->
eval_bif(I0, Ts)
end;
simplify(#b_set{op={bif,hd},args=[List]}=I, Ts) ->
- case get_type(List, Ts) of
- cons ->
+ case normalized_type(List, Ts) of
+ #t_cons{} ->
I#b_set{op=get_hd};
_ ->
eval_bif(I, Ts)
end;
simplify(#b_set{op={bif,tl},args=[List]}=I, Ts) ->
- case get_type(List, Ts) of
- cons ->
+ case normalized_type(List, Ts) of
+ #t_cons{} ->
I#b_set{op=get_tl};
_ ->
eval_bif(I, Ts)
end;
simplify(#b_set{op={bif,size},args=[Term]}=I, Ts) ->
- case get_type(Term, Ts) of
+ case normalized_type(Term, Ts) of
#t_tuple{} ->
simplify(I#b_set{op={bif,tuple_size}}, Ts);
+ #t_bitstring{size_unit=U} when U rem 8 =:= 0 ->
+ %% If the bitstring is a binary (the size in bits is
+ %% evenly divisibly by 8), byte_size/1 gives
+ %% the same result as size/1.
+ simplify(I#b_set{op={bif,byte_size}}, Ts);
_ ->
eval_bif(I, Ts)
end;
simplify(#b_set{op={bif,tuple_size},args=[Term]}=I, Ts) ->
- case get_type(Term, Ts) of
+ case normalized_type(Term, Ts) of
#t_tuple{size=Size,exact=true} ->
#b_literal{val=Size};
_ ->
I
end;
-simplify(#b_set{op={bif,'=='},args=Args}=I, Ts) ->
- Types = get_types(Args, Ts),
- EqEq = case {meet(Types),join(Types)} of
- {none,any} -> true;
- {#t_integer{},#t_integer{}} -> true;
- {float,float} -> true;
- {{binary,_},_} -> true;
- {#t_atom{},_} -> true;
- {_,_} -> false
- end,
+simplify(#b_set{op={bif,is_function},args=[Fun,#b_literal{val=Arity}]}=I, Ts)
+ when is_integer(Arity), Arity >= 0 ->
+ case normalized_type(Fun, Ts) of
+ #t_fun{arity=any} ->
+ I;
+ #t_fun{arity=Arity} ->
+ #b_literal{val=true};
+ any ->
+ I;
+ _ ->
+ #b_literal{val=false}
+ end;
+simplify(#b_set{op={bif,is_map_key},args=[Key,Map]}=I, Ts) ->
+ case normalized_type(Map, Ts) of
+ #t_map{} ->
+ I#b_set{op=has_map_field,args=[Map,Key]};
+ _ ->
+ I
+ end;
+simplify(#b_set{op={bif,Op0},args=Args}=I, Ts) when Op0 =:= '==';
+ Op0 =:= '/=' ->
+ Types = normalized_types(Args, Ts),
+ EqEq0 = case {beam_types:meet(Types),beam_types:join(Types)} of
+ {none,any} -> true;
+ {#t_integer{},#t_integer{}} -> true;
+ {#t_float{},#t_float{}} -> true;
+ {#t_bitstring{},_} -> true;
+ {#t_atom{},_} -> true;
+ {_,_} -> false
+ end,
+ EqEq = EqEq0 orelse any_non_numeric_argument(Args, Ts),
case EqEq of
true ->
- simplify(I#b_set{op={bif,'=:='}}, Ts);
+ Op = case Op0 of
+ '==' -> '=:=';
+ '/=' -> '=/='
+ end,
+ simplify(I#b_set{op={bif,Op}}, Ts);
false ->
eval_bif(I, Ts)
end;
simplify(#b_set{op={bif,'=:='},args=[Same,Same]}, _Ts) ->
#b_literal{val=true};
-simplify(#b_set{op={bif,'=:='},args=[A1,_A2]=Args}=I, Ts) ->
- [T1,T2] = get_types(Args, Ts),
- case meet(T1, T2) of
+simplify(#b_set{op={bif,'=:='},args=[LHS,RHS]}=I, Ts) ->
+ LType = raw_type(LHS, Ts),
+ RType = raw_type(RHS, Ts),
+ case beam_types:meet(LType, RType) of
none ->
#b_literal{val=false};
_ ->
- case {t_is_boolean(T1),T2} of
+ case {beam_types:is_boolean_type(LType),
+ beam_types:normalize(RType)} of
{true,#t_atom{elements=[true]}} ->
%% Bool =:= true ==> Bool
- A1;
+ LHS;
+ {true,#t_atom{elements=[false]}} ->
+ %% Bool =:= false ==> not Bool
+ %%
+ %% This will be further optimized to eliminate the
+ %% 'not', swapping the success and failure
+ %% branches in the br instruction. If LHS comes
+ %% from a type test (such as is_atom/1) or a
+ %% comparison operator (such as >=) that can be
+ %% translated to test instruction, this
+ %% optimization will eliminate one instruction.
+ simplify(I#b_set{op={bif,'not'},args=[LHS]}, Ts);
{_,_} ->
eval_bif(I, Ts)
end
end;
simplify(#b_set{op={bif,Op},args=Args}=I, Ts) ->
- Types = get_types(Args, Ts),
+ Types = normalized_types(Args, Ts),
case is_float_op(Op, Types) of
false ->
eval_bif(I, Ts);
@@ -564,29 +870,57 @@ simplify(#b_set{op={bif,Op},args=Args}=I, Ts) ->
AnnoArgs = [anno_float_arg(A) || A <- Types],
eval_bif(beam_ssa:add_anno(float_op, AnnoArgs, I), Ts)
end;
-simplify(#b_set{op=get_tuple_element,args=[Tuple,#b_literal{val=N}]}=I, Ts) ->
- case get_type(Tuple, Ts) of
- #t_tuple{size=Size,elements=Es} when Size > N ->
- ElemType = get_element_type(N + 1, Es),
- case get_literal_from_type(ElemType) of
- #b_literal{}=Lit -> Lit;
- none -> I
- end;
- none ->
- %% Will never be executed because of type conflict.
- %% #b_literal{val=ignored};
+simplify(#b_set{op=bs_extract,args=[Ctx]}=I, Ts) ->
+ case raw_type(Ctx, Ts) of
+ #t_bitstring{} ->
+ %% This is a bs_match that has been rewritten as a bs_get_tail;
+ %% just return the input as-is.
+ Ctx;
+ #t_bs_context{} ->
+ I
+ end;
+simplify(#b_set{op=bs_match,
+ args=[#b_literal{val=binary}, Ctx, _Flags,
+ #b_literal{val=all},
+ #b_literal{val=OpUnit}]}=I, Ts) ->
+ %% <<..., Foo/binary>> can be rewritten as <<..., Foo/bits>> if we know the
+ %% unit is correct.
+ #t_bs_context{tail_unit=CtxUnit} = raw_type(Ctx, Ts),
+ if
+ CtxUnit rem OpUnit =:= 0 ->
+ I#b_set{op=bs_get_tail,args=[Ctx]};
+ CtxUnit rem OpUnit =/= 0 ->
+ I
+ end;
+simplify(#b_set{op=bs_start_match,args=[#b_literal{val=new}, Src]}=I, Ts) ->
+ case raw_type(Src, Ts) of
+ #t_bs_context{} ->
+ I#b_set{op=bs_start_match,args=[#b_literal{val=resume}, Src]};
+ _ ->
I
end;
+simplify(#b_set{op=get_tuple_element,args=[Tuple,#b_literal{val=N}]}=I, Ts) ->
+ #t_tuple{size=Size,elements=Es} = normalized_type(Tuple, Ts),
+ true = Size > N, %Assertion.
+ ElemType = beam_types:get_tuple_element(N + 1, Es),
+ case beam_types:get_singleton_value(ElemType) of
+ {ok, Val} -> #b_literal{val=Val};
+ error -> I
+ end;
simplify(#b_set{op=is_nonempty_list,args=[Src]}=I, Ts) ->
- case get_type(Src, Ts) of
- any -> I;
- list -> I;
- cons -> #b_literal{val=true};
- _ -> #b_literal{val=false}
+ case normalized_type(Src, Ts) of
+ any ->
+ I;
+ #t_list{} ->
+ I;
+ #t_cons{} ->
+ #b_literal{val=true};
+ _ ->
+ #b_literal{val=false}
end;
simplify(#b_set{op=is_tagged_tuple,
args=[Src,#b_literal{val=Size},#b_literal{}=Tag]}=I, Ts) ->
- simplify_is_record(I, get_type(Src, Ts), Size, Tag, Ts);
+ simplify_is_record(I, normalized_type(Src, Ts), Size, Tag, Ts);
simplify(#b_set{op=put_list,args=[#b_literal{val=H},
#b_literal{val=T}]}, _Ts) ->
#b_literal{val=[H|T]};
@@ -597,10 +931,312 @@ simplify(#b_set{op=put_tuple,args=Args}=I, _Ts) ->
end;
simplify(#b_set{op=wait_timeout,args=[#b_literal{val=0}]}, _Ts) ->
#b_literal{val=true};
-simplify(#b_set{op=wait_timeout,args=[#b_literal{val=infinity}]}=I, _Ts) ->
- I#b_set{op=wait,args=[]};
+simplify(#b_set{op=call,args=[#b_remote{}=Rem|Args]}=I, _Ts) ->
+ case Rem of
+ #b_remote{mod=#b_literal{val=Mod},
+ name=#b_literal{val=Name}} ->
+ case erl_bifs:is_pure(Mod, Name, length(Args)) of
+ true ->
+ simplify_remote_call(Mod, Name, Args, I);
+ false ->
+ I
+ end;
+ #b_remote{} ->
+ I
+ end;
+simplify(#b_set{op=call,args=[#b_literal{val=Fun}|Args]}=I, _Ts)
+ when is_function(Fun, length(Args)) ->
+ FI = erlang:fun_info(Fun),
+ {module,M} = keyfind(module, 1, FI),
+ {name,F} = keyfind(name, 1, FI),
+ {arity,A} = keyfind(arity, 1, FI),
+ Rem = #b_remote{mod=#b_literal{val=M},
+ name=#b_literal{val=F},
+ arity=A},
+ I#b_set{args=[Rem|Args]};
simplify(I, _Ts) -> I.
+will_succeed(#b_set{args=[Src]}, Ts, Ds, Sub) ->
+ case {Ds, Ts} of
+ {#{}, #{ Src := none }} ->
+ %% Checked operation never returns.
+ no;
+ {#{ Src := I }, #{}} ->
+ will_succeed_1(I, Src, Ts, Sub);
+ {#{}, #{}} ->
+ %% The checked instruction has been removed and substituted, so we
+ %% can assume it always succeeds.
+ true = is_map_key(Src, Sub), %Assertion.
+ yes
+ end.
+
+will_succeed_1(#b_set{op=bs_get_tail}, _Src, _Ts, _Sub) ->
+ yes;
+will_succeed_1(#b_set{op=bs_start_match,args=[_, Arg]}, _Src, Ts, _Sub) ->
+ ArgType = raw_type(Arg, Ts),
+ case beam_types:is_bs_matchable_type(ArgType) of
+ true ->
+ %% In the future we may be able to remove this instruction
+ %% altogether when we have a #t_bs_context{}, but for now we need
+ %% to keep it for compatibility with older releases of OTP.
+ yes;
+ false ->
+ %% Is it at all possible to match?
+ case beam_types:meet(ArgType, #t_bs_matchable{}) of
+ none -> no;
+ _ -> maybe
+ end
+ end;
+
+will_succeed_1(#b_set{op={bif,Bif},args=BifArgs}, _Src, Ts, _Sub) ->
+ ArgTypes = normalized_types(BifArgs, Ts),
+ beam_call_types:will_succeed(erlang, Bif, ArgTypes);
+will_succeed_1(#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=Mod},
+ name=#b_literal{val=Func}} |
+ CallArgs]},
+ _Src, Ts, _Sub) ->
+ ArgTypes = normalized_types(CallArgs, Ts),
+ beam_call_types:will_succeed(Mod, Func, ArgTypes);
+
+will_succeed_1(#b_set{op=get_hd}, _Src, _Ts, _Sub) ->
+ yes;
+will_succeed_1(#b_set{op=get_tl}, _Src, _Ts, _Sub) ->
+ yes;
+will_succeed_1(#b_set{op=has_map_field}, _Src, _Ts, _Sub) ->
+ yes;
+will_succeed_1(#b_set{op=get_tuple_element}, _Src, _Ts, _Sub) ->
+ yes;
+will_succeed_1(#b_set{op=put_tuple}, _Src, _Ts, _Sub) ->
+ yes;
+
+%% Remove the success branch from binary operations with invalid
+%% sizes. That will remove subsequent bs_put and bs_match instructions,
+%% which are probably not loadable.
+will_succeed_1(#b_set{op=bs_add,args=[Arg1,Arg2,_]},
+ _Src, _Ts, _Sub) ->
+ case all(fun(#b_literal{val=Size}) -> is_integer(Size) andalso Size >= 0;
+ (#b_var{}) -> true
+ end, [Arg1,Arg2]) of
+ true -> maybe;
+ false -> no
+ end;
+will_succeed_1(#b_set{op=bs_init,
+ args=[#b_literal{val=new},#b_literal{val=Size},_Unit]},
+ _Src, _Ts, _Sub) ->
+ if
+ is_integer(Size), Size >= 0 ->
+ maybe;
+ true ->
+ no
+ end;
+will_succeed_1(#b_set{op=bs_init,
+ args=[#b_literal{},_,#b_literal{val=Size},_Unit]},
+ _Src, _Ts, _Sub) ->
+ if
+ is_integer(Size), Size >= 0 ->
+ maybe;
+ true ->
+ no
+ end;
+will_succeed_1(#b_set{op=bs_match,
+ args=[#b_literal{val=Type},_,_,#b_literal{val=Size},_]},
+ _Src, _Ts, _Sub) ->
+ if
+ is_integer(Size), Size >= 0 ->
+ maybe;
+ Type =:= binary, Size =:= all ->
+ %% `all` is a legal size for binary segments at the end of
+ %% a binary pattern.
+ maybe;
+ true ->
+ %% Invalid size. Matching will fail.
+ no
+ end;
+
+%% These operations may fail even though we know their return value on success.
+will_succeed_1(#b_set{op=call}, _Src, _Ts, _Sub) ->
+ maybe;
+will_succeed_1(#b_set{op=get_map_element}, _Src, _Ts, _Sub) ->
+ maybe;
+
+will_succeed_1(#b_set{op=wait}, _Src, _Ts, _Sub) ->
+ no;
+
+will_succeed_1(#b_set{}, Src, Ts, Sub) ->
+ case simplify_arg(Src, Ts, Sub) of
+ #b_var{}=Src ->
+ %% No substitution; might fail at runtime.
+ maybe;
+ _ ->
+ %% Substituted with literal or other variable; always succeeds.
+ yes
+ end.
+
+simplify_is_record(I, #t_tuple{exact=Exact,
+ size=Size,
+ elements=Es},
+ RecSize, #b_literal{val=TagVal}=RecTag, Ts) ->
+ TagType = maps:get(1, Es, any),
+ TagMatch = case beam_types:get_singleton_value(TagType) of
+ {ok, TagVal} -> yes;
+ {ok, _} -> no;
+ error ->
+ %% Is it at all possible for the tag to match?
+ case beam_types:meet(raw_type(RecTag, Ts), TagType) of
+ none -> no;
+ _ -> maybe
+ end
+ end,
+ if
+ Size =/= RecSize, Exact; Size > RecSize; TagMatch =:= no ->
+ #b_literal{val=false};
+ Size =:= RecSize, Exact, TagMatch =:= yes ->
+ #b_literal{val=true};
+ true ->
+ I
+ end;
+simplify_is_record(I, any, _Size, _Tag, _Ts) ->
+ I;
+simplify_is_record(_I, _Type, _Size, _Tag, _Ts) ->
+ #b_literal{val=false}.
+
+simplify_switch_bool(#b_switch{arg=B,fail=Fail,list=List0}, Ts, Ds, Sub) ->
+ FalseVal = #b_literal{val=false},
+ TrueVal = #b_literal{val=true},
+ List1 = List0 ++ [{FalseVal,Fail},{TrueVal,Fail}],
+ {_,FalseLbl} = keyfind(FalseVal, 1, List1),
+ {_,TrueLbl} = keyfind(TrueVal, 1, List1),
+ Br = #b_br{bool=B,succ=TrueLbl,fail=FalseLbl},
+ simplify_terminator(Br, Ts, Ds, Sub).
+
+simplify_not(#b_br{bool=#b_var{}=V,succ=Succ,fail=Fail}=Br0, Ts, Ds, Sub) ->
+ case Ds of
+ #{V:=#b_set{op={bif,'not'},args=[Bool]}} ->
+ case beam_types:is_boolean_type(raw_type(Bool, Ts)) of
+ true ->
+ Br = Br0#b_br{bool=Bool,succ=Fail,fail=Succ},
+ simplify_terminator(Br, Ts, Ds, Sub);
+ false ->
+ Br0
+ end;
+ #{} ->
+ Br0
+ end;
+simplify_not(#b_br{bool=#b_literal{}}=Br, _Sub, _Ts, _Ds) ->
+ Br.
+
+simplify_phi_args([{Arg0, From} | Rest], Ls, Sub, Type0, Args) ->
+ case Ls of
+ #{ From := Outgoing } ->
+ {outgoing, Ts} = Outgoing, %Assertion.
+
+ Arg = simplify_arg(Arg0, Ts, Sub),
+ Type = beam_types:join(raw_type(Arg, Ts), Type0),
+ Phi = {Arg, From},
+
+ simplify_phi_args(Rest, Ls, Sub, Type, [Phi | Args]);
+ #{} ->
+ simplify_phi_args(Rest, Ls, Sub, Type0, Args)
+ end;
+simplify_phi_args([], _Ls, _Sub, Type, Args) ->
+ %% We return the arguments in their incoming order so that they won't
+ %% change back and forth and ruin fixpoint iteration in beam_ssa_opt.
+ {Type, reverse(Args)}.
+
+phi_all_same([{Arg, _From} | Phis]) ->
+ phi_all_same_1(Phis, Arg).
+
+phi_all_same_1([{Arg, _From} | Phis], Arg) ->
+ phi_all_same_1(Phis, Arg);
+phi_all_same_1([], _Arg) ->
+ true;
+phi_all_same_1(_Phis, _Arg) ->
+ false.
+
+%% Simplify a remote call to a pure BIF.
+simplify_remote_call(erlang, '++', [#b_literal{val=[]},Tl], _I) ->
+ Tl;
+simplify_remote_call(erlang, setelement,
+ [#b_literal{val=Pos},
+ #b_literal{val=Tuple},
+ #b_var{}=Value], I)
+ when is_integer(Pos), 1 =< Pos, Pos =< tuple_size(Tuple) ->
+ %% Position is a literal integer and the shape of the
+ %% tuple is known.
+ Els0 = [#b_literal{val=El} || El <- tuple_to_list(Tuple)],
+ {Bef,[_|Aft]} = split(Pos - 1, Els0),
+ Els = Bef ++ [Value|Aft],
+ I#b_set{op=put_tuple,args=Els};
+simplify_remote_call(Mod, Name, Args0, I) ->
+ case make_literal_list(Args0) of
+ none ->
+ I;
+ Args ->
+ %% The arguments are literals. Try to evaluate the BIF.
+ try apply(Mod, Name, Args) of
+ Val ->
+ case cerl:is_literal_term(Val) of
+ true ->
+ #b_literal{val=Val};
+ false ->
+ %% The value can't be expressed as a literal
+ %% (e.g. a pid).
+ I
+ end
+ catch
+ _:_ ->
+ %% Failed. Don't bother trying to optimize
+ %% the call.
+ I
+ end
+ end.
+
+any_non_numeric_argument([#b_literal{val=Lit}|_], _Ts) ->
+ is_non_numeric(Lit);
+any_non_numeric_argument([#b_var{}=V|T], Ts) ->
+ is_non_numeric_type(raw_type(V, Ts)) orelse any_non_numeric_argument(T, Ts);
+any_non_numeric_argument([], _Ts) -> false.
+
+is_non_numeric([H|T]) ->
+ is_non_numeric(H) andalso is_non_numeric(T);
+is_non_numeric(Tuple) when is_tuple(Tuple) ->
+ is_non_numeric_tuple(Tuple, tuple_size(Tuple));
+is_non_numeric(Map) when is_map(Map) ->
+ %% Starting from OTP 18, map keys are compared using `=:=`.
+ %% Therefore, we only need to check that the values in the map are
+ %% non-numeric. (Support for compiling BEAM files for OTP releases
+ %% older than OTP 18 has been dropped.)
+ is_non_numeric(maps:values(Map));
+is_non_numeric(Num) when is_number(Num) ->
+ false;
+is_non_numeric(_) -> true.
+
+is_non_numeric_tuple(Tuple, El) when El >= 1 ->
+ is_non_numeric(element(El, Tuple)) andalso
+ is_non_numeric_tuple(Tuple, El-1);
+is_non_numeric_tuple(_Tuple, 0) -> true.
+
+is_non_numeric_type(#t_atom{}) -> true;
+is_non_numeric_type(#t_bitstring{}) -> true;
+is_non_numeric_type(#t_cons{type=Type,terminator=Terminator}) ->
+ is_non_numeric_type(Type) andalso is_non_numeric_type(Terminator);
+is_non_numeric_type(#t_list{type=Type,terminator=Terminator}) ->
+ is_non_numeric_type(Type) andalso is_non_numeric_type(Terminator);
+is_non_numeric_type(#t_map{super_value=Value}) ->
+ is_non_numeric_type(Value);
+is_non_numeric_type(nil) -> true;
+is_non_numeric_type(#t_tuple{size=Size,exact=true,elements=Types})
+ when map_size(Types) =:= Size ->
+ is_non_numeric_tuple_type(Size, Types);
+is_non_numeric_type(_) -> false.
+
+is_non_numeric_tuple_type(0, _Types) ->
+ true;
+is_non_numeric_tuple_type(Pos, Types) ->
+ is_non_numeric_type(map_get(Pos, Types)) andalso
+ is_non_numeric_tuple_type(Pos - 1, Types).
+
make_literal_list(Args) ->
make_literal_list(Args, []).
@@ -611,12 +1247,11 @@ make_literal_list([_|_], _) ->
make_literal_list([], Acc) ->
reverse(Acc).
-is_safe_bool_op(Args, Ts) ->
- [T1,T2] = get_types(Args, Ts),
- t_is_boolean(T1) andalso t_is_boolean(T2).
-
-all_same([{H,_}|T]) ->
- all(fun({E,_}) -> E =:= H end, T).
+is_safe_bool_op([LHS, RHS], Ts) ->
+ LType = raw_type(LHS, Ts),
+ RType = raw_type(RHS, Ts),
+ beam_types:is_boolean_type(LType) andalso
+ beam_types:is_boolean_type(RType).
eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) ->
Arity = length(Args),
@@ -626,21 +1261,7 @@ eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) ->
true ->
case make_literal_list(Args) of
none ->
- case get_types(Args, Ts) of
- [any] ->
- I;
- [Type] ->
- case will_succeed(Bif, Type) of
- yes ->
- #b_literal{val=true};
- no ->
- #b_literal{val=false};
- maybe ->
- I
- end;
- _ ->
- I
- end;
+ eval_type_test_bif(I, Bif, raw_types(Args, Ts));
LitArgs ->
try apply(erlang, Bif, LitArgs) of
Val -> #b_literal{val=Val}
@@ -651,24 +1272,101 @@ eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) ->
end
end.
-simplify_args(Args, Sub, Ts) ->
- [simplify_arg(Arg, Sub, Ts) || Arg <- Args].
+eval_type_test_bif(I, is_atom, [Type]) ->
+ eval_type_test_bif_1(I, Type, #t_atom{});
+eval_type_test_bif(I, is_binary, [Type]) ->
+ eval_type_test_bif_1(I, Type, #t_bs_matchable{tail_unit=8});
+eval_type_test_bif(I, is_bitstring, [Type]) ->
+ eval_type_test_bif_1(I, Type, #t_bs_matchable{});
+eval_type_test_bif(I, is_boolean, [Type]) ->
+ case beam_types:is_boolean_type(Type) of
+ true ->
+ #b_literal{val=true};
+ false ->
+ case beam_types:meet(Type, #t_atom{}) of
+ #t_atom{elements=[_|_]=Es} ->
+ case any(fun is_boolean/1, Es) of
+ true -> I;
+ false -> #b_literal{val=false}
+ end;
+ #t_atom{} ->
+ I;
+ none ->
+ #b_literal{val=false}
+ end
+ end;
+eval_type_test_bif(I, is_float, [Type]) ->
+ eval_type_test_bif_1(I, Type, #t_float{});
+eval_type_test_bif(I, is_function, [Type]) ->
+ eval_type_test_bif_1(I, Type, #t_fun{});
+eval_type_test_bif(I, is_integer, [Type]) ->
+ eval_type_test_bif_1(I, Type, #t_integer{});
+eval_type_test_bif(I, is_list, [Type]) ->
+ eval_type_test_bif_1(I, Type, #t_list{});
+eval_type_test_bif(I, is_map, [Type]) ->
+ eval_type_test_bif_1(I, Type, #t_map{});
+eval_type_test_bif(I, is_number, [Type]) ->
+ eval_type_test_bif_1(I, Type, number);
+eval_type_test_bif(I, is_tuple, [Type]) ->
+ eval_type_test_bif_1(I, Type, #t_tuple{});
+eval_type_test_bif(I, Op, Types) ->
+ case Types of
+ [#t_integer{},#t_integer{elements={0,0}}]
+ when Op =:= '+'; Op =:= '-'; Op =:= 'bor'; Op =:= 'bxor' ->
+ #b_set{args=[Result,_]} = I,
+ Result;
+ [#t_integer{},#t_integer{elements={0,0}}] when Op =:= '*'; Op =:= 'band' ->
+ #b_literal{val=0};
+ [#t_integer{},#t_integer{elements={1,1}}] when Op =:= '*'; Op =:= 'div' ->
+ #b_set{args=[Result,_]} = I,
+ Result;
+ [#t_integer{elements={LMin,LMax}},#t_integer{elements={RMin,RMax}}] ->
+ case is_inequality_op(Op) of
+ true ->
+ case {erlang:Op(LMin, RMin),erlang:Op(LMax, RMin),
+ erlang:Op(LMin, RMax),erlang:Op(LMax, RMax)} of
+ {Bool,Bool,Bool,Bool} ->
+ #b_literal{val=Bool};
+ _ ->
+ I
+ end;
+ false ->
+ I
+ end;
+ _ ->
+ I
+ end.
+
+is_inequality_op('<') -> true;
+is_inequality_op('=<') -> true;
+is_inequality_op('>') -> true;
+is_inequality_op('>=') -> true;
+is_inequality_op(_) -> false.
+
+eval_type_test_bif_1(I, ArgType, Required) ->
+ case beam_types:meet(ArgType, Required) of
+ ArgType -> #b_literal{val=true};
+ none -> #b_literal{val=false};
+ _ -> I
+ end.
-simplify_arg(#b_var{}=Arg0, Sub, Ts) ->
+simplify_args(Args, Ts, Sub) ->
+ [simplify_arg(Arg, Ts, Sub) || Arg <- Args].
+
+simplify_arg(#b_var{}=Arg0, Ts, Sub) ->
case sub_arg(Arg0, Sub) of
#b_literal{}=LitArg ->
LitArg;
#b_var{}=Arg ->
- Type = get_type(Arg, Ts),
- case get_literal_from_type(Type) of
- none -> Arg;
- #b_literal{}=Lit -> Lit
+ case beam_types:get_singleton_value(raw_type(Arg, Ts)) of
+ {ok, Val} -> #b_literal{val=Val};
+ error -> Arg
end
end;
-simplify_arg(#b_remote{mod=Mod,name=Name}=Rem, Sub, Ts) ->
- Rem#b_remote{mod=simplify_arg(Mod, Sub, Ts),
- name=simplify_arg(Name, Sub, Ts)};
-simplify_arg(Arg, _Sub, _Ts) -> Arg.
+simplify_arg(#b_remote{mod=Mod,name=Name}=Rem, Ts, Sub) ->
+ Rem#b_remote{mod=simplify_arg(Mod, Ts, Sub),
+ name=simplify_arg(Name, Ts, Sub)};
+simplify_arg(Arg, _Ts, _Sub) -> Arg.
sub_arg(#b_var{}=Old, Sub) ->
case Sub of
@@ -676,13 +1374,13 @@ sub_arg(#b_var{}=Old, Sub) ->
#{} -> Old
end.
-is_float_op('-', [float]) ->
+is_float_op('-', [#t_float{}]) ->
true;
is_float_op('/', [_,_]) ->
true;
-is_float_op(Op, [float,_Other]) ->
+is_float_op(Op, [#t_float{},_Other]) ->
is_float_op_1(Op);
-is_float_op(Op, [_Other,float]) ->
+is_float_op(Op, [_Other,#t_float{}]) ->
is_float_op_1(Op);
is_float_op(_, _) -> false.
@@ -691,486 +1389,380 @@ is_float_op_1('-') -> true;
is_float_op_1('*') -> true;
is_float_op_1(_) -> false.
-anno_float_arg(float) -> float;
+anno_float_arg(#t_float{}) -> float;
anno_float_arg(_) -> convert.
-opt_terminator(#b_br{bool=#b_literal{}}=Br, _Ts, _Ds) ->
- beam_ssa:normalize(Br);
-opt_terminator(#b_br{bool=#b_var{}}=Br, Ts, Ds) ->
- simplify_not(Br, Ts, Ds);
-opt_terminator(#b_switch{arg=#b_literal{}}=Sw, _Ts, _Ds) ->
- beam_ssa:normalize(Sw);
-opt_terminator(#b_switch{arg=#b_var{}=V}=Sw, Ts, Ds) ->
- case get_type(V, Ts) of
- any ->
- beam_ssa:normalize(Sw);
- Type ->
- beam_ssa:normalize(opt_switch(Sw, Type, Ts, Ds))
- end;
-opt_terminator(#b_ret{}=Ret, _Ts, _Ds) -> Ret.
-
-
-opt_switch(#b_switch{fail=Fail,list=List0}=Sw0, Type, Ts, Ds) ->
- List = prune_switch_list(List0, Fail, Type, Ts),
- Sw1 = Sw0#b_switch{list=List},
- case Type of
- #t_integer{elements={_,_}=Range} ->
- simplify_switch_int(Sw1, Range);
- #t_atom{elements=[_|_]} ->
- case t_is_boolean(Type) of
- true ->
- #b_br{} = Br = simplify_switch_bool(Sw1, Ts, Ds),
- opt_terminator(Br, Ts, Ds);
- false ->
- simplify_switch_atom(Type, Sw1)
- end;
- _ ->
- Sw1
- end.
+%%%
+%%% Type helpers
+%%%
-prune_switch_list([{_,Fail}|T], Fail, Type, Ts) ->
- prune_switch_list(T, Fail, Type, Ts);
-prune_switch_list([{Arg,_}=Pair|T], Fail, Type, Ts) ->
- case meet(get_type(Arg, Ts), Type) of
- none ->
- %% Different types. This value can never match.
- prune_switch_list(T, Fail, Type, Ts);
- _ ->
- [Pair|prune_switch_list(T, Fail, Type, Ts)]
- end;
-prune_switch_list([], _, _, _) -> [].
+%% Returns the narrowest possible return type for the given success types and
+%% arguments.
+return_type(SuccTypes0, CallArgs0) ->
+ SuccTypes = st_filter_reachable(SuccTypes0, CallArgs0, [], []),
+ st_join_return_types(SuccTypes, none).
-update_successors(#b_br{bool=#b_literal{val=true},succ=S}, Ts, D) ->
- update_successor(S, Ts, D);
-update_successors(#b_br{bool=#b_var{}=Bool,succ=Succ,fail=Fail}, Ts0, D0) ->
- case cerl_sets:is_element(Bool, D0#d.once) of
+st_filter_reachable([{SuccArgs, {call_self, SelfArgs}}=SuccType | Rest],
+ CallArgs0, Deferred, Acc) ->
+ case st_is_reachable(SuccArgs, CallArgs0) of
true ->
- %% This variable is defined in this block and is only
- %% referenced by this br terminator. Therefore, there is
- %% no need to include it in the type database passed on to
- %% the successors of this block.
- Ts = maps:remove(Bool, Ts0),
- {SuccTs,FailTs} = infer_types_br(Bool, Ts, D0),
- D = update_successor(Fail, FailTs, D0),
- update_successor(Succ, SuccTs, D);
+ %% If we return a call to ourselves, we need to join our current
+ %% argument types with that of the call to ensure all possible
+ %% return paths are covered.
+ CallArgs = parallel_join(SelfArgs, CallArgs0),
+ st_filter_reachable(Rest, CallArgs, Deferred, Acc);
false ->
- {SuccTs,FailTs} = infer_types_br(Bool, Ts0, D0),
- D = update_successor_bool(Bool, false, Fail, FailTs, D0),
- update_successor_bool(Bool, true, Succ, SuccTs, D)
+ %% This may be reachable after we've joined another self-call, so
+ %% we defer it until we've gone through all other self-calls.
+ st_filter_reachable(Rest, CallArgs0, [SuccType | Deferred], Acc)
end;
-update_successors(#b_switch{arg=#b_var{}=V,fail=Fail,list=List}, Ts, D0) ->
- case cerl_sets:is_element(V, D0#d.once) of
+st_filter_reachable([SuccType | Rest], CallArgs, Deferred, Acc) ->
+ st_filter_reachable(Rest, CallArgs, Deferred, [SuccType | Acc]);
+st_filter_reachable([], CallArgs, Deferred, Acc) ->
+ case st_any_reachable(Deferred, CallArgs) of
true ->
- %% This variable is defined in this block and is only
- %% referenced by this switch terminator. Therefore, there is
- %% no need to include it in the type database passed on to
- %% the successors of this block.
- D = update_successor(Fail, Ts, D0),
- F = fun({Val,S}, A) ->
- SuccTs0 = infer_types_switch(V, Val, Ts, D),
- SuccTs = maps:remove(V, SuccTs0),
- update_successor(S, SuccTs, A)
- end,
- foldl(F, D, List);
+ %% Handle all deferred self calls that may be reachable now that
+ %% we've expanded our argument types.
+ st_filter_reachable(Deferred, CallArgs, [], Acc);
false ->
- %% V can not be equal to any of the values in List at the fail
- %% block.
- FailTs = subtract_sw_list(V, List, Ts),
- D = update_successor(Fail, FailTs, D0),
- F = fun({Val,S}, A) ->
- SuccTs = infer_types_switch(V, Val, Ts, D),
- update_successor(S, SuccTs, A)
- end,
- foldl(F, D, List)
- end;
-update_successors(#b_ret{arg=Arg}, Ts, D) ->
- FuncId = D#d.func_id,
- case D#d.ds of
- #{ Arg := #b_set{op=call,args=[FuncId | _]} } ->
- %% Returning a call to ourselves doesn't affect our own return
- %% type.
- D;
- #{} ->
- RetType = join([get_type(Arg, Ts) | D#d.ret_type]),
- D#d{ret_type=[RetType]}
+ %% We have no reachable self calls, so we know our argument types
+ %% can't expand any further. Filter out our reachable sites and
+ %% return.
+ [ST || {SuccArgs, _}=ST <- Acc, st_is_reachable(SuccArgs, CallArgs)]
end.
-subtract_sw_list(V, List, Ts) ->
- Ts#{ V := sub_sw_list_1(get_type(V, Ts), List, Ts) }.
+st_join_return_types([{_SuccArgs, SuccRet} | Rest], Acc0) ->
+ st_join_return_types(Rest, beam_types:join(SuccRet, Acc0));
+st_join_return_types([], Acc) ->
+ Acc.
-sub_sw_list_1(Type, [{Val,_}|T], Ts) ->
- ValType = get_type(Val, Ts),
- sub_sw_list_1(subtract(Type, ValType), T, Ts);
-sub_sw_list_1(Type, [], _Ts) ->
- Type.
+st_any_reachable([{SuccArgs, _} | SuccType], CallArgs) ->
+ case st_is_reachable(SuccArgs, CallArgs) of
+ true -> true;
+ false -> st_any_reachable(SuccType, CallArgs)
+ end;
+st_any_reachable([], _CallArgs) ->
+ false.
-update_successor_bool(#b_var{}=Var, BoolValue, S, Ts, D) ->
- case t_is_boolean(get_type(Var, Ts)) of
- true ->
- update_successor(S, Ts#{Var:=t_atom(BoolValue)}, D);
- false ->
- %% The `br` terminator is preceeded by an instruction that
- %% does not produce a boolean value, such a `new_try_tag`.
- update_successor(S, Ts, D)
- end.
+st_is_reachable([A | SuccArgs], [B | CallArgs]) ->
+ case beam_types:meet(A, B) of
+ none -> false;
+ _Other -> st_is_reachable(SuccArgs, CallArgs)
+ end;
+st_is_reachable([], []) ->
+ true.
+
+update_success_types(#b_ret{arg=Arg}, Ts, Ds, Meta, SuccTypes) ->
+ #metadata{ func_id=FuncId,
+ limit_return=Limited,
+ params=Params } = Meta,
+
+ RetType = case Ds of
+ #{ Arg := #b_set{op=call,args=[FuncId | Args]} } ->
+ {call_self, argument_types(Args, Ts)};
+ #{} ->
+ argument_type(Arg, Ts)
+ end,
+ ArgTypes = argument_types(Params, Ts),
+
+ case Limited of
+ true -> ust_limited(SuccTypes, ArgTypes, RetType);
+ false -> ust_unlimited(SuccTypes, ArgTypes, RetType)
+ end;
+update_success_types(_Last, _Ts, _Ds, _Meta, SuccTypes) ->
+ SuccTypes.
+
+%% See ?RETURN_LIMIT for details.
+ust_limited(SuccTypes, CallArgs, {call_self, SelfArgs}) ->
+ NewArgs = parallel_join(CallArgs, SelfArgs),
+ ust_limited_1(SuccTypes, NewArgs, none);
+ust_limited(SuccTypes, CallArgs, CallRet) ->
+ ust_limited_1(SuccTypes, CallArgs, CallRet).
+
+ust_limited_1([], ArgTypes, RetType) ->
+ [{ArgTypes, RetType}];
+ust_limited_1([{SuccArgs, SuccRet}], CallArgs, CallRet) ->
+ NewTypes = parallel_join(SuccArgs, CallArgs),
+ NewType = beam_types:join(SuccRet, CallRet),
+ [{NewTypes, NewType}].
+
+%% Adds a new success type. Note that we no longer try to keep the list short
+%% by combining entries with the same return type, as that can make effective
+%% return types less specific as analysis goes on, which may cause endless
+%% loops or render previous optimizations unsafe.
+%%
+%% See beam_type_SUITE:success_type_oscillation/1 for more details.
+ust_unlimited(SuccTypes, _CallArgs, none) ->
+ %% 'none' is implied since functions can always fail.
+ SuccTypes;
+ust_unlimited([{SameArgs, SameType} | _]=SuccTypes, SameArgs, SameType) ->
+ %% Already covered, return as-is.
+ SuccTypes;
+ust_unlimited([SuccType | SuccTypes], CallArgs, CallRet) ->
+ [SuccType | ust_unlimited(SuccTypes, CallArgs, CallRet)];
+ust_unlimited([], CallArgs, CallRet) ->
+ [{CallArgs, CallRet}].
+
+update_successors(#b_br{bool=#b_literal{val=true},succ=Succ}=Last,
+ Ts, _Ds, Ls, _UsedOnce) ->
+ {Last, update_successor(Succ, Ts, Ls)};
+update_successors(#b_br{bool=#b_var{}=Bool,succ=Succ,fail=Fail}=Last0,
+ Ts, Ds, Ls0, UsedOnce) ->
+ IsTempVar = cerl_sets:is_element(Bool, UsedOnce),
+ case infer_types_br(Bool, Ts, IsTempVar, Ds) of
+ {#{}=SuccTs, #{}=FailTs} ->
+ Ls1 = update_successor(Succ, SuccTs, Ls0),
+ Ls = update_successor(Fail, FailTs, Ls1),
+ {Last0, Ls};
+ {#{}=SuccTs, none} ->
+ Last = Last0#b_br{bool=#b_literal{val=true},fail=Succ},
+ {Last, update_successor(Succ, SuccTs, Ls0)};
+ {none, #{}=FailTs} ->
+ Last = Last0#b_br{bool=#b_literal{val=true},succ=Fail},
+ {Last, update_successor(Fail, FailTs, Ls0)}
+ end;
+update_successors(#b_switch{arg=#b_var{}=V,fail=Fail0,list=List0}=Last0,
+ Ts, Ds, Ls0, UsedOnce) ->
+ IsTempVar = cerl_sets:is_element(V, UsedOnce),
+
+ {List1, FailTs, Ls1} =
+ update_switch(List0, V, raw_type(V, Ts), Ts, Ds, Ls0, IsTempVar, []),
-update_successor(?BADARG_BLOCK, _Ts, #d{}=D) ->
- %% We KNOW that no variables are used in the ?BADARG_BLOCK,
+ case FailTs of
+ none ->
+ %% The fail block is unreachable; swap it with one of the choices.
+ case List1 of
+ [{#b_literal{val=0},_}|_] ->
+ %% Swap with the last choice in order to keep the zero the
+ %% first choice. If the loader can substitute a jump table
+ %% instruction, then a shorter version of the jump table
+ %% instruction can be used if the first value is zero.
+ {List, [{_,Fail}]} = split(length(List1)-1, List1),
+ Last = Last0#b_switch{fail=Fail,list=List},
+ {Last, Ls1};
+ [{_,Fail}|List] ->
+ %% Swap with the first choice in the list.
+ Last = Last0#b_switch{fail=Fail,list=List},
+ {Last, Ls1}
+ end;
+ #{} ->
+ Ls = update_successor(Fail0, FailTs, Ls1),
+ Last = Last0#b_switch{list=List1},
+ {Last, Ls}
+ end;
+update_successors(#b_ret{}=Last, _Ts, _Ds, Ls, _UsedOnce) ->
+ {Last, Ls}.
+
+update_switch([{Val, Lbl}=Sw | List],
+ V, FailType0, Ts, Ds, Ls0, IsTempVar, Acc) ->
+ FailType = beam_types:subtract(FailType0, raw_type(Val, Ts)),
+ case infer_types_switch(V, Val, Ts, IsTempVar, Ds) of
+ none ->
+ update_switch(List, V, FailType, Ts, Ds, Ls0, IsTempVar, Acc);
+ SwTs ->
+ Ls = update_successor(Lbl, SwTs, Ls0),
+ update_switch(List, V, FailType, Ts, Ds, Ls, IsTempVar, [Sw | Acc])
+ end;
+update_switch([], _V, none, _Ts, _Ds, Ls, _IsTempVar, Acc) ->
+ %% Fail label is unreachable.
+ {reverse(Acc), none, Ls};
+update_switch([], V, FailType, Ts, Ds, Ls, IsTempVar, Acc) ->
+ %% Fail label is reachable, see if we can narrow the type down further.
+ FailTs = case beam_types:get_singleton_value(FailType) of
+ {ok, Value} ->
+ %% This is the only possible value at the fail label, so
+ %% we can infer types as if we matched it directly.
+ Lit = #b_literal{val=Value},
+ infer_types_switch(V, Lit, Ts, IsTempVar, Ds);
+ error when IsTempVar ->
+ ts_remove_var(V, Ts);
+ error ->
+ Ts#{ V := FailType }
+ end,
+ {reverse(Acc), FailTs, Ls}.
+
+update_successor(?EXCEPTION_BLOCK, _Ts, Ls) ->
+ %% We KNOW that no variables are used in the ?EXCEPTION_BLOCK,
%% so there is no need to update the type information. That
%% can be a huge timesaver for huge functions.
- D;
-update_successor(S, Ts0, #d{ls=Ls}=D) ->
+ Ls;
+update_successor(S, Ts0, Ls) ->
case Ls of
- #{S:=Ts1} ->
- Ts = join_types(Ts0, Ts1),
- D#d{ls=Ls#{S:=Ts}};
+ #{ S := {outgoing, _} } ->
+ %% We're in a receive loop or similar; the target block will not be
+ %% revisited.
+ Ls;
+ #{ S := {incoming, InTs} } ->
+ Ts = join_types(Ts0, InTs),
+ Ls#{ S := {incoming, Ts} };
#{} ->
- D#d{ls=Ls#{S=>Ts0}}
+ Ls#{ S => {incoming, Ts0} }
end.
-update_types(#b_set{op=Op,dst=Dst,args=Args}, Ts, Ds) ->
- T = type(Op, Args, Ts, Ds),
+update_types(#b_set{op=Op,dst=Dst,anno=Anno,args=Args}, Ts, Ds) ->
+ T = type(Op, Args, Anno, Ts, Ds),
Ts#{Dst=>T}.
-type(phi, Args, Ts, _Ds) ->
- Types = [get_type(A, Ts) || {A,_} <- Args],
- join(Types);
-type({bif,'band'}, Args, Ts, _Ds) ->
- band_type(Args, Ts);
-type({bif,Bif}, Args, Ts, _Ds) ->
- case bif_type(Bif, Args) of
- number ->
- arith_op_type(Args, Ts);
- Type ->
- Type
+type({bif,Bif}, Args, _Anno, Ts, _Ds) ->
+ ArgTypes = normalized_types(Args, Ts),
+ {RetType, _, _} = beam_call_types:types(erlang, Bif, ArgTypes),
+ RetType;
+type(bs_init, _Args, _Anno, _Ts, _Ds) ->
+ #t_bitstring{};
+type(bs_extract, [Ctx], _Anno, _Ts, Ds) ->
+ #b_set{op=bs_match,args=Args} = map_get(Ctx, Ds),
+ bs_match_type(Args);
+type(bs_start_match, [_, Src], _Anno, Ts, _Ds) ->
+ case beam_types:meet(#t_bs_matchable{}, raw_type(Src, Ts)) of
+ none ->
+ none;
+ T ->
+ Unit = beam_types:get_bs_matchable_unit(T),
+ #t_bs_context{tail_unit=Unit}
end;
-type(bs_init, _Args, _Ts, _Ds) ->
- {binary, 1};
-type(bs_extract, [Ctx], Ts, _Ds) ->
- #t_bs_match{type=Type} = get_type(Ctx, Ts),
- Type;
-type(bs_match, Args, _Ts, _Ds) ->
- #t_bs_match{type=bs_match_type(Args)};
-type(bs_get_tail, _Args, _Ts, _Ds) ->
- {binary, 1};
+type(bs_match, [#b_literal{val=binary}, Ctx, _Flags,
+ #b_literal{val=all}, #b_literal{val=OpUnit}],
+ _Anno, Ts, _Ds) ->
+
+ %% This is an explicit tail unit test which does not advance the match
+ %% position.
+ CtxType = raw_type(Ctx, Ts),
+ OpType = #t_bs_context{tail_unit=OpUnit},
+
+ beam_types:meet(CtxType, OpType);
+type(bs_match, Args, _Anno, Ts, _Ds) ->
+ [_, Ctx | _] = Args,
+
+ %% Matches advance the current position without testing the tail unit. We
+ %% try to retain unit information by taking the GCD of our current unit and
+ %% the increments we know the match will advance by.
+ #t_bs_context{tail_unit=CtxUnit} = raw_type(Ctx, Ts),
+ OpUnit = bs_match_stride(Args, Ts),
+
+ #t_bs_context{tail_unit=gcd(OpUnit, CtxUnit)};
+type(bs_get_tail, [Ctx], _Anno, Ts, _Ds) ->
+ #t_bs_context{tail_unit=Unit} = raw_type(Ctx, Ts),
+ #t_bitstring{size_unit=Unit};
type(call, [#b_remote{mod=#b_literal{val=Mod},
- name=#b_literal{val=Name}}|Args], Ts, _Ds) ->
- case {Mod,Name,Args} of
- {erlang,setelement,[Pos,Tuple,Arg]} ->
- case {get_type(Pos, Ts),get_type(Tuple, Ts)} of
- {#t_integer{elements={Index,Index}},
- #t_tuple{elements=Es0,size=Size}=T} ->
- %% This is an exact index, update the type of said element
- %% or return 'none' if it's known to be out of bounds.
- Es = set_element_type(Index, get_type(Arg, Ts), Es0),
- case T#t_tuple.exact of
- false ->
- T#t_tuple{size=max(Index, Size),elements=Es};
- true when Index =< Size ->
- T#t_tuple{elements=Es};
- true ->
- none
- end;
- {#t_integer{elements={Min,_}}=IntType,
- #t_tuple{elements=Es0,size=Size}=T} ->
- %% Remove type information for all indices that
- %% falls into the range of the integer.
- Es = remove_element_info(IntType, Es0),
- case T#t_tuple.exact of
- false ->
- T#t_tuple{elements=Es,size=max(Min, Size)};
- true when Min =< Size ->
- T#t_tuple{elements=Es,size=Size};
- true ->
- none
- end;
- {_,#t_tuple{}=T} ->
- %% Position unknown, so we have to discard all element
- %% information.
- T#t_tuple{elements=#{}};
- {#t_integer{elements={Min,_Max}},_} ->
- #t_tuple{size=Min};
- {_,_} ->
- #t_tuple{}
- end;
- {erlang,'++',[LHS,RHS]} ->
- LType = get_type(LHS, Ts),
- RType = get_type(RHS, Ts),
- case LType =:= cons orelse RType =:= cons of
- true ->
- cons;
- false ->
- %% `[] ++ RHS` yields RHS, even if RHS is not a list.
- join(list, RType)
- end;
- {erlang,'--',[_,_]} ->
- list;
- {lists,F,Args} ->
- Types = get_types(Args, Ts),
- lists_function_type(F, Types);
- {math,_,_} ->
- case is_math_bif(Name, length(Args)) of
- false -> any;
- true -> float
- end;
- {_,_,_} ->
- case erl_bifs:is_exit_bif(Mod, Name, length(Args)) of
- true -> none;
- false -> any
- end
+ name=#b_literal{val=Name}}|Args], _Anno, Ts, _Ds) ->
+ ArgTypes = normalized_types(Args, Ts),
+ {RetType, _, _} = beam_call_types:types(Mod, Name, ArgTypes),
+ RetType;
+type(call, [#b_remote{} | _Args], _Anno, _Ts, _Ds) ->
+ %% Remote call with variable Module and/or Function.
+ any;
+type(call, [#b_local{} | _Args], Anno, _Ts, _Ds) ->
+ case Anno of
+ #{ result_type := Type } -> Type;
+ #{} -> any
+ end;
+type(call, [#b_var{} | _Args], Anno, _Ts, _Ds) ->
+ case Anno of
+ #{ result_type := Type } -> Type;
+ #{} -> any
end;
-type(get_tuple_element, [Tuple, Offset], Ts, _Ds) ->
- #t_tuple{size=Size,elements=Es} = get_type(Tuple, Ts),
+type(call, [#b_literal{val=Fun} | Args], _Anno, _Ts, _Ds) ->
+ case is_function(Fun, length(Args)) of
+ true ->
+ %% This is an external fun literal (fun M:F/A).
+ any;
+ false ->
+ %% This is either not a fun literal or the number of
+ %% arguments is wrong.
+ none
+ end;
+type(get_hd, [Src], _Anno, Ts, _Ds) ->
+ SrcType = #t_cons{} = normalized_type(Src, Ts), %Assertion.
+ {RetType, _, _} = beam_call_types:types(erlang, hd, [SrcType]),
+ RetType;
+type(get_tl, [Src], _Anno, Ts, _Ds) ->
+ SrcType = #t_cons{} = normalized_type(Src, Ts), %Assertion.
+ {RetType, _, _} = beam_call_types:types(erlang, tl, [SrcType]),
+ RetType;
+type(get_map_element, [_, _]=Args0, _Anno, Ts, _Ds) ->
+ [#t_map{}=Map, Key] = normalized_types(Args0, Ts), %Assertion.
+ {RetType, _, _} = beam_call_types:types(erlang, map_get, [Key, Map]),
+ RetType;
+type(get_tuple_element, [Tuple, Offset], _Anno, Ts, _Ds) ->
+ #t_tuple{size=Size,elements=Es} = normalized_type(Tuple, Ts),
#b_literal{val=N} = Offset,
true = Size > N, %Assertion.
- get_element_type(N + 1, Es);
-type(is_nonempty_list, [_], _Ts, _Ds) ->
- t_boolean();
-type(is_tagged_tuple, [_,#b_literal{},#b_literal{}], _Ts, _Ds) ->
- t_boolean();
-type(put_map, _Args, _Ts, _Ds) ->
- map;
-type(put_list, _Args, _Ts, _Ds) ->
- cons;
-type(put_tuple, Args, Ts, _Ds) ->
+ beam_types:get_tuple_element(N + 1, Es);
+type(has_map_field, [_, _]=Args0, _Anno, Ts, _Ds) ->
+ [#t_map{}=Map, Key] = normalized_types(Args0, Ts), %Assertion.
+ {RetType, _, _} = beam_call_types:types(erlang, is_map_key, [Key, Map]),
+ RetType;
+type(is_nonempty_list, [_], _Anno, _Ts, _Ds) ->
+ beam_types:make_boolean();
+type(is_tagged_tuple, [_,#b_literal{},#b_literal{}], _Anno, _Ts, _Ds) ->
+ beam_types:make_boolean();
+type(make_fun, [#b_local{arity=TotalArity} | Env], Anno, _Ts, _Ds) ->
+ RetType = case Anno of
+ #{ result_type := Type } -> Type;
+ #{} -> any
+ end,
+ #t_fun{arity=TotalArity - length(Env), type=RetType};
+type(put_map, [_Kind, Map | Ss], _Anno, Ts, _Ds) ->
+ put_map_type(Map, Ss, Ts);
+type(put_list, [Head, Tail], _Anno, Ts, _Ds) ->
+ HeadType = raw_type(Head, Ts),
+ TailType = raw_type(Tail, Ts),
+ beam_types:make_cons(HeadType, TailType);
+type(put_tuple, Args, _Anno, Ts, _Ds) ->
{Es, _} = foldl(fun(Arg, {Es0, Index}) ->
- Type = get_type(Arg, Ts),
- Es = set_element_type(Index, Type, Es0),
- {Es, Index + 1}
+ Type = raw_type(Arg, Ts),
+ Es = beam_types:set_tuple_element(Index, Type, Es0),
+ {Es, Index + 1}
end, {#{}, 1}, Args),
#t_tuple{exact=true,size=length(Args),elements=Es};
-type(succeeded, [#b_var{}=Src], Ts, Ds) ->
- case maps:get(Src, Ds) of
- #b_set{op={bif,Bif},args=BifArgs} ->
- Types = get_types(BifArgs, Ts),
- case {Bif,Types} of
- {BoolOp,[T1,T2]} when BoolOp =:= 'and'; BoolOp =:= 'or' ->
- case t_is_boolean(T1) andalso t_is_boolean(T2) of
- true -> t_atom(true);
- false -> t_boolean()
- end;
- {byte_size,[{binary,_}]} ->
- t_atom(true);
- {bit_size,[{binary,_}]} ->
- t_atom(true);
- {map_size,[map]} ->
- t_atom(true);
- {'not',[Type]} ->
- case t_is_boolean(Type) of
- true -> t_atom(true);
- false -> t_boolean()
- end;
- {size,[{binary,_}]} ->
- t_atom(true);
- {tuple_size,[#t_tuple{}]} ->
- t_atom(true);
- {_,_} ->
- t_boolean()
- end;
- #b_set{op=get_hd} ->
- t_atom(true);
- #b_set{op=get_tl} ->
- t_atom(true);
- #b_set{op=get_tuple_element} ->
- t_atom(true);
- #b_set{op=wait} ->
- t_atom(false);
- #b_set{} ->
- t_boolean()
- end;
-type(succeeded, [#b_literal{}], _Ts, _Ds) ->
- t_atom(true);
-type(_, _, _, _) -> any.
-
-arith_op_type(Args, Ts) ->
- Types = get_types(Args, Ts),
- foldl(fun(#t_integer{}, unknown) -> t_integer();
- (#t_integer{}, number) -> number;
- (#t_integer{}, float) -> float;
- (#t_integer{}, #t_integer{}) -> t_integer();
- (float, unknown) -> float;
- (float, #t_integer{}) -> float;
- (float, number) -> float;
- (number, unknown) -> number;
- (number, #t_integer{}) -> number;
- (number, float) -> float;
- (any, _) -> number;
- (Same, Same) -> Same;
- (_, _) -> none
- end, unknown, Types).
-
-lists_function_type(F, Types) ->
- case {F,Types} of
- %% Functions that return booleans.
- {all,[_,_]} ->
- t_boolean();
- {any,[_,_]} ->
- t_boolean();
- {keymember,[_,_,_]} ->
- t_boolean();
- {member,[_,_]} ->
- t_boolean();
- {prefix,[_,_]} ->
- t_boolean();
- {suffix,[_,_]} ->
- t_boolean();
-
- %% Functions that return lists.
- {dropwhile,[_,_]} ->
- list;
- {duplicate,[_,_]} ->
- list;
- {filter,[_,_]} ->
- list;
- {flatten,[_]} ->
- list;
- {map,[_Fun,List]} ->
- same_length_type(List);
- {MapFold,[_Fun,_Acc,List]} when MapFold =:= mapfoldl;
- MapFold =:= mapfoldr ->
- #t_tuple{size=2,exact=true,
- elements=#{1=>same_length_type(List)}};
- {partition,[_,_]} ->
- t_two_tuple(list, list);
- {reverse,[List]} ->
- same_length_type(List);
- {sort,[List]} ->
- same_length_type(List);
- {splitwith,[_,_]} ->
- t_two_tuple(list, list);
- {takewhile,[_,_]} ->
- list;
- {unzip,[List]} ->
- ListType = same_length_type(List),
- t_two_tuple(ListType, ListType);
- {usort,[List]} ->
- same_length_type(List);
- {zip,[_,_]} ->
- list;
- {zipwith,[_,_,_]} ->
- list;
- {_,_} ->
- any
- end.
-
-%% For a lists function that return a list of the same
-%% length as the input list, return the type of the list.
-same_length_type(cons) -> cons;
-same_length_type(nil) -> nil;
-same_length_type(_) -> list.
-
-t_two_tuple(Type1, Type2) ->
- #t_tuple{size=2,exact=true,
- elements=#{1=>Type1,2=>Type2}}.
-
-%% will_succeed(TestOperation, Type) -> yes|no|maybe.
-%% Test whether TestOperation applied to an argument of type Type
-%% will succeed. Return yes, no, or maybe.
-%%
-%% Type is a type as described in the comment for verified_type/1 at
-%% the very end of this file, but it will *never* be 'any'.
-
-will_succeed(is_atom, Type) ->
- case Type of
- #t_atom{} -> yes;
- _ -> no
- end;
-will_succeed(is_binary, Type) ->
- case Type of
- {binary,U} when U rem 8 =:= 0 -> yes;
- {binary,_} -> maybe;
- _ -> no
- end;
-will_succeed(is_bitstring, Type) ->
- case Type of
- {binary,_} -> yes;
- _ -> no
- end;
-will_succeed(is_boolean, Type) ->
- case Type of
- #t_atom{elements=any} ->
- maybe;
- #t_atom{elements=Es} ->
- case t_is_boolean(Type) of
- true ->
- yes;
- false ->
- case any(fun is_boolean/1, Es) of
- true -> maybe;
- false -> no
- end
- end;
- _ ->
- no
- end;
-will_succeed(is_float, Type) ->
- case Type of
- float -> yes;
- number -> maybe;
- _ -> no
- end;
-will_succeed(is_integer, Type) ->
- case Type of
- #t_integer{} -> yes;
- number -> maybe;
- _ -> no
- end;
-will_succeed(is_list, Type) ->
- case Type of
- list -> yes;
- cons -> yes;
- _ -> no
- end;
-will_succeed(is_map, Type) ->
- case Type of
- map -> yes;
- _ -> no
- end;
-will_succeed(is_number, Type) ->
- case Type of
- float -> yes;
- #t_integer{} -> yes;
- number -> yes;
- _ -> no
- end;
-will_succeed(is_tuple, Type) ->
- case Type of
- #t_tuple{} -> yes;
- _ -> no
- end;
-will_succeed(_, _) -> maybe.
+type(resume, [_, _], _Anno, _Ts, _Ds) ->
+ none;
+type(_, _, _, _, _) -> any.
+put_map_type(Map, Ss, Ts) ->
+ pmt_1(Ss, Ts, normalized_type(Map, Ts)).
-band_type([Other,#b_literal{val=Int}], Ts) when is_integer(Int) ->
- band_type_1(Int, Other, Ts);
-band_type([_,_], _) -> t_integer().
+pmt_1([Key0, Value0 | Ss], Ts, Acc0) ->
+ Key = normalized_type(Key0, Ts),
+ Value = normalized_type(Value0, Ts),
+ {Acc, _, _} = beam_call_types:types(maps, put, [Key, Value, Acc0]),
+ pmt_1(Ss, Ts, Acc);
+pmt_1([], _Ts, Acc) ->
+ Acc.
-band_type_1(Int, OtherSrc, Ts) ->
- Type = band_type_2(Int, 0),
- OtherType = get_type(OtherSrc, Ts),
- meet(Type, OtherType).
+%% We seldom know how far a match operation may advance, but we can often tell
+%% which increment it will advance by.
+bs_match_stride([#b_literal{val=Type} | Args], Ts) ->
+ bs_match_stride(Type, Args, Ts).
-band_type_2(N, Bits) when Bits < 64 ->
- case 1 bsl Bits of
- P when P =:= N + 1 ->
- t_integer(0, N);
- P when P > N + 1 ->
- t_integer();
+bs_match_stride(_, [_,_,Size,#b_literal{val=Unit}], Ts) ->
+ case raw_type(Size, Ts) of
+ #t_integer{elements={Sz, Sz}} when is_integer(Sz) ->
+ Sz * Unit;
_ ->
- band_type_2(N, Bits+1)
+ Unit
end;
-band_type_2(_, _) ->
- %% Negative or large positive number. Give up.
- t_integer().
+bs_match_stride(string, [_,#b_literal{val=String}], _) ->
+ bit_size(String);
+bs_match_stride(utf8, _, _) ->
+ 8;
+bs_match_stride(utf16, _, _) ->
+ 16;
+bs_match_stride(utf32, _, _) ->
+ 32;
+bs_match_stride(_, _, _) ->
+ 1.
+
+-define(UNICODE_MAX, (16#10FFFF)).
bs_match_type([#b_literal{val=Type}|Args]) ->
bs_match_type(Type, Args).
bs_match_type(binary, Args) ->
[_,_,_,#b_literal{val=U}] = Args,
- {binary,U};
+ #t_bitstring{size_unit=U};
bs_match_type(float, _) ->
- float;
+ #t_float{};
bs_match_type(integer, Args) ->
case Args of
[_,
@@ -1180,194 +1772,48 @@ bs_match_type(integer, Args) ->
NumBits = Size * Unit,
case member(unsigned, Flags) of
true ->
- t_integer(0, (1 bsl NumBits)-1);
+ beam_types:make_integer(0, (1 bsl NumBits)-1);
false ->
%% Signed integer. Don't bother.
- t_integer()
+ #t_integer{}
end;
[_|_] ->
- t_integer()
+ #t_integer{}
end;
bs_match_type(skip, _) ->
any;
bs_match_type(string, _) ->
any;
bs_match_type(utf8, _) ->
- ?UNICODE_INT;
+ beam_types:make_integer(0, ?UNICODE_MAX);
bs_match_type(utf16, _) ->
- ?UNICODE_INT;
+ beam_types:make_integer(0, ?UNICODE_MAX);
bs_match_type(utf32, _) ->
- ?UNICODE_INT.
-
-simplify_switch_atom(#t_atom{elements=Atoms}, #b_switch{list=List0}=Sw) ->
- case sort([A || {#b_literal{val=A},_} <- List0]) of
- Atoms ->
- %% All possible atoms are included in the list. The
- %% failure label will never be used.
- [{_,Fail}|List] = List0,
- Sw#b_switch{fail=Fail,list=List};
- _ ->
- Sw
- end.
-
-simplify_switch_int(#b_switch{list=List0}=Sw, {Min,Max}) ->
- List1 = sort(List0),
- Vs = [V || {#b_literal{val=V},_} <- List1],
- case eq_ranges(Vs, Min, Max) of
- true ->
- {_,LastL} = last(List1),
- List = droplast(List1),
- Sw#b_switch{fail=LastL,list=List};
- false ->
- Sw
- end.
-
-eq_ranges([H], H, H) -> true;
-eq_ranges([H|T], H, Max) -> eq_ranges(T, H+1, Max);
-eq_ranges(_, _, _) -> false.
-
-simplify_is_record(I, #t_tuple{exact=Exact,
- size=Size,
- elements=Es},
- RecSize, RecTag, Ts) ->
- TagType = maps:get(1, Es, any),
- TagMatch = case get_literal_from_type(TagType) of
- #b_literal{}=RecTag -> yes;
- #b_literal{} -> no;
- none ->
- %% Is it at all possible for the tag to match?
- case meet(get_type(RecTag, Ts), TagType) of
- none -> no;
- _ -> maybe
- end
- end,
- if
- Size =/= RecSize, Exact; Size > RecSize; TagMatch =:= no ->
- #b_literal{val=false};
- Size =:= RecSize, Exact, TagMatch =:= yes ->
- #b_literal{val=true};
- true ->
- I
- end;
-simplify_is_record(I, any, _Size, _Tag, _Ts) ->
- I;
-simplify_is_record(_I, _Type, _Size, _Tag, _Ts) ->
- #b_literal{val=false}.
-
-simplify_switch_bool(#b_switch{arg=B,fail=Fail,list=List0}, Ts, Ds) ->
- FalseVal = #b_literal{val=false},
- TrueVal = #b_literal{val=true},
- List1 = List0 ++ [{FalseVal,Fail},{TrueVal,Fail}],
- {_,FalseLbl} = keyfind(FalseVal, 1, List1),
- {_,TrueLbl} = keyfind(TrueVal, 1, List1),
- Br = beam_ssa:normalize(#b_br{bool=B,succ=TrueLbl,fail=FalseLbl}),
- simplify_not(Br, Ts, Ds).
-
-simplify_not(#b_br{bool=#b_var{}=V,succ=Succ,fail=Fail}=Br0, Ts, Ds) ->
- case Ds of
- #{V:=#b_set{op={bif,'not'},args=[Bool]}} ->
- case t_is_boolean(get_type(Bool, Ts)) of
- true ->
- Br = Br0#b_br{bool=Bool,succ=Fail,fail=Succ},
- beam_ssa:normalize(Br);
- false ->
- Br0
- end;
- #{} ->
- Br0
- end;
-simplify_not(#b_br{bool=#b_literal{}}=Br, _Ts, _Ds) -> Br.
+ beam_types:make_integer(0, ?UNICODE_MAX).
-%%%
-%%% Calculate the set of variables that are only used once in the
-%%% terminator of the block that defines them. That will allow us to
-%%% discard type information for variables that will never be
-%%% referenced by the successor blocks, potentially improving
-%%% compilation times.
-%%%
+normalized_types(Values, Ts) ->
+ [normalized_type(Val, Ts) || Val <- Values].
-used_once(Linear, Args) ->
- Map0 = used_once_1(reverse(Linear), #{}),
- Map = maps:without(Args, Map0),
- cerl_sets:from_list(maps:keys(Map)).
+normalized_type(V, Ts) ->
+ beam_types:normalize(raw_type(V, Ts)).
-used_once_1([{L,#b_blk{is=Is,last=Last}}|Bs], Uses0) ->
- Uses1 = used_once_last_uses(beam_ssa:used(Last), L, Uses0),
- Uses = used_once_2(reverse(Is), L, Uses1),
- used_once_1(Bs, Uses);
-used_once_1([], Uses) -> Uses.
+argument_types(Values, Ts) ->
+ [argument_type(Val, Ts) || Val <- Values].
-used_once_2([#b_set{dst=Dst}=I|Is], L, Uses0) ->
- Uses = used_once_uses(beam_ssa:used(I), L, Uses0),
- case Uses of
- #{Dst:=[L]} ->
- used_once_2(Is, L, Uses);
- #{} ->
- %% Used more than once or used once in
- %% in another block.
- used_once_2(Is, L, maps:remove(Dst, Uses))
- end;
-used_once_2([], _, Uses) -> Uses.
+-spec argument_type(beam_ssa:value(), type_db()) -> type().
-used_once_uses([V|Vs], L, Uses) ->
- case Uses of
- #{V:=more_than_once} ->
- used_once_uses(Vs, L, Uses);
- #{} ->
- %% Already used or first use is not in
- %% a terminator.
- used_once_uses(Vs, L, Uses#{V=>more_than_once})
- end;
-used_once_uses([], _, Uses) -> Uses.
+argument_type(V, Ts) ->
+ beam_types:limit_depth(raw_type(V, Ts)).
-used_once_last_uses([V|Vs], L, Uses) ->
- case Uses of
- #{V:=[_]} ->
- %% Second time this variable is used.
- used_once_last_uses(Vs, L, Uses#{V:=more_than_once});
- #{V:=more_than_once} ->
- %% Used at least twice before.
- used_once_last_uses(Vs, L, Uses);
- #{} ->
- %% First time this variable is used.
- used_once_last_uses(Vs, L, Uses#{V=>[L]})
- end;
-used_once_last_uses([], _, Uses) -> Uses.
+raw_types(Values, Ts) ->
+ [raw_type(Val, Ts) || Val <- Values].
+-spec raw_type(beam_ssa:value(), type_db()) -> type().
-get_types(Values, Ts) ->
- [get_type(Val, Ts) || Val <- Values].
--spec get_type(beam_ssa:value(), type_db()) -> type().
-
-get_type(#b_var{}=V, Ts) ->
- #{V:=T} = Ts,
- T;
-get_type(#b_literal{val=Val}, _Ts) ->
- if
- is_atom(Val) ->
- t_atom(Val);
- is_float(Val) ->
- float;
- is_integer(Val) ->
- t_integer(Val);
- is_list(Val), Val =/= [] ->
- cons;
- is_map(Val) ->
- map;
- Val =:= {} ->
- #t_tuple{exact=true};
- is_tuple(Val) ->
- {Es, _} = foldl(fun(E, {Es0, Index}) ->
- Type = get_type(#b_literal{val=E}, #{}),
- Es = set_element_type(Index, Type, Es0),
- {Es, Index + 1}
- end, {#{}, 1}, tuple_to_list(Val)),
- #t_tuple{exact=true,size=tuple_size(Val),elements=Es};
- Val =:= [] ->
- nil;
- true ->
- any
- end.
+raw_type(#b_literal{val=Value}, _Ts) ->
+ beam_types:make_type_from_value(Value);
+raw_type(V, Ts) ->
+ map_get(V, Ts).
%% infer_types(Var, Types, #d{}) -> {SuccTypes,FailTypes}
%% Looking at the expression that defines the variable Var, infer
@@ -1390,10 +1836,107 @@ get_type(#b_literal{val=Val}, _Ts) ->
%% 'cons' would give 'nil' as the only possible type. The result of the
%% subtraction for L will be added to FailTypes.
-infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) ->
+infer_types_br(#b_var{}=V, Ts, IsTempVar, Ds) ->
#{V:=#b_set{op=Op,args=Args}} = Ds,
- PosTypes0 = infer_type(Op, Args, Ds),
- NegTypes0 = infer_type_negative(Op, Args, Ds),
+
+ {PosTypes, NegTypes} = infer_type(Op, Args, Ts, Ds),
+
+ SuccTs0 = meet_types(PosTypes, Ts),
+ FailTs0 = subtract_types(NegTypes, Ts),
+
+ case IsTempVar of
+ true ->
+ %% The branch variable is defined in this block and is only
+ %% referenced by this terminator. Therefore, there is no need to
+ %% include it in the type database passed on to the successors of
+ %% of this block.
+ SuccTs = ts_remove_var(V, SuccTs0),
+ FailTs = ts_remove_var(V, FailTs0),
+ {SuccTs, FailTs};
+ false ->
+ SuccTs = infer_br_value(V, true, SuccTs0),
+ FailTs = infer_br_value(V, false, FailTs0),
+ {SuccTs, FailTs}
+ end.
+
+infer_br_value(_V, _Bool, none) ->
+ none;
+infer_br_value(V, Bool, NewTs) ->
+ #{ V := T } = NewTs,
+ case beam_types:is_boolean_type(T) of
+ true ->
+ NewTs#{ V := beam_types:make_atom(Bool) };
+ false ->
+ %% V is a try/catch tag or similar, leave it alone.
+ NewTs
+ end.
+
+infer_types_switch(V, Lit, Ts0, IsTempVar, Ds) ->
+ {PosTypes, _} = infer_type({bif,'=:='}, [V, Lit], Ts0, Ds),
+ Ts = meet_types(PosTypes, Ts0),
+ case IsTempVar of
+ true -> ts_remove_var(V, Ts);
+ false -> Ts
+ end.
+
+ts_remove_var(_V, none) -> none;
+ts_remove_var(V, Ts) -> maps:remove(V, Ts).
+
+infer_type({succeeded,_}, [#b_var{}=Src], Ts, Ds) ->
+ #b_set{op=Op,args=Args} = maps:get(Src, Ds),
+ infer_success_type(Op, Args, Ts, Ds);
+
+%% Type tests are handled separately from other BIFs as we're inferring types
+%% based on their result, so we know that subtraction is safe even if we're
+%% not branching on 'succeeded'.
+infer_type(is_tagged_tuple, [#b_var{}=Src,#b_literal{val=Size},
+ #b_literal{}=Tag], _Ts, _Ds) ->
+ Es = beam_types:set_tuple_element(1, raw_type(Tag, #{}), #{}),
+ T = {Src,#t_tuple{exact=true,size=Size,elements=Es}},
+ {[T], [T]};
+infer_type(is_nonempty_list, [#b_var{}=Src], _Ts, _Ds) ->
+ T = {Src,#t_cons{}},
+ {[T], [T]};
+infer_type({bif,is_atom}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_atom{}},
+ {[T], [T]};
+infer_type({bif,is_binary}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_bitstring{size_unit=8}},
+ {[T], [T]};
+infer_type({bif,is_bitstring}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_bitstring{}},
+ {[T], [T]};
+infer_type({bif,is_boolean}, [Arg], _Ts, _Ds) ->
+ T = {Arg, beam_types:make_boolean()},
+ {[T], [T]};
+infer_type({bif,is_float}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_float{}},
+ {[T], [T]};
+infer_type({bif,is_integer}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_integer{}},
+ {[T], [T]};
+infer_type({bif,is_list}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_list{}},
+ {[T], [T]};
+infer_type({bif,is_map}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_map{}},
+ {[T], [T]};
+infer_type({bif,is_number}, [Arg], _Ts, _Ds) ->
+ T = {Arg, number},
+ {[T], [T]};
+infer_type({bif,is_tuple}, [Arg], _Ts, _Ds) ->
+ T = {Arg, #t_tuple{}},
+ {[T], [T]};
+infer_type({bif,'=:='}, [#b_var{}=LHS,#b_var{}=RHS], Ts, _Ds) ->
+ %% As an example, assume that L1 is known to be 'list', and L2 is
+ %% known to be 'cons'. Then if 'L1 =:= L2' evaluates to 'true', it can
+ %% be inferred that L1 is 'cons' (the meet of 'cons' and 'list').
+ LType = raw_type(LHS, Ts),
+ RType = raw_type(RHS, Ts),
+ Type = beam_types:meet(LType, RType),
+
+ PosTypes = [{V,Type} || {V, OrigType} <- [{LHS, LType}, {RHS, RType}],
+ OrigType =/= Type],
%% We must be careful with types inferred from '=:='.
%%
@@ -1404,564 +1947,268 @@ infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) ->
%%
%% However, it is safe to subtract a type inferred from '=:=' if
%% it is single-valued, e.g. if it is [] or the atom 'true'.
+ %%
+ %% Note that we subtract the left-hand type from the right-hand
+ %% value and vice versa. We must not subtract the meet of the two
+ %% as it may be too specific. See beam_type_SUITE:type_subtraction/1
+ %% for details.
+ NegTypes = [T || {_, OtherType}=T <- [{RHS, LType}, {LHS, RType}],
+ beam_types:is_singleton_type(OtherType)],
+
+ {PosTypes, NegTypes};
+infer_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ts, Ds) ->
+ Def = maps:get(Src, Ds),
+ LitType = raw_type(Lit, Ts),
+ PosTypes = [{Src, LitType} | infer_eq_lit(Def, LitType)],
- EqTypes = infer_eq_type(Op, Args, Ts, Ds),
- NegTypes1 = [P || {_,T}=P <- EqTypes, is_singleton_type(T)],
-
- PosTypes = EqTypes ++ PosTypes0,
- SuccTs = meet_types(PosTypes, Ts),
+ %% Subtraction is only safe if LitType is single-valued.
+ NegTypes = case beam_types:is_singleton_type(LitType) of
+ true -> PosTypes;
+ false -> []
+ end,
- NegTypes = NegTypes0 ++ NegTypes1,
- FailTs = subtract_types(NegTypes, Ts),
+ {PosTypes, NegTypes};
+infer_type(_Op, _Args, _Ts, _Ds) ->
+ {[], []}.
- {SuccTs,FailTs}.
+infer_success_type({bif,Op}, Args, Ts, _Ds) ->
+ ArgTypes = normalized_types(Args, Ts),
-infer_types_switch(V, Lit, Ts, #d{ds=Ds}) ->
- Types = infer_eq_type({bif,'=:='}, [V, Lit], Ts, Ds),
- meet_types(Types, Ts).
+ {_, PosTypes0, CanSubtract} = beam_call_types:types(erlang, Op, ArgTypes),
+ PosTypes = [T || {#b_var{},_}=T <- zip(Args, PosTypes0)],
-infer_eq_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ts, Ds) ->
- Def = maps:get(Src, Ds),
- Type = get_type(Lit, Ts),
- [{Src,Type} | infer_eq_lit(Def, Lit)];
-infer_eq_type({bif,'=:='}, [#b_var{}=Arg0,#b_var{}=Arg1], Ts, _Ds) ->
- %% As an example, assume that L1 is known to be 'list', and L2 is
- %% known to be 'cons'. Then if 'L1 =:= L2' evaluates to 'true', it can
- %% be inferred that L1 is 'cons' (the meet of 'cons' and 'list').
- Type0 = get_type(Arg0, Ts),
- Type1 = get_type(Arg1, Ts),
- Type = meet(Type0, Type1),
- [{V,MeetType} ||
- {V,OrigType,MeetType} <-
- [{Arg0,Type0,Type},{Arg1,Type1,Type}],
- OrigType =/= MeetType];
-infer_eq_type(_Op, _Args, _Ts, _Ds) ->
- [].
+ case CanSubtract of
+ true -> {PosTypes, PosTypes};
+ false -> {PosTypes, []}
+ end;
+infer_success_type(call, [#b_var{}=Fun|Args], _Ts, _Ds) ->
+ T = {Fun, #t_fun{arity=length(Args)}},
+ {[T], []};
+infer_success_type(bs_start_match, [_, #b_var{}=Src], _Ts, _Ds) ->
+ T = {Src,#t_bs_matchable{}},
+ {[T], [T]};
+infer_success_type(bs_match, [#b_literal{val=binary},
+ Ctx, _Flags,
+ #b_literal{val=all},
+ #b_literal{val=OpUnit}],
+ _Ts, _Ds) ->
+ %% This is an explicit tail unit test which does not advance the match
+ %% position, so we know that Ctx has the same unit.
+ T = {Ctx, #t_bs_context{tail_unit=OpUnit}},
+ {[T], [T]};
+infer_success_type(_Op, _Args, _Ts, _Ds) ->
+ {[], []}.
infer_eq_lit(#b_set{op={bif,tuple_size},args=[#b_var{}=Tuple]},
- #b_literal{val=Size}) when is_integer(Size) ->
+ #t_integer{elements={Size,Size}}) ->
[{Tuple,#t_tuple{exact=true,size=Size}}];
infer_eq_lit(#b_set{op=get_tuple_element,
args=[#b_var{}=Tuple,#b_literal{val=N}]},
- #b_literal{}=Lit) ->
+ LitType) ->
Index = N + 1,
- Es = set_element_type(Index, get_type(Lit, #{}), #{}),
- [{Tuple,#t_tuple{size=Index,elements=Es}}];
-infer_eq_lit(_, _) -> [].
-
-infer_type_negative(Op, Args, Ds) ->
- case is_negative_inference_safe(Op, Args) of
- true ->
- infer_type(Op, Args, Ds);
- false ->
- []
- end.
-
-%% Conservative list of instructions for which negative
-%% inference is safe.
-is_negative_inference_safe(is_nonempty_list, _Args) -> true;
-is_negative_inference_safe(_, _) -> false.
-
-infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) ->
- if
- is_integer(Pos), 1 =< Pos ->
- [{Tuple,#t_tuple{size=Pos}}];
- true ->
+ case beam_types:set_tuple_element(Index, LitType, #{}) of
+ #{ Index := _ }=Es ->
+ [{Tuple,#t_tuple{size=Index,elements=Es}}];
+ #{} ->
+ %% Index was above the element limit; subtraction is not safe.
[]
end;
-infer_type({bif,element}, [#b_var{}=Position,#b_var{}=Tuple], _Ds) ->
- [{Position,t_integer()},{Tuple,#t_tuple{}}];
-infer_type({bif,Bif}, [#b_var{}=Src]=Args, _Ds) ->
- case inferred_bif_type(Bif, Args) of
- any -> [];
- T -> [{Src,T}]
- end;
-infer_type({bif,binary_part}, [#b_var{}=Src,_], _Ds) ->
- [{Src,{binary,8}}];
-infer_type({bif,is_map_key}, [_,#b_var{}=Src], _Ds) ->
- [{Src,map}];
-infer_type({bif,map_get}, [_,#b_var{}=Src], _Ds) ->
- [{Src,map}];
-infer_type({bif,Bif}, [_,_]=Args, _Ds) ->
- case inferred_bif_type(Bif, Args) of
- any -> [];
- T -> [{A,T} || #b_var{}=A <- Args]
- end;
-infer_type({bif,binary_part}, [#b_var{}=Src,Pos,Len], _Ds) ->
- [{Src,{binary,8}}|
- [{V,t_integer()} || #b_var{}=V <- [Pos,Len]]];
-infer_type(bs_start_match, [#b_var{}=Bin], _Ds) ->
- [{Bin,{binary,1}}];
-infer_type(is_nonempty_list, [#b_var{}=Src], _Ds) ->
- [{Src,cons}];
-infer_type(is_tagged_tuple, [#b_var{}=Src,#b_literal{val=Size},
- #b_literal{}=Tag], _Ds) ->
- Es = set_element_type(1, get_type(Tag, #{}), #{}),
- [{Src,#t_tuple{exact=true,size=Size,elements=Es}}];
-infer_type(succeeded, [#b_var{}=Src], Ds) ->
- #b_set{op=Op,args=Args} = maps:get(Src, Ds),
- infer_type(Op, Args, Ds);
-infer_type(_Op, _Args, _Ds) ->
+infer_eq_lit(_, _) ->
[].
-%% bif_type(Name, Args) -> Type
-%% Return the return type for the guard BIF or operator Name with
-%% arguments Args.
-%%
-%% Note that that the following BIFs are handle elsewhere:
-%%
-%% band/2
-
-bif_type(abs, [_]) -> number;
-bif_type(bit_size, [_]) -> t_integer();
-bif_type(byte_size, [_]) -> t_integer();
-bif_type(ceil, [_]) -> t_integer();
-bif_type(float, [_]) -> float;
-bif_type(floor, [_]) -> t_integer();
-bif_type(is_map_key, [_,_]) -> t_boolean();
-bif_type(length, [_]) -> t_integer();
-bif_type(map_size, [_]) -> t_integer();
-bif_type(node, []) -> #t_atom{};
-bif_type(node, [_]) -> #t_atom{};
-bif_type(round, [_]) -> t_integer();
-bif_type(size, [_]) -> t_integer();
-bif_type(trunc, [_]) -> t_integer();
-bif_type(tuple_size, [_]) -> t_integer();
-bif_type('bnot', [_]) -> t_integer();
-bif_type('bor', [_,_]) -> t_integer();
-bif_type('bsl', [_,_]) -> t_integer();
-bif_type('bsr', [_,_]) -> t_integer();
-bif_type('bxor', [_,_]) -> t_integer();
-bif_type('div', [_,_]) -> t_integer();
-bif_type('rem', [_,_]) -> t_integer();
-bif_type('/', [_,_]) -> float;
-bif_type(Name, Args) ->
- Arity = length(Args),
- case erl_internal:new_type_test(Name, Arity) orelse
- erl_internal:bool_op(Name, Arity) orelse
- erl_internal:comp_op(Name, Arity) of
- true ->
- t_boolean();
- false ->
- case erl_internal:arith_op(Name, Arity) of
- true -> number;
- false -> any
- end
- end.
-
-inferred_bif_type(is_atom, [_]) -> t_atom();
-inferred_bif_type(is_binary, [_]) -> {binary,8};
-inferred_bif_type(is_bitstring, [_]) -> {binary,1};
-inferred_bif_type(is_boolean, [_]) -> t_boolean();
-inferred_bif_type(is_float, [_]) -> float;
-inferred_bif_type(is_integer, [_]) -> t_integer();
-inferred_bif_type(is_list, [_]) -> list;
-inferred_bif_type(is_map, [_]) -> map;
-inferred_bif_type(is_number, [_]) -> number;
-inferred_bif_type(is_tuple, [_]) -> #t_tuple{};
-inferred_bif_type(abs, [_]) -> number;
-inferred_bif_type(bit_size, [_]) -> {binary,1};
-inferred_bif_type('bnot', [_]) -> t_integer();
-inferred_bif_type(byte_size, [_]) -> {binary,1};
-inferred_bif_type(ceil, [_]) -> number;
-inferred_bif_type(float, [_]) -> number;
-inferred_bif_type(floor, [_]) -> number;
-inferred_bif_type(hd, [_]) -> cons;
-inferred_bif_type(length, [_]) -> list;
-inferred_bif_type(map_size, [_]) -> map;
-inferred_bif_type('not', [_]) -> t_boolean();
-inferred_bif_type(round, [_]) -> number;
-inferred_bif_type(trunc, [_]) -> number;
-inferred_bif_type(tl, [_]) -> cons;
-inferred_bif_type(tuple_size, [_]) -> #t_tuple{};
-inferred_bif_type('and', [_,_]) -> t_boolean();
-inferred_bif_type('or', [_,_]) -> t_boolean();
-inferred_bif_type('xor', [_,_]) -> t_boolean();
-inferred_bif_type('band', [_,_]) -> t_integer();
-inferred_bif_type('bor', [_,_]) -> t_integer();
-inferred_bif_type('bsl', [_,_]) -> t_integer();
-inferred_bif_type('bsr', [_,_]) -> t_integer();
-inferred_bif_type('bxor', [_,_]) -> t_integer();
-inferred_bif_type('div', [_,_]) -> t_integer();
-inferred_bif_type('rem', [_,_]) -> t_integer();
-inferred_bif_type('+', [_,_]) -> number;
-inferred_bif_type('-', [_,_]) -> number;
-inferred_bif_type('*', [_,_]) -> number;
-inferred_bif_type('/', [_,_]) -> number;
-inferred_bif_type(_, _) -> any.
-
-is_math_bif(cos, 1) -> true;
-is_math_bif(cosh, 1) -> true;
-is_math_bif(sin, 1) -> true;
-is_math_bif(sinh, 1) -> true;
-is_math_bif(tan, 1) -> true;
-is_math_bif(tanh, 1) -> true;
-is_math_bif(acos, 1) -> true;
-is_math_bif(acosh, 1) -> true;
-is_math_bif(asin, 1) -> true;
-is_math_bif(asinh, 1) -> true;
-is_math_bif(atan, 1) -> true;
-is_math_bif(atanh, 1) -> true;
-is_math_bif(erf, 1) -> true;
-is_math_bif(erfc, 1) -> true;
-is_math_bif(exp, 1) -> true;
-is_math_bif(log, 1) -> true;
-is_math_bif(log2, 1) -> true;
-is_math_bif(log10, 1) -> true;
-is_math_bif(sqrt, 1) -> true;
-is_math_bif(atan2, 2) -> true;
-is_math_bif(pow, 2) -> true;
-is_math_bif(ceil, 1) -> true;
-is_math_bif(floor, 1) -> true;
-is_math_bif(fmod, 2) -> true;
-is_math_bif(pi, 0) -> true;
-is_math_bif(_, _) -> false.
-
-join_types(Ts0, Ts1) ->
+join_types(Ts, Ts) ->
+ Ts;
+join_types(LHS, RHS) ->
if
- map_size(Ts0) < map_size(Ts1) ->
- join_types_1(maps:keys(Ts0), Ts1, Ts0);
+ map_size(LHS) < map_size(RHS) ->
+ join_types_1(maps:keys(LHS), RHS, LHS);
true ->
- join_types_1(maps:keys(Ts1), Ts0, Ts1)
- end.
-
-join_types_1([V|Vs], Ts0, Ts1) ->
- case {Ts0,Ts1} of
- {#{V:=Same},#{V:=Same}} ->
- join_types_1(Vs, Ts0, Ts1);
- {#{V:=T0},#{V:=T1}} ->
- case join(T0, T1) of
- T1 ->
- join_types_1(Vs, Ts0, Ts1);
- T ->
- join_types_1(Vs, Ts0, Ts1#{V:=T})
- end;
- {#{},#{V:=_}} ->
- join_types_1(Vs, Ts0, Ts1)
- end;
-join_types_1([], Ts0, Ts1) ->
- maps:merge(Ts0, Ts1).
-
-join([T1,T2|Ts]) ->
- join([join(T1, T2)|Ts]);
-join([T]) -> T.
-
-get_literal_from_type(#t_atom{elements=[Atom]}) ->
- #b_literal{val=Atom};
-get_literal_from_type(#t_integer{elements={Int,Int}}) ->
- #b_literal{val=Int};
-get_literal_from_type(nil) ->
- #b_literal{val=[]};
-get_literal_from_type(_) -> none.
-
-remove_element_info(#t_integer{elements={Min,Max}}, Es) ->
- foldl(fun(El, Acc) when Min =< El, El =< Max ->
- maps:remove(El, Acc);
- (_El, Acc) -> Acc
- end, Es, maps:keys(Es)).
-
-t_atom() ->
- #t_atom{elements=any}.
-
-t_atom(Atom) when is_atom(Atom) ->
- #t_atom{elements=[Atom]}.
-
-t_boolean() ->
- #t_atom{elements=[false,true]}.
-
-t_integer() ->
- #t_integer{elements=any}.
-
-t_integer(Int) when is_integer(Int) ->
- #t_integer{elements={Int,Int}}.
-
-t_integer(Min, Max) when is_integer(Min), is_integer(Max) ->
- #t_integer{elements={Min,Max}}.
-
-t_is_boolean(#t_atom{elements=[F,T]}) ->
- F =:= false andalso T =:= true;
-t_is_boolean(#t_atom{elements=[B]}) ->
- is_boolean(B);
-t_is_boolean(_) -> false.
-
-t_tuple_size(#t_tuple{size=Size,exact=false}) ->
- {at_least,Size};
-t_tuple_size(#t_tuple{size=Size,exact=true}) ->
- {exact,Size};
-t_tuple_size(_) ->
- none.
-
-is_singleton_type(Type) ->
- get_literal_from_type(Type) =/= none.
-
-get_element_type(Index, Es) ->
- case Es of
- #{ Index := T } -> T;
- #{} -> any
+ join_types_1(maps:keys(RHS), LHS, RHS)
end.
-set_element_type(_Key, none, Es) ->
- Es;
-set_element_type(Key, any, Es) ->
- maps:remove(Key, Es);
-set_element_type(Key, Type, Es) ->
- Es#{ Key => Type }.
-
-%% join(Type1, Type2) -> Type
-%% Return the "join" of Type1 and Type2. The join is a more general
-%% type than Type1 and Type2. For example:
-%%
-%% join(#t_integer{elements=any}, #t_integer=elements={0,3}}) ->
-%% #t_integer{}
-%%
-%% The join for two different types result in 'any', which is
-%% the top element for our type lattice:
-%%
-%% join(#t_integer{}, map) -> any
-
--spec join(type(), type()) -> type().
-
-join(T, T) ->
- verified_type(T);
-join(none, T) ->
- verified_type(T);
-join(T, none) ->
- verified_type(T);
-join(any, _) -> any;
-join(_, any) -> any;
-join(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
- Set = ordsets:union(Set1, Set2),
- case ordsets:size(Set) of
- Size when Size =< ?ATOM_SET_SIZE ->
- #t_atom{elements=Set};
- _Size ->
- #t_atom{elements=any}
- end;
-join(#t_atom{elements=any}=T, #t_atom{elements=[_|_]}) -> T;
-join(#t_atom{elements=[_|_]}, #t_atom{elements=any}=T) -> T;
-join({binary,U1}, {binary,U2}) ->
- {binary,gcd(U1, U2)};
-join(#t_integer{}, #t_integer{}) -> t_integer();
-join(list, cons) -> list;
-join(cons, list) -> list;
-join(nil, cons) -> list;
-join(cons, nil) -> list;
-join(nil, list) -> list;
-join(list, nil) -> list;
-join(#t_integer{}, float) -> number;
-join(float, #t_integer{}) -> number;
-join(#t_integer{}, number) -> number;
-join(number, #t_integer{}) -> number;
-join(float, number) -> number;
-join(number, float) -> number;
-join(#t_tuple{size=Sz,exact=ExactA,elements=EsA},
- #t_tuple{size=Sz,exact=ExactB,elements=EsB}) ->
- Exact = ExactA and ExactB,
- Es = join_tuple_elements(Sz, EsA, EsB),
- #t_tuple{size=Sz,exact=Exact,elements=Es};
-join(#t_tuple{size=SzA,elements=EsA}, #t_tuple{size=SzB,elements=EsB}) ->
- Sz = min(SzA, SzB),
- Es = join_tuple_elements(Sz, EsA, EsB),
- #t_tuple{size=Sz,elements=Es};
-join(_T1, _T2) ->
- %%io:format("~p ~p\n", [_T1,_T2]),
- any.
-
-join_tuple_elements(MinSize, EsA, EsB) ->
- Es0 = join_elements(EsA, EsB),
- maps:filter(fun(Index, _Type) -> Index =< MinSize end, Es0).
-
-join_elements(Es1, Es2) ->
- Keys = if
- map_size(Es1) =< map_size(Es2) -> maps:keys(Es1);
- map_size(Es1) > map_size(Es2) -> maps:keys(Es2)
- end,
- join_elements_1(Keys, Es1, Es2, #{}).
-
-join_elements_1([Key | Keys], Es1, Es2, Acc0) ->
- case {Es1, Es2} of
- {#{ Key := Type1 }, #{ Key := Type2 }} ->
- Acc = set_element_type(Key, join(Type1, Type2), Acc0),
- join_elements_1(Keys, Es1, Es2, Acc);
- {#{}, #{}} ->
- join_elements_1(Keys, Es1, Es2, Acc0)
+%% Joins two type maps, keeping the variables that are common to both maps.
+join_types_1([V | Vs], Bigger, Smaller) ->
+ case {Bigger, Smaller} of
+ {#{ V := Same }, #{ V := Same }} ->
+ join_types_1(Vs, Bigger, Smaller);
+ {#{ V := LHS }, #{ V := RHS }} ->
+ T = beam_types:join(LHS, RHS),
+ join_types_1(Vs, Bigger, Smaller#{ V := T });
+ {#{}, #{ V := _ }} ->
+ join_types_1(Vs, Bigger, maps:remove(V, Smaller))
end;
-join_elements_1([], _Es1, _Es2, Acc) ->
- Acc.
-
-gcd(A, B) ->
- case A rem B of
- 0 -> B;
- X -> gcd(B, X)
- end.
+join_types_1([], _Bigger, Smaller) ->
+ Smaller.
meet_types([{V,T0}|Vs], Ts) ->
#{V:=T1} = Ts,
- case meet(T0, T1) of
+ case beam_types:meet(T0, T1) of
+ none -> none;
T1 -> meet_types(Vs, Ts);
T -> meet_types(Vs, Ts#{V:=T})
end;
meet_types([], Ts) -> Ts.
-meet([T1,T2|Ts]) ->
- meet([meet(T1, T2)|Ts]);
-meet([T]) -> T.
-
subtract_types([{V,T0}|Vs], Ts) ->
#{V:=T1} = Ts,
- case subtract(T1, T0) of
+ case beam_types:subtract(T1, T0) of
+ none -> none;
T1 -> subtract_types(Vs, Ts);
T -> subtract_types(Vs, Ts#{V:=T})
end;
subtract_types([], Ts) -> Ts.
-%% subtract(Type1, Type2) -> Type.
-%% Subtract Type2 from Type1. Example:
-%%
-%% subtract(list, cons) -> nil
+parallel_join([A | As], [B | Bs]) ->
+ [beam_types:join(A, B) | parallel_join(As, Bs)];
+parallel_join([], []) ->
+ [].
-subtract(#t_atom{elements=[_|_]=Set0}, #t_atom{elements=[_|_]=Set1}) ->
- case ordsets:subtract(Set0, Set1) of
- [] -> none;
- [_|_]=Set -> #t_atom{elements=Set}
- end;
-subtract(number, float) -> #t_integer{};
-subtract(number, #t_integer{elements=any}) -> float;
-subtract(list, cons) -> nil;
-subtract(list, nil) -> cons;
-subtract(T, _) -> T.
-
-%% meet(Type1, Type2) -> Type
-%% Return the "meet" of Type1 and Type2. The meet is a narrower
-%% type than Type1 and Type2. For example:
-%%
-%% meet(#t_integer{elements=any}, #t_integer{elements={0,3}}) ->
-%% #t_integer{elements={0,3}}
-%%
-%% The meet for two different types result in 'none', which is
-%% the bottom element for our type lattice:
-%%
-%% meet(#t_integer{}, map) -> none
+gcd(A, B) ->
+ case A rem B of
+ 0 -> B;
+ X -> gcd(B, X)
+ end.
+
+%%%
+%%% Helpers
+%%%
--spec meet(type(), type()) -> type().
+init_metadata(FuncId, Linear, Params) ->
+ {RetCounter, Map0} = init_metadata_1(reverse(Linear), 0, #{}),
+ Map = maps:without(Params, Map0),
+ UsedOnce = cerl_sets:from_list(maps:keys(Map)),
+
+ #metadata{ func_id = FuncId,
+ limit_return = (RetCounter >= ?RETURN_LIMIT),
+ params = Params,
+ used_once = UsedOnce }.
+
+init_metadata_1([{L,#b_blk{is=Is,last=Last}} | Bs], RetCounter0, Uses0) ->
+ %% Track the number of return terminators in use. See ?RETURN_LIMIT for
+ %% details.
+ RetCounter = case Last of
+ #b_ret{} -> RetCounter0 + 1;
+ _ -> RetCounter0
+ end,
+
+ %% Calculate the set of variables that are only used once in the terminator
+ %% of the block that defines them. That will allow us to discard type
+ %% information discard type information for variables that will never be
+ %% referenced by the successor blocks, potentially improving compilation
+ %% times.
-meet(T, T) ->
- verified_type(T);
-meet(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
- case ordsets:intersection(Set1, Set2) of
- [] ->
- none;
- [_|_]=Set ->
- #t_atom{elements=Set}
+ Uses1 = used_once_last_uses(beam_ssa:used(Last), L, Uses0),
+ Uses = used_once_2(reverse(Is), L, Uses1),
+ init_metadata_1(Bs, RetCounter, Uses);
+init_metadata_1([], RetCounter, Uses) ->
+ {RetCounter, Uses}.
+
+used_once_2([#b_set{dst=Dst}=I|Is], L, Uses0) ->
+ Uses = used_once_uses(beam_ssa:used(I), L, Uses0),
+ case Uses of
+ #{Dst:=[L]} ->
+ used_once_2(Is, L, Uses);
+ #{} ->
+ %% Used more than once or used once in
+ %% in another block.
+ used_once_2(Is, L, maps:remove(Dst, Uses))
end;
-meet(#t_atom{elements=[_|_]}=T, #t_atom{elements=any}) ->
- T;
-meet(#t_atom{elements=any}, #t_atom{elements=[_|_]}=T) ->
- T;
-meet(#t_integer{elements={_,_}}=T, #t_integer{elements=any}) ->
- T;
-meet(#t_integer{elements=any}, #t_integer{elements={_,_}}=T) ->
- T;
-meet(#t_integer{elements={Min1,Max1}},
- #t_integer{elements={Min2,Max2}}) ->
- #t_integer{elements={max(Min1, Min2),min(Max1, Max2)}};
-meet(#t_integer{}=T, number) -> T;
-meet(float=T, number) -> T;
-meet(number, #t_integer{}=T) -> T;
-meet(number, float=T) -> T;
-meet(list, cons) -> cons;
-meet(list, nil) -> nil;
-meet(cons, list) -> cons;
-meet(nil, list) -> nil;
-meet(#t_tuple{}=T1, #t_tuple{}=T2) ->
- meet_tuples(T1, T2);
-meet({binary,U1}, {binary,U2}) ->
- {binary,max(U1, U2)};
-meet(any, T) ->
- verified_type(T);
-meet(T, any) ->
- verified_type(T);
-meet(_, _) ->
- %% Inconsistent types. There will be an exception at runtime.
- none.
-
-meet_tuples(#t_tuple{size=Sz1,exact=true},
- #t_tuple{size=Sz2,exact=true}) when Sz1 =/= Sz2 ->
- none;
-meet_tuples(#t_tuple{size=Sz1,exact=Ex1,elements=Es1},
- #t_tuple{size=Sz2,exact=Ex2,elements=Es2}) ->
- Size = max(Sz1, Sz2),
- Exact = Ex1 or Ex2,
- case meet_elements(Es1, Es2) of
- none ->
- none;
- Es ->
- #t_tuple{size=Size,exact=Exact,elements=Es}
- end.
+used_once_2([], _, Uses) -> Uses.
-meet_elements(Es1, Es2) ->
- Keys = maps:keys(Es1) ++ maps:keys(Es2),
- meet_elements_1(Keys, Es1, Es2, #{}).
+used_once_uses([V|Vs], L, Uses) ->
+ case Uses of
+ #{V:=more_than_once} ->
+ used_once_uses(Vs, L, Uses);
+ #{} ->
+ %% Already used or first use is not in
+ %% a terminator.
+ used_once_uses(Vs, L, Uses#{V=>more_than_once})
+ end;
+used_once_uses([], _, Uses) -> Uses.
-meet_elements_1([Key | Keys], Es1, Es2, Acc) ->
- case {Es1, Es2} of
- {#{ Key := Type1 }, #{ Key := Type2 }} ->
- case meet(Type1, Type2) of
- none -> none;
- Type -> meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type })
- end;
- {#{ Key := Type1 }, _} ->
- meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type1 });
- {_, #{ Key := Type2 }} ->
- meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type2 })
+used_once_last_uses([V|Vs], L, Uses) ->
+ case Uses of
+ #{V:=[_]} ->
+ %% Second time this variable is used.
+ used_once_last_uses(Vs, L, Uses#{V:=more_than_once});
+ #{V:=more_than_once} ->
+ %% Used at least twice before.
+ used_once_last_uses(Vs, L, Uses);
+ #{} ->
+ %% First time this variable is used.
+ used_once_last_uses(Vs, L, Uses#{V=>[L]})
end;
-meet_elements_1([], _Es1, _Es2, Acc) ->
- Acc.
+used_once_last_uses([], _, Uses) -> Uses.
-%% verified_type(Type) -> Type
-%% Returns the passed in type if it is one of the defined types.
-%% Crashes if there is anything wrong with the type.
%%
-%% Here are all possible types:
+%% Ordered worklist used in signatures/2.
%%
-%% any Any Erlang term (top element for the type lattice).
+%% This is equivalent to consing (wl_add) and appending (wl_defer_list)
+%% to a regular list, but avoids uneccessary work by reordering elements.
%%
-%% #t_atom{} Any atom or some specific atoms.
-%% {binary,Unit} Binary/bitstring aligned to unit Unit.
-%% float Floating point number.
-%% #t_integer{} Integer
-%% list Empty or nonempty list.
-%% map Map.
-%% nil Empty list.
-%% cons Cons (nonempty list).
-%% number A number (float or integer).
-%% #t_tuple{} Tuple.
+%% We can do this since a function only needs to be visited *once* for all
+%% prior updates to take effect, so if an element is added to the front, then
+%% all earlier instances of the same element are redundant.
%%
-%% none No type (bottom element for the type lattice).
-
--spec verified_type(T) -> T when
- T :: type().
-
-verified_type(any=T) -> T;
-verified_type(none=T) -> T;
-verified_type(#t_atom{elements=any}=T) -> T;
-verified_type(#t_atom{elements=[_|_]}=T) -> T;
-verified_type({binary,U}=T) when is_integer(U) -> T;
-verified_type(#t_integer{elements=any}=T) -> T;
-verified_type(#t_integer{elements={Min,Max}}=T)
- when is_integer(Min), is_integer(Max) -> T;
-verified_type(list=T) -> T;
-verified_type(map=T) -> T;
-verified_type(nil=T) -> T;
-verified_type(cons=T) -> T;
-verified_type(number=T) -> T;
-verified_type(#t_tuple{size=Size,elements=Es}=T) ->
- %% All known elements must have a valid index and type. 'any' is prohibited
- %% since it's implicit and should never be present in the map.
- maps:fold(fun(Index, Element, _) when is_integer(Index),
- 1 =< Index, Index =< Size,
- Element =/= any, Element =/= none ->
- verified_type(Element)
- end, [], Es),
- T;
-verified_type(float=T) -> T.
+
+-record(worklist,
+ { counter = 0 :: integer(),
+ elements = gb_trees:empty() :: gb_trees:tree(integer(), term()),
+ indexes = #{} :: #{ term() => integer() } }).
+
+-type worklist() :: #worklist{}.
+
+wl_new() -> #worklist{}.
+
+%% Adds an element to the worklist, or moves it to the front if it's already
+%% present.
+wl_add(Element, #worklist{counter=Counter,elements=Es,indexes=Is}) ->
+ case Is of
+ #{ Element := Index } ->
+ wl_add_1(Element, Counter, gb_trees:delete(Index, Es), Is);
+ #{} ->
+ wl_add_1(Element, Counter, Es, Is)
+ end.
+
+wl_add_1(Element, Counter0, Es0, Is0) ->
+ Counter = Counter0 + 1,
+ Es = gb_trees:insert(Counter, Element, Es0),
+ Is = Is0#{ Element => Counter },
+ #worklist{counter=Counter,elements=Es,indexes=Is}.
+
+%% All mutations bump the counter, so we can check for changes without a deep
+%% comparison.
+wl_changed(#worklist{counter=Same}, #worklist{counter=Same}) -> false;
+wl_changed(#worklist{}, #worklist{}) -> true.
+
+%% Adds the given elements to the back of the worklist, skipping the elements
+%% that are already present. This lets us append elements arbitrarly after the
+%% current front without changing the work order.
+wl_defer_list(Elements, #worklist{counter=Counter,elements=Es,indexes=Is}) ->
+ wl_defer_list_1(Elements, Counter, Es, Is).
+
+wl_defer_list_1([Element | Elements], Counter0, Es0, Is0) ->
+ case Is0 of
+ #{ Element := _ } ->
+ wl_defer_list_1(Elements, Counter0, Es0, Is0);
+ #{} ->
+ Counter = Counter0 + 1,
+ Es = gb_trees:insert(-Counter, Element, Es0),
+ Is = Is0#{ Element => -Counter },
+ wl_defer_list_1(Elements, Counter, Es, Is)
+ end;
+wl_defer_list_1([], Counter, Es, Is) ->
+ #worklist{counter=Counter,elements=Es,indexes=Is}.
+
+wl_next(#worklist{indexes=Is}) when Is =:= #{} ->
+ empty;
+wl_next(#worklist{elements=Es,indexes=Is}) when Is =/= #{} ->
+ {_Key, Element} = gb_trees:largest(Es),
+ {ok, Element}.
+
+%% Removes the front of the worklist.
+wl_pop(Element, #worklist{counter=Counter0,elements=Es0,indexes=Is0}=Wl) ->
+ Counter = Counter0 + 1,
+ {_Key, Element, Es} = gb_trees:take_largest(Es0), %Assertion.
+ Is = maps:remove(Element, Is0),
+ Wl#worklist{counter=Counter,elements=Es,indexes=Is}.
diff --git a/lib/compiler/src/beam_trim.erl b/lib/compiler/src/beam_trim.erl
index 4807a60eaf..017d2b7d8c 100644
--- a/lib/compiler/src/beam_trim.erl
+++ b/lib/compiler/src/beam_trim.erl
@@ -63,7 +63,8 @@ trim([{kill,_}|_]=Is0, St, Acc) ->
%% Calculate all recipes that are not worse in terms
%% of estimated execution time. The recipes are ordered
%% in descending order from how much they trim.
- Recipes = trim_recipes(Layout),
+ IsNotRecursive = is_not_recursive(Is1),
+ Recipes = trim_recipes(Layout, IsNotRecursive),
%% Try the recipes in order. A recipe may not work out because
%% a register that was previously killed may be
@@ -85,6 +86,21 @@ trim([I|Is], St, Acc) ->
trim([], _, Acc) ->
reverse(Acc).
+%% is_not_recursive([Instruction]) -> true|false.
+%% Test whether the next call or apply instruction may
+%% do a recursive call. Return `true` if the call is
+%% definitely not recursive, and `false` otherwise.
+is_not_recursive([{call_ext,_,Ext}|_]) ->
+ case Ext of
+ {extfunc,M,F,A} ->
+ erl_bifs:is_pure(M, F, A);
+ _ ->
+ false
+ end;
+is_not_recursive([{block,_}|Is]) -> is_not_recursive(Is);
+is_not_recursive([{line,_}|Is]) -> is_not_recursive(Is);
+is_not_recursive(_) -> false.
+
%% trim_recipes([{kill,R}|{live,R}|{dead,R}]) -> [Recipe].
%% Recipe = {Kills,NumberToTrim,Moves}
%% Kills = [{kill,Y}]
@@ -93,34 +109,34 @@ trim([], _, Acc) ->
%% Calculate how to best trim the stack and kill the correct
%% Y registers. Return a list of possible recipes. The best
%% recipe (the one that trims the most) is first in the list.
-%% All of the recipes are no worse in estimated execution time
-%% than the original sequences of kill instructions.
-trim_recipes(Layout) ->
- Cost = length([I || {kill,_}=I <- Layout]),
- trim_recipes_1(Layout, 0, [], {Cost,[]}).
+trim_recipes(Layout, IsNotRecursive) ->
+ Recipes = construct_recipes(Layout, 0, [], []),
+ NumOrigKills = length([I || {kill,_}=I <- Layout]),
+ IsTooExpensive = is_too_expensive_fun(IsNotRecursive),
+ [R || R <- Recipes,
+ not is_too_expensive(R, NumOrigKills, IsTooExpensive)].
-trim_recipes_1([{kill,{y,Trim0}}|Ks], Trim0, Moves, Recipes0) ->
+construct_recipes([{kill,{y,Trim0}}|Ks], Trim0, Moves, Acc) ->
Trim = Trim0 + 1,
- Recipes = save_recipe(Ks, Trim, Moves, Recipes0),
- trim_recipes_1(Ks, Trim, Moves, Recipes);
-trim_recipes_1([{dead,{y,Trim0}}|Ks], Trim0, Moves, Recipes0) ->
+ Recipe = {Ks,Trim,Moves},
+ construct_recipes(Ks, Trim, Moves, [Recipe|Acc]);
+construct_recipes([{dead,{y,Trim0}}|Ks], Trim0, Moves, Acc) ->
Trim = Trim0 + 1,
- Recipes = save_recipe(Ks, Trim, Moves, Recipes0),
- trim_recipes_1(Ks, Trim, Moves, Recipes);
-trim_recipes_1([{live,{y,Trim0}=Src}|Ks0], Trim0, Moves0, Recipes0) ->
+ Recipe = {Ks,Trim,Moves},
+ construct_recipes(Ks, Trim, Moves, [Recipe|Acc]);
+construct_recipes([{live,{y,Trim0}=Src}|Ks0], Trim0, Moves0, Acc) ->
case take_last_dead(Ks0) of
none ->
- {_,RecipesList} = Recipes0,
- RecipesList;
+ %% No more recipes are possible.
+ Acc;
{Dst,Ks} ->
Trim = Trim0 + 1,
Moves = [{move,Src,Dst}|Moves0],
- Recipes = save_recipe(Ks, Trim, Moves, Recipes0),
- trim_recipes_1(Ks, Trim, Moves, Recipes)
+ Recipe = {Ks,Trim,Moves},
+ construct_recipes(Ks, Trim, Moves, [Recipe|Acc])
end;
-trim_recipes_1([], _, _, {_,RecipesList}) ->
- RecipesList.
+construct_recipes([], _, _, Acc) -> Acc.
take_last_dead(L) ->
take_last_dead_1(reverse(L)).
@@ -131,33 +147,47 @@ take_last_dead_1([{dead,Reg}|Is]) ->
{Reg,reverse(Is)};
take_last_dead_1(_) -> none.
-save_recipe(Ks, Trim, Moves, {MaxCost,Acc}=Recipes) ->
- case recipe_cost(Ks, Moves) of
- Cost when Cost =< MaxCost ->
- %% The price is right.
- {MaxCost,[{Ks,Trim,Moves}|Acc]};
- _Cost ->
- %% Too expensive.
- Recipes
+%% Is trimming too expensive?
+is_too_expensive({Ks,_,Moves}, NumOrigKills, IsTooExpensive) ->
+ NumKills = num_kills(Ks, 0),
+ NumMoves = length(Moves),
+ IsTooExpensive(NumKills, NumMoves, NumOrigKills).
+
+num_kills([{kill,_}|T], Acc) ->
+ num_kills(T, Acc+1);
+num_kills([_|T], Acc) ->
+ num_kills(T, Acc);
+num_kills([], Acc) -> Acc.
+
+is_too_expensive_fun(true) ->
+ %% This call is not recursive (because it is a call to a BIF).
+ %% Here we should avoid trimming if the trimming sequence is
+ %% likely to be more expensive than the original sequence.
+ fun(NumKills, NumMoves, NumOrigKills) ->
+ Penalty =
+ if
+ %% Slightly penalize the use of any `move`
+ %% instruction to avoid replacing two `kill`
+ %% instructions with a `move` and a `trim`.
+ NumMoves =/= 0 -> 1;
+ true -> 0
+ end,
+ 1 + Penalty + NumKills + NumMoves > NumOrigKills
+ end;
+is_too_expensive_fun(false) ->
+ %% This call **may** be recursive. In a recursive function that
+ %% builds up a huge stack, having unused stack slots will be very
+ %% expensive. Therefore, we want to be biased towards trimming.
+ %% We will do that by not counting the `trim` instruction in
+ %% the formula below.
+ fun(NumKills, NumMoves, NumOrigKills) ->
+ NumKills + NumMoves > NumOrigKills
end.
-recipe_cost(Ks, Moves) ->
- %% We estimate that a {move,{y,_},{y,_}} instruction is roughly twice as
- %% expensive as a {kill,{y,_}} instruction. A {trim,_} instruction is
- %% roughly as expensive as a {kill,{y,_}} instruction.
-
- recipe_cost_1(Ks, 1+2*length(Moves)).
-
-recipe_cost_1([{kill,_}|Ks], Cost) ->
- recipe_cost_1(Ks, Cost+1);
-recipe_cost_1([_|Ks], Cost) ->
- recipe_cost_1(Ks, Cost);
-recipe_cost_1([], Cost) -> Cost.
-
%% try_remap([Recipe], [Instruction], FrameSize) ->
%% {[Instruction],[TrimInstruction]}.
%% Try to renumber Y registers in the instruction stream. The
-%% first rececipe that works will be used.
+%% first recipe that works will be used.
%%
%% This function will issue a `not_possible` exception if none
%% of the recipes were possible to apply.
@@ -208,6 +238,9 @@ remap([{block,Bl0}|Is], Map, Acc) ->
remap([{bs_get_tail,Src,Dst,Live}|Is], Map, Acc) ->
I = {bs_get_tail,Map(Src),Map(Dst),Live},
remap(Is, Map, [I|Acc]);
+remap([{bs_start_match4,Fail,Live,Src,Dst}|Is], Map, Acc) ->
+ I = {bs_start_match4,Fail,Live,Map(Src),Map(Dst)},
+ remap(Is, Map, [I|Acc]);
remap([{bs_set_position,Src1,Src2}|Is], Map, Acc) ->
I = {bs_set_position,Map(Src1),Map(Src2)},
remap(Is, Map, [I|Acc]);
@@ -244,6 +277,9 @@ remap([{make_fun2,_,_,_,_}=I|T], Map, Acc) ->
remap([{deallocate,N}|Is], Map, Acc) ->
I = {deallocate,Map({frame_size,N})},
remap(Is, Map, [I|Acc]);
+remap([{swap,Reg1,Reg2}|Is], Map, Acc) ->
+ I = {swap,Map(Reg1),Map(Reg2)},
+ remap(Is, Map, [I|Acc]);
remap([{test,Name,Fail,Ss}|Is], Map, Acc) ->
I = {test,Name,Fail,[Map(S) || S <- Ss]},
remap(Is, Map, [I|Acc]);
@@ -378,10 +414,17 @@ frame_size([{deallocate,N}|_], _) ->
N;
frame_size([{line,_}|Is], Safe) ->
frame_size(Is, Safe);
+frame_size([{bs_start_match4,Fail,_,_,_}|Is], Safe) ->
+ case Fail of
+ {f,L} -> frame_size_branch(L, Is, Safe);
+ _ -> frame_size(Is, Safe)
+ end;
frame_size([{bs_set_position,_,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{bs_get_tail,_,_,_}|Is], Safe) ->
frame_size(Is, Safe);
+frame_size([{swap,_,_}|Is], Safe) ->
+ frame_size(Is, Safe);
frame_size(_, _) -> throw(not_possible).
frame_size_branch(0, Is, Safe) ->
@@ -417,6 +460,9 @@ is_not_used(Y, [{bs_init,_,_,_,Ss,Dst}|Is]) ->
is_not_used_ss_dst(Y, Ss, Dst, Is);
is_not_used(Y, [{bs_put,{f,_},_,Ss}|Is]) ->
not member(Y, Ss) andalso is_not_used(Y, Is);
+is_not_used(Y, [{bs_start_match4,_Fail,_Live,Src,Dst}|Is]) ->
+ Y =/= Src andalso Y =/= Dst andalso
+ is_not_used(Y, Is);
is_not_used(Y, [{bs_set_position,Src1,Src2}|Is]) ->
Y =/= Src1 andalso Y =/= Src2 andalso
is_not_used(Y, Is);
@@ -444,6 +490,8 @@ is_not_used(Y, [{line,_}|Is]) ->
is_not_used(Y, Is);
is_not_used(Y, [{make_fun2,_,_,_,_}|Is]) ->
is_not_used(Y, Is);
+is_not_used(Y, [{swap,Reg1,Reg2}|Is]) ->
+ Y =/= Reg1 andalso Y =/= Reg2 andalso is_not_used(Y, Is);
is_not_used(Y, [{test,_,_,Ss}|Is]) ->
not member(Y, Ss) andalso is_not_used(Y, Is);
is_not_used(Y, [{test,_Op,{f,_},_Live,Ss,Dst}|Is]) ->
diff --git a/lib/compiler/src/beam_types.erl b/lib/compiler/src/beam_types.erl
new file mode 100644
index 0000000000..5577fe79d8
--- /dev/null
+++ b/lib/compiler/src/beam_types.erl
@@ -0,0 +1,1127 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(beam_types).
+
+-define(BEAM_TYPES_INTERNAL, true).
+-include("beam_types.hrl").
+
+-import(lists, [foldl/3, reverse/1]).
+
+-export([meet/1, meet/2, join/1, join/2, subtract/2]).
+
+-export([is_boolean_type/1,
+ get_bs_matchable_unit/1,
+ is_bs_matchable_type/1,
+ get_singleton_value/1,
+ is_singleton_type/1,
+ normalize/1]).
+
+-export([get_tuple_element/2, set_tuple_element/3]).
+
+-export([make_type_from_value/1]).
+
+-export([make_atom/1,
+ make_boolean/0,
+ make_cons/2,
+ make_float/1,
+ make_float/2,
+ make_integer/1,
+ make_integer/2]).
+
+-export([limit_depth/1]).
+
+%% This is exported to help catch errors in property test generators and is not
+%% meant to be used outside of test suites.
+-export([verified_type/1]).
+
+-define(IS_LIST_TYPE(N),
+ is_record(N, t_list) orelse
+ is_record(N, t_cons) orelse
+ N =:= nil).
+
+-define(IS_NUMBER_TYPE(N),
+ N =:= number orelse
+ is_record(N, t_float) orelse
+ is_record(N, t_integer)).
+
+%% Folds meet/2 over a list.
+
+-spec meet([type()]) -> type().
+
+meet([T1, T2 | Ts]) ->
+ meet([meet(T1, T2) | Ts]);
+meet([T]) -> T.
+
+%% Return the "meet" of Type1 and Type2, which is more specific than Type1 and
+%% Type2. This is identical to glb/2 but can operate on and produce unions.
+%%
+%% A = #t_union{list=nil, number=[number], other=[#t_map{}]}
+%% B = #t_union{number=[#t_integer{}], other=[#t_map{}]}
+%%
+%% meet(A, B) ->
+%% #t_union{number=[#t_integer{}], other=[#t_map{}]}
+%%
+%% The meet of two different types result in 'none', which is the bottom
+%% element for our type lattice:
+%%
+%% meet(#t_integer{}, #t_map{}) -> none
+
+-spec meet(type(), type()) -> type().
+
+meet(T, T) ->
+ verified_type(T);
+meet(any, T) ->
+ verified_type(T);
+meet(T, any) ->
+ verified_type(T);
+meet(#t_union{}=A, B) ->
+ meet_unions(A, B);
+meet(A, #t_union{}=B) ->
+ meet_unions(B, A);
+meet(A, B) ->
+ glb(A, B).
+
+meet_unions(#t_union{atom=AtomA,list=ListA,number=NumberA,
+ tuple_set=TSetA,other=OtherA},
+ #t_union{atom=AtomB,list=ListB,number=NumberB,
+ tuple_set=TSetB,other=OtherB}) ->
+ Union = #t_union{atom=glb(AtomA, AtomB),
+ list=glb(ListA, ListB),
+ number=glb(NumberA, NumberB),
+ tuple_set=meet_tuple_sets(TSetA, TSetB),
+ other=glb(OtherA, OtherB)},
+ shrink_union(Union);
+meet_unions(#t_union{atom=AtomA}, #t_atom{}=B) ->
+ case glb(AtomA, B) of
+ none -> none;
+ Atom -> Atom
+ end;
+meet_unions(#t_union{number=NumberA}, B) when ?IS_NUMBER_TYPE(B) ->
+ case glb(NumberA, B) of
+ none -> none;
+ Number -> Number
+ end;
+meet_unions(#t_union{list=ListA}, B) when ?IS_LIST_TYPE(B) ->
+ case glb(ListA, B) of
+ none -> none;
+ List -> List
+ end;
+meet_unions(#t_union{tuple_set=Tuples}, #t_tuple{}=B) ->
+ Set = meet_tuple_sets(Tuples, new_tuple_set(B)),
+ shrink_union(#t_union{tuple_set=Set});
+meet_unions(#t_union{other=OtherA}, OtherB) ->
+ case glb(OtherA, OtherB) of
+ none -> none;
+ Other -> Other
+ end.
+
+meet_tuple_sets(none, _) ->
+ none;
+meet_tuple_sets(_, none) ->
+ none;
+meet_tuple_sets(#t_tuple{}=A, #t_tuple{}=B) ->
+ new_tuple_set(glb(A, B));
+meet_tuple_sets(#t_tuple{}=Tuple, Records) ->
+ mts_tuple(Records, Tuple, []);
+meet_tuple_sets(Records, #t_tuple{}=Tuple) ->
+ meet_tuple_sets(Tuple, Records);
+meet_tuple_sets(RecordsA, RecordsB) ->
+ mts_records(RecordsA, RecordsB).
+
+mts_tuple([{Key, Type} | Records], Tuple, Acc) ->
+ case glb(Type, Tuple) of
+ none -> mts_tuple(Records, Tuple, Acc);
+ T -> mts_tuple(Records, Tuple, [{Key, T} | Acc])
+ end;
+mts_tuple([], _Tuple, [_|_]=Acc) ->
+ reverse(Acc);
+mts_tuple([], _Tuple, []) ->
+ none.
+
+mts_records(RecordsA, RecordsB) ->
+ mts_records(RecordsA, RecordsB, []).
+
+mts_records([{Key, A} | RsA], [{Key, B} | RsB], Acc) ->
+ case glb(A, B) of
+ none -> mts_records(RsA, RsB, Acc);
+ T -> mts_records(RsA, RsB, [{Key, T} | Acc])
+ end;
+mts_records([{KeyA, _} | _ ]=RsA, [{KeyB, _} | RsB], Acc) when KeyA > KeyB ->
+ mts_records(RsA, RsB, Acc);
+mts_records([{KeyA, _} | RsA], [{KeyB, _} | _] = RsB, Acc) when KeyA < KeyB ->
+ mts_records(RsA, RsB, Acc);
+mts_records(_RsA, [], [_|_]=Acc) ->
+ reverse(Acc);
+mts_records([], _RsB, [_|_]=Acc) ->
+ reverse(Acc);
+mts_records(_RsA, _RsB, []) ->
+ none.
+
+%% Folds join/2 over a list.
+
+-spec join([type()]) -> type().
+
+join([T1, T2| Ts]) ->
+ join([join(T1, T2) | Ts]);
+join([T]) -> T.
+
+%% Return the "join" of Type1 and Type2, which is more general than Type1 and
+%% Type2. This is identical to lub/2 but can operate on and produce unions.
+%%
+%% join(#t_integer{}, #t_map{}) -> #t_union{number=[#t_integer{}],
+%% other=[#t_map{}]}
+
+-spec join(type(), type()) -> type().
+
+join(T, T) -> T;
+join(_T, any) -> any;
+join(any, _T) -> any;
+join(T, none) -> T;
+join(none, T) -> T;
+
+join(#t_union{}=A, B) ->
+ join_unions(A, B);
+join(A, #t_union{}=B) ->
+ join_unions(B, A);
+
+%% Union creation...
+join(#t_atom{}=A, #t_atom{}=B) ->
+ lub(A, B);
+join(#t_atom{}=A, B) when ?IS_LIST_TYPE(B) ->
+ #t_union{atom=A,list=B};
+join(#t_atom{}=A, B) when ?IS_NUMBER_TYPE(B) ->
+ #t_union{atom=A,number=B};
+join(#t_atom{}=A, #t_tuple{}=B) ->
+ #t_union{atom=A,tuple_set=new_tuple_set(B)};
+join(#t_atom{}=A, B) ->
+ #t_union{atom=A,other=B};
+join(A, #t_atom{}=B) ->
+ join(B, A);
+
+join(A, B) when ?IS_LIST_TYPE(A), ?IS_LIST_TYPE(B) ->
+ lub(A, B);
+join(A, B) when ?IS_LIST_TYPE(A), ?IS_NUMBER_TYPE(B) ->
+ #t_union{list=A,number=B};
+join(A, #t_tuple{}=B) when ?IS_LIST_TYPE(A) ->
+ #t_union{list=A,tuple_set=new_tuple_set(B)};
+join(A, B) when ?IS_LIST_TYPE(A) ->
+ #t_union{list=A,other=B};
+join(A, B) when ?IS_LIST_TYPE(B) ->
+ join(B, A);
+
+join(A, B) when ?IS_NUMBER_TYPE(A), ?IS_NUMBER_TYPE(B) ->
+ lub(A, B);
+join(A, #t_tuple{}=B) when ?IS_NUMBER_TYPE(A) ->
+ #t_union{number=A,tuple_set=new_tuple_set(B)};
+join(A, B) when ?IS_NUMBER_TYPE(A) ->
+ #t_union{number=A,other=B};
+join(A, B) when ?IS_NUMBER_TYPE(B) ->
+ join(B, A);
+
+join(#t_tuple{}=A, #t_tuple{}=B) ->
+ case {record_key(A), record_key(B)} of
+ {Same, Same} ->
+ lub(A, B);
+ {none, _Key} ->
+ lub(A, B);
+ {_Key, none} ->
+ lub(A, B);
+ {KeyA, KeyB} when KeyA < KeyB ->
+ #t_union{tuple_set=[{KeyA, A}, {KeyB, B}]};
+ {KeyA, KeyB} when KeyA > KeyB ->
+ #t_union{tuple_set=[{KeyB, B}, {KeyA, A}]}
+ end;
+join(#t_tuple{}=A, B) ->
+ %% All other combinations have been tried already, so B must be 'other'
+ #t_union{tuple_set=new_tuple_set(A),other=B};
+join(A, #t_tuple{}=B) ->
+ join(B, A);
+
+join(A, B) ->
+ lub(A, B).
+
+join_unions(#t_union{atom=AtomA,list=ListA,number=NumberA,
+ tuple_set=TSetA,other=OtherA},
+ #t_union{atom=AtomB,list=ListB,number=NumberB,
+ tuple_set=TSetB,other=OtherB}) ->
+ Union = #t_union{atom=lub(AtomA, AtomB),
+ list=lub(ListA, ListB),
+ number=lub(NumberA, NumberB),
+ tuple_set=join_tuple_sets(TSetA, TSetB),
+ other=lub(OtherA, OtherB)},
+ shrink_union(Union);
+join_unions(#t_union{atom=AtomA}=A, #t_atom{}=B) ->
+ A#t_union{atom=lub(AtomA, B)};
+join_unions(#t_union{list=ListA}=A, B) when ?IS_LIST_TYPE(B) ->
+ A#t_union{list=lub(ListA, B)};
+join_unions(#t_union{number=NumberA}=A, B) when ?IS_NUMBER_TYPE(B) ->
+ A#t_union{number=lub(NumberA, B)};
+join_unions(#t_union{tuple_set=TSetA}=A, #t_tuple{}=B) ->
+ Set = join_tuple_sets(TSetA, new_tuple_set(B)),
+ shrink_union(A#t_union{tuple_set=Set});
+join_unions(#t_union{other=OtherA}=A, B) ->
+ case lub(OtherA, B) of
+ any -> any;
+ T -> A#t_union{other=T}
+ end.
+
+join_tuple_sets(A, none) ->
+ A;
+join_tuple_sets(none, B) ->
+ B;
+join_tuple_sets(#t_tuple{}=A, #t_tuple{}=B) ->
+ lub(A, B);
+join_tuple_sets(#t_tuple{}=Tuple, Records) ->
+ jts_tuple(Records, Tuple);
+join_tuple_sets(Records, #t_tuple{}=Tuple) ->
+ join_tuple_sets(Tuple, Records);
+join_tuple_sets(RecordsA, RecordsB) ->
+ jts_records(RecordsA, RecordsB).
+
+jts_tuple([{_Key, Tuple} | Records], Acc) ->
+ jts_tuple(Records, lub(Tuple, Acc));
+jts_tuple([], Acc) ->
+ Acc.
+
+jts_records(RsA, RsB) ->
+ jts_records(RsA, RsB, 0, []).
+
+jts_records([], [], _N, Acc) ->
+ reverse(Acc);
+jts_records(RsA, RsB, N, Acc) when N > ?TUPLE_SET_LIMIT ->
+ A = normalize_tuple_set(RsA, none),
+ B = normalize_tuple_set(RsB, A),
+ #t_tuple{} = normalize_tuple_set(Acc, B);
+jts_records([{Key, A} | RsA], [{Key, B} | RsB], N, Acc) ->
+ jts_records(RsA, RsB, N + 1, [{Key, lub(A, B)} | Acc]);
+jts_records([{KeyA, _} | _]=RsA, [{KeyB, B} | RsB], N, Acc) when KeyA > KeyB ->
+ jts_records(RsA, RsB, N + 1, [{KeyB, B} | Acc]);
+jts_records([{KeyA, A} | RsA], [{KeyB, _} | _] = RsB, N, Acc) when KeyA < KeyB ->
+ jts_records(RsA, RsB, N + 1, [{KeyA, A} | Acc]);
+jts_records([{KeyA, A} | RsA], [], N, Acc) ->
+ jts_records(RsA, [], N + 1, [{KeyA, A} | Acc]);
+jts_records([], [{KeyB, B} | RsB], N, Acc) ->
+ jts_records([], RsB, N + 1, [{KeyB, B} | Acc]).
+
+%% Subtract Type2 from Type1. Example:
+%% subtract(list, cons) -> nil
+
+-spec subtract(type(), type()) -> type().
+
+subtract(#t_atom{elements=[_|_]=Set0}, #t_atom{elements=[_|_]=Set1}) ->
+ case ordsets:subtract(Set0, Set1) of
+ [] -> none;
+ [_|_]=Set -> #t_atom{elements=Set}
+ end;
+subtract(#t_bitstring{size_unit=UnitA}=T, #t_bs_matchable{tail_unit=UnitB}) ->
+ subtract_matchable(T, UnitA, UnitB);
+subtract(#t_bitstring{size_unit=UnitA}=T, #t_bitstring{size_unit=UnitB}) ->
+ subtract_matchable(T, UnitA, UnitB);
+subtract(#t_bs_context{tail_unit=UnitA}=T, #t_bs_matchable{tail_unit=UnitB}) ->
+ subtract_matchable(T, UnitA, UnitB);
+subtract(#t_bs_context{tail_unit=UnitA}=T, #t_bs_context{tail_unit=UnitB}) ->
+ subtract_matchable(T, UnitA, UnitB);
+subtract(#t_integer{elements={Min, Max}}, #t_integer{elements={N,N}}) ->
+ if
+ Min =:= N, Max =:= N ->
+ none;
+ Min =/= N, Max =/= N ->
+ #t_integer{elements={Min, Max}};
+ Min =:= N ->
+ #t_integer{elements={Min + 1, Max}};
+ Max =:= N ->
+ #t_integer{elements={Min, Max - 1}}
+ end;
+subtract(number, #t_float{elements=any}) -> #t_integer{};
+subtract(number, #t_integer{elements=any}) -> #t_float{};
+
+%% A list is essentially `#t_cons{} | nil`, so we're left with nil if we
+%% subtract a cons cell that is more general than the one in the list.
+subtract(#t_list{type=TypeA,terminator=TermA}=T,
+ #t_cons{type=TypeB,terminator=TermB}) ->
+ case {meet(TypeA, TypeB), meet(TermA, TermB)} of
+ {TypeA, TermA} -> nil;
+ _ -> T
+ end;
+subtract(#t_list{type=Type,terminator=Term}, nil) ->
+ #t_cons{type=Type,terminator=Term};
+
+subtract(#t_union{atom=Atom}=A, #t_atom{}=B)->
+ shrink_union(A#t_union{atom=subtract(Atom, B)});
+subtract(#t_union{number=Number}=A, B) when ?IS_NUMBER_TYPE(B) ->
+ shrink_union(A#t_union{number=subtract(Number, B)});
+subtract(#t_union{list=List}=A, B) when ?IS_LIST_TYPE(B) ->
+ shrink_union(A#t_union{list=subtract(List, B)});
+subtract(#t_union{tuple_set=[_|_]=Records0}=A, #t_tuple{}=B) ->
+ %% Filter out all records that are more specific than B.
+ NewSet = case [{Key, T} || {Key, T} <- Records0, meet(T, B) =/= T] of
+ [_|_]=Records -> Records;
+ [] -> none
+ end,
+ shrink_union(A#t_union{tuple_set=NewSet});
+subtract(#t_union{tuple_set=#t_tuple{}=Tuple}=A, #t_tuple{}=B) ->
+ %% Exclude Tuple if it's more specific than B.
+ case meet(Tuple, B) of
+ Tuple -> shrink_union(A#t_union{tuple_set=none});
+ _ -> A
+ end;
+subtract(#t_union{other=Other}=A, B) ->
+ shrink_union(A#t_union{other=subtract(Other, B)});
+
+subtract(A, B) ->
+ %% There's nothing left if A is more specific than B.
+ case meet(A, B) of
+ A -> none;
+ _Other -> A
+ end.
+
+subtract_matchable(T, UnitA, UnitB) ->
+ if
+ UnitA rem UnitB =:= 0 -> none;
+ UnitA rem UnitB =/= 0 -> T
+ end.
+
+%%%
+%%% Type operators
+%%%
+
+-spec get_bs_matchable_unit(type()) -> pos_integer() | error.
+get_bs_matchable_unit(#t_bitstring{size_unit=Unit}) ->
+ Unit;
+get_bs_matchable_unit(#t_bs_context{tail_unit=Unit}) ->
+ Unit;
+get_bs_matchable_unit(#t_bs_matchable{tail_unit=Unit}) ->
+ Unit;
+get_bs_matchable_unit(_) ->
+ error.
+
+-spec is_bs_matchable_type(type()) -> boolean().
+is_bs_matchable_type(Type) ->
+ get_bs_matchable_unit(Type) =/= error.
+
+-spec get_singleton_value(Type) -> Result when
+ Type :: type(),
+ Result :: {ok, term()} | error.
+get_singleton_value(#t_atom{elements=[Atom]}) ->
+ {ok, Atom};
+get_singleton_value(#t_float{elements={Float,Float}}) ->
+ {ok, Float};
+get_singleton_value(#t_integer{elements={Int,Int}}) ->
+ {ok, Int};
+get_singleton_value(#t_map{super_key=none,super_value=none}) ->
+ {ok, #{}};
+get_singleton_value(#t_tuple{exact=true,size=Size,elements=Es}) ->
+ case gsv_elements(Size, Es, []) of
+ Values when is_list(Values) ->
+ {ok, list_to_tuple(Values)};
+ error ->
+ error
+ end;
+get_singleton_value(nil) ->
+ {ok, []};
+get_singleton_value(_) ->
+ error.
+
+gsv_elements(0, _Es, Acc) ->
+ %% The elements were added right-to-left, so it's already in order.
+ Acc;
+gsv_elements(N, Es, Acc) ->
+ ElementType = get_tuple_element(N, Es),
+ case get_singleton_value(ElementType) of
+ {ok, Value} -> gsv_elements(N - 1, Es, [Value | Acc]);
+ error -> error
+ end.
+
+-spec is_singleton_type(type()) -> boolean().
+is_singleton_type(Type) ->
+ get_singleton_value(Type) =/= error.
+
+-spec is_boolean_type(type()) -> boolean().
+is_boolean_type(#t_atom{elements=[F,T]}) ->
+ F =:= false andalso T =:= true;
+is_boolean_type(#t_atom{elements=[B]}) ->
+ is_boolean(B);
+is_boolean_type(#t_union{}=T) ->
+ is_boolean_type(normalize(T));
+is_boolean_type(_) ->
+ false.
+
+-spec set_tuple_element(Index, Type, Elements) -> Elements when
+ Index :: pos_integer(),
+ Type :: type(),
+ Elements :: tuple_elements().
+set_tuple_element(Index, _Type, Es) when Index > ?TUPLE_ELEMENT_LIMIT ->
+ Es;
+set_tuple_element(_Index, none, Es) ->
+ Es;
+set_tuple_element(Index, any, Es) ->
+ maps:remove(Index, Es);
+set_tuple_element(Index, Type, Es) ->
+ Es#{ Index => Type }.
+
+-spec get_tuple_element(Index, Elements) -> type() when
+ Index :: pos_integer(),
+ Elements :: tuple_elements().
+get_tuple_element(Index, Es) ->
+ case Es of
+ #{ Index := T } -> T;
+ #{} -> any
+ end.
+
+-spec normalize(type()) -> normal_type().
+normalize(#t_union{atom=Atom,list=List,number=Number,
+ tuple_set=Tuples,other=Other}) ->
+ A = lub(Atom, List),
+ B = lub(A, Number),
+ C = lub(B, Other),
+ normalize_tuple_set(Tuples, C);
+normalize(T) ->
+ verified_normal_type(T).
+
+normalize_tuple_set([{_, A} | Records], B) ->
+ normalize_tuple_set(Records, lub(A, B));
+normalize_tuple_set([], B) ->
+ B;
+normalize_tuple_set(A, B) ->
+ lub(A, B).
+
+%%%
+%%% Type constructors
+%%%
+
+-spec make_type_from_value(term()) -> type().
+make_type_from_value(Value) ->
+ mtfv_1(Value).
+
+mtfv_1(A) when is_atom(A) ->
+ #t_atom{elements=[A]};
+mtfv_1(B) when is_bitstring(B) ->
+ case bit_size(B) of
+ 0 ->
+ %% This is a bit of a hack, but saying that empty binaries have a
+ %% unit of 8 helps us get rid of is_binary/1 checks.
+ #t_bitstring{size_unit=8};
+ Size ->
+ #t_bitstring{size_unit=Size}
+ end;
+mtfv_1(F) when is_float(F) ->
+ make_float(F);
+mtfv_1(F) when is_function(F) ->
+ {arity, Arity} = erlang:fun_info(F, arity),
+ #t_fun{arity=Arity};
+mtfv_1(I) when is_integer(I) ->
+ make_integer(I);
+mtfv_1(L) when is_list(L) ->
+ case L of
+ [_|_] -> mtfv_cons(L, none);
+ [] -> nil
+ end;
+mtfv_1(M) when is_map(M) ->
+ {SKey, SValue} =
+ maps:fold(fun(Key, Value, {SKey0, SValue0}) ->
+ SKey = join(mtfv_1(Key), SKey0),
+ SValue = join(mtfv_1(Value), SValue0),
+ {SKey, SValue}
+ end, {none, none}, M),
+ #t_map{super_key=SKey,super_value=SValue};
+mtfv_1(T) when is_tuple(T) ->
+ {Es,_} = foldl(fun(Val, {Es0, Index}) ->
+ Type = mtfv_1(Val),
+ Es = set_tuple_element(Index, Type, Es0),
+ {Es, Index + 1}
+ end, {#{}, 1}, tuple_to_list(T)),
+ #t_tuple{exact=true,size=tuple_size(T),elements=Es};
+mtfv_1(_Term) ->
+ any.
+
+mtfv_cons([Head | Tail], Type) ->
+ mtfv_cons(Tail, join(mtfv_1(Head), Type));
+mtfv_cons(Terminator, Type) ->
+ #t_cons{type=Type,terminator=mtfv_1(Terminator)}.
+
+-spec make_atom(atom()) -> type().
+make_atom(Atom) when is_atom(Atom) ->
+ #t_atom{elements=[Atom]}.
+
+-spec make_boolean() -> type().
+make_boolean() ->
+ #t_atom{elements=[false,true]}.
+
+-spec make_cons(type(), type()) -> type().
+make_cons(Head0, Tail) ->
+ case meet(Tail, #t_cons{}) of
+ #t_cons{type=Type0,terminator=Term0} ->
+ %% Propagate element and terminator types. Note that if the tail is
+ %% the union of a list and something else, the new list could be
+ %% terminated by the other types in the union.
+ Type = join(Head0, Type0),
+ Term = join(subtract(Tail, #t_cons{}), Term0),
+ #t_cons{type=Type,terminator=Term};
+ _ ->
+ %% Tail can't be a cons cell, so we know it terminates the list.
+ #t_cons{type=Head0,terminator=Tail}
+ end.
+
+-spec make_float(float()) -> type().
+make_float(Float) when is_float(Float) ->
+ make_float(Float, Float).
+
+-spec make_float(float(), float()) -> type().
+make_float(Min, Max) when is_float(Min), is_float(Max), Min =< Max ->
+ #t_float{elements={Min, Max}}.
+
+-spec make_integer(integer()) -> type().
+make_integer(Int) when is_integer(Int) ->
+ make_integer(Int, Int).
+
+-spec make_integer(Min, Max) -> type() when
+ Min :: integer(),
+ Max :: integer().
+make_integer(Min, Max) when is_integer(Min), is_integer(Max), Min =< Max ->
+ #t_integer{elements={Min,Max}}.
+
+-spec limit_depth(type()) -> type().
+
+limit_depth(Type) ->
+ limit_depth(Type, ?MAX_TYPE_DEPTH).
+
+limit_depth(#t_cons{}=T, Depth) ->
+ limit_depth_list(T, Depth);
+limit_depth(#t_list{}=T, Depth) ->
+ limit_depth_list(T, Depth);
+limit_depth(#t_tuple{}=T, Depth) ->
+ limit_depth_tuple(T, Depth);
+limit_depth(#t_fun{}=T, Depth) ->
+ limit_depth_fun(T, Depth);
+limit_depth(#t_map{}=T, Depth) ->
+ limit_depth_map(T, Depth);
+limit_depth(#t_union{list=List0,tuple_set=TupleSet0,other=Other0}=U, Depth) ->
+ TupleSet = limit_depth_tuple(TupleSet0, Depth),
+ List = limit_depth_list(List0, Depth),
+ Other = limit_depth(Other0, Depth),
+ shrink_union(U#t_union{list=List,tuple_set=TupleSet,other=Other});
+limit_depth(Type, _Depth) ->
+ Type.
+
+limit_depth_fun(#t_fun{type=Type0}=T, Depth) ->
+ Type = if
+ Depth > 0 -> limit_depth(Type0, Depth - 1);
+ Depth =< 0 -> any
+ end,
+ T#t_fun{type=Type}.
+
+limit_depth_list(#t_cons{type=Type0,terminator=Term0}=T, Depth) ->
+ {Type, Term} = limit_depth_list_1(Type0, Term0, Depth),
+ T#t_cons{type=Type,terminator=Term};
+limit_depth_list(#t_list{type=Type0,terminator=Term0}=T, Depth) ->
+ {Type, Term} = limit_depth_list_1(Type0, Term0, Depth),
+ T#t_list{type=Type,terminator=Term};
+limit_depth_list(nil, _Depth) ->
+ nil;
+limit_depth_list(none, _Depth) ->
+ none.
+
+limit_depth_list_1(Type0, Terminator0, Depth) when Depth > 0 ->
+ Type = limit_depth(Type0, Depth - 1),
+ Terminator = limit_depth(Terminator0, Depth - 1),
+ {Type, Terminator};
+limit_depth_list_1(_Type, _Terminator, Depth) when Depth =< 0 ->
+ {any, any}.
+
+limit_depth_map(#t_map{ super_key=SKey0,
+ super_value=SValue0 }, Depth) when Depth > 0 ->
+ SKey = limit_depth(SKey0, Depth - 1),
+ SValue = limit_depth(SValue0, Depth - 1),
+ #t_map{super_key=SKey,super_value=SValue};
+limit_depth_map(#t_map{}, Depth) when Depth =< 0 ->
+ #t_map{}.
+
+limit_depth_tuple(#t_tuple{elements=Es0}=T, Depth) ->
+ if
+ Depth > 0 ->
+ Es = maps:map(fun(_, E) -> limit_depth(E, Depth - 1) end, Es0),
+ T#t_tuple{elements=Es};
+ Depth =< 0 ->
+ #t_tuple{elements=#{}}
+ end;
+limit_depth_tuple([{{MinSize,_},_}|_], Depth) when Depth =< 0 ->
+ %% Preserve the minimum size of the tuple set.
+ #t_tuple{exact=false,size=MinSize};
+limit_depth_tuple([{SzTag,Tuple}|Ts], Depth) ->
+ [{SzTag, limit_depth_tuple(Tuple, Depth)} | limit_depth_tuple(Ts, Depth)];
+limit_depth_tuple([], _Depth) ->
+ [];
+limit_depth_tuple(none, _Depth) ->
+ none.
+
+%%%
+%%% Helpers
+%%%
+
+%% Return the greatest lower bound of the types Type1 and Type2. The GLB is a
+%% more specific type than Type1 and Type2, and is always a normal type.
+%%
+%% glb(#t_integer{elements=any}, #t_integer{elements={0,3}}) ->
+%% #t_integer{elements={0,3}}
+%%
+%% The GLB of two different types result in 'none', which is the bottom
+%% element for our type lattice:
+%%
+%% glb(#t_integer{}, #t_map{}) -> none
+
+-spec glb(normal_type(), normal_type()) -> normal_type().
+
+glb(T, T) ->
+ verified_normal_type(T);
+glb(any, T) ->
+ verified_normal_type(T);
+glb(T, any) ->
+ verified_normal_type(T);
+glb(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
+ case ordsets:intersection(Set1, Set2) of
+ [] ->
+ none;
+ [_|_]=Set ->
+ #t_atom{elements=Set}
+ end;
+glb(#t_atom{elements=[_|_]}=T, #t_atom{elements=any}) ->
+ T;
+glb(#t_atom{elements=any}, #t_atom{elements=[_|_]}=T) ->
+ T;
+glb(#t_bitstring{size_unit=U1}, #t_bitstring{size_unit=U2}) ->
+ #t_bitstring{size_unit=U1 * U2 div gcd(U1, U2)};
+glb(#t_bitstring{size_unit=UnitA}=T, #t_bs_matchable{tail_unit=UnitB}) ->
+ Unit = UnitA * UnitB div gcd(UnitA, UnitB),
+ T#t_bitstring{size_unit=Unit};
+glb(#t_bs_context{tail_unit=UnitA,slots=SlotCountA,valid=ValidSlotsA},
+ #t_bs_context{tail_unit=UnitB,slots=SlotCountB,valid=ValidSlotsB}) ->
+ CommonSlotMask = (1 bsl min(SlotCountA, SlotCountB)) - 1,
+ CommonSlotsA = ValidSlotsA band CommonSlotMask,
+ CommonSlotsB = ValidSlotsB band CommonSlotMask,
+ Unit = UnitA * UnitB div gcd(UnitA, UnitB),
+ if
+ CommonSlotsA =:= CommonSlotsB ->
+ #t_bs_context{tail_unit=Unit,
+ slots=max(SlotCountA, SlotCountB),
+ valid=ValidSlotsA bor ValidSlotsB};
+ CommonSlotsA =/= CommonSlotsB ->
+ none
+ end;
+glb(#t_bs_context{tail_unit=UnitA}=T, #t_bs_matchable{tail_unit=UnitB}) ->
+ Unit = UnitA * UnitB div gcd(UnitA, UnitB),
+ T#t_bs_context{tail_unit=Unit};
+glb(#t_bs_matchable{tail_unit=UnitA}, #t_bs_matchable{tail_unit=UnitB}) ->
+ Unit = UnitA * UnitB div gcd(UnitA, UnitB),
+ #t_bs_matchable{tail_unit=Unit};
+glb(#t_bs_matchable{tail_unit=UnitA}, #t_bitstring{size_unit=UnitB}=T) ->
+ Unit = UnitA * UnitB div gcd(UnitA, UnitB),
+ T#t_bitstring{size_unit=Unit};
+glb(#t_bs_matchable{tail_unit=UnitA}, #t_bs_context{tail_unit=UnitB}=T) ->
+ Unit = UnitA * UnitB div gcd(UnitA, UnitB),
+ T#t_bs_context{tail_unit=Unit};
+glb(#t_cons{type=TypeA,terminator=TermA},
+ #t_cons{type=TypeB,terminator=TermB}) ->
+ %% Note the use of meet/2; elements don't need to be normal types.
+ case {meet(TypeA, TypeB), meet(TermA, TermB)} of
+ {none, _} -> none;
+ {_, none} -> none;
+ {Type, Term} -> #t_cons{type=Type,terminator=Term}
+ end;
+glb(#t_cons{type=TypeA,terminator=TermA},
+ #t_list{type=TypeB,terminator=TermB}) ->
+ case {meet(TypeA, TypeB), meet(TermA, TermB)} of
+ {none, _} -> none;
+ {_, none} -> none;
+ {Type, Term} -> #t_cons{type=Type,terminator=Term}
+ end;
+glb(#t_float{}=T, #t_float{elements=any}) ->
+ T;
+glb(#t_float{elements=any}, #t_float{}=T) ->
+ T;
+glb(#t_float{elements={MinA,MaxA}}, #t_float{elements={MinB,MaxB}})
+ when MinA >= MinB, MinA =< MaxB;
+ MinB >= MinA, MinB =< MaxA ->
+ true = MinA =< MaxA andalso MinB =< MaxB, %Assertion.
+ #t_float{elements={max(MinA, MinB),min(MaxA, MaxB)}};
+glb(#t_fun{arity=Same,type=TypeA}, #t_fun{arity=Same,type=TypeB}=T) ->
+ T#t_fun{type=meet(TypeA, TypeB)};
+glb(#t_fun{arity=any,type=TypeA}, #t_fun{type=TypeB}=T) ->
+ T#t_fun{type=meet(TypeA, TypeB)};
+glb(#t_fun{type=TypeA}=T, #t_fun{arity=any,type=TypeB}) ->
+ T#t_fun{type=meet(TypeA, TypeB)};
+glb(#t_integer{elements={_,_}}=T, #t_integer{elements=any}) ->
+ T;
+glb(#t_integer{elements=any}, #t_integer{elements={_,_}}=T) ->
+ T;
+glb(#t_integer{elements={MinA,MaxA}}, #t_integer{elements={MinB,MaxB}})
+ when MinA >= MinB, MinA =< MaxB;
+ MinB >= MinA, MinB =< MaxA ->
+ true = MinA =< MaxA andalso MinB =< MaxB, %Assertion.
+ #t_integer{elements={max(MinA, MinB),min(MaxA, MaxB)}};
+glb(#t_integer{}=T, number) ->
+ T;
+glb(#t_float{}=T, number) ->
+ T;
+glb(#t_list{type=TypeA,terminator=TermA},
+ #t_list{type=TypeB,terminator=TermB}) ->
+ %% A list is a union of `[type() | _]` and `[]`, so we're left with
+ %% nil when the element types are incompatible.
+ case {meet(TypeA, TypeB), meet(TermA, TermB)} of
+ {none, _} -> nil;
+ {_, none} -> nil;
+ {Type, Term} -> #t_list{type=Type,terminator=Term}
+ end;
+glb(#t_list{}=A, #t_cons{}=B) ->
+ glb(B, A);
+glb(#t_list{}, nil) ->
+ nil;
+glb(nil, #t_list{}) ->
+ nil;
+glb(number, #t_integer{}=T) ->
+ T;
+glb(number, #t_float{}=T) ->
+ T;
+glb(#t_map{super_key=SKeyA,super_value=SValueA},
+ #t_map{super_key=SKeyB,super_value=SValueB}) ->
+ %% Note the use of meet/2; elements don't need to be normal types.
+ SKey = meet(SKeyA, SKeyB),
+ SValue = meet(SValueA, SValueB),
+ #t_map{super_key=SKey,super_value=SValue};
+glb(#t_tuple{}=T1, #t_tuple{}=T2) ->
+ glb_tuples(T1, T2);
+glb(_, _) ->
+ %% Inconsistent types. There will be an exception at runtime.
+ none.
+
+glb_tuples(#t_tuple{size=Sz1,exact=Ex1}, #t_tuple{size=Sz2,exact=Ex2})
+ when Ex1, Sz1 < Sz2;
+ Ex2, Sz2 < Sz1 ->
+ none;
+glb_tuples(#t_tuple{size=Sz1,exact=Ex1,elements=Es1},
+ #t_tuple{size=Sz2,exact=Ex2,elements=Es2}) ->
+ Size = max(Sz1, Sz2),
+ Exact = Ex1 or Ex2,
+ case glb_elements(Es1, Es2) of
+ none ->
+ none;
+ Es ->
+ #t_tuple{size=Size,exact=Exact,elements=Es}
+ end.
+
+glb_elements(Es1, Es2) ->
+ Keys = maps:keys(Es1) ++ maps:keys(Es2),
+ glb_elements_1(Keys, Es1, Es2, #{}).
+
+glb_elements_1([Key | Keys], Es1, Es2, Acc) ->
+ case {Es1, Es2} of
+ {#{ Key := Type1 }, #{ Key := Type2 }} ->
+ %% Note the use of meet/2; elements don't need to be normal types.
+ case meet(Type1, Type2) of
+ none -> none;
+ Type -> glb_elements_1(Keys, Es1, Es2, Acc#{ Key => Type })
+ end;
+ {#{ Key := Type1 }, _} ->
+ glb_elements_1(Keys, Es1, Es2, Acc#{ Key => Type1 });
+ {_, #{ Key := Type2 }} ->
+ glb_elements_1(Keys, Es1, Es2, Acc#{ Key => Type2 })
+ end;
+glb_elements_1([], _Es1, _Es2, Acc) ->
+ Acc.
+
+%% Return the least upper bound of the types Type1 and Type2. The LUB is a more
+%% general type than Type1 and Type2, and is always a normal type.
+%%
+%% For example:
+%%
+%% lub(#t_integer{elements=any}, #t_integer=elements={0,3}}) ->
+%% #t_integer{}
+%%
+%% The LUB for two different types result in 'any' (not a union type!), which
+%% is the top element for our type lattice:
+%%
+%% lub(#t_integer{}, #t_map{}) -> any
+
+-spec lub(normal_type(), normal_type()) -> normal_type().
+
+lub(T, T) ->
+ verified_normal_type(T);
+lub(none, T) ->
+ verified_normal_type(T);
+lub(T, none) ->
+ verified_normal_type(T);
+lub(any, _) ->
+ any;
+lub(_, any) ->
+ any;
+lub(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
+ Set = ordsets:union(Set1, Set2),
+ case ordsets:size(Set) of
+ Size when Size =< ?ATOM_SET_SIZE ->
+ #t_atom{elements=Set};
+ _Size ->
+ #t_atom{elements=any}
+ end;
+lub(#t_atom{elements=any}=T, #t_atom{elements=[_|_]}) -> T;
+lub(#t_atom{elements=[_|_]}, #t_atom{elements=any}=T) -> T;
+lub(#t_bitstring{size_unit=U1}, #t_bitstring{size_unit=U2}) ->
+ #t_bitstring{size_unit=gcd(U1, U2)};
+lub(#t_bitstring{size_unit=U1}, #t_bs_context{tail_unit=U2}) ->
+ #t_bs_matchable{tail_unit=gcd(U1, U2)};
+lub(#t_bitstring{size_unit=UnitA}, #t_bs_matchable{tail_unit=UnitB}) ->
+ lub_bs_matchable(UnitA, UnitB);
+lub(#t_bs_context{tail_unit=UnitA,slots=SlotsA,valid=ValidA},
+ #t_bs_context{tail_unit=UnitB,slots=SlotsB,valid=ValidB}) ->
+ #t_bs_context{tail_unit=gcd(UnitA, UnitB),
+ slots=min(SlotsA, SlotsB),
+ valid=ValidA band ValidB};
+lub(#t_bs_context{tail_unit=U1}, #t_bitstring{size_unit=U2}) ->
+ #t_bs_matchable{tail_unit=gcd(U1, U2)};
+lub(#t_bs_context{tail_unit=UnitA}, #t_bs_matchable{tail_unit=UnitB}) ->
+ lub_bs_matchable(UnitA, UnitB);
+lub(#t_bs_matchable{tail_unit=UnitA}, #t_bs_matchable{tail_unit=UnitB}) ->
+ lub_bs_matchable(UnitA, UnitB);
+lub(#t_bs_matchable{tail_unit=UnitA}, #t_bitstring{size_unit=UnitB}) ->
+ lub_bs_matchable(UnitA, UnitB);
+lub(#t_bs_matchable{tail_unit=UnitA}, #t_bs_context{tail_unit=UnitB}) ->
+ lub_bs_matchable(UnitA, UnitB);
+lub(#t_cons{type=TypeA,terminator=TermA},
+ #t_cons{type=TypeB,terminator=TermB}) ->
+ %% Note the use of join/2; elements don't need to be normal types.
+ #t_cons{type=join(TypeA,TypeB),terminator=join(TermA, TermB)};
+lub(#t_cons{type=TypeA,terminator=TermA},
+ #t_list{type=TypeB,terminator=TermB}) ->
+ #t_list{type=join(TypeA,TypeB),terminator=join(TermA, TermB)};
+lub(#t_cons{type=Type,terminator=Term}, nil) ->
+ #t_list{type=Type,terminator=Term};
+lub(#t_float{elements={MinA,MaxA}},
+ #t_float{elements={MinB,MaxB}}) ->
+ #t_float{elements={min(MinA,MinB),max(MaxA,MaxB)}};
+lub(#t_float{}, #t_float{}) ->
+ #t_float{};
+lub(#t_float{}, #t_integer{}) ->
+ number;
+lub(#t_float{}, number) ->
+ number;
+lub(#t_fun{arity=Same,type=TypeA}, #t_fun{arity=Same,type=TypeB}) ->
+ #t_fun{arity=Same,type=join(TypeA, TypeB)};
+lub(#t_fun{type=TypeA}, #t_fun{type=TypeB}) ->
+ #t_fun{type=join(TypeA, TypeB)};
+lub(#t_integer{elements={MinA,MaxA}},
+ #t_integer{elements={MinB,MaxB}}) ->
+ #t_integer{elements={min(MinA,MinB),max(MaxA,MaxB)}};
+lub(#t_integer{}, #t_integer{}) ->
+ #t_integer{};
+lub(#t_integer{}, #t_float{}) ->
+ number;
+lub(#t_integer{}, number) ->
+ number;
+lub(#t_list{type=TypeA,terminator=TermA},
+ #t_list{type=TypeB,terminator=TermB}) ->
+ #t_list{type=join(TypeA, TypeB),terminator=join(TermA, TermB)};
+lub(#t_list{}=A, #t_cons{}=B) ->
+ lub(B, A);
+lub(nil=A, #t_cons{}=B) ->
+ lub(B, A);
+lub(nil, #t_list{}=T) ->
+ T;
+lub(#t_list{}=T, nil) ->
+ T;
+lub(number, #t_integer{}) ->
+ number;
+lub(number, #t_float{}) ->
+ number;
+lub(#t_map{super_key=SKeyA,super_value=SValueA},
+ #t_map{super_key=SKeyB,super_value=SValueB}) ->
+ %% Note the use of join/2; elements don't need to be normal types.
+ SKey = join(SKeyA, SKeyB),
+ SValue = join(SValueA, SValueB),
+ #t_map{super_key=SKey,super_value=SValue};
+lub(#t_tuple{size=Sz,exact=ExactA,elements=EsA},
+ #t_tuple{size=Sz,exact=ExactB,elements=EsB}) ->
+ Exact = ExactA and ExactB,
+ Es = lub_tuple_elements(Sz, EsA, EsB),
+ #t_tuple{size=Sz,exact=Exact,elements=Es};
+lub(#t_tuple{size=SzA,elements=EsA}, #t_tuple{size=SzB,elements=EsB}) ->
+ Sz = min(SzA, SzB),
+ Es = lub_tuple_elements(Sz, EsA, EsB),
+ #t_tuple{size=Sz,elements=Es};
+lub(_T1, _T2) ->
+ %%io:format("~p ~p\n", [_T1,_T2]),
+ any.
+
+lub_bs_matchable(UnitA, UnitB) ->
+ #t_bs_matchable{tail_unit=gcd(UnitA, UnitB)}.
+
+lub_tuple_elements(MinSize, EsA, EsB) ->
+ Es0 = lub_elements(EsA, EsB),
+ maps:filter(fun(Index, _Type) -> Index =< MinSize end, Es0).
+
+lub_elements(Es1, Es2) ->
+ Keys = if
+ map_size(Es1) =< map_size(Es2) -> maps:keys(Es1);
+ map_size(Es1) > map_size(Es2) -> maps:keys(Es2)
+ end,
+ lub_elements_1(Keys, Es1, Es2, #{}).
+
+lub_elements_1([Key | Keys], Es1, Es2, Acc0) ->
+ case {Es1, Es2} of
+ {#{ Key := Type1 }, #{ Key := Type2 }} ->
+ %% Note the use of join/2; elements don't need to be normal types.
+ Acc = set_tuple_element(Key, join(Type1, Type2), Acc0),
+ lub_elements_1(Keys, Es1, Es2, Acc);
+ {#{}, #{}} ->
+ lub_elements_1(Keys, Es1, Es2, Acc0)
+ end;
+lub_elements_1([], _Es1, _Es2, Acc) ->
+ Acc.
+
+%%
+
+gcd(A, B) ->
+ case A rem B of
+ 0 -> B;
+ X -> gcd(B, X)
+ end.
+
+%%
+
+record_key(#t_tuple{exact=true,size=Size,elements=#{ 1 := Tag }}) ->
+ case is_singleton_type(Tag) of
+ true -> {Size, Tag};
+ false -> none
+ end;
+record_key(_) ->
+ none.
+
+new_tuple_set(T) ->
+ case record_key(T) of
+ none -> T;
+ Key -> [{Key, T}]
+ end.
+
+%%
+
+shrink_union(#t_union{other=any}) ->
+ any;
+shrink_union(#t_union{atom=Atom,list=none,number=none,
+ tuple_set=none,other=none}) ->
+ Atom;
+shrink_union(#t_union{atom=none,list=List,number=none,
+ tuple_set=none,other=none}) ->
+ List;
+shrink_union(#t_union{atom=none,list=none,number=Number,
+ tuple_set=none,other=none}) ->
+ Number;
+shrink_union(#t_union{atom=none,list=none,number=none,
+ tuple_set=#t_tuple{}=Tuple,other=none}) ->
+ Tuple;
+shrink_union(#t_union{atom=none,list=none,number=none,
+ tuple_set=[{_Key, Record}],other=none}) ->
+ #t_tuple{} = Record; %Assertion.
+shrink_union(#t_union{atom=none,list=none,number=none,
+ tuple_set=none,other=Other}) ->
+ Other;
+shrink_union(#t_union{}=T) ->
+ T.
+
+%% Verifies that the given type is well-formed.
+
+-spec verified_type(T) -> T when
+ T :: type().
+
+verified_type(#t_union{atom=Atom,
+ list=List,
+ number=Number,
+ tuple_set=TSet,
+ other=Other}=T) ->
+ _ = verified_normal_type(Atom),
+ _ = verified_normal_type(List),
+ _ = verified_normal_type(Number),
+ _ = verify_tuple_set(TSet),
+ _ = verified_normal_type(Other),
+ T;
+verified_type(T) ->
+ verified_normal_type(T).
+
+verify_tuple_set([_|_]=T) ->
+ _ = verify_tuple_set_1(T, 0),
+ T;
+verify_tuple_set(#t_tuple{}=T) ->
+ none = record_key(T), %Assertion.
+ T;
+verify_tuple_set(none=T) ->
+ T.
+
+verify_tuple_set_1([{_Tag, Record} | Records], Size) ->
+ true = Size =< ?TUPLE_SET_LIMIT, %Assertion.
+ _ = verified_normal_type(Record),
+ verify_tuple_set_1(Records, Size + 1);
+verify_tuple_set_1([], _Size) ->
+ ok.
+
+-spec verified_normal_type(T) -> T when
+ T :: normal_type().
+
+verified_normal_type(any=T) -> T;
+verified_normal_type(none=T) -> T;
+verified_normal_type(#t_atom{elements=any}=T) -> T;
+verified_normal_type(#t_atom{elements=[_|_]}=T) -> T;
+verified_normal_type(#t_bitstring{size_unit=U}=T)
+ when is_integer(U), U >= 1 ->
+ T;
+verified_normal_type(#t_bs_context{tail_unit=U}=T)
+ when is_integer(U), U >= 1 ->
+ T;
+verified_normal_type(#t_bs_matchable{tail_unit=U}=T)
+ when is_integer(U), U >= 1 ->
+ T;
+verified_normal_type(#t_cons{type=Type,terminator=Term}=T) ->
+ _ = verified_type(Type),
+ _ = verified_type(Term),
+ T;
+verified_normal_type(#t_fun{arity=Arity,type=ReturnType}=T)
+ when Arity =:= any; is_integer(Arity) ->
+ _ = verified_type(ReturnType),
+ T;
+verified_normal_type(#t_float{}=T) -> T;
+verified_normal_type(#t_integer{elements=any}=T) -> T;
+verified_normal_type(#t_integer{elements={Min,Max}}=T)
+ when is_integer(Min), is_integer(Max), Min =< Max ->
+ T;
+verified_normal_type(#t_list{type=Type,terminator=Term}=T) ->
+ _ = verified_type(Type),
+ _ = verified_type(Term),
+ T;
+verified_normal_type(#t_map{}=T) -> T;
+verified_normal_type(nil=T) -> T;
+verified_normal_type(number=T) -> T;
+verified_normal_type(#t_tuple{size=Size,elements=Es}=T) ->
+ %% All known elements must have a valid index and type (which may be a
+ %% union). 'any' is prohibited since it's implicit and should never be
+ %% present in the map, and a 'none' element ought to have reduced the
+ %% entire tuple to 'none'.
+ maps:fold(fun(Index, Element, _) when is_integer(Index),
+ 1 =< Index, Index =< Size,
+ Index =< ?TUPLE_ELEMENT_LIMIT,
+ Element =/= any, Element =/= none ->
+ verified_type(Element)
+ end, [], Es),
+ T.
diff --git a/lib/compiler/src/beam_types.hrl b/lib/compiler/src/beam_types.hrl
new file mode 100644
index 0000000000..c20e1ce7a0
--- /dev/null
+++ b/lib/compiler/src/beam_types.hrl
@@ -0,0 +1,154 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% Common term types for passes operating on beam SSA and assembly. Helper
+%% functions for wrangling these can be found in beam_types.erl
+%%
+%% The type lattice is as follows:
+%%
+%% any Any Erlang term (top element).
+%%
+%% - #t_atom{} Atom, or a set thereof.
+%% - #t_bs_matchable{} Binary-matchable types.
+%% - #t_bitstring{} Bitstring.
+%% - #t_bs_context{} Match context.
+%% - #t_fun{} Fun.
+%% - #t_map{} Map.
+%% - number Any number.
+%% -- #t_float{} Floating point number.
+%% -- #t_integer{} Integer.
+%% - #t_list{} Any list.
+%% -- #t_cons{} Cons (nonempty list).
+%% -- nil The empty list.
+%% - #t_tuple{} Tuple.
+%%
+%% none No type (bottom element).
+%%
+%% We also use #t_union{} to represent conflicting types produced by certain
+%% expressions, e.g. the "#t_atom{} or #t_tuple{}" of lists:keyfind/3, which is
+%% very useful for preserving type information when we would otherwise have
+%% reduced it to 'any'. Since few operations can make direct use of this extra
+%% type information, types should generally be normalized to one of the above
+%% before use.
+%%
+%% When adding a new type it's important that the lattice stays consistent [1].
+%% In brief, the following properties must hold:
+%%
+%% * All types must be unambiguous; any given value must narrow down to a
+%% single type, and multiple supertypes are not allowed.
+%%
+%% * `meet` is used when we know more about a value (e.g. type tests), so it
+%% must not return a more general type than either of its arguments. In other
+%% words, we're only allowed to *add* knowledge in a `meet`.
+%%
+%% * `join` is used when we know less about a value (e.g. phi node), so it
+%% must not return a more specific type than either of its arguments. In
+%% other words we're only allowed to *remove* knowledge in a `join`.
+%%
+%% * Both `join` and `meet` must be commutative, associative, and idempotent.
+%%
+%% Maintaining the above may seem trivial but subtle errors can creep in when
+%% adding fields or restrictions to a type. ?TUPLE_ELEMENT_LIMIT is a great
+%% example of this.
+%%
+%% The property test suite ensures that the above holds, so don't forget to
+%% add your new types there. You should also consider increasing ?REPETITIONS
+%% during development to ensure it hits all nooks and crannies.
+%%
+%% [1] https://en.wikipedia.org/wiki/Lattice_(order)#General_lattice
+
+-define(ATOM_SET_SIZE, 5).
+
+-record(t_atom, {elements=any :: 'any' | ordsets:ordset(atom())}).
+-record(t_bitstring, {size_unit=1 :: pos_integer()}).
+-record(t_bs_context, {tail_unit=1 :: pos_integer(),
+ slots=0 :: non_neg_integer(),
+ valid=0 :: non_neg_integer()}).
+-record(t_bs_matchable, {tail_unit=1}).
+-record(t_float, {elements=any :: 'any' | {float(),float()}}).
+-record(t_fun, {arity=any :: arity() | 'any',
+ type=any :: type() }).
+-record(t_integer, {elements=any :: 'any' | {integer(),integer()}}).
+
+%% `super_key` and `super_value` are the join of all key and value types.
+%%
+%% Note that we don't track specific elements as we have no obvious way to
+%% limit them. See ?TUPLE_ELEMENT_LIMIT for details.
+-record(t_map, {super_key=any :: type(),
+ super_value=any :: type()}).
+
+%% `type` is the join of all list elements, and `terminator` is the tail of the
+%% last cons cell ('nil' for proper lists).
+%%
+%% Note that `type` may not be updated unless the entire list is known, and
+%% that the terminator being known is not a guarantee that the rest of the list
+%% is.
+-record(t_cons, {type=any :: type(), terminator=any :: type()}).
+-record(t_list, {type=any :: type(), terminator=any :: type()}).
+
+-record(t_tuple, {size=0 :: integer(),
+ exact=false :: boolean(),
+ elements=#{} :: tuple_elements()}).
+
+%% Known element types, where the key is a 1-based integer index. Unknown
+%% elements are assumed to be 'any', and indexes above ?TUPLE_ELEMENT_LIMIT are
+%% ignored for performance reasons.
+%%
+%% Cutting off all indexes above a certain limit may seem strange, but is
+%% required to ensure that a meet of two types always returns a type that's at
+%% least as specific as either type. Consider the following types:
+%%
+%% A = #t_tuple{elements=#{ ... elements 1 .. 6 ... }}
+%% B = #t_tuple{elements=#{ ... elements 7 .. 13 ... }}
+%%
+%% If we'd collapse types once a tuple has more than 12 elements, meet(A, B)
+%% would suddenly be less specific than either A or B. Ignoring all elements
+%% above a certain index avoids this problem, at the small price of losing type
+%% information in huge tuples.
+
+-define(TUPLE_ELEMENT_LIMIT, 12).
+-type tuple_elements() :: #{ Key :: pos_integer() => type() }.
+
+-type normal_type() :: any | none |
+ number | #t_float{} | #t_integer{} |
+ #t_atom{} |
+ #t_bitstring{} | #t_bs_context{} | #t_bs_matchable{} |
+ #t_fun{} |
+ #t_list{} | #t_cons{} | nil |
+ #t_map{} |
+ #t_tuple{}.
+
+-type record_key() :: {Arity :: integer(), Tag :: normal_type() }.
+-type record_set() :: ordsets:ordset({record_key(), #t_tuple{}}).
+-type tuple_set() :: #t_tuple{} | record_set().
+
+-record(t_union, {atom=none :: none | #t_atom{},
+ list=none :: none | #t_list{} | #t_cons{} | nil,
+ number=none :: none | number | #t_float{} | #t_integer{},
+ tuple_set=none :: none | tuple_set(),
+ other=none :: normal_type()}).
+
+-type type() :: #t_union{} | normal_type().
+
+-ifdef(BEAM_TYPES_INTERNAL).
+%% Internal constants used by beam_types.erl and its whitebox tests
+-define(TUPLE_SET_LIMIT, 12).
+-define(MAX_TYPE_DEPTH, 4).
+-endif.
diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl
index 6e6574c0b3..9bf18911c5 100644
--- a/lib/compiler/src/beam_utils.erl
+++ b/lib/compiler/src/beam_utils.erl
@@ -88,11 +88,12 @@ split_even(Rs) -> split_even(Rs, [], []).
%%%
%%% Local functions.
%%%
-
replace_labels_1([{test,Test,{f,Lbl},Ops}|Is], Acc, D, Fb) ->
- replace_labels_1(Is, [{test,Test,{f,label(Lbl, D, Fb)},Ops}|Acc], D, Fb);
+ I = {test,Test,{f,label(Lbl, D, Fb)},Ops},
+ replace_labels_1(Is, [I | Acc], D, Fb);
replace_labels_1([{test,Test,{f,Lbl},Live,Ops,Dst}|Is], Acc, D, Fb) ->
- replace_labels_1(Is, [{test,Test,{f,label(Lbl, D, Fb)},Live,Ops,Dst}|Acc], D, Fb);
+ I = {test,Test,{f,label(Lbl, D, Fb)},Live,Ops,Dst},
+ replace_labels_1(Is, [I | Acc], D, Fb);
replace_labels_1([{select,I,R,{f,Fail0},Vls0}|Is], Acc, D, Fb) ->
Vls = map(fun ({f,L}) -> {f,label(L, D, Fb)};
(Other) -> Other
@@ -134,6 +135,9 @@ replace_labels_1([{put_map=I,{f,Lbl},Op,Src,Dst,Live,List}|Is], Acc, D, Fb)
replace_labels_1(Is, [{I,{f,label(Lbl, D, Fb)},Op,Src,Dst,Live,List}|Acc], D, Fb);
replace_labels_1([{get_map_elements=I,{f,Lbl},Src,List}|Is], Acc, D, Fb) when Lbl =/= 0 ->
replace_labels_1(Is, [{I,{f,label(Lbl, D, Fb)},Src,List}|Acc], D, Fb);
+replace_labels_1([{bs_start_match4,{f,Lbl},Live,Src,Dst}|Is], Acc, D, Fb) ->
+ I = {bs_start_match4,{f,label(Lbl, D, Fb)},Live,Src,Dst},
+ replace_labels_1(Is, [I | Acc], D, Fb);
replace_labels_1([I|Is], Acc, D, Fb) ->
replace_labels_1(Is, [I|Acc], D, Fb);
replace_labels_1([], Acc, _, _) -> Acc.
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index b5617ba524..055debe620 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -19,58 +19,46 @@
-module(beam_validator).
+-include("beam_types.hrl").
+
+-define(UNICODE_MAX, (16#10FFFF)).
+
+%% When an instruction throws an exception it does so by branching (branch/4)
+%% to this label, following the {f,0} convention used in BIFs.
+%%
+%% Instructions that have fail labels but cannot throw exceptions should guard
+%% themselves with assert_no_exception/1, and instructions that are statically
+%% guaranteed not to fail should simply skip branching.
+-define(EXCEPTION_LABEL, 0).
+
-compile({no_auto_import,[min/2]}).
%% Avoid warning for local function error/1 clashing with autoimported BIF.
-compile({no_auto_import,[error/1]}).
%% Interface for compiler.
--export([module/2, format_error/1]).
--export([type_anno/1, type_anno/2, type_anno/4]).
+-export([validate/2, format_error/1]).
--import(lists, [dropwhile/2,foldl/3,member/2,reverse/1,sort/1,zip/2]).
+-import(lists, [dropwhile/2,foldl/3,member/2,reverse/2,zip/2]).
%% To be called by the compiler.
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs,Lc}=Code, _Opts)
- when is_atom(Mod), is_list(Exp), is_list(Attr), is_integer(Lc) ->
- case validate(Mod, Fs) of
- [] ->
- {ok,Code};
- Es0 ->
- Es = [{?MODULE,E} || E <- Es0],
- {error,[{atom_to_list(Mod),Es}]}
- end.
-
-%% Provides a stable interface for type annotations, used by certain passes to
-%% indicate that we can safely assume that a register has a given type.
--spec type_anno(term()) -> term().
-type_anno(atom) -> {atom,[]};
-type_anno(bool) -> bool;
-type_anno({binary,_}) -> binary;
-type_anno(cons) -> cons;
-type_anno(float) -> {float,[]};
-type_anno(integer) -> {integer,[]};
-type_anno(list) -> list;
-type_anno(map) -> map;
-type_anno(match_context) -> match_context;
-type_anno(number) -> number;
-type_anno(nil) -> nil.
-
--spec type_anno(term(), term()) -> term().
-type_anno(atom, Value) when is_atom(Value) -> {atom, Value};
-type_anno(float, Value) when is_float(Value) -> {float, Value};
-type_anno(integer, Value) when is_integer(Value) -> {integer, Value}.
-
--spec type_anno(term(), term(), term(), term()) -> term().
-type_anno(tuple, Size, Exact, Elements) when is_integer(Size), Size >= 0,
- is_map(Elements) ->
- case Exact of
- true -> {tuple, Size, Elements};
- false -> {tuple, [Size], Elements}
+-spec validate(Code, Level) -> Result when
+ Code :: beam_utils:module_code(),
+ Level :: strong | weak,
+ Result :: ok | {error, [{atom(), list()}]}.
+
+validate({Mod,Exp,Attr,Fs,Lc}, Level) when is_atom(Mod),
+ is_list(Exp),
+ is_list(Attr),
+ is_integer(Lc) ->
+ Ft = build_function_table(Fs, #{}),
+ case validate_0(Fs, Mod, Level, Ft) of
+ [] ->
+ ok;
+ Es0 ->
+ Es = [{?MODULE,E} || E <- Es0],
+ {error,[{atom_to_list(Mod),Es}]}
end.
-spec format_error(term()) -> iolist().
@@ -114,30 +102,37 @@ format_error(Error) ->
%%% - Heap allocation for binaries.
%%%
-%% validate(Module, [Function]) -> [] | [Error]
+%% validate(Module, Level, [Function], Ft) -> [] | [Error]
%% A list of functions with their code. The code is in the same
%% format as used in the compiler and in .S files.
-validate(Module, Fs) ->
- Ft = index_parameter_types(Fs, []),
- validate_0(Module, Fs, Ft).
-validate_0(_Module, [], _) -> [];
-validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
- try validate_1(Code, Name, Ar, Entry, Ft) of
- _ -> validate_0(Module, Fs, Ft)
+validate_0([], _Module, _Level, _Ft) ->
+ [];
+validate_0([{function, Name, Arity, Entry, Code} | Fs], Module, Level, Ft) ->
+ MFA = {Module, Name, Arity},
+ try validate_1(Code, MFA, Entry, Level, Ft) of
+ _ ->
+ validate_0(Fs, Module, Level, Ft)
catch
- throw:Error ->
- %% Controlled error.
- [Error|validate_0(Module, Fs, Ft)];
+ throw:Error ->
+ %% Controlled error.
+ [Error | validate_0(Fs, Module, Level, Ft)];
Class:Error:Stack ->
- %% Crash.
- io:fwrite("Function: ~w/~w\n", [Name,Ar]),
- erlang:raise(Class, Error, Stack)
+ %% Crash.
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
end.
+-record(t_abstract, {kind}).
+
+%% The types are the same as in 'beam_types.hrl', with the addition of
+%% #t_abstract{} that describes tuples under construction, match context
+%% positions, and so on.
+-type validator_type() :: #t_abstract{} | type().
+
-record(value_ref, {id :: index()}).
--record(value, {op :: term(), args :: [argument()], type :: type()}).
+-record(value, {op :: term(), args :: [argument()], type :: validator_type()}).
-type argument() :: #value_ref{} | literal().
@@ -149,34 +144,28 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
{literal, term()} |
nil.
--type tuple_sz() :: [non_neg_integer()] | %% Inexact
- non_neg_integer(). %% Exact.
-
-%% Match context type.
--record(ms,
- {id=make_ref() :: reference(), %Unique ID.
- valid=0 :: non_neg_integer(), %Valid slots
- slots=0 :: non_neg_integer() %Number of slots
- }).
-
--type type() :: binary |
- cons |
- list |
- map |
- nil |
- #ms{} |
- ms_position |
- none |
- number |
- term |
- tuple_in_progress |
- {tuple, tuple_sz(), #{ literal() => type() }} |
- literal().
-
+%% Register tags describe the state of the register rather than the value they
+%% contain (if any).
+%%
+%% initialized The register has been initialized with some valid term
+%% so that it is safe to pass to the garbage collector.
+%% NOT safe to use in any other way (will not crash the
+%% emulator, but clearly points to a bug in the compiler).
+%%
+%% uninitialized The register contains any old garbage and can not be
+%% passed to the garbage collector.
+%%
+%% {catchtag,[Lbl]} A special term used within a catch. Must only be used
+%% by the catch instructions; NOT safe to use in other
+%% instructions.
+%%
+%% {trytag,[Lbl]} A special term used within a try block. Must only be
+%% used by the catch instructions; NOT safe to use in other
+%% instructions.
-type tag() :: initialized |
uninitialized |
- {catchtag, [label()]} |
- {trytag, [label()]}.
+ {catchtag, ordsets:ordset(label())} |
+ {trytag, ordsets:ordset(label())}.
-type x_regs() :: #{ {x, index()} => #value_ref{} }.
-type y_regs() :: #{ {y, index()} => tag() | #value_ref{} }.
@@ -200,11 +189,9 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
numy=none :: none | undecided | index(),
%% Available heap size.
h=0,
- %Available heap size for floats.
+ %%Available heap size for floats.
hf=0,
- %% Floating point state.
- fls=undefined,
- %% List of hot catch/try labels
+ %% List of hot catch/try tags
ct=[],
%% Previous instruction was setelement/3.
setelem=false,
@@ -214,240 +201,383 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
%%
%% 'initialized' means we've saved a message position, and 'committed'
%% means the next loop_rec instruction will use it.
- recv_marker=none :: none | undecided | initialized | committed
+ recv_marker=none :: none | undecided | initialized | committed,
+ %% Holds the current saved position for each `#t_bs_context{}`, in the
+ %% sense that the position is equal to that of their context. They are
+ %% invalidated whenever their context advances.
+ %%
+ %% These are used to update the unit of saved positions after
+ %% operations that test the incoming unit, such as bs_test_unit and
+ %% bs_get_binary2 with {atom,all}.
+ ms_positions=#{} :: #{ Ctx :: #value_ref{} => Pos :: #value_ref{} }
}).
-type label() :: integer().
--type label_set() :: gb_sets:set(label()).
--type branched_tab() :: gb_trees:tree(label(), #st{}).
--type ft_tab() :: gb_trees:tree().
+-type state() :: #st{} | 'none'.
%% Validator state
-record(vst,
{%% Current state
- current=none :: #st{} | 'none',
+ current=none :: state(),
+ %% Validation level
+ level :: strong | weak,
%% States at labels
- branched=gb_trees:empty() :: branched_tab(),
+ branched=#{} :: #{ label() => state() },
%% All defined labels
- labels=gb_sets:empty() :: label_set(),
- %% Argument information of other functions in the module
- ft=gb_trees:empty() :: ft_tab(),
+ labels=cerl_sets:new() :: cerl_sets:set(),
+ %% Information of other functions in the module
+ ft=#{} :: #{ label() => map() },
%% Counter for #value_ref{} creation
ref_ctr=0 :: index()
}).
-index_parameter_types([{function,_,_,Entry,Code0}|Fs], Acc0) ->
- Code = dropwhile(fun({label,L}) when L =:= Entry -> false;
- (_) -> true
- end, Code0),
+build_function_table([{function,_,Arity,Entry,Code0}|Fs], Acc) ->
+ Code = dropwhile(fun({label,L}) when L =:= Entry ->
+ false;
+ (_) ->
+ true
+ end, Code0),
case Code of
- [{label,Entry}|Is] ->
- Acc = index_parameter_types_1(Is, Entry, Acc0),
- index_parameter_types(Fs, Acc);
- _ ->
- %% Something serious is wrong. Ignore it for now.
- %% It will be detected and diagnosed later.
- index_parameter_types(Fs, Acc0)
+ [{label,Entry} | Is] ->
+ Info = #{ arity => Arity,
+ parameter_info => find_parameter_info(Is, #{}) },
+ build_function_table(Fs, Acc#{ Entry => Info });
+ _ ->
+ %% Something is seriously wrong. Ignore it for now.
+ %% It will be detected and diagnosed later.
+ build_function_table(Fs, Acc)
end;
-index_parameter_types([], Acc) ->
- gb_trees:from_orddict(sort(Acc)).
-
-index_parameter_types_1([{'%', {type_info, Reg, Type0}} | Is], Entry, Acc) ->
- Type = case Type0 of
- match_context -> #ms{};
- _ -> Type0
- end,
- Key = {Entry, Reg},
- index_parameter_types_1(Is, Entry, [{Key, Type} | Acc]);
-index_parameter_types_1(_, _, Acc) ->
+build_function_table([], Acc) ->
Acc.
-validate_1(Is, Name, Arity, Entry, Ft) ->
- validate_2(labels(Is), Name, Arity, Entry, Ft).
-
-validate_2({Ls1,[{func_info,{atom,Mod},{atom,Name},Arity}=_F|Is]},
- Name, Arity, Entry, Ft) ->
- validate_3(labels(Is), Name, Arity, Entry, Mod, Ls1, Ft);
-validate_2({Ls1,Is}, Name, Arity, _Entry, _Ft) ->
- error({{'_',Name,Arity},{first(Is),length(Ls1),illegal_instruction}}).
+find_parameter_info([{'%', {var_info, Reg, Info}} | Is], Acc) ->
+ find_parameter_info(Is, Acc#{ Reg => Info });
+find_parameter_info([{'%', _} | Is], Acc) ->
+ find_parameter_info(Is, Acc);
+find_parameter_info(_, Acc) ->
+ Acc.
-validate_3({Ls2,Is}, Name, Arity, Entry, Mod, Ls1, Ft) ->
- Offset = 1 + length(Ls1) + 1 + length(Ls2),
- EntryOK = member(Entry, Ls2),
- if
- EntryOK ->
- Vst0 = init_vst(Arity, Ls1, Ls2, Ft),
- MFA = {Mod,Name,Arity},
- Vst = valfun(Is, MFA, Offset, Vst0),
- validate_fun_info_branches(Ls1, MFA, Vst);
- true ->
- error({{Mod,Name,Arity},{first(Is),Offset,no_entry_label}})
- end.
+validate_1(Is, MFA0, Entry, Level, Ft) ->
+ {Offset, MFA, Header, Body} = extract_header(Is, MFA0, Entry, 1, []),
-validate_fun_info_branches([L|Ls], MFA, #vst{branched=Branches}=Vst0) ->
- Vst = Vst0#vst{current=gb_trees:get(L, Branches)},
- validate_fun_info_branches_1(0, MFA, Vst),
- validate_fun_info_branches(Ls, MFA, Vst);
-validate_fun_info_branches([], _, _) -> ok.
-
-validate_fun_info_branches_1(Arity, {_,_,Arity}, _) -> ok;
-validate_fun_info_branches_1(X, {Mod,Name,Arity}=MFA, Vst) ->
- try
- case Vst of
- #vst{current=#st{numy=none}} ->
- ok;
- #vst{current=#st{numy=Size}} ->
- error({unexpected_stack_frame,Size})
- end,
- assert_term({x,X}, Vst)
- catch Error ->
- I = {func_info,{atom,Mod},{atom,Name},Arity},
- Offset = 2,
- error({MFA,{I,Offset,Error}})
- end,
- validate_fun_info_branches_1(X+1, MFA, Vst).
+ Vst0 = init_vst(MFA, Level, Ft),
-first([X|_]) -> X;
-first([]) -> [].
+ %% We validate the header after the body as the latter may jump to the
+ %% former to raise 'function_clause' exceptions.
+ Vst1 = validate_instrs(Body, MFA, Offset, Vst0),
+ Vst = validate_instrs(Header, MFA, 1, Vst1),
-labels(Is) ->
- labels_1(Is, []).
+ validate_branches(MFA, Vst).
-labels_1([{label,L}|Is], R) ->
- labels_1(Is, [L|R]);
-labels_1([{line,_}|Is], R) ->
- labels_1(Is, R);
-labels_1(Is, R) ->
- {reverse(R),Is}.
+extract_header([{func_info, {atom,Mod}, {atom,Name}, Arity}=I | Is],
+ MFA0, Entry, Offset, Acc) ->
+ {_, Name, Arity} = MFA0, %Assertion.
+ MFA = {Mod, Name, Arity},
-init_vst(Arity, Ls1, Ls2, Ft) ->
- Vst0 = init_function_args(Arity - 1, #vst{current=#st{}}),
- Branches = gb_trees_from_list([{L,Vst0#vst.current} || L <- Ls1]),
- Labels = gb_sets:from_list(Ls1++Ls2),
- Vst0#vst{branched=Branches,
- labels=Labels,
- ft=Ft}.
+ case Is of
+ [{label, Entry} | _] ->
+ Header = reverse(Acc, [I]),
+ {Offset + 1, MFA, Header, Is};
+ _ ->
+ error({MFA, no_entry_label})
+ end;
+extract_header([{label,_}=I | Is], MFA, Entry, Offset, Acc) ->
+ extract_header(Is, MFA, Entry, Offset + 1, [I | Acc]);
+extract_header([{line,_}=I | Is], MFA, Entry, Offset, Acc) ->
+ extract_header(Is, MFA, Entry, Offset + 1, [I | Acc]);
+extract_header(_Is, MFA, _Entry, _Offset, _Acc) ->
+ error({MFA, invalid_function_header}).
+
+init_vst({_, _, Arity}, Level, Ft) ->
+ Vst = #vst{branched=#{},
+ current=#st{},
+ ft=Ft,
+ labels=cerl_sets:new(),
+ level=Level},
+ init_function_args(Arity - 1, Vst).
init_function_args(-1, Vst) ->
Vst;
init_function_args(X, Vst) ->
- init_function_args(X - 1, create_term(term, argument, [], {x,X}, Vst)).
+ init_function_args(X - 1, create_term(any, argument, [], {x,X}, Vst)).
-kill_heap_allocation(St) ->
- St#st{h=0,hf=0}.
+kill_heap_allocation(#vst{current=St0}=Vst) ->
+ St = St0#st{h=0,hf=0},
+ Vst#vst{current=St}.
-valfun([], MFA, _Offset, #vst{branched=Targets0,labels=Labels0}=Vst) ->
- Targets = gb_trees:keys(Targets0),
- Labels = gb_sets:to_list(Labels0),
+validate_branches(MFA, Vst) ->
+ #vst{ branched=Targets0, labels=Labels0 } = Vst,
+ Targets = maps:keys(Targets0),
+ Labels = cerl_sets:to_list(Labels0),
case Targets -- Labels of
- [] -> Vst;
- Undef ->
- Error = {undef_labels,Undef},
- error({MFA,Error})
- end;
-valfun([I|Is], MFA, Offset, Vst0) ->
- valfun(Is, MFA, Offset+1,
- try
- Vst = val_dsetel(I, Vst0),
- valfun_1(I, Vst)
- catch Error ->
- error({MFA,{I,Offset,Error}})
- end).
-
-%% Instructions that are allowed in dead code or when failing,
-%% that is while the state is undecided in some way.
-valfun_1({label,Lbl}, #vst{current=St0,
- ref_ctr=Counter0,
- branched=B,
- labels=Lbls}=Vst) ->
- {St, Counter} = merge_states(Lbl, St0, B, Counter0),
+ [_|_]=Undef ->
+ Error = {undef_labels, Undef},
+ error({MFA, Error});
+ [] ->
+ Vst
+ end.
+
+validate_instrs([I|Is], MFA, Offset, Vst0) ->
+ validate_instrs(Is, MFA, Offset+1,
+ try
+ Vst = validate_mutation(I, Vst0),
+ vi(I, Vst)
+ catch Error ->
+ error({MFA, {I, Offset, Error}})
+ end);
+validate_instrs([], _MFA, _Offset, Vst) ->
+ Vst.
+
+vi({label,Lbl}, #vst{current=St0,
+ ref_ctr=Counter0,
+ branched=Branched0,
+ labels=Labels0}=Vst) ->
+ {St, Counter} = merge_states(Lbl, St0, Branched0, Counter0),
+
+ Branched = Branched0#{ Lbl => St },
+ Labels = cerl_sets:add_element(Lbl, Labels0),
+
Vst#vst{current=St,
ref_ctr=Counter,
- branched=gb_trees:enter(Lbl, St, B),
- labels=gb_sets:add(Lbl, Lbls)};
-valfun_1(_I, #vst{current=none}=Vst) ->
- %% Ignore instructions after erlang:error/1,2, which
- %% the original R10B compiler thought would return.
+ branched=Branched,
+ labels=Labels};
+vi(_I, #vst{current=none}=Vst) ->
+ %% Ignore all unreachable code.
Vst;
-valfun_1({badmatch,Src}, Vst) ->
- assert_durable_term(Src, Vst),
- verify_y_init(Vst),
- kill_state(Vst);
-valfun_1({case_end,Src}, Vst) ->
- assert_durable_term(Src, Vst),
- verify_y_init(Vst),
- kill_state(Vst);
-valfun_1(if_end, Vst) ->
- verify_y_init(Vst),
- kill_state(Vst);
-valfun_1({try_case_end,Src}, Vst) ->
- verify_y_init(Vst),
- assert_durable_term(Src, Vst),
- kill_state(Vst);
-%% Instructions that cannot cause exceptions
-valfun_1({bs_get_tail,Ctx,Dst,Live}, Vst0) ->
- bsm_validate_context(Ctx, Vst0),
- verify_live(Live, Vst0),
- verify_y_init(Vst0),
- Vst = prune_x_regs(Live, Vst0),
- extract_term(binary, bs_get_tail, [Ctx], Dst, Vst, Vst0);
-valfun_1(bs_init_writable=I, Vst) ->
- call(I, 1, Vst);
-valfun_1(build_stacktrace=I, Vst) ->
- call(I, 1, Vst);
-valfun_1({move,Src,Dst}, Vst) ->
+
+%%
+%% Misc annotations
+%%
+
+vi({'%', {var_info, Reg, Info}}, Vst) ->
+ validate_var_info(Info, Reg, Vst);
+vi({'%', {remove_fragility, Reg}}, Vst) ->
+ %% This is a hack to make prim_eval:'receive'/2 work.
+ %%
+ %% Normally it's illegal to pass fragile terms as a function argument as we
+ %% have no way of knowing what the callee will do with it, but we know that
+ %% prim_eval:'receive'/2 won't leak the term, nor cause a GC since it's
+ %% disabled while matching messages.
+ remove_fragility(Reg, Vst);
+vi({'%',_}, Vst) ->
+ Vst;
+vi({line,_}, Vst) ->
+ Vst;
+
+%%
+%% Moves
+%%
+
+vi({move,Src,Dst}, Vst) ->
assign(Src, Dst, Vst);
-valfun_1({fmove,Src,{fr,_}=Dst}, Vst) ->
- assert_type(float, Src, Vst),
+vi({swap,RegA,RegB}, Vst0) ->
+ assert_movable(RegA, Vst0),
+ assert_movable(RegB, Vst0),
+
+ %% We don't expect fragile registers to be swapped.
+ %% Therefore, we can conservatively make both registers
+ %% fragile if one of the register is fragile instead of
+ %% swapping the fragility of the registers.
+ Sources = [RegA,RegB],
+ Vst1 = propagate_fragility(RegA, Sources, Vst0),
+ Vst2 = propagate_fragility(RegB, Sources, Vst1),
+
+ %% Swap the value references.
+ VrefA = get_reg_vref(RegA, Vst2),
+ VrefB = get_reg_vref(RegB, Vst2),
+ Vst = set_reg_vref(VrefB, RegA, Vst2),
+ set_reg_vref(VrefA, RegB, Vst);
+vi({fmove,Src,{fr,_}=Dst}, Vst) ->
+ assert_type(#t_float{}, Src, Vst),
set_freg(Dst, Vst);
-valfun_1({fmove,{fr,_}=Src,Dst}, Vst0) ->
+vi({fmove,{fr,_}=Src,Dst}, Vst0) ->
assert_freg_set(Src, Vst0),
- assert_fls(checked, Vst0),
Vst = eat_heap_float(Vst0),
- create_term({float,[]}, fmove, [], Dst, Vst);
-valfun_1({kill,Reg}, Vst) ->
+ create_term(#t_float{}, fmove, [], Dst, Vst);
+vi({kill,Reg}, Vst) ->
create_tag(initialized, kill, [], Reg, Vst);
-valfun_1({init,Reg}, Vst) ->
+vi({init,Reg}, Vst) ->
create_tag(initialized, init, [], Reg, Vst);
-valfun_1({test_heap,Heap,Live}, Vst) ->
+
+%%
+%% Matching and test instructions.
+%%
+
+vi({jump,{f,Lbl}}, Vst) ->
+ assert_no_exception(Lbl),
+
+ %% The next instruction is never executed.
+ branch(Lbl, Vst, fun kill_state/1);
+vi({select_val,Src,{f,Fail},{list,Choices}}, Vst) ->
+ assert_term(Src, Vst),
+ assert_choices(Choices),
+ validate_select_val(Fail, Choices, Src, Vst);
+vi({select_tuple_arity,Tuple,{f,Fail},{list,Choices}}, Vst) ->
+ assert_type(#t_tuple{}, Tuple, Vst),
+ assert_arities(Choices),
+ validate_select_tuple_arity(Fail, Choices, Tuple, Vst);
+vi({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) ->
+ verify_has_map_fields(Lbl, Src, List, Vst);
+vi({test,is_atom,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, #t_atom{}, Src, Vst);
+vi({test,is_binary,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, #t_bitstring{size_unit=8}, Src, Vst);
+vi({test,is_bitstr,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, #t_bitstring{}, Src, Vst);
+vi({test,is_boolean,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, beam_types:make_boolean(), Src, Vst);
+vi({test,is_float,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, #t_float{}, Src, Vst);
+vi({test,is_tuple,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, #t_tuple{}, Src, Vst);
+vi({test,is_integer,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, #t_integer{}, Src, Vst);
+vi({test,is_nonempty_list,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, #t_cons{}, Src, Vst);
+vi({test,is_number,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, number, Src, Vst);
+vi({test,is_list,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, #t_list{}, Src, Vst);
+vi({test,is_map,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, #t_map{}, Src, Vst);
+vi({test,is_nil,{f,Lbl},[Src]}, Vst) ->
+ %% is_nil is an exact check against the 'nil' value, and should not be
+ %% treated as a simple type test.
+ assert_term(Src, Vst),
+ branch(Lbl, Vst,
+ fun(FailVst) ->
+ update_ne_types(Src, nil, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_eq_types(Src, nil, SuccVst)
+ end);
+vi({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst) when is_integer(Sz) ->
+ assert_type(#t_tuple{}, Tuple, Vst),
+ Type = #t_tuple{exact=true,size=Sz},
+ type_test(Lbl, Type, Tuple, Vst);
+vi({test,is_tagged_tuple,{f,Lbl},[Src,Sz,Atom]}, Vst) ->
+ assert_term(Src, Vst),
+ Es = #{ 1 => get_literal_type(Atom) },
+ Type = #t_tuple{exact=true,size=Sz,elements=Es},
+ type_test(Lbl, Type, Src, Vst);
+vi({test,is_eq_exact,{f,Lbl},[Src,Val]=Ss}, Vst) ->
+ assert_no_exception(Lbl),
+ validate_src(Ss, Vst),
+ branch(Lbl, Vst,
+ fun(FailVst) ->
+ update_ne_types(Src, Val, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_eq_types(Src, Val, SuccVst)
+ end);
+vi({test,is_ne_exact,{f,Lbl},[Src,Val]=Ss}, Vst) ->
+ assert_no_exception(Lbl),
+ validate_src(Ss, Vst),
+ branch(Lbl, Vst,
+ fun(FailVst) ->
+ update_eq_types(Src, Val, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_ne_types(Src, Val, SuccVst)
+ end);
+
+
+%%
+%% Simple getters that can't fail.
+%%
+
+vi({get_list,Src,D1,D2}, Vst0) ->
+ assert_not_literal(Src),
+ assert_type(#t_cons{}, Src, Vst0),
+
+ SrcType = get_term_type(Src, Vst0),
+ {HeadType, _, _} = beam_call_types:types(erlang, hd, [SrcType]),
+ {TailType, _, _} = beam_call_types:types(erlang, tl, [SrcType]),
+
+ Vst = extract_term(HeadType, get_hd, [Src], D1, Vst0),
+ extract_term(TailType, get_tl, [Src], D2, Vst, Vst0);
+vi({get_hd,Src,Dst}, Vst) ->
+ assert_not_literal(Src),
+ assert_type(#t_cons{}, Src, Vst),
+
+ SrcType = get_term_type(Src, Vst),
+ {HeadType, _, _} = beam_call_types:types(erlang, hd, [SrcType]),
+
+ extract_term(HeadType, get_hd, [Src], Dst, Vst);
+vi({get_tl,Src,Dst}, Vst) ->
+ assert_not_literal(Src),
+ assert_type(#t_cons{}, Src, Vst),
+
+ SrcType = get_term_type(Src, Vst),
+ {TailType, _, _} = beam_call_types:types(erlang, tl, [SrcType]),
+
+ extract_term(TailType, get_tl, [Src], Dst, Vst);
+vi({get_tuple_element,Src,N,Dst}, Vst) ->
+ Index = N+1,
+ assert_not_literal(Src),
+ assert_type(#t_tuple{size=Index}, Src, Vst),
+ #t_tuple{elements=Es} = normalize(get_term_type(Src, Vst)),
+ Type = beam_types:get_tuple_element(Index, Es),
+ extract_term(Type, {bif,element}, [{integer,Index}, Src], Dst, Vst);
+
+%%
+%% Allocate, deallocate, et.al.
+%%
+
+vi({test_heap,Heap,Live}, Vst) ->
test_heap(Heap, Live, Vst);
-valfun_1({bif,Op,{f,_},Ss,Dst}=I, Vst) ->
- case is_bif_safe(Op, length(Ss)) of
- false ->
- %% Since the BIF can fail, make sure that any catch state
- %% is updated.
- valfun_2(I, Vst);
- true ->
- %% It can't fail, so we finish handling it here (not updating
- %% catch state).
- validate_src(Ss, Vst),
- Type = bif_return_type(Op, Ss, Vst),
- extract_term(Type, {bif,Op}, Ss, Dst, Vst)
+vi({allocate,Stk,Live}, Vst) ->
+ allocate(uninitialized, Stk, 0, Live, Vst);
+vi({allocate_heap,Stk,Heap,Live}, Vst) ->
+ allocate(uninitialized, Stk, Heap, Live, Vst);
+vi({allocate_zero,Stk,Live}, Vst) ->
+ allocate(initialized, Stk, 0, Live, Vst);
+vi({allocate_heap_zero,Stk,Heap,Live}, Vst) ->
+ allocate(initialized, Stk, Heap, Live, Vst);
+vi({deallocate,StkSize}, #vst{current=#st{numy=StkSize}}=Vst) ->
+ verify_no_ct(Vst),
+ deallocate(Vst);
+vi({deallocate,_}, #vst{current=#st{numy=NumY}}) ->
+ error({allocated,NumY});
+vi({trim,N,Remaining}, #vst{current=St0}=Vst) ->
+ #st{numy=NumY} = St0,
+ if
+ N =< NumY, N+Remaining =:= NumY ->
+ Vst#vst{current=trim_stack(N, 0, NumY, St0)};
+ N > NumY; N+Remaining =/= NumY ->
+ error({trim,N,Remaining,allocated,NumY})
end;
-%% Put instructions.
-valfun_1({put_list,A,B,Dst}, Vst0) ->
- assert_term(A, Vst0),
- assert_term(B, Vst0),
+
+%%
+%% Term-building instructions
+%%
+
+vi({put_list,A,B,Dst}, Vst0) ->
Vst = eat_heap(2, Vst0),
- create_term(cons, put_list, [A, B], Dst, Vst);
-valfun_1({put_tuple2,Dst,{list,Elements}}, Vst0) ->
+
+ Head = get_term_type(A, Vst),
+ Tail = get_term_type(B, Vst),
+
+ create_term(beam_types:make_cons(Head, Tail), put_list, [A, B], Dst, Vst);
+vi({put_tuple2,Dst,{list,Elements}}, Vst0) ->
_ = [assert_term(El, Vst0) || El <- Elements],
Size = length(Elements),
Vst = eat_heap(Size+1, Vst0),
{Es,_} = foldl(fun(Val, {Es0, Index}) ->
Type = get_term_type(Val, Vst0),
- Es = set_element_type({integer,Index}, Type, Es0),
+ Es = beam_types:set_tuple_element(Index, Type, Es0),
{Es, Index + 1}
end, {#{}, 1}, Elements),
- Type = {tuple,Size,Es},
+ Type = #t_tuple{exact=true,size=Size,elements=Es},
create_term(Type, put_tuple2, [], Dst, Vst);
-valfun_1({put_tuple,Sz,Dst}, Vst0) when is_integer(Sz) ->
+vi({put_tuple,Sz,Dst}, Vst0) when is_integer(Sz) ->
Vst1 = eat_heap(1, Vst0),
- Vst = create_term(tuple_in_progress, put_tuple, [], Dst, Vst1),
+ Vst = create_term(#t_abstract{kind=unfinished_tuple}, put_tuple, [],
+ Dst, Vst1),
#vst{current=St0} = Vst,
St = St0#st{puts_left={Sz,{Dst,Sz,#{}}}},
Vst#vst{current=St};
-valfun_1({put,Src}, Vst0) ->
+vi({put,Src}, Vst0) ->
assert_term(Src, Vst0),
Vst = eat_heap(1, Vst0),
#vst{current=St0} = Vst,
@@ -455,91 +585,162 @@ valfun_1({put,Src}, Vst0) ->
#st{puts_left=none} ->
error(not_building_a_tuple);
#st{puts_left={1,{Dst,Sz,Es0}}} ->
- Es = Es0#{ {integer,Sz} => get_term_type(Src, Vst0) },
+ ElementType = get_term_type(Src, Vst0),
+ Es = beam_types:set_tuple_element(Sz, ElementType, Es0),
St = St0#st{puts_left=none},
- create_term({tuple,Sz,Es}, put_tuple, [], Dst, Vst#vst{current=St});
+ Type = #t_tuple{exact=true,size=Sz,elements=Es},
+ create_term(Type, put_tuple, [], Dst, Vst#vst{current=St});
#st{puts_left={PutsLeft,{Dst,Sz,Es0}}} when is_integer(PutsLeft) ->
Index = Sz - PutsLeft + 1,
- Es = Es0#{ {integer,Index} => get_term_type(Src, Vst0) },
+ ElementType = get_term_type(Src, Vst0),
+ Es = beam_types:set_tuple_element(Index, ElementType, Es0),
St = St0#st{puts_left={PutsLeft-1,{Dst,Sz,Es}}},
Vst#vst{current=St}
end;
-%% Instructions for optimization of selective receives.
-valfun_1({recv_mark,{f,Fail}}, Vst) when is_integer(Fail) ->
+vi({set_tuple_element,Src,Tuple,N}, Vst) ->
+ %% This instruction never fails, though it may be invalid in some contexts;
+ %% see validate_mutation/2
+ I = N + 1,
+ assert_term(Src, Vst),
+ assert_type(#t_tuple{size=I}, Tuple, Vst),
+ %% Manually update the tuple type; we can't rely on the ordinary update
+ %% helpers as we must support overwriting (rather than just widening or
+ %% narrowing) known elements, and we can't use extract_term either since
+ %% the source tuple may be aliased.
+ #t_tuple{elements=Es0}=Type = normalize(get_term_type(Tuple, Vst)),
+ Es = beam_types:set_tuple_element(I, get_term_type(Src, Vst), Es0),
+ override_type(Type#t_tuple{elements=Es}, Tuple, Vst);
+
+%%
+%% Calls
+%%
+
+vi({apply,Live}, Vst) ->
+ validate_body_call(apply, Live+2, Vst);
+vi({apply_last,Live,N}, Vst) ->
+ validate_tail_call(N, apply, Live+2, Vst);
+vi({call,Live,Func}, Vst) ->
+ validate_body_call(Func, Live, Vst);
+vi({call_ext,Live,Func}, Vst) ->
+ validate_body_call(Func, Live, Vst);
+vi({call_only,Live,Func}, Vst) ->
+ validate_tail_call(none, Func, Live, Vst);
+vi({call_ext_only,Live,Func}, Vst) ->
+ validate_tail_call(none, Func, Live, Vst);
+vi({call_last,Live,Func,N}, Vst) ->
+ validate_tail_call(N, Func, Live, Vst);
+vi({call_ext_last,Live,Func,N}, Vst) ->
+ validate_tail_call(N, Func, Live, Vst);
+vi({call_fun,Live}, Vst) ->
+ Fun = {x,Live},
+ assert_term(Fun, Vst),
+
+ branch(?EXCEPTION_LABEL, Vst,
+ fun(SuccVst0) ->
+ SuccVst = update_type(fun meet/2, #t_fun{arity=Live},
+ Fun, SuccVst0),
+ validate_body_call('fun', Live+1, SuccVst)
+ end);
+vi({make_fun2,{f,Lbl},_,_,NumFree}, #vst{ft=Ft}=Vst0) ->
+ #{ arity := Arity0 } = map_get(Lbl, Ft),
+ Arity = Arity0 - NumFree,
+
+ true = Arity >= 0, %Assertion.
+
+ Vst = prune_x_regs(NumFree, Vst0),
+ verify_call_args(make_fun, NumFree, Vst),
+ verify_y_init(Vst),
+ Type = #t_fun{arity=Arity},
+ create_term(Type, make_fun, [], {x,0}, Vst);
+vi(return, Vst) ->
+ assert_durable_term({x,0}, Vst),
+ verify_return(Vst);
+
+%%
+%% BIF calls
+%%
+
+vi({bif,Op,{f,Fail},Ss,Dst}, Vst0) ->
+ case is_float_arith_bif(Op, Ss) of
+ true ->
+ %% Float arithmetic BIFs neither fail nor throw an exception;
+ %% errors are postponed until the next fcheckerror instruction.
+ ?EXCEPTION_LABEL = Fail, %Assertion.
+ validate_float_arith_bif(Ss, Dst, Vst0);
+ false ->
+ validate_src(Ss, Vst0),
+ validate_bif(bif, Op, Fail, Ss, Dst, Vst0, Vst0)
+ end;
+vi({gc_bif,Op,{f,Fail},Live,Ss,Dst}, Vst0) ->
+ validate_src(Ss, Vst0),
+ verify_live(Live, Vst0),
+ verify_y_init(Vst0),
+
+ %% Heap allocations and X registers are killed regardless of whether we
+ %% fail or not, as we may fail after GC.
+ Vst1 = kill_heap_allocation(Vst0),
+ Vst = prune_x_regs(Live, Vst1),
+
+ validate_bif(gc_bif, Op, Fail, Ss, Dst, Vst0, Vst);
+
+%%
+%% Message instructions
+%%
+
+vi(send, Vst) ->
+ validate_body_call(send, 2, Vst);
+vi({loop_rec,{f,Fail},Dst}, Vst) ->
+ %% This term may not be part of the root set until remove_message/0 is
+ %% executed. If control transfers to the loop_rec_end/1 instruction, no
+ %% part of this term must be stored in a Y register.
+ assert_no_exception(Fail),
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ {Ref, SuccVst} = new_value(any, loop_rec, [], SuccVst0),
+ mark_fragile(Dst, set_reg_vref(Ref, Dst, SuccVst))
+ end);
+vi({loop_rec_end,Lbl}, Vst) ->
+ assert_no_exception(Lbl),
+ verify_y_init(Vst),
+ kill_state(Vst);
+vi({recv_mark,{f,Fail}}, Vst) when is_integer(Fail) ->
+ assert_no_exception(Fail),
set_receive_marker(initialized, Vst);
-valfun_1({recv_set,{f,Fail}}, Vst) when is_integer(Fail) ->
+vi({recv_set,{f,Fail}}, Vst) when is_integer(Fail) ->
+ assert_no_exception(Fail),
set_receive_marker(committed, Vst);
-%% Misc.
-valfun_1(remove_message, Vst0) ->
+vi(remove_message, Vst0) ->
Vst = set_receive_marker(none, Vst0),
%% The message term is no longer fragile. It can be used
%% without restrictions.
remove_fragility(Vst);
-valfun_1({'%', {type_info, Reg, match_context}}, Vst) ->
- update_type(fun meet/2, #ms{}, Reg, Vst);
-valfun_1({'%', {type_info, Reg, Type}}, Vst) ->
- %% Explicit type information inserted by optimization passes to indicate
- %% that Reg has a certain type, so that we can accept cross-function type
- %% optimizations.
- update_type(fun meet/2, Type, Reg, Vst);
-valfun_1({'%', {remove_fragility, Reg}}, Vst) ->
- %% This is a hack to make prim_eval:'receive'/2 work.
- %%
- %% Normally it's illegal to pass fragile terms as a function argument as we
- %% have no way of knowing what the callee will do with it, but we know that
- %% prim_eval:'receive'/2 won't leak the term, nor cause a GC since it's
- %% disabled while matching messages.
- remove_fragility(Reg, Vst);
-valfun_1({'%',_}, Vst) ->
- Vst;
-valfun_1({line,_}, Vst) ->
- Vst;
-%% Exception generating calls
-valfun_1({call_ext,Live,Func}=I, Vst) ->
- case call_return_type(Func, Vst) of
- exception ->
- verify_live(Live, Vst),
- %% The stack will be scanned, so Y registers
- %% must be initialized.
- verify_y_init(Vst),
- kill_state(Vst);
- _ ->
- valfun_2(I, Vst)
- end;
-valfun_1(_I, #vst{current=#st{ct=undecided}}) ->
- error(unknown_catch_try_state);
+vi(timeout, Vst0) ->
+ Vst = set_receive_marker(none, Vst0),
+ prune_x_regs(0, Vst);
+vi({wait,{f,Lbl}}, Vst) ->
+ assert_no_exception(Lbl),
+ verify_y_init(Vst),
+ branch(Lbl, Vst, fun kill_state/1);
+vi({wait_timeout,{f,Lbl},Src}, Vst0) ->
+ assert_no_exception(Lbl),
+
+ assert_term(Src, Vst0),
+ verify_y_init(Vst0),
+
+ Vst = branch(Lbl, schedule_out(0, Vst0)),
+ branch(?EXCEPTION_LABEL, Vst);
+
%%
-%% Allocate and deallocate, et.al
-valfun_1({allocate,Stk,Live}, Vst) ->
- allocate(uninitialized, Stk, 0, Live, Vst);
-valfun_1({allocate_heap,Stk,Heap,Live}, Vst) ->
- allocate(uninitialized, Stk, Heap, Live, Vst);
-valfun_1({allocate_zero,Stk,Live}, Vst) ->
- allocate(initialized, Stk, 0, Live, Vst);
-valfun_1({allocate_heap_zero,Stk,Heap,Live}, Vst) ->
- allocate(initialized, Stk, Heap, Live, Vst);
-valfun_1({deallocate,StkSize}, #vst{current=#st{numy=StkSize}}=Vst) ->
- verify_no_ct(Vst),
- deallocate(Vst);
-valfun_1({deallocate,_}, #vst{current=#st{numy=NumY}}) ->
- error({allocated,NumY});
-valfun_1({trim,N,Remaining}, #vst{current=St0}=Vst) ->
- #st{numy=NumY} = St0,
- if
- N =< NumY, N+Remaining =:= NumY ->
- Vst#vst{current=trim_stack(N, 0, NumY, St0)};
- N > NumY; N+Remaining =/= NumY ->
- error({trim,N,Remaining,allocated,NumY})
- end;
%% Catch & try.
-valfun_1({'catch',Dst,{f,Fail}}, Vst) when Fail =/= none ->
+%%
+vi({'catch',Dst,{f,Fail}}, Vst) when Fail =/= none ->
init_try_catch_branch(catchtag, Dst, Fail, Vst);
-valfun_1({'try',Dst,{f,Fail}}, Vst) when Fail =/= none ->
+vi({'try',Dst,{f,Fail}}, Vst) when Fail =/= none ->
init_try_catch_branch(trytag, Dst, Fail, Vst);
-valfun_1({catch_end,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst0) ->
+vi({catch_end,Reg}, #vst{current=#st{ct=[Tag|_]}}=Vst0) ->
case get_tag_type(Reg, Vst0) of
- {catchtag,Fail} ->
+ {catchtag,_Fail}=Tag ->
%% Kill the catch tag and receive marker.
%%
%% The marker is only cleared when an exception is thrown, but it's
@@ -548,473 +749,277 @@ valfun_1({catch_end,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst0) ->
Vst = set_receive_marker(none, Vst1),
%% {x,0} contains the caught term, if any.
- create_term(term, catch_end, [], {x,0}, Vst);
+ create_term(any, catch_end, [], {x,0}, Vst);
Type ->
error({wrong_tag_type,Type})
end;
-valfun_1({try_end,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst) ->
+vi({try_end,Reg}, #vst{current=#st{ct=[Tag|_]}}=Vst) ->
case get_tag_type(Reg, Vst) of
- {trytag,Fail} ->
+ {trytag,_Fail}=Tag ->
%% Kill the catch tag. Note that x registers and the receive marker
%% are unaffected.
kill_catch_tag(Reg, Vst);
Type ->
error({wrong_tag_type,Type})
end;
-valfun_1({try_case,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst0) ->
+vi({try_case,Reg}, #vst{current=#st{ct=[Tag|_]}}=Vst0) ->
case get_tag_type(Reg, Vst0) of
- {trytag,Fail} ->
- %% Kill the catch tag, all x registers, and the receive marker.
+ {trytag,_Fail}=Tag ->
+ %% Kill the catch tag and all other state (as if we've been
+ %% scheduled out with no live registers). Only previously allocated
+ %% Y registers are alive at this point.
Vst1 = kill_catch_tag(Reg, Vst0),
- Vst2 = prune_x_regs(0, Vst1),
- Vst3 = set_receive_marker(none, Vst2),
+ Vst2 = schedule_out(0, Vst1),
%% Class:Error:Stacktrace
- Vst4 = create_term({atom,[]}, try_case, [], {x,0}, Vst3),
- Vst = create_term(term, try_case, [], {x,1}, Vst4),
- create_term(term, try_case, [], {x,2}, Vst);
+ Vst3 = create_term(#t_atom{}, try_case, [], {x,0}, Vst2),
+ Vst = create_term(any, try_case, [], {x,1}, Vst3),
+ create_term(any, try_case, [], {x,2}, Vst);
Type ->
error({wrong_tag_type,Type})
end;
-valfun_1({get_list,Src,D1,D2}, Vst0) ->
- assert_not_literal(Src),
- assert_type(cons, Src, Vst0),
- Vst = extract_term(term, get_hd, [Src], D1, Vst0),
- extract_term(term, get_tl, [Src], D2, Vst);
-valfun_1({get_hd,Src,Dst}, Vst) ->
- assert_not_literal(Src),
- assert_type(cons, Src, Vst),
- extract_term(term, get_hd, [Src], Dst, Vst);
-valfun_1({get_tl,Src,Dst}, Vst) ->
- assert_not_literal(Src),
- assert_type(cons, Src, Vst),
- extract_term(term, get_tl, [Src], Dst, Vst);
-valfun_1({get_tuple_element,Src,N,Dst}, Vst) ->
- assert_not_literal(Src),
- assert_type({tuple_element,N+1}, Src, Vst),
- Index = {integer,N+1},
- Type = get_element_type(Index, Src, Vst),
- extract_term(Type, {bif,element}, [Index, Src], Dst, Vst);
-valfun_1({jump,{f,Lbl}}, Vst) ->
- branch(Lbl, Vst,
- fun(SuccVst) ->
- %% The next instruction is never executed.
- kill_state(SuccVst)
- end);
+vi(build_stacktrace=I, Vst) ->
+ validate_body_call(I, 1, Vst);
-valfun_1(return, Vst) ->
- assert_durable_term({x,0}, Vst),
- verify_return(Vst),
- kill_state(Vst);
+%%
+%% Map instructions.
+%%
-valfun_1({set_tuple_element,Src,Tuple,N}, Vst) ->
- I = N + 1,
- assert_term(Src, Vst),
- assert_type({tuple_element,I}, Tuple, Vst),
- %% Manually update the tuple type; we can't rely on the ordinary update
- %% helpers as we must support overwriting (rather than just widening or
- %% narrowing) known elements, and we can't use extract_term either since
- %% the source tuple may be aliased.
- {tuple, Sz, Es0} = get_term_type(Tuple, Vst),
- Es = set_element_type({integer,I}, get_term_type(Src, Vst), Es0),
- override_type({tuple, Sz, Es}, Tuple, Vst);
+vi({get_map_elements,{f,Fail},Src,{list,List}}, Vst) ->
+ verify_get_map(Fail, Src, List, Vst);
+vi({put_map_assoc=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
+ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);
+vi({put_map_exact=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
+ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);
-%% Match instructions.
-valfun_1({select_val,Src,{f,Fail},{list,Choices}}, Vst) ->
- assert_term(Src, Vst),
- assert_choices(Choices),
- validate_select_val(Fail, Choices, Src, Vst);
-valfun_1({select_tuple_arity,Tuple,{f,Fail},{list,Choices}}, Vst) ->
- assert_type(tuple, Tuple, Vst),
- assert_arities(Choices),
- validate_select_tuple_arity(Fail, Choices, Tuple, Vst);
+%%
+%% Bit syntax matching
+%%
-%% New bit syntax matching instructions.
-valfun_1({test,bs_start_match3,{f,Fail},Live,[Src],Dst}, Vst) ->
- validate_bs_start_match(Fail, Live, bsm_match_state(), Src, Dst, Vst);
-valfun_1({test,bs_start_match2,{f,Fail},Live,[Src,Slots],Dst}, Vst) ->
- validate_bs_start_match(Fail, Live, bsm_match_state(Slots), Src, Dst, Vst);
-valfun_1({test,bs_match_string,{f,Fail},[Ctx,_,_]}, Vst) ->
- bsm_validate_context(Ctx, Vst),
- branch(Fail, Vst, fun(V) -> V end);
-valfun_1({test,bs_skip_bits2,{f,Fail},[Ctx,Src,_,_]}, Vst) ->
- bsm_validate_context(Ctx, Vst),
- assert_term(Src, Vst),
- branch(Fail, Vst, fun(V) -> V end);
-valfun_1({test,bs_test_tail2,{f,Fail},[Ctx,_]}, Vst) ->
- bsm_validate_context(Ctx, Vst),
- branch(Fail, Vst, fun(V) -> V end);
-valfun_1({test,bs_test_unit,{f,Fail},[Ctx,_]}, Vst) ->
- bsm_validate_context(Ctx, Vst),
- branch(Fail, Vst, fun(V) -> V end);
-valfun_1({test,bs_skip_utf8,{f,Fail},[Ctx,Live,_]}, Vst) ->
- validate_bs_skip_utf(Fail, Ctx, Live, Vst);
-valfun_1({test,bs_skip_utf16,{f,Fail},[Ctx,Live,_]}, Vst) ->
- validate_bs_skip_utf(Fail, Ctx, Live, Vst);
-valfun_1({test,bs_skip_utf32,{f,Fail},[Ctx,Live,_]}, Vst) ->
- validate_bs_skip_utf(Fail, Ctx, Live, Vst);
-valfun_1({test,bs_get_integer2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
-valfun_1({test,bs_get_float2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {float, []}, Dst, Vst);
-valfun_1({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, binary, Dst, Vst);
-valfun_1({test,bs_get_utf8=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
-valfun_1({test,bs_get_utf16=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
-valfun_1({test,bs_get_utf32=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
- validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
-valfun_1({bs_save2,Ctx,SavePoint}, Vst) ->
- bsm_save(Ctx, SavePoint, Vst);
-valfun_1({bs_restore2,Ctx,SavePoint}, Vst) ->
- bsm_restore(Ctx, SavePoint, Vst);
-valfun_1({bs_get_position, Ctx, Dst, Live}, Vst0) ->
- bsm_validate_context(Ctx, Vst0),
+vi({bs_get_tail,Ctx,Dst,Live}, Vst0) ->
+ assert_type(#t_bs_context{}, Ctx, Vst0),
verify_live(Live, Vst0),
verify_y_init(Vst0),
+
+ #t_bs_context{tail_unit=Unit} = get_raw_type(Ctx, Vst0),
+
Vst = prune_x_regs(Live, Vst0),
- create_term(ms_position, bs_get_position, [Ctx], Dst, Vst, Vst0);
-valfun_1({bs_set_position, Ctx, Pos}, Vst) ->
- bsm_validate_context(Ctx, Vst),
- assert_type(ms_position, Pos, Vst),
- Vst;
-valfun_1({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) ->
- assert_type(map, Src, Vst),
- assert_unique_map_keys(List),
- branch(Lbl, Vst, fun(V) -> V end);
-valfun_1({test,is_atom,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, {atom,[]}, Src, Vst);
-valfun_1({test,is_binary,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, binary, Src, Vst);
-valfun_1({test,is_bitstr,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, binary, Src, Vst);
-valfun_1({test,is_boolean,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, bool, Src, Vst);
-valfun_1({test,is_float,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, {float,[]}, Src, Vst);
-valfun_1({test,is_tuple,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, {tuple,[0],#{}}, Src, Vst);
-valfun_1({test,is_integer,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, {integer,[]}, Src, Vst);
-valfun_1({test,is_nonempty_list,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, cons, Src, Vst);
-valfun_1({test,is_number,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, number, Src, Vst);
-valfun_1({test,is_list,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, list, Src, Vst);
-valfun_1({test,is_map,{f,Lbl},[Src]}, Vst) ->
- type_test(Lbl, map, Src, Vst);
-valfun_1({test,is_nil,{f,Lbl},[Src]}, Vst) ->
- %% is_nil is an exact check against the 'nil' value, and should not be
- %% treated as a simple type test.
- assert_term(Src, Vst),
- branch(Lbl, Vst,
- fun(FailVst) ->
- update_ne_types(Src, nil, FailVst)
- end,
- fun(SuccVst) ->
- update_eq_types(Src, nil, SuccVst)
- end);
-valfun_1({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst) when is_integer(Sz) ->
- assert_type(tuple, Tuple, Vst),
- Type = {tuple, Sz, #{}},
- type_test(Lbl, Type, Tuple, Vst);
-valfun_1({test,is_tagged_tuple,{f,Lbl},[Src,Sz,Atom]}, Vst) ->
- assert_term(Src, Vst),
- Type = {tuple, Sz, #{ {integer,1} => Atom }},
- type_test(Lbl, Type, Src, Vst);
-valfun_1({test,is_eq_exact,{f,Lbl},[Src,Val]=Ss}, Vst) ->
- validate_src(Ss, Vst),
- branch(Lbl, Vst,
+ extract_term(#t_bitstring{size_unit=Unit}, bs_get_tail, [Ctx], Dst,
+ Vst, Vst0);
+vi({bs_start_match4,Fail,Live,Src,Dst}, Vst) ->
+ validate_bs_start_match(Fail, Live, 0, Src, Dst, Vst);
+vi({test,bs_start_match3,{f,_}=Fail,Live,[Src],Dst}, Vst) ->
+ validate_bs_start_match(Fail, Live, 0, Src, Dst, Vst);
+vi({test,bs_start_match2,{f,_}=Fail,Live,[Src,Slots],Dst}, Vst) ->
+ validate_bs_start_match(Fail, Live, Slots, Src, Dst, Vst);
+vi({test,bs_match_string,{f,Fail},[Ctx,Stride,{string,String}]}, Vst) ->
+ true = is_bitstring(String), %Assertion.
+ validate_bs_skip(Fail, Ctx, Stride, Vst);
+vi({test,bs_skip_bits2,{f,Fail},[Ctx,Size,Unit,_Flags]}, Vst) ->
+ assert_term(Size, Vst),
+
+ Stride = case get_raw_type(Size, Vst) of
+ #t_integer{elements={Same,Same}} -> Same * Unit;
+ _ -> Unit
+ end,
+
+ validate_bs_skip(Fail, Ctx, Stride, Vst);
+vi({test,bs_test_tail2,{f,Fail},[Ctx,_Size]}, Vst) ->
+ assert_no_exception(Fail),
+ assert_type(#t_bs_context{}, Ctx, Vst),
+ branch(Fail, Vst);
+vi({test,bs_test_unit,{f,Fail},[Ctx,Unit]}, Vst) ->
+ assert_type(#t_bs_context{}, Ctx, Vst),
+
+ Type = #t_bs_context{tail_unit=Unit},
+
+ branch(Fail, Vst,
fun(FailVst) ->
- update_ne_types(Src, Val, FailVst)
+ update_type(fun subtract/2, Type, Ctx, FailVst)
end,
- fun(SuccVst) ->
- update_eq_types(Src, Val, SuccVst)
+ fun(SuccVst0) ->
+ SuccVst = update_bs_unit(Ctx, Unit, SuccVst0),
+ update_type(fun meet/2, Type, Ctx, SuccVst)
end);
-valfun_1({test,is_ne_exact,{f,Lbl},[Src,Val]=Ss}, Vst) ->
- validate_src(Ss, Vst),
- branch(Lbl, Vst,
- fun(FailVst) ->
- update_eq_types(Src, Val, FailVst)
+vi({test,bs_skip_utf8,{f,Fail},[Ctx,Live,_]}, Vst) ->
+ validate_bs_skip(Fail, Ctx, 8, Live, Vst);
+vi({test,bs_skip_utf16,{f,Fail},[Ctx,Live,_]}, Vst) ->
+ validate_bs_skip(Fail, Ctx, 16, Live, Vst);
+vi({test,bs_skip_utf32,{f,Fail},[Ctx,Live,_]}, Vst) ->
+ validate_bs_skip(Fail, Ctx, 32, Live, Vst);
+vi({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,{atom,all},Unit,_],Dst}, Vst) ->
+ Type = #t_bitstring{size_unit=Unit},
+ validate_bs_get_all(Op, Fail, Ctx, Live, Unit, Type, Dst, Vst);
+vi({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,{integer,Sz},Unit,_],Dst}, Vst) ->
+ Stride = Unit * max(1, Sz),
+ Type = #t_bitstring{size_unit=Stride},
+ validate_bs_get(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst);
+vi({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,_,Unit,_],Dst}, Vst) ->
+ Type = #t_bitstring{size_unit=Unit},
+ validate_bs_get(Op, Fail, Ctx, Live, Unit, Type, Dst, Vst);
+vi({test,bs_get_integer2=Op,{f,Fail},Live,
+ [Ctx,{integer,Sz},Unit,{field_flags,Flags}],Dst},Vst) ->
+ NumBits = Unit * Sz,
+ Stride = max(1, NumBits),
+ Type = case member(unsigned, Flags) of
+ true when 0 =< NumBits, NumBits =< 64 ->
+ beam_types:make_integer(0, (1 bsl NumBits)-1);
+ _ ->
+ %% Signed integer, way too large, or negative size.
+ #t_integer{}
end,
- fun(SuccVst) ->
- update_ne_types(Src, Val, SuccVst)
- end);
-valfun_1({test,_Op,{f,Lbl},Src}, Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst);
+vi({test,bs_get_integer2=Op,{f,Fail},Live,[Ctx,_Sz,Unit,_Flags],Dst},Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, Unit, #t_integer{}, Dst, Vst);
+vi({test,bs_get_float2=Op,{f,Fail},Live,[Ctx,{integer,Sz},Unit,_],Dst},Vst) ->
+ Stride = Unit * max(1, Sz),
+ validate_bs_get(Op, Fail, Ctx, Live, Stride, #t_float{}, Dst, Vst);
+vi({test,bs_get_float2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, 32, #t_float{}, Dst, Vst);
+vi({test,bs_get_utf8=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
+ Type = beam_types:make_integer(0, ?UNICODE_MAX),
+ validate_bs_get(Op, Fail, Ctx, Live, 8, Type, Dst, Vst);
+vi({test,bs_get_utf16=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
+ Type = beam_types:make_integer(0, ?UNICODE_MAX),
+ validate_bs_get(Op, Fail, Ctx, Live, 16, Type, Dst, Vst);
+vi({test,bs_get_utf32=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
+ Type = beam_types:make_integer(0, ?UNICODE_MAX),
+ validate_bs_get(Op, Fail, Ctx, Live, 32, Type, Dst, Vst);
+vi({test,_Op,{f,Lbl},Src}, Vst) ->
%% is_pid, is_reference, et cetera.
validate_src(Src, Vst),
- branch(Lbl, Vst, fun(V) -> V end);
+ branch(Lbl, Vst);
-%% Map instructions.
-valfun_1({put_map_assoc=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
- verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);
-valfun_1({get_map_elements,{f,Fail},Src,{list,List}}, Vst) ->
- verify_get_map(Fail, Src, List, Vst);
+%%
+%% Bit syntax positioning
+%%
-valfun_1(I, Vst) ->
- valfun_2(I, Vst).
+vi({bs_save2,Ctx,SavePoint}, Vst) ->
+ bsm_save(Ctx, SavePoint, Vst);
+vi({bs_restore2,Ctx,SavePoint}, Vst) ->
+ bsm_restore(Ctx, SavePoint, Vst);
+vi({bs_get_position, Ctx, Dst, Live}, Vst0) ->
+ assert_type(#t_bs_context{}, Ctx, Vst0),
-init_try_catch_branch(Tag, Dst, Fail, Vst0) ->
- Vst1 = create_tag({Tag,[Fail]}, 'try_catch', [], Dst, Vst0),
- #vst{current=#st{ct=Fails}=St0} = Vst1,
- St = St0#st{ct=[[Fail]|Fails]},
- Vst = Vst0#vst{current=St},
+ verify_live(Live, Vst0),
+ verify_y_init(Vst0),
- branch(Fail, Vst,
- fun(CatchVst0) ->
- #vst{current=#st{ys=Ys}} = CatchVst0,
- CatchVst = maps:fold(fun init_catch_handler_1/3,
- CatchVst0, Ys),
- %% The receive marker is cleared on exceptions.
- set_receive_marker(none, CatchVst)
- end,
- fun(SuccVst) ->
- %% All potentially-throwing instructions after this
- %% one will implicitly branch to the fail label;
- %% see valfun_2/2
- SuccVst
- end).
+ #t_bs_context{tail_unit=Unit} = get_raw_type(Ctx, Vst0),
-%% Set the initial state at the try/catch label. Assume that Y registers
-%% contain terms or try/catch tags.
-init_catch_handler_1(Reg, initialized, Vst) ->
- create_term(term, 'catch_handler', [], Reg, Vst);
-init_catch_handler_1(Reg, uninitialized, Vst) ->
- create_term(term, 'catch_handler', [], Reg, Vst);
-init_catch_handler_1(_, _, Vst) ->
- Vst.
+ Vst1 = prune_x_regs(Live, Vst0),
+ Vst = create_term(#t_abstract{kind={ms_position, Unit}},
+ bs_get_position, [Ctx], Dst, Vst1, Vst0),
-valfun_2(I, #vst{current=#st{ct=[[Fail]|_]}}=Vst) when is_integer(Fail) ->
- %% We have an active try/catch tag and we can jump there from this
- %% instruction, so we need to update the branched state of the try/catch
- %% handler.
- valfun_3(I, branch_state(Fail, Vst));
-valfun_2(I, #vst{current=#st{ct=[]}}=Vst) ->
- valfun_3(I, Vst);
-valfun_2(_, _) ->
- error(ambiguous_catch_try_state).
-
-%% Handle the remaining floating point instructions here.
-%% Floating point.
-valfun_3({fconv,Src,{fr,_}=Dst}, Vst) ->
- assert_term(Src, Vst),
+ mark_current_ms_position(Ctx, Dst, Vst);
+vi({bs_set_position, Ctx, Pos}, Vst0) ->
+ assert_type(#t_bs_context{}, Ctx, Vst0),
+ assert_type(#t_abstract{kind={ms_position,1}}, Pos, Vst0),
- %% An exception is raised on error, hence branching to 0.
- branch(0, Vst,
- fun(SuccVst0) ->
- SuccVst = update_type(fun meet/2, number, Src, SuccVst0),
- set_freg(Dst, SuccVst)
- end);
-valfun_3({bif,fadd,_,[_,_]=Ss,Dst}, Vst) ->
- float_op(Ss, Dst, Vst);
-valfun_3({bif,fdiv,_,[_,_]=Ss,Dst}, Vst) ->
- float_op(Ss, Dst, Vst);
-valfun_3({bif,fmul,_,[_,_]=Ss,Dst}, Vst) ->
- float_op(Ss, Dst, Vst);
-valfun_3({bif,fnegate,_,[_]=Ss,Dst}, Vst) ->
- float_op(Ss, Dst, Vst);
-valfun_3({bif,fsub,_,[_,_]=Ss,Dst}, Vst) ->
- float_op(Ss, Dst, Vst);
-valfun_3(fclearerror, Vst) ->
- case get_fls(Vst) of
- undefined -> ok;
- checked -> ok;
- Fls -> error({bad_floating_point_state,Fls})
- end,
- set_fls(cleared, Vst);
-valfun_3({fcheckerror,_}, Vst) ->
- assert_fls(cleared, Vst),
- set_fls(checked, Vst);
-valfun_3(I, Vst) ->
- %% The instruction is not a float instruction.
- case get_fls(Vst) of
- undefined ->
- valfun_4(I, Vst);
- checked ->
- valfun_4(I, Vst);
- Fls ->
- error({unsafe_instruction,{float_error_state,Fls}})
- end.
+ #t_abstract{kind={ms_position,Unit}} = get_raw_type(Pos, Vst0),
+ Vst = override_type(#t_bs_context{tail_unit=Unit}, Ctx, Vst0),
-%% Instructions that can cause exceptions.
-valfun_4({put_map_exact=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
- verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);
-valfun_4({apply,Live}, Vst) ->
- call(apply, Live+2, Vst);
-valfun_4({apply_last,Live,_}, Vst) ->
- tail_call(apply, Live+2, Vst);
-valfun_4({call_fun,Live}, Vst) ->
- validate_src([{x,Live}], Vst),
- call('fun', Live+1, Vst);
-valfun_4({call,Live,Func}, Vst) ->
- call(Func, Live, Vst);
-valfun_4({call_ext,Live,Func}, Vst) ->
- %% Exception BIFs has already been taken care of above.
- call(Func, Live, Vst);
-valfun_4({call_only,Live,Func}, Vst) ->
- tail_call(Func, Live, Vst);
-valfun_4({call_ext_only,Live,Func}, Vst) ->
- tail_call(Func, Live, Vst);
-valfun_4({call_last,Live,Func,StkSize}, #vst{current=#st{numy=StkSize}}=Vst) ->
- tail_call(Func, Live, Vst);
-valfun_4({call_last,_,_,_}, #vst{current=#st{numy=NumY}}) ->
- error({allocated,NumY});
-valfun_4({call_ext_last,Live,Func,StkSize},
- #vst{current=#st{numy=StkSize}}=Vst) ->
- tail_call(Func, Live, Vst);
-valfun_4({call_ext_last,_,_,_}, #vst{current=#st{numy=NumY}}) ->
- error({allocated,NumY});
-valfun_4({make_fun2,_,_,_,Live}, Vst) ->
- call(make_fun, Live, Vst);
-%% Other BIFs
-valfun_4({bif,element,{f,Fail},[Pos,Src],Dst}, Vst) ->
- branch(Fail, Vst,
- fun(SuccVst0) ->
- PosType = get_term_type(Pos, SuccVst0),
- TupleType = {tuple,[get_tuple_size(PosType)],#{}},
-
- SuccVst1 = update_type(fun meet/2, TupleType,
- Src, SuccVst0),
- SuccVst = update_type(fun meet/2, {integer,[]},
- Pos, SuccVst1),
+ mark_current_ms_position(Ctx, Pos, Vst);
- ElementType = get_element_type(PosType, Src, SuccVst),
- extract_term(ElementType, {bif,element}, [Pos,Src],
- Dst, SuccVst)
- end);
-valfun_4({bif,raise,{f,0},Src,_Dst}, Vst) ->
- validate_src(Src, Vst),
- kill_state(Vst);
-valfun_4(raw_raise=I, Vst) ->
- call(I, 3, Vst);
-valfun_4({bif,Op,{f,Fail},[Src]=Ss,Dst}, Vst) when Op =:= hd; Op =:= tl ->
+%%
+%% Floating-point instructions (excluding BIFs)
+%%
+vi({fconv,Src,{fr,_}=Dst}, Vst) ->
assert_term(Src, Vst),
- branch(Fail, Vst,
- fun(FailVst) ->
- update_type(fun subtract/2, cons, Src, FailVst)
- end,
- fun(SuccVst0) ->
- SuccVst = update_type(fun meet/2, cons, Src, SuccVst0),
- extract_term(term, {bif,Op}, Ss, Dst, SuccVst)
- end);
-valfun_4({bif,Op,{f,Fail},Ss,Dst}, Vst) ->
- validate_src(Ss, Vst),
- branch(Fail, Vst,
+
+ branch(?EXCEPTION_LABEL, Vst,
fun(SuccVst0) ->
- %% Infer argument types. Note that we can't subtract
- %% types as the BIF could fail for reasons other than
- %% bad argument types.
- ArgTypes = bif_arg_types(Op, Ss),
- SuccVst = foldl(fun({Arg, T}, V) ->
- update_type(fun meet/2, T, Arg, V)
- end, SuccVst0, zip(Ss, ArgTypes)),
- Type = bif_return_type(Op, Ss, SuccVst),
- extract_term(Type, {bif,Op}, Ss, Dst, SuccVst)
+ SuccVst = update_type(fun meet/2, number, Src, SuccVst0),
+ set_freg(Dst, SuccVst)
end);
-valfun_4({gc_bif,Op,{f,Fail},Live,Ss,Dst}, #vst{current=St0}=Vst0) ->
- validate_src(Ss, Vst0),
- verify_live(Live, Vst0),
- verify_y_init(Vst0),
+vi(fclearerror, Vst) ->
+ Vst;
+vi({fcheckerror, _}, Vst) ->
+ branch(?EXCEPTION_LABEL, Vst);
- %% Heap allocations and X registers are killed regardless of whether we
- %% fail or not, as we may fail after GC.
- St = kill_heap_allocation(St0),
- Vst = prune_x_regs(Live, Vst0#vst{current=St}),
+%%
+%% Exception-raising instructions
+%%
- branch(Fail, Vst,
- fun(SuccVst0) ->
- ArgTypes = bif_arg_types(Op, Ss),
- SuccVst = foldl(fun({Arg, T}, V) ->
- update_type(fun meet/2, T, Arg, V)
- end, SuccVst0, zip(Ss, ArgTypes)),
+vi({func_info, {atom, _Mod}, {atom, _Name}, Arity}, Vst) ->
+ #vst{current=#st{numy=NumY}} = Vst,
+ if
+ NumY =:= none ->
+ verify_live(Arity, Vst),
+ verify_call_args(func_info, Arity, Vst),
- Type = bif_return_type(Op, Ss, SuccVst),
+ branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
+ NumY =/= none ->
+ error({allocated, NumY})
+ end;
+vi({badmatch,Src}, Vst) ->
+ assert_durable_term(Src, Vst),
+ branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
+vi({case_end,Src}, Vst) ->
+ assert_durable_term(Src, Vst),
+ branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
+vi(if_end, Vst) ->
+ branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
+vi({try_case_end,Src}, Vst) ->
+ assert_durable_term(Src, Vst),
+ branch(?EXCEPTION_LABEL, Vst, fun kill_state/1);
+vi(raw_raise=I, Vst) ->
+ branch(?EXCEPTION_LABEL, Vst,
+ fun(FailVst) -> validate_body_call(I, 3, FailVst) end,
+ fun kill_state/1);
- %% We're passing Vst0 as the original because the
- %% registers were pruned before the branch.
- extract_term(Type, {gc_bif,Op}, Ss, Dst, SuccVst, Vst0)
- end);
-valfun_4({loop_rec,{f,Fail},Dst}, Vst) ->
- %% This term may not be part of the root set until remove_message/0 is
- %% executed. If control transfers to the loop_rec_end/1 instruction, no
- %% part of this term must be stored in a Y register.
- branch(Fail, Vst,
- fun(SuccVst0) ->
- {Ref, SuccVst} = new_value(term, loop_rec, [], SuccVst0),
- mark_fragile(Dst, set_reg_vref(Ref, Dst, SuccVst))
- end);
-valfun_4({wait,_}, Vst) ->
- verify_y_init(Vst),
- kill_state(Vst);
-valfun_4({wait_timeout,_,Src}, Vst) ->
- %% Note that the receive marker is not cleared since we may re-enter the
- %% loop while waiting. If we time out we'll be transferred to a timeout
- %% instruction that clears the marker.
- assert_term(Src, Vst),
- verify_y_init(Vst),
- prune_x_regs(0, Vst);
-valfun_4({loop_rec_end,_}, Vst) ->
- verify_y_init(Vst),
- kill_state(Vst);
-valfun_4(timeout, Vst0) ->
- Vst = set_receive_marker(none, Vst0),
- prune_x_regs(0, Vst);
-valfun_4(send, Vst) ->
- call(send, 2, Vst);
+%%
+%% Binary construction
+%%
-%% Other test instructions.
-valfun_4({bs_add,{f,Fail},[A,B,_],Dst}, Vst) ->
- assert_term(A, Vst),
- assert_term(B, Vst),
- branch(Fail, Vst,
- fun(SuccVst) ->
- create_term({integer,[]}, bs_add, [A, B], Dst, SuccVst)
- end);
-valfun_4({bs_utf8_size,{f,Fail},A,Dst}, Vst) ->
- assert_term(A, Vst),
- branch(Fail, Vst,
- fun(SuccVst) ->
- create_term({integer,[]}, bs_utf8_size, [A], Dst, SuccVst)
- end);
-valfun_4({bs_utf16_size,{f,Fail},A,Dst}, Vst) ->
- assert_term(A, Vst),
- branch(Fail, Vst,
- fun(SuccVst) ->
- create_term({integer,[]}, bs_utf16_size, [A], Dst, SuccVst)
- end);
-valfun_4({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
+vi(bs_init_writable=I, Vst) ->
+ validate_body_call(I, 1, Vst);
+vi({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
if
- is_integer(Sz) ->
- ok;
- true ->
- assert_term(Sz, Vst0)
+ is_integer(Sz) -> ok;
+ true -> assert_term(Sz, Vst0)
end,
Vst = heap_alloc(Heap, Vst0),
branch(Fail, Vst,
fun(SuccVst0) ->
SuccVst = prune_x_regs(Live, SuccVst0),
- create_term(binary, bs_init2, [], Dst, SuccVst, SuccVst0)
+ create_term(#t_bitstring{size_unit=8}, bs_init2, [], Dst,
+ SuccVst, SuccVst0)
end);
-valfun_4({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
+vi({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
if
- is_integer(Sz) ->
- ok;
- true ->
- assert_term(Sz, Vst0)
+ is_integer(Sz) -> ok;
+ true -> assert_term(Sz, Vst0)
end,
Vst = heap_alloc(Heap, Vst0),
branch(Fail, Vst,
fun(SuccVst0) ->
SuccVst = prune_x_regs(Live, SuccVst0),
- create_term(binary, bs_init_bits, [], Dst, SuccVst)
+ create_term(#t_bitstring{}, bs_init_bits, [], Dst, SuccVst)
end);
-valfun_4({bs_append,{f,Fail},Bits,Heap,Live,_Unit,Bin,_Flags,Dst}, Vst0) ->
+vi({bs_add,{f,Fail},[A,B,_],Dst}, Vst) ->
+ assert_term(A, Vst),
+ assert_term(B, Vst),
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ create_term(#t_integer{}, bs_add, [A, B], Dst, SuccVst)
+ end);
+vi({bs_utf8_size,{f,Fail},A,Dst}, Vst) ->
+ assert_term(A, Vst),
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ create_term(#t_integer{}, bs_utf8_size, [A], Dst, SuccVst)
+ end);
+vi({bs_utf16_size,{f,Fail},A,Dst}, Vst) ->
+ assert_term(A, Vst),
+ branch(Fail, Vst,
+ fun(SuccVst) ->
+ create_term(#t_integer{}, bs_utf16_size, [A], Dst, SuccVst)
+ end);
+vi({bs_append,{f,Fail},Bits,Heap,Live,Unit,Bin,_Flags,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
assert_term(Bits, Vst0),
@@ -1023,62 +1028,165 @@ valfun_4({bs_append,{f,Fail},Bits,Heap,Live,_Unit,Bin,_Flags,Dst}, Vst0) ->
branch(Fail, Vst,
fun(SuccVst0) ->
SuccVst = prune_x_regs(Live, SuccVst0),
- create_term(binary, bs_append, [Bin], Dst, SuccVst, SuccVst0)
+ create_term(#t_bitstring{size_unit=Unit}, bs_append,
+ [Bin], Dst, SuccVst, SuccVst0)
end);
-valfun_4({bs_private_append,{f,Fail},Bits,_Unit,Bin,_Flags,Dst}, Vst) ->
+vi({bs_private_append,{f,Fail},Bits,Unit,Bin,_Flags,Dst}, Vst) ->
assert_term(Bits, Vst),
assert_term(Bin, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- create_term(binary, bs_private_append, [Bin], Dst, SuccVst)
+ create_term(#t_bitstring{size_unit=Unit}, bs_private_append,
+ [Bin], Dst, SuccVst)
end);
-valfun_4({bs_put_string,Sz,_}, Vst) when is_integer(Sz) ->
+vi({bs_put_string,Sz,_}, Vst) when is_integer(Sz) ->
Vst;
-valfun_4({bs_put_binary,{f,Fail},Sz,_,_,Src}, Vst) ->
+vi({bs_put_binary,{f,Fail},Sz,_,_,Src}, Vst) ->
assert_term(Sz, Vst),
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, binary, Src, SuccVst)
+ update_type(fun meet/2, #t_bitstring{}, Src, SuccVst)
end);
-valfun_4({bs_put_float,{f,Fail},Sz,_,_,Src}, Vst) ->
+vi({bs_put_float,{f,Fail},Sz,_,_,Src}, Vst) ->
assert_term(Sz, Vst),
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {float,[]}, Src, SuccVst)
+ update_type(fun meet/2, #t_float{}, Src, SuccVst)
end);
-valfun_4({bs_put_integer,{f,Fail},Sz,_,_,Src}, Vst) ->
+vi({bs_put_integer,{f,Fail},Sz,_,_,Src}, Vst) ->
assert_term(Sz, Vst),
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ update_type(fun meet/2, #t_integer{}, Src, SuccVst)
end);
-valfun_4({bs_put_utf8,{f,Fail},_,Src}, Vst) ->
+vi({bs_put_utf8,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ update_type(fun meet/2, #t_integer{}, Src, SuccVst)
end);
-valfun_4({bs_put_utf16,{f,Fail},_,Src}, Vst) ->
+vi({bs_put_utf16,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ update_type(fun meet/2, #t_integer{}, Src, SuccVst)
end);
-valfun_4({bs_put_utf32,{f,Fail},_,Src}, Vst) ->
+vi({bs_put_utf32,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
branch(Fail, Vst,
fun(SuccVst) ->
- update_type(fun meet/2, {integer,[]}, Src, SuccVst)
+ update_type(fun meet/2, #t_integer{}, Src, SuccVst)
end);
-valfun_4(_, _) ->
+
+vi(_, _) ->
error(unknown_instruction).
+validate_var_info([{fun_type, Type} | Info], Reg, Vst0) ->
+ %% Explicit type information inserted after make_fun2 instructions to mark
+ %% the return type of the created fun.
+ Vst = update_type(fun meet/2, #t_fun{type=Type}, Reg, Vst0),
+ validate_var_info(Info, Reg, Vst);
+validate_var_info([{type, none} | _Info], _Reg, Vst) ->
+ %% Unreachable code, typically after a call that never returns.
+ kill_state(Vst);
+validate_var_info([{type, Type} | Info], Reg, Vst0) ->
+ %% Explicit type information inserted by optimization passes to indicate
+ %% that Reg has a certain type, so that we can accept cross-function type
+ %% optimizations.
+ Vst = update_type(fun meet/2, Type, Reg, Vst0),
+ validate_var_info(Info, Reg, Vst);
+validate_var_info([_ | Info], Reg, Vst) ->
+ validate_var_info(Info, Reg, Vst);
+validate_var_info([], _Reg, Vst) ->
+ Vst.
+
+%% Tail call.
+%% The stackframe must have a known size and be initialized.
+%% Does not return to the instruction following the call.
+validate_tail_call(Deallocate, Func, Live, #vst{current=#st{numy=NumY}}=Vst0) ->
+ verify_y_init(Vst0),
+ verify_live(Live, Vst0),
+ verify_call_args(Func, Live, Vst0),
+
+ case will_call_succeed(Func, Vst0) of
+ yes when Deallocate =:= NumY ->
+ %% The call cannot fail; we don't need to handle exceptions
+ Vst = deallocate(Vst0),
+ verify_return(Vst);
+ maybe when Deallocate =:= NumY ->
+ %% The call may fail; make sure we update exception state
+ Vst = deallocate(Vst0),
+ branch(?EXCEPTION_LABEL, Vst, fun verify_return/1);
+ no ->
+ %% The compiler is allowed to emit garbage values for "Deallocate"
+ %% as we know that it will not be used in this case.
+ branch(?EXCEPTION_LABEL, Vst0, fun kill_state/1);
+ _ when Deallocate =/= NumY ->
+ error({allocated, NumY})
+ end.
+
+%% A "plain" call.
+%% The stackframe must be initialized.
+%% The instruction will return to the instruction following the call.
+validate_body_call(Func, Live,
+ #vst{current=#st{numy=NumY}}=Vst) when is_integer(NumY)->
+ verify_y_init(Vst),
+ verify_live(Live, Vst),
+ verify_call_args(Func, Live, Vst),
+
+ SuccFun = fun(SuccVst0) ->
+ {RetType, _, _} = call_types(Func, Live, SuccVst0),
+ true = RetType =/= none, %Assertion.
+
+ SuccVst = schedule_out(0, SuccVst0),
+
+ create_term(RetType, call, [], {x,0}, SuccVst)
+ end,
+
+ case will_call_succeed(Func, Vst) of
+ yes ->
+ SuccFun(Vst);
+ maybe ->
+ branch(?EXCEPTION_LABEL, Vst, SuccFun);
+ no ->
+ branch(?EXCEPTION_LABEL, Vst, fun kill_state/1)
+ end;
+validate_body_call(_, _, #vst{current=#st{numy=NumY}}) ->
+ error({allocated, NumY}).
+
+init_try_catch_branch(Kind, Dst, Fail, Vst0) ->
+ assert_no_exception(Fail),
+
+ Tag = {Kind, [Fail]},
+ Vst = create_tag(Tag, 'try_catch', [], Dst, Vst0),
+
+ #vst{current=St0} = Vst,
+ #st{ct=Tags}=St0,
+ St = St0#st{ct=[Tag|Tags]},
+
+ Vst#vst{current=St}.
+
+verify_has_map_fields(Lbl, Src, List, Vst) ->
+ assert_type(#t_map{}, Src, Vst),
+ assert_unique_map_keys(List),
+ verify_map_fields(List, Src, Lbl, Vst).
+
+verify_map_fields([Key | Keys], Map, Lbl, Vst) ->
+ assert_term(Key, Vst),
+ case bif_types(map_get, [Key, Map], Vst) of
+ {none, _, _} -> kill_state(Vst);
+ {_, _, _} -> verify_map_fields(Keys, Map, Lbl, Vst)
+ end;
+verify_map_fields([], _Map, Lbl, Vst) ->
+ branch(Lbl, Vst).
+
verify_get_map(Fail, Src, List, Vst0) ->
+ assert_no_exception(Fail),
assert_not_literal(Src), %OTP 22.
- assert_type(map, Src, Vst0),
+ assert_type(#t_map{}, Src, Vst0),
branch(Fail, Vst0,
fun(FailVst) ->
@@ -1102,7 +1210,7 @@ verify_get_map(Fail, Src, List, Vst0) ->
clobber_map_vals([Key,Dst|T], Map, Vst0) ->
case is_reg_initialized(Dst, Vst0) of
true ->
- Vst = extract_term(term, {bif,map_get}, [Key, Map], Dst, Vst0),
+ Vst = extract_term(any, {bif,map_get}, [Key, Map], Dst, Vst0),
clobber_map_vals(T, Map, Vst);
false ->
clobber_map_vals(T, Map, Vst0)
@@ -1125,28 +1233,55 @@ extract_map_keys([Key,_Val|T]) ->
[Key|extract_map_keys(T)];
extract_map_keys([]) -> [].
-extract_map_vals([Key,Dst|Vs], Map, Vst0, Vsti0) ->
+extract_map_vals([Key, Dst | Vs], Map, Vst0, Vsti0) ->
assert_term(Key, Vst0),
- Vsti = extract_term(term, {bif,map_get}, [Key, Map], Dst, Vsti0),
- extract_map_vals(Vs, Map, Vst0, Vsti);
+ case bif_types(map_get, [Key, Map], Vst0) of
+ {none, _, _} ->
+ kill_state(Vsti0);
+ {DstType, _, _} ->
+ Vsti = extract_term(DstType, {bif,map_get}, [Key, Map], Dst, Vsti0),
+ extract_map_vals(Vs, Map, Vst0, Vsti)
+ end;
extract_map_vals([], _Map, _Vst0, Vst) ->
Vst.
verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) ->
- assert_type(map, Src, Vst0),
+ assert_type(#t_map{}, Src, Vst0),
verify_live(Live, Vst0),
verify_y_init(Vst0),
+
_ = [assert_term(Term, Vst0) || Term <- List],
Vst = heap_alloc(0, Vst0),
- branch(Fail, Vst,
- fun(SuccVst0) ->
- SuccVst = prune_x_regs(Live, SuccVst0),
- Keys = extract_map_keys(List),
- assert_unique_map_keys(Keys),
- create_term(map, Op, [Src], Dst, SuccVst, SuccVst0)
- end).
+ SuccFun = fun(SuccVst0) ->
+ SuccVst = prune_x_regs(Live, SuccVst0),
+ Keys = extract_map_keys(List),
+ assert_unique_map_keys(Keys),
+ Type = put_map_type(Src, List, Vst),
+ create_term(Type, Op, [Src], Dst, SuccVst, SuccVst0)
+ end,
+
+ case Op of
+ put_map_exact ->
+ branch(Fail, Vst, SuccFun);
+ put_map_assoc ->
+ %% This instruction cannot fail.
+ ?EXCEPTION_LABEL = Fail, %Assertion.
+ SuccFun(Vst)
+ end.
+
+put_map_type(Map0, List, Vst) ->
+ Map = normalize(get_term_type(Map0, Vst)),
+ pmt_1(List, Vst, Map).
+
+pmt_1([Key0, Value0 | List], Vst, Acc0) ->
+ Key = normalize(get_term_type(Key0, Vst)),
+ Value = normalize(get_term_type(Value0, Vst)),
+ {Acc, _, _} = beam_call_types:types(maps, put, [Key, Value, Acc0]),
+ pmt_1(List, Vst, Acc);
+pmt_1([], _Vst, Acc) ->
+ Acc.
%%
%% Common code for validating returns, whether naked or as part of a tail call.
@@ -1172,66 +1307,225 @@ verify_return(#vst{current=#st{recv_marker=Mark}}) when Mark =/= none ->
error({return_with_receive_marker,Mark});
verify_return(Vst) ->
verify_no_ct(Vst),
- ok.
+ kill_state(Vst).
+
+%%
+%% Common code for validating BIFs.
+%%
+%% OrigVst is the state we entered the instruction with, which is needed for
+%% gc_bifs as X registers are pruned prior to calling this function, which may
+%% have clobbered the sources.
+%%
+
+validate_bif(Kind, Op, Fail, Ss, Dst, OrigVst, Vst) ->
+ case {will_bif_succeed(Op, Ss, Vst), Fail} of
+ {yes, _} ->
+ %% This BIF cannot fail (neither throw nor branch), make sure it's
+ %% handled without updating exception state.
+ validate_bif_1(Kind, Op, cannot_fail, Ss, Dst, OrigVst, Vst);
+ {no, _} ->
+ %% This BIF always fails; jump directly to the fail block or
+ %% exception handler.
+ branch(Fail, Vst, fun kill_state/1);
+ {maybe, _} ->
+ validate_bif_1(Kind, Op, Fail, Ss, Dst, OrigVst, Vst)
+ end.
+
+validate_bif_1(Kind, Op, cannot_fail, Ss, Dst, OrigVst, Vst0) ->
+ %% This BIF explicitly cannot fail; it will not jump to a guard nor throw
+ %% an exception. Validation will fail if it returns 'none' or has a type
+ %% conflict on one of its arguments.
+
+ {Type, ArgTypes, _CanSubtract} = bif_types(Op, Ss, Vst0),
+ ZippedArgs = zip(Ss, ArgTypes),
+
+ Vst = foldl(fun({A, T}, V) ->
+ update_type(fun meet/2, T, A, V)
+ end, Vst0, ZippedArgs),
+
+ true = Type =/= none, %Assertion.
+
+ extract_term(Type, {Kind, Op}, Ss, Dst, Vst, OrigVst);
+validate_bif_1(Kind, Op, Fail, Ss, Dst, OrigVst, Vst) ->
+ {Type, ArgTypes, CanSubtract} = bif_types(Op, Ss, Vst),
+ ZippedArgs = zip(Ss, ArgTypes),
+
+ FailFun = case CanSubtract of
+ true ->
+ fun(FailVst0) ->
+ foldl(fun({A, T}, V) ->
+ update_type(fun subtract/2, T, A, V)
+ end, FailVst0, ZippedArgs)
+ end;
+ false ->
+ fun(S) -> S end
+ end,
+ SuccFun = fun(SuccVst0) ->
+ SuccVst = foldl(fun({A, T}, V) ->
+ update_type(fun meet/2, T, A, V)
+ end, SuccVst0, ZippedArgs),
+ extract_term(Type, {Kind, Op}, Ss, Dst, SuccVst, OrigVst)
+ end,
+
+ branch(Fail, Vst, FailFun, SuccFun).
%%
%% Common code for validating bs_start_match* instructions.
%%
-validate_bs_start_match(Fail, Live, Type, Src, Dst, Vst) ->
- verify_live(Live, Vst),
- verify_y_init(Vst),
+validate_bs_start_match({atom,resume}, Live, 0, Src, Dst, Vst0) ->
+ assert_type(#t_bs_context{}, Src, Vst0),
+ verify_live(Live, Vst0),
+ verify_y_init(Vst0),
+
+ Vst = assign(Src, Dst, Vst0),
+ prune_x_regs(Live, Vst);
+validate_bs_start_match({atom,no_fail}, Live, Slots, Src, Dst, Vst0) ->
+ verify_live(Live, Vst0),
+ verify_y_init(Vst0),
+
+ Vst1 = update_type(fun meet/2, #t_bs_matchable{}, Src, Vst0),
+
+ %% Retain the current unit, if known.
+ SrcType = get_movable_term_type(Src, Vst1),
+ TailUnit = beam_types:get_bs_matchable_unit(SrcType),
+
+ CtxType = #t_bs_context{slots=Slots,tail_unit=TailUnit},
+
+ Vst = prune_x_regs(Live, Vst1),
+ extract_term(CtxType, bs_start_match, [Src], Dst, Vst, Vst0);
+validate_bs_start_match({f,Fail}, Live, Slots, Src, Dst, Vst) ->
+ assert_no_exception(Fail),
- %% #ms{} can represent either a match context or a term, so we have to mark
- %% the source as a term if it fails with a match context as an input. This
- %% hack is only needed until we get proper union types.
branch(Fail, Vst,
fun(FailVst) ->
- case get_movable_term_type(Src, FailVst) of
- #ms{} -> override_type(term, Src, FailVst);
- _ -> FailVst
- end
+ update_type(fun subtract/2, #t_bs_matchable{}, Src, FailVst)
end,
- fun(SuccVst0) ->
- SuccVst1 = update_type(fun meet/2, binary,
- Src, SuccVst0),
- SuccVst = prune_x_regs(Live, SuccVst1),
- extract_term(Type, bs_start_match, [Src], Dst,
- SuccVst, SuccVst0)
+ fun(SuccVst) ->
+ validate_bs_start_match({atom,no_fail}, Live, Slots,
+ Src, Dst, SuccVst)
end).
%%
%% Common code for validating bs_get* instructions.
%%
-validate_bs_get(Op, Fail, Ctx, Live, Type, Dst, Vst) ->
- bsm_validate_context(Ctx, Vst),
+validate_bs_get(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst) ->
+ assert_no_exception(Fail),
+
+ assert_type(#t_bs_context{}, Ctx, Vst),
verify_live(Live, Vst),
verify_y_init(Vst),
branch(Fail, Vst,
fun(SuccVst0) ->
- SuccVst = prune_x_regs(Live, SuccVst0),
+ SuccVst1 = advance_bs_context(Ctx, Stride, SuccVst0),
+ SuccVst = prune_x_regs(Live, SuccVst1),
+ extract_term(Type, Op, [Ctx], Dst, SuccVst, SuccVst0)
+ end).
+
+validate_bs_get_all(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst) ->
+ assert_no_exception(Fail),
+
+ assert_type(#t_bs_context{}, Ctx, Vst),
+ verify_live(Live, Vst),
+ verify_y_init(Vst),
+
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ %% This acts as an implicit unit test on the current match
+ %% position, so we'll update the unit in case we rewind here
+ %% later on.
+ SuccVst1 = update_bs_unit(Ctx, Stride, SuccVst0),
+
+ SuccVst2 = advance_bs_context(Ctx, Stride, SuccVst1),
+ SuccVst = prune_x_regs(Live, SuccVst2),
extract_term(Type, Op, [Ctx], Dst, SuccVst, SuccVst0)
end).
%%
-%% Common code for validating bs_skip_utf* instructions.
+%% Common code for validating bs_skip* instructions.
%%
-validate_bs_skip_utf(Fail, Ctx, Live, Vst) ->
- bsm_validate_context(Ctx, Vst),
- verify_y_init(Vst),
- verify_live(Live, Vst),
+validate_bs_skip(Fail, Ctx, Stride, Vst) ->
+ validate_bs_skip(Fail, Ctx, Stride, no_live, Vst).
+
+validate_bs_skip(Fail, Ctx, Stride, Live, Vst) ->
+ assert_no_exception(Fail),
+
+ assert_type(#t_bs_context{}, Ctx, Vst),
+ validate_bs_skip_1(Fail, Ctx, Stride, Live, Vst).
+
+validate_bs_skip_1(Fail, Ctx, Stride, no_live, Vst) ->
branch(Fail, Vst,
fun(SuccVst) ->
+ advance_bs_context(Ctx, Stride, SuccVst)
+ end);
+validate_bs_skip_1(Fail, Ctx, Stride, Live, Vst) ->
+ verify_y_init(Vst),
+ verify_live(Live, Vst),
+ branch(Fail, Vst,
+ fun(SuccVst0) ->
+ SuccVst = advance_bs_context(Ctx, Stride, SuccVst0),
prune_x_regs(Live, SuccVst)
end).
+advance_bs_context(Ctx, Stride, Vst0) ->
+ %% slots/valid must remain untouched to support +r21, and the prior unit
+ %% must be retained if we _KNOW_ we won't advance.
+ CtxType0 = get_raw_type(Ctx, Vst0),
+ CtxType = case Stride of
+ 0 -> CtxType0;
+ N -> CtxType0#t_bs_context{ tail_unit=N }
+ end,
+
+ Vst = update_type(fun join/2, CtxType, Ctx, Vst0),
+
+ %% The latest saved position (if any) is no longer current, make sure
+ %% it isn't updated on the next match operation.
+ invalidate_current_ms_position(Ctx, Vst).
+
+%% Updates the unit of our latest saved position, if it's current.
+update_bs_unit(Ctx, Unit, #vst{current=St}=Vst) ->
+ CtxRef = get_reg_vref(Ctx, Vst),
+
+ case St#st.ms_positions of
+ #{ CtxRef := PosRef } ->
+ PosType = #t_abstract{kind={ms_position, Unit}},
+ update_type(fun meet/2, PosType, PosRef, Vst);
+ #{} ->
+ Vst
+ end.
+
+mark_current_ms_position(Ctx, Pos, #vst{current=St0}=Vst) ->
+ CtxRef = get_reg_vref(Ctx, Vst),
+ PosRef = get_reg_vref(Pos, Vst),
+
+ #st{ms_positions=MsPos0} = St0,
+
+ MsPos = MsPos0#{ CtxRef => PosRef },
+
+ St = St0#st{ ms_positions=MsPos },
+ Vst#vst{current=St}.
+
+invalidate_current_ms_position(Ctx, #vst{current=St0}=Vst) ->
+ CtxRef = get_reg_vref(Ctx, Vst),
+ #st{ms_positions=MsPos0} = St0,
+
+ case MsPos0 of
+ #{ CtxRef := _ } ->
+ MsPos = maps:remove(CtxRef, MsPos0),
+ St = St0#st{ms_positions=MsPos},
+ Vst#vst{current=St};
+ #{} ->
+ Vst
+ end.
+
%%
%% Common code for is_$type instructions.
%%
type_test(Fail, Type, Reg, Vst) ->
assert_term(Reg, Vst),
+ assert_no_exception(Fail),
branch(Fail, Vst,
fun(FailVst) ->
update_type(fun subtract/2, Type, Reg, FailVst)
@@ -1247,60 +1541,44 @@ type_test(Fail, Type, Reg, Vst) ->
%%
%% Note that #vst.current will be 'none' if the instruction is unreachable.
%%
-val_dsetel({move,_,_}, Vst) ->
+
+validate_mutation(I, Vst) ->
+ vm_1(I, Vst).
+
+vm_1({move,_,_}, Vst) ->
Vst;
-val_dsetel({call_ext,3,{extfunc,erlang,setelement,3}}, #vst{current=#st{}=St}=Vst) ->
+vm_1({swap,_,_}, Vst) ->
+ Vst;
+vm_1({call_ext,3,{extfunc,erlang,setelement,3}}, #vst{current=#st{}=St}=Vst) ->
Vst#vst{current=St#st{setelem=true}};
-val_dsetel({set_tuple_element,_,_,_}, #vst{current=#st{setelem=false}}) ->
+vm_1({set_tuple_element,_,_,_}, #vst{current=#st{setelem=false}}) ->
error(illegal_context_for_set_tuple_element);
-val_dsetel({set_tuple_element,_,_,_}, #vst{current=#st{setelem=true}}=Vst) ->
+vm_1({set_tuple_element,_,_,_}, #vst{current=#st{setelem=true}}=Vst) ->
Vst;
-val_dsetel({get_tuple_element,_,_,_}, Vst) ->
+vm_1({get_tuple_element,_,_,_}, Vst) ->
Vst;
-val_dsetel({line,_}, Vst) ->
+vm_1({line,_}, Vst) ->
Vst;
-val_dsetel(_, #vst{current=#st{setelem=true}=St}=Vst) ->
+vm_1(_, #vst{current=#st{setelem=true}=St}=Vst) ->
Vst#vst{current=St#st{setelem=false}};
-val_dsetel(_, Vst) -> Vst.
+vm_1(_, Vst) -> Vst.
kill_state(Vst) ->
Vst#vst{current=none}.
-%% A "plain" call.
-%% The stackframe must be initialized.
-%% The instruction will return to the instruction following the call.
-call(Name, Live, #vst{current=St0}=Vst0) ->
- verify_call_args(Name, Live, Vst0),
- verify_y_init(Vst0),
- case call_return_type(Name, Vst0) of
- Type when Type =/= exception ->
- %% Type is never 'exception' because it has been handled earlier.
- St = St0#st{f=init_fregs()},
- Vst = prune_x_regs(0, Vst0#vst{current=St}),
- create_term(Type, call, [], {x,0}, Vst)
- end.
-
-%% Tail call.
-%% The stackframe must have a known size and be initialized.
-%% Does not return to the instruction following the call.
-tail_call(Name, Live, Vst0) ->
- verify_y_init(Vst0),
- Vst = deallocate(Vst0),
- verify_call_args(Name, Live, Vst),
- case call_return_type(Name, Vst0) of
- exception -> verify_no_ct(Vst);
- _ -> verify_return(Vst)
- end,
- kill_state(Vst).
-
verify_call_args(_, 0, #vst{}) ->
ok;
-verify_call_args({f,Lbl}, Live, Vst) when is_integer(Live)->
- verify_local_args(Live - 1, Lbl, #{}, Vst);
-verify_call_args(_, Live, Vst) when is_integer(Live)->
- verify_remote_args_1(Live - 1, Vst);
-verify_call_args(_, Live, _) ->
- error({bad_number_of_live_regs,Live}).
+verify_call_args({f,Lbl}, Live, #vst{ft=Ft}=Vst) ->
+ case Ft of
+ #{ Lbl := FuncInfo } ->
+ #{ arity := Live,
+ parameter_info := ParamInfo } = FuncInfo,
+ verify_local_args(Live - 1, ParamInfo, #{}, Vst);
+ #{} ->
+ error(local_call_to_unknown_function)
+ end;
+verify_call_args(_, Live, Vst) ->
+ verify_remote_args_1(Live - 1, Vst).
verify_remote_args_1(-1, _) ->
ok;
@@ -1308,86 +1586,81 @@ verify_remote_args_1(X, Vst) ->
assert_durable_term({x, X}, Vst),
verify_remote_args_1(X - 1, Vst).
-verify_local_args(-1, _Lbl, _CtxIds, _Vst) ->
+verify_local_args(-1, _ParamInfo, _CtxIds, _Vst) ->
ok;
-verify_local_args(X, Lbl, CtxIds, Vst) ->
+verify_local_args(X, ParamInfo, CtxRefs, Vst) ->
Reg = {x, X},
assert_not_fragile(Reg, Vst),
case get_movable_term_type(Reg, Vst) of
- #ms{id=Id}=Type ->
- case CtxIds of
- #{ Id := Other } ->
+ #t_bs_context{}=Type ->
+ VRef = get_reg_vref(Reg, Vst),
+ case CtxRefs of
+ #{ VRef := Other } ->
error({multiple_match_contexts, [Reg, Other]});
#{} ->
- verify_arg_type(Lbl, Reg, Type, Vst),
- verify_local_args(X - 1, Lbl, CtxIds#{ Id => Reg }, Vst)
+ verify_arg_type(Reg, Type, ParamInfo, Vst),
+ verify_local_args(X - 1, ParamInfo,
+ CtxRefs#{ VRef => Reg }, Vst)
end;
Type ->
- verify_arg_type(Lbl, Reg, Type, Vst),
- verify_local_args(X - 1, Lbl, CtxIds, Vst)
+ verify_arg_type(Reg, Type, ParamInfo, Vst),
+ verify_local_args(X - 1, ParamInfo, CtxRefs, Vst)
end.
-%% Verifies that the given argument narrows to what the function expects.
-verify_arg_type(Lbl, Reg, #ms{}, #vst{ft=Ft}) ->
- %% Match contexts require explicit support, and may not be passed to a
- %% function that accepts arbitrary terms.
- case gb_trees:lookup({Lbl, Reg}, Ft) of
- {value, #ms{}} -> ok;
- _ -> error(no_bs_start_match2)
- end;
-verify_arg_type(Lbl, Reg, GivenType, #vst{ft=Ft}) ->
- case gb_trees:lookup({Lbl, Reg}, Ft) of
- {value, #ms{}} ->
- %% Functions that accept match contexts also accept all other
- %% terms. This will change once we support union types.
- ok;
- {value, RequiredType} ->
- case vat_1(GivenType, RequiredType) of
- true -> ok;
- false -> error({bad_arg_type, Reg, GivenType, RequiredType})
+verify_arg_type(Reg, GivenType, ParamInfo, Vst) ->
+ case {ParamInfo, GivenType} of
+ {#{ Reg := Info }, #t_bs_context{}} ->
+ %% Match contexts require explicit support, and may not be passed
+ %% to a function that accepts arbitrary terms.
+ case member(accepts_match_context, Info) of
+ true -> verify_arg_type_1(Reg, GivenType, Info, Vst);
+ false -> error(no_bs_start_match2)
end;
- none ->
+ {_, #t_bs_context{}} ->
+ error(no_bs_start_match2);
+ {#{ Reg := Info }, _} ->
+ verify_arg_type_1(Reg, GivenType, Info, Vst);
+ {#{}, _} ->
ok
end.
-%% Checks whether the Given argument is compatible with the Required one. This
-%% is essentially a relaxed version of 'meet(Given, Req) =:= Given', where we
-%% accept that the Given value has the right type but not necessarily the exact
-%% same value; if {atom,gurka} is required, we'll consider {atom,[]} valid.
-%%
-%% This will catch all problems that could crash the emulator, like passing a
-%% 1-tuple when the callee expects a 3-tuple, but some value errors might slip
-%% through.
-vat_1(Same, Same) -> true;
-vat_1({atom,A}, {atom,B}) -> A =:= B orelse is_list(A) orelse is_list(B);
-vat_1({atom,A}, bool) -> is_boolean(A) orelse is_list(A);
-vat_1(bool, {atom,B}) -> is_boolean(B) orelse is_list(B);
-vat_1(cons, list) -> true;
-vat_1({float,A}, {float,B}) -> A =:= B orelse is_list(A) orelse is_list(B);
-vat_1({float,_}, number) -> true;
-vat_1({integer,A}, {integer,B}) -> A =:= B orelse is_list(A) orelse is_list(B);
-vat_1({integer,_}, number) -> true;
-vat_1(_, {literal,_}) -> false;
-vat_1({literal,_}=Lit, Required) -> vat_1(get_literal_type(Lit), Required);
-vat_1(nil, list) -> true;
-vat_1({tuple,SzA,EsA}, {tuple,SzB,EsB}) ->
- if
- is_list(SzB) ->
- tuple_sz(SzA) >= tuple_sz(SzB) andalso vat_elements(EsA, EsB);
- SzA =:= SzB ->
- vat_elements(EsA, EsB);
- SzA =/= SzB ->
- false
- end;
-vat_1(_, _) -> false.
-
-vat_elements(EsA, EsB) ->
- maps:fold(fun(Key, Req, Acc) ->
- case EsA of
- #{ Key := Given } -> Acc andalso vat_1(Given, Req);
- #{} -> false
- end
- end, true, EsB).
+verify_arg_type_1(Reg, GivenType, Info, Vst) ->
+ RequiredType = proplists:get_value(type, Info, any),
+ case meet(GivenType, RequiredType) of
+ Type when Type =/= none, Vst#vst.level =:= weak ->
+ %% Strictly speaking this should always match GivenType, but
+ %% beam_jump:share/1 sometimes joins blocks in a manner that makes
+ %% it very difficult to accurately reconstruct type information,
+ %% for example:
+ %%
+ %% bug({Tag, _, _} = Key) when Tag == a; Tag == b ->
+ %% foo(Key);
+ %% bug({Tag, _} = Key) when Tag == a; Tag == b ->
+ %% foo(Key).
+ %%
+ %% foo(I) -> I.
+ %%
+ %% At the first call to foo/1, we know that we have either `{a,_}`
+ %% or `{b,_}`, and at the second call `{a,_,_}` or `{b,_,_}`.
+ %%
+ %% When both calls to foo/1 are joined into the same block, all we
+ %% know is that we have tuple with an arity of at least 2, whose
+ %% first element is `a` or `b`.
+ %%
+ %% Fixing this properly is a big undertaking, so for now we've
+ %% decided on a compromise that splits validation into two steps.
+ %%
+ %% We run a 'strong' pass directly after code generation in which
+ %% arguments must be at least as narrow as expected, and a 'weak'
+ %% pass after all optimizations have been applied in which we
+ %% tolerate arguments that aren't in direct conflict.
+ ok;
+ GivenType ->
+ true = GivenType =/= none, %Assertion.
+ ok;
+ _ ->
+ error({bad_arg_type, Reg, GivenType, RequiredType})
+ end.
allocate(Tag, Stk, Heap, Live, #vst{current=#st{numy=none}=St}=Vst0) ->
verify_live(Live, Vst0),
@@ -1429,22 +1702,29 @@ test_heap(Heap, Live, Vst0) ->
heap_alloc(Heap, Vst).
heap_alloc(Heap, #vst{current=St0}=Vst) ->
- St1 = kill_heap_allocation(St0),
- St = heap_alloc_1(Heap, St1),
+ {HeapWords, Floats} = heap_alloc_1(Heap),
+
+ St = St0#st{h=HeapWords,hf=Floats},
+
Vst#vst{current=St}.
-heap_alloc_1({alloc,Alloc}, St) ->
- heap_alloc_2(Alloc, St);
-heap_alloc_1(HeapWords, St) when is_integer(HeapWords) ->
- St#st{h=HeapWords}.
+heap_alloc_1({alloc, Alloc}) ->
+ heap_alloc_2(Alloc, 0, 0);
+heap_alloc_1(HeapWords) when is_integer(HeapWords) ->
+ {HeapWords, 0}.
-heap_alloc_2([{words,HeapWords}|T], St0) ->
- St = St0#st{h=HeapWords},
- heap_alloc_2(T, St);
-heap_alloc_2([{floats,Floats}|T], St0) ->
- St = St0#st{hf=Floats},
- heap_alloc_2(T, St);
-heap_alloc_2([], St) -> St.
+heap_alloc_2([{words, HeapWords} | T], 0, Floats) ->
+ heap_alloc_2(T, HeapWords, Floats);
+heap_alloc_2([{floats, Floats} | T], HeapWords, 0) ->
+ heap_alloc_2(T, HeapWords, Floats);
+heap_alloc_2([], HeapWords, Floats) ->
+ {HeapWords, Floats}.
+
+schedule_out(Live, Vst0) when is_integer(Live) ->
+ Vst1 = prune_x_regs(Live, Vst0),
+ Vst2 = kill_heap_allocation(Vst1),
+ Vst = kill_fregs(Vst2),
+ set_receive_marker(none, Vst).
prune_x_regs(Live, #vst{current=St0}=Vst) when is_integer(Live) ->
#st{fragile=Fragile0,xs=Xs0} = St0,
@@ -1483,43 +1763,29 @@ assert_arities(_) -> error(bad_tuple_arity_list).
%%%
-%%% Floating point checking.
-%%%
-%%% Possible values for the fls field (=floating point error state).
-%%%
-%%% undefined - Undefined (initial state). No float operations allowed.
-%%%
-%%% cleared - fclearerror/0 has been executed. Float operations
-%%% are allowed (such as fadd).
-%%%
-%%% checked - fcheckerror/1 has been executed. It is allowed to
-%%% move values out of floating point registers.
-%%%
-%%% The following instructions may be executed in any state:
+%%% Floating point helpers.
%%%
-%%% fconv Src {fr,_}
-%%% fmove Src {fr,_} %% Move INTO floating point register.
+%%% fconv Src {fr,_}
+%%% fmove Src {fr,_} %% Move known float INTO floating point register.
%%%
-float_op(Ss, Dst, Vst0) ->
- _ = [assert_freg_set(S, Vst0) || S <- Ss],
- assert_fls(cleared, Vst0),
- Vst = set_fls(cleared, Vst0),
- set_freg(Dst, Vst).
-
-assert_fls(Fls, Vst) ->
- case get_fls(Vst) of
- Fls -> ok;
- OtherFls -> error({bad_floating_point_state,OtherFls})
- end.
+is_float_arith_bif(fadd, [_, _]) -> true;
+is_float_arith_bif(fdiv, [_, _]) -> true;
+is_float_arith_bif(fmul, [_, _]) -> true;
+is_float_arith_bif(fnegate, [_]) -> true;
+is_float_arith_bif(fsub, [_, _]) -> true;
+is_float_arith_bif(_, _) -> false.
-set_fls(Fls, #vst{current=#st{}=St}=Vst) when is_atom(Fls) ->
- Vst#vst{current=St#st{fls=Fls}}.
-
-get_fls(#vst{current=#st{fls=Fls}}) when is_atom(Fls) -> Fls.
+validate_float_arith_bif(Ss, Dst, Vst) ->
+ _ = [assert_freg_set(S, Vst) || S <- Ss],
+ set_freg(Dst, Vst).
init_fregs() -> 0.
+kill_fregs(#vst{current=St0}=Vst) ->
+ St = St0#st{f=init_fregs()},
+ Vst#vst{current=St}.
+
set_freg({fr,Fr}=Freg, #vst{current=#st{f=Fregs0}=St}=Vst) ->
check_limit(Freg),
Bit = 1 bsl Fr,
@@ -1560,7 +1826,7 @@ assert_unique_map_keys([_,_|_]=Ls) ->
assert_literal(L),
L
end || L <- Ls],
- case length(Vs) =:= sets:size(sets:from_list(Vs)) of
+ case length(Vs) =:= cerl_sets:size(cerl_sets:from_list(Vs)) of
true -> ok;
false -> error(keys_not_unique)
end.
@@ -1569,49 +1835,35 @@ assert_unique_map_keys([_,_|_]=Ls) ->
%%% New binary matching instructions.
%%%
-bsm_match_state() ->
- #ms{}.
-bsm_match_state(Slots) ->
- #ms{slots=Slots}.
-
-bsm_validate_context(Reg, Vst) ->
- _ = bsm_get_context(Reg, Vst),
- ok.
-
-bsm_get_context({Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y->
- case get_movable_term_type(Reg, Vst) of
- #ms{}=Ctx -> Ctx;
- _ -> error({no_bsm_context,Reg})
- end;
-bsm_get_context(Reg, _) ->
- error({bad_source,Reg}).
-
bsm_save(Reg, {atom,start}, Vst) ->
%% Save point refering to where the match started.
%% It is always valid. But don't forget to validate the context register.
- bsm_validate_context(Reg, Vst),
+ assert_type(#t_bs_context{}, Reg, Vst),
Vst;
bsm_save(Reg, SavePoint, Vst) ->
- case bsm_get_context(Reg, Vst) of
- #ms{valid=Bits,slots=Slots}=Ctxt0 when SavePoint < Slots ->
- Ctx = Ctxt0#ms{valid=Bits bor (1 bsl SavePoint),slots=Slots},
- override_type(Ctx, Reg, Vst);
- _ -> error({illegal_save,SavePoint})
+ case get_movable_term_type(Reg, Vst) of
+ #t_bs_context{valid=Bits,slots=Slots}=Ctxt0 when SavePoint < Slots ->
+ Ctx = Ctxt0#t_bs_context{valid=Bits bor (1 bsl SavePoint),
+ slots=Slots},
+ override_type(Ctx, Reg, Vst);
+ _ ->
+ error({illegal_save, SavePoint})
end.
bsm_restore(Reg, {atom,start}, Vst) ->
%% (Mostly) automatic save point refering to where the match started.
%% It is always valid. But don't forget to validate the context register.
- bsm_validate_context(Reg, Vst),
+ assert_type(#t_bs_context{}, Reg, Vst),
Vst;
bsm_restore(Reg, SavePoint, Vst) ->
- case bsm_get_context(Reg, Vst) of
- #ms{valid=Bits,slots=Slots} when SavePoint < Slots ->
- case Bits band (1 bsl SavePoint) of
- 0 -> error({illegal_restore,SavePoint,not_set});
- _ -> Vst
- end;
- _ -> error({illegal_restore,SavePoint,range})
+ case get_movable_term_type(Reg, Vst) of
+ #t_bs_context{valid=Bits,slots=Slots} when SavePoint < Slots ->
+ case Bits band (1 bsl SavePoint) of
+ 0 -> error({illegal_restore, SavePoint, not_set});
+ _ -> Vst
+ end;
+ _ ->
+ error({illegal_restore, SavePoint, range})
end.
validate_select_val(_Fail, _Choices, _Src, #vst{current=none}=Vst) ->
@@ -1627,7 +1879,7 @@ validate_select_val(Fail, [Val,{f,L}|T], Src, Vst0) ->
update_ne_types(Src, Val, FailVst)
end),
validate_select_val(Fail, T, Src, Vst);
-validate_select_val(Fail, [], _, Vst) ->
+validate_select_val(Fail, [], _Src, Vst) ->
branch(Fail, Vst,
fun(SuccVst) ->
%% The next instruction is never executed.
@@ -1639,7 +1891,7 @@ validate_select_tuple_arity(_Fail, _Choices, _Src, #vst{current=none}=Vst) ->
%% can't reach the fail label or any of the remaining choices.
Vst;
validate_select_tuple_arity(Fail, [Arity,{f,L}|T], Tuple, Vst0) ->
- Type = {tuple, Arity, #{}},
+ Type = #t_tuple{exact=true,size=Arity},
Vst = branch(L, Vst0,
fun(BranchVst) ->
update_type(fun meet/2, Type, Tuple, BranchVst)
@@ -1655,68 +1907,85 @@ validate_select_tuple_arity(Fail, [], _, #vst{}=Vst) ->
kill_state(SuccVst)
end).
-infer_types({Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y ->
- infer_types(get_reg_vref(Reg, Vst), Vst);
-infer_types(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) ->
+%%
+%% Infers types from comparisons, looking at the expressions that produced the
+%% compared values and updates their types if we've learned something new from
+%% the comparison.
+%%
+
+infer_types(CompareOp, {Kind,_}=LHS, RHS, Vst) when Kind =:= x; Kind =:= y ->
+ infer_types(CompareOp, get_reg_vref(LHS, Vst), RHS, Vst);
+infer_types(CompareOp, LHS, {Kind,_}=RHS, Vst) when Kind =:= x; Kind =:= y ->
+ infer_types(CompareOp, LHS, get_reg_vref(RHS, Vst), Vst);
+infer_types(CompareOp, LHS, RHS, #vst{current=#st{vs=Vs}}=Vst0) ->
case Vs of
- #{ Ref := Entry } -> infer_types_1(Entry);
- #{} -> fun(_, S) -> S end
- end;
-infer_types(_, #vst{}) ->
- fun(_, S) -> S end.
-
-infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}) ->
- fun({atom,true}, S) ->
- update_eq_types(LHS, RHS, S);
- ({atom,false}, S) ->
- update_ne_types(LHS, RHS, S);
- (_, S) -> S
- end;
-infer_types_1(#value{op={bif,'=/='},args=[LHS,RHS]}) ->
- fun({atom,true}, S) ->
- update_ne_types(LHS, RHS, S);
- ({atom,false}, S) ->
- update_eq_types(LHS, RHS, S);
- (_, S) -> S
+ #{ LHS := LEntry, RHS := REntry } ->
+ Vst = infer_types_1(LEntry, RHS, CompareOp, Vst0),
+ infer_types_1(REntry, LHS, CompareOp, Vst);
+ #{ LHS := LEntry } ->
+ infer_types_1(LEntry, RHS, CompareOp, Vst0);
+ #{ RHS := REntry } ->
+ infer_types_1(REntry, LHS, CompareOp, Vst0);
+ #{} ->
+ Vst0
+ end.
+
+infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}, Val, Op, Vst) ->
+ case Val of
+ {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
+ update_eq_types(LHS, RHS, Vst);
+ {atom, Bool} when Op =:= ne_exact, Bool; Op =:= eq_exact, not Bool ->
+ update_ne_types(LHS, RHS, Vst);
+ _ ->
+ Vst
end;
-infer_types_1(#value{op={bif,element},args=[{integer,Index}=Key,Tuple]}) ->
- fun(Val, S) ->
- Type = {tuple,[Index], #{ Key => get_term_type(Val, S) }},
- update_type(fun meet/2, Type, Tuple, S)
+infer_types_1(#value{op={bif,'=/='},args=[LHS,RHS]}, Val, Op, Vst) ->
+ case Val of
+ {atom, Bool} when Op =:= ne_exact, Bool; Op =:= eq_exact, not Bool ->
+ update_ne_types(LHS, RHS, Vst);
+ {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
+ update_eq_types(LHS, RHS, Vst);
+ _ ->
+ Vst
end;
-infer_types_1(#value{op={bif,is_atom},args=[Src]}) ->
- infer_type_test_bif({atom,[]}, Src);
-infer_types_1(#value{op={bif,is_boolean},args=[Src]}) ->
- infer_type_test_bif(bool, Src);
-infer_types_1(#value{op={bif,is_binary},args=[Src]}) ->
- infer_type_test_bif(binary, Src);
-infer_types_1(#value{op={bif,is_bitstring},args=[Src]}) ->
- infer_type_test_bif(binary, Src);
-infer_types_1(#value{op={bif,is_float},args=[Src]}) ->
- infer_type_test_bif(float, Src);
-infer_types_1(#value{op={bif,is_integer},args=[Src]}) ->
- infer_type_test_bif({integer,{}}, Src);
-infer_types_1(#value{op={bif,is_list},args=[Src]}) ->
- infer_type_test_bif(list, Src);
-infer_types_1(#value{op={bif,is_map},args=[Src]}) ->
- infer_type_test_bif(map, Src);
-infer_types_1(#value{op={bif,is_number},args=[Src]}) ->
- infer_type_test_bif(number, Src);
-infer_types_1(#value{op={bif,is_tuple},args=[Src]}) ->
- infer_type_test_bif({tuple,[0],#{}}, Src);
-infer_types_1(#value{op={bif,tuple_size}, args=[Tuple]}) ->
- fun({integer,Arity}, S) ->
- update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S);
- (_, S) -> S
+infer_types_1(#value{op={bif,is_atom},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(#t_atom{}, Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,is_boolean},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(beam_types:make_boolean(), Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,is_binary},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(#t_bitstring{size_unit=8}, Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,is_bitstring},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(#t_bitstring{}, Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,is_float},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(#t_float{}, Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,is_integer},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(#t_integer{}, Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,is_list},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(#t_list{}, Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,is_map},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(#t_map{}, Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,is_number},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(number, Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,is_tuple},args=[Src]}, Val, Op, Vst) ->
+ infer_type_test_bif(#t_tuple{}, Src, Val, Op, Vst);
+infer_types_1(#value{op={bif,tuple_size}, args=[Tuple]},
+ {integer,Arity}, Op, Vst) ->
+ Type = #t_tuple{exact=true,size=Arity},
+ case Op of
+ eq_exact -> update_type(fun meet/2, Type, Tuple, Vst);
+ ne_exact -> update_type(fun subtract/2, Type, Tuple, Vst)
end;
-infer_types_1(_) ->
- fun(_, S) -> S end.
-
-infer_type_test_bif(Type, Src) ->
- fun({atom,true}, S) ->
- update_type(fun meet/2, Type, Src, S);
- (_, S) ->
- S
+infer_types_1(_, _, _, Vst) ->
+ Vst.
+
+infer_type_test_bif(Type, Src, Val, Op, Vst) ->
+ case Val of
+ {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool ->
+ update_type(fun meet/2, Type, Src, Vst);
+ {atom, Bool} when Op =:= ne_exact, Bool; Op =:= eq_exact, not Bool ->
+ update_type(fun subtract/2, Type, Src, Vst);
+ _ ->
+ Vst
end.
%%%
@@ -1800,7 +2069,7 @@ override_type(Type, Reg, Vst) ->
%% This is used when linear code finds out more and more information about a
%% type, so that the type gets more specialized.
-update_type(Merge, With, #value_ref{}=Ref, Vst) ->
+update_type(Merge, With, #value_ref{}=Ref, Vst0) ->
%% If the old type can't be merged with the new one, the type information
%% is inconsistent and we know that some instructions will never be
%% executed at run-time. For example:
@@ -1819,34 +2088,64 @@ update_type(Merge, With, #value_ref{}=Ref, Vst) ->
%% We therefore throw a 'type_conflict' error instead, which causes
%% validation to fail unless we're in a context where such errors can be
%% handled, such as in a branch handler.
- Current = get_raw_type(Ref, Vst),
+ Current = get_raw_type(Ref, Vst0),
case Merge(Current, With) of
- none -> throw({type_conflict, Current, With});
- Type -> set_type(Type, Ref, Vst)
+ none ->
+ throw({type_conflict, Current, With});
+ Type ->
+ Vst = update_container_type(Type, Ref, Vst0),
+ set_type(Type, Ref, Vst)
end;
update_type(Merge, With, {Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y ->
update_type(Merge, With, get_reg_vref(Reg, Vst), Vst);
update_type(Merge, With, Literal, Vst) ->
- assert_literal(Literal),
%% Literals always retain their type, but we still need to bail on type
%% conflicts.
- case Merge(Literal, With) of
- none -> throw({type_conflict, Literal, With});
+ Type = get_literal_type(Literal),
+ case Merge(Type, With) of
+ none -> throw({type_conflict, Type, With});
_Type -> Vst
end.
+%% Updates the container the given value was extracted from, if any.
+update_container_type(Type, Ref, #vst{current=#st{vs=Vs}}=Vst) ->
+ case Vs of
+ #{ Ref := #value{op={bif,element},
+ args=[{integer,Index},Tuple]} } when Index >= 1 ->
+ Es = beam_types:set_tuple_element(Index, Type, #{}),
+ TupleType = #t_tuple{size=Index,elements=Es},
+ update_type(fun meet/2, TupleType, Tuple, Vst);
+ #{} ->
+ Vst
+ end.
+
+update_eq_types(LHS, RHS, Vst0) ->
+ LType = get_term_type(LHS, Vst0),
+ RType = get_term_type(RHS, Vst0),
+
+ Vst1 = update_type(fun meet/2, RType, LHS, Vst0),
+ Vst = update_type(fun meet/2, LType, RHS, Vst1),
+
+ infer_types(eq_exact, LHS, RHS, Vst).
+
update_ne_types(LHS, RHS, Vst0) ->
+ Vst1 = update_ne_types_1(LHS, RHS, Vst0),
+ Vst = update_ne_types_1(RHS, LHS, Vst1),
+
+ infer_types(ne_exact, LHS, RHS, Vst).
+
+update_ne_types_1(LHS, RHS, Vst0) ->
%% While updating types on equality is fairly straightforward, inequality
%% is a bit trickier since all we know is that the *value* of LHS differs
%% from RHS, so we can't blindly subtract their types.
%%
- %% Consider `number =/= {integer,[]}`; all we know is that LHS isn't equal
+ %% Consider `number =/= #t_integer{}`; all we know is that LHS isn't equal
%% to some *specific integer* of unknown value, and if we were to subtract
- %% {integer,[]} we would erroneously infer that the new type is {float,[]}.
+ %% #t_integer{} we would erroneously infer that the new type is float.
%%
%% Therefore, we only subtract when we know that RHS has a specific value.
RType = get_term_type(RHS, Vst0),
- case is_literal(RType) of
+ case beam_types:is_singleton_type(RType) of
true ->
Vst = update_type(fun subtract/2, RType, LHS, Vst0),
@@ -1854,27 +2153,16 @@ update_ne_types(LHS, RHS, Vst0) ->
%% as if we've made an exact match, which is much stronger than
%% ne_exact.
LType = get_term_type(LHS, Vst),
- case is_literal(LType) of
- true -> update_eq_types(LHS, LType, Vst);
- false -> Vst
+ case beam_types:get_singleton_value(LType) of
+ {ok, Value} ->
+ infer_types(eq_exact, LHS, value_to_literal(Value), Vst);
+ error ->
+ Vst
end;
false ->
Vst0
end.
-update_eq_types(LHS, RHS, Vst0) ->
- %% Either side might contain something worth inferring, so we need
- %% to check them both.
- Infer_L = infer_types(RHS, Vst0),
- Infer_R = infer_types(LHS, Vst0),
- Vst1 = Infer_R(RHS, Infer_L(LHS, Vst0)),
-
- T1 = get_term_type(LHS, Vst1),
- T2 = get_term_type(RHS, Vst1),
-
- Vst = update_type(fun meet/2, T2, LHS, Vst1),
- update_type(fun meet/2, T1, RHS, Vst).
-
%% Helper functions for the above.
assign_1(Src, Dst, Vst0) ->
@@ -1901,7 +2189,9 @@ set_reg_vref(Ref, {y,_}=Dst, #vst{current=#st{ys=Ys0}=St0} = Vst) ->
%% Storing into a non-existent Y register means that we haven't set
%% up a (sufficiently large) stack.
error({invalid_store, Dst})
- end.
+ end;
+set_reg_vref(_Ref, Dst, _Vst) ->
+ error({invalid_register, Dst}).
get_reg_vref({x,_}=Src, #vst{current=#st{xs=Xs}}) ->
check_limit(Src),
@@ -1922,29 +2212,26 @@ get_reg_vref({y,_}=Src, #vst{current=#st{ys=Ys}}) ->
error(Tag);
#{} ->
error({uninitialized_reg, Src})
- end.
+ end;
+get_reg_vref(Src, _Vst) ->
+ error({invalid_register, Src}).
set_type(Type, #value_ref{}=Ref, #vst{current=#st{vs=Vs0}=St}=Vst) ->
- case Vs0 of
- #{ Ref := #value{}=Entry } ->
- Vs = Vs0#{ Ref => Entry#value{type=Type} },
- Vst#vst{current=St#st{vs=Vs}};
- #{} ->
- %% Dead references may happen during type inference and are not an
- %% error in and of themselves. If a problem were to arise from this
- %% it'll explode elsewhere.
- Vst
- end.
+ #{ Ref := #value{}=Entry } = Vs0,
+ Vs = Vs0#{ Ref => Entry#value{type=Type} },
+ Vst#vst{current=St#st{vs=Vs}}.
+new_value(none, _, _, _) ->
+ error(creating_none_value);
new_value(Type, Op, Ss, #vst{current=#st{vs=Vs0}=St,ref_ctr=Counter}=Vst) ->
Ref = #value_ref{id=Counter},
Vs = Vs0#{ Ref => #value{op=Op,args=Ss,type=Type} },
{Ref, Vst#vst{current=St#st{vs=Vs},ref_ctr=Counter+1}}.
-kill_catch_tag(Reg, #vst{current=#st{ct=[Fail|Fails]}=St}=Vst0) ->
- Vst = Vst0#vst{current=St#st{ct=Fails,fls=undefined}},
- {_, Fail} = get_tag_type(Reg, Vst), %Assertion.
+kill_catch_tag(Reg, #vst{current=#st{ct=[Tag|Tags]}=St}=Vst0) ->
+ Vst = Vst0#vst{current=St#st{ct=Tags}},
+ Tag = get_tag_type(Reg, Vst), %Assertion.
kill_tag(Reg, Vst).
check_try_catch_tags(Type, {y,N}=Reg, Vst) ->
@@ -1962,6 +2249,11 @@ check_try_catch_tags(Type, {y,N}=Reg, Vst) ->
ok
end.
+assert_no_exception(?EXCEPTION_LABEL) ->
+ error(throws_exception);
+assert_no_exception(_) ->
+ ok.
+
assert_term(Src, Vst) ->
_ = get_term_type(Src, Vst),
ok.
@@ -1989,308 +2281,62 @@ is_literal({integer,I}) when is_integer(I) -> true;
is_literal({literal,_L}) -> true;
is_literal(_) -> false.
-%% The possible types.
-%%
-%% First non-term types:
-%%
-%% initialized Only for Y registers. Means that the Y register
-%% has been initialized with some valid term so that
-%% it is safe to pass to the garbage collector.
-%% NOT safe to use in any other way (will not crash the
-%% emulator, but clearly points to a bug in the compiler).
-%%
-%% {catchtag,[Lbl]} A special term used within a catch. Must only be used
-%% by the catch instructions; NOT safe to use in other
-%% instructions.
-%%
-%% {trytag,[Lbl]} A special term used within a try block. Must only be
-%% used by the catch instructions; NOT safe to use in other
-%% instructions.
-%%
-%% exception Can only be used as a type returned by
-%% call_return_type/2 (which gives the type of the value
-%% returned by a call). Thus 'exception' is never stored
-%% as type descriptor for a register.
-%%
-%% #ms{} A match context for bit syntax matching. We do allow
-%% it to moved/to from stack, but otherwise it must only
-%% be accessed by bit syntax matching instructions.
-%%
-%%
-%% Normal terms:
-%%
-%% term Any valid Erlang (but not of the special types above).
-%%
-%% binary Binary or bitstring.
-%%
-%% bool The atom 'true' or the atom 'false'.
-%%
-%% cons Cons cell: [_|_]
-%%
-%% nil Empty list: []
-%%
-%% list List: [] or [_|_]
-%%
-%% {tuple,[Sz],Es} Tuple. An element has been accessed using
-%% element/2 or setelement/3 so that it is known that
-%% the type is a tuple of size at least Sz. Es is a map
-%% containing known types by tuple index.
-%%
-%% {tuple,Sz,Es} Tuple. A test_arity instruction has been seen
-%% so that it is known that the size is exactly Sz.
-%%
-%% {atom,[]} Atom.
-%% {atom,Atom}
-%%
-%% {integer,[]} Integer.
-%% {integer,Integer}
-%%
-%% {float,[]} Float.
-%% {float,Float}
-%%
-%% number Integer or Float of unknown value
-%%
-%% map Map.
-%%
-%% none A conflict in types. There will be an exception at runtime.
-%%
+value_to_literal([]) -> nil;
+value_to_literal(A) when is_atom(A) -> {atom,A};
+value_to_literal(F) when is_float(F) -> {float,F};
+value_to_literal(I) when is_integer(I) -> {integer,I};
+value_to_literal(Other) -> {literal,Other}.
-%% join(Type1, Type2) -> Type
-%% Return the most specific type possible.
-join(Same, Same) ->
- Same;
-join(none, Other) ->
- Other;
-join(Other, none) ->
- Other;
-join({literal,_}=T1, T2) ->
- join_literal(T1, T2);
-join(T1, {literal,_}=T2) ->
- join_literal(T2, T1);
-join({tuple,Size,EsA}, {tuple,Size,EsB}) ->
- Es = join_tuple_elements(tuple_sz(Size), EsA, EsB),
- {tuple, Size, Es};
-join({tuple,A,EsA}, {tuple,B,EsB}) ->
- Size = min(tuple_sz(A), tuple_sz(B)),
- Es = join_tuple_elements(Size, EsA, EsB),
- {tuple, [Size], Es};
-join({Type,A}, {Type,B})
- when Type =:= atom; Type =:= integer; Type =:= float ->
- if A =:= B -> {Type,A};
- true -> {Type,[]}
- end;
-join({Type,_}, number)
- when Type =:= integer; Type =:= float ->
- number;
-join(number, {Type,_})
- when Type =:= integer; Type =:= float ->
- number;
-join({integer,_}, {float,_}) ->
- number;
-join({float,_}, {integer,_}) ->
- number;
-join(bool, {atom,A}) ->
- join_bool(A);
-join({atom,A}, bool) ->
- join_bool(A);
-join({atom,A}, {atom,B}) when is_boolean(A), is_boolean(B) ->
- bool;
-join({atom,_}, {atom,_}) ->
- {atom,[]};
-join(#ms{id=Id1,valid=B1,slots=Slots1},
- #ms{id=Id2,valid=B2,slots=Slots2}) ->
- Id = if
- Id1 =:= Id2 -> Id1;
- true -> make_ref()
- end,
- #ms{id=Id,valid=B1 band B2,slots=min(Slots1, Slots2)};
-join(T1, T2) when T1 =/= T2 ->
- %% We've exhaused all other options, so the type must either be a list or
- %% a 'term'.
- join_list(T1, T2).
+%% These are just wrappers around their equivalents in beam_types, which
+%% handle the validator-specific #t_abstract{} type.
+%%
+%% The funny-looking abstract types produced here are intended to provoke
+%% errors on actual use; they do no harm just lying around.
-join_tuple_elements(Limit, EsA, EsB) ->
- Es0 = join_elements(EsA, EsB),
- maps:filter(fun({integer,Index}, _Type) -> Index =< Limit end, Es0).
+normalize(#t_abstract{}=A) -> error({abstract_type, A});
+normalize(T) -> beam_types:normalize(T).
-join_elements(Es1, Es2) ->
- Keys = if
- map_size(Es1) =< map_size(Es2) -> maps:keys(Es1);
- map_size(Es1) > map_size(Es2) -> maps:keys(Es2)
- end,
- join_elements_1(Keys, Es1, Es2, #{}).
-
-join_elements_1([Key | Keys], Es1, Es2, Acc0) ->
- Type = case {Es1, Es2} of
- {#{ Key := Same }, #{ Key := Same }} -> Same;
- {#{ Key := Type1 }, #{ Key := Type2 }} -> join(Type1, Type2);
- {#{}, #{}} -> term
- end,
- Acc = set_element_type(Key, Type, Acc0),
- join_elements_1(Keys, Es1, Es2, Acc);
-join_elements_1([], _Es1, _Es2, Acc) ->
- Acc.
-
-%% Joins types of literals; note that the left argument must either be a
-%% literal or exactly equal to the second argument.
-join_literal(Same, Same) ->
+join(Same, Same) ->
Same;
-join_literal({literal,_}=Lit, T) ->
- join_literal(T, get_literal_type(Lit));
-join_literal(T1, T2) ->
- %% We're done extracting the types, try merging them again.
- join(T1, T2).
-
-join_list(nil, cons) -> list;
-join_list(nil, list) -> list;
-join_list(cons, list) -> list;
-join_list(T, nil) -> join_list(nil, T);
-join_list(T, cons) -> join_list(cons, T);
-join_list(_, _) ->
- %% Not a list, so it must be a term.
- term.
-
-join_bool([]) -> {atom,[]};
-join_bool(true) -> bool;
-join_bool(false) -> bool;
-join_bool(_) -> {atom,[]}.
-
-%% meet(Type1, Type2) -> Type
-%% Return the meet of two types. The meet is a more specific type.
-%% It will be 'none' if the types are in conflict.
+join(#t_abstract{kind={ms_position, UnitA}},
+ #t_abstract{kind={ms_position, UnitB}}) ->
+ #t_abstract{kind={ms_position, gcd(UnitA, UnitB)}};
+join(#t_abstract{}=A, B) ->
+ #t_abstract{kind={join, A, B}};
+join(A, #t_abstract{}=B) ->
+ #t_abstract{kind={join, A, B}};
+join(A, B) ->
+ beam_types:join(A, B).
meet(Same, Same) ->
Same;
-meet(term, Other) ->
- Other;
-meet(Other, term) ->
- Other;
-meet(#ms{}, binary) ->
- #ms{};
-meet(binary, #ms{}) ->
- #ms{};
-meet({literal,_}, {literal,_}) ->
- none;
-meet(T1, {literal,_}=T2) ->
- meet(T2, T1);
-meet({literal,_}=T1, T2) ->
- case meet(get_literal_type(T1), T2) of
- none -> none;
- _ -> T1
- end;
-meet(T1, T2) ->
- case {erlang:min(T1, T2),erlang:max(T1, T2)} of
- {{atom,_}=A,{atom,[]}} -> A;
- {bool,{atom,B}=Atom} when is_boolean(B) -> Atom;
- {bool,{atom,[]}} -> bool;
- {cons,list} -> cons;
- {{float,_}=T,{float,[]}} -> T;
- {{integer,_}=T,{integer,[]}} -> T;
- {list,nil} -> nil;
- {number,{integer,_}=T} -> T;
- {number,{float,_}=T} -> T;
- {{tuple,Size1,Es1},{tuple,Size2,Es2}} ->
- Es = meet_elements(Es1, Es2),
- case {Size1,Size2,Es} of
- {_, _, none} ->
- none;
- {[Sz1],[Sz2],_} ->
- Sz = erlang:max(Sz1, Sz2),
- assert_tuple_elements(Sz, Es),
- {tuple,[Sz],Es};
- {Sz1,[Sz2],_} when Sz2 =< Sz1 ->
- assert_tuple_elements(Sz1, Es),
- {tuple,Sz1,Es};
- {Sz,Sz,_} ->
- assert_tuple_elements(Sz, Es),
- {tuple,Sz,Es};
- {_,_,_} ->
- none
- end;
- {_,_} -> none
+meet(#t_abstract{kind={ms_position, UnitA}},
+ #t_abstract{kind={ms_position, UnitB}}) ->
+ Unit = UnitA * UnitB div gcd(UnitA, UnitB),
+ #t_abstract{kind={ms_position, Unit}};
+meet(#t_abstract{}=A, B) ->
+ #t_abstract{kind={meet, A, B}};
+meet(A, #t_abstract{}=B) ->
+ #t_abstract{kind={meet, A, B}};
+meet(A, B) ->
+ beam_types:meet(A, B).
+
+subtract(#t_abstract{}=A, B) ->
+ #t_abstract{kind={subtract, A, B}};
+subtract(A, #t_abstract{}=B) ->
+ #t_abstract{kind={subtract, A, B}};
+subtract(A, B) ->
+ beam_types:subtract(A, B).
+
+assert_type(RequiredType, Term, Vst) ->
+ GivenType = get_movable_term_type(Term, Vst),
+ case meet(RequiredType, GivenType) of
+ GivenType ->
+ ok;
+ _RequiredType ->
+ error({bad_type,{needed,RequiredType},{actual,GivenType}})
end.
-meet_elements(Es1, Es2) ->
- Keys = maps:keys(Es1) ++ maps:keys(Es2),
- meet_elements_1(Keys, Es1, Es2, #{}).
-
-meet_elements_1([Key | Keys], Es1, Es2, Acc) ->
- case {Es1, Es2} of
- {#{ Key := Type1 }, #{ Key := Type2 }} ->
- case meet(Type1, Type2) of
- none -> none;
- Type -> meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type })
- end;
- {#{ Key := Type1 }, _} ->
- meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type1 });
- {_, #{ Key := Type2 }} ->
- meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type2 })
- end;
-meet_elements_1([], _Es1, _Es2, Acc) ->
- Acc.
-
-%% No tuple elements may have an index above the known size.
-assert_tuple_elements(Limit, Es) ->
- true = maps:fold(fun({integer,Index}, _T, true) ->
- Index =< Limit
- end, true, Es). %Assertion.
-
-%% subtract(Type1, Type2) -> Type
-%% Subtract Type2 from Type2. Example:
-%% subtract(list, nil) -> cons
-
-subtract(Same, Same) -> none;
-subtract(list, nil) -> cons;
-subtract(list, cons) -> nil;
-subtract(number, {integer,[]}) -> {float,[]};
-subtract(number, {float,[]}) -> {integer,[]};
-subtract(bool, {atom,false}) -> {atom, true};
-subtract(bool, {atom,true}) -> {atom, false};
-subtract(Type, _) -> Type.
-
-assert_type(WantedType, Term, Vst) ->
- Type = get_term_type(Term, Vst),
- assert_type(WantedType, Type).
-
-assert_type(Correct, Correct) -> ok;
-assert_type(float, {float,_}) -> ok;
-assert_type(tuple, {tuple,_,_}) -> ok;
-assert_type(tuple, {literal,Tuple}) when is_tuple(Tuple) -> ok;
-assert_type({tuple_element,I}, {tuple,[Sz],_})
- when 1 =< I, I =< Sz ->
- ok;
-assert_type({tuple_element,I}, {tuple,Sz,_})
- when is_integer(Sz), 1 =< I, I =< Sz ->
- ok;
-assert_type({tuple_element,I}, {literal,Lit}) when I =< tuple_size(Lit) ->
- ok;
-assert_type(cons, {literal,[_|_]}) ->
- ok;
-assert_type(Needed, Actual) ->
- error({bad_type,{needed,Needed},{actual,Actual}}).
-
-get_element_type(Key, Src, Vst) ->
- get_element_type_1(Key, get_term_type(Src, Vst)).
-
-get_element_type_1({integer,_}=Key, {tuple,_Sz,Es}) ->
- case Es of
- #{ Key := Type } -> Type;
- #{} -> term
- end;
-get_element_type_1(_Index, _Type) ->
- term.
-
-set_element_type(_Key, none, Es) ->
- Es;
-set_element_type(Key, term, Es) ->
- maps:remove(Key, Es);
-set_element_type(Key, Type, Es) ->
- Es#{ Key => Type }.
-
-get_tuple_size({integer,[]}) -> 0;
-get_tuple_size({integer,Sz}) -> Sz;
-get_tuple_size(_) -> 0.
-
validate_src(Ss, Vst) when is_list(Ss) ->
_ = [assert_term(S, Vst) || S <- Ss],
ok.
@@ -2301,7 +2347,8 @@ validate_src(Ss, Vst) when is_list(Ss) ->
get_term_type(Src, Vst) ->
case get_movable_term_type(Src, Vst) of
- #ms{} -> error({match_context,Src});
+ #t_bs_context{} -> error({match_context,Src});
+ #t_abstract{} -> error({abstract_term,Src});
Type -> Type
end.
@@ -2311,12 +2358,11 @@ get_term_type(Src, Vst) ->
get_movable_term_type(Src, Vst) ->
case get_raw_type(Src, Vst) of
+ #t_abstract{kind=unfinished_tuple=Kind} -> error({Kind,Src});
initialized -> error({unassigned,Src});
uninitialized -> error({uninitialized_reg,Src});
{catchtag,_} -> error({catchtag,Src});
{trytag,_} -> error({trytag,Src});
- tuple_in_progress -> error({tuple_in_progress,Src});
- {literal,_}=Lit -> get_literal_type(Lit);
Type -> Type
end.
@@ -2355,33 +2401,21 @@ get_raw_type(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) ->
#{ Ref := #value{type=Type} } -> Type;
#{} -> none
end;
-get_raw_type(Src, #vst{}) ->
+get_raw_type(Src, #vst{current=#st{}}) ->
get_literal_type(Src).
-get_literal_type(nil=T) -> T;
-get_literal_type({atom,A}=T) when is_atom(A) -> T;
-get_literal_type({float,F}=T) when is_float(F) -> T;
-get_literal_type({integer,I}=T) when is_integer(I) -> T;
-get_literal_type({literal,[_|_]}) -> cons;
-get_literal_type({literal,Bitstring}) when is_bitstring(Bitstring) -> binary;
-get_literal_type({literal,Map}) when is_map(Map) -> map;
-get_literal_type({literal,Tuple}) when is_tuple(Tuple) -> glt_1(Tuple);
-get_literal_type({literal,_}) -> term;
-get_literal_type(T) -> error({not_literal,T}).
-
-glt_1([]) -> nil;
-glt_1(A) when is_atom(A) -> {atom, A};
-glt_1(F) when is_float(F) -> {float, F};
-glt_1(I) when is_integer(I) -> {integer, I};
-glt_1(T) when is_tuple(T) ->
- {Es,_} = foldl(fun(Val, {Es0, Index}) ->
- Type = glt_1(Val),
- Es = set_element_type({integer,Index}, Type, Es0),
- {Es, Index + 1}
- end, {#{}, 1}, tuple_to_list(T)),
- {tuple, tuple_size(T), Es};
-glt_1(L) ->
- {literal, L}.
+get_literal_type(nil) ->
+ beam_types:make_type_from_value([]);
+get_literal_type({atom,A}) when is_atom(A) ->
+ beam_types:make_type_from_value(A);
+get_literal_type({float,F}) when is_float(F) ->
+ beam_types:make_type_from_value(F);
+get_literal_type({integer,I}) when is_integer(I) ->
+ beam_types:make_type_from_value(I);
+get_literal_type({literal,L}) ->
+ beam_types:make_type_from_value(L);
+get_literal_type(T) ->
+ error({not_literal,T}).
%%%
%%% Branch tracking
@@ -2399,10 +2433,12 @@ glt_1(L) ->
SuccFun :: BranchFun) -> #vst{} when
BranchFun :: fun((#vst{}) -> #vst{}).
branch(Lbl, Vst0, FailFun, SuccFun) ->
+ validate_branch(Lbl, Vst0),
#vst{current=St0} = Vst0,
+
try FailFun(Vst0) of
Vst1 ->
- Vst2 = branch_state(Lbl, Vst1),
+ Vst2 = fork_state(Lbl, Vst1),
Vst = Vst2#vst{current=St0},
try SuccFun(Vst) of
V -> V
@@ -2420,47 +2456,83 @@ branch(Lbl, Vst0, FailFun, SuccFun) ->
SuccFun(Vst0)
end.
+validate_branch(Lbl, #vst{current=#st{ct=Tags}}) ->
+ validate_branch_1(Lbl, Tags).
+
+validate_branch_1(Lbl, [{trytag, FailLbls} | Tags]) ->
+ %% 'try_case' assumes that an exception has been thrown, so a direct branch
+ %% will crash the emulator.
+ %%
+ %% (Jumping to a 'catch_end' is fine however as it will simply nop in the
+ %% absence of an exception.)
+ case ordsets:is_element(Lbl, FailLbls) of
+ true -> error({illegal_branch, try_handler, Lbl});
+ false -> validate_branch_1(Lbl, Tags)
+ end;
+validate_branch_1(Lbl, [_ | Tags]) ->
+ validate_branch_1(Lbl, Tags);
+validate_branch_1(_Lbl, _Tags) ->
+ ok.
+
%% A shorthand version of branch/4 for when the state is only altered on
%% success.
branch(Fail, Vst, SuccFun) ->
branch(Fail, Vst, fun(V) -> V end, SuccFun).
+%% Shorthand of branch/4 for when the state is neither altered on failure nor
+%% success.
+branch(Fail, Vst) ->
+ branch(Fail, Vst, fun(V) -> V end).
+
%% Directly branches off the state. This is an "internal" operation that should
%% be used sparingly.
-branch_state(0, #vst{}=Vst) ->
- %% If the instruction fails, the stack may be scanned looking for a catch
- %% tag. Therefore the Y registers must be initialized at this point.
- verify_y_init(Vst),
- Vst;
-branch_state(L, #vst{current=St,branched=B,ref_ctr=Counter0}=Vst) ->
- case gb_trees:is_defined(L, B) of
- true ->
- {MergedSt, Counter} = merge_states(L, St, B, Counter0),
- Branched = gb_trees:update(L, MergedSt, B),
- Vst#vst{branched=Branched,ref_ctr=Counter};
- false ->
- Vst#vst{branched=gb_trees:insert(L, St, B)}
- end.
+fork_state(?EXCEPTION_LABEL, Vst0) ->
+ #vst{current=#st{ct=CatchTags,numy=NumY}} = Vst0,
+
+ %% The stack will be scanned looking for a catch tag, so all Y registers
+ %% must be initialized.
+ verify_y_init(Vst0),
+
+ case CatchTags of
+ [{_, [Fail]} | _] when is_integer(Fail) ->
+ true = Fail =/= ?EXCEPTION_LABEL, %Assertion.
+ true = NumY =/= none, %Assertion.
+
+ %% Clear the receive marker and fork to our exception handler.
+ Vst = set_receive_marker(none, Vst0),
+ fork_state(Fail, Vst);
+ [] ->
+ %% No catch handler; the exception leaves the function.
+ Vst0;
+ _ ->
+ error(ambiguous_catch_try_state)
+ end;
+fork_state(L, #vst{current=St0,branched=Branched0,ref_ctr=Counter0}=Vst) ->
+ {St, Counter} = merge_states(L, St0, Branched0, Counter0),
+ Branched = Branched0#{ L => St },
+ Vst#vst{branched=Branched,ref_ctr=Counter}.
%% merge_states/3 is used when there's more than one way to arrive at a
%% certain point, requiring the states to be merged down to the least
%% common subset for the subsequent code.
merge_states(L, St, Branched, Counter) when L =/= 0 ->
- case gb_trees:lookup(L, Branched) of
- none ->
- {St, Counter};
- {value,OtherSt} when St =:= none ->
- {OtherSt, Counter};
- {value,OtherSt} ->
- merge_states_1(St, OtherSt, Counter)
+ case Branched of
+ #{ L := OtherSt } -> merge_states_1(St, OtherSt, Counter);
+ #{} -> {St, Counter}
end.
+merge_states_1(St, none, Counter) ->
+ {St, Counter};
+merge_states_1(none, St, Counter) ->
+ {St, Counter};
merge_states_1(StA, StB, Counter0) ->
#st{xs=XsA,ys=YsA,vs=VsA,fragile=FragA,numy=NumYA,
- h=HA,ct=CtA,recv_marker=MarkerA} = StA,
+ h=HA,ct=CtA,recv_marker=MarkerA,
+ ms_positions=MsPosA} = StA,
#st{xs=XsB,ys=YsB,vs=VsB,fragile=FragB,numy=NumYB,
- h=HB,ct=CtB,recv_marker=MarkerB} = StB,
+ h=HB,ct=CtB,recv_marker=MarkerB,
+ ms_positions=MsPosB} = StB,
%% When merging registers we drop all registers that aren't defined in both
%% states, and resolve conflicts by creating new values (similar to phi
@@ -2475,12 +2547,15 @@ merge_states_1(StA, StB, Counter0) ->
Vs = merge_values(Merge, VsA, VsB),
Marker = merge_receive_marker(MarkerA, MarkerB),
+ MsPos = merge_ms_positions(MsPosA, MsPosB, Vs),
Fragile = merge_fragility(FragA, FragB),
NumY = merge_stk(NumYA, NumYB),
Ct = merge_ct(CtA, CtB),
St = #st{xs=Xs,ys=Ys,vs=Vs,fragile=Fragile,numy=NumY,
- h=min(HA, HB),ct=Ct,recv_marker=Marker},
+ h=min(HA, HB),ct=Ct,recv_marker=Marker,
+ ms_positions=MsPos},
+
{St, Counter}.
%% Merges the contents of two register maps, returning the updated "merge map"
@@ -2517,10 +2592,10 @@ merge_tags(uninitialized, _) ->
uninitialized;
merge_tags(_, uninitialized) ->
uninitialized;
-merge_tags({catchtag,T0}, {catchtag,T1}) ->
- {catchtag, ordsets:from_list(T0 ++ T1)};
-merge_tags({trytag,T0}, {trytag,T1}) ->
- {trytag, ordsets:from_list(T0 ++ T1)};
+merge_tags({trytag, LblsA}, {trytag, LblsB}) ->
+ {trytag, ordsets:union(LblsA, LblsB)};
+merge_tags({catchtag, LblsA}, {catchtag, LblsB}) ->
+ {catchtag, ordsets:union(LblsA, LblsB)};
merge_tags(_A, _B) ->
%% All other combinations leave the register initialized. Errors arising
%% from this will be caught later on.
@@ -2585,6 +2660,23 @@ mv_args([], _VsA, _VsB, Acc) ->
merge_fragility(FragileA, FragileB) ->
cerl_sets:union(FragileA, FragileB).
+merge_ms_positions(MsPosA, MsPosB, Vs) ->
+ Keys = if
+ map_size(MsPosA) =< map_size(MsPosB) -> maps:keys(MsPosA);
+ map_size(MsPosA) > map_size(MsPosB) -> maps:keys(MsPosB)
+ end,
+ merge_ms_positions_1(Keys, MsPosA, MsPosB, Vs, #{}).
+
+merge_ms_positions_1([Key | Keys], MsPosA, MsPosB, Vs, Acc) ->
+ case {MsPosA, MsPosB} of
+ {#{ Key := Pos }, #{ Key := Pos }} when is_map_key(Pos, Vs) ->
+ merge_ms_positions_1(Keys, MsPosA, MsPosB, Vs, Acc#{ Key => Pos });
+ {#{}, #{}} ->
+ merge_ms_positions_1(Keys, MsPosA, MsPosB, Vs, Acc)
+ end;
+merge_ms_positions_1([], _MsPosA, _MsPosB, _Vs, Acc) ->
+ Acc.
+
merge_receive_marker(Same, Same) ->
Same;
merge_receive_marker(none, initialized) ->
@@ -2603,13 +2695,14 @@ merge_stk(_, _) -> undecided.
merge_ct(S, S) -> S;
merge_ct(Ct0, Ct1) -> merge_ct_1(Ct0, Ct1).
-merge_ct_1([C0|Ct0], [C1|Ct1]) ->
- [ordsets:from_list(C0++C1)|merge_ct_1(Ct0, Ct1)];
-merge_ct_1([], []) -> [];
-merge_ct_1(_, _) -> undecided.
-
-tuple_sz([Sz]) -> Sz;
-tuple_sz(Sz) -> Sz.
+merge_ct_1([], []) ->
+ [];
+merge_ct_1([{trytag, LblsA} | CtA], [{trytag, LblsB} | CtB]) ->
+ [{trytag, ordsets:union(LblsA, LblsB)} | merge_ct_1(CtA, CtB)];
+merge_ct_1([{catchtag, LblsA} | CtA], [{catchtag, LblsB} | CtB]) ->
+ [{catchtag, ordsets:union(LblsA, LblsB)} | merge_ct_1(CtA, CtB)];
+merge_ct_1(_, _) ->
+ undecided.
verify_y_init(#vst{current=#st{numy=NumY,ys=Ys}}=Vst) when is_integer(NumY) ->
HighestY = maps:fold(fun({y,Y}, _, Acc) -> max(Y, Acc) end, -1, Ys),
@@ -2786,321 +2879,50 @@ assert_not_fragile(Lit, #vst{}) ->
ok.
%%%
-%%% Return/argument types of BIFs
-%%%
-
-bif_return_type('-', Src, Vst) ->
- arith_return_type(Src, Vst);
-bif_return_type('+', Src, Vst) ->
- arith_return_type(Src, Vst);
-bif_return_type('*', Src, Vst) ->
- arith_return_type(Src, Vst);
-bif_return_type(abs, [Num], Vst) ->
- case get_term_type(Num, Vst) of
- {float,_}=T -> T;
- {integer,_}=T -> T;
- _ -> number
- end;
-bif_return_type(float, _, _) -> {float,[]};
-bif_return_type('/', _, _) -> {float,[]};
-%% Binary operations
-bif_return_type('binary_part', [_,_], _) -> binary;
-bif_return_type('binary_part', [_,_,_], _) -> binary;
-bif_return_type('bit_size', [_], _) -> {integer,[]};
-bif_return_type('byte_size', [_], _) -> {integer,[]};
-%% Integer operations.
-bif_return_type(ceil, [_], _) -> {integer,[]};
-bif_return_type('div', [_,_], _) -> {integer,[]};
-bif_return_type(floor, [_], _) -> {integer,[]};
-bif_return_type('rem', [_,_], _) -> {integer,[]};
-bif_return_type(length, [_], _) -> {integer,[]};
-bif_return_type(size, [_], _) -> {integer,[]};
-bif_return_type(trunc, [_], _) -> {integer,[]};
-bif_return_type(round, [_], _) -> {integer,[]};
-bif_return_type('band', [_,_], _) -> {integer,[]};
-bif_return_type('bor', [_,_], _) -> {integer,[]};
-bif_return_type('bxor', [_,_], _) -> {integer,[]};
-bif_return_type('bnot', [_], _) -> {integer,[]};
-bif_return_type('bsl', [_,_], _) -> {integer,[]};
-bif_return_type('bsr', [_,_], _) -> {integer,[]};
-%% Booleans.
-bif_return_type('==', [_,_], _) -> bool;
-bif_return_type('/=', [_,_], _) -> bool;
-bif_return_type('=<', [_,_], _) -> bool;
-bif_return_type('<', [_,_], _) -> bool;
-bif_return_type('>=', [_,_], _) -> bool;
-bif_return_type('>', [_,_], _) -> bool;
-bif_return_type('=:=', [_,_], _) -> bool;
-bif_return_type('=/=', [_,_], _) -> bool;
-bif_return_type('not', [_], _) -> bool;
-bif_return_type('and', [_,_], _) -> bool;
-bif_return_type('or', [_,_], _) -> bool;
-bif_return_type('xor', [_,_], _) -> bool;
-bif_return_type(is_atom, [_], _) -> bool;
-bif_return_type(is_boolean, [_], _) -> bool;
-bif_return_type(is_binary, [_], _) -> bool;
-bif_return_type(is_float, [_], _) -> bool;
-bif_return_type(is_function, [_], _) -> bool;
-bif_return_type(is_function, [_,_], _) -> bool;
-bif_return_type(is_integer, [_], _) -> bool;
-bif_return_type(is_list, [_], _) -> bool;
-bif_return_type(is_map, [_], _) -> bool;
-bif_return_type(is_map_key, [_, _], _) -> bool;
-bif_return_type(is_number, [_], _) -> bool;
-bif_return_type(is_pid, [_], _) -> bool;
-bif_return_type(is_port, [_], _) -> bool;
-bif_return_type(is_reference, [_], _) -> bool;
-bif_return_type(is_tuple, [_], _) -> bool;
-%% Misc.
-bif_return_type(tuple_size, [_], _) -> {integer,[]};
-bif_return_type(map_size, [_], _) -> {integer,[]};
-bif_return_type(node, [], _) -> {atom,[]};
-bif_return_type(node, [_], _) -> {atom,[]};
-bif_return_type(hd, [_], _) -> term;
-bif_return_type(tl, [_], _) -> term;
-bif_return_type(get, [_], _) -> term;
-bif_return_type(Bif, _, _) when is_atom(Bif) -> term.
-
-%% Generic
-bif_arg_types(tuple_size, [_]) -> [{tuple,[0],#{}}];
-bif_arg_types(map_size, [_]) -> [map];
-bif_arg_types(is_map_key, [_,_]) -> [term, map];
-bif_arg_types(map_get, [_,_]) -> [term, map];
-bif_arg_types(length, [_]) -> [list];
-bif_arg_types(hd, [_]) -> [cons];
-bif_arg_types(tl, [_]) -> [cons];
-%% Boolean
-bif_arg_types('not', [_]) -> [bool];
-bif_arg_types('and', [_,_]) -> [bool, bool];
-bif_arg_types('or', [_,_]) -> [bool, bool];
-bif_arg_types('xor', [_,_]) -> [bool, bool];
-%% Binary
-bif_arg_types('binary_part', [_,_]) ->
- PosLen = {tuple, 2, #{ {integer,1} => {integer,[]},
- {integer,2} => {integer,[]} }},
- [binary, PosLen];
-bif_arg_types('binary_part', [_,_,_]) ->
- [binary, {integer,[]}, {integer,[]}];
-bif_arg_types('bit_size', [_]) -> [binary];
-bif_arg_types('byte_size', [_]) -> [binary];
-%% Numerical
-bif_arg_types('-', [_]) -> [number];
-bif_arg_types('-', [_,_]) -> [number,number];
-bif_arg_types('+', [_]) -> [number];
-bif_arg_types('+', [_,_]) -> [number,number];
-bif_arg_types('*', [_,_]) -> [number, number];
-bif_arg_types('/', [_,_]) -> [number, number];
-bif_arg_types(abs, [_]) -> [number];
-bif_arg_types(ceil, [_]) -> [number];
-bif_arg_types(float, [_]) -> [number];
-bif_arg_types(floor, [_]) -> [number];
-bif_arg_types(trunc, [_]) -> [number];
-bif_arg_types(round, [_]) -> [number];
-%% Integer-specific
-bif_arg_types('div', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('rem', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('band', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('bor', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('bxor', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('bnot', [_]) -> [{integer,[]}];
-bif_arg_types('bsl', [_,_]) -> [{integer,[]}, {integer,[]}];
-bif_arg_types('bsr', [_,_]) -> [{integer,[]}, {integer,[]}];
-%% Unsafe type tests that may fail if an argument doesn't have the right type.
-bif_arg_types(is_function, [_,_]) -> [term, {integer,[]}];
-bif_arg_types(_, Args) -> [term || _Arg <- Args].
-
-is_bif_safe('/=', 2) -> true;
-is_bif_safe('<', 2) -> true;
-is_bif_safe('=/=', 2) -> true;
-is_bif_safe('=:=', 2) -> true;
-is_bif_safe('=<', 2) -> true;
-is_bif_safe('==', 2) -> true;
-is_bif_safe('>', 2) -> true;
-is_bif_safe('>=', 2) -> true;
-is_bif_safe(is_atom, 1) -> true;
-is_bif_safe(is_boolean, 1) -> true;
-is_bif_safe(is_binary, 1) -> true;
-is_bif_safe(is_bitstring, 1) -> true;
-is_bif_safe(is_float, 1) -> true;
-is_bif_safe(is_function, 1) -> true;
-is_bif_safe(is_integer, 1) -> true;
-is_bif_safe(is_list, 1) -> true;
-is_bif_safe(is_map, 1) -> true;
-is_bif_safe(is_number, 1) -> true;
-is_bif_safe(is_pid, 1) -> true;
-is_bif_safe(is_port, 1) -> true;
-is_bif_safe(is_reference, 1) -> true;
-is_bif_safe(is_tuple, 1) -> true;
-is_bif_safe(get, 1) -> true;
-is_bif_safe(self, 0) -> true;
-is_bif_safe(node, 0) -> true;
-is_bif_safe(_, _) -> false.
-
-arith_return_type([A], Vst) ->
- %% Unary '+' or '-'.
- case get_term_type(A, Vst) of
- {integer,_} -> {integer,[]};
- {float,_} -> {float,[]};
- _ -> number
- end;
-arith_return_type([A,B], Vst) ->
- TypeA = get_term_type(A, Vst),
- TypeB = get_term_type(B, Vst),
- case {TypeA, TypeB} of
- {{integer,_},{integer,_}} -> {integer,[]};
- {{float,_},_} -> {float,[]};
- {_,{float,_}} -> {float,[]};
- {_,_} -> number
- end;
-arith_return_type(_, _) -> number.
-
-%%%
-%%% Return/argument types of calls
+%%% Return/argument types of calls and BIFs
%%%
-call_return_type({extfunc,M,F,A}, Vst) -> call_return_type_1(M, F, A, Vst);
-call_return_type(_, _) -> term.
-
-call_return_type_1(erlang, setelement, 3, Vst) ->
- IndexType = get_term_type({x,0}, Vst),
- TupleType =
- case get_term_type({x,1}, Vst) of
- {literal,Tuple}=Lit when is_tuple(Tuple) -> get_literal_type(Lit);
- {tuple,_,_}=TT -> TT;
- _ -> {tuple,[0],#{}}
- end,
- case IndexType of
- {integer,I} when is_integer(I) ->
- case meet({tuple,[I],#{}}, TupleType) of
- {tuple, Sz, Es0} ->
- ValueType = get_term_type({x,2}, Vst),
- Es = set_element_type({integer,I}, ValueType, Es0),
- {tuple, Sz, Es};
- none ->
- TupleType
- end;
- _ ->
- %% The index could point anywhere, so we must discard all element
- %% information.
- setelement(3, TupleType, #{})
- end;
-call_return_type_1(erlang, '++', 2, Vst) ->
- LType = get_term_type({x,0}, Vst),
- RType = get_term_type({x,1}, Vst),
- case LType =:= cons orelse RType =:= cons of
+bif_types(Op, Ss, Vst) ->
+ Args = [normalize(get_term_type(Arg, Vst)) || Arg <- Ss],
+ beam_call_types:types(erlang, Op, Args).
+
+call_types({extfunc,M,F,A}, A, Vst) ->
+ Args = get_call_args(A, Vst),
+ beam_call_types:types(M, F, Args);
+call_types(_, A, Vst) ->
+ {any, get_call_args(A, Vst), false}.
+
+will_bif_succeed(raise, [_,_], _Vst) ->
+ %% Compiler-generated raise, the user-facing variant that can return
+ %% 'badarg' is erlang:raise/3.
+ no;
+will_bif_succeed(Op, Ss, Vst) ->
+ case is_float_arith_bif(Op, Ss) of
true ->
- cons;
+ %% Float arithmetic BIFs can't fail; their error checking is
+ %% deferred until fcheckerror.
+ yes;
false ->
- %% `[] ++ RHS` yields RHS, even if RHS is not a list
- join(list, RType)
- end;
-call_return_type_1(erlang, '--', 2, _Vst) ->
- list;
-call_return_type_1(erlang, F, A, _) ->
- erlang_mod_return_type(F, A);
-call_return_type_1(lists, F, A, Vst) ->
- lists_mod_return_type(F, A, Vst);
-call_return_type_1(math, F, A, _) ->
- math_mod_return_type(F, A);
-call_return_type_1(M, F, A, _) when is_atom(M), is_atom(F), is_integer(A), A >= 0 ->
- term.
-
-erlang_mod_return_type(exit, 1) -> exception;
-erlang_mod_return_type(throw, 1) -> exception;
-erlang_mod_return_type(error, 1) -> exception;
-erlang_mod_return_type(error, 2) -> exception;
-erlang_mod_return_type(F, A) when is_atom(F), is_integer(A), A >= 0 -> term.
-
-math_mod_return_type(cos, 1) -> {float,[]};
-math_mod_return_type(cosh, 1) -> {float,[]};
-math_mod_return_type(sin, 1) -> {float,[]};
-math_mod_return_type(sinh, 1) -> {float,[]};
-math_mod_return_type(tan, 1) -> {float,[]};
-math_mod_return_type(tanh, 1) -> {float,[]};
-math_mod_return_type(acos, 1) -> {float,[]};
-math_mod_return_type(acosh, 1) -> {float,[]};
-math_mod_return_type(asin, 1) -> {float,[]};
-math_mod_return_type(asinh, 1) -> {float,[]};
-math_mod_return_type(atan, 1) -> {float,[]};
-math_mod_return_type(atanh, 1) -> {float,[]};
-math_mod_return_type(erf, 1) -> {float,[]};
-math_mod_return_type(erfc, 1) -> {float,[]};
-math_mod_return_type(exp, 1) -> {float,[]};
-math_mod_return_type(log, 1) -> {float,[]};
-math_mod_return_type(log2, 1) -> {float,[]};
-math_mod_return_type(log10, 1) -> {float,[]};
-math_mod_return_type(sqrt, 1) -> {float,[]};
-math_mod_return_type(atan2, 2) -> {float,[]};
-math_mod_return_type(pow, 2) -> {float,[]};
-math_mod_return_type(ceil, 1) -> {float,[]};
-math_mod_return_type(floor, 1) -> {float,[]};
-math_mod_return_type(fmod, 2) -> {float,[]};
-math_mod_return_type(pi, 0) -> {float,[]};
-math_mod_return_type(F, A) when is_atom(F), is_integer(A), A >= 0 -> term.
-
-lists_mod_return_type(all, 2, _Vst) ->
- bool;
-lists_mod_return_type(any, 2, _Vst) ->
- bool;
-lists_mod_return_type(keymember, 3, _Vst) ->
- bool;
-lists_mod_return_type(member, 2, _Vst) ->
- bool;
-lists_mod_return_type(prefix, 2, _Vst) ->
- bool;
-lists_mod_return_type(suffix, 2, _Vst) ->
- bool;
-lists_mod_return_type(dropwhile, 2, _Vst) ->
- list;
-lists_mod_return_type(duplicate, 2, _Vst) ->
- list;
-lists_mod_return_type(filter, 2, _Vst) ->
- list;
-lists_mod_return_type(flatten, 1, _Vst) ->
- list;
-lists_mod_return_type(map, 2, Vst) ->
- same_length_type({x,1}, Vst);
-lists_mod_return_type(MF, 3, Vst) when MF =:= mapfoldl; MF =:= mapfoldr ->
- ListType = same_length_type({x,2}, Vst),
- {tuple,2,#{ {integer,1} => ListType} };
-lists_mod_return_type(partition, 2, _Vst) ->
- two_tuple(list, list);
-lists_mod_return_type(reverse, 1, Vst) ->
- same_length_type({x,0}, Vst);
-lists_mod_return_type(seq, 2, _Vst) ->
- list;
-lists_mod_return_type(sort, 1, Vst) ->
- same_length_type({x,0}, Vst);
-lists_mod_return_type(sort, 2, Vst) ->
- same_length_type({x,1}, Vst);
-lists_mod_return_type(splitwith, 2, _Vst) ->
- two_tuple(list, list);
-lists_mod_return_type(takewhile, 2, _Vst) ->
- list;
-lists_mod_return_type(unzip, 1, Vst) ->
- ListType = same_length_type({x,0}, Vst),
- two_tuple(ListType, ListType);
-lists_mod_return_type(usort, 1, Vst) ->
- same_length_type({x,0}, Vst);
-lists_mod_return_type(zip, 2, _Vst) ->
- list;
-lists_mod_return_type(zipwith, 3, _Vst) ->
- list;
-lists_mod_return_type(_, _, _) ->
- term.
-
-two_tuple(Type1, Type2) ->
- {tuple,2,#{ {integer,1} => Type1,
- {integer,2} => Type2 }}.
-
-same_length_type(Reg, Vst) ->
- case get_term_type(Reg, Vst) of
- {literal,[_|_]} -> cons;
- cons -> cons;
- nil -> nil;
- _ -> list
+ Args = [normalize(get_term_type(Arg, Vst)) || Arg <- Ss],
+ beam_call_types:will_succeed(erlang, Op, Args)
end.
+will_call_succeed({extfunc,M,F,A}, Vst) ->
+ beam_call_types:will_succeed(M, F, get_call_args(A, Vst));
+will_call_succeed(bs_init_writable, _Vst) ->
+ yes;
+will_call_succeed(_Call, _Vst) ->
+ maybe.
+
+get_call_args(Arity, Vst) ->
+ get_call_args_1(0, Arity, Vst).
+
+get_call_args_1(Arity, Arity, _) ->
+ [];
+get_call_args_1(N, Arity, Vst) when N < Arity ->
+ ArgType = normalize(get_movable_term_type({x,N}, Vst)),
+ [ArgType | get_call_args_1(N + 1, Arity, Vst)].
+
check_limit({x,X}=Src) when is_integer(X) ->
if
%% Note: x(1023) is reserved for use by the BEAM loader.
@@ -3124,6 +2946,10 @@ check_limit({fr,Fr}=Src) when is_integer(Fr) ->
min(A, B) when is_integer(A), is_integer(B), A < B -> A;
min(A, B) when is_integer(A), is_integer(B) -> B.
-gb_trees_from_list(L) -> gb_trees:from_orddict(sort(L)).
+gcd(A, B) ->
+ case A rem B of
+ 0 -> B;
+ X -> gcd(B, X)
+ end.
error(Error) -> throw(Error).
diff --git a/lib/compiler/src/beam_z.erl b/lib/compiler/src/beam_z.erl
index e891912067..2744c96c2e 100644
--- a/lib/compiler/src/beam_z.erl
+++ b/lib/compiler/src/beam_z.erl
@@ -47,16 +47,31 @@ function({function,Name,Arity,CLabel,Is0}, NoGetHdTl) ->
undo_renames([{call_ext,2,send}|Is]) ->
[send|undo_renames(Is)];
+
undo_renames([{apply,A},{deallocate,N},return|Is]) ->
[{apply_last,A,N}|undo_renames(Is)];
+
+undo_renames([{call,A,F},{'%',{var_info,{x,0},_}},{deallocate,N},return|Is]) ->
+ %% We've removed a redundant move of a literal to {x,0}.
+ [{call_last,A,F,N} | undo_renames(Is)];
undo_renames([{call,A,F},{deallocate,N},return|Is]) ->
- [{call_last,A,F,N}|undo_renames(Is)];
+ [{call_last,A,F,N} | undo_renames(Is)];
+
+undo_renames([{call_ext,A,F},{'%',{var_info,{x,0},_}},{deallocate,N},return|Is]) ->
+ [{call_ext_last,A,F,N} | undo_renames(Is)];
undo_renames([{call_ext,A,F},{deallocate,N},return|Is]) ->
- [{call_ext_last,A,F,N}|undo_renames(Is)];
+ [{call_ext_last,A,F,N} | undo_renames(Is)];
+
+undo_renames([{call,A,F},{'%',{var_info,{x,0},_}},return|Is]) ->
+ [{call_only,A,F} | undo_renames(Is)];
undo_renames([{call,A,F},return|Is]) ->
[{call_only,A,F}|undo_renames(Is)];
+
+undo_renames([{call_ext,A,F},{'%',{var_info,{x,0},_}},return|Is]) ->
+ [{call_ext_only,A,F} | undo_renames(Is)];
undo_renames([{call_ext,A,F},return|Is]) ->
[{call_ext_only,A,F}|undo_renames(Is)];
+
undo_renames([{bif,raise,_,_,_}=I|Is0]) ->
%% A minor optimization. Done here because:
%% (1) beam_jump may move or share 'raise' instructions, and that
@@ -75,7 +90,8 @@ undo_renames([{bs_put,_,{bs_put_binary,1,_},
[{atom,all},{literal,<<>>}]}|Is]) ->
undo_renames(Is);
undo_renames([{bs_put,Fail,{bs_put_binary,1,_Flags},
- [{atom,all},{literal,BinString}]}|Is0]) ->
+ [{atom,all},{literal,BinString}]}|Is0])
+ when is_bitstring(BinString)->
Bits = bit_size(BinString),
Bytes = Bits div 8,
case Bits rem 8 of
@@ -118,13 +134,6 @@ undo_rename({bs_init,F,{I,Extra,U,Flags},Live,[Sz,Src],Dst}) ->
{I,F,Sz,Extra,Live,U,Src,Flags,Dst};
undo_rename({bs_init,_,bs_init_writable=I,_,_,_}) ->
I;
-undo_rename({test,bs_match_string=Op,F,[Ctx,Bin0]}) ->
- Bits = bit_size(Bin0),
- Bin = case Bits rem 8 of
- 0 -> Bin0;
- Rem -> <<Bin0/bitstring,0:(8-Rem)>>
- end,
- {test,Op,F,[Ctx,Bits,{string,Bin}]};
undo_rename({put_map,Fail,assoc,S,D,R,L}) ->
{put_map_assoc,Fail,S,D,R,L};
undo_rename({put_map,Fail,exact,S,D,R,L}) ->
diff --git a/lib/compiler/src/cerl_inline.erl b/lib/compiler/src/cerl_inline.erl
index caff47dbcb..8a2ea77b99 100644
--- a/lib/compiler/src/cerl_inline.erl
+++ b/lib/compiler/src/cerl_inline.erl
@@ -65,7 +65,7 @@
map_pair_op/1, map_pair_key/1, map_pair_val/1
]).
--import(lists, [foldl/3, foldr/3, mapfoldl/3, reverse/1]).
+-import(lists, [foldl/3, foldr/3, member/2, mapfoldl/3, reverse/1]).
%%
%% Constants
@@ -142,7 +142,7 @@ weight(module) -> 1. % Like a letrec with a constant body
%% environment, the state location, and the effort counter at the call
%% site (cf. `visit').
--record(opnd, {expr, ren, env, loc, effort}).
+-record(opnd, {expr, ren, env, loc, effort, no_inline}).
%% Since expressions are only visited in `effect' context when they are
%% not bound to a referenced variable, only expressions visited in
@@ -903,10 +903,14 @@ i_fun(E, Ctxt, Ren, Env, S) ->
%% side of each definition.
i_letrec(E, Ctxt, Ren, Env, S) ->
+ %% We must turn off inlining if this `letrec' is specially
+ %% implemented.
+ NoInline = member(letrec_goto, get_ann(E)),
+
%% Note that we pass an empty list for the auto-referenced
%% (exported) functions here.
{Es, B, _, S1} = i_letrec(letrec_defs(E), letrec_body(E), [], Ctxt,
- Ren, Env, S),
+ Ren, Env, NoInline, S),
%% If no bindings remain, only the body is returned.
case Es of
@@ -920,12 +924,13 @@ i_letrec(E, Ctxt, Ren, Env, S) ->
%% The major part of this is shared by letrec-expressions and module
%% definitions alike.
-i_letrec(Es, B, Xs, Ctxt, Ren, Env, S) ->
+i_letrec(Es, B, Xs, Ctxt, Ren, Env, NoInline, S) ->
%% First, we create operands with dummy renamings and environments,
%% and with fresh store locations for cached expressions and operand
%% info.
{Opnds, S1} = mapfoldl(fun ({_, E}, S) ->
- make_opnd(E, undefined, undefined, S)
+ make_opnd(E, undefined, undefined,
+ NoInline, S)
end,
S, Es),
@@ -1277,7 +1282,7 @@ i_module(E, Ctxt, Ren, Env, S) ->
%% "body" parameter.
Exps = i_module_exports(E),
{Es, _, Xs1, S1} = i_letrec(module_defs(E), void(),
- Exps, Ctxt, Ren, Env, S),
+ Exps, Ctxt, Ren, Env, false, S),
%% Sanity check:
case Es of
[] ->
@@ -1500,23 +1505,15 @@ inline(E, #app{opnds = Opnds, ctxt = Ctxt, loc = L}, Ren, Env, S) ->
%% respective operand structures from the app-structure.
{Rs, Ren1, Env1, S1} = bind_locals(Vs, Opnds, Ren, Env, S),
- %% function_clause exceptions that have been inlined
- %% into another function (or even into the same function)
- %% will not work properly. The v3_kernel pass will
- %% take care of it, but we will need to help it by
- %% removing any function_name annotations on match_fail
- %% primops that we inline.
- E1 = kill_function_name_anns(fun_body(E)),
-
%% Visit the body in the context saved in the structure.
- {E2, S2} = i(E1, Ctxt, Ren1, Env1, S1),
+ {E1, S2} = i(fun_body(E), Ctxt, Ren1, Env1, S1),
%% Create necessary bindings and/or set flags.
- {E3, S3} = make_let_bindings(Rs, E2, S2),
+ {E2, S3} = make_let_bindings(Rs, E1, S2),
%% Lastly, flag the application as inlined, since the inlining
%% attempt was not aborted before we reached this point.
- {E3, st__set_app_inlined(L, S3)}
+ {E2, st__set_app_inlined(L, S3)}
end.
%% For the (possibly renamed) argument variables to an inlined call,
@@ -1674,6 +1671,8 @@ copy_var(R, Ctxt, Env, S) ->
end
end.
+copy_1(R, #opnd{no_inline = true}, _E, _Ctxt, _Env, S) ->
+ residualize_var(R, S);
copy_1(R, Opnd, E, Ctxt, Env, S) ->
case type(E) of
'fun' ->
@@ -2075,9 +2074,13 @@ ref_to_var(#ref{name = Name}) ->
%% passive, the operands will also be processed with a passive counter.
make_opnd(E, Ren, Env, S) ->
+ make_opnd(E, Ren, Env, false, S).
+
+make_opnd(E, Ren, Env, NoInline, S) ->
{L, S1} = st__new_opnd_loc(S),
C = st__get_effort(S1),
- Opnd = #opnd{expr = E, ren = Ren, env = Env, loc = L, effort = C},
+ Opnd = #opnd{expr = E, ren = Ren, env = Env, loc = L,
+ effort = C, no_inline = NoInline},
{Opnd, S1}.
keep_referenced(Rs, S) ->
@@ -2469,19 +2472,6 @@ kill_id_anns([A | As]) ->
kill_id_anns([]) ->
[].
-kill_function_name_anns(Body) ->
- F = fun(P) ->
- case type(P) of
- primop ->
- Ann = get_ann(P),
- Ann1 = lists:keydelete(function_name, 1, Ann),
- set_ann(P, Ann1);
- _ ->
- P
- end
- end,
- cerl_trees:map(F, Body).
-
%% =====================================================================
%% General utilities
@@ -2526,21 +2516,19 @@ set_clause_bodies([], _) ->
%% Abstract datatype: renaming()
ren__identity() ->
- dict:new().
+ #{}.
ren__add(X, Y, Ren) ->
- dict:store(X, Y, Ren).
+ Ren#{X=>Y}.
ren__map(X, Ren) ->
- case dict:find(X, Ren) of
- {ok, Y} ->
- Y;
- error ->
- X
+ case Ren of
+ #{X:=Y} -> Y;
+ #{} -> X
end.
ren__add_identity(X, Ren) ->
- dict:erase(X, Ren).
+ maps:remove(X, Ren).
%% =====================================================================
@@ -2633,7 +2621,7 @@ st__new(Effort, Size, Unroll) ->
size = counter__new_passive(Size),
effort = counter__new_passive(Effort),
unroll = Unroll,
- cache = dict:new(),
+ cache = maps:new(),
var_flags = ets:new(var, EtsOpts),
opnd_flags = ets:new(opnd, EtsOpts),
app_flags = ets:new(app, EtsOpts)}.
@@ -2664,12 +2652,12 @@ st__get_var_referenced(L, S) ->
ets:lookup_element(S#state.var_flags, L, #var_flags.referenced).
st__lookup_opnd_cache(L, S) ->
- dict:find(L, S#state.cache).
+ maps:find(L, S#state.cache).
%% Note that setting the cache should only be done once.
st__set_opnd_cache(L, C, S) ->
- S#state{cache = dict:store(L, C, S#state.cache)}.
+ S#state{cache = maps:put(L, C, S#state.cache)}.
st__set_opnd_effect(L, S) ->
T = S#state.opnd_flags,
diff --git a/lib/compiler/src/cerl_sets.erl b/lib/compiler/src/cerl_sets.erl
index 38500160d5..8fe278b216 100644
--- a/lib/compiler/src/cerl_sets.erl
+++ b/lib/compiler/src/cerl_sets.erl
@@ -130,8 +130,10 @@ union1(S1, []) -> S1.
Set2 :: set(Element),
Set3 :: set(Element).
+intersection(S1, S2) when map_size(S1) >= map_size(S2) ->
+ filter(fun (E) -> is_element(E, S1) end, S2);
intersection(S1, S2) ->
- filter(fun (E) -> is_element(E, S1) end, S2).
+ intersection(S2, S1).
%% intersection([Set]) -> Set.
%% Return the intersection of the list of sets.
@@ -153,14 +155,21 @@ intersection1(S1, []) -> S1.
Set1 :: set(Element),
Set2 :: set(Element).
-is_disjoint(S1, S2) when map_size(S1) < map_size(S2) ->
- fold(fun (_, false) -> false;
- (E, true) -> not is_element(E, S2)
- end, true, S1);
+is_disjoint(S1, S2) when map_size(S1) > map_size(S2) ->
+ is_disjoint_1(S1, maps:iterator(S2));
is_disjoint(S1, S2) ->
- fold(fun (_, false) -> false;
- (E, true) -> not is_element(E, S1)
- end, true, S2).
+ is_disjoint_1(S2, maps:iterator(S1)).
+
+is_disjoint_1(Set, Iter) ->
+ case maps:next(Iter) of
+ {K, _, NextIter} ->
+ case Set of
+ #{K := _} -> false;
+ #{} -> is_disjoint_1(Set, NextIter)
+ end;
+ none ->
+ true
+ end.
%% subtract(Set1, Set2) -> Set.
%% Return all and only the elements of Set1 which are not also in
@@ -180,8 +189,21 @@ subtract(S1, S2) ->
Set1 :: set(Element),
Set2 :: set(Element).
+is_subset(S1, S2) when map_size(S1) > map_size(S2) ->
+ false;
is_subset(S1, S2) ->
- fold(fun (E, Sub) -> Sub andalso is_element(E, S2) end, true, S1).
+ is_subset_1(S2, maps:iterator(S1)).
+
+is_subset_1(Set, Iter) ->
+ case maps:next(Iter) of
+ {K, _, NextIter} ->
+ case Set of
+ #{K := _} -> is_subset_1(Set, NextIter);
+ #{} -> false
+ end;
+ none ->
+ true
+ end.
%% fold(Fun, Accumulator, Set) -> Accumulator.
%% Fold function Fun over all elements in Set and return Accumulator.
@@ -193,8 +215,16 @@ is_subset(S1, S2) ->
AccIn :: Acc,
AccOut :: Acc.
-fold(F, Init, D) ->
- lists:foldl(fun(E,Acc) -> F(E,Acc) end,Init,maps:keys(D)).
+fold(Fun, Init, Set) ->
+ fold_1(Fun, Init, maps:iterator(Set)).
+
+fold_1(Fun, Acc, Iter) ->
+ case maps:next(Iter) of
+ {K, _, NextIter} ->
+ fold_1(Fun, Fun(K,Acc), NextIter);
+ none ->
+ Acc
+ end.
%% filter(Fun, Set) -> Set.
%% Filter Set with Fun.
@@ -203,5 +233,18 @@ fold(F, Init, D) ->
Set1 :: set(Element),
Set2 :: set(Element).
-filter(F, D) ->
- maps:filter(fun(K,_) -> F(K) end, D).
+filter(Fun, Set) ->
+ maps:from_list(filter_1(Fun, maps:iterator(Set))).
+
+filter_1(Fun, Iter) ->
+ case maps:next(Iter) of
+ {K, _, NextIter} ->
+ case Fun(K) of
+ true ->
+ [{K,ok} | filter_1(Fun, NextIter)];
+ false ->
+ filter_1(Fun, NextIter)
+ end;
+ none ->
+ []
+ end.
diff --git a/lib/compiler/src/cerl_trees.erl b/lib/compiler/src/cerl_trees.erl
index 533c984221..a2089b5c1b 100644
--- a/lib/compiler/src/cerl_trees.erl
+++ b/lib/compiler/src/cerl_trees.erl
@@ -823,7 +823,7 @@ label(T) ->
-spec label(cerl:cerl(), integer()) -> {cerl:cerl(), integer()}.
label(T, N) ->
- label(T, N, dict:new()).
+ label(T, N, #{}).
label(T, N, Env) ->
case type(T) of
@@ -831,12 +831,13 @@ label(T, N, Env) ->
%% Constant literals are not labeled.
{T, N};
var ->
+ VarName = var_name(T),
{As, N1} =
- case dict:find(var_name(T), Env) of
- {ok, L} ->
+ case Env of
+ #{VarName := L} ->
{A, _} = label_ann(T, L),
{A, N};
- error ->
+ #{} ->
label_ann(T, N)
end,
{set_ann(T, As), N1};
@@ -974,7 +975,7 @@ label_list([], N, _Env) ->
{[], N}.
label_vars([T | Ts], N, Env) ->
- Env1 = dict:store(var_name(T), N, Env),
+ Env1 = Env#{var_name(T) => N},
{As, N1} = label_ann(T, N),
T1 = set_ann(T, As),
{Ts1, N2, Env2} = label_vars(Ts, N1, Env1),
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 49c44d2408..9a1a0d9640 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -40,7 +40,7 @@
-include("core_parse.hrl").
-import(lists, [member/2,reverse/1,reverse/2,keyfind/3,last/1,
- map/2,flatmap/2,foreach/2,foldr/3,any/2]).
+ map/2,flatmap/2,flatten/1,foreach/2,foldr/3,any/2]).
-define(SUB_PASS_TIMES, compile__sub_pass_times).
@@ -253,11 +253,11 @@ expand_opt(return, Os) ->
[return_errors,return_warnings|Os];
expand_opt(no_bsm3, Os) ->
%% The new bsm pass requires bsm3 instructions.
- [no_bsm3,no_bsm_opt|Os];
-expand_opt(r16, Os) ->
- expand_opt_before_21(Os);
-expand_opt(r17, Os) ->
- expand_opt_before_21(Os);
+ [no_bsm3,no_bsm_opt|expand_opt(no_bsm4, Os)];
+expand_opt(no_bsm4, Os) ->
+ %% bsm4 instructions are only used when type optimization has determined
+ %% that a match instruction won't fail.
+ expand_opt(no_type_opt, Os);
expand_opt(r18, Os) ->
expand_opt_before_21(Os);
expand_opt(r19, Os) ->
@@ -265,7 +265,11 @@ expand_opt(r19, Os) ->
expand_opt(r20, Os) ->
expand_opt_before_21(Os);
expand_opt(r21, Os) ->
- [no_put_tuple2 | expand_opt(no_bsm3, Os)];
+ [no_shared_fun_wrappers,
+ no_swap, no_put_tuple2 | expand_opt(no_bsm3, Os)];
+expand_opt(r22, Os) ->
+ [no_shared_fun_wrappers,
+ no_swap | expand_opt(no_bsm4, Os)];
expand_opt({debug_info_key,_}=O, Os) ->
[encrypt_debug_info,O|Os];
expand_opt(no_type_opt=O, Os) ->
@@ -278,7 +282,8 @@ expand_opt(no_type_opt=O, Os) ->
expand_opt(O, Os) -> [O|Os].
expand_opt_before_21(Os) ->
- [no_put_tuple2, no_get_hd_tl, no_ssa_opt_record,
+ [no_shared_fun_wrappers, no_swap,
+ no_put_tuple2, no_get_hd_tl, no_ssa_opt_record,
no_utf8_atoms | expand_opt(no_bsm3, Os)].
%% format_error(ErrorDescriptor) -> string()
@@ -450,11 +455,15 @@ run_sub_passes_1([{Name,Run}|Ps], Runner, St0)
run_sub_passes_1([], _, St) -> St.
run_tc({Name,Fun}, Code, St) ->
- put(?SUB_PASS_TIMES, []),
+ OldTimes = put(?SUB_PASS_TIMES, []),
T1 = erlang:monotonic_time(),
Val = (catch Fun(Code, St)),
T2 = erlang:monotonic_time(),
- Times = erase(?SUB_PASS_TIMES),
+ Times = get(?SUB_PASS_TIMES),
+ case OldTimes of
+ undefined -> erase(?SUB_PASS_TIMES);
+ _ -> put(?SUB_PASS_TIMES, OldTimes)
+ end,
Elapsed = erlang:convert_time_unit(T2 - T1, native, microsecond),
Mem0 = erts_debug:flat_size(Val)*erlang:system_info(wordsize),
Mem = lists:flatten(io_lib:format("~.1f kB", [Mem0/1024])),
@@ -597,7 +606,7 @@ passes_1([]) ->
{".erl",[?pass(parse_module)|standard_passes()]}.
pass(from_core) ->
- {".core",[?pass(parse_core)|core_passes(mandatory_core_lint)]};
+ {".core",[?pass(parse_core)|core_passes(non_verified_core)]};
pass(from_asm) ->
{".S",[?pass(beam_consult_asm)|asm_passes()]};
pass(from_beam) ->
@@ -780,11 +789,15 @@ standard_passes() ->
{iff,'dpp',{listing,"pp"}},
?pass(lint_module),
+
+ %% Add all -compile() directives to #compile.options
+ ?pass(compile_directives),
+
{iff,'P',{src_listing,"P"}},
{iff,'to_pp',{done,"P"}},
{iff,'dabstr',{listing,"abstr"}},
- {iff,debug_info,?pass(save_abstract_code)},
+ {delay,[{iff,debug_info,?pass(save_abstract_code)}]},
?pass(expand_records),
{iff,'dexp',{listing,"expand"}},
@@ -795,33 +808,35 @@ standard_passes() ->
?pass(core),
{iff,'dcore',{listing,"core"}},
{iff,'to_core0',{done,"core"}}
- | core_passes(optional_core_lint)].
+ | core_passes(verified_core)].
-core_passes(LintOpt) ->
+core_passes(CoreStatus) ->
%% Optimization and transforms of Core Erlang code.
- CoreLint = case LintOpt of
- mandatory_core_lint ->
- ?pass(core_lint_module);
- optional_core_lint ->
- {iff,clint0,?pass(core_lint_module)}
- end,
- [CoreLint,
- {delay,
- [{unless,no_copt,
- [{core_old_inliner,fun test_old_inliner/1,fun core_old_inliner/2},
- {iff,doldinline,{listing,"oldinline"}},
- {unless,no_fold,{pass,sys_core_fold}},
- {iff,dcorefold,{listing,"corefold"}},
- {core_inline_module,fun test_core_inliner/1,fun core_inline_module/2},
- {iff,dinline,{listing,"inline"}},
- {core_fold_after_inlining,fun test_any_inliner/1,
- fun core_fold_module_after_inlining/2},
- {iff,dcopt,{listing,"copt"}},
- {unless,no_alias,{pass,sys_core_alias}},
- {iff,dalias,{listing,"core_alias"}},
- ?pass(core_transforms)]},
- {iff,'to_core',{done,"core"}}]}
- | kernel_passes()].
+ case CoreStatus of
+ non_verified_core ->
+ [?pass(core_lint_module),
+ {unless,no_core_prepare,{pass,sys_core_prepare}},
+ {iff,dprep,{listing,"prepare"}}];
+ verified_core ->
+ [{iff,clint0,?pass(core_lint_module)}]
+ end ++
+ [
+ {delay,
+ [{unless,no_copt,
+ [{core_old_inliner,fun test_old_inliner/1,fun core_old_inliner/2},
+ {iff,doldinline,{listing,"oldinline"}},
+ {unless,no_fold,{pass,sys_core_fold}},
+ {iff,dcorefold,{listing,"corefold"}},
+ {core_inline_module,fun test_core_inliner/1,fun core_inline_module/2},
+ {iff,dinline,{listing,"inline"}},
+ {core_fold_after_inlining,fun test_any_inliner/1,
+ fun core_fold_module_after_inlining/2},
+ {iff,dcopt,{listing,"copt"}},
+ {unless,no_alias,{pass,sys_core_alias}},
+ {iff,dalias,{listing,"core_alias"}},
+ ?pass(core_transforms)]},
+ {iff,'to_core',{done,"core"}}]}
+ | kernel_passes()].
kernel_passes() ->
%% Optimizations that must be done after all other optimizations.
@@ -839,26 +854,36 @@ kernel_passes() ->
{iff,dssa,{listing,"ssa"}},
{iff,ssalint,{pass,beam_ssa_lint}},
{delay,
- [{unless,no_share_opt,{pass,beam_ssa_share}},
+ [{unless,no_bool_opt,{pass,beam_ssa_bool}},
+ {iff,dbool,{listing,"bool"}},
+ {unless,no_bool_opt,{iff,ssalint,{pass,beam_ssa_lint}}},
+
+ {unless,no_share_opt,{pass,beam_ssa_share}},
{iff,dssashare,{listing,"ssashare"}},
- {iff,ssalint,{pass,beam_ssa_lint}},
+ {unless,no_share_opt,{iff,ssalint,{pass,beam_ssa_lint}}},
+
{unless,no_bsm_opt,{pass,beam_ssa_bsm}},
{iff,dssabsm,{listing,"ssabsm"}},
- {iff,ssalint,{pass,beam_ssa_lint}},
+ {unless,no_bsm_opt,{iff,ssalint,{pass,beam_ssa_lint}}},
+
{unless,no_fun_opt,{pass,beam_ssa_funs}},
{iff,dssafuns,{listing,"ssafuns"}},
- {iff,ssalint,{pass,beam_ssa_lint}},
+ {unless,no_fun_opt,{iff,ssalint,{pass,beam_ssa_lint}}},
+
{unless,no_ssa_opt,{pass,beam_ssa_opt}},
{iff,dssaopt,{listing,"ssaopt"}},
- {iff,ssalint,{pass,beam_ssa_lint}},
+ {unless,no_ssa_opt,{iff,ssalint,{pass,beam_ssa_lint}}},
+
{unless,no_recv_opt,{pass,beam_ssa_recv}},
- {iff,drecv,{listing,"recv"}}]},
+ {iff,drecv,{listing,"recv"}},
+ {unless,no_recv_opt,{iff,ssalint,{pass,beam_ssa_lint}}}]},
{pass,beam_ssa_pre_codegen},
{iff,dprecg,{listing,"precodegen"}},
{iff,ssalint,{pass,beam_ssa_lint}},
{pass,beam_ssa_codegen},
{iff,dcg,{listing,"codegen"}},
- {iff,doldcg,{listing,"codegen"}}
+ {iff,doldcg,{listing,"codegen"}},
+ ?pass(beam_validator_strong)
| asm_passes()].
asm_passes() ->
@@ -869,8 +894,6 @@ asm_passes() ->
{unless,no_postopt,
[{pass,beam_block},
{iff,dblk,{listing,"block"}},
- {unless,no_except,{pass,beam_except}},
- {iff,dexcept,{listing,"except"}},
{unless,no_jopt,{pass,beam_jump}},
{iff,djmp,{listing,"jump"}},
{unless,no_peep_opt,{pass,beam_peep}},
@@ -892,7 +915,7 @@ asm_passes() ->
{iff,dopt,{listing,"optimize"}},
{iff,'S',{listing,"S"}},
{iff,'to_asm',{done,"S"}}]},
- {pass,beam_validator},
+ ?pass(beam_validator_weak),
?pass(beam_asm)
| binary_passes()].
@@ -915,8 +938,6 @@ remove_file(Code, St) ->
exports,
labels,
functions=[],
- cfun,
- code,
attributes=[]}).
preprocess_asm_forms(Forms) ->
@@ -926,36 +947,30 @@ preprocess_asm_forms(Forms) ->
{R1#asm_module.module,
R1#asm_module.exports,
R1#asm_module.attributes,
- R1#asm_module.functions,
+ reverse(R1#asm_module.functions),
R1#asm_module.labels}}.
-collect_asm([], R) ->
- case R#asm_module.cfun of
- undefined ->
- R;
- {A,B,C} ->
- R#asm_module{functions=R#asm_module.functions++
- [{function,A,B,C,R#asm_module.code}]}
- end;
collect_asm([{module,M} | Rest], R) ->
collect_asm(Rest, R#asm_module{module=M});
collect_asm([{exports,M} | Rest], R) ->
collect_asm(Rest, R#asm_module{exports=M});
collect_asm([{labels,M} | Rest], R) ->
collect_asm(Rest, R#asm_module{labels=M});
-collect_asm([{function,A,B,C} | Rest], R) ->
- R1 = case R#asm_module.cfun of
- undefined ->
- R;
- {A0,B0,C0} ->
- R#asm_module{functions=R#asm_module.functions++
- [{function,A0,B0,C0,R#asm_module.code}]}
- end,
- collect_asm(Rest, R1#asm_module{cfun={A,B,C}, code=[]});
+collect_asm([{function,A,B,C} | Rest0], R0) ->
+ {Code,Rest} = collect_asm_function(Rest0, []),
+ Func = {function,A,B,C,Code},
+ R = R0#asm_module{functions=[Func | R0#asm_module.functions]},
+ collect_asm(Rest, R);
collect_asm([{attributes, Attr} | Rest], R) ->
collect_asm(Rest, R#asm_module{attributes=Attr});
-collect_asm([X | Rest], R) ->
- collect_asm(Rest, R#asm_module{code=R#asm_module.code++[X]}).
+collect_asm([], R) -> R.
+
+collect_asm_function([{function,_,_,_}|_]=Is, Acc) ->
+ {reverse(Acc),Is};
+collect_asm_function([I|Is], Acc) ->
+ collect_asm_function(Is, [I|Acc]);
+collect_asm_function([], Acc) ->
+ {reverse(Acc),[]}.
beam_consult_asm(_Code, St) ->
case file:consult(St#compile.ifile) of
@@ -1435,9 +1450,11 @@ expand_records(Code0, #compile{options=Opts}=St) ->
Code = erl_expand_records:module(Code0, Opts),
{ok,Code,St}.
-core(Forms, #compile{options=Opts0}=St) ->
- Opts1 = lists:flatten([C || {attribute,_,compile,C} <- Forms] ++ Opts0),
- Opts = expand_opts(Opts1),
+compile_directives(Forms, #compile{options=Opts0}=St) ->
+ Opts = expand_opts(flatten([C || {attribute,_,compile,C} <- Forms])),
+ {ok, Forms, St#compile{options=Opts ++ Opts0}}.
+
+core(Forms, #compile{options=Opts}=St) ->
{ok,Core,Ws} = v3_core:module(Forms, Opts),
Mod = cerl:concrete(cerl:module_name(Core)),
{ok,Core,St#compile{module=Mod,options=Opts,
@@ -1492,17 +1509,8 @@ core_inline_module(Code0, #compile{options=Opts}=St) ->
save_abstract_code(Code, St) ->
{ok,Code,St#compile{abstract_code=erl_parse:anno_to_term(Code)}}.
-debug_info(#compile{module=Module,mod_options=Opts0,ofile=OFile,abstract_code=Abst}) ->
- AbstOpts = cleanup_compile_options(Opts0),
- Opts1 = proplists:delete(debug_info, Opts0),
- {Backend,Metadata,Opts2} =
- case proplists:get_value(debug_info, Opts0, false) of
- {OptBackend,OptMetadata} when is_atom(OptBackend) -> {OptBackend,OptMetadata,Opts1};
- false -> {erl_abstract_code,{none,AbstOpts},Opts1};
- true -> {erl_abstract_code,{Abst,AbstOpts},[debug_info | Opts1]}
- end,
- DebugInfo = erlang:term_to_binary({debug_info_v1,Backend,Metadata}, [compressed]),
-
+debug_info(#compile{module=Module,ofile=OFile}=St) ->
+ {DebugInfo,Opts2} = debug_info_chunk(St),
case member(encrypt_debug_info, Opts2) of
true ->
case lists:keytake(debug_info_key, 1, Opts2) of
@@ -1521,6 +1529,25 @@ debug_info(#compile{module=Module,mod_options=Opts0,ofile=OFile,abstract_code=Ab
{ok,DebugInfo,Opts2}
end.
+debug_info_chunk(#compile{mod_options=ModOpts0,
+ options=CompOpts,
+ abstract_code=Abst}) ->
+ AbstOpts = cleanup_compile_options(ModOpts0),
+ {Backend,Metadata,ModOpts} =
+ case proplists:get_value(debug_info, CompOpts, false) of
+ {OptBackend,OptMetadata} when is_atom(OptBackend) ->
+ ModOpts1 = proplists:delete(debug_info, ModOpts0),
+ {OptBackend,OptMetadata,ModOpts1};
+ true ->
+ ModOpts1 = proplists:delete(debug_info, ModOpts0),
+ {erl_abstract_code,{Abst,AbstOpts},[debug_info | ModOpts1]};
+ false ->
+ {erl_abstract_code,{none,AbstOpts},ModOpts0}
+ end,
+ DebugInfo = erlang:term_to_binary({debug_info_v1,Backend,Metadata},
+ [compressed]),
+ {DebugInfo, ModOpts}.
+
encrypt_debug_info(DebugInfo, Key, Opts) ->
try
RealKey = generate_key(Key),
@@ -1578,13 +1605,27 @@ encrypt({des3_cbc=Type,Key,IVec,BlockSize}, Bin0) ->
0 -> Bin0;
N -> list_to_binary([Bin0,crypto:strong_rand_bytes(BlockSize-N)])
end,
- Bin = crypto:block_encrypt(Type, Key, IVec, Bin1),
+ Bin = crypto:crypto_one_time(des_ede3_cbc, Key, IVec, Bin1, true),
TypeString = atom_to_list(Type),
list_to_binary([0,length(TypeString),TypeString,Bin]).
save_core_code(Code, St) ->
{ok,Code,St#compile{core_code=cerl:from_records(Code)}}.
+beam_validator_strong(Code, St) ->
+ beam_validator_1(Code, St, strong).
+
+beam_validator_weak(Code, St) ->
+ beam_validator_1(Code, St, weak).
+
+beam_validator_1(Code, #compile{errors=Errors0}=St, Level) ->
+ case beam_validator:validate(Code, Level) of
+ ok ->
+ {ok, Code, St};
+ {error, Es} ->
+ {error, St#compile{errors=Errors0 ++ Es}}
+ end.
+
beam_asm(Code0, #compile{ifile=File,extra_chunks=ExtraChunks,options=CompilerOpts}=St) ->
case debug_info(St) of
{ok,DebugInfo,Opts0} ->
@@ -2102,15 +2143,17 @@ pre_load() ->
L = [beam_a,
beam_asm,
beam_block,
+ beam_call_types,
beam_clean,
beam_dict,
- beam_except,
+ beam_digraph,
beam_flatten,
beam_jump,
beam_kernel_to_ssa,
beam_opcodes,
beam_peep,
beam_ssa,
+ beam_ssa_bool,
beam_ssa_bsm,
beam_ssa_codegen,
beam_ssa_dead,
@@ -2121,6 +2164,7 @@ pre_load() ->
beam_ssa_share,
beam_ssa_type,
beam_trim,
+ beam_types,
beam_utils,
beam_validator,
beam_z,
diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src
index db32d35dac..92e9fa74e5 100644
--- a/lib/compiler/src/compiler.app.src
+++ b/lib/compiler/src/compiler.app.src
@@ -24,10 +24,11 @@
beam_a,
beam_asm,
beam_block,
+ beam_call_types,
beam_clean,
beam_dict,
+ beam_digraph,
beam_disasm,
- beam_except,
beam_flatten,
beam_jump,
beam_kernel_to_ssa,
@@ -35,6 +36,7 @@
beam_opcodes,
beam_peep,
beam_ssa,
+ beam_ssa_bool,
beam_ssa_bsm,
beam_ssa_codegen,
beam_ssa_dead,
@@ -47,6 +49,7 @@
beam_ssa_share,
beam_ssa_type,
beam_trim,
+ beam_types,
beam_utils,
beam_validator,
beam_z,
@@ -68,6 +71,7 @@
sys_core_fold,
sys_core_fold_lists,
sys_core_inline,
+ sys_core_prepare,
sys_pre_attributes,
v3_core,
v3_kernel,
@@ -76,5 +80,5 @@
{registered, []},
{applications, [kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["stdlib-2.5","kernel-4.0","hipe-3.12","erts-9.0",
+ {runtime_dependencies, ["stdlib-3.13","kernel-7.0","hipe-3.12","erts-11.0",
"crypto-3.6"]}]}.
diff --git a/lib/compiler/src/core_lib.erl b/lib/compiler/src/core_lib.erl
index c1806272bd..724f7edcb0 100644
--- a/lib/compiler/src/core_lib.erl
+++ b/lib/compiler/src/core_lib.erl
@@ -26,6 +26,15 @@
-include("core_parse.hrl").
+%% Removed functions
+
+-removed([{get_anno,1,"use cerl:get_ann/1 instead"},
+ {set_anno,2,"use cerl:set_ann/2 instead"}]).
+
+-removed([{is_literal,1,"use cerl:is_literal/1 instead"},
+ {is_literal_list,1,"use cerl:is_literal_list/1 instead"},
+ {literal_value,1,"use cerl:concrete/1 instead"}]).
+
%% Make a suitable values structure, expr or values, depending on Expr.
-spec make_values([cerl:cerl()] | cerl:cerl()) -> cerl:cerl().
@@ -42,10 +51,6 @@ is_var_used(V, B) -> vu_expr(V, B).
vu_expr(V, #c_values{es=Es}) ->
vu_expr_list(V, Es);
vu_expr(V, #c_var{name=V2}) -> V =:= V2;
-vu_expr(V, #c_alias{var=V2,pat=Pat}) ->
- %% XXX Must handle aliases in expressions because of sys_core_fold:kill_types/2,
- %% that uses a pattern as if it was an expression.
- V =:= V2 orelse vu_expr(V, Pat);
vu_expr(_, #c_literal{}) -> false;
vu_expr(V, #c_cons{hd=H,tl=T}) ->
vu_expr(V, H) orelse vu_expr(V, T);
@@ -79,8 +84,6 @@ vu_expr(V, #c_seq{arg=Arg,body=B}) ->
vu_expr(V, Arg) orelse vu_expr(V, B);
vu_expr(V, #c_case{arg=Arg,clauses=Cs}) ->
vu_expr(V, Arg) orelse vu_clauses(V, Cs);
-vu_expr(V, #c_receive{clauses=Cs,timeout=T,action=A}) ->
- vu_clauses(V, Cs) orelse vu_expr(V, T) orelse vu_expr(V, A);
vu_expr(V, #c_apply{op=Op,args=As}) ->
vu_expr_list(V, [Op|As]);
vu_expr(V, #c_call{module=M,name=N,args=As}) ->
@@ -115,77 +118,47 @@ vu_seg_list(V, Ss) ->
vu_expr(V, Val) orelse vu_expr(V, Size)
end, Ss).
-%% Have to get the pattern results right.
-
-spec vu_clause(cerl:var_name(), cerl:c_clause()) -> boolean().
vu_clause(V, #c_clause{pats=Ps,guard=G,body=B}) ->
- case vu_pattern_list(V, Ps) of
- {true,_Shad} -> true; %It is used
- {false,true} -> false; %Shadowed
- {false,false} -> %Not affected
- %% Neither used nor shadowed. Check guard and body.
- vu_expr(V, G) orelse vu_expr(V, B)
- end.
+ vu_pattern_list(V, Ps) orelse vu_expr(V, G) orelse vu_expr(V, B).
-spec vu_clauses(cerl:var_name(), [cerl:c_clause()]) -> boolean().
vu_clauses(V, Cs) ->
lists:any(fun(C) -> vu_clause(V, C) end, Cs).
-%% vu_pattern(VarName, Pattern) -> {Used,Shadow}.
-%% vu_pattern_list(VarName, [Pattern]) -> {Used,Shadow}.
-%% Binaries complicate patterns as a variable can both be properly
-%% used, in a bit segment size, and shadow. They can also do both.
-
-%% vu_pattern(V, Pat) -> vu_pattern(V, Pat, {false,false}).
-
-vu_pattern(V, #c_var{name=V2}, {Used,_}) ->
- {Used,V =:= V2};
-vu_pattern(V, #c_cons{hd=H,tl=T}, St0) ->
- case vu_pattern(V, H, St0) of
- {true,_}=St1 -> St1; %Nothing more to know
- St1 -> vu_pattern(V, T, St1)
- end;
-vu_pattern(V, #c_tuple{es=Es}, St) ->
- vu_pattern_list(V, Es, St);
-vu_pattern(V, #c_binary{segments=Ss}, St) ->
- vu_pat_seg_list(V, Ss, St);
-vu_pattern(V, #c_map{es=Es}, St) ->
- vu_map_pairs(V, Es, St);
-vu_pattern(V, #c_alias{var=Var,pat=P}, St0) ->
- case vu_pattern(V, Var, St0) of
- {true,_}=St1 -> St1;
- St1 -> vu_pattern(V, P, St1)
- end;
-vu_pattern(_, _, St) -> St.
-
-vu_pattern_list(V, Ps) -> vu_pattern_list(V, Ps, {false,false}).
-
-vu_pattern_list(V, Ps, St0) ->
- lists:foldl(fun(P, St) -> vu_pattern(V, P, St) end, St0, Ps).
-
-vu_pat_seg_list(V, Ss, St) ->
- lists:foldl(fun(_, {true,_}=St0) -> St0;
- (#c_bitstr{val=Val,size=Size}, St0) ->
- case vu_pattern(V, Val, St0) of
- {true,_}=St1 -> St1;
- {false,Shad} ->
- {vu_expr(V, Size),Shad}
- end
- end, St, Ss).
-
-vu_map_pairs(V, [#c_map_pair{key=Key,val=Pat}|T], St0) ->
- case vu_expr(V, Key) of
- true ->
- {true,false};
- false ->
- case vu_pattern(V, Pat, St0) of
- {true,_}=St -> St;
- St -> vu_map_pairs(V, T, St)
- end
- end;
-vu_map_pairs(_, [], St) -> St.
+%% vu_pattern(VarName, Pattern) -> Used.
+%% vu_pattern_list(VarName, [Pattern]) -> Used.
+%% Binary and map patterns can use variables.
+
+vu_pattern(V, #c_var{name=V2}) ->
+ V =:= V2;
+vu_pattern(V, #c_cons{hd=H,tl=T}) ->
+ vu_pattern(V, H) orelse vu_pattern(V, T);
+vu_pattern(V, #c_tuple{es=Es}) ->
+ vu_pattern_list(V, Es);
+vu_pattern(V, #c_binary{segments=Ss}) ->
+ vu_pat_seg_list(V, Ss);
+vu_pattern(V, #c_map{es=Es}) ->
+ vu_map_pairs(V, Es);
+vu_pattern(V, #c_alias{var=Var,pat=P}) ->
+ vu_pattern(V, Var) orelse vu_pattern(V, P);
+vu_pattern(_V, #c_literal{}) -> false.
+
+vu_pattern_list(V, Ps) ->
+ lists:any(fun(P) -> vu_pattern(V, P) end, Ps).
+
+vu_pat_seg_list(V, Ss) ->
+ lists:any(fun(#c_bitstr{size=Size}) ->
+ vu_pattern(V, Size)
+ end, Ss).
+
+vu_map_pairs(V, [#c_map_pair{key=Key,val=Pat}|T]) ->
+ vu_expr(V, Key) orelse
+ vu_pattern(V, Pat) orelse
+ vu_map_pairs(V, T);
+vu_map_pairs(_, []) -> false.
-spec vu_var_list(cerl:var_name(), [cerl:c_var()]) -> boolean().
diff --git a/lib/compiler/src/core_lint.erl b/lib/compiler/src/core_lint.erl
index 3f69cb03a9..f3fe901949 100644
--- a/lib/compiler/src/core_lint.erl
+++ b/lib/compiler/src/core_lint.erl
@@ -55,9 +55,9 @@
-type fa() :: {atom(), arity()}.
-type err_desc() :: 'invalid_attributes' | 'invalid_exports'
- | {'arg_mismatch', fa()} | {'bittype_unit', fa()}
+ | {'arg_mismatch', fa()}
| {'illegal_expr', fa()} | {'illegal_guard', fa()}
- | {'illegal_pattern', fa()} | {'illegal_try', fa()}
+ | {'illegal_try', fa()}
| {'not_bs_pattern', fa()} | {'not_pattern', fa()}
| {'not_var', fa()} | {'pattern_mismatch', fa()}
| {'return_mismatch', fa()} | {'undefined_function', fa()}
@@ -88,14 +88,10 @@ format_error(invalid_attributes) -> "invalid attributes";
format_error(invalid_exports) -> "invalid exports";
format_error({arg_mismatch,{F,A}}) ->
io_lib:format("argument count mismatch in ~w/~w", [F,A]);
-format_error({bittype_unit,{F,A}}) ->
- io_lib:format("unit without size in bit syntax pattern/expression in ~w/~w", [F,A]);
format_error({illegal_expr,{F,A}}) ->
io_lib:format("illegal expression in ~w/~w", [F,A]);
format_error({illegal_guard,{F,A}}) ->
io_lib:format("illegal guard expression in ~w/~w", [F,A]);
-format_error({illegal_pattern,{F,A}}) ->
- io_lib:format("illegal pattern in ~w/~w", [F,A]);
format_error({illegal_try,{F,A}}) ->
io_lib:format("illegal try expression in ~w/~w", [F,A]);
format_error({not_bs_pattern,{F,A}}) ->
@@ -111,9 +107,9 @@ format_error({return_mismatch,{F,A}}) ->
format_error({undefined_function,{F,A}}) ->
io_lib:format("function ~w/~w undefined", [F,A]);
format_error({duplicate_var,N,{F,A}}) ->
- io_lib:format("duplicate variable ~s in ~w/~w", [N,F,A]);
+ io_lib:format("duplicate variable ~p in ~w/~w", [N,F,A]);
format_error({unbound_var,N,{F,A}}) ->
- io_lib:format("unbound variable ~s in ~w/~w", [N,F,A]);
+ io_lib:format("unbound variable ~p in ~w/~w", [N,F,A]);
format_error({undefined_function,{F1,A1},{F2,A2}}) ->
io_lib:format("undefined function ~w/~w in ~w/~w", [F1,A1,F2,A2]);
format_error({tail_segment_not_at_end,{F,A}}) ->
@@ -201,8 +197,13 @@ module_defs(B, Def, St) ->
%% functions([Fdef], Defined, State) -> State.
-functions(Fs, Def, St0) ->
- foldl(fun (F, St) -> function(F, Def, St) end, St0, Fs).
+functions(Fs, Def, Rt, St0) ->
+ foldl(fun ({_Name,#c_fun{vars=Vs,body=B}}, Sti0) ->
+ {Vvs,St} = variable_list(Vs, Sti0),
+ body(B, union(Vvs, Def), Rt, St);
+ (_, St) ->
+ add_error({illegal_expr,St#lint.func}, St)
+ end, St0, Fs).
%% function(CoreFunc, Defined, State) -> State.
@@ -276,11 +277,17 @@ gexpr(#c_call{module=#c_literal{val=erlang},name=#c_literal{val=is_record}},
gexpr(#c_call{module=#c_literal{val=erlang},name=#c_literal{val=Name},args=As},
Def, Rt, St0) when is_atom(Name) ->
St1 = return_match(Rt, 1, St0),
- case is_guard_bif(Name, length(As)) of
+ Arity = length(As),
+ case is_guard_bif(Name, Arity) of
true ->
gexpr_list(As, Def, St1);
false ->
- add_error({illegal_guard,St1#lint.func}, St1)
+ case {Name,Arity} of
+ {error,1} ->
+ gexpr_list(As, Def, St1);
+ _ ->
+ add_error({illegal_guard,St1#lint.func}, St1)
+ end
end;
gexpr(#c_primop{name=#c_literal{val=A},args=As}, Def, _Rt, St0) when is_atom(A) ->
gexpr_list(As, Def, St0);
@@ -347,7 +354,7 @@ expr(#c_let{vars=Vs,arg=Arg,body=B}, Def, Rt, St0) ->
body(B, union(Lvs, Def), Rt, St2);
expr(#c_letrec{defs=Fs,body=B}, Def0, Rt, St0) ->
Def1 = union(defined_funcs(Fs), Def0), %All defined stuff
- St1 = functions(Fs, Def1, St0),
+ St1 = functions(Fs, Def1, Rt, St0),
body(B, Def1, Rt, St1#lint{func=St0#lint.func});
expr(#c_case{arg=Arg,clauses=Cs}, Def, Rt, St0) ->
Pc = case_patcount(Cs),
@@ -357,9 +364,9 @@ expr(#c_receive{clauses=Cs,timeout=T,action=A}, Def, Rt, St0) ->
St1 = expr(T, Def, 1, St0),
St2 = body(A, Def, Rt, St1),
clauses(Cs, Def, 1, Rt, St2);
-expr(#c_apply{op=Op,args=As}, Def, Rt, St0) ->
+expr(#c_apply{op=Op,args=As}, Def, _Rt, St0) ->
St1 = apply_op(Op, Def, length(As), St0),
- return_match(Rt, 1, expr_list(As, Def, St1));
+ return_match(any, 1, expr_list(As, Def, St1));
expr(#c_call{module=#c_literal{val=erlang},name=#c_literal{val=Name},args=As},
Def, Rt, St0) when is_atom(Name) ->
St1 = expr_list(As, Def, St0),
@@ -375,6 +382,7 @@ expr(#c_primop{name=#c_literal{val=A},args=As}, Def, Rt, St0) when is_atom(A) ->
St1 = expr_list(As, Def, St0),
case A of
match_fail -> St1;
+ recv_peek_message -> return_match(Rt, 2, St1);
_ -> return_match(Rt, 1, St1)
end;
expr(#c_catch{body=B}, Def, Rt, St) ->
@@ -513,22 +521,16 @@ pat_var(N, _Def, Ps, St) ->
%% pat_bin_list([Elem], Defined, [PatVar], State) -> {[PatVar],State}.
-pat_bin(Es, Def0, Ps0, St0) ->
- {Ps,_,St} = foldl(fun (E, {Ps,Def,St}) ->
- pat_segment(E, Def, Ps, St)
- end, {Ps0,Def0,St0}, Es),
- {Ps,St}.
-
-pat_segment(#c_bitstr{val=V,size=S,type=T}, Def0, Ps0, St0) ->
- St1 = pat_bit_expr(S, T, Def0, St0),
- {Ps,St2} = pattern(V, Def0, Ps0, St1),
- Def = case V of
- #c_var{name=Name} -> add_element(Name, Def0);
- _ -> Def0
- end,
- {Ps,Def,St2};
-pat_segment(_, Def, Ps, St) ->
- {Ps,Def,add_error({not_bs_pattern,St#lint.func}, St)}.
+pat_bin(Es, Def, Ps0, St0) ->
+ foldl(fun (E, {Ps,St}) ->
+ pat_segment(E, Def, Ps, St)
+ end, {Ps0,St0}, Es).
+
+pat_segment(#c_bitstr{val=V,size=S,type=T}, Def, Ps0, St0) ->
+ St1 = pat_bit_expr(S, T, Def, St0),
+ pattern(V, Def, Ps0, St1);
+pat_segment(_, _, Ps, St) ->
+ {Ps,add_error({not_bs_pattern,St#lint.func}, St)}.
%% pat_bin_tail_check([Elem], State) -> State.
%% There must be at most one tail segment (a size-less segment of
diff --git a/lib/compiler/src/core_pp.erl b/lib/compiler/src/core_pp.erl
index cb3f24fd08..19fa11235c 100644
--- a/lib/compiler/src/core_pp.erl
+++ b/lib/compiler/src/core_pp.erl
@@ -214,7 +214,7 @@ format_1(#c_let{anno=Anno0,vars=Vs0,arg=A0,body=B}, #ctxt{clean=Clean}=Ctxt) ->
{Vs0,A0,Anno0};
true ->
{[cerl:set_ann(V, []) || V <- Vs0],
- cerl:set_ann(A0, []),
+ clean_anno_carefully(A0),
[]}
end,
case is_simple_term(A) andalso Anno =:= [] of
@@ -546,3 +546,13 @@ segs_from_bitstring(Bitstring) ->
unit=#c_literal{val=1},
type=#c_literal{val=integer},
flags=#c_literal{val=[unsigned,big]}}].
+
+clean_anno_carefully(Node) ->
+ Anno = clean_anno_carefully_1(cerl:get_ann(Node)),
+ cerl:set_ann(Node, Anno).
+
+clean_anno_carefully_1([letrec_goto=Keep|Annos]) ->
+ [Keep|clean_anno_carefully_1(Annos)];
+clean_anno_carefully_1([_|Annos]) ->
+ clean_anno_carefully_1(Annos);
+clean_anno_carefully_1([]) -> [].
diff --git a/lib/compiler/src/erl_bifs.erl b/lib/compiler/src/erl_bifs.erl
index 3a66fe1d39..567e7e8f42 100644
--- a/lib/compiler/src/erl_bifs.erl
+++ b/lib/compiler/src/erl_bifs.erl
@@ -80,10 +80,12 @@ is_pure(erlang, 'or', 2) -> true;
is_pure(erlang, 'rem', 2) -> true;
is_pure(erlang, 'xor', 2) -> true;
is_pure(erlang, abs, 1) -> true;
+is_pure(erlang, atom_to_binary, 1) -> true;
is_pure(erlang, atom_to_binary, 2) -> true;
is_pure(erlang, atom_to_list, 1) -> true;
is_pure(erlang, binary_part, 2) -> true;
is_pure(erlang, binary_part, 3) -> true;
+is_pure(erlang, binary_to_atom, 1) -> true;
is_pure(erlang, binary_to_atom, 2) -> true;
is_pure(erlang, binary_to_float, 1) -> true;
is_pure(erlang, binary_to_integer, 1) -> true;
@@ -144,6 +146,9 @@ is_pure(erlang, tuple_size, 1) -> true;
is_pure(erlang, tuple_to_list, 1) -> true;
is_pure(lists, append, 2) -> true;
is_pure(lists, subtract, 2) -> true;
+is_pure(maps, get, 2) -> true;
+is_pure(maps, is_key, 2) -> true;
+is_pure(maps, new, 0) -> true;
is_pure(math, acos, 1) -> true;
is_pure(math, acosh, 1) -> true;
is_pure(math, asin, 1) -> true;
@@ -183,8 +188,13 @@ is_pure(_, _, _) -> false.
%% and does not affect the state (although the value it returns
%% might depend on the state).
%%
-%% Note: is_function/2 and is_record/3 are NOT safe: is_function(X, foo)
-%% and is_record(X, foo, bar) will fail.
+%% NOTES
+%%
+%% is_function/2 is not safe: is_function(X, foo) will fail.
+%%
+%% is_record/3 is not safe: is_record(X, foo, bar) will fail.
+%%
+%% erlang:make_fun/3 is safe: erlang:make_fun3(foo, bar, baz) will fail.
-spec is_safe(atom(), atom(), arity()) -> boolean().
@@ -218,7 +228,6 @@ is_safe(erlang, is_port, 1) -> true;
is_safe(erlang, is_reference, 1) -> true;
is_safe(erlang, is_tuple, 1) -> true;
is_safe(erlang, make_ref, 0) -> true;
-is_safe(erlang, make_fun, 3) -> true;
is_safe(erlang, max, 2) -> true;
is_safe(erlang, min, 2) -> true;
is_safe(erlang, node, 0) -> true;
diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab
index 86590fad87..64680ca1ed 100755
--- a/lib/compiler/src/genop.tab
+++ b/lib/compiler/src/genop.tab
@@ -596,3 +596,15 @@ BEAM_FORMAT_NUMBER=0
## @spec bs_set_positon Ctx Pos
## @doc Sets the current position of Ctx to Pos
168: bs_set_position/2
+
+# OTP 23
+
+## @spec swap Register1 Register2
+## @doc Swaps the contents of two registers.
+169: swap/2
+
+## @spec bs_start_match4 Fail Bin Live Dst
+## @doc As bs_start_match3, but the fail label can be 'no_fail' when we know
+## it will never fail at runtime, or 'resume' when we know the input is
+## a match context.
+170: bs_start_match4/4
diff --git a/lib/compiler/src/sys_core_alias.erl b/lib/compiler/src/sys_core_alias.erl
index 3326c6a2a8..591f38d0b7 100644
--- a/lib/compiler/src/sys_core_alias.erl
+++ b/lib/compiler/src/sys_core_alias.erl
@@ -284,24 +284,31 @@ nodes_to_alias(Kind, Inner, Anno, Node, Keys0) ->
%% Builds the key used to check if a value can be
%% replaced by an alias. It considers literals,
%% aliases, variables, tuples and cons recursively.
+%%
+%% We bail out after an arbitrary amount of nodes
+%% as very complex expressions may take forever to
+%% process otherwise.
+-define(DEPTH_LIMIT, 100).
nodes_to_key(Kind, Nodes) ->
- nodes_to_key(Nodes, [], Kind).
-
-nodes_to_key([#c_alias{var=Var}|T], Acc, Kind) ->
- nodes_to_key([Var|T], Acc, Kind);
-nodes_to_key([#c_var{name=Name}|T], Acc, Kind) ->
- nodes_to_key(T, [[var,Name]|Acc], Kind);
-nodes_to_key([Node|T], Acc0, Kind) ->
+ ntk_1(Nodes, [], Kind, ?DEPTH_LIMIT).
+
+ntk_1(_, _Acc, _Kind, 0) ->
+ error;
+ntk_1([#c_alias{var=Var}|T], Acc, Kind, N) ->
+ ntk_1([Var|T], Acc, Kind, N - 1);
+ntk_1([#c_var{name=Name}|T], Acc, Kind, N) ->
+ ntk_1(T, [[var,Name]|Acc], Kind, N - 1);
+ntk_1([Node|T], Acc0, Kind, N) ->
case cerl:is_data(Node) of
- false ->
- error;
true ->
- case nodes_to_key(cerl:data_es(Node), [], cerl:data_type(Node)) of
+ case ntk_1(cerl:data_es(Node), [], cerl:data_type(Node), N - 1) of
{ok,Key} ->
- nodes_to_key(T, [Key|Acc0], Kind);
+ ntk_1(T, [Key|Acc0], Kind, N - 1);
error ->
error
- end
+ end;
+ false ->
+ error
end;
-nodes_to_key([], Acc, Kind) ->
+ntk_1([], Acc, Kind, _N) ->
{ok,[Kind|Acc]}.
diff --git a/lib/compiler/src/sys_core_bsm.erl b/lib/compiler/src/sys_core_bsm.erl
index 685e807e65..97f0dab194 100644
--- a/lib/compiler/src/sys_core_bsm.erl
+++ b/lib/compiler/src/sys_core_bsm.erl
@@ -21,7 +21,7 @@
-module(sys_core_bsm).
--export([module/2,format_error/1]).
+-export([module/2]).
-include("core_parse.hrl").
@@ -41,11 +41,6 @@ function([{#c_var{name={F,Arity}}=Name,B0}|Fs]) ->
function([]) ->
[].
--type error() :: atom().
--spec format_error(error()) -> nonempty_string().
-
-format_error(_) -> error(badarg).
-
%%% Reorder bit syntax matching to faciliate optimization in further passes.
bsm_reorder(#c_case{arg=#c_var{}=V}=Case) ->
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index cdcf08c084..d987c6f3b5 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -99,10 +99,6 @@
t=#{} :: map(), %Types
in_guard=false}). %In guard or not.
--type type_info() :: cerl:cerl() | 'bool' | 'integer' | {'fun', pos_integer()}.
--type yes_no_maybe() :: 'yes' | 'no' | 'maybe'.
--type sub() :: #sub{}.
-
-spec module(cerl:c_module(), [compile:option()]) ->
{'ok', cerl:c_module(), [_]}.
@@ -165,63 +161,6 @@ guard(Expr, Sub) ->
?ASSERT(verify_scope(Expr, Sub)),
expr(Expr, value, Sub#sub{in_guard=true}).
-%% opt_guard_try(Expr) -> Expr.
-%%
-opt_guard_try(#c_seq{arg=Arg,body=Body0}=Seq) ->
- Body = opt_guard_try(Body0),
- WillFail = case Body of
- #c_call{module=#c_literal{val=erlang},
- name=#c_literal{val=error},
- args=[_]} ->
- true;
- #c_literal{val=false} ->
- true;
- _ ->
- false
- end,
- case Arg of
- #c_call{module=#c_literal{val=Mod},
- name=#c_literal{val=Name},
- args=Args} when WillFail ->
- %% We have sequence consisting of a call (evaluated
- %% for a possible exception and/or side effect only),
- %% followed by 'false' or a call to error/1.
- %% Since the sequence is inside a try block that will
- %% default to 'false' if any exception occurs, not
- %% evalutating the call will not change the behaviour
- %% provided that the call has no side effects.
- case erl_bifs:is_pure(Mod, Name, length(Args)) of
- false ->
- %% Not a pure BIF (meaning that this is not
- %% a guard and that we must keep the call).
- Seq#c_seq{body=Body};
- true ->
- %% The BIF has no side effects, so it can
- %% be safely removed.
- Body
- end;
- _ ->
- Seq#c_seq{body=Body}
- end;
-opt_guard_try(#c_case{clauses=Cs}=Term) ->
- Term#c_case{clauses=opt_guard_try_list(Cs)};
-opt_guard_try(#c_clause{body=B0}=Term) ->
- Term#c_clause{body=opt_guard_try(B0)};
-opt_guard_try(#c_let{vars=[],arg=#c_values{es=[]},body=B}) ->
- B;
-opt_guard_try(#c_let{arg=Arg,body=B0}=Term) ->
- case opt_guard_try(B0) of
- #c_literal{}=B ->
- opt_guard_try(#c_seq{arg=Arg,body=B});
- B ->
- Term#c_let{body=B}
- end;
-opt_guard_try(Term) -> Term.
-
-opt_guard_try_list([C|Cs]) ->
- [opt_guard_try(C)|opt_guard_try_list(Cs)];
-opt_guard_try_list([]) -> [].
-
%% expr(Expr, Sub) -> Expr.
%% expr(Expr, Context, Sub) -> Expr.
@@ -315,10 +254,10 @@ expr(#c_seq{arg=Arg0,body=B0}=Seq0, Ctxt, Sub) ->
false ->
%% Arg cannot be "values" here - only a single value
%% make sense here.
- case {Ctxt,is_safe_simple(Arg, Sub)} of
+ case {Ctxt,is_safe_simple(Arg)} of
{effect,true} -> B1;
{effect,false} ->
- case is_safe_simple(B1, Sub) of
+ case is_safe_simple(B1) of
true -> Arg;
false -> Seq0#c_seq{arg=Arg,body=B1}
end;
@@ -384,11 +323,11 @@ expr(#c_case{}=Case0, Ctxt, Sub) ->
%% according to the rules above).
%%
case opt_bool_case(Case0, Sub) of
- #c_case{arg=Arg0,clauses=Cs0}=Case1 ->
+ #c_case{anno=Anno,arg=Arg0,clauses=Cs0}=Case1 ->
Arg1 = body(Arg0, value, Sub),
LitExpr = cerl:is_literal(Arg1),
{Arg2,Cs1} = case_opt(Arg1, Cs0, Sub),
- Cs2 = clauses(Arg2, Cs1, Ctxt, Sub, LitExpr),
+ Cs2 = clauses(Arg2, Cs1, Ctxt, Sub, LitExpr, Anno),
Case = Case1#c_case{arg=Arg2,clauses=Cs2},
warn_no_clause_match(Case1, Case),
Expr = eval_case(Case, Sub),
@@ -396,11 +335,6 @@ expr(#c_case{}=Case0, Ctxt, Sub) ->
Other ->
expr(Other, Ctxt, Sub)
end;
-expr(#c_receive{clauses=Cs0,timeout=T0,action=A0}=Recv, Ctxt, Sub) ->
- Cs1 = clauses(#c_var{name='_'}, Cs0, Ctxt, Sub, false),
- T1 = expr(T0, value, Sub),
- A1 = body(A0, Ctxt, Sub),
- Recv#c_receive{clauses=Cs1,timeout=T1,action=A1};
expr(#c_apply{anno=Anno,op=Op0,args=As0}=Apply0, _, Sub) ->
Op1 = expr(Op0, value, Sub),
As1 = expr_list(As0, value, Sub),
@@ -442,7 +376,7 @@ expr(#c_catch{anno=Anno,body=B}, effect, Sub) ->
expr(#c_catch{body=B0}=Catch, _, Sub) ->
%% We can remove catch if the value is simple
B1 = body(B0, value, Sub),
- case is_safe_simple(B1, Sub) of
+ case is_safe_simple(B1) of
true -> B1;
false -> Catch#c_catch{body=B1}
end;
@@ -453,14 +387,11 @@ expr(#c_try{arg=E0,vars=[#c_var{name=X}],body=#c_var{name=X},
E1 = body(E0, value, Sub),
case will_fail(E1) of
false ->
- %% Remove any calls that are evaluated for effect only.
- E2 = opt_guard_try(E1),
-
%% We can remove try/catch if the expression is an
%% expression that cannot fail.
- case is_safe_bool_expr(E2, Sub) orelse is_safe_simple(E2, Sub) of
- true -> E2;
- false -> Try#c_try{arg=E2}
+ case is_safe_bool_expr(E1) orelse is_safe_simple(E1) of
+ true -> E1;
+ false -> Try#c_try{arg=E1}
end;
true ->
%% Expression will always fail.
@@ -472,27 +403,15 @@ expr(#c_try{anno=A,arg=E0,vars=Vs0,body=B0,evars=Evs0,handler=H0}=Try, _, Sub0)
E1 = body(E0, value, Sub0),
{Vs1,Sub1} = var_list(Vs0, Sub0),
B1 = body(B0, value, Sub1),
- case is_safe_simple(E1, Sub0) of
+ case is_safe_simple(E1) of
true ->
expr(#c_let{anno=A,vars=Vs1,arg=E1,body=B1}, value, Sub0);
false ->
{Evs1,Sub2} = var_list(Evs0, Sub0),
H1 = body(H0, value, Sub2),
- H2 = opt_try_handler(H1, lists:last(Evs1)),
- Try#c_try{arg=E1,vars=Vs1,body=B1,evars=Evs1,handler=H2}
+ Try#c_try{arg=E1,vars=Vs1,body=B1,evars=Evs1,handler=H1}
end.
-%% Attempts to convert old erlang:get_stacktrace/0 calls into the new
-%% three-argument catch, with possibility of further optimisations.
-opt_try_handler(#c_call{anno=A,module=#c_literal{val=erlang},name=#c_literal{val=get_stacktrace},args=[]}, Var) ->
- #c_primop{anno=A,name=#c_literal{val=build_stacktrace},args=[Var]};
-opt_try_handler(#c_case{clauses=Cs0} = Case, Var) ->
- Cs = [C#c_clause{body=opt_try_handler(B, Var)} || #c_clause{body=B} = C <- Cs0],
- Case#c_case{clauses=Cs};
-opt_try_handler(#c_let{arg=Arg} = Let, Var) ->
- Let#c_let{arg=opt_try_handler(Arg, Var)};
-opt_try_handler(X, _) -> X.
-
%% If a fun or its application is used as an argument, then it's unsafe to
%% handle it in effect context as the side-effects may rely on its return
%% value. The following is a minimal example of where it can go wrong:
@@ -545,10 +464,6 @@ ifes_1(FVar, #c_map_pair{key=Key,val=Val}, _Safe) ->
ifes_1(FVar, Key, false) andalso ifes_1(FVar, Val, false);
ifes_1(FVar, #c_primop{args=Args}, _Safe) ->
ifes_list(FVar, Args, false);
-ifes_1(FVar, #c_receive{timeout=Timeout,action=Action,clauses=Clauses}, Safe) ->
- ifes_1(FVar, Timeout, false) andalso
- ifes_1(FVar, Action, Safe) andalso
- ifes_list(FVar, Clauses, Safe);
ifes_1(FVar, #c_seq{arg=Arg,body=Body}, Safe) ->
%% Arg of a #c_seq{} has no effect so it's okay to use FVar there even if
%% Safe=false.
@@ -602,20 +517,20 @@ is_literal_fun(_) -> false.
%% Currently, we don't attempt to check binaries because they
%% are difficult to check.
-is_safe_simple(#c_var{}=Var, _) ->
+is_safe_simple(#c_var{}=Var) ->
not cerl:is_c_fname(Var);
-is_safe_simple(#c_cons{hd=H,tl=T}, Sub) ->
- is_safe_simple(H, Sub) andalso is_safe_simple(T, Sub);
-is_safe_simple(#c_tuple{es=Es}, Sub) -> is_safe_simple_list(Es, Sub);
-is_safe_simple(#c_literal{}, _) -> true;
+is_safe_simple(#c_cons{hd=H,tl=T}) ->
+ is_safe_simple(H) andalso is_safe_simple(T);
+is_safe_simple(#c_tuple{es=Es}) -> is_safe_simple_list(Es);
+is_safe_simple(#c_literal{}) -> true;
is_safe_simple(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=Name},
- args=Args}, Sub) when is_atom(Name) ->
+ args=Args}) when is_atom(Name) ->
NumArgs = length(Args),
case erl_internal:bool_op(Name, NumArgs) of
true ->
%% Boolean operators are safe if the arguments are boolean.
- all(fun(C) -> is_boolean_type(C, Sub) =:= yes end, Args);
+ all(fun is_bool_expr/1, Args);
false ->
%% We need a rather complicated test to ensure that
%% we only allow safe calls that are allowed in a guard.
@@ -624,9 +539,9 @@ is_safe_simple(#c_call{module=#c_literal{val=erlang},
(erl_internal:comp_op(Name, NumArgs) orelse
erl_internal:new_type_test(Name, NumArgs))
end;
-is_safe_simple(_, _) -> false.
+is_safe_simple(_) -> false.
-is_safe_simple_list(Es, Sub) -> all(fun(E) -> is_safe_simple(E, Sub) end, Es).
+is_safe_simple_list(Es) -> all(fun(E) -> is_safe_simple(E) end, Es).
%% will_fail(Expr) -> true|false.
%% Determine whether the expression will fail with an exception.
@@ -696,15 +611,7 @@ eval_binary(#c_binary{anno=Anno,segments=Ss}=Bin) ->
eval_binary_1([#c_bitstr{val=#c_literal{val=Val},size=#c_literal{val=Sz},
unit=#c_literal{val=Unit},type=#c_literal{val=Type},
flags=#c_literal{val=Flags}}|Ss], Acc0) ->
- Endian = case member(big, Flags) of
- true ->
- big;
- false ->
- case member(little, Flags) of
- true -> little;
- false -> throw(impossible) %Native endian.
- end
- end,
+ Endian = bs_endian(Flags),
%% Make sure that the size is reasonable.
case Type of
@@ -738,10 +645,14 @@ eval_binary_1([#c_bitstr{val=#c_literal{val=Val},size=#c_literal{val=Sz},
end;
float when is_float(Val) ->
%% Bad float size.
- case Sz*Unit of
+ try Sz*Unit of
32 -> ok;
64 -> ok;
- _ -> throw(impossible)
+ _ ->
+ throw({badarg,bad_float_size})
+ catch
+ error:_ ->
+ throw({badarg,bad_float_size})
end;
utf8 -> ok;
utf16 -> ok;
@@ -750,6 +661,11 @@ eval_binary_1([#c_bitstr{val=#c_literal{val=Val},size=#c_literal{val=Sz},
throw(impossible)
end,
+ case Endian =:= native andalso Type =/= binary of
+ true -> throw(impossible);
+ false -> ok
+ end,
+
%% Evaluate the field.
try eval_binary_2(Acc0, Val, Sz, Unit, Type, Endian) of
Acc -> eval_binary_1(Ss, Acc)
@@ -813,6 +729,11 @@ eval_binary_2(Acc, Val, all, Unit, binary, _) ->
eval_binary_2(Acc, Val, Size, Unit, binary, _) ->
<<Acc/bitstring,Val:(Size*Unit)/bitstring>>.
+bs_endian([big=E|_]) -> E;
+bs_endian([little=E|_]) -> E;
+bs_endian([native=E|_]) -> E;
+bs_endian([_|Fs]) -> bs_endian(Fs).
+
%% Count the number of bits approximately needed to store Int.
%% (We don't need an exact result for this purpose.)
@@ -853,7 +774,7 @@ useless_call(_, _) -> no.
%% Anything that will not have any effect will be thrown away.
make_effect_seq([H|T], Sub) ->
- case is_safe_simple(H, Sub) of
+ case is_safe_simple(H) of
true -> make_effect_seq(T, Sub);
false -> #c_seq{arg=H,body=make_effect_seq(T, Sub)}
end;
@@ -880,25 +801,45 @@ fold_apply(Apply, _, _) -> Apply.
%% Handling remote calls. The module/name fields have been processed.
-call(#c_call{args=As}=Call, #c_literal{val=M}=M0, #c_literal{val=N}=N0, Sub) ->
- case get(no_inline_list_funcs) of
- true ->
- call_1(Call, M0, N0, As, Sub);
- false ->
- case sys_core_fold_lists:call(Call, M, N, As) of
- none ->
- call_1(Call, M0, N0, As, Sub);
- Core ->
- expr(Core, Sub)
- end
-
- end;
-call(#c_call{args=As}=Call, M, N, Sub) ->
- call_1(Call, M, N, As, Sub).
-
-call_1(Call, M, N, As0, Sub) ->
+call(#c_call{args=As0}=Call0, #c_literal{val=M}=M0, #c_literal{val=N}=N0, Sub) ->
As1 = expr_list(As0, value, Sub),
- fold_call(Call#c_call{args=As1}, M, N, As1, Sub).
+ case simplify_call(Call0, M, N, As1) of
+ #c_literal{}=Lit ->
+ Lit;
+ #c_call{args=As}=Call ->
+ case get(no_inline_list_funcs) of
+ true ->
+ fold_call(Call, M0, N0, As, Sub);
+ false ->
+ case sys_core_fold_lists:call(Call, M, N, As) of
+ none -> fold_call(Call, M0, N0, As, Sub);
+ Core -> expr(Core, Sub)
+ end
+ end
+ end;
+call(#c_call{args=As0}=Call, M, N, Sub) ->
+ As = expr_list(As0, value, Sub),
+ fold_call(Call#c_call{args=As}, M, N, As, Sub).
+
+%% Rewrite certain known functions to BIFs, improving performance
+%% slightly at the cost of making tracing and stack traces incorrect.
+simplify_call(Call, maps, get, [Key, Map]) ->
+ rewrite_call(Call, erlang, map_get, [Key, Map]);
+simplify_call(Call, maps, is_key, [Key, Map]) ->
+ rewrite_call(Call, erlang, is_map_key, [Key, Map]);
+simplify_call(_Call, maps, new, []) ->
+ #c_literal{val=#{}};
+simplify_call(Call, maps, size, [Map]) ->
+ rewrite_call(Call, erlang, map_size, [Map]);
+simplify_call(Call, _, _, Args) ->
+ Call#c_call{args=Args}.
+
+%% rewrite_call(Call0, Mod, Func, Args, Sub) -> Call
+%% Rewrites a call to the given MFA.
+rewrite_call(Call, Mod, Func, Args) ->
+ ModLit = #c_literal{val=Mod},
+ FuncLit = #c_literal{val=Func},
+ Call#c_call{module=ModLit,name=FuncLit,args=Args}.
%% fold_call(Call, Mod, Name, Args, Sub) -> Expr.
%% Try to safely evaluate the call. Just try to evaluate arguments,
@@ -959,138 +900,14 @@ fold_lit_args(Call, Module, Name, Args0) ->
%% Attempt to evaluate some pure BIF calls with one or more
%% non-literals arguments.
%%
-fold_non_lit_args(Call, erlang, is_boolean, [Arg], Sub) ->
- eval_is_boolean(Call, Arg, Sub);
fold_non_lit_args(Call, erlang, length, [Arg], _) ->
eval_length(Call, Arg);
fold_non_lit_args(Call, erlang, '++', [Arg1,Arg2], _) ->
eval_append(Call, Arg1, Arg2);
fold_non_lit_args(Call, lists, append, [Arg1,Arg2], _) ->
eval_append(Call, Arg1, Arg2);
-fold_non_lit_args(Call, erlang, is_function, [Arg1], Sub) ->
- eval_is_function_1(Call, Arg1, Sub);
-fold_non_lit_args(Call, erlang, is_function, [Arg1,Arg2], Sub) ->
- eval_is_function_2(Call, Arg1, Arg2, Sub);
-fold_non_lit_args(Call, erlang, N, Args, Sub) ->
- NumArgs = length(Args),
- case erl_internal:comp_op(N, NumArgs) of
- true ->
- eval_rel_op(Call, N, Args, Sub);
- false ->
- case erl_internal:bool_op(N, NumArgs) of
- true ->
- eval_bool_op(Call, N, Args, Sub);
- false ->
- Call
- end
- end;
fold_non_lit_args(Call, _, _, _, _) -> Call.
-eval_is_function_1(Call, Arg1, Sub) ->
- case get_type(Arg1, Sub) of
- none -> Call;
- {'fun',_} -> #c_literal{anno=cerl:get_ann(Call),val=true};
- _ -> #c_literal{anno=cerl:get_ann(Call),val=false}
- end.
-
-eval_is_function_2(Call, Arg1, #c_literal{val=Arity}, Sub)
- when is_integer(Arity), Arity > 0 ->
- case get_type(Arg1, Sub) of
- none -> Call;
- {'fun',Arity} -> #c_literal{anno=cerl:get_ann(Call),val=true};
- _ -> #c_literal{anno=cerl:get_ann(Call),val=false}
- end;
-eval_is_function_2(Call, _Arg1, _Arg2, _Sub) -> Call.
-
-%% Evaluate a relational operation using type information.
-eval_rel_op(Call, Op, [#c_var{name=V},#c_var{name=V}], _) ->
- Bool = erlang:Op(same, same),
- #c_literal{anno=cerl:get_ann(Call),val=Bool};
-eval_rel_op(Call, '=:=', [Term,#c_literal{val=true}], Sub) ->
- %% BoolVar =:= true ==> BoolVar
- case is_boolean_type(Term, Sub) of
- yes -> Term;
- maybe -> Call;
- no -> #c_literal{val=false}
- end;
-eval_rel_op(Call, '==', Ops, Sub) ->
- case is_exact_eq_ok(Ops, Sub) of
- true ->
- Name = #c_literal{anno=cerl:get_ann(Call),val='=:='},
- Call#c_call{name=Name};
- false ->
- Call
- end;
-eval_rel_op(Call, '/=', Ops, Sub) ->
- case is_exact_eq_ok(Ops, Sub) of
- true ->
- Name = #c_literal{anno=cerl:get_ann(Call),val='=/='},
- Call#c_call{name=Name};
- false ->
- Call
- end;
-eval_rel_op(Call, _, _, _) -> Call.
-
-is_exact_eq_ok([A,B]=L, Sub) ->
- case is_int_type(A, Sub) =:= yes andalso is_int_type(B, Sub) =:= yes of
- true -> true;
- false -> is_exact_eq_ok_1(L)
- end.
-
-is_exact_eq_ok_1([#c_literal{val=Lit}|_]) ->
- is_non_numeric(Lit);
-is_exact_eq_ok_1([_|T]) ->
- is_exact_eq_ok_1(T);
-is_exact_eq_ok_1([]) -> false.
-
-is_non_numeric([H|T]) ->
- is_non_numeric(H) andalso is_non_numeric(T);
-is_non_numeric(Tuple) when is_tuple(Tuple) ->
- is_non_numeric_tuple(Tuple, tuple_size(Tuple));
-is_non_numeric(Map) when is_map(Map) ->
- %% Note that 17.x and 18.x compare keys in different ways.
- %% Be very conservative -- require that both keys and values
- %% are non-numeric.
- is_non_numeric(maps:to_list(Map));
-is_non_numeric(Num) when is_number(Num) ->
- false;
-is_non_numeric(_) -> true.
-
-is_non_numeric_tuple(Tuple, El) when El >= 1 ->
- is_non_numeric(element(El, Tuple)) andalso
- is_non_numeric_tuple(Tuple, El-1);
-is_non_numeric_tuple(_Tuple, 0) -> true.
-
-%% Evaluate a bool op using type information. We KNOW that
-%% there must be at least one non-literal argument (i.e.
-%% there is no need to handle the case that all argments
-%% are literal).
-
-eval_bool_op(Call, 'and', [#c_literal{val=true},Term], Sub) ->
- eval_bool_op_1(Call, Term, Term, Sub);
-eval_bool_op(Call, 'and', [Term,#c_literal{val=true}], Sub) ->
- eval_bool_op_1(Call, Term, Term, Sub);
-eval_bool_op(Call, 'and', [#c_literal{val=false}=Res,Term], Sub) ->
- eval_bool_op_1(Call, Res, Term, Sub);
-eval_bool_op(Call, 'and', [Term,#c_literal{val=false}=Res], Sub) ->
- eval_bool_op_1(Call, Res, Term, Sub);
-eval_bool_op(Call, _, _, _) -> Call.
-
-eval_bool_op_1(Call, Res, Term, Sub) ->
- case is_boolean_type(Term, Sub) of
- yes -> Res;
- no -> eval_failure(Call, badarg);
- maybe -> Call
- end.
-
-%% Evaluate is_boolean/1 using type information.
-eval_is_boolean(Call, Term, Sub) ->
- case is_boolean_type(Term, Sub) of
- no -> #c_literal{val=false};
- yes -> #c_literal{val=true};
- maybe -> Call
- end.
-
%% eval_length(Call, List) -> Val.
%% Evaluates the length for the prefix of List which has a known
%% shape.
@@ -1199,10 +1016,6 @@ clause_1(#c_clause{guard=G0,body=B0}=Cl, Ps1, Cexpr, Ctxt, Sub1) ->
%% No need for substitution tricks when the guard
%% does not contain any variables.
Sub1;
- {#c_var{name='_'},_,_} ->
- %% In a 'receive', Cexpr is the variable '_', which represents the
- %% message being matched. We must NOT do any extra substiutions.
- Sub1;
{#c_var{},[#c_var{}=Var],_} ->
%% The idea here is to optimize expressions such as
%%
@@ -1329,20 +1142,27 @@ map_pair_pattern(#c_map_pair{op=#c_literal{val=exact},key=K0,val=V0}=Pair,{Isub,
{V,Osub} = pattern(V0,Isub,Osub0),
{Pair#c_map_pair{key=K,val=V},{Isub,Osub}}.
-bin_pattern_list(Ps0, Isub, Osub0) ->
- {Ps,{_,Osub}} = mapfoldl(fun bin_pattern/2, {Isub,Osub0}, Ps0),
- {Ps,Osub}.
-
-bin_pattern(#c_bitstr{val=E0,size=Size0}=Pat0, {Isub0,Osub0}) ->
- Size1 = expr(Size0, Isub0),
- {E1,Osub} = pattern(E0, Isub0, Osub0),
- Isub = case E0 of
- #c_var{} -> sub_set_var(E0, E1, Isub0);
- _ -> Isub0
- end,
- Pat = Pat0#c_bitstr{val=E1,size=Size1},
+bin_pattern_list(Ps, Isub, Osub0) ->
+ mapfoldl(fun(P, Osub) ->
+ bin_pattern(P, Isub, Osub)
+ end, Osub0, Ps).
+
+bin_pattern(#c_bitstr{val=E0,size=Size0}=Pat0, Isub, Osub0) ->
+ Size2 = case {Size0,expr(Size0, Isub)} of
+ {#c_var{},#c_literal{val=all}} ->
+ %% The size `all` is used for the size of the final binary
+ %% segment in a pattern. Using `all` explicitly is not allowed,
+ %% so we convert it to an obvious invalid size. We also need
+ %% to add an annotation to get the correct wording of the warning
+ %% that will soon be issued.
+ #c_literal{anno=[size_was_all],val=bad_size};
+ {_,Size1} ->
+ Size1
+ end,
+ {E1,Osub} = pattern(E0, Isub, Osub0),
+ Pat = Pat0#c_bitstr{val=E1,size=Size2},
bin_pat_warn(Pat),
- {Pat,{Isub,Osub}}.
+ {Pat,Osub}.
pattern_list(Ps, Sub) -> pattern_list(Ps, Sub, Sub).
@@ -1365,7 +1185,7 @@ var_list(Vs, Sub0) ->
bin_pat_warn(#c_bitstr{type=#c_literal{val=Type},
val=Val0,
- size=#c_literal{val=Sz},
+ size=#c_literal{anno=SizeAnno,val=Sz},
unit=#c_literal{val=Unit},
flags=Fl}=Pat) ->
case {Type,Sz} of
@@ -1375,7 +1195,12 @@ bin_pat_warn(#c_bitstr{type=#c_literal{val=Type},
{utf16,undefined} -> ok;
{utf32,undefined} -> ok;
{_,_} ->
- add_warning(Pat, {nomatch_bit_syntax_size,Sz}),
+ case member(size_was_all, SizeAnno) of
+ true ->
+ add_warning(Pat, {nomatch_bit_syntax_size,all});
+ false ->
+ add_warning(Pat, {nomatch_bit_syntax_size,Sz})
+ end,
throw(nomatch)
end,
case {Type,Val0} of
@@ -1562,11 +1387,11 @@ warn_no_clause_match(CaseOrig, CaseOpt) ->
ok
end.
-%% clauses(E, [Clause], TopLevel, Context, Sub) -> [Clause].
+%% clauses(E, [Clause], TopLevel, Context, Sub, Anno) -> [Clause].
%% Trim the clauses by removing all clauses AFTER the first one which
%% is guaranteed to match. Also remove all trivially false clauses.
-clauses(E, [C0|Cs], Ctxt, Sub, LitExpr) ->
+clauses(E, [C0|Cs], Ctxt, Sub, LitExpr, Anno) ->
#c_clause{pats=Ps,guard=G} = C1 = clause(C0, E, Ctxt, Sub),
%%ok = io:fwrite("~w: ~p~n", [?LINE,{E,Ps}]),
case {will_match(E, Ps),will_succeed(G)} of
@@ -1574,7 +1399,7 @@ clauses(E, [C0|Cs], Ctxt, Sub, LitExpr) ->
case LitExpr of
false ->
Line = get_line(cerl:get_ann(C1)),
- shadow_warning(Cs, Line);
+ shadow_warning(Cs, Line, Anno);
true ->
%% If the case expression is a literal,
%% it is probably OK that some clauses don't match.
@@ -1584,19 +1409,24 @@ clauses(E, [C0|Cs], Ctxt, Sub, LitExpr) ->
[C1]; %Skip the rest
{_Mat,no} -> %Guard fails.
add_warning(C1, nomatch_guard),
- clauses(E, Cs, Ctxt, Sub, LitExpr); %Skip this clause
+ clauses(E, Cs, Ctxt, Sub, LitExpr, Anno); %Skip this clause
{_Mat,_Suc} ->
- [C1|clauses(E, Cs, Ctxt, Sub, LitExpr)]
+ [C1|clauses(E, Cs, Ctxt, Sub, LitExpr, Anno)]
end;
-clauses(_, [], _, _, _) -> [].
+clauses(_, [], _, _, _, _) -> [].
-shadow_warning([C|Cs], none) ->
+shadow_warning([C|Cs], none, Anno) ->
add_warning(C, nomatch_shadow),
- shadow_warning(Cs, none);
-shadow_warning([C|Cs], Line) ->
- add_warning(C, {nomatch_shadow, Line}),
- shadow_warning(Cs, Line);
-shadow_warning([], _) -> ok.
+ shadow_warning(Cs, none, Anno);
+shadow_warning([C|Cs], Line, Anno) ->
+ case keyfind(function, 1, Anno) of
+ {function, {Name, Arity}} ->
+ add_warning(C, {nomatch_shadow, Line, {Name, Arity}});
+ _ ->
+ add_warning(C, {nomatch_shadow, Line})
+ end,
+ shadow_warning(Cs, Line, Anno);
+shadow_warning([], _, _) -> ok.
%% will_succeed(Guard) -> yes | maybe | no.
%% Test if we know whether a guard will succeed/fail or just don't
@@ -1623,16 +1453,15 @@ will_match_1({true,_}) -> yes.
%%
%% In bodies, do various optimizations to case statements that have
%% boolean case expressions. We don't do the optimizations in guards,
-%% because they would thwart the optimization in v3_kernel.
+%% because they would thwart the optimization in beam_ssa_bool.
%%
-%% We start with some simple optimizations and normalization
+%% We start with some simple optimizations and normalizations
%% to facilitate later optimizations.
%%
-%% If the case expression can only return a boolean
-%% (or fail), we can remove any clause that cannot
-%% possibly match 'true' or 'false'. Also, any clause
-%% following both 'true' and 'false' clause can
-%% be removed. If successful, we will end up like this:
+%% If the case expression can only return a boolean we can remove any
+%% clause that cannot possibly match 'true' or 'false'. Also, any
+%% clause following both 'true' and 'false' clause can be removed. If
+%% successful, we will end up like this:
%%
%% case BoolExpr of case BoolExpr of
%% true -> false ->
@@ -1669,7 +1498,7 @@ opt_bool_clauses(Cs, true, true) ->
%% Any remaining clauses cannot possibly match.
case Cs of
[_|_] ->
- shadow_warning(Cs, none),
+ shadow_warning(Cs, none, []),
[];
[] ->
[]
@@ -1771,7 +1600,7 @@ opt_bool_not_invert(#c_clause{pats=[#c_literal{val=Bool}]}=C) ->
opt_bool_case_redundant(#c_case{arg=Arg,clauses=Cs}=Case) ->
case all(fun opt_bool_case_redundant_1/1, Cs) of
true -> Arg;
- false -> opt_bool_case_guard(Case)
+ false -> Case
end.
opt_bool_case_redundant_1(#c_clause{pats=[#c_literal{val=B}],
@@ -1779,45 +1608,6 @@ opt_bool_case_redundant_1(#c_clause{pats=[#c_literal{val=B}],
true;
opt_bool_case_redundant_1(_) -> false.
-%% opt_bool_case_guard(Case) -> Case'.
-%% Move a boolean case expression into the guard if we are sure that
-%% it cannot fail.
-%%
-%% case SafeBoolExpr of case <> of
-%% true -> TrueClause; ==> <> when SafeBoolExpr -> TrueClause;
-%% false -> FalseClause <> when true -> FalseClause
-%% end. end.
-%%
-%% Generally, evaluting a boolean expression in a guard should
-%% be faster than evaulating it in the body.
-%%
-opt_bool_case_guard(#c_case{arg=#c_literal{}}=Case) ->
- %% It is not necessary to move a literal case expression into the
- %% guard, because it will be handled quite well in other
- %% optimizations, and moving the literal into the guard will
- %% cause some extra warnings, for instance for this code
- %%
- %% case true of
- %% true -> ...;
- %% false -> ...
- %% end.
- %%
- Case;
-opt_bool_case_guard(#c_case{arg=Arg,clauses=Cs0}=Case) ->
- case is_safe_bool_expr(Arg, sub_new()) of
- false ->
- Case;
- true ->
- Cs = opt_bool_case_guard(Arg, Cs0),
- Case#c_case{arg=#c_values{anno=cerl:get_ann(Arg),es=[]},
- clauses=Cs}
- end.
-
-opt_bool_case_guard(Arg, [#c_clause{pats=[#c_literal{val=true}]}=Tc,Fc]) ->
- [Tc#c_clause{pats=[],guard=Arg},Fc#c_clause{pats=[]}];
-opt_bool_case_guard(Arg, [#c_clause{pats=[#c_literal{val=false}]}=Fc,Tc]) ->
- [Tc#c_clause{pats=[],guard=Arg},Fc#c_clause{pats=[]}].
-
%% eval_case(Case) -> #c_case{} | #c_let{}.
%% If possible, evaluate a case at compile time. We know that the
%% last clause is guaranteed to match so if there is only one clause
@@ -1945,7 +1735,7 @@ case_opt_arg(E0, Sub, Cs, LitExpr) ->
{error,Cs};
false ->
%% If possible, expand this variable to a previously
- %% matched term.
+ %% constructed tuple
E = case_expand_var(E0, Sub),
case_opt_arg_1(E, Cs, LitExpr)
end
@@ -2004,13 +1794,8 @@ case_opt_compiler_generated(Core) ->
case_expand_var(E, #sub{t=Tdb}) ->
Key = cerl:var_name(E),
case Tdb of
- #{Key:=T} ->
- case cerl:is_c_tuple(T) of
- false -> E;
- true -> T
- end;
- _ ->
- E
+ #{Key:=T} -> T;
+ _ -> E
end.
%% case_opt_nomatch(E, Clauses, LitExpr) -> Clauses'
@@ -2299,118 +2084,86 @@ is_simple_case_arg(#c_apply{}) -> true;
is_simple_case_arg(_) -> false.
%% is_bool_expr(Core) -> true|false
-%% Check whether the Core expression is guaranteed to return
-%% a boolean IF IT RETURNS AT ALL.
+%% Check whether the Core expression is guaranteed to
+%% return a boolean.
%%
-is_bool_expr(Core) ->
- is_bool_expr(Core, sub_new()).
-%% is_bool_expr(Core, Sub) -> true|false
-%% Check whether the Core expression is guaranteed to return
-%% a boolean IF IT RETURNS AT ALL. Uses type information
-%% to be able to identify more expressions as booleans.
-%%
is_bool_expr(#c_call{module=#c_literal{val=erlang},
- name=#c_literal{val=Name},args=Args}=Call, _) ->
+ name=#c_literal{val=Name},args=Args}) ->
NumArgs = length(Args),
erl_internal:comp_op(Name, NumArgs) orelse
erl_internal:new_type_test(Name, NumArgs) orelse
- erl_internal:bool_op(Name, NumArgs) orelse
- will_fail(Call);
+ erl_internal:bool_op(Name, NumArgs);
is_bool_expr(#c_try{arg=E,vars=[#c_var{name=X}],body=#c_var{name=X},
- handler=#c_literal{val=false}}, Sub) ->
- is_bool_expr(E, Sub);
-is_bool_expr(#c_case{clauses=Cs}, Sub) ->
- is_bool_expr_list(Cs, Sub);
-is_bool_expr(#c_clause{body=B}, Sub) ->
- is_bool_expr(B, Sub);
-is_bool_expr(#c_let{vars=[V],arg=Arg,body=B}, Sub0) ->
- Sub = case is_bool_expr(Arg, Sub0) of
- true -> update_types(V, [bool], Sub0);
- false -> Sub0
- end,
- is_bool_expr(B, Sub);
-is_bool_expr(#c_let{body=B}, Sub) ->
- %% Binding of multiple variables.
- is_bool_expr(B, Sub);
-is_bool_expr(C, Sub) ->
- is_boolean_type(C, Sub) =:= yes.
-
-is_bool_expr_list([C|Cs], Sub) ->
- is_bool_expr(C, Sub) andalso is_bool_expr_list(Cs, Sub);
-is_bool_expr_list([], _) -> true.
+ handler=#c_literal{val=false}}) ->
+ is_bool_expr(E);
+is_bool_expr(#c_case{clauses=Cs}) ->
+ is_bool_expr_list(Cs);
+is_bool_expr(#c_clause{body=B}) ->
+ is_bool_expr(B);
+is_bool_expr(#c_let{body=B}) ->
+ is_bool_expr(B);
+is_bool_expr(#c_literal{val=Val}) ->
+ is_boolean(Val);
+is_bool_expr(_) -> false.
+
+is_bool_expr_list([C|Cs]) ->
+ is_bool_expr(C) andalso is_bool_expr_list(Cs);
+is_bool_expr_list([]) -> true.
%% is_safe_bool_expr(Core) -> true|false
%% Check whether the Core expression ALWAYS returns a boolean
-%% (i.e. it cannot fail). Also make sure that the expression
-%% is suitable for a guard (no calls to non-guard BIFs, local
-%% functions, or is_record/2).
+%% (i.e. it cannot fail).
%%
-is_safe_bool_expr(Core, Sub) ->
- is_safe_bool_expr_1(Core, Sub, cerl_sets:new()).
+is_safe_bool_expr(Core) ->
+ is_safe_bool_expr_1(Core, cerl_sets:new()).
is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
- name=#c_literal{val=is_record},
- args=[A,#c_literal{val=Tag},#c_literal{val=Size}]},
- Sub, _BoolVars) when is_atom(Tag), is_integer(Size) ->
- is_safe_simple(A, Sub);
-is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
- name=#c_literal{val=is_record}},
- _Sub, _BoolVars) ->
- %% The is_record/2 BIF is NOT allowed in guards.
- %% The is_record/3 BIF where its second argument is not an atom or its third
- %% is not an integer is NOT allowed in guards.
- %%
- %% NOTE: Calls like is_record(Expr, LiteralTag), where LiteralTag
- %% is a literal atom referring to a defined record, have already
- %% been rewritten to is_record(Expr, LiteralTag, TupleSize).
- false;
-is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[A,#c_literal{val=Arity}]},
- Sub, _BoolVars) when is_integer(Arity), Arity >= 0 ->
- is_safe_simple(A, Sub);
+ _BoolVars) when is_integer(Arity), Arity >= 0 ->
+ is_safe_simple(A);
is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function}},
- _Sub, _BoolVars) ->
+ _BoolVars) ->
false;
is_safe_bool_expr_1(#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=Name},args=Args},
- Sub, BoolVars) ->
+ BoolVars) ->
NumArgs = length(Args),
case (erl_internal:comp_op(Name, NumArgs) orelse
erl_internal:new_type_test(Name, NumArgs)) andalso
- is_safe_simple_list(Args, Sub) of
+ is_safe_simple_list(Args) of
true ->
true;
false ->
%% Boolean operators are safe if all arguments are boolean.
erl_internal:bool_op(Name, NumArgs) andalso
- is_safe_bool_expr_list(Args, Sub, BoolVars)
+ is_safe_bool_expr_list(Args, BoolVars)
end;
-is_safe_bool_expr_1(#c_let{vars=Vars,arg=Arg,body=B}, Sub, BoolVars) ->
- case is_safe_simple(Arg, Sub) of
+is_safe_bool_expr_1(#c_let{vars=Vars,arg=Arg,body=B}, BoolVars) ->
+ case is_safe_simple(Arg) of
true ->
- case {is_safe_bool_expr_1(Arg, Sub, BoolVars),Vars} of
+ case {is_safe_bool_expr_1(Arg, BoolVars),Vars} of
{true,[#c_var{name=V}]} ->
- is_safe_bool_expr_1(B, Sub, cerl_sets:add_element(V, BoolVars));
+ is_safe_bool_expr_1(B, cerl_sets:add_element(V, BoolVars));
{false,_} ->
- is_safe_bool_expr_1(B, Sub, BoolVars)
+ is_safe_bool_expr_1(B, BoolVars)
end;
false -> false
end;
-is_safe_bool_expr_1(#c_literal{val=Val}, _Sub, _) ->
+is_safe_bool_expr_1(#c_literal{val=Val}, _BoolVars) ->
is_boolean(Val);
-is_safe_bool_expr_1(#c_var{name=V}, _Sub, BoolVars) ->
+is_safe_bool_expr_1(#c_var{name=V}, BoolVars) ->
cerl_sets:is_element(V, BoolVars);
-is_safe_bool_expr_1(_, _, _) -> false.
+is_safe_bool_expr_1(_, _) -> false.
-is_safe_bool_expr_list([C|Cs], Sub, BoolVars) ->
- case is_safe_bool_expr_1(C, Sub, BoolVars) of
- true -> is_safe_bool_expr_list(Cs, Sub, BoolVars);
+is_safe_bool_expr_list([C|Cs], BoolVars) ->
+ case is_safe_bool_expr_1(C, BoolVars) of
+ true -> is_safe_bool_expr_list(Cs, BoolVars);
false -> false
end;
-is_safe_bool_expr_list([], _, _) -> true.
+is_safe_bool_expr_list([], _) -> true.
%% simplify_let(Let, Sub) -> Expr | impossible
%% If the argument part of an let contains a complex expression, such
@@ -2569,13 +2322,16 @@ opt_build_stacktrace(#c_let{vars=[#c_var{name=Cooked}],
true ->
Let
end;
- #c_case{arg=Arg,clauses=Cs0} ->
- case core_lib:is_var_used(Cooked, Arg) orelse
- is_used_in_any_guard(Cooked, Cs0) of
+ #c_case{clauses=Cs0} ->
+ NilBody = #c_literal{val=[]},
+ Cs1 = [C#c_clause{body=NilBody} || C <- Cs0],
+ Case = Body#c_case{clauses=Cs1},
+ case core_lib:is_var_used(Cooked, Case) of
false ->
- %% The built stacktrace is not used in the argument,
- %% so we can sink the building of the stacktrace into
- %% each arm of the case.
+ %% The built stacktrace is not used in the case
+ %% argument or in the head of any clause. Thus
+ %% it is safe sink the building of the stacktrace
+ %% into each arm of the case.
Cs = [begin
B = opt_build_stacktrace(Let#c_let{body=B0}),
C#c_clause{body=B}
@@ -2590,11 +2346,6 @@ opt_build_stacktrace(#c_let{vars=[#c_var{name=Cooked}],
opt_build_stacktrace(Expr) ->
Expr.
-is_used_in_any_guard(V, Cs) ->
- any(fun(#c_clause{guard=G}) ->
- core_lib:is_var_used(V, G)
- end, Cs).
-
%% opt_case_in_let(Let) -> Let'
%% Try to avoid building tuples that are immediately matched.
%% A common pattern is:
@@ -2705,19 +2456,6 @@ delay_build_expr_1(#c_case{clauses=Cs0}=Case, TypeSig) ->
delay_build_expr_1(#c_let{body=B0}=Let, TypeSig) ->
B = delay_build_expr(B0, TypeSig),
Let#c_let{body=B};
-delay_build_expr_1(#c_receive{clauses=Cs0,
- timeout=Timeout,
- action=A0}=Rec, TypeSig) ->
- Cs = delay_build_cs(Cs0, TypeSig),
- A = case {Timeout,A0} of
- {#c_literal{val=infinity},#c_literal{}} ->
- {_Type,Arity} = TypeSig,
- Es = lists:duplicate(Arity, A0),
- core_lib:make_values(Es);
- _ ->
- delay_build_expr(A0, TypeSig)
- end,
- Rec#c_receive{clauses=Cs,action=A};
delay_build_expr_1(#c_seq{body=B0}=Seq, TypeSig) ->
B = delay_build_expr(B0, TypeSig),
Seq#c_seq{body=B};
@@ -2770,32 +2508,24 @@ opt_simple_let_2(Let0, Vs0, Arg0, Body, PrevBody, Sub) ->
{[],#c_values{es=[]},_} ->
%% No variables left.
Body;
- {[#c_var{name=V}=Var|Vars]=Vars0,Arg1,Body} ->
+ {[#c_var{name=V}=Var]=Vars0,Arg1,Body} ->
case core_lib:is_var_used(V, Body) of
- false when Vars =:= [] ->
+ false ->
%% If the variable is not used in the body, we can
%% rewrite the let to a sequence:
%% let <Var> = Arg in BodyWithoutVar ==>
%% seq Arg BodyWithoutVar
Arg = maybe_suppress_warnings(Arg1, Var, PrevBody),
#c_seq{arg=Arg,body=Body};
- false ->
- %% There are multiple values returned by the argument
- %% and the first value is not used (this is a 'case'
- %% with exported variables, but the return value is
- %% ignored). We can remove the first variable and the
- %% the first value returned from the 'let' argument.
- Arg2 = remove_first_value(Arg1, Sub),
- Let1 = Let0#c_let{vars=Vars,arg=Arg2,body=Body},
- post_opt_let(Let1, Sub);
true ->
Let1 = Let0#c_let{vars=Vars0,arg=Arg1,body=Body},
post_opt_let(Let1, Sub)
end;
- {[],Arg,Body} ->
+ {_,_,_} ->
%% The argument for a sequence must be a single value (not
%% #c_values{}). Therefore, we must keep the let.
- post_opt_let(#c_let{vars=[],arg=Arg,body=Body}, Sub)
+ Let1 = Let0#c_let{vars=Vs0,arg=Arg0,body=Body},
+ post_opt_let(Let1, Sub)
end.
%% post_opt_let(Let, Sub)
@@ -2808,39 +2538,6 @@ post_opt_let(Let0, Sub) ->
Let1 = opt_bool_case_in_let(Let0, Sub),
opt_build_stacktrace(Let1).
-
-%% remove_first_value(Core0, Sub) -> Core.
-%% Core0 is an expression that returns at least two values.
-%% Remove the first value returned from Core0.
-
-remove_first_value(#c_values{es=[V|Vs]}, Sub) ->
- Values = core_lib:make_values(Vs),
- case is_safe_simple(V, Sub) of
- false ->
- #c_seq{arg=V,body=Values};
- true ->
- Values
- end;
-remove_first_value(#c_case{clauses=Cs0}=Core, Sub) ->
- Cs = remove_first_value_cs(Cs0, Sub),
- Core#c_case{clauses=Cs};
-remove_first_value(#c_receive{clauses=Cs0,action=Act0}=Core, Sub) ->
- Cs = remove_first_value_cs(Cs0, Sub),
- Act = remove_first_value(Act0, Sub),
- Core#c_receive{clauses=Cs,action=Act};
-remove_first_value(#c_let{body=B}=Core, Sub) ->
- Core#c_let{body=remove_first_value(B, Sub)};
-remove_first_value(#c_seq{body=B}=Core, Sub) ->
- Core#c_seq{body=remove_first_value(B, Sub)};
-remove_first_value(#c_primop{}=Core, _Sub) ->
- Core;
-remove_first_value(#c_call{}=Core, _Sub) ->
- Core.
-
-remove_first_value_cs(Cs, Sub) ->
- [C#c_clause{body=remove_first_value(B, Sub)} ||
- #c_clause{body=B}=C <- Cs].
-
%% maybe_suppress_warnings(Arg, #c_var{}, PreviousBody) -> Arg'
%% Try to suppress false warnings when a variable is not used.
%% For instance, we don't expect a warning for useless building in:
@@ -2966,54 +2663,6 @@ move_case_into_arg(Expr, _) ->
Expr.
%%%
-%%% Retrieving information about types.
-%%%
-
--spec get_type(cerl:cerl(), #sub{}) -> type_info() | 'none'.
-
-get_type(#c_var{name=V}, #sub{t=Tdb}) ->
- case Tdb of
- #{V:=Type} -> Type;
- _ -> none
- end;
-get_type(C, _) ->
- case cerl:type(C) of
- binary -> C;
- map -> C;
- _ ->
- case cerl:is_data(C) of
- true -> C;
- false -> none
- end
- end.
-
--spec is_boolean_type(cerl:cerl(), sub()) -> yes_no_maybe().
-
-is_boolean_type(Var, Sub) ->
- case get_type(Var, Sub) of
- none ->
- maybe;
- bool ->
- yes;
- C ->
- B = cerl:is_c_atom(C) andalso
- is_boolean(cerl:atom_val(C)),
- yes_no(B)
- end.
-
--spec is_int_type(cerl:cerl(), sub()) -> yes_no_maybe().
-
-is_int_type(Var, Sub) ->
- case get_type(Var, Sub) of
- none -> maybe;
- integer -> yes;
- C -> yes_no(cerl:is_c_int(C))
- end.
-
-yes_no(true) -> yes;
-yes_no(false) -> no.
-
-%%%
%%% Update type information.
%%%
@@ -3024,70 +2673,14 @@ update_let_types(_Vs, _Arg, Sub) ->
%% that returns multiple values.
Sub.
-update_let_types_1([#c_var{}=V|Vs], [A|As], Sub0) ->
- Sub = update_types_from_expr(V, A, Sub0),
+update_let_types_1([#c_var{name=V}|Vs], [A|As], Sub0) ->
+ Sub = update_types(V, A, Sub0),
update_let_types_1(Vs, As, Sub);
update_let_types_1([], [], Sub) -> Sub.
-update_types_from_expr(V, Expr, Sub) ->
- Type = extract_type(Expr, Sub),
- update_types(V, [Type], Sub).
-
-extract_type(#c_call{module=#c_literal{val=erlang},
- name=#c_literal{val=Name},
- args=Args}=Call, Sub) ->
- case returns_integer(Name, Args) of
- true -> integer;
- false -> extract_type_1(Call, Sub)
- end;
-extract_type(Expr, Sub) ->
- extract_type_1(Expr, Sub).
-
-extract_type_1(Expr, Sub) ->
- case is_bool_expr(Expr, Sub) of
- false -> Expr;
- true -> bool
- end.
-
-returns_integer('band', [_,_]) -> true;
-returns_integer('bnot', [_]) -> true;
-returns_integer('bor', [_,_]) -> true;
-returns_integer('bxor', [_,_]) -> true;
-returns_integer(bit_size, [_]) -> true;
-returns_integer('bsl', [_,_]) -> true;
-returns_integer('bsr', [_,_]) -> true;
-returns_integer(byte_size, [_]) -> true;
-returns_integer(ceil, [_]) -> true;
-returns_integer('div', [_,_]) -> true;
-returns_integer(floor, [_]) -> true;
-returns_integer(length, [_]) -> true;
-returns_integer('rem', [_,_]) -> true;
-returns_integer('round', [_]) -> true;
-returns_integer(size, [_]) -> true;
-returns_integer(tuple_size, [_]) -> true;
-returns_integer(trunc, [_]) -> true;
-returns_integer(_, _) -> false.
-
-%% update_types(Expr, Pattern, Sub) -> Sub'
-%% Update the type database.
-
--spec update_types(cerl:c_var(), [type_info()], sub()) -> sub().
-
-update_types(#c_var{name=V}, Pat, #sub{t=Tdb0}=Sub) ->
- Tdb = update_types_1(V, Pat, Tdb0),
- Sub#sub{t=Tdb}.
-
-update_types_1(V, [#c_tuple{}=P], Types) ->
- Types#{V=>P};
-update_types_1(V, [#c_literal{val=Bool}], Types) when is_boolean(Bool) ->
- Types#{V=>bool};
-update_types_1(V, [#c_fun{vars=Vars}], Types) ->
- Types#{V=>{'fun',length(Vars)}};
-update_types_1(V, [#c_var{name={_,Arity}}], Types) ->
- Types#{V=>{'fun',Arity}};
-update_types_1(V, [Type], Types) when is_atom(Type) ->
- Types#{V=>Type};
-update_types_1(_, _, Types) -> Types.
+update_types(V, #c_tuple{}=P, #sub{t=Tdb}=Sub) ->
+ Sub#sub{t=Tdb#{V=>P}};
+update_types(_, _, Sub) -> Sub.
%% kill_types(V, Tdb) -> Tdb'
%% Kill any entries that references the variable,
@@ -3103,10 +2696,6 @@ kill_types2(V, [{_,#c_tuple{}=Tuple}=Entry|Tdb]) ->
false -> [Entry|kill_types2(V, Tdb)];
true -> kill_types2(V, Tdb)
end;
-kill_types2(V, [{_, {'fun',_}}=Entry|Tdb]) ->
- [Entry|kill_types2(V, Tdb)];
-kill_types2(V, [{_,Atom}=Entry|Tdb]) when is_atom(Atom) ->
- [Entry|kill_types2(V, Tdb)];
kill_types2(_, []) -> [].
%% copy_type(DestVar, SrcVar, Tdb) -> Tdb'
@@ -3198,6 +2787,13 @@ format_error({embedded_unit,Unit,Size}) ->
format_error(bad_unicode) ->
"binary construction will fail with a 'badarg' exception "
"(invalid Unicode code point in a utf8/utf16/utf32 segment)";
+format_error(bad_float_size) ->
+ "binary construction will fail with a 'badarg' exception "
+ "(invalid size for a float segment)";
+format_error({nomatch_shadow,Line,{Name, Arity}}) ->
+ M = io_lib:format("this clause for ~ts/~B cannot match because a previous "
+ "clause at line ~p always matches", [Name, Arity, Line]),
+ flatten(M);
format_error({nomatch_shadow,Line}) ->
M = io_lib:format("this clause cannot match because a previous clause at line ~p "
"always matches", [Line]),
diff --git a/lib/compiler/src/sys_core_fold_lists.erl b/lib/compiler/src/sys_core_fold_lists.erl
index 4d7e132523..f94a25d961 100644
--- a/lib/compiler/src/sys_core_fold_lists.erl
+++ b/lib/compiler/src/sys_core_fold_lists.erl
@@ -56,9 +56,8 @@ call(#c_call{anno=Anno}, lists, all, [Arg1,Arg2]) ->
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=true}},
- Err2 = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^all',1}}|Anno], Err2)},
+ body=function_clause(Anno, [F, Xs])},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -90,9 +89,8 @@ call(#c_call{anno=Anno}, lists, any, [Arg1,Arg2]) ->
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=false}},
- Err2 = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^any',1}}|Anno], Err2)},
+ body=function_clause(Anno, [F, Xs])},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -113,9 +111,8 @@ call(#c_call{anno=Anno}, lists, foreach, [Arg1,Arg2]) ->
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=ok}},
- Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^foreach',1}}|Anno], Err)},
+ body=function_clause(Anno, [F, Xs])},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -143,9 +140,8 @@ call(#c_call{anno=Anno}, lists, map, [Arg1,Arg2]) ->
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=[]}},
- Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^map',1}}|Anno], Err)},
+ body=function_clause(Anno, [F, Xs])},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -174,9 +170,8 @@ call(#c_call{anno=Anno}, lists, flatmap, [Arg1,Arg2]) ->
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=[]}},
- Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^flatmap',1}}|Anno], Err)},
+ body=function_clause(Anno, [F, Xs])},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -213,10 +208,9 @@ call(#c_call{anno=Anno}, lists, filter, [Arg1,Arg2]) ->
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=[]}},
- Err2 = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
C3 = #c_clause{anno=Anno,
pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^filter',1}}|Anno], Err2)},
+ body=function_clause(Anno, [F, Xs])},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -241,9 +235,8 @@ call(#c_call{anno=Anno}, lists, foldl, [Arg1,Arg2,Arg3]) ->
name=#c_literal{val=is_function},
args=[F, #c_literal{val=2}]},
body=A},
- Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, A, Xs]},
C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^foldl',2}}|Anno], Err)},
+ body=function_clause(Anno, [F, A, Xs])},
Fun = #c_fun{vars=[Xs, A],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -268,9 +261,8 @@ call(#c_call{anno=Anno}, lists, foldr, [Arg1,Arg2,Arg3]) ->
name=#c_literal{val=is_function},
args=[F, #c_literal{val=2}]},
body=A},
- Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, A, Xs]},
C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^foldr',2}}|Anno], Err)},
+ body=function_clause(Anno, [F, A, Xs])},
Fun = #c_fun{vars=[Xs, A],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -321,9 +313,8 @@ call(#c_call{anno=Anno}, lists, mapfoldl, [Arg1,Arg2,Arg3]) ->
es=[#c_literal{val=[]}, Avar]}},
%%% Multiple-value version
%%% body=#c_values{es=[#c_literal{val=[]}, A]}},
- Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, Avar, Xs]},
C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^mapfoldl',2}}|Anno], Err)},
+ body=function_clause(Anno, [F, Avar, Xs])},
Fun = #c_fun{vars=[Xs, Avar],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -382,9 +373,8 @@ call(#c_call{anno=Anno}, lists, mapfoldr, [Arg1,Arg2,Arg3]) ->
es=[#c_literal{val=[]}, Avar]}},
%%% Multiple-value version
%%% body=#c_values{es=[#c_literal{val=[]}, A]}},
- Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, Avar, Xs]},
C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
- body=match_fail([{function_name,{'lists^mapfoldr',2}}|Anno], Err)},
+ body=function_clause(Anno, [F, Avar, Xs])},
Fun = #c_fun{vars=[Xs, Avar],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
L = #c_var{name='L'},
@@ -406,3 +396,9 @@ match_fail(Ann, Arg) ->
Name = cerl:abstract(match_fail),
Args = [Arg],
cerl:ann_c_primop(Ann, Name, Args).
+
+function_clause(Anno, Args) ->
+ #c_call{anno=Anno,
+ module=#c_literal{val=erlang},
+ name=#c_literal{val=error},
+ args=[#c_literal{val=function_clause},cerl:ann_make_list(Anno, Args)]}.
diff --git a/lib/compiler/src/sys_core_inline.erl b/lib/compiler/src/sys_core_inline.erl
index 9c417f66f5..9851899709 100644
--- a/lib/compiler/src/sys_core_inline.erl
+++ b/lib/compiler/src/sys_core_inline.erl
@@ -44,7 +44,7 @@
-export([module/2]).
--import(lists, [member/2,map/2,foldl/3,mapfoldl/3,keydelete/3]).
+-import(lists, [member/2,map/2,foldl/3,mapfoldl/3]).
-include("core_parse.hrl").
@@ -116,14 +116,11 @@ inline(Fs0, St0) ->
false -> {Fst,Ifs}
end
end, [], Fs1),
- Is1 = map(fun (#ifun{body=B}=If) ->
- If#ifun{body=cerl_trees:map(match_fail_fun(), B)}
- end, Is0),
- Is2 = [inline_inline(If, Is1) || If <- Is1],
+ Is1 = [inline_inline(If, Is0) || If <- Is0],
%% We would like to remove inlined, non-exported functions here,
%% but this can be difficult as they may be recursive.
%% Use fixed inline functions on all functions.
- Fs = [inline_func(F, Is2) || F <- Fs2],
+ Fs = [inline_func(F, Is1) || F <- Fs2],
%% Regenerate module body.
[Def || #fstat{def=Def} <- Fs].
@@ -172,17 +169,6 @@ inline_func(#fstat{def={Name,F0}}=Fstat, Is) ->
weight_func(_Core, Acc) -> Acc + 1.
-%% match_fail_fun() -> fun/1.
-%% Return a function to use with map to fix inlineable functions
-%% function_clause match_fail (if they have one).
-
-match_fail_fun() ->
- fun (#c_primop{anno=Anno0,name=#c_literal{val=match_fail}}=P) ->
- Anno = keydelete(function_name, 1, Anno0),
- P#c_primop{anno=Anno};
- (Other) -> Other
- end.
-
%% find_inl(Func, Arity, [Inline]) -> #ifun{} | no.
find_inl(F, A, [#ifun{func=F,arity=A}=If|_]) -> If;
diff --git a/lib/compiler/src/sys_core_prepare.erl b/lib/compiler/src/sys_core_prepare.erl
new file mode 100644
index 0000000000..5d9954e04f
--- /dev/null
+++ b/lib/compiler/src/sys_core_prepare.erl
@@ -0,0 +1,130 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Purpose: Prepare Core Erlang not generated by v3_core.
+
+-module(sys_core_prepare).
+-export([module/2]).
+
+-include("core_parse.hrl").
+
+-spec module(cerl:c_module(), [compile:option()]) ->
+ {'ok',cerl:c_module(),[]}.
+
+module(Mod0, _Opts) ->
+ Count = cerl_trees:next_free_variable_name(Mod0),
+ {Mod,_} = cerl_trees:mapfold(fun rewrite_recv/2, Count, Mod0),
+ {ok,Mod,[]}.
+
+rewrite_recv(#c_receive{clauses=[],timeout=Timeout0,action=Action}, Count0) ->
+ %% Lower a receive with only an after blcok to its primitive operations.
+ False = #c_literal{val=false},
+ True = #c_literal{val=true},
+
+ {TimeoutVal,Count1} = new_var(Count0),
+ {LoopName,Count2} = new_func_varname(Count1),
+ LoopFun = #c_var{name={LoopName,0}},
+ ApplyLoop = #c_apply{op=LoopFun,args=[]},
+
+ TimeoutCs = [#c_clause{pats=[True],guard=True,
+ body=#c_seq{arg=primop(timeout),
+ body=Action}},
+ #c_clause{pats=[False],guard=True,
+ body=ApplyLoop}],
+ {TimeoutBool,Count4} = new_var(Count2),
+ TimeoutCase = #c_case{arg=TimeoutBool,clauses=TimeoutCs},
+ TimeoutLet = #c_let{vars=[TimeoutBool],
+ arg=primop(recv_wait_timeout, [TimeoutVal]),
+ body=TimeoutCase},
+
+ Fun = #c_fun{vars=[],body=TimeoutLet},
+
+ Letrec = #c_letrec{anno=[letrec_goto],
+ defs=[{LoopFun,Fun}],
+ body=ApplyLoop},
+
+ OuterLet = #c_let{vars=[TimeoutVal],arg=Timeout0,body=Letrec},
+ {OuterLet,Count4};
+rewrite_recv(#c_receive{clauses=Cs0,timeout=Timeout0,action=Action}, Count0) ->
+ %% Lower receive to its primitive operations.
+ False = #c_literal{val=false},
+ True = #c_literal{val=true},
+
+ {TimeoutVal,Count1} = new_var(Count0),
+ {LoopName,Count2} = new_func_varname(Count1),
+ LoopFun = #c_var{name={LoopName,0}},
+ ApplyLoop = #c_apply{op=LoopFun,args=[]},
+
+ Cs1 = rewrite_cs(Cs0),
+ RecvNext = #c_seq{arg=primop(recv_next),
+ body=ApplyLoop},
+ RecvNextC = #c_clause{anno=[compiler_generated],
+ pats=[#c_var{name='Other'}],guard=True,body=RecvNext},
+ Cs = Cs1 ++ [RecvNextC],
+ {Msg,Count3} = new_var(Count2),
+ MsgCase = #c_case{arg=Msg,clauses=Cs},
+
+ TimeoutCs = [#c_clause{pats=[True],guard=True,
+ body=#c_seq{arg=primop(timeout),
+ body=Action}},
+ #c_clause{pats=[False],guard=True,
+ body=ApplyLoop}],
+ {TimeoutBool,Count4} = new_var(Count3),
+ TimeoutCase = #c_case{arg=TimeoutBool,clauses=TimeoutCs},
+ TimeoutLet = #c_let{vars=[TimeoutBool],
+ arg=primop(recv_wait_timeout, [TimeoutVal]),
+ body=TimeoutCase},
+
+ {PeekSucceeded,Count5} = new_var(Count4),
+ PeekCs = [#c_clause{pats=[True],guard=True,
+ body=MsgCase},
+ #c_clause{pats=[False],guard=True,
+ body=TimeoutLet}],
+ PeekCase = #c_case{arg=PeekSucceeded,clauses=PeekCs},
+ PeekLet = #c_let{vars=[PeekSucceeded,Msg],
+ arg=primop(recv_peek_message),
+ body=PeekCase},
+ Fun = #c_fun{vars=[],body=PeekLet},
+
+ Letrec = #c_letrec{anno=[letrec_goto],
+ defs=[{LoopFun,Fun}],
+ body=ApplyLoop},
+
+ OuterLet = #c_let{vars=[TimeoutVal],arg=Timeout0,body=Letrec},
+ {OuterLet,Count5};
+rewrite_recv(Tree, Count) ->
+ {Tree,Count}.
+
+rewrite_cs([#c_clause{body=B0}=C|Cs]) ->
+ B = #c_seq{arg=primop(remove_message),body=B0},
+ [C#c_clause{body=B}|rewrite_cs(Cs)];
+rewrite_cs([]) -> [].
+
+primop(Name) ->
+ primop(Name, []).
+
+primop(Name, Args) ->
+ #c_primop{name=#c_literal{val=Name},args=Args}.
+
+new_var(Count) ->
+ {#c_var{name=Count},Count+1}.
+
+new_func_varname(Count) ->
+ Name = list_to_atom("@pre" ++ integer_to_list(Count)),
+ {Name,Count+1}.
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 4ad9167714..2be6568977 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -22,7 +22,7 @@
%% At this stage all preprocessing has been done. All that is left are
%% "pure" Erlang functions.
%%
-%% Core transformation is done in three stages:
+%% Core transformation is done in four stages:
%%
%% 1. Flatten expressions into an internal core form without doing
%% matching.
@@ -37,6 +37,12 @@
%% annotations to change implicit exported variables to explicit
%% returns.
%%
+%% 4. Lower receives to more primitive operations. Split binary
+%% patterns where a value is matched out and then used used as
+%% a size in the same pattern. That simplifies the subsequent
+%% passes as all variables are within a single pattern are either
+%% new or used, but never both at the same time.
+%%
%% To ensure the evaluation order we ensure that all arguments are
%% safe. A "safe" is basically a core_lib simple with VERY restricted
%% binaries.
@@ -76,7 +82,8 @@
-export([module/2,format_error/1]).
-import(lists, [reverse/1,reverse/2,map/2,member/2,foldl/3,foldr/3,mapfoldl/3,
- splitwith/2,keyfind/3,sort/1,foreach/2,droplast/1,last/1]).
+ splitwith/2,keyfind/3,sort/1,foreach/2,droplast/1,last/1,
+ duplicate/2]).
-import(ordsets, [add_element/2,del_element/2,is_element/2,
union/1,union/2,intersection/2,subtract/2]).
-import(cerl, [ann_c_cons/3,ann_c_tuple/2,c_tuple/1,
@@ -91,13 +98,16 @@
-record(iapply, {anno=#a{},op,args}).
-record(ibinary, {anno=#a{},segments}). %Not used in patterns.
+-record(ibitstr, {anno=#a{},val,size,unit,type,flags}).
-record(icall, {anno=#a{},module,name,args}).
-record(icase, {anno=#a{},args,clauses,fc}).
-record(icatch, {anno=#a{},body}).
--record(iclause, {anno=#a{},pats,pguard=[],guard,body}).
+-record(iclause, {anno=#a{},pats,guard,body}).
-record(ifun, {anno=#a{},id,vars,clauses,fc,name=unnamed}).
-record(iletrec, {anno=#a{},defs,body}).
-record(imatch, {anno=#a{},pat,guard=[],arg,fc}).
+-record(imap, {anno=#a{},arg=#c_literal{val=#{}},es,is_pat=false}).
+-record(imappair, {anno=#a{},op,key,val}).
-record(iprimop, {anno=#a{},name,args}).
-record(iprotect, {anno=#a{},body}).
-record(ireceive1, {anno=#a{},clauses}).
@@ -105,7 +115,7 @@
-record(iset, {anno=#a{},var,arg}).
-record(itry, {anno=#a{},args,vars,body,evars,handler}).
-record(ifilter, {anno=#a{},arg}).
--record(igen, {anno=#a{},ceps=[],acc_pat,acc_guard,
+-record(igen, {anno=#a{},acc_pat,acc_guard,
skip_pat,tail,tail_pat,arg}).
-record(isimple, {anno=#a{},term :: cerl:cerl()}).
@@ -118,6 +128,7 @@
-type ifun() :: #ifun{}.
-type iletrec() :: #iletrec{}.
-type imatch() :: #imatch{}.
+-type imap() :: #imap{}.
-type iprimop() :: #iprimop{}.
-type iprotect() :: #iprotect{}.
-type ireceive1() :: #ireceive1{}.
@@ -128,19 +139,22 @@
-type igen() :: #igen{}.
-type isimple() :: #isimple{}.
--type i() :: iapply() | ibinary() | icall() | icase() | icatch()
- | iclause() | ifun() | iletrec() | imatch() | iprimop()
- | iprotect() | ireceive1() | ireceive2() | iset() | itry()
- | ifilter() | igen() | isimple().
+-type i() :: iapply() | ibinary() | icall() | icase() | icatch()
+ | iclause() | ifun() | iletrec() | imatch() | imap()
+ | iprimop() | iprotect() | ireceive1() | ireceive2()
+ | iset() | itry() | ifilter()
+ | igen() | isimple().
-type warning() :: {file:filename(), [{integer(), module(), term()}]}.
-record(core, {vcount=0 :: non_neg_integer(), %Variable counter
fcount=0 :: non_neg_integer(), %Function counter
+ gcount=0 :: non_neg_integer(), %Goto counter
function={none,0} :: fa(), %Current function.
in_guard=false :: boolean(), %In guard or not.
wanted=true :: boolean(), %Result wanted or not.
- opts :: [compile:option()], %Options.
+ opts=[] :: [compile:option()], %Options.
+ dialyzer=false :: boolean(), %Help dialyzer or not.
ws=[] :: [warning()], %Warnings.
file=[{file,""}] %File.
}).
@@ -216,69 +230,66 @@ defined_functions(Forms) ->
%% ok.
function({function,_,Name,Arity,Cs0}, Ws0, File, Opts) ->
- St0 = #core{vcount=0,function={Name,Arity},opts=Opts,
- ws=Ws0,file=[{file,File}]},
- {B0,St1} = body(Cs0, Name, Arity, St0),
- %% ok = function_dump(Name,Arity,"body:~n~p~n",[B0]),
- {B1,St2} = ubody(B0, St1),
- %% ok = function_dump(Name,Arity,"ubody:~n~p~n",[B1]),
- {B2,#core{ws=Ws}} = cbody(B1, St2),
- %% ok = function_dump(Name,Arity,"cbody:~n~p~n",[B2]),
- {{#c_var{name={Name,Arity}},B2},Ws}.
+ try
+ St0 = #core{vcount=0,function={Name,Arity},opts=Opts,
+ dialyzer=member(dialyzer, Opts),
+ ws=Ws0,file=[{file,File}]},
+ {B0,St1} = body(Cs0, Name, Arity, St0),
+ %% ok = function_dump(Name, Arity, "body:~n~p~n",[B0]),
+ {B1,St2} = ubody(B0, St1),
+ %% ok = function_dump(Name, Arity, "ubody:~n~p~n",[B1]),
+ {B2,St3} = cbody(B1, St2),
+ %% ok = function_dump(Name, Arity, "cbody:~n~p~n",[B2]),
+ {B3,#core{ws=Ws}} = lbody(B2, St3),
+ %% ok = function_dump(Name, Arity, "lbody:~n~p~n",[B3]),
+ {{#c_var{name={Name,Arity}},B3},Ws}
+ catch
+ Class:Error:Stack ->
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
body(Cs0, Name, Arity, St0) ->
Anno = lineno_anno(element(2, hd(Cs0)), St0),
+ FunAnno = [{function,{Name,Arity}} | Anno],
{Args0,St1} = new_vars(Anno, Arity, St0),
Args = reverse(Args0), %Nicer order
- case clauses(Cs0, St1) of
- {Cs1,[],St2} ->
- {Ps,St3} = new_vars(Arity, St2), %Need new variables here
- Fc = function_clause(Ps, Anno, {Name,Arity}),
- {#ifun{anno=#a{anno=Anno},id=[],vars=Args,clauses=Cs1,fc=Fc},St3};
- {Cs1,Eps,St2} ->
- %% We have pre-expressions from patterns and
- %% these needs to be letified before matching
- %% since only bound variables are allowed
- AnnoGen = #a{anno=[compiler_generated]},
- {Ps1,St3} = new_vars(Arity, St2), %Need new variables here
- Fc1 = function_clause(Ps1, Anno, {Name,Arity}),
- {Ps2,St4} = new_vars(Arity, St3), %Need new variables here
- Fc2 = function_clause(Ps2, Anno, {Name,Arity}),
- Case = #icase{anno=AnnoGen,args=Args,
- clauses=Cs1,
- fc=Fc2},
- {#ifun{anno=#a{anno=Anno},id=[],vars=Args,
- clauses=[#iclause{anno=AnnoGen,pats=Ps1,
- guard=[#c_literal{val=true}],
- body=Eps ++ [Case]}],
- fc=Fc1},St4}
- end.
+ {Cs1,St2} = clauses(Cs0, St1),
+ {Ps,St3} = new_vars(Arity, St2), %Need new variables here
+ Fc = function_clause(Ps, Anno),
+ {#ifun{anno=#a{anno=FunAnno},id=[],vars=Args,clauses=Cs1,fc=Fc},St3}.
-%% clause(Clause, State) -> {Cclause,State} | noclause.
+%% clause(Clause, State) -> {Cclause,State}.
%% clauses([Clause], State) -> {[Cclause],State}.
-%% Convert clauses. Trap bad pattern aliases and remove clause from
-%% clause list.
-
-clauses([C0|Cs0],St0) ->
- case clause(C0, St0) of
- {noclause,_,St} -> clauses(Cs0,St);
- {C,Eps1,St1} ->
- {Cs,Eps2,St2} = clauses(Cs0, St1),
- {[C|Cs],Eps1++Eps2,St2}
- end;
-clauses([],St) -> {[],[],St}.
+%% Convert clauses. Trap bad pattern aliases.
+
+clauses([C0|Cs0], St0) ->
+ {C,St1} = clause(C0, St0),
+ {Cs,St2} = clauses(Cs0, St1),
+ {[C|Cs],St2};
+clauses([], St) -> {[],St}.
clause({clause,Lc,H0,G0,B0}, St0) ->
try head(H0, St0) of
- {H1,Eps,St1} ->
+ {H1,St1} ->
{G1,St2} = guard(G0, St1),
{B1,St3} = exprs(B0, St2),
Anno = lineno_anno(Lc, St3),
- {#iclause{anno=#a{anno=Anno},pats=H1,guard=G1,body=B1},Eps,St3}
+ {#iclause{anno=#a{anno=Anno},pats=H1,guard=G1,body=B1},St3}
catch
throw:nomatch ->
- St = add_warning(Lc, nomatch, St0),
- {noclause,[],St} %Bad pattern
+ %% This pattern can't possibly match. If we simply remove
+ %% the clause, varibles that are used later might not be
+ %% bound. Therefore, we must keep the clause, but rewrite
+ %% the pattern to a pattern that will bind the same
+ %% variables and ensure that the clause can't be executed
+ %% by letting the guard return false.
+ St1 = add_warning(Lc, nomatch, St0),
+ H1 = [sanitize(P) || P <- H0],
+ false = H0 =:= H1, %Assertion.
+ G1 = [[{atom,Lc,false}]],
+ LcNoWarn = no_compiler_warning(Lc),
+ clause({clause,LcNoWarn,H1,G1,B0}, St1)
end.
clause_arity({clause,_,H0,_,_}) -> length(H0).
@@ -335,7 +346,7 @@ gexpr({op,_,'andalso',_,_}=E0, Bools, St0) ->
{#c_var{name=V0},St} = new_var(Anno, St0),
V = {var,L,V0},
False = {atom,L,false},
- E = make_bool_switch_guard(L, E1, V, E2, False),
+ E = make_bool_switch(L, E1, V, E2, False),
gexpr(E, Bools, St);
gexpr({op,_,'orelse',_,_}=E0, Bools, St0) ->
{op,L,'orelse',E1,E2} = right_assoc(E0, 'orelse'),
@@ -343,7 +354,7 @@ gexpr({op,_,'orelse',_,_}=E0, Bools, St0) ->
{#c_var{name=V0},St} = new_var(Anno, St0),
V = {var,L,V0},
True = {atom,L,true},
- E = make_bool_switch_guard(L, E1, V, True, E2),
+ E = make_bool_switch(L, E1, V, True, E2),
gexpr(E, Bools, St);
gexpr({op,Line,Op,L,R}=E, Bools, St) ->
case erl_internal:bool_op(Op, 2) of
@@ -419,9 +430,21 @@ gexpr_test(E0, Bools0, St0) ->
{E1,Eps0,St1} = expr(E0, St0),
%% Generate "top-level" test and argument calls.
case E1 of
+ #icall{anno=Anno,module=#c_literal{val=erlang},
+ name=#c_literal{val=is_function},
+ args=[_,_]} ->
+ %% is_function/2 is not a safe type test. We must force
+ %% it to be protected.
+ Lanno = Anno#a.anno,
+ {New,St2} = new_var(Lanno, St1),
+ {icall_eq_true(New),
+ Eps0 ++ [#iset{anno=Anno,var=New,arg=E1}],Bools0,St2};
#icall{anno=Anno,module=#c_literal{val=erlang},name=#c_literal{val=N},args=As} ->
+ %% Note that erl_expand_records has renamed type
+ %% tests to the new names; thus, float/1 as a type
+ %% test will now be named is_float/1.
Ar = length(As),
- case erl_internal:type_test(N, Ar) orelse
+ case erl_internal:new_type_test(N, Ar) orelse
erl_internal:comp_op(N, Ar) orelse
erl_internal:bool_op(N, Ar) of
true -> {E1,Eps0,Bools0,St1};
@@ -586,14 +609,14 @@ expr({bin,L,Es0}, St0) ->
try expr_bin(Es0, full_anno(L, St0), St0) of
{_,_,_}=Res -> Res
catch
- throw:bad_binary ->
- St = add_warning(L, bad_binary, St0),
+ throw:{bad_binary,Eps,St1} ->
+ St = add_warning(L, bad_binary, St1),
LineAnno = lineno_anno(L, St),
As = [#c_literal{anno=LineAnno,val=badarg}],
{#icall{anno=#a{anno=LineAnno}, %Must have an #a{}
module=#c_literal{anno=LineAnno,val=erlang},
name=#c_literal{anno=LineAnno,val=error},
- args=As},[],St}
+ args=As},Eps,St}
end;
expr({block,_,Es0}, St0) ->
%% Inline the block directly.
@@ -601,26 +624,26 @@ expr({block,_,Es0}, St0) ->
{E1,Eps,St2} = expr(last(Es0), St1),
{E1,Es1 ++ Eps,St2};
expr({'if',L,Cs0}, St0) ->
- {Cs1,Ceps,St1} = clauses(Cs0, St0),
+ {Cs1,St1} = clauses(Cs0, St0),
Lanno = lineno_anno(L, St1),
Fc = fail_clause([], Lanno, #c_literal{val=if_clause}),
- {#icase{anno=#a{anno=Lanno},args=[],clauses=Cs1,fc=Fc},Ceps,St1};
+ {#icase{anno=#a{anno=Lanno},args=[],clauses=Cs1,fc=Fc},[],St1};
expr({'case',L,E0,Cs0}, St0) ->
{E1,Eps,St1} = novars(E0, St0),
- {Cs1,Ceps,St2} = clauses(Cs0, St1),
+ {Cs1,St2} = clauses(Cs0, St1),
{Fpat,St3} = new_var(St2),
Lanno = lineno_anno(L, St2),
Fc = fail_clause([Fpat], Lanno, c_tuple([#c_literal{val=case_clause},Fpat])),
- {#icase{anno=#a{anno=Lanno},args=[E1],clauses=Cs1,fc=Fc},Eps++Ceps,St3};
+ {#icase{anno=#a{anno=Lanno},args=[E1],clauses=Cs1,fc=Fc},Eps,St3};
expr({'receive',L,Cs0}, St0) ->
- {Cs1,Ceps,St1} = clauses(Cs0, St0),
- {#ireceive1{anno=#a{anno=lineno_anno(L, St1)},clauses=Cs1},Ceps, St1};
+ {Cs1,St1} = clauses(Cs0, St0),
+ {#ireceive1{anno=#a{anno=lineno_anno(L, St1)},clauses=Cs1},[],St1};
expr({'receive',L,Cs0,Te0,Tes0}, St0) ->
{Te1,Teps,St1} = novars(Te0, St0),
{Tes1,St2} = exprs(Tes0, St1),
- {Cs1,Ceps,St3} = clauses(Cs0, St2),
+ {Cs1,St3} = clauses(Cs0, St2),
{#ireceive2{anno=#a{anno=lineno_anno(L, St3)},
- clauses=Cs1,timeout=Te1,action=Tes1},Teps++Ceps,St3};
+ clauses=Cs1,timeout=Te1,action=Tes1},Teps,St3};
expr({'try',L,Es0,[],Ecs,[]}, St0) ->
%% 'try ... catch ... end'
{Es1,St1} = exprs(Es0, St0),
@@ -634,7 +657,7 @@ expr({'try',L,Es0,Cs0,Ecs,[]}, St0) ->
%% 'try ... of ... catch ... end'
{Es1,St1} = exprs(Es0, St0),
{V,St2} = new_var(St1), %This name should be arbitrary
- {Cs1,Ceps,St3} = clauses(Cs0, St2),
+ {Cs1,St3} = clauses(Cs0, St2),
{Fpat,St4} = new_var(St3),
Lanno = lineno_anno(L, St4),
Fc = fail_clause([Fpat], Lanno,
@@ -643,7 +666,7 @@ expr({'try',L,Es0,Cs0,Ecs,[]}, St0) ->
{#itry{anno=#a{anno=lineno_anno(L, St5)},args=Es1,
vars=[V],body=[#icase{anno=#a{anno=Lanno},args=[V],clauses=Cs1,fc=Fc}],
evars=Evs,handler=Hs},
- Ceps,St5};
+ [],St5};
expr({'try',L,Es0,[],[],As0}, St0) ->
%% 'try ... after ... end'
{Es1,St1} = exprs(Es0, St0),
@@ -652,7 +675,7 @@ expr({'try',L,Es0,[],[],As0}, St0) ->
{V,St4} = new_var(St3), % (must not exist in As1)
LA = lineno_anno(L, St4),
Lanno = #a{anno=LA},
- Fc = function_clause([], LA, {Name,0}),
+ Fc = function_clause([], LA),
Fun = #ifun{anno=Lanno,id=[],vars=[],
clauses=[#iclause{anno=Lanno,pats=[],
guard=[#c_literal{val=true}],
@@ -699,7 +722,7 @@ expr({call,Lc,{atom,Lf,F},As0}, St0) ->
Op = #c_var{anno=lineno_anno(Lf, St1),name={F,length(As1)}},
{#iapply{anno=#a{anno=lineno_anno(Lc, St1)},op=Op,args=As1},Aps,St1};
expr({call,L,FunExp,As0}, St0) ->
- {Fun,Fps,St1} = safe_fun(length(As0), FunExp, St0),
+ {Fun,Fps,St1} = safe(FunExp, St0),
{As1,Aps,St2} = safe_list(As0, St1),
Lanno = lineno_anno(L, St2),
{#iapply{anno=#a{anno=Lanno},op=Fun,args=As1},Fps ++ Aps,St2};
@@ -712,12 +735,12 @@ expr({match,L,P0,E0}, St0) ->
end,
{E2,Eps1,St2} = novars(E1, St1),
St3 = St2#core{wanted=St0#core.wanted},
- {P2,Eps2,St4} = try
- pattern(P1, St3)
- catch
- throw:Thrown ->
- {Thrown,[],St3}
- end,
+ {P2,St4} = try
+ pattern(P1, St3)
+ catch
+ throw:Thrown ->
+ {Thrown,St3}
+ end,
{Fpat,St5} = new_var(St4),
Lanno = lineno_anno(L, St5),
Fc = fail_clause([Fpat], Lanno, c_tuple([#c_literal{val=badmatch},Fpat])),
@@ -746,15 +769,45 @@ expr({match,L,P0,E0}, St0) ->
St6 = add_warning(L, nomatch, St5),
{Expr,Eps3,St7} = safe(E1, St6),
SanPat0 = sanitize(P1),
- {SanPat,Eps4,St} = pattern(SanPat0, St7),
+ {SanPat,St} = pattern(SanPat0, St7),
Badmatch = c_tuple([#c_literal{val=badmatch},Expr]),
Fail = #iprimop{anno=#a{anno=Lanno},
name=#c_literal{val=match_fail},
args=[Badmatch]},
- Eps = Eps3 ++ Eps4 ++ [Fail],
+ Eps = Eps3 ++ [Fail],
{#imatch{anno=#a{anno=Lanno},pat=SanPat,arg=Expr,fc=Fc},Eps,St};
Other when not is_atom(Other) ->
- {#imatch{anno=#a{anno=Lanno},pat=P2,arg=E2,fc=Fc},Eps1++Eps2,St5}
+ %% We must rewrite top-level aliases to lets to avoid unbound
+ %% variables in code such as:
+ %%
+ %% <<42:Sz>> = Sz = B
+ %%
+ %% If we would keep the top-level aliases the example would
+ %% be translated like this:
+ %%
+ %% case B of
+ %% <Sz = #{#<42>(Sz,1,'integer',['unsigned'|['big']])}#>
+ %% when 'true' ->
+ %% .
+ %% .
+ %% .
+ %%
+ %% Here the variable Sz would be unbound in the binary pattern.
+ %%
+ %% Instead we bind Sz in a let to ensure it is bound when
+ %% used in the binary pattern:
+ %%
+ %% let <Sz> = B
+ %% in case Sz of
+ %% <#{#<42>(Sz,1,'integer',['unsigned'|['big']])}#>
+ %% when 'true' ->
+ %% .
+ %% .
+ %% .
+ %%
+ {P3,E3,Eps2} = letify_aliases(P2, E2),
+ Eps = Eps1 ++ Eps2,
+ {#imatch{anno=#a{anno=Lanno},pat=P3,arg=E3,fc=Fc},Eps,St5}
end;
expr({op,_,'++',{lc,Llc,E,Qs0},More}, St0) ->
%% Optimise '++' here because of the list comprehension algorithm.
@@ -773,7 +826,7 @@ expr({op,_,'andalso',_,_}=E0, St0) ->
{#c_var{name=V0},St} = new_var(Anno, St0),
V = {var,L,V0},
False = {atom,L,false},
- E = make_bool_switch(L, E1, V, E2, False, St0),
+ E = make_bool_switch(L, E1, V, E2, False),
expr(E, St);
expr({op,_,'orelse',_,_}=E0, St0) ->
{op,L,'orelse',E1,E2} = right_assoc(E0, 'orelse'),
@@ -781,7 +834,7 @@ expr({op,_,'orelse',_,_}=E0, St0) ->
{#c_var{name=V0},St} = new_var(Anno, St0),
V = {var,L,V0},
True = {atom,L,true},
- E = make_bool_switch(L, E1, V, True, E2, St0),
+ E = make_bool_switch(L, E1, V, True, E2),
expr(E, St);
expr({op,L,Op,A0}, St0) ->
{A1,Aps,St1} = safe(A0, St0),
@@ -796,6 +849,11 @@ expr({op,L,Op,L0,R0}, St0) ->
module=#c_literal{anno=LineAnno,val=erlang},
name=#c_literal{anno=LineAnno,val=Op},args=As},Aps,St1}.
+letify_aliases(#c_alias{var=V,pat=P0}, E0) ->
+ {P1,E1,Eps0} = letify_aliases(P0, V),
+ {P1,E1,[#iset{var=V,arg=E0}|Eps0]};
+letify_aliases(P, E) ->
+ {P,E,[]}.
%% sanitize(Pat) -> SanitizedPattern
%% Rewrite Pat so that it will be accepted by pattern/2 and will
@@ -817,17 +875,17 @@ sanitize({cons,L,H,T}) ->
sanitize({tuple,L,Ps0}) ->
Ps = [sanitize(P) || P <- Ps0],
{tuple,L,Ps};
+sanitize({bin,L,Segs0}) ->
+ Segs = [Var || {bin_element,_,{var,_,_}=Var,_,_} <- Segs0],
+ {tuple,L,Segs};
sanitize({map,L,Ps0}) ->
Ps = [sanitize(V) || {map_field_exact,_,_,V} <- Ps0],
{tuple,L,Ps};
+sanitize({op,L,_Name,P1,P2}) ->
+ {tuple,L,[sanitize(P1),sanitize(P2)]};
sanitize(P) -> P.
-make_bool_switch(L, E, V, T, F, #core{in_guard=true}) ->
- make_bool_switch_guard(L, E, V, T, F);
-make_bool_switch(L, E, V, T, F, #core{}) ->
- make_bool_switch_body(L, E, V, T, F).
-
-make_bool_switch_body(L, E, V, T, F) ->
+make_bool_switch(L, E, V, T, F) ->
NegL = no_compiler_warning(L),
Error = {tuple,NegL,[{atom,NegL,badarg},V]},
{'case',NegL,E,
@@ -837,42 +895,42 @@ make_bool_switch_body(L, E, V, T, F) ->
[{call,NegL,{remote,NegL,{atom,NegL,erlang},{atom,NegL,error}},
[Error]}]}]}.
-make_bool_switch_guard(_, E, _, {atom,_,true}, {atom,_,false}) -> E;
-make_bool_switch_guard(L, E, V, T, F) ->
- NegL = no_compiler_warning(L),
- {'case',NegL,E,
- [{clause,NegL,[{atom,NegL,true}],[],[T]},
- {clause,NegL,[{atom,NegL,false}],[],[F]},
- {clause,NegL,[V],[],[V]}
- ]}.
-
expr_map(M0, Es0, L, St0) ->
- {M1,Eps0,St1} = safe(M0, St0),
+ {M1,Eps0,St1} = safe_map(M0, St0),
Badmap = badmap_term(M1, St1),
A = lineno_anno(L, St1),
Fc = fail_clause([], [{eval_failure,badmap}|A], Badmap),
- case is_valid_map_src(M1) of
- true ->
- {M2,Eps1,St2} = map_build_pairs(M1, Es0, full_anno(L, St1), St1),
- M3 = case Es0 of
- [] -> M1;
- [_|_] -> M2
- end,
- Cs = [#iclause{
- anno=#a{anno=[compiler_generated|A]},
- pats=[],
- guard=[#icall{anno=#a{anno=A},
- module=#c_literal{anno=A,val=erlang},
- name=#c_literal{anno=A,val=is_map},
- args=[M1]}],
- body=[M3]}],
- Eps = Eps0 ++ Eps1,
- {#icase{anno=#a{anno=A},args=[],clauses=Cs,fc=Fc},Eps,St2};
- false ->
- %% Not a map source. The update will always fail.
- St2 = add_warning(L, badmap, St1),
- #iclause{body=[Fail]} = Fc,
- {Fail,Eps0,St2}
+ {M2,Eps1,St2} = map_build_pairs(M1, Es0, full_anno(L, St1), St1),
+ M3 = case Es0 of
+ [] -> M1;
+ [_|_] -> M2
+ end,
+ Cs = [#iclause{
+ anno=#a{anno=[compiler_generated|A]},
+ pats=[],
+ guard=[#icall{anno=#a{anno=A},
+ module=#c_literal{anno=A,val=erlang},
+ name=#c_literal{anno=A,val=is_map},
+ args=[M1]}],
+ body=[M3]}],
+ Eps = Eps0 ++ Eps1,
+ {#icase{anno=#a{anno=A},args=[],clauses=Cs,fc=Fc},Eps,St2}.
+
+safe_map(M0, St0) ->
+ case safe(M0, St0) of
+ {#c_var{},_,_}=Res ->
+ Res;
+ {#c_literal{val=Map},_,_}=Res when is_map(Map) ->
+ Res;
+ {NotMap,Eps0,St1} ->
+ %% Not a map. There will be a syntax error if we try to
+ %% pretty-print the Core Erlang code and then try to parse
+ %% it. To avoid the syntax error, force the term into a
+ %% variable.
+ {V,St2} = new_var(St1),
+ Anno = cerl:get_ann(NotMap),
+ Eps1 = [#iset{anno=#a{anno=Anno},var=V,arg=NotMap}],
+ {V,Eps0++Eps1,St2}
end.
badmap_term(_Map, #core{in_guard=true}) ->
@@ -915,16 +973,12 @@ maybe_warn_repeated_keys(Ck,Line,Used,St) ->
map_op(map_field_assoc) -> #c_literal{val=assoc};
map_op(map_field_exact) -> #c_literal{val=exact}.
-is_valid_map_src(#c_literal{val = M}) when is_map(M) -> true;
-is_valid_map_src(#c_var{}=Var) -> not cerl:is_c_fname(Var);
-is_valid_map_src(_) -> false.
-
%% try_exception([ExcpClause], St) -> {[ExcpVar],Handler,St}.
try_exception(Ecs0, St0) ->
%% Note that Tag is not needed for rethrow - it is already in Info.
{Evs,St1} = new_vars(3, St0), % Tag, Value, Info
- {Ecs1,Ceps,St2} = clauses(Ecs0, St1),
+ {Ecs1,St2} = clauses(Ecs0, St1),
Ecs2 = try_build_stacktrace(Ecs1, hd(Evs)),
[_,Value,Info] = Evs,
LA = case Ecs2 of
@@ -937,7 +991,7 @@ try_exception(Ecs0, St0) ->
name=#c_literal{val=raise},
args=[Info,Value]}]},
Hs = [#icase{anno=#a{anno=LA},args=[c_tuple(Evs)],clauses=Ecs2,fc=Ec}],
- {Evs,Ceps++Hs,St2}.
+ {Evs,Hs,St2}.
try_after(As, St0) ->
%% See above.
@@ -975,31 +1029,149 @@ try_build_stacktrace([], _) -> [].
%% Flatten the arguments of a bin. Do this straight left to right!
%% Note that ibinary needs to have its annotation wrapped in a #a{}
%% record whereas c_literal should not have a wrapped annotation
-
+
expr_bin(Es0, Anno, St0) ->
Es1 = [bin_element(E) || E <- Es0],
case constant_bin(Es1) of
error ->
- {Es,Eps,St} = expr_bin_1(bin_expand_strings(Es1), St0),
- {#ibinary{anno=#a{anno=Anno},segments=Es},Eps,St};
+ case expr_bin_1(Es1, St0) of
+ {[],Eps,St} ->
+ EmptyBin = <<>>,
+ {#c_literal{anno=Anno,val=EmptyBin},Eps,St};
+ {Es,Eps,St} ->
+ {#ibinary{anno=#a{anno=Anno},segments=Es},Eps,St}
+ end;
Bin ->
{#c_literal{anno=Anno,val=Bin},[],St0}
end.
+expr_bin_1(Es, St0) ->
+ Res = foldr(fun (E, {Ces,Eps0,S0}) ->
+ try bitstr(E, S0) of
+ {Ce,Eps,S1} when is_list(Ces) ->
+ {Ce++Ces,Eps ++ Eps0,S1};
+ {_Ce,Eps,S1} ->
+ {Ces,Eps ++ Eps0,S1}
+ catch
+ {bad_binary,Eps,S1} ->
+ {bad_binary,Eps ++ Eps0,S1}
+ end
+ end, {[],[],St0}, Es),
+ case Res of
+ {bad_binary,Eps,St} ->
+ throw({bad_binary,Eps,St});
+ {_,_,_}=Res ->
+ Res
+ end.
+
+bitstrs([E0|Es0], St0) ->
+ {E,Eps0,St1} = bitstr(E0, St0),
+ {Es,Eps1,St2} = bitstrs(Es0, St1),
+ {E++Es,Eps0++Eps1,St2};
+bitstrs([], St) ->
+ {[],[],St}.
+
+bitstr({bin_element,Line,{string,_,S},{integer,_,8},_}, St) ->
+ bitstrs(bin_expand_string(S, Line, 0, 0), St);
+bitstr({bin_element,Line,{string,_,[]},Sz0,Ts}, St0) ->
+ %% Empty string. We must make sure that the type is correct.
+ {[#c_bitstr{size=Sz}],Eps0,St1} =
+ bitstr({bin_element,Line,{char,Line,0},Sz0,Ts}, St0),
+
+ %% At this point, the type is either a correct literal or
+ %% an expression.
+ case Sz of
+ #c_literal{val=undefined} ->
+ %% One of the utf* types. The size is not used.
+ {[],[],St1};
+ #c_literal{val=Int} when is_integer(Int), Int >= 0 ->
+ {[],[],St1};
+ #c_var{} ->
+ %% Must add a test to verify that the size expression is
+ %% an integer >= 0.
+ Erlang = {atom,Line,erlang},
+ Test0 = {call,Line,{remote,Line,Erlang,{atom,Line,is_integer}},
+ [Sz0]},
+ Test1 = {call,Line,{remote,Line,Erlang,{atom,Line,'>='}},
+ [Sz0,{integer,Line,0}]},
+ Test2 = {op,Line,'andalso',Test0,Test1},
+ Fail = {call,Line,{remote,Line,Erlang,{atom,Line,error}},
+ [{atom,Line,badarg}]},
+ Test = {op,Line,'orelse',Test2,Fail},
+ Match = {match,Line,{var,Line,'_'},Test},
+ {_,Eps1,St2} = expr(Match, St1),
+ Eps = Eps0 ++ Eps1,
+ {[],Eps,St2}
+ end;
+bitstr({bin_element,Line,{string,_,S},Sz0,Ts}, St0) ->
+ {[Bitstr],Eps,St1} = bitstr({bin_element,Line,{char,Line,0},Sz0,Ts}, St0),
+ Es = [Bitstr#c_bitstr{val=#c_literal{anno=full_anno(Line, St1),val=C}} ||
+ C <- S],
+ {Es,Eps,St1};
+bitstr({bin_element,_,E0,Size0,[Type,{unit,Unit}|Flags]}, St0) ->
+ {E1,Eps0,St1} = safe(E0, St0),
+ {Size1,Eps1,St2} = safe(Size0, St1),
+ Eps = Eps0 ++ Eps1,
+ case {Type,E1} of
+ {_,#c_var{}} -> ok;
+ {integer,#c_literal{val=I}} when is_integer(I) -> ok;
+ {utf8,#c_literal{val=I}} when is_integer(I) -> ok;
+ {utf16,#c_literal{val=I}} when is_integer(I) -> ok;
+ {utf32,#c_literal{val=I}} when is_integer(I) -> ok;
+ {float,#c_literal{val=V}} when is_number(V) -> ok;
+ {binary,#c_literal{val=V}} when is_bitstring(V) -> ok;
+ {_,_} ->
+ %% Note that the pre expressions may bind variables that
+ %% are used later or have side effects.
+ throw({bad_binary,Eps,St2})
+ end,
+ case Size1 of
+ #c_var{} -> ok;
+ #c_literal{val=Sz} when is_integer(Sz), Sz >= 0 -> ok;
+ #c_literal{val=undefined} -> ok;
+ #c_literal{val=all} -> ok;
+ _ -> throw({bad_binary,Eps,St2})
+ end,
+ {[#c_bitstr{val=E1,size=Size1,
+ unit=#c_literal{val=Unit},
+ type=#c_literal{val=Type},
+ flags=#c_literal{val=Flags}}],
+ Eps,St2}.
+
bin_element({bin_element,Line,Expr,Size0,Type0}) ->
{Size,Type} = make_bit_type(Line, Size0, Type0),
{bin_element,Line,Expr,Size,Type}.
make_bit_type(Line, default, Type0) ->
case erl_bits:set_bit_type(default, Type0) of
- {ok,all,Bt} -> {{atom,Line,all},erl_bits:as_list(Bt)};
+ {ok,all,Bt} -> {make_all_size(Line),erl_bits:as_list(Bt)};
{ok,undefined,Bt} -> {{atom,Line,undefined},erl_bits:as_list(Bt)};
{ok,Size,Bt} -> {{integer,Line,Size},erl_bits:as_list(Bt)}
end;
-make_bit_type(_Line, Size, Type0) -> %Integer or 'all'
- {ok,Size,Bt} = erl_bits:set_bit_type(Size, Type0),
+make_bit_type(_Line, {atom,Anno,all}=Size, Type0) ->
+ case erl_anno:generated(Anno) of
+ true ->
+ %% This `all` was created by the compiler from a binary
+ %% segment without a size.
+ {ok,Size,Bt} = erl_bits:set_bit_type(Size, Type0),
+ {Size,erl_bits:as_list(Bt)};
+ false ->
+ %% This `all` was present in the source code. It is not
+ %% a valid size.
+ throw(nomatch)
+ end;
+make_bit_type(_Line, Size0, Type0) -> %Integer or 'all'
+ {ok,Size1,Bt} = erl_bits:set_bit_type(Size0, Type0),
+ Size = case Size1 of
+ {char,Anno,CharVal} -> {integer,Anno,CharVal};
+ _ -> Size1
+ end,
{Size,erl_bits:as_list(Bt)}.
+make_all_size(Line) ->
+ Anno = erl_anno:set_generated(true, Line),
+ {atom,Anno,all}.
+
%% constant_bin([{bin_element,_,_,_,_}]) -> binary() | error
%% If the binary construction is truly constant (no variables,
%% no native fields), and does not contain fields whose expansion
@@ -1080,18 +1252,6 @@ count_bits(Int) ->
count_bits_1(0, Bits) -> Bits;
count_bits_1(Int, Bits) -> count_bits_1(Int bsr 64, Bits+64).
-bin_expand_strings(Es0) ->
- foldr(fun ({bin_element,Line,{string,_,S},{integer,_,8},_}, Es) ->
- bin_expand_string(S, Line, 0, 0) ++ Es;
- ({bin_element,Line,{string,_,S},Sz,Ts}, Es1) ->
- foldr(
- fun (C, Es) ->
- [{bin_element,Line,{char,Line,C},Sz,Ts}|Es]
- end, Es1, S);
- (E, Es) ->
- [E|Es]
- end, [], Es0).
-
bin_expand_string(S, Line, Val, Size) when Size >= 2048 ->
Combined = make_combined(Line, Val, Size),
[Combined|bin_expand_string(S, Line, 0, 0)];
@@ -1105,59 +1265,26 @@ make_combined(Line, Val, Size) ->
{integer,Line,Size},
[integer,{unit,1},unsigned,big]}.
-expr_bin_1(Es, St) ->
- foldr(fun (E, {Ces,Esp,St0}) ->
- {Ce,Ep,St1} = bitstr(E, St0),
- {[Ce|Ces],Ep ++ Esp,St1}
- end, {[],[],St}, Es).
-
-bitstr({bin_element,_,E0,Size0,[Type,{unit,Unit}|Flags]}, St0) ->
- {E1,Eps,St1} = safe(E0, St0),
- {Size1,Eps2,St2} = safe(Size0, St1),
- case {Type,E1} of
- {_,#c_var{}} -> ok;
- {integer,#c_literal{val=I}} when is_integer(I) -> ok;
- {utf8,#c_literal{val=I}} when is_integer(I) -> ok;
- {utf16,#c_literal{val=I}} when is_integer(I) -> ok;
- {utf32,#c_literal{val=I}} when is_integer(I) -> ok;
- {float,#c_literal{val=V}} when is_number(V) -> ok;
- {binary,#c_literal{val=V}} when is_bitstring(V) -> ok;
- {_,_} ->
- throw(bad_binary)
- end,
- case Size1 of
- #c_var{} -> ok;
- #c_literal{val=Sz} when is_integer(Sz), Sz >= 0 -> ok;
- #c_literal{val=undefined} -> ok;
- #c_literal{val=all} -> ok;
- _ -> throw(bad_binary)
- end,
- {#c_bitstr{val=E1,size=Size1,
- unit=#c_literal{val=Unit},
- type=#c_literal{val=Type},
- flags=#c_literal{val=Flags}},
- Eps ++ Eps2,St2}.
-
%% fun_tq(Id, [Clauses], Line, State, NameInfo) -> {Fun,[PreExp],State}.
fun_tq(Cs0, L, St0, NameInfo) ->
Arity = clause_arity(hd(Cs0)),
- {Cs1,Ceps,St1} = clauses(Cs0, St0),
+ {Cs1,St1} = clauses(Cs0, St0),
{Args,St2} = new_vars(Arity, St1),
{Ps,St3} = new_vars(Arity, St2), %Need new variables here
Anno = full_anno(L, St3),
{Name,St4} = new_fun_name(St3),
- Fc = function_clause(Ps, Anno, {Name,Arity}),
+ Fc = function_clause(Ps, Anno),
Id = {0,0,Name},
Fun = #ifun{anno=#a{anno=Anno},
id=[{id,Id}], %We KNOW!
vars=Args,clauses=Cs1,fc=Fc,name=NameInfo},
- {Fun,Ceps,St4}.
+ {Fun,[],St4}.
%% lc_tq(Line, Exp, [Qualifier], Mc, State) -> {LetRec,[PreExp],State}.
%% This TQ from Simon PJ pp 127-138.
-lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,ceps=Ceps,
+lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
acc_pat=AccPat,acc_guard=AccGuard,
skip_pat=SkipPat,tail=Tail,tail_pat=TailPat,
arg={Pre,Arg}}|Qs], Mc, St0) ->
@@ -1166,8 +1293,8 @@ lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,ceps=Ceps,
LAnno = #a{anno=LA},
F = #c_var{anno=LA,name={Name,1}},
Nc = #iapply{anno=GAnno,op=F,args=[Tail]},
- {Var,St2} = new_var(St1),
- Fc = function_clause([Var], GA, {Name,1}),
+ {[FcVar,Var],St2} = new_vars(2, St1),
+ Fc = function_clause([FcVar], GA),
TailClause = #iclause{anno=LAnno,pats=[TailPat],guard=[],body=[Mc]},
Cs0 = case {AccPat,AccGuard} of
{SkipPat,[]} ->
@@ -1193,7 +1320,7 @@ lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,ceps=Ceps,
Fun = #ifun{anno=GAnno,id=[],vars=[Var],clauses=Cs,fc=Fc},
{#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},defs=[{{Name,1},Fun}],
body=Pre ++ [#iapply{anno=GAnno,op=F,args=[Arg]}]},
- Ceps,St4};
+ [],St4};
lc_tq(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
filter_tq(Line, E, Filter, Mc, St, Qs, fun lc_tq/5);
lc_tq(Line, E0, [], Mc0, St0) ->
@@ -1218,17 +1345,18 @@ bc_tq(Line, Exp, Qs0, St0) ->
args=[Sz]}}] ++ BcPre,
{E,Pre,St}.
-bc_tq1(Line, E, [#igen{anno=GAnno,ceps=Ceps,
+bc_tq1(Line, E, [#igen{anno=GAnno,
acc_pat=AccPat,acc_guard=AccGuard,
skip_pat=SkipPat,tail=Tail,tail_pat=TailPat,
arg={Pre,Arg}}|Qs], Mc, St0) ->
{Name,St1} = new_fun_name("lbc", St0),
LA = lineno_anno(Line, St1),
LAnno = #a{anno=LA},
- {Vars=[_,AccVar],St2} = new_vars(LA, 2, St1),
+ {[_,AccVar]=Vars,St2} = new_vars(LA, 2, St1),
+ {[_,_]=FcVars,St3} = new_vars(LA, 2, St2),
F = #c_var{anno=LA,name={Name,2}},
Nc = #iapply{anno=GAnno,op=F,args=[Tail,AccVar]},
- Fc = function_clause(Vars, LA, {Name,2}),
+ Fc = function_clause(FcVars, LA),
TailClause = #iclause{anno=LAnno,pats=[TailPat,AccVar],guard=[],
body=[AccVar]},
Cs0 = case {AccPat,AccGuard} of
@@ -1241,30 +1369,30 @@ bc_tq1(Line, E, [#igen{anno=GAnno,ceps=Ceps,
pats=[SkipPat,AccVar],guard=[],body=[Nc]},
TailClause]
end,
- {Cs,St4} = case AccPat of
- nomatch ->
- %% The accumulator pattern never matches, no need
- %% for an accumulator clause.
- {Cs0,St2};
- _ ->
- {Bc,Bps,St3} = bc_tq1(Line, E, Qs, AccVar, St2),
- Body = Bps ++ [#iset{var=AccVar,arg=Bc},Nc],
- {[#iclause{anno=LAnno,
- pats=[AccPat,AccVar],guard=AccGuard,
- body=Body}|Cs0],
- St3}
- end,
+ {Cs,St} = case AccPat of
+ nomatch ->
+ %% The accumulator pattern never matches, no need
+ %% for an accumulator clause.
+ {Cs0,St3};
+ _ ->
+ {Bc,Bps,St4} = bc_tq1(Line, E, Qs, AccVar, St3),
+ Body = Bps ++ [#iset{var=AccVar,arg=Bc},Nc],
+ {[#iclause{anno=LAnno,
+ pats=[AccPat,AccVar],guard=AccGuard,
+ body=Body}|Cs0],
+ St4}
+ end,
Fun = #ifun{anno=LAnno,id=[],vars=Vars,clauses=Cs,fc=Fc},
{#iletrec{anno=LAnno#a{anno=[list_comprehension|LA]},defs=[{{Name,2},Fun}],
body=Pre ++ [#iapply{anno=LAnno,op=F,args=[Arg,Mc]}]},
- Ceps,St4};
+ [],St};
bc_tq1(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
filter_tq(Line, E, Filter, Mc, St, Qs, fun bc_tq1/5);
bc_tq1(_, {bin,Bl,Elements}, [], AccVar, St0) ->
bc_tq_build(Bl, [], AccVar, Elements, St0);
bc_tq1(Line, E0, [], AccVar, St0) ->
BsFlags = [binary,{unit,1}],
- BsSize = {atom,Line,all},
+ BsSize = make_all_size(Line),
{E1,Pre0,St1} = safe(E0, St0),
case E1 of
#c_var{name=VarName} ->
@@ -1287,7 +1415,7 @@ bc_tq1(Line, E0, [], AccVar, St0) ->
end.
bc_tq_build(Line, Pre0, #c_var{name=AccVar}, Elements0, St0) ->
- Elements = [{bin_element,Line,{var,Line,AccVar},{atom,Line,all},
+ Elements = [{bin_element,Line,{var,Line,AccVar},make_all_size(Line),
[binary,{unit,1}]}|Elements0],
{E,Pre,St} = expr({bin,Line,Elements}, St0),
#a{anno=A} = Anno0 = get_anno(E),
@@ -1399,7 +1527,7 @@ get_qual_anno(Abstract) -> element(2, Abstract).
generator(Line, {generate,Lg,P0,E}, Gs, St0) ->
LA = lineno_anno(Line, St0),
GA = lineno_anno(Lg, St0),
- {Head,Ceps,St1} = list_gen_pattern(P0, Line, St0),
+ {Head,St1} = list_gen_pattern(P0, Line, St0),
{[Tail,Skip],St2} = new_vars(2, St1),
{Cg,St3} = lc_guard_tests(Gs, St2),
{AccPat,SkipPat} = case Head of
@@ -1419,44 +1547,55 @@ generator(Line, {generate,Lg,P0,E}, Gs, St0) ->
ann_c_cons(LA, Skip, Tail)}
end,
{Ce,Pre,St4} = safe(E, St3),
- Gen = #igen{anno=#a{anno=GA},ceps=Ceps,
+ Gen = #igen{anno=#a{anno=GA},
acc_pat=AccPat,acc_guard=Cg,skip_pat=SkipPat,
tail=Tail,tail_pat=#c_literal{anno=LA,val=[]},arg={Pre,Ce}},
{Gen,St4};
generator(Line, {b_generate,Lg,P,E}, Gs, St0) ->
LA = lineno_anno(Line, St0),
GA = lineno_anno(Lg, St0),
- {Cp = #c_binary{segments=Segs},[],St1} = pattern(P, St0),
-
- %% The function append_tail_segment/2 keeps variable patterns as-is, making
- %% it possible to have the same skip clause removal as with list generators.
- {AccSegs,Tail,TailSeg,St2} = append_tail_segment(Segs, St1),
- AccPat = Cp#c_binary{segments=AccSegs},
- {Cg,St3} = lc_guard_tests(Gs, St2),
- {SkipSegs,St4} = emasculate_segments(AccSegs, St3),
- SkipPat = Cp#c_binary{segments=SkipSegs},
- {Ce,Pre,St5} = safe(E, St4),
- Gen = #igen{anno=#a{anno=GA},acc_pat=AccPat,acc_guard=Cg,skip_pat=SkipPat,
- tail=Tail,tail_pat=#c_binary{anno=LA,segments=[TailSeg]},
- arg={Pre,Ce}},
- {Gen,St5}.
+ try pattern(P, St0) of
+ {#ibinary{segments=Segs}=Cp,St1} ->
+ %% The function append_tail_segment/2 keeps variable
+ %% patterns as-is, making it possible to have the same
+ %% skip clause removal as with list generators.
+ {AccSegs,Tail,TailSeg,St2} = append_tail_segment(Segs, St1),
+ AccPat = Cp#ibinary{segments=AccSegs},
+ {Cg,St3} = lc_guard_tests(Gs, St2),
+ {SkipSegs,St4} = emasculate_segments(AccSegs, St3),
+ SkipPat = Cp#ibinary{segments=SkipSegs},
+ {Ce,Pre,St5} = safe(E, St4),
+ Gen = #igen{anno=#a{anno=GA},acc_pat=AccPat,acc_guard=Cg,
+ skip_pat=SkipPat,tail=Tail,
+ tail_pat=#ibinary{anno=#a{anno=LA},segments=[TailSeg]},
+ arg={Pre,Ce}},
+ {Gen,St5}
+ catch
+ throw:nomatch ->
+ {Ce,Pre,St1} = safe(E, St0),
+ Gen = #igen{anno=#a{anno=GA},acc_pat=nomatch,acc_guard=[],
+ skip_pat=nomatch,
+ tail_pat=#c_var{name='_'},
+ arg={Pre,Ce}},
+ {Gen,St1}
+ end.
append_tail_segment(Segs, St0) ->
{Var,St} = new_var(St0),
- Tail = #c_bitstr{val=Var,size=#c_literal{val=all},
- unit=#c_literal{val=1},
- type=#c_literal{val=binary},
- flags=#c_literal{val=[unsigned,big]}},
+ Tail = #ibitstr{val=Var,size=[#c_literal{val=all}],
+ unit=#c_literal{val=1},
+ type=#c_literal{val=binary},
+ flags=#c_literal{val=[unsigned,big]}},
{Segs++[Tail],Var,Tail,St}.
emasculate_segments(Segs, St) ->
emasculate_segments(Segs, St, []).
-emasculate_segments([#c_bitstr{val=#c_var{}}=B|Rest], St, Acc) ->
+emasculate_segments([#ibitstr{val=#c_var{}}=B|Rest], St, Acc) ->
emasculate_segments(Rest, St, [B|Acc]);
emasculate_segments([B|Rest], St0, Acc) ->
{Var,St1} = new_var(St0),
- emasculate_segments(Rest, St1, [B#c_bitstr{val=Var}|Acc]);
+ emasculate_segments(Rest, St1, [B#ibitstr{val=Var}|Acc]);
emasculate_segments([], St, Acc) ->
{reverse(Acc),St}.
@@ -1468,9 +1607,9 @@ lc_guard_tests(Gs0, St0) ->
list_gen_pattern(P0, Line, St) ->
try
- pattern(P0,St)
- catch
- nomatch -> {nomatch,[],add_warning(Line, nomatch, St)}
+ pattern(P0, St)
+ catch
+ nomatch -> {nomatch,add_warning(Line, nomatch, St)}
end.
%%%
@@ -1502,7 +1641,9 @@ bc_initial_size(E0, Q, St0) ->
end
catch
throw:impossible ->
- {#c_literal{val=256},[],St0}
+ {#c_literal{val=256},[],St0};
+ throw:nomatch ->
+ {#c_literal{val=1},[],St0}
end.
bc_elem_size({bin,_,El}, St0) ->
@@ -1576,8 +1717,17 @@ bc_add_list_1([H|T], Pre, E, St0) ->
bc_add_list_1([], Pre, E, St) ->
{E,reverse(Pre),St}.
-bc_gen_size(Q, EVs, St) ->
- bc_gen_size_1(Q, EVs, #c_literal{val=1}, [], St).
+bc_gen_size([_]=Q, EVs, St) ->
+ %% Single generator.
+ bc_gen_size_1(Q, EVs, #c_literal{val=1}, [], St);
+bc_gen_size(_, _, _) ->
+ %% There are multiple generators (or there are filters).
+ %% To avoid introducing unbound variables in the size
+ %% calculation when one generator references a
+ %% variable bound by a previous generator, we will
+ %% not do any size calculation. This issue will be
+ %% handled in a cleaner way in OTP 24.
+ throw(impossible).
bc_gen_size_1([{generate,L,El,Gen}|Qs], EVs, E0, Pre0, St0) ->
bc_verify_non_filtering(El, EVs),
@@ -1737,18 +1887,6 @@ force_novars(#c_map{}=Bin, St) -> {Bin,[],St};
force_novars(Ce, St) ->
force_safe(Ce, St).
-
-%% safe_pattern_expr(Expr, State) -> {Cexpr,[PreExpr],State}.
-%% only literals and variables are safe expressions in patterns
-safe_pattern_expr(E,St0) ->
- case safe(E,St0) of
- {#c_var{},_,_}=Safe -> Safe;
- {#c_literal{},_,_}=Safe -> Safe;
- {Ce,Eps,St1} ->
- {V,St2} = new_var(St1),
- {V,Eps++[#iset{var=V,arg=Ce}],St2}
- end.
-
%% safe(Expr, State) -> {Safe,[PreExpr],State}.
%% Generate an internal safe expression. These are simples without
%% binaries which can fail. At this level we do not need to do a
@@ -1759,15 +1897,6 @@ safe(E0, St0) ->
{Se,Sps,St2} = force_safe(E1, St1),
{Se,Eps ++ Sps,St2}.
-safe_fun(A0, E0, St0) ->
- case safe(E0, St0) of
- {#c_var{name={_,A1}}=E1,Eps,St1} when A1 =/= A0 ->
- {V,St2} = new_var(St1),
- {V,Eps ++ [#iset{var=V,arg=E1}],St2};
- Result ->
- Result
- end.
-
safe_list(Es, St) ->
foldr(fun (E, {Ces,Esp,St0}) ->
{Ce,Ep,St1} = safe(E, St0),
@@ -1825,44 +1954,33 @@ fold_match({match,L,P0,E0}, P) ->
fold_match(E, P) -> {P,E}.
%% pattern(Pattern, State) -> {CorePat,[PreExp],State}.
-%% Transform a pattern by removing line numbers. We also normalise
-%% aliases in patterns to standard form, {alias,Pat,[Var]}.
-%%
-%% In patterns we may have expressions
-%% 1) Binaries -> #c_bitstr{size=Expr}
-%% 2) Maps -> #c_map_pair{key=Expr}
-%%
-%% Both of these may generate pre-expressions since only bound variables
-%% or literals are allowed for these in core patterns.
-%%
-%% Therefor, we need to drag both the state and the collection of pre-expression
-%% around in the whole pattern transformation tree.
-
-pattern({var,L,V}, St) -> {#c_var{anno=lineno_anno(L, St),name=V},[],St};
-pattern({char,L,C}, St) -> {#c_literal{anno=lineno_anno(L, St),val=C},[],St};
-pattern({integer,L,I}, St) -> {#c_literal{anno=lineno_anno(L, St),val=I},[],St};
-pattern({float,L,F}, St) -> {#c_literal{anno=lineno_anno(L, St),val=F},[],St};
-pattern({atom,L,A}, St) -> {#c_literal{anno=lineno_anno(L, St),val=A},[],St};
-pattern({string,L,S}, St) -> {#c_literal{anno=lineno_anno(L, St),val=S},[],St};
-pattern({nil,L}, St) -> {#c_literal{anno=lineno_anno(L, St),val=[]},[],St};
+%% Transform a pattern by removing line numbers. We also normalise
+%% aliases in patterns to standard form: {alias,Pat,[Var]}.
+
+pattern({var,L,V}, St) -> {#c_var{anno=lineno_anno(L, St),name=V},St};
+pattern({char,L,C}, St) -> {#c_literal{anno=lineno_anno(L, St),val=C},St};
+pattern({integer,L,I}, St) -> {#c_literal{anno=lineno_anno(L, St),val=I},St};
+pattern({float,L,F}, St) -> {#c_literal{anno=lineno_anno(L, St),val=F},St};
+pattern({atom,L,A}, St) -> {#c_literal{anno=lineno_anno(L, St),val=A},St};
+pattern({string,L,S}, St) -> {#c_literal{anno=lineno_anno(L, St),val=S},St};
+pattern({nil,L}, St) -> {#c_literal{anno=lineno_anno(L, St),val=[]},St};
pattern({cons,L,H,T}, St) ->
- {Ph,Eps1,St1} = pattern(H, St),
- {Pt,Eps2,St2} = pattern(T, St1),
- {annotate_cons(lineno_anno(L, St), Ph, Pt, St2),Eps1++Eps2,St2};
+ {Ph,St1} = pattern(H, St),
+ {Pt,St2} = pattern(T, St1),
+ {annotate_cons(lineno_anno(L, St), Ph, Pt, St2),St2};
pattern({tuple,L,Ps}, St) ->
- {Ps1,Eps,St1} = pattern_list(Ps,St),
- {annotate_tuple(record_anno(L, St), Ps1, St),Eps,St1};
+ {Ps1,St1} = pattern_list(Ps, St),
+ {annotate_tuple(record_anno(L, St), Ps1, St),St1};
pattern({map,L,Pairs}, St0) ->
- {Ps,Eps,St1} = pattern_map_pairs(Pairs, St0),
- {#c_map{anno=lineno_anno(L, St1),es=Ps,is_pat=true},Eps,St1};
-pattern({bin,L,Ps}, St) ->
- %% We don't create a #ibinary record here, since there is
- %% no need to hold any used/new annotations in a pattern.
- {#c_binary{anno=lineno_anno(L, St),segments=pat_bin(Ps, St)},[],St};
+ {Ps,St1} = pattern_map_pairs(Pairs, St0),
+ {#imap{anno=#a{anno=lineno_anno(L, St1)},es=Ps},St1};
+pattern({bin,L,Ps}, St0) ->
+ {Segments,St} = pat_bin(Ps, St0),
+ {#ibinary{anno=#a{anno=lineno_anno(L, St)},segments=Segments},St};
pattern({match,_,P1,P2}, St) ->
- {Cp1,Eps1,St1} = pattern(P1,St),
- {Cp2,Eps2,St2} = pattern(P2,St1),
- {pat_alias(Cp1,Cp2),Eps1++Eps2,St2};
+ {Cp1,St1} = pattern(P1, St),
+ {Cp2,St2} = pattern(P2, St1),
+ {pat_alias(Cp1, Cp2),St2};
%% Evaluate compile-time expressions.
pattern({op,_,'++',{nil,_},R}, St) ->
pattern(R, St);
@@ -1876,57 +1994,86 @@ pattern({op,_Line,_Op,_L,_R}=Op, St) ->
pattern(erl_eval:partial_eval(Op), St).
%% pattern_map_pairs([MapFieldExact],State) -> [#c_map_pairs{}]
-pattern_map_pairs(Ps, St) ->
- %% check literal key uniqueness
- %% - guaranteed via aliasing map pairs
- %% pattern all pairs in two steps
- %% 1) Construct Core Pattern
- %% 2) Alias Keys in Core Pattern
- {CMapPairs, {Eps,St1}} = lists:mapfoldl(fun
- (P,{EpsM,Sti0}) ->
- {CMapPair,EpsP,Sti1} = pattern_map_pair(P,Sti0),
- {CMapPair, {EpsM++EpsP,Sti1}}
- end, {[],St}, Ps),
- {pat_alias_map_pairs(CMapPairs),Eps,St1}.
+pattern_map_pairs(Ps, St0) ->
+ {CMapPairs,St1} = mapfoldl(fun pattern_map_pair/2, St0, Ps),
+ {pat_alias_map_pairs(CMapPairs),St1}.
pattern_map_pair({map_field_exact,L,K,V}, St0) ->
- {Ck,EpsK,St1} = safe_pattern_expr(K, St0),
- {Cv,EpsV,St2} = pattern(V, St1),
- {#c_map_pair{anno=lineno_anno(L, St2),
- op=#c_literal{val=exact},
- key=Ck,
- val=Cv},EpsK++EpsV,St2}.
+ Ck0 = erl_eval:partial_eval(K),
+ {Ck,St1} = exprs([Ck0], St0),
+ {Cv,St2} = pattern(V, St1),
+ {#imappair{anno=#a{anno=lineno_anno(L, St2)},
+ op=#c_literal{val=exact},
+ key=Ck,
+ val=Cv},St2}.
pat_alias_map_pairs(Ps) ->
- D = foldl(fun(#c_map_pair{key=K0}=Pair, D0) ->
- K = cerl:set_ann(K0, []),
- dict:append(K, Pair, D0)
- end, dict:new(), Ps),
- pat_alias_map_pairs_1(dict:to_list(D)).
-
-pat_alias_map_pairs_1([{_,[#c_map_pair{val=V0}=Pair|Vs]}|T]) ->
- V = foldl(fun(#c_map_pair{val=V}, Pat) ->
+ D0 = foldl(fun(#imappair{key=K0}=Pair, A) ->
+ K = map_sort_key(K0, A),
+ case A of
+ #{K:=Aliases} ->
+ A#{K:=[Pair|Aliases]};
+ #{} ->
+ A#{K=>[Pair]}
+ end
+ end, #{}, Ps),
+ %% We must sort to ensure that the order remains consistent
+ %% between compilations.
+ D = sort(maps:to_list(D0)),
+ pat_alias_map_pairs_1(D).
+
+pat_alias_map_pairs_1([{_,[#imappair{val=V0}=Pair|Vs]}|T]) ->
+ V = foldl(fun(#imappair{val=V}, Pat) ->
pat_alias(V, Pat)
end, V0, Vs),
- [Pair#c_map_pair{val=V}|pat_alias_map_pairs_1(T)];
+ [Pair#imappair{val=V}|pat_alias_map_pairs_1(T)];
pat_alias_map_pairs_1([]) -> [].
+map_sort_key(Key, KeyMap) ->
+ case Key of
+ [#c_literal{}=Lit] ->
+ {atomic,cerl:set_ann(Lit, [])};
+ [#c_var{}=Var] ->
+ {atomic,cerl:set_ann(Var, [])};
+ _ ->
+ {expr,map_size(KeyMap)}
+ end.
+
%% pat_bin([BinElement], State) -> [BinSeg].
-pat_bin(Ps, St) -> [pat_segment(P, St) || P <- bin_expand_strings(Ps)].
+pat_bin(Ps0, St) ->
+ Ps = pat_bin_expand_strings(Ps0),
+ pat_segments(Ps, St).
+
+pat_bin_expand_strings(Es0) ->
+ foldr(fun ({bin_element,Line,{string,_,S},Sz,Ts}, Es1) ->
+ foldr(
+ fun (C, Es) ->
+ [{bin_element,Line,{char,Line,C},Sz,Ts}|Es]
+ end, Es1, S);
+ (E, Es) ->
+ [E|Es]
+ end, [], Es0).
+
+pat_segments([P0|Ps0], St0) ->
+ {P,St1} = pat_segment(P0, St0),
+ {Ps,St2} = pat_segments(Ps0, St1),
+ {[P|Ps],St2};
+pat_segments([], St) -> {[],St}.
pat_segment({bin_element,L,Val,Size0,Type0}, St) ->
- {Size,Type1} = make_bit_type(L, Size0, Type0),
+ {Size1,Type1} = make_bit_type(L, Size0, Type0),
[Type,{unit,Unit}|Flags] = Type1,
Anno = lineno_anno(L, St),
- {Pval0,[],St1} = pattern(Val, St),
+ {Pval0,St1} = pattern(Val, St),
Pval = coerce_to_float(Pval0, Type0),
- {Psize,[],_St2} = pattern(Size, St1),
- #c_bitstr{anno=Anno,
- val=Pval,size=Psize,
- unit=#c_literal{val=Unit},
- type=#c_literal{val=Type},
- flags=#c_literal{val=Flags}}.
+ Size = erl_eval:partial_eval(Size1),
+ {Psize,St2} = exprs([Size], St1),
+ {#ibitstr{anno=#a{anno=Anno},
+ val=Pval,size=Psize,
+ unit=#c_literal{val=Unit},
+ type=#c_literal{val=Type},
+ flags=#c_literal{val=Flags}},St2}.
coerce_to_float(#c_literal{val=Int}=E, [float|_]) when is_integer(Int) ->
try
@@ -1964,8 +2111,8 @@ pat_alias(#c_alias{var=#c_var{name=V1}=Var1,pat=P1},
pat_alias(#c_alias{var=#c_var{}=Var,pat=P1}, P2) ->
#c_alias{var=Var,pat=pat_alias(P1, P2)};
-pat_alias(#c_map{es=Es1}=M, #c_map{es=Es2}) ->
- M#c_map{es=pat_alias_map_pairs(Es1 ++ Es2)};
+pat_alias(#imap{es=Es1}=M, #imap{es=Es2}) ->
+ M#imap{es=pat_alias_map_pairs(Es1 ++ Es2)};
pat_alias(P1, #c_var{}=Var) ->
#c_alias{var=Var,pat=P1};
@@ -1999,11 +2146,11 @@ pat_alias_list(_, _) -> throw(nomatch).
%% pattern_list([P], State) -> {[P],Exprs,St}
pattern_list([P0|Ps0], St0) ->
- {P1,Eps,St1} = pattern(P0, St0),
- {Ps1,Epsl,St2} = pattern_list(Ps0, St1),
- {[P1|Ps1], Eps ++ Epsl, St2};
+ {P1,St1} = pattern(P0, St0),
+ {Ps1,St2} = pattern_list(Ps0, St1),
+ {[P1|Ps1],St2};
pattern_list([], St) ->
- {[],[],St}.
+ {[],St}.
string_to_conses(Line, Cs, Tail) ->
foldr(fun (C, T) -> {cons,Line,{char,Line,C},T} end, Tail, Cs).
@@ -2049,9 +2196,8 @@ new_vars_1(N, Anno, St0, Vs) when N > 0 ->
new_vars_1(N-1, Anno, St1, [V|Vs]);
new_vars_1(0, _, St, Vs) -> {Vs,St}.
-function_clause(Ps, LineAnno, Name) ->
- FcAnno = [{function_name,Name}|LineAnno],
- fail_clause(Ps, FcAnno,
+function_clause(Ps, LineAnno) ->
+ fail_clause(Ps, LineAnno,
ann_c_tuple(LineAnno, [#c_literal{val=function_clause}|Ps])).
fail_clause(Pats, Anno, Arg) ->
@@ -2065,8 +2211,8 @@ right_assoc({op,L1,Op,{op,L2,Op,E1,E2},E3}, Op) ->
right_assoc({op,L2,Op,E1,{op,L1,Op,E2,E3}}, Op);
right_assoc(E, _Op) -> E.
-annotate_tuple(A, Es, St) ->
- case member(dialyzer, St#core.opts) of
+annotate_tuple(A, Es, #core{dialyzer=Dialyzer}) ->
+ case Dialyzer of
true ->
%% Do not coalesce constant tuple elements. A Hack.
Node = cerl:ann_c_tuple(A, [cerl:c_var(any)]),
@@ -2075,8 +2221,8 @@ annotate_tuple(A, Es, St) ->
ann_c_tuple(A, Es)
end.
-annotate_cons(A, H, T, St) ->
- case member(dialyzer, St#core.opts) of
+annotate_cons(A, H, T, #core{dialyzer=Dialyzer}) ->
+ case Dialyzer of
true ->
%% Do not coalesce constant conses. A Hack.
Node= cerl:ann_c_cons(A, cerl:c_var(any), cerl:c_var(any)),
@@ -2087,6 +2233,35 @@ annotate_cons(A, H, T, St) ->
ubody(B, St) -> uexpr(B, [], St).
+%% ufun_clauses([Lclause], [KnownVar], State) -> {[Lclause],State}.
+
+ufun_clauses(Lcs, Ks, St0) ->
+ mapfoldl(fun (Lc, St) -> ufun_clause(Lc, Ks, St) end, St0, Lcs).
+
+%% ufun_clause(Lclause, [KnownVar], State) -> {Lclause,State}.
+
+ufun_clause(Cl0, Ks, St0) ->
+ %% Since variables in fun heads shadow previous variables
+ %% with the same name, we used to send an empty list as the
+ %% known variables when doing liveness analysis of the patterns
+ %% (in the upattern functions).
+ %%
+ %% With the introduction of expressions in size for binary
+ %% segments and in map keys, all known variables must be
+ %% available when analysing those expressions, or some variables
+ %% might not be seen as used if, for example, the expression includes
+ %% a case construct.
+ %%
+ %% Therefore, we will send in the complete list of known variables
+ %% when doing liveness analysis of patterns. This is
+ %% safe because any shadowing variables in a fun head has
+ %% been renamed.
+
+ {Cl1,Pvs,Used,_,St1} = do_uclause(Cl0, Ks, St0),
+ A0 = get_anno(Cl1),
+ A = A0#a{us=subtract(Used, Pvs),ns=[]},
+ {Cl1#iclause{anno=A},St1}.
+
%% uclauses([Lclause], [KnownVar], State) -> {[Lclause],State}.
uclauses(Lcs, Ks, St0) ->
@@ -2095,13 +2270,13 @@ uclauses(Lcs, Ks, St0) ->
%% uclause(Lclause, [KnownVar], State) -> {Lclause,State}.
uclause(Cl0, Ks, St0) ->
- {Cl1,_Pvs,Used,New,St1} = uclause(Cl0, Ks, Ks, St0),
+ {Cl1,_Pvs,Used,New,St1} = do_uclause(Cl0, Ks, St0),
A0 = get_anno(Cl1),
A = A0#a{us=Used,ns=New},
{Cl1#iclause{anno=A},St1}.
-uclause(#iclause{anno=Anno,pats=Ps0,guard=G0,body=B0}, Pks, Ks0, St0) ->
- {Ps1,Pg,Pvs,Pus,St1} = upattern_list(Ps0, Pks, St0),
+do_uclause(#iclause{anno=Anno,pats=Ps0,guard=G0,body=B0}, Ks0, St0) ->
+ {Ps1,Pg,Pvs,Pus,St1} = upattern_list(Ps0, Ks0, St0),
Pu = union(Pus, intersection(Pvs, Ks0)),
Pn = subtract(Pvs, Pu),
Ks1 = union(Pn, Ks0),
@@ -2210,18 +2385,26 @@ uexpr(#icase{anno=#a{anno=Anno}=A,args=As0,clauses=Cs0,fc=Fc0}, Ks, St0) ->
false -> new_in_all(Cs1)
end,
{#icase{anno=A#a{us=Used,ns=New},args=As1,clauses=Cs1,fc=Fc1},St3};
-uexpr(#ifun{anno=A0,id=Id,vars=As,clauses=Cs0,fc=Fc0,name=Name}, Ks0, St0) ->
+uexpr(#ifun{anno=A0,id=Id,vars=As,clauses=Cs0,fc=Fc0,name=Name}=Fun0, Ks0, St0) ->
+ {Fun1,St2} = case Ks0 of
+ [] ->
+ {Fun0,St0};
+ [_|_] ->
+ {Cs1,St1} = rename_shadowing_clauses(Cs0, Ks0, St0),
+ {Fun0#ifun{clauses=Cs1},St1}
+ end,
+ #ifun{clauses=Cs2} = Fun1,
Avs = lit_list_vars(As),
Ks1 = case Name of
unnamed -> Ks0;
{named,FName} -> union(subtract([FName], Avs), Ks0)
end,
Ks2 = union(Avs, Ks1),
- {Cs1,St1} = ufun_clauses(Cs0, Ks2, St0),
- {Fc1,St2} = ufun_clause(Fc0, Ks2, St1),
- Used = subtract(intersection(used_in_any(Cs1), Ks1), Avs),
+ {Cs3,St3} = ufun_clauses(Cs2, Ks2, St2),
+ {Fc1,St4} = ufun_clause(Fc0, Ks2, St3),
+ Used = subtract(intersection(used_in_any(Cs3), Ks1), Avs),
A1 = A0#a{us=Used,ns=[]},
- {#ifun{anno=A1,id=Id,vars=As,clauses=Cs1,fc=Fc1,name=Name},St2};
+ {#ifun{anno=A1,id=Id,vars=As,clauses=Cs3,fc=Fc1,name=Name},St4};
uexpr(#iapply{anno=A,op=Op,args=As}, _, St) ->
Used = union(lit_vars(Op), lit_list_vars(As)),
{#iapply{anno=A#a{us=Used},op=Op,args=As},St};
@@ -2278,19 +2461,6 @@ uexpr(Simple, _, St) ->
uexpr_list(Les0, Ks, St0) ->
mapfoldl(fun (Le, St) -> uexpr(Le, Ks, St) end, St0, Les0).
-%% ufun_clauses([Lclause], [KnownVar], State) -> {[Lclause],State}.
-
-ufun_clauses(Lcs, Ks, St0) ->
- mapfoldl(fun (Lc, St) -> ufun_clause(Lc, Ks, St) end, St0, Lcs).
-
-%% ufun_clause(Lclause, [KnownVar], State) -> {Lclause,State}.
-
-ufun_clause(Cl0, Ks, St0) ->
- {Cl1,Pvs,Used,_,St1} = uclause(Cl0, [], Ks, St0),
- A0 = get_anno(Cl1),
- A = A0#a{us=subtract(intersection(Used, Ks), Pvs),ns=[]},
- {Cl1#iclause{anno=A},St1}.
-
%% upattern(Pat, [KnownVar], State) ->
%% {Pat,[GuardTest],[NewVar],[UsedVar],State}.
@@ -2318,20 +2488,17 @@ upattern(#c_cons{hd=H0,tl=T0}=Cons, Ks, St0) ->
upattern(#c_tuple{es=Es0}=Tuple, Ks, St0) ->
{Es1,Esg,Esv,Eus,St1} = upattern_list(Es0, Ks, St0),
{Tuple#c_tuple{es=Es1},Esg,Esv,Eus,St1};
-upattern(#c_map{es=Es0}=Map, Ks, St0) ->
+upattern(#imap{es=Es0}=Map, Ks, St0) ->
{Es1,Esg,Esv,Eus,St1} = upattern_list(Es0, Ks, St0),
- {Map#c_map{es=Es1},Esg,Esv,Eus,St1};
-upattern(#c_map_pair{op=#c_literal{val=exact},key=K0,val=V0}=Pair,Ks,St0) ->
+ {Map#imap{es=Es1},Esg,Esv,Eus,St1};
+upattern(#imappair{op=#c_literal{val=exact},key=K0,val=V0}=Pair,Ks,St0) ->
{V,Vg,Vn,Vu,St1} = upattern(V0, Ks, St0),
- % A variable key must be considered used here
- Ku = case K0 of
- #c_var{name=Name} -> [Name];
- _ -> []
- end,
- {Pair#c_map_pair{val=V},Vg,Vn,union(Ku,Vu),St1};
-upattern(#c_binary{segments=Es0}=Bin, Ks, St0) ->
+ {K,St2} = uexprs(K0, Ks, St1),
+ Ku = used_in_expr(K),
+ {Pair#imappair{key=K,val=V},Vg,Vn,union(Ku, Vu),St2};
+upattern(#ibinary{segments=Es0}=Bin, Ks, St0) ->
{Es1,Esg,Esv,Eus,St1} = upat_bin(Es0, Ks, St0),
- {Bin#c_binary{segments=Es1},Esg,Esv,Eus,St1};
+ {Bin#ibinary{segments=Es1},Esg,Esv,Eus,St1};
upattern(#c_alias{var=V0,pat=P0}=Alias, Ks, St0) ->
{V1,Vg,Vv,Vu,St1} = upattern(V0, Ks, St0),
{P1,Pg,Pv,Pu,St2} = upattern(P0, union(Vv, Ks), St1),
@@ -2377,7 +2544,7 @@ upat_bin([], _, _, St) -> {[],[],[],[],St}.
%% upat_element(Segment, [KnownVar], [LocalVar], State) ->
%% {Segment,[GuardTest],[NewVar],[UsedVar],[LocalVar],State}
-upat_element(#c_bitstr{val=H0,size=Sz0}=Seg, Ks, Bs0, St0) ->
+upat_element(#ibitstr{val=H0,size=Sz0}=Seg, Ks, Bs0, St0) ->
{H1,Hg,Hv,[],St1} = upattern(H0, Ks, St0),
Bs1 = case H0 of
#c_var{name=Hname} ->
@@ -2390,13 +2557,22 @@ upat_element(#c_bitstr{val=H0,size=Sz0}=Seg, Ks, Bs0, St0) ->
_ ->
Bs0
end,
- {Sz1,Us} = case Sz0 of
- #c_var{name=Vname} ->
- rename_bitstr_size(Vname, Bs0);
- _Other ->
- {Sz0,[]}
- end,
- {Seg#c_bitstr{val=H1,size=Sz1},Hg,Hv,Us,Bs1,St1}.
+ case Sz0 of
+ [#c_var{name=Vname}] ->
+ {Sz1,Us} = rename_bitstr_size(Vname, Bs0),
+ {Sz2,St2} = uexprs([Sz1], Ks, St1),
+ {Seg#ibitstr{val=H1,size=Sz2},Hg,Hv,Us,Bs1,St2};
+ [#c_literal{}] ->
+ {Sz1,St2} = uexprs(Sz0, Ks, St1),
+ Us = [],
+ {Seg#ibitstr{val=H1,size=Sz1},Hg,Hv,Us,Bs1,St2};
+ Expr when is_list(Expr) ->
+ Sz1 = [#iset{var=#c_var{name=Old},arg=#c_var{name=New}} ||
+ {Old,New} <- Bs0] ++ Expr,
+ {Sz2,St2} = uexprs(Sz1, Ks, St1),
+ Us = used_in_expr(Sz2),
+ {Seg#ibitstr{val=H1,size=Sz2},Hg,Hv,Us,Bs1,St2}
+ end.
rename_bitstr_size(V, [{V,N}|_]) ->
New = #c_var{name=N},
@@ -2406,7 +2582,13 @@ rename_bitstr_size(V, [_|Rest]) ->
rename_bitstr_size(V, []) ->
Old = #c_var{name=V},
{Old,[V]}.
-
+
+used_in_expr([Le|Les]) ->
+ #a{us=Us,ns=Ns} = get_anno(Le),
+ Used = used_in_expr(Les),
+ union(Us, subtract(Used, Ns));
+used_in_expr([]) -> [].
+
used_in_any(Les) ->
foldl(fun (Le, Ns) -> union((get_anno(Le))#a.us, Ns) end,
[], Les).
@@ -2420,6 +2602,114 @@ new_in_all([Le|Les]) ->
(get_anno(Le))#a.ns, Les);
new_in_all([]) -> [].
+%%%
+%%% Rename shadowing variables in fun heads.
+%%%
+%%% Pattern variables in fun heads always shadow variables bound in
+%%% the enclosing environment. Because that is the way that variables
+%%% behave in Core Erlang, there was previously no need to rename
+%%% the variables.
+%%%
+%%% However, to support splitting of patterns and/or pattern matching
+%%% compilation in Core Erlang, there is a need to rename all
+%%% shadowing variables to avoid changing the semantics of the Erlang
+%%% program.
+%%%
+
+rename_shadowing_clauses([C0|Cs0], Ks, St0) ->
+ {C,St1} = rename_shadowing_clause(C0, Ks, St0),
+ {Cs,St} = rename_shadowing_clauses(Cs0, Ks, St1),
+ {[C|Cs],St};
+rename_shadowing_clauses([], _Ks, St) ->
+ {[],St}.
+
+rename_shadowing_clause(#iclause{pats=Ps0,guard=G0,body=B0}=C, Ks, St0) ->
+ Subs = {[],[]},
+ {Ps,{_Isub,Osub},St} = ren_pats(Ps0, Ks, Subs, St0),
+ G = case G0 of
+ [] -> G0;
+ [_|_] -> Osub ++ G0
+ end,
+ B = Osub ++ B0,
+ {C#iclause{pats=Ps,guard=G,body=B},St}.
+
+ren_pats([P0|Ps0], Ks, {_,_}=Subs0, St0) ->
+ {P,Subs1,St1} = ren_pat(P0, Ks, Subs0, St0),
+ {Ps,Subs,St} = ren_pats(Ps0, Ks, Subs1, St1),
+ {[P|Ps],Subs,St};
+ren_pats([], _Ks, {_,_}=Subs, St) ->
+ {[],Subs,St}.
+
+ren_pat(#c_var{name='_'}=P, _Ks, Subs, St) ->
+ {P,Subs,St};
+ren_pat(#c_var{name=V}=Old, Ks, {Isub0,Osub0}=Subs, St0) ->
+ case member(V, Ks) of
+ true ->
+ case ren_is_subst(V, Osub0) of
+ {yes,New} ->
+ {New,Subs,St0};
+ no ->
+ {New,St} = new_var(St0),
+ Osub = [#iset{var=Old,arg=New}|Osub0],
+ {New,{Isub0,Osub},St}
+ end;
+ false ->
+ {Old,Subs,St0}
+ end;
+ren_pat(#c_literal{}=P, _Ks, {_,_}=Subs, St) ->
+ {P,Subs,St};
+ren_pat(#c_alias{var=Var0,pat=Pat0}=Alias, Ks, {_,_}=Subs0, St0) ->
+ {Var,Subs1,St1} = ren_pat(Var0, Ks, Subs0, St0),
+ {Pat,Subs,St} = ren_pat(Pat0, Ks, Subs1, St1),
+ {Alias#c_alias{var=Var,pat=Pat},Subs,St};
+ren_pat(#imap{es=Es0}=Map, Ks, {_,_}=Subs0, St0) ->
+ {Es,Subs,St} = ren_pat_map(Es0, Ks, Subs0, St0),
+ {Map#imap{es=Es},Subs,St};
+ren_pat(#ibinary{segments=Es0}=P, Ks, {Isub,Osub0}, St0) ->
+ {Es,_Isub,Osub,St} = ren_pat_bin(Es0, Ks, Isub, Osub0, St0),
+ {P#ibinary{segments=Es},{Isub,Osub},St};
+ren_pat(P, Ks0, {_,_}=Subs0, St0) ->
+ Es0 = cerl:data_es(P),
+ {Es,Subs,St} = ren_pats(Es0, Ks0, Subs0, St0),
+ {cerl:make_data(cerl:data_type(P), Es),Subs,St}.
+
+ren_pat_bin([#ibitstr{val=Val0,size=Sz0}=E|Es0], Ks, Isub0, Osub0, St0) ->
+ Sz = ren_get_subst(Sz0, Isub0),
+ {Val,{_,Osub1},St1} = ren_pat(Val0, Ks, {Isub0,Osub0}, St0),
+ Isub1 = case Val0 of
+ #c_var{} ->
+ [#iset{var=Val0,arg=Val}|Isub0];
+ _ ->
+ Isub0
+ end,
+ {Es,Isub,Osub,St} = ren_pat_bin(Es0, Ks, Isub1, Osub1, St1),
+ {[E#ibitstr{val=Val,size=Sz}|Es],Isub,Osub,St};
+ren_pat_bin([], _Ks, Isub, Osub, St) ->
+ {[],Isub,Osub,St}.
+
+ren_pat_map([#imappair{val=Val0}=MapPair|Es0], Ks, Subs0, St0) ->
+ {Val,Subs1,St1} = ren_pat(Val0, Ks, Subs0, St0),
+ {Es,Subs,St} = ren_pat_map(Es0, Ks, Subs1, St1),
+ {[MapPair#imappair{val=Val}|Es],Subs,St};
+ren_pat_map([], _Ks, Subs, St) ->
+ {[],Subs,St}.
+
+ren_get_subst([#c_var{name=V}]=Old, Sub) ->
+ case ren_is_subst(V, Sub) of
+ no -> Old;
+ {yes,New} -> [New]
+ end;
+ren_get_subst([#c_literal{}]=Old, _Sub) ->
+ Old;
+ren_get_subst(Expr, Sub) when is_list(Expr) ->
+ Sub ++ Expr.
+
+ren_is_subst(V, [#iset{var=#c_var{name=V},arg=Arg}|_]) ->
+ {yes,Arg};
+ren_is_subst(V, [_|Sub]) ->
+ ren_is_subst(V, Sub);
+ren_is_subst(_V, []) -> no.
+
%% The AfterVars are the variables which are used afterwards. We need
%% this to work out which variables are actually exported and used
%% from case/receive. In subblocks/clauses the AfterVars of the block
@@ -2432,7 +2722,8 @@ cbody(B0, St0) ->
%% cclause(Lclause, [AfterVar], State) -> {Cclause,State}.
%% The AfterVars are the exported variables.
-cclause(#iclause{anno=#a{anno=Anno},pats=Ps,guard=G0,body=B0}, Exp, St0) ->
+cclause(#iclause{anno=#a{anno=Anno},pats=Ps0,guard=G0,body=B0}, Exp, St0) ->
+ Ps = cpattern_list(Ps0),
{B1,_Us1,St1} = cexprs(B0, Exp, St0),
{G1,St2} = cguard(G0, St1),
{#c_clause{anno=Anno,pats=Ps,guard=G1,body=B1},St2}.
@@ -2444,7 +2735,36 @@ cguard([], St) -> {#c_literal{val=true},St};
cguard(Gs, St0) ->
{G,_,St1} = cexprs(Gs, [], St0),
{G,St1}.
-
+
+cpattern_list([P|Ps]) ->
+ [cpattern(P)|cpattern_list(Ps)];
+cpattern_list([]) -> [].
+
+cpattern(#c_alias{pat=Pat}=Alias) ->
+ Alias#c_alias{pat=cpattern(Pat)};
+cpattern(#c_cons{hd=Hd,tl=Tl}=Cons) ->
+ Cons#c_cons{hd=cpattern(Hd),tl=cpattern(Tl)};
+cpattern(#c_tuple{es=Es}=Tup) ->
+ Tup#c_tuple{es=cpattern_list(Es)};
+cpattern(#imap{anno=#a{anno=Anno},es=Es}) ->
+ #c_map{anno=Anno,es=cpat_map_pairs(Es),is_pat=true};
+cpattern(#ibinary{anno=#a{anno=Anno},segments=Segs0}) ->
+ Segs = [cpat_bin_seg(S) || S <- Segs0],
+ #c_binary{anno=Anno,segments=Segs};
+cpattern(Other) -> Other.
+
+cpat_map_pairs([#imappair{anno=#a{anno=Anno},op=Op,key=Key0,val=Val0}|T]) ->
+ {Key,_,_} = cexprs(Key0, [], #core{}),
+ Val = cpattern(Val0),
+ Pair = #c_map_pair{anno=Anno,op=Op,key=Key,val=Val},
+ [Pair|cpat_map_pairs(T)];
+cpat_map_pairs([]) -> [].
+
+cpat_bin_seg(#ibitstr{anno=#a{anno=Anno},val=E,size=Sz0,unit=Unit,
+ type=Type,flags=Flags}) ->
+ {Sz,_,_} = cexprs(Sz0, [], #core{}),
+ #c_bitstr{anno=Anno,val=E,size=Sz,unit=Unit,type=Type,flags=Flags}.
+
%% cexprs([Lexpr], [AfterVar], State) -> {Cexpr,[AfterVar],State}.
%% Must be sneaky here at the last expr when combining exports for the
%% whole sequence and exports for that expr.
@@ -2500,10 +2820,11 @@ cexpr(#icase{anno=A,args=Largs,clauses=Lcs,fc=Lfc}, As, St0) ->
{[Ca|Cas],Stb}
end, {[],St0}, Largs),
{Ccs,St2} = cclauses(Lcs, Exp, St1),
- {Cfc,St3} = cclause(Lfc, [], St2), %Never exports
+ {Cfc0,St3} = cclause(Lfc, [], St2), %Never exports
+ {Cfc,St4} = c_add_dummy_export(Cfc0, Exp, St3),
{#c_case{anno=A#a.anno,
arg=core_lib:make_values(Cargs),clauses=Ccs ++ [Cfc]},
- Exp,A#a.us,St3};
+ Exp,A#a.us,St4};
cexpr(#ireceive1{anno=A,clauses=Lcs}, As, St0) ->
Exp = intersection(A#a.ns, As), %Exports
{Ccs,St1} = cclauses(Lcs, Exp, St0),
@@ -2625,6 +2946,519 @@ c_call_erl(Fun, Args) ->
As = [compiler_generated],
cerl:ann_c_call(As, cerl:c_atom(erlang), cerl:c_atom(Fun), Args).
+
+c_add_dummy_export(#c_clause{body=B0}=C, [_|_]=Exp, St0) ->
+ %% Add dummy export in order to always return the correct number
+ %% of values for the default clause.
+ {V,St1} = new_var(St0),
+ B = #c_let{vars=[V],arg=B0,
+ body=#c_values{es=[V|duplicate(length(Exp), #c_literal{val=[]})]}},
+ {C#c_clause{body=B},St1};
+c_add_dummy_export(C, [], St) ->
+ {C,St}.
+
+%%%
+%%% Lower a `receive` to more primitive operations. Rewrite patterns
+%%% that use and bind the same variable as nested cases.
+%%%
+%%% Here follows an example of how a receive in this Erlang code:
+%%%
+%%% foo(Timeout) ->
+%%% receive
+%%% {tag,Msg} -> Msg
+%%% after
+%%% Timeout ->
+%%% no_message
+%%% end.
+%%%
+%%% is translated into Core Erlang:
+%%%
+%%% 'foo'/1 =
+%%% fun (Timeout) ->
+%%% ( letrec
+%%% 'recv$^0'/0 =
+%%% fun () ->
+%%% let <PeekSucceeded,Message> =
+%%% primop 'recv_peek_message'()
+%%% in case PeekSucceeded of
+%%% <'true'> when 'true' ->
+%%% case Message of
+%%% <{'tag',Msg}> when 'true' ->
+%%% do primop 'remove_message'()
+%%% Msg
+%%% ( <Other> when 'true' ->
+%%% do primop 'recv_next'()
+%%% apply 'recv$^0'/0()
+%%% -| ['compiler_generated'] )
+%%% end
+%%% <'false'> when 'true' ->
+%%% let <TimedOut> =
+%%% primop 'recv_wait_timeout'(Timeout)
+%%% in case TimedOut of
+%%% <'true'> when 'true' ->
+%%% do primop 'timeout'()
+%%% 'no_message'
+%%% <'false'> when 'true' ->
+%%% apply 'recv$^0'/0()
+%%% end
+%%% end
+%%% in apply 'recv$^0'/0()
+%%% -| ['letrec_goto'] )
+
+lbody(B, St) ->
+ cerl_trees:mapfold(fun lexpr/2, St, B).
+
+lexpr(#c_case{}=Case, St) ->
+ %% Split patterns that bind and use the same variable.
+ split_case(Case, St);
+lexpr(#c_receive{clauses=[],timeout=Timeout0,action=Action}, St0) ->
+ %% Lower a receive with only an after to its primitive operations.
+ False = #c_literal{val=false},
+ True = #c_literal{val=true},
+
+ {Timeout,Outer0,St1} =
+ case is_safe(Timeout0) of
+ true ->
+ {Timeout0,False,St0};
+ false ->
+ {TimeoutVar,Sti0} = new_var(St0),
+ OuterLet = #c_let{vars=[TimeoutVar],arg=Timeout0,body=False},
+ {TimeoutVar,OuterLet,Sti0}
+ end,
+
+ MaybeIgnore = case Timeout of
+ #c_literal{val=infinity} -> [dialyzer_ignore];
+ _ -> []
+ end,
+
+ {LoopName,St2} = new_fun_name("recv", St1),
+ LoopFun = #c_var{name={LoopName,0}},
+ ApplyLoop = #c_apply{anno=[dialyzer_ignore],op=LoopFun,args=[]},
+
+ TimeoutCs = [#c_clause{anno=MaybeIgnore,pats=[True],guard=True,
+ body=#c_seq{arg=primop(timeout),
+ body=Action}},
+ #c_clause{anno=[compiler_generated,dialyzer_ignore],
+ pats=[False],guard=True,
+ body=ApplyLoop}],
+ {TimeoutBool,St3} = new_var(St2),
+ TimeoutCase = #c_case{anno=[receive_timeout],arg=TimeoutBool,
+ clauses=TimeoutCs},
+ TimeoutLet = #c_let{vars=[TimeoutBool],
+ arg=primop(recv_wait_timeout, [Timeout]),
+ body=TimeoutCase},
+
+ Fun = #c_fun{vars=[],body=TimeoutLet},
+
+ Letrec = #c_letrec{anno=[letrec_goto],
+ defs=[{LoopFun,Fun}],
+ body=ApplyLoop},
+
+ %% If the 'after' expression is unsafe we evaluate it in an outer 'let'.
+ Outer = case Outer0 of
+ #c_let{} -> Outer0#c_let{body=Letrec};
+ _ -> Letrec
+ end,
+ {Outer,St3};
+lexpr(#c_receive{anno=RecvAnno,clauses=Cs0,timeout=Timeout0,action=Action}, St0) ->
+ %% Lower receive to its primitive operations.
+ False = #c_literal{val=false},
+ True = #c_literal{val=true},
+
+ {Timeout,Outer0,St1} =
+ case is_safe(Timeout0) of
+ true ->
+ {Timeout0,False,St0};
+ false ->
+ {TimeoutVar,Sti0} = new_var(St0),
+ OuterLet = #c_let{vars=[TimeoutVar],arg=Timeout0,body=False},
+ {TimeoutVar,OuterLet,Sti0}
+ end,
+
+ MaybeIgnore = case Timeout of
+ #c_literal{val=infinity} -> [dialyzer_ignore];
+ _ -> []
+ end,
+
+ {LoopName,St2} = new_fun_name("recv", St1),
+ LoopFun = #c_var{name={LoopName,0}},
+ ApplyLoop = #c_apply{anno=[dialyzer_ignore],op=LoopFun,args=[]},
+
+ Cs1 = rewrite_cs(Cs0),
+ RecvNext = #c_seq{arg=primop(recv_next),
+ body=ApplyLoop},
+ RecvNextC = #c_clause{anno=[compiler_generated,dialyzer_ignore],
+ pats=[#c_var{name='Other'}],guard=True,body=RecvNext},
+ Cs = Cs1 ++ [RecvNextC],
+ {Msg,St3} = new_var(St2),
+ {MsgCase,St4} = split_case(#c_case{anno=RecvAnno,arg=Msg,clauses=Cs}, St3),
+
+ TimeoutCs = [#c_clause{pats=[True],guard=True,
+ body=#c_seq{arg=primop(timeout),
+ body=Action}},
+ #c_clause{anno=[dialyzer_ignore],pats=[False],guard=True,
+ body=ApplyLoop}],
+ {TimeoutBool,St5} = new_var(St4),
+ TimeoutCase = #c_case{arg=TimeoutBool,clauses=TimeoutCs},
+ TimeoutLet = #c_let{vars=[TimeoutBool],
+ arg=primop(recv_wait_timeout, [Timeout]),
+ body=TimeoutCase},
+
+ {PeekSucceeded,St6} = new_var(St5),
+ PeekCs = [#c_clause{pats=[True],guard=True,
+ body=MsgCase},
+ #c_clause{anno=MaybeIgnore,
+ pats=[False],guard=True,
+ body=TimeoutLet}],
+ PeekCase = #c_case{arg=PeekSucceeded,clauses=PeekCs},
+ PeekLet = #c_let{vars=[PeekSucceeded,Msg],
+ arg=primop(recv_peek_message),
+ body=PeekCase},
+ Fun = #c_fun{vars=[],body=PeekLet},
+
+ Letrec = #c_letrec{anno=[letrec_goto],
+ defs=[{LoopFun,Fun}],
+ body=ApplyLoop},
+
+ %% If the 'after' expression is unsafe we evaluate it in an outer 'let'.
+ Outer = case Outer0 of
+ #c_let{} -> Outer0#c_let{body=Letrec};
+ _ -> Letrec
+ end,
+ {Outer,St6};
+lexpr(Tree, St) ->
+ {Tree,St}.
+
+rewrite_cs([#c_clause{body=B0}=C|Cs]) ->
+ B = #c_seq{arg=primop(remove_message),body=B0},
+ [C#c_clause{body=B}|rewrite_cs(Cs)];
+rewrite_cs([]) -> [].
+
+primop(Name) ->
+ primop(Name, []).
+
+primop(Name, Args) ->
+ #c_primop{name=#c_literal{val=Name},args=Args}.
+
+%%%
+%%% Split patterns such as <<Size:32,Tail:Size>> that bind
+%%% and use a variable in the same pattern. Rewrite to a
+%%% nested case in a letrec.
+%%%
+
+split_case(#c_case{anno=CaseAnno,arg=Arg,clauses=Cs0}=Case0, St0) ->
+ Args = case Arg of
+ #c_values{es=Es} -> Es;
+ _ -> [Arg]
+ end,
+ {VarArgs,St1} = split_var_args(Args, St0),
+ case split_clauses(Cs0, VarArgs, CaseAnno, St1) of
+ none ->
+ {Case0,St0};
+ {PreCase,AftCs,St2} ->
+ AftCase = Case0#c_case{arg=core_lib:make_values(VarArgs),
+ clauses=AftCs},
+ AftFun = #c_fun{vars=[],body=AftCase},
+ {Letrec,St3} = split_case_letrec(AftFun, PreCase, St2),
+ Body = split_letify(VarArgs, Args, Letrec, [], []),
+ {Body,St3}
+ end.
+
+split_var_args(Args, St) ->
+ mapfoldl(fun(#c_var{}=Var, S0) ->
+ {Var,S0};
+ (#c_literal{}=Lit, S0) ->
+ {Lit,S0};
+ (_, S0) ->
+ new_var(S0)
+ end, St, Args).
+
+split_letify([Same|Vs], [Same|Args], Body, VsAcc, ArgAcc) ->
+ split_letify(Vs, Args, Body, VsAcc, ArgAcc);
+split_letify([V|Vs], [Arg|Args], Body, VsAcc, ArgAcc) ->
+ split_letify(Vs, Args, Body, [V|VsAcc], [Arg|ArgAcc]);
+split_letify([], [], Body, [], []) ->
+ Body;
+split_letify([], [], Body, [_|_]=VsAcc, [_|_]=ArgAcc) ->
+ #c_let{vars=reverse(VsAcc),
+ arg=core_lib:make_values(reverse(ArgAcc)),
+ body=Body}.
+
+split_case_letrec(#c_fun{anno=FunAnno0}=Fun0, Body, #core{gcount=C}=St0) ->
+ FunAnno = [compiler_generated|FunAnno0],
+ Fun = Fun0#c_fun{anno=FunAnno},
+ Anno = [letrec_goto],
+ DefFunName = goto_func(C),
+ Letrec = #c_letrec{anno=Anno,defs=[{#c_var{name=DefFunName},Fun}],body=Body},
+ St = St0#core{gcount=C+1},
+ lbody(Letrec, St).
+
+split_clauses([C0|Cs0], Args, CaseAnno, St0) ->
+ case split_clauses(Cs0, Args, CaseAnno, St0) of
+ none ->
+ case split_clause(C0, St0) of
+ none ->
+ none;
+ {Ps,Nested,St1} ->
+ {Case,St2} = split_reconstruct(Args, Ps, Nested,
+ C0, CaseAnno, St1),
+ {Case,Cs0,St2}
+ end;
+ {Case0,Cs,St} ->
+ #c_case{clauses=NewClauses} = Case0,
+ Case = Case0#c_case{clauses=[C0|NewClauses]},
+ {Case,Cs,St}
+ end;
+split_clauses([], _, _, _) ->
+ none.
+
+goto_func(Count) ->
+ {list_to_atom("label^" ++ integer_to_list(Count)),0}.
+
+split_reconstruct(Args, Ps, nil, #c_clause{anno=Anno}=C0, CaseAnno, St0) ->
+ C = C0#c_clause{pats=Ps},
+ {Fc,St1} = split_fc_clause(Ps, Anno, St0),
+ {#c_case{anno=CaseAnno,arg=core_lib:make_values(Args),clauses=[C,Fc]},St1};
+split_reconstruct(Args, Ps, {split,SplitArgs,Pat,Nested}, C, CaseAnno, St) ->
+ Split = {split,SplitArgs,fun(Body) -> Body end,Pat,Nested},
+ split_reconstruct(Args, Ps, Split, C, CaseAnno, St);
+split_reconstruct(Args, Ps, {split,SplitArgs,Wrap,Pat,Nested},
+ #c_clause{anno=Anno}=C0, CaseAnno, St0) ->
+ {InnerCase,St1} = split_reconstruct(SplitArgs, [Pat], Nested, C0,
+ CaseAnno, St0),
+ {Fc,St2} = split_fc_clause(Args, Anno, St1),
+ Wrapped = Wrap(InnerCase),
+ C = C0#c_clause{pats=Ps,guard=#c_literal{val=true},body=Wrapped},
+ {#c_case{anno=CaseAnno,arg=core_lib:make_values(Args),clauses=[C,Fc]},St2}.
+
+split_fc_clause(Args, Anno0, #core{gcount=Count}=St0) ->
+ Anno = [compiler_generated|Anno0],
+ Arity = length(Args),
+ {Vars,St1} = new_vars(Arity, St0),
+ Op = #c_var{name=goto_func(Count)},
+ Apply = #c_apply{anno=Anno,op=Op,args=[]},
+ {#c_clause{anno=[dialyzer_ignore|Anno],pats=Vars,
+ guard=#c_literal{val=true},body=Apply},St1}.
+
+split_clause(#c_clause{pats=Ps0}, St0) ->
+ case split_pats(Ps0, St0) of
+ none ->
+ none;
+ {Ps,Case,St} ->
+ {Ps,Case,St}
+ end.
+
+split_pats([P0|Ps0], St0) ->
+ case split_pats(Ps0, St0) of
+ none ->
+ case split_pat(P0, St0) of
+ none ->
+ none;
+ {P,Case,St} ->
+ {[P|Ps0],Case,St}
+ end;
+ {Ps,Case,St} ->
+ {[P0|Ps],Case,St}
+ end;
+split_pats([], _) ->
+ none.
+
+split_pat(#c_binary{segments=Segs0}=Bin, St0) ->
+ Vars = gb_sets:empty(),
+ case split_bin_segments(Segs0, Vars, St0, []) of
+ none ->
+ none;
+ {TailVar,Wrap,Bef,Aft,St} ->
+ BefBin = Bin#c_binary{segments=Bef},
+ {BefBin,{split,[TailVar],Wrap,Bin#c_binary{segments=Aft},nil},St}
+ end;
+split_pat(#c_map{es=Es}=Map, St) ->
+ split_map_pat(Es, Map, St, []);
+split_pat(#c_var{}, _) ->
+ none;
+split_pat(#c_alias{pat=Pat}=Alias0, St0) ->
+ case split_pat(Pat, St0) of
+ none ->
+ none;
+ {Ps,Split,St1} ->
+ {Var,St} = new_var(St1),
+ Alias = Alias0#c_alias{pat=Var},
+ {Alias,{split,[Var],Ps,Split},St}
+ end;
+split_pat(Data, St0) ->
+ Type = cerl:data_type(Data),
+ Es = cerl:data_es(Data),
+ split_data(Es, Type, St0, []).
+
+split_map_pat([#c_map_pair{key=Key,val=Val}=E0|Es], Map0, St0, Acc) ->
+ case eval_map_key(Key, E0, Es, Map0, St0) of
+ none ->
+ case split_pat(Val, St0) of
+ none ->
+ split_map_pat(Es, Map0, St0, [E0|Acc]);
+ {Ps,Split,St1} ->
+ {Var,St} = new_var(St1),
+ E = E0#c_map_pair{val=Var},
+ Map = Map0#c_map{es=reverse(Acc, [E|Es])},
+ {Map,{split,[Var],Ps,Split},St}
+ end;
+ {MapVar,Split,St1} ->
+ BefMap0 = Map0#c_map{es=reverse(Acc)},
+ BefMap = #c_alias{var=MapVar,pat=BefMap0},
+ {BefMap,Split,St1}
+ end;
+split_map_pat([], _, _, _) -> none.
+
+eval_map_key(#c_var{}, _E, _Es, _Map, _St) ->
+ none;
+eval_map_key(#c_literal{}, _E, _Es, _Map, _St) ->
+ none;
+eval_map_key(Key, E0, Es, Map, St0) ->
+ {[KeyVar,MapVar],St1} = new_vars(2, St0),
+ E = E0#c_map_pair{key=KeyVar},
+ AftMap0 = Map#c_map{es=[E|Es]},
+ {Wrap,CaseArg,AftMap,St2} = wrap_map_key_fun(Key, KeyVar, MapVar, AftMap0, St1),
+ {MapVar,{split,[CaseArg],Wrap,AftMap,nil},St2}.
+
+wrap_map_key_fun(Key, KeyVar, MapVar, AftMap, St0) ->
+ case is_safe(Key) of
+ true ->
+ {fun(Body) ->
+ #c_let{vars=[KeyVar],arg=Key,body=Body}
+ end,MapVar,AftMap,St0};
+ false ->
+ {[SuccVar|Evars],St} = new_vars(4, St0),
+ {fun(Body) ->
+ Try = #c_try{arg=Key,vars=[KeyVar],
+ body=#c_values{es=[#c_literal{val=true},KeyVar]},
+ evars=Evars,
+ handler=#c_values{es=[#c_literal{val=false},
+ #c_literal{val=false}]}},
+ #c_let{vars=[SuccVar,KeyVar],arg=Try,body=Body}
+ end,
+ #c_tuple{es=[SuccVar,MapVar]},
+ #c_tuple{es=[#c_literal{val=true},AftMap]},
+ St}
+ end.
+
+split_data([E|Es0], Type, St0, Acc) ->
+ case split_pat(E, St0) of
+ none ->
+ split_data(Es0, Type, St0, [E|Acc]);
+ {Ps,Split,St1} ->
+ {Var,St} = new_var(St1),
+ Data = cerl:make_data(Type, reverse(Acc, [Var|Es0])),
+ {Data,{split,[Var],Ps,Split},St}
+ end;
+split_data([], _, _, _) -> none.
+
+split_bin_segments([#c_bitstr{val=Val,size=Size}=S0|Segs], Vars0, St0, Acc) ->
+ Vars = case Val of
+ #c_var{name=V} -> gb_sets:add(V, Vars0);
+ _ -> Vars0
+ end,
+ case Size of
+ #c_literal{} ->
+ split_bin_segments(Segs, Vars, St0, [S0|Acc]);
+ #c_var{name=SizeVar} ->
+ case gb_sets:is_member(SizeVar, Vars0) of
+ true ->
+ %% The size variable is variable previously bound
+ %% in this same segment. Split the clause here to
+ %% avoid a variable that is both defined and used
+ %% in the same pattern.
+ {TailVar,Tail,St} = split_tail_seg(S0, Segs, St0),
+ Wrap = fun(Body) -> Body end,
+ {TailVar,Wrap,reverse(Acc, [Tail]),[S0|Segs],St};
+ false ->
+ split_bin_segments(Segs, Vars, St0, [S0|Acc])
+ end;
+ _ ->
+ %% The size is an expression. Split the clause here,
+ %% calculate the expression in a try/catch, and finally
+ %% continue the match in an inner case.
+ {TailVar,Tail,St1} = split_tail_seg(S0, Segs, St0),
+ {SizeVar,St2} = new_var(St1),
+ S = S0#c_bitstr{size=SizeVar},
+ {Wrap,St3} = split_wrap(SizeVar, Size, St2),
+ {TailVar,Wrap,reverse(Acc, [Tail]),[S|Segs],St3}
+ end;
+split_bin_segments(_, _, _, _) ->
+ none.
+
+split_tail_seg(#c_bitstr{anno=A}=S, Segs, St0) ->
+ {TailVar,St} = new_var(St0),
+ Unit = split_bin_unit([S|Segs], St0),
+ {TailVar,
+ #c_bitstr{anno=A,val=TailVar,
+ size=#c_literal{val=all},
+ unit=#c_literal{val=Unit},
+ type=#c_literal{val=binary},
+ flags=#c_literal{val=[unsigned,big]}},
+ St}.
+
+split_wrap(SizeVar, SizeExpr, St0) ->
+ {Evars,St1} = new_vars(3, St0),
+ {fun(Body) ->
+ Try = #c_try{arg=SizeExpr,vars=[SizeVar],body=SizeVar,
+ evars=Evars,handler=#c_literal{val=bad_size}},
+ #c_let{vars=[SizeVar],arg=Try,body=Body}
+ end,St1}.
+
+split_bin_unit(Ss, #core{dialyzer=Dialyzer}) ->
+ case Dialyzer of
+ true ->
+ %% When a binary match has been rewritten to a nested
+ %% case like this:
+ %%
+ %% case Bin of
+ %% <<Size:32,Tail:Size/bitstring-unit:1>> ->
+ %% case Tail of
+ %% <<Result/binary-unit:8>> -> Result;
+ %% ...
+ %% end
+ %%
+ %% dialyzer will determine the type of Bin based solely on
+ %% the binary pattern in the outer case. It will not
+ %% back-propagate any type information for Tail to Bin. For
+ %% this example, dialyzer would infer the type of Bin to
+ %% be <<_:8,_:_*1>>.
+ %%
+ %% Help dialyzer to infer a better type by calculating the
+ %% greatest common unit for the segments in the inner case
+ %% expression. For this example, the greatest common unit
+ %% for the pattern in the inner case is 8; it will allow
+ %% dialyzer to infer the type for Bin to be
+ %% <<_:32,_:_*8>>.
+
+ split_bin_unit_1(Ss, 0);
+ false ->
+ %% Return the unit for pattern in the outer case that
+ %% results in the best code.
+
+ 1
+ end.
+
+split_bin_unit_1([#c_bitstr{type=#c_literal{val=Type},size=Size,
+ unit=#c_literal{val=U}}|Ss],
+ GCU) ->
+ Bits = case {Type,Size} of
+ {utf8,_} -> 8;
+ {utf16,_} -> 16;
+ {utf32,_} -> 32;
+ {_,#c_literal{val=0}} -> 1;
+ {_,#c_literal{val=Sz}} when is_integer(Sz) -> Sz * U;
+ {_,_} -> U
+ end,
+ split_bin_unit_1(Ss, gcd(GCU, Bits));
+split_bin_unit_1([], GCU) -> GCU.
+
+gcd(A, B) ->
+ case A rem B of
+ 0 -> B;
+ X -> gcd(B, X)
+ end.
+
%% lit_vars(Literal) -> [Var].
lit_vars(Lit) -> lit_vars(Lit, []).
@@ -2649,10 +3483,8 @@ bitstr_vars(Segs, Vs) ->
lit_vars(V, lit_vars(S, Vs0))
end, Vs, Segs).
-record_anno(L, St) ->
- case
- erl_anno:record(L) andalso member(dialyzer, St#core.opts)
- of
+record_anno(L, #core{dialyzer=Dialyzer}=St) ->
+ case erl_anno:record(L) andalso Dialyzer of
true ->
[record | lineno_anno(L, St)];
false ->
@@ -2718,8 +3550,6 @@ format_error(nomatch) ->
"pattern cannot possibly match";
format_error(bad_binary) ->
"binary construction will fail because of a type mismatch";
-format_error(badmap) ->
- "map construction will fail because of a type mismatch";
format_error({map_key_repeated,Key}) when is_atom(Key) ->
io_lib:format("key '~w' will be overridden in expression", [Key]);
format_error({map_key_repeated,Key}) ->
diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl
index fec5e2bea3..4e2b8926bf 100644
--- a/lib/compiler/src/v3_kernel.erl
+++ b/lib/compiler/src/v3_kernel.erl
@@ -29,11 +29,7 @@
%%
%% 3. Pattern matching (in cases and receives) has been compiled.
%%
-%% 4. The annotations contain variable usages. Seeing we have to work
-%% this out anyway for funs we might as well pass it on for free to
-%% later passes.
-%%
-%% 5. All remote-calls are to statically named m:f/a. Meta-calls are
+%% 4. All remote-calls are to statically named m:f/a. Meta-calls are
%% passed via erlang:apply/3.
%%
%% The translation is done in two passes:
@@ -81,13 +77,17 @@
-export([module/2,format_error/1]).
--import(lists, [map/2,foldl/3,foldr/3,mapfoldl/3,splitwith/2,member/2,
- keyfind/3,partition/2,droplast/1,last/1,sort/1,reverse/1]).
--import(ordsets, [add_element/2,del_element/2,union/2,union/1,subtract/2]).
--import(cerl, [c_tuple/1]).
+-import(lists, [all/2,droplast/1,flatten/1,foldl/3,foldr/3,
+ map/2,mapfoldl/3,member/2,
+ keyfind/3,keyreplace/4,
+ last/1,partition/2,reverse/1,
+ sort/1,sort/2,splitwith/2]).
+-import(ordsets, [add_element/2,intersection/2,
+ subtract/2,union/2,union/1]).
-include("core_parse.hrl").
-include("v3_kernel.hrl").
+-define(EXPAND_MAX_SIZE_SEGMENT, 1024).
%% These are not defined in v3_kernel.hrl.
get_kanno(Kthing) -> element(2, Kthing).
@@ -105,33 +105,34 @@ copy_anno(Kdst, Ksrc) ->
-record(iletrec, {anno=[],defs}).
-record(ialias, {anno=[],vars,pat}).
-record(iclause, {anno=[],isub,osub,pats,guard,body}).
--record(ireceive_accept, {anno=[],arg}).
--record(ireceive_next, {anno=[],arg}).
--record(ignored, {anno=[]}).
-type warning() :: term(). % XXX: REFINE
%% State record for kernel translator.
-record(kern, {func, %Current host function
- ff, %Current function
+ fargs=[] :: [#k_var{}], %Arguments for current function
vcount=0, %Variable counter
fcount=0, %Fun counter
ds=cerl_sets:new() :: cerl_sets:set(), %Defined variables
funs=[], %Fun functions
free=#{}, %Free variables
ws=[] :: [warning()], %Warnings.
- guard_refc=0}). %> 0 means in guard
+ no_shared_fun_wrappers=false :: boolean(),
+ labels=cerl_sets:new()
+ }).
-spec module(cerl:c_module(), [compile:option()]) ->
{'ok', #k_mdef{}, [warning()]}.
-module(#c_module{anno=A,name=M,exports=Es,attrs=As,defs=Fs}, _Options) ->
+module(#c_module{anno=A,name=M,exports=Es,attrs=As,defs=Fs}, Options) ->
Kas = attributes(As),
Kes = map(fun (#c_var{name={_,_}=Fname}) -> Fname end, Es),
- St0 = #kern{},
+ NoSharedFunWrappers = proplists:get_bool(no_shared_fun_wrappers,
+ Options),
+ St0 = #kern{no_shared_fun_wrappers=NoSharedFunWrappers},
{Kfs,St} = mapfoldl(fun function/2, St0, Fs),
{ok,#k_mdef{anno=A,name=M#c_literal.val,exports=Kes,attributes=Kas,
- body=Kfs ++ St#kern.funs},lists:sort(St#kern.ws)}.
+ body=Kfs ++ St#kern.funs},sort(St#kern.ws)}.
attributes([{#c_literal{val=Name},#c_literal{val=Val}}|As]) ->
case include_attribute(Name) of
@@ -162,11 +163,11 @@ function({#c_var{name={F,Arity}=FA},Body}, St0) ->
%% the function. We use integers as variable names to avoid
%% filling up the atom table when compiling huge functions.
Count = cerl_trees:next_free_variable_name(Body),
- St1 = St0#kern{func=FA,ff=undefined,vcount=Count,fcount=0,ds=cerl_sets:new()},
+ St1 = St0#kern{func=FA,vcount=Count,fcount=0,ds=cerl_sets:new()},
{#ifun{anno=Ab,vars=Kvs,body=B0},[],St2} = expr(Body, new_sub(), St1),
{B1,_,St3} = ubody(B0, return, St2),
%%B1 = B0, St3 = St2, %Null second pass
- {make_fdef(#k{us=[],ns=[],a=Ab}, F, Arity, Kvs, B1),St3}
+ {make_fdef(Ab, F, Arity, Kvs, B1),St3}
catch
Class:Error:Stack ->
io:fwrite("Function: ~w/~w\n", [F,Arity]),
@@ -180,512 +181,27 @@ function({#c_var{name={F,Arity}=FA},Body}, St0) ->
body(#c_values{anno=A,es=Ces}, Sub, St0) ->
%% Do this here even if only in bodies.
{Kes,Pe,St1} = atomic_list(Ces, Sub, St0),
- %%{Kes,Pe,St1} = expr_list(Ces, Sub, St0),
{#ivalues{anno=A,args=Kes},Pe,St1};
-body(#ireceive_next{anno=A}, _, St) ->
- {#k_receive_next{anno=A},[],St};
body(Ce, Sub, St0) ->
expr(Ce, Sub, St0).
%% guard(Cexpr, Sub, State) -> {Kexpr,State}.
%% We handle guards almost as bodies. The only special thing we
%% must do is to make the final Kexpr a #k_test{}.
-%% Also, we wrap the entire guard in a try/catch which is
-%% not strictly needed, but makes sure that every 'bif' instruction
-%% will get a proper failure label.
guard(G0, Sub, St0) ->
- {G1,St1} = wrap_guard(G0, St0),
- {Ge0,Pre,St2} = expr(G1, Sub, St1),
- {Ge1,St3} = gexpr_test(Ge0, St2),
- {Ge,St} = guard_opt(Ge1, St3),
+ {Ge0,Pre,St1} = expr(G0, Sub, St0),
+ {Ge,St} = gexpr_test(Ge0, St1),
{pre_seq(Pre, Ge),St}.
-%% guard_opt(Kexpr, State) -> {Kexpr,State}.
-%% Optimize the Kexpr for the guard. Instead of evaluating a boolean
-%% expression comparing it to 'true' in a final #k_test{},
-%% replace BIF calls with #k_test{} in the expression.
-%%
-%% As an example, take the guard:
-%%
-%% when is_integer(V0), is_atom(V1) ->
-%%
-%% The unoptimized Kexpr translated to pseudo BEAM assembly
-%% code would look like:
-%%
-%% bif is_integer V0 => Bool0
-%% bif is_atom V1 => Bool1
-%% bif and Bool0 Bool1 => Bool
-%% test Bool =:= true else goto Fail
-%% ...
-%% Fail:
-%% ...
-%%
-%% The optimized code would look like:
-%%
-%% test is_integer V0 else goto Fail
-%% test is_atom V1 else goto Fail
-%% ...
-%% Fail:
-%% ...
-%%
-%% An 'or' operation is only slightly more complicated:
-%%
-%% test is_integer V0 else goto NotFailedYet
-%% goto Success
-%%
-%% NotFailedYet:
-%% test is_atom V1 else goto Fail
-%%
-%% Success:
-%% ...
-%% Fail:
-%% ...
-
-guard_opt(G, St0) ->
- {Root,Forest0,St1} = make_forest(G, St0),
- {Exprs,Forest,St} = rewrite_bool(Root, Forest0, false, St1),
- E = forest_pre_seq(Exprs, Forest),
- {G#k_try{arg=E},St}.
-
-%% rewrite_bool(Kexpr, Forest, Inv, St) -> {[Kexpr],Forest,St}.
-%% Rewrite Kexpr to use #k_test{} operations instead of comparison
-%% and type test BIFs.
-%%
-%% If Kexpr is a #k_test{} operation, the call will always
-%% succeed. Otherwise, a 'not_possible' exception will be
-%% thrown if Kexpr cannot be rewritten.
-
-rewrite_bool(#k_test{op=#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val='=:='}},
- args=[#k_var{}=V,#k_atom{val=true}]}=Test, Forest0, Inv, St0) ->
- try rewrite_bool_var(V, Forest0, Inv, St0) of
- {_,_,_}=Res ->
- Res
- catch
- throw:not_possible ->
- {[Test],Forest0,St0}
- end;
-rewrite_bool(#k_test{op=#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val='=:='}},
- args=[#k_var{}=V,#k_atom{val=false}]}=Test, Forest0, Inv, St0) ->
- try rewrite_bool_var(V, Forest0, not Inv, St0) of
- {_,_,_}=Res ->
- Res
- catch
- throw:not_possible ->
- {[Test],Forest0,St0}
- end;
-rewrite_bool(#k_test{op=#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val='=:='}},
- args=[#k_atom{val=V1},#k_atom{val=V2}]}, Forest0, false, St0) ->
- case V1 =:= V2 of
- true ->
- {[make_test(is_boolean, [#k_atom{val=true}])],Forest0,St0};
- false ->
- {[make_failing_test()],Forest0,St0}
- end;
-rewrite_bool(#k_test{}=Test, Forest, false, St) ->
- {[Test],Forest,St};
-rewrite_bool(#k_try{vars=[#k_var{name=X}],body=#k_var{name=X},
- handler=#k_atom{val=false},ret=[]}=Prot,
- Forest0, Inv, St0) ->
- {Root,Forest1,St1} = make_forest(Prot, Forest0, St0),
- {Exprs,Forest2,St} = rewrite_bool(Root, Forest1, Inv, St1),
- InnerForest = maps:without(maps:keys(Forest0), Forest2),
- Forest = maps:without(maps:keys(InnerForest), Forest2),
- E = forest_pre_seq(Exprs, InnerForest),
- {[Prot#k_try{arg=E}],Forest,St};
-rewrite_bool(#k_match{body=Body,ret=[]}, Forest, Inv, St) ->
- rewrite_match(Body, Forest, Inv, St);
-rewrite_bool(Other, Forest, Inv, St) ->
- case extract_bif(Other) of
- {Name,Args} ->
- rewrite_bif(Name, Args, Forest, Inv, St);
- error ->
- throw(not_possible)
- end.
-
-%% rewrite_bool_var(Var, Forest, Inv, St) -> {[Kexpr],Forest,St}.
-%% Rewrite the boolean expression whose key in Forest is
-%% given by Var. Throw a 'not_possible' expression if something
-%% prevents the rewriting.
-
-rewrite_bool_var(Arg, Forest0, Inv, St) ->
- {Expr,Forest} = forest_take_expr(Arg, Forest0),
- rewrite_bool(Expr, Forest, Inv, St).
-
-%% rewrite_bool_args([Kexpr], Forest, Inv, St) -> {[[Kexpr]],Forest,St}.
-%% Rewrite each Kexpr in the list. The input Kexpr should be variables
-%% or boolean values. Throw a 'not_possible' expression if something
-%% prevents the rewriting.
-%%
-%% This function is suitable for handling the arguments for both
-%% 'and' and 'or'.
-
-rewrite_bool_args([#k_atom{val=B}=A|Vs], Forest0, false=Inv, St0) when is_boolean(B) ->
- {Tail,Forest1,St1} = rewrite_bool_args(Vs, Forest0, Inv, St0),
- Bif = make_bif('=:=', [A,#k_atom{val=true}]),
- {Exprs,Forest,St} = rewrite_bool(Bif, Forest1, Inv, St1),
- {[Exprs|Tail],Forest,St};
-rewrite_bool_args([#k_var{}=Var|Vs], Forest0, false=Inv, St0) ->
- {Tail,Forest1,St1} = rewrite_bool_args(Vs, Forest0, Inv, St0),
- {Exprs,Forest,St} =
- case is_bool_expr(Var, Forest0) of
- true ->
- rewrite_bool_var(Var, Forest1, Inv, St1);
- false ->
- Bif = make_bif('=:=', [Var,#k_atom{val=true}]),
- rewrite_bool(Bif, Forest1, Inv, St1)
- end,
- {[Exprs|Tail],Forest,St};
-rewrite_bool_args([_|_], _Forest, _Inv, _St) ->
- throw(not_possible);
-rewrite_bool_args([], Forest, _Inv, St) ->
- {[],Forest,St}.
-
-%% rewrite_bif(Name, [Kexpr], Forest, Inv, St) -> {[Kexpr],Forest,St}.
-%% Rewrite a BIF. Throw a 'not_possible' expression if something
-%% prevents the rewriting.
-
-rewrite_bif('or', Args, Forest, true, St) ->
- rewrite_not_args('and', Args, Forest, St);
-rewrite_bif('and', Args, Forest, true, St) ->
- rewrite_not_args('or', Args, Forest, St);
-rewrite_bif('and', [#k_atom{val=Val},Arg], Forest0, Inv, St0) ->
- false = Inv, %Assertion.
- case Val of
- true ->
- %% The result only depends on Arg.
- rewrite_bool_var(Arg, Forest0, Inv, St0);
- _ ->
- %% Will fail. There is no need to evalute the expression
- %% represented by Arg. Take it out from the forest and
- %% discard the expression.
- Failing = make_failing_test(),
- try rewrite_bool_var(Arg, Forest0, Inv, St0) of
- {_,Forest,St} ->
- {[Failing],Forest,St}
- catch
- throw:not_possible ->
- try forest_take_expr(Arg, Forest0) of
- {_,Forest} ->
- {[Failing],Forest,St0}
- catch
- throw:not_possible ->
- %% Arg is probably a variable bound in an
- %% outer scope.
- {[Failing],Forest0,St0}
- end
- end
- end;
-rewrite_bif('and', [Arg,#k_atom{}=Atom], Forest, Inv, St) ->
- false = Inv, %Assertion.
- rewrite_bif('and', [Atom,Arg], Forest, Inv, St);
-rewrite_bif('and', Args, Forest0, Inv, St0) ->
- false = Inv, %Assertion.
- {[Es1,Es2],Forest,St} = rewrite_bool_args(Args, Forest0, Inv, St0),
- {Es1 ++ Es2,Forest,St};
-rewrite_bif('or', Args, Forest0, Inv, St0) ->
- false = Inv, %Assertion.
- {[First,Then],Forest,St} = rewrite_bool_args(Args, Forest0, Inv, St0),
- Alt = make_alt(First, Then),
- {[Alt],Forest,St};
-rewrite_bif('xor', [_,_], _Forest, _Inv, _St) ->
- %% Rewriting 'xor' is not practical. Fortunately, 'xor' is
- %% almost never used in practice.
- throw(not_possible);
-rewrite_bif('not', [Arg], Forest0, Inv, St) ->
- {Expr,Forest} = forest_take_expr(Arg, Forest0),
- rewrite_bool(Expr, Forest, not Inv, St);
-rewrite_bif(Op, Args, Forest, Inv, St) ->
- case is_test(Op, Args) of
- true ->
- rewrite_bool(make_test(Op, Args, Inv), Forest, false, St);
- false ->
- throw(not_possible)
- end.
-
-rewrite_not_args(Op, [A0,B0], Forest0, St0) ->
- {A,Forest1,St1} = rewrite_not_args_1(A0, Forest0, St0),
- {B,Forest2,St2} = rewrite_not_args_1(B0, Forest1, St1),
- rewrite_bif(Op, [A,B], Forest2, false, St2).
-
-rewrite_not_args_1(Arg, Forest, St) ->
- Not = make_bif('not', [Arg]),
- forest_add_expr(Not, Forest, St).
-
-%% rewrite_match(Kvar, TypeClause, Forest, Inv, St) ->
-%% {[Kexpr],Forest,St}.
-%% Try to rewrite a #k_match{} originating from an 'andalso' or an 'orelse'.
-
-rewrite_match(#k_alt{first=First,then=Then}, Forest, Inv, St) ->
- case {First,Then} of
- {#k_select{var=#k_var{name=V}=Var,types=[TypeClause]},#k_var{name=V}} ->
- rewrite_match_1(Var, TypeClause, Forest, Inv, St);
- {_,_} ->
- throw(not_possible)
- end.
-
-rewrite_match_1(Var, #k_type_clause{values=Cs0}, Forest0, Inv, St0) ->
- Cs = sort([{Val,B} || #k_val_clause{val=#k_atom{val=Val},body=B} <- Cs0]),
- case Cs of
- [{false,False},{true,True}] ->
- rewrite_match_2(Var, False, True, Forest0, Inv, St0);
- _ ->
- throw(not_possible)
- end.
-
-rewrite_match_2(Var, False, #k_atom{val=true}, Forest0, Inv, St0) ->
- %% Originates from an 'orelse'.
- case False of
- #k_atom{val=NotBool} when not is_boolean(NotBool) ->
- rewrite_bool(Var, Forest0, Inv, St0);
- _ ->
- {CodeVar,Forest1,St1} = add_protected_expr(False, Forest0, St0),
- rewrite_bif('or', [Var,CodeVar], Forest1, Inv, St1)
- end;
-rewrite_match_2(Var, #k_atom{val=false}, True, Forest0, Inv, St0) ->
- %% Originates from an 'andalso'.
- {CodeVar,Forest1,St1} = add_protected_expr(True, Forest0, St0),
- rewrite_bif('and', [Var,CodeVar], Forest1, Inv, St1);
-rewrite_match_2(_V, _, _, _Forest, _Inv, _St) ->
- throw(not_possible).
-
-%% is_bool_expr(#k_var{}, Forest) -> true|false.
-%% Return true if the variable refers to a boolean expression
-%% that does not need an explicit '=:= true' test.
-
-is_bool_expr(V, Forest) ->
- case forest_peek_expr(V, Forest) of
- error ->
- %% Defined outside of the guard. We can't know.
- false;
- Expr ->
- case extract_bif(Expr) of
- {Name,Args} ->
- is_test(Name, Args) orelse
- erl_internal:bool_op(Name, length(Args));
- error ->
- %% Not a BIF. Should be possible to rewrite
- %% to a boolean. Definitely does not need
- %% a '=:= true' test.
- true
- end
- end.
-
-make_bif(Op, Args) ->
- #k_bif{op=#k_remote{mod=#k_atom{val=erlang},
- name=#k_atom{val=Op},
- arity=length(Args)},
- args=Args}.
-
-extract_bif(#k_bif{op=#k_remote{mod=#k_atom{val=erlang},
- name=#k_atom{val=Name}},
- args=Args}) ->
- {Name,Args};
-extract_bif(_) ->
- error.
-
-%% make_alt(First, Then) -> KMatch.
-%% Make a #k_alt{} within a #k_match{} to implement
-%% 'or' or 'orelse'.
-
-make_alt(First0, Then0) ->
- First1 = pre_seq(droplast(First0), last(First0)),
- Then1 = pre_seq(droplast(Then0), last(Then0)),
- First2 = make_protected(First1),
- Then2 = make_protected(Then1),
- Body = #ignored{},
- First3 = #k_guard_clause{guard=First2,body=Body},
- Then3 = #k_guard_clause{guard=Then2,body=Body},
- First = #k_guard{clauses=[First3]},
- Then = #k_guard{clauses=[Then3]},
- Alt = #k_alt{first=First,then=Then},
- #k_match{vars=[],body=Alt}.
-
-add_protected_expr(#k_atom{}=Atom, Forest, St) ->
- {Atom,Forest,St};
-add_protected_expr(#k_var{}=Var, Forest, St) ->
- {Var,Forest,St};
-add_protected_expr(E0, Forest, St) ->
- E = make_protected(E0),
- forest_add_expr(E, Forest, St).
-
-make_protected(#k_try{}=Try) ->
- Try;
-make_protected(B) ->
- #k_try{arg=B,vars=[#k_var{name=''}],body=#k_var{name=''},
- handler=#k_atom{val=false}}.
-
-make_failing_test() ->
- make_test(is_boolean, [#k_atom{val=fail}]).
-
-make_test(Op, Args) ->
- make_test(Op, Args, false).
-
-make_test(Op, Args, Inv) ->
- Remote = #k_remote{mod=#k_atom{val=erlang},
- name=#k_atom{val=Op},
- arity=length(Args)},
- #k_test{op=Remote,args=Args,inverted=Inv}.
-
-is_test(Op, Args) ->
- A = length(Args),
- erl_internal:new_type_test(Op, A) orelse erl_internal:comp_op(Op, A).
-
-%% make_forest(Kexpr, St) -> {RootKexpr,Forest,St}.
-%% Build a forest out of Kexpr. RootKexpr is the final expression
-%% nested inside Kexpr.
-
-make_forest(G, St) ->
- make_forest_1(G, #{}, 0, St).
-
-%% make_forest(Kexpr, St) -> {RootKexpr,Forest,St}.
-%% Add to Forest from Kexpr. RootKexpr is the final expression
-%% nested inside Kexpr.
-
-make_forest(G, Forest0, St) ->
- N = forest_next_index(Forest0),
- make_forest_1(G, Forest0, N, St).
-
-make_forest_1(#k_try{arg=B}, Forest, I, St) ->
- make_forest_1(B, Forest, I, St);
-make_forest_1(#iset{vars=[]}=Iset0, Forest, I, St0) ->
- {UnrefVar,St} = new_var(St0),
- Iset = Iset0#iset{vars=[UnrefVar]},
- make_forest_1(Iset, Forest, I, St);
-make_forest_1(#iset{vars=[#k_var{name=V}],arg=Arg,body=B}, Forest0, I, St) ->
- Forest = Forest0#{V => {I,Arg}, {untaken,V} => true},
- make_forest_1(B, Forest, I+1, St);
-make_forest_1(Innermost, Forest, _I, St) ->
- {Innermost,Forest,St}.
-
-%% forest_take_expr(Kexpr, Forest) -> {Expr,Forest}.
-%% If Kexpr is a variable, take out the expression corresponding
-%% to variable in Forest. Expressions that have been taken out
-%% of the forest will not be included the Kexpr returned
-%% by forest_pre_seq/2.
-%%
-%% Throw a 'not_possible' exception if Kexpr is not a variable or
-%% if the name of the variable is not a key in Forest.
-
-forest_take_expr(#k_var{name=V}, Forest0) ->
- %% v3_core currently always generates guard expressions that can
- %% be represented as a tree. Other code generators (such as LFE)
- %% could generate guard expressions that can only be represented
- %% as a DAG (i.e. some nodes are referenced more than once). To
- %% handle DAGs, we must never remove a node from the forest, but
- %% just remove the {untaken,V} marker. That will effectively convert
- %% the DAG to a tree by duplicating the shared nodes and their
- %% descendants.
-
- case maps:find(V, Forest0) of
- {ok,{_,Expr}} ->
- Forest = maps:remove({untaken,V}, Forest0),
- {Expr,Forest};
- error ->
- throw(not_possible)
- end;
-forest_take_expr(_, _) ->
- throw(not_possible).
-
-%% forest_peek_expr(Kvar, Forest) -> Kexpr | error.
-%% Return the expression corresponding to Kvar in Forest or
-%% return 'error' if there is a corresponding expression.
-
-forest_peek_expr(#k_var{name=V}, Forest0) ->
- case maps:find(V, Forest0) of
- {ok,{_,Expr}} -> Expr;
- error -> error
- end.
-
-%% forest_add_expr(Kexpr, Forest, St) -> {Kvar,Forest,St}.
-%% Add a new expression to Forest.
-
-forest_add_expr(Expr, Forest0, St0) ->
- {#k_var{name=V}=Var,St} = new_var(St0),
- N = forest_next_index(Forest0),
- Forest = Forest0#{V => {N,Expr}},
- {Var,Forest,St}.
-
-forest_next_index(Forest) ->
- 1 + lists:max([N || {N,_} <- maps:values(Forest),
- is_integer(N)] ++ [0]).
-
-%% forest_pre_seq([Kexpr], Forest) -> Kexpr.
-%% Package the list of Kexprs into a nested Kexpr, prepending all
-%% expressions in Forest that have not been taken out using
-%% forest_take_expr/2.
-
-forest_pre_seq(Exprs, Forest) ->
- Es0 = [#k_var{name=V} || {untaken,V} <- maps:keys(Forest)],
- Es = Es0 ++ Exprs,
- Vs = extract_all_vars(Es, Forest, []),
- Pre0 = sort([{maps:get(V, Forest),V} || V <- Vs]),
- Pre = [#iset{vars=[#k_var{name=V}],arg=A} ||
- {{_,A},V} <- Pre0],
- pre_seq(Pre++droplast(Exprs), last(Exprs)).
-
-extract_all_vars(Es, Forest, Acc0) ->
- case extract_var_list(Es) of
- [] ->
- Acc0;
- [_|_]=Vs0 ->
- Vs = [V || V <- Vs0, maps:is_key(V, Forest)],
- NewVs = ordsets:subtract(Vs, Acc0),
- NewEs = [begin
- {_,E} = maps:get(V, Forest),
- E
- end || V <- NewVs],
- Acc = union(NewVs, Acc0),
- extract_all_vars(NewEs, Forest, Acc)
- end.
-
-extract_vars(#iset{arg=A,body=B}) ->
- union(extract_vars(A), extract_vars(B));
-extract_vars(#k_bif{args=Args}) ->
- ordsets:from_list(lit_list_vars(Args));
-extract_vars(#k_call{}) ->
- [];
-extract_vars(#k_test{args=Args}) ->
- ordsets:from_list(lit_list_vars(Args));
-extract_vars(#k_match{body=Body}) ->
- extract_vars(Body);
-extract_vars(#k_alt{first=First,then=Then}) ->
- union(extract_vars(First), extract_vars(Then));
-extract_vars(#k_guard{clauses=Cs}) ->
- extract_var_list(Cs);
-extract_vars(#k_guard_clause{guard=G}) ->
- extract_vars(G);
-extract_vars(#k_select{var=Var,types=Types}) ->
- union(ordsets:from_list(lit_vars(Var)),
- extract_var_list(Types));
-extract_vars(#k_type_clause{values=Values}) ->
- extract_var_list(Values);
-extract_vars(#k_val_clause{body=Body}) ->
- extract_vars(Body);
-extract_vars(#k_try{arg=Arg}) ->
- extract_vars(Arg);
-extract_vars(Lit) ->
- ordsets:from_list(lit_vars(Lit)).
-
-extract_var_list(L) ->
- union([extract_vars(E) || E <- L]).
-
-%% Wrap the entire guard in a try/catch if needed.
-
-wrap_guard(#c_try{}=Try, St) -> {Try,St};
-wrap_guard(Core, St0) ->
- {VarName,St} = new_var_name(St0),
- Var = #c_var{name=VarName},
- Try = #c_try{arg=Core,vars=[Var],body=Var,evars=[],handler=#c_literal{val=false}},
- {Try,St}.
-
%% gexpr_test(Kexpr, State) -> {Kexpr,State}.
%% Builds the final boolean test from the last Kexpr in a guard test.
%% Must enter try blocks and isets and find the last Kexpr in them.
%% This must end in a recognised BEAM test!
-gexpr_test(#k_bif{anno=A,op=#k_remote{mod=#k_atom{val=erlang},
- name=#k_atom{val=F},arity=Ar}=Op,
+gexpr_test(#k_bif{anno=A,
+ op=#k_remote{mod=#k_literal{val=erlang},
+ name=#k_literal{val=F},arity=Ar}=Op,
args=Kargs}=Ke, St) ->
%% Either convert to test if ok, or add test.
%% At this stage, erlang:float/1 is not a type test. (It should
@@ -696,7 +212,7 @@ gexpr_test(#k_bif{anno=A,op=#k_remote{mod=#k_atom{val=erlang},
false -> gexpr_test_add(Ke, St) %Add equality test
end;
gexpr_test(#k_try{arg=B0,vars=[#k_var{name=X}],body=#k_var{name=X},
- handler=#k_atom{val=false}}=Try, St0) ->
+ handler=#k_literal{val=false}}=Try, St0) ->
{B,St} = gexpr_test(B0, St0),
%%ok = io:fwrite("~w: ~p~n", [?LINE,{B0,B}]),
{Try#k_try{arg=B},St};
@@ -706,42 +222,41 @@ gexpr_test(#iset{body=B0}=Iset, St0) ->
gexpr_test(Ke, St) -> gexpr_test_add(Ke, St). %Add equality test
gexpr_test_add(Ke, St0) ->
- Test = #k_remote{mod=#k_atom{val='erlang'},
- name=#k_atom{val='=:='},
+ Test = #k_remote{mod=#k_literal{val='erlang'},
+ name=#k_literal{val='=:='},
arity=2},
{Ae,Ap,St1} = force_atomic(Ke, St0),
{pre_seq(Ap, #k_test{anno=get_kanno(Ke),
- op=Test,args=[Ae,#k_atom{val='true'}]}),St1}.
+ op=Test,args=[Ae,#k_literal{val='true'}]}),St1}.
%% expr(Cexpr, Sub, State) -> {Kexpr,[PreKexpr],State}.
%% Convert a Core expression, flattening it at the same time.
-expr(#c_var{anno=A,name={_Name,Arity}}=Fname, Sub, St) ->
- %% A local in an expression.
- %% For now, these are wrapped into a fun by reverse
- %% eta-conversion, but really, there should be exactly one
- %% such "lambda function" for each escaping local name,
- %% instead of one for each occurrence as done now.
+expr(#c_var{anno=A0,name={Name,Arity}}=Fname, Sub, St) ->
Vs = [#c_var{name=list_to_atom("V" ++ integer_to_list(V))} ||
- V <- integers(1, Arity)],
- Fun = #c_fun{anno=A,vars=Vs,body=#c_apply{anno=A,op=Fname,args=Vs}},
- expr(Fun, Sub, St);
+ V <- integers(1, Arity)],
+ case St#kern.no_shared_fun_wrappers of
+ false ->
+ %% Generate a (possibly shared) wrapper function for calling
+ %% this function.
+ Wrapper0 = ["-fun.",atom_to_list(Name),"/",integer_to_list(Arity),"-"],
+ Wrapper = list_to_atom(flatten(Wrapper0)),
+ Id = {id,{0,0,Wrapper}},
+ A = keyreplace(id, 1, A0, Id),
+ Fun = #c_fun{anno=A,vars=Vs,body=#c_apply{anno=A,op=Fname,args=Vs}},
+ expr(Fun, Sub, St);
+ true ->
+ %% For backward compatibility with OTP 22 and earlier,
+ %% use the pre-generated name for the fun wrapper.
+ %% There will be one wrapper function for each occurrence
+ %% of `fun F/A`.
+ Fun = #c_fun{anno=A0,vars=Vs,body=#c_apply{anno=A0,op=Fname,args=Vs}},
+ expr(Fun, Sub, St)
+ end;
expr(#c_var{anno=A,name=V}, Sub, St) ->
{#k_var{anno=A,name=get_vsub(V, Sub)},[],St};
expr(#c_literal{anno=A,val=V}, _Sub, St) ->
- Klit = case V of
- [] ->
- #k_nil{anno=A};
- V when is_integer(V) ->
- #k_int{anno=A,val=V};
- V when is_float(V) ->
- #k_float{anno=A,val=V};
- V when is_atom(V) ->
- #k_atom{anno=A,val=V};
- _ ->
- #k_literal{anno=A,val=V}
- end,
- {Klit,[],St};
+ {#k_literal{anno=A,val=V},[],St};
expr(#c_cons{anno=A,hd=Ch,tl=Ct}, Sub, St0) ->
%% Do cons in two steps, first the expressions left to right, then
%% any remaining literals right to left.
@@ -768,24 +283,12 @@ expr(#c_binary{anno=A,segments=Cv}, Sub, St0) ->
Error = #c_call{anno=A,module=Erl,name=Name,args=Args},
expr(Error, Sub, St1)
end;
-expr(#c_fun{anno=A,vars=Cvs,body=Cb}, Sub0, #kern{ff=OldFF,func=Func}=St0) ->
- FA = case OldFF of
- undefined ->
- Func;
- _ ->
- case lists:keyfind(id, 1, A) of
- {id,{_,_,Name}} -> Name;
- _ ->
- case lists:keyfind(letrec_name, 1, A) of
- {letrec_name,Name} -> Name;
- _ -> unknown_fun
- end
- end
- end,
- {Kvs,Sub1,St1} = pattern_list(Cvs, Sub0, St0#kern{ff=FA}),
+expr(#c_fun{anno=A,vars=Cvs,body=Cb}, Sub0,
+ #kern{fargs=OldFargs}=St0) ->
+ {Kvs,Sub1,St1} = pattern_list(Cvs, Sub0, St0),
%%ok = io:fwrite("~w: ~p~n", [?LINE,{{Cvs,Sub0,St0},{Kvs,Sub1,St1}}]),
- {Kb,Pb,St2} = body(Cb, Sub1, St1#kern{ff=FA}),
- {#ifun{anno=A,vars=Kvs,body=pre_seq(Pb, Kb)},[],St2#kern{ff=OldFF}};
+ {Kb,Pb,St2} = body(Cb, Sub1, St1#kern{fargs=Kvs}),
+ {#ifun{anno=A,vars=Kvs,body=pre_seq(Pb, Kb)},[],St2#kern{fargs=OldFargs}};
expr(#c_seq{arg=Ca,body=Cb}, Sub, St0) ->
{Ka,Pa,St1} = body(Ca, Sub, St0),
{Kb,Pb,St2} = body(Cb, Sub, St1),
@@ -806,104 +309,42 @@ expr(#c_let{anno=A,vars=Cvs,arg=Ca,body=Cb}, Sub0, St0) ->
end,
{Kb,Pb,St3} = body(Cb, Sub1, St2),
{Kb,Pa ++ Sets ++ Pb,St3};
-expr(#c_letrec{anno=A,defs=Cfs,body=Cb}, Sub0, St0) ->
- %% Make new function names and store substitution.
- {Fs0,{Sub1,St1}} =
- mapfoldl(fun ({#c_var{name={F,Ar}},B0}, {Sub,S0}) ->
- {N,St1} = new_fun_name(atom_to_list(F)
- ++ "/" ++
- integer_to_list(Ar),
- S0),
- B = set_kanno(B0, [{letrec_name,N}]),
- {{N,B},{set_fsub(F, Ar, N, Sub),St1}}
- end, {Sub0,St0}, Cfs),
- %% Run translation on functions and body.
- {Fs1,St2} = mapfoldl(fun ({N,Fd0}, S1) ->
- {Fd1,[],St2} = expr(Fd0, Sub1, S1#kern{ff=N}),
- Fd = set_kanno(Fd1, A),
- {{N,Fd},St2}
- end, St1, Fs0),
- {Kb,Pb,St3} = body(Cb, Sub1, St2#kern{ff=St1#kern.ff}),
- {Kb,[#iletrec{anno=A,defs=Fs1}|Pb],St3};
+expr(#c_letrec{anno=A,defs=Cfs,body=Cb}, Sub, St) ->
+ case member(letrec_goto, A) of
+ true ->
+ letrec_goto(Cfs, Cb, Sub, St);
+ false ->
+ letrec_local_function(A, Cfs, Cb, Sub, St)
+ end;
expr(#c_case{arg=Ca,clauses=Ccs}, Sub, St0) ->
{Ka,Pa,St1} = body(Ca, Sub, St0), %This is a body!
{Kvs,Pv,St2} = match_vars(Ka, St1), %Must have variables here!
{Km,St3} = kmatch(Kvs, Ccs, Sub, St2),
- Match = flatten_seq(build_match(Kvs, Km)),
+ Match = flatten_seq(build_match(Km)),
{last(Match),Pa ++ Pv ++ droplast(Match),St3};
-expr(#c_receive{anno=A,clauses=Ccs0,timeout=Ce,action=Ca}, Sub, St0) ->
- {Ke,Pe,St1} = atomic(Ce, Sub, St0), %Force this to be atomic!
- {Rvar,St2} = new_var(St1),
- %% Need to massage accept clauses and add reject clause before matching.
- Ccs1 = map(fun (#c_clause{anno=Banno,body=B0}=C) ->
- B1 = #c_seq{arg=#ireceive_accept{anno=A},body=B0},
- C#c_clause{anno=Banno,body=B1}
- end, Ccs0),
- {Mpat,St3} = new_var_name(St2),
- Rc = #c_clause{anno=[compiler_generated|A],
- pats=[#c_var{name=Mpat}],guard=#c_literal{anno=A,val=true},
- body=#ireceive_next{anno=A}},
- {Km,St4} = kmatch([Rvar], Ccs1 ++ [Rc], Sub, add_var_def(Rvar, St3)),
- {Ka,Pa,St5} = body(Ca, Sub, St4),
- {#k_receive{anno=A,var=Rvar,body=Km,timeout=Ke,action=pre_seq(Pa, Ka)},
- Pe,St5};
expr(#c_apply{anno=A,op=Cop,args=Cargs}, Sub, St) ->
c_apply(A, Cop, Cargs, Sub, St);
-expr(#c_call{anno=A,module=#c_literal{val=erlang},name=#c_literal{val=is_record},
- args=[_,Tag,Sz]=Args0}, Sub, St0) ->
- {Args,Ap,St} = atomic_list(Args0, Sub, St0),
- Remote = #k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=is_record},arity=3},
- case {Tag,Sz} of
- {#c_literal{val=Atom},#c_literal{val=Int}}
- when is_atom(Atom), is_integer(Int) ->
- %% Tag and size are literals. Make it a BIF, which will actually
- %% be expanded out in a later pass.
- {#k_bif{anno=A,op=Remote,args=Args},Ap,St};
- {_,_} ->
- %% (Only in bodies.) Make it into an actual call to the BIF.
- {#k_call{anno=A,op=Remote,args=Args},Ap,St}
- end;
expr(#c_call{anno=A,module=M0,name=F0,args=Cargs}, Sub, St0) ->
Ar = length(Cargs),
- {Type,St1} = case call_type(M0, F0, Ar) of
- error ->
- %% Invalid call (e.g. M:42/3). Issue a warning,
- %% and let the generated code use the old explict apply.
- {old_apply,add_warning(get_line(A), bad_call, A, St0)};
- Type0 ->
- {Type0,St0}
- end,
-
- case Type of
- old_apply ->
+ {[M,F|Kargs],Ap,St1} = atomic_list([M0,F0|Cargs], Sub, St0),
+ Remote = #k_remote{mod=M,name=F,arity=Ar},
+ case call_type(M0, F0, Cargs) of
+ bif ->
+ {#k_bif{anno=A,op=Remote,args=Kargs},Ap,St1};
+ call ->
+ {#k_call{anno=A,op=Remote,args=Kargs},Ap,St1};
+ error ->
+ %% Invalid call (e.g. M:42/3). Issue a warning, and let
+ %% the generated code use the old explict apply.
+ St = add_warning(get_line(A), bad_call, A, St0),
Call = #c_call{anno=A,
module=#c_literal{val=erlang},
name=#c_literal{val=apply},
args=[M0,F0,cerl:make_list(Cargs)]},
- expr(Call, Sub, St1);
- _ ->
- {[M1,F1|Kargs],Ap,St} = atomic_list([M0,F0|Cargs], Sub, St1),
- Call = case Type of
- bif ->
- #k_bif{anno=A,op=#k_remote{mod=M1,name=F1,arity=Ar},
- args=Kargs};
- call ->
- #k_call{anno=A,op=#k_remote{mod=M1,name=F1,arity=Ar},
- args=Kargs};
- apply ->
- #k_call{anno=A,op=#k_remote{mod=M1,name=F1,arity=Ar},
- args=Kargs}
- end,
- {Call,Ap,St}
+ expr(Call, Sub, St)
end;
-expr(#c_primop{anno=A,name=#c_literal{val=match_fail},args=Cargs0}, Sub, St0) ->
- Cargs = translate_match_fail(Cargs0, Sub, A, St0),
- {Kargs,Ap,St} = atomic_list(Cargs, Sub, St0),
- Ar = length(Cargs),
- Call = #k_call{anno=A,op=#k_remote{mod=#k_atom{val=erlang},
- name=#k_atom{val=error},
- arity=Ar},args=Kargs},
- {Call,Ap,St};
+expr(#c_primop{anno=A,name=#c_literal{val=match_fail},args=[Arg]}, Sub, St) ->
+ translate_match_fail(Arg, Sub, A, St);
expr(#c_primop{anno=A,name=#c_literal{val=N},args=Cargs}, Sub, St0) ->
{Kargs,Ap,St1} = atomic_list(Cargs, Sub, St0),
Ar = length(Cargs),
@@ -921,60 +362,90 @@ expr(#c_try{anno=A,arg=Ca,vars=Cvs,body=Cb,evars=Evs,handler=Ch}, Sub0, St0) ->
evars=Kevs,handler=pre_seq(Ph, Kh)},[],St5};
expr(#c_catch{anno=A,body=Cb}, Sub, St0) ->
{Kb,Pb,St1} = body(Cb, Sub, St0),
- {#k_catch{anno=A,body=pre_seq(Pb, Kb)},[],St1};
-%% Handle internal expressions.
-expr(#ireceive_accept{anno=A}, _Sub, St) -> {#k_receive_accept{anno=A},[],St}.
-
-%% Translate a function_clause exception to a case_clause exception if
-%% it has been moved into another function. (A function_clause exception
-%% will not work correctly if it is moved into another function, or
-%% even if it is invoked not from the top level in the correct function.)
-translate_match_fail(Args, Sub, Anno, St) ->
- case Args of
- [#c_tuple{es=[#c_literal{val=function_clause}|As]}] ->
- translate_match_fail_1(Anno, As, Sub, St);
- [#c_literal{val=Tuple}] when is_tuple(Tuple) ->
- %% The inliner may have created a literal out of
- %% the original #c_tuple{}.
- case tuple_to_list(Tuple) of
- [function_clause|As0] ->
- As = [#c_literal{val=E} || E <- As0],
- translate_match_fail_1(Anno, As, Sub, St);
- _ ->
- Args
- end;
- _ ->
- %% Not a function_clause exception.
- Args
+ {#k_catch{anno=A,body=pre_seq(Pb, Kb)},[],St1}.
+
+%% Implement letrec in the traditional way as a local
+%% function for each definition in the letrec.
+
+letrec_local_function(A, Cfs, Cb, Sub0, St0) ->
+ %% Make new function names and store substitution.
+ {Fs0,{Sub1,St1}} =
+ mapfoldl(fun ({#c_var{name={F,Ar}},B0}, {Sub,S0}) ->
+ {N,St1} = new_fun_name(atom_to_list(F)
+ ++ "/" ++
+ integer_to_list(Ar),
+ S0),
+ B = set_kanno(B0, [{letrec_name,N}]),
+ {{N,B},{set_fsub(F, Ar, N, Sub),St1}}
+ end, {Sub0,St0}, Cfs),
+ %% Run translation on functions and body.
+ {Fs1,St2} = mapfoldl(fun ({N,Fd0}, S1) ->
+ {Fd1,[],St2} = expr(Fd0, Sub1, S1),
+ Fd = set_kanno(Fd1, A),
+ {{N,Fd},St2}
+ end, St1, Fs0),
+ {Kb,Pb,St3} = body(Cb, Sub1, St2),
+ {Kb,[#iletrec{anno=A,defs=Fs1}|Pb],St3}.
+
+%% Implement letrec with the single definition as a label and each
+%% apply of it as a goto.
+
+letrec_goto([{#c_var{name={Label,0}},Cfail}], Cb, Sub0,
+ #kern{labels=Labels0}=St0) ->
+ Labels = cerl_sets:add_element(Label, Labels0),
+ {Kb,Pb,St1} = body(Cb, Sub0, St0#kern{labels=Labels}),
+ #c_fun{body=FailBody} = Cfail,
+ {Kfail,Fb,St2} = body(FailBody, Sub0, St1),
+ case {Kb,Kfail,Fb} of
+ {#k_goto{label=Label},#k_goto{}=InnerGoto,[]} ->
+ {InnerGoto,Pb,St2};
+ {_,_,_} ->
+ St3 = St2#kern{labels=Labels0},
+ Alt = #k_letrec_goto{label=Label,first=Kb,then=pre_seq(Fb, Kfail)},
+ {Alt,Pb,St3}
end.
-translate_match_fail_1(Anno, As, Sub, #kern{ff=FF}) ->
- AnnoFunc = case keyfind(function_name, 1, Anno) of
- false ->
- none; %Force rewrite.
- {function_name,{Name,Arity}} ->
- {get_fsub(Name, Arity, Sub),Arity}
- end,
- case {AnnoFunc,FF} of
- {Same,Same} ->
- %% Still in the correct function.
- translate_fc(As);
- {{F,_},F} ->
- %% Still in the correct function.
- translate_fc(As);
- _ ->
- %% Wrong function or no function_name annotation.
- %%
- %% The inliner has copied the match_fail(function_clause)
- %% primop from another function (or from another instance of
- %% the current function). match_fail(function_clause) will
- %% only work at the top level of the function it was originally
- %% defined in, so we will need to rewrite it to a case_clause.
- [c_tuple([#c_literal{val=case_clause},c_tuple(As)])]
+%% translate_match_fail(Arg, Sub, Anno, St) -> {Kexpr,[PreKexpr],State}.
+%% Translate a match_fail primop to a call erlang:error/1 or
+%% erlang:error/2.
+
+translate_match_fail(Arg, Sub, Anno, St0) ->
+ Cargs = case {cerl:data_type(Arg),cerl:data_es(Arg)} of
+ {tuple,[#c_literal{val=function_clause}|As]} ->
+ translate_fc_args(As, Sub, St0);
+ {_,_} ->
+ [Arg]
+ end,
+ {Kargs,Ap,St} = atomic_list(Cargs, Sub, St0),
+ Ar = length(Cargs),
+ Call = #k_call{anno=Anno,
+ op=#k_remote{mod=#k_literal{val=erlang},
+ name=#k_literal{val=error},
+ arity=Ar},args=Kargs},
+ {Call,Ap,St}.
+
+translate_fc_args(As, Sub, #kern{fargs=Fargs}) ->
+ case same_args(As, Fargs, Sub) of
+ true ->
+ %% The arguments for the `function_clause` exception are
+ %% the arguments for the current function in the correct
+ %% order.
+ [#c_literal{val=function_clause},cerl:make_list(As)];
+ false ->
+ %% The arguments in the `function_clause` exception don't
+ %% match the arguments for the current function because
+ %% of inlining. Keeping the `function_clause`
+ %% exception reason would be confusing. Rewrite it to
+ %% a `case_clause` exception with the arguments in a
+ %% tuple.
+ [cerl:c_tuple([#c_literal{val=case_clause},
+ cerl:c_tuple(As)])]
end.
-translate_fc(Args) ->
- [#c_literal{val=function_clause},cerl:make_list(Args)].
+same_args([#c_var{name=Cv}|Vs], [#k_var{name=Kv}|As], Sub) ->
+ get_vsub(Cv, Sub) =:= Kv andalso same_args(Vs, As, Sub);
+same_args([], [], _Sub) -> true;
+same_args(_, _, _) -> false.
expr_map(A,Var0,Ces,Sub,St0) ->
{Var,Mps,St1} = expr(Var0, Sub, St0),
@@ -1031,45 +502,43 @@ map_group_pairs(A, Var, Pairs0, Esp, St0) ->
end.
map_remove_dup_keys(Es) ->
- dict:to_list(map_remove_dup_keys(Es, dict:new())).
+ map_remove_dup_keys(Es, #{}).
-map_remove_dup_keys([{assoc,K0,V}|Es0],Used0) ->
+map_remove_dup_keys([{assoc,K0,V}|Es0], Used0) ->
K = map_key_clean(K0),
- Op = case dict:find(K, Used0) of
- {ok,{exact,_,_}} -> exact;
- _ -> assoc
- end,
- Used1 = dict:store(K, {Op,K0,V}, Used0),
+ Op = case Used0 of
+ #{K:={exact,_,_}} -> exact;
+ #{} -> assoc
+ end,
+ Used1 = Used0#{K=>{Op,K0,V}},
map_remove_dup_keys(Es0, Used1);
-map_remove_dup_keys([{exact,K0,V}|Es0],Used0) ->
+map_remove_dup_keys([{exact,K0,V}|Es0], Used0) ->
K = map_key_clean(K0),
- Op = case dict:find(K, Used0) of
- {ok,{assoc,_,_}} -> assoc;
- _ -> exact
- end,
- Used1 = dict:store(K, {Op,K0,V}, Used0),
+ Op = case Used0 of
+ #{K:={assoc,_,_}} -> assoc;
+ #{} -> exact
+ end,
+ Used1 = Used0#{K=>{Op,K0,V}},
map_remove_dup_keys(Es0, Used1);
-map_remove_dup_keys([], Used) -> Used.
+map_remove_dup_keys([], Used) ->
+ %% We must sort the map entries to ensure consistent
+ %% order from compilation to compilation.
+ sort(maps:to_list(Used)).
-%% Be explicit instead of using set_kanno(K, []).
+%% Clean a map key from annotations.
map_key_clean(#k_var{name=V}) -> {var,V};
-map_key_clean(#k_literal{val=V}) -> {lit,V};
-map_key_clean(#k_int{val=V}) -> {lit,V};
-map_key_clean(#k_float{val=V}) -> {lit,V};
-map_key_clean(#k_atom{val=V}) -> {lit,V};
-map_key_clean(#k_nil{}) -> {lit,[]}.
-
+map_key_clean(#k_literal{val=V}) -> {lit,V}.
-%% call_type(Module, Function, Arity) -> call | bif | apply | error.
+%% call_type(Module, Function, Arity) -> call | bif | error.
%% Classify the call.
-call_type(#c_literal{val=M}, #c_literal{val=F}, Ar) when is_atom(M), is_atom(F) ->
- case is_remote_bif(M, F, Ar) of
+call_type(#c_literal{val=M}, #c_literal{val=F}, As) when is_atom(M), is_atom(F) ->
+ case is_remote_bif(M, F, As) of
false -> call;
true -> bif
end;
-call_type(#c_var{}, #c_literal{val=A}, _) when is_atom(A) -> apply;
-call_type(#c_literal{val=A}, #c_var{}, _) when is_atom(A) -> apply;
-call_type(#c_var{}, #c_var{}, _) -> apply;
+call_type(#c_var{}, #c_literal{val=A}, _) when is_atom(A) -> call;
+call_type(#c_literal{val=A}, #c_var{}, _) when is_atom(A) -> call;
+call_type(#c_var{}, #c_var{}, _) -> call;
call_type(_, _, _) -> error.
%% match_vars(Kexpr, State) -> {[Kvar],[PreKexpr],State}.
@@ -1085,13 +554,19 @@ match_vars(Ka, St0) ->
{[V],Vp,St1}.
%% c_apply(A, Op, [Carg], Sub, State) -> {Kexpr,[PreKexpr],State}.
-%% Transform application, detect which are guaranteed to be bifs.
+%% Transform application.
-c_apply(A, #c_var{anno=Ra,name={F0,Ar}}, Cargs, Sub, St0) ->
- {Kargs,Ap,St1} = atomic_list(Cargs, Sub, St0),
- F1 = get_fsub(F0, Ar, Sub), %Has it been rewritten
- {#k_call{anno=A,op=#k_local{anno=Ra,name=F1,arity=Ar},args=Kargs},
- Ap,St1};
+c_apply(A, #c_var{anno=Ra,name={F0,Ar}}, Cargs, Sub, #kern{labels=Labels}=St0) ->
+ case Ar =:= 0 andalso cerl_sets:is_element(F0, Labels) of
+ true ->
+ %% This is a goto to a label in a letrec_goto construct.
+ {#k_goto{label=F0},[],St0};
+ false ->
+ {Kargs,Ap,St1} = atomic_list(Cargs, Sub, St0),
+ F1 = get_fsub(F0, Ar, Sub), %Has it been rewritten
+ {#k_call{anno=A,op=#k_local{anno=Ra,name=F1,arity=Ar},args=Kargs},
+ Ap,St1}
+ end;
c_apply(A, Cop, Cargs, Sub, St0) ->
{Kop,Op,St1} = variable(Cop, Sub, St0),
{Kargs,Ap,St2} = atomic_list(Cargs, Sub, St1),
@@ -1125,12 +600,6 @@ force_atomic(Ke, St0) ->
{V,[#iset{vars=[V],arg=Ke}],St1}
end.
-% force_atomic_list(Kes, St) ->
-% foldr(fun (Ka, {As,Asp,St0}) ->
-% {A,Ap,St1} = force_atomic(Ka, St0),
-% {[A|As],Ap ++ Asp,St1}
-% end, {[],[],St}, Kes).
-
atomic_bin([#c_bitstr{anno=A,val=E0,size=S0,unit=U0,type=T,flags=Fs0}|Es0],
Sub, St0) ->
{E,Ap1,St1} = atomic(E0, Sub, St0),
@@ -1148,11 +617,14 @@ atomic_bin([#c_bitstr{anno=A,val=E0,size=S0,unit=U0,type=T,flags=Fs0}|Es0],
atomic_bin([], _Sub, St) -> {#k_bin_end{},[],St}.
validate_bin_element_size(#k_var{}) -> ok;
-validate_bin_element_size(#k_int{val=V}) when V >= 0 -> ok;
-validate_bin_element_size(#k_atom{val=all}) -> ok;
-validate_bin_element_size(#k_atom{val=undefined}) -> ok;
-validate_bin_element_size(_) -> throw(bad_element_size).
-
+validate_bin_element_size(#k_literal{val=Val}) ->
+ case Val of
+ all -> ok;
+ undefined -> ok;
+ _ when is_integer(Val), Val >= 0 -> ok;
+ _ -> throw(bad_element_size)
+ end.
+
%% atomic_list([Cexpr], Sub, State) -> {[Kexpr],[PreKexpr],State}.
atomic_list(Ces, Sub, St) ->
@@ -1162,15 +634,11 @@ atomic_list(Ces, Sub, St) ->
end, {[],[],St}, Ces).
%% is_atomic(Kexpr) -> boolean().
-%% Is a Kexpr atomic? Strings are NOT considered atomic!
+%% Is a Kexpr atomic?
is_atomic(#k_literal{}) -> true;
-is_atomic(#k_int{}) -> true;
-is_atomic(#k_float{}) -> true;
-is_atomic(#k_atom{}) -> true;
-%%is_atomic(#k_char{}) -> true; %No characters
-is_atomic(#k_nil{}) -> true;
is_atomic(#k_var{}) -> true;
+%%is_atomic(#k_char{}) -> true; %No characters
is_atomic(_) -> false.
%% variable(Cexpr, Sub, State) -> {Kvar,[PreKexpr],State}.
@@ -1234,7 +702,7 @@ flatten_alias(Pat) -> {[],Pat}.
pattern_map_pairs(Ces0, Isub, Osub0, St0) ->
%% pattern the pair keys and values as normal
- {Kes,{Osub1,St1}} = lists:mapfoldl(fun
+ {Kes,{Osub1,St1}} = mapfoldl(fun
(#c_map_pair{anno=A,key=Ck,val=Cv},{Osubi0,Sti0}) ->
{Kk,[],Sti1} = expr(Ck, Isub, Sti0),
{Kv,Osubi2,Sti2} = pattern(Cv, Isub, Osubi0, Sti1),
@@ -1242,7 +710,7 @@ pattern_map_pairs(Ces0, Isub, Osub0, St0) ->
end, {Osub0, St0}, Ces0),
%% It is later assumed that these keys are term sorted
%% so we need to sort them here
- Kes1 = lists:sort(fun
+ Kes1 = sort(fun
(#k_map_pair{key=KkA},#k_map_pair{key=KkB}) ->
A = map_key_clean(KkA),
B = map_key_clean(KkB),
@@ -1250,41 +718,85 @@ pattern_map_pairs(Ces0, Isub, Osub0, St0) ->
end, Kes),
{Kes1,Osub1,St1}.
-pattern_bin(Es, Isub, Osub0, St0) ->
- {Kbin,{_,Osub},St} = pattern_bin_1(Es, Isub, Osub0, St0),
- {Kbin,Osub,St}.
+pattern_bin(Es, Isub, Osub0, St) ->
+ pattern_bin_1(Es, Isub, Osub0, St).
-pattern_bin_1([#c_bitstr{anno=A,val=E0,size=S0,unit=U,type=T,flags=Fs}|Es0],
- Isub0, Osub0, St0) ->
- {S1,[],St1} = expr(S0, Isub0, St0),
+pattern_bin_1([#c_bitstr{anno=A,val=E0,size=S0,unit=U0,type=T,flags=Fs0}|Es0],
+ Isub, Osub0, St0) ->
+ {S1,[],St1} = expr(S0, Isub, St0),
S = case S1 of
- #k_int{} -> S1;
#k_var{} -> S1;
- #k_atom{} -> S1;
+ #k_literal{val=Val} when is_integer(Val); is_atom(Val) -> S1;
_ ->
%% Bad size (coming from an optimization or Core Erlang
%% source code) - replace it with a known atom because
%% a literal or bit syntax construction can cause further
%% problems.
- #k_atom{val=bad_size}
+ #k_literal{val=bad_size}
end,
- U0 = cerl:concrete(U),
- Fs0 = cerl:concrete(Fs),
- %%ok= io:fwrite("~w: ~p~n", [?LINE,{B0,S,U0,Fs0}]),
- {E,Osub1,St2} = pattern(E0, Isub0, Osub0, St1),
- Isub1 = case E0 of
- #c_var{name=V} ->
- set_vsub(V, E#k_var.name, Isub0);
- _ -> Isub0
- end,
- {Es,{Isub,Osub},St3} = pattern_bin_1(Es0, Isub1, Osub1, St2),
- {#k_bin_seg{anno=A,size=S,
- unit=U0,
- type=cerl:concrete(T),
- flags=Fs0,
- seg=E,next=Es},
- {Isub,Osub},St3};
-pattern_bin_1([], Isub, Osub, St) -> {#k_bin_end{},{Isub,Osub},St}.
+ U = cerl:concrete(U0),
+ Fs = cerl:concrete(Fs0),
+ {E,Osub1,St2} = pattern(E0, Isub, Osub0, St1),
+ {Es,Osub,St3} = pattern_bin_1(Es0, Isub, Osub1, St2),
+ {build_bin_seg(A, S, U, cerl:concrete(T), Fs, E, Es),Osub,St3};
+pattern_bin_1([], _Isub, Osub, St) ->
+ {#k_bin_end{},Osub,St}.
+
+%% build_bin_seg(Anno, Size, Unit, Type, Flags, Seg, Next) -> #k_bin_seg{}.
+%% This function normalizes literal integers with size > 8 and literal
+%% utf8 segments into integers with size = 8 (and potentially an integer
+%% with size less than 8 at the end). This is so further optimizations
+%% have a normalized view of literal integers, allowing us to generate
+%% more literals and group more clauses. Those integers may be "squeezed"
+%% later into the largest integer possible.
+%%
+build_bin_seg(A, #k_literal{val=Bits} = Sz, U, integer=Type,
+ [unsigned,big]=Flags, #k_literal{val=Int}=Seg, Next) when is_integer(Bits) ->
+ Size = Bits * U,
+ case integer_fits_and_is_expandable(Int, Size) of
+ true -> build_bin_seg_integer_recur(A, Size, Int, Next);
+ false -> #k_bin_seg{anno=A,size=Sz,unit=U,type=Type,flags=Flags,seg=Seg,next=Next}
+ end;
+build_bin_seg(A, Sz, U, utf8=Type, [unsigned,big]=Flags, #k_literal{val=Utf8} = Seg, Next) ->
+ case utf8_fits(Utf8) of
+ {Int, Bits} -> build_bin_seg_integer_recur(A, Bits, Int, Next);
+ error -> #k_bin_seg{anno=A,size=Sz,unit=U,type=Type,flags=Flags,seg=Seg,next=Next}
+ end;
+build_bin_seg(A, Sz, U, Type, Flags, Seg, Next) ->
+ #k_bin_seg{anno=A,size=Sz,unit=U,type=Type,flags=Flags,seg=Seg,next=Next}.
+
+build_bin_seg_integer_recur(A, Bits, Val, Next) when Bits > 8 ->
+ NextBits = Bits - 8,
+ NextVal = Val band ((1 bsl NextBits) - 1),
+ Last = build_bin_seg_integer_recur(A, NextBits, NextVal, Next),
+ build_bin_seg_integer(A, 8, Val bsr NextBits, Last);
+
+build_bin_seg_integer_recur(A, Bits, Val, Next) ->
+ build_bin_seg_integer(A, Bits, Val, Next).
+
+build_bin_seg_integer(A, Bits, Val, Next) ->
+ Sz = #k_literal{anno=A,val=Bits},
+ Seg = #k_literal{anno=A,val=Val},
+ #k_bin_seg{anno=A,size=Sz,unit=1,type=integer,flags=[unsigned,big],seg=Seg,next=Next}.
+
+integer_fits_and_is_expandable(Int, Size) when is_integer(Int), is_integer(Size),
+ 0 < Size, Size =< ?EXPAND_MAX_SIZE_SEGMENT ->
+ case <<Int:Size>> of
+ <<Int:Size>> -> true;
+ _ -> false
+ end;
+integer_fits_and_is_expandable(_Int, _Size) ->
+ false.
+
+utf8_fits(Utf8) ->
+ try
+ Bin = <<Utf8/utf8>>,
+ Bits = bit_size(Bin),
+ <<Int:Bits>> = Bin,
+ {Int, Bits}
+ catch
+ _:_ -> error
+ end.
%% pattern_list([Cexpr], Sub, State) -> {[Kexpr],Sub,State}.
@@ -1411,23 +923,25 @@ new_vars(0, St, Vs) -> {Vs,St}.
make_vars(Vs) -> [ #k_var{name=V} || V <- Vs ].
-add_var_def(V, St) ->
- St#kern{ds=cerl_sets:add_element(V#k_var.name, St#kern.ds)}.
-
-%%add_vars_def(Vs, St) ->
-%% Ds = foldl(fun (#k_var{name=V}, Ds) -> add_element(V, Ds) end,
-%% St#kern.ds, Vs),
-%% St#kern{ds=Ds}.
-
%% is_remote_bif(Mod, Name, Arity) -> true | false.
%% Test if function is really a BIF.
-is_remote_bif(erlang, get, 1) -> true;
-is_remote_bif(erlang, N, A) ->
- case erl_internal:guard_bif(N, A) of
+is_remote_bif(erlang, get, [_]) -> true;
+is_remote_bif(erlang, is_record, [_,Tag,Sz]) ->
+ case {Tag,Sz} of
+ {#c_literal{val=Atom},#c_literal{val=Int}}
+ when is_atom(Atom), is_integer(Int) ->
+ %% Tag and size are literals. This is a guard BIF.
+ true;
+ {_,_} ->
+ false
+ end;
+is_remote_bif(erlang, N, As) ->
+ Arity = length(As),
+ case erl_internal:guard_bif(N, Arity) of
true -> true;
false ->
- try erl_internal:op_type(N, A) of
+ try erl_internal:op_type(N, Arity) of
arith -> true;
bool -> true;
comp -> true;
@@ -1445,6 +959,7 @@ is_remote_bif(_, _, _) -> false.
%% return multiple values. Only used in bodies where a BIF may be
%% called for effect only.
+bif_vals(recv_peek_message, 0) -> 2;
bif_vals(_, _) -> 1.
bif_vals(_, _, _) -> 1.
@@ -1488,11 +1003,6 @@ foldr2(_, Acc, [], []) -> Acc.
kmatch(Us, Ccs, Sub, St0) ->
{Cs,St1} = match_pre(Ccs, Sub, St0), %Convert clauses
Def = fail,
-%% Def = #k_call{anno=[compiler_generated],
-%% op=#k_remote{mod=#k_atom{val=erlang},
-%% name=#k_atom{val=exit},
-%% arity=1},
-%% args=[#k_atom{val=kernel_match_error}]},
match(Us, Cs, Def, St1). %Do the match.
%% match_pre([Cclause], Sub, State) -> {[Clause],State}.
@@ -1566,7 +1076,7 @@ maybe_add_warning(Ke, MatchAnno, St) ->
get_line([Line|_]) when is_integer(Line) -> Line;
get_line([_|T]) -> get_line(T);
get_line([]) -> none.
-
+
get_file([{file,File}|_]) -> File;
get_file([_|T]) -> get_file(T);
get_file([]) -> "no_file". % should not happen
@@ -1682,31 +1192,27 @@ expand_pat_lit_clause(#iclause{pats=[#k_literal{anno=A,val=Val}|Ps]}=C) ->
expand_pat_lit_clause(C) -> C.
expand_pat_lit([H|T], A) ->
- #k_cons{anno=A,hd=literal(H, A),tl=literal(T, A)};
+ #k_cons{anno=A,hd=#k_literal{anno=A,val=H},tl=#k_literal{anno=A,val=T}};
expand_pat_lit(Tuple, A) when is_tuple(Tuple) ->
- #k_tuple{anno=A,es=[literal(E, A) || E <- tuple_to_list(Tuple)]};
+ #k_tuple{anno=A,es=[#k_literal{anno=A,val=E} || E <- tuple_to_list(Tuple)]};
expand_pat_lit(Lit, A) ->
- literal(Lit, A).
-
-literal([], A) ->
- #k_nil{anno=A};
-literal(Val, A) when is_integer(Val) ->
- #k_int{anno=A,val=Val};
-literal(Val, A) when is_float(Val) ->
- #k_float{anno=A,val=Val};
-literal(Val, A) when is_atom(Val) ->
- #k_atom{anno=A,val=Val};
-literal(Val, A) when is_list(Val); is_tuple(Val) ->
- #k_literal{anno=A,val=Val}.
+ #k_literal{anno=A,val=Lit}.
%% opt_singled_valued([{Type,Clauses}]) -> [{Type,Clauses}].
-%% If a type only has one clause and if the pattern is literal,
-%% the matching can be done more efficiently by directly comparing
-%% with the literal (that is especially true for binaries).
+%% If a type only has one clause and if the pattern is a complex
+%% literal, the matching can be done more efficiently by directly
+%% comparing with the literal (that is especially true for binaries).
+%%
+%% It is important not to do this transformation for atomic literals
+%% (such as `[]`), since that would cause the test for an emtpy list
+%% to be executed before the test for a nonempty list.
opt_single_valued(Ttcs) ->
opt_single_valued(Ttcs, [], []).
+opt_single_valued([{_,[#iclause{pats=[#k_literal{}|_]}]}=Ttc|Ttcs], TtcAcc, LitAcc) ->
+ %% This is an atomic literal.
+ opt_single_valued(Ttcs, [Ttc|TtcAcc], LitAcc);
opt_single_valued([{_,[#iclause{pats=[P0|Ps]}=Tc]}=Ttc|Ttcs], TtcAcc, LitAcc) ->
try combine_lit_pat(P0) of
P ->
@@ -1736,26 +1242,13 @@ opt_single_valued([], TtcAcc, LitAcc) ->
combine_lit_pat(#ialias{pat=Pat0}=Alias) ->
Pat = combine_lit_pat(Pat0),
Alias#ialias{pat=Pat};
+combine_lit_pat(#k_literal{}) ->
+ %% This is an atomic literal. Rewriting would be a pessimization,
+ %% especially for `[]`.
+ throw(not_possible);
combine_lit_pat(Pat) ->
- case do_combine_lit_pat(Pat) of
- #k_literal{val=Val} when is_atom(Val) ->
- throw(not_possible);
- #k_literal{val=Val} when is_number(Val) ->
- throw(not_possible);
- #k_literal{val=[]} ->
- throw(not_possible);
- #k_literal{}=Lit ->
- Lit
- end.
+ do_combine_lit_pat(Pat).
-do_combine_lit_pat(#k_atom{anno=A,val=Val}) ->
- #k_literal{anno=A,val=Val};
-do_combine_lit_pat(#k_float{anno=A,val=Val}) ->
- #k_literal{anno=A,val=Val};
-do_combine_lit_pat(#k_int{anno=A,val=Val}) ->
- #k_literal{anno=A,val=Val};
-do_combine_lit_pat(#k_nil{anno=A}) ->
- #k_literal{anno=A,val=[]};
do_combine_lit_pat(#k_binary{anno=A,segs=Segs}) ->
Bin = combine_bin_segs(Segs),
#k_literal{anno=A,val=Bin};
@@ -1774,27 +1267,10 @@ do_combine_lit_pat(#k_tuple{anno=A,es=Es0}) ->
do_combine_lit_pat(_) ->
throw(not_possible).
-combine_bin_segs(#k_bin_seg{size=Size0,unit=Unit,type=integer,
- flags=[unsigned,big],seg=Seg,next=Next}) ->
- #k_literal{val=Size1} = do_combine_lit_pat(Size0),
- #k_literal{val=Int} = do_combine_lit_pat(Seg),
- Size = Size1 * Unit,
- if
- 0 < Size, Size < 64 ->
- Bin = <<Int:Size>>,
- case Bin of
- <<Int:Size>> ->
- NextBin = combine_bin_segs(Next),
- <<Bin/bits,NextBin/bits>>;
- _ ->
- %% The integer Int does not fit in the segment,
- %% thus it will not match.
- throw(not_possible)
- end;
- true ->
- %% Avoid creating huge binary literals.
- throw(not_possible)
- end;
+combine_bin_segs(#k_bin_seg{size=#k_literal{val=8},unit=1,type=integer,
+ flags=[unsigned,big],seg=#k_literal{val=Int},next=Next})
+ when is_integer(Int), 0 =< Int, Int =< 255 ->
+ <<Int,(combine_bin_segs(Next))/bits>>;
combine_bin_segs(#k_bin_end{}) ->
<<>>;
combine_bin_segs(_) ->
@@ -1862,13 +1338,12 @@ handle_bin_con_not_possible([]) -> [].
%% exception is thrown.
select_bin_int([#iclause{pats=[#k_bin_seg{anno=A,type=integer,
- size=#k_int{val=Bits0}=Sz,unit=U,
- flags=Fl,seg=#k_literal{val=Val},
- next=N}|Ps]}=C|Cs0])
- when is_integer(Val) ->
+ size=#k_literal{val=Bits0}=Sz,unit=U,
+ flags=Fl,seg=#k_literal{val=Val},
+ next=N}|Ps]}=C|Cs0]) when is_integer(Bits0) ->
Bits = U * Bits0,
if
- Bits > 1024 -> throw(not_possible); %Expands the code too much.
+ Bits > ?EXPAND_MAX_SIZE_SEGMENT -> throw(not_possible); %Expands the code too much.
true -> ok
end,
select_assert_match_possible(Bits, Val, Fl),
@@ -1879,20 +1354,10 @@ select_bin_int([#iclause{pats=[#k_bin_seg{anno=A,type=integer,
end,
Cs = select_bin_int_1(Cs0, Bits, Fl, Val),
[{k_bin_int,[C#iclause{pats=[P|Ps]}|Cs]}];
-select_bin_int([#iclause{pats=[#k_bin_seg{anno=A,type=utf8,
- flags=[unsigned,big]=Fl,
- seg=#k_literal{val=Val0},
- next=N}|Ps]}=C|Cs0])
- when is_integer(Val0) ->
- {Val,Bits} = select_utf8(Val0),
- P = #k_bin_int{anno=A,size=#k_int{val=Bits},unit=1,
- flags=Fl,val=Val,next=N},
- Cs = select_bin_int_1(Cs0, Bits, Fl, Val),
- [{k_bin_int,[C#iclause{pats=[P|Ps]}|Cs]}];
select_bin_int(_) -> throw(not_possible).
select_bin_int_1([#iclause{pats=[#k_bin_seg{anno=A,type=integer,
- size=#k_int{val=Bits0}=Sz,
+ size=#k_literal{val=Bits0}=Sz,
unit=U,
flags=Fl,seg=#k_literal{val=Val},
next=N}|Ps]}=C|Cs],
@@ -1903,22 +1368,11 @@ select_bin_int_1([#iclause{pats=[#k_bin_seg{anno=A,type=integer,
end,
P = #k_bin_int{anno=A,size=Sz,unit=U,flags=Fl,val=Val,next=N},
[C#iclause{pats=[P|Ps]}|select_bin_int_1(Cs, Bits, Fl, Val)];
-select_bin_int_1([#iclause{pats=[#k_bin_seg{anno=A,type=utf8,
- flags=Fl,
- seg=#k_literal{val=Val0},
- next=N}|Ps]}=C|Cs],
- Bits, Fl, Val) when is_integer(Val0) ->
- case select_utf8(Val0) of
- {Val,Bits} -> ok;
- {_,_} -> throw(not_possible)
- end,
- P = #k_bin_int{anno=A,size=#k_int{val=Bits},unit=1,
- flags=[unsigned,big],val=Val,next=N},
- [C#iclause{pats=[P|Ps]}|select_bin_int_1(Cs, Bits, Fl, Val)];
select_bin_int_1([], _, _, _) -> [];
select_bin_int_1(_, _, _, _) -> throw(not_possible).
-select_assert_match_possible(Sz, Val, Fs) ->
+select_assert_match_possible(Sz, Val, Fs)
+ when is_integer(Sz), Sz >= 0, is_integer(Val) ->
EmptyBindings = erl_eval:new_bindings(),
MatchFun = match_fun(Val),
EvalFun = fun({integer,_,S}, B) -> {value,S,B} end,
@@ -1933,24 +1387,15 @@ select_assert_match_possible(Sz, Val, Fs) ->
catch
throw:nomatch ->
throw(not_possible)
- end.
+ end;
+select_assert_match_possible(_, _, _) ->
+ throw(not_possible).
match_fun(Val) ->
fun(match, {{integer,_,_},NewV,Bs}) when NewV =:= Val ->
{match,Bs}
end.
-select_utf8(Val0) ->
- try
- Bin = <<Val0/utf8>>,
- Size = bit_size(Bin),
- <<Val:Size>> = Bin,
- {Val,Size}
- catch
- error:_ ->
- throw(not_possible)
- end.
-
%% match_value([Var], Con, [Clause], Default, State) -> {SelectExpr,State}.
%% At this point all the clauses have the same constructor, we must
%% now separate them according to value.
@@ -1961,104 +1406,108 @@ match_value(Us0, T, Cs0, Def, St0) ->
%%ok = io:format("match_value ~p ~p~n", [T, Css]),
mapfoldl(fun ({Us,Cs}, St) -> match_clause(Us, Cs, Def, St) end, St1, UCss).
-%% partition_intersection
-%% Partitions a map into two maps with the most common keys to the first map.
+%% partition_intersection(Type, Us, [Clause], State) -> {Us,Cs,State}.
+%% Partitions a map into two maps with the most common keys to the
+%% first map.
+%%
%% case <M> of
-%% <#{a}>
%% <#{a,b}>
%% <#{a,c}>
-%% <#{c}>
+%% <#{a}>
%% end
+%%
%% becomes
+%%
%% case <M,M> of
-%% <#{a}, #{ }>
%% <#{a}, #{b}>
-%% <#{ }, #{c}>
%% <#{a}, #{c}>
+%% <#{a}, #{ }>
%% end
-%% The intention is to group as many keys together as possible and thus
-%% reduce the number of lookups to that key.
-partition_intersection(k_map, [U|_]=Us0, [_,_|_]=Cs0,St0) ->
+%%
+%% The intention is to group as many keys together as possible and
+%% thus reduce the number of lookups to that key.
+
+partition_intersection(k_map, [U|_]=Us, [_,_|_]=Cs0, St0) ->
Ps = [clause_val(C) || C <- Cs0],
- case find_key_partition(Ps) of
- no_partition ->
- {Us0,Cs0,St0};
+ case find_key_intersection(Ps) of
+ none ->
+ {Us,Cs0,St0};
Ks ->
- {Cs1,St1} = mapfoldl(fun(#iclause{pats=[Arg|Args]}=C, Sti) ->
- {{Arg1,Arg2},St} = partition_key_intersection(Arg, Ks, Sti),
- {C#iclause{pats=[Arg1,Arg2|Args]}, St}
- end, St0, Cs0),
- {[U|Us0],Cs1,St1}
+ Cs1 = map(fun(#iclause{pats=[Arg|Args]}=C) ->
+ {Arg1,Arg2} = partition_keys(Arg, Ks),
+ C#iclause{pats=[Arg1,Arg2|Args]}
+ end, Cs0),
+ {[U|Us],Cs1,St0}
end;
partition_intersection(_, Us, Cs, St) ->
{Us,Cs,St}.
-partition_key_intersection(#k_map{es=Pairs}=Map,Ks,St0) ->
- F = fun(#k_map_pair{key=Key}) -> member(map_key_clean(Key), Ks) end,
+partition_keys(#k_map{es=Pairs}=Map, Ks) ->
+ F = fun(#k_map_pair{key=Key}) ->
+ cerl_sets:is_element(map_key_clean(Key), Ks)
+ end,
{Ps1,Ps2} = partition(F, Pairs),
- {{Map#k_map{es=Ps1},Map#k_map{es=Ps2}},St0};
-partition_key_intersection(#ialias{pat=Map}=Alias,Ks,St0) ->
- %% only alias one of them
- {{Map1,Map2},St1} = partition_key_intersection(Map, Ks, St0),
- {{Map1,Alias#ialias{pat=Map2}},St1}.
-
-% Only check for the complete intersection of keys and not commonality
-find_key_partition(Ps) ->
- Sets = [sets:from_list(Ks)||Ks <- Ps],
- Is = sets:intersection(Sets),
- case sets:to_list(Is) of
- [] -> no_partition;
- KeyIntersection ->
- %% Check if the intersection are all keys in all clauses.
- %% Don't split if they are since this will only
- %% infer extra is_map instructions with no gain.
- All = foldl(fun (Kset, Bool) ->
- Bool andalso sets:is_subset(Kset, Is)
- end, true, Sets),
- if All -> no_partition;
- true -> KeyIntersection
+ {Map#k_map{es=Ps1},Map#k_map{es=Ps2}};
+partition_keys(#ialias{pat=Map}=Alias, Ks) ->
+ %% Only alias one of them.
+ {Map1,Map2} = partition_keys(Map, Ks),
+ {Map1,Alias#ialias{pat=Map2}}.
+
+find_key_intersection(Ps) ->
+ Sets = [cerl_sets:from_list(Ks) || Ks <- Ps],
+ Intersection = cerl_sets:intersection(Sets),
+ case cerl_sets:size(Intersection) of
+ 0 ->
+ none;
+ _ ->
+ All = all(fun (Kset) -> Kset =:= Intersection end, Sets),
+ case All of
+ true ->
+ %% All clauses test the same keys. Partitioning
+ %% the keys could only make the code worse.
+ none;
+ false ->
+ Intersection
end
end.
%% group_value([Clause]) -> [[Clause]].
%% Group clauses according to value. Here we know that
%% 1. Some types are singled valued
-%% 2. The clauses in bin_segs cannot be reordered only grouped
+%% 2. The clauses in maps and bin_segs cannot be reordered,
+%% only grouped
%% 3. Other types are disjoint and can be reordered
-group_value(k_cons, Us, Cs) -> [{Us,Cs}]; %These are single valued
+group_value(k_cons, Us, Cs) -> [{Us,Cs}]; %These are single valued
group_value(k_nil, Us, Cs) -> [{Us,Cs}];
group_value(k_binary, Us, Cs) -> [{Us,Cs}];
group_value(k_bin_end, Us, Cs) -> [{Us,Cs}];
-group_value(k_bin_seg, Us, Cs) -> group_bin_seg(Us,Cs);
+group_value(k_bin_seg, Us, Cs) -> group_keeping_order(Us, Cs);
group_value(k_bin_int, Us, Cs) -> [{Us,Cs}];
-group_value(k_map, Us, Cs) -> group_map(Us,Cs);
+group_value(k_map, Us, Cs) -> group_keeping_order(Us, Cs);
group_value(_, Us, Cs) ->
- %% group_value(Cs).
- Cd = foldl(fun (C, Gcs0) -> dict:append(clause_val(C), C, Gcs0) end,
- dict:new(), Cs),
- dict:fold(fun (_, Vcs, Css) -> [{Us,Vcs}|Css] end, [], Cd).
-
-group_bin_seg(Us, [C1|Cs]) ->
- V1 = clause_val(C1),
- {More,Rest} = splitwith(fun (C) -> clause_val(C) == V1 end, Cs),
- [{Us,[C1|More]}|group_bin_seg(Us,Rest)];
-group_bin_seg(_, []) -> [].
+ Map = group_values(Cs, #{}),
+ %% We must sort the grouped values to ensure consistent
+ %% order from compilation to compilation.
+ sort(maps:fold(fun (_, Vcs, Css) ->
+ [{Us,reverse(Vcs)}|Css]
+ end, [], Map)).
+
+group_values([C|Cs], Acc) ->
+ Val = clause_val(C),
+ case Acc of
+ #{Val:=Gcs} ->
+ group_values(Cs, Acc#{Val:=[C|Gcs]});
+ #{} ->
+ group_values(Cs, Acc#{Val=>[C]})
+ end;
+group_values([], Acc) -> Acc.
-group_map(Us, [C1|Cs]) ->
+group_keeping_order(Us, [C1|Cs]) ->
V1 = clause_val(C1),
{More,Rest} = splitwith(fun (C) -> clause_val(C) =:= V1 end, Cs),
- [{Us,[C1|More]}|group_map(Us,Rest)];
-group_map(_, []) -> [].
-
-%% Profiling shows that this quadratic implementation account for a big amount
-%% of the execution time if there are many values.
-% group_value([C|Cs]) ->
-% V = clause_val(C),
-% Same = [ Cv || Cv <- Cs, clause_val(Cv) == V ], %Same value
-% Rest = [ Cv || Cv <- Cs, clause_val(Cv) /= V ], % and all the rest
-% [[C|Same]|group_value(Rest)];
-% group_value([]) -> [].
+ [{Us,[C1|More]}|group_keeping_order(Us, Rest)];
+group_keeping_order(_, []) -> [].
%% match_clause([Var], [Clause], Default, State) -> {Clause,State}.
%% At this point all the clauses have the same "value". Build one
@@ -2070,7 +1519,8 @@ match_clause([U|Us], [C|_]=Cs0, Def, St0) ->
{Match0,Vs,St1} = get_match(get_con(Cs0), St0),
Match = sub_size_var(Match0, Cs0),
{Cs1,St2} = new_clauses(Cs0, U, St1),
- {B,St3} = match(Vs ++ Us, Cs1, Def, St2),
+ Cs2 = squeeze_clauses_by_bin_integer_count(Cs1, []),
+ {B,St3} = match(Vs ++ Us, Cs2, Def, St2),
{#k_val_clause{anno=Anno,val=Match,body=B},St3}.
sub_size_var(#k_bin_seg{size=#k_var{name=Name}=Kvar}=BinSeg, [#iclause{isub=Sub}|_]) ->
@@ -2085,17 +1535,14 @@ get_match(#k_cons{}, St0) ->
get_match(#k_binary{}, St0) ->
{[V]=Mes,St1} = new_vars(1, St0),
{#k_binary{segs=V},Mes,St1};
-get_match(#k_bin_seg{size=#k_atom{val=all},next={k_bin_end,[]}}=Seg, St0) ->
- {[S,N0],St1} = new_vars(2, St0),
- N = set_kanno(N0, [no_usage]),
+get_match(#k_bin_seg{size=#k_literal{val=all},next={k_bin_end,[]}}=Seg, St0) ->
+ {[S,N],St1} = new_vars(2, St0),
{Seg#k_bin_seg{seg=S,next=N},[S],St1};
get_match(#k_bin_seg{}=Seg, St0) ->
- {[S,N0],St1} = new_vars(2, St0),
- N = set_kanno(N0, [no_usage]),
+ {[S,N],St1} = new_vars(2, St0),
{Seg#k_bin_seg{seg=S,next=N},[S,N],St1};
get_match(#k_bin_int{}=BinInt, St0) ->
- {N0,St1} = new_var(St0),
- N = set_kanno(N0, [no_usage]),
+ {N,St1} = new_var(St0),
{BinInt#k_bin_int{next=N},[N],St1};
get_match(#k_tuple{es=Es}, St0) ->
{Mes,St1} = new_vars(length(Es), St0),
@@ -2116,7 +1563,7 @@ new_clauses(Cs0, U, St) ->
#k_cons{hd=H,tl=T} -> [H,T|As];
#k_tuple{es=Es} -> Es ++ As;
#k_binary{segs=E} -> [E|As];
- #k_bin_seg{size=#k_atom{val=all},
+ #k_bin_seg{size=#k_literal{val=all},
seg=S,next={k_bin_end,[]}} ->
[S|As];
#k_bin_seg{seg=S,next=N} ->
@@ -2140,6 +1587,108 @@ new_clauses(Cs0, U, St) ->
end, Cs0),
{Cs1,St}.
+%% group and squeeze
+%% The goal of those functions is to group subsequent integer k_bin_seg
+%% literals by count so we can leverage bs_get_integer_16 whenever possible.
+%%
+%% The priority is to create large groups. So if we have three clauses matching
+%% on 16-bits/16-bits/8-bits, we will first have a single 8-bits match for all
+%% three clauses instead of clauses (one with 16 and another with 8). But note
+%% the algorithm is recursive, so the remaining 8-bits for the first two clauses
+%% will be grouped next.
+%%
+%% We also try to not create too large groups. If we have too many clauses,
+%% it is preferrable to match on 8-bits, select a branch, then match on the
+%% next 8-bits, rather than match on 16-bits which would force us to have
+%% to select to many values at the same time, which would not be efficient.
+%%
+%% Another restriction is that we create groups only if the end of the
+%% group is a variadic clause or the end of the binary. That's because
+%% if we have 16-bits/16-bits/catch-all, breaking it into a 16-bits lookup
+%% will make the catch-all more expensive.
+%%
+%% Clauses are grouped in reverse when squeezing and then flattened and
+%% re-reversed at the end.
+squeeze_clauses_by_bin_integer_count([Clause | Clauses], Acc) ->
+ case clause_count_bin_integer_segments(Clause) of
+ {literal, N} -> squeeze_clauses_by_bin_integer_count(Clauses, N, 1, [Clause], Acc);
+ _ -> squeeze_clauses_by_bin_integer_count(Clauses, [[Clause] | Acc])
+ end;
+squeeze_clauses_by_bin_integer_count(_, Acc) ->
+ flat_reverse(Acc, []).
+
+squeeze_clauses_by_bin_integer_count([], N, Count, GroupAcc, Acc) ->
+ Squeezed = squeeze_clauses(GroupAcc, fix_count_without_variadic_segment(N), Count),
+ flat_reverse([Squeezed | Acc], []);
+squeeze_clauses_by_bin_integer_count([#iclause{pats=[#k_bin_end{} | _]} = Clause], N, Count, GroupAcc, Acc) ->
+ Squeezed = squeeze_clauses(GroupAcc, fix_count_without_variadic_segment(N), Count),
+ flat_reverse([[Clause | Squeezed] | Acc], []);
+squeeze_clauses_by_bin_integer_count([Clause | Clauses], N, Count, GroupAcc, Acc) ->
+ case clause_count_bin_integer_segments(Clause) of
+ {literal, NewN} ->
+ squeeze_clauses_by_bin_integer_count(Clauses, min(N, NewN), Count + 1, [Clause | GroupAcc], Acc);
+
+ {variadic, NewN} when NewN =< N ->
+ Squeezed = squeeze_clauses(GroupAcc, NewN, Count),
+ squeeze_clauses_by_bin_integer_count(Clauses, [[Clause | Squeezed] | Acc]);
+
+ _ ->
+ squeeze_clauses_by_bin_integer_count(Clauses, [[Clause | GroupAcc] | Acc])
+ end.
+
+clause_count_bin_integer_segments(#iclause{pats=[#k_bin_seg{seg=#k_literal{}} = BinSeg | _]}) ->
+ count_bin_integer_segments(BinSeg, 0);
+clause_count_bin_integer_segments(#iclause{pats=[#k_bin_seg{size=#k_literal{val=Size},unit=Unit,
+ type=integer,flags=[unsigned,big],
+ seg=#k_var{}} | _]})
+ when ((Size * Unit) rem 8) =:= 0 ->
+ {variadic, (Size * Unit) div 8};
+clause_count_bin_integer_segments(_) ->
+ error.
+
+count_bin_integer_segments(#k_bin_seg{size=#k_literal{val=8},unit=1,type=integer,flags=[unsigned,big],
+ seg=#k_literal{val=Int},next=Next}, Count)
+ when is_integer(Int), 0 =< Int, Int =< 255 ->
+ count_bin_integer_segments(Next, Count + 1);
+count_bin_integer_segments(_, Count) when Count > 0 ->
+ {literal, Count};
+count_bin_integer_segments(_, _Count) ->
+ error.
+
+%% Since 4 bytes in on 32-bits systems are bignums, we convert
+%% anything more than 3 into 2 bytes lookup. The goal is to convert
+%% any multi-clause segment into 2-byte lookups with a potential
+%% 3 byte lookup at the end.
+fix_count_without_variadic_segment(N) when N > 3 -> 2;
+fix_count_without_variadic_segment(N) -> N.
+
+%% If we have more than 16 clauses, then it is better
+%% to branch multiple times than getting a large integer.
+%% We also abort if we have nothing to squeeze.
+squeeze_clauses(Clauses, Size, Count) when Count >= 16; Size =< 1 -> Clauses;
+squeeze_clauses(Clauses, Size, _Count) ->
+ squeeze_clauses(Clauses, Size).
+
+squeeze_clauses([#iclause{pats=[#k_bin_seg{seg=#k_literal{}} = BinSeg | Pats]} = Clause | Clauses], Size) ->
+ [Clause#iclause{pats=[squeeze_segments(BinSeg, 0, 0, Size) | Pats]} |
+ squeeze_clauses(Clauses, Size)];
+squeeze_clauses([], _Size) ->
+ [].
+
+squeeze_segments(#k_bin_seg{size=Sz, seg=#k_literal{val=Val}=Lit} = BinSeg, Acc, Size, 1) ->
+ BinSeg#k_bin_seg{size=Sz#k_literal{val=Size + 8}, seg=Lit#k_literal{val=(Acc bsl 8) bor Val}};
+squeeze_segments(#k_bin_seg{seg=#k_literal{val=Val},next=Next}, Acc, Size, Count) ->
+ squeeze_segments(Next, (Acc bsl 8) bor Val, Size + 8, Count - 1);
+squeeze_segments(#k_bin_end{}, Acc, Size, Count) ->
+ error({Acc,Size,Count}).
+
+
+flat_reverse([Head | Tail], Acc) -> flat_reverse(Tail, flat_reverse_1(Head, Acc));
+flat_reverse([], Acc) -> Acc.
+
+flat_reverse_1([Head | Tail], Acc) -> flat_reverse_1(Tail, [Head | Acc]);
+flat_reverse_1([], Acc) -> Acc.
+
%% build_guard([GuardClause]) -> GuardExpr.
build_guard([]) -> fail;
@@ -2160,13 +1709,13 @@ build_alt_1st_no_fail(First, fail) -> First;
build_alt_1st_no_fail(First, Then) ->
copy_anno(#k_alt{first=First,then=Then}, First).
-%% build_match([MatchVar], MatchExpr) -> Kexpr.
+%% build_match(MatchExpr) -> Kexpr.
%% Build a match expr if there is a match.
-build_match(Us, #k_alt{}=Km) -> copy_anno(#k_match{vars=Us,body=Km}, Km);
-build_match(Us, #k_select{}=Km) -> copy_anno(#k_match{vars=Us,body=Km}, Km);
-build_match(Us, #k_guard{}=Km) -> copy_anno(#k_match{vars=Us,body=Km}, Km);
-build_match(_, Km) -> Km.
+build_match(#k_alt{}=Km) -> copy_anno(#k_match{body=Km}, Km);
+build_match(#k_select{}=Km) -> copy_anno(#k_match{body=Km}, Km);
+build_match(#k_guard{}=Km) -> copy_anno(#k_match{body=Km}, Km);
+build_match(Km) -> Km.
%% clause_arg(Clause) -> FirstArg.
%% clause_con(Clause) -> Constructor.
@@ -2195,26 +1744,26 @@ arg_alias(_Con) -> [].
arg_con(Arg) ->
case arg_arg(Arg) of
- #k_literal{} -> k_literal;
- #k_int{} -> k_int;
- #k_float{} -> k_float;
- #k_atom{} -> k_atom;
- #k_nil{} -> k_nil;
- #k_cons{} -> k_cons;
+ #k_cons{} -> k_cons;
#k_tuple{} -> k_tuple;
#k_map{} -> k_map;
#k_binary{} -> k_binary;
#k_bin_end{} -> k_bin_end;
#k_bin_seg{} -> k_bin_seg;
- #k_var{} -> k_var
+ #k_var{} -> k_var;
+ #k_literal{val=[]} -> k_nil;
+ #k_literal{val=Val} ->
+ if
+ is_atom(Val) -> k_atom;
+ is_integer(Val) -> k_int;
+ is_float(Val) -> k_float;
+ true -> k_literal
+ end
end.
arg_val(Arg, C) ->
case arg_arg(Arg) of
#k_literal{val=Lit} -> Lit;
- #k_int{val=I} -> I;
- #k_float{val=F} -> F;
- #k_atom{val=A} -> A;
#k_tuple{es=Es} -> length(Es);
#k_bin_seg{size=S,unit=U,type=T,flags=Fs} ->
case S of
@@ -2225,7 +1774,7 @@ arg_val(Arg, C) ->
{set_kanno(S, []),U,T,Fs}
end;
#k_map{op=exact,es=Es} ->
- lists:sort(fun(A,B) ->
+ sort(fun(A,B) ->
%% on the form K :: {'lit' | 'var', term()}
%% lit < var as intended
erts_internal:cmp_term(A,B) < 0
@@ -2248,23 +1797,22 @@ ubody(#iset{vars=[],arg=#iletrec{}=Let,body=B0}, Br, St0) ->
%% An iletrec{} should never be last.
St = iletrec_funs(Let, St0),
ubody(B0, Br, St);
+ubody(#iset{vars=[],arg=#k_literal{},body=B0}, Br, St0) ->
+ ubody(B0, Br, St0);
ubody(#iset{anno=A,vars=Vs,arg=E0,body=B0}, Br, St0) ->
{E1,Eu,St1} = uexpr(E0, {break,Vs}, St0),
{B1,Bu,St2} = ubody(B0, Br, St1),
Ns = lit_list_vars(Vs),
Used = union(Eu, subtract(Bu, Ns)), %Used external vars
- {#k_seq{anno=#k{us=Used,ns=Ns,a=A},arg=E1,body=B1},Used,St2};
+ {#k_seq{anno=A,arg=E1,body=B1},Used,St2};
ubody(#ivalues{anno=A,args=As}, return, St) ->
Au = lit_list_vars(As),
- {#k_return{anno=#k{us=Au,ns=[],a=A},args=As},Au,St};
+ {#k_return{anno=A,args=As},Au,St};
ubody(#ivalues{anno=A,args=As}, {break,_Vbs}, St) ->
Au = lit_list_vars(As),
- case is_in_guard(St) of
- true ->
- {#k_guard_break{anno=#k{us=Au,ns=[],a=A},args=As},Au,St};
- false ->
- {#k_break{anno=#k{us=Au,ns=[],a=A},args=As},Au,St}
- end;
+ {#k_break{anno=A,args=As},Au,St};
+ubody(#k_goto{}=Goto, _Br, St) ->
+ {Goto,[],St};
ubody(E, return, St0) ->
%% Enterable expressions need no trailing return.
case is_enter_expr(E) of
@@ -2273,27 +1821,14 @@ ubody(E, return, St0) ->
{Ea,Pa,St1} = force_atomic(E, St0),
ubody(pre_seq(Pa, #ivalues{args=[Ea]}), return, St1)
end;
-ubody(#ignored{}, {break,_} = Break, St) ->
- ubody(#ivalues{args=[]}, Break, St);
ubody(E, {break,[_]} = Break, St0) ->
- %%ok = io:fwrite("ubody ~w:~p~n", [?LINE,{E,Br}]),
- %% Exiting expressions need no trailing break.
- case is_exit_expr(E) of
- true -> uexpr(E, return, St0);
- false ->
- {Ea,Pa,St1} = force_atomic(E, St0),
- ubody(pre_seq(Pa, #ivalues{args=[Ea]}), Break, St1)
- end;
+ {Ea,Pa,St1} = force_atomic(E, St0),
+ ubody(pre_seq(Pa, #ivalues{args=[Ea]}), Break, St1);
ubody(E, {break,Rs}=Break, St0) ->
- case is_exit_expr(E) of
- true ->
- uexpr(E, return, St0);
- false ->
- {Vs,St1} = new_vars(length(Rs), St0),
- Iset = #iset{vars=Vs,arg=E},
- PreSeq = pre_seq([Iset], #ivalues{args=Vs}),
- ubody(PreSeq, Break, St1)
- end.
+ {Vs,St1} = new_vars(length(Rs), St0),
+ Iset = #iset{vars=Vs,arg=E},
+ PreSeq = pre_seq([Iset], #ivalues{args=Vs}),
+ ubody(PreSeq, Break, St1).
iletrec_funs(#iletrec{defs=Fs}, St0) ->
%% Use union of all free variables.
@@ -2319,20 +1854,13 @@ iletrec_funs_gen(_, _, #kern{funs=ignore}=St) ->
iletrec_funs_gen(Fs, FreeVs, St) ->
foldl(fun ({N,#ifun{anno=Fa,vars=Vs,body=Fb0}}, Lst0) ->
Arity0 = length(Vs),
- {Fb1,_,Lst1} = ubody(Fb0, return, Lst0#kern{ff={N,Arity0}}),
+ {Fb1,_,Lst1} = ubody(Fb0, return, Lst0),
Arity = Arity0 + length(FreeVs),
- Fun = make_fdef(#k{us=[],ns=[],a=Fa}, N, Arity,
- Vs++FreeVs, Fb1),
+ Fun = make_fdef(Fa, N, Arity, Vs++FreeVs, Fb1),
Lst1#kern{funs=[Fun|Lst1#kern.funs]}
end, St, Fs).
-%% is_exit_expr(Kexpr) -> boolean().
-%% Test whether Kexpr always exits and never returns.
-
-is_exit_expr(#k_receive_next{}) -> true;
-is_exit_expr(_) -> false.
-
%% is_enter_expr(Kexpr) -> boolean().
%% Test whether Kexpr is "enterable", i.e. can handle return from
%% within itself without extra #k_return{}.
@@ -2340,95 +1868,77 @@ is_exit_expr(_) -> false.
is_enter_expr(#k_try{}) -> true;
is_enter_expr(#k_call{}) -> true;
is_enter_expr(#k_match{}) -> true;
-is_enter_expr(#k_receive{}) -> true;
-is_enter_expr(#k_receive_next{}) -> true;
+is_enter_expr(#k_letrec_goto{}) -> true;
is_enter_expr(_) -> false.
%% uexpr(Expr, Break, State) -> {Expr,[UsedVar],State}.
-%% Tag an expression with its used variables.
+%% Calculate the used variables for an expression.
%% Break = return | {break,[RetVar]}.
uexpr(#k_test{anno=A,op=Op,args=As}=Test, {break,Rs}, St) ->
[] = Rs, %Sanity check
Used = union(op_vars(Op), lit_list_vars(As)),
- {Test#k_test{anno=#k{us=Used,ns=lit_list_vars(Rs),a=A}},
- Used,St};
+ {Test#k_test{anno=A},Used,St};
uexpr(#iset{anno=A,vars=Vs,arg=E0,body=B0}, {break,_}=Br, St0) ->
Ns = lit_list_vars(Vs),
{E1,Eu,St1} = uexpr(E0, {break,Vs}, St0),
{B1,Bu,St2} = uexpr(B0, Br, St1),
Used = union(Eu, subtract(Bu, Ns)),
- {#k_seq{anno=#k{us=Used,ns=Ns,a=A},arg=E1,body=B1},Used,St2};
+ {#k_seq{anno=A,arg=E1,body=B1},Used,St2};
uexpr(#k_call{anno=A,op=#k_local{name=F,arity=Ar}=Op,args=As0}=Call, Br, St) ->
Free = get_free(F, Ar, St),
As1 = As0 ++ Free, %Add free variables LAST!
Used = lit_list_vars(As1),
{case Br of
{break,Rs} ->
- Call#k_call{anno=#k{us=Used,ns=lit_list_vars(Rs),a=A},
+ Call#k_call{anno=A,
op=Op#k_local{arity=Ar + length(Free)},
args=As1,ret=Rs};
return ->
- #k_enter{anno=#k{us=Used,ns=[],a=A},
+ #k_enter{anno=A,
op=Op#k_local{arity=Ar + length(Free)},
args=As1}
end,Used,St};
uexpr(#k_call{anno=A,op=Op,args=As}=Call, {break,Rs}, St) ->
Used = union(op_vars(Op), lit_list_vars(As)),
- {Call#k_call{anno=#k{us=Used,ns=lit_list_vars(Rs),a=A},ret=Rs},
- Used,St};
+ {Call#k_call{anno=A,ret=Rs},Used,St};
uexpr(#k_call{anno=A,op=Op,args=As}, return, St) ->
Used = union(op_vars(Op), lit_list_vars(As)),
- {#k_enter{anno=#k{us=Used,ns=[],a=A},op=Op,args=As},
- Used,St};
+ {#k_enter{anno=A,op=Op,args=As},Used,St};
uexpr(#k_bif{anno=A,op=Op,args=As}=Bif, {break,Rs}, St0) ->
Used = union(op_vars(Op), lit_list_vars(As)),
{Brs,St1} = bif_returns(Op, Rs, St0),
- {Bif#k_bif{anno=#k{us=Used,ns=lit_list_vars(Brs),a=A},ret=Brs},
- Used,St1};
-uexpr(#k_match{anno=A,vars=Vs,body=B0}, Br, St0) ->
- Rs = break_rets(Br),
- {B1,Bu,St1} = umatch(B0, Br, St0),
- case is_in_guard(St1) of
- true ->
- {#k_guard_match{anno=#k{us=Bu,ns=lit_list_vars(Rs),a=A},
- vars=Vs,body=B1,ret=Rs},Bu,St1};
- false ->
- {#k_match{anno=#k{us=Bu,ns=lit_list_vars(Rs),a=A},
- vars=Vs,body=B1,ret=Rs},Bu,St1}
- end;
-uexpr(#k_receive{anno=A,var=V,body=B0,timeout=T,action=A0}, Br, St0) ->
+ {Bif#k_bif{anno=A,ret=Brs},Used,St1};
+uexpr(#k_match{anno=A,body=B0}, Br, St0) ->
Rs = break_rets(Br),
- Tu = lit_vars(T), %Timeout is atomic
{B1,Bu,St1} = umatch(B0, Br, St0),
- {A1,Au,St2} = ubody(A0, Br, St1),
- Used = del_element(V#k_var.name, union(Bu, union(Tu, Au))),
- {#k_receive{anno=#k{us=Used,ns=lit_list_vars(Rs),a=A},
- var=V,body=B1,timeout=T,action=A1,ret=Rs},
- Used,St2};
-uexpr(#k_receive_accept{anno=A}, _, St) ->
- {#k_receive_accept{anno=#k{us=[],ns=[],a=A}},[],St};
-uexpr(#k_receive_next{anno=A}, _, St) ->
- {#k_receive_next{anno=#k{us=[],ns=[],a=A}},[],St};
+ {#k_match{anno=A,body=B1,ret=Rs},Bu,St1};
uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0},
{break,Rs0}=Br, St0) ->
- case is_in_guard(St0) of
- true ->
- {[#k_var{name=X}],#k_var{name=X}} = {Vs,B0}, %Assertion.
- #k_atom{val=false} = H0, %Assertion.
- {Avs,St1} = new_vars(length(Rs0), St0),
- {A1,Bu,St} = uexpr(A0, {break,Avs}, St1),
- {#k_protected{anno=#k{us=Bu,ns=lit_list_vars(Rs0),a=A},
- arg=A1,ret=Rs0,inner=Avs},Bu,St};
- false ->
+ case {Vs,B0,H0,Rs0} of
+ {[#k_var{name=X}],#k_var{name=X},#k_literal{},[]} ->
+ %% This is a simple try/catch whose return value is
+ %% ignored:
+ %%
+ %% try E of V -> V when _:_:_ -> ignored_literal end, ...
+ %%
+ %% This is most probably a try/catch in a guard. To
+ %% correctly handle the #k_test{} that ends the body of
+ %% the guard, we MUST pass an empty list of break
+ %% variables when processing the body.
+ {A1,Bu,St} = ubody(A0, {break,[]}, St0),
+ {#k_try{anno=A,arg=A1,vars=[],body=#k_break{},
+ evars=[],handler=#k_break{},ret=Rs0},
+ Bu,St};
+ {_,_,_,_} ->
+ %% The general try/catch (in a guard or in body).
{Avs,St1} = new_vars(length(Vs), St0),
{A1,Au,St2} = ubody(A0, {break,Avs}, St1),
{B1,Bu,St3} = ubody(B0, Br, St2),
{H1,Hu,St4} = ubody(H0, Br, St3),
Used = union([Au,subtract(Bu, lit_list_vars(Vs)),
subtract(Hu, lit_list_vars(Evs))]),
- {#k_try{anno=#k{us=Used,ns=lit_list_vars(Rs0),a=A},
- arg=A1,vars=Vs,body=B1,evars=Evs,handler=H1,ret=Rs0},
+ {#k_try{anno=A,arg=A1,vars=Vs,body=B1,evars=Evs,handler=H1,ret=Rs0},
Used,St4}
end;
uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0},
@@ -2439,8 +1949,7 @@ uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0},
{H1,Hu,St4} = ubody(H0, return, St3),
Used = union([Au,subtract(Bu, lit_list_vars(Vs)),
subtract(Hu, lit_list_vars(Evs))]),
- {#k_try_enter{anno=#k{us=Used,ns=[],a=A},
- arg=A1,vars=Vs,body=B1,evars=Evs,handler=H1},
+ {#k_try_enter{anno=A,arg=A1,vars=Vs,body=B1,evars=Evs,handler=H1},
Used,St4};
uexpr(#k_catch{anno=A,body=B0}, {break,Rs0}, St0) ->
{Rb,St1} = new_var(St0),
@@ -2448,7 +1957,7 @@ uexpr(#k_catch{anno=A,body=B0}, {break,Rs0}, St0) ->
%% Guarantee ONE return variable.
{Ns,St3} = new_vars(1 - length(Rs0), St2),
Rs1 = Rs0 ++ Ns,
- {#k_catch{anno=#k{us=Bu,ns=lit_list_vars(Rs1),a=A},body=B1,ret=Rs1},Bu,St3};
+ {#k_catch{anno=A,body=B1,ret=Rs1},Bu,St3};
uexpr(#ifun{anno=A,vars=Vs,body=B0}, {break,Rs}, St0) ->
{B1,Bu,St1} = ubody(B0, return, St0), %Return out of new function
Ns = lit_list_vars(Vs),
@@ -2456,37 +1965,55 @@ uexpr(#ifun{anno=A,vars=Vs,body=B0}, {break,Rs}, St0) ->
Fvs = make_vars(Free),
Arity = length(Vs) + length(Free),
{Fname,St} =
- case lists:keyfind(id, 1, A) of
+ case keyfind(id, 1, A) of
{id,{_,_,Fname0}} ->
{Fname0,St1};
false ->
%% No id annotation. Must invent a fun name.
new_fun_name(St1)
end,
- Fun = make_fdef(#k{us=[],ns=[],a=A}, Fname, Arity, Vs++Fvs, B1),
- {#k_bif{anno=#k{us=Free,ns=lit_list_vars(Rs),a=A},
+ Fun = make_fdef(A, Fname, Arity, Vs++Fvs, B1),
+ Local = #k_local{name=Fname,arity=Arity},
+ {#k_bif{anno=A,
op=#k_internal{name=make_fun,arity=length(Free)+2},
- args=[#k_atom{val=Fname},#k_int{val=Arity}|Fvs],
+ args=[Local|Fvs],
ret=Rs},
Free,add_local_function(Fun, St)};
+uexpr(#k_letrec_goto{anno=A,first=F0,then=T0}=MatchAlt, Br, St0) ->
+ Rs = break_rets(Br),
+ {F1,Fu,St1} = ubody(F0, Br, St0),
+ {T1,Tu,St2} = ubody(T0, Br, St1),
+ Used = union(Fu, Tu),
+ {MatchAlt#k_letrec_goto{anno=A,first=F1,then=T1,ret=Rs},Used,St2};
uexpr(Lit, {break,Rs0}, St0) ->
%% Transform literals to puts here.
%%ok = io:fwrite("uexpr ~w:~p~n", [?LINE,Lit]),
Used = lit_vars(Lit),
{Rs,St1} = ensure_return_vars(Rs0, St0),
- {#k_put{anno=#k{us=Used,ns=lit_list_vars(Rs),a=get_kanno(Lit)},
- arg=Lit,ret=Rs},Used,St1}.
+ {#k_put{anno=get_kanno(Lit),arg=Lit,ret=Rs},Used,St1}.
-add_local_function(_, #kern{funs=ignore}=St) -> St;
-add_local_function(F, #kern{funs=Funs}=St) -> St#kern{funs=[F|Funs]}.
+add_local_function(_, #kern{funs=ignore}=St) ->
+ St;
+add_local_function(#k_fdef{func=Name,arity=Arity}=F, #kern{funs=Funs}=St) ->
+ case is_defined(Name, Arity, Funs) of
+ false ->
+ St#kern{funs=[F|Funs]};
+ true ->
+ St
+ end.
+
+is_defined(Name, Arity, [#k_fdef{func=Name,arity=Arity}|_]) ->
+ true;
+is_defined(Name, Arity, [#k_fdef{}|T]) ->
+ is_defined(Name, Arity, T);
+is_defined(_, _, []) -> false.
%% Make a #k_fdef{}, making sure that the body is always a #k_match{}.
make_fdef(Anno, Name, Arity, Vs, #k_match{}=Body) ->
#k_fdef{anno=Anno,func=Name,arity=Arity,vars=Vs,body=Body};
make_fdef(Anno, Name, Arity, Vs, Body) ->
Ka = get_kanno(Body),
- Match = #k_match{anno=#k{us=Ka#k.us,ns=[],a=Ka#k.a},
- vars=Vs,body=Body,ret=[]},
+ Match = #k_match{anno=Ka,body=Body,ret=[]},
#k_fdef{anno=Anno,func=Name,arity=Arity,vars=Vs,body=Match}.
%% get_free(Name, Arity, State) -> [Free].
@@ -2524,42 +2051,34 @@ ensure_return_vars([], St) -> new_vars(1, St);
ensure_return_vars([_]=Rs, St) -> {Rs,St}.
%% umatch(Match, Break, State) -> {Match,[UsedVar],State}.
-%% Tag a match expression with its used variables.
+%% Calculate the used variables for a match expression.
umatch(#k_alt{anno=A,first=F0,then=T0}, Br, St0) ->
{F1,Fu,St1} = umatch(F0, Br, St0),
{T1,Tu,St2} = umatch(T0, Br, St1),
Used = union(Fu, Tu),
- {#k_alt{anno=#k{us=Used,ns=[],a=A},first=F1,then=T1},
- Used,St2};
+ {#k_alt{anno=A,first=F1,then=T1},Used,St2};
umatch(#k_select{anno=A,var=V,types=Ts0}, Br, St0) ->
{Ts1,Tus,St1} = umatch_list(Ts0, Br, St0),
- Used = case member(no_usage, get_kanno(V)) of
- true -> Tus;
- false -> add_element(V#k_var.name, Tus)
- end,
- {#k_select{anno=#k{us=Used,ns=[],a=A},var=V,types=Ts1},Used,St1};
+ Used = add_element(V#k_var.name, Tus),
+ {#k_select{anno=A,var=V,types=Ts1},Used,St1};
umatch(#k_type_clause{anno=A,type=T,values=Vs0}, Br, St0) ->
{Vs1,Vus,St1} = umatch_list(Vs0, Br, St0),
- {#k_type_clause{anno=#k{us=Vus,ns=[],a=A},type=T,values=Vs1},Vus,St1};
+ {#k_type_clause{anno=A,type=T,values=Vs1},Vus,St1};
umatch(#k_val_clause{anno=A,val=P0,body=B0}, Br, St0) ->
{U0,Ps} = pat_vars(P0),
- P = set_kanno(P0, #k{us=U0,ns=Ps,a=get_kanno(P0)}),
{B1,Bu,St1} = umatch(B0, Br, St0),
+ P = pat_anno_unused(P0, Bu, Ps),
Used = union(U0, subtract(Bu, Ps)),
- {#k_val_clause{anno=#k{us=Used,ns=[],a=A},val=P,body=B1},
- Used,St1};
+ {#k_val_clause{anno=A,val=P,body=B1},Used,St1};
umatch(#k_guard{anno=A,clauses=Gs0}, Br, St0) ->
{Gs1,Gus,St1} = umatch_list(Gs0, Br, St0),
- {#k_guard{anno=#k{us=Gus,ns=[],a=A},clauses=Gs1},Gus,St1};
+ {#k_guard{anno=A,clauses=Gs1},Gus,St1};
umatch(#k_guard_clause{anno=A,guard=G0,body=B0}, Br, St0) ->
- %%ok = io:fwrite("~w: ~p~n", [?LINE,G0]),
- {G1,Gu,St1} = uexpr(G0, {break,[]},
- St0#kern{guard_refc=St0#kern.guard_refc+1}),
- %%ok = io:fwrite("~w: ~p~n", [?LINE,G1]),
- {B1,Bu,St2} = umatch(B0, Br, St1#kern{guard_refc=St1#kern.guard_refc-1}),
+ {G1,Gu,St1} = uexpr(G0, {break,[]}, St0),
+ {B1,Bu,St2} = umatch(B0, Br, St1),
Used = union(Gu, Bu),
- {#k_guard_clause{anno=#k{us=Used,ns=[],a=A},guard=G1,body=B1},Used,St2};
+ {#k_guard_clause{anno=A,guard=G1,body=B1},Used,St2};
umatch(B0, Br, St0) -> ubody(B0, Br, St0).
umatch_list(Ms0, Br, St) ->
@@ -2568,6 +2087,19 @@ umatch_list(Ms0, Br, St) ->
{[M1|Ms1],union(Mu, Us),Stb}
end, {[],[],St}, Ms0).
+pat_anno_unused(#k_tuple{es=Es0}=P, Used0, Ps) ->
+ %% Not extracting unused tuple elements is an optimization for
+ %% compile time and memory use during compilation. It is probably
+ %% worthwhile because it is common to extract only a few elements
+ %% from a huge record.
+ Used = intersection(Used0, Ps),
+ Es = [case member(V, Used) of
+ true -> Var;
+ false -> set_kanno(Var, [unused|get_kanno(Var)])
+ end || #k_var{name=V}=Var <- Es0],
+ P#k_tuple{es=Es};
+pat_anno_unused(P, _Used, _Ps) -> P.
+
%% op_vars(Op) -> [VarName].
op_vars(#k_remote{mod=Mod,name=Name}) ->
@@ -2579,11 +2111,7 @@ op_vars(Atomic) -> lit_vars(Atomic).
%% Return the variables in a literal.
lit_vars(#k_var{name=N}) -> [N];
-lit_vars(#k_int{}) -> [];
-lit_vars(#k_float{}) -> [];
-lit_vars(#k_atom{}) -> [];
%%lit_vars(#k_char{}) -> [];
-lit_vars(#k_nil{}) -> [];
lit_vars(#k_cons{hd=H,tl=T}) ->
union(lit_vars(H), lit_vars(T));
lit_vars(#k_map{var=Var,es=Es}) ->
@@ -2603,27 +2131,24 @@ lit_list_vars(Ps) ->
%% pat_vars(Pattern) -> {[UsedVarName],[NewVarName]}.
%% Return variables in a pattern. All variables are new variables
-%% except those in the size field of binary segments.
-%% and map_pair keys
+%% except those in the size field of binary segments and the key
+%% field in map_pairs.
pat_vars(#k_var{name=N}) -> {[],[N]};
%%pat_vars(#k_char{}) -> {[],[]};
pat_vars(#k_literal{}) -> {[],[]};
-pat_vars(#k_int{}) -> {[],[]};
-pat_vars(#k_float{}) -> {[],[]};
-pat_vars(#k_atom{}) -> {[],[]};
-pat_vars(#k_nil{}) -> {[],[]};
pat_vars(#k_cons{hd=H,tl=T}) ->
pat_list_vars([H,T]);
pat_vars(#k_binary{segs=V}) ->
pat_vars(V);
-pat_vars(#k_bin_seg{size=Size,seg=S}) ->
- {U1,New} = pat_list_vars([S]),
+pat_vars(#k_bin_seg{size=Size,seg=S,next=N}) ->
+ {U1,New} = pat_list_vars([S,N]),
{[],U2} = pat_vars(Size),
{union(U1, U2),New};
-pat_vars(#k_bin_int{size=Size}) ->
+pat_vars(#k_bin_int{size=Size,next=N}) ->
+ {[],New} = pat_vars(N),
{[],U} = pat_vars(Size),
- {U,[]};
+ {U,New};
pat_vars(#k_bin_end{}) -> {[],[]};
pat_vars(#k_tuple{es=Es}) ->
pat_list_vars(Es);
@@ -2646,11 +2171,6 @@ integers(N, M) when N =< M ->
[N|integers(N + 1, M)];
integers(_, _) -> [].
-%% is_in_guard(State) -> true|false.
-
-is_in_guard(#kern{guard_refc=Refc}) ->
- Refc > 0.
-
%%%
%%% Handling of errors and warnings.
%%%
@@ -2662,7 +2182,7 @@ is_in_guard(#kern{guard_refc=Refc}) ->
format_error({nomatch_shadow,Line}) ->
M = io_lib:format("this clause cannot match because a previous clause at line ~p "
"always matches", [Line]),
- lists:flatten(M);
+ flatten(M);
format_error(nomatch_shadow) ->
"this clause cannot match because a previous clause always matches";
format_error(bad_call) ->
diff --git a/lib/compiler/src/v3_kernel.hrl b/lib/compiler/src/v3_kernel.hrl
index e26360a6da..582e4f9b12 100644
--- a/lib/compiler/src/v3_kernel.hrl
+++ b/lib/compiler/src/v3_kernel.hrl
@@ -24,19 +24,10 @@
%% this could make including this file difficult.
%% N.B. the annotation field is ALWAYS the first field!
-%% Kernel annotation record.
--record(k, {us, %Used variables
- ns, %New variables
- a}). %Core annotation
-
%% Literals
%% NO CHARACTERS YET.
%%-record(k_char, {anno=[],val}).
--record(k_literal, {anno=[],val}). %Only used for complex literals.
--record(k_int, {anno=[],val}).
--record(k_float, {anno=[],val}).
--record(k_atom, {anno=[],val}).
--record(k_nil, {anno=[]}).
+-record(k_literal, {anno=[],val}).
-record(k_tuple, {anno=[],es}).
-record(k_map, {anno=[],var=#k_literal{val=#{}},op,es}).
@@ -58,19 +49,17 @@
-record(k_seq, {anno=[],arg,body}).
-record(k_put, {anno=[],arg,ret=[]}).
-record(k_bif, {anno=[],op,args,ret=[]}).
--record(k_test, {anno=[],op,args,inverted=false}).
+-record(k_test, {anno=[],op,args}).
-record(k_call, {anno=[],op,args,ret=[]}).
-record(k_enter, {anno=[],op,args}).
--record(k_receive, {anno=[],var,body,timeout,action,ret=[]}).
--record(k_receive_accept, {anno=[]}).
--record(k_receive_next, {anno=[]}).
-record(k_try, {anno=[],arg,vars,body,evars,handler,ret=[]}).
-record(k_try_enter, {anno=[],arg,vars,body,evars,handler}).
--record(k_protected, {anno=[],arg,ret=[],inner}).
-record(k_catch, {anno=[],body,ret=[]}).
--record(k_guard_match, {anno=[],vars,body,ret=[]}).
--record(k_match, {anno=[],vars,body,ret=[]}).
+-record(k_letrec_goto, {anno=[],label,first,then,ret=[]}).
+-record(k_goto, {anno=[],label}).
+
+-record(k_match, {anno=[],body,ret=[]}).
-record(k_alt, {anno=[],first,then}).
-record(k_select, {anno=[],var,types}).
-record(k_type_clause, {anno=[],type,values}).
@@ -79,7 +68,6 @@
-record(k_guard_clause, {anno=[],guard,body}).
-record(k_break, {anno=[],args=[]}).
--record(k_guard_break, {anno=[],args=[]}).
-record(k_return, {anno=[],args=[]}).
%%k_get_anno(Thing) -> element(2, Thing).
diff --git a/lib/compiler/src/v3_kernel_pp.erl b/lib/compiler/src/v3_kernel_pp.erl
index c12c301ee2..f7479e6b15 100644
--- a/lib/compiler/src/v3_kernel_pp.erl
+++ b/lib/compiler/src/v3_kernel_pp.erl
@@ -57,8 +57,6 @@ format(Node, Ctxt) ->
format_1(Node, Ctxt);
[L,{file,_}] when is_integer(L) ->
format_1(Node, Ctxt);
- #k{a=Anno}=K when Anno =/= [] ->
- format(setelement(2, Node, K#k{a=[]}), Ctxt);
List ->
format_anno(List, Ctxt, fun (Ctxt1) ->
format_1(Node, Ctxt1)
@@ -83,11 +81,7 @@ format_anno(Anno, Ctxt0, ObjFun) ->
%% format_1(Kexpr, Context) -> string().
-format_1(#k_atom{val=A}, _Ctxt) -> core_atom(A);
%%format_1(#k_char{val=C}, _Ctxt) -> io_lib:write_char(C);
-format_1(#k_float{val=F}, _Ctxt) -> float_to_list(F);
-format_1(#k_int{val=I}, _Ctxt) -> integer_to_list(I);
-format_1(#k_nil{}, _Ctxt) -> "[]";
format_1(#k_var{name=V}, _Ctxt) ->
if is_atom(V) ->
case atom_to_list(V) of
@@ -135,10 +129,13 @@ format_1(#k_bin_seg{next=Next}=S, Ctxt) ->
[format_bin_seg_1(S, Ctxt),
format_bin_seg(Next, ctxt_bump_indent(Ctxt, 2))];
format_1(#k_bin_int{size=Sz,unit=U,flags=Fs,val=Val,next=Next}, Ctxt) ->
- S = #k_bin_seg{size=Sz,unit=U,type=integer,flags=Fs,seg=#k_int{val=Val},next=Next},
+ S = #k_bin_seg{size=Sz,unit=U,type=integer,flags=Fs,
+ seg=#k_literal{val=Val},next=Next},
[format_bin_seg_1(S, Ctxt),
format_bin_seg(Next, ctxt_bump_indent(Ctxt, 2))];
format_1(#k_bin_end{}, _Ctxt) -> "#<>#";
+format_1(#k_literal{val=A}, _Ctxt) when is_atom(A) ->
+ core_atom(A);
format_1(#k_literal{val=Term}, _Ctxt) ->
io_lib:format("~p", [Term]);
format_1(#k_local{name=N,arity=A}, Ctxt) ->
@@ -158,20 +155,9 @@ format_1(#k_seq{arg=A,body=B}, Ctxt) ->
nl_indent(Ctxt)
| format(B, Ctxt)
];
-format_1(#k_match{vars=Vs,body=Bs,ret=Rs}, Ctxt) ->
+format_1(#k_match{body=Bs,ret=Rs}, Ctxt) ->
Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.item_indent),
- ["match ",
- format_hseq(Vs, ",", ctxt_bump_indent(Ctxt, 6), fun format/2),
- nl_indent(Ctxt1),
- format(Bs, Ctxt1),
- nl_indent(Ctxt),
- "end",
- format_ret(Rs, Ctxt1)
- ];
-format_1(#k_guard_match{vars=Vs,body=Bs,ret=Rs}, Ctxt) ->
- Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.item_indent),
- ["guard_match ",
- format_hseq(Vs, ",", ctxt_bump_indent(Ctxt, 6), fun format/2),
+ ["match",
nl_indent(Ctxt1),
format(Bs, Ctxt1),
nl_indent(Ctxt),
@@ -185,6 +171,20 @@ format_1(#k_alt{first=O,then=T}, Ctxt) ->
format(O, Ctxt1),
nl_indent(Ctxt1),
format(T, Ctxt1)];
+format_1(#k_letrec_goto{label=Label,first=First,then=Then,ret=Rs}, Ctxt) ->
+ Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.item_indent),
+ ["letrec_goto ",
+ atom_to_list(Label),
+ nl_indent(Ctxt1),
+ format(Then, Ctxt1),
+ nl_indent(Ctxt1),
+ format(First, Ctxt1),
+ nl_indent(Ctxt),
+ "end",
+ format_ret(Rs, Ctxt1)
+ ];
+format_1(#k_goto{label=Label}, _Ctxt) ->
+ ["goto ",atom_to_list(Label)];
format_1(#k_select{var=V,types=Cs}, Ctxt) ->
Ctxt1 = ctxt_bump_indent(Ctxt, 2),
["select ",
@@ -235,13 +235,8 @@ format_1(#k_bif{op=Op,args=As,ret=Rs}, Ctxt) ->
[Txt,format_args(As, Ctxt1),
format_ret(Rs, Ctxt1)
];
-format_1(#k_test{op=Op,args=As,inverted=Inverted}, Ctxt) ->
- Txt = case Inverted of
- false ->
- ["test (",format(Op, ctxt_bump_indent(Ctxt, 6)),$)];
- true ->
- ["inverted_test (",format(Op, ctxt_bump_indent(Ctxt, 6)),$)]
- end,
+format_1(#k_test{op=Op,args=As}, Ctxt) ->
+ Txt = ["test (",format(Op, ctxt_bump_indent(Ctxt, 6)),$)],
Ctxt1 = ctxt_bump_indent(Ctxt, 2),
[Txt,format_args(As, Ctxt1)];
format_1(#k_put{arg=A,ret=Rs}, Ctxt) ->
@@ -285,15 +280,6 @@ format_1(#k_try_enter{arg=A,vars=Vs,body=B,evars=Evs,handler=H}, Ctxt) ->
nl_indent(Ctxt),
"end"
];
-format_1(#k_protected{arg=A,ret=Rs}, Ctxt) ->
- Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent),
- ["protected",
- nl_indent(Ctxt1),
- format(A, Ctxt1),
- nl_indent(Ctxt),
- "end",
- format_ret(Rs, ctxt_bump_indent(Ctxt, 1))
- ];
format_1(#k_catch{body=B,ret=Rs}, Ctxt) ->
Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.body_indent),
["catch",
@@ -303,34 +289,11 @@ format_1(#k_catch{body=B,ret=Rs}, Ctxt) ->
"end",
format_ret(Rs, Ctxt1)
];
-format_1(#k_receive{var=V,body=B,timeout=T,action=A,ret=Rs}, Ctxt) ->
- Ctxt1 = ctxt_bump_indent(Ctxt, Ctxt#ctxt.item_indent),
- ["receive ",
- format(V, Ctxt),
- nl_indent(Ctxt1),
- format(B, Ctxt1),
- nl_indent(Ctxt),
- "after ",
- format(T, ctxt_bump_indent(Ctxt, 6)),
- " ->",
- nl_indent(Ctxt1),
- format(A, Ctxt1),
- nl_indent(Ctxt),
- "end",
- format_ret(Rs, Ctxt1)
- ];
-format_1(#k_receive_accept{}, _Ctxt) -> "receive_accept";
-format_1(#k_receive_next{}, _Ctxt) -> "receive_next";
format_1(#k_break{args=As}, Ctxt) ->
["<",
format_hseq(As, ",", ctxt_bump_indent(Ctxt, 1), fun format/2),
">"
];
-format_1(#k_guard_break{args=As}, Ctxt) ->
- [":<",
- format_hseq(As, ",", ctxt_bump_indent(Ctxt, 1), fun format/2),
- ">:"
- ];
format_1(#k_return{args=As}, Ctxt) ->
["<<",
format_hseq(As, ",", ctxt_bump_indent(Ctxt, 1), fun format/2),
@@ -347,7 +310,7 @@ format_1(#k_fdef{func=F,arity=A,vars=Vs,body=B}, Ctxt) ->
];
format_1(#k_mdef{name=N,exports=Es,attributes=As,body=B}, Ctxt) ->
["module ",
- format(#k_atom{val=N}, ctxt_bump_indent(Ctxt, 7)),
+ format(#k_literal{val=N}, ctxt_bump_indent(Ctxt, 7)),
nl_indent(Ctxt),
"export [",
format_vseq(Es,
@@ -437,17 +400,17 @@ format_fa_pair({F,A}, _Ctxt) -> [core_atom(F),$/,integer_to_list(A)].
%% format_attribute({Name,Val}, Context) -> Txt.
format_attribute({Name,Val}, Ctxt) when is_list(Val) ->
- Txt = format(#k_atom{val=Name}, Ctxt),
+ Txt = format(#k_literal{val=Name}, Ctxt),
Ctxt1 = ctxt_bump_indent(Ctxt, width(Txt,Ctxt)+4),
[Txt," = ",
$[,format_vseq(Val, "", ",", Ctxt1,
fun (A, _C) -> io_lib:write(A) end),$]
];
format_attribute({Name,Val}, Ctxt) ->
- Txt = format(#k_atom{val=Name}, Ctxt),
+ Txt = format(#k_literal{val=Name}, Ctxt),
[Txt," = ",io_lib:write(Val)].
-format_list_tail(#k_nil{anno=[]}, _Ctxt) -> "]";
+format_list_tail(#k_literal{anno=[],val=[]}, _Ctxt) -> "]";
format_list_tail(#k_cons{anno=[],hd=H,tl=T}, Ctxt) ->
Txt = [$,|format(H, Ctxt)],
Ctxt1 = ctxt_bump_indent(Ctxt, width(Txt, Ctxt)),
diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile
index 7be23fbb93..31434565c3 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -16,12 +16,14 @@ MODULES= \
beam_reorder_SUITE \
beam_ssa_SUITE \
beam_type_SUITE \
+ beam_types_SUITE \
beam_utils_SUITE \
bif_SUITE \
bs_bincomp_SUITE \
bs_bit_binaries_SUITE \
bs_construct_SUITE \
bs_match_SUITE \
+ bs_size_expr_SUITE \
bs_utf_SUITE \
core_alias_SUITE \
core_fold_SUITE \
@@ -38,6 +40,7 @@ MODULES= \
match_SUITE \
misc_SUITE \
overridden_bif_SUITE \
+ random_code_SUITE \
receive_SUITE \
record_SUITE \
regressions_SUITE \
@@ -58,7 +61,9 @@ NO_OPT= \
beam_utils \
bif \
bs_construct \
+ bs_bincomp \
bs_match \
+ bs_size_expr \
bs_utf \
core_fold \
float \
@@ -84,6 +89,7 @@ INLINE= \
bs_bit_binaries \
bs_construct \
bs_match \
+ bs_size_expr \
bs_utf \
core_fold \
float \
@@ -101,6 +107,8 @@ R21= \
bs_construct \
bs_match
+DIALYZER = bs_match
+
CORE_MODULES = \
lfe_andor_SUITE \
lfe_guard_SUITE
@@ -115,6 +123,8 @@ NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE)
NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl)
POST_OPT_MODULES= $(NO_OPT:%=%_post_opt_SUITE)
POST_OPT_ERL_FILES= $(POST_OPT_MODULES:%=%.erl)
+NO_CORE_OPT_MODULES= $(NO_OPT:%=%_no_copt_SUITE)
+NO_CORE_OPT_ERL_FILES= $(NO_CORE_OPT_MODULES:%=%.erl)
INLINE_MODULES= $(INLINE:%=%_inline_SUITE)
INLINE_ERL_FILES= $(INLINE_MODULES:%=%.erl)
R21_MODULES= $(R21:%=%_r21_SUITE)
@@ -125,6 +135,8 @@ NO_SSA_OPT_MODULES= $(NO_SSA_OPT:%=%_no_ssa_opt_SUITE)
NO_SSA_OPT_ERL_FILES= $(NO_SSA_OPT_MODULES:%=%.erl)
NO_TYPE_OPT_MODULES= $(NO_TYPE_OPT:%=%_no_type_opt_SUITE)
NO_TYPE_OPT_ERL_FILES= $(NO_TYPE_OPT_MODULES:%=%.erl)
+DIALYZER_MODULES= $(DIALYZER:%=%_dialyzer_SUITE)
+DIALYZER_ERL_FILES= $(DIALYZER_MODULES:%=%.erl)
ERL_FILES= $(MODULES:%=%.erl)
CORE_FILES= $(CORE_MODULES:%=%.core)
@@ -153,28 +165,34 @@ EBIN = .
# Targets
# ----------------------------------------------------
+DISABLE_SSA_OPT = +no_bool_opt +no_share_opt +no_bsm_opt +no_fun_opt +no_ssa_opt +no_recv_opt
+
make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES) \
- $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES) $(NO_TYPE_OPT_ERL_FILES)
+ $(NO_CORE_OPT_ERL_FILES) $(INLINE_ERL_FILES) $(R21_ERL_FILES) \
+ $(NO_MOD_OPT_ERL_FILES) $(NO_TYPE_OPT_ERL_FILES) \
+ $(DIALYZER_ERL_FILES)
$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \
- > $(EMAKEFILE)
- $(ERL_TOP)/make/make_emakefile +no_copt +no_postopt \
- +no_ssa_opt +no_recv_opt $(ERL_COMPILE_FLAGS) \
- -o$(EBIN) $(NO_OPT_MODULES) >> $(EMAKEFILE)
- $(ERL_TOP)/make/make_emakefile +no_share_opt +no_bsm_opt +no_fun_opt \
- +no_ssa_opt +no_recv_opt $(ERL_COMPILE_FLAGS) \
- -o$(EBIN) $(NO_SSA_OPT_MODULES) >> $(EMAKEFILE)
+ > $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +no_copt $(DISABLE_SSA_OPT) +no_postopt \
+ $(ERL_COMPILE_FLAGS) -o$(EBIN) $(NO_OPT_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile $(DISABLE_SSA_OPT) $(ERL_COMPILE_FLAGS) \
+ -o$(EBIN) $(NO_SSA_OPT_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +no_copt $(DISABLE_SSA_OPT) $(ERL_COMPILE_FLAGS) \
+ -o$(EBIN) $(POST_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_copt $(ERL_COMPILE_FLAGS) \
- -o$(EBIN) $(POST_OPT_MODULES) >> $(EMAKEFILE)
+ -o$(EBIN) $(NO_CORE_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +inline $(ERL_COMPILE_FLAGS) \
- -o$(EBIN) $(INLINE_MODULES) >> $(EMAKEFILE)
+ -o$(EBIN) $(INLINE_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +r21 $(ERL_COMPILE_FLAGS) \
- -o$(EBIN) $(R21_MODULES) >> $(EMAKEFILE)
+ -o$(EBIN) $(R21_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_module_opt $(ERL_COMPILE_FLAGS) \
- -o$(EBIN) $(NO_MOD_OPT_MODULES) >> $(EMAKEFILE)
+ -o$(EBIN) $(NO_MOD_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +from_core $(ERL_COMPILE_FLAGS) \
- -o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE)
+ -o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_type_opt $(ERL_COMPILE_FLAGS) \
- -o$(EBIN) $(NO_TYPE_OPT_MODULES) >> $(EMAKEFILE)
+ -o$(EBIN) $(NO_TYPE_OPT_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +dialyzer $(ERL_COMPILE_FLAGS) \
+ -o$(EBIN) $(DIALYZER_MODULES) >> $(EMAKEFILE)
tests debug opt: make_emakefile
erl $(ERL_MAKE_FLAGS) -make
@@ -199,6 +217,9 @@ docs:
%_post_opt_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+%_no_copt_SUITE.erl: %_SUITE.erl
+ sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+
%_inline_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
@@ -211,6 +232,8 @@ docs:
%_no_type_opt_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+%_dialyzer_SUITE.erl: %_SUITE.erl
+ sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
# ----------------------------------------------------
# Release Target
@@ -225,9 +248,11 @@ release_tests_spec: make_emakefile
$(EMAKEFILE) $(ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \
$(INLINE_ERL_FILES) $(R21_ERL_FILES) \
+ $(NO_CORE_OPT_ERL_FILES) \
$(NO_MOD_OPT_ERL_FILES) \
$(NO_SSA_OPT_ERL_FILES) \
- $(NO_TYPE_OPT_ERL_FILES) "$(RELSYSDIR)"
+ $(NO_TYPE_OPT_ERL_FILES) \
+ $(DIALYZER_ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(CORE_FILES) "$(RELSYSDIR)"
for file in $(ERL_DUMMY_FILES); do \
module=`basename $$file .erl`; \
@@ -236,6 +261,6 @@ release_tests_spec: make_emakefile
$(INSTALL_DATA) $(ERL_DUMMY_FILES) "$(RELSYSDIR)"
rm $(ERL_DUMMY_FILES)
chmod -R u+w "$(RELSYSDIR)"
- @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -)
+ @tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -)
release_docs_spec:
diff --git a/lib/compiler/test/andor_SUITE.erl b/lib/compiler/test/andor_SUITE.erl
index 5c463063c1..c986cc2346 100644
--- a/lib/compiler/test/andor_SUITE.erl
+++ b/lib/compiler/test/andor_SUITE.erl
@@ -66,6 +66,17 @@ t_case(Config) when is_list(Config) ->
true = (catch t_case_e({a,b}, {a,b})),
false = (catch t_case_e({a,b}, 42)),
+ {true,false} = t_case_f1(true, pos),
+ {false,true} = t_case_f1(true, whatever),
+ {false,true} = t_case_f1(false, pos),
+ {false,true} = t_case_f1(false, whatever),
+ {false,false} = t_case_f1(not_boolean, pos),
+ {false,false} = t_case_f1(not_boolean, whatever),
+
+ false = t_case_f2(true),
+ true = t_case_f2(false),
+ false = t_case_f2(whatever),
+
true = t_case_xy(42, 100, 700),
true = t_case_xy(42, 100, whatever),
false = t_case_xy(42, wrong, 700),
@@ -109,6 +120,25 @@ t_case_e(A, B) ->
Bool when is_tuple(A) -> id(Bool)
end.
+t_case_f1(IsInt, Eval) ->
+ B = case IsInt of
+ true -> Eval =:= pos;
+ false -> false;
+ _ -> IsInt
+ end,
+
+ %% The above is the same as `IsInt andalso Eval =:= pos` in a guard.
+ %% In a real guard, variable `B` will only be used once.
+ {B =:= true, B =:= false}.
+
+t_case_f2(IsInt) ->
+ B = case IsInt of
+ true -> false;
+ false -> true;
+ _ -> IsInt
+ end,
+ B =:= true.
+
t_case_xy(X, Y, Z) ->
Res = t_case_x(X, Y, Z),
Res = t_case_y(X, Y, Z).
@@ -212,6 +242,9 @@ t_andalso(Config) when is_list(Config) ->
true = begin (X1 = true) andalso X1, X1 end,
false = false = begin (X2 = false) andalso X2, X2 end,
+ %% Cover conversion to right associativity.
+ true = (is_list(Config) andalso is_list(Bs)) andalso is_list(Ps),
+
ok.
t_orelse(Config) when is_list(Config) ->
@@ -245,6 +278,9 @@ t_orelse(Config) when is_list(Config) ->
true = begin (X1 = true) orelse X1, X1 end,
false = begin (X2 = false) orelse X2, X2 end,
+ %% Cover conversion to right associativity.
+ false = (is_atom(Config) orelse is_atom(Bs)) orelse is_atom(Ps),
+
ok.
t_andalso_1({X,Y}) ->
diff --git a/lib/compiler/test/apply_SUITE.erl b/lib/compiler/test/apply_SUITE.erl
index 2730f3dff0..4a9292d37a 100644
--- a/lib/compiler/test/apply_SUITE.erl
+++ b/lib/compiler/test/apply_SUITE.erl
@@ -19,8 +19,9 @@
%%
-module(apply_SUITE).
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
- init_per_group/2,end_per_group/2,mfa/1,fun_apply/1]).
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ mfa/1,fun_apply/1,involved/1]).
-export([foo/0,bar/1,baz/2]).
@@ -28,11 +29,15 @@
suite() -> [{ct_hooks,[ts_install_cth]}].
-all() ->
- [mfa, fun_apply].
+all() ->
+ [{group,p}].
-groups() ->
- [].
+groups() ->
+ [{p,test_lib:parallel(),
+ [mfa,
+ fun_apply,
+ involved
+ ]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -132,4 +137,40 @@ fun_apply(Config) when is_list(Config) ->
ok.
+involved(_Config) ->
+ self() ! message,
+ ok = involved_1(),
+
+ self() ! message,
+ error = involved_2(),
+ ok.
+
+involved_1() ->
+ try
+ receive
+ _ ->
+ fun erlang:atom_to_list/1('')
+ end
+ of
+ [] ->
+ ok
+ catch
+ _:_ ->
+ error
+ end.
+
+involved_2() ->
+ try
+ receive
+ _ ->
+ fun erlang:atom_to_list/1()
+ end
+ of
+ [] ->
+ ok
+ catch
+ _:_ ->
+ error
+ end.
+
id(I) -> I.
diff --git a/lib/compiler/test/beam_block_SUITE.erl b/lib/compiler/test/beam_block_SUITE.erl
index 40a30b65d7..988a3545e8 100644
--- a/lib/compiler/test/beam_block_SUITE.erl
+++ b/lib/compiler/test/beam_block_SUITE.erl
@@ -22,7 +22,8 @@
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
get_map_elements/1,otp_7345/1,move_opt_across_gc_bif/1,
- erl_202/1,repro/1,local_cse/1,second_block_pass/1]).
+ erl_202/1,repro/1,local_cse/1,second_block_pass/1,
+ coverage/1]).
%% The only test for the following functions is that
%% the code compiles and is accepted by beam_validator.
@@ -41,7 +42,8 @@ groups() ->
erl_202,
repro,
local_cse,
- second_block_pass
+ second_block_pass,
+ coverage
]}].
init_per_suite(Config) ->
@@ -305,6 +307,15 @@ second_block_pass(_Config) ->
second_1(Fs, TS) ->
[F#{dts=>DTS / TS} || #{dts:=DTS} = F <- Fs].
+coverage(_Config) ->
+ [] = coverage_1(),
+ ok.
+
+coverage_1() ->
+ [(bnot head):bar(hdr, case kind of
+ [] -> whatever
+ end) || 7 <- []].
+
%%%
%%% Common functions.
%%%
diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl
index a38c9131b0..1a9406b5ea 100644
--- a/lib/compiler/test/beam_except_SUITE.erl
+++ b/lib/compiler/test/beam_except_SUITE.erl
@@ -127,19 +127,26 @@ coverage(_) ->
{'EXIT',{function_clause,[{?MODULE,foobar,[[fail],1,2],
[{file,"fake.erl"},{line,16}]}|_]}} =
(catch foobar([fail], 1, 2)),
- {'EXIT',{function_clause,[{?MODULE,fake_function_clause,[{a,b},42.0],_}|_]}} =
- (catch fake_function_clause({a,b})),
+ {'EXIT',{function_clause,[{?MODULE,fake_function_clause1,[{a,b},42.0],_}|_]}} =
+ (catch fake_function_clause1({a,b})),
+
+ {'EXIT',{function_clause,[{?MODULE,fake_function_clause2,[42|bad_tl],_}|_]}} =
+ (catch fake_function_clause2(42, bad_tl)),
+ {'EXIT',{function_clause,[{?MODULE,fake_function_clause3,[x,y],_}|_]}} =
+ (catch fake_function_clause3(42, id([x,y]))),
{'EXIT',{{badmatch,0.0},_}} = (catch coverage_1(id(42))),
{'EXIT',{badarith,_}} = (catch coverage_1(id(a))),
+
ok.
coverage_1(X) ->
%% ERL-1167: Would crash beam_except.
true = 0 / X.
-fake_function_clause(A) -> error(function_clause, [A,42.0]).
-
+fake_function_clause1(A) -> error(function_clause, [A,42.0]).
+fake_function_clause2(A, Tl) -> error(function_clause, [A|Tl]).
+fake_function_clause3(_, Stk) -> error(function_clause, Stk).
binary_construction_allocation(_Config) ->
ok = do_binary_construction_allocation("PUT"),
diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl
index bd48504483..78f2e8b42a 100644
--- a/lib/compiler/test/beam_jump_SUITE.erl
+++ b/lib/compiler/test/beam_jump_SUITE.erl
@@ -23,7 +23,7 @@
init_per_group/2,end_per_group/2,
undefined_label/1,ambiguous_catch_try_state/1,
unsafe_move_elimination/1,build_tuple/1,
- coverage/1]).
+ coverage/1,call_sharing/1]).
suite() ->
[{ct_hooks,[ts_install_cth]}].
@@ -37,7 +37,8 @@ groups() ->
ambiguous_catch_try_state,
unsafe_move_elimination,
build_tuple,
- coverage
+ coverage,
+ call_sharing
]}].
init_per_suite(Config) ->
@@ -67,9 +68,18 @@ flights(_, Reproduction, introduction) when false, Reproduction ->
%% [ERL-209] beam_jump would share 'catch' blocks, causing an
%% ambiguous_catch_try_state error in beam_validator.
-ambiguous_catch_try_state(_Config) ->
+ambiguous_catch_try_state(Config) ->
{{'EXIT',{{case_clause,song},_}},{'EXIT',{{case_clause,song},_}}} =
checks(42),
+
+ {'EXIT',{{try_clause,42},_}} = (catch unsafe_sharing()),
+
+ {'EXIT',{{badmatch,b},_}} = (catch ambiguous_catch_try_state_1(<<>>)),
+ {'EXIT',{{badmatch,b},_}} = (catch ambiguous_catch_try_state_1(Config)),
+
+ {'EXIT',{{badmatch,0},_}} = (catch ambiguous_catch_try_state_2()),
+ {'EXIT',{{badmatch,0},_}} = (catch ambiguous_catch_try_state_3()),
+
ok.
river() -> song.
@@ -78,6 +88,9 @@ checks(Wanted) ->
%% Must be one line to cause the unsafe optimization.
{catch case river() of sheet -> begin +Wanted, if "da" -> Wanted end end end, catch case river() of sheet -> begin + Wanted, if "da" -> Wanted end end end}.
+%% Must be one line to cause the unsafe optimization. Would cause beam_validator to reject the function.
+unsafe_sharing() -> try try id(42) catch parent:215 -> []; education:17 -> try 12 catch _:_ -> a end /= if false -> fy end end of [] -> if false -> a end catch _:_ -> name end.
+
unsafe_move_elimination(_Config) ->
{{left,right,false},false} = unsafe_move_elimination_1(left, right, false),
{{false,right,false},false} = unsafe_move_elimination_1(false, right, true),
@@ -154,6 +167,66 @@ expects_h(7, Atom) ->
Atom = id(h),
ok.
+%% When compiled with +no_copt, beam_validator would complain about
+%% ambigous try/catch state.
+ambiguous_catch_try_state_1(<<42:false>>) ->
+ %% The beam_ssa_bsm pass will duplicate the entire second clause.
+ %% beam_jump will share the blocks with the build_stacktrace
+ %% instructions.
+ [];
+ambiguous_catch_try_state_1(V0) ->
+ try
+ try
+ receive after bad -> timeout end
+ catch
+ _:V0 ->
+ error
+ after
+ ok
+ end
+ of
+ true ->
+ ok
+ catch
+ month:power:V2 ->
+ %% A build_stacktrace instruction would be shared, causing
+ %% an ambiguous try/catch state.
+ V2
+ after
+ a = b
+ end.
+
+ambiguous_catch_try_state_2() ->
+ case
+ try
+ case false = 0 of
+ false ->
+ hand
+ end
+ catch
+ idea:[]:V1 ->
+ V1;
+ country:42 ->
+ %% if_end would be shared in an unsafe way.
+ if 0 -> way end after [] end of [] -> if $X -> "D" end
+ end.
+
+ambiguous_catch_try_state_3() ->
+ case
+ try
+ case false = 0 of
+ false ->
+ hand
+ end
+ catch
+ idea:[]:V1 ->
+ V1;
+ country:42 ->
+ %% case_end would be shared in an unsafe way.
+ case x of y -> way end after [] end of [] -> case x of $X -> "D" end
+ end.
+
+
-record(message2, {id, p1}).
-record(message3, {id, p1, p2}).
@@ -176,6 +249,8 @@ coverage(_Config) ->
le = coverage_2([], []),
gt = coverage_2([], xxx),
+ error = coverage_3(#{key => <<"child">>}),
+ error = coverage_3(#{}),
ok.
coverage_1(Var) ->
@@ -205,6 +280,42 @@ coverage_2(Pre1, Pre2) ->
end
end.
+coverage_3(#{key := <<child>>}) when false ->
+ ok;
+coverage_3(#{}) ->
+ error.
+
+%% ERIERL-478: The validator failed to validate argument types when calls were
+%% shared and the types at the common block turned out wider than the join of
+%% each individual call site.
+call_sharing(_Config) ->
+ A_2 = {a, 1},
+ A_3 = {a, 1, 2},
+
+ A_2 = cs_1(id(A_2)),
+ A_3 = cs_1(id(A_3)),
+
+ B_2 = {b, 1},
+ B_3 = {b, 1, 2},
+ B_2 = cs_1(id(B_2)),
+ B_3 = cs_1(id(B_3)),
+
+ C_2 = {c, 1},
+ C_3 = {c, 1, 2},
+ {'EXIT',_} = (catch (cs_1(id(C_2)))),
+ {'EXIT',_} = (catch (cs_1(id(C_3)))),
+
+ ok.
+
+cs_1(Key) ->
+ A = case Key of
+ %% Must be a single line to trigger the bug.
+ {Tag, _, _} when Tag == a; Tag == b -> cs_2(Key); {Tag, _} when Tag == a; Tag == b -> cs_2(Key)
+ end,
+ id(A).
+
+cs_2(I) -> I.
+
id(I) ->
I.
diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
index e7bd88bc61..2596d62216 100644
--- a/lib/compiler/test/beam_ssa_SUITE.erl
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -23,12 +23,15 @@
init_per_group/2,end_per_group/2,
calls/1,tuple_matching/1,recv/1,maps/1,
cover_ssa_dead/1,combine_sw/1,share_opt/1,
- beam_ssa_dead_crash/1,stack_init/1]).
+ beam_ssa_dead_crash/1,stack_init/1,
+ mapfoldl/0,mapfoldl/1,
+ grab_bag/1,coverage/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [{group,p}].
+ [mapfoldl,
+ {group,p}].
groups() ->
[{p,test_lib:parallel(),
@@ -40,7 +43,9 @@ groups() ->
combine_sw,
share_opt,
beam_ssa_dead_crash,
- stack_init
+ stack_init,
+ grab_bag,
+ coverage
]}].
init_per_suite(Config) ->
@@ -208,6 +213,8 @@ recv(_Config) ->
%% tricky_recv_6/0 is a compile-time error.
tricky_recv_6(),
+ recv_coverage(),
+
ok.
sync_wait_mon({Pid, Ref}, Timeout) ->
@@ -357,7 +364,7 @@ tricky_recv_5a() ->
%% When fixing tricky_recv_5, we introduced a compiler crash when the common
-%% exit block was ?BADARG_BLOCK and floats were in the picture.
+%% exit block was ?EXCEPTION_BLOCK and floats were in the picture.
tricky_recv_6() ->
RefA = make_ref(),
RefB = make_ref(),
@@ -368,8 +375,79 @@ tricky_recv_6() ->
ok
end.
+recv_coverage() ->
+ self() ! 1,
+ a = recv_coverage_1(),
+ self() ! 2,
+ b = recv_coverage_1(),
+
+ self() ! 1,
+ a = recv_coverage_2(),
+ self() ! 2,
+ b = recv_coverage_2(),
+
+ ok.
+
+%% Similar to tricky_recv_5/0, but provides test coverage for the #b_switch{}
+%% terminator.
+recv_coverage_1() ->
+ receive
+ X=1 ->
+ %% Jump to common exit block through #b_switch{list=L}
+ case id(0) of
+ 0 -> a;
+ 1 -> b;
+ 2 -> c;
+ 3 -> d
+ end;
+ X=2 ->
+ %% Jump to common exit block through #b_switch{fail=F}
+ case id(42) of
+ 0 -> exit(quit);
+ 1 -> exit(quit);
+ 2 -> exit(quit);
+ 3 -> exit(quit);
+ _ -> b
+ end
+ end,
+ case X of
+ 1 -> a;
+ 2 -> b
+ end.
+
+%% Similar to recv_coverage_1/0, providing test coverage for #b_br{}.
+recv_coverage_2() ->
+ receive
+ X=1 ->
+ A = id(1),
+ %% Jump to common exit block through #b_br{succ=S}.
+ if
+ A =:= 1 -> a;
+ true -> exit(quit)
+ end;
+ X=2 ->
+ A = id(2),
+ %% Jump to common exit block through #b_br{fail=F}.
+ if
+ A =:= 1 -> exit(quit);
+ true -> a
+ end
+ end,
+ case X of
+ 1 -> a;
+ 2 -> b
+ end.
+
maps(_Config) ->
{'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)),
+
+ {jkl,nil,nil} = maps_2(#{abc => 0, jkl => 0}),
+ {def,ghi,abc} = maps_2(#{abc => 0, def => 0}),
+ {def,ghi,jkl} = maps_2(#{def => 0, jkl => 0}),
+ {mno,nil,abc} = maps_2(#{abc => 0, mno => 0, jkl => 0}),
+ {jkl,nil,nil} = maps_2(#{jkl => 0}),
+ error = maps_2(#{}),
+
ok.
maps_1(K) ->
@@ -377,6 +455,71 @@ maps_1(K) ->
#{K:=V} = #{},
V.
+maps_2(Map) ->
+ Res = maps_2a(Map),
+ Res = maps_2b(Map),
+ Res.
+
+maps_2a(#{} = Map) ->
+ case case Abc = is_map_key(abc, Map) of
+ false -> false;
+ _ -> is_map_key(def, Map)
+ end of
+ true ->
+ {def, ghi, abc};
+ false ->
+ case case Jkl = is_map_key(jkl, Map) of
+ false -> false;
+ _ -> is_map_key(def, Map)
+ end of
+ true ->
+ {def, ghi, jkl};
+ false ->
+ case case Abc of
+ false -> false;
+ _ -> is_map_key(mno, Map)
+ end of
+ true ->
+ {mno, nil, abc};
+ false ->
+ case Jkl of
+ true -> {jkl, nil, nil};
+ false -> error
+ end
+ end
+ end
+ end.
+
+maps_2b(#{}=Map) ->
+ case case is_map_key(abc, Map) of
+ false -> false;
+ _ -> is_map_key(def, Map)
+ end of
+ true ->
+ {def, ghi, abc};
+ false ->
+ case case is_map_key(jkl, Map) of
+ false -> false;
+ _ -> is_map_key(def, Map)
+ end of
+ true ->
+ {def, ghi, jkl};
+ false ->
+ case case is_map_key(abc, Map) of
+ false -> false;
+ _ -> is_map_key(mno, Map)
+ end of
+ true ->
+ {mno, nil, abc};
+ false ->
+ case is_map_key(jkl, Map) of
+ true -> {jkl, nil, nil};
+ false -> error
+ end
+ end
+ end
+ end.
+
-record(wx_ref, {type=any_type,ref=any_ref}).
cover_ssa_dead(_Config) ->
@@ -419,48 +562,14 @@ cover_ssa_dead(_Config) ->
40.0 = percentage(4.0, 10.0),
60.0 = percentage(6, 10),
- %% Cover '=:=', followed by '=/='.
- false = 'cover__=:=__=/='(41),
- true = 'cover__=:=__=/='(42),
- false = 'cover__=:=__=/='(43),
+ {'EXIT',{{badmatch,42},_}} = (catch #{key => abs(("a" = id(42)) /= teacher)}),
- %% Cover '<', followed by '=/='.
- true = 'cover__<__=/='(41),
- false = 'cover__<__=/='(42),
- false = 'cover__<__=/='(43),
+ <<>> = id(<< V || V <- [], V andalso false >>),
- %% Cover '=<', followed by '=/='.
- true = 'cover__=<__=/='(41),
- true = 'cover__=<__=/='(42),
- false = 'cover__=<__=/='(43),
-
- %% Cover '>=', followed by '=/='.
- false = 'cover__>=__=/='(41),
- true = 'cover__>=__=/='(42),
- true = 'cover__>=__=/='(43),
-
- %% Cover '>', followed by '=/='.
- false = 'cover__>__=/='(41),
- false = 'cover__>__=/='(42),
- true = 'cover__>__=/='(43),
+ false = id(([] = id([])) =/= []),
ok.
-'cover__=:=__=/='(X) when X =:= 42 -> X =/= 43;
-'cover__=:=__=/='(_) -> false.
-
-'cover__<__=/='(X) when X < 42 -> X =/= 42;
-'cover__<__=/='(_) -> false.
-
-'cover__=<__=/='(X) when X =< 42 -> X =/= 43;
-'cover__=<__=/='(_) -> false.
-
-'cover__>=__=/='(X) when X >= 42 -> X =/= 41;
-'cover__>=__=/='(_) -> false.
-
-'cover__>__=/='(X) when X > 42 -> X =/= 42;
-'cover__>__=/='(_) -> false.
-
format_str(Str, FormatData, IoList, EscChars) ->
Escapable = FormatData =:= escapable,
case id(Str) of
@@ -668,5 +777,337 @@ stack_init(Key, Map) ->
%% (if the second clause was executed).
id(Res).
+%% Test that compiler "optimizations" don't rewrite mapfold/3 to the
+%% equivalent of slow_mapfoldl/3.
+mapfoldl() ->
+ {N,Size} = mapfoldl_limits(),
+ {Time,_} = timer:tc(fun() ->
+ mapfoldl(fun(Sz, _) ->
+ erlang:garbage_collect(),
+ {Sz,erlang:make_tuple(Sz, a)}
+ end, [], [Size])
+ end),
+ Seconds = 15 + ceil(10 * Time * N / 1_000_000),
+ io:format("~p seconds timetrap\n", [Seconds]),
+ [{timetrap,{seconds,Seconds}}].
+
+mapfoldl(_Config) ->
+ test_mapfoldl_implementations(),
+ F = fun(Sz, _) ->
+ erlang:garbage_collect(),
+ {Sz,erlang:make_tuple(Sz, a)}
+ end,
+ {N,Size} = mapfoldl_limits(),
+ List = lists:duplicate(N, Size),
+ {List,Tuple} = mapfoldl(F, [], List),
+ {List,Tuple} = fast_mapfoldl(F, [], List),
+ Size = tuple_size(Tuple),
+ ok.
+
+mapfoldl_limits() ->
+ {1_000,100_000}.
+
+test_mapfoldl_implementations() ->
+ Seq = lists:seq(1, 10),
+ F = fun(N, Sum) -> {N,Sum+N} end,
+ {Seq,55} = mapfoldl(F, 0, Seq),
+ {Seq,55} = fast_mapfoldl(F, 0, Seq),
+ {Seq,55} = slow_mapfoldl(F, 0, Seq),
+ ok.
+
+mapfoldl(F, Acc0, [Hd|Tail]) ->
+ {R,Acc1} = F(Hd, Acc0),
+ {Rs,Acc2} = mapfoldl(F, Acc1, Tail),
+ {[R|Rs],Acc2};
+mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}.
+
+%% Here is an illustration of how the compiler used to sink
+%% get_tuple_element instructions in a way that would cause all
+%% versions of the accumulator to be kept until the end. The compiler
+%% now uses a heuristic to only sink get_tuple_element instructions if
+%% that would cause fewer values to be saved in the stack frame.
+slow_mapfoldl(F, Acc0, [Hd|Tail]) ->
+ Res1 = F(Hd, Acc0),
+ %% By saving the Res1 tuple, all intermediate accumulators will be
+ %% kept to the end.
+ Res2 = slow_mapfoldl(F, element(2, Res1), Tail),
+ {[element(1, Res1)|element(1, Res2)],element(2, Res2)};
+slow_mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}.
+
+%% Here is an illustration how the compiler should compile mapfoldl/3
+%% to avoid keeping all intermediate accumulators. Note that
+%% slow_mapfoldl/3 and fast_mapfoldl/3 use the same amount of stack
+%% space.
+fast_mapfoldl(F, Acc0, [Hd|Tail]) ->
+ Res1 = F(Hd, Acc0),
+ R = element(1, Res1),
+ Res2 = fast_mapfoldl(F, element(2, Res1), Tail),
+ {[R|element(1, Res2)],element(2, Res2)};
+fast_mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}.
+
+grab_bag(_Config) ->
+ {'EXIT',_} = (catch grab_bag_1()),
+ {'EXIT',_} = (catch grab_bag_2()),
+ {'EXIT',_} = (catch grab_bag_3()),
+ {'EXIT',_} = (catch grab_bag_4()),
+ {'EXIT',{function_clause,[{?MODULE,grab_bag_5,[a,17],_}|_]}} =
+ (catch grab_bag_5(a, 17)),
+ way = grab_bag_6(face),
+ no_match = grab_bag_6("ABC"),
+ no_match = grab_bag_6(any),
+ ok = grab_bag_7(),
+ [] = grab_bag_8(),
+ ok = grab_bag_9(),
+ whatever = grab_bag_10(ignore, whatever),
+ other = grab_bag_11(),
+ {'EXIT',_} = (catch grab_bag_12()),
+ {'EXIT',{{badmatch,[]},_}} = (catch grab_bag_13()),
+ timeout = grab_bag_14(),
+ ?MODULE = grab_bag_15(?MODULE),
+
+ error = grab_bag_16a(timeout_value),
+ {'EXIT',{timeout_value,_}} = (catch grab_bag_16a(whatever)),
+ {'EXIT',{timeout_value,_}} = (catch grab_bag_16b(whatever)),
+ timeout_value = grab_bag_16b(error),
+
+ fact = grab_bag_17(),
+
+ ok.
+
+grab_bag_1() ->
+ %% beam_kernel_to_ssa would crash when attempting to translate a make_fun
+ %% instruction without a destination variable.
+ (catch fun () -> 15 end)(true#{}).
+
+grab_bag_2() ->
+ %% is_guard_cg_safe/1 will be called with #cg_unreachable{}, which was
+ %% not handled.
+ 27
+ or
+ try
+ try
+ x#{}
+ catch
+ _:_ ->
+ []
+ end
+ after
+ false
+ end.
+
+grab_bag_3() ->
+ case
+ fun (V0)
+ when
+ %% The only thing left after optimizations would be
+ %% a bs_add instruction not followed by succeeded,
+ %% which would crash beam_ssa_codegen because there
+ %% was no failure label available.
+ binary_part(<<>>,
+ <<V0:V0/unit:196>>) ->
+ []
+ end
+ of
+ <<>> ->
+ []
+ end.
+
+grab_bag_4() ->
+ %% beam_kernel_to_ssa would crash because there was a #cg_phi{}
+ %% instruction that was not referenced from any #cg_break{}.
+ case $f of
+ V0 ->
+ try
+ try fy of
+ V0 ->
+ fu
+ catch
+ throw:$s ->
+ fy
+ end
+ catch
+ error:#{#{[] + [] => []} := false} when [] ->
+ fy
+ after
+ ok
+ end
+ end.
+
+grab_bag_5(A, B) when <<business:(node(power))>> ->
+ true.
+
+grab_bag_6(face) ->
+ way;
+grab_bag_6("ABC") when (node([]))#{size(door) => $k} ->
+ false;
+grab_bag_6(_) ->
+ no_match.
+
+grab_bag_7() ->
+ catch
+ case
+ case 1.6 of
+ %% The hd([] call will be translated to erlang:error(badarg).
+ %% This case exports two variables in Core Erlang (the
+ %% return value of the case and V). beam_kernel_to_ssa was not
+ %% prepared to handle a call to error/1 which is supposed to
+ %% export two variables.
+ <<0.5:(hd([])),V:false>> ->
+ ok
+ end
+ of
+ _ ->
+ V
+ end,
+ ok.
+
+%% ssa_opt_sink would crash if sys_core_fold had not been run.
+grab_bag_8() ->
+ try
+ []
+ catch
+ _:_ ->
+ try
+ []
+ catch
+ _:any:_ ->
+ a
+ end;
+ _:right ->
+ b
+ end.
+
+%% The ssa_opt_try optimization would leave a succeeded:body
+%% instruction followed by a #b_ret{} terminator, which would crash
+%% beam_ssa_pre_codegen.
+grab_bag_9() ->
+ catch
+ <<1 || 99, [] <- hour>> bsr false,
+ ok.
+
+grab_bag_10(_, V) ->
+ %% This function needs a stack frame in order to preserve V.
+ fun() -> ok end,
+ V.
+
+grab_bag_11() ->
+ try 0 of
+ false -> error;
+ true -> ok;
+ _ -> other
+ catch
+ _:_ ->
+ catched
+ end.
+
+grab_bag_12() ->
+ %% beam_ssa_pre_codegen would try to place the created map in x1.
+ %% That would not be safe because x0 is not initialized.
+ check_process_code(1, (#{})#{key := teacher}),
+ ok.
+
+grab_bag_13() ->
+ %% If sys_core_fold was skipped, beam_ssa_beam would leave
+ %% unreachable code with invalid phi nodes.
+ case <<810:true>> = [] of
+ <<709:false>> ->
+ ok;
+ whatever ->
+ case 42 of
+ 175 ->
+ {ok,case "b" of
+ $X -> time
+ end}
+ end
+ end.
+
+grab_bag_14() ->
+ %% If optimizations were turned off, beam_ssa_pre_codegen would
+ %% sanitize the binary construction instruction, replacing it with
+ %% a call to erlang:error/1, which is not allowed in a receive.
+ receive
+ #{<<42:(-1)>> := _} ->
+ ok
+ after 0 ->
+ timeout
+ end.
+
+grab_bag_15(V) ->
+ %% Instead of:
+ %%
+ %% move x0, y0
+ %% move y0, x0
+ %%
+ %% a swap instruction would be emitted by beam_ssa_codegen:
+ %%
+ %% swap x0, y0
+ %%
+ case [] of
+ [] -> V
+ end:all(),
+ V.
+
+grab_bag_16a(V) ->
+ try
+ catch 22,
+ receive
+ after bad ->
+ not_reached
+ end
+ catch
+ _:V ->
+ error
+ end.
+
+grab_bag_16b(V) ->
+ try
+ receive
+ after get() ->
+ ok
+ end
+ catch
+ V:Reason ->
+ Reason
+ end.
+
+grab_bag_17() ->
+ try "xwCl" of
+ V when V ->
+ <<[] || V>>;
+ [_|_] ->
+ %% Constant propagation in beam_ssa_codegen:prefer_xregs/2
+ %% would produce get_hd and get_tl instructions with literal
+ %% operands.
+ fact
+ catch
+ _:_ ->
+ []
+ end.
+
+
+coverage(_Config) ->
+
+ %% Cover beam_ssa_codegen:force_reg/2
+ no_match = case true of
+ <<_:42>> -> true;
+ _ -> no_match
+ end,
+
+ no_match = case [] of
+ <<$f:1.7>> -> ok;
+ _ -> no_match
+ end,
+ {'EXIT',{{badmatch,$T},_}} = (catch coverage_1()),
+
+ error = coverage_2(),
+ ok.
+
+coverage_1() ->
+ <<area/signed-bitstring>> = $T.
+
+coverage_2() when << []:<<0/native>> >> -> ok;
+coverage_2() -> error.
+
+
%% The identity function.
id(I) -> I.
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index f3b16fb1ae..65e4e59be9 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -25,7 +25,7 @@
cons/1,tuple/1,record_float/1,binary_float/1,float_compare/1,
arity_checks/1,elixir_binaries/1,find_best/1,
test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1,
- none_argument/1]).
+ none_argument/1,success_type_oscillation/1,type_subtraction/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -51,7 +51,9 @@ groups() ->
cover_lists_functions,
list_append,
bad_binary_unit,
- none_argument
+ none_argument,
+ success_type_oscillation,
+ type_subtraction
]}].
init_per_suite(Config) ->
@@ -85,6 +87,10 @@ integers(_Config) ->
two = do_integers_5(0, 2),
three = do_integers_5(0, 3),
+ {'EXIT',{badarith,_}} = (catch do_integers_6()),
+
+ house = do_integers_7(),
+
ok.
do_integers_1(B0) ->
@@ -131,6 +137,25 @@ do_integers_5(X0, Y0) ->
3 -> three
end.
+do_integers_6() ->
+ try b after 1 end band 0.
+
+do_integers_7() ->
+ try
+ 0
+ band
+ try
+ 0:any(),
+ ok
+ catch
+ bad_class:_:_ ->
+ {tag, "nt"}
+ end
+ catch
+ _:_:_ ->
+ house
+ end.
+
numbers(_Config) ->
Int = id(42),
true = is_integer(Int),
@@ -222,6 +247,8 @@ coverage(Config) ->
%% Cover beam_type:verified_type(none).
{'EXIT',{badarith,_}} = (catch (id(2) / id(1)) band 16#ff),
+ false = fun lot:life/147 == #{},
+
ok.
booleans(_Config) ->
@@ -539,5 +566,72 @@ uncompress(CompressedBinary) ->
%% did not handle properly.
zlib:uncompress(CompressedBinary).
+%% ERL-1289: The compiler could enter an endless loop when a return/argument
+%% type pairing was joined with another.
+%%
+%% While this always resulted in correct success types, the joined argument
+%% types could now cover more cases than they did before, making the effective
+%% return type less specific. When a function affected by this was analyzed
+%% again its success typing could become more specific again and start the
+%% process anew.
+success_type_oscillation(_Config) ->
+ Base = {a, []},
+
+ Base = sto_1(id(case_1_1)),
+ Base = sto_1(id(case_2_1)),
+ {b, [Base]} = sto_1(id(case_2_2)),
+
+ ok.
+
+sto_1(case_1_1) -> {a, []};
+sto_1(case_1_2) -> {a, []};
+sto_1(case_2_1) -> sto_1(case_1_1);
+sto_1(case_2_2) -> {b, [sto_1(case_1_1)]};
+sto_1(case_2_3) -> {b, [sto_1(case_1_1)]};
+sto_1(case_2_4) -> {b, [sto_1(case_1_2)]};
+sto_1(case_3_1) -> {b, [sto_1(case_2_1)]};
+sto_1(case_3_2) -> {b, [sto_1(case_2_2)]};
+sto_1(case_3_3) -> {b, [sto_1(case_2_3)]};
+sto_1(case_3_4) -> {b, [sto_1(case_2_4)]};
+sto_1(case_4_1) -> {b, [sto_1(case_3_1)]};
+sto_1(case_4_2) -> {b, [sto_1(case_3_2)]};
+sto_1(step_4_3) -> {b, [sto_1(case_3_3)]}.
+
+%% ERL-1440: On inequality, we subtracted the type *common to* both variables
+%% rather than the left-hand type from the right-hand variable and vice versa,
+%% giving an erroneously narrow type.
+%%
+%% In the test below, we have functions returning integers ranged 1..2 and
+%% 2..3 and test for their equality. We know that it can only succeed when both
+%% return 2, but they can fail when the former returns 1 or the latter returns
+%% 3, so we must not subtract 2 on the failure path.
+type_subtraction(Config) when is_list(Config) ->
+ true = type_subtraction_1(id(<<"A">>)),
+ ok.
+
+type_subtraction_1(_x@1) ->
+ _a@1 = ts_12(_x@1),
+ _b@1 = ts_23(_x@1),
+ case _a@1 /= _b@1 of
+ false -> error;
+ true -> _a@1 =:= 3 andalso _b@1 =:= 2
+ end.
+
+ts_12(_x@1) ->
+ case _x@1 == <<"A">> of
+ false ->
+ 2;
+ true ->
+ 3
+ end.
+
+ts_23(_x@1) ->
+ case _x@1 == <<"A">> of
+ false ->
+ 1;
+ true ->
+ 2
+ end.
+
id(I) ->
I.
diff --git a/lib/compiler/test/beam_types_SUITE.erl b/lib/compiler/test/beam_types_SUITE.erl
new file mode 100644
index 0000000000..4fbf6a130e
--- /dev/null
+++ b/lib/compiler/test/beam_types_SUITE.erl
@@ -0,0 +1,166 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(beam_types_SUITE).
+
+-define(BEAM_TYPES_INTERNAL, true).
+-include_lib("compiler/src/beam_types.hrl").
+
+-export([all/0, suite/0, groups/0,
+ init_per_suite/1, end_per_suite/1]).
+
+-export([absorption/1,
+ associativity/1,
+ commutativity/1,
+ idempotence/1,
+ identity/1,
+ subtraction/1]).
+
+-export([binary_absorption/1,
+ integer_absorption/1,
+ integer_associativity/1,
+ tuple_absorption/1,
+ tuple_set_limit/1]).
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group,property_tests},
+ binary_absorption,
+ integer_absorption,
+ integer_associativity,
+ tuple_absorption,
+ tuple_set_limit].
+
+groups() ->
+ [{property_tests,[parallel],
+ [absorption,
+ associativity,
+ commutativity,
+ idempotence,
+ identity,
+ subtraction]}].
+
+init_per_suite(Config) ->
+ ct_property_test:init_per_suite(Config).
+
+end_per_suite(Config) ->
+ Config.
+
+absorption(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(beam_types_prop:absorption()).
+ true = ct_property_test:quickcheck(beam_types_prop:absorption(), Config).
+
+associativity(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(beam_types_prop:associativity()).
+ true = ct_property_test:quickcheck(beam_types_prop:associativity(), Config).
+
+commutativity(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(beam_types_prop:commutativity()).
+ true = ct_property_test:quickcheck(beam_types_prop:commutativity(), Config).
+
+idempotence(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(beam_types_prop:idempotence()).
+ true = ct_property_test:quickcheck(beam_types_prop:idempotence(), Config).
+
+identity(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(beam_types_prop:identity()).
+ true = ct_property_test:quickcheck(beam_types_prop:identity(), Config).
+
+subtraction(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(beam_types_prop:subtraction()).
+ true = ct_property_test:quickcheck(beam_types_prop:subtraction(), Config).
+
+binary_absorption(Config) when is_list(Config) ->
+ %% These binaries should meet into {binary,12} as that's the best common
+ %% unit for both types.
+ A = #t_bitstring{size_unit=4},
+ B = #t_bitstring{size_unit=6},
+
+ #t_bitstring{size_unit=12} = beam_types:meet(A, B),
+ #t_bitstring{size_unit=2} = beam_types:join(A, B),
+
+ A = beam_types:meet(A, beam_types:join(A, B)),
+ A = beam_types:join(A, beam_types:meet(A, B)),
+
+ ok.
+
+integer_absorption(Config) when is_list(Config) ->
+ %% Integers that don't overlap at all should never meet.
+ A = #t_integer{elements={2,3}},
+ B = #t_integer{elements={4,5}},
+
+ none = beam_types:meet(A, B),
+ #t_integer{elements={2,5}} = beam_types:join(A, B),
+
+ A = beam_types:meet(A, beam_types:join(A, B)),
+ A = beam_types:join(A, beam_types:meet(A, B)),
+
+ ok.
+
+integer_associativity(Config) when is_list(Config) ->
+ A = #t_integer{elements={3,5}},
+ B = #t_integer{elements={4,6}},
+ C = #t_integer{elements={5,5}},
+
+ %% a ∨ (b ∨ c) = (a ∨ b) ∨ c,
+ LHS_Join = beam_types:join(A, beam_types:join(B, C)),
+ RHS_Join = beam_types:join(beam_types:join(A, B), C),
+ #t_integer{elements={3,6}} = LHS_Join = RHS_Join,
+
+ %% a ∧ (b ∧ c) = (a ∧ b) ∧ c.
+ LHS_Meet = beam_types:meet(A, beam_types:meet(B, C)),
+ RHS_Meet = beam_types:meet(beam_types:meet(A, B), C),
+ #t_integer{elements={5,5}} = LHS_Meet = RHS_Meet,
+
+ ok.
+
+tuple_absorption(Config) when is_list(Config) ->
+ %% An inexact tuple can't meet an exact one that's smaller
+
+ A = #t_tuple{size=3,exact=true,
+ elements=#{1 => #t_atom{elements=[gurka]}}},
+ B = #t_tuple{size=5,exact=false,
+ elements=#{3 => #t_atom{elements=[gaffel]}}},
+
+ A = beam_types:meet(A, beam_types:join(A, B)),
+ A = beam_types:join(A, beam_types:meet(A, B)),
+
+ ok.
+
+tuple_set_limit(Config) when is_list(Config) ->
+ %% When joining two tuple sets of differing sizes, the resulting set could
+ %% become larger than ?TUPLE_SET_LIMIT.
+
+ As = [#t_tuple{size=N,exact=true,
+ elements=#{ 1 => #t_integer{elements={N,N}} }} ||
+ N <- lists:seq(1, ?TUPLE_SET_LIMIT)],
+
+ Bs = [#t_tuple{size=1,exact=true,
+ elements=#{ 1 => #t_integer{elements={N,N}} }} ||
+ N <- lists:seq(1, ?TUPLE_SET_LIMIT)],
+
+ A = beam_types:join(As),
+ B = beam_types:join(Bs),
+
+ beam_types:verified_type(beam_types:join(A, B)),
+
+ ok.
diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl
index 778a099987..38be30b165 100644
--- a/lib/compiler/test/beam_validator_SUITE.erl
+++ b/lib/compiler/test/beam_validator_SUITE.erl
@@ -28,16 +28,18 @@
dead_code/1,
overwrite_catchtag/1,overwrite_trytag/1,accessing_tags/1,bad_catch_try/1,
cons_guard/1,
- freg_range/1,freg_uninit/1,freg_state/1,
+ freg_range/1,freg_uninit/1,
bad_bin_match/1,bad_dsetel/1,
state_after_fault_in_catch/1,no_exception_in_catch/1,
undef_label/1,illegal_instruction/1,failing_gc_guard_bif/1,
map_field_lists/1,cover_bin_opt/1,
val_dsetel/1,bad_tuples/1,bad_try_catch_nesting/1,
receive_stacked/1,aliased_types/1,type_conflict/1,
- infer_on_eq/1,infer_dead_value/1,
+ infer_on_eq/1,infer_dead_value/1,infer_on_ne/1,
+ branch_to_try_handler/1,call_without_stack/1,
receive_marker/1,safe_instructions/1,
- missing_return_type/1,infer_on_ne/1]).
+ missing_return_type/1,will_bif_succeed/1,
+ bs_saved_position_units/1,parent_container/1]).
-include_lib("common_test/include/ct.hrl").
@@ -61,15 +63,17 @@ groups() ->
unsafe_catch,dead_code,
overwrite_catchtag,overwrite_trytag,accessing_tags,
bad_catch_try,cons_guard,freg_range,freg_uninit,
- freg_state,bad_bin_match,bad_dsetel,
+ bad_bin_match,bad_dsetel,
state_after_fault_in_catch,no_exception_in_catch,
undef_label,illegal_instruction,failing_gc_guard_bif,
map_field_lists,cover_bin_opt,val_dsetel,
bad_tuples,bad_try_catch_nesting,
receive_stacked,aliased_types,type_conflict,
- infer_on_eq,infer_dead_value,receive_marker,
- safe_instructions,missing_return_type,
- infer_on_ne]}].
+ infer_on_eq,infer_dead_value,infer_on_ne,
+ branch_to_try_handler,call_without_stack,
+ receive_marker,safe_instructions,
+ missing_return_type,will_bif_succeed,
+ bs_saved_position_units,parent_container]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -152,19 +156,34 @@ stack(Config) when is_list(Config) ->
call_last(Config) when is_list(Config) ->
Errors = do_val(call_last, Config),
- [{{t,a,1},{{call_last,1,{f,8},2},9,{allocated,1}}},
+ [{{t,a,1},
+ {{call_last,1,{f,8},2},9,{allocated,1}}},
{{t,b,1},
- {{call_ext_last,2,{extfunc,lists,seq,2},2},
- 10,
- {allocated,1}}}] = Errors,
+ {{call_ext_last,2,{extfunc,lists,seq,2},2},10,{allocated,1}}},
+ {{t,baz,2},
+ {{call_ext_only,2,{extfunc,erlang,put,2}},5,{allocated,0}}},
+ {{t,biz,2},
+ {{call_only,2,{f,10}},5,{allocated,0}}}] = Errors,
+ ok.
+
+call_without_stack(Config) when is_list(Config) ->
+ Errors = do_val(call_without_stack, Config),
+ [{{t,local,2},
+ {{call,2,{f,2}},4,{allocated,none}}},
+ {{t,remote,2},
+ {{call_ext,2,{extfunc,lists,seq,2}},4,{allocated,none}}}] = Errors,
ok.
merge_undefined(Config) when is_list(Config) ->
Errors = do_val(merge_undefined, Config),
- [{{t,handle_call,2},
+ [{{t,undecided,2},
{{call_ext,2,{extfunc,debug,filter,2}},
22,
- {uninitialized_reg,{y,_}}}}] = Errors,
+ {allocated,undecided}}},
+ {{t,uninitialized,2},
+ {{call_ext,2,{extfunc,io,format,2}},
+ 17,
+ {uninitialized_reg,{y,1}}}}] = Errors,
ok.
uninit(Config) when is_list(Config) ->
@@ -221,11 +240,11 @@ bad_catch_try(Config) when is_list(Config) ->
{{catch_end,{x,9}},
8,{invalid_tag_register,{x,9}}}},
{{bad_catch_try,bad_3,1},
- {{catch_end,{y,1}},9,{invalid_tag,{y,1},{atom,kalle}}}},
+ {{catch_end,{y,1}},9,{invalid_tag,{y,1},{t_atom,[kalle]}}}},
{{bad_catch_try,bad_4,1},
{{'try',{x,0},{f,15}},5,{invalid_tag_register,{x,0}}}},
{{bad_catch_try,bad_5,1},
- {{try_case,{y,1}},12,{invalid_tag,{y,1},term}}},
+ {{try_case,{y,1}},12,{invalid_tag,{y,1},any}}},
{{bad_catch_try,bad_6,1},
{{move,{integer,1},{y,1}},7,
{invalid_store,{y,1}}}}] = Errors,
@@ -236,7 +255,7 @@ cons_guard(Config) when is_list(Config) ->
[{{cons,foo,1},
{{get_list,{x,0},{x,1},{x,2}},
5,
- {bad_type,{needed,cons},{actual,term}}}}] = Errors,
+ {bad_type,{needed,{t_cons,any,any}},{actual,any}}}}] = Errors,
ok.
freg_range(Config) when is_list(Config) ->
@@ -267,32 +286,10 @@ freg_uninit(Config) when is_list(Config) ->
{uninitialized_reg,{fr,1}}}},
{{t,sum_2,2},
{{bif,fadd,{f,0},[{fr,0},{fr,1}],{fr,0}},
- 9,
+ 10,
{uninitialized_reg,{fr,0}}}}] = Errors,
ok.
-freg_state(Config) when is_list(Config) ->
- Errors = do_val(freg_state, Config),
- [{{t,sum_1,2},
- {{bif,fmul,{f,0},[{fr,0},{fr,1}],{fr,0}},
- 6,
- {bad_floating_point_state,undefined}}},
- {{t,sum_2,2},
- {{fmove,{fr,0},{x,0}},
- 8,
- {bad_floating_point_state,cleared}}},
- {{t,sum_3,2},
- {{bif,'-',{f,0},[{x,1},{x,0}],{x,1}},
- 8,
- {unsafe_instruction,{float_error_state,cleared}}}},
- {{t,sum_4,2},
- {{fcheckerror,{f,0}},
- 4,
- {bad_floating_point_state,undefined}}},
- {{t,sum_5,2},
- {fclearerror,5,{bad_floating_point_state,cleared}}}] = Errors,
- ok.
-
bad_bin_match(Config) when is_list(Config) ->
[{{t,t,1},{return,5,{match_context,{x,0}}}}] =
do_val(bad_bin_match, Config),
@@ -321,7 +318,7 @@ state_after_fault_in_catch(Config) when is_list(Config) ->
no_exception_in_catch(Config) when is_list(Config) ->
Errors = do_val(no_exception_in_catch, Config),
[{{no_exception_in_catch,nested_of_1,4},
- {{move,{x,3},{x,0}},87,{uninitialized_reg,{x,3}}}}] = Errors,
+ {{try_case_end,{x,0}},180,ambiguous_catch_try_state}}] = Errors,
ok.
undef_label(Config) when is_list(Config) ->
@@ -343,7 +340,7 @@ undef_label(Config) when is_list(Config) ->
5},
Errors = beam_val(M),
[{{undef_label,t,1},{undef_labels,[42]}},
- {{undef_label,x,1},{return,4,no_entry_label}}] = Errors,
+ {{undef_label,x,1},no_entry_label}] = Errors,
ok.
illegal_instruction(Config) when is_list(Config) ->
@@ -367,8 +364,8 @@ illegal_instruction(Config) when is_list(Config) ->
Errors = beam_val(M),
[{{illegal_instruction,t,1},
{{my_illegal_instruction,{x,0}},4,unknown_instruction}},
- {{'_',x,1},{bad_func_info,1,illegal_instruction}},
- {{'_',y,0},{[],0,illegal_instruction}}] = Errors,
+ {{illegal_instruction,x,1},invalid_function_header},
+ {{illegal_instruction,y,0},invalid_function_header}] = Errors,
ok.
%% The beam_validator used to assume that a GC guard BIF could
@@ -405,13 +402,12 @@ process_request_bar(Pid, [Response]) when is_pid(Pid) ->
map_field_lists(Config) ->
Errors = do_val(map_field_lists, Config),
[{{map_field_lists,x,1},
- {{test,has_map_fields,{f,1},{x,0},
- {list,[{atom,a},{atom,a}]}},
- 5,
+ {{test,has_map_fields,{f,1},{x,0},{list,[{atom,a},{atom,a}]}},
+ 6,
keys_not_unique}},
{{map_field_lists,y,1},
{{test,has_map_fields,{f,3},{x,0},{list,[]}},
- 5,
+ 6,
empty_field_list}}
] = Errors.
@@ -520,13 +516,13 @@ destroy_reg({Tag,N}) ->
bad_tuples(Config) ->
Errors = do_val(bad_tuples, Config),
[{{bad_tuples,heap_overflow,1},
- {{put,{x,0}},8,{heap_overflow,{left,0},{wanted,1}}}},
+ {{put,{x,0}},9,{heap_overflow,{left,0},{wanted,1}}}},
{{bad_tuples,long,2},
- {{put,{atom,too_long}},8,not_building_a_tuple}},
+ {{put,{atom,too_long}},9,not_building_a_tuple}},
{{bad_tuples,self_referential,1},
- {{put,{x,1}},7,{tuple_in_progress,{x,1}}}},
+ {{put,{x,1}},8,{unfinished_tuple,{x,1}}}},
{{bad_tuples,short,1},
- {{move,{x,1},{x,0}},7,{tuple_in_progress,{x,1}}}}] = Errors,
+ {{move,{x,1},{x,0}},8,{unfinished_tuple,{x,1}}}}] = Errors,
ok.
@@ -534,7 +530,7 @@ bad_try_catch_nesting(Config) ->
Errors = do_val(bad_try_catch_nesting, Config),
[{{bad_try_catch_nesting,main,2},
{{'try',{y,2},{f,3}},
- 7,
+ 8,
{bad_try_catch_nesting,{y,2},[{{y,1},{trytag,[5]}}]}}}] = Errors,
ok.
@@ -543,37 +539,37 @@ receive_stacked(Config) ->
Errors = do_val(Mod, Config),
[{{receive_stacked,f1,0},
{{loop_rec_end,{f,3}},
- 17,
+ 18,
{fragile_message_reference,{y,_}}}},
{{receive_stacked,f2,0},
- {{test_heap,3,0},10,{fragile_message_reference,{y,_}}}},
+ {{test_heap,3,0},11,{fragile_message_reference,{y,_}}}},
{{receive_stacked,f3,0},
- {{test_heap,3,0},10,{fragile_message_reference,{y,_}}}},
+ {{test_heap,3,0},11,{fragile_message_reference,{y,_}}}},
{{receive_stacked,f4,0},
- {{test_heap,3,0},10,{fragile_message_reference,{y,_}}}},
+ {{test_heap,3,0},11,{fragile_message_reference,{y,_}}}},
{{receive_stacked,f5,0},
{{loop_rec_end,{f,23}},
- 23,
+ 24,
{fragile_message_reference,{y,_}}}},
{{receive_stacked,f6,0},
{{gc_bif,byte_size,{f,29},0,[{y,_}],{x,0}},
- 12,
+ 13,
{fragile_message_reference,{y,_}}}},
{{receive_stacked,f7,0},
{{loop_rec_end,{f,33}},
- 20,
+ 21,
{fragile_message_reference,{y,_}}}},
{{receive_stacked,f8,0},
{{loop_rec_end,{f,38}},
- 20,
+ 21,
{fragile_message_reference,{y,_}}}},
{{receive_stacked,m1,0},
{{loop_rec_end,{f,43}},
- 19,
+ 20,
{fragile_message_reference,{y,_}}}},
{{receive_stacked,m2,0},
{{loop_rec_end,{f,48}},
- 33,
+ 34,
{fragile_message_reference,{y,_}}}}] = Errors,
%% Compile the original source code as a smoke test.
@@ -709,6 +705,25 @@ idv_1({_A, _B, _C, _D, _E, F, G},
idv_1(_A, _B) ->
error.
+%% ERL-998; type inference for select_val (#b_switch{}) was more clever than
+%% that for is_ne_exact (#b_br{}), sometimes failing validation when the type
+%% optimization pass acted on the former and the validator got the latter.
+
+-record(ion, {state}).
+
+infer_on_ne(Config) when is_list(Config) ->
+ #ion{state = closing} = ion_1(#ion{ state = id(open) }),
+ #ion{state = closing} = ion_close(#ion{ state = open }),
+ ok.
+
+ion_1(State = #ion{state = open}) -> ion_2(State);
+ion_1(State = #ion{state = closing}) -> ion_2(State).
+
+ion_2(State = #ion{state = open}) -> ion_close(State);
+ion_2(#ion{state = closing}) -> ok.
+
+ion_close(State = #ion{}) -> State#ion{state = closing}.
+
%% ERL-995: The first solution to ERIERL-348 was incomplete and caused
%% validation to fail when living values depended on delayed type inference on
%% "dead" values.
@@ -726,6 +741,17 @@ idv_2(State) ->
idv_called_once(_State) -> ok.
+%% Direct jumps to try/catch handlers crash the emulator and must fail
+%% validation. This is provoked by OTP-15945.
+
+branch_to_try_handler(Config) ->
+ Errors = do_val(branch_to_try_handler, Config),
+ [{{branch_to_try_handler,main,1},
+ {{bif,tuple_size,{f,3},[{y,0}],{x,0}},
+ 13,
+ {illegal_branch,try_handler,3}}}] = Errors,
+ ok.
+
receive_marker(Config) when is_list(Config) ->
Errors = do_val(receive_marker, Config),
@@ -762,27 +788,83 @@ mrt_1(Bool) ->
true = is_boolean(Bool),
Bool.
-%% ERL-1212: validation failed to infer types on both sides of '=/='
-infer_on_ne(Config) when is_list(Config) ->
- empty = infer_on_ne_1([]),
+%% ERL-1340: the unit of previously saved match positions wasn't updated.
+bs_saved_position_units(Config) when is_list(Config) ->
+ M = {bs_saved_position_units,
+ [{no_errors,1},{some_errors,1}],
+ [],
+ [{function,ctx_test_8,1,2,
+ [{label,1},
+ {func_info,{atom,bs_saved_position_units},{atom,ctx_test_8},1},
+ {label,2},
+ {'%',
+ {var_info,
+ {x,0},
+ [{type,{t_bs_context,8,0,0}},accepts_match_context]}},
+ {move,nil,{x,0}},
+ return]},
+ {function,no_errors,1,4,
+ [{label,3},
+ {func_info,{atom,bs_saved_position_units},{atom,no_errors},1},
+ {label,4},
+ {'%',{var_info,{x,0},[accepts_match_context]}},
+ {test,bs_start_match3,{f,3},1,[{x,0}],{x,1}},
+ {bs_get_position,{x,1},{x,0},2},
+ {test,bs_test_unit,{f,5},[{x,1},8]},
+ {bs_set_position,{x,1},{x,0}},
+ {test,bs_get_binary2,
+ {f,5},
+ 2,
+ [{x,1},{atom,all},1,{field_flags,[unsigned,big]}],
+ {x,2}},
+ {bs_set_position,{x,1},{x,0}},
+ {bs_get_tail,{x,1},{x,0},3},
+ {test,is_eq_exact,{f,5},[{x,2},{x,0}]},
+ {move,{x,1},{x,0}},
+ %% Context unit should be 8 here.
+ {call_only,1,{f,2}},
+ {label,5},
+ {bs_get_tail,{x,1},{x,0},2},
+ {jump,{f,3}}]},
+ {function,some_errors,1,7,
+ [{label,6},
+ {func_info,{atom,bs_saved_position_units},{atom,some_errors},1},
+ {label,7},
+ {'%',{var_info,{x,0},[accepts_match_context]}},
+ {test,bs_start_match3,{f,6},1,[{x,0}],{x,1}},
+ {bs_get_position,{x,1},{x,0},2},
+ {test,bs_get_binary2,
+ {f,8},
+ 2,
+ [{x,1},{atom,all},4,{field_flags,[unsigned,big]}],
+ {x,2}},
+ {bs_set_position,{x,1},{x,0}},
+ {test,bs_test_unit,{f,9},[{x,1},3]},
+ {bs_set_position,{x,1},{x,0}},
+ {bs_get_tail,{x,1},{x,0},3},
+ {test,is_eq_exact,{f,8},[{x,2},{x,0}]},
+ {move,{x,1},{x,0}},
+ %% Context unit should be 12 here, failing validation.
+ {call_only,1,{f,2}},
+ {label,8},
+ {bs_get_tail,{x,1},{x,0},2},
+ {jump,{f,6}},
+ {label,9},
+ %% Context unit should be 4 here.
+ {move,nil,{x,0}},
+ return]}],
+ 10},
- gurka = infer_on_ne_1([a]),
- gaffel = infer_on_ne_1([b]),
+ Errors = beam_val(M),
- ok.
+ [{{bs_saved_position_units,some_errors,1},
+ {{call_only,1,{f,2}},
+ 14,
+ {bad_arg_type,{x,0},
+ {t_bs_context,12,0,0},
+ {t_bs_context,8,0,0}}}}] = Errors,
-infer_on_ne_1(FilterList) ->
- NoFilters = (FilterList == []),
- OtherFilters = (length(FilterList) == 1),
- case id(FilterList) of
- _ when (NoFilters == true) ->
- empty;
- _ when (OtherFilters == true) ->
- case hd(FilterList) of
- a -> gurka;
- b -> gaffel
- end
- end.
+ ok.
%%%-------------------------------------------------------------------------
@@ -817,7 +899,7 @@ do_val(Mod, Config) ->
beam_val(M) ->
Name = atom_to_list(element(1, M)),
- {error,[{Name,Errors0}]} = beam_validator:module(M, []),
+ {error,[{Name,Errors0}]} = beam_validator:validate(M, strong),
Errors = [E || {beam_validator,E} <- Errors0],
_ = [io:put_chars(beam_validator:format_error(E)) ||
E <- Errors],
@@ -843,5 +925,40 @@ night(Turned) ->
participating(_, _, _, _) -> ok.
+%% map_get was known as returning 'none', but 'will_succeed' still returned
+%% 'maybe' causing validation to continue, eventually exploding when the 'none'
+%% value was used.
+will_bif_succeed(_Config) ->
+ ok = f1(body).
+
+%% +no_ssa_opt
+f1(body) when map_get(girl, #{friend => node()}); [], community ->
+ case $q and $K of
+ _V0 ->
+ 0.1825965401179273;
+ 0 ->
+ state#{[] => 0.10577334580729858, $J => 0}
+ end;
+f1(body) ->
+ ok.
+
+%% ERL-1426: When a value was extracted from a tuple, subsequent type tests did
+%% not update the type of said tuple.
+
+-record(pc, {a}).
+
+parent_container(_Config) ->
+ ok = pc_1(id(#pc{a=true})).
+
+pc_1(#pc{a=A}=R) ->
+ case A of
+ true -> ok;
+ false -> ok
+ end,
+ ok = pc_2(R).
+
+pc_2(_R) ->
+ ok.
+
id(I) ->
I.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/branch_to_try_handler.S b/lib/compiler/test/beam_validator_SUITE_data/branch_to_try_handler.S
new file mode 100644
index 0000000000..6d43ec7b54
--- /dev/null
+++ b/lib/compiler/test/beam_validator_SUITE_data/branch_to_try_handler.S
@@ -0,0 +1,48 @@
+{module, branch_to_try_handler}. %% version = 0
+
+{exports, [{main,1}]}.
+
+{attributes, []}.
+
+{labels, 11}.
+
+{function, main, 1, 2}.
+ {label,1}.
+ {line,[{location,"t.erl",4}]}.
+ {func_info,{atom,branch_to_try_handler},{atom,main},1}.
+ {label,2}.
+ {allocate,2,1}.
+ {move,{x,0},{y,0}}.
+ {'try',{y,1},{f,3}}.
+ {move,{atom,ignored},{x,0}}.
+ {line,[{location,"t.erl",6}]}.
+ {call,1,{f,6}}.
+ {'%',{type_info,{x,0},{t_atom,[ignored]}}}.
+ {line,[{location,"t.erl",7}]}.
+ %%
+ %% Fail directly to the try handler instead of throwing an exception; this
+ %% will crash the emulator.
+ %%
+ {bif,tuple_size,{f,3},[{y,0}],{x,0}}.
+ %%
+ {test,is_eq_exact,{f,4},[{x,0},{integer,1}]}.
+ {move,{atom,error},{x,0}}.
+ {try_end,{y,1}}.
+ {deallocate,2}.
+ return.
+ {label,3}.
+ {try_case,{y,1}}.
+ {move,{atom,ok},{x,0}}.
+ {deallocate,2}.
+ return.
+ {label,4}.
+ {line,[{location,"t.erl",7}]}.
+ {badmatch,{x,0}}.
+
+{function, id, 1, 6}.
+ {label,5}.
+ {line,[{location,"t.erl",13}]}.
+ {func_info,{atom,branch_to_try_handler},{atom,id},1}.
+ {label,6}.
+ {'%',{type_info,{x,0},{t_atom,[ignored]}}}.
+ return.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/call_last.S b/lib/compiler/test/beam_validator_SUITE_data/call_last.S
index 827b6c0ae6..ff81da1b57 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/call_last.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/call_last.S
@@ -1,6 +1,6 @@
{module, call_last}. %% version = 0
-{exports, [{a,1},{b,1},{bar,1},{foo,1},{module_info,0},{module_info,1}]}.
+{exports, [{a,1},{b,1},{bar,1},{foo,1},{baz,2},{biz,2}]}.
{attributes, []}.
@@ -53,19 +53,16 @@
{'%live',1}.
return.
-
-{function, module_info, 0, 10}.
+{function, baz, 2, 10}.
{label,9}.
- {func_info,{atom,t},{atom,module_info},0}.
+ {func_info,{atom,t},{atom,baz},2}.
{label,10}.
- {move,{atom,t},{x,0}}.
- {call_ext_only,1,{extfunc,erlang,get_module_info,1}}.
-
+ {allocate,0,2}.
+ {call_ext_only,2,{extfunc,erlang,put,2}}.
-{function, module_info, 1, 12}.
+{function, biz, 2, 12}.
{label,11}.
- {func_info,{atom,t},{atom,module_info},1}.
+ {func_info,{atom,t},{atom,biz},2}.
{label,12}.
- {move,{x,0},{x,1}}.
- {move,{atom,t},{x,0}}.
- {call_ext_only,2,{extfunc,erlang,get_module_info,2}}.
+ {allocate,0,2}.
+ {call_only,2,{f,10}}.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/call_without_stack.S b/lib/compiler/test/beam_validator_SUITE_data/call_without_stack.S
new file mode 100644
index 0000000000..9ccbc163e3
--- /dev/null
+++ b/lib/compiler/test/beam_validator_SUITE_data/call_without_stack.S
@@ -0,0 +1,21 @@
+{module, call_without_stack}. %% version = 0
+
+{exports, [{remote,2},{local,2}]}.
+
+{attributes, []}.
+
+{labels, 9}.
+
+{function, remote, 2, 2}.
+ {label,1}.
+ {func_info,{atom,t},{atom,remote},2}.
+ {label,2}.
+ {call_ext,2,{extfunc,lists,seq,2}}.
+ if_end.
+
+{function, local, 2, 4}.
+ {label,3}.
+ {func_info,{atom,t},{atom,local},2}.
+ {label,4}.
+ {call,2,{f,2}}.
+ if_end.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/freg_state.S b/lib/compiler/test/beam_validator_SUITE_data/freg_state.S
deleted file mode 100644
index 7466763482..0000000000
--- a/lib/compiler/test/beam_validator_SUITE_data/freg_state.S
+++ /dev/null
@@ -1,59 +0,0 @@
-{module, freg_state}. %% version = 0
-
-{exports, [{sum_1,2},{sum_2,2},{sum_3,2},{sum_4,2},{sum_5,2}]}.
-
-{attributes, []}.
-
-
-{function, sum_1, 2, 2}.
- {label,1}.
- {func_info,{atom,t},{atom,sum_1},2}.
- {label,2}.
- {fconv,{x,0},{fr,0}}.
- {fconv,{x,1},{fr,1}}.
- {bif,fmul,{f,0},[{fr,0},{fr,1}],{fr,0}}.
- {'%live',1}.
- return.
-
-{function, sum_2, 2, 4}.
- {label,3}.
- {func_info,{atom,t},{atom,sum_2},2}.
- {label,4}.
- {fconv,{x,0},{fr,0}}.
- {fconv,{x,1},{fr,1}}.
- fclearerror.
- {bif,fmul,{f,0},[{fr,0},{fr,1}],{fr,0}}.
- {fmove,{fr,0},{x,0}}.
- {'%live',1}.
- return.
-
-{function, sum_3, 2, 6}.
- {label,5}.
- {func_info,{atom,t},{atom,sum_3},2}.
- {label,6}.
- {fconv,{x,0},{fr,0}}.
- {fconv,{x,1},{fr,1}}.
- fclearerror.
- {bif,fmul,{f,0},[{fr,0},{fr,1}],{fr,0}}.
- {bif,'-',{f,0},[{x,1},{x,0}],{x,1}}.
- {fcheckerror,{f,0}}.
- {fmove,{fr,0},{x,0}}.
- {'%live',1}.
- return.
-
-{function, sum_4, 2, 8}.
- {label,6}.
- {func_info,{atom,t},{atom,sum_4},2}.
- {label,8}.
- {fcheckerror,{f,0}}.
- {fmove,{fr,0},{x,0}}.
- {'%live',1}.
- return.
-
-{function, sum_5, 2, 10}.
- {label,9}.
- {func_info,{atom,t},{atom,sum_5},2}.
- {label,10}.
- fclearerror.
- fclearerror.
- return.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/freg_uninit.S b/lib/compiler/test/beam_validator_SUITE_data/freg_uninit.S
index 71e833446a..2d4cbc9388 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/freg_uninit.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/freg_uninit.S
@@ -21,12 +21,14 @@
{label,3}.
{func_info,{atom,t},{atom,sum_2},2}.
{label,4}.
+ {allocate,0,2}.
{fconv,{x,0},{fr,0}}.
{fconv,{x,1},{fr,1}}.
fclearerror.
{fcheckerror,{f,0}}.
{call,2,{f,6}}.
{bif,fadd,{f,0},[{fr,0},{fr,1}],{fr,0}}.
+ {deallocate,0}.
return.
{function, foo, 2, 6}.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S b/lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S
index aa344807e4..96bd9708d4 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/merge_undefined.S
@@ -1,15 +1,14 @@
{module, merge_undefined}. %% version = 0
-{exports, [{bar,2},{foo,1},{handle_call,2},{module_info,0},{module_info,1}]}.
+{exports, [{uninitialized,2},{undecided,2}]}.
{attributes, []}.
{labels, 15}.
-
-{function, handle_call, 2, 2}.
+{function, uninitialized, 2, 2}.
{label,1}.
- {func_info,{atom,t},{atom,handle_call},2}.
+ {func_info,{atom,t},{atom,uninitialized},2}.
{label,2}.
{test,is_atom,{f,1},[{x,0}]}.
{select_val,{x,0},{f,1},{list,[{atom,gurka},{f,3},{atom,delete},{f,4}]}}.
@@ -21,7 +20,7 @@
{move,{atom,nisse},{x,0}}.
{call_ext,1,{extfunc,erlang,exit,1}}.
{label,4}.
- {allocate_heap,1,6,2}.
+ {allocate_heap,2,6,2}.
{move,{x,1},{y,0}}.
{put_list,{integer,112},nil,{x,0}}.
{put_list,{integer,126},{x,0},{x,0}}.
@@ -51,37 +50,57 @@
{call_ext,1,{extfunc,erlang,exit,1}}.
{label,6}.
{move,{y,0},{x,0}}.
- {call_last,1,{f,8},1}.
+ {call_last,1,{f,14},1}.
-
-{function, foo, 1, 8}.
+{function, undecided, 2, 8}.
{label,7}.
- {func_info,{atom,t},{atom,foo},1}.
+ {func_info,{atom,t},{atom,undecided},2}.
{label,8}.
- {move,{atom,ok},{x,0}}.
- return.
-
-
-{function, bar, 2, 10}.
+ {test,is_atom,{f,7},[{x,0}]}.
+ {select_val,{x,0},{f,7},{list,[{atom,gurka},{f,9},{atom,delete},{f,10}]}}.
{label,9}.
- {func_info,{atom,t},{atom,bar},2}.
+ {allocate_heap,2,6,2}.
+ {test,is_eq_exact,{f,11},[{x,0},{atom,ok}]}.
+ %% This is unreachable since {x,0} is known not to be 'ok'. We should not
+ %% fail with "uninitialized y registers" on erlang:exit/1
+ {move,{atom,nisse},{x,0}}.
+ {call_ext,1,{extfunc,erlang,exit,1}}.
{label,10}.
- {move,{atom,ok},{x,0}}.
- return.
-
-
-{function, module_info, 0, 12}.
+ {allocate_heap,1,6,2}.
+ {move,{x,1},{y,0}}.
+ {put_list,{integer,112},nil,{x,0}}.
+ {put_list,{integer,126},{x,0},{x,0}}.
+ {put_list,{y,0},nil,{x,1}}.
+ {'%live',2}.
+ {call_ext,2,{extfunc,io,format,2}}.
+ {test,is_ne_exact,{f,12},[{x,0},{atom,ok}]}.
{label,11}.
- {func_info,{atom,t},{atom,module_info},0}.
+ %% The number of allocated Y registers are in conflict here.
+ {move,{atom,logReader},{x,1}}.
+ {move,{atom,console},{x,0}}.
+ {call_ext,2,{extfunc,debug,filter,2}}.
+ {test_heap,14,1}.
+ {put_list,{atom,logReader},nil,{x,1}}.
+ {put_list,{atom,console},{x,1},{x,1}}.
+ {put_tuple,3,{x,2}}.
+ {put,{atom,debug}}.
+ {put,{atom,filter}}.
+ {put,{x,1}}.
+ {put_tuple,2,{x,1}}.
+ {put,{x,2}}.
+ {put,{x,0}}.
+ {put_tuple,2,{x,0}}.
+ {put,{atom,badmatch}}.
+ {put,{x,1}}.
+ {'%live',1}.
+ {call_ext,1,{extfunc,erlang,exit,1}}.
{label,12}.
- {move,{atom,t},{x,0}}.
- {call_ext_only,1,{extfunc,erlang,get_module_info,1}}.
-
+ {move,{y,0},{x,0}}.
+ {call_last,1,{f,8},1}.
-{function, module_info, 1, 14}.
+{function, foo, 1, 14}.
{label,13}.
- {func_info,{atom,t},{atom,module_info},1}.
+ {func_info,{atom,t},{atom,foo},1}.
{label,14}.
- {move,{x,0},{x,1}}.
- {move,{atom,t},{x,0}}.
- {call_ext_only,2,{extfunc,erlang,get_module_info,2}}.
+ {move,{atom,ok},{x,0}}.
+ return.
diff --git a/lib/compiler/test/bif_SUITE.erl b/lib/compiler/test/bif_SUITE.erl
index 9f0a6dbb08..354a2a5ec7 100644
--- a/lib/compiler/test/bif_SUITE.erl
+++ b/lib/compiler/test/bif_SUITE.erl
@@ -119,5 +119,6 @@ cover_safe_and_pure_bifs(Config) ->
_ = registered(),
_ = term_to_binary(Config),
42 = list_to_integer("2A", 16),
+ a = binary_to_atom(atom_to_binary(a)),
ok.
diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl
index 0419b16eea..0d129a4a48 100644
--- a/lib/compiler/test/bs_bincomp_SUITE.erl
+++ b/lib/compiler/test/bs_bincomp_SUITE.erl
@@ -26,7 +26,8 @@
init_per_group/2,end_per_group/2,
byte_aligned/1,bit_aligned/1,extended_byte_aligned/1,
extended_bit_aligned/1,mixed/1,filters/1,trim_coverage/1,
- nomatch/1,sizes/1,general_expressions/1,matched_out_size/1]).
+ nomatch/1,sizes/1,general_expressions/1,matched_out_size/1,
+ no_generator/1]).
-include_lib("common_test/include/ct.hrl").
@@ -35,7 +36,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[byte_aligned, bit_aligned, extended_byte_aligned,
extended_bit_aligned, mixed, filters, trim_coverage,
- nomatch, sizes, general_expressions, matched_out_size].
+ nomatch, sizes, general_expressions, matched_out_size,
+ no_generator].
groups() ->
[].
@@ -99,26 +101,26 @@ extended_bit_aligned(Config) when is_list(Config) ->
mixed(Config) when is_list(Config) ->
cs_init(),
<<2,3,3,4,4,5,5,6>> =
- cs(<< <<(X+Y)>> || <<X>> <= <<1,2,3,4>>, <<Y>> <= <<1,2>> >>),
+ cs_default(<< <<(X+Y)>> || <<X>> <= <<1,2,3,4>>, <<Y>> <= <<1,2>> >>),
<<2,3,3,4,4,5,5,6>> =
- << <<(X+Y)>> || <<X>> <= <<1,2,3,4>>, Y <- [1,2] >>,
+ cs_default(<< <<(X+Y)>> || <<X>> <= <<1,2,3,4>>, Y <- [1,2] >>),
<<2,3,3,4,4,5,5,6>> =
- cs(<< <<(X+Y)>> || X <- [1,2,3,4], Y <- [1,2] >>),
+ cs_default(<< <<(X+Y)>> || X <- [1,2,3,4], Y <- [1,2] >>),
One = id([1,2,3,4]),
Two = id([1,2]),
<<2,3,3,4,4,5,5,6>> =
- cs(<< <<(X+Y)>> || X <- One, Y <- Two >>),
+ cs_default(<< <<(X+Y)>> || X <- One, Y <- Two >>),
[2,3,3,4,4,5,5,6] =
[(X+Y) || <<X>> <= <<1,2,3,4>>, <<Y>> <= <<1,2>>],
[2,3,3,4,4,5,5,6] =
[(X+Y) || <<X>> <= <<1,2,3,4>>, Y <- [1,2]],
<<2:3,3:3,3:3,4:3,4:3,5:3,5:3,6:3>> =
- cs(<< <<(X+Y):3>> || <<X:3>> <= <<1:3,2:3,3:3,4:3>>,
- <<Y:3>> <= <<1:3,2:3>> >>),
+ cs_default(<< <<(X+Y):3>> || <<X:3>> <= <<1:3,2:3,3:3,4:3>>,
+ <<Y:3>> <= <<1:3,2:3>> >>),
<<2:3,3:3,3:3,4:3,4:3,5:3,5:3,6:3>> =
- cs(<< <<(X+Y):3>> || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, Y <- [1,2] >>),
+ cs_default(<< <<(X+Y):3>> || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, Y <- [1,2] >>),
<<2:3,3:3,3:3,4:3,4:3,5:3,5:3,6:3>> =
- cs(<< <<(X+Y):3>> || X <- [1,2,3,4], Y <- [1,2] >>),
+ cs_default(<< <<(X+Y):3>> || X <- [1,2,3,4], Y <- [1,2] >>),
<<2:3,3:3,3:3,4:3,4:3,5:3,5:3,6:3>> =
cs_default(<< <<(X+Y):3>> || {X,Y} <- [{1,1},{1,2},{2,1},{2,2},
{3,1},{3,2},{4,1},{4,2}] >>),
@@ -126,8 +128,17 @@ mixed(Config) when is_list(Config) ->
[(X+Y) || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, <<Y:3>> <= <<1:3,2:3>>],
[2,3,3,4,4,5,5,6] =
[(X+Y) || <<X:3>> <= <<1:3,2:3,3:3,4:3>>, {_,Y} <- [{a,1},{b,2}]],
+
+ %% OTP-16899: Nested binary comprehensions would fail to load.
+ <<0,1,0,2,0,3,99>> = mixed_nested([1,2,3]),
+
+ <<1>> = cs_default(<< <<X>> || L <- [[1]], X <- L >>),
+
cs_end().
+mixed_nested(L) ->
+ << << << << E:16 >> || E <- L >> || true >>/binary, 99:(id(8))>>.
+
filters(Config) when is_list(Config) ->
cs_init(),
<<"BDF">> =
@@ -191,7 +202,18 @@ coverage_trimmer(Params) ->
coverage_summer(A, B, C, D) -> A+B+C+D.
nomatch(Config) when is_list(Config) ->
+ Bin = id(<<1,2,3,4,5>>),
<<>> = << <<X:8>> || X = {_,_} = [_|_] <- [1,2,3] >>,
+ [] = [X || <<X:all/binary>> <= Bin],
+ [] = [X || <<X:bad/binary>> <= Bin],
+ <<>> = << <<X:32>> || <<X:all/binary>> <= Bin >>,
+ <<>> = << <<X:32>> || <<X:bad/binary>> <= Bin >>,
+
+ <<>> = << <<"a">> || <<_:1/float>> <= Bin>>,
+
+ NaN = <<(-1):32>>,
+ <<>> = << <<"a">> || <<_:32/float>> <= NaN >>,
+
ok.
sizes(Config) when is_list(Config) ->
@@ -345,6 +367,15 @@ matched_out_size(Config) when is_list(Config) ->
matched_out_size_1(Binary) ->
<< <<X>> || <<S, X:S>> <= Binary>>.
+no_generator(Config) ->
+ [<<"abc">>] = [<<(id(<<"abc">>)) || true >>],
+ {<<>>} = {<<(id(<<"abc">>)) || false >>},
+
+ %% Would crash the compiler when compiled with +no_type_opt.
+ {'EXIT',{badarg,_}} = (catch << (catch "\001") || true >>),
+
+ ok.
+
cs_init() ->
erts_debug:set_internal_state(available_internal_state, true),
ok.
diff --git a/lib/compiler/test/bs_construct_SUITE.erl b/lib/compiler/test/bs_construct_SUITE.erl
index e494cfc33f..d0a1d07861 100644
--- a/lib/compiler/test/bs_construct_SUITE.erl
+++ b/lib/compiler/test/bs_construct_SUITE.erl
@@ -31,7 +31,7 @@
two/1,test1/1,fail/1,float_bin/1,in_guard/1,in_catch/1,
nasty_literals/1,coerce_to_float/1,side_effect/1,
opt/1,otp_7556/1,float_arith/1,otp_8054/1,
- cover/1]).
+ strings/1,bad_size/1]).
-include_lib("common_test/include/ct.hrl").
@@ -47,7 +47,7 @@ groups() ->
[verify_highest_opcode,
two,test1,fail,float_bin,in_guard,in_catch,
nasty_literals,side_effect,opt,otp_7556,float_arith,
- otp_8054,cover]}].
+ otp_8054,strings,bad_size]}].
init_per_suite(Config) ->
@@ -332,8 +332,32 @@ fail(Config) when is_list(Config) ->
%% Unaligned sizes with literal binaries.
{'EXIT',{badarg,_}} = (catch <<0,(<<7777:17>>)/binary>>),
+ %% Make sure that variables are bound even if binary
+ %% construction fails.
+ {'EXIT',{badarg,_}} = (catch case <<face:(V0 = 42)>> of
+ _Any -> V0
+ end),
+ {'EXIT',{badarg,_}} = (catch case <<face:(V1 = 3)>> of
+ a when V1 ->
+ office
+ end),
+ {'EXIT',{badarg,_}} = (catch <<13:(put(?FUNCTION_NAME, 17))>>),
+ 17 = erase(?FUNCTION_NAME),
+ {'EXIT',{badarg,_}} = (catch fail_1()),
+
+ %% Size exceeds length of binary. 'native' is redundant for
+ %% binaries, but when it was present sys_core_fold would not
+ %% detect the overlong binary and beam_ssa_opt would crash.
+ {'EXIT',{badarg,_}} = (catch << <<$t/little-signed>>:42/native-bytes >>),
+ {'EXIT',{badarg,_}} = (catch << <<$t/little-signed>>:42/bytes >>),
+
ok.
+fail_1() ->
+ case <<(V0 = 1),[]/utf32>> of
+ _ when V0 -> true
+ end.
+
float_bin(Config) when is_list(Config) ->
%% Some more coverage.
{<<1,2,3>>,7.0} = float_bin_1(4),
@@ -559,6 +583,7 @@ otp_7556(Bin, A, B, C) ->
float_arith(Config) when is_list(Config) ->
{<<1,2,3,64,69,0,0,0,0,0,0>>,21.0} = do_float_arith(<<1,2,3>>, 42, 2),
+
ok.
do_float_arith(Bin0, X, Y) ->
@@ -591,9 +616,83 @@ otp_8054_1([], Bin) -> Bin.
"Zuo1J1J6CCwEVZ/wDc79OpDPPj/qOGhDK73F8DaMcynZ91El+01vfTn"
"uUxNFUHLpuoQ==").
-cover(Config) ->
- %% Cover handling of a huge partially literal string.
+strings(Config) ->
L = length(Config),
+
+ <<$a:16,$b:16,$c:16,L:32>> = <<"abc":16,L:32>>,
+
+ %% Empty strings.
+ <<L:16>> = <<L:16,""/utf8>>,
+ <<L:16>> = <<L:16,"":(length(Config))>>,
+ <<L:16>> = <<L:16,"":(BindMe = 42)>>,
+ 42 = BindMe,
+ <<L:16>> = <<"":70,L:16>>,
+ <<L:16>> = <<L:16,"":$F>>,
+
+ %% Cover handling of a huge partially literal string.
Bin = id(<<L:32,?LONG_STRING>>),
<<L:32,?LONG_STRING>> = Bin,
+
+ %% Bad sizes for empty strings.
+ {'EXIT',{badarg,_}} = (catch <<"":(-42)>>),
+ {'EXIT',{badarg,_}} = (catch <<"":bad_size>>),
+ {'EXIT',{badarg,_}} = (catch bad_empty_string_1()),
+ {'EXIT',{badarg,_}} = (catch bad_empty_string_2()),
+ error = bad_empty_string_3(),
+ error = bad_empty_string_4(true),
+ error = bad_empty_string_4(false),
+
ok.
+
+bad_empty_string_1() ->
+ <<"":(V0 = true)>>,
+ V0.
+
+bad_empty_string_2() ->
+ <<"":(V0 = -1)>>,
+ V0.
+
+bad_empty_string_3() when <<a, "":96>> -> ok;
+bad_empty_string_3() -> error.
+
+bad_empty_string_4(V) when <<"","eFN"/utf8-native>>, V -> ok;
+bad_empty_string_4(_) -> error.
+
+bad_size(_Config) ->
+ {'EXIT',{badarg,_}} = (catch bad_float_size()),
+ {'EXIT',{badarg,_}} = (catch bad_float_size(<<"abc">>)),
+ {'EXIT',{badarg,_}} = (catch bad_integer_size()),
+ {'EXIT',{badarg,_}} = (catch bad_integer_size(<<"xyz">>)),
+ {'EXIT',{badarg,_}} = (catch bad_integer_size2()),
+ {'EXIT',{badarg,_}} = (catch bad_binary_size()),
+ {'EXIT',{badarg,_}} = (catch bad_binary_size(<<"xyz">>)),
+ {'EXIT',{badarg,_}} = (catch bad_binary_size2()),
+ ok.
+
+bad_float_size() ->
+ <<4.087073429964284:case 0 of 0 -> art end/float>>.
+
+bad_float_size(Bin) ->
+ <<Bin/binary,4.087073429964284:case 0 of 0 -> art end/float>>.
+
+bad_integer_size() ->
+ <<0:case 0 of 0 -> art end/integer>>.
+
+bad_integer_size(Bin) ->
+ <<Bin/binary,0:case 0 of 0 -> art end/integer>>.
+
+bad_integer_size2() ->
+ <<
+ <<(id(42))>>:[ <<>> || <<123:true>> <= <<>> ],
+ <<(id(100))>>:7>>.
+
+bad_binary_size() ->
+ <<<<"abc">>:case 0 of 0 -> art end/binary>>.
+
+bad_binary_size(Bin) ->
+ <<Bin/binary,<<"abc">>:case 0 of 0 -> art end/binary>>.
+
+bad_binary_size2() ->
+ <<
+ <<(id(42))>>:[ <<>> || <<123:true>> <= <<>> ]/binary,
+ <<(id(100))>>:7>>.
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index 3482615194..8748cc8f46 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -24,7 +24,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
init_per_testcase/2,end_per_testcase/2,
- verify_highest_opcode/1,
+ verify_highest_opcode/1, expand_and_squeeze/1,
size_shadow/1,int_float/1,otp_5269/1,null_fields/1,wiger/1,
bin_tail/1,save_restore/1,
partitioned_bs_match/1,function_clause/1,
@@ -45,8 +45,10 @@
expression_before_match/1,erl_689/1,restore_on_call/1,
restore_after_catch/1,matches_on_parameter/1,big_positions/1,
matching_meets_apply/1,bs_start_match2_defs/1,
- exceptions_after_match_failure/1, bad_phi_paths/1,
- combine_empty_segments/1]).
+ exceptions_after_match_failure/1,
+ bad_phi_paths/1,many_clauses/1,
+ combine_empty_segments/1,hangs_forever/1,
+ bs_saved_position_units/1,empty_get_binary/1]).
-export([coverage_id/1,coverage_external_ignore/2]).
@@ -62,10 +64,10 @@ all() ->
[{group,p}].
groups() ->
- [{p,[],
+ [{p,test_lib:parallel(),
[verify_highest_opcode,
size_shadow,int_float,otp_5269,null_fields,wiger,
- bin_tail,save_restore,
+ bin_tail,save_restore,expand_and_squeeze,
partitioned_bs_match,function_clause,unit,
shared_sub_bins,bin_and_float,dec_subidentifiers,
skip_optional_tag,decode_integer,wfbm,degenerated_match,bs_sum,
@@ -84,8 +86,8 @@ groups() ->
matches_on_parameter,big_positions,
matching_meets_apply,bs_start_match2_defs,
exceptions_after_match_failure,bad_phi_paths,
- combine_empty_segments]}].
-
+ many_clauses,combine_empty_segments,hangs_forever,
+ bs_saved_position_units,empty_get_binary]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -139,22 +141,59 @@ size_shadow(Config) when is_list(Config) ->
size_shadow_1() ->
L = 8,
- F = fun(<<L:L,B:L>>) -> B end,
- F(<<16:8, 7:16>>).
+ Fs = [fun(<<L:L,B:L>>) -> B end,
+ fun(A) ->
+ (fun([<<L:L,B:L>>]) -> B end)([A])
+ end,
+ fun(A) ->
+ (fun([<<L:L,B:L>>,<<L:L,B:L>>]) -> B end)([A,A])
+ end,
+ fun(A) ->
+ <<Size:L,_/bits>> = A,
+ Inner = fun([L], {#{key1 := <<L:L,B:L>>,
+ key2 := <<L:L,B:L>>}, L}) -> B end,
+ Inner([Size], {#{key1 => A,key2 => A},Size})
+ end],
+ size_shadow_apply(Fs, <<16:8, 7:16>>).
size_shadow_2(L) ->
- F = fun(<<L:L,B:L>>) -> B end,
- F(<<16:8, 7:16>>).
+ Fs = [fun(<<L:L,B:L>>) -> B end,
+ fun(A) ->
+ (fun([<<L:L,B:L>>]) -> B end)([A])
+ end,
+ fun(A) ->
+ (fun({<<L:L,B:L>>,<<L:L,B:L>>}) -> B end)({A,A})
+ end],
+ size_shadow_apply(Fs, <<16:8, 7:16>>).
size_shadow_3() ->
L = 8,
- F = fun(<<L:L,B:L,L:L>>) -> B end,
- F(<<16:8, 7:16,16:16>>).
+ Fs = [fun(<<L:L,B:L,L:L>>) -> B end,
+ fun(A) ->
+ (fun({tag,[<<L:L,B:L,L:L>>]}) -> B end)({tag,[A]})
+ end,
+ fun(A) ->
+ (fun({tag,<<L:L,B:L,L:L>>,<<L:L,B:L,L:L>>}) -> B end)({tag,A,A})
+ end],
+ size_shadow_apply(Fs, <<16:8, 7:16,16:16>>).
size_shadow_4(L) ->
- F = fun(<<L:L,B:L,L:L>>) -> B;
- (_) -> no end,
- F(<<16:8, 7:16,15:16>>).
+ Fs = [fun(<<L:L,B:L,L:L>>) -> B;
+ (_) -> no
+ end,
+ fun(A) ->
+ Inner = fun([<<L:L,B:L,L:L>>]) -> B;
+ (_) -> no
+ end,
+ Inner([A])
+ end,
+ fun(A) ->
+ Inner = fun({<<L:L,B:L,L:L>>,<<L:L,B:L,L:L>>}) -> B;
+ (_) -> no
+ end,
+ Inner({A,A})
+ end],
+ size_shadow_apply(Fs, <<16:8, 7:16,15:16>>).
size_shadow_5(X, Y) ->
fun (<< A:Y >>, Y, B) -> fum(A, X, Y, B) end.
@@ -168,6 +207,14 @@ fum(A, B, C, D) ->
size_shadow_7({int,N}, <<N:16,B:N/binary,T/binary>>) ->
{B,T}.
+size_shadow_apply([F|Fs], Arg) when is_function(F, 1) ->
+ size_shadow_apply(Fs, Arg, F(Arg)).
+
+size_shadow_apply([F|Fs], Arg, Res) when is_function(F, 1) ->
+ Res = F(Arg),
+ size_shadow_apply(Fs, Arg, Res);
+size_shadow_apply([], _, Res) ->
+ Res.
int_float(Config) when is_list(Config) ->
%% OTP-5323
@@ -504,6 +551,9 @@ unit(Config) when is_list(Config) ->
{'EXIT',_} = (catch unit_opt_2(<<1:32,33:7>>)),
{'EXIT',_} = (catch unit_opt_2(<<2:32,55:7>>)),
+ <<0:64>> = unit_opt_3(<<1:128>>),
+ <<1:64>> = unit_opt_3(<<1:64>>),
+
ok.
peek1(<<B:8,_/bitstring>>) -> B.
@@ -535,6 +585,13 @@ unit_opt_2(<<St:32,KO/binary>> = Bin0) ->
end,
id(Bin).
+unit_opt_3(A) when is_binary(A) ->
+ %% There should be no test_unit instruction after the first segment, since
+ %% we already know A is a binary and its tail will still be a binary after
+ %% matching 8 bytes from it.
+ <<Bin:8/binary, _/binary>> = A,
+ Bin.
+
shared_sub_bins(Config) when is_list(Config) ->
{15,[<<>>,<<5>>,<<4,5>>,<<3,4,5>>,<<2,3,4,5>>]} = sum(<<1,2,3,4,5>>, [], 0),
ok.
@@ -776,6 +833,8 @@ coverage(Config) when is_list(Config) ->
{10,<<"-">>,""} = coverage_trim_2(<<"-">>, 10, []),
{8,<<"-">>,"aa"} = coverage_trim_2(<<"aa-">>, 10, []),
+ {<<"abc">>,<<"tag">>} = coverage_trim_3([<<"abc","tag">>], 3),
+
ok.
coverage_fold(Fun, Acc, <<H,T/binary>>) ->
@@ -898,6 +957,12 @@ coverage_trim_2(<<C/utf8,R/binary>> = Bin, I, L) ->
{I,Bin,lists:reverse(L)}
end.
+coverage_trim_3(CipherTextFragment, TagLen) ->
+ CipherLen = iolist_size(CipherTextFragment) - TagLen,
+ <<CipherText:CipherLen/bytes, CipherTag:TagLen/bytes>> =
+ iolist_to_binary(CipherTextFragment),
+ {CipherText, CipherTag}.
+
printable_char($a) -> true;
printable_char(_) -> false.
@@ -1252,16 +1317,113 @@ zero_width(Config) when is_list(Config) ->
<<256:8>> -> ct:fail(should_not_match);
_ -> ok
end,
+
+ %% Would crash in the segment squeezing functions in v3_kernel.
+ F = fun (<<42>>) -> star;
+ (<<V:0>>) -> V;
+ (_) -> no_match
+ end,
+ star = F(<<42>>),
+ 0 = F(<<>>),
+ no_match = F(<<1>>),
+ no_match = F(whatever),
+
ok.
%% OTP_7650: A invalid size for binary segments could crash the compiler.
bad_size(Config) when is_list(Config) ->
Tuple = {a,b,c},
- {'EXIT',{{badmatch,<<>>},_}} = (catch <<32:Tuple>> = id(<<>>)),
Binary = <<1,2,3>>,
+ Atom = an_atom,
+ NaN = <<(-1):32>>,
+
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<32:Tuple>> = id(<<>>)),
{'EXIT',{{badmatch,<<>>},_}} = (catch <<32:Binary>> = id(<<>>)),
- ok.
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<32:Atom>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<32:3.14>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<32:"ZJV">> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<32:(-1)>> = id(<<>>)),
+
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<42.0:Tuple/float>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<42.0:Binary/float>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<42.0:Atom/float>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<42.0:2.5/float>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<42.0:1/float>> = id(<<>>)),
+ {'EXIT',{{badmatch,NaN},_}} = (catch <<42.0:32/float>> = id(NaN)),
+
+ %% Matched out value is ignored.
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<_:Binary>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<_:Tuple>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<_:Atom>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<>>},_}} = (catch <<_:2.5>> = id(<<>>)),
+ {'EXIT',{{badmatch,<<1:1>>},_}} = (catch <<_:1/float>> = id(<<1:1>>)),
+ {'EXIT',{{badmatch,NaN},_}} = (catch <<_:32/float>> = id(NaN)),
+
+ no_match = bad_all_size(<<>>),
+ no_match = bad_all_size(<<1,2,3>>),
+
+ true = bad_size_1(<<0>>),
+ error = bad_size_1(<<0,1>>),
+
+ ok.
+
+bad_all_size(Bin) ->
+ Res = bad_all_size_1(Bin),
+ Res = bad_all_size_2(Bin),
+ Res = bad_all_size_3(Bin),
+ Res = bad_all_size_4(Bin),
+ Res = bad_all_size_5(Bin),
+ Res = bad_all_size_6(Bin),
+ Res.
+
+bad_all_size_1(Bin) ->
+ case Bin of
+ <<B:all/binary>> -> B;
+ _ -> no_match
+ end.
+
+bad_all_size_2(Bin) ->
+ case Bin of
+ <<_:all/binary>> -> ok;
+ _ -> no_match
+ end.
+
+bad_all_size_3(Bin) ->
+ All = all,
+ case Bin of
+ <<B:All/binary>> -> B;
+ _ -> no_match
+ end.
+
+bad_all_size_4(Bin) ->
+ All = all,
+ case Bin of
+ <<_:All/binary>> -> ok;
+ _ -> no_match
+ end.
+
+bad_all_size_5(Bin) ->
+ All = case 0 of
+ 0 -> all
+ end,
+ case Bin of
+ <<B:All/binary>> -> B;
+ _ -> no_match
+ end.
+
+bad_all_size_6(Bin) ->
+ All = case 0 of
+ 0 -> all
+ end,
+ case Bin of
+ <<_:All/binary>> -> ok;
+ _ -> no_match
+ end.
+
+bad_size_1(<<0>>) -> true;
+bad_size_1(<<0:[]>>) -> false;
+bad_size_1(_) -> error.
haystack(Config) when is_list(Config) ->
<<0:10/unit:8>> = haystack_1(<<0:10/unit:8>>),
@@ -1326,23 +1488,182 @@ matched_out_size(Config) when is_list(Config) ->
{<<1,2,3,7>>,19,42} = mos_bin(<<4,1,2,3,7,19,4,42>>),
<<1,2,3,7>> = mos_bin(<<4,1,2,3,7,"abcdefghij">>),
- ok.
+ false = mos_verify_sig(not_a_binary),
+ false = mos_verify_sig(<<>>),
+ false = mos_verify_sig(<<42:32>>),
+ <<"123456789">> = mos_verify_sig(<<77:32,0:77/unit:8,9:32,"123456789">>),
+
+ ok.
+
+mos_int(B) ->
+ Res = mos_int_plain(B),
+ Res = mos_int_list([B]),
+ Res = mos_int_tuple({a,[B],z}),
+
+ Res = mos_int_mixed([B]),
+ Res = mos_int_mixed({a,[B],z}),
+ 42 = mos_int_mixed({30,12}),
+ no_match = mos_int_mixed([B,B,B]),
+
+ Res = mos_int_pats1({tag,[B]}, {0,1,2,3,4,5,6,7,8,9}),
+ Res = mos_int_pats2({tag,[B]}, {a,a,a,a,a,a,a,a,a,a}, [z]),
+ {I,X} = Res,
+ Res = mos_int_pats3({tag,[B]}, [I,{X,B,X},I]),
+ Res = mos_int_map(#{key => [B]}),
+ Key = {my,key},
+ Res = mos_int_map(Key, #{Key => [B]}),
+ {I,X,B} = mos_int_alias([[B]]),
+ Res = {I,X},
+ Res = mos_int_try([B]),
+ Res = mos_int_receive(B),
+ Res = mos_int_fun([B]),
+ Res = mos_int_exported(B),
+ Res = mos_int_utf(B),
+ Res.
+
+mos_int_plain(<<L,I:L,X:32>>) ->
+ {I,X};
+mos_int_plain(<<L,I:L,X:64>>) ->
+ {I,X}.
+
+mos_int_list([<<L,I:L,X:32>>]) ->
+ {I,X};
+mos_int_list([<<L,I:L,X:64>>]) ->
+ {I,X}.
+
+mos_int_tuple({a,[<<L,I:L,X:32>>],z}) ->
+ {I,X};
+mos_int_tuple({a,[<<L,I:L,X:64>>],z}) ->
+ {I,X}.
+
+mos_int_mixed({a,[<<L,I:L,X:32>>],z}) ->
+ {I,X};
+mos_int_mixed({a,[<<L,I:L,X:64>>],z}) ->
+ {I,X};
+mos_int_mixed([<<L,I:L,X:32>>]) ->
+ {I,X};
+mos_int_mixed([<<L,I:L,X:64>>]) ->
+ {I,X};
+mos_int_mixed({A,B}) when is_integer(A), is_integer(B) ->
+ A + B;
+mos_int_mixed(_) ->
+ no_match.
+
+mos_int_pats1({tag,[<<L,I:L,X:32>>]}, {_,_,_,_,_,_,_,_,_,_}) ->
+ {I,X};
+mos_int_pats1({tag,[<<L,I:L,X:64>>]}, {_,_,_,_,_,_,_,_,_,_}) ->
+ {I,X}.
+
+mos_int_pats2({tag,[<<L,I:L,X:32>>]}, {S,S,S,S,S,S,S,S,S,S}, [_|_]) ->
+ {I,X};
+mos_int_pats2({tag,[<<L,I:L,X:64>>]}, {S,S,S,S,S,S,S,S,S,S}, [_|_]) ->
+ {I,X}.
+
+mos_int_pats3({tag,[<<L,I:L,X:32>>]}, [I,{X,<<L,I:L,X:32>>,X},I]) ->
+ {I,X};
+mos_int_pats3({tag,[<<L,I:L,X:64>>]}, [I,{X,<<L,I:L,X:64>>,X},I]) ->
+ {I,X}.
-mos_int(<<L,I:L,X:32>>) ->
+mos_int_map(#{key := [<<L,I:L,X:32>>]}) ->
{I,X};
-mos_int(<<L,I:L,X:64>>) ->
+mos_int_map(#{key := [<<L,I:L,X:64>>]}) ->
+ {I,X}.
+
+mos_int_map(Key, Map) ->
+ case Map of
+ #{Key := [<<L,I:L,X:32>>]} -> {I,X};
+ #{Key := [<<L,I:L,X:64>>]} -> {I,X}
+ end.
+
+mos_int_alias([[<<L,I:L,X:32>> = B]]) ->
+ {I,X,B};
+mos_int_alias([[<<L,I:L,X:64>> = B]]) ->
+ {I,X,B}.
+
+mos_int_try(B) ->
+ try id(B) of
+ [<<L,I:L,X:32>>] -> {I,X};
+ [<<L,I:L,X:64>>] -> {I,X}
+ after
+ ok
+ end.
+
+mos_int_receive(Msg) ->
+ Res = (fun() ->
+ self() ! Msg,
+ receive
+ <<L,I:L,X:32>> -> {I,X};
+ <<L,I:L,X:64>> -> {I,X}
+ end
+ end)(),
+ self() ! Msg,
+ Res = receive
+ <<L,I:L,X:32>> -> {I,X};
+ <<L,I:L,X:64>> -> {I,X}
+ end,
+ self() ! {tag,[Msg]},
+ Res = receive
+ {tag,[<<L,I:L,X:32>>]} -> {I,X};
+ {tag,[<<L,I:L,X:64>>]} -> {I,X}
+ end,
+ Res.
+
+mos_int_fun(B) ->
+ L = ignore_me,
+ F = fun ([<<L,I:L,X:32>>]) -> {I,X};
+ ([<<L,I:L,X:64>>]) -> {I,X}
+ end,
+ F(B).
+
+mos_int_exported(B) ->
+ case B of
+ <<L,I:L,X:32>> -> ok;
+ <<L,I:L,X:64>> -> ok
+ end,
{I,X}.
-mos_bin(<<L,Bin:L/binary,X:8,L>>) ->
+mos_int_utf(B0) ->
+ B = id(<<B0/bits,777/utf8,7777/utf16,9999/utf32>>),
+ case B of
+ <<L,I:L,X:32,777/utf8,7777/utf16,9999/utf32>> -> {I,X};
+ <<L,I:L,X:64,777/utf8,7777/utf16,9999/utf32>> -> {I,X}
+ end.
+
+mos_bin(B) ->
+ Res = mos_bin_plain(B),
+ Res = mos_bin_tuple({outer,{inner,B}}),
+ Res.
+
+mos_bin_plain(<<L,Bin:L/binary,X:8,L>>) ->
+ L = byte_size(Bin),
+ {Bin,X};
+mos_bin_plain(<<L,Bin:L/binary,X:8,L,Y:8>>) ->
+ L = byte_size(Bin),
+ {Bin,X,Y};
+mos_bin_plain(<<L,Bin:L/binary,"abcdefghij">>) ->
+ L = byte_size(Bin),
+ Bin.
+
+mos_bin_tuple({outer,{inner,<<L,Bin:L/binary,X:8,L>>}}) ->
L = byte_size(Bin),
{Bin,X};
-mos_bin(<<L,Bin:L/binary,X:8,L,Y:8>>) ->
+mos_bin_tuple({outer,{inner,<<L,Bin:L/binary,X:8,L,Y:8>>}}) ->
L = byte_size(Bin),
{Bin,X,Y};
-mos_bin(<<L,Bin:L/binary,"abcdefghij">>) ->
+mos_bin_tuple({outer,{inner,<<L,Bin:L/binary,"abcdefghij">>}}) ->
L = byte_size(Bin),
Bin.
+mos_verify_sig(AlgSig) ->
+ try
+ <<AlgLen:32, _Alg:AlgLen/binary,
+ SigLen:32, Sig:SigLen/binary>> = AlgSig,
+ Sig
+ catch
+ _:_ ->
+ false
+ end.
+
follow_fail_branch(_) ->
42 = ffb_1(<<0,1>>, <<0>>),
8 = ffb_1(<<0,1>>, [a]),
@@ -1528,6 +1849,7 @@ bad_literals(_Config) ->
Mod:f(),
{'EXIT',<<42>>} = (catch bad_literals_1()),
+ no_match = bad_literals_2(<<"abc">>),
Sz = id(8),
{'EXIT',{{badmatch,_},_}} = (catch <<-1:Sz>> = <<-1>>),
@@ -1543,6 +1865,13 @@ bad_literals_1() ->
error -> error
end.
+bad_literals_2(<<atom:16>>) ->
+ fail;
+bad_literals_2(<<2.5:16>>) ->
+ fail;
+bad_literals_2(_) ->
+ no_match.
+
signed_lit_match(V, Sz) ->
case <<V:Sz>> of
<<V:Sz/signed>> ->
@@ -2045,4 +2374,233 @@ combine_empty_segments_1(A) ->
<<D/bits>> = C,
D.
+%% This never finishes compiling under +no_copt.
+hangs_forever(Config) ->
+ true = is_function(id(fun() -> hangs_forever_1(Config) end)),
+ ok.
+
+hangs_forever_1(V0) ->
+ case hangs_forever_1(V0) of
+ <<A:1>> -> A
+ end.
+
+
+%% ERL-1340: the unit of previously saved match positions wasn't updated.
+bs_saved_position_units(Config) when is_list(Config) ->
+ [<<0,1,2,3,4>>, <<5,6,7,8,9>>] = bspu_1(id(<<0,1,2,3,4,5,6,7,8,9>>)),
+ [] = bspu_1(id(<<>>)),
+
+ ok.
+
+bspu_1(<<Bin/binary>> = Bin) ->
+ [Chunk || <<Chunk:5/binary>> <= Bin].
+
+
+empty_get_binary(Config) when is_list(Config) ->
+ {<<>>, <<1,2,3,4:4>>} = egb_1(<<1,2,3,4:4>>),
+ {<<>>, <<1,2,3>>} = egb_1(<<1,2,3>>),
+ {<<>>, <<>>} = egb_1(<<>>),
+
+ <<0,1,0,2,0,3>> = egb_2(id(<<1,2,3>>)),
+ <<>> = egb_2(id(<<>>)),
+
+ ok.
+
+egb_1(Bytes) ->
+ {Term, Bytes} = begin
+ <<V2@V0:0/binary-unit:8,V2@Buf1/bitstring>> = Bytes,
+ V2@Conv2 = binary:copy(V2@V0),
+ {V2@Conv2, V2@Buf1}
+ end,
+ {Term, Bytes}.
+
+egb_2(Bin) ->
+ <<
+ <<K,N>> || <<K:0,N>> <= Bin
+ >>.
+
id(I) -> I.
+
+expand_and_squeeze(Config) when is_list(Config) ->
+ %% UTF8 literals are expanded and then squeezed into integer16
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,16}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<$á/utf8,_/binary>>"),
+ ?Q("<<$é/utf8,_/binary>>")
+ ]),
+
+ %% Sized integers are expanded and then squeezed into integer16
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,16}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<0:32,_/binary>>"),
+ ?Q("<<\"bbbb\",_/binary>>")
+ ]),
+
+ %% Groups of 8 bits are squeezed into integer16
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,16}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<\"aaaa\",_/binary>>"),
+ ?Q("<<\"bbbb\",_/binary>>")
+ ]),
+
+ %% Groups of 8 bits with empty binary are also squeezed
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,16}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<\"aaaa\",_/binary>>"),
+ ?Q("<<\"bbbb\",_/binary>>"),
+ ?Q("<<>>")
+ ]),
+
+ %% Groups of 8 bits with float lookup are not squeezed
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,8}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<\"aaaa\",_/binary>>"),
+ ?Q("<<\"bbbb\",_/binary>>"),
+ ?Q("<<_/float>>")
+ ]),
+
+ %% Groups of diverse bits go with minimum possible
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,8}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<\"aa\",_/binary>>"),
+ ?Q("<<\"bb\",_/binary>>"),
+ ?Q("<<\"c\",_/binary>>")
+ ]),
+
+ %% Groups of diverse bits go with minimum possible but are recursive...
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,8}|_],_}
+ | RestDiverse
+ ] = binary_match_to_asm([
+ ?Q("<<\"aaa\",_/binary>>"),
+ ?Q("<<\"abb\",_/binary>>"),
+ ?Q("<<\"c\",_/binary>>")
+ ]),
+
+ %% so we still perform a 16 bits lookup for the remaining
+ true = lists:any(fun({test,bs_get_integer2,_,_,[_,{integer,16}|_],_}) -> true;
+ (_) -> false end, RestDiverse),
+
+ %% Large match is kept as is if there is a sized match later
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,64}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<255,255,255,255,255,255,255,255>>"),
+ ?Q("<<_:64>>")
+ ]),
+
+ %% Large match is kept as is with large matches before and after
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,32}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<A:32,_:A>>"),
+ ?Q("<<0:32>>"),
+ ?Q("<<_:32>>")
+ ]),
+
+ %% Large match is kept as is with large matches before and after
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,32}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<A:32,_:A>>"),
+ ?Q("<<0,0,0,0>>"),
+ ?Q("<<_:32>>")
+ ]),
+
+ %% Large match is kept as is with smaller but still large matches before and after
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,32}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<A:32, _:A>>"),
+ ?Q("<<0:64>>"),
+ ?Q("<<_:32>>")
+ ]),
+
+ %% There is no squeezing for groups with more than 16 matches
+ [
+ {test,bs_get_integer2,_,_,[_,{integer,8}|_],_}
+ | _
+ ] = binary_match_to_asm([
+ ?Q("<<\"aa\", _/binary>>"),
+ ?Q("<<\"bb\", _/binary>>"),
+ ?Q("<<\"cc\", _/binary>>"),
+ ?Q("<<\"dd\", _/binary>>"),
+ ?Q("<<\"ee\", _/binary>>"),
+ ?Q("<<\"ff\", _/binary>>"),
+ ?Q("<<\"gg\", _/binary>>"),
+ ?Q("<<\"hh\", _/binary>>"),
+ ?Q("<<\"ii\", _/binary>>"),
+ ?Q("<<\"jj\", _/binary>>"),
+ ?Q("<<\"kk\", _/binary>>"),
+ ?Q("<<\"ll\", _/binary>>"),
+ ?Q("<<\"mm\", _/binary>>"),
+ ?Q("<<\"nn\", _/binary>>"),
+ ?Q("<<\"oo\", _/binary>>"),
+ ?Q("<<\"pp\", _/binary>>")
+ ]),
+
+ ok.
+
+binary_match_to_asm(Matches) ->
+ Clauses = [
+ begin
+ Ann = element(2, Match),
+ {clause,Ann,[Match],[],[{integer,Ann,Return}]}
+ end || {Match,Return} <- lists:zip(Matches, lists:seq(1, length(Matches)))
+ ],
+
+ Module = [
+ {attribute,erl_anno:new(1),module,match_to_asm},
+ {attribute,erl_anno:new(2),export,[{example,1}]},
+ {function,erl_anno:new(3),example,1,Clauses}
+ ],
+
+ {ok,match_to_asm,{match_to_asm,_Exports,_Attrs,Funs,_},_} =
+ compile:forms(Module, [return, to_asm]),
+
+ [{function,example,1,2,AllInstructions}|_] = Funs,
+ [{label,_},{line,_},{func_info,_,_,_},{label,_},{'%',_},
+ {test,bs_start_match3,_,_,_,_},{bs_get_position,_,_,_}|Instructions] = AllInstructions,
+ Instructions.
+
+many_clauses(_Config) ->
+ Mod = list_to_atom(?MODULE_STRING ++ "_" ++
+ atom_to_list(?FUNCTION_NAME)),
+ Seq = lists:seq(1, 200),
+ S = [one_clause(I) || I <- Seq],
+ Code = ?Q(["-module('@Mod@').\n"
+ "-export([f/1]).\n"
+ "f(Bin) ->\n"
+ "case Bin of\n"
+ " dummy -> _@_@S\n"
+ "end.\n"]),
+ %% merl:print(Code),
+ Opts = test_lib:opt_opts(?MODULE),
+ {ok,_} = merl:compile_and_load(Code, Opts),
+ _ = [begin
+ H = erlang:phash2(I),
+ Sz = 16,
+ <<Res0:Sz>> = <<H:Sz>>,
+ Res = I + Res0,
+ Res = Mod:f({I,<<Sz:8,H:Sz>>})
+ end || I <- Seq],
+ ok.
+
+one_clause(I) ->
+ ?Q(<<"{_@I@,<<L:8,Val:L>>} -> _@I@ + Val">>).
diff --git a/lib/compiler/test/bs_size_expr_SUITE.erl b/lib/compiler/test/bs_size_expr_SUITE.erl
new file mode 100644
index 0000000000..8df57529ef
--- /dev/null
+++ b/lib/compiler/test/bs_size_expr_SUITE.erl
@@ -0,0 +1,296 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(bs_size_expr_SUITE).
+-compile(nowarn_shadow_vars).
+
+-export([all/0,suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ init_per_testcase/2,end_per_testcase/2,
+ basic/1,size_shadow/1,complex/1,
+ recv/1,no_match/1]).
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}].
+
+all() ->
+ [{group,p}].
+
+groups() ->
+ [{p,test_lib:parallel(),
+ [basic,
+ size_shadow,
+ complex,
+ recv,
+ no_match]}].
+
+init_per_suite(Config) ->
+ test_lib:recompile(?MODULE),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
+ Config.
+
+end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
+ ok.
+
+basic(_Config) ->
+ <<>> = do_basic(<<1:32>>),
+ <<"abcd">> = do_basic(<<2:32,"abcd">>),
+ no_match = do_basic(<<0:32>>),
+ no_match = do_basic(<<777:32>>),
+ ok.
+
+do_basic(Bin) ->
+ Res = do_basic_1(Bin),
+
+ Res = do_basic_2({tag,Bin}),
+ Res = do_basic_2([list,Bin]),
+ 6 = do_basic_2({2,4}),
+
+ Res = do_basic_3(Bin),
+ Res = do_basic_4(Bin),
+
+ {result,Res} = do_basic_5(Bin),
+ case Res of
+ no_match ->
+ ok;
+ _ ->
+ {result,{Res,7777777}} = do_basic_5(<<Bin/binary,7777777:32>>)
+ end,
+
+ Res.
+
+do_basic_1(<<Sz:32,Tail:(4*Sz-4)/binary>>) ->
+ Tail;
+do_basic_1(<<_/binary>>) ->
+ no_match.
+
+do_basic_2({tag,<<Sz:32,Tail:(4*Sz-4)/binary>>}) ->
+ Tail;
+do_basic_2([list,<<Sz:32,Tail:((Sz-1)*4)/binary>>]) ->
+ Tail;
+do_basic_2({A,B}) when is_integer(A), is_integer(B) ->
+ A + B;
+do_basic_2(_) ->
+ no_match.
+
+do_basic_3(Bin) ->
+ WordSize = id(4),
+ case Bin of
+ <<Sz:32,Tail:(WordSize*Sz-WordSize)/binary>> ->
+ Tail;
+ _ ->
+ no_match
+ end.
+
+do_basic_4(Bin) ->
+ WordSize = id(4),
+ F = fun() ->
+ case Bin of
+ <<Sz:32,Tail:(WordSize*Sz-WordSize)/binary>> ->
+ Tail;
+ _ ->
+ no_match
+ end
+ end,
+ F().
+
+do_basic_5(Bin) ->
+ WordSize = id(4),
+ F = fun() ->
+ Res = case Bin of
+ <<Sz:32,Tail:(WordSize*Sz-WordSize)/binary,More:(8*WordSize)>> ->
+ {Tail,More};
+ <<Sz:32,Tail:(WordSize*Sz-WordSize)/binary>> ->
+ Tail;
+ _ ->
+ no_match
+ end,
+ {result,Res}
+ end,
+ F().
+
+size_shadow(_Config) ->
+ 12345678 = size_shadow_1(),
+ ok.
+
+size_shadow_1() ->
+ L = 8,
+ Offset = 16,
+ Fs = [fun(<<L:L,B:(L+16)>>) -> B end,
+ fun(<<L:L,B:(L+Offset)>>) -> B end,
+ fun(A) ->
+ Res = (fun([<<L:L,B:(L+16)>>]) -> B end)([A]),
+ Res = (fun([<<L:L,B:(L+Offset)>>]) -> B end)([A])
+ end,
+ fun(A) ->
+ Res = (fun({<<L:L,B:(L+16)>>,<<L:L,B:(L+16)>>}) -> B end)({A,A}),
+ Res = (fun({<<L:L,B:(L+Offset)>>,<<L:L,B:(L+16)>>}) -> B end)({A,A}),
+ Res = (fun({<<L:L,B:(L+16)>>,<<L:L,B:(L+Offset)>>}) -> B end)({A,A}),
+ Res = (fun({<<L:L,B:(L+Offset)>>,<<L:L,B:(L+Offset)>>}) -> B end)({A,A})
+ end,
+ fun(A) ->
+ <<Size:L,_/bits>> = A,
+ Inner = fun([L], {#{key1 := <<L:L,B:(L+Offset)>>,
+ key2 := <<L:L,B:(L+Offset)>>}, L}) -> B end,
+ Inner([Size], {#{key1 => A,key2 => A},Size})
+ end],
+ size_shadow_apply(Fs, <<16:8, 12345678:32>>).
+
+size_shadow_apply([F|Fs], Arg) when is_function(F, 1) ->
+ size_shadow_apply(Fs, Arg, F(Arg)).
+
+size_shadow_apply([F|Fs], Arg, Res) when is_function(F, 1) ->
+ Res = F(Arg),
+ size_shadow_apply(Fs, Arg, Res);
+size_shadow_apply([], _, Res) ->
+ Res.
+
+-record(r, {a,b,c}).
+complex(Config) ->
+ (fun() ->
+ Len = length(id(Config)),
+ Bin = << <<I:13>> || I <- lists:seq(1, Len) >>,
+ <<Bin:(length(Config))/binary-unit:13>> = Bin
+ end)(),
+
+ (fun() ->
+ V = id([a,b,c]),
+ F = fun(<<V:(bit_size(<<0:(length(V))>>)*8)/signed-integer>>) ->
+ V;
+ ({A,B}) ->
+ A + B
+ end,
+ -1 = F(<<-1:(length(V)*8)>>),
+ 7 = F({3,4})
+ end)(),
+
+ (fun() ->
+ A = a,
+ B = b,
+ F = fun(<<A:16,B:16,C:(A+B),D/bits>>) ->
+ {A,B,C,D};
+ (<<A:16,B:16>>) ->
+ {A,B};
+ (<<A:8,B:8>>) ->
+ {A,B}
+ end,
+ {13,21,16#cafebeef,<<"more">>} = F(<<13:16,21:16,16#cafebeef:34,"more">>),
+ {100,500} = F(<<100:16,500:16>>),
+ {157,77} = F(<<157:8,77:8>>),
+ {A,B}
+ end)(),
+
+ (fun() ->
+ Two = id(2),
+ F = fun(a, <<_:(#r.a - Two)/binary,Int:8,_/binary>>) -> Int;
+ (b, <<_:(#r.b - Two)/binary,Int:8,_/binary>>) -> Int;
+ (c, <<_:(#r.c - Two)/binary,Int:8,_/binary>>) -> Int
+ end,
+ 1 = F(a, <<1,2,3>>),
+ 2 = F(b, <<1,2,3>>),
+ 3 = F(c, <<1,2,3>>)
+ end)(),
+
+ (fun() ->
+ Bin = <<1,2,3,4>>,
+ F = fun(R) ->
+ <<First:(R#r.a)/binary,Tail/binary>> = Bin,
+ {First,Tail}
+ end,
+ {<<>>,<<1,2,3,4>>} = F(#r{a=0}),
+ {<<1>>,<<2,3,4>>} = F(#r{a=1}),
+ {<<1,2>>,<<3,4>>} = F(#r{a=2}),
+ {<<1,2,3>>,<<4>>} = F(#r{a=3}),
+ {<<1,2,3,4>>,<<>>} = F(#r{a=4})
+ end)(),
+
+ [] = fun() ->
+ case day of
+ V0 -> ok
+ end,
+ %% A bug in v3_core prevented V0 as being seen as used,
+ %% and because of that, V0 would not be exported from the
+ %% case above.
+ [0 || <<42:(V0#{key => value})>> <- []]
+ end(),
+
+ ok.
+
+recv(_Config) ->
+ R = fun(Msg) ->
+ self() ! Msg,
+ Res = receive
+ <<L,I:(L-1)/unit:8,X:32>> -> {I,X};
+ <<L,I:(L-1)/unit:8,X:64>> -> {I,X}
+ end,
+ self() ! {tag,[Msg]},
+ Res = receive
+ {tag,[<<L,I:(8*(L-1)),X:32>>]} -> {I,X};
+ {tag,[<<L,I:(8*(L-1)),X:64>>]} -> {I,X}
+ end
+ end,
+ {1234,16#deadbeef} = R(<<3,1234:16,16#deadbeef:32>>),
+ {99,16#cafebeeff00d} = R(<<2,99:8,16#cafebeeff00d:64>>),
+ ok.
+
+no_match(_Config) ->
+ B = id(<<1,2,3,4>>),
+ no_match = case B of
+ <<Int:(bit_size(B)-1)>> -> Int;
+ <<Int:(bit_size(B)*2)>> -> Int;
+ <<Int:(length(B))>> -> Int;
+ _ -> no_match
+ end,
+ no_match = case B of
+ <<L:8,Int2:(is_integer(L))>> -> Int2;
+ <<L:8,Int2:(L+3.0)>> -> Int2;
+ _ -> no_match
+ end,
+
+ no_match = case B of
+ <<Int3:(1/0)>> -> Int3;
+ _ -> no_match
+ end,
+
+ no_match = case B of
+ <<Int4:all>> -> Int4;
+ <<Int4:bad_size>> -> Int4;
+ _ -> no_match
+ end,
+
+ [] = [X || <<X:(is_list(B))/binary>> <= B],
+ <<>> = << <<X:32>> || <<X:(is_list(B))/binary>> <= B >>,
+
+ ok.
+
+id(I) ->
+ I.
diff --git a/lib/compiler/test/bs_utf_SUITE.erl b/lib/compiler/test/bs_utf_SUITE.erl
index 8ea4a849ec..5706184df7 100644
--- a/lib/compiler/test/bs_utf_SUITE.erl
+++ b/lib/compiler/test/bs_utf_SUITE.erl
@@ -233,6 +233,14 @@ utf32_to_unicode(<<C/utf32,T/binary>>) ->
utf32_to_unicode(<<>>) -> [].
literals(Config) when is_list(Config) ->
+ <<>> = id(<<""/utf8>>),
+ <<>> = id(<<""/utf16>>),
+ <<>> = id(<<""/little-utf16>>),
+ <<>> = id(<<""/native-utf16>>),
+ <<>> = id(<<""/utf32>>),
+ <<>> = id(<<""/little-utf32>>),
+ <<>> = id(<<""/native-utf32>>),
+
abc_utf8 = match_literal(<<"abc"/utf8>>),
abc_utf8 = match_literal(<<$a,$b,$c>>),
abc_utf8 = match_literal(<<$a/utf8,$b/utf8,$c/utf8>>),
@@ -247,6 +255,10 @@ literals(Config) when is_list(Config) ->
abc_utf32le = match_literal(<<"abc"/little-utf32>>),
abc_utf32le = match_literal(<<$a:32/little,$b:32/little,$c:32/little>>),
+ mm_utf8 = match_literal(<<"Мастер и Маргарита"/utf8>>),
+ mm_utf16be = match_literal(<<"Мастер и Маргарита"/utf16>>),
+ mm_utf32be = match_literal(<<"Мастер и Маргарита"/utf32>>),
+
bjorn_utf8 = match_literal(<<"bj\366rn"/utf8>>),
bjorn_utf8 = match_literal(<<$b,$j,195,182,$r,$n>>),
@@ -294,6 +306,9 @@ match_literal(<<"abc"/big-utf16>>) -> abc_utf16be;
match_literal(<<"abc"/little-utf16>>) -> abc_utf16le;
match_literal(<<"abc"/big-utf32>>) -> abc_utf32be;
match_literal(<<"abc"/little-utf32>>) -> abc_utf32le;
+match_literal(<<"Мастер и Маргарита"/utf8>>) -> mm_utf8;
+match_literal(<<"Мастер и Маргарита"/utf16>>) -> mm_utf16be;
+match_literal(<<"Мастер и Маргарита"/big-utf32>>) -> mm_utf32be;
match_literal(<<"bj\366rn"/utf8>>) -> bjorn_utf8;
match_literal(<<"bj\366rn"/big-utf16>>) -> bjorn_utf16be;
match_literal(<<"bj\366rn"/little-utf16>>) -> bjorn_utf16le.
@@ -396,3 +411,5 @@ utf32_data() ->
fc({'EXIT',{function_clause,_}}) -> ok;
fc({'EXIT',{{case_clause,_},_}}) when ?MODULE =:= bs_utf_inline_SUITE -> ok.
+
+id(I) -> I.
diff --git a/lib/compiler/test/compilation_SUITE.erl b/lib/compiler/test/compilation_SUITE.erl
index 74f9dbd9b4..94c857471b 100644
--- a/lib/compiler/test/compilation_SUITE.erl
+++ b/lib/compiler/test/compilation_SUITE.erl
@@ -57,7 +57,8 @@
string_table/1,
vsn_1/1,
vsn_2/1,
- vsn_3/1]).
+ vsn_3/1,
+ infinite_loop/0,infinite_loop/1]).
-include_lib("common_test/include/ct.hrl").
@@ -84,7 +85,8 @@ groups() ->
opt_crash,otp_5404,otp_5436,otp_5481,
otp_5553,otp_5632,otp_5714,otp_5872,otp_6121,
otp_7202,on_load,on_load_inline,
- string_table,otp_8949_a,split_cases]}].
+ string_table,otp_8949_a,split_cases,
+ infinite_loop]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -136,6 +138,9 @@ end_per_group(_GroupName, Config) ->
?comp(on_load).
?comp(on_load_inline).
+infinite_loop() -> [{timetrap,{minutes,1}}].
+?comp(infinite_loop).
+
%% Code snippet submitted from Ulf Wiger which fails in R3 Beam.
beam_compiler_7(Config) when is_list(Config) ->
done = empty(2, false).
diff --git a/lib/compiler/test/compilation_SUITE_data/infinite_loop.erl b/lib/compiler/test/compilation_SUITE_data/infinite_loop.erl
new file mode 100644
index 0000000000..6d9135bb01
--- /dev/null
+++ b/lib/compiler/test/compilation_SUITE_data/infinite_loop.erl
@@ -0,0 +1,22 @@
+-module(infinite_loop).
+-compile([export_all]).
+
+?MODULE() ->
+ ok.
+
+%% When a series of calls initiated by a fun went into an infinite loop,
+%% the compiler went into an infinite loop too.
+foo() ->
+ fun bar/0().
+
+bar() ->
+ baz().
+
+baz() ->
+ bar().
+
+foobar() ->
+ fun barfoo/0().
+
+barfoo() ->
+ barfoo().
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index 78ed78ab25..36e5221da6 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -34,9 +34,10 @@
strict_record/1, utf8_atoms/1, utf8_functions/1, extra_chunks/1,
cover/1, env/1, core_pp/1, tuple_calls/1,
core_roundtrip/1, asm/1,
- sys_pre_attributes/1, dialyzer/1,
+ sys_pre_attributes/1, dialyzer/1, no_core_prepare/1,
warnings/1, pre_load_check/1, env_compiler_options/1,
- bc_options/1, deterministic_include/1, deterministic_paths/1
+ bc_options/1, deterministic_include/1, deterministic_paths/1,
+ compile_attribute/1
]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -50,10 +51,11 @@ all() ->
binary, makedep, cond_and_ifdef, listings, listings_big,
other_output, kernel_listing, encrypted_abstr, tuple_calls,
strict_record, utf8_atoms, utf8_functions, extra_chunks,
- cover, env, core_pp, core_roundtrip, asm,
+ cover, env, core_pp, core_roundtrip, asm, no_core_prepare,
sys_pre_attributes, dialyzer, warnings, pre_load_check,
env_compiler_options, custom_debug_info, bc_options,
- custom_compile_info, deterministic_include, deterministic_paths].
+ custom_compile_info, deterministic_include, deterministic_paths,
+ compile_attribute].
groups() ->
[].
@@ -331,6 +333,23 @@ makedep_modify_target(Mf, Target) ->
%% Tests that conditional compilation, defining values, including files work.
+no_core_prepare(_Config) ->
+ Mod = {c_module,[],
+ {c_literal,[],sample_receive},
+ [{c_var,[],{discard,0}}],
+ [],
+ [{{c_var,[],{discard,0}},
+ {c_fun,[],[],
+ {c_case,[],
+ {c_values,[],[]},
+ [{c_clause,[],[],
+ {c_literal,[],true},
+ {c_receive,[],[],{c_literal,[],0},{c_literal,[],ok}}}]}}}]},
+
+ {ok,sample_receive,_,_} = compile:forms(Mod, [from_core,binary,return]),
+ {error,_,_} = compile:forms(Mod, [from_core,binary,return,no_core_prepare]),
+ ok.
+
cond_and_ifdef(Config) when is_list(Config) ->
{Simple, Target} = get_files(Config, simple, "cond_and_ifdef"),
IncludeDir = filename:join(filename:dirname(Simple), "include"),
@@ -374,11 +393,12 @@ do_file_listings(DataDir, PrivDir, [File|Files]) ->
{dcbsm, ".core_bsm"},
{dkern, ".kernel"},
{dssa, ".ssa"},
+ {dbool, ".bool"},
+ {dssashare, ".ssashare"},
{dssaopt, ".ssaopt"},
{dprecg, ".precodegen"},
{dcg, ".codegen"},
{dblk, ".block"},
- {dexcept, ".except"},
{djmp, ".jump"},
{dclean, ".clean"},
{dpeep, ".peep"},
@@ -1383,36 +1403,48 @@ env_compiler_options(_Config) ->
bc_options(Config) ->
DataDir = proplists:get_value(data_dir, Config),
- L = [{101, small_float, [no_get_hd_tl,no_line_info]},
- {103, big, [no_put_tuple2,no_get_hd_tl,no_ssa_opt_record,
- no_line_info,no_stack_trimming]},
- {125, small_float, [no_get_hd_tl,no_line_info,no_ssa_opt_float]},
+ L = [{101, small_float, [no_shared_fun_wrappers,
+ no_get_hd_tl,no_line_info]},
+ {125, small_float, [no_shared_fun_wrappers,no_get_hd_tl,
+ no_line_info,
+ no_ssa_opt_float]},
- {132, small, [no_put_tuple2,no_get_hd_tl,no_ssa_opt_record,
+ {132, small, [no_shared_fun_wrappers,
+ no_put_tuple2,no_get_hd_tl,no_ssa_opt_record,
no_ssa_opt_float,no_line_info,no_bsm3]},
{153, small, [r20]},
{153, small, [r21]},
- {136, big, [no_put_tuple2,no_get_hd_tl,
- no_ssa_opt_record,no_line_info]},
-
- {153, big, [no_put_tuple2,no_get_hd_tl, no_ssa_opt_record]},
- {153, big, [r16]},
- {153, big, [r17]},
{153, big, [r18]},
{153, big, [r19]},
- {153, small_float, [r16]},
- {153, small_float, []},
+ {153, small_float, [no_shared_fun_wrappers]},
- {158, small_maps, [r17]},
{158, small_maps, [r18]},
{158, small_maps, [r19]},
{158, small_maps, [r20]},
{158, small_maps, [r21]},
- {164, small_maps, []},
- {164, big, []}
+ {164, small_maps, [r22]},
+ {164, big, [r22]},
+ {164, small_maps, [no_shared_fun_wrappers]},
+
+ {168, small, [r22]},
+
+ {169, big, [no_shared_fun_wrappers,
+ no_put_tuple2,no_get_hd_tl,no_ssa_opt_record,
+ no_line_info,no_stack_trimming]},
+ {169, big, [no_shared_fun_wrappers,no_put_tuple2,no_get_hd_tl,
+ no_ssa_opt_record,no_line_info]},
+ {169, big, [no_shared_fun_wrappers,
+ no_put_tuple2,no_get_hd_tl, no_ssa_opt_record]},
+ {169, big, [no_shared_fun_wrappers]},
+
+ {170, small, [no_shared_fun_wrappers]},
+
+ {169, small_maps, []},
+ {169, big, []},
+ {170, small, []}
],
Test = fun({Expected,Mod,Options}) ->
@@ -1474,6 +1506,30 @@ deterministic_paths_1(DataDir, Name, Opts) ->
file:set_cwd(Cwd)
end.
+%% ERL-1058: -compile(debug_info) had no effect
+compile_attribute(Config) when is_list(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+
+ %% The test module has a -compile([debug_info]). attribute, which means
+ %% debug information should always be included.
+ debug_info_attribute(DataDir, "debug_info", [debug_info]),
+ debug_info_attribute(DataDir, "debug_info", []),
+
+ ok.
+
+debug_info_attribute(DataDir, Name, Opts) ->
+ File = filename:join(DataDir, Name),
+ {ok,_,Bin} = compile:file(File, [binary | Opts]),
+ {ok, {_, Attrs}} = beam_lib:chunks(Bin, [debug_info]),
+
+ [{debug_info,{debug_info_v1,erl_abstract_code,
+ {[{attribute,1,file,{_,1}},
+ {attribute,1,module,debug_info},
+ {attribute,2,compile,[debug_info]},
+ {eof,2}], _}}}] = Attrs,
+
+ ok.
+
%%%
%%% Utilities.
%%%
diff --git a/lib/compiler/test/compile_SUITE_data/debug_info.erl b/lib/compiler/test/compile_SUITE_data/debug_info.erl
new file mode 100644
index 0000000000..3ec48efeb8
--- /dev/null
+++ b/lib/compiler/test/compile_SUITE_data/debug_info.erl
@@ -0,0 +1,2 @@
+-module(debug_info).
+-compile([debug_info]). \ No newline at end of file
diff --git a/lib/compiler/test/core_SUITE.erl b/lib/compiler/test/core_SUITE.erl
index fc20b4aa30..5bd609c652 100644
--- a/lib/compiler/test/core_SUITE.erl
+++ b/lib/compiler/test/core_SUITE.erl
@@ -30,7 +30,8 @@
cover_v3_kernel_1/1,cover_v3_kernel_2/1,cover_v3_kernel_3/1,
cover_v3_kernel_4/1,cover_v3_kernel_5/1,
non_variable_apply/1,name_capture/1,fun_letrec_effect/1,
- get_map_element/1]).
+ get_map_element/1,receive_tests/1,
+ core_lint/1]).
-include_lib("common_test/include/ct.hrl").
@@ -59,7 +60,8 @@ groups() ->
cover_v3_kernel_1,cover_v3_kernel_2,cover_v3_kernel_3,
cover_v3_kernel_4,cover_v3_kernel_5,
non_variable_apply,name_capture,fun_letrec_effect,
- get_map_element
+ get_map_element,receive_tests,
+ core_lint
]}].
@@ -98,6 +100,7 @@ end_per_group(_GroupName, Config) ->
?comp(name_capture).
?comp(fun_letrec_effect).
?comp(get_map_element).
+?comp(receive_tests).
try_it(Mod, Conf) ->
Src = filename:join(proplists:get_value(data_dir, Conf),
@@ -112,3 +115,58 @@ compile_and_load(Src, Opts) ->
_ = code:delete(Mod),
_ = code:purge(Mod),
ok.
+
+core_lint(_Config) ->
+ OK = cerl:c_atom(ok),
+ core_lint_function(illegal),
+ core_lint_function(cerl:c_let([OK], OK, OK)),
+ core_lint_function(cerl:c_let([cerl:c_var(var)], cerl:c_var(999), OK)),
+ core_lint_function(cerl:c_let([cerl:c_var(var)], cerl:c_var(unknown), OK)),
+ core_lint_function(cerl:c_try(OK, [], OK, [], handler)),
+ core_lint_function(cerl:c_apply(cerl:c_var({OK,0}), [OK])),
+
+ core_lint_function([], [OK], OK),
+ core_lint_function([cerl:c_var({cerl:c_char($*),OK})], [], OK),
+
+ core_lint_pattern([cerl:c_var(99),cerl:c_var(99)]),
+ core_lint_pattern([cerl:c_let([cerl:c_var(var)], OK, OK)]),
+ core_lint_bs_pattern([OK]),
+ Flags = cerl:make_list([big,unsigned]),
+ core_lint_bs_pattern([cerl:c_bitstr(cerl:c_var(tail), cerl:c_atom(binary), Flags),
+ cerl:c_bitstr(cerl:c_var(value), cerl:c_atom(binary), Flags)]),
+
+ BadGuard1 = cerl:c_call(OK, OK, []),
+ BadGuard2 = cerl:c_call(cerl:c_atom(erlang), OK, []),
+ BadGuard3 = cerl:c_call(cerl:c_atom(erlang), cerl:c_atom(is_record), [OK,OK,OK]),
+ PatMismatch = cerl:c_case(cerl:c_nil(),
+ [cerl:c_clause([], OK),
+ cerl:c_clause([OK], OK),
+ cerl:c_clause([OK], BadGuard1, OK),
+ cerl:c_clause([OK], BadGuard2, OK),
+ cerl:c_clause([OK], BadGuard3, OK)]),
+ core_lint_function(PatMismatch),
+
+ ok.
+
+core_lint_bs_pattern(Ps) ->
+ core_lint_pattern([cerl:c_binary(Ps)]).
+
+core_lint_pattern(Ps) ->
+ Cs = [cerl:c_clause(Ps, cerl:c_float(42))],
+ core_lint_function(cerl:c_case(cerl:c_nil(), Cs)).
+
+core_lint_function(Body) ->
+ core_lint_function([], [], Body).
+
+core_lint_function(Exports, Attributes, Body) ->
+ ModName = cerl:c_atom(core_lint_test),
+ MainFun = cerl:c_fun([], Body),
+ MainVar = cerl:c_var({main,0}),
+ Mod = cerl:c_module(ModName, Exports, Attributes, [{MainVar,MainFun}]),
+ {error,[{core_lint_test,Errors}],[]} =
+ compile:forms(Mod, [from_core,clint0,return]),
+ io:format("~p\n", [Errors]),
+ [] = lists:filter(fun({none,core_lint,_}) -> false;
+ (_) -> true
+ end, Errors),
+ error = compile:forms(Mod, [from_core,clint0,report]).
diff --git a/lib/compiler/test/core_SUITE_data/bs_shadowed_size_var.core b/lib/compiler/test/core_SUITE_data/bs_shadowed_size_var.core
index 0ade037e05..2e59f9efde 100644
--- a/lib/compiler/test/core_SUITE_data/bs_shadowed_size_var.core
+++ b/lib/compiler/test/core_SUITE_data/bs_shadowed_size_var.core
@@ -52,8 +52,14 @@ module 'bs_shadowed_size_var'
case T of
%% Variable 'Sz' repeated here. Should work.
<#{#<Sz>(32,1,'integer',['unsigned','big']),
- #<Data>(Sz,8,'binary',['unsigned','big'])}#> when 'true' ->
- Data
+ #<Tail>('all',1,'binary',['unsigned','big'])}#> when 'true' ->
+ case Tail of
+ <#{#<Data>(Sz,8,'binary',['unsigned','big'])}#> when 'true' ->
+ Data
+ <_cor5> when 'true' ->
+ primop 'match_fail'
+ ({'case_clause',{_cor5}})
+ end
<_cor5> when 'true' ->
primop 'match_fail'
({'case_clause',{_cor5}})
diff --git a/lib/compiler/test/core_SUITE_data/receive_tests.core b/lib/compiler/test/core_SUITE_data/receive_tests.core
new file mode 100644
index 0000000000..8e56af8cd4
--- /dev/null
+++ b/lib/compiler/test/core_SUITE_data/receive_tests.core
@@ -0,0 +1,1761 @@
+%% Derived from receive_SUITE, with the ref_opt/1 test case removed.
+%% The purpose if this module is to make sure that the traditional
+%% syntax for receive in Core Erlang continues to work and is properly
+%% lowered.
+
+module 'receive_tests' ['module_info'/0,
+ 'module_info'/1,
+ 'receive_tests'/0]
+ attributes []
+'receive_tests'/0 =
+ %% Line 27
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ do %% Line 28
+ apply 'recv'/0
+ ()
+ do %% Line 28
+ apply 'coverage'/0
+ ()
+ do %% Line 28
+ apply 'otp_7980'/0
+ ()
+ do %% Line 28
+ apply 'export'/0
+ ()
+ do %% Line 28
+ apply 'wait'/0
+ ()
+ do %% Line 29
+ apply 'recv_in_try'/0
+ ()
+ do %% Line 29
+ apply 'double_recv'/0
+ ()
+ do %% Line 29
+ apply 'receive_var_zero'/0
+ ()
+ do %% Line 30
+ apply 'match_built_terms'/0
+ ()
+ do %% Line 30
+ apply 'elusive_common_exit'/0
+ ()
+ do %% Line 31
+ apply 'after_expression'/0
+ ()
+ %% Line 32
+ 'ok'
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'recv'/0 =
+ %% Line 36
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_0> =
+ fun () ->
+ %% Line 37
+ case <> of
+ <> when 'true' ->
+ apply 'loop'/1
+ ({'state','true'})
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+ in let <Pid> =
+ call %% Line 37
+ 'erlang':%% Line 37
+ 'spawn_link'
+ (_0)
+ in let <Self> =
+ call %% Line 38
+ 'erlang':%% Line 38
+ 'self'
+ ()
+ in do %% Line 39
+ call 'erlang':'!'
+ (Pid, {Self,'test'})
+ do %% Line 40
+ receive
+ %% Line 41
+ <{'ok','test'}> when 'true' ->
+ 'ok'
+ %% Line 42
+ <{'error',Other}> when 'true' ->
+ do %% Line 43
+ call 'io':'format'
+ ([71|[111|[116|[32|[117|[110|[112|[101|[120|[101|[99|[116|[101|[100|[32|[126|[112]]]]]]]]]]]]]]]]], [Other|[]])
+ %% Line 44
+ call 'ct':'fail'
+ ('unexpected')
+ after %% Line 45
+ 10000 ->
+ %% Line 46
+ call 'ct':'fail'
+ ('no_answer')
+ do %% Line 48
+ receive
+ %% Line 49
+ <X> when 'true' ->
+ do %% Line 50
+ call 'io':'format'
+ ([85|[110|[101|[120|[112|[101|[99|[116|[101|[100|[32|[101|[120|[116|[114|[97|[32|[109|[101|[115|[115|[97|[103|[101|[58|[32|[126|[112]]]]]]]]]]]]]]]]]]]]]]]]]]]], [X|[]])
+ %% Line 51
+ call 'ct':'fail'
+ ('unexpected')
+ after %% Line 52
+ 10 ->
+ do %% Line 53
+ call 'erlang':'unlink'
+ (Pid)
+ do %% Line 54
+ call 'erlang':'exit'
+ (Pid, 'kill')
+ %% Line 55
+ 'ok'
+ %% Line 57
+ 'ok'
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'loop'/1 =
+ %% Line 59
+ fun (_0) ->
+ case _0 of
+ <S> when 'true' ->
+ %% Line 60
+ receive
+ %% Line 61
+ <_8>
+ when ( try
+ ( let <_3> =
+ case ( call ( 'erlang'
+ -| ['compiler_generated'] ):( 'is_record'
+ -| ['compiler_generated'] )
+ (S, ( 'state'
+ -| ['compiler_generated'] ), ( 2
+ -| ['compiler_generated'] ))
+ -| ['compiler_generated'] ) of
+ ( <( 'true'
+ -| ['compiler_generated'] )> when 'true' ->
+ ( 'true'
+ -| ['compiler_generated'] )
+ -| ['compiler_generated'] )
+ ( <( 'false'
+ -| ['compiler_generated'] )> when 'true' ->
+ ( 'fail'
+ -| ['compiler_generated'] )
+ -| ['compiler_generated'] )
+ ( <( _1
+ -| ['compiler_generated'] )> when 'true' ->
+ ( _1
+ -| ['compiler_generated'] )
+ -| ['compiler_generated'] )
+ ( <_2> when 'true' ->
+ ( primop 'match_fail'
+ ({'case_clause',_2})
+ -| ['compiler_generated'] )
+ -| ['compiler_generated'] )
+ end
+ in let <_4> =
+ call 'erlang':'=:='
+ (( _3
+ -| ['compiler_generated'] ), 'true')
+ in let <_5> =
+ call 'erlang':'element'
+ (2, S)
+ in let <_6> =
+ call 'erlang':'=='
+ (_5, 'false')
+ in ( call ( 'erlang'
+ -| ['compiler_generated'] ):( 'and'
+ -| ['compiler_generated'] )
+ (_4, _6)
+ -| ['compiler_generated'] )
+ -| ['compiler_generated'] )
+ of <Try> ->
+ Try
+ catch <T,R> ->
+ 'false'
+ -| ['compiler_generated'] ) ->
+ %% Line 62
+ apply 'loop'/1
+ (S)
+ %% Line 63
+ <{P,'test'}> when 'true' ->
+ do %% Line 64
+ call 'erlang':'!'
+ (P, {'ok','test'})
+ %% Line 65
+ apply 'loop'/1
+ (S)
+ %% Line 66
+ <_X_X> when 'true' ->
+ %% Line 67
+ apply 'loop'/1
+ (S)
+ after 'infinity' ->
+ 'true'
+ ( <_7> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_7})
+ -| ['compiler_generated'] )
+ end
+'coverage'/0 =
+ %% Line 70
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_0> =
+ call %% Line 71
+ 'erlang':%% Line 71
+ 'self'
+ ()
+ in do %% Line 71
+ apply 'do_link'/1
+ (_0)
+ let <_1> =
+ call %% Line 72
+ 'erlang':%% Line 72
+ 'self'
+ ()
+ in do %% Line 72
+ apply 'do_unlink'/1
+ (_1)
+ let <_2> =
+ call %% Line 73
+ 'erlang':%% Line 73
+ 'node'
+ ()
+ in do %% Line 73
+ apply 'do_monitor_node'/2
+ (_2, 'true')
+ let <_3> =
+ call %% Line 74
+ 'erlang':%% Line 74
+ 'node'
+ ()
+ in do %% Line 74
+ apply 'do_monitor_node'/2
+ (_3, 'false')
+ let <_5> =
+ call %% Line 75
+ 'erlang':%% Line 75
+ 'group_leader'
+ ()
+ in let <_4> =
+ call %% Line 75
+ 'erlang':%% Line 75
+ 'self'
+ ()
+ in do %% Line 75
+ apply 'do_group_leader'/2
+ (_5, _4)
+ let <_6> =
+ call %% Line 76
+ 'erlang':%% Line 76
+ 'self'
+ ()
+ in let <_7> =
+ call %% Line 76
+ 'erlang':%% Line 76
+ 'node'
+ (_6)
+ in do %% Line 76
+ apply 'id'/1
+ (_7)
+ let <_8> =
+ call %% Line 78
+ 'erlang':%% Line 78
+ 'self'
+ ()
+ in do %% Line 78
+ call 'erlang':'!'
+ (_8, {'a',10})
+ let <_9> =
+ call %% Line 79
+ 'erlang':%% Line 79
+ 'self'
+ ()
+ in do %% Line 79
+ call 'erlang':'!'
+ (_9, {'b',20})
+ %% Line 80
+ case apply 'receive_all'/0
+ () of
+ <[{'a',10}|[{'b',20}]]> when 'true' ->
+ let <_11> =
+ call %% Line 81
+ 'erlang':%% Line 81
+ 'self'
+ ()
+ in do %% Line 81
+ call 'erlang':'!'
+ (_11, {'c',42})
+ do %% Line 82
+ receive
+ %% Line 83
+ <{'c',42}> when 'true' ->
+ %% Line 84
+ 'ok'
+ after %% Line 85
+ 'infinity' ->
+ %% Line 86
+ call 'erlang':'exit'
+ ('cant_happen')
+ let <_12> =
+ call %% Line 89
+ 'erlang':%% Line 89
+ 'self'
+ ()
+ in do %% Line 89
+ call 'erlang':'!'
+ (_12, 17)
+ let <_13> =
+ call %% Line 90
+ 'erlang':%% Line 90
+ 'self'
+ ()
+ in do %% Line 90
+ call 'erlang':'!'
+ (_13, 19)
+ %% Line 91
+ case apply 'tuple_to_values'/2
+ ('infinity', 'x') of
+ <59> when 'true' ->
+ %% Line 92
+ case apply 'tuple_to_values'/2
+ (999999, 'x') of
+ <61> when 'true' ->
+ %% Line 93
+ case apply 'tuple_to_values'/2
+ (1, 'x') of
+ <0> when 'true' ->
+ let <_18> =
+ catch
+ let <_17> =
+ call %% Line 95
+ 'erlang':%% Line 95
+ 'self'
+ ()
+ in %% Line 95
+ apply 'monitor_plus_badmap'/1
+ (_17)
+ in %% Line 95
+ case _18 of
+ <{'EXIT',{{'badmap',[]},_23}}> when 'true' ->
+ let <_20> =
+ call %% Line 98
+ 'erlang':%% Line 98
+ 'self'
+ ()
+ in do %% Line 98
+ call 'erlang':'!'
+ (_20, {'data','no_data'})
+ %% Line 99
+ case apply 'receive_sink_tuple'/1
+ ({'any','pattern'}) of
+ <'ok'> when 'true' ->
+ %% Line 100
+ case apply 'receive_sink_tuple'/1
+ ({'a','b'}) of
+ <{'b','a'}> when 'true' ->
+ %% Line 102
+ 'ok'
+ ( <_22> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_22})
+ -| ['compiler_generated'] )
+ end
+ ( <_21> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_21})
+ -| ['compiler_generated'] )
+ end
+ ( <_19> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_19})
+ -| ['compiler_generated'] )
+ end
+ ( <_16> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_16})
+ -| ['compiler_generated'] )
+ end
+ ( <_15> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_15})
+ -| ['compiler_generated'] )
+ end
+ ( <_14> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_14})
+ -| ['compiler_generated'] )
+ end
+ ( <_10> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_10})
+ -| ['compiler_generated'] )
+ end
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'monitor_plus_badmap'/1 =
+ %% Line 104
+ fun (_0) ->
+ case _0 of
+ <Pid> when 'true' ->
+ let <_2> =
+ call %% Line 105
+ 'erlang':%% Line 105
+ 'monitor'
+ (%% Line 105
+ 'process', %% Line 105
+ Pid)
+ in let <_1> =
+ primop 'match_fail'
+ ({'badmap',[]})
+ in %% Line 105
+ call 'erlang':'+'
+ (_2, _1)
+ ( <_3> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_3})
+ -| ['compiler_generated'] )
+ end
+'receive_all'/0 =
+ %% Line 107
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ %% Line 108
+ receive
+ %% Line 109
+ <Any> when 'true' ->
+ let <_0> =
+ apply %% Line 110
+ 'receive_all'/0
+ ()
+ in %% Line 110
+ [Any|_0]
+ after %% Line 111
+ 0 ->
+ %% Line 112
+ []
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'do_monitor_node'/2 =
+ %% Line 115
+ fun (_0,_1) ->
+ case <_0,_1> of
+ <Node,Bool> when 'true' ->
+ %% Line 116
+ call 'erlang':'monitor_node'
+ (Node, Bool)
+ ( <_3,_2> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_3,_2})
+ -| ['compiler_generated'] )
+ end
+'do_link'/1 =
+ %% Line 118
+ fun (_0) ->
+ case _0 of
+ <Pid> when 'true' ->
+ %% Line 119
+ call 'erlang':'link'
+ (Pid)
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_1})
+ -| ['compiler_generated'] )
+ end
+'do_unlink'/1 =
+ %% Line 121
+ fun (_0) ->
+ case _0 of
+ <Pid> when 'true' ->
+ %% Line 122
+ call 'erlang':'unlink'
+ (Pid)
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_1})
+ -| ['compiler_generated'] )
+ end
+'do_group_leader'/2 =
+ %% Line 124
+ fun (_0,_1) ->
+ case <_0,_1> of
+ <Leader,Pid> when 'true' ->
+ %% Line 125
+ call 'erlang':'group_leader'
+ (Leader, Pid)
+ ( <_3,_2> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_3,_2})
+ -| ['compiler_generated'] )
+ end
+'tuple_to_values'/2 =
+ %% Line 129
+ fun (_0,_1) ->
+ case <_0,_1> of
+ <'infinity',X> when 'true' ->
+ let <_3> =
+ case %% Line 130
+ X of
+ %% Line 131
+ <'x'> when 'true' ->
+ %% Line 132
+ receive
+ %% Line 133
+ <Any> when 'true' ->
+ %% Line 134
+ {42,Any}
+ after 'infinity' ->
+ 'true'
+ ( <_2> when 'true' ->
+ %% Line 130
+ primop 'match_fail'
+ ({'case_clause',_2})
+ -| ['compiler_generated'] )
+ end
+ in %% Line 130
+ case _3 of
+ <{A,B}> when 'true' ->
+ %% Line 137
+ call 'erlang':'+'
+ (A, B)
+ ( <_4> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_4})
+ -| ['compiler_generated'] )
+ end
+ %% Line 138
+ <Timeout,X> when 'true' ->
+ let <_6> =
+ case %% Line 139
+ X of
+ %% Line 140
+ <'x'> when 'true' ->
+ %% Line 141
+ receive
+ %% Line 142
+ <Any> when 'true' ->
+ %% Line 143
+ {42,Any}
+ after %% Line 144
+ Timeout ->
+ %% Line 145
+ {0,0}
+ ( <_5> when 'true' ->
+ %% Line 139
+ primop 'match_fail'
+ ({'case_clause',_5})
+ -| ['compiler_generated'] )
+ end
+ in %% Line 139
+ case _6 of
+ <{A,B}> when 'true' ->
+ %% Line 148
+ call 'erlang':'+'
+ (A, B)
+ ( <_7> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_7})
+ -| ['compiler_generated'] )
+ end
+ ( <_9,_8> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_9,_8})
+ -| ['compiler_generated'] )
+ end
+'receive_sink_tuple'/1 =
+ %% Line 151
+ fun (_0) ->
+ case _0 of
+ <{Line,Pattern}> when 'true' ->
+ %% Line 152
+ receive
+ %% Line 153
+ <{'data',_2}> when 'true' ->
+ %% Line 154
+ 'ok'
+ after %% Line 155
+ 1 ->
+ %% Line 156
+ apply 'id'/1
+ ({Pattern,Line})
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_1})
+ -| ['compiler_generated'] )
+ end
+'otp_7980'/0 =
+ %% Line 163
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ %% Line 164
+ case apply 'otp_7980_add_clients'/1
+ (10) of
+ <7> when 'true' ->
+ %% Line 165
+ 'ok'
+ ( <_0> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_0})
+ -| ['compiler_generated'] )
+ end
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'otp_7980_add_clients'/1 =
+ %% Line 167
+ fun (_0) ->
+ case _0 of
+ <Count> when 'true' ->
+ let <Timeout> = 42
+ in let <_7> =
+ fun (_4,_3) ->
+ %% Line 169
+ case <_4,_3> of
+ <_9,N> when 'true' ->
+ do %% Line 170
+ case N of
+ %% Line 171
+ <1> when 'true' ->
+ 'ok'
+ %% Line 172
+ <_10> when 'true' ->
+ receive
+
+ after Timeout ->
+ 'ok'
+ ( <_2> when 'true' ->
+ primop 'match_fail'
+ ({'case_clause',_2})
+ -| ['compiler_generated'] )
+ end
+ %% Line 174
+ call 'erlang':'-'
+ (N, 1)
+ ( <_6,_5> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_6,_5})
+ -| ['compiler_generated'] )
+ end
+ in %% Line 169
+ call 'lists':'foldl'
+ (_7, %% Line 175
+ Count, %% Line 175
+ [1|[2|[3]]])
+ ( <_8> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_8})
+ -| ['compiler_generated'] )
+ end
+'export'/0 =
+ %% Line 177
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <Ref> =
+ call %% Line 178
+ 'erlang':%% Line 178
+ 'make_ref'
+ ()
+ in let <_1> =
+ call %% Line 179
+ 'erlang':%% Line 179
+ 'self'
+ ()
+ in do %% Line 179
+ call 'erlang':'!'
+ (_1, {'result',Ref,42})
+ %% Line 180
+ case apply 'export_1'/1
+ (Ref) of
+ <42> when 'true' ->
+ %% Line 181
+ case apply 'export_1'/1
+ (Ref) of
+ <{'error','timeout'}> when 'true' ->
+ let <_4> =
+ call %% Line 183
+ 'erlang':%% Line 183
+ 'self'
+ ()
+ in do %% Line 183
+ call 'erlang':'!'
+ (_4, {'result',Ref})
+ %% Line 184
+ case apply 'export_2'/0
+ () of
+ <{'ok',_6}>
+ when call 'erlang':'=:='
+ (_6,
+ Ref) ->
+ %% Line 186
+ 'ok'
+ ( <_5> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_5})
+ -| ['compiler_generated'] )
+ end
+ ( <_3> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_3})
+ -| ['compiler_generated'] )
+ end
+ ( <_2> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_2})
+ -| ['compiler_generated'] )
+ end
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'export_1'/1 =
+ %% Line 188
+ fun (_0) ->
+ case _0 of
+ <Reference> when 'true' ->
+ do %% Line 189
+ apply 'id'/1
+ (Reference)
+ let <_5,Result> =
+ receive
+ %% Line 191
+ <{'result',_4,Result}>
+ when call 'erlang':'=:='
+ (_4,
+ Reference) ->
+ %% Line 192
+ <Result,Result>
+ after %% Line 193
+ 1 ->
+ let <Result> =
+ {'error','timeout'}
+ in %% Line 194
+ <Result,Result>
+ in let <_2> =
+ call %% Line 199
+ 'erlang':%% Line 199
+ 'self'
+ ()
+ in do %% Line 199
+ apply 'id'/1
+ ({'build',_2})
+ %% Line 200
+ Result
+ ( <_3> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_3})
+ -| ['compiler_generated'] )
+ end
+'export_2'/0 =
+ %% Line 202
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_0,Result> =
+ receive
+ %% Line 203
+ <{'result',Result}> when 'true' ->
+ <'ok',Result>
+ after 'infinity' ->
+ <'true','true'>
+ in %% Line 204
+ {'ok',Result}
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'wait'/0 =
+ %% Line 206
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_0> =
+ call %% Line 207
+ 'erlang':%% Line 207
+ 'self'
+ ()
+ in do %% Line 207
+ call 'erlang':'!'
+ (_0, #{#<42>(8,1,'integer',['unsigned'|['big']])}#)
+ %% Line 208
+ case apply 'wait_1'/3
+ ('r', 1, 2) of
+ <#{#<42>(8,1,'integer',['unsigned'|['big']])}#> when 'true' ->
+ %% Line 209
+ case apply 'wait_1'/3
+ (1, 2, 3) of
+ <{1,2,3}> when 'true' ->
+ let <_3> =
+ catch
+ %% Line 210
+ receive
+
+ after [] ->
+ 'timeout'
+ in %% Line 210
+ case _3 of
+ <{'EXIT',{'timeout_value',_5}}> when 'true' ->
+ %% Line 211
+ 'ok'
+ ( <_4> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_4})
+ -| ['compiler_generated'] )
+ end
+ ( <_2> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_2})
+ -| ['compiler_generated'] )
+ end
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_1})
+ -| ['compiler_generated'] )
+ end
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'wait_1'/3 =
+ %% Line 213
+ fun (_0,_1,_2) ->
+ case <_0,_1,_2> of
+ <'r',_7,_8> when 'true' ->
+ %% Line 214
+ receive
+ %% Line 215
+ <B>
+ when try
+ let <_3> =
+ call 'erlang':'byte_size'
+ (B)
+ in call 'erlang':'>'
+ (_3, 0)
+ of <Try> ->
+ Try
+ catch <T,R> ->
+ 'false' ->
+ %% Line 216
+ B
+ after 'infinity' ->
+ 'true'
+ %% Line 220
+ <A,B,C> when 'true' ->
+ %% Line 221
+ {A,B,C}
+ ( <_6,_5,_4> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_6,_5,_4})
+ -| ['compiler_generated'] )
+ end
+'recv_in_try'/0 =
+ %% Line 223
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_0> =
+ call %% Line 224
+ 'erlang':%% Line 224
+ 'self'
+ ()
+ in do %% Line 224
+ call 'erlang':'!'
+ (_0, {'ok','fh'})
+ %% Line 224
+ case apply 'recv_in_try'/2
+ ('infinity', 'native') of
+ <{'ok','fh'}> when 'true' ->
+ let <_2> =
+ call %% Line 225
+ 'erlang':%% Line 225
+ 'self'
+ ()
+ in do %% Line 225
+ call 'erlang':'!'
+ (_2, {'ok','ignored'})
+ %% Line 225
+ case apply 'recv_in_try'/2
+ ('infinity', 'plain') of
+ <{'ok',42}> when 'true' ->
+ let <_4> =
+ call %% Line 226
+ 'erlang':%% Line 226
+ 'self'
+ ()
+ in do %% Line 226
+ call 'erlang':'!'
+ (_4, {'error','ignored'})
+ %% Line 226
+ case apply 'recv_in_try'/2
+ ('infinity', 'plain') of
+ <'nok'> when 'true' ->
+ %% Line 227
+ case apply 'recv_in_try'/2
+ (1, 'plain') of
+ <'timeout'> when 'true' ->
+ %% Line 228
+ 'ok'
+ ( <_6> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_6})
+ -| ['compiler_generated'] )
+ end
+ ( <_5> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_5})
+ -| ['compiler_generated'] )
+ end
+ ( <_3> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_3})
+ -| ['compiler_generated'] )
+ end
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_1})
+ -| ['compiler_generated'] )
+ end
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'recv_in_try'/2 =
+ %% Line 230
+ fun (_0,_1) ->
+ case <_0,_1> of
+ <Timeout,Format> when 'true' ->
+ %% Line 231
+ try
+ %% Line 232
+ receive
+ %% Line 233
+ <{Status,History}> when 'true' ->
+ let <_3> =
+ case %% Line 244
+ Format of
+ %% Line 245
+ <'native'> when 'true' ->
+ %% Line 246
+ apply 'id'/1
+ (History)
+ %% Line 247
+ <'plain'> when 'true' ->
+ %% Line 248
+ apply 'id'/1
+ (42)
+ ( <_2> when 'true' ->
+ %% Line 244
+ primop 'match_fail'
+ ({'case_clause',_2})
+ -| ['compiler_generated'] )
+ end
+ in let <FH> = _3
+ in %% Line 250
+ case Status of
+ %% Line 251
+ <'ok'> when 'true' ->
+ %% Line 252
+ {'ok',FH}
+ %% Line 253
+ <'error'> when 'true' ->
+ %% Line 254
+ 'nok'
+ ( <_5> when 'true' ->
+ primop 'match_fail'
+ ({'case_clause',_5})
+ -| ['compiler_generated'] )
+ end
+ after %% Line 256
+ Timeout ->
+ %% Line 257
+ 'timeout'
+ of <_6> ->
+ _6
+ catch <_9,_8,_7> ->
+ %% Line 262
+ case {_9,_8,_7} of
+ <{'throw',{'error',Reason},_12}> when 'true' ->
+ %% Line 263
+ {'nok',Reason}
+ ( <{_9,_8,_7}> when 'true' ->
+ primop 'raise'
+ (_7, _8)
+ -| ['compiler_generated'] )
+ end
+ ( <_11,_10> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_11,_10})
+ -| ['compiler_generated'] )
+ end
+'double_recv'/0 =
+ %% Line 270
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_0> =
+ call %% Line 271
+ 'erlang':%% Line 271
+ 'self'
+ ()
+ in do %% Line 271
+ call 'erlang':'!'
+ (_0, {'more',{'a','term'}})
+ %% Line 272
+ case apply 'do_double_recv'/2
+ ({'more',{'a','term'}}, 'any') of
+ <'ok'> when 'true' ->
+ let <_2> =
+ call %% Line 273
+ 'erlang':%% Line 273
+ 'self'
+ ()
+ in do %% Line 273
+ call 'erlang':'!'
+ (_2, 'message')
+ %% Line 274
+ case apply 'do_double_recv'/2
+ ('whatever', 'message') of
+ <'ok'> when 'true' ->
+ %% Line 276
+ case apply 'do_double_recv'/2
+ ({'more',42}, 'whatever') of
+ <'error'> when 'true' ->
+ %% Line 277
+ case apply 'do_double_recv'/2
+ ('whatever', 'whatever') of
+ <'error'> when 'true' ->
+ %% Line 278
+ 'ok'
+ ( <_5> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_5})
+ -| ['compiler_generated'] )
+ end
+ ( <_4> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_4})
+ -| ['compiler_generated'] )
+ end
+ ( <_3> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_3})
+ -| ['compiler_generated'] )
+ end
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_1})
+ -| ['compiler_generated'] )
+ end
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'do_double_recv'/2 =
+ %% Line 280
+ fun (_0,_1) ->
+ case <_0,_1> of
+ <{'more',Rest},_X_Msg> when 'true' ->
+ %% Line 281
+ receive
+ %% Line 282
+ <{'more',_4}>
+ when call 'erlang':'=:='
+ (_4,
+ Rest) ->
+ %% Line 283
+ 'ok'
+ after %% Line 284
+ 0 ->
+ %% Line 285
+ 'error'
+ %% Line 287
+ <_5,Msg> when 'true' ->
+ %% Line 288
+ receive
+ %% Line 289
+ <_6>
+ when call 'erlang':'=:='
+ (_6,
+ Msg) ->
+ %% Line 290
+ 'ok'
+ after %% Line 291
+ 0 ->
+ %% Line 292
+ 'error'
+ ( <_3,_2> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_3,_2})
+ -| ['compiler_generated'] )
+ end
+'receive_var_zero'/0 =
+ %% Line 297
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_0> =
+ call %% Line 298
+ 'erlang':%% Line 298
+ 'self'
+ ()
+ in do %% Line 298
+ call 'erlang':'!'
+ (_0, 'x')
+ let <_1> =
+ call %% Line 299
+ 'erlang':%% Line 299
+ 'self'
+ ()
+ in do %% Line 299
+ call 'erlang':'!'
+ (_1, 'y')
+ let <Z> =
+ apply %% Line 300
+ 'zero'/0
+ ()
+ in let <_3> =
+ receive
+ %% Line 302
+ <'z'> when 'true' ->
+ 'ok'
+ after %% Line 303
+ Z ->
+ %% Line 303
+ 'timeout'
+ in %% Line 301
+ case _3 of
+ <'timeout'> when 'true' ->
+ let <_5> =
+ receive
+
+ after %% Line 306
+ Z ->
+ %% Line 306
+ 'timeout'
+ in %% Line 305
+ case _5 of
+ <'timeout'> when 'true' ->
+ let <_7> =
+ call %% Line 308
+ 'erlang':%% Line 308
+ 'self'
+ ()
+ in do %% Line 308
+ call 'erlang':'!'
+ (_7, 'w')
+ %% Line 309
+ receive
+ %% Line 310
+ <'x'> when 'true' ->
+ do %% Line 311
+ receive
+ <'y'> when 'true' ->
+ 'ok'
+ after 'infinity' ->
+ 'true'
+ do %% Line 312
+ receive
+ <'w'> when 'true' ->
+ 'ok'
+ after 'infinity' ->
+ 'true'
+ %% Line 313
+ 'ok'
+ %% Line 314
+ <Other> when 'true' ->
+ %% Line 315
+ call 'ct':'fail'
+ ({'bad_message',Other})
+ after 'infinity' ->
+ 'true'
+ ( <_6> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_6})
+ -| ['compiler_generated'] )
+ end
+ ( <_4> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_4})
+ -| ['compiler_generated'] )
+ end
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'zero'/0 =
+ %% Line 318
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ 0
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'match_built_terms'/0 =
+ %% Line 339
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_5> =
+ fun () ->
+ %% Line 340
+ case <> of
+ <> when 'true' ->
+ let <Ref> =
+ call 'erlang':'make_ref'
+ ()
+ in let <A> =
+ apply 'id'/1
+ (97)
+ in let <B> =
+ apply 'id'/1
+ (98)
+ in let <Built> =
+ apply 'id'/1
+ ([A|[B|[]]])
+ in let <_4> =
+ call 'erlang':'self'
+ ()
+ in do call 'erlang':'!'
+ (_4, {Ref,A,B})
+ receive
+ <{_28,_29,_30}>
+ when let <_35> =
+ call 'erlang':'=:='
+ (_28, Ref)
+ in let <_33> =
+ call 'erlang':'=:='
+ (_29, A)
+ in let <_31> =
+ call 'erlang':'=:='
+ (_30, B)
+ in let <_32> =
+ call 'erlang':'=:='
+ ([A|[B|[]]], Built)
+ in let <_34> =
+ call 'erlang':'and'
+ (_31, _32)
+ in let <_36> =
+ call 'erlang':'and'
+ (_33, _34)
+ in call 'erlang':'and'
+ (_35, _36) ->
+ 'ok'
+ after 5000 ->
+ call 'ct':'fail'
+ ([70|[97|[105|[108|[101|[100|[32|[116|[111|[32|[109|[97|[116|[99|[104|[32|[109|[101|[115|[115|[97|[103|[101|[32|[119|[105|[116|[104|[32|[116|[101|[114|[109|[32|[98|[117|[105|[108|[116|[32|[105|[110|[32|[114|[101|[99|[101|[105|[118|[101|[32|[103|[117|[97|[114|[100|[46]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]])
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+ in do %% Line 340
+ apply _5
+ ()
+ let <_11> =
+ fun () ->
+ %% Line 341
+ case <> of
+ <> when 'true' ->
+ let <Ref> =
+ call 'erlang':'make_ref'
+ ()
+ in let <A> =
+ apply 'id'/1
+ (97)
+ in let <B> =
+ apply 'id'/1
+ (98)
+ in let <Built> =
+ apply 'id'/1
+ ({A,B})
+ in let <_10> =
+ call 'erlang':'self'
+ ()
+ in do call 'erlang':'!'
+ (_10, {Ref,A,B})
+ receive
+ <{_37,_38,_39}>
+ when let <_44> =
+ call 'erlang':'=:='
+ (_37, Ref)
+ in let <_42> =
+ call 'erlang':'=:='
+ (_38, A)
+ in let <_40> =
+ call 'erlang':'=:='
+ (_39, B)
+ in let <_41> =
+ call 'erlang':'=:='
+ ({A,B}, Built)
+ in let <_43> =
+ call 'erlang':'and'
+ (_40, _41)
+ in let <_45> =
+ call 'erlang':'and'
+ (_42, _43)
+ in call 'erlang':'and'
+ (_44, _45) ->
+ 'ok'
+ after 5000 ->
+ call 'ct':'fail'
+ ([70|[97|[105|[108|[101|[100|[32|[116|[111|[32|[109|[97|[116|[99|[104|[32|[109|[101|[115|[115|[97|[103|[101|[32|[119|[105|[116|[104|[32|[116|[101|[114|[109|[32|[98|[117|[105|[108|[116|[32|[105|[110|[32|[114|[101|[99|[101|[105|[118|[101|[32|[103|[117|[97|[114|[100|[46]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]])
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+ in do %% Line 341
+ apply _11
+ ()
+ let <_19> =
+ fun () ->
+ %% Line 342
+ case <> of
+ <> when 'true' ->
+ let <Ref> =
+ call 'erlang':'make_ref'
+ ()
+ in let <A> =
+ apply 'id'/1
+ (97)
+ in let <B> =
+ apply 'id'/1
+ (98)
+ in let <_15> =
+ #{#<A>(8,1,'integer',['unsigned'|['big']]),
+ #<B>(8,1,'integer',['unsigned'|['big']])}#
+ in let <Built> =
+ apply 'id'/1
+ (_15)
+ in let <_17> =
+ call 'erlang':'self'
+ ()
+ in do call 'erlang':'!'
+ (_17, {Ref,A,B})
+ receive
+ <{_46,_47,_48}>
+ when let <_53> =
+ call 'erlang':'=:='
+ (_46, Ref)
+ in let <_51> =
+ call 'erlang':'=:='
+ (_47, A)
+ in let <_49> =
+ call 'erlang':'=:='
+ (_48, B)
+ in let <_50> =
+ try
+ let <_18> =
+ #{#<A>(8,1,'integer',['unsigned'|['big']]),
+ #<B>(8,1,'integer',['unsigned'|['big']])}#
+ in call 'erlang':'=:='
+ (_18, Built)
+ of <Try> ->
+ Try
+ catch <T,R> ->
+ 'false'
+ in let <_52> =
+ call 'erlang':'and'
+ (_49, _50)
+ in let <_54> =
+ call 'erlang':'and'
+ (_51, _52)
+ in call 'erlang':'and'
+ (_53, _54) ->
+ 'ok'
+ after 5000 ->
+ call 'ct':'fail'
+ ([70|[97|[105|[108|[101|[100|[32|[116|[111|[32|[109|[97|[116|[99|[104|[32|[109|[101|[115|[115|[97|[103|[101|[32|[119|[105|[116|[104|[32|[116|[101|[114|[109|[32|[98|[117|[105|[108|[116|[32|[105|[110|[32|[114|[101|[99|[101|[105|[118|[101|[32|[103|[117|[97|[114|[100|[46]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]])
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+ in do %% Line 342
+ apply _19
+ ()
+ let <_27> =
+ fun () ->
+ %% Line 343
+ case <> of
+ <> when 'true' ->
+ let <Ref> =
+ call 'erlang':'make_ref'
+ ()
+ in let <A> =
+ apply 'id'/1
+ (97)
+ in let <B> =
+ apply 'id'/1
+ (98)
+ in let <_23> =
+ ~{1=>A,2=>B}~
+ in let <Built> =
+ apply 'id'/1
+ (_23)
+ in let <_25> =
+ call 'erlang':'self'
+ ()
+ in do call 'erlang':'!'
+ (_25, {Ref,A,B})
+ receive
+ <{_55,_56,_57}>
+ when let <_62> =
+ call 'erlang':'=:='
+ (_55, Ref)
+ in let <_60> =
+ call 'erlang':'=:='
+ (_56, A)
+ in let <_58> =
+ call 'erlang':'=:='
+ (_57, B)
+ in let <_59> =
+ try
+ let <_26> =
+ ~{1=>A,2=>B}~
+ in call 'erlang':'=:='
+ (_26, Built)
+ of <Try> ->
+ Try
+ catch <T,R> ->
+ 'false'
+ in let <_61> =
+ call 'erlang':'and'
+ (_58, _59)
+ in let <_63> =
+ call 'erlang':'and'
+ (_60, _61)
+ in call 'erlang':'and'
+ (_62, _63) ->
+ 'ok'
+ after 5000 ->
+ call 'ct':'fail'
+ ([70|[97|[105|[108|[101|[100|[32|[116|[111|[32|[109|[97|[116|[99|[104|[32|[109|[101|[115|[115|[97|[103|[101|[32|[119|[105|[116|[104|[32|[116|[101|[114|[109|[32|[98|[117|[105|[108|[116|[32|[105|[110|[32|[114|[101|[99|[101|[105|[118|[101|[32|[103|[117|[97|[114|[100|[46]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]])
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+ in %% Line 343
+ apply _27
+ ()
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'elusive_common_exit'/0 =
+ %% Line 345
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_0> =
+ call %% Line 346
+ 'erlang':%% Line 346
+ 'self'
+ ()
+ in do %% Line 346
+ call 'erlang':'!'
+ (_0, {1,'a'})
+ let <_1> =
+ call %% Line 347
+ 'erlang':%% Line 347
+ 'self'
+ ()
+ in do %% Line 347
+ call 'erlang':'!'
+ (_1, {2,'b'})
+ %% Line 348
+ case apply 'elusive_loop'/3
+ (['x'|['y'|['z']]], 2, []) of
+ <{['z'],[{2,'b'}|[{1,'a'}]]}> when 'true' ->
+ let <CodeServer> =
+ call %% Line 350
+ 'erlang':%% Line 350
+ 'whereis'
+ (%% Line 350
+ 'code_server')
+ in let <Self> =
+ call %% Line 351
+ 'erlang':%% Line 351
+ 'self'
+ ()
+ in do %% Line 352
+ call 'erlang':'!'
+ (Self, {Self,'abc'})
+ do %% Line 353
+ call 'erlang':'!'
+ (Self, {CodeServer,[]})
+ do %% Line 354
+ call 'erlang':'!'
+ (Self, {Self,'other'})
+ do %% Line 355
+ try
+ apply 'elusive2'/1
+ ([])
+ of <_5> ->
+ case _5 of
+ %% Line 356
+ <Unexpected> when 'true' ->
+ %% Line 357
+ call 'ct':'fail'
+ ([69|[120|[112|[101|[99|[116|[101|[100|[32|[97|[110|[32|[101|[120|[99|[101|[112|[116|[105|[111|[110|[59|[32|[103|[111|[116|[32|[126|[112|[10]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], [Unexpected|[]])
+ ( <_6> when 'true' ->
+ primop 'match_fail'
+ ({'try_clause',_6})
+ -| ['compiler_generated'] )
+ end
+ catch <_9,_8,_7> ->
+ %% Line 359
+ case {_9,_8,_7} of
+ <{'throw',['other'|[_10|[_11|[]]]],_12}>
+ when let <_13> =
+ call 'erlang':'=:='
+ (_10, CodeServer)
+ in let <_14> =
+ call 'erlang':'=:='
+ (_11, Self)
+ in call 'erlang':'and'
+ (_13, _14) ->
+ %% Line 360
+ 'ok'
+ ( <{_9,_8,_7}> when 'true' ->
+ primop 'raise'
+ (_7, _8)
+ -| ['compiler_generated'] )
+ end
+ %% Line 363
+ 'ok'
+ ( <_2> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_2})
+ -| ['compiler_generated'] )
+ end
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'elusive_loop'/3 =
+ %% Line 365
+ fun (_0,_1,_2) ->
+ case <_0,_1,_2> of
+ <List,0,Results> when 'true' ->
+ %% Line 366
+ {List,Results}
+ %% Line 367
+ <List,ToReceive,Results> when 'true' ->
+ let <_4> =
+ receive
+ %% Line 370
+ <Res = {_X_Pos,_X_R}>
+ when call 'erlang':'=/='
+ (List,
+ []) ->
+ %% Line 371
+ case List of
+ <[_X_H|T]> when 'true' ->
+ %% Line 372
+ {Res,T}
+ ( <_3> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_3})
+ -| ['compiler_generated'] )
+ end
+ %% Line 373
+ <Res = {_X_Pos,_X_R}>
+ when call 'erlang':'=:='
+ (List,
+ []) ->
+ %% Line 374
+ {Res,[]}
+ after 'infinity' ->
+ 'true'
+ in %% Line 368
+ case _4 of
+ <{Result,RemList}> when 'true' ->
+ let <_6> =
+ call %% Line 379
+ 'erlang':%% Line 379
+ '-'
+ (%% Line 379
+ ToReceive, %% Line 379
+ 1)
+ in %% Line 379
+ apply 'elusive_loop'/3
+ (RemList, _6, [Result|Results])
+ ( <_5> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_5})
+ -| ['compiler_generated'] )
+ end
+ ( <_9,_8,_7> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_9,_8,_7})
+ -| ['compiler_generated'] )
+ end
+'elusive2'/1 =
+ %% Line 382
+ fun (_0) ->
+ case _0 of
+ <Acc> when 'true' ->
+ let <_2,Pid> =
+ receive
+ %% Line 384
+ <{Pid,'abc'}> when 'true' ->
+ %% Line 385
+ <'ok',Pid>
+ %% Line 386
+ <{Pid,[]}> when 'true' ->
+ %% Line 387
+ <'ok',Pid>
+ %% Line 388
+ <{Pid,Res}> when 'true' ->
+ %% Line 397
+ <call 'erlang':'throw'
+ ([Res|Acc]),Pid>
+ after 'infinity' ->
+ <'true','true'>
+ in %% Line 400
+ apply 'elusive2'/1
+ ([Pid|Acc])
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_1})
+ -| ['compiler_generated'] )
+ end
+'after_expression'/0 =
+ %% Line 402
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ let <_0> =
+ call %% Line 403
+ 'erlang':%% Line 403
+ 'self'
+ ()
+ in do %% Line 403
+ call 'erlang':'!'
+ (_0, {'a','message'})
+ %% Line 404
+ case apply 'after_expr'/1
+ (0) of
+ <{'a','message'}> when 'true' ->
+ %% Line 405
+ case apply 'after_expr'/1
+ (0) of
+ <'timeout'> when 'true' ->
+ %% Line 406
+ case apply 'after_expr'/1
+ (10) of
+ <'timeout'> when 'true' ->
+ %% Line 407
+ 'ok'
+ ( <_3> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_3})
+ -| ['compiler_generated'] )
+ end
+ ( <_2> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_2})
+ -| ['compiler_generated'] )
+ end
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'badmatch',_1})
+ -| ['compiler_generated'] )
+ end
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'after_expr'/1 =
+ %% Line 409
+ fun (_0) ->
+ case _0 of
+ <Timeout> when 'true' ->
+ %% Line 410
+ receive
+ %% Line 411
+ <Msg> when 'true' ->
+ Msg
+ after %% Line 412
+ apply 'id'/1
+ (Timeout) ->
+ %% Line 413
+ 'timeout'
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_1})
+ -| ['compiler_generated'] )
+ end
+'id'/1 =
+ %% Line 416
+ fun (_0) ->
+ case _0 of
+ <I> when 'true' ->
+ I
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_1})
+ -| ['compiler_generated'] )
+ end
+'module_info'/0 =
+ fun () ->
+ case <> of
+ <> when 'true' ->
+ call 'erlang':'get_module_info'
+ ('receive_tests')
+ ( <> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause'})
+ -| ['compiler_generated'] )
+ end
+'module_info'/1 =
+ fun (_0) ->
+ case _0 of
+ <X> when 'true' ->
+ call 'erlang':'get_module_info'
+ ('receive_tests', X)
+ ( <_1> when 'true' ->
+ primop 'match_fail'
+ ({'function_clause',_1})
+ -| ['compiler_generated'] )
+ end
+end \ No newline at end of file
diff --git a/lib/compiler/test/core_alias_SUITE.erl b/lib/compiler/test/core_alias_SUITE.erl
index 737b1567d4..7e5dc4cbc0 100644
--- a/lib/compiler/test/core_alias_SUITE.erl
+++ b/lib/compiler/test/core_alias_SUITE.erl
@@ -21,7 +21,7 @@
-export([all/0, suite/0, groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2, end_per_group/2,
- tuples/1, cons/1]).
+ tuples/1, cons/1, catastrophic_runtime/1]).
-include_lib("common_test/include/ct.hrl").
@@ -32,7 +32,7 @@ all() ->
groups() ->
[{p,[parallel],
- [tuples, cons]}].
+ [tuples, cons, catastrophic_runtime]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -47,11 +47,10 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
-
id(X) -> X.
tuples(Config) when is_list(Config) ->
- Tuple = {ok,id(value)},
+ Tuple = id({ok,id(value)}),
true = erts_debug:same(Tuple, simple_tuple(Tuple)),
true = erts_debug:same(Tuple, simple_tuple_in_map(#{hello => Tuple})),
@@ -59,24 +58,24 @@ tuples(Config) when is_list(Config) ->
true = erts_debug:same(Tuple, simple_tuple_fun_repeated(Tuple, Tuple)),
true = erts_debug:same(Tuple, simple_tuple_twice_head(Tuple, Tuple)),
- {Tuple1, Tuple2} = simple_tuple_twice_body(Tuple),
+ {Tuple1, Tuple2} = id(simple_tuple_twice_body(Tuple)),
true = erts_debug:same(Tuple, Tuple1),
true = erts_debug:same(Tuple, Tuple2),
- Nested = {nested,Tuple},
+ Nested = id({nested,Tuple}),
true = erts_debug:same(Tuple, nested_tuple_part(Nested)),
true = erts_debug:same(Nested, nested_tuple_whole(Nested)),
true = erts_debug:same(Nested, nested_tuple_with_alias(Nested)),
true = erts_debug:same(Tuple, tuple_rebinding_after(Tuple)),
- Tuple = unaliased_tuple_rebinding_before(Tuple),
+ Tuple = id(unaliased_tuple_rebinding_before(Tuple)),
false = erts_debug:same(Tuple, unaliased_tuple_rebinding_before(Tuple)),
- Nested = unaliased_literal_tuple_head(Nested),
+ Nested = id(unaliased_literal_tuple_head(Nested)),
false = erts_debug:same(Nested, unaliased_literal_tuple_head(Nested)),
- Nested = unaliased_literal_tuple_body(Nested),
+ Nested = id(unaliased_literal_tuple_body(Nested)),
false = erts_debug:same(Nested, unaliased_literal_tuple_body(Nested)),
- Nested = unaliased_different_var_tuple(Nested, Tuple),
+ Nested = id(unaliased_different_var_tuple(Nested, Tuple)),
false = erts_debug:same(Nested, unaliased_different_var_tuple(Nested, Tuple)).
simple_tuple({ok,X}) ->
@@ -119,7 +118,7 @@ unaliased_different_var_tuple({nested,{ok,value}=X}, Y) ->
{nested,Y}.
cons(Config) when is_list(Config) ->
- Cons = [ok|id(value)],
+ Cons = id([ok|id(value)]),
true = erts_debug:same(Cons, simple_cons(Cons)),
true = erts_debug:same(Cons, simple_cons_in_map(#{hello => Cons})),
@@ -127,27 +126,27 @@ cons(Config) when is_list(Config) ->
true = erts_debug:same(Cons, simple_cons_fun_repeated(Cons, Cons)),
true = erts_debug:same(Cons, simple_cons_twice_head(Cons, Cons)),
- {Cons1,Cons2} = simple_cons_twice_body(Cons),
+ {Cons1,Cons2} = id(simple_cons_twice_body(Cons)),
true = erts_debug:same(Cons, Cons1),
true = erts_debug:same(Cons, Cons2),
- Nested = [nested,Cons],
+ Nested = id([nested,Cons]),
true = erts_debug:same(Cons, nested_cons_part(Nested)),
true = erts_debug:same(Nested, nested_cons_whole(Nested)),
true = erts_debug:same(Nested, nested_cons_with_alias(Nested)),
true = erts_debug:same(Cons, cons_rebinding_after(Cons)),
Unstripped = id([a,b]),
- Stripped = cons_with_binary([<<>>|Unstripped]),
+ Stripped = id(cons_with_binary([<<>>|Unstripped])),
true = erts_debug:same(Unstripped, Stripped),
- Cons = unaliased_cons_rebinding_before(Cons),
+ Cons = id(unaliased_cons_rebinding_before(Cons)),
false = erts_debug:same(Cons, unaliased_cons_rebinding_before(Cons)),
- Nested = unaliased_literal_cons_head(Nested),
+ Nested = id(unaliased_literal_cons_head(Nested)),
false = erts_debug:same(Nested, unaliased_literal_cons_head(Nested)),
- Nested = unaliased_literal_cons_body(Nested),
+ Nested = id(unaliased_literal_cons_body(Nested)),
false = erts_debug:same(Nested, unaliased_literal_cons_body(Nested)),
- Nested = unaliased_different_var_cons(Nested, Cons),
+ Nested = id(unaliased_different_var_cons(Nested, Cons)),
false = erts_debug:same(Nested, unaliased_different_var_cons(Nested, Cons)).
simple_cons([ok|X]) ->
@@ -193,3 +192,28 @@ unaliased_literal_cons_body([nested,[ok|value]=X]) ->
unaliased_different_var_cons([nested,[ok|value]=X], Y) ->
io:format("~p~n", [X]),
[nested,Y].
+
+catastrophic_runtime(Config) ->
+ ct:timetrap({minutes, 6}),
+ Depth = 16000,
+
+ PrivDir = proplists:get_value(priv_dir,Config),
+ Path = filename:join(PrivDir, "catastrophic_runtime.erl"),
+
+ Term = catastrophic_runtime_1(Depth),
+ Source = <<"-module(catastrophic_runtime). t(Value) -> ", Term/bits, ".">>,
+ file:write_file(Path, Source),
+
+ {ok, catastrophic_runtime} = compile:file(Path, [return_error]),
+ file:delete(Path),
+
+ ok.
+
+catastrophic_runtime_1(0) ->
+ <<"Value">>;
+catastrophic_runtime_1(N) ->
+ Nested = catastrophic_runtime_1(N - 1),
+ Integer = integer_to_binary(N),
+ Eq = <<"{{'.',[],[erlang,'=:=']},[],[Value, \"", Integer/bits, "\"]}">>,
+ <<"{{'.',[],[erlang,atom]},[],[", Nested/bits, ",", Eq/bits, "]}">>.
+
diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl
index f683600f64..1c279dd316 100644
--- a/lib/compiler/test/core_fold_SUITE.erl
+++ b/lib/compiler/test/core_fold_SUITE.erl
@@ -309,6 +309,13 @@ coverage(Config) when is_list(Config) ->
{_,_} ->
Tuple =:= true
end,
+
+ %% Cover is literal_fun/1.
+ {'EXIT',{{case_clause,42},_}} = (catch cover_is_literal_fun()),
+
+ %% Cover core_lib.
+ ok = cover_core_lib([ok,nok]),
+
ok.
cover_will_match_list_type(A) ->
@@ -376,6 +383,21 @@ cover_eval_is_function(X) ->
bsm_an_inlined(<<_:8>>, _) -> ok;
bsm_an_inlined(_, _) -> error.
+cover_is_literal_fun() ->
+ [case id(42) of
+ [] ->
+ try right of
+ wrong -> true
+ catch
+ error:_ -> error
+ end
+ end]().
+
+cover_core_lib(Modules) ->
+ R = id(Modules),
+ _ = [id(Error) || Error <- R, element(1, Error) =/= ok],
+ ok.
+
unused_multiple_values_error(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
Dir = test_lib:get_data_dir(Config),
diff --git a/lib/compiler/test/error_SUITE.erl b/lib/compiler/test/error_SUITE.erl
index 8b9dbe4aa0..9436ad5d53 100644
--- a/lib/compiler/test/error_SUITE.erl
+++ b/lib/compiler/test/error_SUITE.erl
@@ -270,8 +270,7 @@ maps_warnings(Config) when is_list(Config) ->
id(I) -> I.
">>,
[return],
- {error,[{3,erl_lint,{unbound_var,'K'}},
- {6,erl_lint,illegal_map_key}],[]}}
+ {error,[{3,erl_lint,{unbound_var,'K'}}],[]}}
],
[] = run2(Config, Ts1),
ok.
diff --git a/lib/compiler/test/float_SUITE.erl b/lib/compiler/test/float_SUITE.erl
index e52e5eb07d..bf154eeb62 100644
--- a/lib/compiler/test/float_SUITE.erl
+++ b/lib/compiler/test/float_SUITE.erl
@@ -21,7 +21,8 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
pending/1,bif_calls/1,math_functions/1,mixed_float_and_int/1,
- subtract_number_type/1,float_followed_by_guard/1]).
+ subtract_number_type/1,float_followed_by_guard/1,
+ fconv_line_numbers/1,exception_signals/1]).
-include_lib("common_test/include/ct.hrl").
@@ -30,7 +31,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[pending, bif_calls, math_functions,
mixed_float_and_int, subtract_number_type,
- float_followed_by_guard].
+ float_followed_by_guard,fconv_line_numbers,
+ exception_signals].
groups() ->
[].
@@ -204,5 +206,37 @@ ffbg_1(A, B0) ->
A - B =< 0.0 -> false
end.
+%% ERL-1178: fconv instructions didn't inherit line numbers from their
+%% respective BIF calls.
+fconv_line_numbers(Config) when is_list(Config) ->
+ fconv_line_numbers_1(id(gurka)),
+ ok.
+
+fconv_line_numbers_1(A) ->
+ %% The ?LINE macro must be on the same line as the division.
+ {'EXIT',{badarith, Stacktrace}} = (catch 10 / A), Line = ?LINE,
+ true = lists:any(fun({?MODULE,?FUNCTION_NAME,1,[{file,_},{line,L}]}) ->
+ L =:= Line;
+ (_) ->
+ false
+ end, Stacktrace).
+
+%% ERL-1471: compiler generated invalid 'fclearerror' / 'fcheckerror'
+%% sequences.
+exception_signals(Config) when is_list(Config) ->
+ 2.0 = exception_signals_1(id(25), id(true), []),
+ 2.0 = exception_signals_1(id(25), id(false), []),
+ 2.0 = exception_signals_1(id(25.0), id(true), []),
+ 2.0 = exception_signals_1(id(25.0), id(false), []),
+ ok.
+
+exception_signals_1(Width, Value, _Opts) ->
+ Height = Width / 25.0,
+ _Middle = case Value of
+ true -> Width / 2.0;
+ false -> 0
+ end,
+ _More = Height + 1.
+
id(I) -> I.
diff --git a/lib/compiler/test/fun_SUITE.erl b/lib/compiler/test/fun_SUITE.erl
index 3b8e8698de..bd8603ae81 100644
--- a/lib/compiler/test/fun_SUITE.erl
+++ b/lib/compiler/test/fun_SUITE.erl
@@ -206,11 +206,18 @@ external(Config) when is_list(Config) ->
{'EXIT',{{badarity,_},_}} = (catch (id(fun lists:sum/1))(1, 2, 3)),
{'EXIT',{{badarity,_},_}} = (catch apply(fun lists:sum/1, [1,2,3])),
+ {'EXIT',{badarg,_}} = (catch bad_external_fun()),
+
ok.
call_me(I) ->
{ok,I}.
+bad_external_fun() ->
+ V0 = idea,
+ fun V0:V0/V0, %Should fail.
+ never_reached.
+
eep37(Config) when is_list(Config) ->
F = fun Fact(N) when N > 0 -> N * Fact(N - 1); Fact(0) -> 1 end,
Add = fun _(N) -> N + 1 end,
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index 10f8464101..63b636b023 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -19,7 +19,7 @@
%%
-module(guard_SUITE).
--include_lib("common_test/include/ct.hrl").
+-include_lib("syntax_tools/include/merl.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
@@ -31,12 +31,13 @@
old_guard_tests/1,complex_guard/1,
build_in_guard/1,gbif/1,
t_is_boolean/1,is_function_2/1,
- tricky/1,rel_ops/1,rel_op_combinations/1,literal_type_tests/1,
+ tricky/1,rel_ops/1,rel_op_combinations/1,
+ generated_combinations/1,literal_type_tests/1,
basic_andalso_orelse/1,traverse_dcd/1,
check_qlc_hrl/1,andalso_semi/1,t_tuple_size/1,binary_part/1,
bad_constants/1,bad_guards/1,
guard_in_catch/1,beam_bool_SUITE/1,
- repeated_type_tests/1]).
+ repeated_type_tests/1,use_after_branch/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -55,8 +56,8 @@ groups() ->
basic_andalso_orelse,traverse_dcd,
check_qlc_hrl,andalso_semi,t_tuple_size,binary_part,
bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE,
- repeated_type_tests]},
- {slow,[],[literal_type_tests]}].
+ repeated_type_tests,use_after_branch]},
+ {slow,[],[literal_type_tests,generated_combinations]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -1164,6 +1165,11 @@ t_is_boolean(Config) when is_list(Config) ->
false = my_is_bool([1,2,3,4]),
false = my_is_bool({a,b,c}),
+ %% Cover code in beam_ssa_dead.
+ ok = bool_semi(true),
+ ok = bool_semi(false),
+ error = bool_semi(a),
+
ok.
bool(X) when is_boolean(X) -> ok;
@@ -1187,6 +1193,9 @@ my_is_bool_b(V) ->
_ -> false
end.
+bool_semi(V) when is_boolean(V); is_atom(not V) -> ok;
+bool_semi(_) -> error.
+
is_function_2(Config) when is_list(Config) ->
true = is_function(id(fun ?MODULE:all/1), 1),
true = is_function(id(fun() -> ok end), 0),
@@ -1222,6 +1231,10 @@ tricky(Config) when is_list(Config) ->
error = tricky_3(#{}),
error = tricky_3({a,b}),
+ {'EXIT',_} = (catch tricky_4(x)),
+ {'EXIT',_} = (catch tricky_4(42)),
+ {'EXIT',_} = (catch tricky_4(true)),
+
ok.
tricky_1(X, Y) when abs((X == 1) or (Y == 2)) -> ok;
@@ -1239,6 +1252,13 @@ tricky_3(X)
tricky_3(_) ->
error.
+tricky_4(X) ->
+ B = (abs(X) or abs(X)) =:= true,
+ case B of
+ true -> ok;
+ false -> error
+ end.
+
%% From dets_v9:read_buckets/11, simplified.
rb(Size, ToRead, SoFar) when SoFar + Size < 81920; ToRead == [] -> true;
@@ -1352,7 +1372,9 @@ rel_op_combinations(Config) when is_list(Config) ->
Red0 = [{I,2*I} || I <- lists:seq(0, 50)] ++
[{I,5*I} || I <- lists:seq(51, 80)],
Red = gb_trees:from_orddict(Red0),
- rel_op_combinations_3(100, Red).
+ rel_op_combinations_3(100, Red),
+
+ rel_op_combinations_4().
rel_op_combinations_1(0, _) ->
ok;
@@ -1530,7 +1552,7 @@ rel_op_combinations_3(N, Red) ->
Val = redundant_9(N),
Val = redundant_10(N),
Val = redundant_11(N),
- Val = redundant_11(N),
+ Val = redundant_12(N),
rel_op_combinations_3(N-1, Red).
redundant_1(X) when X >= 51, X =< 80 -> 5*X;
@@ -1585,10 +1607,136 @@ redundant_11(X) when X =:= 10 -> 2*X;
redundant_11(X) when X >= 51, X =< 80 -> 5*X;
redundant_11(_) -> none.
-redundant_12(X) when X >= 50, X =< 80 -> 2*X;
-redundant_12(X) when X < 51 -> 5*X;
+redundant_12(50) -> 100;
+redundant_12(X) when X >= 50, X =< 80 -> 5*X;
+redundant_12(X) when X < 51 -> 2*X;
redundant_12(_) -> none.
+rel_op_combinations_4() ->
+ ne = rel_op_vars_1(id(a), id(b)),
+ le = rel_op_vars_1(id(x), id(x)),
+
+ ne = rel_op_vars_2(id(a), id(b)),
+ ge = rel_op_vars_2(id(x), id(x)),
+
+ ok.
+
+rel_op_vars_1(X, N) when X =/= N -> ne;
+rel_op_vars_1(X, N) when X =< N -> le.
+
+rel_op_vars_2(X, N) when X =/= N -> ne;
+rel_op_vars_2(X, N) when X >= N -> ge.
+
+%% Exhaustively test all combinations of relational operators
+%% to ensure the correctness of the optimizations in beam_ssa_dead.
+
+generated_combinations(_Config) ->
+ Mod = ?FUNCTION_NAME,
+ RelOps = ['=:=','=/=','==','/=','<','=<','>=','>'],
+ Combinations0 = [{Op1,Op2} || Op1 <- RelOps, Op2 <- RelOps],
+ Combinations1 = gen_lit_combs(Combinations0),
+ Combinations2 = [{neq,Comb} ||
+ {_Op1,_Lit1,Op2,_Lit2}=Comb <- Combinations1,
+ Op2 =:= '=/=' orelse Op2 =:= '/='] ++ Combinations1,
+ Combinations = gen_func_names(Combinations2, 0),
+ Fs = gen_rel_op_functions(Combinations),
+ Tree = ?Q(["-module('@Mod@').",
+ "-compile([export_all,nowarn_export_all])."]) ++ Fs,
+ %%merl:print(Tree),
+ Opts = test_lib:opt_opts(?MODULE),
+ {ok,_Bin} = merl:compile_and_load(Tree, Opts),
+ test_combinations(Combinations, Mod).
+
+gen_lit_combs([{Op1,Op2}|T]) ->
+ [{Op1,7,Op2,6},
+ {Op1,7.0,Op2,6},
+ {Op1,7,Op2,6.0},
+ {Op1,7.0,Op2,6.0},
+
+ {Op1,7,Op2,7},
+ {Op1,7.0,Op2,7},
+ {Op1,7,Op2,7.0},
+ {Op1,7.0,Op2,7.0},
+
+ {Op1,6,Op2,7},
+ {Op1,6.0,Op2,7},
+ {Op1,6,Op2,7.0},
+ {Op1,6.0,Op2,7.0}|gen_lit_combs(T)];
+gen_lit_combs([]) -> [].
+
+gen_func_names([E|Es], I) ->
+ Name = list_to_atom("f" ++ integer_to_list(I)),
+ [{Name,E}|gen_func_names(Es, I+1)];
+gen_func_names([], _) -> [].
+
+gen_rel_op_functions([{Name,{neq,{Op1,Lit1,Op2,Lit2}}}|T]) ->
+ %% Note that in the translation to SSA, '=/=' will be
+ %% translated to '=:=' in a guard (with switched success
+ %% and failure labels). Therefore, to test the optimization,
+ %% we must use '=/=' (or '/=') in a body context.
+ %%
+ %% Here is an example of a generated function:
+ %%
+ %% f160(A) when erlang:'>='(A, 7) ->
+ %% one;
+ %% f160(A) ->
+ %% true = erlang:'/='(A, 7),
+ %% two.
+ [?Q("'@Name@'(A) when erlang:'@Op1@'(A, _@Lit1@) -> one;
+ '@Name@'(A) -> true = erlang:'@Op2@'(A, _@Lit2@), two. ")|
+ gen_rel_op_functions(T)];
+gen_rel_op_functions([{Name,{Op1,Lit1,Op2,Lit2}}|T]) ->
+ %% Example of a generated function:
+ %%
+ %% f721(A) when erlang:'=<'(A, 7.0) -> one;
+ %% f721(A) when erlang:'<'(A, 6) -> two;
+ %% f721(_) -> three.
+ [?Q("'@Name@'(A) when erlang:'@Op1@'(A, _@Lit1@) -> one;
+ '@Name@'(A) when erlang:'@Op2@'(A, _@Lit2@) -> two;
+ '@Name@'(_) -> three.")|gen_rel_op_functions(T)];
+gen_rel_op_functions([]) -> [].
+
+test_combinations([{Name,E}|T], Mod) ->
+ try
+ test_combinations_1([5,6,7,8,9], E, fun Mod:Name/1),
+ test_combination(6.5, E, fun Mod:Name/1)
+ catch
+ error:Reason:Stk ->
+ io:format("~p: ~p\n", [Name,E]),
+ erlang:raise(error, Reason, Stk)
+ end,
+ test_combinations(T, Mod);
+test_combinations([], _Mod) -> ok.
+
+test_combinations_1([V|Vs], E, Fun) ->
+ test_combination(V, E, Fun),
+ test_combination(float(V), E, Fun),
+ test_combinations_1(Vs, E, Fun);
+test_combinations_1([], _, _) -> ok.
+
+test_combination(Val, {neq,Expr}, Fun) ->
+ Result = eval_combination_expr(Expr, Val),
+ Result = try
+ Fun(Val) %Returns 'one' or 'two'.
+ catch
+ error:{badmatch,_} ->
+ three
+ end;
+test_combination(Val, Expr, Fun) ->
+ Result = eval_combination_expr(Expr, Val),
+ Result = Fun(Val).
+
+eval_combination_expr({Op1,Lit1,Op2,Lit2}, Val) ->
+ case erlang:Op1(Val, Lit1) of
+ true ->
+ one;
+ false ->
+ case erlang:Op2(Val, Lit2) of
+ true -> two;
+ false -> three
+ end
+ end.
+
%% Test type tests on literal values. (From emulator test suites.)
literal_type_tests(Config) when is_list(Config) ->
%% Generate an Erlang module with all different type of type tests.
@@ -1818,6 +1966,15 @@ andalso_semi(Config) when is_list(Config) ->
ok = andalso_semi_bar([a,b,c]),
ok = andalso_semi_bar(1),
fc(catch andalso_semi_bar([a,b])),
+
+ ok = andalso_semi_dispatch(name, fun andalso_semi/1),
+ ok = andalso_semi_dispatch(name, fun ?MODULE:andalso_semi/1),
+ ok = andalso_semi_dispatch(name, {?MODULE,andalso_semi,1}),
+ fc(catch andalso_semi_dispatch(42, fun andalso_semi/1)),
+ fc(catch andalso_semi_dispatch(name, not_fun)),
+ fc(catch andalso_semi_dispatch(name, fun andalso_semi_dispatch/2)),
+ fc(catch andalso_semi_dispatch(42, {a,b})),
+
ok.
andalso_semi_foo(Bar) when is_integer(Bar) andalso Bar =:= 0; Bar =:= 1 ->
@@ -1826,6 +1983,10 @@ andalso_semi_foo(Bar) when is_integer(Bar) andalso Bar =:= 0; Bar =:= 1 ->
andalso_semi_bar(Bar) when is_list(Bar) andalso length(Bar) =:= 3; Bar =:= 1 ->
ok.
+andalso_semi_dispatch(Registry, MFAOrFun) when
+ is_atom(Registry) andalso is_function(MFAOrFun, 1);
+ is_atom(Registry) andalso tuple_size(MFAOrFun) == 3 ->
+ ok.
t_tuple_size(Config) when is_list(Config) ->
10 = do_tuple_size({1,2,3,4}),
@@ -2121,7 +2282,8 @@ do_guard_in_catch_bin(From) ->
%%%
%%% The beam_bool pass has been eliminated. Here are the tests from
-%%% beam_bool_SUITE.
+%%% beam_bool_SUITE, as well as new tests to test the new beam_ssa_bool
+%%% module.
%%%
beam_bool_SUITE(_Config) ->
@@ -2130,6 +2292,17 @@ beam_bool_SUITE(_Config) ->
y_registers(),
protected(),
maps(),
+ cover_shortcut_branches(),
+ wrong_order(),
+ megaco(),
+ looks_like_a_guard(),
+ fail_in_guard(),
+ in_catch(),
+ recv_semi(),
+ andalso_repeated_var(),
+ erl1246(),
+ erl1253(),
+ erl1384(),
ok.
before_and_inside_if() ->
@@ -2267,6 +2440,346 @@ maps() ->
evidence(#{0 := Charge}) when 0; #{[] => Charge} == #{[] => 42} ->
ok.
+cover_shortcut_branches() ->
+ ok = cover_shortcut_branches({r1}, 0, 42, false),
+ ok = cover_shortcut_branches({r1}, 42, 42, true),
+ error = cover_shortcut_branches({r1}, same, same, false),
+ error = cover_shortcut_branches({r1}, x, y, true),
+ error = cover_shortcut_branches({r2}, 0, 42, false),
+ error = cover_shortcut_branches({}, 0, 42, false),
+ error = cover_shortcut_branches(not_tuple, 0, 42, false),
+ ok.
+
+cover_shortcut_branches(St, X, Y, Z) ->
+ if
+ %% The ((Y =:= X) =:= Z) part will test handling of a comparison
+ %% operator followed by a one-way `br`.
+ ((element(1, St) =:= r1) orelse fail) and ((Y =:= X) =:= Z) ->
+ ok;
+ true ->
+ error
+ end.
+
+wrong_order() ->
+ ok = wrong_order(repeat_until_fail, true),
+ ok = wrong_order(repeat_until_fail, whatever),
+ error = wrong_order(repeat_until_fail, false),
+ error = wrong_order(nope, true),
+ ok.
+
+wrong_order(RepeatType, Mode) ->
+ Parallel = Mode =/= false,
+ RepeatStop = RepeatType =:= repeat_until_fail,
+ if
+ Parallel andalso RepeatStop ->
+ ok;
+ true ->
+ error
+ end.
+
+megaco() ->
+ ok = megaco('NULL', 0),
+ ok = megaco('NULL', 7),
+ ok = megaco('NULL', 15),
+ ok = megaco('NULL', asn1_NOVALUE),
+ ok = megaco(asn1_NOVALUE, 0),
+ ok = megaco(asn1_NOVALUE, 7),
+ ok = megaco(asn1_NOVALUE, 15),
+ ok = megaco(asn1_NOVALUE, asn1_NOVALUE),
+
+ error = megaco(bad, 0),
+ error = megaco(bad, 7),
+ error = megaco(bad, 15),
+ error = megaco(bad, asn1_NOVALUE),
+
+ error = megaco('NULL', not_integer),
+ error = megaco('NULL', -1),
+ error = megaco('NULL', 16),
+ error = megaco(asn1_NOVALUE, not_integer),
+ error = megaco(asn1_NOVALUE, -1),
+ error = megaco(asn1_NOVALUE, 16),
+
+ error = megaco(bad, bad),
+ error = megaco(bad, -1),
+ error = megaco(bad, 42),
+
+ ok.
+
+megaco(Top, SelPrio)
+ when (Top =:= 'NULL' orelse Top =:= asn1_NOVALUE) andalso
+ ((is_integer(SelPrio) andalso ((0 =< SelPrio) and (SelPrio =< 15))) orelse
+ SelPrio =:= asn1_NOVALUE) ->
+ ok;
+megaco(_, _) ->
+ error.
+
+%% ERL-1054.
+looks_like_a_guard() ->
+ ok = looks_like_a_guard(0),
+ ok = looks_like_a_guard(1),
+ ok.
+
+looks_like_a_guard(N) ->
+ GuessPosition = id(42),
+ %% The matching of `true` would look like a guard to
+ %% beam_ssa_bool. The optimized code would not be safe.
+ case {1 >= N, GuessPosition == 0} of
+ {true, _} -> ok;
+ {_, true} -> ok;
+ _ -> looks_like_a_guard(N)
+ end.
+
+-record(fail_in_guard, {f}).
+fail_in_guard() ->
+ false = struct_or_map(a, "foo"),
+ false = struct_or_map(a, foo),
+ false = struct_or_map(#{}, "foo"),
+ true = struct_or_map(#{}, foo),
+
+ false = (fun() when whatever =/= (program andalso []) -> true;
+ () -> false
+ end)(),
+ false = if whatever =/= (program orelse []) -> true;
+ true -> false
+ end,
+
+ %% Would crash the compiler if optimizing passes
+ %% were disabled.
+ error = if
+ 0.1 orelse "VZ", 42 -> ok;
+ true -> error
+ end,
+ error = (fun() when 3.14; <<[]:(ceil($D))>> -> ok;
+ () -> error
+ end)(),
+ error = fun() when eye; ""#fail_in_guard.f andalso #{[] => true and false} ->
+ ok;
+ () ->
+ error
+ end(),
+ error = fun() when (0 #fail_in_guard.f)#fail_in_guard.f -> ok;
+ () -> error
+ end(),
+
+ ok.
+
+%% ERL-1183. If Name is not an atom, the `fail` atom must cause the
+%% entire guard to fail.
+struct_or_map(Arg, Name) when
+ (is_map(Arg) andalso (is_atom(Name) orelse fail) andalso
+ is_map_key(struct, Arg)) orelse is_map(Arg) -> true;
+struct_or_map(_Arg, _Name) ->
+ false.
+
+in_catch() ->
+ ok = in_catch(true),
+ {'EXIT',{{case_clause,false},[_|_]}} = in_catch(false),
+ {'EXIT',{badarg,[_|_]}} = in_catch(any),
+ ok.
+
+in_catch(V) ->
+ catch
+ case false or V of
+ true -> ok
+ end.
+
+recv_semi() ->
+ timeout = id(receive
+ ok when home; <<(m#{}):false>> ->
+ ok
+ after 0 ->
+ timeout
+ end).
+
+andalso_repeated_var() ->
+ ok = andalso_repeated_var(true),
+ error = andalso_repeated_var(false),
+ error = andalso_repeated_var([not_boolean]),
+ ok.
+
+andalso_repeated_var(B) when B andalso B -> ok;
+andalso_repeated_var(_) -> error.
+
+-record(erl1246, {tran_stat = 0}).
+
+erl1246() ->
+ false = erl1246(#erl1246{tran_stat = 0}, #{cid => 1131}),
+ false = erl1246(#erl1246{tran_stat = 12}, #{cid => 1131}),
+ false = erl1246(#erl1246{tran_stat = 12}, #{cid => 9502}),
+ true = erl1246(#erl1246{tran_stat = 0}, #{cid => 9502}),
+ ok.
+
+erl1246(Rec, #{cid := CollID}) ->
+ {GiftCollID, _} = erl1246_conf(gift_coll),
+ IsTranStat = Rec#erl1246.tran_stat =:= erl1246_conf(transform_id),
+ if
+ %% Optimization of 'not' in a guard was broken.
+ CollID =:= GiftCollID andalso not IsTranStat ->
+ true;
+ true ->
+ false
+ end.
+
+erl1246_conf(gift_coll) -> {9502, {112, 45}};
+erl1246_conf(transform_id) -> 12;
+erl1246_conf(_) -> undefined.
+
+erl1253() ->
+ ok = erl1253_orelse_false(a, a, any),
+ ok = erl1253_orelse_false(a, a, true),
+ ok = erl1253_orelse_false(a, a, false),
+ error = erl1253_orelse_false(a, b, any),
+ error = erl1253_orelse_false(a, b, true),
+ ok = erl1253_orelse_false(a, b, false),
+
+ ok = erl1253_orelse_true(a, a, any),
+ ok = erl1253_orelse_true(a, a, true),
+ ok = erl1253_orelse_true(a, a, false),
+ error = erl1253_orelse_true(a, b, any),
+ ok = erl1253_orelse_true(a, b, true),
+ error = erl1253_orelse_true(a, b, false),
+
+ error = erl1253_andalso_false(a, a, any),
+ error = erl1253_andalso_false(a, a, true),
+ ok = erl1253_andalso_false(a, a, false),
+ error = erl1253_andalso_false(a, b, any),
+ error = erl1253_andalso_false(a, b, true),
+ error = erl1253_andalso_false(a, b, false),
+
+ error = erl1253_andalso_true(a, a, any),
+ ok = erl1253_andalso_true(a, a, true),
+ error = erl1253_andalso_true(a, a, false),
+ error = erl1253_andalso_true(a, b, any),
+ error = erl1253_andalso_true(a, b, true),
+ error = erl1253_andalso_true(a, b, false),
+
+ ok.
+
+erl1253_orelse_false(X, Y, Z) ->
+ Res = erl1253_orelse_false_1(X, Y, Z),
+ Res = erl1253_orelse_false_2(X, Y, Z),
+ Res = erl1253_orelse_false_3(X, Y, Z).
+
+erl1253_orelse_false_1(X, Y, Z) ->
+ Bool = Z =:= false,
+ if
+ X =:= Y orelse Bool -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_false_2(X, Y, Z) ->
+ Bool = Z =:= false,
+ if
+ Bool orelse X =:= Y -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_false_3(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= false,
+ if
+ Bool1 orelse Bool2 -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_true(X, Y, Z) ->
+ Res = erl1253_orelse_true_1(X, Y, Z),
+ Res = erl1253_orelse_true_2(X, Y, Z),
+ Res = erl1253_orelse_true_3(X, Y, Z).
+
+erl1253_orelse_true_1(X, Y, Z) ->
+ Bool = Z =:= true,
+ if
+ X =:= Y orelse Bool -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_true_2(X, Y, Z) ->
+ Bool = Z =:= true,
+ if
+ Bool orelse X =:= Y -> ok;
+ true -> error
+ end.
+
+erl1253_orelse_true_3(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= true,
+ if
+ Bool1 orelse Bool2 -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_false(X, Y, Z) ->
+ Res = erl1253_andalso_false_1(X, Y, Z),
+ Res = erl1253_andalso_false_2(X, Y, Z),
+ Res = erl1253_andalso_false_3(X, Y, Z).
+
+erl1253_andalso_false_1(X, Y, Z) ->
+ Bool = Z =:= false,
+ if
+ X =:= Y andalso Bool -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_false_2(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= false,
+ if
+ Bool1 andalso Bool2 -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_false_3(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= false,
+ if
+ Bool1 andalso Bool2 -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_true(X, Y, Z) ->
+ Res = erl1253_andalso_true_1(X, Y, Z),
+ Res = erl1253_andalso_true_2(X, Y, Z),
+ Res = erl1253_andalso_true_3(X, Y, Z).
+
+erl1253_andalso_true_1(X, Y, Z) ->
+ Bool = Z =:= true,
+ if
+ X =:= Y andalso Bool -> ok;
+ true -> error
+ end.
+
+erl1253_andalso_true_2(X, Y, Z) ->
+ Bool = Z =:= true,
+ if
+ Bool andalso X =:= Y-> ok;
+ true -> error
+ end.
+
+erl1253_andalso_true_3(X, Y, Z) ->
+ Bool1 = X =:= Y,
+ Bool2 = Z =:= true,
+ if
+ Bool1 andalso Bool2 -> ok;
+ true -> error
+ end.
+
+erl1384() ->
+ gurka = erl1384_1(id(a)),
+ gaffel = erl1384_1(id(b)),
+ ok.
+
+erl1384_1(V) ->
+ case {id(false), V =/= a} of
+ {true, true} -> not_reachable;
+ {_, false} -> gurka;
+ _ -> gaffel
+ end.
+
+%%%
+%%% End of beam_bool_SUITE tests.
+%%%
+
repeated_type_tests(_Config) ->
binary = repeated_type_test(<<42>>),
bitstring = repeated_type_test(<<1:1>>),
@@ -2285,6 +2798,23 @@ repeated_type_test(T) ->
other
end.
+%% ERL-1179: The result of '=/=' would be flipped if it was used after being
+%% branched on.
+use_after_branch(_Config) ->
+ {false, gaffel} = use_after_branch_1(foo),
+ {true, gurka} = use_after_branch_1(bar),
+ ok.
+
+use_after_branch_1(A) ->
+ Boolean = A =/= foo,
+ case Boolean of
+ true -> id(something);
+ false -> id(other)
+ end,
+ case Boolean of
+ true -> {id(Boolean), gurka};
+ false -> {id(Boolean), gaffel}
+ end.
%% Call this function to turn off constant propagation.
id(I) -> I.
diff --git a/lib/compiler/test/lfe_andor_SUITE.core b/lib/compiler/test/lfe_andor_SUITE.core
index df58b39ae6..e8cb0919a0 100644
--- a/lib/compiler/test/lfe_andor_SUITE.core
+++ b/lib/compiler/test/lfe_andor_SUITE.core
@@ -34,6 +34,8 @@ module 'lfe_andor_SUITE' ['$handle_undefined_function'/2,
'init_per_suite'/1 =
%% Line 48
fun (_config) ->
+ do
+ call 'test_lib':'recompile_core'('lfe_andor_SUITE')
_config
'end_per_suite'/1 =
%% Line 50
diff --git a/lib/compiler/test/lfe_guard_SUITE.core b/lib/compiler/test/lfe_guard_SUITE.core
index 920be82f61..9d184ed166 100644
--- a/lib/compiler/test/lfe_guard_SUITE.core
+++ b/lib/compiler/test/lfe_guard_SUITE.core
@@ -53,6 +53,8 @@ module 'lfe_guard_SUITE' ['$handle_undefined_function'/2,
'init_per_suite'/1 =
%% Line 62
fun (_config) ->
+ do
+ call 'test_lib':'recompile_core'('lfe_guard_SUITE')
_config
'end_per_suite'/1 =
%% Line 64
diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl
index 2bcb6133da..36eb9755d2 100644
--- a/lib/compiler/test/map_SUITE.erl
+++ b/lib/compiler/test/map_SUITE.erl
@@ -74,7 +74,10 @@
%% new in OTP 22
t_mixed_clause/1,cover_beam_trim/1,
- t_duplicate_keys/1
+ t_duplicate_keys/1,
+
+ %% new in OTP 23
+ t_key_expressions/1
]).
suite() -> [].
@@ -132,7 +135,10 @@ all() ->
%% new in OTP 22
t_mixed_clause,cover_beam_trim,
- t_duplicate_keys
+ t_duplicate_keys,
+
+ %% new in OTP 23
+ t_key_expressions
].
groups() -> [].
@@ -973,6 +979,24 @@ t_update_assoc(Config) when is_list(Config) ->
BadMap = id(badmap),
{'EXIT',{{badmap,BadMap},_}} = (catch BadMap#{nonexisting=>val}),
{'EXIT',{{badmap,<<>>},_}} = (catch <<>>#{nonexisting=>val}),
+ F1 = fun() ->
+ 0 #{part => V = false},
+ V
+ end,
+ {'EXIT',{{badmap,0},_}} = (catch F1()),
+ F2 = fun() ->
+ case 42 of
+ V ->
+ [];
+ power ->
+ []#{key =>
+ receive
+ V -> false
+ end}
+ end,
+ V
+ end,
+ 42 = F2(),
%% Evaluation order.
{'EXIT',{blurf,_}} =
@@ -1971,6 +1995,15 @@ t_nested_pattern_expressions(Config) when is_list(Config) ->
F1 = F0(wat),
F2 = F1(watzor),
{yep,ok} = F2(M0),
+
+ %% Test matching of nested maps. There used to be an unsafe optimization in beam_peep.
+ #{ <<"result">> := #{<<"foo">> := <<"6">> } } =
+ nested_map(),
+
+ InnerMap = #{a => id({a,value})},
+ OuterMap = #{inner_map => InnerMap},
+ #{inner_map := #{a := {a,value}}} = OuterMap,
+
ok.
map_nested_pattern_funs(M) ->
@@ -1990,6 +2023,10 @@ map_nested_pattern_funs(M) ->
end
end.
+nested_map() ->
+ #{ <<"result">> := #{<<"foo">> := <<"6">> } } =
+ #{ <<"result">> => #{<<"foo">> => <<"6">> } }.
+
t_guard_update_variables(Config) when is_list(Config) ->
error = map_guard_update_variables(n,#{},#{}),
first = map_guard_update_variables(x,#{}, #{x=>first}),
@@ -2193,6 +2230,92 @@ do_cover_beam_trim(Id, OldMax, Max, Id, M) ->
#{Id:=Val} = id(M),
Val.
+t_key_expressions(_Config) ->
+ Int = id(42),
+ #{{tag,Int} := 42} = id(#{{tag,Int} => 42}),
+ #{{tag,Int+1} := 42} = id(#{{tag,Int+1} => 42}),
+ #{{a,b} := x, {tag,Int} := 42, Int := 0} =
+ id(#{{a,b} => x, {tag,Int} => 42, Int => 0}),
+
+ F1 = fun(#{Int + 1 := Val}) -> Val end,
+ val = F1(#{43 => val}),
+ {'EXIT',_} = (catch F1(a)),
+
+ F2 = fun(M, X, Y) ->
+ case M of
+ #{element(X, Y) := <<Sz:16,Bin:Sz/binary>>} ->
+ Bin;
+ #{} ->
+ not_found;
+ {A,B} ->
+ A + B
+ end
+ end,
+ <<"xyz">> = F2(#{b => <<3:16,"xyz">>}, 2, {a,b,c}),
+ not_found = F2(#{b => <<3:16,"xyz">>}, 999, {a,b,c}),
+ 13 = F2({6,7}, 1, 2),
+
+ #{<<"Спутник"/utf8>> := 1} = id(#{<<"Спутник"/utf8>> => 1}),
+
+ F3 = fun(Arg) ->
+ erase(once),
+ RunOnce = fun(I) ->
+ undefined = put(once, twice),
+ id(I)
+ end,
+ case RunOnce(Arg) of
+ #{{tag,<<Int:42>>} := Value} -> Value;
+ {X,Y} -> X + Y
+ end
+ end,
+ 10 = F3({7,3}),
+ whatever = F3(#{{tag,<<Int:42>>} => whatever}),
+
+ F4 = fun(K1, K2, M) ->
+ case M of
+ #{K1 div K2 := V} -> V;
+ #{} -> no_match
+ end
+ end,
+ value = F4(42, 21, #{2 => value}),
+ no_match = F4(42, 21, #{}),
+ no_match = F4(42, 0, #{2 => value}),
+ no_match = F4(42, a, #{2 => value}),
+
+ F5 = fun(Term) ->
+ self() ! Term,
+ receive
+ #{[<<(3 bsr 30 + 2):0,$k:[]/signed-integer>>] := _} ->
+ ok;
+ 0.5 ->
+ error
+ end
+ end,
+ error = F5(0.5),
+
+ F6 = fun(Term) ->
+ self() ! Term,
+ receive
+ #{<<a/utf8>> := {a,b,c}} -> ok;
+ Other -> {error,Other}
+ end
+ end,
+ {error,any} = F6(any),
+
+ F7 = fun(Term) ->
+ self() ! Term,
+ (?MODULE:all()):a(catch
+ receive
+ <<1.14:{<<"a":(tuple_size(1))>>}>> ->
+ 4;
+ Other ->
+ Other
+ end)
+ end,
+ {'EXIT',{badarg,_}} = (catch F7(whatever)),
+
+ ok.
+
t_duplicate_keys(Config) when is_list(Config) ->
Map = #{ gurka => gaffel },
Map = dup_keys_1(id(Map)),
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index bc74ec4984..e354c88730 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -26,7 +26,7 @@
selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1,
coverage/1,grab_bag/1,literal_binary/1,
unary_op/1,eq_types/1,match_after_return/1,match_right_tuple/1,
- tuple_size_in_try/1]).
+ tuple_size_in_try/1,match_boolean_list/1]).
-include_lib("common_test/include/ct.hrl").
@@ -43,7 +43,7 @@ groups() ->
underscore,match_map,map_vars_used,coverage,
grab_bag,literal_binary,unary_op,eq_types,
match_after_return,match_right_tuple,
- tuple_size_in_try]}].
+ tuple_size_in_try,match_boolean_list]}].
init_per_suite(Config) ->
@@ -91,6 +91,7 @@ mixed(Config) when is_list(Config) ->
{error,blurf} = mixit(5),
{error,87987987} = mixit(6),
{error,{a,b,c}} = mixit(7),
+ no_match = mixed_1(),
ok.
mixit(X) ->
@@ -110,6 +111,17 @@ mixit(X) ->
Other -> {error,Other}
end.
+mixed_1() ->
+ case 0 of
+ 0.0 ->
+ %% This clause must not match.
+ zero;
+ 0.5 ->
+ half;
+ _ ->
+ no_match
+ end.
+
aliases(Config) when is_list(Config) ->
%% Lists/strings.
ok = str_alias("abc"),
@@ -148,6 +160,31 @@ aliases(Config) when is_list(Config) ->
{a,b} = list_alias2([a,b]),
{a,b} = list_alias3([a,b]),
+ %% Multiple matches.
+ {'EXIT',{{badmatch,home},_}} =
+ (catch fun() ->
+ Rec = (42 = V) = home,
+ {Rec,V}
+ end()),
+ {home,home} =
+ fun() ->
+ Rec = (home = V) = home,
+ {Rec,V}
+ end(),
+ {'EXIT',{{badmatch,16},_}} =
+ (catch fun(B) ->
+ <<42:V>> = V = B
+ end(16)),
+ {'EXIT',{{badmatch,0},_}} =
+ (catch fun() ->
+ <<2:V>> = V = 0
+ end()),
+ {42,42} =
+ fun(E) ->
+ Rec = (42 = V) = id(E),
+ {Rec,V}
+ end(42),
+
ok.
str_alias(V) ->
@@ -260,6 +297,7 @@ non_matching_aliases(_Config) ->
none = mixed_aliases(<<6789:16>>),
none = mixed_aliases(#{key=>value}),
+ {'EXIT',{{badmatch,bar},_}} = (catch plus_plus_prefix()),
{'EXIT',{{badmatch,42},_}} = (catch nomatch_alias(42)),
{'EXIT',{{badmatch,job},_}} = (catch entirely()),
{'EXIT',{{badmatch,associates},_}} = (catch printer()),
@@ -278,6 +316,10 @@ non_matching_aliases(_Config) ->
1 = erase(shark),
{'EXIT',{{badmatch,_},_}} = (catch radio(research)),
+
+ {'EXIT',{{case_clause,whatever},_}} = (catch pike1(whatever)),
+ {'EXIT',{{case_clause,whatever},_}} = (catch pike2(whatever)),
+
ok.
mixed_aliases(<<X:8>> = x) -> {a,X};
@@ -294,8 +336,12 @@ mixed_aliases([X] = #{key:=X}) -> {k,X};
mixed_aliases(#{key:=X} = [X]) -> {l,X};
mixed_aliases({a,X} = #{key:=X}) -> {m,X};
mixed_aliases(#{key:=X} = {a,X}) -> {n,X};
+mixed_aliases([] ++ (foo = [])) -> o;
mixed_aliases(_) -> none.
+plus_plus_prefix() ->
+ [] ++ (foo = []) = bar.
+
nomatch_alias(I) ->
{ok={A,B}} = id(I),
{A,B}.
@@ -336,6 +382,26 @@ radio(research) ->
(catch erlang:trace_pattern(catch mechanisms + assist,
summary = mechanisms)).
+pike1(X) ->
+ case id([]) of
+ [] ->
+ case X of
+ [Var] = [] ->
+ ok
+ end
+ end,
+ Var.
+
+pike2(X) ->
+ case id([]) of
+ [] ->
+ case X of
+ [_] = [] ->
+ Var = 42
+ end
+ end,
+ Var.
+
%% OTP-7018.
match_in_call(Config) when is_list(Config) ->
@@ -939,4 +1005,17 @@ tsit(A) ->
_:_ -> ok
end.
+match_boolean_list(Config) when is_list(Config) ->
+ BoolList = [N rem 2 =:= 0 || N <- lists:seq(1, 8)],
+ %% The compiler knows that all list elements are booleans, so it translates
+ %% the expression below to a #b_br{} on the list head.
+ %%
+ %% This is fine, but since the value was only used in that branch,
+ %% reserve_zregs/3 (pre_codegen) would place the variable in a z register,
+ %% crashing the compiler in a later pass.
+ ok = case BoolList of
+ [true | _] -> error;
+ [false | _] -> ok
+ end.
+
id(I) -> I.
diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl
index 2a23ce7679..bc07099915 100644
--- a/lib/compiler/test/misc_SUITE.erl
+++ b/lib/compiler/test/misc_SUITE.erl
@@ -164,6 +164,11 @@ md5_1(Beam) ->
%% Cover some code that handles internal errors.
silly_coverage(Config) when is_list(Config) ->
+ %% v3_core
+ BadAbstr = [{attribute,0,module,bad_module},
+ {function,0,foo,2,[bad_clauses]}],
+ expect_error(fun() -> v3_core:module(BadAbstr, []) end),
+
%% sys_core_fold, sys_core_alias, sys_core_bsm, v3_kernel
BadCoreErlang = {c_module,[],
name,[],[],
@@ -184,6 +189,7 @@ silly_coverage(Config) when is_list(Config) ->
expect_error(fun() -> beam_kernel_to_ssa:module(BadKernel, []) end),
%% beam_ssa_lint
+ %% beam_ssa_bool
%% beam_ssa_recv
%% beam_ssa_share
%% beam_ssa_pre_codegen
@@ -191,6 +197,7 @@ silly_coverage(Config) when is_list(Config) ->
BadSSA = {b_module,#{},a,b,c,
[{b_function,#{func_info=>{mod,foo,0}},args,bad_blocks,0}]},
expect_error(fun() -> beam_ssa_lint:module(BadSSA, []) end),
+ expect_error(fun() -> beam_ssa_bool:module(BadSSA, []) end),
expect_error(fun() -> beam_ssa_recv:module(BadSSA, []) end),
expect_error(fun() -> beam_ssa_share:module(BadSSA, []) end),
expect_error(fun() -> beam_ssa_pre_codegen:module(BadSSA, []) end),
@@ -198,7 +205,7 @@ silly_coverage(Config) when is_list(Config) ->
%% beam_ssa_opt
BadSSABlocks = #{0 => {b_blk,#{},[bad_code],{b_ret,#{},arg}}},
- BadSSAOpt = {b_module,#{},a,[],c,
+ BadSSAOpt = {b_module,#{},a,[],[],
[{b_function,#{func_info=>{mod,foo,0}},[],
BadSSABlocks,0}]},
expect_error(fun() -> beam_ssa_opt:module(BadSSAOpt, []) end),
@@ -230,15 +237,6 @@ silly_coverage(Config) when is_list(Config) ->
{label,2}|non_proper_list]}],99},
expect_error(fun() -> beam_block:module(BlockInput, []) end),
- %% beam_except
- ExceptInput = {?MODULE,[{foo,0}],[],
- [{function,foo,0,2,
- [{label,1},
- {line,loc},
- {func_info,{atom,?MODULE},{atom,foo},0},
- {label,2}|non_proper_list]}],99},
- expect_error(fun() -> beam_except:module(ExceptInput, []) end),
-
%% beam_jump
JumpInput = BlockInput,
expect_error(fun() -> beam_jump:module(JumpInput, []) end),
@@ -280,20 +278,40 @@ silly_coverage(Config) when is_list(Config) ->
[{label,1},
{func_info,{atom,?MODULE},{atom,foo},0},
{label,2}|non_proper_list]}],99},
- expect_error(fun() -> beam_validator:module(BeamValInput, []) end),
+ expect_error(fun() -> beam_validator:validate(BeamValInput, strong) end),
ok.
bad_ssa_lint_input() ->
{b_module,#{},t,
- [{foobar,1},{module_info,0},{module_info,1}],
+ [{a,1},{b,1},{c,1},{module_info,0},{module_info,1}],
[],
[{b_function,
- #{func_info => {t,foobar,1},location => {"t.erl",4}},
+ #{func_info => {t,a,1},location => {"t.erl",4}},
[{b_var,0}],
#{0 => {b_blk,#{},[],{b_ret,#{},{b_var,'@undefined_var'}}}},
3},
{b_function,
+ #{func_info => {t,b,1},location => {"t.erl",5}},
+ [{b_var,0}],
+ #{0 =>
+ {b_blk,#{},
+ [{b_set,#{},{b_var,'@first_var'},first_op,[]},
+ {b_set,#{},{b_var,'@second_var'},second_op,[]},
+ {b_set,#{},{b_var,'@ret'},succeeded,[{b_var,'@first_var'}]}],
+ {b_ret,#{},{b_var,'@ret'}}}},
+ 3},
+ {b_function,
+ #{func_info => {t,c,1},location => {"t.erl",6}},
+ [{b_var,0}],
+ #{0 =>
+ {b_blk,#{},
+ [{b_set,#{},{b_var,'@first_var'},first_op,[]},
+ {b_set,#{},{b_var,'@ret'},succeeded,[{b_var,'@first_var'}]},
+ {b_set,#{},{b_var,'@second_var'},second_op,[]}],
+ {b_ret,#{},{b_var,'@ret'}}}},
+ 3},
+ {b_function,
#{func_info => {t,module_info,0}},
[],
#{0 =>
diff --git a/lib/compiler/test/property_test/beam_types_prop.erl b/lib/compiler/test/property_test/beam_types_prop.erl
new file mode 100644
index 0000000000..1e5da10aa0
--- /dev/null
+++ b/lib/compiler/test/property_test/beam_types_prop.erl
@@ -0,0 +1,315 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(beam_types_prop).
+
+-compile([export_all, nowarn_export_all]).
+
+%% This module only supports proper, as we don't have an eqc license to test
+%% with.
+
+-proptest([proper]).
+
+-ifdef(PROPER).
+
+-define(BEAM_TYPES_INTERNAL, true).
+-include_lib("compiler/src/beam_types.hrl").
+
+-include_lib("proper/include/proper.hrl").
+-define(MOD_eqc,proper).
+
+-import(lists, [duplicate/2,foldl/3]).
+
+%% The default repetitions of 100 is a bit too low to reliably cover all type
+%% combinations, so we crank it up a bit.
+-define(REPETITIONS, 5000).
+
+absorption() ->
+ numtests(?REPETITIONS, absorption_1()).
+
+absorption_1() ->
+ ?FORALL({TypeA, TypeB},
+ ?LET(TypeA, type(),
+ ?LET(TypeB, type(), {TypeA, TypeB})),
+ absorption_check(TypeA, TypeB)).
+
+absorption_check(A, B) ->
+ verified_type(A),
+ verified_type(B),
+
+ %% a ∨ (a ∧ b) = a
+ A = join(A, meet(A, B)),
+
+ %% a ∧ (a ∨ b) = a
+ A = meet(A, join(A, B)),
+
+ true.
+
+associativity() ->
+ numtests(?REPETITIONS, associativity_1()).
+
+associativity_1() ->
+ ?FORALL({TypeA, TypeB, TypeC},
+ ?LET(TypeA, type(),
+ ?LET(TypeB, type(),
+ ?LET(TypeC, type(), {TypeA, TypeB, TypeC}))),
+ associativity_check(TypeA, TypeB, TypeC)).
+
+associativity_check(A, B, C) ->
+ verified_type(A),
+ verified_type(B),
+ verified_type(C),
+
+ %% a ∨ (b ∨ c) = (a ∨ b) ∨ c
+ LHS_Join = join(A, join(B, C)),
+ RHS_Join = join(join(A, B), C),
+ LHS_Join = RHS_Join,
+
+ %% a ∧ (b ∧ c) = (a ∧ b) ∧ c
+ LHS_Meet = meet(A, meet(B, C)),
+ RHS_Meet = meet(meet(A, B), C),
+ LHS_Meet = RHS_Meet,
+
+ true.
+
+commutativity() ->
+ numtests(?REPETITIONS, commutativity_1()).
+
+commutativity_1() ->
+ ?FORALL({TypeA, TypeB},
+ ?LET(TypeA, type(),
+ ?LET(TypeB, type(), {TypeA, TypeB})),
+ commutativity_check(TypeA, TypeB)).
+
+commutativity_check(A, B) ->
+ verified_type(A),
+ verified_type(B),
+
+ %% a ∨ b = b ∨ a
+ true = join(A, B) =:= join(B, A),
+
+ %% a ∧ b = b ∧ a
+ true = meet(A, B) =:= meet(B, A),
+
+ true.
+
+idempotence() ->
+ numtests(?REPETITIONS, idempotence_1()).
+
+idempotence_1() ->
+ ?FORALL(Type, type(), idempotence_check(Type)).
+
+idempotence_check(Type) ->
+ verified_type(Type),
+
+ %% a ∨ a = a
+ Type = join(Type, Type),
+
+ %% a ∧ a = a
+ Type = meet(Type, Type),
+
+ true.
+
+identity() ->
+ ?FORALL(Type, type(), identity_check(Type)).
+
+identity_check(Type) ->
+ verified_type(Type),
+
+ %% a ∨ [bottom element] = a
+ Type = join(Type, none),
+
+ %% a ∧ [top element] = a
+ Type = meet(Type, any),
+
+ true.
+
+subtraction() ->
+ numtests(?REPETITIONS, subtraction_1()).
+
+subtraction_1() ->
+ ?FORALL({TypeA, TypeB},
+ ?LET(TypeA, type(),
+ ?LET(TypeB, type(), {TypeA, TypeB})),
+ subtraction_check(TypeA, TypeB)).
+
+subtraction_check(A, B) ->
+ verified_type(A),
+ verified_type(B),
+
+ %% Subtraction can be thought of as `a ∧ ¬b`, so the result must be at
+ %% least as specific as `a`.
+ Res = subtract(A, B),
+ Res = meet(A, Res),
+
+ true.
+
+meet(A, B) -> beam_types:meet(A, B).
+join(A, B) -> beam_types:join(A, B).
+subtract(A, B) -> beam_types:subtract(A, B).
+verified_type(T) -> beam_types:verified_type(T).
+
+%%%
+%%% Generators
+%%%
+
+type() ->
+ type(?MAX_TYPE_DEPTH).
+
+type(Depth) ->
+ ?SHRINK(?LAZY(oneof([any, none] ++ term_types(Depth))),
+ [nil, any, none]).
+
+term_type(Depth) ->
+ ?SHRINK(?LAZY(oneof([any | term_types(Depth)])),
+ [nil, any]).
+
+term_types(Depth) ->
+ nested_generators(Depth) ++
+ numerical_generators() ++
+ [gen_atom(), gen_bs_matchable()].
+
+numerical_generators() ->
+ [gen_integer(), gen_float(), number].
+
+nested_generators(Depth) when Depth =< 0 ->
+ [nil];
+nested_generators(Depth) ->
+ [gen_list(Depth - 1),
+ gen_fun(Depth - 1),
+ gen_map(Depth - 1),
+ ?LAZY(gen_tuple(Depth - 1)),
+ ?LAZY(gen_union(Depth - 1))].
+
+%% Proper's atom generator is far too wide, generating strings like 'û\2144Bò}'
+%% which are both hard to read and fill up the atom table really fast.
+readable_atom() ->
+ ?LET(Atom, range($0, $~), list_to_atom([Atom])).
+
+%%
+
+gen_atom() ->
+ ?LET(Size, range(0, ?ATOM_SET_SIZE),
+ ?LET(Set, duplicate(Size, readable_atom()),
+ case ordsets:from_list(Set) of
+ [_|_]=Vs -> #t_atom{elements=ordsets:from_list(Vs)};
+ [] -> #t_atom{}
+ end)).
+
+gen_bs_matchable() ->
+ oneof([?LET(Unit, range(1, 16), #t_bs_matchable{tail_unit=Unit}),
+ ?LET(Unit, range(1, 16), #t_bs_context{tail_unit=Unit}),
+ ?LET(Unit, range(1, 16), #t_bitstring{size_unit=Unit})]).
+
+gen_float() ->
+ oneof([?LET({A, B}, {integer(), integer()},
+ begin
+ Min = float(min(A,B)),
+ Max = float(max(A,B)),
+ #t_float{elements={Min,Max}}
+ end),
+ #t_float{}]).
+
+gen_fun(Depth) ->
+ ?SHRINK(?LET({Type, Arity}, {type(Depth), oneof([any, range(1, 4)])},
+ #t_fun{type=Type,arity=Arity}),
+ [#t_fun{}]).
+
+gen_integer() ->
+ oneof([?LET({A, B}, {integer(), integer()},
+ #t_integer{elements={min(A,B), max(A,B)}}),
+ #t_integer{}]).
+
+gen_list(Depth) ->
+ ?SHRINK(oneof([?LET({Type, Term}, {term_type(Depth), term_type(Depth)},
+ #t_list{type=Type,terminator=Term}),
+ ?LET({Type, Term}, {term_type(Depth), term_type(Depth)},
+ #t_cons{type=Type,terminator=Term}),
+ nil]),
+ [nil]).
+
+gen_map(Depth) ->
+ ?SHRINK(?LET({SKey, SValue}, {term_type(Depth), term_type(Depth)},
+ #t_map{super_key=SKey,super_value=SValue}),
+ [#t_map{}]).
+
+gen_tuple(Depth) ->
+ ?SHRINK(oneof([gen_tuple_plain(Depth), gen_tuple_record(Depth)]),
+ [#t_tuple{}]).
+
+gen_tuple_record(Depth) ->
+ ?LET({Start, Size}, {range(2, ?TUPLE_ELEMENT_LIMIT),
+ range(1, ?TUPLE_ELEMENT_LIMIT * 2)},
+ ?LET({Tag, Es0}, {readable_atom(),
+ gen_tuple_elements(Start, Size, Depth)},
+ begin
+ Es = Es0#{ 1 => #t_atom{elements=[Tag]} },
+ #t_tuple{exact=true,size=Size,elements=Es}
+ end)).
+
+gen_tuple_plain(Depth) ->
+ ?LET({Start, Size}, {range(1, ?TUPLE_ELEMENT_LIMIT),
+ range(0, ?TUPLE_ELEMENT_LIMIT * 2)},
+ ?LET({Exact, Es}, {boolean(), gen_tuple_elements(Start, Size, Depth)},
+ #t_tuple{exact=Exact,size=Size,elements=Es})).
+
+gen_tuple_elements(Start, Size, Depth) ->
+ End = min(Size, ?TUPLE_ELEMENT_LIMIT),
+ ?SHRINK(?LET(Types, gen_tuple_elements_1(Start, End, term_type(Depth)),
+ foldl(fun({Index, Type}, Acc) ->
+ beam_types:set_tuple_element(Index, Type, Acc)
+ end, #{}, Types)),
+ [#{}]).
+
+gen_tuple_elements_1(Index, End, _Gen) when Index > End ->
+ [];
+gen_tuple_elements_1(Index, End, Gen) ->
+ case rand:uniform(2) of
+ 1 -> [{Index, Gen} | gen_tuple_elements_1(Index + 1, End, Gen)];
+ 2 -> gen_tuple_elements_1(Index + 1, End, Gen)
+ end.
+
+gen_union(Depth) ->
+ ?SHRINK(oneof([gen_union_wide(Depth), gen_union_record(Depth)]),
+ [gen_union_record(?MAX_TYPE_DEPTH)]).
+
+%% Creates a union with most (if not all) slots filled.
+gen_union_wide(Depth) ->
+ ?LET({A, B, C, D, E, F}, {gen_atom(),
+ gen_bs_matchable(),
+ gen_list(Depth),
+ gen_tuple(Depth),
+ oneof(nested_generators(Depth)),
+ oneof(numerical_generators())},
+ begin
+ T0 = join(A, B),
+ T1 = join(T0, C),
+ T2 = join(T1, D),
+ T3 = join(T2, E),
+ join(T3, F)
+ end).
+
+%% Creates a union consisting solely of records
+gen_union_record(Depth) ->
+ ?LET(Size, range(2, ?TUPLE_SET_LIMIT),
+ ?LET(Tuples, duplicate(Size, gen_tuple_record(Depth)),
+ foldl(fun join/2, none, Tuples))).
+
+-endif.
diff --git a/lib/compiler/test/property_test/compile_prop.erl b/lib/compiler/test/property_test/compile_prop.erl
new file mode 100644
index 0000000000..5fdd6409d6
--- /dev/null
+++ b/lib/compiler/test/property_test/compile_prop.erl
@@ -0,0 +1,59 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(compile_prop).
+-compile([export_all, nowarn_export_all]).
+
+%% This module only supports proper, as we don't have an eqc license to test
+%% with.
+
+-proptest([proper]).
+
+-ifdef(PROPER).
+
+-define(BEAM_TYPES_INTERNAL, true).
+-include_lib("compiler/src/beam_types.hrl").
+
+-include_lib("proper/include/proper.hrl").
+-define(MOD_eqc, proper).
+
+-import(lists, [duplicate/2,foldl/3]).
+
+-define(REPETITIONS, 1000).
+
+compile() ->
+ numtests(?REPETITIONS, compile_1()).
+
+compile_1() ->
+ Opts = [{resize,true}],
+ ?FORALL(Abstr, proper_abstr:module(Opts),
+ ?WHENFAIL(
+ begin
+ io:format("~ts\n", [[erl_pp:form(F) || F <- Abstr]]),
+ compile(Abstr, [binary,report_errors])
+ end,
+ case compile(Abstr, [binary]) of
+ {error, _Es, _Ws} -> false;
+ _ -> true
+ end)).
+
+compile(Abstr, Opts) ->
+ compile:noenv_forms(Abstr, Opts).
+-endif.
diff --git a/lib/compiler/test/random_code_SUITE.erl b/lib/compiler/test/random_code_SUITE.erl
new file mode 100644
index 0000000000..747a9aebd1
--- /dev/null
+++ b/lib/compiler/test/random_code_SUITE.erl
@@ -0,0 +1,57 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(random_code_SUITE).
+
+-export([all/0, suite/0, groups/0,
+ init_per_suite/1, end_per_suite/1]).
+
+-export([compile/1]).
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group,property_tests}].
+
+groups() ->
+ [{property_tests,[parallel],
+ [compile]}].
+
+init_per_suite(Config0) ->
+ case ct_property_test:init_per_suite(Config0) of
+ [_|_]=Config ->
+ try proper_abstr:module() of
+ _ ->
+ Config
+ catch
+ error:undef ->
+ {skip,"No proper_abstr module"}
+ end;
+ Other ->
+ Other
+ end.
+
+end_per_suite(Config) ->
+ Config.
+
+compile(Config) ->
+ true = ct_property_test:quickcheck(compile_prop:compile(), Config),
+ ok.
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index ecc9493648..09404930bf 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -27,7 +27,8 @@
export/1,recv/1,coverage/1,otp_7980/1,ref_opt/1,
wait/1,recv_in_try/1,double_recv/1,receive_var_zero/1,
match_built_terms/1,elusive_common_exit/1,
- return_before_receive/1,trapping/1]).
+ return_before_receive/1,trapping/1,
+ after_expression/1,in_after/1]).
-include_lib("common_test/include/ct.hrl").
@@ -49,7 +50,8 @@ groups() ->
[recv,coverage,otp_7980,export,wait,
recv_in_try,double_recv,receive_var_zero,
match_built_terms,elusive_common_exit,
- return_before_receive,trapping]},
+ return_before_receive,trapping,
+ after_expression,in_after]},
{slow,[],[ref_opt]}].
init_per_suite(Config) ->
@@ -94,6 +96,8 @@ recv(Config) when is_list(Config) ->
io:format("Unexpected extra message: ~p", [X]),
ct:fail(unexpected)
after 10 ->
+ unlink(Pid),
+ exit(Pid, kill),
ok
end,
ok.
@@ -136,6 +140,16 @@ coverage(Config) when is_list(Config) ->
{'EXIT',{{badmap,[]},_}} = (catch monitor_plus_badmap(self())),
+
+ self() ! {data,no_data},
+ ok = receive_sink_tuple({any,pattern}),
+ {b,a} = receive_sink_tuple({a,b}),
+
+ %% Basically a smoke test of no_clauses_left/0.
+ smoke_receive(fun no_clauses_left_1/0),
+ smoke_receive(fun no_clauses_left_2/0),
+ smoke_receive(fun no_clauses_left_3/0),
+
ok.
monitor_plus_badmap(Pid) ->
@@ -184,6 +198,44 @@ tuple_to_values(Timeout, X) ->
end,
A+B.
+no_clauses_left_1() ->
+ receive
+ %% This clause would be removed because it cannot match...
+ a = b ->
+ V = whatever
+ end,
+ %% ... leaving a reference to an unbound variable. Crash.
+ V.
+
+no_clauses_left_2() ->
+ [receive
+ %% This clause would be removed because it cannot match...
+ a = <<V0:(node())>> ->
+ year
+ end],
+ %% ... leaving a reference to an unbound variable. Crash.
+ V0.
+
+no_clauses_left_3() ->
+ case id([]) of
+ [] ->
+ receive
+ [Var] = [] ->
+ ok
+ end
+ end,
+ Var.
+
+%% Cover a help function for beam_ssa_opt:ssa_opt_sink/1.
+receive_sink_tuple({Line,Pattern}) ->
+ receive
+ {data,_} ->
+ ok
+ after 1 ->
+ id({Pattern,Line})
+ end.
+
+
%% OTP-7980. Thanks to Vincent de Phily. The following code would
%% be inccorrectly optimized by beam_jump.
@@ -315,13 +367,19 @@ wait_1(A, B, C) ->
{A,B,C}.
recv_in_try(_Config) ->
- self() ! {ok,fh}, {ok,fh} = recv_in_try(infinity, native),
- self() ! {ok,ignored}, {ok,42} = recv_in_try(infinity, plain),
- self() ! {error,ignored}, nok = recv_in_try(infinity, plain),
- timeout = recv_in_try(1, plain),
+ self() ! {ok,fh}, {ok,fh} = recv_in_try_1(infinity, native),
+ self() ! {ok,ignored}, {ok,42} = recv_in_try_1(infinity, plain),
+ self() ! {error,ignored}, nok = recv_in_try_1(infinity, plain),
+ timeout = recv_in_try_1(1, plain),
+
+ smoke_receive(fun recv_in_try_2/0),
+ smoke_receive(fun recv_in_try_3/0),
+ smoke_receive(fun recv_in_try_4/0),
+ smoke_receive(fun recv_in_catch_1/0),
+
ok.
-recv_in_try(Timeout, Format) ->
+recv_in_try_1(Timeout, Format) ->
try
receive
{Status,History} ->
@@ -357,6 +415,47 @@ recv_in_try(Timeout, Format) ->
{nok,Reason}
end.
+recv_in_try_2() ->
+ try
+ %% The live range of the try tag would stop here because of
+ %% the infinite receive below. The code generator would
+ %% generate a kill instruction that would kill the try tag.
+ %% Although probably safe in practice, beam_validator does not
+ %% consider it safe.
+ _ = (catch try a after [] end),
+ receive after infinity -> ok end
+ after
+ []
+ end.
+
+recv_in_try_3() ->
+ #{make_ref() =>
+ not (catch
+ (catch 9 = kid)#{key =>
+ receive after infinity ->
+ ok
+ end})}.
+
+recv_in_try_4() ->
+ #{make_ref() =>
+ not (catch
+ (catch 9 = kid)#{key =>
+ receive
+ [] when false ->
+ ok
+ end})}.
+
+recv_in_catch_1() ->
+ catch
+ (catch
+ try
+ some_module
+ after
+ ok
+ end):some_function(receive
+ after infinity -> ok
+ end#{key := value}).
+
%% ERL-703. The compiler would crash because beam_utils:anno_defs/1
%% failed to take into account that code after loop_rec_end is
%% unreachable.
@@ -401,7 +500,9 @@ receive_var_zero(Config) when is_list(Config) ->
end,
self() ! w,
receive
- x -> ok;
+ x ->
+ receive y -> ok end,
+ receive w -> ok end;
Other ->
ct:fail({bad_message,Other})
end.
@@ -541,4 +642,61 @@ do_trapping(N) ->
receive Ref -> ok end,
receive after 1 -> ok end.
+after_expression(_Config) ->
+ self() ! {a,message},
+ {a,message} = after_expr(0),
+ timeout = after_expr(0),
+ timeout = after_expr(10),
+ ok = after_expr_timeout(0),
+ ok = after_expr_timeout(1),
+ ok.
+
+after_expr(Timeout) ->
+ receive
+ Msg -> Msg
+ after id(Timeout) ->
+ timeout
+ end.
+
+after_expr_timeout(Timeout) ->
+ receive
+ after id(Timeout) ->
+ ok
+ end.
+
+in_after(_Config) ->
+ self() ! first,
+ self() ! message,
+ do_in_after(fun() -> ok end),
+ do_in_after(fun() -> ok end),
+ self() ! message,
+ catch do_in_after(fun() -> error(bad) end),
+ catch do_in_after(fun() -> error(bad) end),
+ self() ! last,
+ first = receive M1 -> M1 end,
+ last = receive M2 -> M2 end,
+ ok.
+
+do_in_after(E) ->
+ try
+ E()
+ after
+ receive
+ message ->
+ ok
+ after 1 ->
+ ok
+ end
+ end,
+ ok.
+
+%%%
+%%% Common utilities.
+%%%
+
+smoke_receive(Fun) ->
+ NoClausesLeft = spawn(Fun),
+ receive after 1 -> ok end,
+ exit(NoClausesLeft, kill).
+
id(I) -> I.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_19.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_19.erl
new file mode 100644
index 0000000000..470bef54ae
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_19.erl
@@ -0,0 +1,15 @@
+-module(yes_19).
+-export([?MODULE/0,f/2]).
+
+?MODULE() ->
+ ok.
+
+f(Pid, Msg) ->
+ MyRef = make_ref(),
+ Pid ! Msg,
+ receive
+ {Ref,Reply} when Ref == MyRef ->
+ Reply
+ after 0 ->
+ ok
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_20.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_20.erl
new file mode 100644
index 0000000000..e85f1b99ca
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_20.erl
@@ -0,0 +1,16 @@
+-module(yes_20).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f() ->
+ Ref = spawn_request(fun () -> ok end),
+ Pid = receive
+ {spawn_reply, Ref, _, P} ->
+ P
+ end,
+ receive
+ {'DOWN', Ref, process, Pid, normal} ->
+ ok
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_21.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_21.erl
new file mode 100644
index 0000000000..5e0a92b10d
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_21.erl
@@ -0,0 +1,16 @@
+-module(yes_21).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f() ->
+ Ref = spawn_request(fun () -> ok end),
+ receive
+ {spawn_reply, Ref, _, _} ->
+ ok
+ end,
+ receive
+ {'DOWN', Ref, _, _, _} ->
+ ok
+ end.
diff --git a/lib/compiler/test/record_SUITE.erl b/lib/compiler/test/record_SUITE.erl
index 82db0dc936..03c85a36e7 100644
--- a/lib/compiler/test/record_SUITE.erl
+++ b/lib/compiler/test/record_SUITE.erl
@@ -589,6 +589,7 @@ nested_access(Config) when is_list(Config) ->
ok.
-record(rr, {a,b,c}).
+-record(fileheader, {read_md5,md5,eof,trailer}).
coverage(Config) when is_list(Config) ->
%% There should only remain one record test in the code below.
@@ -600,8 +601,23 @@ coverage(Config) when is_list(Config) ->
ok
end,
#rr{a=1,b=2,c=42} = id(R), %Test for correctness.
+
+ %% Cover beam_ssa_opt:ssa_opt_element/1 and friends.
+ error1 = check_file_header(#fileheader{read_md5=1,md5=2}),
+ error2 = check_file_header(#fileheader{trailer=true,eof=false}),
+ error3 = check_file_header(#fileheader{}),
+
ok.
+check_file_header(FH) ->
+ if
+ FH#fileheader.read_md5 =/= FH#fileheader.md5 ->
+ error1;
+ FH#fileheader.trailer =/= FH#fileheader.eof ->
+ error2;
+ true ->
+ error3
+ end.
-record(default_fun, {a = fun(X) -> X*X end}).
@@ -609,8 +625,9 @@ coverage(Config) when is_list(Config) ->
-record(gb_nil, {}).
-record(gb_foo, {hello=1}).
-record(gb_bar, {hello=2,there=3}).
+-record(gb_rh, {mod,mid}).
-%% Taken from compilation_SUITE.
+%% Taken from compilation_SUITE and other places.
grab_bag(_Config) ->
T1 = fun() ->
X = #foo{},
@@ -654,6 +671,23 @@ grab_bag(_Config) ->
end,
T4(),
+ %% Used to crash beam_ssa_bool during its development.
+ T5 = fun(RH) ->
+ if
+ is_record(RH, gb_rh) andalso
+ is_atom(RH#gb_rh.mod) andalso
+ RH#gb_rh.mid /= 42 -> ok;
+ true -> error
+ end
+ end,
+ ok = T5(#gb_rh{}),
+ ok = T5(#gb_rh{mod=atom,mid=0}),
+ error = T5(#gb_rh{mod=100,mid=0}),
+ error = T5(#gb_rh{mod=atom,mid=42}),
+ error = T5(#gb_nil{}),
+ error = T5(#gb_bar{}),
+ error = T5(atom),
+
ok.
%% ERIERL-436; the following code used to be very slow to compile.
diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl
index 1c97e992d6..1bf1040163 100644
--- a/lib/compiler/test/test_lib.erl
+++ b/lib/compiler/test/test_lib.erl
@@ -21,7 +21,8 @@
-include_lib("common_test/include/ct.hrl").
-compile({no_auto_import,[binary_part/2]}).
--export([id/1,recompile/1,parallel/0,uniq/0,opt_opts/1,get_data_dir/1,
+-export([id/1,recompile/1,recompile_core/1,parallel/0,
+ uniq/0,opt_opts/1,get_data_dir/1,
is_cloned_mod/1,smoke_disasm/1,p_run/2,
highest_opcode/1]).
@@ -45,6 +46,21 @@ recompile(Mod) when is_atom(Mod) ->
%% Smoke-test of beam disassembler.
smoke_disasm(Mod).
+recompile_core(Mod) when is_atom(Mod) ->
+ case whereis(cover_server) of
+ undefined -> ok;
+ _ ->
+ %% Re-compile the test suite if the cover server is running.
+ Beam = code:which(Mod),
+ Src = filename:rootname(Beam, ".beam"),
+ Opts = [bin_opt_info|opt_opts(Mod)],
+ io:format("Recompiling ~p (~p)\n", [Mod,Opts]),
+ c:c(Src, [from_core,{outdir,filename:dirname(Src)}|Opts])
+ end,
+
+ %% Smoke-test of beam disassembler.
+ smoke_disasm(Mod).
+
smoke_disasm(Mod) when is_atom(Mod) ->
smoke_disasm(code:which(Mod));
smoke_disasm(File) when is_list(File) ->
@@ -69,6 +85,7 @@ opt_opts(Mod) ->
{options,Opts} = lists:keyfind(options, 1, Comp),
lists:filter(fun
(debug_info) -> true;
+ (dialyzer) -> true;
(inline) -> true;
(no_bsm3) -> true;
(no_bsm_opt) -> true;
@@ -79,9 +96,11 @@ opt_opts(Mod) ->
(no_put_tuple2) -> true;
(no_recv_opt) -> true;
(no_share_opt) -> true;
+ (no_shared_fun_wrappers) -> true;
(no_ssa_float) -> true;
(no_ssa_opt) -> true;
(no_stack_trimming) -> true;
+ (no_swap) -> true;
(no_type_opt) -> true;
(_) -> false
end, Opts).
@@ -91,15 +110,20 @@ opt_opts(Mod) ->
%% This function retrieves the path to the original data directory.
get_data_dir(Config) ->
- Data0 = proplists:get_value(data_dir, Config),
+ Data = proplists:get_value(data_dir, Config),
Opts = [{return,list}],
- Data1 = re:replace(Data0, "_no_opt_SUITE", "_SUITE", Opts),
- Data2 = re:replace(Data1, "_post_opt_SUITE", "_SUITE", Opts),
- Data3 = re:replace(Data2, "_inline_SUITE", "_SUITE", Opts),
- Data4 = re:replace(Data3, "_r21_SUITE", "_SUITE", Opts),
- Data5 = re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts),
- Data = re:replace(Data5, "_no_type_opt_SUITE", "_SUITE", Opts),
- re:replace(Data, "_no_ssa_opt_SUITE", "_SUITE", Opts).
+ Suffixes = ["_no_opt_SUITE",
+ "_no_copt_SUITE",
+ "_post_opt_SUITE",
+ "_inline_SUITE",
+ "_r21_SUITE",
+ "_no_module_opt_SUITE",
+ "_no_type_opt_SUITE",
+ "_no_ssa_opt_SUITE"],
+ lists:foldl(fun(Suffix, Acc) ->
+ Opts = [{return,list}],
+ re:replace(Acc, Suffix, "_SUITE", Opts)
+ end, Data, Suffixes).
is_cloned_mod(Mod) ->
is_cloned_mod_1(atom_to_list(Mod)).
@@ -107,6 +131,7 @@ is_cloned_mod(Mod) ->
%% Test whether Mod is a cloned module.
is_cloned_mod_1("_no_opt_SUITE") -> true;
+is_cloned_mod_1("_no_copt_SUITE") -> true;
is_cloned_mod_1("_no_ssa_opt_SUITE") -> true;
is_cloned_mod_1("_post_opt_SUITE") -> true;
is_cloned_mod_1("_inline_SUITE") -> true;
diff --git a/lib/compiler/test/trycatch_SUITE.erl b/lib/compiler/test/trycatch_SUITE.erl
index 5c490ecef6..18926a23ee 100644
--- a/lib/compiler/test/trycatch_SUITE.erl
+++ b/lib/compiler/test/trycatch_SUITE.erl
@@ -28,7 +28,8 @@
plain_catch_coverage/1,andalso_orelse/1,get_in_try/1,
hockey/1,handle_info/1,catch_in_catch/1,grab_bag/1,
stacktrace/1,nested_stacktrace/1,raise/1,
- no_return_in_try_block/1]).
+ no_return_in_try_block/1,
+ coverage/1]).
-include_lib("common_test/include/ct.hrl").
@@ -45,7 +46,7 @@ groups() ->
bool,plain_catch_coverage,andalso_orelse,get_in_try,
hockey,handle_info,catch_in_catch,grab_bag,
stacktrace,nested_stacktrace,raise,
- no_return_in_try_block]}].
+ no_return_in_try_block,coverage]}].
init_per_suite(Config) ->
@@ -1051,8 +1052,69 @@ grab_bag(_Config) ->
%% Unnecessary catch.
22 = (catch 22),
+ fun() ->
+ F = grab_bag_1(any),
+ true = is_function(F, 1)
+ end(),
+
+ <<>> = grab_bag_2(whatever),
+
+ {'EXIT',_} = (catch grab_bag_3()),
+
ok.
+grab_bag_1(V) ->
+ %% V will be stored in y0.
+ try
+ receive
+ after 0 ->
+ %% y0 will be re-used for the catch tag.
+ %% This is safe, because there are no instructions
+ %% that can raise an exception.
+ catch 22
+ end,
+ %% beam_validator incorrectly assumed that the make_fun2
+ %% instruction could raise an exception and end up at
+ %% the catch part of the try.
+ fun id/1
+ catch
+ %% Never reached, because nothing in the try body raises any
+ %% exception.
+ _:V ->
+ ok
+ end.
+
+grab_bag_2(V) ->
+ try
+ %% y0 will be re-used for the catch tag.
+ %% This is safe, because there are no instructions
+ %% that can raise an exception.
+ catch 22,
+
+ %% beam_validator incorrectly assumed that the bs_init_writable
+ %% instruction could raise an exception and end up at
+ %% the catch part of the try.
+ <<0 || [], #{} <- []>>
+ catch
+ %% Never reached, because nothing in the try body raises any
+ %% exception.
+ error:_ ->
+ V
+ end.
+
+grab_bag_3() ->
+ try 2 of
+ true ->
+ <<
+ "" || [V0] = door
+ >>
+ catch
+ error:true:V0 ->
+ []
+ %% The default clause here (which re-throws the exception)
+ %% would not return two values as expected.
+ end =:= (V0 = 42).
+
stacktrace(_Config) ->
V = [make_ref()|self()],
case ?MODULE:module_info(native) of
@@ -1079,7 +1141,7 @@ stacktrace(_Config) ->
error:{badmatch,_}:Stk2 ->
[{?MODULE,stacktrace_2,0,_},
{?MODULE,stacktrace,1,_}|_] = Stk2,
- Stk2 = erlang:get_stacktrace(),
+ [] = erlang:get_stacktrace(),
ok
end,
@@ -1087,7 +1149,7 @@ stacktrace(_Config) ->
stacktrace_3(a, b)
catch
error:function_clause:Stk3 ->
- Stk3 = erlang:get_stacktrace(),
+ [] = erlang:get_stacktrace(),
case lists:module_info(native) of
false ->
[{lists,prefix,[a,b],_}|_] = Stk3;
@@ -1108,14 +1170,16 @@ stacktrace_1(X, C1, Y) ->
C1 -> value1
catch
C1:D1:Stk1 ->
- Stk1 = erlang:get_stacktrace(),
+ [] = erlang:get_stacktrace(),
{caught1,D1,Stk1}
after
foo(Y)
end of
V2 -> {value2,V2}
catch
- C2:D2:Stk2 -> {caught2,{C2,D2},Stk2=erlang:get_stacktrace()}
+ C2:D2:Stk2 ->
+ [] = erlang:get_stacktrace(),
+ {caught2,{C2,D2},Stk2}
end.
stacktrace_2() ->
@@ -1160,12 +1224,10 @@ nested_stacktrace_1({X1,C1,V1}, {X2,C2,V2}) ->
V1 -> value1
catch
C1:V1:S1 ->
- S1 = erlang:get_stacktrace(),
T2 = try foo(X2) of
V2 -> value2
catch
C2:V2:S2 ->
- S2 = erlang:get_stacktrace(),
{caught2,S2}
end,
{caught1,S1,T2}
@@ -1178,8 +1240,22 @@ raise(_Config) ->
badarg = bad_raise(fun() -> abs(id(x)) end),
+ error = stk_used_in_bin_size(<<0:42>>),
ok.
+stk_used_in_bin_size(Bin) ->
+ try
+ throw(fail)
+ catch
+ throw:fail:Stk ->
+ %% The compiler would crash because the building of the
+ %% stacktrack was sunk into each case arm.
+ case Bin of
+ <<0:Stk>> -> ok;
+ _ -> error
+ end
+ end.
+
bad_raise(Expr) ->
try
Expr()
@@ -1311,4 +1387,24 @@ no_return_in_try_block_1(H) ->
no_return() -> throw(no_return).
+coverage(_Config) ->
+ {'EXIT',{{badfun,true},[_|_]}} = (catch coverage_1()),
+ ok.
+
+%% Cover some code in beam_trim.
+coverage_1() ->
+ try
+ true
+ catch
+ law:business ->
+ program
+ after
+ head
+ end(0),
+ if
+ [2 or 1] ->
+ true
+ end.
+
+
id(I) -> I.
diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl
index 2902630da8..7fdc08eea2 100644
--- a/lib/compiler/test/warnings_SUITE.erl
+++ b/lib/compiler/test/warnings_SUITE.erl
@@ -121,7 +121,7 @@ pattern2(Config) when is_list(Config) ->
Ts = [{pattern2,
Source,
[nowarn_unused_vars],
- {warnings,[{2,sys_core_fold,{nomatch_shadow,1}},
+ {warnings,[{2,sys_core_fold,{nomatch_shadow,1,{f,1}}},
{4,sys_core_fold,no_clause_match},
{5,sys_core_fold,nomatch_clause_type},
{6,sys_core_fold,nomatch_clause_type}]}}],
@@ -270,6 +270,7 @@ bad_arith(Config) when is_list(Config) ->
{3,sys_core_fold,{eval_failure,badarith}},
{9,sys_core_fold,nomatch_guard},
{9,sys_core_fold,{eval_failure,badarith}},
+ {9,sys_core_fold,{no_effect,{erlang,is_integer,1}}},
{10,sys_core_fold,nomatch_guard},
{10,sys_core_fold,{eval_failure,badarith}},
{15,sys_core_fold,{eval_failure,badarith}}
@@ -621,8 +622,8 @@ maps(Config) when is_list(Config) ->
ok.
">>,
[],
- {warnings,[{3,v3_core,badmap}]}},
- {ok_map_literal_key,
+ {warnings,[{3,sys_core_fold,{eval_failure,badmap}}]}},
+ {ok_map_literal_key,
<<"
t() ->
V = id(1),
@@ -786,7 +787,7 @@ latin1_fallback(Conf) when is_list(Conf) ->
">>,
[],
{warnings,[{1,compile,reparsing_invalid_unicode},
- {3,sys_core_fold,{nomatch_shadow,2}}]}}],
+ {3,sys_core_fold,{nomatch_shadow,2,{t,1}}}]}}],
[] = run(Conf, Ts1),
Ts2 = [{latin1_fallback2,
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index 87d821d7e8..c628ece88c 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 7.5.4
+COMPILER_VSN = 7.6.7
diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile
index afe56aa7d6..e5812bee15 100644
--- a/lib/crypto/Makefile
+++ b/lib/crypto/Makefile
@@ -38,3 +38,4 @@ SPECIAL_TARGETS =
include $(ERL_TOP)/make/otp_subdir.mk
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in
index 129d0a7822..0821bd8d00 100644
--- a/lib/crypto/c_src/Makefile.in
+++ b/lib/crypto/c_src/Makefile.in
@@ -37,13 +37,14 @@ LIBS = @DED_LIBS@
LDFLAGS += @DED_LDFLAGS@
CFLAGS = @DED_CFLAGS@ @SSL_FLAGS@
-# From erts/configure
+# From configure
SSL_LIBDIR = @SSL_LIBDIR@
SSL_INCLUDE = @SSL_INCLUDE@
SSL_CRYPTO_LIBNAME = @SSL_CRYPTO_LIBNAME@
SSL_SSL_LIBNAME = @SSL_SSL_LIBNAME@
INCLUDES = $(SSL_INCLUDE) @DED_INCLUDE@
+SSL_EXTRA_LIBS=@SSL_EXTRA_LIBS@
ifeq ($(TYPE),debug)
TYPEMARKER = .debug
@@ -53,10 +54,22 @@ ifeq ($(TYPE),valgrind)
TYPEMARKER = .valgrind
TYPE_FLAGS = $(subst -O3,,$(subst -O2,,$(CFLAGS))) -DVALGRIND
else
+ifeq ($(TYPE),gprof)
+TYPEMARKER = .gprof
+TYPE_EXTRA_CFLAGS = -DGPROF -pg
+TYPE_FLAGS = $(CFLAGS) $(TYPE_EXTRA_CFLAGS)
+else
+ifeq ($(TYPE),asan)
+TYPEMARKER = .asan
+TYPE_FLAGS = $(CFLAGS) -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer -DADDRESS_SANITIZER
+LDFLAGS += -fsanitize=address
+else
TYPEMARKER =
TYPE_FLAGS = $(CFLAGS)
endif
endif
+endif
+endif
# ----------------------------------------------------
# Release directory specification
@@ -141,8 +154,10 @@ ifndef RANLIB
RANLIB=true
endif
-ALL_CFLAGS = $(TYPE_FLAGS) $(EXTRA_FLAGS) $(INCLUDES)
-ALL_STATIC_CFLAGS = @DED_STATIC_CFLAGS@ $(INCLUDES)
+CONFIGURE_ARGS = -DDISABLE_EVP_DH=@DISABLE_EVP_DH@ -DDISABLE_EVP_HMAC=@DISABLE_EVP_HMAC@
+
+ALL_CFLAGS = $(TYPE_FLAGS) $(EXTRA_FLAGS) $(CONFIGURE_ARGS) $(INCLUDES)
+ALL_STATIC_CFLAGS = @DED_STATIC_CFLAGS@ $(TYPE_EXTRA_CFLAGS) $(CONFIGURE_ARGS) $(INCLUDES)
# ----------------------------------------------------
# Targets
@@ -150,7 +165,7 @@ ALL_STATIC_CFLAGS = @DED_STATIC_CFLAGS@ $(INCLUDES)
_create_dirs := $(shell mkdir -p $(OBJDIR) $(LIBDIR))
-debug opt valgrind: $(NIF_LIB) $(CALLBACK_LIB) $(TEST_ENGINE_LIB)
+debug opt valgrind asan: $(NIF_LIB) $(CALLBACK_LIB) $(TEST_ENGINE_LIB)
static_lib: $(NIF_ARCHIVE)
@@ -164,7 +179,7 @@ $(LIBDIR)/otp_test_engine$(TYPEMARKER).so: $(TEST_ENGINE_OBJS)
$(LIBDIR)/otp_test_engine$(TYPEMARKER).dll: $(TEST_ENGINE_OBJS)
$(V_at)$(INSTALL_DIR) $(LIBDIR)
- $(V_LD) $(LDFLAGS) -o $@ $(SSL_DED_LD_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) $(TEST_ENGINE_OBJS) -l$(SSL_CRYPTO_LIBNAME) -l$(SSL_SSL_LIBNAME)
+ $(V_LD) $(LDFLAGS) -o $@ $(SSL_DED_LD_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) $(TEST_ENGINE_OBJS) -l$(SSL_CRYPTO_LIBNAME) -l$(SSL_SSL_LIBNAME) $(SSL_EXTRA_LIBS)
$(OBJDIR)/%$(TYPEMARKER).o: %.c
$(V_at)$(INSTALL_DIR) $(OBJDIR)
@@ -184,7 +199,7 @@ $(LIBDIR)/crypto$(TYPEMARKER).a: $(CRYPTO_STATIC_OBJS)
$(LIBDIR)/crypto$(TYPEMARKER).dll: $(CRYPTO_OBJS)
$(V_at)$(INSTALL_DIR) $(LIBDIR)
- $(V_LD) $(LDFLAGS) -o $@ $(SSL_DED_LD_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) $(CRYPTO_OBJS) -l$(SSL_CRYPTO_LIBNAME) -l$(SSL_SSL_LIBNAME)
+ $(V_LD) $(LDFLAGS) -o $@ $(SSL_DED_LD_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) $(CRYPTO_OBJS) -l$(SSL_CRYPTO_LIBNAME) -l$(SSL_SSL_LIBNAME) $(SSL_EXTRA_LIBS)
ifeq ($(DYNAMIC_CRYPTO_LIB),yes)
$(LIBDIR)/crypto_callback$(TYPEMARKER).so: $(CALLBACK_OBJS)
diff --git a/lib/crypto/c_src/algorithms.c b/lib/crypto/c_src/algorithms.c
index adabad77ea..f78fcd778b 100644
--- a/lib/crypto/c_src/algorithms.c
+++ b/lib/crypto/c_src/algorithms.c
@@ -18,21 +18,57 @@
* %CopyrightEnd%
*/
+#include "common.h"
#include "algorithms.h"
#include "cipher.h"
#include "mac.h"
static unsigned int algo_hash_cnt, algo_hash_fips_cnt;
static ERL_NIF_TERM algo_hash[14]; /* increase when extending the list */
+void init_hash_types(ErlNifEnv* env);
+
static unsigned int algo_pubkey_cnt, algo_pubkey_fips_cnt;
static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */
+void init_pubkey_types(ErlNifEnv* env);
+
static unsigned int algo_curve_cnt, algo_curve_fips_cnt;
-static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */
+static ERL_NIF_TERM algo_curve[2][89]; /* increase when extending the list */
+void init_curve_types(ErlNifEnv* env);
+
static unsigned int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt;
static ERL_NIF_TERM algo_rsa_opts[11]; /* increase when extending the list */
+void init_rsa_opts_types(ErlNifEnv* env);
+
+
+
void init_algorithms_types(ErlNifEnv* env)
{
+ init_hash_types(env);
+ init_pubkey_types(env);
+ init_curve_types(env);
+ init_rsa_opts_types(env);
+ /* ciphers and macs are initiated statically */
+}
+
+
+
+/*================================================================
+ Hash algorithms
+*/
+
+ERL_NIF_TERM hash_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned int cnt =
+#ifdef FIPS_SUPPORT
+ FIPS_mode() ? algo_hash_fips_cnt :
+#endif
+ algo_hash_cnt;
+
+ return enif_make_list_from_array(env, algo_hash, cnt);
+}
+
+void init_hash_types(ErlNifEnv* env) {
// Validated algorithms first
algo_hash_cnt = 0;
algo_hash[algo_hash_cnt++] = atom_sha;
@@ -77,6 +113,27 @@ void init_algorithms_types(ErlNifEnv* env)
algo_hash[algo_hash_cnt++] = enif_make_atom(env, "ripemd160");
#endif
+ ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM));
+}
+
+
+/*================================================================
+ Public key algorithms
+*/
+
+ERL_NIF_TERM pubkey_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned int cnt =
+#ifdef FIPS_SUPPORT
+ FIPS_mode() ? algo_pubkey_fips_cnt :
+#endif
+ algo_pubkey_cnt;
+
+ return enif_make_list_from_array(env, algo_pubkey, cnt);
+}
+
+void init_pubkey_types(ErlNifEnv* env) {
+ // Validated algorithms first
algo_pubkey_cnt = 0;
algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "rsa");
#ifdef HAVE_DSA
@@ -98,113 +155,506 @@ void init_algorithms_types(ErlNifEnv* env)
#if defined(HAVE_EDDSA)
algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "eddsa");
#endif
+#if defined(HAVE_EDDH)
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "eddh");
+#endif
algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp");
- // Validated algorithms first
- algo_curve_cnt = 0;
+ ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
+}
+
+
+/*================================================================
+ Cipher key algorithms
+*/
+
+ERL_NIF_TERM cipher_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ return cipher_types_as_list(env); /* Exclude old api ciphers */
+}
+
+
+/*================================================================
+ MAC key algorithms
+*/
+
+ERL_NIF_TERM mac_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ return mac_types_as_list(env);
+}
+
+
+/*================================================================
+ Curves
+*/
+
+ERL_NIF_TERM curve_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#ifdef FIPS_SUPPORT
+ if (FIPS_mode())
+ return enif_make_list_from_array(env, algo_curve[1], algo_curve_fips_cnt);
+ else
+#endif
+ return enif_make_list_from_array(env, algo_curve[0], algo_curve_cnt);
+}
+
#if defined(HAVE_EC)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp384r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp521r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime256v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls7");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls9");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls12");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512t1");
-#if !defined(OPENSSL_NO_EC2M)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect239k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb176v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb208w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb272w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb304w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb359v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb368w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb431r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls5");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls10");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls11");
+int init_curves(ErlNifEnv* env, int fips);
+int valid_curve(int nid);
+#endif
+
+void init_curve_types(ErlNifEnv* env) {
+ algo_curve_cnt = algo_curve_fips_cnt = 0;
+#if defined(HAVE_EC)
+#ifdef FIPS_SUPPORT
+ if (FIPS_mode()) {
+ // enabled
+ algo_curve_fips_cnt = init_curves(env, 1);
+ FIPS_mode_set(0); // disable
+ algo_curve_cnt = init_curves(env, 0);
+ FIPS_mode_set(1); // re-enable
+ } else {
+ // disabled
+ algo_curve_cnt = init_curves(env, 0);
+ FIPS_mode_set(1); // enable
+ algo_curve_fips_cnt = init_curves(env, 1);
+ FIPS_mode_set(0); // re-disable
+ }
+#else
+ // No fips support
+ algo_curve_cnt = algo_curve_fips_cnt = init_curves(env, 0);
+#endif
+#endif /* defined(HAVE_EC) */
+
+ ASSERT(algo_curve_cnt+algo_curve_fips_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM));
+}
+
+
+#if defined(HAVE_EC)
+int init_curves(ErlNifEnv* env, int fips) {
+ int cnt = 0;
+
+#ifdef NID_secp160k1
+ if (valid_curve(NID_secp160k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp160k1");
+#else
+#endif
+#ifdef NID_secp160r1
+ if (valid_curve(NID_secp160r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp160r1");
+#else
+#endif
+#ifdef NID_secp160r2
+ if (valid_curve(NID_secp160r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp160r2");
+#else
+#endif
+#ifdef NID_secp192k1
+ if (valid_curve(NID_secp192k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp192k1");
+#else
+#endif
+#ifdef NID_secp224k1
+ if (valid_curve(NID_secp224k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp224k1");
+#else
+#endif
+#ifdef NID_secp224r1
+ if (valid_curve(NID_secp224r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp224r1");
+#else
+#endif
+#ifdef NID_secp256k1
+ if (valid_curve(NID_secp256k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp256k1");
+#else
+#endif
+#ifdef NID_secp384r1
+ if (valid_curve(NID_secp384r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp384r1");
+#else
+#endif
+#ifdef NID_secp521r1
+ if (valid_curve(NID_secp521r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp521r1");
+#else
+#endif
+#ifdef NID_X9_62_prime192v1
+ if (valid_curve(NID_X9_62_prime192v1)) {
+ algo_curve[fips][cnt++] = enif_make_atom(env,"secp192r1");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"prime192v1");
+ }
+#else
+#endif
+#ifdef NID_X9_62_prime192v2
+ if (valid_curve(NID_X9_62_prime192v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime192v2");
+#else
+#endif
+#ifdef NID_X9_62_prime192v3
+ if (valid_curve(NID_X9_62_prime192v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime192v3");
+#else
+#endif
+#ifdef NID_X9_62_prime239v1
+ if (valid_curve(NID_X9_62_prime239v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime239v1");
+#else
+#endif
+#ifdef NID_X9_62_prime239v2
+ if (valid_curve(NID_X9_62_prime239v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime239v2");
+#else
+#endif
+#ifdef NID_X9_62_prime239v3
+ if (valid_curve(NID_X9_62_prime239v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"prime239v3");
+#else
+#endif
+#ifdef NID_X9_62_prime256v1
+ if (valid_curve(NID_X9_62_prime256v1)) {
+ algo_curve[fips][cnt++] = enif_make_atom(env,"secp256r1");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"prime256v1");
+ }
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls7
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls7)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls7");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls9
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls9)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls9");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls12
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls12)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls12");
+#else
+#endif
+#ifdef NID_brainpoolP160r1
+ if (valid_curve(NID_brainpoolP160r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP160r1");
+#else
+#endif
+#ifdef NID_brainpoolP160t1
+ if (valid_curve(NID_brainpoolP160t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP160t1");
+#else
+#endif
+#ifdef NID_brainpoolP192r1
+ if (valid_curve(NID_brainpoolP192r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP192r1");
+#else
+#endif
+#ifdef NID_brainpoolP192t1
+ if (valid_curve(NID_brainpoolP192t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP192t1");
+#else
+#endif
+#ifdef NID_brainpoolP224r1
+ if (valid_curve(NID_brainpoolP224r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP224r1");
+#else
+#endif
+#ifdef NID_brainpoolP224t1
+ if (valid_curve(NID_brainpoolP224t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP224t1");
+#else
+#endif
+#ifdef NID_brainpoolP256r1
+ if (valid_curve(NID_brainpoolP256r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP256r1");
+#else
+#endif
+#ifdef NID_brainpoolP256t1
+ if (valid_curve(NID_brainpoolP256t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP256t1");
+#else
+#endif
+#ifdef NID_brainpoolP320r1
+ if (valid_curve(NID_brainpoolP320r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP320r1");
+#else
+#endif
+#ifdef NID_brainpoolP320t1
+ if (valid_curve(NID_brainpoolP320t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP320t1");
+#else
+#endif
+#ifdef NID_brainpoolP384r1
+ if (valid_curve(NID_brainpoolP384r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP384r1");
+#else
+#endif
+#ifdef NID_brainpoolP384t1
+ if (valid_curve(NID_brainpoolP384t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP384t1");
+#else
+#endif
+#ifdef NID_brainpoolP512r1
+ if (valid_curve(NID_brainpoolP512r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP512r1");
+#else
+#endif
+#ifdef NID_brainpoolP512t1
+ if (valid_curve(NID_brainpoolP512t1)) algo_curve[fips][cnt++] = enif_make_atom(env,"brainpoolP512t1");
+#else
+#endif
+ //#if !defined(OPENSSL_NO_EC2M)
+#ifdef NID_sect163k1
+ if (valid_curve(NID_sect163k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect163k1");
+#else
+#endif
+#ifdef NID_sect163r1
+ if (valid_curve(NID_sect163r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect163r1");
+#else
+#endif
+#ifdef NID_sect163r2
+ if (valid_curve(NID_sect163r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect163r2");
+#else
+#endif
+#ifdef NID_sect193r1
+ if (valid_curve(NID_sect193r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect193r1");
+#else
+#endif
+#ifdef NID_sect193r2
+ if (valid_curve(NID_sect193r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect193r2");
+#else
+#endif
+#ifdef NID_sect233k1
+ if (valid_curve(NID_sect233k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect233k1");
+#else
+#endif
+#ifdef NID_sect233r1
+ if (valid_curve(NID_sect233r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect233r1");
+#else
+#endif
+#ifdef NID_sect239k1
+ if (valid_curve(NID_sect239k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect239k1");
+#else
+#endif
+#ifdef NID_sect283k1
+ if (valid_curve(NID_sect283k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect283k1");
+#else
+#endif
+#ifdef NID_sect283r1
+ if (valid_curve(NID_sect283r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect283r1");
+#else
+#endif
+#ifdef NID_sect409k1
+ if (valid_curve(NID_sect409k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect409k1");
+#else
+#endif
+#ifdef NID_sect409r1
+ if (valid_curve(NID_sect409r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect409r1");
+#else
#endif
+#ifdef NID_sect571k1
+ if (valid_curve(NID_sect571k1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect571k1");
+#else
+#endif
+#ifdef NID_sect571r1
+ if (valid_curve(NID_sect571r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect571r1");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb163v1
+ if (valid_curve(NID_X9_62_c2pnb163v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb163v1");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb163v2
+ if (valid_curve(NID_X9_62_c2pnb163v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb163v2");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb163v3
+ if (valid_curve(NID_X9_62_c2pnb163v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb163v3");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb176v1
+ if (valid_curve(NID_X9_62_c2pnb176v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb176v1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb191v1
+ if (valid_curve(NID_X9_62_c2tnb191v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb191v1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb191v2
+ if (valid_curve(NID_X9_62_c2tnb191v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb191v2");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb191v3
+ if (valid_curve(NID_X9_62_c2tnb191v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb191v3");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb208w1
+ if (valid_curve(NID_X9_62_c2pnb208w1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb208w1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb239v1
+ if (valid_curve(NID_X9_62_c2tnb239v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb239v1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb239v2
+ if (valid_curve(NID_X9_62_c2tnb239v2)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb239v2");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb239v3
+ if (valid_curve(NID_X9_62_c2tnb239v3)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb239v3");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb272w1
+ if (valid_curve(NID_X9_62_c2pnb272w1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb272w1");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb304w1
+ if (valid_curve(NID_X9_62_c2pnb304w1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb304w1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb359v1
+ if (valid_curve(NID_X9_62_c2tnb359v1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb359v1");
+#else
+#endif
+#ifdef NID_X9_62_c2pnb368w1
+ if (valid_curve(NID_X9_62_c2pnb368w1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2pnb368w1");
+#else
+#endif
+#ifdef NID_X9_62_c2tnb431r1
+ if (valid_curve(NID_X9_62_c2tnb431r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"c2tnb431r1");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls3
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls3)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls3");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls5
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls5)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls5");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls10
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls10)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls10");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls11
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls11)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls11");
+#else
#endif
// Non-validated algorithms follow
- algo_curve_fips_cnt = algo_curve_cnt;
-#if defined(HAVE_EC)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls6");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls8");
-#if !defined(OPENSSL_NO_EC2M)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls4");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec4");
+#ifdef NID_secp112r1
+ if (valid_curve(NID_secp112r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp112r1");
+#else
+#endif
+#ifdef NID_secp112r2
+ if (valid_curve(NID_secp112r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp112r2");
+#else
+#endif
+#ifdef NID_secp128r1
+ if (valid_curve(NID_secp128r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp128r1");
+#else
+#endif
+#ifdef NID_secp128r2
+ if (valid_curve(NID_secp128r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"secp128r2");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls6
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls6)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls6");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls8
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls8)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls8");
+#else
+#endif
+ //#if !defined(OPENSSL_NO_EC2M)
+#ifdef NID_sect113r1
+ if (valid_curve(NID_sect113r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect113r1");
+#else
+#endif
+#ifdef NID_sect113r2
+ if (valid_curve(NID_sect113r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect113r2");
+#else
#endif
+#ifdef NID_sect131r1
+ if (valid_curve(NID_sect131r1)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect131r1");
+#else
#endif
- //--
+#ifdef NID_sect131r2
+ if (valid_curve(NID_sect131r2)) algo_curve[fips][cnt++] = enif_make_atom(env,"sect131r2");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls1
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls1)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls1");
+#else
+#endif
+#ifdef NID_wap_wsg_idm_ecid_wtls4
+ if (valid_curve(NID_wap_wsg_idm_ecid_wtls4)) algo_curve[fips][cnt++] = enif_make_atom(env,"wtls4");
+#else
+#endif
+#ifdef NID_ipsec3
+ if (valid_curve(NID_ipsec3)) algo_curve[fips][cnt++] = enif_make_atom(env,"ipsec3");
+#else
+#endif
+#ifdef NID_ipsec4
+ if (valid_curve(NID_ipsec4)) algo_curve[fips][cnt++] = enif_make_atom(env,"ipsec4");
+#else
+#endif
+
+ if (!fips) {
#ifdef HAVE_EDDSA
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed25519");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed448");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"ed25519");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"ed448");
#endif
-#ifdef HAVE_ED_CURVE_DH
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x25519");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x448");
+#ifdef HAVE_EDDH
+ algo_curve[fips][cnt++] = enif_make_atom(env,"x25519");
+ algo_curve[fips][cnt++] = enif_make_atom(env,"x448");
+#endif
+ }
+
+ return cnt;
+}
+
+/* Check if the curve in nid is supported by the
+ current cryptolib and current FIPS state.
+*/
+
+int valid_curve(int nid) {
+ int ret = 0;
+
+#if defined(HAVE_DH)
+# if defined(HAS_EVP_PKEY_CTX) && (! DISABLE_EVP_DH)
+ EVP_PKEY_CTX *pctx = NULL, *kctx = NULL;
+ EVP_PKEY *pkey = NULL, *params = NULL;
+
+ if (NULL == (pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)))
+ goto out;
+
+ if (1 != EVP_PKEY_paramgen_init(pctx))
+ goto out;
+
+ if (1 != EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, nid))
+ goto out;
+
+ if (!EVP_PKEY_paramgen(pctx, &params))
+ goto out;
+
+ if (NULL == (kctx = EVP_PKEY_CTX_new(params, NULL)))
+ goto out;
+
+ if(1 != EVP_PKEY_keygen_init(kctx))
+ goto out;
+ if (1 != EVP_PKEY_keygen(kctx, &pkey))
+ goto out;
+ ret = 1;
+ out:
+ if (pkey) EVP_PKEY_free(pkey);
+ if (kctx) EVP_PKEY_CTX_free(kctx);
+ if (params) EVP_PKEY_free(params);
+ if (pctx) EVP_PKEY_CTX_free(pctx);
+
+# else
+ EC_KEY *key;
+
+ if (NULL == (key = EC_KEY_new_by_curve_name(nid)))
+ goto out;
+
+ if(1 != EC_KEY_generate_key(key))
+ goto out;
+
+ ret = 1;
+ out:
+ if (key) EC_KEY_free(key);
+# endif
+#endif /* HAVE_DH etc */
+
+ return ret;
+}
+#endif /* HAVE_EC */
+
+/*================================================================
+ RSA Options
+*/
+
+ERL_NIF_TERM rsa_opts_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned int cnt =
+#ifdef FIPS_SUPPORT
+ FIPS_mode() ? algo_rsa_opts_fips_cnt :
#endif
+ algo_rsa_opts_cnt;
+ return enif_make_list_from_array(env, algo_rsa_opts, cnt);
+}
+
+void init_rsa_opts_types(ErlNifEnv* env) {
// Validated algorithms first
algo_rsa_opts_cnt = 0;
#ifdef HAS_EVP_PKEY_CTX
@@ -232,68 +682,6 @@ void init_algorithms_types(ErlNifEnv* env)
algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_no_padding");
algo_rsa_opts_fips_cnt = algo_rsa_opts_cnt;
-
- // Check that the max number of algos is updated
- ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM));
ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM));
}
-
-ERL_NIF_TERM hash_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- unsigned int cnt =
-#ifdef FIPS_SUPPORT
- FIPS_mode() ? algo_hash_fips_cnt :
-#endif
- algo_hash_cnt;
-
- return enif_make_list_from_array(env, algo_hash, cnt);
-}
-
-ERL_NIF_TERM pubkey_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- unsigned int cnt =
-#ifdef FIPS_SUPPORT
- FIPS_mode() ? algo_pubkey_fips_cnt :
-#endif
- algo_pubkey_cnt;
-
- return enif_make_list_from_array(env, algo_pubkey, cnt);
-}
-
-
-ERL_NIF_TERM cipher_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- return cipher_types_as_list(env); /* Exclude old api ciphers */
-}
-
-
-ERL_NIF_TERM mac_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- return mac_types_as_list(env);
-}
-
-ERL_NIF_TERM curve_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- unsigned int cnt =
-#ifdef FIPS_SUPPORT
- FIPS_mode() ? algo_curve_fips_cnt :
-#endif
- algo_curve_cnt;
-
- return enif_make_list_from_array(env, algo_curve, cnt);
-}
-
-
-ERL_NIF_TERM rsa_opts_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- unsigned int cnt =
-#ifdef FIPS_SUPPORT
- FIPS_mode() ? algo_rsa_opts_fips_cnt :
-#endif
- algo_rsa_opts_cnt;
-
- return enif_make_list_from_array(env, algo_rsa_opts, cnt);
-}
diff --git a/lib/crypto/c_src/api_ng.c b/lib/crypto/c_src/api_ng.c
index 7bdd565bd8..942d17482a 100644
--- a/lib/crypto/c_src/api_ng.c
+++ b/lib/crypto/c_src/api_ng.c
@@ -29,6 +29,9 @@
ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM ng_crypto_one_time(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+/*************************************************************************/
+/* Compatibility functions. */
+/*************************************************************************/
#ifdef HAVE_ECB_IVEC_BUG
/* <= 0.9.8l returns faulty ivec length */
# define GET_IV_LEN(Ciph) ((Ciph)->flags & ECB_BUG_0_9_8L) ? 0 : EVP_CIPHER_iv_length((Ciph)->cipher.p)
@@ -36,6 +39,48 @@ ERL_NIF_TERM ng_crypto_one_time(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg
# define GET_IV_LEN(Ciph) EVP_CIPHER_iv_length((Ciph)->cipher.p)
#endif
+#if !defined(HAVE_EVP_CIPHER_CTX_COPY)
+/*
+ The EVP_CIPHER_CTX_copy is not available in older cryptolibs although
+ the function is needed.
+ Instead of implement it in-place, we have a copy here as a compatibility
+ function
+*/
+
+int EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, const EVP_CIPHER_CTX *in);
+
+int EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, const EVP_CIPHER_CTX *in)
+{
+ if ((in == NULL) || (in->cipher == NULL))
+ {
+ return 0;
+ }
+#ifdef HAS_ENGINE_SUPPORT
+ /* Make sure it's safe to copy a cipher context using an ENGINE */
+ if (in->engine && !ENGINE_init(in->engine))
+ return 0;
+#endif
+
+ EVP_CIPHER_CTX_cleanup(out);
+ memcpy(out,in,sizeof *out);
+
+ if (in->cipher_data && in->cipher->ctx_size)
+ {
+ out->cipher_data=OPENSSL_malloc(in->cipher->ctx_size);
+ if (!out->cipher_data)
+ return 0;
+ memcpy(out->cipher_data,in->cipher_data,in->cipher->ctx_size);
+ }
+
+#if defined(EVP_CIPH_CUSTOM_COPY) && defined(EVP_CTRL_COPY)
+ if (in->cipher->flags & EVP_CIPH_CUSTOM_COPY)
+ return in->cipher->ctrl((EVP_CIPHER_CTX *)in, EVP_CTRL_COPY, 0, out);
+#endif
+ return 1;
+}
+/****** End of !defined(HAVE_EVP_CIPHER_CTX_COPY) ******/
+#endif
+
/*************************************************************************/
/* Get the arguments for the initialization of the EVP_CIPHER_CTX. Check */
/* them and initialize that context. */
@@ -46,27 +91,30 @@ static int get_init_args(ErlNifEnv* env,
const ERL_NIF_TERM key_arg,
const ERL_NIF_TERM ivec_arg,
const ERL_NIF_TERM encflg_arg,
+ const ERL_NIF_TERM padding_arg,
const struct cipher_type_t **cipherp,
ERL_NIF_TERM *return_term)
{
int ivec_len;
ErlNifBinary key_bin;
ErlNifBinary ivec_bin;
- int encflg;
ctx_res->ctx = NULL; /* For testing if *ctx should be freed after errors */
#if !defined(HAVE_EVP_AES_CTR)
ctx_res->env = NULL; /* For testing if *env should be freed after errors */
#endif
-
+ ctx_res->padding = atom_undefined;
+ ctx_res->padded_size = -1;
+ ctx_res->size = 0;
+
/* Fetch the flag telling if we are going to encrypt (=true) or decrypt (=false) */
if (encflg_arg == atom_true)
- encflg = 1;
+ ctx_res->encflag = 1;
else if (encflg_arg == atom_false)
- encflg = 0;
+ ctx_res->encflag = 0;
else if (encflg_arg == atom_undefined)
/* For compat funcs in crypto.erl */
- encflg = -1;
+ ctx_res->encflag = -1;
else
{
*return_term = EXCP_BADARG(env, "Bad enc flag");
@@ -193,7 +241,7 @@ static int get_init_args(ErlNifEnv* env,
goto err;
}
- if (!EVP_CipherInit_ex(ctx_res->ctx, (*cipherp)->cipher.p, NULL, NULL, NULL, encflg))
+ if (!EVP_CipherInit_ex(ctx_res->ctx, (*cipherp)->cipher.p, NULL, NULL, NULL, ctx_res->encflag))
{
*return_term = EXCP_ERROR(env, "Can't initialize context, step 1");
goto err;
@@ -232,7 +280,20 @@ static int get_init_args(ErlNifEnv* env,
goto err;
}
- EVP_CIPHER_CTX_set_padding(ctx_res->ctx, 0);
+ /* Set padding */
+ if ((padding_arg == atom_undefined) ||
+ (padding_arg == atom_none) ||
+ (padding_arg == atom_zero) ||
+ (padding_arg == atom_random) )
+ EVP_CIPHER_CTX_set_padding(ctx_res->ctx, 0);
+
+ else if (padding_arg != atom_pkcs_padding) /* pkcs_padding is default */
+ {
+ *return_term = EXCP_BADARG(env, "Bad padding flag");
+ goto err;
+ }
+
+ ctx_res->padding = padding_arg;
*return_term = atom_ok;
@@ -266,8 +327,11 @@ static int get_update_args(ErlNifEnv* env,
ASSERT(in_data_bin.size <= INT_MAX);
+ ctx_res->size += in_data_bin.size;
+
#if !defined(HAVE_EVP_AES_CTR)
if (ctx_res->state != atom_undefined) {
+ /* Use AES_CTR compatibility code */
ERL_NIF_TERM state0, newstate_and_outdata;
const ERL_NIF_TERM *tuple_argv;
int tuple_argc;
@@ -288,6 +352,17 @@ static int get_update_args(ErlNifEnv* env,
}
} else
#endif
+#if defined(HAVE_UPDATE_EMPTY_DATA_BUG)
+ if (in_data_bin.size == 0)
+ {
+ if (!enif_alloc_binary(0, &out_data_bin))
+ {
+ *return_term = EXCP_ERROR(env, "Can't allocate outdata");
+ goto err;
+ }
+ *return_term = enif_make_binary(env, &out_data_bin);
+ } else
+#endif
{
block_size = EVP_CIPHER_CTX_block_size(ctx_res->ctx);
@@ -324,22 +399,186 @@ static int get_update_args(ErlNifEnv* env,
}
/*************************************************************************/
+/* Get the arguments for the EVP_CipherFinal function, and call it. */
+/*************************************************************************/
+
+static int get_final_args(ErlNifEnv* env,
+ struct evp_cipher_ctx *ctx_res,
+ ERL_NIF_TERM *return_term
+ )
+{
+ ErlNifBinary out_data_bin;
+ int block_size, pad_size;
+ int out_len, pad_offset;
+
+#if !defined(HAVE_EVP_AES_CTR)
+ if (ctx_res->state != atom_undefined) {
+ /* Use AES_CTR compatibility code */
+ /* Padding size is always 0, because the block_size is 1 and therefore
+ always filled
+ */
+ ctx_res->padded_size = 0;
+ out_len = 0;
+
+ if (!enif_alloc_binary(out_len, &out_data_bin))
+ {
+ *return_term = EXCP_ERROR(env, "Can't allocate empty outdata");
+ goto err0;
+ }
+ } else
+#endif
+ {
+ block_size = EVP_CIPHER_CTX_block_size(ctx_res->ctx);
+
+ pad_size = ctx_res->size % block_size;
+ if (pad_size)
+ pad_size = block_size - pad_size;
+
+ if (!enif_alloc_binary((size_t)block_size, &out_data_bin))
+ {
+ *return_term = EXCP_ERROR(env, "Can't allocate final outdata");
+ goto err0;
+ }
+
+ if (ctx_res->encflag)
+ {/* Maybe do padding */
+
+ /* First set lengths etc and do the otp_padding */
+ if (ctx_res->padding == atom_undefined)
+ {
+ ctx_res->padded_size = pad_size;
+ pad_offset = 0;
+ }
+
+ else if (ctx_res->padding == atom_none)
+ {
+ ASSERT(pad_size == 0);
+ ctx_res->padded_size = pad_size;
+ pad_offset = 0;
+ }
+
+ else if (ctx_res->padding == atom_pkcs_padding)
+ {
+ ctx_res->padded_size = pad_size ? pad_size : block_size;
+ pad_offset = 0;
+ }
+
+ else if ((ctx_res->padding == atom_zero) ||
+ (ctx_res->padding == atom_random))
+ {
+ if (pad_size)
+ {
+ unsigned char padding[EVP_MAX_BLOCK_LENGTH];
+ int i;
+ if (ctx_res->padding == atom_zero)
+ for(i=0; i<pad_size; i++) padding[i] = (unsigned char)0;
+ else
+ RAND_bytes(padding, pad_size);
+ if (!EVP_CipherUpdate(ctx_res->ctx, out_data_bin.data, &out_len, padding, pad_size))
+ {
+ *return_term = EXCP_ERROR(env, "Can't pad");
+ goto err;
+ }
+ }
+ else
+ out_len = 0;
+
+ ctx_res->padded_size = pad_size;
+ pad_offset = out_len;
+ }
+
+ else
+ {
+ *return_term = EXCP_ERROR(env, "Bad padding flg");
+ goto err;
+ }
+
+ /* Decide how many bytes that are to be returned and set out_len to that value */
+ if (ctx_res->padding == atom_undefined)
+ {
+ out_len = 0;
+ }
+
+ else
+ {
+ if (!EVP_CipherFinal_ex(ctx_res->ctx, out_data_bin.data+pad_offset, &out_len))
+ {
+ if (ctx_res->padding == atom_none)
+ *return_term = EXCP_ERROR(env, "Padding 'none' but unfilled last block");
+ else if (ctx_res->padding == atom_pkcs_padding)
+ *return_term = EXCP_ERROR(env, "Can't finalize");
+ else
+ *return_term = EXCP_ERROR(env, "Padding failed");
+ goto err;
+ }
+ else
+ out_len += pad_offset;
+ }
+
+ /* (end of encryption part) */
+ }
+ else
+ { /* decryption. */
+ /* Decide how many bytes that are to be returned and set out_len to that value */
+ if (ctx_res->padding == atom_undefined)
+ {
+ out_len = 0;
+ }
+
+ else if ((ctx_res->padding == atom_none) ||
+ (ctx_res->padding == atom_pkcs_padding) ||
+ (ctx_res->padding == atom_zero) ||
+ (ctx_res->padding == atom_random) )
+ {
+ if (!EVP_CipherFinal_ex(ctx_res->ctx, out_data_bin.data, &out_len))
+ {
+ *return_term = EXCP_ERROR(env, "Can't finalize");
+ goto err;
+ }
+ }
+ else
+ {
+ *return_term = EXCP_ERROR(env, "Bad padding flg");
+ goto err;
+ }
+ /* (end of decryption part) */
+ }
+ }
+
+ /* success: */
+ if (!enif_realloc_binary(&out_data_bin, (size_t)out_len))
+ {
+ *return_term = EXCP_ERROR(env, "Can't reallocate");
+ goto err;
+ }
+
+ *return_term = enif_make_binary(env, &out_data_bin);
+
+ return 1;
+
+ err:
+ enif_release_binary(&out_data_bin);
+ err0:
+ return 0;
+}
+
+
+/*************************************************************************/
/* Initialize the state for (de/en)cryption */
/*************************************************************************/
ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Cipher, Key, IVec, Encrypt) % if no IV for the Cipher, set IVec = <<>>
+{/* (Cipher, Key, IVec, Encrypt, Padding) % if no IV for the Cipher, set IVec = <<>>
*/
struct evp_cipher_ctx *ctx_res = NULL;
const struct cipher_type_t *cipherp;
ERL_NIF_TERM ret;
- int encflg;
if (enif_is_atom(env, argv[0])) {
if ((ctx_res = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
return EXCP_ERROR(env, "Can't allocate resource");
- if (get_init_args(env, ctx_res, argv[0], argv[1], argv[2], argv[argc-1],
+ if (get_init_args(env, ctx_res, argv[0], argv[1], argv[2], argv[3], argv[4],
&cipherp, &ret))
ret = enif_make_resource(env, ctx_res);
/* else error msg in ret */
@@ -349,16 +588,16 @@ ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg
} else if (enif_get_resource(env, argv[0], (ErlNifResourceType*)evp_cipher_ctx_rtype, (void**)&ctx_res)) {
/* Fetch the flag telling if we are going to encrypt (=true) or decrypt (=false) */
if (argv[3] == atom_true)
- encflg = 1;
+ ctx_res->encflag = 1;
else if (argv[3] == atom_false)
- encflg = 0;
+ ctx_res->encflag = 0;
else {
ret = EXCP_BADARG(env, "Bad enc flag");
goto ret;
}
if (ctx_res->ctx) {
/* It is *not* a ctx_res for the compatibility handling of non-EVP aes_ctr */
- if (!EVP_CipherInit_ex(ctx_res->ctx, NULL, NULL, NULL, NULL, encflg)) {
+ if (!EVP_CipherInit_ex(ctx_res->ctx, NULL, NULL, NULL, NULL, ctx_res->encflag)) {
ret = EXCP_ERROR(env, "Can't initialize encflag");
goto ret;
}
@@ -378,49 +617,6 @@ ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg
/* Encrypt/decrypt */
/*************************************************************************/
-#if !defined(HAVE_EVP_CIPHER_CTX_COPY)
-/*
- The EVP_CIPHER_CTX_copy is not available in older cryptolibs although
- the function is needed.
- Instead of implement it in-place, we have a copy here as a compatibility
- function
-*/
-
-int EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, const EVP_CIPHER_CTX *in);
-
-int EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, const EVP_CIPHER_CTX *in)
-{
- if ((in == NULL) || (in->cipher == NULL))
- {
- return 0;
- }
-#ifdef HAS_ENGINE_SUPPORT
- /* Make sure it's safe to copy a cipher context using an ENGINE */
- if (in->engine && !ENGINE_init(in->engine))
- return 0;
-#endif
-
- EVP_CIPHER_CTX_cleanup(out);
- memcpy(out,in,sizeof *out);
-
- if (in->cipher_data && in->cipher->ctx_size)
- {
- out->cipher_data=OPENSSL_malloc(in->cipher->ctx_size);
- if (!out->cipher_data)
- return 0;
- memcpy(out->cipher_data,in->cipher_data,in->cipher->ctx_size);
- }
-
-#if defined(EVP_CIPH_CUSTOM_COPY) && defined(EVP_CTRL_COPY)
- if (in->cipher->flags & EVP_CIPH_CUSTOM_COPY)
- return in->cipher->ctrl((EVP_CIPHER_CTX *)in, EVP_CTRL_COPY, 0, out);
-#endif
- return 1;
-}
-/****** End of compatibility function ******/
-#endif
-
-
ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Context, Data [, IV]) */
struct evp_cipher_ctx *ctx_res;
@@ -433,6 +629,7 @@ ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
return EXCP_BADARG(env, "Bad 1:st arg");
if (argc == 3) {
+ /* We have an IV in this call. Make a copy of the context */
ErlNifBinary ivec_bin;
memcpy(&ctx_res_copy, ctx_res, sizeof ctx_res_copy);
@@ -449,8 +646,6 @@ ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
}
}
- ctx_res = &ctx_res_copy;
-
if (!enif_inspect_iolist_as_binary(env, argv[2], &ivec_bin))
{
ret = EXCP_BADARG(env, "Bad iv type");
@@ -483,7 +678,9 @@ ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
}
get_update_args(env, &ctx_res_copy, argv[1], &ret);
+ ctx_res->size = ctx_res_copy.size;
} else
+ /* argc != 3, that is, argc = 2 (we don't have an IV in this call) */
get_update_args(env, ctx_res, argv[1], &ret);
err:
@@ -498,8 +695,6 @@ ERL_NIF_TERM ng_crypto_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
{/* (Context, Data [, IV]) */
ErlNifBinary data_bin;
- ASSERT(argc <= 3);
-
if (!enif_inspect_binary(env, argv[1], &data_bin))
return EXCP_BADARG(env, "expected binary as data");
@@ -517,26 +712,78 @@ ERL_NIF_TERM ng_crypto_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
}
/*************************************************************************/
+/* Final */
+/*************************************************************************/
+
+ERL_NIF_TERM ng_crypto_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context) */
+ /* No need for enif_schedule_nif since maximum BlockSize-1 bytes are handled */
+ struct evp_cipher_ctx *ctx_res;
+ ERL_NIF_TERM ret;
+
+ if (!enif_get_resource(env, argv[0], (ErlNifResourceType*)evp_cipher_ctx_rtype, (void**)&ctx_res))
+ return EXCP_BADARG(env, "Bad arg");
+
+ get_final_args(env, ctx_res, &ret);
+
+ return ret;
+}
+
+/*************************************************************************/
/* One shot */
/*************************************************************************/
ERL_NIF_TERM ng_crypto_one_time(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Cipher, Key, IVec, Data, Encrypt) */
+{/* (Cipher, Key, IVec, Data, Encrypt, PaddingType) */
struct evp_cipher_ctx ctx_res;
const struct cipher_type_t *cipherp;
ERL_NIF_TERM ret;
-
+ ErlNifBinary out_data_bin, final_data_bin;
+ unsigned char *append_buf;
+
ctx_res.ctx = NULL;
#if !defined(HAVE_EVP_AES_CTR)
ctx_res.env = NULL;
#endif
- if (!get_init_args(env, &ctx_res, argv[0], argv[1], argv[2], argv[4], &cipherp, &ret))
- goto ret;
+ /* EVP_CipherInit */
+ if (!get_init_args(env, &ctx_res, argv[0], argv[1], argv[2], argv[4], argv[5], &cipherp, &ret))
+ goto out;
- get_update_args(env, &ctx_res, argv[3], &ret);
+ /* out_data = EVP_CipherUpdate */
+ if (!get_update_args(env, &ctx_res, argv[3], &ret))
+ /* Got an exception as result in &ret */
+ goto out;
- ret:
+ if (!enif_inspect_binary(env, ret, &out_data_bin) )
+ {
+ ret = EXCP_ERROR(env, "Can't inspect first");
+ goto out;
+ }
+
+ /* final_data = EVP_CipherFinal_ex */
+ if (!get_final_args(env, &ctx_res, &ret))
+ /* Got an exception as result in &ret */
+ goto out;
+
+ if (!enif_inspect_binary(env, ret, &final_data_bin) )
+ {
+ ret = EXCP_ERROR(env, "Can't inspect final");
+ goto out;
+ }
+
+ /* Concatenate out_data and final_date into a new binary kept in the variable ret. */
+ append_buf = enif_make_new_binary(env, out_data_bin.size + final_data_bin.size, &ret);
+ if (!append_buf)
+ {
+ ret = EXCP_ERROR(env, "Can't append");
+ goto out;
+ }
+
+ memcpy(append_buf, out_data_bin.data, out_data_bin.size);
+ memcpy(append_buf+out_data_bin.size, final_data_bin.data, final_data_bin.size);
+
+ out:
if (ctx_res.ctx)
EVP_CIPHER_CTX_free(ctx_res.ctx);
@@ -550,12 +797,10 @@ ERL_NIF_TERM ng_crypto_one_time(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg
ERL_NIF_TERM ng_crypto_one_time_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Cipher, Key, IVec, Data, Encrypt) % if no IV for the Cipher, set IVec = <<>>
+{/* (Cipher, Key, IVec, Data, Encrypt, Padding) % if no IV for the Cipher, set IVec = <<>>
*/
ErlNifBinary data_bin;
- ASSERT(argc == 5);
-
if (!enif_inspect_binary(env, argv[3], &data_bin))
return EXCP_BADARG(env, "expected binary as data");
@@ -571,3 +816,37 @@ ERL_NIF_TERM ng_crypto_one_time_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM
return ng_crypto_one_time(env, argc, argv);
}
+
+
+/*************************************************************************/
+/* Get data from the cipher resource */
+/*************************************************************************/
+
+ERL_NIF_TERM ng_crypto_get_data_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context) -> map */
+ struct evp_cipher_ctx *ctx_res;
+ ERL_NIF_TERM ret;
+
+ if (!enif_get_resource(env, argv[0], (ErlNifResourceType*)evp_cipher_ctx_rtype, (void**)&ctx_res))
+ return EXCP_BADARG(env, "Bad arg");
+
+ ret = enif_make_new_map(env);
+
+ enif_make_map_put(env, ret, atom_size,
+ enif_make_int(env, ctx_res->size),
+ &ret);
+
+ enif_make_map_put(env, ret, atom_padding_size,
+ enif_make_int(env, ctx_res->padded_size),
+ &ret);
+
+ enif_make_map_put(env, ret, atom_padding_type,
+ ctx_res->padding,
+ &ret);
+
+ enif_make_map_put(env, ret, atom_encrypt,
+ (ctx_res->encflag) ? atom_true : atom_false,
+ &ret);
+
+ return ret;
+}
diff --git a/lib/crypto/c_src/api_ng.h b/lib/crypto/c_src/api_ng.h
index ee8df8f323..d1238c17f3 100644
--- a/lib/crypto/c_src/api_ng.h
+++ b/lib/crypto/c_src/api_ng.h
@@ -25,6 +25,8 @@
ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM ng_crypto_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM ng_crypto_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM ng_crypto_one_time_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM ng_crypto_get_data_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
#endif /* E_AES_H__ */
diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c
index 0ccf94508b..735749a56c 100644
--- a/lib/crypto/c_src/atoms.c
+++ b/lib/crypto/c_src/atoms.c
@@ -24,6 +24,9 @@ ERL_NIF_TERM atom_true;
ERL_NIF_TERM atom_false;
ERL_NIF_TERM atom_sha;
ERL_NIF_TERM atom_error;
+ERL_NIF_TERM atom_pkcs_padding;
+ERL_NIF_TERM atom_zero;
+ERL_NIF_TERM atom_random;
ERL_NIF_TERM atom_rsa_pkcs1_padding;
ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding;
ERL_NIF_TERM atom_rsa_no_padding;
@@ -52,6 +55,9 @@ ERL_NIF_TERM atom_block_size;
ERL_NIF_TERM atom_key_length;
ERL_NIF_TERM atom_iv_length;
ERL_NIF_TERM atom_mode;
+ERL_NIF_TERM atom_encrypt;
+ERL_NIF_TERM atom_padding_size;
+ERL_NIF_TERM atom_padding_type;
ERL_NIF_TERM atom_ecb_mode;
ERL_NIF_TERM atom_cbc_mode;
ERL_NIF_TERM atom_cfb_mode;
@@ -86,9 +92,11 @@ ERL_NIF_TERM atom_rsa;
ERL_NIF_TERM atom_dss;
ERL_NIF_TERM atom_ecdsa;
-#ifdef HAVE_ED_CURVE_DH
+#ifdef HAVE_EDDH
ERL_NIF_TERM atom_x25519;
ERL_NIF_TERM atom_x448;
+ERL_NIF_TERM atom_ed25519;
+ERL_NIF_TERM atom_ed448;
#endif
ERL_NIF_TERM atom_eddsa;
@@ -154,6 +162,9 @@ int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM
atom_sha = enif_make_atom(env,"sha");
atom_error = enif_make_atom(env,"error");
+ atom_pkcs_padding = enif_make_atom(env,"pkcs_padding");
+ atom_zero = enif_make_atom(env,"zero");
+ atom_random = enif_make_atom(env,"random");
atom_rsa_pkcs1_padding = enif_make_atom(env,"rsa_pkcs1_padding");
atom_rsa_pkcs1_oaep_padding = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding");
@@ -176,6 +187,9 @@ int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM
atom_key_length = enif_make_atom(env,"key_length");
atom_iv_length = enif_make_atom(env,"iv_length");
atom_mode = enif_make_atom(env,"mode");
+ atom_encrypt = enif_make_atom(env, "encrypt");
+ atom_padding_size = enif_make_atom(env, "padding_size");
+ atom_padding_type = enif_make_atom(env, "padding_type");
atom_ecb_mode = enif_make_atom(env,"ecb_mode");
atom_cbc_mode = enif_make_atom(env,"cbc_mode");
atom_cfb_mode = enif_make_atom(env,"cfb_mode");
@@ -216,9 +230,11 @@ int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM
atom_dss = enif_make_atom(env,"dss");
atom_ecdsa = enif_make_atom(env,"ecdsa");
-#ifdef HAVE_ED_CURVE_DH
+#ifdef HAVE_EDDH
atom_x25519 = enif_make_atom(env,"x25519");
atom_x448 = enif_make_atom(env,"x448");
+ atom_ed25519 = enif_make_atom(env,"ed25519");
+ atom_ed448 = enif_make_atom(env,"ed448");
#endif
atom_eddsa = enif_make_atom(env,"eddsa");
#ifdef HAVE_EDDSA
diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h
index 99a617d699..dd41227cec 100644
--- a/lib/crypto/c_src/atoms.h
+++ b/lib/crypto/c_src/atoms.h
@@ -28,6 +28,9 @@ extern ERL_NIF_TERM atom_true;
extern ERL_NIF_TERM atom_false;
extern ERL_NIF_TERM atom_sha;
extern ERL_NIF_TERM atom_error;
+extern ERL_NIF_TERM atom_pkcs_padding;
+extern ERL_NIF_TERM atom_zero;
+extern ERL_NIF_TERM atom_random;
extern ERL_NIF_TERM atom_rsa_pkcs1_padding;
extern ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding;
extern ERL_NIF_TERM atom_rsa_no_padding;
@@ -56,6 +59,9 @@ extern ERL_NIF_TERM atom_block_size;
extern ERL_NIF_TERM atom_key_length;
extern ERL_NIF_TERM atom_iv_length;
extern ERL_NIF_TERM atom_mode;
+extern ERL_NIF_TERM atom_encrypt;
+extern ERL_NIF_TERM atom_padding_size;
+extern ERL_NIF_TERM atom_padding_type;
extern ERL_NIF_TERM atom_ecb_mode;
extern ERL_NIF_TERM atom_cbc_mode;
extern ERL_NIF_TERM atom_cfb_mode;
@@ -90,9 +96,11 @@ extern ERL_NIF_TERM atom_rsa;
extern ERL_NIF_TERM atom_dss;
extern ERL_NIF_TERM atom_ecdsa;
-#ifdef HAVE_ED_CURVE_DH
+#ifdef HAVE_EDDH
extern ERL_NIF_TERM atom_x25519;
extern ERL_NIF_TERM atom_x448;
+extern ERL_NIF_TERM atom_ed25519;
+extern ERL_NIF_TERM atom_ed448;
#endif
extern ERL_NIF_TERM atom_eddsa;
diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c
index 3db2258d5e..f9fac1761a 100644
--- a/lib/crypto/c_src/cipher.c
+++ b/lib/crypto/c_src/cipher.c
@@ -26,72 +26,74 @@
#define COND_NO_DES_PTR(Ptr) (NULL)
#endif
+#define NOT_AEAD {{0,0,0}}
+
static struct cipher_type_t cipher_types[] =
{
#ifdef HAVE_RC2
- {{"rc2_cbc"}, {&EVP_rc2_cbc}, 0, NO_FIPS_CIPHER},
+ {{"rc2_cbc"}, {&EVP_rc2_cbc}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#else
- {{"rc2_cbc"}, {NULL}, 0, NO_FIPS_CIPHER},
+ {{"rc2_cbc"}, {NULL}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#endif
#ifdef HAVE_RC4
- {{"rc4"}, {&EVP_rc4}, 0, NO_FIPS_CIPHER},
+ {{"rc4"}, {&EVP_rc4}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#else
- {{"rc4"}, {NULL}, 0, NO_FIPS_CIPHER},
+ {{"rc4"}, {NULL}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#endif
- {{"des_cbc"}, {COND_NO_DES_PTR(&EVP_des_cbc)}, 0, NO_FIPS_CIPHER},
- {{"des_cfb"}, {COND_NO_DES_PTR(&EVP_des_cfb8)}, 0, NO_FIPS_CIPHER},
- {{"des_ecb"}, {COND_NO_DES_PTR(&EVP_des_ecb)}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L},
+ {{"des_cbc"}, {COND_NO_DES_PTR(&EVP_des_cbc)}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"des_cfb"}, {COND_NO_DES_PTR(&EVP_des_cfb8)}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"des_ecb"}, {COND_NO_DES_PTR(&EVP_des_ecb)}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L, NOT_AEAD},
- {{"des_ede3_cbc"}, {COND_NO_DES_PTR(&EVP_des_ede3_cbc)}, 0, 0},
+ {{"des_ede3_cbc"}, {COND_NO_DES_PTR(&EVP_des_ede3_cbc)}, 0, 0, NOT_AEAD},
#ifdef HAVE_DES_ede3_cfb_encrypt
- {{"des_ede3_cfb"}, {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}, 0, 0},
+ {{"des_ede3_cfb"}, {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}, 0, 0, NOT_AEAD},
#else
- {{"des_ede3_cfb"}, {NULL}, 0, 0},
+ {{"des_ede3_cfb"}, {NULL}, 0, 0, NOT_AEAD},
#endif
#ifdef HAVE_BF
- {{"blowfish_cbc"}, {&EVP_bf_cbc}, 0, NO_FIPS_CIPHER},
- {{"blowfish_cfb64"}, {&EVP_bf_cfb64}, 0, NO_FIPS_CIPHER},
- {{"blowfish_ofb64"}, {&EVP_bf_ofb}, 0, NO_FIPS_CIPHER},
- {{"blowfish_ecb"}, {&EVP_bf_ecb}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L},
+ {{"blowfish_cbc"}, {&EVP_bf_cbc}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"blowfish_cfb64"}, {&EVP_bf_cfb64}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"blowfish_ofb64"}, {&EVP_bf_ofb}, 0, NO_FIPS_CIPHER, NOT_AEAD},
+ {{"blowfish_ecb"}, {&EVP_bf_ecb}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L, NOT_AEAD},
#else
- {{"blowfish_cbc"}, {NULL}, 0, 0},
- {{"blowfish_cfb64"}, {NULL}, 0, 0},
- {{"blowfish_ofb64"}, {NULL}, 0, 0},
- {{"blowfish_ecb"}, {NULL}, 0, 0},
+ {{"blowfish_cbc"}, {NULL}, 0, 0, NOT_AEAD},
+ {{"blowfish_cfb64"}, {NULL}, 0, 0, NOT_AEAD},
+ {{"blowfish_ofb64"}, {NULL}, 0, 0, NOT_AEAD},
+ {{"blowfish_ecb"}, {NULL}, 0, 0, NOT_AEAD},
#endif
- {{"aes_128_cbc"}, {&EVP_aes_128_cbc}, 16, 0},
- {{"aes_192_cbc"}, {&EVP_aes_192_cbc}, 24, 0},
- {{"aes_256_cbc"}, {&EVP_aes_256_cbc}, 32, 0},
+ {{"aes_128_cbc"}, {&EVP_aes_128_cbc}, 16, 0, NOT_AEAD},
+ {{"aes_192_cbc"}, {&EVP_aes_192_cbc}, 24, 0, NOT_AEAD},
+ {{"aes_256_cbc"}, {&EVP_aes_256_cbc}, 32, 0, NOT_AEAD},
- {{"aes_128_cfb8"}, {&EVP_aes_128_cfb8}, 16, AES_CFBx},
- {{"aes_192_cfb8"}, {&EVP_aes_192_cfb8}, 24, AES_CFBx},
- {{"aes_256_cfb8"}, {&EVP_aes_256_cfb8}, 32, AES_CFBx},
+ {{"aes_128_cfb8"}, {&EVP_aes_128_cfb8}, 16, AES_CFBx, NOT_AEAD},
+ {{"aes_192_cfb8"}, {&EVP_aes_192_cfb8}, 24, AES_CFBx, NOT_AEAD},
+ {{"aes_256_cfb8"}, {&EVP_aes_256_cfb8}, 32, AES_CFBx, NOT_AEAD},
- {{"aes_128_cfb128"}, {&EVP_aes_128_cfb128}, 16, AES_CFBx},
- {{"aes_192_cfb128"}, {&EVP_aes_192_cfb128}, 24, AES_CFBx},
- {{"aes_256_cfb128"}, {&EVP_aes_256_cfb128}, 32, AES_CFBx},
+ {{"aes_128_cfb128"}, {&EVP_aes_128_cfb128}, 16, AES_CFBx, NOT_AEAD},
+ {{"aes_192_cfb128"}, {&EVP_aes_192_cfb128}, 24, AES_CFBx, NOT_AEAD},
+ {{"aes_256_cfb128"}, {&EVP_aes_256_cfb128}, 32, AES_CFBx, NOT_AEAD},
- {{"aes_128_ecb"}, {&EVP_aes_128_ecb}, 16, ECB_BUG_0_9_8L},
- {{"aes_192_ecb"}, {&EVP_aes_192_ecb}, 24, ECB_BUG_0_9_8L},
- {{"aes_256_ecb"}, {&EVP_aes_256_ecb}, 32, ECB_BUG_0_9_8L},
+ {{"aes_128_ecb"}, {&EVP_aes_128_ecb}, 16, ECB_BUG_0_9_8L, NOT_AEAD},
+ {{"aes_192_ecb"}, {&EVP_aes_192_ecb}, 24, ECB_BUG_0_9_8L, NOT_AEAD},
+ {{"aes_256_ecb"}, {&EVP_aes_256_ecb}, 32, ECB_BUG_0_9_8L, NOT_AEAD},
#if defined(HAVE_EVP_AES_CTR)
- {{"aes_128_ctr"}, {&EVP_aes_128_ctr}, 16, 0},
- {{"aes_192_ctr"}, {&EVP_aes_192_ctr}, 24, 0},
- {{"aes_256_ctr"}, {&EVP_aes_256_ctr}, 32, 0},
+ {{"aes_128_ctr"}, {&EVP_aes_128_ctr}, 16, 0, NOT_AEAD},
+ {{"aes_192_ctr"}, {&EVP_aes_192_ctr}, 24, 0, NOT_AEAD},
+ {{"aes_256_ctr"}, {&EVP_aes_256_ctr}, 32, 0, NOT_AEAD},
#else
- {{"aes_128_ctr"}, {NULL}, 16, AES_CTR_COMPAT},
- {{"aes_192_ctr"}, {NULL}, 24, AES_CTR_COMPAT},
- {{"aes_256_ctr"}, {NULL}, 32, AES_CTR_COMPAT},
+ {{"aes_128_ctr"}, {NULL}, 16, AES_CTR_COMPAT, NOT_AEAD},
+ {{"aes_192_ctr"}, {NULL}, 24, AES_CTR_COMPAT, NOT_AEAD},
+ {{"aes_256_ctr"}, {NULL}, 32, AES_CTR_COMPAT, NOT_AEAD},
#endif
#if defined(HAVE_CHACHA20)
- {{"chacha20"}, {&EVP_chacha20}, 32, NO_FIPS_CIPHER},
+ {{"chacha20"}, {&EVP_chacha20}, 32, NO_FIPS_CIPHER, NOT_AEAD},
#else
- {{"chacha20"}, {NULL}, 0, NO_FIPS_CIPHER},
+ {{"chacha20"}, {NULL}, 0, NO_FIPS_CIPHER, NOT_AEAD},
#endif
/*==== AEAD ciphers ====*/
@@ -123,12 +125,12 @@ static struct cipher_type_t cipher_types[] =
/*==== Specialy handled ciphers, only for inclusion in algorithm's list ====*/
#ifdef HAVE_AES_IGE
- {{"aes_ige256"}, {NULL}, 0, NO_FIPS_CIPHER | NON_EVP_CIPHER},
+ {{"aes_ige256"}, {NULL}, 0, NO_FIPS_CIPHER | NON_EVP_CIPHER, NOT_AEAD},
#endif
/*==== End of list ==== */
- {{NULL},{NULL},0,0}
+ {{NULL},{NULL},0,0,NOT_AEAD}
};
ErlNifResourceType* evp_cipher_ctx_rtype;
diff --git a/lib/crypto/c_src/cipher.h b/lib/crypto/c_src/cipher.h
index 6843dde8b9..d4c1bed98d 100644
--- a/lib/crypto/c_src/cipher.h
+++ b/lib/crypto/c_src/cipher.h
@@ -62,9 +62,13 @@ extern ErlNifResourceType* evp_cipher_ctx_rtype;
struct evp_cipher_ctx {
EVP_CIPHER_CTX* ctx;
int iv_len;
+ ERL_NIF_TERM padding; /* id of the padding to add by get_final_args() */
+ int padded_size; /* Length of the padding that was added */
+ int encflag; /* 1 if encrypting, 0 if decrypting */
+ unsigned int size; /* The sum of all sizes of input texts to get_update_args() */
#if !defined(HAVE_EVP_AES_CTR)
ErlNifEnv* env;
- ERL_NIF_TERM state;
+ ERL_NIF_TERM state; /* Is == atom_undefined if not handling an aes_ctr crypto */
#endif
};
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index c968cec6b5..4559a15b07 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -61,7 +61,7 @@ static int library_initialized = 0;
static ErlNifFunc nif_funcs[] = {
{"info_lib", 0, info_lib, 0},
{"info_fips", 0, info_fips, 0},
- {"enable_fips_mode", 1, enable_fips_mode, 0},
+ {"enable_fips_mode_nif", 1, enable_fips_mode_nif, 0},
{"hash_algorithms", 0, hash_algorithms, 0},
{"pubkey_algorithms", 0, pubkey_algorithms, 0},
{"cipher_algorithms", 0, cipher_algorithms, 0},
@@ -79,10 +79,12 @@ static ErlNifFunc nif_funcs[] = {
{"mac_final_nif", 1, mac_final_nif, 0},
{"cipher_info_nif", 1, cipher_info_nif, 0},
{"aes_ige_crypt_nif", 4, aes_ige_crypt_nif, 0},
- {"ng_crypto_init_nif", 4, ng_crypto_init_nif, 0},
+ {"ng_crypto_init_nif", 5, ng_crypto_init_nif, 0},
{"ng_crypto_update_nif", 2, ng_crypto_update_nif, 0},
{"ng_crypto_update_nif", 3, ng_crypto_update_nif, 0},
- {"ng_crypto_one_time_nif", 5, ng_crypto_one_time_nif, 0},
+ {"ng_crypto_final_nif", 1, ng_crypto_final_nif, 0},
+ {"ng_crypto_get_data_nif", 1, ng_crypto_get_data_nif, 0},
+ {"ng_crypto_one_time_nif", 6, ng_crypto_one_time_nif, 0},
{"strong_rand_bytes_nif", 1, strong_rand_bytes_nif, 0},
{"strong_rand_range_nif", 1, strong_rand_range_nif, 0},
{"rand_uniform_nif", 2, rand_uniform_nif, 0},
@@ -95,7 +97,7 @@ static ErlNifFunc nif_funcs[] = {
{"dh_generate_key_nif", 4, dh_generate_key_nif, 0},
{"dh_compute_key_nif", 3, dh_compute_key_nif, 0},
{"evp_compute_key_nif", 3, evp_compute_key_nif, 0},
- {"evp_generate_key_nif", 1, evp_generate_key_nif, 0},
+ {"evp_generate_key_nif", 2, evp_generate_key_nif, 0},
{"privkey_to_pubkey_nif", 2, privkey_to_pubkey_nif, 0},
{"srp_value_B_nif", 5, srp_value_B_nif, 0},
{"srp_user_secret_nif", 7, srp_user_secret_nif, 0},
@@ -130,7 +132,11 @@ ERL_NIF_INIT(crypto,nif_funcs,load,NULL,upgrade,unload)
static int verify_lib_version(void)
{
+#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
const unsigned long libv = SSLeay();
+#else
+ const unsigned long libv = OpenSSL_version_num();
+#endif
const unsigned long hdrv = OPENSSL_VERSION_NUMBER;
# define MAJOR_VER(V) ((unsigned long)(V) >> (7*4))
@@ -145,9 +151,11 @@ static int verify_lib_version(void)
static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
{
+#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
#ifdef OPENSSL_THREADS
ErlNifSysInfo sys_info;
#endif
+#endif
get_crypto_callbacks_t* funcp;
struct crypto_callbacks* ccb;
int nlocks = 0;
@@ -155,8 +163,8 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
const ERL_NIF_TERM* tpl_array;
int vernum;
ErlNifBinary lib_bin;
- char lib_buf[1000];
#ifdef HAVE_DYNAMIC_CRYPTO_LIB
+ char lib_buf[1000];
void *handle;
#endif
@@ -217,6 +225,7 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
funcp = &get_crypto_callbacks;
#endif
+#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
#ifdef OPENSSL_THREADS
enif_system_info(&sys_info, sizeof(sys_info));
if (sys_info.scheduler_threads > 1) {
@@ -224,6 +233,7 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
}
/* else no need for locks */
#endif
+#endif
ccb = (*funcp)(nlocks);
@@ -237,8 +247,10 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
return __LINE__;
#endif
+#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
#ifdef OPENSSL_THREADS
if (nlocks > 0) {
+ CRYPTO_set_add_lock_callback(ccb->add_lock_function);
CRYPTO_set_locking_callback(ccb->locking_function);
CRYPTO_set_id_callback(ccb->id_function);
CRYPTO_set_dynlock_create_callback(ccb->dyn_create_function);
@@ -246,6 +258,7 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
CRYPTO_set_dynlock_destroy_callback(ccb->dyn_destroy_function);
}
#endif /* OPENSSL_THREADS */
+#endif
init_digest_types(env);
init_mac_types(env);
diff --git a/lib/crypto/c_src/crypto_callback.c b/lib/crypto/c_src/crypto_callback.c
index 3a47b3b060..53b4bbf1e0 100644
--- a/lib/crypto/c_src/crypto_callback.c
+++ b/lib/crypto/c_src/crypto_callback.c
@@ -22,10 +22,16 @@
#include <string.h>
#include <openssl/opensslconf.h>
#include <stdint.h>
-
#include <erl_nif.h>
+
#include "crypto_callback.h"
+#define PACKED_OPENSSL_VERSION(MAJ, MIN, FIX, P) \
+ ((((((((MAJ << 8) | MIN) << 8 ) | FIX) << 8) | (P-'a'+1)) << 4) | 0xf)
+
+#define PACKED_OPENSSL_VERSION_PLAIN(MAJ, MIN, FIX) \
+ PACKED_OPENSSL_VERSION(MAJ,MIN,FIX,('a'-1))
+
#ifdef DEBUG
# define ASSERT(e) \
((void) ((e) ? 1 : (fprintf(stderr,"Assert '%s' failed at %s:%d\n",\
@@ -100,7 +106,7 @@ static void crypto_free(void* ptr CCB_FILE_LINE_ARGS)
#ifdef OPENSSL_THREADS /* vvvvvvvvvvvvvvv OPENSSL_THREADS vvvvvvvvvvvvvvvv */
-
+#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
static ErlNifRWLock** lock_vec = NULL; /* Static locks used by openssl */
#include <openssl/crypto.h>
@@ -125,6 +131,27 @@ static INLINE void locking(int mode, ErlNifRWLock* lock)
}
}
+/* TODO: there should be an enif_atomic32_add_return() */
+
+typedef int (*add_lock_function_t)(int *var, int incr, int type, const char *file, int line);
+
+#if defined(__GNUC__) && defined(__ATOMIC_ACQ_REL)
+static int add_lock_function(int *var, int incr, int type, const char *file, int line)
+{
+ return __atomic_add_fetch(var, incr, __ATOMIC_ACQ_REL);
+}
+
+static add_lock_function_t get_add_lock_function(void)
+{
+ return __atomic_always_lock_free(sizeof(int), NULL) ? add_lock_function : NULL;
+}
+#else
+static add_lock_function_t get_add_lock_function(void)
+{
+ return NULL;
+}
+#endif
+
static void locking_function(int mode, int n, const char *file, int line)
{
locking(mode, lock_vec[n]);
@@ -149,7 +176,7 @@ static void dyn_destroy_function(struct CRYPTO_dynlock_value *ptr, const char *f
{
enif_rwlock_destroy((ErlNifRWLock*)ptr);
}
-
+#endif /* ^^^^^^^^^^^^ OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0) ^^^^^^^^^^^ */
#endif /* ^^^^^^^^^^^^^^^^^^^^^^ OPENSSL_THREADS ^^^^^^^^^^^^^^^^^^^^^^ */
DLLEXPORT struct crypto_callbacks* get_crypto_callbacks(int nlocks)
@@ -161,18 +188,20 @@ DLLEXPORT struct crypto_callbacks* get_crypto_callbacks(int nlocks)
&crypto_alloc,
&crypto_realloc,
&crypto_free,
-
-#ifdef OPENSSL_THREADS
+
+#if defined OPENSSL_THREADS && OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+ NULL, /* add_lock_function, filled in below */
&locking_function,
&id_function,
&dyn_create_function,
&dyn_lock_function,
&dyn_destroy_function
-#endif /* OPENSSL_THREADS */
+#endif /* OPENSSL_THREADS && PACKED_OPENSSL_VERSION_PLAIN(1,1,0) */
};
if (!is_initialized) {
-#ifdef OPENSSL_THREADS
+#if defined OPENSSL_THREADS && OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+ the_struct.add_lock_function = get_add_lock_function();
if (nlocks > 0) {
int i;
@@ -188,13 +217,15 @@ DLLEXPORT struct crypto_callbacks* get_crypto_callbacks(int nlocks)
goto err;
}
}
-#endif
+#endif /* OPENSSL_THREADS && PACKED_OPENSSL_VERSION_PLAIN(1,1,0) */
is_initialized = 1;
}
return &the_struct;
+#if defined OPENSSL_THREADS && OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
err:
return NULL;
+#endif
}
#ifdef HAVE_DYNAMIC_CRYPTO_LIB
diff --git a/lib/crypto/c_src/crypto_callback.h b/lib/crypto/c_src/crypto_callback.h
index d46266fd8b..d7f14ac1cd 100644
--- a/lib/crypto/c_src/crypto_callback.h
+++ b/lib/crypto/c_src/crypto_callback.h
@@ -34,7 +34,10 @@ struct crypto_callbacks
void (*crypto_free)(void* ptr CCB_FILE_LINE_ARGS);
/* openssl callbacks */
+#if OPENSSL_VERSION_NUMBER < 0x10100000
#ifdef OPENSSL_THREADS
+ int (*add_lock_function)(int *num, int amount, int type,
+ const char *file, int line);
void (*locking_function)(int mode, int n, const char *file, int line);
unsigned long (*id_function)(void);
struct CRYPTO_dynlock_value* (*dyn_create_function)(const char *file,
@@ -44,6 +47,7 @@ struct crypto_callbacks
void (*dyn_destroy_function)(struct CRYPTO_dynlock_value *ptr,
const char *file, int line);
#endif /* OPENSSL_THREADS */
+#endif
};
typedef struct crypto_callbacks* get_crypto_callbacks_t(int nlocks);
diff --git a/lib/crypto/c_src/dh.c b/lib/crypto/c_src/dh.c
index 2ac8ab8308..52d1023e8c 100644
--- a/lib/crypto/c_src/dh.c
+++ b/lib/crypto/c_src/dh.c
@@ -36,7 +36,7 @@ ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
int pub_len, prv_len;
ERL_NIF_TERM ret_pub, ret_prv, ret;
const BIGNUM *pub_key_gen, *priv_key_gen;
-#ifdef HAS_EVP_PKEY_CTX
+#if defined(HAS_EVP_PKEY_CTX) && (! DISABLE_EVP_DH)
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY *dhkey = NULL, *params = NULL;
#endif
@@ -100,7 +100,7 @@ ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
goto bad_arg;
}
-#ifdef HAS_EVP_PKEY_CTX
+#if defined(HAS_EVP_PKEY_CTX) && (! DISABLE_EVP_DH)
if ((params = EVP_PKEY_new()) == NULL)
goto err;
@@ -178,7 +178,7 @@ ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
if (dh_params)
DH_free(dh_params);
-#ifdef HAS_EVP_PKEY_CTX
+#if defined(HAS_EVP_PKEY_CTX) && (! DISABLE_EVP_DH)
if (ctx)
EVP_PKEY_CTX_free(ctx);
if (dhkey)
diff --git a/lib/crypto/c_src/engine.c b/lib/crypto/c_src/engine.c
index b2fdcfaa9f..82ba50e4bd 100644
--- a/lib/crypto/c_src/engine.c
+++ b/lib/crypto/c_src/engine.c
@@ -23,6 +23,7 @@
#ifdef HAS_ENGINE_SUPPORT
struct engine_ctx {
ENGINE *engine;
+ int is_functional;
char *id;
};
@@ -44,6 +45,12 @@ static void engine_ctx_dtor(ErlNifEnv* env, struct engine_ctx* ctx) {
enif_free(ctx->id);
} else
PRINTF_ERR0(" empty ctx->id=NULL");
+
+ if (ctx->engine) {
+ if (ctx->is_functional)
+ ENGINE_finish(ctx->engine);
+ ENGINE_free(ctx->engine);
+ }
}
int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e)
@@ -144,6 +151,7 @@ ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
if ((ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx))) == NULL)
goto err;
ctx->engine = engine;
+ ctx->is_functional = 0;
ctx->id = engine_id;
/* ctx now owns engine_id */
engine_id = NULL;
@@ -181,7 +189,7 @@ ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
if (!ENGINE_init(ctx->engine))
return ERROR_Atom(env, "engine_init_failed");
-
+ ctx->is_functional = 1;
return atom_ok;
bad_arg:
@@ -200,11 +208,13 @@ ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
// Get Engine
ASSERT(argc == 1);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || ctx->is_functional)
goto bad_arg;
if (!ENGINE_free(ctx->engine))
goto err;
+ ctx->engine = NULL;
return atom_ok;
bad_arg:
@@ -223,11 +233,13 @@ ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
// Get Engine
ASSERT(argc == 1);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || !ctx->is_functional)
goto bad_arg;
if (!ENGINE_finish(ctx->engine))
goto err;
+ ctx->is_functional = 0;
return atom_ok;
bad_arg:
@@ -265,7 +277,8 @@ ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF
// Get Engine
ASSERT(argc == 3);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || !ctx->engine)
goto bad_arg;
PRINTF_ERR1("Engine Id: %s\r\n", ENGINE_get_id(ctx->engine));
@@ -333,7 +346,8 @@ ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
// Get Engine
ASSERT(argc == 1);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || !ctx->engine)
goto bad_arg;
if (!ENGINE_add(ctx->engine))
@@ -360,7 +374,8 @@ ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
// Get Engine
ASSERT(argc == 1);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || !ctx->engine)
goto bad_arg;
if (!ENGINE_remove(ctx->engine))
@@ -387,7 +402,8 @@ ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
// Get Engine
ASSERT(argc == 2);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || !ctx->engine)
goto bad_arg;
if (!enif_get_uint(env, argv[1], &method))
goto bad_arg;
@@ -492,7 +508,8 @@ ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM
// Get Engine
ASSERT(argc == 2);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || !ctx->engine)
goto bad_arg;
if (!enif_get_uint(env, argv[1], &method))
goto bad_arg;
@@ -592,6 +609,7 @@ ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
if ((ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx))) == NULL)
goto err;
+ ctx->is_functional = 0;
ctx->engine = engine;
ctx->id = NULL;
@@ -623,10 +641,14 @@ ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
// Get Engine
ASSERT(argc == 1);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || !ctx->engine)
goto bad_arg;
- if ((engine = ENGINE_get_next(ctx->engine)) == NULL) {
+ engine = ENGINE_get_next(ctx->engine);
+ ctx->engine = NULL;
+
+ if (engine == NULL) {
if (!enif_alloc_binary(0, &engine_bin))
goto err;
engine_bin.size = 0;
@@ -636,6 +658,7 @@ ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
if ((next_ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx))) == NULL)
goto err;
next_ctx->engine = engine;
+ next_ctx->is_functional = 0;
next_ctx->id = NULL;
result = enif_make_resource(env, next_ctx);
@@ -667,7 +690,8 @@ ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
// Get Engine
ASSERT(argc == 1);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || !ctx->engine)
goto bad_arg;
if ((engine_id = ENGINE_get_id(ctx->engine)) == NULL) {
@@ -705,7 +729,8 @@ ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
// Get Engine
ASSERT(argc == 1);
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)
+ || !ctx->engine)
goto bad_arg;
if ((engine_name = ENGINE_get_name(ctx->engine)) == NULL) {
diff --git a/lib/crypto/c_src/evp.c b/lib/crypto/c_src/evp.c
index 1148cf3843..7491db3d57 100644
--- a/lib/crypto/c_src/evp.c
+++ b/lib/crypto/c_src/evp.c
@@ -23,7 +23,7 @@
ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
/* (Curve, PeerBin, MyBin) */
{
-#ifdef HAVE_ED_CURVE_DH
+#ifdef HAVE_EDDH
ERL_NIF_TERM ret;
int type;
EVP_PKEY_CTX *ctx = NULL;
@@ -101,30 +101,39 @@ ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
/* (Curve) */
{
-#ifdef HAVE_ED_CURVE_DH
+#ifdef HAVE_EDDH
int type;
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY *pkey = NULL;
ERL_NIF_TERM ret_pub, ret_prv, ret;
+ ErlNifBinary prv_key;
size_t key_len;
unsigned char *out_pub = NULL, *out_priv = NULL;
- ASSERT(argc == 1);
-
if (argv[0] == atom_x25519)
type = EVP_PKEY_X25519;
else if (argv[0] == atom_x448)
type = EVP_PKEY_X448;
+ else if (argv[0] == atom_ed25519)
+ type = EVP_PKEY_ED25519;
+ else if (argv[0] == atom_ed448)
+ type = EVP_PKEY_ED448;
else
goto bad_arg;
- if ((ctx = EVP_PKEY_CTX_new_id(type, NULL)) == NULL)
- goto bad_arg;
-
- if (EVP_PKEY_keygen_init(ctx) != 1)
- goto err;
- if (EVP_PKEY_keygen(ctx, &pkey) != 1)
- goto err;
+ if (argv[1] == atom_undefined) {
+ if ((ctx = EVP_PKEY_CTX_new_id(type, NULL)) == NULL)
+ goto bad_arg;
+ if (EVP_PKEY_keygen_init(ctx) != 1)
+ goto err;
+ if (EVP_PKEY_keygen(ctx, &pkey) != 1)
+ goto err;
+ } else {
+ if (!enif_inspect_binary(env, argv[1], &prv_key))
+ goto bad_arg;
+ if ((pkey = EVP_PKEY_new_raw_private_key(type, NULL, prv_key.data, prv_key.size)) == NULL)
+ goto bad_arg;
+ }
if (EVP_PKEY_get_raw_public_key(pkey, NULL, &key_len) != 1)
goto err;
diff --git a/lib/crypto/c_src/fips.c b/lib/crypto/c_src/fips.c
index b2d892d00b..072aade7b9 100644
--- a/lib/crypto/c_src/fips.c
+++ b/lib/crypto/c_src/fips.c
@@ -29,7 +29,7 @@ ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
#endif
}
-ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+ERL_NIF_TERM enable_fips_mode_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{/* (Boolean) */
if (argv[0] == atom_true) {
#ifdef FIPS_SUPPORT
diff --git a/lib/crypto/c_src/fips.h b/lib/crypto/c_src/fips.h
index 9a436bd202..293176a581 100644
--- a/lib/crypto/c_src/fips.h
+++ b/lib/crypto/c_src/fips.h
@@ -24,6 +24,6 @@
#include "common.h"
ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM enable_fips_mode_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
#endif /* E_FIPS_H__ */
diff --git a/lib/crypto/c_src/hmac.c b/lib/crypto/c_src/hmac.c
index 3b3e0bf894..3ea7c1f5dc 100644
--- a/lib/crypto/c_src/hmac.c
+++ b/lib/crypto/c_src/hmac.c
@@ -28,7 +28,7 @@
*
****************************************************************/
-#ifndef HAS_EVP_PKEY_CTX
+#if !defined(HAS_EVP_PKEY_CTX) || DISABLE_EVP_HMAC
#include "hmac.h"
#include "digest.h"
@@ -233,18 +233,19 @@ int hmac_low_level(ErlNifEnv* env, const EVP_MD *md,
{
unsigned int size_int;
size_t size;
+ unsigned char buff[EVP_MAX_MD_SIZE];
- /* Find the needed space */
if (HMAC(md,
key_bin.data, (int)key_bin.size,
text.data, text.size,
- NULL, &size_int) == NULL)
+ buff, &size_int) == NULL)
{
- *return_term = EXCP_ERROR(env, "Get HMAC size failed");
+ *return_term = EXCP_ERROR(env, "HMAC sign failed");
return 0;
}
size = (size_t)size_int; /* Otherwise "size" is unused in 0.9.8.... */
+ ASSERT(0 < size && size <= EVP_MAX_MD_SIZE);
if (!enif_alloc_binary(size, ret_bin))
{
*return_term = EXCP_ERROR(env, "Alloc binary");
@@ -252,15 +253,7 @@ int hmac_low_level(ErlNifEnv* env, const EVP_MD *md,
}
*ret_bin_alloc = 1;
- /* And do the real HMAC calc */
- if (HMAC(md,
- key_bin.data, (int)key_bin.size,
- text.data, text.size,
- ret_bin->data, &size_int) == NULL)
- {
- *return_term = EXCP_ERROR(env, "HMAC sign failed");
- return 0;
- }
+ memcpy(ret_bin->data, buff, size);
return 1;
}
diff --git a/lib/crypto/c_src/hmac.h b/lib/crypto/c_src/hmac.h
index deeab168a9..df432512f3 100644
--- a/lib/crypto/c_src/hmac.h
+++ b/lib/crypto/c_src/hmac.h
@@ -21,7 +21,7 @@
#ifndef E_HMAC_H__
#define E_HMAC_H__ 1
-#ifndef HAS_EVP_PKEY_CTX
+#if !defined(HAS_EVP_PKEY_CTX) || DISABLE_EVP_HMAC
#include "common.h"
diff --git a/lib/crypto/c_src/info.c b/lib/crypto/c_src/info.c
index 573039203c..1d7e744995 100644
--- a/lib/crypto/c_src/info.c
+++ b/lib/crypto/c_src/info.c
@@ -26,6 +26,8 @@
char *crypto_callback_name = "crypto_callback.debug";
# elif defined(VALGRIND)
char *crypto_callback_name = "crypto_callback.valgrind";
+# elif defined(ADDRESS_SANITIZER)
+char *crypto_callback_name = "crypto_callback.asan";
# else
char *crypto_callback_name = "crypto_callback";
# endif
diff --git a/lib/crypto/c_src/mac.c b/lib/crypto/c_src/mac.c
index 149975ba9d..8735158a01 100644
--- a/lib/crypto/c_src/mac.c
+++ b/lib/crypto/c_src/mac.c
@@ -62,7 +62,7 @@ static struct mac_type_t mac_types[] =
},
{{"hmac"}, 0,
-#ifdef HAS_EVP_PKEY_CTX
+#if defined(HAS_EVP_PKEY_CTX) && (! DISABLE_EVP_HMAC)
{EVP_PKEY_HMAC}, HMAC_mac, 0
#else
/* HMAC is always supported, but possibly with low-level routines */
@@ -271,7 +271,7 @@ ERL_NIF_TERM mac_one_time(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
md = digp->md.p;
-#ifdef HAS_EVP_PKEY_CTX
+#if defined(HAS_EVP_PKEY_CTX) && (! DISABLE_EVP_HMAC)
# ifdef HAVE_PKEY_new_raw_private_key
/* Prefered for new applications according to EVP_PKEY_new_mac_key(3) */
pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, /*engine*/ NULL, key_bin.data, key_bin.size);
diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h
index 5acec53996..1a11ab12fe 100644
--- a/lib/crypto/c_src/openssl_config.h
+++ b/lib/crypto/c_src/openssl_config.h
@@ -28,6 +28,7 @@
#include <openssl/des.h>
/* #include <openssl/idea.h> This is not supported on the openssl OTP requires */
+#include <openssl/dh.h>
#include <openssl/dsa.h>
#include <openssl/rsa.h>
#include <openssl/aes.h>
@@ -118,6 +119,16 @@
# endif
#endif
+#if defined(HAS_EVP_PKEY_CTX) \
+ && OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+ /* EVP is slow on antique crypto libs.
+ * DISABLE_EVP_* is 0 or 1 from the configure script
+ */
+# undef DISABLE_EVP_DH
+# define DISABLE_EVP_DH 1
+# undef DISABLE_EVP_HMAC
+# define DISABLE_EVP_HMAC 1
+#endif
#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
#include <openssl/modes.h>
@@ -220,7 +231,9 @@
#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1) -7) \
&& !defined(HAS_LIBRESSL) \
&& defined(HAVE_EC)
-# define HAVE_ED_CURVE_DH
+# ifdef HAVE_DH
+# define HAVE_EDDH
+# endif
# if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1))
# define HAVE_EDDSA
# endif
@@ -275,6 +288,7 @@
#if OPENSSL_VERSION_NUMBER <= PACKED_OPENSSL_VERSION(0,9,8,'l')
# define HAVE_ECB_IVEC_BUG
+# define HAVE_UPDATE_EMPTY_DATA_BUG
#endif
#ifndef HAS_LIBRESSL
@@ -294,8 +308,10 @@
/* If OPENSSL_NO_EC is set, there will be an error in ec.h included from engine.h
So if EC is disabled, you can't use Engine either....
*/
+#if !defined(OPENSSL_NO_ENGINE)
# define HAS_ENGINE_SUPPORT
#endif
+#endif
#if defined(HAS_ENGINE_SUPPORT)
diff --git a/lib/crypto/c_src/otp_test_engine.c b/lib/crypto/c_src/otp_test_engine.c
index 0d887a16e2..f5fff85b14 100644
--- a/lib/crypto/c_src/otp_test_engine.c
+++ b/lib/crypto/c_src/otp_test_engine.c
@@ -50,8 +50,10 @@
&& !defined(OPENSSL_NO_EC) \
&& !defined(OPENSSL_NO_ECDH) \
&& !defined(OPENSSL_NO_ECDSA)
+#if !defined(OPENSSL_NO_ENGINE)
# define HAVE_EC
#endif
+#endif
#if defined(HAVE_EC)
/* If OPENSSL_NO_EC is set, there will be an error in ec.h included from engine.h
@@ -201,7 +203,7 @@ static int test_engine_digest_selector(ENGINE *e, const EVP_MD **digest,
if (!digest) {
*nids = test_digest_ids;
fprintf(stderr, "Digest is empty! Nid:%d\r\n", nid);
- return 2;
+ return sizeof(test_digest_ids) / sizeof(*test_digest_ids);
}
fprintf(stderr, "Digest no %d requested\r\n",nid);
if (nid == NID_md5) {
@@ -373,8 +375,6 @@ int pem_passwd_cb_fun(char *buf, int size, int rwflag, void *password)
return 0;
}
-#endif
-
#if defined(FAKE_RSA_IMPL)
/* RSA sign. This returns a fixed string so the test case can test that it was called
instead of the cryptolib default RSA sign */
@@ -452,3 +452,5 @@ static int test_rsa_free(RSA *rsa)
}
#endif /* if defined(FAKE_RSA_IMPL) */
+
+#endif /* if defined(HAVE_EC) */
diff --git a/lib/crypto/c_src/pkey.c b/lib/crypto/c_src/pkey.c
index d70db8570d..2125aed537 100644
--- a/lib/crypto/c_src/pkey.c
+++ b/lib/crypto/c_src/pkey.c
@@ -187,6 +187,10 @@ static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF
opt->rsa_mgf1_md = NULL;
opt->rsa_padding = RSA_PKCS1_PADDING;
opt->rsa_pss_saltlen = -2;
+ } else {
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_padding = 0;
+ opt->rsa_pss_saltlen = 0;
}
if (enif_is_empty_list(env, options))
@@ -528,8 +532,8 @@ ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
#endif
PKeySignOptions sig_opt;
ErlNifBinary sig_bin; /* signature */
- unsigned char *tbs; /* data to be signed */
- size_t tbslen;
+ unsigned char *tbs = NULL; /* data to be signed */
+ size_t tbslen = 0;
RSA *rsa = NULL;
#ifdef HAVE_DSA
DSA *dsa = NULL;
@@ -757,8 +761,8 @@ ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]
#endif
PKeySignOptions sig_opt;
ErlNifBinary sig_bin; /* signature */
- unsigned char *tbs; /* data to be signed */
- size_t tbslen;
+ unsigned char *tbs = NULL; /* data to be signed */
+ size_t tbslen = 0;
ERL_NIF_TERM ret;
RSA *rsa = NULL;
#ifdef HAVE_DSA
@@ -934,12 +938,19 @@ static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NI
/* defaults */
if (algorithm == atom_rsa) {
- opt->rsa_mgf1_md = NULL;
- opt->rsa_oaep_label.data = NULL;
- opt->rsa_oaep_label.size = 0;
- opt->rsa_oaep_md = NULL;
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_oaep_label.data = NULL;
+ opt->rsa_oaep_label.size = 0;
+ opt->rsa_oaep_md = NULL;
opt->rsa_padding = RSA_PKCS1_PADDING;
- opt->signature_md = NULL;
+ opt->signature_md = NULL;
+ } else {
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_oaep_label.data = NULL;
+ opt->rsa_oaep_label.size = 0;
+ opt->rsa_oaep_md = NULL;
+ opt->rsa_padding = 0;
+ opt->signature_md = NULL;
}
if (enif_is_empty_list(env, options))
diff --git a/lib/crypto/c_src/srp.c b/lib/crypto/c_src/srp.c
index 2afd05d7b5..22fbedb4eb 100644
--- a/lib/crypto/c_src/srp.c
+++ b/lib/crypto/c_src/srp.c
@@ -57,6 +57,7 @@ ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
goto err;
/* g^b % N */
+ BN_set_flags(bn_exponent, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx))
goto err;
@@ -154,6 +155,7 @@ ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
/* (B - (k * g^x)) */
if ((bn_base = BN_new()) == NULL)
goto err;
+ BN_set_flags(bn_exponent, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx))
goto err;
if (!BN_mod_mul(bn_result, bn_multiplier, bn_result, bn_prime, bn_ctx))
@@ -170,6 +172,7 @@ ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
goto err;
/* (B - (k * g^x)) ^ (a + (u * x)) % N */
+ BN_set_flags(bn_exp2, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_result, bn_base, bn_exp2, bn_prime, bn_ctx))
goto err;
@@ -258,12 +261,14 @@ ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
/* (A * v^u) */
if ((bn_base = BN_new()) == NULL)
goto err;
+ BN_set_flags(bn_u, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_base, bn_verifier, bn_u, bn_prime, bn_ctx))
goto err;
if (!BN_mod_mul(bn_base, bn_A, bn_base, bn_prime, bn_ctx))
goto err;
/* (A * v^u) ^ b % N */
+ BN_set_flags(bn_b, BN_FLG_CONSTTIME);
if (!BN_mod_exp(bn_result, bn_base, bn_b, bn_prime, bn_ctx))
goto err;
diff --git a/lib/crypto/configure.in b/lib/crypto/configure.in
index a3b6673f29..acab542cda 100644
--- a/lib/crypto/configure.in
+++ b/lib/crypto/configure.in
@@ -2,7 +2,7 @@ dnl Process this file with autoconf to produce a configure script. -*-m4-*-
dnl
dnl %CopyrightBegin%
dnl
-dnl Copyright Ericsson AB 2018. All Rights Reserved.
+dnl Copyright Ericsson AB 2018-2020. All Rights Reserved.
dnl
dnl Licensed under the Apache License, Version 2.0 (the "License");
dnl you may not use this file except in compliance with the License.
@@ -25,12 +25,7 @@ dnl define([AC_CACHE_SAVE], )dnl
AC_INIT(vsn.mk)
-if test -z "$ERL_TOP" || test ! -d "$ERL_TOP" ; then
- AC_CONFIG_AUX_DIRS(autoconf)
-else
- erl_top=${ERL_TOP}
- AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
-fi
+AC_CONFIG_AUX_DIRS(${ERL_TOP}/erts/autoconf)
if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
AC_CANONICAL_HOST
@@ -84,7 +79,7 @@ elif test "x$with_ssl_zlib" = "xyes" || test "x$with_ssl_zlib" = "x"; then
AC_MSG_WARN([Cannot search for zlib; missing cross system root (erl_xcomp_sysroot).])
SSL_LINK_WITH_ZLIB=no
STATIC_ZLIB_LIBS=
- elif test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
+ elif test "$host_os" = "win32"; then
SSL_LINK_WITH_ZLIB=no
STATIC_ZLIB_LIBS=
else
@@ -177,6 +172,24 @@ AS_HELP_STRING([--disable-dynamic-ssl-lib],
*) enable_dynamic_ssl=yes ;;
esac ], enable_dynamic_ssl=yes)
+AC_ARG_ENABLE(evp-dh,
+AS_HELP_STRING([--disable-evp-dh],
+ [intentionally undocumented workaround]),
+[ case "$enableval" in
+ no) DISABLE_EVP_DH=1;;
+ *) DISABLE_EVP_DH=0;;
+ esac ], DISABLE_EVP_DH=0)
+
+
+AC_ARG_ENABLE(evp-hmac,
+AS_HELP_STRING([--disable-evp-hmac],
+ [intentionally undocumented workaround]),
+[ case "$enableval" in
+ no) DISABLE_EVP_HMAC=1;;
+ *) DISABLE_EVP_HMAC=0;;
+ esac ], DISABLE_EVP_HMAC=0)
+
+
#----------------------------------------------------------------------
# We actually might do the SSL tests twice due to late discovery of
# kerberos problems with static linking, in case we redo it all trying
@@ -216,46 +229,18 @@ case "$erl_xcomp_without_sysroot-$with_ssl" in
# of Shining Light OpenSSL, which can be found by poking in
# the uninstall section in the registry, it's worth a try...
extra_dir=""
- if test "x$MIXED_CYGWIN" = "xyes"; then
- AC_CHECK_PROG(REGTOOL, regtool, regtool, false)
- if test "$ac_cv_prog_REGTOOL" != false; then
- wrp="/machine/software/microsoft/windows/currentversion/"
- if test "x$ac_cv_sizeof_void_p" = "x8"; then
- urp="uninstall/openssl (64-bit)_is1/inno setup: app path"
- regtool_subsystem=-w
- else
- urp="uninstall/openssl (32-bit)_is1/inno setup: app path"
- regtool_subsystem=-W
- fi
- rp="$wrp$urp"
- if regtool -q $regtool_subsystem get "$rp" > /dev/null; then
- true
- else
- # Fallback to unspecified wordlength
- urp="uninstall/openssl_is1/inno setup: app path"
- rp="$wrp$urp"
- fi
- if regtool -q $regtool_subsystem get "$rp" > /dev/null; then
- ssl_install_dir=`regtool -q $regtool_subsystem get "$rp"`
- # Try hard to get rid of spaces...
- if cygpath -d "$ssl_install_dir" > /dev/null 2>&1; then
- ssl_install_dir=`cygpath -d "$ssl_install_dir"`
- fi
- extra_dir=`cygpath $ssl_install_dir`
- fi
- fi
- elif test "x$MIXED_MSYS" = "xyes"; then
+ if test "$host_os" = "win32"; then
AC_CHECK_PROG(REGTOOL, reg_query.sh, reg_query.sh, false)
if test "$ac_cv_prog_REGTOOL" != false; then
if test "x$ac_cv_sizeof_void_p" = "x8"; then
rp="HKLM/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/OpenSSL (64-bit)_is1"
else
rp="HKLM/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/OpenSSL_is1"
- fi
+ fi
key="Inno Setup: App Path"
if "$ac_cv_prog_REGTOOL" "$rp" "$key" > /dev/null; then
ssl_install_dir=`"$ac_cv_prog_REGTOOL" "$rp" "$key"`
- extra_dir=`win2msys_path.sh "$ssl_install_dir"`
+ extra_dir=`w32_path.sh -u "$ssl_install_dir"`
fi
fi
fi
@@ -266,12 +251,15 @@ case "$erl_xcomp_without_sysroot-$with_ssl" in
SSL_CRYPTO_LIBNAME=crypto
SSL_SSL_LIBNAME=ssl
+ SSL_EXTRA_LIBS=""
- if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
+ if test "$host_os" = "win32" ; then
if test "x$ac_cv_sizeof_void_p" = "x8"; then
- std_win_ssl_locations="/cygdrive/c/OpenSSL-Win64 /c/OpenSSL-Win64 /opt/local64/pgm/OpenSSL"
+ std_win_ssl_locations="/mnt/c/OpenSSL-Win64 /c/OpenSSL-Win64 /mnt/c/opt/local64/pgm/OpenSSL /opt/local64/pgm/OpenSSL /cygdrive/c/OpenSSL-Win64"
+ lib_bits=64
else
- std_win_ssl_locations="/cygdrive/c/OpenSSL-Win32 /c/OpenSSL-Win32 /cygdrive/c/OpenSSL /c/OpenSSL /opt/local/pgm/OpenSSL"
+ std_win_ssl_locations="/mnt/c/OpenSSL-Win32 /c/OpenSSL-Win32 /mnt/c/OpenSSL /c/OpenSSL /cygdrive/c/OpenSSL /opt/local/pgm/OpenSSL /opt/local32/pgm/OpenSSL /mnt/c/opt/local/pgm/OpenSSL /mnt/c/opt/local32/pgm/OpenSSL /cygdrive/c/OpenSSL-Win32"
+ lib_bits=32
fi
else
std_win_ssl_locations=""
@@ -279,12 +267,12 @@ case "$erl_xcomp_without_sysroot-$with_ssl" in
AC_MSG_CHECKING(for OpenSSL >= 0.9.8c in standard locations)
- for rdir in $extra_dir $std_win_ssl_locations $std_ssl_locations; do
+ for rdir in "$extra_dir" $std_win_ssl_locations $std_ssl_locations; do
dir="$erl_xcomp_sysroot$rdir"
if test -f "$erl_xcomp_isysroot$rdir/include/openssl/opensslv.h"; then
is_real_ssl=yes
SSL_INCDIR="$dir"
- if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
+ if test "$host_os" = "win32" ; then
if test -f "$dir/lib/VC/libeay32.lib"; then
SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
SSL_LIBDIR="$dir/lib/VC"
@@ -293,11 +281,11 @@ case "$erl_xcomp_without_sysroot-$with_ssl" in
elif test -f "$dir/lib/VC/openssl.lib"; then
SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
SSL_LIBDIR="$dir/lib/VC"
- elif test -f $dir/lib/VC/libeay32MD.lib; then
+ elif test -f "$dir/lib/VC/libeay32MD.lib"; then
SSL_CRYPTO_LIBNAME=libeay32MD
SSL_SSL_LIBNAME=ssleay32MD
if test "x$enable_dynamic_ssl" = "xno" && \
- test -f $dir/lib/VC/static/libeay32MD.lib; then
+ test -f "$dir/lib/VC/static/libeay32MD.lib"; then
SSL_RUNTIME_LIBDIR="$rdir/lib/VC/static"
SSL_LIBDIR="$dir/lib/VC/static"
else
@@ -309,6 +297,19 @@ case "$erl_xcomp_without_sysroot-$with_ssl" in
SSL_LIBDIR="$dir/lib"
SSL_CRYPTO_LIBNAME=libeay32
SSL_SSL_LIBNAME=ssleay32
+ elif test -f "$dir/lib/VC/libcrypto${lib_bits}MD.lib"; then
+ SSL_CRYPTO_LIBNAME=libcrypto${lib_bits}MD
+ # NOTE: Additional ugly extra libs at the end
+ SSL_SSL_LIBNAME="libssl${lib_bits}MD"
+ if test "x$enable_dynamic_ssl" = "xno" && \
+ test -f "$dir/lib/VC/static/$SSL_CRYPTO_LIBNAME.lib"; then
+ SSL_EXTRA_LIBS="-lCRYPT32 -lWS2_32"
+ SSL_RUNTIME_LIBDIR="$rdir/lib/VC/static"
+ SSL_LIBDIR="$dir/lib/VC/static"
+ else
+ SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
+ SSL_LIBDIR="$dir/lib/VC"
+ fi
elif test -f "$dir/lib/openssl.lib"; then
SSL_RUNTIME_LIBDIR="$rdir/lib"
SSL_LIBDIR="$dir/lib"
@@ -352,7 +353,7 @@ case "$erl_xcomp_without_sysroot-$with_ssl" in
SSL_INCLUDE="-I$dir/include"
old_CPPFLAGS=$CPPFLAGS
CPPFLAGS=$SSL_INCLUDE
- AC_EGREP_CPP(^yes$,[
+ AC_EGREP_CPP(^yes.?$,[
#include <openssl/opensslv.h>
#if OPENSSL_VERSION_NUMBER >= 0x0090803fL
yes
@@ -365,7 +366,7 @@ yes
])
CPPFLAGS=$old_CPPFLAGS
if test "x$ssl_found" = "xyes"; then
- if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
+ if test "x$host_os" = "xwin32" ; then
ssl_linkable=yes
elif test "x${SSL_CRYPTO_LIBNAME}" = "xsslcrypto"; then
# This should only be triggered seen OSE
@@ -485,7 +486,8 @@ dnl so it is - be adoptable
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
+ SSL_EXTRA_LIBS=""
+ if test "x$host_os" = "xwin32" && test -d "$with_ssl/lib/VC"; then
if test -f "$with_ssl/lib/VC/libeay32.lib"; then
SSL_LIBDIR="$with_ssl/lib/VC"
SSL_CRYPTO_LIBNAME=libeay32
@@ -500,15 +502,33 @@ dnl so it is - be adoptable
SSL_LIBDIR="$with_ssl/lib/VC/static"
else
SSL_LIBDIR="$with_ssl/lib/VC"
- fi
+ fi
+ elif test -f "$dir/lib/VC/libcrypto${lib_bits}MD.lib"; then
+ SSL_CRYPTO_LIBNAME=libcrypto${lib_bits}MD
+ # NOTE: Additional ugly extra libs at the end
+ SSL_SSL_LIBNAME="libssl${lib_bits}MD"
+ if test "x$enable_dynamic_ssl" = "xno" && \
+ test -f "$dir/lib/VC/static/$SSL_CRYPTO_LIBNAME.lib"; then
+ SSL_EXTRA_LIBS="-lCRYPT32 -lWS2_32"
+ SSL_RUNTIME_LIBDIR="$rdir/lib/VC/static"
+ SSL_LIBDIR="$dir/lib/VC/static"
+ else
+ SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
+ SSL_LIBDIR="$dir/lib/VC"
+ fi
elif test -f "$with_ssl/lib/libeay32.lib"; then
SSL_LIBDIR="$with_ssl/lib"
SSL_CRYPTO_LIBNAME=libeay32
SSL_SSL_LIBNAME=ssleay32
+ elif test -f "$dir/lib/openssl.lib"; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib"
+ SSL_LIBDIR="$dir/lib"
+ SSL_CRYPTO_LIBNAME=libcrypto
+ SSL_SSL_LIBNAME=openssl
else
# This probably wont work, but that's what the user said, so...
SSL_LIBDIR="$with_ssl/lib"
- fi
+ fi
elif test -f "$dir/lib/powerpc/libsslcrypto.a"; then
SSL_CRYPTO_LIBNAME=sslcrypto
SSL_LIBDIR="$with_ssl/lib/powerpc/"
@@ -530,7 +550,7 @@ dnl so it is - be adoptable
if test '!' -f "${SSL_LIBDIR}/lib${SSL_CRYPTO_LIBNAME}.a"; then
SSL_DYNAMIC_ONLY=yes
elif test '!' -f ${SSL_LIBDIR}/lib${SSL_CRYPTO_LIBNAME}.so -a '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.dylib"; then
- SSL_STATIC_ONLY=yes
+ SSL_STATIC_ONLY=yes
fi
SSL_INCLUDE="-I$with_ssl_incl/include"
SSL_APP=ssl
@@ -764,12 +784,15 @@ AC_SUBST(SSL_LIBDIR)
AC_SUBST(SSL_FLAGS)
AC_SUBST(SSL_CRYPTO_LIBNAME)
AC_SUBST(SSL_SSL_LIBNAME)
+AC_SUBST(SSL_EXTRA_LIBS)
AC_SUBST(SSL_DED_LD_RUNTIME_LIBRARY_PATH)
AC_SUBST(SSL_DYNAMIC_ONLY)
AC_SUBST(SSL_LINK_WITH_KERBEROS)
AC_SUBST(STATIC_KERBEROS_LIBS)
AC_SUBST(SSL_LINK_WITH_ZLIB)
AC_SUBST(STATIC_ZLIB_LIBS)
+AC_SUBST(DISABLE_EVP_DH)
+AC_SUBST(DISABLE_EVP_HMAC)
AC_OUTPUT(c_src/$host/Makefile:c_src/Makefile.in)
diff --git a/lib/crypto/doc/src/Makefile b/lib/crypto/doc/src/Makefile
index 8da494dad6..b4926d6d7c 100644
--- a/lib/crypto/doc/src/Makefile
+++ b/lib/crypto/doc/src/Makefile
@@ -27,11 +27,6 @@ VSN=$(CRYPTO_VSN)
APPLICATION=crypto
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -47,78 +42,10 @@ BOOK_FILES = book.xml
XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) $(XML_REF6_FILES) \
$(XML_PART_FILES) $(XML_CHAPTER_FILES)
-GIF_FILES =
-
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+IMAGE_FILES =
TOP_SPECS_FILE = specs.xml
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-#in ssh it looks like this: SPECS_FLAGS = -I../../../public_key/include -I../../../public_key/src -I../../..
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES) $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt valgrind:
-
-clean clean_docs clean_tex:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECS_FILES)
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6DIR)/* "$(RELEASE_PATH)/man/man6"
-
+include $(ERL_TOP)/make/doc.mk
-release_spec:
+valgrind asan:
diff --git a/lib/crypto/doc/src/algorithm_details.xml b/lib/crypto/doc/src/algorithm_details.xml
index d40664bb9f..7dd57c55bf 100644
--- a/lib/crypto/doc/src/algorithm_details.xml
+++ b/lib/crypto/doc/src/algorithm_details.xml
@@ -37,21 +37,21 @@
<section>
<title>Ciphers</title>
- <p>A <seealso marker="crypto#type-cipher">cipher</seealso> in the
- <seealso marker="crypto:new_api#the-new-api">new api</seealso>
+ <p>A <seetype marker="crypto#cipher">cipher</seetype> in the
+ <seeguide marker="crypto:new_api#the-new-api">new api</seeguide>
is categorized as either
- <seealso marker="crypto#type-cipher_no_iv">cipher_no_iv()</seealso>,
- <seealso marker="crypto#type-cipher_iv">cipher_iv()</seealso> or
- <seealso marker="crypto#type-cipher_aead">cipher_aead()</seealso>.
+ <seetype marker="crypto#cipher_no_iv">cipher_no_iv()</seetype>,
+ <seetype marker="crypto#cipher_iv">cipher_iv()</seetype> or
+ <seetype marker="crypto#cipher_aead">cipher_aead()</seetype>.
The letters IV are short for <i>Initialization Vector</i> and
AEAD is an abreviation of <i>Authenticated Encryption with Associated Data</i>.
</p>
<p>Due to irregular naming conventions, some cipher names in the old api are
substitued by new names in the new api. For a list of retired names, see
- <seealso marker="crypto:new_api#retired-cipher-names">Retired cipher names</seealso>.
+ <seeguide marker="crypto:new_api#retired-cipher-names">Retired cipher names</seeguide>.
</p>
<p>To dynamically check availability, check that the name in the <i>Cipher and Mode</i> column is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(ciphers)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(ciphers)</seemfa>.
</p>
<section>
@@ -59,8 +59,8 @@
<p>To be used with:
</p>
<list>
- <item><seealso marker="crypto#crypto_one_time/4">crypto_one_time/4</seealso></item>
- <item><seealso marker="crypto#crypto_init/3">crypto_init/3</seealso></item>
+ <item><seemfa marker="crypto#crypto_one_time/4">crypto_one_time/4</seemfa></item>
+ <item><seemfa marker="crypto#crypto_init/3">crypto_init/3</seemfa></item>
</list>
<p>The ciphers are:
</p>
@@ -85,9 +85,9 @@
<p>To be used with:
</p>
<list>
- <item><seealso marker="crypto#crypto_one_time/5">crypto_one_time/5</seealso></item>
- <item><seealso marker="crypto#crypto_init/4">crypto_init/4</seealso></item>
- <item><seealso marker="crypto#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seealso></item>
+ <item><seemfa marker="crypto#crypto_one_time/5">crypto_one_time/5</seemfa></item>
+ <item><seemfa marker="crypto#crypto_init/4">crypto_init/4</seemfa></item>
+ <item><seemfa marker="crypto#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seemfa></item>
</list>
<p>The ciphers are:
</p>
@@ -130,8 +130,8 @@
<p>To be used with:
</p>
<list>
- <item><seealso marker="crypto#crypto_one_time_aead/6">crypto_one_time_aead/6</seealso></item>
- <item><seealso marker="crypto#crypto_one_time_aead/7">crypto_one_time_aead/7</seealso></item>
+ <item><seemfa marker="crypto#crypto_one_time_aead/6">crypto_one_time_aead/6</seemfa></item>
+ <item><seemfa marker="crypto#crypto_one_time_aead/7">crypto_one_time_aead/7</seemfa></item>
</list>
<p>The ciphers are:
</p>
@@ -162,8 +162,8 @@
<section>
<title>Message Authentication Codes (MACs)</title>
- <p>To be used in <seealso marker="crypto#mac-4">mac/4</seealso> and
- <seealso marker="crypto:new_api#macs--message-authentication-codes-">related functions</seealso>.
+ <p>To be used in <seemfa marker="crypto#mac/4">mac/4</seemfa> and
+ <seeguide marker="crypto:new_api#macs--message-authentication-codes-">related functions</seeguide>.
</p>
<section>
@@ -172,9 +172,9 @@
</p>
<p>To dynamically check availability, check that the name <c>cmac</c> is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(macs)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(macs)</seemfa>.
Also check that the name in the <i>Cipher and Mode</i> column is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(ciphers)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(ciphers)</seemfa>.
</p>
<table>
<row>
@@ -203,9 +203,9 @@
<p>Available in all OpenSSL compatible with Erlang CRYPTO if not disabled by configuration.
</p>
<p>To dynamically check availability, check that the name <c>hmac</c> is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(macs)</seealso> and
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(macs)</seemfa> and
that the hash name is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(hashs)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(hashs)</seemfa>.
</p>
<table>
@@ -238,7 +238,7 @@
<p>POLY1305 is available with OpenSSL 1.1.1 or later if not disabled by configuration.
</p>
<p>To dynamically check availability, check that the name <c>poly1305</c> is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(macs)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(macs)</seemfa>.
</p>
<p>The poly1305 mac wants an 32 bytes key and produces a 16 byte MAC by default.
</p>
@@ -250,7 +250,7 @@
<title>Hash</title>
<p>To dynamically check availability, check that the wanted name in the <i>Names</i> column is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(hashs)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(hashs)</seemfa>.
</p>
<table>
@@ -275,7 +275,7 @@
<title>RSA</title>
<p>RSA is available with all OpenSSL versions compatible with Erlang CRYPTO if not disabled by configuration.
To dynamically check availability, check that the atom <c>rsa</c> is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(public_keys)</seemfa>.
</p>
<warning>
<!-- In RefMan rsa_opt(), rsa_sign_verify_opt() and User's man RSA -->
@@ -347,7 +347,7 @@
<title>DSS</title>
<p>DSS is available with OpenSSL versions compatible with Erlang CRYPTO if not disabled by configuration.
To dynamically check availability, check that the atom <c>dss</c> is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(public_keys)</seemfa>.
</p>
</section>
@@ -355,11 +355,11 @@
<title>ECDSA</title>
<p>ECDSA is available with OpenSSL 0.9.8o or later if not disabled by configuration.
To dynamically check availability, check that the atom <c>ecdsa</c> is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(public_keys)</seemfa>.
If the atom <c>ec_gf2m</c> also is present, the characteristic two field curves are available.
</p>
<p>The actual supported named curves could be checked by examining the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(curves)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(curves)</seemfa>.
</p>
</section>
@@ -367,11 +367,11 @@
<title>EdDSA</title>
<p>EdDSA is available with OpenSSL 1.1.1 or later if not disabled by configuration.
To dynamically check availability, check that the atom <c>eddsa</c> is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(public_keys)</seemfa>.
</p>
<p>Support for the curves ed25519 and ed448 is implemented.
The actual supported named curves could be checked by examining the list with the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(curves)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(curves)</seemfa>.
</p>
</section>
@@ -380,7 +380,7 @@
<p>Diffie-Hellman computations are available with OpenSSL versions compatible with Erlang CRYPTO
if not disabled by configuration.
To dynamically check availability, check that the atom <c>dh</c> is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(public_keys)</seemfa>.
</p>
</section>
@@ -388,7 +388,7 @@
<title>Elliptic Curve Diffie-Hellman</title>
<p>Elliptic Curve Diffie-Hellman is available with OpenSSL 0.9.8o or later if not disabled by configuration.
To dynamically check availability, check that the atom <c>ecdh</c> is present in the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(public_keys)</seemfa>.
</p>
<p>The Edward curves <c>x25519</c> and <c>x448</c> are supported with OpenSSL 1.1.1 or later
@@ -396,7 +396,7 @@
</p>
<p>The actual supported named curves could be checked by examining the
- list returned by <seealso marker="crypto#supports-1">crypto:supports(curves)</seealso>.
+ list returned by <seemfa marker="crypto#supports/1">crypto:supports(curves)</seemfa>.
</p>
</section>
diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml
index d2e6429022..e0b190a508 100644
--- a/lib/crypto/doc/src/crypto.xml
+++ b/lib/crypto/doc/src/crypto.xml
@@ -178,13 +178,13 @@
<note>
<p>The actual supported algorithms and features depends on their availability in the actual libcrypto used.
- See the <seealso marker="crypto:crypto_app">crypto (App)</seealso> about dependencies.
+ See the <seeapp marker="crypto:crypto_app">crypto (App)</seeapp> about dependencies.
</p>
<p>Enabling FIPS mode will also disable algorithms and features.
</p>
</note>
- <p>The <seealso marker="users_guide">CRYPTO User's Guide</seealso> has more information on
+ <p>The <seeguide marker="index">CRYPTO User's Guide</seeguide> has more information on
FIPS, Engines and Algorithm Details like key lengths.
</p>
</description>
@@ -210,11 +210,47 @@
<name name="cipher_aead"/>
<desc>
<p>Ciphers known by the CRYPTO application when using the
- <seealso marker="crypto:new_api#the-new-api">new API</seealso>.</p>
+ <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.</p>
<p>Note that this list might be reduced if the underlying libcrypto does not support all of them.</p>
</desc>
</datatype>
+ <datatype>
+ <name name="crypto_opts"/>
+ <name name="crypto_opt"/>
+ <desc>
+ <p>Selects encryption (<c>{encrypt,true}</c>) or decryption (<c>{encrypt,false}</c>)
+ in the <seeguide marker="crypto:new_api#the-new-api"><i>New API</i></seeguide>.
+ </p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="padding"/>
+ <desc>
+ <p>This option handles padding in the last block. If not set, no padding is done
+ and any bytes in the last unfilled block is silently discarded.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="cryptolib_padding"/>
+ <desc>
+ <p>The <c>cryptolib_padding</c> are paddings that may be present in the underlying cryptolib
+ linked to the Erlang/OTP crypto app.
+ </p>
+ <p>For OpenSSL, see the <url href="http:www.openssl.org">OpenSSL documentation</url>.
+ and find <c>EVP_CIPHER_CTX_set_padding()</c> in cryptolib for your linked version.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="otp_padding"/>
+ <desc>
+ <p>Erlang/OTP adds a either padding of zeroes or padding with random bytes.</p>
+ </desc>
+ </datatype>
+
<datatype_title>Ciphers, old API</datatype_title>
<datatype>
<name name="block_cipher_with_iv"/>
@@ -255,7 +291,7 @@
<name name="ecb_cipher"/>
<desc>
<p>Ciphers known by the CRYPTO application when using the
- <seealso marker="crypto:new_api#the-old-api">old API</seealso>.</p>
+ <seeguide marker="crypto:new_api#the-old-api">old API</seeguide>.</p>
<p>Note that this list might be reduced if the underlying libcrypto does not support all of them.</p>
</desc>
</datatype>
@@ -279,10 +315,10 @@
<name name="retired_ecb_cipher_aliases"/>
<desc>
<p>Alternative, old names of ciphers known by the CRYPTO application when using the
- <seealso marker="crypto:new_api#the-old-api">old API</seealso>.
- See <seealso marker="crypto:new_api#retired-cipher-names">Retired cipher names</seealso> for names to
+ <seeguide marker="crypto:new_api#the-old-api">old API</seeguide>.
+ See <seeguide marker="crypto:new_api#retired-cipher-names">Retired cipher names</seeguide> for names to
use instead to be prepared for an easy convertion to the
- <seealso marker="crypto:new_api#the-new-api">new API</seealso>.
+ <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
</p>
<p>Note that this list might be reduced if the underlying libcrypto does not support all of them.</p>
</desc>
@@ -375,7 +411,7 @@
<name name="des3_key"/>
<desc>
<p>For keylengths, iv-sizes and blocksizes see the
- <seealso marker="crypto:algorithm_details#ciphers">User's Guide</seealso>.
+ <seeguide marker="crypto:algorithm_details#ciphers">User's Guide</seeguide>.
</p>
<p>A key for des3 is a list of three iolists</p>
</desc>
@@ -398,9 +434,10 @@
<code>rsa_private() = [E, N, D] | [E, N, D, P1, P2, E1, E2, C]</code>
<p>Where E is the public exponent, N is public modulus and D is
the private exponent. The longer key format contains redundant
- information that will make the calculation faster. P1,P2 are first
- and second prime factors. E1,E2 are first and second exponents. C
- is the CRT coefficient. Terminology is taken from <url href="http://www.ietf.org/rfc/rfc3477.txt"> RFC 3447</url>.</p>
+ information that will make the calculation faster. P1 and P2 are first
+ and second prime factors. E1 and E2 are first and second exponents. C
+ is the CRT coefficient. The terminology is taken from
+ <url href="http://www.ietf.org/rfc/rfc3447.txt"> RFC 3447</url>.</p>
</desc>
</datatype>
@@ -447,19 +484,15 @@
<datatype>
<name name="srp_gen_params"/>
<name name="srp_comp_params"/>
- <desc>
- <marker id="type-srp_user_gen_params"/>
- <code>srp_user_gen_params() = [DerivedKey::binary(), Prime::binary(), Generator::binary(), Version::atom()]</code>
- <marker id="type-srp_host_gen_params"/>
- <code>srp_host_gen_params() = [Verifier::binary(), Prime::binary(), Version::atom() ]</code>
- <marker id="type-srp_user_comp_params"/>
- <code>srp_user_comp_params() = [DerivedKey::binary(), Prime::binary(), Generator::binary(), Version::atom() | ScramblerArg::list()]</code>
- <marker id="type-srp_host_comp_params"/>
- <code>srp_host_comp_params() = [Verifier::binary(), Prime::binary(), Version::atom() | ScramblerArg::list()]</code>
- <p>Where Verifier is <c>v</c>, Generator is <c>g</c> and Prime is<c> N</c>, DerivedKey is <c>X</c>, and Scrambler is
- <c>u</c> (optional will be generated if not provided) from <url href="http://srp.stanford.edu/design.html">SRP design</url>
- Version = '3' | '6' | '6a'
- </p>
+ <name>srp_user_gen_params() = [DerivedKey::binary(), Prime::binary(), Generator::binary(), Version::atom()]</name>
+ <name>srp_host_gen_params() = [Verifier::binary(), Prime::binary(), Version::atom() ]</name>
+ <name>srp_user_comp_params() = [DerivedKey::binary(), Prime::binary(), Generator::binary(), Version::atom() | ScramblerArg::list()]</name>
+ <name>srp_host_comp_params() = [Verifier::binary(), Prime::binary(), Version::atom() | ScramblerArg::list()]</name>
+ <desc>
+ <p>Where Verifier is <c>v</c>, Generator is <c>g</c> and Prime is<c> N</c>, DerivedKey is <c>X</c>, and Scrambler is
+ <c>u</c> (optional will be generated if not provided) from <url href="http://srp.stanford.edu/design.html">SRP design</url>
+ Version = '3' | '6' | '6a'
+ </p>
</desc>
</datatype>
@@ -549,7 +582,7 @@
<name name="engine_key_ref"/>
<name name="engine_ref"/>
<desc>
- <p>The result of a call to <seealso marker="#engine_load-3">engine_load/3</seealso>.
+ <p>The result of a call to <seemfa marker="#engine_load/3">engine_load/3</seemfa>.
</p>
</desc>
</datatype>
@@ -578,7 +611,7 @@
<datatype>
<name name="engine_cmnd"/>
<desc>
- <p>Pre and Post commands for <seealso marker="#engine_load-3">engine_load/3 and /4</seealso>.
+ <p>Pre and Post commands for <seemfa marker="#engine_load/3">engine_load/3 and /4</seemfa>.
</p>
</desc>
</datatype>
@@ -608,7 +641,7 @@
<p>The exception <c>error:notsup</c> signifies that the algorithm is known but is not supported
by current underlying libcrypto or explicitly disabled when building that.
</p>
- <p>For a list of supported algorithms, see <seealso marker="#supports-0">supports/0</seealso>.
+ <p>For a list of supported algorithms, see <seemfa marker="#supports/0">supports/0</seemfa>.
</p>
</desc>
</datatype>
@@ -617,7 +650,7 @@
<name name="descriptive_error"/>
<desc>
<p>This is a more developed variant of the older
- <seealso marker="#type-run_time_error">run_time_error()</seealso>.
+ <seetype marker="#run_time_error">run_time_error()</seetype>.
</p>
<p>The exception is:</p>
<pre>
@@ -647,7 +680,7 @@
<tag><c>error</c></tag>
<item><p>An error condition that should not occur, for example a memory allocation failed or
the underlying cryptolib returned an error code, for example "Can't initialize context, step 1".
- Thoose text usually needs searching the C-code to be understood.</p>
+ Those text usually needs searching the C-code to be understood.</p>
</item>
</taglist>
<p>To catch the exception, use for example:</p>
@@ -665,16 +698,22 @@
</datatypes>
<!--================ FUNCTIONS ================-->
- <section>
- <title>New API</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>New API</title>
+ </fsdescription>
<func>
<name name="crypto_init" arity="3" since="OTP 22.0"/>
<fsummary>Initializes a series of encryptions or decryptions</fsummary>
<desc>
- <p>As <seealso marker="#crypto_init/4">crypto_init/4</seealso> but for ciphers without IVs.</p>
+ <p>Part of the <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
+ </p>
+ <p>Equivalent to the call
+ <seemfa marker="#crypto_init/4"><c>crypto_init(Cipher, Key, &lt;&lt;>>, FlagOrOptions)</c></seemfa>.
+ It is intended for ciphers without an IV (nounce).
+ </p>
</desc>
</func>
@@ -682,16 +721,54 @@
<name name="crypto_init" arity="4" since="OTP 22.0"/>
<fsummary>Initializes a series of encryptions or decryptions</fsummary>
<desc>
- <p>Part of the <seealso marker="crypto:new_api#the-new-api">new API</seealso>.
+ <p>Part of the <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
Initializes a series of encryptions or decryptions and creates an internal state
with a reference that is returned.
+ </p>
+ <p>If <c>IV = &lt;&lt;>></c>, no IV is used. This is intended for ciphers without an IV (nounce).
+ See <seemfa marker="#crypto_init/3">crypto_init/3</seemfa>.
+ </p>
+ <p>
+ If <c>IV = undefined</c>, the IV must be added by calls to
+ <seemfa marker="crypto#crypto_dyn_iv_update/3">crypto_dyn_iv_update/3</seemfa>. This is intended
+ for cases where the IV (nounce) need to be changed for each encryption and decryption.
+ See <seemfa marker="#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seemfa>.
+ </p>
+ <p>
The actual encryption or decryption is done by
- <seealso marker="crypto#crypto_update/2">crypto_update/2</seealso>.
+ <seemfa marker="crypto#crypto_update/2">crypto_update/2</seemfa> (or
+ <seemfa marker="crypto#crypto_dyn_iv_update/3">crypto_dyn_iv_update/3</seemfa>
+ ).
+ </p>
+ <p>For encryption, set the <c>FlagOrOptions</c> to <c>true</c> or <c>[{encrypt,true}]</c>.
+ For decryption, set it to <c>false</c> or <c>[{encrypt,false}]</c>.
+ </p>
+ <p>Padding could be enabled with the option
+ <seetype marker="#padding">{padding,Padding}</seetype>. The
+ <seetype marker="#cryptolib_padding">cryptolib_padding</seetype> enables
+ <c>pkcs_padding</c> or no padding (<c>none</c>).
+ The paddings <c>zero</c> or <c>random</c> fills the last part of the last block with zeroes or random bytes.
+ If the last block is already full, nothing is added.
+ </p>
+ <p>In decryption, the <seetype marker="#cryptolib_padding">cryptolib_padding</seetype> removes
+ such padding, if present.
+ The <seetype marker="#otp_padding">otp_padding</seetype> is not
+ removed - it has to be done elsewhere.
+ </p>
+ <p>If padding is <c>{padding,none}</c> or not specifed and the total data from all subsequent
+ <seemfa marker="crypto#crypto_update/2">crypto_updates</seemfa> does
+ not fill the last block fully, that last data is lost. In case of <c>{padding,none}</c> there will
+ be an error in this case. If padding is not specified, the bytes of the unfilled block is silently
+ discarded.
+ </p>
+ <p>The actual padding is performed by
+ <seemfa marker="crypto#crypto_final/1">crypto_final/1</seemfa>.
</p>
- <p>For encryption, set the <c>EncryptFlag</c> to <c>true</c>. For decryption, set it to <c>false</c>.
+ <p>
+ For blocksizes call <seemfa marker="#cipher_info/1">cipher_info/1</seemfa>.
</p>
- <p>See <seealso marker="crypto:new_api#examples-of-crypto_init-4-and-crypto_update-2">
- examples in the User's Guide.</seealso>
+ <p>See <seeguide marker="crypto:new_api#examples-of-crypto_init-4-and-crypto_update-2">
+ examples in the User's Guide.</seeguide>
</p>
</desc>
</func>
@@ -700,17 +777,17 @@
<name name="crypto_update" arity="2" since="OTP 22.0"/>
<fsummary>Do an actual crypto operation on a part of the full text</fsummary>
<desc>
- <p>Part of the <seealso marker="crypto:new_api#the-new-api">new API</seealso>.
+ <p>Part of the <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
It does an actual crypto operation on a part of the full text. If the part is less
than a number of full blocks, only the full blocks (possibly none) are encrypted
or decrypted and the remaining bytes are saved to the next <c>crypto_update</c> operation.
The <c>State</c> should be created with
- <seealso marker="crypto#crypto_init/3">crypto_init/3</seealso>
+ <seemfa marker="crypto#crypto_init/3">crypto_init/3</seemfa>
or
- <seealso marker="crypto#crypto_init/4">crypto_init/4</seealso>.
+ <seemfa marker="crypto#crypto_init/4">crypto_init/4</seemfa>.
</p>
- <p>See <seealso marker="crypto:new_api#examples-of-crypto_init-4-and-crypto_update-2">
- examples in the User's Guide.</seealso>
+ <p>See <seeguide marker="crypto:new_api#examples-of-crypto_init-4-and-crypto_update-2">
+ examples in the User's Guide.</seeguide>
</p>
</desc>
</func>
@@ -719,13 +796,60 @@
<name name="crypto_dyn_iv_init" arity="3" since="OTP 22.0"/>
<fsummary>Initializes a series of encryptions or decryptions where the IV is provided later</fsummary>
<desc>
- <p>Part of the <seealso marker="crypto:new_api#the-new-api">new API</seealso>.
- Initializes a series of encryptions or decryptions where the IV is provided later.
+ <p>Part of the <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
+ </p>
+ <p>Initializes a series of encryptions or decryptions where the IV is provided later.
The actual encryption or decryption is done by
- <seealso marker="crypto#crypto_dyn_iv_update/3">crypto_dyn_iv_update/3</seealso>.
+ <seemfa marker="crypto#crypto_dyn_iv_update/3">crypto_dyn_iv_update/3</seemfa>.
+ </p>
+ <p>The function is equivalent to
+ <seemfa marker="#crypto_init/4"><c>crypto_init(Cipher, Key, undefined, FlagOrOptions)</c></seemfa>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="crypto_final" arity="1" since=""/>
+ <fsummary>Ends a series of encryptions or decryptions</fsummary>
+ <desc>
+ <p>Part of the <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
+ </p>
+ <p>
+ Finalizes a series of encryptions or decryptions and delivers the final bytes of the final block.
+ The data returned from this function may be empty if no padding was enabled in
+ <seemfa marker="#crypto_init/3">crypto_init/3,4</seemfa> or
+ <seemfa marker="#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seemfa>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="crypto_get_data" arity="1" since=""/>
+ <fsummary>Get information about crypto states</fsummary>
+ <desc>
+ <p>Part of the <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
</p>
- <p>For encryption, set the <c>EncryptFlag</c> to <c>true</c>. For decryption, set it to <c>false</c>.
+ <p>
+ Returns information about the State in the argument. The information is the form of a map,
+ which currently contains at least:
</p>
+ <taglist>
+ <tag><c>size</c></tag>
+ <item>The number of bytes encrypted or decrypted so far.
+ </item>
+ <tag><c>padding_size</c></tag>
+ <item>After a call to
+ <seemfa marker="#crypto_final/1">crypto_final/1</seemfa> it contains
+ the number of bytes padded. Otherwise 0.
+ </item>
+ <tag><c>padding_type</c></tag>
+ <item>The type of the padding as provided in the call ot
+ <seemfa marker="#crypto_init/3">crypto_init/3,4</seemfa>.
+ </item>
+ <tag><c>encrypt</c></tag>
+ <item>Is <c>true</c> if encryption is performed. It is <c>false</c> otherwise.
+ </item>
+ </taglist>
</desc>
</func>
@@ -733,10 +857,10 @@
<name name="crypto_dyn_iv_update" arity="3" since="OTP 22.0"/>
<fsummary>Do an actual crypto operation on a part of the full text and the IV is supplied for each part</fsummary>
<desc>
- <p>Part of the <seealso marker="crypto:new_api#the-new-api">new API</seealso>.
+ <p>Part of the <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
Do an actual crypto operation on a part of the full text and the IV is supplied for each part.
The <c>State</c> should be created with
- <seealso marker="crypto#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seealso>.
+ <seemfa marker="crypto#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seemfa>.
</p>
</desc>
</func>
@@ -745,7 +869,7 @@
<name name="crypto_one_time" arity="4" since="OTP 22.0"/>
<fsummary>Do a complete encrypt or decrypt of the full text</fsummary>
<desc>
- <p>As <seealso marker="#crypto_one_time/5">crypto_one_time/5</seealso> but for ciphers without IVs.</p>
+ <p>As <seemfa marker="#crypto_one_time/5">crypto_one_time/5</seemfa> but for ciphers without IVs.</p>
</desc>
</func>
@@ -753,12 +877,14 @@
<name name="crypto_one_time" arity="5" since="OTP 22.0"/>
<fsummary>Do a complete encrypt or decrypt of the full text</fsummary>
<desc>
- <p>Part of the <seealso marker="crypto:new_api#the-new-api">new API</seealso>.
+ <p>Part of the <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
Do a complete encrypt or decrypt of the full text in the argument <c>Data</c>.
</p>
- <p>For encryption, set the <c>EncryptFlag</c> to <c>true</c>. For decryption, set it to <c>false</c>.
+ <p>For encryption, set the <c>FlagOrOptions</c> to <c>true</c>. For decryption, set it to <c>false</c>.
+ For setting other options, see
+ <seemfa marker="crypto#crypto_init/4">crypto_init/4</seemfa>.
</p>
- <p>See <seealso marker="crypto:new_api#example-of-crypto_one_time-5">examples in the User's Guide.</seealso>
+ <p>See <seeguide marker="crypto:new_api#example-of-crypto_one_time-5">examples in the User's Guide.</seeguide>
</p>
</desc>
</func>
@@ -768,7 +894,7 @@
<name name="crypto_one_time_aead" arity="7" since="OTP 22.0"/>
<fsummary>Do a complete encrypt or decrypt with an AEAD cipher of the full text</fsummary>
<desc>
- <p>Part of the <seealso marker="crypto:new_api#the-new-api">new API</seealso>.
+ <p>Part of the <seeguide marker="crypto:new_api#the-new-api">new API</seeguide>.
Do a complete encrypt or decrypt with an AEAD cipher of the full text.
</p>
<p>For encryption, set the <c>EncryptFlag</c> to <c>true</c> and set the <c>TagOrTagLength</c>
@@ -778,7 +904,7 @@
<p>For decryption, set the <c>EncryptFlag</c> to <c>false</c> and put the tag to be checked
in the argument <c>TagOrTagLength</c>.
</p>
- <p>See <seealso marker="crypto:new_api#example-of-crypto_one_time_aead-6">examples in the User's Guide.</seealso>
+ <p>See <seeguide marker="crypto:new_api#example-of-crypto_one_time_aead-6">examples in the User's Guide.</seeguide>
</p>
</desc>
</func>
@@ -789,7 +915,7 @@
<desc>
<p> Can be used to determine which crypto algorithms that are supported
by the underlying libcrypto library</p>
- <p>See <seealso marker="#hash_info-1">hash_info/1</seealso> and <seealso marker="#cipher_info-1">cipher_info/1</seealso>
+ <p>See <seemfa marker="#hash_info/1">hash_info/1</seemfa> and <seemfa marker="#cipher_info/1">cipher_info/1</seemfa>
for information about the hash and cipher algorithms.
</p>
</desc>
@@ -799,7 +925,7 @@
<name name="mac" arity="3" since="OTP 22.1"/>
<fsummary></fsummary>
<desc>
- <p>Short for <seealso marker="#mac-4">mac(Type, undefined, Key, Data)</seealso>.
+ <p>Short for <seemfa marker="#mac/4">mac(Type, undefined, Key, Data)</seemfa>.
</p>
</desc>
</func>
@@ -815,31 +941,31 @@
</p>
<list>
<item>For <c>hmac</c> it is a hash algorithm, see
- <seealso marker="algorithm_details#hmac">Algorithm Details</seealso> in the User's Guide.
+ <seeguide marker="algorithm_details#hmac">Algorithm Details</seeguide> in the User's Guide.
</item>
<item>For <c>cmac</c> it is a cipher suitable for cmac, see
- <seealso marker="algorithm_details#cmac">Algorithm Details</seealso> in the User's Guide.
+ <seeguide marker="algorithm_details#cmac">Algorithm Details</seeguide> in the User's Guide.
</item>
<item>For <c>poly1305</c> it should be set to <c>undefined</c> or the
- <seealso marker="#mac_init-2">mac/2</seealso> function could be used instead, see
- <seealso marker="algorithm_details#poly1305">Algorithm Details</seealso> in the User's Guide.
+ <seemfa marker="#mac_init/2">mac/2</seemfa> function could be used instead, see
+ <seeguide marker="algorithm_details#poly1305">Algorithm Details</seeguide> in the User's Guide.
</item>
</list>
<p><c>Key</c> is the authentication key with a length according to the
<c>Type</c> and <c>SubType</c>.
The key length could be found with the
- <seealso marker="#hash_info-1">hash_info/1</seealso> (<c>hmac</c>) for and
- <seealso marker="#cipher_info-1">cipher_info/1</seealso> (<c>cmac</c>)
+ <seemfa marker="#hash_info/1">hash_info/1</seemfa> (<c>hmac</c>) for and
+ <seemfa marker="#cipher_info/1">cipher_info/1</seemfa> (<c>cmac</c>)
functions. For <c>poly1305</c> the key length is 32 bytes. Note that
the cryptographic quality of the key is not checked.
</p>
<p>The <c>Mac</c> result will have a default length depending on the <c>Type</c> and <c>SubType</c>.
- To set a shorter length, use <seealso marker="#macN-4">macN/4</seealso> or
- <seealso marker="#macN-5">macN/5</seealso> instead.
+ To set a shorter length, use <seemfa marker="#macN/4">macN/4</seemfa> or
+ <seemfa marker="#macN/5">macN/5</seemfa> instead.
The default length is documented in
- <seealso marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seealso>
+ <seeguide marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seeguide>
in the User's Guide.
</p>
</desc>
@@ -849,7 +975,7 @@
<name name="macN" arity="4" since="OTP 22.1"/>
<fsummary></fsummary>
<desc>
- <p>Short for <seealso marker="#macN-5">macN(Type, undefined, Key, Data, MacLength)</seealso>.
+ <p>Short for <seemfa marker="#macN/5">macN(Type, undefined, Key, Data, MacLength)</seemfa>.
</p>
</desc>
</func>
@@ -859,7 +985,7 @@
<fsummary></fsummary>
<desc>
<p>Computes a MAC (Message Authentication Code)
- as <seealso marker="#mac-3">mac/3</seealso> and <seealso marker="#mac-4">mac/4</seealso> but
+ as <seemfa marker="#mac/3">mac/3</seemfa> and <seemfa marker="#mac/4">mac/4</seemfa> but
<c>MacLength</c> will limit the size of the resultant <c>Mac</c> to
at most <c>MacLength</c> bytes.
Note that if <c>MacLength</c> is greater than the actual number of
@@ -867,7 +993,7 @@
that shorter length instead.
</p>
<p>The max <c>MacLength</c> is documented in
- <seealso marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seealso>
+ <seeguide marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seeguide>
in the User's Guide.
</p>
</desc>
@@ -877,7 +1003,7 @@
<name name="mac_init" arity="2" since="OTP 22.1"/>
<fsummary></fsummary>
<desc>
- <p>Short for <seealso marker="#mac_init-3">mac_init(Type, undefined, Key)</seealso>.
+ <p>Short for <seemfa marker="#mac_init/3">mac_init(Type, undefined, Key)</seemfa>.
</p>
</desc>
</func>
@@ -895,35 +1021,35 @@
</p>
<list>
<item>For <c>hmac</c> it is a hash algorithm, see
- <seealso marker="algorithm_details#hmac">Algorithm Details</seealso> in the User's Guide.
+ <seeguide marker="algorithm_details#hmac">Algorithm Details</seeguide> in the User's Guide.
</item>
<item>For <c>cmac</c> it is a cipher suitable for cmac, see
- <seealso marker="algorithm_details#cmac">Algorithm Details</seealso> in the User's Guide.
+ <seeguide marker="algorithm_details#cmac">Algorithm Details</seeguide> in the User's Guide.
</item>
<item>For <c>poly1305</c> it should be set to <c>undefined</c> or the
- <seealso marker="#mac_init-2">mac/2</seealso> function could be used instead, see
- <seealso marker="algorithm_details#poly1305">Algorithm Details</seealso> in the User's Guide.
+ <seemfa marker="#mac_init/2">mac/2</seemfa> function could be used instead, see
+ <seeguide marker="algorithm_details#poly1305">Algorithm Details</seeguide> in the User's Guide.
</item>
</list>
<p><c>Key</c> is the authentication key with a length according to the
<c>Type</c> and <c>SubType</c>.
The key length could be found with the
- <seealso marker="#hash_info-1">hash_info/1</seealso> (<c>hmac</c>) for and
- <seealso marker="#cipher_info-1">cipher_info/1</seealso> (<c>cmac</c>)
+ <seemfa marker="#hash_info/1">hash_info/1</seemfa> (<c>hmac</c>) for and
+ <seemfa marker="#cipher_info/1">cipher_info/1</seemfa> (<c>cmac</c>)
functions. For <c>poly1305</c> the key length is 32 bytes. Note that
the cryptographic quality of the key is not checked.
</p>
<p>The returned <c>State</c> should be used in one or more subsequent calls to
- <seealso marker="#mac_update-2">mac_update/2</seealso>.
+ <seemfa marker="#mac_update/2">mac_update/2</seemfa>.
The MAC value is finally returned by calling
- <seealso marker="#mac_final-1">mac_final/1</seealso> or
- <seealso marker="#mac_finalN-2">mac_finalN/2</seealso>.
+ <seemfa marker="#mac_final/1">mac_final/1</seemfa> or
+ <seemfa marker="#mac_finalN/2">mac_finalN/2</seemfa>.
</p>
- <p>See <seealso marker="crypto:new_api#example-of-mac_init-mac_update-and-mac_final">
- examples in the User's Guide.</seealso>
+ <p>See <seeguide marker="crypto:new_api#example-of-mac_init-mac_update-and-mac_final">
+ examples in the User's Guide.</seeguide>
</p>
</desc>
</func>
@@ -936,8 +1062,8 @@
could be of any length.
</p>
<p>The <c>State0</c> is the State value originally from a MAC init function, that is
- <seealso marker="#mac_init-2">mac_init/2</seealso>,
- <seealso marker="#mac_init-3">mac_init/3</seealso> or
+ <seemfa marker="#mac_init/2">mac_init/2</seemfa>,
+ <seemfa marker="#mac_init/3">mac_init/3</seemfa> or
a previous call of <c>mac_update/2</c>.
The value <c>State0</c> is returned unchanged by the function as <c>State</c>.
</p>
@@ -950,10 +1076,10 @@
<desc>
<p>Finalizes the MAC operation referenced by <c>State</c>. The <c>Mac</c> result will have
a default length depending on the <c>Type</c> and <c>SubType</c> in the
- <seealso marker="#mac_init-3">mac_init/2,3</seealso> call.
- To set a shorter length, use <seealso marker="#mac_finalN-2">mac_finalN/2</seealso> instead.
+ <seemfa marker="#mac_init/3">mac_init/2,3</seemfa> call.
+ To set a shorter length, use <seemfa marker="#mac_finalN/2">mac_finalN/2</seemfa> instead.
The default length is documented in
- <seealso marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seealso>
+ <seeguide marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seeguide>
in the User's Guide.
</p>
</desc>
@@ -971,18 +1097,19 @@
that shorter length instead.
</p>
<p>The max <c>MacLength</c> is documented in
- <seealso marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seealso>
+ <seeguide marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seeguide>
in the User's Guide.
</p>
</desc>
</func>
</funcs>
- <section>
- <title>API kept from previous versions</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>API kept from previous versions</title>
+ </fsdescription>
<func>
<name name="bytes_to_integer" arity="1" since="OTP R16B01"/>
<fsummary>Convert binary representation, of an integer, to an Erlang integer.</fsummary>
@@ -997,7 +1124,7 @@
<fsummary>Computes the shared secret</fsummary>
<desc>
<p>Computes the shared secret from the private key and the other party's public key.
- See also <seealso marker="public_key:public_key#compute_key-2">public_key:compute_key/2</seealso>
+ See also <seemfa marker="public_key:public_key#compute_key/2">public_key:compute_key/2</seemfa>
</p>
</desc>
</func>
@@ -1017,7 +1144,7 @@
<fsummary>Generates a public key of type <c>Type</c></fsummary>
<desc>
<p>Generates a public key of type <c>Type</c>.
- See also <seealso marker="public_key:public_key#generate_key-1">public_key:generate_key/1</seealso>.
+ See also <seemfa marker="public_key:public_key#generate_key/1">public_key:generate_key/1</seemfa>.
May raise exception:
</p>
<list type="bulleted">
@@ -1049,7 +1176,7 @@
<desc>
<p>Initializes the context for streaming hash operations. <c>Type</c> determines
which digest to use. The returned context should be used as argument
- to <seealso marker="#hash_update-2">hash_update</seealso>.</p>
+ to <seemfa marker="#hash_update/2">hash_update</seemfa>.</p>
<p>May raise exception <c>error:notsup</c> in case the chosen <c>Type</c>
is not supported by the underlying libcrypto implementation.</p>
</desc>
@@ -1060,10 +1187,10 @@
<fsummary></fsummary>
<desc>
<p>Updates the digest represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c>
- must have been generated using <seealso marker="#hash_init-1">hash_init</seealso>
+ must have been generated using <seemfa marker="#hash_init/1">hash_init</seemfa>
or a previous call to this function. <c>Data</c> can be any length. <c>NewContext</c>
must be passed into the next call to <c>hash_update</c>
- or <seealso marker="#hash_final-1">hash_final</seealso>.</p>
+ or <seemfa marker="#hash_final/1">hash_final</seemfa>.</p>
</desc>
</func>
@@ -1072,7 +1199,7 @@
<fsummary></fsummary>
<desc>
<p>Finalizes the hash operation referenced by <c>Context</c> returned
- from a previous call to <seealso marker="#hash_update-2">hash_update</seealso>.
+ from a previous call to <seemfa marker="#hash_update/2">hash_update</seemfa>.
The size of <c>Digest</c> is determined by the type of hash
function used to generate it.</p>
</desc>
@@ -1088,13 +1215,13 @@
running in FIPS mode) or <c>not_enabled</c>. For other builds
this value is always <c>not_supported</c>.
</p>
- <p>See <seealso marker="#enable_fips_mode-1">enable_fips_mode/1</seealso> about how to enable
+ <p>See <seemfa marker="#enable_fips_mode/1">enable_fips_mode/1</seemfa> about how to enable
FIPS mode.
</p>
<warning>
<p>In FIPS mode all non-FIPS compliant algorithms are
disabled and raise exception <c>error:notsup</c>. Check
- <seealso marker="#supports-0">supports</seealso> that in
+ <seemfa marker="#supports/0">supports</seemfa> that in
FIPS mode returns the restricted list of available
algorithms.</p>
</warning>
@@ -1111,7 +1238,7 @@
<p>Note that to enable FIPS mode succesfully, OTP must be built with the configure option <c>--enable-fips</c>,
and the underlying libcrypto must also support FIPS.
</p>
- <p>See also <seealso marker="#info_fips-0">info_fips/0</seealso>.
+ <p>See also <seemfa marker="#info_fips/0">info_fips/0</seemfa>.
</p>
</desc>
</func>
@@ -1144,7 +1271,7 @@
<p>Provides a map with information about block_size, size and possibly other properties of the
hash algorithm in question.
</p>
- <p>For a list of supported hash algorithms, see <seealso marker="#supports-0">supports/0</seealso>.
+ <p>For a list of supported hash algorithms, see <seemfa marker="#supports/0">supports/0</seemfa>.
</p>
</desc>
</func>
@@ -1165,7 +1292,7 @@
<p>Always use a <c>Type</c> with an explicit key length,
</p>
</note>
- <p>For a list of supported cipher algorithms, see <seealso marker="#supports-0">supports/0</seealso>.
+ <p>For a list of supported cipher algorithms, see <seemfa marker="#supports/0">supports/0</seemfa>.
</p>
</desc>
</func>
@@ -1196,11 +1323,11 @@
<fsummary>Decrypts CipherText using the private Key.</fsummary>
<desc>
<p>Decrypts the <c>CipherText</c>, encrypted with
- <seealso marker="#public_encrypt-4">public_encrypt/4</seealso> (or equivalent function)
+ <seemfa marker="#public_encrypt/4">public_encrypt/4</seemfa> (or equivalent function)
using the <c>PrivateKey</c>, and returns the
plaintext (message digest). This is a low level signature verification operation
used for instance by older versions of the SSL protocol.
- See also <seealso marker="public_key:public_key#decrypt_private-2">public_key:decrypt_private/[2,3]</seealso>
+ See also <seemfa marker="public_key:public_key#decrypt_private/2">public_key:decrypt_private/[2,3]</seemfa>
</p>
</desc>
</func>
@@ -1212,8 +1339,8 @@
<p>Encrypts the <c>PlainText</c> using the <c>PrivateKey</c>
and returns the ciphertext. This is a low level signature operation
used for instance by older versions of the SSL protocol. See
- also <seealso
- marker="public_key:public_key#encrypt_private-2">public_key:encrypt_private/[2,3]</seealso>
+ also <seemfa
+ marker="public_key:public_key#encrypt_private/2">public_key:encrypt_private/[2,3]</seemfa>
</p>
</desc>
</func>
@@ -1223,11 +1350,11 @@
<fsummary>Decrypts CipherText using the public Key.</fsummary>
<desc>
<p>Decrypts the <c>CipherText</c>, encrypted with
- <seealso marker="#private_encrypt-4">private_encrypt/4</seealso>(or equivalent function)
+ <seemfa marker="#private_encrypt/4">private_encrypt/4</seemfa>(or equivalent function)
using the <c>PrivateKey</c>, and returns the
plaintext (message digest). This is a low level signature verification operation
used for instance by older versions of the SSL protocol.
- See also <seealso marker="public_key:public_key#decrypt_public-2">public_key:decrypt_public/[2,3]</seealso>
+ See also <seemfa marker="public_key:public_key#decrypt_public/2">public_key:decrypt_public/[2,3]</seemfa>
</p>
</desc>
</func>
@@ -1238,8 +1365,8 @@
<desc>
<p>Encrypts the <c>PlainText</c> (message digest) using the <c>PublicKey</c>
and returns the <c>CipherText</c>. This is a low level signature operation
- used for instance by older versions of the SSL protocol. See also <seealso
- marker="public_key:public_key#encrypt_public-2">public_key:encrypt_public/[2,3]</seealso>
+ used for instance by older versions of the SSL protocol. See also <seemfa
+ marker="public_key:public_key#encrypt_public/2">public_key:encrypt_public/[2,3]</seemfa>
</p>
</desc>
</func>
@@ -1252,7 +1379,7 @@
RAND_seed function from openssl. Only use this if the system
you are running on does not have enough "randomness" built in.
Normally this is when
- <seealso marker="#strong_rand_bytes/1">strong_rand_bytes/1</seealso>
+ <seemfa marker="#strong_rand_bytes/1">strong_rand_bytes/1</seemfa>
raises <c>error:low_entropy</c></p>
</desc>
</func>
@@ -1305,17 +1432,17 @@
<desc>
<p>
Creates state object for
- <seealso marker="stdlib:rand">random number generation</seealso>,
+ <seeerl marker="stdlib:rand">random number generation</seeerl>,
in order to generate cryptographically strong random numbers
(based on OpenSSL's <c>BN_rand_range</c>),
and saves it in the process dictionary before returning it as well.
See also
- <seealso marker="stdlib:rand#seed-1">rand:seed/1</seealso> and
- <seealso marker="#rand_seed_s-0">rand_seed_s/0</seealso>.
+ <seemfa marker="stdlib:rand#seed/1">rand:seed/1</seemfa> and
+ <seemfa marker="#rand_seed_s/0">rand_seed_s/0</seemfa>.
</p>
<p>
When using the state object from this function the
- <seealso marker="stdlib:rand">rand</seealso> functions using it
+ <seeerl marker="stdlib:rand">rand</seeerl> functions using it
may raise exception <c>error:low_entropy</c> in case the random generator
failed due to lack of secure "randomness".
</p>
@@ -1333,15 +1460,15 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
<desc>
<p>
Creates state object for
- <seealso marker="stdlib:rand">random number generation</seealso>,
+ <seeerl marker="stdlib:rand">random number generation</seeerl>,
in order to generate cryptographically strongly random numbers
(based on OpenSSL's <c>BN_rand_range</c>).
See also
- <seealso marker="stdlib:rand#seed_s-1">rand:seed_s/1</seealso>.
+ <seemfa marker="stdlib:rand#seed_s/1">rand:seed_s/1</seemfa>.
</p>
<p>
When using the state object from this function the
- <seealso marker="stdlib:rand">rand</seealso> functions using it
+ <seeerl marker="stdlib:rand">rand</seeerl> functions using it
may raise exception <c>error:low_entropy</c> in case the random generator
failed due to lack of secure "randomness".
</p>
@@ -1350,7 +1477,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
The state returned from this function cannot be used
to get a reproducable random sequence as from
the other
- <seealso marker="stdlib:rand">rand</seealso>
+ <seeerl marker="stdlib:rand">rand</seeerl>
functions,
since reproducability does not match cryptographically safe.
</p>
@@ -1372,16 +1499,16 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
<marker id="rand_seed_alg-1" />
<p>
Creates state object for
- <seealso marker="stdlib:rand">random number generation</seealso>,
+ <seeerl marker="stdlib:rand">random number generation</seeerl>,
in order to generate cryptographically strong random numbers,
and saves it in the process dictionary before returning it as well.
See also
- <seealso marker="stdlib:rand#seed-1">rand:seed/1</seealso> and
- <seealso marker="#rand_seed_alg_s-1">rand_seed_alg_s/1</seealso>.
+ <seemfa marker="stdlib:rand#seed/1">rand:seed/1</seemfa> and
+ <seemfa marker="#rand_seed_alg_s/1">rand_seed_alg_s/1</seemfa>.
</p>
<p>
When using the state object from this function the
- <seealso marker="stdlib:rand">rand</seealso> functions using it
+ <seeerl marker="stdlib:rand">rand</seeerl> functions using it
may raise exception <c>error:low_entropy</c> in case the random generator
failed due to lack of secure "randomness".
</p>
@@ -1403,11 +1530,11 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
<marker id="rand_seed_alg-2" />
<p>
Creates a state object for
- <seealso marker="stdlib:rand">random number generation</seealso>,
+ <seeerl marker="stdlib:rand">random number generation</seeerl>,
in order to generate cryptographically unpredictable random numbers,
and saves it in the process dictionary before returning it as well.
See also
- <seealso marker="#rand_seed_alg_s-2">rand_seed_alg_s/2</seealso>.
+ <seemfa marker="#rand_seed_alg_s/2">rand_seed_alg_s/2</seemfa>.
</p>
<p><em>Example</em></p>
<pre>
@@ -1431,14 +1558,14 @@ FloatValue = rand:uniform(). % again
<marker id="rand_seed_alg_s-1" />
<p>
Creates state object for
- <seealso marker="stdlib:rand">random number generation</seealso>,
+ <seeerl marker="stdlib:rand">random number generation</seeerl>,
in order to generate cryptographically strongly random numbers.
See also
- <seealso marker="stdlib:rand#seed_s-1">rand:seed_s/1</seealso>.
+ <seemfa marker="stdlib:rand#seed_s/1">rand:seed_s/1</seemfa>.
</p>
<p>
If <c>Alg</c> is <c>crypto</c> this function behaves exactly like
- <seealso marker="#rand_seed_s-0">rand_seed_s/0</seealso>.
+ <seemfa marker="#rand_seed_s/0">rand_seed_s/0</seemfa>.
</p>
<p>
If <c>Alg</c> is <c>crypto_cache</c> this function
@@ -1448,19 +1575,19 @@ FloatValue = rand:uniform(). % again
</p>
<p>
When using the state object from this function the
- <seealso marker="stdlib:rand">rand</seealso> functions using it
+ <seeerl marker="stdlib:rand">rand</seeerl> functions using it
may raise exception <c>error:low_entropy</c> in case the random generator
failed due to lack of secure "randomness".
</p>
<p>
The cache size can be changed from its default value using the
- <seealso marker="crypto_app">
+ <seeapp marker="crypto_app">
crypto app's
- </seealso> configuration parameter <c>rand_cache_size</c>.
+ </seeapp> configuration parameter <c>rand_cache_size</c>.
</p>
<p>
When using the state object from this function the
- <seealso marker="stdlib:rand">rand</seealso> functions using it
+ <seeerl marker="stdlib:rand">rand</seeerl> functions using it
may throw exception <c>low_entropy</c> in case the random generator
failed due to lack of secure "randomness".
</p>
@@ -1469,7 +1596,7 @@ FloatValue = rand:uniform(). % again
The state returned from this function cannot be used
to get a reproducable random sequence as from
the other
- <seealso marker="stdlib:rand">rand</seealso>
+ <seeerl marker="stdlib:rand">rand</seeerl>
functions,
since reproducability does not match cryptographically safe.
</p>
@@ -1495,14 +1622,14 @@ FloatValue = rand:uniform(). % again
<marker id="rand_seed_alg_s-2" />
<p>
Creates a state object for
- <seealso marker="stdlib:rand">random number generation</seealso>,
+ <seeerl marker="stdlib:rand">random number generation</seeerl>,
in order to generate cryptographically unpredictable random numbers.
See also
- <seealso marker="#rand_seed_alg-1">rand_seed_alg/1</seealso>.
+ <seemfa marker="#rand_seed_alg/1">rand_seed_alg/1</seemfa>.
</p>
<p>
To get a long period the Xoroshiro928 generator from the
- <seealso marker="stdlib:rand">rand</seealso>
+ <seeerl marker="stdlib:rand">rand</seeerl>
module is used as a counter (with period 2^928 - 1)
and the generator states are scrambled through AES
to create 58-bit pseudo random values.
@@ -1518,7 +1645,7 @@ FloatValue = rand:uniform(). % again
<item>
<p>
If you need cryptographically strong random numbers use
- <seealso marker="#rand_seed_alg_s-1">rand_seed_alg_s/1</seealso>
+ <seemfa marker="#rand_seed_alg_s/1">rand_seed_alg_s/1</seemfa>
with <c>Alg =:= crypto</c> or <c>Alg =:= crypto_cache</c>.
</p>
</item>
@@ -1531,22 +1658,22 @@ FloatValue = rand:uniform(). % again
<p>
If you do not need the statistical quality of this function,
there are faster algorithms in the
- <seealso marker="stdlib:rand">rand</seealso>
+ <seeerl marker="stdlib:rand">rand</seeerl>
module.
</p>
</item>
</list>
<p>
Thanks to the used generator the state object supports the
- <seealso marker="stdlib:rand#jump-0"><c>rand:jump/0,1</c></seealso>
+ <seemfa marker="stdlib:rand#jump/0"><c>rand:jump/0,1</c></seemfa>
function with distance 2^512.
</p>
<p>
Numbers are generated in batches and cached for speed reasons.
The cache size can be changed from its default value using the
- <seealso marker="crypto_app">
+ <seeapp marker="crypto_app">
crypto app's
- </seealso> configuration parameter <c>rand_cache_size</c>.
+ </seeapp> configuration parameter <c>rand_cache_size</c>.
</p>
</desc>
</func>
@@ -1578,7 +1705,7 @@ FloatValue = rand:uniform(). % again
digest (plaintext).</p>
<p>Algorithm <c>dss</c> can only be used together with digest type
<c>sha</c>.</p>
- <p>See also <seealso marker="public_key:public_key#sign-3">public_key:sign/3</seealso>.</p>
+ <p>See also <seemfa marker="public_key:public_key#sign/3">public_key:sign/3</seemfa>.</p>
</desc>
</func>
@@ -1594,16 +1721,17 @@ FloatValue = rand:uniform(). % again
<p>Algorithm <c>dss</c> can only be used together with digest type
<c>sha</c>.</p>
- <p>See also <seealso marker="public_key:public_key#verify-4">public_key:verify/4</seealso>.</p>
+ <p>See also <seemfa marker="public_key:public_key#verify/4">public_key:verify/4</seemfa>.</p>
</desc>
</func>
</funcs>
- <section>
- <title>Engine API</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Engine API</title>
+ </fsdescription>
<!-- Engine functions -->
<func>
<name name="privkey_to_pubkey" arity="2" since="OTP 20.2"/>
@@ -1627,7 +1755,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
</desc>
@@ -1649,7 +1777,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
</desc>
@@ -1669,7 +1797,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
</desc>
@@ -1689,7 +1817,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
</desc>
@@ -1709,7 +1837,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
</desc>
@@ -1815,7 +1943,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
<p>
@@ -1841,7 +1969,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
</desc>
@@ -1864,7 +1992,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
</desc>
@@ -1887,7 +2015,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
</desc>
@@ -1908,7 +2036,7 @@ FloatValue = rand:uniform(). % again
no engine support in the underlying OpenSSL implementation.
</p>
<p>
- See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See also the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
in the User's Guide.
</p>
</desc>
@@ -1916,21 +2044,21 @@ FloatValue = rand:uniform(). % again
</funcs>
-<section>
- <title>Old API</title>
-</section>
<funcs>
+ <fsdescription>
+ <title>Old API</title>
+ </fsdescription>
<func>
<name name="block_encrypt" arity="3" since="OTP 18.0"/>
<fsummary>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher</fsummary>
<desc>
- <dont><p>Don't use this function for new programs! Use <seealso marker="crypto:new_api">the-new-api</seealso>.</p></dont>
+ <dont><p>Don't use this function for new programs! Use <seeguide marker="crypto:new_api">the-new-api</seeguide>.</p></dont>
<p>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher.</p>
<p>May raise exception <c>error:notsup</c> in case the chosen <c>Type</c>
is not supported by the underlying libcrypto implementation.</p>
<p>For keylengths and blocksizes see the
- <seealso marker="crypto:algorithm_details#ciphers">User's Guide</seealso>.
+ <seeguide marker="crypto:algorithm_details#ciphers">User's Guide</seeguide>.
</p>
</desc>
</func>
@@ -1939,12 +2067,12 @@ FloatValue = rand:uniform(). % again
<name name="block_decrypt" arity="3" since="OTP 18.0"/>
<fsummary>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher</fsummary>
<desc>
- <dont><p>Don't use this function for new programs! Use <seealso marker="crypto:new_api">the new api</seealso>.</p></dont>
+ <dont><p>Don't use this function for new programs! Use <seeguide marker="crypto:new_api">the new api</seeguide>.</p></dont>
<p>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher.</p>
<p>May raise exception <c>error:notsup</c> in case the chosen <c>Type</c>
is not supported by the underlying libcrypto implementation.</p>
<p>For keylengths and blocksizes see the
- <seealso marker="crypto:algorithm_details#ciphers">User's Guide</seealso>.
+ <seeguide marker="crypto:algorithm_details#ciphers">User's Guide</seeguide>.
</p>
</desc>
</func>
@@ -1955,16 +2083,16 @@ FloatValue = rand:uniform(). % again
<name since="OTP R16B01">block_encrypt(aes_gcm | aes_ccm, Key, Ivec, {AAD, PlainText, TagLength}) -> {CipherText, CipherTag} | Error </name>
<fsummary>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher</fsummary>
<type>
- <v>Type = <seealso marker="#type-block_cipher_with_iv">block_cipher_with_iv()</seealso></v>
- <v>AeadType = <seealso marker="#type-aead_cipher">aead_cipher()</seealso></v>
- <v>Key = <seealso marker="#type-key">key()</seealso> | <seealso marker="#type-des3_key">des3_key()</seealso></v>
+ <v>Type = <seetype marker="#block_cipher_with_iv">block_cipher_with_iv()</seetype></v>
+ <v>AeadType = <seetype marker="#aead_cipher">aead_cipher()</seetype></v>
+ <v>Key = <seetype marker="#key">key()</seetype> | <seetype marker="#des3_key">des3_key()</seetype></v>
<v>PlainText = iodata()</v>
<v>AAD = IVec = CipherText = CipherTag = binary()</v>
<v>TagLength = 1..16</v>
- <v>Error = <seealso marker="#type-run_time_error">run_time_error()</seealso></v>
+ <v>Error = <seetype marker="#run_time_error">run_time_error()</seetype></v>
</type>
<desc>
- <dont><p>Don't use this function for new programs! Use <seealso marker="crypto:new_api">the new api</seealso>.</p></dont>
+ <dont><p>Don't use this function for new programs! Use <seeguide marker="crypto:new_api">the new api</seeguide>.</p></dont>
<p>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher.
<c>IVec</c> is an arbitrary initializing vector.</p>
<p>In AEAD (Authenticated Encryption with Associated Data) mode, encrypt
@@ -1973,7 +2101,7 @@ FloatValue = rand:uniform(). % again
<p>May raise exception <c>error:notsup</c> in case the chosen <c>Type</c>
is not supported by the underlying libcrypto implementation.</p>
<p>For keylengths, iv-sizes and blocksizes see the
- <seealso marker="crypto:algorithm_details#ciphers">User's Guide</seealso>.
+ <seeguide marker="crypto:algorithm_details#ciphers">User's Guide</seeguide>.
</p>
</desc>
</func>
@@ -1983,16 +2111,16 @@ FloatValue = rand:uniform(). % again
<name since="OTP R16B01">block_decrypt(AeadType, Key, Ivec, {AAD, CipherText, CipherTag}) -> PlainText | Error</name>
<fsummary>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher</fsummary>
<type>
- <v>Type = <seealso marker="#type-block_cipher_with_iv">block_cipher_with_iv()</seealso></v>
- <v>AeadType = <seealso marker="#type-aead_cipher">aead_cipher()</seealso></v>
- <v>Key = <seealso marker="#type-key">key()</seealso> | <seealso marker="#type-des3_key">des3_key()</seealso></v>
+ <v>Type = <seetype marker="#block_cipher_with_iv">block_cipher_with_iv()</seetype></v>
+ <v>AeadType = <seetype marker="#aead_cipher">aead_cipher()</seetype></v>
+ <v>Key = <seetype marker="#key">key()</seetype> | <seetype marker="#des3_key">des3_key()</seetype></v>
<v>PlainText = iodata()</v>
<v>AAD = IVec = CipherText = CipherTag = binary()</v>
- <v>Error = BadTag | <seealso marker="#type-run_time_error">run_time_error()</seealso></v>
+ <v>Error = BadTag | <seetype marker="#run_time_error">run_time_error()</seetype></v>
<v>BadTag = error</v>
</type>
<desc>
- <dont><p>Don't use this function for new programs! Use <seealso marker="crypto:new_api">the new api</seealso>.</p></dont>
+ <dont><p>Don't use this function for new programs! Use <seeguide marker="crypto:new_api">the new api</seeguide>.</p></dont>
<p>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher.
<c>IVec</c> is an arbitrary initializing vector.</p>
<p>In AEAD (Authenticated Encryption with Associated Data) mode, decrypt
@@ -2002,7 +2130,7 @@ FloatValue = rand:uniform(). % again
<p>May raise exception <c>error:notsup</c> in case the chosen <c>Type</c>
is not supported by the underlying libcrypto implementation.</p>
<p>For keylengths, iv-sizes and blocksizes see the
- <seealso marker="crypto:algorithm_details#ciphers">User's Guide</seealso>.
+ <seeguide marker="crypto:algorithm_details#ciphers">User's Guide</seeguide>.
</p>
</desc>
</func>
@@ -2011,12 +2139,12 @@ FloatValue = rand:uniform(). % again
<name name="stream_init" arity="2" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
- <dont><p>Don't use this function for new programs! Use <seealso marker="crypto:new_api">the new api</seealso>.</p></dont>
+ <dont><p>Don't use this function for new programs! Use <seeguide marker="crypto:new_api">the new api</seeguide>.</p></dont>
<p>Initializes the state for use in RC4 stream encryption
- <seealso marker="#stream_encrypt-2">stream_encrypt</seealso> and
- <seealso marker="#stream_decrypt-2">stream_decrypt</seealso></p>
+ <seemfa marker="#stream_encrypt/2">stream_encrypt</seemfa> and
+ <seemfa marker="#stream_decrypt/2">stream_decrypt</seemfa></p>
<p>For keylengths see the
- <seealso marker="crypto:algorithm_details#ciphers">User's Guide</seealso>.
+ <seeguide marker="crypto:algorithm_details#ciphers">User's Guide</seeguide>.
</p>
</desc>
</func>
@@ -2025,14 +2153,14 @@ FloatValue = rand:uniform(). % again
<name name="stream_init" arity="3" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
- <dont><p>Don't use this function for new programs! Use <seealso marker="crypto:new_api">the new api</seealso>.</p></dont>
+ <dont><p>Don't use this function for new programs! Use <seeguide marker="crypto:new_api">the new api</seeguide>.</p></dont>
<p>Initializes the state for use in streaming AES encryption using Counter mode (CTR).
<c>Key</c> is the AES key and must be either 128, 192, or 256 bits long. <c>IVec</c> is
an arbitrary initializing vector of 128 bits (16 bytes). This state is for use with
- <seealso marker="#stream_encrypt-2">stream_encrypt</seealso> and
- <seealso marker="#stream_decrypt-2">stream_decrypt</seealso>.</p>
+ <seemfa marker="#stream_encrypt/2">stream_encrypt</seemfa> and
+ <seemfa marker="#stream_decrypt/2">stream_decrypt</seemfa>.</p>
<p>For keylengths and iv-sizes see the
- <seealso marker="crypto:algorithm_details#ciphers">User's Guide</seealso>.
+ <seeguide marker="crypto:algorithm_details#ciphers">User's Guide</seeguide>.
</p>
</desc>
</func>
@@ -2041,10 +2169,10 @@ FloatValue = rand:uniform(). % again
<name name="stream_encrypt" arity="2" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
- <dont><p>Don't use this function for new programs! Use <seealso marker="crypto:new_api">the new api</seealso>.</p></dont>
+ <dont><p>Don't use this function for new programs! Use <seeguide marker="crypto:new_api">the new api</seeguide>.</p></dont>
<p>Encrypts <c>PlainText</c> according to the stream cipher <c>Type</c> specified in stream_init/3.
<c>Text</c> can be any number of bytes. The initial <c>State</c> is created using
- <seealso marker="#stream_init-2">stream_init</seealso>.
+ <seemfa marker="#stream_init/2">stream_init</seemfa>.
<c>NewState</c> must be passed into the next call to <c>stream_encrypt</c>.</p>
</desc>
</func>
@@ -2053,10 +2181,10 @@ FloatValue = rand:uniform(). % again
<name name="stream_decrypt" arity="2" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
- <dont><p>Don't use this function for new programs! Use <seealso marker="crypto:new_api">the new api</seealso>.</p></dont>
+ <dont><p>Don't use this function for new programs! Use <seeguide marker="crypto:new_api">the new api</seeguide>.</p></dont>
<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>.
+ <seemfa marker="#stream_init/2">stream_init</seemfa>.
<c>NewState</c> must be passed into the next call to <c>stream_decrypt</c>.</p>
</desc>
</func>
@@ -2066,11 +2194,11 @@ FloatValue = rand:uniform(). % again
<fsummary>Provide a list of available crypto algorithms.</fsummary>
<desc>
<dont><p>Don't use this function for new programs! Use
- <seealso marker="crypto#supports-1">supports/1</seealso> in
- <seealso marker="crypto:new_api">the new api</seealso>.</p></dont>
+ <seemfa marker="crypto#supports/1">supports/1</seemfa> in
+ <seeguide marker="crypto:new_api">the new api</seeguide>.</p></dont>
<p> Can be used to determine which crypto algorithms that are supported
by the underlying libcrypto library</p>
- <p>See <seealso marker="#hash_info-1">hash_info/1</seealso> and <seealso marker="#cipher_info-1">cipher_info/1</seealso>
+ <p>See <seemfa marker="#hash_info/1">hash_info/1</seemfa> and <seemfa marker="#cipher_info/1">cipher_info/1</seemfa>
for information about the hash and cipher algorithms.
</p>
</desc>
@@ -2082,9 +2210,9 @@ FloatValue = rand:uniform(). % again
<fsummary></fsummary>
<desc>
<dont><p>Don't use this function for new programs! Use
- <seealso marker="crypto#mac-4">mac/4</seealso> or
- <seealso marker="crypto#macN-5">macN/5</seealso> in
- <seealso marker="crypto:new_api">the new api</seealso>.</p>
+ <seemfa marker="crypto#mac/4">mac/4</seemfa> or
+ <seemfa marker="crypto#macN/5">macN/5</seemfa> in
+ <seeguide marker="crypto:new_api">the new api</seeguide>.</p>
</dont>
<p>Computes a HMAC of type <c>Type</c> from <c>Data</c> using
<c>Key</c> as the authentication key.</p> <p><c>MacLength</c>
@@ -2097,8 +2225,8 @@ FloatValue = rand:uniform(). % again
<fsummary></fsummary>
<desc>
<dont><p>Don't use this function for new programs! Use
- <seealso marker="crypto#mac_init-3">mac_init/3</seealso> in
- <seealso marker="crypto:new_api">the new api</seealso>.</p>
+ <seemfa marker="crypto#mac_init/3">mac_init/3</seemfa> in
+ <seeguide marker="crypto:new_api">the new api</seeguide>.</p>
</dont>
<p>Initializes the context for streaming HMAC operations. <c>Type</c> determines
which hash function to use in the HMAC operation. <c>Key</c> is the authentication
@@ -2111,15 +2239,15 @@ FloatValue = rand:uniform(). % again
<fsummary></fsummary>
<desc>
<dont><p>Don't use this function for new programs! Use
- <seealso marker="crypto#mac_update-2">mac_update/2</seealso> in
- <seealso marker="crypto:new_api">the new api</seealso>.</p>
+ <seemfa marker="crypto#mac_update/2">mac_update/2</seemfa> in
+ <seeguide marker="crypto:new_api">the new api</seeguide>.</p>
</dont>
<p>Updates the HMAC represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c>
must have been generated using an HMAC init function (such as
- <seealso marker="#hmac_init-2">hmac_init</seealso>). <c>Data</c> can be any length. <c>NewContext</c>
+ <seemfa marker="#hmac_init/2">hmac_init</seemfa>). <c>Data</c> can be any length. <c>NewContext</c>
must be passed into the next call to <c>hmac_update</c>
- or to one of the functions <seealso marker="#hmac_final-1">hmac_final</seealso> and
- <seealso marker="#hmac_final_n-2">hmac_final_n</seealso>
+ or to one of the functions <seemfa marker="#hmac_final/1">hmac_final</seemfa> and
+ <seemfa marker="#hmac_final_n/2">hmac_final_n</seemfa>
</p>
<warning><p>Do not use a <c>Context</c> as argument in more than one
call to hmac_update or hmac_final. The semantics of reusing old contexts
@@ -2134,8 +2262,8 @@ FloatValue = rand:uniform(). % again
<fsummary></fsummary>
<desc>
<dont><p>Don't use this function for new programs! Use
- <seealso marker="crypto#mac_final-1">mac_final/1</seealso> in
- <seealso marker="crypto:new_api">the new api</seealso>.</p>
+ <seemfa marker="crypto#mac_final/1">mac_final/1</seemfa> in
+ <seeguide marker="crypto:new_api">the new api</seeguide>.</p>
</dont>
<p>Finalizes the HMAC operation referenced by <c>Context</c>. The size of the resultant MAC is
determined by the type of hash function used to generate it.</p>
@@ -2147,8 +2275,8 @@ FloatValue = rand:uniform(). % again
<fsummary></fsummary>
<desc>
<dont><p>Don't use this function for new programs! Use
- <seealso marker="crypto#mac_finalN-2">mac_finalN/2</seealso> in
- <seealso marker="crypto:new_api">the new api</seealso>.</p>
+ <seemfa marker="crypto#mac_finalN/2">mac_finalN/2</seemfa> in
+ <seeguide marker="crypto:new_api">the new api</seeguide>.</p>
</dont>
<p>Finalizes the HMAC operation referenced by <c>Context</c>. <c>HashLen</c> must be greater than
zero. <c>Mac</c> will be a binary with at most <c>HashLen</c> bytes. Note that if HashLen is greater than the actual number of bytes returned from the underlying hash, the returned hash will have fewer than <c>HashLen</c> bytes.</p>
@@ -2161,9 +2289,9 @@ FloatValue = rand:uniform(). % again
<fsummary>Calculates the Cipher-based Message Authentication Code.</fsummary>
<desc>
<dont><p>Don't use this function for new programs! Use
- <seealso marker="crypto#mac-4">mac/4</seealso> or
- <seealso marker="crypto#macN-5">macN/5</seealso> in
- <seealso marker="crypto:new_api">the new api</seealso>.</p>
+ <seemfa marker="crypto#mac/4">mac/4</seemfa> or
+ <seemfa marker="crypto#macN/5">macN/5</seemfa> in
+ <seeguide marker="crypto:new_api">the new api</seeguide>.</p>
</dont>
<p>Computes a CMAC of type <c>Type</c> from <c>Data</c> using
<c>Key</c> as the authentication key.</p> <p><c>MacLength</c>
@@ -2176,9 +2304,9 @@ FloatValue = rand:uniform(). % again
<fsummary></fsummary>
<desc>
<dont><p>Don't use this function for new programs! Use
- <seealso marker="crypto#mac-3">mac/3</seealso> or
- <seealso marker="crypto#macN-4">macN/4</seealso> in
- <seealso marker="crypto:new_api">the new api</seealso>.</p>
+ <seemfa marker="crypto#mac/3">mac/3</seemfa> or
+ <seemfa marker="crypto#macN/4">macN/4</seemfa> in
+ <seeguide marker="crypto:new_api">the new api</seeguide>.</p>
</dont>
<p>Computes a POLY1305 message authentication code (<c>Mac</c>) from <c>Data</c> using
<c>Key</c> as the authentication key.</p>
diff --git a/lib/crypto/doc/src/crypto_app.xml b/lib/crypto/doc/src/crypto_app.xml
index 8296b1bc77..3b9d9f1a8e 100644
--- a/lib/crypto/doc/src/crypto_app.xml
+++ b/lib/crypto/doc/src/crypto_app.xml
@@ -31,9 +31,9 @@
<appsummary>The Crypto Application</appsummary>
<description>
<p>The purpose of the Crypto application is to provide an Erlang API
- to cryptographic functions, see <seealso marker="crypto">crypto(3)</seealso>.
+ to cryptographic functions, see <seeerl marker="crypto">crypto(3)</seeerl>.
Note that the API is on a fairly low level and there are some
- corresponding API functions available in <seealso marker="public_key:public_key">public_key(3)</seealso>,
+ corresponding API functions available in <seeerl marker="public_key:public_key">public_key(3)</seeerl>,
on a higher abstraction level, that uses the crypto application in its implementation.
</p>
</description>
@@ -72,12 +72,12 @@
<item>
<p>
Sets the cache size in bytes to use by
- <seealso marker="crypto#rand_seed_alg-1">
+ <seemfa marker="crypto#rand_seed_alg/1">
<c>crypto:rand_seed_alg(crypto_cache)</c>
- </seealso> and
- <seealso marker="crypto#rand_seed_alg_s-1">
+ </seemfa> and
+ <seemfa marker="crypto#rand_seed_alg_s/1">
<c>crypto:rand_seed_alg_s(crypto_cache)</c>
- </seealso>.
+ </seemfa>.
This parameter is read when a seed function is called,
and then kept in generators state object. It has a rather
small default value that causes reads of strong random bytes
diff --git a/lib/crypto/doc/src/engine_keys.xml b/lib/crypto/doc/src/engine_keys.xml
index 153ea8dd24..4e0ec17e6e 100644
--- a/lib/crypto/doc/src/engine_keys.xml
+++ b/lib/crypto/doc/src/engine_keys.xml
@@ -35,7 +35,7 @@
<url href="https://www.openssl.org/">OpenSSL</url> exposes an Engine API, which makes
it possible to plug in alternative implementations for some of the cryptographic
operations implemented by OpenSSL.
- See the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
+ See the chapter <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
for details and how to load an Engine.
</p>
<p>
@@ -51,18 +51,18 @@
<p>
OTP/Crypto requires that the user provides two or three items of information about the key. The application used
by the user is usually on a higher level, for example in
- <seealso marker="ssl:ssl#type-key">SSL</seealso>. If using
+ <seetype marker="ssl:ssl#key">SSL</seetype>. If using
the crypto application directly, it is required that:
</p>
<list>
- <item>an Engine is loaded, see the chapter on <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso>
- or the <seealso marker="crypto:crypto#engine_load-3">Reference Manual</seealso>
+ <item>an Engine is loaded, see the chapter on <seeguide marker="crypto:engine_load#engine_load">Engine Load</seeguide>
+ or the <seemfa marker="crypto:crypto#engine_load/3">Reference Manual</seemfa>
</item>
<item>a reference to a key in the Engine is available. This should be an Erlang string or binary and depends
on the Engine loaded
</item>
<item>an Erlang map is constructed with the Engine reference, the key reference and possibly a key passphrase if
- needed by the Engine. See the <seealso marker="crypto:crypto#type-engine_key_ref">Reference Manual</seealso> for
+ needed by the Engine. See the <seetype marker="crypto:crypto#engine_key_ref">Reference Manual</seetype> for
details of the map.
</item>
</list>
diff --git a/lib/crypto/doc/src/insidecover.xml b/lib/crypto/doc/src/insidecover.xml
deleted file mode 100644
index bf2427afdf..0000000000
--- a/lib/crypto/doc/src/insidecover.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE bookinsidecover SYSTEM "bookinsidecover.dtd">
-
-<bookinsidecover>
-
- The Erlang/OTP SSL application includes software developed by the
- OpenSSL Project for use in the OpenSSL Toolkit
- (http://www.openssl.org/). Copyright (c) 1998-2002 The OpenSSL
- Project. All rights reserved.
-
- <br/>
- This product includes cryptographic software written by Eric Young
- (eay@cryptsoft.com). This product includes software written by Tim
- Hudson (tjh@cryptsoft.com). Copyright (C) 1995-1998 Eric Young
- (eay@cryptsoft.com). All rights reserved.
-
- <br/>
- For further OpenSSL and SSLeay license information se the chapter
- <bold>Licenses</bold>.
-
- <vfill/>
- <br/>
- <tt>http://www.erlang.org</tt>
- <br/>
-</bookinsidecover>
-
diff --git a/lib/crypto/doc/src/new_api.xml b/lib/crypto/doc/src/new_api.xml
index aacf5e4f76..54097dbfee 100644
--- a/lib/crypto/doc/src/new_api.xml
+++ b/lib/crypto/doc/src/new_api.xml
@@ -51,32 +51,34 @@
<title>The old API</title>
<p>The old functions - not recommended for new programs - are for chipers:</p>
<list>
- <item><seealso marker="crypto#block_encrypt-3">block_encrypt/3</seealso></item>
- <item><seealso marker="crypto#block_encrypt-4">block_encrypt/4</seealso></item>
- <item><seealso marker="crypto#block_decrypt-3">block_decrypt/3</seealso></item>
- <item><seealso marker="crypto#block_decrypt-4">block_decrypt/4</seealso></item>
- <item><seealso marker="crypto#stream_init-2">stream_init/2</seealso></item>
- <item><seealso marker="crypto#stream_init-2">stream_init/3</seealso></item>
- <item><seealso marker="crypto#stream_encrypt-2">stream_encrypt/2</seealso></item>
- <item><seealso marker="crypto#stream_decrypt-2">stream_decrypt/2</seealso></item>
+ <item><seemfa marker="crypto#block_encrypt/3">block_encrypt/3</seemfa></item>
+ <item><seemfa marker="crypto#block_encrypt/4">block_encrypt/4</seemfa></item>
+ <item><seemfa marker="crypto#block_decrypt/3">block_decrypt/3</seemfa></item>
+ <item><seemfa marker="crypto#block_decrypt/4">block_decrypt/4</seemfa></item>
+ <item><seemfa marker="crypto#stream_init/2">stream_init/2</seemfa></item>
+ <item><seemfa marker="crypto#stream_init/2">stream_init/3</seemfa></item>
+ <item><seemfa marker="crypto#stream_encrypt/2">stream_encrypt/2</seemfa></item>
+ <item><seemfa marker="crypto#stream_decrypt/2">stream_decrypt/2</seemfa></item>
+ <item><seemfa marker="crypto#next_iv/2">next_iv/2</seemfa></item>
+ <item><seemfa marker="crypto#next_iv/3">next_iv/3</seemfa></item>
</list>
<p>for lists of supported algorithms:</p>
<list>
- <item><seealso marker="crypto#supports-0">supports/0</seealso></item>
+ <item><seemfa marker="crypto#supports/0">supports/0</seemfa></item>
</list>
<p>and for MACs (Message Authentication Codes):</p>
<list>
- <item><seealso marker="crypto#cmac-3">cmac/3</seealso></item>
- <item><seealso marker="crypto#cmac-4">cmac/4</seealso></item>
- <item><seealso marker="crypto#hmac-3">hmac/3</seealso></item>
- <item><seealso marker="crypto#hmac-4">hmac/4</seealso></item>
- <item><seealso marker="crypto#hmac_init-2">hmac_init/2</seealso></item>
- <item><seealso marker="crypto#hmac_update-2">hmac_update/2</seealso></item>
- <item><seealso marker="crypto#hmac_final-1">hmac_final/1</seealso></item>
- <item><seealso marker="crypto#hmac_final_n-2">hmac_final_n/2</seealso></item>
- <item><seealso marker="crypto#poly1305-2">poly1305/2</seealso></item>
+ <item><seemfa marker="crypto#cmac/3">cmac/3</seemfa></item>
+ <item><seemfa marker="crypto#cmac/4">cmac/4</seemfa></item>
+ <item><seemfa marker="crypto#hmac/3">hmac/3</seemfa></item>
+ <item><seemfa marker="crypto#hmac/4">hmac/4</seemfa></item>
+ <item><seemfa marker="crypto#hmac_init/2">hmac_init/2</seemfa></item>
+ <item><seemfa marker="crypto#hmac_update/2">hmac_update/2</seemfa></item>
+ <item><seemfa marker="crypto#hmac_final/1">hmac_final/1</seemfa></item>
+ <item><seemfa marker="crypto#hmac_final_n/2">hmac_final_n/2</seemfa></item>
+ <item><seemfa marker="crypto#poly1305/2">poly1305/2</seemfa></item>
</list>
- <p>They are not deprecated for now, but may be in a future release.
+ <p>They are deprecated from 23.0 and for removal in 24.0.
</p>
</section>
@@ -87,10 +89,10 @@
<p>The new functions for encrypting or decrypting one single binary are:
</p>
<list>
- <item><seealso marker="crypto#crypto_one_time/4">crypto_one_time/4</seealso></item>
- <item><seealso marker="crypto#crypto_one_time/5">crypto_one_time/5</seealso></item>
- <item><seealso marker="crypto#crypto_one_time_aead/6">crypto_one_time_aead/6</seealso></item>
- <item><seealso marker="crypto#crypto_one_time_aead/7">crypto_one_time_aead/7</seealso></item>
+ <item><seemfa marker="crypto#crypto_one_time/4">crypto_one_time/4</seemfa></item>
+ <item><seemfa marker="crypto#crypto_one_time/5">crypto_one_time/5</seemfa></item>
+ <item><seemfa marker="crypto#crypto_one_time_aead/6">crypto_one_time_aead/6</seemfa></item>
+ <item><seemfa marker="crypto#crypto_one_time_aead/7">crypto_one_time_aead/7</seemfa></item>
</list>
<p>In those functions the internal crypto state is first created and initialized
with the cipher type, the key and possibly other data. Then the single binary is encrypted
@@ -105,9 +107,10 @@
the same state, the functions are:
</p>
<list>
- <item><seealso marker="crypto#crypto_init/4">crypto_init/4</seealso></item>
- <item><seealso marker="crypto#crypto_init/3">crypto_init/3</seealso></item>
- <item><seealso marker="crypto#crypto_update/2">crypto_update/2</seealso></item>
+ <item><seemfa marker="crypto#crypto_init/4">crypto_init/4</seemfa></item>
+ <item><seemfa marker="crypto#crypto_init/3">crypto_init/3</seemfa></item>
+ <item><seemfa marker="crypto#crypto_update/2">crypto_update/2</seemfa></item>
+ <item><seemfa marker="crypto#crypto_final/1">crypto_final/1</seemfa></item>
</list>
<p>The <c>crypto_init</c> initialies an internal cipher state, and one or more calls of
<c>crypto_update</c> does the acual encryption or decryption. Note that AEAD ciphers
@@ -118,35 +121,45 @@
for each part, the functions are:
</p>
<list>
- <item><seealso marker="crypto#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seealso></item>
- <item><seealso marker="crypto#crypto_dyn_iv_update/3">crypto_dyn_iv_update/3</seealso></item>
+ <item><seemfa marker="crypto#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seemfa></item>
+ <item><seemfa marker="crypto#crypto_dyn_iv_update/3">crypto_dyn_iv_update/3</seemfa></item>
</list>
<p>An example of where those functions are needed, is when handling the TLS protocol.</p>
+ <p>If padding was not enabled, the call to
+ <seemfa marker="crypto#crypto_final/1">crypto_final/1</seemfa>
+ may be excluded.
+ </p>
<p>For information about available algorithms, use:
</p>
<list>
- <item><seealso marker="crypto#supports-1">supports/1</seealso></item>
- <item><seealso marker="crypto#hash_info-1">hash_info/1</seealso></item>
- <item><seealso marker="crypto#cipher_info-1">cipher_info/1</seealso></item>
+ <item><seemfa marker="crypto#supports/1">supports/1</seemfa></item>
+ <item><seemfa marker="crypto#hash_info/1">hash_info/1</seemfa></item>
+ <item><seemfa marker="crypto#cipher_info/1">cipher_info/1</seemfa></item>
</list>
+
+ <p>The <seemfa marker="crypto#next_iv/2">next_iv/2</seemfa> and
+ <seemfa marker="crypto#next_iv/3">next_iv/3</seemfa> is not needed since the
+ <c>crypto_init</c> and <c>crypto_update</c> includes this functionality.
+ </p>
+
</section>
<section>
<title>MACs (Message Authentication Codes)</title>
<p>The new functions for calculating a MAC of a single piece of text are:</p>
<list>
- <item><seealso marker="crypto#mac-3">mac/3</seealso></item>
- <item><seealso marker="crypto#mac-4">mac/4</seealso></item>
- <item><seealso marker="crypto#macN-4">macN/4</seealso></item>
- <item><seealso marker="crypto#macN-5">macN/5</seealso></item>
+ <item><seemfa marker="crypto#mac/3">mac/3</seemfa></item>
+ <item><seemfa marker="crypto#mac/4">mac/4</seemfa></item>
+ <item><seemfa marker="crypto#macN/4">macN/4</seemfa></item>
+ <item><seemfa marker="crypto#macN/5">macN/5</seemfa></item>
</list>
<p>For calculating a MAC of a text divided in parts use:</p>
<list>
- <item><seealso marker="crypto#mac_init-2">mac_init/2</seealso></item>
- <item><seealso marker="crypto#mac_init-3">mac_init/3</seealso></item>
- <item><seealso marker="crypto#mac_update-2">mac_update/2</seealso></item>
- <item><seealso marker="crypto#mac_final-1">mac_final/1</seealso></item>
- <item><seealso marker="crypto#mac_finalN-2">mac_finalN/2</seealso></item>
+ <item><seemfa marker="crypto#mac_init/2">mac_init/2</seemfa></item>
+ <item><seemfa marker="crypto#mac_init/3">mac_init/3</seemfa></item>
+ <item><seemfa marker="crypto#mac_update/2">mac_update/2</seemfa></item>
+ <item><seemfa marker="crypto#mac_final/1">mac_final/1</seemfa></item>
+ <item><seemfa marker="crypto#mac_finalN/2">mac_finalN/2</seemfa></item>
</list>
</section>
</section>
@@ -155,8 +168,8 @@
<title>Examples of the new api</title>
<section>
<title>Examples of crypto_init/4 and crypto_update/2</title>
- <p>The functions <seealso marker="crypto#crypto_init/4">crypto_init/4</seealso>
- and <seealso marker="crypto#crypto_update/2">crypto_update/2</seealso> are intended
+ <p>The functions <seemfa marker="crypto#crypto_init/4">crypto_init/4</seemfa>
+ and <seemfa marker="crypto#crypto_update/2">crypto_update/2</seemfa> are intended
to be used for encrypting or decrypting a sequence of blocks. First one call of
<c>crypto_init/4</c> initialises the crypto context. One or more calls <c>crypto_update/2</c>
does the actual encryption or decryption for each block.
@@ -190,7 +203,7 @@
11>
</code>
<p>Note that the internal data that the <c>StateEnc</c> and <c>StateDec</c> references are
- destructivly updated by the calls to <seealso marker="crypto#crypto_update/2">crypto_update/2</seealso>.
+ destructivly updated by the calls to <seemfa marker="crypto#crypto_update/2">crypto_update/2</seemfa>.
This is to gain time in the calls of the nifs interfacing the cryptolib. In a loop where the
state is saved in the loop's state, it also saves one update of the loop state per crypto operation.
</p>
@@ -213,8 +226,8 @@
<section>
<title>Example of crypto_one_time/5</title>
<p>The same example as in the
- <seealso marker="#examples-of-crypto_init-4-and-crypto_update-2">previous section</seealso>,
- but now with one call to <seealso marker="crypto#crypto_one_time/5">crypto_one_time/5</seealso>:
+ <seeguide marker="#examples-of-crypto_init-4-and-crypto_update-2">previous section</seeguide>,
+ but now with one call to <seemfa marker="crypto#crypto_one_time/5">crypto_one_time/5</seemfa>:
</p>
<code>
1> Key = &lt;&lt;1:128>>.
@@ -236,8 +249,8 @@
<section>
<title>Example of crypto_one_time_aead/6</title>
<p>The same example as in the
- <seealso marker="#example-of-crypto_one_time-5">previous section</seealso>,
- but now with one call to <seealso marker="crypto#crypto_one_time_aead/6">crypto_one_time_aead/6</seealso>:
+ <seeguide marker="#example-of-crypto_one_time-5">previous section</seeguide>,
+ but now with one call to <seemfa marker="crypto#crypto_one_time_aead/6">crypto_one_time_aead/6</seemfa>:
</p>
<code>
1> Key = &lt;&lt;1:128>>.
diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml
index b22b46d5e5..304e263f88 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -31,6 +31,296 @@
</header>
<p>This document describes the changes made to the Crypto application.</p>
+<section><title>Crypto 4.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix minor memory leaks in crypto ENGINE and robustify the
+ code.</p>
+ <p>
+ Own Id: OTP-17212</p>
+ </item>
+ <item>
+ <p>
+ The otp_test_engine no longer fails if NO_EC* is set in
+ the OpenSSL configuration.</p>
+ <p>
+ Own Id: OTP-17256 Aux Id: PR-4580, GH-4573 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Various address sanitizer support.</p>
+ <p>
+ Own Id: OTP-16959 Aux Id: PR-2965 </p>
+ </item>
+ <item>
+ <p>
+ EVP is now disabled for OpenSSL cryptolib versions up to
+ and including 1.0.2</p>
+ <p>
+ Own Id: OTP-17116 Aux Id: PR-2972 </p>
+ </item>
+ <item>
+ <p>
+ Warning for unused C function removed</p>
+ <p>
+ Own Id: OTP-17145 Aux Id: OTP-17105, PR-2872 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Crypto 4.8.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Adding missing flag in BN-calls in SRP.</p>
+ <p>
+ Own Id: OTP-17107</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Crypto 4.8.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed usage of <c>AC_CONFIG_AUX_DIRS()</c> macros in
+ configure script sources.</p>
+ <p>
+ Own Id: OTP-17093 Aux Id: ERL-1447, PR-2948 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Crypto 4.8.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Build the supported curves cache in the NIF when crypto
+ is loaded, no matter how it is loaded.</p>
+ <p>
+ This prevents a possible problem with different processes
+ starting the crypto application concurrently.</p>
+ <p>
+ Own Id: OTP-16819 Aux Id: PR-2720 </p>
+ </item>
+ <item>
+ <p>
+ It is now possible to build with crypto and openssl
+ gprof-enabled and statically link them into the VM.</p>
+ <p>
+ Own Id: OTP-17029</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Fixed performance loss in HMAC when using older OpenSSL
+ due to mutex issues.</p>
+ <p>
+ A workaround is implemented to allow fallback from using
+ the EVP API for HMAC operations. On some architectures
+ this may improve the performance, especially with old
+ OpenSSL versions. This fallback to low-level functions is
+ always enabled for openssl versions before 1.0.2.</p>
+ <p>
+ Own Id: OTP-17025 Aux Id: ERL-1400, PR-2877 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Crypto 4.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix type spec bug in crypto for crypto_init and
+ crypto:one_time</p>
+ <p>
+ Own Id: OTP-16658 Aux Id: OTP-15884, ERL-1257 </p>
+ </item>
+ <item>
+ <p>
+ The deprecation message for crypto:rand_uniform/2
+ indicated a non-existent function. The correct one
+ (rand:uniform/1) is now suggested.</p>
+ <p>
+ Own Id: OTP-16846 Aux Id: PR-2741 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Implemented a workaround to allow fallback from using the
+ EVP API for Diffie-Hellman key generation</p>
+ <p>
+ Own Id: OTP-16771 Aux Id: ERIERL-509 </p>
+ </item>
+ <item>
+ <p>
+ The internal Diffie-Hellman high level API for key
+ generation was slow in old and by OpenSSL now unsupported
+ cryptolib versions (1.0.1 and earlier).</p>
+ <p>
+ If such a cryptolib is used anyhow, the low-level API is
+ used internally in the crypto application.</p>
+ <p>
+ Own Id: OTP-16774</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Crypto 4.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Crypto reported unsupported elliptic curves as supported
+ on e.g Fedora distros.</p>
+ <p>
+ Own Id: OTP-16579 Aux Id: ERL-825 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Support for ed25519 and ed448 added to
+ <c>crypto:generate_key</c>.</p>
+ <p>
+ Own Id: OTP-15967 Aux Id: PR-2329 </p>
+ </item>
+ <item>
+ <p>
+ The <seeguide marker="crypto:new_api#the-new-api">new
+ crypto functions api</seeguide> (crypto_init,
+ crypto_update and crypto_one_time) has been updated.</p>
+ <p>
+ There is now a function <seemfa
+ marker="crypto:crypto#crypto_final/1"><c>crypto_final/1</c></seemfa>
+ and a possibility to set options in <seemfa
+ marker="crypto:crypto#crypto_init/3"><c>crypto_init/3</c></seemfa>
+ and <seemfa
+ marker="crypto:crypto#crypto_init/4"><c>crypto_init/4</c></seemfa>.
+ See the manual for details.</p>
+ <p>
+ Own Id: OTP-16160</p>
+ </item>
+ <item>
+ <p>
+ As <seeguide
+ marker="crypto:notes#crypto-4.5">announced</seeguide> in
+ OTP 22.0, a New API was introduced in CRYPTO. See the
+ <seeguide marker="crypto:new_api"><i>New and Old
+ API</i></seeguide> chapter in the CRYPTO User's Guide for
+ more information and suggested replacement functions.</p>
+ <p>
+ <seeguide marker="crypto:new_api#the-old-api">The Old
+ API</seeguide> is now deprecated in OTP-23.0 and will be
+ removed in OTP-24.0.</p>
+ <p>
+ This deprecation includes cipher names. See the section
+ <seeguide
+ marker="crypto:new_api#retired-cipher-names">Retired
+ cipher names</seeguide> in the crypto User's Guide,
+ chapter <seeguide marker="crypto:new_api#the-old-api">The
+ Old API</seeguide>.</p>
+ <p>
+ Own Id: OTP-16232</p>
+ </item>
+ <item>
+ <p>
+ Fix C-compilation without deprecated OpenSSL cryptolib
+ APIs</p>
+ <p>
+ Own Id: OTP-16369 Aux Id: PR-2474 </p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ Added missing 'eddh' to <seemfa
+ marker="crypto:crypto#supports/1">crypto:supports(public_keys)</seemfa>.</p>
+ <p>
+ Own Id: OTP-16583</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Crypto 4.6.5.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Adding missing flag in BN-calls in SRP.</p>
+ <p>
+ Own Id: OTP-17107</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Crypto 4.6.5.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Implemented a workaround to allow fallback from using the
+ EVP API for Diffie-Hellman key generation</p>
+ <p>
+ Own Id: OTP-16771 Aux Id: ERIERL-509 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 4.6.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -327,6 +617,21 @@
</section>
+<section><title>Crypto 4.4.2.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Adding missing flag in BN-calls in SRP.</p>
+ <p>
+ Own Id: OTP-17107</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 4.4.2.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/crypto/doc/src/ref_man.xml b/lib/crypto/doc/src/ref_man.xml
index 14922cbcec..ed7a301c28 100644
--- a/lib/crypto/doc/src/ref_man.xml
+++ b/lib/crypto/doc/src/ref_man.xml
@@ -42,7 +42,7 @@
<p>This product includes software written by Tim Hudson
(tjh@cryptsoft.com).
</p>
- <p>For full OpenSSL and SSLeay license texts, see <seealso marker="licenses#licenses">Licenses</seealso>.</p>
+ <p>For full OpenSSL and SSLeay license texts, see <seeguide marker="licenses#licenses">Licenses</seeguide>.</p>
</description>
<xi:include href="crypto_app.xml"/>
<xi:include href="crypto.xml"/>
diff --git a/lib/crypto/doc/src/release_notes.xml b/lib/crypto/doc/src/release_notes.xml
index a2d7964455..1d85be3b18 100644
--- a/lib/crypto/doc/src/release_notes.xml
+++ b/lib/crypto/doc/src/release_notes.xml
@@ -44,7 +44,7 @@
<p>This product includes software written by Tim Hudson
(tjh@cryptsoft.com).
</p>
- <p>For full OpenSSL and SSLeay license texts, see <seealso marker="licenses#licenses">Licenses</seealso>.</p>
+ <p>For full OpenSSL and SSLeay license texts, see <seeguide marker="licenses#licenses">Licenses</seeguide>.</p>
</description>
<include file="notes"></include>
</part>
diff --git a/lib/crypto/doc/src/usersguide.xml b/lib/crypto/doc/src/usersguide.xml
index 2fdb126a05..93de4870c0 100644
--- a/lib/crypto/doc/src/usersguide.xml
+++ b/lib/crypto/doc/src/usersguide.xml
@@ -43,7 +43,7 @@
<p>This product includes software written by Tim Hudson
(tjh@cryptsoft.com).
</p>
- <p>For full OpenSSL and SSLeay license texts, see <seealso marker="licenses#licenses">Licenses</seealso>.
+ <p>For full OpenSSL and SSLeay license texts, see <seeguide marker="licenses#licenses">Licenses</seeguide>.
</p>
</description>
<xi:include href="licenses.xml"/>
diff --git a/lib/crypto/src/Makefile b/lib/crypto/src/Makefile
index 1753ba4f36..c3f1c859e5 100644
--- a/lib/crypto/src/Makefile
+++ b/lib/crypto/src/Makefile
@@ -61,7 +61,7 @@ ERL_COMPILE_FLAGS += -DCRYPTO_VSN=\"$(VSN)\" -Werror -I../include
# Targets
# ----------------------------------------------------
-debug opt valgrind: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
+debug opt valgrind asan: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
clean:
rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index 991a9e630c..b38d13d844 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -38,37 +38,202 @@
-export([rand_plugin_uniform/2]).
-export([rand_cache_plugin_next/1]).
-export([rand_uniform/2]).
--export([next_iv/2, next_iv/3]).
-export([public_encrypt/4, private_decrypt/4]).
-export([private_encrypt/4, public_decrypt/4]).
-export([privkey_to_pubkey/2]).
-export([ec_curve/1, ec_curves/0]).
-export([rand_seed/1]).
-%% Old interface. Now implemented with the New interface
+%%%----------------------------------------------------------------
+%% Removed functions.
+%%
+-removed([{rand_bytes,1,"use crypto:strong_rand_bytes/1 instead"}]).
+
+-removed([{md4,1,"use crypto:hash/2 instead"}]).
+-removed([{md5,1,"use crypto:hash/2 instead"}]).
+-removed([{sha,1,"use crypto:hash/2 instead"}]).
+
+-removed([{md4_init,0,"use crypto:hash_init/1 instead"}]).
+-removed([{md5_init,0,"use crypto:hash_init/1 instead"}]).
+-removed([{sha_init,0,"use crypto:hash_init/1 instead"}]).
+
+-removed([{md4_update,2,"use crypto:hash_update/2 instead"}]).
+-removed([{md5_update,2,"use crypto:hash_update/2 instead"}]).
+-removed([{sha_update,2,"use crypto:hash_update/2 instead"}]).
+
+-removed([{md4_final,1,"use crypto:hash_final/1 instead"}]).
+-removed([{md5_final,1,"use crypto:hash_final/1 instead"}]).
+-removed([{sha_final,1,"use crypto:hash_final/1 instead"}]).
+
+-removed([{md5_mac,2,"use crypto:hmac/3 instead"}]).
+-removed([{md5_mac_96,2,"use crypto:hmac/4 instead"}]).
+
+-removed([{sha_mac,2,"use crypto:hmac/3 instead"}]).
+-removed([{sha_mac,3,"use crypto:hmac/4 instead"}]).
+-removed([{sha_mac_96,2,"use crypto:hmac/4 instead"}]).
+
+-removed([{rsa_sign,'_',"use crypto:sign/4 instead"}]).
+-removed([{rsa_verify,'_',"use crypto:verify/5 instead"}]).
+
+-removed([{dss_sign,'_',"use crypto:sign/4 instead"}]).
+-removed([{dss_verify,'_',"use crypto:verify/5 instead"}]).
+
+-removed([{mod_exp,3,"use crypto:mod_pow/3 instead"}]).
+
+-removed([{dh_compute_key,3,"use crypto:compute_key/4 instead"}]).
+-removed([{dh_generate_key,1,"use crypto:generate_key/2 instead"}]).
+-removed([{dh_generate_key,2,"use crypto:generate_key/3 instead"}]).
+
+%% DES
+
+-removed([{des_cfb_ivec,2,"use crypto:next_iv/3 instead"}]).
+-removed([{des_cbc_ivec,2,"use crypto:next_iv/2 instead"}]).
+
+-removed([{des_ecb_encrypt,2,"use crypto:block_encrypt/3 instead"}]).
+-removed([{des_cbc_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+-removed([{des_ede3_cbc_encrypt,5,"use crypto:block_encrypt/4 instead"}]).
+-removed([{des_cfb_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+
+-removed([{des_ecb_decrypt,2,"use crypto:block_decrypt/3 instead"}]).
+-removed([{des_cbc_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+-removed([{des_cfb_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+
+%% Triple-DES
+
+-removed([{des3_cbc_encrypt,5,"use crypto:block_encrypt/4 instead"}]).
+-removed([{des3_cfb_encrypt,5,"use crypto:block_encrypt/4 instead"}]).
+
+-removed([{des3_cbc_decrypt,5,"use crypto:block_decrypt/4 instead"}]).
+-removed([{des3_cfb_decrypt,5,"use crypto:block_decrypt/4 instead"}]).
+-removed([{des3_ede3_cbc_decrypt,5,"use crypto:block_decrypt/4 instead"}]).
+
+%% Blowfish
+
+-removed([{blowfish_ecb_encrypt,2,"use crypto:block_encrypt/3 instead"}]).
+-removed([{blowfish_cbc_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+-removed([{blowfish_cfb64_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+-removed([{blowfish_ofb64_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+
+-removed([{blowfish_ecb_decrypt,2,"use crypto:block_decrypt/3 instead"}]).
+-removed([{blowfish_cbc_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+-removed([{blowfish_cfb64_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+-removed([{blowfish_ofb64_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+
+%% AES
+
+-removed([{aes_cfb_128_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+-removed([{aes_cbc_128_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+-removed([{aes_cbc_256_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+
+-removed([{aes_cfb_128_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+-removed([{aes_cbc_128_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+-removed([{aes_cbc_256_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+
+-removed([{aes_ctr_stream_init,2,"use crypto:stream_init/3 instead"}]).
+-removed([{aes_ctr_stream_encrypt,2,"use crypto:stream_encrypt/2 instead"}]).
+-removed([{aes_ctr_encrypt,3,"use crypto:stream_encrypt/2 instead"}]).
+-removed([{aes_ctr_stream_decrypt,2,"use crypto:stream_decrypt/2 instead"}]).
+-removed([{aes_ctr_decrypt,3,"use crypto:stream_decrypt/2 instead"}]).
+
+-removed([{aes_cbc_ivec,2,"use crypto:next_iv/2 instead"}]).
+
+%% RC2
+
+-removed([{rc2_cbc_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+-removed([{rc2_40_cbc_encrypt,3,"use crypto:block_encrypt/4 instead"}]).
+
+-removed([{rc2_cbc_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+-removed([{rc2_40_cbc_decrypt,3,"use crypto:block_decrypt/4 instead"}]).
+
+%% RC4
+
+-removed([{rc4_set_key,2,"use crypto:stream_init/2 instead"}]).
+-removed([{rc4_encrypt,2,"use crypto:stream_encrypt/2 instead"}]).
+-removed([{rc4_encrypt_with_state,2,"use crypto:stream_encrypt/2 instead"}]).
+
+%% Other
+
+-removed([{info,0,"use crypto:module_info/0 instead"}]).
+
+-removed([{strong_rand_mpint,3,"only needed by other removed functions"}]).
+-removed([{erlint,1,"only needed by other removed functions"}]).
+-removed([{mpint,1,"only needed by other removed functions"}]).
+
+%%%----------------------------------------------------------------
+%% Old interface. Now implemented with the New interface.
+%% Remove in OTP-24.0 See OTP-16232
+
+-deprecated([{next_iv, '_',
+ "see the 'New and Old API' chapter of the CRYPTO User's guide"}]).
+-export([next_iv/2, next_iv/3]).
+
+-deprecated([{hmac, 3, "use crypto:mac/4 instead"},
+ {hmac, 4, "use crypto:macN/5 instead"},
+ {hmac_init, 2, "use crypto:mac_init/3 instead"},
+ {hmac_update, 2, "use crypto:mac_update/2 instead"},
+ {hmac_final, 1, "use crypto:mac_final/1 instead"},
+ {hmac_final_n, 2, "use crypto:mac_finalN/2 instead"}]).
+
-export([hmac/3, hmac/4, hmac_init/2, hmac_update/2, hmac_final/1, hmac_final_n/2]).
+
+-deprecated([{cmac, 3, "use crypto:mac/4 instead"},
+ {cmac, 4, "use crypto:macN/5 instead"}]).
-export([cmac/3, cmac/4]).
+
+-deprecated([{poly1305, 2, "use crypto:mac/3 instead"}]).
-export([poly1305/2]).
+
+-deprecated([{stream_init, '_',
+ "use crypto:crypto_init/3 + crypto:crypto_update/2 + "
+ "crypto:crypto_final/1 or crypto:crypto_one_time/4 instead"},
+ {stream_encrypt, 2, "use crypto:crypto_update/2 instead"},
+ {stream_decrypt, 2, "use crypto:crypto_update/2 instead"},
+ {block_encrypt, 3,
+ "use crypto:crypto_one_time/4 or crypto:crypto_init/3 + "
+ "crypto:crypto_update/2 + crypto:crypto_final/1 instead"},
+ {block_encrypt, 4,
+ "use crypto:crypto_one_time/5, crypto:crypto_one_time_aead/6,7 "
+ "or crypto:crypto_(dyn_iv)?_init + "
+ "crypto:crypto_(dyn_iv)?_update + crypto:crypto_final instead"},
+ {block_decrypt, 3,
+ "use crypto:crypto_one_time/4 or crypto:crypto_init/3 + "
+ "crypto:crypto_update/2 + crypto:crypto_final/1 instead"},
+ {block_decrypt, 4,
+ "use crypto:crypto_one_time/5, crypto:crypto_one_time_aead/6,7 "
+ "or crypto:crypto_(dyn_iv)?_init + "
+ "crypto:crypto_(dyn_iv)?_update + crypto:crypto_final instead"}
+ ]).
-export([stream_init/2, stream_init/3,
stream_encrypt/2,
stream_decrypt/2,
block_encrypt/3, block_encrypt/4,
block_decrypt/3, block_decrypt/4
]).
+-deprecated_type([{retired_cbc_cipher_aliases, 0, "Use aes_*_cbc or des_ede3_cbc"},
+ {retired_cfb_cipher_aliases, 0, "Use aes_*_cfb8, aes_*_cfb128 or des_ede3_cfb"},
+ {retired_ctr_cipher_aliases, 0, "Use aes_*_ctr"},
+ {retired_ecb_cipher_aliases, 0, "Use aes_*_ecb"}
+ ]).
+%%%----------------------------------------------------------------
%% New interface
-export([crypto_init/4, crypto_init/3,
crypto_update/2,
+
crypto_one_time/4, crypto_one_time/5,
crypto_one_time_aead/6, crypto_one_time_aead/7,
+
crypto_dyn_iv_init/3,
crypto_dyn_iv_update/3,
+ crypto_final/1,
+ crypto_get_data/1,
+
supports/1,
mac/3, mac/4, macN/4, macN/5,
mac_init/2, mac_init/3, mac_update/2, mac_final/1, mac_finalN/2
]).
-
+%%%----------------------------------------------------------------
%% Engine
-export([
engine_get_all_methods/0,
@@ -121,7 +286,7 @@
get_test_engine/0]).
-export([rand_plugin_aes_jump_2pow20/1]).
--deprecated({rand_uniform, 2, next_major_release}).
+-deprecated({rand_uniform, 2, "use rand:uniform/1 instead"}).
%% This should correspond to the similar macro in crypto.c
-define(MAX_BYTES_TO_NIF, 20000). %% Current value is: erlang:system_info(context_reductions) * 10
@@ -434,13 +599,13 @@
%% Exceptions
%% error:badarg
%% error:notsup
--type run_time_error() :: no_return().
+-type run_time_error() :: any().
%% Exceptions
%% error:{badarg,Reason::term()}
%% error:{notsup,Reason::term()}
%% error:{error,Reason::term()}
--type descriptive_error() :: no_return() .
+-type descriptive_error() :: any() .
%%--------------------------------------------------------------------
@@ -496,17 +661,18 @@ stop() ->
| {rsa_opts, RSAopts},
Hashs :: [sha1() | sha2() | sha3() | blake2() | ripemd160 | compatibility_only_hash()],
Ciphers :: [cipher()],
- PKs :: [rsa | dss | ecdsa | dh | ecdh | ec_gf2m],
+ PKs :: [rsa | dss | ecdsa | dh | ecdh | eddh | ec_gf2m],
Macs :: [hmac | cmac | poly1305],
Curves :: [ec_named_curve() | edwards_curve_dh() | edwards_curve_ed()],
RSAopts :: [rsa_sign_verify_opt() | rsa_opt()] .
supports() ->
- [{hashs, hash_algorithms()},
- {ciphers, prepend_old_aliases( cipher_algorithms())},
- {public_keys, pubkey_algorithms()},
- {macs, mac_algorithms()},
- {curves, curve_algorithms()},
- {rsa_opts, rsa_opts_algorithms()}
+ [{hashs, supports(hashs)},
+ {ciphers, prepend_old_aliases(supports(ciphers))}
+ | [{T,supports(T)} || T <- [public_keys,
+ macs,
+ curves,
+ rsa_opts]
+ ]
].
@@ -525,11 +691,13 @@ supports() ->
| RSAopts,
Hashs :: [sha1() | sha2() | sha3() | blake2() | ripemd160 | compatibility_only_hash()],
Ciphers :: [cipher()],
- PKs :: [rsa | dss | ecdsa | dh | ecdh | ec_gf2m],
+ PKs :: [rsa | dss | ecdsa | dh | ecdh | eddh | ec_gf2m],
Macs :: [hmac | cmac | poly1305],
Curves :: [ec_named_curve() | edwards_curve_dh() | edwards_curve_ed()],
RSAopts :: [rsa_sign_verify_opt() | rsa_opt()] .
+-define(CURVES, '$curves$').
+
supports(hashs) -> hash_algorithms();
supports(public_keys) -> pubkey_algorithms();
supports(ciphers) -> cipher_algorithms();
@@ -549,7 +717,10 @@ info_fips() -> ?nif_stub.
-spec enable_fips_mode(Enable) -> Result when Enable :: boolean(),
Result :: boolean().
-enable_fips_mode(_) -> ?nif_stub.
+enable_fips_mode(Enable) ->
+ enable_fips_mode_nif(Enable).
+
+enable_fips_mode_nif(_) -> ?nif_stub.
%%%================================================================
%%%
@@ -923,7 +1094,7 @@ block_encrypt(Type, Key0, Ivec, Data) ->
{AAD, PlainText, TagLength} ->
crypto_one_time_aead(alias(Type,Key), Key, Ivec, PlainText, AAD, TagLength, true);
PlainText ->
- crypto_one_time(alias(Type,Key), Key, Ivec, PlainText, true)
+ block_crypt(alias(Type,Key), Key, Ivec, PlainText, true)
end).
-spec block_encrypt(Type::block_cipher_without_iv(), Key::key(), PlainText::iodata()) ->
@@ -931,7 +1102,7 @@ block_encrypt(Type, Key0, Ivec, Data) ->
block_encrypt(Type, Key0, PlainText) ->
Key = iolist_to_binary(Key0),
- ?COMPAT(crypto_one_time(alias(Type,Key), Key, PlainText, true)).
+ ?COMPAT(block_crypt(alias(Type,Key), Key, undefined, PlainText, true)).
%%%----------------------------------------------------------------
@@ -952,7 +1123,7 @@ block_decrypt(Type, Key0, Ivec, Data) ->
{AAD, CryptoText, Tag} ->
crypto_one_time_aead(alias(Type,Key), Key, Ivec, CryptoText, AAD, Tag, false);
CryptoText ->
- crypto_one_time(alias(Type,Key), Key, Ivec, CryptoText, false)
+ block_crypt(alias(Type,Key), Key, Ivec, CryptoText, false)
end).
@@ -961,7 +1132,16 @@ block_decrypt(Type, Key0, Ivec, Data) ->
block_decrypt(Type, Key0, CryptoText) ->
Key = iolist_to_binary(Key0),
- ?COMPAT(crypto_one_time(alias(Type,Key), Key, CryptoText, false)).
+ ?COMPAT(block_crypt(alias(Type,Key), Key, undefined, CryptoText, false)).
+
+
+
+block_crypt(Cipher, Key, IV, Data, EncryptFlag) ->
+ Ctx = case IV of
+ undefined -> crypto_init(Cipher, Key, EncryptFlag);
+ _ -> crypto_init(Cipher, Key, IV, EncryptFlag)
+ end,
+ crypto_update(Ctx, Data).
%%%-------- Stream ciphers API
@@ -979,7 +1159,7 @@ stream_init(Type, Key0, IVec) when is_binary(IVec) ->
Key = iolist_to_binary(Key0),
Ref = ?COMPAT(ng_crypto_init_nif(alias(Type,Key),
Key, iolist_to_binary(IVec),
- undefined)
+ get_crypto_opts([{encrypt,undefined}]))
),
{Type, {Ref,flg_undefined}}.
@@ -992,7 +1172,7 @@ stream_init(rc4 = Type, Key0) ->
Key = iolist_to_binary(Key0),
Ref = ?COMPAT(ng_crypto_init_nif(alias(Type,Key),
Key, <<>>,
- undefined)
+ get_crypto_opts([{encrypt,undefined}]))
),
{Type, {Ref,flg_undefined}}.
@@ -1017,7 +1197,8 @@ stream_decrypt(State, Data) ->
%%%-------- helpers
crypto_stream_emulate({Cipher,{Ref0,flg_undefined}}, Data, EncryptFlag) when is_reference(Ref0) ->
?COMPAT(begin
- Ref = ng_crypto_init_nif(Ref0, <<>>, <<>>, EncryptFlag),
+ Ref = ng_crypto_init_nif(Ref0, <<>>, <<>>,
+ get_crypto_opts([{encrypt,EncryptFlag}])),
{{Cipher,Ref}, crypto_update(Ref, Data)}
end);
@@ -1059,42 +1240,86 @@ next_iv(Type, Data, _Ivec) ->
-opaque crypto_state() :: reference() .
+-type crypto_opts() :: boolean()
+ | [ crypto_opt() ] .
+-type crypto_opt() :: {encrypt,boolean()}
+ | {padding, padding()} .
+-type padding() :: cryptolib_padding() | otp_padding().
+-type cryptolib_padding() :: none | pkcs_padding .
+-type otp_padding() :: zero | random .
+
%%%----------------------------------------------------------------
%%%
%%% Create and initialize a new state for encryption or decryption
%%%
--spec crypto_init(Cipher, Key, EncryptFlag) -> State | descriptive_error()
+-spec crypto_init(Cipher, Key, FlagOrOptions) -> State | descriptive_error()
when Cipher :: cipher_no_iv(),
Key :: iodata(),
- EncryptFlag :: boolean(),
+ FlagOrOptions :: crypto_opts() | boolean(),
State :: crypto_state() .
-crypto_init(Cipher, Key, EncryptFlag) ->
- %% The IV is supposed to be supplied by calling crypto_update/3
- ng_crypto_init_nif(Cipher, iolist_to_binary(Key), <<>>, EncryptFlag).
+crypto_init(Cipher, Key, FlagOrOptions) ->
+ ng_crypto_init_nif(Cipher,
+ iolist_to_binary(Key),
+ <<>>,
+ get_crypto_opts(FlagOrOptions)).
--spec crypto_init(Cipher, Key, IV, EncryptFlag) -> State | descriptive_error()
+-spec crypto_init(Cipher, Key, IV, FlagOrOptions) -> State | descriptive_error()
when Cipher :: cipher_iv(),
Key :: iodata(),
IV :: iodata(),
- EncryptFlag :: boolean(),
+ FlagOrOptions :: crypto_opts(),
State :: crypto_state() .
-crypto_init(Cipher, Key, IV, EncryptFlag) ->
- ng_crypto_init_nif(Cipher, iolist_to_binary(Key), iolist_to_binary(IV), EncryptFlag).
+crypto_init(Cipher, Key, IV, FlagOrOptions) ->
+ ng_crypto_init_nif(Cipher,
+ iolist_to_binary(Key),
+ iolist_to_binary(IV),
+ get_crypto_opts(FlagOrOptions)).
+
+%%%----------------------------------------------------------------
+get_crypto_opts(Options) when is_list(Options) ->
+ lists:foldl(fun chk_opt/2,
+ #{encrypt => true,
+ padding => undefined
+ },
+ Options);
+get_crypto_opts(Flag) when is_boolean(Flag) ->
+ #{encrypt => Flag,
+ padding => undefined
+ };
+get_crypto_opts(X) ->
+ error({badarg,{bad_option,X}}).
+
+
+chk_opt({Tag,Val}, A) ->
+ case ok_opt(Tag,Val) of
+ true ->
+ A#{Tag => Val};
+ false ->
+ error({badarg,{bad_option,{Tag,Val}}})
+ end;
+chk_opt(X, _) ->
+ error({badarg,{bad_option,X}}).
+ok_opt(encrypt, V) -> lists:member(V, [true, false, undefined]);
+ok_opt(padding, V) -> lists:member(V, [none, pkcs_padding, zero, random, undefined]);
+ok_opt(_, _) -> false.
%%%----------------------------------------------------------------
--spec crypto_dyn_iv_init(Cipher, Key, EncryptFlag) -> State | descriptive_error()
+-spec crypto_dyn_iv_init(Cipher, Key, FlagOrOptions) -> State | descriptive_error()
when Cipher :: cipher_iv(),
Key :: iodata(),
- EncryptFlag :: boolean(),
+ FlagOrOptions :: crypto_opts() | boolean(),
State :: crypto_state() .
-crypto_dyn_iv_init(Cipher, Key, EncryptFlag) ->
+crypto_dyn_iv_init(Cipher, Key, FlagOrOptions) ->
%% The IV is supposed to be supplied by calling crypto_update/3
- ng_crypto_init_nif(Cipher, iolist_to_binary(Key), undefined, EncryptFlag).
+ ng_crypto_init_nif(Cipher,
+ iolist_to_binary(Key),
+ undefined,
+ get_crypto_opts(FlagOrOptions)).
%%%----------------------------------------------------------------
%%%
@@ -1107,14 +1332,8 @@ crypto_dyn_iv_init(Cipher, Key, EncryptFlag) ->
when State :: crypto_state(),
Data :: iodata(),
Result :: binary() .
-crypto_update(State, Data0) ->
- case iolist_to_binary(Data0) of
- <<>> ->
- <<>>; % Known to fail on OpenSSL 0.9.8h
- Data ->
- ng_crypto_update_nif(State, Data)
- end.
-
+crypto_update(State, Data) ->
+ ng_crypto_update_nif(State, iolist_to_binary(Data)).
%%%----------------------------------------------------------------
-spec crypto_dyn_iv_update(State, Data, IV) -> Result | descriptive_error()
@@ -1122,14 +1341,30 @@ crypto_update(State, Data0) ->
Data :: iodata(),
IV :: iodata(),
Result :: binary() .
-crypto_dyn_iv_update(State, Data0, IV) ->
- %% When State is from State = crypto_init(Cipher, Key, undefined, EncryptFlag)
- case iolist_to_binary(Data0) of
- <<>> ->
- <<>>; % Known to fail on OpenSSL 0.9.8h
- Data ->
- ng_crypto_update_nif(State, Data, iolist_to_binary(IV))
- end.
+crypto_dyn_iv_update(State, Data, IV) ->
+ ng_crypto_update_nif(State, iolist_to_binary(Data), iolist_to_binary(IV)).
+
+%%%----------------------------------------------------------------
+%%%
+%%% Finalize encrypt/decrypt bytes. If the size of the bytes in
+%%% to crypto_uptate was not an integer number of blocks, the rest
+%%% is returned from this function.
+
+-spec crypto_final(State) -> FinalResult | descriptive_error()
+ when State :: crypto_state(),
+ FinalResult :: binary() .
+crypto_final(State) ->
+ ng_crypto_final_nif(State).
+
+%%%----------------------------------------------------------------
+%%%
+%%% Get result of padding etc
+
+-spec crypto_get_data(State) -> Result
+ when State :: crypto_state(),
+ Result :: map() .
+crypto_get_data(State) ->
+ ng_crypto_get_data_nif(State).
%%%----------------------------------------------------------------
%%%
@@ -1137,44 +1372,39 @@ crypto_dyn_iv_update(State, Data0, IV) ->
%%% The size must be an integer multiple of the crypto's blocksize.
%%%
--spec crypto_one_time(Cipher, Key, Data, EncryptFlag) ->
+-spec crypto_one_time(Cipher, Key, Data, FlagOrOptions) ->
Result | descriptive_error()
when Cipher :: cipher_no_iv(),
Key :: iodata(),
Data :: iodata(),
- EncryptFlag :: boolean(),
+ FlagOrOptions :: crypto_opts() | boolean(),
Result :: binary() .
-crypto_one_time(Cipher, Key, Data0, EncryptFlag) ->
- case iolist_to_binary(Data0) of
- <<>> ->
- <<>>; % Known to fail on OpenSSL 0.9.8h
- Data ->
- ng_crypto_one_time_nif(Cipher,
- iolist_to_binary(Key), <<>>, Data,
- EncryptFlag)
- end.
+crypto_one_time(Cipher, Key, Data, FlagOrOptions) ->
+ ng_crypto_one_time_nif(Cipher,
+ iolist_to_binary(Key),
+ <<>>,
+ iolist_to_binary(Data),
+ get_crypto_opts(FlagOrOptions)).
+
--spec crypto_one_time(Cipher, Key, IV, Data, EncryptFlag) ->
+-spec crypto_one_time(Cipher, Key, IV, Data, FlagOrOptions) ->
Result | descriptive_error()
when Cipher :: cipher_iv(),
Key :: iodata(),
IV :: iodata(),
Data :: iodata(),
- EncryptFlag :: boolean(),
+ FlagOrOptions :: crypto_opts() | boolean(),
Result :: binary() .
-crypto_one_time(Cipher, Key, IV, Data0, EncryptFlag) ->
- case iolist_to_binary(Data0) of
- <<>> ->
- <<>>; % Known to fail on OpenSSL 0.9.8h
- Data ->
- ng_crypto_one_time_nif(Cipher,
- iolist_to_binary(Key), iolist_to_binary(IV), Data,
- EncryptFlag)
- end.
-
+crypto_one_time(Cipher, Key, IV, Data, FlagOrOptions) ->
+ ng_crypto_one_time_nif(Cipher,
+ iolist_to_binary(Key),
+ iolist_to_binary(IV),
+ iolist_to_binary(Data),
+ get_crypto_opts(FlagOrOptions)).
+%%%----------------------------------------------------------------
-spec crypto_one_time_aead(Cipher, Key, IV, InText, AAD, EncFlag::true) ->
Result | descriptive_error()
when Cipher :: cipher_aead(),
@@ -1227,26 +1457,25 @@ aead_tag_len(_) -> error({badarg, "Not an AEAD cipher"}).
%%%----------------------------------------------------------------
%%% NIFs
--spec ng_crypto_init_nif(atom(), binary(), binary()|undefined, boolean()|undefined ) ->
- crypto_state() | descriptive_error()
- ; (crypto_state(), <<>>, <<>>, boolean())
- -> crypto_state() | descriptive_error().
+ng_crypto_init_nif(Cipher, Key, IVec, #{encrypt := EncryptFlag,
+ padding := Padding}) ->
+ ng_crypto_init_nif(Cipher, Key, IVec, EncryptFlag, Padding).
+
+ng_crypto_init_nif(_Cipher, _Key, _IVec, _EncryptFlag, _Padding) -> ?nif_stub.
-ng_crypto_init_nif(_Cipher, _Key, _IVec, _EncryptFlg) -> ?nif_stub.
-
--spec ng_crypto_update_nif(crypto_state(), binary()) ->
- binary() | descriptive_error() .
ng_crypto_update_nif(_State, _Data) -> ?nif_stub.
-
--spec ng_crypto_update_nif(crypto_state(), binary(), binary()) ->
- binary() | descriptive_error() .
ng_crypto_update_nif(_State, _Data, _IV) -> ?nif_stub.
+ng_crypto_final_nif(_State) -> ?nif_stub.
+
+ng_crypto_get_data_nif(_State) -> ?nif_stub.
--spec ng_crypto_one_time_nif(atom(), binary(), binary(), binary(), boolean() ) ->
- binary() | descriptive_error().
-ng_crypto_one_time_nif(_Cipher, _Key, _IVec, _Data, _EncryptFlg) -> ?nif_stub.
+ng_crypto_one_time_nif(Cipher, Key, IVec, Data, #{encrypt := EncryptFlag,
+ padding := Padding}) ->
+ ng_crypto_one_time_nif(Cipher, Key, IVec, Data, EncryptFlag, Padding).
+
+ng_crypto_one_time_nif(_Cipher, _Key, _IVec, _Data, _EncryptFlag, _Padding) -> ?nif_stub.
%%%----------------------------------------------------------------
%%% Cipher aliases
@@ -1461,7 +1690,7 @@ rand_plugin_aes_next({Key,GenWords,F,_JumpBase,Count}) ->
%%
rand_plugin_aes_next(Key, GenWords, F, Count) ->
{Cleartext,NewCount} = aes_cleartext(<<>>, F, Count, GenWords),
- Encrypted = crypto:block_encrypt(aes_ecb, Key, Cleartext),
+ Encrypted = block_encrypt(aes_ecb, Key, Cleartext),
[V|Cache] = aes_cache(Encrypted, {Key,GenWords,F,Count,NewCount}),
{V,Cache}.
@@ -1791,21 +2020,21 @@ pkey_crypt_nif(_Algorithm, _In, _Key, _Options, _IsPrivate, _IsEncrypt) -> ?nif_
-spec generate_key(Type, Params)
-> {PublicKey, PrivKeyOut}
- when Type :: dh | ecdh | rsa | srp,
+ when Type :: dh | ecdh | eddh | eddsa | rsa | srp,
PublicKey :: dh_public() | ecdh_public() | rsa_public() | srp_public(),
PrivKeyOut :: dh_private() | ecdh_private() | rsa_private() | {srp_public(),srp_private()},
- Params :: dh_params() | ecdh_params() | rsa_params() | srp_gen_params()
+ Params :: dh_params() | ecdh_params() | eddsa_params() | rsa_params() | srp_gen_params()
.
generate_key(Type, Params) ->
generate_key(Type, Params, undefined).
-spec generate_key(Type, Params, PrivKeyIn)
-> {PublicKey, PrivKeyOut}
- when Type :: dh | ecdh | rsa | srp,
+ when Type :: dh | ecdh | eddh | eddsa | rsa | srp,
PublicKey :: dh_public() | ecdh_public() | rsa_public() | srp_public(),
PrivKeyIn :: undefined | dh_private() | ecdh_private() | rsa_private() | {srp_public(),srp_private()},
PrivKeyOut :: dh_private() | ecdh_private() | rsa_private() | {srp_public(),srp_private()},
- Params :: dh_params() | ecdh_params() | rsa_params() | srp_comp_params()
+ Params :: dh_params() | ecdh_params() | eddsa_params() | rsa_params() | srp_comp_params()
.
generate_key(dh, DHParameters0, PrivateKey) ->
@@ -1843,20 +2072,26 @@ generate_key(rsa, {ModulusSize, PublicExponent}, undefined) ->
{lists:sublist(Private, 2), Private}
end;
-
-generate_key(ecdh, Curve, undefined) when Curve == x448 ;
- Curve == x25519 ->
- evp_generate_key_nif(Curve);
+generate_key(eddh, Curve, PrivKey) when Curve == x448 ;
+ Curve == x25519 ->
+ evp_generate_key_nif(Curve, ensure_int_as_bin(PrivKey));
+generate_key(ecdh, Curve, PrivKey) when Curve == x448 ;
+ Curve == x25519 ->
+ %% This was here before the eddh was added as an own Type
+ evp_generate_key_nif(Curve, ensure_int_as_bin(PrivKey));
generate_key(ecdh, Curve, PrivKey) ->
- ec_key_generate(nif_curve_params(Curve), ensure_int_as_bin(PrivKey)).
+ ec_key_generate(nif_curve_params(Curve), ensure_int_as_bin(PrivKey));
+generate_key(eddsa, Curve, PrivKey) when Curve == ed448 ;
+ Curve == ed25519 ->
+ evp_generate_key_nif(Curve, ensure_int_as_bin(PrivKey)).
-evp_generate_key_nif(_Curve) -> ?nif_stub.
+evp_generate_key_nif(_Curve, _PrivKey) -> ?nif_stub.
-spec compute_key(Type, OthersPublicKey, MyPrivateKey, Params)
-> SharedSecret
- when Type :: dh | ecdh | srp,
+ when Type :: dh | ecdh | eddh | srp,
SharedSecret :: binary(),
OthersPublicKey :: dh_public() | ecdh_public() | srp_public(),
MyPrivateKey :: dh_private() | ecdh_private() | {srp_public(),srp_private()},
@@ -1906,6 +2141,10 @@ compute_key(ecdh, Others, My, Curve) when Curve == x448 ;
Curve == x25519 ->
evp_compute_key_nif(Curve, ensure_int_as_bin(Others), ensure_int_as_bin(My));
+compute_key(eddh, Others, My, Curve) when Curve == x448 ;
+ Curve == x25519 ->
+ evp_compute_key_nif(Curve, ensure_int_as_bin(Others), ensure_int_as_bin(My));
+
compute_key(ecdh, Others, My, Curve) ->
ecdh_compute_key_nif(ensure_int_as_bin(Others),
nif_curve_params(Curve),
@@ -2306,7 +2545,7 @@ on_load() ->
filename:join([PrivDir, "lib",
erlang:system_info(system_architecture)]),
Candidate =
- filelib:wildcard(filename:join([ArchLibDir,LibName ++ "*" ])),
+ filelib:wildcard(filename:join([ArchLibDir,LibName ++ "*" ]),erl_prim_loader),
case Candidate of
[] -> Error1;
_ ->
@@ -2746,10 +2985,10 @@ get_test_engine() ->
end.
check_otp_test_engine(LibDir) ->
- case filelib:wildcard("otp_test_engine*", LibDir) of
- [] ->
+ case choose_otp_test_engine(LibDir) of
+ false ->
{error, notexist};
- [LibName|_] -> % In case of Valgrind there could be more than one
+ LibName ->
LibPath = filename:join(LibDir,LibName),
case filelib:is_file(LibPath) of
true ->
@@ -2760,3 +2999,20 @@ check_otp_test_engine(LibDir) ->
end.
+choose_otp_test_engine(LibDir) ->
+ LibNames = filelib:wildcard("otp_test_engine.*", LibDir),
+ Type = atom_to_list(erlang:system_info(build_type)),
+ choose_otp_test_engine(LibNames, Type, false).
+
+choose_otp_test_engine([LibName | T], Type, Acc) ->
+ case string:lexemes(LibName, ".") of
+ [_, Type, _SO] ->
+ LibName; %% Choose typed if exists (valgrind,asan)
+ [_, _SO] ->
+ %% Fallback on typeless (opt)
+ choose_otp_test_engine(T, Type, LibName);
+ _ ->
+ choose_otp_test_engine(T, Type, Acc)
+ end;
+choose_otp_test_engine([], _, Acc) ->
+ Acc.
diff --git a/lib/crypto/test/Makefile b/lib/crypto/test/Makefile
index bc3d25585a..aaa2df0534 100644
--- a/lib/crypto/test/Makefile
+++ b/lib/crypto/test/Makefile
@@ -5,7 +5,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# Target Specs
# ----------------------------------------------------
-MODULES = \
+MODULES= \
crypto_bench_SUITE \
crypto_SUITE \
crypto_property_test_SUITE \
@@ -15,56 +15,30 @@ ERL_FILES= $(MODULES:%=%.erl)
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-SOURCE = $(ERL_FILES) $(HRL_FILES)
+SPEC_FILES = \
+ crypto.spec \
+ crypto_bench.spec
-EMAKEFILE=Emakefile
+COVER_FILE = crypto.cover
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
RELSYSDIR = $(RELEASE_PATH)/crypto_test
-
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
-ERL_MAKE_FLAGS +=
-ERL_COMPILE_FLAGS += +nowarn_export_all
EBIN = .
-MAKE_EMAKE = $(wildcard $(ERL_TOP)/make/make_emakefile)
-
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-
-# Backward compatibility, for R9B and earlier.
-
-ifeq ($(MAKE_EMAKE),)
-
-RELTEST_FILES = $(SOURCE) $(TARGET_FILES)
-TEST_TARGET = tests
-
tests debug opt: $(TARGET_FILES)
-else
-
-RELTEST_FILES = $(EMAKEFILE) $(SOURCE)
-TEST_TARGET = make_emakefile
-
-
-tests debug opt: make_emakefile
- erl $(ERL_MAKE_FLAGS) -make
-
-make_emakefile:
- $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) \
- $(MODULES) > $(EMAKEFILE)
-
-endif
clean:
- rm -f $(EMAKEFILE)
- rm -f $(TARGET_FILES) $(GEN_FILES)
+ rm -f $(TARGET_FILES)
rm -f core
docs:
@@ -74,14 +48,16 @@ docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_spec:
+release_spec: opt
-release_tests_spec: $(TEST_TARGET)
+release_tests_spec: opt
$(INSTALL_DIR) "$(RELSYSDIR)"
- $(INSTALL_DATA) crypto.spec crypto_bench.spec crypto.cover $(RELTEST_FILES) "$(RELSYSDIR)"
- @tar cfh - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -)
+ $(INSTALL_DATA) $(SPEC_FILES) $(COVER_FILE) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)"
+# $(INSTALL_DATA) $(HRL_FILES) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(TARGET_FILES) "$(RELSYSDIR)"
chmod -R u+w "$(RELSYSDIR)"
- @tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -)
+ @tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -)
release_docs_spec:
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index deaa3ffba0..f7b7c39099 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -21,8 +21,192 @@
-include_lib("common_test/include/ct.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+
+-export([
+ %% CT callbacks:
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2,
+
+ %% Test cases:
+ aead/1,
+ aead_bad_tag/1,
+ aead_ng/1,
+ api_errors_ecdh/1,
+ api_ng/0,
+ api_ng/1,
+ api_ng_one_shot/0,
+ api_ng_one_shot/1,
+ api_ng_tls/0,
+ api_ng_tls/1,
+ app/0,
+ app/1,
+ appup/0,
+ appup/1,
+ bad_cipher_name/1,
+ bad_generate_key_name/1,
+ bad_hash_name/1,
+ bad_hmac_name/1,
+ bad_mac_name/1,
+ bad_sign_name/1,
+ bad_verify_name/1,
+ block/0,
+ block/1,
+ cipher_info/0,
+ cipher_info/1,
+ cipher_padding/1,
+ cmac/0,
+ cmac/1,
+ compute/0,
+ compute/1,
+ compute_bug/0,
+ compute_bug/1,
+ crypto_load/1,
+ crypto_load_and_call/1,
+ exor/0,
+ exor/1,
+ generate/0,
+ generate/1,
+ generate_compute/0,
+ generate_compute/1,
+ hash/0,
+ hash/1,
+ hash_info/0,
+ hash_info/1,
+ hmac/0,
+ hmac/1,
+ hmac_update/0,
+ hmac_update/1,
+ mod_pow/0,
+ mod_pow/1,
+ no_aead/0,
+ no_aead/1,
+ no_aead_ng/0,
+ no_aead_ng/1,
+ no_block/0,
+ no_block/1,
+ no_generate_compute/0,
+ no_generate_compute/1,
+ no_hash/0,
+ no_hash/1,
+ no_hmac/0,
+ no_hmac/1,
+ no_hmac_update/0,
+ no_hmac_update/1,
+ no_poly1305/0,
+ no_poly1305/1,
+ no_sign_verify/0,
+ no_sign_verify/1,
+ no_stream/0,
+ no_stream/1,
+ no_stream_ivec/0,
+ no_stream_ivec/1,
+ no_support/0,
+ no_support/1,
+ node_supports_cache/1,
+ poly1305/0,
+ poly1305/1,
+ private_encrypt/0,
+ private_encrypt/1,
+ public_encrypt/0,
+ public_encrypt/1,
+ rand_plugin/0,
+ rand_plugin/1,
+ rand_plugin_s/0,
+ rand_plugin_s/1,
+ rand_threads/0,
+ rand_threads/1,
+ rand_uniform/0,
+ rand_uniform/1,
+ sign_verify/0,
+ sign_verify/1,
+ stream/0,
+ stream/1,
+ use_all_ec_sign_verify/1,
+ use_all_ecdh_generate_compute/1,
+ use_all_eddh_generate_compute/1,
+
+ %% Others:
+ aes_128_cbc/1,
+ aes_128_ccm/1,
+ aes_128_cfb128/1,
+ aes_128_cfb8/1,
+ aes_128_ctr/1,
+ aes_128_ecb/1,
+ aes_128_gcm/1,
+ aes_192_cbc/1,
+ aes_192_ccm/1,
+ aes_192_cfb128/1,
+ aes_192_cfb8/1,
+ aes_192_ctr/1,
+ aes_192_ecb/1,
+ aes_192_gcm/1,
+ aes_256_cbc/1,
+ aes_256_ccm/1,
+ aes_256_cfb128/1,
+ aes_256_cfb8/1,
+ aes_256_ctr/1,
+ aes_256_ecb/1,
+ aes_256_gcm/1,
+ aes_cbc/1,
+ aes_cbc128/1,
+ aes_cbc256/1,
+ aes_ccm/1,
+ aes_cfb128/1,
+ aes_cfb8/1,
+ aes_ctr/1,
+ aes_ecb/1,
+ aes_gcm/1,
+ aes_ige256/1,
+ blowfish_cbc/1,
+ blowfish_cfb64/1,
+ blowfish_ecb/1,
+ blowfish_ofb64/1,
+ chacha20/1,
+ chacha20_poly1305/1,
+ des3_cbc/1,
+ des3_cbf/1,
+ des3_cfb/1,
+ des_cbc/1,
+ des_cfb/1,
+ des_ede3/1,
+ des_ede3_cbc/1,
+ des_ede3_cfb/1,
+ mac_check/1,
+ rc2_cbc/1,
+ rc4/1,
+ ripemd160_incr_digest/0,
+ ripemd160_incr_msgs/0,
+ rsa_oaep/0,
+ rsa_oaep256/0,
+ rsa_oaep_label/0
+ ]).
+
+-compile([{nowarn_deprecated_function,
+ [{crypto,block_decrypt,3},
+ {crypto,block_decrypt,4},
+ {crypto,block_encrypt,3},
+ {crypto,block_encrypt,4},
+ {crypto,cmac,3},
+ {crypto,cmac,4},
+ {crypto,hmac,3},
+ {crypto,hmac,4},
+ {crypto,hmac_final,1},
+ {crypto,hmac_init,2},
+ {crypto,hmac_update,2},
+ {crypto,next_iv,2},
+ {crypto,poly1305,2},
+ {crypto,stream_decrypt,2},
+ {crypto,stream_encrypt,2},
+ {crypto,stream_init,2},
+ {crypto,stream_init,3}
+ ]}]).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -33,8 +217,12 @@ all() ->
[app,
{group, api_errors},
appup,
+ crypto_load,
+ crypto_load_and_call,
{group, fips},
{group, non_fips},
+ cipher_padding,
+ node_supports_cache,
mod_pow,
exor,
rand_uniform,
@@ -115,6 +303,7 @@ groups() ->
{group, dh},
{group, ecdh},
+ {group, eddh},
{group, srp},
{group, chacha20_poly1305},
@@ -197,17 +386,20 @@ groups() ->
{dss, [], [sign_verify
%% Does not work yet: ,public_encrypt, private_encrypt
]},
- {ecdsa, [], [sign_verify
+ {ecdsa, [], [sign_verify, use_all_ec_sign_verify
%% Does not work yet: ,public_encrypt, private_encrypt
]},
- {ed25519, [], [sign_verify
+ {ed25519, [], [sign_verify,
%% Does not work yet: ,public_encrypt, private_encrypt
+ generate
]},
- {ed448, [], [sign_verify
+ {ed448, [], [sign_verify,
%% Does not work yet: ,public_encrypt, private_encrypt
+ generate
]},
{dh, [], [generate_compute, compute_bug]},
- {ecdh, [], [use_all_elliptic_curves, compute, generate]},
+ {ecdh, [], [compute, generate, use_all_ecdh_generate_compute]},
+ {eddh, [], [compute, generate, use_all_eddh_generate_compute]},
{srp, [], [generate_compute]},
{des_cbc, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
{des_cfb, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
@@ -308,7 +500,7 @@ init_per_suite(Config) ->
{ok, _} = zip:unzip("cmactestvectors.zip"),
{ok, _} = zip:unzip("gcmtestvectors.zip"),
- try crypto:start() of
+ try is_ok(crypto:start()) of
ok ->
catch ct:comment("~s",[element(3,hd(crypto:info_lib()))]),
catch ct:log("crypto:info_lib() -> ~p~n"
@@ -331,10 +523,18 @@ init_per_suite(Config) ->
crypto:rand_seed(<< <<Bin/binary>> || _ <- lists:seq(1,16) >>),
Config
end
- catch _:_ ->
+
+ catch C:E:S ->
+ ct:log("~p ~p~n~p", [C,E,S]),
{fail, "Crypto did not start"}
end.
+is_ok(ok) -> ok;
+is_ok({error, already_started}) -> ok;
+is_ok({error,{already_started,crypto}}) -> ok.
+
+
+
end_per_suite(_Config) ->
application:stop(crypto).
@@ -426,6 +626,58 @@ no_support(Config) when is_list(Config) ->
Type = ?config(type, Config),
false = is_supported(Type).
%%--------------------------------------------------------------------
+crypto_load(_Config) ->
+ (catch crypto:stop()),
+ code:delete(crypto),
+ code:purge(crypto),
+ crypto:start().
+%%--------------------------------------------------------------------
+crypto_load_and_call(_Config) ->
+ (catch crypto:stop()),
+ code:delete(crypto),
+ code:purge(crypto),
+ Key0 = "ablurf123BX#$;3",
+ Bin0 = erlang:md5(<<"whatever">>),
+ {Key,IVec,BlockSize}=make_crypto_key(Key0),
+ crypto:crypto_one_time(des_ede3_cbc, Key, IVec, Bin0, true).
+
+make_crypto_key(String) ->
+ <<K1:8/binary,K2:8/binary>> = First = erlang:md5(String),
+ <<K3:8/binary,IVec:8/binary>> = erlang:md5([First|lists:reverse(String)]),
+ {[K1,K2,K3],IVec,8}.
+%%--------------------------------------------------------------------
+%% Test that a spawned node has initialized the cache
+-define(at_node,
+ (fun(N, M, F, As) ->
+ R = rpc:call(N, M, F, As),
+ ct:log("~p ~p ~p:~p(~s) = ~p", [?LINE,N,M,F,args2list(As), R]),
+ R
+ end) ).
+args2list(As) -> lists:join(", ", [io_lib:format("~p",[A]) || A <- As]).
+
+node_supports_cache(_Config) ->
+ ECs = crypto:supports(curves),
+ {ok,Node} = start_slave_node(random_node_name(?MODULE)),
+ case ?at_node(Node, crypto, supports, [curves]) of
+ ECs ->
+ test_server:stop_node(Node);
+ OtherECs ->
+ ct:log("At master:~p~nAt slave:~p~n"
+ "Missing at slave: ~p~nmissing at master: ~p",
+ [ECs, OtherECs, ECs--OtherECs, OtherECs--ECs]),
+ {fail, "different support at slave"}
+ end.
+
+
+start_slave_node(Name) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{args, " -pa " ++ Pa}]).
+
+random_node_name(BaseName) ->
+ L = integer_to_list(erlang:unique_integer([positive])),
+ lists:concat([BaseName,"___",L]).
+
+%%--------------------------------------------------------------------
hash() ->
[{doc, "Test all different hash functions"}].
hash(Config) when is_list(Config) ->
@@ -449,8 +701,7 @@ hmac() ->
[{doc, "Test hmac function"}].
hmac(Config) when is_list(Config) ->
Tuples = lazy_eval(proplists:get_value(hmac, Config)),
- lists:foreach(fun hmac_check/1, Tuples),
- lists:foreach(fun hmac_check/1, mac_listify(Tuples)).
+ do_cipher_tests(fun hmac_check/1, Tuples++mac_listify(Tuples)).
%%--------------------------------------------------------------------
no_hmac() ->
@@ -478,8 +729,7 @@ cmac() ->
[{doc, "Test all different cmac functions"}].
cmac(Config) when is_list(Config) ->
Pairs = lazy_eval(proplists:get_value(cmac, Config)),
- lists:foreach(fun cmac_check/1, Pairs),
- lists:foreach(fun cmac_check/1, mac_listify(Pairs)).
+ do_cipher_tests(fun cmac_check/1, Pairs ++ mac_listify(Pairs)).
%%--------------------------------------------------------------------
poly1305() ->
@@ -509,8 +759,7 @@ block() ->
[{doc, "Test block ciphers"}].
block(Config) when is_list(Config) ->
[_|_] = Blocks = lazy_eval(proplists:get_value(cipher, Config)),
- lists:foreach(fun block_cipher/1, Blocks),
- lists:foreach(fun block_cipher/1, block_iolistify(Blocks)),
+ do_cipher_tests(fun block_cipher/1, Blocks++block_iolistify(Blocks)),
lists:foreach(fun block_cipher_increment/1, block_iolistify(Blocks)).
%%--------------------------------------------------------------------
@@ -551,7 +800,8 @@ api_ng_cipher_increment({Type, Key, IV, PlainText0, ExpectedEncText}=_X) ->
RefEnc = crypto:crypto_init(Type, Key, IV, true),
RefDec = crypto:crypto_init(Type, Key, IV, false),
EncTexts = api_ng_cipher_increment_loop(RefEnc, PlainTexts),
- Enc = iolist_to_binary(EncTexts),
+ EncFinal = crypto:crypto_final(RefEnc),
+ Enc = iolist_to_binary(EncTexts++[EncFinal]),
case ExpectedEncText of
undefined ->
ok;
@@ -562,7 +812,9 @@ api_ng_cipher_increment({Type, Key, IV, PlainText0, ExpectedEncText}=_X) ->
ct:fail("api_ng_cipher_increment (encode)",[])
end,
Plain = iolist_to_binary(PlainTexts),
- case iolist_to_binary(api_ng_cipher_increment_loop(RefDec, EncTexts)) of
+ DecTexts = api_ng_cipher_increment_loop(RefDec, EncTexts),
+ DecFinal = crypto:crypto_final(RefDec),
+ case iolist_to_binary(DecTexts++[DecFinal]) of
Plain ->
ok;
OtherPT ->
@@ -579,7 +831,7 @@ api_ng_cipher_increment_loop(Ref, InTexts) ->
Bin
catch
error:Error ->
- ct:pal("Txt = ~p",[Txt]),
+ ct:log("Txt = ~p",[Txt]),
ct:fail("~p",[Error])
end
end, InTexts).
@@ -651,17 +903,16 @@ api_ng_tls(Config) when is_list(Config) ->
lists:foreach(fun do_api_ng_tls/1, Ciphers).
-do_api_ng_tls({Type, Key, PlainTexts}=_X) ->
- ct:log("~p",[_X]),
+do_api_ng_tls({Type, Key, PlainTexts}) ->
do_api_ng_tls({Type, Key, <<>>, PlainTexts});
-do_api_ng_tls({Type, Key, IV, PlainTexts}=_X) ->
- ct:log("~p",[_X]),
+do_api_ng_tls({Type, Key, IV, PlainTexts}) ->
do_api_ng_tls({Type, Key, IV, PlainTexts, undefined});
-do_api_ng_tls({Type, Key, IV, PlainText0, ExpectedEncText}=_X) ->
- ct:log("~p",[_X]),
+do_api_ng_tls({Type, Key, IV, PlainText0, ExpectedEncText}) ->
PlainText = iolist_to_binary(lazy_eval(PlainText0)),
+ ct:log("Type = ~p~nKey = ~p~nIV = ~p~nPlainText = ~p~nExpectedEncText = ~p",
+ [Type, Key, IV, PlainText, ExpectedEncText]),
Renc = crypto:crypto_dyn_iv_init(Type, Key, true),
Rdec = crypto:crypto_dyn_iv_init(Type, Key, false),
EncTxt = crypto:crypto_dyn_iv_update(Renc, PlainText, IV),
@@ -697,6 +948,60 @@ do_api_ng_tls({Type, Key, IV, PlainText0, ExpectedEncText}=_X) ->
end.
%%--------------------------------------------------------------------
+cipher_padding(_Config) ->
+ Ciphers = [{C,pkcs_padding}
+ || C <- crypto:supports(ciphers),
+ C =/= aes_ige256,
+ C =/= chacha20_poly1305,
+ case crypto:cipher_info(C) of
+ #{mode := ccm_mode} -> false;
+ #{mode := gcm_mode} -> false;
+ _ -> true
+ end],
+ lists:foreach(fun cipher_padding_test/1, Ciphers).
+
+cipher_padding_test({Cipher, Padding}) ->
+ #{block_size := Sblock,
+ iv_length := Siv,
+ key_length := Skey} = Inf = crypto:cipher_info(Cipher),
+ ct:log("~p ~p", [Cipher,Inf]),
+
+ Key = <<1:Skey/unit:8>>,
+ IV = <<0:Siv/unit:8>>,
+ MsgLen = 5*Sblock + 3,
+ Tplain = crypto:strong_rand_bytes(MsgLen),
+ PadSize = if
+ (Padding == zero) ; (Padding == random) ->
+ (Sblock - (MsgLen rem Sblock)) rem Sblock;
+ true ->
+ 0
+ end,
+ Tcrypt =
+ case Siv of
+ 0 ->
+ crypto:crypto_one_time(Cipher, Key, Tplain, [{encrypt,true},{padding,Padding}]);
+ _ ->
+ crypto:crypto_one_time(Cipher, Key, IV, Tplain, [{encrypt,true},{padding,Padding}])
+ end,
+
+ TdecryptPadded =
+ case Siv of
+ 0 ->
+ crypto:crypto_one_time(Cipher, Key, Tcrypt, [{encrypt,false},{padding,Padding}]);
+ _ ->
+ crypto:crypto_one_time(Cipher, Key, IV, Tcrypt, [{encrypt,false},{padding,Padding}])
+ end,
+
+ case split_binary(TdecryptPadded, size(TdecryptPadded) - PadSize) of
+ {Tplain, _} ->
+ ok;
+ {Tdecrypt,Tpad} ->
+ ct:log("Key = ~p~nIV = ~p~nTplain = ~p~nTcrypt = ~p~nTdecrypt = ~p~nPadding = ~p",
+ [Key, IV, Tplain, Tcrypt, Tdecrypt, Tpad]),
+ ct:fail("~p", [Cipher])
+ end.
+
+%%--------------------------------------------------------------------
no_aead() ->
[{doc, "Test disabled aead ciphers"}].
no_aead(Config) when is_list(Config) ->
@@ -753,8 +1058,6 @@ no_stream_ivec(Config) when is_list(Config) ->
notsup(fun crypto:stream_init/3, [Type, <<"Key">>, <<"Ivec">>]).
%%--------------------------------------------------------------------
-aead() ->
- [{doc, "Test AEAD ciphers"}].
aead(Config) when is_list(Config) ->
[_|_] = AEADs = lazy_eval(proplists:get_value(cipher, Config)),
FilteredAEADs =
@@ -769,7 +1072,7 @@ aead(Config) when is_list(Config) ->
IVLen >= 12
end, AEADs)
end,
- lists:foreach(fun aead_cipher/1, FilteredAEADs).
+ do_cipher_tests(fun aead_cipher/1, FilteredAEADs).
%%--------------------------------------------------------------------
aead_ng(Config) when is_list(Config) ->
@@ -786,7 +1089,7 @@ aead_ng(Config) when is_list(Config) ->
IVLen >= 12
end, AEADs)
end,
- lists:foreach(fun aead_cipher_ng/1, FilteredAEADs ++ spec_0_bytes(Config)).
+ do_cipher_tests(fun aead_cipher_ng/1, FilteredAEADs ++ spec_0_bytes(Config)).
%%--------------------------------------------------------------------
aead_bad_tag(Config) ->
@@ -803,7 +1106,7 @@ aead_bad_tag(Config) ->
IVLen >= 12
end, AEADs)
end,
- lists:foreach(fun aead_cipher_bad_tag/1, FilteredAEADs).
+ do_cipher_tests(fun aead_cipher_bad_tag/1, FilteredAEADs).
%%--------------------------------------------------------------------
sign_verify() ->
@@ -823,6 +1126,7 @@ no_sign_verify(Config) when is_list(Config) ->
public_encrypt() ->
[{doc, "Test public_encrypt/decrypt "}].
public_encrypt(Config) when is_list(Config) ->
+ ct:log("public_encrypt", []),
Params = proplists:get_value(pub_pub_encrypt, Config, []),
lists:foreach(fun do_public_encrypt/1, Params).
@@ -882,9 +1186,7 @@ compute(Config) when is_list(Config) ->
Gen = proplists:get_value(compute, Config),
lists:foreach(fun do_compute/1, Gen).
%%--------------------------------------------------------------------
-use_all_elliptic_curves() ->
- [{doc, " Test that all curves from crypto:ec_curves/0"}].
-use_all_elliptic_curves(_Config) ->
+use_all_ec_sign_verify(_Config) ->
Msg = <<"hello world!">>,
Sups = crypto:supports(),
Curves = proplists:get_value(curves, Sups),
@@ -896,6 +1198,7 @@ use_all_elliptic_curves(_Config) ->
Results =
[{{Curve,Hash},
try
+ ct:log("~p ~p",[Curve,Hash]),
{Pub,Priv} = crypto:generate_key(ecdh, Curve),
true = is_binary(Pub),
true = is_binary(Priv),
@@ -921,6 +1224,57 @@ use_all_elliptic_curves(_Config) ->
end.
%%--------------------------------------------------------------------
+use_all_ecdh_generate_compute(Config) ->
+ Curves = crypto:supports(curves) -- [ed25519, ed448, x25519, x448],
+ do_dh_curves(Config, Curves).
+
+use_all_eddh_generate_compute(Config) ->
+ AllCurves = crypto:supports(curves),
+ Curves = [C || C <- [x25519, x448],
+ lists:member(C, AllCurves)],
+ do_dh_curves(Config, Curves).
+
+do_dh_curves(_Config, Curves) ->
+ ct:log("Lib: ~p~nFIPS: ~p~nCurves:~n~p~n", [crypto:info_lib(),
+ crypto:info_fips(),
+ Curves]),
+ Results =
+ [{Curve,
+ try
+ ct:log("~p",[Curve]),
+ {APub,APriv} = crypto:generate_key(ecdh, Curve),
+ {BPub,BPriv} = crypto:generate_key(ecdh, Curve),
+ true = is_binary(APub),
+ true = is_binary(APriv),
+ true = is_binary(BPub),
+ true = is_binary(BPriv),
+
+ ACommonSecret = crypto:compute_key(ecdh, BPub, APriv, Curve),
+ BCommonSecret = crypto:compute_key(ecdh, APub, BPriv, Curve),
+ ACommonSecret == BCommonSecret
+ catch
+ C:E ->
+ {C,E}
+ end}
+ || Curve <- Curves
+ ],
+
+ Fails =
+ lists:filter(fun({_,true}) -> false;
+ (_) -> true
+ end, Results),
+
+ case Fails of
+ [] ->
+ ct:comment("All ~p passed",[length(Results)]),
+ ok;
+ _ ->
+ ct:comment("passed: ~p, failed: ~p",[length(Results),length(Fails)]),
+ ct:log("Fails:~n~p",[Fails]),
+ ct:fail("Bad curve(s)",[])
+ end.
+
+%%--------------------------------------------------------------------
generate() ->
[{doc, " Test crypto:generate_key"}].
generate(Config) when is_list(Config) ->
@@ -986,13 +1340,14 @@ cipher_info(Config) when is_list(Config) ->
of
_ -> Ok
catch Cls:Exc ->
- ct:pal("~p:~p ~p",[Cls,Exc,C]),
+ ct:log("~p:~p ~p",[Cls,Exc,C]),
false
end
end,
true,
-crypto:supports(ciphers)) of
-%% proplists:get_value(ciphers, crypto:supports())) of
+ crypto:supports(ciphers)
+ )
+ of
true ->
ok;
false ->
@@ -1096,76 +1451,49 @@ hmac_increment(State0, [Increment | Rest]) ->
hmac_increment(State, Rest).
%%%----------------------------------------------------------------
-cmac_check({cmac, Type, Key, Text, CMac}) ->
+cmac_check({cmac, Type, Key, Text, CMac}=T) ->
ExpCMac = iolist_to_binary(CMac),
- case crypto:cmac(Type, Key, Text) of
- ExpCMac ->
- ok;
- Other ->
- ct:fail({{crypto, cmac, [Type, Key, Text]}, {expected, ExpCMac}, {got, Other}})
- end;
-cmac_check({cmac, Type, Key, Text, Size, CMac}) ->
+ cipher_test(T,
+ fun() -> crypto:cmac(Type, Key, Text) end,
+ ExpCMac);
+cmac_check({cmac, Type, Key, Text, Size, CMac}=T) ->
ExpCMac = iolist_to_binary(CMac),
- case crypto:cmac(Type, Key, Text, Size) of
- ExpCMac ->
- ok;
- Other ->
- ct:fail({{crypto, cmac, [Type, Key, Text, Size]}, {expected, ExpCMac}, {got, Other}})
- end.
-
+ cipher_test(T,
+ fun() -> crypto:cmac(Type, Key, Text, Size) end,
+ ExpCMac).
-mac_check({MacType, SubType, Key, Text, Mac}) ->
+mac_check({MacType, SubType, Key, Text, Mac}=T) ->
ExpMac = iolist_to_binary(Mac),
- case crypto:mac(MacType, SubType, Key, Text) of
- ExpMac ->
- ok;
- Other ->
- ct:fail({{crypto, mac, [MacType, SubType, Key, Text]}, {expected, ExpMac}, {got, Other}})
- end;
-mac_check({MacType, SubType, Key, Text, Size, Mac}) ->
+ cipher_test(T,
+ fun() -> crypto:mac(MacType, SubType, Key, Text) end,
+ ExpMac);
+mac_check({MacType, SubType, Key, Text, Size, Mac}=T) ->
ExpMac = iolist_to_binary(Mac),
- case crypto:mac(MacType, SubType, Key, Text, Size) of
- ExpMac ->
- ok;
- Other ->
- ct:fail({{crypto, mac, [MacType, SubType, Key, Text]}, {expected, ExpMac}, {got, Other}})
- end.
+ cipher_test(T,
+ fun() -> crypto:mac(MacType, SubType, Key, Text, Size) end,
+ ExpMac).
-
-block_cipher({Type, Key, PlainText}) ->
+block_cipher({Type, Key, PlainText}=T) ->
Plain = iolist_to_binary(PlainText),
CipherText = crypto:block_encrypt(Type, Key, PlainText),
- case crypto:block_decrypt(Type, Key, CipherText) of
- Plain ->
- ok;
- Other ->
- ct:fail({{crypto, block_decrypt, [Type, Key, CipherText]}, {expected, Plain}, {got, Other}})
- end;
+ cipher_test(T,
+ fun() -> crypto:block_decrypt(Type, Key, CipherText) end,
+ Plain);
-block_cipher({Type, Key, IV, PlainText}) ->
+block_cipher({Type, Key, IV, PlainText}=T) ->
Plain = iolist_to_binary(PlainText),
CipherText = crypto:block_encrypt(Type, Key, IV, PlainText),
- case crypto:block_decrypt(Type, Key, IV, CipherText) of
- Plain ->
- ok;
- Other ->
- ct:fail({{crypto, block_decrypt, [Type, Key, IV, CipherText]}, {expected, Plain}, {got, Other}})
- end;
+ cipher_test(T,
+ fun() -> crypto:block_decrypt(Type, Key, IV, CipherText) end,
+ Plain);
-block_cipher({Type, Key, IV, PlainText, CipherText}) ->
+block_cipher({Type, Key, IV, PlainText, CipherText}=T) ->
Plain = iolist_to_binary(PlainText),
- case crypto:block_encrypt(Type, Key, IV, Plain) of
- CipherText ->
- ok;
- Other0 ->
- ct:fail({{crypto, block_encrypt, [Type, Key, IV, Plain]}, {expected, CipherText}, {got, Other0}})
- end,
- case crypto:block_decrypt(Type, Key, IV, CipherText) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto, block_decrypt, [Type, Key, IV, CipherText]}, {expected, Plain}, {got, Other1}})
- end.
+ cipher_test(T,
+ fun() -> crypto:block_encrypt(Type, Key, IV, Plain) end,
+ CipherText,
+ fun() -> crypto:block_decrypt(Type, Key, IV, CipherText) end,
+ Plain).
block_cipher_increment({Type, Key, IV, PlainTexts}) when Type == des_cbc ;
Type == des3_cbc ;
@@ -1286,124 +1614,99 @@ stream_cipher_incment_loop(State0, OrigState, [PlainText | PlainTexts], Acc, Pla
{State, CipherText} = crypto:stream_encrypt(State0, PlainText),
stream_cipher_incment_loop(State, OrigState, PlainTexts, [CipherText | Acc], Plain).
-aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, Info}) ->
+aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, _Info}=T) ->
Plain = iolist_to_binary(PlainText),
- case crypto:block_encrypt(Type, Key, IV, {AAD, Plain}) of
- {CipherText, CipherTag} ->
- ok;
- Other0 ->
- ct:fail({{crypto,
- block_encrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]},
- {expected, {CipherText, CipherTag}},
- {got, Other0}})
- end,
- case crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, CipherTag}) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto,
- block_decrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]},
- {expected, Plain},
- {got, Other1}})
- end;
-aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}) ->
+ cipher_test(T,
+ fun() -> crypto:block_encrypt(Type, Key, IV, {AAD, Plain}) end,
+ {CipherText, CipherTag},
+ fun() -> crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, CipherTag}) end,
+ Plain);
+aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, _Info}=T) ->
<<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag,
Plain = iolist_to_binary(PlainText),
- try crypto:block_encrypt(Type, Key, IV, {AAD, Plain, TagLen}) of
- {CipherText, TruncatedCipherTag} ->
- ok;
- Other0 ->
- ct:fail({{crypto,
- block_encrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}, {taglen,TagLen}]},
- {expected, {CipherText, TruncatedCipherTag}},
- {got, Other0}})
- catch
- error:E ->
- ct:log("~p",[{Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}]),
- try crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true)
- of
- RR ->
- ct:log("Works: ~p",[RR])
- catch
- CC:EE ->
- ct:log("~p:~p", [CC,EE])
- end,
- ct:fail("~p",[E])
- end,
- case crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, TruncatedCipherTag}) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto,
- block_decrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag},
- {truncated,TruncatedCipherTag}]},
- {expected, Plain},
- {got, Other1}})
- end.
+ cipher_test(T,
+ fun() -> crypto:block_encrypt(Type, Key, IV, {AAD, Plain, TagLen}) end,
+ {CipherText, TruncatedCipherTag},
+ fun() -> crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, TruncatedCipherTag}) end,
+ Plain).
-aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, Info}) ->
+aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, _Info}=T) ->
Plain = iolist_to_binary(PlainText),
- case crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, true) of
- {CipherText, CipherTag} ->
- ok;
- Other0 ->
- ct:fail({{crypto,
- block_encrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]},
- {expected, {CipherText, CipherTag}},
- {got, Other0}})
- end,
- case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, CipherTag, false) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto,
- block_decrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]},
- {expected, Plain},
- {got, Other1}})
- end;
-aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}) ->
+ cipher_test(T,
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, true) end,
+ {CipherText, CipherTag},
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, CipherTag, false) end,
+ Plain);
+aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, _Info}=T) ->
<<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag,
Plain = iolist_to_binary(PlainText),
- try crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true) of
- {CipherText, TruncatedCipherTag} ->
- ok;
- Other0 ->
- ct:fail({{crypto,
- block_encrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}, {taglen,TagLen}]},
- {expected, {CipherText, TruncatedCipherTag}},
- {got, Other0}})
+ cipher_test(T,
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true) end,
+ {CipherText, TruncatedCipherTag},
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, TruncatedCipherTag, false) end,
+ Plain).
+
+aead_cipher_bad_tag({Type, Key, _PlainText, IV, AAD, CipherText, CipherTag, _Info}=T) ->
+ BadTag = mk_bad_tag(CipherTag),
+ cipher_test(T,
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTag, false) end,
+ error);
+aead_cipher_bad_tag({Type, Key, _PlainText, IV, AAD, CipherText, CipherTag, TagLen, _Info}=T) ->
+ <<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag,
+ BadTruncatedTag = mk_bad_tag(TruncatedCipherTag),
+ cipher_test(T,
+ fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTruncatedTag, false) end,
+ error).
+
+
+cipher_test(T, Fe, Ee, Fd, Ed) ->
+ %% Test encrypt
+ Re = cipher_test(encrypt, T, Fe, Ee),
+ %% Test decrypt
+ Rd = cipher_test(decrypt, T, Fd, Ed),
+ case {Re, Rd} of
+ {ok,ok} -> ok;
+ {ok,_} -> Rd;
+ {_,ok} -> Re;
+ _ -> {Re,Rd}
+ end.
+
+cipher_test(T, F, E) ->
+ cipher_test(notag, T, F, E).
+
+cipher_test(Tag, T, F, E) ->
+ try F() of
+ E -> ok;
+ Other -> {other, {Tag,T,Other}}
catch
- error:E ->
- ct:log("~p",[{Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}]),
- try crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true)
- of
- RR ->
- ct:log("Works: ~p",[RR])
- catch
- CC:EE ->
- ct:log("~p:~p", [CC,EE])
- end,
- ct:fail("~p",[E])
- end,
- case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, TruncatedCipherTag, false) of
- Plain ->
- ok;
- Other1 ->
- ct:fail({{crypto,
- block_decrypt,
- [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag},
- {truncated,TruncatedCipherTag}]},
- {expected, Plain},
- {got, Other1}})
+ error:Error -> {error, {Tag,T,Error}}
+ end.
+
+do_cipher_tests(F, TestVectors) when is_function(F,1) ->
+ {Passed,Failed} =
+ lists:partition(
+ fun(R) -> R == ok end,
+ lists:map(F, TestVectors)
+ ),
+ BothFailed = lists:filter(fun({ok,_}) -> false;
+ ({_,ok}) -> false;
+ (ok) -> false;
+ (_) -> true
+ end,
+ Failed),
+ ct:log("Passed: ~p, BothFailed: ~p OnlyOneFailed: ~p",
+ [length(Passed), length(BothFailed), length(Failed)-length(BothFailed)]),
+ case Failed of
+ [] ->
+ ct:comment("All ~p passed", [length(Passed)]);
+ _ ->
+ ct:log("~p",[hd(Failed)]),
+ ct:comment("Passed: ~p, BothFailed: ~p OnlyOneFailed: ~p",
+ [length(Passed), length(BothFailed), length(Failed)-length(BothFailed)]),
+ ct:fail("Failed", [])
end.
+
mk_bad_tag(CipherTag) ->
case <<0:(size(CipherTag))/unit:8>> of
CipherTag -> % The correct tag may happen to be a suite of zeroes
@@ -1412,30 +1715,6 @@ mk_bad_tag(CipherTag) ->
X
end.
-aead_cipher_bad_tag({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, Info}) ->
- Plain = iolist_to_binary(PlainText),
- BadTag = mk_bad_tag(CipherTag),
- case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTag, false) of
- error ->
- ok;
- Plain ->
- ct:log("~p:~p~n info: ~p~n key: ~p~n pt: ~p~n iv: ~p~n aad: ~p~n ct: ~p~n tag: ~p~n bad tag: ~p~n",
- [?MODULE,?LINE,Info, Key, PlainText, IV, AAD, CipherText, CipherTag, BadTag]),
- ct:fail("Didn't fail on bad tag")
- end;
-aead_cipher_bad_tag({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}) ->
- Plain = iolist_to_binary(PlainText),
- <<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag,
- BadTruncatedTag = mk_bad_tag(TruncatedCipherTag),
- case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTruncatedTag, false) of
- error ->
- ok;
- Plain ->
- ct:log("~p:~p~n info: ~p~n key: ~p~n pt: ~p~n iv: ~p~n aad: ~p~n ct: ~p~n tag: ~p~n bad tag: ~p~n",
- [Info, Key, PlainText, IV, AAD, CipherText, TruncatedCipherTag, BadTruncatedTag]),
- ct:fail("Didn't fail on bad tag")
- end.
-
do_sign_verify({Type, undefined=Hash, Private, Public, Msg, Signature}) ->
case crypto:sign(eddsa, Hash, Msg, [Private,Type]) of
Signature ->
@@ -1519,45 +1798,65 @@ negative_verify(Type, Hash, Msg, Signature, Public, Options) ->
end.
do_public_encrypt({Type, Public, Private, Msg, Padding}) ->
+ ct:log("do_public_encrypt Type=~p, Padding=~p,~nPublic = ~p,~nPrivate = ~p,~nMsg = ~p.",
+ [Type, Padding, Public, Private, Msg]),
+ timer:sleep(100),
try
crypto:public_encrypt(Type, Msg, Public, Padding)
of
PublicEcn ->
+ ct:log("private_decrypt~nPublicEcn = ~p.", [PublicEcn]),
+ timer:sleep(100),
try
crypto:private_decrypt(Type, PublicEcn, Private, Padding)
of
Msg ->
+ ct:log("~p:~p ok", [?MODULE,?LINE]),
+ timer:sleep(100),
ok;
Other ->
+ ct:log("~p:~p Other = ~p", [?MODULE,?LINE,Other]),
+ timer:sleep(100),
ct:fail({{crypto, private_decrypt, [Type, PublicEcn, Private, Padding]}, {expected, Msg}, {got, Other}})
catch
CC:EE ->
+ ct:log("~p:~p EXC. ~p:~p", [?MODULE,?LINE,CC,EE]),
+ timer:sleep(100),
ct:fail({{crypto, private_decrypt, [Type, PublicEcn, Private, Padding]}, {expected, Msg}, {got, {CC,EE}}})
end
catch
CC:EE ->
+ ct:log("~p:~p EXC 2. ~p:~p", [?MODULE,?LINE,CC,EE]),
+ timer:sleep(100),
ct:fail({{crypto, public_encrypt, [Type, Msg, Public, Padding]}, {got, {CC,EE}}})
end.
do_private_encrypt({Type, Public, Private, Msg, Padding}) ->
+ ct:log("do_private_encrypt Type=~p, Padding=~p,~nPublic = ~p,~nPrivate = ~p,~nMsg = ~p.",
+ [Type, Padding, Public, Private, Msg]),
try
crypto:private_encrypt(Type, Msg, Private, Padding)
of
PrivEcn ->
try
+ ct:log("public_decrypt~nPrivEcn = ~p.", [PrivEcn]),
crypto:public_decrypt(Type, PrivEcn, Public, Padding)
of
Msg ->
+ ct:log("~p:~p ok", [?MODULE,?LINE]),
ok;
Other ->
+ ct:log("~p:~p Other = ~p", [?MODULE,?LINE,Other]),
ct:fail({{crypto, public_decrypt, [Type, PrivEcn, Public, Padding]}, {expected, Msg}, {got, Other}})
catch
CC:EE ->
+ ct:log("~p:~p EXC. ~p:~p", [?MODULE,?LINE,CC,EE]),
ct:fail({{crypto, public_decrypt, [Type, PrivEcn, Public, Padding]}, {expected, Msg}, {got, {CC,EE}}})
end
catch
CC:EE ->
+ ct:log("~p:~p EXC 2. ~p:~p", [?MODULE,?LINE,CC,EE]),
ct:fail({{crypto, private_encrypt, [Type, Msg, Private, Padding]}, {got, {CC,EE}}})
end.
@@ -1569,6 +1868,9 @@ do_generate_compute({srp = Type, UserPrivate, UserGenParams, UserComParams,
UserComParams),
SessionKey = crypto:compute_key(Type, UserPublic, {HostPublic, HostPrivate},
HostComParam);
+
+
+
do_generate_compute({dh, P, G}) ->
{UserPub, UserPriv} = crypto:generate_key(dh, [P, G]),
{HostPub, HostPriv} = crypto:generate_key(dh, [P, G]),
@@ -1576,6 +1878,7 @@ do_generate_compute({dh, P, G}) ->
SharedSecret = crypto:compute_key(dh, UserPub, HostPriv, [P, G]).
do_compute({ecdh = Type, Pub, Priv, Curve, SharedSecret}) ->
+ ct:log("~p ~p", [Type,Curve]),
Secret = crypto:compute_key(Type, Pub, Priv, Curve),
case Secret of
SharedSecret ->
@@ -1584,7 +1887,8 @@ do_compute({ecdh = Type, Pub, Priv, Curve, SharedSecret}) ->
ct:fail({{crypto, compute_key, [Type, Pub, Priv, Curve]}, {expected, SharedSecret}, {got, Other}})
end.
-do_generate({ecdh = Type, Curve, Priv, Pub}) ->
+do_generate({Type, Curve, Priv, Pub}) when Type == ecdh ; Type == eddsa ->
+ ct:log("~p ~p", [Type,Curve]),
case crypto:generate_key(Type, Curve, Priv) of
{Pub, _} ->
ok;
@@ -1592,6 +1896,7 @@ do_generate({ecdh = Type, Curve, Priv, Pub}) ->
ct:fail({{crypto, generate_key, [Type, Priv, Curve]}, {expected, Pub}, {got, Other}})
end;
do_generate({rsa = Type, Mod, Exp}) ->
+ ct:log("~p", [Type]),
case crypto:info_fips() of
enabled when Mod < 3072 ->
ct:log("SKIP do_generate ~p FIPS=~p, Mod=~p Exp=~p", [Type, enabled, Mod, Exp]),
@@ -1644,9 +1949,6 @@ mkint(C) when $A =< C, C =< $F ->
mkint(C) when $a =< C, C =< $f ->
C - $a + 10.
-bin2hexstr(B) when is_binary(B) ->
- io_lib:format("~.16b",[crypto:bytes_to_integer(B)]).
-
decstr2int(S) when is_binary(S) ->
list_to_integer(binary:bin_to_list(S));
decstr2int(S) ->
@@ -2011,7 +2313,10 @@ group_config(ecdsa = Type, Config) ->
[{sign_verify, SignVerify}, {pub_priv_encrypt, PubPrivEnc} | Config];
group_config(Type, Config) when Type == ed25519 ; Type == ed448 ->
TestVectors = eddsa(Type),
- [{sign_verify,TestVectors} | Config];
+ Generate = lists:map(fun({Curve, _Hash, Priv, Pub, _Msg, _Signature}) ->
+ {eddsa, Curve, Priv, Pub}
+ end, TestVectors),
+ [{sign_verify,TestVectors}, {generate, Generate} | Config];
group_config(srp, Config) ->
GenerateCompute = [srp3(), srp6(), srp6a(), srp6a_smaller_prime()],
[{generate_compute, GenerateCompute} | Config];
@@ -2019,15 +2324,17 @@ group_config(ecdh, Config) ->
Compute = ecdh(),
Generate = ecc(),
[{compute, Compute}, {generate, Generate} | Config];
+group_config(eddh, Config) ->
+ [{compute, []}, {generate, []} | Config];
group_config(dh, Config) ->
GenerateCompute = [dh()],
[{generate_compute, GenerateCompute} | Config];
group_config(poly1305, Config) ->
V = [%% {Key, Txt, Expect}
{%% RFC7539 2.5.2
- crypto_SUITE:hexstr2bin("85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b"),
+ hexstr2bin("85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b"),
<<"Cryptographic Forum Research Group">>,
- crypto_SUITE:hexstr2bin("a8061dc1305136c6c22b8baf0c0127a9")
+ hexstr2bin("a8061dc1305136c6c22b8baf0c0127a9")
}
],
[{poly1305,V} | Config];
@@ -2157,12 +2464,13 @@ gen_rsa_sign_verify_tests(Hashs, Msg, Public, Private, Opts) ->
gen_rsa_pub_priv_tests(Public, Private, Msg, OptsToTry) ->
- SupOpts = proplists:get_value(rsa_opts, crypto:supports(), []),
+ SupOpts = proplists:get_value(rsa_opts, crypto:supports(), []) --
+ [rsa_x931_padding],
lists:foldr(fun(Opt, Acc) ->
case rsa_opt_is_supported(Opt, SupOpts) of
true ->
[{rsa, Public, Private, Msg, Opt} | Acc];
- false ->
+ false ->
Acc
end
end, [], OptsToTry).
@@ -3508,7 +3816,7 @@ srp(ClientPrivate, Generator, Prime, Version, Verifier, ServerPublic, ServerPriv
eddsa(ed25519) ->
%% https://tools.ietf.org/html/rfc8032#section-7.1
- %% {ALGORITHM, (SHA)}, SECRET KEY, PUBLIC KEY, MESSAGE, SIGNATURE}
+ %% {ALGORITHM, (SHA), SECRET KEY, PUBLIC KEY, MESSAGE, SIGNATURE}
[
%% TEST 1
{ed25519, undefined,
@@ -3893,11 +4201,13 @@ eddsa(ed448) ->
ecdh() ->
%% http://csrc.nist.gov/groups/STM/cavp/
- Curves = crypto:ec_curves() ++
- [X || X <- proplists:get_value(curves, crypto:supports(), []),
- lists:member(X, [x25519,x448])],
- TestCases =
- [{ecdh, hexstr2point("42ea6dd9969dd2a61fea1aac7f8e98edcc896c6e55857cc0", "dfbe5d7c61fac88b11811bde328e8a0d12bf01a9d204b523"),
+ Curves = crypto:supports(curves),
+ lists:filter(
+ fun ({_Type, _Pub, _Priv, Curve, _SharedSecret}) ->
+ lists:member(Curve, Curves)
+ end,
+
+ [{ecdh, hexstr2point("42ea6dd9969dd2a61fea1aac7f8e98edcc896c6e55857cc0", "dfbe5d7c61fac88b11811bde328e8a0d12bf01a9d204b523"),
hexstr2bin("f17d3fea367b74d340851ca4270dcb24c271f445bed9d527"),
secp192r1,
hexstr2bin("803d8ab2e5b6e6fca715737c3a82f7ce3c783124f6d51cd0")},
@@ -4003,11 +4313,8 @@ ecdh() ->
16#9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b,
x448,
hexstr2bin("07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d")}
- ],
- lists:filter(fun ({_Type, _Pub, _Priv, Curve, _SharedSecret}) ->
- lists:member(Curve, Curves)
- end,
- TestCases).
+ ]
+ ).
dh() ->
{dh, 90970053988169282502023478715631717259407236400413906591937635666709823903223997309250405131675572047545403771567755831138144089197560332757755059848492919215391041119286178688014693040542889497092308638580104031455627238700168892909539193174537248629499995652186913900511641708112112482297874449292467498403, 2}.
@@ -4061,8 +4368,11 @@ ecc() ->
%% information about the curves see
%% http://csrc.nist.gov/encryption/dss/ecdsa/NISTReCur.pdf
%%
- Curves = crypto:ec_curves(),
- TestCases =
+ Curves = crypto:supports(curves),
+ lists:filter(
+ fun ({_Type, Curve, _Priv, _Pub}) ->
+ lists:member(Curve, Curves)
+ end,
[{ecdh,secp192r1,1,
hexstr2point("188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012",
"07192B95FFC8DA78631011ED6B24CDD573F977A11E794811")},
@@ -4074,24 +4384,27 @@ ecc() ->
"782C37E372BA4520AA62E0FED121D49EF3B543660CFD05FD")},
{ecdh,secp192r1,4,
hexstr2point("35433907297CC378B0015703374729D7A4FE46647084E4BA",
- "A2649984F2135C301EA3ACB0776CD4F125389B311DB3BE32")}],
- lists:filter(fun ({_Type, Curve, _Priv, _Pub}) ->
- lists:member(Curve, Curves)
- end,
- TestCases).
-
-int_to_bin(X) when X < 0 -> int_to_bin_neg(X, []);
-int_to_bin(X) -> int_to_bin_pos(X, []).
-
-int_to_bin_pos(0,Ds=[_|_]) ->
- list_to_binary(Ds);
-int_to_bin_pos(X,Ds) ->
- int_to_bin_pos(X bsr 8, [(X band 255)|Ds]).
-
-int_to_bin_neg(-1, Ds=[MSB|_]) when MSB >= 16#80 ->
- list_to_binary(Ds);
-int_to_bin_neg(X,Ds) ->
- int_to_bin_neg(X bsr 8, [(X band 255)|Ds]).
+ "A2649984F2135C301EA3ACB0776CD4F125389B311DB3BE32")},
+ %% RFC 7748, 6.2
+ {ecdh, x448,
+ hexstr2bin("9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d"
+ "d9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b"),
+ hexstr2bin("9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c"
+ "22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0")},
+ {ecdh, x448,
+ hexstr2bin("1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d"
+ "6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d"),
+ hexstr2bin("3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b430"
+ "27d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609")},
+ %% RFC 7748, 6.1
+ {ecdh, x25519,
+ hexstr2bin("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"),
+ hexstr2bin("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")},
+ {ecdh, x25519,
+ hexstr2bin("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"),
+ hexstr2bin("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")}
+ ]
+ ).
datadir(Config) ->
proplists:get_value(data_dir, Config).
diff --git a/lib/crypto/test/crypto_bench_SUITE.erl b/lib/crypto/test/crypto_bench_SUITE.erl
index fb6434b72f..8aa9789598 100644
--- a/lib/crypto/test/crypto_bench_SUITE.erl
+++ b/lib/crypto/test/crypto_bench_SUITE.erl
@@ -21,8 +21,24 @@
%%
-module(crypto_bench_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ block/1,
+ stream/1,
+ chacha/1
+ ]).
+
+-compile([{nowarn_deprecated_function,
+ [{crypto,block_encrypt,4},
+ {crypto,stream_encrypt,2},
+ {crypto,stream_init,3}]}]).
+
-include_lib("common_test/include/ct_event.hrl").
-include_lib("common_test/include/ct.hrl").
@@ -186,14 +202,6 @@ run(Crypto, KeySize, BlockSize, MilliSecGoal) ->
fmt(X) -> X.
-find_value(KeyPath, PropList, Default) ->
- try find_value(KeyPath, PropList)
- of
- undefined -> Default
- catch
- error:function_clause -> Default
- end.
-
find_value(KeyPath, PropList) ->
lists:foldl(fun(K, L) when is_list(L) -> proplists:get_value(K,L);
(_, _) -> undefined
diff --git a/lib/crypto/test/crypto_property_test_SUITE.erl b/lib/crypto/test/crypto_property_test_SUITE.erl
index 2ea56934fd..1c786c986a 100644
--- a/lib/crypto/test/crypto_property_test_SUITE.erl
+++ b/lib/crypto/test/crypto_property_test_SUITE.erl
@@ -20,7 +20,14 @@
-module(crypto_property_test_SUITE).
--compile(export_all).
+-export([
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ encrypt_decrypt_one_time/1,
+ init_update/1,
+ init_update_multi/1
+ ]).
-include_lib("common_test/include/ct.hrl").
@@ -35,6 +42,7 @@ init_per_suite(Config) ->
try crypto:start() of
ok -> true;
{error, already_started} -> true;
+ {error,{already_started,crypto}} -> true;
_ -> false
catch
_:_ -> false
@@ -61,7 +69,7 @@ encrypt_decrypt_one_time(Config) ->
init_update(Config) ->
ct_property_test:quickcheck(
- crypto_ng_api:prop__crypto_init_update(),
+ crypto_ng_api:prop__crypto_init_update_final(),
Config
).
diff --git a/lib/crypto/test/engine_SUITE.erl b/lib/crypto/test/engine_SUITE.erl
index 71bbff6e85..de902ca3f0 100644
--- a/lib/crypto/test/engine_SUITE.erl
+++ b/lib/crypto/test/engine_SUITE.erl
@@ -23,8 +23,63 @@
-include_lib("common_test/include/ct.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ get_all_possible_methods/0,
+ get_all_possible_methods/1,
+ engine_load_all_methods/0,
+ engine_load_all_methods/1,
+ engine_load_some_methods/0,
+ engine_load_some_methods/1,
+ multiple_engine_load/0,
+ multiple_engine_load/1,
+ engine_list/0,
+ engine_list/1,
+ get_id_and_name/0,
+ get_id_and_name/1,
+ engine_by_id/0,
+ engine_by_id/1,
+ bad_arguments/0,
+ bad_arguments/1,
+ unknown_engine/0,
+ unknown_engine/1,
+ pre_command_fail_bad_value/0,
+ pre_command_fail_bad_value/1,
+ pre_command_fail_bad_key/0,
+ pre_command_fail_bad_key/1,
+ failed_engine_init/0,
+ failed_engine_init/1,
+ ctrl_cmd_string/0,
+ ctrl_cmd_string/1,
+ ctrl_cmd_string_optional/0,
+ ctrl_cmd_string_optional/1,
+ ensure_load/0,
+ ensure_load/1,
+ sign_verify_rsa/1,
+ sign_verify_rsa_fake/1,
+ sign_verify_dsa/1,
+ sign_verify_ecdsa/1,
+ sign_verify_rsa_pwd/1,
+ sign_verify_rsa_pwd_bad_pwd/1,
+ priv_encrypt_pub_decrypt_rsa/1,
+ priv_encrypt_pub_decrypt_rsa_pwd/1,
+ pub_encrypt_priv_decrypt_rsa/1,
+ pub_encrypt_priv_decrypt_rsa_pwd/1,
+ get_pub_from_priv_key_rsa/1,
+ get_pub_from_priv_key_rsa_pwd/1,
+ get_pub_from_priv_key_rsa_pwd_no_pwd/1,
+ get_pub_from_priv_key_rsa_pwd_bad_pwd/1,
+ get_pub_from_priv_key_dsa/1,
+ get_pub_from_priv_key_ecdsa/1
+ ]).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -879,9 +934,6 @@ pkey_supported(Type) ->
lists:member(Type, proplists:get_value(public_keys, crypto:supports(), [])).
-load_storage_engine(Config) ->
- load_storage_engine(Config, []).
-
load_storage_engine(_Config, ExcludeMthds) ->
case crypto:get_test_engine() of
{ok, Engine} ->
diff --git a/lib/crypto/test/property_test/crypto_ng_api.erl b/lib/crypto/test/property_test/crypto_ng_api.erl
index e88cf29b3e..b3decf1cbe 100644
--- a/lib/crypto/test/property_test/crypto_ng_api.erl
+++ b/lib/crypto/test/property_test/crypto_ng_api.erl
@@ -32,27 +32,28 @@
prop__crypto_one_time() ->
numtests(10000,
- ?FORALL({TextPlain, Cipher, Key, IV}, ?LET(Ciph,cipher(),
- {text_plain(), Ciph, key(Ciph), iv(Ciph)}),
+ ?FORALL({TextPlain, Cipher, Key, IV, Padding}, ?LET(Ciph,cipher(),
+ {text_plain(), Ciph, key(Ciph), iv(Ciph), padding()}),
begin
R = equal(TextPlain,
- full_blocks(TextPlain, Cipher),
- decrypt_encrypt_one_time(Cipher, Key, IV, TextPlain)),
+ full_blocks(TextPlain, Cipher, Padding),
+ decrypt_encrypt_one_time(Cipher, Key, IV, TextPlain, Padding)),
prt_inf(Cipher, TextPlain, R)
end
)
).
-prop__crypto_init_update() ->
+prop__crypto_init_update_final() ->
numtests(10000,
- ?FORALL({TextPlain, Cipher, Key, IV}, ?LET(Ciph,cipher(),
- {text_plain(), Ciph, key(Ciph), iv(Ciph)}),
+ ?FORALL({TextPlain, Cipher, Key, IV, Padding}, ?LET(Ciph,cipher(),
+ {text_plain(), Ciph, key(Ciph), iv(Ciph), padding()}),
begin
R = equal(TextPlain,
- full_blocks(TextPlain, Cipher),
- decrypt_encrypt_init_update(Cipher, Key, IV, TextPlain)),
+ full_blocks(TextPlain, Cipher, Padding),
+ decrypt_encrypt_init_update_final(Cipher, Key, IV, TextPlain, Padding)),
prt_inf(Cipher, TextPlain, R)
- end)
+ end
+ )
).
prt_inf(Cipher, TextPlain, R) ->
@@ -69,6 +70,7 @@ prt_inf(Cipher, TextPlain, R) ->
%%%================================================================
%%% Lib
+equal(_, _, correct_exception) -> true;
equal(_, T, T) -> true;
equal(F, Tp, Td) ->
ct:pal("Full: ~p~n"
@@ -78,42 +80,94 @@ equal(F, Tp, Td) ->
false.
-decrypt_encrypt_one_time(Cipher, Key, IV, TextPlain) ->
- io:format("~p:~p Cipher: ~p, BlockSize: ~p, Key: ~p, IV: ~p, TextPlain: ~p (~p chunks)",
+decrypt_encrypt_one_time(Cipher, Key, IV, TextPlain, Padding) ->
+ io:format("~p:~p Cipher: ~p, BlockSize: ~p, Key: ~p, IV: ~p, TextPlain: ~p (~p chunks), Padding: ~p",
[?MODULE,?LINE, Cipher, block_size(Cipher), size(Key), size(IV), size(iolist_to_binary(TextPlain)),
- num_chunks(TextPlain)]),
- TextCrypto = crypto:crypto_one_time(Cipher, Key, IV, TextPlain, true),
- io:format("~p:~p TextCrypto: ~p", [?MODULE,?LINE, size(TextCrypto)]),
- TextDecrypt = crypto:crypto_one_time(Cipher, Key, IV, TextCrypto, false),
- io:format("~p:~p TextDecrypt: ~p", [?MODULE,?LINE, size(TextDecrypt)]),
- TextDecrypt.
-
+ num_chunks(TextPlain), Padding]),
+ Sblock = block_size(Cipher),
+ ExcessBytesLastBlock = size(iolist_to_binary(TextPlain)) rem Sblock,
+ PadSize = if
+ (Padding == zero) ; (Padding == random) ->
+ (Sblock - ExcessBytesLastBlock) rem Sblock;
+ true ->
+ 0
+ end,
+ try
+ crypto:crypto_one_time(Cipher, Key, IV, TextPlain, [{encrypt,true},{padding,Padding}])
+ of
+ TextCrypto ->
+ io:format("~p:~p PadSize: ~p, TextCrypto: ~p",
+ [?MODULE,?LINE, PadSize, size(TextCrypto)]),
+ TextDecryptPadded =
+ crypto:crypto_one_time(Cipher, Key, IV, TextCrypto, [{encrypt,false},{padding,Padding}]),
+ io:format("~p:~p TextDecryptPadded: ~p",
+ [?MODULE,?LINE, size(TextDecryptPadded)]),
+ element(1, split_binary(TextDecryptPadded, size(TextDecryptPadded) - PadSize))
+ catch
+ error:{error,{"api_ng.c",Line},Msg} when ExcessBytesLastBlock>0,
+ Padding == none,
+ Msg == "Padding 'none' but unfilled last block" ->
+ io:format("~p:~p Correct exception: ~p",
+ [?MODULE,?LINE, {error,{"api_ng.c",Line},Msg}]),
+ correct_exception
+ end.
+
-decrypt_encrypt_init_update(Cipher, Key, IV, TextPlain) when is_binary(TextPlain) ->
- decrypt_encrypt_init_update(Cipher, Key, IV, [TextPlain]);
+decrypt_encrypt_init_update_final(Cipher, Key, IV, TextPlain, Padding) when is_binary(TextPlain) ->
+ decrypt_encrypt_init_update_final(Cipher, Key, IV, [TextPlain], Padding);
-decrypt_encrypt_init_update(Cipher, Key, IV, TextPlain) ->
- io:format("~p:~p Cipher: ~p, BlockSize: ~p, Key: ~p, IV: ~p, TextPlain: ~p (~p chunks)",
+decrypt_encrypt_init_update_final(Cipher, Key, IV, TextPlain, Padding) ->
+ io:format("~p:~p Cipher: ~p, BlockSize: ~p, Key: ~p, IV: ~p, TextPlain: ~p (~p chunks), Padding: ~p",
[?MODULE,?LINE, Cipher, block_size(Cipher), size(Key), size(IV), size(iolist_to_binary(TextPlain)),
- num_chunks(TextPlain)]),
- Cenc = crypto:crypto_init(Cipher, Key, IV, true),
+ num_chunks(TextPlain), Padding]),
+ Sblock = block_size(Cipher),
+ ExcessBytesLastBlock = size(iolist_to_binary(TextPlain)) rem Sblock,
+
+ Cenc = crypto:crypto_init(Cipher, Key, IV, [{encrypt,true},{padding,Padding}]),
TextOut = lists:foldl(fun(TextIn, TextOutAcc) ->
[crypto:crypto_update(Cenc,TextIn) | TextOutAcc]
end, [], TextPlain),
- TextCrypto = lists:reverse(TextOut),
- io:format("~p:~p TextCrypto: ~p",
- [?MODULE,?LINE, size(iolist_to_binary(TextCrypto))]),
+ try
+ Rf = crypto:crypto_final(Cenc),
+ Rps = maps:get(padding_size,crypto:crypto_get_data(Cenc)),
+ {Rps,Rf}
+ of
+ {PadSize0,LastTextOut} ->
+ TextCrypto = lists:reverse([LastTextOut|TextOut]),
+ io:format("~p:~p PadSize0: ~p, TextCrypto: ~p",
+ [?MODULE,?LINE, PadSize0, size(iolist_to_binary(TextCrypto))]),
+ PadSize = case Padding of
+ pkcs_padding -> 0;
+ none -> 0;
+ undefined -> 0;
+ _ -> PadSize0
+ end,
- Cdec = crypto:crypto_init(Cipher, Key, IV, false),
- TextDec = lists:foldl(fun(TextC, TextDecAcc) ->
- [crypto:crypto_update(Cdec,TextC) | TextDecAcc]
- end, [], TextCrypto),
- iolist_to_binary(lists:reverse(TextDec)).
-
-full_blocks(TextPlain, Cipher) ->
+ Cdec = crypto:crypto_init(Cipher, Key, IV, [{encrypt,false},{padding,Padding}]),
+ TextDec = lists:foldl(fun(TextC, TextDecAcc) ->
+ [crypto:crypto_update(Cdec,TextC) | TextDecAcc]
+ end, [], TextCrypto),
+ LastDecOut = crypto:crypto_final(Cdec),
+ TextDecryptPadded = iolist_to_binary(lists:reverse([LastDecOut|TextDec])),
+ io:format("~p:~p TextDecryptPadded: ~p",
+ [?MODULE,?LINE, size(TextDecryptPadded)]),
+ element(1, split_binary(TextDecryptPadded, size(TextDecryptPadded) - PadSize))
+ catch
+ error:{error,{"api_ng.c",Line},Msg} when ExcessBytesLastBlock>0,
+ Padding == none ->
+ io:format("~p:~p Correct exception: ~p",
+ [?MODULE,?LINE, {error,{"api_ng.c",Line},Msg}]),
+ correct_exception
+ end.
+
+
+full_blocks(TextPlain, Cipher, Pad) when Pad == undefined ; Pad == none ->
TextPlainBin = iolist_to_binary(TextPlain),
{Head,_Tail} = split_binary(TextPlainBin, (size(TextPlainBin) - num_rest_bytes(TextPlainBin,Cipher))),
- Head.
+ Head;
+full_blocks(TextPlain, _Cipher, _) ->
+ iolist_to_binary(TextPlain).
+
num_chunks(B) when is_binary(B) -> 1;
num_chunks(L) when is_list(L) -> length(L).
diff --git a/lib/crypto/test/property_test/crypto_prop_generators.erl b/lib/crypto/test/property_test/crypto_prop_generators.erl
index df36ae1dde..f8964413c4 100644
--- a/lib/crypto/test/property_test/crypto_prop_generators.erl
+++ b/lib/crypto/test/property_test/crypto_prop_generators.erl
@@ -27,6 +27,7 @@
iv/1,
iolist/0,
mybinary/1,
+ padding/0,
non_aead_ciphers/0,
block_size/1,
key_length/1,
@@ -58,6 +59,9 @@ iolist() -> frequency([{5, list( oneof([list(byte()),
mybinary(MaxSize) -> ?LET(Sz, integer(0,MaxSize), binary(Sz)).
+padding() -> oneof([pkcs_padding, none,
+ zero, random,
+ undefined]).
%%%================================================================
non_aead_ciphers() ->
diff --git a/lib/crypto/test/property_test/crypto_prop_generators.hrl b/lib/crypto/test/property_test/crypto_prop_generators.hrl
index 1072411091..2c12002771 100644
--- a/lib/crypto/test/property_test/crypto_prop_generators.hrl
+++ b/lib/crypto/test/property_test/crypto_prop_generators.hrl
@@ -28,6 +28,7 @@
iv/1,
iolist/0,
mybinary/1,
+ padding/0,
non_aead_ciphers/0,
block_size/1,
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 72f3b9b792..1aaf84ba37 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 4.6.5
+CRYPTO_VSN = 4.9
diff --git a/lib/debugger/Makefile b/lib/debugger/Makefile
index 8c8b617831..3a06d72c5d 100644
--- a/lib/debugger/Makefile
+++ b/lib/debugger/Makefile
@@ -34,3 +34,7 @@ SPECIAL_TARGETS =
# Default Subdir Targets
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+
+DIA_PLT_APPS=wx
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/debugger/doc/src/Makefile b/lib/debugger/doc/src/Makefile
index 49b5a4be57..b02d35b802 100644
--- a/lib/debugger/doc/src/Makefile
+++ b/lib/debugger/doc/src/Makefile
@@ -30,11 +30,6 @@ VSN=$(DEBUGGER_VSN)
APPLICATION=debugger
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -49,74 +44,13 @@ XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) $(XML_PART_FILES) \
$(XML_REF3_FILES) $(XML_APPLICATION_FILES)
-GIF_FILES = \
- images/attach.jpg \
- images/cond_break_dialog.jpg \
- images/function_break_dialog.jpg \
- images/interpret.jpg \
- images/line_break_dialog.jpg \
- images/monitor.jpg \
- images/view.jpg
-
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.jpg: %.jpg
- $(INSTALL_DIR) $(HTMLDIR)/images
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-debug opt:
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR_DATA) $(HTMLDIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
+IMAGE_FILES = \
+ attach.jpg \
+ cond_break_dialog.jpg \
+ function_break_dialog.jpg \
+ interpret.jpg \
+ line_break_dialog.jpg \
+ monitor.jpg \
+ view.jpg
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/debugger/doc/src/images/attach.jpg b/lib/debugger/doc/src/attach.jpg
index 95f227d21b..95f227d21b 100644
--- a/lib/debugger/doc/src/images/attach.jpg
+++ b/lib/debugger/doc/src/attach.jpg
Binary files differ
diff --git a/lib/debugger/doc/src/images/cond_break_dialog.jpg b/lib/debugger/doc/src/cond_break_dialog.jpg
index 40bcb299a9..40bcb299a9 100644
--- a/lib/debugger/doc/src/images/cond_break_dialog.jpg
+++ b/lib/debugger/doc/src/cond_break_dialog.jpg
Binary files differ
diff --git a/lib/debugger/doc/src/debugger_chapter.xml b/lib/debugger/doc/src/debugger_chapter.xml
index 3c37d4b924..a0c67b3257 100644
--- a/lib/debugger/doc/src/debugger_chapter.xml
+++ b/lib/debugger/doc/src/debugger_chapter.xml
@@ -38,7 +38,7 @@
<p><em>Step 1.</em> Start Debugger by calling
<c>debugger:start()</c>.</p>
- <p>The <seealso marker="#monitor">Monitor window</seealso> is
+ <p>The <seeguide marker="#monitor">Monitor window</seeguide> is
displayed with information about all debugged processes,
interpreted modules, and selected options. Initially there are
normally no debugged processes. First, it must be specified which
@@ -48,7 +48,7 @@
<p><em>Step 2.</em> Select <em>Module > Interpret...</em> in the
Monitor window.</p>
- <p>The <seealso marker="#interpret">Interpret Modules window</seealso>
+ <p>The <seeguide marker="#interpret">Interpret Modules window</seeguide>
is displayed.</p>
<p><em>Step 3.</em> Select the appropriate modules from the Interpret
@@ -64,10 +64,10 @@
<em>Module > the module to be interpreted > View</em>.</p>
<p>The contents of the source file is displayed in the
- <seealso marker="#view">View Module window</seealso>.</p>
+ <seeguide marker="#view">View Module window</seeguide>.</p>
<p><em>Step 5.</em> Set the
- <seealso marker="#breakpoints">breakpoints</seealso>, if any.</p>
+ <seeguide marker="#breakpoints">breakpoints</seeguide>, if any.</p>
<p><em>Step 6.</em> Start the program to be debugged. This is done
the normal way from the Erlang shell.</p>
@@ -78,7 +78,7 @@
<p><em>Step 7.</em> To <em>attach</em> to one of these processes,
double-click it, or select the process and then choose
<em>Process > Attach</em>. Attaching to a process opens an
- <seealso marker="#attach">Attach Process window</seealso> for this
+ <seeguide marker="#attach">Attach Process window</seeguide> for this
process.</p>
<p><em>Step 8.</em> From the Attach Process window, you can control
@@ -153,7 +153,7 @@
<p>A line breakpoint is created at a certain line in a module.</p>
- <image file="images/line_break_dialog.jpg">
+ <image file="line_break_dialog.jpg">
<icaption>Line Break Dialog Window</icaption>
</image>
@@ -182,10 +182,10 @@
<p><c>Bindings</c> is a list of variable bindings. To retrieve the
value of <c>Variable</c> (given as an atom), use function
- <seealso marker="int#get_binding/2"><c>int:get_binding(Variable,Bindings)</c></seealso>.
+ <seemfa marker="int#get_binding/2"><c>int:get_binding(Variable,Bindings)</c></seemfa>.
The function returns <c>unbound</c> or <c>{value,Value}</c>.</p>
- <image file="images/cond_break_dialog.jpg">
+ <image file="cond_break_dialog.jpg">
<icaption>Conditional Break Dialog Window</icaption>
</image>
@@ -225,7 +225,7 @@ c_break(Bindings) ->
<p>A function breakpoint is a set of line breakpoints, one at
the first line of each clause in the specified function.</p>
- <image file="images/function_break_dialog.jpg">
+ <image file="function_break_dialog.jpg">
<icaption>Function Break Dialog Window</icaption>
</image>
@@ -255,7 +255,7 @@ c_break(Bindings) ->
{shell,eval_loop,3,[{file,"shell.erl"},{line,614}]}]}}</pre>
<p>For details about the stack trace, see section
- <seealso marker="doc/reference_manual:errors">Errors and Error Handling</seealso>
+ <seeguide marker="system/reference_manual:errors">Errors and Error Handling</seeguide>
in the Erlang Reference Manual.</p>
<p>Debugger emulates the stack trace by keeping track of recently
@@ -265,7 +265,7 @@ c_break(Bindings) ->
<p>This information can be used to traverse the chain of function
calls, using the <em>Up</em> and <em>Down</em> buttons in the
- <seealso marker="#attach">Attach Process window</seealso>.</p>
+ <seeguide marker="#attach">Attach Process window</seeguide>.</p>
<p>By default, Debugger only saves information about recursive
function calls, that is, function calls that have not yet returned
@@ -286,7 +286,7 @@ c_break(Bindings) ->
</note>
<p>For information about how to change the stack trace option, see
- section <seealso marker="#monitor">Monitor Window</seealso>.</p>
+ section <seeguide marker="#monitor">Monitor Window</seeguide>.</p>
</section>
<section>
@@ -307,14 +307,14 @@ c_break(Bindings) ->
modules</p></item>
</list>
- <image file="images/monitor.jpg">
+ <image file="monitor.jpg">
<icaption>Monitor Window</icaption>
</image>
<p>The <em>Auto Attach</em> boxes, <em>Stack Trace</em> label,
<em>Back Trace Size</em> label, and <em>Strings</em> box display
some options set. For details about these options, see section
- <seealso marker="#options">Options Menu</seealso>.</p>
+ <seeguide marker="#options">Options Menu</seeguide>.</p>
<section>
<title>Process Grid</title>
@@ -410,7 +410,7 @@ c_break(Bindings) ->
<taglist>
<tag><em>Interpret...</em></tag>
<item><p>Opens the
- <seealso marker="#interpret">Interpret Modules window</seealso>,
+ <seeguide marker="#interpret">Interpret Modules window</seeguide>,
where new modules to be interpreted can be specified.</p></item>
<tag><em>Delete All</em></tag>
@@ -427,7 +427,7 @@ c_break(Bindings) ->
<tag><em>View</em></tag>
<item><p>Opens a
- <seealso marker="#view">View Module window</seealso>, displaying the
+ <seeguide marker="#view">View Module window</seeguide>, displaying the
contents of the selected module.</p></item>
</taglist>
</section>
@@ -437,7 +437,7 @@ c_break(Bindings) ->
<p>The following menu items apply to the currently selected
process, provided it is stopped at a breakpoint (for details, see
section
- <seealso marker="#attach">Attach Process window</seealso>):</p>
+ <seeguide marker="#attach">Attach Process window</seeguide>):</p>
<taglist>
<tag><em>Step</em></tag><item></item>
<tag><em>Next</em></tag><item></item>
@@ -450,7 +450,7 @@ c_break(Bindings) ->
<taglist>
<tag><em>Attach</em></tag>
<item><p>Attaches to the process and open an
- <seealso marker="#attach">Attach Process window</seealso>.</p></item>
+ <seeguide marker="#attach">Attach Process window</seeguide>.</p></item>
<tag><em>Kill</em></tag>
<item><p>Terminates the process using <c>exit(Pid,kill)</c>.</p></item>
@@ -461,7 +461,7 @@ c_break(Bindings) ->
<title>Break Menu</title>
<p>The items in this menu are used to create and delete breakpoints.
For details, see section
- <seealso marker="#breakpoints">Breakpoints</seealso>.</p>
+ <seeguide marker="#breakpoints">Breakpoints</seeguide>.</p>
<taglist>
<tag><em>Line Break...</em></tag>
@@ -495,7 +495,7 @@ c_break(Bindings) ->
<taglist>
<tag><em>Trace Window</em></tag>
<item><p>Sets the areas to be visible in an
- <seealso marker="#attach">Attach Process window</seealso>.
+ <seeguide marker="#attach">Attach Process window</seeguide>.
Does not affect existing Attach Process windows.</p>
</item>
@@ -513,7 +513,7 @@ c_break(Bindings) ->
<tag><em>Stack Trace</em></tag>
<item><p>Sets the stack trace option, see section
- <seealso marker="#stack_trace">Stack Trace</seealso>. Does not
+ <seeguide marker="#stack_trace">Stack Trace</seeguide>. Does not
affect existing debugged processes.</p>
<list type="bulleted">
<item><p><em>Stack On, Tail</em> - Saves information about all
@@ -532,7 +532,7 @@ c_break(Bindings) ->
<list type="bulleted">
<item><p><em>Use range of +pc flag</em> - Uses the printable
character range set by the <c>erl(1)</c> flag
- <seealso marker="erts:erl#printable_character_range"><c>+pc</c></seealso>.</p>
+ <seecom marker="erts:erl#printable_character_range"><c>+pc</c></seecom>.</p>
</item>
</list>
</item>
@@ -593,7 +593,7 @@ c_break(Bindings) ->
<pre>
4> c(module, debug_info).</pre>
- <image file="images/interpret.jpg">
+ <image file="interpret.jpg">
<icaption>Interpret Modules Window</icaption>
</image>
@@ -609,7 +609,7 @@ c_break(Bindings) ->
<note>
<p>When Debugger is started in global mode (which is the default, see
- <seealso marker="debugger#start/0">debugger:start/0</seealso>),
+ <seemfa marker="debugger#start/0">debugger:start/0</seemfa>),
modules added (or deleted) for interpretation are added (or
deleted) on all known Erlang nodes.</p>
</note>
@@ -624,7 +624,7 @@ c_break(Bindings) ->
been attached to. Notice that when attaching to a process, its
execution is automatically stopped.</p>
- <image file="images/attach.jpg">
+ <image file="attach.jpg">
<icaption>Attach Process Window</icaption>
</image>
@@ -785,13 +785,13 @@ c_break(Bindings) ->
other Attach Process windows.</p></item>
<tag><em>Stack Trace</em></tag>
- <item><p>Same as in the <seealso marker="#monitor">Monitor
- window</seealso>, but only affects the debugged
+ <item><p>Same as in the <seeguide marker="#monitor">Monitor
+ window</seeguide>, but only affects the debugged
process the window is attached to.</p></item>
<tag><em>Strings</em></tag>
- <item><p>Same as in the <seealso marker="#monitor">Monitor
- window</seealso>, but only affects the debugged
+ <item><p>Same as in the <seeguide marker="#monitor">Monitor
+ window</seeguide>, but only affects the debugged
process the window is attached to.</p></item>
<tag><em>Back Trace Size...</em></tag>
@@ -806,7 +806,7 @@ c_break(Bindings) ->
<p>The <em>Break</em>, <em>Windows</em>, and <em>Help</em> menus
are the same as in the
- <seealso marker="#monitor">Monitor Window</seealso>, except
+ <seeguide marker="#monitor">Monitor Window</seeguide>, except
that the <em>Breaks</em> menu applies only to local
breakpoints.</p>
</section>
@@ -819,7 +819,7 @@ c_break(Bindings) ->
<p>The View Module window displays the contents of an interpreted
module and makes it possible to set breakpoints.</p>
- <image file="images/view.jpg">
+ <image file="view.jpg">
<icaption>View Module Window</icaption>
</image>
@@ -837,7 +837,7 @@ c_break(Bindings) ->
<section>
<title>File and Edit Menus</title>
<p>The <em>File</em> and <em>Edit</em> menus are the same as in the
- <seealso marker="#attach">Attach Process Window</seealso>.</p>
+ <seeguide marker="#attach">Attach Process Window</seeguide>.</p>
</section>
<section>
@@ -845,7 +845,7 @@ c_break(Bindings) ->
<p>The <em>Break</em>, <em>Windows</em>, and <em>Help</em> menus
are the same as in the
- <seealso marker="#monitor">Monitor Window</seealso>, except
+ <seeguide marker="#monitor">Monitor Window</seeguide>, except
that the <em>Break</em> menu applies only to local breakpoints.</p>
</section>
</section>
@@ -881,7 +881,7 @@ c_break(Bindings) ->
<title>Debugging Remote Nodes</title>
<p>By using
- <seealso marker="debugger#start/1">debugger:start/1</seealso>,
+ <seemfa marker="debugger#start/1">debugger:start/1</seemfa>,
you can specify if Debugger is to be started in local or global
mode:</p>
<pre>
diff --git a/lib/debugger/doc/src/images/function_break_dialog.jpg b/lib/debugger/doc/src/function_break_dialog.jpg
index db56d5a096..db56d5a096 100644
--- a/lib/debugger/doc/src/images/function_break_dialog.jpg
+++ b/lib/debugger/doc/src/function_break_dialog.jpg
Binary files differ
diff --git a/lib/debugger/doc/src/i.xml b/lib/debugger/doc/src/i.xml
index 06b0eb876a..b74a7ab8b9 100644
--- a/lib/debugger/doc/src/i.xml
+++ b/lib/debugger/doc/src/i.xml
@@ -34,7 +34,7 @@
<p>The <c>i</c> module provides short forms for some of
the functions used by the graphical Debugger and some of
the functions in module
- <seealso marker="int"><c>int</c></seealso>, the Erlang interpreter.</p>
+ <seeerl marker="int"><c>int</c></seeerl>, the Erlang interpreter.</p>
<p>This module also provides facilities for displaying status
information about interpreted processes and break points.</p>
@@ -45,7 +45,7 @@
attached manually or automatically.</p>
<p>By preference, these functions can be included in module
- <seealso marker="stdlib:shell_default"><c>shell_default</c></seealso>.
+ <seeerl marker="stdlib:shell_default"><c>shell_default</c></seeerl>.
By default, they are included in that module.</p>
</description>
@@ -77,9 +77,9 @@
<desc>
<p>Interprets the specified module(s). <c>ii/1</c> interprets
the module(s) only at the current node, see
- <seealso marker="int#i/1">int:i/1</seealso>. <c>ini/1</c>
+ <seemfa marker="int#i/1">int:i/1</seemfa>. <c>ini/1</c>
interprets the module(s) at all known nodes, see
- <seealso marker="int#ni/1">int:ni/1</seealso>.</p>
+ <seemfa marker="int#ni/1">int:ni/1</seemfa>.</p>
</desc>
</func>
@@ -141,7 +141,7 @@
<desc>
<p>Sets when and how to attach to a debugged process
automatically, see
- <seealso marker="int#auto_attach/0">int:auto_attach/2</seealso>.
+ <seemfa marker="int#auto_attach/0">int:auto_attach/2</seemfa>.
<c>Function</c> defaults to the standard function used by
Debugger.</p>
</desc>
@@ -155,7 +155,7 @@
</type>
<desc>
<p>Sets how to save call frames in the stack, see
- <seealso marker="int#stack_trace/0">int:stack_trace/1</seealso>.</p>
+ <seemfa marker="int#stack_trace/0">int:stack_trace/1</seemfa>.</p>
</desc>
</func>
@@ -343,7 +343,7 @@
the current variable bindings. The function must return
<c>true</c> (break) or <c>false</c> (do not break).
To retrieve the value of a variable <c>Var</c>, use
- <seealso marker="int#get_binding/2">int:get_binding(Var, Bindings)</seealso>.</p>
+ <seemfa marker="int#get_binding/2">int:get_binding(Var, Bindings)</seemfa>.</p>
</desc>
</func>
@@ -387,7 +387,7 @@
<section>
<title>See Also</title>
- <p><seealso marker="int"><c>int(3)</c></seealso></p>
+ <p><seeerl marker="int"><c>int(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/debugger/doc/src/int.xml b/lib/debugger/doc/src/int.xml
index a0078714e6..d0706f2aa2 100644
--- a/lib/debugger/doc/src/int.xml
+++ b/lib/debugger/doc/src/int.xml
@@ -34,7 +34,7 @@
<p>The Erlang interpreter provides mechanisms for breakpoints and
stepwise execution of code. It is primarily intended to be used by
Debugger, see the User's Guide and
- <seealso marker="debugger"><c>debugger(3)</c></seealso>.</p>
+ <seeerl marker="debugger"><c>debugger(3)</c></seeerl>.</p>
<p>The following can be done from the shell:</p>
<list type="bulleted">
@@ -316,7 +316,7 @@ spawn(Module, Name, [Pid | Args])</pre>
tail recursive calls. This is the default.</p></item>
<tag><c>false</c></tag>
- <item><p>Save no information about currentcalls.</p></item>
+ <item><p>Save no information about current calls.</p></item>
</taglist>
</desc>
</func>
@@ -433,7 +433,7 @@ spawn(Module, Name, [Pid | Args])</pre>
<p>Sets the conditional test of the breakpoint at <c>Line</c> in
<c>Module</c> to <c>Function</c>. The function must
fulfill the requirements specified in section
- <seealso marker="#int_breakpoints">Breakpoints</seealso>.</p>
+ <seeerl marker="#int_breakpoints">Breakpoints</seeerl>.</p>
</desc>
</func>
diff --git a/lib/debugger/doc/src/images/interpret.jpg b/lib/debugger/doc/src/interpret.jpg
index 030c06fa23..030c06fa23 100644
--- a/lib/debugger/doc/src/images/interpret.jpg
+++ b/lib/debugger/doc/src/interpret.jpg
Binary files differ
diff --git a/lib/debugger/doc/src/introduction.xml b/lib/debugger/doc/src/introduction.xml
index 95a42cad09..66a86ff753 100644
--- a/lib/debugger/doc/src/introduction.xml
+++ b/lib/debugger/doc/src/introduction.xml
@@ -39,7 +39,7 @@
</p>
<p>The Erlang interpreter can also be accessed through the interface
- module <seealso marker="int"><c>int(3)</c></seealso>.
+ module <seeerl marker="int"><c>int(3)</c></seeerl>.
</p>
<warning>
diff --git a/lib/debugger/doc/src/images/line_break_dialog.jpg b/lib/debugger/doc/src/line_break_dialog.jpg
index 18ac6a9f81..18ac6a9f81 100644
--- a/lib/debugger/doc/src/images/line_break_dialog.jpg
+++ b/lib/debugger/doc/src/line_break_dialog.jpg
Binary files differ
diff --git a/lib/debugger/doc/src/images/monitor.jpg b/lib/debugger/doc/src/monitor.jpg
index 32f210cbf2..32f210cbf2 100644
--- a/lib/debugger/doc/src/images/monitor.jpg
+++ b/lib/debugger/doc/src/monitor.jpg
Binary files differ
diff --git a/lib/debugger/doc/src/notes.xml b/lib/debugger/doc/src/notes.xml
index 64af47a4fb..5e3f2ce878 100644
--- a/lib/debugger/doc/src/notes.xml
+++ b/lib/debugger/doc/src/notes.xml
@@ -33,6 +33,43 @@
<p>This document describes the changes made to the Debugger
application.</p>
+<section><title>Debugger 5.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>EEP-52 has been implemented.</p>
+ <p>In binary matching, the size of the segment to be
+ matched is now allowed to be a guard expression, and
+ similarly in map matching the keys can now be guard
+ expressions. See the Erlang Reference Manual and
+ Programming Examples for more details.</p>
+ <p>Language compilers or code generators that generate
+ Core Erlang code may need to be updated to be compatible
+ with the compiler in OTP 23. For more details, see the
+ section Backwards Compatibility in <url
+ href="http://erlang.org/eeps/eep-0052.html">EEP
+ 52</url>.</p>
+ <p>
+ Own Id: OTP-14708</p>
+ </item>
+ <item>
+ <p>The deprecated <c>erlang:get_stacktrace/0</c> BIF now
+ returns an empty list instead of a stacktrace. To
+ retrieve the stacktrace, use the extended try/catch
+ syntax that was introduced in OTP 21.
+ <c>erlang:get_stacktrace/0</c> is scheduled for removal
+ in OTP 24.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16484</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Debugger 4.2.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/debugger/doc/src/images/view.jpg b/lib/debugger/doc/src/view.jpg
index 7ffd511eff..7ffd511eff 100644
--- a/lib/debugger/doc/src/images/view.jpg
+++ b/lib/debugger/doc/src/view.jpg
Binary files differ
diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl
index b6703d5d9e..38c2035c5c 100644
--- a/lib/debugger/src/dbg_ieval.erl
+++ b/lib/debugger/src/dbg_ieval.erl
@@ -693,7 +693,7 @@ expr({'try',Line,Es,CaseCs,CatchCs,[]}, Bs0, Ieval0) ->
end
catch
Class:Reason when CatchCs =/= [] ->
- catch_clauses({Class,Reason,[]}, CatchCs, Bs0, Ieval)
+ catch_clauses({Class,Reason,get_stacktrace()}, CatchCs, Bs0, Ieval)
end;
expr({'try',Line,Es,CaseCs,CatchCs,As}, Bs0, Ieval0) ->
Ieval = Ieval0#ieval{line=Line},
@@ -706,7 +706,7 @@ expr({'try',Line,Es,CaseCs,CatchCs,As}, Bs0, Ieval0) ->
end
catch
Class:Reason when CatchCs =/= [] ->
- catch_clauses({Class,Reason,[]}, CatchCs, Bs0, Ieval)
+ catch_clauses({Class,Reason,get_stacktrace()}, CatchCs, Bs0, Ieval)
after
seq(As, Bs0, Ieval#ieval{top=false})
end;
@@ -905,14 +905,9 @@ expr({dbg,Line,self,[]}, Bs, #ieval{level=Le}) ->
Self = get(self),
trace(return, {Le,Self}),
{value,Self,Bs};
-expr({dbg,Line,get_stacktrace,[]}, Bs, #ieval{level=Le}) ->
- trace(bif, {Le,Line,erlang,get_stacktrace,[]}),
- Stacktrace = get_stacktrace(),
- trace(return, {Le,Stacktrace}),
- {value,Stacktrace,Bs};
expr({dbg,Line,raise,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
- %% Since erlang:get_stacktrace/0 is emulated, we will
- %% need to emulate erlang:raise/3 too so that we can
+ %% Since stacktraces are emulated, we will
+ %% need to emulate erlang:raise/3 so that we can
%% capture the stacktrace.
Ieval = Ieval0#ieval{line=Line},
{[Class,Reason,Stk0]=As,Bs} = eval_list(As0, Bs0, Ieval),
@@ -1383,7 +1378,7 @@ catch_clauses(Exception, [{clause,_,[P],G,B}|CatchCs], Bs0, Ieval) ->
nomatch ->
catch_clauses(Exception, CatchCs, Bs0, Ieval)
end;
-catch_clauses({Class,Reason,[]}, [], _Bs, _Ieval) ->
+catch_clauses({Class,Reason,_}, [], _Bs, _Ieval) ->
erlang:Class(Reason).
receive_clauses(Cs, Bs0, [Msg|Msgs]) ->
@@ -1592,12 +1587,17 @@ match_tuple([], _, _, Bs, _BBs) ->
{match,Bs}.
match_map([{map_field_exact,_,K0,Pat}|Fs], Map, Bs0, BBs) ->
- {value,K,BBs} = expr(K0, BBs, #ieval{}),
- case maps:find(K, Map) of
- {ok,Value} ->
- {match,Bs} = match1(Pat, Value, Bs0, BBs),
- match_map(Fs, Map, Bs, BBs);
- error -> throw(nomatch)
+ try guard_expr(K0, BBs) of
+ {value,K} ->
+ case Map of
+ #{K := Value} ->
+ {match,Bs} = match1(Pat, Value, Bs0, BBs),
+ match_map(Fs, Map, Bs, BBs);
+ #{} ->
+ throw(nomatch)
+ end
+ catch _:_ ->
+ throw(nomatch)
end;
match_map([], _, Bs, _BBs) ->
{match,Bs}.
diff --git a/lib/debugger/src/dbg_iload.erl b/lib/debugger/src/dbg_iload.erl
index 468f6a809f..d15292d51d 100644
--- a/lib/debugger/src/dbg_iload.erl
+++ b/lib/debugger/src/dbg_iload.erl
@@ -204,7 +204,7 @@ pattern({tuple,Anno,Ps0}) ->
{tuple,ln(Anno),Ps1};
pattern({map,Anno,Fs0}) ->
Fs1 = lists:map(fun ({map_field_exact,L,K,V}) ->
- {map_field_exact,L,expr(K, false),pattern(V)}
+ {map_field_exact,L,gexpr(K),pattern(V)}
end, Fs0),
{map,ln(Anno),Fs1};
pattern({op,_,'-',{integer,Anno,I}}) ->
@@ -226,7 +226,7 @@ pattern({bin_element,Anno,Expr0,Size0,Type0}) ->
{Size1,Type} = make_bit_type(Anno, Size0, Type0),
Expr1 = pattern(Expr0),
Expr = coerce_to_float(Expr1, Type0),
- Size = pattern(Size1),
+ Size = expr(Size1, false),
{bin_element,ln(Anno),Expr,Size,Type};
%% Evaluate compile-time expressions.
pattern({op,_,'++',{nil,_},R}) ->
@@ -438,8 +438,6 @@ expr({'fun',Anno,{function,M,F,A}}, _Lc) ->
{make_ext_fun,ln(Anno),MFA};
expr({call,Anno,{remote,_,{atom,_,erlang},{atom,_,self}},[]}, _Lc) ->
{dbg,ln(Anno),self,[]};
-expr({call,Anno,{remote,_,{atom,_,erlang},{atom,_,get_stacktrace}},[]}, _Lc) ->
- {dbg,ln(Anno),get_stacktrace,[]};
expr({call,Anno,{remote,_,{atom,_,erlang},{atom,_,throw}},[_]=As}, _Lc) ->
{dbg,ln(Anno),throw,expr_list(As)};
expr({call,Anno,{remote,_,{atom,_,erlang},{atom,_,error}},[_]=As}, _Lc) ->
diff --git a/lib/debugger/src/dbg_wx_win.erl b/lib/debugger/src/dbg_wx_win.erl
index 4391ad1598..25b76d1739 100644
--- a/lib/debugger/src/dbg_wx_win.erl
+++ b/lib/debugger/src/dbg_wx_win.erl
@@ -304,7 +304,10 @@ to_string(Integer) when is_integer(Integer) ->
integer_to_list(Integer);
to_string([]) -> "";
to_string(List) when is_list(List) ->
- List;
+ try unicode:characters_to_list(List)
+ catch _:_ ->
+ io_lib:format("~tp",[List])
+ end;
to_string(Term) ->
io_lib:format("~tp",[Term]).
diff --git a/lib/debugger/test/Makefile b/lib/debugger/test/Makefile
index efb6d9ed8b..0f942923f3 100644
--- a/lib/debugger/test/Makefile
+++ b/lib/debugger/test/Makefile
@@ -32,6 +32,7 @@ MODULES= \
bs_match_int_SUITE \
bs_match_misc_SUITE \
bs_match_tail_SUITE \
+ bs_size_expr_SUITE \
bs_utf_SUITE \
bug_SUITE \
erl_eval_SUITE \
diff --git a/lib/debugger/test/bs_size_expr_SUITE.erl b/lib/debugger/test/bs_size_expr_SUITE.erl
new file mode 100644
index 0000000000..864233f282
--- /dev/null
+++ b/lib/debugger/test/bs_size_expr_SUITE.erl
@@ -0,0 +1,273 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(bs_size_expr_SUITE).
+-compile(nowarn_shadow_vars).
+
+-export([all/0,suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ init_per_testcase/2,end_per_testcase/2,
+ basic/1,size_shadow/1,complex/1,
+ recv/1,no_match/1]).
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}].
+
+all() ->
+ [{group,p}].
+
+groups() ->
+ [{p,[],
+ [basic,
+ size_shadow,
+ complex,
+ recv,
+ no_match]}].
+
+init_per_suite(Config) ->
+ test_lib:interpret(?MODULE),
+ true = lists:member(?MODULE, int:interpreted()),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
+ test_lib:interpret(?MODULE),
+ Config.
+
+end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
+ ok.
+
+basic(_Config) ->
+ <<>> = do_basic(<<1:32>>),
+ <<"abcd">> = do_basic(<<2:32,"abcd">>),
+ no_match = do_basic(<<0:32>>),
+ no_match = do_basic(<<777:32>>),
+ ok.
+
+do_basic(Bin) ->
+ Res = do_basic_1(Bin),
+
+ Res = do_basic_2({tag,Bin}),
+ Res = do_basic_2([list,Bin]),
+ 6 = do_basic_2({2,4}),
+
+ Res = do_basic_3(Bin),
+ Res = do_basic_4(Bin),
+
+ {result,Res} = do_basic_5(Bin),
+ case Res of
+ no_match ->
+ ok;
+ _ ->
+ {result,{Res,7777777}} = do_basic_5(<<Bin/binary,7777777:32>>)
+ end,
+
+ Res.
+
+do_basic_1(<<Sz:32,Tail:(4*Sz-4)/binary>>) ->
+ Tail;
+do_basic_1(<<_/binary>>) ->
+ no_match.
+
+do_basic_2({tag,<<Sz:32,Tail:(4*Sz-4)/binary>>}) ->
+ Tail;
+do_basic_2([list,<<Sz:32,Tail:((Sz-1)*4)/binary>>]) ->
+ Tail;
+do_basic_2({A,B}) when is_integer(A), is_integer(B) ->
+ A + B;
+do_basic_2(_) ->
+ no_match.
+
+do_basic_3(Bin) ->
+ WordSize = id(4),
+ case Bin of
+ <<Sz:32,Tail:(WordSize*Sz-WordSize)/binary>> ->
+ Tail;
+ _ ->
+ no_match
+ end.
+
+do_basic_4(Bin) ->
+ WordSize = id(4),
+ F = fun() ->
+ case Bin of
+ <<Sz:32,Tail:(WordSize*Sz-WordSize)/binary>> ->
+ Tail;
+ _ ->
+ no_match
+ end
+ end,
+ F().
+
+do_basic_5(Bin) ->
+ WordSize = id(4),
+ F = fun() ->
+ Res = case Bin of
+ <<Sz:32,Tail:(WordSize*Sz-WordSize)/binary,More:(8*WordSize)>> ->
+ {Tail,More};
+ <<Sz:32,Tail:(WordSize*Sz-WordSize)/binary>> ->
+ Tail;
+ _ ->
+ no_match
+ end,
+ {result,Res}
+ end,
+ F().
+
+size_shadow(_Config) ->
+ 12345678 = size_shadow_1(),
+ ok.
+
+size_shadow_1() ->
+ L = 8,
+ Offset = 16,
+ Fs = [fun(<<L:L,B:(L+16)>>) -> B end,
+ fun(<<L:L,B:(L+Offset)>>) -> B end,
+ fun(A) ->
+ Res = (fun([<<L:L,B:(L+16)>>]) -> B end)([A]),
+ Res = (fun([<<L:L,B:(L+Offset)>>]) -> B end)([A])
+ end,
+ fun(A) ->
+ Res = (fun({<<L:L,B:(L+16)>>,<<L:L,B:(L+16)>>}) -> B end)({A,A}),
+ Res = (fun({<<L:L,B:(L+Offset)>>,<<L:L,B:(L+16)>>}) -> B end)({A,A}),
+ Res = (fun({<<L:L,B:(L+16)>>,<<L:L,B:(L+Offset)>>}) -> B end)({A,A}),
+ Res = (fun({<<L:L,B:(L+Offset)>>,<<L:L,B:(L+Offset)>>}) -> B end)({A,A})
+ end,
+ fun(A) ->
+ <<Size:L,_/bits>> = A,
+ Inner = fun([L], {#{key1 := <<L:L,B:(L+Offset)>>,
+ key2 := <<L:L,B:(L+Offset)>>}, L}) -> B end,
+ Inner([Size], {#{key1 => A,key2 => A},Size})
+ end],
+ size_shadow_apply(Fs, <<16:8, 12345678:32>>).
+
+size_shadow_apply([F|Fs], Arg) when is_function(F, 1) ->
+ size_shadow_apply(Fs, Arg, F(Arg)).
+
+size_shadow_apply([F|Fs], Arg, Res) when is_function(F, 1) ->
+ Res = F(Arg),
+ size_shadow_apply(Fs, Arg, Res);
+size_shadow_apply([], _, Res) ->
+ Res.
+
+-record(r, {a,b,c}).
+complex(Config) ->
+ (fun() ->
+ Len = length(id(Config)),
+ Bin = << <<I:13>> || I <- lists:seq(1, Len) >>,
+ <<Bin:(length(Config))/binary-unit:13>> = Bin
+ end)(),
+
+ (fun() ->
+ V = id([a,b,c]),
+ F = fun(<<V:(bit_size(<<0:(length(V))>>)*8)/signed-integer>>) ->
+ V;
+ ({A,B}) ->
+ A + B
+ end,
+ -1 = F(<<-1:(length(V)*8)>>),
+ 7 = F({3,4})
+ end)(),
+
+ (fun() ->
+ A = a,
+ B = b,
+ F = fun(<<A:16,B:16,C:(A+B),D/bits>>) ->
+ {A,B,C,D};
+ (<<A:16,B:16>>) ->
+ {A,B};
+ (<<A:8,B:8>>) ->
+ {A,B}
+ end,
+ {13,21,16#cafebeef,<<"more">>} = F(<<13:16,21:16,16#cafebeef:34,"more">>),
+ {100,500} = F(<<100:16,500:16>>),
+ {157,77} = F(<<157:8,77:8>>),
+ {A,B}
+ end)(),
+
+ (fun() ->
+ Two = id(2),
+ F = fun(a, <<_:(#r.a - Two)/binary,Int:8,_/binary>>) -> Int;
+ (b, <<_:(#r.b - Two)/binary,Int:8,_/binary>>) -> Int;
+ (c, <<_:(#r.c - Two)/binary,Int:8,_/binary>>) -> Int
+ end,
+ 1 = F(a, <<1,2,3>>),
+ 2 = F(b, <<1,2,3>>),
+ 3 = F(c, <<1,2,3>>)
+ end)(),
+
+ (fun() ->
+ Bin = <<1,2,3,4>>,
+ F = fun(R) ->
+ <<First:(R#r.a)/binary,Tail/binary>> = Bin,
+ {First,Tail}
+ end,
+ {<<>>,<<1,2,3,4>>} = F(#r{a=0}),
+ {<<1>>,<<2,3,4>>} = F(#r{a=1}),
+ {<<1,2>>,<<3,4>>} = F(#r{a=2}),
+ {<<1,2,3>>,<<4>>} = F(#r{a=3}),
+ {<<1,2,3,4>>,<<>>} = F(#r{a=4})
+ end)(),
+
+ ok.
+
+recv(_Config) ->
+ R = fun(Msg) ->
+ self() ! Msg,
+ Res = receive
+ <<L,I:(L-1)/unit:8,X:32>> -> {I,X};
+ <<L,I:(L-1)/unit:8,X:64>> -> {I,X}
+ end,
+ self() ! {tag,[Msg]},
+ Res = receive
+ {tag,[<<L,I:(8*(L-1)),X:32>>]} -> {I,X};
+ {tag,[<<L,I:(8*(L-1)),X:64>>]} -> {I,X}
+ end
+ end,
+ {1234,16#deadbeef} = R(<<3,1234:16,16#deadbeef:32>>),
+ {99,16#cafebeeff00d} = R(<<2,99:8,16#cafebeeff00d:64>>),
+ ok.
+
+no_match(_Config) ->
+ B = id(<<1,2,3,4>>),
+ no_match = case B of
+ <<Int:(bit_size(B)-1)>> -> Int;
+ <<Int:(bit_size(B)*2)>> -> Int;
+ <<Int:(length(B))>> -> Int;
+ _ -> no_match
+ end,
+ no_match = case B of
+ <<L:8,Int2:(is_integer(L))>> -> Int2;
+ <<L:8,Int2:(L+3.0)>> -> Int2;
+ _ -> no_match
+ end,
+ ok.
+
+id(I) ->
+ I.
diff --git a/lib/debugger/test/exception_SUITE.erl b/lib/debugger/test/exception_SUITE.erl
index ef824b00be..506d4d0366 100644
--- a/lib/debugger/test/exception_SUITE.erl
+++ b/lib/debugger/test/exception_SUITE.erl
@@ -275,67 +275,43 @@ ba_bnot(A) ->
{'EXIT', {badarith, _}} = (catch bnot A).
stacktrace(Conf) when is_list(Conf) ->
- Tag = make_ref(),
- {_,Mref} = spawn_monitor(fun() -> exit({Tag,erlang:get_stacktrace()}) end),
- {Tag,[]} = receive {'DOWN',Mref,_,_,Info} -> Info end,
V = [make_ref()|self()],
{value2,{caught1,badarg,[{erlang,abs,[V],_}|_]=St1}} =
stacktrace_1({'abs',V}, error, {value,V}),
- St1 = erase(stacktrace1),
- St1 = erase(stacktrace2),
- St1 = erlang:get_stacktrace(),
{caught2,{error,badarith},[{?MODULE,my_add,2,_}|_]=St2} =
stacktrace_1({'div',{1,0}}, error, {'add',{0,a}}),
- [{?MODULE,my_div,2,_}|_] = erase(stacktrace1),
- St2 = erase(stacktrace2),
- St2 = erlang:get_stacktrace(),
{caught2,{error,{try_clause,V}},[{?MODULE,stacktrace_1,3,_}|_]=St3} =
stacktrace_1({value,V}, error, {value,V}),
- St3 = erase(stacktrace1),
- St3 = erase(stacktrace2),
- St3 = erlang:get_stacktrace(),
{caught2,{throw,V},[{?MODULE,foo,1,_}|_]=St4} =
stacktrace_1({value,V}, error, {throw,V}),
- [{?MODULE,stacktrace_1,3,_}|_] = erase(stacktrace1),
- St4 = erase(stacktrace2),
- St4 = erlang:get_stacktrace(),
ok.
stacktrace_1(X, C1, Y) ->
- erase(stacktrace1),
- erase(stacktrace2),
try try foo(X) of
C1 -> value1
catch
- C1:D1 -> {caught1,D1,erlang:get_stacktrace()}
+ C1:D1:S1 -> {caught1,D1,S1}
after
- put(stacktrace1, erlang:get_stacktrace()),
foo(Y)
end of
V2 -> {value2,V2}
catch
- C2:D2 -> {caught2,{C2,D2},erlang:get_stacktrace()}
- after
- put(stacktrace2, erlang:get_stacktrace())
+ C2:D2:S2 -> {caught2,{C2,D2},S2}
end.
nested_stacktrace(Conf) when is_list(Conf) ->
V = [{make_ref()}|[self()]],
- value1 =
- nested_stacktrace_1({{value,{V,x1}},void,{V,x1}},
- {void,void,void}),
+ value1 = nested_stacktrace_1({{value,{V,x1}},void,{V,x1}},
+ {void,void,void}),
{caught1,
[{?MODULE,my_add,2,_}|_],
- value2,
- [{?MODULE,my_add,2,_}|_]} =
- nested_stacktrace_1({{'add',{V,x1}},error,badarith},
- {{value,{V,x2}},void,{V,x2}}),
+ value2} = nested_stacktrace_1({{'add',{V,x1}},error,badarith},
+ {{value,{V,x2}},void,{V,x2}}),
{caught1,
[{?MODULE,my_add,2,_}|_],
- {caught2,[{erlang,abs,[V],_}|_]},
- [{erlang,abs,[V],_}|_]} =
+ {caught2,[{erlang,abs,[V],_}|_]}} =
nested_stacktrace_1({{'add',{V,x1}},error,badarith},
{{'abs',V},error,badarg}),
ok.
@@ -344,15 +320,13 @@ nested_stacktrace_1({X1,C1,V1}, {X2,C2,V2}) ->
try foo(X1) of
V1 -> value1
catch
- C1:V1 ->
- S1 = erlang:get_stacktrace(),
- T2 =
- try foo(X2) of
- V2 -> value2
- catch
- C2:V2 -> {caught2,erlang:get_stacktrace()}
+ C1:V1:S1 ->
+ T2 = try foo(X2) of
+ V2 -> value2
+ catch
+ C2:V2:S2 -> {caught2,S2}
end,
- {caught1,S1,T2,erlang:get_stacktrace()}
+ {caught1,S1,T2}
end.
@@ -363,17 +337,14 @@ raise(Conf) when is_list(Conf) ->
try
try foo({'div',{1,0}})
catch
- error:badarith ->
- put(raise, A0 = erlang:get_stacktrace()),
+ error:badarith:A0 ->
+ put(raise, A0),
erlang:raise(error, badarith, A0)
end
catch
- error:badarith ->
- A1 = erlang:get_stacktrace(),
+ error:badarith:A1 ->
A1 = get(raise)
end,
- A = erlang:get_stacktrace(),
- A = get(raise),
[{?MODULE,my_div,2,_}|_] = A,
%%
N = 8, % Must be even
@@ -381,19 +352,18 @@ raise(Conf) when is_list(Conf) ->
try even(N)
catch error:function_clause -> ok
end,
- B = odd_even(N, []),
- B = erlang:get_stacktrace(),
%%
- C0 = odd_even(N+1, []),
- C = lists:sublist(C0, N),
- try odd(N+1)
- catch error:function_clause -> ok
+ C = odd_even(N+1, []),
+ try
+ odd(N+1)
+ catch
+ error:function_clause -> ok
end,
- C = erlang:get_stacktrace(),
- try erlang:raise(error, function_clause, C0)
- catch error:function_clause -> ok
+ try
+ erlang:raise(error, function_clause, C)
+ catch
+ error:function_clause -> ok
end,
- C = erlang:get_stacktrace(),
ok.
odd_even(N, R) when is_integer(N), N > 1 ->
@@ -436,7 +406,6 @@ my_abs(X) -> abs(X).
gunilla(Config) when is_list(Config) ->
{throw,kalle} = gunilla_1(),
- [] = erlang:get_stacktrace(),
ok.
gunilla_1() ->
diff --git a/lib/debugger/test/int_eval_SUITE_data/stacktrace.erl b/lib/debugger/test/int_eval_SUITE_data/stacktrace.erl
index 3380178fdc..591841ada3 100644
--- a/lib/debugger/test/int_eval_SUITE_data/stacktrace.erl
+++ b/lib/debugger/test/int_eval_SUITE_data/stacktrace.erl
@@ -3,10 +3,15 @@
?MODULE() ->
OldDepth = erlang:system_flag(backtrace_depth, 32),
- done = (catch do_try()),
- Stk = trim(erlang:get_stacktrace()),
- erlang:system_flag(backtrace_depth, OldDepth),
- {done,Stk}.
+ try
+ do_try()
+ catch
+ throw:done:Stk0 ->
+ Stk = trim(Stk0),
+ {done,Stk}
+ after
+ erlang:system_flag(backtrace_depth, OldDepth)
+ end.
trim([{int_eval_SUITE,_,_,_}|_]) ->
[];
diff --git a/lib/debugger/test/line_number_SUITE.erl b/lib/debugger/test/line_number_SUITE.erl
index 276473b95f..4ad84b5a3b 100644
--- a/lib/debugger/test/line_number_SUITE.erl
+++ b/lib/debugger/test/line_number_SUITE.erl
@@ -90,8 +90,8 @@ close_calls(Where) -> %Line 86
call2(), %Line 90
call3(), %Line 91
no_crash %Line 92
- catch error:crash ->
- erlang:get_stacktrace() %Line 94
+ catch error:crash:Stk ->
+ Stk %Line 94
end. %Line 95
call1() -> %Line 97
diff --git a/lib/debugger/test/map_SUITE.erl b/lib/debugger/test/map_SUITE.erl
index 4d8a86f5a2..7c4ded5082 100644
--- a/lib/debugger/test/map_SUITE.erl
+++ b/lib/debugger/test/map_SUITE.erl
@@ -88,7 +88,10 @@
%% misc
t_pdict/1,
- t_ets/1
+ t_ets/1,
+
+ %% new in OTP 23
+ t_key_expressions/1
]).
-include_lib("stdlib/include/ms_transform.hrl").
@@ -150,7 +153,10 @@ all() -> [
%% Other functions
t_pdict,
- t_ets
+ t_ets,
+
+ %% new in OTP 23
+ t_key_expressions
].
groups() -> [].
@@ -2233,6 +2239,48 @@ validate_frequency([{T,C}|Fs],Tf) ->
end;
validate_frequency([], _) -> ok.
+t_key_expressions(_Config) ->
+ Int = id(42),
+ #{{tag,Int} := 42} = id(#{{tag,Int} => 42}),
+ #{{tag,Int+1} := 42} = id(#{{tag,Int+1} => 42}),
+ #{{a,b} := x, {tag,Int} := 42, Int := 0} =
+ id(#{{a,b} => x, {tag,Int} => 42, Int => 0}),
+
+ F1 = fun(#{Int + 1 := Val}) -> Val end,
+ val = F1(#{43 => val}),
+ {'EXIT',_} = (catch F1(a)),
+
+ F2 = fun(M, X, Y) ->
+ case M of
+ #{element(X, Y) := <<Sz:16,Bin:Sz/binary>>} ->
+ Bin;
+ #{} ->
+ not_found;
+ {A,B} ->
+ A + B
+ end
+ end,
+ <<"xyz">> = F2(#{b => <<3:16,"xyz">>}, 2, {a,b,c}),
+ not_found = F2(#{b => <<3:16,"xyz">>}, 999, {a,b,c}),
+ 13 = F2({6,7}, 1, 2),
+
+ #{<<"Спутник"/utf8>> := 1} = id(#{<<"Спутник"/utf8>> => 1}),
+
+ F3 = fun(Arg) ->
+ erase(once),
+ RunOnce = fun(I) ->
+ undefined = put(once, twice),
+ id(I)
+ end,
+ case RunOnce(Arg) of
+ #{{tag,<<Int:42>>} := Value} -> Value;
+ {X,Y} -> X + Y
+ end
+ end,
+ 10 = F3({7,3}),
+ whatever = F3(#{{tag,<<Int:42>>} => whatever}),
+
+ ok.
%% aux
diff --git a/lib/debugger/vsn.mk b/lib/debugger/vsn.mk
index 06fc743270..8e334a00f5 100644
--- a/lib/debugger/vsn.mk
+++ b/lib/debugger/vsn.mk
@@ -1 +1 @@
-DEBUGGER_VSN = 4.2.8
+DEBUGGER_VSN = 5.0
diff --git a/lib/dialyzer/Makefile b/lib/dialyzer/Makefile
index e4f681dcd9..53083f267d 100644
--- a/lib/dialyzer/Makefile
+++ b/lib/dialyzer/Makefile
@@ -42,3 +42,6 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=compiler syntax_tools hipe wx
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/dialyzer/doc/src/Makefile b/lib/dialyzer/doc/src/Makefile
index 3ce777392b..bcceb3b661 100644
--- a/lib/dialyzer/doc/src/Makefile
+++ b/lib/dialyzer/doc/src/Makefile
@@ -26,15 +26,11 @@ VSN=$(DIALYZER_VSN)
APPLICATION=dialyzer
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
-XML_REF3_FILES = dialyzer.xml typer.xml
+XML_REF3_FILES = dialyzer.xml
+XML_REF1_FILES = typer_cmd.xml
XML_PART_FILES = part.xml
XML_CHAPTER_FILES = dialyzer_chapter.xml notes.xml
@@ -43,72 +39,10 @@ BOOK_FILES = book.xml
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
- $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES)
-
-
-# ----------------------------------------------------
-
-TEXT_FILES = \
- ../about.txt \
- ../manual.txt \
- ../warnings.txt
+ $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF1_FILES) \
+ $(XML_APPLICATION_FILES)
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml
index 8dd814982d..5dd40a595b 100644
--- a/lib/dialyzer/doc/src/dialyzer.xml
+++ b/lib/dialyzer/doc/src/dialyzer.xml
@@ -57,8 +57,8 @@
dialyzer --help</code>
<p>For more details about the operation of Dialyzer, see section
- <seealso marker="dialyzer_chapter#dialyzer_gui">
- Using Dialyzer from the GUI</seealso> in the User's Guide.</p>
+ <seeguide marker="dialyzer_chapter#dialyzer_gui">
+ Using Dialyzer from the GUI</seeguide> in the User's Guide.</p>
<p><em>Exit status of the command-line version:</em></p>
@@ -300,9 +300,9 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
(For help on the names of warnings, use
<c>dialyzer -Whelp</c>.)
Notice that the options can also be specified in the file with a
- <c>-dialyzer()</c> attribute. For details, see section <seealso
+ <c>-dialyzer()</c> attribute. For details, see section <seeerl
marker="#suppression">Requesting or Suppressing Warnings in
- Source Files</seealso>.</p>
+ Source Files</seeerl>.</p>
</item>
</taglist>
@@ -310,7 +310,7 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<p>** options <c>-D</c> and <c>-I</c> work both
from the command line and in the Dialyzer GUI; the syntax of
defines and includes is the same as that used by
- <seealso marker="erts:erlc">erlc(1)</seealso>.</p>
+ <seecom marker="erts:erlc">erlc(1)</seecom>.</p>
</note>
<p><em>Warning options:</em></p>
@@ -391,7 +391,9 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<item>
<p>Include warnings for function calls that ignore a structured return
value or do not match against one of many possible return
- value(s).</p>
+ values. However, no warnings are included if the possible return
+ values are a union of atoms or a union of numbers.
+ </p>
</item>
</taglist>
@@ -422,8 +424,8 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<p>Dialyzer can be used directly from Erlang. Both the GUI and the
command-line versions are also available. The options are similar to the
ones given from the command line, see section
- <seealso marker="#command_line">
- Using Dialyzer from the Command Line</seealso>.</p>
+ <seeerl marker="#command_line">
+ Using Dialyzer from the Command Line</seeerl>.</p>
</section>
<section>
@@ -458,8 +460,8 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
-dialyzer({[no_return, no_match], [g/0, h/0]}).</code>
<p>For help on the warning options, use <c>dialyzer -Whelp</c>. The
- options are also enumerated, see function <seealso marker="#gui/1">
- <c>gui/1</c></seealso> below (<c>WarnOpts</c>).</p>
+ options are also enumerated, see function <seemfa marker="#gui/1">
+ <c>gui/1</c></seemfa> below (<c>WarnOpts</c>).</p>
<note>
<p>Warning option <c>-Wrace_conditions</c> has no effect when
@@ -485,7 +487,7 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
</type>
<desc>
<p>Get a string from warnings as returned by
- <seealso marker="#run/1"><c>run/1</c></seealso>.</p>
+ <seemfa marker="#run/1"><c>run/1</c></seemfa>.</p>
</desc>
</func>
@@ -499,7 +501,7 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
</type>
<desc>
<p>Get a string from warnings as returned by
- <seealso marker="#run/1"><c>run/1</c></seealso>.</p>
+ <seemfa marker="#run/1"><c>run/1</c></seemfa>.</p>
<p>If <c>indent_opt</c> is set to <c>true</c> (default),
line breaks are inserted in types, contracts, and Erlang
code to improve readability.</p>
diff --git a/lib/dialyzer/doc/src/dialyzer_chapter.xml b/lib/dialyzer/doc/src/dialyzer_chapter.xml
index b5acf3732e..896705d3eb 100644
--- a/lib/dialyzer/doc/src/dialyzer_chapter.xml
+++ b/lib/dialyzer/doc/src/dialyzer_chapter.xml
@@ -121,13 +121,13 @@ dialyzer --plt_info</code>
<section>
<title>Using Dialyzer from the Command Line</title>
<p>Dialyzer has a command-line version for automated use.
- See <seealso marker="dialyzer"><c>dialyzer(3)</c></seealso>.</p>
+ See <seeerl marker="dialyzer"><c>dialyzer(3)</c></seeerl>.</p>
</section>
<section>
<title>Using Dialyzer from Erlang</title>
<p>Dialyzer can also be used directly from Erlang.
- See <seealso marker="dialyzer"><c>dialyzer(3)</c></seealso>.</p>
+ See <seeerl marker="dialyzer"><c>dialyzer(3)</c></seeerl>.</p>
</section>
<section>
@@ -189,7 +189,7 @@ dialyzer --plt_info</code>
<title>Include Directories and Macro Definitions</title>
<p>When analyzing from source, you might have to supply Dialyzer
with a list of include directories and macro definitions (as you can do
- with the <seealso marker="erts:erlc"><c>erlc</c></seealso> flags
+ with the <seecom marker="erts:erlc"><c>erlc</c></seecom> flags
<c>-I</c> and <c>-D</c>). This can be done
either by starting Dialyzer with these flags from the command
line as in:</p>
@@ -215,12 +215,12 @@ dialyzer -I my_includes -DDEBUG -Dvsn=42 -I one_more_dir</code>
<title>Inspecting the Inferred Types of the Analyzed Functions</title>
<p>Dialyzer stores the information of the analyzed functions in a
Persistent Lookup Table (PLT), see section
- <seealso marker="#plt">The Persistent Lookup Table</seealso>.</p>
+ <seeguide marker="#plt">The Persistent Lookup Table</seeguide>.</p>
<p>After an analysis, you can inspect this information.
In the <em>PLT</em> menu you can choose to either search the PLT
or inspect the contents of the whole PLT. The information is presented
- in <seealso marker="edoc:edoc"><c>EDoc</c></seealso> format.</p>
+ in <seeerl marker="edoc:edoc"><c>EDoc</c></seeerl> format.</p>
</section>
</section>
diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml
index c2e8875635..eac56be316 100644
--- a/lib/dialyzer/doc/src/notes.xml
+++ b/lib/dialyzer/doc/src/notes.xml
@@ -32,6 +32,66 @@
<p>This document describes the changes made to the Dialyzer
application.</p>
+<section><title>Dialyzer 4.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct handling of PLTs in the GUI.</p>
+ <p>
+ Own Id: OTP-17091</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Dialyzer 4.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Clarify warning option <c>-Wunmatched_returns</c> in
+ <c>dialyzer(3)</c>.</p>
+ <p>
+ Own Id: OTP-17068 Aux Id: ERL-1223 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Dialyzer 4.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>In rare circumstance, dialyzer wold crash when
+ analyzing a list comprehension.</p>
+ <p>
+ Own Id: OTP-16813 Aux Id: ERL-1307 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Dialyzer 4.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Improve handling of <c>maps:remove/2</c>. </p>
+ <p>
+ Own Id: OTP-16055 Aux Id: ERL-1002 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Dialyzer 4.1.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1150,8 +1210,8 @@
</taglist>
<p>
For information on how to use Maps please see Map Expressions in the
- <seealso marker="doc/reference_manual:expressions#map_expressions">
- Reference Manual</seealso>.</p>
+ <seeguide marker="system/reference_manual:expressions#map_expressions">
+ Reference Manual</seeguide>.</p>
<p>
The current implementation is without the following
features:</p>
diff --git a/lib/dialyzer/doc/src/ref_man.xml b/lib/dialyzer/doc/src/ref_man.xml
index d8cf324232..74455143b1 100644
--- a/lib/dialyzer/doc/src/ref_man.xml
+++ b/lib/dialyzer/doc/src/ref_man.xml
@@ -31,6 +31,5 @@
<description>
</description>
<xi:include href="dialyzer.xml"/>
- <xi:include href="typer.xml"/>
+ <xi:include href="typer_cmd.xml"/>
</application>
-
diff --git a/lib/dialyzer/doc/src/typer.xml b/lib/dialyzer/doc/src/typer_cmd.xml
index 524956ed4b..d070b31a74 100644
--- a/lib/dialyzer/doc/src/typer.xml
+++ b/lib/dialyzer/doc/src/typer_cmd.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE erlref SYSTEM "erlref.dtd">
+<!DOCTYPE comref SYSTEM "comref.dtd">
-<erlref>
+<comref>
<header>
<copyright>
<year>2006</year><year>2017</year>
@@ -29,9 +29,9 @@
<rev></rev>
<file>type.xml</file>
</header>
- <module>typer</module>
- <modulesummary>Typer, a Type annotator for ERlang programs.
- </modulesummary>
+ <com>typer</com>
+ <comsummary>Typer, a Type annotator for ERlang programs.
+ </comsummary>
<description>
<p>TypEr shows type information for Erlang modules to the user.
Additionally, it can annotate the code of files with such type
@@ -149,9 +149,9 @@ typer [--help] [--version] [--plt PLT] [--edoc]
<p>** options <c>-D</c> and <c>-I</c> work both
from the command line and in the TypEr GUI; the syntax of
defines and includes is the same as that used by
- <seealso marker="erts:erlc">erlc(1)</seealso>.</p>
+ <seecom marker="erts:erlc">erlc(1)</seecom>.</p>
</note>
</section>
-</erlref>
+</comref>
diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile
index bddd761705..1f5b308c7d 100644
--- a/lib/dialyzer/src/Makefile
+++ b/lib/dialyzer/src/Makefile
@@ -53,6 +53,7 @@ MODULES = \
dialyzer_callgraph \
dialyzer_cl \
dialyzer_cl_parse \
+ dialyzer_clean_core \
dialyzer_codeserver \
dialyzer_contracts \
dialyzer_dataflow \
diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src
index e3a0fc967d..36f5d96ea6 100644
--- a/lib/dialyzer/src/dialyzer.app.src
+++ b/lib/dialyzer/src/dialyzer.app.src
@@ -28,6 +28,7 @@
dialyzer_callgraph,
dialyzer_cl,
dialyzer_cl_parse,
+ dialyzer_clean_core,
dialyzer_codeserver,
dialyzer_contracts,
dialyzer_coordinator,
diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl
index 5e680062fb..f887f661bd 100644
--- a/lib/dialyzer/src/dialyzer_cl.erl
+++ b/lib/dialyzer/src/dialyzer_cl.erl
@@ -320,6 +320,12 @@ report_analysis_start(#options{analysis_type = Type,
end
end.
+report_native_comp(#options{report_mode = ReportMode}) ->
+ case ReportMode of
+ quiet -> ok;
+ _ -> io:format(" Compiling some key modules to native code...")
+ end.
+
report_elapsed_time(T1, T2, #options{report_mode = ReportMode}) ->
case ReportMode of
quiet -> ok;
@@ -369,6 +375,7 @@ do_analysis(Options) ->
do_analysis(Files, Options, Plt, PltInfo) ->
assert_writable(Options#options.output_plt),
+ hipe_compile(Files, Options),
report_analysis_start(Options),
State0 = new_state(),
State1 = init_output(State0, Options),
@@ -477,6 +484,106 @@ expand_dependent_modules_1([Mod|Mods], Included, ModDeps) ->
expand_dependent_modules_1([], Included, _ModDeps) ->
Included.
+-define(MIN_PARALLELISM, 7).
+-define(MIN_FILES_FOR_NATIVE_COMPILE, 20).
+
+-spec hipe_compile([file:filename()], #options{}) -> 'ok'.
+
+hipe_compile(Files, #options{erlang_mode = ErlangMode} = Options) ->
+ NoNative = (get(dialyzer_options_native) =:= false),
+ FewFiles = (length(Files) < ?MIN_FILES_FOR_NATIVE_COMPILE),
+ case NoNative orelse FewFiles orelse ErlangMode of
+ true -> ok;
+ false ->
+ case erlang:system_info(hipe_architecture) of
+ undefined -> ok;
+ _ ->
+ Mods = [lists, dict, digraph, digraph_utils, ets,
+ gb_sets, gb_trees, ordsets, sets, sofs,
+ cerl, erl_types, cerl_trees, erl_bif_types,
+ dialyzer_analysis_callgraph, dialyzer, dialyzer_behaviours,
+ dialyzer_codeserver, dialyzer_contracts,
+ dialyzer_coordinator, dialyzer_dataflow, dialyzer_dep,
+ dialyzer_plt, dialyzer_succ_typings, dialyzer_typesig,
+ dialyzer_worker],
+ report_native_comp(Options),
+ {T1, _} = statistics(wall_clock),
+ Cache = (get(dialyzer_options_native_cache) =/= false),
+ native_compile(Mods, Cache),
+ {T2, _} = statistics(wall_clock),
+ report_elapsed_time(T1, T2, Options)
+ end
+ end.
+
+native_compile(Mods, Cache) ->
+ case dialyzer_utils:parallelism() > ?MIN_PARALLELISM of
+ true ->
+ Parent = self(),
+ Pids = [spawn(fun () -> Parent ! {self(), hc(M, Cache)} end) || M <- Mods],
+ lists:foreach(fun (Pid) -> receive {Pid, Res} -> Res end end, Pids);
+ false ->
+ lists:foreach(fun (Mod) -> hc(Mod, Cache) end, Mods)
+ end.
+
+hc(Mod, Cache) ->
+ {module, Mod} = code:ensure_loaded(Mod),
+ case code:is_module_native(Mod) of
+ true -> ok;
+ false ->
+ %% io:format(" ~w", [Mod]),
+ case Cache of
+ false ->
+ {ok, Mod} = hipe:c(Mod),
+ ok;
+ true ->
+ hc_cache(Mod)
+ end
+ end.
+
+hc_cache(Mod) ->
+ CacheBase = cache_base_dir(),
+ %% Use HiPE architecture, version and erts checksum in directory name,
+ %% to avoid clashes between incompatible binaries.
+ HipeArchVersion =
+ lists:concat(
+ [erlang:system_info(hipe_architecture), "-",
+ hipe:version(), "-",
+ hipe:erts_checksum()]),
+ CacheDir = filename:join(CacheBase, HipeArchVersion),
+ OrigBeamFile = code:which(Mod),
+ {ok, {Mod, <<Checksum:128>>}} = beam_lib:md5(OrigBeamFile),
+ CachedBeamFile = filename:join(CacheDir, lists:concat([Mod, "-", Checksum, ".beam"])),
+ ok = filelib:ensure_dir(CachedBeamFile),
+ ModBin =
+ case filelib:is_file(CachedBeamFile) of
+ true ->
+ {ok, BinFromFile} = file:read_file(CachedBeamFile),
+ BinFromFile;
+ false ->
+ {ok, Mod, CompiledBin} = compile:file(OrigBeamFile, [from_beam, native, binary]),
+ ok = file:write_file(CachedBeamFile, CompiledBin),
+ CompiledBin
+ end,
+ code:unstick_dir(filename:dirname(OrigBeamFile)),
+ {module, Mod} = code:load_binary(Mod, CachedBeamFile, ModBin),
+ true = code:is_module_native(Mod),
+ ok.
+
+cache_base_dir() ->
+ %% http://standards.freedesktop.org/basedir-spec/basedir-spec-0.7.html
+ %% If XDG_CACHE_HOME is set to an absolute path, use it as base.
+ XdgCacheHome = os:getenv("XDG_CACHE_HOME"),
+ CacheHome =
+ case is_list(XdgCacheHome) andalso filename:pathtype(XdgCacheHome) =:= absolute of
+ true ->
+ XdgCacheHome;
+ false ->
+ %% Otherwise, the default is $HOME/.cache.
+ {ok, [[Home]]} = init:get_argument(home),
+ filename:join(Home, ".cache")
+ end,
+ filename:join([CacheHome, "dialyzer_hipe_cache"]).
+
new_state() ->
#cl_state{}.
diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl
index cadc2116b0..a3ec1b92f1 100644
--- a/lib/dialyzer/src/dialyzer_cl_parse.erl
+++ b/lib/dialyzer/src/dialyzer_cl_parse.erl
@@ -330,12 +330,16 @@ get_lib_dir(Apps) ->
get_lib_dir([H|T], Acc) ->
NewElem =
case code:lib_dir(list_to_atom(H)) of
- {error, bad_name} ->
- case H =:= "erts" of % hack for including erts in an un-installed system
- true -> filename:join(code:root_dir(), "erts/preloaded/ebin");
- false -> H
- end;
- LibDir -> LibDir ++ "/ebin"
+ {error, bad_name} -> H;
+ LibDir when H =:= "erts" -> % hack for including erts in an un-installed system
+ EbinDir = filename:join([LibDir,"ebin"]),
+ case file:read_file_info(EbinDir) of
+ {error,enoent} ->
+ filename:join([LibDir,"preloaded","ebin"]);
+ _ ->
+ EbinDir
+ end;
+ LibDir -> filename:join(LibDir,"ebin")
end,
get_lib_dir(T, [NewElem|Acc]);
get_lib_dir([], Acc) ->
diff --git a/lib/dialyzer/src/dialyzer_clean_core.erl b/lib/dialyzer/src/dialyzer_clean_core.erl
new file mode 100644
index 0000000000..d591ad3473
--- /dev/null
+++ b/lib/dialyzer/src/dialyzer_clean_core.erl
@@ -0,0 +1,225 @@
+%% -*- erlang-indent-level: 2 -*-
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+
+-module(dialyzer_clean_core).
+-export([clean/1]).
+
+-spec clean(cerl:cerl()) -> cerl:cerl().
+
+clean(Tree) ->
+ case cerl:type(Tree) of
+ apply ->
+ Op = clean(cerl:apply_op(Tree)),
+ Args = clean_list(cerl:apply_args(Tree)),
+ cerl:update_c_apply(Tree, Op, Args);
+ binary ->
+ Segments = clean_list(cerl:binary_segments(Tree)),
+ cerl:update_c_binary(Tree, Segments);
+ bitstr ->
+ Val = clean(cerl:bitstr_val(Tree)),
+ Size = clean(cerl:bitstr_size(Tree)),
+ Unit = cerl:bitstr_unit(Tree),
+ Type = cerl:bitstr_type(Tree),
+ Flags = cerl:bitstr_flags(Tree),
+ cerl:update_c_bitstr(Tree, Val, Size, Unit, Type, Flags);
+ 'case' ->
+ Arg = clean(cerl:case_arg(Tree)),
+ Clauses = clean_clauses(cerl:case_clauses(Tree)),
+ cerl:update_c_case(Tree, Arg, Clauses);
+ call ->
+ Args = clean_list(cerl:call_args(Tree)),
+ Module = clean(cerl:call_module(Tree)),
+ Name = clean(cerl:call_name(Tree)),
+ cerl:update_c_call(Tree, Module, Name, Args);
+ 'catch' ->
+ Body = clean(cerl:catch_body(Tree)),
+ cerl:update_c_catch(Tree, Body);
+ cons ->
+ Hd = clean(cerl:cons_hd(Tree)),
+ Tl = clean(cerl:cons_tl(Tree)),
+ cerl:update_c_cons_skel(Tree, Hd, Tl);
+ 'fun' ->
+ Body = clean(cerl:fun_body(Tree)),
+ Vars = cerl:fun_vars(Tree),
+ cerl:update_c_fun(Tree, Vars, Body);
+ 'let' ->
+ Arg = clean(cerl:let_arg(Tree)),
+ Body = clean(cerl:let_body(Tree)),
+ Vars = cerl:let_vars(Tree),
+ cerl:update_c_let(Tree, Vars, Arg, Body);
+ letrec ->
+ clean_letrec(Tree);
+ literal ->
+ Tree;
+ module ->
+ Defs = clean_defs(cerl:module_defs(Tree)),
+ Name = cerl:module_name(Tree),
+ Exports = cerl:module_exports(Tree),
+ Attrs = cerl:module_attrs(Tree),
+ cerl:update_c_module(Tree, Name, Exports, Attrs, Defs);
+ primop ->
+ Args = clean_list(cerl:primop_args(Tree)),
+ Name = cerl:primop_name(Tree),
+ cerl:update_c_primop(Tree, Name, Args);
+ 'receive' ->
+ Clauses = clean_clauses(cerl:receive_clauses(Tree)),
+ Timeout = clean(cerl:receive_timeout(Tree)),
+ Action = clean(cerl:receive_action(Tree)),
+ cerl:update_c_receive(Tree, Clauses, Timeout, Action);
+ seq ->
+ Arg = clean(cerl:seq_arg(Tree)),
+ Body = clean(cerl:seq_body(Tree)),
+ cerl:update_c_seq(Tree, Arg, Body);
+ 'try' ->
+ Arg = clean(cerl:try_arg(Tree)),
+ Body = clean(cerl:try_body(Tree)),
+ Handler = clean(cerl:try_handler(Tree)),
+ Vs = cerl:try_vars(Tree),
+ Evs = cerl:try_evars(Tree),
+ Try = cerl:update_c_try(Tree, Arg, Vs, Body, Evs, Handler),
+ Try;
+ tuple ->
+ Elements = clean_list(cerl:tuple_es(Tree)),
+ cerl:update_c_tuple_skel(Tree, Elements);
+ map ->
+ Arg = clean(cerl:map_arg(Tree)),
+ Entries = clean_map_pairs(cerl:map_es(Tree)),
+ cerl:update_c_map(Tree, Arg, Entries);
+ values ->
+ Elements = clean_list(cerl:values_es(Tree)),
+ cerl:update_c_values(Tree, Elements);
+ var ->
+ Tree
+ end.
+
+clean_letrec(Tree) ->
+ case lists:member(letrec_goto, cerl:get_ann(Tree)) of
+ true ->
+ %% This is a restricted form of letrec used to allow rewriting
+ %% pattern matching without duplicating code. When a letrec is
+ %% used in this way, Dialyzer will not be able to infer much
+ %% type information, so we will need to eliminate the letrec.
+ [{_Name, Fun}] = cerl:letrec_defs(Tree),
+ FunBody = cerl:fun_body(Fun),
+ FunBody1 = clean(FunBody),
+ Body = clean(cerl:letrec_body(Tree)),
+ case dialyzer_ignore(Body) of
+ true ->
+ %% The body of the letrec directly transfer controls to
+ %% defined function in the letrec. We only need to keep
+ %% the body of that function. (This is is the code for
+ %% a receive construct.)
+ FunBody1;
+ false ->
+ %% The body is non-trivial. Here is an example:
+ %%
+ %% letrec 'more_matching'/0 =
+ %% fun () ->
+ %% case CaseExpr of . . . end
+ %% end
+ %% in case CaseExpr of
+ %% <<..., Tail>> ->
+ %% case Tail of
+ %% <<...>> -> . . .
+ %% _ -> apply 'more_matching'/0()
+ %% end
+ %% _ -> apply 'more_matching'/0()
+ %% end
+ %%
+ %% The clauses that invoke `apply` are marked with
+ %% a `dialyzer_ignore` annotation to indicate that
+ %% Dialyzer should ignore them.
+ %%
+ %% The example is translated like this:
+ %%
+ %% case primop:dialyzer_unknown() of
+ %% 'a' ->
+ %% case Var of
+ %% <<..., Tail>> ->
+ %% case Tail of
+ %% <<...>> -> . . .
+ %% end
+ %% end
+ %% 'b' ->
+ %% %% Body of more_matching/0.
+ %% case Var of . . . end
+ %% end
+ %%
+ PrimopUnknown = cerl:c_primop(cerl:abstract(dialyzer_unknown), []),
+ Clauses = [cerl:c_clause([cerl:abstract(a)], Body),
+ cerl:c_clause([cerl:abstract(b)], FunBody1)],
+ cerl:c_case(PrimopUnknown, Clauses)
+ end;
+ false ->
+ %% This is a plain letrec. (Originating from a list or binary comprehension.)
+ Defs = clean_defs(cerl:letrec_defs(Tree)),
+ Body = clean(cerl:letrec_body(Tree)),
+ cerl:update_c_letrec(Tree, Defs, Body)
+ end.
+
+clean_defs(Defs) ->
+ [{Name, clean(Fun)} || {Name, Fun} <- Defs].
+
+clean_clauses([Clause|Tail]) ->
+ case clean_clause(Clause) of
+ ignore ->
+ %% The clause is either annotated with `dialyzer_ignore` or its
+ %% body is primop that raises an exception.
+ clean_clauses(Tail);
+ Clause1 ->
+ Tail1 = clean_clauses(Tail),
+ [Clause1|Tail1]
+ end;
+clean_clauses([]) ->
+ [].
+
+clean_clause(Clause) ->
+ Body = cerl:clause_body(Clause),
+ case dialyzer_ignore(Clause) orelse is_raising_body(Body) of
+ true ->
+ ignore;
+ false ->
+ G = clean(cerl:clause_guard(Clause)),
+ Body1 = clean(Body),
+ Pats = cerl:clause_pats(Clause),
+ cerl:update_c_clause(Clause, Pats, G, Body1)
+ end.
+
+is_raising_body(Body) ->
+ case cerl:type(Body) of
+ primop ->
+ case cerl:atom_val(cerl:primop_name(Body)) of
+ match_fail -> true;
+ raise -> true;
+ _ -> false
+ end;
+ _ ->
+ false
+ end.
+
+clean_list(Trees) ->
+ [clean(Tree) || Tree <- Trees].
+
+clean_map_pairs([Pair|Pairs]) ->
+ Key = clean(cerl:map_pair_key(Pair)),
+ Val = clean(cerl:map_pair_val(Pair)),
+ Pairs1 = clean_map_pairs(Pairs),
+ Op = cerl:map_pair_op(Pair),
+ Pair1 = cerl:update_c_map_pair(Pair, Op, Key, Val),
+ [Pair1|Pairs1];
+clean_map_pairs([]) ->
+ [].
+
+dialyzer_ignore(Tree) ->
+ lists:member(dialyzer_ignore, cerl:get_ann(Tree)).
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 55c77814f8..cff3981393 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -298,15 +298,36 @@ traverse(Tree, Map, State) ->
module ->
handle_module(Tree, Map, State);
primop ->
- Type =
- case cerl:atom_val(cerl:primop_name(Tree)) of
- match_fail -> t_none();
- raise -> t_none();
- bs_init_writable -> t_from_term(<<>>);
- build_stacktrace -> erl_bif_types:type(erlang, build_stacktrace, 0);
- Other -> erlang:error({'Unsupported primop', Other})
- end,
- {State, Map, Type};
+ case cerl:atom_val(cerl:primop_name(Tree)) of
+ match_fail ->
+ {State, Map, t_none()};
+ raise ->
+ {State, Map, t_none()};
+ bs_init_writable ->
+ {State, Map, t_from_term(<<>>)};
+ build_stacktrace ->
+ {State, Map, erl_bif_types:type(erlang, build_stacktrace, 0)};
+ dialyzer_unknown ->
+ {State, Map, t_any()};
+ recv_peek_message ->
+ {State, Map, t_product([t_boolean(), t_any()])};
+ recv_wait_timeout ->
+ [Arg] = cerl:primop_args(Tree),
+ {State1, Map1, TimeoutType} = traverse(Arg, Map, State),
+ Opaques = State1#state.opaques,
+ case t_is_atom(TimeoutType, Opaques) andalso
+ t_atom_vals(TimeoutType, Opaques) =:= ['infinity'] of
+ true ->
+ {State1, Map1, t_boolean()};
+ false ->
+ {State1, Map1, t_boolean()}
+ end;
+ remove_message ->
+ {State, Map, t_any()};
+ timeout ->
+ {State, Map, t_any()};
+ Other -> erlang:error({'Unsupported primop', Other})
+ end;
'receive' ->
handle_receive(Tree, Map, State);
seq ->
@@ -967,7 +988,7 @@ handle_call(Tree, Map, State) ->
handle_case(Tree, Map, State) ->
Arg = cerl:case_arg(Tree),
- Clauses = filter_match_fail(cerl:case_clauses(Tree)),
+ Clauses = cerl:case_clauses(Tree),
{State1, Map1, ArgType} = SMA = traverse(Arg, Map, State),
case t_is_none_or_unit(ArgType) of
true -> SMA;
@@ -1084,7 +1105,7 @@ handle_module(Tree, Map, State) ->
%%----------------------------------------
handle_receive(Tree, Map, State) ->
- Clauses = filter_match_fail(cerl:receive_clauses(Tree)),
+ Clauses = cerl:receive_clauses(Tree),
Timeout = cerl:receive_timeout(Tree),
State1 =
case is_race_analysis_enabled(State) of
@@ -3019,24 +3040,6 @@ is_lc_simple_list(Tree, TreeType, State) ->
andalso t_is_list(TreeType)
andalso t_is_simple(t_list_elements(TreeType, Opaques), State).
-filter_match_fail([Clause] = Cls) ->
- Body = cerl:clause_body(Clause),
- case cerl:type(Body) of
- primop ->
- case cerl:atom_val(cerl:primop_name(Body)) of
- match_fail -> [];
- raise -> [];
- _ -> Cls
- end;
- _ -> Cls
- end;
-filter_match_fail([H|T]) ->
- [H|filter_match_fail(T)];
-filter_match_fail([]) ->
- %% This can actually happen, for example in
- %% receive after 1 -> ok end
- [].
-
%%% ===========================================================================
%%%
%%% The State.
@@ -3819,7 +3822,20 @@ find_terminals(Tree) ->
%% We cannot make assumptions. Say that both are true.
{true, true}
end;
- 'case' -> find_terminals_list(cerl:case_clauses(Tree));
+ 'case' ->
+ case cerl:case_clauses(Tree) of
+ [] ->
+ case lists:member(receive_timeout, cerl:get_ann(Tree)) of
+ true ->
+ %% Handle a never ending receive without any
+ %% clauses specially. (Not sure why.)
+ {false, true};
+ false ->
+ {false, false}
+ end;
+ [_|_] ->
+ find_terminals_list(cerl:case_clauses(Tree))
+ end;
'catch' -> find_terminals(cerl:catch_body(Tree));
clause -> find_terminals(cerl:clause_body(Tree));
cons -> {false, true};
diff --git a/lib/dialyzer/src/dialyzer_dep.erl b/lib/dialyzer/src/dialyzer_dep.erl
index 36cdc0876c..d4d1feeae0 100644
--- a/lib/dialyzer/src/dialyzer_dep.erl
+++ b/lib/dialyzer/src/dialyzer_dep.erl
@@ -87,7 +87,8 @@ traverse(Tree, Out, State, CurrentFun) ->
true ->
%% Op is a variable and should not be marked as escaping
%% based on its use.
- OpFuns = case map__lookup(cerl_trees:get_label(Op), Out) of
+ OpLabel = cerl_trees:get_label(Op),
+ OpFuns = case map__lookup(OpLabel, Out) of
none -> output(none);
{value, OF} -> OF
end,
@@ -96,7 +97,13 @@ traverse(Tree, Out, State, CurrentFun) ->
State4 = state__add_deps(CurrentFun, OpFuns, State3),
State5 = state__store_callsite(cerl_trees:get_label(Tree),
OpFuns, length(Args), State4),
- {output(set__singleton(external)), State5}
+ case state__get_rvals(OpLabel, State5) of
+ 1 ->
+ {output(set__singleton(external)), State5};
+ NumRvals ->
+ List = lists:duplicate(NumRvals, output(set__singleton(external))),
+ {output(List), State5}
+ end
end;
binary ->
{output(none), State};
@@ -137,9 +144,12 @@ traverse(Tree, Out, State, CurrentFun) ->
Vars = cerl:let_vars(Tree),
Arg = cerl:let_arg(Tree),
Body = cerl:let_body(Tree),
- {ArgFuns, State1} = traverse(Arg, Out, State, CurrentFun),
+ OldNumRvals = state__num_rvals(State),
+ State1 = state__store_num_rvals(length(Vars), State),
+ {ArgFuns, State2} = traverse(Arg, Out, State1, CurrentFun),
Out1 = bind_list(Vars, ArgFuns, Out),
- traverse(Body, Out1, State1, CurrentFun);
+ State3 = state__store_num_rvals(OldNumRvals, State2),
+ traverse(Body, Out1, State3, CurrentFun);
letrec ->
Defs = cerl:letrec_defs(Tree),
Body = cerl:letrec_body(Tree),
@@ -147,14 +157,15 @@ traverse(Tree, Out, State, CurrentFun) ->
state__add_letrecs(cerl_trees:get_label(Var), cerl_trees:get_label(Fun), Acc)
end, State, Defs),
Out1 = bind_defs(Defs, Out),
- State2 = traverse_defs(Defs, Out1, State1, CurrentFun),
+ NumRvals = state__num_rvals(State1),
+ State2 = traverse_defs(Defs, Out1, State1, CurrentFun, NumRvals),
traverse(Body, Out1, State2, CurrentFun);
literal ->
{output(none), State};
module ->
Defs = cerl:module_defs(Tree),
Out1 = bind_defs(Defs, Out),
- State1 = traverse_defs(Defs, Out1, State, CurrentFun),
+ State1 = traverse_defs(Defs, Out1, State, CurrentFun, 1),
{output(none), State1};
primop ->
Args = cerl:primop_args(Tree),
@@ -197,8 +208,12 @@ traverse(Tree, Out, State, CurrentFun) ->
Val = cerl:map_pair_val(Tree),
{List, State1} = traverse_list([Key,Val], Out, State, CurrentFun),
{merge_outs(List), State1};
- values ->
- traverse_list(cerl:values_es(Tree), Out, State, CurrentFun);
+ values ->
+ OldNumRvals = state__num_rvals(State),
+ State1 = state__store_num_rvals(1, State),
+ {List, State2} = traverse_list(cerl:values_es(Tree), Out, State1, CurrentFun),
+ State3 = state__store_num_rvals(OldNumRvals, State2),
+ {List, State3};
var ->
case map__lookup(cerl_trees:get_label(Tree), Out) of
none -> {output(none), State};
@@ -223,14 +238,15 @@ traverse_list([Tree|Left], Out, State, CurrentFun, Acc) ->
traverse_list([], _Out, State, _CurrentFun, Acc) ->
{output(lists:reverse(Acc)), State}.
-traverse_defs([{_, Fun}|Left], Out, State, CurrentFun) ->
- {_, State1} = traverse(Fun, Out, State, CurrentFun),
- traverse_defs(Left, Out, State1, CurrentFun);
-traverse_defs([], _Out, State, _CurrentFun) ->
+traverse_defs([{_, Fun}|Left], Out, State, CurrentFun, NumRvals) ->
+ State1 = state__store_num_rvals(NumRvals, State),
+ {_, State2} = traverse(Fun, Out, State1, CurrentFun),
+ traverse_defs(Left, Out, State2, CurrentFun, NumRvals);
+traverse_defs([], _Out, State, _CurrentFun, _NumRvals) ->
State.
traverse_clauses(Clauses, ArgFuns, Out, State, CurrentFun) ->
- case filter_match_fail(Clauses) of
+ case Clauses of
[] ->
%% Can happen for example with receives used as timouts.
{output(none), State};
@@ -249,24 +265,6 @@ traverse_clauses([Clause|Left], ArgFuns, Out, State, CurrentFun, Acc) ->
traverse_clauses([], _ArgFuns, _Out, State, _CurrentFun, Acc) ->
{merge_outs(Acc), State}.
-filter_match_fail([Clause]) ->
- Body = cerl:clause_body(Clause),
- case cerl:type(Body) of
- primop ->
- case cerl:atom_val(cerl:primop_name(Body)) of
- match_fail -> [];
- raise -> [];
- _ -> [Clause]
- end;
- _ -> [Clause]
- end;
-filter_match_fail([H|T]) ->
- [H|filter_match_fail(T)];
-filter_match_fail([]) ->
- %% This can actually happen, for example in
- %% receive after 1 -> ok end
- [].
-
remote_call(Tree, ArgFuns, State) ->
M = cerl:call_module(Tree),
F = cerl:call_name(Tree),
@@ -483,12 +481,16 @@ all_vars(Tree, AccIn) ->
%%
-type local_set() :: 'none' | #set{}.
+-type rvals() :: #{label() => non_neg_integer()}.
-record(state, {deps :: deps(),
esc :: local_set(),
calls :: calls(),
arities :: dict:dict(label() | 'top', arity()),
- letrecs :: letrecs()}).
+ letrecs :: letrecs(),
+ num_rvals = 1 :: non_neg_integer(),
+ rvals = #{} :: rvals()
+ }).
state__new(Tree) ->
Exports = set__from_list([X || X <- cerl:module_exports(Tree)]),
@@ -526,8 +528,11 @@ state__add_deps(From, #output{type = single, content = To},
%% io:format("Adding deps from ~w to ~w\n", [From, set__to_ordsets(To)]),
State#state{deps = map__add(From, To, Map)}.
-state__add_letrecs(Var, Fun, #state{letrecs = Map} = State) ->
- State#state{letrecs = map__store(Var, Fun, Map)}.
+state__add_letrecs(Var, Fun, #state{letrecs = Map,
+ num_rvals = NumRvals,
+ rvals = Rvals} = State) ->
+ State#state{letrecs = map__store(Var, Fun, Map),
+ rvals = Rvals#{Var => NumRvals}}.
state__deps(#state{deps = Deps}) ->
Deps.
@@ -539,6 +544,10 @@ state__add_esc(#output{content = none}, State) ->
State;
state__add_esc(#output{type = single, content = Set},
#state{esc = Esc} = State) ->
+ State#state{esc = set__union(Set, Esc)};
+state__add_esc(#output{type = list, content = [H|T]},
+ #state{esc = Esc} = State) ->
+ #output{type = single, content = Set} = merge_outs(T, H),
State#state{esc = set__union(Set, Esc)}.
state__esc(#state{esc = Esc}) ->
@@ -559,6 +568,19 @@ state__store_callsite(From, To, CallArity,
state__calls(#state{calls = Calls}) ->
Calls.
+state__store_num_rvals(NumRval, State) ->
+ State#state{num_rvals = NumRval}.
+
+state__num_rvals(#state{num_rvals = NumRvals}) ->
+ NumRvals.
+
+state__get_rvals(FunLabel, #state{rvals = Rvals}) ->
+ case Rvals of
+ #{FunLabel := NumRvals} -> NumRvals;
+ #{} -> 1
+ end.
+
+
%%------------------------------------------------------------
%% A test function. Not part of the intended interface.
%%
diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl
index f47d90b91f..dd0d1b8979 100644
--- a/lib/dialyzer/src/dialyzer_gui_wx.erl
+++ b/lib/dialyzer/src/dialyzer_gui_wx.erl
@@ -498,10 +498,10 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt,
end,
ExplanationPid = spawn_link(Fun),
gui_loop(State#gui_state{expl_pid = ExplanationPid});
- {BackendPid, done, NewPlt, NewDocPlt} ->
+ {BackendPid, done, _NewPlt, NewDocPlt} ->
message(State, "Analysis done"),
- dialyzer_plt:delete(NewPlt),
config_gui_stop(State),
+ dialyzer_plt:delete(State#gui_state.doc_plt),
gui_loop(State#gui_state{doc_plt = NewDocPlt});
{'EXIT', BackendPid, {error, Reason}} ->
free_editor(State, ?DIALYZER_ERROR_TITLE, Reason),
diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl
index 2af4534396..fe85fa81de 100644
--- a/lib/dialyzer/src/dialyzer_plt.erl
+++ b/lib/dialyzer/src/dialyzer_plt.erl
@@ -539,7 +539,7 @@ compute_md5_from_file(File) ->
Filtered = [[ID, Chunk] || {ID, Chunk} <- Chunks, ID =/= "CInf", ID =/= "Docs"],
erlang:md5(lists:sort(Filtered));
{error, beam_lib, {file_error, _, enoent}} ->
- Msg = io_lib:format("Not a regular file: ~ts\n", [File]),
+ Msg = io_lib:format("File not found: ~ts\n", [File]),
throw({dialyzer_error, Msg});
{error, beam_lib, _} ->
Msg = io_lib:format("Could not compute MD5 for .beam: ~ts\n", [File]),
diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl
index dede475f98..5f40f80ae7 100644
--- a/lib/dialyzer/src/dialyzer_typesig.erl
+++ b/lib/dialyzer/src/dialyzer_typesig.erl
@@ -296,7 +296,7 @@ traverse(Tree, DefinedVars, State) ->
{State4, mk_var(Tree)};
'case' ->
Arg = cerl:case_arg(Tree),
- Clauses = filter_match_fail(cerl:case_clauses(Tree)),
+ Clauses = cerl:case_clauses(Tree),
{State1, ArgVar} = traverse(Arg, DefinedVars, State),
handle_clauses(Clauses, mk_var(Tree), ArgVar, DefinedVars, State1);
call ->
@@ -423,10 +423,31 @@ traverse(Tree, DefinedVars, State) ->
Type = erl_bif_types:type(erlang, build_stacktrace, 0),
State1 = state__store_conj(V, sub, Type, State),
{State1, V};
+ dialyzer_unknown ->
+ %% See dialyzer_clean_core:clean_letrec/1.
+ {State, mk_var(Tree)};
+ recv_peek_message ->
+ {State1, Vars} = state__mk_vars(2, State),
+ {State1, t_product(Vars)};
+ recv_wait_timeout ->
+ [Timeout] = cerl:primop_args(Tree),
+ case cerl:is_c_atom(Timeout) andalso
+ cerl:atom_val(Timeout) =:= infinity of
+ true ->
+ {State, t_none()};
+ false ->
+ {State1, TimeoutVar} = traverse(Timeout, DefinedVars, State),
+ State2 = state__store_conj(TimeoutVar, sub, t_timeout(), State1),
+ {State2, mk_var(Tree)}
+ end;
+ remove_message ->
+ {State, t_any()};
+ timeout ->
+ {State, t_any()};
Other -> erlang:error({'Unsupported primop', Other})
end;
'receive' ->
- Clauses = filter_match_fail(cerl:receive_clauses(Tree)),
+ Clauses = cerl:receive_clauses(Tree),
Timeout = cerl:receive_timeout(Tree),
case (cerl:is_c_atom(Timeout) andalso
(cerl:atom_val(Timeout) =:= infinity)) of
@@ -829,24 +850,6 @@ get_plt_constr(MFA, Dst, ArgVars, State) ->
get_contract_return(C, ArgTypes) ->
dialyzer_contracts:get_contract_return(C, ArgTypes).
-filter_match_fail([Clause] = Cls) ->
- Body = cerl:clause_body(Clause),
- case cerl:type(Body) of
- primop ->
- case cerl:atom_val(cerl:primop_name(Body)) of
- match_fail -> [];
- raise -> [];
- _ -> Cls
- end;
- _ -> Cls
- end;
-filter_match_fail([H|T]) ->
- [H|filter_match_fail(T)];
-filter_match_fail([]) ->
- %% This can actually happen, for example in
- %% receive after 1 -> ok end
- [].
-
%% If there is a significant number of clauses, we cannot apply the
%% list subtraction scheme since it causes the analysis to be too
%% slow. Typically, this only affects automatically generated files.
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index 245c099fef..f679f146cc 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -99,7 +99,7 @@ get_core_from_src(File, Opts) ->
case compile:noenv_file(File, Opts ++ src_compiler_opts()) of
error -> {error, []};
{error, Errors, _} -> {error, format_errors(Errors)};
- {ok, _, Core} -> {ok, Core}
+ {ok, _, Core} -> {ok, dialyzer_clean_core:clean(Core)}
end.
-type get_core_from_beam_ret() :: {'ok', cerl:c_module()} | {'error', string()}.
@@ -116,7 +116,7 @@ get_core_from_beam(File, Opts) ->
{ok, {Module, [{debug_info, {debug_info_v1, Backend, Metadata}}]}} ->
case Backend:debug_info(core_v1, Module, Metadata, Opts ++ src_compiler_opts()) of
{ok, Core} ->
- {ok, Core};
+ {ok, dialyzer_clean_core:clean(Core)};
{error, _} ->
{error, " Could not get Core Erlang code for: " ++ File ++ "\n"}
end;
diff --git a/lib/dialyzer/test/map_SUITE_data/results/maps_remove b/lib/dialyzer/test/map_SUITE_data/results/maps_remove
new file mode 100644
index 0000000000..32e43466b0
--- /dev/null
+++ b/lib/dialyzer/test/map_SUITE_data/results/maps_remove
@@ -0,0 +1,4 @@
+
+maps_remove.erl:22: Function get/2 has no local return
+maps_remove.erl:23: The call maps:get(K::'a',M::#{}) will never return since the success typing arguments are (any(),map())
+maps_remove.erl:7: Function t1/0 has no local return
diff --git a/lib/dialyzer/test/map_SUITE_data/src/maps_remove.erl b/lib/dialyzer/test/map_SUITE_data/src/maps_remove.erl
new file mode 100644
index 0000000000..b913e389f8
--- /dev/null
+++ b/lib/dialyzer/test/map_SUITE_data/src/maps_remove.erl
@@ -0,0 +1,23 @@
+%% ERL-1002, maps:remove
+
+-module(maps_remove).
+
+-export([t1/0]).
+
+t1() ->
+ A = new(),
+ B = put(a, 1, A),
+ C = remove(a, B),
+ get(a, C).
+
+new() ->
+ maps:new().
+
+put(K, V, M) ->
+ maps:put(K, V, M).
+
+remove(K, M) ->
+ maps:remove(K, M).
+
+get(K, M) ->
+ maps:get(K, M).
diff --git a/lib/dialyzer/test/plt_SUITE.erl b/lib/dialyzer/test/plt_SUITE.erl
index 680f5b5088..0c7078b9bf 100644
--- a/lib/dialyzer/test/plt_SUITE.erl
+++ b/lib/dialyzer/test/plt_SUITE.erl
@@ -9,14 +9,16 @@
-export([suite/0, all/0, build_plt/1, beam_tests/1, update_plt/1,
local_fun_same_as_callback/1,
remove_plt/1, run_plt_check/1, run_succ_typings/1,
- bad_dialyzer_attr/1, merge_plts/1, bad_record_type/1]).
+ bad_dialyzer_attr/1, merge_plts/1, bad_record_type/1,
+ letrec_rvals/1]).
suite() ->
[{timetrap, ?plt_timeout}].
all() -> [build_plt, beam_tests, update_plt, run_plt_check,
remove_plt, run_succ_typings, local_fun_same_as_callback,
- bad_dialyzer_attr, merge_plts, bad_record_type].
+ bad_dialyzer_attr, merge_plts, bad_record_type,
+ letrec_rvals].
build_plt(Config) ->
OutDir = ?config(priv_dir, Config),
@@ -222,7 +224,7 @@ local_fun_same_as_callback(Config) when is_list(Config) ->
{files, [TestBeam]},
{init_plt, Plt}] ++ Opts),
ok.
-
+
%%% [James Fish:]
%%% Dialyzer always asserts that files and directories passed in its
%%% options exist. Therefore it is not possible to remove a beam/module
@@ -395,6 +397,26 @@ bad_record_type(Config) ->
true = P > 0,
ok.
+letrec_rvals(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ Plt = filename:join(PrivDir, "letrec_rvals.plt"),
+ Prog = <<"
+-module(letrec_rvals).
+
+-export([demo_fun/1]).
+
+demo_fun(_Arg) ->
+ case ok of
+ _ ->
+ _Res = _Arg,
+ [ ok || _ <- [] ]
+ end,
+ _Res.
+ ">>,
+ {ok, BeamFile} = compile(Config, Prog, letrec_rvals, []),
+ [] = run_dialyzer(plt_build, [BeamFile], [{output_plt, Plt}]),
+ ok.
+
erlang_beam() ->
case code:where_is_file("erlang.beam") of
non_existing ->
diff --git a/lib/dialyzer/test/small_SUITE_data/results/no_match b/lib/dialyzer/test/small_SUITE_data/results/no_match
index 9760b980a2..644310a973 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/no_match
+++ b/lib/dialyzer/test/small_SUITE_data/results/no_match
@@ -1,4 +1,4 @@
-no_match.erl:5: Function t1/1 has no clauses that will ever match
-no_match.erl:7: Function t2/1 has no clauses that will ever match
+no_match.erl:5: Function t1/1 has no local return
+no_match.erl:7: Function t2/1 has no local return
no_match.erl:9: Function t3/1 has no local return
diff --git a/lib/dialyzer/test/small_SUITE_data/results/stacktrace b/lib/dialyzer/test/small_SUITE_data/results/stacktrace
index fd60881953..5126a88297 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/stacktrace
+++ b/lib/dialyzer/test/small_SUITE_data/results/stacktrace
@@ -1,5 +1,5 @@
stacktrace.erl:11: The pattern {'a', 'b'} can never match the type [{atom(),atom(),[any()] | byte(),[{'file',string()} | {'line',pos_integer()}]}]
stacktrace.erl:19: The pattern ['a', 'b'] can never match the type [{atom(),atom(),[any()] | byte(),[{'file',string()} | {'line',pos_integer()}]}]
-stacktrace.erl:44: The pattern {'a', 'b'} can never match the type [{atom(),atom(),[any()] | byte(),[{'file',string()} | {'line',pos_integer()}]}]
-stacktrace.erl:53: The pattern ['a', 'b'] can never match the type [{atom(),atom(),[any()] | byte(),[{'file',string()} | {'line',pos_integer()}]}]
+stacktrace.erl:43: The pattern {'a', 'b'} can never match the type [{atom(),atom(),[any()] | byte(),[{'file',string()} | {'line',pos_integer()}]}]
+stacktrace.erl:51: The pattern ['a', 'b'] can never match the type [{atom(),atom(),[any()] | byte(),[{'file',string()} | {'line',pos_integer()}]}]
diff --git a/lib/dialyzer/test/small_SUITE_data/src/stacktrace.erl b/lib/dialyzer/test/small_SUITE_data/src/stacktrace.erl
index de79e710e9..b2a0c04554 100644
--- a/lib/dialyzer/test/small_SUITE_data/src/stacktrace.erl
+++ b/lib/dialyzer/test/small_SUITE_data/src/stacktrace.erl
@@ -39,8 +39,7 @@ t4() ->
s1() ->
try foo:bar()
catch
- E:P ->
- S = erlang:get_stacktrace(),
+ E:P:S ->
{a,b} = S, % can never match
{E, P}
end.
@@ -48,8 +47,7 @@ s1() ->
s2() ->
try foo:bar()
catch
- E:P ->
- S = erlang:get_stacktrace(),
+ E:P:S ->
[a,b] = S, % can never match
{E, P}
end.
@@ -57,8 +55,7 @@ s2() ->
s3() ->
try foo:bar()
catch
- E:P ->
- S = erlang:get_stacktrace(),
+ E:P:S ->
[{m,f,[],[]}] = S,
{E, P}
end.
@@ -66,8 +63,7 @@ s3() ->
s4() ->
try foo:bar()
catch
- E:P ->
- S = erlang:get_stacktrace(),
+ E:P:S ->
[{m,f,1,[{file,"tjo"},{line,95}]}] = S,
{E, P}
end.
diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk
index ee680f3bcf..803d121b53 100644
--- a/lib/dialyzer/vsn.mk
+++ b/lib/dialyzer/vsn.mk
@@ -1 +1 @@
-DIALYZER_VSN = 4.1.1
+DIALYZER_VSN = 4.3.1
diff --git a/lib/diameter/Makefile b/lib/diameter/Makefile
index a0195a0988..a25baeb929 100644
--- a/lib/diameter/Makefile
+++ b/lib/diameter/Makefile
@@ -27,7 +27,6 @@ SPECIAL_TARGETS =
include $(ERL_TOP)/make/otp_subdir.mk
-info:
- @echo "APP_VSN = $(APP_VSN)"
+DIA_PLT_APPS=ssl runtime_tools syntax_tools
-.PHONY: info
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/diameter/doc/src/Makefile b/lib/diameter/doc/src/Makefile
index 7c7fbeafef..3553b68510 100644
--- a/lib/diameter/doc/src/Makefile
+++ b/lib/diameter/doc/src/Makefile
@@ -24,8 +24,6 @@ include ../../vsn.mk
VSN = $(DIAMETER_VSN)
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
@@ -35,118 +33,29 @@ XML_REF_FILES = $(XML_REF1_FILES) $(XML_REF3_FILES) $(XML_REF4_FILES)
XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) \
$(XML_REF_FILES) \
- $(XML_PART_FILES) $(XML_CHAPTER_FILES) \
- $(XML_EXTRA_FILES)
-
-INFO_FILE = ../../info
+ $(XML_PART_FILES) $(XML_CHAPTER_FILES)
-MAN1_FILES = $(XML_REF1_FILES:%.xml=$(MAN1DIR)/%.1)
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN4_FILES = $(XML_REF4_FILES:%.xml=$(MAN4DIR)/%.4)
+XML_GEN_FILES = $(XMLDIR)/seehere.ent $(patsubst %.ent,$(XMLDIR)/%.ent,$(XML_EXTRA_FILES))
-PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+EXTRA_FILES=depend.mk $(XMLDIR)/seehere.ent
-STANDARD_DIR = ../standard
+NO_CHUNKS = diameter_app.xml diameter_transport.xml
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
+include $(ERL_TOP)/make/doc.mk
-docs: pdf html man
+$(XMLDIR)/seehere.ent: Makefile seealso.ent
+ $(gen_verbose) sed -f seehere.sed seealso.ent > $@
+$(XMLDIR)/%.ent: %.ent
+ $(gen_verbose) cp $< $@
ldocs: local_docs $(INDEX_TARGET)
-$(PDF_FILE): $(XML_FILES)
-
-pdf: $(PDF_FILE)
-
-html: gifs $(HTMLDIR)/index.html
-
-clean clean_docs: clean_pdf clean_html clean_man
- rm -f errs core *~
- rm -f depend.mk seehere.ent
-
-clean_pdf:
- rm -f $(PDFDIR)/*
-
-clean_man:
- rm -f $(MAN1DIR)/* $(MAN3DIR)/* $(MAN4DIR)/*
-
-clean_html:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-man: $(MAN1_FILES) $(MAN3_FILES) $(MAN4_FILES)
-
-$(INDEX_TARGET): $(INDEX_SRC) $(APP_FILE)
+depend.mk: depend.sed Makefile $(XMLDIR)/seehere.ent $(XMLDIR)/seealso.ent $(XML_REF_FILES) $(XML_CHAPTER_FILES)
$(gen_verbose) \
- sed -e 's/%VSN%/$(VSN)/; \
- s/%ERLANG_SITE%/www\.erlang\.se\//; \
- s/%UP_ONE_LEVEL%/..\/..\/..\/doc\/index.html/; \
- s/%OFF_PRINT%/pdf\/diameter-$(VSN).pdf/' $< > $@
-
-depend: depend.mk
-
-debug opt:
-
-info:
- @echo ""
- @echo "INDEX_FILE = $(INDEX_FILE)"
- @echo "INDEX_SRC = $(INDEX_SRC)"
- @echo "INDEX_TARGET = $(INDEX_TARGET)"
- @echo ""
- @echo "XML_APPLICATION_FILES = $(XML_APPLICATION_FILES)"
- @echo "XML_PART_FILES = $(XML_PART_FILES)"
- @echo "XML_REF1_FILES = $(XML_REF1_FILES)"
- @echo "XML_REF3_FILES = $(XML_REF3_FILES)"
- @echo "XML_REF4_FILES = $(XML_REF4_FILES)"
- @echo "XML_CHAPTER_FILES = $(XML_CHAPTER_FILES)"
- @echo ""
- @echo "GIF_FILES = $(GIF_FILES)"
- @echo ""
- @echo "MAN1_FILES = $(MAN1_FILES)"
- @echo "MAN3_FILES = $(MAN3_FILES)"
- @echo "MAN4_FILES = $(MAN4_FILES)"
- @echo ""
- @echo "DEFAULT_HTML_FILES = $(DEFAULT_HTML_FILES)"
- @echo "DEFAULT_GIF_FILES = $(DEFAULT_GIF_FILES)"
- @echo ""
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: $(LOCAL)docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man4"
- $(INSTALL_DATA) $(PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(HTMLDIR)/*.* "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DATA) $(MAN1_FILES) "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DATA) $(MAN3_FILES) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN4_FILES) "$(RELEASE_PATH)/man/man4"
- [ -z "$(LOCAL)" ] || cp -r $(HTMLDIR)/js "$(RELSYSDIR)/doc/html"
- echo $(LOCAL)
-
-release_spec:
-
-depend.mk: depend.sed Makefile seealso.ent \
- $(XML_REF_FILES) $(XML_CHAPTER_FILES)
- $(gen_verbose)
- $(V_at) \
- sed -f seehere.sed seealso.ent > seehere.ent
- $(V_at) \
(for f in $(XML_REF_FILES) $(XML_CHAPTER_FILES); do \
sed -f $< $$f | sed "s@%FILE%@`basename $$f .xml`@g"; \
done) \
@@ -154,7 +63,4 @@ depend.mk: depend.sed Makefile seealso.ent \
-include depend.mk
-.PHONY: clean clean_html clean_man clean_pdf \
- depend debug opt info \
- docs gifs html ldocs man pdf \
- release_docs_spec release_spec
+.PHONY: depend ldocs
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index aa4eb6ad45..5536dd9860 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd" [
<!ENTITY spawn_opt2
- '<seealso marker="erts:erlang#spawn_opt-2">erlang:spawn_opt/2</seealso>'>
+ '<seemfa marker="erts:erlang#spawn_opt/2">erlang:spawn_opt/2</seemfa>'>
<!ENTITY spawn_opt5
- '<seealso marker="erts:erlang#spawn_opt-5">erlang:spawn_opt/5</seealso>'>
+ '<seemfa marker="erts:erlang#spawn_opt/5">erlang:spawn_opt/5</seemfa>'>
<!ENTITY nodes
- '<seealso marker="erts:erlang#nodes-0">erlang:nodes/0</seealso>'>
+ '<seemfa marker="erts:erlang#nodes/0">erlang:nodes/0</seemfa>'>
<!ENTITY make_ref
- '<seealso marker="erts:erlang#make_ref-0">erlang:make_ref/0</seealso>'>
+ '<seemfa marker="erts:erlang#make_ref/0">erlang:make_ref/0</seemfa>'>
<!ENTITY transport_module
- '<seealso marker="diameter_transport">transport module</seealso>'>
+ '<seeerl marker="diameter_transport">transport module</seeerl>'>
<!ENTITY dictionary
- '<seealso marker="diameter_dict">dictionary</seealso>'>
+ '<seefile marker="diameter_dict">dictionary</seefile>'>
<!ENTITY % also SYSTEM "seealso.ent" >
<!ENTITY % here SYSTEM "seehere.ent" >
%also;
diff --git a/lib/diameter/doc/src/diameter_app.xml b/lib/diameter/doc/src/diameter_app.xml
index 3e8defd3ed..b4fdd2516e 100644
--- a/lib/diameter/doc/src/diameter_app.xml
+++ b/lib/diameter/doc/src/diameter_app.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd" [
- <!ENTITY message '<seealso marker="#message">message()</seealso>'>
+ <!ENTITY message '<seeerl marker="#message">message()</seeerl>'>
<!ENTITY dict
- '<seealso marker="diameter_dict#MESSAGE_RECORDS">diameter_dict(4)</seealso>'>
+ '<seefile marker="diameter_dict#MESSAGE_RECORDS">diameter_dict(4)</seefile>'>
<!ENTITY % also SYSTEM "seealso.ent" >
<!ENTITY % here SYSTEM "seehere.ent" >
%also;
diff --git a/lib/diameter/doc/src/diameter_codec.xml b/lib/diameter/doc/src/diameter_codec.xml
index 79bbacc723..b76233f322 100644
--- a/lib/diameter/doc/src/diameter_codec.xml
+++ b/lib/diameter/doc/src/diameter_codec.xml
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd" [
<!ENTITY records
- '<seealso marker="diameter_dict#MESSAGE_RECORDS">diameter_dict(4)</seealso>'>
+ '<seefile marker="diameter_dict#MESSAGE_RECORDS">diameter_dict(4)</seefile>'>
<!ENTITY types
- '<seealso marker="diameter_dict#DATA_TYPES">diameter_dict(4)</seealso>'>
+ '<seefile marker="diameter_dict#DATA_TYPES">diameter_dict(4)</seefile>'>
<!ENTITY decode_format
- '<seealso marker="diameter#decode_format">decode format</seealso>'>
+ '<seeerl marker="diameter#decode_format">decode format</seeerl>'>
<!ENTITY % also SYSTEM "seealso.ent" >
<!ENTITY % here SYSTEM "seehere.ent" >
diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml
index 37d30b709b..a23c22dda9 100644
--- a/lib/diameter/doc/src/diameter_dict.xml
+++ b/lib/diameter/doc/src/diameter_dict.xml
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE fileref SYSTEM "fileref.dtd" [
<!ENTITY format
- '<seealso marker="#FILE_FORMAT">FILE FORMAT</seealso>'>
+ '<seefile marker="#FILE_FORMAT">FILE FORMAT</seefile>'>
<!ENTITY records
- '<seealso marker="#MESSAGE_RECORDS">MESSAGE RECORDS</seealso>'>
+ '<seefile marker="#MESSAGE_RECORDS">MESSAGE RECORDS</seefile>'>
<!ENTITY types
- '<seealso marker="#DATA_TYPES">DATA TYPES</seealso>'>
+ '<seefile marker="#DATA_TYPES">DATA TYPES</seefile>'>
<!ENTITY % also SYSTEM "seealso.ent" >
<!ENTITY % here SYSTEM "seehere.ent" >
%also;
diff --git a/lib/diameter/doc/src/diameter_intro.xml b/lib/diameter/doc/src/diameter_intro.xml
index cb0aa3de2a..02d2100696 100644
--- a/lib/diameter/doc/src/diameter_intro.xml
+++ b/lib/diameter/doc/src/diameter_intro.xml
@@ -50,7 +50,7 @@ This chapter provides a short overview of the application.</p>
<p>
A Diameter node is implemented by configuring a <em>service</em> and
one or more <em>transports</em> using the interface module
-<seealso marker="diameter">diameter</seealso>.
+<seeerl marker="diameter">diameter</seeerl>.
The service configuration defines the Diameter applications to be
supported by the node and, typically, the capabilities that it should
send to remote peers at capabilities exchange upon the establishment
@@ -58,14 +58,14 @@ of transport connections.
A transport is configured on a service and provides protocol-specific
send/receive functionality by way of a transport interface defined by
diameter and implemented by a transport module.
-The diameter application provides two transport modules: <seealso
-marker="diameter_tcp">diameter_tcp</seealso> and <seealso
-marker="diameter_sctp">diameter_sctp</seealso> for transport over TCP
-(using <seealso marker="kernel:gen_tcp">gen_tcp</seealso>) and SCTP
-(using <seealso marker="kernel:gen_sctp">gen_sctp</seealso>) respectively.
+The diameter application provides two transport modules: <seeerl
+marker="diameter_tcp">diameter_tcp</seeerl> and <seeerl
+marker="diameter_sctp">diameter_sctp</seeerl> for transport over TCP
+(using <seeerl marker="kernel:gen_tcp">gen_tcp</seeerl>) and SCTP
+(using <seeerl marker="kernel:gen_sctp">gen_sctp</seeerl>) respectively.
Other transports can be provided by any module that implements
-diameter's <seealso marker="diameter_transport">transport
-interface</seealso>.</p>
+diameter's <seeerl marker="diameter_transport">transport
+interface</seeerl>.</p>
<p>
While a service typically implements a single Diameter node (as
@@ -75,8 +75,8 @@ used to implement more than one Diameter node.</p>
<p>
Each Diameter application defined on a service is configured with a
-callback module that implements the <seealso
-marker="diameter_app">application interface</seealso> through which
+callback module that implements the <seeerl
+marker="diameter_app">application interface</seeerl> through which
diameter communicates the connectivity of remote peers, requests peer
selection for outgoing requests, and communicates the reception of
incoming Diameter request and answer messages.
@@ -89,9 +89,9 @@ Each Diameter application is also configured with a
dictionary module
that provide encode/decode functionality for outgoing/incoming
Diameter messages belonging to the application.
-A dictionary module is generated from a <seealso
-marker="diameter_dict">dictionary file</seealso> using the <seealso
-marker="diameterc">diameterc</seealso> utility.
+A dictionary module is generated from a <seefile
+marker="diameter_dict">dictionary file</seefile> using the <seecom
+marker="diameterc">diameterc</seecom> utility.
Dictionaries for the &the_rfc; Diameter Common Messages, Base
Accounting and Relay applications are provided with the diameter
application.</p>
diff --git a/lib/diameter/doc/src/diameter_make.xml b/lib/diameter/doc/src/diameter_make.xml
index 4a205b9a89..ee8b0b458d 100644
--- a/lib/diameter/doc/src/diameter_make.xml
+++ b/lib/diameter/doc/src/diameter_make.xml
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd" [
<!ENTITY compile_forms2
- '<seealso marker="compiler:compile#forms-2">compile:forms/2</seealso>'>
+ '<seemfa marker="compiler:compile#forms/2">compile:forms/2</seemfa>'>
<!ENTITY filename
- '<seealso marker="kernel:file#type-name">file:name()</seealso>'>
+ '<seetype marker="kernel:file#name">file:name()</seetype>'>
<!ENTITY dictionary
- '<seealso marker="diameter_dict">dictionary file</seealso>'>
+ '<seefile marker="diameter_dict">dictionary file</seefile>'>
<!ENTITY % also SYSTEM "seealso.ent" >
<!ENTITY % here SYSTEM "seehere.ent" >
%also;
diff --git a/lib/diameter/doc/src/diameter_sctp.xml b/lib/diameter/doc/src/diameter_sctp.xml
index 097bfce7d9..bbb9fbab9d 100644
--- a/lib/diameter/doc/src/diameter_sctp.xml
+++ b/lib/diameter/doc/src/diameter_sctp.xml
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd" [
<!ENTITY man_tcp_sender
- '<seealso marker="diameter_tcp#sender">diameter_tcp(3)</seealso>'>
- <!ENTITY gen_sctp '<seealso marker="kernel:gen_sctp">gen_sctp(3)</seealso>'>
+ '<seeerl marker="diameter_tcp#sender">diameter_tcp(3)</seeerl>'>
+ <!ENTITY gen_sctp '<seeerl marker="kernel:gen_sctp">gen_sctp(3)</seeerl>'>
<!ENTITY gen_sctp_open1
- '<seealso marker="kernel:gen_sctp#open-1">gen_sctp:open/1</seealso>'>
+ '<seemfa marker="kernel:gen_sctp#open/1">gen_sctp:open/1</seemfa>'>
<!ENTITY ip_address
- '<seealso marker="kernel:inet#type-ip_address">inet:ip_address()</seealso>'>
- <!ENTITY inet '<seealso marker="kernel:inet">inet(3)</seealso>'>
+ '<seetype marker="kernel:inet#ip_address">inet:ip_address()</seetype>'>
+ <!ENTITY inet '<seeerl marker="kernel:inet">inet(3)</seeerl>'>
<!ENTITY % also SYSTEM "seealso.ent" >
<!ENTITY % here SYSTEM "seehere.ent" >
%also;
diff --git a/lib/diameter/doc/src/diameter_soc.xml b/lib/diameter/doc/src/diameter_soc.xml
index 2d2d66a243..19d13c8f77 100644
--- a/lib/diameter/doc/src/diameter_soc.xml
+++ b/lib/diameter/doc/src/diameter_soc.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd" [
- <!ENTITY gen_sctp '<seealso marker="kernel:gen_sctp">gen_sctp(3)</seealso>'>
- <!ENTITY gen_tcp '<seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>'>
- <!ENTITY service '<seealso marker="diameter#start_service-2">service</seealso>'>
- <!ENTITY capabilities '<seealso marker="diameter#capability">capabilities</seealso>'>
- <!ENTITY events '<seealso marker="diameter#service_event">events</seealso>'>
+ <!ENTITY gen_sctp '<seeerl marker="kernel:gen_sctp">gen_sctp(3)</seeerl>'>
+ <!ENTITY gen_tcp '<seeerl marker="kernel:gen_tcp">gen_tcp(3)</seeerl>'>
+ <!ENTITY service '<seemfa marker="diameter#start_service/2">service</seemfa>'>
+ <!ENTITY capabilities '<seeerl marker="diameter#capability">capabilities</seeerl>'>
+ <!ENTITY events '<seeerl marker="diameter#service_event">events</seeerl>'>
<!ENTITY NA '&#8212;'>
- <!ENTITY BR '<br/>&nbsp;<br/>'>
+ <!ENTITY BR '<br/>&#160;<br/>'>
<!ENTITY % also SYSTEM "seealso.ent" >
%also;
]>
diff --git a/lib/diameter/doc/src/diameter_tcp.xml b/lib/diameter/doc/src/diameter_tcp.xml
index 932bcab1ea..0ccdd065ea 100644
--- a/lib/diameter/doc/src/diameter_tcp.xml
+++ b/lib/diameter/doc/src/diameter_tcp.xml
@@ -1,23 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd" [
- <!ENTITY start '<seealso marker="#start-3">start/3</seealso>'>
+ <!ENTITY start '<seemfa marker="#start/3">start/3</seemfa>'>
<!ENTITY gen_tcp_connect3
- '<seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect/3</seealso>'>
+ '<seemfa marker="kernel:gen_tcp#connect/3">gen_tcp:connect/3</seemfa>'>
<!ENTITY gen_tcp_listen2
- '<seealso marker="kernel:gen_tcp#listen-2">gen_tcp:listen/2</seealso>'>
+ '<seemfa marker="kernel:gen_tcp#listen/2">gen_tcp:listen/2</seemfa>'>
<!ENTITY ip_address
- '<seealso marker="kernel:inet#type-ip_address">inet:ip_address()</seealso>'>
+ '<seetype marker="kernel:inet#ip_address">inet:ip_address()</seetype>'>
<!ENTITY ssl_connect2
- '<seealso marker="ssl:ssl#connect-2">ssl:connect/2</seealso>'>
+ '<seemfa marker="ssl:ssl#connect/2">ssl:connect/2</seemfa>'>
<!ENTITY ssl_connect3
- '<seealso marker="ssl:ssl#connect-3">ssl:connect/3</seealso>'>
+ '<seemfa marker="ssl:ssl#connect/3">ssl:connect/3</seemfa>'>
<!ENTITY ssl_accept2
- '<seealso marker="ssl:ssl#ssl_accept-2">ssl:ssl_accept/2</seealso>'>
+ '<seemfa marker="ssl:ssl#ssl_accept/2">ssl:ssl_accept/2</seemfa>'>
<!ENTITY ssl_listen2
- '<seealso marker="ssl:ssl#listen-2">ssl:listen/2</seealso>'>
- <!ENTITY gen_tcp '<seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>'>
- <!ENTITY inet '<seealso marker="kernel:inet">inet(3)</seealso>'>
- <!ENTITY ssl '<seealso marker="ssl:ssl">ssl(3)</seealso>'>
+ '<seemfa marker="ssl:ssl#listen/2">ssl:listen/2</seemfa>'>
+ <!ENTITY gen_tcp '<seeerl marker="kernel:gen_tcp">gen_tcp(3)</seeerl>'>
+ <!ENTITY inet '<seeerl marker="kernel:inet">inet(3)</seeerl>'>
+ <!ENTITY ssl '<seeerl marker="ssl:ssl">ssl(3)</seeerl>'>
<!ENTITY % also SYSTEM "seealso.ent" >
<!ENTITY % here SYSTEM "seehere.ent" >
%also;
diff --git a/lib/diameter/doc/src/diameter_transport.xml b/lib/diameter/doc/src/diameter_transport.xml
index 0a8ef321c6..5fcdcb3707 100644
--- a/lib/diameter/doc/src/diameter_transport.xml
+++ b/lib/diameter/doc/src/diameter_transport.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd" [
- <!ENTITY message '<seealso marker="#message">message()</seealso>'>
- <!ENTITY MESSAGES '<seealso marker="#MESSAGES">MESSAGES</seealso>'>
- <!ENTITY start '<seealso marker="#Mod:start-3">start/3</seealso>'>
+ <!ENTITY message '<seeerl marker="#message">message()</seeerl>'>
+ <!ENTITY MESSAGES '<seeerl marker="#MESSAGES">MESSAGES</seeerl>'>
+ <!ENTITY start '<seemfa marker="#Mod:start/3">start/3</seemfa>'>
<!ENTITY ip_address
- '<seealso marker="kernel:inet#type-ip_address">inet:ip_address()</seealso>'>
+ '<seetype marker="kernel:inet#ip_address">inet:ip_address()</seetype>'>
<!ENTITY % also SYSTEM "seealso.ent" >
<!ENTITY % here SYSTEM "seehere.ent" >
%also;
diff --git a/lib/diameter/doc/src/diameterc.xml b/lib/diameter/doc/src/diameterc_cmd.xml
index 8f1c660989..074ebf418f 100644
--- a/lib/diameter/doc/src/diameterc.xml
+++ b/lib/diameter/doc/src/diameterc_cmd.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE comref SYSTEM "comref.dtd" [
<!ENTITY dictionary
- '<seealso marker="diameter_dict">dictionary file</seealso>'>
+ '<seefile marker="diameter_dict">dictionary file</seefile>'>
<!ENTITY % also SYSTEM "seealso.ent" >
<!ENTITY % here SYSTEM "seehere.ent" >
%also;
diff --git a/lib/diameter/doc/src/files.mk b/lib/diameter/doc/src/files.mk
index 4c1297f6cc..ab47beb99e 100644
--- a/lib/diameter/doc/src/files.mk
+++ b/lib/diameter/doc/src/files.mk
@@ -22,7 +22,7 @@ XML_APPLICATION_FILES = \
ref_man.xml
XML_REF1_FILES = \
- diameterc.xml
+ diameterc_cmd.xml
XML_REF3_FILES = \
diameter.xml \
@@ -52,4 +52,4 @@ XML_CHAPTER_FILES = \
BOOK_FILES = \
book.xml
-GIF_FILES =
+IMAGE_FILES =
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index 2cf9473c33..3546bebf38 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -2694,7 +2694,7 @@ in the next release.</p>
</list>
<p>
-See <seealso marker="diameter_soc">Standards Compliance</seealso> for
+See <seeguide marker="diameter_soc">Standards Compliance</seeguide> for
standards-related issues.</p>
</section>
diff --git a/lib/diameter/doc/src/ref_man.xml b/lib/diameter/doc/src/ref_man.xml
index a0ef28844d..d57e121c60 100644
--- a/lib/diameter/doc/src/ref_man.xml
+++ b/lib/diameter/doc/src/ref_man.xml
@@ -40,7 +40,7 @@ applications on top of the Diameter protocol. </p>
</description>
<xi:include href="diameter.xml"/>
-<xi:include href="diameterc.xml"/>
+<xi:include href="diameterc_cmd.xml"/>
<xi:include href="diameter_app.xml"/>
<xi:include href="diameter_codec.xml"/>
<xi:include href="diameter_dict.xml"/>
diff --git a/lib/diameter/doc/src/seealso.ent b/lib/diameter/doc/src/seealso.ent
index 72d74c103c..13395f4d44 100644
--- a/lib/diameter/doc/src/seealso.ent
+++ b/lib/diameter/doc/src/seealso.ent
@@ -37,109 +37,109 @@ significant.
<!-- diameter -->
-<!ENTITY mod_add_transport '<seealso marker="diameter#add_transport-2">diameter:add_transport/2</seealso>'>
-<!ENTITY mod_call '<seealso marker="diameter#call-4">diameter:call/4</seealso>'>
-<!ENTITY mod_origin_state_id '<seealso marker="diameter#origin_state_id-0">diameter:origin_state_id/0</seealso>'>
-<!ENTITY mod_remove_transport '<seealso marker="diameter#remove_transport-2">diameter:remove_transport/2</seealso>'>
-<!ENTITY mod_service_info '<seealso marker="diameter#service_info-2">diameter:service_info/2</seealso>'>
-<!ENTITY mod_services '<seealso marker="diameter#services-0">diameter:services/0</seealso>'>
-<!ENTITY mod_session_id '<seealso marker="diameter#session_id-1">diameter:session_id/1</seealso>'>
-<!ENTITY mod_start_service '<seealso marker="diameter#start_service-2">diameter:start_service/2</seealso>'>
-<!ENTITY mod_stop_service '<seealso marker="diameter#stop_service-1">diameter:stop_service/1</seealso>'>
-<!ENTITY mod_subscribe '<seealso marker="diameter#subscribe-1">diameter:subscribe/1</seealso>'>
-
-<!ENTITY mod_application_alias '<seealso marker="diameter#application_alias">diameter:application_alias()</seealso>'>
-<!ENTITY mod_application_module '<seealso marker="diameter#application_module">diameter:application_module()</seealso>'>
-<!ENTITY mod_application_opt '<seealso marker="diameter#application_opt">diameter:application_opt()</seealso>'>
-<!ENTITY mod_call_opt '<seealso marker="diameter#call_opt">diameter:call_opt()</seealso>'>
-<!ENTITY mod_capability '<seealso marker="diameter#capability">diameter:capability()</seealso>'>
-<!ENTITY mod_eval '<seealso marker="diameter#eval">diameter:eval()</seealso>'>
-<!ENTITY mod_peer_filter '<seealso marker="diameter#peer_filter">diameter:peer_filter()</seealso>'>
-<!ENTITY mod_service_event '<seealso marker="diameter#service_event">diameter:service_event()</seealso>'>
-<!ENTITY mod_service_event_info '<seealso marker="diameter#service_event_info">diameter:service_event_info()</seealso>'>
-<!ENTITY mod_service_name '<seealso marker="diameter#service_name">diameter:service_name()</seealso>'>
-<!ENTITY mod_service_opt '<seealso marker="diameter#service_opt">diameter:service_opt()</seealso>'>
-<!ENTITY mod_transport_opt '<seealso marker="diameter#transport_opt">diameter:transport_opt()</seealso>'>
-<!ENTITY mod_transport_ref '<seealso marker="diameter#transport_ref">diameter:transport_ref()</seealso>'>
-
-<!ENTITY capabilities_cb '<seealso marker="#capabilities_cb">capabilities_cb</seealso>'>
-<!ENTITY capx_timeout '<seealso marker="#capx_timeout">capx_timeout</seealso>'>
-<!ENTITY disconnect_cb '<seealso marker="#disconnect_cb">disconnect_cb</seealso>'>
-<!ENTITY dpa_timeout '<seealso marker="#dpa_timeout">dpa_timeout</seealso>'>
-<!ENTITY transport_config '<seealso marker="#transport_config">transport_config</seealso>'>
-<!ENTITY transport_module '<seealso marker="#transport_module">transport_module</seealso>'>
-<!ENTITY connect_timer '<seealso marker="#connect_timer">connect_timer</seealso>'>
-<!ENTITY watchdog_timer '<seealso marker="#watchdog_timer">watchdog_timer</seealso>'>
-
-<!ENTITY mod_string_decode '<seealso marker="diameter#service_opt">diameter:service_opt()</seealso> <seealso marker="diameter#string_decode">string_decode</seealso>'>
-<!ENTITY mod_decode_format '<seealso marker="diameter#service_opt">diameter:service_opt()</seealso> <seealso marker="diameter#decode_format">decode_format</seealso>'>
+<!ENTITY mod_add_transport '<seemfa marker="diameter#add_transport/2">diameter:add_transport/2</seemfa>'>
+<!ENTITY mod_call '<seemfa marker="diameter#call/4">diameter:call/4</seemfa>'>
+<!ENTITY mod_origin_state_id '<seemfa marker="diameter#origin_state_id/0">diameter:origin_state_id/0</seemfa>'>
+<!ENTITY mod_remove_transport '<seemfa marker="diameter#remove_transport/2">diameter:remove_transport/2</seemfa>'>
+<!ENTITY mod_service_info '<seemfa marker="diameter#service_info/2">diameter:service_info/2</seemfa>'>
+<!ENTITY mod_services '<seemfa marker="diameter#services/0">diameter:services/0</seemfa>'>
+<!ENTITY mod_session_id '<seemfa marker="diameter#session_id/1">diameter:session_id/1</seemfa>'>
+<!ENTITY mod_start_service '<seemfa marker="diameter#start_service/2">diameter:start_service/2</seemfa>'>
+<!ENTITY mod_stop_service '<seemfa marker="diameter#stop_service/1">diameter:stop_service/1</seemfa>'>
+<!ENTITY mod_subscribe '<seemfa marker="diameter#subscribe/1">diameter:subscribe/1</seemfa>'>
+
+<!ENTITY mod_application_alias '<seeerl marker="diameter#application_alias">diameter:application_alias()</seeerl>'>
+<!ENTITY mod_application_module '<seeerl marker="diameter#application_module">diameter:application_module()</seeerl>'>
+<!ENTITY mod_application_opt '<seeerl marker="diameter#application_opt">diameter:application_opt()</seeerl>'>
+<!ENTITY mod_call_opt '<seeerl marker="diameter#call_opt">diameter:call_opt()</seeerl>'>
+<!ENTITY mod_capability '<seeerl marker="diameter#capability">diameter:capability()</seeerl>'>
+<!ENTITY mod_eval '<seeerl marker="diameter#eval">diameter:eval()</seeerl>'>
+<!ENTITY mod_peer_filter '<seeerl marker="diameter#peer_filter">diameter:peer_filter()</seeerl>'>
+<!ENTITY mod_service_event '<seeerl marker="diameter#service_event">diameter:service_event()</seeerl>'>
+<!ENTITY mod_service_event_info '<seeerl marker="diameter#service_event_info">diameter:service_event_info()</seeerl>'>
+<!ENTITY mod_service_name '<seeerl marker="diameter#service_name">diameter:service_name()</seeerl>'>
+<!ENTITY mod_service_opt '<seeerl marker="diameter#service_opt">diameter:service_opt()</seeerl>'>
+<!ENTITY mod_transport_opt '<seeerl marker="diameter#transport_opt">diameter:transport_opt()</seeerl>'>
+<!ENTITY mod_transport_ref '<seeerl marker="diameter#transport_ref">diameter:transport_ref()</seeerl>'>
+
+<!ENTITY capabilities_cb '<seeerl marker="#capabilities_cb">capabilities_cb</seeerl>'>
+<!ENTITY capx_timeout '<seeerl marker="#capx_timeout">capx_timeout</seeerl>'>
+<!ENTITY disconnect_cb '<seeerl marker="#disconnect_cb">disconnect_cb</seeerl>'>
+<!ENTITY dpa_timeout '<seeerl marker="#dpa_timeout">dpa_timeout</seeerl>'>
+<!ENTITY transport_config '<seeerl marker="#transport_config">transport_config</seeerl>'>
+<!ENTITY transport_module '<seeerl marker="#transport_module">transport_module</seeerl>'>
+<!ENTITY connect_timer '<seeerl marker="#connect_timer">connect_timer</seeerl>'>
+<!ENTITY watchdog_timer '<seeerl marker="#watchdog_timer">watchdog_timer</seeerl>'>
+
+<!ENTITY mod_string_decode '<seeerl marker="diameter#service_opt">diameter:service_opt()</seeerl> <seeerl marker="diameter#string_decode">string_decode</seeerl>'>
+<!ENTITY mod_decode_format '<seeerl marker="diameter#service_opt">diameter:service_opt()</seeerl> <seeerl marker="diameter#decode_format">decode_format</seeerl>'>
<!-- diameter_app -->
-<!ENTITY app_handle_answer '<seealso marker="diameter_app#Mod:handle_answer-4">handle_answer/4</seealso>'>
-<!ENTITY app_handle_request '<seealso marker="diameter_app#Mod:handle_request-3">handle_request/3</seealso>'>
-<!ENTITY app_handle_error '<seealso marker="diameter_app#Mod:handle_error-4">handle_error/4</seealso>'>
-<!ENTITY app_peer_down '<seealso marker="diameter_app#Mod:peer_down-3">peer_down/3</seealso>'>
-<!ENTITY app_peer_up '<seealso marker="diameter_app#Mod:peer_up-3">peer_up/3</seealso>'>
-<!ENTITY app_pick_peer '<seealso marker="diameter_app#Mod:pick_peer-4">pick_peer/4</seealso>'>
-<!ENTITY app_prepare_retransmit '<seealso marker="diameter_app#Mod:prepare_retransmit-3">prepare_retransmit/3</seealso>'>
-<!ENTITY app_prepare_request '<seealso marker="diameter_app#Mod:prepare_request-3">prepare_request/3</seealso>'>
+<!ENTITY app_handle_answer '<seemfa marker="diameter_app#Mod:handle_answer/4">handle_answer/4</seemfa>'>
+<!ENTITY app_handle_request '<seemfa marker="diameter_app#Mod:handle_request/3">handle_request/3</seemfa>'>
+<!ENTITY app_handle_error '<seemfa marker="diameter_app#Mod:handle_error/4">handle_error/4</seemfa>'>
+<!ENTITY app_peer_down '<seemfa marker="diameter_app#Mod:peer_down/3">peer_down/3</seemfa>'>
+<!ENTITY app_peer_up '<seemfa marker="diameter_app#Mod:peer_up/3">peer_up/3</seemfa>'>
+<!ENTITY app_pick_peer '<seemfa marker="diameter_app#Mod:pick_peer/4">pick_peer/4</seemfa>'>
+<!ENTITY app_prepare_retransmit '<seemfa marker="diameter_app#Mod:prepare_retransmit/3">prepare_retransmit/3</seemfa>'>
+<!ENTITY app_prepare_request '<seemfa marker="diameter_app#Mod:prepare_request/3">prepare_request/3</seemfa>'>
-<!ENTITY app_capabilities '<seealso marker="diameter_app#capabilities">diameter_app:capabilities()</seealso>'>
-<!ENTITY app_peer '<seealso marker="diameter_app#peer">diameter_app:peer()</seealso>'>
-<!ENTITY app_peer_ref '<seealso marker="diameter_app#peer_ref">diameter_app:peer_ref()</seealso>'>
-<!ENTITY app_state '<seealso marker="diameter_app#state">diameter_app:state()</seealso>'>
+<!ENTITY app_capabilities '<seeerl marker="diameter_app#capabilities">diameter_app:capabilities()</seeerl>'>
+<!ENTITY app_peer '<seeerl marker="diameter_app#peer">diameter_app:peer()</seeerl>'>
+<!ENTITY app_peer_ref '<seeerl marker="diameter_app#peer_ref">diameter_app:peer_ref()</seeerl>'>
+<!ENTITY app_state '<seeerl marker="diameter_app#state">diameter_app:state()</seeerl>'>
<!-- diameter_codec -->
-<!ENTITY codec_encode '<seealso marker="diameter_codec#encode-2">diameter_codec:encode/2</seealso>'>
-<!ENTITY codec_decode '<seealso marker="diameter_codec#decode-2">diameter_codec:decode/2</seealso>'>
+<!ENTITY codec_encode '<seemfa marker="diameter_codec#encode/2">diameter_codec:encode/2</seemfa>'>
+<!ENTITY codec_decode '<seemfa marker="diameter_codec#decode/2">diameter_codec:decode/2</seemfa>'>
-<!ENTITY codec_avp '<seealso marker="diameter_codec#avp">diameter_codec:avp()</seealso>'>
-<!ENTITY codec_header '<seealso marker="diameter_codec#header">diameter_codec:header()</seealso>'>
-<!ENTITY codec_dictionary '<seealso marker="diameter_codec#dictionary">diameter_codec:dictionary()</seealso>'>
-<!ENTITY codec_message '<seealso marker="diameter_codec#message">diameter_codec:message()</seealso>'>
-<!ENTITY codec_packet '<seealso marker="diameter_codec#packet">diameter_codec:packet()</seealso>'>
+<!ENTITY codec_avp '<seeerl marker="diameter_codec#avp">diameter_codec:avp()</seeerl>'>
+<!ENTITY codec_header '<seeerl marker="diameter_codec#header">diameter_codec:header()</seeerl>'>
+<!ENTITY codec_dictionary '<seeerl marker="diameter_codec#dictionary">diameter_codec:dictionary()</seeerl>'>
+<!ENTITY codec_message '<seeerl marker="diameter_codec#message">diameter_codec:message()</seeerl>'>
+<!ENTITY codec_packet '<seeerl marker="diameter_codec#packet">diameter_codec:packet()</seeerl>'>
<!-- diameter_dict -->
-<!ENTITY dict_data_types '<seealso marker="diameter_dict#DATA_TYPES">diameter_dict(4)</seealso>'>
+<!ENTITY dict_data_types '<seefile marker="diameter_dict#DATA_TYPES">diameter_dict(4)</seefile>'>
-<!ENTITY dict_Address '<seealso marker="diameter_dict#DATA_TYPES">Address()</seealso>'>
-<!ENTITY dict_DiameterIdentity '<seealso marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso>'>
-<!ENTITY dict_DiameterURI '<seealso marker="diameter_dict#DATA_TYPES">DiameterURI()</seealso>'>
-<!ENTITY dict_IPFilterRule '<seealso marker="diameter_dict#DATA_TYPES">IPFilterRule()</seealso>'>
-<!ENTITY dict_QoSFilterRule '<seealso marker="diameter_dict#DATA_TYPES">QoSFilterRule()</seealso>'>
-<!ENTITY dict_Grouped '<seealso marker="diameter_dict#DATA_TYPES">Grouped()</seealso>'>
-<!ENTITY dict_OctetString '<seealso marker="diameter_dict#DATA_TYPES">OctetString()</seealso>'>
-<!ENTITY dict_Time '<seealso marker="diameter_dict#DATA_TYPES">Time()</seealso>'>
-<!ENTITY dict_UTF8String '<seealso marker="diameter_dict#DATA_TYPES">UTF8String()</seealso>'>
-<!ENTITY dict_Unsigned32 '<seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>'>
+<!ENTITY dict_Address '<seefile marker="diameter_dict#DATA_TYPES">Address()</seefile>'>
+<!ENTITY dict_DiameterIdentity '<seefile marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seefile>'>
+<!ENTITY dict_DiameterURI '<seefile marker="diameter_dict#DATA_TYPES">DiameterURI()</seefile>'>
+<!ENTITY dict_IPFilterRule '<seefile marker="diameter_dict#DATA_TYPES">IPFilterRule()</seefile>'>
+<!ENTITY dict_QoSFilterRule '<seefile marker="diameter_dict#DATA_TYPES">QoSFilterRule()</seefile>'>
+<!ENTITY dict_Grouped '<seefile marker="diameter_dict#DATA_TYPES">Grouped()</seefile>'>
+<!ENTITY dict_OctetString '<seefile marker="diameter_dict#DATA_TYPES">OctetString()</seefile>'>
+<!ENTITY dict_Time '<seefile marker="diameter_dict#DATA_TYPES">Time()</seefile>'>
+<!ENTITY dict_UTF8String '<seefile marker="diameter_dict#DATA_TYPES">UTF8String()</seefile>'>
+<!ENTITY dict_Unsigned32 '<seefile marker="diameter_dict#DATA_TYPES">Unsigned32()</seefile>'>
-<!ENTITY dict_name '<seealso marker="diameter_dict#name">@name</seealso>'>
-<!ENTITY dict_prefix '<seealso marker="diameter_dict#prefix">@prefix</seealso>'>
-<!ENTITY dict_inherits '<seealso marker="diameter_dict#inherits">@inherits</seealso>'>
+<!ENTITY dict_name '<seefile marker="diameter_dict#name">@name</seefile>'>
+<!ENTITY dict_prefix '<seefile marker="diameter_dict#prefix">@prefix</seefile>'>
+<!ENTITY dict_inherits '<seefile marker="diameter_dict#inherits">@inherits</seefile>'>
<!-- diameter_make -->
-<!ENTITY make_codec '<seealso marker="diameter_make#codec-2">diameter_make:codec/2</seealso>'>
-<!ENTITY make_format '<seealso marker="diameter_make#format-1">diameter_make:format/1</seealso>'>
-<!ENTITY make_flatten '<seealso marker="diameter_make#flatten-1">diameter_make:flatten/1</seealso>'>
-<!ENTITY make_format_error '<seealso marker="diameter_make#format_error-1">diameter_make:format_error/1</seealso>'>
+<!ENTITY make_codec '<seemfa marker="diameter_make#codec/2">diameter_make:codec/2</seemfa>'>
+<!ENTITY make_format '<seemfa marker="diameter_make#format/1">diameter_make:format/1</seemfa>'>
+<!ENTITY make_flatten '<seemfa marker="diameter_make#flatten/1">diameter_make:flatten/1</seemfa>'>
+<!ENTITY make_format_error '<seemfa marker="diameter_make#format_error/1">diameter_make:format_error/1</seemfa>'>
<!-- diameter_transport -->
<!ENTITY transport_start
- '<seealso marker="diameter_transport#Mod:start-3">start/3</seealso>'>
+ '<seemfa marker="diameter_transport#Mod:start/3">start/3</seemfa>'>
<!-- reference pages -->
-<!ENTITY man_compile '<seealso marker="diameterc">diameterc(1)</seealso>'>
-<!ENTITY man_main '<seealso marker="diameter">diameter(3)</seealso>'>
-<!ENTITY man_app '<seealso marker="diameter_app">diameter_app(3)</seealso>'>
-<!ENTITY man_codec '<seealso marker="diameter_codec">diameter_codec(3)</seealso>'>
-<!ENTITY man_dict '<seealso marker="diameter_dict">diameter_dict(4)</seealso>'>
-<!ENTITY man_make '<seealso marker="diameter_make">diameter_make(3)</seealso>'>
-<!ENTITY man_transport '<seealso marker="diameter_transport">diameter_transport(3)</seealso>'>
-<!ENTITY man_sctp '<seealso marker="diameter_sctp">diameter_sctp(3)</seealso>'>
-<!ENTITY man_tcp '<seealso marker="diameter_tcp">diameter_tcp(3)</seealso>'>
+<!ENTITY man_compile '<seecom marker="diameterc">diameterc(1)</seecom>'>
+<!ENTITY man_main '<seeerl marker="diameter">diameter(3)</seeerl>'>
+<!ENTITY man_app '<seeerl marker="diameter_app">diameter_app(3)</seeerl>'>
+<!ENTITY man_codec '<seeerl marker="diameter_codec">diameter_codec(3)</seeerl>'>
+<!ENTITY man_dict '<seefile marker="diameter_dict">diameter_dict(4)</seefile>'>
+<!ENTITY man_make '<seeerl marker="diameter_make">diameter_make(3)</seeerl>'>
+<!ENTITY man_transport '<seeerl marker="diameter_transport">diameter_transport(3)</seeerl>'>
+<!ENTITY man_sctp '<seeerl marker="diameter_sctp">diameter_sctp(3)</seeerl>'>
+<!ENTITY man_tcp '<seeerl marker="diameter_tcp">diameter_tcp(3)</seeerl>'>
diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile
index d6854cfd27..36e8fefd4c 100644
--- a/lib/diameter/src/Makefile
+++ b/lib/diameter/src/Makefile
@@ -96,6 +96,12 @@ APPUP_FILE = diameter.appup
APPUP_SRC = $(APPUP_FILE).src
APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
+ifeq ($(TARGET),win32)
+ EXE_SUFFIX=.exe
+else
+ EXE_SUFFIX=
+endif
+
# ----------------------------------------------------
# Flags
# ----------------------------------------------------
@@ -123,7 +129,7 @@ ERL_COMPILE_FLAGS += \
# erl/hrl from dictionary file.
gen/diameter_gen_%.erl gen/diameter_gen_%.hrl: dict/%.dia
$(dia_verbose) \
- escript ../bin/diameterc -o gen -i $(EBIN) $<
+ escript$(EXE_SUFFIX) ../bin/diameterc -o gen -i $(EBIN) $<
opt: $(TARGET_FILES)
diff --git a/lib/edoc/Makefile b/lib/edoc/Makefile
index 70bf1f3d48..a8258015b1 100644
--- a/lib/edoc/Makefile
+++ b/lib/edoc/Makefile
@@ -69,7 +69,7 @@ SPECIAL_TARGETS =
include $(ERL_TOP)/make/otp_subdir.mk
-.PHONY: info version
+.PHONY: version
version:
@@ -92,9 +92,6 @@ edocs:
-pa $(XMERL_DIR)/ebin -run edoc_run application \
"'$(APPNAME)'" '"."' '$(DOC_OPTS)'
-info:
- @echo $(HTML_FILES)
-
app_release: tar
@@ -124,3 +121,7 @@ tar: $(APP_TAR_FILE)
$(APP_TAR_FILE): $(APP_DIR)
(cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME))
+
+DIA_PLT_APPS=syntax_tools xmerl inets
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/edoc/doc/overview.edoc b/lib/edoc/doc/overview.edoc
index 981320f1fc..e19dc1bed4 100644
--- a/lib/edoc/doc/overview.edoc
+++ b/lib/edoc/doc/overview.edoc
@@ -767,10 +767,10 @@ following escape sequences may be used: <dl>
Note that although the syntax described in the following can still be used
for specifying functions we recommend that Erlang specifications as
-described in <seealso marker="doc/reference_manual:typespec"> Types
-and Function Specification</seealso> should be added to the source
-code instead. This way the analyses of <seealso
-marker="dialyzer:dialyzer">Dialyzer</seealso>'s can be utilized in the
+described in <seeguide marker="system/reference_manual:typespec"> Types
+and Function Specification</seeguide> should be added to the source
+code instead. This way the analyses of <seeerl
+marker="dialyzer:dialyzer">Dialyzer</seeerl>'s can be utilized in the
process of keeping the documentation consistent and up-to-date.
Erlang specifications will be used unless there is also a function
specification (a `@spec' tag followed by a type) with the same name.
@@ -984,8 +984,8 @@ contain any annotations at all.
Note that although the syntax described in the following can still be used
for specifying types we recommend that Erlang types as described in
-<seealso marker="doc/reference_manual:typespec"> Types and Function
-Specification</seealso> should be added to the source code instead.
+<seeguide marker="system/reference_manual:typespec"> Types and Function
+Specification</seeguide> should be added to the source code instead.
Erlang types will be used unless there is a type alias with the same
name.
diff --git a/lib/edoc/doc/src/Makefile b/lib/edoc/doc/src/Makefile
index 3e53e75c75..29244467f9 100644
--- a/lib/edoc/doc/src/Makefile
+++ b/lib/edoc/doc/src/Makefile
@@ -25,22 +25,10 @@ VSN=$(EDOC_VSN)
APPLICATION=edoc
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
-# Man page source directory (with .erl files)
-# ----------------------------------------------------
-SRC_DIR = $(ERL_TOP)/lib/edoc/src
-INC_DIR = $(ERL_TOP)/lib/edoc/include
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
-XML_REF3_FILES = \
- edoc.xml \
+EDOC_REF3_FILES = edoc.xml \
edoc_doclet.xml \
edoc_extract.xml \
edoc_layout.xml \
@@ -48,7 +36,7 @@ XML_REF3_FILES = \
edoc_run.xml
XML_PART_FILES = part.xml
-XML_CHAPTER_FILES = chapter.xml
+EDOC_CHAPTER_FILE = chapter.xml
XML_NOTES_FILES = notes.xml
BOOK_FILES = book.xml
@@ -57,89 +45,12 @@ XML_FILES=\
$(BOOK_FILES) $(XML_APPLICATION_FILES) \
$(XML_PART_FILES) $(XML_NOTES_FILES)
-XML_GEN_FILES=$(XML_REF3_FILES:%=$(XMLDIR)/%) $(XML_CHAPTER_FILES:%=$(XMLDIR)/%)
-
-# ----------------------------------------------------
-INFO_FILE = ../../info
-
-HTML_FILES = \
- $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-EXTRA_FILES = \
- $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_NOTES_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
INCLUDES_DIR = ../../include
INCLUDES = $(INCLUDES_DIR)/edoc_doclet.hrl
DTDS_DIR = ../../priv
DTDS = $(DTDS_DIR)/edoc.dtd
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES)
-
-$(XML_REF3_FILES:%=$(XMLDIR)/%):
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -def vsn $(EDOC_VSN) -i $(ERL_TOP)/lib/edoc/include -dir $(XMLDIR) $(SRC_DIR)/$(@:$(XMLDIR)/%.xml=%.erl)
-
-$(XML_CHAPTER_FILES:%=$(XMLDIR)/%): ../overview.edoc
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -def vsn $(EDOC_VSN) -chapter -dir $(XMLDIR) $<
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(XML_REF3_FILES) $(XML_CHAPTER_FILES) *.html
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(INCLUDES) $(DTDS) "$(RELSYSDIR)/doc/html"
+HTML_EXTRA_FILES = $(DTDS) $(INCLUDES)
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/edoc/doc/src/notes.xml b/lib/edoc/doc/src/notes.xml
index 48bc5d9c74..2871a09476 100644
--- a/lib/edoc/doc/src/notes.xml
+++ b/lib/edoc/doc/src/notes.xml
@@ -32,6 +32,32 @@
<p>This document describes the changes made to the EDoc
application.</p>
+<section><title>Edoc 0.12</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Remove Inets dependency from EDoc. </p>
+ <p>
+ Own Id: OTP-15999 Aux Id: PR-2317 </p>
+ </item>
+ <item>
+ <p>
+ Add support for overloaded Erlang specifications.</p>
+ <p>
+ Own Id: OTP-16407 Aux Id: PR-2430 </p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Edoc 0.11</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/edoc/src/edoc.app.src b/lib/edoc/src/edoc.app.src
index 43343e2ae8..834c0eb005 100644
--- a/lib/edoc/src/edoc.app.src
+++ b/lib/edoc/src/edoc.app.src
@@ -23,4 +23,4 @@
{applications, [compiler,kernel,stdlib,syntax_tools]},
{env, []},
{runtime_dependencies, ["xmerl-1.3.7","syntax_tools-1.6.14","stdlib-2.5",
- "kernel-3.0","inets-5.10","erts-6.0"]}]}.
+ "kernel-3.0","erts-6.0"]}]}.
diff --git a/lib/edoc/src/edoc.erl b/lib/edoc/src/edoc.erl
index 62483602aa..0fdc818fae 100644
--- a/lib/edoc/src/edoc.erl
+++ b/lib/edoc/src/edoc.erl
@@ -259,10 +259,9 @@ opt_negations() ->
%% </dd>
%% <dt>{@type {doc_path, [string()]@}}
%% </dt>
-%% <dd>Specifies a list of URI:s pointing to directories that contain
-%% EDoc-generated documentation. URI without a `scheme://' part are
-%% taken as relative to `file://'. (Note that such paths must use
-%% `/' as separator, regardless of the host operating system.)
+%% <dd>Specifies a list of file system paths pointing to directories that
+%% contain EDoc-generated documentation. All paths for applications
+%% in the code path are automatically added.
%% </dd>
%% <dt>{@type {doclet, Module::atom()@}}
%% </dt>
diff --git a/lib/edoc/src/edoc_data.erl b/lib/edoc/src/edoc_data.erl
index a8373d6536..3075e47942 100644
--- a/lib/edoc/src/edoc_data.erl
+++ b/lib/edoc/src/edoc_data.erl
@@ -230,9 +230,11 @@ callback({N, A}, _Env, _Opts) ->
%% <!ELEMENT throws (type, localdef*)>
%% <!ELEMENT equiv (expr, see?)>
%% <!ELEMENT expr (#PCDATA)>
-
-function({N, A}, As, Export, Ts, Env, Opts) ->
- {Args, Ret, Spec} = signature(Ts, As, Env),
+function({N, A}, []=As, Export, Ts, Env, Opts)->
+ function({N, A}, [As], Export, Ts, Env, Opts);
+function({N, A}, [HAs | _]=As, Export, Ts, Env, Opts) when not is_list(HAs) ->
+ function({N, A}, [As], Export, Ts, Env, Opts);
+function({N, A}, As0, Export, Ts, Env, Opts) ->
{function, [{name, atom_to_list(N)},
{arity, integer_to_list(A)},
{exported, case Export of
@@ -240,13 +242,8 @@ function({N, A}, As, Export, Ts, Env, Opts) ->
false -> "no"
end},
{label, edoc_refs:to_label(edoc_refs:function(N, A))}],
- [{args, [{arg, [{argName, [atom_to_list(A)]}] ++ description(D)}
- || {A, D} <- Args]}]
- ++ Spec
- ++ case Ret of
- [] -> [];
- _ -> [{returns, description(Ret)}]
- end
+ lists:append([get_args(lists:nth(Clause, As0), Ts, Clause, Env)
+ || Clause <- lists:seq(1, length(As0))])
++ get_throws(Ts, Env)
++ get_equiv(Ts, Env)
++ get_doc(Ts)
@@ -256,6 +253,16 @@ function({N, A}, As, Export, Ts, Env, Opts) ->
++ todos(Ts, Opts)
}.
+get_args(As, Ts, Clause, Env) ->
+ {Args, Ret, Spec} = signature(Ts, As, Clause, Env),
+ [{args, [{arg, [{argName, [atom_to_list(A)]}] ++ description(D)}
+ || {A, D} <- Args]}]
+ ++ Spec
+ ++ case Ret of
+ [] -> [];
+ _ -> [{returns, description(Ret)}]
+ end.
+
get_throws(Ts, Env) ->
case get_tags(throws, Ts) of
[Throws] ->
@@ -328,8 +335,6 @@ get_deprecated(Ts, F, A, Env) ->
case otp_internal:obsolete(M, F, A) of
{Tag, Text} when Tag =:= deprecated; Tag =:= removed ->
deprecated([Text]);
- {Tag, Repl, _Rel} when Tag =:= deprecated; Tag =:= removed ->
- deprecated(Repl, Env);
_ ->
[]
end;
@@ -337,24 +342,9 @@ get_deprecated(Ts, F, A, Env) ->
Es
end.
-deprecated(Repl, Env) ->
- {Text, Ref} = replacement_function(Env#env.module, Repl),
- Desc = ["Use ", {a, href(Ref, Env), [{code, [Text]}]}, " instead."],
- deprecated(Desc).
-
deprecated(Desc) ->
[{deprecated, description(Desc)}].
--dialyzer({no_match, replacement_function/2}).
-
-replacement_function(M0, {M,F,A}) when is_list(A) ->
- %% refer to the largest listed arity - the most general version
- replacement_function(M0, {M,F,lists:last(lists:sort(A))});
-replacement_function(M, {M,F,A}) ->
- {io_lib:fwrite("~w/~w", [F, A]), edoc_refs:function(F, A)};
-replacement_function(_, {M,F,A}) ->
- {io_lib:fwrite("~w:~w/~w", [M, F, A]), edoc_refs:function(M, F, A)}.
-
get_expr_ref(Expr) ->
case catch {ok, erl_syntax_lib:analyze_application(Expr)} of
{ok, {F, A}} when is_atom(F), is_integer(A) ->
@@ -423,10 +413,10 @@ todos(Tags, Opts) ->
[]
end.
-signature(Ts, As, Env) ->
+signature(Ts, As, Clause, Env) ->
case get_tags(spec, Ts) of
[T] ->
- Spec = T#tag.data,
+ Spec = maybe_nth(Clause, T#tag.data),
R = merge_returns(Spec, Ts),
As0 = edoc_types:arg_names(Spec),
Ds0 = edoc_types:arg_descs(Spec),
@@ -441,6 +431,11 @@ signature(Ts, As, Env) ->
{[{A, ""} || A <- fix_argnames(As, S, 1)], [], []}
end.
+maybe_nth(N, List) when is_list(List) ->
+ lists:nth(N, List);
+maybe_nth(1, Other) ->
+ Other.
+
params(Ts) ->
[T#tag.data || T <- get_tags(param, Ts)].
diff --git a/lib/edoc/src/edoc_extract.erl b/lib/edoc/src/edoc_extract.erl
index 390851e9ef..f7e2c28b6f 100644
--- a/lib/edoc/src/edoc_extract.erl
+++ b/lib/edoc/src/edoc_extract.erl
@@ -634,7 +634,7 @@ select_spec(Ts, _Where, _Specs) ->
selected_specs([], Ts) ->
Ts;
selected_specs([F], [_ | Ts]) ->
- [edoc_specs:spec(F, _Clause=1) | Ts].
+ [edoc_specs:spec(F) | Ts].
%% Macros for modules
diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl
index 47ff7b21fc..3643419ba1 100644
--- a/lib/edoc/src/edoc_layout.erl
+++ b/lib/edoc/src/edoc_layout.erl
@@ -357,10 +357,10 @@ label_href(Content, F) ->
functions(Fs, Opts) ->
Es = lists:flatmap(fun ({Name, E}) -> function(Name, E, Opts) end, Fs),
if Es == [] -> [];
- true ->
- [?NL,
- {h2, [{a, [{name, ?FUNCTIONS_LABEL}], [?FUNCTIONS_TITLE]}]},
- ?NL | Es]
+ true ->
+ [?NL,
+ {h2, [{a, [{name, ?FUNCTIONS_LABEL}], [?FUNCTIONS_TITLE]}]},
+ ?NL | Es]
end.
function(Name, E=#xmlElement{content = Es}, Opts) ->
@@ -369,22 +369,24 @@ function(Name, E=#xmlElement{content = Es}, Opts) ->
label_anchor(function_header(Name, E, " *"), E)},
?NL]
++ [{'div', [{class, "spec"}],
- [?NL,
- {p,
- case typespec(get_content(typespec, Es), Opts) of
+ case [typespec(T, Opts) || T <- get_contents(typespec, Es)] of
[] ->
- signature(get_content(args, Es),
- atom(get_attrval(name, E), Opts));
- Spec -> Spec
- end},
- ?NL]
- ++ case params(get_content(args, Es)) of
+ [?NL,{p,
+ signature(get_content(args, Es),
+ atom(get_attrval(name, E), Opts))
+ },?NL];
+ Specs ->
+ [?NL]++[{p, Spec} || Spec <- Specs]++[?NL]
+ end
+ ++ case [params(A) || A <- get_contents(args, Es)] of
[] -> [];
- Ps -> [{p, Ps}, ?NL]
+ As ->
+ lists:append([[{p, Ps}, ?NL] || Ps <- As])
end
- ++ case returns(get_content(returns, Es)) of
+ ++ case [returns(Ret) || Ret <- get_contents(returns, Es)] of
[] -> [];
- Rs -> [{p, Rs}, ?NL]
+ Rets ->
+ lists:append([[{p, Rs}, ?NL] || Rs <- Rets])
end}]
++ throws(Es, Opts)
++ equiv_p(Es)
@@ -968,12 +970,8 @@ seq(F, [E | Es], Sep, Tail) ->
seq(_F, [], _Sep, Tail) ->
Tail.
-get_elem(Name, [#xmlElement{name = Name} = E | Es]) ->
- [E | get_elem(Name, Es)];
-get_elem(Name, [_ | Es]) ->
- get_elem(Name, Es);
-get_elem(_, []) ->
- [].
+get_elem(Name, Es) ->
+ [E || #xmlElement{name=N}=E <- Es, N=:=Name].
get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
[A | get_attr(Name, As)];
@@ -989,6 +987,13 @@ get_attrval(Name, #xmlElement{attributes = As}) ->
[] -> ""
end.
+get_contents(Name, Es) ->
+ case get_elem(Name, Es) of
+ [] -> [];
+ Elems ->
+ [Es1 || #xmlElement{content = Es1} <- Elems]
+ end.
+
get_content(Name, Es) ->
case get_elem(Name, Es) of
[#xmlElement{content = Es1}] ->
diff --git a/lib/edoc/src/edoc_lib.erl b/lib/edoc/src/edoc_lib.erl
index d00a283794..5959fa6f08 100644
--- a/lib/edoc/src/edoc_lib.erl
+++ b/lib/edoc/src/edoc_lib.erl
@@ -32,12 +32,12 @@
-export([count/2, lines/1, split_at/2, split_at_stop/1,
split_at_space/1, filename/1, transpose/1, segment/2,
get_first_sentence/1, is_space/1, strip_space/1, parse_expr/2,
- parse_contact/2, escape_uri/1, join_uri/2, is_relative_uri/1,
+ parse_contact/2, escape_uri/1, join_uri/2,
is_name/1, to_label/1, find_doc_dirs/0, find_sources/2,
find_file/2, try_subdir/2, unique/1,
write_file/3, write_file/4, write_info_file/3,
read_info_file/1, get_doc_env/1, get_doc_env/3, copy_file/2,
- uri_get/1, run_doclet/2, run_layout/2,
+ run_doclet/2, run_layout/2,
simplify_path/1, timestr/1, datestr/1, read_encoding/2]).
-import(edoc_report, [report/2, warning/2]).
@@ -438,128 +438,6 @@ join_uri("", Path) ->
join_uri(Base, Path) ->
Base ++ "/" ++ Path.
-%% Check for relative URI; "network paths" ("//...") not included!
-
-%% @private
-is_relative_uri([$: | _]) ->
- false;
-is_relative_uri([$/, $/ | _]) ->
- false;
-is_relative_uri([$/ | _]) ->
- true;
-is_relative_uri([$? | _]) ->
- true;
-is_relative_uri([$# | _]) ->
- true;
-is_relative_uri([_ | Cs]) ->
- is_relative_uri(Cs);
-is_relative_uri([]) ->
- true.
-
-%% @private
-uri_get("file:///" ++ Path) ->
- uri_get_file(Path);
-uri_get("file://localhost/" ++ Path) ->
- uri_get_file(Path);
-uri_get("file://" ++ Path) ->
- Msg = io_lib:format("cannot handle 'file:' scheme with "
- "nonlocal network-path: 'file://~ts'.",
- [Path]),
- {error, Msg};
-uri_get("file:/" ++ Path) ->
- uri_get_file(Path);
-uri_get("file:" ++ Path) ->
- Msg = io_lib:format("ignoring malformed URI: 'file:~ts'.", [Path]),
- {error, Msg};
-uri_get("http:" ++ Path) ->
- uri_get_http("http:" ++ Path);
-uri_get("ftp:" ++ Path) ->
- uri_get_ftp("ftp:" ++ Path);
-uri_get("//" ++ Path) ->
- Msg = io_lib:format("cannot access network-path: '//~ts'.", [Path]),
- {error, Msg};
-uri_get([C, $:, $/ | _]=Path) when C >= $A, C =< $Z; C >= $a, C =< $z ->
- uri_get_file(Path); % special case for Windows
-uri_get([C, $:, $\ | _]=Path) when C >= $A, C =< $Z; C >= $a, C =< $z ->
- uri_get_file(Path); % special case for Windows
-uri_get(URI) ->
- case is_relative_uri(URI) of
- true ->
- uri_get_file(URI);
- false ->
- Msg = io_lib:format("cannot handle URI: '~ts'.", [URI]),
- {error, Msg}
- end.
-
-uri_get_file(File0) ->
- File = filename:join(?FILE_BASE, File0),
- case read_file(File) of
- {ok, Text} ->
- {ok, Text};
- {error, R} ->
- {error, file:format_error(R)}
- end.
-
-uri_get_http(URI) ->
- %% Try using option full_result=false
- case catch {ok, httpc:request(get, {URI,[]}, [],
- [{full_result, false}])} of
- {'EXIT', _} ->
- uri_get_http_r10(URI);
- Result ->
- uri_get_http_1(Result, URI)
- end.
-
-uri_get_http_r10(URI) ->
- %% Try most general form of request
- Result = (catch {ok, httpc:request(get, {URI,[]}, [], [])}),
- uri_get_http_1(Result, URI).
-
-uri_get_http_1(Result, URI) ->
- case Result of
- {ok, {ok, {200, Text}}} when is_list(Text) ->
- %% new short result format
- {ok, Text};
- {ok, {ok, {Status, Text}}} when is_integer(Status), is_list(Text) ->
- %% new short result format when status /= 200
- Phrase = httpd_util:reason_phrase(Status),
- {error, http_errmsg(Phrase, URI)};
- {ok, {ok, {{_Vsn, 200, _Phrase}, _Hdrs, Text}}} when is_list(Text) ->
- %% new long result format
- {ok, Text};
- {ok, {ok, {{_Vsn, _Status, Phrase}, _Hdrs, Text}}} when is_list(Text) ->
- %% new long result format when status /= 200
- {error, http_errmsg(Phrase, URI)};
- {ok, {200,_Hdrs,Text}} when is_list(Text) ->
- %% old result format
- {ok, Text};
- {ok, {Status,_Hdrs,Text}} when is_list(Text) ->
- %% old result format when status /= 200
- Phrase = httpd_util:reason_phrase(Status),
- {error, http_errmsg(Phrase, URI)};
- {ok, {error, R}} ->
- Reason = inet:format_error(R),
- {error, http_errmsg(Reason, URI)};
- {ok, R} ->
- Reason = io_lib:format("bad return value ~tP", [R, 5]),
- {error, http_errmsg(Reason, URI)};
- {'EXIT', R} ->
- Reason = io_lib:format("crashed with reason ~tw", [R]),
- {error, http_errmsg(Reason, URI)};
- R ->
- Reason = io_lib:format("uncaught throw: ~tw", [R]),
- {error, http_errmsg(Reason, URI)}
- end.
-
-http_errmsg(Reason, URI) ->
- io_lib:format("http error: ~ts: '~ts'", [Reason, URI]).
-
-%% TODO: implement ftp access method
-
-uri_get_ftp(URI) ->
- Msg = io_lib:format("cannot access ftp scheme yet: '~ts'.", [URI]),
- {error, Msg}.
-
%% @private
to_label([$\s | Cs]) ->
to_label(Cs);
@@ -754,18 +632,6 @@ read_info_file(Dir) ->
{?NO_APP, []}
end.
-%% URI access
-
-uri_get_info_file(Base) ->
- URI = join_uri(Base, ?INFO_FILE),
- case uri_get(URI) of
- {ok, Text} ->
- parse_info_file(Text, URI);
- {error, Msg} ->
- warning("could not read '~ts': ~ts.", [URI, Msg]),
- {?NO_APP, []}
- end.
-
parse_info_file(Text, Name) ->
case parse_terms(Text) of
{ok, Vs} ->
@@ -897,7 +763,7 @@ find_doc_dirs([]) ->
get_doc_links(App, Modules, Opts) ->
Path = proplists:append_values(doc_path, Opts) ++ find_doc_dirs(),
- Ds = [{P, uri_get_info_file(P)} || P <- Path],
+ Ds = [{P, read_info_file(P)} || P <- Path],
Ds1 = [{"", {App, Modules}} | Ds],
D = dict:new(),
make_links(Ds1, D, D).
diff --git a/lib/edoc/src/edoc_specs.erl b/lib/edoc/src/edoc_specs.erl
index 7b451c43f8..19f890ed8b 100644
--- a/lib/edoc/src/edoc_specs.erl
+++ b/lib/edoc/src/edoc_specs.erl
@@ -21,7 +21,7 @@
-module(edoc_specs).
--export([type/2, spec/2, dummy_spec/1, docs/2]).
+-export([type/2, spec/1, dummy_spec/1, docs/2]).
-export([add_data/4, tag/1, is_tag/1]).
@@ -67,15 +67,14 @@ type(Form, TypeDocs) ->
type = d2e(opaque2abstr(Name, Type))},
Doc}}.
--spec spec(Form::syntaxTree(), ClauseN::pos_integer()) -> #tag{}.
+-spec spec(Form::syntaxTree()) -> #tag{}.
%% @doc Convert an Erlang spec to EDoc representation.
-spec(Form, Clause) ->
+spec(Form) ->
{Name, _Arity, TypeSpecs} = get_spec(Form),
- TypeSpec = lists:nth(Clause, TypeSpecs),
- #tag{name = spec, line = get_line(element(2, TypeSpec)),
+ #tag{name = spec, line = get_line(element(2, lists:nth(1, TypeSpecs))),
origin = code,
- data = aspec(d2e(TypeSpec), Name)}.
+ data = [aspec(d2e(TypeSpec), Name) || TypeSpec <- TypeSpecs]}.
-spec dummy_spec(Form::syntaxTree()) -> #tag{}.
@@ -264,8 +263,9 @@ use_tags([#tag{origin = code}=T | Ts], E, TypeTable, NTs) ->
use_tags([T | Ts], E, TypeTable, NTs) ->
use_tags(Ts, E, TypeTable, [T | NTs]).
-params(#tag{name = spec, data=#t_spec{type = #t_fun{args = As}}}, Default) ->
- parms(As, Default).
+
+params(#tag{name = spec, data=Data}, Default) when is_list(Data) ->
+ [parms(As, Default) || #t_spec{type = #t_fun{args = As}} <- Data].
parms([], []) ->
[];
@@ -485,13 +485,17 @@ entries([E0 | Es], P, Opts) ->
entries([], _P, _Opts) ->
[].
-specs([#tag{line = L, name = spec, origin = code, data = Spec}=Tag0 | Tags],
+specs([#tag{line = L, name = spec, origin = code, data = Specs}=Tag0 | Tags],
P0) ->
- #t_spec{type = Type0, defs = Defs0} = Spec,
P = P0#parms{line = L},
- Type = xrecs(Type0, P),
- Defs = xrecs(Defs0, P),
- Tag = Tag0#tag{data = Spec#t_spec{type = Type, defs = Defs}},
+ Data =
+ [ begin
+ #t_spec{type = Type0, defs = Defs0} = Spec,
+ Type = xrecs(Type0, P),
+ Defs = xrecs(Defs0, P),
+ Spec#t_spec{type = Type, defs = Defs}
+ end || Spec <- Specs],
+ Tag = Tag0#tag{data = Data},
[Tag | specs(Tags, P)];
specs([Tag | Tags], P) ->
[Tag | specs(Tags, P)];
diff --git a/lib/edoc/vsn.mk b/lib/edoc/vsn.mk
index 3510fdfccf..5d2bbe769d 100644
--- a/lib/edoc/vsn.mk
+++ b/lib/edoc/vsn.mk
@@ -1 +1 @@
-EDOC_VSN = 0.11
+EDOC_VSN = 0.12
diff --git a/lib/eldap/Makefile b/lib/eldap/Makefile
index 28f995e068..30a8f778ab 100644
--- a/lib/eldap/Makefile
+++ b/lib/eldap/Makefile
@@ -38,3 +38,6 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=asn ssl
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/eldap/doc/src/Makefile b/lib/eldap/doc/src/Makefile
index bf1eca267a..3eaee276a0 100644
--- a/lib/eldap/doc/src/Makefile
+++ b/lib/eldap/doc/src/Makefile
@@ -27,11 +27,6 @@ VSN=$(ELDAP_VSN)
APPLICATION=eldap
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -45,73 +40,6 @@ BOOK_FILES = book.xml
XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) $(XML_REF6_FILES) \
$(XML_PART_FILES) $(XML_CHAPTER_FILES)
-GIF_FILES =
-
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES) $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt valgrind:
-
-clean clean_docs clean_tex:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-# $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
-# $(INSTALL_DATA) $(MAN6DIR)/* "$(RELEASE_PATH)/man/man6"
-
-
-release_spec:
+IMAGE_FILES =
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml
index 790a2f4e26..0ea2e43841 100644
--- a/lib/eldap/doc/src/eldap.xml
+++ b/lib/eldap/doc/src/eldap.xml
@@ -55,38 +55,38 @@
<tag><c>modify_op()</c></tag>
<item><p>See
- <seealso marker="#mod_add/2">mod_add/2</seealso>,
- <seealso marker="#mod_delete/2">mod_delete/2</seealso>,
- <seealso marker="#mod_replace/2">mod_replace/2</seealso>
+ <seemfa marker="#mod_add/2">mod_add/2</seemfa>,
+ <seemfa marker="#mod_delete/2">mod_delete/2</seemfa>,
+ <seemfa marker="#mod_replace/2">mod_replace/2</seemfa>
</p></item>
<tag><c>scope()</c></tag>
<item><p>See
- <seealso marker="#baseObject/0">baseObject/0</seealso>,
- <seealso marker="#singleLevel/0">singleLevel/0</seealso>,
- <seealso marker="#wholeSubtree/0">wholeSubtree/0</seealso>
+ <seemfa marker="#baseObject/0">baseObject/0</seemfa>,
+ <seemfa marker="#singleLevel/0">singleLevel/0</seemfa>,
+ <seemfa marker="#wholeSubtree/0">wholeSubtree/0</seemfa>
</p></item>
<tag><c>dereference()</c></tag>
<item><p>See
- <seealso marker="#neverDerefAliases/0">neverDerefAliases/0</seealso>,
- <seealso marker="#derefInSearching/0">derefInSearching/0</seealso>,
- <seealso marker="#derefFindingBaseObj/0">derefFindingBaseObj/0</seealso>,
- <seealso marker="#derefAlways/0">derefAlways/0</seealso>
+ <seemfa marker="#neverDerefAliases/0">neverDerefAliases/0</seemfa>,
+ <seemfa marker="#derefInSearching/0">derefInSearching/0</seemfa>,
+ <seemfa marker="#derefFindingBaseObj/0">derefFindingBaseObj/0</seemfa>,
+ <seemfa marker="#derefAlways/0">derefAlways/0</seemfa>
</p></item>
<tag><c>filter()</c></tag>
<item><p>See
- <seealso marker="#present/1">present/1</seealso>,
- <seealso marker="#substrings/2">substrings/2</seealso>,
- <seealso marker="#equalityMatch/2">equalityMatch/2</seealso>,
- <seealso marker="#greaterOrEqual/2">greaterOrEqual/2</seealso>,
- <seealso marker="#lessOrEqual/2">lessOrEqual/2</seealso>,
- <seealso marker="#approxMatch/2">approxMatch/2</seealso>,
- <seealso marker="#extensibleMatch/2">extensibleMatch/2</seealso>,
- <seealso marker="#'and'/1">'and'/1</seealso>,
- <seealso marker="#'or'/1">'or'/1</seealso>,
- <seealso marker="#'not'/1">'not'/1</seealso>
+ <seemfa marker="#present/1">present/1</seemfa>,
+ <seemfa marker="#substrings/2">substrings/2</seemfa>,
+ <seemfa marker="#equalityMatch/2">equalityMatch/2</seemfa>,
+ <seemfa marker="#greaterOrEqual/2">greaterOrEqual/2</seemfa>,
+ <seemfa marker="#lessOrEqual/2">lessOrEqual/2</seemfa>,
+ <seemfa marker="#approxMatch/2">approxMatch/2</seemfa>,
+ <seemfa marker="#extensibleMatch/2">extensibleMatch/2</seemfa>,
+ <seemfa marker="#'and'/1">'and'/1</seemfa>,
+ <seemfa marker="#'or'/1">'or'/1</seemfa>,
+ <seemfa marker="#'not'/1">'not'/1</seemfa>
</p></item>
<tag><c>return_value() = </c></tag>
@@ -170,7 +170,7 @@
<item>Any error responded from ssl:connect/3</item>
</taglist>
<p>The <c>Timeout</c> parameter is for the actual tls upgrade (phase 2) while the timeout in
- <seealso marker="#open/2">eldap:open/2</seealso> is used for the initial negotiation about
+ <seemfa marker="#open/2">eldap:open/2</seemfa> is used for the initial negotiation about
upgrade (phase 1).
</p>
</desc>
@@ -274,7 +274,7 @@
<v>NewPasswd = string()</v>
</type>
<desc>
- <p>Modify the password of a user. See <seealso marker="#modify_password/4">modify_password/4</seealso>.</p>
+ <p>Modify the password of a user. See <seemfa marker="#modify_password/4">modify_password/4</seemfa>.</p>
</desc>
</func>
<func>
@@ -301,7 +301,7 @@
<item>
<p><c>OldPasswd</c>. Sometimes required by server policy
for a user to change their password. If not required, use
- <seealso marker="#modify_password/3">modify_password/3</seealso>.</p>
+ <seemfa marker="#modify_password/3">modify_password/3</seemfa>.</p>
</item>
</list>
</desc>
@@ -317,7 +317,7 @@
</type>
<desc>
<p> Modify the DN of an entry. <c>DeleteOldRDN</c> indicates
- whether the current RDN should be removed from the attribute list after the after operation.
+ whether the current RDN should be removed from the attribute list after the operation.
<c>NewSupDN</c> is the new parent that the RDN shall be moved to. If the old parent should
remain as parent, <c>NewSupDN</c> shall be "".</p>
<code>
@@ -347,7 +347,7 @@
search(Handle, [{base, "dc=example, dc=com"}, {filter, Filter}, {attributes, ["cn"]}]),
</code>
<p>The <c>timeout</c> option in the <c>SearchOptions</c> is for the ldap server, while
- the timeout in <seealso marker="#open/2">eldap:open/2</seealso> is used for each
+ the timeout in <seemfa marker="#open/2">eldap:open/2</seemfa> is used for each
individual request in the search operation.
</p>
</desc>
diff --git a/lib/eldap/doc/src/notes.xml b/lib/eldap/doc/src/notes.xml
index 946db5b93d..667c0075cd 100644
--- a/lib/eldap/doc/src/notes.xml
+++ b/lib/eldap/doc/src/notes.xml
@@ -31,6 +31,21 @@
</header>
<p>This document describes the changes made to the Eldap application.</p>
+<section><title>Eldap 1.2.9</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add ability to specify size limit on ldap requests</p>
+ <p>
+ Own Id: OTP-17166 Aux Id: PR-2904 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Eldap 1.2.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/eldap/include/eldap.hrl b/lib/eldap/include/eldap.hrl
index 7c12cd4f2b..b670de871f 100644
--- a/lib/eldap/include/eldap.hrl
+++ b/lib/eldap/include/eldap.hrl
@@ -7,6 +7,7 @@
-record(eldap_search, {
base = [], % Baseobject
filter = [], % Search conditions
+ size_limit = 0, % Setting default size limit to 0 makes it unlimited
scope=wholeSubtree, % Search scope
deref=derefAlways, % Dereference
attributes = [], % Attributes to be returned
diff --git a/lib/eldap/src/eldap.erl b/lib/eldap/src/eldap.erl
index 9b7e254dfe..c647083024 100644
--- a/lib/eldap/src/eldap.erl
+++ b/lib/eldap/src/eldap.erl
@@ -326,6 +326,8 @@ parse_search_args([{base, Base}|T],A) ->
parse_search_args(T,A#eldap_search{base = Base});
parse_search_args([{filter, Filter}|T],A) ->
parse_search_args(T,A#eldap_search{filter = Filter});
+parse_search_args([{size_limit, SizeLimit}|T],A) when is_integer(SizeLimit) ->
+ parse_search_args(T,A#eldap_search{size_limit = SizeLimit});
parse_search_args([{scope, Scope}|T],A) ->
parse_search_args(T,A#eldap_search{scope = Scope});
parse_search_args([{deref, Deref}|T],A) ->
@@ -749,7 +751,7 @@ do_search_0(Data, A, Controls) ->
Req = #'SearchRequest'{baseObject = A#eldap_search.base,
scope = v_scope(A#eldap_search.scope),
derefAliases = v_deref(A#eldap_search.deref),
- sizeLimit = 0, % no size limit
+ sizeLimit = v_size_limit(A#eldap_search.size_limit),
timeLimit = v_timeout(A#eldap_search.timeout),
typesOnly = v_bool(A#eldap_search.types_only),
filter = v_filter(A#eldap_search.filter),
@@ -777,6 +779,9 @@ collect_search_responses(Data, S, ID, {ok,Msg}, Acc, Ref)
success ->
log2(Data, "search reply = searchResDone ~n", []),
{ok,Acc,Ref,Data};
+ sizeLimitExceeded ->
+ log2(Data, "[TRUNCATED] search reply = searchResDone ~n", []),
+ {ok,Acc,Ref,Data};
referral ->
{{ok, {referral,R#'LDAPResult'.referral}}, Data};
Reason ->
@@ -1088,6 +1093,9 @@ v_bool(true) -> true;
v_bool(false) -> false;
v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}).
+v_size_limit(I) when is_integer(I), I>=0 -> I;
+v_size_limit(_I) -> throw({error,concat(["size_limit not positive integer: ",_I])}).
+
v_timeout(I) when is_integer(I), I>=0 -> I;
v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}).
diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl
index b871eda19c..683e1d5393 100644
--- a/lib/eldap/test/eldap_basic_SUITE.erl
+++ b/lib/eldap/test/eldap_basic_SUITE.erl
@@ -20,7 +20,63 @@
-module(eldap_basic_SUITE).
--compile(export_all).
+-export([
+ add_already_exists/1,
+ add_referral/1,
+ add_when_bound/1,
+ add_when_not_bound/1,
+ app/1,
+ appup/1,
+ bind/1,
+ client_side_add_timeout/1,
+ client_side_bind_timeout/1,
+ client_side_search_timeout/1,
+ client_side_start_tls_timeout/1,
+ close_after_tcp_error/1,
+ close_ret_val/1,
+ decode/1,
+ delete/1,
+ delete_referral/1,
+ elementary_search/1,
+ encode/1,
+ modify/1,
+ modify_dn_delete_old/1,
+ modify_dn_keep_old/1,
+ modify_referral/1,
+ more_add/1,
+ open_ret_val_error/1,
+ open_ret_val_success/1,
+ search_filter_and/1,
+ search_filter_and_not/1,
+ search_filter_equalityMatch/1,
+ search_filter_equalityMatch_objectClass_exists/1,
+ search_filter_final/1,
+ search_filter_initial/1,
+ search_filter_or/1,
+ search_filter_or_sizelimit_ok/1,
+ search_filter_or_sizelimit_exceeded/1,
+ search_filter_substring_any/1,
+ search_non_existant/1,
+ search_referral/1,
+ search_two_hits/1,
+ ssl_connection/1,
+ start_tls_on_ssl_should_fail/1,
+ start_tls_twice_should_fail/1,
+ tcp_connection/1,
+ tcp_connection_option/1
+ ]).
+
+-export([
+ all/0,
+ end_per_group/2,
+ end_per_suite/1,
+ end_per_testcase/2,
+ groups/0,
+ init_per_group/2,
+ init_per_suite/1,
+ init_per_testcase/2,
+ suite/0
+ ]).
%%-include_lib("common_test/include/ct.hrl").
-include_lib("common_test/include/ct.hrl").
@@ -65,6 +121,7 @@ groups() ->
more_add,
add_referral,
search_filter_equalityMatch,
+ search_filter_equalityMatch_objectClass_exists,
search_filter_substring_any,
search_filter_initial,
search_filter_final,
@@ -73,6 +130,8 @@ groups() ->
search_filter_and_not,
search_two_hits,
search_referral,
+ search_filter_or_sizelimit_ok,
+ search_filter_or_sizelimit_exceeded,
modify,
modify_referral,
delete,
@@ -208,8 +267,8 @@ init_per_testcase(ssl_connection, Config) ->
ct:log("ssl server waiting for connections...",[]),
{ok, S} = ssl:transport_accept(SSL_LSock),
ct:log("ssl:transport_accept/1 ok",[]),
- ok = ssl:ssl_accept(S),
- ct:log("ssl:ssl_accept/1 ok",[]),
+ {ok,_} = ssl:handshake(S),
+ ct:log("ssl:handshake/1 ok",[]),
L()
end)();
Other ->
@@ -516,6 +575,17 @@ search_filter_equalityMatch(Config) ->
scope=eldap:singleLevel()}).
%%%----------------------------------------------------------------
+search_filter_equalityMatch_objectClass_exists(Config) ->
+ BasePath = proplists:get_value(eldap_path, Config),
+ ExpectedDN = "cn=Jonas Jonsson," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(proplists:get_value(handle, Config),
+ #eldap_search{base = BasePath,
+ filter = eldap:'and'([eldap:equalityMatch("sn", "Jonsson"),
+ eldap:present("objectclass")]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
search_filter_substring_any(Config) ->
BasePath = proplists:get_value(eldap_path, Config),
ExpectedDN = "cn=Jonas Jonsson," ++ BasePath,
@@ -574,6 +644,39 @@ search_filter_or(Config) ->
ExpectedDNs = lists:sort([DN || #eldap_entry{object_name=DN} <- Es]).
%%%----------------------------------------------------------------
+search_filter_or_sizelimit_ok(Config) ->
+ H = proplists:get_value(handle, Config),
+ BasePath = proplists:get_value(eldap_path, Config),
+ ExpectedDNs = lists:sort(["cn=Foo Bar," ++ BasePath,
+ "ou=Team," ++ BasePath]),
+ {ok, #eldap_search_result{entries=Es}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:'or'([eldap:substrings("sn", [{any, "a"}]),
+ eldap:equalityMatch("ou","Team")]),
+ size_limit = 2,
+ scope=eldap:singleLevel()}),
+ ExpectedDNs = lists:sort([DN || #eldap_entry{object_name=DN} <- Es]).
+
+%%%----------------------------------------------------------------
+search_filter_or_sizelimit_exceeded(Config) ->
+ H = proplists:get_value(handle, Config),
+ BasePath = proplists:get_value(eldap_path, Config),
+ %% The quesry without the {size_limit,1} option would return two answers:
+ ExpectedDNs = ["cn=Foo Bar," ++ BasePath,
+ "ou=Team," ++ BasePath],
+ %% Expect exact one of the two answers, but we don't know which:
+ {ok, #eldap_search_result{entries=[E]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:'or'([eldap:substrings("sn", [{any, "a"}]),
+ eldap:equalityMatch("ou","Team")]),
+ size_limit = 1,
+ scope=eldap:singleLevel()}),
+ #eldap_entry{object_name=DN} = E,
+ true = lists:member(DN, ExpectedDNs).
+
+%%%----------------------------------------------------------------
search_filter_and_not(Config) ->
H = proplists:get_value(handle, Config),
BasePath = proplists:get_value(eldap_path, Config),
@@ -847,9 +950,9 @@ find_first_server(UseSSL, [{config,Key}|Ss]) ->
find_first_server(UseSSL, Ss)
end;
find_first_server(UseSSL, [{Host,Port}|Ss]) ->
- case eldap:open([Host],[{port,Port},{ssl,UseSSL}]) of
+ case eldap:open([Host],[{port,Port},{ssl,UseSSL},{timeout,10000}]) of
{ok,H} when UseSSL==false, Ss=/=[] ->
- case eldap:start_tls(H,[]) of
+ case eldap:start_tls(H, [], 10000) of
ok ->
ct:log("find_first_server ~p UseSSL=~p -> ok",[{Host,Port},UseSSL]),
eldap:close(H),
diff --git a/lib/eldap/test/make_certs.erl b/lib/eldap/test/make_certs.erl
index e8a13ae113..2436ebbe9a 100644
--- a/lib/eldap/test/make_certs.erl
+++ b/lib/eldap/test/make_certs.erl
@@ -19,9 +19,8 @@
%%
-module(make_certs).
--compile([export_all]).
-%-export([all/1, all/2, rootCA/2, intermediateCA/3, endusers/3, enduser/3, revoke/3, gencrl/2, verify/3]).
+-export([all/1, all/2, default_config/0, verify/4]).
-record(config, {commonName,
organizationalUnitName = "Erlang OTP",
diff --git a/lib/eldap/vsn.mk b/lib/eldap/vsn.mk
index 8f969e6945..f0b9745f08 100644
--- a/lib/eldap/vsn.mk
+++ b/lib/eldap/vsn.mk
@@ -1 +1 @@
-ELDAP_VSN = 1.2.8
+ELDAP_VSN = 1.2.9
diff --git a/lib/erl_docgen/Makefile b/lib/erl_docgen/Makefile
index 30ff2bf16e..acb1ea9776 100644
--- a/lib/erl_docgen/Makefile
+++ b/lib/erl_docgen/Makefile
@@ -36,4 +36,6 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=edoc xmerl syntax_tools crypto
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/erl_docgen/doc/src/Makefile b/lib/erl_docgen/doc/src/Makefile
index d6d2550425..33eb44a049 100644
--- a/lib/erl_docgen/doc/src/Makefile
+++ b/lib/erl_docgen/doc/src/Makefile
@@ -29,11 +29,6 @@ VSN=$(ERL_DOCGEN_VSN)
APPLICATION=erl_docgen
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -52,7 +47,8 @@ XML_CHAPTER_FILES = \
inline_tags.xml \
header_tags.xml \
character_entities.xml \
- block_tags.xml
+ block_tags.xml \
+ doc_storage.xml
BOOK_FILES = book.xml
@@ -64,75 +60,9 @@ TECHNICAL_DESCR_FILES =
EXAMPLE_FILES = \
example.txt
-GIF_FILES = \
+IMAGE_FILES = \
man.gif
# ----------------------------------------------------
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-docs: pdf html man
-
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-$(HTMLDIR)/example.txt: example.txt
- $(INSTALL_DATA) $< $@
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs examples $(HTML_REF_MAN_FILE)
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
- rm -f $(JD_HTML) $(JD_PACK)
-
-man: $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-examples: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6DIR)/* "$(RELEASE_PATH)/man/man6"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/erl_docgen/doc/src/block_tags.xml b/lib/erl_docgen/doc/src/block_tags.xml
index ceed9305f4..ecf71db479 100644
--- a/lib/erl_docgen/doc/src/block_tags.xml
+++ b/lib/erl_docgen/doc/src/block_tags.xml
@@ -36,13 +36,12 @@
<p>The following subset of block tags are common for all DTDs in
the OTP DTD suite:
<marker id="block_subset"></marker>
- <seealso marker="#pTAG">&lt;p&gt;</seealso>,
- <seealso marker="#preTAG">&lt;pre&gt;</seealso>,
- <seealso marker="#codeTAG">&lt;code&gt;</seealso>,
- <seealso marker="#listTAG">&lt;list&gt;</seealso>,
- <seealso marker="#taglistTAG">&lt;taglist&gt;</seealso>,
- <seealso marker="#codeincludeTAG">&lt;codeinclude&gt;</seealso> and
- <seealso marker="#erlevalTAG">&lt;erleval&gt;</seealso>.
+ <seeguide marker="#pTAG">&lt;p&gt;</seeguide>,
+ <seeguide marker="#preTAG">&lt;pre&gt;</seeguide>,
+ <seeguide marker="#codeTAG">&lt;code&gt;</seeguide>,
+ <seeguide marker="#listTAG">&lt;list&gt;</seeguide>,
+ <seeguide marker="#taglistTAG">&lt;taglist&gt;</seeguide>,
+ <seeguide marker="#codeincludeTAG">&lt;codeinclude&gt;</seeguide> and
</p>
<section>
@@ -87,8 +86,8 @@ sum([]) ->
<note>
<p>No tags are allowed within the tag and no
- <seealso marker="character_entities">character
- entities</seealso> are expanded.</p>
+ <seeguide marker="character_entities">character
+ entities</seeguide> are expanded.</p>
</note>
</section>
@@ -131,21 +130,6 @@ start(Pid) ->
</section>
<section>
- <marker id="erlevalTAG"></marker>
- <title>&lt;erleval&gt; - Erlang Evaluation</title>
-
- <p>Include the result from evaluating an Erlang expression. Example:
- </p>
- <code><![CDATA[
-<erleval expr="{A,b,C}={a,b,c}. "/>
- ]]></code>
- <p>results in:</p>
- <erleval expr="{A,b,C}={a,b,c}. "></erleval>
-
- <p>Note the '.' and space after the expression.</p>
- </section>
-
- <section>
<marker id="listTAG"></marker>
<title>&lt;list&gt; - List</title>
@@ -155,9 +139,9 @@ start(Pid) ->
<p>Lists contains list items, tag <c><![CDATA[<item>]]></c>, which
can contain plain text,
- the <seealso marker="#block_subset">common subset of block
- tags</seealso> and <seealso marker="inline_tags">inline
- tags</seealso>. Example:</p>
+ the <seeguide marker="#block_subset">common subset of block
+ tags</seeguide> and <seeguide marker="inline_tags">inline
+ tags</seeguide>. Example:</p>
<pre>
&lt;list type="ordered"&gt;
&lt;item&gt;Askosal:
@@ -189,7 +173,7 @@ start(Pid) ->
<p>Used as an anchor for hypertext references. The
<c><![CDATA[<marker>]]></c> tag is both a block- and an inline
tag and is described in
- the <seealso marker="inline_tags#markerTAG">Inline Tags</seealso>
+ the <seeguide marker="inline_tags#markerTAG">Inline Tags</seeguide>
section.</p>
</section>
@@ -198,7 +182,7 @@ start(Pid) ->
<title>&lt;p&gt; - Paragraph</title>
<p>Paragraphs contain plain text and
- <seealso marker="inline_tags">inline tags</seealso>. Example:</p>
+ <seeguide marker="inline_tags">inline tags</seeguide>. Example:</p>
<pre>
&lt;p&gt;I call specific attention to
the authority given by the &lt;em&gt;21st Amendment&lt;/em&gt;
@@ -240,8 +224,8 @@ start(Pid) ->
<title>&lt;pre&gt; - Pre-formatted Text</title>
<p>Used for documentation of system interaction. Can contain text,
- <seealso marker="inline_tags#seealsoTAG">seealso</seealso>,
- <seealso marker="inline_tags#urlTAG">url</seealso> and
+ <seeguide marker="inline_tags#seeTAG">&lt;see*&gt; tags</seeguide>,
+ <seeguide marker="inline_tags#urlTAG">&lt;url&gt;</seeguide> and
<c><![CDATA[<input>]]></c> tags.</p>
<p>The <c><![CDATA[<input>]]></c> tag is used to highlight user
@@ -268,8 +252,8 @@ Eshell V5.5.3 (abort with ^G)
2> <input>halt().</input>
</pre>
- <p>All <seealso marker="character_entities">character
- entities</seealso> are expanded.</p>
+ <p>All <seeguide marker="character_entities">character
+ entities</seeguide> are expanded.</p>
</section>
<section>
@@ -278,7 +262,7 @@ Eshell V5.5.3 (abort with ^G)
<p>Highlight quotations from other works, or dialog spoken by
characters in a narrative. Contains one or more
- <seealso marker="#pTAG">&lt;p&gt;</seealso> tags. Example:</p>
+ <seeguide marker="#pTAG">&lt;p&gt;</seeguide> tags. Example:</p>
<pre>
&lt;p&gt;Whereas Section 217(a) of the Act of Congress entitled
"An Act ..." approved June 16, 1933, provides as follows:&lt;/p&gt;
@@ -304,16 +288,16 @@ Eshell V5.5.3 (abort with ^G)
<c><![CDATA[<item>]]></c>.</p>
<p><c><![CDATA[<tag>]]></c> can contain plain text,
- <seealso marker="inline_tags#cTAG">&lt;c&gt;</seealso>,
- <seealso marker="inline_tags#emTAG">&lt;em&gt;</seealso>,
- <seealso marker="inline_tags#seealsoTAG">&lt;seealso&gt;</seealso>
- and <seealso marker="inline_tags#urlTAG">&lt;url&gt;</seealso>
+ <seeguide marker="inline_tags#cTAG">&lt;c&gt;</seeguide>,
+ <seeguide marker="inline_tags#emTAG">&lt;em&gt;</seeguide>,
+ <seeguide marker="inline_tags#seeTAG">&lt;see*&gt; tags</seeguide>
+ and <seeguide marker="inline_tags#urlTAG">&lt;url&gt;</seeguide>
tags.</p>
<p><c><![CDATA[<item>]]></c> can contain plain text,
- the <seealso marker="#block_subset">common subset of block
- tags</seealso> and <seealso marker="inline_tags">inline
- tags</seealso>. Example:</p>
+ the <seeguide marker="#block_subset">common subset of block
+ tags</seeguide> and <seeguide marker="inline_tags">inline
+ tags</seeguide>. Example:</p>
<pre>
&lt;taglist>
&lt;tag>&lt;c>eacces&lt;/c>&lt;/tag>
@@ -393,7 +377,7 @@ Eshell V5.5.3 (abort with ^G)
"<c>left</c>" and "<c>middle</c>".</p>
<p>Each cell contains plain text and
- <seealso marker="inline_tags">inline tags</seealso>. Example:</p>
+ <seeguide marker="inline_tags">inline tags</seeguide>. Example:</p>
<pre><![CDATA[
<table>
<row>
diff --git a/lib/erl_docgen/doc/src/doc-build.xml b/lib/erl_docgen/doc/src/doc-build.xml
index 17e13bff81..ee5bd260da 100644
--- a/lib/erl_docgen/doc/src/doc-build.xml
+++ b/lib/erl_docgen/doc/src/doc-build.xml
@@ -43,7 +43,7 @@
</p>
<code>
- 1> escript $(ERL_TOP)/lib/erl_docgen/priv/bin/xml_from_edoc.escript ex1.erl
+ 1> escript $ERL_TOP/lib/erl_docgen/priv/bin/xml_from_edoc.escript ex1.erl
</code>
</section>
<section>
@@ -57,7 +57,7 @@
</p>
<code>
- 1> escript $(ERL_TOP)/lib/erl_docgen/priv/bin/codeline_preprocessing.escript \
+ 1> escript $ERL_TOP/lib/erl_docgen/priv/bin/codeline_preprocessing.escript \
ex1.xmlsrc ex1.xml
</code>
</section>
@@ -116,9 +116,9 @@
<code>
1> xsltproc --noout --stringparam outdir /tmp/myhtmldoc \
- --stringparam docgen $(ERL_TOP)/lib/erl_docgen \
+ --stringparam docgen $ERL_TOP/lib/erl_docgen \
--stringparam topdocdir . \
- --stringparam pdfdir "$(PDFDIR)" \
+ --stringparam pdfdir $PDFDIR \
--xinclude \
--stringparam gendate "December 5 2011" \
--stringparam appname MyApp \
diff --git a/lib/erl_docgen/doc/src/doc_storage.xml b/lib/erl_docgen/doc/src/doc_storage.xml
new file mode 100644
index 0000000000..2c719b3f20
--- /dev/null
+++ b/lib/erl_docgen/doc/src/doc_storage.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>1997</year><year>2016</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+ <title>EEP-48: Implementation in Erlang/OTP</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>doc_storage.xml</file>
+ </header>
+
+ <section>
+ <title>EEP-48: Documentation storage and format</title>
+ <p><seeguide marker="kernel:eep48_chapter">EEP-48</seeguide>
+ defines a common documentation storage format for module documentation in the Erlang/OTP
+ ecosystem. Erl_Docgen can generate documentation in this format from XML files following
+ the DTD's descibed in the other User's Guides in this application.</p>
+ <p>Some special considerations have to be taken when writing documentation that
+ should also be available through EEP-48 style storage.</p>
+ <list>
+ <item>The <c>#PCDATA</c> within <c>&lt;name&gt;</c> tags must be parseable
+ to figure out the arity of the function.</item>
+ <item>It is not allowed to mix <c>&lt;name&gt;</c> tags with #PCDATA and attributes.</item>
+ <item>All <c>&lt;name&gt;</c> tags within <c>&lt;func&gt;</c>
+ has to have a <c>since</c> attribute.</item>
+ <item>All callback function documentations have to start with a <c>Module</c> prefix.</item>
+ </list>
+ </section>
+
+ <section>
+ <title>Erlang Documentation Format</title>
+ <p>When generating documentation for EEP-48 Erl_Docgen uses the format mime type
+ &lt;&lt;"application/erlang+html"&gt;&gt;. The documentation content is an Erlang
+ term that represents an HTML like structure.</p>
+ <code>
+-type chunk_elements() :: [chunk_element()].
+-type chunk_element() :: {chunk_element_type(),chunk_element_attrs(),
+ chunk_elements()} | unicode:unicode_binary().
+-type chunk_element_attrs() :: [chunk_element_attr()].
+-type chunk_element_attr() :: {atom(),unicode:unicode_binary()}.
+-type chunk_element_type() :: chunk_element_inline_type() | chunk_element_block_type().
+-type chunk_element_inline_type() :: a | code | em | i.
+-type chunk_element_block_type() :: p | 'div' | br | pre | ul |
+ ol | li | dl | dt | dd | h1 | h2 | h3.
+ </code>
+ <p>The different element types follow their HTML meaning when rendered.
+ The following are some general rules for how the chunk elements are allowed
+ to be generated.</p>
+ <list>
+ <item>Inline and <c>pre</c> elements are not allowed to contain block elements.</item>
+ <item><c>p</c> elements are not allowed to be nested.</item>
+ </list>
+ <p>The attributes on some elements have a special meaning.</p>
+ <taglist>
+ <tag><c>{'div',[{class,unicode:unicode_binary()}],_}</c></tag>
+ <item>The class name will be used to provide styling to the content in the div.
+ The types of classes used by Erlang/OTP are: <c>warning</c>, <c>note</c>, <c>do</c>,
+ <c>dont</c> and <c>quote</c>.</item>
+ <tag><c><![CDATA[{ul,[{class,<<"types">>}],_}]]></c></tag>
+ <item>This is a list containing type documentation.</item>
+ <tag><c><![CDATA[{li,[{name,TypeName :: unicode:unicode_binary()}],_}]]></c></tag>
+ <item>A list item with a type specification located in the metadata of this modules
+ EEP-48 documentation. The implementation should look for the AST representation of
+ the type under the <c>types</c> key. This attribute is only valid under a <c>ul</c>
+ with class &lt;&lt;"types"&gt;&gt;.</item>
+ <tag><c><![CDATA[{li,[{class,<<"type">>}],_}]]></c></tag>
+ <item>A list item with the type described in the Erlang Documentation Format.
+ This attribute is only valid under a <c>ul</c> with class &lt;&lt;"types"&gt;&gt;.</item>
+ <tag><c><![CDATA[{li,[{class,<<"description">>}],_}]]></c></tag>
+ <item>A list item with the description of the type previous in the list.
+ This attribute is only valid under a <c>ul</c> with class &lt;&lt;"types"&gt;&gt;.</item>
+ </taglist>
+ <p>The <seemfa marker="stdlib:shell_docs#validate/1"><c>shell_docs:validate/1</c></seemfa>
+ function can be used to do a validation of the Erlang Documentation Format.</p>
+ </section>
+
+ <section>
+ <title>Erlang Documentation extra Metadata</title>
+ <p>Erlang/OTP uses some extra metadata fields to embed more information into the EEP-48 docs.</p>
+ <list>
+ <item>Fields on module level:
+ <taglist>
+ <tag><c>otp_doc_vsn := {non_neg_integer(),non_neg_integer(),non_neg_integer()}</c></tag>
+ <item>Describes the version of the Erlang Documentation Format used
+ within this module</item>
+ <tag><c>types := #{ TypeName :: unicode:unicode_binary() => TypeAST }</c></tag>
+ <item>A map containing the AST of the types that are part of this module.
+ This map is used to by functions and callbacks to render the types inline
+ into their documentation.</item>
+ </taglist>
+ </item>
+ <item>Fields on functions and types:
+ <taglist>
+ <tag><c>signature := SpecAST</c></tag>
+ <item>The spec AST associated with this function. It is used to render a more
+ descriptive slogan for the documentation entry.</item>
+ <tag><c>equiv := {Type,Name,Arity}</c></tag>
+ <item>The current function/type shares documentation with another function/type.
+ This means that if this and the target function/type are to be shown at the
+ same time only the prototype of this function/type should will be displayed
+ and the documentation will use a common body of text.</item>
+ </taglist>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p>
+ <seeguide marker="kernel:eep48_chapter"></seeguide>
+ <seeerl marker="stdlib:shell_docs"><c>shell_docs(3)</c></seeerl>,
+ <seemfa marker="kernel:code#get_doc/1"><c>code:get_doc(3)</c></seemfa>
+ </p>
+ </section>
+
+</chapter>
diff --git a/lib/erl_docgen/doc/src/inline_tags.xml b/lib/erl_docgen/doc/src/inline_tags.xml
index 25b0cd4d87..d56eac41ca 100644
--- a/lib/erl_docgen/doc/src/inline_tags.xml
+++ b/lib/erl_docgen/doc/src/inline_tags.xml
@@ -39,7 +39,7 @@
<p>Forces a newline. The <c><![CDATA[<br>]]></c> tag is both a
block- and an inline tag and is described in
- the <seealso marker="block_tags#brTAG">Block Tags</seealso>
+ the <seeguide marker="block_tags#brTAG">Block Tags</seeguide>
section.</p>
</section>
@@ -49,9 +49,9 @@
<p>Highlights things like variables and file names in a text flow.
Can contain plain text only. Newlines and tabs are ignored as
- opposed to the <seealso marker="block_tags#codeTAG">code</seealso>
- tag. All <seealso marker="character_entities">character
- entities</seealso> are expanded. Example:</p>
+ opposed to the <seeguide marker="block_tags#codeTAG">code</seeguide>
+ tag. All <seeguide marker="character_entities">character
+ entities</seeguide> are expanded. Example:</p>
<pre>
&lt;p>Returns &lt;c>true&lt;/c> if &lt;c>Term&lt;/c> is an integer.&lt;/p>
</pre>
@@ -72,7 +72,7 @@
<p>The application <em>must</em> be up and running.</p>
<p>Contains plain text or a
- <seealso marker="#cTAG">&lt;c&gt;</seealso> tag.</p>
+ <seeguide marker="#cTAG">&lt;c&gt;</seeguide> tag.</p>
</section>
<section>
@@ -86,77 +86,104 @@
&lt;marker id="marker_example"/&gt;
</pre>
- <p>The <seealso marker="#seealsoTAG">&lt;seealso&gt;</seealso> tag
- is used to refer to the marker.</p>
+ <p>The <seeguide marker="#seeTAG">&lt;see*&gt;</seeguide> tags
+ are used to refer to the marker.</p>
<p>The <c><![CDATA[<marker>]]></c> tag is both a block- and an
inline tag.</p>
</section>
- <!-- section>
- <marker id="pathTAG"></marker>
- <title>&lt;path&gt; - Path</title>
-
- <p>Highlights file paths. The attributes <c>unix</c> and
- <c>windows</c> makes it possible to specify different paths for
- different file path notations. Default for both are "".
- Example:</p>
- <pre>
-&lt;p>Look at the &lt;path unix=".profile" windows="win.ini"&gt;start-up file&lt;/path&gt;
- if you intend to alter the initial behavior.&lt;/p>
- </pre>
- <p>If no <c>ptype</c> option is specified when calling
- <seealso marker="docb_transform#file/1">docb_transform:file/1,2</seealso>,
- this simply results in:</p>
- <p>"Look at the <path>start-up file</path>
- if you intend to alter the initial behavior."</p>
-
- <p>If both the options <c>{ptype,unix}</c> and
- <c>{ptype,windows}</c> are specified, the example instead results
- in:</p>
- <p>"Look at the <path unix=".profile" windows="win.ini">start-up file</path>
- if you intend to alter the initial behavior."</p>
- </section -->
-
<section>
- <marker id="seealsoTAG"></marker>
- <title>&lt;seealso&gt; - Local Cross Reference</title>
-
+ <marker id="seeTAG"/>
+ <title>&lt;see*&gt; - See tags</title>
<p>A cross reference (hypertext link) to a marker in the same file,
a marker in another file, or (the top of) another file, given by
- the <c>marker</c> attribute. Must contain plain text. Examples:
- </p>
-
- <pre><![CDATA[
- <seealso marker="#marker_example">marker example</seealso>
- ]]></pre>
- <p>results in:
- <seealso marker="#marker_example">marker example</seealso>
- (a hypertext link to the marker example above).</p>
-
- <pre><![CDATA[
- <seealso marker="block_tags#markerTAG">marker tag</seealso>
- ]]></pre>
- <p>results in:
- <seealso marker="block_tags#markerTAG">marker tag</seealso>
- (a hypertext link to the marker section in the Block Tags
- chapter).</p>
-
- <pre><![CDATA[
- <seealso marker="overview">Overview</seealso>
- ]]></pre>
- <p>results in:
- <seealso marker="overview">Overview</seealso>
- (a hypertext link to the Overview chapter).</p>
-
- <p>Note the use of "#" before the name of the marker. Note also
- that the filename extension <c>.html</c> is omitted. This is
- because the default behavior is to translate
- <c><![CDATA[<seealso marker="File#Marker">text</seealso>]]></c>
- to <c><![CDATA[<A HREF="File.html#Marker">text</A>]]></c>.</p>
-
+ the <c>marker</c> attribute. The syntax used within the <c>marker</c>
+ attribute is <c>application:file#anchor</c> for the general case.
+ <c>application</c> and <c>file</c> can be omitted if the link target
+ is the current application or file.</p>
+ <p>There are several different see tags that are to be used depending on
+ what it is that they point to.</p>
+ <taglist>
+ <tag>&lt;seemfa&gt;</tag>
+ <item>
+ <p>Points to an MFA using the syntax <c>application:module#function/arity</c>.
+ These links must point to functions documented in a &lt;funcs&gt; section.
+ Examples:</p>
+ <pre><![CDATA[
+<seemfa marker="stdlib:string#length/1">string:length/1</seemfa>
+<seemfa marker="string#length/1">string:length/1</seemfa>
+<seemfa marker="#length/1">string:length/1</seemfa>
+]]></pre>
+ results in: <seemfa marker="stdlib:string#length/1">string:length/1</seemfa>.
+ </item>
+ <tag>&lt;seeerl&gt;</tag>
+ <item>
+ <p>Points to an Erlang module or a custom <seeguide marker="#markerTAG">marker</seeguide>
+ within a module. Example:</p>
+ <pre><![CDATA[
+<seeerl marker="stdlib:string">string(3)</seemfa>,
+<seeerl marker="stdlib:string#oldapi">Old API in string</seemfa>
+]]></pre>
+ results in: <seeerl marker="stdlib:string">string(3)</seeerl>,<seeerl marker="stdlib:string#oldapi">Old API in string</seeerl>.
+ </item>
+ <tag>&lt;seetype&gt;</tag>
+ <item>
+ <p>Points to a type using the syntax <c>application:module#type</c>.
+ These links must point to types documented in a &lt;datatypes&gt; section.
+ Example:</p>
+ <pre><![CDATA[
+<seetype marker="stdlib:string#grapheme_cluster">string::grapheme_cluster()</seetype>
+]]></pre>
+ results in: <seetype marker="stdlib:string#grapheme_cluster">string::grapheme_cluster()</seetype>.
+ </item>
+ <tag>&lt;seeapp&gt;</tag>
+ <item><p>Points to the application documentation. <c>index</c> can be used as the target file.
+ Example:</p>
+ <pre><![CDATA[
+<seeapp marker="stdlib:STDLIB_app">STDLIB app</seeapp>,
+<seeapp marker="stdlib:index">STDLIB index</seeapp>
+]]></pre>
+ results in: <seeapp marker="stdlib:STDLIB_app">STDLIB</seeapp>, <seeapp marker="stdlib:index">STDLIB</seeapp>.
+ </item>
+ <tag>&lt;seecom&gt;</tag>
+ <item>
+ <p>Points to the documentation of any command line utility. Example:</p>
+ <pre><![CDATA[
+<seecom marker="erts:epmd">epmd</seecom>
+]]></pre>
+ results in: <seecom marker="erts:epmd">epmd</seecom>.
+ </item>
+ <tag>&lt;seecref&gt;</tag>
+ <item>
+ <p>Points to the documentation of any C reference. Example:</p>
+ <pre><![CDATA[
+<seecref marker="erts:erl_nif">erl_nif</seecref>
+]]></pre>
+ results in: <seecref marker="erts:erl_nif">erl_nif</seecref>.
+ </item>
+ <tag>&lt;seefile&gt;</tag>
+ <item>
+ <p>Points to the documentation of a file format. Example:</p>
+ <pre><![CDATA[
+<seefile marker="kernel:config">config(3)</seefile>
+]]></pre>
+ results in: <seefile marker="kernel:config">config(3)</seefile>.
+ </item>
+ <tag>&lt;seeguide&gt;</tag>
+ <item>
+ <p>Points to the User&quot;s Guide of any application.
+ <c>index</c> can be used as the target file. Example:</p>
+ <pre><![CDATA[
+<seeguide marker="kernel:index">Kernel User's Guide Index</seeguide>,
+<seeguide marker="kernel:logger_chapter">Logging in the Kernel User's Guide</seeguide>
+]]></pre>
+ results in: <seeguide marker="kernel:index">Kernel User's Guide Index</seeguide>,
+ <seeguide marker="kernel:logger_chapter">Logging in the Kernel User's Guide</seeguide>.
+ </item>
+ </taglist>
</section>
-
+
<section>
<marker id="urlTAG"></marker>
<title>&lt;url&gt; - Non-Local Cross Reference</title>
@@ -171,45 +198,5 @@
</p>
</section>
- <section>
- <marker id="termTAG"></marker>
- <marker id="termdefTAG"></marker>
- <title>&lt;term&gt;, &lt;termdef&gt; - Glossary</title>
-
- <p>Used to highlight a term with a local (for this document only) or
- global definition. The identity of the term is given by
- the <c>id</c> attribute.</p>
-
- <p>For a locally defined term, the tag contains a
- <c>&lt;termdef&gt;</c>, which in turn contains an explanation of
- the term as plain text. Example:</p>
- <pre><![CDATA[
-<term id="HTML"><termdef>Hyper-Text Markup Language</termdef></term>
- ]]></pre>
-
- <p>In the generated HTML, it is the term name which will be visible.
- For locally defined terms, the id and the name are the same.
- The name has a hypertext link to the definition in the glossary.
- Example:</p>
- <pre><![CDATA[
-<term id="HTML"><termdef>Hyper-Text Markup Language</termdef></term>
- ]]></pre>
- <p>results in: <term id="HTML"><termdef>Hyper-Text Markup Language</termdef></term>
- </p>
-
- <p>If a term is defined both locally and globally, the global
- definition takes precedence.</p>
- </section>
-
- <section>
- <marker id="citeTAG"></marker>
- <marker id="citedefTAG"></marker>
- <title>&lt;cite&gt;, &lt;citedef&gt; - Bibliography</title>
-
- <p>Works the same way as <c>&lt;term&gt;</c> and
- <c>&lt;termdef&gt;</c>, but for a bibliography list rather than
- a glossary.</p>
-
- </section>
</chapter>
diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml
index e5076a4790..603fcdf1be 100644
--- a/lib/erl_docgen/doc/src/notes.xml
+++ b/lib/erl_docgen/doc/src/notes.xml
@@ -31,7 +31,97 @@
</header>
<p>This document describes the changes made to the <em>erl_docgen</em> application.</p>
- <section><title>Erl_Docgen 0.11</title>
+ <section><title>Erl_Docgen 1.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix links in titles to github and anchors to work.</p>
+ <p>
+ Own Id: OTP-17013</p>
+ </item>
+ <item>
+ <p>
+ Fix some typing errors on variable names in documentation
+ examples.</p>
+ <p>
+ Own Id: OTP-17065 Aux Id: ERL-1386 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Docgen 1.0.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Repaired lost function "since" versions in the right
+ margin of the module reference HTML documentation.</p>
+ <p>
+ Own Id: OTP-16661 Aux Id: ERL-1259 </p>
+ </item>
+ <item>
+ <p>Remove erlang compilation warnings and trailing
+ whitespaces.</p>
+ <p>
+ Own Id: OTP-16675</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Docgen 1.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Embedded documentation (also known as Documentation
+ Chunks) is now also available in the form of files
+ according to <url
+ href="https://www.erlang.org/erlang-enhancement-proposals/eep-0048.html">EEP-48</url>.
+ The Documentation Chunks are produced by default when
+ building the other Erlang/OTP documentation. If you want
+ to only build the embedded documentation you can pass the
+ <c>DOC_TARGETS=chunks</c> environment variable to make.</p>
+ <p>
+ Own Id: OTP-16406</p>
+ </item>
+ <item>
+ <p>
+ Minor DTD additions.</p>
+ <p>
+ Own Id: OTP-16497</p>
+ </item>
+ <item>
+ <p>
+ The <c>seealso</c> tag has been replaced with type aware
+ tags instead. The new tags are:
+ <c>seemfa|seeerl|seetype|seeapp|seecom|seecref|seefile|seeguide</c>.</p>
+ <p>
+ <c>fsdescription</c> has been added for adding a title to
+ groups of functions, for instance Module Callbacks.</p>
+ <p>
+ The <c>dtd</c>s of all documentation files have been
+ trimmed from all unused or rarely-used tags.</p>
+ <p>
+ Unused <c>dtd</c>s have been removed.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16503</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Docgen 0.11</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/erl_docgen/doc/src/overview.xml b/lib/erl_docgen/doc/src/overview.xml
index 5c4576000c..ef6986024a 100644
--- a/lib/erl_docgen/doc/src/overview.xml
+++ b/lib/erl_docgen/doc/src/overview.xml
@@ -39,26 +39,26 @@
<tag><em>User's Guide</em></tag>
<item>
<p>(DTD:
- <seealso marker="user_guide_dtds#partDTD">part</seealso>)
+ <seeguide marker="user_guide_dtds#partDTD">part</seeguide>)
A collection of chapters
- (<seealso marker="user_guide_dtds#chapterDTD">chapter</seealso>).
+ (<seeguide marker="user_guide_dtds#chapterDTD">chapter</seeguide>).
</p>
</item>
<tag><em>Reference Manual</em></tag>
<item>
<p>(DTD:
- <seealso marker="refman_dtds#applicationDTD">application</seealso>
+ <seeguide marker="refman_dtds#applicationDTD">application</seeguide>
A collection of manual pages for modules
- (<seealso marker="refman_dtds#erlrefDTD">erlref</seealso>),
+ (<seeguide marker="refman_dtds#erlrefDTD">erlref</seeguide>),
applications
- (<seealso marker="refman_dtds#apprefDTD">appref</seealso>),
+ (<seeguide marker="refman_dtds#apprefDTD">appref</seeguide>),
commands
- (<seealso marker="refman_dtds#comrefDTD">comref</seealso>),
+ (<seeguide marker="refman_dtds#comrefDTD">comref</seeguide>),
C libraries
- (<seealso marker="refman_dtds#crefDTD">cref</seealso>) and
+ (<seeguide marker="refman_dtds#crefDTD">cref</seeguide>) and
files
- (<seealso marker="refman_dtds#filerefDTD">fileref</seealso>).
+ (<seeguide marker="refman_dtds#filerefDTD">fileref</seeguide>).
</p>
</item>
@@ -95,8 +95,8 @@
use the same DTD.</p>
<p>The basic set of tags are divided into two categories:
- <seealso marker="block_tags">block tags</seealso> and
- <seealso marker="inline_tags">inline tags</seealso>. Block tags
+ <seeguide marker="block_tags">block tags</seeguide> and
+ <seeguide marker="inline_tags">inline tags</seeguide>. Block tags
typically define a separate block of information, like a
paragraph or a list. Inline tags are typically used within block
tags, for example a highlighted word within a paragraph.</p>
diff --git a/lib/erl_docgen/doc/src/part.xml b/lib/erl_docgen/doc/src/part.xml
index 0e97af7169..91ff979af5 100644
--- a/lib/erl_docgen/doc/src/part.xml
+++ b/lib/erl_docgen/doc/src/part.xml
@@ -40,5 +40,6 @@
<xi:include href="block_tags.xml"/>
<xi:include href="inline_tags.xml"/>
<xi:include href="character_entities.xml"/>
+ <xi:include href="doc_storage.xml"/>
</part>
diff --git a/lib/erl_docgen/doc/src/refman_dtds.xml b/lib/erl_docgen/doc/src/refman_dtds.xml
index ee3e2f09ad..322e9c7442 100644
--- a/lib/erl_docgen/doc/src/refman_dtds.xml
+++ b/lib/erl_docgen/doc/src/refman_dtds.xml
@@ -55,11 +55,11 @@
groups a set of manual pages into one unit. The structure is
similar to the part DTD: first an introduction and then the manual
pages, written in separate files with the
- <seealso marker="#apprefDTD">appref</seealso>,
- <seealso marker="#comrefDTD">comref</seealso>,
- <seealso marker="#crefDTD">cref</seealso>,
- <seealso marker="#erlrefDTD">erlref</seealso>, or
- <seealso marker="#filerefDTD">fileref</seealso> DTD.</p>
+ <seeguide marker="#apprefDTD">appref</seeguide>,
+ <seeguide marker="#comrefDTD">comref</seeguide>,
+ <seeguide marker="#crefDTD">cref</seeguide>,
+ <seeguide marker="#erlrefDTD">erlref</seeguide>, or
+ <seeguide marker="#filerefDTD">fileref</seeguide> DTD.</p>
<p>Example:</p>
<pre>
@@ -91,11 +91,11 @@
<p>The top level tag of an <c>application</c> DTD.</p>
<p>Contains a
- <seealso marker="header_tags">&lt;header&gt;</seealso>,
+ <seeguide marker="header_tags">&lt;header&gt;</seeguide>,
an optional
- <seealso marker="user_guide_dtds#descriptionTAG">&lt;description&gt;</seealso>,
+ <seeguide marker="user_guide_dtds#descriptionTAG">&lt;description&gt;</seeguide>,
followed by one or more
- <seealso marker="user_guide_dtds#includeTAG">&lt;include&gt;</seealso>.
+ <seeguide marker="user_guide_dtds#includeTAG">&lt;include&gt;</seeguide>.
</p>
</section>
@@ -148,15 +148,15 @@
<p>The top level tag of an <c>appref</c> DTD.</p>
<p>Contains
- <seealso marker="header_tags#headerTAG">&lt;header&gt;</seealso>,
- <seealso marker="#appTAG">&lt;app&gt;</seealso>,
- <seealso marker="#appsummaryTAG">&lt;appsummary&gt;</seealso>,
- <seealso marker="#descriptionTAG">&lt;description&gt;</seealso>,
+ <seeguide marker="header_tags#headerTAG">&lt;header&gt;</seeguide>,
+ <seeguide marker="#appTAG">&lt;app&gt;</seeguide>,
+ <seeguide marker="#appsummaryTAG">&lt;appsummary&gt;</seeguide>,
+ <seeguide marker="#descriptionTAG">&lt;description&gt;</seeguide>,
zero or more
- <seealso marker="#sectionTAG">&lt;section&gt;</seealso> and
- <seealso marker="#funcsTAG">&lt;funcs&gt;</seealso>,
+ <seeguide marker="#sectionTAG">&lt;section&gt;</seeguide> and
+ <seeguide marker="#funcsTAG">&lt;funcs&gt;</seeguide>,
followed by zero or more
- <seealso marker="#authorsTAG">&lt;authors&gt;</seealso>.</p>
+ <seeguide marker="#authorsTAG">&lt;authors&gt;</seeguide>.</p>
</section>
<section>
@@ -232,15 +232,15 @@
<p>The top level tag for a <c>comref</c> DTD.</p>
<p>Contains
- <seealso marker="header_tags#headerTAG">&lt;header&gt;</seealso>,
- <seealso marker="#comTAG">&lt;com&gt;</seealso>,
- <seealso marker="#comsummaryTAG">&lt;comsummary&gt;</seealso>,
- <seealso marker="#descriptionTAG">&lt;description&gt;</seealso>,
+ <seeguide marker="header_tags#headerTAG">&lt;header&gt;</seeguide>,
+ <seeguide marker="#comTAG">&lt;com&gt;</seeguide>,
+ <seeguide marker="#comsummaryTAG">&lt;comsummary&gt;</seeguide>,
+ <seeguide marker="#descriptionTAG">&lt;description&gt;</seeguide>,
zero or more
- <seealso marker="#sectionTAG">&lt;section&gt;</seealso> and
- <seealso marker="#funcsTAG">&lt;funcs&gt;</seealso>,
+ <seeguide marker="#sectionTAG">&lt;section&gt;</seeguide> and
+ <seeguide marker="#funcsTAG">&lt;funcs&gt;</seeguide>,
followed by zero or more
- <seealso marker="#authorsTAG">&lt;authors&gt;</seealso>.</p>
+ <seeguide marker="#authorsTAG">&lt;authors&gt;</seeguide>.</p>
</section>
<section>
@@ -320,15 +320,15 @@
<p>The top level tag for a <c>cref</c> DTD.</p>
<p>Contains
- <seealso marker="header_tags#headerTAG">&lt;header&gt;</seealso>,
- <seealso marker="#libTAG">&lt;lib&gt;</seealso>,
- <seealso marker="#libsummaryTAG">&lt;libsummary&gt;</seealso>,
- <seealso marker="#descriptionTAG">&lt;description&gt;</seealso>,
+ <seeguide marker="header_tags#headerTAG">&lt;header&gt;</seeguide>,
+ <seeguide marker="#libTAG">&lt;lib&gt;</seeguide>,
+ <seeguide marker="#libsummaryTAG">&lt;libsummary&gt;</seeguide>,
+ <seeguide marker="#descriptionTAG">&lt;description&gt;</seeguide>,
zero or more
- <seealso marker="#sectionTAG">&lt;section&gt;</seealso> and
- <seealso marker="#funcsTAG">&lt;funcs&gt;</seealso>, followed by
+ <seeguide marker="#sectionTAG">&lt;section&gt;</seeguide> and
+ <seeguide marker="#funcsTAG">&lt;funcs&gt;</seeguide>, followed by
zero or more
- <seealso marker="#authorsTAG">&lt;authors&gt;</seealso>.</p>
+ <seeguide marker="#authorsTAG">&lt;authors&gt;</seeguide>.</p>
</section>
<section>
@@ -412,15 +412,15 @@
<p>The top level tag for an <c>erlref</c> DTD.</p>
<p>Contains
- <seealso marker="header_tags#headerTAG">&lt;header&gt;</seealso>,
- <seealso marker="#moduleTAG">&lt;module&gt;</seealso>,
- <seealso marker="#modulesummaryTAG">&lt;modulesummary&gt;</seealso>,
- <seealso marker="#descriptionTAG">&lt;description&gt;</seealso>,
+ <seeguide marker="header_tags#headerTAG">&lt;header&gt;</seeguide>,
+ <seeguide marker="#moduleTAG">&lt;module&gt;</seeguide>,
+ <seeguide marker="#modulesummaryTAG">&lt;modulesummary&gt;</seeguide>,
+ <seeguide marker="#descriptionTAG">&lt;description&gt;</seeguide>,
zero or more
- <seealso marker="#sectionTAG">&lt;section&gt;</seealso> and
- <seealso marker="#funcsTAG">&lt;funcs&gt;</seealso>,
+ <seeguide marker="#sectionTAG">&lt;section&gt;</seeguide> and
+ <seeguide marker="#funcsTAG">&lt;funcs&gt;</seeguide>,
followed by zero or more
- <seealso marker="#authorsTAG">&lt;authors&gt;</seealso>.</p>
+ <seeguide marker="#authorsTAG">&lt;authors&gt;</seeguide>.</p>
</section>
<section>
@@ -490,15 +490,15 @@
<p>The top level tag for a <c>fileref</c> DTD.</p>
<p>Contains
- <seealso marker="header_tags#headerTAG">&lt;header&gt;</seealso>,
- <seealso marker="#fileTAG">&lt;file&gt;</seealso>,
- <seealso marker="#filesummaryTAG">&lt;filesummary&gt;</seealso>,
- <seealso marker="#descriptionTAG">&lt;description&gt;</seealso>,
+ <seeguide marker="header_tags#headerTAG">&lt;header&gt;</seeguide>,
+ <seeguide marker="#fileTAG">&lt;file&gt;</seeguide>,
+ <seeguide marker="#filesummaryTAG">&lt;filesummary&gt;</seeguide>,
+ <seeguide marker="#descriptionTAG">&lt;description&gt;</seeguide>,
zero or more
- <seealso marker="#sectionTAG">&lt;section&gt;</seealso> and
- <seealso marker="#funcsTAG">&lt;funcs&gt;</seealso>,
+ <seeguide marker="#sectionTAG">&lt;section&gt;</seeguide> and
+ <seeguide marker="#funcsTAG">&lt;funcs&gt;</seeguide>,
followed by zero or more
- <seealso marker="#authorsTAG">&lt;authors&gt;</seealso>.</p>
+ <seeguide marker="#authorsTAG">&lt;authors&gt;</seeguide>.</p>
</section>
<section>
@@ -524,7 +524,7 @@
"formal" definitions.</p>
<p>Contains any combination and any number of
- <seealso marker="block_tags">block tags</seealso> except
+ <seeguide marker="block_tags">block tags</seeguide> except
<c><![CDATA[<image>]]></c> and <c><![CDATA[<table>]]></c>.</p>
</section>
@@ -533,11 +533,11 @@
<title>&lt;section&gt;</title>
<p>Subdivisions of the document. Contains an optional
- <seealso marker="inline_tags#markerTAG">&lt;marker&gt;</seealso>,
- a <seealso marker="user_guide_dtds#titleTAG">&lt;title&gt;</seealso>,
+ <seeguide marker="inline_tags#markerTAG">&lt;marker&gt;</seeguide>,
+ a <seeguide marker="user_guide_dtds#titleTAG">&lt;title&gt;</seeguide>,
followed by any combination and any number of
- <seealso marker="block_tags">block tags</seealso> except
+ <seeguide marker="block_tags">block tags</seeguide> except
<c><![CDATA[<image>]]></c> and <c><![CDATA[<table>]]></c>.</p>
</section>
@@ -548,7 +548,7 @@
<p>A group of "formal" function definitions.</p>
<p>Contains one or more
- <seealso marker="#funcTAG">&lt;func&gt;</seealso>.</p>
+ <seeguide marker="#funcTAG">&lt;func&gt;</seeguide>.</p>
</section>
<section>
@@ -558,10 +558,10 @@
<p>A "formal" function definition.</p>
<p>Contains one or more
- <seealso marker="#nameTAG">&lt;name&gt;</seealso>, followed by
- <seealso marker="#fsummaryTAG">&lt;fsummary&gt;</seealso>,
- <seealso marker="#typeTAG">&lt;type&gt;</seealso> (optional) and
- <seealso marker="#descTAG">&lt;desc&gt;</seealso> (optional).</p>
+ <seeguide marker="#nameTAG">&lt;name&gt;</seeguide>, followed by
+ <seeguide marker="#fsummaryTAG">&lt;fsummary&gt;</seeguide>,
+ <seeguide marker="#typeTAG">&lt;type&gt;</seeguide> (optional) and
+ <seeguide marker="#descTAG">&lt;desc&gt;</seeguide> (optional).</p>
</section>
<section>
@@ -576,7 +576,7 @@
<p>In the case of an <c>erlref</c> DTD, it will
automatically be added a
- <seealso marker="inline_tags#markerTAG">marker</seealso>,
+ <seeguide marker="inline_tags#markerTAG">marker</seeguide>,
<c><![CDATA[<marker id="Name/Arity">]]></c> or
<c><![CDATA[<marker id="Name">]]></c>, based on the contents of
this tag before the function definition.</p>
@@ -590,7 +590,7 @@
<c><![CDATA[<marker id="foo/2">]]></c> before the function
definition in the generated HTML. That is, referring to
the function using
- <c><![CDATA[<seealso marker="#foo/2">foo/2</seealso>]]></c> will
+ <c><![CDATA[<seemfa marker="#foo/2">foo/2</seemfa>]]></c> will
automatically work.</p>
</section>
@@ -599,8 +599,8 @@
<title>&lt;fsummary&gt;</title>
<p>Function/command summary. Contains plain text,
- <seealso marker="inline_tags#cTAG">&lt;c&gt;</seealso> and
- <seealso marker="inline_tags#emTAG">&lt;em&gt;</seealso>.</p>
+ <seeguide marker="inline_tags#cTAG">&lt;c&gt;</seeguide> and
+ <seeguide marker="inline_tags#emTAG">&lt;em&gt;</seeguide>.</p>
</section>
<section>
@@ -610,8 +610,8 @@
<p>Type declarations for the function/command.</p>
<p>Contains one or more pairs of
- <seealso marker="#vTAG">&lt;v&gt;</seealso> and
- <seealso marker="#dTAG">&lt;d&gt;</seealso> (optional).</p>
+ <seeguide marker="#vTAG">&lt;v&gt;</seeguide> and
+ <seeguide marker="#dTAG">&lt;d&gt;</seeguide> (optional).</p>
</section>
<section>
@@ -627,8 +627,8 @@
<title>&lt;d&gt;</title>
<p>Description for an argument or return value. Contains plain text,
- <seealso marker="inline_tags#cTAG">&lt;c&gt;</seealso> and
- <seealso marker="inline_tags#emTAG">&lt;em&gt;</seealso>.</p>
+ <seeguide marker="inline_tags#cTAG">&lt;c&gt;</seeguide> and
+ <seeguide marker="inline_tags#emTAG">&lt;em&gt;</seeguide>.</p>
</section>
<section>
@@ -636,7 +636,7 @@
<title>&lt;desc&gt;</title>
<p>Function/command description. Contains
- <seealso marker="block_tags">block tags</seealso> except
+ <seeguide marker="block_tags">block tags</seeguide> except
<c>&lt;image&gt;</c> and <c>&lt;table&gt;</c>.</p>
</section>
@@ -647,8 +647,8 @@
<p>Authors of the manual page. The <c>authors</c> element is optional.</p>
<p>Contains one or more pairs of
- <seealso marker="#anameTAG">&lt;aname&gt;</seealso> and
- <seealso marker="#emailTAG">&lt;email&gt;</seealso>.</p>
+ <seeguide marker="#anameTAG">&lt;aname&gt;</seeguide> and
+ <seeguide marker="#emailTAG">&lt;email&gt;</seeguide>.</p>
</section>
<section>
diff --git a/lib/erl_docgen/doc/src/user_guide_dtds.xml b/lib/erl_docgen/doc/src/user_guide_dtds.xml
index fdbaba2d92..0bebf43cdb 100644
--- a/lib/erl_docgen/doc/src/user_guide_dtds.xml
+++ b/lib/erl_docgen/doc/src/user_guide_dtds.xml
@@ -38,7 +38,7 @@
the User's Guide or Release Notes. First are some paragraphs
introducing the main contents. After that follows chapters,
written in separate files with
- the <seealso marker="#chapterDTD">chapter</seealso> DTD.</p>
+ the <seeguide marker="#chapterDTD">chapter</seeguide> DTD.</p>
<p>Example:</p>
<pre>
@@ -70,11 +70,11 @@
<p>The top level tag of a <c>part</c> DTD.</p>
<p>Contains a
- <seealso marker="header_tags">&lt;header&gt;</seealso>,
+ <seeguide marker="header_tags">&lt;header&gt;</seeguide>,
an optional
- <seealso marker="#descriptionTAG">&lt;description&gt;</seealso>,
+ <seeguide marker="#descriptionTAG">&lt;description&gt;</seeguide>,
followed by one or more
- <seealso marker="#includeTAG">&lt;include&gt;</seealso>.</p>
+ <seeguide marker="#includeTAG">&lt;include&gt;</seeguide>.</p>
</section>
<section>
@@ -85,7 +85,7 @@
included chapters/manual pages.</p>
<p>Contains any combination and any number
- of <seealso marker="block_tags">block tags</seealso> except
+ of <seeguide marker="block_tags">block tags</seeguide> except
<c><![CDATA[<image>]]></c> and <c><![CDATA[<table>]]></c>.</p>
</section>
@@ -151,11 +151,11 @@
<p>The top level tag of a <c>chapter</c> DTD.</p>
<p>Contains a
- <seealso marker="header_tags">&lt;header&gt;</seealso>,
+ <seeguide marker="header_tags">&lt;header&gt;</seeguide>,
an optional introduction consisting of any combination of
- <seealso marker="block_tags">block tags</seealso>,
+ <seeguide marker="block_tags">block tags</seeguide>,
followed by one or more
- <seealso marker="#sectionTAG">&lt;section&gt;</seealso>.</p>
+ <seeguide marker="#sectionTAG">&lt;section&gt;</seeguide>.</p>
</section>
<section>
@@ -165,10 +165,10 @@
<p>Subdivision of a chapter.</p>
<p>Contains an optional
- <seealso marker="inline_tags#markerTAG">&lt;marker&gt;</seealso>,
- a <seealso marker="#titleTAG">&lt;title&gt;</seealso>,
+ <seeguide marker="inline_tags#markerTAG">&lt;marker&gt;</seeguide>,
+ a <seeguide marker="#titleTAG">&lt;title&gt;</seeguide>,
followed by any combination and any number of
- <seealso marker="block_tags">block tags</seealso> and
+ <seeguide marker="block_tags">block tags</seeguide> and
<c><![CDATA[<section>]]></c>.</p>
</section>
diff --git a/lib/erl_docgen/priv/bin/chunk.escript b/lib/erl_docgen/priv/bin/chunk.escript
new file mode 100644
index 0000000000..a9ea6b9b73
--- /dev/null
+++ b/lib/erl_docgen/priv/bin/chunk.escript
@@ -0,0 +1,30 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%! +A 1 +SDio 1 +S 1 -mode minimal
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%----------------------------------------------------------------------
+%% File : chunk.escript
+%%
+%% Created : 1 Nov 2018 by Kenneth Lundin <uabkeld@elxa31hr002>
+%%
+%% Trampoline to xml to chunk creation.
+%%----------------------------------------------------------------------
+
+main(Args) ->
+ docgen_xml_to_chunk:main(Args).
diff --git a/lib/erl_docgen/priv/bin/specs_gen.escript b/lib/erl_docgen/priv/bin/specs_gen.escript
index 859f3c21f5..5b75a83a7e 100644
--- a/lib/erl_docgen/priv/bin/specs_gen.escript
+++ b/lib/erl_docgen/priv/bin/specs_gen.escript
@@ -48,7 +48,8 @@ main(Args) ->
parse(["-o"++Dir | Opts], InclFs, _, Module) ->
parse(Opts, InclFs, Dir, Module);
parse(["-I"++I | Opts], InclFs, Dir, Module) ->
- parse(Opts, [I | InclFs], Dir, Module);
+ Is = filelib:wildcard(I),
+ parse(Opts, Is ++ InclFs, Dir, Module);
parse(["-module", Module | Opts], InclFs, Dir, _) ->
parse(Opts, InclFs, Dir, Module);
parse([File], InclFs, Dir, no_module) ->
@@ -88,8 +89,9 @@ call_edoc(FileSpec, InclFs, Dir) ->
ok = write_text(Text, File, Dir),
rename(Dir, File)
catch
- _:_ ->
+ E:R:ST ->
io:format("EDoc could not process file '~s'\n", [File]),
+ io:format("~p:~p ~p\n", [E,R,ST]),
clean_up(Dir),
halt(3)
end.
@@ -131,7 +133,7 @@ write_text(Text, File, Dir) ->
ok;
{error, R} ->
R1 = file:format_error(R),
- io:format("could not write file '~s': ~s\n", [File, R1]),
+ io:format("could not write file '~s': ~s\n", [OutFile, R1]),
halt(2)
end.
diff --git a/lib/erl_docgen/priv/bin/validate_links.escript b/lib/erl_docgen/priv/bin/validate_links.escript
new file mode 100755
index 0000000000..4d93d9c253
--- /dev/null
+++ b/lib/erl_docgen/priv/bin/validate_links.escript
@@ -0,0 +1,360 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%! +A 1 +SDio 1 +S 1 -mode minimal
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%----------------------------------------------------------------------
+%% File : chunk.escript
+%%
+%% Created : 1 Nov 2018 by Kenneth Lundin <uabkeld@elxa31hr002>
+%%
+%% Trampoline to xml to chunk creation.
+%%----------------------------------------------------------------------
+
+-mode(compile).
+
+main([ErlTop,Mod2App|Args]) ->
+ Files = [parse_file(Arg) || Arg <- Args],
+ M2A = parse_mod2app(Mod2App),
+ put(top, ErlTop),
+ put(exit_code, 0),
+ CachedFiles = maps:from_list([{m2a,M2A}|Files]),
+ [validate_links(File,CachedFiles) ||
+ {{App,_},_} = File <- Files,
+ lists:member(App,["edoc","wx","eunit","syntax_tools"]) =:= false],
+ erlang:halt(get(exit_code)).
+
+parse_file(Filename) ->
+% io:format("Parse: ~p~n",[Filename]),
+ case xmerl_sax_parser:file(Filename, [skip_external_dtd,
+ {event_fun,fun handle_event/3},
+ {event_state,#{ markers => [],
+ funcs => [],
+ datatypes => []}}]) of
+ {ok, Res, _} ->
+% [io:format("~p~n",[Res]) || maps:get(filename,Res) == {"erts","erl_tracer"}],
+ {maps:get(filename,Res), Res}
+ end.
+
+handle_event(startDocument, _Line, State) ->
+ {State,[]};
+handle_event({startElement, _, Tag, _, Attr}, Line, {State, Stack}) ->
+ NewState = event({startElement, Tag, [{A,V} || {_,_,A,V} <- Attr]}, Line, State, Stack),
+ {NewState,[Tag | Stack]};
+handle_event({endElement, _, Tag, _}, Line, {State, [Tag|NewStack]}) ->
+ {event({endElement,Tag}, Line, State, NewStack), NewStack};
+handle_event({characters, Chars}, Line, {State, Stack}) ->
+ {event({characters,Chars}, Line, State, Stack), Stack};
+handle_event(endDocument, _Line, {State,[]}) ->
+ State;
+handle_event(Ignore, _, S) when
+ (not is_tuple(Ignore)) orelse (element(1,Ignore) =/= startElement),
+ (not is_tuple(Ignore)) orelse (element(1,Ignore) =/= endElement),
+ (not is_tuple(Ignore)) orelse (element(1,Ignore) =/= characters),
+ Ignore =/= startDocument,
+ Ignore =/= endDocument ->
+ S;
+handle_event(E,L,S) ->
+ io:format("handle_event(~p,~p,~p) did not match~n",[E,L,S]),
+ halt(1).
+
+%% Get the filename of document
+event({startElement, Tag, _Attr}, _Line, #{ type := Type } = State, _) when
+ (Tag =:= "module") and (Type =:= "erlref");
+ (Tag =:= "lib") and (Type =:= "cref");
+ (Tag =:= "com") and (Type =:= "comref");
+ (Tag =:= "file") and (Type =:= "fileref");
+ (Tag =:= "app") and (Type =:= "appref");
+ (Tag =:= "file") and (Type =:= "chapter") ->
+ maps:put(filename,undefined,State);
+event({characters,ModuleName},{Path,_,_},
+ #{ type := "chapter", filename := undefined } = State, _) ->
+ maps:put(filename,{path2app(Path),filename:rootname(ModuleName)}, State);
+event({characters,ModuleName},{Path,_,_},
+ #{ type := "appref", filename := undefined } = State, _) ->
+ maps:put(filename,{path2app(Path),ModuleName ++ "_app"}, State);
+event({characters,ModuleName},{Path,_,_},#{ filename := undefined } = State, _) ->
+ maps:put(filename,{path2app(Path),ModuleName}, State);
+
+%% Get the see* attributes
+event({startElement, "see" ++ _ = What, Attr}, Line, State, [Parent|_])
+%% Links are not rendered when emitted in <name>, but the edoc docs still has links here
+%% So we ignore those...
+ when Parent =/= "name" ->
+ maps:put(What, [{Line,Attr}|maps:get(What, State, [])], State);
+
+%% Get all the marker attributes
+event({startElement, "marker", [{"id",Id}]}, _Line, State, _) ->
+ maps:put(markers, [Id|maps:get(markers, State, [])], State);
+event({startElement, "title", []}, _Line, State, _) ->
+ error = maps:find(characters,State),
+ maps:put(characters,[],State);
+event({endElement, "title"}, _Line, State, _) ->
+ Id = string:lowercase(
+ re:replace(maps:get(characters,State),"[?: /()\"\r\n]","-",[global,{return,list}])),
+ NewState = maps:put(markers, [Id|maps:get(markers, State, [])], State),
+ maps:remove(characters,NewState);
+event({startElement, "nametext", []}, _Line, State, _) ->
+ error = maps:find(characters,State),
+ maps:put(characters,[],State);
+event({endElement, "nametext"}, _Line, State, _) ->
+ Id = re:replace(maps:get(characters,State),"\\(.*","",[global,{return,list},dotall]),
+ NewState = maps:put(markers, [Id|maps:get(markers, State, [])], State),
+ maps:remove(characters,NewState);
+event({startElement, "datatype_title", []}, _Line, State, _) ->
+ error = maps:find(characters,State),
+ maps:put(characters,[],State);
+event({endElement, "datatype_title"}, _Line, State, _) ->
+ Id = lists:flatten(maps:get(characters,State)),
+ NewState = maps:put(markers, [Id|maps:get(markers, State, [])], State),
+ maps:remove(characters,NewState);
+event({startElement, "description", _}, _Line, State, _) ->
+ maps:put(markers, ["description"|maps:get(markers, State, [])], State);
+
+%% Get all func->name markers in erlref
+event({startElement, "name", Attr}, _Line, #{ type := "erlref" } = State, ["func" | _]) ->
+ AnchorState =
+ case proplists:get_value("anchor",Attr) of
+ undefined -> State;
+ Anchor -> maps:put(markers, [Anchor|maps:get(markers, State, [])], State)
+ end,
+ case {proplists:get_value("name",Attr),proplists:get_value("arity",Attr)} of
+ {undefined,undefined} ->
+ %% We parse the function name from the content,
+ %% so have to capture characters
+ error = maps:find(characters,AnchorState),
+ maps:put(characters,[],AnchorState);
+ {Func,Arity} when Func =/= undefined, Arity =/= undefined ->
+ maps:put(funcs, [{Func,Arity}|maps:get(funcs,AnchorState,[])], AnchorState)
+ end;
+event({endElement,"name"}, _Line, #{ characters := Chars} = State, ["func"|_]) ->
+ FAs = docgen_xml_to_chunk:func_to_tuple(Chars),
+ NewFuncs =
+ lists:foldl(
+ fun({F,A},Acc) ->
+ [{F,integer_to_list(A)} | Acc]
+ end, maps:get(funcs,State,[]), FAs),
+ maps:remove(characters, maps:put(funcs, NewFuncs, State));
+
+%% Get all datatype->name markers in erlref
+event({startElement, "name", Attr}, _Line, #{ type := "erlref" } = State, ["datatype" | _]) ->
+ case {proplists:get_value("name",Attr),proplists:get_value("arity",Attr)} of
+ {undefined,undefined} ->
+ %% We parse the datatype name from the content,
+ %% so have to capture characters
+ error = maps:find(characters,State),
+ maps:put(characters,[],State);
+ {Name,Arity} when Name =/= undefined, Arity =:= undefined ->
+ maps:put(datatypes, [Name|maps:get(datatypes,State,[])], State)
+ end;
+event({endElement,"name"}, _Line, #{ characters := Chars} = State, ["datatype"|_]) ->
+ FAs = docgen_xml_to_chunk:func_to_tuple(Chars),
+ NewFuncs =
+ lists:foldl(
+ fun({F,_A},Acc) -> [F | Acc] end, maps:get(datatypes,State,[]), FAs),
+ maps:remove(characters, maps:put(datatypes, NewFuncs, State));
+
+%% Capture all characters
+event({characters,Chars},_Line, #{ characters := Acc } = State, _) ->
+ maps:put(characters,[Acc,Chars],State);
+
+%% Get the type of document
+event({startElement, What, _Attr}, _, State, _) when
+ What =:= "erlref";
+ What =:= "appref";
+ What =:= "comref";
+ What =:= "cref";
+ What =:= "fileref";
+ What =:= "chapter" ->
+ maps:put(type,What,State);
+event({startElement, What, _Attr}, {Path,Filename,_}, State, _) when
+ What =:= "book";
+ What =:= "application";
+ What =:= "internal";
+ What =:= "part" ->
+ State#{ filename => {path2app(Path),filename:rootname(Filename)}, type => What };
+event(_Event, _Line, State, _) ->
+ State.
+
+parse_mod2app(Filename) ->
+% io:format("Parse: ~p~n",[Filename]),
+ Event =
+ fun({startElement,_,"module",_,[{_,_,"name",Name}]}, _, {undefined, Acc}) ->
+ {Name,Acc};
+ ({characters,Chrs}, _, {Name, Acc}) when Name =/= undefined ->
+ {undefined,[{Name,Chrs}|Acc]};
+ (_, _, State) ->
+ State
+ end,
+ case xmerl_sax_parser:file(Filename, [skip_external_dtd,
+ {event_fun,Event},
+ {event_state,{undefined,[]}}]) of
+ {ok, {_,Res}, _} ->
+ maps:from_list(Res)
+ end.
+
+validate_links({Filename, Links}, CachedFiles) ->
+ %% io:format("~s ~p~n",[Links]),
+ lists:foreach(
+ fun({LinkType, TypeLinks}) ->
+ lists:foreach(
+ fun({Line,Link}) ->
+ validate_link(Filename, LinkType, Line, Link, CachedFiles)
+ end, TypeLinks)
+ end, maps:to_list(maps:filter(fun(Key,_) -> not is_atom(Key) end,Links))).
+validate_link(Filename, LinkType, Line, [{"marker",Marker}], CachedFiles) ->
+ validate_link(Filename, LinkType, Line, Marker, CachedFiles);
+validate_link(Filename, "seemfa", Line, Link, CachedFiles) ->
+ case string:find(Link,"#") of
+ nomatch ->
+ fail(Line, "Invalid link in seemfa. "
+ "Must contains a '#'.");
+ _ ->
+ {App,Mod,Anchor} = ParsedLink = parse_link(Filename, maps:new(), Link),
+ case string:lexemes(Anchor,"/") of
+ [Func, Arity] ->
+ try list_to_integer(Arity) of
+ _ ->
+ MF = App ++ ":" ++ Mod ++ "#" ++ Func,
+ Funcs = maps:get(funcs,maps:get({App,Mod},CachedFiles)),
+ case lists:member({Func,Arity},Funcs) of
+ true ->
+ validate_type(Line, "seemfa",
+ read_link(Line, ParsedLink, CachedFiles));
+ false ->
+ fail(Line, "Could not find documentation for ~s when "
+ "resolving link",[MF ++ "/" ++ Arity])
+ end
+ catch _:_ ->
+ fail(Line, "Invalid arity for seemfa. "
+ "Must end in a number")
+ end;
+ _ ->
+ fail(Line, "Invalid anchor for seemfa. "
+ "Must contain a '/'.")
+ end
+ end;
+validate_link(Filename, LinkType = "seetype", Line, Link, CachedFiles) ->
+ {App,Mod,Type} = ParsedLink = parse_link(Filename, maps:get(m2a,CachedFiles), Link),
+ Types = maps:get(datatypes,maps:get({App,Mod},CachedFiles)),
+ case lists:member(Type, Types) of
+ false ->
+ fail(Line, "Could not find documentation for ~s when "
+ "resolving link",[App ++ ":" ++ Mod ++ "#" ++ Type]);
+ _ ->
+ validate_type(Line,LinkType,read_link(Line, ParsedLink, CachedFiles))
+ end;
+validate_link({"jinterface","jinterface_users_guide"},"seefile",_, _, _) ->
+ %% Skip links to java documentation
+ ok;
+validate_link(Filename, LinkType, Line, Link, CachedFiles) ->
+ ParsedLink = parse_link(Filename, maps:new(), Link),
+ TargetInfo = read_link(Line, ParsedLink, CachedFiles),
+ validate_type(Line,LinkType,TargetInfo),
+ validate_marker(Line,ParsedLink,TargetInfo).
+
+parse_link({SelfApp,Filename},Mod2App,Link) ->
+ {AppMod, Marker} =
+ case string:split(Link, "#") of
+ [Link] ->
+ {Link, ""};
+ [AppMod0,Marker0] ->
+ {AppMod0, Marker0}
+ end,
+ {App,Mod} =
+ case string:split(AppMod, ":") of
+ [""] ->
+ {SelfApp,Filename};
+ [AppMod] ->
+ {maps:get(AppMod,Mod2App,SelfApp),AppMod};
+ [App0,Mod0] ->
+ {App0,Mod0}
+ end,
+ {App,Mod,Marker}.
+
+read_link(_Line, {_App,"index",_}, _Cache) ->
+ #{type => "index", markers => [] };
+read_link(Line, {App,Mod,_}, Cache) ->
+ case maps:find({App,Mod}, Cache) of
+ {ok, Info } ->
+ Info;
+ error ->
+ %% fail(Line, "Could not find: ~p~p~n~p~n",
+ %% [App,Mod,lists:sort(maps:keys(Cache))]),
+ fail(Line, "Could not find: ~s:~s~n",[App,Mod]),
+ halt(1)
+ end.
+
+validate_type(_Line, "seeerl",#{ type := "erlref" }) ->
+ ok;
+validate_type(_Line, "seemfa", #{ type := "erlref"}) ->
+ ok;
+validate_type(_Line, "seetype", #{ type := "erlref"}) ->
+ ok;
+validate_type(_Line, "seeguide", #{ type := "chapter"}) ->
+ ok;
+validate_type(_Line, "seeguide", #{ type := "index"}) ->
+ ok;
+validate_type(_Line, "seefile", #{ type := "fileref"}) ->
+ ok;
+validate_type(_Line, "seeapp", #{ type := "appref"}) ->
+ ok;
+validate_type(_Line, "seeapp", #{ type := "index"}) ->
+ ok;
+validate_type(_Line, "seecom", #{ type := "comref"}) ->
+ ok;
+validate_type(_Line, "seecref", #{ type := "cref"}) ->
+ ok;
+validate_type(Line, From, #{ type := To }) ->
+ fail(Line, "Invalid <~s> pointing to a <~s> file",[From,To]).
+
+validate_marker(_Line, {_,_,[]}, _) ->
+ ok;
+validate_marker(Line, {_,_,Marker}, #{ filename := {App,Name}, markers := Markers }) ->
+ case lists:any(fun(M) -> string:equal(Marker,M,true) end,Markers) of
+ true ->
+ ok;
+ false ->
+ StringMarkers = lists:join(", ",Markers),
+ fail(Line, "Could not find marker <~s> in ~s:~s~n Available: ~s",
+ [Marker,App,Name,StringMarkers])
+ end.
+
+path2app(Path) ->
+ case lists:reverse(string:lexemes(Path,"/")) of
+ ["xml","doc",App|_] ->
+ App;
+ [System,"xml","doc","system"|_] ->
+ "system/" ++ System
+ end.
+
+%% getFileName("erts",Mod) ->
+%% filename:join([get(top),"erts","doc","xml",Mod++".xml"]);
+%% getFileName("system/"++System,Mod) ->
+%% filename:join([get(top),"system","doc",System,"..","xml",System,Mod++".xml"]);
+%% getFileName(App,Mod) ->
+%% filename:join([get(top),"lib",App,"doc","src","..","xml",Mod++".xml"]).
+
+fail(Line, Slogan) ->
+ fail(Line, Slogan,[]).
+fail({Path, Filename, Line}, Slogan, SloganArgs) ->
+ Format = lists:flatten(
+ io_lib:format("~s:~p ~s~n",
+ [filename:join(Path,Filename),Line,Slogan])),
+ io:format(Format, SloganArgs),
+ put(exit_code, 1).
diff --git a/lib/erl_docgen/priv/css/Makefile b/lib/erl_docgen/priv/css/Makefile
index e3d2ee7e3f..da2ef5ad15 100644
--- a/lib/erl_docgen/priv/css/Makefile
+++ b/lib/erl_docgen/priv/css/Makefile
@@ -53,7 +53,9 @@ CSS_FILES = \
# ----------------------------------------------------
debug opt:
-docs:
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
clean:
$(RM) $(TARGET_FILES)
@@ -64,16 +66,17 @@ clean:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-
release_spec: opt
$(INSTALL_DIR) "$(RELSYSDIR)/priv/css"
$(INSTALL_DATA) $(CSS_FILES) "$(RELSYSDIR)/priv/css"
-release_docs_spec:
+release_html_spec: html
$(INSTALL_DIR) "$(RELEASE_PATH)/doc"
$(INSTALL_DATA) $(CSS_FILES) ../nyi.html "$(RELEASE_PATH)/doc"
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+
release_tests_spec:
diff --git a/lib/erl_docgen/priv/css/otp_doc.css b/lib/erl_docgen/priv/css/otp_doc.css
index 17d9f8dd56..d3e26f30e5 100644
--- a/lib/erl_docgen/priv/css/otp_doc.css
+++ b/lib/erl_docgen/priv/css/otp_doc.css
@@ -49,9 +49,15 @@ a:visited { color: #1b6ec2; text-decoration: none }
border-right: 1px solid #ccc;
}
+.leftnav-tube
+{
+ margin: 15px;
+}
+
#content {
margin-left: 340px; /* set left value to WidthOfFrameDiv */
- max-width: 42em;
+ max-width: 52em;
+ overflow-x: hidden;
}
.frontpage
@@ -61,7 +67,8 @@ a:visited { color: #1b6ec2; text-decoration: none }
.innertube
{
- margin: 15px; /* Magins for inner DIV inside each DIV (to provide padding) */
+ margin-left: 15px; /* Magins for inner DIV inside each DIV (to provide padding) */
+ margin-right: 11em;
}
.footer
@@ -75,7 +82,7 @@ a:visited { color: #1b6ec2; text-decoration: none }
/* Invisible table for function specs,
* just to get since-version out in right margin */
.func-table, .func-tr, .func-td, .cfunc-td, .func-since-td {
- width: 200%;
+ width: 100%;
border: 0;
padding: 0;
margin: 0;
@@ -86,18 +93,18 @@ a:visited { color: #1b6ec2; text-decoration: none }
}
.func-td {
- width: 50%;
+ width: 38em;
}
.cfunc-td {
- width: 50%;
- padding-left: 100px;
- text-indent: -100px;
+ width: 31em;
+ padding-left: 7em;
+ text-indent: -7em;
}
.func-since-td {
- width: 50%;
- padding-left: 1em
+ width: auto;
+ padding-left: 1em;
}
.func-td:hover {
@@ -187,10 +194,17 @@ pre {
}
-.exports-body, .data-types-body, .REFBODY{
+.data-types-body, .REFBODY{
margin-left: 2em;
}
.REFTYPES { margin-left: 1.5em }
+.exports-body {
+ margin-left: 3em;
+}
+.exports-tube
+{
+ margin-right: 11em;
+}
footer { }
@@ -280,22 +294,47 @@ th {
outline: none;
}
-.ghlink {
- margin-left: -2.7em; /* .pencil.font-size + .pencil.padding.left + .pencil.padding.right = 2.7 */
+.ghlink-before {
+ margin-left: -4em;
visibility: hidden;
}
-.pencil:before {
+.pencil-before:before {
transform: rotateZ(90deg);
+ display: inline-block;
content: "\270E";
color: #1a1a1a !important;
- font-weight: bold;
font-size: 1.5em;
- padding: .3em .6em .6em;
- line-height: 1em;
font-family: mono;
}
+.paperclip-before:before {
+ display: inline-block;
+ content: "\1F517";
+ padding-left: 1em;
+ padding-right: .3em;
+}
+
+.ghlink-after {
+ visibility: hidden;
+}
+
+.pencil-after:after {
+ display: inline-block;
+ transform: rotate(90deg);
+ content: "\270E";
+ color: #1a1a1a !important;
+ font-size: 1.5em;
+ font-family: mono;
+}
+
+.paperclip-after:after {
+ display: inline-block;
+ content: "\1F517";
+ padding-right: .3em;
+ padding-left: .7em;
+}
+
hr{
border: 0;
border-top: 1px solid #aaa;
@@ -321,4 +360,4 @@ a > .code {
color: gray;
font-weight: normal;
font-size: small;
-} \ No newline at end of file
+}
diff --git a/lib/erl_docgen/priv/dtd/Makefile b/lib/erl_docgen/priv/dtd/Makefile
index e35e5f8826..fd8d8a43c7 100644
--- a/lib/erl_docgen/priv/dtd/Makefile
+++ b/lib/erl_docgen/priv/dtd/Makefile
@@ -46,7 +46,6 @@ DTD_FILES = \
fileref.dtd \
xhtml1-frameset.dtd \
appref.dtd \
- cites.dtd \
common.image.dtd \
cref.dtd \
part.dtd \
@@ -55,13 +54,9 @@ DTD_FILES = \
common.dtd \
common.refs.dtd \
erlref.dtd \
- report.dtd \
xhtml1-transitional.dtd \
- bookinsidecover.dtd \
common.entities.dtd \
- common.table.dtd \
- fascicules.dtd \
- terms.dtd
+ common.table.dtd
ENT_FILES = \
xhtml-special.ent \
diff --git a/lib/erl_docgen/priv/dtd/bookinsidecover.dtd b/lib/erl_docgen/priv/dtd/bookinsidecover.dtd
deleted file mode 100644
index ae22c45884..0000000000
--- a/lib/erl_docgen/priv/dtd/bookinsidecover.dtd
+++ /dev/null
@@ -1,37 +0,0 @@
-<!--
- ``Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- The Initial Developer of the Original Code is Ericsson AB.
- Portions created by Ericsson are Copyright 1999-2007, Ericsson AB.
- All Rights Reserved.''
-
- $Id$
--->
-<!ENTITY % ISOlat1 SYSTEM "xhtml-lat1.ent" >
-%ISOlat1;
-
-<!ENTITY amp "&#x0026;" >
-<!ENTITY gt "&#x003E;" >
-<!ENTITY lt "&#x003C;" >
-
-<!ELEMENT bookinsidecover (#PCDATA|br|theheader|vfill|tt|bold)* >
-
-<!ELEMENT tt (#PCDATA|br|theheader|vfill)* >
-<!ELEMENT bold (#PCDATA|br|theheader|vfill)* >
-<!ELEMENT vfill EMPTY >
-<!ELEMENT theheader EMPTY >
-<!ATTLIST theheader tag (title|prepared|responsible|docno|
- approved|checked|date|rev|file|
- none) "none" >
-
-<!ELEMENT br EMPTY >
diff --git a/lib/erl_docgen/priv/dtd/cites.dtd b/lib/erl_docgen/priv/dtd/cites.dtd
deleted file mode 100644
index 4558947db0..0000000000
--- a/lib/erl_docgen/priv/dtd/cites.dtd
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
- ``Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- The Initial Developer of the Original Code is Ericsson AB.
- Portions created by Ericsson are Copyright 1999-2007, Ericsson AB.
- All Rights Reserved.''
-
- $Id$
--->
-<!ENTITY % ISOlat1 SYSTEM "xhtml-lat1.ent" >
-%ISOlat1;
-
-<!ENTITY amp "&#x0026;" >
-<!ENTITY gt "&#x003E;" >
-<!ENTITY lt "&#x003C;" >
-
-<!-- Structure -->
-
-<!ELEMENT cites (cite)* >
-<!ELEMENT cite (id, shortdef, def, resp?) >
-<!ELEMENT id (#PCDATA) >
-<!ELEMENT shortdef (#PCDATA) >
-<!ELEMENT def (#PCDATA|c|i|em)* >
-<!ELEMENT resp (#PCDATA) >
-<!ELEMENT c (#PCDATA) >
-<!ELEMENT em (#PCDATA|c)* >
diff --git a/lib/erl_docgen/priv/dtd/common.dtd b/lib/erl_docgen/priv/dtd/common.dtd
index 0ccd52068b..cc186fe5d6 100644
--- a/lib/erl_docgen/priv/dtd/common.dtd
+++ b/lib/erl_docgen/priv/dtd/common.dtd
@@ -22,14 +22,15 @@
<!ENTITY % common.entities SYSTEM "common.entities.dtd" >
%common.entities;
-<!ENTITY % block "p|pre|code|list|taglist|codeinclude|
- erleval" >
-<!ENTITY % inline "#PCDATA|c|i|em|strong|term|cite|br|path|seealso|
- url|marker|anno|image" >
+<!ENTITY % refs "seemfa|seeerl|seetype|seeapp|seecom|seecref|seefile|seeguide|url" >
+
+<!ENTITY % block "p|pre|code|list|taglist|codeinclude" >
+<!ENTITY % inline "#PCDATA|c|i|em|strong|term|br|%refs;|
+ marker|anno|image" >
<!-- XXX -->
<!ELEMENT p (%inline;)* >
-<!ELEMENT pre (#PCDATA|seealso|url|input|anno)* >
-<!ELEMENT input (#PCDATA|seealso|url|anno)* >
+<!ELEMENT pre (#PCDATA|%refs;|input|anno)* >
+<!ELEMENT input (#PCDATA|%refs;|anno)* >
<!ELEMENT code (#PCDATA|anno)* >
<!ATTLIST code type (erl|c|none) "none" >
<!ELEMENT quote (p)* >
@@ -43,38 +44,37 @@
<!ELEMENT strong (#PCDATA|c|anno)* >
<!ELEMENT anno (#PCDATA) >
-<!-- XXX -->
-<!ELEMENT term (termdef?) >
-<!ATTLIST term id CDATA #REQUIRED >
-<!ELEMENT termdef (#PCDATA) >
-<!ELEMENT cite (citedef?) >
-<!ATTLIST cite id CDATA #REQUIRED >
-<!ELEMENT citedef (ctitle,cauthor,chowpublished) >
-<!ELEMENT ctitle (#PCDATA) >
-<!ELEMENT cauthor (#PCDATA) >
-<!ELEMENT chowpublished (#PCDATA) >
-
<!-- XXX -->
<!ELEMENT br EMPTY >
-<!-- Path -->
-
-<!ELEMENT path (#PCDATA) >
-<!ATTLIST path unix CDATA ""
- windows CDATA "" >
-
<!-- List -->
<!ELEMENT list (item+) >
<!ATTLIST list type (ordered|bulleted) "bulleted" >
<!ELEMENT taglist (tag,item+)+ >
-<!ELEMENT tag (#PCDATA|c|i|em|br|seealso|url|marker|anno)* >
-<!ELEMENT item (%inline;|%block;|warning|note|dont|do|quote)* >
+<!ELEMENT tag (#PCDATA|c|i|em|br|%refs;|marker|anno)* >
+<!ELEMENT item (%inline;|%block;|warning|note|dont|do|quote|table)* >
<!-- References -->
-<!ELEMENT seealso (#PCDATA|c|i|em|anno)* >
-<!ATTLIST seealso marker CDATA #REQUIRED >
+<!ENTITY % refinline "#PCDATA|c|i|em|anno" >
+
+<!ELEMENT seemfa (%refinline;)* >
+<!ATTLIST seemfa marker CDATA #REQUIRED >
+<!ELEMENT seeerl (%refinline;)* >
+<!ATTLIST seeerl marker CDATA #REQUIRED >
+<!ELEMENT seetype (%refinline;)* >
+<!ATTLIST seetype marker CDATA #REQUIRED >
+<!ELEMENT seeapp (%refinline;)* >
+<!ATTLIST seeapp marker CDATA "" >
+<!ELEMENT seecom (%refinline;)* >
+<!ATTLIST seecom marker CDATA #REQUIRED >
+<!ELEMENT seecref (%refinline;)* >
+<!ATTLIST seecref marker CDATA #REQUIRED >
+<!ELEMENT seefile (%refinline;)* >
+<!ATTLIST seefile marker CDATA #REQUIRED >
+<!ELEMENT seeguide (%refinline;)* >
+<!ATTLIST seeguide marker CDATA "" >
<!ELEMENT url (#PCDATA) >
<!ATTLIST url href CDATA #REQUIRED >
<!ELEMENT marker EMPTY >
@@ -86,8 +86,3 @@
<!ATTLIST codeinclude file CDATA #REQUIRED
tag CDATA ""
type (erl|c|none) "none" >
-
-<!-- ErlEval -->
-
-<!ELEMENT erleval EMPTY >
-<!ATTLIST erleval expr CDATA #REQUIRED >
diff --git a/lib/erl_docgen/priv/dtd/common.refs.dtd b/lib/erl_docgen/priv/dtd/common.refs.dtd
index 07c876a17f..280efcd99c 100644
--- a/lib/erl_docgen/priv/dtd/common.refs.dtd
+++ b/lib/erl_docgen/priv/dtd/common.refs.dtd
@@ -27,7 +27,7 @@
<!ELEMENT description (%block;|quote|br|marker|warning|note|dont|do)* >
<!ATTLIST description ghlink CDATA #IMPLIED>
-<!ELEMENT funcs (func)+ >
+<!ELEMENT funcs (fsdescription?,func+) >
<!ELEMENT func (name+,fsummary,(type|type_desc)*,desc?) >
<!ATTLIST func ghlink CDATA #IMPLIED>
<!-- ELEMENT name is defined in each ref dtd -->
@@ -36,8 +36,8 @@
<!ATTLIST type variable CDATA #IMPLIED
name CDATA #IMPLIED
name_i CDATA #IMPLIED>
-<!ELEMENT v (#PCDATA|seealso)* >
-<!ELEMENT d (#PCDATA|seealso|c|i|em)* >
+<!ELEMENT v (#PCDATA|%refs;)* >
+<!ELEMENT d (#PCDATA|%refs;|c|i|em)* >
<!ELEMENT desc (%block;|quote|br|marker|warning|note|dont|do)* >
<!ELEMENT authors (aname,email)+ >
<!ELEMENT aname (#PCDATA) >
@@ -45,10 +45,13 @@
<!ELEMENT section (marker*,title,(%block;|quote|br|marker|
warning|note|dont|do|section)*) >
<!ATTLIST section ghlink CDATA #IMPLIED>
+<!ELEMENT fsdescription (marker*,title,(%block;|quote|br|marker|
+ warning|note|dont|do|section)*) >
+<!ATTLIST fsdescription ghlink CDATA #IMPLIED>
<!ELEMENT datatypes (datatype_title?,datatype)+ >
<!ELEMENT datatype_title (#PCDATA) >
<!ELEMENT datatype (name+,desc?) >
<!ATTLIST datatype ghlink CDATA #IMPLIED>
-<!ELEMENT type_desc (#PCDATA|anno|c|seealso)* >
+<!ELEMENT type_desc (#PCDATA|anno|c|%refs;)* >
<!ATTLIST type_desc variable CDATA #IMPLIED
name CDATA #IMPLIED>
diff --git a/lib/erl_docgen/priv/dtd/erlref.dtd b/lib/erl_docgen/priv/dtd/erlref.dtd
index 8202ea5a4d..960a00025d 100644
--- a/lib/erl_docgen/priv/dtd/erlref.dtd
+++ b/lib/erl_docgen/priv/dtd/erlref.dtd
@@ -30,7 +30,7 @@
<!-- `name' is used in common.refs.dtd and must therefore
be defined in each *ref. dtd -->
-<!ELEMENT name (#PCDATA|seealso)* >
+<!ELEMENT name (#PCDATA|%refs;)* >
<!ATTLIST name name CDATA #IMPLIED
arity CDATA #IMPLIED
clause_i CDATA #IMPLIED
diff --git a/lib/erl_docgen/priv/dtd/fascicules.dtd b/lib/erl_docgen/priv/dtd/fascicules.dtd
deleted file mode 100644
index 073d0cc1d9..0000000000
--- a/lib/erl_docgen/priv/dtd/fascicules.dtd
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!--
- ``Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- The Initial Developer of the Original Code is Ericsson AB.
- Portions created by Ericsson are Copyright 1999-2007, Ericsson AB.
- All Rights Reserved.''
-
- $Id$
--->
-
-<!ENTITY % ISOlat1 SYSTEM "xhtml-lat1.ent" >
-%ISOlat1;
-
-<!ENTITY amp "&#x0026;" >
-<!ENTITY gt "&#x003E;" >
-<!ENTITY lt "&#x003C;" >
-
-<!-- Structure -->
-
-<!ELEMENT fascicules (fascicule)+ >
-<!ELEMENT fascicule (#PCDATA) >
-<!ATTLIST fascicule file CDATA #REQUIRED
- href CDATA #REQUIRED
- entry (yes|no) "no" >
-
diff --git a/lib/erl_docgen/priv/dtd/report.dtd b/lib/erl_docgen/priv/dtd/report.dtd
deleted file mode 100644
index 3dd1c3d347..0000000000
--- a/lib/erl_docgen/priv/dtd/report.dtd
+++ /dev/null
@@ -1,141 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!--
- ``Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- The Initial Developer of the Original Code is Ericsson AB.
- Portions created by Ericsson are Copyright 1999-2007, Ericsson AB.
- All Rights Reserved.''
-
- $Id$
--->
-
-<!ENTITY % ISOlat1 SYSTEM "xhtml-lat1.ent" >
-%ISOlat1;
-
-<!ENTITY amp "&#x0026;" >
-<!ENTITY gt "&#x003E;" >
-<!ENTITY lt "&#x003C;" >
-
-<!ENTITY % header "title,prepared,responsible,docno,approved,
- checked,date,rev,file" >
-<!ENTITY % block "p|pre|code|list|taglist|erlinclude|
- codeinclude|erleval" >
-<!ENTITY % inline "#PCDATA|i|b|c|em|term|cite|br|path|seealso|
- url|marker" >
-
-<!-- Structure -->
-
-<!ELEMENT report (header,section+) >
-<!ELEMENT header (title,prepared,responsible?,docno,approved?,
- checked?,date,rev,file?) >
-<!ELEMENT title (#PCDATA) >
-<!ELEMENT prepared (#PCDATA) >
-<!ELEMENT responsible (#PCDATA) >
-<!ELEMENT docno (#PCDATA) >
-<!ELEMENT approved (#PCDATA) >
-<!ELEMENT checked (#PCDATA) >
-<!ELEMENT date (#PCDATA) >
-<!ELEMENT rev (#PCDATA) >
-<!ELEMENT file (#PCDATA) >
-
-<!ELEMENT section (marker*,title,
- (%block;|quote|warning|note|dont|do|br|image|marker|
- table|section)*) >
-<!ELEMENT p (%inline;|index)* >
-<!ELEMENT pre (#PCDATA|seealso|url|input)* >
-<!ELEMENT input (#PCDATA|seealso|url)* >
-<!ELEMENT code (#PCDATA) >
-<!ATTLIST code type (erl|c|none) "none" >
-<!ELEMENT quote (p)* >
-<!ELEMENT warning (%block;|quote|br|image|marker|table)* >
-<!ELEMENT note (%block;|quote|br|image|marker|table)* >
-<!ELEMENT dont (%block;|quote|br|image|marker|table)* >
-<!ELEMENT do (%block;|quote|br|image|marker|table)* >
-<!ELEMENT i (#PCDATA|b|c|em)* >
-<!ELEMENT b (#PCDATA|i|c|em)* >
-<!ELEMENT c (#PCDATA) >
-<!ELEMENT em (#PCDATA|i|b|c)* >
-<!ELEMENT term (termdef?) >
-<!ATTLIST term id CDATA #REQUIRED >
-<!ELEMENT termdef (#PCDATA) >
-<!ELEMENT cite (citedef?) >
-<!ATTLIST cite id CDATA #REQUIRED >
-<!ELEMENT citedef (ctitle,cauthor,chowpublished) >
-<!ELEMENT ctitle (#PCDATA) >
-<!ELEMENT cauthor (#PCDATA) >
-<!ELEMENT chowpublished (#PCDATA) >
-<!ELEMENT br EMPTY >
-
-<!-- Path -->
-
-<!ELEMENT path (#PCDATA) >
-<!ATTLIST path unix CDATA ""
- windows CDATA "" >
-
-<!-- List -->
-
-<!ELEMENT list (item+) >
-<!ATTLIST list type (ordered|bulleted) "bulleted" >
-<!ELEMENT taglist (tag,item)+ >
-<!ELEMENT tag (#PCDATA|i|b|c|em|seealso|url)* >
-<!ELEMENT item (%inline;|%block;)* >
-
-<!-- Image -->
-
-<!ELEMENT image (icaption?) >
-<!ATTLIST image file CDATA #REQUIRED >
-<!ELEMENT icaption (#PCDATA) >
-
-<!-- References -->
-
-<!ELEMENT seealso (#PCDATA) >
-<!ATTLIST seealso marker CDATA #REQUIRED >
-<!ELEMENT url (#PCDATA) >
-<!ATTLIST url href CDATA #REQUIRED >
-<!ELEMENT marker EMPTY >
-<!ATTLIST marker id CDATA #REQUIRED >
-
-<!-- Table -->
-
-<!ELEMENT table (row+,tcaption?) >
-<!ATTLIST table width CDATA "0"
- colspec CDATA "" >
-<!ELEMENT row (cell+) >
-<!ELEMENT cell (%inline;)* >
-<!ATTLIST cell align (left|center|right) "left"
- valign (top|middle|bottom) "middle" >
-<!ELEMENT tcaption (#PCDATA) >
-
-<!-- ErlInclude -->
-
-<!ELEMENT erlinclude EMPTY >
-<!ATTLIST erlinclude file CDATA #REQUIRED
- tag CDATA #REQUIRED >
-
-<!-- CodeInclude -->
-
-<!ELEMENT codeinclude EMPTY >
-<!ATTLIST codeinclude file CDATA #REQUIRED
- tag CDATA ""
- type (erl|c|none) "none" >
-
-<!-- ErlEval -->
-
-<!ELEMENT erleval EMPTY >
-<!ATTLIST erleval expr CDATA #REQUIRED >
-
-<!-- Index FOR COMPATIBILITY -->
-
-<!ELEMENT index EMPTY >
-<!ATTLIST index txt CDATA #REQUIRED >
-
diff --git a/lib/erl_docgen/priv/dtd/terms.dtd b/lib/erl_docgen/priv/dtd/terms.dtd
deleted file mode 100644
index c2965eb61c..0000000000
--- a/lib/erl_docgen/priv/dtd/terms.dtd
+++ /dev/null
@@ -1,37 +0,0 @@
-<!--
- ``Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- The Initial Developer of the Original Code is Ericsson AB.
- Portions created by Ericsson are Copyright 1999-2007, Ericsson AB.
- All Rights Reserved.''
-
- $Id$
--->
-<!ENTITY % ISOlat1 SYSTEM "xhtml-lat1.ent" >
-%ISOlat1;
-
-<!ENTITY amp "&#x0026;" >
-<!ENTITY gt "&#x003E;" >
-<!ENTITY lt "&#x003C;" >
-
-<!-- Structure -->
-
-<!ELEMENT terms (term)* >
-<!ELEMENT term (id, shortdef, def, resp?) >
-<!ELEMENT id (#PCDATA) >
-<!ELEMENT shortdef (#PCDATA) >
-<!ELEMENT def (#PCDATA|c|i|em)* >
-<!ELEMENT resp (#PCDATA) >
-<!ELEMENT c (#PCDATA) >
-<!ELEMENT em (#PCDATA|c)* >
-
diff --git a/lib/erl_docgen/priv/images/Makefile b/lib/erl_docgen/priv/images/Makefile
index cd98399b6a..b0263524fb 100644
--- a/lib/erl_docgen/priv/images/Makefile
+++ b/lib/erl_docgen/priv/images/Makefile
@@ -54,7 +54,9 @@ PNG_FILES = \
# ----------------------------------------------------
debug opt:
-docs:
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
clean:
$(RM) $(TARGET_FILES)
@@ -70,11 +72,12 @@ release_spec: opt
$(INSTALL_DIR) "$(RELSYSDIR)/priv/images"
$(INSTALL_DATA) $(GIF_FILES) $(PNG_FILES) "$(RELSYSDIR)/priv/images"
-
-release_docs_spec:
+release_html_spec:
$(INSTALL_DIR) "$(RELEASE_PATH)/doc"
$(INSTALL_DATA) $(PNG_FILES) "$(RELEASE_PATH)/doc"
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+
release_tests_spec:
diff --git a/lib/erl_docgen/priv/js/flipmenu/Makefile b/lib/erl_docgen/priv/js/flipmenu/Makefile
index ad6d4acb6c..be0bed74fb 100644
--- a/lib/erl_docgen/priv/js/flipmenu/Makefile
+++ b/lib/erl_docgen/priv/js/flipmenu/Makefile
@@ -56,7 +56,9 @@ JS_FILES = \
# ----------------------------------------------------
debug opt:
-docs:
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
clean:
$(RM) $(TARGET_FILES)
@@ -67,17 +69,17 @@ clean:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-
release_spec: opt
$(INSTALL_DIR) "$(RELSYSDIR)/priv/js/flipmenu"
$(INSTALL_DATA) $(JS_FILES) $(GIF_FILES) "$(RELSYSDIR)/priv/js/flipmenu"
-
-release_docs_spec:
+release_html_spec: html
$(INSTALL_DIR) "$(RELEASE_PATH)/doc/js/flipmenu"
$(INSTALL_DATA) $(JS_FILES) $(GIF_FILES) "$(RELEASE_PATH)/doc/js/flipmenu"
$(INSTALL_DATA) ../highlight.js ../highlight.pack.js "$(RELEASE_PATH)/doc/js/"
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+
release_tests_spec:
diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl
index aee496b948..690aa592c7 100644
--- a/lib/erl_docgen/priv/xsl/db_html.xsl
+++ b/lib/erl_docgen/priv/xsl/db_html.xsl
@@ -54,6 +54,27 @@
<func:result select="$result"/>
</func:function>
+ <!-- This is a XSLT 1.0 version of replace for string -->
+ <xsl:template name="string-replace-all">
+ <xsl:param name="text" />
+ <xsl:param name="replace" />
+ <xsl:param name="by" />
+ <xsl:choose>
+ <xsl:when test="contains($text, $replace)">
+ <xsl:value-of select="substring-before($text,$replace)" />
+ <xsl:value-of select="$by" />
+ <xsl:call-template name="string-replace-all">
+ <xsl:with-param name="text" select="substring-after($text,$replace)" />
+ <xsl:with-param name="replace" select="$replace" />
+ <xsl:with-param name="by" select="$by" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$text" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
<func:function name="erl:lower-case">
<xsl:param name="str"/>
@@ -227,10 +248,10 @@
<xsl:variable name="local_types"
select="../type[string-length(@name) > 0]"/>
<xsl:apply-templates select="$spec/contract/clause/head">
- <xsl:with-param name="ghlink" select="ancestor-or-self::*[@ghlink]/@ghlink"/>
- <xsl:with-param name="local_types" select="$local_types"/>
- <xsl:with-param name="global_types" select="$global_types"/>
- <xsl:with-param name="since" select="$since"/>
+ <xsl:with-param name="ghlink" select="ancestor-or-self::*[@ghlink]/@ghlink"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ <xsl:with-param name="since" select="$since"/>
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
@@ -241,7 +262,8 @@
<xsl:param name="local_types"/>
<xsl:param name="global_types"/>
<xsl:param name="since"/>
- <xsl:variable name="id" select="concat(concat(concat(concat(../../../name,'-'),../../../arity),'-'),generate-id(.))"/>
+ <xsl:variable name="mfa" select="concat(concat(../../../name,'-'),../../../arity)"/>
+ <xsl:variable name="id" select="concat(concat($mfa,'-'),generate-id(.))"/>
<table class="func-table">
<tr class="func-tr">
<td class="func-td">
@@ -249,6 +271,7 @@
onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
<xsl:call-template name="ghlink">
+ <xsl:with-param name="mfa" select="$mfa"/>
<xsl:with-param name="ghlink" select="$ghlink"/>
<xsl:with-param name="id" select="$id"/>
</xsl:call-template>
@@ -445,10 +468,12 @@
<!-- Datatypes -->
<xsl:template match="datatypes">
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Data Types</xsl:with-param>
</xsl:call-template>
<xsl:apply-templates/>
+ </div>
</xsl:template>
<!-- Datatype Title, is the really needed? not used by anything -->
@@ -467,15 +492,32 @@
<div class="data-types-body">
<xsl:choose>
<xsl:when test="string-length(name/@name) > 0">
- <xsl:variable name="id" select="concat('type-',name/@name)"/>
- <div class="data-type-name"
- onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
- onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
- <xsl:call-template name="ghlink">
- <xsl:with-param name="id" select="$id"/>
+ <xsl:variable name="apostrophe">'</xsl:variable>
+ <xsl:variable name="slash">/</xsl:variable>
+ <xsl:variable name="slash_encoded">%2f</xsl:variable>
+ <xsl:variable name="id">
+ <xsl:variable name="id-no-apostrophe">
+ <xsl:call-template name="string-replace-all">
+ <xsl:with-param name="text" select="concat('type-',name/@name)" />
+ <xsl:with-param name="replace" select="$apostrophe" />
+ <xsl:with-param name="by" select="''"/>
</xsl:call-template>
- <xsl:apply-templates select="name"/>
- </div>
+ </xsl:variable>
+ <xsl:call-template name="string-replace-all">
+ <xsl:with-param name="text" select="$id-no-apostrophe" />
+ <xsl:with-param name="replace" select="$slash" />
+ <xsl:with-param name="by" select="$slash_encoded" />
+ </xsl:call-template>
+ </xsl:variable>
+ <div class="data-type-name"
+ onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
+ onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
+ <xsl:call-template name="ghlink">
+ <xsl:with-param name="mfa" select="$id"/>
+ <xsl:with-param name="id" select="$id"/>
+ </xsl:call-template>
+ <xsl:apply-templates select="name"/>
+ </div>
</xsl:when>
<xsl:otherwise>
<div class="data-type-name">
@@ -682,7 +724,7 @@
</xsl:template>
<!-- Does not look at @n_vars -->
- <xsl:template match="seealso" mode="local_type">
+ <xsl:template match="node()[starts-with(name(), 'see')]" mode="local_type">
<xsl:param name="local_types"/>
<xsl:param name="global_types"/>
@@ -693,10 +735,10 @@
<xsl:when test="string-length($filepart) > 0">
<xsl:call-template name="seealso"/>
</xsl:when>
- <xsl:when test="count($local_types[concat('type-', @name) = $linkpart]) = 0">
+ <xsl:when test="count($local_types[@name = $linkpart]) = 0">
<xsl:call-template name="seealso"/>
</xsl:when>
- <xsl:when test="count($global_types/datatype/name[concat('type-', @name) = $linkpart]) > 0">
+ <xsl:when test="count($global_types/datatype/name[@name = $linkpart]) > 0">
<!-- The type is both local and global; link to the global type -->
<xsl:call-template name="seealso"/>
</xsl:when>
@@ -801,13 +843,11 @@
</xsl:call-template>
<div id="content">
- <div class="innertube">
<!-- Insert the node-specific content -->
<xsl:call-template name="content">
<xsl:with-param name="chapnum" select="$chapnum"/>
</xsl:call-template>
- </div>
<div class="footer">
<hr/>
@@ -837,6 +877,7 @@
<xsl:variable name="lname"><xsl:value-of select="local-name()"/></xsl:variable>
+ <div class="innertube">
<xsl:if test="$lname = 'releasenotes'">
<!-- .../part -->
<xsl:call-template name="releasenotes.content" />
@@ -859,6 +900,7 @@
<!-- .../application -->
<xsl:call-template name="app.content" />
</xsl:if>
+ </div>
<xsl:if test="$lname = 'erlref' or $lname = 'cref' or $lname= 'comref' or $lname= 'fileref' or $lname= 'appref'">
<!-- .../application/*ref -->
<xsl:comment> refpage </xsl:comment>
@@ -993,7 +1035,7 @@
<xsl:template match="header"/>
<!-- Section/Title -->
- <xsl:template match="section/title"/>
+ <xsl:template match="section/title|fsdescription/title"/>
<xsl:template match="pagetext"/>
@@ -1052,8 +1094,9 @@
</xsl:template>
<!-- *ref/Section -->
- <xsl:template match="erlref/section|cref/section|comref/section|fileref/section|appref/section">
+ <xsl:template match="erlref/section|cref/section|comref/section|fileref/section|appref/section|funcs/fsdescription">
<xsl:param name="chapnum"/>
+ <div class="innertube">
<h3>
<xsl:for-each select="marker">
<xsl:call-template name="marker-before-title"/>
@@ -1067,6 +1110,7 @@
<xsl:with-param name="chapnum" select="$chapnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- *ref/Subsection -->
@@ -1378,7 +1422,7 @@
<xsl:param name="chapnum"/>
<div id="leftnav">
- <div class="innertube">
+ <div class="leftnav-tube">
<xsl:call-template name="erlang_logo"/>
@@ -1406,7 +1450,7 @@
<xsl:template name="menu.internal.ref">
<xsl:param name="curModule"/>
<div id="leftnav">
- <div class="innertube">
+ <div class="leftnav-tube">
<xsl:call-template name="erlang_logo"/>
@@ -1473,7 +1517,7 @@
<xsl:param name="chapnum"/>
<div id="leftnav">
- <div class="innertube">
+ <div class="leftnav-tube">
<xsl:call-template name="erlang_logo"/>
@@ -1634,7 +1678,7 @@
<xsl:template name="menu.ref">
<xsl:param name="curModule"/>
<div id="leftnav">
- <div class="innertube">
+ <div class="leftnav-tube">
<xsl:call-template name="erlang_logo"/>
@@ -1964,6 +2008,7 @@
<xsl:template name="ref.content">
<xsl:param name="partnum"/>
+ <div class="innertube">
<center>
<h1>
<xsl:choose>
@@ -1985,17 +2030,18 @@
</xsl:choose>
</h1>
</center>
+ </div>
<xsl:apply-templates>
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
-
</xsl:template>
<!-- Module -->
<xsl:template match="module">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Module</xsl:with-param>
</xsl:call-template>
@@ -2004,12 +2050,14 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- Modulesummary -->
<xsl:template match="modulesummary">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Module Summary</xsl:with-param>
</xsl:call-template>
@@ -2028,11 +2076,13 @@
<xsl:value-of select="../module/@since"/>.
</div>
</xsl:if>
+ </div>
</xsl:template>
<!-- Lib -->
<xsl:template match="lib">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">C Library</xsl:with-param>
</xsl:call-template>
@@ -2041,12 +2091,14 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- Libsummary -->
<xsl:template match="libsummary">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Library Summary</xsl:with-param>
</xsl:call-template>
@@ -2055,11 +2107,13 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- Com -->
<xsl:template match="com">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Command</xsl:with-param>
</xsl:call-template>
@@ -2068,12 +2122,14 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- Comsummary -->
<xsl:template match="comsummary">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Command Summary</xsl:with-param>
</xsl:call-template>
@@ -2082,11 +2138,13 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- File -->
<xsl:template match="file">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">File</xsl:with-param>
</xsl:call-template>
@@ -2095,12 +2153,14 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- Filesummary -->
<xsl:template match="filesummary">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">File Summary</xsl:with-param>
</xsl:call-template>
@@ -2109,12 +2169,14 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- App -->
<xsl:template match="app">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Application</xsl:with-param>
</xsl:call-template>
@@ -2123,12 +2185,14 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- Appsummary -->
<xsl:template match="appsummary">
<xsl:param name="partnum"/>
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Application Summary</xsl:with-param>
</xsl:call-template>
@@ -2137,12 +2201,15 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ </div>
</xsl:template>
<!-- Description -->
<xsl:template match="description">
<xsl:param name="partnum"/>
- <xsl:call-template name="h3_title_link">
+
+ <div class="innertube">
+ <xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Description</xsl:with-param>
</xsl:call-template>
<div class="REFBODY description-body">
@@ -2152,20 +2219,27 @@
</xsl:apply-templates>
</p>
</div>
+ </div>
</xsl:template>
<!-- Funcs -->
<xsl:template match="funcs">
<xsl:param name="partnum"/>
+ <xsl:apply-templates select="fsdescription">
+ <xsl:with-param name="partnum" select="$partnum"/>
+ </xsl:apply-templates>
+
+ <div class="innertube">
<xsl:call-template name="h3_title_link">
<xsl:with-param name="title">Exports</xsl:with-param>
</xsl:call-template>
+ </div>
<div class="exports-body">
- <xsl:apply-templates>
- <xsl:with-param name="partnum" select="$partnum"/>
- </xsl:apply-templates>
+ <xsl:apply-templates select="func">
+ <xsl:with-param name="partnum" select="$partnum"/>
+ </xsl:apply-templates>
</div>
</xsl:template>
@@ -2179,9 +2253,11 @@
select="name[string-length(@arity) > 0 and position()=last()]"
mode="types"/>
+ <div class="exports-tube">
<xsl:apply-templates select="fsummary|type|desc">
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
+ </div>
</xsl:template>
@@ -2242,6 +2318,7 @@
<span class="bold_code bc-7">
<xsl:call-template name="title_link">
<xsl:with-param name="link" select="substring-before(nametext, '(')"/>
+ <xsl:with-param name="where" select="'before'"/>
</xsl:call-template>
</span>
</td>
@@ -2263,10 +2340,10 @@
</xsl:variable>
<xsl:choose>
<xsl:when test="string-length($fname2) > 0">
- <xsl:value-of select="$fname2"/>
+ <xsl:value-of select="normalize-space($fname2)"/>
</xsl:when>
<xsl:otherwise>
- <xsl:value-of select="$fname1"/>
+ <xsl:value-of select="normalize-space($fname1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
@@ -2275,6 +2352,7 @@
<div class="bold_code bc-8">
<xsl:call-template name="title_link">
<xsl:with-param name="link" select="concat('type-',$fname)"/>
+ <xsl:with-param name="where" select="'before'"/>
<xsl:with-param name="title">
<xsl:apply-templates/>
</xsl:with-param>
@@ -2288,6 +2366,7 @@
<div class="bold_code fun-type">
<xsl:call-template name="title_link">
<xsl:with-param name="link" select="concat(concat($fname,'-'),$arity)"/>
+ <xsl:with-param name="where" select="'before'"/>
<xsl:with-param name="title">
<xsl:apply-templates/>
</xsl:with-param>
@@ -2366,43 +2445,63 @@
<xsl:template name="title_link">
<xsl:param name="title" select="'APPLY'"/>
<xsl:param name="link" select="erl:to-link(title)"/>
+ <xsl:param name="where" select="'after'"/>
<xsl:param name="ghlink" select="ancestor-or-self::*[@ghlink][position() = 1]/@ghlink"/>
<xsl:variable name="id" select="concat(concat($link,'-'), generate-id(.))"/>
<span onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
- <xsl:call-template name="ghlink">
- <xsl:with-param name="id" select="$id"/>
- <xsl:with-param name="ghlink" select="$ghlink"/>
- </xsl:call-template>
- <a class="title_link" name="{$link}" href="#{$link}">
- <xsl:choose>
+ <xsl:choose>
+ <xsl:when test="$where = 'before'">
+ <xsl:call-template name="ghlink">
+ <xsl:with-param name="mfa" select="$link"/>
+ <xsl:with-param name="id" select="$id"/>
+ <xsl:with-param name="ghlink" select="$ghlink"/>
+ <xsl:with-param name="where" select="$where"/>
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
+ <a class="title_link" name="{$link}">
+ <xsl:choose>
<xsl:when test="$title = 'APPLY'">
<xsl:apply-templates/> <!-- like <ret> and <nametext> -->
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$title"/>
</xsl:otherwise>
- </xsl:choose>
+ </xsl:choose>
</a>
+ <xsl:choose>
+ <xsl:when test="$where = 'after'">
+ <xsl:call-template name="ghlink">
+ <xsl:with-param name="mfa" select="$link"/>
+ <xsl:with-param name="id" select="$id"/>
+ <xsl:with-param name="ghlink" select="$ghlink"/>
+ <xsl:with-param name="where" select="$where"/>
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
</span>
</xsl:template>
<xsl:template name="ghlink">
+ <xsl:param name="mfa"/>
<xsl:param name="id"/>
<xsl:param name="ghlink" select="ancestor-or-self::*[@ghlink][position() = 1]/@ghlink"/>
- <xsl:choose>
- <xsl:when test="string-length($ghlink) > 0">
- <span id="ghlink-{$id}" class="ghlink">
+ <xsl:param name="where" select="'before'"/>
+ <xsl:variable name="escaped_mfa" select="$mfa"/>
+ <span id="ghlink-{$id}" class="ghlink-{$where}">
+ <a href="#{$mfa}" title="Link to this place!">
+ <span class="paperclip-{$where}"/>
+ </a>
+ <xsl:choose>
+ <xsl:when test="string-length($ghlink) > 0">
<a href="https://github.com/erlang/otp/edit/{$ghlink}"
title="Found an issue with the documentation? Fix it by clicking here!">
- <span class="pencil"/>
+ <span class="pencil-{$where}"/>
</a>
- </span>
- </xsl:when>
- <xsl:otherwise>
- <span id="ghlink-{$id}"/>
- </xsl:otherwise>
- </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+ </span>
</xsl:template>
<!-- Desc -->
@@ -2428,22 +2527,83 @@
<span class="bold_code bc-12"><xsl:apply-templates/></span>
</xsl:template>
- <xsl:template match="seealso">
+ <xsl:template match="node()[starts-with(name(), 'see')]">
<xsl:call-template name="seealso"/>
</xsl:template>
<xsl:template name="seealso">
- <xsl:variable name="filepart"><xsl:value-of select="substring-before(@marker, '#')"/></xsl:variable>
- <xsl:variable name="linkpart"><xsl:value-of select="translate(substring-after(@marker, '#'), '/', '-')"/></xsl:variable>
+
+ <xsl:variable name="app_part">
+ <xsl:variable name="base">
+ <xsl:value-of select="substring-before(substring-before(concat(@marker,'#'), '#'),':')"/>
+ </xsl:variable>
+ <xsl:choose>
+ <xsl:when test="starts-with($base,'system/')">
+ <xsl:text>doc/</xsl:text>
+ <xsl:value-of select="substring-after($base,'/')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$base"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:variable name="mod_part">
+ <xsl:variable name="filepart">
+ <!-- Get everything before the first #. We concat a # so that if there
+ is no # we will get the entire string -->
+ <xsl:value-of select="substring-before(concat(@marker,'#'), '#')"/>
+ </xsl:variable>
+ <xsl:variable name="base">
+ <!-- Remove the app part of there is any -->
+ <xsl:choose>
+ <xsl:when test="string-length($app_part) > 0">
+ <xsl:value-of select="substring-after($filepart, ':')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$filepart"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:choose>
+ <!-- If this is a <seeguide> and name is index then we change it to users_guide -->
+ <xsl:when test="node()[starts-with(name(parent::*), 'seeguide')] and $base = 'index'">
+ <xsl:text>users_guide</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$base"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:variable name="linkpart">
+ <xsl:variable name="base">
+ <xsl:value-of select="substring-after(@marker, '#')"/>
+ </xsl:variable>
+ <xsl:choose>
+ <!-- If this is a <seetype> we prepend type- to the anchor -->
+ <xsl:when test="node()[starts-with(name(parent::*), 'seetype')]">
+ <xsl:text>type-</xsl:text><xsl:value-of select="$base"/>
+ </xsl:when>
+ <xsl:when test="node()[starts-with(name(parent::*), 'seemfa')]">
+ <xsl:value-of select="translate($base, '/', '-')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$base"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
<xsl:choose>
- <xsl:when test="string-length($filepart) > 0">
+ <xsl:when test="starts-with(@marker,'#')">
+ <!-- "#Linkpart" -->
+ <span class="bold_code bc-17"><a href="#{$linkpart}"><xsl:apply-templates/></a></span>
+ </xsl:when>
+ <xsl:when test="contains(@marker,'#')">
<!-- "Filepart#Linkpart" (or "Filepart#") -->
- <xsl:variable name="app_part"><xsl:value-of select="substring-before($filepart, ':')"/></xsl:variable>
<xsl:choose>
<xsl:when test="string-length($app_part) > 0">
<!-- "AppPart:ModPart#Linkpart" -->
- <xsl:variable name="mod_part"><xsl:value-of select="substring-after($filepart, ':')"/></xsl:variable>
<span class="bold_code bc-13"><a href="javascript:erlhref('{$topdocdir}/../','{$app_part}','{$mod_part}.html#{$linkpart}');"><xsl:apply-templates/></a></span>
</xsl:when>
<xsl:otherwise>
@@ -2453,7 +2613,7 @@
<xsl:choose>
<xsl:when test="$minus_prefix = 'type'
and string-length($specs_file) > 0
- and count($i/specs/module[@name=$filepart]) = 0">
+ and count($i/specs/module[@name=$mod_part]) = 0">
<!-- Dialyzer seealso (the application is unknown) -->
<!-- Following code deemed too slow; use key() instead
<xsl:variable name="app"
@@ -2461,49 +2621,45 @@
-->
<xsl:variable name="this" select="."/>
<xsl:for-each select="$m2a">
- <xsl:variable name="app" select="key('mod2app', $filepart)"/>
+ <xsl:variable name="app" select="key('mod2app', $mod_part)"/>
<xsl:choose>
<xsl:when test="string-length($app) > 0">
- <span class="bold_code bc-14"><a href="javascript:erlhref('{$topdocdir}/../','{$app}','{$filepart}.html#{$linkpart}');"><xsl:value-of select="$this"/></a></span>
+ <span class="bold_code bc-14"><a href="javascript:erlhref('{$topdocdir}/../','{$app}','{$mod_part}.html#{$linkpart}');"><xsl:value-of select="$this"/></a></span>
</xsl:when>
<xsl:otherwise>
<!-- Unknown application -->
<xsl:message terminate="yes">
- Error <xsl:value-of select="$filepart"/>: cannot find module exporting type
+ Error <xsl:value-of select="$mod_part"/>: cannot find module exporting type
+ <xsl:value-of select="$app_part"/> -
+ <xsl:value-of select="$linkpart"/>
</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:when>
<xsl:when test="string-length($linkpart) > 0">
- <!-- Still Filepart#Linkpart (there is no ':' in Filepart -->
- <span class="bold_code bc-15"><a href="{$filepart}.html#{$linkpart}"><xsl:apply-templates/></a></span>
+ <!-- Still Filepart#Linkpart (there is no ':' in Filepart) -->
+ <span class="bold_code bc-15"><a href="{$mod_part}.html#{$linkpart}"><xsl:apply-templates/></a></span>
</xsl:when>
<xsl:otherwise>
- <!-- "Filepart#" (there is no ':' in Filepart -->
- <span class="bold_code bc-16"><a href="{$filepart}.html"><xsl:apply-templates/></a></span>
+ <!-- "Filepart#" (there is no ':' in Filepart) -->
+ <span class="bold_code bc-16"><a href="{$mod_part}.html"><xsl:apply-templates/></a></span>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
- </xsl:when> <!-- string-length($filepart) > 0 -->
- <xsl:when test="string-length($linkpart) > 0">
- <!-- "#Linkpart" -->
- <span class="bold_code bc-17"><a href="#{$linkpart}"><xsl:apply-templates/></a></span>
</xsl:when>
<xsl:otherwise>
<!-- "AppPart:Mod" or "Mod" (there is no '#') -->
- <xsl:variable name="app_part"><xsl:value-of select="substring-before(@marker, ':')"/></xsl:variable>
<xsl:choose>
<xsl:when test="string-length($app_part) > 0">
<!-- "App:Mod" -->
- <xsl:variable name="mod_part"><xsl:value-of select="substring-after(@marker, ':')"/></xsl:variable>
<span class="bold_code bc-18"><a href="javascript:erlhref('{$topdocdir}/../','{$app_part}','{$mod_part}.html');"><xsl:apply-templates/></a></span>
</xsl:when>
<xsl:otherwise>
<!-- "Mod" -->
- <span class="bold_code bc-19"><a href="{@marker}.html"><xsl:apply-templates/></a></span>
+ <span class="bold_code bc-19"><a href="{$mod_part}.html"><xsl:apply-templates/></a></span>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
@@ -2532,30 +2688,6 @@
</xsl:template>
- <xsl:template match="term">
- <xsl:value-of select="@id"/>
- <!-- xsl:choose>
- <xsl:when test="boolean(termdef)">
- <xsl:choose>
- <xsl:when test="ancestor::parts">
- <a href="users_guide_glossary.html#{@id}"><xsl:value-of select="@id"/></a>
- </xsl:when>
- <xsl:when test="ancestor::applications">
- <a href="ref_man_glossary.html#{@id}"><xsl:value-of select="@id"/></a>
- </xsl:when>
- </xsl:choose>
- </xsl:when>
- <xsl:otherwise>
- <a href="{$topdocdir}/glossary.html#{@id}"><xsl:value-of select="@id"/></a>
- </xsl:otherwise>
- </xsl:choose -->
- </xsl:template>
-
- <xsl:template match="cite">
- <xsl:value-of select="@id"/>
- </xsl:template>
-
-
<!-- Release Notes -->
<xsl:template match="releasenotes">
@@ -2585,7 +2717,7 @@
<xsl:param name="chapnum"/>
<div id="leftnav">
- <div class="innertube">
+ <div class="leftnav-tube">
<xsl:call-template name="erlang_logo"/>
@@ -2609,118 +2741,6 @@
</div>
</xsl:template>
- <!-- Glossary -->
- <xsl:template name="glossary">
- <xsl:param name="type"/>
- <xsl:document href="{$outdir}/{$type}_glossary.html" method="html" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <link rel="stylesheet" href="{$topdocdir}/otp_doc.css" type="text/css"/>
- <title>Erlang Documentation -- <xsl:value-of select="header/title"/></title>
- </head>
- <body bgcolor="white" text="#000000" link="#0000ff" vlink="#ff00ff" alink="#ff0000">
-
- <div id="container">
- <script id="js" type="text/javascript" language="JavaScript" src="{$topdocdir}/js/flipmenu/flipmenu.js"/>
- <script id="js2" type="text/javascript" src="{$topdocdir}/js/erlresolvelinks.js"></script>
-
- <!-- Generate menu -->
- <xsl:call-template name="menu"/>
-
- <div id="content">
- <div class="innertube">
- <h1>Glossary</h1>
- </div>
-
- <dl>
- <xsl:for-each select="descendant::term">
- <xsl:sort select="@id"/>
- <xsl:if test="boolean(termdef)">
- <dt><a name="{@id}"><strong><xsl:value-of select="@id"/></strong></a></dt>
- <dd><xsl:value-of select="termdef"/></dd>
- </xsl:if>
- </xsl:for-each>
- </dl>
-
- <div class="footer">
- <hr/>
- <p>
- <xsl:value-of select="$copyright"/>
- <xsl:value-of select="header/copyright/year[1]"/>
- <xsl:text>-</xsl:text>
- <xsl:value-of select="header/copyright/year[2]"/>
- <xsl:text> </xsl:text>
- <xsl:value-of select="header/copyright/holder"/>
- </p>
- </div>
-
- </div>
- </div>
-
- </body>
- </html>
-
- </xsl:document>
- </xsl:template>
-
- <!-- Bibliography -->
- <xsl:template name="bibliography">
-
- <xsl:param name="type"/>
- <xsl:document href="{$outdir}/{$type}_bibliography.html" method="html" encoding="UTF-8" indent="yes" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <link rel="stylesheet" href="{$topdocdir}/otp_doc.css" type="text/css"/>
- <title>Erlang Documentation -- <xsl:value-of select="header/title"/></title>
- </head>
- <body bgcolor="white" text="#000000" link="#0000ff" vlink="#ff00ff" alink="#ff0000">
-
- <div id="container">
- <script id="js" type="text/javascript" language="JavaScript" src="{$topdocdir}/js/flipmenu/flipmenu.js"/>
- <script id="js2" type="text/javascript" src="{$topdocdir}/js/erlresolvelinks.js"></script>
-
- <!-- Generate menu -->
- <xsl:call-template name="menu"/>
-
- <div id="content">
- <div class="innertube">
- <h1>Bibliography</h1>
- </div>
-
- <table>
- <xsl:for-each select="descendant::cite">
- <xsl:sort select="@id"/>
- <xsl:if test="boolean(citedef)">
- <tr>
- <td><xsl:value-of select="@id"/></td>
- <td><xsl:value-of select="citedef"/></td>
- </tr>
- </xsl:if>
- </xsl:for-each>
- </table>
-
- <div class="footer">
- <hr/>
- <p>
- <xsl:value-of select="$copyright"/>
- <xsl:value-of select="header/copyright/year[1]"/>
- <xsl:text>-</xsl:text>
- <xsl:value-of select="header/copyright/year[2]"/>
- <xsl:text> </xsl:text>
- <xsl:value-of select="header/copyright/holder"/>
- </p>
- </div>
-
- </div>
- </div>
-
- </body>
- </html>
-
- </xsl:document>
- </xsl:template>
-
-
<!-- Special templates to calculate the arity of functions -->
<xsl:template name="calc-arity">
<xsl:param name="string"/>
@@ -2809,7 +2829,7 @@
</xsl:text>
</xsl:template>
- <xsl:template match="seealso//text()">
+ <xsl:template match="node()[starts-with(name(), 'see')]//text()">
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>
diff --git a/lib/erl_docgen/priv/xsl/db_man.xsl b/lib/erl_docgen/priv/xsl/db_man.xsl
index 27b2bd4066..4278a41800 100644
--- a/lib/erl_docgen/priv/xsl/db_man.xsl
+++ b/lib/erl_docgen/priv/xsl/db_man.xsl
@@ -456,11 +456,11 @@
</xsl:template>
<!-- Section/Title -->
- <xsl:template match="section/title">
+ <xsl:template match="section/title|fsdescription/title">
</xsl:template>
<!-- *ref/Section -->
- <xsl:template match="erlref/section|comref/section|cref/section|fileref/section|appref/section">
+ <xsl:template match="erlref/section|comref/section|cref/section|fileref/section|appref/section|funcs/fsdescription">
<xsl:text>&#10;.SH "</xsl:text><xsl:call-template name="replace-string">
<xsl:with-param name="text" select="translate(title, 'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
<xsl:with-param name="replace" select="&quot;\&quot;" />
@@ -765,8 +765,9 @@
<!-- Funcs -->
<xsl:template match="funcs">
+ <xsl:apply-templates select="fsdescription"/>
<xsl:text>&#10;.SH EXPORTS</xsl:text>
- <xsl:apply-templates/>
+ <xsl:apply-templates select="func"/>
</xsl:template>
<!-- Func -->
diff --git a/lib/erl_docgen/priv/xsl/db_pdf.xsl b/lib/erl_docgen/priv/xsl/db_pdf.xsl
index 7080394298..fcae6ec132 100644
--- a/lib/erl_docgen/priv/xsl/db_pdf.xsl
+++ b/lib/erl_docgen/priv/xsl/db_pdf.xsl
@@ -980,7 +980,7 @@
</fo:block>
- <xsl:apply-templates select="section|quote|warning|note|br|image|marker|table|p|pre|code|list|taglist|codeinclude|erleval">
+ <xsl:apply-templates select="section|quote|warning|note|br|image|marker|table|p|pre|code|list|taglist|codeinclude">
<xsl:with-param name="partnum" select="$partnum"/>
<xsl:with-param name="chapnum"><xsl:number/></xsl:with-param>
</xsl:apply-templates>
@@ -1065,7 +1065,7 @@
</xsl:template>
<!-- Section/title -->
- <xsl:template match="section/title">
+ <xsl:template match="section/title|fsdescription/title">
</xsl:template>
<!-- Lists -->
@@ -1446,11 +1446,14 @@
<!-- Funcs -->
<xsl:template match="funcs">
<xsl:param name="partnum"/>
+ <xsl:apply-templates select="fsdescription">
+ <xsl:with-param name="partnum" select="$partnum"/>
+ </xsl:apply-templates>
<fo:block xsl:use-attribute-sets="h3">
<xsl:text>Exports</xsl:text>
</fo:block>
- <xsl:apply-templates>
+ <xsl:apply-templates select="func">
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
diff --git a/lib/erl_docgen/src/Makefile b/lib/erl_docgen/src/Makefile
index 82d051e9bb..4c6f542ebb 100644
--- a/lib/erl_docgen/src/Makefile
+++ b/lib/erl_docgen/src/Makefile
@@ -38,7 +38,8 @@ RELSYSDIR = $(RELEASE_PATH)/lib/erl_docgen-$(VSN)
MODULES = \
docgen_otp_specs \
docgen_edoc_xml_cb \
- docgen_xmerl_xml_cb
+ docgen_xmerl_xml_cb \
+ docgen_xml_to_chunk
HRL_FILES =
diff --git a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
index 5342d02947..d5511eae9d 100644
--- a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
+++ b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
@@ -11,7 +11,7 @@
%% limitations under the License.
%%
%% Copyright (c) 2001-2016 Richard Carlsson. Parts written by Ericsson
-%% are Copyright (c) Ericsson AB 2001-2017. All Rights Reserved.
+%% are Copyright (c) Ericsson AB 2001-2020. All Rights Reserved.
%%
-module(docgen_edoc_xml_cb).
@@ -26,7 +26,7 @@
%% The origin of this file is the edoc module otpsgml_layout.erl
%% written by Richard Carlsson and Kenneth Lundin.
--export([module/2, overview/2]).
+-export([module/2, overview/2, makesee/1]).
-include("xmerl.hrl").
@@ -50,6 +50,7 @@ overview(Element, Opts) ->
%%--Internal functions--------------------------------------------------
layout_module(#xmlElement{name = module, content = Es}=E, SortP) ->
+ put(type, module),
Name = get_attrval(name, E),
Desc = get_content(description, Es),
ShortDesc = text_only(get_content(briefDescription, Desc)),
@@ -120,6 +121,7 @@ reformat_encoding(List) when is_list(List) ->
reformat_encoding(Other) -> Other.
layout_chapter(#xmlElement{name=overview, content=Es}) ->
+ put(type, chapter),
Title = get_text(title, Es),
Header = {header, [
?NL,{title,[Title]},
@@ -485,8 +487,8 @@ filter_a_attrs([]) ->
%% otp_xmlify_a_href(Href0, Es0) -> {Href1, Es1}
%% Href = string()
-otp_xmlify_a_href("#"++_ = Marker, Es0) -> % <seealso marker="#what">
- {Marker, Es0};
+otp_xmlify_a_href("#"++_ = Marker, Es0) -> % <see* marker="#what">
+ {make_mfa_anchor(Marker), Es0};
otp_xmlify_a_href("http:"++_ = URL, Es0) -> % external URL
{URL, Es0};
otp_xmlify_a_href("https:"++_ = URL, Es0) -> % external URL
@@ -541,48 +543,7 @@ otp_xmlify_a_fileref(FileRef1, AppS) ->
Ext=="html", AppS/=this ->
File;
[File, Ext, Marker0] ->
- %% Here is an awkward solution to an awkward problem
- %% The marker automatically inserted at each function
- %% does not seem to work for EDoc generated ERLREFs.
- %% So if the referenced marker is in an ERLREF generated
- %% by EDoc, keep it "as is", ie "function-arity".
- %% If the referenced marker is NOT in an ERLREF generated
- %% by EDoc, the marker should be on the format
- %% "function/arity".
- %% The awkward part of the solution is to decide wheather
- %% the ERLREF is generated by EDoc or not: Here we make
- %% the decision based on which application the module
- %% belongs to -- which is ok when the module was written
- %% but probably not in the future...
- EDocApps = ["edoc","hipe","syntax_tools","xmerl"],
- IsEDocGenerated = lists:member(AppS, EDocApps),
- Marker = if
- %% The marker is in a file in *this*
- %% application (which documentation obviously
- %% is generated by EDoc), or it is in a file
- %% in an application which documentation
- %% is assumed to be generated by EDoc
- AppS==this; IsEDocGenerated ->
- Marker0;
-
- %% The marker is in a file in an application
- %% which documentation is assumed NOT to be
- %% generated by EDoc
- true ->
- case split(Marker0, "-") of
- [Func,Arity] ->
- try list_to_integer(Arity) of
- _ ->
- Func++"/"++Arity
- catch
- _:_ ->
- %% This is "type-"<a-type>.
- Marker0
- end;
- _ ->
- Marker0
- end
- end,
+ Marker = make_mfa_anchor(Marker0),
if
%% Ignore file extension in file reference if it either
%% is ".xml" or if it is ".html" but AppS/=this, that
@@ -683,6 +644,22 @@ otp_xmlify_img(E0) ->
%%--Misc help functions used by otp_xmlify/1 et al---------------------
+%% make_mfa_anchor(Marker) -> NewMarker
+%% Returns the anchor in func/arity format if the heuristic guesses that
+%% the anchor points to a func/arity
+make_mfa_anchor(Marker) ->
+ case split(Marker,"-") of
+ [Func,Arity] ->
+ try list_to_integer(Arity) of
+ _ ->
+ Func ++ "/" ++ Arity
+ catch _:_ ->
+ Marker
+ end;
+ _ ->
+ Marker
+ end.
+
%% find_next(Tag, Es) -> {Es1, Es2}
%% Returns {Es1, Es2} where Es1 is the list of all elements up to (but
%% not including) the first element with tag Tag in Es, and Es2
@@ -780,23 +757,21 @@ functions(Fs) ->
function(_Name, E=#xmlElement{content = Es}) ->
TypeSpec = get_content(typespec, Es),
- [?NL,{func, [ ?NL,
- {name,
- case funcheader(TypeSpec) of
- [] ->
- signature(get_content(args, Es),
- get_attrval(name, E));
- Spec -> Spec
- end
- },
- ?NL,{fsummary, fsummary(Es)},
- ?NL,local_types(TypeSpec),
- ?NL,{desc,
- label_anchor(E)++
- deprecated(Es)++
- fulldesc(Es)++
- seealso_function(Es)}
- ]}].
+ FuncHeaders =
+ case funcheader(TypeSpec) of
+ [] ->
+ [signature(get_content(args, Es), get_attrval(name, E))];
+ Specs ->
+ Specs
+ end,
+ [?NL, {func, [?NL]++
+ [{name, [{since,""}], Spec} || Spec <- FuncHeaders]++
+ [?NL, {fsummary, fsummary(Es)},
+ ?NL, local_types(TypeSpec),
+ ?NL, {desc, label_anchor(E)++
+ deprecated(Es)++
+ fulldesc(Es)++
+ seealso_function(Es)}]}].
fsummary([]) -> ["\s"];
fsummary(Es) ->
@@ -832,7 +807,7 @@ label_anchor(Content, E) ->
{em, Content}]}
end.
-signature(Es, Name) ->
+signature(Es, Name) ->
[Name, "("] ++ seq(fun arg/1, Es) ++ [") -> term()", ?NL].
arg(#xmlElement{content = Es}) ->
@@ -840,7 +815,9 @@ arg(#xmlElement{content = Es}) ->
funcheader([]) -> [];
funcheader(Es) ->
- [t_name(get_elem(erlangName, Es))] ++ t_utype(get_elem(type, Es)).
+ Name = t_name(get_elem(erlangName, Es)),
+ [ [Name] ++ t_utype([E]) || E <- get_elem(type, Es)].
+
local_types([]) -> [];
local_types(Es) ->
@@ -921,7 +898,7 @@ local_defs(Es) ->
localdef(E = #xmlElement{content = Es}) ->
Var = case get_elem(typevar, Es) of
- [] ->
+ [] ->
[label_anchor(t_abstype(get_content(abstype, Es)), E)];
[V] ->
t_var(V)
@@ -976,8 +953,8 @@ seealso_function(Es) ->
see(#xmlElement{content=Es0} = E) ->
Href0 = get_attrval(href, E),
{Href, Es} = otp_xmlify_a_href(Href0, Es0),
- [{seealso, [{marker, Href}], Es}].
-
+ [makesee(Href, Es)].
+
equiv(Es) ->
case get_content(equiv, Es) of
[] -> ["\s"];
@@ -993,14 +970,52 @@ equiv(Es) ->
case get_attrval(href, E) of
"" ->
{c,Expr1};
- Ref ->
- {seealso, [{marker, Ref}], Expr1}
+ Ref0 ->
+ {Ref, _Es2} = otp_xmlify_a_href(Ref0, [E]),
+ makesee(Ref, Expr1)
end
end,
[{p, ["Equivalent to ", Expr2, "."]}, ?NL]
end
end.
+makesee(Ref, Es) ->
+ {Tag, Marker} = makesee(Ref),
+ {Tag, [{marker,Marker}], Es}.
+makesee(Ref) ->
+ case string:split(Ref,"#") of
+ ["chapter"] ->
+ {seeguide,"chapter"};
+ ["chapter",Anchor] ->
+ {seeguide,"chapter#" ++ Anchor};
+ [Mod,"type-"++Anchor] ->
+ {seeerl,Mod ++ "#type-" ++ Anchor};
+ ["",_Anchor] ->
+ case get(type) of
+ chapter ->
+ {seeguide, Ref};
+ module ->
+ case split(Ref,"/") of
+ [_,_] ->
+ {seemfa, Ref};
+ _ ->
+ {seeerl, Ref}
+ end
+ end;
+ _Else ->
+ case split(Ref,":") of
+ [_,"index"] ->
+ {seeapp, Ref};
+ _ ->
+ case split(Ref,"/") of
+ [_,_] ->
+ {seemfa, Ref};
+ _ ->
+ {seeerl, Ref}
+ end
+ end
+ end.
+
authors(Es) ->
case get_elem(author, Es) of
[] ->
@@ -1020,7 +1035,7 @@ author(E=#xmlElement{}) ->
end,
[?NL,{aname,[Name]},?NL,{email,[Mail]}].
-t_name([E]) ->
+t_name([E | _]) ->
N = get_attrval(name, E),
case get_attrval(module, E) of
"" -> N;
@@ -1147,7 +1162,7 @@ see_type(E, Es0) ->
%% Fails for parametrized types:
Text = #xmlText{value = lists:append(Es0)},
{Href, Es} = otp_xmlify_a_href(Href0, [Text]),
- [{seealso, [{marker, Href}], Es}]
+ [makesee(Href, Es)]
catch
_:_ ->
Es0
@@ -1231,16 +1246,19 @@ get_attrval(Name, #xmlElement{attributes = As}) ->
%% get_content(Tag, Es1) -> Es2
%% If there is one element in Es1 with name Tag, returns its contents,
-%% otherwise []
+%% if there are no tags, return [],
+%% if there are multiple, merge their contents.
get_content(Name, Es) ->
case get_elem(Name, Es) of
- [#xmlElement{content = Es1}] ->
- Es1;
- [] -> []
+ [#xmlElement{content = Es1}] ->
+ Es1;
+ [] -> [];
+ Elems ->
+ lists:append([Es1 || #xmlElement{content = Es1} <- Elems])
end.
%% get_text(Tag, Es) -> string()
-%% If there is one element in Es with name Tag, and its content is
+%% If there is one element in Es with name Tag, and its content is
%% a single xmlText, return the value of this xmlText.
%% Otherwise return "".
get_text(Name, Es) ->
diff --git a/lib/erl_docgen/src/docgen_otp_specs.erl b/lib/erl_docgen/src/docgen_otp_specs.erl
index 311ec1471a..7a81056aae 100644
--- a/lib/erl_docgen/src/docgen_otp_specs.erl
+++ b/lib/erl_docgen/src/docgen_otp_specs.erl
@@ -252,10 +252,10 @@ diaf(L, St, " "++O, R, Opts) ->
diaf(L, St, "\n"++O, R, Opts) ->
Ss = lists:takewhile(fun(C) -> C =:= $\s end, O),
diaf(L, St, lists:nthtail(length(Ss), O), ["\n"++Ss | R], Opts);
-diaf([{seealso, HRef0, S0} | L], St, O0, R, Opts) ->
+diaf([{seetype, HRef0, S0} | L], St, O0, R, Opts) ->
{S, O} = diaf(S0, app_fix(O0), Opts),
HRef = fix_mod_ref(HRef0, Opts),
- diaf(L, St, O, [{seealso, HRef, S} | R], Opts);
+ diaf(L, St, O, [{seetype, HRef, S} | R], Opts);
diaf("="++L, St, "::"++O, R, Opts) ->
%% EDoc uses "=" for record field types; Dialyzer uses "::". Maybe
%% there should be an option for this, possibly affecting other
@@ -329,8 +329,8 @@ fix_mod_ref([{marker, S}]=HRef0, #opts{file_suffix = FS}) ->
see(E, Es) ->
case href(E) of
[] -> Es;
- Ref ->
- [{seealso, Ref, Es}]
+ [{marker,Ref}] ->
+ [{seetype, [{marker,lists:flatten(string:replace(Ref,"#type-","#"))}], Es}]
end.
href(E) ->
diff --git a/lib/erl_docgen/src/docgen_xmerl_xml_cb.erl b/lib/erl_docgen/src/docgen_xmerl_xml_cb.erl
index 9d69143c3c..b4f619ea22 100644
--- a/lib/erl_docgen/src/docgen_xmerl_xml_cb.erl
+++ b/lib/erl_docgen/src/docgen_xmerl_xml_cb.erl
@@ -65,7 +65,7 @@ convert_tag(a, [Attr]) ->
true ->
{url, [Attr]};
false ->
- {seealso, [Attr#xmlAttribute{name=marker}]}
+ makesee(Val)
end;
name ->
{marker, [Attr#xmlAttribute{name=id}]}
@@ -94,3 +94,7 @@ is_url(FileRef) ->
"" -> false; % no extension = xml file
_Ext -> true % extension
end.
+
+makesee(Ref) ->
+ {Tag, Marker} = docgen_edoc_xml_cb:makesee(Ref),
+ {Tag,[#xmlAttribute{name = marker, value = Marker}]}.
diff --git a/lib/erl_docgen/src/docgen_xml_to_chunk.erl b/lib/erl_docgen/src/docgen_xml_to_chunk.erl
new file mode 100644
index 0000000000..6c9ae59996
--- /dev/null
+++ b/lib/erl_docgen/src/docgen_xml_to_chunk.erl
@@ -0,0 +1,852 @@
+%% -*- erlang -*-
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%----------------------------------------------------------------------
+%% File : docgen_xml_to_chunk
+%%
+%% Created : 1 Nov 2018 by Kenneth Lundin <uabkeld@elxa31hr002>
+%%
+%% Does translation of Erlang XML docs to EEP-48 doc chunks.
+%%----------------------------------------------------------------------
+-module(docgen_xml_to_chunk).
+-export([main/1, func_to_tuple/1]).
+
+-include_lib("kernel/include/eep48.hrl").
+
+main([_Application, FromBeam, _Escript, ToChunk]) ->
+ %% The given module is not documented, generate a hidden beam chunk file
+ Name = filename:basename(filename:rootname(FromBeam)) ++ ".erl",
+
+ EmptyDocs = #docs_v1{ anno = erl_anno:set_file(Name, erl_anno:new(0)),
+ module_doc = hidden, docs = []},
+ ok = file:write_file(ToChunk, term_to_binary(EmptyDocs,[compressed])),
+ ok;
+main([Application, FromXML, FromBeam, _Escript, ToChunk]) ->
+ _ = erlang:process_flag(max_heap_size,20 * 1000 * 1000),
+ case docs(Application, FromXML, FromBeam) of
+ {error, Reason} ->
+ io:format("Failed to create chunks: ~p~n",[Reason]),
+ erlang:halt(1);
+ {docs_v1,_,_,_,_,#{ source := S },[]} when
+ %% This is a list of all modules that do are known not have any functions
+ S =/= "../xml/gen_fsm.xml",
+ S =/= "../xml/shell_default.xml",
+ S =/= "../xml/user.xml",
+ S =/= "../xml/wxClipboardTextEvent.xml",
+ S =/= "../xml/wxDisplayChangedEvent.xml",
+ S =/= "../xml/wxGBSizerItem.xml",
+ S =/= "../xml/wxGraphicsBrush.xml",
+ S =/= "../xml/wxGraphicsFont.xml",
+ S =/= "../xml/wxGraphicsPen.xml",
+ S =/= "../xml/wxInitDialogEvent.xml",
+ S =/= "../xml/wxMaximizeEvent.xml",
+ S =/= "../xml/wxMouseCaptureLostEvent.xml",
+ S =/= "../xml/wxPaintEvent.xml",
+ S =/= "../xml/wxPreviewCanvas.xml",
+ S =/= "../xml/wxSysColourChangedEvent.xml",
+ S =/= "../xml/wxTaskBarIconEvent.xml",
+ S =/= "../xml/wxWindowCreateEvent.xml",
+ S =/= "../xml/wxWindowDestroyEvent.xml",
+ S =/= "../xml/wxDataObject.xml"
+ ->
+ io:format("Failed to create chunks: no functions found ~s~n",[S]),
+ erlang:halt(1),
+ ok;
+ Docs ->
+ ok = file:write_file(ToChunk, term_to_binary(Docs,[compressed]))
+ end.
+
+%% Error handling
+%%----------------------------------------------------------------------
+
+-define(error(Reason),
+ throw({dom_error, Reason})).
+
+%%----------------------------------------------------------------------
+
+%%======================================================================
+%% Records
+%%======================================================================
+
+%%----------------------------------------------------------------------
+%% State record for the validator
+%%----------------------------------------------------------------------
+-record(state, {
+ tags=[], %% Tag stack
+ cno=[], %% Current node number
+ namespaces = [], %% NameSpace stack
+ dom=[] %% DOM structure
+ }).
+
+%%======================================================================
+%% External functions
+%%======================================================================
+
+%%----------------------------------------------------------------------
+%% Function: initial_state() -> Result
+%% Parameters:
+%% Result:
+%% Description:
+%%----------------------------------------------------------------------
+initial_state() ->
+ #state{}.
+
+%%----------------------------------------------------------------------
+%% Function: get_dom(State) -> Result
+%% Parameters:
+%% Result:
+%% Description:
+%%----------------------------------------------------------------------
+get_dom(#state{dom=Dom}) ->
+ Dom.
+
+%%----------------------------------------------------------------------
+%% Function: event(Event, LineNo, State) -> Result
+%% Parameters:
+%% Result:
+%% Description:
+%%----------------------------------------------------------------------
+event(Event, _LineNo, State) ->
+ build_dom(Event, State).
+
+
+%%======================================================================
+%% Internal functions
+%%======================================================================
+
+%%----------------------------------------------------------------------
+%% Function : build_dom(Event, State) -> Result
+%% Parameters: Event = term()
+%% State = #xmerl_sax_simple_dom_state{}
+%% Result : #xmerl_sax_simple_dom_state{} |
+%% Description:
+%%----------------------------------------------------------------------
+
+%% Document
+%%----------------------------------------------------------------------
+build_dom(startDocument, State) ->
+ State#state{dom=[startDocument]};
+build_dom(endDocument,
+ #state{dom=[{Tag, Attributes, Content} |D]} = State) ->
+ case D of
+ [startDocument] ->
+ State#state{dom=[{Tag, Attributes,
+ lists:reverse(Content)}]};
+ [Decl, startDocument] ->
+ State#state{dom=[Decl, {Tag, Attributes,
+ lists:reverse(Content)}]};
+ _ ->
+ %% endDocument is also sent by the parser when a fault occur to tell
+ %% the event receiver that no more input will be sent
+ State
+ end;
+
+%% Element
+%%----------------------------------------------------------------------
+build_dom({startElement, _Uri, LocalName, _QName, Attributes},
+ #state{tags=T, dom=D} = State) ->
+
+ A = parse_attributes(LocalName, Attributes),
+ CName = list_to_atom(LocalName),
+
+ State#state{tags=[CName |T],
+ dom=[{CName,
+ lists:reverse(A),
+ []
+ } | D]};
+build_dom({endElement, _Uri, LocalName, _QName},
+ #state{tags=[_ |T],
+ dom=[{CName, CAttributes, CContent},
+ {PName, PAttributes, PContent} = _Parent | D]} = State) ->
+ case list_to_atom(LocalName) of
+ CName ->
+ SectionDepth = length([E || E <- T, E =:= section]),
+ MappedCName =
+ case CName of
+ title ->
+ lists:nth(SectionDepth+1,[h1,h2,h3]);
+ section when SectionDepth > 0 ->
+ 'div';
+ CName -> CName
+ end,
+
+ State#state{tags=T,
+ dom=[{PName, PAttributes,
+ [{MappedCName, CAttributes,
+ lists:reverse(CContent)}
+ |PContent]
+ } | D]};
+ _ ->
+ ?error("Got end of element: " ++ LocalName ++ " but expected: " ++
+ CName)
+ end;
+
+%% Text
+%%----------------------------------------------------------------------
+build_dom({characters, String},
+ #state{dom=[{Name, Attributes, Content}| D]} = State) ->
+ HtmlEnts = [{"&nbsp;",[160]},
+ {"&times;",[215]},
+ {"&plusmn;",[177]},
+ {"&ouml;","ö"},
+ {"&auml;","ä"},
+ {"&aring;","å"}
+ ],
+
+ NoHtmlEnt =
+ lists:foldl(
+ fun({Pat,Sub},Str) ->
+ re:replace(Str,Pat,Sub,[global,unicode])
+ end,String,HtmlEnts),
+
+ case re:run(NoHtmlEnt,"&[a-z]*;",[{capture,first,binary},unicode]) of
+ nomatch -> ok;
+ {match,[<<"&lt;">>]} -> ok;
+ {match,[<<"&gt;">>]} -> ok;
+ Else -> throw({found_illigal_thing,Else,String})
+ end,
+ NewContent =
+ [unicode:characters_to_binary(NoHtmlEnt,utf8)| Content],
+ State#state{dom=[{Name, Attributes, NewContent} | D]};
+
+build_dom({ignorableWhitespace, String},
+ #state{dom=[{Name,_,_} = _E|_]} = State) ->
+ case lists:member(Name,
+ [p,pre,input,code,quote,warning,
+ note,dont,do,c,i,em,strong,
+ seemfa,seeerl,seetype,seeapp,
+ seecom,seecref,seefile,seeguide,
+ tag,item]) of
+ true ->
+% io:format("Keep ign white: ~p ~p~n",[String, _E]),
+ build_dom({characters, String}, State);
+ false ->
+ State
+ end;
+
+build_dom({startEntity, SysId}, State) ->
+ io:format("startEntity:~p~n",[SysId]),
+ State;
+
+%% Default
+%%----------------------------------------------------------------------
+build_dom(_E, State) ->
+ State.
+
+%%----------------------------------------------------------------------
+%% Function : parse_attributes(ElName, Attributes) -> Result
+%% Parameters:
+%% Result :
+%% Description:
+%%----------------------------------------------------------------------
+parse_attributes(ElName, Attributes) ->
+ parse_attributes(ElName, Attributes, 1, []).
+
+parse_attributes(_, [], _, Acc) ->
+ Acc;
+parse_attributes(ElName, [{_Uri, _Prefix, LocalName, AttrValue} |As], N, Acc) ->
+ parse_attributes(ElName, As, N+1, [{list_to_atom(LocalName), AttrValue} |Acc]).
+
+docs(Application, OTPXml, FromBEAM)->
+ case xmerl_sax_parser:file(OTPXml,
+ [skip_external_dtd,
+ {event_fun,fun event/3},
+ {event_state,initial_state()}]) of
+ {ok,Tree,_} ->
+ {ok, {Module, Chunks}} = beam_lib:chunks(FromBEAM,[exports,abstract_code]),
+ Dom = get_dom(Tree),
+ put(application, Application),
+ put(module, filename:basename(filename:rootname(FromBEAM))),
+ NewDom = transform(Dom,[]),
+ Chunk = to_chunk(NewDom, OTPXml, Module, proplists:get_value(abstract_code, Chunks)),
+ verify_chunk(Module,proplists:get_value(exports, Chunks), Chunk),
+ Chunk;
+ Else ->
+ {error,Else}
+ end.
+
+verify_chunk(M, Exports, #docs_v1{ docs = Docs } = Doc) ->
+
+ %% Make sure that each documented function actually is exported
+ Exported = [begin
+ FA = {F,A},
+ {M,F,A,lists:member(FA,Exports)}
+ end || {{function,F,A},_,_,_,_} <- Docs],
+ lists:foreach(fun({_M,_F,_A,true}) ->
+ ok
+ end,Exported),
+
+ try
+ shell_docs:validate(Doc)
+ catch Err ->
+ throw({maps:get(<<"en">>,Doc#docs_v1.module_doc), Err})
+ end.
+
+%% skip <erlref> but transform and keep its content
+transform([{erlref,_Attr,Content}|T],Acc) ->
+ Module = [Mod || Mod = {module,_,_} <- Content],
+ NewContent = Content -- Module,
+ [{module,SinceAttr,[Mname]}] = Module,
+ Since = case proplists:get_value(since,SinceAttr) of
+ undefined -> [];
+ [] -> [];
+ Vsn -> [{since,Vsn}]
+ end,
+ transform([{module,[{name,Mname}|Since],NewContent}|T],Acc);
+
+%% skip <header> and all of its content
+transform([{header,_Attr,_Content}|T],Acc) ->
+ transform(T,Acc);
+transform([{section,Attr,Content}|T],Acc) ->
+ transform(T,[{section,Attr,transform(Content,[])}|Acc]);
+
+%% transform <list><item> to <ul><li> or <ol><li> depending on type attribute
+transform([{list,Attr,Content}|T],Acc) ->
+ transform([transform_list(Attr,Content)|T],Acc);
+
+%% transform <taglist>(tag,item+)+ to <dl>(dt,item+)+
+transform([{taglist,Attr,Content}|T],Acc) ->
+ transform([transform_taglist(Attr,Content)|T],Acc);
+
+%% remove <anno> as it is only used to validate specs vs xml src
+transform([{anno,[],Content}|T],Acc) ->
+ transform([Content|T],Acc);
+
+%% transform <c> to <code>
+transform([{c,[],Content}|T],Acc) ->
+ transform(T, [{code,[],transform(Content,[])}|Acc]);
+
+%% transform <code> to <pre><code>
+transform([{code,Attr,Content}|T],Acc) ->
+ transform(T, [{pre,[],[{code,a2b(Attr),transform(Content,[])}]}|Acc]);
+%% transform <pre> to <pre><code>
+transform([{pre,Attr,Content}|T],Acc) ->
+ transform(T, [{pre,[],[{code,Attr,transform(Content,[])}]}|Acc]);
+
+%% transform <funcs> with <func> as children
+transform([{funcs,_Attr,Content}|T],Acc) ->
+ Fns = {functions,[],transform_funcs(Content, [])},
+ transform(T,[Fns|Acc]);
+%% transform <datatypes> with <datatype> as children
+transform([{datatypes,_Attr,Content}|T],Acc) ->
+ Dts = transform(Content, []),
+ transform(T,[{datatypes,[],Dts}|Acc]);
+transform([{datatype,_Attr,Content}|T],Acc) ->
+ transform(T,transform_datatype(Content, []) ++ Acc);
+%% Ignore <datatype_title>
+transform([{datatype_title,_Attr,_Content}|T],Acc) ->
+ transform(T,Acc);
+%% transform <desc>Content</desc> to Content
+transform([{desc,_Attr,Content}|T],Acc) ->
+ transform(T,[transform(Content,[])|Acc]);
+transform([{strong,Attr,Content}|T],Acc) ->
+ transform([{em,Attr,Content}|T],Acc);
+%% transform <marker id="name"/> to <a id="name"/>....
+transform([{marker,Attrs,Content}|T],Acc) ->
+ transform(T,[{a,a2b(Attrs),transform(Content,[])}|Acc]);
+%% transform <url href="external URL"> Content</url> to <a href....
+transform([{url,Attrs,Content}|T],Acc) ->
+ transform(T,[{a,a2b(Attrs),transform(Content,[])}|Acc]);
+%% transform note/warning/do/don't to <p class="thing">
+transform([{What,[],Content}|T],Acc)
+ when What =:= note; What =:= warning; What =:= do; What =:= dont ->
+ WhatP = {'div',[{class,atom_to_binary(What)}], transform(Content,[])},
+ transform(T,[WhatP|Acc]);
+
+transform([{type,_,[]}|_] = Dom,Acc) ->
+ %% Types are laid out sequentially in the source xml so we need to
+ %% parse them like that here too.
+ case transform_types(Dom,[]) of
+ {[],T} ->
+ transform(T,Acc);
+ {Types,T} ->
+ %% We sort the types here because in the source xml
+ %% the description and the declaration do not have
+ %% to be next to each other. But we want to have that
+ %% for the doc chunks.
+ NameSort = fun({li,A,_},{li,B,_}) ->
+ NameA = proplists:get_value(name,A),
+ NameB = proplists:get_value(name,B),
+ if NameA == NameB ->
+ length(A) =< length(B);
+ true ->
+ NameA < NameB
+ end
+ end,
+ transform(T,[{ul,[{class,<<"types">>}],lists:sort(NameSort,Types)}|Acc])
+ end;
+transform([{type_desc,Attr,_Content}|T],Acc) ->
+ %% We skip any type_desc with the variable attribute
+ true = proplists:is_defined(variable, Attr),
+ transform(T,Acc);
+transform([{type,[],Content}|T],Acc) ->
+ transform(T,[{ul,[{class,<<"types">>}],transform(Content,[])}|Acc]);
+transform([{v,[],Content}|T],Acc) ->
+ transform(T, [{li,[{class,<<"type">>}],transform(Content,[])}|Acc]);
+transform([{d,[],Content}|T],Acc) ->
+ transform(T, [{li,[{class,<<"description">>}],transform(Content,[])}|Acc]);
+
+transform([Elem = {See,_Attr,_Content}|T],Acc)
+ when See =:= seemfa; See =:= seeerl; See =:= seetype; See =:= seeapp;
+ See =:= seecom; See =:= seecref; See =:= seefile; See =:= seeguide ->
+ transform([transform_see(Elem)|T],Acc);
+
+transform([{term,Attr,[]}|T],Acc) ->
+ transform([list_to_binary(proplists:get_value(id,Attr))|T],Acc);
+
+transform([{fsummary,_,_}|T],Acc) ->
+ %% We skip fsummary as it many times is just a duplicate of the
+ %% first line of the docs.
+ transform(T,Acc);
+
+transform([{input,_,Content}|T],Acc) ->
+ %% Just remove input as it is not used by anything
+ transform(T,[transform(Content,[])|Acc]);
+
+transform([{p,Attr,Content}|T],Acc) ->
+ transform(T,[{p,a2b(Attr),transform(Content,[])}|Acc]);
+transform([{'div',Attr,Content}|T],Acc) ->
+ transform(T,[{'div',a2b(Attr),transform(Content,[])}|Acc]);
+
+%% Tag and Attr is used as is but Content is transformed
+transform([{Tag,Attr,Content}|T],Acc) ->
+ transform(T,[{Tag,Attr,transform(Content,[])}|Acc]);
+transform([Binary|T],Acc) ->
+ transform(T,[Binary|Acc]);
+transform([],Acc) ->
+ lists:flatten(lists:reverse(Acc)).
+
+transform_list([{type,"ordered"}],Content) ->
+ {ol,[],[{li,A2,C2}||{item,A2,C2}<-Content]};
+transform_list(_,Content) ->
+ {ul,[],[{li,A2,C2}||{item,A2,C2}<-Content]}.
+
+transform_types([{type,Attr,[]}|T],Acc) ->
+ case proplists:is_defined(name,Attr) of
+ true ->
+ transform_types(T, [{li,a2b(Attr),[]}|Acc]);
+ false ->
+ true = proplists:is_defined(variable, Attr),
+ transform_types(T, Acc)
+ end;
+transform_types([{type_desc,Attr,Content}|T],Acc) ->
+ case proplists:is_defined(name,Attr) of
+ true ->
+ TypeDesc = transform(Content,[]),
+ transform_types(T, [{li,a2b(Attr) ++ [{class,<<"description">>}],TypeDesc}|Acc]);
+ false ->
+ true = proplists:is_defined(variable, Attr),
+ transform_types(T, Acc)
+ end;
+transform_types([{type,_,_}|_T],_Acc) ->
+ throw(mixed_type_declarations);
+transform_types(Dom,Acc) ->
+ {lists:reverse(Acc),Dom}.
+
+transform_taglist(Attr,Content) ->
+ Items =
+ lists:map(fun({tag,A,C}) ->
+ {dt,A,C};
+ ({item,A,C}) ->
+ {dd,A,C}
+ end, Content),
+ {dl,Attr,Items}.
+
+%% if we have {func,[],[{name,...},{name,....},...]}
+%% we convert it to one {func,[],[{name,...}] per arity lowest first.
+transform_funcs([Func|T],Acc) ->
+ transform_funcs(T,func2func(Func) ++ Acc);
+transform_funcs([],Acc) ->
+ lists:reverse(Acc).
+
+func2func({fsdescription,_Attr,_Contents}) ->
+ [];
+func2func({func,Attr,Contents}) ->
+
+ ContentsNoName = [NC||NC <- Contents, element(1,NC) /= name],
+
+ EditLink =
+ case proplists:get_value(ghlink,Attr) of
+ undefined ->
+ #{};
+ GhLink ->
+ #{ edit_url =>
+ iolist_to_binary(["https://github.com/erlang/otp/edit/",GhLink]) }
+ end,
+
+ VerifyNameList =
+ fun(NameList, Test) ->
+ %% Assert that we don't mix ways to write <name>
+ [begin
+ ok = Test(C),
+ {proplists:get_value(name,T),proplists:get_value(arity,T)}
+ end || {name,T,C} <- NameList]
+ end,
+
+ NameList = [Name || {name,_,_} = Name <- Contents],
+
+ %% "Since" is hard to accurately as there can be multiple <name> per <func> and they
+ %% can refer to the same or other arities. This should be improved in the future but
+ %% for now we set since to a comma separated list of all since attributes.
+ SinceMD =
+ case [proplists:get_value(since, SinceAttr) ||
+ {name,SinceAttr,_} <- NameList, proplists:get_value(since, SinceAttr) =/= []] of
+ [] -> EditLink;
+ Sinces ->
+ EditLink#{ since => unicode:characters_to_binary(
+ lists:join(",",lists:usort(Sinces))) }
+ end,
+
+ Functions =
+ case NameList of
+ [{name,_,[]}|_] ->
+ %% Spec style function docs
+ TagsToFA =
+ fun(Tags) ->
+ {proplists:get_value(name,Tags),
+ proplists:get_value(arity,Tags)}
+ end,
+
+ _ = VerifyNameList(NameList,fun([]) -> ok end),
+
+ FAs = [TagsToFA(FAttr) || {name,FAttr,[]} <- NameList ],
+ SortedFAs = lists:usort(FAs),
+ FAClauses = lists:usort([{TagsToFA(FAttr),proplists:get_value(clause_i,FAttr)}
+ || {name,FAttr,[]} <- NameList ]),
+
+ MakeFunc = fun({F,A}, MD, Doc) ->
+ Specs = [begin
+ {function,Name} = func_to_atom(CF),
+ {Name,list_to_integer(CA),C}
+ end || {{CF,CA},C} <- FAClauses,
+ F =:= CF, A =:= CA],
+ {function,[{name,F},{arity,list_to_integer(A)},
+ {signature,[iolist_to_binary([F,"/",A])]},
+ {meta,MD#{ signature => Specs }}],
+ Doc}
+ end,
+
+ Base = MakeFunc(hd(SortedFAs), SinceMD, ContentsNoName),
+
+ {BaseF,BaseA} = hd(SortedFAs),
+ MD = SinceMD#{ equiv => {function,list_to_atom(BaseF),list_to_integer(BaseA)}},
+ Equiv = lists:map(
+ fun(FA) ->
+ MakeFunc(FA, MD, [])
+ end, tl(SortedFAs)),
+ [Base | Equiv];
+ NameList ->
+ %% Manual style function docs
+ FAs = lists:foldl(
+ fun({name,_,NameString}, Acc) ->
+ FAs = func_to_tuple(NameString),
+ lists:foldl(
+ fun(FA, FAAcc) ->
+ Slogan = maps:get(FA, FAAcc, []),
+ FAAcc#{ FA => [strip_tags(NameString)|Slogan] }
+ end, Acc, FAs)
+ end, #{}, NameList),
+
+ _ = VerifyNameList(NameList,fun([_|_]) -> ok end),
+
+ SortedFAs = lists:usort(maps:to_list(FAs)),
+
+ {{BaseF, BaseA}, BaseSig} = hd(SortedFAs),
+
+ Base = {function,[{name,BaseF},{arity,BaseA},
+ {signature,BaseSig},
+ {meta,SinceMD}],
+ ContentsNoName},
+
+ Equiv = [{function,
+ [{name,F},{arity,A},
+ {signature,Signature},
+ {meta,SinceMD#{ equiv => {function,list_to_atom(BaseF),BaseA}}}],[]}
+ || {{F,A},Signature} <- tl(SortedFAs)],
+ [Base | Equiv]
+ end,
+ transform(Functions,[]).
+
+func_to_tuple(Chars) ->
+ try
+ [Name,Args] = string:split(strip_tags(Chars),"("),
+ Arities = parse_args(unicode:characters_to_list(Args)),
+ [{unicode:characters_to_list(Name),Arity} || Arity <- Arities]
+ catch E:R:ST ->
+ io:format("Failed to parse: ~p~n",[Chars]),
+ erlang:raise(E,R,ST)
+ end.
+
+%% This function parses a documentation <name> attribute to figure
+%% out the arities if that function. Example:
+%% "start([go,Mode] [,Extra])" returns [1, 2].
+%%
+%% This assumes that when a single <name> describes many arities
+%% the arities are listed with [, syntax.
+parse_args(")" ++ _) ->
+ [0];
+parse_args(Args) ->
+ parse_args(unicode:characters_to_list(Args),1,[]).
+parse_args([$[,$,|T],Arity,[]) ->
+ parse_args(T,Arity,[$[]) ++ parse_args(T,Arity+1,[]);
+parse_args([$,|T],Arity,[]) ->
+ parse_args(T,Arity+1,[]);
+parse_args([Open|T],Arity,Stack)
+ when Open =:= $[; Open =:= ${; Open =:= $( ->
+ parse_args(T,Arity,[Open|Stack]);
+parse_args([$]|T],Arity,[$[|Stack]) ->
+ parse_args(T,Arity,Stack);
+parse_args([$}|T],Arity,[${|Stack]) ->
+ parse_args(T,Arity,Stack);
+parse_args([$)|T],Arity,[$(|Stack]) ->
+ parse_args(T,Arity,Stack);
+parse_args([$)|_T],Arity,[]) ->
+ [Arity];
+parse_args([_H|T],Arity,Stack) ->
+ parse_args(T,Arity,Stack).
+
+strip_tags([{_Tag,_Attr,Content}|T]) ->
+ [Content | strip_tags(T)];
+strip_tags([H|T]) when not is_tuple(H) ->
+ [H | strip_tags(T)];
+strip_tags([]) ->
+ [].
+
+transform_datatype(Dom,_Acc) ->
+ ContentsNoName = transform([NC||NC <- Dom, element(1,NC) /= name],[]),
+ [case N of
+ {name,NameAttr,[]} ->
+ {datatype,NameAttr,ContentsNoName};
+ {name,[],Content} ->
+ [{Name,Arity}] = func_to_tuple(Content),
+ Signature = strip_tags(Content),
+ {datatype,[{name,Name},{n_vars,integer_to_list(Arity)},
+ {signature,Signature}],ContentsNoName}
+ end || N = {name,_,_} <- Dom].
+
+transform_see({See,[{marker,Marker}],Content}) ->
+ AbsMarker =
+ case string:lexemes(Marker,"#") of
+ [Link] -> [get(application),":",get(module),"#",Link];
+ [AppMod, Link] ->
+ case string:lexemes(AppMod,":") of
+ [Mod] -> [get(application),":",Mod,"#",Link];
+ [App, Mod] -> [App,":",Mod,"#",Link]
+ end
+ end,
+ {a, [{href,iolist_to_binary(AbsMarker)},
+ {rel,<<"https://erlang.org/doc/link/",(atom_to_binary(See))/binary>>}], Content}.
+
+to_chunk(Dom, Source, Module, AST) ->
+ [{module,MAttr,Mcontent}] = Dom,
+
+ ModuleDocs = lists:flatmap(
+ fun({Tag,_,Content}) when Tag =:= description;
+ Tag =:= section ->
+ Content;
+ ({_,_,_}) ->
+ []
+ end, Mcontent),
+
+ TypeMeta = add_types(AST, maps:from_list([{source,Source}|MAttr])),
+
+ TypeMap = maps:get(types, TypeMeta, []),
+
+ Anno = erl_anno:set_file(atom_to_list(Module)++".erl",erl_anno:new(0)),
+
+ Types = lists:flatten([Types || {datatypes,[],Types} <- Mcontent]),
+
+ TypeEntries =
+ lists:map(
+ fun({datatype,Attr,Descr}) ->
+ {function, TypeName} = func_to_atom(proplists:get_value(name,Attr)),
+ TypeArity = case proplists:get_value(n_vars,Attr) of
+ undefined ->
+ find_type_arity(TypeName, TypeMap);
+ Arity ->
+ list_to_integer(Arity)
+ end,
+ TypeArgs = lists:join(",",[lists:concat(["Arg",I]) || I <- lists:seq(1,TypeArity)]),
+ PlaceholderSig = io_lib:format("-type ~p(~s) :: term().",[TypeName,TypeArgs]),
+ TypeSignature = proplists:get_value(
+ signature,Attr,[iolist_to_binary(PlaceholderSig)]),
+ MetaSig =
+ case maps:get({TypeName, TypeArity}, TypeMap, undefined) of
+ undefined ->
+ #{};
+ Sig ->
+ #{ signature => [Sig] }
+ end,
+
+ MetaDepr
+ = case otp_internal:obsolete_type(Module, TypeName, TypeArity) of
+ {deprecated, Text} ->
+ MetaSig#{ deprecated =>
+ unicode:characters_to_binary(
+ erl_lint:format_error({deprecated_type,{Module,TypeName,TypeArity}, Text})) };
+ %% Commented out to make dialyzer happy
+ %% {deprecated, Replacement, Rel} ->
+ %% MetaSig#{ deprecated =>
+ %% unicode:characters_to_binary(
+ %% erl_lint:format_error({deprecated_type,{Module,TypeName,TypeArity}, Replacement, Rel})) };
+ no ->
+ MetaSig
+ end,
+
+ docs_v1_entry(type, Anno, TypeName, TypeArity, TypeSignature, MetaDepr, Descr)
+ end, Types),
+
+ Functions = lists:flatten([Functions || {functions,[],Functions} <- Mcontent]),
+
+ FuncEntrys =
+ lists:map(
+ fun({function,Attr,Fdoc}) ->
+ {Type, Name} = func_to_atom(proplists:get_value(name,Attr)),
+ Arity = proplists:get_value(arity,Attr),
+ Signature = proplists:get_value(signature,Attr),
+ FMeta = proplists:get_value(meta,Attr),
+ MetaWSpec = add_spec(AST,FMeta),
+ MetaDepr
+ = case otp_internal:obsolete(Module, Name, Arity) of
+ {deprecated, Text} ->
+ MetaWSpec#{ deprecated =>
+ unicode:characters_to_binary(
+ erl_lint:format_error({deprecated,{Module,Name,Arity}, Text})) };
+ {deprecated, Replacement, Rel} ->
+ MetaWSpec#{ deprecated =>
+ unicode:characters_to_binary(
+ erl_lint:format_error({deprecated,{Module,Name,Arity}, Replacement, Rel})) };
+ _ -> MetaWSpec
+ end,
+ docs_v1_entry(Type, Anno, Name, Arity, Signature, MetaDepr, Fdoc)
+ end, Functions),
+
+ docs_v1(ModuleDocs, Anno, TypeMeta, FuncEntrys ++ TypeEntries).
+
+docs_v1(DocContents, Anno, Metadata, Docs) ->
+ #docs_v1{ anno = Anno,
+ module_doc = #{<<"en">> => shell_docs:normalize(DocContents)},
+ metadata = maps:merge(Metadata, (#docs_v1{})#docs_v1.metadata),
+ docs = Docs }.
+
+docs_v1_entry(Kind, Anno, Name, Arity, Signature, Metadata, DocContents) ->
+
+ AnnoWLine =
+ case Metadata of
+ #{ signature := [Sig|_] } ->
+ SigAnno = element(2, Sig),
+ erl_anno:set_line(erl_anno:line(SigAnno), Anno);
+ _NoSignature ->
+ Anno
+ end,
+
+ Doc =
+ case DocContents of
+ [] ->
+ #{};
+ DocContents ->
+ #{ <<"en">> => shell_docs:normalize(DocContents) }
+ end,
+
+ {{Kind, Name, Arity}, AnnoWLine, lists:flatten(Signature), Doc, Metadata}.
+
+%% A special list_to_atom that handles
+%% 'and'
+%% Destroy
+%% 'begin'
+func_to_atom(List) ->
+ case erl_scan:string(List) of
+ {ok,[{atom,_,Fn}],_} ->
+ {function, Fn};
+ {ok,[{var,_,Fn}],_} ->
+ {function, Fn};
+ {ok,[{Fn,_}],_} ->
+ {function, Fn};
+ {ok,[{var,_,_},{':',_},{atom,_,Fn}],_} ->
+ {callback, Fn};
+ {ok,[{var,_,_},{':',_},{var,_,Fn}],_} ->
+ {callback, Fn}
+ end.
+
+-define(IS_TYPE(TO),(TO =:= type orelse TO =:= opaque)).
+
+add_spec(no_abstract_code, Meta) ->
+ Meta;
+add_spec({raw_abstract_v1, AST}, Meta = #{ signature := Specs } ) ->
+ Meta#{ signature := add_spec_clauses(AST, merge_clauses(Specs,#{})) };
+add_spec(_, Meta) ->
+ Meta.
+
+add_types(no_abstract_code, Meta) ->
+ Meta;
+add_types({raw_abstract_v1, AST}, Meta) ->
+ Meta#{ types =>
+ maps:from_list(
+ [{{Name,length(Args)},T} || T = {attribute,_,TO,{Name, _, Args}} <- AST,
+ ?IS_TYPE(TO)]) }.
+
+add_spec_clauses(AST, [{{F,A},Clauses}|T]) ->
+ [filter_clauses(find_spec(AST,F,A),Clauses) | add_spec_clauses(AST,T)];
+add_spec_clauses(_AST, []) ->
+ [].
+
+filter_clauses(Spec,[undefined]) ->
+ Spec;
+filter_clauses({attribute,Ln,spec,{FA,Clauses}},ClauseIds) ->
+ {_,FilteredClauses} =
+ lists:foldl(
+ fun({TO,_,_,_} = C,{Cnt,Acc}) when ?IS_TYPE(TO) ->
+ case lists:member(integer_to_list(Cnt),ClauseIds) of
+ true ->
+ {Cnt+1,[C | Acc]};
+ false ->
+ {Cnt+1,Acc}
+ end
+ end, {1, []}, Clauses),
+ {attribute,Ln,spec,{FA,lists:reverse(FilteredClauses)}}.
+
+merge_clauses([{F,A,Clause}|T],Acc) ->
+ merge_clauses(T,Acc#{ {F,A} => [Clause | maps:get({F,A},Acc,[])]});
+merge_clauses([],Acc) ->
+ maps:to_list(Acc).
+
+find_type_arity(Name, [{{Name,_},{attribute,_,TO,{Name,_,Args}}}|_T]) when ?IS_TYPE(TO) ->
+ length(Args);
+find_type_arity(Name, [_|T]) ->
+ find_type_arity(Name,T);
+find_type_arity(Name, Map) when is_map(Map) ->
+ find_type_arity(Name, maps:to_list(Map)).
+
+find_spec(AST, Func, Arity) ->
+ Specs = lists:filter(fun({attribute,_,spec,{{F,A},_}}) ->
+ F =:= Func andalso A =:= Arity;
+ ({attribute,_,spec,{{_,F,A},_}}) ->
+ F =:= Func andalso A =:= Arity;
+ (_) ->
+ false
+ end, AST),
+ case Specs of
+ [S] ->
+ S;
+ [] ->
+ io:format("Could not find spec for ~p/~p~n",[Func,Arity]),
+ exit(1)
+ end.
+
+a2b(Attrs) ->
+ [{Tag,unicode:characters_to_binary(Value)} || {Tag,Value} <- Attrs].
diff --git a/lib/erl_docgen/src/erl_docgen.app.src b/lib/erl_docgen/src/erl_docgen.app.src
index 171c697585..c2641f30df 100644
--- a/lib/erl_docgen/src/erl_docgen.app.src
+++ b/lib/erl_docgen/src/erl_docgen.app.src
@@ -3,7 +3,8 @@
{vsn, "%VSN%"},
{modules, [docgen_otp_specs,
docgen_edoc_xml_cb,
- docgen_xmerl_xml_cb
+ docgen_xmerl_xml_cb,
+ docgen_xml_to_chunk
]
},
{registered,[]},
diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk
index ebc9516da3..4b05826b61 100644
--- a/lib/erl_docgen/vsn.mk
+++ b/lib/erl_docgen/vsn.mk
@@ -1 +1 @@
-ERL_DOCGEN_VSN = 0.11
+ERL_DOCGEN_VSN = 1.0.2
diff --git a/lib/erl_interface/Makefile b/lib/erl_interface/Makefile
index e7d43623ce..47ff4e625b 100644
--- a/lib/erl_interface/Makefile
+++ b/lib/erl_interface/Makefile
@@ -31,3 +31,5 @@ SPECIAL_TARGETS =
# Default Subdir Targets
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/erl_interface/configure.in b/lib/erl_interface/configure.in
index 79012919ea..fa07048424 100644
--- a/lib/erl_interface/configure.in
+++ b/lib/erl_interface/configure.in
@@ -274,8 +274,8 @@ case "$threads_disabled" in
fi
;;
win32_threads)
- EI_THREADS="true"
- THR_DEFS="$THR_DEFS -D_WIN32_WINNT=0x0600 -DWINVER=0x0600"
+ EI_THREADS="true"
+ THR_DEFS="$THR_DEFS -D_REENTRANT -D_WIN32_WINNT=0x0600 -DWINVER=0x0600"
;;
pthread)
EI_THREADS="true"
@@ -336,6 +336,10 @@ if test "X$host" = "Xwin32"; then
LIB_CFLAGS="$CFLAGS"
else
if test "x$GCC" = xyes; then
+ # Remove all PIE stuff
+ CFLAGS=`echo $CFLAGS | sed 's/-f\(no-\)\?PIE//g'`
+ LDFLAGS=`echo $LDFLAGS | sed 's/-\(no-\)\?pie//g'`
+
LIB_CFLAGS="$CFLAGS -fPIC"
else
LIB_CFLAGS="$CFLAGS"
diff --git a/lib/erl_interface/doc/src/Makefile b/lib/erl_interface/doc/src/Makefile
index 5651a10b3e..73344b1a65 100644
--- a/lib/erl_interface/doc/src/Makefile
+++ b/lib/erl_interface/doc/src/Makefile
@@ -36,93 +36,24 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
# Target Specs
# ----------------------------------------------------
-XML_REF1_FILES = erl_call.xml
-XML_REF3_FILES = erl_connect.xml \
- erl_error.xml \
- erl_eterm.xml \
- erl_format.xml \
- erl_malloc.xml \
- erl_marshal.xml \
- erl_global.xml \
+XML_REF1_FILES = erl_call_cmd.xml
+XML_REF3_FILES = ei_global.xml \
ei.xml \
ei_connect.xml \
registry.xml
BOOK_FILES = book.xml
XML_APPLICATION_FILES = ref_man.xml
-#ref_man_ei.xml ref_man_erl_interface.xml
+
XML_PART_FILES = \
part.xml
XML_CHAPTER_FILES = ei_users_guide.xml notes.xml
XML_FILES = $(XML_REF1_FILES) $(XML_REF3_FILES) $(BOOK_FILES) \
$(XML_APPLICATION_FILES) $(XML_PART_FILES) $(XML_CHAPTER_FILES)
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-GIF_FILES =
-
-MAN1_FILES = $(XML_REF1_FILES:%.xml=$(MAN1DIR)/%.1)
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+NO_CHUNKS=$(XML_REF3_FILES)
# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN1_FILES) $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt lcnt:
-
-clean clean_docs clean_tex:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN1DIR)/*
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DATA) $(MAN1_FILES) "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3_FILES) "$(RELEASE_PATH)/man/man3"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml
index 3b100e2819..9161279ef7 100644
--- a/lib/erl_interface/doc/src/ei.xml
+++ b/lib/erl_interface/doc/src/ei.xml
@@ -35,9 +35,6 @@
<lib>ei</lib>
<libsummary>Routines for handling the Erlang binary term format.</libsummary>
<description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
-
<p>The library <c>ei</c> contains macros and functions to encode
and decode the Erlang binary term format.</p>
@@ -49,14 +46,9 @@
<p><c>ei</c> also handles C-nodes, C-programs that talks Erlang
distribution with Erlang nodes (or other C-nodes) using the
- Erlang distribution format. The difference between <c>ei</c>
- and <c>erl_interface</c> is that <c>ei</c> uses
- the binary format directly when sending and receiving terms. It is also
+ Erlang distribution format.The <c>ei</c> library is
thread safe, and using threads, one process can handle multiple
- C-nodes. The <c>erl_interface</c> library is built on top of
- <c>ei</c>, but of legacy reasons, it does not allow for
- multiple C-nodes. In general, <c>ei</c> is the preferred way
- of doing C-nodes.</p>
+ C-nodes.</p>
<p>The decode and encode functions use a buffer and an index into the
buffer, which points at the point where to encode and
@@ -90,7 +82,9 @@
<p>There are also encode functions that use a dynamic buffer. It
is often more convenient to use these to encode data. All encode
functions comes in two versions; those starting with
- <c>ei_x</c> use a dynamic buffer.</p>
+ <c>ei_x_</c> use a dynamic buffer of type
+ <seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref>.
+ </p>
<p>All functions return <c>0</c> if successful, otherwise
<c>-1</c> (for example, if a term is not of the expected
@@ -98,7 +92,7 @@
<p>Some of the decode functions need a pre-allocated buffer. This
buffer must be allocated large enough, and for non-compound types
- the <c>ei_get_type()</c>
+ the <seecref marker="#ei_get_type"><c>ei_get_type()</c></seecref>
function returns the size required (notice that for strings an
extra byte is needed for the <c>NULL</c>-terminator).</p>
</description>
@@ -106,7 +100,55 @@
<section>
<title>Data Types</title>
<taglist>
- <tag><marker id="erlang_char_encoding"/>erlang_char_encoding</tag>
+ <tag><marker id="ei_term"/><c>ei_term</c></tag>
+ <item>
+ <code type="none">
+typedef struct {
+ char ei_type;
+ int arity;
+ int size;
+ union {
+ long i_val;
+ double d_val;
+ char atom_name[MAXATOMLEN_UTF8];
+ erlang_pid pid;
+ erlang_port port;
+ erlang_ref ref;
+ } value;
+} ei_term;</code>
+ <p>Structure written by
+ <seecref marker="#ei_decode_ei_term"><c>ei_decode_ei_term()</c></seecref>.
+ The <c>ei_type</c> field is the type of the term which equals to
+ what <seecref marker="#ei_get_type"><c>ei_get_type()</c></seecref>
+ sets <c>*type</c> to.
+ </p>
+ </item>
+ <tag><marker id="ei_x_buff"/><c>ei_x_buff</c></tag>
+ <item>
+ <p>A dynamically resized buffer. It is a <c>struct</c> with
+ two fields of interest for the user:
+ </p>
+ <taglist>
+ <tag><c>char *buff</c></tag>
+ <item>
+ <p>Pointer to the dynamically allocated buffer.</p>
+ </item>
+ <tag><c>int index</c></tag>
+ <item>
+ <p>Offset to the next byte to write which also equals the
+ amount of bytes currently written.</p>
+ </item>
+ </taglist>
+ <p>
+ An <c>ei_x_buff</c> is initialized by calling either
+ <seecref marker="#ei_x_new"><c>ei_x_new()</c></seecref> or
+ <seecref marker="#ei_x_new_with_version"><c>ei_x_new_with_version()</c></seecref>.
+ The memory used by an initialized <c>ei_x_buff</c> is released
+ by calling
+ <seecref marker="#ei_x_free"><c>ei_x_free()</c></seecref>.
+ </p>
+ </item>
+ <tag><marker id="erlang_char_encoding"/><c>erlang_char_encoding</c></tag>
<item>
<code type="none">
typedef enum {
@@ -122,11 +164,91 @@ typedef enum {
Notice that these constants are bit-flags and can be combined with
bitwise OR.</p>
</item>
+ <tag><marker id="erlang_fun"/><c>erlang_fun</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang fun.</p>
+ </item>
+ <tag><marker id="erlang_pid"/><c>erlang_pid</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang process identifier.</p>
+ </item>
+ <tag><marker id="erlang_port"/><c>erlang_port</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang port identifier.</p>
+ </item>
+ <tag><marker id="erlang_ref"/><c>erlang_ref</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang reference.</p>
+ </item>
+ <tag><marker id="erlang_trace"/><c>erlang_trace</c></tag>
+ <item>
+ <p>Opaque data type representing an Erlang sequential trace token.</p>
+ </item>
</taglist>
</section>
<funcs>
<func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_cmp_pids(erlang_pid *a, erlang_pid *b)</nametext></name>
+ <fsummary>Compare two pids.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Compare two process identifiers. The comparison is done the same way
+ as Erlang does.
+ </p>
+ <p>
+ Returns <c>0</c> if <c>a</c> and <c>b</c> are equal. Returns a value
+ less than <c>0</c> if <c>a</c> compares as less than <c>b</c>.
+ Returns a value larger than <c>0</c> if <c>a</c> compares as larger
+ than <c>b</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_cmp_ports(erlang_port *a, erlang_port *b)</nametext></name>
+ <fsummary>Compare two ports.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_port"><c>erlang_port</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Compare two port identifiers. The comparison is done the same way as
+ Erlang does.
+ </p>
+ <p>
+ Returns <c>0</c> if <c>a</c> and <c>b</c> are equal. Returns a value
+ less than <c>0</c> if <c>a</c> compares as less than <c>b</c>.
+ Returns a value larger than <c>0</c> if <c>a</c> compares as larger
+ than <c>b</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_cmp_refs(erlang_ref *a, erlang_ref *b)</nametext></name>
+ <fsummary>Compare two references.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_ref"><c>erlang_ref</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Compare two references. The comparison is done the same way as Erlang
+ does.
+ </p>
+ <p>
+ Returns <c>0</c> if <c>a</c> and <c>b</c> are equal. Returns a value
+ less than <c>0</c> if <c>a</c> compares as less than <c>b</c>.
+ Returns a value larger than <c>0</c> if <c>a</c> compares as larger
+ than <c>b</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
<name since=""><ret>int</ret><nametext>ei_decode_atom(const char *buf, int *index, char *p)</nametext></name>
<fsummary>Decode an atom.</fsummary>
<desc>
@@ -139,12 +261,15 @@ typedef enum {
<func>
<name since="OTP R16B"><ret>int</ret><nametext>ei_decode_atom_as(const char *buf, int *index, char *p, int plen, erlang_char_encoding want, erlang_char_encoding* was, erlang_char_encoding* result)</nametext></name>
<fsummary>Decode an atom.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_char_encoding"><c>erlang_char_encoding</c></seecref></v>
+ </type>
<desc>
<p>Decodes an atom from the binary format. The <c>NULL</c>-terminated
name of the atom is placed in buffer at <c>p</c> of length <c>plen</c>
bytes.</p>
<p>The wanted string encoding is specified by
- <seealso marker="#erlang_char_encoding"><c>want</c></seealso>.
+ <seecref marker="#erlang_char_encoding"><c>want</c></seecref>.
The original encoding used in the binary format (Latin-1 or UTF-8) can
be obtained from <c>*was</c>. The encoding of the resulting string
(7-bit ASCII, Latin-1, or UTF-8) can be obtained from <c>*result</c>.
@@ -178,7 +303,8 @@ typedef enum {
<c>len</c> is set to the actual size of the
binary. Notice that <c>ei_decode_binary()</c> assumes that
there is enough room for the binary. The size required can be
- fetched by <c>ei_get_type()</c>.</p>
+ fetched by
+ <seecref marker="#ei_get_type"><c>ei_get_type()</c></seecref>.</p>
</desc>
</func>
@@ -254,6 +380,9 @@ typedef enum {
<func>
<name since=""><ret>int</ret><nametext>ei_decode_ei_term(const char* buf, int* index, ei_term* term)</nametext></name>
<fsummary>Decode a term, without previous knowledge of type.</fsummary>
+ <type>
+ <v><seecref marker="#ei_term"><c>ei_term</c></seecref></v>
+ </type>
<desc>
<p>Decodes any term, or at least tries to. If the term
pointed at by <c>*index</c> in <c>buf</c> fits
@@ -276,6 +405,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_decode_fun(const char *buf, int *index, erlang_fun *p)</nametext></name>
<name since=""><ret>void</ret><nametext>free_fun(erlang_fun* f)</nametext></name>
<fsummary>Decode a fun.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_fun"><c>erlang_fun</c></seecref></v>
+ </type>
<desc>
<p>Decodes a fun from the binary format. Parameter
<c>p</c> is to be <c>NULL</c> or point to an
@@ -288,6 +420,37 @@ typedef enum {
</func>
<func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_decode_iodata(const char *buf, int *index, int *size, char *outbuf)</nametext></name>
+ <fsummary>Decode iodata().</fsummary>
+ <desc>
+ <p>Decodes a term of the type <seeguide marker="system/reference_manual:typespec#builtin_types"><c>iodata()</c></seeguide>. The <c>iodata()</c> term will be
+ flattened an written into the buffer pointed to by the <c>outbuf</c>
+ argument. The byte size of the <c>iodata</c> is written into the
+ integer variable pointed to by the <c>size</c> argument. Both <c>size</c>
+ and <c>outbuf</c> can be set to <c>NULL</c>. The integer pointed to
+ by the <c>index</c> argument is updated to refer to the term
+ following after the <c>iodata()</c> term regardless of the the state
+ of the <c>size</c> and the <c>outbuf</c> arguments.
+ </p>
+ <p>Note that the buffer pointed to by the <c>outbuf</c> argument
+ must be large enough if a non <c>NULL</c> value is passed as
+ <c>outbuf</c>. You typically want to call <c>ei_decode_iodata()</c>
+ twice. First with a non <c>NULL</c> <c>size</c> argument and
+ a <c>NULL</c> <c>outbuf</c> argument in order to determine the
+ size of the buffer needed, and then once again in order to do
+ the actual decoding. Note that the integer pointed to by <c>index</c>
+ will be updated by the call determining the size as well, so you
+ need to reset it before the second call doing the actual decoding.
+ </p>
+ <p>Returns <c>0</c> on success and <c>-1</c> on failure. Failure
+ might be either due to invalid encoding of the term or due to
+ the term not being of the type <c>iodata()</c>. On failure, the
+ integer pointed to by the <c>index</c> argument will be updated
+ to refer to the sub term where the failure was detected.</p>
+ </desc>
+ </func>
+
+ <func>
<name since=""><ret>int</ret><nametext>ei_decode_list_header(const char *buf, int *index, int *arity)</nametext></name>
<fsummary>Decode a list.</fsummary>
<desc>
@@ -320,8 +483,7 @@ typedef enum {
<desc>
<p>Decodes a GCC <c>long long</c> or Visual C++
<c>__int64</c>
- (64-bit) integer from the binary format. This
- function is missing in the VxWorks port.</p>
+ (64-bit) integer from the binary format.</p>
</desc>
</func>
@@ -341,6 +503,9 @@ typedef enum {
<func>
<name since=""><ret>int</ret><nametext>ei_decode_pid(const char *buf, int *index, erlang_pid *p)</nametext></name>
<fsummary>Decode a <c>pid</c>.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Decodes a process identifier (pid) from the binary format.</p>
</desc>
@@ -349,6 +514,9 @@ typedef enum {
<func>
<name since=""><ret>int</ret><nametext>ei_decode_port(const char *buf, int *index, erlang_port *p)</nametext></name>
<fsummary>Decode a port.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_port"><c>erlang_port</c></seecref></v>
+ </type>
<desc>
<p>Decodes a port identifier from the binary format.</p>
</desc>
@@ -357,6 +525,9 @@ typedef enum {
<func>
<name since=""><ret>int</ret><nametext>ei_decode_ref(const char *buf, int *index, erlang_ref *p)</nametext></name>
<fsummary>Decode a reference.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_ref"><c>erlang_ref</c></seecref></v>
+ </type>
<desc>
<p>Decodes a reference from the binary format.</p>
</desc>
@@ -378,24 +549,11 @@ typedef enum {
</func>
<func>
- <name since=""><ret>int</ret><nametext>ei_decode_term(const char *buf, int *index, void *t)</nametext></name>
- <fsummary>Decode a <c>ETERM</c>.</fsummary>
- <desc>
- <p>Decodes a term from the binary format. The term
- is return in <c>t</c> as a <c>ETERM*</c>, so
- <c>t</c> is actually an <c>ETERM**</c> (see
- <seealso marker="erl_eterm"><c>erl_eterm</c></seealso>).
- The term is later to be deallocated.</p>
- <note><p>This function is deprecated as of OTP 22 and will be removed in
- OTP 23 together with the old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>).</p>
- </note>
- </desc>
- </func>
-
- <func>
<name since=""><ret>int</ret><nametext>ei_decode_trace(const char *buf, int *index, erlang_trace *p)</nametext></name>
<fsummary>Decode a trace token.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_trace"><c>erlang_trace</c></seecref></v>
+ </type>
<desc>
<p>Decodes an Erlang trace token from the binary format.</p>
</desc>
@@ -427,7 +585,7 @@ typedef enum {
<desc>
<p>Decodes a GCC <c>unsigned long long</c> or Visual C++
<c>unsigned __int64</c> (64-bit) integer from the binary
- format. This function is missing in the VxWorks port.</p>
+ format.</p>
</desc>
</func>
@@ -447,6 +605,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_x_encode_atom(ei_x_buff* x, const char *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_atom_len(ei_x_buff* x, const char *p, int len)</nametext></name>
<fsummary>Encode an atom.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes an atom in the binary format. Parameter <c>p</c>
is the name of the atom in Latin-1 encoding. Only up to
@@ -462,10 +623,14 @@ typedef enum {
<name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_as(ei_x_buff* x, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
<name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_len_as(ei_x_buff* x, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
<fsummary>Encode an atom.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_char_encoding"><c>erlang_char_encoding</c></seecref></v>
+ </type>
<desc>
<p>Encodes an atom in the binary format. Parameter <c>p</c> is the name of the atom with
character encoding
- <seealso marker="#erlang_char_encoding"><c>from_enc</c></seealso>
+ <seecref marker="#erlang_char_encoding"><c>from_enc</c></seecref>
(ASCII, Latin-1, or UTF-8). The name must either be <c>NULL</c>-terminated or
a function variant with a <c>len</c> parameter must be used.</p>
<p>The encoding fails if <c>p</c> is not a valid string in encoding
@@ -480,6 +645,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_bignum(char *buf, int *index, mpz_t obj)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_bignum(ei_x_buff *x, mpz_t obj)</nametext></name>
<fsummary>Encode an arbitrary precision integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a GMP <c>mpz_t</c> integer to binary format.
To use this function, the <c>ei</c> library must be configured and
@@ -491,6 +659,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_binary(char *buf, int *index, const void *p, long len)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_binary(ei_x_buff* x, const void *p, long len)</nametext></name>
<fsummary>Encode a binary.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a binary in the binary format. The data is at
<c>p</c>, of <c>len</c> bytes length.</p>
@@ -503,6 +674,9 @@ typedef enum {
<name since="OTP 22.0"><ret>int</ret>
<nametext>ei_x_encode_bitstring(ei_x_buff* x, const char *p, size_t bitoffs, size_t nbits)</nametext></name>
<fsummary>Encode a bitstring.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a bit string in the binary format.</p>
<p>The data is at <c>p</c>. The length of the bit string is <c>nbits</c>
@@ -523,6 +697,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_boolean(char *buf, int *index, int p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_boolean(ei_x_buff* x, int p)</nametext></name>
<fsummary>Encode a boolean.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a boolean value as the atom <c>true</c> if
<c>p</c> is not zero, or <c>false</c> if <c>p</c> is
@@ -534,6 +711,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_char(char *buf, int *index, char p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_char(ei_x_buff* x, char p)</nametext></name>
<fsummary>Encode an 8-bit integer between 0-255.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a char (8-bit) as an integer between 0-255 in the binary
format. For historical reasons the integer argument is of
@@ -548,6 +728,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_double(char *buf, int *index, double p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_double(ei_x_buff* x, double p)</nametext></name>
<fsummary>Encode a double float.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a double-precision (64-bit) floating point number in
the binary format.</p>
@@ -560,6 +743,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_empty_list(char* buf, int* index)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_empty_list(ei_x_buff* x)</nametext></name>
<fsummary>Encode an empty list (<c>nil</c>).</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes an empty list. It is often used at the tail of a list.</p>
</desc>
@@ -569,6 +755,10 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_fun(char *buf, int *index, const erlang_fun *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_fun(ei_x_buff* x, const erlang_fun* fun)</nametext></name>
<fsummary>Encode a fun.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_fun"><c>erlang_fun</c></seecref></v>
+ </type>
<desc>
<p>Encodes a fun in the binary format. Parameter <c>p</c>
points to an <c>erlang_fun</c> structure. The
@@ -582,6 +772,9 @@ typedef enum {
<name since=""><ret>int</ret><nametext>ei_encode_list_header(char *buf, int *index, int arity)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_list_header(ei_x_buff* x, int arity)</nametext></name>
<fsummary>Encode a list.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a list header, with a specified
arity. The next <c>arity+1</c> terms are the elements
@@ -619,6 +812,9 @@ ei_x_encode_empty_list(&amp;x);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_long(char *buf, int *index, long p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_long(ei_x_buff* x, long p)</nametext></name>
<fsummary>Encode integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a long integer in the binary format.
If the code is 64 bits, the function <c>ei_encode_long()</c> is
@@ -630,10 +826,12 @@ ei_x_encode_empty_list(&amp;x);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_longlong(char *buf, int *index, long long p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_longlong(ei_x_buff* x, long long p)</nametext></name>
<fsummary>Encode integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a GCC <c>long long</c> or Visual C++
- <c>__int64</c> (64-bit) integer in the binary format.
- This function is missing in the VxWorks port.</p>
+ <c>__int64</c> (64-bit) integer in the binary format.</p>
</desc>
</func>
@@ -641,6 +839,9 @@ ei_x_encode_empty_list(&amp;x);</pre>
<name since="OTP 17.0"><ret>int</ret><nametext>ei_encode_map_header(char *buf, int *index, int arity)</nametext></name>
<name since="OTP 17.0"><ret>int</ret><nametext>ei_x_encode_map_header(ei_x_buff* x, int arity)</nametext></name>
<fsummary>Encode a map.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a map header, with a specified arity. The next
<c>arity*2</c> terms encoded will be the keys and values of the map
@@ -662,11 +863,20 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_encode_pid(char *buf, int *index, const erlang_pid *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_pid(ei_x_buff* x, const erlang_pid *p)</nametext></name>
<fsummary>Encode a pid.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Encodes an Erlang process identifier (pid) in the binary
format. Parameter <c>p</c> points to an
- <c>erlang_pid</c> structure (which should have been
- obtained earlier with <c>ei_decode_pid()</c>).</p>
+ <c>erlang_pid</c> structure which should either have been
+ obtained earlier with
+ <seecref marker="#ei_decode_pid"><c>ei_decode_pid()</c></seecref>,
+ <seecref marker="ei_connect#ei_self"><c>ei_self()</c></seecref> or
+ created by
+ <seecref marker="ei_connect#ei_make_pid"><c>ei_make_pid()</c></seecref>.
+ </p>
</desc>
</func>
@@ -674,11 +884,16 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_encode_port(char *buf, int *index, const erlang_port *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_port(ei_x_buff* x, const erlang_port *p)</nametext></name>
<fsummary>Encode a port.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_port"><c>erlang_port</c></seecref></v>
+ </type>
<desc>
<p>Encodes an Erlang port in the binary format. Parameter
- <c>p</c> points to a <c>erlang_port</c>
- structure (which should have been obtained earlier with
- <c>ei_decode_port()</c>).</p>
+ <c>p</c> points to an <c>erlang_port</c> structure which
+ should have been obtained earlier with
+ <seecref marker="#ei_decode_port"><c>ei_decode_port()</c></seecref>,
+ </p>
</desc>
</func>
@@ -686,11 +901,17 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_encode_ref(char *buf, int *index, const erlang_ref *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_ref(ei_x_buff* x, const erlang_ref *p)</nametext></name>
<fsummary>Encode a ref.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_ref"><c>erlang_ref</c></seecref></v>
+ </type>
<desc>
<p>Encodes an Erlang reference in the binary format. Parameter
- <c>p</c> points to a <c>erlang_ref</c>
- structure (which should have been obtained earlier with
- <c>ei_decode_ref()</c>).</p>
+ <c>p</c> points to an <c>erlang_ref</c>
+ structure which either should have been obtained earlier with
+ <seecref marker="#ei_decode_ref"><c>ei_decode_ref()</c></seecref>, or
+ created by
+ <seecref marker="ei_connect#ei_make_ref"><c>ei_make_ref()</c></seecref>.</p>
</desc>
</func>
@@ -700,6 +921,9 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_x_encode_string(ei_x_buff* x, const char *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_string_len(ei_x_buff* x, const char* s, int len)</nametext></name>
<fsummary>Encode a string.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a string in the binary format. (A string in Erlang
is a list, but is encoded as a character array in the binary
@@ -707,31 +931,20 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
the <c>ei_x_encode_string_len()</c> function.</p>
</desc>
</func>
-
- <func>
- <name since=""><ret>int</ret><nametext>ei_encode_term(char *buf, int *index, void *t)</nametext></name>
- <name since=""><ret>int</ret><nametext>ei_x_encode_term(ei_x_buff* x, void *t)</nametext></name>
- <fsummary>Encode an <c>erl_interface</c> term.</fsummary>
- <desc>
- <p>Encodes an <c>ETERM</c>, as obtained from
- <c>erl_interface</c>. Parameter <c>t</c> is
- actually an <c>ETERM</c> pointer. This function
- does not free the <c>ETERM</c>.</p>
- <note><p>These functions are deprecated as of OTP 22 and will be removed in
- OTP 23 together with the old legacy <c>erl_interface</c> library
- (functions with prefix <c>erl_</c>).</p>
- </note>
- </desc>
- </func>
<func>
<name since=""><ret>int</ret><nametext>ei_encode_trace(char *buf, int *index, const erlang_trace *p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_trace(ei_x_buff* x, const erlang_trace *p)</nametext></name>
<fsummary>Encode a trace token.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_trace"><c>erlang_trace</c></seecref></v>
+ </type>
<desc>
<p>Encodes an Erlang trace token in the binary format.
Parameter <c>p</c> points to a
- <c>erlang_trace</c> structure (which should have been
- obtained earlier with <c>ei_decode_trace()</c>).</p>
+ <c>erlang_trace</c> structure which should have been
+ obtained earlier with
+ <seecref marker="#ei_decode_trace"><c>ei_decode_trace()</c></seecref>.</p>
</desc>
</func>
@@ -739,6 +952,9 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
<name since=""><ret>int</ret><nametext>ei_encode_tuple_header(char *buf, int *index, int arity)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_tuple_header(ei_x_buff* x, int arity)</nametext></name>
<fsummary>Encode a tuple.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a tuple header, with a specified
arity. The next <c>arity</c> terms encoded will be the
@@ -758,6 +974,9 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_ulong(char *buf, int *index, unsigned long p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_ulong(ei_x_buff* x, unsigned long p)</nametext></name>
<fsummary>Encode unsigned integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes an unsigned long integer in the binary format.
If the code is 64 bits, the function <c>ei_encode_ulong()</c> is
@@ -769,10 +988,13 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_ulonglong(char *buf, int *index, unsigned long long p)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_ulonglong(ei_x_buff* x, unsigned long long p)</nametext></name>
<fsummary>Encode unsigned integer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a GCC <c>unsigned long long</c> or Visual C++
<c>unsigned __int64</c> (64-bit) integer in the binary
- format. This function is missing in the VxWorks port.</p>
+ format.</p>
</desc>
</func>
@@ -780,6 +1002,9 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_encode_version(char *buf, int *index)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_encode_version(ei_x_buff* x)</nametext></name>
<fsummary>Encode version.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Encodes a version magic number for the binary format. Must
be the first token in a binary term.</p>
@@ -795,8 +1020,109 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
size is the number of characters <em>not</em> including the
terminating <c>NULL</c>. For binaries and bitstrings, <c>*size</c> is
the number of bytes. For lists, tuples and maps, <c>*size</c> is the
- arity of the object. For other types, <c>*size</c> is 0. In all
- cases, <c>index</c> is left unchanged.</p>
+ arity of the object. For bignum integers, <c>*size</c> is
+ the number of bytes for the absolute value of the bignum.
+ For other types, <c>*size</c> is 0.
+ In all cases, <c>index</c> is left unchanged.</p>
+ <p>Currently <c>*type</c> is one of:</p>
+ <taglist>
+ <tag>ERL_ATOM_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_atom"><c>ei_decode_atom()</c></seecref>,
+ <seecref marker="#ei_decode_atom_as"><c>ei_decode_atom_as()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_boolean"><c>ei_decode_boolean()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_BINARY_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_binary"><c>ei_decode_binary()</c></seecref>,
+ <seecref marker="#ei_decode_bitstring"><c>ei_decode_bitstring()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_BIT_BINARY_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_bitstring"><c>ei_decode_bitstring()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_FLOAT_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_double"><c>ei_decode_double()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_NEW_FUN_EXT<br/>ERL_FUN_EXT<br/>ERL_EXPORT_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_fun"><c>ei_decode_fun()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_SMALL_INTEGER_EXT<br/>ERL_INTEGER_EXT<br/>ERL_SMALL_BIG_EXT<br/>ERL_LARGE_BIG_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_char"><c>ei_decode_char()</c></seecref>,
+ <seecref marker="#ei_decode_long"><c>ei_decode_long()</c></seecref>,
+ <seecref marker="#ei_decode_longlong"><c>ei_decode_longlong()</c></seecref>,
+ <seecref marker="#ei_decode_ulong"><c>ei_decode_ulong()</c></seecref>,
+ <seecref marker="#ei_decode_ulonglong"><c>ei_decode_ulonglong()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_bignum"><c>ei_decode_bignum()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_LIST_EXT<br/>ERL_NIL_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_list_header"><c>ei_decode_list_header()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_STRING_EXT</tag>
+ <item><p>
+ Decode using either
+ <seecref marker="#ei_decode_string"><c>ei_decode_string()</c></seecref>,
+ or
+ <seecref marker="#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_MAP_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_map_header"><c>ei_decode_map_header()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_PID_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_pid"><c>ei_decode_pid()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_PORT_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_port"><c>ei_decode_port()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_NEW_REFERENCE_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_ref"><c>ei_decode_ref()</c></seecref>.
+ </p></item>
+
+ <tag>ERL_SMALL_TUPLE_EXT<br/>ERL_LARGE_TUPLE_EXT</tag>
+ <item><p>
+ Decode using
+ <seecref marker="#ei_decode_tuple_header"><c>ei_decode_tuple_header()</c></seecref>.
+ </p></item>
+ </taglist>
+ <p>Instead of decoding a term you can also skipped past it if you are
+ not interested in the data by usage of
+ <seecref marker="#ei_skip_term"><c>ei_skip_term()</c></seecref>.</p>
</desc>
</func>
@@ -806,11 +1132,7 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<desc>
<p>Initialize the <c>ei</c> library. This function should be called once
(and only once) before calling any other functionality in the <c>ei</c>
- library. However, note the exception below.</p>
- <p>If the <c>ei</c> library is used together with the <c>erl_interface</c>
- library, this function should <em>not</em> be called directly. It will be
- called by the <c>erl_init()</c> function which should be used to initialize
- the combination of the two libraries instead.</p>
+ library.</p>
<p>On success zero is returned. On failure a posix error code is returned.</p>
</desc>
</func>
@@ -842,11 +1164,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name since=""><ret>void</ret><nametext>ei_set_compat_rel(release_number)</nametext></name>
+ <name since=""><ret>void</ret><nametext>ei_set_compat_rel(unsigned release_number)</nametext></name>
<fsummary>Set the ei library in compatibility mode.</fsummary>
- <type>
- <v>unsigned release_number;</v>
- </type>
<desc>
<marker id="ei_set_compat_rel"></marker>
<p>In general, the <c>ei</c> library is guaranteed
@@ -879,8 +1198,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</taglist>
<p>If <c>ei_set_compat_rel(21)</c> is <em>not</em> called then a connected
emulator will send bit strings and export funs correctly encoded. The
- functions <seealso marker="#ei_decode_bitstring"><c>ei_decode_bitstring</c></seealso>
- and <seealso marker="#ei_decode_fun"><c>ei_decode_fun</c></seealso>
+ functions <seecref marker="#ei_decode_bitstring"><c>ei_decode_bitstring</c></seecref>
+ and <seecref marker="#ei_decode_fun"><c>ei_decode_fun</c></seecref>
has to be used to decode such terms. Calling
<c>ei_set_compat_rel(21)</c> should only be done as a workaround to
keep an old implementation alive, which expects to receive the
@@ -919,6 +1238,9 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_x_append(ei_x_buff* x, const ei_x_buff* x2)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_append_buf(ei_x_buff* x, const char* buf, int len)</nametext></name>
<fsummary>Append a buffer at the end.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
<p>Appends data at the end of buffer <c>x</c>.</p>
</desc>
@@ -928,6 +1250,10 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
<name since=""><ret>int</ret><nametext>ei_x_format(ei_x_buff* x, const char* fmt, ...)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_format_wo_ver(ei_x_buff* x, const char *fmt, ... )</nametext></name>
<fsummary>Format a term from a format string and parameters.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Formats a term, given as a string, to a buffer.
Works like a sprintf for Erlang terms.
@@ -956,9 +1282,13 @@ encodes the tuple {numbers,12,3.14159}</pre>
<func>
<name since=""><ret>int</ret><nametext>ei_x_free(ei_x_buff* x)</nametext></name>
<fsummary>Free a buffer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
- <p>Frees an <c>ei_x_buff</c> buffer.
- The memory used by the buffer is returned to the OS.</p>
+ <p>Deallocates the dynamically allocated content of the buffer
+ referred by <c>x</c>. After deallocation, the <c>buff</c> field
+ is set to <c>NULL</c>.</p>
</desc>
</func>
@@ -966,10 +1296,14 @@ encodes the tuple {numbers,12,3.14159}</pre>
<name since=""><ret>int</ret><nametext>ei_x_new(ei_x_buff* x)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_x_new_with_version(ei_x_buff* x)</nametext></name>
<fsummary>Allocate a new buffer.</fsummary>
+ <type>
+ <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ </type>
<desc>
- <p>Allocates a new <c>ei_x_buff</c> buffer. The
- fields of the structure pointed to by parameter <c>x</c>
- is filled in, and a default buffer is allocated.
+ <p>Initialize the dynamically realizable buffer referred to
+ by <c>x</c>. The fields of the structure pointed to by
+ parameter <c>x</c> is filled in, and a default buffer is
+ allocated.
<c>ei_x_new_with_version()</c> also puts an initial
version byte, which is used in the binary format (so that
<c>ei_x_encode_version()</c> will not be needed.)</p>
@@ -990,8 +1324,4 @@ encodes the tuple {numbers,12,3.14159}</pre>
</list>
</section>
- <section>
- <title>See Also</title>
- <p><seealso marker="erl_eterm"><c>erl_eterm</c></seealso></p>
- </section>
</cref>
diff --git a/lib/erl_interface/doc/src/ei_connect.xml b/lib/erl_interface/doc/src/ei_connect.xml
index 665792fd4b..c5ef9440c5 100644
--- a/lib/erl_interface/doc/src/ei_connect.xml
+++ b/lib/erl_interface/doc/src/ei_connect.xml
@@ -34,9 +34,6 @@
<lib>ei_connect</lib>
<libsummary>Communicate with distributed Erlang.</libsummary>
<description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
-
<p>This module enables C-programs to communicate with Erlang nodes,
using the Erlang distribution over TCP/IP.</p>
@@ -44,7 +41,7 @@
That is, Erlang processes that know the name of the
C-node can communicate with it in a normal manner, but
the node name is not shown in the listing provided by
- <seealso marker="erts:erlang#nodes/0"><c>erlang:nodes/0</c></seealso>
+ <seemfa marker="erts:erlang#nodes/0"><c>erlang:nodes/0</c></seemfa>
in <c>ERTS</c>.</p>
<p>The environment variable <c>ERL_EPMD_PORT</c> can be used
@@ -96,11 +93,11 @@
his/her own IPv4 socket implementation. This, for example, in order
to communicate over TLS. A user supplied socket implementation
is plugged in by passing a
- <seealso marker="#ei_socket_callbacks">callback structure</seealso>
+ <seecref marker="#ei_socket_callbacks">callback structure</seecref>
to either
- <seealso marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seealso>
+ <seecref marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seecref>
or
- <seealso marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seealso>.</p>
+ <seecref marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seecref>.</p>
<p>All callbacks in the <c>ei_socket_callbacks</c> structure
<em>should</em> return zero on success; and a posix error
@@ -112,27 +109,10 @@
only supports IPv4. That is, at this time <c>addr</c> always
points to a <c>struct sockaddr_in</c> structure.</p>
- <p>The <c>ei_socket_callbacks</c> structure may be enlarged in
- the future. All fields not set, <em>needs</em> to be zeroed out.</p>
-
- <marker id="ei_socket_callbacks"/>
- <code type="none"><![CDATA[
-typedef struct {
- int flags;
- int (*socket)(void **ctx, void *setup_ctx);
- int (*close)(void *ctx);
- int (*listen)(void *ctx, void *addr, int *len, int backlog);
- int (*accept)(void **ctx, void *addr, int *len, unsigned tmo);
- int (*connect)(void *ctx, void *addr, int len, unsigned tmo);
- int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo);
- int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo);
- int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo);
- int (*handshake_packet_header_size)(void *ctx, int *sz);
- int (*connect_handshake_complete)(void *ctx);
- int (*accept_handshake_complete)(void *ctx);
- int (*get_fd)(void *ctx, int *fd);
-} ei_socket_callbacks;
- ]]></code>
+ <p><marker id="ei_socket_callbacks_fields"/>The
+ <seecref marker="#ei_socket_callbacks"><c>ei_socket_callbacks</c></seecref>
+ structure may be enlarged in the future. All fields not set, <em>needs</em>
+ to be zeroed out. Currently the following fields exist:</p>
<taglist>
@@ -186,9 +166,9 @@ typedef struct {
the created socket. This context will be passed to all other
socket callbacks. This function will be passed the same
<c>setup_context</c> as passed to the preceeding
- <seealso marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seealso>
+ <seecref marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seecref>
or
- <seealso marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seealso>
+ <seecref marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seecref>
call.</p>
<note><p>During the lifetime of a socket, the pointer <c>*ctx</c>
@@ -355,6 +335,79 @@ typedef struct {
</item>
</taglist>
</section>
+ <section>
+ <title>Data Types</title>
+ <taglist>
+ <tag><marker id="ei_cnode"/><c>ei_cnode</c></tag>
+ <item><p>
+ Opaque data type representing a C-node. A <c>ei_cnode</c>
+ structure is initialized by calling
+ <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref>
+ or friends.
+ </p></item>
+
+ <tag><marker id="ei_socket_callbacks"/><c>ei_socket_callbacks</c></tag>
+ <item><code type="none">
+typedef struct {
+ int flags;
+ int (*socket)(void **ctx, void *setup_ctx);
+ int (*close)(void *ctx);
+ int (*listen)(void *ctx, void *addr, int *len, int backlog);
+ int (*accept)(void **ctx, void *addr, int *len, unsigned tmo);
+ int (*connect)(void *ctx, void *addr, int len, unsigned tmo);
+ int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo);
+ int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo);
+ int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo);
+ int (*handshake_packet_header_size)(void *ctx, int *sz);
+ int (*connect_handshake_complete)(void *ctx);
+ int (*accept_handshake_complete)(void *ctx);
+ int (*get_fd)(void *ctx, int *fd);
+} ei_socket_callbacks;</code>
+ <p>
+ Callbacks functions for a <seecref marker="#ussi"><i>User
+ Supplied Socket Implementation</i></seecref>.
+ <seecref marker="#ei_socket_callbacks_fields">Documentation
+ of each field</seecref> can be found in the
+ <i>User Supplied Socket Implementation</i> section
+ above.
+ </p>
+
+ </item>
+
+ <tag><marker id="ErlConnect"/><c>ErlConnect</c></tag>
+ <item><code type="none">
+typedef struct {
+ char ipadr[4]; /* Ip v4 address in network byte order */
+ char nodename[MAXNODELEN];
+} ErlConnect;</code>
+ <p>IP v4 address and nodename.</p>
+ </item>
+
+ <tag><marker id="Erl_IpAddr"/><c>Erl_IpAddr</c></tag>
+ <item><code type="none">
+typedef struct {
+ unsigned s_addr; /* Ip v4 address in network byte order */
+} Erl_IpAddr;</code>
+ <p>IP v4 address.</p>
+ </item>
+
+ <tag><marker id="erlang_msg"/><c>erlang_msg</c></tag>
+ <item>
+ <code type="none">
+typedef struct {
+ long msgtype;
+ erlang_pid from;
+ erlang_pid to;
+ char toname[MAXATOMLEN+1];
+ char cookie[MAXATOMLEN+1];
+ erlang_trace token;
+} erlang_msg;</code>
+ <p>Information about a message received via
+ <seecref marker="#ei_receive_msg"><c>ei_receive_msg()</c></seecref>
+ or friends.</p>
+ </item>
+ </taglist>
+ </section>
<funcs>
<func>
<name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyaddr(const char *addr, int len, int type)</nametext></name>
@@ -371,6 +424,10 @@ typedef struct {
<func>
<name since=""><ret>int</ret><nametext>ei_accept(ei_cnode *ec, int listensock, ErlConnect *conp)</nametext></name>
<fsummary>Accept a connection from another node.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#ErlConnect"><c>ErlConnect</c></seecref></v>
+ </type>
<desc>
<p>Used by a server process to accept a
connection from a client process.</p>
@@ -384,13 +441,8 @@ typedef struct {
</item>
<item>
<p><c>conp</c> is a pointer to an
- <c>ErlConnect</c> struct, described as follows:</p>
- <code type="none"><![CDATA[
-typedef struct {
- char ipadr[4];
- char nodename[MAXNODELEN];
-} ErlConnect;
- ]]></code>
+ <seecref marker="#ErlConnect"><c>ErlConnect</c></seecref>
+ struct.</p>
</item>
</list>
<p>On success, <c>conp</c> is filled in with the address and
@@ -404,6 +456,10 @@ typedef struct {
<name since=""><ret>int</ret><nametext>ei_accept_tmo(ei_cnode *ec, int listensock, ErlConnect *conp, unsigned timeout_ms)</nametext></name>
<fsummary>Accept a connection from another node with optional
time-out.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#ErlConnect"><c>ErlConnect</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_accept</c> with an optional time-out argument,
@@ -422,20 +478,34 @@ typedef struct {
<func>
<name since=""><ret>int</ret><nametext>ei_connect(ei_cnode* ec, char *nodename)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)</nametext></name>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_connect_host_port(ei_cnode* ec, char *hostname, int port)</nametext></name>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_xconnect_host_port(ei_cnode* ec, Erl_IpAddr adr, int port)</nametext></name>
<fsummary>Establish a connection to an Erlang node.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v>
+ </type>
<desc>
<p>Sets up a connection to an Erlang node.</p>
<p><c>ei_xconnect()</c> requires the IP address of the
remote host and the alive name of the remote node to be
specified. <c>ei_connect()</c> provides an alternative
interface and determines the information from the node name
- provided.</p>
+ provided. The <c>ei_xconnect_host_port()</c> function provides
+ yet another alternative that will work even if there is no
+ EPMD instance on the host where the remote node is running. The
+ <c>ei_xconnect_host_port()</c> function requires the IP
+ address and port of the remote node to be specified.
+ The <c>ei_connect_host_port()</c> function is an alternative
+ to <c>ei_xconnect_host_port()</c> that lets the user specify
+ a hostname instead of an IP address.</p>
<list type="bulleted">
- <item><c>addr</c> is the 32-bit IP address of the remote
+ <item><c>adr</c> is the 32-bit IP address of the remote
host.</item>
<item><c>alive</c> is the alivename of the remote node.
</item>
<item><c>node</c> is the name of the remote node.</item>
+ <item><c>port</c> is the port number of the remote node.</item>
</list>
<p>These functions return an open file descriptor on success, or
a negative value indicating that an error occurred. In the latter
@@ -476,6 +546,11 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
<name since=""><ret>int</ret><nametext>ei_connect_xinit(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation)</nametext></name>
<name since="OTP 21.3"><ret>int</ret><nametext>ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
<fsummary>Initialize for a connection.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v>
+ <v><seecref marker="#ei_socket_callbacks"><c>ei_socket_callbacks</c></seecref></v>
+ </type>
<desc>
<p>Initializes the <c>ec</c> structure, to
identify the node name and cookie of the server. One of them
@@ -489,8 +564,8 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
for connecting and receiving data.</p>
</item>
<item>
- <p><c>this_node_name</c> is the registered name of the
- process (the name before '@').</p>
+ <p><c>this_node_name</c> is the name of the C-node
+ (the name before '@' in the full node name).</p>
</item>
<item>
<p><c>cookie</c> is the cookie for the node.</p>
@@ -507,19 +582,21 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
instead of <c>durin</c>).</p>
</item>
<item>
- <p><c>thisalivename</c> is the registered name of the
- process.</p>
+ <p><c>thisalivename</c> is the name of the local C-node (the name
+ before '@' in the full node name). Can be <c>NULL</c> (from OTP 23)
+ to get a dynamically assigned name from the peer node.</p>
</item>
<item>
- <p><c>thisnodename</c> is the full name of the node,
- that is, <c>einode@durin</c>.</p>
+ <p><c>thisnodename</c> is the full name of the local C-node,
+ that is, <c>mynode@myhost</c>. Can be <c>NULL</c> if
+ <c>thisalivename</c> is <c>NULL</c>.</p>
</item>
<item>
<p><c>thispaddr</c> if the IP address of the host.</p>
</item>
<item>
<p><c>cbs</c> is a pointer to a
- <seealso marker="#ei_socket_callbacks">callback structure</seealso>
+ <seecref marker="#ei_socket_callbacks">callback structure</seecref>
implementing and alternative socket interface.</p>
</item>
<item>
@@ -571,13 +648,20 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<func>
<name since=""><ret>int</ret><nametext>ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned timeout_ms)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned timeout_ms)</nametext></name>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_connect_host_port_tmo(ei_cnode* ec, char *hostname, int port, unsigned ms)</nametext></name>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_xconnect_host_port_tmo(ei_cnode* ec, Erl_IpAddr adr, int port, unsigned ms)</nametext></name>
<fsummary>Establish a connection to an Erlang node with optional
time-out.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v>
+ </type>
<desc>
- <p>Equivalent to
- <c>ei_connect</c> and <c>ei_xconnect</c> with an optional time-out
- argument, see the description at the beginning of this manual
- page.</p>
+ <p>Equivalent to <c>ei_connect</c>, <c>ei_xconnect</c>,
+ <c>ei_connect_host_port</c> and
+ <c>ei_xconnect_host_port</c> with an optional time-out
+ argument, see the description at the beginning of this manual
+ page.</p>
</desc>
</func>
@@ -588,8 +672,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<desc>
<p>Used to set tracing on the distribution. The levels are different
verbosity levels. A higher level means more information. See also
- section <seealso marker="#debug_information">
- Debug Information</seealso>.</p>
+ section <seecref marker="#debug_information">
+ Debug Information</seecref>.</p>
<p>These functions are not thread safe.</p>
</desc>
</func>
@@ -598,6 +682,10 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<name since="OTP 21.3"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name>
<name since="OTP 21.3"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name>
<fsummary>Create a listen socket.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v>
+ </type>
<desc>
<p>Used by a server process to setup a listen socket which
later can be used for accepting connections from client processes.
@@ -634,8 +722,60 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_make_pid(ei_cnode *ec, erlang_pid *pid)</nametext></name>
+ <fsummary>Create a new process identifier</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Creates a new process identifier in the argument <c>pid</c>. This process identifier
+ refers to a conseptual process residing on the C-node identified by the argument
+ <c>ec</c>. On success <c>0</c> is returned. On failure <c>ERL_ERROR</c> is
+ returned and <c>erl_errno</c> is set.
+ </p>
+ <p>
+ The C-node identified by <c>ec</c> must have been initialized and must have
+ received a name prior to the call to <c>ei_make_pid()</c>. Initialization
+ of the C-node is done by a call to
+ <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref>
+ or friends. If the name is dynamically assigned from the peer node, the
+ C-node also has to be connected.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 23.0"><ret>int</ret><nametext>ei_make_ref(ei_cnode *ec, erlang_ref *ref)</nametext></name>
+ <fsummary>Create a new reference</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="ei#erlang_ref"><c>erlang_ref</c></seecref></v>
+ </type>
+ <desc>
+ <p>
+ Creates a new reference in the argument <c>ref</c>. This reference originates
+ from the C-node identified by the argument <c>ec</c>. On success <c>0</c> is
+ returned. On failure <c>ERL_ERROR</c> is returned and <c>erl_errno</c> is set.
+ </p>
+ <p>
+ The C-node identified by <c>ec</c> must have been initialized and must have
+ received a name prior to the call to <c>ei_make_ref()</c>. Initialization
+ of the C-node is done by a call to
+ <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref>
+ or friends. If the name is dynamically assigned from the peer node, the
+ C-node also has to be connected.
+ </p>
+ </desc>
+ </func>
+
+ <func>
<name since=""><ret>int</ret><nametext>ei_publish(ei_cnode *ec, int port)</nametext></name>
<fsummary>Publish a node name.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Used by a server process to register
with the local name server EPMD, thereby allowing
@@ -673,6 +813,9 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<func>
<name since=""><ret>int</ret><nametext>ei_publish_tmo(ei_cnode *ec, int port, unsigned timeout_ms)</nametext></name>
<fsummary>Publish a node name with optional time-out.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_publish</c> with an optional time-out argument,
@@ -724,6 +867,9 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<func>
<name since=""><ret>int</ret><nametext>ei_receive_encoded(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen)</nametext></name>
<fsummary>Obsolete function for receiving a message.</fsummary>
+ <type>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>This function is retained for compatibility with code
generated by the interface compiler and with code following
@@ -755,6 +901,9 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<name since=""><ret>int</ret><nametext>ei_receive_encoded_tmo(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen, unsigned timeout_ms)</nametext></name>
<fsummary>Obsolete function for receiving a message with time-out.
</fsummary>
+ <type>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_receive_encoded</c> with an optional time-out argument,
@@ -766,6 +915,10 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<name since=""><ret>int</ret><nametext>ei_receive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_xreceive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
<fsummary>Receive a message.</fsummary>
+ <type>
+ <v><seecref marker="ei#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>Receives a message to the buffer in <c>x</c>.
<c>ei_xreceive_msg</c> allows the buffer in
@@ -782,18 +935,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
<c>ei_x_new</c>.</item>
</list>
<p>On success, the functions return <c>ERL_MSG</c> and the
- <c>msg</c> struct is initialized.
- <c>erlang_msg</c> is defined as follows:</p>
- <code type="none"><![CDATA[
-typedef struct {
- long msgtype;
- erlang_pid from;
- erlang_pid to;
- char toname[MAXATOMLEN+1];
- char cookie[MAXATOMLEN+1];
- erlang_trace token;
-} erlang_msg;
- ]]></code>
+ <seecref marker="#erlang_msg"><c>msg</c></seecref> struct
+ is initialized.</p>
<p><c>msgtype</c> identifies the type of message, and is
one of the following:</p>
<taglist>
@@ -823,7 +966,7 @@ typedef struct {
</item>
</taglist>
<p>The return value is the same as for
- <seealso marker="#ei_receive"><c>ei_receive</c></seealso>.</p>
+ <seecref marker="#ei_receive"><c>ei_receive</c></seecref>.</p>
</desc>
</func>
@@ -831,6 +974,10 @@ typedef struct {
<name since=""><ret>int</ret><nametext>ei_receive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned imeout_ms)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_xreceive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned timeout_ms)</nametext></name>
<fsummary>Receive a message with optional time-out.</fsummary>
+ <type>
+ <v><seecref marker="ei#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to <c>ei_receive_msg</c> and <c>ei_xreceive_msg</c>
with an optional time-out argument,
@@ -851,6 +998,9 @@ typedef struct {
<func>
<name since=""><ret>int</ret><nametext>ei_reg_send(ei_cnode* ec, int fd, char* server_name, char* buf, int len)</nametext></name>
<fsummary>Send a message to a registered name.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Sends an Erlang term to a registered process.</p>
<list type="bulleted">
@@ -884,6 +1034,9 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
<name since=""><ret>int</ret><nametext>ei_reg_send_tmo(ei_cnode* ec, int fd, char* server_name, char* buf, int len, unsigned timeout_ms)</nametext></name>
<fsummary>Send a message to a registered name with optional time-out
</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_reg_send</c> with an optional time-out argument,
@@ -896,14 +1049,19 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
<name since=""><ret>int</ret><nametext>ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x)</nametext></name>
<fsummary>Remote Procedure Call from C to Erlang.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="ei#ei_x_buff"><c>ei_x_buff</c></seecref></v>
+ <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v>
+ </type>
<desc>
<p>Supports calling Erlang functions on remote nodes.
<c>ei_rpc_to()</c> sends an RPC request to a remote node
and <c>ei_rpc_from()</c> receives the results of such a
call. <c>ei_rpc()</c> combines the functionality of these
two functions by sending an RPC request and waiting for the results.
- See also <seealso marker="kernel:rpc#call/4">
- <c>rpc:call/4</c></seealso> in Kernel.</p>
+ See also <seemfa marker="kernel:rpc#call/4">
+ <c>rpc:call/4</c></seemfa> in Kernel.</p>
<list type="bulleted">
<item>
<p><c>ec</c> is the C-node structure previously
@@ -942,8 +1100,8 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
<c>erlang_msg</c> and contains information on the
message
received. For a description of the <c>erlang_msg</c>
- format, see <seealso marker="#ei_receive_msg">
- <c>ei_receive_msg</c></seealso>.</p>
+ format, see <seecref marker="#ei_receive_msg">
+ <c>ei_receive_msg</c></seecref>.</p>
</item>
<item>
<p><c>x</c> points to the dynamic buffer that receives
@@ -995,19 +1153,40 @@ if (ei_decode_version(result.buff, &index) < 0
<func>
<name since=""><ret>erlang_pid *</ret><nametext>ei_self(ei_cnode *ec)</nametext></name>
<fsummary>Retrieve the pid of the C-node.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
- <p>Retrieves the pid of the C-node. Every C-node
+ <p>Retrieves a generic pid of the C-node. Every C-node
has a (pseudo) pid used in <c>ei_send_reg</c>,
- <c>ei_rpc</c>,
+ <c>ei_rpc()</c>,
and others. This is contained in a field in the <c>ec</c>
- structure. It will be safe for a long time to fetch this
- field directly from the <c>ei_cnode</c> structure.</p>
+ structure. Do <em>not</em> modify this structure.
+ </p>
+ <p>
+ On success a pointer to the process identifier is returned.
+ On failure <c>NULL</c> is returned and <c>erl_errno</c> is
+ set.
+ </p>
+ <p>
+ The C-node identified by <c>ec</c> must have been initialized
+ and must have received a name prior to the call to <c>ei_self()</c>.
+ Initialization of the C-node is done by a call to
+ <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref>
+ or friends. If the name is dynamically assigned from the peer node, the
+ C-node also has to be connected.
+ </p>
+
</desc>
</func>
<func>
<name since=""><ret>int</ret><nametext>ei_send(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
<fsummary>Send a message.</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Sends an Erlang term to a process.</p>
<list type="bulleted">
@@ -1029,6 +1208,9 @@ if (ei_decode_version(result.buff, &index) < 0
<func>
<name since=""><ret>int</ret><nametext>ei_send_encoded(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
<fsummary>Obsolete function to send a message.</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Works exactly as <c>ei_send</c>, the alternative name is retained for
backward compatibility. The function will <em>not</em> be
@@ -1040,6 +1222,9 @@ if (ei_decode_version(result.buff, &index) < 0
<name since=""><ret>int</ret><nametext>ei_send_encoded_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
<fsummary>Obsolete function to send a message with optional time-out.
</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_send_encoded</c> with an optional time-out argument,
@@ -1051,6 +1236,9 @@ if (ei_decode_version(result.buff, &index) < 0
<name since=""><ret>int</ret><nametext>ei_send_reg_encoded(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name>
<fsummary>Obsolete function to send a message to a registered name.
</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>This function is retained for compatibility with code
generated by the interface compiler and with code following
@@ -1061,17 +1249,8 @@ if (ei_decode_version(result.buff, &index) < 0
<c>erlang_pid</c>,
which is to be the process identifier of the sending process
(in the Erlang distribution protocol).</p>
- <p>A suitable <c>erlang_pid</c> can be constructed from the
- <c>ei_cnode</c> structure by the following example
- code:</p>
- <code type="none"><![CDATA[
-ei_cnode ec;
-erlang_pid *self;
-int fd; /* the connection fd */
-...
-self = ei_self(&ec);
-self->num = fd;
- ]]></code>
+ <p>A suitable <c>erlang_pid</c> can be retrieved from the
+ <c>ei_cnode</c> structure by calling <c>ei_self(cnode_pointer)</c>.</p>
</desc>
</func>
@@ -1079,6 +1258,9 @@ self->num = fd;
<name since=""><ret>int</ret><nametext>ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name>
<fsummary>Obsolete function to send a message to a registered name with
time-out.</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_send_reg_encoded</c> with an optional time-out argument,
@@ -1089,6 +1271,9 @@ self->num = fd;
<func>
<name since=""><ret>int</ret><nametext>ei_send_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
<fsummary>Send a message with optional time-out.</fsummary>
+ <type>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_send</c> with an optional time-out argument,
@@ -1101,6 +1286,9 @@ self->num = fd;
<name since=""><ret>const char *</ret><nametext>ei_thishostname(ei_cnode *ec)</nametext></name>
<name since=""><ret>const char *</ret><nametext>ei_thisalivename(ei_cnode *ec)</nametext></name>
<fsummary>Retrieve some values.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Can be used to retrieve information about
the C-node. These values are initially set with
@@ -1116,6 +1304,9 @@ self->num = fd;
<func>
<name since=""><ret>int</ret><nametext>ei_unpublish(ei_cnode *ec)</nametext></name>
<fsummary>Forcefully unpublish a node name.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Can be called by a process to unregister a
specified node from EPMD on the local host. This is, however, usually
@@ -1139,6 +1330,9 @@ self->num = fd;
<func>
<name since=""><ret>int</ret><nametext>ei_unpublish_tmo(ei_cnode *ec, unsigned timeout_ms)</nametext></name>
<fsummary>Unpublish a node name with optional time-out.</fsummary>
+ <type>
+ <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v>
+ </type>
<desc>
<p>Equivalent to
<c>ei_unpublish</c> with an optional time-out argument,
diff --git a/lib/erl_interface/doc/src/erl_global.xml b/lib/erl_interface/doc/src/ei_global.xml
index 83221fd8dc..148068490e 100644
--- a/lib/erl_interface/doc/src/erl_global.xml
+++ b/lib/erl_interface/doc/src/ei_global.xml
@@ -22,7 +22,7 @@
</legalnotice>
- <title>erl_global</title>
+ <title>ei_global</title>
<prepared>Gordon Beaton</prepared>
<responsible>Gordon Beaton</responsible>
<docno></docno>
@@ -30,24 +30,15 @@
<checked>Gordon Beaton</checked>
<date>1998-07-03</date>
<rev>A</rev>
- <file>erl_global.xml</file>
+ <file>ei_global.xml</file>
</header>
- <lib>erl_global</lib>
+ <lib>ei_global</lib>
<libsummary>Access globally registered names.</libsummary>
<description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
-
<p>This module provides support for registering, looking
up, and unregistering names in the <c>global</c> module.
For more information, see
- <seealso marker="kernel:global"><c>kernel:global</c></seealso>.</p>
+ <seeerl marker="kernel:global"><c>kernel:global</c></seeerl>.</p>
<p>Notice that the functions below perform an RPC using an open file
descriptor provided by the caller. This file descriptor must
@@ -57,15 +48,17 @@
<funcs>
<func>
- <name since=""><ret>char **</ret><nametext>erl_global_names(fd,count)</nametext></name>
+ <name since=""><ret>char **</ret><nametext>ei_global_names(ec,fd,count)</nametext></name>
<fsummary>Obtain list of global names.</fsummary>
<type>
+ <v><seecref marker="ei_connect#ei_cnode"><c>ei_cnode</c></seecref> *ec;</v>
<v>int fd;</v>
<v>int *count;</v>
</type>
<desc>
<p>Retrieves a list of all known global names.</p>
<list type="bulleted">
+ <item><c>ec</c> is the <c>ei_cnode</c> representing the current cnode.</item>
<item><c>fd</c> is an open descriptor to an Erlang
connection.</item>
<item><c>count</c> is the address of an integer, or
@@ -88,12 +81,12 @@
</func>
<func>
- <name since=""><ret>int</ret><nametext>erl_global_register(fd,name,pid)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_global_register(fd,name,pid)</nametext></name>
<fsummary>Register a name in global.</fsummary>
<type>
<v>int fd;</v>
<v>const char *name;</v>
- <v>ETERM *pid;</v>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref> *pid;</v>
</type>
<desc>
<p>Registers a name in <c>global</c>.</p>
@@ -112,15 +105,17 @@
</func>
<func>
- <name since=""><ret>int</ret><nametext>erl_global_unregister(fd,name)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_global_unregister(ec,fd,name)</nametext></name>
<fsummary>Unregister a name from global.</fsummary>
<type>
+ <v><seecref marker="ei_connect#ei_cnode"><c>ei_cnode</c></seecref> *ec;</v>
<v>int fd;</v>
<v>const char *name;</v>
</type>
<desc>
<p>Unregisters a name from <c>global</c>.</p>
<list type="bulleted">
+ <item><c>ec</c> is the <c>ei_cnode</c> representing the current cnode.</item>
<item><c>fd</c> is an open descriptor to an Erlang
connection.</item>
<item><c>name</c> is the name to unregister from
@@ -131,30 +126,35 @@
</func>
<func>
- <name since=""><ret>ETERM *</ret><nametext>erl_global_whereis(fd,name,node)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_global_whereis(ec,fd,name,pid,node)</nametext></name>
<fsummary>Look up a name in global.</fsummary>
<type>
+ <v><seecref marker="ei_connect#ei_cnode"><c>ei_cnode</c></seecref> *ec;</v>
<v>int fd;</v>
<v>const char *name;</v>
+ <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref> *pid;</v>
<v>char *node;</v>
</type>
<desc>
<p>Looks up a name in <c>global</c>.</p>
<list type="bulleted">
+ <item><c>ec</c> is the <c>ei_cnode</c> representing the current cnode.</item>
<item><c>fd</c> is an open descriptor to an Erlang
connection.</item>
<item><c>name</c> is the name that is to be looked up in
<c>global</c>.</item>
</list>
+ <p>The <c>pid</c> parameter is a pointer to a
+ <c>erlang_pid</c> that the function will update with the pid associated
+ with the global name, if successful.</p>
<p>If <c>node</c> is not <c>NULL</c>, it is a pointer to a
buffer where the function can fill in the name of the node where
<c>name</c> is found. <c>node</c> can be
- passed directly to <c>erl_connect()</c> if necessary.</p>
- <p>On success, the function returns an Erlang pid containing the address
- of the specified name, and the node is initialized to
+ passed directly to <c>ei_connect()</c> if necessary.</p>
+ <p>On success, the function returns 0, updates the <c>erlang_pid</c>
+ pointed to by the pid parameter, and the <c>node</c> parameter is initialized to
the node name where <c>name</c> is found. On failure,
- <c>NULL</c> is returned and <c>node</c> is not
- modified.</p>
+ a negative number is returned.</p>
</desc>
</func>
</funcs>
diff --git a/lib/erl_interface/doc/src/ei_users_guide.xml b/lib/erl_interface/doc/src/ei_users_guide.xml
index 42bed42b3c..5dfaf556da 100644
--- a/lib/erl_interface/doc/src/ei_users_guide.xml
+++ b/lib/erl_interface/doc/src/ei_users_guide.xml
@@ -34,18 +34,6 @@
</header>
<section>
- <title>Deprecation and Removal</title>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
- </section>
-
- <section>
<title>Introduction</title>
<p>The <c>Erl_Interface</c> library contains functions that help you
integrate programs written in C and Erlang. The functions in
@@ -57,17 +45,15 @@
storage</item>
<item>Communication between C nodes and Erlang processes</item>
<item>Backup and restore of C node state to and from
- <seealso marker="mnesia:mnesia">Mnesia</seealso></item>
+ <seeerl marker="mnesia:mnesia">Mnesia</seeerl></item>
</list>
<note>
- <p>By default, the <c>Erl_Interface</c> libraries are only guaranteed
+ <p>By default, the <c>Erl_Interface</c> library is only guaranteed
to be compatible with other Erlang/OTP components from the same
release as the libraries themselves. For information about how to
communicate with Erlang/OTP components from earlier releases, see
- function <seealso marker="ei#ei_set_compat_rel">
- <c>ei:ei_set_compat_rel</c></seealso> and
- <seealso marker="erl_eterm#erl_set_compat_rel">
- <c>erl_eterm:erl_set_compat_rel</c></seealso>.</p>
+ function <seecref marker="ei#ei_set_compat_rel">
+ <c>ei_set_compat_rel</c></seecref>.</p>
</note>
<section>
@@ -98,10 +84,9 @@
<section>
<title>Compiling and Linking Your Code</title>
<p>To use any of the <c>Erl_Interface</c> functions, include the
- following lines in your code:</p>
+ following line in your code:</p>
<code type="none"><![CDATA[
-#include "erl_interface.h"
#include "ei.h" ]]></code>
<p>Determine where the top directory of your OTP installation is.
@@ -114,7 +99,7 @@ Eshell V4.7.4 (abort with ^G)
/usr/local/otp ]]></code>
<p>To compile your code, ensure that your C compiler knows where
- to find <c>erl_interface.h</c> by specifying an appropriate
+ to find <c>ei.h</c> by specifying an appropriate
<c>-I</c> argument on the command line, or add it to
the <c>CFLAGS</c> definition in your
<c>Makefile</c>. The correct value for this path is
@@ -140,11 +125,9 @@ $ cc -c -I/usr/local/otp/lib/erl_interface-3.2.3/include myprog.c ]]></code>
<p>When linking:</p>
<list type="bulleted">
- <item>Specify the path to <c>liberl_interface.a</c> and
- <c>libei.a</c> with
+ <item>Specify the path to <c>libei.a</c> with
<c>-L$OTPROOT/lib/erl_interface-3.2.3/lib</c>.</item>
- <item>Specify the name of the libraries with
- <c>-lerl_interface -lei</c>.</item>
+ <item>Specify the name of the library with <c>-lei</c>.</item>
</list>
<p>Do this on the command line or add the flags to the
@@ -155,7 +138,7 @@ $ cc -c -I/usr/local/otp/lib/erl_interface-3.2.3/include myprog.c ]]></code>
<code type="none"><![CDATA[
$ ld -L/usr/local/otp/lib/erl_interface-3.2.3/
- lib myprog.o -lerl_interface -lei -o myprog ]]></code>
+ lib myprog.o -lei -o myprog ]]></code>
<p>On some systems it can be necessary to link with some more
libraries (for example, <c>libnsl.a</c> and
@@ -174,19 +157,11 @@ $ ld -L/usr/local/otp/lib/erl_interface-3.2.3/
</section>
<section>
- <title>Initializing the Libraries</title>
+ <title>Initializing the Library</title>
<p>
- Before calling any of the other functions in the <c>erl_interface</c>
- and <c>ei</c> libraries, call <c>erl_init()</c> exactly once to initialize
- both libraries.
- <c>erl_init()</c> takes two arguments. However, the arguments
- are no longer used by <c>erl_interface</c> and are therefore to be
- specified as <c>erl_init(NULL,0)</c>.
- </p>
- <p>
- If you only use the <c>ei</c> library, instead initialize it by calling
- <c>ei_init()</c> exactly once before calling any other functions in
- the <c>ei</c> library.
+ Before calling any of the other functions in the library,
+ initialize it by calling
+ <c>ei_init()</c> exactly once.
</p>
</section>
@@ -199,192 +174,98 @@ $ ld -L/usr/local/otp/lib/erl_interface-3.2.3/
<p>The <c>Erl_Interface</c> library supports this activity. It has
several C functions that create and manipulate Erlang data
- structures. The library also contains an encode and a decode function.
- The following example shows how to create and encode an Erlang tuple
- <c>{tobbe,3928}</c>:</p>
+ structures. The following example shows how to create and encode
+ an Erlang tuple <c>{tobbe,3928}</c>:</p>
<code type="none"><![CDATA[
-ETERM *arr[2], *tuple;
-char buf[BUFSIZ];
-int i;
-
-arr[0] = erl_mk_atom("tobbe");
-arr[1] = erl_mk_integer(3928);
-tuple = erl_mk_tuple(arr, 2);
-i = erl_encode(tuple, buf); ]]></code>
-
- <p>Alternatively, you can use <c>erl_send()</c> and
- <c>erl_receive_msg</c>, which handle the encoding and
- decoding of messages transparently.</p>
-
- <p>For a complete description, see the following modules:</p>
- <list type="bulleted">
- <item><seealso marker="erl_eterm"><c>erl_eterm</c></seealso>
- for creating Erlang terms</item>
- <item><seealso marker="erl_marshal"><c>erl_marshal</c></seealso>
- for encoding and decoding routines</item>
- </list>
+ei_x_buff buf;
+ei_x_new(&buf);
+int i = 0;
+ei_x_encode_tuple_header(&buf, 2);
+ei_x_encode_atom(&buf, "tobbe");
+ei_x_encode_long(&buf, 3928); ]]></code>
+
+ <p>For a complete description, see the
+ <seecref marker="ei"><c>ei</c></seecref> module.</p>
</section>
<section>
<marker id="building_terms_and_patterns"/>
- <title>Building Terms and Patterns</title>
+ <title>Building Terms</title>
<p>The previous example can be simplified by using the
- <seealso marker="erl_format"><c>erl_format</c></seealso> module
+ <seecref marker="ei#ei_x_format_wo_ver"><c>ei_x_format_wo_ver</c></seecref> function
to create an Erlang term:</p>
<code type="none"><![CDATA[
-ETERM *ep;
-ep = erl_format("{~a,~i}", "tobbe", 3928); ]]></code>
+ei_x_buff buf;
+ei_x_new(&buf);
+ei_x_format_wo_ver(&buf, "{~a,~i}", "tobbe", 3928); ]]></code>
- <p>For a complete description of the different format directives, see
- the <seealso marker="erl_format"><c>erl_format</c></seealso> module.</p>
+ <p>For a complete description of the different format directives, see the
+ the <seecref marker="ei#ei_x_format_wo_ver"><c>ei_x_format_wo_ver</c></seecref> function.</p>
<p>The following example is more complex:</p>
<code type="none"><![CDATA[
-ETERM *ep;
-ep = erl_format("[{name,~a},{age,~i},{data,~w}]",
- "madonna",
- 21,
- erl_format("[{adr,~s,~i}]", "E-street", 42));
-erl_free_compound(ep); ]]></code>
+ei_x_buff buf;
+int i = 0;
+ei_x_new(&buf);
+ei_x_format_wo_ver(&buf,
+ "[{name,~a},{age,~i},{data,[{adr,~s,~i}]}]",
+ "madonna",
+ 21,
+ "E-street", 42);
+ei_print_term(stdout, buf.buff, &i);
+ei_x_free(&buf); ]]></code>
<p>As in the previous examples, it is your responsibility to free the
memory allocated for Erlang terms. In this example,
- <c>erl_free_compound()</c> ensures that the complete term
- pointed to by <c>ep</c> is released. This is necessary
- because the pointer from the second call to <c>erl_format</c> is lost.</p>
-
- <p>The following example shows a slightly different solution:</p>
-
- <code type="none"><![CDATA[
-ETERM *ep,*ep2;
-ep2 = erl_format("[{adr,~s,~i}]","E-street",42);
-ep = erl_format("[{name,~a},{age,~i},{data,~w}]",
- "madonna", 21, ep2);
-erl_free_term(ep);
-erl_free_term(ep2); ]]></code>
-
- <p>In this case, you free the two terms independently. The order in
- which you free the terms <c>ep</c> and <c>ep2</c>
- is not important,
- because the <c>Erl_Interface</c> library uses reference counting to
- determine when it is safe to remove objects.</p>
-
- <p>If you are unsure whether you have freed the terms properly, you
- can use the following function to see the status of the fixed term
- allocator:</p>
+ <c>ei_x_free()</c> ensures that the data
+ pointed to by <c>buf</c> is released.</p>
- <code type="none"><![CDATA[
-long allocated, freed;
-
-erl_eterm_statistics(&allocated,&freed);
-printf("currently allocated blocks: %ld\n",allocated);
-printf("length of freelist: %ld\n",freed);
-
-/* really free the freelist */
-erl_eterm_release();
- ]]></code>
-
- <p>For more information, see the
- <seealso marker="erl_malloc"><c>erl_malloc</c></seealso> module.</p>
- </section>
-
- <section>
- <title>Pattern Matching</title>
- <p>An Erlang pattern is a term that can contain unbound variables or
- <c>"do not care"</c> symbols. Such a pattern can be matched
- against a
- term and, if the match is successful, any unbound variables in the
- pattern will be bound as a side effect. The content of a bound
- variable can then be retrieved:</p>
-
- <code type="none"><![CDATA[
-ETERM *pattern;
-pattern = erl_format("{madonna,Age,_}"); ]]></code>
-
- <p>The <seealso marker="erl_format#erl_match">
- <c>erl_format:erl_match</c></seealso> function
- performs pattern matching. It takes a
- pattern and a term and tries to match them. As a side effect any unbound
- variables in the pattern will be bound. In the following example, a
- pattern is created with a variable <c>Age</c>, which is included at two
- positions in the tuple. The pattern match is performed as follows:</p>
-
- <list type="bulleted">
- <item>
- <p><c>erl_match</c> binds the contents of <c>Age</c> to <c>21</c>
- the first time it reaches the variable.</p>
- </item>
- <item>
- <p>The second occurrence of <c>Age</c> causes a test for
- equality between the terms, as <c>Age</c> is already bound to
- <c>21</c>. As <c>Age</c> is bound to <c>21</c>, the equality test
- succeeds and the match continues until the end of the pattern.</p>
- </item>
- <item>
- <p>If the end of the pattern is reached, the match succeeds and you
- can retrieve the contents of the variable.</p>
- </item>
- </list>
-
- <code type="none"><![CDATA[
-ETERM *pattern,*term;
-pattern = erl_format("{madonna,Age,Age}");
-term = erl_format("{madonna,21,21}");
-if (erl_match(pattern, term)) {
- fprintf(stderr, "Yes, they matched: Age = ");
- ep = erl_var_content(pattern, "Age");
- erl_print_term(stderr, ep);
- fprintf(stderr,"\n");
- erl_free_term(ep);
-}
-erl_free_term(pattern);
-erl_free_term(term); ]]></code>
-
- <p>For more information, see the
- <seealso marker="erl_format#erl_match">
- <c>erl_format:erl_match</c></seealso> function.</p>
</section>
<section>
<title>Connecting to a Distributed Erlang Node</title>
<p>To connect to a distributed Erlang node, you must first
- initialize the connection routine with
- <seealso marker="erl_connect#erl_connect_init">
- <c>erl_connect:erl_connect_init</c></seealso>,
- which stores information, such as the hostname, node name, and IP
- address for later use:</p>
+ initialize the connection routine with one of the
+ <seecref marker="ei_connect#ei_connect_init">
+ <c>ei_connect_init_*</c></seecref> functions,
+ which stores information, such as the hostname, and node name
+ for later use:</p>
<code type="none"><![CDATA[
int identification_number = 99;
int creation=1;
char *cookie="a secret cookie string"; /* An example */
-erl_connect_init(identification_number, cookie, creation); ]]></code>
+const char* node_name = "einode@durin";
+const char *cookie = NULL;
+short creation = time(NULL) + 1;
+ei_cnode ec;
+ei_connect_init(ec,
+ node_name,
+ cookie,
+ creation); ]]></code>
<p>For more information, see the
- <seealso marker="erl_connect"><c>erl_connect</c></seealso> module.</p>
+ <seecref marker="ei_connect"><c>ei_connect</c></seecref> module.</p>
<p>After initialization, you set up the connection to the Erlang node.
- To specify the Erlang node you want to connect to, use
- <c>erl_connect()</c>. The following example sets up the
+ To specify the Erlang node you want to connect to, use the
+ <c>ei_connect_*()</c> family of functions. The following example sets up the
connection and is to result in a valid socket file descriptor:</p>
<code type="none"><![CDATA[
int sockfd;
-char *nodename="xyz@chivas.du.etx.ericsson.se"; /* An example */
-if ((sockfd = erl_connect(nodename)) < 0)
- erl_err_quit("ERROR: erl_connect failed"); ]]></code>
+const char* node_name = "einode@durin"; /* An example */
+if ((sockfd = ei_connect(ec, nodename)) < 0)
+ fprintf(stderr, "ERROR: ei_connect failed"); ]]></code>
- <p><c>erl_err_quit()</c> prints the specified string and
- terminates the program. For more information, see the
- <seealso marker="erl_error"><c>erl_error</c></seealso> module.</p>
</section>
<section>
<title>Using EPMD</title>
- <p><seealso marker="erts:epmd"><c>erts:epmd</c></seealso>
+ <p><seecom marker="erts:epmd"><c>erts:epmd</c></seecom>
is the Erlang Port Mapper Daemon. Distributed
Erlang nodes register with <c>epmd</c> on the local host to
indicate to other nodes that they exist and can accept connections.
@@ -394,7 +275,7 @@ if ((sockfd = erl_connect(nodename)) < 0)
correct port number to connect to.</p>
<p>When you use
- <seealso marker="erl_connect"><c>erl_connect</c></seealso>
+ <seecref marker="ei_connect"><c>ei_connect</c></seecref>
to connect to an Erlang node, a connection is first made to
<c>epmd</c> and, if the node is known, a
connection is then made to the Erlang node.</p>
@@ -409,7 +290,7 @@ if ((sockfd = erl_connect(nodename)) < 0)
<code type="none"><![CDATA[
int pub;
-pub = erl_publish(port); ]]></code>
+pub = ei_publish(ec, port); ]]></code>
<p><c>pub</c> is a file descriptor now connected to
<c>epmd</c>. <c>epmd</c>
@@ -424,17 +305,9 @@ pub = erl_publish(port); ]]></code>
failed. If a node has failed in this way, <c>epmd</c>
prevents you from
registering a new node with the old name, as it thinks that the old
- name is still in use. In this case, you must unregister the name
- explicitly:</p>
+ name is still in use. In this case, you must close the port
+ explicitly</p>
- <code type="none"><![CDATA[
-erl_unpublish(node); ]]></code>
-
- <p>This causes <c>epmd</c> to close the connection from the
- far end. Notice
- that if the name was in fact still in use by a node, the results of
- this operation are unpredictable. Also, doing this does not cause the
- local end of the connection to close, so resources can be consumed.</p>
</section>
<section>
@@ -442,10 +315,10 @@ erl_unpublish(node); ]]></code>
<p>Use one of the following two functions to send messages:</p>
<list type="bulleted">
- <item><seealso marker="erl_connect#erl_send">
- <c>erl_connect:erl_send</c></seealso></item>
- <item><seealso marker="erl_connect#erl_reg_send">
- <c>erl_connect:erl_reg_send</c></seealso></item>
+ <item><seecref marker="ei_connect#ei_send">
+ <c>ei_send</c></seecref></item>
+ <item><seecref marker="ei_connect#ei_reg_send">
+ <c>ei_reg_send</c></seecref></item>
</list>
<p>As in Erlang, messages can be sent to a
@@ -456,82 +329,79 @@ erl_unpublish(node); ]]></code>
<p>Use one of the following two functions to receive messages:</p>
<list type="bulleted">
- <item><seealso marker="erl_connect#erl_receive">
- <c>erl_connect:erl_receive</c></seealso></item>
- <item><seealso marker="erl_connect#erl_receive_msg">
- <c>erl_connect:erl_receive_msg</c></seealso></item>
+ <item><seecref marker="ei_connect#ei_receive">
+ <c>ei_receive</c></seecref></item>
+ <item><seecref marker="ei_connect#ei_receive_msg">
+ <c>ei_receive_msg</c></seecref></item>
</list>
- <p><c>erl_receive()</c> receives the message into a buffer,
- while <c>erl_receive_msg()</c> decodes the message into an
- Erlang term.</p>
-
<section>
<title>Example of Sending Messages</title>
<p>In the following example, <c>{Pid, hello_world}</c> is
- sent to a registered process <c>my_server</c>. The message
- is encoded by <c>erl_send()</c>:</p>
+ sent to a registered process <c>my_server</c>:</p>
<code type="none"><![CDATA[
-extern const char *erl_thisnodename(void);
-extern short erl_thiscreation(void);
-#define SELF(fd) erl_mk_pid(erl_thisnodename(),fd,0,erl_thiscreation())
-ETERM *arr[2], *emsg;
-int sockfd, creation=1;
-
-arr[0] = SELF(sockfd);
-arr[1] = erl_mk_atom("Hello world");
-emsg = erl_mk_tuple(arr, 2);
-
-erl_reg_send(sockfd, "my_server", emsg);
-erl_free_term(emsg); ]]></code>
+ei_x_buff buf;
+ei_x_new_with_version(&buf);
+
+ei_x_encode_tuple_header(&buf, 2);
+ei_x_encode_pid(&buf, ei_self(ec));
+ei_x_encode_atom(&buf, "Hello world");
+
+ei_reg_send(ec,fd,"my_server",buf,buf.index);]]></code>
<p>The first element of the tuple that is sent is your own
pid. This enables <c>my_server</c> to reply.
For more information about the primitives, see the
- <seealso marker="erl_connect"><c>erl_connect</c></seealso> module.</p>
+ <seecref marker="ei_connect"><c>ei_connect</c></seecref> module.</p>
</section>
<section>
<title>Example of Receiving Messages</title>
- <p>In this example, <c>{Pid, Something}</c> is received. The
- received pid is then used to return
- <c>{goodbye,Pid}</c>.</p>
-
- <code type="none"><![CDATA[
-ETERM *arr[2], *answer;
-int sockfd,rc;
-char buf[BUFSIZE];
-ErlMessage emsg;
-
-if ((rc = erl_receive_msg(sockfd , buf, BUFSIZE, &emsg)) == ERL_MSG) {
- arr[0] = erl_mk_atom("goodbye");
- arr[1] = erl_element(1, emsg.msg);
- answer = erl_mk_tuple(arr, 2);
- erl_send(sockfd, arr[1], answer);
- erl_free_term(answer);
- erl_free_term(emsg.msg);
- erl_free_term(emsg.to);
-} ]]></code>
+ <p>In this example, <c>{Pid, Something}</c> is received.</p>
+
+ <code type="none"><![CDATA[
+erlang_msg msg;
+int index = 0;
+int version;
+int arity = 0;
+erlang_pid pid;
+ei_x_buff buf;
+ei_x_new(&buf);
+for (;;) {
+ int got = ei_xreceive_msg(fd, &msg, &x);
+ if (got == ERL_TICK)
+ continue;
+ if (got == ERL_ERROR) {
+ fprintf(stderr, "ei_xreceive_msg, got==%d", got);
+ exit(1);
+ }
+ break;
+}
+ei_decode_version(buf.buff, &index, &version);
+ei_decode_tuple_header(buf.buff, &index, &arity);
+if (arity != 2) {
+ fprintf(stderr, "got wrong message");
+ exit(1);
+}
+ei_decode_pid(buf.buff, &index, &pid); ]]></code>
<p>To provide robustness, a distributed Erlang node
occasionally polls all its connected neighbors in an attempt to
detect failed nodes or communication links. A node that receives such
a message is expected to respond immediately with an
<c>ERL_TICK</c> message. This is done automatically by
- <c>erl_receive()</c>. However, when this has occurred,
- <c>erl_receive</c> returns <c>ERL_TICK</c> to
+ <c>ei_xreceive_msg()</c>. However, when this has occurred,
+ <c>ei_xreceive_msg</c> returns <c>ERL_TICK</c> to
the caller without storing a message into the
- <c>ErlMessage</c> structure.</p>
+ <c>erlang_msg</c> structure.</p>
<p>When a message has been received, it is the caller's responsibility
- to free the received message <c>emsg.msg</c> and
- <c>emsg.to</c> or <c>emsg.from</c>,
- depending on the type of message received.</p>
+ to free the received message.</p>
<p>For more information, see the
- <seealso marker="erl_connect"><c>erl_connect</c></seealso> and
- <seealso marker="erl_eterm"><c>erl_eterm</c></seealso> modules.</p>
+ <seecref marker="ei_connect"><c>ei_connect</c></seecref> and
+ <seecref marker="ei"><c>ei</c></seecref> modules.</p>
</section>
</section>
@@ -542,37 +412,36 @@ if ((rc = erl_receive_msg(sockfd , buf, BUFSIZE, &emsg)) == ERL_MSG) {
included in a function call at a remote node and is called a remote
procedure call.</p>
- <p>The following example shows how the
- <c>Erl_Interface</c> library supports remote procedure calls:</p>
+ <p>The following example checks if a specific Erlang process is alive:</p>
<code type="none"><![CDATA[
-char modname[]=THE_MODNAME;
-ETERM *reply,*ep;
-ep = erl_format("[~a,[]]", modname);
-if (!(reply = erl_rpc(fd, "c", "c", ep)))
- erl_err_msg("<ERROR> when compiling file: %s.erl !\n", modname);
-erl_free_term(ep);
-ep = erl_format("{ok,_}");
-if (!erl_match(ep, reply))
- erl_err_msg("<ERROR> compiler errors !\n");
-erl_free_term(ep);
-erl_free_term(reply); ]]></code>
-
- <p><c>c:c/1</c> is called to compile the specified module on
- the remote node. <c>erl_match()</c> checks that the
- compilation was
- successful by testing for the expected <c>ok</c>.</p>
-
- <p>For more information about <c>erl_rpc()</c> and its
- companions <c>erl_rpc_to()</c> and
- <c>erl_rpc_from()</c>, see the
- <seealso marker="erl_connect"><c>erl_connect</c></seealso> module.</p>
+int index = 0, is_alive;
+ei_x_buff args, result;
+
+ei_x_new(&result);
+ei_x_new(&args);
+ei_x_encode_list_header(&args, 1);
+ei_x_encode_pid(&args, &check_pid);
+ei_x_encode_empty_list(&args);
+
+if (ei_rpc(&ec, fd, "erlang", "is_process_alive",
+ args.buff, args.index, &result) < 0)
+ handle_error();
+
+if (ei_decode_version(result.buff, &index) < 0
+ || ei_decode_bool(result.buff, &index, &is_alive) < 0)
+ handle_error(); ]]></code>
+
+ <p>For more information about <c>ei_rpc()</c> and its
+ companions <c>ei_rpc_to()</c> and
+ <c>ei_rpc_from()</c>, see the
+ <seecref marker="ei_connect"><c>ei_connect</c></seecref> module.</p>
</section>
<section>
<title>Using Global Names</title>
<p>A C node has access to names registered through the
- <seealso marker="kernel:global"><c>global</c></seealso>
+ <seeerl marker="kernel:global"><c>global</c></seeerl>
module in Kernel. Names can be looked up, allowing the C node to send messages
to named Erlang services. C nodes can also register global names,
allowing them to provide named services to Erlang processes or other C
@@ -590,7 +459,7 @@ char **names;
int count;
int i;
-names = erl_global_names(fd,&count);
+names = ei_global_names(ec,fd,&count);
if (names)
for (i=0; i<count; i++)
@@ -598,8 +467,8 @@ if (names)
free(names); ]]></code>
- <p><seealso marker="erl_global#erl_global_names">
- <c>erl_global:erl_global_names</c></seealso>
+ <p><seecref marker="ei_global#ei_global_names">
+ <c>ei_global_names</c></seecref>
allocates and returns a buffer containing
all the names known to the <c>global</c> module in <c>Kernel</c>.
<c>count</c> is initialized to
@@ -608,7 +477,7 @@ free(names); ]]></code>
<c>count</c> to determine when the last name is reached.</p>
<p>It is the caller's responsibility to free the array.
- <c>erl_global_names</c> allocates the array and all the strings
+ <c>ei_global_names</c> allocates the array and all the strings
using a single call to <c>malloc()</c>, so
<c>free(names)</c> is all that is necessary.</p>
@@ -617,16 +486,18 @@ free(names); ]]></code>
<code type="none"><![CDATA[
ETERM *pid;
char node[256];
+erlang_pid the_pid;
-pid = erl_global_whereis(fd,"schedule",node); ]]></code>
+if (ei_global_whereis(ec,fd,"schedule",&the_pid,node) < 0)
+ fprintf(stderr, "ei_global_whereis error\n"); ]]></code>
<p>If <c>"schedule"</c> is known to the
<c>global</c> module in <c>Kernel</c>, an Erlang pid is
- returned that can be used to send messages to the schedule service.
+ written to the_pid. This pid that can be used to send messages to the schedule service.
Also, <c>node</c> is initialized to contain the name of
the node where the service is registered, so that you can make a
connection to it by simply passing the variable to
- <seealso marker="erl_connect"><c>erl_connect</c></seealso>.</p>
+ <seecref marker="ei_connect"><c>ei_connect</c></seecref>.</p>
<p>Before registering a name, you should already have registered your
port number with <c>epmd</c>. This is not strictly necessary,
@@ -634,39 +505,42 @@ pid = erl_global_whereis(fd,"schedule",node); ]]></code>
neglect to do so, then other nodes wishing to communicate with your
service cannot find or connect to your process.</p>
- <p>Create a pid that Erlang processes can use to communicate with your
+ <p>Create a name that Erlang processes can use to communicate with your
service:</p>
<code type="none"><![CDATA[
-ETERM *pid;
-
-pid = erl_mk_pid(thisnode,14,0,0);
-erl_global_register(fd,servicename,pid); ]]></code>
+ei_global_register(fd,servicename,ei_self(ec)); ]]></code>
<p>After registering the name, use
- <seealso marker="erl_connect#erl_accept">
- <c>erl_connect:erl_accept</c></seealso>
+ <seecref marker="ei_connect#ei_accept">
+ <c>ei_accept</c></seecref>
to wait for incoming connections.</p>
<note>
<p>Remember to free <c>pid</c> later with
- <seealso marker="erl_malloc#erl_free_term">
- <c>erl_malloc:erl_free_term</c></seealso>.</p>
+ <seecref marker="ei#ei_x_free">
+ <c>ei_x_free</c></seecref>.</p>
</note>
<p>To unregister a name:</p>
<code type="none"><![CDATA[
-erl_global_unregister(fd,servicename); ]]></code>
+ei_global_unregister(ec,fd,servicename); ]]></code>
</section>
<section>
<title>Using the Registry</title>
+
+ <note><p>This functionality is deprecated as of OTP 23, and will be
+ removed in OTP 24. Reasonably new <c>gcc</c> compilers will issue
+ deprecation warnings. In order to disable these warnings, define the
+ macro <c>EI_NO_DEPR_WARN</c>.</p></note>
+
<p>This section describes the use of the registry, a simple mechanism
for storing key-value pairs in a C-node, as well as backing them up or
restoring them from an <c>Mnesia</c> table on an Erlang node. For more
detailed information about the individual API functions, see the
- <seealso marker="registry"><c>registry</c></seealso> module.</p>
+ <seecref marker="registry"><c>registry</c></seecref> module.</p>
<p>Keys are strings, that is, <c>NULL</c>-terminated arrays of characters, and
values are arbitrary objects. Although integers and floating point numbers
@@ -753,9 +627,9 @@ ei_reg_close(reg); ]]></code>
<section>
<title>Backing Up the Registry to Mnesia</title>
<p>The contents of a registry can be backed up to
- <seealso marker="mnesia:mnesia"><c>Mnesia</c></seealso> on a "nearby" Erlang
+ <seeerl marker="mnesia:mnesia"><c>Mnesia</c></seeerl> on a "nearby" Erlang
node. You must provide an open connection to the Erlang node
- (see <seealso marker="erl_connect"><c>erl_connect</c></seealso>).
+ (see <seecref marker="ei_connect"><c>ei_connect</c></seecref>).
Also, <c>Mnesia</c> 3.0 or later must be running
on the Erlang node before the backup is initiated:</p>
@@ -817,10 +691,10 @@ ei_reg_restore(fd, reg, "mtab"); ]]></code>
possibly causing it to be missed the next time you make an
<c>Mnesia</c> backup of the registry contents. This can be avoided if
you mark the object as dirty after any such changes with
- <seealso marker="registry#ei_reg_markdirty">
- <c>registry:ei_reg_markdirty</c></seealso>, or pass appropriate flags to
- <seealso marker="registry#ei_reg_dump">
- <c>registry:ei_reg_dump</c></seealso>.</p>
+ <seecref marker="registry#ei_reg_markdirty">
+ <c>ei_reg_markdirty</c></seecref>, or pass appropriate flags to
+ <seecref marker="registry#ei_reg_dump">
+ <c>ei_reg_dump</c></seecref>.</p>
</section>
</section>
</chapter>
diff --git a/lib/erl_interface/doc/src/erl_call.xml b/lib/erl_interface/doc/src/erl_call_cmd.xml
index 73b9b13e4d..04b5ec74bf 100644
--- a/lib/erl_interface/doc/src/erl_call.xml
+++ b/lib/erl_interface/doc/src/erl_call_cmd.xml
@@ -71,13 +71,29 @@
However, <c>start</c> and <c>[]</c> are assumed for unspecified
<c>Fun</c> and <c>Args</c>, respectively.
<c>Args</c> is to be in the same format as for
- <seealso marker="erts:erlang#apply/3">
- <c>erlang:apply/3</c></seealso> in <c>ERTS</c>.</p>
+ <seemfa marker="erts:erlang#apply/3">
+ <c>erlang:apply/3</c></seemfa> in <c>ERTS</c>.</p>
<p>Notice that this flag takes exactly one argument, so quoting
can be necessary to group <c>Mod</c>,
<c>Fun</c>, and <c>Args</c> in a manner
dependent on the behavior of your command shell.</p>
</item>
+ <tag><c>-address [Hostname:]Port</c></tag>
+ <item>
+ <p>(One of <c>-n</c>, <c>-name</c>, <c>-sname</c> or
+ <c>-address</c> is required.) <c>Hostname</c> is the
+ hostname of the machine that is running the peer node that
+ <c>erl_call</c> shall communicate with. The default
+ hostname is the hostname of the local machine. <c>Port</c>
+ is the port number of the node that <c>erl_call</c> shall
+ communicate with. The <c>-address</c> flag cannot be
+ combined with any of the flags <c>-n</c>, <c>-name</c>,
+ <c>-sname</c> or <c>-s</c>.</p>
+ <p>The <c>-address</c> flag is typically useful when one
+ wants to call a node that is running on machine without an
+ accessible <seecom marker="erts:epmd">epmd</seecom>
+ instance.</p>
+ </item>
<tag><c>-c Cookie</c></tag>
<item>
<p>(<em>Optional.</em>) Use this option to specify a certain cookie.
@@ -112,14 +128,16 @@
</item>
<tag><c>-n Node</c></tag>
<item>
- <p>(One of <c>-n, -name, -sname</c> is required.)
+ <p>(One of <c>-n</c>, <c>-name</c>, <c>-sname</c> or
+ <c>-address</c> is required.)
Has the same meaning as <c>-name</c> and can still be
used for backward compatibility reasons.</p>
</item>
<tag><c>-name Node</c></tag>
<item>
- <p>(One of <c>-n, -name, -sname</c> is required.)
- <c>Node</c> is the name of the node to be
+ <p>(One of <c>-n</c>, <c>-name</c>, <c>-sname</c> or
+ <c>-address</c> is required.)
+ <c>Node</c> is the name of the peer node to be
started or communicated with. It is assumed that
<c>Node</c> is started with
<c>erl -name</c>, which means that fully
@@ -137,6 +155,13 @@
<p>(<em>Optional.</em>) Generates a random name of the hidden node
that <c>erl_call</c> represents.</p>
</item>
+ <tag><c>-R</c></tag>
+ <item>
+ <p>(<em>Optional.</em>) Request a dynamic random name, of the hidden node
+ that <c>erl_call</c> represents, from the peer node. Supported
+ since OTP 23. Prefer <c>-R</c> over <c>-r</c> when doing repeated
+ requests toward the same peer node.</p>
+ </item>
<tag><c>-s</c></tag>
<item>
<p>(<em>Optional.</em>) Starts a distributed Erlang node if
@@ -149,14 +174,22 @@
</item>
<tag><c>-sname Node</c></tag>
<item>
- <p>(One of <c>-n, -name, -sname</c> is required.)
- <c>Node</c> is the name of the node to be started
+ <p>(One of <c>-n</c>, <c>-name</c>, <c>-sname</c> or
+ <c>-address</c> is required.)
+ <c>Node</c> is the name of the peer node to be started
or communicated with. It is assumed that <c>Node</c>
is started with <c>erl -sname</c>, which means that
short node names are used. If option <c>-s</c> is
specified, an Erlang node is started (if necessary) with
<c>erl -sname</c>.</p>
</item>
+ <tag><c>-timeout Seconds</c></tag>
+ <item>
+ <p>(<em>Optional.</em>) Aborts the <c>erl_call</c> process after
+ the timeout expires. Note that this does not abort commands that
+ have already been started with <c>-a</c>, <c>-e</c>, or similar.
+ </p>
+ </item>
<tag><c>-v</c></tag>
<item>
<p>(<em>Optional.</em>) Prints a lot of <c>verbose</c>
diff --git a/lib/erl_interface/doc/src/erl_connect.xml b/lib/erl_interface/doc/src/erl_connect.xml
deleted file mode 100644
index ab07535612..0000000000
--- a/lib/erl_interface/doc/src/erl_connect.xml
+++ /dev/null
@@ -1,662 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE cref SYSTEM "cref.dtd">
-
-<cref>
- <header>
- <copyright>
- <year>1996</year><year>2020</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>erl_connect</title>
- <prepared>Torbj&ouml;rn T&ouml;rnkvist</prepared>
- <responsible>Torbj&ouml;rn T&ouml;rnkvist</responsible>
- <docno></docno>
- <approved>Bjarne D&auml;cker</approved>
- <checked>Torbj&ouml;rn T&ouml;rnkvist</checked>
- <date>1998-07-03</date>
- <rev>A</rev>
- <file>erl_connect.xml</file>
- </header>
- <lib>erl_connect</lib>
- <libsummary>Communicate with distributed Erlang.</libsummary>
- <description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
-
- <p>This module provides support for communication between distributed
- Erlang nodes and C-nodes, in a manner that is transparent to Erlang
- processes.</p>
-
- <p>A C-node appears to Erlang as a <em>hidden node</em>.
- That is, Erlang processes that know the name of the
- C-node can communicate with it in a normal manner, but
- the node name does not appear in the listing provided by
- <seealso marker="erts:erlang#nodes/0"><c>erlang:nodes/0</c></seealso>
- in <c>ERTS</c>.</p>
- </description>
-
- <funcs>
- <func>
- <name since=""><ret>int</ret><nametext>erl_accept(listensock, conp)</nametext></name>
- <fsummary>Accept a connection.</fsummary>
- <type>
- <v>int listensock;</v>
- <v>ErlConnect *conp;</v>
- </type>
- <desc>
- <p>This function is used by a server process to accept a
- connection from a client process.</p>
- <list type="bulleted">
- <item><c>listensock</c> is an open socket descriptor on
- which <c>listen()</c> has previously been called.</item>
- <item><c>conp</c> is a pointer to an
- <c>ErlConnect</c> struct, described as follows:</item>
- </list>
- <code type="none"><![CDATA[
-typedef struct {
- char ipadr[4];
- char nodename[MAXNODELEN];
-} ErlConnect;
- ]]></code>
- <p>On success, <c>conp</c> is filled in with the address and
- node name of the connecting client and a file descriptor is
- returned. On failure, <c>ERL_ERROR</c> is returned and
- <c>erl_errno</c> is set to <c>EIO</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_close_connection(fd)</nametext></name>
- <fsummary>Close a connection to an Erlang node.</fsummary>
- <type>
- <v>int fd;</v>
- </type>
- <desc>
- <p>Closes an open connection to an Erlang node.</p>
- <p><c>Fd</c> is a file descriptor obtained from
- <c>erl_connect()</c> or
- <c>erl_xconnect()</c>.</p>
- <p>Returns <c>0</c> on success. If the call fails, a non-zero value
- is returned, and the reason for the error can be obtained with the
- appropriate platform-dependent call.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_connect(node)</nametext></name>
- <name since=""><ret>int</ret><nametext>erl_xconnect(addr, alive)</nametext></name>
- <fsummary>Establish a connection to an Erlang node.</fsummary>
- <type>
- <v>char *node, *alive;</v>
- <v>struct in_addr *addr;</v>
- </type>
- <desc>
- <p>Sets up a connection to an Erlang node.</p>
- <p><c>erl_xconnect()</c> requires the IP address of the
- remote host and the alivename of the remote node to be
- specified. <c>erl_connect()</c> provides an alternative
- interface, and determines the information from the node name
- provided.</p>
- <list type="bulleted">
- <item><c>addr</c> is the 32-bit IP address of the remote
- host.</item>
- <item><c>alive</c> is the alivename of the remote node.
- </item>
- <item><c>node</c> is the name of the remote node.</item>
- </list>
- <p>Returns an open file descriptor on success, otherwise a negative
- value. In the latter case <c>erl_errno</c> is set to one
- of:</p>
- <taglist>
- <tag><c>EHOSTUNREACH</c></tag>
- <item>The remote host <c>node</c> is unreachable.</item>
- <tag><c>ENOMEM</c></tag>
- <item>No more memory is available.</item>
- <tag><c>EIO</c></tag>
- <item>I/O error.</item>
- </taglist>
- <p>Also, <c>errno</c> values from
- <c>socket</c><em>(2)</em> and
- <c>connect</c><em>(2)</em>
- system calls can be propagated into <c>erl_errno</c>.</p>
- <p><em>Example:</em></p>
- <code type="none"><![CDATA[
-#define NODE "madonna@chivas.du.etx.ericsson.se"
-#define ALIVE "madonna"
-#define IP_ADDR "150.236.14.75"
-
-/*** Variant 1 ***/
-erl_connect( NODE );
-
-/*** Variant 2 ***/
-struct in_addr addr;
-addr = inet_addr(IP_ADDR);
-erl_xconnect( &addr , ALIVE );
- ]]></code>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_connect_init(number, cookie, creation)</nametext></name>
- <name since=""><ret>int</ret><nametext>erl_connect_xinit(host, alive, node, addr, cookie, creation)</nametext></name>
- <fsummary>Initialize communication.</fsummary>
- <type>
- <v>int number;</v>
- <v>char *cookie;</v>
- <v>short creation;</v>
- <v>char *host,*alive,*node;</v>
- <v>struct in_addr *addr;</v>
- </type>
- <desc>
- <p>Initializes the <c>erl_connect</c> module.
- In particular, these functions are used to identify the name of the
- C-node from which they are called. One of these functions must
- be called before any of the other functions in the <c>erl_connect</c>
- module are used.</p>
- <p><c>erl_connect_xinit()</c> stores for later use
- information about:</p>
- <list type="bulleted">
- <item>Hostname of the node, <c>host</c></item>
- <item>Alivename, <c>alive</c></item>
- <item>Node name, <c>node</c></item>
- <item>IP address, <c>addr</c></item>
- <item>Cookie, <c>cookie</c></item>
- <item>Creation number, <c>creation</c></item>
- </list>
- <p><c>erl_connect_init()</c>
- provides an alternative interface that does not require as much
- information from the caller. Instead,
- <c>erl_connect_init()</c>
- uses <c>gethostbyname()</c> to obtain default values.</p>
- <p>If you use <c>erl_connect_init()</c>, your node will
- have a short name, that is, it will not be fully qualified. If you
- need to use fully qualified (long) names, use
- <c>erl_connect_xinit()</c> instead.</p>
- <list type="bulleted">
- <item>
- <p><c>host</c> is the name of the host on which the node
- is running.</p>
- </item>
- <item>
- <p><c>alive</c> is the alivename of the node.</p>
- </item>
- <item>
- <p><c>node</c> is the node name. It is to
- be of the form <em>alivename@hostname</em>.</p>
- </item>
- <item>
- <p><c>addr</c> is the 32-bit IP address of
- <c>host</c>.</p>
- </item>
- <item>
- <p><c>cookie</c> is the authorization string required
- for access to the remote node. If <c>NULL</c>, the user
- <c>HOME</c> directory is searched for a cookie file
- <c>.erlang.cookie</c>. The path to
- the home directory is retrieved from environment variable
- <c>HOME</c> on Unix and from the
- <c>HOMEDRIVE</c> and
- <c>HOMEPATH</c> variables on Windows. For more
- details, see the <seealso marker="kernel:auth">
- <c>auth</c></seealso> module in Kernel.</p>
- </item>
- <item>
- <p><c>creation</c> helps identifying a particular
- instance of a C-node. In particular, it can help prevent us from
- receiving messages sent to an earlier process with the same
- registered name.</p>
- </item>
- </list>
- <p>A C-node acting as a server is assigned a creation number
- when it calls <c>erl_publish()</c>.</p>
- <p><c>number</c> is used by
- <c>erl_connect_init()</c> to
- construct the actual node name. In Example 2
- below, <em>"c17@a.DNS.name"</em> is the resulting node name.</p>
- <p><em>Example 1:</em></p>
- <code type="none"><![CDATA[
-struct in_addr addr;
-addr = inet_addr("150.236.14.75");
-if (!erl_connect_xinit("chivas",
- "madonna",
- "madonna@chivas.du.etx.ericsson.se",
- &addr;
- "samplecookiestring..."),
- 0)
- erl_err_quit("<ERROR> when initializing !");
- ]]></code>
- <p><em>Example 2:</em></p>
- <code type="none"><![CDATA[
-if (!erl_connect_init(17, "samplecookiestring...", 0))
- erl_err_quit("<ERROR> when initializing !");
- ]]></code>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_publish(port)</nametext></name>
- <fsummary>Publish a node name.</fsummary>
- <type>
- <v>int port;</v>
- </type>
- <desc>
- <p>This function is used by a server process to register
- with the local name server EPMD, thereby allowing
- other processes to send messages by using the registered name.
- Before calling this function, the process should
- have called <c>bind()</c> and <c>listen()</c>
- on an open socket.</p>
- <p><c>port</c> is the local name to register, and is to be
- the same as the port number that was previously bound to the
- socket.</p>
- <p>To unregister with EPMD, simply close the returned descriptor.</p>
- <p>On success, a descriptor connecting the calling process to EPMD is
- returned. On failure, <c>-1</c> is returned and
- <c>erl_errno</c> is set to:</p>
- <taglist>
- <tag><c>EIO</c></tag>
- <item>I/O error.</item>
- </taglist>
- <p>Also, <c>errno</c> values from
- <c>socket</c><em>(2)</em>
- and <c>connect</c><em>(2)</em> system calls can be
- propagated into <c>erl_errno</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_receive(fd, bufp, bufsize)</nametext></name>
- <fsummary>Receive a message.</fsummary>
- <type>
- <v>int fd;</v>
- <v>char *bufp;</v>
- <v>int bufsize;</v>
- </type>
- <desc>
- <p>Receives a message consisting of a sequence
- of bytes in the Erlang external format.</p>
- <list type="bulleted">
- <item><c>fd</c> is an open descriptor to an Erlang
- connection.</item>
- <item><c>bufp</c> is a buffer large enough to hold the
- expected message.</item>
- <item><c>bufsize</c> indicates the size of
- <c>bufp</c>.</item>
- </list>
- <p>If a <em>tick</em> occurs, that is, the Erlang node on the
- other end of the connection has polled this node to see if it
- is still alive, the function returns <c>ERL_TICK</c> and
- no message is placed in the buffer. Also,
- <c>erl_errno</c> is set to <c>EAGAIN</c>.</p>
- <p>On success, the message is placed in the specified buffer
- and the function returns the number of bytes actually read. On
- failure, the function returns a negative value and sets
- <c>erl_errno</c> to one of:</p>
- <taglist>
- <tag><c>EAGAIN</c></tag>
- <item>Temporary error: Try again.</item>
- <tag><c>EMSGSIZE</c></tag>
- <item>Buffer is too small.</item>
- <tag><c>EIO</c></tag>
- <item>I/O error.</item>
- </taglist>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_receive_msg(fd, bufp, bufsize, emsg)</nametext></name>
- <fsummary>Receive and decode a message.</fsummary>
- <type>
- <v>int fd;</v>
- <v>unsigned char *bufp;</v>
- <v>int bufsize;</v>
- <v>ErlMessage *emsg;</v>
- </type>
- <desc>
- <p>Receives the message into the specified buffer
- and decodes into <c>(ErlMessage *) emsg</c>.</p>
- <list type="bulleted">
- <item><c>fd</c> is an open descriptor to an Erlang
- connection.</item>
- <item><c>bufp</c> is a buffer large enough to hold the
- expected message.</item>
- <item><c>bufsize</c> indicates the size of
- <c>bufp</c>.</item>
- <item>><c>emsg</c> is a pointer to an
- <c>ErlMessage</c> structure
- into which the message will be decoded.
- <c>ErlMessage</c> is defined as follows:</item>
- </list>
- <code type="none"><![CDATA[
-typedef struct {
- int type;
- ETERM *msg;
- ETERM *to;
- ETERM *from;
- char to_name[MAXREGLEN];
-} ErlMessage;
- ]]></code>
- <note>
- <p>The definition of <c>ErlMessage</c> has changed since
- earlier versions of <c>Erl_Interface</c>.</p>
- </note>
- <p><c>type</c> identifies the type of message, one of the
- following:</p>
- <taglist>
- <tag><c>ERL_SEND</c></tag>
- <item>
- <p>An ordinary send operation has occurred and
- <c>emsg->to</c> contains the pid of the recipient.
- The message is in <c>emsg->msg</c>.</p>
- </item>
- <tag><c>ERL_REG_SEND</c></tag>
- <item>
- <p>A registered send operation has occurred and
- <c>emsg->from</c> contains the pid of the sender.
- The message is in <c>emsg->msg</c>.</p>
- </item>
- <tag><c>ERL_LINK</c> or <c>ERL_UNLINK</c>
- </tag>
- <item>
- <p><c>emsg->to</c> and <c>emsg->from</c>
- contain the pids of the sender and recipient of the link or
- unlink. <c>emsg->msg</c> is not used.</p>
- </item>
- <tag><c>ERL_EXIT</c></tag>
- <item>
- <p>A link is broken. <c>emsg->to</c> and
- <c>emsg->from</c> contain the pids of the linked
- processes, and <c>emsg->msg</c> contains the reason
- for the exit.</p>
- </item>
- </taglist>
- <note>
- <p>It is the caller's responsibility to release the
- memory pointed to by <c>emsg->msg</c>,
- <c>emsg->to</c>, and
- <c>emsg->from</c>.</p>
- </note>
- <p>If a <em>tick</em> occurs, that is, the Erlang node on the
- other end of the connection has polled this node to see if it
- is still alive, the function returns <c>ERL_TICK</c>
- indicating that the tick has been received and responded to,
- but no message is placed in the buffer. In this case you
- are to call <c>erl_receive_msg()</c> again.</p>
- <p>On success, the function returns <c>ERL_MSG</c> and the
- <c>Emsg</c> struct is initialized as described above, or
- <c>ERL_TICK</c>, in which case no message is returned. On
- failure, the function returns <c>ERL_ERROR</c> and sets
- <c>erl_errno</c> to one of:</p>
- <taglist>
- <tag><c>EMSGSIZE</c></tag>
- <item>Buffer is too small.</item>
- <tag><c>ENOMEM</c></tag>
- <item>No more memory is available.</item>
- <tag><c>EIO</c></tag>
- <item>I/O error.</item>
- </taglist>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_reg_send(fd, to, msg)</nametext></name>
- <fsummary>Send a message to a registered name.</fsummary>
- <type>
- <v>int fd;</v>
- <v>char *to;</v>
- <v>ETERM *msg;</v>
- </type>
- <desc>
- <p>Sends an Erlang term to a registered process.</p>
- <list type="bulleted">
- <item><c>fd</c> is an open descriptor to an Erlang
- connection.</item>
- <item><c>to</c> is a string containing the registered name
- of the intended recipient of the message.</item>
- <item><c>msg</c> is the Erlang term to be sent.</item>
- </list>
- <p>Returns <c>1</c> on success, otherwise <c>0</c>. In
- the latter case <c>erl_errno</c> is set to one of:</p>
- <taglist>
- <tag><c>ENOMEM</c></tag>
- <item>No more memory is available.</item>
- <tag><c>EIO</c></tag>
- <item>I/O error.</item>
- </taglist>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_rpc(fd, mod, fun, args)</nametext></name>
- <name since=""><ret>int</ret><nametext>erl_rpc_from(fd, timeout, emsg)</nametext></name>
- <name since=""><ret>int</ret><nametext>erl_rpc_to(fd, mod, fun, args)</nametext></name>
- <fsummary>Remote Procedure Call.</fsummary>
- <type>
- <v>int fd, timeout;</v>
- <v>char *mod, *fun;</v>
- <v>ETERM *args;</v>
- <v>ErlMessage *emsg;</v>
- </type>
- <desc>
- <p>Supports calling Erlang functions on remote nodes.
- <c>erl_rpc_to()</c> sends an RPC request to a remote node
- and <c>erl_rpc_from()</c> receives the results of such a
- call. <c>erl_rpc()</c> combines the functionality of
- these two functions by sending an RPC request and waiting for the
- results. See also <seealso marker="kernel:rpc#call/4">
- <c>rpc:call/4</c></seealso> in <c>Kernel</c>.</p>
- <list type="bulleted">
- <item><c>fd</c> is an open descriptor to an Erlang
- connection.</item>
- <item><c>timeout</c> is the maximum time (in milliseconds)
- to wait for
- results. To wait forever, specify <c>ERL_NO_TIMEOUT</c>.
- When <c>erl_rpc()</c> calls <c>erl_rpc_from()</c>, the call will
- never timeout.</item>
- <item><c>mod</c> is the name of the module containing the
- function to be run on the remote node.</item>
- <item><c>fun</c> is the name of the function to run.
- </item>
- <item><c>args</c> is an Erlang list, containing the
- arguments to be passed to the function.</item>
- <item><c>emsg</c> is a message containing the result of
- the function call.</item>
- </list>
- <p>The actual message returned by the RPC server
- is a 2-tuple <c>{rex,Reply}</c>. If you use
- <c>erl_rpc_from()</c> in your code, this is the message
- you will need to parse. If you use <c>erl_rpc()</c>, the
- tuple itself is parsed for you, and the message returned to your
- program is the Erlang term containing <c>Reply</c> only.
- Replies to RPC requests are always <c>ERL_SEND</c> messages.</p>
- <note>
- <p>It is the caller's responsibility to free the returned
- <c>ETERM</c> structure and the memory pointed to by
- <c>emsg->msg</c> and <c>emsg->to</c>.</p>
- </note>
- <p><c>erl_rpc()</c> returns the remote function's return
- value on success, otherwise <c>NULL</c>.</p>
- <p><c>erl_rpc_to()</c> returns <c>0</c> on
- success, otherwise a negative number.</p>
- <p><c>erl_rcp_from()</c> returns <c>ERL_MSG</c>
- on success (with <c>Emsg</c> now
- containing the reply tuple), otherwise one of
- <c>ERL_TICK</c>, <c>ERL_TIMEOUT</c>, or
- <c>ERL_ERROR</c>.</p>
- <p>When failing,
- all three functions set <c>erl_errno</c> to one of:</p>
- <taglist>
- <tag><c>ENOMEM</c></tag>
- <item>No more memory is available.</item>
- <tag><c>EIO</c></tag>
- <item>I/O error.</item>
- <tag><c>ETIMEDOUT</c></tag>
- <item>Timeout has expired.</item>
- <tag><c>EAGAIN</c></tag>
- <item>Temporary error: Try again.</item>
- </taglist>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_send(fd, to, msg)</nametext></name>
- <fsummary>Send a message.</fsummary>
- <type>
- <v>int fd;</v>
- <v>ETERM *to, *msg;</v>
- </type>
- <desc>
- <p>Sends an Erlang term to a process.</p>
- <list type="bulleted">
- <item><c>fd</c> is an open descriptor to an Erlang
- connection.</item>
- <item><c>to</c> is an Erlang term containing the pid of
- the intended recipient of the message.</item>
- <item>><c>msg</c> is the Erlang term to be sent.</item>
- </list>
- <p>Returns <c>1</c> on success, otherwise <c>0</c>. In
- the latter case <c>erl_errno</c> is set to one of:</p>
- <taglist>
- <tag><c>EINVAL</c></tag>
- <item>Invalid argument: <c>to</c> is not a valid Erlang
- pid.</item>
- <tag><c>ENOMEM</c></tag>
- <item>No more memory is available.</item>
- <tag><c>EIO</c></tag>
- <item>I/O error.</item>
- </taglist>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>const char *</ret><nametext>erl_thisalivename()</nametext></name>
- <name since=""><ret>const char *</ret><nametext>erl_thiscookie()</nametext></name>
- <name since=""><ret>short</ret><nametext>erl_thiscreation()</nametext></name>
- <name since=""><ret>const char *</ret><nametext>erl_thishostname()</nametext></name>
- <name since=""><ret>const char *</ret><nametext>erl_thisnodename()</nametext></name>
- <fsummary>Retrieve some values.</fsummary>
- <desc>
- <p>Retrieves information about
- the C-node. These values are initially set with
- <c>erl_connect_init()</c> or
- <c>erl_connect_xinit()</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_unpublish(alive)</nametext></name>
- <fsummary>Forcefully unpublish a node name.</fsummary>
- <type>
- <v>char *alive;</v>
- </type>
- <desc>
- <p>This function can be called by a process to unregister a
- specified node from EPMD on the local host. This is, however, usually
- not allowed, unless EPMD was started with flag
- <c>-relaxed_command_check</c>, which it normally is not.</p>
- <p>To unregister a node you have published, you should instead
- close the descriptor that was returned by
- <c>ei_publish()</c>.</p>
- <warning>
- <p>This function is deprecated and will be removed in a future
- release.</p>
- </warning>
- <p><c>alive</c> is the name of the node to unregister, that
- is, the first component of the node name, without
- <c>@hostname</c>.</p>
- <p>If the node was successfully unregistered from EPMD, <c>0</c> is
- returned, otherwise <c>-1</c> is returned and
- <c>erl_errno</c> is set to <c>EIO</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_xreceive_msg(fd, bufpp, bufsizep, emsg)</nametext></name>
- <fsummary>Receive and decode a message.</fsummary>
- <type>
- <v>int fd;</v>
- <v>unsigned char **bufpp;</v>
- <v>int *bufsizep;</v>
- <v>ErlMessage *emsg;</v>
- </type>
- <desc>
- <p>Similar to <c>erl_receive_msg</c>. The difference is
- that <c>erl_xreceive_msg</c> expects the buffer to
- have been allocated by <c>malloc</c>, and reallocates it
- if the received
- message does not fit into the original buffer. Therefore
- both buffer and buffer length are given as pointers; their values
- can change by the call.</p>
- <p>On success, the function returns <c>ERL_MSG</c> and the
- <c>Emsg</c> struct is initialized as described above, or
- <c>ERL_TICK</c>, in which case no message is returned. On
- failure, the function returns <c>ERL_ERROR</c> and sets
- <c>erl_errno</c> to one of:</p>
- <taglist>
- <tag><c>EMSGSIZE</c></tag>
- <item>Buffer is too small.</item>
- <tag><c>ENOMEM</c></tag>
- <item>No more memory is available.</item>
- <tag><c>EIO</c></tag>
- <item>I/O error.</item>
- </taglist>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyaddr(addr, length, type)</nametext></name>
- <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyaddr_r(addr, length, type, hostp, buffer, buflen, h_errnop)</nametext></name>
- <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyname(name)</nametext></name>
- <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyname_r(name, hostp, buffer, buflen, h_errnop)</nametext></name>
-
- <fsummary>Name lookup functions.</fsummary>
- <type>
- <v>const char *name;</v>
- <v>const char *addr;</v>
- <v>int length;</v>
- <v>int type;</v>
- <v>struct hostent *hostp;</v>
- <v>char *buffer;</v>
- <v>int buflen;</v>
- <v>int *h_errnop;</v>
- </type>
- <desc>
- <p>Convenience functions for some common name lookup functions.</p>
- </desc>
- </func>
- </funcs>
-
- <section>
- <title>Debug Information</title>
- <p>If a connection attempt fails, the following can be checked:</p>
-
- <list type="bulleted">
- <item><c>erl_errno</c></item>
- <item>That the correct cookie was used</item>
- <item>That EPMD is running</item>
- <item>That the remote Erlang node on the other side is running the same
- version of Erlang as the <c>erl_interface</c> library</item>
- </list>
- </section>
-</cref>
diff --git a/lib/erl_interface/doc/src/erl_error.xml b/lib/erl_interface/doc/src/erl_error.xml
deleted file mode 100644
index d2566fae5b..0000000000
--- a/lib/erl_interface/doc/src/erl_error.xml
+++ /dev/null
@@ -1,145 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE cref SYSTEM "cref.dtd">
-
-<cref>
- <header>
- <copyright>
- <year>1996</year><year>2020</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>erl_error</title>
- <prepared>Torbj&ouml;rn T&ouml;rnkvist</prepared>
- <responsible>Torbj&ouml;rn T&ouml;rnkvist</responsible>
- <docno></docno>
- <approved>Bjarne D&auml;cker</approved>
- <checked>Torbj&ouml;rn T&ouml;rnkvist</checked>
- <date>1996-10-14</date>
- <rev>A</rev>
- <file>erl_error.xml</file>
- </header>
- <lib>erl_error</lib>
- <libsummary>Error print routines.</libsummary>
- <description>
- <p>This module contains some error printing routines taken
- from "Advanced Programming in the UNIX Environment"
- by W. Richard Stevens.</p>
-
- <p>These functions are all called in the same manner as
- <c>printf()</c>, that is, with a string containing format
- specifiers followed by a list of corresponding arguments. All output from
- these functions is to <c>stderr</c>.</p>
- </description>
-
- <funcs>
- <func>
- <name since=""><ret>void</ret><nametext>erl_err_msg(FormatStr, ... )</nametext></name>
- <fsummary>Non-fatal error, and not system call error.</fsummary>
- <type>
- <v>const char *FormatStr;</v>
- </type>
- <desc>
- <p>The message provided by the caller is printed. This
- function is simply a wrapper for <c>fprintf()</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_err_quit(FormatStr, ... )</nametext></name>
- <fsummary>Fatal error, but not system call error.</fsummary>
- <type>
- <v>const char *FormatStr;</v>
- </type>
- <desc>
- <p>Use this function when a fatal error has occurred that
- is not because of a system call. The message provided by the
- caller is printed and the process terminates with exit
- value <c>1</c>. This function does not return.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_err_ret(FormatStr, ... )</nametext></name>
- <fsummary>Non-fatal system call error.</fsummary>
- <type>
- <v>const char *FormatStr;</v>
- </type>
- <desc>
- <p>Use this function after a failed system call. The message
- provided by the caller is printed followed by a string
- describing the reason for failure.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_err_sys(FormatStr, ... )</nametext></name>
- <fsummary>Fatal system call error.</fsummary>
- <type>
- <v>const char *FormatStr;</v>
- </type>
- <desc>
- <p>Use this function after a failed system call. The message
- provided by the caller is printed followed by a string
- describing the reason for failure, and the process
- terminates with exit value <c>1</c>. This function does not
- return.</p>
- </desc>
- </func>
- </funcs>
-
- <section>
- <title>Error Reporting</title>
- <p>Most functions in <c>Erl_Interface</c> report failures to the caller by
- returning some otherwise meaningless value (typically
- <c>NULL</c>
- or a negative number). As this only tells you that things did not
- go well, examine the error code in <c>erl_errno</c> if you
- want to find out more about the failure.</p>
- </section>
-
- <funcs>
- <func>
- <name since=""><ret>volatile int</ret><nametext>erl_errno</nametext></name>
- <fsummary>Variable <c>erl_errno</c> contains the
- Erl_Interface error number. You can change the value if you wish.
- </fsummary>
- <desc>
- <p><c>erl_errno</c> is initially (at program startup) zero
- and is then set by many <c>Erl_Interface</c> functions on failure to
- a non-zero error code to indicate what kind of error it
- encountered. A successful function call can change
- <c>erl_errno</c> (by calling some other function that
- fails), but no function does never set it to zero. This means
- that you cannot use <c>erl_errno</c> to see <em>if</em> a
- function call failed. Instead, each function reports failure
- in its own way (usually by returning a negative number or
- <c>NULL</c>), in which case you can examine
- <c>erl_errno</c> for details.</p>
- <p><c>erl_errno</c> uses the error codes defined in your
- system's <c>&lt;errno.h&gt;</c>.</p>
- <note>
- <p><c>erl_errno</c> is a "modifiable lvalue" (just
- like ISO C defines <c>errno</c> to be) rather than a
- variable. This means it can be implemented as a macro
- (expanding to, for example, <c>*_erl_errno()</c>).
- For reasons of thread safety (or task safety), this is exactly what
- we do on most platforms.</p>
- </note>
- </desc>
- </func>
- </funcs>
-</cref>
diff --git a/lib/erl_interface/doc/src/erl_eterm.xml b/lib/erl_interface/doc/src/erl_eterm.xml
deleted file mode 100644
index 9e71060813..0000000000
--- a/lib/erl_interface/doc/src/erl_eterm.xml
+++ /dev/null
@@ -1,776 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE cref SYSTEM "cref.dtd">
-
-<cref>
- <header>
- <copyright>
- <year>1996</year><year>2020</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>erl_eterm</title>
- <prepared>Torbj&ouml;rn T&ouml;rnkvist</prepared>
- <responsible>Torbj&ouml;rn T&ouml;rnkvist</responsible>
- <docno></docno>
- <approved>Bjarne D&auml;cker</approved>
- <checked>Torbj&ouml;rn T&ouml;rnkvist</checked>
- <date>1998-07-03</date>
- <rev>A</rev>
- <file>erl_eterm.xml</file>
- </header>
- <lib>erl_eterm</lib>
- <libsummary>Functions for Erlang term construction.</libsummary>
- <description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
-
- <p>This module provides functions for creating and manipulating
- Erlang terms.</p>
-
- <p>An Erlang term is represented by a C structure of type
- <c>ETERM</c>. Applications should not reference any fields
- in this structure directly, as it can be changed in future releases
- to provide faster and more compact term storage. Instead,
- applications should use the macros and functions provided.</p>
-
- <p>Each of the following macros takes a single <c>ETERM</c> pointer as an
- argument. The macros return a non-zero value if the test is true,
- otherwise <c>0</c>.</p>
-
- <taglist>
- <tag><c>ERL_IS_INTEGER(t)</c></tag>
- <item>True if <c>t</c> is an integer.</item>
- <tag><c>ERL_IS_UNSIGNED_INTEGER(t)</c></tag>
- <item>True if <c>t</c> is an integer.</item>
- <tag><c>ERL_IS_FLOAT(t)</c></tag>
- <item>True if <c>t</c> is a floating point number.</item>
- <tag><c>ERL_IS_ATOM(t)</c></tag>
- <item>True if <c>t</c> is an atom.</item>
- <tag><c>ERL_IS_PID(t)</c></tag>
- <item>True if <c>t</c> is a pid (process identifier).</item>
- <tag><c>ERL_IS_PORT(t)</c></tag>
- <item>True if <c>t</c> is a port.</item>
- <tag><c>ERL_IS_REF(t)</c></tag>
- <item>True if <c>t</c> is a reference.</item>
- <tag><c>ERL_IS_TUPLE(t)</c></tag>
- <item>True if <c>t</c> is a tuple.</item>
- <tag><c>ERL_IS_BINARY(t)</c></tag>
- <item>True if <c>t</c> is a binary.</item>
- <tag><c>ERL_IS_LIST(t)</c></tag>
- <item>True if <c>t</c> is a list with zero or more
- elements.</item>
- <tag><c>ERL_IS_EMPTY_LIST(t)</c></tag>
- <item>True if <c>t</c> is an empty list.</item>
- <tag><c>ERL_IS_CONS(t)</c></tag>
- <item>True if <c>t</c> is a list with at least one
- element.</item>
- </taglist>
-
- <p>The following macros can be used for retrieving parts of Erlang
- terms. None of these do any type checking. Results are undefined
- if you pass an <c>ETERM*</c> containing the wrong type. For example,
- passing a tuple to <c>ERL_ATOM_PTR()</c> likely results in garbage.</p>
-
- <taglist>
- <tag><c>char *ERL_ATOM_PTR(t)</c></tag>
- <item></item>
- <tag><c>char *ERL_ATOM_PTR_UTF8(t)</c></tag>
- <item>A string representing atom <c>t</c>.</item>
- <tag><c>int ERL_ATOM_SIZE(t)</c></tag>
- <item></item>
- <tag><c>int ERL_ATOM_SIZE_UTF8(t)</c></tag>
- <item>The length (in bytes) of atom <c>t</c>.</item>
- <tag><c>void *ERL_BIN_PTR(t)</c></tag>
- <item>A pointer to the contents of <c>t</c>.</item>
- <tag><c>int ERL_BIN_SIZE(t)</c></tag>
- <item>The length (in bytes) of binary object <c>t</c>.</item>
- <tag><c>int ERL_INT_VALUE(t)</c></tag>
- <item>The integer of <c>t</c>.</item>
- <tag><c>unsigned int ERL_INT_UVALUE(t)</c></tag>
- <item>The unsigned integer value of <c>t</c>.</item>
- <tag><c>double ERL_FLOAT_VALUE(t)</c></tag>
- <item>The floating point value of <c>t</c>.</item>
- <tag><c>ETERM *ERL_PID_NODE(t)</c></tag>
- <item></item>
- <tag><c>ETERM *ERL_PID_NODE_UTF8(t)</c></tag>
- <item>The node in pid <c>t</c>.</item>
- <tag><c>int ERL_PID_NUMBER(t)</c></tag>
- <item>The sequence number in pid <c>t</c>.</item>
- <tag><c>int ERL_PID_SERIAL(t)</c></tag>
- <item>The serial number in pid <c>t</c>.</item>
- <tag><c>int ERL_PID_CREATION(t)</c></tag>
- <item>The creation number in pid <c>t</c>.</item>
- <tag><c>int ERL_PORT_NUMBER(t)</c></tag>
- <item>The sequence number in port <c>t</c>.</item>
- <tag><c>int ERL_PORT_CREATION(t)</c></tag>
- <item>The creation number in port <c>t</c>.</item>
- <tag><c>ETERM *ERL_PORT_NODE(t)</c></tag>
- <item></item>
- <tag><c>ETERM *ERL_PORT_NODE_UTF8(t)</c></tag>
- <item>The node in port <c>t</c>.</item>
- <tag><c>int ERL_REF_NUMBER(t)</c></tag>
- <item>The first part of the reference number in ref <c>t</c>.
- Use only for compatibility.</item>
- <tag><c>int ERL_REF_NUMBERS(t)</c></tag>
- <item>Pointer to the array of reference numbers in ref
- <c>t</c>.</item>
- <tag><c>int ERL_REF_LEN(t)</c></tag>
- <item>The number of used reference numbers in ref
- <c>t</c>.</item>
- <tag><c>int ERL_REF_CREATION(t)</c></tag>
- <item>The creation number in ref <c>t</c>.</item>
- <tag><c>int ERL_TUPLE_SIZE(t)</c></tag>
- <item>The number of elements in tuple <c>t</c>.</item>
- <tag><c>ETERM *ERL_CONS_HEAD(t)</c></tag>
- <item>The head element of list <c>t</c>.</item>
- <tag><c>ETERM *ERL_CONS_TAIL(t)</c></tag>
- <item>A list representing the tail elements of list
- <c>t</c>.</item>
- </taglist>
- </description>
-
- <funcs>
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_cons(head, tail)</nametext></name>
- <fsummary>Prepend a term to the head of a list.</fsummary>
- <type>
- <v>ETERM *head;</v>
- <v>ETERM *tail;</v>
- </type>
- <desc>
- <p>Concatenates two Erlang terms, prepending <c>head</c>
- onto <c>tail</c> and thereby creating a
- <c>cons</c> cell.
- To make a proper list, <c>tail</c> is always to be a list
- or an empty list. Notice that <c>NULL</c> is not a valid list.</p>
- <list type="bulleted">
- <item><c>head</c> is the new term to be added.</item>
- <item><c>tail</c> is the existing list to which
- <c>head</c> is concatenated.</item>
- </list>
- <p>The function returns a new list.</p>
- <p><c>ERL_CONS_HEAD(list)</c> and
- <c>ERL_CONS_TAIL(list)</c>
- can be used to retrieve the head and tail components
- from the list. <c>erl_hd(list)</c> and
- <c>erl_tl(list)</c> do
- the same thing, but check that the argument really is a list.</p>
- <p><em>Example:</em></p>
- <code type="none"><![CDATA[
-ETERM *list,*anAtom,*anInt;
-anAtom = erl_mk_atom("madonna");
-anInt = erl_mk_int(21);
-list = erl_mk_empty_list();
-list = erl_cons(anAtom, list);
-list = erl_cons(anInt, list);
- ... /* do some work */
-erl_free_compound(list);
- ]]></code>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_copy_term(term)</nametext></name>
- <fsummary>Create a copy of an Erlang term.</fsummary>
- <type>
- <v>ETERM *term;</v>
- </type>
- <desc>
- <p>Creates and returns a copy of the Erlang term
- <c>term</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_element(position, tuple)</nametext></name>
- <fsummary>Extract an element from an Erlang tuple.</fsummary>
- <type>
- <v>int position;</v>
- <v>ETERM *tuple;</v>
- </type>
- <desc>
- <p>Extracts a specified element from an Erlang tuple.</p>
- <list type="bulleted">
- <item><c>position</c> specifies which element to retrieve
- from <c>tuple</c>. The elements are numbered starting
- from 1.</item>
- <item><c>tuple</c> is an Erlang term containing at least
- <c>position</c> elements.</item>
- </list>
- <p>Returns a new Erlang term corresponding to the requested element, or
- <c>NULL</c> if <c>position</c> was greater
- than the arity of <c>tuple</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_hd(list)</nametext></name>
- <fsummary>Extract the first element from a list.</fsummary>
- <type>
- <v>ETERM *list;</v>
- </type>
- <desc>
- <p>Extracts the first element from a list.</p>
- <p><c>list</c> is an Erlang term containing a list.</p>
- <p>Returns an Erlang term corresponding to the head
- head element in the list, or a <c>NULL</c> pointer if
- <c>list</c> was not a list.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_init(NULL, 0)</nametext></name>
- <fsummary>Initialization routine.</fsummary>
- <type>
- <v>void *NULL;</v>
- <v>int 0;</v>
- </type>
- <desc>
- <p>This function must be called before any of the others in the
- <c>Erl_Interface</c> library to initialize the
- library functions. The arguments must be specified as
- <c>erl_init(NULL,0)</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_iolist_length(list)</nametext></name>
- <fsummary>Return the length of an I/O list.</fsummary>
- <type>
- <v>ETERM *list;</v>
- </type>
- <desc>
- <p>Returns the length of an I/O list.</p>
- <p><c>list</c> is an Erlang term containing an I/O list.</p>
- <p>Returns the length of <c>list</c>, or
- <c>-1</c> if <c>list</c> is not an I/O list.</p>
- <p>For the definition of an I/O list, see
- <seealso marker="#erl_iolist_to_binary">
- <c>erl_iolist_to_binary</c></seealso>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_iolist_to_binary(term)</nametext></name>
- <fsummary>Convert an I/O list to a binary.</fsummary>
- <type>
- <v>ETERM *list;</v>
- </type>
- <desc>
- <p>Converts an I/O list to a binary term.</p>
- <p><c>list</c> is an Erlang term containing a list.</p>
- <p>Returns an Erlang binary term, or <c>NULL</c> if
- <c>list</c> was not an I/O list.</p>
- <p>Informally, an I/O list is a deep list of characters and
- binaries that can be sent to an Erlang port. In BNF, an I/O
- list is formally defined as follows:</p>
- <code type="none"><![CDATA[
-iolist ::= []
- | Binary
- | [iohead | iolist]
- ;
-iohead ::= Binary
- | Byte (integer in the range [0..255])
- | iolist
- ;
- ]]></code>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>char *</ret><nametext>erl_iolist_to_string(list)</nametext></name>
- <fsummary>Convert an I/O list to a <c>NULL</c>-terminated string.</fsummary>
- <type>
- <v>ETERM *list;</v>
- </type>
- <desc>
- <p>Converts an I/O list to a <c>NULL</c>-terminated C string.</p>
- <p><c>list</c> is an Erlang term containing an I/O list.
- The I/O list must not contain the integer 0, as C strings may not
- contain this value except as a terminating marker.</p>
- <p>Returns a pointer to a dynamically allocated
- buffer containing a string. If <c>list</c> is not an I/O
- list, or if <c>list</c> contains the integer 0,
- <c>NULL</c> is returned. It
- is the caller's responsibility to free the allocated buffer
- with <c>erl_free()</c>.</p>
- <p>For the definition of an I/O list, see
- <seealso marker="#erl_iolist_to_binary">
- <c>erl_iolist_to_binary</c></seealso>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_length(list)</nametext></name>
- <fsummary>Determine the length of a list.</fsummary>
- <type>
- <v>ETERM *list;</v>
- </type>
- <desc>
- <p>Determines the length of a proper list.</p>
- <p><c>list</c> is an Erlang term containing a proper list.
- In a proper list, all tails except the last point to another list
- cell, and the last tail points to an empty list.</p>
- <p>Returns <c>-1</c> if <c>list</c> is not a proper
- list.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_atom(string)</nametext></name>
- <fsummary>Create an atom.</fsummary>
- <type>
- <v>const char *string;</v>
- </type>
- <desc>
- <p>Creates an atom.</p>
- <p><c>string</c> is the sequence of characters that will be
- used to create the atom.</p>
- <p>Returns an Erlang term containing an atom. Notice that it is
- the caller's responsibility to ensure that <c>string</c>
- contains a valid name for an atom.</p>
- <p><c>ERL_ATOM_PTR(atom)</c> and
- <c>ERL_ATOM_PTR_UTF8(atom)</c>
- can be used to retrieve the atom name (as a <c>NULL</c>-terminated string).
- <c>ERL_ATOM_SIZE(atom)</c>
- and <c>ERL_ATOM_SIZE_UTF8(atom)</c> return the length
- of the atom name.</p>
- <note>
- <p>The UTF-8 variants were introduced in Erlang/OTP R16 and the
- string returned by <c>ERL_ATOM_PTR(atom)</c> was not
- <c>NULL</c>-terminated on older releases.</p>
- </note>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_binary(bptr, size)</nametext></name>
- <fsummary>Create a binary object.</fsummary>
- <type>
- <v>char *bptr;</v>
- <v>int size;</v>
- </type>
- <desc>
- <p>Produces an Erlang binary object from a
- buffer containing a sequence of bytes.</p>
- <list type="bulleted">
- <item><c>bptr</c> is a pointer to a buffer containing
- data to be converted.</item>
- <item><c>size</c> indicates the length of
- <c>bptr</c>.</item>
- </list>
- <p>Returns an Erlang binary object.</p>
- <p><c>ERL_BIN_PTR(bin)</c> retrieves a pointer to
- the binary data. <c>ERL_BIN_SIZE(bin)</c> retrieves the
- size.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_empty_list()</nametext></name>
- <fsummary>Create an empty Erlang list.</fsummary>
- <desc>
- <p>Creates and returns an empty Erlang list.
- Notice that <c>NULL</c> is not used to represent an empty list;
- Use this function instead.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_estring(string, len)</nametext></name>
- <fsummary>Create an Erlang string.</fsummary>
- <type>
- <v>char *string;</v>
- <v>int len;</v>
- </type>
- <desc>
- <p>Creates a list from a sequence of bytes.</p>
- <list type="bulleted">
- <item><c>string</c> is a buffer containing a sequence of
- bytes. The buffer does not need to be <c>NULL</c>-terminated.</item>
- <item><c>len</c> is the length of
- <c>string</c>.</item>
- </list>
- <p>Returns an Erlang list object corresponding to
- the character sequence in <c>string</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_float(f)</nametext></name>
- <fsummary>Create an Erlang float.</fsummary>
- <type>
- <v>double f;</v>
- </type>
- <desc>
- <p>Creates an Erlang float.</p>
- <p><c>f</c> is a value to be converted to an Erlang
- float.</p>
- <p>Returns an Erlang float object with the value
- specified in <c>f</c> or <c>NULL</c> if
- <c>f</c> is not finite.</p>
- <p><c>ERL_FLOAT_VALUE(t)</c> can be used to retrieve the
- value from an Erlang float.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_int(n)</nametext></name>
- <fsummary>Create an Erlang integer.</fsummary>
- <type>
- <v>int n;</v>
- </type>
- <desc>
- <p>Creates an Erlang integer.</p>
- <p><c>n</c> is a value to be converted to an Erlang
- integer.</p>
- <p>Returns an Erlang integer object with the
- value specified in <c>n</c>.</p>
- <p><c>ERL_INT_VALUE(t)</c> can be used to retrieve the
- value from an Erlang integer.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_list(array, arrsize)</nametext></name>
- <fsummary>Create a list from an array.</fsummary>
- <type>
- <v>ETERM **array;</v>
- <v>int arrsize;</v>
- </type>
- <desc>
- <p>Creates an Erlang list from an array of Erlang terms, such
- that each element in the list corresponds to one element in
- the array.</p>
- <list type="bulleted">
- <item><c>array</c> is an array of Erlang terms.</item>
- <item><c>arrsize</c> is the number of elements in
- <c>array</c>.</item>
- </list>
- <p>The function creates an Erlang list object, whose length
- <c>arrsize</c> and whose elements are taken from the
- terms in <c>array</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_long_ref(node, n1, n2, n3, creation)</nametext></name>
- <fsummary>Create an Erlang reference.</fsummary>
- <type>
- <v>const char *node;</v>
- <v>unsigned int n1, n2, n3;</v>
- <v>unsigned int creation;</v>
- </type>
- <desc>
- <p>Creates an Erlang reference, with 82 bits.</p>
- <list type="bulleted">
- <item><c>node</c> is the name of the C-node.</item>
- <item><c>n1</c>, <c>n2</c>, and
- <c>n3</c> can be seen as one big number
- <c>n1*2^64+n2*2^32+n3</c>, which is to be chosen
- uniquely for each reference created for a given C-node.</item>
- <item><c>creation</c> is an arbitrary number.</item>
- </list>
- <p>Notice that <c>n3</c> and <c>creation</c>
- are limited in precision, so only the low 18 and 2 bits of these
- numbers are used.</p>
- <p>Returns an Erlang reference object.</p>
- <p><c>ERL_REF_NODE(ref)</c>,
- <c>ERL_REF_NUMBERS(ref)</c>,
- <c>ERL_REF_LEN(ref)</c>, and
- <c>ERL_REF_CREATION(ref)</c> can be used to retrieve the
- values used to create the reference.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_pid(node, number, serial, creation)</nametext></name>
- <fsummary>Create a process identifier.</fsummary>
- <type>
- <v>const char *node;</v>
- <v>unsigned int number;</v>
- <v>unsigned int serial;</v>
- <v>unsigned int creation;</v>
- </type>
- <desc>
- <p>Creates an Erlang process identifier (pid). The
- resulting pid can be used by Erlang processes wishing to
- communicate with the C-node.</p>
- <list type="bulleted">
- <item><c>node</c> is the name of the C-node.</item>
- <item><c>number</c>, <c>serial</c>, and
- <c>creation</c> are
- arbitrary numbers. Notice that these are limited in
- precision, so only the low 15, 3, and 2 bits of these numbers
- are used.</item>
- </list>
- <p>Returns an Erlang pid object.</p>
- <p><c>ERL_PID_NODE(pid)</c>,
- <c>ERL_PID_NUMBER(pid)</c>,
- <c>ERL_PID_SERIAL(pid)</c>, and
- <c>ERL_PID_CREATION(pid)</c>
- can be used to retrieve the four values used to create the pid.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_port(node, number, creation)</nametext></name>
- <fsummary>Create a port identifier.</fsummary>
- <type>
- <v>const char *node;</v>
- <v>unsigned int number;</v>
- <v>unsigned int creation;</v>
- </type>
- <desc>
- <p>Creates an Erlang port identifier.</p>
- <list type="bulleted">
- <item><c>node</c> is the name of the C-node.</item>
- <item><c>number</c> and <c>creation</c> are
- arbitrary numbers. Notice that these are limited in
- precision, so only the low 18 and 2 bits of these numbers
- are used.</item>
- </list>
- <p>Returns an Erlang port object.</p>
- <p><c>ERL_PORT_NODE(port)</c>,
- <c>ERL_PORT_NUMBER(port)</c>,
- and <c>ERL_PORT_CREATION</c> can be used to retrieve the
- three values used to create the port.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_ref(node, number, creation)</nametext></name>
- <fsummary>Create an old Erlang reference.</fsummary>
- <type>
- <v>const char *node;</v>
- <v>unsigned int number;</v>
- <v>unsigned int creation;</v>
- </type>
- <desc>
- <p>Creates an old Erlang reference, with
- only 18 bits - use <c>erl_mk_long_ref</c> instead.</p>
- <list type="bulleted">
- <item><c>node</c> is the name of the C-node.</item>
- <item><c>number</c> is to be chosen uniquely for each
- reference created for a given C-node.</item>
- <item><c>creation</c> is an arbitrary number.</item>
- </list>
- <p>Notice that <c>number</c> and <c>creation</c>
- are limited in precision, so only the low 18 and 2 bits of these
- numbers are used.</p>
- <p>Returns an Erlang reference object.</p>
- <p><c>ERL_REF_NODE(ref)</c>,
- <c>ERL_REF_NUMBER(ref)</c>, and
- <c>ERL_REF_CREATION(ref)</c> can be used to retrieve the
- three values used to create the reference.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_string(string)</nametext></name>
- <fsummary>Create a string.</fsummary>
- <type>
- <v>char *string;</v>
- </type>
- <desc>
- <p>Creates a list from a <c>NULL</c>-terminated string.</p>
- <p><c>string</c> is a <c>NULL</c>-terminated sequence of
- characters
- (that is, a C string) from which the list will be created.</p>
- <p>Returns an Erlang list.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_tuple(array, arrsize)</nametext></name>
- <fsummary>Create an Erlang tuple from an array.</fsummary>
- <type>
- <v>ETERM **array;</v>
- <v>int arrsize;</v>
- </type>
- <desc>
- <p>Creates an Erlang tuple from an array of Erlang terms.</p>
- <list type="bulleted">
- <item><c>array</c> is an array of Erlang terms.</item>
- <item><c>arrsize</c> is the number of elements in
- <c>array</c>.</item>
- </list>
- <p>The function creates an Erlang tuple, whose arity is
- <c>size</c> and whose elements are taken from the terms
- in <c>array</c>.</p>
- <p>To retrieve the size of a tuple, either use function
- <c>erl_size</c> (which checks the type of the
- checked term and works for a binary as well as for a tuple) or
- <c>ERL_TUPLE_SIZE(tuple)</c> returns the arity of a tuple.
- <c>erl_size()</c> does the same thing, but it checks
- that the argument is a tuple.
- <c>erl_element(index,tuple)</c> returns the element
- corresponding to a given position in the tuple.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_uint(n)</nametext></name>
- <fsummary>Create an unsigned integer.</fsummary>
- <type>
- <v>unsigned int n;</v>
- </type>
- <desc>
- <p>Creates an Erlang unsigned integer.</p>
- <p><c>n</c> is a value to be converted to an Erlang
- unsigned integer.</p>
- <p>Returns an Erlang unsigned integer object with
- the value specified in <c>n</c>.</p>
- <p><c>ERL_INT_UVALUE(t)</c> can be used to retrieve the
- value from an Erlang unsigned integer.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_mk_var(name)</nametext></name>
- <fsummary>Create an Erlang variable.</fsummary>
- <type>
- <v>char *name;</v>
- </type>
- <desc>
- <p>Creates an unbound Erlang variable. The variable can later be bound
- through pattern matching or assignment.</p>
- <p><c>name</c> specifies a name for the variable.</p>
- <p>Returns an Erlang variable object with the
- name <c>name</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_print_term(stream, term)</nametext></name>
- <fsummary>Print an Erlang term.</fsummary>
- <type>
- <v>FILE *stream;</v>
- <v>ETERM *term;</v>
- </type>
- <desc>
- <p>Prints the specified Erlang term to the specified output stream.</p>
- <list type="bulleted">
- <item><c>stream</c> indicates where the function is to
- send its output.</item>
- <item><c>term</c> is the Erlang term to print.</item>
- </list>
- <p>Returns the number of characters written on success, otherwise a
- negative value.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_set_compat_rel(release_number)</nametext></name>
- <fsummary>Set the Erl_Interface library in compatibility mode.</fsummary>
- <type>
- <v>unsigned release_number;</v>
- </type>
- <desc>
- <p>By default, the <c>Erl_Interface</c> library is only
- guaranteed to be compatible with other Erlang/OTP components from the
- same release as the <c>Erl_Interface</c> library itself.
- For example, <c>Erl_Interface</c> from Erlang/OTP R10
- is not compatible
- with an Erlang emulator from Erlang/OTP R9 by default.</p>
- <p>A call to <c>erl_set_compat_rel(release_number)</c> sets
- the <c>Erl_Interface</c> library in compatibility mode of
- release <c>release_number</c>. Valid range of
- <c>release_number</c>
- is [7, current release]. This makes it possible to
- communicate with Erlang/OTP components from earlier releases.</p>
- <note>
- <p>If this function is called, it may only be called once
- directly after the call to function
- <seealso marker="#erl_init">erl_init()</seealso>.</p>
- </note>
- <warning>
- <p>You may run into trouble if this feature is used
- carelessly. Always ensure that all communicating
- components are either from the same Erlang/OTP release, or
- from release X and release Y where all components
- from release Y are in compatibility mode of release X.</p>
- </warning>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_size(term)</nametext></name>
- <fsummary>Return the arity of a tuple or binary.</fsummary>
- <type>
- <v>ETERM *term;</v>
- </type>
- <desc>
- <p>Returns either the arity of an Erlang tuple or the
- number of bytes in an Erlang binary object.</p>
- <p><c>term</c> is an Erlang tuple or an Erlang binary
- object.</p>
- <p>Returns the size of <c>term</c> as described
- above, or <c>-1</c> if <c>term</c> is not one of the two
- supported types.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_tl(list)</nametext></name>
- <fsummary>Extract the tail from a list.</fsummary>
- <type>
- <v>ETERM *list;</v>
- </type>
- <desc>
- <p>Extracts the tail from a list.</p>
- <p><c>list</c> is an Erlang term containing a list.</p>
- <p>Returns an Erlang list corresponding to the
- original list minus the first element, or <c>NULL</c> pointer if
- <c>list</c> was not a list.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_var_content(term, name)</nametext></name>
- <fsummary>Extract the content of a variable.</fsummary>
- <type>
- <v>ETERM *term;</v>
- <v>char *name;</v>
- </type>
- <desc>
- <p>Returns the contents of the specified variable in an Erlang term.</p>
- <list type="bulleted">
- <item><c>term</c> is an Erlang term. In order for this
- function to succeed,
- <c>term</c> must either be an Erlang variable with
- the specified name, or it must be an Erlang list or tuple
- containing a variable with the specified name. Other Erlang
- types cannot contain variables.</item>
- <item><c>name</c> is the name of an Erlang variable.
- </item>
- </list>
- <p>Returns the Erlang object corresponding to the value of
- <c>name</c> in <c>term</c>. If no variable
- with the name <c>name</c> is found in
- <c>term</c>, or if <c>term</c> is
- not a valid Erlang term, <c>NULL</c> is returned.</p>
- </desc>
- </func>
- </funcs>
-</cref>
diff --git a/lib/erl_interface/doc/src/erl_format.xml b/lib/erl_interface/doc/src/erl_format.xml
deleted file mode 100644
index 65374fd762..0000000000
--- a/lib/erl_interface/doc/src/erl_format.xml
+++ /dev/null
@@ -1,138 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE cref SYSTEM "cref.dtd">
-
-<cref>
- <header>
- <copyright>
- <year>1996</year><year>2020</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>erl_format</title>
- <prepared>Torbj&ouml;rn T&ouml;rnkvist</prepared>
- <responsible>Torbj&ouml;rn T&ouml;rnkvist</responsible>
- <docno></docno>
- <approved>Bjarne D&auml;cker</approved>
- <checked>Torbj&ouml;rn T&ouml;rnkvist</checked>
- <date>1996-10-16</date>
- <rev>A</rev>
- <file>erl_format.xml</file>
- </header>
- <lib>erl_format</lib>
- <libsummary>Create and match Erlang terms.</libsummary>
- <description>
- <p>This module contains two routines: one general function for
- creating Erlang terms and one for pattern matching Erlang terms.</p>
- </description>
-
- <funcs>
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_format(FormatStr, ...)</nametext></name>
- <fsummary>Create an Erlang term.</fsummary>
- <type>
- <v>char *FormatStr;</v>
- </type>
- <desc>
- <p>A general function for creating Erlang terms using
- a format specifier and a corresponding set of arguments, much
- in the way <c>printf()</c> works.</p>
- <p><c>FormatStr</c> is a format specification string.
- The valid format specifiers are as follows:</p>
- <list type="bulleted">
- <item><c>~i</c> - Integer</item>
- <item><c>~f</c> - Floating point</item>
- <item><c>~a</c> - Atom</item>
- <item><c>~s</c> - String</item>
- <item><c>~w</c> - Arbitrary Erlang term</item>
- </list>
- <p>For each format specifier included in <c>FormatStr</c>,
- there must be a corresponding argument following
- <c>FormatStr</c>. An Erlang term is built according to
- <c>FormatStr</c> with values and Erlang terms substituted
- from the corresponding arguments, and according to the individual
- format specifiers. For example:</p>
- <code type="none"><![CDATA[
-erl_format("[{name,~a},{age,~i},{data,~w}]",
- "madonna",
- 21,
- erl_format("[{adr,~s,~i}]","E-street",42));
- ]]></code>
- <p>This creates an <c>(ETERM *)</c> structure corresponding
- to the Erlang term
- <c>[{name,madonna},{age,21},{data,[{adr,"E-street",42}]}]</c></p>
- <p>The function returns an Erlang term, or <c>NULL</c> if
- <c>FormatStr</c> does not describe a valid Erlang
- term.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_match(Pattern, Term)</nametext></name>
- <fsummary>Perform pattern matching.</fsummary>
- <type>
- <v>ETERM *Pattern,*Term;</v>
- </type>
- <desc>
- <p>This function is used to perform pattern matching similar
- to that done in Erlang. For matching rules and more examples, see
- section <seealso marker="doc/reference_manual:patterns">
- Pattern Matching</seealso> in the Erlang Reference Manual.</p>
- <list type="bulleted">
- <item><c>Pattern</c> is an Erlang term, possibly
- containing unbound variables.</item>
- <item><c>Term</c> is an Erlang term that we wish to match
- against <c>Pattern</c>.</item>
- </list>
- <p><c>Term</c> and <c>Pattern</c> are compared
- and any unbound variables in <c>Pattern</c> are bound to
- corresponding values in <c>Term</c>.</p>
- <p>If <c>Term</c> and <c>Pattern</c> can be
- matched, the function returns a non-zero value and binds any unbound
- variables in <c>Pattern</c>. If <c>Term</c>
- and <c>Pattern</c> do
- not match, <c>0</c> is returned. For example:</p>
- <code type="none"><![CDATA[
-ETERM *term, *pattern, *pattern2;
-term1 = erl_format("{14,21}");
-term2 = erl_format("{19,19}");
-pattern1 = erl_format("{A,B}");
-pattern2 = erl_format("{F,F}");
-if (erl_match(pattern1, term1)) {
- /* match succeeds:
- * A gets bound to 14,
- * B gets bound to 21
- */
- ...
-}
-if (erl_match(pattern2, term1)) {
- /* match fails because F cannot be
- * bound to two separate values, 14 and 21
- */
- ...
-}
-if (erl_match(pattern2, term2)) {
- /* match succeeds and F gets bound to 19 */
- ...
-}
- ]]></code>
- <p><c>erl_var_content()</c> can be used to retrieve the
- content of any variables bound as a result of a call to
- <c>erl_match()</c>.</p>
- </desc>
- </func>
- </funcs>
-</cref>
diff --git a/lib/erl_interface/doc/src/erl_interface.xml b/lib/erl_interface/doc/src/erl_interface.xml
deleted file mode 100644
index dd11081138..0000000000
--- a/lib/erl_interface/doc/src/erl_interface.xml
+++ /dev/null
@@ -1,637 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE chapter SYSTEM "chapter.dtd">
-
-<chapter>
- <header>
- <copyright>
- <year>1996</year><year>2020</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>The Erl_Interface Library</title>
- <prepared>Torbj&ouml;rn T&ouml;rnkvist</prepared>
- <responsible>Torbj&ouml;rn T&ouml;rnkvist</responsible>
- <docno></docno>
- <approved>Bjarne D&auml;cker</approved>
- <checked>K.Lundin</checked>
- <date>990113</date>
- <rev>A</rev>
- <file>erl_interface.sgml</file>
- </header>
- <p>The Erl_Interface library contains functions. which help you
- integrate programs written in C and Erlang. The functions in
- Erl_Interface support the following:</p>
- <list type="bulleted">
- <item>manipulation of data represented as Erlang data types</item>
- <item>conversion of data between C and Erlang formats</item>
- <item>encoding and decoding of Erlang data types for transmission or storage</item>
- <item>communication between C nodes and Erlang processes</item>
- <item>backup and restore of C node state to and from Mnesia</item>
- </list>
- <p>In the following sections, these topics are described:</p>
- <list type="bulleted">
- <item>compiling your code for use with Erl_Interface</item>
- <item>initializing Erl_Interface</item>
- <item>encoding, decoding, and sending Erlang terms</item>
- <item>building terms and patterns</item>
- <item>pattern matching</item>
- <item>connecting to a distributed Erlang node</item>
- <item>using EPMD</item>
- <item>sending and receiving Erlang messages</item>
- <item>remote procedure calls</item>
- <item>global names</item>
- <item>the registry</item>
- </list>
-
- <section>
- <title>Deprecation and Removal</title>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
- </section>
-
- <section>
- <title>Compiling and Linking Your Code</title>
- <p>In order to use any of the Erl_Interface functions, include the
- following lines in your code:</p>
- <code type="none"><![CDATA[
-#include "erl_interface.h"
-#include "ei.h" ]]></code>
- <p>Determine where the top directory of your OTP installation is. You
- can find this out by starting Erlang and entering the following
- command at the Eshell prompt:</p>
- <code type="none"><![CDATA[
-Eshell V4.7.4 (abort with ^G)
-1> code:root_dir().
-/usr/local/otp ]]></code>
- <p>To compile your code, make sure that your C compiler knows where
- to find <c>erl_interface.h</c> by specifying an appropriate <c>-I</c>
- argument on the command line, or by adding it to the <c>CFLAGS</c>
- definition in your <c>Makefile</c>. The correct value for this path is
- <c>$OTPROOT/lib/erl_interface</c><em>Vsn</em><c>/include</c>, where <c>$OTPROOT</c> is the path
- reported by <c>code:root_dir/0</c> in the above example, and <em>Vsn</em> is
- the version of the Erl_interface application, for example
- <c>erl_interface-3.2.3</c></p>
- <code type="none"><![CDATA[
-$ cc -c -I/usr/local/otp/lib/erl_interface-3.2.3/include myprog.c ]]></code>
- <p>When linking, you will need to specify the path to
- <c>liberl_interface.a</c> and <c>libei.a</c> with
- <c>-L$OTPROOT/lib/erl_interface-3.2.3/lib</c>, and you will need to specify the
- name of the libraries with <c>-lerl_interface -lei</c>. You can do
- this on the command line or by adding the flags to the <c>LDFLAGS</c>
- definition in your <c>Makefile</c>.</p>
- <code type="none"><![CDATA[
-$ ld -L/usr/local/otp/lib/erl_interface-3.2.3/
- lib myprog.o -lerl_interface -lei -o myprog ]]></code>
- <p>Also, on some systems it may be necessary to link with some
- additional libraries (e.g. <c>libnsl.a</c> and <c>libsocket.a</c> on
- Solaris, or <c>wsock32.lib</c> on Windows) in order to use the
- communication facilities of Erl_Interface.</p>
- <p>If you are using Erl_Interface functions in a threaded
- application based on POSIX threads or Solaris threads, then
- Erl_Interface needs access to some of the synchronization
- facilities in your threads package, and you will need to specify
- additional compiler flags in order to indicate which of the packages
- you are using. Define <c>_REENTRANT</c> and either <c>STHREADS</c> or
- <c>PTHREADS</c>. The default is to use POSIX threads if
- <c>_REENTRANT</c> is specified.</p>
- <p>Note that both single threaded and default versions of the Erl_interface
- and Ei libraries are provided. (The single threaded versions are named
- <c>liberl_interface_st</c> and <c>libei_st</c>). Whether the default
- versions of the libraries have support for threads or not is determined by if
- the platform in question has support for POSIX or Solaris threads. To check this,
- have a look in the <c>eidefs.mk</c> file in the erl_interface src directory.</p>
- </section>
-
- <section>
- <title>Initializing the erl_interface Library</title>
- <p>Before calling any of the other Erl_Interface functions, you
- must call <c>erl_init()</c> exactly once to initialize the library.
- <c>erl_init()</c> takes two arguments, however the arguments are no
- longer used by Erl_Interface, and should therefore be specified
- as <c>erl_init(NULL,0)</c>.</p>
- </section>
-
- <section>
- <title>Encoding, Decoding and Sending Erlang Terms</title>
- <p>Data sent between distributed Erlang nodes is encoded in the
- Erlang external format. Consequently, you have to encode and decode
- Erlang terms into byte streams if you want to use the distribution
- protocol to communicate between a C program and Erlang. </p>
- <p>The Erl_Interface library supports this activity. It has a
- number of C functions which create and manipulate Erlang data
- structures. The library also contains an encode and a decode function.
- The example below shows how to create and encode an Erlang tuple
- <c>{tobbe,3928}</c>:</p>
- <code type="none"><![CDATA[
-
-ETERM *arr[2], *tuple;
-char buf[BUFSIZ];
-int i;
-
-arr[0] = erl_mk_atom("tobbe");
-arr[1] = erl_mk_integer(3928);
-tuple = erl_mk_tuple(arr, 2);
-i = erl_encode(tuple, buf); ]]></code>
- <p>Alternatively, you can use <c>erl_send()</c> and
- <c>erl_receive_msg</c>, which handle the encoding and decoding of
- messages transparently.</p>
- <p>Refer to the Reference Manual for a complete description of the
- following modules:</p>
- <list type="bulleted">
- <item>the <c>erl_eterm</c> module for creating Erlang terms</item>
- <item>the <c>erl_marshal</c> module for encoding and decoding routines.</item>
- </list>
- </section>
-
- <section>
- <title>Building Terms and Patterns</title>
- <p>The previous example can be simplified by using
- <c>erl_format()</c> to create an Erlang term.</p>
- <code type="none"><![CDATA[
-
-ETERM *ep;
-ep = erl_format("{~a,~i}", "tobbe", 3928); ]]></code>
- <p>Refer to the Reference Manual, the <c>erl_format</c> module, for a
- full description of the different format directives. The following
- example is more complex:</p>
- <code type="none"><![CDATA[
-
-ETERM *ep;
-ep = erl_format("[{name,~a},{age,~i},{data,~w}]",
- "madonna",
- 21,
- erl_format("[{adr,~s,~i}]", "E-street", 42));
-erl_free_compound(ep); ]]></code>
- <p>As in previous examples, it is your responsibility to free the
- memory allocated for Erlang terms. In this example,
- <c>erl_free_compound()</c> ensures that the complete term pointed to
- by <c>ep</c> is released. This is necessary, because the pointer from
- the second call to <c>erl_format()</c> is lost. </p>
- <p>The following
- example shows a slightly different solution:</p>
- <code type="none"><![CDATA[
-
-ETERM *ep,*ep2;
-ep2 = erl_format("[{adr,~s,~i}]","E-street",42);
-ep = erl_format("[{name,~a},{age,~i},{data,~w}]",
- "madonna", 21, ep2);
-erl_free_term(ep);
-erl_free_term(ep2); ]]></code>
- <p>In this case, you free the two terms independently. The order in
- which you free the terms <c>ep</c> and <c>ep2</c> is not important,
- because the Erl_Interface library uses reference counting to
- determine when it is safe to actually remove objects. </p>
- <p>If you are not sure whether you have freed the terms properly, you
- can use the following function to see the status of the fixed term
- allocator:</p>
- <code type="none"><![CDATA[
-long allocated, freed;
-
-erl_eterm_statistics(&allocated,&freed);
-printf("currently allocated blocks: %ld\
-",allocated);
-printf("length of freelist: %ld\
-",freed);
-
-/* really free the freelist */
-erl_eterm_release();
- ]]></code>
- <p>Refer to the Reference Manual, the <c>erl_malloc</c> module for more
- information.</p>
- </section>
-
- <section>
- <title>Pattern Matching</title>
- <p>An Erlang pattern is a term that may contain unbound variables or
- <c>"do not care"</c> symbols. Such a pattern can be matched against a
- term and, if the match is successful, any unbound variables in the
- pattern will be bound as a side effect. The content of a bound
- variable can then be retrieved.</p>
- <code type="none"><![CDATA[
-
-ETERM *pattern;
-pattern = erl_format("{madonna,Age,_}"); ]]></code>
- <p><c>erl_match()</c> is used to perform pattern matching. It takes a
- pattern and a term and tries to match them. As a side effect any unbound
- variables in the pattern will be bound. In the following example, we
- create a pattern with a variable <em>Age</em> which appears at two
- positions in the tuple. The pattern match is performed as follows:</p>
- <list type="ordered">
- <item><c>erl_match()</c> will bind the contents of
- <em>Age</em> to <em>21</em> the first time it reaches the variable</item>
- <item>the second occurrence of <em>Age</em> will cause a test for
- equality between the terms since <em>Age</em> is already bound to
- <em>21</em>. Since <em>Age</em> is bound to 21, the equality test will
- succeed and the match continues until the end of the pattern.</item>
- <item>if the end of the pattern is reached, the match succeeds and you
- can retrieve the contents of the variable</item>
- </list>
- <code type="none"><![CDATA[
-ETERM *pattern,*term;
-pattern = erl_format("{madonna,Age,Age}");
-term = erl_format("{madonna,21,21}");
-if (erl_match(pattern, term)) {
- fprintf(stderr, "Yes, they matched: Age = ");
- ep = erl_var_content(pattern, "Age");
- erl_print_term(stderr, ep);
- fprintf(stderr,"\
-");
- erl_free_term(ep);
-}
-erl_free_term(pattern);
-erl_free_term(term); ]]></code>
- <p>Refer to the Reference Manual, the <c>erl_match()</c> function for
- more information.</p>
- </section>
-
- <section>
- <title>Connecting to a Distributed Erlang Node</title>
- <p>In order to connect to a distributed Erlang node you need to first
- initialize the connection routine with <c>erl_connect_init()</c>,
- which stores information such as the host name, node name, and IP
- address for later use:</p>
- <code type="none"><![CDATA[
-int identification_number = 99;
-int creation=1;
-char *cookie="a secret cookie string"; /* An example */
-erl_connect_init(identification_number, cookie, creation); ]]></code>
- <p>Refer to the Reference Manual, the <c>erl_connect</c> module for more information.</p>
- <p>After initialization, you set up the connection to the Erlang node.
- Use <c>erl_connect()</c> to specify the Erlang node you want to
- connect to. The following example sets up the connection and should
- result in a valid socket file descriptor:</p>
- <code type="none"><![CDATA[
-int sockfd;
-char *nodename="xyz@chivas.du.etx.ericsson.se"; /* An example */
-if ((sockfd = erl_connect(nodename)) < 0)
- erl_err_quit("ERROR: erl_connect failed"); ]]></code>
- <p><c>erl_err_quit()</c> prints the specified string and terminates
- the program. Refer to the Reference Manual, the <c>erl_error()</c>
- function for more information.</p>
- </section>
-
- <section>
- <title>Using EPMD</title>
- <p><c>Epmd</c> is the Erlang Port Mapper Daemon. Distributed Erlang nodes
- register with <c>epmd</c> on the localhost to indicate to other nodes that
- they exist and can accept connections. <c>Epmd</c> maintains a register of
- node and port number information, and when a node wishes to connect to
- another node, it first contacts <c>epmd</c> in order to find out the correct
- port number to connect to.</p>
- <p>When you use <c>erl_connect()</c> to connect to an Erlang node, a
- connection is first made to <c>epmd</c> and, if the node is known, a
- connection is then made to the Erlang node.</p>
- <p>C nodes can also register themselves with <c>epmd</c> if they want other
- nodes in the system to be able to find and connect to them.</p>
- <p>Before registering with <c>epmd</c>, you need to first create a listen socket
- and bind it to a port. Then:</p>
- <code type="none"><![CDATA[
-int pub;
-
-pub = erl_publish(port); ]]></code>
- <p><c>pub</c> is a file descriptor now connected to <c>epmd</c>. <c>Epmd</c>
- monitors the other end of the connection, and if it detects that the
- connection has been closed, the node will be unregistered. So, if you
- explicitly close the descriptor or if your node fails, it will be
- unregistered from <c>epmd</c>.</p>
- <p>Be aware that on some systems (such as VxWorks), a failed node will
- not be detected by this mechanism since the operating system does not
- automatically close descriptors that were left open when the node
- failed. If a node has failed in this way, <c>epmd</c> will prevent you from
- registering a new node with the old name, since it thinks that the old
- name is still in use. In this case, you must unregister the name
- explicitly:</p>
- <code type="none"><![CDATA[
-erl_unpublish(node); ]]></code>
- <p>This will cause <c>epmd</c> to close the connection from the far end. Note
- that if the name was in fact still in use by a node, the results of
- this operation are unpredictable. Also, doing this does not cause the
- local end of the connection to close, so resources may be consumed.</p>
- </section>
-
- <section>
- <title>Sending and Receiving Erlang Messages</title>
- <p>Use one of the following two functions to send messages:</p>
- <list type="bulleted">
- <item><c>erl_send()</c></item>
- <item><c>erl_reg_send()</c></item>
- </list>
- <p>As in Erlang, it is possible to send messages to a
- <em>Pid</em> or to a registered name. It is easier to send a
- message to a registered name because it avoids the problem of finding
- a suitable <em>Pid</em>.</p>
- <p>Use one of the following two functions to receive messages:</p>
- <list type="bulleted">
- <item><c>erl_receive()</c></item>
- <item><c>erl_receive_msg()</c></item>
- </list>
- <p><c>erl_receive()</c> receives the message into a buffer, while
- <c>erl_receive_msg()</c> decodes the message into an Erlang term. </p>
-
- <section>
- <title>Example of Sending Messages</title>
- <p>In the following example, <c>{Pid, hello_world}</c> is
- sent to a registered process <c>my_server</c>. The message is encoded
- by <c>erl_send()</c>:</p>
- <code type="none"><![CDATA[
-extern const char *erl_thisnodename(void);
-extern short erl_thiscreation(void);
-#define SELF(fd) erl_mk_pid(erl_thisnodename(),fd,0,erl_thiscreation())
-ETERM *arr[2], *emsg;
-int sockfd, creation=1;
-
-arr[0] = SELF(sockfd);
-arr[1] = erl_mk_atom("Hello world");
-emsg = erl_mk_tuple(arr, 2);
-
-erl_reg_send(sockfd, "my_server", emsg);
-erl_free_term(emsg); ]]></code>
- <p>The first element of the tuple that is sent is your own
- <em>Pid</em>. This enables <c>my_server</c> to reply. Refer to the
- Reference Manual, the <c>erl_connect</c> module for more information
- about send primitives.</p>
- </section>
-
- <section>
- <title>Example of Receiving Messages</title>
- <p>In this example <c>{Pid, Something}</c> is received. The
- received Pid is then used to return <c>{goodbye,Pid}</c></p>
- <code type="none"><![CDATA[
-ETERM *arr[2], *answer;
-int sockfd,rc;
-char buf[BUFSIZE];
-ErlMessage emsg;
-
-if ((rc = erl_receive_msg(sockfd , buf, BUFSIZE, &emsg)) == ERL_MSG) {
- arr[0] = erl_mk_atom("goodbye");
- arr[1] = erl_element(1, emsg.msg);
- answer = erl_mk_tuple(arr, 2);
- erl_send(sockfd, arr[1], answer);
- erl_free_term(answer);
- erl_free_term(emsg.msg);
- erl_free_term(emsg.to);
-}
-} ]]></code>
- <p>In order to provide robustness, a distributed Erlang node
- occasionally polls all its connected neighbours in an attempt to
- detect failed nodes or communication links. A node which receives such
- a message is expected to respond immediately with an <c>ERL_TICK</c> message.
- This is done automatically by <c>erl_receive()</c>, however when this
- has occurred <c>erl_receive</c> returns <c>ERL_TICK</c> to the caller
- without storing a message into the <c>ErlMessage</c> structure.</p>
- <p>When a message has been received, it is the caller's responsibility
- to free the received message <c>emsg.msg</c> as well as <c>emsg.to</c>
- or <c>emsg.from</c>, depending on the type of message received.</p>
- <p>Refer to the Reference Manual for additional information about the
- following modules:</p>
- <list type="bulleted">
- <item><c>erl_connect</c></item>
- <item><c>erl_eterm</c>.</item>
- </list>
- </section>
- </section>
-
- <section>
- <title>Remote Procedure Calls</title>
- <p>An Erlang node acting as a client to another Erlang node
- typically sends a request and waits for a reply. Such a request is
- included in a function call at a remote node and is called a remote
- procedure call. The following example shows how the
- Erl_Interface library supports remote procedure calls:</p>
- <code type="none"><![CDATA[
-
-char modname[]=THE_MODNAME;
-ETERM *reply,*ep;
-ep = erl_format("[~a,[]]", modname);
-if (!(reply = erl_rpc(fd, "c", "c", ep)))
- erl_err_msg("<ERROR> when compiling file: %s.erl !\
-", modname);
-erl_free_term(ep);
-ep = erl_format("{ok,_}");
-if (!erl_match(ep, reply))
- erl_err_msg("<ERROR> compiler errors !\
-");
-erl_free_term(ep);
-erl_free_term(reply); ]]></code>
- <p><c>c:c/1</c> is called to compile the specified module on the
- remote node. <c>erl_match()</c> checks that the compilation was
- successful by testing for the expected <c>ok</c>.</p>
- <p>Refer to the Reference Manual, the <c>erl_connect</c> module for
- more information about <c>erl_rpc()</c>, and its companions
- <c>erl_rpc_to()</c> and <c>erl_rpc_from()</c>.</p>
- </section>
-
- <section>
- <title>Using Global Names</title>
- <p>A C node has access to names registered through the Erlang Global
- module. Names can be looked up, allowing the C node to send messages
- to named Erlang services. C nodes can also register global names,
- allowing them to provide named services to Erlang processes or other C
- nodes. </p>
- <p>Erl_Interface does not provide a native implementation of the global
- service. Instead it uses the global services provided by a "nearby"
- Erlang node. In order to use the services described in this section,
- it is necessary to first open a connection to an Erlang node.</p>
- <p>To see what names there are:</p>
- <code type="none"><![CDATA[
-char **names;
-int count;
-int i;
-
-names = erl_global_names(fd,&count);
-
-if (names)
- for (i=0; i<count; i++)
- printf("%s\
-",names[i]);
-
-free(names); ]]></code>
- <p><c>erl_global_names()</c> allocates and returns a buffer containing
- all the names known to global. <c>count</c> will be initialized to
- indicate how many names are in the array. The array of strings in
- names is terminated by a NULL pointer, so it is not necessary to use
- <c>count</c> to determine when the last name is reached.</p>
- <p>It is the caller's responsibility to free the array.
- <c>erl_global_names()</c> allocates the array and all of the strings
- using a single call to <c>malloc()</c>, so <c>free(names)</c> is all
- that is necessary.</p>
- <p>To look up one of the names:</p>
- <code type="none"><![CDATA[
-ETERM *pid;
-char node[256];
-
-pid = erl_global_whereis(fd,"schedule",node); ]]></code>
- <p>If <c>"schedule"</c> is known to global, an Erlang pid is returned
- that can be used to send messages to the schedule service.
- Additionally, <c>node</c> will be initialized to contain the name of
- the node where the service is registered, so that you can make a
- connection to it by simply passing the variable to <c>erl_connect()</c>.</p>
- <p>Before registering a name, you should already have registered your
- port number with <c>epmd</c>. This is not strictly necessary, but if you
- neglect to do so, then other nodes wishing to communicate with your
- service will be unable to find or connect to your process.</p>
- <p>Create a pid that Erlang processes can use to communicate with your
- service:</p>
- <code type="none"><![CDATA[
-ETERM *pid;
-
-pid = erl_mk_pid(thisnode,14,0,0);
-erl_global_register(fd,servicename,pid); ]]></code>
- <p>After registering the name, you should use <c>erl_accept()</c> to wait for
- incoming connections.</p>
- <p>Do not forget to free <c>pid</c> later with <c>erl_free_term()</c>!</p>
- <p>To unregister a name:</p>
- <code type="none"><![CDATA[
-erl_global_unregister(fd,servicename); ]]></code>
- </section>
-
- <section>
- <title>The Registry</title>
- <p>This section describes the use of the registry, a simple mechanism
- for storing key-value pairs in a C-node, as well as backing them up or
- restoring them from a Mnesia table on an Erlang node. More detailed
- information about the individual API functions can be found in the
- Reference Manual.</p>
- <p>Keys are strings, i.e. <c>NULL</c>-terminated arrays of characters, and values
- are arbitrary objects. Although integers and floating point numbers
- are treated specially by the registry, you can store strings or binary
- objects of any type as pointers.</p>
- <p>To start, you need to open a registry:</p>
- <code type="none"><![CDATA[
-ei_reg *reg;
-
-reg = ei_reg_open(45); ]]></code>
- <p>The number 45 in the example indicates the approximate number of
- objects that you expect to store in the registry. Internally the
- registry uses hash tables with collision chaining, so there is no
- absolute upper limit on the number of objects that the registry can
- contain, but if performance or memory usage are important, then you
- should choose a number accordingly. The registry can be resized later.</p>
- <p>You can open as many registries as you like (if memory permits).</p>
- <p>Objects are stored and retrieved through set and get functions. In
- the following examples you see how to store integers, floats, strings
- and arbitrary binary objects:</p>
- <code type="none"><![CDATA[
-struct bonk *b = malloc(sizeof(*b));
-char *name = malloc(7);
-
-ei_reg_setival(reg,"age",29);
-ei_reg_setfval(reg,"height",1.85);
-
-strcpy(name,"Martin");
-ei_reg_setsval(reg,"name",name);
-
-b->l = 42;
-b->m = 12;
-ei_reg_setpval(reg,"jox",b,sizeof(*b)); ]]></code>
- <p>If you attempt to store an object in the registry and there is an
- existing object with the same key, the new value will replace the old
- one. This is done regardless of whether the new object and the old one
- have the same type, so you can, for example, replace a string with an
- integer. If the existing value is a string or binary, it will be freed
- before the new value is assigned.</p>
- <p>Stored values are retrieved from the registry as follows:</p>
- <code type="none"><![CDATA[
-long i;
-double f;
-char *s;
-struct bonk *b;
-int size;
-
-i = ei_reg_getival(reg,"age");
-f = ei_reg_getfval(reg,"height");
-s = ei_reg_getsval(reg,"name");
-b = ei_reg_getpval(reg,"jox",&size); ]]></code>
- <p>In all of the above examples, the object must exist and it must be of
- the right type for the specified operation. If you do not know the
- type of a given object, you can ask:</p>
- <code type="none"><![CDATA[
-struct ei_reg_stat buf;
-
-ei_reg_stat(reg,"name",&buf); ]]></code>
- <p>Buf will be initialized to contain object attributes.</p>
- <p>Objects can be removed from the registry:</p>
- <code type="none"><![CDATA[
-ei_reg_delete(reg,"name"); ]]></code>
- <p>When you are finished with a registry, close it to remove all the
- objects and free the memory back to the system:</p>
- <code type="none"><![CDATA[
-ei_reg_close(reg); ]]></code>
-
- <section>
- <title>Backing Up the Registry to Mnesia</title>
- <p>The contents of a registry can be backed up to Mnesia on a "nearby"
- Erlang node. You need to provide an open connection to the Erlang node
- (see <c>erl_connect()</c>). Also, Mnesia 3.0 or later must be running
- on the Erlang node before the backup is initiated:</p>
- <code type="none"><![CDATA[
-ei_reg_dump(fd, reg, "mtab", dumpflags); ]]></code>
- <p>The example above will backup the contents of the registry to the
- specified Mnesia table <c>"mtab"</c>. Once a registry has been backed
- up to Mnesia in this manner, additional backups will only affect
- objects that have been modified since the most recent backup, i.e.
- objects that have been created, changed or deleted. The backup
- operation is done as a single atomic transaction, so that the entire
- backup will be performed or none of it will.</p>
- <p>In the same manner, a registry can be restored from a Mnesia table:</p>
- <code type="none"><![CDATA[
-ei_reg_restore(fd, reg, "mtab"); ]]></code>
- <p>This will read the entire contents of <c>"mtab"</c> into the specified
- registry. After the restore, all of the objects in the registry will
- be marked as unmodified, so a subsequent backup will only affect
- objects that you have modified since the restore.</p>
- <p>Note that if you restore to a non-empty registry, objects in the
- table will overwrite objects in the registry with the same keys. Also,
- the <em>entire</em> contents of the registry is marked as unmodified
- after the restore, including any modified objects that were not
- overwritten by the restore operation. This may not be your intention.</p>
- </section>
-
- <section>
- <title>Storing Strings and Binaries</title>
- <p>When string or binary objects are stored in the registry it is
- important that a number of simple guidelines are followed. </p>
- <p>Most importantly, the object must have been created with a single call
- to <c>malloc()</c> (or similar), so that it can later be removed by a
- single call to <c>free()</c>. Objects will be freed by the registry
- when it is closed, or when you assign a new value to an object that
- previously contained a string or binary.</p>
- <p>You should also be aware that if you store binary objects that are
- context-dependent (e.g. containing pointers or open file descriptors),
- they will lose their meaning if they are backed up to a Mnesia table
- and subsequently restored in a different context.</p>
- <p>When you retrieve a stored string or binary value from the registry,
- the registry maintains a pointer to the object and you are passed a
- copy of that pointer. You should never free an object retrieved in
- this manner because when the registry later attempts to free it, a
- runtime error will occur that will likely cause the C-node to crash.</p>
- <p>You are free to modify the contents of an object retrieved this way.
- However when you do so, the registry will not be aware of the changes
- you make, possibly causing it to be missed the next time you make a
- Mnesia backup of the registry contents. This can be avoided if you
- mark the object as dirty after any such changes with
- <c>ei_reg_markdirty()</c>, or pass appropriate flags to
- <c>ei_reg_dump()</c>.</p>
- </section>
- </section>
-</chapter>
diff --git a/lib/erl_interface/doc/src/erl_malloc.xml b/lib/erl_interface/doc/src/erl_malloc.xml
deleted file mode 100644
index 711ec2328b..0000000000
--- a/lib/erl_interface/doc/src/erl_malloc.xml
+++ /dev/null
@@ -1,212 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE cref SYSTEM "cref.dtd">
-
-<cref>
- <header>
- <copyright>
- <year>1996</year><year>2020</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>erl_malloc</title>
- <prepared>Torbj&ouml;rn T&ouml;rnkvist</prepared>
- <responsible>Torbj&ouml;rn T&ouml;rnkvist</responsible>
- <docno></docno>
- <approved>Bjarne D&auml;cker</approved>
- <checked>Torbj&ouml;rn T&ouml;rnkvist</checked>
- <date>1998-07-03</date>
- <rev>A</rev>
- <file>erl_malloc.xml</file>
- </header>
- <lib>erl_malloc</lib>
- <libsummary>Memory allocation functions.</libsummary>
- <description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
- <p>This module provides functions for allocating and deallocating
- memory.</p>
- </description>
-
- <funcs>
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_alloc_eterm(etype)</nametext></name>
- <fsummary>Allocate an ETERM structure.</fsummary>
- <type>
- <v>unsigned char etype;</v>
- </type>
- <desc>
- <p>Allocates an <c>(ETERM)</c> structure.</p>
- <p>Specify <c>etype</c> as one of the following
- constants:</p>
- <list type="bulleted">
- <item><c>ERL_INTEGER</c>
- </item>
- <item><c>ERL_U_INTEGER</c> (unsigned integer)
- </item>
- <item><c>ERL_ATOM</c>
- </item>
- <item><c>ERL_PID</c> (Erlang process identifier)
- </item>
- <item><c>ERL_PORT</c>
- </item>
- <item><c>ERL_REF</c> (Erlang reference)
- </item>
- <item><c>ERL_LIST</c>
- </item>
- <item><c>ERL_EMPTY_LIST</c>
- </item>
- <item><c>ERL_TUPLE</c>
- </item>
- <item><c>ERL_BINARY</c>
- </item>
- <item><c>ERL_FLOAT</c>
- </item>
- <item><c>ERL_VARIABLE</c>
- </item>
- <item><c>ERL_SMALL_BIG</c> (bignum)
- </item>
- <item><c>ERL_U_SMALL_BIG</c> (bignum)
- </item>
- </list>
- <p><c>ERL_SMALL_BIG</c> and
- <c>ERL_U_SMALL_BIG</c> are for
- creating Erlang <c>bignums</c>, which can contain integers
- of any size. The size of an integer in Erlang is machine-dependent,
- but any integer &gt; 2^28 requires a bignum.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_eterm_release(void)</nametext></name>
- <fsummary>Clear the ETERM freelist.</fsummary>
- <desc>
- <p>Clears the freelist, where blocks are placed when they are
- released by <c>erl_free_term()</c> and
- <c>erl_free_compound()</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_eterm_statistics(allocated, freed)</nametext></name>
- <fsummary>Report term allocation statistics.</fsummary>
- <type>
- <v>long *allocated;</v>
- <v>long *freed;</v>
- </type>
- <desc>
- <p>Reports term allocation statistics.</p>
- <p><c>allocated</c> and <c>freed</c> are
- initialized to
- contain information about the fix-allocator used to allocate
- <c>ETERM</c> components.</p>
- <list type="bulleted">
- <item>
- <p><c>allocated</c> is the number of blocks currently
- allocated to <c>ETERM</c> objects.</p>
- </item>
- <item>
- <p><c>freed</c> is the length of the freelist, where
- blocks are placed when they are
- released by <c>erl_free_term()</c> and
- <c>erl_free_compound()</c>.</p>
- </item>
- </list>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_free(ptr)</nametext></name>
- <fsummary>Free some memory.</fsummary>
- <type>
- <v>void *ptr;</v>
- </type>
- <desc>
- <p>Calls the standard
- <c>free()</c> function.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_free_array(array, size)</nametext></name>
- <fsummary>Free an array of ETERM structures.</fsummary>
- <type>
- <v>ETERM **array;</v>
- <v>int size;</v>
- </type>
- <desc>
- <p>Frees an array of Erlang terms.</p>
- <list type="bulleted">
- <item><c>array</c> is an array of ETERM* objects.</item>
- <item><c>size</c> is the number of terms in the array.
- </item>
- </list>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_free_compound(t)</nametext></name>
- <fsummary>Free an array of ETERM structures.</fsummary>
- <type>
- <v>ETERM *t;</v>
- </type>
- <desc>
- <p>Normally it is the programmer's responsibility to free each
- Erlang term that has been returned from any of the
- <c>Erl_Interface</c> functions. However, as many of the
- functions that build new Erlang terms in fact share objects
- with other existing terms, it can be difficult for the
- programmer to maintain pointers to all such terms to
- free them individually.</p>
- <p><c>erl_free_compound()</c> recursively frees all of the
- subterms associated with a specified Erlang term, regardless of
- whether we are still holding pointers to the subterms.</p>
- <p>For an example, see section
- <seealso marker="ei_users_guide#building_terms_and_patterns">Building Terms and Patterns</seealso>
- in the User's Guide.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_free_term(t)</nametext></name>
- <fsummary>Free an ETERM structure.</fsummary>
- <type>
- <v>ETERM *t;</v>
- </type>
- <desc>
- <p>Frees an Erlang term.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>void</ret><nametext>erl_malloc(size)</nametext></name>
- <fsummary>Allocate some memory.</fsummary>
- <type>
- <v>long size;</v>
- </type>
- <desc>
- <p>Calls the standard
- <c>malloc()</c> function.</p>
- </desc>
- </func>
- </funcs>
-</cref>
diff --git a/lib/erl_interface/doc/src/erl_marshal.xml b/lib/erl_interface/doc/src/erl_marshal.xml
deleted file mode 100644
index 02c95fcf94..0000000000
--- a/lib/erl_interface/doc/src/erl_marshal.xml
+++ /dev/null
@@ -1,276 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE cref SYSTEM "cref.dtd">
-
-<cref>
- <header>
- <copyright>
- <year>1996</year><year>2020</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>erl_marshal</title>
- <prepared>Torbj&ouml;rn T&ouml;rnkvist</prepared>
- <responsible>Torbj&ouml;rn T&ouml;rnkvist</responsible>
- <docno></docno>
- <approved>Bjarne D&auml;cker</approved>
- <checked>Torbj&ouml;rn T&ouml;rnkvist</checked>
- <date>1998-07-03</date>
- <rev>A</rev>
- <file>erl_marshal.xml</file>
- </header>
- <lib>erl_marshal</lib>
- <libsummary>Encoding and decoding of Erlang terms.</libsummary>
- <description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
- <p>This module contains functions for encoding Erlang terms into
- a sequence of bytes, and for decoding Erlang terms from a
- sequence of bytes.</p>
- </description>
-
- <funcs>
- <func>
- <name since=""><ret>int</ret><nametext>erl_compare_ext(bufp1, bufp2)</nametext></name>
- <fsummary>Compare encoded byte sequences.</fsummary>
- <type>
- <v>unsigned char *bufp1,*bufp2;</v>
- </type>
- <desc>
- <p>Compares two encoded terms.</p>
- <list type="bulleted">
- <item><c>bufp1</c> is a buffer containing an encoded
- Erlang term term1.</item>
- <item><c>bufp2</c> is a buffer containing an encoded
- Erlang term term2.</item>
- </list>
- <p>Returns <c>0</c> if the terms are equal, <c>-1</c> if
- <c>term1</c> &lt; <c>term2</c>, or <c>1</c> if <c>term2</c> &lt;
- <c>term1</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>ETERM *</ret><nametext>erl_decode(bufp)</nametext></name>
- <name since=""><ret>ETERM *</ret><nametext>erl_decode_buf(bufpp)</nametext></name>
- <fsummary>Convert a term from Erlang external format.</fsummary>
- <type>
- <v>unsigned char *bufp;</v>
- <v>unsigned char **bufpp;</v>
- </type>
- <desc>
- <p><c>erl_decode()</c> and
- <c>erl_decode_buf()</c> decode
- the contents of a buffer and return the corresponding
- Erlang term. <c>erl_decode_buf()</c> provides a simple
- mechanism for dealing with several encoded terms stored
- consecutively in the buffer.</p>
- <list type="bulleted">
- <item>
- <p><c>bufp</c> is a pointer to a buffer containing one
- or more encoded Erlang terms.</p>
- </item>
- <item>
- <p><c>bufpp</c> is the address of a buffer pointer. The
- buffer contains one or more consecutively encoded Erlang terms.
- Following a successful call to
- <c>erl_decode_buf()</c>, <c>bufpp</c> is
- updated so that it points to the next encoded term.</p>
- </item>
- </list>
- <p><c>erl_decode()</c> returns an Erlang term
- corresponding to the contents of <c>bufp</c> on success,
- otherwise <c>NULL</c>. <c>erl_decode_buf()</c>
- returns an Erlang
- term corresponding to the first of the consecutive terms in
- <c>bufpp</c> and moves <c>bufpp</c> forward
- to point to the
- next term in the buffer. On failure, each of the functions
- return <c>NULL</c>.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_encode(term, bufp)</nametext></name>
- <name since=""><ret>int</ret><nametext>erl_encode_buf(term, bufpp)</nametext></name>
- <fsummary>Convert a term into Erlang external format.</fsummary>
- <type>
- <v>ETERM *term;</v>
- <v>unsigned char *bufp;</v>
- <v>unsigned char **bufpp;</v>
- </type>
- <desc>
- <p><c>erl_encode()</c> and
- <c>erl_encode_buf()</c> encode
- Erlang terms into external format for storage or transmission.
- <c>erl_encode_buf()</c> provides a simple mechanism for
- encoding several terms consecutively in the same buffer.</p>
- <list type="bulleted">
- <item>
- <p><c>term</c> is an Erlang term to be encoded.</p>
- </item>
- <item>
- <p><c>bufp</c> is a pointer to a buffer containing one or
- more encoded Erlang terms.</p>
- </item>
- <item>
- <p><c>bufpp</c> is a pointer to a pointer to a buffer
- containing one or more consecutively encoded Erlang terms.
- Following a successful call to
- <c>erl_encode_buf()</c>, <c>bufpp</c> is updated so
- that it points to the
- position for the next encoded term.</p>
- </item>
- </list>
- <p>These functions return the number of bytes written to buffer
- on success, otherwise <c>0</c>.</p>
- <p>Notice that no bounds checking is done on the buffer. It is
- the caller's responsibility to ensure that the buffer is
- large enough to hold the encoded terms. You can either use a
- static buffer that is large enough to hold the terms you expect
- to need in your program, or use <c>erl_term_len()</c>
- to determine the exact requirements for a given term.</p>
- <p>The following can help you estimate the buffer
- requirements for a term. Notice that this information is
- implementation-specific, and can change in future versions.
- If you are unsure, use <c>erl_term_len()</c>.</p>
- <p>Erlang terms are encoded with a 1 byte tag that
- identifies the type of object, a 2- or 4-byte length field,
- and then the data itself. Specifically:</p>
- <taglist>
- <tag><c>Tuples</c></tag>
- <item>Need 5 bytes, plus the space for each element.</item>
- <tag><c>Lists</c></tag>
- <item>Need 5 bytes, plus the space for each element, and 1
- more byte for the empty list at the end.</item>
- <tag><c>Strings and atoms</c></tag>
- <item>Need 3 bytes, plus 1 byte for each character (the
- terminating 0 is not encoded). Really long strings (more
- than 64k characters) are encoded as lists. Atoms cannot
- contain more than 256 characters.</item>
- <tag><c>Integers</c></tag>
- <item>Need 5 bytes.</item>
- <tag><c>Characters</c></tag>
- <item>(Integers &lt; 256) need 2 bytes.</item>
- <tag><c>Floating point numbers</c></tag>
- <item>Need 32 bytes.</item>
- <tag><c>Pids</c></tag>
- <item>Need 10 bytes, plus the space for the node name, which
- is an atom.</item>
- <tag><c>Ports and Refs</c></tag>
- <item>Need 6 bytes, plus the space for the node name, which
- is an atom.</item>
- </taglist>
- <p>The total space required is the result calculated
- from the information above, plus 1 more byte for a
- version identifier.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_ext_size(bufp)</nametext></name>
- <fsummary>Count elements in encoded term.</fsummary>
- <type>
- <v>unsigned char *bufp;</v>
- </type>
- <desc>
- <p>Returns the number of elements in an encoded term.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>unsigned char</ret><nametext>erl_ext_type(bufp)</nametext></name>
- <fsummary>Determine type of an encoded byte sequence.</fsummary>
- <type>
- <v>unsigned char *bufp;</v>
- </type>
- <desc>
- <p>Identifies and returns the type of Erlang term encoded
- in a buffer. It skips a trailing <em>magic</em> identifier.</p>
- <p>Returns <c>0</c> if the type cannot be determined or
- one of:</p>
- <list type="bulleted">
- <item><c>ERL_INTEGER</c>
- </item>
- <item><c>ERL_ATOM</c>
- </item>
- <item><c>ERL_PID</c> (Erlang process identifier)
- </item>
- <item><c>ERL_PORT</c>
- </item>
- <item><c>ERL_REF</c> (Erlang reference)
- </item>
- <item><c>ERL_EMPTY_LIST</c>
- </item>
- <item><c>ERL_LIST</c>
- </item>
- <item><c>ERL_TUPLE</c>
- </item>
- <item><c>ERL_FLOAT</c>
- </item>
- <item><c>ERL_BINARY</c>
- </item>
- <item><c>ERL_FUNCTION</c>
- </item>
- </list>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>unsigned char *</ret><nametext>erl_peek_ext(bufp, pos)</nametext></name>
- <fsummary>Step over encoded term.</fsummary>
- <type>
- <v>unsigned char *bufp;</v>
- <v>int pos;</v>
- </type>
- <desc>
- <p>This function is used for stepping over one or more
- encoded terms in a buffer, to directly access later term.</p>
- <list type="bulleted">
- <item><c>bufp</c> is a pointer to a buffer containing one
- or more encoded Erlang terms.</item>
- <item><c>pos</c> indicates how many terms to step over in
- the buffer.</item>
- </list>
- <p>Returns a pointer to a subterm that can be
- used in a later call to <c>erl_decode()</c> to retrieve
- the term at that position. If there is no term, or
- <c>pos</c> would exceed the size of the terms in the
- buffer, <c>NULL</c> is returned.</p>
- </desc>
- </func>
-
- <func>
- <name since=""><ret>int</ret><nametext>erl_term_len(t)</nametext></name>
- <fsummary>Determine encoded size of term.</fsummary>
- <type>
- <v>ETERM *t;</v>
- </type>
- <desc>
- <p>Determines the buffer space that would be
- needed by <c>t</c> if it were encoded into Erlang external
- format by <c>erl_encode()</c>.</p>
- <p>Returns the size in bytes.</p>
- </desc>
- </func>
- </funcs>
-</cref>
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index c1782320b2..fde35537ba 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -31,6 +31,283 @@
</header>
<p>This document describes the changes made to the Erl_interface application.</p>
+<section><title>Erl_Interface 4.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Integers outside of the range [-(1 bsl 32) - 1, (1 bsl
+ 32) -1] were previously intended to be printed in an
+ internal bignum format by <c>ei_print_term()</c> and
+ <c>ei_s_print_term()</c>. Unfortunately the
+ implementation has been buggy since OTP R13B02 and since
+ then produced results with random content which also
+ could crash the calling program.</p>
+ <p>
+ This fix replaces the printing of the internal format
+ with printing in hexadecimal form and extend the range
+ for printing in decimal form. Currently integers in the
+ range [-(1 bsl 64), (1 bsl 64)] are printed in decimal
+ form and integers outside of this range in Erlang
+ hexadecimal form.</p>
+ <p>
+ Own Id: OTP-17099 Aux Id: ERIERL-585 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ The <c>ei</c> API for decoding/encoding terms is not
+ fully 64-bit compatible since terms that have a
+ representation on the external term format larger than 2
+ GB cannot be handled.</p>
+ <p>
+ Own Id: OTP-16607 Aux Id: OTP-16608 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Interface 4.0.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix erl_interface on windows to be compiled with correct
+ flags to make internal primitives reentrant.</p>
+ <p>
+ Own Id: OTP-16740</p>
+ </item>
+ <item>
+ <p>
+ Fixed <c>ei_get_type</c> to set <c>*size</c> to zero for
+ floats, pids, port and refs according to documentation.</p>
+ <p>
+ Own Id: OTP-16753 Aux Id: ERL-1288, PR-2678 </p>
+ </item>
+ <item>
+ <p>
+ Fix ei_connect when using a dynamic node name to force
+ usage of distribution version 6.</p>
+ <p>
+ This bug caused <c>erl_call -R -address</c> to not work
+ properly.</p>
+ <p>
+ Own Id: OTP-16786</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Changes in order to build on the Haiku operating system.</p>
+ <p>
+ Thanks to Calvin Buckley</p>
+ <p>
+ Own Id: OTP-16707 Aux Id: PR-2638 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ The <c>ei</c> API for decoding/encoding terms is not
+ fully 64-bit compatible since terms that have a
+ representation on the external term format larger than 2
+ GB cannot be handled.</p>
+ <p>
+ Own Id: OTP-16607 Aux Id: OTP-16608 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Interface 4.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix various compiler warnings on 64-bit Windows.</p>
+ <p>
+ Own Id: OTP-15800</p>
+ </item>
+ <item>
+ <p><c>erl_call</c> will now work properly on systems that
+ cannot resolve their own hostname.</p>
+ <p>
+ Own Id: OTP-16604</p>
+ </item>
+ <item>
+ <p>Various bug fixes:</p> <list> <item>Internal error
+ checking in various functions.</item> <item><seecref
+ marker="ei_connect#ei_rpc"><c>ei_rpc()</c></seecref>
+ accepted any 2-tuple message as an rpc response.</item>
+ <item><seecref
+ marker="ei#ei_decode_ref"><c>ei_decode_ref()</c></seecref>
+ now refuse to write outside of allocated memory in case a
+ huge reference is decoded.</item> <item><seecref
+ marker="ei#ei_decode_ei_term"><c>ei_decode_ei_term()</c></seecref>
+ now reports the same term types as <seecref
+ marker="ei#ei_get_type"><c>ei_get_type()</c></seecref>.</item>
+ </list>
+ <p>
+ Own Id: OTP-16623</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>A client node can receive its node name dynamically
+ from the node that it first connects to. This featured
+ can by used by</p> <list> <item><p>starting with <c>erl
+ -sname undefined</c></p></item> <item><p>erl_interface
+ functions <c>ei_connect_init</c> and friends</p></item>
+ <item><p><c>erl_call -R</c></p></item> </list>
+ <p>
+ Own Id: OTP-13812</p>
+ </item>
+ <item>
+ <p>
+ Increased size of node incarnation numbers (aka
+ "creation"), from 2 bits to 32 bits. This will reduce the
+ risk of pids/ports/refs, from different node incarnation
+ with the same name, being mixed up.</p>
+ <p>
+ Own Id: OTP-15603</p>
+ </item>
+ <item>
+ <p>
+ Fix various build issues when compiling Erlang/OTP to the
+ IBM AIX platform.</p>
+ <p>
+ Own Id: OTP-15866 Aux Id: PR-2110 </p>
+ </item>
+ <item>
+ <p>
+ Improved node connection setup handshake protocol. Made
+ possible to agree on protocol version without dependence
+ on <c>epmd</c> or other prior knowledge of peer node
+ version. Also added exchange of node incarnation
+ ("creation") values and expanded the distribution
+ capability flag field from 32 to 64 bits.</p>
+ <p>
+ Own Id: OTP-16229</p>
+ </item>
+ <item>
+ <p>
+ New <c>erl_call</c> option <c>-address [Host]:Port</c> to
+ connect directly to a node without being dependent on
+ <c>epmd</c> to resolve the node name.</p>
+ <p>
+ Own Id: OTP-16251</p>
+ </item>
+ <item>
+ <p>
+ As announced in OTP 22.0, the deprecated parts of
+ <c>erl_interface</c> have now been removed (essentially
+ all C functions with prefix <c>erl_</c>).</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16328</p>
+ </item>
+ <item>
+ <p>
+ As announced in OTP 22.0, the previously existing limited
+ support for VxWorks has now been removed.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16329 Aux Id: OTP-15621 </p>
+ </item>
+ <item>
+ <p>
+ New function <c>ei_connect_host_port</c> and friends to
+ allow node connection without being dependent on
+ <c>epmd</c> for node name resolution.</p>
+ <p>
+ Own Id: OTP-16496 Aux Id: OTP-16251 </p>
+ </item>
+ <item>
+ <p>A number of new functions have been added to the
+ <c>erl_interface</c> API:</p> <list> <item><seecref
+ marker="erl_interface:ei#ei_cmp_pids"><c>ei_cmp_pids()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei#ei_cmp_ports"><c>ei_cmp_ports()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei#ei_cmp_refs"><c>ei_cmp_refs()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei_connect#ei_make_pid"><c>ei_make_pid()</c></seecref></item>
+ <item><seecref
+ marker="erl_interface:ei_connect#ei_make_ref"><c>ei_make_ref()</c></seecref></item>
+ </list>
+ <p>
+ Own Id: OTP-16594</p>
+ </item>
+ <item>
+ <p>Added a <c>-timeout</c> option to <c>erl_call</c>.</p>
+ <p>
+ Own Id: OTP-16624</p>
+ </item>
+ <item>
+ <p>The <c>erl_interface</c> <seecref
+ marker="erl_interface:registry"><c>registry</c></seecref>
+ functionality is deprecated as of OTP 23, and will be
+ removed in OTP 24. Reasonably new <c>gcc</c> compilers
+ will issue deprecation warnings when using this
+ functionality. In order to disable these warnings, define
+ the macro <c>EI_NO_DEPR_WARN</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16630</p>
+ </item>
+ <item>
+ <p>
+ Documentation improvements.</p>
+ <p>
+ Own Id: OTP-16633</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ The <c>ei</c> API for decoding/encoding terms is not
+ fully 64-bit compatible since terms that have a
+ representation on the external term format larger than 2
+ GB cannot be handled.</p>
+ <p>
+ Own Id: OTP-16607 Aux Id: OTP-16608 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erl_Interface 3.13.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -176,6 +453,39 @@
</section>
+<section><title>Erl_Interface 3.11.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix link error "multiple definition of
+ `ei_default_socket_callbacks'" for gcc version 10 or when
+ built with gcc option -fno-common. Error exists since
+ OTP-21.3.</p>
+ <p>
+ Own Id: OTP-16412 Aux Id: PR-2503 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ The <c>ei</c> API for decoding/encoding terms is not
+ fully 64-bit compatible since terms that have a
+ representation on the external term format larger than 2
+ GB cannot be handled.</p>
+ <p>
+ Own Id: OTP-16607 Aux Id: OTP-16608 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erl_Interface 3.11.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -284,9 +594,9 @@
<list>
<item>
<p>
- Support for plugin of a <seealso
+ Support for plugin of a <seecref
marker="ei_connect#ussi">user supplied socket
- implementation</seealso> has been added.</p>
+ implementation</seecref> has been added.</p>
<p>
Own Id: OTP-15442 Aux Id: ERIERL-258 </p>
</item>
@@ -481,8 +791,8 @@
by <c>term_to_binary()</c> using latin1 encoding. Note
that all atoms will by default be encoded using utf8 in a
future Erlang/OTP release. For more information see the
- documentation of <seealso
- marker="erts:erlang#term_to_binary/2"><c>erlang:term_to_binary/2</c></seealso>.</p>
+ documentation of <seemfa
+ marker="erts:erlang#term_to_binary/2"><c>erlang:term_to_binary/2</c></seemfa>.</p>
<p>
Own Id: OTP-14337</p>
</item>
diff --git a/lib/erl_interface/doc/src/ref_man.xml b/lib/erl_interface/doc/src/ref_man.xml
index 6fe1a1e10c..064a83a4ba 100644
--- a/lib/erl_interface/doc/src/ref_man.xml
+++ b/lib/erl_interface/doc/src/ref_man.xml
@@ -29,24 +29,10 @@
<file>ref_man.xml</file>
</header>
<description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
</description>
<xi:include href="ei.xml"/>
<xi:include href="ei_connect.xml"/>
<xi:include href="registry.xml"/>
- <xi:include href="erl_connect.xml"/>
- <xi:include href="erl_error.xml"/>
- <xi:include href="erl_eterm.xml"/>
- <xi:include href="erl_format.xml"/>
- <xi:include href="erl_global.xml"/>
- <xi:include href="erl_malloc.xml"/>
- <xi:include href="erl_marshal.xml"/>
- <xi:include href="erl_call.xml"/>
+ <xi:include href="ei_global.xml"/>
+ <xi:include href="erl_call_cmd.xml"/>
</application>
diff --git a/lib/erl_interface/doc/src/ref_man_ei.xml b/lib/erl_interface/doc/src/ref_man_ei.xml
deleted file mode 100644
index d0788cf419..0000000000
--- a/lib/erl_interface/doc/src/ref_man_ei.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE application SYSTEM "application.dtd">
-
-<application xmlns:xi="http://www.w3.org/2001/XInclude">
- <header>
- <copyright>
- <year>2002</year><year>2020</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>EI Library Reference</title>
- <prepared>Gordon Beaton</prepared>
- <docno></docno>
- <date>1998-11-30</date>
- <rev>1.2</rev>
- <file>ref_man_ei.xml</file>
- </header>
- <description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
- <note>
- <p>By default, the <c>ei</c> library is only guaranteed
- to be compatible with other Erlang/OTP components from the same
- release as the <c>ei</c> library itself. See the documentation of the
- <seealso marker="ei#ei_set_compat_rel">ei_set_compat_rel()</seealso>
- function on how to communicate with Erlang/OTP components from earlier
- releases.</p>
- </note>
- </description>
- <xi:include href="ei.xml"/>
- <xi:include href="ei_connect.xml"/>
- <xi:include href="registry.xml"/>
-</application>
diff --git a/lib/erl_interface/doc/src/ref_man_erl_interface.xml b/lib/erl_interface/doc/src/ref_man_erl_interface.xml
deleted file mode 100644
index 855c9cdac2..0000000000
--- a/lib/erl_interface/doc/src/ref_man_erl_interface.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE application SYSTEM "application.dtd">
-
-<application xmlns:xi="http://www.w3.org/2001/XInclude">
- <header>
- <copyright>
- <year>1996</year><year>2020</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>Erl_Interface Library Reference</title>
- <prepared>Gordon Beaton</prepared>
- <docno></docno>
- <date>1998-11-30</date>
- <rev>1.2</rev>
- <file>ref_man_erl_interface.xml</file>
- </header>
- <description>
- <note><p>The support for VxWorks is deprecated as of OTP 22, and
- will be removed in OTP 23.</p></note>
- <note><p>The old legacy <c>erl_interface</c> library (functions
- with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
- removed in OTP 23. This does not apply to the <c>ei</c>
- library. Reasonably new <c>gcc</c> compilers will issue deprecation
- warnings. In order to disable these warnings, define the macro
- <c>EI_NO_DEPR_WARN</c>.</p></note>
- <p>The <c>erl_interface</c> library is a <c>C</c> interface library
- for communication with <c>Erlang</c>.</p>
- <note>
- <p>By default, the <c>erl_interface</c> library is only guaranteed
- to be compatible with other Erlang/OTP components from the same
- release as the <c>erl_interface</c> library. See the documentation
- of the
- <seealso marker="erl_eterm#erl_set_compat_rel">erl_set_compat_rel()</seealso>
- function on how to communicate with Erlang/OTP components from earlier
- releases.</p>
- </note>
- </description>
- <xi:include href="erl_connect.xml"/>
- <xi:include href="erl_error.xml"/>
- <xi:include href="erl_eterm.xml"/>
- <xi:include href="erl_format.xml"/>
- <xi:include href="erl_global.xml"/>
- <xi:include href="erl_malloc.xml"/>
- <xi:include href="erl_marshal.xml"/>
-</application>
diff --git a/lib/erl_interface/doc/src/registry.xml b/lib/erl_interface/doc/src/registry.xml
index 127ffc9577..92858516d9 100644
--- a/lib/erl_interface/doc/src/registry.xml
+++ b/lib/erl_interface/doc/src/registry.xml
@@ -35,9 +35,14 @@
<lib>registry</lib>
<libsummary>Store and back up key-value pairs.</libsummary>
<description>
+ <note><p>This functionality is deprecated as of OTP 23, and will be
+ removed in OTP 24. Reasonably new <c>gcc</c> compilers will issue
+ deprecation warnings. In order to disable these warnings, define the
+ macro <c>EI_NO_DEPR_WARN</c>.</p></note>
+
<p>This module provides support for storing key-value
pairs in a table known as a registry, backing up registries to
- <seealso marker="mnesia:mnesia">Mnesia</seealso>
+ <seeerl marker="mnesia:mnesia">Mnesia</seeerl>
in an atomic manner, and later restoring the contents of a
registry from <c>Mnesia</c>.</p>
</description>
@@ -107,7 +112,7 @@
where the backed up data is to be placed. If the table does not
exist, it is created automatically using configurable defaults.
For information about configuring this behavior, see
- <seealso marker="mnesia:mnesia"><c>Mnesia</c></seealso>.</item>
+ <seeerl marker="mnesia:mnesia"><c>Mnesia</c></seeerl>.</item>
</list>
<p>If <c>flags</c> is <c>0</c>, the backup includes only
those objects that have been created, modified, or deleted since the
diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h
index 5c3d659c93..803e22d4b2 100644
--- a/lib/erl_interface/include/ei.h
+++ b/lib/erl_interface/include/ei.h
@@ -43,7 +43,7 @@ typedef LONG_PTR ssize_t; /* Sigh... */
#include <stdio.h> /* Need type FILE */
#include <errno.h> /* Need EHOSTUNREACH, ENOMEM, ... */
-#if !(defined(__WIN32__) || defined(_WIN32)) && !defined(VXWORKS) || (defined(VXWORKS) && defined(HAVE_SENS))
+#if !(defined(__WIN32__) || defined(_WIN32))
# include <netdb.h>
#endif
@@ -188,20 +188,20 @@ extern "C" {
* the 'ei' interface as well.... :-(
*/
-#if defined(_REENTRANT) || defined(VXWORKS) || defined(__WIN32__)
+#if defined(_REENTRANT) || defined(__WIN32__)
/* 'erl_errno' as a function return value */
volatile int* __erl_errno_place(void) __attribute__ ((__const__));
#define erl_errno (*__erl_errno_place ())
-#else /* !_REENTRANT && !VXWORKS && !__WIN32__ */
+#else /* !_REENTRANT && !__WIN32__ */
extern volatile int __erl_errno;
#define erl_errno __erl_errno
-#endif /* !_REENTRANT && !VXWORKS && !__WIN32__ */
+#endif /* !_REENTRANT && !__WIN32__ */
/* -------------------------------------------------------------------- */
@@ -323,13 +323,24 @@ typedef struct {
#define EI_SCLBK_FLG_FULL_IMPL (1 << 0)
+/*
+ * HACK: AIX defines many socket functions like accept to be naccept, which
+ * pollutes the global namespace. Set up an ugly ifdef for consumers of this
+ * API here so they get a mangled name for AIX and the sane name elsewhere.
+ */
+#ifdef _AIX
+#define EI_ACCEPT_NAME accept_ei
+#else
+#define EI_ACCEPT_NAME accept
+#endif
+
typedef struct {
int flags;
int (*socket)(void **ctx, void *setup_ctx);
int (*close)(void *ctx);
int (*listen)(void *ctx, void *addr, int *len, int backlog);
- int (*accept)(void **ctx, void *addr, int *len, unsigned tmo);
+ int (*EI_ACCEPT_NAME)(void **ctx, void *addr, int *len, unsigned tmo);
int (*connect)(void *ctx, void *addr, int len, unsigned tmo);
int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo);
int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo);
@@ -351,10 +362,11 @@ typedef struct ei_cnode_s {
/* Currently this_ipaddr isn't used */
/* struct in_addr this_ipaddr; */
char ei_connect_cookie[EI_MAX_COOKIE_SIZE+1];
- short creation;
+ unsigned int creation;
erlang_pid self;
ei_socket_callbacks *cbs;
void *setup_context;
+ unsigned int pidsn;
} ei_cnode;
typedef struct in_addr *Erl_IpAddr;
@@ -393,8 +405,12 @@ int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname,
int ei_connect(ei_cnode* ec, char *nodename);
int ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned ms);
+int ei_connect_host_port(ei_cnode* ec, char *hostname, int port);
+int ei_connect_host_port_tmo(ei_cnode* ec, char *hostname, int port, unsigned ms);
int ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename);
int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned ms);
+int ei_xconnect_host_port(ei_cnode* ec, Erl_IpAddr adr, int port);
+int ei_xconnect_host_port_tmo(ei_cnode* ec, Erl_IpAddr adr, int port, unsigned ms);
int ei_receive(int fd, unsigned char *bufp, int bufsize);
int ei_receive_tmo(int fd, unsigned char *bufp, int bufsize, unsigned ms);
@@ -431,6 +447,8 @@ const char *ei_thishostname(const ei_cnode* ec);
const char *ei_thisalivename(const ei_cnode* ec);
erlang_pid *ei_self(ei_cnode* ec);
+int ei_make_pid(ei_cnode *ec, erlang_pid *pid);
+int ei_make_ref(ei_cnode *ec, erlang_ref *ref);
/*
* settings
@@ -444,41 +462,6 @@ int ei_get_tracelevel(void);
* We have erl_gethost*() so we include ei versions as well.
*/
-#if defined(VXWORKS)
-
-extern int h_errno;
-
-/*
- * We need these definitions - if the user has SENS then he gets them
- * from netdb.h, otherwise we define them ourselves.
- *
- * If you are getting "multiple definition" errors here,
- * make sure you have included <netdb.h> BEFORE "erl_interface.h"
- * or define HAVE_SENS in your CFLAGS.
- */
-
-#if !defined(HAVE_SENS) && !defined(HOST_NOT_FOUND) /* just in case */
-
-struct hostent {
- char *h_name; /* official name of host */
- char **h_aliases; /* alias list */
- int h_addrtype; /* host address type */
- int h_length; /* length of address */
- char **h_addr_list; /* list of addresses from name server */
-#define h_addr h_addr_list[0] /* address, for backward compatiblity */
- unsigned int unused; /* SENS defines this as ttl */
-};
-
-#define HOST_NOT_FOUND 1 /* Authoritative Answer Host not found */
-#define TRY_AGAIN 2 /* Non-Authoritive Host not found, or SERVERFAIL */
-#define NO_RECOVERY 3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */
-#define NO_DATA 4 /* Valid name, no data record of requested type */
-#define NO_ADDRESS NO_DATA /* no address, look for MX record */
-
-#endif /* !HAVE_SENS && !HOST_NOT_FOUND */
-#endif /* VXWORKS */
-
-
struct hostent *ei_gethostbyname(const char *name);
struct hostent *ei_gethostbyaddr(const char *addr, int len, int type);
struct hostent *ei_gethostbyname_r(const char *name,
@@ -537,8 +520,6 @@ int ei_encode_port(char *buf, int *index, const erlang_port *p);
int ei_x_encode_port(ei_x_buff* x, const erlang_port *p);
int ei_encode_ref(char *buf, int *index, const erlang_ref *p);
int ei_x_encode_ref(ei_x_buff* x, const erlang_ref *p);
-int ei_encode_term(char *buf, int *index, void *t) EI_DEPRECATED_ATTR;
-int ei_x_encode_term(ei_x_buff* x, void* t) EI_DEPRECATED_ATTR;
int ei_encode_trace(char *buf, int *index, const erlang_trace *p);
int ei_x_encode_trace(ei_x_buff* x, const erlang_trace *p);
int ei_encode_tuple_header(char *buf, int *index, int arity);
@@ -586,11 +567,11 @@ void free_fun(erlang_fun* f);
int ei_decode_pid(const char *buf, int *index, erlang_pid *p);
int ei_decode_port(const char *buf, int *index, erlang_port *p);
int ei_decode_ref(const char *buf, int *index, erlang_ref *p);
-int ei_decode_term(const char *buf, int *index, void *t) EI_DEPRECATED_ATTR;
int ei_decode_trace(const char *buf, int *index, erlang_trace *p);
int ei_decode_tuple_header(const char *buf, int *index, int *arity);
int ei_decode_list_header(const char *buf, int *index, int *arity);
int ei_decode_map_header(const char *buf, int *index, int *arity);
+int ei_decode_iodata(const char *buf, int* index, int *szp, char *out_buf);
/*
* ei_decode_ei_term() returns 1 if term is decoded, 0 if term is OK,
@@ -622,6 +603,10 @@ int ei_x_append(ei_x_buff* x, const ei_x_buff* x2);
int ei_x_append_buf(ei_x_buff* x, const char* buf, int len);
int ei_skip_term(const char* buf, int* index);
+int ei_cmp_refs(erlang_ref *a, erlang_ref *b);
+int ei_cmp_pids(erlang_pid *a, erlang_pid *b);
+int ei_cmp_ports(erlang_port *a, erlang_port *b);
+
/***************************************************************************
*
* Hash types needed by registry types
@@ -714,9 +699,9 @@ int ei_init(void);
* be specified in all subsequent calls to registry functions. You can
* open as many registries as you like.
*/
-ei_reg *ei_reg_open(int size);
-int ei_reg_resize(ei_reg *oldreg, int newsize);
-int ei_reg_close(ei_reg *reg);
+ei_reg *ei_reg_open(int size) EI_DEPRECATED_ATTR;
+int ei_reg_resize(ei_reg *oldreg, int newsize) EI_DEPRECATED_ATTR;
+int ei_reg_close(ei_reg *reg) EI_DEPRECATED_ATTR;
/* set values... these routines assign values to keys. If the key
* exists, the previous value is discarded and the new one replaces
@@ -733,10 +718,10 @@ int ei_reg_close(ei_reg *reg);
* On success the function returns 0, otherwise a value
* indicating the reason for failure will be returned.
*/
-int ei_reg_setival(ei_reg *reg, const char *key, long i);
-int ei_reg_setfval(ei_reg *reg, const char *key, double f);
-int ei_reg_setsval(ei_reg *reg, const char *key, const char *s);
-int ei_reg_setpval(ei_reg *reg, const char *key, const void *p, int size);
+int ei_reg_setival(ei_reg *reg, const char *key, long i) EI_DEPRECATED_ATTR;
+int ei_reg_setfval(ei_reg *reg, const char *key, double f) EI_DEPRECATED_ATTR;
+int ei_reg_setsval(ei_reg *reg, const char *key, const char *s) EI_DEPRECATED_ATTR;
+int ei_reg_setpval(ei_reg *reg, const char *key, const void *p, int size) EI_DEPRECATED_ATTR;
/* general set function (specifiy type via flags)
* optional arguments are as for equivalent type-specific function,
@@ -746,16 +731,16 @@ int ei_reg_setpval(ei_reg *reg, const char *key, const void *p, int size);
* ei_reg_setval(fd, path, EI_STR, const char *s);
* ei_reg_setval(fd, path, EI_BIN, const void *p, int size);
*/
-int ei_reg_setval(ei_reg *reg, const char *key, int flags, ...);
+int ei_reg_setval(ei_reg *reg, const char *key, int flags, ...) EI_DEPRECATED_ATTR;
/* get value of specific type object */
/* warning: it may be difficult to detect errors when using these
* functions, since the error values are returned "in band"
*/
-long ei_reg_getival(ei_reg *reg, const char *key);
-double ei_reg_getfval(ei_reg *reg, const char *key);
-const char *ei_reg_getsval(ei_reg *reg, const char *key);
-const void *ei_reg_getpval(ei_reg *reg, const char *key, int *size);
+long ei_reg_getival(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
+double ei_reg_getfval(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
+const char *ei_reg_getsval(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
+const void *ei_reg_getpval(ei_reg *reg, const char *key, int *size) EI_DEPRECATED_ATTR;
/* get value of any type object (must specify)
* Retrieve a value from an object. The type of value expected and a
@@ -771,7 +756,7 @@ const void *ei_reg_getpval(ei_reg *reg, const char *key, int *size);
* for BIN objects an int* is needed to return the size of the object, i.e.
* int ei_reg_getval(ei_reg *reg, const char *path, int flags, void **p, int *size);
*/
-int ei_reg_getval(ei_reg *reg, const char *key, int flags, ...);
+int ei_reg_getval(ei_reg *reg, const char *key, int flags, ...) EI_DEPRECATED_ATTR;
/* mark the object as dirty. Normally this operation will not be
* necessary, as it is done automatically by all of the above 'set'
@@ -781,27 +766,34 @@ int ei_reg_getval(ei_reg *reg, const char *key, int flags, ...);
* backup operation. Use this function to set the dirty bit on the
* object.
*/
-int ei_reg_markdirty(ei_reg *reg, const char *key);
+int ei_reg_markdirty(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
/* remove objects. The value, if any, is discarded. For STR and BIN
* objects, the object itself is removed using free(). */
-int ei_reg_delete(ei_reg *reg, const char *key);
+int ei_reg_delete(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR;
/* get information about an object */
-int ei_reg_stat(ei_reg *reg, const char *key, struct ei_reg_stat *obuf);
+int ei_reg_stat(ei_reg *reg, const char *key, struct ei_reg_stat *obuf) EI_DEPRECATED_ATTR;
/* get information about table */
-int ei_reg_tabstat(ei_reg *reg, struct ei_reg_tabstat *obuf);
+int ei_reg_tabstat(ei_reg *reg, struct ei_reg_tabstat *obuf) EI_DEPRECATED_ATTR;
/* dump to / restore from backup */
/* fd is open descriptor to Erlang, mntab is Mnesia table name */
/* flags here: */
#define EI_FORCE 0x1 /* dump all records (not just dirty ones) */
#define EI_NOPURGE 0x2 /* don't purge deleted records */
-int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags);
-int ei_reg_restore(int fd, ei_reg *reg, const char *mntab);
-int ei_reg_purge(ei_reg *reg);
+int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags) EI_DEPRECATED_ATTR;
+int ei_reg_restore(int fd, ei_reg *reg, const char *mntab) EI_DEPRECATED_ATTR;
+int ei_reg_purge(ei_reg *reg) EI_DEPRECATED_ATTR;
+/* -------------------------------------------------------------------- */
+/* The ei_global functions */
+/* -------------------------------------------------------------------- */
+char **ei_global_names(ei_cnode *ec, int fd, int *count);
+int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, char *node);
+int ei_global_register(int fd, const char *name, erlang_pid *self);
+int ei_global_unregister(ei_cnode *ec, int fd, const char *name);
/* -------------------------------------------------------------------- */
/* Encoding/decoding bugnums to GNU MP format */
@@ -832,14 +824,12 @@ int ei_x_encode_bignum(ei_x_buff *x, mpz_t obj);
#define EI_ULONGLONG unsigned long long
#endif
-#ifndef VXWORKS
int ei_decode_longlong(const char *buf, int *index, EI_LONGLONG *p);
int ei_decode_ulonglong(const char *buf, int *index, EI_ULONGLONG *p);
int ei_encode_longlong(char *buf, int *index, EI_LONGLONG p);
int ei_encode_ulonglong(char *buf, int *index, EI_ULONGLONG p);
int ei_x_encode_longlong(ei_x_buff* x, EI_LONGLONG n);
int ei_x_encode_ulonglong(ei_x_buff* x, EI_ULONGLONG n);
-#endif
#ifdef USE_EI_UNDOCUMENTED
diff --git a/lib/erl_interface/src/legacy/erl_global.h b/lib/erl_interface/include/ei_global.h
index d2eec08b35..b85a31217d 100644
--- a/lib/erl_interface/src/legacy/erl_global.h
+++ b/lib/erl_interface/include/ei_global.h
@@ -17,12 +17,13 @@
*
* %CopyrightEnd%
*/
-#ifndef _ERL_GLOBAL_H
-#define _ERL_GLOBAL_H
+#ifndef _EI_GLOBAL_H
+#define _EI_GLOBAL_H
+#include "ei.h"
-char **erl_global_names(int fd, int *count);
-ETERM *erl_global_whereis(int fd, const char *name, char *node);
-int erl_global_register(int fd, const char *name, ETERM *pid);
-int erl_global_unregister(int fd, const char *name);
+char **ei_global_names(ei_cnode *ec, int fd, int *count);
+int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, char *node);
+int ei_global_register(int fd, const char *name, erlang_pid *self);
+int ei_global_unregister(ei_cnode *ec, int fd, const char *name);
-#endif /* _ERL_GLOBAL_H */
+#endif /* _EI_GLOBAL_H */
diff --git a/lib/erl_interface/include/erl_interface.h b/lib/erl_interface/include/erl_interface.h
deleted file mode 100644
index c72194341e..0000000000
--- a/lib/erl_interface/include/erl_interface.h
+++ /dev/null
@@ -1,473 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2020. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-#ifndef _ERL_INTERFACE_H
-#define _ERL_INTERFACE_H
-
-/************************************************************************/
-/* This file defines the complete interface to erl_interface */
-/* Note: the 'ei' interface is the prefered C API. */
-/************************************************************************/
-
-#include "ei.h" /* ei is the base */
-
-/* -------------------------------------------------------------------- */
-/* Public defines */
-/* -------------------------------------------------------------------- */
-
-#define ERL_COMPOUND (1 << 7)
-
-#define ERL_UNDEF 0
-#define ERL_INTEGER 1
-#define ERL_U_INTEGER 2 /* unsigned int */
-#define ERL_ATOM 3
-#define ERL_PID 4
-#define ERL_PORT 5
-#define ERL_REF 6
-#define ERL_CONS (7 | ERL_COMPOUND)
-#define ERL_LIST ERL_CONS
-#define ERL_NIL 8
-#define ERL_EMPTY_LIST ERL_NIL
-#define ERL_TUPLE (9 | ERL_COMPOUND)
-#define ERL_BINARY 10
-#define ERL_FLOAT 11
-#define ERL_VARIABLE (12 | ERL_COMPOUND) /* used in patterns */
-#define ERL_SMALL_BIG 13
-#define ERL_U_SMALL_BIG 14
-#define ERL_FUNCTION (15 | ERL_COMPOUND)
-#define ERL_BIG 16
-#define ERL_LONGLONG 17
-#define ERL_U_LONGLONG 18
-
-
-#define ERL_TYPE(x) (ERL_HEADER(x)->type)
-
-/* FIXME some macros left in erl_eterm.h should probably be documented */
-
-#define ERL_IS_INTEGER(x) (ERL_TYPE(x) == ERL_INTEGER)
-#define ERL_IS_UNSIGNED_INTEGER(x) (ERL_TYPE(x) == ERL_U_INTEGER)
-#define ERL_IS_LONGLONG(x) (ERL_TYPE(x) == ERL_LONGLONG)
-#define ERL_IS_UNSIGNED_LONGLONG(x) (ERL_TYPE(x) == ERL_U_LONGLONG)
-#define ERL_IS_FLOAT(x) (ERL_TYPE(x) == ERL_FLOAT)
-#define ERL_IS_ATOM(x) (ERL_TYPE(x) == ERL_ATOM)
-#define ERL_IS_PID(x) (ERL_TYPE(x) == ERL_PID)
-#define ERL_IS_PORT(x) (ERL_TYPE(x) == ERL_PORT)
-#define ERL_IS_REF(x) (ERL_TYPE(x) == ERL_REF)
-#define ERL_IS_TUPLE(x) (ERL_TYPE(x) == ERL_TUPLE)
-#define ERL_IS_BINARY(x) (ERL_TYPE(x) == ERL_BINARY)
-#define ERL_IS_NIL(x) (ERL_TYPE(x) == ERL_NIL)
-#define ERL_IS_EMPTY_LIST(x) ERL_IS_NIL(x)
-#define ERL_IS_CONS(x) (ERL_TYPE(x) == ERL_CONS)
-#define ERL_IS_LIST(x) (ERL_IS_CONS(x) || ERL_IS_EMPTY_LIST(x))
-
-/*
- * Macros used for XXXX
- */
-
-#define ERL_HEADER(x) ((Erl_Header *)x)
-#define ERL_COUNT(x) (ERL_HEADER(x)->count)
-
-/*
- * Macros used for retrieving values from Erlang terms.
- */
-
-#define ERL_INT_VALUE(x) ((x)->uval.ival.i)
-#define ERL_INT_UVALUE(x) ((x)->uval.uival.u)
-#define ERL_LL_VALUE(x) ((x)->uval.llval.i)
-#define ERL_LL_UVALUE(x) ((x)->uval.ullval.u)
-
-#define ERL_FLOAT_VALUE(x) ((x)->uval.fval.f)
-
-#define ERL_ATOM_PTR(x) erl_atom_ptr_latin1((Erl_Atom_data*) &(x)->uval.aval.d)
-#define ERL_ATOM_PTR_UTF8(x) erl_atom_ptr_utf8((Erl_Atom_data*) &(x)->uval.aval.d)
-#define ERL_ATOM_SIZE(x) erl_atom_size_latin1((Erl_Atom_data*) &(x)->uval.aval.d)
-#define ERL_ATOM_SIZE_UTF8(x) erl_atom_size_utf8((Erl_Atom_data*) &(x)->uval.aval.d)
-
-#define ERL_PID_NODE(x) erl_atom_ptr_latin1((Erl_Atom_data*) &(x)->uval.pidval.node)
-#define ERL_PID_NODE_UTF8(x) erl_atom_ptr_utf8((Erl_Atom_data*) &(x)->uval.pidval.node)
-#define ERL_PID_NUMBER(x) ((x)->uval.pidval.number)
-#define ERL_PID_SERIAL(x) ((x)->uval.pidval.serial)
-#define ERL_PID_CREATION(x) ((x)->uval.pidval.creation)
-
-#define ERL_PORT_NODE(x) erl_atom_ptr_latin1((Erl_Atom_data*) &(x)->uval.portval.node)
-#define ERL_PORT_NODE_UTF8(x) erl_atom_ptr_utf8((Erl_Atom_data*) &(x)->uval.portval.node)
-#define ERL_PORT_NUMBER(x) ((x)->uval.portval.number)
-#define ERL_PORT_CREATION(x) ((x)->uval.portval.creation)
-
-#define ERL_REF_NODE(x) erl_atom_ptr_latin1((Erl_Atom_data*) &(x)->uval.refval.node)
-#define ERL_REF_NODE_UTF8(x) erl_atom_ptr_utf8((Erl_Atom_data*) &(x)->uval.refval.node)
-#define ERL_REF_NUMBER(x) ((x)->uval.refval.n[0])
-#define ERL_REF_NUMBERS(x) ((x)->uval.refval.n)
-#define ERL_REF_LEN(x) ((x)->uval.refval.len)
-#define ERL_REF_CREATION(x) ((x)->uval.refval.creation)
-
-#define ERL_TUPLE_SIZE(x) ((x)->uval.tval.size)
-
-/* NOTE!!! This is 0-based!! (first item is number 0)
- * Note too that element/2 (in Erlang) and
- * erl_element() are both 1-based.
- */
-#define ERL_TUPLE_ELEMS(x) ((x)->uval.tval.elems)
-#define ERL_TUPLE_ELEMENT(x, i) (ERL_TUPLE_ELEMS(x)[(i)])
-
-#define ERL_BIN_SIZE(x) ((x)->uval.bval.size)
-#define ERL_BIN_PTR(x) ((x)->uval.bval.b)
-
-#define ERL_CONS_HEAD(x) ((x)->uval.lval.head)
-#define ERL_CONS_TAIL(x) ((x)->uval.lval.tail)
-
-#define ERL_VAR_LEN(x) ((x)->uval.vval.len)
-#define ERL_VAR_NAME(x) ((x)->uval.vval.name)
-#define ERL_VAR_VALUE(x) ((x)->uval.vval.v)
-
-#define ERL_CLOSURE_SIZE(x) ((x)->uval.funcval.size)
-#define ERL_FUN_CREATOR(x) ((x)->uval.funcval.creator)
-#define ERL_FUN_MODULE(x) ((x)->uval.funcval.module)
-#define ERL_FUN_UNIQ(x) ((x)->uval.funcval.uniq)
-#define ERL_FUN_INDEX(x) ((x)->uval.funcval.index)
-#define ERL_FUN_ARITY(x) ((x)->uval.funcval.arity)
-#define ERL_FUN_NEW_INDEX(x) ((x)->uval.funcval.new_index)
-#define ERL_FUN_MD5(x) ((x)->uval.funcval.md5)
-#define ERL_CLOSURE(x) ((x)->uval.funcval.closure)
-#define ERL_CLOSURE_ELEMENT(x,i) (ERL_CLOSURE(x)[(i)])
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* -------------------------------------------------------------------- */
-/* Type definitions of Erlang terms in C */
-/* -------------------------------------------------------------------- */
-
-typedef struct {
- unsigned int count:24; /* reference counter */
- unsigned int type:8; /* type of Erlang term */
-} Erl_Header;
-
-typedef struct {
- Erl_Header h;
- int i;
-} Erl_Integer;
-
-typedef struct {
- Erl_Header h;
- unsigned int u;
-} Erl_Uinteger;
-
-typedef struct {
- Erl_Header h;
- long long i;
-} Erl_LLInteger;
-
-typedef struct {
- Erl_Header h;
- unsigned long long u;
-} Erl_ULLInteger;
-
-typedef struct {
- Erl_Header h;
- double f;
-} Erl_Float;
-
-typedef struct {
- char *utf8;
- int lenU;
- char *latin1;
- int lenL;
-} Erl_Atom_data;
-
-char* erl_atom_ptr_latin1(Erl_Atom_data*) EI_DEPRECATED_ATTR;
-char* erl_atom_ptr_utf8(Erl_Atom_data*) EI_DEPRECATED_ATTR;
-int erl_atom_size_latin1(Erl_Atom_data*) EI_DEPRECATED_ATTR;
-int erl_atom_size_utf8(Erl_Atom_data*) EI_DEPRECATED_ATTR;
-char* erl_atom_init_latin1(Erl_Atom_data*, const char*) EI_DEPRECATED_ATTR;
-
-typedef struct {
- Erl_Header h;
- Erl_Atom_data d;
-} Erl_Atom;
-
-typedef struct {
- Erl_Header h;
- Erl_Atom_data node;
- unsigned int number;
- unsigned int serial;
- unsigned int creation;
-} Erl_Pid;
-
-typedef struct {
- Erl_Header h;
- Erl_Atom_data node;
- unsigned int number;
- unsigned int creation;
-} Erl_Port;
-
-typedef struct {
- Erl_Header h;
- Erl_Atom_data node;
- int len;
- unsigned int n[3];
- unsigned int creation;
-} Erl_Ref;
-
-typedef struct {
- Erl_Header h;
- int arity;
- int is_neg;
- unsigned short *digits;
-} Erl_Big;
-
-struct _eterm; /* forward */
-
-typedef struct {
- Erl_Header h;
- struct _eterm *head;
- struct _eterm *tail;
-} Erl_List;
-
-typedef struct {
- Erl_Header h;
-} Erl_EmptyList;
-
-typedef struct {
- Erl_Header h;
- int size;
- struct _eterm **elems;
-} Erl_Tuple;
-
-typedef struct {
- Erl_Header h;
- int size;
- unsigned char *b;
-} Erl_Binary;
-
-/* Variables may only exist in patterns.
- * Note: identical variable names in a pattern
- * denotes the same value.
- */
-typedef struct {
- Erl_Header h;
- int len;
- char *name;
- struct _eterm *v;
-} Erl_Variable;
-
-
-typedef struct {
- Erl_Header h;
- int size; /* size of closure */
- int arity; /* arity for new (post R7) external funs */
- unsigned char md5[16]; /* md5 for new funs */
- int new_index; /* new funs */
- struct _eterm* creator; /* pid */
- struct _eterm* module; /* module */
- struct _eterm* index;
- struct _eterm* uniq;
- struct _eterm** closure;
-} Erl_Function;
-
-typedef struct _eterm {
- union {
- Erl_Integer ival;
- Erl_Uinteger uival;
- Erl_LLInteger llval;
- Erl_ULLInteger ullval;
- Erl_Float fval;
- Erl_Atom aval;
- Erl_Pid pidval;
- Erl_Port portval;
- Erl_Ref refval;
- Erl_List lval;
- Erl_EmptyList nval;
- Erl_Tuple tval;
- Erl_Binary bval;
- Erl_Variable vval;
- Erl_Function funcval;
- Erl_Big bigval;
- } uval;
-} ETERM;
-
-
-#define MAXREGLEN (255*4) /* max length of registered (atom) name */
-
-typedef struct {
- int type; /* one of the message type constants in eiext.h */
- ETERM *msg; /* the actual message */
- ETERM *from;
- ETERM *to;
- char to_name[MAXREGLEN+1];
-} ErlMessage;
-
-typedef unsigned char Erl_Heap;
-
-
-/* -------------------------------------------------------------------- */
-/* The functions */
-/* -------------------------------------------------------------------- */
-
-void erl_init(void *x, long y) EI_DEPRECATED_ATTR;
-void erl_set_compat_rel(unsigned) EI_DEPRECATED_ATTR;
-int erl_connect_init(int, char*,short) EI_DEPRECATED_ATTR;
-int erl_connect_xinit(char*,char*,char*,struct in_addr*,char*,short) EI_DEPRECATED_ATTR;
-int erl_connect(char*) EI_DEPRECATED_ATTR;
-int erl_xconnect(struct in_addr*,char *) EI_DEPRECATED_ATTR;
-int erl_close_connection(int) EI_DEPRECATED_ATTR;
-int erl_receive(int, unsigned char*, int) EI_DEPRECATED_ATTR;
-int erl_receive_msg(int, unsigned char*, int, ErlMessage*) EI_DEPRECATED_ATTR;
-int erl_xreceive_msg(int, unsigned char**, int*, ErlMessage*) EI_DEPRECATED_ATTR;
-int erl_send(int, ETERM*, ETERM*) EI_DEPRECATED_ATTR;
-int erl_reg_send(int, char*, ETERM*) EI_DEPRECATED_ATTR;
-ETERM *erl_rpc(int,char*,char*,ETERM*) EI_DEPRECATED_ATTR;
-int erl_rpc_to(int,char*,char*,ETERM*) EI_DEPRECATED_ATTR;
-int erl_rpc_from(int,int,ErlMessage*) EI_DEPRECATED_ATTR;
-
-/* erl_publish returns open descriptor on success, or -1 */
-int erl_publish(int port) EI_DEPRECATED_ATTR;
-int erl_accept(int,ErlConnect*) EI_DEPRECATED_ATTR;
-
-const char *erl_thiscookie(void) EI_DEPRECATED_ATTR;
-const char *erl_thisnodename(void) EI_DEPRECATED_ATTR;
-const char *erl_thishostname(void) EI_DEPRECATED_ATTR;
-const char *erl_thisalivename(void) EI_DEPRECATED_ATTR;
-short erl_thiscreation(void) EI_DEPRECATED_ATTR;
-
-/* returns 0 on success, -1 if node not known to epmd or epmd not reached */
-int erl_unpublish(const char *alive) EI_DEPRECATED_ATTR;
-
-#ifdef EI_HAVE_DEPRECATED_ATTR__
-#define EI_DEPR_ATTR_EXTRA , EI_DEPRECATED_ATTR_NAME
-#else
-#define EI_DEPR_ATTR_EXTRA
-#endif
-
-
-/* Report generic error to stderr. */
-void erl_err_msg(const char * __template, ...)
- __attribute__ ((__format__ (printf, 1, 2) EI_DEPR_ATTR_EXTRA)) ;
-/* Report generic error to stderr and die. */
-void erl_err_quit(const char * __template, ...)
- __attribute__ ((__format__ (printf, 1, 2), __noreturn__ EI_DEPR_ATTR_EXTRA));
-/* Report system/libc error to stderr. */
-void erl_err_ret(const char * __template, ...)
- __attribute__ ((__format__ (printf, 1, 2) EI_DEPR_ATTR_EXTRA));
-/* Report system/libc error to stderr and die. */
-void erl_err_sys(const char * __template, ...)
- __attribute__ ((__format__ (printf, 1, 2), __noreturn__ EI_DEPR_ATTR_EXTRA));
-
-ETERM *erl_cons(ETERM*,ETERM*) EI_DEPRECATED_ATTR;
-ETERM *erl_copy_term(const ETERM*) EI_DEPRECATED_ATTR;
-ETERM *erl_element(int,const ETERM*) EI_DEPRECATED_ATTR;
-
-ETERM *erl_hd(const ETERM*) EI_DEPRECATED_ATTR;
-ETERM* erl_iolist_to_binary(const ETERM* term) EI_DEPRECATED_ATTR;
-char* erl_iolist_to_string(const ETERM* term) EI_DEPRECATED_ATTR;
-int erl_iolist_length(const ETERM*) EI_DEPRECATED_ATTR;
-int erl_length(const ETERM*) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_atom(const char*) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_binary(const char*,int) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_empty_list(void) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_estring(const char*, int) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_float(double) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_int(int) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_longlong(long long) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_list(ETERM**,int) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_pid(const char*,unsigned int,unsigned int,unsigned char) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_port(const char*,unsigned int,unsigned char) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_ref(const char*,unsigned int,unsigned char) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_long_ref(const char*,unsigned int,unsigned int,
- unsigned int,unsigned char) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_string(const char*) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_tuple(ETERM**,int) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_uint(unsigned int) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_ulonglong(unsigned long long) EI_DEPRECATED_ATTR;
-ETERM *erl_mk_var(const char*) EI_DEPRECATED_ATTR;
-int erl_print_term(FILE*,const ETERM*) EI_DEPRECATED_ATTR;
-/* int erl_sprint_term(char*,const ETERM*) EI_DEPRECATED_ATTR; */
-int erl_size(const ETERM*) EI_DEPRECATED_ATTR;
-ETERM *erl_tl(const ETERM*) EI_DEPRECATED_ATTR;
-ETERM *erl_var_content(const ETERM*, const char*) EI_DEPRECATED_ATTR;
-
-ETERM *erl_format(char*, ... ) EI_DEPRECATED_ATTR;
-int erl_match(ETERM*, ETERM*) EI_DEPRECATED_ATTR;
-
-char **erl_global_names(int fd, int *count) EI_DEPRECATED_ATTR;
-int erl_global_register(int fd, const char *name, ETERM *pid) EI_DEPRECATED_ATTR;
-int erl_global_unregister(int fd, const char *name) EI_DEPRECATED_ATTR;
-ETERM *erl_global_whereis(int fd, const char *name, char *node) EI_DEPRECATED_ATTR;
-
-void erl_init_malloc(Erl_Heap*,long) EI_DEPRECATED_ATTR;
-ETERM *erl_alloc_eterm(unsigned char) EI_DEPRECATED_ATTR;
-void erl_eterm_release(void) EI_DEPRECATED_ATTR;
-void erl_eterm_statistics(unsigned long*,unsigned long*) EI_DEPRECATED_ATTR;
-void erl_free_array(ETERM**,int) EI_DEPRECATED_ATTR;
-void erl_free_term(ETERM*) EI_DEPRECATED_ATTR;
-void erl_free_compound(ETERM*) EI_DEPRECATED_ATTR;
-void *erl_malloc(long) EI_DEPRECATED_ATTR;
-void erl_free(void*) EI_DEPRECATED_ATTR;
-
-int erl_compare_ext(unsigned char*, unsigned char*) EI_DEPRECATED_ATTR;
-ETERM *erl_decode(unsigned char*) EI_DEPRECATED_ATTR;
-ETERM *erl_decode_buf(unsigned char**) EI_DEPRECATED_ATTR;
-int erl_encode(ETERM*,unsigned char*t) EI_DEPRECATED_ATTR;
-int erl_encode_buf(ETERM*,unsigned char**) EI_DEPRECATED_ATTR;
-int erl_ext_size(unsigned char*) EI_DEPRECATED_ATTR;
-unsigned char erl_ext_type(unsigned char*) EI_DEPRECATED_ATTR; /* Note: returned 'char' before R9C */
-unsigned char *erl_peek_ext(unsigned char*,int) EI_DEPRECATED_ATTR;
-int erl_term_len(ETERM*) EI_DEPRECATED_ATTR;
-
-int cmp_latin1_vs_utf8(const char* sL, int lenL, const char* sU, int lenU) EI_DEPRECATED_ATTR;
-
-/* -------------------------------------------------------------------- */
-/* Wrappers around ei functions */
-/* -------------------------------------------------------------------- */
-
-/*
- * Undocumented before R9C, included for compatibility with old code
- */
-
-struct hostent *erl_gethostbyname(const char *name) EI_DEPRECATED_ATTR;
-struct hostent *erl_gethostbyaddr(const char *addr, int len, int type) EI_DEPRECATED_ATTR;
-struct hostent *erl_gethostbyname_r(const char *name,
- struct hostent *hostp,
- char *buffer,
- int buflen,
- int *h_errnop) EI_DEPRECATED_ATTR;
-struct hostent *erl_gethostbyaddr_r(const char *addr,
- int length,
- int type,
- struct hostent *hostp,
- char *buffer,
- int buflen,
- int *h_errnop) EI_DEPRECATED_ATTR;
-
-/*
- * Undocumented, included for compatibility with old code
- */
-
-void erl_init_resolve(void) EI_DEPRECATED_ATTR;
-int erl_distversion(int fd) EI_DEPRECATED_ATTR;
-int erl_epmd_connect(struct in_addr *inaddr) EI_DEPRECATED_ATTR;
-int erl_epmd_port(struct in_addr *inaddr, const char *alive, int *dist) EI_DEPRECATED_ATTR;
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/lib/erl_interface/src/Makefile.in b/lib/erl_interface/src/Makefile.in
index ba0164ea51..7ff3f09abb 100644
--- a/lib/erl_interface/src/Makefile.in
+++ b/lib/erl_interface/src/Makefile.in
@@ -52,9 +52,7 @@ APPUP_FILE= erl_interface.appup
APPUP_SRC= $(APPUP_FILE).src
APPUP_TARGET= $(EBINDIR)/$(APPUP_FILE)
-USING_MINGW=@MIXED_CYGWIN_MINGW@
-USING_MSYS_VC==@MIXED_MSYS_VC@
-USING_CYGWIN_VC==@MIXED_MSYS_VC@
+USING_MINGW=@MIXED_MINGW@
USING_VC=@MIXED_VC@
ifdef TESTROOT
@@ -130,12 +128,7 @@ endif
WARNFLAGS += -DEI_NO_DEPR_WARN
CFLAGS = @LIB_CFLAGS@ $(WARNFLAGS) $(INCFLAGS) $(TYPE_FLAGS)
-PROG_CFLAGS = @CFLAGS@ $(WARNFLAGS) $(INCFLAGS) $(TYPE_FLAGS) -Ilegacy
-
-ifeq ($(findstring vxworks,$(TARGET)),vxworks)
-PROG_CFLAGS += -nostartfiles -Wl,-r,-d
-endif
-
+PROG_CFLAGS = @CFLAGS@ $(WARNFLAGS) $(INCFLAGS) $(TYPE_FLAGS) -Iglobal
INSTALL = @INSTALL@
INSTALL_DIR = @INSTALL_DIR@
@@ -161,7 +154,7 @@ BINDIR = $(ERL_TOP)/lib/erl_interface/bin/$(TARGET)
# -Wno-char-subscripts
# -Wshadow
-vpath %.c connect:encode:decode:misc:epmd:legacy:registry
+vpath %.c connect:encode:decode:misc:epmd:global:registry
###########################################################################
# List targets
@@ -193,17 +186,12 @@ ERL_CALL = $(BINDIR)/erl_call$(EXE)
ifdef THR_DEFS
ST_EILIB = $(OBJDIR)/$(LIBPRE)ei_st$(LIBEXT)
-ST_ERLLIB = $(OBJDIR)/$(LIBPRE)erl_interface_st$(LIBEXT)
MT_EILIB = $(OBJDIR)/$(LIBPRE)ei$(LIBEXT)
-MT_ERLLIB = $(OBJDIR)/$(LIBPRE)erl_interface$(LIBEXT)
else
ST_EILIB = $(OBJDIR)/$(LIBPRE)ei$(LIBEXT)
-ST_ERLLIB = $(OBJDIR)/$(LIBPRE)erl_interface$(LIBEXT)
endif
MD_EILIB = $(OBJDIR)/$(LIBPRE)ei_md$(LIBEXT)
MDD_EILIB = $(OBJDIR)/$(LIBPRE)ei_mdd$(LIBEXT)
-MD_ERLLIB = $(OBJDIR)/$(LIBPRE)erl_interface_md$(LIBEXT)
-MDD_ERLLIB = $(OBJDIR)/$(LIBPRE)erl_interface_mdd$(LIBEXT)
###########################################################################
# Specify targets to build
@@ -225,10 +213,7 @@ TARGETS = \
OBJ_TARGETS = \
$(MT_EILIB) \
$(MD_EILIB) \
- $(MDD_EILIB) \
- $(MT_ERLLIB) \
- $(MD_ERLLIB) \
- $(MDD_ERLLIB)
+ $(MDD_EILIB)
FAKE_TARGETS = \
$(OBJDIR)/erl_fake_prog_mt$(EXE) \
@@ -254,8 +239,7 @@ TARGETS = \
$(APPUP_TARGET)
OBJ_TARGETS = \
- $(MD_EILIB) \
- $(MD_ERLLIB)
+ $(MD_EILIB)
FAKE_TARGETS = \
$(OBJDIR)/erl_fake_prog_md$(EXE) \
@@ -275,9 +259,7 @@ TARGETS = \
OBJ_TARGETS = \
$(ST_EILIB) \
- $(ST_ERLLIB) \
- $(MT_EILIB) \
- $(MT_ERLLIB)
+ $(MT_EILIB)
FAKE_TARGETS = \
$(ST_OBJDIR)/erl_fake_prog_st$(EXE) \
@@ -298,8 +280,7 @@ TARGETS = \
$(APPUP_TARGET)
OBJ_TARGETS = \
- $(ST_EILIB) \
- $(ST_ERLLIB)
+ $(ST_EILIB)
FAKE_TARGETS = \
$(ST_OBJDIR)/erl_fake_prog_st$(EXE) \
@@ -321,8 +302,7 @@ endif
HEADERS = \
../include/ei.h \
../include/ei_connect.h \
- ../include/eicode.h \
- ../include/erl_interface.h
+ ../include/eicode.h
EISOURCES = \
$(CONNECTSRC) \
@@ -330,7 +310,8 @@ EISOURCES = \
$(ENCODESRC) \
$(EPMDSRC) \
$(MISCSRC) \
- $(REGISTRYSRC)
+ $(REGISTRYSRC) \
+ $(GLOBALSOURCES)
CONNECTSRC = \
connect/ei_connect.c \
@@ -350,6 +331,7 @@ DECODESRC = \
decode/decode_double.c \
decode/decode_fun.c \
decode/decode_intlist.c \
+ decode/decode_iodata.c \
decode/decode_list_header.c \
decode/decode_long.c \
decode/decode_pid.c \
@@ -363,14 +345,9 @@ DECODESRC = \
decode/decode_version.c \
$(DECODESRC_LONGLONG)
-ifneq ($(findstring vxworks,$(TARGET)),vxworks)
DECODESRC_LONGLONG = \
decode/decode_longlong.c \
decode/decode_ulonglong.c
-else
-DECODESRC_LONGLONG =
-endif
-
ENCODESRC = \
encode/encode_atom.c \
@@ -393,13 +370,9 @@ ENCODESRC = \
encode/encode_version.c \
$(ENCODESRC_LONGLONG)
-ifneq ($(findstring vxworks,$(TARGET)),vxworks)
ENCODESRC_LONGLONG = \
encode/encode_longlong.c \
encode/encode_ulonglong.c
-else
-ENCODESRC_LONGLONG =
-endif
EPMDSRC = \
@@ -421,7 +394,8 @@ MISCSRC = \
misc/get_type.c \
misc/show_msg.c \
misc/ei_compat.c \
- misc/ei_init.c
+ misc/ei_init.c \
+ misc/ei_cmp_nc.c
REGISTRYSRC = \
registry/hash_dohash.c \
@@ -457,24 +431,13 @@ REGISTRYSRC = \
registry/reg_stat.c \
registry/reg_tabstat.c
-ERLSOURCES = \
- legacy/decode_term.c \
- legacy/encode_term.c \
- legacy/erl_connect.c \
- legacy/erl_error.c \
- legacy/erl_eterm.c \
- legacy/erl_fix_alloc.c \
- legacy/erl_format.c \
- legacy/erl_malloc.c \
- legacy/erl_marshal.c \
- legacy/erl_resolve.c \
- legacy/erl_timeout.c \
- legacy/global_names.c \
- legacy/global_register.c \
- legacy/global_unregister.c \
- legacy/global_whereis.c
-
-SOURCES = $(EISOURCES) $(ERLSOURCES)
+GLOBALSOURCES = \
+ global/global_names.c \
+ global/global_register.c \
+ global/global_unregister.c \
+ global/global_whereis.c
+
+SOURCES = $(EISOURCES)
NEVERUSED = \
whereis.c \
@@ -491,13 +454,9 @@ ERLCALL = \
# located in the erl_interface library, not ei library.
ST_EIOBJECTS = $(addprefix $(ST_OBJDIR)/,$(notdir $(EISOURCES:.c=.o)))
-ST_ERLOBJECTS = $(addprefix $(ST_OBJDIR)/,$(notdir $(ERLSOURCES:.c=.o)))
MT_EIOBJECTS = $(addprefix $(MT_OBJDIR)/,$(notdir $(EISOURCES:.c=.o)))
-MT_ERLOBJECTS = $(addprefix $(MT_OBJDIR)/,$(notdir $(ERLSOURCES:.c=.o)))
MD_EIOBJECTS = $(addprefix $(MD_OBJDIR)/,$(notdir $(EISOURCES:.c=.o)))
-MD_ERLOBJECTS = $(addprefix $(MD_OBJDIR)/,$(notdir $(ERLSOURCES:.c=.o)))
MDD_EIOBJECTS = $(addprefix $(MDD_OBJDIR)/,$(notdir $(EISOURCES:.c=.o)))
-MDD_ERLOBJECTS = $(addprefix $(MDD_OBJDIR)/,$(notdir $(ERLSOURCES:.c=.o)))
###########################################################################
# Main targets
@@ -513,10 +472,10 @@ docs:
tests:
clean:
- rm -f $(ST_EIOBJECTS) $(ST_ERLOBJECTS) $(ST_EILIB) $(ST_ERLLIB)
- rm -f $(MT_EIOBJECTS) $(MT_ERLOBJECTS) $(MT_EILIB) $(MT_ERLLIB)
- rm -f $(MD_EIOBJECTS) $(MD_ERLOBJECTS) $(MD_EILIB) $(MD_ERLLIB)
- rm -f $(MDD_EIOBJECTS) $(MDD_ERLOBJECTS) $(MDD_EILIB) $(MDD_ERLLIB)
+ rm -f $(ST_EIOBJECTS) $(ST_EILIB)
+ rm -f $(MT_EIOBJECTS) $(MT_EILIB)
+ rm -f $(MD_EIOBJECTS) $(MD_EILIB)
+ rm -f $(MDD_EIOBJECTS) $(MDD_EILIB)
rm -f $(ERL_CALL)
rm -f $(FAKE_TARGETS)
rm -f $(APP_TARGET)
@@ -527,20 +486,6 @@ distclean: clean
###########################################################################
-# FIXME move this VxWorks stuff to configure or something
-###########################################################################
-
-# FIXME depend on $(TARGET)/Makefile ???
-
-ifeq ($(findstring vxworks,$(TARGET)),vxworks)
-$(TARGET)/config.h:
- $(gen_verbose)
- $(V_at)echo "/* Generated by Makefile */" > $@
- $(V_at)echo "#define HAVE_STRERROR 1" >> $@
- $(V_at)echo "#define HAVE_SOCKLEN_T 1" >> $@
-endif
-
-###########################################################################
# Default rules, normal and threaded
###########################################################################
@@ -582,34 +527,18 @@ $(ST_EILIB) : $(ST_EIOBJECTS)
$(V_AR) -out:$@ $(ST_EIOBJECTS)
$(V_RANLIB) $@
-$(ST_ERLLIB) : $(ST_ERLOBJECTS)
- $(V_AR) -out:$@ $(ST_ERLOBJECTS)
- $(V_RANLIB) $@
-
$(MT_EILIB) : $(MT_EIOBJECTS)
$(V_AR) -out:$@ $(MT_EIOBJECTS)
$(V_RANLIB) $@
-$(MT_ERLLIB) : $(MT_ERLOBJECTS)
- $(V_AR) -out:$@ $(MT_ERLOBJECTS)
- $(V_RANLIB) $@
-
$(MD_EILIB) : $(MD_EIOBJECTS)
$(V_AR) -out:$@ $(MD_EIOBJECTS)
$(V_RANLIB) $@
-$(MD_ERLLIB) : $(MD_ERLOBJECTS)
- $(V_AR) -out:$@ $(MD_ERLOBJECTS)
- $(V_RANLIB) $@
-
$(MDD_EILIB) : $(MDD_EIOBJECTS)
$(V_AR) -out:$@ $(MDD_EIOBJECTS)
$(V_RANLIB) $@
-$(MDD_ERLLIB) : $(MDD_ERLOBJECTS)
- $(V_AR) -out:$@ $(MDD_ERLOBJECTS)
- $(V_RANLIB) $@
-
else
# Unix archive creation
@@ -621,12 +550,6 @@ ifdef RANLIB
$(V_RANLIB) $@
endif
-$(ST_ERLLIB) : $(ST_ERLOBJECTS)
- $(V_at)rm -f $@
- $(V_AR) $(AR_FLAGS) $@ $(ST_ERLOBJECTS)
-ifdef RANLIB
- $(V_RANLIB) $@
-endif
$(MT_EILIB) : $(MT_EIOBJECTS)
$(V_at)rm -f $@
@@ -635,13 +558,6 @@ ifdef RANLIB
$(V_RANLIB) $@
endif
-$(MT_ERLLIB) : $(MT_ERLOBJECTS)
- $(V_at)rm -f $@
- $(V_AR) $(AR_FLAGS) $@ $(MT_ERLOBJECTS)
-ifdef RANLIB
- $(V_RANLIB) $@
-endif
-
endif
###########################################################################
@@ -653,17 +569,6 @@ $(ERL_CALL): $(ERLCALL) ../include/ei.h $(MD_EILIB)
$(ld_verbose)$(PURIFY) $(CC) -MD $(PROG_CFLAGS) $(THR_DEFS) -o $@ $(ERLCALL) \
-L$(OBJDIR) -lei_md $(THR_LIBS) $(LIBS) -lsocket
else
-ifeq ($(findstring vxworks,$(TARGET)),vxworks)
-$(ERL_CALL): $(ST_OBJDIR)/erl_call.o $(ST_OBJDIR)/erl_start.o ../include/ei.h $(ST_EILIB)
- $(V_LD) -r -d -o $@ $(ST_OBJDIR)/erl_call.o $(ST_OBJDIR)/erl_start.o -L$(OBJDIR) -lei $(LIBS)
-
-$(ST_OBJDIR)/erl_call.o: prog/erl_call.c
- $(V_CC) $(CFLAGS) -c $< -o $@
-
-$(ST_OBJDIR)/erl_start.o: prog/erl_start.c
- $(V_CC) $(CFLAGS) -c $< -o $@
-
-else
ifdef THR_DEFS
$(ERL_CALL): $(ERLCALL) ../include/ei.h $(MT_EILIB)
$(ld_verbose)$(PURIFY) $(CC) $(PROG_CFLAGS) $(THR_DEFS) $(LDFLAGS) -o $@ $(ERLCALL) \
@@ -674,7 +579,6 @@ $(ERL_CALL): $(ERLCALL) ../include/ei.h $(ST_EILIB)
-L$(OBJDIR) -lei $(LIBS)
endif
endif
-endif
###########################################################################
# Fake application targets used to test header files and linking
@@ -683,7 +587,7 @@ endif
check: $(FAKE_TARGETS)
ifndef THR_DEFS
-$(ST_OBJDIR)/erl_fake_prog_st$(EXE): prog/erl_fake_prog.c $(ST_ERLLIB) $(ST_EILIB)
+$(ST_OBJDIR)/erl_fake_prog_st$(EXE): prog/erl_fake_prog.c $(ST_EILIB)
$(V_CC) $(PROG_CFLAGS) -o $@ $< -L$(OBJDIR) -lerl_interface -lei \
$(LIBS)
@@ -691,7 +595,7 @@ $(ST_OBJDIR)/ei_fake_prog_st$(EXE): prog/ei_fake_prog.c $(ST_EILIB)
$(V_CC) $(PROG_CFLAGS) -o $@ $< -L$(OBJDIR) -lei $(LIBS)
$(ST_OBJDIR)/erl_fake_prog_cxx_st$(EXE): prog/erl_fake_prog.c \
- $(ST_ERLLIB) $(ST_EILIB)
+ $(ST_EILIB)
$(V_CC) $(PROG_CFLAGS) -o $@ -xc++ $< -L$(OBJDIR) \
-lerl_interface -lei $(LIBS)
@@ -700,7 +604,7 @@ $(ST_OBJDIR)/ei_fake_prog_cxx_st$(EXE): prog/ei_fake_prog.c $(ST_EILIB)
else
-$(ST_OBJDIR)/erl_fake_prog_st$(EXE): prog/erl_fake_prog.c $(ST_ERLLIB) $(ST_EILIB)
+$(ST_OBJDIR)/erl_fake_prog_st$(EXE): prog/erl_fake_prog.c $(ST_EILIB)
$(V_CC) $(PROG_CFLAGS) -o $@ $< -L$(OBJDIR) -lerl_interface_st -lei_st \
$(LIBS)
@@ -708,7 +612,7 @@ $(ST_OBJDIR)/ei_fake_prog_st$(EXE): prog/ei_fake_prog.c $(ST_EILIB)
$(V_CC) $(PROG_CFLAGS) -o $@ $< -L$(OBJDIR) -lei_st $(LIBS)
$(ST_OBJDIR)/erl_fake_prog_cxx_st$(EXE): prog/erl_fake_prog.c \
- $(ST_ERLLIB) $(ST_EILIB)
+ $(ST_EILIB)
$(V_CC) $(PROG_CFLAGS) -o $@ -xc++ $< -L$(OBJDIR) \
-lerl_interface_st -lei_st $(LIBS)
@@ -720,7 +624,7 @@ endif
####
$(MT_OBJDIR)/erl_fake_prog_mt$(EXE): prog/erl_fake_prog.c \
- $(MT_ERLLIB) $(MT_EILIB)
+ $(MT_EILIB)
$(V_CC) $(MTFLAG) $(PROG_CFLAGS) $(THR_DEFS) -o $@ $< -L$(OBJDIR) \
-lerl_interface -lei $(THR_LIBS) $(LIBS)
@@ -729,7 +633,7 @@ $(MT_OBJDIR)/ei_fake_prog_mt$(EXE): prog/ei_fake_prog.c $(MT_EILIB)
-L$(OBJDIR) -lei $(THR_LIBS) $(LIBS)
$(MT_OBJDIR)/erl_fake_prog_mt_cxx$(EXE): prog/erl_fake_prog.c \
- $(MT_ERLLIB) $(MT_EILIB)
+ $(MT_EILIB)
$(V_CC) $(MTFLAG) $(PROG_CFLAGS) $(THR_DEFS) -o $@ -xc++ $< \
-L$(OBJDIR) -lerl_interface -lei \
$(THR_LIBS) $(LIBS)
@@ -741,7 +645,7 @@ $(MT_OBJDIR)/ei_fake_prog_mt_cxx$(EXE): prog/ei_fake_prog.c $(MT_EILIB)
####
$(MD_OBJDIR)/erl_fake_prog_md$(EXE): prog/erl_fake_prog.c \
- $(MD_ERLLIB) $(MD_EILIB)
+ $(MD_EILIB)
$(V_CC) -MD $(PROG_CFLAGS) $(THR_DEFS) -o $@ $< -L$(OBJDIR) \
-lerl_interface_r -lei_r $(THR_LIBS) $(LIBS)
@@ -750,7 +654,7 @@ $(MD_OBJDIR)/ei_fake_prog_md$(EXE): prog/ei_fake_prog.c $(MD_EILIB)
-L$(OBJDIR) -lei_r $(THR_LIBS) $(LIBS)
$(MD_OBJDIR)/erl_fake_prog_md_cxx$(EXE): prog/erl_fake_prog.c \
- $(MD_ERLLIB) $(MD_EILIB)
+ $(MD_EILIB)
$(V_CC) -MD $(PROG_CFLAGS) $(THR_DEFS) -o $@ -xc++ $< \
-L$(OBJDIR) -lerl_interface_r -lei_r \
$(THR_LIBS) $(LIBS)
@@ -762,7 +666,7 @@ $(MD_OBJDIR)/ei_fake_prog_md_cxx$(EXE): prog/ei_fake_prog.c $(MD_EILIB)
####
$(MDD_OBJDIR)/erl_fake_prog_mdd$(EXE): prog/erl_fake_prog.c \
- $(MDD_ERLLIB) $(MDD_EILIB)
+ $(MDD_EILIB)
$(V_CC) -MDD $(PROG_CFLAGS) $(THR_DEFS) -o $@ $< -L$(OBJDIR) \
-lerl_interface_r -lei_r $(THR_LIBS) $(LIBS)
@@ -771,7 +675,7 @@ $(MDD_OBJDIR)/ei_fake_prog_mdd$(EXE): prog/ei_fake_prog.c $(MDD_EILIB)
-L$(OBJDIR) -lei_r $(THR_LIBS) $(LIBS)
$(MDD_OBJDIR)/erl_fake_prog_mdd_cxx$(EXE): prog/erl_fake_prog.c \
- $(MDD_ERLLIB) $(MDD_EILIB)
+ $(MDD_EILIB)
$(V_CC) -MDD $(PROG_CFLAGS) $(THR_DEFS) -o $@ -xc++ $< \
-L$(OBJDIR) -lerl_interface_r -lei_r \
$(THR_LIBS) $(LIBS)
@@ -784,32 +688,30 @@ $(MDD_OBJDIR)/ei_fake_prog_mdd_cxx$(EXE): prog/ei_fake_prog.c $(MDD_EILIB)
# Create dependency file using gcc -MM
###########################################################################
+ifneq ($(ERTS_SKIP_DEPEND),true)
depend: $(TARGET)/depend.mk
$(TARGET)/depend.mk: $(TARGET)/config.h
$(gen_verbose)
$(V_colon)echo "Generating dependency file depend.mk..."
@echo "# Generated dependency rules" > $@
- $(V_CC) $(CFLAGS) -MM $(SOURCES) | \
- sed 's&$(TARGET)&\$$\(TARGET\)&g' | \
- sed 's/^.*:/\$$\(ST_OBJDIR\)\/&/' >> $@
- @echo >> $@
- $(V_CC) $(CFLAGS) -MM $(SOURCES) | \
- sed 's&$(TARGET)&\$$\(TARGET\)&g' | \
- sed 's/^.*:/\$$\(MT_OBJDIR\)\/&/' >> $@
- @echo >> $@
- $(V_CC) $(CFLAGS) -MM $(SOURCES) | \
- sed 's&$(TARGET)&\$$\(TARGET\)&g' | \
- sed 's/^.*:/\$$\(MD_OBJDIR\)\/&/' >> $@
- @echo >> $@
- $(V_CC) $(CFLAGS) -MM $(SOURCES) | \
- sed 's&$(TARGET)&\$$\(TARGET\)&g' | \
- sed 's/^.*:/\$$\(MDD_OBJDIR\)\/&/' >> $@
- @echo >> $@
+ $(V_CC) $(CFLAGS) -MM $(SOURCES) | \
+ sed 's&$(TARGET)&\$$\(TARGET\)&g' > $@.$$$$; \
+ sed 's/^.*:/\$$\(ST_OBJDIR\)\/&/' < $@.$$$$ >> $@; \
+ echo >> $@; \
+ sed 's/^.*:/\$$\(MT_OBJDIR\)\/&/' < $@.$$$$ >> $@; \
+ echo >> $@; \
+ sed 's/^.*:/\$$\(MD_OBJDIR\)\/&/' < $@.$$$$ >> $@; \
+ echo >> $@; \
+ sed 's/^.*:/\$$\(MDD_OBJDIR\)\/&/' < $@.$$$$ >> $@; \
+ echo >> $@; \
+ rm -f $@.$$$$
# For some reason this has to be after 'opt' target
-include $(TARGET)/depend.mk
-
+else
+depend:
+endif
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -832,7 +734,7 @@ release: opt
$(INSTALL_DIR) "$(RELSYSDIR)/src/decode"
$(INSTALL_DIR) "$(RELSYSDIR)/src/encode"
$(INSTALL_DIR) "$(RELSYSDIR)/src/epmd"
- $(INSTALL_DIR) "$(RELSYSDIR)/src/legacy"
+ $(INSTALL_DIR) "$(RELSYSDIR)/src/global"
$(INSTALL_DIR) "$(RELSYSDIR)/src/misc"
$(INSTALL_DIR) "$(RELSYSDIR)/src/prog"
$(INSTALL_DIR) "$(RELSYSDIR)/src/registry"
@@ -844,9 +746,7 @@ release: opt
$(INSTALL_DATA) $(HEADERS) "$(RELEASE_PATH)/usr/include"
$(INSTALL_DATA) $(OBJ_TARGETS) "$(RELSYSDIR)/lib"
$(INSTALL_DATA) $(OBJ_TARGETS) "$(RELEASE_PATH)/usr/lib"
-ifneq ($(EXE_TARGETS),)
$(INSTALL_PROGRAM) $(EXE_TARGETS) "$(RELSYSDIR)/bin"
-endif
$(INSTALL_DATA) $(EXTRA) "$(RELSYSDIR)/src"
$(INSTALL_DATA) connect/*.[ch] "$(RELSYSDIR)/src/connect"
$(INSTALL_DATA) decode/*.[ch] "$(RELSYSDIR)/src/decode"
@@ -854,7 +754,7 @@ endif
$(INSTALL_DATA) epmd/*.[ch] "$(RELSYSDIR)/src/epmd"
$(INSTALL_DATA) misc/*.[ch] "$(RELSYSDIR)/src/misc"
$(INSTALL_DATA) registry/*.[ch] "$(RELSYSDIR)/src/registry"
- $(INSTALL_DATA) legacy/*.[ch] "$(RELSYSDIR)/src/legacy"
+ $(INSTALL_DATA) global/*.[ch] "$(RELSYSDIR)/src/global"
$(INSTALL_DATA) prog/*.[ch] "$(RELSYSDIR)/src/prog"
release_docs:
diff --git a/lib/erl_interface/src/README b/lib/erl_interface/src/README
index 7591615f78..823575c6b7 100644
--- a/lib/erl_interface/src/README
+++ b/lib/erl_interface/src/README
@@ -21,7 +21,7 @@ The cause of an assertion can be either errors in the application
program (for instance, passing NULL pointers to any function that
expects an ETERM pointer) or in erl_interface itself.
-If you encounter any assertion failures which think you originate in
+If you encounter any assertion failures which you think originate in
erl_interface, I'll need the following information to track it down:
1a) The printout from the assertion, especially filename and line number.
@@ -35,7 +35,7 @@ erl_interface, I'll need the following information to track it down:
Changes in this version
-----------------------
-There is now *one* representation for an empty list, not two as is used
+There is now *one* representation for an empty list, not two as it used
to be. An empty list is represented by the Erl_EmptyList structure.
There are new macros, ERL_IS_EMPTY_LIST(ep), to test for an empty list,
and ERL_IS_CONS(ep) to test for a cons cell (more on that below).
diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c
index 61437f1edb..f5471777b0 100644
--- a/lib/erl_interface/src/connect/ei_connect.c
+++ b/lib/erl_interface/src/connect/ei_connect.c
@@ -32,26 +32,7 @@
#include <windows.h>
#include <winbase.h>
-#elif VXWORKS
-#include <vxWorks.h>
-#include <hostLib.h>
-#include <selectLib.h>
-#include <ifLib.h>
-#include <sockLib.h>
-#include <taskLib.h>
-#include <inetLib.h>
-
-#include <unistd.h>
-#include <sys/times.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <timers.h>
-
-#define getpid() taskIdSelf()
-
-#else /* some other unix */
+#else /* some unix */
#include <unistd.h>
#include <sys/times.h>
@@ -106,7 +87,6 @@ int ei_tracelevel = 0;
(offsetof(ei_socket_callbacks, get_fd) \
+ sizeof(int (*)(void *)))
-/* FIXME why not macro? */
static char *null_cookie = "";
static int get_cookie(char *buf, int len);
@@ -118,17 +98,19 @@ static void gen_digest(unsigned challenge, char cookie[],
unsigned char digest[16]);
static int send_status(ei_socket_callbacks *cbs, void *ctx,
int pkt_sz, char *status, unsigned ms);
-static int recv_status(ei_socket_callbacks *cbs, void *ctx,
+static int recv_status(ei_cnode*, void *ctx,
int pkt_sz, unsigned ms);
-static int send_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
- char *nodename, unsigned challenge,
- unsigned version, unsigned ms);
+static int send_challenge(ei_cnode *ec, void *ctx, int pkt_sz,
+ unsigned challenge,
+ DistFlags version, unsigned ms);
static int recv_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
unsigned *challenge, unsigned *version,
- unsigned *flags, char *namebuf, unsigned ms);
+ DistFlags *flags, char *namebuf, unsigned ms);
static int send_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
int pkt_sz, unsigned char digest[16],
unsigned challenge, unsigned ms);
+static int recv_complement(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned ms);
static int recv_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
int pkt_sz, unsigned our_challenge,
char cookie[],
@@ -139,12 +121,20 @@ static int send_challenge_ack(ei_socket_callbacks *cbs, void *ctx,
static int recv_challenge_ack(ei_socket_callbacks *cbs, void *ctx,
int pkt_sz, unsigned our_challenge,
char cookie[], unsigned ms);
-static int send_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
- char *nodename, unsigned version, unsigned ms);
-
+static int send_name(ei_cnode *ec, void *ctx, int pkt_sz,
+ unsigned version, unsigned ms);
+static int send_complement(ei_cnode *ec, void *ctx, int pkt_sz,
+ unsigned epmd_says_version, DistFlags her_flags,
+ unsigned ms);
static int recv_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
- unsigned *version, unsigned *flags, char *namebuf,
- unsigned ms);
+ char* send_name_tag, DistFlags *flags,
+ char *namebuf, unsigned ms);
+static int ei_connect_helper(ei_cnode* ec,
+ Erl_IpAddr ip_addr,
+ char *alivename,
+ unsigned ms,
+ int rport,
+ int epmd_says_version);
static struct hostent*
dyn_gethostbyname_r(const char *name, struct hostent *hostp, char **buffer_p,
@@ -450,7 +440,7 @@ static ei_socket_info* get_ei_socket_info(int fd)
static int remove_ei_socket_info(int fd)
{
- return put_ei_socket_info(fd, -1, NULL, NULL, NULL, NULL);
+ return put_ei_socket_info(fd, -1, null_cookie, NULL, NULL, NULL);
}
ei_cnode *ei_fd_to_cnode(int fd)
@@ -535,11 +525,272 @@ const char *ei_thiscookie(const ei_cnode* ec)
return (const char *)ec->ei_connect_cookie;
}
+static int
+check_initialized_node(ei_cnode *ec)
+{
+ /*
+ * Try to guard against returning garbage pids and refs
+ * by verifying that the node has got its name...
+ */
+ int i, at, end;
+ char *nodename = &ec->thisnodename[0];
+
+ for (i = at = end = 0; i < sizeof(ec->thisnodename); i++) {
+ if (!nodename[i]) {
+ end = !0;
+ break;
+ }
+ if (nodename[i] == '@')
+ at = !0;
+ }
+
+ if (!at || !end) {
+ erl_errno = EINVAL;
+ return ERL_ERROR;
+ }
+
+ return 0;
+}
+
erlang_pid *ei_self(ei_cnode* ec)
{
+ int err = check_initialized_node(ec);
+ if (err)
+ return NULL;
return &ec->self;
}
+/*
+ * ei_make_pid()
+ */
+
+#undef EI_MAKE_PID_ATOMIC__
+#ifdef _REENTRANT
+# if (SIZEOF_INT == 4 \
+ && (ETHR_HAVE___atomic_compare_exchange_n & 4) \
+ && (ETHR_HAVE___atomic_load_n & 4))
+# define EI_MAKE_PID_ATOMIC__
+# else /* !EI_MAKE_PID_ATOMIC__ */
+static ei_mutex_t *pid_mtx = NULL;
+# endif /* !EI_MAKE_PID_ATOMIC__ */
+#endif /* _REENTRANT */
+
+static int
+init_make_pid(int late)
+{
+#if defined(_REENTRANT) && !defined(EI_MAKE_PID_ATOMIC__)
+
+ if (late)
+ return ENOTSUP; /* Refuse doing unsafe initialization... */
+
+ pid_mtx = ei_mutex_create();
+ if (!pid_mtx)
+ return ENOMEM;
+
+#endif /* _REENTRANT */
+
+ return 0;
+}
+
+int ei_make_pid(ei_cnode *ec, erlang_pid *pid)
+{
+ unsigned int new;
+ int err;
+
+ if (!ei_connect_initialized) {
+ fprintf(stderr,"<ERROR> erl_interface not initialized\n");
+ exit(1);
+ }
+
+ err = check_initialized_node(ec);
+ if (err) {
+ /*
+ * write invalid utf8 in nodename which will make
+ * ei_encode_pid() fail if used...
+ */
+ pid->node[0] = 0xff;
+ pid->node[1] = 0;
+ pid->serial = -1;
+ pid->num = -1;
+ return err;
+ }
+
+ strcpy(pid->node, ec->thisnodename);
+ pid->creation = ec->creation;
+
+ /*
+ * We avoid creating pids with serial set to 0 since the
+ * documentation previously gave some really bad advise
+ * of modifying the 'num' field in the pid returned by
+ * ei_self(). Since 'serial' field in pid returned by
+ * ei_self() is initialized to 0, pids created by
+ * ei_make_pid() wont clash with such badly created pids
+ * using ei_self() unless user also modified serial, but
+ * that has at least never been suggested by the
+ * documentation.
+ */
+
+#ifdef EI_MAKE_PID_ATOMIC__
+ {
+ unsigned int xchg = __atomic_load_n(&ec->pidsn, __ATOMIC_RELAXED);
+ do {
+ new = xchg + 1;
+ if ((new & 0x0fff8000) == 0)
+ new = 0x8000; /* serial==0 -> serial=1 num=0 */
+ } while(!__atomic_compare_exchange_n(&ec->pidsn, &xchg, new, 0,
+ __ATOMIC_ACQ_REL,
+ __ATOMIC_RELAXED));
+ }
+#else /* !EI_MAKE_PID_ATOMIC__ */
+
+#ifdef _REENTRANT
+ ei_mutex_lock(pid_mtx, 0);
+#endif
+
+ new = ec->pidsn + 1;
+ if ((new & 0x0fff8000) == 0)
+ new = 0x8000; /* serial==0 -> serial=1 num=0 */
+
+ ec->pidsn = new;
+
+#ifdef _REENTRANT
+ ei_mutex_unlock(pid_mtx);
+#endif
+
+#endif /* !EI_MAKE_PID_ATOMIC__ */
+
+ pid->num = new & 0x7fff; /* 15-bits */
+ pid->serial = (new >> 15) & 0x1fff; /* 13-bits */
+
+ return 0;
+}
+
+/*
+ * ei_make_ref()
+ */
+
+#undef EI_MAKE_REF_ATOMIC__
+#ifdef _REENTRANT
+# if ((SIZEOF_LONG == 8 || SIZEOF_LONGLONG == 8) \
+ && (ETHR_HAVE___atomic_compare_exchange_n & 8) \
+ && (ETHR_HAVE___atomic_load_n & 8))
+# define EI_MAKE_REF_ATOMIC__
+# if SIZEOF_LONG == 8
+typedef unsigned long ei_atomic_ref__;
+# else
+typedef unsigned long long ei_atomic_ref__;
+# endif
+# else /* !EI_MAKE_REF_ATOMIC__ */
+static ei_mutex_t *ref_mtx = NULL;
+# endif /* !EI_MAKE_REF_ATOMIC__ */
+#endif /* _REENTRANT */
+
+/*
+ * We use a global counter for all c-nodes in this process.
+ * We wont wrap anyway due to the enormous amount of values
+ * available.
+ */
+#ifdef EI_MAKE_REF_ATOMIC__
+static ei_atomic_ref__ ref_count;
+#else
+static unsigned int ref_count[3];
+#endif
+
+static int
+init_make_ref(int late)
+{
+
+#ifdef EI_MAKE_REF_ATOMIC__
+ ref_count = 0;
+#else /* !EI_MAKE_REF_ATOMIC__ */
+
+#ifdef _REENTRANT
+
+ if (late)
+ return ENOTSUP; /* Refuse doing unsafe initialization... */
+
+ ref_mtx = ei_mutex_create();
+ if (!ref_mtx)
+ return ENOMEM;
+
+#endif /* _REENTRANT */
+
+ ref_count[0] = 0;
+ ref_count[1] = 0;
+ ref_count[2] = 0;
+
+#endif /* !EI_MAKE_REF_ATOMIC__ */
+
+ return 0;
+}
+
+int ei_make_ref(ei_cnode *ec, erlang_ref *ref)
+{
+ int err;
+ if (!ei_connect_initialized) {
+ fprintf(stderr,"<ERROR> erl_interface not initialized\n");
+ exit(1);
+ }
+
+ err = check_initialized_node(ec);
+ if (err) {
+ /*
+ * write invalid utf8 in nodename which will make
+ * ei_encode_ref() fail if used...
+ */
+ ref->node[0] = 0xff;
+ ref->node[1] = 0;
+ ref->len = -1;
+ return err;
+ }
+
+ strcpy(ref->node, ec->thisnodename);
+ ref->creation = ec->creation;
+ ref->len = 3;
+
+#ifdef EI_MAKE_REF_ATOMIC__
+ {
+ ei_atomic_ref__ xchg, new;
+ xchg = __atomic_load_n(&ref_count, __ATOMIC_RELAXED);
+ do {
+ new = xchg + 1;
+ } while(!__atomic_compare_exchange_n(&ref_count, &xchg, new, 0,
+ __ATOMIC_ACQ_REL,
+ __ATOMIC_RELAXED));
+ ref->n[0] = (unsigned int) (new & 0x3ffff);
+ ref->n[1] = (unsigned int) ((new >> 18) & 0xffffffff);
+ ref->n[2] = (unsigned int) ((new >> (18+32)) & 0xffffffff);
+ }
+#else /* !EI_MAKE_REF_ATOMIC__ */
+
+#ifdef _REENTRANT
+ ei_mutex_lock(ref_mtx, 0);
+#endif
+
+ ref->n[0] = ref_count[0];
+ ref->n[1] = ref_count[1];
+ ref->n[2] = ref_count[2];
+
+ ref_count[0]++;
+ ref_count[0] &= 0x3ffff;
+ if (ref_count[0] == 0) {
+ ref_count[1]++;
+ ref_count[1] &= 0xffffffff;
+ if (ref_count[1] == 0) {
+ ref_count[2]++;
+ ref_count[2] &= 0xffffffff;
+ }
+ }
+
+#ifdef _REENTRANT
+ ei_mutex_unlock(ref_mtx);
+#endif
+
+#endif /* !EI_MAKE_REF_ATOMIC__ */
+
+ return 0;
+}
+
/* two internal functions that will let us support different cookies
* (to be able to connect to other nodes that don't have the same
* cookie as each other or us)
@@ -626,6 +877,18 @@ static int init_connect(int late)
return error;
}
+ error = init_make_ref(late);
+ if (error) {
+ EI_TRACE_ERR0("ei_init_connect","can't initiate ei_make_ref()");
+ return error;
+ }
+
+ error = init_make_pid(late);
+ if (error) {
+ EI_TRACE_ERR0("ei_init_connect","can't initiate ei_make_pid()");
+ return error;
+ }
+
ei_connect_initialized = !0;
return 0;
}
@@ -659,7 +922,8 @@ int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname,
return ERL_ERROR;
}
- ec->creation = creation & 0x3; /* 2 bits */
+ ec->creation = creation;
+ ec->pidsn = 0;
if (cookie) {
if (strlen(cookie) >= sizeof(ec->ei_connect_cookie)) {
@@ -679,26 +943,34 @@ int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname,
}
strcpy(ec->thishostname, thishostname);
- if (strlen(thisalivename) >= sizeof(ec->thisalivename)) {
- EI_TRACE_ERR0("ei_connect_init","Thisalivename too long");
- return ERL_ERROR;
+ if (thisalivename) {
+ if (strlen(thisalivename) >= sizeof(ec->thisalivename)) {
+ EI_TRACE_ERR0("ei_connect_init","Thisalivename too long");
+ return ERL_ERROR;
+ }
+
+ strcpy(ec->thisalivename, thisalivename);
+
+ if (strlen(thisnodename) >= sizeof(ec->thisnodename)) {
+ EI_TRACE_ERR0("ei_connect_init","Thisnodename too long");
+ return ERL_ERROR;
+ }
+ strcpy(ec->thisnodename, thisnodename);
+
+ strcpy(ec->self.node, thisnodename);
+ ec->self.num = 0;
+ ec->self.serial = 0;
+ ec->self.creation = creation;
}
-
- strcpy(ec->thisalivename, thisalivename);
-
- if (strlen(thisnodename) >= sizeof(ec->thisnodename)) {
- EI_TRACE_ERR0("ei_connect_init","Thisnodename too long");
- return ERL_ERROR;
+ else {
+ /* dynamic name */
+ ec->thisalivename[0] = 0;
+ ec->thisnodename[0] = 0;
}
- strcpy(ec->thisnodename, thisnodename);
/* FIXME right now this_ipaddr is never used */
-/* memmove(&ec->this_ipaddr, thisipaddr, sizeof(ec->this_ipaddr)); */
+ /* memmove(&ec->this_ipaddr, thisipaddr, sizeof(ec->this_ipaddr)); */
- strcpy(ec->self.node,thisnodename);
- ec->self.num = 0;
- ec->self.serial = 0;
- ec->self.creation = creation & 0x3; /* 2 bits */
ec->cbs = cbs;
ec->setup_context = setup_context;
@@ -874,78 +1146,57 @@ struct hostent *dyn_gethostbyname_r(const char *name,
#endif
}
- /*
- * Set up a connection to a given Node, and
- * interchange hand shake messages with it.
- * Returns a valid file descriptor at success,
- * otherwise a negative error code.
-*/
-int ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned ms)
+/* Finds the the IP address for hostname and saves that IP address at
+ the location that ip_wb points to. Returns a negative error code if
+ the IP address cannot be found for the hostname. */
+static int ip_address_from_hostname(char* hostname,
+ char** buffer_p,
+ size_t buffer_size,
+ Erl_IpAddr* ip_wb)
{
- char *hostname, alivename[BUFSIZ];
struct hostent *hp;
-#if !defined (__WIN32__)
+#ifndef __WIN32__
/* these are needed for the call to gethostbyname_r */
struct hostent host;
- char buffer[1024];
- char *buf = buffer;
int ei_h_errno;
-#endif /* !win32 */
- int res;
-
- if (strlen(nodename) > MAXNODELEN) {
- EI_TRACE_ERR0("ei_connect","Too long nodename");
- return ERL_ERROR;
- }
-
- /* extract the host and alive parts from nodename */
- if (!(hostname = strchr(nodename,'@'))) {
- EI_TRACE_ERR0("ei_connect","Node name has no @ in name");
- return ERL_ERROR;
- } else {
- strncpy(alivename, nodename, hostname - nodename);
- alivename[hostname - nodename] = 0x0;
- hostname++;
- }
-
-#ifndef __WIN32__
- hp = dyn_gethostbyname_r(hostname,&host,&buf,sizeof(buffer),&ei_h_errno);
+ hp = dyn_gethostbyname_r(hostname,&host,buffer_p,buffer_size,&ei_h_errno);
if (hp == NULL) {
char thishostname[EI_MAXHOSTNAMELEN+1];
/* gethostname requies len to be max(hostname) + 1*/
if (gethostname(thishostname,EI_MAXHOSTNAMELEN+1) < 0) {
- EI_TRACE_ERR0("ei_connect_tmo",
+ EI_TRACE_ERR0("ip_address_from_hostname",
"Failed to get name of this host");
erl_errno = EHOSTUNREACH;
return ERL_ERROR;
} else {
char *ct;
- /* We use a short node name */
+ /* We use a short node name */
if ((ct = strchr(thishostname, '.')) != NULL) *ct = '\0';
}
if (strcmp(hostname,thishostname) == 0)
/* Both nodes on same standalone host, use loopback */
- hp = dyn_gethostbyname_r("localhost",&host,&buf,sizeof(buffer),&ei_h_errno);
+ hp = dyn_gethostbyname_r("localhost",&host,buffer_p,buffer_size,&ei_h_errno);
if (hp == NULL) {
EI_TRACE_ERR2("ei_connect",
- "Can't find host for %s: %d\n",nodename,ei_h_errno);
+ "Can't find host for %s: %d\n",hostname,ei_h_errno);
erl_errno = EHOSTUNREACH;
return ERL_ERROR;
}
}
+ *ip_wb = (Erl_IpAddr) *hp->h_addr_list;
#else /* __WIN32__ */
if ((hp = ei_gethostbyname(hostname)) == NULL) {
char thishostname[EI_MAXHOSTNAMELEN+1];
/* gethostname requires len to be max(hostname) + 1 */
if (gethostname(thishostname,EI_MAXHOSTNAMELEN+1) < 0) {
- EI_TRACE_ERR1("ei_connect_tmo",
- "Failed to get name of this host: %d",
+ EI_TRACE_ERR1("ip_address_from_hostname",
+ "Failed to get name of this host: %d",
WSAGetLastError());
erl_errno = EHOSTUNREACH;
return ERL_ERROR;
} else {
char *ct;
- /* We use a short node name */
+ /* We use a short node name */
if ((ct = strchr(thishostname, '.')) != NULL) *ct = '\0';
}
if (strcmp(hostname,thishostname) == 0)
@@ -955,42 +1206,29 @@ int ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned ms)
char reason[1024];
win32_error(reason,sizeof(reason));
EI_TRACE_ERR2("ei_connect",
- "Can't find host for %s: %s",nodename,reason);
+ "Can't find host for %s: %s",hostname,reason);
erl_errno = EHOSTUNREACH;
return ERL_ERROR;
}
}
+ *ip_wb = (Erl_IpAddr) *hp->h_addr_list;
#endif /* win32 */
-
- res = ei_xconnect_tmo(ec, (Erl_IpAddr) *hp->h_addr_list, alivename, ms);
-
-#ifndef __WIN32__
- if (buf != buffer)
- free(buf);
-#endif
- return res;
-} /* ei_connect */
-
-int ei_connect(ei_cnode* ec, char *nodename)
-{
- return ei_connect_tmo(ec, nodename, 0);
+ return 0;
}
-
- /* ip_addr is now in network byte order
- *
- * first we have to get hold of the portnumber to
- * the node through epmd at that host
- *
-*/
-int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename, unsigned ms)
+/* Helper function for ei_connect family of functions */
+static int ei_connect_helper(ei_cnode* ec,
+ Erl_IpAddr ip_addr, /* network byte order */
+ char *alivename,
+ unsigned ms,
+ int rport,
+ int epmd_says_version)
{
ei_socket_callbacks *cbs = ec->cbs;
void *ctx;
- int rport = 0; /*uint16 rport = 0;*/
int sockd;
- int dist = 0;
- unsigned her_flags, her_version;
+ unsigned her_version;
+ DistFlags her_flags;
unsigned our_challenge, her_challenge;
unsigned char our_digest[16];
int err;
@@ -999,21 +1237,27 @@ int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename, unsigned
unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
erl_errno = EIO; /* Default error code */
-
- EI_TRACE_CONN1("ei_xconnect","-> CONNECT attempt to connect to %s",
- alivename);
-
- if ((rport = ei_epmd_port_tmo(ip_addr,alivename,&dist, tmo)) < 0) {
- EI_TRACE_ERR0("ei_xconnect","-> CONNECT can't get remote port");
- /* ei_epmd_port_tmo() has set erl_errno */
- return ERL_NO_PORT;
+
+ if (alivename != NULL) {
+ EI_TRACE_CONN1("ei_xconnect","-> CONNECT attempt to connect to %s",
+ alivename);
+ } else {
+ EI_TRACE_CONN1("ei_xconnect","-> CONNECT attempt to connect to port %d",
+ rport);
}
- if (dist <= 4) {
- EI_TRACE_ERR0("ei_xconnect","-> CONNECT remote version not compatible");
+ if (epmd_says_version < EI_DIST_LOW) {
+ EI_TRACE_ERR1("ei_xconnect","-> CONNECT remote version %d not compatible",
+ epmd_says_version);
return ERL_ERROR;
}
+ if (!ec->thisnodename[0] && epmd_says_version < EI_DIST_6) {
+ /* This is a dynamic node name. We have to use at least vsn 6
+ of the dist protocol for this to work. */
+ epmd_says_version = EI_DIST_6;
+ }
+
err = ei_socket_ctx__(cbs, &ctx, ec->setup_context);
if (err) {
EI_TRACE_ERR2("ei_xconnect","-> SOCKET failed: %s (%d)",
@@ -1050,21 +1294,24 @@ int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename, unsigned
goto error;
}
- if (send_name(cbs, ctx, pkt_sz, ec->thisnodename, (unsigned) dist, tmo))
+ if (send_name(ec, ctx, pkt_sz, epmd_says_version, tmo))
goto error;
- if (recv_status(cbs, ctx, pkt_sz, tmo))
+ if (recv_status(ec, ctx, pkt_sz, tmo))
goto error;
- if (recv_challenge(cbs, ctx, pkt_sz, &her_challenge,
- &her_version, &her_flags, NULL, tmo))
+ if (recv_challenge(cbs, ctx, pkt_sz, &her_challenge, &her_version,
+ &her_flags, NULL, tmo))
goto error;
+ her_version = (her_flags & DFLAG_HANDSHAKE_23) ? EI_DIST_6 : EI_DIST_5;
our_challenge = gen_challenge();
gen_digest(her_challenge, ec->ei_connect_cookie, our_digest);
+ if (send_complement(ec, ctx, pkt_sz, epmd_says_version, her_flags, tmo))
+ goto error;
if (send_challenge_reply(cbs, ctx, pkt_sz, our_digest, our_challenge, tmo))
goto error;
if (recv_challenge_ack(cbs, ctx, pkt_sz, our_challenge,
ec->ei_connect_cookie, tmo))
goto error;
- if (put_ei_socket_info(sockd, dist, null_cookie, ec, cbs, ctx) != 0)
+ if (put_ei_socket_info(sockd, her_version, null_cookie, ec, cbs, ctx) != 0)
goto error;
if (cbs->connect_handshake_complete) {
@@ -1077,8 +1324,12 @@ int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename, unsigned
return ERL_ERROR;
}
}
-
- EI_TRACE_CONN1("ei_xconnect","-> CONNECT (ok) remote = %s",alivename);
+
+ if (alivename != NULL) {
+ EI_TRACE_CONN1("ei_xconnect","-> CONNECT (ok) remote = %s",alivename);
+ } else {
+ EI_TRACE_CONN1("ei_xconnect","-> CONNECT (ok) remote port = %d",rport);
+ }
erl_errno = 0;
return sockd;
@@ -1089,11 +1340,103 @@ error:
return ERL_ERROR;
} /* ei_xconnect */
+ /*
+ * Set up a connection to a given Node, and
+ * interchange hand shake messages with it.
+ * Returns a valid file descriptor at success,
+ * otherwise a negative error code.
+*/
+int ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned ms)
+{
+ char *hostname, alivename[BUFSIZ];
+ Erl_IpAddr ip;
+ int res;
+ char buffer[1024];
+ char* buf = buffer;
+
+ if (strlen(nodename) > MAXNODELEN) {
+ EI_TRACE_ERR0("ei_connect","Too long nodename");
+ return ERL_ERROR;
+ }
+
+ /* extract the host and alive parts from nodename */
+ if (!(hostname = strchr(nodename,'@'))) {
+ EI_TRACE_ERR0("ei_connect","Node name has no @ in name");
+ return ERL_ERROR;
+ } else {
+ strncpy(alivename, nodename, hostname - nodename);
+ alivename[hostname - nodename] = 0x0;
+ hostname++;
+ }
+
+ res = ip_address_from_hostname(hostname, &buf, sizeof(buffer), &ip);
+
+ if (res < 0) {
+ return res;
+ }
+
+ res = ei_xconnect_tmo(ec, ip, alivename, ms);
+
+ if(buf != buffer) {
+ free(buf);
+ }
+
+ return res;
+} /* ei_connect */
+
+int ei_connect(ei_cnode* ec, char *nodename)
+{
+ return ei_connect_tmo(ec, nodename, 0);
+}
+
+int ei_connect_host_port_tmo(ei_cnode* ec, char *host, int port, unsigned ms)
+{
+ Erl_IpAddr ip;
+ char buffer[1024];
+ char* buf = buffer;
+ int res = ip_address_from_hostname(host, &buf, sizeof(buffer), &ip);
+ if (res < 0) {
+ return res;
+ }
+ if(buf != buffer) {
+ free(buf);
+ }
+ return ei_xconnect_host_port_tmo(ec, ip, port, ms);
+}
+
+int ei_connect_host_port(ei_cnode* ec, char *host, int port)
+{
+ return ei_connect_host_port_tmo(ec, host, port, 0);
+}
+
+int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename, unsigned ms)
+{
+ int epmd_says_version = 0;
+ int port;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+ if ((port = ei_epmd_port_tmo(ip_addr,alivename,&epmd_says_version, tmo)) < 0) {
+ EI_TRACE_ERR0("ei_xconnect","-> CONNECT can't get remote port");
+ /* ei_epmd_port_tmo() has set erl_errno */
+ return ERL_NO_PORT;
+ }
+ return ei_connect_helper(ec, ip_addr, alivename, ms, port, epmd_says_version);
+}
+
int ei_xconnect(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename)
{
return ei_xconnect_tmo(ec, ip_addr, alivename, 0);
}
+int ei_xconnect_host_port_tmo(ei_cnode* ec, Erl_IpAddr ip_addr, int port, unsigned ms)
+{
+ return ei_connect_helper(ec, ip_addr, NULL, ms, port, EI_DIST_LOW);
+}
+
+int ei_xconnect_host_port(ei_cnode* ec, Erl_IpAddr ip_addr, int port)
+{
+ return ei_xconnect_host_port_tmo(ec, ip_addr, port, 0);
+}
+
int ei_listen(ei_cnode *ec, int *port, int backlog)
{
struct in_addr ip_addr;
@@ -1209,8 +1552,9 @@ int ei_accept(ei_cnode* ec, int lfd, ErlConnect *conp)
int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms)
{
int fd;
- unsigned her_version, her_flags;
+ DistFlags her_flags;
char tmp_nodename[MAXNODELEN+1];
+ char send_name_tag;
char *her_name;
int pkt_sz, err;
struct sockaddr_in addr;
@@ -1235,6 +1579,10 @@ int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms)
ctx = EI_FD_AS_CTX__(lfd);
}
+ if (ec->cbs != cbs) {
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+ return ERL_ERROR;
+ }
EI_TRACE_CONN0("ei_accept","<- ACCEPT waiting for connection");
@@ -1281,16 +1629,14 @@ int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms)
EI_TRACE_CONN0("ei_accept","<- ACCEPT connected to remote");
- if (recv_name(cbs, ctx, pkt_sz, &her_version, &her_flags, her_name, tmo)) {
+ if (recv_name(cbs, ctx, pkt_sz, &send_name_tag, &her_flags,
+ her_name, tmo)) {
EI_TRACE_ERR0("ei_accept","<- ACCEPT initial ident failed");
goto error;
}
- if (her_version <= 4) {
- EI_TRACE_ERR0("ei_accept","<- ACCEPT remote version not compatible");
- goto error;
- }
- else {
+ {
+ unsigned her_version = (her_flags & DFLAG_HANDSHAKE_23) ? 6 : 5;
unsigned our_challenge;
unsigned her_challenge;
unsigned char our_digest[16];
@@ -1298,9 +1644,12 @@ int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms)
if (send_status(cbs, ctx, pkt_sz, "ok", tmo))
goto error;
our_challenge = gen_challenge();
- if (send_challenge(cbs, ctx, pkt_sz, ec->thisnodename,
- our_challenge, her_version, tmo))
+ if (send_challenge(ec, ctx, pkt_sz, our_challenge, her_flags, tmo))
goto error;
+ if (send_name_tag == 'n' && (her_flags & DFLAG_HANDSHAKE_23)) {
+ if (recv_complement(cbs, ctx, pkt_sz, tmo))
+ goto error;
+ }
if (recv_challenge_reply(cbs, ctx, pkt_sz, our_challenge,
ec->ei_connect_cookie, &her_challenge, tmo))
goto error;
@@ -1406,11 +1755,8 @@ int ei_receive(int fd, unsigned char *bufp, int bufsize)
int ei_reg_send_tmo(ei_cnode* ec, int fd, char *server_name,
char* buf, int len, unsigned ms)
{
- erlang_pid *self = ei_self(ec);
- self->num = fd;
-
/* erl_errno and return code is set by ei_reg_send_encoded_tmo() */
- return ei_send_reg_encoded_tmo(fd, self, server_name, buf, len, ms);
+ return ei_send_reg_encoded_tmo(fd, ei_self(ec), server_name, buf, len, ms);
}
@@ -1519,27 +1865,45 @@ int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun,
ei_x_buff x;
erlang_pid *self = ei_self(ec);
- self->num = fd;
+ int err = ERL_ERROR;
/* encode header */
- ei_x_new_with_version(&x);
- ei_x_encode_tuple_header(&x, 2); /* A */
-
- self->num = fd;
- ei_x_encode_pid(&x, self); /* A 1 */
-
- ei_x_encode_tuple_header(&x, 5); /* B A 2 */
- ei_x_encode_atom(&x, "call"); /* B 1 */
- ei_x_encode_atom(&x, mod); /* B 2 */
- ei_x_encode_atom(&x, fun); /* B 3 */
- ei_x_append_buf(&x, buf, len); /* B 4 */
- ei_x_encode_atom(&x, "user"); /* B 5 */
-
- /* ei_x_encode_atom(&x,"user"); */
- ei_send_reg_encoded(fd, self, "rex", x.buff, x.index);
- ei_x_free(&x);
-
+ if (ei_x_new_with_version(&x) < 0)
+ goto einval;
+ if (ei_x_encode_tuple_header(&x, 2) < 0) /* A */
+ goto einval;
+
+ if (ei_x_encode_pid(&x, self) < 0) /* A 1 */
+ goto einval;
+
+ if (ei_x_encode_tuple_header(&x, 5) < 0) /* B A 2 */
+ goto einval;
+ if (ei_x_encode_atom(&x, "call") < 0) /* B 1 */
+ goto einval;
+ if (ei_x_encode_atom(&x, mod) < 0) /* B 2 */
+ goto einval;
+ if (ei_x_encode_atom(&x, fun) < 0) /* B 3 */
+ goto einval;
+ if (ei_x_append_buf(&x, buf, len) < 0) /* B 4 */
+ goto einval;
+ if (ei_x_encode_atom(&x, "user") < 0) /* B 5 */
+ goto einval;
+
+ err = ei_send_reg_encoded(fd, self, "rex", x.buff, x.index);
+ if (err)
+ goto error;
+
+ ei_x_free(&x);
+
return 0;
+
+einval:
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+
+error:
+ if (x.buff != NULL)
+ ei_x_free(&x);
+ return err;
} /* rpc_to */
/*
@@ -1557,11 +1921,6 @@ int ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg,
return res;
} /* rpc_from */
- /*
- * A true RPC. It return a NULL pointer
- * in case of failure, otherwise a valid
- * (ETERM *) pointer containing the reply
- */
int ei_rpc(ei_cnode* ec, int fd, char *mod, char *fun,
const char* inbuf, int inbuflen, ei_x_buff* x)
{
@@ -1571,27 +1930,45 @@ int ei_rpc(ei_cnode* ec, int fd, char *mod, char *fun,
char rex[MAXATOMLEN];
if (ei_rpc_to(ec, fd, mod, fun, inbuf, inbuflen) < 0) {
- return -1;
+ return ERL_ERROR;
}
- /* FIXME are we not to reply to the tick? */
+
+ /* ei_rpc_from() responds with a tick if it gets one... */
while ((i = ei_rpc_from(ec, fd, ERL_NO_TIMEOUT, &msg, x)) == ERL_TICK)
;
- if (i == ERL_ERROR) return -1;
- /*ep = 'erl'_element(2,emsg.msg);*/ /* {RPC_Tag, RPC_Reply} */
+ if (i == ERL_ERROR) return i;
+
+ /* Expect: {rex, RPC_Reply} */
+
index = 0;
- if (ei_decode_version(x->buff, &index, &i) < 0
- || ei_decode_ei_term(x->buff, &index, &t) < 0)
- return -1; /* FIXME ei_decode_version don't set erl_errno as before */
- /* FIXME this is strange, we don't check correct "rex" atom
- and we let it pass if not ERL_SMALL_TUPLE_EXT and arity == 2 */
- if (t.ei_type == ERL_SMALL_TUPLE_EXT && t.arity == 2)
- if (ei_decode_atom(x->buff, &index, rex) < 0)
- return -1;
+ if (ei_decode_version(x->buff, &index, &i) < 0)
+ goto ebadmsg;
+
+ if (ei_decode_ei_term(x->buff, &index, &t) < 0)
+ goto ebadmsg;
+
+ if (t.ei_type != ERL_SMALL_TUPLE_EXT && t.ei_type != ERL_LARGE_TUPLE_EXT)
+ goto ebadmsg;
+
+ if (t.arity != 2)
+ goto ebadmsg;
+
+ if (ei_decode_atom(x->buff, &index, rex) < 0)
+ goto ebadmsg;
+
+ if (strcmp("rex", rex) != 0)
+ goto ebadmsg;
+
/* remove header */
x->index -= index;
memmove(x->buff, &x->buff[index], x->index);
return 0;
+
+ebadmsg:
+
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
@@ -1629,21 +2006,6 @@ unsigned int gen_challenge(void)
return md_32((char*) &s, sizeof(s));
}
-#elif defined(VXWORKS)
-
-static unsigned int gen_challenge(void)
-{
- struct {
- struct timespec tv;
- clock_t cpu;
- int pid;
- } s;
- s.cpu = clock();
- clock_gettime(CLOCK_REALTIME, &s.tv);
- s.pid = getpid();
- return md_32((char*) &s, sizeof(s));
-}
-
#else /* some unix */
static unsigned int gen_challenge(void)
@@ -1663,10 +2025,10 @@ static unsigned int gen_challenge(void)
uname(&s.name);
s.cpu = clock();
s.pid = getpid();
-#ifndef __ANDROID__
- s.hid = gethostid();
-#else
+#if defined(__ANDROID__) || defined(__HAIKU__)
s.hid = 0;
+#else
+ s.hid = gethostid();
#endif
s.uid = getuid();
s.gid = getgid();
@@ -1815,7 +2177,7 @@ static int send_status(ei_socket_callbacks *cbs, void *ctx,
return 0;
}
-static int recv_status(ei_socket_callbacks *cbs, void *ctx,
+static int recv_status(ei_cnode *ec, void *ctx,
int pkt_sz, unsigned ms)
{
char dbuf[DEFBUF_SIZ];
@@ -1824,7 +2186,7 @@ static int recv_status(ei_socket_callbacks *cbs, void *ctx,
int buflen = DEFBUF_SIZ;
int rlen;
- if ((rlen = read_hs_package(cbs, ctx, pkt_sz,
+ if ((rlen = read_hs_package(ec->cbs, ctx, pkt_sz,
&buf, &buflen, &is_static, ms)) <= 0) {
EI_TRACE_ERR1("recv_status",
"<- RECV_STATUS socket read failed (%d)", rlen);
@@ -1834,11 +2196,48 @@ static int recv_status(ei_socket_callbacks *cbs, void *ctx,
EI_TRACE_CONN2("recv_status",
"<- RECV_STATUS (%.*s)", (rlen>20 ? 20 : rlen), buf);
- if (rlen >= 3 && buf[0] == 's' && buf[1] == 'o' && buf[2] == 'k') {
- /* Expecting "sok" or "sok_simultaneous" */
- if (!is_static)
- free(buf);
- return 0;
+ if (ec->thisnodename[0]) {
+ if (rlen >= 3 && buf[0] == 's' && buf[1] == 'o' && buf[2] == 'k') {
+ /* Expecting "sok" or "sok_simultaneous" */
+ if (!is_static)
+ free(buf);
+ return 0;
+ }
+ }
+ else { /* dynamic node name */
+ const char* at;
+ int namelen;
+ if (rlen >= 7 && strncmp(buf, "snamed:", 7) == 0) {
+ buf += 7;
+ rlen -= 7;
+ namelen = get16be(buf);
+ rlen -= 2;
+ if (namelen > MAXNODELEN || (namelen+4) > rlen) {
+ EI_TRACE_ERR1("recv_status","<- RECV_STATUS nodename too long (%d)",
+ namelen);
+ goto error;
+ }
+ memcpy(ec->thisnodename, buf, namelen);
+ ec->thisnodename[namelen] = '\0';
+ buf += namelen;
+ ec->creation = get32be(buf);
+
+ /* extract alive part from nodename */
+ if (!(at = strchr(ec->thisnodename,'@'))) {
+ EI_TRACE_ERR0("ei_connect","Dynamic node name has no @ in name");
+ return ERL_ERROR;
+ } else {
+ const int alen = at - ec->thisnodename;
+ strncpy(ec->thisalivename, ec->thisnodename, alen);
+ ec->thisalivename[alen] = '\0';
+ }
+
+ strcpy(ec->self.node, ec->thisnodename);
+ ec->self.num = 0;
+ ec->self.serial = 0;
+ ec->self.creation = ec->creation;
+ return 0;
+ }
}
error:
if (!is_static)
@@ -1846,26 +2245,64 @@ error:
return -1;
}
-static int send_name_or_challenge(ei_socket_callbacks *cbs,
- void *ctx,
- int pkt_sz,
- char *nodename,
- int f_chall,
- unsigned challenge,
- unsigned version,
- unsigned ms)
+static DistFlags preferred_flags(void)
+{
+ DistFlags flags =
+ DFLAG_EXTENDED_REFERENCES
+ | DFLAG_DIST_MONITOR
+ | DFLAG_EXTENDED_PIDS_PORTS
+ | DFLAG_FUN_TAGS
+ | DFLAG_NEW_FUN_TAGS
+ | DFLAG_NEW_FLOATS
+ | DFLAG_SMALL_ATOM_TAGS
+ | DFLAG_UTF8_ATOMS
+ | DFLAG_MAP_TAG
+ | DFLAG_BIG_CREATION
+ | DFLAG_EXPORT_PTR_TAG
+ | DFLAG_BIT_BINARIES
+ | DFLAG_HANDSHAKE_23;
+ if (ei_internal_use_21_bitstr_expfun()) {
+ flags &= ~(DFLAG_EXPORT_PTR_TAG
+ | DFLAG_BIT_BINARIES);
+ }
+ return flags;
+}
+
+static int send_name(ei_cnode *ec,
+ void *ctx,
+ int pkt_sz,
+ unsigned version,
+ unsigned ms)
{
char *buf;
unsigned char *s;
char dbuf[DEFBUF_SIZ];
- int siz = pkt_sz + 1 + 2 + 4 + strlen(nodename);
- const char* function[] = {"SEND_NAME", "SEND_CHALLENGE"};
+ const char* name_ptr;
+ unsigned int name_len;
+ int siz;
int err;
ssize_t len;
- unsigned int flags;
+ DistFlags flags = preferred_flags();
+ char tag;
+
+ if (ec->thisnodename[0]) {
+ name_ptr = ec->thisnodename;
+ tag = (version == EI_DIST_5) ? 'n' : 'N';
+ }
+ else {
+ /* dynamic node name */
+ name_ptr = ec->thishostname;
+ tag = 'N'; /* presume ver 6 */
+ flags |= DFLAG_NAME_ME;
+ }
+
+ name_len = strlen(name_ptr);
+
+ if (tag == 'n')
+ siz = pkt_sz + 1 + 2 + 4 + name_len;
+ else
+ siz = pkt_sz + 1 + 8 + 4 + 2 + name_len;
- if (f_chall)
- siz += 4;
buf = (siz > DEFBUF_SIZ) ? malloc(siz) : dbuf;
if (!buf) {
erl_errno = ENOMEM;
@@ -1882,35 +2319,94 @@ static int send_name_or_challenge(ei_socket_callbacks *cbs,
default:
return -1;
}
- put8(s, 'n');
- put16be(s, version);
- flags = (DFLAG_EXTENDED_REFERENCES
- | DFLAG_DIST_MONITOR
- | DFLAG_EXTENDED_PIDS_PORTS
- | DFLAG_FUN_TAGS
- | DFLAG_NEW_FUN_TAGS
- | DFLAG_NEW_FLOATS
- | DFLAG_SMALL_ATOM_TAGS
- | DFLAG_UTF8_ATOMS
- | DFLAG_MAP_TAG
- | DFLAG_BIG_CREATION
- | DFLAG_EXPORT_PTR_TAG
- | DFLAG_BIT_BINARIES);
- if (ei_internal_use_21_bitstr_expfun()) {
- flags &= ~(DFLAG_EXPORT_PTR_TAG
- | DFLAG_BIT_BINARIES);
+
+ put8(s, tag);
+ if (tag == 'n') {
+ put16be(s, EI_DIST_5); /* some impl (jinterface) demand ver==5 */
+ put32be(s, flags);
+ }
+ else { /* tag == 'N' */
+ put64be(s, flags);
+ put32be(s, ec->creation);
+ put16be(s, name_len);
}
- put32be(s, flags);
- if (f_chall)
- put32be(s, challenge);
- memcpy(s, nodename, strlen(nodename));
+ memcpy(s, name_ptr, name_len);
len = (ssize_t) siz;
- err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms);
+ err = ei_write_fill_ctx_t__(ec->cbs, ctx, buf, &len, ms);
if (!err && len != (ssize_t) siz)
err = EIO;
if (err) {
- EI_TRACE_ERR1("send_name_or_challenge",
- "-> %s socket write failed", function[f_chall]);
+ EI_TRACE_ERR0("send_name", "SEND_NAME -> socket write failed");
+ if (buf != dbuf)
+ free(buf);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
+
+ if (buf != dbuf)
+ free(buf);
+ return 0;
+}
+
+static int send_challenge(ei_cnode *ec,
+ void *ctx,
+ int pkt_sz,
+ unsigned challenge,
+ DistFlags her_flags,
+ unsigned ms)
+{
+ char *buf;
+ unsigned char *s;
+ char dbuf[DEFBUF_SIZ];
+ const unsigned int nodename_len = strlen(ec->thisnodename);
+ int siz;
+ int err;
+ ssize_t len;
+ DistFlags flags;
+ const char tag = (her_flags & DFLAG_HANDSHAKE_23) ? 'N' : 'n';
+
+ if (tag == 'n')
+ siz = pkt_sz + 1 + 2 + 4 + 4 + nodename_len;
+ else
+ siz = pkt_sz + 1 + 8 + 4 + 4 + 2 + nodename_len;
+
+ buf = (siz > DEFBUF_SIZ) ? malloc(siz) : dbuf;
+ if (!buf) {
+ erl_errno = ENOMEM;
+ return -1;
+ }
+ s = (unsigned char *)buf;
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
+
+ flags = preferred_flags();
+ put8(s, tag);
+ if (tag == 'n') {
+ put16be(s, EI_DIST_5); /* choosen version */
+ put32be(s, flags);
+ put32be(s, challenge);
+ }
+ else {
+ put64be(s, flags);
+ put32be(s, challenge);
+ put32be(s, ec->creation);
+ put16be(s, nodename_len);
+ }
+ memcpy(s, ec->thisnodename, nodename_len);
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(ec->cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
+ EI_TRACE_ERR0("send_challenge", "-> SEND_CHALLENGE socket write failed");
if (buf != dbuf)
free(buf);
EI_CONN_SAVE_ERRNO__(err);
@@ -1924,13 +2420,13 @@ static int send_name_or_challenge(ei_socket_callbacks *cbs,
static int recv_challenge(ei_socket_callbacks *cbs, void *ctx,
int pkt_sz, unsigned *challenge, unsigned *version,
- unsigned *flags, char *namebuf, unsigned ms)
+ DistFlags *flags, char *namebuf, unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
int is_static = 1;
int buflen = DEFBUF_SIZ;
- int rlen;
+ int rlen, nodename_len;
char *s;
char tag;
char tmp_nodename[MAXNODELEN+1];
@@ -1943,21 +2439,57 @@ static int recv_challenge(ei_socket_callbacks *cbs, void *ctx,
"<- RECV_CHALLENGE socket read failed (%d)",rlen);
goto error;
}
- if ((rlen - 11) > MAXNODELEN) {
- EI_TRACE_ERR1("recv_challenge",
- "<- RECV_CHALLENGE nodename too long (%d)",rlen - 11);
- goto error;
- }
s = buf;
- if ((tag = get8(s)) != 'n') {
+ tag = get8(s);
+ if (tag != 'n' && tag != 'N') {
EI_TRACE_ERR2("recv_challenge",
"<- RECV_CHALLENGE incorrect tag, "
- "expected 'n' got '%c' (%u)",tag,tag);
+ "expected 'n' or 'N', got '%c' (%u)",tag,tag);
goto error;
}
- *version = get16be(s);
- *flags = get32be(s);
- *challenge = get32be(s);
+ if (tag == 'n') { /* OLD */
+ unsigned int version;
+ if (rlen < 1+2+4+4) {
+ EI_TRACE_ERR1("recv_challenge","<- RECV_CHALLENGE 'n' packet too short (%d)",
+ rlen)
+ goto error;
+ }
+
+ version = get16be(s);
+ if (version != EI_DIST_5) {
+ EI_TRACE_ERR1("recv_challenge",
+ "<- RECV_CHALLENGE 'n' incorrect version=%d",
+ version);
+ goto error;
+ }
+ *flags = get32be(s);
+ *challenge = get32be(s);
+ nodename_len = (buf + rlen) - s;
+ }
+ else { /* NEW */
+ if (rlen < 1+8+4+4+2) {
+ EI_TRACE_ERR1("recv_challenge","<- RECV_CHALLENGE 'N' packet too short (%d)",
+ rlen)
+ goto error;
+ }
+ *version = EI_DIST_6;
+ *flags = get64be(s);
+ *challenge = get32be(s);
+ s += 4; /* ignore peer 'creation' */
+ nodename_len = get16be(s);
+ if (nodename_len > (buf + rlen) - s) {
+ EI_TRACE_ERR1("recv_challenge",
+ "<- RECV_CHALLENGE 'N' nodename too long (%d)",
+ nodename_len);
+ goto error;
+ }
+ }
+
+ if (nodename_len > MAXNODELEN) {
+ EI_TRACE_ERR1("recv_challenge",
+ "<- RECV_CHALLENGE nodename too long (%d)", nodename_len);
+ goto error;
+ }
if (!(*flags & DFLAG_EXTENDED_REFERENCES)) {
EI_TRACE_ERR0("recv_challenge","<- RECV_CHALLENGE peer cannot "
@@ -1981,8 +2513,8 @@ static int recv_challenge(ei_socket_callbacks *cbs, void *ctx,
if (!namebuf)
namebuf = &tmp_nodename[0];
- memcpy(namebuf, s, rlen - 11);
- namebuf[rlen - 11] = '\0';
+ memcpy(namebuf, s, nodename_len);
+ namebuf[nodename_len] = '\0';
if (!is_static)
free(buf);
@@ -2003,6 +2535,63 @@ error:
return -1;
}
+static int send_complement(ei_cnode *ec,
+ void *ctx,
+ int pkt_sz,
+ unsigned epmd_says_version,
+ DistFlags her_flags,
+ unsigned ms)
+{
+ if (epmd_says_version == EI_DIST_5 && (her_flags & DFLAG_HANDSHAKE_23)) {
+ char *buf;
+ unsigned char *s;
+ char dbuf[DEFBUF_SIZ];
+ int err;
+ ssize_t len;
+ unsigned int flagsHigh;
+ const int siz = pkt_sz + 1 + 4 + 4;
+
+ buf = (siz > DEFBUF_SIZ) ? malloc(siz) : dbuf;
+ if (!buf) {
+ erl_errno = ENOMEM;
+ return -1;
+ }
+ s = (unsigned char *)buf;
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
+ flagsHigh = preferred_flags() >> 32;
+
+ put8(s, 'c');
+ put32be(s, flagsHigh);
+ put32be(s, ec->creation);
+
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(ec->cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
+ EI_TRACE_ERR0("send_name", "SEND_NAME -> socket write failed");
+ if (buf != dbuf)
+ free(buf);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
+
+ if (buf != dbuf)
+ free(buf);
+ }
+ return 0;
+}
+
+
static int send_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
int pkt_sz, unsigned char digest[16],
unsigned challenge, unsigned ms)
@@ -2049,6 +2638,54 @@ static int send_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
return 0;
}
+static int recv_complement(ei_socket_callbacks *cbs,
+ void *ctx,
+ int pkt_sz,
+ unsigned ms)
+{
+ char dbuf[DEFBUF_SIZ];
+ char *buf = dbuf;
+ int is_static = 1;
+ int buflen = DEFBUF_SIZ;
+ int rlen;
+ char *s;
+ char tag;
+ unsigned int creation;
+
+ erl_errno = EIO; /* Default */
+
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen, &is_static, ms)) != 21) {
+ EI_TRACE_ERR1("recv_complement",
+ "<- RECV_COMPLEMENT socket read failed (%d)",rlen);
+ goto error;
+ }
+
+ s = buf;
+ if ((tag = get8(s)) != 'c') {
+ EI_TRACE_ERR2("recv_complement",
+ "<- RECV_COMPLEMENT incorrect tag, "
+ "expected 'c' got '%c' (%u)",tag,tag);
+ goto error;
+ }
+ creation = get32be(s);
+ if (!is_static)
+ free(buf);
+
+ if (ei_tracelevel >= 3) {
+ EI_TRACE_CONN1("recv_complement",
+ "<- RECV_COMPLEMENT (ok) creation = %u",
+ creation);
+ }
+ /* We don't have any use for 'creation' of other node, so we drop it */
+ erl_errno = 0;
+ return 0;
+
+error:
+ if (!is_static)
+ free(buf);
+ return -1;
+}
+
static int recv_challenge_reply(ei_socket_callbacks *cbs,
void *ctx,
int pkt_sz,
@@ -2204,30 +2841,16 @@ error:
return -1;
}
-static int send_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
- char *nodename, unsigned version, unsigned ms)
-{
- return send_name_or_challenge(cbs, ctx, pkt_sz, nodename, 0,
- 0, version, ms);
-}
-
-static int send_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
- char *nodename, unsigned challenge, unsigned version,
- unsigned ms)
-{
- return send_name_or_challenge(cbs, ctx, pkt_sz, nodename, 1,
- challenge, version, ms);
-}
-
static int recv_name(ei_socket_callbacks *cbs, void *ctx,
- int pkt_sz, unsigned *version,
- unsigned *flags, char *namebuf, unsigned ms)
+ int pkt_sz, char *send_name_tag,
+ DistFlags *flags, char *namebuf, unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
int is_static = 1;
int buflen = DEFBUF_SIZ;
int rlen;
+ unsigned int namelen;
char *s;
char tmp_nodename[MAXNODELEN+1];
char tag;
@@ -2239,19 +2862,40 @@ static int recv_name(ei_socket_callbacks *cbs, void *ctx,
EI_TRACE_ERR1("recv_name","<- RECV_NAME socket read failed (%d)",rlen);
goto error;
}
- if ((rlen - 7) > MAXNODELEN) {
- EI_TRACE_ERR1("recv_name","<- RECV_NAME nodename too long (%d)",rlen-7);
- goto error;
- }
s = buf;
tag = get8(s);
- if (tag != 'n') {
+ *send_name_tag = tag;
+ if (tag != 'n' && tag != 'N') {
EI_TRACE_ERR2("recv_name","<- RECV_NAME incorrect tag, "
- "expected 'n' got '%c' (%u)",tag,tag);
+ "expected 'n' or 'N', got '%c' (%u)",tag,tag);
goto error;
}
- *version = get16be(s);
- *flags = get32be(s);
+ if (tag == 'n') {
+ unsigned int version;
+ if (rlen < 1+2+4) {
+ EI_TRACE_ERR1("recv_name","<- RECV_NAME 'n' packet too short (%d)",
+ rlen)
+ goto error;
+ }
+ version = get16be(s);
+ if (version < EI_DIST_5) {
+ EI_TRACE_ERR1("recv_name","<- RECV_NAME 'n' invalid version=%d",
+ version)
+ goto error;
+ }
+ *flags = get32be(s);
+ namelen = rlen - (1+2+4);
+ }
+ else { /* tag == 'N' */
+ if (rlen < 1+8+4+2) {
+ EI_TRACE_ERR1("recv_name","<- RECV_NAME 'N' packet too short (%d)",
+ rlen)
+ goto error;
+ }
+ *flags = get64be(s);
+ s += 4; /* ignore peer 'creation' */
+ namelen = get16be(s);
+ }
if (!(*flags & DFLAG_EXTENDED_REFERENCES)) {
EI_TRACE_ERR0("recv_name","<- RECV_NAME peer cannot handle"
@@ -2269,14 +2913,20 @@ static int recv_name(ei_socket_callbacks *cbs, void *ctx,
if (!namebuf)
namebuf = &tmp_nodename[0];
- memcpy(namebuf, s, rlen - 7);
- namebuf[rlen - 7] = '\0';
+ if (namelen > MAXNODELEN || s+namelen > buf+rlen) {
+ EI_TRACE_ERR2("recv_name","<- RECV_NAME '%c' nodename too long (%d)",
+ tag, namelen);
+ goto error;
+ }
+
+ memcpy(namebuf, s, namelen);
+ namebuf[namelen] = '\0';
if (!is_static)
free(buf);
EI_TRACE_CONN3("recv_name",
- "<- RECV_NAME (ok) node = %s, version = %u, flags = %u",
- namebuf,*version,*flags);
+ "<- RECV_NAME (ok) node = %s, tag = %c, flags = %u",
+ namebuf,tag,*flags);
erl_errno = 0;
return 0;
diff --git a/lib/erl_interface/src/connect/ei_connect_int.h b/lib/erl_interface/src/connect/ei_connect_int.h
index 2ed28365b1..7d0c548ae0 100644
--- a/lib/erl_interface/src/connect/ei_connect_int.h
+++ b/lib/erl_interface/src/connect/ei_connect_int.h
@@ -38,30 +38,7 @@
#include <windows.h>
#include <winbase.h>
-#elif VXWORKS
-#include <vxWorks.h>
-#include <hostLib.h>
-#include <selectLib.h>
-#include <ifLib.h>
-#include <sockLib.h>
-#include <taskLib.h>
-#include <inetLib.h>
-#include <ioLib.h>
-
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/times.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <timers.h>
-
-#define getpid() taskIdSelf()
-extern int h_errno;
-
-#else /* some other unix */
+#else /* some unix */
#include <unistd.h>
#include <sys/types.h>
#include <sys/times.h>
@@ -95,20 +72,25 @@ extern int h_errno;
#endif
/* Distribution capability flags */
-#define DFLAG_PUBLISHED 1
-#define DFLAG_ATOM_CACHE 2
-#define DFLAG_EXTENDED_REFERENCES 4
-#define DFLAG_DIST_MONITOR 8
-#define DFLAG_FUN_TAGS 16
-#define DFLAG_NEW_FUN_TAGS 0x80
-#define DFLAG_EXTENDED_PIDS_PORTS 0x100
-#define DFLAG_EXPORT_PTR_TAG 0x200
-#define DFLAG_BIT_BINARIES 0x400
-#define DFLAG_NEW_FLOATS 0x800
-#define DFLAG_SMALL_ATOM_TAGS 0x4000
-#define DFLAG_UTF8_ATOMS 0x10000
-#define DFLAG_MAP_TAG 0x20000
-#define DFLAG_BIG_CREATION 0x40000
+typedef EI_ULONGLONG DistFlags;
+
+#define DFLAG_PUBLISHED 1
+#define DFLAG_ATOM_CACHE 2
+#define DFLAG_EXTENDED_REFERENCES 4
+#define DFLAG_DIST_MONITOR 8
+#define DFLAG_FUN_TAGS 0x10
+#define DFLAG_NEW_FUN_TAGS 0x80
+#define DFLAG_EXTENDED_PIDS_PORTS 0x100
+#define DFLAG_EXPORT_PTR_TAG 0x200
+#define DFLAG_BIT_BINARIES 0x400
+#define DFLAG_NEW_FLOATS 0x800
+#define DFLAG_SMALL_ATOM_TAGS 0x4000
+#define DFLAG_UTF8_ATOMS 0x10000
+#define DFLAG_MAP_TAG 0x20000
+#define DFLAG_BIG_CREATION 0x40000
+#define DFLAG_HANDSHAKE_23 0x1000000
+#define DFLAG_RESERVED 0xfe000000
+#define DFLAG_NAME_ME ((DistFlags)0x2 << 32)
ei_cnode *ei_fd_to_cnode(int fd);
int ei_distversion(int fd);
diff --git a/lib/erl_interface/src/connect/ei_resolve.c b/lib/erl_interface/src/connect/ei_resolve.c
index 9ace9c9d4a..1d11c5043d 100644
--- a/lib/erl_interface/src/connect/ei_resolve.c
+++ b/lib/erl_interface/src/connect/ei_resolve.c
@@ -21,19 +21,7 @@
* Interface functions to different versions of gethostbyname
*/
-#ifdef VXWORKS
-#include <vxWorks.h>
-#include <stdio.h>
-#include <semLib.h>
-#include <hostLib.h>
-#include <resolvLib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <errno.h>
-#include <symLib.h>
-#include <sysSymTbl.h>
-
-#elif __WIN32__
+#ifdef __WIN32__
#include <winsock2.h>
#include <windows.h>
#include <winbase.h>
@@ -55,6 +43,16 @@
#include "ei_resolve.h"
#include "ei_locking.h"
+/* AIX has a totally different signature (allegedly shared with some other
+ * Unices) that isn't compatible. It turns out that the _r version isn't
+ * thread-safe according to curl - but bizarrely, since AIX 4.3, libc
+ * is thread-safe in a manner that makes the normal gethostbyname OK
+ * for re-entrant use.
+ */
+#if defined(_AIX) || defined(__NetBSD__)
+#undef HAVE_GETHOSTBYNAME_R
+#endif
+
#ifdef HAVE_GETHOSTBYNAME_R
int ei_init_resolve(void)
@@ -75,7 +73,7 @@ int ei_init_resolve(void)
static ei_mutex_t *ei_gethost_sem = NULL;
#endif /* _REENTRANT */
static int ei_resolve_initialized = 0;
-#ifndef __WIN32__
+#if !defined(__WIN32__) && !defined(_AIX) && !defined(__NetBSD__)
int h_errno;
#endif
@@ -85,18 +83,6 @@ int h_errno;
#define DEBUGF(X) /* Nothing */
#endif
-#ifdef VXWORKS
-/* FIXME problem for threaded ? */
-static struct hostent *(*sens_gethostbyname)(const char *name,
- char *, int) = NULL;
-static struct hostent *(*sens_gethostbyaddr)(const char *addr,
- char *, int) = NULL;
-#endif
-
-#ifdef VXWORKS
-static int verify_dns_configuration(void);
-#endif
-
/*
* If we find SENS resolver, use the functions found there, i.e.
* resolvGetHostByName() and resolvGetHostByAddr(). Otherwise we use
@@ -106,32 +92,6 @@ static int verify_dns_configuration(void);
int ei_init_resolve(void)
{
-#ifdef VXWORKS
- void *sym;
- SYM_TYPE symtype;
-
- if (symFindByName(sysSymTbl,"resolvGetHostByName",
- (char **)&sym,&symtype) == OK &&
- verify_dns_configuration()) {
- sens_gethostbyname = sym;
- DEBUGF((stderr,"found SENS resolver - using it for gethostbyname()\n"));
- if (symFindByName(sysSymTbl,"resolvGetHostByAddr",
- (char **)&sym,&symtype) == OK) {
- sens_gethostbyaddr = sym;
- DEBUGF((stderr,"found SENS resolver - "
- "using it for gethostbyaddr()\n"));
- }
- else {
- DEBUGF((stderr,"SENS resolver not found - "
- "using default gethostbyaddr()\n"));
- }
- }
- else {
- DEBUGF((stderr,"SENS resolver not found - "
- "using default gethostbyname()\n"));
- }
-#endif /* VXWORKS */
-
#ifdef _REENTRANT
ei_gethost_sem = ei_mutex_create();
if (!ei_gethost_sem)
@@ -142,42 +102,7 @@ int ei_init_resolve(void)
return 0;
}
-#ifdef VXWORKS
-/*
-** Function to verify the DNS configuration on VwXorks SENS.
-** Actually configures to a default value if unconfigured...
-*/
-static int verify_dns_configuration(void)
-{
- /* FIXME problem for threaded ? */
- static char resolv_params[sizeof(RESOLV_PARAMS_S)];
- void (*rpg)(char *);
- STATUS (*rps)(char *);
- SYM_TYPE dummy;
- int get_result, set_result;
-
- get_result = symFindByName(sysSymTbl,"resolvParamsGet", (char **) &rpg, &dummy);
- set_result = symFindByName(sysSymTbl,"resolvParamsSet", (char **) &rps, &dummy);
-
- if (!(get_result == OK &&
- set_result == OK))
- return -1;
- (*rpg)(resolv_params);
- if (*resolv_params == '\0') {
- /* It exists, but is not configured, ei_connect would fail
- if we left it this way... The best we can do is to configure
- it to use the local host database on the card, as a fallback */
- *resolv_params = (char) 1;
- fprintf(stderr,"Trying to fix up DNS configuration.\n");
- if (((*rps)(resolv_params)) != OK)
- return -1;
- }
- return 0;
-}
-
-#endif
-
-#if defined(VXWORKS) || _REENTRANT
+#if _REENTRANT
/*
* Copy the contents of one struct hostent to another, i.e. don't just
@@ -196,7 +121,7 @@ static int verify_dns_configuration(void)
#else
#define EI_ALIGNBYTES (sizeof(void*) - 1)
#endif
-#define align_buf(buf,len) for (;(((unsigned)buf) & EI_ALIGNBYTES); (buf)++, len--)
+#define align_buf(buf,len) for (;(((size_t)buf) & EI_ALIGNBYTES); (buf)++, len--)
#define advance_buf(buf,len,n) ((buf)+=(n),(len)-=(n))
/* "and now the tricky part..." */
@@ -365,9 +290,9 @@ static struct hostent *my_gethostbyname_r(const char *name,
return rval;
}
-#endif /* defined(VXWORKS) || _REENTRANT */
+#endif /* _REENTRANT */
-#if defined(VXWORKS) || EI_THREADS != false
+#if EI_THREADS != false
static struct hostent *my_gethostbyaddr_r(const char *addr,
int length,
@@ -433,7 +358,7 @@ static struct hostent *my_gethostbyaddr_r(const char *addr,
return rval;
}
-#endif /* defined(VXWORKS) || EI_THREADS != false */
+#endif /* EI_THREADS != false */
#endif /* !HAVE_GETHOSTBYNAME_R */
@@ -449,154 +374,6 @@ struct hostent *ei_gethostbyaddr(const char *addr, int len, int type)
return gethostbyaddr(addr, len, type);
}
-#elif VXWORKS
-
-
-/* these are a couple of substitutes for the real thing when we run on
- * stock vxworks (i.e. no sens).
- *
- * len and type are ignored, but we make up some reasonable values and
- * insert them
- */
-static struct hostent *my_gethostbyname(const char *name)
-{
- /* FIXME problem for threaded ? */
- static struct hostent h;
- static char hostname[EI_MAXHOSTNAMELEN+1];
- static char *aliases[1] = {NULL};
- static char *addrp[2] = {NULL,NULL};
- static unsigned long addr = 0;
-
- strcpy(hostname,name);
- if ((addr = (unsigned long)hostGetByName(hostname)) == ERROR) {
- h_errno = HOST_NOT_FOUND;
- return NULL;
- }
-
- h_errno = 0;
- h.h_name = hostname;
- h.h_aliases = aliases;
- h.h_length = 4;
- h.h_addrtype = AF_INET;
- addrp[0] = (char *)&addr;
- h.h_addr_list = addrp;
-
- return &h;
-}
-
-static struct hostent *my_gethostbyaddr(const char *addr, int len, int type)
-{
- /* FIXME problem for threaded ? */
- static struct hostent h;
- static char hostname[EI_MAXHOSTNAMELEN+1];
- static char *aliases[1] = { NULL };
- static unsigned long inaddr;
- static char *addrp[2] = {(char *)&inaddr, NULL};
-
- memmove(&inaddr,addr,sizeof(inaddr));
-
- if ((hostGetByAddr(inaddr,hostname)) == ERROR) {
- h_errno = HOST_NOT_FOUND;
- return NULL;
- }
-
- h_errno = 0;
- h.h_name = hostname;
- h.h_aliases = aliases;
- h.h_length = 4;
- h.h_addrtype = AF_INET;
- h.h_addr_list = addrp;
-
- return &h;
-}
-
-/* use sens functions for these, if found. */
-struct hostent *ei_gethostbyname(const char *name)
-{
- struct hostent *h = NULL;
-
- if (!sens_gethostbyname) {
- h = my_gethostbyname(name);
- }
- else {
- /* FIXME problem for threaded ? */
- static char buf[1024];
- h = sens_gethostbyname(name,buf,1024);
- }
-
- return h;
-}
-
-struct hostent *ei_gethostbyaddr(const char *addr, int len, int type)
-{
- struct hostent *h = NULL;
-
- if (!sens_gethostbyaddr) {
- h = my_gethostbyaddr(addr,len,type);
- }
- else {
- /* FIXME problem for threaded ? */
- static char buf[1024];
- h = sens_gethostbyaddr(addr,buf,1024);
- }
-
- return h;
-}
-
-struct hostent *ei_gethostbyaddr_r(const char *addr,
- int length,
- int type,
- struct hostent *hostp,
- char *buffer,
- int buflen,
- int *h_errnop)
-{
- struct hostent *h = NULL;
-
- /* use own func if sens function not available */
- if (!sens_gethostbyaddr) {
- h = my_gethostbyaddr_r(addr,length,type,hostp,buffer,buflen,h_errnop);
- }
- else {
- if (!(h = sens_gethostbyaddr(addr,buffer,buflen))) {
- /* sens returns status via errno */
- *h_errnop = errno;
- }
- else {
- *hostp = *h;
- *h_errnop = 0;
- }
- }
-
- return h;
-}
-
-struct hostent *ei_gethostbyname_r(const char *name,
- struct hostent *hostp,
- char *buffer,
- int buflen,
- int *h_errnop)
-{
- struct hostent *h = NULL;
-
- /* use own func if sens function not available */
- if (!sens_gethostbyname) {
- h = my_gethostbyname_r(name,hostp,buffer,buflen,h_errnop);
- }
- else {
- if (!(h = sens_gethostbyname(name,buffer,buflen))) {
- /* sens returns status via errno */
- *h_errnop = errno;
- }
- else {
- *hostp = *h;
- *h_errnop = 0;
- }
- }
-
- return h;
-}
-
#else /* unix of some kind */
struct hostent *ei_gethostbyname(const char *name)
@@ -667,5 +444,5 @@ struct hostent *ei_gethostbyname_r(const char *name,
#endif
}
-#endif /* vxworks, win, unix */
+#endif /* win, unix */
diff --git a/lib/erl_interface/src/connect/eirecv.c b/lib/erl_interface/src/connect/eirecv.c
index 0673d25d38..96732db5b6 100644
--- a/lib/erl_interface/src/connect/eirecv.c
+++ b/lib/erl_interface/src/connect/eirecv.c
@@ -70,7 +70,7 @@ ei_recv_internal (int fd,
err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
/* get length field */
@@ -80,7 +80,7 @@ ei_recv_internal (int fd,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
len = get32be(s);
@@ -91,7 +91,7 @@ ei_recv_internal (int fd,
ssize_t wlen = sizeof(tock);
ei_write_fill_ctx_t__(cbs, ctx, tock, &wlen, tmo); /* Failure no problem */
*msglenp = 0;
- return 0; /* maybe flag ERL_EAGAIN [sverkerw] */
+ return ERL_TICK;
}
/* turn off tracing on each receive. it will be turned back on if
@@ -106,7 +106,7 @@ ei_recv_internal (int fd,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
/* now decode header */
@@ -119,8 +119,8 @@ ei_recv_internal (int fd,
|| ei_decode_tuple_header(header,&index,&arity)
|| ei_decode_long(header,&index,&msg->msgtype))
{
- erl_errno = EIO; /* Maybe another code for decoding errors */
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
switch (msg->msgtype) {
@@ -129,8 +129,8 @@ ei_recv_internal (int fd,
if (ei_decode_atom_as(header,&index,msg->cookie,sizeof(msg->cookie),ERLANG_UTF8,NULL,NULL)
|| ei_decode_pid(header,&index,&msg->to))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
break;
@@ -141,8 +141,8 @@ ei_recv_internal (int fd,
|| ei_decode_atom_as(header,&index,msg->cookie,sizeof(msg->cookie),ERLANG_UTF8,NULL,NULL)
|| ei_decode_atom_as(header,&index,msg->toname,sizeof(msg->toname),ERLANG_UTF8,NULL,NULL))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
/* actual message is remaining part of headerbuf, plus any unread bytes */
@@ -155,8 +155,8 @@ ei_recv_internal (int fd,
if (ei_decode_pid(header,&index,&msg->from)
|| ei_decode_pid(header,&index,&msg->to))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
break;
@@ -167,8 +167,8 @@ ei_recv_internal (int fd,
if (ei_decode_pid(header,&index,&msg->from)
|| ei_decode_pid(header,&index,&msg->to))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
break;
@@ -179,8 +179,8 @@ ei_recv_internal (int fd,
|| ei_decode_pid(header,&index,&msg->to)
|| ei_decode_trace(header,&index,&msg->token))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
ei_trace(1,&msg->token); /* turn on tracing */
@@ -193,8 +193,8 @@ ei_recv_internal (int fd,
|| ei_decode_atom_as(header,&index,msg->toname,sizeof(msg->toname),ERLANG_UTF8,NULL,NULL)
|| ei_decode_trace(header,&index,&msg->token))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
ei_trace(1,&msg->token); /* turn on tracing */
@@ -207,8 +207,8 @@ ei_recv_internal (int fd,
|| ei_decode_pid(header,&index,&msg->to)
|| ei_decode_trace(header,&index,&msg->token))
{
- erl_errno = EIO;
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
ei_trace(1,&msg->token); /* turn on tracing */
@@ -235,14 +235,14 @@ ei_recv_internal (int fd,
err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo);
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
if (rlen == 0)
break;
remain -= rlen;
}
erl_errno = EMSGSIZE;
- return -1;
+ return ERL_ERROR;
}
else {
/* Dynamic buffer --- grow it. */
@@ -253,7 +253,7 @@ ei_recv_internal (int fd,
if ((mbuf = realloc(*mbufp, msglen)) == NULL)
{
erl_errno = ENOMEM;
- return -1;
+ return ERL_ERROR;
}
*mbufp = mbuf;
@@ -276,7 +276,7 @@ ei_recv_internal (int fd,
if (err) {
*msglenp = bytesread-index+1; /* actual bytes in users buffer */
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
}
diff --git a/lib/erl_interface/src/connect/send.c b/lib/erl_interface/src/connect/send.c
index 0670b269d7..df2e3e023d 100644
--- a/lib/erl_interface/src/connect/send.c
+++ b/lib/erl_interface/src/connect/send.c
@@ -24,13 +24,6 @@
# include <windows.h>
# include <winbase.h>
-#elif VXWORKS
-
-# include <sys/types.h>
-# include <unistd.h>
-# include <sysLib.h>
-# include <tickLib.h>
-
#else /* unix */
# include <sys/types.h>
@@ -74,19 +67,31 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
/* check that he can receive trace tokens first */
if (ei_distversion(fd) > 0) token = ei_trace(0,NULL);
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+
/* header = SEND, cookie, to max sizes: */
- ei_encode_version(header,&index); /* 1 */
+ if (ei_encode_version(header,&index) < 0) /* 1 */
+ return ERL_ERROR;
if (token) {
- ei_encode_tuple_header(header,&index,4); /* 2 */
- ei_encode_long(header,&index,ERL_SEND_TT); /* 2 */
+ if (ei_encode_tuple_header(header,&index,4) < 0) /* 2 */
+ return ERL_ERROR;
+ if (ei_encode_long(header,&index,ERL_SEND_TT) < 0) /* 2 */
+ return ERL_ERROR;
} else {
- ei_encode_tuple_header(header,&index,3);
- ei_encode_long(header,&index,ERL_SEND);
+ if (ei_encode_tuple_header(header,&index,3) < 0)
+ return ERL_ERROR;
+ if (ei_encode_long(header,&index,ERL_SEND) < 0)
+ return ERL_ERROR;
}
- ei_encode_atom(header,&index,ei_getfdcookie(fd)); /* 258 */
- ei_encode_pid(header,&index,to); /* 268 */
+ if (ei_encode_atom(header,&index,ei_getfdcookie(fd)) < 0) /* 258 */
+ return ERL_ERROR;
+ if (ei_encode_pid(header,&index,to) < 0) /* 268 */
+ return ERL_ERROR;
- if (token) ei_encode_trace(header,&index,token); /* 534 */
+ if (token) {
+ if (ei_encode_trace(header,&index,token) < 0) /* 534 */
+ return ERL_ERROR;
+ }
/* control message (precedes header actually) */
/* length = 1 ('p') + header len + message len */
@@ -114,9 +119,11 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
+ erl_errno = 0;
+
return 0;
}
#endif /* EI_HAVE_STRUCT_IOVEC__ */
@@ -128,7 +135,7 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
len = tot_len = (ssize_t) msglen;
@@ -137,9 +144,10 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
+ erl_errno = 0;
return 0;
}
diff --git a/lib/erl_interface/src/connect/send_exit.c b/lib/erl_interface/src/connect/send_exit.c
index af0fb2af88..231aad0ae0 100644
--- a/lib/erl_interface/src/connect/send_exit.c
+++ b/lib/erl_interface/src/connect/send_exit.c
@@ -67,9 +67,12 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
return ERL_ERROR;
}
- if (len > EISMALLBUF)
- if (!(dbuf = malloc(len)))
- return -1;
+ if (len > EISMALLBUF) {
+ if (!(dbuf = malloc(len))) {
+ EI_CONN_SAVE_ERRNO__(ENOMEM);
+ return ERL_ERROR;
+ }
+ }
msgbuf = (dbuf ? dbuf : sbuf);
@@ -77,31 +80,46 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
/* check that he can receive trace tokens first */
if (ei_distversion(fd) > 0) token = ei_trace(0,NULL);
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+
index = 5; /* max sizes: */
- ei_encode_version(msgbuf,&index); /* 1 */
+ if (ei_encode_version(msgbuf,&index) < 0) /* 1 */
+ return ERL_ERROR;
if (token) {
- ei_encode_tuple_header(msgbuf,&index,5); /* 2 */
- ei_encode_long(msgbuf,&index,ERL_EXIT_TT); /* 2 */
+ if (ei_encode_tuple_header(msgbuf,&index,5) < 0) /* 2 */
+ return ERL_ERROR;
+ if (ei_encode_long(msgbuf,&index,ERL_EXIT_TT) < 0)/* 2 */
+ return ERL_ERROR;
}
else {
- ei_encode_tuple_header(msgbuf,&index,4);
- ei_encode_long(msgbuf,&index,ERL_EXIT);
+ if (ei_encode_tuple_header(msgbuf,&index,4) < 0)
+ return ERL_ERROR;
+ if (ei_encode_long(msgbuf,&index,ERL_EXIT) < 0)
+ return ERL_ERROR;
}
- ei_encode_pid(msgbuf,&index,from); /* 268 */
- ei_encode_pid(msgbuf,&index,to); /* 268 */
+ if (ei_encode_pid(msgbuf,&index,from) < 0) /* 268 */
+ return ERL_ERROR;
+ if (ei_encode_pid(msgbuf,&index,to) < 0) /* 268 */
+ return ERL_ERROR;
- if (token) ei_encode_trace(msgbuf,&index,token); /* 534 */
+ if (token) {
+ if (ei_encode_trace(msgbuf,&index,token) < 0) /* 534 */
+ return ERL_ERROR;
+ }
/* Reason */
- ei_encode_string(msgbuf,&index,reason); /* len */
+ if (ei_encode_string(msgbuf,&index,reason) < 0) /* len */
+ return ERL_ERROR;
/* 5 byte header missing */
s = msgbuf;
put32be(s, index - 4); /* 4 */
- put8(s, ERL_PASS_THROUGH); /* 1 */
+ put8(s, ERL_PASS_THROUGH); /* 1 */
/*** sum: len + 1080 */
- if (ei_tracelevel >= 4)
- ei_show_sendmsg(stderr,msgbuf,NULL);
+ if (ei_tracelevel >= 4) {
+ if (ei_show_sendmsg(stderr,msgbuf,NULL) < 0)
+ return ERL_ERROR;
+ }
wlen = (ssize_t) index;
err = ei_write_fill_ctx_t__(cbs, ctx, msgbuf, &wlen, tmo);
@@ -113,6 +131,8 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
EI_CONN_SAVE_ERRNO__(err);
return ERL_ERROR;
}
+
+ erl_errno = 0;
return 0;
}
diff --git a/lib/erl_interface/src/connect/send_reg.c b/lib/erl_interface/src/connect/send_reg.c
index 74c454eb3d..1d9c85f210 100644
--- a/lib/erl_interface/src/connect/send_reg.c
+++ b/lib/erl_interface/src/connect/send_reg.c
@@ -22,10 +22,6 @@
#include <windows.h>
#include <winbase.h>
-#elif VXWORKS
-#include <sys/types.h>
-#include <unistd.h>
-
#else /* unix */
#include <sys/types.h>
#include <unistd.h>
@@ -68,21 +64,33 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
if (ei_distversion(fd) > 0)
token = ei_trace(0,NULL);
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+
/* header = REG_SEND, from, cookie, toname max sizes: */
- ei_encode_version(header,&index); /* 1 */
+ if (ei_encode_version(header,&index) < 0) /* 1 */
+ return ERL_ERROR;
if (token) {
- ei_encode_tuple_header(header,&index,5); /* 2 */
- ei_encode_long(header,&index,ERL_REG_SEND_TT); /* 2 */
+ if (ei_encode_tuple_header(header,&index,5) < 0) /* 2 */
+ return ERL_ERROR;
+ if (ei_encode_long(header,&index,ERL_REG_SEND_TT) < 0) /* 2 */
+ return ERL_ERROR;
} else {
- ei_encode_tuple_header(header,&index,4);
- ei_encode_long(header,&index,ERL_REG_SEND);
+ if (ei_encode_tuple_header(header,&index,4) < 0)
+ return ERL_ERROR;
+ if (ei_encode_long(header,&index,ERL_REG_SEND) < 0)
+ return ERL_ERROR;
}
- ei_encode_pid(header, &index, from); /* 268 */
- ei_encode_atom(header, &index, ei_getfdcookie(fd)); /* 258 */
- ei_encode_atom(header, &index, to); /* 268 */
-
- if (token) ei_encode_trace(header,&index,token); /* 534 */
+ if (ei_encode_pid(header, &index, from) < 0) /* 268 */
+ return ERL_ERROR;
+ if (ei_encode_atom(header, &index, ei_getfdcookie(fd)) < 0) /* 258 */
+ return ERL_ERROR;
+ if (ei_encode_atom(header, &index, to) < 0) /* 268 */
+ return ERL_ERROR;
+ if (token) {
+ if (ei_encode_trace(header,&index,token) < 0) /* 534 */
+ return ERL_ERROR;
+ }
/* control message (precedes header actually) */
/* length = 1 ('p') + header len + message len */
s = header;
@@ -107,8 +115,11 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
+
+ erl_errno = 0;
+
return 0;
}
#endif /* EI_HAVE_STRUCT_IOVEC__ */
@@ -120,7 +131,7 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
len = tot_len = (ssize_t) msglen;
@@ -129,8 +140,10 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
err = EIO;
if (err) {
EI_CONN_SAVE_ERRNO__(err);
- return -1;
+ return ERL_ERROR;
}
+
+ erl_errno = 0;
return 0;
}
diff --git a/lib/erl_interface/src/decode/decode_big.c b/lib/erl_interface/src/decode/decode_big.c
index cbbbd3f0b7..bd2d6662a5 100644
--- a/lib/erl_interface/src/decode/decode_big.c
+++ b/lib/erl_interface/src/decode/decode_big.c
@@ -144,13 +144,6 @@ int ei_big_comp(erlang_big *x, erlang_big *y)
* Handling of floating point exceptions.
*/
-#if defined(VXWORKS) && CPU == PPC860
-#undef NO_FPE_SIGNALS
-#define NO_FPE_SIGNALS 1
-#undef INLINED_FP_CONVERSION
-#define INLINED_FP_CONVERSION 1
-#endif
-
#ifdef NO_FPE_SIGNALS
# define ERTS_FP_CHECK_INIT() do {} while (0)
# define ERTS_FP_ERROR(f, Action) if (!isfinite(f)) { Action; } else {}
diff --git a/lib/erl_interface/src/decode/decode_iodata.c b/lib/erl_interface/src/decode/decode_iodata.c
new file mode 100644
index 0000000000..88d23d8a1e
--- /dev/null
+++ b/lib/erl_interface/src/decode/decode_iodata.c
@@ -0,0 +1,166 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2020. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ *
+ */
+
+#include <string.h>
+#include "eidef.h"
+#include "eiext.h"
+#include "putget.h"
+
+static int decode_list_ext_iodata(const char *buf, int* index,
+ int *szp, unsigned char **pp);
+static void decode_string(const char *buf, int* index,
+ int *szp, unsigned char **pp);
+
+int ei_decode_iodata(const char *buf, int* index, int *szp, char *out_buf)
+{
+ int type, len;
+
+ if (szp)
+ *szp = 0;
+
+ if (ei_get_type(buf, index, &type, &len) < 0)
+ return -1;
+
+ /* Top level of iodata is either a list or a binary... */
+
+ switch (type) {
+
+ case ERL_BINARY_EXT: {
+ long llen;
+ if (ei_decode_binary(buf, index, out_buf, &llen) < 0)
+ return -1;
+ if (llen != (long) len)
+ return -1; /* general 64-bit issue with ei api... */
+ if (szp)
+ *szp += len;
+ return 0;
+ }
+
+ case ERL_STRING_EXT: {
+ unsigned char *p = (unsigned char *) out_buf;
+ decode_string(buf, index, szp, p ? &p : NULL);
+ return 0;
+ }
+ case ERL_NIL_EXT:
+ return ei_decode_list_header(buf, index, NULL);
+
+ case ERL_LIST_EXT: {
+ unsigned char *ptr = (unsigned char *) out_buf;
+ len = 0;
+ return decode_list_ext_iodata(buf, index,
+ szp ? szp : &len,
+ ptr ? &ptr : NULL);
+ }
+
+ default:
+ return -1; /* Not a list nor a binary... */
+ }
+}
+
+static int decode_list_ext_iodata(const char *buf, int* index,
+ int *szp, unsigned char **pp)
+{
+ int type, len, i, conses;
+
+ if (ei_decode_list_header(buf, index, &conses) < 0)
+ return -1;
+
+ for (i = 0; i <= conses; i++) {
+
+ if (ei_get_type(buf, index, &type, &len) < 0)
+ return -1;
+
+ switch (type) {
+ case ERL_SMALL_INTEGER_EXT:
+ case ERL_INTEGER_EXT: {
+ long val;
+ if (i == conses)
+ return -1; /* int not allowed in cdr of cons */
+ if (ei_decode_long(buf, index, &val) < 0)
+ return -1;
+ if (val < 0 || 255 < val)
+ return -1;
+ if (pp)
+ *((*pp)++) = (unsigned char) val;
+ *szp += 1;
+ break;
+ }
+ case ERL_BINARY_EXT: {
+ void *p = pp ? *pp : NULL;
+ long llen;
+ if (ei_decode_binary(buf, index, p, &llen) < 0)
+ return -1;
+ if (llen != (long) len)
+ return -1; /* general 64-bit issue with ei api... */
+ if (pp)
+ *pp += len;
+ *szp += len;
+ break;
+ }
+ case ERL_STRING_EXT:
+ decode_string(buf, index, szp, pp);
+ break;
+ case ERL_LIST_EXT:
+ if (decode_list_ext_iodata(buf, index, szp, pp) < 0)
+ return -1;
+ break;
+ case ERL_NIL_EXT:
+ if (ei_decode_list_header(buf, index, NULL) < 0)
+ return -1;
+ break;
+ default:
+ /* Not a list, a binary, nor a byte sized integer... */
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+decode_string(const char *buf, int* index, int *szp, unsigned char **pp)
+{
+ /*
+ * ei_decode_string() null-terminates the string
+ * which we do not want, so we decode it ourselves
+ * here instead...
+ */
+ int len;
+ char *s = (char *) buf + *index;
+ char *s0 = s;
+
+ /* ASSERT(*s == ERL_STRING_EXT); */
+ s++;
+
+ len = get16be(s);
+
+ if (pp) {
+ memcpy(*pp, s, len);
+ *pp += len;
+ }
+
+ if (szp)
+ *szp += len;
+
+ s += len;
+ *index += s-s0;
+
+}
diff --git a/lib/erl_interface/src/decode/decode_ref.c b/lib/erl_interface/src/decode/decode_ref.c
index c9b38c1c3b..c10a02094e 100644
--- a/lib/erl_interface/src/decode/decode_ref.c
+++ b/lib/erl_interface/src/decode/decode_ref.c
@@ -54,6 +54,9 @@ int ei_decode_ref(const char *buf, int *index, erlang_ref *p)
/* first the integer count */
count = get16be(s);
+ if (count > sizeof(p->n)/sizeof(p->n[0]))
+ return -1; /* Not enough space in struct... */
+
if (p) {
p->len = count;
if (get_atom(&s, p->node, NULL) < 0) return -1;
diff --git a/lib/erl_interface/src/encode/encode_pid.c b/lib/erl_interface/src/encode/encode_pid.c
index e1316f3355..264dc1dd4e 100644
--- a/lib/erl_interface/src/encode/encode_pid.c
+++ b/lib/erl_interface/src/encode/encode_pid.c
@@ -25,7 +25,6 @@
int ei_encode_pid(char *buf, int *index, const erlang_pid *p)
{
char* s = buf + *index;
- const char tag = (p->creation > 3) ? ERL_NEW_PID_EXT : ERL_PID_EXT;
++(*index); /* skip ERL_PID_EXT */
if (ei_encode_atom_len_as(buf, index, p->node, strlen(p->node),
@@ -33,21 +32,17 @@ int ei_encode_pid(char *buf, int *index, const erlang_pid *p)
return -1;
if (buf) {
- put8(s, tag);
+ put8(s, ERL_NEW_PID_EXT);
s = buf + *index;
/* now the integers */
put32be(s,p->num & 0x7fff); /* 15 bits */
put32be(s,p->serial & 0x1fff); /* 13 bits */
- if (tag == ERL_PID_EXT) {
- put8(s,(p->creation & 0x03)); /* 2 bits */
- } else {
- put32be(s, p->creation); /* 32 bits */
- }
+ put32be(s, p->creation); /* 32 bits */
}
- *index += 4 + 4 + (tag == ERL_PID_EXT ? 1 : 4);
+ *index += 4 + 4 + 4;
return 0;
}
diff --git a/lib/erl_interface/src/encode/encode_port.c b/lib/erl_interface/src/encode/encode_port.c
index c4fd5c40a8..4d0d1f9290 100644
--- a/lib/erl_interface/src/encode/encode_port.c
+++ b/lib/erl_interface/src/encode/encode_port.c
@@ -25,7 +25,6 @@
int ei_encode_port(char *buf, int *index, const erlang_port *p)
{
char *s = buf + *index;
- const char tag = p->creation > 3 ? ERL_NEW_PORT_EXT : ERL_PORT_EXT;
++(*index); /* skip ERL_PORT_EXT */
if (ei_encode_atom_len_as(buf, index, p->node, strlen(p->node), ERLANG_UTF8,
@@ -33,19 +32,15 @@ int ei_encode_port(char *buf, int *index, const erlang_port *p)
return -1;
}
if (buf) {
- put8(s, tag);
+ put8(s, ERL_NEW_PORT_EXT);
s = buf + *index;
/* now the integers */
put32be(s,p->id & 0x0fffffff /* 28 bits */);
- if (tag == ERL_PORT_EXT) {
- put8(s,(p->creation & 0x03));
- } else {
- put32be(s, p->creation);
- }
+ put32be(s, p->creation);
}
- *index += 4 + (tag == ERL_PORT_EXT ? 1 : 4);
+ *index += 4 + 4;
return 0;
}
diff --git a/lib/erl_interface/src/encode/encode_ref.c b/lib/erl_interface/src/encode/encode_ref.c
index e24ee392cc..7e65d647ec 100644
--- a/lib/erl_interface/src/encode/encode_ref.c
+++ b/lib/erl_interface/src/encode/encode_ref.c
@@ -24,7 +24,6 @@
int ei_encode_ref(char *buf, int *index, const erlang_ref *p)
{
- const char tag = (p->creation > 3) ? ERL_NEWER_REFERENCE_EXT : ERL_NEW_REFERENCE_EXT;
char *s = buf + *index;
int i;
@@ -37,7 +36,7 @@ int ei_encode_ref(char *buf, int *index, const erlang_ref *p)
/* Always encode as an extended reference; all participating parties
are now expected to be able to decode extended references. */
if (buf) {
- put8(s, tag);
+ put8(s, ERL_NEWER_REFERENCE_EXT);
/* first, number of integers */
put16be(s, p->len);
@@ -46,15 +45,12 @@ int ei_encode_ref(char *buf, int *index, const erlang_ref *p)
s = buf + *index;
/* now the integers */
- if (tag == ERL_NEW_REFERENCE_EXT)
- put8(s,(p->creation & 0x03));
- else
- put32be(s, p->creation);
+ put32be(s, p->creation);
for (i = 0; i < p->len; i++)
put32be(s,p->n[i]);
}
- *index += p->len*4 + (tag == ERL_NEW_REFERENCE_EXT ? 1 : 4);
+ *index += p->len*4 + 4;
return 0;
}
diff --git a/lib/erl_interface/src/encode/encode_trace.c b/lib/erl_interface/src/encode/encode_trace.c
index d0e6aec474..4c51e3d84b 100644
--- a/lib/erl_interface/src/encode/encode_trace.c
+++ b/lib/erl_interface/src/encode/encode_trace.c
@@ -23,12 +23,18 @@
int ei_encode_trace(char *buf, int *index, const erlang_trace *p)
{
/* { Flags, Label, Serial, FromPid, Prev } */
- ei_encode_tuple_header(buf,index,5);
- ei_encode_long(buf,index,p->flags);
- ei_encode_long(buf,index,p->label);
- ei_encode_long(buf,index,p->serial);
- ei_encode_pid(buf,index,&p->from);
- ei_encode_long(buf,index,p->prev);
+ if (ei_encode_tuple_header(buf,index,5) < 0)
+ return -1;
+ if (ei_encode_long(buf,index,p->flags) < 0)
+ return -1;
+ if (ei_encode_long(buf,index,p->label) < 0)
+ return -1;
+ if (ei_encode_long(buf,index,p->serial) < 0)
+ return -1;
+ if (ei_encode_pid(buf,index,&p->from) < 0)
+ return -1;
+ if (ei_encode_long(buf,index,p->prev) < 0)
+ return -1;
/* index is updated by the functions we called */
diff --git a/lib/erl_interface/src/epmd/ei_epmd.h b/lib/erl_interface/src/epmd/ei_epmd.h
index ef2959822e..6a274b606a 100644
--- a/lib/erl_interface/src/epmd/ei_epmd.h
+++ b/lib/erl_interface/src/epmd/ei_epmd.h
@@ -24,9 +24,12 @@
#define INADDR_LOOPBACK ((u_long) 0x7F000001)
#endif
+#define EI_DIST_5 5 /* OTP R4 - 22 */
+#define EI_DIST_6 6 /* OTP 23 and later */
+
#ifndef EI_DIST_HIGH
-#define EI_DIST_HIGH 5 /* R4 and later */
-#define EI_DIST_LOW 1 /* R3 and earlier */
+#define EI_DIST_HIGH EI_DIST_6
+#define EI_DIST_LOW EI_DIST_5
#endif
#ifndef EPMD_PORT
@@ -45,6 +48,7 @@
#ifndef EI_EPMD_ALIVE2_REQ
#define EI_EPMD_ALIVE2_REQ 120
#define EI_EPMD_ALIVE2_RESP 121
+#define EI_EPMD_ALIVE2_X_RESP 118
#define EI_EPMD_PORT2_REQ 122
#define EI_EPMD_PORT2_RESP 119
#define EI_EPMD_STOP_REQ 's'
diff --git a/lib/erl_interface/src/epmd/epmd_port.c b/lib/erl_interface/src/epmd/epmd_port.c
index 64cd0986b0..0bb75520ce 100644
--- a/lib/erl_interface/src/epmd/epmd_port.c
+++ b/lib/erl_interface/src/epmd/epmd_port.c
@@ -25,16 +25,6 @@
#include <windows.h>
#include <winbase.h>
-#elif VXWORKS
-#include <vxWorks.h>
-#include <ifLib.h>
-#include <sockLib.h>
-#include <inetLib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-
#else
#include <unistd.h>
#include <sys/types.h>
@@ -114,9 +104,6 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
int err;
ssize_t dlen;
unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
-#if defined(VXWORKS)
- char ntoabuf[32];
-#endif
if (len > sizeof(buf) - 3)
{
@@ -144,17 +131,8 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
return -1;
}
-#ifdef VXWORKS
- /* FIXME use union/macro for level. Correct level? */
- if (ei_tracelevel > 2) {
- inet_ntoa_b(*addr,ntoabuf);
- EI_TRACE_CONN2("ei_epmd_r4_port",
- "-> PORT2_REQ alive=%s ip=%s",alive,ntoabuf);
- }
-#else
EI_TRACE_CONN2("ei_epmd_r4_port",
"-> PORT2_REQ alive=%s ip=%s",alive,inet_ntoa(*addr));
-#endif
dlen = (ssize_t) 2;
err = ei_read_fill_t__(fd, buf, &dlen, tmo);
diff --git a/lib/erl_interface/src/epmd/epmd_publish.c b/lib/erl_interface/src/epmd/epmd_publish.c
index 81c0b477fa..3e251d6a64 100644
--- a/lib/erl_interface/src/epmd/epmd_publish.c
+++ b/lib/erl_interface/src/epmd/epmd_publish.c
@@ -25,16 +25,6 @@
#include <windows.h>
#include <winbase.h>
-#elif VXWORKS
-#include <vxWorks.h>
-#include <ifLib.h>
-#include <sockLib.h>
-#include <inetLib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-
#else
#include <unistd.h>
#include <sys/types.h>
@@ -68,7 +58,8 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms)
int nlen = strlen(alive);
int len = elen + nlen + 13; /* hard coded: be careful! */
int n;
- int err, res, creation;
+ int err, response, res;
+ unsigned creation;
ssize_t dlen;
unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
@@ -124,8 +115,10 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms)
/* Don't close fd here! It keeps us registered with epmd */
s = buf;
- if (((res=get8(s)) != EI_EPMD_ALIVE2_RESP)) { /* response */
- EI_TRACE_ERR1("ei_epmd_r4_publish","<- unknown (%d)",res);
+ response = get8(s);
+ if (response != EI_EPMD_ALIVE2_RESP &&
+ response != EI_EPMD_ALIVE2_X_RESP) {
+ EI_TRACE_ERR1("ei_epmd_r4_publish","<- unknown (%d)",response);
EI_TRACE_ERR0("ei_epmd_r4_publish","-> CLOSE");
ei_close__(fd);
erl_errno = EIO;
@@ -141,18 +134,21 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms)
return -1;
}
- creation = get16be(s);
+ if (response == EI_EPMD_ALIVE2_RESP)
+ creation = get16be(s);
+ else /* EI_EPMD_ALIVE2_X_RESP */
+ creation = get32be(s);
EI_TRACE_CONN2("ei_epmd_r4_publish",
- " result=%d (ok) creation=%d",res,creation);
-
- /* probably should save fd so we can close it later... */
- /* epmd_saveconn(OPEN,fd,alive); */
+ " result=%d (ok) creation=%u",res,creation);
- /* return the creation number, for no good reason */
- /* return creation;*/
+ /*
+ * Would be nice to somehow use the nice "unique" creation value
+ * received here from epmd instead of using the crappy one
+ * passed (already) to ei_connect_init.
+ */
- /* no - return the descriptor */
+ /* return the descriptor */
return fd;
}
diff --git a/lib/erl_interface/src/epmd/epmd_unpublish.c b/lib/erl_interface/src/epmd/epmd_unpublish.c
index d5d59c4731..c463732250 100644
--- a/lib/erl_interface/src/epmd/epmd_unpublish.c
+++ b/lib/erl_interface/src/epmd/epmd_unpublish.c
@@ -22,16 +22,6 @@
#include <windows.h>
#include <winbase.h>
-#elif VXWORKS
-#include <vxWorks.h>
-#include <ifLib.h>
-#include <sockLib.h>
-#include <inetLib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-
#else
#include <unistd.h>
#include <sys/types.h>
diff --git a/lib/erl_interface/src/legacy/global_names.c b/lib/erl_interface/src/global/global_names.c
index ee808620fb..4524439467 100644
--- a/lib/erl_interface/src/legacy/global_names.c
+++ b/lib/erl_interface/src/global/global_names.c
@@ -24,8 +24,9 @@
#include "eisend.h"
#include "eirecv.h"
#include "ei_connect_int.h"
-#include "erl_interface.h"
-#include "erl_connect.h"
+#include "ei.h"
+#include "ei_connect.h"
+#include "ei_internal.h"
#define GLOBALNAMEBUF (16*1024) /* not very small actually */
@@ -36,14 +37,14 @@
* caller can make one call to free().
*/
/* global:registered_names() -> [name1,name2,...] */
-char **erl_global_names(int fd, int *count)
+char **ei_global_names(ei_cnode *ec, int fd, int *count)
{
char buf[GLOBALNAMEBUF];
char *bufp=buf;
char tmpbuf[64];
int size = 0;
int index = 0;
- erlang_pid *self = erl_self();
+ erlang_pid *self = ei_self(ec);
erlang_msg msg;
int i;
int version;
@@ -52,16 +53,18 @@ char **erl_global_names(int fd, int *count)
char **names;
char *s;
- self->num = fd;
- ei_encode_version(buf,&index);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_pid(buf,&index,self); /* PidFrom */
- ei_encode_tuple_header(buf,&index,5);
- ei_encode_atom(buf,&index,"call"); /* call */
- ei_encode_atom(buf,&index,"global"); /* Mod */
- ei_encode_atom(buf,&index,"registered_names"); /* Fun */
- ei_encode_list_header(buf,&index,0); /* Args: [ ] */
- ei_encode_atom(buf,&index,"user"); /* user */
+ if (ei_encode_version(buf,&index)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_pid(buf,&index,self) /* PidFrom */
+ || ei_encode_tuple_header(buf,&index,5)
+ || ei_encode_atom(buf,&index,"call") /* call */
+ || ei_encode_atom(buf,&index,"global") /* Mod */
+ || ei_encode_atom(buf,&index,"registered_names")/* Fun */
+ || ei_encode_list_header(buf,&index,0) /* Args: [ ] */
+ || ei_encode_atom(buf,&index,"user")) { /* user */
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+ return NULL;
+ }
/* make the rpc call */
if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return NULL;
@@ -72,7 +75,10 @@ char **erl_global_names(int fd, int *count)
else break;
}
- if (i != ERL_SEND) return NULL;
+ if (i != ERL_SEND) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return NULL;
+ }
/* expecting { rex, [name1, name2, ...] } */
size = msglen;
@@ -83,7 +89,10 @@ char **erl_global_names(int fd, int *count)
|| (arity != 2)
|| ei_decode_atom(buf,&index,tmpbuf)
|| strcmp(tmpbuf,"rex")
- || ei_decode_list_header(buf,&index,&arity)) return NULL;
+ || ei_decode_list_header(buf,&index,&arity)) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return NULL;
+ }
/* we use the size of the rest of the received message to estimate
@@ -92,7 +101,10 @@ char **erl_global_names(int fd, int *count)
* a little less than the atoms themselves needed in the reply.
*/
arity++; /* we will need a terminating NULL as well */
- if (!(names = malloc((arity * sizeof(char**)) + (size-index)))) return NULL;
+ if (!(names = malloc((arity * sizeof(char**)) + (size-index)))) {
+ EI_CONN_SAVE_ERRNO__(ENOMEM);
+ return NULL;
+ }
/* arity pointers first, followed by s */
s = (char *)(names+arity);
diff --git a/lib/erl_interface/src/legacy/global_register.c b/lib/erl_interface/src/global/global_register.c
index 4cb6d8071f..dbdab8dbc5 100644
--- a/lib/erl_interface/src/legacy/global_register.c
+++ b/lib/erl_interface/src/global/global_register.c
@@ -22,15 +22,15 @@
#include "eiext.h"
#include "eisend.h"
#include "eirecv.h"
-#include "erl_interface.h"
+#include "ei.h"
+#include "ei_internal.h"
-int erl_global_register(int fd, const char *name, ETERM *pid)
+int ei_global_register(int fd, const char *name, erlang_pid *self)
{
char buf[EISMALLBUF];
char *bufp=buf;
char tmpbuf[64];
int index = 0;
- erlang_pid self;
erlang_msg msg;
int needlink, needatom, needmonitor;
int arity;
@@ -38,32 +38,30 @@ int erl_global_register(int fd, const char *name, ETERM *pid)
int msglen;
int i;
- /* get that pid into a better format */
- if (!erl_encode(pid,(unsigned char*)buf)) return -1;
- if (ei_decode_version(buf,&index,&version)
- || ei_decode_pid(buf,&index,&self)) return -1;
-
/* set up rpc arguments */
/* { PidFrom, { call, Mod, Fun, Args, user }} */
index = 0;
- ei_encode_version(buf,&index);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_pid(buf,&index,&self); /* PidFrom */
- ei_encode_tuple_header(buf,&index,5);
- ei_encode_atom(buf,&index,"call"); /* call */
- ei_encode_atom(buf,&index,"global"); /* Mod */
- ei_encode_atom(buf,&index,"register_name_external"); /* Fun */
- ei_encode_list_header(buf,&index,3); /* Args: [ name, self(), cnode ] */
- ei_encode_atom(buf,&index,name);
- ei_encode_pid(buf,&index,&self);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_atom(buf,&index,"global"); /* special "resolve" treatment */
- ei_encode_atom(buf,&index,"cnode"); /* i.e. we get a SEND when conflict */
- ei_encode_empty_list(buf,&index);
- ei_encode_atom(buf,&index,"user"); /* user */
+ if (ei_encode_version(buf,&index)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_pid(buf,&index,self) /* PidFrom */
+ || ei_encode_tuple_header(buf,&index,5)
+ || ei_encode_atom(buf,&index,"call") /* call */
+ || ei_encode_atom(buf,&index,"global") /* Mod */
+ || ei_encode_atom(buf,&index,"register_name_external")/* Fun */
+ || ei_encode_list_header(buf,&index,3) /* Args: [ name, self(), cnode ] */
+ || ei_encode_atom(buf,&index,name)
+ || ei_encode_pid(buf,&index,self)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_atom(buf,&index,"global") /* special "resolve" treatment */
+ || ei_encode_atom(buf,&index,"cnode") /* i.e. we get a SEND when conflict */
+ || ei_encode_empty_list(buf,&index)
+ || ei_encode_atom(buf,&index,"user")) { /* user */
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+ return ERL_ERROR;
+ }
/* make the rpc call */
- if (ei_send_reg_encoded(fd,&self,"rex",buf,index)) return -1;
+ if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return ERL_ERROR;
/* get the reply: expect link and an atom, or just an atom */
needlink = needatom = needmonitor = 1;
@@ -78,19 +76,28 @@ int erl_global_register(int fd, const char *name, ETERM *pid)
switch (i) {
case ERL_LINK:
/* got link */
- if (!needlink) return -1;
+ if (!needlink) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
needlink = 0;
break;
case ERL_MONITOR_P-10:
/* got monitor */
- if (!needmonitor) { return -1;}
+ if (!needmonitor) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
needmonitor = 0;
break;
case ERL_SEND:
/* got message - does it contain our atom? */
- if (!needatom) return -1;
+ if (!needatom) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
else {
/* expecting { rex, yes } */
index = 0;
@@ -100,8 +107,10 @@ int erl_global_register(int fd, const char *name, ETERM *pid)
|| ei_decode_atom(buf,&index,tmpbuf)
|| strcmp(tmpbuf,"rex")
|| ei_decode_atom(buf,&index,tmpbuf)
- || strcmp(tmpbuf,"yes"))
- return -1; /* bad response from other side */
+ || strcmp(tmpbuf,"yes")) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR; /* bad response from other side */
+ }
/* we're done */
return 0;
@@ -109,7 +118,8 @@ int erl_global_register(int fd, const char *name, ETERM *pid)
break;
default:
- return -1; /* something else */
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR; /* something else */
}
}
return 0;
diff --git a/lib/erl_interface/src/legacy/global_unregister.c b/lib/erl_interface/src/global/global_unregister.c
index 27f68670ca..4743fe65d7 100644
--- a/lib/erl_interface/src/legacy/global_unregister.c
+++ b/lib/erl_interface/src/global/global_unregister.c
@@ -23,39 +23,41 @@
#include "eisend.h"
#include "eirecv.h"
#include "ei_connect_int.h"
-#include "erl_interface.h"
-#include "erl_connect.h"
+#include "ei_connect.h"
+#include "ei.h"
+#include "ei_internal.h"
/* remove the association between name and its pid */
/* global:unregister_name(name) -> ok */
-int erl_global_unregister(int fd, const char *name)
+int ei_global_unregister(ei_cnode *ec, int fd, const char *name)
{
char buf[EISMALLBUF];
char *bufp=buf;
char tmpbuf[64];
int index = 0;
- erlang_pid *self = erl_self();
+ erlang_pid *self = ei_self(ec);
erlang_msg msg;
int i;
int version,arity,msglen;
int needunlink, needatom, needdemonitor;
- /* make a self pid */
- self->num = fd;
- ei_encode_version(buf,&index);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_pid(buf,&index,self); /* PidFrom */
- ei_encode_tuple_header(buf,&index,5);
- ei_encode_atom(buf,&index,"call"); /* call */
- ei_encode_atom(buf,&index,"global"); /* Mod */
- ei_encode_atom(buf,&index,"unregister_name_external"); /* Fun */
- ei_encode_list_header(buf,&index,1); /* Args: [ name ] */
- ei_encode_atom(buf,&index,name);
- ei_encode_empty_list(buf,&index);
- ei_encode_atom(buf,&index,"user"); /* user */
+ if (ei_encode_version(buf,&index)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_pid(buf,&index,self) /* PidFrom */
+ || ei_encode_tuple_header(buf,&index,5)
+ || ei_encode_atom(buf,&index,"call") /* call */
+ || ei_encode_atom(buf,&index,"global") /* Mod */
+ || ei_encode_atom(buf,&index,"unregister_name_external") /* Fun */
+ || ei_encode_list_header(buf,&index,1) /* Args: [ name ] */
+ || ei_encode_atom(buf,&index,name)
+ || ei_encode_empty_list(buf,&index)
+ || ei_encode_atom(buf,&index,"user")) { /* user */
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+ return ERL_ERROR;
+ }
/* make the rpc call */
- if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return -1;
+ if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return ERL_ERROR;
/* get the reply: expect unlink and an atom, or just an atom */
needunlink = needatom = needdemonitor = 1;
@@ -70,19 +72,28 @@ int erl_global_unregister(int fd, const char *name)
switch (i) {
case ERL_UNLINK:
/* got unlink */
- if (!needunlink) return -1;
+ if (!needunlink) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
needunlink = 0;
break;
case ERL_DEMONITOR_P-10:
/* got demonitor */
- if (!needdemonitor) return -1;
+ if (!needdemonitor) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
needdemonitor = 0;
break;
case ERL_SEND:
/* got message - does it contain our atom? */
- if (!needatom) return -1;
+ if (!needatom) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
+ }
else {
/* expecting { rex, ok } */
index = 0;
@@ -92,8 +103,10 @@ int erl_global_unregister(int fd, const char *name)
|| ei_decode_atom(buf,&index,tmpbuf)
|| strcmp(tmpbuf,"rex")
|| ei_decode_atom(buf,&index,tmpbuf)
- || strcmp(tmpbuf,"ok"))
- return -1; /* bad response from other side */
+ || strcmp(tmpbuf,"ok")) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR; /* bad response from other side */
+ }
/* we're done here */
return 0;
@@ -101,7 +114,8 @@ int erl_global_unregister(int fd, const char *name)
break;
default:
- return -1;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
}
diff --git a/lib/erl_interface/src/legacy/global_whereis.c b/lib/erl_interface/src/global/global_whereis.c
index 13c4c93ca7..5a6134c2ab 100644
--- a/lib/erl_interface/src/legacy/global_whereis.c
+++ b/lib/erl_interface/src/global/global_whereis.c
@@ -24,43 +24,43 @@
#include "eisend.h"
#include "eirecv.h"
#include "ei_connect_int.h"
-#include "erl_interface.h"
-#include "erl_connect.h"
+#include "ei.h"
+#include "ei_connect.h"
+#include "ei_internal.h"
/* return the ETERM pid corresponding to name. If caller
* provides non-NULL node, nodename will be returned there
*/
/* global:whereis_name(name) -> pid */
-ETERM *erl_global_whereis(int fd, const char *name, char *node)
+int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, char *node)
{
char buf[EISMALLBUF];
char *bufp=buf;
char tmpbuf[64];
int index = 0;
- erlang_pid *self = erl_self();
+ erlang_pid *self = ei_self(ec);
erlang_pid epid;
- ETERM *opid;
erlang_msg msg;
int i;
int version,arity,msglen;
- self->num = fd; /* FIXME looks strange to change something?! */
-
- ei_encode_version(buf,&index);
- ei_encode_tuple_header(buf,&index,2);
- ei_encode_pid(buf,&index,self); /* PidFrom */
- ei_encode_tuple_header(buf,&index,5);
- ei_encode_atom(buf,&index,"call"); /* call */
- ei_encode_atom(buf,&index,"global"); /* Mod */
- ei_encode_atom(buf,&index,"whereis_name"); /* Fun */
- ei_encode_list_header(buf,&index,1); /* Args: [ name ] */
- ei_encode_atom(buf,&index,name);
- ei_encode_empty_list(buf,&index);
- ei_encode_atom(buf,&index,"user"); /* user */
-
+ if (ei_encode_version(buf,&index)
+ || ei_encode_tuple_header(buf,&index,2)
+ || ei_encode_pid(buf,&index,self) /* PidFrom */
+ || ei_encode_tuple_header(buf,&index,5)
+ || ei_encode_atom(buf,&index,"call") /* call */
+ || ei_encode_atom(buf,&index,"global") /* Mod */
+ || ei_encode_atom(buf,&index,"whereis_name") /* Fun */
+ || ei_encode_list_header(buf,&index,1) /* Args: [ name ] */
+ || ei_encode_atom(buf,&index,name)
+ || ei_encode_empty_list(buf,&index)
+ || ei_encode_atom(buf,&index,"user")) { /* user */
+ EI_CONN_SAVE_ERRNO__(EINVAL);
+ return ERL_ERROR;
+ }
/* make the rpc call */
- if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return NULL;
+ if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return ERL_ERROR;
while (1) {
index = EISMALLBUF;
@@ -68,7 +68,7 @@ ETERM *erl_global_whereis(int fd, const char *name, char *node)
else break;
}
- if (i != ERL_SEND) return NULL;
+ if (i != ERL_SEND) return ERL_ERROR;
/* expecting { rex, pid } */
index = 0;
@@ -77,25 +77,22 @@ ETERM *erl_global_whereis(int fd, const char *name, char *node)
|| (arity != 2)
|| ei_decode_atom(buf,&index,tmpbuf)
|| strcmp(tmpbuf,"rex")
- || ei_decode_pid(buf,&index,&epid))
- return NULL; /* bad response from other side */
-
- /* put the pid into a format for the caller */
- index = 0;
- ei_encode_pid(buf,&index,&epid);
- opid = erl_decode((unsigned char*)buf);
+ || ei_decode_pid(buf,&index,&epid)) {
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR; /* bad response from other side */
+ }
/* extract the nodename for the caller */
if (node) {
- char* node_str = ERL_PID_NODE(opid);
+ char* node_str = epid.node;
if (node_str) {
strcpy(node, node_str);
}
else {
- erl_free_term(opid);
- return NULL;
+ EI_CONN_SAVE_ERRNO__(EBADMSG);
+ return ERL_ERROR;
}
}
-
- return opid;
+ *pid = epid;
+ return 0;
}
diff --git a/lib/erl_interface/src/legacy/decode_term.c b/lib/erl_interface/src/legacy/decode_term.c
deleted file mode 100644
index 72bacc3123..0000000000
--- a/lib/erl_interface/src/legacy/decode_term.c
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-#include "eidef.h"
-#include "eiext.h"
-#include "putget.h"
-#include "erl_interface.h"
-
-/*
- * This file is actually part of the erl_interface library,
- * not the newer 'ei' library. The header file is still in "ei.h"
- */
-
-/* FIXME: is this to be completed? */
-
-#if (0)
-int ei_decode_term(const char *buf, int *index, void *t)
-{
- const char *s = buf + *index;
- const char *s0 = s;
-
- if (t) {
- ETERM *tmp;
-
- /* this decodes and advances s */
- if (!(tmp = erl_decode_buf((unsigned char **)&s))) return -1;
-
- *(ETERM **)t = tmp;
- *index += s - s0;
-
- return 0;
- }
- else {
- int tmpindex = *index;
- long ttype;
- int arity;
- int i;
-
- /* these are all the external types */
- switch ((ttype = get8(s))) {
- case ERL_SMALL_INTEGER_EXT:
- case ERL_INTEGER_EXT:
- case ERL_SMALL_BIG_EXT:
- return ei_decode_long(buf,index,NULL);
-
- case ERL_FLOAT_EXT:
- case NEW_FLOAT_EXT:
- return ei_decode_double(buf,index,NULL);
-
- case ERL_ATOM_EXT:
- return ei_decode_atom(buf,index,NULL);
-
- case ERL_REFERENCE_EXT:
- case ERL_NEW_REFERENCE_EXT:
- return ei_decode_ref(buf,index,NULL);
-
- case ERL_PORT_EXT:
- return ei_decode_port(buf,index,NULL);
-
- case ERL_PID_EXT:
- return ei_decode_pid(buf,index,NULL);
-
- case ERL_SMALL_TUPLE_EXT:
- case ERL_LARGE_TUPLE_EXT:
- if (ei_decode_tuple_header(buf,index,&arity) < 0)
- return -1;
-
- for (i=0; i<arity; i++) {
- if (ei_decode_term(buf,index,NULL)) {
- /* restore possibly changed index before returning */
- *index = tmpindex;
- return -1;
- }
- }
- return 0;
-
- case ERL_STRING_EXT:
- return ei_decode_string(buf,index,NULL);
-
- case ERL_LIST_EXT:
- case ERL_NIL_EXT:
- if (ei_decode_list_header(buf,index,&arity) < 0)
- return -1;
-
- if (arity) {
- for (i=0; i<arity; i++) {
- if (ei_decode_term(buf,index,NULL) < 0) {
- /* restore possibly changed index before returning */
- *index = tmpindex;
- return -1;
- }
- }
- if (ei_decode_list_header(buf,index,&arity) < 0) {
- *index = tmpindex;
- return -1;
- }
- }
- return 0;
-
- case ERL_BINARY_EXT:
- return ei_decode_binary(buf,index,NULL,NULL);
-
- case ERL_LARGE_BIG_EXT:
- default:
- break;
- }
- }
-
- return -1;
-}
-#else
-int ei_decode_term(const char *buf, int *index, void *t)
-{
- const char *s = buf + *index;
- const char *s0 = s;
- ETERM *tmp;
-
- /* this decodes and advances s */
- if (!(tmp = erl_decode_buf((unsigned char **)&s))) return -1;
-
- if (t) *(ETERM **)t = tmp;
- else erl_free_term(tmp);
-
- *index += s - s0;
-
- return 0;
-}
-#endif
diff --git a/lib/erl_interface/src/legacy/encode_term.c b/lib/erl_interface/src/legacy/encode_term.c
deleted file mode 100644
index df740ab487..0000000000
--- a/lib/erl_interface/src/legacy/encode_term.c
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-#include "eidef.h"
-#include "eiext.h"
-#include "putget.h"
-#include "ei_x_encode.h"
-#include "erl_interface.h"
-#include "erl_marshal.h"
-
-/* FIXME: depends on old erl_interface */
-
-int ei_x_encode_term(ei_x_buff* x, void* t)
-{
- int i = x->index;
- ei_encode_term(NULL, &i, t);
- if (!x_fix_buff(x, i))
- return -1;
- return ei_encode_term(x->buff, &x->index, t);
-}
-
-int ei_encode_term(char *buf, int *index, void *t)
-{
- char *s = buf + *index;
- char *s0 = s;
-
- if (!buf) s += erl_term_len(t) -1; /* -1 for version */
- else {
- /* this encodes all but the version at the start */
- /* and it will move s forward the right number of bytes */
- if (erl_encode_it(t,(unsigned char **)&s, 5)) return -1;
- }
-
- *index += s - s0;
-
- return 0;
-}
-
diff --git a/lib/erl_interface/src/legacy/erl_connect.c b/lib/erl_interface/src/legacy/erl_connect.c
deleted file mode 100644
index 3941a5775d..0000000000
--- a/lib/erl_interface/src/legacy/erl_connect.c
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2020. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-/*
- * Purpose: Connect to any node at any host.
- */
-
-/***************************************************************************
- *
- * 'erl_interface' node connection handling is to use 'ei' for all
- * operations without access to the internal structure of saved data,
- * e.i. it should use the public interface functions. The connection
- * handling can be seen as a restricted node interface where only one
- * node can be used in one operating system process.
- *
- ***************************************************************************/
-
-#include "eidef.h"
-
-#include <stdlib.h>
-#include <sys/types.h>
-#include <fcntl.h>
-
-#ifdef __WIN32__
-#include <winsock2.h>
-#include <windows.h>
-#include <winbase.h>
-
-#elif VXWORKS
-#include <vxWorks.h>
-#include <hostLib.h>
-#include <selectLib.h>
-#include <ifLib.h>
-#include <sockLib.h>
-#include <taskLib.h>
-#include <inetLib.h>
-
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/times.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <timers.h>
-
-#include "erl_error.h"
-
-#else /* some other unix */
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/times.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-#include <sys/utsname.h> /* for gen_challenge (NEED FIX?) */
-#endif
-
-/* common includes */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-/* FIXME include less */
-#include "erl_interface.h"
-#include "erl_connect.h"
-#include "erl_eterm.h"
-#include "erl_malloc.h"
-#include "putget.h"
-#include "ei.h"
-#include "ei_connect_int.h"
-#include "ei_locking.h"
-#include "ei_epmd.h"
-#include "ei_internal.h"
-
-/* rpc_from() uses a buffer this size */
-#ifndef MAX_RECEIVE_BUF
-#define MAX_RECEIVE_BUF 32*1024
-#endif
-
-/* This is the global state of the old erl_* API */
-
-static ei_cnode erl_if_ec;
-
-/***************************************************************************
- *
- * API: erl_connect_init()
- * API: erl_connect_xinit()
- *
- * Returns 1 on success and 0 on failure.
- * Not documented to set erl_errno.
- *
- ***************************************************************************/
-
-int erl_connect_init(int this_node_number, char *cookie, short creation)
-{
- char nn[MAXATOMLEN];
-
- sprintf(nn, "c%d", this_node_number);
-
- return ei_connect_init(&erl_if_ec, nn, cookie, creation) == 0;
-}
-
-/* FIXME documented to use struct in_addr as addr */
-
-int erl_connect_xinit(char *thishostname,
- char *thisalivename,
- char *thisnodename,
- struct in_addr *thisipaddr,
- char *cookie,
- short creation)
-{
- return ei_connect_xinit(&erl_if_ec, thishostname, thisalivename,
- thisnodename, thisipaddr, cookie, creation) >= 0;
-}
-
-/***************************************************************************
- *
- * API: erl_connect()
- * API: erl_xconnect()
- *
- * Set up a connection to a given Node, and interchange hand shake
- * messages with it.
- *
- * Returns valid file descriptor on success and < 0 on failure.
- * Set erl_errno to EHOSTUNREACH, ENOMEM, EIO or errno from socket(2)
- * or connect(2).
- *
- ***************************************************************************/
-
-int erl_connect(char *nodename)
-{
- int res = ei_connect(&erl_if_ec, nodename);
- if (res < 0) erl_errno = EIO;
- return res;
-}
-
-/* FIXME documented to use struct in_addr as addr */
-
-int erl_xconnect(Erl_IpAddr addr, char *alivename)
-{
- return ei_xconnect(&erl_if_ec, addr, alivename);
-}
-
-
-/***************************************************************************
- *
- * API: erl_close_connection()
- *
- * Returns 0 on success and -1 on failure.
- *
- ***************************************************************************/
-
-int erl_close_connection(int fd)
-{
- return ei_close_connection(fd);
-}
-
-/*
- * Accept and initiate a connection from another
- * Erlang node. Return a file descriptor at success,
- * otherwise -1;
- */
-int erl_accept(int lfd, ErlConnect *conp)
-{
- return ei_accept(&erl_if_ec, lfd, conp);
-}
-
-
-/* Receives a message from an Erlang socket.
- * If the message was a TICK it is immediately
- * answered. Returns: ERL_ERROR, ERL_TICK or
- * the number of bytes read.
- */
-int erl_receive(int s, unsigned char *bufp, int bufsize)
-{
- return ei_receive(s, bufp, bufsize);
-}
-
-/*
- * Send an Erlang message to a registered process
- * at the Erlang node, connected with a socket.
- */
-int erl_reg_send(int fd, char *server_name, ETERM *msg)
-{
- ei_x_buff x;
- int r;
-
- if (ei_x_new_with_version(&x) < 0) {
- erl_errno = ENOMEM;
- return 0;
- }
- if (ei_x_encode_term(&x, msg) < 0) {
- erl_errno = EINVAL;
- r = 0;
- } else {
- r = ei_reg_send(&erl_if_ec, fd, server_name, x.buff, x.index);
- }
- ei_x_free(&x);
- return r == 0;
-}
-
-/*
- * Sends an Erlang message to a process at an Erlang node
- */
-int erl_send(int fd, ETERM *to ,ETERM *msg)
-{
- erlang_pid topid;
- ei_x_buff x;
- int r;
-
- ei_x_new_with_version(&x);
- ei_x_encode_term(&x, msg);
- /* make the to-pid */
- if (!ERL_IS_PID(to)) {
- ei_x_free(&x);
- erl_errno = EINVAL;
- return -1;
- }
-
- if (to->uval.pidval.node.latin1) {
- strcpy(topid.node, to->uval.pidval.node.latin1);
- }
- else {
- strcpy(topid.node, to->uval.pidval.node.utf8);
- }
- topid.num = ERL_PID_NUMBER(to);
- topid.serial = ERL_PID_SERIAL(to);
- topid.creation = ERL_PID_CREATION(to);
- r = ei_send(fd, &topid, x.buff, x.index);
- ei_x_free(&x);
- return r == 0;
-}
-
-static int erl_do_receive_msg(int fd, ei_x_buff* x, ErlMessage* emsg)
-{
- erlang_msg msg;
-
- int r;
- msg.from.node[0] = msg.to.node[0] = msg.toname[0] = '\0';
- r = ei_do_receive_msg(fd, 0, &msg, x, 0);
-
- if (r == ERL_MSG) {
- int index = 0;
- emsg->type = msg.msgtype;
-
- /*
- We can't call ei_decode_term for cases where there are no
- data following the type information. If there are other
- types added later where there are data this case has to be
- extended.
- */
-
- switch (msg.msgtype) {
- case ERL_SEND:
- case ERL_REG_SEND:
- case ERL_EXIT:
- case ERL_EXIT2:
- if (ei_decode_term(x->buff, &index, &emsg->msg) < 0)
- r = ERL_ERROR;
- break;
- default:
- emsg->msg = NULL; /* Not needed but may avoid problems for unsafe caller */
- break;
- }
- } else
- emsg->msg = NULL;
- if (msg.from.node[0] != '\0')
- emsg->from = erl_mk_pid(msg.from.node, msg.from.num, msg.from.serial, msg.from.creation);
- else
- emsg->from = NULL;
- if (msg.to.node[0] != '\0')
- emsg->to = erl_mk_pid(msg.to.node, msg.to.num, msg.to.serial, msg.to.creation);
- else
- emsg->to = NULL;
- strcpy(emsg->to_name, msg.toname);
- return r;
-}
-
-int erl_receive_msg(int fd, unsigned char *buf, int bufsize, ErlMessage *emsg)
-{
- ei_x_buff x;
- int r;
-
- ei_x_new(&x);
- r = erl_do_receive_msg(fd, &x, emsg);
- /* FIXME what is this about? */
- if (bufsize > x.index)
- bufsize = x.index;
- memcpy(buf, x.buff, bufsize);
- ei_x_free(&x);
- return r;
-}
-
-int erl_xreceive_msg(int fd, unsigned char **buf, int *bufsize,
- ErlMessage *emsg)
-{
- ei_x_buff x;
- int r;
-
- ei_x_new(&x);
- r = erl_do_receive_msg(fd, &x, emsg);
- if (*bufsize < x.index)
- *buf = erl_realloc(*buf, x.index);
- *bufsize = x.index;
- memcpy(*buf, x.buff, *bufsize);
- ei_x_free(&x);
- return r;
-}
-
-/*
- * The RPC consists of two parts, send and receive.
- * Here is the send part !
- * { PidFrom, { call, Mod, Fun, Args, user }}
- */
-/*
- * Now returns non-negative number for success, negative for failure.
- */
-int erl_rpc_to(int fd, char *mod, char *fun, ETERM *args)
-{
- int r;
- ei_x_buff x;
-
- ei_x_new(&x);
- ei_x_encode_term(&x, args);
- r = ei_rpc_to(&erl_if_ec, fd, mod, fun, x.buff, x.index);
- ei_x_free(&x);
- return r;
-} /* rpc_to */
-
- /*
- * And here is the rpc receiving part. A negative
- * timeout means 'infinity'. Returns either of: ERL_MSG,
- * ERL_TICK, ERL_ERROR or ERL_TIMEOUT.
-*/
-int erl_rpc_from(int fd, int timeout, ErlMessage *emsg)
-{
- fd_set readmask;
- struct timeval tv;
- struct timeval *t = NULL;
- unsigned char rbuf[MAX_RECEIVE_BUF];
-
- if (timeout >= 0) {
- tv.tv_sec = timeout / 1000;
- tv.tv_usec = (timeout % 1000) * 1000;
- t = &tv;
- }
-
- FD_ZERO(&readmask);
- FD_SET(fd,&readmask);
-
- switch (select(fd+1, &readmask, NULL, NULL, t)) {
- case -1:
- erl_errno = EIO;
- return ERL_ERROR;
- case 0:
- erl_errno = ETIMEDOUT;
- return ERL_TIMEOUT;
- default:
- if (FD_ISSET(fd, &readmask))
- return erl_receive_msg(fd, rbuf, MAX_RECEIVE_BUF, emsg);
- else {
- erl_errno = EIO;
- return ERL_ERROR;
- }
- }
-} /* rpc_from */
-
-/*
- * A true RPC. It return a NULL pointer
- * in case of failure, otherwise a valid
- * (ETERM *) pointer containing the reply
- */
-ETERM *erl_rpc(int fd, char *mod, char *fun, ETERM *args)
-{
- int i;
- ETERM *ep;
- ErlMessage emsg;
-
- if (erl_rpc_to(fd, mod, fun, args) < 0) {
- return NULL; }
- while ((i=erl_rpc_from(fd, ERL_NO_TIMEOUT, &emsg)) == ERL_TICK);
-
- if (i == ERL_ERROR) return NULL;
-
- ep = erl_element(2,emsg.msg); /* {RPC_Tag, RPC_Reply} */
- erl_free_term(emsg.msg);
- erl_free_term(emsg.to);
- return ep;
-} /* rpc */
-
-
-/*
- ** Handshake
- */
-
-int erl_publish(int port)
-{
- return ei_publish(&erl_if_ec, port);
-}
-
-int erl_unpublish(const char *alive)
-{
- return ei_unpublish_tmo(alive,0);
-}
-
-erlang_pid *erl_self(void)
-{
- return ei_self(&erl_if_ec);
-}
-
-const char *erl_thisnodename(void)
-{
- return ei_thisnodename(&erl_if_ec);
-}
-
-const char *erl_thishostname(void)
-{
- return ei_thishostname(&erl_if_ec);
-}
-
-const char *erl_thisalivename(void)
-{
- return ei_thisalivename(&erl_if_ec);
-}
-
-const char *erl_thiscookie(void)
-{
- return ei_thiscookie(&erl_if_ec);
-}
-
-short erl_thiscreation(void)
-{
- return ei_thiscreation(&erl_if_ec);
-}
diff --git a/lib/erl_interface/src/legacy/erl_error.c b/lib/erl_interface/src/legacy/erl_error.c
deleted file mode 100644
index a3bbfbc58f..0000000000
--- a/lib/erl_interface/src/legacy/erl_error.c
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-/*
- * Function: Some nice error routines taken from:
- * "Advanced Programming in the UNIX Environment",
- * by W.Richard Stevens
- *
- * void erl_err_sys(const char *fmt, ... ) fatal, sys-error
- * void erl_err_ret(const char *fmt, ... ) non-fatal, sys-error
- * void erl_err_quit(const char *fmt, ...) fatal, non-sys-error
- * void erl_err_msg(const char *fmt, ... ) non-fatal, non-sys-error
- */
-
-#include <stdio.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <string.h>
-
-#ifdef VRTX /* What's VRIX? [sverkerw] */
-#define __READY_EXTENSIONS__
-#endif
-#include <errno.h>
-
-#if defined(VXWORKS)
-#include <taskLib.h>
-#include <taskVarLib.h>
-#endif
-
-#include "eidef.h"
-#include "erl_interface.h"
-#include "erl_error.h"
-
-/* Forward */
-static void err_doit(int, const char*, va_list);
-/* __attribute__ ((format (printf, 2, 0)))*/
-
-/*
- * Some thoughts on flushing stdout/stderr:
- *
- * The defaults are reasonable (linebuffered stdout, unbuffered
- * stderr). If they are in effect (the user neither knows nor cares),
- * there's no need to flush.
- *
- * If the user changes these defaults (and knows what he's doing, so
- * he knows and cares) we shouldn't surprise him by
- * second-guessing. So there's a need to not flush.
- *
- * If the user doesn't know what he's doing, he's hosed anyway.
- */
-
-/* Fatal error related to a system call.
- * Print a message and terminate.
- */
-void erl_err_sys(const char *fmt, ... )
-{
- va_list ap;
-
- va_start(ap, fmt);
- err_doit(1, fmt, ap);
- va_end(ap);
- exit(1);
-} /* erl_err_sys */
-
-/* Nonfatal error related to a system call.
- * Print a message and return
- */
-void erl_err_ret(const char *fmt, ... )
-{
- va_list ap;
-
- va_start(ap, fmt);
- err_doit(1, fmt, ap);
- va_end(ap);
- return;
-} /* erl_err_ret */
-
-/* Nonfatal error unrelated to a system call.
- * Print a message and return
- */
-void erl_err_msg(const char *fmt, ... )
-{
- va_list ap;
-
- va_start(ap, fmt);
- err_doit(0, fmt, ap);
- va_end(ap);
- return;
-} /* erl_err_msg */
-
-/* Fatal error unrelated to a system call.
- * Print a message and terminate
- */
-void erl_err_quit(const char *fmt, ... )
-{
- va_list ap;
-
- va_start(ap, fmt);
- err_doit(0, fmt, ap);
- va_end(ap);
- exit(1);
-} /* erl_err_quit */
-
-
-
-/*
- * For example on SunOS we don't have the ANSI C strerror.
- *
- * maybe move to a convenince lib [sverkerw]
- */
-#ifndef HAVE_STRERROR
-
-/* FIXME: move to configure */
-/* CONFIG: probe for sys_nerr/_sys_nerr */
-extern int sys_nerr;
-
-/* CONFIG: probe for sys_errlist/_sys_errlist and maybe for const-ness */
-#ifdef FREEBSD
-extern const char * const sys_errlist[];
-#else
-extern char * sys_errlist[];
-#endif
-
-/* Should be in string.h */
-/* Is supposed to return 'char *' (no const-ness in ANSI's prototype),
- but if you rewrite the returned string in place you deserve to
- lose. */
-static const char *strerror(int errnum)
-{
- if (errnum >= 0 && errnum < sys_nerr) {
- return sys_errlist[errnum];
- } else {
- /* Enough buffer for 64 bits of error. It should last a while. */
- /* FIXME problem for threaded ? */
- static char b[] = "(error -9223372036854775808)";
- sprintf(b, "(error %d)", errnum);
- buf[sizeof(b)-1] = '\0';
- return b;
- }
-}
-#endif /* !HAVE_STRERROR */
-
-
-/* Print a message and return to caller.
- * Caller specifies "errnoflag".
- */
-static void err_doit(int errnoflag, const char *fmt, va_list ap)
-{
-#ifndef NO_ERR_MSG
- int errno_save;
-
- errno_save = errno;
-
- vfprintf(stderr, fmt, ap);
- if (errnoflag)
- {
- fputs(": ", stderr);
- fputs(strerror(errno_save), stderr);
- }
- fputs("\n", stderr);
-#endif
-
- return;
-} /* err_doit */
-
diff --git a/lib/erl_interface/src/legacy/erl_eterm.c b/lib/erl_interface/src/legacy/erl_eterm.c
deleted file mode 100644
index ff8c65209e..0000000000
--- a/lib/erl_interface/src/legacy/erl_eterm.c
+++ /dev/null
@@ -1,1413 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2020. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-/*
- * Purpose: Representation of Erlang terms.
- */
-
-#include "eidef.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#if defined(HAVE_ISFINITE)
-#include <math.h>
-#endif
-
-#include "ei_locking.h"
-#include "ei_resolve.h"
-#include "erl_interface.h"
-#include "erl_eterm.h"
-#include "erl_malloc.h"
-#include "erl_marshal.h"
-#include "erl_error.h"
-#include "erl_internal.h"
-#include "ei_internal.h"
-#include "putget.h"
-
-#define ERL_IS_BYTE(x) (ERL_IS_INTEGER(x) && (ERL_INT_VALUE(x) & ~0xFF) == 0)
-
-/* FIXME use unsigned char, or uint8 for buffers, cast (int) really needed? */
-
-static void iolist_to_buf(const ETERM* term, char** bufp);
-static char* strsave(const char *src);
-
-/***************************************************************************
- *
- * API: erl_init()
- *
- * Not documented to set erl_errno.
- *
- ***************************************************************************/
-
-/* all initialisation of erl_interface modules should be called from here */
-/* order is important: erl_malloc and erl_resolve depend on ei_locking */
-/* NOTE: don't call this directly - please use erl_init() macro defined
- in ei_locking.h! */
-void erl_init(void *hp,long heap_size)
-{
- erl_init_malloc(hp, heap_size);
- erl_init_marshal();
- (void) ei_init();
-}
-
-void erl_set_compat_rel(unsigned rel)
-{
- ei_set_compat_rel(rel);
-}
-
-/*
- * Create an INTEGER. Depending on its value it
- * may end up as a BigNum.
- */
-ETERM *erl_mk_int (int i)
-{
- ETERM *ep;
-
- ep = erl_alloc_eterm(ERL_INTEGER);
- ERL_COUNT(ep) = 1;
- ERL_INT_VALUE(ep) = i;
- return ep;
-}
-
-ETERM *erl_mk_longlong (long long i)
-{
- ETERM *ep;
-
- ep = erl_alloc_eterm(ERL_LONGLONG);
- ERL_COUNT(ep) = 1;
- ERL_LL_VALUE(ep) = i;
- return ep;
-}
-
-/*
- * Create an UNSIGNED INTEGER. Depending on its
- * value it may end up as a BigNum.
- */
-
-ETERM *erl_mk_uint (unsigned int u)
-{
- ETERM *ep;
-
- ep = erl_alloc_eterm(ERL_U_INTEGER);
- ERL_COUNT(ep) = 1;
- ERL_INT_UVALUE(ep) = u;
- return ep;
-}
-
-ETERM *erl_mk_ulonglong (unsigned long long i)
-{
- ETERM *ep;
-
- ep = erl_alloc_eterm(ERL_U_LONGLONG);
- ERL_COUNT(ep) = 1;
- ERL_LL_UVALUE(ep) = i;
- return ep;
-}
-
-/*
- * Create a FLOAT.
- */
-ETERM *erl_mk_float (double d)
-{
- ETERM *ep;
-
-#if defined(HAVE_ISFINITE)
- /* Erlang does not handle Inf and NaN, so we return an error
- * rather than letting the Erlang VM complain about a bad external
- * term. */
- if(!isfinite(d)) {
- return NULL;
- }
-#endif
-
- ep = erl_alloc_eterm(ERL_FLOAT);
- ERL_COUNT(ep) = 1;
- ERL_FLOAT_VALUE(ep) = d;
- return ep;
-}
-
-/*
- * Create an ATOM
- */
-ETERM *erl_mk_atom (const char *s)
-{
- ETERM *ep;
-
- /* ASSERT(s != NULL); */
- if (!s) return NULL;
-
- ep = erl_alloc_eterm(ERL_ATOM);
- ERL_COUNT(ep) = 1;
- if (erl_atom_init_latin1(&ep->uval.aval.d, s) == NULL) {
- erl_free_term(ep);
- erl_errno = ENOMEM;
- return NULL;
- }
- return ep;
-}
-
-char* erl_atom_ptr_latin1(Erl_Atom_data* a)
-{
- if (a->latin1 == NULL) {
- erlang_char_encoding enc;
- a->lenL = utf8_to_latin1(NULL, a->utf8, a->lenU, a->lenU, &enc);
- if (a->lenL < 0) {
- a->lenL = 0;
- return NULL;
- }
- if (enc == ERLANG_ASCII) {
- a->latin1 = a->utf8;
- }
- else {
- a->latin1 = malloc(a->lenL+1);
- utf8_to_latin1(a->latin1, a->utf8, a->lenU, a->lenL, NULL);
- a->latin1[a->lenL] = '\0';
- }
- }
- return a->latin1;
-}
-
-char* erl_atom_ptr_utf8(Erl_Atom_data* a)
-{
- if (a->utf8 == NULL) {
- erlang_char_encoding enc;
- a->lenU = latin1_to_utf8(NULL, a->latin1, a->lenL, a->lenL*2, &enc);
- if (enc == ERLANG_ASCII) {
- a->utf8 = a->latin1;
- }
- else {
- a->utf8 = malloc(a->lenU + 1);
- latin1_to_utf8(a->utf8, a->latin1, a->lenL, a->lenU, NULL);
- a->utf8[a->lenU] = '\0';
- }
- }
- return a->utf8;
-}
-
-int erl_atom_size_latin1(Erl_Atom_data* a)
-{
- if (a->latin1 == NULL) {
- erl_atom_ptr_latin1(a);
- }
- return a->lenL;
-}
-int erl_atom_size_utf8(Erl_Atom_data* a)
-{
- if (a->utf8 == NULL) {
- erl_atom_ptr_utf8(a);
- }
- return a->lenU;
-}
-char* erl_atom_init_latin1(Erl_Atom_data* a, const char* s)
-{
- a->lenL = strlen(s);
- if ((a->latin1 = strsave(s)) == NULL)
- {
- return NULL;
- }
- a->utf8 = NULL;
- a->lenU = 0;
- return a->latin1;
-}
-
-
-/*
- * Given a string as input, creates a list.
- */
-ETERM *erl_mk_string(const char *s)
-{
- /* ASSERT(s != NULL); */
- if (!s) return NULL;
-
- return erl_mk_estring(s, strlen(s));
-}
-
-ETERM *erl_mk_estring(const char *s, int len)
-{
- ETERM *ep;
- int i;
-
- if ((!s) || (len < 0)) return NULL;
-
- /*
- * ASSERT(s != NULL);
- * ASSERT(len >= 0);
- */
-
- ep = erl_mk_empty_list();
- for (i = len-1; i >= 0; i--) {
- ETERM* integer;
- ETERM* cons;
-
- integer = erl_alloc_eterm(ERL_INTEGER);
- ERL_COUNT(integer) = 1;
- ERL_INT_VALUE(integer) = (unsigned char)s[i];
-
- cons = erl_alloc_eterm(ERL_LIST);
- ERL_COUNT(cons) = 1;
- HEAD(cons) = integer;
- TAIL(cons) = ep;
- ep = cons;
- }
- return ep;
-}
-
-/*
- * Create a PID.
- */
-ETERM *erl_mk_pid(const char *node,
- unsigned int number,
- unsigned int serial,
- unsigned char creation)
-{
- ETERM *ep;
-
- if (!node) return NULL;
- /* ASSERT(node != NULL); */
-
- ep = erl_alloc_eterm(ERL_PID);
- ERL_COUNT(ep) = 1;
- if (erl_atom_init_latin1(&ep->uval.pidval.node, node) == NULL)
- {
- erl_free_term(ep);
- erl_errno = ENOMEM;
- return NULL;
- }
- erl_mk_pid_helper(ep, number, serial, creation & 0x03);
- return ep;
-}
-
-void erl_mk_pid_helper(ETERM *ep, unsigned int number,
- unsigned int serial, unsigned int creation)
-{
- ERL_PID_NUMBER(ep) = number & 0x7fff; /* 15 bits */
- ERL_PID_SERIAL(ep) = serial & 0x1fff; /* 13 bits */
- ERL_PID_CREATION(ep) = creation; /* 32 bits */
-}
-
-/*
- * Create a PORT.
- */
-ETERM *erl_mk_port(const char *node,
- unsigned int number,
- unsigned char creation)
-{
- ETERM *ep;
-
- if (!node) return NULL;
- /* ASSERT(node != NULL); */
-
- ep = erl_alloc_eterm(ERL_PORT);
- ERL_COUNT(ep) = 1;
- if (erl_atom_init_latin1(&ep->uval.portval.node, node) == NULL)
- {
- erl_free_term(ep);
- erl_errno = ENOMEM;
- return NULL;
- }
- erl_mk_port_helper(ep, number, creation);
- return ep;
-}
-
-void erl_mk_port_helper(ETERM* ep, unsigned number, unsigned int creation)
-{
- ERL_PORT_NUMBER(ep) = number & 0x0fffffff; /* 18 bits */
- ERL_PORT_CREATION(ep) = creation; /* 32 bits */
-}
-
-/*
- * Create any kind of reference.
- */
-ETERM *__erl_mk_reference (ETERM* t,
- const char *node,
- size_t len,
- unsigned int n[],
- unsigned int creation)
-{
- if (t == NULL) {
- if (node == NULL) return NULL;
-
- t = erl_alloc_eterm(ERL_REF);
- ERL_COUNT(t) = 1;
-
- if (erl_atom_init_latin1(&t->uval.refval.node, node) == NULL)
- {
- erl_free_term(t);
- erl_errno = ENOMEM;
- return NULL;
- }
- }
- ERL_REF_LEN(t) = len;
- ERL_REF_NUMBERS(t)[0] = n[0] & 0x3ffff; /* 18 bits */
- ERL_REF_NUMBERS(t)[1] = n[1];
- ERL_REF_NUMBERS(t)[2] = n[2];
- ERL_REF_CREATION(t) = creation; /* 32 bits */
-
- return t;
-}
-
-/*
- * Create a REFERENCE.
- */
-ETERM *erl_mk_ref (const char *node,
- unsigned int number,
- unsigned char creation)
-{
- unsigned int n[3] = {0, 0, 0};
- n[0] = number;
- return __erl_mk_reference(NULL, node, 1, n, creation);
-}
-
-/*
- * Create a long REFERENCE.
- */
-ETERM *
-erl_mk_long_ref (const char *node,
- unsigned int n1, unsigned int n2, unsigned int n3,
- unsigned char creation)
-{
- unsigned int n[3] = {0, 0, 0};
- n[0] = n3; n[1] = n2; n[2] = n1;
- return __erl_mk_reference(NULL, node, 3, n, creation);
-}
-
-/*
- * Create a BINARY.
- */
-ETERM *erl_mk_binary (const char *b, int size)
-{
- ETERM *ep;
-
- if ((!b) || (size < 0)) return NULL;
- /* ASSERT(b != NULL); */
-
- ep = erl_alloc_eterm(ERL_BINARY);
- ERL_COUNT(ep) = 1;
- ERL_BIN_SIZE(ep) = size;
- ERL_BIN_PTR(ep) = (unsigned char *) erl_malloc(size);
- memcpy(ERL_BIN_PTR(ep), b, size);
- return ep;
-}
-
-/*
- * Create a TUPLE. For each element in the tuple
- * bump its reference counter.
- */
-ETERM *erl_mk_tuple (ETERM **arr,int size)
-{
- ETERM *ep;
- int i;
-
- if ((!arr) || (size < 0)) return NULL;
- for (i=0; i<size; i++) if (!arr[i]) return NULL;
- /* ASSERT(arr != NULL); */
-
- ep = erl_alloc_eterm(ERL_TUPLE);
- ERL_COUNT(ep) = 1;
- ERL_TUPLE_SIZE(ep) = size;
- ERL_TUPLE_ELEMS(ep) = (ETERM**) erl_malloc((size) * (sizeof(ETERM*)));
- for (i = 0; i < size; i++) {
- /* ASSERT(arr[i] != NULL); */
- ERL_COUNT(arr[i])++;
- ERL_TUPLE_ELEMENT(ep, i) = arr[i];
- }
- return ep;
-}
-
-/*
- * SET an ELEMENT in a TUPLE. Free the old element
- * and bump the reference counter of the new one.
- * Return 1 on success, otherwise 0.
- */
-#if 0
-int erl_setelement (int ix, ETERM *ep, ETERM *vp)
-{
- if ((!ep) || (!vp)) return 0;
- /* ASSERT(ep != NULL);
- * ASSERT(vp != NULL);
- */
-
- if ((ERL_TYPE(ep) == ERL_TUPLE) && (ix <= ERL_TUPLE_SIZE(ep))) {
- erl_free_term(ERL_TUPLE_ELEMENT(ep, ix-1));
- ERL_TUPLE_ELEMENT(ep, ix-1) = vp;
- ERL_COUNT(vp)++;
- return 1;
- }
- erl_err_msg("<ERROR> erl_setelement: Bad type to setelement or out of range \n");
- return 0;
-}
-#endif
-
-/*
- * Extract an ELEMENT from a TUPLE. Bump the
- * reference counter on the extracted object.
- */
-ETERM *erl_element (int ix, const ETERM *ep)
-{
- if ((!ep) || (ix < 0)) return NULL;
- /*
- * ASSERT(ep != NULL);
- * ASSERT(ix >= 0);
- */
-
- if ((ERL_TYPE(ep) == ERL_TUPLE) && (ix <= ERL_TUPLE_SIZE(ep))) {
- ERL_COUNT(ERL_TUPLE_ELEMENT(ep, ix-1))++;
- return ERL_TUPLE_ELEMENT(ep, ix-1);
- }
- else
- return NULL;
-} /* erl_element */
-
-ETERM *erl_mk_empty_list(void)
-{
- ETERM *ep;
-
- ep = erl_alloc_eterm(ERL_EMPTY_LIST);
- ERL_COUNT(ep) = 1;
- return ep;
-}
-
-/*
- * Construct a new list by CONS'ing a HEAD on
- * to the TAIL. Bump the reference counter on
- * the head and tail object. Note that we allow
- * non-well formed lists to be created.
- */
-ETERM *erl_cons(ETERM *hd, ETERM *tl)
-{
- ETERM *ep;
-
- if ((!hd) || (!tl)) return NULL;
-
- /*
- * ASSERT(hd != NULL);
- * ASSERT(tl != NULL);
- */
-
- ep = erl_alloc_eterm(ERL_LIST);
- ERL_COUNT(ep) = 1;
- HEAD(ep) = hd;
- TAIL(ep) = tl;
- ERL_COUNT(hd)++;
- ERL_COUNT(tl)++;
- return ep;
-}
-
-/*
- * Extract the HEAD of a LIST. Bump the reference
- * counter on the head object.
- */
-ETERM *erl_hd (const ETERM *ep)
-{
- if (!ep) return NULL;
- /* ASSERT(ep != NULL); */
-
- if (ERL_TYPE(ep) != ERL_LIST) {
- return (ETERM *) NULL;
- }
- ERL_COUNT(ERL_CONS_HEAD(ep))++;
- return ERL_CONS_HEAD(ep);
-}
-
-/*
- * Extract the TAIL of a LIST. Bump the reference
- * counter on the tail object.
- */
-ETERM *erl_tl (const ETERM *ep)
-{
- ETERM *tl;
-
- if (!ep) return NULL;
- /* ASSERT(ep != NULL); */
-
- if (ERL_TYPE(ep) != ERL_LIST) {
- return (ETERM *) NULL;
- }
-
- tl = TAIL(ep);
- ERL_COUNT(tl)++;
- return tl;
-}
-
-/*
- * Create a LIST from an array of elements. Note that
- * we create it from the last element in the array to
- * the first. Also, note that we decrement the reference
- * counter for each member in the list but the first one.
- * This is done because of the use of erl_cons.
- */
-
-ETERM *erl_mk_list (ETERM **arr, int size)
-{
- ETERM *ep;
- int i;
-
- if ((!arr) || (size < 0)) return NULL;
- for (i=0; i<size; i++) if (!arr[i]) return NULL;
-
- /* ASSERT(arr != NULL); */
- ep = erl_mk_empty_list();
- if (size > 0) {
- ERL_COUNT(ep)--;
- }
-
- for (i = size-1; i >= 0; i--) {
- /* ASSERT(arr[i] != NULL); */
- ep = erl_cons(arr[i], ep);
- if (i > 0)
- ERL_COUNT(ep)--; /* Internal reference */
- }
- return ep;
-}
-
-/*
- * Create an empty VARIABLE.
- */
-ETERM *erl_mk_var(const char *s)
-{
- ETERM *ep;
-
- if (!s) return NULL;
-
- /* ASSERT(s != NULL); */
-
- ep = erl_alloc_eterm(ERL_VARIABLE);
- ERL_COUNT(ep) = 1;
- ERL_VAR_LEN(ep) = strlen(s);
- if ((ERL_VAR_NAME(ep) = strsave(s)) == NULL)
- {
- erl_free_term(ep);
- erl_errno = ENOMEM;
- return NULL;
- }
- ERL_VAR_VALUE(ep) = (ETERM *) NULL;
- return ep;
-}
-
-/*
- * Return the CONTENT of a VARIABLE with NAME.
- * If the content is non-nil then bump its
- * reference counter.
- */
-ETERM *erl_var_content (const ETERM *ep, const char *name)
-{
- int i;
- ETERM *vp;
-
- if ((!ep) || (!name)) return NULL;
-
- /* ASSERT(ep != NULL); */
-
- switch(ERL_TYPE(ep))
- {
- case ERL_VARIABLE:
- if (strcmp(ERL_VAR_NAME(ep), name) == 0) {
- if ((vp = ERL_VAR_VALUE(ep)) != NULL) {
- ERL_COUNT(vp)++;
- return vp;
- }
- }
- break;
-
- case ERL_LIST:
- while (ep && (ERL_TYPE(ep) != ERL_EMPTY_LIST)) {
- if ((vp = erl_var_content(HEAD(ep), name))) return vp;
- ep = TAIL(ep);
- }
- break;
-
- case ERL_TUPLE:
- for (i=0; i < ERL_TUPLE_SIZE(ep); i++)
- if ((vp = erl_var_content(ERL_TUPLE_ELEMENT(ep, i), name)))
- {
- return vp;
- }
- break;
-
- default:
- /* variables can't occur in other types */
- break;
- }
-
- /* nothing found ! */
- return NULL;
-}
-
-/*
- * Return the SIZE of a TUPLE or a BINARY.
- * At failure -1 is returned.
- */
-int erl_size (const ETERM *ep)
-{
- if (!ep) return -1;
-
- /* ASSERT(ep != NULL); */
-
- switch (ERL_TYPE(ep)) {
- case ERL_TUPLE:
- return ERL_TUPLE_SIZE(ep);
-
- case ERL_BINARY:
- return ERL_BIN_SIZE(ep);
-
- default:
- return -1;
-
- }
-}
-
-/*
- * Return the LENGTH of a LIST.
- * At failure -1 is returned (this include non-proper lists like [a|b]).
- */
-int erl_length(const ETERM *ep)
-{
- int n = 0;
-
- if (!ep) return -1;
- /* ASSERT(ep != NULL); */
-
- while (ERL_TYPE(ep) == ERL_LIST) {
- n++;
- ep = TAIL(ep);
- }
-
- if (!ERL_IS_EMPTY_LIST(ep)) return -1;
-
- return n;
-}
-
-
-/***********************************************************************
- * I o l i s t f u n c t i o n s
- *
- * The following functions handles I/O lists.
- *
- * Informally, an I/O list is a deep list of characters and binaries,
- * which can be sent to an Erlang port.
- *
- * Formally, in BNF, an I/O list is defined as:
- *
- * iolist ::= []
- * | Binary
- * | [iohead | iolist]
- * ;
- *
- * iohead ::= Binary
- * | Byte (integer in the range [0..255])
- * | iolist
- * ;
- *
- * Note that versions of Erlang/OTP prior to R2 had a slightly more
- * restricted definition of I/O lists, in that the tail of a an I/O list
- * was not allowed to be a binary. The erl_interface functions
- * for I/O lists follows the more liberal rules described by the BNF
- * description above.
- ***********************************************************************/
-
-/*
- * This function converts an I/O list to a '\0' terminated C string.
- * The I/O list must not contain any occurrences of the integer 0.
- *
- * The string will be in memory allocated by erl_malloc(). It is the
- * responsibility of the caller to eventually call erl_free() to free
- * the memory.
- *
- * Returns: NULL if the list was not an I/O list or contained
- * the integer 0, otherwise a pointer to '\0' terminated string.
- */
-
-char* erl_iolist_to_string(const ETERM* term)
-{
- ETERM* bin;
-
- if ((bin = erl_iolist_to_binary(term)) == NULL) {
- return NULL;
- } else {
- char* result = NULL;
-
- if (memchr(ERL_BIN_PTR(bin), '\0', ERL_BIN_SIZE(bin)) == NULL) {
- result = (char *) erl_malloc(ERL_BIN_SIZE(bin)+1);
- memcpy(result, ERL_BIN_PTR(bin), ERL_BIN_SIZE(bin));
- result[ERL_BIN_SIZE(bin)] = '\0';
- }
- erl_free_term(bin);
- return result;
- }
-}
-
-/*
- * This function converts an I/O list to a binary term.
- *
- * Returns: NULL if the list was not an I/O list, otherwise
- * an ETERM pointer pointing to a binary term.
- */
-
-ETERM *erl_iolist_to_binary (const ETERM* term)
-{
- ETERM *dest;
- int size;
- char* ptr;
-
- if (!term) return NULL;
- /* ASSERT(term != NULL); */
-
- /*
- * Verify that the term is an I/O list and get its length.
- */
-
- size = erl_iolist_length(term);
- if (size == -1) {
- return NULL;
- }
-
- /*
- * Allocate the binary and copy the contents of the I/O list into it.
- */
-
- dest = erl_alloc_eterm(ERL_BINARY);
- ERL_COUNT(dest) = 1;
- ERL_BIN_SIZE(dest) = size;
- ptr = (char *)erl_malloc(size);
- ERL_BIN_PTR(dest) = (unsigned char *)ptr;
- iolist_to_buf(term, &ptr);
-
- /*
- * If ptr doesn't point exactly one byte beyond the end of the
- * binary, something must be seriously wrong.
- */
-
- if (ERL_BIN_PTR(dest) + size != (unsigned char *) ptr) return NULL;
- /* ASSERT(ERL_BIN_PTR(dest) + size == (unsigned char *) ptr); */
-
- return dest;
-}
-
-/*
- * Returns the length of an I/O list.
- *
- * Returns: -1 if the term if the given term is not a I/O list,
- * or the length otherwise.
- */
-
-int erl_iolist_length (const ETERM* term)
-{
- int len = 0;
-
- while (ERL_IS_CONS(term)) {
- ETERM* obj = HEAD(term);
-
- if (ERL_IS_BYTE(obj)) {
- len++;
- } else if (ERL_IS_CONS(obj)) {
- int i;
- if ((i = erl_iolist_length(obj)) < 0)
- return i;
- len += i;
- } else if (ERL_IS_BINARY(obj)) {
- len += ERL_BIN_SIZE(obj);
- } else if (!ERL_IS_EMPTY_LIST(obj)) {
- return(-1);
- }
- term = TAIL(term);
- }
- if (ERL_IS_EMPTY_LIST(term))
- return len;
- else if (ERL_IS_BINARY(term))
- return len + ERL_BIN_SIZE(term);
- else
- return -1;
-}
-
-static int erl_atom_copy(Erl_Atom_data* dst, const Erl_Atom_data* src)
-{
- if (src->latin1 == src->utf8) {
- dst->latin1 = dst->utf8 = strsave(src->latin1);
- dst->lenL = dst->lenU = strlen(src->latin1);
- }
- else if (src->latin1) {
- dst->latin1 = strsave(src->latin1);
- dst->lenL = strlen(src->latin1);
- dst->utf8 = NULL;
- dst->lenU = 0;
- }
- else {
- dst->utf8 = strsave(src->utf8);
- dst->lenU = strlen(src->utf8);
- dst->latin1 = NULL;
- dst->lenL = 0;
- }
- return (dst->latin1 != NULL || dst->utf8 == NULL);
-}
-
-
-/*
- * Return a brand NEW COPY of an ETERM.
- */
-/*
- * FIXME: Deep (the whole tree) or shallow (just the top term) copy?
- * The documentation never says, but the code as written below will
- * make a deep copy. This should be documented.
- */
-ETERM *erl_copy_term(const ETERM *ep)
-{
- int i;
- ETERM *cp;
-
- if (!ep) return NULL;
- /* ASSERT(ep != NULL); */
-
- cp = erl_alloc_eterm(ERL_TYPE(ep));
- ERL_COUNT(cp) = 1;
-
- switch(ERL_TYPE(cp)) {
- case ERL_INTEGER:
- case ERL_SMALL_BIG:
- ERL_INT_VALUE(cp) = ERL_INT_VALUE(ep);
- break;
- case ERL_U_INTEGER:
- case ERL_U_SMALL_BIG:
- ERL_INT_UVALUE(cp) = ERL_INT_UVALUE(ep);
- break;
- case ERL_LONGLONG:
- ERL_LL_VALUE(cp) = ERL_LL_VALUE(ep);
- break;
- case ERL_U_LONGLONG:
- ERL_LL_UVALUE(cp) = ERL_LL_UVALUE(ep);
- break;
- case ERL_FLOAT:
- ERL_FLOAT_VALUE(cp) = ERL_FLOAT_VALUE(ep);
- break;
- case ERL_ATOM:
- if (!erl_atom_copy(&cp->uval.aval.d, &ep->uval.aval.d))
- {
- erl_free_term(cp);
- erl_errno = ENOMEM;
- return NULL;
- }
- break;
- case ERL_PID:
- /* FIXME: First copy the bit pattern, then duplicate the node
- name and plug in. Somewhat ugly (also done with port and
- ref below). */
- memcpy(&cp->uval.pidval, &ep->uval.pidval, sizeof(Erl_Pid));
- erl_atom_copy(&cp->uval.pidval.node, &ep->uval.pidval.node);
- ERL_COUNT(cp) = 1;
- break;
- case ERL_PORT:
- memcpy(&cp->uval.portval, &ep->uval.portval, sizeof(Erl_Port));
- erl_atom_copy(&cp->uval.portval.node, &ep->uval.portval.node);
- ERL_COUNT(cp) = 1;
- break;
- case ERL_REF:
- memcpy(&cp->uval.refval, &ep->uval.refval, sizeof(Erl_Ref));
- erl_atom_copy(&cp->uval.refval.node, &ep->uval.refval.node);
- ERL_COUNT(cp) = 1;
- break;
- case ERL_LIST:
- HEAD(cp) = erl_copy_term(HEAD(ep));
- TAIL(cp) = erl_copy_term(TAIL(ep));
- break;
- case ERL_EMPTY_LIST:
- break;
- case ERL_TUPLE:
- i = ERL_TUPLE_SIZE(cp) = ERL_TUPLE_SIZE(ep);
- ERL_TUPLE_ELEMS(cp) = (ETERM**) erl_malloc(i * sizeof(ETERM*));
- for(i=0; i < ERL_TUPLE_SIZE(ep); i++)
- ERL_TUPLE_ELEMENT(cp,i) = erl_copy_term(ERL_TUPLE_ELEMENT(ep, i));
- break;
- case ERL_BINARY:
- ERL_BIN_SIZE(cp) = ERL_BIN_SIZE(ep);
- ERL_BIN_PTR(cp) = (unsigned char *) erl_malloc(ERL_BIN_SIZE(ep));
- memcpy(ERL_BIN_PTR(cp), ERL_BIN_PTR(ep), ERL_BIN_SIZE(ep));
- break;
- case ERL_FUNCTION:
- i = ERL_CLOSURE_SIZE(cp) = ERL_CLOSURE_SIZE(ep);
- ERL_FUN_ARITY(cp) = ERL_FUN_ARITY(ep);
- ERL_FUN_NEW_INDEX(cp) = ERL_FUN_NEW_INDEX(ep);
- ERL_FUN_INDEX(cp) = erl_copy_term(ERL_FUN_INDEX(ep));
- ERL_FUN_UNIQ(cp) = erl_copy_term(ERL_FUN_UNIQ(ep));
- ERL_FUN_CREATOR(cp) = erl_copy_term(ERL_FUN_CREATOR(ep));
- ERL_FUN_MODULE(cp) = erl_copy_term(ERL_FUN_MODULE(ep));
- memcpy(ERL_FUN_MD5(cp), ERL_FUN_MD5(ep), sizeof(ERL_FUN_MD5(ep)));
- ERL_CLOSURE(cp) = (ETERM**) erl_malloc(i * sizeof(ETERM*));
- for(i=0; i < ERL_CLOSURE_SIZE(ep); i++)
- ERL_CLOSURE_ELEMENT(cp,i) =
- erl_copy_term(ERL_CLOSURE_ELEMENT(ep, i));
- break;
- default:
- erl_err_msg("<ERROR> erl_copy_term: wrong type encountered !");
- erl_free_term(cp);
- return (ETERM *) NULL;
- }
-
- return cp;
-}
-
-#ifndef SILENT
-
-static int print_string(FILE* fp, const ETERM* ep);
-static int is_printable_list(const ETERM* term);
-
-/*
- * PRINT out an ETERM.
- */
-
-int erl_print_term(FILE *fp, const ETERM *ep)
-{
- int j,i,doquote;
- int ch_written = 0; /* counter of written chars */
-
- if ((!fp) || (!ep)) return 0;
- /* ASSERT(ep != NULL); */
-
- j = i = doquote = 0;
- switch(ERL_TYPE(ep))
- {
- case ERL_ATOM: {
- char* adata = ERL_ATOM_PTR(ep);
- /* FIXME: what if some weird locale is in use? */
- if (!islower(adata[0]))
- doquote = 1;
-
- for (i = 0; !doquote && i < ERL_ATOM_SIZE(ep); i++)
- {
- doquote = !(isalnum(adata[i]) || (adata[i] == '_'));
- }
-
- if (doquote) {
- putc('\'', fp);
- ch_written++;
- }
- fputs(adata, fp);
- ch_written += ERL_ATOM_SIZE(ep);
- if (doquote) {
- putc('\'', fp);
- ch_written++;
- }
- break;
- }
- case ERL_VARIABLE:
- if (!isupper((int)ERL_VAR_NAME(ep)[0])) {
- doquote = 1;
- putc('\'', fp);
- ch_written++;
- }
-
- fputs(ERL_VAR_NAME(ep), fp);
- ch_written += ERL_VAR_LEN(ep);
-
- if (doquote) {
- putc('\'', fp);
- ch_written++;
- }
- break;
-
- case ERL_PID:
- ch_written += fprintf(fp, "<%s.%d.%d>",
- ERL_PID_NODE(ep),
- ERL_PID_NUMBER(ep), ERL_PID_SERIAL(ep));
- break;
- case ERL_PORT:
- ch_written += fprintf(fp, "#Port");
- break;
- case ERL_REF:
- ch_written += fprintf(fp, "#Ref");
- break;
- case ERL_EMPTY_LIST:
- ch_written += fprintf(fp, "[]");
- break;
- case ERL_LIST:
- if (is_printable_list(ep)) {
- ch_written += print_string(fp, ep);
- } else {
- putc('[', fp);
- ch_written++;
- while (ERL_IS_CONS(ep)) {
- ch_written += erl_print_term(fp, HEAD(ep));
- ep = TAIL(ep);
- if (ERL_IS_CONS(ep)) {
- putc(',', fp);
- ch_written++;
- }
- }
- if (!ERL_IS_EMPTY_LIST(ep)) {
- putc('|', fp);
- ch_written++;
- ch_written += erl_print_term(fp, ep);
- }
- putc(']', fp);
- ch_written++;
- }
- break;
- case ERL_TUPLE:
- putc('{', fp);
- ch_written++;
- for (i=0; i < ERL_TUPLE_SIZE(ep); i++) {
- ch_written += erl_print_term(fp, ERL_TUPLE_ELEMENT(ep, j++) );
- if (i != ERL_TUPLE_SIZE(ep)-1) {
- putc(',', fp);
- ch_written++;
- }
- }
- putc('}', fp);
- ch_written++;
- break;
- case ERL_BINARY: {
- int sz = (ERL_BIN_SIZE(ep) > 20) ? 20 : ERL_BIN_SIZE(ep);
- unsigned char *ptr = ERL_BIN_PTR(ep);
- ch_written += fprintf(fp, "#Bin<");
- for (i = 0; i < sz; i++) {
- putc(ptr[i], fp); ch_written++;
- }
- if (sz == 20) ch_written += fprintf(fp, "(%d)....>", ERL_BIN_SIZE(ep)-20);
- else ch_written += fprintf(fp, ">");
- break;
- }
- case ERL_INTEGER:
- case ERL_SMALL_BIG:
- ch_written += fprintf(fp, "%d", ERL_INT_VALUE(ep));
- break;
- case ERL_U_INTEGER:
- case ERL_U_SMALL_BIG:
- ch_written += fprintf(fp, "%d", ERL_INT_UVALUE(ep));
- break;
- case ERL_LONGLONG:
- case ERL_U_LONGLONG:
- ch_written += fprintf(fp, "%lld", ERL_LL_UVALUE(ep));
- break;
- case ERL_FLOAT:
- ch_written += fprintf(fp, "%f", ERL_FLOAT_VALUE(ep));
- break;
- case ERL_FUNCTION:
- ch_written += fprintf(fp, "#Fun<");
- ch_written += erl_print_term(fp, ERL_FUN_MODULE(ep));
- putc('.', fp);
- ch_written++;
- ch_written += erl_print_term(fp, ERL_FUN_INDEX(ep));
- putc('.', fp);
- ch_written++;
- ch_written += erl_print_term(fp, ERL_FUN_UNIQ(ep));
- putc('>', fp);
- ch_written++;
- break;
- default:
- ch_written = -10000;
- erl_err_msg("<ERROR> erl_print_term: Bad type of term !");
- }
- return ch_written;
-}
-
-/*
- * FIXME not done yet....
- */
-
-#if 0
-
-int erl_sprint_term(char *buf, const ETERM *ep)
-{
- int j,i,doquote;
- int ch_written = 0; /* counter of written chars */
-
- if ((!buf) || (!ep)) return 0;
- /* ASSERT(ep != NULL); */
-
- j = i = doquote = 0;
- switch(ERL_TYPE(ep))
- {
- case ERL_ATOM:
- /* FIXME: what if some weird locale is in use? */
- if (!islower((int)ERL_ATOM_PTR(ep)[0]))
- doquote = 1;
-
- for (i = 0; !doquote && i < ERL_ATOM_SIZE(ep); i++)
- {
- doquote = !(isalnum((int)ERL_ATOM_PTR(ep)[i])
- || (ERL_ATOM_PTR(ep)[i] == '_'));
- }
-
- if (doquote) {
- *buf++ = '\'';
- ch_written++;
- }
- {
- int len = ERL_ATOM_SIZE(ep);
- strncpy(buf, ERL_ATOM_PTR(ep), len);
- buf += len;
- ch_written += len;
- }
- if (doquote) {
- *buf++ = '\'';
- ch_written++;
- }
- break;
-
- case ERL_VARIABLE:
- if (!isupper((int)ERL_VAR_NAME(ep)[0])) {
- doquote = 1;
- *buf++ = '\'';
- ch_written++;
- }
- len = ERL_VAR_LEN(ep);
- strncpy(buf, ERL_VAR_NAME(ep), len);
- buf += len;
- ch_written += len;
-
- if (doquote) {
- *buf++ = '\'';
- ch_written++;
- }
- break;
-
- case ERL_PID:
- len = sprintf(buf, "<%s.%d.%d>",
- ERL_PID_NODE(ep),
- ERL_PID_NUMBER(ep), ERL_PID_SERIAL(ep));
- buf += len;
- ch_written += len;
- break;
- case ERL_PORT:
- len = sprintf(buf , "#Port");
- buf += len;
- ch_written += len;
- break;
- case ERL_REF:
- len = sprintf(buf , "#Ref");
- buf += len;
- ch_written += len;
- break;
- case ERL_EMPTY_LIST:
- len = sprintf(buf , "[]");
- buf += len;
- ch_written += len;
- break;
- case ERL_LIST:
- if (is_printable_list(ep)) {
- ch_written += print_string(fp, ep);
- } else {
- putc('[', fp);
- ch_written++;
- while (ERL_IS_CONS(ep)) {
- ch_written += erl_sprint_term(fp, HEAD(ep));
- ep = TAIL(ep);
- if (ERL_IS_CONS(ep)) {
- putc(',', fp);
- ch_written++;
- }
- }
- if (!ERL_IS_EMPTY_LIST(ep)) {
- putc('|', fp);
- ch_written++;
- ch_written += erl_sprint_term(fp, ep);
- }
- putc(']', fp);
- ch_written++;
- }
- break;
- case ERL_TUPLE:
- putc('{', fp);
- ch_written++;
- for (i=0; i < ERL_TUPLE_SIZE(ep); i++) {
- ch_written += erl_sprint_term(fp, ERL_TUPLE_ELEMENT(ep, j++) );
- if (i != ERL_TUPLE_SIZE(ep)-1) {
- putc(',', fp);
- ch_written++;
- }
- }
- putc('}', fp);
- ch_written++;
- break;
- case ERL_BINARY:
- len = sprintf(buf , "#Bin");
- buf += len;
- ch_written += len;
- break;
- case ERL_INTEGER:
- case ERL_SMALL_BIG:
- len = sprintf(buf , "%d", ERL_INT_VALUE(ep));
- buf += len;
- ch_written += len;
- break;
- case ERL_U_INTEGER:
- case ERL_U_SMALL_BIG:
- len = sprintf(buf , "%d", ERL_INT_UVALUE(ep));
- buf += len;
- ch_written += len;
- break;
- case ERL_FLOAT:
- len = sprintf(buf , "%f", ERL_FLOAT_VALUE(ep));
- buf += len;
- ch_written += len;
- break;
- case ERL_FUNCTION:
- len = sprintf(buf , "#Fun<");
- buf += len;
- ch_written += len;
- ch_written += erl_sprint_term(fp, ERL_FUN_MODULE(ep));
- putc('.', fp);
- ch_written++;
- ch_written += erl_sprint_term(fp, ERL_FUN_INDEX(ep));
- putc('.', fp);
- ch_written++;
- ch_written += erl_sprint_term(fp, ERL_FUN_UNIQ(ep));
- putc('>', fp);
- ch_written++;
- break;
- default:
- ch_written = -10000;
- erl_err_msg("<ERROR> erl_sprint_term: Bad type of term !");
- }
- return ch_written;
-}
-#endif
-
-static int print_string(FILE* fp, const ETERM* ep)
-{
- int ch_written = 0; /* counter of written chars */
-
- putc('"', fp);
- ch_written++;
- while (ERL_IS_CONS(ep)) {
- int c = ERL_INT_VALUE(HEAD(ep));
-
- if (c >= ' ') {
- putc(c, fp);
- ch_written++;
- }
- else {
- switch (c) {
- case '\n': fputs("\\n", fp); ch_written += 2; break;
- case '\r': fputs("\\r", fp); ch_written += 2; break;
- case '\t': fputs("\\t", fp); ch_written += 2; break;
- case '\v': fputs("\\v", fp); ch_written += 2; break;
- case '\b': fputs("\\b", fp); ch_written += 2; break;
- case '\f': fputs("\\f", fp); ch_written += 2; break;
- break;
- default:
- ch_written += fprintf(fp, "\\%o", c);
- break;
- }
- }
- ep = TAIL(ep);
- }
- putc('"', fp);
- ch_written++;
- return ch_written;
-}
-
-/*
- * Returns 1 if term is a list of printable character, otherwise 0.
- */
-
-static int is_printable_list(const ETERM* term)
-{
- while (ERL_TYPE(term) == ERL_LIST) {
- ETERM* head = HEAD(term);
-
- if (!ERL_IS_BYTE(head)) {
- return 0;
- }
- if (ERL_INT_VALUE(head) < ' ') {
- switch (ERL_INT_VALUE(head)) {
- case '\n':
- case '\r':
- case '\t':
- case '\v':
- case '\b':
- case '\f':
- break;
- default:
- return 0;
- }
- }
- term = TAIL(term);
- }
-
- return ERL_IS_EMPTY_LIST(term);
-}
-
-#endif
-
-/*
- * Retrieves the bytes from an I/O list and copy into a buffer.
- *
- * NOTE! It is the responsibility of the caller to ensure that
- * that the buffer is big enough (typically by calling
- * erl_iolist_length()), and that the term is an I/O list.
- *
- * ETERM* term; Term to convert to bytes.
- * char** bufp; Pointer to pointer to buffer
- * where the bytes should be stored.
- * On return, the pointer will point beyond
- * the last byte stored.
- */
-
-static void iolist_to_buf(const ETERM* term, char** bufp)
-{
- char* dest = *bufp;
-
- while (ERL_IS_CONS(term)) {
- ETERM* obj = HEAD(term);
-
- if (ERL_IS_BYTE(obj)) {
- *dest++ = ERL_INT_VALUE(obj);
- } else if (ERL_IS_CONS(obj)) {
- iolist_to_buf(obj, &dest);
- } else if (ERL_IS_BINARY(obj)) {
- memcpy(dest, ERL_BIN_PTR(obj), ERL_BIN_SIZE(obj));
- dest += ERL_BIN_SIZE(obj);
- } else {
- /*
- * Types have been checked by caller.
- */
- if (!ERL_IS_EMPTY_LIST(obj)) return;
- /* ASSERT(ERL_IS_EMPTY_LIST(obj)); */
- }
- term = TAIL(term);
- }
- if (ERL_IS_BINARY(term)) {
- memcpy(dest, ERL_BIN_PTR(term), ERL_BIN_SIZE(term));
- dest += ERL_BIN_SIZE(term);
- } else {
- /*
- * Types have been checked by caller.
- */
- if (!ERL_IS_EMPTY_LIST(term)) return;
- /* ASSERT(ERL_IS_EMPTY_LIST(term));*/
- }
- *bufp = dest;
-}
-
-static char* strsave(const char *src)
-{
- char * dest = malloc(strlen(src)+1);
-
- if (dest != NULL)
- strcpy(dest, src);
- return dest;
-}
-
-
-/*
- * Local Variables:
- * compile-command: "cd ..; ERL_TOP=/clearcase/otp/erts make -k"
- * End:
- */
diff --git a/lib/erl_interface/src/legacy/erl_eterm.h b/lib/erl_interface/src/legacy/erl_eterm.h
deleted file mode 100644
index e2f3a90531..0000000000
--- a/lib/erl_interface/src/legacy/erl_eterm.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-#ifndef _ERL_ETERM_H
-#define _ERL_ETERM_H
-
-#ifndef SILENT
-#include <stdio.h>
-#endif
-
-#include "portability.h"
-
-#define ERL_MAX_COUNT 0xffffff
-#define ERL_MAX ((1 << 27)-1)
-#define ERL_MIN -(1 << 27)
-
-/* FIXME should this be documented and in erl_interface.h ??? */
-#define ERL_BIG_ARITY(x) ((x)->uval.bigval.arity)
-#define ERL_BIG_IS_NEG(x) ((x)->uval.bigval.is_neg)
-#define ERL_BIG_DIGITS(x) ((x)->uval.bigval.digits)
-#define ERL_BIG_DIGIT(x,i) (ERL_BIG_DIGITS(x)[(i)])
-
-/*
- * Typing checking macros.
- */
-
-/* FIXME should this be documented and in erl_interface.h ??? */
-#define ERL_IS_DEFINED(x) (ERL_TYPE(x) != 0)
-#define ERL_IS_COMPOUND(x) (ERL_TYPE(x) & ERL_COMPOUND)
-#define ERL_IS_FUNCTION(x) (ERL_TYPE(x) == ERL_FUNCTION)
-#define ERL_IS_BIG(x) (ERL_TYPE(x) == ERL_BIG)
-
-
-typedef struct _heapmark {
- unsigned long mark; /* id */
- int size; /* size of buffer */
- Erl_Heap *base; /* points to start of buffer */
- Erl_Heap *cur; /* points into buffer */
- struct _heapmark *prev; /* previous heapmark */
-} Erl_HeapMark;
-
-
-void erl_mk_port_helper(ETERM* ep, unsigned number, unsigned int creation);
-void erl_mk_pid_helper(ETERM*, unsigned,unsigned, unsigned int);
-ETERM * __erl_mk_reference(ETERM*, const char *, size_t, unsigned int n[], unsigned int);
-int erl_current_fix_desc(void);
-
-#endif /* _ERL_ETERM_H */
diff --git a/lib/erl_interface/src/legacy/erl_fix_alloc.c b/lib/erl_interface/src/legacy/erl_fix_alloc.c
deleted file mode 100644
index 890a9ce291..0000000000
--- a/lib/erl_interface/src/legacy/erl_fix_alloc.c
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-/*
- * Function: General purpose Memory allocator for fixed block
- * size objects. This allocater is at least an order of
- * magnitude faster than malloc().
- */
-#include "eidef.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include "ei_locking.h"
-#include "erl_interface.h"
-#include "erl_error.h"
-#include "erl_malloc.h"
-#include "erl_fix_alloc.h"
-#include "erl_eterm.h"
-
-#define WIPE_CHAR ((char)0xaa) /* 10101010 */
-
-/* the freelist is a singly linked list of these */
-/* i.e. the user structure and a link pointer */
-struct fix_block {
- ETERM term;
- struct fix_block *next;
- int free;
-};
-
-/* this is a struct just to keep namespace pollution low on VxWorks */
-struct eterm_stateinfo {
- struct fix_block *freelist;
- unsigned long freed;
- unsigned long allocated;
-#ifdef _REENTRANT
- ei_mutex_t *lock;
-#endif /* _REENTRANT */
-};
-/* FIXME problem for threaded ? */
-static struct eterm_stateinfo *erl_eterm_state=NULL;
-
-
-int erl_init_eterm_alloc (void)
-{
-#if defined(PURIFY) && defined (DEBUG)
- fprintf(stderr,"erl_fix_alloc() compiled for Purify - using \"real\" malloc()");
-#endif
-
- erl_eterm_state = malloc(sizeof(*erl_eterm_state));
- if (erl_eterm_state == NULL) goto err1;
-
- erl_eterm_state->freelist = NULL;
- erl_eterm_state->freed = 0;
- erl_eterm_state->allocated = 0;
-#ifdef _REENTRANT
- erl_eterm_state->lock = ei_mutex_create();
- if (erl_eterm_state->lock == NULL) goto err2;
-#endif /* _REENTRANT */
-
- return 1;
-
- /* Error cleanup */
-#ifdef _REENTRANT
- err2:
- /* FIXME ENOMEM is not what went wrong... */
- free(erl_eterm_state);
-#endif /* _REENTRANT */
- err1:
- erl_errno = ENOMEM;
- return 0;
-}
-
-/* get an eterm, from the freelist if possible or from malloc() */
-void *erl_eterm_alloc (void)
-{
-#ifdef PURIFY
- ETERM *p;
-
- if ((p = malloc(sizeof(*p)))) {
- memset(p, WIPE_CHAR, sizeof(*p));
- }
- return p;
-#else
- struct fix_block *b;
-
-#ifdef _REENTRANT
- ei_mutex_lock(erl_eterm_state->lock, 0);
-#endif /* _REENTRANT */
-
- /* try to pop block from head of freelist */
- if ((b = erl_eterm_state->freelist) != NULL) {
- erl_eterm_state->freelist = b->next;
- erl_eterm_state->freed--;
- } else if ((b = malloc(sizeof(*b))) == NULL) {
- erl_errno = ENOMEM;
-#ifdef _REENTRANT
- ei_mutex_unlock(erl_eterm_state->lock);
-#endif /* _REENTRANT */
- return NULL;
- }
- erl_eterm_state->allocated++;
- b->free = 0;
- b->next = NULL;
-#ifdef _REENTRANT
- ei_mutex_unlock(erl_eterm_state->lock);
-#endif /* _REENTRANT */
- return (void *) &b->term;
-#endif /* !PURIFY */
-}
-
-/* free an eterm back to the freelist */
-void erl_eterm_free(void *p)
-{
-#ifdef PURIFY
- if (p) {
- memset(p, WIPE_CHAR, sizeof(ETERM));
- }
- free(p);
-#else
- struct fix_block *b = p;
-
- if (b) {
- if (b->free) {
-#ifdef DEBUG
- fprintf(stderr,"erl_eterm_free: attempt to free already freed block %p\n",b);
-#endif
- return;
- }
-
-#ifdef _REENTRANT
- ei_mutex_lock(erl_eterm_state->lock,0);
-#endif /* _REENTRANT */
- b->free = 1;
- b->next = erl_eterm_state->freelist;
- erl_eterm_state->freelist = b;
- erl_eterm_state->freed++;
- erl_eterm_state->allocated--;
-#ifdef _REENTRANT
- ei_mutex_unlock(erl_eterm_state->lock);
-#endif /* _REENTRANT */
- }
-#endif /* !PURIFY */
-}
-
-/* really free the freelist */
-void erl_eterm_release (void)
-{
-#if !defined(PURIFY)
- struct fix_block *b;
-
-#ifdef _REENTRANT
- ei_mutex_lock(erl_eterm_state->lock,0);
-#endif /* _REENTRANT */
- {
- while (erl_eterm_state->freelist != NULL) {
- b = erl_eterm_state->freelist;
- erl_eterm_state->freelist = b->next;
- free(b);
- erl_eterm_state->freed--;
- }
- }
-#ifdef _REENTRANT
- ei_mutex_unlock(erl_eterm_state->lock);
-#endif /* _REENTRANT */
-#endif /* !PURIFY */
-}
-
-void erl_eterm_statistics (unsigned long *allocd, unsigned long *freed)
-{
- if (allocd) *allocd = erl_eterm_state->allocated;
- if (freed) *freed = erl_eterm_state->freed;
-
- return;
-}
-
-
-/*
- * Local Variables:
- * compile-command: "cd ..; ERL_TOP=/clearcase/otp/erts make -k"
- * End:
- */
diff --git a/lib/erl_interface/src/legacy/erl_format.c b/lib/erl_interface/src/legacy/erl_format.c
deleted file mode 100644
index 45f5489e54..0000000000
--- a/lib/erl_interface/src/legacy/erl_format.c
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-/*
- * Function: Provides two primitives: erl_format to build
- * Erlang terms in an easy way, and erl_match to perform
- * pattern match similar to what is done in Erlang.
- *
- */
-
-#include "eidef.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-#include <ctype.h>
-
-#ifdef VRTX
-#define __READY_EXTENSIONS__
-#include <errno.h>
-#endif
-#include "erl_interface.h"
-#include "erl_eterm.h"
-#include "erl_malloc.h"
-#include "erl_error.h"
-#include "erl_internal.h"
-
-#define ERL_TRUE 1
-#define ERL_FALSE 0
-#define ERL_OK 0
-#define ERL_FORMAT_ERROR -1
-
-#define ERL_MAX_ENTRIES 255 /* Max entries in a tuple/list term */
-#define ERL_MAX_NAME_LENGTH 255 /* Max length of variable names */
-
-#define PRINT(t) \
-{ \
- print_term(stderr,t); \
- fprintf(stderr,"\n"); \
- }
-
-
-typedef struct lvar {
- ETERM *var;
- struct lvar *next;
-} lvar;
-
-
-/* Forward */
-static ETERM *eformat(char**, va_list*);
-static int ematch(ETERM*, ETERM*);
-
-/* FIXME not thread safe */
-struct _ef {
- lvar *chain; /* Chain of local variables */
- lvar *idle; /* Idle list of lvar's */
-} ef;
-
-/* Find local variable in term.
- */
-static ETERM *find_lvar(char *name)
-{
- lvar *tmp=ef.chain;
-
- while (tmp != NULL) {
- if (strcmp(tmp->var->uval.vval.name,name) == 0)
- return tmp->var->uval.vval.v;
- tmp = tmp->next;
- }
- return (ETERM *) NULL;
-
-} /* find_lvar */
-
-static void lvar_free(lvar *lv)
-{
- lvar *tmp=ef.chain;
-
- /* Link in the chain into the idle list */
- if (ef.idle == NULL)
- ef.idle = lv;
- else {
- tmp = ef.idle;
- while (tmp->next != NULL)
- tmp = tmp->next;
- tmp->next = lv;
- }
-
-
- /* Clear out the variable information */
- tmp = lv;
- while (tmp != NULL) {
- tmp->var = (ETERM *) NULL;
- tmp = tmp->next;
- }
-
-} /* lvar_free */
-
-static lvar *lvar_alloc(void)
-{
- lvar *tmp;
-
- if ((tmp = ef.idle) == NULL) {
- tmp = (lvar *) erl_malloc(sizeof(lvar));
- }
- else {
- tmp = ef.idle;
- ef.idle = tmp->next;
- }
- return tmp;
-
-} /* lvar_alloc */
-
-static void undo_bindings(void)
-{
- lvar *tmp=ef.chain;
-
- while (tmp != NULL) {
- erl_free_term(tmp->var->uval.vval.v);
- tmp->var->uval.vval.v = (ETERM *) NULL;
- tmp = tmp->next;
- }
-
-} /* undo_bindings */
-
-static void release_chain(void)
-{
-
- lvar_free(ef.chain);
- ef.chain = (lvar *) NULL;
-
-} /* release_chain */
-
-static void add_lvar(ETERM *t)
-{
- lvar *lv;
-
- lv = lvar_alloc();
- lv->var = t;
- lv->next = ef.chain;
- ef.chain = lv;
-
-} /* add_lvar */
-
-static char *pvariable(char **fmt, char *buf)
-{
- char *start=*fmt;
- char c;
- int len;
-
- while (1) {
- c = *(*fmt)++;
- if (isalnum((int) c) || (c == '_'))
- continue;
- else
- break;
- }
- (*fmt)--;
- len = *fmt - start;
- memcpy(buf, start, len);
- buf[len] = 0;
-
- return buf;
-
-} /* pvariable */
-
-static char *patom(char **fmt, char *buf)
-{
- char *start=*fmt;
- char c;
- int len;
-
- while (1) {
- c = *(*fmt)++;
- if (isalnum((int) c) || (c == '_') || (c == '@'))
- continue;
- else
- break;
- }
- (*fmt)--;
- len = *fmt - start;
- memcpy(buf, start, len);
- buf[len] = 0;
-
- return buf;
-
-} /* patom */
-
-/* Check if integer or float
- */
-static char *pdigit(char **fmt, char *buf)
-{
- char *start=*fmt;
- char c;
- int len,dotp=0;
-
- while (1) {
- c = *(*fmt)++;
- if (isdigit((int) c))
- continue;
- else if (!dotp && (c == '.')) {
- dotp = 1;
- continue;
- }
- else
- break;
- }
- (*fmt)--;
- len = *fmt - start;
- memcpy(buf, start, len);
- buf[len] = 0;
-
- return buf;
-
-} /* pdigit */
-
-static char *pstring(char **fmt, char *buf)
-{
- char *start=++(*fmt); /* skip first quote */
- char c;
- int len;
-
- while (1) {
- c = *(*fmt)++;
- if (c == '"') {
- if (*((*fmt)-1) == '\\')
- continue;
- else
- break;
- } else
- continue;
- }
- len = *fmt - 1 - start; /* skip last quote */
- memcpy(buf, start, len);
- buf[len] = 0;
-
- return buf;
-
-} /* pstring */
-
-static char *pquotedatom(char **fmt, char *buf)
-{
- char *start=++(*fmt); /* skip first quote */
- char c;
- int len;
-
- while (1) {
- c = *(*fmt)++;
- if (c == '\'') {
- if (*((*fmt)-1) == '\\')
- continue;
- else
- break;
- } else
- continue;
- }
- len = *fmt - 1 - start; /* skip last quote */
- memcpy(buf, start, len);
- buf[len] = 0;
-
- return buf;
-
-} /* pquotedatom */
-
-
-/*
- * The format letters are:
- * w - Any Erlang term
- * a - An Atom
- * b - A Binary
- * s - A String
- * i - An Integer
- * f - A Float (double)
- */
-static int pformat(char **fmt, va_list *pap, ETERM *v[], int size)
-{
- int rc=ERL_OK;
-
- /* this next section hacked to remove the va_arg calls */
- switch (*(*fmt)++) {
-
- case 'w':
- v[size] = va_arg(*pap, ETERM*);
- ERL_COUNT(v[size])++;
- break;
-
- case 'a':
- v[size] = erl_mk_atom(va_arg(*pap, char *));
- break;
-
- case 's':
- v[size] = erl_mk_string(va_arg(*pap, char *));
- break;
-
- case 'i':
- v[size] = erl_mk_int(va_arg(*pap, int));
- break;
-
- case 'f':
- v[size] = erl_mk_float(va_arg(*pap, double));
- break;
-
- case 'b': {
- char *sarg = va_arg(*pap, char *);
- v[size] = erl_mk_binary(sarg, strlen(sarg));
- break;
- }
-
- default:
- rc = ERL_FORMAT_ERROR;
- break;
- }
-
- return rc;
-
-} /* pformat */
-
-static int ptuple(char **fmt, va_list *pap, ETERM *v[], int size)
-{
- int res=ERL_FORMAT_ERROR;
-
- switch (*(*fmt)++) {
-
- case '}':
- res = size;
- break;
-
- case ',':
- res = ptuple(fmt, pap, v, size);
- break;
-
- case '~':
-
- if (pformat(fmt, pap, v, size) == ERL_OK)
- res = ptuple(fmt, pap, v, ++size);
- else
- erl_err_msg("ptuple(1): Wrong format sequence !");
- break;
-
- case ' ':
- return ptuple(fmt, pap, v, size);
- break;
-
- default: {
- (*fmt)--;
- if ((v[size++] = eformat(fmt, pap)) != (ETERM *) NULL)
- res = ptuple(fmt, pap, v, size);
- break;
-
- /*
- if (isupper(**fmt)) {
- v[size++] = erl_mk_var(pvariable(fmt, wbuf));
- res = ptuple(fmt, pap, v, size);
- }
- else if ((v[size++] = eformat(fmt, pap)) != (ETERM *) NULL)
- res = ptuple(fmt, pap, v, size);
- break;
- */
- }
-
- } /* switch */
-
- return res;
-
-} /* ptuple */
-
-
-static int plist(char **fmt, va_list *pap, ETERM *v[], int size)
-{
- int res=ERL_FORMAT_ERROR;
-
- switch (*(*fmt)++) {
-
- case ']':
- res = size;
- break;
-
- case ',':
- res = plist(fmt, pap, v, size);
- break;
-
- case '~':
-
- if (pformat(fmt, pap, v, size) == ERL_OK)
- res = plist(fmt, pap, v, ++size);
- else
- erl_err_msg("plist(1): Wrong format sequence !");
- break;
-
- case ' ':
- return plist(fmt, pap, v, size);
- break;
-
- default: {
- (*fmt)--;
- if ((v[size++] = eformat(fmt, pap)) != (ETERM *) NULL)
- res = plist(fmt, pap, v, size);
- break;
-
- /*
- if (isupper(**fmt)) {
- v[size++] = erl_mk_var(pvariable(fmt, wbuf));
- res = plist(fmt, pap, v, size);
- }
- else if ((v[size++] = eformat(fmt, pap)) != (ETERM *) NULL)
- res = plist(fmt, pap, v, size);
- break;
- */
- }
-
- } /* switch */
-
- return res;
-
-} /* plist */
-
-
-static ETERM *eformat(char **fmt, va_list *pap)
-{
- int size;
- ETERM *v[ERL_MAX_ENTRIES],*ep;
-
- switch (*(*fmt)++) {
- case '{':
- if ((size = ptuple(fmt, pap , v, 0)) != ERL_FORMAT_ERROR) {
- ep = erl_mk_tuple(v, size);
- erl_free_array(v, size);
- return ep;
- }
- else
- return (ETERM *) NULL;
- break;
-
- case '[':
- if (**fmt == ']') {
- (*fmt)++;
- return erl_mk_empty_list();
- } else if ((size = plist(fmt, pap , v, 0)) != ERL_FORMAT_ERROR) {
- ep = erl_mk_list(v, size);
- erl_free_array(v, size);
- return ep;
- } else
- return (ETERM *) NULL;
- break;
-
- case '$': /* char-value? */
- return erl_mk_int((int)(*(*fmt)++));
- break;
-
- case '~':
- if (pformat(fmt, pap, v, 0) == ERL_OK) {
- ep = erl_copy_term(v[0]);
- erl_free_term(v[0]);
- return ep;
- }
- break;
-
- case ' ':
- return eformat(fmt, pap);
- break;
-
- /* handle negative numbers too...
- * case '-':
- * {
- * ETERM *tmp;
- *
- * tmp = eformat(fmt,pap);
- * if (ERL_IS_INTEGER(tmp)) ERL_INT_VALUE(tmp) = -(ERL_INT_VALUE(tmp));
- * return tmp;
- * }
- *
- *
- * break;
- */
-
- default:
- {
- char wbuf[BUFSIZ]; /* now local to this function for reentrancy */
-
- (*fmt)--;
- if (islower((int)**fmt)) { /* atom ? */
- char *atom=patom(fmt, wbuf);
- return erl_mk_atom(atom);
- }
- else if (isupper((int)**fmt) || (**fmt == '_')) {
- char *var=pvariable(fmt, wbuf);
- return erl_mk_var(var);
- }
- else if (isdigit((int)**fmt)) { /* integer/float ? */
- char *digit=pdigit(fmt, wbuf);
- if (strchr(digit,(int) '.') == NULL)
- return erl_mk_int(atoi((const char *) digit));
- else
- return erl_mk_float(atof((const char *) digit));
- }
- else if (**fmt == '"') { /* string ? */
- char *string=pstring(fmt, wbuf);
- return erl_mk_string(string);
- }
- else if (**fmt == '\'') { /* quoted atom ? */
- char *qatom=pquotedatom(fmt, wbuf);
- return erl_mk_atom(qatom);
- }
- }
- break;
-
- }
-
- erl_err_msg("<ERROR> Syntax error in eformat, char was: %c !", **fmt);
- return (ETERM *) NULL;
-
-} /* eformat */
-
-
-ETERM *erl_format(char *fmt, ... )
-{
- ETERM *res=NULL;
- va_list ap;
-
- va_start(ap, fmt);
- res = eformat(&fmt, &ap);
- va_end(ap);
-
- return res;
-} /* erl_format */
-
-
-/*
- * Perform a pattern match between a pattern p and a term t.
- * As a side effect bind any unbound variables in p.
- * Return true or false.
- */
-static int ematch(ETERM *p, ETERM *t)
-{
- unsigned int type_p;
- unsigned int type_t;
- ETERM *tmp;
-
- /* two NULLs are equal, one is not... */
- if (!p && !t) return ERL_TRUE;
- if (!p || !t) return ERL_FALSE;
- /*
- * ASSERT(p != NULL);
- * ASSERT(t != NULL);
- */
-
- type_p = ERL_TYPE(p);
- type_t = ERL_TYPE(t);
-
- if (type_t == ERL_VARIABLE) {
- if (t->uval.vval.v == NULL)
- return ERL_FALSE; /* Can't have an unbound variable here ! */
- else
- t = t->uval.vval.v;
- }
-
- if (type_p != ERL_VARIABLE && type_p != type_t)
- return ERL_FALSE;
-
- switch (type_p) {
-
- case ERL_ATOM: {
- Erl_Atom_data* pa = &p->uval.aval.d;
- Erl_Atom_data* ta = &t->uval.aval.d;
- if (pa->utf8 && ta->utf8) {
- return pa->lenU == ta->lenU && memcmp(pa->utf8, ta->utf8, pa->lenU)==0;
- }
- else if (pa->latin1 && ta->latin1) {
- return pa->lenL == ta->lenL && memcmp(pa->latin1, ta->latin1, pa->lenL)==0;
- }
- else if (pa->latin1) {
- return cmp_latin1_vs_utf8(pa->latin1, pa->lenL, ta->utf8, ta->lenU)==0;
- }
- else {
- return cmp_latin1_vs_utf8(ta->latin1, ta->lenL, pa->utf8, pa->lenU)==0;
- }
- }
- case ERL_VARIABLE:
- if (strcmp(p->uval.vval.name, "_") == 0) /* anon. variable */
- return ERL_TRUE;
- else if ((tmp = find_lvar(p->uval.vval.name)) != (ETERM *) NULL) {
- /* v points to NULL in cases like erl_format("{X,X}") for the
- second variable */
- if (p->uval.vval.v == NULL)
- p->uval.vval.v = erl_copy_term(tmp);
- return ematch(p->uval.vval.v, t);
- }
- else {
- /* check if the variable is bound already */
- if (p->uval.vval.v != NULL) {
- if (ematch(p->uval.vval.v, t) == ERL_TRUE ){
- add_lvar(p);
- return ERL_TRUE;
- }
- else
- return ERL_FALSE;
- }
- else {
- p->uval.vval.v = erl_copy_term(t);
- add_lvar(p);
- return ERL_TRUE;
- }
- }
- break;
-
- case ERL_PID:
- if ((strcmp(ERL_PID_NODE(p), ERL_PID_NODE(t)) == 0) &&
- (ERL_PID_NUMBER(p) == ERL_PID_NUMBER(t)) &&
- (ERL_PID_SERIAL(p) == ERL_PID_SERIAL(t)) &&
- (ERL_PID_CREATION(p) == ERL_PID_CREATION(t)))
- return ERL_TRUE;
- else
- return ERL_FALSE;
- break;
-
- case ERL_PORT:
- if ((strcmp(ERL_PORT_NODE(p), ERL_PORT_NODE(t)) == 0) &&
- (ERL_PORT_NUMBER(p) == ERL_PORT_NUMBER(t)) &&
- (ERL_PORT_CREATION(p) == ERL_PORT_CREATION(t)))
- return ERL_TRUE;
- else
- return ERL_FALSE;
- break;
-
- case ERL_REF: {
- int i, len;
-
- if (strcmp(ERL_REF_NODE(p), ERL_REF_NODE(t)) != 0 ||
- ERL_REF_CREATION(p) != ERL_REF_CREATION(t))
- return ERL_FALSE;
-
- /* FIXME: {len=1, n={42}} and {len=3, n={42, 17, 13}} tests equal. */
- len = ERL_REF_LEN(p);
- if (len > ERL_REF_LEN(t))
- len = ERL_REF_LEN(t);
-
- for (i = 0; i < len; i++)
- if (ERL_REF_NUMBERS(p)[i] != ERL_REF_NUMBERS(t)[i])
- return ERL_FALSE;
-
- return ERL_TRUE;
- break;
- }
-
- case ERL_EMPTY_LIST:
- return ERL_TRUE;
-
- case ERL_LIST:
- while (ERL_IS_CONS(p) && ERL_IS_CONS(t)) {
- if (ematch(p->uval.lval.head, t->uval.lval.head) == ERL_FALSE)
- return ERL_FALSE;
- p = p->uval.lval.tail;
- t = t ->uval.lval.tail;
- }
- return ematch(p, t);
-
- case ERL_TUPLE:
- {
- int i;
- if (erl_size(p) != erl_size(t))
- return ERL_FALSE;
- else {
- for(i=0; i<erl_size(p); i++)
- if (ematch(p->uval.tval.elems[i],t->uval.tval.elems[i]) == ERL_FALSE)
- return ERL_FALSE;
- return ERL_TRUE;
- }
- }
- break;
-
- case ERL_BINARY:
- {
- int i;
- if ((i = p->uval.bval.size) != t->uval.bval.size)
- return ERL_FALSE;
- else
- return (memcmp(p->uval.bval.b,t->uval.bval.b,i)==0) ? ERL_TRUE : ERL_FALSE;
- }
- break;
-
- case ERL_INTEGER:
- return (p->uval.ival.i == t->uval.ival.i) ? ERL_TRUE : ERL_FALSE;
- break;
-
- case ERL_SMALL_BIG:
- case ERL_U_SMALL_BIG:
- /* This case can't happend since it is impossible
- * to create a bignum from the C code.
- */
- return ERL_FALSE;
- break;
-
- case ERL_FLOAT:
-#if defined(VXWORKS) && CPU == PPC860
- {
- return (erl_fp_compare((unsigned *)&(p->uval.fval.f),
- (unsigned *)&(t->uval.fval.f)) == 0)
- ? ERL_TRUE : ERL_FALSE;
- }
-#else
- return (p->uval.fval.f == t->uval.fval.f) ? ERL_TRUE : ERL_FALSE;
-#endif
- break;
- default:
- return ERL_FALSE;
- break;
- }
-
- /* erl_err_msg("ematch: Unknown type == %c\n", type_p); */
- return ERL_FALSE;
-
-} /* ematch */
-
-
-int erl_match(ETERM *p, ETERM *t)
-{
- int i;
-
- if ((i = ematch(p, t)) == ERL_FALSE)
- undo_bindings();
- release_chain();
- return i;
-
-} /* erl_match */
-
-
diff --git a/lib/erl_interface/src/legacy/erl_format.h b/lib/erl_interface/src/legacy/erl_format.h
deleted file mode 100644
index 92fa068206..0000000000
--- a/lib/erl_interface/src/legacy/erl_format.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-#ifndef _ERL_FORMAT_H
-#define _ERL_FORMAT_H
-
-#endif /* _ERL_FORMAT_H */
diff --git a/lib/erl_interface/src/legacy/erl_malloc.c b/lib/erl_interface/src/legacy/erl_malloc.c
deleted file mode 100644
index 27ef8c4b32..0000000000
--- a/lib/erl_interface/src/legacy/erl_malloc.c
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#include "eidef.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-
-#include "erl_interface.h"
-#include "erl_fix_alloc.h"
-#include "erl_malloc.h"
-#include "erl_internal.h"
-#include "erl_eterm.h"
-#include "ei_malloc.h"
-
-void erl_init_malloc(Erl_Heap *hp, long heap_size)
-{
- erl_init_eterm_alloc();
-} /* erl_init_malloc */
-
-ETERM *erl_alloc_eterm(unsigned char type)
-{
- ETERM *e;
-
- /* Use fix size allocator */
- if (!(e = (ETERM *) erl_eterm_alloc()))
- erl_err_sys("<ERROR> erl_alloc_eterm: Failed to allocate more memory\n");
-
- ERL_HEADER(e)->count = 0;
- ERL_HEADER(e)->type = type;
- return e;
-
-} /* erl_alloc_eterm */
-
-#define EXTERNAL 1
-#define INTERNAL 0
-#define COMPOUND 1
-#define NOT_COMPOUND 0
-
-static void _erl_free_term (ETERM *ep, int external, int compound);
-
-/*
- * Free a term, but don't deallocate it until
- * the reference counter triggers.
- */
-void erl_free_term(ETERM *ep)
-{
- _erl_free_term(ep, EXTERNAL, NOT_COMPOUND);
-} /* erl_free_term */
-
-/*
- * Free a term regardless of its reference
- * counter value. Use this when you have
- * built compound terms such as lists or tuples.
- */
-
-/*
- * FIXME is this true?!
- * Tearing down term structures no-matter-what is a horrible idea if
- * any term happens to be shared (with some other structure or even
- * with yourself).
- */
-
-void erl_free_compound (ETERM *ep)
-{
- _erl_free_term(ep, EXTERNAL, COMPOUND);
-} /* erl_free_compound */
-
-
-/*
-** The actual free'ing is done here in _erl_free_term.
-** It is by nature recursive, but does not recurse
-** on the CDR of a list, which makes it usable for large lists.
-*/
-
-/*
-** Convenience macro, called for variables and lists,
-** avoids deep recursions.
-*/
-#define RESTART(Eterm, External, Compound) \
-do { \
- ETERM *sep; \
- sep = (Eterm); \
- external = (External); \
- compound = (Compound); \
- /* Clear header info */ \
- ERL_TYPE(ep) = ERL_UNDEF; \
- erl_eterm_free((unsigned int *) ep); \
- ep = sep; \
- goto restart; \
-} while(0)
-
-#define FREE_AND_CLEAR(ptr) \
-do { \
- erl_free(ptr); \
- (ptr) = NULL; \
-} while (0)
-
-static void erl_atom_free(Erl_Atom_data* p)
-{
- erl_free(p->latin1);
- if (p->utf8 != p->latin1) {
- erl_free(p->utf8);
- }
- p->latin1 = NULL;
- p->utf8 = NULL;
- p->lenL = 0;
- p->lenU = 0;
-}
-
-static void _erl_free_term (ETERM *ep, int external, int compound)
-{
-restart:
- if (ep == NULL)
- return;
- if (compound || ERL_NO_REF(ep)) {
- /* Yes, it's time to *really* free this one ! */
- switch(ERL_TYPE(ep))
- {
- case ERL_ATOM:
- erl_atom_free(&ep->uval.aval.d);
- break;
- case ERL_VARIABLE:
- FREE_AND_CLEAR(ERL_VAR_NAME(ep));
- /* Note: It may be unbound ! */
- if (ERL_VAR_VALUE(ep) != NULL) {
- ERL_COUNT(ERL_VAR_VALUE(ep))--;
- /* Cleanup and Restart with the actual value */
- RESTART(ERL_VAR_VALUE(ep), INTERNAL, compound);
- }
- break;
- case ERL_LIST:
- if (HEAD(ep)) {
- ERL_COUNT(HEAD(ep))--;
- /* FIXME added cast, is this correct? */
- _erl_free_term((ETERM *)HEAD(ep), INTERNAL, compound);
- }
- if (TAIL(ep)) {
- ERL_COUNT(TAIL(ep))--;
- /* Clean up and walk on to CDR in list */
- RESTART(TAIL(ep), INTERNAL, compound);
- }
- break;
- case ERL_TUPLE:
- {
- int i;
- for (i=0; i < ERL_TUPLE_SIZE(ep); i++)
- if (ERL_TUPLE_ELEMENT(ep, i)) {
- ERL_COUNT(ERL_TUPLE_ELEMENT(ep, i))--;
- _erl_free_term(ERL_TUPLE_ELEMENT(ep, i),
- INTERNAL, compound);
- }
- FREE_AND_CLEAR(ERL_TUPLE_ELEMS(ep));
- }
- break;
- case ERL_BINARY:
- FREE_AND_CLEAR(ERL_BIN_PTR(ep));
- break;
- case ERL_PID:
- erl_atom_free(&ep->uval.pidval.node);
- break;
- case ERL_PORT:
- erl_atom_free(&ep->uval.portval.node);
- break;
- case ERL_REF:
- erl_atom_free(&ep->uval.refval.node);
- break;
- case ERL_EMPTY_LIST:
- case ERL_INTEGER:
- case ERL_SMALL_BIG:
- case ERL_U_SMALL_BIG:
- case ERL_FLOAT:
- break;
- case ERL_FUNCTION:
- {
- int i;
-
- _erl_free_term(ERL_FUN_INDEX(ep), INTERNAL, compound);
- _erl_free_term(ERL_FUN_UNIQ(ep), INTERNAL, compound);
- _erl_free_term(ERL_FUN_CREATOR(ep), INTERNAL, compound);
- _erl_free_term(ERL_FUN_MODULE(ep), INTERNAL, compound);
- if (ERL_CLOSURE(ep) != NULL) {
- for (i = 0; i < ERL_CLOSURE_SIZE(ep); i++)
- _erl_free_term(ERL_CLOSURE_ELEMENT(ep,i),
- INTERNAL, compound);
- }
- }
- break;
- } /* switch */
-
- /* Clear header info for those cases where we are done */
- ERL_TYPE(ep) = ERL_UNDEF;
- erl_eterm_free(ep);
- } else if (external) {
- ERL_COUNT(ep)--;
- external = INTERNAL;
- goto restart;
- }
-} /* _erl_free_term */
-#undef RESTART
-#undef FREE_AND_CLEAR
-
-void erl_free_array(ETERM **arr, int size)
-{
- int i;
-
- for (i=0; i<size; i++)
- erl_free_term(arr[i]);
-
-} /* erl_free_array */
-
-
-void* erl_malloc (long size)
-{
- void *res;
-
- if ((res = ei_malloc(size)) == NULL)
- erl_err_sys("<ERROR> erl_malloc: Failed to allocate more memory");
-
- return res;
-}
-
-void* erl_realloc(void* orig, long size)
-{
- void *res;
-
- if ((res = ei_realloc(orig, size)) == NULL)
- erl_err_sys("<ERROR> erl_realloc: Failed to allocate more memory");
- return res;
-}
-
-void erl_free (void *ptr)
-{
- ei_free(ptr);
-}
diff --git a/lib/erl_interface/src/legacy/erl_malloc.h b/lib/erl_interface/src/legacy/erl_malloc.h
deleted file mode 100644
index 6cbc01faba..0000000000
--- a/lib/erl_interface/src/legacy/erl_malloc.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-#ifndef _ERL_MALLOC_H
-#define _ERL_MALLOC_H
-
-/* FIXME: not documented */
-void *erl_realloc(void*, long);
-int erl_current_fix_desc(void);
-
-#endif /* _ERL_MALLOC_H */
diff --git a/lib/erl_interface/src/legacy/erl_marshal.c b/lib/erl_interface/src/legacy/erl_marshal.c
deleted file mode 100644
index 932bba43bf..0000000000
--- a/lib/erl_interface/src/legacy/erl_marshal.c
+++ /dev/null
@@ -1,2267 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2018. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-/*
- * Purpose: Decoding and encoding Erlang terms.
- */
-#include "eidef.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <string.h>
-#include <limits.h>
-
-#include "erl_interface.h"
-#include "erl_marshal.h"
-#include "erl_eterm.h"
-#include "erl_malloc.h"
-#include "erl_error.h"
-#include "erl_internal.h"
-
-#include "eiext.h" /* replaces external.h */
-#include "putget.h"
-
-static int is_string(ETERM* term);
-#if defined(VXWORKS) && CPU == PPC860
-int erl_fp_compare(unsigned *a, unsigned *b);
-static void erl_long_to_fp(long l, unsigned *d);
-#endif
-
-static int cmpbytes(unsigned char* s1,int l1,unsigned char* s2,int l2);
-static int cmpatoms(unsigned char* s1, int l1, unsigned char tag1, unsigned char* s2, int l2, unsigned char tag2);
-
-/* Used when comparing two encoded byte arrays */
-/* this global data is ok (from threading point of view) since it is
- * initialized once and never changed
- */
-
-#define CMP_ARRAY_SIZE 256
-/* FIXME problem for threaded ? */
-
-static enum
-{
- ERL_NUM_CMP=1, ERL_ATOM_CMP, ERL_REF_CMP, ERL_FUN_CMP, ERL_PORT_CMP,
- ERL_PID_CMP, ERL_TUPLE_CMP, ERL_NIL_CMP, ERL_LIST_CMP, ERL_BIN_CMP
-}cmp_array[CMP_ARRAY_SIZE];
-
-static int init_cmp_array_p=1; /* initialize array, the first time */
-
-#if defined(VXWORKS) && CPU == PPC860
-#include <limits.h>
-#endif
-
-#if defined(__GNUC__)
-# define INLINE __inline__
-#elif defined(__WIN32__)
-# define INLINE __inline
-#else
-# define INLINE
-#endif
-
-static int cmp_floats(double f1, double f2);
-static INLINE double to_float(long l);
-
-#define IS_ERL_NUM(t) (cmp_array[t]==ERL_NUM_CMP)
-#define IS_ERL_ATOM(t) (cmp_array[t]==ERL_ATOM_CMP)
-
-#define CMP_NUM_CLASS_SIZE 256
-static unsigned char cmp_num_class[CMP_NUM_CLASS_SIZE];
-static int init_cmp_num_class_p=1; /* initialize array, the first time */
-
-#define MK_CMP_NUM_CODE(x,y) (((x)<<2)|(y))
-#define CMP_NUM_CLASS(x) (cmp_num_class[x] & 0x03)
-#define CMP_NUM_CODE(x,y) (MK_CMP_NUM_CODE(CMP_NUM_CLASS(x),CMP_NUM_CLASS(y)))
-
-#define SMALL 1
-#define FLOAT 2
-#define BIG 3
-
-#define SMALL_SMALL MK_CMP_NUM_CODE(SMALL,SMALL)
-#define SMALL_FLOAT MK_CMP_NUM_CODE(SMALL,FLOAT)
-#define SMALL_BIG MK_CMP_NUM_CODE(SMALL,BIG)
-#define FLOAT_SMALL MK_CMP_NUM_CODE(FLOAT,SMALL)
-#define FLOAT_FLOAT MK_CMP_NUM_CODE(FLOAT,FLOAT)
-#define FLOAT_BIG MK_CMP_NUM_CODE(FLOAT,BIG)
-#define BIG_SMALL MK_CMP_NUM_CODE(BIG,SMALL)
-#define BIG_FLOAT MK_CMP_NUM_CODE(BIG,FLOAT)
-#define BIG_BIG MK_CMP_NUM_CODE(BIG,BIG)
-
-void erl_init_marshal(void)
-{
- if (init_cmp_array_p) {
- memset(cmp_array, 0, sizeof cmp_array);
- cmp_array[ERL_SMALL_INTEGER_EXT] = ERL_NUM_CMP;
- cmp_array[ERL_INTEGER_EXT] = ERL_NUM_CMP;
- cmp_array[ERL_FLOAT_EXT] = ERL_NUM_CMP;
- cmp_array[NEW_FLOAT_EXT] = ERL_NUM_CMP;
- cmp_array[ERL_SMALL_BIG_EXT] = ERL_NUM_CMP;
- cmp_array[ERL_LARGE_BIG_EXT] = ERL_NUM_CMP;
- cmp_array[ERL_ATOM_EXT] = ERL_ATOM_CMP;
- cmp_array[ERL_ATOM_UTF8_EXT] = ERL_ATOM_CMP;
- cmp_array[ERL_SMALL_ATOM_EXT] = ERL_ATOM_CMP;
- cmp_array[ERL_SMALL_ATOM_UTF8_EXT] = ERL_ATOM_CMP;
- cmp_array[ERL_REFERENCE_EXT] = ERL_REF_CMP;
- cmp_array[ERL_NEW_REFERENCE_EXT] = ERL_REF_CMP;
- cmp_array[ERL_NEWER_REFERENCE_EXT]=ERL_REF_CMP;
- cmp_array[ERL_FUN_EXT] = ERL_FUN_CMP;
- cmp_array[ERL_NEW_FUN_EXT] = ERL_FUN_CMP;
- cmp_array[ERL_PORT_EXT] = ERL_PORT_CMP;
- cmp_array[ERL_NEW_PORT_EXT] = ERL_PORT_CMP;
- cmp_array[ERL_PID_EXT] = ERL_PID_CMP;
- cmp_array[ERL_NEW_PID_EXT] = ERL_PID_CMP;
- cmp_array[ERL_SMALL_TUPLE_EXT] = ERL_TUPLE_CMP;
- cmp_array[ERL_LARGE_TUPLE_EXT] = ERL_TUPLE_CMP;
- cmp_array[ERL_NIL_EXT] = ERL_NIL_CMP;
- cmp_array[ERL_STRING_EXT] = ERL_LIST_CMP;
- cmp_array[ERL_LIST_EXT] = ERL_LIST_CMP;
- cmp_array[ERL_BINARY_EXT] = ERL_BIN_CMP;
- init_cmp_array_p = 0;
- }
- if (init_cmp_num_class_p) {
- memset(cmp_num_class, 0, CMP_NUM_CLASS_SIZE);
- cmp_num_class[ERL_SMALL_INTEGER_EXT] = SMALL;
- cmp_num_class[ERL_INTEGER_EXT] = SMALL;
- cmp_num_class[ERL_FLOAT_EXT] = FLOAT;
- cmp_num_class[NEW_FLOAT_EXT] = FLOAT;
- cmp_num_class[ERL_SMALL_BIG_EXT] = BIG;
- cmp_num_class[ERL_LARGE_BIG_EXT] = BIG;
- init_cmp_num_class_p = 0;
- }
-}
-
-/* The encoder calls length, if erl_length() should return */
-/* -1 for dotted pairs (why !!!!) we can't use erl_length() */
-/* from the encoder in erl_marshal.c */
-
-static int erl_length_x(const ETERM *ep) {
- int n = 0;
-
- if (!ep) return -1;
-
- while (ERL_TYPE(ep) == ERL_LIST) {
- n++;
- ep = TAIL(ep);
- }
-
- return n;
-}
-
-
-/*==============================================================
- * Marshalling routines.
- *==============================================================
- */
-
-static void encode_atom(Erl_Atom_data* a, unsigned char **ext)
-{
- int ix = 0;
- if (a->latin1) {
- ei_encode_atom_len_as((char*)*ext, &ix, a->latin1, a->lenL,
- ERLANG_LATIN1, ERLANG_UTF8);
- }
- else {
- ei_encode_atom_len_as((char*)*ext, &ix, a->utf8, a->lenU,
- ERLANG_UTF8, ERLANG_UTF8);
- }
- *ext += ix;
-}
-
-/*
- * The actual ENCODE engine.
- * Returns 0 on success, otherwise 1.
- */
-int erl_encode_it(ETERM *ep, unsigned char **ext, int dist)
-{
- int i;
- unsigned int u;
- long long l;
- unsigned long long ul;
-
- switch(ERL_TYPE(ep))
- {
- case ERL_ATOM:
- encode_atom(&ep->uval.aval.d, ext);
- return 0;
-
- case ERL_INTEGER:
- i = ep->uval.ival.i;
- /* SMALL_INTEGER */
- if ((i < 256) && (i >= 0)) {
- *(*ext)++ = ERL_SMALL_INTEGER_EXT;
- *(*ext)++ = i & 0xff;
- return 0;
- }
- /* R14B: Use all 32 bits of INTEGER_EXT */
- *(*ext)++ = ERL_INTEGER_EXT;
- *(*ext)++ = (i >> 24) & 0xff;
- *(*ext)++ = (i >> 16) & 0xff;
- *(*ext)++ = (i >> 8) & 0xff;
- *(*ext)++ = i & 0xff;
- return 0;
-
- case ERL_U_INTEGER:
- u = ep->uval.uival.u;
- /* ERL_U_SMALL_BIG */
- if ((int)u < 0) {
- *(*ext)++ = ERL_SMALL_BIG_EXT;
- *(*ext)++ = 4; /* four bytes */
- *(*ext)++ = 0; /* sign byte */
- *(*ext)++ = u & 0xff; /* LSB first */
- *(*ext)++ = (u >> 8) & 0xff;
- *(*ext)++ = (u >> 16) & 0xff;
- *(*ext)++ = (u >> 24) & 0xff;
- return 0;
- }
- /* SMALL_INTEGER */
- if (u < 256) {
- *(*ext)++ = ERL_SMALL_INTEGER_EXT;
- *(*ext)++ = u & 0xff;
- return 0;
- }
- /* R14B: Use all 32 bits of INTEGER_EXT */
- *(*ext)++ = ERL_INTEGER_EXT;
- *(*ext)++ = (u >> 24) & 0xff;
- *(*ext)++ = (u >> 16) & 0xff;
- *(*ext)++ = (u >> 8) & 0xff;
- *(*ext)++ = u & 0xff;
- return 0;
- case ERL_LONGLONG:
- l = ep->uval.llval.i;
- /* ERL_SMALL_BIG */
- if (l > ((long long) INT_MAX) || l < ((long long) INT_MIN)) {
- *(*ext)++ = ERL_SMALL_BIG_EXT;
- *(*ext)++ = 8;
- if ((*(*ext)++ = (l<0))) /* sign byte */
- l = -l;
- *(*ext)++ = l & 0xff; /* LSB first */
- *(*ext)++ = (l >> 8) & 0xff;
- *(*ext)++ = (l >> 16) & 0xff;
- *(*ext)++ = (l >> 24) & 0xff;
- *(*ext)++ = (l >> 32) & 0xff;
- *(*ext)++ = (l >> 40) & 0xff;
- *(*ext)++ = (l >> 48) & 0xff;
- *(*ext)++ = (l >> 56) & 0xff;
- return 0;
- }
- /* SMALL_INTEGER */
- if ((l < 256) && (l >= 0)) {
- *(*ext)++ = ERL_SMALL_INTEGER_EXT;
- *(*ext)++ = l & 0xff;
- return 0;
- }
- /* R14B: Use all 32 bits of INTEGER_EXT */
- *(*ext)++ = ERL_INTEGER_EXT;
- *(*ext)++ = (l >> 24) & 0xff;
- *(*ext)++ = (l >> 16) & 0xff;
- *(*ext)++ = (l >> 8) & 0xff;
- *(*ext)++ = l & 0xff;
- return 0;
-
- case ERL_U_LONGLONG:
- ul = ep->uval.ullval.u;
- /* ERL_U_SMALL_BIG */
- if (ul > ((unsigned long long) INT_MAX)) {
- *(*ext)++ = ERL_SMALL_BIG_EXT;
- *(*ext)++ = 8; /* eight bytes */
- *(*ext)++ = 0; /* sign byte */
- *(*ext)++ = ul & 0xff; /* LSB first */
- *(*ext)++ = (ul >> 8) & 0xff;
- *(*ext)++ = (ul >> 16) & 0xff;
- *(*ext)++ = (ul >> 24) & 0xff;
- *(*ext)++ = (ul >> 32) & 0xff;
- *(*ext)++ = (ul >> 40) & 0xff;
- *(*ext)++ = (ul >> 48) & 0xff;
- *(*ext)++ = (ul >> 56) & 0xff;
- return 0;
- }
- /* SMALL_INTEGER */
- if (ul < 256) {
- *(*ext)++ = ERL_SMALL_INTEGER_EXT;
- *(*ext)++ = ul & 0xff;
- return 0;
- }
- /* R14B: Use all 32 bits of INTEGER_EXT */
- *(*ext)++ = ERL_INTEGER_EXT;
- *(*ext)++ = (ul >> 24) & 0xff;
- *(*ext)++ = (ul >> 16) & 0xff;
- *(*ext)++ = (ul >> 8) & 0xff;
- *(*ext)++ = ul & 0xff;
- return 0;
-
- case ERL_PID: {
- unsigned char* tagp = (*ext)++;
- /* First poke in node as an atom */
- encode_atom(&ep->uval.pidval.node, ext);
- /* And then fill in the integer fields */
- i = ERL_PID_NUMBER(ep);
- *(*ext)++ = (i >> 24) &0xff;
- *(*ext)++ = (i >> 16) &0xff;
- *(*ext)++ = (i >> 8) &0xff;
- *(*ext)++ = i &0xff;
- i = ERL_PID_SERIAL(ep);
- *(*ext)++ = (i >> 24) &0xff;
- *(*ext)++ = (i >> 16) &0xff;
- *(*ext)++ = (i >> 8) &0xff;
- *(*ext)++ = i &0xff;
-
- i = ERL_PID_CREATION(ep);
- if ((unsigned int)i <= 3) {
- *tagp = ERL_PID_EXT;
- *(*ext)++ = i;
- } else {
- *tagp = ERL_NEW_PID_EXT;
- *(*ext)++ = (i >> 24) &0xff;
- *(*ext)++ = (i >> 16) &0xff;
- *(*ext)++ = (i >> 8) &0xff;
- *(*ext)++ = i &0xff;
- }
- return 0;
- }
- case ERL_REF: {
- unsigned char* tagp = (*ext)++;
-
- int len, j;
-
- /* Always encode as an extended reference; all
- participating parties are now expected to be
- able to decode extended references. */
-
- i = strlen((char *)ERL_REF_NODE(ep));
- len = ERL_REF_LEN(ep);
- *(*ext)++ = (len >> 8) &0xff;
- *(*ext)++ = len &0xff;
-
- encode_atom(&ep->uval.refval.node, ext);
-
- i = ERL_REF_CREATION(ep);
- if ((unsigned int)i <= 3) {
- *tagp = ERL_NEW_REFERENCE_EXT;
- *(*ext)++ = i;
- } else {
- *tagp = ERL_NEWER_REFERENCE_EXT;
- *(*ext)++ = (i >> 24) &0xff;
- *(*ext)++ = (i >> 16) &0xff;
- *(*ext)++ = (i >> 8) &0xff;
- *(*ext)++ = i &0xff;
- }
-
- /* Then the integer fields */
- for (j = 0; j < ERL_REF_LEN(ep); j++) {
- i = ERL_REF_NUMBERS(ep)[j];
- *(*ext)++ = (i >> 24) &0xff;
- *(*ext)++ = (i >> 16) &0xff;
- *(*ext)++ = (i >> 8) &0xff;
- *(*ext)++ = i &0xff;
- }
- }
- return 0;
- case ERL_PORT: {
- unsigned char* tagp = (*ext)++;
- /* First poke in node as an atom */
- encode_atom(&ep->uval.portval.node, ext);
- /* Then the integer fields */
- i = ERL_PORT_NUMBER(ep);
- *(*ext)++ = (i >> 24) &0xff;
- *(*ext)++ = (i >> 16) &0xff;
- *(*ext)++ = (i >> 8) &0xff;
- *(*ext)++ = i &0xff;
-
- i = ERL_PORT_CREATION(ep);
- if ((unsigned int)i <= 3) {
- *tagp = ERL_PORT_EXT;
- *(*ext)++ = i;
- } else {
- *tagp = ERL_NEW_PORT_EXT;
- *(*ext)++ = (i >> 24) &0xff;
- *(*ext)++ = (i >> 16) &0xff;
- *(*ext)++ = (i >> 8) &0xff;
- *(*ext)++ = i &0xff;
- }
- return 0;
- }
- case ERL_EMPTY_LIST:
- *(*ext)++ = ERL_NIL_EXT;
- break;
- case ERL_LIST:
- i = is_string(ep);
- if (0 < i && i < 0x10000) { /* String. */
- *(*ext)++ = ERL_STRING_EXT;
- *(*ext)++ = (i >>8) &0xff;
- *(*ext)++ = i &0xff;
- while (ERL_TYPE(ep) == ERL_LIST) {
- *(*ext)++ = HEAD(ep)->uval.ival.i;
- ep = TAIL(ep);
- }
- break;
- } else { /* List. */
- i = erl_length_x(ep);
- *(*ext)++ = ERL_LIST_EXT;
- *(*ext)++ = (i >> 24) &0xff;
- *(*ext)++ = (i >> 16) &0xff;
- *(*ext)++ = (i >> 8) &0xff;
- *(*ext)++ = i &0xff;
- while (ERL_TYPE(ep) == ERL_LIST) {
- if (erl_encode_it(HEAD(ep), ext, dist))
- return 1;
- ep = TAIL(ep);
- }
- i = erl_encode_it(ep, ext, dist);
- return i;
- }
- case ERL_TUPLE:
- i = ep->uval.tval.size;
- if (i <= 0xff) {
- *(*ext)++ = ERL_SMALL_TUPLE_EXT;
- *(*ext)++ = i & 0xff;
- }
- else {
- *(*ext)++ = ERL_LARGE_TUPLE_EXT;
- *(*ext)++ = (i >> 24) & 0xff;
- *(*ext)++ = (i >> 16) & 0xff;
- *(*ext)++ = (i >> 8) & 0xff;
- *(*ext)++ = i & 0xff;
- }
- for (i=0; i<ep->uval.tval.size; i++)
- if (erl_encode_it(ep->uval.tval.elems[i], ext, dist))
- return 1;
- break;
- case ERL_FLOAT:
- *(*ext)++ = ERL_FLOAT_EXT;
- memset(*ext, 0, 31);
- sprintf((char *) *ext, "%.20e", ep->uval.fval.f);
- *ext += 31;
- break;
- case ERL_BINARY:
- *(*ext)++ = ERL_BINARY_EXT;
- i = ep->uval.bval.size;
- *(*ext)++ = (i >> 24) & 0xff;
- *(*ext)++ = (i >> 16) & 0xff;
- *(*ext)++ = (i >> 8) & 0xff;
- *(*ext)++ = i & 0xff;
- memcpy((char *) *ext, (char*) ep->uval.bval.b, i);
- *ext += i;
- break;
- case ERL_FUNCTION:
- if (ERL_FUN_ARITY(ep) != -1) {
- unsigned char *size_p = *ext + 1;
- *(*ext)++ = ERL_NEW_FUN_EXT;
- *ext += 4;
- i = ERL_FUN_ARITY(ep);
- put8(*ext, i);
- memcpy(*ext, ERL_FUN_MD5(ep), 16);
- *ext += 16;
- i = ERL_FUN_NEW_INDEX(ep);
- put32be(*ext, i);
- i = ERL_CLOSURE_SIZE(ep);
- put32be(*ext, i);
- erl_encode_it(ERL_FUN_MODULE(ep), ext, dist);
- erl_encode_it(ERL_FUN_INDEX(ep), ext, dist);
- erl_encode_it(ERL_FUN_UNIQ(ep), ext, dist);
- erl_encode_it(ERL_FUN_CREATOR(ep), ext, dist);
- for (i = 0; i < ERL_CLOSURE_SIZE(ep); i++)
- erl_encode_it(ep->uval.funcval.closure[i], ext, dist);
- if (size_p != NULL) {
- i = *ext - size_p;
- put32be(size_p, i);
- }
- } else {
- *(*ext)++ = ERL_FUN_EXT;
- i = ERL_CLOSURE_SIZE(ep);
- *(*ext)++ = (i >> 24) & 0xff;
- *(*ext)++ = (i >> 16) & 0xff;
- *(*ext)++ = (i >> 8) & 0xff;
- *(*ext)++ = i & 0xff;
- erl_encode_it(ERL_FUN_CREATOR(ep), ext, dist);
- erl_encode_it(ERL_FUN_MODULE(ep), ext, dist);
- erl_encode_it(ERL_FUN_INDEX(ep), ext, dist);
- erl_encode_it(ERL_FUN_UNIQ(ep), ext, dist);
- for (i = 0; i < ERL_CLOSURE_SIZE(ep); i++)
- erl_encode_it(ep->uval.funcval.closure[i], ext, dist);
- }
- break;
- default:
- return 1;
- }
- return 0;
-}
-
-/*
- * ENCODE an ETERM into a BUFFER, assuming BUFFER is of
- * enough size. At success return number of bytes written
- * into it, otherwise return 0.
- */
-static int erl_encode3(ETERM *ep, unsigned char *t, int dist)
-{
- unsigned char *x = t;
-
- *x++ = ERL_VERSION_MAGIC;
- if (erl_encode_it(ep, &x, dist)) {
-#ifdef DEBUG
- erl_err_msg("<ERROR> erl_encode: Error while encoding");
-#endif
- return 0;
- }
- return (x - t);
-
-}
-
-/* API */
-
-int erl_encode(ETERM *ep, unsigned char *t)
-{
- return erl_encode3(ep, t, 4);
-}
-
-/* determine the buffer size that will be required for the eterm */
-static int erl_term_len_helper(ETERM *ep, int dist);
-
-/* FIXME hard coded dist version */
-int erl_term_len(ETERM *ep)
-{
- return 1+erl_term_len_helper(ep, 4);
-}
-
-static int atom_len_helper(Erl_Atom_data* a)
-{
- (void) erl_atom_ptr_utf8(a);
- return 1 + 1 + (a->lenU > 255) + a->lenU;
-}
-
-static int erl_term_len_helper(ETERM *ep, int dist)
-{
- int len = 0;
- int i;
- unsigned int u;
- long long l;
- unsigned long long ul;
-
- if (ep) {
- switch (ERL_TYPE(ep)) {
- case ERL_ATOM:
- len = atom_len_helper(&ep->uval.aval.d);
- break;
-
- case ERL_INTEGER:
- i = ep->uval.ival.i;
- if ((i < 256) && (i >= 0)) len = 2;
- else len = 5;
- break;
-
- case ERL_U_INTEGER:
- u = ep->uval.uival.u;
- if ((int)u < 0) len = 7;
- else if (u < 256) len = 2;
- else len = 5;
- break;
-
- case ERL_LONGLONG:
- l = ep->uval.llval.i;
- if ((l > ((long long) INT_MAX)) ||
- (l < ((long long) INT_MIN))) len = 11;
- else if ((l < 256) && (l >= 0)) len = 2;
- else len = 5;
- break;
-
- case ERL_U_LONGLONG:
- ul = ep->uval.ullval.u;
- if (ul > ((unsigned long long) INT_MAX)) len = 11;
- else if (ul < 256) len = 2;
- else len = 5;
- break;
-
- case ERL_PID:
- len = 1 + atom_len_helper(&ep->uval.pidval.node) + 4 + 4 + 1;
- break;
-
- case ERL_REF:
- len = 1 + 2 + atom_len_helper(&ep->uval.refval.node) + 1 + ERL_REF_LEN(ep) * 4;
- break;
-
- case ERL_PORT:
- len = 1 + atom_len_helper(&ep->uval.portval.node) + 4 + 1;
- break;
-
- case ERL_EMPTY_LIST:
- len = 1;
- break;
-
- case ERL_LIST:
- i = is_string(ep);
- if ((i > 0) && (i < 0x10000)) { /* string: 3 + strlen */
- for (len = 3; ERL_TYPE(ep) == ERL_LIST; ep = TAIL(ep)) {
- len++;
- }
- }
- else { /* list: 5 + len(elem1) + len(elem2) ... */
- for (len = 5; ERL_TYPE(ep) == ERL_LIST; ep = TAIL(ep)) {
- len += erl_term_len_helper(HEAD(ep), dist);
- }
- len += erl_term_len_helper(ep, dist); /* last element */
- }
- break;
-
- case ERL_TUPLE:
- /* (2 or 5) + len(elem1) + len(elem2) ... */
- i = ep->uval.tval.size;
- if (i <= 0xff) len = 2;
- else len = 5;
-
- for (i=0; i<ep->uval.tval.size; i++) {
- len += erl_term_len_helper(ep->uval.tval.elems[i], dist);
- }
- break;
-
- case ERL_FLOAT:
- len = 32;
- break;
-
- case ERL_BINARY:
- i = ep->uval.bval.size;
- len = 5 + i;
- break;
-
- case ERL_FUNCTION:
- if (ERL_FUN_ARITY(ep) == -1) {
- len = 1 + 4;
- len += erl_term_len_helper(ERL_FUN_CREATOR(ep),dist);
- len += erl_term_len_helper(ERL_FUN_MODULE(ep),dist);
- len += erl_term_len_helper(ERL_FUN_INDEX(ep),dist);
- len += erl_term_len_helper(ERL_FUN_UNIQ(ep),dist);
- for (i = 0; i < ERL_CLOSURE_SIZE(ep); i++)
- len += erl_term_len_helper(ERL_CLOSURE_ELEMENT(ep,i), dist);
- } else {
- len = 1 + 4 + 16 + 4 + 4;
- len += erl_term_len_helper(ERL_FUN_MODULE(ep),dist);
- len += erl_term_len_helper(ERL_FUN_INDEX(ep),dist);
- len += erl_term_len_helper(ERL_FUN_UNIQ(ep),dist);
- len += erl_term_len_helper(ERL_FUN_CREATOR(ep),dist);
- for (i = 0; i < ERL_CLOSURE_SIZE(ep); i++)
- len += erl_term_len_helper(ERL_CLOSURE_ELEMENT(ep,i), dist);
- }
- break;
-
- default:
-#ifdef DEBUG
- fprintf(stderr, "Shouldn't happen: erl_term_len, unknown term type: '%c'\n",ERL_TYPE(ep));
-#endif
- erl_errno = EINVAL;
- exit(1);
- }
- }
-
- return len;
-}
-
-/*
- * This one makes it easy to ENCODE several CONSECUTIVE
- * ETERM's into the same buffer.
- */
-int erl_encode_buf(ETERM *ep, unsigned char **ext)
-{
- unsigned char *start=*ext;
-
- *(*ext)++ = ERL_VERSION_MAGIC;
- if (erl_encode_it(ep, ext, 0)) {
-#ifdef DEBUG
- erl_err_msg("<ERROR> erl_encode_buf: Error while encoding\n");
-#endif
- return 0;
- }
- return (*ext - start);
-
-} /* erl_encode_buf */
-
-
-static int read_atom(unsigned char** ext, Erl_Atom_data* a)
-{
- char buf[MAXATOMLEN_UTF8];
- int offs = 0;
- erlang_char_encoding enc;
- int ret = ei_decode_atom_as((char*)*ext, &offs, buf, MAXATOMLEN_UTF8,
- ERLANG_LATIN1|ERLANG_UTF8, NULL, &enc);
- *ext += offs;
-
- if (ret == 0) {
- int i = strlen(buf);
- char* clone = erl_malloc(i+1);
- memcpy(clone, buf, i+1);
-
- a->latin1 = NULL;
- a->lenL = 0;
- a->utf8 = NULL;
- a->lenU = 0;
- if (enc & (ERLANG_LATIN1 | ERLANG_ASCII)) {
- a->latin1 = clone;
- a->lenL = i;
- }
- if (enc & (ERLANG_UTF8 | ERLANG_ASCII)) {
- a->utf8 = clone;
- a->lenU = i;
- }
- }
- return ret;
-}
-
-/*
- * The actual DECODE engine.
- * Returns NULL in case of failure.
- */
-static ETERM *erl_decode_it(unsigned char **ext)
-{
- char *cp;
- ETERM *ep,*tp,*np;
- unsigned int u,sign;
- int i,j,arity;
- double ff;
- unsigned char tag;
-
- /* Assume we are going to decode an integer */
- ep = erl_alloc_eterm(ERL_INTEGER);
- ERL_COUNT(ep) = 1;
-
- tag = *(*ext)++;
- switch (tag)
- {
- case ERL_INTEGER_EXT:
- i = (int) (**ext << 24) | ((*ext)[1] << 16) |
- ((*ext)[2] << 8) | (*ext)[3];
- *ext += 4;
- ep->uval.ival.i = i;
- return ep;
-
- case ERL_SMALL_INTEGER_EXT:
- i = *(*ext)++;
- ep->uval.ival.i = i;
- return ep;
-
- /* NOTE: The arity below for bigs is not really the arity (= number of digits) */
- /* It is the byte count and this might cause problems in other parts... */
- case ERL_SMALL_BIG_EXT:
- arity = *(*ext)++;
- goto big_cont;
- case ERL_LARGE_BIG_EXT:
- arity = (**ext << 24) | ((*ext)[1])<< 16 |
- ((*ext)[2]) << 8 |((*ext)[3]);
- *ext += 4;
- big_cont:
-
-#ifdef _MSC_VER
-#define MAX_TO_NEGATE 0x8000000000000000Ui64
-#else
-#define MAX_TO_NEGATE 0x8000000000000000ULL
-#endif
-
- sign = *(*ext)++;
- if (arity > 8)
- goto big_truncate;
-
- if (arity == 4 && ((*ext)[3] & 0x80) && !sign) {
- /* It will fit into an unsigned int !! */
- u = (((*ext)[3] << 24)|((*ext)[2])<< 16|((*ext)[1]) << 8 |(**ext));
- ERL_TYPE(ep) = ERL_U_INTEGER;
- ep->uval.uival.u = u;
- /* *ext += i; */
- *ext += arity;
- return ep;
- } else if (arity == 4 && !((*ext)[3] & 0x80)) {
- /* It will fit into an int !!
- */
- i = (int) (((*ext)[3] << 24) | ((*ext)[2])<< 16 |
- ((*ext)[1]) << 8 | (**ext));
- if (sign) i = -i;
- ERL_TYPE(ep) = ERL_INTEGER;
- ep->uval.ival.i = i;
- *ext += arity;
- return ep;
- } else if (arity == 8 && ((*ext)[7] & 0x80) && !sign) {
- /* Fits in an unsigned long long */
- int x;
- unsigned long long ul = 0LL;
-
- for(x = 0 ; x < arity ; x++) {
- ul |= ((unsigned long long)(*ext)[x]) << ((unsigned long long)(8*x));
- }
-
- ERL_TYPE(ep) = ERL_U_LONGLONG;
- ep->uval.ullval.u = ul;
- *ext += arity;
- return ep;
- } else {
- /* Fits in a signed long long */
- int x;
- unsigned long long l = 0LL;
- long long sl;
-
- for(x = 0 ; x < arity ; x++) {
- l |= ((unsigned long long)(*ext)[x]) << ((unsigned long long)(8*x));
- }
-
- sl = (long long)l;
-
- if (sign && l != MAX_TO_NEGATE) {
- sl = -sl;
- if (sl > 0) goto big_truncate;
- }
-
- ERL_TYPE(ep) = ERL_LONGLONG;
- ep->uval.llval.i = sl;
- *ext += arity;
- return ep;
- }
-#undef MAX_TO_NEGATE
- big_truncate:
- /* truncate to: (+/-) 1 */
-#ifdef DEBUG
- erl_err_msg("<WARNING> erl_decode_it: Integer truncated...");
-#endif
- ERL_TYPE(ep) = ERL_INTEGER;
- ep->uval.ival.i = sign?-1:1;
- *ext += arity;
- return ep;
-
- case ERL_ATOM_EXT:
- case ERL_SMALL_ATOM_EXT:
- case ERL_ATOM_UTF8_EXT:
- case ERL_SMALL_ATOM_UTF8_EXT:
-
- ERL_TYPE(ep) = ERL_ATOM;
- --(*ext);
- if (read_atom(ext, &ep->uval.aval.d) < 0) return NULL;
- return ep;
-
- case ERL_PID_EXT:
- case ERL_NEW_PID_EXT:
- {
- unsigned int number, serial;
- unsigned int creation;
-
- ERL_TYPE(ep) = ERL_PID;
- if (read_atom(ext, &ep->uval.pidval.node) < 0) return NULL;
-
- /* get the integers */
- number = ((*ext)[0] << 24) | ((*ext)[1]) << 16 |
- ((*ext)[2]) << 8 | ((*ext)[3]);
- *ext += 4;
- serial = ((*ext)[0] << 24) | ((*ext)[1]) << 16 |
- ((*ext)[2]) << 8 | ((*ext)[3]);
- *ext += 4;
- if (tag == ERL_PID_EXT)
- creation = *(*ext)++;
- else {
- creation = ((*ext)[0] << 24) | ((*ext)[1]) << 16 |
- ((*ext)[2]) << 8 | ((*ext)[3]);
- *ext += 4;
- }
- erl_mk_pid_helper(ep, number, serial, creation);
- return ep;
- }
- case ERL_REFERENCE_EXT:
- {
- unsigned int n[3] = {0, 0, 0};
- unsigned char creation;
-
- ERL_TYPE(ep) = ERL_REF;
- if (read_atom(ext, &ep->uval.refval.node) < 0) return NULL;
-
- /* get the integers */
- n[0] = ((*ext)[0] << 24) | ((*ext)[1]) << 16 |
- ((*ext)[2]) << 8 | ((*ext)[3]);
- *ext += 4;
- creation = *(*ext)++;
- __erl_mk_reference(ep, NULL, 1, n, creation);
- return ep;
- }
-
- case ERL_NEW_REFERENCE_EXT:
- case ERL_NEWER_REFERENCE_EXT:
- {
- size_t cnt, i;
- unsigned int n[3];
- unsigned int creation;
-
- ERL_TYPE(ep) = ERL_REF;
- cnt = ((*ext)[0] << 8) | (*ext)[1];
- *ext += 2;
-
- if (read_atom(ext, &ep->uval.refval.node) < 0) return NULL;
-
- /* get the integers */
- if (tag == ERL_NEW_REFERENCE_EXT)
- creation = *(*ext)++;
- else {
- creation = ((*ext)[0] << 24) | ((*ext)[1]) << 16 |
- ((*ext)[2]) << 8 | ((*ext)[3]);
- *ext += 4;
- }
- for(i = 0; i < cnt; i++)
- {
- n[i] = ((*ext)[0] << 24) | ((*ext)[1]) << 16 |
- ((*ext)[2]) << 8 | ((*ext)[3]);
- *ext += 4;
- }
- __erl_mk_reference(ep, NULL, cnt, n, creation);
- return ep;
- }
-
- case ERL_PORT_EXT:
- case ERL_NEW_PORT_EXT:
- {
- unsigned int number;
- unsigned int creation;
-
- ERL_TYPE(ep) = ERL_PORT;
- if (read_atom(ext, &ep->uval.portval.node) < 0) return NULL;
-
- /* get the integers */
- number = ((*ext)[0] << 24) | ((*ext)[1]) << 16 |
- ((*ext)[2]) << 8 | ((*ext)[3]);
- *ext += 4;
- if (tag == ERL_PORT_EXT)
- creation = *(*ext)++;
- else {
- creation = (((*ext)[0] << 24) | ((*ext)[1]) << 16 |
- ((*ext)[2]) << 8 | ((*ext)[3]));
- *ext += 4;
- }
- erl_mk_port_helper(ep, number, creation);
- return ep;
- }
-
- case ERL_NIL_EXT:
- ERL_TYPE(ep) = ERL_EMPTY_LIST;
- return ep;
-
- case ERL_LIST_EXT:
- ERL_TYPE(ep) = ERL_LIST;
- i = (**ext << 24) | ((*ext)[1] << 16) |((*ext)[2] << 8) | (*ext)[3];
- *ext += 4;
- /* ASSERT(i != 0); */ /* Should be represented by ERL_NIL_EXT. */
- tp = ep;
- for (j = 0; j < i; j++)
- if ((HEAD(tp) = erl_decode_it(ext)) == NULL)
- goto failure;
- else if (j + 1 < i) {
- /* We have to watch out for how we allocates the
- * last tail element since we may encounter non-
- * well formed lists.
- */
- np = erl_alloc_eterm(ERL_LIST);
- ERL_COUNT(np) = 1;
- TAIL(np) = NULL; /* in case of failure */
- TAIL(tp) = np;
- tp = np;
- }
- if ((TAIL(tp) = erl_decode_it(ext)) == NULL)
- goto failure;
- return ep;
-
- case ERL_STRING_EXT:
- {
- unsigned char* s;
-
- ERL_TYPE(ep) = ERL_EMPTY_LIST;
- i = (**ext << 8) | ((*ext)[1]);
- *ext += 2;
- s = *ext+i;
-
- while (*ext < s) {
- ETERM* integer;
- ETERM* cons;
-
- integer = erl_alloc_eterm(ERL_INTEGER);
- ERL_COUNT(integer) = 1;
- integer->uval.ival.i = *--s;
-
- cons = erl_alloc_eterm(ERL_LIST);
- ERL_COUNT(cons) = 1;
- HEAD(cons) = integer;
- TAIL(cons) = ep;
- ep = cons;
- }
- *ext += i;
- return ep;
- }
-
- case ERL_SMALL_TUPLE_EXT:
- ERL_TYPE(ep) = ERL_TUPLE;
- i = *(*ext)++;
- goto decode_tuple;
-
- case ERL_LARGE_TUPLE_EXT:
- i = (**ext << 24) | ((*ext)[1]) << 16 |
- ((*ext)[2]) << 8 | ((*ext)[3]) ;
- *ext += 4;
- decode_tuple:
- ep->uval.tval.size = i;
- j = (i + 1) * sizeof(ETERM*);
- ep->uval.tval.elems = (ETERM**) erl_malloc(j);
- memset(ep->uval.tval.elems, 0, j); /* in case of failure below... */
- for (i=0; i<ep->uval.tval.size; i++)
- if ((tp = erl_decode_it(ext)) == NULL)
- goto failure;
- else
- ep->uval.tval.elems[i] = tp;
- return ep;
-
- case ERL_FLOAT_EXT:
- case NEW_FLOAT_EXT:
- ERL_TYPE(ep) = ERL_FLOAT;
- cp = (char *) *ext;
- i = -1;
- if (ei_decode_double(cp, &i, &ff) == -1)
- goto failure;
- *ext += i;
- ep->uval.fval.f = ff;
- return ep;
-
- case ERL_BINARY_EXT:
- ERL_TYPE(ep) = ERL_BINARY;
- i = (**ext << 24) | ((*ext)[1] << 16) |
- ((*ext)[2] << 8) | (*ext)[3];
- *ext += 4;
- ep->uval.bval.size = i;
- ep->uval.bval.b = (unsigned char *) erl_malloc(i);
- memcpy(ep->uval.bval.b, *ext, i);
- *ext += i;
- return ep;
-
- case ERL_FUN_EXT: /* FIXME: error checking */
- ERL_TYPE(ep) = ERL_FUNCTION;
- i = get32be(*ext);
- /*i = *(**ext << 24) | ((*ext)[1] << 16) | ((*ext)[2] << 8) | (*ext)[3];
- *ext += 4; */
- ERL_FUN_ARITY(ep) = -1;
- ERL_CLOSURE_SIZE(ep) = i;
- ERL_FUN_CREATOR(ep) = erl_decode_it(ext);
- ERL_FUN_MODULE(ep) = erl_decode_it(ext);
- ERL_FUN_INDEX(ep) = erl_decode_it(ext);
- ERL_FUN_UNIQ(ep) = erl_decode_it(ext);
- j = i * sizeof(ETERM*);
- ERL_CLOSURE(ep) = (ETERM**) erl_malloc(j);
- memset(ERL_CLOSURE(ep), 0, j);
- for (i = 0; i < ERL_CLOSURE_SIZE(ep); i++)
- ERL_CLOSURE_ELEMENT(ep,i) = erl_decode_it(ext);
- return ep;
-
- case ERL_NEW_FUN_EXT: /* FIXME: error checking */
- ERL_TYPE(ep) = ERL_FUNCTION;
- i = get32be(*ext); /* size, we don't use it here */
- ERL_FUN_ARITY(ep) = get8(*ext);
- memcpy(ERL_FUN_MD5(ep), *ext, 16);
- *ext += 16;
- ERL_FUN_NEW_INDEX(ep) = get32be(*ext);
- i = get32be(*ext);
- ERL_CLOSURE_SIZE(ep) = i;
- ERL_FUN_MODULE(ep) = erl_decode_it(ext);
- ERL_FUN_INDEX(ep) = erl_decode_it(ext);
- ERL_FUN_UNIQ(ep) = erl_decode_it(ext);
- ERL_FUN_CREATOR(ep) = erl_decode_it(ext);
- j = i * sizeof(ETERM*);
- ERL_CLOSURE(ep) = (ETERM**) erl_malloc(j);
- memset(ERL_CLOSURE(ep), 0, j);
- for (i = 0; i < ERL_CLOSURE_SIZE(ep); i++)
- ERL_CLOSURE_ELEMENT(ep,i) = erl_decode_it(ext);
- return ep;
-
- } /* switch */
-
- failure:
- erl_free_term(ep);
- return (ETERM *) NULL;
-
-} /* erl_decode_it */
-
-/*
- * DECODE a buffer of BYTES into an ETERM.
- * Returns NULL in case of failure.
- */
-ETERM *erl_decode(unsigned char *t)
-{
- ETERM *ep;
- unsigned char *ext;
-
- ext = t;
-
- /* We ignore the version magic since it might be
- * possible that the buffer has been manipulated
- * with erl_peek_ext.
- */
- if (*ext == ERL_VERSION_MAGIC)
- ext++;
-
- ep = NULL;
- ep = erl_decode_it(&ext);
-#ifdef DEBUG
- if (!ep) erl_err_msg("<ERROR> erl_decode: Error while decoding");
-#endif
- return ep;
-
-} /* erl_decode */
-
-/*
- * This one makes it possible to DECODE two CONSECUTIVE
- * ETERM's in the same buffer.
- */
-ETERM *erl_decode_buf(unsigned char **ext)
-{
- ETERM *ep;
-
- /* We ignore the version magic since it might be
- * possible that the buffer has been manipulated
- * with erl_peek_ext.
- */
- if (**ext == ERL_VERSION_MAGIC)
- (*ext)++;
-
- ep = NULL;
- ep = erl_decode_it(ext);
-#ifdef DEBUG
- if (!ep) erl_err_msg("<ERROR> erl_decode_buf: Error while decoding");
-#endif
- return ep;
-
-} /* erl_decode_buf */
-
-
-/*==============================================================
- * Ok, here comes routines for inspecting/manipulating
- * an encoded buffer of bytes.
- *==============================================================
- */
-
-/*
- * Return 1 if the VERSION MAGIC in the BUFFER is the
- * same as the this library version.
- */
-int erl_verify_magic(unsigned char *ext)
-{
-
- if (*ext == ERL_VERSION_MAGIC)
- return 1;
- else
- return 0;
-
-} /* erl_verify_magic */
-
-/*
- * Return the TYPE of an ENCODED ETERM.
- * At failure, return 0.
- */
-unsigned char erl_ext_type(unsigned char *ext)
-{
- /* FIXME old code could skip multiple magic */
-
- /* Move over magic number if any */
- if (*ext == ERL_VERSION_MAGIC) ext++;
-
- switch (*ext) {
- case ERL_SMALL_INTEGER_EXT:
- case ERL_INTEGER_EXT:
- return ERL_INTEGER;
- case ERL_ATOM_EXT:
- case ERL_ATOM_UTF8_EXT:
- case ERL_SMALL_ATOM_EXT:
- case ERL_SMALL_ATOM_UTF8_EXT:
- return ERL_ATOM;
- case ERL_PID_EXT:
- case ERL_NEW_PID_EXT:
- return ERL_PID;
- case ERL_PORT_EXT:
- case ERL_NEW_PORT_EXT:
- return ERL_PORT;
- case ERL_REFERENCE_EXT:
- case ERL_NEW_REFERENCE_EXT:
- case ERL_NEWER_REFERENCE_EXT:
- return ERL_REF;
- case ERL_NIL_EXT:
- return ERL_EMPTY_LIST;
- case ERL_LIST_EXT:
- return ERL_LIST;
- case ERL_SMALL_TUPLE_EXT:
- case ERL_LARGE_TUPLE_EXT:
- return ERL_TUPLE;
- case ERL_FLOAT_EXT:
- case NEW_FLOAT_EXT:
- return ERL_FLOAT;
- case ERL_BINARY_EXT:
- return ERL_BINARY;
- case ERL_FUN_EXT:
- case ERL_NEW_FUN_EXT:
- return ERL_FUNCTION;
- case ERL_SMALL_BIG_EXT:
- case ERL_LARGE_BIG_EXT:
- return ERL_BIG;
- default:
- return 0;
-
- } /* switch */
-
-} /* erl_ext_type */
-
-/*
- * Returns the number of elements in compund
- * terms. For other kind of terms zero is returned.
- * At failure -1 is returned.
- */
-int erl_ext_size(unsigned char *t)
-{
- int i;
- unsigned char *v;
-
- if (*t == ERL_VERSION_MAGIC)
- return erl_ext_size(t+1);
-
- v = t+1;
- switch(*t) {
- case ERL_SMALL_INTEGER_EXT:
- case ERL_INTEGER_EXT:
- case ERL_ATOM_EXT:
- case ERL_ATOM_UTF8_EXT:
- case ERL_SMALL_ATOM_EXT:
- case ERL_SMALL_ATOM_UTF8_EXT:
- case ERL_PID_EXT:
- case ERL_NEW_PID_EXT:
- case ERL_PORT_EXT:
- case ERL_NEW_PORT_EXT:
- case ERL_REFERENCE_EXT:
- case ERL_NEW_REFERENCE_EXT:
- case ERL_NEWER_REFERENCE_EXT:
- case ERL_NIL_EXT:
- case ERL_BINARY_EXT:
- case ERL_STRING_EXT:
- case ERL_FLOAT_EXT:
- case NEW_FLOAT_EXT:
- case ERL_SMALL_BIG_EXT:
- case ERL_LARGE_BIG_EXT:
- return 0;
- break;
- case ERL_SMALL_TUPLE_EXT:
- i = v[0];
- return i;
- break;
- case ERL_LIST_EXT:
- case ERL_LARGE_TUPLE_EXT:
- i = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | v[3];
- return i;
- break;
- case ERL_FUN_EXT:
- i = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | v[3];
- return i+4;
- break;
- case ERL_NEW_FUN_EXT:
- v += 4 + 1 + 16 + 4;
- i = get32be(v);
- return i + 4;
- break;
- default:
- return -1;
- break;
- } /* switch */
-
-} /* ext_size */
-
-
-static int jump_atom(unsigned char** ext)
-{
- unsigned char* e = *ext;
- int len;
-
- switch (*e++) {
- case ERL_ATOM_EXT:
- case ERL_ATOM_UTF8_EXT:
- len = (e[0] << 8) | e[1];
- e += (len + 2);
- break;
-
- case ERL_SMALL_ATOM_EXT:
- case ERL_SMALL_ATOM_UTF8_EXT:
- len = e[0];
- e += (len + 1);
- break;
-
- default:
- return 0;
- }
- *ext = e;
- return 1;
-}
-
-
-/*
- * MOVE the POINTER PAST the ENCODED ETERM we
- * are currently pointing at. Returns 1 at
- * success, otherwise 0.
- */
-static int jump(unsigned char **ext)
-{
- int j,k,i=0;
- int n;
- const int tag = *(*ext)++;
-
- switch (tag) {
- case ERL_VERSION_MAGIC:
- return jump(ext);
- case ERL_INTEGER_EXT:
- *ext += 4;
- break;
- case ERL_SMALL_INTEGER_EXT:
- *ext += 1;
- break;
- case ERL_ATOM_EXT:
- case ERL_ATOM_UTF8_EXT:
- case ERL_SMALL_ATOM_EXT:
- case ERL_SMALL_ATOM_UTF8_EXT:
- jump_atom(ext);
- break;
- case ERL_PID_EXT:
- if (!jump_atom(ext)) return 0;
- *ext += 4 + 4 + 1;
- break;
- case ERL_NEW_PID_EXT:
- if (!jump_atom(ext)) return 0;
- *ext += 4 + 4 + 4;
- break;
- case ERL_REFERENCE_EXT:
- case ERL_PORT_EXT:
- if (!jump_atom(ext)) return 0;
- *ext += 4 + 1;
- break;
- case ERL_NEW_PORT_EXT:
- if (!jump_atom(ext)) return 0;
- *ext += 4 + 4;
- break;
- case ERL_NEW_REFERENCE_EXT:
- case ERL_NEWER_REFERENCE_EXT:
- n = (**ext << 8) | (*ext)[1];
- *ext += 2;
- /* first field is an atom */
- if (!jump_atom(ext)) return 0;
- *ext += 4*n + (tag == ERL_NEW_REFERENCE_EXT ? 1 : 4);
- break;
- case ERL_NIL_EXT:
- /* We just passed it... */
- break;
- case ERL_LIST_EXT:
- i = j = 0;
- j = (**ext << 24) | ((*ext)[1] << 16) |((*ext)[2] << 8) | (*ext)[3];
- *ext += 4;
- for(k=0; k<j; k++)
- if ((i = jump(ext)) == 0)
- return(0);
- if (**ext == ERL_NIL_EXT) {
- *ext += 1;
- break;
- }
- if (jump(ext) == 0) return 0;
- break;
- case ERL_STRING_EXT:
- i = **ext << 8 | (*ext)[1];
- *ext += 2 + i;
- break;
- case ERL_SMALL_TUPLE_EXT:
- i = *(*ext)++;
- goto jump_tuple;
- case ERL_LARGE_TUPLE_EXT:
- i = (**ext << 24) | ((*ext)[1] << 16) |((*ext)[2] << 8) | (*ext)[3];
- *ext += 4;
- jump_tuple:
- for (j = 0; j < i; j++)
- if ((k = jump(ext)) == 0)
- return(0);
- break;
- case ERL_FLOAT_EXT:
- *ext += 31;
- break;
- case NEW_FLOAT_EXT:
- *ext += 8;
- break;
- case ERL_BINARY_EXT:
- i = (**ext << 24) | ((*ext)[1] << 16) |((*ext)[2] << 8) | (*ext)[3];
- *ext += 4+i;
- break;
- case ERL_FUN_EXT:
- i = (**ext << 24) | ((*ext)[1] << 16) |((*ext)[2] << 8) | (*ext)[3];
- *ext += 4;
- i += 4;
- for (j = 0; j < i; j++)
- if ((k = jump(ext)) == 0)
- return(0);
- break;
- case ERL_NEW_FUN_EXT:
- i = get32be(*ext);
- *ext += i + 4;
- break;
- case ERL_SMALL_BIG_EXT:
- i = *(*ext);
- *ext += i + 1;
- break;
- case ERL_LARGE_BIG_EXT:
- i = get32be(*ext);
- *ext += i + 4;
- break;
- default:
- return 0;
- } /* switch */
-
- return 1;
-
-} /* jump */
-
-/*
- * The actual PEEK engine.
- */
-static unsigned char *peek_ext(unsigned char **ext, int jumps)
-{
- int i;
-
- switch (*(*ext)++)
- {
- case ERL_VERSION_MAGIC:
- return peek_ext(ext, jumps);
- case ERL_SMALL_TUPLE_EXT:
- i = *(*ext)++;
- goto do_the_peek_stuff;
- case ERL_LARGE_TUPLE_EXT:
- case ERL_LIST_EXT:
- i = (**ext << 24) | ((*ext)[1]) << 16| ((*ext)[2]) << 8| ((*ext)[3]) ;
- *ext += 4;
- do_the_peek_stuff:
- if (i <= jumps) {
-#ifdef DEBUG
- erl_err_msg("<ERROR> peek_ext: Out of range");
-#endif
- return NULL;
- }
- for(i=0; i<jumps; i++)
- if (!jump(ext)) {
-#ifdef DEBUG
- erl_err_msg("<ERROR> peek_ext: Bad data");
-#endif
- return NULL;
- }
- return *ext;
- default:
-#ifdef DEBUG
- erl_err_msg("<ERROR> peek_ext: Can't peek in non list/tuple type");
-#endif
- return NULL;
- } /* switch */
-
-} /* peek_ext */
-
-/*
- * Return a POINTER TO the N:TH ELEMENT in a
- * COMPUND ENCODED ETERM.
- */
-unsigned char *erl_peek_ext(unsigned char *ext, int jumps)
-{
- unsigned char *x=ext;
-
- return peek_ext(&x, jumps);
-
-} /* erl_peek_ext */
-
-/*
- * Lexically compare two strings of bytes,
- * (string s1 length l1 and s2 l2).
- * Return: -1 if s1 < s2
- * 0 if s1 = s2
- * 1 if s1 > s2
- */
-static int cmpbytes(unsigned char* s1,int l1,unsigned char* s2,int l2)
-{
- int i;
- i = 0;
- while((i < l1) && (i < l2)) {
- if (s1[i] < s2[i]) return(-1);
- if (s1[i] > s2[i]) return(1);
- i++;
- }
- if (l1 < l2) return(-1);
- if (l1 > l2) return(1);
- return(0);
-
-} /* cmpbytes */
-
-#define tag2enc(T) ((T)==ERL_ATOM_EXT || (T)==ERL_SMALL_ATOM_EXT ? ERLANG_LATIN1 : ERLANG_UTF8)
-
-static int cmpatoms(unsigned char* s1, int l1, unsigned char tag1,
- unsigned char* s2, int l2, unsigned char tag2)
-{
- erlang_char_encoding enc1 = tag2enc(tag1);
- erlang_char_encoding enc2 = tag2enc(tag2);
-
- if (enc1 == enc2) {
- return cmpbytes(s1, l1,s2,l2);
- }
-
- if (enc1 == ERLANG_LATIN1) {
- return cmp_latin1_vs_utf8((char*)s1, l1, (char*)s2, l2);
- }
- else {
- return -cmp_latin1_vs_utf8((char*)s2, l2, (char*)s1, l1);
- }
-}
-
-int cmp_latin1_vs_utf8(const char* strL, int lenL, const char* strU, int lenU)
-{
- unsigned char* sL = (unsigned char*)strL;
- unsigned char* sU = (unsigned char*)strU;
- unsigned char* sL_end = sL + lenL;
- unsigned char* sU_end = sU + lenU;
-
- while(sL < sL_end && sU < sU_end) {
- unsigned char UasL;
- if (*sL >= 0x80) {
- if (*sU < 0xC4 && (sU+1) < sU_end) {
- UasL = ((sU[0] & 0x3) << 6) | (sU[1] & 0x3F);
- }
- else return -1;
- }
- else {
- UasL = *sU;
- }
- if (*sL < UasL) return -1;
- if (*sL > UasL) return 1;
-
- sL++;
- if (*sU < 0x80) sU++;
- else if (*sU < 0xE0) sU += 2;
- else if (*sU < 0xF0) sU += 3;
- else /*if (*sU < 0xF8)*/ sU += 4;
- }
-
- return (sU >= sU_end) - (sL >= sL_end); /* -1, 0 or 1 */
-}
-
-
-#define CMP_EXT_ERROR_CODE 4711
-
-#define CMP_EXT_INT32_BE(AP, BP) \
-do { \
- if ((AP)[0] != (BP)[0]) return (AP)[0] < (BP)[0] ? -1 : 1; \
- if ((AP)[1] != (BP)[1]) return (AP)[1] < (BP)[1] ? -1 : 1; \
- if ((AP)[2] != (BP)[2]) return (AP)[2] < (BP)[2] ? -1 : 1; \
- if ((AP)[3] != (BP)[3]) return (AP)[3] < (BP)[3] ? -1 : 1; \
-} while (0)
-
-#define CMP_EXT_SKIP_ATOM(EP) \
-do { \
- if (!jump_atom(&(EP))) \
- return CMP_EXT_ERROR_CODE; \
-} while (0)
-
-/*
- * We now know that both byte arrays are of the same type.
- */
-static int compare_top_ext(unsigned char**, unsigned char **); /* forward */
-static int cmp_exe2(unsigned char **e1, unsigned char **e2);
-
-static int cmp_refs(unsigned char **e1, unsigned char **e2)
-{
- int tmp, n1, n2;
- unsigned char *node1, *node2, *id1, *id2, cre1, cre2;
-
- if (*((*e1)++) == ERL_REFERENCE_EXT) {
- node1 = *e1;
- CMP_EXT_SKIP_ATOM(*e1);
- n1 = 1;
- id1 = *e1;
- cre1 = (*e1)[4];
- *e1 += 5;
- } else {
- n1 = get16be(*e1);
- node1 = *e1;
- CMP_EXT_SKIP_ATOM(*e1);
- cre1 = **e1;
- id1 = (*e1) + 1 + (n1 - 1)*4;
- *e1 = id1 + 4;
- }
-
- if (*((*e2)++) == ERL_REFERENCE_EXT) {
- node2 = *e2;
- CMP_EXT_SKIP_ATOM(*e2);
- n2 = 1;
- id2 = *e2;
- cre2 = (*e2)[4];
- *e2 += 5;
- } else {
- n2 = get16be(*e2);
- node2 = *e2;
- CMP_EXT_SKIP_ATOM(*e2);
- cre2 = **e2;
- id2 = (*e2) + 1 + (n2 - 1)*4;
- *e2 = id2 + 4;
- }
-
- /* First compare node names... */
- tmp = cmp_exe2(&node1, &node2);
- if (tmp != 0)
- return tmp;
-
- /* ... then creations ... */
- if (cre1 != cre2)
- return cre1 < cre2 ? -1 : 1;
-
- /* ... and then finally ids. */
- if (n1 != n2) {
- unsigned char zero[] = {0, 0, 0, 0};
- if (n1 > n2)
- do {
- CMP_EXT_INT32_BE(id1, zero);
- id1 -= 4;
- n1--;
- } while (n1 > n2);
- else
- do {
- CMP_EXT_INT32_BE(zero, id2);
- id2 -= 4;
- n2--;
- } while (n2 > n1);
- }
-
- for (; n1 > 0; n1--, id1 -= 4, id2 -= 4)
- CMP_EXT_INT32_BE(id1, id2);
-
- return 0;
-}
-
-static int cmp_string_list(unsigned char **e1, unsigned char **e2) {
-
- /* we need to compare a string in **e1 and a list in **e2 */
- /* convert the string to list representation and convert that with e2 */
- /* we need a temporary buffer of: */
- /* 5 (list tag + length) + 2*string length + 1 (end of list tag) */
- /* for short lists we use a stack allocated buffer, otherwise we malloc */
-
- unsigned char *bp;
- unsigned char buf[5+2*255+1]; /* used for short lists */
- int i,e1_len;
- int res;
-
- e1_len = ((*e1)[1] << 8) | ((*e1)[2]);
- if ( e1_len < 256 ) {
- bp = buf;
- } else {
- bp = erl_malloc(5+(2*e1_len)+1);
- }
-
- bp[0] = ERL_LIST_EXT;
- bp[1] = bp[2] = 0;
- bp[3] = (*e1)[1];
- bp[4] = (*e1)[2];
-
- for(i=0;i<e1_len;i++) {
- bp[5+2*i] = ERL_SMALL_INTEGER_EXT;
- bp[5+2*i+1] = (*e1)[3+i];
- }
-
- bp[5+2*e1_len] = ERL_NIL_EXT;
-
- res = cmp_exe2(&bp, e2);
-
- if ( e1_len >= 256 ) free(bp);
-
- return res;
-}
-
-static int cmp_exe2(unsigned char **e1, unsigned char **e2)
-{
- int min, ret,i,j,k;
- double ff1, ff2;
- unsigned char tag1, tag2;
-
- if ( ((*e1)[0] == ERL_STRING_EXT) && ((*e2)[0] == ERL_LIST_EXT) ) {
- return cmp_string_list(e1, e2);
- } else if ( ((*e1)[0] == ERL_LIST_EXT) && ((*e2)[0] == ERL_STRING_EXT) ) {
- return -cmp_string_list(e2, e1);
- }
-
- tag1 = *(*e1)++;
- tag2 = *(*e2)++;
- i = j = 0;
- switch (tag1)
- {
- case ERL_SMALL_INTEGER_EXT:
- if (**e1 < **e2) ret = -1;
- else if (**e1 > **e2) ret = 1;
- else ret = 0;
- *e1 += 1; *e2 += 1;
- return ret;
- case ERL_INTEGER_EXT:
- i = (int) (**e1 << 24) | ((*e1)[1] << 16) |((*e1)[2] << 8) | (*e1)[3];
- j = (int) (**e2 << 24) | ((*e2)[1] << 16) |((*e2)[2] << 8) | (*e2)[3];
- if ( i < j)
- ret = -1;
- else if ( i > j)
- ret = 1;
- else
- ret = 0;
- *e1 += 4; *e2 += 4;
- return ret;
- case ERL_ATOM_EXT:
- case ERL_ATOM_UTF8_EXT:
- i = (**e1) << 8; (*e1)++;
- j = (**e2) << 8; (*e2)++;
- /*fall through*/
- case ERL_SMALL_ATOM_EXT:
- case ERL_SMALL_ATOM_UTF8_EXT:
- i |= (**e1); (*e1)++;
- j |= (**e2); (*e2)++;
- ret = cmpatoms(*e1, i, tag1, *e2, j, tag2);
- *e1 += i;
- *e2 += j;
- return ret;
- case ERL_PID_EXT:
- case ERL_NEW_PID_EXT: {
- erlang_pid pid1, pid2;
- unsigned char* buf1 = *e1 - 1;
- unsigned char* buf2 = *e2 - 1;
- int ix1 = 0, ix2 = 0;
-
- if (ei_decode_pid((char*)buf1, &ix1, &pid1) ||
- ei_decode_pid((char*)buf2, &ix2, &pid2))
- return CMP_EXT_ERROR_CODE;
-
- *e1 = buf1 + ix1;
- *e2 = buf2 + ix2;
-
- /* First compare serials ... */
- if (pid1.serial < pid2.serial) return -1;
- else if (pid1.serial > pid2.serial) return 1;
-
- /* ... then ids ... */
- if (pid1.num < pid2.num) return -1;
- else if (pid1.num > pid2.num) return 1;
-
- /* ... then node names ... */
- j = strcmp(pid1.node, pid2.node);
- if (j < 0) return -1;
- else if (j > 0) return 1;
-
- /* ... and then finaly creations. */
- if (pid1.creation < pid2.creation) return -1;
- else if (pid1.creation > pid2.creation) return 1;
-
- return 0;
- }
- case ERL_PORT_EXT:
- case ERL_NEW_PORT_EXT: {
- erlang_port port1, port2;
- unsigned char* buf1 = *e1 - 1;
- unsigned char* buf2 = *e2 - 1;
- int ix1 = 0, ix2 = 0;
-
- if (ei_decode_port((char*)buf1, &ix1, &port1) ||
- ei_decode_port((char*)buf2, &ix2, &port2))
- return CMP_EXT_ERROR_CODE;
-
- *e1 = buf1 + ix1;
- *e2 = buf2 + ix2;
-
- /* First compare node names ... */
- j = strcmp(port1.node, port2.node);
- if (j < 0) return -1;
- else if (j > 0) return 1;
-
- /* ... then creations ... */
- if (port1.creation < port2.creation) return -1;
- else if (port1.creation > port2.creation) return 1;
-
- /* ... and then finally ids. */
- if (port1.id < port2.id) return -1;
- else if (port1.id > port2.id) return 1;
-
- return 0;
- }
- case ERL_NIL_EXT: return 0;
- case ERL_LIST_EXT:
- i = (**e1 << 24) | ((*e1)[1] << 16) |((*e1)[2] << 8) | (*e1)[3];
- *e1 += 4;
- j = (**e2 << 24) | ((*e2)[1] << 16) |((*e2)[2] << 8) | (*e2)[3];
- *e2 += 4;
- if ( i == j && j == 0 ) return 0;
- min = (i < j) ? i : j;
- k = 0;
- while (1) {
- if (k++ == min){
- if (i == j) return compare_top_ext(e1 , e2);
- if (i < j) return -1;
- return 1;
- }
- if ((ret = compare_top_ext(e1 , e2)) == 0)
- continue;
- return ret;
- }
- case ERL_STRING_EXT:
- i = (**e1 << 8) | ((*e1)[1]);
- *e1 += 2;
- j = (**e2 << 8) | ((*e2)[1]);
- *e2 += 2;
- ret = cmpbytes(*e1, i, *e2, j);
- *e1 += i;
- *e2 += j;
- return ret;
- case ERL_SMALL_TUPLE_EXT:
- i = *(*e1)++; j = *(*e2)++;
- if (i < j) return -1;
- if (i > j ) return 1;
- while (i--) {
- if ((j = compare_top_ext(e1, e2))) return j;
- }
- return 0;
- case ERL_LARGE_TUPLE_EXT:
- i = (**e1 << 24) | ((*e1)[1]) << 16| ((*e1)[2]) << 8| ((*e1)[3]) ;
- *e1 += 4;
- j = (**e2 << 24) | ((*e2)[1]) << 16| ((*e2)[2]) << 8| ((*e2)[3]) ;
- *e2 += 4;
- if (i < j) return -1;
- if (i > j ) return 1;
- while (i--) {
- if ((j = compare_top_ext(e1, e2))) return j;
- }
- return 0;
- case ERL_FLOAT_EXT:
- case NEW_FLOAT_EXT:
- i = -1;
- if (ei_decode_double((char *) *e1, &i, &ff1) != 0)
- return -1;
- *e1 += i;
- j = -1;
- if (ei_decode_double((char *) *e2, &j, &ff2) != 0)
- return -1;
- *e2 += j;
- return cmp_floats(ff1,ff2);
-
- case ERL_BINARY_EXT:
- i = (**e1 << 24) | ((*e1)[1] << 16) |((*e1)[2] << 8) | (*e1)[3];
- *e1 += 4;
- j = (**e2 << 24) | ((*e2)[1] << 16) |((*e2)[2] << 8) | (*e2)[3];
- *e2 += 4;
- ret = cmpbytes(*e1, i , *e2 , j);
- *e1 += i; *e2 += j;
- return ret;
-
- case ERL_FUN_EXT: /* FIXME: */
- case ERL_NEW_FUN_EXT: /* FIXME: */
- return -1;
-
- default:
- return cmpbytes(*e1, 1, *e2, 1);
-
- } /* switch */
-
-} /* cmp_exe2 */
-
-/* Number compare */
-
-static int cmp_floats(double f1, double f2)
-{
-#if defined(VXWORKS) && CPU == PPC860
- return erl_fp_compare((unsigned *) &f1, (unsigned *) &f2);
-#else
- if (f1<f2) return -1;
- else if (f1>f2) return 1;
- else return 0;
-#endif
-}
-
-static INLINE double to_float(long l)
-{
- double f;
-#if defined(VXWORKS) && CPU == PPC860
- erl_long_to_fp(l, (unsigned *) &f);
-#else
- f = l;
-#endif
- return f;
-}
-
-
-static int cmp_small_big(unsigned char**e1, unsigned char **e2)
-{
- int i1,i2;
- int t2;
- int n2;
- long l1;
- int res;
-
- erlang_big *b1,*b2;
-
- i1 = i2 = 0;
- if ( ei_decode_long((char *)*e1,&i1,&l1) < 0 ) return -1;
-
- ei_get_type((char *)*e2,&i2,&t2,&n2);
-
- /* any small will fit in two digits */
- if ( (b1 = ei_alloc_big(2)) == NULL ) return -1;
- if ( ei_small_to_big(l1,b1) < 0 ) {
- ei_free_big(b1);
- return -1;
- }
-
- if ( (b2 = ei_alloc_big(n2)) == NULL ) {
- ei_free_big(b1);
- return 1;
- }
-
- if ( ei_decode_big((char *)*e2,&i2,b2) < 0 ) {
- ei_free_big(b1);
- ei_free_big(b2);
- return 1;
- }
-
- res = ei_big_comp(b1,b2);
-
- ei_free_big(b1);
- ei_free_big(b2);
-
- *e1 += i1;
- *e2 += i2;
-
- return res;
-}
-
-static int cmp_small_float(unsigned char**e1, unsigned char **e2)
-{
- int i1,i2;
- long l1;
- double f1,f2;
-
- /* small -> float -> float_comp */
-
- i1 = i2 = 0;
- if ( ei_decode_long((char *)*e1,&i1,&l1) < 0 ) return -1;
- if ( ei_decode_double((char *)*e2,&i2,&f2) < 0 ) return 1;
-
- f1 = to_float(l1);
-
- *e1 += i1;
- *e2 += i2;
-
- return cmp_floats(f1,f2);
-}
-
-static int cmp_float_big(unsigned char**e1, unsigned char **e2)
-{
- int res;
- int i1,i2;
- int t2,n2;
- double f1,f2;
- erlang_big *b2;
-
- /* big -> float if overflow return big sign else float_comp */
-
- i1 = i2 = 0;
- if ( ei_decode_double((char *)*e1,&i1,&f1) < 0 ) return -1;
-
- if (ei_get_type((char *)*e2,&i2,&t2,&n2) < 0) return 1;
- if ((b2 = ei_alloc_big(n2)) == NULL) return 1;
- if (ei_decode_big((char *)*e2,&i2,b2) < 0) return 1;
-
- /* convert the big to float */
- if ( ei_big_to_double(b2,&f2) < 0 ) {
- /* exception look at the sign */
- res = b2->is_neg ? 1 : -1;
- ei_free_big(b2);
- return res;
- }
-
- ei_free_big(b2);
-
- *e1 += i1;
- *e2 += i2;
-
- return cmp_floats(f1,f2);
-}
-
-static int cmp_small_small(unsigned char**e1, unsigned char **e2)
-{
- int i1,i2;
- long l1,l2;
-
- i1 = i2 = 0;
- if ( ei_decode_long((char *)*e1,&i1,&l1) < 0 ) {
- fprintf(stderr,"Failed to decode 1\r\n");
- return -1;
- }
- if ( ei_decode_long((char *)*e2,&i2,&l2) < 0 ) {
- fprintf(stderr,"Failed to decode 2\r\n");
- return 1;
- }
-
- *e1 += i1;
- *e2 += i2;
-
- if ( l1 < l2 ) return -1;
- else if ( l1 > l2 ) return 1;
- else return 0;
-}
-
-static int cmp_float_float(unsigned char**e1, unsigned char **e2)
-{
- int i1,i2;
- double f1,f2;
-
- i1 = i2 = 0;
- if ( ei_decode_double((char *)*e1,&i1,&f1) < 0 ) return -1;
- if ( ei_decode_double((char *)*e2,&i2,&f2) < 0 ) return 1;
-
- *e1 += i1;
- *e2 += i2;
-
- return cmp_floats(f1,f2);
-}
-
-static int cmp_big_big(unsigned char**e1, unsigned char **e2)
-{
- int res;
- int i1,i2;
- int t1,t2;
- int n1,n2;
- erlang_big *b1,*b2;
-
- i1 = i2 = 0;
- ei_get_type((char *)*e1,&i1,&t1,&n1);
- ei_get_type((char *)*e2,&i2,&t2,&n2);
-
- if ( (b1 = ei_alloc_big(n1)) == NULL) return -1;
- if ( (b2 = ei_alloc_big(n2)) == NULL) {
- ei_free_big(b1);
- return 1;
- }
-
- ei_decode_big((char *)*e1,&i1,b1);
- ei_decode_big((char *)*e2,&i2,b2);
-
- res = ei_big_comp(b1,b2);
-
- ei_free_big(b1);
- ei_free_big(b2);
-
- *e1 += i1;
- *e2 += i2;
-
- return res;
-}
-
-static int cmp_number(unsigned char**e1, unsigned char **e2)
-{
- switch (CMP_NUM_CODE(**e1,**e2)) {
-
- case SMALL_BIG:
- /* fprintf(stderr,"compare small_big\r\n"); */
- return cmp_small_big(e1,e2);
-
- case BIG_SMALL:
- /* fprintf(stderr,"compare sbig_small\r\n"); */
- return -cmp_small_big(e2,e1);
-
- case SMALL_FLOAT:
- /* fprintf(stderr,"compare small_float\r\n"); */
- return cmp_small_float(e1,e2);
-
- case FLOAT_SMALL:
- /* fprintf(stderr,"compare float_small\r\n"); */
- return -cmp_small_float(e2,e1);
-
- case FLOAT_BIG:
- /* fprintf(stderr,"compare float_big\r\n"); */
- return cmp_float_big(e1,e2);
-
- case BIG_FLOAT:
- /* fprintf(stderr,"compare big_float\r\n"); */
- return -cmp_float_big(e2,e1);
-
- case SMALL_SMALL:
- /* fprintf(stderr,"compare small_small\r\n"); */
- return cmp_small_small(e1,e2);
-
- case FLOAT_FLOAT:
- /* fprintf(stderr,"compare float_float\r\n"); */
- return cmp_float_float(e1,e2);
-
- case BIG_BIG:
- /* fprintf(stderr,"compare big_big\r\n"); */
- return cmp_big_big(e1,e2);
-
- default:
- /* should never get here ... */
- /* fprintf(stderr,"compare standard\r\n"); */
- return cmp_exe2(e1,e2);
- }
-
-}
-
-/*
- * If the arrays are of the same type, then we
- * have to do a real compare.
- */
-/*
- * COMPARE TWO encoded BYTE ARRAYS e1 and e2.
- * Return: -1 if e1 < e2
- * 0 if e1 == e2
- * 1 if e2 > e1
- */
-static int compare_top_ext(unsigned char**e1, unsigned char **e2)
-{
- if (**e1 == ERL_VERSION_MAGIC) (*e1)++;
- if (**e2 == ERL_VERSION_MAGIC) (*e2)++;
-
- if (cmp_array[**e1] < cmp_array[**e2]) return -1;
- if (cmp_array[**e1] > cmp_array[**e2]) return 1;
-
- if (IS_ERL_NUM(**e1))
- return cmp_number(e1,e2);
-
- if (cmp_array[**e1] == ERL_REF_CMP)
- return cmp_refs(e1, e2);
-
- return cmp_exe2(e1, e2);
-}
-
-int erl_compare_ext(unsigned char *e1, unsigned char *e2)
-{
- return compare_top_ext(&e1, &e2);
-} /* erl_compare_ext */
-
-#if defined(VXWORKS) && CPU == PPC860
-/* FIXME we have no floating point but don't we have emulation?! */
-int erl_fp_compare(unsigned *a, unsigned *b)
-{
- /* Big endian mode of powerPC, IEEE floating point. */
- unsigned a_split[4] = {a[0] >> 31, /* Sign bit */
- (a[0] >> 20) & 0x7FFU, /* Exponent */
- a[0] & 0xFFFFFU, /* Mantissa MS bits */
- a[1]}; /* Mantissa LS bits */
- unsigned b_split[4] = {b[0] >> 31,
- (b[0] >> 20) & 0x7FFU,
- b[0] & 0xFFFFFU,
- b[1]};
- int a_is_infinite, b_is_infinite;
- int res;
-
-
- /* Make -0 be +0 */
- if (a_split[1] == 0 && a_split[2] == 0 && a_split[3] == 0)
- a_split[0] = 0;
- if (b_split[1] == 0 && b_split[2] == 0 && b_split[3] == 0)
- b_split[0] = 0;
- /* Check for infinity */
- a_is_infinite = (a_split[1] == 0x7FFU && a_split[2] == 0 &&
- a_split[3] == 0);
- b_is_infinite = (b_split[1] == 0x7FFU && b_split[2] == 0 &&
- b_split[3] == 0);
-
- if (a_is_infinite && !b_is_infinite)
- return (a_split[0]) ? -1 : 1;
- if (b_is_infinite && !a_is_infinite)
- return (b_split[0]) ? 1 : -1;
- if (a_is_infinite && b_is_infinite)
- return b[0] - a[0];
- /* Check for indeterminate or nan, infinite is already handled,
- so we only check the exponent. */
- if((a_split[1] == 0x7FFU) || (b_split[1] == 0x7FFU))
- return INT_MAX; /* Well, they are not equal anyway,
- abort() could be an alternative... */
-
- if (a_split[0] && !b_split[0])
- return -1;
- if (b_split[0] && !a_split[0])
- return 1;
- /* Compare */
- res = memcmp(a_split + 1, b_split + 1, 3 * sizeof(unsigned));
- /* Make -1, 0 or 1 */
- res = (!!res) * ((res < 0) ? -1 : 1);
- /* Turn sign if negative values */
- if (a_split[0]) /* Both are negative */
- res = -1 * res;
- return res;
-}
-
-static void join(unsigned d_split[4], unsigned *d)
-{
- d[0] = (d_split[0] << 31) | /* Sign bit */
- ((d_split[1] & 0x7FFU) << 20) | /* Exponent */
- (d_split[2] & 0xFFFFFU); /* Mantissa MS bits */
- d[1] = d_split[3]; /* Mantissa LS bits */
-}
-
-static int blength(unsigned long l)
-{
- int i;
- for(i = 0; l; ++i)
- l >>= 1;
- return i;
-}
-
-static void erl_long_to_fp(long l, unsigned *d)
-{
- unsigned d_split[4];
- unsigned x;
- if (l < 0) {
- d_split[0] = 1;
- x = -l;
- } else {
- d_split[0] = 0;
- x = l;
- }
-
- if (!l) {
- memset(d_split,0,sizeof(d_split));
- } else {
- int len = blength(x);
- x <<= (33 - len);
- d_split[2] = (x >> 12);
- d_split[3] = (x << 20);
- d_split[1] = 1023 + len - 1;
- }
- join(d_split,d);
-}
-
-#endif
-
-
-/*
- * Checks if a term is a "string": a flat list of byte-sized integers.
- *
- * Returns: 0 if the term is not a string, otherwise the length is returned.
- */
-
-static int is_string(ETERM* term)
-{
- int len = 0;
-
- while (ERL_TYPE(term) == ERL_LIST) {
- ETERM* head = HEAD(term);
-
- if (!ERL_IS_INTEGER(head) || ((unsigned)head->uval.ival.i) > 255) {
- return 0;
- }
- len++;
- term = TAIL(term);
- }
-
- if (ERL_IS_EMPTY_LIST(term)) {
- return len;
- }
- return 0;
-}
diff --git a/lib/erl_interface/src/legacy/erl_resolve.c b/lib/erl_interface/src/legacy/erl_resolve.c
deleted file mode 100644
index bb09caec85..0000000000
--- a/lib/erl_interface/src/legacy/erl_resolve.c
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2003-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-/***************************************************************************
- *
- * Compatibility with the old erl_interface library that had some
- * undocumented functions.
- *
- ***************************************************************************/
-
-#include "eidef.h"
-
-#include "erl_interface.h"
-#include "ei_resolve.h"
-#include "ei_connect_int.h"
-#include "ei_epmd.h"
-
-struct hostent *erl_gethostbyname(const char *name)
-{
- return ei_gethostbyname(name);
-}
-
-
-void erl_init_resolve(void)
-{
- ei_init_resolve();
-}
-
-
-struct hostent *erl_gethostbyaddr(const char *addr, int len, int type)
-{
- return ei_gethostbyaddr(addr, len, type);
-}
-
-
-struct hostent *erl_gethostbyname_r(const char *name,
- struct hostent *hostp,
- char *buffer,
- int buflen,
- int *h_errnop)
-{
- return ei_gethostbyname_r(name,hostp,buffer,buflen,h_errnop);
-}
-
-
-struct hostent *erl_gethostbyaddr_r(const char *addr,
- int length,
- int type,
- struct hostent *hostp,
- char *buffer,
- int buflen,
- int *h_errnop)
-{
- return ei_gethostbyaddr_r(addr,length,type,hostp,buffer,buflen,h_errnop);
-}
-
-
-int erl_distversion(int fd)
-{
- return ei_distversion(fd);
-}
-
-int erl_epmd_connect(struct in_addr *inaddr)
-{
- return ei_epmd_connect_tmo(inaddr,0);
-}
-
-int erl_epmd_port(struct in_addr *inaddr, const char *alive, int *dist)
-{
- return ei_epmd_port(inaddr, alive, dist);
-}
-
-
-
-/* FIXME !!!!!
-erl_epmd_port ei_epmd_port
-erl_mutex_lock ei_mutex_lock
-erl_malloc erl_free ????
-erl_publish erl_unpublish
-< extern int erl_epmd_connect(struct in_addr *inaddr);
-< extern int erl_epmd_publish(int port, const char *alive);
-< extern int erl_epmd_port(struct in_addr *inaddr, const char *alive, int *dist);
-
-< int erl_unpublish(const char *alive)
----
-> int ei_unpublish_alive(const char *alive)
-
-erl_self
-erl_getfdcookie
-*/
diff --git a/lib/erl_interface/src/legacy/erl_timeout.c b/lib/erl_interface/src/legacy/erl_timeout.c
deleted file mode 100644
index e36ea0e250..0000000000
--- a/lib/erl_interface/src/legacy/erl_timeout.c
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-/* timeout.c
- *
- * todo: use posix timers (timer_create etc) instead of setitimer.
- *
- */
-#if !defined(__WIN32__) && !defined(VXWORKS)
-
-/* FIXME: well, at least I can compile now... */
-
-#include "eidef.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <setjmp.h>
-#include <signal.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include "erl_interface.h"
-#include "erl_timeout.h"
-
-typedef struct jmp_s {
- jmp_buf jmpbuf;
- struct itimerval timerinfo;
- void *siginfo;
- struct jmp_s *next;
-} *jmp_t;
-
-static jmp_t push(jmp_t j);
-static jmp_t pop(void);
-static void timeout_handler(int dummy);
-
-jmp_buf *timeout_setup(int ms)
-{
- struct itimerval t;
- jmp_t j;
- void *s;
-
-#ifdef DEBUG
- fprintf(stderr,"timeout setup\n");
-#endif
- s=signal(SIGALRM,timeout_handler);
-
- /* set the timer */
- t.it_interval.tv_sec = 0;
- t.it_interval.tv_usec = 0;
- t.it_value.tv_sec = ms / 1000;
- t.it_value.tv_usec = (ms % 1000) * 1000;
-
- /* get a jump buffer and save it */
- j = erl_malloc(sizeof(*j));
- j->siginfo = s;
- push(j);
-
- setitimer(ITIMER_REAL,&t,&(j->timerinfo));
-
- return &(j->jmpbuf);
-}
-
-
-int timeout_cancel(void)
-{
- jmp_t j;
-
-#ifdef DEBUG
- fprintf(stderr,"timeout cancel\n");
-#endif
- /* retrieve the jump buffer */
- j=pop();
- /* restore the timer and signal disposition */
- setitimer(ITIMER_REAL,&(j->timerinfo),NULL);
- signal(SIGALRM,j->siginfo);
-
- free(j);
-
- return 0;
-}
-
-void timeout_handler(int dummy)
-{
- jmp_t j;
-
-#ifdef DEBUG
- fprintf(stderr,"timeout handler\n");
-#endif
-
- /* retrieve the jump buffer */
- j=pop();
-
- /* restore the timer and signal disposition */
- setitimer(ITIMER_REAL,&(j->timerinfo),NULL);
- signal(SIGALRM,j->siginfo);
-
- free(j);
- longjmp(j->jmpbuf,JMPVAL);
- return; /* not reached */
-}
-
-
-/* a simple stack for saving the jump buffer allows us to pass a
- * variable between functions that don't call each other, in a way
- * that will survive the longjmp().
- */
-
-/* FIXME problem for threaded ? */
-static jmp_t jmp_head=NULL;
-#ifdef DEBUG
-static int depth = 0;
-static int maxdepth = 0;
-#endif
-
-static jmp_t push(jmp_t j)
-{
- j->next = jmp_head;
- jmp_head = j;
-
-#ifdef DEBUG
- depth++;
- if (depth > maxdepth) maxdepth = depth;
-#endif
-
- return j;
-}
-
-static jmp_t pop(void)
-{
- jmp_t j = jmp_head;
- if (j) jmp_head = j->next;
-#ifdef DEBUG
- depth--;
-#endif
- return j;
-}
-
-#endif /* platform */
diff --git a/lib/erl_interface/src/legacy/erl_timeout.h b/lib/erl_interface/src/legacy/erl_timeout.h
deleted file mode 100644
index 6bcfa5ecbb..0000000000
--- a/lib/erl_interface/src/legacy/erl_timeout.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-#ifndef _ERL_TIMEOUT_H
-#define _ERL_TIMEOUT_H
-
-#if !defined (__WIN32__) && !defined (VXWORKS)
-
-#include <setjmp.h>
-
-/*
- use timeout like this (delay in ms):
-
- if (timeout(delay,fun(a,b,c))) {
- printf("timeout occurred\n");
- }
- else {
- ...
- }
-
-If the call to fun() has not returned before 'delay' ms, it will be
-interrupted and and timeout() will return a non-zero value.
-
-If fun() finishes before 'delay' ms, then timeout will return 0.
-
-If you need the return value from fun then assign it like this:
-
- if (timeout(delay,(x = fun(...)))) {
- }
-
-These functions work by setting and catching SIGALRM, and although it
-saves and restores the signal handler, it may not work in situations
-where you are already using SIGALRM (this includes calls to sleep(3)).
-
-Note that although recursive calls to timeout will not fail, they may
-not give the expected results. All invocations of timeout use the same
-timer, which is set on entrance to timeout and restored on exit from
-timeout. So although an inner call to timeout will restart the timer
-for any pending outer call when it exits, any time that has already
-elapsed against the outer timeout is forgotten. In addition, the alarm
-signal will always go to the innermost (last called) timeout, which
-may or may not be the intention in recursive cases.
-
-*/
-
-#define JMPVAL 997 /* magic */
-
-#define timeout(ms,funcall) \
- (setjmp(*timeout_setup(ms)) == JMPVAL ? -1: \
- ((void)(funcall), timeout_cancel()))
-
-
-/* don't call any of these directly - use the macro! see above! */
-jmp_buf *timeout_setup(int ms);
-int timeout_cancel(void);
-
-#endif /* WIN32 && VXWORKS */
-
-#endif /* _ERL_TIMEOUT_H */
diff --git a/lib/erl_interface/src/misc/ei_cmp_nc.c b/lib/erl_interface/src/misc/ei_cmp_nc.c
new file mode 100644
index 0000000000..73650f429f
--- /dev/null
+++ b/lib/erl_interface/src/misc/ei_cmp_nc.c
@@ -0,0 +1,117 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2020. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ *
+ */
+
+#include <string.h>
+#include "eidef.h"
+
+/*
+ * Comparison of node container types (pid, port, ref). Comparison
+ * done the same way as ERTS compare them. In ref and port case,
+ * node info is compared before other data, and in pid case node
+ * info is compared after other data.
+ */
+
+static int cmp_nodes(char *aname, unsigned int acreation,
+ char *bname, unsigned int bcreation)
+{
+ int differ = strcmp(aname, bname);
+ if (differ)
+ return differ;
+ if (acreation == bcreation)
+ return 0;
+ if (acreation < bcreation)
+ return -1;
+ return 1;
+}
+
+int
+ei_cmp_refs(erlang_ref *a, erlang_ref *b)
+{
+ int differ;
+ int i, alen, blen;
+ unsigned int *anum, *bnum;
+
+ differ = cmp_nodes(a->node, a->creation, b->node, b->creation);
+ if (differ)
+ return differ;
+
+ alen = a->len;
+ blen = b->len;
+
+ anum = &a->n[0];
+ bnum = &b->n[0];
+
+ if (alen != blen) {
+ if (alen > blen) {
+ do {
+ if (anum[alen - 1] != 0)
+ return 1;
+ alen--;
+ } while (alen > blen);
+ }
+ else {
+ do {
+ if (bnum[blen - 1] != 0)
+ return -1;
+ blen--;
+ } while (alen < blen);
+ }
+ }
+
+ for (i = alen - 1; i >= 0; i--)
+ if (anum[i] != bnum[i])
+ return anum[i] < bnum[i] ? -1 : 1;
+
+ return 0;
+}
+
+int
+ei_cmp_pids(erlang_pid *a, erlang_pid *b)
+{
+ int differ;
+
+ if (a->serial != b->serial)
+ return a->serial < b->serial ? -1 : 1;
+
+ if (a->num != b->num)
+ return a->num < b->num ? -1 : 1;
+
+ differ = cmp_nodes(a->node, a->creation, b->node, b->creation);
+ if (differ)
+ return differ;
+
+ return 0;
+}
+
+int
+ei_cmp_ports(erlang_port *a, erlang_port *b)
+{
+ int differ;
+
+ differ = cmp_nodes(a->node, a->creation, b->node, b->creation);
+ if (differ)
+ return differ;
+
+ if (a->id != b->id)
+ return a->id < b->id ? -1 : 1;
+
+ return 0;
+}
diff --git a/lib/erl_interface/src/misc/ei_decode_term.c b/lib/erl_interface/src/misc/ei_decode_term.c
index 79c8fb4388..72f1e0d511 100644
--- a/lib/erl_interface/src/misc/ei_decode_term.c
+++ b/lib/erl_interface/src/misc/ei_decode_term.c
@@ -47,25 +47,30 @@ int ei_decode_ei_term(const char* buf, int* index, ei_term* term)
break;
case ERL_FLOAT_EXT:
case NEW_FLOAT_EXT:
+ term->ei_type = ERL_FLOAT_EXT;
return (ei_decode_double(buf, index, &term->value.d_val) < 0
? -1 : 1);
case ERL_ATOM_EXT:
case ERL_ATOM_UTF8_EXT:
case ERL_SMALL_ATOM_EXT:
case ERL_SMALL_ATOM_UTF8_EXT:
+ term->ei_type = ERL_ATOM_EXT;
return (ei_decode_atom(buf, index, term->value.atom_name) < 0
? -1 : 1);
case ERL_REFERENCE_EXT:
case ERL_NEW_REFERENCE_EXT:
case ERL_NEWER_REFERENCE_EXT:
+ term->ei_type = ERL_NEW_REFERENCE_EXT;
return (ei_decode_ref(buf, index, &term->value.ref) < 0
? -1 : 1);
case ERL_PORT_EXT:
case ERL_NEW_PORT_EXT:
+ term->ei_type = ERL_PORT_EXT;
return (ei_decode_port(buf, index, &term->value.port) < 0
? -1 : 1);
case ERL_PID_EXT:
case ERL_NEW_PID_EXT:
+ term->ei_type = ERL_PID_EXT;
return (ei_decode_pid(buf, index, &term->value.pid) < 0
? -1 : 1);
case ERL_SMALL_TUPLE_EXT:
diff --git a/lib/erl_interface/src/misc/ei_format.c b/lib/erl_interface/src/misc/ei_format.c
index a188171f40..695c3404f7 100644
--- a/lib/erl_interface/src/misc/ei_format.c
+++ b/lib/erl_interface/src/misc/ei_format.c
@@ -24,10 +24,6 @@
* ei_format to build binary format terms a bit like printf
*/
-#ifdef VXWORKS
-#include <vxWorks.h>
-#endif
-
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
diff --git a/lib/erl_interface/src/misc/ei_locking.c b/lib/erl_interface/src/misc/ei_locking.c
index a5ddbb85f2..20464869ad 100644
--- a/lib/erl_interface/src/misc/ei_locking.c
+++ b/lib/erl_interface/src/misc/ei_locking.c
@@ -26,7 +26,7 @@
/* Note that these locks are NOT recursive on Win32 or Solaris,
* i.e. self-deadlock will occur if a thread tries to obtain a lock it
- * is already holding. The primitives used on VxWorks are recursive however.
+ * is already holding.
*/
#include "eidef.h"
@@ -36,10 +36,6 @@
#include <windows.h>
#include <winbase.h>
-#elif VXWORKS
-#include <vxWorks.h>
-#include <semLib.h>
-
#else /* unix */
#include <stdlib.h>
#include <unistd.h>
@@ -63,12 +59,6 @@ ei_mutex_t *ei_mutex_create(void)
#ifdef __WIN32__
l->lock = CreateMutex(NULL,FALSE,NULL);
-
-#elif VXWORKS
- if (!(l->lock = semMCreate(SEM_DELETE_SAFE))) {
- ei_free(l);
- return NULL;
- }
#else /* unix */
l->lock = ei_m_create();
#endif
@@ -97,10 +87,6 @@ int ei_mutex_free(ei_mutex_t *l, int nblock)
/* we are now holding the lock */
#ifdef __WIN32__
CloseHandle(l->lock);
-
-#elif VXWORKS
- if (semDelete(l->lock) == ERROR) return -1;
-
#else /* unix */
ei_m_destroy(l->lock);
#endif
@@ -131,11 +117,6 @@ int ei_mutex_lock(ei_mutex_t *l, int nblock)
/* check valid values for timeout: is 0 ok? */
if (WaitForSingleObject(l->lock,(nblock? 0 : INFINITE)) != WAIT_OBJECT_0)
return -1;
-
-#elif VXWORKS
- if (semTake(l->lock,(nblock? NO_WAIT : WAIT_FOREVER)) == ERROR)
- return -1;
-
#else /* unix */
if (nblock) {
if (ei_m_trylock(l->lock) < 0) return -1;
@@ -151,10 +132,6 @@ int ei_mutex_unlock(ei_mutex_t *l)
{
#ifdef __WIN32__
ReleaseMutex(l->lock);
-
-#elif VXWORKS
- semGive(l->lock);
-
#else /* unix */
ei_m_unlock(l->lock);
#endif
diff --git a/lib/erl_interface/src/misc/ei_locking.h b/lib/erl_interface/src/misc/ei_locking.h
index 1bbee2d499..93aade6b2d 100644
--- a/lib/erl_interface/src/misc/ei_locking.h
+++ b/lib/erl_interface/src/misc/ei_locking.h
@@ -24,11 +24,6 @@
#include "config.h"
-#if defined(VXWORKS)
-#include <taskLib.h>
-#include <taskVarLib.h>
-#endif
-
#ifdef __WIN32__
#include <winsock2.h>
#include <windows.h>
@@ -45,8 +40,6 @@
typedef struct ei_mutex_s {
#ifdef __WIN32__
HANDLE lock;
-#elif VXWORKS
- SEM_ID lock;
#else /* unix */
#if defined(HAVE_MIT_PTHREAD_H) || defined(HAVE_PTHREAD_H)
pthread_mutex_t *lock;
@@ -64,7 +57,7 @@ int ei_mutex_lock(ei_mutex_t *l, int nblock);
int ei_mutex_unlock(ei_mutex_t *l);
-#if defined(_REENTRANT) && !defined(VXWORKS) && !defined(__WIN32__)
+#if defined(_REENTRANT) && !defined(__WIN32__)
void *ei_m_create(void);
int ei_m_destroy(void *l);
@@ -72,6 +65,6 @@ int ei_m_lock(void *l);
int ei_m_trylock(void *l);
int ei_m_unlock(void *l);
-#endif /* _REENTRANT && !VXWORKS && !__WIN32__ */
+#endif /* _REENTRANT && !__WIN32__ */
#endif /* _EI_LOCKING_H */
diff --git a/lib/erl_interface/src/misc/ei_portio.c b/lib/erl_interface/src/misc/ei_portio.c
index 5b7902cf0a..f8f777319b 100644
--- a/lib/erl_interface/src/misc/ei_portio.c
+++ b/lib/erl_interface/src/misc/ei_portio.c
@@ -42,29 +42,7 @@ static unsigned long param_one = 1;
#define MEANS_SOCKET_ERROR(Ret) ((Ret == SOCKET_ERROR))
#define IS_INVALID_SOCKET(Sock) ((Sock) == INVALID_SOCKET)
-#elif VXWORKS
-#include <vxWorks.h>
-#include <hostLib.h>
-#include <ifLib.h>
-#include <sockLib.h>
-#include <taskLib.h>
-#include <inetLib.h>
-#include <selectLib.h>
-#include <ioLib.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <timers.h>
-
-static unsigned long param_zero = 0;
-static unsigned long param_one = 1;
-#define SET_BLOCKING(Sock) ioctl((Sock),FIONBIO,(int)&param_zero)
-#define SET_NONBLOCKING(Sock) ioctl((Sock),FIONBIO,(int)&param_one)
-#define MEANS_SOCKET_ERROR(Ret) ((Ret) == ERROR)
-#define IS_INVALID_SOCKET(Sock) ((Sock) < 0)
-
-#else /* other unix */
+#else /* unix */
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
@@ -622,7 +600,7 @@ int ei_accept_ctx_t__(ei_socket_callbacks *cbs, void **ctx,
} while (error == EINTR);
}
do {
- error = cbs->accept(ctx, addr, len, ms);
+ error = cbs->EI_ACCEPT_NAME(ctx, addr, len, ms);
} while (error == EINTR);
return error;
}
diff --git a/lib/erl_interface/src/misc/ei_portio.h b/lib/erl_interface/src/misc/ei_portio.h
index 69ebf1dab8..1805411804 100644
--- a/lib/erl_interface/src/misc/ei_portio.h
+++ b/lib/erl_interface/src/misc/ei_portio.h
@@ -22,8 +22,10 @@
#ifndef _EI_PORTIO_H
#define _EI_PORTIO_H
+#include <stdint.h>
+
#undef EI_HAVE_STRUCT_IOVEC__
-#if !defined(__WIN32__) && !defined(VXWORKS) && defined(HAVE_SYS_UIO_H)
+#if !defined(__WIN32__) && defined(HAVE_SYS_UIO_H)
/* Declaration of struct iovec *iov should be visible in this scope. */
# include <sys/uio.h>
# define EI_HAVE_STRUCT_IOVEC__
@@ -50,12 +52,12 @@ int ei_socket_callbacks_have_writev__(ei_socket_callbacks *cbs);
extern ei_socket_callbacks ei_default_socket_callbacks;
#define EI_FD_AS_CTX__(FD) \
- ((void *) (long) (FD))
+ ((void *) (intptr_t) (FD))
#define EI_DFLT_CTX_TO_FD__(CTX, FD) \
- ((int) (long) (CTX) < 0 \
+ ((intptr_t) (CTX) < 0 \
? EBADF \
- : (*(FD) = (int) (long) (CTX), 0))
+ : (*(FD) = (int) (intptr_t) (CTX), 0))
#define EI_GET_FD__(CBS, CTX, FD) \
((CBS) == &ei_default_socket_callbacks \
diff --git a/lib/erl_interface/src/misc/ei_printterm.c b/lib/erl_interface/src/misc/ei_printterm.c
index 58a46adf01..0bad730095 100644
--- a/lib/erl_interface/src/misc/ei_printterm.c
+++ b/lib/erl_interface/src/misc/ei_printterm.c
@@ -24,10 +24,6 @@
* ei_print_term to print out a binary coded term
*/
-#ifdef VXWORKS
-#include <vxWorks.h>
-#endif
-
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
@@ -91,24 +87,73 @@ static char *ei_big_to_str(erlang_big *b)
{
int buf_len;
char *s,*buf;
+ unsigned int no_digits;
unsigned short *sp;
int i;
+ int printed;
+
+ /* Number of 16-bit digits */
+ no_digits = (b->arity + 1) / 2;
+
+ if (no_digits <= 4) {
+ EI_ULONGLONG val;
+ buf_len = 22;
+ s = buf = malloc(buf_len);
+ if (!buf)
+ return NULL;
+ val = 0;
+ sp=b->digits;
+ for (i = 0; i < no_digits; i++)
+ val |= ((EI_ULONGLONG) sp[i]) << (i*16);
+ if (b->is_neg)
+ s += sprintf(s,"-");
+ sprintf(s, "%llu", val);
+ return buf;
+ }
- buf_len = 64+b->is_neg+10*b->arity;
+ /* big nums this large gets printed in base 16... */
+ buf_len = (!!b->is_neg /* "-" */
+ + 3 /* "16#" */
+ + 4*no_digits /* 16-bit digits in base 16 */
+ + 1); /* \0 */
if ( (buf=malloc(buf_len)) == NULL) return NULL;
- memset(buf,(char)0,buf_len);
-
s = buf;
if ( b->is_neg )
- s += sprintf(s,"-");
- s += sprintf(s,"#integer(%d) = {",b->arity);
- for(sp=b->digits,i=0;i<b->arity;i++) {
- s += sprintf(s,"%d",sp[i]);
- if ( (i+1) != b->arity )
- s += sprintf(s,",");
+ *(s++) = '-';
+ *(s++) = '1';
+ *(s++) = '6';
+ *(s++) = '#';
+
+ sp = b->digits;
+ printed = 0;
+ for (i = no_digits - 1; i >= 0; i--) {
+ unsigned short val = sp[i];
+ int j;
+
+ for (j = 3; j >= 0; j--) {
+ char c = (char) ((val >> (j*4)) & 0xf);
+ if (c < 10)
+ c += '0';
+ else
+ c += 'A' - 10;
+
+ if (printed)
+ *(s++) = c;
+ else if (c != '0') {
+ *(s++) = c;
+ printed = !0;
+ }
+ }
}
- s += sprintf(s,"}");
+
+ if (!printed) {
+ /* very strange to encode zero like this... */
+ *(s++) = '0';
+ }
+
+
+ *s = '\0';
return buf;
}
diff --git a/lib/erl_interface/src/misc/ei_pthreads.c b/lib/erl_interface/src/misc/ei_pthreads.c
index c6d07a9a0a..df7a7fbb8f 100644
--- a/lib/erl_interface/src/misc/ei_pthreads.c
+++ b/lib/erl_interface/src/misc/ei_pthreads.c
@@ -38,25 +38,6 @@ static LONG volatile tls_init_mutex = 0;
#endif
#endif
-#if defined(VXWORKS)
-
-/*
- Moved to each of the erl_*threads.c files, as they seem to know how
- to get thread-safety.
-*/
-static volatile int __erl_errno;
-volatile int *__erl_errno_place(void)
-{
- /* This check is somewhat insufficient, double task var entries will occur
- if __erl_errno is actually -1, which on the other hand is an invalid
- error code. */
- if (taskVarGet(taskIdSelf(), &__erl_errno) == ERROR) {
- taskVarAdd(taskIdSelf(), &__erl_errno);
- }
- return &__erl_errno;
-}
-#endif /* VXWORKS */
-
#if defined(__WIN32__)
#ifdef USE_DECLSPEC_THREAD
@@ -106,7 +87,7 @@ volatile int *__erl_errno_place(void)
#endif /* __WIN32__ */
-#if defined(_REENTRANT) && !defined(VXWORKS) && !defined(__WIN32__)
+#if defined(_REENTRANT) && !defined(__WIN32__)
#if defined(HAVE_PTHREAD_H) || defined(HAVE_MIT_PTHREAD_H)
@@ -219,9 +200,9 @@ volatile int *__erl_errno_place(void)
#endif /* HAVE_PTHREAD_H || HAVE_MIT_PTHREAD_H */
-#endif /* _REENTRANT && !VXWORKS && !__WIN32__ */
+#endif /* _REENTRANT && !__WIN32__ */
-#if !defined(_REENTRANT) && !defined(VXWORKS) && !defined(__WIN32__)
+#if !defined(_REENTRANT) && !defined(__WIN32__)
volatile int __erl_errno;
diff --git a/lib/erl_interface/src/misc/ei_x_encode.c b/lib/erl_interface/src/misc/ei_x_encode.c
index a203994791..9e781e67b0 100644
--- a/lib/erl_interface/src/misc/ei_x_encode.c
+++ b/lib/erl_interface/src/misc/ei_x_encode.c
@@ -23,10 +23,6 @@
* ei_x_encode to encode in a self-expanding buffer
*/
-#ifdef VXWORKS
-#include <vxWorks.h>
-#endif
-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/lib/erl_interface/src/misc/eidef.h b/lib/erl_interface/src/misc/eidef.h
index d76cae1a9a..e6039cfca8 100644
--- a/lib/erl_interface/src/misc/eidef.h
+++ b/lib/erl_interface/src/misc/eidef.h
@@ -27,11 +27,6 @@
#include "config.h" /* Central include of config.h */
-/* vxWorks.h needs to be before stddef.h */
-#ifdef VXWORKS
-#include <vxWorks.h>
-#endif
-
#include <stddef.h> /* We want to get definition of NULL */
#include "ei.h" /* Want the API function declarations */
diff --git a/lib/erl_interface/src/misc/get_type.c b/lib/erl_interface/src/misc/get_type.c
index 4150e4143d..033f4c541d 100644
--- a/lib/erl_interface/src/misc/get_type.c
+++ b/lib/erl_interface/src/misc/get_type.c
@@ -32,6 +32,7 @@ int ei_get_type(const char *buf, const int *index, int *type, int *len)
const char *s = buf + *index;
*type = get8(s);
+ *len = 0;
switch (*type) {
case ERL_SMALL_ATOM_EXT:
@@ -60,7 +61,7 @@ int ei_get_type(const char *buf, const int *index, int *type, int *len)
case ERL_BIT_BINARY_EXT:
*len = get32be(s);
break;
-
+
case ERL_SMALL_BIG_EXT:
*len = get8(s); /* #digit_bytes */
break;
@@ -78,9 +79,6 @@ int ei_get_type(const char *buf, const int *index, int *type, int *len)
case ERL_NEWER_REFERENCE_EXT:
*type = ERL_NEW_REFERENCE_EXT;
break;
- default:
- *len = 0;
- break;
}
/* leave index unchanged */
diff --git a/lib/erl_interface/src/misc/show_msg.c b/lib/erl_interface/src/misc/show_msg.c
index 518d9dd595..c87e73c545 100644
--- a/lib/erl_interface/src/misc/show_msg.c
+++ b/lib/erl_interface/src/misc/show_msg.c
@@ -129,9 +129,9 @@ int ei_show_sendmsg(FILE *stream, const char *header, const char *msgbuf)
/* skip five bytes */
index = 5;
- ei_decode_version(header,&index,&version);
- ei_decode_tuple_header(header,&index,&arity);
- ei_decode_long(header,&index,&msg.msgtype);
+ if (ei_decode_version(header,&index,&version)
+ || ei_decode_tuple_header(header,&index,&arity)
+ || ei_decode_long(header,&index,&msg.msgtype)) return -1;
switch (msg.msgtype) {
case ERL_SEND:
diff --git a/lib/erl_interface/src/not_used/ei_send.c b/lib/erl_interface/src/not_used/ei_send.c
deleted file mode 100644
index 8071876677..0000000000
--- a/lib/erl_interface/src/not_used/ei_send.c
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2001-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- *
-
- */
-#ifdef __WIN32__
-#include <winsock2.h>
-#include <windows.h>
-#include <winbase.h>
-
-#elif VXWORKS
-#include <sys/types.h>
-#include <unistd.h>
-
-#else /* unix */
-#include <sys/types.h>
-#include <unistd.h>
-#include <sys/uio.h>
-#endif
-
-#include "eidef.h"
-#include "eiext.h"
-#include "ei_connect.h"
-#include "ei_internal.h"
-#include "putget.h"
-#include "ei_trace.h"
-#include "show_msg.h"
-
-/* FIXME this is not useed !!!!! */
-
-/* length (4), PASS_THROUGH (1), header, message */
-int ei_ei_send_encoded(ei_cnode* ec, int fd, const erlang_pid *to,
- const char *msg, int msglen)
-{
- char *s, header[1200]; /* see size calculation below */
- erlang_trace *token = NULL;
- int index = 5; /* reserve 5 bytes for control message */
-#ifdef HAVE_WRITEV
- struct iovec v[2];
-#endif
-
- /* are we tracing? */
- /* check that he can receive trace tokens first */
- if (ei_distversion(fd) > 0)
- token = ei_trace(0,(erlang_trace *)NULL);
-
- /* header = SEND, cookie, to max sizes: */
- ei_encode_version(header,&index); /* 1 */
- if (token) {
- ei_encode_tuple_header(header,&index,4); /* 2 */
- ei_encode_long(header,&index,ERL_SEND_TT); /* 2 */
- } else {
- ei_encode_tuple_header(header,&index,3);
- ei_encode_long(header,&index,ERL_SEND);
- }
- ei_encode_atom(header,&index, "" /*ei_getfdcookie(ec, fd)*/); /* 258 */
- ei_encode_pid(header,&index,to); /* 268 */
-
- if (token) ei_encode_trace(header,&index,token); /* 534 */
-
- /* control message (precedes header actually) */
- /* length = 1 ('p') + header len + message len */
- s = header;
- put32be(s, index + msglen - 4); /* 4 */
- put8(s, ERL_PASS_THROUGH); /* 1 */
- /*** sum: 1070 */
-
-#ifdef DEBUG_DIST
- if (ei_trace_distribution > 0) ei_show_sendmsg(stderr,header,msg);
-#endif
-
-#ifdef HAVE_WRITEV
-
- v[0].iov_base = (char *)header;
- v[0].iov_len = index;
- v[1].iov_base = (char *)msg;
- v[1].iov_len = msglen;
-
- if (writev(fd,v,2) != index+msglen) return -1;
-
-#else /* !HAVE_WRITEV */
-
- if (writesocket(fd,header,index) != index) return -1;
- if (writesocket(fd,msg,msglen) != msglen) return -1;
-
-#endif /* !HAVE_WRITEV */
-
- return 0;
-}
diff --git a/lib/erl_interface/src/not_used/ei_send_reg.c b/lib/erl_interface/src/not_used/ei_send_reg.c
deleted file mode 100644
index ba9c7348f9..0000000000
--- a/lib/erl_interface/src/not_used/ei_send_reg.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2001-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- *
-
- */
-#ifdef __WIN32__
-#include <winsock2.h>
-#include <windows.h>
-#include <winbase.h>
-
-#elif VXWORKS
-#include <sys/types.h>
-#include <unistd.h>
-
-#else /* unix */
-#include <sys/types.h>
-#include <unistd.h>
-#include <sys/uio.h>
-#endif
-
-#include "eidef.h"
-#include "eiext.h"
-#include "ei_connect.h"
-#include "ei_internal.h"
-#include "putget.h"
-#include "ei_trace.h"
-#include "show_msg.h"
-
-/* FIXME this is not useed !!!!! */
-/* FIXME merge with ei_send.c */
-
-/* length (4), PASS_THROUGH (1), header, message */
-int ei_ei_send_reg_encoded(ei_cnode* ec, int fd, const erlang_pid *from,
- const char *to, const char *msg, int msglen)
-{
- char *s, header[1400]; /* see size calculation below */
- erlang_trace *token = NULL;
- int index = 5; /* reserve 5 bytes for control message */
-#ifdef HAVE_WRITEV
- struct iovec v[2];
-#endif
-
- /* are we tracing? */
- /* check that he can receive trace tokens first */
- if (ei_distversion(fd) > 0)
- token = ei_trace(0,(erlang_trace *)NULL);
-
- /* header = REG_SEND, from, cookie, toname max sizes: */
- ei_encode_version(header,&index); /* 1 */
- if (token) {
- ei_encode_tuple_header(header,&index,5); /* 2 */
- ei_encode_long(header,&index,ERL_REG_SEND_TT); /* 2 */
- } else {
- ei_encode_tuple_header(header,&index,4);
- ei_encode_long(header,&index,ERL_REG_SEND);
- }
- ei_encode_pid(header,&index,from); /* 268 */
- ei_encode_atom(header,&index,"" /*ei_getfdcookie(ec, fd)*/ ); /* 258 */
- ei_encode_atom(header,&index,to); /* 268 */
-
- if (token) ei_encode_trace(header,&index,token); /* 534 */
-
- /* control message (precedes header actually) */
- /* length = 1 ('p') + header len + message len */
- s = header;
- put32be(s, index + msglen - 4); /* 4 */
- put8(s, ERL_PASS_THROUGH); /* 1 */
- /*** sum: 1336 */
-
-#ifdef DEBUG_DIST
- if (ei_trace_distribution > 0) ei_show_sendmsg(stderr,header,msg);
-#endif
-
-#ifdef HAVE_WRITEV
-
- v[0].iov_base = (char *)header;
- v[0].iov_len = index;
- v[1].iov_base = (char *)msg;
- v[1].iov_len = msglen;
-
- if (writev(fd,v,2) != index+msglen) return -1;
-
-#else
-
- /* no writev() */
- if (writesocket(fd,header,index) != index) return -1;
- if (writesocket(fd,msg,msglen) != msglen) return -1;
-
-#endif
-
- return 0;
-}
diff --git a/lib/erl_interface/src/not_used/send_link.c b/lib/erl_interface/src/not_used/send_link.c
deleted file mode 100644
index c528f93ee0..0000000000
--- a/lib/erl_interface/src/not_used/send_link.c
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2020. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- *
-
- */
-#ifdef __WIN32__
-#include <winsock2.h>
-#include <windows.h>
-#include <winbase.h>
-
-#elif VXWORKS
-#include <unistd.h>
-
-#else /* unix */
-#include <unistd.h>
-
-#endif
-
-#include <string.h>
-#include <stdlib.h>
-#include "eidef.h"
-#include "eiext.h"
-#include "eisend.h"
-#include "ei_internal.h"
-#include "putget.h"
-#include "erl_rport.h"
-
-
-/* this sends either link or unlink ('which' decides) */
-static int link_unlink(int fd, const erlang_pid *from, const erlang_pid *to,
- int which, unsigned ms)
-{
- char msgbuf[EISMALLBUF];
- char *s;
- int index = 0;
- int n;
- unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
-
- index = 5; /* max sizes: */
- ei_encode_version(msgbuf,&index); /* 1 */
- ei_encode_tuple_header(msgbuf,&index,3);
- ei_encode_long(msgbuf,&index,which);
- ei_encode_pid(msgbuf,&index,from); /* 268 */
- ei_encode_pid(msgbuf,&index,to); /* 268 */
-
- /* 5 byte header missing */
- s = msgbuf;
- put32be(s, index - 4); /* 4 */
- put8(s, ERL_PASS_THROUGH); /* 1 */
- /* sum: 542 */
-
-
-#ifdef DEBUG_DIST
- if (ei_trace_distribution > 1) ei_show_sendmsg(stderr,msgbuf,NULL);
-#endif
-
- n = ei_write_fill_t__(fd,msgbuf,index,tmo);
-
- return (n==index ? 0 : -1);
-}
-
-/* FIXME not used? */
-#if 0
-/* use this to send a link */
-int ei_send_unlink(int fd, const erlang_pid *from, const erlang_pid *to)
-{
- return link_unlink(fd, from, to, ERL_UNLINK,0);
-}
-
-/* use this to send an unlink */
-int ei_send_link(int fd, const erlang_pid *from, const erlang_pid *to)
-{
- return link_unlink(fd, from, to, ERL_LINK,0);
-}
-/* use this to send a link */
-int ei_send_unlink_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
- unsigned ms)
-{
- return link_unlink(fd, from, to, ERL_UNLINK,ms);
-}
-
-/* use this to send an unlink */
-int ei_send_link_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
- unsigned ms)
-{
- return link_unlink(fd, from, to, ERL_LINK,ms);
-}
-#endif
diff --git a/lib/erl_interface/src/not_used/whereis.c b/lib/erl_interface/src/not_used/whereis.c
deleted file mode 100644
index 4072fa7b33..0000000000
--- a/lib/erl_interface/src/not_used/whereis.c
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- *
-
- */
-#ifdef __WIN32__
-#include <winsock2.h>
-#include <windows.h>
-#include <winbase.h>
-
-#elif VXWORKS
-#include <unistd.h>
-
-#else /* unix */
-#include <unistd.h>
-
-#endif
-
-#include <string.h>
-#include <stdlib.h>
-#include "erl_interface.h"
-#include "erl_connect.h"
-#include "erl_format.h"
-#include "erl_eterm.h"
-#include "erl_malloc.h"
-
-/* FIXME rewrite to ei functions */
-/* FIXME not used */
-
-erlang_pid *erl_whereis(int fd, const char *name)
-{
- ETERM *reply;
- ETERM *n;
- /* FIXME problem for threaded ? */
- static erlang_pid pid;
-
- n = erl_format("[~a]",name);
- reply = erl_rpc(fd,"erlang","whereis",n);
- erl_free_term(n);
-
- if (reply && (ERL_IS_PID(reply))) {
- char *node;
- node = ERL_PID_NODE(reply);
- strcpy(pid.node,node);
- pid.num = ERL_PID_NUMBER(reply);
- pid.serial = ERL_PID_SERIAL(reply);
- pid.creation = ERL_PID_CREATION(reply);
- erl_free_term(reply);
- return &pid;
- }
-
- if (reply) erl_free_term(reply);
- return NULL;
-}
-
diff --git a/lib/erl_interface/src/prog/ei_fake_prog.c b/lib/erl_interface/src/prog/ei_fake_prog.c
index 13fb71b880..eceea28051 100644
--- a/lib/erl_interface/src/prog/ei_fake_prog.c
+++ b/lib/erl_interface/src/prog/ei_fake_prog.c
@@ -54,11 +54,7 @@
/* #include <netdb.h> now included by ei.h */
#include "ei.h"
-#ifdef VXWORKS
-int ei_fake_prog_main(void)
-#else
int main(void)
-#endif
{
ErlConnect conp;
Erl_IpAddr thisipaddr = (Erl_IpAddr)0;
@@ -91,12 +87,10 @@ int main(void)
unsigned long *ulongp = NULL;
unsigned long ulongx = 0;
void *voidp = NULL;
-#ifndef VXWORKS
EI_LONGLONG *longlongp = (EI_LONGLONG*)NULL;
EI_LONGLONG longlongx = 0;
EI_ULONGLONG *ulonglongp = (EI_ULONGLONG*)NULL;
EI_ULONGLONG ulonglongx = 0;
-#endif
erlang_char_encoding enc;
ei_socket_callbacks cbs;
@@ -260,8 +254,6 @@ int main(void)
}
#endif /* HAVE_GMP_H && HAVE_LIBGMP */
-#ifndef VXWORKS
-
ei_decode_longlong(charp, intp, longlongp);
ei_decode_ulonglong(charp, intp, ulonglongp);
ei_encode_longlong(charp, intp, longlongx);
@@ -269,8 +261,6 @@ int main(void)
ei_x_encode_longlong(&eix, longlongx);
ei_x_encode_ulonglong(&eix, ulonglongx);
-#endif
-
#ifdef USE_EI_UNDOCUMENTED
ei_decode_intlist(charp, intp, longp, intp);
diff --git a/lib/erl_interface/src/prog/erl_call.c b/lib/erl_interface/src/prog/erl_call.c
index 46b4bc5dab..1c9bd69a96 100644
--- a/lib/erl_interface/src/prog/erl_call.c
+++ b/lib/erl_interface/src/prog/erl_call.c
@@ -33,23 +33,6 @@
#include <windows.h>
#include <winbase.h>
-#elif VXWORKS
-
-#include <stdio.h>
-#include <string.h>
-#include <vxWorks.h>
-#include <hostLib.h>
-#include <selectLib.h>
-#include <ifLib.h>
-#include <sockLib.h>
-#include <taskLib.h>
-#include <inetLib.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <time.h>
-
#else /* unix */
#include <sys/types.h>
@@ -88,11 +71,6 @@
#include "ei_resolve.h"
#include "erl_start.h" /* FIXME remove dependency */
-/*
- * Some nice global variables
- * (I don't think "nice" is the right word actually... -gordon)
- */
-/* FIXME problem for threaded ? */
struct call_flags {
int startp;
@@ -100,10 +78,14 @@ struct call_flags {
int modp;
int evalp;
int randomp;
+ int dynamic_name;
int use_long_name; /* indicates if -name was used, else -sname or -n */
+ int use_localhost_fallback;
int debugp;
int verbosep;
int haltp;
+ long port;
+ char *hostname;
char *cookie;
char *node;
char *hidden;
@@ -124,6 +106,23 @@ static void* ei_chk_calloc(size_t nmemb, size_t size);
static void* ei_chk_realloc(void *old, size_t size);
static char* ei_chk_strdup(char *s);
+/* Converts the given hostname to a shortname, if required. */
+static void format_node_hostname(const struct call_flags *flags,
+ const char *hostname,
+ char dst[EI_MAXHOSTNAMELEN + 1])
+{
+ char *ct;
+
+ strncpy(dst, hostname, EI_MAXHOSTNAMELEN);
+ dst[EI_MAXHOSTNAMELEN] = '\0';
+
+ /* If shortnames, cut off the name at first '.' */
+ if (flags->use_long_name == 0 && (ct = strchr(dst, '.'))) {
+ *ct = '\0';
+ }
+}
+
+static void start_timeout(int timeout);
/***************************************************************************
*
@@ -131,20 +130,13 @@ static char* ei_chk_strdup(char *s);
*
***************************************************************************/
-/* FIXME isn't VxWorks to handle arguments differently? */
-
-#if !defined(VXWORKS)
int main(int argc, char *argv[])
-#else
-int erl_call(int argc, char **argv)
-#endif
{
int i = 1,fd,creation;
struct hostent *hp;
char host_name[EI_MAXHOSTNAMELEN+1];
char nodename[MAXNODELEN+1];
char *p = NULL;
- char *ct = NULL; /* temporary used when truncating nodename */
int modsize = 0;
char *host = NULL;
char *module = NULL;
@@ -152,6 +144,8 @@ int erl_call(int argc, char **argv)
struct call_flags flags = {0}; /* Default 0 and NULL in all fields */
char* progname = argv[0];
ei_cnode ec;
+ flags.port = -1;
+ flags.hostname = NULL;
ei_init();
@@ -177,6 +171,47 @@ int erl_call(int argc, char **argv)
flags.node = ei_chk_strdup(argv[i+1]);
i++;
flags.use_long_name = 1;
+ } else if (strcmp(argv[i], "-address") == 0) { /* -address [HOST:]PORT */
+ if (i+1 >= argc) {
+ usage_arg(progname, "-address ");
+ }
+ {
+ char* hostname_port_arg = ei_chk_strdup(argv[i+1]);
+ char* address_string_end = strchr(hostname_port_arg, ':');
+ if (address_string_end == NULL) {
+ flags.port = strtol(hostname_port_arg, NULL, 10);
+ } else {
+ flags.port = strtol(address_string_end + 1, NULL, 10);
+ /* Remove port part from hostname_port_arg*/
+ *address_string_end = '\0';
+ if (strlen(hostname_port_arg) > 0) {
+ flags.hostname = hostname_port_arg;
+ }
+ }
+
+ if (flags.port < 1 || flags.port > 65535) {
+ usage_error(progname, "-address");
+ }
+ i++;
+ }
+ } else if (strcmp(argv[i], "-timeout") == 0) {
+ long timeout;
+
+ if (i+1 >= argc) {
+ usage_arg(progname, "-timeout ");
+ }
+
+ timeout = strtol(argv[i+1], NULL, 10);
+ if (timeout <= 0 || timeout >= (1 << 20)) {
+ usage_error(progname, "-timeout");
+ }
+
+ start_timeout(timeout);
+ i++;
+ } else if (strcmp(argv[i], "-__uh_test__") == 0) {
+ /* Fakes a failure in the call to ei_gethostbyname(h_hostname) so
+ * we can test the localhost fallback. */
+ flags.use_localhost_fallback = 1;
} else {
if (strlen(argv[i]) != 2) {
usage_error(progname, argv[i]);
@@ -198,6 +233,9 @@ int erl_call(int argc, char **argv)
case 'r':
flags.randomp = 1;
break;
+ case 'R':
+ flags.dynamic_name = 1;
+ break;
case 'e':
flags.evalp = 1;
break;
@@ -251,11 +289,12 @@ int erl_call(int argc, char **argv)
} /* while */
-
/*
* Can't have them both !
*/
- if (flags.modp && flags.evalp) {
+ if ((flags.modp && flags.evalp) ||
+ (flags.port != -1 && flags.startp) ||
+ (flags.port != -1 && flags.node)) {
usage(progname);
}
@@ -284,7 +323,7 @@ int erl_call(int argc, char **argv)
/*
* What we, at least, requires !
*/
- if (flags.node == NULL) {
+ if (flags.node == NULL && flags.port == -1) {
usage(progname);
}
@@ -292,50 +331,52 @@ int erl_call(int argc, char **argv)
flags.cookie = NULL;
}
- /* FIXME decide how many bits etc or leave to connect_xinit? */
- creation = (time(NULL) % 3) + 1; /* "random" */
+ creation = time(NULL) + 1; /* "random" */
- if (flags.hidden == NULL) {
+ if (flags.hidden == NULL && !flags.dynamic_name) {
/* As default we are c17@gethostname */
i = flags.randomp ? (time(NULL) % 997) : 17;
flags.hidden = (char *) ei_chk_malloc(10 + 2 ); /* c17 or cXYZ */
-#if defined(VXWORKS)
- sprintf(flags.hidden, "c%d",
- i < 0 ? (int) taskIdSelf() : i);
-#else
sprintf(flags.hidden, "c%d",
i < 0 ? (int) getpid() : i);
-#endif
}
{
/* A name for our hidden node was specified */
char h_hostname[EI_MAXHOSTNAMELEN+1];
- char h_nodename[MAXNODELEN+1];
- char *h_alivename=flags.hidden;
+ char h_nodename_buf[MAXNODELEN+1];
+ char *h_nodename = h_nodename_buf;
+ char *h_alivename = flags.hidden;
struct in_addr h_ipadr;
- char* ct;
/* gethostname requires len to be max(hostname) + 1 */
if (gethostname(h_hostname, EI_MAXHOSTNAMELEN+1) < 0) {
- fprintf(stderr,"erl_call: failed to get host name: %d\n", errno);
- exit(1);
- }
- if ((hp = ei_gethostbyname(h_hostname)) == 0) {
- fprintf(stderr,"erl_call: can't resolve hostname %s\n", h_hostname);
- exit(1);
+ fprintf(stderr,"erl_call: failed to get host name: %d\n", errno);
+ exit(1);
}
- /* If shortnames, cut off the name at first '.' */
- if (flags.use_long_name == 0 && (ct = strchr(hp->h_name, '.')) != NULL) {
- *ct = '\0';
+
+ if (flags.use_localhost_fallback || (hp = ei_gethostbyname(h_hostname)) == 0) {
+ /* Failed to resolve our own hostname; try binding to loopback and
+ * hope for the best. */
+ hp = ei_gethostbyname("localhost");
+ flags.use_localhost_fallback = 1;
+
+ format_node_hostname(&flags, h_hostname, h_hostname);
+ } else {
+ format_node_hostname(&flags, hp->h_name, h_hostname);
}
- strncpy(h_hostname, hp->h_name, EI_MAXHOSTNAMELEN);
- h_hostname[EI_MAXHOSTNAMELEN] = '\0';
+
memcpy(&h_ipadr.s_addr, *hp->h_addr_list, sizeof(struct in_addr));
- if (strlen(h_alivename) + strlen(h_hostname) + 2 > sizeof(h_nodename)) {
- fprintf(stderr,"erl_call: hostname too long: %s\n", h_hostname);
- exit(1);
+ if (h_alivename) {
+ if (strlen(h_alivename) + strlen(h_hostname) + 2 > sizeof(h_nodename_buf)) {
+ fprintf(stderr,"erl_call: hostname too long: %s\n", h_hostname);
+ exit(1);
+ }
+ sprintf(h_nodename, "%s@%s", h_alivename, h_hostname);
+ }
+ else {
+ /* dynamic node name */
+ h_nodename = NULL;
}
- sprintf(h_nodename, "%s@%s", h_alivename, h_hostname);
if (ei_connect_xinit(&ec, h_hostname, h_alivename, h_nodename,
(Erl_IpAddr)&h_ipadr, flags.cookie,
@@ -346,50 +387,73 @@ int erl_call(int argc, char **argv)
}
}
- if ((p = strchr((const char *)flags.node, (int) '@')) == 0) {
+ if (flags.port != -1 && flags.hostname != NULL) {
+ host = flags.hostname;
+ strcpy(host_name, flags.hostname);
+ } else if ((flags.port != -1 && flags.hostname == NULL) ||
+ (strchr((const char *)flags.node, (int) '@') == 0)) {
strcpy(host_name, ei_thishostname(&ec));
host = host_name;
} else {
+ p = strchr((const char *)flags.node, (int) '@');
*p = 0;
host = p+1;
}
- /*
- * Expand name to a real name (may be ip-address)
- */
- /* FIXME better error string */
- if ((hp = ei_gethostbyname(host)) == 0) {
- fprintf(stderr,"erl_call: can't ei_gethostbyname(%s)\n", host);
- exit(1);
- }
- /* If shortnames, cut off the name at first '.' */
- if (flags.use_long_name == 0 && (ct = strchr(hp->h_name, '.')) != NULL) {
- *ct = '\0';
- }
- strncpy(host_name, hp->h_name, EI_MAXHOSTNAMELEN);
- host_name[EI_MAXHOSTNAMELEN] = '\0';
- if (strlen(flags.node) + strlen(host_name) + 2 > sizeof(nodename)) {
- fprintf(stderr,"erl_call: nodename too long: %s\n", flags.node);
- exit(1);
+ if (flags.use_localhost_fallback && strcmp(host, ei_thishostname(&ec)) == 0) {
+ /* We're on the same host *and* have used the localhost fallback, so we
+ * skip canonical name resolution since it's bound to fail.
+ *
+ * `ei_connect` will do the right thing later on. */
+ strcpy(host_name, ei_thishostname(&ec));
+ } else {
+ if ((hp = ei_gethostbyname(host)) == 0) {
+ fprintf(stderr,"erl_call: can't ei_gethostbyname(%s)\n", host);
+ exit(1);
+ }
+
+ format_node_hostname(&flags, hp->h_name, host_name);
}
- sprintf(nodename, "%s@%s", flags.node, host_name);
+ if (flags.port == -1) {
+ if (strlen(flags.node) + strlen(host_name) + 2 > sizeof(nodename)) {
+ fprintf(stderr,"erl_call: nodename too long: %s\n", flags.node);
+ exit(1);
+ }
+ sprintf(nodename, "%s@%s", flags.node, host_name);
+ }
/*
* Try to connect. Start an Erlang system if the
* start option is on and no system is running.
*/
if (flags.startp && !flags.haltp) {
fd = do_connect(&ec, nodename, &flags);
- } else if ((fd = ei_connect(&ec, nodename)) < 0) {
- /* We failed to connect ourself */
- /* FIXME do we really know we failed because of node not up? */
- if (flags.haltp) {
- exit(0);
- } else {
- fprintf(stderr,"erl_call: failed to connect to node %s\n",
- nodename);
- exit(1);
- }
+ } else if (flags.port == -1) {
+ if ((fd = ei_connect(&ec, nodename)) < 0) {
+ /* We failed to connect ourself */
+ /* FIXME do we really know we failed because of node not up? */
+ if (flags.haltp) {
+ exit(0);
+ } else {
+ fprintf(stderr,"erl_call: failed to connect to node %s\n",
+ nodename);
+ exit(1);
+ }
+ }
+ } else {
+ /* Connect using address:port */
+ if ((fd = ei_connect_host_port(&ec, host, (int)flags.port)) < 0) {
+ /* We failed to connect ourself */
+ /* FIXME do we really know we failed because of node not up? */
+ if (flags.haltp) {
+ exit(0);
+ } else {
+ fprintf(stderr,"erl_call: failed to connect to node with address \"%s:%ld\"\n",
+ flags.hostname == NULL ? "" : flags.hostname,
+ flags.port);
+ exit(1);
+ }
+ }
}
/* If we are connected and the halt switch is set */
@@ -415,8 +479,14 @@ int erl_call(int argc, char **argv)
}
if (flags.verbosep) {
- fprintf(stderr,"erl_call: we are now connected to node \"%s\"\n",
- nodename);
+ if (flags.port == -1) {
+ fprintf(stderr,"erl_call: we are now connected to node \"%s\"\n",
+ nodename);
+ } else {
+ fprintf(stderr,"erl_call: we are now connected to node with address \"%s:%ld\"\n",
+ flags.hostname == NULL ? "": flags.hostname,
+ flags.port);
+ }
}
/*
@@ -799,6 +869,25 @@ static int get_module(char **mbuf, char **mname)
} /* get_module */
+#ifdef __WIN32__
+static DWORD WINAPI timer_thread(void *data) {
+ DWORD_PTR timeout = (DWORD_PTR)data * 1000;
+
+ Sleep(timeout);
+ exit(1);
+}
+
+static void start_timeout(int timeout) {
+ if (CreateThread(NULL, 0, timer_thread, (void*)timeout, 0, NULL) == NULL) {
+ fprintf(stderr,"erl_call: Failed to start timer thread\n");
+ exit(1);
+ }
+}
+#else
+static void start_timeout(int timeout) {
+ alarm(timeout);
+}
+#endif
/***************************************************************************
*
@@ -808,8 +897,8 @@ static int get_module(char **mbuf, char **mname)
static void usage_noexit(const char *progname) {
fprintf(stderr,"\nUsage: %s [-[demqrsv]] [-c Cookie] [-h HiddenName] \n", progname);
- fprintf(stderr," [-x ErlScript] [-a [Mod [Fun [Args]]]]\n");
- fprintf(stderr," (-n Node | -sname Node | -name Node)\n\n");
+ fprintf(stderr," [-x ErlScript] [-a [Mod [Fun [Args]]]] [-timeout Secs]\n");
+ fprintf(stderr," (-n Node | -sname Node | -name Node | -address [HOSTNAME:]PORT)\n\n");
#ifdef __WIN32__
fprintf(stderr," where: -a apply(Mod,Fun,Args) (e.g -a \"erlang length [[a,b,c]]\"\n");
#else
@@ -817,12 +906,19 @@ static void usage_noexit(const char *progname) {
#endif
fprintf(stderr," -c cookie string; by default read from ~/.erlang.cookie\n");
fprintf(stderr," -d direct Erlang output to ~/.erl_call.out.<Nodename>\n");
- fprintf(stderr," -e evaluate contents of standard input (e.g echo \"X=1,Y=2,{X,Y}.\"|erl_call -e ...)\n");
+ fprintf(stderr," -e evaluate contents of standard input (e.g., echo \"X=1,Y=2,{X,Y}.\"|%s -e ...)\n",
+ progname);
fprintf(stderr," -h specify a name for the erl_call client node\n");
fprintf(stderr," -m read and compile Erlang module from stdin\n");
fprintf(stderr," -n name of Erlang node, same as -name\n");
fprintf(stderr," -name name of Erlang node, expanded to a fully qualified\n");
fprintf(stderr," -sname name of Erlang node, short form will be used\n");
+ fprintf(stderr," -address [HOSTNAME:]PORT of Erlang node\n"
+ " (the default hostname is the hostname of the local manchine)\n"
+ " (e.g., %s -address my_host:36303 ...)\n"
+ " (cannot be combinated with -s, -n, -name and -sname)\n",
+ progname);
+ fprintf(stderr," -timeout command timeout, in seconds\n");
fprintf(stderr," -q halt the Erlang node (overrides the -s switch)\n");
fprintf(stderr," -r use a random name for the erl_call client node\n");
fprintf(stderr," -s start a new Erlang node if necessary\n");
diff --git a/lib/erl_interface/src/prog/erl_fake_prog.c b/lib/erl_interface/src/prog/erl_fake_prog.c
deleted file mode 100644
index 093bad8d7c..0000000000
--- a/lib/erl_interface/src/prog/erl_fake_prog.c
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2002-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-/***************************************************************************
- *
- * This is a fake program that contains all functions, variables and
- * defined symbols mentioned in the manual. We compile this file to see
- * that the header files and created library is complete.
- *
- * You can't run this program, it is for compiling and linking only.
- *
- ***************************************************************************/
-
-/* Use most of
- * CFLAGS="-I../include -g -O2
- * -ansi -pedantic
- * -Wall
- * -Wshadow
- * -Wstrict-prototypes
- * -Wmissing-prototypes
- * -Wmissing-declarations
- * -Wnested-externs
- * -Winline
- * -Werror"
- */
-
-/* #include <netdb.h> now included by ei.h */
-#include "erl_interface.h"
-
-#ifdef VXWORKS
-int erl_fake_prog_main(void)
-#else
-int main(void)
-#endif
-{
- ei_x_buff eix;
- int index = 0;
- ETERM **etermpp = NULL, *etermp = NULL;
- char *charp = NULL;
- unsigned char uchar, **ucharpp = NULL, *ucharp = NULL;
- void *voidp = NULL;
- Erl_Heap *erl_heapp = NULL;
- int intx = 0;
- int *intp = NULL;
- unsigned int uintx, *uintp;
- unsigned long *ulongp = NULL;
- long longx = 0;
- double doublex = 0.0;
- short shortx = 42;
- FILE *filep = NULL;
- Erl_IpAddr erl_ipaddr = NULL;
- ErlMessage *erlmessagep = NULL;
- ErlConnect *erlconnectp = NULL;
- struct hostent *hostp = NULL;
- struct in_addr *inaddrp = NULL;
-
- /* Converion to erl_interface format is in liberl_interface */
-
- intx = erl_errno;
-
- ei_encode_term(charp, &index, voidp);
- ei_x_encode_term(&eix, voidp);
- ei_decode_term(charp, &index, voidp);
-
- erl_init(voidp, longx);
- erl_connect_init(intx, charp,shortx);
- erl_connect_xinit(charp,charp,charp,erl_ipaddr,charp,shortx);
- erl_connect(charp);
- erl_xconnect(erl_ipaddr,charp);
- erl_close_connection(intx);
- erl_receive(intx, ucharp, intx);
- erl_receive_msg(intx, ucharp, intx, erlmessagep);
- erl_xreceive_msg(intx, ucharpp, intp, erlmessagep);
- erl_send(intx, etermp, etermp);
- erl_reg_send(intx, charp, etermp);
- erl_rpc(intx,charp,charp,etermp);
- erl_rpc_to(intx,charp,charp,etermp);
- erl_rpc_from(intx,intx,erlmessagep);
-
- erl_publish(intx);
- erl_accept(intx,erlconnectp);
-
- erl_thiscookie();
- erl_thisnodename();
- erl_thishostname();
- erl_thisalivename();
- erl_thiscreation();
- erl_unpublish(charp);
- erl_err_msg(charp);
- erl_err_quit(charp);
- erl_err_ret(charp);
- erl_err_sys(charp);
-
- erl_cons(etermp,etermp);
- erl_copy_term(etermp);
- erl_element(intx,etermp);
-
- erl_hd(etermp);
- erl_iolist_to_binary(etermp);
- erl_iolist_to_string(etermp);
- erl_iolist_length(etermp);
- erl_length(etermp);
- erl_mk_atom(charp);
- erl_mk_binary(charp,intx);
- erl_mk_empty_list();
- erl_mk_estring(charp, intx);
- erl_mk_float(doublex);
- erl_mk_int(intx);
- erl_mk_list(etermpp,intx);
- erl_mk_pid(charp,uintx,uintx,uchar);
- erl_mk_port(charp,uintx,uchar);
- erl_mk_ref(charp,uintx,uchar);
- erl_mk_long_ref(charp,uintx,uintx,uintx,uchar);
- erl_mk_string(charp);
- erl_mk_tuple(etermpp,intx);
- erl_mk_uint(uintx);
- erl_mk_var(charp);
- erl_print_term(filep,etermp);
- /* erl_sprint_term(charp,etermp); */
- erl_size(etermp);
- erl_tl(etermp);
- erl_var_content(etermp, charp);
-
- erl_format(charp);
- erl_match(etermp, etermp);
-
- erl_global_names(intx, intp);
- erl_global_register(intx, charp, etermp);
- erl_global_unregister(intx, charp);
- erl_global_whereis(intx, charp, charp);
-
- erl_init_malloc(erl_heapp,longx);
- erl_alloc_eterm(uchar);
- erl_eterm_release();
- erl_eterm_statistics(ulongp,ulongp);
- erl_free_array(etermpp,intx);
- erl_free_term(etermp);
- erl_free_compound(etermp);
- erl_malloc(longx);
- erl_free(voidp);
-
- erl_compare_ext(ucharp, ucharp);
- erl_decode(ucharp);
- erl_decode_buf(ucharpp);
- erl_encode(etermp,ucharp);
- erl_encode_buf(etermp,ucharpp);
- erl_ext_size(ucharp);
- erl_ext_type(ucharp);
- erl_peek_ext(ucharp,intx);
- erl_term_len(etermp);
-
- erl_gethostbyname(charp);
- erl_gethostbyaddr(charp, intx, intx);
- erl_gethostbyname_r(charp, hostp, charp, intx, intp);
- erl_gethostbyaddr_r(charp, intx, intx, hostp, charp, intx, intp);
-
- erl_init_resolve();
- erl_distversion(intx);
-
- erl_epmd_connect(inaddrp);
- erl_epmd_port(inaddrp, charp, intp);
-
- charp = ERL_ATOM_PTR(etermp);
- intx = ERL_ATOM_SIZE(etermp);
- ucharp = ERL_BIN_PTR(etermp);
- intx = ERL_BIN_SIZE(etermp);
- etermp = ERL_CONS_HEAD(etermp);
- etermp = ERL_CONS_TAIL(etermp);
- intx = ERL_COUNT(etermp);
- doublex= ERL_FLOAT_VALUE(etermp);
- uintx = ERL_INT_UVALUE(etermp);
- intx = ERL_INT_VALUE(etermp);
- intx = ERL_IS_ATOM(etermp);
- intx = ERL_IS_BINARY(etermp);
- intx = ERL_IS_CONS(etermp);
- intx = ERL_IS_EMPTY_LIST(etermp);
- intx = ERL_IS_FLOAT(etermp);
- intx = ERL_IS_INTEGER(etermp);
- intx = ERL_IS_LIST(etermp);
- intx = ERL_IS_PID(etermp);
- intx = ERL_IS_PORT(etermp);
- intx = ERL_IS_REF(etermp);
- intx = ERL_IS_TUPLE(etermp);
- intx = ERL_IS_UNSIGNED_INTEGER(etermp);
- uchar = ERL_PID_CREATION(etermp);
- charp = ERL_PID_NODE(etermp);
- uintx = ERL_PID_NUMBER(etermp);
- uintx = ERL_PID_SERIAL(etermp);
- uchar = ERL_PORT_CREATION(etermp);
- charp = ERL_PORT_NODE(etermp);
- uintx = ERL_PORT_NUMBER(etermp);
- uchar = ERL_REF_CREATION(etermp);
- intx = ERL_REF_LEN(etermp);
- charp = ERL_REF_NODE(etermp);
- uintx = ERL_REF_NUMBER(etermp);
- uintp = ERL_REF_NUMBERS(etermp);
- etermp = ERL_TUPLE_ELEMENT(etermp,intx);
- intx = ERL_TUPLE_SIZE(etermp);
-
- return
- BUFSIZ +
- EAGAIN +
- EHOSTUNREACH +
- EINVAL +
- EIO +
- EMSGSIZE +
- ENOMEM +
- ERL_ATOM +
- ERL_BINARY +
- ERL_ERROR +
- ERL_EXIT +
- ERL_FLOAT +
- ERL_INTEGER +
- ERL_LINK +
- ERL_LIST +
- ERL_MSG +
- ERL_NO_TIMEOUT +
- ERL_PID +
- ERL_PORT +
- ERL_REF +
- ERL_REG_SEND +
- ERL_SEND +
- ERL_SMALL_BIG +
- ERL_TICK +
- ERL_TIMEOUT +
- ERL_TUPLE +
- ERL_UNLINK +
- ERL_U_INTEGER +
- ERL_U_SMALL_BIG +
- ERL_VARIABLE +
- ETIMEDOUT +
- MAXNODELEN +
- MAXREGLEN;
-}
diff --git a/lib/erl_interface/src/prog/erl_start.c b/lib/erl_interface/src/prog/erl_start.c
index c9be941f19..9c876feb5e 100644
--- a/lib/erl_interface/src/prog/erl_start.c
+++ b/lib/erl_interface/src/prog/erl_start.c
@@ -31,40 +31,7 @@
#include <windows.h>
#include <winbase.h>
-#elif VXWORKS
-#include <stdio.h>
-#include <string.h>
-#include <vxWorks.h>
-#include <hostLib.h>
-#include <selectLib.h>
-#include <ifLib.h>
-#include <sockLib.h>
-#include <taskLib.h>
-#include <inetLib.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <symLib.h>
-#include <sysSymTbl.h>
-#include <sysLib.h>
-#include <tickLib.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-# include <sys/time.h>
-# else
-# include <time.h>
-# endif
-#endif
-
-#include <a_out.h>
-
-/* #include "netdb.h" */
-#else /* other unix */
+#else /* unix */
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
@@ -111,9 +78,9 @@ typedef socklen_t SocklenType;
static struct in_addr *get_addr(const char *hostname, struct in_addr *oaddr);
static int wait_for_erlang(int sockd, int magic, struct timeval *timeout);
-#if defined(VXWORKS) || defined(__WIN32__)
+#if defined(__WIN32__)
static int unique_id(void);
-static unsigned long spawn_erlang_epmd(ei_cnode *ec,
+static HANDLE spawn_erlang_epmd(ei_cnode *ec,
char *alive,
Erl_IpAddr adr,
int flags,
@@ -148,10 +115,10 @@ int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
int port;
int sockd = 0;
int one = 1;
-#if defined(VXWORKS) || defined(__WIN32__)
- unsigned long pid = 0;
+#if defined(__WIN32__)
+ HANDLE pid;
#else
- int pid = 0;
+ int pid;
#endif
int r = 0;
@@ -177,23 +144,17 @@ int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
listen(sockd,5);
-#if defined(VXWORKS) || defined(__WIN32__)
- if((pid = spawn_erlang_epmd(ec,alive,adr,flags,erl,args,port,1))
- == 0)
+#if defined(__WIN32__)
+ pid = spawn_erlang_epmd(ec,alive,adr,flags,erl,args,port,1);
+ if (pid == INVALID_HANDLE_VALUE)
return ERL_SYS_ERROR;
timeout.tv_usec = 0;
timeout.tv_sec = 10; /* ignoring ERL_START_TIME */
if((r = wait_for_erlang(sockd,unique_id(),&timeout))
== ERL_TIMEOUT) {
-#if defined(VXWORKS)
- taskDelete((int) pid);
- if(taskIdVerify((int) pid) != ERROR)
- taskDeleteForce((int) pid);
-#else /* Windows */
/* Well, this is not a nice way to do it, and it does not
always kill the emulator, but the alternatives are few.*/
- TerminateProcess((HANDLE) pid,1);
-#endif /* defined(VXWORKS) */
+ TerminateProcess(pid,1);
}
#else /* Unix */
switch ((pid = fork())) {
@@ -229,7 +190,7 @@ int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags,
}
}
-#endif /* defined(VXWORKS) || defined(__WIN32__) */
+#endif /* defined(__WIN32__) */
done:
#if defined(__WIN32__)
@@ -240,18 +201,10 @@ done:
return r;
} /* erl_start_sys() */
-#if defined(VXWORKS) || defined(__WIN32__)
-#if defined(VXWORKS)
-#define DEF_ERL_COMMAND ""
-#define DEF_EPMD_COMMAND ""
-#define ERLANG_SYM "start_erl"
-#define EPMD_SYM "start_epmd"
-#define ERL_REPLY_FMT "-s erl_reply reply %s %d %d"
-#else
+#if defined(__WIN32__)
#define DEF_ERL_COMMAND "erl"
#define DEF_EPMD_COMMAND "epmd"
#define ERL_REPLY_FMT "-s erl_reply reply \"%s\" \"%d\" \"%d\""
-#endif
#define ERL_NAME_FMT "-noinput -name %s"
#define ERL_SNAME_FMT "-noinput -sname %s"
@@ -259,11 +212,7 @@ done:
#define FORMATTED_INT_LEN 10
static int unique_id(void){
-#if defined(VXWORKS)
- return taskIdSelf();
-#else
return (int) GetCurrentThreadId();
-#endif
}
static int enquote_args(char **oargs, char ***qargs){
@@ -317,23 +266,10 @@ static void free_args(char **args){
free(args);
}
-#if defined(VXWORKS)
-static FUNCPTR lookup_function(char *symname){
- char *value;
- SYM_TYPE type;
- if(symFindByName(sysSymTbl,
- symname,
- &value,
- &type) == ERROR /*|| type != N_TEXT*/)
- return NULL;
- return (FUNCPTR) value;
-}
-#endif /* defined(VXWORKS) */
-
-/* In NT and VxWorks, we cannot fork(), Erlang and Epmd gets
+/* In NT we cannot fork(), Erlang and Epmd gets
spawned by this function instead. */
-static unsigned long spawn_erlang_epmd(ei_cnode *ec,
+static HANDLE spawn_erlang_epmd(ei_cnode *ec,
char *alive,
Erl_IpAddr adr,
int flags,
@@ -342,13 +278,9 @@ static unsigned long spawn_erlang_epmd(ei_cnode *ec,
int port,
int is_erlang)
{
-#if defined(VXWORKS)
- FUNCPTR erlfunc;
-#else /* Windows */
STARTUPINFO sinfo;
SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pinfo;
-#endif
char *cmdbuf;
int cmdlen;
char *ptr;
@@ -358,31 +290,23 @@ static unsigned long spawn_erlang_epmd(ei_cnode *ec,
struct in_addr myaddr;
struct in_addr *hisaddr = (struct in_addr *)adr;
char iaddrbuf[IP_ADDR_CHARS + 1];
- int ret;
+ HANDLE ret;
if(is_erlang){
get_addr(ei_thishostname(ec), &myaddr);
-#if defined(VXWORKS)
- inet_ntoa_b(myaddr, iaddrbuf);
-#else /* Windows */
if((ptr = inet_ntoa(myaddr)) == NULL)
- return 0;
+ return INVALID_HANDLE_VALUE;
else
strcpy(iaddrbuf,ptr);
-#endif
}
if ((flags & ERL_START_REMOTE) ||
(is_erlang && (hisaddr->s_addr != myaddr.s_addr))) {
- return 0;
+ return INVALID_HANDLE_VALUE;
} else {
num_args = enquote_args(args, &args);
for(cmdlen = i = 0; args[i] != NULL; ++i)
cmdlen += strlen(args[i]) + 1;
-#if !defined(VXWORKS)
- /* On VxWorks, we dont actually run a command,
- we call start_erl() */
if(!erl_or_epmd)
-#endif
erl_or_epmd = (is_erlang) ? DEF_ERL_COMMAND :
DEF_EPMD_COMMAND;
if(is_erlang){
@@ -417,23 +341,6 @@ static unsigned long spawn_erlang_epmd(ei_cnode *ec,
fprintf(stderr,"erl_call: commands are %s\n",cmdbuf);
}
/* OK, one single command line... */
-#if defined(VXWORKS)
- erlfunc = lookup_function((is_erlang) ? ERLANG_SYM :
- EPMD_SYM);
- if(erlfunc == NULL){
- if (flags & ERL_START_VERBOSE) {
- fprintf(stderr,"erl_call: failed to find symbol %s\n",
- (is_erlang) ? ERLANG_SYM : EPMD_SYM);
- }
- ret = 0;
- } else {
- /* Just call it, it spawns itself... */
- ret = (unsigned long)
- (*erlfunc)((int) cmdbuf,0,0,0,0,0,0,0,0,0);
- if(ret == (unsigned long) ERROR)
- ret = 0;
- }
-#else /* Windows */
/* Hmmm, hidden or unhidden window??? */
memset(&sinfo,0,sizeof(sinfo));
sinfo.cb = sizeof(STARTUPINFO);
@@ -457,10 +364,9 @@ static unsigned long spawn_erlang_epmd(ei_cnode *ec,
NULL,
&sinfo,
&pinfo))
- ret = 0;
+ ret = INVALID_HANDLE_VALUE;
else
- ret = (unsigned long) pinfo.hProcess;
-#endif
+ ret = pinfo.hProcess;
free(cmdbuf);
return ret;
}
@@ -488,7 +394,7 @@ static int exec_erlang(ei_cnode *ec,
char *args[],
int port)
{
-#if !defined(__WIN32__) && !defined(VXWORKS)
+#if !defined(__WIN32__)
int fd,len,l,i;
char **s;
char *argv[4];
@@ -587,7 +493,7 @@ static int exec_erlang(ei_cnode *ec,
return ERL_SYS_ERROR;
} /* exec_erlang() */
-#endif /* defined(VXWORKS) || defined(WINDOWS) */
+#endif /* defined(WINDOWS) */
#if defined(__WIN32__)
static void gettimeofday(struct timeval *now,void *dummy){
@@ -601,13 +507,6 @@ static void gettimeofday(struct timeval *now,void *dummy){
now->tv_usec = x % 1000000;
}
-#elif defined(VXWORKS)
-static void gettimeofday(struct timeval *now, void *dummy){
- int rate = sysClkRateGet(); /* Ticks per second */
- unsigned long ctick = tickGet();
- now->tv_sec = ctick / rate; /* secs since reboot */
- now->tv_usec = ((ctick - (now->tv_sec * rate))*1000000)/rate;
-}
#endif
diff --git a/lib/erl_interface/src/registry/reg_dump.c b/lib/erl_interface/src/registry/reg_dump.c
index a027c4cdf5..f90fd4d4b6 100644
--- a/lib/erl_interface/src/registry/reg_dump.c
+++ b/lib/erl_interface/src/registry/reg_dump.c
@@ -209,11 +209,11 @@ static int mn_send_write(int fd, erlang_pid *mnesia, const char *key, ei_reg_obj
break;
case EI_STR:
if (obj->size > 0) ei_encode_string(msgbuf,&index,obj->val.s);
- else ei_encode_long(msgbuf,&index, (long)NULL); /* just the NULL pointer */
+ else ei_encode_long(msgbuf,&index, 0); /* just the NULL pointer */
break;
case EI_BIN:
if (obj->size > 0) ei_encode_binary(msgbuf,&index,obj->val.p,obj->size);
- else ei_encode_long(msgbuf,&index,(long)(obj->val.p)); /* just the pointer */
+ else ei_encode_long(msgbuf,&index, obj->val.i); /* just the pointer */
break;
default:
if (dbuf) free(dbuf);
@@ -255,7 +255,7 @@ static int mn_get_unlink(int fd)
int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags)
{
ei_hash *tab;
- erlang_pid self;
+ erlang_pid *self;
erlang_pid mnesia;
ei_bucket *b;
ei_reg_obj *obj;
@@ -271,12 +271,10 @@ int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags)
if ((ec = ei_fd_to_cnode(fd)) == NULL) {
return -1;
}
- strcpy(self.node,ei_thisnodename(ec));
- self.num = fd;
- self.serial = 0;
- self.creation = ei_thiscreation(ec);
- if (mn_start_dump(fd,&self,&mnesia,mntab)) return -1;
+ self = ei_self(ec);
+
+ if (mn_start_dump(fd,self,&mnesia,mntab)) return -1;
/* traverse the table, passing objects to mnesia */
for (i=0; i<tab->size; i++) {
@@ -288,13 +286,13 @@ int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags)
if ((flags & EI_FORCE) || (obj->attr & EI_DIRTY)) {
if (obj->attr & EI_DELET) {
if (mn_send_delete(fd,&mnesia,key)) {
- ei_send_exit(fd,&self,&mnesia,"delete failed");
+ ei_send_exit(fd,self,&mnesia,"delete failed");
return -1;
}
}
else {
if (mn_send_write(fd,&mnesia,key,obj)) {
- ei_send_exit(fd,&self,&mnesia,"update failed");
+ ei_send_exit(fd,self,&mnesia,"update failed");
return -1;
}
}
@@ -304,8 +302,8 @@ int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags)
}
/* end the transaction */
- if (mn_send_commit(fd,&mnesia,&self)) {
- ei_send_exit(fd,&self,&mnesia,"commit failed");
+ if (mn_send_commit(fd,&mnesia,self)) {
+ ei_send_exit(fd,self,&mnesia,"commit failed");
return -1;
}
diff --git a/lib/erl_interface/src/registry/reg_get.c b/lib/erl_interface/src/registry/reg_get.c
index 67d99e231e..73975f6a91 100644
--- a/lib/erl_interface/src/registry/reg_get.c
+++ b/lib/erl_interface/src/registry/reg_get.c
@@ -19,10 +19,6 @@
*
*/
-#ifdef VXWORKS
-#include <vxWorks.h>
-#endif
-
#include <stdarg.h>
#include "reg.h"
diff --git a/lib/erl_interface/src/registry/reg_restore.c b/lib/erl_interface/src/registry/reg_restore.c
index 75d073303f..030bab19b9 100644
--- a/lib/erl_interface/src/registry/reg_restore.c
+++ b/lib/erl_interface/src/registry/reg_restore.c
@@ -227,7 +227,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
char *dbuf = NULL;
char *msgbuf = NULL;
char *keybuf = NULL;
- erlang_pid self;
+ erlang_pid *self;
erlang_pid mnesia = {"",0,0,0};
erlang_msg msg;
int index = 0;
@@ -247,20 +247,18 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
if ((ec = ei_fd_to_cnode(fd)) == NULL) {
return -1;
}
- strcpy(self.node,ei_thisnodename(ec));
- self.num = fd;
- self.serial = 0;
- self.creation = ei_thiscreation(ec);
+
+ self = ei_self(ec);
- if (mn_start_restore(fd,&self,&mnesia,mntab,&count,&maxkey,&maxobj)) {
+ if (mn_start_restore(fd,self,&mnesia,mntab,&count,&maxkey,&maxobj)) {
/* send exit *only* if we have pid */
- if (mnesia.node[0]) ei_send_exit(fd,&self,&mnesia,"bad response from rpc start");
+ if (mnesia.node[0]) ei_send_exit(fd,self,&mnesia,"bad response from rpc start");
return -1;
}
if (count <= 0) {
- ei_send_exit(fd,&self,&mnesia,"nothing to do");
+ ei_send_exit(fd,self,&mnesia,"nothing to do");
return 0;
}
@@ -268,7 +266,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
len = maxkey + maxobj + 512;
if (len > EISMALLBUF)
if (!(dbuf = malloc(len))) {
- ei_send_exit(fd,&self,&mnesia,"cannot allocate space for incoming data");
+ ei_send_exit(fd,self,&mnesia,"cannot allocate space for incoming data");
return -1;
}
msgbuf = (dbuf ? dbuf : sbuf);
@@ -281,7 +279,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
ei_encode_version(msgbuf,&index);
ei_encode_tuple_header(msgbuf,&index,2);
ei_encode_atom(msgbuf,&index,"send_records");
- ei_encode_pid(msgbuf,&index,&self);
+ ei_encode_pid(msgbuf,&index,self);
if (ei_send_encoded(fd,&mnesia,msgbuf,index)) goto restore_failure;
/* read as much as possible, until count or EXIT */
@@ -317,7 +315,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab)
return 0;
restore_failure:
- ei_send_exit(fd,&self,&mnesia,"restore failure");
+ ei_send_exit(fd,self,&mnesia,"restore failure");
if (keybuf) free(keybuf);
if (dbuf) free(dbuf);
return -1;
diff --git a/lib/erl_interface/src/registry/reg_set.c b/lib/erl_interface/src/registry/reg_set.c
index 95b90adb87..3846df1cb5 100644
--- a/lib/erl_interface/src/registry/reg_set.c
+++ b/lib/erl_interface/src/registry/reg_set.c
@@ -19,10 +19,6 @@
*
*/
-#ifdef VXWORKS
-#include <vxWorks.h>
-#endif
-
#include <stdarg.h>
#include "reg.h"
diff --git a/lib/erl_interface/test/Makefile b/lib/erl_interface/test/Makefile
index bd1fa61495..bdfedecc66 100644
--- a/lib/erl_interface/test/Makefile
+++ b/lib/erl_interface/test/Makefile
@@ -24,7 +24,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# Target Specs
# ----------------------------------------------------
-MODULES= \
+EI_MODULES= \
ei_accept_SUITE \
ei_connect_SUITE \
ei_decode_SUITE \
@@ -34,21 +34,20 @@ MODULES= \
ei_print_SUITE \
ei_tmo_SUITE \
erl_call_SUITE \
- erl_connect_SUITE \
- erl_global_SUITE \
- erl_eterm_SUITE \
- erl_ext_SUITE \
- erl_format_SUITE \
- erl_match_SUITE \
+ ei_global_SUITE \
port_call_SUITE \
runner
+ERTS_MODULES= erts_test_utils
+
+MODULES=$(EI_MODULES) $(ERTS_MODULES)
+
SPEC_FILES = \
erl_interface.spec erl_interface_smoke.spec
COVER_FILE = erl_interface.cover
-ERL_FILES = $(MODULES:%=%.erl)
+ERL_FILES = $(EI_MODULES:%=%.erl) $(ERTS_MODULES:%=$(ERL_TOP)/erts/emulator/test/%.erl)
# ----------------------------------------------------
# Release directory specification
diff --git a/lib/erl_interface/test/all_SUITE_data/Makefile.src b/lib/erl_interface/test/all_SUITE_data/Makefile.src
index 7b8d835e18..daef471847 100644
--- a/lib/erl_interface/test/all_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/all_SUITE_data/Makefile.src
@@ -23,7 +23,7 @@ CC0 = @CC@
CC = .@DS@gccifier@exe@ -CC"$(CC0)"
CFLAGS0 = @CFLAGS@ -I@erl_interface_include@
CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@
-EI_COMMON_OBJS = runner@obj@ ei_runner@obj@
+EI_COMMON_OBJS = runner@obj@ ei_runner@obj@ my_ussi@obj@
ALL_OBJS = gccifier@exe@ $(EI_COMMON_OBJS)
CP=cp
diff --git a/lib/erl_interface/test/all_SUITE_data/ei_runner.c b/lib/erl_interface/test/all_SUITE_data/ei_runner.c
index cd7a67c57c..56c88c46f2 100644
--- a/lib/erl_interface/test/all_SUITE_data/ei_runner.c
+++ b/lib/erl_interface/test/all_SUITE_data/ei_runner.c
@@ -18,11 +18,13 @@
* %CopyrightEnd%
*/
#include <stdio.h>
+#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include <ctype.h>
#ifndef __WIN32__
#include <unistd.h>
#endif
diff --git a/lib/erl_interface/test/all_SUITE_data/init_tc.erl b/lib/erl_interface/test/all_SUITE_data/init_tc.erl
index d9ad291f3d..da3d8053a0 100644
--- a/lib/erl_interface/test/all_SUITE_data/init_tc.erl
+++ b/lib/erl_interface/test/all_SUITE_data/init_tc.erl
@@ -77,11 +77,7 @@ generate_c(Cases, File, TcName) ->
lists:foreach(fun(Case) -> io:format(File, " ~s,~n", [Case]) end, Cases),
io:format(File, "~s",
[["};\n\n",
- "#ifdef VXWORKS\n",
- "int ", TcName, "(int argc, char* argv[])\n",
- "#else\n",
"int main(int argc, char* argv[])\n",
- "#endif\n",
"{\n",
" run_tests(argv[0], test_cases, ",
"sizeof(test_cases)/sizeof(test_cases[0]));\n",
diff --git a/lib/erl_interface/test/all_SUITE_data/my_ussi.c b/lib/erl_interface/test/all_SUITE_data/my_ussi.c
new file mode 100644
index 0000000000..5f8c79b7cf
--- /dev/null
+++ b/lib/erl_interface/test/all_SUITE_data/my_ussi.c
@@ -0,0 +1,198 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2019. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * User Supplied Socket Implementation (ussi)
+ * for test purpose.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include "ei.h"
+
+struct my_ctx
+{
+ void* ctx;
+};
+
+/*
+ * To minimize effort but still test a different context format
+ * we cheat and wrap the existing TCP default callbacks.
+ */
+extern ei_socket_callbacks ei_default_socket_callbacks;
+
+static int my_socket(void **ctx, void *setup_ctx)
+{
+ struct my_ctx *myctx = malloc(sizeof(struct my_ctx));
+ int ret;
+ ret = ei_default_socket_callbacks.socket(&myctx->ctx, NULL);
+ *ctx = myctx;
+ return ret;
+}
+
+static int my_close(void *ctx)
+{
+ struct my_ctx *myctx = ctx;
+ int ret = ei_default_socket_callbacks.close(myctx->ctx);
+ free(myctx);
+ return ret;
+}
+
+static int my_get_fd(void *ctx, int *fd)
+{
+ struct my_ctx *myctx = ctx;
+ return ei_default_socket_callbacks.get_fd(myctx->ctx, fd);
+}
+
+static int my_hs_packet_header_size(void *ctx, int *sz)
+{
+ struct my_ctx *myctx = ctx;
+ return ei_default_socket_callbacks.handshake_packet_header_size(myctx->ctx, sz);
+}
+
+static int my_connect_handshake_complete(void *ctx)
+{
+ struct my_ctx *myctx = ctx;
+ return ei_default_socket_callbacks.connect_handshake_complete(myctx->ctx);
+}
+
+static int my_accept_handshake_complete(void *ctx)
+{
+ struct my_ctx *myctx = ctx;
+ return ei_default_socket_callbacks.accept_handshake_complete(myctx->ctx);
+}
+
+static int my_listen(void *ctx, void *addr, int *len, int backlog)
+{
+ struct my_ctx *myctx = ctx;
+ return ei_default_socket_callbacks.listen(myctx->ctx, addr, len, backlog);
+}
+
+static int my_accept(void **ctx, void *addr, int *len, unsigned tmo)
+{
+ struct my_ctx *listen_ctx = *ctx;
+ struct my_ctx *conn_ctx = malloc(sizeof(struct my_ctx));
+ int ret;
+ *conn_ctx = *listen_ctx;
+ ret = ei_default_socket_callbacks.accept(&conn_ctx->ctx, addr, len, tmo);
+ if (ret == 0)
+ *ctx = conn_ctx;
+ else
+ free(conn_ctx);
+ return ret;
+}
+
+static int my_connect(void *ctx, void *addr, int len, unsigned tmo)
+{
+ struct my_ctx *myctx = ctx;
+ return ei_default_socket_callbacks.connect(myctx->ctx, addr, len, tmo);
+}
+
+static void* memdup(const void* mem, int nbytes)
+{
+ void *p = malloc(nbytes);
+ memcpy(p, mem, nbytes);
+ return p;
+}
+
+static void scramble(void* bytes, int nbytes)
+{
+/* Would be nice to really test that only our callbacks are used
+ and the default ones are not.
+ Need corresponding Erlang distribution impl to work.
+
+ unsigned char *p = bytes;
+ int i;
+ for (i=0; i < nbytes; ++i)
+ p[i] = ~p[i];
+*/
+}
+
+/* our own iovec struct to avoid config dependency HAVE_WRITEV */
+struct my_iovec {
+ void *iov_base; /* Starting address */
+ size_t iov_len; /* Number of bytes to transfer */
+};
+
+static int my_writev(void *ctx, const void *viov, int iovcnt, ssize_t *len, unsigned tmo)
+{
+ struct my_ctx *myctx = ctx;
+ struct my_iovec *iov;
+ int i, ret;
+
+ /* create mutable copy of both iovec and data */
+ iov = memdup(viov, sizeof(struct my_iovec) * iovcnt);
+ for (i=0; i < iovcnt; ++i) {
+ iov[i].iov_base = memdup(iov[i].iov_base, iov[i].iov_len);
+ scramble(iov[i].iov_base, iov[i].iov_len);
+ }
+
+ ret = ei_default_socket_callbacks.writev(myctx->ctx, viov, iovcnt, len, tmo);
+
+ for (i=0; i < iovcnt; ++i)
+ free(iov[i].iov_base);
+ free(iov);
+ return ret;
+}
+
+static int my_write(void *ctx, const char* buf, ssize_t *len, unsigned tmo)
+{
+ struct my_ctx *myctx = ctx;
+ unsigned char* copy = memdup(buf, *len);
+ int i, ret;
+
+ scramble(copy, *len);
+ ret = ei_default_socket_callbacks.write(myctx->ctx, copy, len, tmo);
+ free(copy);
+ return ret;
+}
+
+static int my_read(void *ctx, char* buf, ssize_t *len, unsigned tmo)
+{
+ struct my_ctx *myctx = ctx;
+ int ret, i;
+
+ ret = ei_default_socket_callbacks.read(myctx->ctx, buf, len, tmo);
+ if (ret == 0)
+ scramble(buf, *len);
+ return ret;
+}
+
+ei_socket_callbacks my_ussi = {
+ 0, /* flags */
+ my_socket,
+ my_close,
+ my_listen,
+ my_accept,
+ my_connect,
+ my_writev,
+ my_write,
+ my_read,
+ my_hs_packet_header_size,
+ my_connect_handshake_complete,
+ my_accept_handshake_complete,
+ my_get_fd
+};
+
+void my_ussi_init(void)
+{
+ my_ussi.flags = ei_default_socket_callbacks.flags;
+ if (!ei_default_socket_callbacks.writev)
+ my_ussi.writev = NULL;
+}
diff --git a/lib/erl_interface/src/legacy/erl_config.h b/lib/erl_interface/test/all_SUITE_data/my_ussi.h
index fb72169f23..0db07c990e 100644
--- a/lib/erl_interface/src/legacy/erl_config.h
+++ b/lib/erl_interface/test/all_SUITE_data/my_ussi.h
@@ -1,8 +1,8 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2016. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2019. All Rights Reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -14,10 +14,15 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
+ *
* %CopyrightEnd%
*/
-#ifndef _ERL_CONFIG_H
-#define _ERL_CONFIG_H
-#endif /* _ERL_CONFIG_H */
+/*
+ * User Supplied Socket Implementation (ussi)
+ * for test purpose.
+ */
+
+extern ei_socket_callbacks my_ussi;
+
+extern void my_ussi_init(void);
diff --git a/lib/erl_interface/test/all_SUITE_data/runner.c b/lib/erl_interface/test/all_SUITE_data/runner.c
deleted file mode 100644
index 42e8bb03e5..0000000000
--- a/lib/erl_interface/test/all_SUITE_data/runner.c
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#ifndef __WIN32__
-#include <unistd.h>
-#endif
-#include <stdarg.h>
-
-#include "runner.h"
-
-#ifndef __WIN32__
-#define _O_BINARY 0
-#define _setmode(fd, mode)
-#endif
-
-#define HEADER_SIZE 4
-
-static char* progname; /* Name of this program (from argv[0]). */
-static int fd_from_erl; /* File descriptor from Erlang. */
-static int fd_to_erl; /* File descriptor to Erlang. */
-
-static int packet_loop();
-static void ensure_buf_big_enough();
-static int readn();
-static void reply(char* buf, unsigned size);
-static void dump();
-
-void
-run_tests(char* argv0, TestCase test_cases[], unsigned number)
-{
- int i;
- int n;
- char* packet;
-
- progname = argv0;
- _setmode(0, _O_BINARY);
- _setmode(1, _O_BINARY);
- fd_from_erl = 0;
- fd_to_erl = 1;
-
- packet = read_packet(&n);
-
- /*
- * Dispatch to the appropriate test function.
- */
-
- i = packet[0] * 256 + packet[1];
- if (i >= number) {
- fprintf(stderr, "%s: bad test case number %d",
- progname, i);
- free(packet);
- exit(1);
- } else {
- (*test_cases[i])();
- free(packet);
- }
-}
-
-
-/***********************************************************************
- *
- * R e a d i n g p a c k e t s
- *
- ************************************************************************/
-
-/*
- * Reads an Erlang term.
- *
- * Returns: A pointer to a term (an ETERM structure) if there was
- * at term available, or a NULL pointer if there was an 'eot' (end-of-test)
- * packet. Aborts if anything else received.
- */
-
-ETERM*
-get_term(void)
-{
- char* encoded;
- ETERM* term;
- int n;
-
- encoded = read_packet(&n);
-
- switch (encoded[0]) {
- case 'e':
- free(encoded);
- return NULL;
- case 't':
- term = erl_decode(encoded+1);
- free(encoded);
- if (term == NULL) {
- fail("Failed to decode term");
- exit(0);
- }
- return term;
- default:
- fprintf(stderr, "Garbage received: ");
- dump(encoded, n, 16);
- putc('\n', stderr);
- fail("C program received garbage");
- free(encoded);
- exit(1);
- }
-}
-
-
-/*
- * Reads a packet from Erlang. The packet must be a standard {packet, 2}
- * packet. This function aborts if any error is detected (including EOF).
- *
- * Returns: The number of bytes in the packet.
- */
-
-char *read_packet(int *len)
-{
-
- unsigned char* io_buf = NULL; /* Buffer for file i/o. */
- int i;
- unsigned char header[HEADER_SIZE];
- unsigned packet_length; /* Length of current packet. */
- int bytes_read;
-
- /*
- * Read the packet header.
- */
-
- bytes_read = readn(fd_from_erl, header, HEADER_SIZE);
-
- if (bytes_read == 0) {
- fprintf(stderr, "%s: Unexpected end of file\n", progname);
- exit(1);
- }
- if (bytes_read != HEADER_SIZE) {
- fprintf(stderr, "%s: Failed to read packet header\n", progname);
- exit(1);
- }
-
- /*
- * Get the length of this packet.
- */
-
- packet_length = 0;
-
- for (i = 0; i < HEADER_SIZE; i++)
- packet_length = (packet_length << 8) | header[i];
-
- if (len) *len=packet_length; /* report length only if caller requested it */
-
- if ((io_buf = (char *) malloc(packet_length)) == NULL) {
- fprintf(stderr, "%s: insufficient memory for i/o buffer of size %d\n",
- progname, packet_length);
- exit(1);
- }
-
- /*
- * Read the packet itself.
- */
-
- bytes_read = readn(fd_from_erl, io_buf, packet_length);
- if (bytes_read != packet_length) {
- fprintf(stderr, "%s: couldn't read packet of length %d\r\n",
- progname, packet_length);
- free(io_buf);
- exit(1);
- }
-
- return io_buf;
-}
-
-
-/***********************************************************************
- * S e n d i n g r e p l i e s
- *
- * The functions below send various types of replies back to Erlang.
- * Each reply start with a letter indicating the type of reply.
- *
- * Reply Translated to on Erlang side
- * ----- ----------------------------
- * [$b|Bytes] {bytes, Bytes}
- * [$e] eot
- * [$f] ct:fail()
- * [$f|Reason] ct:fail(Reason)
- * [$t|EncodedTerm] {term, Term}
- * [$N] 'NULL'
- * [$m|Message] io:format("~s", [Message]) (otherwise ignored)
- *
- ***********************************************************************/
-
-/*
- * This function reports the outcome of a test fail. It is useful if
- * you implement a test case entirely in C code.
- *
- * If the ok argument is zero, a [$f] reply will be sent to the
- * Erlang side (causing ct:fail() to be called); otherwise,
- * the atom 'eot' will be sent to Erlang.
- *
- * If you need to provide more details on a failure, use the fail() function.
- */
-
-void
-do_report(file, line, ok)
- char* file;
- int line;
- int ok; /* Zero if failed; non-zero otherwise. */
-{
- char reason;
- unsigned long ab;
- unsigned long fb;
-
- reason = ok ? 'e' : 'f';
-
- if (!ok) {
- do_fail(file, line, "Generic failure");
- } else {
- /* release all unallocated blocks */
- erl_eterm_release();
- /* check mem usage stats */
- erl_eterm_statistics(&ab, &fb);
- if ((ab == 0) && (fb == 0) ) {
- reply(&reason, 1);
- }
- else {
- char sbuf[128];
-
- sprintf(sbuf, "still %lu terms allocated,"
- " %lu on freelist at end of test", ab, fb);
- do_fail(file, line, sbuf);
- }
- }
-}
-
-
-/*
- * This function causes a call to ct:fail(Reason) on the
- * Erlang side.
- */
-
-void
-do_fail(char* file, int line, char* reason)
-{
- char sbuf[2048];
-
- sbuf[0] = 'f';
- sprintf(sbuf+1, "%s, line %d: %s", file, line, reason);
- reply(sbuf, 1+strlen(sbuf+1));
-}
-
-/*
- * This function sends a message to the Erlang side.
- * The message will be written to the test servers log file,
- * but will otherwise be completly ignored.
- */
-
-void
-message(char* format, ...)
-{
- va_list ap;
- char sbuf[1024];
-
- sbuf[0] = 'm';
- va_start(ap, format);
- vsprintf(sbuf+1, format, ap);
- va_end(ap);
-
- reply(sbuf, 1+strlen(sbuf+1));
-}
-
-/*
- * This function sends the given term to the Erlang side,
- * where it will be received as {term, Term}.
- *
- * If the given pointer is NULL (indicating an invalid term),
- * the result on the Erlang side will be the atom 'NULL'.
- *
- * After sending the term, this function frees the term by
- * calling erl_free_term().
- */
-
-void
-send_term(term)
- ETERM* term; /* Term to be sent to Erlang side. */
-{
- char encoded[64*1024];
- int n;
-
- if (term == NULL) {
- encoded[0] = 'N';
- n = 1;
- } else {
- encoded[0] = 't';
- n = 1 + erl_encode(term, encoded+1);
- erl_free_term(term);
- }
- reply(encoded, n);
-}
-
-#if 0
-
-/* Seriously broken!!! */
-
-void
-send_bin_term(x_ei_buff* x)
-{
- x_ei_buff x2;
- x_ei_new(&x2);
- x2.buff[x2.index++] = 't';
- x_ei_append(&x2, x);
- reply(x2.buff, x2.index);
- free(x2.buff);
-}
-#endif
-
-/*
- * This function sends a raw buffer of data to the
- * Erlang side, where it will be received as {bytes, Bytes}.
- */
-
-void
-send_buffer(buf, size)
- char* buf; /* Buffer with bytes to send to Erlang. */
- int size; /* Size of data to send to Erlang. */
-{
- char* send_buf;
-
- send_buf = (char *) malloc(size+1);
- send_buf[0] = 'b';
- memcpy(send_buf+1, buf, size);
- reply(send_buf, size+1);
- free(send_buf);
-}
-
-/***********************************************************************
- *
- * P r i v a t e h e l p e r s
- *
- ***********************************************************************/
-
-/*
- * Sends a packet back to Erlang.
- */
-
-static void
-reply(reply_buf, size)
- char* reply_buf; /* Buffer with reply. */
- unsigned size; /* Size of reply. */
-{
- int n; /* Temporary to hold size. */
- int i; /* Loop counter. */
- char* buf;
-
-
- buf = (char *) malloc(size+HEADER_SIZE);
- memcpy(buf+HEADER_SIZE, reply_buf, size);
-
- /*
- * Fill the header starting with the least significant byte.
- */
-
- n = size;
- for (i = HEADER_SIZE-1; i >= 0; i--) {
- buf[i] = (char) n; /* Store least significant byte. */
- n = n >> 8;
- }
-
- size += HEADER_SIZE;
-/*
- fprintf(stderr, "\r\nReply size: %u\r\n",
- (unsigned)buf[0] << 8 + (unsigned)buf[1]);
-
- for (i = 0; i < size; i++) {
- fprintf(stderr,"%u %c\r\n",buf[i],buf[i]);
- }
-
- fprintf(stderr, "\r\n");
-*/
- write(fd_to_erl, buf, size);
- free(buf);
-}
-
-
-/*
- * Reads len number of bytes.
- */
-
-static int
-readn(fd, buf, len)
- int fd; /* File descriptor to read from. */
- unsigned char *buf; /* Store in this buffer. */
- int len; /* Number of bytes to read. */
-{
- int n; /* Byte count in last read call. */
- int sofar = 0; /* Bytes read so far. */
-
- do {
- if ((n = read(fd, buf+sofar, len-sofar)) <= 0)
- /* error or EOF in read */
- return(n);
- sofar += n;
- } while (sofar < len);
- return sofar;
-}
-
-void
-dump(buf, sz, max)
- unsigned char* buf;
- int sz;
- int max;
-{
- int i, imax;
- char comma[5] = ",";
-
- if (!sz)
- return;
- if (sz > max)
- imax = max;
- else
- imax = sz;
-
- for (i=0; i<imax; i++) {
- if (i == imax-1) {
- if (sz > max)
- strcpy(comma, ",...");
- else
- comma[0] = 0;
- }
- if (isdigit(buf[i]))
- fprintf(stderr, "%u%s", (int)(buf[i]), comma);
- else {
- if (isalpha(buf[i])) {
- fprintf(stderr, "%c%s", buf[i], comma);
- }
- else
- fprintf(stderr, "%u%s", (int)(buf[i]), comma);
- }
- }
-}
-
diff --git a/lib/erl_interface/test/all_SUITE_data/runner.h b/lib/erl_interface/test/all_SUITE_data/runner.h
deleted file mode 100644
index 493602869f..0000000000
--- a/lib/erl_interface/test/all_SUITE_data/runner.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#include "erl_interface.h"
-
-typedef void (*TestCase)(void);
-
-#define TESTCASE(name) void name(void)
-#define ASIZE(a) (sizeof(a)/sizeof(a[0]))
-
-void run_tests(char* argv0, TestCase cases[], unsigned number);
-
-/*
- * Reading.
- */
-
-ETERM* get_term(void);
-char *read_packet(int *len);
-
-/*
- * Sending replies.
- */
-
-#define fail(reason) do_fail(__FILE__, __LINE__, reason)
-#define report(ok) do_report(__FILE__, __LINE__, ok)
-
-void do_report(char* file, int line, int ok);
-void do_fail(char* file, int line, char* reason);
-void send_term(ETERM* term);
-void send_buffer(char* buf, int size);
-void message(char* format, ...);
-
-void send_bin_term(ei_x_buff* x);
-
diff --git a/lib/erl_interface/test/ei_accept_SUITE.erl b/lib/erl_interface/test/ei_accept_SUITE.erl
index 17aa76f0a3..612d6e1b81 100644
--- a/lib/erl_interface/test/ei_accept_SUITE.erl
+++ b/lib/erl_interface/test/ei_accept_SUITE.erl
@@ -43,12 +43,15 @@ init_per_testcase(Case, Config) ->
runner:init_per_testcase(?MODULE, Case, Config).
ei_accept(Config) when is_list(Config) ->
- ei_accept_do(Config, 0), % default
- ei_accept_do(Config, 21). % ei_set_compat_rel
+ [ei_accept_do(Config, CR, SI)
+ || CR <- [0,21],
+ SI <- [default, ussi]],
+ ok.
-ei_accept_do(Config, CompatRel) ->
+ei_accept_do(Config, CompatRel, SockImpl) ->
+ io:format("CompatRel=~p, SockImpl=~p\n", [CompatRel, SockImpl]),
P = runner:start(Config, ?interpret),
- 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, CompatRel),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, CompatRel, SockImpl),
Myname = hd(tl(string:tokens(atom_to_list(node()), "@"))),
io:format("Myname ~p ~n", [Myname]),
@@ -72,7 +75,8 @@ ei_accept_do(Config, CompatRel) ->
{ok, ListenFd} = ei_publish(P, Port),
{any, EINode} ! TermToSend,
- {ok, Fd, _Node} = ei_accept(P, ListenFd),
+ {ok, Fd, Node} = ei_accept(P, ListenFd),
+ Node = node(),
Got1 = ei_receive(P, Fd),
%% Send again, now without auto-connect
@@ -88,9 +92,14 @@ ei_accept_do(Config, CompatRel) ->
ei_threaded_accept(Config) when is_list(Config) ->
Einode = filename:join(proplists:get_value(data_dir, Config), "eiaccnode"),
+ ei_threaded_accept_do(Einode, default),
+ ei_threaded_accept_do(Einode, ussi),
+ ok.
+
+ei_threaded_accept_do(Einode, SockImpl) ->
N = 3,
- Host = atom_to_list(node()),
- start_einode(Einode, N, Host),
+ wait_unreg_nodename(["eiacc0", "eiacc1", "eiacc2"], 10),
+ start_einode(Einode, N, SockImpl),
io:format("started eiaccnode"),
TestServerPid = self(),
[spawn_link(fun() -> send_rec_einode(I, TestServerPid) end) || I <- lists:seq(0, N-1)],
@@ -101,7 +110,7 @@ ei_threaded_accept(Config) when is_list(Config) ->
%% Test erlang:monitor toward erl_interface "processes"
monitor_ei_process(Config) when is_list(Config) ->
P = runner:start(Config, ?interpret),
- 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, 0, default),
Myname = hd(tl(string:tokens(atom_to_list(node()), "@"))),
io:format("Myname ~p ~n", [Myname]),
@@ -125,12 +134,42 @@ monitor_ei_process(Config) when is_list(Config) ->
runner:finish(P),
- [{'DOWN', MRef1, process, {any, EINode}, noconnection},
- {'DOWN', MRef2, process, {any, EINode}, noconnection}
- ] = lists:sort(flush(2, 1000)),
-
+ ok =receive
+ {'DOWN', MRef1, process, {any, EINode}, noconnection} ->
+ ok
+ after 1000 ->
+ timeout
+ end,
+ ok = receive
+ {'DOWN', MRef2, process, {any, EINode}, noconnection} ->
+ ok
+ after 1000 ->
+ timeout
+ end,
+ [] = flush(0, 1000),
ok.
+wait_unreg_nodename([], _) ->
+ ok;
+wait_unreg_nodename(Names, 0) ->
+ ct:fail({name_not_unregistered, Names});
+wait_unreg_nodename(Names, N) ->
+ Registered = [X || {X,_} <- element(2,erl_epmd:names())],
+ case lists:foldl(fun (Name, Acc) ->
+ case lists:member(Name, Registered) of
+ true -> [Name | Acc];
+ false -> Acc
+ end
+ end,
+ [],
+ Names) of
+ [] ->
+ ok;
+ NewNames ->
+ timer:sleep(1000),
+ waitfornode(NewNames,N-1)
+ end.
+
waitfornode(String,0) ->
io:format("~s never published itself.~n",[String]),
false;
@@ -164,9 +203,10 @@ send_rec_einode(N, TestServerPid) ->
ct:fail(EINode)
end.
-start_einode(Einode, N, Host) ->
+start_einode(Einode, N, SockImpl) ->
Einodecmd = Einode ++ " " ++ atom_to_list(erlang:get_cookie())
- ++ " " ++ integer_to_list(N) ++ " " ++ Host,
+ ++ " " ++ integer_to_list(N)
+ ++ " " ++ atom_to_list(SockImpl),
io:format("Einodecmd ~p ~n", [Einodecmd]),
open_port({spawn, Einodecmd}, []),
ok.
@@ -174,8 +214,8 @@ start_einode(Einode, N, Host) ->
%%% Interface functions for ei (erl_interface) functions.
-ei_connect_init(P, Num, Cookie, Creation, Compat) ->
- send_command(P, ei_connect_init, [Num,Cookie,Creation,Compat]),
+ei_connect_init(P, Num, Cookie, Creation, Compat, SockImpl) ->
+ send_command(P, ei_connect_init, [Num,Cookie,Creation,Compat,SockImpl]),
case get_term(P) of
{term,Int} when is_integer(Int) -> Int
end.
diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src
index 5b196ab976..9bd71d33b8 100644
--- a/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src
@@ -25,6 +25,7 @@ CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
LD = @LD@
LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
+ ../all_SUITE_data/my_ussi@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c b/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c
index dc2e65331f..7cfc0c9da0 100644
--- a/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c
+++ b/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c
@@ -27,9 +27,6 @@
#include <stdio.h>
#include <string.h>
-#ifdef VXWORKS
-#include "reclaim.h"
-#endif
#ifdef __WIN32__
#include <winsock2.h>
@@ -41,6 +38,7 @@
#endif
#include "ei_runner.h"
+#include "my_ussi.h"
static void cmd_ei_connect_init(char* buf, int len);
static void cmd_ei_publish(char* buf, int len);
@@ -58,7 +56,7 @@ static struct {
int num_args; /* Number of arguments. */
void (*func)(char* buf, int len);
} commands[] = {
- "ei_connect_init", 4, cmd_ei_connect_init,
+ "ei_connect_init", 5, cmd_ei_connect_init,
"ei_publish", 1, cmd_ei_publish,
"ei_accept", 1, cmd_ei_accept,
"ei_receive", 1, cmd_ei_receive,
@@ -110,10 +108,11 @@ static void cmd_ei_connect_init(char* buf, int len)
unsigned long compat;
char node_name[100];
char cookie[MAXATOMLEN], * cp = cookie;
+ char socket_impl[10];
ei_x_buff res;
if (ei_decode_long(buf, &index, &num) < 0)
fail("expected int");
- sprintf(node_name, "c%d", num);
+ sprintf(node_name, "c%ld", num);
if (ei_decode_atom(buf, &index, cookie) < 0)
fail("expected atom (cookie)");
if (cookie[0] == '\0')
@@ -124,7 +123,18 @@ static void cmd_ei_connect_init(char* buf, int len)
fail("expected uint");
if (compat)
ei_set_compat_rel(compat);
- r = ei_connect_init(&ec, node_name, cp, creation);
+ if (ei_decode_atom_as(buf, &index, socket_impl, sizeof(socket_impl),
+ ERLANG_ASCII, NULL, NULL) < 0)
+ fail("expected atom (socket_impl)");
+ if (strcmp(socket_impl,"default") == 0)
+ r = ei_connect_init(&ec, node_name, cp, creation);
+ else if (strcmp(socket_impl,"ussi") == 0)
+ r = ei_connect_init_ussi(&ec, node_name, cp, creation,
+ &my_ussi, sizeof(my_ussi), NULL);
+ else
+ fail1("unknown socket_impl atom '%s'", socket_impl);
+
+
ei_x_new_with_version(&res);
ei_x_encode_long(&res, r);
send_bin_term(&res);
@@ -152,9 +162,6 @@ static void cmd_ei_publish(char* buf, int len)
if ((i = ei_publish(&ec, lport)) == -1)
fail("ei_publish");
-#ifdef VXWORKS
- save_fd(i);
-#endif
/* send listen-fd, result and errno */
ei_x_new_with_version(&x);
ei_x_encode_tuple_header(&x, 3);
@@ -179,9 +186,6 @@ static void cmd_ei_accept(char* buf, int len)
fail("expected int (listen fd)");
r = ei_accept(&ec, listen, &conn);
-#ifdef VXWORKS
- save_fd(r);
-#endif
/* send result, errno and nodename */
ei_x_new_with_version(&x);
ei_x_encode_tuple_header(&x, 3);
diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c b/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c
index 62e949cd8b..ef1ab96d41 100644
--- a/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c
+++ b/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c
@@ -22,35 +22,28 @@
#include <stdlib.h>
#include <stdio.h>
+#include <string.h>
#ifdef __WIN32__
#include <winsock2.h>
#include <windows.h>
#include <process.h>
#else
-#ifndef VXWORKS
#include <pthread.h>
-#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#include "ei.h"
+#include "my_ussi.h"
-#ifdef VXWORKS
-#include <vxWorks.h>
-#include <sockLib.h>
-#include <inetLib.h>
-#define MAIN cnode
-#else
#define MAIN main
-#endif
/*
A small einode.
To be called from the test case ei_accept_SUITE:multi_thread
- usage: eiaccnode <cookie> <n>
+ usage: eiaccnode <cookie> <n> <default|ussi>
- start threads 0..n-1
- in each thread
@@ -61,7 +54,8 @@
- shutdown gracefully
*/
-static const char* cookie, * desthost;
+static const char* cookie;
+static int use_ussi;
#ifndef SD_SEND
#ifdef SHUTWR
@@ -78,7 +72,7 @@ static void*
#endif
einode_thread(void* num)
{
- int n = (int)num;
+ int n = (int)(long)num;
int port;
ei_cnode ec;
char myname[100], destname[100], filename[100];
@@ -88,18 +82,26 @@ static void*
FILE* file;
sprintf(filename, "eiacc%d_trace.txt", n);
- file = fopen(filename, "w");
+ file = fopen(filename, "a");
sprintf(myname, "eiacc%d", n); fflush(file);
- r = ei_connect_init(&ec, myname, cookie, 0);
+ fprintf(file, "---- use_ussi = %d ----\n", use_ussi); fflush(file);
+ if (use_ussi)
+ r = ei_connect_init_ussi(&ec, myname, cookie, 0,
+ &my_ussi, sizeof(my_ussi), NULL);
+ else
+ r = ei_connect_init(&ec, myname, cookie, 0);
+ fprintf(file, "r=%d\n", r); fflush(file);
port = 0;
listen = ei_listen(&ec, &port, 5);
if (listen <= 0) {
fprintf(file, "listen err\n"); fflush(file);
exit(7);
}
- fprintf(file, "thread %d (%s:%s) listening on port %d\n", n, myname, destname, port);
- if (ei_publish(&ec, port) == -1) {
+ fprintf(file, "thread %d (%s:%s) listening on port %d\n", n, myname, destname, port); fflush(file);
+ r = ei_publish(&ec, port);
+ fprintf(file, "r=%d\n", r); fflush(file);
+ if (r == -1) {
fprintf(file, "ei_publish port %d\n", port+n); fflush(file);
exit(8);
}
@@ -151,54 +153,52 @@ static void*
return 0;
}
+int
MAIN(int argc, char *argv[])
{
int i, n, no_threads;
-#ifndef VXWORKS
#ifdef __WIN32__
HANDLE threads[100];
#else
pthread_t threads[100];
#endif
-#endif
- if (argc < 3)
+ if (argc < 4)
exit(1);
cookie = argv[1];
n = atoi(argv[2]);
if (n > 100)
exit(2);
- desthost = argv[3];
- if (argc == 3)
+
+ if (strcmp(argv[3], "default") == 0)
+ use_ussi = 0;
+ else if (strcmp(argv[3], "ussi") == 0)
+ use_ussi = 1;
+ else
+ printf("bad argv[3] '%s'", argv[3]);
+
+ if (argc == 4)
no_threads = 0;
else
no_threads = argv[4] != NULL && strcmp(argv[4], "nothreads") == 0;
-#ifdef VXWORKS
- no_threads = 1;
-#endif
ei_init();
for (i = 0; i < n; ++i) {
if (!no_threads) {
-#ifndef VXWORKS
#ifdef __WIN32__
unsigned tid;
threads[i] = (HANDLE)_beginthreadex(NULL, 0, einode_thread,
- (void*)i, 0, &tid);
-#else
- pthread_create(&threads[i], NULL, einode_thread, (void*)i);
-#endif
+ (void*)(size_t)i, 0, &tid);
#else
- ;
+ pthread_create(&threads[i], NULL, einode_thread, (void*)(size_t)i);
#endif
} else
- einode_thread((void*)i);
+ einode_thread((void*)(size_t)i);
}
if (!no_threads)
-#ifndef VXWORKS
for (i = 0; i < n; ++i) {
#ifdef __WIN32__
if (WaitForSingleObject(threads[i], INFINITE) != WAIT_OBJECT_0)
@@ -207,9 +207,6 @@ MAIN(int argc, char *argv[])
#endif
printf("bad wait thread %d\n", i);
}
-#else
- ;
-#endif
printf("ok\n");
return 0;
}
diff --git a/lib/erl_interface/test/ei_connect_SUITE.erl b/lib/erl_interface/test/ei_connect_SUITE.erl
index 3e9f8eacb9..0506359b71 100644
--- a/lib/erl_interface/test/ei_connect_SUITE.erl
+++ b/lib/erl_interface/test/ei_connect_SUITE.erl
@@ -24,7 +24,7 @@
-include_lib("common_test/include/ct.hrl").
-include("ei_connect_SUITE_data/ei_connect_test_cases.hrl").
--export([all/0, suite/0,
+-export([all/0, suite/0, groups/0,
init_per_testcase/2,
ei_send/1,
ei_reg_send/1,
@@ -33,7 +33,10 @@
rpc_test/1,
ei_send_funs/1,
ei_threaded_send/1,
- ei_set_get_tracelevel/1]).
+ ei_set_get_tracelevel/1,
+ ei_connect_host_port_test/1,
+ ei_make_ref/1,
+ ei_make_pid/1]).
-import(runner, [get_term/1,send_term/2]).
@@ -42,15 +45,32 @@ suite() ->
{timetrap, {seconds, 30}}].
all() ->
- [ei_send, ei_reg_send, ei_rpc, ei_format_pid, ei_send_funs,
- ei_threaded_send, ei_set_get_tracelevel].
+ [ei_threaded_send,
+ ei_connect_host_port_test,
+ {group, default},
+ {group, ussi}].
+
+groups() ->
+ Members = [ei_send,
+ ei_format_pid,
+ ei_send_funs,
+ ei_set_get_tracelevel,
+ ei_reg_send,
+ ei_rpc,
+ ei_make_ref,
+ ei_make_pid],
+ [{default, [], Members},
+ {ussi, [], Members}].
+
+get_group(Config) ->
+ proplists:get_value(name, proplists:get_value(tc_group_properties,Config)).
init_per_testcase(Case, Config) ->
runner:init_per_testcase(?MODULE, Case, Config).
ei_send(Config) when is_list(Config) ->
P = runner:start(Config, ?interpret),
- 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
{ok,Fd} = ei_connect(P, node()),
ok = ei_send(P, Fd, self(), AMsg={a,message}),
@@ -63,7 +83,7 @@ ei_send(Config) when is_list(Config) ->
ei_format_pid(Config) when is_list(Config) ->
S = self(),
P = runner:start(Config, ?interpret),
- 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
{ok,Fd} = ei_connect(P, node()),
ok = ei_format_pid(P, Fd, S),
@@ -75,7 +95,7 @@ ei_format_pid(Config) when is_list(Config) ->
ei_send_funs(Config) when is_list(Config) ->
P = runner:start(Config, ?interpret),
- 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
{ok,Fd} = ei_connect(P, node()),
Fun1 = fun ei_send/1,
@@ -94,7 +114,7 @@ ei_send_funs(Config) when is_list(Config) ->
ei_reg_send(Config) when is_list(Config) ->
P = runner:start(Config, ?interpret),
- 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
{ok,Fd} = ei_connect(P, node()),
ARegName = a_strange_registred_name,
@@ -143,7 +163,7 @@ start_einode(Einode, N, Host) ->
ei_rpc(Config) when is_list(Config) ->
P = runner:start(Config, ?interpret),
- 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
{ok,Fd} = ei_connect(P, node()),
S= "Hej du glade!", SRev = lists:reverse(S),
@@ -157,7 +177,7 @@ ei_rpc(Config) when is_list(Config) ->
ei_set_get_tracelevel(Config) when is_list(Config) ->
P = runner:start(Config, ?interpret),
5 = ei_set_get_tracelevel(P, 5),
- 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
{ok,Fd} = ei_connect(P, node()),
S= "Hej du glade!", SRev = lists:reverse(S),
@@ -171,10 +191,125 @@ ei_set_get_tracelevel(Config) when is_list(Config) ->
ok.
+ei_connect_host_port_test(Config) when is_list(Config) ->
+ P = runner:start(Config, ?interpret),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, default),
+ [NodeName, Hostname] = string:lexemes(atom_to_list(node()), "@"),
+ {ok, NamePortList} = net_adm:names(),
+ {value, {_, Port}}
+ = lists:search(fun({N, _}) ->
+ string:equal(N, NodeName)
+ end,
+ NamePortList),
+ {ok,Fd} = ei_connect_host_port(P,
+ erlang:list_to_atom(Hostname),
+ Port),
+ ok = ei_send(P, Fd, self(), AMsg={a,message}),
+ receive AMsg -> ok end,
+
+ runner:send_eot(P),
+ runner:recv_eot(P),
+ ok.
+
+ei_make_ref(Config) when is_list(Config) ->
+ P = runner:start(Config, ?interpret),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
+ {ok,Fd} = ei_connect(P, node()),
+
+ %% Call ei_make_ref() enough times for it to
+ %% wrap the first internal integer..
+
+ N = 270,
+ {CNode, Refs} = make_refs(N, undefined, P, Fd, []),
+ io:format("Last Ref ~p~n", [hd(Refs)]),
+
+ io:format("CNode = ~p", [CNode]),
+
+ true = lists:member(CNode, nodes(hidden)),
+
+ %% Ensure that all references are
+ %% unique...
+ RefsLen = N*1000,
+ RefsLen = length(lists:usort(Refs)),
+
+ runner:send_eot(P),
+ runner:recv_eot(P),
+ ok.
+
+make_refs(0, CNode, _P, _Fd, Refs) ->
+ {CNode, Refs};
+make_refs(N, CNode, P, Fd, Refs) ->
+ ok = ei_make_refs(P, Fd, self()),
+ receive
+ {Node, NewRefs} ->
+ NewNode = if CNode == undefined ->
+ Node;
+ true ->
+ CNode = Node
+ end,
+ make_refs(N-1, NewNode, P, Fd,
+ chk_refs(NewRefs, NewNode, Refs))
+ end.
+
+chk_refs([], _CNode, Refs) ->
+ Refs;
+chk_refs([NewRef|NewRefs], CNode, Refs) ->
+ true = is_reference(NewRef),
+ CNode = node(NewRef),
+ chk_refs(NewRefs, CNode, [NewRef|Refs]).
+
+ei_make_pid(Config) when is_list(Config) ->
+ P = runner:start(Config, ?interpret),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)),
+ {ok,Fd} = ei_connect(P, node()),
+
+ %%
+ %% Ensure to wrap all num values...
+ %%
+ N = 200,
+ {CNode, Pids} = make_pids(N, undefined, P, Fd, []),
+ io:format("Last Pid ~p~n", [hd(Pids)]),
+
+ io:format("CNode = ~p", [CNode]),
+
+ true = lists:member(CNode, nodes(hidden)),
+
+ %% Ensure that all pid created by ei_make_pid()
+ %% are unique. Note that ei_self() is passed
+ %% along in each call as well...
+ PidsLen = N*1000 + 1,
+ PidsLen = length(lists:usort(Pids)),
+
+ runner:send_eot(P),
+ runner:recv_eot(P),
+ ok.
+
+make_pids(0, CNode, _P, _Fd, Pids) ->
+ {CNode, Pids};
+make_pids(N, CNode, P, Fd, Pids) ->
+ ok = ei_make_pids(P, Fd, self()),
+ receive
+ {Node, NewPids} ->
+ NewNode = if CNode == undefined ->
+ Node;
+ true ->
+ CNode = Node
+ end,
+ make_pids(N-1, NewNode, P, Fd,
+ chk_pids(NewPids, NewNode, Pids))
+ end.
+
+chk_pids([], _CNode, Pids) ->
+ Pids;
+chk_pids([NewPid|NewPids], CNode, Pids) ->
+ true = is_pid(NewPid),
+ CNode = node(NewPid),
+ chk_pids(NewPids, CNode, [NewPid|Pids]).
+
%%% Interface functions for ei (erl_interface) functions.
-ei_connect_init(P, Num, Cookie, Creation) ->
- send_command(P, ei_connect_init, [Num,Cookie,Creation]),
+ei_connect_init(P, Num, Cookie, Creation, SockImpl) ->
+ send_command(P, ei_connect_init, [Num,Cookie,Creation,SockImpl]),
case get_term(P) of
{term,Int} when is_integer(Int) -> Int
end.
@@ -186,6 +321,13 @@ ei_connect(P, Node) ->
{term,{-1,Errno}} -> {error,Errno}
end.
+ei_connect_host_port(P, Hostname, Port) ->
+ send_command(P, ei_connect_host_port, [Hostname, Port]),
+ case get_term(P) of
+ {term,{Fd,_}} when Fd >= 0 -> {ok,Fd};
+ {term,{-1,Errno}} -> {error,Errno}
+ end.
+
ei_set_get_tracelevel(P, Tracelevel) ->
send_command(P, ei_set_get_tracelevel, [Tracelevel]),
case get_term(P) of
@@ -212,6 +354,14 @@ ei_rpc(P, Fd, To, Func, Msg) ->
send_command(P, ei_rpc, [Fd, To, Func, Msg]),
get_term(P).
+ei_make_refs(P, Fd, To) ->
+ send_command(P, ei_make_refs, [Fd,To]),
+ get_send_result(P).
+
+ei_make_pids(P, Fd, To) ->
+ send_command(P, ei_make_pids, [Fd,To]),
+ get_send_result(P).
+
get_send_result(P) ->
case get_term(P) of
diff --git a/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src
index 8a63cd899f..be8253988b 100644
--- a/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src
@@ -23,10 +23,9 @@ include @erl_interface_mk_include@
CC0 = @CC@
CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
LD = @LD@
-LIBERL = @erl_interface_lib@
LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
- $(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
+ $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_CONNECT_OBJS = ei_connect_test@obj@ ei_connect_test_decl@obj@
@@ -39,7 +38,8 @@ clean:
$(RM) ei_connect_test@exe@ einode@exe@
ei_connect_test@exe@: $(EI_CONNECT_OBJS) $(LIBEI)
- $(LD) @CROSSLDFLAGS@ -o $@ $(EI_CONNECT_OBJS) $(LIBFLAGS)
+ $(LD) @CROSSLDFLAGS@ -o $@ $(EI_CONNECT_OBJS) \
+ ../all_SUITE_data/my_ussi@obj@ $(LIBFLAGS)
einode@exe@: $(EINODE_OBJS) $(LIBEI)
diff --git a/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c b/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c
index 65f0d8a05a..0aa6879adf 100644
--- a/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c
+++ b/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c
@@ -27,20 +27,21 @@
#include <stdio.h>
#include <string.h>
-#ifdef VXWORKS
-#include "reclaim.h"
-#endif
#include "ei_runner.h"
+#include "my_ussi.h"
static void cmd_ei_connect_init(char* buf, int len);
static void cmd_ei_connect(char* buf, int len);
+static void cmd_ei_connect_host_port(char* buf, int len);
static void cmd_ei_send(char* buf, int len);
static void cmd_ei_format_pid(char* buf, int len);
static void cmd_ei_send_funs(char* buf, int len);
static void cmd_ei_reg_send(char* buf, int len);
static void cmd_ei_rpc(char* buf, int len);
static void cmd_ei_set_get_tracelevel(char* buf, int len);
+static void cmd_ei_make_refs(char* buf, int len);
+static void cmd_ei_make_pids(char* buf, int len);
static void send_errno_result(int value);
@@ -52,14 +53,17 @@ static struct {
int num_args; /* Number of arguments. */
void (*func)(char* buf, int len);
} commands[] = {
- "ei_connect_init", 3, cmd_ei_connect_init,
+ "ei_connect_init", 4, cmd_ei_connect_init,
"ei_connect", 1, cmd_ei_connect,
+ "ei_connect_host_port", 2, cmd_ei_connect_host_port,
"ei_send", 3, cmd_ei_send,
"ei_send_funs", 3, cmd_ei_send_funs,
"ei_reg_send", 3, cmd_ei_reg_send,
"ei_rpc", 4, cmd_ei_rpc,
"ei_set_get_tracelevel", 1, cmd_ei_set_get_tracelevel,
"ei_format_pid", 2, cmd_ei_format_pid,
+ "ei_make_refs", 2, cmd_ei_make_refs,
+ "ei_make_pids", 2, cmd_ei_make_pids,
};
@@ -107,9 +111,11 @@ TESTCASE(interpret)
static void cmd_ei_connect_init(char* buf, int len)
{
int index = 0, r = 0;
- long l;
+ long l, creation;
char b[100];
char cookie[MAXATOMLEN], * cp = cookie;
+ char socket_impl[10];
+ int use_ussi;
ei_x_buff res;
if (ei_decode_long(buf, &index, &l) < 0)
fail("expected int");
@@ -118,7 +124,23 @@ static void cmd_ei_connect_init(char* buf, int len)
fail("expected atom (cookie)");
if (cookie[0] == '\0')
cp = NULL;
- r = ei_connect_init(&ec, b, cp, 0);
+ if (ei_decode_long(buf, &index, &creation) < 0)
+ fail("expected int (creation)");
+ if (ei_decode_atom_as(buf, &index, socket_impl,
+ sizeof(socket_impl), ERLANG_ASCII, NULL, NULL) < 0)
+ fail("expected atom (socket_impl)");
+ if (strcmp(socket_impl, "default") == 0)
+ use_ussi = 0;
+ else if (strcmp(socket_impl, "ussi") == 0)
+ use_ussi = 1;
+ else
+ fail1("expected atom 'default' or 'ussi', got '%s'", socket_impl);
+
+ if (use_ussi)
+ r = ei_connect_init_ussi(&ec, b, cp, (short)creation,
+ &my_ussi, sizeof(my_ussi), NULL);
+ else
+ r = ei_connect_init(&ec, b, cp, (short)creation);
ei_x_new_with_version(&res);
ei_x_encode_long(&res, r);
send_bin_term(&res);
@@ -133,11 +155,20 @@ static void cmd_ei_connect(char* buf, int len)
if (ei_decode_atom(buf, &index, node) < 0)
fail("expected atom");
i=ei_connect(&ec, node);
-#ifdef VXWORKS
- if(i >= 0) {
- save_fd(i);
- }
-#endif
+ send_errno_result(i);
+}
+
+static void cmd_ei_connect_host_port(char* buf, int len)
+{
+ int index = 0;
+ char hostname[256];
+ int i;
+ long port;
+ if (ei_decode_atom(buf, &index, hostname) < 0)
+ fail("expected atom");
+ if (ei_decode_long(buf, &index, &port) < 0)
+ fail("expected int");
+ i = ei_connect_host_port(&ec, hostname, (int)port);
send_errno_result(i);
}
@@ -183,6 +214,80 @@ static void cmd_ei_send(char* buf, int len)
ei_x_free(&x);
}
+static void cmd_ei_make_refs(char* buf, int len)
+{
+ int index = 0;
+ long fd;
+ erlang_pid pid;
+ ei_x_buff x;
+ int i;
+ int nref = 1000;
+
+ if (ei_decode_long(buf, &index, &fd) < 0)
+ fail("expected long");
+ if (ei_decode_pid(buf, &index, &pid) < 0)
+ fail("expected pid (node)");
+ if (ei_x_new_with_version(&x) < 0)
+ fail("ei_x_new_with_version");
+ if (ei_x_encode_tuple_header(&x, 2) < 0)
+ fail("ei_x_encode_tuple_header() failed");
+ if (ei_x_encode_atom(&x, ei_thisnodename(&ec)) < 0)
+ fail("ei_x_encode_atom() failed");
+ if (ei_x_encode_list_header(&x, nref) < 0)
+ fail("ei_x_encode_list_header() failed");
+ for (i = 0; i < nref; i++) {
+ erlang_ref ref;
+ if (ei_make_ref(&ec, &ref))
+ fail("ei_make_ref() failed");
+ if (ei_x_encode_ref(&x, &ref))
+ fail("ei_x_encode_ref() failed");
+ }
+ if (ei_x_encode_empty_list(&x) < 0)
+ fail("ei_x_encode_empty_list() failed");
+ send_errno_result(ei_send(fd, &pid, x.buff, x.index));
+ ei_x_free(&x);
+}
+
+static void cmd_ei_make_pids(char* buf, int len)
+{
+ int index = 0;
+ long fd;
+ erlang_pid from_pid;
+ erlang_pid *self;
+ ei_x_buff x;
+ int i;
+ int npid = 1000;
+
+ if (ei_decode_long(buf, &index, &fd) < 0)
+ fail("expected long");
+ if (ei_decode_pid(buf, &index, &from_pid) < 0)
+ fail("expected pid (node)");
+ if (ei_x_new_with_version(&x) < 0)
+ fail("ei_x_new_with_version");
+ if (ei_x_encode_tuple_header(&x, 2) < 0)
+ fail("ei_x_encode_tuple_header() failed");
+ if (ei_x_encode_atom(&x, ei_thisnodename(&ec)) < 0)
+ fail("ei_x_encode_atom() failed");
+ if (ei_x_encode_list_header(&x, 1+npid) < 0)
+ fail("ei_x_encode_list_header() failed");
+ self = ei_self(&ec);
+ if (!self)
+ fail("ei_self() failed");
+ if (ei_x_encode_pid(&x, self))
+ fail("ei_x_encode_pid() failed");
+ for (i = 0; i < npid; i++) {
+ erlang_pid pid;
+ if (ei_make_pid(&ec, &pid))
+ fail("ei_make_pid() failed");
+ if (ei_x_encode_pid(&x, &pid))
+ fail("ei_x_encode_pid() failed");
+ }
+ if (ei_x_encode_empty_list(&x) < 0)
+ fail("ei_x_encode_empty_list() failed");
+ send_errno_result(ei_send(fd, &from_pid, x.buff, x.index));
+ ei_x_free(&x);
+}
+
static void cmd_ei_format_pid(char* buf, int len)
{
int index = 0;
@@ -225,7 +330,7 @@ static void cmd_ei_send_funs(char* buf, int len)
fail("expected Fun1");
if (ei_decode_fun(buf, &index, &fun2) < 0)
fail("expected Fun2");
- if (ei_decode_bitstring(buf, &index, &bitstring, &bitoffs, &bits) < 0)
+ if (ei_decode_bitstring(buf, &index, (const char**)&bitstring, &bitoffs, &bits) < 0)
fail("expected bitstring");
if (ei_x_new_with_version(&x) < 0)
fail("ei_x_new_with_version");
diff --git a/lib/erl_interface/test/ei_connect_SUITE_data/einode.c b/lib/erl_interface/test/ei_connect_SUITE_data/einode.c
index bb71575740..8af8760f30 100644
--- a/lib/erl_interface/test/ei_connect_SUITE_data/einode.c
+++ b/lib/erl_interface/test/ei_connect_SUITE_data/einode.c
@@ -21,6 +21,7 @@
/* to test multiple threads in ei */
#include <stdlib.h>
+#include <string.h>
#include <stdio.h>
#ifdef __WIN32__
@@ -28,20 +29,12 @@
#include <windows.h>
#include <process.h>
#else
-#ifndef VXWORKS
#include <pthread.h>
-#endif
#include <sys/socket.h>
+#include <unistd.h>
#endif
#include "ei.h"
-#include "erl_interface.h"
-
-#ifdef VXWORKS
-#define MAIN cnode
-#else
-#define MAIN main
-#endif
/*
A small einode.
@@ -102,35 +95,28 @@ static void*
return 0;
}
-MAIN(int argc, char *argv[])
+int main(int argc, char *argv[])
{
int i, n, no_threads;
-#ifndef VXWORKS
#ifdef __WIN32__
HANDLE threads[100];
#else
pthread_t threads[100];
#endif
-#endif
if (argc < 3)
exit(1);
- erl_init(NULL, 0);
+ ei_init();
cookie = argv[1];
n = atoi(argv[2]);
if (n > 100)
exit(2);
desthost = argv[3];
-#ifndef VXWORKS
no_threads = argv[4] != NULL && strcmp(argv[4], "nothreads") == 0;
-#else
- no_threads = 1;
-#endif
for (i = 0; i < n; ++i) {
if (!no_threads) {
-#ifndef VXWORKS
#ifdef __WIN32__
unsigned tid;
threads[i] = (HANDLE)_beginthreadex(NULL, 0, einode_thread,
@@ -138,14 +124,10 @@ MAIN(int argc, char *argv[])
#else
pthread_create(&threads[i], NULL, einode_thread, (void*)i);
#endif
-#else
- ;
-#endif
} else
einode_thread((void*)i);
}
if (!no_threads)
-#ifndef VXWORKS
for (i = 0; i < n; ++i) {
#ifdef __WIN32__
if (WaitForSingleObject(threads[i], INFINITE) != WAIT_OBJECT_0)
@@ -154,9 +136,6 @@ MAIN(int argc, char *argv[])
#endif
printf("bad wait thread %d\n", i);
}
-#else
- ;
-#endif
printf("ok\n");
return 0;
}
diff --git a/lib/erl_interface/test/ei_decode_SUITE.erl b/lib/erl_interface/test/ei_decode_SUITE.erl
index a5fd5542c8..a2b5d1c443 100644
--- a/lib/erl_interface/test/ei_decode_SUITE.erl
+++ b/lib/erl_interface/test/ei_decode_SUITE.erl
@@ -33,7 +33,9 @@
test_ei_decode_char/1,
test_ei_decode_nonoptimal/1,
test_ei_decode_misc/1,
- test_ei_decode_utf8_atom/1]).
+ test_ei_decode_utf8_atom/1,
+ test_ei_decode_iodata/1,
+ test_ei_cmp_nc/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -41,7 +43,8 @@ all() ->
[test_ei_decode_long, test_ei_decode_ulong,
test_ei_decode_longlong, test_ei_decode_ulonglong,
test_ei_decode_char, test_ei_decode_nonoptimal,
- test_ei_decode_misc, test_ei_decode_utf8_atom].
+ test_ei_decode_misc, test_ei_decode_utf8_atom,
+ test_ei_decode_iodata, test_ei_cmp_nc].
init_per_testcase(Case, Config) ->
runner:init_per_testcase(?MODULE, Case, Config).
@@ -77,29 +80,19 @@ test_ei_decode_ulong(Config) when is_list(Config) ->
%% ######################################################################## %%
test_ei_decode_longlong(Config) when is_list(Config) ->
- case os:type() of
- vxworks ->
- {skip,"Skipped on VxWorks"};
- _ ->
- P = runner:start(Config, ?test_ei_decode_longlong),
- send_integers2(P),
- runner:recv_eot(P),
- ok
- end.
+ P = runner:start(Config, ?test_ei_decode_longlong),
+ send_integers2(P),
+ runner:recv_eot(P),
+ ok.
%% ######################################################################## %%
test_ei_decode_ulonglong(Config) when is_list(Config) ->
- case os:type() of
- vxworks ->
- {skip,"Skipped on VxWorks"};
- _ ->
- P = runner:start(Config, ?test_ei_decode_ulonglong),
- send_integers2(P),
- runner:recv_eot(P),
- ok
- end.
+ P = runner:start(Config, ?test_ei_decode_ulonglong),
+ send_integers2(P),
+ runner:recv_eot(P),
+ ok.
%% ######################################################################## %%
@@ -128,13 +121,8 @@ test_ei_decode_nonoptimal(Config) when is_list(Config) ->
send_non_optimal_pos(P), % decode_char
send_non_optimal(P), % decode_long
send_non_optimal_pos(P), % decode_ulong
- case os:type() of
- vxworks ->
- ok;
- _ ->
- send_non_optimal(P), % decode_longlong
- send_non_optimal_pos(P) % decode_ulonglong
- end,
+ send_non_optimal(P), % decode_longlong
+ send_non_optimal_pos(P), % decode_ulonglong
runner:recv_eot(P),
ok.
@@ -230,6 +218,165 @@ test_ei_decode_utf8_atom(Config) ->
%% ######################################################################## %%
+test_ei_decode_iodata(Config) when is_list(Config) ->
+ P = runner:start(Config, ?test_ei_decode_iodata),
+
+ check_decode_iodata(P, [], true),
+ check_decode_iodata(P, $a, false),
+ check_decode_iodata(P, an_atom, false),
+ check_decode_iodata(P, self(), false),
+ check_decode_iodata(P, [$a,$a], true),
+ check_decode_iodata(P, [$a|$a], false),
+ check_decode_iodata(P, [[$a|$a],$a], false),
+ check_decode_iodata(P, "hej", true),
+ check_decode_iodata(P, ["hej", " ", "hopp"], true),
+ check_decode_iodata(P, <<"hopp san sa">>, true),
+ check_decode_iodata(P, [$a | <<"a">>], true),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, $ , "san" | <<" sa">>]], true),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, 0, "san" | <<" sa">>]], true),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, 256, "san" | <<" sa">>]], false),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, -2, "san" | <<" sa">>]], false),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, $ , san | <<" sa">>]], false),
+ check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, $ , "san s" | $a], " "], false),
+ check_decode_iodata(P, [[[[[[[]|<<"a">>]]]]]], true),
+ check_decode_iodata(P, [[[[[[[[]]]]]]],[[[],[],[]]]], true),
+
+ send_raw(P, <<"done">>),
+ runner:recv_eot(P),
+ ok.
+
+check_decode_iodata(P, Data, Valid) ->
+ io:format("~n~nChecking: ~p~n", [Data]),
+ Expect = case Valid of
+ true ->
+ io:format("Expecting decode SUCCESS... ", []),
+ iolist_to_binary(Data);
+ false ->
+ io:format("Expecting decode FAILURE... ", []),
+ badarg = try
+ iolist_to_binary(Data)
+ catch
+ error:badarg ->
+ badarg
+ end,
+ decode_size_failed
+ end,
+ send_term_as_binary(P, Data),
+ Actual = case runner:get_term(P) of
+ {bytes, B} when is_binary(B) ->
+ B;
+ {bytes, L} when is_list(L) ->
+ list_to_binary(L);
+ {term, T} ->
+ T
+ end,
+ case Expect =:= Actual of
+ true ->
+ io:format("Expected result!~n",[]),
+ ok;
+ false ->
+ io:format("Expect: ~w~nActual: ~w~n", [Expect, Actual]),
+ ct:fail(unexpected_result)
+ end.
+
+%% ######################################################################## %%
+
+%% Should be moved to its own suite...
+
+test_ei_cmp_nc(Config) when is_list(Config) ->
+ P = runner:start(Config, ?test_ei_cmp_nc),
+ R0 = make_ref(),
+ R1 = make_ref(),
+ check_cmp(P, R0, R0),
+ check_cmp(P, R0, R1),
+ check_cmp(P, R1, R0),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@c, 4711}, [17, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@d, 4711}, [17, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@bc, 4711}, [17, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4712}, [17, 17, 17]), mk_ref({a@b, 4711}, [17, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@b, 4711}, [18, 17, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@b, 4711}, [17, 18, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@b, 4711}, [17, 17, 18])),
+ check_cmp(P, mk_ref({a@b, 4711}, [0, 17]), mk_ref({a@b, 4711}, [18])),
+ check_cmp(P, mk_ref({a@b, 4711}, [17, 17]), mk_ref({a@b, 4711}, [17, 18])),
+ check_cmp(P, mk_ref({a@b, 4711}, [0, 0, 0]), mk_ref({a@b, 4711}, [17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [0, 0, 17]), mk_ref({a@b, 4711}, [18, 17])),
+ check_cmp(P, mk_ref({a@b, 4711}, [0, 17, 17]), mk_ref({a@b, 4711}, [18])),
+
+ check_cmp(P, self(), self()),
+ check_cmp(P, self(), whereis(file_server_2)),
+ check_cmp(P, whereis(file_server_2), self()),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@c, 4711}, 17, 17)),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@d, 4711}, 17, 17)),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@bc, 4711}, 17, 17)),
+ check_cmp(P, mk_pid({a@b, 4712}, 17, 17), mk_pid({a@b, 4711}, 17, 17)),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@b, 4711}, 18, 17)),
+ check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@b, 4711}, 17, 18)),
+
+ Cmd = case os:type() of
+ {win32, _} -> "cmd /q /c true";
+ _ -> "true"
+ end,
+
+ Prt0 = open_port({spawn, Cmd},[]),
+ Prt1 = open_port({spawn, Cmd},[]),
+
+ check_cmp(P, Prt0, Prt0),
+ check_cmp(P, Prt1, Prt0),
+ check_cmp(P, Prt0, Prt1),
+ check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@b, 4711}, 17)),
+ check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@d, 4711}, 17)),
+ check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@bc, 4711}, 17)),
+ check_cmp(P, mk_port({a@b, 4712}, 17), mk_port({a@b, 4711}, 17)),
+ check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@b, 4711}, 18)),
+
+ send_raw(P, <<"done">>),
+ runner:recv_eot(P),
+ ok.
+
+mk_pid(Node, Num, Ser) ->
+ erts_test_utils:mk_ext_pid(Node, Num, Ser).
+
+mk_port(Node, Id) ->
+ erts_test_utils:mk_ext_port(Node, Id).
+
+mk_ref(Node, Numbers) ->
+ erts_test_utils:mk_ext_ref(Node, Numbers).
+
+check_cmp(P, A, B) when is_pid(A), is_pid(B) ->
+ check_cmp(P, {cmp_pids, A, B});
+check_cmp(P, A, B) when is_port(A), is_port(B) ->
+ check_cmp(P, {cmp_ports, A, B});
+check_cmp(P, A, B) when is_reference(A), is_reference(B) ->
+ check_cmp(P, {cmp_refs, A, B}).
+
+check_cmp(P, {_, A, B} = Data) ->
+ io:format("~n~nChecking: ~p~n", [Data]),
+ send_term_as_binary(P, Data),
+ {term, Res} = runner:get_term(P),
+ io:format("Res = ~p~n", [Res]),
+ case {{ei_cmp, Res}, {erlang, cmp_nc(A, B)}} of
+ {{ei_cmp, 0}, {erlang, equal}} ->
+ ok;
+ {{ei_cmp, Cmp}, {erlang, less_than}} when is_integer(Cmp),
+ Cmp < 0 ->
+ ok;
+ {{ei_cmp, Cmp}, {erlang, larger_than}} when is_integer(Cmp),
+ Cmp > 0 -> ok;
+ Fail ->
+ ct:fail(Fail)
+ end.
+
+cmp_nc(A, A) ->
+ equal;
+cmp_nc(A, B) when A < B ->
+ less_than;
+cmp_nc(_, _) ->
+ larger_than.
+
+
+%% ######################################################################## %%
+
send_term_as_binary(Port, Term) when is_port(Port) ->
Port ! {self(), {command, term_to_binary(Term)}}.
diff --git a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
index 29b2ac7f84..074c71733f 100644
--- a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
+++ b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
@@ -19,10 +19,7 @@
*/
#include <string.h>
-
-#ifdef VXWORKS
-#include "reclaim.h"
-#endif
+#include <stdlib.h>
#include "ei_runner.h"
@@ -31,15 +28,9 @@
* Author: Kent
*/
-#ifdef VXWORKS
-#define MESSAGE_BACK(SIZE) \
- message("err = %d, size2 = %d, expected size = %d", \
- err, size1, SIZE);
-#else
#define MESSAGE_BACK(SIZE) \
message("err = %d, size2 = %d, expected size = %d, long long val = %lld", \
- err, size1, SIZE, (EI_LONGLONG)p);
-#endif
+ err, size1, SIZE, (EI_LONGLONG)p)
#define ERLANG_ANY (ERLANG_ASCII|ERLANG_LATIN1|ERLANG_UTF8)
@@ -82,7 +73,7 @@ int ei_decode_my_string(const char *buf, int *index, char *to,
} \
\
err = ei_ ## FUNC(buf+1, &size2, &p); \
- MESSAGE_BACK(SIZE) \
+ MESSAGE_BACK(SIZE); \
if (err != 0) { \
if (err != -1) { \
fail("returned non zero but not -1"); \
@@ -256,6 +247,64 @@ int ei_decode_my_string(const char *buf, int *index, char *to,
//#define EI_DECODE_UTF8_STRING(FUNC,SIZE,VAL)
+static void decode_double(double VAL)
+{
+ const int SIZE = 9;
+ double p;
+ char *buf;
+ int size1 = 0;
+ int size2 = 0;
+ int err;
+ message("ei_decode_double value should be %lf", VAL);
+ buf = read_packet(NULL);
+
+ {
+ int ix = size1, type = -1, len = -2;
+ if (ei_get_type(buf+1, &ix, &type, &len) != 0
+ || ix != size1 || type != ERL_FLOAT_EXT || len != 0) {
+ fail2("ei_get_type failed for double, type=%d, len=%d", type, len);
+ }
+ }
+
+ err = ei_decode_double(buf+1, &size1, NULL);
+ message("err = %d, size1 = %d, expected size = %d",
+ err, size1, SIZE);
+ if (err != 0) {
+ if (err != -1) {
+ fail("returned non zero but not -1 if NULL pointer");
+ } else {
+ fail("returned non zero if NULL pointer");
+ }
+ return;
+ }
+
+ err = ei_decode_double(buf+1, &size2, &p);
+ MESSAGE_BACK(SIZE);
+ if (err != 0) {
+ if (err != -1) {
+ fail("returned non zero but not -1");
+ } else {
+ fail("returned non zero");
+ }
+ return;
+ }
+ if (p != VAL) {
+ fail("value is not correct");
+ return;
+ }
+
+ if (size1 != size2) {
+ fail("size with and without pointer differs");
+ return;
+ }
+
+ if (size1 != SIZE) {
+ fail1("size of encoded data (%d) is incorrect", size1);
+ return;
+ }
+ free_packet(buf);
+}
+
static void decode_bin(int exp_size, const char* val, int exp_len)
{
char p[1024];
@@ -483,7 +532,6 @@ TESTCASE(test_ei_decode_longlong)
{
ei_init();
-#ifndef VXWORKS
EI_DECODE_2 (decode_longlong, 2, EI_LONGLONG, 0);
EI_DECODE_2 (decode_longlong, 2, EI_LONGLONG, 255);
EI_DECODE_2 (decode_longlong, 5, EI_LONGLONG, 256);
@@ -509,7 +557,6 @@ TESTCASE(test_ei_decode_longlong)
EI_DECODE_2_FAIL(decode_longlong, 11, EI_LONGLONG, ll(0xffffffffffffffff));
EI_DECODE_2_FAIL(decode_longlong, 1, EI_LONGLONG, 0); /* Illegal type */
-#endif
report(1);
}
@@ -519,7 +566,6 @@ TESTCASE(test_ei_decode_ulonglong)
{
ei_init();
-#ifndef VXWORKS
EI_DECODE_2 (decode_ulonglong, 2, EI_ULONGLONG, 0);
EI_DECODE_2 (decode_ulonglong, 2, EI_ULONGLONG, 255);
EI_DECODE_2 (decode_ulonglong, 5, EI_ULONGLONG, 256);
@@ -545,7 +591,6 @@ TESTCASE(test_ei_decode_ulonglong)
EI_DECODE_2 (decode_ulonglong,11, EI_ULONGLONG, ll(0xffffffffffffffff));
EI_DECODE_2_FAIL(decode_ulonglong, 1, EI_ULONGLONG, 0); /* Illegal type */
-#endif
report(1);
}
@@ -637,8 +682,6 @@ TESTCASE(test_ei_decode_nonoptimal)
/* ---------------------------------------------------------------- */
-#ifndef VXWORKS
-
EI_DECODE_2(decode_longlong, 2, EI_LONGLONG, 42);
EI_DECODE_2(decode_longlong, 5, EI_LONGLONG, 42);
EI_DECODE_2(decode_longlong, 4, EI_LONGLONG, 42);
@@ -681,8 +724,6 @@ TESTCASE(test_ei_decode_nonoptimal)
/* EI_DECODE_2(decode_ulonglong, EI_ULONGLONG, -42); */
/* EI_DECODE_2(decode_ulonglong, EI_ULONGLONG, -42); */
-#endif /* !VXWORKS */
-
/* ---------------------------------------------------------------- */
report(1);
@@ -697,9 +738,9 @@ TESTCASE(test_ei_decode_misc)
/*
EI_DECODE_0(decode_version);
*/
- EI_DECODE_2(decode_double, 9, double, 0.0);
- EI_DECODE_2(decode_double, 9, double, -1.0);
- EI_DECODE_2(decode_double, 9, double, 1.0);
+ decode_double(0.0);
+ decode_double(-1.0);
+ decode_double(1.0);
EI_DECODE_2(decode_boolean, 8, int, 0);
EI_DECODE_2(decode_boolean, 7, int, 1);
@@ -771,6 +812,192 @@ TESTCASE(test_ei_decode_utf8_atom)
/* ******************************************************************** */
+TESTCASE(test_ei_decode_iodata)
+{
+ char *buf = NULL, *data = NULL;
+ ei_init();
+
+ while (1) {
+ int unexpected_write = 0;
+ int i;
+ int len, index, saved_index, err;
+
+ if (buf)
+ free_packet(buf);
+ buf = read_packet(&len);
+
+ if (len == 4
+ && buf[0] == 'd'
+ && buf[1] == 'o'
+ && buf[2] == 'n'
+ && buf[3] == 'e') {
+ break;
+ }
+
+ index = 0;
+ err = ei_decode_version(buf, &index, NULL);
+ if (err != 0) {
+ free_packet(buf);
+ fail1("ei_decode_version returned %d", err);
+ }
+ saved_index = index;
+ err = ei_decode_iodata(buf, &index, &len, NULL);
+ if (err != 0) {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "decode_size_failed");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ continue;
+ }
+ if (data) {
+ data -= 100;
+ free(data);
+ }
+ data = malloc(len + 200);
+ if (!data) {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "malloc_failed");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ continue;
+ }
+ for (i = 0; i < len + 200; i++)
+ data[i] = 'Y';
+ data += 100;
+ err = ei_decode_iodata(buf, &saved_index, NULL, (unsigned char *) data);
+ if (err != 0) {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "decode_data_failed");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ continue;
+ }
+
+ for (i = -100; i < 0; i++) {
+ if (data[i] != 'Y') {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "unexpected_write_before_data");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ unexpected_write = !0;
+ break;
+ }
+ }
+
+ if (!unexpected_write) {
+ for (i = len; i < len + 100; i++) {
+ if (data[i] != 'Y') {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_atom(&x, "unexpected_write_after_data");
+ send_bin_term(&x);
+ ei_x_free(&x);
+ unexpected_write = !0;
+ break;
+ }
+ }
+ }
+
+ if (!unexpected_write)
+ send_buffer(data, len);
+ }
+
+ if (buf)
+ free_packet(buf);
+ if (data) {
+ data -= 100;
+ free(data);
+ }
+ report(1);
+}
+
+/* ******************************************************************** */
+
+/*
+ * Does not belong here move to its own suite...
+ */
+TESTCASE(test_ei_cmp_nc)
+{
+ char *buf = NULL;
+ ei_init();
+
+ while (1) {
+ int len, index, arity;
+ char atom[MAXATOMLEN_UTF8];
+ ei_x_buff x;
+
+ if (buf)
+ free_packet(buf);
+ buf = read_packet(&len);
+
+ if (len == 4
+ && buf[0] == 'd'
+ && buf[1] == 'o'
+ && buf[2] == 'n'
+ && buf[3] == 'e') {
+ break;
+ }
+
+ ei_x_new_with_version(&x);
+ index = 0;
+ if (ei_decode_version(buf, &index, NULL)
+ || ei_decode_tuple_header(buf, &index, &arity)
+ || (arity != 3)
+ || ei_decode_atom(buf, &index, atom)) {
+ ei_x_encode_atom(&x, "decode_tuple_failed");
+ }
+ else if (strcmp(atom, "cmp_pids") == 0) {
+ erlang_pid a, b;
+ if (ei_decode_pid(buf, &index, &a)
+ || ei_decode_pid(buf, &index, &b)) {
+ ei_x_encode_atom(&x, "decode_pids_failed");
+ }
+ else {
+ long res = (long) ei_cmp_pids(&a, &b);
+ ei_x_encode_long(&x, res);
+ }
+ }
+ else if (strcmp(atom, "cmp_ports") == 0) {
+ erlang_port a, b;
+ if (ei_decode_port(buf, &index, &a)
+ || ei_decode_port(buf, &index, &b)) {
+ ei_x_encode_atom(&x, "decode_ports_failed");
+ }
+ else {
+ long res = (long) ei_cmp_ports(&a, &b);
+ ei_x_encode_long(&x, res);
+ }
+ }
+ else if (strcmp(atom, "cmp_refs") == 0) {
+ erlang_ref a, b;
+ if (ei_decode_ref(buf, &index, &a)
+ || ei_decode_ref(buf, &index, &b)) {
+ ei_x_encode_atom(&x, "decode_refs_failed");
+ }
+ else {
+ long res = (long) ei_cmp_refs(&a, &b);
+ ei_x_encode_long(&x, res);
+ }
+ }
+ else {
+ ei_x_encode_atom(&x, "unexpected_operation");
+ }
+
+ send_bin_term(&x);
+ ei_x_free(&x);
+ }
+
+ if (buf)
+ free_packet(buf);
+ report(1);
+}
+
+/* ******************************************************************** */
+
int ei_decode_my_atom_as(const char *buf, int *index, char *to,
struct my_atom *atom) {
erlang_char_encoding was,result;
diff --git a/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c b/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c
index 7f21688d41..637c71751c 100644
--- a/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c
+++ b/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c
@@ -18,10 +18,6 @@
* %CopyrightEnd%
*/
-#ifdef VXWORKS
-#include "reclaim.h"
-#endif
-
#include "ei_runner.h"
/*
@@ -83,16 +79,52 @@ struct Type fun_type = {
(encodeFT*)ei_encode_fun, (x_encodeFT*)ei_x_encode_fun
};
+int ei_decode_my_pid(const char *buf, int *index, struct my_obj* obj)
+{
+ int ix = *index;
+ int type = -1;
+ int size = -2;
+ if (ei_get_type(buf, &ix, &type, &size) != 0
+ || ix != *index || type != ERL_PID_EXT || size != 0) {
+ fail2("ei_get_type failed for pid, type=%d size=%d", type, size);
+ }
+ return ei_decode_pid(buf, index, (erlang_pid*)obj);
+}
+
struct Type pid_type = {
- "pid", "erlang_pid", (decodeFT*)ei_decode_pid,
+ "pid", "erlang_pid", ei_decode_my_pid,
(encodeFT*)ei_encode_pid, (x_encodeFT*)ei_x_encode_pid
};
+int ei_decode_my_port(const char *buf, int *index, struct my_obj* obj)
+{
+ int ix = *index;
+ int type = -1;
+ int size = -2;
+ if (ei_get_type(buf, &ix, &type, &size) != 0
+ || ix != *index || type != ERL_PORT_EXT || size != 0) {
+ fail2("ei_get_type failed for port, type=%d size=%d", type, size);
+ }
+ return ei_decode_port(buf, index, (erlang_port*)obj);
+}
+
struct Type port_type = {
- "port", "erlang_port", (decodeFT*)ei_decode_port,
+ "port", "erlang_port", ei_decode_my_port,
(encodeFT*)ei_encode_port, (x_encodeFT*)ei_x_encode_port
};
+int ei_decode_my_ref(const char *buf, int *index, struct my_obj* obj)
+{
+ int ix = *index;
+ int type = -1;
+ int size = -2;
+ if (ei_get_type(buf, &ix, &type, &size) != 0
+ || ix != *index || type != ERL_NEW_REFERENCE_EXT || size != 0) {
+ fail2("ei_get_type failed for ref, type=%d size=%d", type, size);
+ }
+ return ei_decode_ref(buf, index, (erlang_ref*)obj);
+}
+
struct Type ref_type = {
"ref", "erlang_ref", (decodeFT*)ei_decode_ref,
(encodeFT*)ei_encode_ref, (x_encodeFT*)ei_x_encode_ref
diff --git a/lib/erl_interface/test/ei_encode_SUITE.erl b/lib/erl_interface/test/ei_encode_SUITE.erl
index 0267a5126f..10fcd6b871 100644
--- a/lib/erl_interface/test/ei_encode_SUITE.erl
+++ b/lib/erl_interface/test/ei_encode_SUITE.erl
@@ -101,59 +101,49 @@ test_ei_encode_ulong(Config) when is_list(Config) ->
%% ######################################################################## %%
test_ei_encode_longlong(Config) when is_list(Config) ->
- case os:type() of
- vxworks ->
- {skip,"Skipped on VxWorks"};
- _ ->
- P = runner:start(Config, ?test_ei_encode_longlong),
-
- {<<97,0>> ,0} = get_buf_and_term(P),
- {<<97,255>> ,255} = get_buf_and_term(P),
- {<<98,256:32/big-signed-integer>>,256} = get_buf_and_term(P),
- {<<98,-1:32/big-signed-integer>> ,-1} = get_buf_and_term(P),
-
- {<<98, 16#07ffffff:32/big-signed-integer>>, 16#07ffffff} = get_buf_and_term(P),
- {<<98,-16#08000000:32/big-signed-integer>>,-16#08000000} = get_buf_and_term(P),
- {<<110,4,0, 0,0,0,8>> , 16#08000000} = get_buf_and_term(P),
- {<<110,4,1, 1,0,0,8>> ,-16#08000001} = get_buf_and_term(P),
-
- {<<110,4,0, 255,255,255,127>> , 16#7fffffff} = get_buf_and_term(P),
- {<<110,4,1, 0,0,0,128>> ,-16#80000000} = get_buf_and_term(P),
- {<<110,6,0, 255,255,255,255,255,127>> , 16#7fffffffffff} = get_buf_and_term(P),
- {<<110,6,1, 0,0,0,0,0,128>> ,-16#800000000000} = get_buf_and_term(P),
- {<<110,8,0, 255,255,255,255,255,255,255,127>>,16#7fffffffffffffff} = get_buf_and_term(P),
- {<<110,8,1, 0,0,0,0,0,0,0,128>> ,-16#8000000000000000} = get_buf_and_term(P),
-
- runner:recv_eot(P),
- ok
- end.
+ P = runner:start(Config, ?test_ei_encode_longlong),
+
+ {<<97,0>> ,0} = get_buf_and_term(P),
+ {<<97,255>> ,255} = get_buf_and_term(P),
+ {<<98,256:32/big-signed-integer>>,256} = get_buf_and_term(P),
+ {<<98,-1:32/big-signed-integer>> ,-1} = get_buf_and_term(P),
+
+ {<<98, 16#07ffffff:32/big-signed-integer>>, 16#07ffffff} = get_buf_and_term(P),
+ {<<98,-16#08000000:32/big-signed-integer>>,-16#08000000} = get_buf_and_term(P),
+ {<<110,4,0, 0,0,0,8>> , 16#08000000} = get_buf_and_term(P),
+ {<<110,4,1, 1,0,0,8>> ,-16#08000001} = get_buf_and_term(P),
+
+ {<<110,4,0, 255,255,255,127>> , 16#7fffffff} = get_buf_and_term(P),
+ {<<110,4,1, 0,0,0,128>> ,-16#80000000} = get_buf_and_term(P),
+ {<<110,6,0, 255,255,255,255,255,127>> , 16#7fffffffffff} = get_buf_and_term(P),
+ {<<110,6,1, 0,0,0,0,0,128>> ,-16#800000000000} = get_buf_and_term(P),
+ {<<110,8,0, 255,255,255,255,255,255,255,127>>,16#7fffffffffffffff} = get_buf_and_term(P),
+ {<<110,8,1, 0,0,0,0,0,0,0,128>> ,-16#8000000000000000} = get_buf_and_term(P),
+
+ runner:recv_eot(P),
+ ok.
%% ######################################################################## %%
test_ei_encode_ulonglong(Config) when is_list(Config) ->
- case os:type() of
- vxworks ->
- {skip,"Skipped on VxWorks"};
- _ ->
- P = runner:start(Config, ?test_ei_encode_ulonglong),
-
- {<<97,0>> ,0} = get_buf_and_term(P),
- {<<97,255>> ,255} = get_buf_and_term(P),
- {<<98,256:32/big-unsigned-integer>>,256} = get_buf_and_term(P),
-
- {<<98, 16#07ffffff:32/big-signed-integer>>,16#07ffffff} = get_buf_and_term(P),
- {<<110,4,0, 0,0,0,8>> ,16#08000000} = get_buf_and_term(P),
-
- {<<110,4,0, 255,255,255,127>> ,16#7fffffff} = get_buf_and_term(P),
- {<<110,4,0, 0,0,0,128>> ,16#80000000} = get_buf_and_term(P),
- {<<110,4,0, 255,255,255,255>> ,16#ffffffff} = get_buf_and_term(P),
- {<<110,6,0, 255,255,255,255,255,255>>,16#ffffffffffff} = get_buf_and_term(P),
- {<<110,8,0, 255,255,255,255,255,255,255,255>>,16#ffffffffffffffff} = get_buf_and_term(P),
+ P = runner:start(Config, ?test_ei_encode_ulonglong),
- runner:recv_eot(P),
- ok
- end.
+ {<<97,0>> ,0} = get_buf_and_term(P),
+ {<<97,255>> ,255} = get_buf_and_term(P),
+ {<<98,256:32/big-unsigned-integer>>,256} = get_buf_and_term(P),
+
+ {<<98, 16#07ffffff:32/big-signed-integer>>,16#07ffffff} = get_buf_and_term(P),
+ {<<110,4,0, 0,0,0,8>> ,16#08000000} = get_buf_and_term(P),
+
+ {<<110,4,0, 255,255,255,127>> ,16#7fffffff} = get_buf_and_term(P),
+ {<<110,4,0, 0,0,0,128>> ,16#80000000} = get_buf_and_term(P),
+ {<<110,4,0, 255,255,255,255>> ,16#ffffffff} = get_buf_and_term(P),
+ {<<110,6,0, 255,255,255,255,255,255>>,16#ffffffffffff} = get_buf_and_term(P),
+ {<<110,8,0, 255,255,255,255,255,255,255,255>>,16#ffffffffffffffff} = get_buf_and_term(P),
+
+ runner:recv_eot(P),
+ ok.
%% ######################################################################## %%
diff --git a/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c b/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c
index ed129b881c..6a1791f507 100644
--- a/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c
+++ b/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c
@@ -18,10 +18,6 @@
* %CopyrightEnd%
*/
-#ifdef VXWORKS
-#include "reclaim.h"
-#endif
-
#include "ei_runner.h"
/*
@@ -460,8 +456,6 @@ TESTCASE(test_ei_encode_longlong)
{
ei_init();
-#ifndef VXWORKS
-
EI_ENCODE_1(encode_longlong, 0);
EI_ENCODE_1(encode_longlong, 255);
@@ -490,8 +484,6 @@ TESTCASE(test_ei_encode_longlong)
EI_ENCODE_1(encode_longlong, -ll(0x8000000000000000));
-#endif /* !VXWORKS */
-
report(1);
}
@@ -501,8 +493,6 @@ TESTCASE(test_ei_encode_ulonglong)
{
ei_init();
-#ifndef VXWORKS
-
EI_ENCODE_1(encode_ulonglong, 0);
EI_ENCODE_1(encode_ulonglong, 255);
@@ -523,8 +513,6 @@ TESTCASE(test_ei_encode_ulonglong)
EI_ENCODE_1(encode_ulonglong, ll(0xffffffffffffffff));
-#endif /* !VXWORKS */
-
report(1);
}
diff --git a/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c b/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c
index a1ba0cd6ec..ef6011b491 100644
--- a/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c
+++ b/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c
@@ -18,10 +18,7 @@
* %CopyrightEnd%
*/
-#ifdef VXWORKS
-#include "reclaim.h"
-#endif
-
+#include <stdlib.h>
#include "ei_runner.h"
#include <string.h>
diff --git a/lib/erl_interface/test/erl_global_SUITE.erl b/lib/erl_interface/test/ei_global_SUITE.erl
index 6d3a75c8d7..da80ab24cd 100644
--- a/lib/erl_interface/test/erl_global_SUITE.erl
+++ b/lib/erl_interface/test/ei_global_SUITE.erl
@@ -19,22 +19,27 @@
%%
%%
--module(erl_global_SUITE).
+-module(ei_global_SUITE).
-include_lib("common_test/include/ct.hrl").
--include("erl_global_SUITE_data/erl_global_test_cases.hrl").
+-include("ei_global_SUITE_data/ei_global_test_cases.hrl").
-export([all/0,suite/0,
init_per_testcase/2,
- erl_global_registration/1,
- erl_global_whereis/1, erl_global_names/1]).
+ ei_global_registration/1,
+ ei_global_whereis/1,
+ ei_global_names/1
+ ]).
-import(runner, [get_term/1,send_term/2]).
-define(GLOBAL_NAME, global_register_node_test).
all() ->
- [erl_global_registration, erl_global_whereis, erl_global_names].
+ [ei_global_registration, ei_global_whereis, ei_global_names].
+
+get_group(Config) ->
+ proplists:get_value(name, proplists:get_value(tc_group_properties,Config)).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -43,82 +48,76 @@ suite() ->
init_per_testcase(Case, Config) ->
runner:init_per_testcase(?MODULE, Case, Config).
-erl_global_registration(Config) when is_list(Config) ->
+ei_global_registration(Config) when is_list(Config) ->
P = runner:start(Config, ?interpret),
- {ok, Fd} = erl_connect(P, node(), 42, erlang:get_cookie(), 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, ussi),
+ {ok,Fd} = ei_connect(P, node()),
- ok = erl_global_register(P, Fd, ?GLOBAL_NAME),
- ok = erl_global_unregister(P, Fd, ?GLOBAL_NAME),
+ ok = ei_global_register(P, Fd, ?GLOBAL_NAME),
+ ok = ei_global_unregister(P, Fd, ?GLOBAL_NAME),
- 0 = erl_close_connection(P,Fd),
runner:send_eot(P),
runner:recv_eot(P),
ok.
-erl_global_whereis(Config) when is_list(Config) ->
+ei_global_whereis(Config) when is_list(Config) ->
P = runner:start(Config, ?interpret),
- {ok, Fd} = erl_connect(P, node(), 42, erlang:get_cookie(), 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, ussi),
+ {ok,Fd} = ei_connect(P, node()),
Self = self(),
yes = global:register_name(?GLOBAL_NAME, Self),
- Self = erl_global_whereis(P, Fd, ?GLOBAL_NAME),
+ Self = ei_global_whereis(P, Fd, ?GLOBAL_NAME),
global:unregister_name(?GLOBAL_NAME),
- 0 = erl_close_connection(P, Fd),
runner:send_eot(P),
runner:recv_eot(P),
ok.
-erl_global_names(Config) when is_list(Config) ->
+ei_global_names(Config) when is_list(Config) ->
P = runner:start(Config, ?interpret),
- {ok, Fd} = erl_connect(P, node(), 42, erlang:get_cookie(), 0),
+ 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, ussi),
+ {ok,Fd} = ei_connect(P, node()),
Self = self(),
global:register_name(?GLOBAL_NAME, Self),
- {Names1, _N1} = erl_global_names(P, Fd),
+ {Names1, _N1} = ei_global_names(P, Fd),
true = lists:member(atom_to_list(?GLOBAL_NAME), Names1),
global:unregister_name(?GLOBAL_NAME),
- {Names2, _N2} = erl_global_names(P, Fd),
+ {Names2, _N2} = ei_global_names(P, Fd),
false = lists:member(atom_to_list(?GLOBAL_NAME), Names2),
- 0 = erl_close_connection(P, Fd),
runner:send_eot(P),
runner:recv_eot(P),
ok.
-%%% Interface functions for erl_interface functions.
-
-erl_connect(P, Node, Num, Cookie, Creation) ->
- send_command(P, erl_connect, [Num, Node, Cookie, Creation]),
- case get_term(P) of
- {term,{Fd,_}} when Fd >= 0 -> {ok,Fd};
- {term,{-1,Errno}} -> {error,Errno}
- end.
+%% %%% Interface functions for erl_interface functions.
-erl_close_connection(P, FD) ->
- send_command(P, erl_close_connection, [FD]),
- case get_term(P) of
- {term,Int} when is_integer(Int) -> Int
- end.
+%% erl_connect(P, Node, Num, Cookie, Creation) ->
+%% send_command(P, erl_connect, [Num, Node, Cookie, Creation]),
+%% case get_term(P) of
+%% {term,{Fd,_}} when Fd >= 0 -> {ok,Fd};
+%% {term,{-1,Errno}} -> {error,Errno}
+%% end.
-erl_global_register(P, Fd, Name) ->
- send_command(P, erl_global_register, [Fd,Name]),
+ei_global_register(P, Fd, Name) ->
+ send_command(P, ei_global_register, [Fd,Name]),
get_send_result(P).
-erl_global_whereis(P, Fd, Name) ->
- send_command(P, erl_global_whereis, [Fd,Name]),
+ei_global_whereis(P, Fd, Name) ->
+ send_command(P, ei_global_whereis, [Fd,Name]),
case get_term(P) of
{term, What} ->
What
end.
-erl_global_names(P, Fd) ->
- send_command(P, erl_global_names, [Fd]),
+ei_global_names(P, Fd) ->
+ send_command(P, ei_global_names, [Fd]),
case get_term(P) of
{term, What} ->
What
end.
-erl_global_unregister(P, Fd, Name) ->
- send_command(P, erl_global_unregister, [Fd,Name]),
+ei_global_unregister(P, Fd, Name) ->
+ send_command(P, ei_global_unregister, [Fd,Name]),
get_send_result(P).
get_send_result(P) ->
@@ -132,3 +131,17 @@ get_send_result(P) ->
send_command(P, Name, Args) ->
runner:send_term(P, {Name,list_to_tuple(Args)}).
+
+
+ei_connect_init(P, Num, Cookie, Creation, SockImpl) ->
+ send_command(P, ei_connect_init, [Num,Cookie,Creation,SockImpl]),
+ case get_term(P) of
+ {term,Int} when is_integer(Int) -> Int
+ end.
+
+ei_connect(P, Node) ->
+ send_command(P, ei_connect, [Node]),
+ case get_term(P) of
+ {term,{Fd,_}} when Fd >= 0 -> {ok,Fd};
+ {term,{-1,Errno}} -> {error,Errno}
+ end.
diff --git a/lib/erl_interface/test/erl_global_SUITE_data/Makefile.first b/lib/erl_interface/test/ei_global_SUITE_data/Makefile.first
index b2c62be1f2..5ec0c06af8 100644
--- a/lib/erl_interface/test/erl_global_SUITE_data/Makefile.first
+++ b/lib/erl_interface/test/ei_global_SUITE_data/Makefile.first
@@ -18,5 +18,5 @@
# %CopyrightEnd%
#
-erl_global_test_decl.c: erl_global_test.c
- erl -noinput -pa ../all_SUITE_data -s init_tc run erl_global_test -s erlang halt
+erl_global_test_decl.c: ei_global_test.c
+ erl -noinput -pa ../all_SUITE_data -s init_tc run ei_global_test -s erlang halt
diff --git a/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_global_SUITE_data/Makefile.src
index 9ad6d7809d..511e29d78c 100644
--- a/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_global_SUITE_data/Makefile.src
@@ -25,17 +25,18 @@ CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
LD = @LD@
LIBERL = @erl_interface_lib@
LIBEI = @erl_interface_eilib@
-LIBFLAGS = ../all_SUITE_data/runner@obj@ \
- $(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
+LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
+ $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
-OBJS = erl_global_test@obj@ erl_global_test_decl@obj@
+EI_GLOBAL_OBJS = ei_global_test@obj@ ei_global_test_decl@obj@
-all: erl_global_test@exe@
-
-erl_global_test@exe@: $(OBJS) $(LIBERL) $(LIBEI)
- $(LD) @CROSSLDFLAGS@ -o $@ $(OBJS) $(LIBFLAGS)
+all: ei_global_test@exe@
clean:
- $(RM) $(OBJS)
- $(RM) erl_global_test@exe@
+ $(RM) $(EI_GLOBAL_OBJS)
+ $(RM) ei_global_test@exe@
+
+ei_global_test@exe@: $(EI_GLOBAL_OBJS) $(LIBEI)
+ $(LD) @CROSSLDFLAGS@ -o $@ $(EI_GLOBAL_OBJS) \
+ ../all_SUITE_data/my_ussi@obj@ $(LIBFLAGS)
diff --git a/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c b/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c
new file mode 100644
index 0000000000..dd41afe77e
--- /dev/null
+++ b/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c
@@ -0,0 +1,240 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2000-2016. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Purpose: Tests the functions in erl_global.c.
+ *
+ * See the ei_global_SUITE.erl file for a "table of contents".
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ei_runner.h"
+#include "my_ussi.h"
+#include "ei_connect.h"
+
+static void cmd_ei_connect_init(char* buf, int len);
+static void cmd_ei_connect(char* buf, int len);
+static void cmd_ei_global_register(char* buf, int len);
+static void cmd_ei_global_whereis(char* buf, int len);
+static void cmd_ei_global_names(char* buf, int len);
+static void cmd_ei_global_unregister(char* buf, int len);
+static void cmd_ei_close_connection(char* buf, int len);
+
+static void send_errno_result(int value);
+
+ei_cnode ec;
+
+static struct {
+ char* name;
+ int num_args; /* Number of arguments. */
+ void (*func)(char* buf, int len);
+} commands[] = {
+ "ei_connect_init", 4, cmd_ei_connect_init,
+ "ei_connect", 1, cmd_ei_connect,
+ "ei_global_register", 2, cmd_ei_global_register,
+ "ei_global_whereis", 2, cmd_ei_global_whereis,
+ "ei_global_names", 1, cmd_ei_global_names,
+ "ei_global_unregister", 2, cmd_ei_global_unregister
+};
+
+
+/*
+ * Sends a list contaning all data types to the Erlang side.
+ */
+
+TESTCASE(interpret)
+{
+ ei_x_buff x;
+ int i;
+ ei_term term;
+
+ ei_init();
+
+ ei_x_new(&x);
+ while (get_bin_term(&x, &term) == 0) {
+ char* buf = x.buff, func[MAXATOMLEN];
+ int index = x.index, arity;
+ if (term.ei_type != ERL_SMALL_TUPLE_EXT || term.arity != 2)
+ fail("term should be a tuple of size 2");
+ if (ei_decode_atom(buf, &index, func) < 0)
+ fail("function name should be an atom");
+ if (ei_decode_tuple_header(buf, &index, &arity) != 0)
+ fail("function arguments should be a tuple");
+ for (i = 0; i < sizeof(commands)/sizeof(commands[0]); i++) {
+ if (strcmp(func, commands[i].name) == 0) {
+ if (arity != commands[i].num_args)
+ fail("wrong number of arguments");
+ commands[i].func(buf + index, x.buffsz - index);
+ break;
+ }
+ }
+ if (i >= sizeof(commands)/sizeof(commands[0])) {
+ message("\"%d\" \n", func);
+ fail("bad command");
+ }
+ }
+ report(1);
+ ei_x_free(&x);
+ return;
+}
+
+static void cmd_ei_connect_init(char* buf, int len)
+{
+ int index = 0, r = 0;
+ long l, creation;
+ char b[100];
+ char cookie[MAXATOMLEN], * cp = cookie;
+ char socket_impl[10];
+ int use_ussi;
+ ei_x_buff res;
+ if (ei_decode_long(buf, &index, &l) < 0)
+ fail("expected int");
+ sprintf(b, "c%ld", l);
+ if (ei_decode_atom(buf, &index, cookie) < 0)
+ fail("expected atom (cookie)");
+ if (cookie[0] == '\0')
+ cp = NULL;
+ if (ei_decode_long(buf, &index, &creation) < 0)
+ fail("expected int (creation)");
+ if (ei_decode_atom_as(buf, &index, socket_impl,
+ sizeof(socket_impl), ERLANG_ASCII, NULL, NULL) < 0)
+ fail("expected atom (socket_impl)");
+ if (strcmp(socket_impl, "default") == 0)
+ use_ussi = 0;
+ else if (strcmp(socket_impl, "ussi") == 0)
+ use_ussi = 1;
+ else
+ fail1("expected atom 'default' or 'ussi', got '%s'", socket_impl);
+
+ if (use_ussi)
+ r = ei_connect_init_ussi(&ec, b, cp, (short)creation,
+ &my_ussi, sizeof(my_ussi), NULL);
+ else
+ r = ei_connect_init(&ec, b, cp, (short)creation);
+ ei_x_new_with_version(&res);
+ ei_x_encode_long(&res, r);
+ send_bin_term(&res);
+ ei_x_free(&res);
+}
+
+static void cmd_ei_connect(char* buf, int len)
+{
+ int index = 0;
+ char node[256];
+ int i;
+ if (ei_decode_atom(buf, &index, node) < 0)
+ fail("expected atom");
+ i=ei_connect(&ec, node);
+ send_errno_result(i);
+}
+
+static void
+cmd_ei_global_register(char* buf, int len)
+{
+ int index = 0;
+ long fd;
+ char name[256];
+ if (ei_decode_long(buf, &index, &fd) < 0)
+ fail("expected long");
+ if (ei_decode_atom(buf, &index, name) < 0)
+ fail("expected atom");
+ send_errno_result(ei_global_register((int)fd, name, ei_self(&ec)));
+}
+
+static void
+cmd_ei_global_whereis(char* buf, int len)
+{
+ int index = 0;
+ long fd;
+ char name[512];
+ char node_name[512];
+ erlang_pid pid;
+ if (ei_decode_long(buf, &index, &fd) < 0)
+ fail("expected long");
+ if (ei_decode_atom(buf, &index, name) < 0)
+ fail("expected atom");
+
+ if (ei_global_whereis(&ec, fd, name, &pid, node_name) < 0)
+ fail("ei_global_whereis error code");
+
+ {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_pid(&x, &pid);
+ send_bin_term(&x);
+ ei_x_free(&x);
+ }
+}
+
+static void
+cmd_ei_global_names(char* buf, int len)
+{
+ int index = 0;
+ long fd;
+ char** names = NULL;
+ int count = 0, i;
+ if (ei_decode_long(buf, &index, &fd) < 0)
+ fail("expected long");
+
+ names = ei_global_names(&ec, (int)fd, &count);
+
+ {
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_tuple_header(&x, 2);
+ ei_x_encode_list_header(&x, count);
+ for(i=0; i<count; i++) {
+ ei_x_encode_string(&x, names[i]);
+ }
+ ei_x_encode_empty_list(&x);
+ ei_x_encode_long(&x, count);
+ send_bin_term(&x);
+ ei_x_free(&x);
+ }
+ free(names);
+}
+
+static void
+cmd_ei_global_unregister(char* buf, int len)
+{
+ int index = 0;
+ long fd;
+ char name[256];
+ if (ei_decode_long(buf, &index, &fd) < 0)
+ fail("expected long");
+ if (ei_decode_atom(buf, &index, name) < 0)
+ fail("expected atom");
+
+ send_errno_result(ei_global_unregister(&ec, (int)fd, name));
+}
+
+static void send_errno_result(int value)
+{
+ ei_x_buff x;
+ ei_x_new_with_version(&x);
+ ei_x_encode_tuple_header(&x, 2);
+ ei_x_encode_long(&x, value);
+ ei_x_encode_long(&x, erl_errno);
+ send_bin_term(&x);
+ ei_x_free(&x);
+}
diff --git a/lib/erl_interface/test/ei_print_SUITE.erl b/lib/erl_interface/test/ei_print_SUITE.erl
index 25dd95649d..43d74066a2 100644
--- a/lib/erl_interface/test/ei_print_SUITE.erl
+++ b/lib/erl_interface/test/ei_print_SUITE.erl
@@ -27,7 +27,8 @@
-export([all/0, suite/0,
init_per_testcase/2,
atoms/1, tuples/1, lists/1, strings/1,
- maps/1, funs/1, binaries/1, bitstrings/1]).
+ maps/1, funs/1, binaries/1, bitstrings/1,
+ integers/1]).
-import(runner, [get_term/1]).
@@ -38,7 +39,7 @@ suite() ->
[{ct_hooks,[ts_install_cth]}].
all() ->
- [atoms, tuples, lists, strings, maps, funs, binaries, bitstrings].
+ [atoms, tuples, lists, strings, maps, funs, binaries, bitstrings, integers].
init_per_testcase(Case, Config) ->
runner:init_per_testcase(?MODULE, Case, Config).
@@ -198,6 +199,69 @@ bitstrings(Config) ->
runner:recv_eot(P),
ok.
+integers(Config) ->
+ Port = runner:start(Config, ?integers),
+
+ test_integers(Port, -1000, 1000),
+ test_integers(Port, (1 bsl 27) - 1000, (1 bsl 27) + 1000),
+ test_integers(Port, -(1 bsl 27) - 1000, -(1 bsl 27) + 1000),
+ test_integers(Port, (1 bsl 28) - 1000, (1 bsl 28) + 1000),
+ test_integers(Port, -(1 bsl 28) - 1000, -(1 bsl 28) + 1000),
+ test_integers(Port, (1 bsl 31) - 1000, (1 bsl 31) + 1000),
+ test_integers(Port, -(1 bsl 31) - 1000, -(1 bsl 31) + 1000),
+ test_integers(Port, (1 bsl 32) - 1000, (1 bsl 32) + 1000),
+ test_integers(Port, -(1 bsl 32) - 1000, -(1 bsl 32) + 1000),
+ test_integers(Port, (1 bsl 60) - 1000, (1 bsl 60) + 1000),
+ test_integers(Port, -(1 bsl 60) - 1000, -(1 bsl 60) + 1000),
+ test_integers(Port, 16#feeddeaddeadbeef - 1000, 16#feeddeaddeadbeef + 1000),
+ test_integers(Port, -16#feeddeaddeadbeef - 1000, -16#feeddeaddeadbeef + 1000),
+ test_integers(Port, (1 bsl 64) - 1000, (1 bsl 64) + 1000),
+ test_integers(Port, 16#addfeeddeaddeadbeef - 1000, 16#addfeeddeaddeadbeef + 1000),
+ test_integers(Port, -16#addfeeddeaddeadbeef - 1000, -16#addfeeddeaddeadbeef + 1000),
+ test_integers(Port, -(1 bsl 64) - 1000, -(1 bsl 64) + 1000),
+ test_integers(Port, (1 bsl 8192) - 1000, (1 bsl 8192) + 1000),
+ test_integers(Port, -(1 bsl 8192) - 1000, -(1 bsl 8192) + 1000),
+
+ "done" = send_term_get_printed(Port, done),
+
+ runner:recv_eot(Port),
+
+ ok.
+
+test_integer(Port, Int, Print) when is_integer(Int) ->
+ Res = send_term_get_printed(Port, Int),
+ case Print of
+ true ->
+ io:format("Res: ~s~n", [Res]);
+ false ->
+ ok
+ end,
+ %% Large bignums are printed in base 16...
+ Exp = case Res of
+ "16#" ++ _ ->
+ "16#" ++ integer_to_list(Int, 16);
+ "-16#" ++ _ ->
+ "-16#" ++ integer_to_list(-1*Int, 16);
+ _ ->
+ integer_to_list(Int)
+ end,
+ case Exp =:= Res of
+ true ->
+ ok;
+ false ->
+ io:format("Exp: ~s~nRes: ~s~n", [Exp, Res]),
+ ct:fail({Exp, Res})
+ end.
+
+test_integers(Port, FromInt, ToInt) ->
+ test_integers(Port, FromInt, ToInt, true).
+
+test_integers(_Port, FromInt, ToInt, _Print) when FromInt > ToInt ->
+ ok;
+test_integers(Port, FromInt, ToInt, Print) ->
+ ok = test_integer(Port, FromInt, Print),
+ NewFromInt = FromInt + 1,
+ test_integers(Port, NewFromInt, ToInt, NewFromInt == ToInt).
send_term_get_printed(Port, Term) ->
diff --git a/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c b/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c
index 2922a05946..b840c4aca0 100644
--- a/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c
+++ b/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c
@@ -37,13 +37,9 @@ send_printed_buf(ei_x_buff* x)
FILE* f;
int n, index = 0, ver;
-#ifdef VXWORKS
- tmp = ".";
-#else
if (tmp == NULL) {
tmp = "/tmp";
}
-#endif
strcpy(fn, tmp);
strcat(fn, "/ei_print_test.txt");
f = fopen(fn, "w+");
@@ -349,3 +345,70 @@ TESTCASE(bitstrings)
}
report(1);
}
+
+TESTCASE(integers)
+{
+ char *buf;
+ long len;
+ int err, n, index, done;
+ ei_x_buff x;
+
+ ei_init();
+
+ done = 0;
+ do {
+ int type, type_len;
+ buf = read_packet(NULL);
+
+ index = 0;
+ err = ei_decode_version(buf, &index, NULL);
+ if (err != 0)
+ fail1("ei_decode_version returned %d", err);
+ err = ei_get_type(buf, &index, &type, &type_len);
+ if (err)
+ fail1("ei_get_type() returned %d", err);
+ switch (type) {
+ case ERL_SMALL_INTEGER_EXT:
+ case ERL_INTEGER_EXT: {
+ long val;
+ err = ei_decode_long(buf, &index, &val);
+ if (err)
+ fail1("ei_decode_long() returned %d", err);
+ break;
+ }
+ case ERL_SMALL_BIG_EXT:
+ case ERL_LARGE_BIG_EXT: {
+ erlang_big *big = ei_alloc_big(type_len);
+ if (!big)
+ fail1("ei_alloc_big() failed %d", ENOMEM);
+ err = ei_decode_big(buf, &index, big);
+ if (err)
+ fail1("ei_decode_big() failed %d", err);
+ ei_free_big(big);
+ break;
+ }
+ case ERL_ATOM_EXT: {
+ char abuf[MAXATOMLEN];
+ err = ei_decode_atom(buf, &index, &abuf[0]);
+ if (err)
+ fail1("ei_decode_atom() failed %d", err);
+ if (strcmp("done", &abuf[0]) == 0)
+ done = 1;
+ break;
+ }
+ default:
+ fail1("Unexpected type %d", type);
+ break;
+ }
+
+ ei_x_new(&x);
+ ei_x_append_buf(&x, buf, index);
+ send_printed_buf(&x);
+ ei_x_free(&x);
+
+ free_packet(buf);
+
+ } while (!done);
+
+ report(1);
+}
diff --git a/lib/erl_interface/test/ei_tmo_SUITE.erl b/lib/erl_interface/test/ei_tmo_SUITE.erl
index 5b9de80128..8d8776949c 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE.erl
+++ b/lib/erl_interface/test/ei_tmo_SUITE.erl
@@ -25,10 +25,12 @@
-include_lib("kernel/include/inet.hrl").
-include("ei_tmo_SUITE_data/ei_tmo_test_cases.hrl").
--export([all/0, suite/0,
+-export([all/0, groups/0, suite/0,
init_per_testcase/2, end_per_testcase/2,
- framework_check/1, ei_accept_tmo/1, ei_connect_tmo/1, ei_send_tmo/1,
- ei_connect_tmo/0,
+ framework_check/1, ei_accept_tmo/1, ei_connect_tmo/1,
+ ei_send_tmo/1,
+ ei_send_failure_tmo/1,
+ ei_connect_unreachable_tmo/0, ei_connect_unreachable_tmo/1,
ei_recv_tmo/1]).
suite() ->
@@ -36,19 +38,25 @@ suite() ->
{timetrap, {minutes, 1}}].
all() ->
- [framework_check, ei_accept_tmo, ei_connect_tmo,
- ei_send_tmo, ei_recv_tmo].
+ [framework_check,
+ ei_connect_unreachable_tmo,
+ ei_send_failure_tmo,
+ {group, default},
+ {group, ussi}].
+
+groups() ->
+ Members = [ei_recv_tmo,
+ ei_accept_tmo,
+ ei_connect_tmo,
+ ei_send_tmo],
+ [{default, [], Members},
+ {ussi, [], Members}].
+
+get_group(Config) ->
+ proplists:get_value(name, proplists:get_value(tc_group_properties,Config)).
init_per_testcase(Case, Config) ->
- Config1 = runner:init_per_testcase(?MODULE, Case, Config),
-
- % test if platform is vxworks_simso
- {_,Host} = split(node()),
- Bool = case atom_to_list(Host) of
- [$v,$x,$s,$i,$m | _] -> true;
- _ -> false
- end,
- [{vxsim,Bool} | Config1].
+ runner:init_per_testcase(?MODULE, Case, Config).
end_per_testcase(_Case, _Config) ->
ok.
@@ -76,7 +84,8 @@ do_one_recv(Config,CNode) ->
P1 = runner:start(Config, ?recv_tmo),
runner:send_term(P1,{CNode,
erlang:get_cookie(),
- node()}),
+ node(),
+ get_group(Config)}),
{term, X} = runner:get_term(P1, 10000),
true = is_integer(X),
CNode1 = join(CNode,Host),
@@ -89,24 +98,22 @@ do_one_recv_failure(Config,CNode) ->
P1 = runner:start(Config, ?recv_tmo),
runner:send_term(P1,{CNode,
erlang:get_cookie(),
- node()}),
+ node(),
+ get_group(Config)}),
{term, X} = runner:get_term(P1, 10000),
true = is_integer(X),
{term, {Ret,ETimedout,ETimedout}} = runner:get_term(P1, 10000),
true = (Ret < 0),
runner:recv_eot(P1).
+-define(EI_DIST_LOW, 5).
+-define(EI_DIST_HIGH, 6).
%% Check send with timeouts.
ei_send_tmo(Config) when is_list(Config) ->
- %dbg:tracer(),
- %dbg:p(self()),
- VxSim = proplists:get_value(vxsim, Config),
register(ei_send_tmo_1,self()),
do_one_send(Config,self(),c_node_send_tmo_1),
do_one_send(Config,ei_send_tmo_1,c_node_send_tmo_2),
- do_one_send_failure(Config,self(),cccc1,c_nod_send_tmo_3,VxSim),
- do_one_send_failure(Config,ei_send_tmo_1,cccc2,c_nod_send_tmo_4,VxSim),
ok.
@@ -115,7 +122,8 @@ do_one_send(Config,From,CNode) ->
P1 = runner:start(Config, ?send_tmo),
runner:send_term(P1,{CNode,
erlang:get_cookie(),
- node()}),
+ node(),
+ get_group(Config)}),
{term, X} = runner:get_term(P1, 10000),
true = is_integer(X),
CNode1 = join(CNode,Host),
@@ -130,7 +138,17 @@ do_one_send(Config,From,CNode) ->
{term, 0} = runner:get_term(P1, 10000),
runner:recv_eot(P1).
-do_one_send_failure(Config,From,FakeName,CName,VxSim) ->
+ei_send_failure_tmo(Config) when is_list(Config) ->
+ register(ei_send_tmo_1,self()),
+ [begin
+ io:format("Test dist version ~p\n", [Ver]),
+ do_one_send_failure(Config,self(),cccc1,c_nod_send_tmo_3, Ver),
+ do_one_send_failure(Config,ei_send_tmo_1,cccc2,c_nod_send_tmo_4, Ver)
+ end
+ || Ver <- lists:seq(?EI_DIST_LOW, ?EI_DIST_HIGH)],
+ ok.
+
+do_one_send_failure(Config,From,FakeName,CName, OurVer) ->
{_,Host} = split(node()),
OurName = join(FakeName,Host),
Node = join(CName,Host),
@@ -140,22 +158,23 @@ do_one_send_failure(Config,From,FakeName,CName,VxSim) ->
Else ->
exit(Else)
end,
- EpmdSocket = register(OurName, LSocket, 1, 5),
+ EpmdSocket = epmd_register(OurName, LSocket, OurVer),
P3 = runner:start(Config, ?send_tmo),
Cookie = kaksmula_som_ingen_bryr_sig_om,
runner:send_term(P3,{CName,
Cookie,
- OurName}),
+ OurName,
+ default}),
SocketB = case gen_tcp:accept(LSocket) of
{ok, Socket1} ->
Socket1;
Else2 ->
exit(Else2)
end,
- {hidden,Node,5} = recv_name(SocketB), % See 1)
+ {hidden,Node} = recv_name(SocketB, OurVer), % See 1)
send_status(SocketB, ok),
MyChallengeB = gen_challenge(),
- send_challenge(SocketB, OurName, MyChallengeB, 5),
+ send_challenge(SocketB, OurName, MyChallengeB, OurVer),
HisChallengeB = recv_challenge_reply(SocketB,
MyChallengeB,
Cookie),
@@ -178,53 +197,43 @@ do_one_send_failure(Config,From,FakeName,CName,VxSim) ->
%% must be large enough so there's time for the select() to time out and
%% the test program to return the error tuple (below).
- Res0 = if VxSim == false ->
- {term,{Res,ETO,Iters,ETO}} = runner:get_term(P3, 20000),
- Res;
- true -> % relax the test for vxsim
- case runner:get_term(P3, 20000) of
- {term,{Res,ETO,Iters,ETO}} ->
- Res;
- {term,{Res,_,Iters,_ETO}} -> % EIO?
- Res
- end
- end,
+ {term,{Res,ETO,Iters,ETO}} = runner:get_term(P3, 20000),
runner:recv_eot(P3),
- true = ((Res0 < 0) and (Iters > 0)),
+ true = ((Res < 0) and (Iters > 0)),
gen_tcp:close(SocketB),
gen_tcp:close(EpmdSocket),
ok.
%% Check accept with timeouts.
-ei_connect_tmo() -> [{require, test_host_not_reachable}].
+ei_connect_unreachable_tmo() -> [{require, test_host_not_reachable}].
-ei_connect_tmo(Config) when is_list(Config) ->
- %dbg:tracer(),
- %dbg:p(self()),
- VxSim = proplists:get_value(vxsim, Config),
+ei_connect_unreachable_tmo(Config) when is_list(Config) ->
DummyNode = make_and_check_dummy(),
P = runner:start(Config, ?connect_tmo),
runner:send_term(P,{c_nod_connect_tmo_1,
kaksmula_som_ingen_bryr_sig_om,
- DummyNode}),
- ETimedout =
- if VxSim == false ->
- {term,{-3,ETO,ETO}} = runner:get_term(P, 10000),
- ETO;
- true -> % relax the test for vxsim
- case runner:get_term(P, 10000) of
- {term,{-3,ETO,ETO}} ->
- ETO;
- {term,{-1,_,ETO}} -> % EHOSTUNREACH = ok
- ETO
- end
- end,
+ DummyNode,
+ default}),
+ {term,{-3,ETimedout,ETimedout}} = runner:get_term(P, 10000),
runner:recv_eot(P),
+ ok.
+
+ei_connect_tmo(Config) when is_list(Config) ->
+ [begin
+ io:format("Test dist version ~p published as ~p\n", [OurVer,OurEpmdVer]),
+ do_ei_connect_tmo(Config, OurVer, OurEpmdVer)
+ end
+ || OurVer <- lists:seq(?EI_DIST_LOW, ?EI_DIST_HIGH),
+ OurEpmdVer <- lists:seq(?EI_DIST_LOW, ?EI_DIST_HIGH),
+ OurVer >= OurEpmdVer].
+
+do_ei_connect_tmo(Config, OurVer, OurEpmdVer) ->
P2 = runner:start(Config, ?connect_tmo),
runner:send_term(P2,{c_nod_connect_tmo_2,
erlang:get_cookie(),
- node()}),
+ node(),
+ get_group(Config)}),
{term, X} = runner:get_term(P2, 10000),
runner:recv_eot(P2),
true = is_integer(X),
@@ -238,22 +247,24 @@ ei_connect_tmo(Config) when is_list(Config) ->
Else ->
exit(Else)
end,
- EpmdSocket = register(OurName, LSocket, 1, 5),
+ EpmdSocket = epmd_register(OurName, LSocket, OurEpmdVer),
P3 = runner:start(Config, ?connect_tmo),
Cookie = kaksmula_som_ingen_bryr_sig_om,
runner:send_term(P3,{c_nod_connect_tmo_3,
Cookie,
- OurName}),
+ OurName,
+ get_group(Config)}),
SocketB = case gen_tcp:accept(LSocket) of
{ok, Socket1} ->
Socket1;
Else2 ->
exit(Else2)
end,
- {hidden,Node,5} = recv_name(SocketB), % See 1)
+ {hidden,Node} = recv_name(SocketB, OurEpmdVer), % See 1)
send_status(SocketB, ok),
MyChallengeB = gen_challenge(),
- send_challenge(SocketB, OurName, MyChallengeB, 5),
+ send_challenge(SocketB, OurName, MyChallengeB, OurVer),
+ recv_complement(SocketB, OurVer, OurEpmdVer),
_HisChallengeB = recv_challenge_reply(SocketB,
MyChallengeB,
Cookie),
@@ -266,16 +277,27 @@ ei_connect_tmo(Config) when is_list(Config) ->
%% Check accept with timeouts.
ei_accept_tmo(Config) when is_list(Config) ->
- %%dbg:tracer(),
- %%dbg:p(self()),
+ [begin
+ io:format("Test our dist ver=~p and assumed ver=~p\n",
+ [OurVer, AssumedVer]),
+ do_ei_accept_tmo(Config, OurVer, AssumedVer)
+ end
+ || OurVer <- lists:seq(?EI_DIST_LOW, ?EI_DIST_HIGH),
+ AssumedVer <- lists:seq(?EI_DIST_LOW, ?EI_DIST_HIGH),
+ OurVer >= AssumedVer],
+ ok.
+
+do_ei_accept_tmo(Config, OurVer, AssumedVer) ->
P = runner:start(Config, ?accept_tmo),
runner:send_term(P,{c_nod_som_ingen_kontaktar_1,
- kaksmula_som_ingen_bryr_sig_om}),
+ kaksmula_som_ingen_bryr_sig_om,
+ get_group(Config)}),
{term,{-1,ETimedout,ETimedout}} = runner:get_term(P, 10000),
runner:recv_eot(P),
P2 = runner:start(Config, ?accept_tmo),
runner:send_term(P2,{c_nod_som_vi_kontaktar_1,
- erlang:get_cookie()}),
+ erlang:get_cookie(),
+ get_group(Config)}),
receive after 1000 -> ok end,
CNode1 = make_node(c_nod_som_vi_kontaktar_1),
{ignored,CNode1} ! tjenare,
@@ -284,19 +306,20 @@ ei_accept_tmo(Config) when is_list(Config) ->
true = is_integer(X),
P3 = runner:start(Config, ?accept_tmo),
runner:send_term(P3,{c_nod_som_vi_kontaktar_2,
- erlang:get_cookie()}),
+ erlang:get_cookie(),
+ get_group(Config)}),
receive after 1000 -> ok end,
CNode2 = make_node(c_nod_som_vi_kontaktar_2),
{NA,NB} = split(CNode2),
{_,Host} = split(node()),
OurName = join(ccc,Host),
- {port,PortNo,_} = erl_epmd:port_please(NA,NB),
+ {port,PortNo,?EI_DIST_HIGH} = erl_epmd:port_please(NA,NB),
{ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
[{active,false},
{packet,2}]),
- send_name(SocketA,OurName,5),
+ send_name(SocketA,OurName,OurVer,AssumedVer),
ok = recv_status(SocketA),
- {hidden,_Node,5,HisChallengeA} = recv_challenge(SocketA), % See 1)
+ {hidden,_Node,HisChallengeA} = recv_challenge(SocketA,OurVer), % See 1)
_OurChallengeA = gen_challenge(),
_OurDigestA = gen_digest(HisChallengeA, erlang:get_cookie()),
%% Dont do the last two steps of the connection setup...
@@ -342,6 +365,7 @@ make_and_check_dummy() ->
-define(DFLAG_EXTENDED_PIDS_PORTS,16#100).
-define(DFLAG_NEW_FLOATS,16#800).
-define(DFLAG_DIST_MONITOR,8).
+-define(DFLAG_HANDSHAKE_23,16#1000000).
%% From R9 and forward extended references is compulsory
%% From 14 and forward new float is compulsory
@@ -405,31 +429,61 @@ recv_status(Socket) ->
exit(Bad)
end.
-send_challenge(Socket, Node, Challenge, Version) ->
- send_challenge(Socket, Node, Challenge, Version, ?COMPULSORY_DFLAGS).
-send_challenge(Socket, Node, Challenge, Version, Flags) ->
- {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
- ?to_port(Socket, [$n,?int16(Version),?int32(Flags),
- ?int32(Challenge), atom_to_list(Node)]).
-
-recv_challenge(Socket) ->
+send_challenge(Socket, Node, Challenge, OurVer) ->
+ send_challenge(Socket, Node, Challenge, OurVer, ?COMPULSORY_DFLAGS).
+
+send_challenge(Socket, Node, Challenge, OurVer, Flags) ->
+ if OurVer =:= 5 ->
+ ?to_port(Socket, [$n, ?int16(OurVer), ?int32(Flags),
+ ?int32(Challenge), atom_to_list(Node)]);
+ OurVer >= 6 ->
+ NodeName = atom_to_binary(Node, latin1),
+ NameLen = byte_size(NodeName),
+ Creation = erts_internal:get_creation(),
+ ?to_port(Socket, [$N,
+ <<(Flags bor ?DFLAG_HANDSHAKE_23):64,
+ Challenge:32,
+ Creation:32,
+ NameLen:16>>,
+ NodeName])
+ end.
+
+recv_challenge(Socket, OurVer) ->
case gen_tcp:recv(Socket, 0) of
{ok,[$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
+ 5 = OurVer,
Flags = ?u32(Fl1,Fl2,Fl3,Fl4),
- Type = case Flags band ?DFLAG_PUBLISHED of
- 0 ->
- hidden;
- _ ->
- normal
- end,
+ Type = flags_to_type(Flags),
Node =list_to_atom(Ns),
- Version = ?u16(V1,V0),
+ OurVer = ?u16(V1,V0), % echoed back
+ Challenge = ?u32(CA3,CA2,CA1,CA0),
+ {Type,Node,Challenge};
+
+ {ok,[$N, F7,F6,F5,F4,F3,F2,F1,F0, CA3,CA2,CA1,CA0,
+ _Cr3,_Cr2,_Cr1,_Cr0, NL1,NL0 | Rest]} ->
+ true = (OurVer >= 6),
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ Type = flags_to_type(Flags),
+ NameLen = ?u16(NL1,NL0),
+ {NodeName,_} = lists:split(NameLen, Rest),
+ Node = list_to_atom(NodeName),
Challenge = ?u32(CA3,CA2,CA1,CA0),
- {Type,Node,Version,Challenge};
+ %%Creation = ?u32(Cr3,Cr2,Cr1,Cr0),
+ %%true = (Creation =/= 0),
+ {Type,Node,Challenge};
+
_ ->
?shutdown(no_node)
end.
+flags_to_type(Flags) ->
+ case Flags band ?DFLAG_PUBLISHED of
+ 0 ->
+ hidden;
+ _ ->
+ normal
+ end.
+
%send_challenge_reply(Socket, Challenge, Digest) ->
% ?to_port(Socket, [$r,?int32(Challenge),Digest]).
@@ -443,8 +497,8 @@ recv_challenge_reply(Socket, ChallengeA, Cookie) ->
true ->
?shutdown(bad_challenge_reply)
end;
- _ ->
- ?shutdown(no_node)
+ Other ->
+ ?shutdown({recv_challenge_reply,Other})
end.
send_challenge_ack(Socket, Digest) ->
@@ -463,37 +517,53 @@ send_challenge_ack(Socket, Digest) ->
% ?shutdown(bad_challenge_ack)
% end.
-send_name(Socket, MyNode0, Version) ->
- send_name(Socket, MyNode0, Version, ?COMPULSORY_DFLAGS).
-send_name(Socket, MyNode0, Version, Flags) ->
- MyNode = atom_to_list(MyNode0),
- ?to_port(Socket, [$n,?int16(Version),?int32(Flags)] ++
- MyNode).
+send_name(Socket, MyNode, OurVer, AssumedVer) ->
+ Flags = ?COMPULSORY_DFLAGS bor (case OurVer of
+ 5 -> 0;
+ 6 -> ?DFLAG_HANDSHAKE_23
+ end),
+ send_name(Socket, MyNode, OurVer, AssumedVer, Flags).
+
+send_name(Socket, MyNode, OurVer, AssumedVer, Flags) ->
+ NodeName = atom_to_binary(MyNode, latin1),
+ if AssumedVer =:= 5 ->
+ ?to_port(Socket, [$n,?int16(OurVer),?int32(Flags),NodeName]);
+ AssumedVer >= 6 ->
+ Creation = erts_internal:get_creation(),
+ ?to_port(Socket, [$N,
+ <<Flags:64,
+ Creation:32,
+ (byte_size(NodeName)):16>>,
+ NodeName])
+ end.
-%%
-%% recv_name is common for both old and new handshake.
-%%
-recv_name(Socket) ->
+recv_name(Socket, OurEpmdVer) ->
case gen_tcp:recv(Socket, 0) of
- {ok,Data} ->
- get_name(Data);
+ {ok,[$n, V1,V0, F3,F2,F1,F0 | OtherNode]} ->
+ 5 = OurEpmdVer,
+ 5 = ?u16(V1,V0),
+ Type = flags_to_type(?u32(F3,F2,F1,F0)),
+ {Type, list_to_atom(OtherNode)};
+ {ok,[$N, F7,F6,F5,F4,F3,F2,F1,F0, _Cr3,_Cr2,_Cr1,_Cr0, NL1, NL0 | Rest]} ->
+ true = (OurEpmdVer >= 6),
+ {OtherNode, _Residue} = lists:split(?u16(NL1,NL0), Rest),
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ Type = flags_to_type(Flags),
+ {Type, list_to_atom(OtherNode)};
Res ->
?shutdown({no_node,Res})
end.
-get_name([$m,VersionA,VersionB,_Ip1,_Ip2,_Ip3,_Ip4|OtherNode]) ->
- {normal, list_to_atom(OtherNode), ?u16(VersionA,VersionB)};
-get_name([$h,VersionA,VersionB,_Ip1,_Ip2,_Ip3,_Ip4|OtherNode]) ->
- {hidden, list_to_atom(OtherNode), ?u16(VersionA,VersionB)};
-get_name([$n,VersionA, VersionB, Flag1, Flag2, Flag3, Flag4 | OtherNode]) ->
- Type = case ?u32(Flag1, Flag2, Flag3, Flag4) band ?DFLAG_PUBLISHED of
- 0 -> hidden;
- _ -> normal
- end,
- {Type, list_to_atom(OtherNode),
- ?u16(VersionA,VersionB)};
-get_name(Data) ->
- ?shutdown(Data).
+recv_complement(Socket, OurVer, 5) when OurVer > 5 ->
+ case gen_tcp:recv(Socket, 0) of
+ {ok,[$c, _F7,_F6,_F5,_F4, _Cr3,_Cr2,_Cr1,_Cr0]} ->
+ ok;
+ Res ->
+ ?shutdown({no_node,Res})
+ end;
+recv_complement(_, _OurVer, _OurEpmdVer) ->
+ ok.
+
%%
%% tell_name is for old handshake
@@ -538,13 +608,10 @@ wait_for_reg_reply(Socket, SoFar) ->
receive
{tcp, Socket, Data0} ->
case SoFar ++ Data0 of
- [$y, Result, A, B] ->
- case Result of
- 0 ->
- {alive, Socket, ?u16(A, B)};
- _ ->
- {error, duplicate_name}
- end;
+ [$y, 0, Cr1,Cr0] ->
+ {alive, Socket, ?u16(Cr1,Cr0)};
+ [$v, 0, Cr3,Cr2,Cr1,Cr0] ->
+ {alive, Socket, ?u32(Cr3,Cr2,Cr1,Cr0)};
Data when length(Data) < 4 ->
wait_for_reg_reply(Socket, Data);
Garbage ->
@@ -558,9 +625,9 @@ wait_for_reg_reply(Socket, SoFar) ->
end.
-register(NodeName, ListenSocket, VLow, VHigh) ->
+epmd_register(NodeName, ListenSocket, OurVer) ->
{ok,{_,TcpPort}} = inet:sockname(ListenSocket),
- case do_register_node(NodeName, TcpPort, VLow, VHigh) of
+ case do_register_node(NodeName, TcpPort, ?EI_DIST_LOW, OurVer) of
{alive, Socket, _Creation} ->
Socket;
Other ->
diff --git a/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src
index b3f6156ff7..c58f128572 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src
@@ -25,7 +25,8 @@ CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
LD = @LD@
LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
- $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
+ ../all_SUITE_data/my_ussi@obj@ \
+ $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
EI_TMO_OBJS = ei_tmo_test@obj@ ei_tmo_test_decl@obj@
diff --git a/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c b/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
index dc04cf88a8..0fd3e1d697 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
+++ b/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
@@ -21,9 +21,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#ifdef VXWORKS
-#include "reclaim.h"
-#endif
#ifdef __WIN32__
#include <winsock2.h>
@@ -32,9 +29,11 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
+#include <unistd.h>
#endif
#include "ei_runner.h"
+#include "my_ussi.h"
#ifndef __WIN32__
#define closesocket(X) close(X)
@@ -67,7 +66,7 @@ static void debugf_open(int number)
{
char filename[1024];
sprintf(filename,"ei_tmo_test%d.debug",number);
-#if !defined(VXWORKS) && !defined(__WIN32__)
+#if !defined(__WIN32__)
close(2);
#endif
debugfile = fopen(filename,"a");
@@ -86,6 +85,7 @@ static void debugf_close(void)
#define DEBUGF(X) /* noop */
#endif
+
TESTCASE(framework_check)
{
char *ptr = NULL;
@@ -128,19 +128,26 @@ TESTCASE(framework_check)
report(1);
}
-int decode_request(char **nodename_p, char **cookie_p, char **peername_p)
+int decode_request(char **nodename_p, char **cookie_p, char **peername_p,
+ int *use_ussi_p)
{
char *nodename = NULL;
char *cookie = NULL;
char *peername = NULL;
+ char socket_impl[10];
char *ptr = NULL;
ei_x_buff x;
int len;
int version;
int type;
int size;
- int expected_size = (peername_p == NULL) ? 2 : 3;
+ int expected_size = 2;
int ret = -1;
+
+ if (peername_p)
+ ++expected_size;
+ if (use_ussi_p)
+ ++expected_size;
ptr = read_packet(&len);
ei_x_new(&x);
@@ -180,7 +187,6 @@ int decode_request(char **nodename_p, char **cookie_p, char **peername_p)
}
nodename = malloc(size+1);
ei_decode_atom(x.buff,&len,nodename);
- nodename[size] = '\0'; /* needed????? */
if (ei_get_type(x.buff,&len,&type,&size) != 0) {
DEBUGF(("Failure at line %d\n",__LINE__));
goto cleanup;
@@ -191,8 +197,7 @@ int decode_request(char **nodename_p, char **cookie_p, char **peername_p)
}
cookie = malloc(size + 1);
ei_decode_atom(x.buff,&len,cookie);
- cookie[size] = '\0'; /* needed????? */
- if (expected_size > 2) {
+ if (peername_p) {
if (ei_get_type(x.buff,&len,&type,&size) != 0) {
DEBUGF(("Failure at line %d\n",__LINE__));
goto cleanup;
@@ -212,6 +217,22 @@ int decode_request(char **nodename_p, char **cookie_p, char **peername_p)
DEBUGF(("nodename = %s, cookie = %s\n",
nodename, cookie));
}
+
+ if (use_ussi_p) {
+ if (ei_decode_atom_as(x.buff,&len,socket_impl,sizeof(socket_impl),
+ ERLANG_ASCII, NULL, NULL)) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ if (strcmp(socket_impl,"default") == 0)
+ *use_ussi_p = 0;
+ else if (strcmp(socket_impl,"ussi") == 0)
+ *use_ussi_p = 1;
+ else {
+ DEBUGF(("Unkown socket_impl '%s' at %d\n",socket_impl,__LINE__));
+ goto cleanup;
+ }
+ }
*nodename_p = nodename;
nodename = NULL;
*cookie_p = cookie;
@@ -339,6 +360,7 @@ TESTCASE(recv_tmo)
char *nodename = NULL;
char *cookie = NULL;
char *peername = NULL;
+ int use_ussi;
int com_sock = -1;
ei_cnode nodeinfo;
@@ -346,12 +368,22 @@ TESTCASE(recv_tmo)
OPEN_DEBUGFILE(5);
- if (decode_request(&nodename,&cookie,&peername) != 0) {
+ if (decode_request(&nodename,&cookie,&peername,&use_ussi) != 0) {
goto cleanup;
}
- if (ei_connect_init(&nodeinfo, nodename, cookie, 0) < 0) {
- DEBUGF(("Failure at line %d\n",__LINE__));
- goto cleanup;
+ if (use_ussi) {
+ my_ussi_init();
+ if (ei_connect_init_ussi(&nodeinfo, nodename, cookie, 0,
+ &my_ussi, sizeof(my_ussi), NULL) < 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ }
+ else {
+ if (ei_connect_init(&nodeinfo, nodename, cookie, 0) < 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
}
if ((com_sock = ei_connect_tmo(&nodeinfo, peername, 5000)) < 0) {
@@ -451,18 +483,29 @@ TESTCASE(send_tmo)
char *cookie = NULL;
char *peername = NULL;
int com_sock = -1;
+ int use_ussi;
ei_cnode nodeinfo;
ei_init();
OPEN_DEBUGFILE(4);
- if (decode_request(&nodename,&cookie,&peername) != 0) {
+ if (decode_request(&nodename,&cookie,&peername,&use_ussi) != 0) {
goto cleanup;
}
- if (ei_connect_init(&nodeinfo, nodename, cookie, 0) < 0) {
- DEBUGF(("Failure at line %d\n",__LINE__));
- goto cleanup;
+ if (use_ussi) {
+ my_ussi_init();
+ if (ei_connect_init_ussi(&nodeinfo, nodename, cookie, 0,
+ &my_ussi, sizeof(my_ussi), NULL) < 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ }
+ else {
+ if (ei_connect_init(&nodeinfo, nodename, cookie, 0) < 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
}
if ((com_sock = ei_connect_tmo(&nodeinfo, peername, 5000)) < 0) {
@@ -593,18 +636,29 @@ TESTCASE(connect_tmo)
char *cookie = NULL;
char *peername = NULL;
int com_sock = -1;
+ int use_ussi;
ei_cnode nodeinfo;
ei_init();
OPEN_DEBUGFILE(3);
- if (decode_request(&nodename,&cookie,&peername) != 0) {
+ if (decode_request(&nodename,&cookie,&peername,&use_ussi) != 0) {
goto cleanup;
}
- if (ei_connect_init(&nodeinfo, nodename, cookie, 0) < 0) {
- DEBUGF(("Failure at line %d\n",__LINE__));
- goto cleanup;
+ if (use_ussi) {
+ my_ussi_init();
+ if (ei_connect_init_ussi(&nodeinfo, nodename, cookie, 0,
+ &my_ussi, sizeof(my_ussi), NULL) < 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ }
+ else {
+ if (ei_connect_init(&nodeinfo, nodename, cookie, 0) < 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
}
if ((com_sock = ei_connect_tmo(&nodeinfo, peername, 5000)) < 0) {
@@ -679,10 +733,12 @@ TESTCASE(accept_tmo)
int listen_sock = -1;
int epmd_sock = -1;
int com_sock = -1;
+ int use_ussi;
struct sockaddr_in sin;
int sin_siz = sizeof(sin);
ErlConnect peer;
ei_cnode nodeinfo;
+ int port_no;
ei_init();
@@ -690,38 +746,55 @@ TESTCASE(accept_tmo)
putenv("EI_TRACELEVEL=10");
- if (decode_request(&nodename,&cookie,NULL) != 0) {
- goto cleanup;
- }
- if (ei_connect_init(&nodeinfo, nodename, cookie, 0) < 0) {
- DEBUGF(("Failure at line %d\n",__LINE__));
+ if (decode_request(&nodename,&cookie,NULL,&use_ussi) != 0) {
goto cleanup;
}
+ if (use_ussi) {
+ my_ussi_init();
+ if (ei_connect_init_ussi(&nodeinfo, nodename, cookie, 0,
+ &my_ussi, sizeof(my_ussi), NULL) < 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ port_no = 0;
+ listen_sock = ei_listen(&nodeinfo, &port_no, 5);
+ if (listen_sock == ERL_ERROR) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ }
+ else {
+ if (ei_connect_init(&nodeinfo, nodename, cookie, 0) < 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
- if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
- DEBUGF(("Failure at line %d\n",__LINE__));
- goto cleanup;
- }
- memset(&sin, 0, sizeof(sin));
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = INADDR_ANY;
-
- if (bind(listen_sock,(struct sockaddr *) &sin, sizeof(sin)) != 0) {
- DEBUGF(("Failure at line %d\n",__LINE__));
- goto cleanup;
- }
- if (getsockname(listen_sock,
- (struct sockaddr *) &sin, &sin_siz) != 0) {
- DEBUGF(("Failure at line %d\n",__LINE__));
- goto cleanup;
- }
- if (listen(listen_sock, 5) != 0) {
- DEBUGF(("Failure at line %d\n",__LINE__));
- goto cleanup;
- }
-
- if ((epmd_sock = ei_publish(&nodeinfo, ntohs(sin.sin_port))) < 0) {
- DEBUGF(("Failure at line %d[%d,%d]\n",__LINE__,sin.sin_port,erl_errno));
+ if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ if (bind(listen_sock,(struct sockaddr *) &sin, sizeof(sin)) != 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ if (getsockname(listen_sock,
+ (struct sockaddr *) &sin, &sin_siz) != 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ if (listen(listen_sock, 5) != 0) {
+ DEBUGF(("Failure at line %d\n",__LINE__));
+ goto cleanup;
+ }
+ port_no = ntohs(sin.sin_port);
+ }
+
+ if ((epmd_sock = ei_publish(&nodeinfo, port_no)) < 0) {
+ DEBUGF(("Failure at line %d[%d,%d]\n",__LINE__,port_no,erl_errno));
goto cleanup;
}
diff --git a/lib/erl_interface/test/erl_call_SUITE.erl b/lib/erl_interface/test/erl_call_SUITE.erl
index 9e2b2e4251..9cfc2ac25c 100644
--- a/lib/erl_interface/test/erl_call_SUITE.erl
+++ b/lib/erl_interface/test/erl_call_SUITE.erl
@@ -23,42 +23,201 @@
-include_lib("common_test/include/ct.hrl").
--export([all/0, smoke/1]).
+-export([all/0, smoke/1,
+ random_cnode_name/1,
+ test_connect_to_host_port/1,
+ unresolvable_hostname/1,
+ timeout/1]).
-all() ->
- [smoke].
+all() ->
+ [smoke,
+ random_cnode_name,
+ test_connect_to_host_port,
+ unresolvable_hostname,
+ timeout].
smoke(Config) when is_list(Config) ->
- ErlCall = find_erl_call(),
- NameSwitch = case net_kernel:longnames() of
- true ->
- "-name";
- false ->
- "-sname"
- end,
Name = atom_to_list(?MODULE)
++ "-"
++ integer_to_list(erlang:system_time(microsecond)),
- ArgsList = ["-s", "-a", "erlang node", NameSwitch, Name],
- io:format("erl_call: \"~ts\"\n~nargs list: ~p~n", [ErlCall, ArgsList]),
- CmdRes = get_smoke_port_res(open_port({spawn_executable, ErlCall},
- [{args, ArgsList}, eof]), []),
- io:format("CmdRes: ~p~n", [CmdRes]),
+ RetNodeName = start_node_and_get_node_name(Name),
+
+ halt_node(Name),
[_, Hostname] = string:lexemes(atom_to_list(node()), "@"),
NodeName = list_to_atom(Name ++ "@" ++ Hostname),
- io:format("NodeName: ~p~n~n", [NodeName]),
+ NodeName = list_to_atom(RetNodeName),
+ ok.
+
+
+random_cnode_name(Config) when is_list(Config) ->
+ Name = atom_to_list(?MODULE)
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(microsecond)),
+
+ try
+ CNodeName = start_node_and_get_c_node_name(Name, []),
+ [_, Hostname] = string:lexemes(atom_to_list(node()), "@"),
+ DefaultName = list_to_atom("c17@" ++ Hostname),
+ check_eq(CNodeName, DefaultName),
+
+ CNodeName_r = start_node_and_get_c_node_name(Name, ["-r"]),
+ [CNode_r, Hostname] = string:lexemes(atom_to_list(CNodeName_r), "@"),
+ check_regex(CNode_r, "^c[0-9]+$"),
+
+ CNodeName_R = start_node_and_get_c_node_name(Name, ["-R"]),
+ [CNode_R, Hostname] = string:lexemes(atom_to_list(CNodeName_R), "@"),
+ check_regex(CNode_R, "^[0-9A-Z]+$"),
+
+ %% we should get the same recycled node name again
+ CNodeName_R2 = start_node_and_get_c_node_name(Name, ["-R"]),
+ check_eq(CNodeName_R, CNodeName_R2),
+
+ %% Check that it works with static ports using address
+ {ok,Nodes} = erl_epmd:names('localhost'),
+ Port = proplists:get_value(nodename(),Nodes),
+ MyName = get_node_name_from_self(
+ ["-R","-address","localhost:"++integer_to_list(Port)]),
+ check_eq(MyName, node())
+
+ after
+ halt_node(Name)
+ end,
+ ok.
+
+nodename() ->
+ hd(string:split(atom_to_list(node()), "@")).
+
+check_eq(X,Y) ->
+ {Y,X} = {X,Y}.
+
+check_regex(String, Regex) ->
+ {ok, RE} = re:compile(Regex),
+ {{match,[{0,_}]}, _} = {re:run(String, RE), String},
+ true.
+
+test_connect_to_host_port(Config) when is_list(Config) ->
+ Name = atom_to_list(?MODULE)
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(microsecond)),
+ try
+ test_connect_to_host_port_do(Name)
+ after
+ halt_node(Name)
+ end,
+ ok.
+
+
+test_connect_to_host_port_do(Name) ->
+ Port = start_node_and_get_port(Name),
+ AddressCaller =
+ fun(Address) ->
+ get_erl_call_result(["-address",
+ Address,
+ "-a",
+ "erlang length [[1,2,3,4,5,6,7,8,9]]"])
+ end,
+ "9" = AddressCaller(erlang:integer_to_list(Port)),
+ "9" = AddressCaller(":" ++ erlang:integer_to_list(Port)),
+ [_, Hostname] = string:lexemes(atom_to_list(node()), "@"),
+ "9" = AddressCaller(Hostname ++ ":" ++ erlang:integer_to_list(Port)),
+ FailedRes = AddressCaller("80"),
+ case string:find(FailedRes, "80") of
+ nomatch -> ct:fail("Incorrect error message");
+ _ -> ok
+ end,
+ ok.
+
+%% OTP-16604: Tests that erl_call works even when the local hostname cannot be
+%% resolved.
+unresolvable_hostname(_Config) ->
+ Name = atom_to_list(?MODULE)
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(microsecond)),
+ Opt = "-__uh_test__",
+
+ try
+ CNodeName = start_node_and_get_c_node_name(Name, [Opt]),
+ [_, Hostname] = string:lexemes(atom_to_list(node()), "@"),
+ DefaultName = list_to_atom("c17@" ++ Hostname),
+ check_eq(CNodeName, DefaultName)
+ after
+ halt_node(Name)
+ end,
+
+ ok.
+
+%% OTP-16604: Test the -timeout option
+timeout(_Config) ->
+ Name = atom_to_list(?MODULE)
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(microsecond)),
+ Opts = ["-timeout", "3"],
+
+ try
+ [] = start_node_and_apply(Name, "timer sleep [10000]", Opts)
+ after
+ halt_node(Name)
+ end,
- pong = net_adm:ping(NodeName),
- rpc:cast(NodeName, erlang, halt, []),
- NodeName = list_to_atom(string:trim(CmdRes, both, "'")),
ok.
%
% Utility functions...
%
+
+halt_node(Name) ->
+ [_, Hostname] = string:lexemes(atom_to_list(node()), "@"),
+ NodeName = list_to_atom(Name ++ "@" ++ Hostname),
+ io:format("NodeName: ~p~n~n", [NodeName]),
+
+ pong = net_adm:ping(NodeName),
+ rpc:cast(NodeName, erlang, halt, []).
+
+
+start_node_and_get_node_name(Name) ->
+ string:trim(start_node_and_apply(Name, "erlang node", []),
+ both,
+ "'").
+
+start_node_and_get_c_node_name(Name, Opts) ->
+ Str = start_node_and_apply(Name, "erlang nodes [hidden]", Opts),
+ {ok, [{'[',_}, {atom, _, CNode}, {']',_}], _} = erl_scan:string(Str),
+ CNode.
+
+start_node_and_apply(Name, MfaStr, Opts) ->
+ NameSwitch = case net_kernel:longnames() of
+ true ->
+ "-name";
+ false ->
+ "-sname"
+ end,
+ get_erl_call_result(Opts ++
+ ["-s",
+ NameSwitch,
+ Name, "-a",
+ MfaStr]).
+
+start_node_and_get_port(Name) ->
+ start_node_and_get_node_name(Name),
+ {ok, NamePortList} = net_adm:names(),
+ {value, {_, Port}}
+ = lists:search(fun({N, _}) ->
+ string:equal(N, Name)
+ end,
+ NamePortList),
+ Port.
+
+get_node_name_from_self(Opts) ->
+ Str = apply_on_self("erlang node []", Opts),
+ {ok, [{atom, _, Node}], _} = erl_scan:string(Str),
+ Node.
+
+apply_on_self(MfaStr, Opts) ->
+ get_erl_call_result(Opts ++ ["-a",MfaStr]).
+
find_erl_call() ->
ErlCallName = case os:type() of
{win32, _} -> "erl_call.exe";
@@ -86,10 +245,19 @@ find_erl_call() ->
ErlCall
end.
-get_smoke_port_res(Port, Acc) when is_port(Port) ->
+
+get_erl_call_result(ArgsList) ->
+ ErlCall = find_erl_call(),
+ io:format("erl_call: \"~ts\"\n~nargs list: ~p~n", [ErlCall, ArgsList]),
+ CmdRes = get_port_res(open_port({spawn_executable, ErlCall},
+ [{args, ArgsList}, eof, stderr_to_stdout]), []),
+ io:format("CmdRes: ~p~n", [CmdRes]),
+ CmdRes.
+
+get_port_res(Port, Acc) when is_port(Port) ->
receive
{Port, {data, Data}} ->
- get_smoke_port_res(Port, [Acc|Data]);
+ get_port_res(Port, [Acc|Data]);
{Port, eof} ->
lists:flatten(Acc)
end.
diff --git a/lib/erl_interface/test/erl_connect_SUITE.erl b/lib/erl_interface/test/erl_connect_SUITE.erl
deleted file mode 100644
index 782691b8fb..0000000000
--- a/lib/erl_interface/test/erl_connect_SUITE.erl
+++ /dev/null
@@ -1,131 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2018. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
--module(erl_connect_SUITE).
-
--include_lib("common_test/include/ct.hrl").
--include("erl_connect_SUITE_data/erl_connect_test_cases.hrl").
-
--export([all/0, suite/0,
- init_per_testcase/2,
- erl_send/1, erl_reg_send/1,
- erl_send_cookie_file/1]).
-
--import(runner, [get_term/1,send_term/2]).
-
-suite() ->
- [{ct_hooks,[ts_install_cth]},
- {timetrap, {seconds, 30}}].
-
-all() ->
- [erl_send, erl_reg_send, erl_send_cookie_file].
-
-
-init_per_testcase(Case, Config) ->
- runner:init_per_testcase(?MODULE, Case, Config).
-
-erl_send(Config) when is_list(Config) ->
- P = runner:start(Config, ?interpret),
- 1 = erl_connect_init(P, 42, erlang:get_cookie(), 0),
- {ok,Fd} = erl_connect(P, node()),
-
- ok = erl_send(P, Fd, self(), AMsg={a,message}),
- receive AMsg -> ok end,
-
- 0 = erl_close_connection(P,Fd),
- runner:send_eot(P),
- runner:recv_eot(P),
- ok.
-
-erl_send_cookie_file(Config) when is_list(Config) ->
- case os:type() of
- vxworks ->
- {skip,"Skipped on VxWorks"};
- _ ->
- P = runner:start(Config, ?interpret),
- 1 = erl_connect_init(P, 42, '', 0),
- {ok,Fd} = erl_connect(P, node()),
-
- ok = erl_send(P, Fd, self(), AMsg={a,message}),
- receive AMsg -> ok end,
-
- 0 = erl_close_connection(P,Fd),
- runner:send_eot(P),
- runner:recv_eot(P),
- ok
- end.
-
-erl_reg_send(Config) when is_list(Config) ->
- P = runner:start(Config, ?interpret),
- 1 = erl_connect_init(P, 42, erlang:get_cookie(), 0),
- {ok,Fd} = erl_connect(P, node()),
-
- ARegName = a_strange_registred_name,
- register(ARegName, self()),
- ok = erl_reg_send(P, Fd, ARegName, AMsg={another,[strange],message}),
- receive AMsg -> ok end,
-
- 0 = erl_close_connection(P,Fd),
- runner:send_eot(P),
- runner:recv_eot(P),
- ok.
-
-
-%%% Interface functions for erl_interface functions.
-
-erl_connect_init(P, Num, Cookie, Creation) ->
- send_command(P, erl_connect_init, [Num,Cookie,Creation]),
- case get_term(P) of
- {term,Int} when is_integer(Int) -> Int
- end.
-
-erl_connect(P, Node) ->
- send_command(P, erl_connect, [Node]),
- case get_term(P) of
- {term,{Fd,_}} when Fd >= 0 -> {ok,Fd};
- {term,{-1,Errno}} -> {error,Errno}
- end.
-
-erl_close_connection(P, FD) ->
- send_command(P, erl_close_connection, [FD]),
- case get_term(P) of
- {term,Int} when is_integer(Int) -> Int
- end.
-
-erl_send(P, Fd, To, Msg) ->
- send_command(P, erl_send, [Fd,To,Msg]),
- get_send_result(P).
-
-erl_reg_send(P, Fd, To, Msg) ->
- send_command(P, erl_reg_send, [Fd,To,Msg]),
- get_send_result(P).
-
-get_send_result(P) ->
- case get_term(P) of
- {term,{1,_}} -> ok;
- {term,{-1,Errno}} -> {error,Errno};
- {term,{Res,Errno}}->
- io:format("Return value: ~p\nerl_errno: ~p", [Res,Errno]),
- ct:fail(bad_return_value)
- end.
-
-send_command(P, Name, Args) ->
- runner:send_term(P, {Name,list_to_tuple(Args)}).
diff --git a/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.first b/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.first
deleted file mode 100644
index 21a7aac0b0..0000000000
--- a/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.first
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2001-2016. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-
-erl_connect_test_decl.c: erl_connect_test.c
- erl -noinput -pa ../all_SUITE_data -s init_tc run erl_connect_test -s erlang halt
diff --git a/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src
deleted file mode 100644
index 23189f2a74..0000000000
--- a/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2000-2020. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-
-include @erl_interface_mk_include@
-
-CC0 = @CC@
-CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
-LD = @LD@
-LIBERL = @erl_interface_lib@
-LIBEI = @erl_interface_eilib@
-LIBFLAGS = ../all_SUITE_data/runner@obj@ \
- $(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
- @erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
-OBJS = erl_connect_test@obj@ erl_connect_test_decl@obj@
-
-all: erl_connect_test@exe@
-
-erl_connect_test@exe@: $(OBJS) $(LIBERL) $(LIBEI)
- $(LD) @CROSSLDFLAGS@ -o $@ $(OBJS) $(LIBFLAGS)
-
-clean:
- $(RM) $(OBJS)
- $(RM) erl_connect_test@exe@
diff --git a/lib/erl_interface/test/erl_connect_SUITE_data/erl_connect_test.c b/lib/erl_interface/test/erl_connect_SUITE_data/erl_connect_test.c
deleted file mode 100644
index 0adaa79a33..0000000000
--- a/lib/erl_interface/test/erl_connect_SUITE_data/erl_connect_test.c
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2000-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-/*
- * Purpose: Tests the functions in erl_connect.c.
- * Author: Bjorn Gustavsson
- *
- * See the erl_connect_SUITE.erl file for a "table of contents".
- */
-
-#include <stdio.h>
-#include <string.h>
-
-#include "runner.h"
-
-static void cmd_erl_connect_init(ETERM* args);
-static void cmd_erl_connect(ETERM* args);
-static void cmd_erl_send(ETERM* args);
-static void cmd_erl_reg_send(ETERM* args);
-static void cmd_erl_close_connection(ETERM *args);
-
-static void send_errno_result(int value);
-
-static struct {
- char* name;
- int num_args; /* Number of arguments. */
- void (*func)(ETERM* args);
-} commands[] = {
- "erl_connect_init", 3, cmd_erl_connect_init,
- "erl_connect", 1, cmd_erl_connect,
- "erl_close_connection", 1, cmd_erl_close_connection,
- "erl_send", 3, cmd_erl_send,
- "erl_reg_send", 3, cmd_erl_reg_send,
-};
-
-
-/*
- * Sends a list contaning all data types to the Erlang side.
- */
-
-TESTCASE(interpret)
-{
- ETERM* term;
-
- erl_init(NULL, 0);
-
- outer_loop:
-
- term = get_term();
-
- if (term == NULL) {
- report(1);
- return;
- } else {
- ETERM* Func;
- ETERM* Args;
- int i;
-
- if (!ERL_IS_TUPLE(term) || ERL_TUPLE_SIZE(term) != 2) {
- fail("term should be a tuple of size 2");
- }
-
- Func = erl_element(1, term);
- if (!ERL_IS_ATOM(Func)) {
- fail("function name should be an atom");
- }
- Args = erl_element(2, term);
- if (!ERL_IS_TUPLE(Args)) {
- fail("function arguments should be a tuple");
- }
- erl_free_term(term);
- for (i = 0; i < sizeof(commands)/sizeof(commands[0]); i++) {
- int n = strlen(commands[i].name);
- if (ERL_ATOM_SIZE(Func) != n) {
- continue;
- }
- if (memcmp(ERL_ATOM_PTR(Func), commands[i].name, n) == 0) {
- erl_free_term(Func);
- if (ERL_TUPLE_SIZE(Args) != commands[i].num_args) {
- fail("wrong number of arguments");
- }
- commands[i].func(Args);
- erl_free_term(Args);
- goto outer_loop;
- }
- }
- fail("bad command");
- }
-}
-
-#define VERIFY_TYPE(Test, Term) \
-if (!Test(Term)) { \
- fail("wrong type for " #Term); \
-} else { \
-}
-
-static void
-cmd_erl_connect_init(ETERM* args)
-{
- ETERM* number;
- ETERM* res;
- ETERM* cookie;
- char cookie_buffer[256];
-
- number = ERL_TUPLE_ELEMENT(args, 0);
- VERIFY_TYPE(ERL_IS_INTEGER, number);
- cookie = ERL_TUPLE_ELEMENT(args, 1);
- VERIFY_TYPE(ERL_IS_ATOM, cookie);
- if (ERL_ATOM_SIZE(cookie) == 0) {
- res = erl_mk_int(erl_connect_init(ERL_INT_VALUE(number), 0, 0));
- } else {
- memcpy(cookie_buffer, ERL_ATOM_PTR(cookie), ERL_ATOM_SIZE(cookie));
- cookie_buffer[ERL_ATOM_SIZE(cookie)] = '\0';
- res = erl_mk_int(erl_connect_init(ERL_INT_VALUE(number),
- cookie_buffer, 0));
- }
- send_term(res);
- erl_free_term(res);
-}
-
-static void
-cmd_erl_connect(ETERM* args)
-{
- ETERM* node;
- char node_buffer[256];
-
- node = ERL_TUPLE_ELEMENT(args, 0);
- VERIFY_TYPE(ERL_IS_ATOM, node);
- memcpy(node_buffer, ERL_ATOM_PTR(node), ERL_ATOM_SIZE(node));
- node_buffer[ERL_ATOM_SIZE(node)] = '\0';
- send_errno_result(erl_connect(node_buffer));
-}
-
-static void
-cmd_erl_close_connection(ETERM* args)
-{
- ETERM* number;
- ETERM* res;
-
- number = ERL_TUPLE_ELEMENT(args, 0);
- VERIFY_TYPE(ERL_IS_INTEGER, number);
- res = erl_mk_int(erl_close_connection(ERL_INT_VALUE(number)));
- send_term(res);
- erl_free_term(res);
-}
-
-static void
-cmd_erl_send(ETERM* args)
-{
- ETERM* fd_term = ERL_TUPLE_ELEMENT(args, 0);
- ETERM* to = ERL_TUPLE_ELEMENT(args, 1);
- ETERM* msg = ERL_TUPLE_ELEMENT(args, 2);
-
- VERIFY_TYPE(ERL_IS_INTEGER, fd_term);
- send_errno_result(erl_send(ERL_INT_VALUE(fd_term), to, msg));
-}
-
-static void
-cmd_erl_reg_send(ETERM* args)
-{
- ETERM* fd_term = ERL_TUPLE_ELEMENT(args, 0);
- ETERM* to = ERL_TUPLE_ELEMENT(args, 1);
- ETERM* msg = ERL_TUPLE_ELEMENT(args, 2);
- char reg_name[256];
-
- VERIFY_TYPE(ERL_IS_INTEGER, fd_term);
- VERIFY_TYPE(ERL_IS_ATOM, to);
- memcpy(reg_name, ERL_ATOM_PTR(to), ERL_ATOM_SIZE(to));
- reg_name[ERL_ATOM_SIZE(to)] = '\0';
- send_errno_result(erl_reg_send(ERL_INT_VALUE(fd_term), reg_name, msg));
-}
-
-static void
-send_errno_result(int value)
-{
- ETERM* res_array[2];
- ETERM* res_tuple;
-
- res_array[0] = erl_mk_int(value);
- res_array[1] = erl_mk_int(erl_errno);
- res_tuple = erl_mk_tuple(res_array, 2);
- send_term(res_tuple);
- erl_free_term(res_array[0]);
- erl_free_term(res_array[1]);
- erl_free_term(res_tuple);
-}
diff --git a/lib/erl_interface/test/erl_eterm_SUITE.erl b/lib/erl_interface/test/erl_eterm_SUITE.erl
deleted file mode 100644
index 6b8050937c..0000000000
--- a/lib/erl_interface/test/erl_eterm_SUITE.erl
+++ /dev/null
@@ -1,1084 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
--module(erl_eterm_SUITE).
-
--include_lib("common_test/include/ct.hrl").
--include("erl_eterm_SUITE_data/eterm_test_cases.hrl").
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%% The tests are organised as follows:
-%%%
-%%% 1. Basic tests (encoding, decoding, memory allocation).
-%%% 2. Constructing terms (the erl_mk_xxx() functions and erl_copy_term()).
-%%% 3. Extracting & info functions (erl_hd(), erl_length() etc).
-%%% 4. I/O list functions.
-%%% 5. Miscellaneous functions.
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
--export([all/0, suite/0,
- init_per_testcase/2,
- build_terms/1, round_trip_conversion/1,
- decode_terms/1, decode_float/1,
- t_erl_mk_int/1, t_erl_mk_list/1,
- basic_copy/1,
- t_erl_cons/1,
- t_erl_mk_atom/1,
- t_erl_mk_binary/1,
- t_erl_mk_empty_list/1,
- t_erl_mk_float/1,
- t_erl_mk_pid/1,
- t_erl_mk_xpid/1,
- t_erl_mk_port/1,
- t_erl_mk_xport/1,
- t_erl_mk_ref/1,
- t_erl_mk_long_ref/1,
- t_erl_mk_string/1,
- t_erl_mk_estring/1,
- t_erl_mk_tuple/1,
- t_erl_mk_uint/1,
- t_erl_mk_var/1,
- t_erl_size/1,
- t_erl_var_content/1,
- t_erl_element/1,
- t_erl_length/1, t_erl_hd/1, t_erl_tl/1,
- type_checks/1, extractor_macros/1,
- t_erl_iolist_length/1, t_erl_iolist_to_binary/1,
- t_erl_iolist_to_string/1,
- erl_print_term/1, print_string/1,
- t_erl_free_compound/1,
- high_chaparal/1,
- broken_data/1,
- cnode_1/1]).
-
--export([start_cnode/1]).
-
--import(runner, [get_term/1]).
-
-%% This test suite controls the running of the C language functions
-%% in eterm_test.c and print_term.c.
-
-suite() ->
- [{ct_hooks,[ts_install_cth]}].
-
-all() ->
- [build_terms, round_trip_conversion, decode_terms,
- decode_float, t_erl_mk_int, t_erl_mk_list, basic_copy,
- t_erl_mk_atom, t_erl_mk_binary, t_erl_mk_empty_list,
- t_erl_mk_float, t_erl_mk_pid, t_erl_mk_xpid,
- t_erl_mk_port, t_erl_mk_xport, t_erl_mk_ref,
- t_erl_mk_long_ref, t_erl_mk_string, t_erl_mk_estring,
- t_erl_mk_tuple, t_erl_mk_uint, t_erl_mk_var, t_erl_size,
- t_erl_var_content, t_erl_element, t_erl_cons,
- t_erl_length, t_erl_hd, t_erl_tl, type_checks,
- extractor_macros, t_erl_iolist_length,
- t_erl_iolist_to_binary, t_erl_iolist_to_string,
- erl_print_term, print_string, t_erl_free_compound,
- high_chaparal, broken_data, cnode_1].
-
-
-init_per_testcase(Case, Config) ->
- runner:init_per_testcase(?MODULE, Case, Config).
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%%
-%%% 1. B a s i c t e s t s
-%%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% This test asks the C function to construct all data types in
-%% a list and verifies that the result is as expected.
-
-build_terms(Config) when is_list(Config) ->
- P = runner:start(Config, ?build_terms),
- {term, Term} = get_term(P),
- io:format("Received: ~p", [Term]),
- [ARefLN, ARef, APortLN, APort, APidLN, APid,
- {element1, 42, 767}, "A string",
- 1, -1, 0, 3.0, ABin, 'I am an atom'] = Term,
- "A binary" = binary_to_list(ABin),
- case ARef of
- R when is_reference(R), node(R) == kalle@localhost -> ok
- end,
- case ARefLN of
- R1 when is_reference(R1), node(R1) == abcdefghijabcdefghij@localhost -> ok
- end,
- case APort of
- Port when is_port(Port), node(Port) == kalle@localhost -> ok
- end,
- case APortLN of
- Port1 when is_port(Port1), node(Port1) == abcdefghijabcdefghij@localhost -> ok
- end,
- case APid of
- Pid when is_pid(Pid), node(Pid) == kalle@localhost -> ok
- end,
- case APidLN of
- Pid1 when is_pid(Pid1), node(Pid1) == abcdefghijabcdefghij@localhost -> ok
- end,
-
- runner:recv_eot(P),
- ok.
-
-%% This test is run entirely in C code.
-
-round_trip_conversion(Config) when is_list(Config) ->
- runner:test(Config, ?round_trip_conversion),
- ok.
-
-%% This test sends a list of all data types to the C code function,
-%% which decodes it and verifies it.
-
-decode_terms(Config) when is_list(Config) ->
- Dummy1 = list_to_atom(filename:join(proplists:get_value(priv_dir, Config),
- dummy_file1)),
- Dummy2 = list_to_atom(filename:join(proplists:get_value(priv_dir, Config),
- dummy_file2)),
- Port1 = open_port(Dummy1, [out]),
- Port2 = open_port(Dummy2, [out]),
- ABinary = list_to_binary("A binary"),
- Terms = [make_ref(), make_ref(),
- Port1, Port2,
- self(), self(),
- {element1, 42, 767}, "A string",
- 1, -1, 0, 3.0, ABinary, 'I am an atom'],
-
- P = runner:start(Config, ?decode_terms),
- runner:send_term(P, Terms),
- runner:recv_eot(P),
-
- ok.
-
-%% Decodes the floating point number 3.1415.
-
-decode_float(Config) when is_list(Config) ->
- P = runner:start(Config, ?decode_float),
- runner:send_term(P, 3.1415),
- runner:recv_eot(P),
- ok.
-
-%% Tests the erl_free_compound() function.
-
-t_erl_free_compound(Config) when is_list(Config) ->
- runner:test(Config, ?t_erl_free_compound),
- ok.
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%%
-%%% 2. C o n s t r u c t i n g t e r m s
-%%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% This tests the erl_mk_list() function.
-
-t_erl_mk_list(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_list),
-
- {term, []} = get_term(P),
- {term, [abc]} = get_term(P),
- {term, [abcdef, 42]} = get_term(P),
- {term, [0.0, 23, [], 3.1415]} = get_term(P),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_int() function.
-
-t_erl_mk_int(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_int),
-
- {term, 0} = get_term(P),
- {term, 127} = get_term(P),
- {term, 128} = get_term(P),
- {term, 255} = get_term(P),
- {term, 256} = get_term(P),
-
- {term, 16#FFFF} = get_term(P),
- {term, 16#10000} = get_term(P),
-
- {term, 16#07FFFFFF} = get_term(P),
- {term, 16#0FFFFFFF} = get_term(P),
- {term, 16#1FFFFFFF} = get_term(P),
- {term, 16#3FFFFFFF} = get_term(P),
- {term, 16#7FFFFFFF} = get_term(P),
-
- {term, 16#08000000} = get_term(P),
- {term, 16#10000000} = get_term(P),
- {term, 16#20000000} = get_term(P),
- {term, 16#40000000} = get_term(P),
-
-
- {term, -16#07FFFFFF} = get_term(P),
- {term, -16#0FFFFFFF} = get_term(P),
- {term, -16#1FFFFFFF} = get_term(P),
- {term, -16#3FFFFFFF} = get_term(P),
- {term, -16#7FFFFFFF} = get_term(P),
-
- {term, -16#08000000} = get_term(P),
- {term, -16#10000000} = get_term(P),
- {term, -16#20000000} = get_term(P),
- {term, -16#40000000} = get_term(P),
-
- {term, -16#08000001} = get_term(P),
- {term, -16#10000001} = get_term(P),
- {term, -16#20000001} = get_term(P),
- {term, -16#40000001} = get_term(P),
-
- {term, -16#08000002} = get_term(P),
- {term, -16#10000002} = get_term(P),
- {term, -16#20000002} = get_term(P),
- {term, -16#40000002} = get_term(P),
-
- {term, -1999999999} = get_term(P),
- {term, -2000000000} = get_term(P),
- {term, -2000000001} = get_term(P),
-
- runner:recv_eot(P),
- ok.
-
-
-%% Basic test of erl_copy_term().
-
-basic_copy(Config) when is_list(Config) ->
- runner:test(Config, ?basic_copy),
- ok.
-
-
-%% This tests the erl_mk_tuple() function.
-
-t_erl_mk_tuple(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_tuple),
-
- {term, {madonna, 21, 'mad donna', 12}} = get_term(P),
- {term, {'Madonna',21,{children,{"Isabella",2}},
- {'home page',"http://www.madonna.com/"}}} = get_term(P),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_atom() function.
-
-t_erl_mk_atom(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_atom),
-
- {term, madonna} = (get_term(P)),
- {term, 'Madonna'} = (get_term(P)),
- {term, 'mad donna'} = (get_term(P)),
- {term, '_madonna_'} = (get_term(P)),
- {term, '/home/madonna/tour_plan'} = (get_term(P)),
- {term, 'http://www.madonna.com/tour_plan'} = (get_term(P)),
- {term, '\'madonna\''} = (get_term(P)),
- {term, '\"madonna\"'} = (get_term(P)),
- {term, '\\madonna\\'} = (get_term(P)),
- {term, '{madonna,21,\'mad donna\',12}'} = (get_term(P)),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_binary() function.
-
-t_erl_mk_binary(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_binary),
-
- {term, Bin} = (get_term(P)),
- "{madonna,21,'mad donna',1234.567.890, !#$%&/()=?+-@, \" \\}" = binary_to_list(Bin),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_empty_list() function.
-
-t_erl_mk_empty_list(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_empty_list),
-
- {term, []} = get_term(P),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_float() function.
-
-t_erl_mk_float(Config) when is_list(Config) ->
- case os:type() of
- vxworks ->
- {skipped, "Floating point numbers never compare equal on PPC"};
- _ ->
- P = runner:start(Config, ?t_erl_mk_float),
- {term, {3.1415, 1.999999, 2.000000, 2.000001,
- 2.000002, 12345.67890}} = get_term(P),
- runner:recv_eot(P),
- ok
- end.
-
-
-%% This tests the erl_mk_pid() function.
-
-t_erl_mk_pid(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_pid),
-
- {term, A_pid} = (get_term(P)),
- {pid, kalle@localhost, 3, 2} = nc2vinfo(A_pid),
-
- runner:recv_eot(P),
- ok.
-
-t_erl_mk_xpid(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_xpid),
-
- {term, A_pid} = (get_term(P)),
- {pid, kalle@localhost, 32767, 8191} = nc2vinfo(A_pid),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_port() function.
-
-t_erl_mk_port(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_port),
-
- {term, A_port} = (get_term(P)),
- {port, kalle@localhost, 4} = nc2vinfo(A_port),
-
- runner:recv_eot(P),
- ok.
-
-t_erl_mk_xport(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_xport),
-
- {term, A_port} = (get_term(P)),
- {port, kalle@localhost, 268435455} = nc2vinfo(A_port),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_ref() function.
-
-t_erl_mk_ref(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_ref),
-
- {term, A_ref} = (get_term(P)),
- {ref, kalle@localhost, _Length, [6]} = nc2vinfo(A_ref),
-
- runner:recv_eot(P),
- ok.
-
-t_erl_mk_long_ref(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_long_ref),
-
- {term, A_ref} = (get_term(P)),
- {ref, kalle@localhost, _Length, [4294967295,4294967295,262143]}
- = nc2vinfo(A_ref),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_string() function.
-
-t_erl_mk_string(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_string),
-
- {term, "madonna"} = (get_term(P)),
- {term, "Madonna"} = (get_term(P)),
- {term, "mad donna"} = (get_term(P)),
- {term, "_madonna_"} = (get_term(P)),
- {term, "/home/madonna/tour_plan"} = (get_term(P)),
- {term, "http://www.madonna.com/tour_plan"} = (get_term(P)),
- {term, "\'madonna\'"} = (get_term(P)),
- {term, "\"madonna\""} = (get_term(P)),
- {term, "\\madonna\\"} = (get_term(P)),
- {term, "{madonna,21,'mad donna',12}"} = (get_term(P)),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_estring() function.
-
-t_erl_mk_estring(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_estring),
-
- {term, "madonna"} = (get_term(P)),
- {term, "Madonna"} = (get_term(P)),
- {term, "mad donna"} = (get_term(P)),
- {term, "_madonna_"} = (get_term(P)),
- {term, "/home/madonna/tour_plan"} = (get_term(P)),
- {term, "http://www.madonna.com/tour_plan"} = (get_term(P)),
- {term, "\'madonna\'"} = (get_term(P)),
- {term, "\"madonna\""} = (get_term(P)),
- {term, "\\madonna\\"} = (get_term(P)),
- {term, "{madonna,21,'mad donna',12}"} = (get_term(P)),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_uint() function.
-
-t_erl_mk_uint(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_uint),
-
- {term, 54321} = (get_term(P)),
- {term, 2147483647} = (get_term(P)),
- {term, 2147483648} = (get_term(P)),
- {term, 2147483649} = (get_term(P)),
- {term, 2147483650} = (get_term(P)),
- {term, 4294967295} = (get_term(P)),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_mk_var() function.
-
-t_erl_mk_var(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_mk_var),
-
- {term, 1} = (get_term(P)),
- {term, 0} = (get_term(P)),
- {term, 1} = (get_term(P)),
- {term, 0} = (get_term(P)),
- {term, 1} = (get_term(P)),
- {term, 0} = (get_term(P)),
- {term, 1} = (get_term(P)),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_cons() function.
-
-t_erl_cons(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_cons),
-
- {term, [madonna, 21]} = get_term(P),
-
- runner:recv_eot(P),
- ok.
-
-
-
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%%
-%%% 3. E x t r a c t i n g & i n f o f u n c t i o n s
-%%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% Tests the erl_length() function.
-
-t_erl_length(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_length),
-
- 0 = erl_length(P, []),
- 1 = erl_length(P, [a]),
- 2 = erl_length(P, [a, b]),
- 3 = erl_length(P, [a, b, c]),
-
- 4 = erl_length(P, [a, [x, y], c, []]),
-
- -1 = erl_length(P, [a|b]),
- -1 = erl_length(P, a),
-
- runner:finish(P),
- ok.
-
-%% Invokes the erl_length() function.
-
-erl_length(Port, List) ->
- call_erl_function(Port, List).
-
-%% Tests the erl_hd() function.
-
-t_erl_hd(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_hd),
-
- 'NULL' = erl_hd(P, 42),
- 'NULL' = erl_hd(P, abc),
- 'NULL' = erl_hd(P, []),
-
- [] = erl_hd(P, [[], a]),
- a = erl_hd(P, [a]),
- a = erl_hd(P, [a, b]),
- a = erl_hd(P, [a, b, c]),
- a = erl_hd(P, [a|b]),
-
- runner:send_eot(P),
- runner:recv_eot(P),
- ok.
-
-%% Invokes the erl_hd() function.
-
-erl_hd(Port, List) ->
- call_erl_function(Port, List).
-
-%% Tests the erl_tail() function.
-
-t_erl_tl(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_tl),
-
- 'NULL' = erl_tl(P, 42),
- 'NULL' = erl_tl(P, abc),
- 'NULL' = erl_tl(P, []),
-
- [] = erl_tl(P, [a]),
- [b] = erl_tl(P, [a, b]),
- [b, c] = erl_tl(P, [a, b, c]),
-
- b = erl_tl(P, [a|b]),
-
- runner:send_eot(P),
- runner:recv_eot(P),
- ok.
-
-%% Invokes the erl_tail() function in erl_interface.
-
-erl_tl(Port, List) ->
- call_erl_function(Port, List).
-
-%% Tests the type checking macros (done in the C program).
-
-type_checks(Config) when is_list(Config) ->
- runner:test(Config, ?type_checks),
- ok.
-
-%% Tests the extractor macros (done in the C program).
-
-extractor_macros(Config) when is_list(Config) ->
- runner:test(Config, ?extractor_macros),
- ok.
-
-
-%% This tests the erl_size() function.
-
-t_erl_size(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_size),
-
- {term, 0} = (get_term(P)),
- {term, 4} = (get_term(P)),
-
- {term, 0} = (get_term(P)),
- {term, 27} = (get_term(P)),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_var_content() function.
-
-t_erl_var_content(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_var_content),
-
- {term, 17} = (get_term(P)),
- {term, "http://www.madonna.com"} = (get_term(P)),
- {term, 2} = (get_term(P)),
- {term, "http://www.madonna.com"} = (get_term(P)),
- {term, 2} = (get_term(P)),
-
- runner:recv_eot(P),
- ok.
-
-
-%% This tests the erl_element() function.
-
-t_erl_element(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_element),
-
- {term, madonna} = get_term(P),
- {term, 21} = get_term(P),
- {term, 'mad donna'} = get_term(P),
- {term, 12} = get_term(P),
-
- {term, 'Madonna'} = get_term(P),
- {term, 21} = get_term(P),
- {term, {children,{"Isabella",2}}} = get_term(P),
- {term, {'home page',"http://www.madonna.com/"}} = get_term(P),
-
- runner:recv_eot(P),
- ok.
-
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%%
-%%% 4. I / O l i s t f u n c t i o n s
-%%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% Tests the erl_iolist_length() function.
-
-t_erl_iolist_length(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_iolist_length),
-
- %% Flat lists.
-
- 0 = erl_iolist_length(P, []),
- 1 = erl_iolist_length(P, [10]),
- 2 = erl_iolist_length(P, [10, 20]),
- 3 = erl_iolist_length(P, [10, 20, 30]),
- 256 = erl_iolist_length(P, lists:seq(0, 255)),
-
- %% Deep lists.
-
- 0 = erl_iolist_length(P, [[]]),
- 1 = erl_iolist_length(P, [[], 42]),
- 1 = erl_iolist_length(P, [42, []]),
- 2 = erl_iolist_length(P, [42, [], 45]),
-
- 3 = erl_iolist_length(P, [42, [90], 45]),
- 3 = erl_iolist_length(P, [[42, [90]], 45]),
- 3 = erl_iolist_length(P, [[42, [90]], 45]),
-
- %% List with binaries.
-
- 0 = erl_iolist_length(P, [list_to_binary([])]),
- 0 = erl_iolist_length(P, [[], list_to_binary([])]),
- 1 = erl_iolist_length(P, [[1], list_to_binary([])]),
- 1 = erl_iolist_length(P, [[], list_to_binary([2])]),
- 2 = erl_iolist_length(P, [[42], list_to_binary([2])]),
- 4 = erl_iolist_length(P, [[42], list_to_binary([2, 3, 4])]),
-
- %% Binaries as tail.
-
- 0 = erl_iolist_length(P, [[]| list_to_binary([])]),
- 1 = erl_iolist_length(P, [[1]| list_to_binary([])]),
- 1 = erl_iolist_length(P, [[]| list_to_binary([2])]),
- 2 = erl_iolist_length(P, [[42]| list_to_binary([2])]),
-
- %% Binaries only.
-
- 0 = erl_iolist_length(P, list_to_binary("")),
- 1 = erl_iolist_length(P, list_to_binary([1])),
- 2 = erl_iolist_length(P, list_to_binary([1, 2])),
-
- %% Illegal cases.
-
- -1 = erl_iolist_length(P, [42|43]),
- -1 = erl_iolist_length(P, a),
-
- -1 = erl_iolist_length(P, [a]),
- -1 = erl_iolist_length(P, [256]),
- -1 = erl_iolist_length(P, [257]),
- -1 = erl_iolist_length(P, [-1]),
- -1 = erl_iolist_length(P, [-2]),
- -1 = erl_iolist_length(P, [-127]),
- -1 = erl_iolist_length(P, [-128]),
-
- runner:finish(P),
- ok.
-
-%% Invokes the erl_iolist_length() function.
-
-erl_iolist_length(Port, List) ->
- call_erl_function(Port, List).
-
-%% Tests the erl_iolist_to_binary() function.
-
-t_erl_iolist_to_binary(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_iolist_to_binary),
-
- %% Flat lists.
-
- [] = iolist_to_list(P, []),
- [10] = iolist_to_list(P, [10]),
- [10, 20] = iolist_to_list(P, [10, 20]),
- [10, 20, 30] = iolist_to_list(P, [10, 20, 30]),
- AllBytes = lists:seq(0, 255),
- AllBytes = iolist_to_list(P, AllBytes),
-
- %% Deep lists.
-
- [] = iolist_to_list(P, [[]]),
- [42] = iolist_to_list(P, [[], 42]),
- [42] = iolist_to_list(P, [42, []]),
- [42, 45] = iolist_to_list(P, [42, [], 45]),
-
- [42, 90, 45] = iolist_to_list(P, [42, [90], 45]),
- [42, 90, 45] = iolist_to_list(P, [[42, [90]], 45]),
- [42, 90, 45] = iolist_to_list(P, [[42, [90]], 45]),
-
- %% List with binaries.
-
- [] = iolist_to_list(P, [list_to_binary([])]),
- [] = iolist_to_list(P, [[], list_to_binary([])]),
- [1] = iolist_to_list(P, [[1], list_to_binary([])]),
- [2] = iolist_to_list(P, [[], list_to_binary([2])]),
- [42, 2] = iolist_to_list(P, [[42], list_to_binary([2])]),
- [42, 2, 3, 4] = iolist_to_list(P, [[42], list_to_binary([2, 3, 4])]),
-
- %% Binaries as tail.
-
- [] = iolist_to_list(P, [[]| list_to_binary([])]),
- [1] = iolist_to_list(P, [[1]| list_to_binary([])]),
- [2] = iolist_to_list(P, [[]| list_to_binary([2])]),
- [42, 2] = iolist_to_list(P, [[42]| list_to_binary([2])]),
-
- %% Binaries only.
-
- [] = iolist_to_list(P, list_to_binary("")),
- [1] = iolist_to_list(P, list_to_binary([1])),
- [1, 2] = iolist_to_list(P, list_to_binary([1, 2])),
-
- %% Illegal cases.
-
- 'NULL' = iolist_to_list(P, [42|43]),
- 'NULL' = iolist_to_list(P, a),
-
- 'NULL' = iolist_to_list(P, [a]),
- 'NULL' = iolist_to_list(P, [256]),
- 'NULL' = iolist_to_list(P, [257]),
- 'NULL' = iolist_to_list(P, [-1]),
- 'NULL' = iolist_to_list(P, [-2]),
- 'NULL' = iolist_to_list(P, [-127]),
- 'NULL' = iolist_to_list(P, [-128]),
-
- runner:finish(P),
- ok.
-
-iolist_to_list(Port, Term) ->
- case call_erl_function(Port, Term) of
- 'NULL' ->
- 'NULL';
- Bin when is_binary(Bin) ->
- binary_to_list(Bin)
- end.
-
-%% Tests the erl_iolist_to_string() function.
-
-t_erl_iolist_to_string(Config) when is_list(Config) ->
- P = runner:start(Config, ?t_erl_iolist_to_string),
-
- %% Flat lists.
-
- [0] = iolist_to_string(P, []),
- [10, 0] = iolist_to_string(P, [10]),
- [10, 20, 0] = iolist_to_string(P, [10, 20]),
- [10, 20, 30, 0] = iolist_to_string(P, [10, 20, 30]),
- AllBytes = lists:seq(1, 255)++[0],
- AllBytes = iolist_to_string(P, lists:seq(1, 255)),
-
- %% Deep lists.
-
- [0] = iolist_to_string(P, [[]]),
- [42, 0] = iolist_to_string(P, [[], 42]),
- [42, 0] = iolist_to_string(P, [42, []]),
- [42, 45, 0] = iolist_to_string(P, [42, [], 45]),
-
- [42, 90, 45, 0] = iolist_to_string(P, [42, [90], 45]),
- [42, 90, 45, 0] = iolist_to_string(P, [[42, [90]], 45]),
- [42, 90, 45, 0] = iolist_to_string(P, [[42, [90]], 45]),
-
- %% List with binaries.
-
- [0] = iolist_to_string(P, [list_to_binary([])]),
- [0] = iolist_to_string(P, [[], list_to_binary([])]),
- [1, 0] = iolist_to_string(P, [[1], list_to_binary([])]),
- [2, 0] = iolist_to_string(P, [[], list_to_binary([2])]),
- [42, 2, 0] = iolist_to_string(P, [[42], list_to_binary([2])]),
- [42, 2, 3, 4, 0] = iolist_to_string(P, [[42],
- list_to_binary([2, 3, 4])]),
-
- %% Binaries as tail.
-
- [0] = iolist_to_string(P, [[]| list_to_binary([])]),
- [1, 0] = iolist_to_string(P, [[1]| list_to_binary([])]),
- [2, 0] = iolist_to_string(P, [[]| list_to_binary([2])]),
- [42, 2, 0] = iolist_to_string(P, [[42]| list_to_binary([2])]),
-
- %% Binaries only.
-
- [0] = iolist_to_string(P, list_to_binary("")),
- [1, 0] = iolist_to_string(P, list_to_binary([1])),
- [1, 2, 0] = iolist_to_string(P, list_to_binary([1, 2])),
-
- %% Illegal cases.
-
- 'NULL' = iolist_to_string(P, [0]),
- 'NULL' = iolist_to_string(P, [65, 0, 66]),
- 'NULL' = iolist_to_string(P, [65, 66, 67, 0]),
-
- 'NULL' = iolist_to_string(P, [42|43]),
- 'NULL' = iolist_to_string(P, a),
-
- 'NULL' = iolist_to_string(P, [a]),
- 'NULL' = iolist_to_string(P, [256]),
- 'NULL' = iolist_to_string(P, [257]),
- 'NULL' = iolist_to_string(P, [-1]),
- 'NULL' = iolist_to_string(P, [-2]),
- 'NULL' = iolist_to_string(P, [-127]),
- 'NULL' = iolist_to_string(P, [-128]),
-
- runner:finish(P),
- ok.
-
-%% Invokes the erl_iolist_to_string() function.
-
-iolist_to_string(Port, Term) ->
- runner:send_term(Port, Term),
- case get_term(Port) of
- {bytes, Result} -> Result;
- 'NULL' -> 'NULL'
- end.
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%%
-%%% 5. M i s c e l l a n o u s T e s t s
-%%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%% Tests the erl_print_term() function
-erl_print_term(Config) when is_list(Config) ->
- PrintTerm = print_term(Config),
- P = open_port({spawn, PrintTerm}, [stream]),
-
- %% Lists.
-
- print(P, "[]", []),
- print(P, "[a]", [a]),
- print(P, "[[a]]", [[a]]),
- print(P, "[[]]", [[]]),
- print(P, "[a,b,c]", [a,b,c]),
- print(P, "[a,b|c]", [a,b|c]),
- print(P, "[a,[],c]", [a,[],c]),
- print(P, "[a,[1000,1],c]", [a,[1000,1],c]),
-
- %% Tuples.
-
- print(P, "{}", {}),
- print(P, "{ok}", {ok}),
- print(P, "{1,2,3}", {1, 2, 3}),
-
- %% Pids.
-
- {_X, Y, Z} = split_pid(self()),
- PidString = lists:flatten(io_lib:format("<~s.~w.~w>",
- [node(), Y, Z])),
- print(P, PidString, self()),
-
- unlink(P),
- exit(P, die),
- ok.
-
-split_pid(Pid) when is_pid(Pid) ->
- split_pid(pid_to_list(Pid), 0, []).
-
-split_pid([$<|Rest], Cur, Result) ->
- split_pid(Rest, Cur, Result);
-split_pid([Digit|Rest], Cur, Result) when $0 =< Digit, Digit =< $9 ->
- split_pid(Rest, 10*Cur+Digit-$0, Result);
-split_pid([$.|Rest], Cur, Result) ->
- split_pid(Rest, 0, Result++[Cur]);
-split_pid([$>], Cur, Result) ->
- list_to_tuple(Result++[Cur]).
-
-%% Test printing a string with erl_print_term()
-print_string(Config) when is_list(Config) ->
- PrintTerm = print_term(Config),
- P = open_port({spawn, PrintTerm}, [stream]),
-
- %% Strings.
-
- print(P, "\"ABC\"", "ABC"),
- {11, "\"\\tABC\\r\\n\""} = print(P, "\tABC\r\n"),
-
- %% Not strings.
-
- print(P, "[65,66,67,0]", "ABC\000"),
-
- unlink(P),
- exit(P, die),
- ok.
-
-print(Port, TermString, Term) ->
- Length = length(TermString),
- {Length, TermString} = print(Port, Term).
-
-%% This function uses the erl_print_term() function in erl_interface
-%% to print a term.
-%% Returns: {NumChars, Chars}
-
-print(Port, Term) ->
- Bin = term_to_binary(Term),
- Size = size(Bin),
- Port ! {self(), {command, [Size div 256, Size rem 256, Bin]}},
- collect_line(Port, []).
-
-collect_line(Port, Result) ->
- receive
- {Port, {data, Data}} ->
- case lists:reverse(Data) of
- [$\n|Rest] ->
- collect_line1(Rest++Result, []);
- Chars ->
- collect_line(Port, Chars++Result)
- end
- after 5000 ->
- ct:fail("No response from C program")
- end.
-
-collect_line1([$\r|Rest], Result) ->
- {list_to_integer(Result), lists:reverse(Rest)};
-collect_line1([C|Rest], Result) ->
- collect_line1(Rest, [C|Result]).
-
-%% Test case submitted by Per Lundgren, ERV.
-
-high_chaparal(Config) when is_list(Config) ->
- P = runner:start(Config, ?high_chaparal),
- {term, [hello, world]} = get_term(P),
- runner:recv_eot(P),
- ok.
-
-%% OTP-7448
-broken_data(Config) when is_list(Config) ->
- P = runner:start(Config, ?broken_data),
- runner:recv_eot(P),
- ok.
-
-%% This calls a C function with one parameter and returns the result.
-
-call_erl_function(Port, Term) ->
- runner:send_term(Port, Term),
- case get_term(Port) of
- {term, Result} -> Result;
- 'NULL' -> 'NULL'
- end.
-
-print_term(Config) when is_list(Config) ->
- filename:join(proplists:get_value(data_dir, Config), "print_term").
-
-
-
-%%% We receive a ref from the cnode, and expect it to be a long ref.
-%%% We also send a ref we created ourselves, and expect to get it
-%%% back, without having been mutated into short form. We must take
-%%% care then to check the actual returned ref, and not the original
-%%% one, which is equal to it.
-
-%% Tests involving cnode: sends a long ref from a cnode to us
-cnode_1(Config) when is_list(Config) ->
- Cnode = filename:join(proplists:get_value(data_dir, Config), "cnode"),
- register(mip, self()),
- spawn_link(?MODULE, start_cnode, [Cnode]),
- Ref1 = get_ref(),
- io:format("Ref1 ~p~n", [Ref1]),
- check_ref(Ref1),
- Ref2 = make_ref(),
- Pid = receive
- Msg -> Msg %% pid
- end,
- Fun1 = fun(X) -> {Pid, X} end, % sneak in a fun test here
- %Fun1 = {wait_with_funs, new_dist_format},
- Term = {Ref2, Fun1, {1,2,3,4,5,6,7,8,9,10}},
- %% A term which will overflow the original buffer used in 'cnode'.
- Pid ! Term,
- receive
- Term2 ->
- io:format("received ~p~n", [Term2]),
- case Term2 of
- Term ->
- {Ref22,_,_} = Term2,
- check_ref(Ref22);
- X ->
- ct:fail({receive1,X})
- end
- after 5000 ->
- ct:fail(receive1)
- end,
- receive
- Pid ->
- ok;
- Y ->
- ct:fail({receive1,Y})
- after 5000 ->
- ct:fail(receive2)
- end,
- io:format("ref = ~p~n", [Ref1]),
- check_ref(Ref1),
- ok.
-
-check_ref(Ref) ->
- case bin_ext_type(Ref) of
- 101 ->
- ct:fail(oldref);
- 114 ->
- ok;
- Type ->
- ct:fail({type, Type})
- end.
-
-bin_ext_type(T) ->
- [131, Type | _] = binary_to_list(term_to_binary(T)),
- Type.
-
-get_ref() ->
- receive
- X when is_reference(X) ->
- X
- after 5000 ->
- ct:fail({cnode, timeout})
- end.
-
-start_cnode(Cnode) ->
- open_port({spawn, Cnode ++ " " ++ atom_to_list(erlang:get_cookie())}, []),
- rec_cnode().
-
-rec_cnode() ->
- receive
- X ->
- io:format("from cnode: ~p~n", [X]),
- rec_cnode()
- end.
-
-nc2vinfo(Pid) when is_pid(Pid) ->
- [_NodeStr, NumberStr, SerialStr]
- = string:tokens(pid_to_list(Pid), "<.>"),
- Number = list_to_integer(NumberStr),
- Serial = list_to_integer(SerialStr),
- {pid, node(Pid), Number, Serial};
-nc2vinfo(Port) when is_port(Port) ->
- ["#Port", _NodeStr, NumberStr]
- = string:tokens(erlang:port_to_list(Port), "<.>"),
- Number = list_to_integer(NumberStr),
- {port, node(Port), Number};
-nc2vinfo(Ref) when is_reference(Ref) ->
- ["#Ref", _NodeStr | NumStrList]
- = string:tokens(erlang:ref_to_list(Ref), "<.>"),
- {Len, RevNumList} = lists:foldl(fun ("0", {N, []}) ->
- {N+1, []};
- (IStr, {N, Is}) ->
- {N+1,
- [list_to_integer(IStr)|Is]}
- end,
- {0, []},
- NumStrList),
- {ref, node(Ref), Len, lists:reverse(RevNumList)};
-nc2vinfo(Other) ->
- {badarg, Other}.
diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src
deleted file mode 100644
index dac23ac898..0000000000
--- a/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2020. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-
-include @erl_interface_mk_include@
-
-CC0 = @CC@
-CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
-LD = @LD@
-LIBERL = @erl_interface_lib@
-LIBEI = @erl_interface_eilib@
-LIBFLAGS = ../all_SUITE_data/runner@obj@ \
- $(LIBERL) $(LIBEI) @erl_interface_sock_libs@ @LIBS@ \
- @erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
-ETERM_OBJS = eterm_test@obj@ eterm_test_decl@obj@
-CNODE_OBJS = cnode@obj@
-PRINT_OBJS = print_term@obj@
-EXE_FILES = eterm_test@exe@ print_term@exe@ cnode@exe@
-
-all: $(EXE_FILES)
-
-eterm_test@exe@: $(ETERM_OBJS) $(LIBERL) $(LIBEI)
- $(LD) @CROSSLDFLAGS@ -o $@ $(ETERM_OBJS) $(LIBFLAGS)
-
-cnode@exe@: $(CNODE_OBJS) $(LIBERL) $(LIBEI)
- $(LD) @CROSSLDFLAGS@ -o $@ $(CNODE_OBJS) $(LIBFLAGS)
-
-print_term@exe@: print_term@obj@ $(LIBERL) $(LIBEI)
- $(LD) @CROSSLDFLAGS@ -o $@ $(PRINT_OBJS) $(LIBFLAGS)
-
-clean:
- $(RM) $(ETERM_OBJS) $(CNODE_OBJS) $(PRINT_OBJS)
- $(RM) $(EXE_FILES)
diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c b/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c
deleted file mode 100644
index d02db3651f..0000000000
--- a/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1999-2020. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include "ei.h"
-#include "erl_interface.h"
-
-#define MSGSIZE 13
-
-#define SELF(fd) erl_mk_pid(erl_thisnodename(),fd,0,erl_thiscreation())
-
-#ifdef VXWORKS
-#define MAIN cnode
-#else
-#define MAIN main
-#endif
-
-/* FIXME uses mix och ei and erl_interface */
-
-/*
- A small cnode.
- To be called from the test case erl_eterm_SUITE:cnode_1.
-
- 1) Set up connection to node 'test_server' on the same host.
- All sends are done to a registered process named 'mip'.
- 2) Create a long ref and send it.
- 3) Create a pid for ourselves and send it.
- 4) Receive a message.
- 5) Send back the message part of the message.
- 6) Send back the 'to' part of the message.
- 7) Exit.
-*/
-
-MAIN(int argc, char **argv)
-
-{
- unsigned char *msgbufp;
- int msgsize;
- ErlMessage msg;
- char msgbuf[MSGSIZE];
- char buf[100];
- char buf1[100];
- char buf2[100];
- int ix;
- int s;
- int fd;
- char node[80];
- char server[80];
- char host[80];
- int number;
- ETERM *ref, *ref1, *ref2;
- FILE *dfile = fopen("cnode_debug_printout", "w");
-
- erl_init(NULL, 0);
-
- number = 1;
- if (argc >= 2) {
- s = erl_connect_init(number, argv[1], 0);
- } else {
- s = erl_connect_init(number, (char *) 0, 0);
- }
- gethostname(host, sizeof(host));
- sprintf(node, "c%d@%s", number, host);
-
- fprintf(dfile, "s = %d\n", s); fflush(dfile);
-
- sprintf(server, "test_server@%s", host);
- fd = erl_connect(server);
- fprintf(dfile, "fd = %d\n", fd);
-
-/* fprintf(dfile, "dist = %d\n", erl_distversion(fd)); */
-
-#if 1
- ref = erl_mk_long_ref(node, 4711, 113, 98, 0);
-#else
- ref = erl_mk_ref(node, 4711, 0);
-#endif
- fprintf(dfile, "ref = %p\n", ref); fflush(dfile);
-
- s = erl_reg_send(fd, "mip", ref);
- fprintf(dfile, "s = %d\n", s); fflush(dfile);
-
- {
- ETERM* emsg;
- emsg = SELF(fd);
- fprintf(dfile, "pid = %p\n", emsg); fflush(dfile);
- s = erl_reg_send(fd,"mip",emsg);
- fprintf(dfile, "s2 = %d\n", s); fflush(dfile);
- erl_free_term(emsg);
- }
-
- msgsize = 4;
- msgbufp = (unsigned char *) malloc(msgsize);
-
- do {
-#if 0
- s = erl_receive_msg(fd, msgbuf, MSGSIZE, &msg);
-#else
- s = erl_xreceive_msg(fd, &msgbufp, &msgsize, &msg);
-#endif
- switch (s) {
- case ERL_TICK:
- fprintf(dfile, "tick\n");
- break;
- case ERL_ERROR:
- fprintf(dfile, "error: %s (%d)\n", strerror(erl_errno), erl_errno);
- break;
- case ERL_MSG:
- fprintf(dfile, "msg %d\n", msgsize);
- break;
- default:
- fprintf(dfile, "unknown result %d\n", s);
- break;
- }
- fflush(dfile);
- } while (s == ERL_TICK);
-
- s = erl_reg_send(fd, "mip", msg.msg);
- fprintf(dfile, "s = %d\n", s); fflush(dfile);
- s = erl_reg_send(fd, "mip", msg.to);
- fprintf(dfile, "s = %d\n", s); fflush(dfile);
-#if 0
- /* from = NULL! */
- s = erl_reg_send(fd, "mip", msg.from);
- fprintf(dfile, "s = %d\n", s); fflush(dfile);
-#endif
-
-#if 0
- /* Unused code which tests refs in some ways. */
- ix = 0;
- s = ei_encode_term(buf, &ix, ref);
- printf ("ei encode = %d, ix = %d\n", s, ix);
-
- /* Compare old and new ref equal */
- ref1 = erl_mk_long_ref(node, 4711, 113, 98, 0);
- ref2 = erl_mk_ref(node, 4711, 0);
- s = erl_encode(ref1, buf1);
- fprintf(dfile, "enc1 s = %d\n", s); fflush(dfile);
- s = erl_encode(ref2, buf2);
- fprintf(dfile, "enc2 s = %d\n", s); fflush(dfile);
- s = erl_compare_ext(buf1, buf2);
- fprintf(dfile, "comp s = %d\n", s); fflush(dfile);
-
- /* Compare, in another way */
- s = erl_match(ref1, ref2);
- fprintf(dfile, "match s = %d\n", s); fflush(dfile);
-#endif
-
- fclose(dfile);
-
- erl_close_connection(fd);
-
- return 0;
-}
diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/eterm_test.c b/lib/erl_interface/test/erl_eterm_SUITE_data/eterm_test.c
deleted file mode 100644
index d97f218a26..0000000000
--- a/lib/erl_interface/test/erl_eterm_SUITE_data/eterm_test.c
+++ /dev/null
@@ -1,1604 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-/*
- * Purpose: Tests the functions in erl_eterm.c and erl_malloc.c.
- * Author: Bjorn Gustavsson
- *
- * See the erl_eterm_SUITE.erl file for a "table of contents".
- */
-
-#include <stdio.h>
-#include <string.h>
-
-#include "runner.h"
-
-/*
- * Find out which version of erl_interface we are using.
- */
-
-#ifdef ERL_IS_STRING
-#undef NEW_ERL_INTERFACE
-#else
-#define NEW_ERL_INTERFACE
-#endif
-
-void dump_term (FILE *fp, ETERM *t);
-
-static ETERM* all_types();
-
-/***********************************************************************
- *
- * 1. B a s i c t e s t s
- *
- ***********************************************************************/
-
-/*
- * Sends a list contaning all data types to the Erlang side.
- */
-
-TESTCASE(build_terms)
-{
- ETERM* t;
-
- erl_init(NULL, 0);
- t = all_types();
- send_term(t);
- report(1);
-}
-
-static int abs_and_sign(ETERM* v, unsigned long long* av, int* sign)
-{
- long long sv;
- switch (ERL_TYPE(v)) {
- case ERL_INTEGER: sv = ERL_INT_VALUE(v); break;
- case ERL_U_INTEGER: *av = ERL_INT_UVALUE(v); *sign = 0; return 1;
- case ERL_LONGLONG: sv = ERL_LL_VALUE(v); break;
- case ERL_U_LONGLONG: *av = ERL_LL_UVALUE(v); *sign = 0; return 1;
- default: return 0;
- }
- if (sv < 0) {
- *av = -sv;
- *sign = 1;
- }
- else {
- *av = sv;
- *sign = 0;
- }
- return 1;
-}
-
-/* Shouldn't erl_match() cope with this?
-*/
-static int eq_ints(ETERM* a, ETERM* b)
-{
- unsigned long long a_abs, b_abs;
- int a_sign, b_sign;
- return abs_and_sign(a, &a_abs, &a_sign) && abs_and_sign(b, &b_abs, &b_sign)
- && (a_abs == b_abs) && (a_sign == b_sign);
-}
-
-static void encode_decode(ETERM* original, const char* text)
-{
- static unsigned char encoded[16*1024];
- ETERM* new_terms;
- ETERM* head;
- int bytes;
- int len;
-
- /* If a list, check the elements one by one first */
- head = erl_hd(original);
- if (head != NULL) {
- encode_decode(head, "CAR");
- encode_decode(erl_tl(original), "CDR");
- }
-
- bytes = erl_encode(original, encoded);
- if (bytes == 0) {
- fail("failed to encode terms");
- }
- else if (bytes > sizeof(encoded)) {
- fail("encoded terms buffer overflow");
- }
- else if (bytes != (len=erl_term_len(original))) {
- fprintf(stderr, "bytes(%d) != len(%d) for term ", bytes, len);
- erl_print_term(stderr, original);
- fprintf(stderr, " [%s]\r\n", text);
- fail("erl_encode and erl_term_len do not agree");
- }
- else if ((new_terms = erl_decode(encoded)) == NULL) {
- fail("failed to decode terms");
- }
- else if (!erl_match(original, new_terms) && !eq_ints(original, new_terms)) {
- erl_print_term(stderr, original);
- fprintf(stderr, "(%i) != (%i)", ERL_TYPE(original), ERL_TYPE(new_terms));
- erl_print_term(stderr, new_terms);
- fprintf(stderr, " [%s]\r\n", text);
- fail("decoded terms didn't match original");
- }
- erl_free_term(original);
- erl_free_term(new_terms);
-}
-/*
- * Converts an Erlang term to the external term format and back again.
- */
-
-TESTCASE(round_trip_conversion)
-{
- int n, i;
-
- erl_init(NULL, 0);
- encode_decode(all_types(), "ALL");
-
- {
- int v;
- for (v = 8, n = 0; n < (sizeof(v)*8-4-1); v <<= 1, n++) {
- for (i=-4; i<4; i++) {
- encode_decode(erl_mk_int(v+i), "INT");
- encode_decode(erl_mk_int(-(v+i)), "NEG INT");
- }
- }
- }
- {
- unsigned int v;
- for (v = 8; v; v <<= 1) {
- for (i=-4; i<4; i++) {
- encode_decode(erl_mk_uint(v+i), "UINT");
- }
- }
- }
- {
- long long v;
- for (v = 8, n = 0; n < (sizeof(v)*8-4-1); v <<= 1, n++) {
- for (i=-4; i<4; i++) {
- encode_decode(erl_mk_longlong(v+i), "LONGLONG");
- encode_decode(erl_mk_longlong(-(v+i)), "NEG LONGLONG");
- }
- }
- }
- {
- unsigned long long v;
- for (v = 8; v; v <<= 1) {
- for (i=-4; i<4; i++) {
- encode_decode(erl_mk_ulonglong(v+i), "ULONGLONG");
- }
- }
- }
-
- report(1);
-}
-
-/*
- * Decodes data from the Erlang side and verifies.
- */
-
-TESTCASE(decode_terms)
-{
- ETERM* terms;
- char* message;
-
- erl_init(NULL, 0);
- terms = get_term();
- if (terms == NULL) {
- fail("unexpected end of file");
- } else {
- ETERM* all;
- ETERM* p;
- ETERM* t;
- int i;
-
- all = p = all_types();
- t = terms;
-
- /*
- * XXX For now, skip the reference, pid, and port, because
- * the match will fail. Must write code here to do some other
- * validating.
- */
-
- for (i=0; i<6; i++) {
-
- p = erl_tl(p);
- t = erl_tl(t);
- erl_free_term(p);
- erl_free_term(t);
-
- }
-
- /*
- * Match the tail of the lists.
- */
-
- if (!erl_match(p, t))
- {
- fail("Received terms didn't match expected");
- }
- erl_free_term(all);
- erl_free_term(terms);
- report(1);
- }
-}
-
-/*
- * Decodes a float from the Erlang side and verifies.
- */
-
-TESTCASE(decode_float)
-{
- ETERM* afnum;
- ETERM* efnum;
- int result;
-
- erl_init(NULL, 0);
- afnum = get_term();
- efnum = erl_mk_float(3.1415);
- result = erl_match(efnum, afnum);
- erl_free_term(afnum);
- erl_free_term(efnum);
- report(result);
-}
-
-/*
- * Tests the erl_free_compound() function.
- */
-
-TESTCASE(t_erl_free_compound)
-{
- ETERM* t;
-
- erl_init(NULL, 0);
-
- t = all_types();
- erl_free_compound(t);
- report(1);
-}
-
-
-/***********************************************************************
- *
- * 2. C o n s t r u c t i n g t e r m s
- *
- ***********************************************************************/
-
-/*
- * Makes various integers, and sends them to Erlang for verification.
- */
-
-TESTCASE(t_erl_mk_int)
-{
-#define SEND_INT(i) \
- do { \
- ETERM* t = erl_mk_int(i); \
- send_term(t); \
- } while (0);
-
- erl_init(NULL, 0);
-
- SEND_INT(0);
- SEND_INT(127);
- SEND_INT(128);
- SEND_INT(255);
- SEND_INT(256);
-
- SEND_INT(0xFFFF);
- SEND_INT(0x10000);
-
- SEND_INT(0x07FFFFFF);
- SEND_INT(0x0FFFFFFF);
- SEND_INT(0x1FFFFFFF);
- SEND_INT(0x3FFFFFFF);
- SEND_INT(0x7FFFFFFF);
-
- SEND_INT(0x08000000);
- SEND_INT(0x10000000);
- SEND_INT(0x20000000);
- SEND_INT(0x40000000);
-
- SEND_INT(-0x07FFFFFF);
- SEND_INT(-0x0FFFFFFF);
- SEND_INT(-0x1FFFFFFF);
- SEND_INT(-0x3FFFFFFF);
- SEND_INT(-0x7FFFFFFF);
-
- SEND_INT(-0x08000000);
- SEND_INT(-0x10000000);
- SEND_INT(-0x20000000);
- SEND_INT(-0x40000000);
-
- SEND_INT(-0x08000001);
- SEND_INT(-0x10000001);
- SEND_INT(-0x20000001);
- SEND_INT(-0x40000001);
-
- SEND_INT(-0x08000002);
- SEND_INT(-0x10000002);
- SEND_INT(-0x20000002);
- SEND_INT(-0x40000002);
-
- SEND_INT(-1999999999);
- SEND_INT(-2000000000);
- SEND_INT(-2000000001);
-
- report(1);
-}
-
-
-/*
- * Makes lists of various sizes, and sends them to Erlang for verification.
- */
-
-TESTCASE(t_erl_mk_list)
-{
- ETERM* a[4];
-
- erl_init(NULL, 0);
-
- /*
- * Empty list.
- */
-
- send_term(erl_mk_list(a, 0));
-
- /*
- * One element: [abc]
- */
-
- a[0] = erl_mk_atom("abc");
- send_term(erl_mk_list(a, 1));
- erl_free_term(a[0]);
-
- /*
- * Two elements: [abcdef, 42].
- */
-
- a[0] = erl_mk_atom("abcdef");
- a[1] = erl_mk_int(42);
- send_term(erl_mk_list(a, 2));
- erl_free_term(a[0]);
- erl_free_term(a[1]);
-
- /*
- * Four elements.
- */
-
- a[0] = erl_mk_float(0.0);
- a[1] = erl_mk_int(23);
- a[2] = erl_mk_empty_list();
- a[3] = erl_mk_float(3.1415);
- send_term(erl_mk_list(a, 4));
- erl_free_term(a[0]);
- erl_free_term(a[1]);
- erl_free_term(a[2]);
- erl_free_term(a[3]);
-
- report(1);
-}
-
-/*
- * A basic test of erl_copy_term().
- */
-
-TESTCASE(basic_copy)
-{
- ETERM* original;
- ETERM* copy;
- int result;
-
- erl_init(NULL, 0);
- original = all_types();
- copy = erl_copy_term(original);
- if (copy == NULL) {
- fail("erl_copy_term() failed");
- } else if (!erl_match(original, copy))
- {
- fail("copy doesn't match original");
- }
-
- erl_free_term(original);
- erl_free_term(copy);
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_atom().
- */
-
-TESTCASE(t_erl_mk_atom)
-{
- erl_init(NULL, 0);
-
- send_term(erl_mk_atom("madonna"));
- send_term(erl_mk_atom("Madonna"));
- send_term(erl_mk_atom("mad donna"));
- send_term(erl_mk_atom("_madonna_"));
- send_term(erl_mk_atom("/home/madonna/tour_plan"));
- send_term(erl_mk_atom("http://www.madonna.com/tour_plan"));
- send_term(erl_mk_atom("\'madonna\'"));
- send_term(erl_mk_atom("\"madonna\""));
- send_term(erl_mk_atom("\\madonna\\"));
- send_term(erl_mk_atom("{madonna,21,'mad donna',12}"));
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_binary().
- */
-
-TESTCASE(t_erl_mk_binary)
-{
-
- char* string;
- erl_init(NULL, 0);
-
- string = "{madonna,21,'mad donna',1234.567.890, !#$%&/()=?+-@, \" \\}";
- send_term(erl_mk_binary(string,strlen(string)));
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_empty_list().
- */
-
-TESTCASE(t_erl_mk_empty_list)
-{
- erl_init(NULL, 0);
-
- send_term(erl_mk_empty_list());
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_float().
- */
-
-TESTCASE(t_erl_mk_float)
-{
- ETERM* arr[6];
- ETERM* emsg;
-
- erl_init(NULL, 0);
-
- arr[0] = erl_mk_float(3.1415);
- arr[1] = erl_mk_float(1.999999);
- arr[2] = erl_mk_float(2.000000);
- arr[3] = erl_mk_float(2.000001);
- arr[4] = erl_mk_float(2.000002);
- arr[5] = erl_mk_float(12345.67890);
- emsg = (erl_mk_tuple(arr,6));
-
- send_term(emsg);
-
- erl_free_array(arr,6);
- /* emsg already freed by send_term() */
- /* erl_free_term(emsg); */
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_pid().
- */
-
-TESTCASE(t_erl_mk_pid)
-{
- erl_init(NULL, 0);
-
- send_term(erl_mk_pid("kalle@localhost", 3, 2, 1));
- report(1);
-}
-
-/*
- * A basic test of erl_mk_pid().
- */
-
-TESTCASE(t_erl_mk_xpid)
-{
- erl_init(NULL, 0);
-
- send_term(erl_mk_pid("kalle@localhost", 32767, 8191, 1));
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_port().
- */
-
-TESTCASE(t_erl_mk_port)
-{
- erl_init(NULL, 0);
-
- send_term(erl_mk_port("kalle@localhost", 4, 1));
- report(1);
-}
-
-/*
- * A basic test of erl_mk_port().
- */
-
-TESTCASE(t_erl_mk_xport)
-{
- erl_init(NULL, 0);
-
- send_term(erl_mk_port("kalle@localhost", 268435455, 1));
- report(1);
-}
-
-/*
- * A basic test of erl_mk_ref().
- */
-
-TESTCASE(t_erl_mk_ref)
-{
- erl_init(NULL, 0);
-
- send_term(erl_mk_ref("kalle@localhost", 6, 1));
- report(1);
-}
-
-/*
- * A basic test of erl_mk_long_ref().
- */
-
-
-TESTCASE(t_erl_mk_long_ref)
-{
- erl_init(NULL, 0);
-
- send_term(erl_mk_long_ref("kalle@localhost",
- 4294967295, 4294967295, 262143,
- 1));
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_string().
- */
-
-TESTCASE(t_erl_mk_string)
-{
-
- erl_init(NULL, 0);
-
- send_term(erl_mk_string("madonna"));
- send_term(erl_mk_string("Madonna"));
- send_term(erl_mk_string("mad donna"));
- send_term(erl_mk_string("_madonna_"));
- send_term(erl_mk_string("/home/madonna/tour_plan"));
- send_term(erl_mk_string("http://www.madonna.com/tour_plan"));
- send_term(erl_mk_string("\'madonna\'"));
- send_term(erl_mk_string("\"madonna\""));
- send_term(erl_mk_string("\\madonna\\"));
- send_term(erl_mk_string("{madonna,21,'mad donna',12}"));
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_estring().
- */
-
-TESTCASE(t_erl_mk_estring)
-{
- char* string;
- erl_init(NULL, 0);
-
- string = "madonna";
- send_term(erl_mk_estring(string,strlen(string)));
- string = "Madonna";
- send_term(erl_mk_estring(string,strlen(string)));
- string = "mad donna";
- send_term(erl_mk_estring(string,strlen(string)));
- string = "_madonna_";
- send_term(erl_mk_estring(string,strlen(string)));
- string = "/home/madonna/tour_plan";
- send_term(erl_mk_estring(string,strlen(string)));
- string = "http://www.madonna.com/tour_plan";
- send_term(erl_mk_estring(string,strlen(string)));
- string = "\'madonna\'";
- send_term(erl_mk_estring(string,strlen(string)));
- string = "\"madonna\"";
- send_term(erl_mk_estring(string,strlen(string)));
- string = "\\madonna\\";
- send_term(erl_mk_estring(string,strlen(string)));
- string = "{madonna,21,'mad donna',12}";
- send_term(erl_mk_estring(string,strlen(string)));
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_tuple().
- */
-
-TESTCASE(t_erl_mk_tuple)
-{
- ETERM* arr[4];
- ETERM* arr2[2];
- ETERM* arr3[2];
- ETERM* arr4[2];
-
- erl_init(NULL, 0);
-
- /* {madonna,21,'mad donna',12} */
- arr[0] = erl_mk_atom("madonna");
- arr[1] = erl_mk_int(21);
- arr[2] = erl_mk_atom("mad donna");
- arr[3] = erl_mk_int(12);
-
- send_term(erl_mk_tuple(arr,4));
-
- erl_free_array(arr,4);
-
-
- /* {'Madonna',21,{children,{"Isabella",2}},{'home page',"http://www.madonna.com/"} */
- arr4[0] = erl_mk_atom("home page");
- arr4[1] = erl_mk_string("http://www.madonna.com/");
-
- arr3[0] = erl_mk_string("Isabella");
- arr3[1] = erl_mk_int(2);
-
- arr2[0] = erl_mk_atom("children");
- arr2[1] = erl_mk_tuple(arr3,2);
-
- arr[0] = erl_mk_atom("Madonna");
- arr[1] = erl_mk_int(21);
- arr[2] = erl_mk_tuple(arr2,2);
- arr[3] = erl_mk_tuple(arr4,2);
-
- send_term(erl_mk_tuple(arr,4));
-
- erl_free_array(arr,4);
- erl_free_array(arr2,2);
- erl_free_array(arr3,2);
- erl_free_array(arr4,2);
-
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_uint().
- */
-
-TESTCASE(t_erl_mk_uint)
-{
- unsigned i;
-
- erl_init(NULL, 0);
-
- send_term(erl_mk_uint(54321));
- i = 2147483647;
- send_term(erl_mk_uint(i));
- send_term(erl_mk_uint(i+1));
- send_term(erl_mk_uint(i+2));
- send_term(erl_mk_uint(i+3));
- send_term(erl_mk_uint(i+i+1));
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_mk_var().
- */
-
-TESTCASE(t_erl_mk_var)
-{
- ETERM* mk_var;
- ETERM* term;
- ETERM* term2;
- ETERM* arr[4];
- ETERM* arr_term[2];
- ETERM* mk_var_tuple;
- ETERM* term_tuple;
-
- erl_init(NULL, 0);
-
-
- /* match unbound/bound variable against an integer */
- term = erl_mk_int(17);
- term2 = erl_mk_int(2);
- mk_var = erl_mk_var("New_var");
- send_term(erl_mk_int(erl_match(mk_var, term))); /* should be ok */
- send_term(erl_mk_int(erl_match(mk_var, term2))); /* should fail */
- send_term(erl_mk_int(erl_match(mk_var, term))); /* should be ok */
- send_term(erl_mk_int(erl_match(mk_var, term2))); /* should fail */
- erl_free_term(mk_var);
- erl_free_term(term);
- erl_free_term(term2);
-
- /* match unbound variable against a tuple */
- arr[0] = erl_mk_atom("madonna");
- arr[1] = erl_mk_int(21);
- arr[2] = erl_mk_atom("mad donna");
- arr[3] = erl_mk_int(12);
- mk_var = erl_mk_var("New_var");
- term = erl_mk_tuple(arr,4);
- send_term(erl_mk_int(erl_match(mk_var, term))); /* should be ok */
- erl_free_term(mk_var);
- erl_free_term(term);
- erl_free_array(arr,4);
-
-
- /* match (twice) unbound variable against an incorrect tuple */
- arr[0] = erl_mk_var("New_var");
- arr[1] = erl_mk_var("New_var");
- arr_term[0] = erl_mk_int(17);
- arr_term[1] = erl_mk_int(27);
- mk_var_tuple = erl_mk_tuple(arr,2);
- term_tuple = erl_mk_tuple(arr_term,2);
- send_term(erl_mk_int(erl_match(mk_var_tuple, term_tuple))); /* should fail */
- erl_free_array(arr,2);
- erl_free_array(arr_term,2);
- erl_free_term(mk_var_tuple);
- erl_free_term(term_tuple);
-
-
- /* match (twice) unbound variable against a correct tuple */
- arr[0] = erl_mk_var("New_var");
- arr[1] = erl_mk_var("New_var");
- arr_term[0] = erl_mk_int(17);
- arr_term[1] = erl_mk_int(17);
- mk_var_tuple = erl_mk_tuple(arr,2);
- term_tuple = erl_mk_tuple(arr_term,2);
- send_term(erl_mk_int(erl_match(mk_var_tuple, term_tuple))); /* should be ok */
- erl_free_array(arr,2);
- erl_free_array(arr_term,2);
- erl_free_term(mk_var_tuple);
- erl_free_term(term_tuple);
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_size().
- */
-
-TESTCASE(t_erl_size)
-{
- ETERM* arr[4];
- ETERM* tuple;
- ETERM* bin;
- char* string;
-
- erl_init(NULL, 0);
-
- /* size of a tuple */
- tuple = erl_format("{}");
- send_term(erl_mk_int(erl_size(tuple)));
- erl_free_term(tuple);
-
- arr[0] = erl_mk_atom("madonna");
- arr[1] = erl_mk_int(21);
- arr[2] = erl_mk_atom("mad donna");
- arr[3] = erl_mk_int(12);
- tuple = erl_mk_tuple(arr,4);
-
- send_term(erl_mk_int(erl_size(tuple)));
-
- erl_free_array(arr,4);
- erl_free_term(tuple);
-
- /* size of a binary */
- string = "";
- bin = erl_mk_binary(string,strlen(string));
- send_term(erl_mk_int(erl_size(bin)));
- erl_free_term(bin);
-
- string = "{madonna,21,'mad donna',12}";
- bin = erl_mk_binary(string,strlen(string));
- send_term(erl_mk_int(erl_size(bin)));
- erl_free_term(bin);
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_var_content().
- */
-
-TESTCASE(t_erl_var_content)
-{
- ETERM* mk_var;
- ETERM* term;
- ETERM* tuple;
- ETERM* list;
- ETERM* a;
- ETERM* b;
- ETERM* arr[4];
- ETERM* arr2[2];
- ETERM* arr3[2];
- ETERM* arr4[2];
-
- erl_init(NULL, 0);
-
- term = erl_mk_int(17);
- mk_var = erl_mk_var("Var");
-
- /* unbound, should return NULL */
- if (erl_var_content(mk_var,"Var") != NULL)
- fail("t_erl_var_content() failed");
-
- erl_match(mk_var, term);
- send_term(erl_var_content(mk_var,"Var")); /* should return 17 */
-
- /* integer, should return NULL */
- if (erl_var_content(term,"Var") != NULL)
- fail("t_erl_var_content() failed");
-
- /* unknown variable, should return NULL */
- if (erl_var_content(mk_var,"Unknown_Var") != NULL)
- fail("t_erl_var_content() failed");
-
- erl_free_term(mk_var);
- erl_free_term(term);
-
- /* {'Madonna',21,{children,{"Name","Age"}},{"Home_page","Tel_no"}} */
- arr4[0] = erl_mk_var("Home_page");
- arr4[1] = erl_mk_var("Tel_no");
- a = erl_mk_string("http://www.madonna.com");
- erl_match(arr4[0], a);
-
- arr3[0] = erl_mk_var("Name");
- arr3[1] = erl_mk_var("Age");
- b = erl_mk_int(2);
- erl_match(arr3[1], b);
-
- arr2[0] = erl_mk_atom("children");
- arr2[1] = erl_mk_tuple(arr3,2);
-
- arr[0] = erl_mk_atom("Madonna");
- arr[1] = erl_mk_int(21);
- arr[2] = erl_mk_tuple(arr2,2);
- arr[3] = erl_mk_tuple(arr4,2);
-
- tuple = erl_mk_tuple(arr,4);
-
- /* should return "http://www.madonna.com" */
- send_term(erl_var_content(tuple,"Home_page"));
-
- /* unbound, should return NULL */
- if (erl_var_content(tuple,"Tel_no") != NULL)
- fail("t_erl_var_content() failed");
-
- /* unbound, should return NULL */
- if (erl_var_content(tuple,"Name") != NULL)
- fail("t_erl_var_content() failed");
-
- /* should return 2 */
- send_term(erl_var_content(tuple,"Age"));
-
- erl_free_array(arr,4);
- erl_free_array(arr2,2);
- erl_free_array(arr3,2);
- erl_free_array(arr4,2);
- erl_free_term(tuple);
- erl_free_term(a);
- erl_free_term(b);
-
-
- /* [] */
- list = erl_mk_empty_list();
- if (erl_var_content(list,"Tel_no") != NULL)
- fail("t_erl_var_content() failed");
- erl_free_term(list);
-
-
- /* ['Madonna',[],{children,{"Name","Age"}},{"Home_page","Tel_no"}] */
- arr4[0] = erl_mk_var("Home_page");
- arr4[1] = erl_mk_var("Tel_no");
- a = erl_mk_string("http://www.madonna.com");
- erl_match(arr4[0], a);
-
- arr3[0] = erl_mk_var("Name");
- arr3[1] = erl_mk_var("Age");
- b = erl_mk_int(2);
- erl_match(arr3[1], b);
-
- arr2[0] = erl_mk_atom("children");
- arr2[1] = erl_mk_tuple(arr3,2);
-
- arr[0] = erl_mk_atom("Madonna");
- arr[1] = erl_mk_empty_list();
- arr[2] = erl_mk_tuple(arr2,2);
- arr[3] = erl_mk_tuple(arr4,2);
-
- list = erl_mk_list(arr,4);
-
- /* should return "http://www.madonna.com" */
- send_term(erl_var_content(list,"Home_page"));
-
- /* unbound, should return NULL */
- if (erl_var_content(list,"Tel_no") != NULL)
- fail("t_erl_var_content() failed");
-
- /* unbound, should return NULL */
- if (erl_var_content(list,"Name") != NULL)
- fail("t_erl_var_content() failed");
-
- /* should return 2 */
- send_term(erl_var_content(list,"Age"));
-
- erl_free_array(arr,4);
- erl_free_array(arr2,2);
- erl_free_array(arr3,2);
- erl_free_array(arr4,2);
- erl_free_term(list);
- erl_free_term(a);
- erl_free_term(b);
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_element().
- */
-
-TESTCASE(t_erl_element)
-{
- ETERM* arr[4];
- ETERM* arr2[2];
- ETERM* arr3[2];
- ETERM* arr4[2];
- ETERM* tuple;
-
- erl_init(NULL, 0);
-
- arr[0] = erl_mk_atom("madonna");
- arr[1] = erl_mk_int(21);
- arr[2] = erl_mk_atom("mad donna");
- arr[3] = erl_mk_int(12);
- tuple = erl_mk_tuple(arr,4);
-
- send_term(erl_element(1,tuple));
- send_term(erl_element(2,tuple));
- send_term(erl_element(3,tuple));
- send_term(erl_element(4,tuple));
-
- erl_free_array(arr,4);
- erl_free_term(tuple);
-
- /* {'Madonna',21,{children,{"Isabella",2}},{'home page',"http://www.madonna.com/"} */
- arr4[0] = erl_mk_atom("home page");
- arr4[1] = erl_mk_string("http://www.madonna.com/");
-
- arr3[0] = erl_mk_string("Isabella");
- arr3[1] = erl_mk_int(2);
-
- arr2[0] = erl_mk_atom("children");
- arr2[1] = erl_mk_tuple(arr3,2);
-
- arr[0] = erl_mk_atom("Madonna");
- arr[1] = erl_mk_int(21);
- arr[2] = erl_mk_tuple(arr2,2);
- arr[3] = erl_mk_tuple(arr4,2);
-
- tuple = erl_mk_tuple(arr,4);
- send_term(erl_element(1,tuple));
- send_term(erl_element(2,tuple));
- send_term(erl_element(3,tuple));
- send_term(erl_element(4,tuple));
-
- erl_free_term(tuple);
- erl_free_array(arr,4);
- erl_free_array(arr2,2);
- erl_free_array(arr3,2);
- erl_free_array(arr4,2);
-
- report(1);
-}
-
-
-/*
- * A basic test of erl_cons().
- */
-
-TESTCASE(t_erl_cons)
-{
- ETERM* list;
- ETERM* anAtom;
- ETERM* anInt;
-
- erl_init(NULL, 0);
-
- anAtom = erl_mk_atom("madonna");
- anInt = erl_mk_int(21);
- list = erl_mk_empty_list();
- list = erl_cons(anInt, list);
- send_term(erl_cons(anAtom, list));
-
- erl_free_term(anAtom);
- erl_free_term(anInt);
- erl_free_compound(list);
-
- report(1);
-}
-
-
-
-
-/***********************************************************************
- *
- * 3. E x t r a c t i n g & i n f o f u n c t i o n s
- *
- ***********************************************************************/
-
-/*
- * Calculates the length of each list sent to it and sends back the result.
- */
-
-TESTCASE(t_erl_length)
-{
- erl_init(NULL, 0);
-
- for (;;) {
- ETERM* term = get_term();
-
- if (term == NULL) {
- report(1);
- return;
- } else {
- ETERM* len_term;
-
- len_term = erl_mk_int(erl_length(term));
- erl_free_term(term);
- send_term(len_term);
- }
- }
-}
-
-/*
- * Gets the head of each term and sends the result back.
- */
-
-TESTCASE(t_erl_hd)
-{
- erl_init(NULL, 0);
-
- for (;;) {
- ETERM* term = get_term();
-
- if (term == NULL) {
- report(1);
- return;
- } else {
- ETERM* head;
-
- head = erl_hd(term);
- send_term(head);
- erl_free_term(term);
- }
- }
-}
-
-/*
- * Gets the tail of each term and sends the result back.
- */
-
-TESTCASE(t_erl_tl)
-{
- erl_init(NULL, 0);
-
- for (;;) {
- ETERM* term = get_term();
-
- if (term == NULL) {
- report(1);
- return;
- } else {
- ETERM* tail;
-
- tail = erl_tl(term);
- send_term(tail);
- erl_free_term(term);
- }
- }
-}
-
-/*
- * Checks the type checking macros.
- */
-
-TESTCASE(type_checks)
-{
- ETERM* t;
- ETERM* atom;
-
- erl_init(NULL, 0);
- atom = erl_mk_atom("an_atom");
-
-#define TYPE_CHECK(macro, term) \
- { ETERM* t = term; \
- if (macro(t)) { \
- erl_free_term(t); \
- } else { \
- fail("Macro " #macro " failed on " #term); \
- } \
- }
-
- TYPE_CHECK(ERL_IS_INTEGER, erl_mk_int(0x7FFFFFFF));
-#ifdef NEW_ERL_INTERFACE
- TYPE_CHECK(ERL_IS_UNSIGNED_INTEGER, erl_mk_uint(0x7FFFFFFF));
-#endif
- TYPE_CHECK(ERL_IS_FLOAT, erl_mk_float(5.5));
- TYPE_CHECK(ERL_IS_ATOM, erl_mk_atom("another_atom"));
-
- TYPE_CHECK(ERL_IS_EMPTY_LIST, erl_mk_empty_list());
- TYPE_CHECK(!ERL_IS_EMPTY_LIST, erl_cons(atom, atom));
-
-#ifdef NEW_ERL_INTERFACE
- TYPE_CHECK(!ERL_IS_CONS, erl_mk_empty_list());
- TYPE_CHECK(ERL_IS_CONS, erl_cons(atom, atom));
-#endif
-
- TYPE_CHECK(ERL_IS_LIST, erl_mk_empty_list());
- TYPE_CHECK(ERL_IS_LIST, erl_cons(atom, atom));
-
- TYPE_CHECK(ERL_IS_PID, erl_mk_pid("a@a", 42, 1, 1));
- TYPE_CHECK(ERL_IS_PORT, erl_mk_port("a@a", 42, 1));
- TYPE_CHECK(ERL_IS_REF, erl_mk_ref("a@a", 42, 1));
-
- TYPE_CHECK(ERL_IS_BINARY, erl_mk_binary("a", 1));
- TYPE_CHECK(ERL_IS_TUPLE, erl_mk_tuple(&atom, 1));
-#undef TYPE_CHECK
-
- erl_free_term(atom);
-
- report(1);
-}
-
-/*
- * Checks the extractor macros.
- */
-
-TESTCASE(extractor_macros)
-{
- ETERM* t;
-
- erl_init(NULL, 0);
-
-#ifdef NEW_ERL_INTERFACE
-#define MATCH(a, b) ((a) == (b) ? 1 : fail("bad match: " #a))
-#define STR_MATCH(a, b) (strcmp((a), (b)) ? fail("bad match: " #a) : 0)
-
- { /* Integer */
- int anInt = 0x7FFFFFFF;
- t = erl_mk_int(anInt);
- MATCH(ERL_INT_VALUE(t), anInt);
- MATCH(ERL_INT_UVALUE(t), anInt);
- erl_free_term(t);
- }
-
- { /* Float */
- double aFloat = 3.1415;
- t = erl_mk_float(aFloat);
- MATCH(ERL_FLOAT_VALUE(t), aFloat);
- erl_free_term(t);
- }
-
- { /* Atom. */
- char* aString = "nisse";
- t = erl_mk_atom(aString);
- if (memcmp(ERL_ATOM_PTR(t), aString, strlen(aString)) != 0)
- fail("bad match");
- MATCH(ERL_ATOM_SIZE(t), strlen(aString));
- erl_free_term(t);
- }
-
- { /* Pid. */
- char* node = "arne@strider";
- int number = 42;
- int serial = 5;
- int creation = 1;
-
- t = erl_mk_pid(node, number, serial, creation);
- STR_MATCH(ERL_PID_NODE(t), node);
- MATCH(ERL_PID_NUMBER(t), number);
- MATCH(ERL_PID_SERIAL(t), serial);
- MATCH(ERL_PID_CREATION(t), creation);
- erl_free_term(t);
- }
-
- { /* Port. */
- char* node = "kalle@strider";
- int number = 45;
- int creation = 1;
-
- t = erl_mk_port(node, number, creation);
- STR_MATCH(ERL_PORT_NODE(t), node);
- MATCH(ERL_PORT_NUMBER(t), number);
- MATCH(ERL_PORT_CREATION(t), creation);
- erl_free_term(t);
- }
-
- { /* Reference. */
- char* node = "kalle@strider";
- int number = 48;
- int creation = 1;
-
- t = erl_mk_ref(node, number, creation);
- STR_MATCH(ERL_REF_NODE(t), node);
- MATCH(ERL_REF_NUMBER(t), number);
- MATCH(ERL_REF_CREATION(t), creation);
- erl_free_term(t);
- }
-
- { /* Tuple. */
- ETERM* arr[2];
-
- arr[0] = erl_mk_int(51);
- arr[1] = erl_mk_int(52);
- t = erl_mk_tuple(arr, ASIZE(arr));
- MATCH(ERL_TUPLE_SIZE(t), ASIZE(arr));
- MATCH(ERL_TUPLE_ELEMENT(t, 0), arr[0]);
- MATCH(ERL_TUPLE_ELEMENT(t, 1), arr[1]);
- erl_free_array(arr, ASIZE(arr));
- erl_free_term(t);
- }
-
- { /* Binary. */
- static char bin[] = {1, 2, 3, 0, 4, 5};
-
- t = erl_mk_binary(bin, ASIZE(bin));
- MATCH(ERL_BIN_SIZE(t), ASIZE(bin));
- if (memcmp(ERL_BIN_PTR(t), bin, ASIZE(bin)) != 0)
- fail("bad match");
- erl_free_term(t);
- }
-
- {
- ETERM* head = erl_mk_atom("head");
- ETERM* tail = erl_mk_atom("tail");
-
- t = erl_cons(head, tail);
- MATCH(ERL_CONS_HEAD(t), head);
- MATCH(ERL_CONS_TAIL(t), tail);
- erl_free_term(head);
- erl_free_term(tail);
- erl_free_term(t);
- }
-#undef MATCH
-#undef STR_MATCH
-#endif
-
- report(1);
-}
-
-
-
-/***********************************************************************
- *
- * 4. I / O l i s t f u n c t i o n s
- *
- ***********************************************************************/
-
-/*
- * Invokes erl_iolist_length() on each term and send backs the result.
- */
-
-TESTCASE(t_erl_iolist_length)
-{
- erl_init(NULL, 0);
-
- for (;;) {
- ETERM* term = get_term();
-
- if (term == NULL) {
- report(1);
- return;
- } else {
-#ifndef NEW_ERL_INTERFACE
- fail("Function not present in this version of erl_interface");
-#else
- ETERM* len_term;
-
- len_term = erl_mk_int(erl_iolist_length(term));
- erl_free_term(term);
- send_term(len_term);
-#endif
- }
- }
-}
-
-/*
- * Invokes erl_iolist_to_binary() on each term and send backs the result.
- */
-
-TESTCASE(t_erl_iolist_to_binary)
-{
- erl_init(NULL, 0);
-
- for (;;) {
- ETERM* term = get_term();
-
- if (term == NULL) {
- report(1);
- return;
- } else {
-#ifndef NEW_ERL_INTERFACE
- fail("Function not present in this version of erl_interface");
-#else
- ETERM* new_term;
-
- new_term = erl_iolist_to_binary(term);
-
- erl_free_term(term);
- send_term(new_term);
-#endif
- }
- }
-}
-
-/*
- * Invokes erl_iolist_to_string() on each term and send backs the result.
- */
-
-TESTCASE(t_erl_iolist_to_string)
-{
- erl_init(NULL, 0);
-
- for (;;) {
- ETERM* term = get_term();
-
- if (term == NULL) {
- report(1);
- return;
- } else {
-#ifndef NEW_ERL_INTERFACE
- fail("Function not present in this version of erl_interface");
-#else
- char* result;
-
- result = erl_iolist_to_string(term);
- erl_free_term(term);
- if (result != NULL) {
- send_buffer(result, strlen(result)+1);
- erl_free(result);
- } else {
- send_term(NULL);
- }
-#endif
- }
- }
-}
-
-
-/***********************************************************************
- *
- * 5. M i s c e l l a n o u s T e s t s
- *
- ***********************************************************************/
-
-/*
- * Test some combinations of operations to verify that the reference pointers
- * are handled correctly.
- *
- * "Det verkar vara lite High Chaparal med minneshanteringen i erl_interface"
- * Per Lundgren, ERV.
- */
-
-TESTCASE(high_chaparal)
-{
- ETERM *L1, *A1, *L2, *A2, *L3;
-
- erl_init(NULL, 0);
-
- L1 = erl_mk_empty_list();
- A1 = erl_mk_atom("world");
- L2 = erl_cons(A1, L1);
- A2 = erl_mk_atom("hello");
- L3 = erl_cons(A2, L2);
-
- erl_free_term(L1);
- erl_free_term(A1);
- erl_free_term(L2);
- erl_free_term(A2);
-
- send_term(L3);
-
- /* already freed by send_term() */
- /* erl_free_term(L3);*/
-
- report(1);
-}
-
-/*
- * Test erl_decode to recover from broken list data (OTP-7448)
- */
-TESTCASE(broken_data)
-{
- ETERM* original;
- ETERM* new_terms;
- char encoded[16*1024];
- int n;
-
- erl_init(NULL, 0);
- original = all_types();
- if ((n=erl_encode(original, encoded)) == 0)
- {
- fail("failed to encode terms");
- } else
- {
- int offs = n/2;
- memset(encoded+offs,0,n-offs); /* destroy */
-
- if ((new_terms = erl_decode(encoded)) != NULL)
- {
- fail("decode accepted broken data");
- erl_free_term(new_terms);
- }
- }
- erl_free_term(original);
- report(1);
-}
-
-/*
- * Returns a list containing instances of all types.
- *
- * Be careful changing the contents of the list returned, because both
- * the build_terms() and decode_terms() test cases depend on it.
- */
-
-static ETERM*
-all_types(void)
-{
- ETERM* t;
- ETERM* terms[3];
- int i;
- static char a_binary[] = "A binary";
-
-#define CONS_AND_FREE(expr, tail) \
- do { \
- ETERM* term = expr; \
- ETERM* nl = erl_cons(term, tail); \
- erl_free_term(term); \
- erl_free_term(tail); \
- tail = nl; \
- } while (0)
-
- t = erl_mk_empty_list();
-
- CONS_AND_FREE(erl_mk_atom("I am an atom"), t);
- CONS_AND_FREE(erl_mk_binary("A binary", sizeof(a_binary)-1), t);
- CONS_AND_FREE(erl_mk_float(3.0), t);
- CONS_AND_FREE(erl_mk_int(0), t);
- CONS_AND_FREE(erl_mk_int(-1), t);
- CONS_AND_FREE(erl_mk_int(1), t);
-
- CONS_AND_FREE(erl_mk_string("A string"), t);
-
- terms[0] = erl_mk_atom("element1");
- terms[1] = erl_mk_int(42);
- terms[2] = erl_mk_int(767);
- CONS_AND_FREE(erl_mk_tuple(terms, ASIZE(terms)), t);
- for (i = 0; i < ASIZE(terms); i++) {
- erl_free_term(terms[i]);
- }
-
- CONS_AND_FREE(erl_mk_pid("kalle@localhost", 3, 2, 1), t);
- CONS_AND_FREE(erl_mk_pid("abcdefghijabcdefghij@localhost", 3, 2, 1), t);
- CONS_AND_FREE(erl_mk_port("kalle@localhost", 4, 1), t);
- CONS_AND_FREE(erl_mk_port("abcdefghijabcdefghij@localhost", 4, 1), t);
- CONS_AND_FREE(erl_mk_ref("kalle@localhost", 6, 1), t);
- CONS_AND_FREE(erl_mk_ref("abcdefghijabcdefghij@localhost", 6, 1), t);
- return t;
-
-#undef CONS_AND_FREE
-}
-
-/*
- * Dump (print for debugging) a term. Useful if/when things go wrong.
- */
-void
-dump_term (FILE *fp, ETERM *t)
-{
- if (fp == NULL) return;
-
- fprintf(fp, "#<%p ", t);
-
- if(t != NULL)
- {
- fprintf(fp, "count:%d, type:%d", ERL_COUNT(t), ERL_TYPE(t));
-
- switch(ERL_TYPE(t))
- {
- case ERL_UNDEF:
- fprintf(fp, "==undef");
- break;
- case ERL_INTEGER:
- fprintf(fp, "==int, val:%d", ERL_INT_VALUE(t));
- break;
- case ERL_U_INTEGER:
- fprintf(fp, "==uint, val:%u", ERL_INT_UVALUE(t));
- break;
- case ERL_FLOAT:
- fprintf(fp, "==float, val:%g", ERL_FLOAT_VALUE(t));
- break;
- case ERL_ATOM:
- fprintf(fp, "==atom, name:%p \"%s\"",
- ERL_ATOM_PTR(t), ERL_ATOM_PTR(t));
- break;
- case ERL_BINARY:
- fprintf(fp, "==binary, data:%p,%u",
- ERL_BIN_PTR(t), ERL_BIN_SIZE(t));
- break;
- case ERL_PID:
- fprintf(fp, "==pid, node:%p \"%s\"",
- ERL_PID_NODE(t), ERL_PID_NODE(t));
- break;
- case ERL_PORT:
- fprintf(fp, "==port, node:%p \"%s\"",
- ERL_PORT_NODE(t), ERL_PORT_NODE(t));
- break;
- case ERL_REF:
- fprintf(fp, "==ref, node:%p \"%s\"",
- ERL_REF_NODE(t), ERL_REF_NODE(t));
- break;
- case ERL_CONS:
- fprintf(fp, "==cons");
- fprintf(fp, ", car:");
- dump_term(fp, ERL_CONS_HEAD(t));
- fprintf(fp, ", cdr:");
- dump_term(fp, ERL_CONS_TAIL(t));
- break;
- case ERL_NIL:
- fprintf(fp, "==nil");
- break;
- case ERL_TUPLE:
- fprintf(fp, "==tuple, elems:%p,%u",
- ERL_TUPLE_ELEMS(t), ERL_TUPLE_SIZE(t));
- {
- size_t i;
- for(i = 0; i < ERL_TUPLE_SIZE(t); i++)
- {
- fprintf(fp, "elem[%u]:", i);
- dump_term(fp, ERL_TUPLE_ELEMENT(t, i));
- }
- }
- break;
- case ERL_VARIABLE:
- fprintf(fp, "==variable, name:%p \"%s\"",
- ERL_VAR_NAME(t), ERL_VAR_NAME(t));
- fprintf(fp, ", value:");
- dump_term(fp, ERL_VAR_VALUE(t));
- break;
-
- default:
- break;
- }
- }
- fprintf(fp, ">");
-}
-
diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/print_term.c b/lib/erl_interface/test/erl_eterm_SUITE_data/print_term.c
deleted file mode 100644
index 5b7cb1aec8..0000000000
--- a/lib/erl_interface/test/erl_eterm_SUITE_data/print_term.c
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-/*
- * Purpose: Test the erl_print_term() function.
- * Author: Bjorn Gustavsson
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#ifndef __WIN32__
-#include <unistd.h>
-#endif
-
-#include "erl_interface.h"
-
-#ifndef __WIN32__
-#define _O_BINARY 0
-#define _setmode(fd, mode)
-#endif
-
-#define HEADER_SIZE 2
-
-static int readn(int, unsigned char*, int);
-
-/*
- * This program doesn't use the runner, because it needs a packet
- * on input, but the result will be as a stream of bytes (since
- * erl_print_term() prints directly on a file).
- *
- * Input is a package of with a packet header size of two bytes.
- *
- * +------------------------------------------------------------+
- * | length | Encoded term... |
- * | (2 bytes) | (as given by "length") |
- * +------------------------------------------------------------+
- *
- * <------------------- length --------------------->
- *
- * This program decodes the encoded terms and passes it to
- * erl_print_term(). Then this program prints
- *
- * CR <result> LF
- *
- * and waits for a new package. <result> is the return value from
- * erl_print_term(), formatted as an ASCII string.
- */
-
-#ifdef VXWORKS
-int print_term()
-#else
-int main()
-#endif
-{
- _setmode(0, _O_BINARY);
- _setmode(1, _O_BINARY);
-
- erl_init(NULL, 0);
-
- for (;;) {
- char buf[4*1024];
- ETERM* term;
- char* message;
- int n;
-
- if (readn(0, buf, 2) <= 0) {
- /* fprintf(stderr, "error reading message header\n"); */
- /* actually this is where we leave the infinite loop */
- exit(1);
- }
- n = buf[0] * 256 + buf[1];
- if (readn(0, buf, n) < 0) {
- fprintf(stderr, "error reading message contents\n");
- exit(1);
- }
-
- term = erl_decode(buf);
- if (term == NULL) {
- fprintf(stderr, "erl_decode() failed\n");
- exit(1);
- }
- n = erl_print_term(stdout, term);
- erl_free_compound(term);
- fprintf(stdout,"\r%d\n", n);
- fflush(stdout);
- }
-}
-
-/*
- * Reads len number of bytes.
- */
-
-static int
-readn(fd, buf, len)
- int fd; /* File descriptor to read from. */
- unsigned char *buf; /* Store in this buffer. */
- int len; /* Number of bytes to read. */
-{
- int n; /* Byte count in last read call. */
- int sofar = 0; /* Bytes read so far. */
-
- do {
- if ((n = read(fd, buf+sofar, len-sofar)) <= 0)
- /* error or EOF in read */
- return(n);
- sofar += n;
- } while (sofar < len);
- return sofar;
-}
-
diff --git a/lib/erl_interface/test/erl_ext_SUITE.erl b/lib/erl_interface/test/erl_ext_SUITE.erl
deleted file mode 100644
index ff3b495f7b..0000000000
--- a/lib/erl_interface/test/erl_ext_SUITE.erl
+++ /dev/null
@@ -1,70 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2018. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
--module(erl_ext_SUITE).
-
--include_lib("common_test/include/ct.hrl").
--include("erl_ext_SUITE_data/ext_test_cases.hrl").
-
--export([all/0, suite/0,
- init_per_testcase/2,
- compare_tuple/1,
- compare_list/1,
- compare_string/1,
- compare_list_string/1,
- compare_nc_ext/1]).
-
--import(runner, [get_term/1]).
-
-suite() ->
- [{ct_hooks,[ts_install_cth]}].
-
-all() ->
- [compare_tuple, compare_list, compare_string,
- compare_list_string, compare_nc_ext].
-
-init_per_testcase(Case, Config) ->
- runner:init_per_testcase(?MODULE, Case, Config).
-
-compare_tuple(Config) when is_list(Config) ->
- P = runner:start(Config, ?compare_tuple),
- runner:recv_eot(P),
- ok.
-
-compare_list(Config) when is_list(Config) ->
- P = runner:start(Config, ?compare_list),
- runner:recv_eot(P),
- ok.
-
-compare_string(Config) when is_list(Config) ->
- P = runner:start(Config, ?compare_string),
- runner:recv_eot(P),
- ok.
-
-compare_list_string(Config) when is_list(Config) ->
- P = runner:start(Config, ?compare_list_string),
- runner:recv_eot(P),
- ok.
-
-compare_nc_ext(Config) when is_list(Config) ->
- P = runner:start(Config, ?compare_nc_ext),
- runner:recv_eot(P),
- ok.
diff --git a/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src
deleted file mode 100644
index 3f0200e862..0000000000
--- a/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2002-2020. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-
-include @erl_interface_mk_include@
-
-CC0 = @CC@
-CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
-LD = @LD@
-LIBERL = @erl_interface_lib@
-LIBEI = @erl_interface_eilib@
-LIBFLAGS = ../all_SUITE_data/runner@obj@ \
- $(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
- @erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
-EXT_OBJS = ext_test@obj@ ext_test_decl@obj@
-
-all: ext_test@exe@
-
-clean:
- $(RM) $(EXT_OBJS)
- $(RM) ext_test@exe@
-
-ext_test@exe@: $(EXT_OBJS) $(LIBERL) $(LIBEI)
- $(LD) @CROSSLDFLAGS@ -o $@ $(EXT_OBJS) $(LIBFLAGS)
diff --git a/lib/erl_interface/test/erl_ext_SUITE_data/ext_test.c b/lib/erl_interface/test/erl_ext_SUITE_data/ext_test.c
deleted file mode 100644
index 6b47c3e510..0000000000
--- a/lib/erl_interface/test/erl_ext_SUITE_data/ext_test.c
+++ /dev/null
@@ -1,510 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2002-2018. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- *
-
- */
-/*
- * Author: Rickard Green
- * Modified: Bjrn-Egil Dahlberg
- * - compare_tuple
- * - compare_string
- * - compare_list
- * - compare_list and string
- */
-
-#include "runner.h"
-#include "erl_interface.h"
-#include <stdio.h>
-#include <string.h>
-
-typedef unsigned int uint;
-
-#define MAX_NC_EXT_SIZE 100
-
-static unsigned char *
-write_pid(unsigned char *buf, char *node, uint cre, uint ser, uint num);
-static unsigned char *
-write_port(unsigned char *buf, char *node, uint cre, uint id);
-static unsigned char *
-write_ref(unsigned char *buf, char *node, uint cre, uint id[], uint no_ids);
-static void
-test_compare_ext(char *test_desc,
- unsigned char *ext1,
- unsigned char *end_ext1,
- unsigned char *ext2,
- unsigned char *end_ext2,
- int exp_res);
-
-/*
- * Test erl_compare_ext with tuples
- */
-TESTCASE(compare_tuple) {
- // erlang:term_to_binary ({'b'})
- unsigned char term1[] = { 131, 104, 1, 100, 0, 1, 98 };
- // erlang:term_to_binary ({'a', 'a'})
- unsigned char term2[] = { 131, 104, 2, 100, 0, 1, 97, 100, 0, 1, 97 };
- unsigned char *start_a, *start_b, *end_a, *end_b;
-
- erl_init(NULL, 0);
- start_a = term1;
- start_b = term2;
- end_a = term1 + 7;
- end_b = term2 + 11;
-
- test_compare_ext("tuples", start_a, end_a, start_b, end_b, -1);
-
- report(1);
-}
-
-/*
- * Test erl_compare_ext with lists
- */
-
-TESTCASE(compare_list) {
- unsigned char *start_a, *start_b, *end_a, *end_b;
- // erlang:term_to_binary([a,b,[],3412])
- unsigned char term1[] = {131,108,0,0,0,4,100,0,1,97,100,0,1,98,106,98,0,0,13,84,106};
- // erlang:term_to_binary([34,{a,n},a,erlang])
- unsigned char term2[] = {131,108,0,0,0,4,97,34,104,2,100,0,1,97,100,0,1,110,100,0,1,97,100,0,6,101,114,108,97,110,103,106};
-
- // erlang:term_to_binary([0])
- unsigned char term3[] = {131,107,0,1,0};
- // erlang:term_to_binary([0, 1000])
- unsigned char term4[] = {131,108,0,0,0,2,97,0,98,0,0,3,232,106};
-
- // erlang:term_to_binary([a|b])
- unsigned char term5a[] = {131,108,0,0,0,1,100,0,1,97,100,0,1,98};
- // erlang:term_to_binary([a|c])
- unsigned char term5b[] = {131,108,0,0,0,1,100,0,1,97,100,0,1,99};
-
- erl_init(NULL, 0);
- start_a = term1;
- start_b = term2;
- end_a = term1 + 21;
- end_b = term2 + 32;
-
- test_compare_ext("lists", start_a, end_a, start_b, end_b, 1);
-
- start_a = term3;
- start_b = term4;
- end_a = term3 + sizeof(term3);
- end_b = term4 + sizeof(term4);
-
- test_compare_ext("lists1", start_a, end_a, start_b, end_b, -1);
-
- start_a = term5a;
- start_b = term5b;
- end_a = term5a + sizeof(term5a);
- end_b = term5b + sizeof(term5b);
-
- test_compare_ext("lists5", start_a, end_a, start_b, end_b, -1);
-
- report(1);
-}
-
-/*
- * Test erl_compare_ext with strings
- */
-
-TESTCASE(compare_string) {
- unsigned char *start_a, *start_b, *end_a, *end_b;
- // erlang:term_to_binary("hej")
- unsigned char term1[] = {131,107,0,3,104,101,106};
- // erlang:term_to_binary("erlang")
- unsigned char term2[] = {131,107,0,6,101,114,108,97,110,103};
-
- erl_init(NULL, 0);
- start_a = term1;
- start_b = term2;
- end_a = term1 + 7;
- end_b = term2 + 10;
-
- test_compare_ext("strings", start_a, end_a, start_b, end_b, 1);
-
- report(1);
-}
-
-/*
- * Test erl_compare_ext with lists and strings
- */
-
-TESTCASE(compare_list_string) {
- unsigned char *start_a, *start_b, *end_a, *end_b;
- // erlang:term_to_binary("hej")
- unsigned char term1[] = {131,107,0,3,104,101,106};
- // erlang:term_to_binary([a,b,[],3412])
- unsigned char term2[] = {131,108,0,0,0,4,100,0,1,97,100,0,1,98,106,98,0,0,13,84,106};
-
- erl_init(NULL, 0);
- start_a = term1;
- start_b = term2;
- end_a = term1 + 7;
- end_b = term2 + 21;
-
- test_compare_ext("strings", start_a, end_a, start_b, end_b, -1);
-
- report(1);
-}
-
-
-
-/*
- * Test erl_compare_ext with node containers
- */
-TESTCASE(compare_nc_ext)
-{
- int res;
- unsigned char buf_a[MAX_NC_EXT_SIZE], buf_b[MAX_NC_EXT_SIZE];
- unsigned char *end_a, *end_b;
- uint id[3];
-
- erl_init(NULL, 0);
-
-
- /*
- * Test pids ----------------------------------------------------
- *
- * Significance (most -> least):
- * nodename, creation, serial, number, nodename, creation
- *
- */
-
- end_a = write_pid(buf_a, "b@b", 2, 4711, 1);
-
- end_b = write_pid(buf_b, "a@b", 1, 4710, 2);
- test_compare_ext("pid test 1", buf_a, end_a, buf_b, end_b, -1);
-
- end_b = write_pid(buf_b, "a@b", 1, 4712, 1);
- test_compare_ext("pid test 2", buf_a, end_a, buf_b, end_b, -1);
-
- end_b = write_pid(buf_b, "c@b", 1, 4711, 1);
- test_compare_ext("pid test 3", buf_a, end_a, buf_b, end_b, -1);
-
- end_b = write_pid(buf_b, "b@b", 3, 4711, 1);
- test_compare_ext("pid test 4", buf_a, end_a, buf_b, end_b, -1);
-
- end_b = write_pid(buf_b, "b@b", 2, 4711, 1);
- test_compare_ext("pid test 5", buf_a, end_a, buf_b, end_b, 0);
-
-
- /*
- * Test ports ---------------------------------------------------
- *
- * Significance (most -> least):
- * nodename, creation, number
- *
- * OBS: Comparison between ports has changed in R9. This
- * since it wasn't stable in R8 (and eariler releases).
- * Significance used to be: dist_slot, number,
- * creation.
- */
-
- end_a = write_port(buf_a, "b@b", 2, 4711),
-
- end_b = write_port(buf_b, "c@b", 1, 4710);
- test_compare_ext("port test 1", buf_a, end_a, buf_b, end_b, -1);
-
- end_b = write_port(buf_b, "b@b", 3, 4710);
- test_compare_ext("port test 2", buf_a, end_a, buf_b, end_b, -1);
-
- end_b = write_port(buf_b, "b@b", 2, 4712);
- test_compare_ext("port test 3", buf_a, end_a, buf_b, end_b, -1);
-
- end_b = write_port(buf_b, "b@b", 2, 4711);
- test_compare_ext("port test 4", buf_a, end_a, buf_b, end_b, 0);
-
- /*
- * Test refs ----------------------------------------------------
- * Significance (most -> least):
- * nodename, creation, (number high, number mid), number low,
- *
- * OBS: Comparison between refs has changed in R9. This
- * since it wasn't stable in R8 (and eariler releases).
- * Significance used to be: dist_slot, number,
- * creation.
- *
- */
-
- /* Long & Long */
-
- id[0] = 4711; id[1] = 4711, id[2] = 4711;
- end_a = write_ref(buf_a, "b@b", 2, id, 3);
-
-
- id[0] = 4710; id[1] = 4710; id[2] = 4710;
- end_b = write_ref(buf_b, "c@b", 1, id, 3);
- test_compare_ext("ref test 1", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4710; id[1] = 4710; id[2] = 4710;
- end_b = write_ref(buf_b, "b@b", 3, id, 3);
- test_compare_ext("ref test 2", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4710; id[1] = 4710; id[2] = 4712;
- end_b = write_ref(buf_b, "b@b", 2, id, 3);
- test_compare_ext("ref test 3", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4710; id[1] = 4712; id[2] = 4711;
- end_b = write_ref(buf_b, "b@b", 2, id, 3);
- test_compare_ext("ref test 4", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4712; id[1] = 4711; id[2] = 4711;
- end_b = write_ref(buf_b, "b@b", 2, id, 3);
- test_compare_ext("ref test 5", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4711; id[1] = 4711; id[2] = 4711;
- end_b = write_ref(buf_b, "b@b", 2, id, 3);
- test_compare_ext("ref test 6", buf_a, end_a, buf_b, end_b, 0);
-
- /* Long & Short */
- id[0] = 4711; id[1] = 0, id[2] = 0;
- end_a = write_ref(buf_a, "b@b", 2, id, 3);
-
-
- id[0] = 4710;
- end_b = write_ref(buf_b, "c@b", 1, id, 1);
- test_compare_ext("ref test 7", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4710;
- end_b = write_ref(buf_b, "b@b", 3, id, 1);
- test_compare_ext("ref test 8", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4712;
- end_b = write_ref(buf_b, "b@b", 2, id, 1);
- test_compare_ext("ref test 9", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4711;
- end_b = write_ref(buf_b, "b@b", 2, id, 1);
- test_compare_ext("ref test 10", buf_a, end_a, buf_b, end_b, 0);
-
- /* Short & Long */
- id[0] = 4711;
- end_a = write_ref(buf_a, "b@b", 2, id, 1);
-
-
- id[0] = 4710; id[1] = 0, id[2] = 0;
- end_b = write_ref(buf_b, "c@b", 1, id, 3);
- test_compare_ext("ref test 11", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4710; id[1] = 0, id[2] = 0;
- end_b = write_ref(buf_b, "b@b", 3, id, 3);
- test_compare_ext("ref test 12", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4712; id[1] = 0, id[2] = 0;
- end_b = write_ref(buf_b, "b@b", 2, id, 3);
- test_compare_ext("ref test 13", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4711; id[1] = 0, id[2] = 0;
- end_b = write_ref(buf_b, "b@b", 2, id, 3);
- test_compare_ext("ref test 14", buf_a, end_a, buf_b, end_b, 0);
-
- /* Short & Short */
- id[0] = 4711;
- end_a = write_ref(buf_a, "b@b", 2, id, 1);
-
-
- id[0] = 4710;
- end_b = write_ref(buf_b, "c@b", 1, id, 1);
- test_compare_ext("ref test 15", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4710;
- end_b = write_ref(buf_b, "b@b", 3, id, 1);
- test_compare_ext("ref test 16", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4712;
- end_b = write_ref(buf_b, "b@b", 2, id, 1);
- test_compare_ext("ref test 17", buf_a, end_a, buf_b, end_b, -1);
-
- id[0] = 4711;
- end_b = write_ref(buf_b, "b@b", 2, id, 1);
- test_compare_ext("ref test 18", buf_a, end_a, buf_b, end_b, 0);
-
- report(1);
-}
-
-static void
-test_compare_ext(char *test_desc,
- unsigned char *ext1,
- unsigned char *end_ext1,
- unsigned char *ext2,
- unsigned char *end_ext2,
- int exp_res)
-{
- int er, ar;
- unsigned char *e1, *e2;
- int reversed_args;
- char ext_str[MAX_NC_EXT_SIZE*4 + 1];
- char *es;
-
- message("*** %s ***", test_desc);
- message(" erl_compare_ext() arguments:", test_desc);
-
- es = &ext_str[0];
-
- e1 = ext1;
- while (e1 < end_ext1)
- es += sprintf(es, "%d,", *(e1++));
- *(--es) = '\0';
- message(" e1 = <<%s>>", ext_str);
-
-
- es = &ext_str[0];
-
- e2 = ext2;
- while (e2 < end_ext2)
- es += sprintf(es, "%d,", *(e2++));
- *(--es) = '\0';
- message(" e2 = <<%s>>", ext_str);
-
- message("Starting %s...", test_desc);
-
-
- reversed_args = 0;
- er = exp_res;
- e1 = ext1;
- e2 = ext2;
-
- reversed_args_start:
-
- ar = erl_compare_ext(e1, e2);
- if (er < 0) {
- if (ar > 0)
- fail("expected result e1 < e2; actual result e1 > e2\n");
- else if (ar == 0)
- fail("expected result e1 < e2; actual result e1 = e2\n");
- }
- else if (er > 0) {
- if (ar < 0)
- fail("expected result e1 > e2; actual result e1 < e2\n");
- else if (ar == 0)
- fail("expected result e1 > e2; actual result e1 = e2\n");
- }
- else {
- if (ar > 0)
- fail("expected result e1 = e2; actual result e1 > e2\n");
- else if (ar < 0)
- fail("expected result e1 = e2; actual result e1 < e2\n");
- }
-
- message("%s", "SUCCEEDED!");
- if (!reversed_args) {
- message("Starting %s with reversed arguments...", test_desc);
- e2 = ext1;
- e1 = ext2;
- if (exp_res < 0)
- er = 1;
- else if (exp_res > 0)
- er = -1;
- reversed_args = 1;
- goto reversed_args_start;
- }
-
- message("%s", "");
-
-}
-
-
-#define SMALL_ATOM_UTF8_EXT (119)
-#define REFERENCE_EXT (101)
-#define PORT_EXT (102)
-#define PID_EXT (103)
-#define NEW_REFERENCE_EXT (114)
-
-
-#define PUT_UINT16(E, X) ((E)[0] = ((X) >> 8) & 0xff, \
- (E)[1] = (X) & 0xff)
-
-#define PUT_UINT32(E, X) ((E)[0] = ((X) >> 24) & 0xff, \
- (E)[1] = ((X) >> 16) & 0xff, \
- (E)[2] = ((X) >> 8) & 0xff, \
- (E)[3] = (X) & 0xff)
-
-static unsigned char *
-write_atom(unsigned char *buf, char *atom)
-{
- uint len;
-
- len = 0;
- while(atom[len]) {
- buf[len + 2] = atom[len];
- len++;
- }
- buf[0] = SMALL_ATOM_UTF8_EXT;
- buf[1] = len;
-
- return buf + 2 + len;
-}
-
-static unsigned char *
-write_pid(unsigned char *buf, char *node, uint cre, uint num, uint ser)
-{
- unsigned char *e = buf;
-
- *(e++) = PID_EXT;
- e = write_atom(e, node);
- PUT_UINT32(e, num & ((1 << 15) - 1));
- e += 4;
- PUT_UINT32(e, ser & ((1 << 3) - 1));
- e += 4;
- *(e++) = cre & ((1 << 2) - 1);
-
- return e;
-}
-
-static unsigned char *
-write_port(unsigned char *buf, char *node, uint cre, uint id)
-{
- unsigned char *e = buf;
-
- *(e++) = PORT_EXT;
- e = write_atom(e, node);
- PUT_UINT32(e, id & ((1 << 15) - 1));
- e += 4;
- *(e++) = cre & ((1 << 2) - 1);
-
- return e;
-}
-
-static unsigned char *
-write_ref(unsigned char *buf, char *node, uint cre, uint id[], uint no_ids)
-{
- int i;
- unsigned char *e = buf;
-
- if (no_ids == 1) {
- *(e++) = REFERENCE_EXT;
- e = write_atom(e, node);
- PUT_UINT32(e, id[0] & ((1 << 15) - 1));
- e += 4;
- *(e++) = cre & ((1 << 2) - 1);
- }
- else {
- *(e++) = NEW_REFERENCE_EXT;
- PUT_UINT16(e, no_ids);
- e += 2;
- e = write_atom(e, node);
- *(e++) = cre & ((1 << 2) - 1);
- for (i = 0; i < no_ids; i++) {
- PUT_UINT32(e, id[i]);
- e += 4;
- }
- }
-
- return e;
-}
-
diff --git a/lib/erl_interface/test/erl_format_SUITE.erl b/lib/erl_interface/test/erl_format_SUITE.erl
deleted file mode 100644
index 69dfdcc4c8..0000000000
--- a/lib/erl_interface/test/erl_format_SUITE.erl
+++ /dev/null
@@ -1,135 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
--module(erl_format_SUITE).
-
--include_lib("common_test/include/ct.hrl").
--include("erl_format_SUITE_data/format_test_cases.hrl").
-
--export([all/0, suite/0,
- init_per_testcase/2,
- atoms/1, tuples/1, lists/1]).
-
--import(runner, [get_term/1]).
-
-%% This test suite test the erl_format() function.
-%% It uses the port program "format_test".
-
-suite() ->
- [{ct_hooks,[ts_install_cth]}].
-
-all() ->
- [atoms, tuples, lists].
-
-init_per_testcase(Case, Config) ->
- runner:init_per_testcase(?MODULE, Case, Config).
-
-%% Tests formatting various atoms.
-
-atoms(Config) when is_list(Config) ->
- P = runner:start(Config, ?atoms),
-
- {term, ''} = get_term(P),
- {term, 'a'} = get_term(P),
- {term, 'A'} = get_term(P),
- {term, 'abc'} = get_term(P),
- {term, 'Abc'} = get_term(P),
- {term, 'ab@c'} = get_term(P),
- {term, 'The rain in Spain stays mainly in the plains'} = get_term(P),
-
- {term, a} = get_term(P),
- {term, ab} = get_term(P),
- {term, abc} = get_term(P),
- {term, ab@c} = get_term(P),
- {term, abcdefghijklmnopq} = get_term(P),
-
- {term, ''} = get_term(P),
- {term, 'a'} = get_term(P),
- {term, 'A'} = get_term(P),
- {term, 'abc'} = get_term(P),
- {term, 'Abc'} = get_term(P),
- {term, 'ab@c'} = get_term(P),
- {term, 'The rain in Spain stays mainly in the plains'} = get_term(P),
-
- {term, a} = get_term(P),
- {term, ab} = get_term(P),
- {term, abc} = get_term(P),
- {term, ab@c} = get_term(P),
- {term, ' abcdefghijklmnopq '} = get_term(P),
-
- runner:recv_eot(P),
- ok.
-
-
-
-%% Tests formatting various tuples
-
-tuples(Config) when is_list(Config) ->
- P = runner:start(Config, ?tuples),
-
- {term, {}} = get_term(P),
- {term, {a}} = get_term(P),
- {term, {a, b}} = get_term(P),
- {term, {a, b, c}} = get_term(P),
- {term, {1}} = get_term(P),
- {term, {[]}} = get_term(P),
- {term, {[], []}} = get_term(P),
- {term, {[], a, b, c}} = get_term(P),
- {term, {[], a, [], b, c}} = get_term(P),
- {term, {[], a, '', b, c}} = get_term(P),
-
- runner:recv_eot(P),
- ok.
-
-
-
-%% Tests formatting various lists
-
-lists(Config) when is_list(Config) ->
- P = runner:start(Config, ?lists),
-
- {term, []} = get_term(P),
- {term, [a]} = get_term(P),
- {term, [a, b]} = get_term(P),
- {term, [a, b, c]} = get_term(P),
- {term, [1]} = get_term(P),
- {term, [[]]} = get_term(P),
- {term, [[], []]} = get_term(P),
- {term, [[], a, b, c]} = get_term(P),
- {term, [[], a, [], b, c]} = get_term(P),
- {term, [[], a, '', b, c]} = get_term(P),
-
- {term, [{name, 'Madonna'}, {age, 21}, {data, [{addr, "E-street", 42}]}]} = get_term(P),
- case os:type() of
- vxworks ->
- {term, [{pi, _}, {'cos(70)', _}]} = get_term(P),
- {term, [[pi, _], ['cos(70)', _]]} = get_term(P),
- {term, [[pi, _], [], ["cos(70)", _]]} = get_term(P);
- _ ->
- {term, [{pi, 3.1415}, {'cos(70)', 0.34202}]} = get_term(P),
- {term, [[pi, 3.1415], ['cos(70)', 0.34202]]} = get_term(P),
- {term, [[pi, 3.1415], [], ["cos(70)", 0.34202]]} = get_term(P)
- end,
-
- {term, [-1]} = get_term(P),
-
- runner:recv_eot(P),
- ok.
diff --git a/lib/erl_interface/test/erl_format_SUITE_data/Makefile.first b/lib/erl_interface/test/erl_format_SUITE_data/Makefile.first
deleted file mode 100644
index acbb8c98bb..0000000000
--- a/lib/erl_interface/test/erl_format_SUITE_data/Makefile.first
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2000-2016. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-
-format_test_decl.c: format_test.c
- erl -noinput -pa ../all_SUITE_data -s init_tc run format_test -s erlang halt
diff --git a/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src
deleted file mode 100644
index 265d5fe01e..0000000000
--- a/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src
+++ /dev/null
@@ -1,43 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2020. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-
-include @erl_interface_mk_include@
-
-CC0 = @CC@
-CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
-LD = @LD@
-LIBERL = @erl_interface_lib@
-LIBEI = @erl_interface_eilib@
-LIBFLAGS = ../all_SUITE_data/runner@obj@ \
- $(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
- @erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
-FORMAT_OBJS = format_test@obj@ format_test_decl@obj@
-
-all: format_test@exe@
-
-clean:
- $(RM) $(FORMAT_OBJS)
- $(RM) format_test@exe@
-
-format_test@exe@: $(FORMAT_OBJS) $(LIBERL) $(LIBEI)
- $(LD) @CROSSLDFLAGS@ -o $@ $(FORMAT_OBJS) $(LIBFLAGS)
-
-
diff --git a/lib/erl_interface/test/erl_format_SUITE_data/format_test.c b/lib/erl_interface/test/erl_format_SUITE_data/format_test.c
deleted file mode 100644
index 258ae92e0f..0000000000
--- a/lib/erl_interface/test/erl_format_SUITE_data/format_test.c
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#include "runner.h"
-
-/*
- * Purpose: Tests the erl_format() function.
- * Author: Bjorn Gustavsson
- */
-
-static void
-send_format(char* format)
-{
- send_term(erl_format(format));
-}
-
-TESTCASE(atoms)
-{
- erl_init(NULL, 0);
-
- send_format("''");
- send_format("'a'");
- send_format("'A'");
- send_format("'abc'");
- send_format("'Abc'");
- send_format("'ab@c'");
- send_format("'The rain in Spain stays mainly in the plains'");
-
- send_format("a");
- send_format("ab");
- send_format("abc");
- send_format("ab@c");
- send_format(" abcdefghijklmnopq ");
-
- send_term(erl_format("~a", ""));
- send_term(erl_format("~a", "a"));
- send_term(erl_format("~a", "A"));
- send_term(erl_format("~a", "abc"));
- send_term(erl_format("~a", "Abc"));
- send_term(erl_format("~a", "ab@c"));
- send_term(erl_format("~a", "The rain in Spain stays mainly in the plains"));
-
- send_term(erl_format("~a", "a"));
- send_term(erl_format("~a", "ab"));
- send_term(erl_format("~a", "abc"));
- send_term(erl_format("~a","ab@c"));
- send_term(erl_format("~a", " abcdefghijklmnopq "));
-
-
- report(1);
-}
-
-TESTCASE(tuples)
-{
- erl_init(NULL, 0);
-
- send_format("{}");
- send_format("{a}");
- send_format("{a, b}");
- send_format("{a, b, c}");
- send_format("{1}");
- send_format("{[]}");
- send_format("{[], []}");
- send_format("{[], a, b, c}");
- send_format("{[], a, [], b, c}");
- send_format("{[], a, '', b, c}");
-
- report(1);
-}
-
-
-
-TESTCASE(lists)
-{
- ETERM* a;
- ETERM* b;
- ETERM* c;
-
- erl_init(NULL, 0);
-
- send_format("[]");
- send_format("[a]");
- send_format("[a, b]");
- send_format("[a, b, c]");
- send_format("[1]");
- send_format("[[]]");
- send_format("[[], []]");
- send_format("[[], a, b, c]");
- send_format("[[], a, [], b, c]");
- send_format("[[], a, '', b, c]");
-
- b = erl_format("[{addr, ~s, ~i}]", "E-street", 42);
- a = erl_format("[{name, ~a}, {age, ~i}, {data, ~w}]", "Madonna", 21, b);
- send_term(a);
- erl_free_term(b);
-
- send_term(erl_format("[{pi, ~f}, {'cos(70)', ~f}]", 3.1415, 0.34202));
-
- a = erl_mk_float(3.1415);
- b = erl_mk_float(0.34202);
- send_term(erl_format("[[pi, ~w], ['cos(70)', ~w]]", a, b));
- erl_free_term(a);
- erl_free_term(b);
-
- a = erl_mk_float(3.1415);
- b = erl_mk_float(0.34202);
- c = erl_mk_empty_list();
- send_term(erl_format("[[~a, ~w], ~w, [~s, ~w]]", "pi", a, c, "cos(70)", b));
- erl_free_term(a);
- erl_free_term(b);
- erl_free_term(c);
-
- send_term(erl_format("[~i]", -1));
-
- report(1);
-}
diff --git a/lib/erl_interface/test/erl_global_SUITE_data/erl_global_test.c b/lib/erl_interface/test/erl_global_SUITE_data/erl_global_test.c
deleted file mode 100644
index 0f08727225..0000000000
--- a/lib/erl_interface/test/erl_global_SUITE_data/erl_global_test.c
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2000-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-/*
- * Purpose: Tests the functions in erl_global.c.
- *
- * See the erl_global_SUITE.erl file for a "table of contents".
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "runner.h"
-
-static void cmd_erl_connect(ETERM* args);
-static void cmd_erl_global_register(ETERM *args);
-static void cmd_erl_global_whereis(ETERM *args);
-static void cmd_erl_global_names(ETERM *args);
-static void cmd_erl_global_unregister(ETERM *args);
-static void cmd_erl_close_connection(ETERM *args);
-
-static void send_errno_result(int value);
-
-static struct {
- char* name;
- int num_args; /* Number of arguments. */
- void (*func)(ETERM* args);
-} commands[] = {
- "erl_connect", 4, cmd_erl_connect,
- "erl_close_connection", 1, cmd_erl_close_connection,
- "erl_global_register", 2, cmd_erl_global_register,
- "erl_global_whereis", 2, cmd_erl_global_whereis,
- "erl_global_names", 1, cmd_erl_global_names,
- "erl_global_unregister", 2, cmd_erl_global_unregister,
-};
-
-
-/*
- * Sends a list contaning all data types to the Erlang side.
- */
-
-TESTCASE(interpret)
-{
- ETERM* term;
-
- erl_init(NULL, 0);
-
- outer_loop:
-
- term = get_term();
-
- if (term == NULL) {
- report(1);
- return;
- } else {
- ETERM* Func;
- ETERM* Args;
- int i;
-
- if (!ERL_IS_TUPLE(term) || ERL_TUPLE_SIZE(term) != 2) {
- fail("term should be a tuple of size 2");
- }
-
- Func = erl_element(1, term);
- if (!ERL_IS_ATOM(Func)) {
- fail("function name should be an atom");
- }
- Args = erl_element(2, term);
- if (!ERL_IS_TUPLE(Args)) {
- fail("function arguments should be a tuple");
- }
- erl_free_term(term);
- for (i = 0; i < sizeof(commands)/sizeof(commands[0]); i++) {
- int n = strlen(commands[i].name);
- if (ERL_ATOM_SIZE(Func) != n) {
- continue;
- }
- if (memcmp(ERL_ATOM_PTR(Func), commands[i].name, n) == 0) {
- erl_free_term(Func);
- if (ERL_TUPLE_SIZE(Args) != commands[i].num_args) {
- fail("wrong number of arguments");
- }
- commands[i].func(Args);
- erl_free_term(Args);
- goto outer_loop;
- }
- }
- fail("bad command");
- }
-}
-
-#define VERIFY_TYPE(Test, Term) \
-if (!Test(Term)) { \
- fail("wrong type for " #Term); \
-} else { \
-}
-
-static void
-cmd_erl_connect(ETERM* args)
-{
- ETERM* number;
- ETERM* node;
- ETERM* cookie;
-
- int res;
- char buffer[256];
-
- number = ERL_TUPLE_ELEMENT(args, 0);
- VERIFY_TYPE(ERL_IS_INTEGER, number);
- node = ERL_TUPLE_ELEMENT(args, 1);
- VERIFY_TYPE(ERL_IS_ATOM, node);
- cookie = ERL_TUPLE_ELEMENT(args, 2);
- VERIFY_TYPE(ERL_IS_ATOM, cookie);
-
- if (ERL_ATOM_SIZE(cookie) == 0) {
- res = erl_connect_init(ERL_INT_VALUE(number), 0, 0);
- } else {
- memcpy(buffer, ERL_ATOM_PTR(cookie), ERL_ATOM_SIZE(cookie));
- buffer[ERL_ATOM_SIZE(cookie)] = '\0';
- res = erl_connect_init(ERL_INT_VALUE(number), buffer, 0);
- }
-
- if(!res) {
- send_errno_result(res);
- return;
- }
-
- memcpy(buffer, ERL_ATOM_PTR(node), ERL_ATOM_SIZE(node));
- buffer[ERL_ATOM_SIZE(node)] = '\0';
- send_errno_result(erl_connect(buffer));
-}
-
-static void
-cmd_erl_close_connection(ETERM* args)
-{
- ETERM* number;
- ETERM* res;
-
- number = ERL_TUPLE_ELEMENT(args, 0);
- VERIFY_TYPE(ERL_IS_INTEGER, number);
- res = erl_mk_int(erl_close_connection(ERL_INT_VALUE(number)));
- send_term(res);
- erl_free_term(res);
-}
-
-static void
-cmd_erl_global_register(ETERM* args)
-{
- ETERM* fd_term = ERL_TUPLE_ELEMENT(args, 0);
- ETERM* name = ERL_TUPLE_ELEMENT(args, 1);
- ETERM* pid = erl_mk_pid(erl_thisnodename(), 14, 0, 0);
-
- char buffer[256];
-
- VERIFY_TYPE(ERL_IS_INTEGER, fd_term);
- VERIFY_TYPE(ERL_IS_ATOM, name);
-
- memcpy(buffer, ERL_ATOM_PTR(name), ERL_ATOM_SIZE(name));
- buffer[ERL_ATOM_SIZE(name)] = '\0';
-
- send_errno_result(erl_global_register(ERL_INT_VALUE(fd_term), buffer, pid));
- erl_free_term(pid);
-}
-
-static void
-cmd_erl_global_whereis(ETERM* args)
-{
- ETERM* fd_term = ERL_TUPLE_ELEMENT(args, 0);
- ETERM* name = ERL_TUPLE_ELEMENT(args, 1);
- ETERM* pid = NULL;
-
- char buffer[256];
-
- VERIFY_TYPE(ERL_IS_INTEGER, fd_term);
- VERIFY_TYPE(ERL_IS_ATOM, name);
-
- memcpy(buffer, ERL_ATOM_PTR(name), ERL_ATOM_SIZE(name));
- buffer[ERL_ATOM_SIZE(name)] = '\0';
-
- pid = erl_global_whereis(ERL_INT_VALUE(fd_term), buffer, NULL);
- send_term(pid);
- erl_free_term(pid);
-}
-
-static void
-cmd_erl_global_names(ETERM* args)
-{
- ETERM* fd_term = ERL_TUPLE_ELEMENT(args, 0);
-
- ETERM* res_array[2], *res_tuple, *name;
- char** names = NULL;
- int count = 0, i;
-
- VERIFY_TYPE(ERL_IS_INTEGER, fd_term);
-
- names = erl_global_names(ERL_INT_VALUE(fd_term), &count);
-
- res_array[0] = erl_mk_empty_list();
- for(i=0; i<count; i++) {
- name = erl_mk_string(names[i]);
- res_array[0] = erl_cons(name, res_array[0]);
- }
-
- free(names);
-
- res_array[1] = erl_mk_int(count);
- res_tuple = erl_mk_tuple(res_array, 2);
-
- send_term(res_tuple);
-
- erl_free_compound(res_array[0]);
- erl_free_term(res_array[1]);
- erl_free_term(res_tuple);
-}
-
-static void
-cmd_erl_global_unregister(ETERM* args)
-{
- ETERM* fd_term = ERL_TUPLE_ELEMENT(args, 0);
- ETERM* name = ERL_TUPLE_ELEMENT(args, 1);
-
- char buffer[256];
-
- VERIFY_TYPE(ERL_IS_INTEGER, fd_term);
- VERIFY_TYPE(ERL_IS_ATOM, name);
-
- memcpy(buffer, ERL_ATOM_PTR(name), ERL_ATOM_SIZE(name));
- buffer[ERL_ATOM_SIZE(name)] = '\0';
-
- send_errno_result(erl_global_unregister(ERL_INT_VALUE(fd_term), buffer));
-}
-
-static void
-send_errno_result(int value)
-{
- ETERM* res_array[2];
- ETERM* res_tuple;
-
- res_array[0] = erl_mk_int(value);
- res_array[1] = erl_mk_int(erl_errno);
- res_tuple = erl_mk_tuple(res_array, 2);
- send_term(res_tuple);
- erl_free_term(res_array[0]);
- erl_free_term(res_array[1]);
- erl_free_term(res_tuple);
-}
diff --git a/lib/erl_interface/test/erl_match_SUITE.erl b/lib/erl_interface/test/erl_match_SUITE.erl
deleted file mode 100644
index bb62d6288d..0000000000
--- a/lib/erl_interface/test/erl_match_SUITE.erl
+++ /dev/null
@@ -1,280 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
--module(erl_match_SUITE).
-
--include_lib("common_test/include/ct.hrl").
--include("erl_match_SUITE_data/match_test_cases.hrl").
-
--export([all/0, suite/0,
- init_per_testcase/2,
- atoms/1, lists/1, tuples/1, references/1, pids/1, ports/1,
- bind/1, integers/1, floats/1, binaries/1, strings/1]).
-
-%% For interactive running of matcher.
--export([start_matcher/1, erl_match/3]).
-
-%% This test suite tests the erl_match() function.
-
-suite() ->
- [{ct_hooks,[ts_install_cth]}].
-
-all() ->
- [atoms, lists, tuples, references, pids, ports, bind,
- integers, floats, binaries, strings].
-
-init_per_testcase(Case, Config) ->
- runner:init_per_testcase(?MODULE, Case, Config).
-
-atoms(Config) when is_list(Config) ->
- P = start_matcher(Config),
-
- eq(P, '', ''),
- eq(P, a, a),
- ne(P, a, b),
- ne(P, a, aa),
- eq(P, kalle, kalle),
- ne(P, kalle, arne),
-
- ne(P, kalle, 42),
- ne(P, 42, kalle),
-
- runner:finish(P),
- ok.
-
-lists(Config) when is_list(Config) ->
- P = start_matcher(Config),
- eq(P, [], []),
-
- ne(P, [], [a]),
- ne(P, [a], []),
-
- eq(P, [a], [a]),
- ne(P, [a], [b]),
-
- eq(P, [a|b], [a|b]),
- ne(P, [a|b], [a|x]),
-
- eq(P, [a, b], [a, b]),
- ne(P, [a, b], [a, x]),
-
- eq(P, [a, b, c], [a, b, c]),
- ne(P, [a, b|c], [a, b|x]),
- ne(P, [a, b, c], [a, b, x]),
- ne(P, [a, b|c], [a, b|x]),
- ne(P, [a, x|c], [a, b|c]),
- ne(P, [a, b, c], [a, x, c]),
-
- runner:finish(P),
- ok.
-
-tuples(Config) when is_list(Config) ->
- P = start_matcher(Config),
-
- ne(P, {}, {a, b}),
- ne(P, {a, b}, {}),
- ne(P, {a}, {a, b}),
- ne(P, {a, b}, {a}),
-
- eq(P, {}, {}),
-
- eq(P, {a}, {a}),
- ne(P, {a}, {b}),
-
- eq(P, {1}, {1}),
- ne(P, {1}, {2}),
-
- eq(P, {a, b}, {a, b}),
- ne(P, {x, b}, {a, b}),
-
- ne(P, {error, x}, {error, y}),
- ne(P, {error, {undefined, {subscriber, last}}},
- {error, {undefined, {subscriber, name}}}),
-
- runner:finish(P),
- ok.
-
-
-references(Config) when is_list(Config) ->
- P = start_matcher(Config),
- Ref1 = make_ref(),
- Ref2 = make_ref(),
-
- eq(P, Ref1, Ref1),
- eq(P, Ref2, Ref2),
- ne(P, Ref1, Ref2),
- ne(P, Ref2, Ref1),
-
- runner:finish(P),
- ok.
-
-
-pids(Config) when is_list(Config) ->
- P = start_matcher(Config),
- Pid1 = c:pid(0,1,2),
- Pid2 = c:pid(0,1,3),
-
- eq(P, self(), self()),
- eq(P, Pid1, Pid1),
- ne(P, Pid1, self()),
- ne(P, Pid2, Pid1),
-
- runner:finish(P),
- ok.
-
-
-ports(Config) when is_list(Config) ->
- case os:type() of
- vxworks ->
- {skipped,"not on vxworks, pucko"};
- _ ->
- P = start_matcher(Config),
- P2 = start_matcher(Config),
-
- eq(P, P, P),
- ne(P, P, P2),
-
- runner:finish(P),
- runner:finish(P2),
- ok
- end.
-
-integers(Config) when is_list(Config) ->
- P = start_matcher(Config),
- I1 = 123,
- I2 = 12345,
- I3 = -123,
- I4 = 2234,
-
- eq(P, I1, I1),
- eq(P, I2, I2),
- ne(P, I1, I2),
- ne(P, I1, I3),
- eq(P, I4, I4),
-
- runner:finish(P),
- ok.
-
-
-
-floats(Config) when is_list(Config) ->
- P = start_matcher(Config),
- F1 = 3.1414,
- F2 = 3.1415,
- F3 = 3.1416,
-
- S1 = "string",
- S2 = "string2",
-
- eq(P, F1, F1),
- eq(P, F2, F2),
- ne(P, F1, F2),
- ne(P, F3, F2),
-
- eq(P, S2, S2),
- ne(P, S1, S2),
-
- runner:finish(P),
- ok.
-
-
-
-binaries(Config) when is_list(Config) ->
- P = start_matcher(Config),
- Bin1 = term_to_binary({kalle, 146015, {kungsgatan, 23}}),
- Bin2 = term_to_binary(sune),
- Bin3 = list_to_binary("sune"),
-
- eq(P, Bin1, Bin1),
- eq(P, Bin2, Bin2),
- eq(P, Bin3, Bin3),
- ne(P, Bin1, Bin2),
- ne(P, Bin1, Bin3),
- ne(P, Bin2, Bin3),
-
- runner:finish(P),
- ok.
-
-
-strings(Config) when is_list(Config) ->
- P = start_matcher(Config),
-
- S1 = "string",
- S2 = "streng",
- S3 = "String",
-
- eq(P, S1, S1),
- ne(P, S1, S2),
- ne(P, S1, S3),
-
- runner:finish(P),
- ok.
-
-
-bind(Config) when is_list(Config) ->
- P = start_bind(Config),
- S = "[X,Y,Z]",
- L1 = [301,302,302],
- L2 = [65,66,67],
-
- bind_ok(P, S, L1),
- bind_ok(P, S, L2),
-
- runner:finish(P),
- ok.
-
-start_bind(Config) ->
- runner:start(Config, ?erl_match_bind).
-
-bind_ok(Port, Bind, Term) ->
- true = erl_bind(Port, Bind, Term).
-
-%bind_nok(Port, Bind, Term) ->
-% false = erl_bind(Port, Bind, Term).
-
-erl_bind(Port, Pattern, Term) ->
- Port ! {self(), {command, [$b, Pattern, 0]}},
- runner:send_term(Port, Term),
- case runner:get_term(Port) of
- {term, 0} -> false;
- {term, 1} -> true
- end.
-
-
-
-start_matcher(Config) ->
- runner:start(Config, ?erl_match_server).
-
-eq(Port, Pattern, Term) ->
- true = erl_match(Port, Pattern, Term).
-
-ne(Port, Pattern, Term) ->
- false = erl_match(Port, Pattern, Term).
-
-
-
-erl_match(Port, Pattern, Term) ->
- runner:send_term(Port, Pattern),
- runner:send_term(Port, Term),
- case runner:get_term(Port) of
- {term, 0} -> false;
- {term, 1} -> true
- end.
diff --git a/lib/erl_interface/test/erl_match_SUITE_data/Makefile.first b/lib/erl_interface/test/erl_match_SUITE_data/Makefile.first
deleted file mode 100644
index 459b5c14c2..0000000000
--- a/lib/erl_interface/test/erl_match_SUITE_data/Makefile.first
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2000-2016. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-
-match_test_decl.c: match_test.c
- erl -noinput -pa ../all_SUITE_data -s init_tc run match_test -s erlang halt
diff --git a/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src
deleted file mode 100644
index f447f3a48f..0000000000
--- a/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2020. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-
-include @erl_interface_mk_include@
-
-CC0 = @CC@
-CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
-LD = @LD@
-LIBERL = @erl_interface_lib@
-LIBEI = @erl_interface_eilib@
-LIBFLAGS = ../all_SUITE_data/runner@obj@ \
- $(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
- @erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
-MATCH_OBJS = match_test@obj@ match_test_decl@obj@
-
-all: match_test@exe@
-
-clean:
- $(RM) $(MATCH_OBJS)
- $(RM) match_test@exe@
-
-match_test@exe@: $(MATCH_OBJS) $(LIBERL) $(LIBEI)
- $(LD) @CROSSLDFLAGS@ -o $@ $(MATCH_OBJS) $(LIBFLAGS)
-
diff --git a/lib/erl_interface/test/erl_match_SUITE_data/match_test.c b/lib/erl_interface/test/erl_match_SUITE_data/match_test.c
deleted file mode 100644
index d577417f5b..0000000000
--- a/lib/erl_interface/test/erl_match_SUITE_data/match_test.c
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2016. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-/*
- * Purpose: Tests the erl_match() function.
- * Author: Bjorn Gustavsson
- */
-
-#include "runner.h"
-
-TESTCASE(erl_match_server)
-{
- erl_init(NULL, 0);
-
- for (;;) {
- ETERM* pattern;
- ETERM* term;
-
- pattern = get_term();
- if (pattern == NULL) {
- report(1);
- return;
- } else {
- term = get_term();
- if (term == NULL) {
- fail("Unexpected EOF term");
- } else {
- send_term(erl_mk_int(erl_match(pattern, term)));
- erl_free_term(pattern);
- erl_free_term(term);
- }
- }
- }
-
-}
-
-TESTCASE(erl_match_bind)
-{
- erl_init(NULL, 0);
-
- for (;;) {
- char* pattern;
- ETERM* term;
-
- pattern=read_packet(NULL);
-
- switch (pattern[0]) {
- case 'e':
- free(pattern);
- report(1);
- return;
-
- case 'b':
- {
- ETERM* patt_term;
-
- /*
- * Get the pattern string and convert it using erl_format().
- *
- * Note that the call to get_term() below destroys the buffer
- * that the pattern variable points to. Therefore, it is
- * essential to call erl_format() here, before
- * calling get_term().
- */
-
- message("Pattern: %s", pattern+1);
- patt_term = erl_format(pattern+1);
- free(pattern);
-
- if (patt_term == NULL) {
- fail("erl_format() failed");
- }
-
- /*
- * Get the term and send back the result of the erl_match()
- * call.
- */
-
- term = get_term();
- if (term == NULL) {
- fail("Unexpected eof term");
- }
- else {
- send_term(erl_mk_int(erl_match(patt_term, term)));
- }
- erl_free_term(patt_term);
- erl_free_term(term);
- }
- break;
-
- default:
- free(pattern);
- fail("Illegal character received");
- }
-
- }
-}
diff --git a/lib/erl_interface/test/port_call_SUITE_data/Makefile.src b/lib/erl_interface/test/port_call_SUITE_data/Makefile.src
index e0fb72c571..c50325d833 100644
--- a/lib/erl_interface/test/port_call_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/port_call_SUITE_data/Makefile.src
@@ -23,10 +23,9 @@ include @erl_interface_mk_include@
CC0 = @CC@
CC = ..@DS@all_SUITE_data@DS@gccifier@exe@ -CC"$(CC0)"
LD = @LD@
-LIBERL = @erl_interface_lib_drv@
LIBEI = @erl_interface_eilib_drv@
-SHLIB_EXTRA_LDLIBS = $(LIBERL) $(LIBEI) @erl_interface_threadlib@
+SHLIB_EXTRA_LDLIBS = $(LIBEI) @erl_interface_threadlib@
SHLIB_EXTRA_CFLAGS = -I@erl_interface_include@ -I../all_SUITE_data
diff --git a/lib/erl_interface/test/port_call_SUITE_data/port_call_drv.c b/lib/erl_interface/test/port_call_SUITE_data/port_call_drv.c
index 4617cb0316..8f7303d645 100644
--- a/lib/erl_interface/test/port_call_SUITE_data/port_call_drv.c
+++ b/lib/erl_interface/test/port_call_SUITE_data/port_call_drv.c
@@ -21,7 +21,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
-#include "erl_interface.h"
+#include "ei.h"
#include "erl_driver.h"
static ErlDrvPort my_erlang_port;
diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk
index 31e3d1c0a0..d928057ca4 100644
--- a/lib/erl_interface/vsn.mk
+++ b/lib/erl_interface/vsn.mk
@@ -1,2 +1,2 @@
-EI_VSN = 3.13.2
+EI_VSN = 4.0.2
ERL_INTERFACE_VSN = $(EI_VSN)
diff --git a/lib/et/Makefile b/lib/et/Makefile
index f0bb7be211..1b89cff83e 100644
--- a/lib/et/Makefile
+++ b/lib/et/Makefile
@@ -35,3 +35,6 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=runtime_tools wx
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/et/doc/src/Makefile b/lib/et/doc/src/Makefile
index 93e2f8eeee..fb13e07d9e 100644
--- a/lib/et/doc/src/Makefile
+++ b/lib/et/doc/src/Makefile
@@ -29,87 +29,11 @@ include ../../vsn.mk
VSN=$(ET_VSN)
APPLICATION=et
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
-# Target Specs
-# ----------------------------------------------------
-
include files.mk
-# ----------------------------------------------------
-
XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) \
- $(XML_PART_FILES) $(XML_CHAPTER_FILES)
+ $(XML_PART_FILES) $(XML_CHAPTER_FILES)
XML_GEN_FILES = $(GEN_XML:%=$(XMLDIR)/%)
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(GEN_XML:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%: %
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: images $(HTML_REF_MAN_FILE)
-
-clean clean_docs:
- for file in $(XML_FILES); do \
- if [ -f $$file\src ]; then \
- rm -f $$file; \
- fi \
- done
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-man: $(MAN3_FILES)
-
-images: $(IMAGE_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
-
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/et/doc/src/et_collector.xml b/lib/et/doc/src/et_collector.xml
index f908612797..d258be2d40 100644
--- a/lib/et/doc/src/et_collector.xml
+++ b/lib/et/doc/src/et_collector.xml
@@ -139,19 +139,6 @@
</desc>
</func>
<func>
- <name since="">load_event_file(CollectorPid, FileName) -> {ok, BadBytes} | exit(Reason)</name>
- <fsummary>Load the event table from a file</fsummary>
- <type>
- <v>CollectorPid = pid()</v>
- <v>FileName = string()</v>
- <v>BadBytes = integer(X) where X >= 0</v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>Load the event table from a file.</p>
- </desc>
- </func>
- <func>
<name since="">report(Handle, TraceOrEvent) -> {ok, Continuation} | exit(Reason)</name>
<name since="">report_event(Handle, DetailLevel, FromTo, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
<name since="">report_event(Handle, DetailLevel, From, To, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
@@ -193,17 +180,6 @@
</desc>
</func>
<func>
- <name since="">get_table_handle(CollectorPid) -> Handle</name>
- <fsummary>Return a table handle</fsummary>
- <type>
- <v>CollectorPid = pid()</v>
- <v>Handle = record(table_handle)</v>
- </type>
- <desc>
- <p>Return a table handle.</p>
- </desc>
- </func>
- <func>
<name since="">get_global_pid() -> CollectorPid | exit(Reason)</name>
<fsummary>Return a the identity of the globally registered collector if there is any</fsummary>
<type>
diff --git a/lib/et/doc/src/et_tutorial.xmlsrc b/lib/et/doc/src/et_tutorial.xmlsrc
index b6e1ca141c..20fe026b31 100644
--- a/lib/et/doc/src/et_tutorial.xmlsrc
+++ b/lib/et/doc/src/et_tutorial.xmlsrc
@@ -113,8 +113,8 @@
et:trace_me(85,from,to,message,extra_stuff).]]></code>
<p>The parameters to <c>et:trace_me/5</c> are the same as to
- <seealso
- marker="#report_event"><c>et_collector:report_event/6</c></seealso>
+ <seeguide
+ marker="#report_event"><c>et_collector:report_event/6</c></seeguide>
in the previous chapter. The big difference between the two is in
the semantics of the two functions. The second actually reports an
<c>Event</c> to the <c>Collector</c> while the first does nothing,
diff --git a/lib/et/doc/src/files.mk b/lib/et/doc/src/files.mk
index c9041caa81..24815d0674 100644
--- a/lib/et/doc/src/files.mk
+++ b/lib/et/doc/src/files.mk
@@ -38,7 +38,6 @@ GEN_XML = \
et_desc.xml \
et_examples.xml
-
BOOK_FILES = book.xml
IMAGE_FILES = \
@@ -53,4 +52,3 @@ IMAGE_FILES = \
sim_trans_mgr_actors.png \
sim_trans_move_actor.png \
sim_trans_write_lock.png
-
diff --git a/lib/et/src/et_collector.erl b/lib/et/src/et_collector.erl
index 3609238509..7fa5dbda38 100644
--- a/lib/et/src/et_collector.erl
+++ b/lib/et/src/et_collector.erl
@@ -40,12 +40,10 @@
start_trace_client/3,
start_trace_port/1,
- %% load_event_file/2,
save_event_file/3,
clear_table/1,
get_global_pid/0,
- %% get_table_handle/1,
get_table_size/1,
change_pattern/2,
make_key/2,
diff --git a/lib/eunit/Makefile b/lib/eunit/Makefile
index 15dae19896..d0dd447a6c 100644
--- a/lib/eunit/Makefile
+++ b/lib/eunit/Makefile
@@ -48,13 +48,7 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
-
-.PHONY: info version
-
-info:
- @echo "APP_RELEASE_DIR: $(APP_RELEASE_DIR)"
- @echo "APP_DIR: $(APP_DIR)"
- @echo "APP_TAR_FILE: $(APP_TAR_FILE)"
+.PHONY: version
version:
@echo "$(VSN)"
@@ -94,3 +88,5 @@ tar: $(APP_TAR_FILE)
$(APP_TAR_FILE): $(APP_DIR)
(cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME))
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/eunit/doc/overview.edoc b/lib/eunit/doc/overview.edoc
index dc9f858812..8ab89fca73 100644
--- a/lib/eunit/doc/overview.edoc
+++ b/lib/eunit/doc/overview.edoc
@@ -277,6 +277,9 @@ while testing, you can write to the `user' output stream, as in
`io:format(user, "~w", [Term])'. The recommended way of doing this is to
use the EUnit {@section Debugging macros}, which make it much simpler.
+For checking the output produced by the unit under test, see
+{@section Macros for checking output}.
+
=== Writing test generating functions ===
A drawback of simple test functions is that you must write a separate
@@ -415,6 +418,7 @@ your code is compiled with testing enabled or disabled.
<li>{@section Compilation control macros}</li>
<li>{@section Utility macros}</li>
<li>{@section Assert macros}</li>
+<li>{@section Macros for checking output}</li>
<li>{@section Macros for running external commands}</li>
<li>{@section Debugging macros}</li>
</ul>
@@ -608,6 +612,22 @@ Examples:
</dd>
</dl>
+=== Macros for checking output ===
+
+The following macro can be used within a test case to retreive the
+output written to standard output.
+
+<dl>
+<dt>`capturedOutput'</dt>
+<dd>The output captured by EUnit in the current test case, as a string.
+
+Examples:
+
+```io:format("Hello~n"),
+ ?assertEqual("Hello\n", ?capturedOutput)'''
+</dd>
+</dl>
+
=== Macros for running external commands ===
Keep in mind that external commands are highly dependent on the
diff --git a/lib/eunit/doc/src/Makefile b/lib/eunit/doc/src/Makefile
index 117542cb37..22f2460fe9 100644
--- a/lib/eunit/doc/src/Makefile
+++ b/lib/eunit/doc/src/Makefile
@@ -29,43 +29,22 @@ VSN=$(EUNIT_VSN)
APPLICATION=eunit
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
-# Help application directory specification
-# ----------------------------------------------------
-
-EDOC_DIR = $(ERL_TOP)/lib/edoc
-SYNTAX_TOOLS_DIR = $(ERL_TOP)/lib/syntax_tools
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
-EUNIT_DIR = $(ERL_TOP)/lib/eunit/src
-EUNIT_INC_DIR = $(ERL_TOP)/lib/eunit/include
-
-EUNIT_MODULES = \
- eunit eunit_surefire
XML_APPLICATION_FILES = ref_man.xml
-XML_REF3_FILES = $(EUNIT_MODULES:=.xml)
-
-XML_PART_FILES = \
- part.xml
+EDOC_REF3_FILES = \
+ eunit.xml eunit_surefire.xml
-XML_CHAPTER_FILES = \
+EDOC_CHAPTER_FILE = \
chapter.xml
XML_NOTES_FILES = \
notes.xml
-HTML_EXAMPLE_FILES =
-
-HTML_STYLESHEET_FILES = \
- ../stylesheet.css
+XML_PART_FILES = \
+ part.xml
BOOK_FILES = book.xml
@@ -73,100 +52,6 @@ XML_FILES = \
$(BOOK_FILES) $(XML_NOTES_FILES) \
$(XML_PART_FILES) $(XML_APPLICATION_FILES)
-XML_GEN_FILES = $(XML_REF3_FILES:%=$(XMLDIR)/%) $(XML_CHAPTER_FILES:%=$(XMLDIR)/%)
-
-# ----------------------------------------------------
-INFO_FILE = ../../info
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-
-EXTRA_FILES = \
- $(DEFAULT_HTML_FILES) \
- $(DEFAULT_GIF_FILES) \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)\
- $(XML_NOTES_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-
# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-$(XML_REF3_FILES:%=$(XMLDIR)/%):
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -def vsn $(EUNIT_VSN) -i $(EUNIT_INC_DIR) -dir $(XMLDIR) $(EUNIT_DIR)/$(@:$(XMLDIR)/%.xml=%.erl)
-
-$(XML_CHAPTER_FILES:%=$(XMLDIR)/%):
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -def vsn $(EUNIT_VSN) -chapter -dir $(XMLDIR) ../overview.edoc
-
-info:
- @echo "XML_PART_FILES: $(XML_PART_FILES)"
- @echo "XML_APPLICATION_FILES: $(XML_APPLICATION_FILES)"
- @echo "EUNIT_XML_FILES: $(EUNIT_XML_FILES)"
- @echo "EUNIT_MODULES: $(EUNIT_MODULES)"
- @echo "HTML_FILES: $(HTML_FILES)"
- @echo "HTMLDIR: $(HTMLDIR)"
- @echo "DEFAULT_GIF_FILES: $(DEFAULT_GIF_FILES)"
- @echo "DEFAULT_HTML_FILES: $(DEFAULT_HTML_FILES)"
- @echo "EXTRA_FILES: $(EXTRA_FILES)"
-
-xml: $(XML_REF3_FILES) $(XML_CHAPTER_FILES)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(XML_REF3_FILES) $(XML_CHAPTER_FILES) *.html
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/eunit/doc/src/notes.xml b/lib/eunit/doc/src/notes.xml
index 200ffc3714..df7c3e2ff4 100644
--- a/lib/eunit/doc/src/notes.xml
+++ b/lib/eunit/doc/src/notes.xml
@@ -33,6 +33,50 @@
</header>
<p>This document describes the changes made to the EUnit application.</p>
+<section><title>Eunit 2.6</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Fixed compiler warning.</p>
+ <p>
+ Own Id: OTP-16674</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Eunit 2.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Let <c>eunit_surefire</c> skip invalid XML 1.0
+ characters. </p>
+ <p>
+ Own Id: OTP-15950 Aux Id: PR-2316, ERL-991 </p>
+ </item>
+ <item>
+ <p>
+ Add new macro ?capturedOutput for enabling to write test
+ cases that verify data printed to standard out</p>
+ <p>
+ Own Id: OTP-16275 Aux Id: PR-2424 </p>
+ </item>
+ <item>
+ <p>
+ Add option to limit print depth of exceptions generated
+ by eunit test suites.</p>
+ <p>
+ Own Id: OTP-16549 Aux Id: PR-2532 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Eunit 2.4.1</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/eunit/include/eunit.hrl b/lib/eunit/include/eunit.hrl
index 3cc36a27bc..6842147fda 100644
--- a/lib/eunit/include/eunit.hrl
+++ b/lib/eunit/include/eunit.hrl
@@ -147,6 +147,16 @@
-define(_assertNotException(Class, Term, Expr),
?_test(?assertNotException(Class, Term, Expr))).
+%% Macros for retrieving the output of a test case
+
+-ifndef(capturedOutput).
+-define(capturedOutput,
+ case ?UNDER_EUNIT of
+ true -> eunit_proc:get_output();
+ false -> ""
+ end).
+-endif.
+
%% Macros for running operating system commands. (Note that these
%% require EUnit to be present at runtime, or at least eunit_lib.)
diff --git a/lib/eunit/src/Makefile b/lib/eunit/src/Makefile
index 3510d3cc93..32f75202a0 100644
--- a/lib/eunit/src/Makefile
+++ b/lib/eunit/src/Makefile
@@ -24,7 +24,8 @@ RELSYSDIR = $(RELEASE_PATH)/lib/eunit-$(VSN)
EBIN = ../ebin
INCLUDE=../include
-ERL_COMPILE_FLAGS += -pa $(EBIN) -pa ../../stdlib/ebin -I$(INCLUDE) +nowarn_shadow_vars +warn_unused_import
+ERL_COMPILE_FLAGS += -pa $(EBIN) -pa ../../stdlib/ebin -I$(INCLUDE) \
+ +nowarn_shadow_vars +warn_unused_import -Werror
PARSE_TRANSFORM = eunit_autoexport.erl
diff --git a/lib/eunit/src/eunit.erl b/lib/eunit/src/eunit.erl
index 1ace85ffde..0238885708 100644
--- a/lib/eunit/src/eunit.erl
+++ b/lib/eunit/src/eunit.erl
@@ -133,6 +133,8 @@ test(Tests) ->
%% <dl>
%% <dt>`verbose'</dt>
%% <dd>Displays more details about the running tests.</dd>
+%% <dt>`print_depth'</dt>
+%% <dd>Maximum depth to which terms are printed in case of error.</dd>
%% </dl>
%%
%% Options in the environment variable EUNIT are also included last in
diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl
index 771541354c..5b7d90ab0d 100644
--- a/lib/eunit/src/eunit_lib.erl
+++ b/lib/eunit/src/eunit_lib.erl
@@ -35,9 +35,10 @@
-export([dlist_next/1, uniq/1, fun_parent/1, is_string/1, command/1,
command/2, command/3, trie_new/0, trie_store/2, trie_match/2,
split_node/1, consult_file/1, list_dir/1, format_exit_term/1,
- format_exception/1, format_exception/2, format_error/1,
+ format_exception/1, format_exception/2, format_error/1, format_error/2,
is_not_test/1]).
+-define(DEFAULT_DEPTH, 20).
%% Type definitions for describing exceptions
%%
@@ -56,7 +57,7 @@
%% ---------------------------------------------------------------------
%% Formatting of error descriptors
format_exception(Exception) ->
- format_exception(Exception, 20).
+ format_exception(Exception, ?DEFAULT_DEPTH).
format_exception({Class,Term,Trace}, Depth)
when is_atom(Class), is_list(Trace) ->
@@ -159,38 +160,41 @@ is_op(erlang, F, A) ->
is_op(_M, _F, _A) ->
false.
-format_error({bad_test, Term}) ->
- error_msg("bad test descriptor", "~tP", [Term, 15]);
-format_error({bad_generator, {{M,F,A}, Term}}) ->
+format_error(Error) ->
+ format_error(Error, ?DEFAULT_DEPTH).
+
+format_error({bad_test, Term}, Depth) ->
+ error_msg("bad test descriptor", "~tP", [Term, Depth]);
+format_error({bad_generator, {{M,F,A}, Term}}, Depth) ->
error_msg(io_lib:format("result from generator ~w:~tw/~w is not a test",
[M,F,A]),
- "~tP", [Term, 15]);
-format_error({generator_failed, {{M,F,A}, Exception}}) ->
+ "~tP", [Term, Depth]);
+format_error({generator_failed, {{M,F,A}, Exception}}, Depth) ->
error_msg(io_lib:format("test generator ~w:~tw/~w failed",[M,F,A]),
- "~ts", [format_exception(Exception)]);
-format_error({no_such_function, {M,F,A}})
+ "~ts", [format_exception(Exception, Depth)]);
+format_error({no_such_function, {M,F,A}}, _)
when is_atom(M), is_atom(F), is_integer(A) ->
error_msg(io_lib:format("no such function: ~w:~tw/~w", [M,F,A]),
"", []);
-format_error({module_not_found, M}) ->
+format_error({module_not_found, M}, _) ->
error_msg("test module not found", "~tp", [M]);
-format_error({application_not_found, A}) when is_atom(A) ->
+format_error({application_not_found, A}, _) when is_atom(A) ->
error_msg("application not found", "~w", [A]);
-format_error({file_read_error, {_R, Msg, F}}) ->
+format_error({file_read_error, {_R, Msg, F}}, _) ->
error_msg("error reading file", "~ts: ~ts", [Msg, F]);
-format_error({setup_failed, Exception}) ->
+format_error({setup_failed, Exception}, Depth) ->
error_msg("context setup failed", "~ts",
- [format_exception(Exception)]);
-format_error({cleanup_failed, Exception}) ->
+ [format_exception(Exception, Depth)]);
+format_error({cleanup_failed, Exception}, Depth) ->
error_msg("context cleanup failed", "~ts",
- [format_exception(Exception)]);
-format_error({{bad_instantiator, {{M,F,A}, Term}}, _DummyException}) ->
+ [format_exception(Exception, Depth)]);
+format_error({{bad_instantiator, {{M,F,A}, Term}}, _DummyException}, Depth) ->
error_msg(io_lib:format("result from instantiator ~w:~tw/~w is not a test",
[M,F,A]),
- "~tP", [Term, 15]);
-format_error({instantiation_failed, Exception}) ->
+ "~tP", [Term, Depth]);
+format_error({instantiation_failed, Exception}, Depth) ->
error_msg("instantiation of subtests failed", "~ts",
- [format_exception(Exception)]).
+ [format_exception(Exception, Depth)]).
error_msg(Title, Fmt, Args) ->
Msg = io_lib:format("**"++Fmt, Args), % gets indentation right
diff --git a/lib/eunit/src/eunit_proc.erl b/lib/eunit/src/eunit_proc.erl
index 6e702543d0..fb6619a6d4 100644
--- a/lib/eunit/src/eunit_proc.erl
+++ b/lib/eunit/src/eunit_proc.erl
@@ -29,7 +29,7 @@
-include("eunit.hrl").
-include("eunit_internal.hrl").
--export([start/4]).
+-export([start/4, get_output/0]).
%% This must be exported; see new_group_leader/1 for details.
-export([group_leader_process/1]).
@@ -52,6 +52,18 @@ start(Tests, Order, Super, Reference)
order = Order},
spawn_group(local, #group{tests = Tests}, St).
+%% Fetches the output captured by the eunit group leader. This is
+%% provided to allow test cases to check the captured output.
+
+-spec get_output() -> string().
+get_output() ->
+ group_leader() ! {get_output, self()},
+ receive
+ {output, Output} -> Output
+ after 100 ->
+ %% The group leader is not an eunit_proc
+ abort_task(get_output)
+ end.
%% Status messages sent to the supervisor process. (A supervisor does
%% not have to act on these messages - it can e.g. just log them, or
@@ -594,6 +606,9 @@ group_leader_loop(Runner, Wait, Buf) ->
receive after 2 -> ok end,
process_flag(priority, low),
group_leader_loop(Runner, 0, Buf);
+ {get_output, From} ->
+ From ! {output, lists:flatten(lists:reverse(Buf))},
+ group_leader_loop(Runner, Wait, Buf);
_ ->
%% discard any other messages
group_leader_loop(Runner, Wait, Buf)
@@ -669,4 +684,9 @@ io_error_test_() ->
[?_assertMatch({error, enotsup}, io:getopts()),
?_assertMatch({error, enotsup}, io:columns()),
?_assertMatch({error, enotsup}, io:rows())].
+
+get_output_test() ->
+ io:format("Hello"),
+ Output = get_output(),
+ ?assertEqual("Hello", Output).
-endif.
diff --git a/lib/eunit/src/eunit_tests.erl b/lib/eunit/src/eunit_tests.erl
index 07a415eeb1..f43f4cca09 100644
--- a/lib/eunit/src/eunit_tests.erl
+++ b/lib/eunit/src/eunit_tests.erl
@@ -45,3 +45,10 @@ if_test_() ->
matches_test_() ->
[?_assert(?MATCHES("hel"++_, "hello")),
?_assertNot(?MATCHES("hal"++_, "hello"))].
+
+get_output_test() ->
+ io:format(<<"Hello ~p!~n">>, [eunit]),
+ ?assertEqual("Hello eunit!\n", ?capturedOutput),
+ io:format("System working?~n~s~n", ["Seems to be."]),
+ ?assertEqual("Hello eunit!\nSystem working?\nSeems to be.\n",
+ ?capturedOutput).
diff --git a/lib/eunit/src/eunit_tty.erl b/lib/eunit/src/eunit_tty.erl
index 2c9a598628..fd5245c971 100644
--- a/lib/eunit/src/eunit_tty.erl
+++ b/lib/eunit/src/eunit_tty.erl
@@ -38,7 +38,8 @@
terminate/2]).
-record(state, {verbose = false,
- indent = 0
+ indent = 0,
+ print_depth = 20
}).
start() ->
@@ -48,7 +49,9 @@ start(Options) ->
eunit_listener:start(?MODULE, Options).
init(Options) ->
- St = #state{verbose = proplists:get_bool(verbose, Options)},
+ PrintDepth = proplists:get_value(print_depth, Options, 20),
+ St = #state{verbose = proplists:get_bool(verbose, Options),
+ print_depth = PrintDepth},
put(no_tty, proplists:get_bool(no_tty, Options)),
receive
{start, _Reference} ->
@@ -89,8 +92,8 @@ terminate({ok, Data}, St) ->
end,
sync_end(error)
end;
-terminate({error, Reason}, _St) ->
- fwrite("Internal error: ~tP.\n", [Reason, 25]),
+terminate({error, Reason}, #state{print_depth = Depth}) ->
+ fwrite("Internal error: ~tP.\n", [Reason, Depth]),
sync_end(error).
sync_end(Result) ->
@@ -147,7 +150,7 @@ handle_end(test, Data, St) ->
if St#state.verbose -> ok;
true -> print_test_begin(St#state.indent, Data)
end,
- print_test_error(Status, Data),
+ print_test_error(Status, Data, St),
St
end.
@@ -161,10 +164,10 @@ handle_cancel(group, Data, St) ->
Reason ->
Desc = proplists:get_value(desc, Data),
if Desc =/= "", Desc =/= undefined, St#state.verbose ->
- print_group_cancel(I, Reason);
+ print_group_cancel(I, Reason, St);
true ->
print_group_start(I, Desc),
- print_group_cancel(I, Reason)
+ print_group_cancel(I, Reason, St)
end,
St#state{indent = I - 1}
end;
@@ -173,7 +176,7 @@ handle_cancel(test, Data, St) ->
if St#state.verbose -> ok;
true -> print_test_begin(St#state.indent, Data)
end,
- print_test_cancel(proplists:get_value(reason, Data)),
+ print_test_cancel(proplists:get_value(reason, Data), St),
St.
@@ -218,9 +221,9 @@ print_test_end(Data) ->
end,
fwrite("~tsok\n", [T]).
-print_test_error({error, Exception}, Data) ->
+print_test_error({error, Exception}, Data, #state{print_depth = Depth}) ->
Output = proplists:get_value(output, Data),
- fwrite("*failed*\n~ts", [eunit_lib:format_exception(Exception)]),
+ fwrite("*failed*\n~ts", [eunit_lib:format_exception(Exception, Depth)]),
case Output of
<<>> ->
fwrite("\n\n");
@@ -229,7 +232,7 @@ print_test_error({error, Exception}, Data) ->
_ ->
fwrite(" output:<<\"~ts\">>\n\n", [Output])
end;
-print_test_error({skipped, Reason}, _) ->
+print_test_error({skipped, Reason}, _, _St) ->
fwrite("*did not run*\n::~ts\n", [format_skipped(Reason)]).
format_skipped({module_not_found, M}) ->
@@ -237,29 +240,29 @@ format_skipped({module_not_found, M}) ->
format_skipped({no_such_function, {M,F,A}}) ->
io_lib:fwrite("no such function: ~w:~tw/~w", [M,F,A]).
-print_test_cancel(Reason) ->
- fwrite(format_cancel(Reason)).
+print_test_cancel(Reason, #state{print_depth = Depth}) ->
+ fwrite(format_cancel(Reason, Depth)).
-print_group_cancel(_I, {blame, _}) ->
+print_group_cancel(_I, {blame, _}, _) ->
ok;
-print_group_cancel(I, Reason) ->
+print_group_cancel(I, Reason, #state{print_depth = Depth}) ->
indent(I),
- fwrite(format_cancel(Reason)).
+ fwrite(format_cancel(Reason, Depth)).
-format_cancel(undefined) ->
+format_cancel(undefined, _) ->
"*skipped*\n";
-format_cancel(timeout) ->
+format_cancel(timeout, _) ->
"*timed out*\n";
-format_cancel({startup, Reason}) ->
+format_cancel({startup, Reason}, Depth) ->
io_lib:fwrite("*could not start test process*\n::~tP\n\n",
- [Reason, 15]);
-format_cancel({blame, _SubId}) ->
+ [Reason, Depth]);
+format_cancel({blame, _SubId}, _) ->
"*cancelled because of subtask*\n";
-format_cancel({exit, Reason}) ->
+format_cancel({exit, Reason}, Depth) ->
io_lib:fwrite("*unexpected termination of test process*\n::~tP\n\n",
- [Reason, 15]);
-format_cancel({abort, Reason}) ->
- eunit_lib:format_error(Reason).
+ [Reason, Depth]);
+format_cancel({abort, Reason}, Depth) ->
+ eunit_lib:format_error(Reason, Depth).
fwrite(String) ->
fwrite(String, []).
diff --git a/lib/eunit/vsn.mk b/lib/eunit/vsn.mk
index f96db657cf..09fd053afc 100644
--- a/lib/eunit/vsn.mk
+++ b/lib/eunit/vsn.mk
@@ -1 +1 @@
-EUNIT_VSN = 2.4.1
+EUNIT_VSN = 2.6
diff --git a/lib/ftp/Makefile b/lib/ftp/Makefile
index e0c9de42e4..a26d1de0a0 100644
--- a/lib/ftp/Makefile
+++ b/lib/ftp/Makefile
@@ -32,47 +32,11 @@ VSN = $(FTP_VSN)
SPECIAL_TARGETS =
-DIA_PLT = ./priv/plt/$(APPLICATION).plt
-DIA_ANALYSIS = $(basename $(DIA_PLT)).dialyzer_analysis
-
-
# ----------------------------------------------------
# Default Subdir Targets
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
-.PHONY: info gclean dialyzer dialyzer_plt dclean
-
-info:
- @echo "OS: $(OS)"
- @echo "DOCB: $(DOCB)"
- @echo ""
- @echo "FTP_VSN: $(FTP_VSN)"
- @echo "APP_VSN: $(APP_VSN)"
- @echo ""
- @echo "DIA_PLT: $(DIA_PLT)"
- @echo "DIA_ANALYSIS: $(DIA_ANALYSIS)"
- @echo ""
-
-gclean:
- git clean -fXd
-
-dclean:
- rm -f $(DIA_PLT)
- rm -f $(DIA_ANALYSIS)
-
-dialyzer_plt: $(DIA_PLT)
-
-$(DIA_PLT):
- @echo "Building $(APPLICATION) plt file"
- @dialyzer --build_plt \
- --output_plt $@ \
- -r ../$(APPLICATION)/ebin \
- --output $(DIA_ANALYSIS) \
- --verbose
+DIA_PLT_APPS = runtime_tools ssl
-dialyzer: $(DIA_PLT)
- @echo "Running dialyzer on $(APPLICATION)"
- @dialyzer --plt $< \
- ../$(APPLICATION)/ebin \
- --verbose
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/ftp/doc/src/Makefile b/lib/ftp/doc/src/Makefile
index 20fbbc73a9..fc95e83ae8 100644
--- a/lib/ftp/doc/src/Makefile
+++ b/lib/ftp/doc/src/Makefile
@@ -26,12 +26,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
include ../../vsn.mk
VSN=$(FTP_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
+APPLICATION=ftp
# ----------------------------------------------------
# Target Specs
@@ -59,97 +54,6 @@ XML_FILES = \
$(XML_REF3_FILES) \
$(XML_APPLICATION_FILES)
-# GIF_FILES = ftp.gif
-
-
-# ----------------------------------------------------
-
-HTML_FILES = \
- $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-EXTRA_FILES = \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-ldocs: local_docs
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs: clean_html clean_man clean_pdf
- rm -rf $(XMLDIR)
- rm -f errs core *~
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean_pdf:
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
-
-clean_html:
- rm -rf $(TOP_HTML_FILES) $(HTMLDIR)/*
-
-clean_man:
- rm -f $(MAN3_FILES)
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
+# IMAGE_FILES = ftp.gif
-info:
- @echo "GIF_FILES:\n$(GIF_FILES)"
- @echo ""
- @echo "EXTRA_FILES:\n$(EXTRA_FILES)"
- @echo ""
- @echo "HTML_FILES:\n$(HTML_FILES)"
- @echo ""
- @echo "TOP_HTML_FILES:\n$(TOP_HTML_FILES)"
- @echo ""
- @echo "XML_REF3_FILES:\n$(XML_REF3_FILES)"
- @echo ""
- @echo "XML_REF6_FILES:\n$(XML_REF6_FILES)"
- @echo ""
- @echo "XML_CHAPTER_FILES:\n$(XML_CHAPTER_FILES)"
- @echo ""
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/ftp/doc/src/ftp.xml b/lib/ftp/doc/src/ftp.xml
index 9645b03364..e18f1e2b2b 100644
--- a/lib/ftp/doc/src/ftp.xml
+++ b/lib/ftp/doc/src/ftp.xml
@@ -40,17 +40,17 @@
<p>The FTP client always tries to use passive FTP mode and only resort
to active FTP mode if this fails. This default behavior can be
- changed by start option <seealso marker="#mode">mode</seealso>.</p>
+ changed by start option <seeerl marker="#mode">mode</seeerl>.</p>
<marker id="two_start"></marker>
<p>An FTP client can be started in two ways. One is using the
- <seealso marker="#service_start">service_start</seealso> function,
+ <seeerl marker="#service_start">service_start</seeerl> function,
the other is to start it directly as a standalone process
- using function <seealso marker="#open">open</seealso>.</p>
+ using function <seeerl marker="#open">open</seeerl>.</p>
<p>For a simple example of an FTP session, see
- <seealso marker="ftp_client">FTP User's Guide</seealso>.</p>
+ <seeguide marker="ftp_client">FTP User's Guide</seeguide>.</p>
<p>In addition to the ordinary functions for receiving and sending
files (see <c>recv/2</c>, <c>recv/3</c>, <c>send/2</c>, and
@@ -58,7 +58,7 @@
binaries (see <c>recv_bin/2</c>) and for sending binaries to be
stored as remote files (see <c>send_bin/3</c>).</p>
- <p>A set of functions is provvided for sending and receiving
+ <p>A set of functions is provided for sending and receiving
contiguous parts of a file to be stored in a remote file. For send,
see <c>send_chunk_start/2</c>, <c>send_chunk/2</c>, and
<c>send_chunk_end/1</c>. For receive, see
@@ -404,7 +404,7 @@
</type>
<desc>
<p>Ends an FTP session, created using function
- <seealso marker="#open">open</seealso>.</p>
+ <seeerl marker="#open">open</seeerl>.</p>
<marker id="delete"></marker>
</desc>
@@ -550,8 +550,8 @@
<v>ipfamily() = inet | inet6 | inet6fb4 (default is inet)</v>
<v>port() = integer() > 0 (default is 21)</v>
<v>mode() = active | passive (default is passive)</v>
- <v>tls_options() = [<seealso marker="ssl:ssl#type-tls_option">ssl:tls_option()</seealso>]</v>
- <v>sock_opts() = [<seealso marker="kernel:gen_tcp#type-option">gen_tcp:option()</seealso> except for ipv6_v6only, active, packet, mode, packet_size and header</v>
+ <v>tls_options() = [<seetype marker="ssl:ssl#tls_option">ssl:tls_option()</seetype>]</v>
+ <v>sock_opts() = [<seetype marker="kernel:gen_tcp#option">gen_tcp:option()</seetype> except for ipv6_v6only, active, packet, mode, packet_size and header</v>
<v>timeout() = integer() > 0 (default is 60000 milliseconds)</v>
<v>dtimeout() = integer() > 0 | infinity (default is infinity)</v>
<v>pogress() = ignore | {module(), function(), initial_data()} (default is ignore)</v>
@@ -570,7 +570,7 @@
is transported over <c>tls</c> (<c>ftps</c>, see
<url href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</url>).
The list <c>tls_options()</c> can be empty. The function
- <seealso marker="ssl:ssl#connect/3"><c>ssl:connect/3</c></seealso>
+ <seemfa marker="ssl:ssl#connect/3"><c>ssl:connect/3</c></seemfa>
is used for securing both the control connection and the data sessions.
</p>
@@ -580,7 +580,7 @@
</p>
<p>A session opened in this way is closed using function
- <seealso marker="#close">close</seealso>.</p>
+ <seeerl marker="#close">close</seeerl>.</p>
<marker id="pwd"></marker>
</desc>
@@ -971,8 +971,8 @@
<section>
<title>SEE ALSO</title>
- <p><seealso marker="kernel:file">file(3)</seealso>
- <seealso marker="stdlib:filename">filename(3)</seealso>
+ <p><seeerl marker="kernel:file">file(3)</seeerl>
+ <seeerl marker="stdlib:filename">filename(3)</seeerl>
and J. Postel and J. Reynolds: File Transfer Protocol
(<url href="http://www.ietf.org/rfc/rfc959.txt">RFC 959</url>).
</p>
diff --git a/lib/ftp/doc/src/notes.xml b/lib/ftp/doc/src/notes.xml
index 44348691a2..e6ce2e3d81 100644
--- a/lib/ftp/doc/src/notes.xml
+++ b/lib/ftp/doc/src/notes.xml
@@ -33,7 +33,39 @@
<file>notes.xml</file>
</header>
- <section><title>Ftp 1.0.4</title>
+ <section><title>Ftp 1.0.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid timing issue when setting active once on a socket
+ that is being closed by the peer.</p>
+ <p>
+ Own Id: OTP-16734 Aux Id: OTP-16697, ERIERL-496 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ftp 1.0.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid timing issue when setting active once on a socket
+ that is being closed by the peer.</p>
+ <p>
+ Own Id: OTP-16734 Aux Id: OTP-16697, ERIERL-496 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ftp 1.0.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/ftp/src/ftp.erl b/lib/ftp/src/ftp.erl
index 36b57837fc..7e32594e5b 100644
--- a/lib/ftp/src/ftp.erl
+++ b/lib/ftp/src/ftp.erl
@@ -37,24 +37,24 @@
-export([start_link/1, start_link/2]).
%% API - Client interface
--export([cd/2, close/1, delete/2, formaterror/1,
- lcd/2, lpwd/1, ls/1, ls/2,
- mkdir/2, nlist/1, nlist/2,
- open/1, open/2,
- pwd/1, quote/2,
- recv/2, recv/3, recv_bin/2,
- recv_chunk_start/2, recv_chunk/1,
- rename/3, rmdir/2,
- send/2, send/3, send_bin/3,
- send_chunk_start/2, send_chunk/2, send_chunk_end/1,
- type/2, user/3, user/4, account/2,
- append/3, append/2, append_bin/3,
- append_chunk/2, append_chunk_end/1, append_chunk_start/2,
- info/1, latest_ctrl_response/1]).
+-export([cd/2, close/1, delete/2, formaterror/1,
+ lcd/2, lpwd/1, ls/1, ls/2,
+ mkdir/2, nlist/1, nlist/2,
+ open/1, open/2,
+ pwd/1, quote/2,
+ recv/2, recv/3, recv_bin/2,
+ recv_chunk_start/2, recv_chunk/1,
+ rename/3, rmdir/2,
+ send/2, send/3, send_bin/3,
+ send_chunk_start/2, send_chunk/2, send_chunk_end/1,
+ type/2, user/3, user/4, account/2,
+ append/3, append/2, append_bin/3,
+ append_chunk/2, append_chunk_end/1, append_chunk_start/2,
+ info/1, latest_ctrl_response/1]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2,
- handle_info/2, terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ftp_internal.hrl").
@@ -63,7 +63,7 @@
-define(DATA_ACCEPT_TIMEOUT, infinity).
-define(DEFAULT_MODE, passive).
-define(PROGRESS_DEFAULT, ignore).
--define(FTP_EXT_DEFAULT, false).
+-define(FTP_EXT_DEFAULT, false).
%% Internal Constants
-define(FTP_PORT, 21).
@@ -76,39 +76,39 @@
%% Internal state
-record(state, {
- csock = undefined, % socket() - Control connection socket
- dsock = undefined, % socket() - Data connection socket
- tls_options = undefined, % list()
- verbose = false, % boolean()
- ldir = undefined, % string() - Current local directory
- type = ftp_server_default, % atom() - binary | ascii
- chunk = false, % boolean() - Receiving data chunks
- mode = ?DEFAULT_MODE, % passive | active
- timeout = ?CONNECTION_TIMEOUT, % integer()
- %% Data received so far on the data connection
- data = <<>>, % binary()
- %% Data received so far on the control connection
- %% {BinStream, AccLines}. If a binary sequence
- %% ends with ?CR then keep it in the binary to
- %% be able to detect if the next received byte is ?LF
- %% and hence the end of the response is reached!
- ctrl_data = {<<>>, [], start}, % {binary(), [bytes()], LineStatus}
- %% pid() - Client pid (note not the same as "From")
- latest_ctrl_response = "",
- owner = undefined,
- client = undefined, % "From" to be used in gen_server:reply/2
- %% Function that activated a connection and maybe some
- %% data needed further on.
- caller = undefined, % term()
- ipfamily, % inet | inet6 | inet6fb4
+ csock = undefined, % socket() - Control connection socket
+ dsock = undefined, % socket() - Data connection socket
+ tls_options = undefined, % list()
+ verbose = false, % boolean()
+ ldir = undefined, % string() - Current local directory
+ type = ftp_server_default, % atom() - binary | ascii
+ chunk = false, % boolean() - Receiving data chunks
+ mode = ?DEFAULT_MODE, % passive | active
+ timeout = ?CONNECTION_TIMEOUT, % integer()
+ %% Data received so far on the data connection
+ data = <<>>, % binary()
+ %% Data received so far on the control connection
+ %% {BinStream, AccLines}. If a binary sequence
+ %% ends with ?CR then keep it in the binary to
+ %% be able to detect if the next received byte is ?LF
+ %% and hence the end of the response is reached!
+ ctrl_data = {<<>>, [], start}, % {binary(), [bytes()], LineStatus}
+ %% pid() - Client pid (note not the same as "From")
+ latest_ctrl_response = "",
+ owner = undefined,
+ client = undefined, % "From" to be used in gen_server:reply/2
+ %% Function that activated a connection and maybe some
+ %% data needed further on.
+ caller = undefined, % term()
+ ipfamily, % inet | inet6 | inet6fb4
sockopts_ctrl = [],
sockopts_data_passive = [],
sockopts_data_active = [],
- progress = ignore, % ignore | pid()
- dtimeout = ?DATA_ACCEPT_TIMEOUT, % non_neg_integer() | infinity
- tls_upgrading_data_connection = false,
- ftp_extension = ?FTP_EXT_DEFAULT
- }).
+ progress = ignore, % ignore | pid()
+ dtimeout = ?DATA_ACCEPT_TIMEOUT, % non_neg_integer() | infinity
+ tls_upgrading_data_connection = false,
+ ftp_extension = ?FTP_EXT_DEFAULT
+ }).
-record(recv_chunk_closing, {
dconn_closed = false,
@@ -136,34 +136,34 @@ start() ->
start_standalone(Options) ->
try
- {ok, StartOptions} = start_options(Options),
- {ok, OpenOptions} = open_options(Options),
- {ok, SocketOptions} = socket_options(Options),
- case start_link(StartOptions, []) of
- {ok, Pid} ->
- call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain);
- Error1 ->
- Error1
- end
+ {ok, StartOptions} = start_options(Options),
+ {ok, OpenOptions} = open_options(Options),
+ {ok, SocketOptions} = socket_options(Options),
+ case start_link(StartOptions, []) of
+ {ok, Pid} ->
+ call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain);
+ Error1 ->
+ Error1
+ end
catch
- throw:Error2 ->
- Error2
+ throw:Error2 ->
+ Error2
end.
start_service(Options) ->
try
- {ok, StartOptions} = start_options(Options),
- {ok, OpenOptions} = open_options(Options),
- {ok, SocketOptions} = socket_options(Options),
- case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
- {ok, Pid} ->
- call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain);
- Error1 ->
- Error1
- end
- catch
- throw:Error2 ->
- Error2
+ {ok, StartOptions} = start_options(Options),
+ {ok, OpenOptions} = open_options(Options),
+ {ok, SocketOptions} = socket_options(Options),
+ case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
+ {ok, Pid} ->
+ call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain);
+ Error1 ->
+ Error1
+ end
+ catch
+ throw:Error2 ->
+ Error2
end.
stop() ->
@@ -173,14 +173,14 @@ stop_service(Pid) ->
close(Pid).
services() ->
- [{ftpc, Pid} || {_, Pid, _, _} <-
- supervisor:which_children(ftp_sup)].
+ [{ftpc, Pid} || {_, Pid, _, _} <-
+ supervisor:which_children(ftp_sup)].
service_info(Pid) ->
{ok, Info} = call(Pid, info, list),
- {ok, [proplists:lookup(mode, Info),
- proplists:lookup(local_port, Info),
- proplists:lookup(peer, Info),
- proplists:lookup(peer_port, Info)]}.
+ {ok, [proplists:lookup(mode, Info),
+ proplists:lookup(local_port, Info),
+ proplists:lookup(peer, Info),
+ proplists:lookup(peer_port, Info)]}.
%%%=========================================================================
@@ -189,9 +189,9 @@ service_info(Pid) ->
%%--------------------------------------------------------------------------
%% open(HostOrOtpList, <Port>, <Flags>) -> {ok, Pid} | {error, ehost}
-%% HostOrOtpList = string() | [{option_list, Options}]
-%% Port = integer(),
-%% Flags = [Flag],
+%% HostOrOtpList = string() | [{option_list, Options}]
+%% Port = integer(),
+%% Flags = [Flag],
%% Flag = verbose | debug | trace
%%
%% Description: Start an ftp client and connect to a host.
@@ -203,18 +203,18 @@ service_info(Pid) ->
%% <BACKWARD-COMPATIBILLITY>
open({option_list, Options}) when is_list(Options) ->
try
- {ok, StartOptions} = start_options(Options),
- {ok, OpenOptions} = open_options(Options),
- {ok, SockOpts} = socket_options(Options),
- case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
- {ok, Pid} ->
- call(Pid, {open, ip_comm, OpenOptions, SockOpts}, plain);
- Error1 ->
- Error1
- end
- catch
- throw:Error2 ->
- Error2
+ {ok, StartOptions} = start_options(Options),
+ {ok, OpenOptions} = open_options(Options),
+ {ok, SockOpts} = socket_options(Options),
+ case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
+ {ok, Pid} ->
+ call(Pid, {open, ip_comm, OpenOptions, SockOpts}, plain);
+ Error1 ->
+ Error1
+ end
+ catch
+ throw:Error2 ->
+ Error2
end;
%% </BACKWARD-COMPATIBILLITY>
@@ -231,67 +231,67 @@ open(Host, Port) when is_integer(Port) ->
open(Host, Opts) when is_list(Opts) ->
try
- {ok, StartOptions} = start_options(Opts),
- {ok, OpenOptions} = open_options([{host, Host}|Opts]),
- {ok, SocketOptions} = socket_options(Opts),
- case start_link(StartOptions, []) of
- {ok, Pid} ->
- do_open(Pid, OpenOptions, SocketOptions, tls_options(Opts));
- Error1 ->
- Error1
- end
+ {ok, StartOptions} = start_options(Opts),
+ {ok, OpenOptions} = open_options([{host, Host}|Opts]),
+ {ok, SocketOptions} = socket_options(Opts),
+ case start_link(StartOptions, []) of
+ {ok, Pid} ->
+ do_open(Pid, OpenOptions, SocketOptions, tls_options(Opts));
+ Error1 ->
+ Error1
+ end
catch
- throw:Error2 ->
- Error2
+ throw:Error2 ->
+ Error2
end.
do_open(Pid, OpenOptions, SocketOptions, TLSOpts) ->
case call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain) of
- {ok, Pid} ->
- maybe_tls_upgrade(Pid, TLSOpts);
- Error ->
- Error
+ {ok, Pid} ->
+ maybe_tls_upgrade(Pid, TLSOpts);
+ Error ->
+ Error
end.
%%--------------------------------------------------------------------------
-%% user(Pid, User, Pass, <Acc>) -> ok | {error, euser} | {error, econn}
+%% user(Pid, User, Pass, <Acc>) -> ok | {error, euser} | {error, econn}
%% | {error, eacct}
-%% Pid = pid(),
+%% Pid = pid(),
%% User = Pass = Acc = string()
%%
%% Description: Login with or without a supplied account name.
%%--------------------------------------------------------------------------
--spec user(Pid :: pid(),
- User :: string(),
- Pass :: string()) ->
+-spec user(Pid :: pid(),
+ User :: string(),
+ Pass :: string()) ->
'ok' | {'error', Reason :: 'euser' | common_reason()}.
user(Pid, User, Pass) ->
case {is_name_sane(User), is_name_sane(Pass)} of
- {true, true} ->
- call(Pid, {user, User, Pass}, atom);
- _ ->
- {error, euser}
+ {true, true} ->
+ call(Pid, {user, User, Pass}, atom);
+ _ ->
+ {error, euser}
end.
--spec user(Pid :: pid(),
- User :: string(),
- Pass :: string(),
- Acc :: string()) ->
+-spec user(Pid :: pid(),
+ User :: string(),
+ Pass :: string(),
+ Acc :: string()) ->
'ok' | {'error', Reason :: 'euser' | common_reason()}.
user(Pid, User, Pass, Acc) ->
case {is_name_sane(User), is_name_sane(Pass), is_name_sane(Acc)} of
- {true, true, true} ->
- call(Pid, {user, User, Pass, Acc}, atom);
- _ ->
- {error, euser}
+ {true, true, true} ->
+ call(Pid, {user, User, Pass, Acc}, atom);
+ _ ->
+ {error, euser}
end.
%%--------------------------------------------------------------------------
%% account(Pid, Acc) -> ok | {error, eacct}
-%% Pid = pid()
-%% Acc= string()
+%% Pid = pid()
+%% Acc= string()
%%
%% Description: Set a user Account.
%%--------------------------------------------------------------------------
@@ -301,32 +301,32 @@ user(Pid, User, Pass, Acc) ->
account(Pid, Acc) ->
case is_name_sane(Acc) of
- true ->
- call(Pid, {account, Acc}, atom);
- _ ->
- {error, eacct}
+ true ->
+ call(Pid, {account, Acc}, atom);
+ _ ->
+ {error, eacct}
end.
%%--------------------------------------------------------------------------
-%% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn}
-%% Pid = pid()
+%% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn}
+%% Pid = pid()
%% Dir = string()
%%
%% Description: Get the current working directory at remote server.
%%--------------------------------------------------------------------------
-spec pwd(Pid :: pid()) ->
- {'ok', Dir :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'ok', Dir :: string()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
pwd(Pid) ->
call(Pid, pwd, ctrl).
%%--------------------------------------------------------------------------
-%% lpwd(Pid) -> {ok, Dir}
-%% Pid = pid()
+%% lpwd(Pid) -> {ok, Dir}
+%% Pid = pid()
%% Dir = string()
%%
%% Description: Get the current working directory at local server.
@@ -341,8 +341,8 @@ lpwd(Pid) ->
%%--------------------------------------------------------------------------
%% cd(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
-%% Pid = pid()
-%% Dir = string()
+%% Pid = pid()
+%% Dir = string()
%%
%% Description: Change current working directory at remote server.
%%--------------------------------------------------------------------------
@@ -352,17 +352,17 @@ lpwd(Pid) ->
cd(Pid, Dir) ->
case is_name_sane(Dir) of
- true ->
- call(Pid, {cd, Dir}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {cd, Dir}, atom);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
%% lcd(Pid, Dir) -> ok | {error, epath}
-%% Pid = pid()
-%% Dir = string()
+%% Pid = pid()
+%% Dir = string()
%%
%% Description: Change current working directory for the local client.
%%--------------------------------------------------------------------------
@@ -377,9 +377,9 @@ lcd(Pid, Dir) ->
%%--------------------------------------------------------------------------
%% ls(Pid) -> Result
%% ls(Pid, <Dir>) -> Result
-%%
-%% Pid = pid()
-%% Dir = string()
+%%
+%% Pid = pid()
+%% Dir = string()
%% Result = {ok, Listing} | {error, Reason}
%% Listing = string()
%% Reason = epath | elogin | econn
@@ -388,31 +388,31 @@ lcd(Pid, Dir) ->
%%--------------------------------------------------------------------------
-spec ls(Pid :: pid()) ->
- {'ok', Listing :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'ok', Listing :: string()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
ls(Pid) ->
ls(Pid, "").
-spec ls(Pid :: pid(), Dir :: string()) ->
- {'ok', Listing :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'ok', Listing :: string()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
ls(Pid, Dir) ->
case is_name_sane(Dir) of
- true ->
- call(Pid, {dir, long, Dir}, string);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {dir, long, Dir}, string);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
%% nlist(Pid) -> Result
%% nlist(Pid, Pathname) -> Result
-%%
-%% Pid = pid()
-%% Pathname = string()
+%%
+%% Pid = pid()
+%% Pathname = string()
%% Result = {ok, Listing} | {error, Reason}
%% Listing = string()
%% Reason = epath | elogin | econn
@@ -421,30 +421,30 @@ ls(Pid, Dir) ->
%%--------------------------------------------------------------------------
-spec nlist(Pid :: pid()) ->
- {'ok', Listing :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'ok', Listing :: string()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
nlist(Pid) ->
nlist(Pid, "").
-spec nlist(Pid :: pid(), Pathname :: string()) ->
- {'ok', Listing :: string()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ {'ok', Listing :: string()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
nlist(Pid, Dir) ->
case is_name_sane(Dir) of
- true ->
- call(Pid, {dir, short, Dir}, string);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {dir, short, Dir}, string);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
-%% rename(Pid, Old, New) -> ok | {error, epath} | {error, elogin}
+%% rename(Pid, Old, New) -> ok | {error, epath} | {error, elogin}
%% | {error, econn}
-%% Pid = pid()
-%% CurrFile = NewFile = string()
+%% Pid = pid()
+%% CurrFile = NewFile = string()
%%
%% Description: Rename a file at remote server.
%%--------------------------------------------------------------------------
@@ -454,18 +454,18 @@ nlist(Pid, Dir) ->
rename(Pid, Old, New) ->
case {is_name_sane(Old), is_name_sane(New)} of
- {true, true} ->
- call(Pid, {rename, Old, New}, string);
- _ ->
- {error, efnamena}
+ {true, true} ->
+ call(Pid, {rename, Old, New}, string);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
-%% delete(Pid, File) -> ok | {error, epath} | {error, elogin} |
+%% delete(Pid, File) -> ok | {error, epath} | {error, elogin} |
%% {error, econn}
-%% Pid = pid()
-%% File = string()
+%% Pid = pid()
+%% File = string()
%%
%% Description: Remove file at remote server.
%%--------------------------------------------------------------------------
@@ -475,17 +475,17 @@ rename(Pid, Old, New) ->
delete(Pid, File) ->
case is_name_sane(File) of
- true ->
- call(Pid, {delete, File}, string);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {delete, File}, string);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
%% mkdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
-%% Pid = pid(),
-%% Dir = string()
+%% Pid = pid(),
+%% Dir = string()
%%
%% Description: Make directory at remote server.
%%--------------------------------------------------------------------------
@@ -495,17 +495,17 @@ delete(Pid, File) ->
mkdir(Pid, Dir) ->
case is_name_sane(Dir) of
- true ->
- call(Pid, {mkdir, Dir}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {mkdir, Dir}, atom);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
%% rmdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
-%% Pid = pid(),
-%% Dir = string()
+%% Pid = pid(),
+%% Dir = string()
%%
%% Description: Remove directory at remote server.
%%--------------------------------------------------------------------------
@@ -515,24 +515,24 @@ mkdir(Pid, Dir) ->
rmdir(Pid, Dir) ->
case is_name_sane(Dir) of
- true ->
- call(Pid, {rmdir, Dir}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {rmdir, Dir}, atom);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
%% type(Pid, Type) -> ok | {error, etype} | {error, elogin} | {error, econn}
-%% Pid = pid()
-%% Type = ascii | binary
+%% Pid = pid()
+%% Type = ascii | binary
%%
%% Description: Set transfer type.
%%--------------------------------------------------------------------------
-spec type(Pid :: pid(), Type :: ascii | binary) ->
- 'ok' |
- {'error', Reason :: 'etype' | restriction_reason() | common_reason()}.
+ 'ok' |
+ {'error', Reason :: 'etype' | restriction_reason() | common_reason()}.
type(Pid, Type) ->
call(Pid, {type, Type}, atom).
@@ -541,165 +541,165 @@ type(Pid, Type) ->
%%--------------------------------------------------------------------------
%% recv(Pid, RemoteFileName [, LocalFileName]) -> ok | {error, epath} |
%% {error, elogin} | {error, econn}
-%% Pid = pid()
-%% RemoteFileName = LocalFileName = string()
+%% Pid = pid()
+%% RemoteFileName = LocalFileName = string()
%%
%% Description: Transfer file from remote server.
%%--------------------------------------------------------------------------
-spec recv(Pid :: pid(), RemoteFileName :: string()) ->
- 'ok' | {'error', Reason :: restriction_reason() |
- common_reason() |
+ 'ok' | {'error', Reason :: restriction_reason() |
+ common_reason() |
file_write_error_reason()}.
recv(Pid, RemotFileName) ->
recv(Pid, RemotFileName, RemotFileName).
--spec recv(Pid :: pid(),
- RemoteFileName :: string(),
- LocalFileName :: string()) ->
+-spec recv(Pid :: pid(),
+ RemoteFileName :: string(),
+ LocalFileName :: string()) ->
'ok' | {'error', Reason :: term()}.
recv(Pid, RemotFileName, LocalFileName) ->
case is_name_sane(RemotFileName) of
- true ->
- call(Pid, {recv, RemotFileName, LocalFileName}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {recv, RemotFileName, LocalFileName}, atom);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
-%% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin}
-%% | {error, econn}
-%% Pid = pid()
-%% RemoteFile = string()
+%% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin}
+%% | {error, econn}
+%% Pid = pid()
+%% RemoteFile = string()
%% Bin = binary()
%%
%% Description: Transfer file from remote server into binary.
%%--------------------------------------------------------------------------
--spec recv_bin(Pid :: pid(),
- RemoteFile :: string()) ->
- {'ok', Bin :: binary()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+-spec recv_bin(Pid :: pid(),
+ RemoteFile :: string()) ->
+ {'ok', Bin :: binary()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
recv_bin(Pid, RemoteFile) ->
case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {recv_bin, RemoteFile}, bin);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {recv_bin, RemoteFile}, bin);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
-%% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
+%% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
%% | {error, econn}
-%% Pid = pid()
-%% RemoteFile = string()
+%% Pid = pid()
+%% RemoteFile = string()
%%
%% Description: Start receive of chunks of remote file.
%%--------------------------------------------------------------------------
--spec recv_chunk_start(Pid :: pid(),
- RemoteFile :: string()) ->
+-spec recv_chunk_start(Pid :: pid(),
+ RemoteFile :: string()) ->
'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
recv_chunk_start(Pid, RemoteFile) ->
case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {recv_chunk_start, RemoteFile}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {recv_chunk_start, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
%% recv_chunk(Pid, RemoteFile) -> ok | {ok, Bin} | {error, Reason}
-%% Pid = pid()
-%% RemoteFile = string()
+%% Pid = pid()
+%% RemoteFile = string()
%%
%% Description: Transfer file from remote server into binary in chunks
%%--------------------------------------------------------------------------
-spec recv_chunk(Pid :: pid()) ->
- 'ok' |
- {'ok', Bin :: binary()} |
- {'error', Reason :: restriction_reason() | common_reason()}.
+ 'ok' |
+ {'ok', Bin :: binary()} |
+ {'error', Reason :: restriction_reason() | common_reason()}.
recv_chunk(Pid) ->
call(Pid, recv_chunk, atom).
%%--------------------------------------------------------------------------
-%% send(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
-%% | {error, elogin}
+%% send(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
+%% | {error, elogin}
%% | {error, econn}
-%% Pid = pid()
-%% LocalFileName = RemotFileName = string()
+%% Pid = pid()
+%% LocalFileName = RemotFileName = string()
%%
%% Description: Transfer file to remote server.
%%--------------------------------------------------------------------------
-spec send(Pid :: pid(), LocalFileName :: string()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
shortage_reason()}.
send(Pid, LocalFileName) ->
send(Pid, LocalFileName, LocalFileName).
--spec send(Pid :: pid(),
- LocalFileName :: string(),
- RemoteFileName :: string()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
+-spec send(Pid :: pid(),
+ LocalFileName :: string(),
+ RemoteFileName :: string()) ->
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
shortage_reason()}.
send(Pid, LocalFileName, RemotFileName) ->
case is_name_sane(RemotFileName) of
- true ->
- call(Pid, {send, LocalFileName, RemotFileName}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {send, LocalFileName, RemotFileName}, atom);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
-%% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
+%% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
%% | {error, enotbinary} | {error, econn}
-%% Pid = pid()
-%% Bin = binary()
-%% RemoteFile = string()
+%% Pid = pid()
+%% Bin = binary()
+%% RemoteFile = string()
%%
%% Description: Transfer a binary to a remote file.
%%--------------------------------------------------------------------------
-spec send_bin(Pid :: pid(), Bin :: binary(), RemoteFile :: string()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
shortage_reason()}.
send_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {send_bin, Bin, RemoteFile}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {send_bin, Bin, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
end;
send_bin(_Pid, _Bin, _RemoteFile) ->
{error, enotbinary}.
%%--------------------------------------------------------------------------
-%% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
+%% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
%% | {error, econn}
-%% Pid = pid()
-%% RemoteFile = string()
+%% Pid = pid()
+%% RemoteFile = string()
%%
%% Description: Start transfer of chunks to remote file.
%%--------------------------------------------------------------------------
@@ -709,18 +709,18 @@ send_bin(_Pid, _Bin, _RemoteFile) ->
send_chunk_start(Pid, RemoteFile) ->
case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {send_chunk_start, RemoteFile}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {send_chunk_start, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
-%% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} |
+%% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} |
%% {error, epath} | {error, econn}
-%% Pid = pid()
-%% RemoteFile = string()
+%% Pid = pid()
+%% RemoteFile = string()
%%
%% Description: Start append chunks of data to remote file.
%%--------------------------------------------------------------------------
@@ -730,26 +730,26 @@ send_chunk_start(Pid, RemoteFile) ->
append_chunk_start(Pid, RemoteFile) ->
case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {append_chunk_start, RemoteFile}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {append_chunk_start, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
-%% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
+%% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
%% | {error, echunk} | {error, econn}
%% Pid = pid()
-%% Bin = binary().
+%% Bin = binary().
%%
%% Purpose: Send chunk to remote file.
%%--------------------------------------------------------------------------
-spec send_chunk(Pid :: pid(), Bin :: binary()) ->
- 'ok' |
- {'error', Reason :: 'echunk' |
- restriction_reason() |
+ 'ok' |
+ {'error', Reason :: 'echunk' |
+ restriction_reason() |
common_reason()}.
send_chunk(Pid, Bin) when is_binary(Bin) ->
@@ -759,18 +759,18 @@ send_chunk(_Pid, _Bin) ->
%%--------------------------------------------------------------------------
-%% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
-%% | {error, echunk} | {error, econn}
-%% Pid = pid()
-%% Bin = binary()
+%% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
+%% | {error, echunk} | {error, econn}
+%% Pid = pid()
+%% Bin = binary()
%%
%% Description: Append chunk to remote file.
%%--------------------------------------------------------------------------
-spec append_chunk(Pid :: pid(), Bin :: binary()) ->
- 'ok' |
- {'error', Reason :: 'echunk' |
- restriction_reason() |
+ 'ok' |
+ {'error', Reason :: 'echunk' |
+ restriction_reason() |
common_reason()}.
append_chunk(Pid, Bin) when is_binary(Bin) ->
@@ -780,17 +780,17 @@ append_chunk(_Pid, _Bin) ->
%%--------------------------------------------------------------------------
-%% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk}
-%% | {error, econn}
-%% Pid = pid()
+%% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk}
+%% | {error, econn}
+%% Pid = pid()
%%
%% Description: End sending of chunks to remote file.
%%--------------------------------------------------------------------------
-spec send_chunk_end(Pid :: pid()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
shortage_reason()}.
send_chunk_end(Pid) ->
@@ -798,17 +798,17 @@ send_chunk_end(Pid) ->
%%--------------------------------------------------------------------------
-%% append_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk}
-%% | {error, econn}
-%% Pid = pid()
+%% append_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk}
+%% | {error, econn}
+%% Pid = pid()
%%
%% Description: End appending of chunks to remote file.
%%--------------------------------------------------------------------------
-spec append_chunk_end(Pid :: pid()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
shortage_reason()}.
append_chunk_end(Pid) ->
@@ -816,64 +816,64 @@ append_chunk_end(Pid) ->
%%--------------------------------------------------------------------------
-%% append(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
-%% | {error, elogin}
+%% append(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
+%% | {error, elogin}
%% | {error, econn}
-%% Pid = pid()
-%% LocalFileName = RemotFileName = string()
+%% Pid = pid()
+%% LocalFileName = RemotFileName = string()
%%
%% Description: Append the local file to the remote file
%%--------------------------------------------------------------------------
-spec append(Pid :: pid(), LocalFileName :: string()) ->
- 'ok' |
- {'error', Reason :: 'epath' |
- 'elogin' |
- 'etnospc' |
- 'epnospc' |
+ 'ok' |
+ {'error', Reason :: 'epath' |
+ 'elogin' |
+ 'etnospc' |
+ 'epnospc' |
'efnamena' | common_reason()}.
append(Pid, LocalFileName) ->
append(Pid, LocalFileName, LocalFileName).
--spec append(Pid :: pid(),
- LocalFileName :: string(),
- RemoteFileName :: string()) ->
+-spec append(Pid :: pid(),
+ LocalFileName :: string(),
+ RemoteFileName :: string()) ->
'ok' | {'error', Reason :: term()}.
append(Pid, LocalFileName, RemotFileName) ->
case is_name_sane(RemotFileName) of
- true ->
- call(Pid, {append, LocalFileName, RemotFileName}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {append, LocalFileName, RemotFileName}, atom);
+ _ ->
+ {error, efnamena}
end.
%%--------------------------------------------------------------------------
-%% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
-%% | {error, enotbinary} | {error, econn}
-%% Pid = pid()
-%% Bin = binary()
-%% RemoteFile = string()
+%% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
+%% | {error, enotbinary} | {error, econn}
+%% Pid = pid()
+%% Bin = binary()
+%% RemoteFile = string()
%%
%% Purpose: Append a binary to a remote file.
%%--------------------------------------------------------------------------
--spec append_bin(Pid :: pid(),
- Bin :: binary(),
- RemoteFile :: string()) ->
- 'ok' |
- {'error', Reason :: restriction_reason() |
- common_reason() |
+-spec append_bin(Pid :: pid(),
+ Bin :: binary(),
+ RemoteFile :: string()) ->
+ 'ok' |
+ {'error', Reason :: restriction_reason() |
+ common_reason() |
shortage_reason()}.
append_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
case is_name_sane(RemoteFile) of
- true ->
- call(Pid, {append_bin, Bin, RemoteFile}, atom);
- _ ->
- {error, efnamena}
+ true ->
+ call(Pid, {append_bin, Bin, RemoteFile}, atom);
+ _ ->
+ {error, efnamena}
end;
append_bin(_Pid, _Bin, _RemoteFile) ->
{error, enotbinary}.
@@ -881,8 +881,8 @@ append_bin(_Pid, _Bin, _RemoteFile) ->
%%--------------------------------------------------------------------------
%% quote(Pid, Cmd) -> list()
-%% Pid = pid()
-%% Cmd = string()
+%% Pid = pid()
+%% Cmd = string()
%%
%% Description: Send arbitrary ftp command.
%%--------------------------------------------------------------------------
@@ -895,7 +895,7 @@ quote(Pid, Cmd) when is_list(Cmd) ->
%%--------------------------------------------------------------------------
%% close(Pid) -> ok
-%% Pid = pid()
+%% Pid = pid()
%%
%% Description: End the ftp session.
%%--------------------------------------------------------------------------
@@ -909,7 +909,7 @@ close(Pid) ->
%%--------------------------------------------------------------------------
%% formaterror(Tag) -> string()
-%% Tag = atom() | {error, atom()}
+%% Tag = atom() | {error, atom()}
%%
%% Description: Return diagnostics.
%%--------------------------------------------------------------------------
@@ -926,7 +926,7 @@ info(Pid) ->
%%--------------------------------------------------------------------------
%% latest_ctrl_response(Pid) -> string()
-%% Pid = pid()
+%% Pid = pid()
%%
%% Description: The latest received response from the server
%%--------------------------------------------------------------------------
@@ -938,7 +938,7 @@ latest_ctrl_response(Pid) ->
%%%========================================================================
-%%% gen_server callback functions
+%%% gen_server callback functions
%%%========================================================================
%%-------------------------------------------------------------------------
@@ -949,34 +949,34 @@ init(Options) ->
process_flag(trap_exit, true),
%% Keep track of the client
- {value, {client, Client}} = lists:keysearch(client, 1, Options),
+ {value, {client, Client}} = lists:keysearch(client, 1, Options),
erlang:monitor(process, Client),
%% Make sure inet is started
_ = inet_db:start(),
-
+
%% Where are we
{ok, Dir} = file:get_cwd(),
%% Maybe activate dbg
case key_search(debug, Options, disable) of
- trace ->
- dbg:tracer(),
- dbg:p(all, [call]),
- {ok, _} = dbg:tpl(ftp, [{'_', [], [{return_trace}]}]),
- {ok, _} = dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]),
- {ok, _} = dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]),
- ok;
- debug ->
- dbg:tracer(),
- dbg:p(all, [call]),
- {ok, _} = dbg:tp(ftp, [{'_', [], [{return_trace}]}]),
- {ok, _} = dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]),
- {ok, _} = dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]),
- ok;
- _ ->
- %% Keep silent
- ok
+ trace ->
+ dbg:tracer(),
+ dbg:p(all, [call]),
+ {ok, _} = dbg:tpl(ftp, [{'_', [], [{return_trace}]}]),
+ {ok, _} = dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]),
+ {ok, _} = dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]),
+ ok;
+ debug ->
+ dbg:tracer(),
+ dbg:p(all, [call]),
+ {ok, _} = dbg:tp(ftp, [{'_', [], [{return_trace}]}]),
+ {ok, _} = dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]),
+ {ok, _} = dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]),
+ ok;
+ _ ->
+ %% Keep silent
+ ok
end,
%% Verbose?
@@ -985,11 +985,11 @@ init(Options) ->
%% IpFamily?
IpFamily = key_search(ipfamily, Options, inet),
- State = #state{owner = Client,
- verbose = Verbose,
- ipfamily = IpFamily,
- ldir = Dir},
-
+ State = #state{owner = Client,
+ verbose = Verbose,
+ ipfamily = IpFamily,
+ ldir = Dir},
+
%% Set process prio
Priority = key_search(priority, Options, low),
process_flag(priority, Priority),
@@ -1004,26 +1004,26 @@ init(Options) ->
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
-%% Description: Handle incoming requests.
+%% Description: Handle incoming requests.
%%-------------------------------------------------------------------------
%% Anyone can ask this question
handle_call({_, info}, _, #state{verbose = Verbose,
- mode = Mode,
- timeout = Timeout,
- ipfamily = IpFamily,
- csock = Socket,
- progress = Progress} = State) ->
+ mode = Mode,
+ timeout = Timeout,
+ ipfamily = IpFamily,
+ csock = Socket,
+ progress = Progress} = State) ->
{ok, {_, LocalPort}} = sockname(Socket),
{ok, {Address, Port}} = peername(Socket),
- Options = [{verbose, Verbose},
- {ipfamily, IpFamily},
- {mode, Mode},
- {peer, Address},
- {peer_port, Port},
- {local_port, LocalPort},
- {timeout, Timeout},
- {progress, Progress}],
+ Options = [{verbose, Verbose},
+ {ipfamily, IpFamily},
+ {mode, Mode},
+ {peer, Address},
+ {peer_port, Port},
+ {local_port, LocalPort},
+ {timeout, Timeout},
+ {progress, Progress}],
{reply, {ok, Options}, State};
handle_call({_,latest_ctrl_response}, _, #state{latest_ctrl_response=Resp} = State) ->
@@ -1035,91 +1035,91 @@ handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid ->
handle_call({_, {open, ip_comm, Opts, {CtrlOpts, DataPassOpts, DataActOpts}}}, From, State) ->
case key_search(host, Opts, undefined) of
- undefined ->
- {stop, normal, {error, ehost}, State};
- Host ->
- Mode = key_search(mode, Opts, ?DEFAULT_MODE),
- Port = key_search(port, Opts, ?FTP_PORT),
- Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT),
- 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,
+ undefined ->
+ {stop, normal, {error, ehost}, State};
+ Host ->
+ Mode = key_search(mode, Opts, ?DEFAULT_MODE),
+ Port = key_search(port, Opts, ?FTP_PORT),
+ Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT),
+ 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,
sockopts_ctrl = CtrlOpts,
sockopts_data_passive = DataPassOpts,
sockopts_data_active = DataActOpts,
- dtimeout = DTimeout,
- ftp_extension = FtpExt},
-
- case setup_ctrl_connection(Host, Port, Timeout, State2) of
- {ok, State3, WaitTimeout} ->
- {noreply, State3, WaitTimeout};
- {error, _Reason} ->
- gen_server:reply(From, {error, ehost}),
- {stop, normal, State2#state{client = undefined}}
- end
- end;
+ dtimeout = DTimeout,
+ ftp_extension = FtpExt},
+
+ case setup_ctrl_connection(Host, Port, Timeout, State2) of
+ {ok, State3, WaitTimeout} ->
+ {noreply, State3, WaitTimeout};
+ {error, _Reason} ->
+ gen_server:reply(From, {error, ehost}),
+ {stop, normal, State2#state{client = undefined}}
+ end
+ end;
handle_call({_, {open, tls_upgrade, TLSOptions}}, From, State0) ->
- _ = send_ctrl_message(State0, mk_cmd("AUTH TLS", [])),
+ _ = send_ctrl_message(State0, mk_cmd("AUTH TLS", [])),
State = activate_ctrl_connection(State0),
{noreply, State#state{client = From, caller = open, tls_options = TLSOptions}};
-handle_call({_, {user, User, Password}}, From,
- #state{csock = CSock} = State) when (CSock =/= undefined) ->
+handle_call({_, {user, User, Password}}, From,
+ #state{csock = CSock} = State) when (CSock =/= undefined) ->
handle_user(User, Password, "", State#state{client = From});
-handle_call({_, {user, User, Password, Acc}}, From,
- #state{csock = CSock} = State) when (CSock =/= undefined) ->
+handle_call({_, {user, User, Password, Acc}}, From,
+ #state{csock = CSock} = State) when (CSock =/= undefined) ->
handle_user(User, Password, Acc, State#state{client = From});
-
+
handle_call({_, {account, Acc}}, From, State)->
handle_user_account(Acc, State#state{client = From});
handle_call({_, pwd}, From, #state{chunk = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("PWD", [])),
+ _ = send_ctrl_message(State0, mk_cmd("PWD", [])),
State = activate_ctrl_connection(State0),
{noreply, State#state{client = From, caller = pwd}};
-handle_call({_, lpwd}, From, #state{ldir = LDir} = State) ->
+handle_call({_, lpwd}, From, #state{ldir = LDir} = State) ->
{reply, {ok, LDir}, State#state{client = From}};
-handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])),
+handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State0) ->
+ _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])),
State = activate_ctrl_connection(State0),
{noreply, State#state{client = From, caller = cd}};
handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) ->
LDir = filename:absname(Dir, LDir0),
case file:read_file_info(LDir) of %% FIX better check that LDir is a dir.
- {ok, _ } ->
- {reply, ok, State#state{ldir = LDir}};
- _ ->
- {reply, {error, epath}, State}
+ {ok, _ } ->
+ {reply, ok, State#state{ldir = LDir}};
+ _ ->
+ {reply, {error, epath}, State}
end;
-handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From,
- #state{chunk = false} = State) ->
+handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From,
+ #state{chunk = false} = State) ->
setup_data_connection(State#state{caller = {dir, Dir, Len},
- client = From});
+ client = From});
handle_call({_, {rename, CurrFile, NewFile}}, From,
- #state{chunk = false} = State0) ->
+ #state{chunk = false} = State0) ->
_ = send_ctrl_message(State0, mk_cmd("RNFR ~s", [CurrFile])),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {rename, NewFile}, client = From}};
-handle_call({_, {delete, File}}, {_Pid, _} = From,
- #state{chunk = false} = State0) ->
+handle_call({_, {delete, File}}, {_Pid, _} = From,
+ #state{chunk = false} = State0) ->
_ = send_ctrl_message(State0, mk_cmd("DELE ~s", [File])),
State = activate_ctrl_connection(State0),
{noreply, State#state{client = From}};
-handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State0) ->
+handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State0) ->
_ = send_ctrl_message(State0, mk_cmd("MKD ~s", [Dir])),
State = activate_ctrl_connection(State0),
{noreply, State#state{client = From}};
@@ -1129,50 +1129,50 @@ handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State0) ->
State = activate_ctrl_connection(State0),
{noreply, State#state{client = From}};
-handle_call({_,{type, Type}}, From, #state{chunk = false} = State0) ->
+handle_call({_,{type, Type}}, From, #state{chunk = false} = State0) ->
case Type of
- ascii ->
- _ = send_ctrl_message(State0, mk_cmd("TYPE A", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = type, type = ascii,
- client = From}};
- binary ->
- _ = send_ctrl_message(State0, mk_cmd("TYPE I", [])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = type, type = binary,
- client = From}};
- _ ->
- {reply, {error, etype}, State0}
+ ascii ->
+ _ = send_ctrl_message(State0, mk_cmd("TYPE A", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = type, type = ascii,
+ client = From}};
+ binary ->
+ _ = send_ctrl_message(State0, mk_cmd("TYPE I", [])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = type, type = binary,
+ client = From}};
+ _ ->
+ {reply, {error, etype}, State0}
end;
-handle_call({_,{recv, RemoteFile, LocalFile}}, From,
- #state{chunk = false, ldir = LocalDir} = State) ->
+handle_call({_,{recv, RemoteFile, LocalFile}}, From,
+ #state{chunk = false, ldir = LocalDir} = State) ->
progress_report({remote_file, RemoteFile}, State),
NewLocalFile = filename:absname(LocalFile, LocalDir),
case file_open(NewLocalFile, write) of
- {ok, Fd} ->
- setup_data_connection(State#state{client = From,
- caller =
- {recv_file,
- RemoteFile, Fd}});
- {error, _What} ->
- {reply, {error, epath}, State}
+ {ok, Fd} ->
+ setup_data_connection(State#state{client = From,
+ caller =
+ {recv_file,
+ RemoteFile, Fd}});
+ {error, _What} ->
+ {reply, {error, epath}, State}
end;
-handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} =
- State) ->
+handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} =
+ State) ->
setup_data_connection(State#state{caller = {recv_bin, RemoteFile},
- client = From});
+ client = From});
-handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false}
- = State) ->
+handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false}
+ = State) ->
setup_data_connection(State#state{caller = {start_chunk_transfer,
- "RETR", RemoteFile},
- client = From});
+ "RETR", RemoteFile},
+ client = From});
handle_call({_, recv_chunk}, _, #state{chunk = false} = State) ->
- {reply, {error, "ftp:recv_chunk_start/2 not called"}, State};
+ {reply, {error, "ftp:recv_chunk_start/2 not called"}, State};
handle_call({_, recv_chunk}, _From, #state{chunk = true,
caller = #recv_chunk_closing{dconn_closed = true,
@@ -1185,7 +1185,7 @@ handle_call({_, recv_chunk}, _From, #state{chunk = true,
{reply, ok, State#state{caller = undefined,
chunk = false,
client = undefined}};
-
+
handle_call({_, recv_chunk}, From, #state{chunk = true,
caller = #recv_chunk_closing{} = R
} = State) ->
@@ -1196,41 +1196,41 @@ handle_call({_, recv_chunk}, From, #state{chunk = true,
handle_call({_, recv_chunk}, From, #state{chunk = true} = State0) ->
State = activate_data_connection(State0),
{noreply, State#state{client = From, caller = recv_chunk}};
-
-handle_call({_, {send, LocalFile, RemoteFile}}, From,
- #state{chunk = false, ldir = LocalDir} = State) ->
- progress_report({local_file, filename:absname(LocalFile, LocalDir)},
- State),
+
+handle_call({_, {send, LocalFile, RemoteFile}}, From,
+ #state{chunk = false, ldir = LocalDir} = State) ->
+ progress_report({local_file, filename:absname(LocalFile, LocalDir)},
+ State),
setup_data_connection(State#state{caller = {transfer_file,
- {"STOR",
- LocalFile, RemoteFile}},
- client = From});
-handle_call({_, {append, LocalFile, RemoteFile}}, From,
- #state{chunk = false} = State) ->
+ {"STOR",
+ LocalFile, RemoteFile}},
+ client = From});
+handle_call({_, {append, LocalFile, RemoteFile}}, From,
+ #state{chunk = false} = State) ->
setup_data_connection(State#state{caller = {transfer_file,
- {"APPE",
- LocalFile, RemoteFile}},
- client = From});
-handle_call({_, {send_bin, Bin, RemoteFile}}, From,
- #state{chunk = false} = State) ->
+ {"APPE",
+ LocalFile, RemoteFile}},
+ client = From});
+handle_call({_, {send_bin, Bin, RemoteFile}}, From,
+ #state{chunk = false} = State) ->
setup_data_connection(State#state{caller = {transfer_data,
- {"STOR", Bin, RemoteFile}},
- client = From});
-handle_call({_,{append_bin, Bin, RemoteFile}}, From,
- #state{chunk = false} = State) ->
+ {"STOR", Bin, RemoteFile}},
+ client = From});
+handle_call({_,{append_bin, Bin, RemoteFile}}, From,
+ #state{chunk = false} = State) ->
setup_data_connection(State#state{caller = {transfer_data,
- {"APPE", Bin, RemoteFile}},
- client = From});
-handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false}
- = State) ->
+ {"APPE", Bin, RemoteFile}},
+ client = From});
+handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false}
+ = State) ->
setup_data_connection(State#state{caller = {start_chunk_transfer,
- "STOR", RemoteFile},
- client = From});
-handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false}
- = State) ->
+ "STOR", RemoteFile},
+ client = From});
+handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false}
+ = State) ->
setup_data_connection(State#state{caller = {start_chunk_transfer,
- "APPE", RemoteFile},
- client = From});
+ "APPE", RemoteFile},
+ client = From});
handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) ->
send_data_message(State, Bin),
{reply, ok, State};
@@ -1241,8 +1241,8 @@ handle_call({_, {transfer_chunk, _}}, _, #state{chunk = false} = State) ->
handle_call({_, chunk_end}, From, #state{chunk = true} = State0) ->
close_data_connection(State0),
State = activate_ctrl_connection(State0),
- {noreply, State#state{client = From, dsock = undefined,
- caller = end_chunk_transfer, chunk = false}};
+ {noreply, State#state{client = From, dsock = undefined,
+ caller = end_chunk_transfer, chunk = false}};
handle_call({_, chunk_end}, _, #state{chunk = false} = State) ->
{reply, {error, echunk}, State};
@@ -1252,24 +1252,24 @@ handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State0) ->
State = activate_ctrl_connection(State0),
{noreply, State#state{client = From, caller = quote}};
-handle_call({_, _Req}, _From, #state{csock = CSock} = State)
+handle_call({_, _Req}, _From, #state{csock = CSock} = State)
when (CSock =:= undefined) ->
{reply, {error, not_connected}, State};
handle_call(_, _, #state{chunk = true} = State) ->
{reply, {error, echunk}, State};
-%% Catch all - This can only happen if the application programmer writes
+%% Catch all - This can only happen if the application programmer writes
%% really bad code that violates the API.
handle_call(Request, _Timeout, State) ->
{stop, {'API_violation_connection_closed', Request},
{error, {connection_terminated, 'API_violation'}}, State}.
%%--------------------------------------------------------------------------
-%% handle_cast(Request, State) -> {noreply, State} |
+%% handle_cast(Request, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handles cast messages.
+%% {stop, Reason, State}
+%% Description: Handles cast messages.
%%-------------------------------------------------------------------------
handle_cast({Pid, close}, #state{owner = Pid} = State) ->
_ = send_ctrl_message(State, mk_cmd("QUIT", [])),
@@ -1279,18 +1279,18 @@ handle_cast({Pid, close}, #state{owner = Pid} = State) ->
handle_cast({Pid, close}, State) ->
Report = io_lib:format("A none owner process ~p tried to close an "
- "ftp connection: ~n", [Pid]),
+ "ftp connection: ~n", [Pid]),
error_logger:info_report(Report),
{noreply, State};
-%% Catch all - This can oly happen if the application programmer writes
+%% Catch all - This can oly happen if the application programmer writes
%% really bad code that violates the API.
handle_cast(Msg, State) ->
{stop, {'API_violation_connection_closed', Msg}, State}.
%%--------------------------------------------------------------------------
%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} |
-%% {stop, Reason, State}
+%% {stop, Reason, State}
%% Description: Handles tcp messages from the ftp-server.
%% Note: The order of the function clauses is significant.
%%--------------------------------------------------------------------------
@@ -1302,18 +1302,18 @@ handle_info(timeout, State) ->
{noreply, State};
%%% Data socket messages %%%
-handle_info({Trpt, Socket, Data},
- #state{dsock = {Trpt,Socket},
- caller = {recv_file, Fd}} = State0) when Trpt==tcp;Trpt==ssl ->
+handle_info({Trpt, Socket, Data},
+ #state{dsock = {Trpt,Socket},
+ caller = {recv_file, Fd}} = State0) when Trpt==tcp;Trpt==ssl ->
?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]),
ok = file_write(binary_to_list(Data), Fd),
progress_report({binary, Data}, State0),
State = activate_data_connection(State0),
{noreply, State};
-handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}, client = From,
- caller = recv_chunk}
- = State) when Trpt==tcp;Trpt==ssl ->
+handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}, client = From,
+ caller = recv_chunk}
+ = State) when Trpt==tcp;Trpt==ssl ->
?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State]),
gen_server:reply(From, {ok, Data}),
{noreply, State#state{client = undefined, data = <<>>}};
@@ -1322,10 +1322,10 @@ handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}} = State0) when T
?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]),
State = activate_data_connection(State0),
{noreply, State#state{data = <<(State#state.data)/binary,
- Data/binary>>}};
+ Data/binary>>}};
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
- caller = {recv_file, Fd}} = State0)
+ caller = {recv_file, Fd}} = State0)
when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
file_close(Fd),
progress_report({transfer_size, 0}, State0),
@@ -1335,7 +1335,7 @@ handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
client = Client,
- caller = recv_chunk} = State0)
+ caller = recv_chunk} = State0)
when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
?DBG("Data channel close recv_chunk",[]),
State = activate_ctrl_connection(State0),
@@ -1344,81 +1344,81 @@ handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
client_called_us = Client =/= undefined}
}};
-handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin,
- data = Data} = State0)
+handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin,
+ data = Data} = State0)
when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
?DBG("Data channel close",[]),
State = activate_ctrl_connection(State0),
- {noreply, State#state{dsock = undefined, data = <<>>,
- caller = {recv_bin, Data}}};
+ {noreply, State#state{dsock = undefined, data = <<>>,
+ caller = {recv_bin, Data}}};
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, data = Data,
- caller = {handle_dir_result, Dir}}
- = State0) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ caller = {handle_dir_result, Dir}}
+ = State0) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
?DBG("Data channel close",[]),
State = activate_ctrl_connection(State0),
- {noreply, State#state{dsock = undefined,
- caller = {handle_dir_result, Dir, Data},
-% data = <<?CR,?LF>>}};
- data = <<>>}};
+ {noreply, State#state{dsock = undefined,
+ caller = {handle_dir_result, Dir, Data},
+% data = <<?CR,?LF>>}};
+ data = <<>>}};
handle_info({Err, Socket, Reason}, #state{dsock = {Trpt,Socket},
- client = From} = State)
+ client = From} = State)
when {Err,Trpt}=={tcp_error,tcp} ; {Err,Trpt}=={ssl_error,ssl} ->
gen_server:reply(From, {error, Reason}),
close_data_connection(State),
{noreply, State#state{dsock = undefined, client = undefined,
- data = <<>>, caller = undefined, chunk = false}};
+ data = <<>>, caller = undefined, chunk = false}};
%%% Ctrl socket messages %%%
-handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket},
- verbose = Verbose,
- caller = Caller,
- client = From,
- ctrl_data = {BinCtrlData, AccLines,
- LineStatus}}
- = State0) ->
+handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket},
+ verbose = Verbose,
+ caller = Caller,
+ client = From,
+ ctrl_data = {BinCtrlData, AccLines,
+ LineStatus}}
+ = State0) ->
?DBG('--ctrl ~p ----> ~s~p~n',[Socket,<<BinCtrlData/binary, Data/binary>>,State]),
case ftp_response:parse_lines(<<BinCtrlData/binary, Data/binary>>,
- AccLines, LineStatus) of
- {ok, Lines, NextMsgData} ->
- verbose(Lines, Verbose, 'receive'),
- CtrlResult = ftp_response:interpret(Lines),
- case Caller of
- quote ->
- gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])),
- {noreply, State0#state{client = undefined,
+ AccLines, LineStatus) of
+ {ok, Lines, NextMsgData} ->
+ verbose(Lines, Verbose, 'receive'),
+ CtrlResult = ftp_response:interpret(Lines),
+ case Caller of
+ quote ->
+ gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])),
+ {noreply, State0#state{client = undefined,
caller = undefined,
latest_ctrl_response = Lines,
- ctrl_data = {NextMsgData, [],
+ ctrl_data = {NextMsgData, [],
start}}};
- _ ->
- ?DBG(' ...handle_ctrl_result(~p,...) ctrl_data=~p~n',[CtrlResult,{NextMsgData, [], start}]),
- handle_ctrl_result(CtrlResult,
- State0#state{latest_ctrl_response = Lines,
- ctrl_data =
+ _ ->
+ ?DBG(' ...handle_ctrl_result(~p,...) ctrl_data=~p~n',[CtrlResult,{NextMsgData, [], start}]),
+ handle_ctrl_result(CtrlResult,
+ State0#state{latest_ctrl_response = Lines,
+ ctrl_data =
{NextMsgData, [], start}})
- end;
- {continue, CtrlData} when CtrlData =/= State0#state.ctrl_data ->
- ?DBG(' ...Continue... ctrl_data=~p~n',[CtrlData]),
- State1 = State0#state{ctrl_data = CtrlData},
- State = activate_ctrl_connection(State1),
- {noreply, State};
- {continue, _CtrlData} ->
- ?DBG(' ...Continue... ctrl_data=~p~n',[_CtrlData]),
- {noreply, State0}
+ end;
+ {continue, CtrlData} when CtrlData =/= State0#state.ctrl_data ->
+ ?DBG(' ...Continue... ctrl_data=~p~n',[CtrlData]),
+ State1 = State0#state{ctrl_data = CtrlData},
+ State = activate_ctrl_connection(State1),
+ {noreply, State};
+ {continue, _CtrlData} ->
+ ?DBG(' ...Continue... ctrl_data=~p~n',[_CtrlData]),
+ {noreply, State0}
end;
-%% If the server closes the control channel it is
+%% If the server closes the control channel it is
%% the expected behavior that connection process terminates.
-handle_info({Cls, Socket}, #state{csock = {Trpt, Socket}})
- when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+handle_info({Cls, Socket}, #state{csock = {Trpt, Socket}})
+ when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
exit(normal); %% User will get error message from terminate/2
handle_info({Err, Socket, Reason}, _) when Err==tcp_error ; Err==ssl_error ->
- Report =
- io_lib:format("~p on socket: ~p for reason: ~p~n",
- [Err, Socket, Reason]),
+ Report =
+ io_lib:format("~p on socket: ~p for reason: ~p~n",
+ [Err, Socket, Reason]),
error_logger:error_report(Report),
%% If tcp does not work the only option is to terminate,
%% this is the expected behavior under these circumstances.
@@ -1431,26 +1431,26 @@ handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) ->
handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) ->
{stop, normal, State#state{client = undefined}};
-
+
handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) ->
{stop, normal, State#state{client = undefined}};
-
+
handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) ->
{stop, {stopped, {'EXIT', Process, Reason}},
State#state{client = undefined}};
handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) ->
Report = io_lib:format("Progress reporting stopped for reason ~p~n",
- [Reason]),
+ [Reason]),
error_logger:info_report(Report),
{noreply, State#state{progress = ignore}};
-
+
%% Catch all - throws away unknown messages (This could happen by "accident"
%% so we do not want to crash, but we make a log entry as it is an
-%% unwanted behaviour.)
+%% unwanted behaviour.)
handle_info(Info, State) ->
Report = io_lib:format("ftp : ~p : Unexpected message: ~p~nState: ~p~n",
- [self(), Info, State]),
+ [self(), Info, State]),
error_logger:info_report(Report),
{noreply, State}.
@@ -1460,9 +1460,9 @@ handle_info(Info, State) ->
terminate(normal, State) ->
%% If terminate reason =/= normal the progress reporting process will
%% be killed by the exit signal.
- progress_report(stop, State),
+ progress_report(stop, State),
do_terminate({error, econn}, State);
-terminate(Reason, State) ->
+terminate(Reason, State) ->
Report = io_lib:format("Ftp connection closed due to: ~p~n", [Reason]),
error_logger:error_report(Report),
do_terminate({error, eclosed}, State).
@@ -1471,66 +1471,66 @@ do_terminate(ErrorMsg, State) ->
close_data_connection(State),
close_ctrl_connection(State),
case State#state.client of
- undefined ->
- ok;
- From ->
- gen_server:reply(From, ErrorMsg)
+ undefined ->
+ ok;
+ From ->
+ gen_server:reply(From, ErrorMsg)
end,
- ok.
+ ok.
code_change(_Vsn, State1, upgrade_from_pre_5_12) ->
- {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
- Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1,
- IpFamily =
- if
- (IPv6Disable =:= true) ->
- inet;
- true ->
- inet6fb4
- end,
+ {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
+ Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1,
+ IpFamily =
+ if
+ (IPv6Disable =:= true) ->
+ inet;
+ true ->
+ inet6fb4
+ end,
State2 = #state{csock = CSock,
- dsock = DSock,
- verbose = Verbose,
- ldir = LDir,
- type = Type,
- chunk = Chunk,
- mode = Mode,
- timeout = Timeout,
- data = Data,
- ctrl_data = CtrlData,
- owner = Owner,
- client = Client,
- caller = Caller,
- ipfamily = IpFamily,
- progress = Progress},
+ dsock = DSock,
+ verbose = Verbose,
+ ldir = LDir,
+ type = Type,
+ chunk = Chunk,
+ mode = Mode,
+ timeout = Timeout,
+ data = Data,
+ ctrl_data = CtrlData,
+ owner = Owner,
+ client = Client,
+ caller = Caller,
+ ipfamily = IpFamily,
+ progress = Progress},
{ok, State2};
code_change(_Vsn, State1, downgrade_to_pre_5_12) ->
#state{csock = CSock,
- dsock = DSock,
- verbose = Verbose,
- ldir = LDir,
- type = Type,
- chunk = Chunk,
- mode = Mode,
- timeout = Timeout,
- data = Data,
- ctrl_data = CtrlData,
- owner = Owner,
- client = Client,
- caller = Caller,
- ipfamily = IpFamily,
- progress = Progress} = State1,
- IPv6Disable =
- if
- (IpFamily =:= inet) ->
- true;
- true ->
- false
- end,
- State2 =
- {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
- Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress},
+ dsock = DSock,
+ verbose = Verbose,
+ ldir = LDir,
+ type = Type,
+ chunk = Chunk,
+ mode = Mode,
+ timeout = Timeout,
+ data = Data,
+ ctrl_data = CtrlData,
+ owner = Owner,
+ client = Client,
+ caller = Caller,
+ ipfamily = IpFamily,
+ progress = Progress} = State1,
+ IPv6Disable =
+ if
+ (IpFamily =:= inet) ->
+ true;
+ true ->
+ false
+ end,
+ State2 =
+ {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
+ Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress},
{ok, State2};
code_change(_Vsn, State, _Extra) ->
@@ -1541,10 +1541,10 @@ code_change(_Vsn, State, _Extra) ->
%% Start/stop
%%%=========================================================================
%%--------------------------------------------------------------------------
-%% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason}
-%%
-%% Description: Callback function for the ftp supervisor. It is called
-%% : when start_service/1 calls ftp_sup:start_child/1 to start an
+%% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason}
+%%
+%% Description: Callback function for the ftp supervisor. It is called
+%% : when start_service/1 calls ftp_sup:start_child/1 to start an
%% : instance of the ftp process. Also called by start_standalone/1
%%--------------------------------------------------------------------------
start_link([Opts, GenServerOptions]) ->
@@ -1552,12 +1552,12 @@ start_link([Opts, GenServerOptions]) ->
start_link(Opts, GenServerOptions) ->
case lists:keysearch(client, 1, Opts) of
- {value, _} ->
- %% Via the supervisor
- gen_server:start_link(?MODULE, Opts, GenServerOptions);
- false ->
- Opts2 = [{client, self()} | Opts],
- gen_server:start_link(?MODULE, Opts2, GenServerOptions)
+ {value, _} ->
+ %% Via the supervisor
+ gen_server:start_link(?MODULE, Opts, GenServerOptions);
+ false ->
+ Opts2 = [{client, self()} | Opts],
+ gen_server:start_link(?MODULE, Opts2, GenServerOptions)
end.
@@ -1570,7 +1570,7 @@ start_link(Opts, GenServerOptions) ->
%%--------------------------------------------------------------------------
%%% Help functions to handle_call and/or handle_ctrl_result
%%--------------------------------------------------------------------------
-%% User handling
+%% User handling
handle_user(User, Password, Acc, State0) ->
_ = send_ctrl_message(State0, mk_cmd("USER ~s", [User])),
State = activate_ctrl_connection(State0),
@@ -1588,26 +1588,26 @@ handle_user_account(Acc, State0) ->
%%--------------------------------------------------------------------------
-%% handle_ctrl_result
+%% handle_ctrl_result
%%--------------------------------------------------------------------------
handle_ctrl_result({tls_upgrade, _}, #state{csock = {tcp, Socket},
- tls_options = TLSOptions,
- timeout = Timeout,
- caller = open, client = From}
- = State0) ->
+ tls_options = TLSOptions,
+ timeout = Timeout,
+ caller = open, client = From}
+ = State0) ->
?DBG('<--ctrl ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
case ssl:connect(Socket, TLSOptions, Timeout) of
- {ok, TLSSocket} ->
- State1 = State0#state{csock = {ssl,TLSSocket}},
- _ = send_ctrl_message(State1, mk_cmd("PBSZ 0", [])),
- State = activate_ctrl_connection(State1),
- {noreply, State#state{tls_upgrading_data_connection = {true, pbsz}} };
- {error, _} = Error ->
- gen_server:reply(From, {Error, self()}),
- {stop, normal, State0#state{client = undefined,
- caller = undefined,
- tls_upgrading_data_connection = false}}
- end;
+ {ok, TLSSocket} ->
+ State1 = State0#state{csock = {ssl,TLSSocket}},
+ _ = send_ctrl_message(State1, mk_cmd("PBSZ 0", [])),
+ State = activate_ctrl_connection(State1),
+ {noreply, State#state{tls_upgrading_data_connection = {true, pbsz}} };
+ {error, _} = Error ->
+ gen_server:reply(From, {Error, self()}),
+ {stop, normal, State0#state{client = undefined,
+ caller = undefined,
+ tls_upgrading_data_connection = false}}
+ end;
handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, pbsz}} = State0) ->
_ = send_ctrl_message(State0, mk_cmd("PROT P", [])),
@@ -1615,223 +1615,223 @@ handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true,
{noreply, State#state{tls_upgrading_data_connection = {true, prot}}};
handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, prot},
- client = From} = State) ->
- gen_server:reply(From, {ok, self()}),
+ client = From} = State) ->
+ gen_server:reply(From, {ok, self()}),
{noreply, State#state{client = undefined,
- caller = undefined,
- tls_upgrading_data_connection = false}};
-
-handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From}
- = State) ->
- gen_server:reply(From, {ok, self()}),
- {noreply, State#state{client = undefined,
- caller = undefined }};
+ caller = undefined,
+ tls_upgrading_data_connection = false}};
+
+handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From}
+ = State) ->
+ gen_server:reply(From, {ok, self()}),
+ {noreply, State#state{client = undefined,
+ caller = undefined }};
handle_ctrl_result({_, Lines}, #state{caller = open} = State) ->
ctrl_result_response(econn, State, {error, Lines});
%%--------------------------------------------------------------------------
-%% Data connection setup active mode
-handle_ctrl_result({pos_compl, _Lines},
- #state{mode = active,
- caller = {setup_data_connection,
- {LSock, Caller}}} = State) ->
+%% Data connection setup active mode
+handle_ctrl_result({pos_compl, _Lines},
+ #state{mode = active,
+ caller = {setup_data_connection,
+ {LSock, Caller}}} = State) ->
handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}});
-handle_ctrl_result({Status, _Lines},
- #state{mode = active,
- caller = {setup_data_connection, {LSock, _}}}
- = State) ->
+handle_ctrl_result({Status, _Lines},
+ #state{mode = active,
+ caller = {setup_data_connection, {LSock, _}}}
+ = State) ->
close_connection({tcp,LSock}),
ctrl_result_response(Status, State, {error, Status});
-%% Data connection setup passive mode
-handle_ctrl_result({pos_compl, Lines},
- #state{mode = passive,
- ipfamily = inet6,
- client = From,
- caller = {setup_data_connection, Caller},
- csock = CSock,
+%% Data connection setup passive mode
+handle_ctrl_result({pos_compl, Lines},
+ #state{mode = passive,
+ ipfamily = inet6,
+ client = From,
+ caller = {setup_data_connection, Caller},
+ csock = CSock,
sockopts_data_passive = SockOpts,
- timeout = Timeout}
- = State) ->
+ timeout = Timeout}
+ = State) ->
[_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")),
{ok, {IP, _}} = peername(CSock),
case connect(IP, list_to_integer(PortStr), SockOpts, 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}}
+ {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;
-handle_ctrl_result({pos_compl, Lines},
- #state{mode = passive,
- ipfamily = inet,
- client = From,
- caller = {setup_data_connection, Caller},
- timeout = Timeout,
+handle_ctrl_result({pos_compl, Lines},
+ #state{mode = passive,
+ ipfamily = inet,
+ client = From,
+ caller = {setup_data_connection, Caller},
+ timeout = Timeout,
sockopts_data_passive = SockOpts,
- ftp_extension = false} = State) ->
-
- {_, [?LEFT_PAREN | Rest]} =
- lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines),
+ ftp_extension = false} = State) ->
+
+ {_, [?LEFT_PAREN | Rest]} =
+ lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines),
{NewPortAddr, _} =
- lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest),
- [A1, A2, A3, A4, P1, P2] =
- lists:map(fun(X) -> list_to_integer(X) end,
- string:tokens(NewPortAddr, [$,])),
- IP = {A1, A2, A3, A4},
- Port = (P1 * 256) + P2,
+ lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest),
+ [A1, A2, A3, A4, P1, P2] =
+ lists:map(fun(X) -> list_to_integer(X) end,
+ string:tokens(NewPortAddr, [$,])),
+ IP = {A1, A2, A3, A4},
+ Port = (P1 * 256) + P2,
?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]),
case connect(IP, Port, SockOpts, 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}}
+ {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;
-handle_ctrl_result({pos_compl, Lines},
- #state{mode = passive,
- ipfamily = inet,
- client = From,
- caller = {setup_data_connection, Caller},
- csock = CSock,
- timeout = Timeout,
+handle_ctrl_result({pos_compl, Lines},
+ #state{mode = passive,
+ ipfamily = inet,
+ client = From,
+ caller = {setup_data_connection, Caller},
+ csock = CSock,
+ timeout = Timeout,
sockopts_data_passive = SockOpts,
- ftp_extension = true} = State) ->
-
+ 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), SockOpts, 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}}
+ case connect(IP, list_to_integer(PortStr), SockOpts, 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,
- caller = {setup_data_connection, Caller}} = State) ->
+handle_ctrl_result(_,
+ #state{mode = passive,
+ caller = {setup_data_connection, Caller}} = State) ->
setup_data_connection(State#state{mode = active, caller = Caller});
-
+
%%--------------------------------------------------------------------------
-%% User handling
-handle_ctrl_result({pos_interm, _},
- #state{caller = {handle_user, PassWord, Acc}} = State) ->
+%% User handling
+handle_ctrl_result({pos_interm, _},
+ #state{caller = {handle_user, PassWord, Acc}} = State) ->
handle_user_passwd(PassWord, Acc, State);
-handle_ctrl_result({Status, _},
- #state{caller = {handle_user, _, _}} = State) ->
+handle_ctrl_result({Status, _},
+ #state{caller = {handle_user, _, _}} = State) ->
ctrl_result_response(Status, State, {error, euser});
-%% Accounts
-handle_ctrl_result({pos_interm_acct, _},
- #state{caller = {handle_user_passwd, Acc}} = State)
+%% Accounts
+handle_ctrl_result({pos_interm_acct, _},
+ #state{caller = {handle_user_passwd, Acc}} = State)
when Acc =/= "" ->
handle_user_account(Acc, State);
handle_ctrl_result({Status, _},
- #state{caller = {handle_user_passwd, _}} = State) ->
+ #state{caller = {handle_user_passwd, _}} = State) ->
ctrl_result_response(Status, State, {error, euser});
%%--------------------------------------------------------------------------
%% Print current working directory
-handle_ctrl_result({pos_compl, Lines},
- #state{caller = pwd, client = From} = State) ->
+handle_ctrl_result({pos_compl, Lines},
+ #state{caller = pwd, client = From} = State) ->
Dir = pwd_result(Lines),
gen_server:reply(From, {ok, Dir}),
{noreply, State#state{client = undefined, caller = undefined}};
%%--------------------------------------------------------------------------
-%% Directory listing
+%% Directory listing
handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State0) ->
case accept_data_connection(State0) of
- {ok, State1} ->
- State = activate_data_connection(State1),
- {noreply, State#state{caller = {handle_dir_result, Dir}}};
- {error, _Reason} = ERROR ->
- case State0#state.client of
- undefined ->
- {stop, ERROR, State0};
- From ->
- gen_server:reply(From, ERROR),
- {stop, normal, State0#state{client = undefined}}
- end
+ {ok, State1} ->
+ State = activate_data_connection(State1),
+ {noreply, State#state{caller = {handle_dir_result, Dir}}};
+ {error, _Reason} = ERROR ->
+ case State0#state.client of
+ undefined ->
+ {stop, ERROR, State0};
+ From ->
+ gen_server:reply(From, ERROR),
+ {stop, normal, State0#state{client = undefined}}
+ end
end;
handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir,
- Data}, client = From}
- = State) ->
+ Data}, client = From}
+ = State) ->
case Dir of
- "" -> % Current directory
- gen_server:reply(From, {ok, Data}),
- {noreply, State#state{client = undefined,
- caller = undefined}};
- _ ->
- %% <WTF>
- %% Dir cannot be assumed to be a dir. It is a string that
- %% could be a dir, but could also be a file or even a string
- %% containing wildcards (*).
- %%
- %% %% If there is only one line it might be a directory with one
- %% %% file but it might be an error message that the directory
- %% %% was not found. So in this case we have to endure a little
- %% %% overhead to be able to give a good return value. Alas not
- %% %% all ftp implementations behave the same and returning
- %% %% an error string is allowed by the FTP RFC.
- %% case lists:dropwhile(fun(?CR) -> false;(_) -> true end,
- %% binary_to_list(Data)) of
- %% L when (L =:= [?CR, ?LF]) orelse (L =:= []) ->
- %% send_ctrl_message(State, mk_cmd("PWD", [])),
- %% activate_ctrl_connection(State),
- %% {noreply,
- %% State#state{caller = {handle_dir_data, Dir, Data}}};
- %% _ ->
- %% gen_server:reply(From, {ok, Data}),
- %% {noreply, State#state{client = undefined,
- %% caller = undefined}}
- %% end
- %% </WTF>
- gen_server:reply(From, {ok, Data}),
- {noreply, State#state{client = undefined,
- caller = undefined}}
+ "" -> % Current directory
+ gen_server:reply(From, {ok, Data}),
+ {noreply, State#state{client = undefined,
+ caller = undefined}};
+ _ ->
+ %% <WTF>
+ %% Dir cannot be assumed to be a dir. It is a string that
+ %% could be a dir, but could also be a file or even a string
+ %% containing wildcards (*).
+ %%
+ %% %% If there is only one line it might be a directory with one
+ %% %% file but it might be an error message that the directory
+ %% %% was not found. So in this case we have to endure a little
+ %% %% overhead to be able to give a good return value. Alas not
+ %% %% all ftp implementations behave the same and returning
+ %% %% an error string is allowed by the FTP RFC.
+ %% case lists:dropwhile(fun(?CR) -> false;(_) -> true end,
+ %% binary_to_list(Data)) of
+ %% L when (L =:= [?CR, ?LF]) orelse (L =:= []) ->
+ %% send_ctrl_message(State, mk_cmd("PWD", [])),
+ %% activate_ctrl_connection(State),
+ %% {noreply,
+ %% State#state{caller = {handle_dir_data, Dir, Data}}};
+ %% _ ->
+ %% gen_server:reply(From, {ok, Data}),
+ %% {noreply, State#state{client = undefined,
+ %% caller = undefined}}
+ %% end
+ %% </WTF>
+ gen_server:reply(From, {ok, Data}),
+ {noreply, State#state{client = undefined,
+ caller = undefined}}
end;
-handle_ctrl_result({pos_compl, Lines},
- #state{caller = {handle_dir_data, Dir, DirData}} =
- State0) ->
- OldDir = pwd_result(Lines),
+handle_ctrl_result({pos_compl, Lines},
+ #state{caller = {handle_dir_data, Dir, DirData}} =
+ State0) ->
+ OldDir = pwd_result(Lines),
_ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {handle_dir_data_second_phase, OldDir,
- DirData}}};
+ DirData}}};
handle_ctrl_result({Status, _},
- #state{caller = {handle_dir_data, _, _}} = State) ->
+ #state{caller = {handle_dir_data, _, _}} = State) ->
ctrl_result_response(Status, State, {error, epath});
handle_ctrl_result(S={_Status, _},
- #state{caller = {handle_dir_result, _, _}} = State) ->
+ #state{caller = {handle_dir_result, _, _}} = State) ->
%% OTP-5731, macosx
ctrl_result_response(S, State, {error, epath});
handle_ctrl_result({pos_compl, _},
- #state{caller = {handle_dir_data_second_phase, OldDir,
- DirData}} = State0) ->
+ #state{caller = {handle_dir_data_second_phase, OldDir,
+ DirData}} = State0) ->
_ = send_ctrl_message(State0, mk_cmd("CWD ~s", [OldDir])),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {handle_dir_data_third_phase, DirData}}};
-handle_ctrl_result({Status, _},
- #state{caller = {handle_dir_data_second_phase, _, _}}
- = State) ->
+handle_ctrl_result({Status, _},
+ #state{caller = {handle_dir_data_second_phase, _, _}}
+ = State) ->
ctrl_result_response(Status, State, {error, epath});
handle_ctrl_result(_, #state{caller = {handle_dir_data_third_phase, DirData},
- client = From} = State) ->
+ client = From} = State) ->
gen_server:reply(From, {ok, DirData}),
{noreply, State#state{client = undefined, caller = undefined}};
@@ -1843,69 +1843,69 @@ handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) ->
%%--------------------------------------------------------------------------
%% File renaming
-handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}}
- = State0) ->
+handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}}
+ = State0) ->
_ = send_ctrl_message(State0, mk_cmd("RNTO ~s", [NewFile])),
State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = rename_second_phase}};
+ {noreply, State#state{caller = rename_second_phase}};
-handle_ctrl_result({Status, _},
- #state{caller = {rename, _}} = State) ->
+handle_ctrl_result({Status, _},
+ #state{caller = {rename, _}} = State) ->
ctrl_result_response(Status, State, {error, Status});
handle_ctrl_result({Status, _},
- #state{caller = rename_second_phase} = State) ->
+ #state{caller = rename_second_phase} = State) ->
ctrl_result_response(Status, State, {error, Status});
%%--------------------------------------------------------------------------
%% File handling - recv_bin
handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State0) ->
case accept_data_connection(State0) of
- {ok, State1} ->
- State = activate_data_connection(State1),
- {noreply, State};
- {error, _Reason} = ERROR ->
- case State0#state.client of
- undefined ->
- {stop, ERROR, State0};
- From ->
- gen_server:reply(From, ERROR),
- {stop, normal, State0#state{client = undefined}}
- end
+ {ok, State1} ->
+ State = activate_data_connection(State1),
+ {noreply, State};
+ {error, _Reason} = ERROR ->
+ case State0#state.client of
+ undefined ->
+ {stop, ERROR, State0};
+ From ->
+ gen_server:reply(From, ERROR),
+ {stop, normal, State0#state{client = undefined}}
+ end
end;
handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data},
- client = From} = State) ->
+ client = From} = State) ->
gen_server:reply(From, {ok, Data}),
close_data_connection(State),
{noreply, State#state{client = undefined, caller = undefined}};
handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) ->
close_data_connection(State),
- ctrl_result_response(Status, State#state{dsock = undefined},
- {error, epath});
+ ctrl_result_response(Status, State#state{dsock = undefined},
+ {error, epath});
handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) ->
close_data_connection(State),
- ctrl_result_response(Status, State#state{dsock = undefined},
- {error, epath});
+ ctrl_result_response(Status, State#state{dsock = undefined},
+ {error, epath});
%%--------------------------------------------------------------------------
%% File handling - start_chunk_transfer
handle_ctrl_result({pos_prel, _}, #state{client = From,
- caller = start_chunk_transfer}
- = State0) ->
+ caller = start_chunk_transfer}
+ = State0) ->
case accept_data_connection(State0) of
- {ok, State1} ->
- State = start_chunk(State1),
- {noreply, State};
- {error, _Reason} = ERROR ->
- case State0#state.client of
- undefined ->
- {stop, ERROR, State0};
- From ->
- gen_server:reply(From, ERROR),
- {stop, normal, State0#state{client = undefined}}
- end
+ {ok, State1} ->
+ State = start_chunk(State1),
+ {noreply, State};
+ {error, _Reason} = ERROR ->
+ case State0#state.client of
+ undefined ->
+ {stop, ERROR, State0};
+ From ->
+ gen_server:reply(From, ERROR),
+ {stop, normal, State0#state{client = undefined}}
+ end
end;
%%--------------------------------------------------------------------------
@@ -1916,7 +1916,7 @@ handle_ctrl_result({pos_compl, _}, #state{client = From,
client_called_us = true,
pos_compl_received = false
}}
- = State0) when From =/= undefined ->
+ = State0) when From =/= undefined ->
%% The pos_compl was the last event we waited for, finnish and clean up
?DBG("recv_chunk_closing pos_compl, last event",[]),
gen_server:reply(From, ok),
@@ -1926,7 +1926,7 @@ handle_ctrl_result({pos_compl, _}, #state{client = From,
client = undefined}};
handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R}
- = State0) ->
+ = State0) ->
%% Waiting for more, don't care what
?DBG("recv_chunk_closing pos_compl, wait more",[]),
{noreply, State0#state{caller = R#recv_chunk_closing{pos_compl_received=true}}};
@@ -1936,59 +1936,59 @@ handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R}
%% File handling - recv_file
handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State0) ->
case accept_data_connection(State0) of
- {ok, State1} ->
- State = activate_data_connection(State1),
- {noreply, State};
- {error, _Reason} = ERROR ->
- case State0#state.client of
- undefined ->
- {stop, ERROR, State0};
- From ->
- gen_server:reply(From, ERROR),
- {stop, normal, State0#state{client = undefined}}
- end
+ {ok, State1} ->
+ State = activate_data_connection(State1),
+ {noreply, State};
+ {error, _Reason} = ERROR ->
+ case State0#state.client of
+ undefined ->
+ {stop, ERROR, State0};
+ From ->
+ gen_server:reply(From, ERROR),
+ {stop, normal, State0#state{client = undefined}}
+ end
end;
handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) ->
file_close(Fd),
close_data_connection(State),
- ctrl_result_response(Status, State#state{dsock = undefined},
- {error, epath});
+ ctrl_result_response(Status, State#state{dsock = undefined},
+ {error, epath});
%%--------------------------------------------------------------------------
%% File handling - transfer_*
-handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}}
- = State0) ->
+handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}}
+ = State0) ->
case accept_data_connection(State0) of
- {ok, State1} ->
- send_file(State1, Fd);
- {error, _Reason} = ERROR ->
- case State0#state.client of
- undefined ->
- {stop, ERROR, State0};
- From ->
- gen_server:reply(From, ERROR),
- {stop, normal, State0#state{client = undefined}}
- end
+ {ok, State1} ->
+ send_file(State1, Fd);
+ {error, _Reason} = ERROR ->
+ case State0#state.client of
+ undefined ->
+ {stop, ERROR, State0};
+ From ->
+ gen_server:reply(From, ERROR),
+ {stop, normal, State0#state{client = undefined}}
+ end
end;
-handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}}
- = State0) ->
+handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}}
+ = State0) ->
case accept_data_connection(State0) of
- {ok, State} ->
- send_bin(State, Bin);
- {error, _Reason} = ERROR ->
- case State0#state.client of
- undefined ->
- {stop, ERROR, State0};
- From ->
- gen_server:reply(From, ERROR),
- {stop, normal, State0#state{client = undefined}}
- end
+ {ok, State} ->
+ send_bin(State, Bin);
+ {error, _Reason} = ERROR ->
+ case State0#state.client of
+ undefined ->
+ {stop, ERROR, State0};
+ From ->
+ gen_server:reply(From, ERROR),
+ {stop, normal, State0#state{client = undefined}}
+ end
end;
%%--------------------------------------------------------------------------
%% Default
-handle_ctrl_result({Status, _Lines}, #state{client = From} = State)
+handle_ctrl_result({Status, _Lines}, #state{client = From} = State)
when From =/= undefined ->
ctrl_result_response(Status, State, {error, Status}).
@@ -2003,10 +2003,10 @@ ctrl_result_response(enofile, #state{client = From} = State, _) ->
gen_server:reply(From, {error, enofile}),
{noreply, State#state{client = undefined, caller = undefined}};
-ctrl_result_response(Status, #state{client = From} = State, _)
- when (Status =:= etnospc) orelse
- (Status =:= epnospc) orelse
- (Status =:= efnamena) orelse
+ctrl_result_response(Status, #state{client = From} = State, _)
+ when (Status =:= etnospc) orelse
+ (Status =:= epnospc) orelse
+ (Status =:= efnamena) orelse
(Status =:= econn) ->
gen_server:reply(From, {error, Status}),
%% {stop, normal, {error, Status}, State#state{client = undefined}};
@@ -2019,9 +2019,9 @@ ctrl_result_response(_, #state{client = From} = State, ErrorMsg) ->
%%--------------------------------------------------------------------------
handle_caller(#state{caller = {dir, Dir, Len}} = State0) ->
Cmd = case Len of
- short -> "NLST";
- long -> "LIST"
- end,
+ short -> "NLST";
+ long -> "LIST"
+ end,
_ = case Dir of
"" ->
send_ctrl_message(State0, mk_cmd(Cmd, ""));
@@ -2030,89 +2030,89 @@ handle_caller(#state{caller = {dir, Dir, Len}} = State0) ->
end,
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {dir, Dir}}};
-
+
handle_caller(#state{caller = {recv_bin, RemoteFile}} = State0) ->
_ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = recv_bin}};
-handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} =
- State0) ->
+handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} =
+ State0) ->
_ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = start_chunk_transfer}};
handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State0) ->
- _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
+ _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {recv_file, Fd}}};
handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}},
- ldir = LocalDir, client = From} = State0) ->
+ ldir = LocalDir, client = From} = State0) ->
case file_open(filename:absname(LocalFile, LocalDir), read) of
- {ok, Fd} ->
- _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {transfer_file, Fd}}};
- {error, _} ->
- gen_server:reply(From, {error, epath}),
- {noreply, State0#state{client = undefined, caller = undefined,
- dsock = undefined}}
+ {ok, Fd} ->
+ _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {transfer_file, Fd}}};
+ {error, _} ->
+ gen_server:reply(From, {error, epath}),
+ {noreply, State0#state{client = undefined, caller = undefined,
+ dsock = undefined}}
end;
-handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} =
- State0) ->
+handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} =
+ State0) ->
_ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {transfer_data, Bin}}}.
-%% ----------- FTP SERVER COMMUNICATION -------------------------
+%% ----------- FTP SERVER COMMUNICATION -------------------------
-%% Connect to FTP server at Host (default is TCP port 21)
+%% Connect to FTP server at Host (default is TCP port 21)
%% in order to establish a control connection.
setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State0) ->
MsTime = erlang:monotonic_time(),
case connect(Host, Port, SockOpts, Timeout, State0) of
- {ok, IpFam, CSock} ->
- State1 = State0#state{csock = {tcp, CSock}, ipfamily = IpFam},
- State = activate_ctrl_connection(State1),
- case Timeout - millisec_passed(MsTime) of
- Timeout2 when (Timeout2 >= 0) ->
- {ok, State#state{caller = open}, Timeout2};
- _ ->
+ {ok, IpFam, CSock} ->
+ State1 = State0#state{csock = {tcp, CSock}, ipfamily = IpFam},
+ State = activate_ctrl_connection(State1),
+ case Timeout - millisec_passed(MsTime) of
+ Timeout2 when (Timeout2 >= 0) ->
+ {ok, State#state{caller = open}, Timeout2};
+ _ ->
%% Oups: Simulate timeout
- {ok, State#state{caller = open}, 0}
- end;
- Error ->
- Error
+ {ok, State#state{caller = open}, 0}
+ end;
+ Error ->
+ Error
end.
-setup_data_connection(#state{mode = active,
- caller = Caller,
- csock = CSock,
+setup_data_connection(#state{mode = active,
+ caller = Caller,
+ csock = CSock,
sockopts_data_active = SockOpts,
- ftp_extension = FtpExt} = State0) ->
+ ftp_extension = FtpExt} = State0) ->
case (catch sockname(CSock)) of
- {ok, {{_, _, _, _, _, _, _, _} = IP0, _}} ->
+ {ok, {{_, _, _, _, _, _, _, _} = IP0, _}} ->
IP = proplists:get_value(ip, SockOpts, IP0),
- {ok, LSock} =
- gen_tcp:listen(0, [{ip, IP}, {active, false},
- inet6, binary, {packet, 0} |
+ {ok, LSock} =
+ gen_tcp:listen(0, [{ip, IP}, {active, false},
+ inet6, binary, {packet, 0} |
lists:keydelete(ip,1,SockOpts)]),
- {ok, {_, Port}} = sockname({tcp,LSock}),
- IpAddress = inet_parse:ntoa(IP),
- Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]),
- _ = send_ctrl_message(State0, Cmd),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {setup_data_connection,
- {LSock, Caller}}}};
- {ok, {{_,_,_,_} = IP0, _}} ->
+ {ok, {_, Port}} = sockname({tcp,LSock}),
+ IpAddress = inet_parse:ntoa(IP),
+ Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]),
+ _ = send_ctrl_message(State0, Cmd),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {setup_data_connection,
+ {LSock, Caller}}}};
+ {ok, {{_,_,_,_} = IP0, _}} ->
IP = proplists:get_value(ip, SockOpts, IP0),
- {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false},
- binary, {packet, 0} |
+ {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false},
+ binary, {packet, 0} |
lists:keydelete(ip,1,SockOpts)]),
- {ok, Port} = inet:port(LSock),
- _ = case FtpExt of
+ {ok, Port} = inet:port(LSock),
+ _ = case FtpExt of
false ->
{IP1, IP2, IP3, IP4} = IP,
{Port1, Port2} = {Port div 256, Port rem 256},
@@ -2124,27 +2124,27 @@ setup_data_connection(#state{mode = active,
Cmd = mk_cmd("EPRT |1|~s|~p|", [IpAddress, Port]),
send_ctrl_message(State0, Cmd)
end,
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {setup_data_connection,
- {LSock, Caller}}}}
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = {setup_data_connection,
+ {LSock, Caller}}}}
end;
setup_data_connection(#state{mode = passive, ipfamily = inet6,
- caller = Caller} = State0) ->
+ caller = Caller} = State0) ->
_ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {setup_data_connection, Caller}}};
setup_data_connection(#state{mode = passive, ipfamily = inet,
- caller = Caller,
- ftp_extension = false} = State0) ->
+ caller = Caller,
+ ftp_extension = false} = State0) ->
_ = send_ctrl_message(State0, mk_cmd("PASV", [])),
State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = {setup_data_connection, Caller}}};
+ {noreply, State#state{caller = {setup_data_connection, Caller}}};
setup_data_connection(#state{mode = passive, ipfamily = inet,
- caller = Caller,
- ftp_extension = true} = State0) ->
+ caller = Caller,
+ ftp_extension = true} = State0) ->
_ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = {setup_data_connection, Caller}}}.
@@ -2157,76 +2157,76 @@ connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6 = IpFam}) ->
connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6fb4}) ->
case inet:getaddr(Host, inet6) of
- {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} ->
- case inet:getaddr(Host, inet) of
- {ok, IPv4} ->
- IpFam = inet,
- connect2(IPv4, Port, IpFam, SockOpts, Timeout);
-
- _ ->
- IpFam = inet6,
- connect2(IPv6, Port, IpFam, SockOpts, Timeout)
- end;
-
- {ok, IPv6} ->
- IpFam = inet6,
- connect2(IPv6, Port, IpFam, SockOpts, Timeout);
-
- _ ->
- case inet:getaddr(Host, inet) of
- {ok, IPv4} ->
- IpFam = inet,
- connect2(IPv4, Port, IpFam, SockOpts, Timeout);
- Error ->
- Error
- end
+ {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} ->
+ case inet:getaddr(Host, inet) of
+ {ok, IPv4} ->
+ IpFam = inet,
+ connect2(IPv4, Port, IpFam, SockOpts, Timeout);
+
+ _ ->
+ IpFam = inet6,
+ connect2(IPv6, Port, IpFam, SockOpts, Timeout)
+ end;
+
+ {ok, IPv6} ->
+ IpFam = inet6,
+ connect2(IPv6, Port, IpFam, SockOpts, Timeout);
+
+ _ ->
+ case inet:getaddr(Host, inet) of
+ {ok, IPv4} ->
+ IpFam = inet,
+ connect2(IPv4, Port, IpFam, SockOpts, Timeout);
+ Error ->
+ Error
+ end
end.
connect2(Host, Port, IpFam, SockOpts, Timeout) ->
Opts = [IpFam, binary, {packet, 0}, {active, false} | SockOpts],
case gen_tcp:connect(Host, Port, Opts, Timeout) of
- {ok, Sock} ->
- {ok, IpFam, Sock};
- Error ->
- Error
+ {ok, Sock} ->
+ {ok, IpFam, Sock};
+ Error ->
+ Error
end.
-
+
accept_data_connection(#state{mode = active,
- dtimeout = DTimeout,
- tls_options = TLSOptions,
- dsock = {lsock, LSock}} = State0) ->
+ dtimeout = DTimeout,
+ tls_options = TLSOptions,
+ dsock = {lsock, LSock}} = State0) ->
case gen_tcp:accept(LSock, DTimeout) of
- {ok, Socket} when is_list(TLSOptions) ->
- gen_tcp:close(LSock),
- ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
- case ssl:connect(Socket, TLSOptions, DTimeout) of
- {ok, TLSSocket} ->
- {ok, State0#state{dsock={ssl,TLSSocket}}};
- {error, Reason} ->
- {error, {ssl_connect_failed, Reason}}
- end;
- {ok, Socket} ->
- gen_tcp:close(LSock),
- {ok, State0#state{dsock={tcp,Socket}}};
- {error, Reason} ->
- {error, {data_connect_failed, Reason}}
+ {ok, Socket} when is_list(TLSOptions) ->
+ gen_tcp:close(LSock),
+ ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
+ case ssl:connect(Socket, TLSOptions, DTimeout) of
+ {ok, TLSSocket} ->
+ {ok, State0#state{dsock={ssl,TLSSocket}}};
+ {error, Reason} ->
+ {error, {ssl_connect_failed, Reason}}
+ end;
+ {ok, Socket} ->
+ gen_tcp:close(LSock),
+ {ok, State0#state{dsock={tcp,Socket}}};
+ {error, Reason} ->
+ {error, {data_connect_failed, Reason}}
end;
accept_data_connection(#state{mode = passive,
- dtimeout = DTimeout,
- dsock = {tcp,Socket},
- tls_options = TLSOptions} = State) when is_list(TLSOptions) ->
+ dtimeout = DTimeout,
+ dsock = {tcp,Socket},
+ tls_options = TLSOptions} = State) when is_list(TLSOptions) ->
?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State]),
case ssl:connect(Socket, TLSOptions, DTimeout) of
- {ok, TLSSocket} ->
- {ok, State#state{dsock={ssl,TLSSocket}}};
- {error, Reason} ->
- {error, {ssl_connect_failed, Reason}}
+ {ok, TLSSocket} ->
+ {ok, State#state{dsock={ssl,TLSSocket}}};
+ {error, Reason} ->
+ {error, {ssl_connect_failed, Reason}}
end;
accept_data_connection(#state{mode = passive} = State) ->
{ok,State}.
-
+
send_ctrl_message(_S=#state{csock = Socket, verbose = Verbose}, Message) ->
verbose(lists:flatten(Message),Verbose,send),
@@ -2236,15 +2236,15 @@ send_ctrl_message(_S=#state{csock = Socket, verbose = Verbose}, Message) ->
send_data_message(_S=#state{dsock = Socket}, Message) ->
?DBG('<==data ~p ==== ~s~n~p~n',[Socket,Message,_S]),
case send_message(Socket, Message) of
- ok ->
- ok;
- {error, Reason} ->
- Report = io_lib:format("send/2 for socket ~p failed with "
- "reason ~p~n", [Socket, Reason]),
- error_logger:error_report(Report),
- %% If tcp/ssl does not work the only option is to terminate,
- %% this is the expected behavior under these circumstances.
- exit(normal) %% User will get error message from terminate/2
+ ok ->
+ ok;
+ {error, Reason} ->
+ Report = io_lib:format("send/2 for socket ~p failed with "
+ "reason ~p~n", [Socket, Reason]),
+ error_logger:error_report(Report),
+ %% If tcp/ssl does not work the only option is to terminate,
+ %% this is the expected behavior under these circumstances.
+ exit(normal) %% User will get error message from terminate/2
end.
send_message({tcp, Socket}, Message) ->
@@ -2253,32 +2253,41 @@ send_message({ssl, Socket}, Message) ->
ssl:send(Socket, Message).
activate_ctrl_connection(#state{csock = CSock, ctrl_data = {<<>>, _, _}} = State) ->
- activate_connection(CSock),
+ _ = activate_connection(CSock),
State;
activate_ctrl_connection(#state{csock = CSock} = State0) ->
- activate_connection(CSock),
+ _ = activate_connection(CSock),
%% We have already received at least part of the next control message,
%% that has been saved in ctrl_data, process this first.
{noreply, State} = handle_info({socket_type(CSock), unwrap_socket(CSock), <<>>}, State0),
State.
activate_data_connection(#state{dsock = DSock} = State) ->
- activate_connection(DSock),
+ _ = activate_connection(DSock),
State.
activate_connection(Socket) ->
- ignore_return_value(
- case socket_type(Socket) of
- tcp -> inet:setopts(unwrap_socket(Socket), [{active, once}]);
- ssl -> ssl:setopts(unwrap_socket(Socket), [{active, once}])
- end).
+ case socket_type(Socket) of
+ tcp ->
+ _ = activate_connection(inet, tcp_closed, Socket);
+ ssl ->
+ _ = activate_connection(ssl, ssl_closed, Socket)
+ end.
+activate_connection(API, CloseTag, Socket0) ->
+ Socket = unwrap_socket(Socket0),
+ case API:setopts(Socket, [{active, once}]) of
+ ok ->
+ ok;
+ {error, _} -> %% inet can retrun einval instead of closed
+ self() ! {CloseTag, Socket}
+ end.
ignore_return_value(_) -> ok.
unwrap_socket({tcp,Socket}) -> Socket;
unwrap_socket({ssl,Socket}) -> Socket.
-
+
socket_type({tcp,_Socket}) -> tcp;
socket_type({ssl,_Socket}) -> ssl.
@@ -2292,25 +2301,25 @@ close_connection({lsock,Socket}) -> ignore_return_value( gen_tcp:close(Socket) )
close_connection({tcp, Socket}) -> ignore_return_value( gen_tcp:close(Socket) );
close_connection({ssl, Socket}) -> ignore_return_value( ssl:close(Socket) ).
-%% ------------ FILE HANDLING ----------------------------------------
+%% ------------ FILE HANDLING ----------------------------------------
send_file(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Fd) ->
{noreply, State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_file, Fd}}};
send_file(State0, Fd) ->
case file_read(Fd) of
- {ok, N, Bin} when N > 0 ->
- send_data_message(State0, Bin),
- progress_report({binary, Bin}, State0),
- send_file(State0, Fd);
- {ok, _, _} ->
- file_close(Fd),
- close_data_connection(State0),
- progress_report({transfer_size, 0}, State0),
- State = activate_ctrl_connection(State0),
- {noreply, State#state{caller = transfer_file_second_phase,
- dsock = undefined}};
+ {ok, N, Bin} when N > 0 ->
+ send_data_message(State0, Bin),
+ progress_report({binary, Bin}, State0),
+ send_file(State0, Fd);
+ {ok, _, _} ->
+ file_close(Fd),
+ close_data_connection(State0),
+ progress_report({transfer_size, 0}, State0),
+ State = activate_ctrl_connection(State0),
+ {noreply, State#state{caller = transfer_file_second_phase,
+ dsock = undefined}};
{error, Reason} ->
- gen_server:reply(State0#state.client, {error, Reason}),
- {stop, normal, State0#state{client = undefined}}
+ gen_server:reply(State0#state.client, {error, Reason}),
+ {stop, normal, State0#state{client = undefined}}
end.
file_open(File, Option) ->
@@ -2319,32 +2328,32 @@ file_open(File, Option) ->
file_close(Fd) ->
ignore_return_value( file:close(Fd) ).
-file_read(Fd) ->
+file_read(Fd) ->
case file:read(Fd, ?FILE_BUFSIZE) of
- {ok, Bytes} ->
- {ok, size(Bytes), Bytes};
- eof ->
- {ok, 0, []};
- Other ->
- Other
+ {ok, Bytes} ->
+ {ok, size(Bytes), Bytes};
+ eof ->
+ {ok, 0, []};
+ Other ->
+ Other
end.
file_write(Bytes, Fd) ->
file:write(Fd, Bytes).
-%% -------------- MISC ----------------------------------------------
+%% -------------- MISC ----------------------------------------------
call(GenServer, Msg, Format) ->
call(GenServer, Msg, Format, infinity).
-call(GenServer, Msg, Format, Timeout) ->
- Req = {self(), Msg},
+call(GenServer, Msg, Format, Timeout) ->
+ Req = {self(), Msg},
case (catch gen_server:call(GenServer, Req, Timeout)) of
- {ok, Bin} when is_binary(Bin) andalso (Format =:= string) ->
- {ok, binary_to_list(Bin)};
- {'EXIT', _} ->
- {error, eclosed};
- Result ->
- Result
+ {ok, Bin} when is_binary(Bin) andalso (Format =:= string) ->
+ {ok, binary_to_list(Bin)};
+ {'EXIT', _} ->
+ {error, eclosed};
+ Result ->
+ Result
end.
cast(GenServer, Msg) ->
@@ -2357,10 +2366,10 @@ send_bin(State0, Bin) ->
close_data_connection(State0),
State = activate_ctrl_connection(State0),
{noreply, State#state{caller = transfer_data_second_phase,
- dsock = undefined}}.
+ dsock = undefined}}.
mk_cmd(Fmt, Args) ->
- [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok.
+ [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok.
is_name_sane([]) ->
true;
@@ -2372,29 +2381,29 @@ is_name_sane([_| Rest]) ->
is_name_sane(Rest).
pwd_result(Lines) ->
- {_, [?DOUBLE_QUOTE | Rest]} =
- lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines),
+ {_, [?DOUBLE_QUOTE | Rest]} =
+ lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines),
{Dir, _} =
- lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest),
+ lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest),
Dir.
-key_search(Key, List, Default) ->
+key_search(Key, List, Default) ->
case lists:keysearch(Key, 1, List) of
- {value, {_,Val}} ->
- Val;
- false ->
- Default
+ {value, {_,Val}} ->
+ Val;
+ false ->
+ Default
end.
verbose(Lines, true, Direction) ->
DirStr =
- case Direction of
- send ->
- "Sending: ";
- _ ->
- "Receiving: "
- end,
+ case Direction of
+ send ->
+ "Sending: ";
+ _ ->
+ "Receiving: "
+ end,
Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR),
erlang:display(DirStr++Str);
verbose(_, false,_) ->
@@ -2429,129 +2438,129 @@ start_chunk(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State) ->
State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, start_chunk, undefined}};
start_chunk(#state{client = From} = State) ->
gen_server:reply(From, ok),
- State#state{chunk = true,
- client = undefined,
- caller = undefined}.
+ State#state{chunk = true,
+ client = undefined,
+ caller = undefined}.
-%% This function extracts the start options from the
-%% Valid options:
-%% debug,
-%% verbose
+%% This function extracts the start options from the
+%% Valid options:
+%% debug,
+%% verbose
%% ipfamily
%% priority
%% flags (for backward compatibillity)
start_options(Options) ->
case lists:keysearch(flags, 1, Options) of
- {value, {flags, Flags}} ->
- Verbose = lists:member(verbose, Flags),
- IsTrace = lists:member(trace, Flags),
- IsDebug = lists:member(debug, Flags),
- DebugLevel =
- if
- (IsTrace =:= true) ->
- trace;
- IsDebug =:= true ->
- debug;
- true ->
- disable
- end,
- {ok, [{verbose, Verbose},
- {debug, DebugLevel},
- {priority, low}]};
- false ->
- ValidateVerbose =
- fun(true) -> true;
- (false) -> true;
- (_) -> false
- end,
- ValidateDebug =
- fun(trace) -> true;
- (debug) -> true;
- (disable) -> true;
- (_) -> false
- end,
- ValidatePriority =
- fun(low) -> true;
- (normal) -> true;
- (high) -> true;
- (_) -> false
- end,
- ValidOptions =
- [{verbose, ValidateVerbose, false, false},
- {debug, ValidateDebug, false, disable},
- {priority, ValidatePriority, false, low}],
- validate_options(Options, ValidOptions, [])
+ {value, {flags, Flags}} ->
+ Verbose = lists:member(verbose, Flags),
+ IsTrace = lists:member(trace, Flags),
+ IsDebug = lists:member(debug, Flags),
+ DebugLevel =
+ if
+ (IsTrace =:= true) ->
+ trace;
+ IsDebug =:= true ->
+ debug;
+ true ->
+ disable
+ end,
+ {ok, [{verbose, Verbose},
+ {debug, DebugLevel},
+ {priority, low}]};
+ false ->
+ ValidateVerbose =
+ fun(true) -> true;
+ (false) -> true;
+ (_) -> false
+ end,
+ ValidateDebug =
+ fun(trace) -> true;
+ (debug) -> true;
+ (disable) -> true;
+ (_) -> false
+ end,
+ ValidatePriority =
+ fun(low) -> true;
+ (normal) -> true;
+ (high) -> true;
+ (_) -> false
+ end,
+ ValidOptions =
+ [{verbose, ValidateVerbose, false, false},
+ {debug, ValidateDebug, false, disable},
+ {priority, ValidatePriority, false, low}],
+ validate_options(Options, ValidOptions, [])
end.
-%% This function extracts and validates the open options from the
-%% Valid options:
+%% This function extracts and validates the open options from the
+%% Valid options:
%% mode
%% host
%% port
%% timeout
%% dtimeout
%% progress
-%% ftp_extension
+%% ftp_extension
open_options(Options) ->
- ValidateMode =
- fun(active) -> true;
- (passive) -> true;
- (_) -> false
- end,
- ValidateHost =
- fun(Host) when is_list(Host) ->
- true;
- (Host) when is_tuple(Host) andalso
- ((size(Host) =:= 4) orelse (size(Host) =:= 8)) ->
- true;
- (_) ->
- false
- end,
- ValidatePort =
- fun(Port) when is_integer(Port) andalso (Port > 0) -> true;
- (_) -> false
- end,
- ValidateIpFamily =
- fun(inet) -> true;
- (inet6) -> true;
- (inet6fb4) -> true;
- (_) -> false
- end,
- ValidateTimeout =
- fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true;
- (_) -> false
- end,
- ValidateDTimeout =
- fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true;
- (infinity) -> true;
- (_) -> false
- end,
- ValidateProgress =
- fun(ignore) ->
- true;
- ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso
- is_atom(Func) ->
- true;
- (_) ->
- false
- end,
- ValidateFtpExtension =
- fun(true) -> true;
- (false) -> true;
- (_) -> false
- end,
- ValidOptions =
- [{mode, ValidateMode, false, ?DEFAULT_MODE},
- {host, ValidateHost, true, ehost},
- {port, ValidatePort, false, ?FTP_PORT},
- {ipfamily, ValidateIpFamily, false, inet},
- {timeout, ValidateTimeout, false, ?CONNECTION_TIMEOUT},
- {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT},
- {progress, ValidateProgress, false, ?PROGRESS_DEFAULT},
- {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}],
+ ValidateMode =
+ fun(active) -> true;
+ (passive) -> true;
+ (_) -> false
+ end,
+ ValidateHost =
+ fun(Host) when is_list(Host) ->
+ true;
+ (Host) when is_tuple(Host) andalso
+ ((size(Host) =:= 4) orelse (size(Host) =:= 8)) ->
+ true;
+ (_) ->
+ false
+ end,
+ ValidatePort =
+ fun(Port) when is_integer(Port) andalso (Port > 0) -> true;
+ (_) -> false
+ end,
+ ValidateIpFamily =
+ fun(inet) -> true;
+ (inet6) -> true;
+ (inet6fb4) -> true;
+ (_) -> false
+ end,
+ ValidateTimeout =
+ fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true;
+ (_) -> false
+ end,
+ ValidateDTimeout =
+ fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true;
+ (infinity) -> true;
+ (_) -> false
+ end,
+ ValidateProgress =
+ fun(ignore) ->
+ true;
+ ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso
+ is_atom(Func) ->
+ true;
+ (_) ->
+ false
+ end,
+ ValidateFtpExtension =
+ fun(true) -> true;
+ (false) -> true;
+ (_) -> false
+ end,
+ ValidOptions =
+ [{mode, ValidateMode, false, ?DEFAULT_MODE},
+ {host, ValidateHost, true, ehost},
+ {port, ValidatePort, false, ?FTP_PORT},
+ {ipfamily, ValidateIpFamily, false, inet},
+ {timeout, ValidateTimeout, false, ?CONNECTION_TIMEOUT},
+ {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT},
+ {progress, ValidateProgress, false, ?PROGRESS_DEFAULT},
+ {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}],
validate_options(Options, ValidOptions, []).
socket_options(Options) ->
@@ -2589,28 +2598,28 @@ validate_options([], [], Acc) ->
validate_options([], ValidOptions, Acc) ->
%% Check if any mandatory options are missing!
case [{Key, Reason} || {Key, _, true, Reason} <- ValidOptions] of
- [] ->
- Defaults =
- [{Key, Default} || {Key, _, _, Default} <- ValidOptions],
- {ok, lists:reverse(Defaults ++ Acc)};
- [{_, Reason}|_Missing] ->
- throw({error, Reason})
+ [] ->
+ Defaults =
+ [{Key, Default} || {Key, _, _, Default} <- ValidOptions],
+ {ok, lists:reverse(Defaults ++ Acc)};
+ [{_, Reason}|_Missing] ->
+ throw({error, Reason})
end;
validate_options([{Key, Value}|Options], ValidOptions, Acc) ->
case lists:keysearch(Key, 1, ValidOptions) of
- {value, {Key, Validate, _, Default}} ->
- case (catch Validate(Value)) of
- true ->
- NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
- validate_options(Options, NewValidOptions,
- [{Key, Value} | Acc]);
- _ ->
- NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
- validate_options(Options, NewValidOptions,
- [{Key, Default} | Acc])
- end;
- false ->
- validate_options(Options, ValidOptions, Acc)
+ {value, {Key, Validate, _, Default}} ->
+ case (catch Validate(Value)) of
+ true ->
+ NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
+ validate_options(Options, NewValidOptions,
+ [{Key, Value} | Acc]);
+ _ ->
+ NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
+ validate_options(Options, NewValidOptions,
+ [{Key, Default} | Acc])
+ end;
+ false ->
+ validate_options(Options, ValidOptions, Acc)
end;
validate_options([_|Options], ValidOptions, Acc) ->
validate_options(Options, ValidOptions, Acc).
@@ -2619,5 +2628,5 @@ validate_options([_|Options], ValidOptions, Acc) ->
millisec_passed(T0) ->
%% OTP 18
erlang:convert_time_unit(erlang:monotonic_time() - T0,
- native,
- micro_seconds) div 1000.
+ native,
+ micro_seconds) div 1000.
diff --git a/lib/ftp/test/Makefile b/lib/ftp/test/Makefile
index 147f8e5dd6..41f23ac7ba 100644
--- a/lib/ftp/test/Makefile
+++ b/lib/ftp/test/Makefile
@@ -115,7 +115,7 @@ SOURCE = $(ERL_FILES) $(HRL_FILES)
TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-FTP_SPECS = ftp.spec ftp_bench.spec
+FTP_SPECS = ftp.spec
COVER_FILE = ftp.cover
FTP_FILES = ftp.config $(FTP_SPECS)
diff --git a/lib/ftp/test/erl_make_certs.erl b/lib/ftp/test/erl_make_certs.erl
index e10ecf01f9..90c15c91cc 100644
--- a/lib/ftp/test/erl_make_certs.erl
+++ b/lib/ftp/test/erl_make_certs.erl
@@ -23,8 +23,7 @@
-module(erl_make_certs).
-include_lib("public_key/include/public_key.hrl").
--export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]).
--compile(export_all).
+-export([make_cert/1, gen_rsa/1, gen_dsa/2, gen_ec/1, verify_signature/3, write_pem/3]).
%%--------------------------------------------------------------------
%% @doc Create and return a der encoded certificate
@@ -179,7 +178,7 @@ make_tbs(SubjectKey, Opts) ->
subject(proplists:get_value(subject, Opts),false)
end,
- {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1,
+ {#'OTPTBSCertificate'{serialNumber = trunc(rand:uniform()*100000000)*10000 + 1,
signature = SignAlgo,
issuer = Issuer,
validity = validity(Opts),
@@ -447,8 +446,8 @@ odd_rand(Size) ->
Max = (1 bsl (Size*8))-1,
odd_rand(Min, Max).
-odd_rand(Min,Max) ->
- Rand = crypto:rand_uniform(Min,Max),
+odd_rand(Min,Max) when Min < Max ->
+ Rand = Min + rand:uniform(Max - Min - 1),
case Rand rem 2 of
0 ->
Rand + 1;
diff --git a/lib/ftp/test/ftp_SUITE.erl b/lib/ftp/test/ftp_SUITE.erl
index 0b070ee8cb..a324a57833 100644
--- a/lib/ftp/test/ftp_SUITE.erl
+++ b/lib/ftp/test/ftp_SUITE.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
+%%
%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
-%%
+%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
@@ -14,7 +14,7 @@
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -28,8 +28,8 @@
-define(FTP_USER, "anonymous").
-define(FTP_PASS(Cmnt), (fun({ok,__H}) -> "ftp_SUITE_"++Cmnt++"@" ++ __H;
- (_) -> "ftp_SUITE_"++Cmnt++"@localhost"
- end)(inet:gethostname())
+ (_) -> "ftp_SUITE_"++Cmnt++"@localhost"
+ end)(inet:gethostname())
).
-define(BAD_HOST, "badhostname").
@@ -37,9 +37,9 @@
-define(BAD_DIR, "baddirectory").
-record(progress, {
- current = 0,
- total
- }).
+ current = 0,
+ total
+ }).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -73,32 +73,32 @@ ftp_tests()->
[
user,
bad_user,
- pwd,
- cd,
+ pwd,
+ cd,
lcd,
- ls,
- nlist,
- rename,
- delete,
- mkdir,
+ ls,
+ nlist,
+ rename,
+ delete,
+ mkdir,
rmdir,
- send,
+ send,
send_3,
- send_bin,
- send_chunk,
- append,
+ send_bin,
+ send_chunk,
+ append,
append_bin,
- append_chunk,
- recv,
- recv_3,
+ append_chunk,
+ recv,
+ recv_3,
recv_bin,
recv_bin_twice,
- recv_chunk,
+ recv_chunk,
recv_chunk_twice,
recv_chunk_three_times,
recv_chunk_delay,
- type,
- quote,
+ type,
+ quote,
error_elogin,
progress_report_send,
progress_report_recv,
@@ -117,80 +117,80 @@ ftp_sup_tests() ->
%%--------------------------------------------------------------------
%%% Config
-%%% key meaning
+%%% key meaning
%%% ................................................................
-%%% ftpservers list of servers to check if they are available
-%%% The element is:
-%%% {Name, % string(). The os command name
-%%% Path, % string(). The os PATH syntax, e.g "/bin:/usr/bin"
-%%% StartCommand, % fun()->{ok,start_result()} | {error,string()}.
-%%% % The command to start the daemon with.
-%%% ChkUp, % fun(start_result()) -> string(). Os command to check
-%%% % if the server is running. [] if not running.
-%%% % The string in string() is suitable for logging.
-%%% StopCommand, % fun(start_result()) -> void(). The command to stop the daemon with.
-%%% AugmentFun, % fun(config()) -> config() Adds two funs for transforming names of files
-%%% % and directories to the form they are returned from this server
-%%% ServerHost, % string(). Mostly "localhost"
-%%% ServerPort % pos_integer()
-%%% }
-%%%
+%%% ftpservers list of servers to check if they are available
+%%% The element is:
+%%% {Name, % string(). The os command name
+%%% Path, % string(). The os PATH syntax, e.g "/bin:/usr/bin"
+%%% StartCommand, % fun()->{ok,start_result()} | {error,string()}.
+%%% % The command to start the daemon with.
+%%% ChkUp, % fun(start_result()) -> string(). Os command to check
+%%% % if the server is running. [] if not running.
+%%% % The string in string() is suitable for logging.
+%%% StopCommand, % fun(start_result()) -> void(). The command to stop the daemon with.
+%%% AugmentFun, % fun(config()) -> config() Adds two funs for transforming names of files
+%%% % and directories to the form they are returned from this server
+%%% ServerHost, % string(). Mostly "localhost"
+%%% ServerPort % pos_integer()
+%%% }
+%%%
-define(default_ftp_servers,
- [{"vsftpd",
- "/sbin:/usr/sbin:/usr/local/sbin",
- fun(__CONF__, AbsName) ->
- DataDir = proplists:get_value(data_dir,__CONF__),
- ConfFile = filename:join(DataDir, "vsftpd.conf"),
- PrivDir = proplists:get_value(priv_dir,__CONF__),
- AnonRoot = PrivDir,
- Cmd = [AbsName ++" "++filename:join(DataDir,"vsftpd.conf"),
- " -oftpd_banner=erlang_otp_testing",
- " -oanon_root=\"",AnonRoot,"\"",
- " -orsa_cert_file=\"",filename:join(DataDir,"server-cert.pem"),"\"",
- " -orsa_private_key_file=\"",filename:join(DataDir,"server-key.pem"),"\""
- ],
- Result = os:cmd(Cmd),
- ct:log("Config file:~n~s~n~nServer start command:~n ~s~nResult:~n ~p",
- [case file:read_file(ConfFile) of
- {ok,X} -> X;
- _ -> ""
- end,
- Cmd, Result
- ]),
- case Result of
- [] -> {ok,'dont care'};
- [Msg] -> {error,Msg}
- end
- end,
- fun(_StartResult) -> os:cmd("ps ax | grep erlang_otp_testing | grep -v grep")
- end,
- fun(_StartResult) -> os:cmd("kill `ps ax | grep erlang_otp_testing | awk '/vsftpd/{print $1}'`")
- end,
- fun(__CONF__) ->
- AnonRoot = proplists:get_value(priv_dir,__CONF__),
- [{id2ftp, fun(Id) -> filename:join(AnonRoot,Id) end},
- {id2ftp_result,fun(Id) -> filename:join(AnonRoot,Id) end} | __CONF__]
- end,
- "localhost",
- 9999
- }
- ]
+ [{"vsftpd",
+ "/sbin:/usr/sbin:/usr/local/sbin",
+ fun(__CONF__, AbsName) ->
+ DataDir = proplists:get_value(data_dir,__CONF__),
+ ConfFile = filename:join(DataDir, "vsftpd.conf"),
+ PrivDir = proplists:get_value(priv_dir,__CONF__),
+ AnonRoot = PrivDir,
+ Cmd = [AbsName ++" "++filename:join(DataDir,"vsftpd.conf"),
+ " -oftpd_banner=erlang_otp_testing",
+ " -oanon_root=\"",AnonRoot,"\"",
+ " -orsa_cert_file=\"",filename:join(DataDir,"server-cert.pem"),"\"",
+ " -orsa_private_key_file=\"",filename:join(DataDir,"server-key.pem"),"\""
+ ],
+ Result = os:cmd(Cmd),
+ ct:log("Config file:~n~s~n~nServer start command:~n ~s~nResult:~n ~p",
+ [case file:read_file(ConfFile) of
+ {ok,X} -> X;
+ _ -> ""
+ end,
+ Cmd, Result
+ ]),
+ case Result of
+ [] -> {ok,'dont care'};
+ [Msg] -> {error,Msg}
+ end
+ end,
+ fun(_StartResult) -> os:cmd("ps ax | grep erlang_otp_testing | grep -v grep")
+ end,
+ fun(_StartResult) -> os:cmd("kill `ps ax | grep erlang_otp_testing | awk '/vsftpd/{print $1}'`")
+ end,
+ fun(__CONF__) ->
+ AnonRoot = proplists:get_value(priv_dir,__CONF__),
+ [{id2ftp, fun(Id) -> filename:join(AnonRoot,Id) end},
+ {id2ftp_result,fun(Id) -> filename:join(AnonRoot,Id) end} | __CONF__]
+ end,
+ "localhost",
+ 9999
+ }
+ ]
).
init_per_suite(Config) ->
case find_executable(Config) of
- false ->
- {skip, "No ftp server found"};
- {ok,Data} ->
- TstDir = filename:join(proplists:get_value(priv_dir,Config), "test"),
- file:make_dir(TstDir),
- %% make_cert_files(dsa, rsa, "server-", proplists:get_value(data_dir,Config)),
+ false ->
+ {skip, "No ftp server found"};
+ {ok,Data} ->
+ TstDir = filename:join(proplists:get_value(priv_dir,Config), "test"),
+ file:make_dir(TstDir),
+ %% make_cert_files(dsa, rsa, "server-", proplists:get_value(data_dir,Config)),
ftp_test_lib:make_cert_files(proplists:get_value(data_dir,Config)),
- start_ftpd([{test_dir,TstDir},
- {ftpd_data,Data}
- | Config])
+ start_ftpd([{test_dir,TstDir},
+ {ftpd_data,Data}
+ | Config])
end.
end_per_suite(Config) ->
@@ -218,14 +218,14 @@ init_per_group(ftp_sup, Config) ->
_:_ ->
{skip, "Ftp did not start"}
end;
-init_per_group(_Group, Config) ->
+init_per_group(_Group, Config) ->
Config.
-end_per_group(ftp_sup, Config) ->
+end_per_group(ftp_sup, Config) ->
ftp:stop(),
Config;
-end_per_group(_Group, Config) ->
+end_per_group(_Group, Config) ->
Config.
%%--------------------------------------------------------------------
@@ -250,31 +250,31 @@ init_per_testcase(Case, Config0) ->
ACTIVE = [{mode,active}],
PASSIVE = [{mode,passive}],
CaseOpts = case Case of
- progress_report_send -> [{progress, {?MODULE,progress,#progress{}}}];
- progress_report_recv -> [{progress, {?MODULE,progress,#progress{}}}];
- _ -> []
- end,
- ExtraOpts = [verbose | CaseOpts],
+ progress_report_send -> [{progress, {?MODULE,progress,#progress{}}}];
+ progress_report_recv -> [{progress, {?MODULE,progress,#progress{}}}];
+ _ -> []
+ end,
+ ExtraOpts = [{verbose,true} | CaseOpts],
Config =
- case Group of
- ftp_active -> ftp__open(Config0, ACTIVE ++ ExtraOpts);
- ftps_active -> ftp__open(Config0, TLS++ ACTIVE ++ ExtraOpts);
- ftp_passive -> ftp__open(Config0, PASSIVE ++ ExtraOpts);
- ftps_passive -> ftp__open(Config0, TLS++PASSIVE ++ ExtraOpts);
+ case Group of
+ ftp_active -> ftp__open(Config0, ACTIVE ++ ExtraOpts);
+ ftps_active -> ftp__open(Config0, TLS++ ACTIVE ++ ExtraOpts);
+ ftp_passive -> ftp__open(Config0, PASSIVE ++ ExtraOpts);
+ ftps_passive -> ftp__open(Config0, TLS++PASSIVE ++ ExtraOpts);
ftp_sup -> ftp_start_service(Config0, ACTIVE ++ ExtraOpts);
- undefined -> Config0
- end,
+ undefined -> Config0
+ end,
case Case of
- user -> Config;
- bad_user -> Config;
- error_elogin -> Config;
- error_ehost -> Config;
- clean_shutdown -> Config;
- _ ->
- Pid = proplists:get_value(ftp,Config),
- ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS(atom_to_list(Group)++"-"++atom_to_list(Case)) ),
- ok = ftp:cd(Pid, proplists:get_value(priv_dir,Config)),
- Config
+ user -> Config;
+ bad_user -> Config;
+ error_elogin -> Config;
+ error_ehost -> Config;
+ clean_shutdown -> Config;
+ _ ->
+ Pid = proplists:get_value(ftp,Config),
+ ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS(atom_to_list(Group)++"-"++atom_to_list(Case)) ),
+ ok = ftp:cd(Pid, proplists:get_value(priv_dir,Config)),
+ Config
end.
end_per_testcase(T, _Config) when T =:= app; T =:= appup -> ok;
@@ -283,16 +283,16 @@ end_per_testcase(bad_user, _Config) -> ok;
end_per_testcase(error_elogin, _Config) -> ok;
end_per_testcase(error_ehost, _Config) -> ok;
end_per_testcase(clean_shutdown, _Config) -> ok;
-end_per_testcase(_Case, Config) ->
+end_per_testcase(_Case, Config) ->
case proplists:get_value(tc_status,Config) of
- ok -> ok;
- _ ->
- try ftp:latest_ctrl_response(proplists:get_value(ftp,Config))
- of
- {ok,S} -> ct:log("***~n*** Latest ctrl channel response:~n*** ~p~n***",[S])
- catch
- _:_ -> ok
- end
+ ok -> ok;
+ _ ->
+ try ftp:latest_ctrl_response(proplists:get_value(ftp,Config))
+ of
+ {ok,S} -> ct:log("***~n*** Latest ctrl channel response:~n*** ~p~n***",[S])
+ catch
+ _:_ -> ok
+ end
end,
Group = proplists:get_value(name, proplists:get_value(tc_group_properties,Config)),
case Group of
@@ -319,17 +319,17 @@ appup(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
user() -> [
- {doc, "Open an ftp connection to a host, and logon as anonymous ftp,"
- " then logoff"}].
+ {doc, "Open an ftp connection to a host, and logon as anonymous ftp,"
+ " then logoff"}].
user(Config) ->
Pid = proplists:get_value(ftp, Config),
ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS("")),% logon
- ok = ftp:close(Pid), % logoff
- {error,eclosed} = ftp:pwd(Pid), % check logoff result
+ ok = ftp:close(Pid), % logoff
+ {error,eclosed} = ftp:pwd(Pid), % check logoff result
ok.
%%-------------------------------------------------------------------------
-bad_user() ->
+bad_user() ->
[{doc, "Open an ftp connection to a host, and logon with bad user."}].
bad_user(Config) ->
Pid = proplists:get_value(ftp, Config),
@@ -337,7 +337,7 @@ bad_user(Config) ->
ok.
%%-------------------------------------------------------------------------
-pwd() ->
+pwd() ->
[{doc, "Test ftp:pwd/1 & ftp:lpwd/1"}].
pwd(Config0) ->
Config = set_state([reset], Config0),
@@ -348,7 +348,7 @@ pwd(Config0) ->
PathLpwd = id2ftp_result("", Config).
%%-------------------------------------------------------------------------
-cd() ->
+cd() ->
["Open an ftp connection, log on as anonymous ftp, and cd to a"
"directory and to a non-existent directory."].
cd(Config0) ->
@@ -376,7 +376,7 @@ lcd(Config0) ->
{error, epath} = ftp:lcd(Pid, ?BAD_DIR).
%%-------------------------------------------------------------------------
-ls() ->
+ls() ->
[{doc, "Open an ftp connection; ls the current directory, and the "
"\"test\" directory. We assume that ls never fails, since "
"it's output is meant to be read by humans. "}].
@@ -386,40 +386,40 @@ ls(Config0) ->
{ok, _R1} = ftp:ls(Pid),
{ok, _R2} = ftp:ls(Pid, id2ftp("test",Config)),
%% neither nlist nor ls operates on a directory
- %% they operate on a pathname, which *can* be a
- %% directory, but can also be a filename or a group
+ %% they operate on a pathname, which *can* be a
+ %% directory, but can also be a filename or a group
%% of files (including wildcards).
case proplists:get_value(wildcard_support, Config) of
- true ->
- {ok, _R3} = ftp:ls(Pid, id2ftp("te*",Config));
- _ ->
- ok
+ true ->
+ {ok, _R3} = ftp:ls(Pid, id2ftp("te*",Config));
+ _ ->
+ ok
end.
%%-------------------------------------------------------------------------
-nlist() ->
+nlist() ->
[{doc,"Open an ftp connection; nlist the current directory, and the "
- "\"test\" directory. Nlist does not behave consistenly over "
- "operating systems. On some it is an error to have an empty "
- "directory."}].
+ "\"test\" directory. Nlist does not behave consistenly over "
+ "operating systems. On some it is an error to have an empty "
+ "directory."}].
nlist(Config0) ->
Config = set_state([reset,{mkdir,"test"}], Config0),
Pid = proplists:get_value(ftp, Config),
{ok, _R1} = ftp:nlist(Pid),
{ok, _R2} = ftp:nlist(Pid, id2ftp("test",Config)),
%% neither nlist nor ls operates on a directory
- %% they operate on a pathname, which *can* be a
- %% directory, but can also be a filename or a group
+ %% they operate on a pathname, which *can* be a
+ %% directory, but can also be a filename or a group
%% of files (including wildcards).
case proplists:get_value(wildcard_support, Config) of
- true ->
- {ok, _R3} = ftp:nlist(Pid, id2ftp("te*",Config));
- _ ->
- ok
+ true ->
+ {ok, _R3} = ftp:nlist(Pid, id2ftp("te*",Config));
+ _ ->
+ ok
end.
%%-------------------------------------------------------------------------
-rename() ->
+rename() ->
[{doc, "Rename a file."}].
rename(Config0) ->
Contents = <<"ftp_SUITE test ...">>,
@@ -428,19 +428,19 @@ rename(Config0) ->
Config = set_state([reset,{mkfile,OldFile,Contents}], Config0),
Pid = proplists:get_value(ftp, Config),
- ok = ftp:rename(Pid,
- id2ftp(OldFile,Config),
- id2ftp(NewFile,Config)),
+ ok = ftp:rename(Pid,
+ id2ftp(OldFile,Config),
+ id2ftp(NewFile,Config)),
- true = (chk_file(NewFile,Contents,Config)
- and chk_no_file([OldFile],Config)),
+ true = (chk_file(NewFile,Contents,Config)
+ and chk_no_file([OldFile],Config)),
{error,epath} = ftp:rename(Pid,
- id2ftp("non_existing_file",Config),
- id2ftp(NewFile,Config)),
+ id2ftp("non_existing_file",Config),
+ id2ftp(NewFile,Config)),
ok.
%%-------------------------------------------------------------------------
-send() ->
+send() ->
[{doc, "Transfer a file with ftp using send/2."}].
send(Config0) ->
Contents = <<"ftp_SUITE test ...">>,
@@ -461,7 +461,7 @@ send(Config0) ->
ok.
%%-------------------------------------------------------------------------
-send_3() ->
+send_3() ->
[{doc, "Transfer a file with ftp using send/3."}].
send_3(Config0) ->
Contents = <<"ftp_SUITE test ...">>,
@@ -479,8 +479,8 @@ send_3(Config0) ->
{error,epath} = ftp:send(Pid, "non_existing_file", RemoteFile),
ok.
-%%-------------------------------------------------------------------------
-send_bin() ->
+%%-------------------------------------------------------------------------
+send_bin() ->
[{doc, "Send a binary."}].
send_bin(Config0) ->
BinContents = <<"ftp_SUITE test ...">>,
@@ -493,8 +493,8 @@ send_bin(Config0) ->
{error, efnamena} = ftp:send_bin(Pid, BinContents, "/nothere"),
ok.
-%%-------------------------------------------------------------------------
-send_chunk() ->
+%%-------------------------------------------------------------------------
+send_chunk() ->
[{doc, "Send a binary using chunks."}].
send_chunk(Config0) ->
Contents1 = <<"1: ftp_SUITE test ...">>,
@@ -518,7 +518,7 @@ send_chunk(Config0) ->
ok.
%%-------------------------------------------------------------------------
-delete() ->
+delete() ->
[{doc, "Delete a file."}].
delete(Config0) ->
Contents = <<"ftp_SUITE test ...">>,
@@ -543,7 +543,7 @@ mkdir(Config0) ->
ok.
%%-------------------------------------------------------------------------
-rmdir() ->
+rmdir() ->
[{doc, "Remove a directory."}].
rmdir(Config0) ->
Dir = "dir",
@@ -555,7 +555,7 @@ rmdir(Config0) ->
ok.
%%-------------------------------------------------------------------------
-append() ->
+append() ->
[{doc, "Append a local file twice to a remote file"}].
append(Config0) ->
SrcFile = "f_src.txt",
@@ -568,9 +568,9 @@ append(Config0) ->
chk_file(DstFile, <<Contents/binary,Contents/binary>>, Config),
{error,epath} = ftp:append(Pid, id2ftp("non_existing_file",Config), id2ftp(DstFile,Config)),
ok.
-
+
%%-------------------------------------------------------------------------
-append_bin() ->
+append_bin() ->
[{doc, "Append a local file twice to a remote file using append_bin"}].
append_bin(Config0) ->
DstFile = "f_dst.txt",
@@ -582,7 +582,7 @@ append_bin(Config0) ->
chk_file(DstFile, <<Contents/binary,Contents/binary>>, Config).
%%-------------------------------------------------------------------------
-append_chunk() ->
+append_chunk() ->
[{doc, "Append chunks."}].
append_chunk(Config0) ->
File = "f_dst.txt",
@@ -598,7 +598,7 @@ append_chunk(Config0) ->
chk_file(File, <<"ERLERL">>, Config).
%%-------------------------------------------------------------------------
-recv() ->
+recv() ->
[{doc, "Receive a file using recv/2"}].
recv(Config0) ->
File1 = "f_dst1.txt",
@@ -618,7 +618,7 @@ recv(Config0) ->
ok.
%%-------------------------------------------------------------------------
-recv_3() ->
+recv_3() ->
[{doc,"Receive a file using recv/3"}].
recv_3(Config0) ->
DstFile = "f_src.txt",
@@ -631,7 +631,7 @@ recv_3(Config0) ->
chk_file(DstFile, Contents, Config).
%%-------------------------------------------------------------------------
-recv_bin() ->
+recv_bin() ->
[{doc, "Receive a file as a binary."}].
recv_bin(Config0) ->
File = "f_dst.txt",
@@ -644,7 +644,7 @@ recv_bin(Config0) ->
ok.
%%-------------------------------------------------------------------------
-recv_bin_twice() ->
+recv_bin_twice() ->
[{doc, "Receive two files as a binaries."}].
recv_bin_twice(Config0) ->
File1 = "f_dst1.txt",
@@ -663,7 +663,7 @@ recv_bin_twice(Config0) ->
{error,epath} = ftp:recv_bin(Pid, id2ftp("non_existing_file",Config)),
ok.
%%-------------------------------------------------------------------------
-recv_chunk() ->
+recv_chunk() ->
[{doc, "Receive a file using chunk-wise."}].
recv_chunk(Config0) ->
File = "big_file.txt",
@@ -709,7 +709,7 @@ recv_chunk_three_times(Config0) ->
ok = ftp:recv_chunk_start(Pid, id2ftp(File3,Config)),
{ok, ReceivedContents3} = do_recv_chunk(Pid),
-
+
ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)),
{ok, ReceivedContents1} = do_recv_chunk(Pid),
@@ -721,15 +721,15 @@ recv_chunk_three_times(Config0) ->
find_diff(ReceivedContents3, Contents3).
-do_recv_chunk(Pid) ->
+do_recv_chunk(Pid) ->
recv_chunk(Pid, <<>>).
-recv_chunk(Pid, Acc) ->
+recv_chunk(Pid, Acc) ->
case ftp:recv_chunk(Pid) of
- ok ->
+ ok ->
{ok, Acc};
- {ok, Bin} ->
+ {ok, Bin} ->
recv_chunk(Pid, <<Acc/binary, Bin/binary>>);
- Error ->
+ Error ->
Error
end.
@@ -742,23 +742,23 @@ recv_chunk_delay(Config0) when is_list(Config0) ->
{ok, ReceivedContents} = delay_recv_chunk(Pid),
find_diff(ReceivedContents, Contents).
-delay_recv_chunk(Pid) ->
+delay_recv_chunk(Pid) ->
delay_recv_chunk(Pid, <<>>).
-delay_recv_chunk(Pid, Acc) ->
- ct:pal("Recived size ~p", [byte_size(Acc)]),
+delay_recv_chunk(Pid, Acc) ->
+ ct:pal("Received size ~p", [byte_size(Acc)]),
case ftp:recv_chunk(Pid) of
- ok ->
+ ok ->
{ok, Acc};
- {ok, Bin} ->
+ {ok, Bin} ->
ct:sleep(100),
delay_recv_chunk(Pid, <<Acc/binary, Bin/binary>>);
- Error ->
+ Error ->
Error
end.
%%-------------------------------------------------------------------------
-type() ->
- [{doc,"Test that we can change btween ASCCI and binary transfer mode"}].
+type() ->
+ [{doc,"Test that we can change between ASCII and binary transfer mode"}].
type(Config) ->
Pid = proplists:get_value(ftp, Config),
ok = ftp:type(Pid, ascii),
@@ -773,32 +773,32 @@ quote(Config) ->
[_| _] = ftp:quote(Pid, "help"),
%% This negativ test causes some ftp servers to hang. This test
%% is not important for the client, so we skip it for now.
- %%["425 Can't build data connection: Connection refused."]
- %% = ftp:quote(Pid, "list"),
+ %%["425 Can't build data connection: Connection refused."]
+ %% = ftp:quote(Pid, "list"),
ok.
%%-------------------------------------------------------------------------
progress_report_send() ->
[{doc, "Test the option progress for ftp:send/[2,3]"}].
progress_report_send(Config) when is_list(Config) ->
- ReportPid =
- spawn_link(?MODULE, progress_report_receiver_init, [self(), 1]),
+ ReportPid =
+ spawn_link(?MODULE, progress_report_receiver_init, [self(), 1]),
send(Config),
receive
- {ReportPid, ok} ->
- ok
+ {ReportPid, ok} ->
+ ok
end.
%%-------------------------------------------------------------------------
progress_report_recv() ->
[{doc, "Test the option progress for ftp:recv/[2,3]"}].
progress_report_recv(Config) when is_list(Config) ->
- ReportPid =
- spawn_link(?MODULE, progress_report_receiver_init, [self(), 3]),
+ ReportPid =
+ spawn_link(?MODULE, progress_report_receiver_init, [self(), 3]),
recv(Config),
receive
- {ReportPid, ok} ->
- ok
+ {ReportPid, ok} ->
+ ok
end.
%%-------------------------------------------------------------------------
@@ -811,14 +811,14 @@ not_owner(Config) when is_list(Config) ->
Parent = self(),
OtherPid = spawn_link(
- fun() ->
- {error, not_connection_owner} = ftp:pwd(Pid),
- ftp:close(Pid),
- Parent ! {self(), ok}
- end),
+ fun() ->
+ {error, not_connection_owner} = ftp:pwd(Pid),
+ ftp:close(Pid),
+ Parent ! {self(), ok}
+ end),
receive
- {OtherPid, ok} ->
- {ok, _} = ftp:pwd(Pid)
+ {OtherPid, ok} ->
+ {ok, _} = ftp:pwd(Pid)
end.
@@ -830,13 +830,13 @@ unexpected_call()->
unexpected_call(Config) when is_list(Config) ->
Flag = process_flag(trap_exit, true),
Pid = proplists:get_value(ftp, Config),
-
- %% Serious programming fault, connetion will be shut down
+
+ %% Serious programming fault, connetion will be shut down
case (catch gen_server:call(Pid, {self(), foobar, 10}, infinity)) of
- {error, {connection_terminated, 'API_violation'}} ->
- ok;
- Unexpected1 ->
- exit({unexpected_result, Unexpected1})
+ {error, {connection_terminated, 'API_violation'}} ->
+ ok;
+ Unexpected1 ->
+ exit({unexpected_result, Unexpected1})
end,
ct:sleep(500),
undefined = process_info(Pid, status),
@@ -848,52 +848,52 @@ unexpected_cast()->
unexpected_cast(Config) when is_list(Config) ->
Flag = process_flag(trap_exit, true),
Pid = proplists:get_value(ftp, Config),
- %% Serious programming fault, connetion will be shut down
+ %% Serious programming fault, connetion will be shut down
gen_server:cast(Pid, {self(), foobar, 10}),
ct:sleep(500),
undefined = process_info(Pid, status),
process_flag(trap_exit, Flag).
%%-------------------------------------------------------------------------
-
+
unexpected_bang()->
[{doc, "Test that connection ignores unexpected bang"}].
unexpected_bang(Config) when is_list(Config) ->
Flag = process_flag(trap_exit, true),
Pid = proplists:get_value(ftp, Config),
- %% Could be an innocent misstake the connection lives.
- Pid ! foobar,
+ %% Could be an innocent misstake the connection lives.
+ Pid ! foobar,
ct:sleep(500),
{status, _} = process_info(Pid, status),
process_flag(trap_exit, Flag).
-
+
%%-------------------------------------------------------------------------
-clean_shutdown() ->
+clean_shutdown() ->
[{doc, "Test that owning process that exits with reason "
"'shutdown' does not cause an error message. OTP 6035"}].
clean_shutdown(Config) ->
Parent = self(),
HelperPid = spawn(
- fun() ->
- ftp__open(Config, [verbose]),
- Parent ! ok,
- receive
- nothing -> ok
- end
- end),
+ fun() ->
+ ftp__open(Config, [{verbose,true}]),
+ Parent ! ok,
+ receive
+ nothing -> ok
+ end
+ end),
receive
- ok ->
- PrivDir = proplists:get_value(priv_dir, Config),
- LogFile = filename:join([PrivDir,"ticket_6035.log"]),
- error_logger:logfile({open, LogFile}),
- exit(HelperPid, shutdown),
- timer:sleep(2000),
- error_logger:logfile(close),
- case is_error_report_6035(LogFile) of
- true -> ok;
- false -> {fail, "Bad logfile"}
- end
+ ok ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ LogFile = filename:join([PrivDir,"ticket_6035.log"]),
+ error_logger:logfile({open, LogFile}),
+ exit(HelperPid, shutdown),
+ timer:sleep(2000),
+ error_logger:logfile(close),
+ case is_error_report_6035(LogFile) of
+ true -> ok;
+ false -> {fail, "Bad logfile"}
+ end
end.
%%-------------------------------------------------------------------------
@@ -906,7 +906,7 @@ start_ftp(Config) ->
{ok, [_|_]} = ftp:service_info(Pid0),
ftp:stop_service(Pid0),
ct:sleep(100),
- Pids1 = [ServicePid || {_, ServicePid} <- ftp:services()],
+ Pids1 = [ServicePid || {_, ServicePid} <- ftp:services()],
false = lists:member(Pid0, Pids1),
Host = proplists:get_value(ftpd_host,Config),
@@ -924,7 +924,7 @@ ftp_worker(Config) ->
Pid = proplists:get_value(ftp,Config),
case supervisor:which_children(ftp_sup) of
[{_,_, worker, [ftp]}] ->
- ftp:stop_service(Pid),
+ ftp:stop_service(Pid),
ct:sleep(5000),
[] = supervisor:which_children(ftp_sup),
ok;
@@ -943,9 +943,9 @@ error_elogin(Config0) ->
SrcDir = "data",
File = "file.txt",
Config = set_state([reset,
- {mkdir,Dir},
- {mkfile,OldFile,<<"Contents..">>},
- {mkfile,[SrcDir,File],<<"Contents..">>}], Config0),
+ {mkdir,Dir},
+ {mkfile,OldFile,<<"Contents..">>},
+ {mkfile,[SrcDir,File],<<"Contents..">>}], Config0),
Pid = proplists:get_value(ftp, Config),
ok = ftp:lcd(Pid, id2ftp(SrcDir,Config)),
@@ -953,15 +953,15 @@ error_elogin(Config0) ->
ok = ftp:lcd(Pid, id2ftp("",Config)),
{error,elogin} = ftp:pwd(Pid),
{error,elogin} = ftp:cd(Pid, id2ftp(Dir,Config)),
- {error,elogin} = ftp:rename(Pid,
- id2ftp(OldFile,Config),
- id2ftp(NewFile,Config)),
+ {error,elogin} = ftp:rename(Pid,
+ id2ftp(OldFile,Config),
+ id2ftp(NewFile,Config)),
ok.
error_ehost(_Config) ->
{error, ehost} = ftp:open("nohost.nodomain"),
ok.
-
+
%%--------------------------------------------------------------------
%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
@@ -973,22 +973,22 @@ chk_file(PathList, ExpectedContents, Config) ->
Path = filename:join(PathList),
AbsPath = id2abs(Path,Config),
case file:read_file(AbsPath) of
- {ok,ExpectedContents} ->
- true;
- {ok,ReadContents} ->
- {error,{diff,Pos,RC,LC}} = find_diff(ReadContents, ExpectedContents, 1),
- ct:log("Bad contents of ~p.~nGot:~n~p~nExpected:~n~p~nDiff at pos ~p ~nRead: ~p~nExp : ~p",
- [AbsPath,ReadContents,ExpectedContents,Pos,RC,LC]),
- ct:fail("Bad contents of ~p", [Path]);
- {error,Error} ->
- try begin
- {ok,CWD} = file:get_cwd(),
- ct:log("file:get_cwd()=~p~nfiles:~n~p",[CWD,file:list_dir(CWD)])
- end
- of _ -> ok
- catch _:_ ->ok
- end,
- ct:fail("Error reading ~p: ~p",[Path,Error])
+ {ok,ExpectedContents} ->
+ true;
+ {ok,ReadContents} ->
+ {error,{diff,Pos,RC,LC}} = find_diff(ReadContents, ExpectedContents, 1),
+ ct:log("Bad contents of ~p.~nGot:~n~p~nExpected:~n~p~nDiff at pos ~p ~nRead: ~p~nExp : ~p",
+ [AbsPath,ReadContents,ExpectedContents,Pos,RC,LC]),
+ ct:fail("Bad contents of ~p", [Path]);
+ {error,Error} ->
+ try begin
+ {ok,CWD} = file:get_cwd(),
+ ct:log("file:get_cwd()=~p~nfiles:~n~p",[CWD,file:list_dir(CWD)])
+ end
+ of _ -> ok
+ catch _:_ ->ok
+ end,
+ ct:fail("Error reading ~p: ~p",[Path,Error])
end.
@@ -999,14 +999,14 @@ chk_no_file(PathList, Config) ->
Path = filename:join(PathList),
AbsPath = id2abs(Path,Config),
case file:read_file(AbsPath) of
- {error,enoent} ->
- true;
- {ok,Contents} ->
- ct:log("File ~p exists although it shouldn't. Contents:~n~p",
- [AbsPath,Contents]),
- ct:fail("File exists: ~p", [Path]);
- {error,Error} ->
- ct:fail("Unexpected error reading ~p: ~p",[Path,Error])
+ {error,enoent} ->
+ true;
+ {ok,Contents} ->
+ ct:log("File ~p exists although it shouldn't. Contents:~n~p",
+ [AbsPath,Contents]),
+ ct:fail("File exists: ~p", [Path]);
+ {error,Error} ->
+ ct:fail("Unexpected error reading ~p: ~p",[Path,Error])
end.
@@ -1017,26 +1017,26 @@ chk_dir(PathList, Config) ->
Path = filename:join(PathList),
AbsPath = id2abs(Path,Config),
case file:read_file_info(AbsPath) of
- {ok, #file_info{type=directory}} ->
- true;
- {ok, #file_info{type=Type}} ->
- ct:fail("Expected dir ~p is a ~p",[Path,Type]);
- {error,Error} ->
- ct:fail("Expected dir ~p: ~p",[Path,Error])
+ {ok, #file_info{type=directory}} ->
+ true;
+ {ok, #file_info{type=Type}} ->
+ ct:fail("Expected dir ~p is a ~p",[Path,Type]);
+ {error,Error} ->
+ ct:fail("Expected dir ~p: ~p",[Path,Error])
end.
chk_no_dir(PathList, Config) ->
Path = filename:join(PathList),
AbsPath = id2abs(Path,Config),
case file:read_file_info(AbsPath) of
- {error,enoent} ->
- true;
- {ok, #file_info{type=directory}} ->
- ct:fail("Dir ~p erroneously exists",[Path]);
- {ok, #file_info{type=Type}} ->
- ct:fail("~p ~p erroneously exists",[Type,Path]);
- {error,Error} ->
- ct:fail("Unexpected error for ~p: ~p",[Path,Error])
+ {error,enoent} ->
+ true;
+ {ok, #file_info{type=directory}} ->
+ ct:fail("Dir ~p erroneously exists",[Path]);
+ {ok, #file_info{type=Type}} ->
+ ct:fail("~p ~p erroneously exists",[Type,Path]);
+ {error,Error} ->
+ ct:fail("Unexpected error for ~p: ~p",[Path,Error])
end.
%%--------------------------------------------------------------------
@@ -1046,12 +1046,12 @@ find_executable(Config) ->
search_executable([{Name,Paths,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}|Srvrs]) ->
case os_find(Name,Paths) of
- false ->
- ct:log("~p not found",[Name]),
- search_executable(Srvrs);
- AbsName ->
- ct:comment("Found ~p",[AbsName]),
- {ok, {AbsName,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}}
+ false ->
+ ct:log("~p not found",[Name]),
+ search_executable(Srvrs);
+ AbsName ->
+ ct:comment("Found ~p",[AbsName]),
+ {ok, {AbsName,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}}
end;
search_executable([]) ->
false.
@@ -1059,33 +1059,33 @@ search_executable([]) ->
os_find(Name, Paths) ->
case os:find_executable(Name, Paths) of
- false -> os:find_executable(Name);
- AbsName -> AbsName
+ false -> os:find_executable(Name);
+ AbsName -> AbsName
end.
%%%----------------------------------------------------------------
start_ftpd(Config0) ->
{AbsName,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} =
- proplists:get_value(ftpd_data, Config0),
+ proplists:get_value(ftpd_data, Config0),
case StartCmd(Config0, AbsName) of
- {ok,StartResult} ->
- Config = [{ftpd_host,Host},
- {ftpd_port,Port},
- {ftpd_start_result,StartResult} | ConfigRewrite(Config0)],
- try
- ftp__close(ftp__open(Config,[verbose]))
- of
- Config1 when is_list(Config1) ->
- ct:log("Usuable ftp server ~p started on ~p:~p",[AbsName,Host,Port]),
- Config
- catch
- Class:Exception ->
- ct:log("Ftp server ~p started on ~p:~p but is unusable:~n~p:~p",
- [AbsName,Host,Port,Class,Exception]),
- {skip, [AbsName," started but unusuable"]}
- end;
- {error,Msg} ->
- {skip, [AbsName," not started: ",Msg]}
+ {ok,StartResult} ->
+ Config = [{ftpd_host,Host},
+ {ftpd_port,Port},
+ {ftpd_start_result,StartResult} | ConfigRewrite(Config0)],
+ try
+ ftp__close(ftp__open(Config,[{verbose,true}]))
+ of
+ Config1 when is_list(Config1) ->
+ ct:log("Usuable ftp server ~p started on ~p:~p",[AbsName,Host,Port]),
+ Config
+ catch
+ Class:Exception ->
+ ct:log("Ftp server ~p started on ~p:~p but is unusable:~n~p:~p",
+ [AbsName,Host,Port,Class,Exception]),
+ {skip, [AbsName," started but unusuable"]}
+ end;
+ {error,Msg} ->
+ {skip, [AbsName," not started: ",Msg]}
end.
stop_ftpd(Config) ->
@@ -1125,13 +1125,13 @@ ftp_stop_service(Config) ->
split(Cs) -> string:tokens(Cs, "\r\n").
-find_diff(Bin1, Bin2) ->
+find_diff(Bin1, Bin2) ->
case find_diff(Bin1, Bin2, 1) of
- {error, {diff,Pos,RC,LC}} ->
- ct:log("Contents differ at position ~p.~nOp1: ~p~nOp2: ~p",[Pos,RC,LC]),
- ct:fail("Contents differ at pos ~p",[Pos]);
- Other ->
- Other
+ {error, {diff,Pos,RC,LC}} ->
+ ct:log("Contents differ at position ~p.~nOp1: ~p~nOp2: ~p",[Pos,RC,LC]),
+ ct:fail("Contents differ at pos ~p",[Pos]);
+ Other ->
+ Other
end.
find_diff(A, A, _) -> true;
@@ -1140,7 +1140,7 @@ find_diff(RC, LC, Pos) -> {error, {diff, Pos, RC, LC}}.
set_state(Ops, Config) when is_list(Ops) -> lists:foldl(fun set_state/2, Config, Ops);
-set_state(reset, Config) ->
+set_state(reset, Config) ->
rm('*', id2abs("",Config)),
PrivDir = proplists:get_value(priv_dir,Config),
file:set_cwd(PrivDir),
@@ -1161,30 +1161,30 @@ mk_path(Abs) -> lists:foldl(fun mk_path/2, [], filename:split(filename:dirname(A
mk_path(F, Pfx) ->
case file:read_file_info(AbsName=filename:join(Pfx,F)) of
- {ok,#file_info{type=directory}} ->
- AbsName;
- {error,eexist} ->
- AbsName;
- {error,enoent} ->
- ok = file:make_dir(AbsName),
- AbsName
+ {ok,#file_info{type=directory}} ->
+ AbsName;
+ {error,eexist} ->
+ AbsName;
+ {error,enoent} ->
+ ok = file:make_dir(AbsName),
+ AbsName
end.
-
+
rm('*', Pfx) ->
{ok,Fs} = file:list_dir(Pfx),
lists:foreach(fun(F) -> rm(F, Pfx) end, Fs);
-rm(F, Pfx) ->
+rm(F, Pfx) ->
case file:read_file_info(AbsName=filename:join(Pfx,F)) of
- {ok,#file_info{type=directory}} ->
- {ok,Fs} = file:list_dir(AbsName),
- lists:foreach(fun(F1) -> rm(F1,AbsName) end, Fs),
- ok = file:del_dir(AbsName);
+ {ok,#file_info{type=directory}} ->
+ {ok,Fs} = file:list_dir(AbsName),
+ lists:foreach(fun(F1) -> rm(F1,AbsName) end, Fs),
+ ok = file:del_dir(AbsName);
- {ok,#file_info{type=regular}} ->
- ok = file:delete(AbsName);
+ {ok,#file_info{type=regular}} ->
+ ok = file:delete(AbsName);
- {error,enoent} ->
- ok
+ {error,enoent} ->
+ ok
end.
id2abs(Id, Conf) -> filename:join(proplists:get_value(priv_dir,Conf),ids(Id)).
@@ -1216,10 +1216,10 @@ progress(#progress{current = Current} = P, _File, {transfer_size, 0} = M) ->
ct:pal("Progress: ~p",[M]),
progress_report_receiver ! finish,
case P#progress.total of
- unknown -> P;
- Current -> P;
- Total -> ct:fail({error, {progress, {total,Total}, {current,Current}}}),
- P
+ unknown -> P;
+ Current -> P;
+ Total -> ct:fail({error, {progress, {total,Total}, {current,Current}}}),
+ P
end;
progress(#progress{current = Current} = P, _File, {transfer_size, Size} = M) ->
@@ -1244,7 +1244,7 @@ progress_report_receiver_expect_N_files(_Parent, 0) ->
progress_report_receiver_expect_N_files(Parent, N) ->
ct:pal("progress_report expects ~p more files",[N]),
receive
- start -> ok
+ start -> ok
end,
progress_report_receiver_loop(Parent, N-1).
@@ -1252,13 +1252,13 @@ progress_report_receiver_expect_N_files(Parent, N) ->
progress_report_receiver_loop(Parent, N) ->
ct:pal("progress_report expect update | finish. N = ~p",[N]),
receive
- update ->
- ct:pal("progress_report got update",[]),
- progress_report_receiver_loop(Parent, N);
- finish ->
- ct:pal("progress_report got finish, send ~p to ~p",[{self(),ok}, Parent]),
- Parent ! {self(), ok},
- progress_report_receiver_expect_N_files(Parent, N)
+ update ->
+ ct:pal("progress_report got update",[]),
+ progress_report_receiver_loop(Parent, N);
+ finish ->
+ ct:pal("progress_report got finish, send ~p to ~p",[{self(),ok}, Parent]),
+ Parent ! {self(), ok},
+ progress_report_receiver_expect_N_files(Parent, N)
end.
%%%----------------------------------------------------------------
@@ -1266,9 +1266,9 @@ progress_report_receiver_loop(Parent, N) ->
is_error_report_6035(LogFile) ->
case file:read_file(LogFile) of
- {ok, Bin} ->
- nomatch =/= binary:match(Bin, <<"=ERROR REPORT====">>);
- _ ->
- false
+ {ok, Bin} ->
+ nomatch =/= binary:match(Bin, <<"=ERROR REPORT====">>);
+ _ ->
+ false
end.
diff --git a/lib/ftp/test/ftp_bench.spec b/lib/ftp/test/ftp_bench.spec
deleted file mode 100644
index 4d1ecf8891..0000000000
--- a/lib/ftp/test/ftp_bench.spec
+++ /dev/null
@@ -1 +0,0 @@
-{suites,"../ftp_test",[]}.
diff --git a/lib/ftp/vsn.mk b/lib/ftp/vsn.mk
index 20a5eea26a..733b082b21 100644
--- a/lib/ftp/vsn.mk
+++ b/lib/ftp/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = ftp
-FTP_VSN = 1.0.4
+FTP_VSN = 1.0.5
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(FTP_VSN)$(PRE_VSN)"
diff --git a/lib/hipe/Makefile b/lib/hipe/Makefile
index 0676484fca..4998522943 100644
--- a/lib/hipe/Makefile
+++ b/lib/hipe/Makefile
@@ -75,3 +75,6 @@ distclean:
realclean:
$(V_at)$(MAKE) MAKETARGET="realclean" all-subdirs all-subdirs-x
+DIA_PLT_APPS=compiler syntax_tools
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/hipe/cerl/cerl_closurean.erl b/lib/hipe/cerl/cerl_closurean.erl
index a2bd7fe0f0..583c5d624a 100644
--- a/lib/hipe/cerl/cerl_closurean.erl
+++ b/lib/hipe/cerl/cerl_closurean.erl
@@ -797,7 +797,8 @@ take_work({Queue0, Set0}) ->
-spec is_escape_op(atom(), arity()) -> boolean().
-is_escape_op(match_fail, 1) -> false;
+is_escape_op(match_fail, 1) -> false;
+is_escape_op(recv_wait_timeout, 1) -> false;
is_escape_op(F, A) when is_atom(F), is_integer(A) -> true.
-spec is_escape_op(atom(), atom(), arity()) -> boolean().
@@ -814,6 +815,7 @@ is_escape_op(M, F, A) when is_atom(M), is_atom(F), is_integer(A) -> true.
-spec is_literal_op(atom(), arity()) -> boolean().
+is_literal_op(recv_wait_timeout, 1) -> true;
is_literal_op(match_fail, 1) -> true;
is_literal_op(F, A) when is_atom(F), is_integer(A) -> false.
diff --git a/lib/hipe/cerl/erl_bif_types.erl b/lib/hipe/cerl/erl_bif_types.erl
index 0b6db544ca..bac489c07c 100644
--- a/lib/hipe/cerl/erl_bif_types.erl
+++ b/lib/hipe/cerl/erl_bif_types.erl
@@ -115,6 +115,7 @@
t_map_is_key/3,
t_map_entries/2,
t_map_put/3,
+ t_map_remove/3,
t_map_update/3,
t_map_pairwise_merge/4
]).
@@ -1700,6 +1701,11 @@ type(maps, put, 3, Xs, Opaques) ->
fun ([Key, Value, Map]) ->
t_map_put({Key, Value}, Map, Opaques)
end, Opaques);
+type(maps, remove, 2, Xs, Opaques) ->
+ strict(maps, remove, 2, Xs,
+ fun ([Key, Map]) ->
+ t_map_remove(Key, Map, Opaques)
+ end, Opaques);
type(maps, size, 1, Xs, Opaques) ->
strict(maps, size, 1, Xs,
fun ([Map]) ->
@@ -2648,6 +2654,8 @@ arg_types(maps, merge, 2) ->
[t_map(), t_map()];
arg_types(maps, put, 3) ->
[t_any(), t_any(), t_map()];
+arg_types(maps, remove, 2) ->
+ [t_any(), t_map()];
arg_types(maps, size, 1) ->
[t_map()];
arg_types(maps, update, 3) ->
diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index badf58936f..5f5612fcd3 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -154,6 +154,7 @@
t_map_update/2, t_map_update/3,
t_map_pairwise_merge/4,
t_map_put/2, t_map_put/3,
+ t_map_remove/3,
t_matchstate/0,
t_matchstate/2,
t_matchstate_present/1,
@@ -1925,6 +1926,27 @@ map_put({Key, Value}, ?map(Pairs,DefK,DefV), Opaques) ->
end
end.
+-spec t_map_remove(erl_type(), erl_type(), opaques()) -> erl_type().
+
+t_map_remove(Key, Map, Opaques) ->
+ do_opaque(Map, Opaques, fun(UM) -> map_remove(Key, UM) end).
+
+map_remove(_, ?none) -> ?none;
+map_remove(_, ?unit) -> ?none;
+map_remove(Key, Map) ->
+ %% ?map(lists:keydelete(Key, 1, Pairs), DefK, DefV).
+ case is_singleton_type(Key) of
+ false -> Map;
+ true ->
+ ?map(Pairs,DefK,DefV) = Map,
+ case lists:keyfind(Key, 1, Pairs) of
+ false -> Map;
+ {Key, _, _} ->
+ Pairs1 = lists:keydelete(Key, 1, Pairs),
+ t_map(Pairs1, DefK, DefV)
+ end
+ end.
+
-spec t_map_update({erl_type(), erl_type()}, erl_type()) -> erl_type().
t_map_update(KV, Map) ->
diff --git a/lib/hipe/doc/src/hipe_app.xml b/lib/hipe/doc/src/HiPE_app.xml
index 50812bc991..27fbf16fd0 100644
--- a/lib/hipe/doc/src/hipe_app.xml
+++ b/lib/hipe/doc/src/HiPE_app.xml
@@ -72,8 +72,8 @@
</item>
<tag>Stack traces</tag>
- <item><p>Stack traces returned from <seealso marker="erts:erlang#get_stacktrace/0">
- <c>erlang:get_stacktrace/0</c></seealso> or as part of <c>'EXIT'</c> terms
+ <item><p>Stack traces returned from <seemfa marker="erts:erlang#get_stacktrace/0">
+ <c>erlang:get_stacktrace/0</c></seemfa> or as part of <c>'EXIT'</c> terms
can look incomplete if HiPE compiled functions are involved. Typically a stack trace
will contain only BEAM compiled functions or only HiPE compiled functions, depending
on where the exception was raised.</p>
@@ -82,18 +82,18 @@
<tag>Tracing</tag>
<item><p>Erlang call trace is not supported by HiPE. Calling
- <seealso marker="erts:erlang#trace_pattern/3"><c>erlang:trace_pattern({M,F,A}, ...)</c></seealso>
+ <seemfa marker="erts:erlang#trace_pattern/3"><c>erlang:trace_pattern({M,F,A}, ...)</c></seemfa>
does not have any effect on HiPE compiled modules.</p>
</item>
<tag>NIFs</tag>
- <item><p>Modules compiled with HiPE cannot call <seealso marker="erts:erlang#load_nif-2">
- <c>erlang:load_nif/2</c></seealso> to load NIFs.</p>
+ <item><p>Modules compiled with HiPE cannot call <seemfa marker="erts:erlang#load_nif/2">
+ <c>erlang:load_nif/2</c></seemfa> to load NIFs.</p>
</item>
<tag>-on_load</tag>
<item><p>Modules compiled with HiPE cannot use
- <seealso marker="doc/reference_manual:code_loading#on_load"><c>-on_load()</c></seealso>
+ <seeguide marker="system/reference_manual:code_loading#on_load"><c>-on_load()</c></seeguide>
directives.</p>
</item>
</taglist>
@@ -190,8 +190,8 @@
<section>
<title>SEE ALSO</title>
<p>
- <seealso marker="stdlib:c">c(3)</seealso>,
- <seealso marker="compiler:compile">compile(3)</seealso>
+ <seeerl marker="stdlib:c">c(3)</seeerl>,
+ <seeerl marker="compiler:compile">compile(3)</seeerl>
</p>
</section>
diff --git a/lib/hipe/doc/src/Makefile b/lib/hipe/doc/src/Makefile
index 104c15f2bb..ddebe3c415 100644
--- a/lib/hipe/doc/src/Makefile
+++ b/lib/hipe/doc/src/Makefile
@@ -28,17 +28,12 @@ VSN=$(HIPE_VSN)
APPLICATION=hipe
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
XML_REF3_FILES =
-XML_PART_FILES = hipe_app.xml
+XML_PART_FILES = HiPE_app.xml
XML_CHAPTER_FILES = notes.xml
BOOK_FILES = book.xml
@@ -47,73 +42,4 @@ XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES)
-GIF_FILES =
-
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-EXTRA_FILES = \
- $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-distclean: clean
-realclean: clean
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
-
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml
index 8e1f5b35cd..600aabaebb 100644
--- a/lib/hipe/doc/src/notes.xml
+++ b/lib/hipe/doc/src/notes.xml
@@ -31,6 +31,54 @@
</header>
<p>This document describes the changes made to HiPE.</p>
+<section><title>Hipe 4.0.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed a warning issued when building the <c>hipe</c>
+ application.</p>
+ <p>
+ Own Id: OTP-16737</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Hipe 4.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a rare miss-compilation of tuple matching.</p>
+ <p>
+ Own Id: OTP-16470</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The deprecated <c>erlang:get_stacktrace/0</c> BIF now
+ returns an empty list instead of a stacktrace. To
+ retrieve the stacktrace, use the extended try/catch
+ syntax that was introduced in OTP 21.
+ <c>erlang:get_stacktrace/0</c> is scheduled for removal
+ in OTP 24.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16484</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Hipe 3.19.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -381,9 +429,9 @@
Own Id: OTP-13810 Aux Id: PR-1124 </p>
</item>
<item>
- <p>Replaced usage of deprecated symbolic <seealso
- marker="erts:erlang#type-time_unit"><c>time
- unit</c></seealso> representations.</p>
+ <p>Replaced usage of deprecated symbolic <seetype
+ marker="erts:erlang#time_unit"><c>time
+ unit</c></seetype> representations.</p>
<p>
Own Id: OTP-13831 Aux Id: OTP-13735 </p>
</item>
@@ -974,8 +1022,8 @@
</taglist>
<p>
For information on how to use Maps please see Map Expressions in the
- <seealso marker="doc/reference_manual:expressions#map_expressions">
- Reference Manual</seealso>.</p>
+ <seeguide marker="system/reference_manual:expressions#map_expressions">
+ Reference Manual</seeguide>.</p>
<p>
The current implementation is without the following
features:</p>
diff --git a/lib/hipe/doc/src/ref_man.xml b/lib/hipe/doc/src/ref_man.xml
index 7938729227..05af6b0c4f 100644
--- a/lib/hipe/doc/src/ref_man.xml
+++ b/lib/hipe/doc/src/ref_man.xml
@@ -31,6 +31,6 @@
</header>
<description>
</description>
- <xi:include href="hipe_app.xml"/>
+ <xi:include href="HiPE_app.xml"/>
</application>
diff --git a/lib/hipe/icode/hipe_beam_to_icode.erl b/lib/hipe/icode/hipe_beam_to_icode.erl
index efdaeecca3..97d50eb472 100644
--- a/lib/hipe/icode/hipe_beam_to_icode.erl
+++ b/lib/hipe/icode/hipe_beam_to_icode.erl
@@ -689,8 +689,8 @@ trans_fun([{call_fun,N}|Instructions], Env) ->
Dst = [mk_var({r,0})],
[hipe_icode:mk_comment('call_fun'),
hipe_icode:mk_primop(Dst,call_fun,Args) | trans_fun(Instructions,Env)];
-%%--- patched_make_fun --- make_fun/make_fun2 after fixes
-trans_fun([{patched_make_fun,MFA,Magic,FreeVarNum,Index}|Instructions], Env) ->
+%%--- make_fun2 ---
+trans_fun([{make_fun2,MFA,Index,Magic,FreeVarNum}|Instructions], Env) ->
Args = extract_fun_args(FreeVarNum),
Dst = [mk_var({r,0})],
Fun = hipe_icode:mk_primop(Dst,
@@ -1204,6 +1204,17 @@ trans_fun([{bs_get_position=Name,_,_,_}|_Instructions], _Env) ->
trans_fun([{bs_set_position=Name,_,_}|_Instructions], _Env) ->
nyi(Name);
%%--------------------------------------------------------------------
+%% New instructions added in OTP 23.
+%%--------------------------------------------------------------------
+%%--- swap ---
+trans_fun([{swap,Reg1,Reg2}|Instructions], Env) ->
+ Var1 = mk_var(Reg1),
+ Var2 = mk_var(Reg2),
+ Temp = mk_var(new),
+ [hipe_icode:mk_move(Temp, Var1),
+ hipe_icode:mk_move(Var1, Var2),
+ hipe_icode:mk_move(Var2, Temp) | trans_fun(Instructions, Env)];
+%%--------------------------------------------------------------------
%%--- ERROR HANDLING ---
%%--------------------------------------------------------------------
trans_fun([X|_], _) ->
@@ -1946,7 +1957,7 @@ mod_find_closure_info([FunCode|Fs], CI) ->
mod_find_closure_info([], CI) ->
CI.
-find_closure_info([{patched_make_fun,MFA={_M,_F,A},_Magic,FreeVarNum,_Index}|BeamCode],
+find_closure_info([{make_fun2,{_M,_F,A}=MFA,_Index,_Magic,FreeVarNum}|BeamCode],
ClosureInfo) ->
NewClosure = %% A-FreeVarNum+1 (The real arity + 1 for the closure)
#closure_info{mfa=MFA, arity=A-FreeVarNum+1, fv_arity=FreeVarNum},
@@ -2024,41 +2035,8 @@ split_params(N, [ArgN|OrgArgs], Args) ->
%%-----------------------------------------------------------------------
preprocess_code(ModuleCode) ->
- PatchedCode = patch_R7_funs(ModuleCode),
- ClosureInfo = find_closure_info(PatchedCode),
- {PatchedCode, ClosureInfo}.
-
-%%-----------------------------------------------------------------------
-%% Patches the "make_fun" BEAM instructions of R7 so that they also
-%% contain the index that the BEAM loader generates for funs.
-%%
-%% The index starts from 0 and is incremented by 1 for each make_fun
-%% instruction encountered.
-%%
-%% Retained only for compatibility with BEAM code prior to R8.
-%%
-%% Temporarily, it also rewrites R8-PRE-RELEASE "make_fun2"
-%% instructions, since their embedded indices don't work.
-%%-----------------------------------------------------------------------
-
-patch_R7_funs(ModuleCode) ->
- patch_make_funs(ModuleCode, 0).
-
-patch_make_funs([FunCode0|Fs], FunIndex0) ->
- {PatchedFunCode,FunIndex} = patch_make_funs(FunCode0, FunIndex0, []),
- [PatchedFunCode|patch_make_funs(Fs, FunIndex)];
-patch_make_funs([], _) -> [].
-
-patch_make_funs([{make_fun,MFA,Magic,FreeVarNum}|Is], FunIndex, Acc) ->
- Patched = {patched_make_fun,MFA,Magic,FreeVarNum,FunIndex},
- patch_make_funs(Is, FunIndex+1, [Patched|Acc]);
-patch_make_funs([{make_fun2,MFA,_BogusIndex,Magic,FreeVarNum}|Is], FunIndex, Acc) ->
- Patched = {patched_make_fun,MFA,Magic,FreeVarNum,FunIndex},
- patch_make_funs(Is, FunIndex+1, [Patched|Acc]);
-patch_make_funs([I|Is], FunIndex, Acc) ->
- patch_make_funs(Is, FunIndex, [I|Acc]);
-patch_make_funs([], FunIndex, Acc) ->
- {lists:reverse(Acc),FunIndex}.
+ ClosureInfo = find_closure_info(ModuleCode),
+ {ModuleCode, ClosureInfo}.
%%-----------------------------------------------------------------------
@@ -2358,9 +2336,8 @@ catch_handler('catch', [TagVar,ValueVar,TraceVar], OldCatchLbl) ->
ValueVar]),
hipe_icode:mk_goto(Cont),
ErrorLbl,
- %% We use the trace variable to hold the symbolic trace. Its previous
- %% value is just that in p->ftrace, so get_stacktrace() works fine.
- hipe_icode:mk_call([TraceVar],erlang,get_stacktrace,[],remote),
+ %% We use the trace variable to hold the symbolic trace.
+ hipe_icode:mk_primop([TraceVar],build_stacktrace,[TraceVar]),
hipe_icode:mk_primop([ValueVar],mktuple, [ValueVar, TraceVar]),
hipe_icode:mk_goto(hipe_icode:label_name(ExitLbl)),
OldCatchLbl, % normal execution paths must go through end_try
diff --git a/lib/hipe/icode/hipe_icode_primops.erl b/lib/hipe/icode/hipe_icode_primops.erl
index 63b34f23a4..2941cf15fc 100644
--- a/lib/hipe/icode/hipe_icode_primops.erl
+++ b/lib/hipe/icode/hipe_icode_primops.erl
@@ -436,14 +436,8 @@ type(Primop, Args) ->
#element{} ->
erl_bif_types:type(erlang, element, 2, Args);
#unsafe_element{index = N} ->
- [Type] = Args,
- case erl_types:t_is_tuple(Type) of
- false ->
- erl_types:t_none();
- true ->
- Index = erl_types:t_from_term(N),
- erl_bif_types:type(erlang, element, 2, [Index|Args])
- end;
+ Index = erl_types:t_from_term(N),
+ erl_bif_types:type(erlang, element, 2, [Index | Args]);
#unsafe_update_element{index = N} ->
%% Same, same
erl_bif_types:type(erlang, setelement, 3, [erl_types:t_integer(N)|Args]);
diff --git a/lib/hipe/main/hipe.erl b/lib/hipe/main/hipe.erl
index 094b7bc508..86cb51008b 100644
--- a/lib/hipe/main/hipe.erl
+++ b/lib/hipe/main/hipe.erl
@@ -501,8 +501,8 @@ compile(Name, [], File, Opts) ->
compile(Name, File, Opts);
compile(Name, Core, File, Opts) when is_atom(Name) ->
DisasmFun = fun (_) -> {false, []} end,
- IcodeFun = fun (_, Opts) ->
- get_core_icode(Name, Core, File, Opts)
+ IcodeFun = fun (_, FOpts) ->
+ get_core_icode(Name, Core, File, FOpts)
end,
run_compiler(Name, DisasmFun, IcodeFun, Opts).
diff --git a/lib/hipe/test/basic_SUITE_data/basic_bugs_hipe.erl b/lib/hipe/test/basic_SUITE_data/basic_bugs_hipe.erl
index 430e097b91..c6bec39632 100644
--- a/lib/hipe/test/basic_SUITE_data/basic_bugs_hipe.erl
+++ b/lib/hipe/test/basic_SUITE_data/basic_bugs_hipe.erl
@@ -8,6 +8,9 @@
-export([test/0]).
+% Ensure type optimization is turned off for id/1
+-export([id/1]).
+
test() ->
ok = test_ets_bifs(),
ok = test_szar_bug(),
@@ -19,6 +22,7 @@ test() ->
ok = test_switch_neg_int(),
ok = test_icode_range_anal(),
ok = test_icode_range_call(),
+ ok = test_icode_type_miscompile(),
ok.
%%-----------------------------------------------------------------------
@@ -503,3 +507,25 @@ range_client(Server, N) ->
receive proceed -> ok end,
range_client(Server, N - 1), % non-tailrecursive call with ignored result
ok.
+
+test_icode_type_miscompile() ->
+ List0 = id([{1,1},{1,1}]),
+
+ %% The expressions below produce a list that the SSA type pass knows is
+ %% a list of two-tuples, but hipe_icode_type does not.
+ %%
+ %% Changing the `F(X)` call to just `X` helps the icode type pass figure
+ %% things out, making the bug disappear.
+ F = fun({_, _}=X) -> X end,
+ List = [F(X) || {_,_}=X <- List0],
+
+ type_miscompile(List, List, []).
+
+type_miscompile([{Same, Same} | As], [{Same, Same} | Bs], Acc) ->
+ type_miscompile(As, Bs, [gaffel | Acc]);
+type_miscompile([], [], Acc) ->
+ %% Acc is non-empty when everything works as expected.
+ true = Acc =/= [],
+ ok.
+
+id(I) -> I.
diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk
index 5d34c61169..ea01a6aeb4 100644
--- a/lib/hipe/vsn.mk
+++ b/lib/hipe/vsn.mk
@@ -1 +1 @@
-HIPE_VSN = 3.19.3
+HIPE_VSN = 4.0.1
diff --git a/lib/inets/Makefile b/lib/inets/Makefile
index 872df9d055..a7723dc0d8 100644
--- a/lib/inets/Makefile
+++ b/lib/inets/Makefile
@@ -32,47 +32,11 @@ VSN = $(INETS_VSN)
SPECIAL_TARGETS =
-DIA_PLT = ./priv/plt/$(APPLICATION).plt
-DIA_ANALYSIS = $(basename $(DIA_PLT)).dialyzer_analysis
-
-
# ----------------------------------------------------
# Default Subdir Targets
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
-.PHONY: info gclean dialyzer dialyzer_plt dclean
-
-info:
- @echo "OS: $(OS)"
- @echo "DOCB: $(DOCB)"
- @echo ""
- @echo "INETS_VSN: $(INETS_VSN)"
- @echo "APP_VSN: $(APP_VSN)"
- @echo ""
- @echo "DIA_PLT: $(DIA_PLT)"
- @echo "DIA_ANALYSIS: $(DIA_ANALYSIS)"
- @echo ""
-
-gclean:
- git clean -fXd
-
-dclean:
- rm -f $(DIA_PLT)
- rm -f $(DIA_ANALYSIS)
-
-dialyzer_plt: $(DIA_PLT)
-
-$(DIA_PLT):
- @echo "Building $(APPLICATION) plt file"
- @dialyzer --build_plt \
- --output_plt $@ \
- -r ../$(APPLICATION)/ebin \
- --output $(DIA_ANALYSIS) \
- --verbose
+DIA_PLT_APPS=runtime_tools ftp mnesia ssl tftp
-dialyzer: $(DIA_PLT)
- @echo "Running dialyzer on $(APPLICATION)"
- @dialyzer --plt $< \
- ../$(APPLICATION)/ebin \
- --verbose
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/inets/doc/src/Makefile b/lib/inets/doc/src/Makefile
index cbc0e384d8..a4405c5728 100644
--- a/lib/inets/doc/src/Makefile
+++ b/lib/inets/doc/src/Makefile
@@ -26,12 +26,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
include ../../vsn.mk
VSN=$(INETS_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
+APPLICATION=inets
# ----------------------------------------------------
# Target Specs
@@ -71,97 +66,8 @@ XML_FILES = \
$(XML_REF3_FILES) \
$(XML_APPLICATION_FILES)
-# GIF_FILES = inets.gif
-
-
-# ----------------------------------------------------
-
-HTML_FILES = \
- $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-EXTRA_FILES = \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-ldocs: local_docs
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs: clean_html clean_man clean_pdf
- rm -rf $(XMLDIR)
- rm -f errs core *~
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
+NO_CHUNKS = httpd_custom_api.xml
-debug opt:
-
-clean_pdf:
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
-
-clean_html:
- rm -rf $(TOP_HTML_FILES) $(HTMLDIR)/*
-
-clean_man:
- rm -f $(MAN3_FILES)
-
-
-# ----------------------------------------------------
-# Release Target
# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
-info:
- @echo "GIF_FILES:\n$(GIF_FILES)"
- @echo ""
- @echo "EXTRA_FILES:\n$(EXTRA_FILES)"
- @echo ""
- @echo "HTML_FILES:\n$(HTML_FILES)"
- @echo ""
- @echo "TOP_HTML_FILES:\n$(TOP_HTML_FILES)"
- @echo ""
- @echo "XML_REF3_FILES:\n$(XML_REF3_FILES)"
- @echo ""
- @echo "XML_REF6_FILES:\n$(XML_REF6_FILES)"
- @echo ""
- @echo "XML_CHAPTER_FILES:\n$(XML_CHAPTER_FILES)"
- @echo ""
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/inets/doc/src/http_client.xml b/lib/inets/doc/src/http_client.xml
index c31a47f4f4..ad11ea2a6c 100644
--- a/lib/inets/doc/src/http_client.xml
+++ b/lib/inets/doc/src/http_client.xml
@@ -54,7 +54,7 @@
<pre>
[{inets, [{services, [{httpc, PropertyList}]}]}]</pre>
<p>For valid properties, see
- <seealso marker="httpc">httpc(3)</seealso>. </p>
+ <seeerl marker="httpc">httpc(3)</seeerl>. </p>
</section>
<section>
diff --git a/lib/inets/doc/src/http_server.xml b/lib/inets/doc/src/http_server.xml
index d6fe9dc6a7..4b0dde86af 100644
--- a/lib/inets/doc/src/http_server.xml
+++ b/lib/inets/doc/src/http_server.xml
@@ -49,15 +49,13 @@
</list>
<p>The configuration of the server is provided as an Erlang
- property list. For backwards compatibility, a configuration
- file using apache-style configuration directives is
- supported.</p>
+ property list.</p>
<p>As of <c>Inets</c> 5.0 the HTTP server is an easy to
start/stop and customize web server providing the most basic
web server functionality. Inets is designed for embedded systems
- and if you want a full-fledged web server there are exists other
- erlang open source alternatives.</p>
+ and if you want a full-fledged web server there are other erlang
+ open source alternatives.</p>
<p>Almost all server functionality has been implemented using an
especially crafted server API, which is described in the Erlang Web
@@ -76,17 +74,14 @@
<p>The server is configured using an Erlang property list.
For the available properties, see
- <seealso marker="httpd">httpd(3)</seealso>.
- For backwards compatibility, apache-like configuration files
- are also supported.
+ <seeerl marker="httpd">httpd(3)</seeerl>.
</p>
<p>The available configuration properties are as follows:</p>
<code type="none">
httpd_service() -> {httpd, httpd()}
httpd() -> [httpd_config()]
- httpd_config() -> {file, file()} |
- {proplist_file, file()}
+ httpd_config() -> {proplist_file, file()}
{debug, debug()} |
{accept_timeout, integer()}
debug() -> disable | [debug_options()]
@@ -95,13 +90,11 @@
{disable, modules()}
modules() -> [atom()]</code>
<p>Here:</p>
- <taglist>
- <tag><c>{file, file()}</c></tag>
- <item><p>If you use an old apace-like configuration file.</p></item>
+ <taglist>
<tag><c>{proplist_file, file()}</c></tag>
<item><p>File containing an Erlang property
- list, followed by a full stop, describing the HTTP server
- configuration.</p></item>
+ list, followed by a full stop, describing the HTTP server
+ configuration.</p></item>
<tag><c>{debug, debug()}</c></tag>
<item><p>Can enable trace on all functions or only exported functions
on chosen modules.</p></item>
@@ -171,162 +164,9 @@
</section>
<section>
- <title>Htaccess - User Configurable Authentication</title>
- <marker id="htaccess"></marker>
- <p>Web server users without server administrative privileges
- that need to manage authentication of web pages that are local
- to their user can use the per-directory runtime configurable
- user-authentication scheme <c>htaccess</c>.
- It works as follows:</p>
- <list type="bulleted">
- <item>Each directory in the path to the requested asset is
- searched for an access file (default is <c>.htaccess</c>), which
- restricts the web servers rights to respond to a request.
- If an access file is found, the rules in that file is applied to the
- request.</item>
- <item>The rules in an access file apply to files in the same
- directory and in subdirectories. If there exists more than one
- access file in the path to an asset, the rules in the
- access file nearest the requested asset is applied.</item>
- <item>To change the rules that restrict the use of
- an asset, the user only needs write access
- to the directory where the asset is.</item>
- <item>All access files in the path to a requested asset are read
- once per request. This means that the load on the server
- increases when <c>htaccess</c> is used.</item>
- <item>If a directory is limited both by authentication directives
- in the HTTP server configuration file and by the <c>htaccess</c>
- files, the user must be allowed to get access to the file by both
- methods for the request to succeed.</item>
- </list>
-
- <section>
- <title>Access Files Directives</title>
- <p>In every directory under <c>DocumentRoot</c> or under an
- <c>Alias</c> a user can place an access file. An access file
- is a plain text file that specifies the restrictions to
- consider before the web server answers to a
- request. If there are more than one access file in the path
- to the requested asset, the directives in the access file in
- the directory nearest the asset is used.</p>
- <taglist>
- <tag><em>"allow"</em></tag>
- <item>
- <p><em>Syntax:</em> <c>Allow</c> from subnet <c>subnet | from all</c></p>
- <p><em>Default:</em> <c>from all</c></p>
- <p>Same as directive <c>allow</c> for the server configuration file.</p>
- </item>
- <tag><em>"AllowOverRide"</em></tag>
- <item>
- <p><em>Syntax:</em> <c>AllowOverRide</c> <c>all | none | Directives</c></p>
- <p><em>Default:</em> <c>none</c></p>
- <p><c>AllowOverRide</c> specifies the parameters that
- access files in subdirectories are not allowed to alter the value
- for. If the parameter is set to <c>none</c>, no further
- access files is parsed.
- </p>
- <p>If only one access file exists, setting this parameter to
- <c>none</c> can ease the burden on the server as the server
- then stops looking for access files.</p>
- </item>
- <tag><em>"AuthGroupfile"</em></tag>
- <item>
- <p><em>Syntax:</em> <c>AuthGroupFile</c> Filename</p>
- <p><em>Default:</em> <c>none</c></p>
- <p><c>AuthGroupFile</c> indicates which file that contains the list
- of groups. The filename must contain the absolute path to the
- file. The format of the file is one group per row and
- every row contains the name of the group and the members
- of the group, separated by a space, for example:</p>
- <pre>
-GroupName: Member1 Member2 .... MemberN</pre>
- </item>
- <tag><em>"AuthName"</em></tag>
- <item>
- <p><em>Syntax:</em> <c>AuthName</c> auth-domain</p>
- <p><em>Default:</em> <c>none</c></p>
- <p>Same as directive <c>AuthName</c> for the server
- configuration file.</p>
- </item>
- <tag><em>"AuthType"</em></tag>
- <item>
- <p><em>Syntax:</em> <c>AuthType</c> <c>Basic</c></p>
- <p><em>Default:</em> <c>Basic</c></p>
- <p><c>AuthType</c> specifies which authentication scheme to
- be used. Only Basic Authenticating using UUEncoding of
- the password and user ID is implemented.</p>
- </item>
- <tag><em>"AuthUserFile"</em></tag>
- <item>
- <p><em>Syntax:</em> <c>AuthUserFile</c> Filename</p>
- <p><em>Default:</em><c>none</c></p>
- <p><c>AuthUserFile</c> indicates which file that contains the list
- of users. The filename must contain the absolute path to the
- file. The username and password are not encrypted so do not
- place the file with users in a directory that is accessible
- through the web server. The format of the file is one user per row.
- Every row contains <c>UserName</c> and <c>Password</c> separated
- by a colon, for example:</p>
- <pre>
-UserName:Password
-UserName:Password</pre>
- </item>
- <tag><em>"deny"</em></tag>
- <item>
- <p><em>Syntax:</em> <c>deny</c> from subnet <c>subnet | from all</c></p>
- <p><em>Context:</em> Limit</p>
- <p>Same as directive <c>deny</c> for the server configuration file.</p>
- </item>
- <tag><em>"Limit"</em></tag>
- <item>
- <p><em>Syntax:</em> <c><![CDATA[<Limit]]></c> RequestMethods<c>></c></p>
- <p><em>Default:</em> <c>none</c></p>
- <p><c><![CDATA[<Limit>]]></c> and <c>&lt;/Limit&gt;</c> are used to enclose
- a group of directives applying only to requests using
- the specified methods. If no request method is specified,
- all request methods are verified against the restrictions.</p>
- <p>Example:</p>
- <pre>
-&lt;Limit POST GET HEAD&gt;
- order allow deny
- require group group1
- allow from 123.145.244.5
-&lt;/Limit&gt;</pre>
- </item>
- <tag><em>"order"</em></tag>
- <item>
- <p><em>Syntax:</em> <c>order</c> <c>allow deny | deny allow</c></p>
- <p><em>Default:</em> <c>allow deny</c></p>
- <p><c>order</c> defines if the deny or allow control is to
- be performed first.</p>
- <p>If the order is set to <c>allow deny</c>, the users
- network address is first controlled to be in the allow subset.
- If the user network address is not in the allowed subset, the user
- is denied to get the asset. If the network address is in the
- allowed subset, a second control is performed. That is,
- the user network address is not in the subset of network
- addresses to be denied as specified by parameter <c>deny</c>.</p>
- <p>If the order is set to <c>deny allow</c>, only users from networks
- specified to be in the allowed subset succeeds to request
- assets in the limited area.</p>
- </item>
- <tag><em>"require"</em></tag>
- <item>
- <p><em>Syntax:</em> <c>require</c>
- <c>group group1 group2... | user user1 user2...</c></p>
- <p><em>Default:</em> <c>none</c></p>
- <p><em>Context:</em> Limit</p>
- <p>For more information, see directive <c>require</c> in
- <seealso marker="mod_auth">mod_auth(3)</seealso>.</p>
- </item>
- </taglist>
- </section>
- </section>
-
- <section>
- <title>Dynamic Web Pages</title>
- <marker id="dynamic_we_pages"></marker>
- <p><c>Inets</c> HTTP server provides two ways of creating dynamic web
+ <title>Dynamic Web Pages</title>
+ <marker id="dynamic_we_pages"></marker>
+ <p><c>Inets</c> HTTP server provides two ways of creating dynamic web
pages, each with its own advantages and disadvantages:</p>
<taglist>
<tag><em>CGI scripts</em></tag>
@@ -390,7 +230,7 @@ http://your.server.org/***/Module[:/]Function(?QueryString|/PathInfo)</code>
function with arity two is only kept for
backwards compatibility reasons.
For implementation details of the ESI callback function,
- see <seealso marker="mod_esi">mod_esi(3)</seealso>.</p>
+ see <seeerl marker="mod_esi">mod_esi(3)</seeerl>.</p>
</section>
</section>
@@ -451,8 +291,8 @@ http://your.server.org/***/Module[:/]Function(?QueryString|/PathInfo)</code>
related steps, are considered server core functionality and are
not implemented using the Erlang web server API. A description of
functionality implemented by the Erlang webserver API is described
- in <seealso marker="#Inets_Web_Server_Modules">Section
- Inets Web Server Modules</seealso>.</p>
+ in <seeguide marker="#Inets_Web_Server_Modules">Section
+ Inets Web Server Modules</seeguide>.</p>
<p>A module can use data generated by previous modules in the
Erlang webserver API module sequence or generate data to be used
@@ -479,7 +319,7 @@ http://your.server.org/***/Module[:/]Function(?QueryString|/PathInfo)</code>
</list>
<p>The latter functions are needed only when new config
directives are to be introduced. For details, see
- <seealso marker="httpd">httpd(3)</seealso>.</p>
+ <seeerl marker="httpd">httpd(3)</seeerl>.</p>
</section>
</section>
@@ -504,7 +344,7 @@ http://your.server.org/***/Module[:/]Function(?QueryString|/PathInfo)</code>
<p>Uses the following Erlang Web Server API interaction data:
</p>
<list type="bulleted">
- <item><c>real_name</c> - from <seealso marker="mod_alias">mod_alias</seealso>.</item>
+ <item><c>real_name</c> - from <seeerl marker="mod_alias">mod_alias</seeerl>.</item>
</list>
<p>Exports the following Erlang Web Server API interaction data, if possible:
</p>
@@ -516,7 +356,7 @@ http://your.server.org/***/Module[:/]Function(?QueryString|/PathInfo)</code>
<section>
<title>mod_alias - URL Aliasing</title>
- <p>The <seealso marker="mod_alias">mod_alias</seealso>
+ <p>The <seeerl marker="mod_alias">mod_alias</seeerl>
module makes it possible to map different parts of the
host file system into the document tree, that is, creates aliases and
redirections.</p>
@@ -525,19 +365,19 @@ http://your.server.org/***/Module[:/]Function(?QueryString|/PathInfo)</code>
<taglist>
<tag><c>{real_name, PathData}</c></tag>
<item><c>PathData</c> is the argument used for API function
- <seealso marker="mod_alias#path/3">mod_alias:path/3</seealso>.</item>
+ <seemfa marker="mod_alias#path/3">mod_alias:path/3</seemfa>.</item>
</taglist>
</section>
<section>
<title>mod_auth - User Authentication</title>
- <p>The <seealso marker="mod_auth">mod_auth(3)</seealso>
+ <p>The <seeerl marker="mod_auth">mod_auth(3)</seeerl>
module provides for basic user authentication using
textual files, Dets databases as well as Mnesia databases.</p>
<p>Uses the following Erlang Web Server API interaction data:
</p>
<list type="bulleted">
- <item><c>real_name</c> - from <seealso marker="mod_alias">mod_alias</seealso></item>
+ <item><c>real_name</c> - from <seeerl marker="mod_alias">mod_alias</seeerl></item>
</list>
<p>Exports the following Erlang Web Server API interaction data:
</p>
@@ -592,11 +432,11 @@ start() ->
Then it starts Mnesia and creates the tables. The first argument
is the name of the tables, the second argument is a list of options of
how to create the table, see
- <seealso marker="mnesia:mnesia"><c>mnesia(3)</c></seealso>, documentation for
+ <seeerl marker="mnesia:mnesia"><c>mnesia(3)</c></seeerl>, documentation for
more information. As the implementation of the <c>mod_auth_mnesia</c>
saves one row for each user, the type must be <c>bag</c>.
When the schema and the tables are created, function
- <seealso marker="mnesia:mnesia#start-0">mnesia:start/0</seealso>
+ <seemfa marker="mnesia:mnesia#start/0">mnesia:start/0</seemfa>
is used to start Mnesia and
waits for the tables to be loaded. Mnesia uses the
directory specified as <c>mnesia_dir</c> at startup if specified,
@@ -625,7 +465,7 @@ start() ->
<p>Uses the following Erlang Web Server API interaction data:
</p>
<list type="bulleted">
- <item><c>real_name</c> - from <seealso marker="mod_alias">mod_alias</seealso></item>
+ <item><c>real_name</c> - from <seeerl marker="mod_alias">mod_alias</seeerl></item>
</list>
<p>Exports the following Erlang Web Server API interaction data:
</p>
@@ -639,7 +479,7 @@ start() ->
<section>
<title>mod_disk_log - Logging Using Disk_Log.</title>
<p>Standard logging using the "Common Logfile Format" and
- <seealso marker="kernel:disk_log">disk_log(3)</seealso>.</p>
+ <seeerl marker="kernel:disk_log">disk_log(3)</seeerl>.</p>
<p>Uses the following Erlang Web Server API interaction data:
</p>
<list type="bulleted">
@@ -649,7 +489,7 @@ start() ->
<section>
<title>mod_esi - Erlang Server Interface</title>
- <p>The <seealso marker="mod_esi">mod_esi(3)</seealso>
+ <p>The <seeerl marker="mod_esi">mod_esi(3)</seeerl>
module implements the Erlang Server Interface (ESI) providing a
tight and efficient interface to the execution of Erlang functions.</p>
<p>Uses the following Erlang web server API interaction data:
@@ -673,7 +513,7 @@ start() ->
<p>Uses the following Erlang web server API interaction data:
</p>
<list type="bulleted">
- <item><c>real_name</c> - from <seealso marker="mod_alias">mod_alias</seealso></item>
+ <item><c>real_name</c> - from <seeerl marker="mod_alias">mod_alias</seeerl></item>
</list>
</section>
@@ -685,26 +525,10 @@ start() ->
<p>Uses the following Erlang Web Server API interaction data:
</p>
<list type="bulleted">
- <item><c>real_name</c> - from <seealso marker="mod_alias">mod_alias</seealso></item>
+ <item><c>real_name</c> - from <seeerl marker="mod_alias">mod_alias</seeerl></item>
</list>
</section>
-
- <section>
- <title>mod_htaccess - User Configurable Access</title>
- <p>This module provides per-directory user configurable access
- control.</p>
- <p>Uses the following Erlang Web Server API interaction data:
- </p>
- <list type="bulleted">
- <item><c>real_name</c> - from <seealso marker="mod_alias">mod_alias</seealso></item>
- </list>
- <p>Exports the following Erlang Web Server API interaction data:
- </p>
- <taglist>
- <tag><c>{remote_user_name, User}</c></tag>
- <item>The username used for authentication.</item>
- </taglist>
- </section>
+
<section>
<title>mod_log - Logging Using Text Files.</title>
@@ -727,7 +551,7 @@ start() ->
<p>Uses the following Erlang Web Server API interaction data:
</p>
<list type="bulleted">
- <item><c>real_name</c> - from <seealso marker="mod_alias">mod_alias</seealso></item>
+ <item><c>real_name</c> - from <seeerl marker="mod_alias">mod_alias</seeerl></item>
</list>
</section>
@@ -741,7 +565,7 @@ start() ->
<p>If a client sends more than one of the header fields that
restricts the servers right to respond, the standard does not
specify how this is to be handled.
- <seealso marker="httpd">httpd(3)</seealso> controls each
+ <seeerl marker="httpd">httpd(3)</seeerl> controls each
field in the following order and if one of the fields does not
match the current state, the request is rejected with a proper
response:</p>
@@ -753,7 +577,7 @@ start() ->
<p>Uses the following Erlang Web Server API interaction data:
</p>
<list type="bulleted">
- <item><c>real_name</c> - from <seealso marker="mod_alias">mod_alias</seealso></item>
+ <item><c>real_name</c> - from <seeerl marker="mod_alias">mod_alias</seeerl></item>
</list>
<p>Exports the following Erlang Web Server API interaction data:
</p>
@@ -767,9 +591,9 @@ start() ->
<section>
<title>mod_security - Security Filter</title>
- <p>The <seealso marker="mod_security">mod_security</seealso>
+ <p>The <seeerl marker="mod_security">mod_security</seeerl>
module serves as a filter for authenticated requests
- handled in <seealso marker="mod_auth">mod_auth(3)</seealso>.
+ handled in <seeerl marker="mod_auth">mod_auth(3)</seeerl>.
It provides a possibility to restrict users from
access for a specified amount of time if they fail to
authenticate several times. It logs failed authentication as
diff --git a/lib/inets/doc/src/http_uri.xml b/lib/inets/doc/src/http_uri.xml
index eda477da0c..f7e4a16919 100644
--- a/lib/inets/doc/src/http_uri.xml
+++ b/lib/inets/doc/src/http_uri.xml
@@ -34,9 +34,10 @@
<modulesummary>Old URI utility module, use uri_string instead</modulesummary>
<description>
- <p>This module provides utility functions for working with URIs,
- according to
- <url href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>.</p>
+ <p>This module is deprecated since OTP 23.
+ Use the module <seeerl marker="stdlib:uri_string">uri_string</seeerl> to properly handle URIs,
+ this is the recommended module since OTP 21.
+ </p>
</description>
<section>
@@ -133,7 +134,7 @@
<desc>
<p>Parses a URI. If no scheme defaults
are provided, the value of the
- <seealso marker="#scheme_defaults">scheme_defaults</seealso>
+ <seeerl marker="#scheme_defaults">scheme_defaults</seeerl>
function is used.</p>
<p>When parsing a URI with an unknown scheme (that is,
@@ -187,9 +188,9 @@ fun(SchemeStr :: string() | binary()) ->
<section>
<title>SEE ALSO</title>
<p><url href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</url>,
- <seealso marker="inets">inets(3)</seealso>,
- <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>,
- <seealso marker="ssl:ssl">ssl(3)</seealso>
+ <seeerl marker="inets">inets(3)</seeerl>,
+ <seeerl marker="kernel:gen_tcp">gen_tcp(3)</seeerl>,
+ <seeerl marker="ssl:ssl">ssl(3)</seeerl>
</p>
</section>
-->
diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml
index 72ac79a0b0..42a6a5b43d 100644
--- a/lib/inets/doc/src/httpc.xml
+++ b/lib/inets/doc/src/httpc.xml
@@ -57,8 +57,8 @@
pipelining are used. That is, the client always waits for
the previous response before sending the next request.</p>
</note>
- <p>Some examples are provided in the <seealso
- marker="http_client">Inets User's Guide</seealso>.</p>
+ <p>Some examples are provided in the <seeguide
+ marker="http_client">Inets User's Guide</seeguide>.</p>
</description>
<section>
@@ -72,10 +72,10 @@
<p><c>profile() = atom()</c></p>
<p><c>path() = string()</c> representing a file path or directory path</p>
<p><c>ip_address()</c> = See the
- <seealso marker="kernel:inet">inet(3)</seealso> manual page in Kernel.</p>
+ <seeerl marker="kernel:inet">inet(3)</seeerl> manual page in Kernel.</p>
<p><c>socket_opt()</c> = See the options used by
- <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso> <c>gen_tcp(3)</c> and
- <seealso marker="ssl:ssl">ssl(3)</seealso> connect(s)</p>
+ <seeerl marker="kernel:gen_tcp">gen_tcp(3)</seeerl> <c>gen_tcp(3)</c> and
+ <seeerl marker="ssl:ssl">ssl(3)</seeerl> connect(s)</p>
</section>
@@ -91,8 +91,16 @@
</item>
</taglist>
<p><c>url() = string()</c> syntax according to the URI definition in
- <url href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</url>,
+ <url href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>,
for example <c>"http://www.erlang.org"</c></p>
+ <warning><p>Please note that httpc normalizes input URIs before internal processing
+ and special care shall be taken when the URI has percent ("%") characters. A percent
+ serves as the indicator for percent-encoded octets and it must be percent-encoded
+ as "%25" for that octet to be used as data within the URI.</p>
+ <p>For example, in order to send an HTTP GET request with the URI
+ <c>http://localhost/foo%25bar</c>, the percent character must be percent-encoded when
+ creating the request: <c>httpc:request("http://localhost/foo%2525bar").</c>
+ </p></warning>
<p><c>status_line() = {http_version(), status_code(), reason_phrase()}</c></p>
<p><c>http_version() = string()</c>, for example, <c>"HTTP/1.1"</c></p>
<p><c>status_code() = integer()</c></p>
@@ -120,7 +128,7 @@
<section>
<title>SSL DATA TYPES</title>
- <p>See <seealso marker="ssl:ssl">ssl(3)</seealso> for information
+ <p>See <seeerl marker="ssl:ssl">ssl(3)</seeerl> for information
about <c>SSL</c> options (<c>ssloptions()</c>). </p>
</section>
@@ -131,12 +139,12 @@
application or started dynamically in runtime by calling the
<c>Inets</c> application API <c>inets:start(httpc, ServiceConfig)</c>
or <c>inets:start(httpc, ServiceConfig, How)</c>,
- see <seealso marker="inets">inets(3)</seealso>.
+ see <seeerl marker="inets">inets(3)</seeerl>.
The configuration options are as follows:</p>
<taglist>
<tag>{profile, profile()}</tag>
<item><p>Name of the profile, see
- <seealso marker="#DATA_TYPES">DATA TYPES</seealso>.
+ <seeerl marker="#DATA_TYPES">DATA TYPES</seeerl>.
This option is mandatory.</p></item>
<tag>{data_dir, path()}</tag>
<item><p>Directory where the profile
@@ -188,7 +196,7 @@
<p>Option <c>ipv6_host_with_bracket</c> deals with how to
parse IPv6 addresses. For details,
see argument <c>Options</c> of
- <seealso marker="#request-4">request/[4,5]</seealso>.</p>
+ <seemfa marker="#request/4">request/[4,5]</seemfa>.</p>
</desc>
</func>
@@ -343,7 +351,7 @@
<tag><c><![CDATA[ssl]]></c></tag>
<item>
<p>This is the <c>SSL/TLS</c> connectin configuration option.</p>
- <p>Defaults to <c>[]</c>. See <seealso marker="ssl:ssl">ssl:connect/[2,3,4]</seealso> for available options.</p>
+ <p>Defaults to <c>[]</c>. See <seeerl marker="ssl:ssl">ssl:connect/[2,3,4]</seeerl> for available options.</p>
</item>
<tag><c><![CDATA[autoredirect]]></c></tag>
@@ -443,7 +451,7 @@
<item>
<p>Socket options to be used for this request.</p>
<p>Overrides any value set by function
- <seealso marker="#set_options-1">set_options</seealso>.</p>
+ <seemfa marker="#set_options/1">set_options</seemfa>.</p>
<p>The validity of the options is <em>not</em> checked by
the HTTP client they are assumed to be correct and passed
on to ssl application and inet driver, which may reject
@@ -459,7 +467,7 @@
</note>
<p>By default the socket options set by function
- <seealso marker="#set_options-1">set_options/[1,2]</seealso>
+ <seemfa marker="#set_options/1">set_options/[1,2]</seemfa>
are used when establishing a connection.</p>
</item>
@@ -587,11 +595,11 @@
<v>IpAddress = ip_address()</v>
<d>If the host has several network interfaces, this option specifies
which one to use.
- See <seealso marker="kernel:gen_tcp#connect">gen_tcp:connect/3,4</seealso>
+ See <seeerl marker="kernel:gen_tcp#connect">gen_tcp:connect/3,4</seeerl>
for details.</d>
<v>Port = integer()</v>
<d>Local port number to use.
- See <seealso marker="kernel:gen_tcp#connect">gen_tcp:connect/3,4</seealso>
+ See <seeerl marker="kernel:gen_tcp#connect">gen_tcp:connect/3,4</seeerl>
for details.</d>
<v>socket_opts() = [socket_opt()]</v>
<d>The options are appended to the socket options used by the
@@ -721,9 +729,9 @@
<section>
<title>SEE ALSO</title>
<p><url href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</url>,
- <seealso marker="inets">inets(3)</seealso>,
- <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>,
- <seealso marker="ssl:ssl">ssl(3)</seealso>
+ <seeerl marker="inets">inets(3)</seeerl>,
+ <seeerl marker="kernel:gen_tcp">gen_tcp(3)</seeerl>,
+ <seeerl marker="ssl:ssl">ssl(3)</seeerl>
</p>
</section>
diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml
index e30c63e03b..21d9a87304 100644
--- a/lib/inets/doc/src/httpd.xml
+++ b/lib/inets/doc/src/httpd.xml
@@ -61,22 +61,20 @@
application, or dynamically in runtime by calling the
<c>Inets</c> application API <c>inets:start(httpd, ServiceConfig)</c> or
<c>inets:start(httpd, ServiceConfig, How)</c>,
- see <seealso marker="inets">inets(3)</seealso>.
+ see <seeerl marker="inets">inets(3)</seeerl>.
The configuration options, also called
properties, are as follows:</p>
<marker id="props_file"></marker>
<p><em>File Properties</em></p>
- <p>When the web server is started
- at application start time, the properties are to be fetched from a
- configuration file that can consist of a regular Erlang property
- list, that is, <c>[{Option, Value}]</c>, where <c> Option = property()
- </c> and <c>Value = term()</c>, followed by a full stop, or for
- backwards compatibility, an Apache-like configuration file. If the
- web server is started dynamically at runtime,
- a file can still be specified but also the complete property
- list.</p>
+ <p>When the web server is started at application start time, the
+ properties are to be fetched from a configuration file that can
+ consist of a regular Erlang property list, that is, <c>[{Option,
+ Value}]</c>, where <c> Option = property() </c> and <c>Value =
+ term()</c>, followed by a full stop. If the web server is started
+ dynamically at runtime, a file can still be specified but also the
+ complete property list.</p>
<taglist>
<tag><marker id="prop_proplist_file"></marker>{proplist_file, path()}</tag>
@@ -87,34 +85,9 @@
properties.</p>
</item>
- <tag><marker id="prop_file"></marker>{file, path()}</tag>
- <item>
- <p>If this property is defined, <c>Inets</c> expects to find all
- other properties defined in this file, which uses Apache-like
- syntax. The file must include all properties listed
- under mandatory properties. The Apache-like syntax is the property,
- written as one word where each new word begins with a capital,
- followed by a white-space, followed by the value, followed by a
- new line.</p>
- <p>Example:</p>
- <code>
-{server_root, "/urs/local/www"} -> ServerRoot /usr/local/www</code>
-
- <p>A few exceptions are documented
- for each property that behaves differently,
- and the special cases <c>{directory, {path(), PropertyList}}</c>
- and <c>{security_directory, {Dir, PropertyList}}</c>, are represented
- as:</p>
- <pre>
- <![CDATA[
-<Directory Dir>
- <Properties handled as described above>
-</Directory>
- ]]></pre>
- </item>
</taglist>
<note>
- <p>The properties <c>proplist_file</c> and <c>file</c> are mutually exclusive. Also newer properties may not be supported as Apache-like options, this is a legacy feature.</p>
+ <p>Note support for legacy configuration file with Apache syntax is dropped in OTP-23.</p>
</note>
<marker id="props_mand"></marker>
@@ -152,30 +125,29 @@
<taglist>
<tag><marker id="prop_bind_address"></marker>{bind_address, ip_address() | hostname() | any}</tag>
<item>
- <p>Default is <c>any</c>. <c>any</c> is denoted <em>*</em>
- in the Apache-like configuration file.</p>
+ <p>Default is <c>any</c></p>
</item>
<tag><marker id="profile"></marker>{profile, atom()}</tag>
<item>
- <p>Used together with <seealso marker="#prop_bind_address"><c>bind_address</c></seealso>
- and <seealso marker="#prop_port"><c>port</c></seealso> to uniquely identify
+ <p>Used together with <seeerl marker="#prop_bind_address"><c>bind_address</c></seeerl>
+ and <seeerl marker="#prop_port"><c>port</c></seeerl> to uniquely identify
a HTTP server. This can be useful in a virtualized environment,
where there can
be more that one server that has the same bind_address and port.
If this property is not explicitly set, it is assumed that the
- <seealso marker="#prop_bind_address"><c>bind_address</c></seealso> and
- <seealso marker="#prop_port"><c>port</c></seealso>uniquely identifies the HTTP server.
+ <seeerl marker="#prop_bind_address"><c>bind_address</c></seeerl> and
+ <seeerl marker="#prop_port"><c>port</c></seeerl> uniquely identifies the HTTP server.
</p>
</item>
<tag><marker id="prop_socket_type"></marker>{socket_type, ip_comm | {ip_comm, Config::proplist()} | {essl, Config::proplist()}}</tag>
<item>
<p>For <c>ip_comm</c> configuration options, see
- <seealso marker="kernel:gen_tcp#listen-2">gen_tcp:listen/2</seealso>, some options
+ <seemfa marker="kernel:gen_tcp#listen/2">gen_tcp:listen/2</seemfa>, some options
that are used internally by httpd cannot be set.</p>
<p>For <c>SSL</c> configuration options, see
- <seealso marker="ssl:ssl#listen-2">ssl:listen/2</seealso>.</p>
+ <seemfa marker="ssl:ssl#listen/2">ssl:listen/2</seemfa>.</p>
<p>Default is <c>ip_comm</c>.</p>
</item>
@@ -204,7 +176,7 @@
mod_disk_log]</c>.
Notice that some <c>mod</c>-modules are dependent on
others, so the order cannot be entirely arbitrary. See the
- <seealso marker="http_server">Inets Web Server Modules</seealso> in the
+ <seeguide marker="http_server">Inets Web Server Modules</seeguide> in the
User's Guide for details.</p>
</item>
</taglist>
@@ -216,7 +188,7 @@
<tag><marker id="prop_customize"></marker>{customize, atom()}</tag>
<item>
<p>A callback module to customize the inets HTTP servers behaviour
- see <seealso marker="httpd_custom_api"> httpd_custom_api</seealso> </p>
+ see <seeerl marker="httpd_custom_api"> httpd_custom_api</seeerl> </p>
</item>
<tag><marker id="prop_disable_chunked_encoding"></marker>{disable_chunked_transfer_encoding_send, boolean()}</tag>
@@ -284,10 +256,10 @@
<tag><marker id="max_client_body_chunk"></marker>{max_client_body_chunk, integer()}</tag>
<item>
- <p>Enforces chunking of a HTTP PUT or POST body data to be deliverd
+ <p>Enforces chunking of a HTTP PUT or POST body data to be delivered
to the mod_esi callback. Note this is not supported for mod_cgi.
- Default is no limit e.i the whole body is deliverd as one entity, which could
- be very memory consuming. <seealso marker="mod_esi">mod_esi(3)</seealso>.
+ Default is no limit e.i the whole body is delivered as one entity, which could
+ be very memory consuming. <seeerl marker="mod_esi">mod_esi(3)</seeerl>.
</p>
</item>
@@ -303,7 +275,7 @@
1590. File suffixes are mapped to MIME types before file delivery.
The mapping between file suffixes and MIME types can be specified
as an Apache-like file or directly in the property list. Such
- a file can look like the follwoing:</p>
+ a file can look like the following:</p>
<pre>
# MIME type Extension
text/html html htm
@@ -362,9 +334,9 @@ text/plain asc txt</pre>
<taglist>
<tag><c>{error, ServerID::atom()}</c></tag>
<item> <p>Produces
- <seealso marker="kernel:logger#type-log_event">logger events</seealso>
- on logger <seealso marker="kernel:logger#type-level">level error</seealso>
- under the hierarchical logger <seealso marker="kernel:logger#type-log_event">domain:</seealso> <c>[otp, inets, httpd, ServerID, error]</c>
+ <seetype marker="kernel:logger#log_event">logger events</seetype>
+ on logger <seetype marker="kernel:logger#level">level error</seetype>
+ under the hierarchical logger <seetype marker="kernel:logger#log_event">domain:</seetype> <c>[otp, inets, httpd, ServerID, error]</c>
The built in logger formatting
function produces log entries from the
error reports:</p>
@@ -519,19 +491,8 @@ Transport: TLS
<code>{re_write, {"^/[~]([^/]+)(.*)$", "/home/\\1/public\\2"}}</code>
<p>Access to http://your.server.org/~bob/foo.gif would refer to
- the file /home/bob/public/foo.gif.
-
- In an Apache-like configuration file, <c>Re</c> is separated
- from <c>Replacement</c> with one single space, and as expected
- backslashes do not need to be backslash escaped, the
- same example would become:</p>
+ the file /home/bob/public/foo.gif. </p>
- <code>ReWrite ^/[~]([^/]+)(.*)$ /home/\1/public\2</code>
-
- <p>Beware of trailing space in <c>Replacement</c> to be used.
- If you must have a space in <c>Re</c>, use, for example, the character
- encoding <c>\040</c>, see
- <seealso marker="stdlib:re">re(3)</seealso>.</p>
</item>
<tag><marker id="prop_dir_idx"></marker>{directory_index, [string()]}</tag>
@@ -791,9 +752,7 @@ Transport: TLS
<p>Sets the type of authentication database that is used for the
directory. The key difference between the different methods is
that dynamic data can be saved when Mnesia and Dets
- are used.
- This property is called <c>AuthDbType</c> in the Apache-like
- configuration files.</p>
+ are used.</p>
</item>
<tag><marker id="prop_auth_user_file"></marker>{auth_user_file, path()}</tag>
@@ -873,20 +832,6 @@ Transport: TLS
</taglist>
- <marker id="props_htaccess"></marker>
- <p><em>Htaccess Authentication Properties - Requires mod_htaccess</em></p>
- <taglist>
- <tag><marker id="prop_access_files"></marker>{access_files, [path()]}</tag>
- <item>
- <p>Specifies the filenames that are used for
- access files. When a request comes, every directory in the path
- to the requested asset are searched after files with the
- names specified by this parameter. If such a file is found, the
- file is parsed and the restrictions specified in it are
- applied to the request.</p>
- </item>
- </taglist>
-
<marker id="props_sec"></marker>
<p><em>Security Properties - Requires mod_security</em></p>
@@ -918,7 +863,7 @@ Transport: TLS
<tag><marker id="prop_block_time"></marker>{block_time, integer()}</tag>
<item>
<p>Specifies the number of minutes a user is blocked. After
- this timehas passed, the user automatically regains access.
+ this time has passed, the user automatically regains access.
Default is <c>60</c>.</p>
</item>
@@ -961,7 +906,7 @@ Transport: TLS
<note><p>Pid is the pid returned from <c>inets:start/[2,3]</c>.
Can also be retrieved form <c>inets:services/0</c> and
<c>inets:services_info/0</c>,
- see <seealso marker="inets">inets(3)</seealso>.
+ see <seeerl marker="inets">inets(3)</seeerl>.
</p></note>
</desc>
</func>
@@ -1114,10 +1059,11 @@ Transport: TLS
</taglist>
</section>
- <section>
- <title>ERLANG WEB SERVER API CALLBACK FUNCTIONS</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>ERLANG WEB SERVER API CALLBACK FUNCTIONS</title>
+ </fsdescription>
<func>
<name since="">Module:do(ModData)-> {proceed, OldData} | {proceed, NewData} | {break, NewData} | done</name>
<fsummary>Called for each request to the web server.</fsummary>
@@ -1164,37 +1110,14 @@ Transport: TLS
<p>If <c>Body</c> is returned and equal to <c>{Fun,Arg}</c>,
the web server tries <c>apply/2</c> on <c>Fun</c> with
<c>Arg</c> as argument. The web server expects that the fun either
- returns a list <c>(Body)</c> that is an HTTP repsonse, or the
+ returns a list <c>(Body)</c> that is an HTTP response, or the
atom <c>sent</c> if the HTTP response is sent back to the
client. If <c>close</c> is returned from the fun, something has gone
wrong and the server signals this to the client by
closing the connection.</p>
</desc>
</func>
-
- <func>
- <name since="">Module:load(Line, AccIn)-> eof | ok | {ok, AccOut} | {ok, AccOut, {Option, Value}} | {ok, AccOut, [{Option, Value}]} | {error, Reason}</name>
- <fsummary>Converts a line in an Apache-like config
- file to an <c>{Option, Value}</c> tuple.</fsummary>
- <type>
- <v>Line = string()</v>
- <v>AccIn = [{Option, Value}]</v>
- <v>AccOut = [{Option, Value}]</v>
- <v>Option = property()</v>
- <v>Value = term() </v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>Converts a line in an Apache-like
- configuration file to an <c>{Option, Value}</c> tuple. Some
- more complex configuration options, such as <c>directory</c>
- and <c>security_directory</c>, create an
- accumulator. This function only needs clauses for the
- options implemented by this particular callback module.
- </p>
- </desc>
- </func>
-
+
<func>
<name since="">Module:remove(ConfigDB) -> ok | {error, Reason} </name>
<fsummary>Callback function that is called when the web server is closed.</fsummary>
@@ -1234,10 +1157,11 @@ Transport: TLS
</func>
</funcs>
- <section>
- <title>ERLANG WEB SERVER API HELP FUNCTIONS</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>ERLANG WEB SERVER API HELP FUNCTIONS</title>
+ </fsdescription>
<func>
<name since="">parse_query(QueryString) -> [{Key,Value}]</name>
<fsummary>Parses incoming data to <c>erl</c> and <c>eval</c> scripts.</fsummary>
@@ -1248,7 +1172,7 @@ Transport: TLS
</type>
<desc>
<p><c>parse_query/1</c> parses incoming data to <c>erl</c> and
- <c>eval</c> scripts (see <seealso marker="mod_esi">mod_esi(3)</seealso>)
+ <c>eval</c> scripts (see <seeerl marker="mod_esi">mod_esi(3)</seeerl>)
as defined in the standard
URL format, that is, '+' becomes 'space' and decoding of
hexadecimal characters (<c>%xx</c>).</p>
@@ -1259,8 +1183,8 @@ Transport: TLS
<section>
<title>SEE ALSO</title>
<p><url href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</url>,
- <seealso marker="inets">inets(3)</seealso>,
- <seealso marker="ssl:ssl">ssl(3)</seealso>
+ <seeerl marker="inets">inets(3)</seeerl>,
+ <seeerl marker="ssl:ssl">ssl(3)</seeerl>
</p>
</section>
diff --git a/lib/inets/doc/src/httpd_custom_api.xml b/lib/inets/doc/src/httpd_custom_api.xml
index 2c0f92ff83..c904f9ab41 100644
--- a/lib/inets/doc/src/httpd_custom_api.xml
+++ b/lib/inets/doc/src/httpd_custom_api.xml
@@ -29,7 +29,7 @@
<modulesummary>Behaviour with optional callbacks to customize the inets HTTP server.</modulesummary>
<description>
<p> The module implementing this behaviour shall be supplied to to the servers
- configuration with the option <seealso marker="httpd#prop_customize"> customize</seealso></p>
+ configuration with the option <seeerl marker="httpd#prop_customize"> customize</seeerl></p>
</description>
<funcs>
diff --git a/lib/inets/doc/src/httpd_socket.xml b/lib/inets/doc/src/httpd_socket.xml
index 22ead06f38..6fac522617 100644
--- a/lib/inets/doc/src/httpd_socket.xml
+++ b/lib/inets/doc/src/httpd_socket.xml
@@ -97,7 +97,7 @@
<section>
<marker id="see_also"></marker>
<title>SEE ALSO</title>
- <p><seealso marker="httpd">httpd(3)</seealso></p>
+ <p><seeerl marker="httpd">httpd(3)</seeerl></p>
</section>
</erlref>
diff --git a/lib/inets/doc/src/httpd_util.xml b/lib/inets/doc/src/httpd_util.xml
index e0f947f860..9ee0d62ab4 100644
--- a/lib/inets/doc/src/httpd_util.xml
+++ b/lib/inets/doc/src/httpd_util.xml
@@ -171,9 +171,7 @@
<desc>
<p><c>lookup_mime</c> returns the MIME type associated with a
specific file suffix as specified in the file <c>mime.types</c>
- (located in the
- <path unix="$SERVER_ROOT/conf/mime.types" windows="%SERVER_ROOT%\conf\mime.types">
- config directory</path>).</p>
+ (located in the config directory).</p>
</desc>
</func>
@@ -191,9 +189,7 @@
<desc>
<p><c>lookup_mime_default</c> returns the MIME type associated
with a specific file suffix as specified in the
- <c>mime.types</c> file (located in the
- <path unix="$SERVER_ROOT/conf/mime.types" windows="%SERVER_ROOT%\conf\mime.types">
- config directory</path>).
+ <c>mime.types</c> file (located in the config directory).
If no appropriate association is found, the value of <c>DefaultType</c> is
returned.</p>
</desc>
@@ -384,7 +380,7 @@
<section>
<title>SEE ALSO</title>
- <p><seealso marker="httpd">httpd(3)</seealso></p>
+ <p><seeerl marker="httpd">httpd(3)</seeerl></p>
</section>
</erlref>
diff --git a/lib/inets/doc/src/inets.xml b/lib/inets/doc/src/inets.xml
index 176af3137a..cdf4eb940a 100644
--- a/lib/inets/doc/src/inets.xml
+++ b/lib/inets/doc/src/inets.xml
@@ -113,7 +113,7 @@
<desc>
<p>Starts the <c>Inets</c> application. Default type
is <c>temporary</c>. See also
- <seealso marker="kernel:application">application(3)</seealso>.</p>
+ <seeerl marker="kernel:application">application(3)</seeerl>.</p>
<marker id="stop"></marker>
</desc>
@@ -160,7 +160,7 @@
<fsummary>Stops the <c>Inets</c> application.</fsummary>
<desc>
<p>Stops the <c>Inets</c> application. See also
- <seealso marker="kernel:application">application(3)</seealso>.</p>
+ <seeerl marker="kernel:application">application(3)</seeerl>.</p>
<marker id="start2"></marker>
</desc>
@@ -188,8 +188,8 @@
<section>
<title>SEE ALSO</title>
- <p><seealso marker="httpc">httpc(3)</seealso>,
- <seealso marker="httpd">httpd(3)</seealso>
+ <p><seeerl marker="httpc">httpc(3)</seeerl>,
+ <seeerl marker="httpd">httpd(3)</seeerl>
</p>
</section>
diff --git a/lib/inets/doc/src/introduction.xml b/lib/inets/doc/src/introduction.xml
index faf911f188..a5037b02dd 100644
--- a/lib/inets/doc/src/introduction.xml
+++ b/lib/inets/doc/src/introduction.xml
@@ -37,7 +37,7 @@
<p><c>Inets</c> is a container for Internet clients and servers
including the following:</p>
<list type="bulleted">
- <item>An <term id="HTTP"></term> client and server</item>
+ <item>An HTTP client and server</item>
</list>
<p>The HTTP client and server are HTTP 1.1 compliant as
defined in
diff --git a/lib/inets/doc/src/mod_alias.xml b/lib/inets/doc/src/mod_alias.xml
index ff57d49d08..8183573ba4 100644
--- a/lib/inets/doc/src/mod_alias.xml
+++ b/lib/inets/doc/src/mod_alias.xml
@@ -57,7 +57,7 @@
file is found or if <c>Path</c> is not a directory.
<c>config_db()</c> is the server config file in ETS table format
as described in
- <seealso marker="http_server">Inets User's Guide</seealso>.</p>
+ <seeguide marker="http_server">Inets User's Guide</seeguide>.</p>
<marker id="path"></marker>
</desc>
@@ -82,7 +82,7 @@
exported, <c>ServerRoot</c> is used to
generate a file <c>Path</c>. <c>config_db()</c> and
<c>interaction_data()</c> are as defined in
- <seealso marker="http_server">Inets User's Guide</seealso>.</p>
+ <seeguide marker="http_server">Inets User's Guide</seeguide>.</p>
<marker id="real_name"></marker>
</desc>
@@ -106,14 +106,14 @@
<c>FakeName</c> is replaced with <c>RealName</c> in the
match. The resulting path is split into two parts,
<c>ShortPath</c> and <c>AfterPath</c>, as defined in
- <seealso marker="httpd_util#split_path-1">httpd_util:split_path/1</seealso>.
+ <seemfa marker="httpd_util#split_path/1">httpd_util:split_path/1</seemfa>.
<c>Path</c> is generated from <c>ShortPath</c>, that is,
the result from
- <seealso marker="#default_index">default_index/2</seealso> with
+ <seeerl marker="#default_index">default_index/2</seeerl> with
<c>ShortPath</c> as an argument.
<c>config_db()</c> is the server config file in ETS table
format as described in
- <seealso marker="http_server">Inets User's Guide</seealso>.</p>
+ <seeguide marker="http_server">Inets User's Guide</seeguide>.</p>
<marker id="real_script_name"></marker>
</desc>
@@ -140,10 +140,10 @@
<c>not_a_script</c> is returned. If it is a script, the
resulting script path is in two parts,
<c>ShortPath</c> and <c>AfterPath</c>, as defined in
- <seealso marker="httpd_util#split_script_path-1">httpd_util:split_script_path/1</seealso>.
+ <seemfa marker="httpd_util#split_script_path/1">httpd_util:split_script_path/1</seemfa>.
<c>config_db()</c> is the server config file in ETS table
format as described in
- <seealso marker="http_server">Inets User's Guide</seealso>.</p>
+ <seeguide marker="http_server">Inets User's Guide</seeguide>.</p>
</desc>
</func>
</funcs>
diff --git a/lib/inets/doc/src/mod_auth.xml b/lib/inets/doc/src/mod_auth.xml
index ad864ca4d1..2e0329dc98 100644
--- a/lib/inets/doc/src/mod_auth.xml
+++ b/lib/inets/doc/src/mod_auth.xml
@@ -289,8 +289,8 @@
<section>
<title>SEE ALSO</title>
- <p><seealso marker="httpd">httpd(3)</seealso>,
- <seealso marker="mod_alias">mod_alias(3)</seealso></p>
+ <p><seeerl marker="httpd">httpd(3)</seeerl>,
+ <seeerl marker="mod_alias">mod_alias(3)</seeerl></p>
</section>
</erlref>
diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml
index bc5f98068f..c6deef1c87 100644
--- a/lib/inets/doc/src/mod_esi.xml
+++ b/lib/inets/doc/src/mod_esi.xml
@@ -115,11 +115,12 @@
</func>
</funcs>
- <section>
- <title>ESI Callback Functions</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>ESI Callback Functions</title>
+ </fsdescription>
<func>
<name since="">Module:Function(SessionID, Env, Input)-> {continue, State} | _ </name>
<fsummary>Creates a dynamic web page and returns it chunk by chunk
@@ -155,11 +156,11 @@
a PUT or POST request. The default behavior (legacy reasons)
for delivering the body, is that the whole body is gathered and
converted to a string. But if the httpd config parameter
- <seealso
- marker="httpd#max_client_body_chunk">max_client_body_chunk</seealso>
+ <seeerl
+ marker="httpd#max_client_body_chunk">max_client_body_chunk</seeerl>
is set, the body will be delivered as binary chunks
- instead. The maximum size of the chunks is either <seealso
- marker="httpd#max_client_body_chunk">max_client_body_chunk</seealso>
+ instead. The maximum size of the chunks is either <seeerl
+ marker="httpd#max_client_body_chunk">max_client_body_chunk</seeerl>
or decide by the client if it uses HTTP chunked encoding
to send the body. When using the chunking
mechanism this callback must return {continue, State::term()}
diff --git a/lib/inets/doc/src/mod_security.xml b/lib/inets/doc/src/mod_security.xml
index c26d7468c2..8a1a1dd7ca 100644
--- a/lib/inets/doc/src/mod_security.xml
+++ b/lib/inets/doc/src/mod_security.xml
@@ -117,17 +117,18 @@
</func>
</funcs>
- <section>
- <marker id="callback_module"></marker>
- <title>SecurityCallbackModule</title>
- <p>The <c>SecurityCallbackModule</c> is a user-written module that can receive
- events from the <c>mod_security</c> Erlang web server API module.
- This module only exports the functions event/[4,5]
- which are described here.
- </p>
- </section>
+
<funcs>
+ <fsdescription>
+ <marker id="callback_module"></marker>
+ <title>SecurityCallbackModule</title>
+ <p>The <c>SecurityCallbackModule</c> is a user-written module that can receive
+ events from the <c>mod_security</c> Erlang web server API module.
+ This module only exports the functions event/[4,5]
+ which are described here.
+ </p>
+ </fsdescription>
<func>
<name since="OTP 18.1">Module:event(What, Port, Dir, Data) -> ignored</name>
<name since="OTP 18.1">Module:event(What, Address, Port, Dir, Data) -> ignored</name>
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 504ef7df8a..a8499b8d43 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,188 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 7.1.3</title>
+ <section><title>Inets 7.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Solves CVE-2021-27563, that is make sure no form of
+ relative path can be used to go outside webservers
+ directory.</p>
+ <p>
+ Own Id: OTP-17205 Aux Id: ERIERL-608 </p>
+ </item>
+ <item>
+ <p>
+ Make sure HEAD requests rejects directory links</p>
+ <p>
+ Own Id: OTP-17220</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix an issue about HTML-escaped filename in inets.</p>
+ <p>
+ Own Id: OTP-16873 Aux Id: ERL-330 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Clarify the handling of percent encoded characters in
+ http client.</p>
+ <p>
+ Own Id: OTP-16650 Aux Id: ERL-1215, PR-2629 </p>
+ </item>
+ <item>
+ <p>
+ fix crash for undefined port in uri.</p>
+ <p>
+ Own Id: OTP-16663 Aux Id: ERL-1241 </p>
+ </item>
+ <item>
+ <p>
+ Avoid timing issue when setting active once on a socket
+ that is being closed by the peer.</p>
+ <p>
+ Own Id: OTP-16735 Aux Id: OTP-16697, ERIERL-496 </p>
+ </item>
+ <item>
+ <p>
+ Handle message body of response with 1XX status code as
+ next http message.</p>
+ <p>
+ Own Id: OTP-16746 Aux Id: ERL-1268 </p>
+ </item>
+ <item>
+ <p>
+ Fix a crash in http server when setopts is called on a
+ socket closed by the peer.</p>
+ <p>
+ Own Id: OTP-16775 Aux Id: ERIERL-519 </p>
+ </item>
+ <item>
+ <p>
+ A vulnerability in the httpd module (inets application)
+ regarding directory traversal that was introduced in OTP
+ 22.3.1 and corrected in OTP 22.3.4.6. It was also
+ introduced in OTP 23.0 and corrected in OTP 23.1 The
+ vulnerability is registered as CVE-2020-25623</p>
+ <p>
+ The vulnerability is only exposed if the http server
+ (httpd) in the inets application is used. The
+ vulnerability makes it possible to read arbitrary files
+ which the Erlang system has read access to with for
+ example a specially prepared http request.</p>
+ <p>
+ Own Id: OTP-16790 Aux Id: ERIERL-522 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add support of PATCH method in mod_esi.</p>
+ <p>
+ Own Id: OTP-16591 Aux Id: ERIERL-484 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove support for deprecated functionality. Support for
+ mod_esi eval scheme, mod_htacess, mod_browser, apache
+ config files and deprecated httpd_conf functions are
+ dropped. Module http_uri is deprecated.</p>
+ <p>
+ Own Id: OTP-16252</p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>Inets 7.1.3.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Corrected an error regarding decode of percent encoded
+ URLs introduced in inets-7.1.3.</p>
+ <p>
+ Own Id: OTP-16790 Aux Id: ERIERL-522 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>Inets 7.1.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix a crash in http server when setopts is called on a
+ socket closed by the peer.</p>
+ <p>
+ Own Id: OTP-16775 Aux Id: ERIERL-519 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>Inets 7.1.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid timing issue when setting active once on a socket
+ that is being closed by the peer.</p>
+ <p>
+ Own Id: OTP-16735 Aux Id: OTP-16697, ERIERL-496 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.1.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
@@ -2052,9 +2233,9 @@
<p>Better handling of errorI(s) during update of the session
database. </p>
<p>Also added and updated some debugging functions
- <seealso marker="httpc#which_sessions-0">which_sessions/[0,1]</seealso>
+ <seemfa marker="httpc#which_sessions/0">which_sessions/[0,1]</seemfa>
and
- <seealso marker="httpc#info-0">info/0</seealso>. </p>
+ <seemfa marker="httpc#info/0">info/0</seemfa>. </p>
<p>Own Id: OTP-10093</p>
<p>Aux Id: Seq 12062</p>
</item>
@@ -2126,11 +2307,11 @@
<item>
<p>[httpd] Make the server header configurable with new config
option
- <seealso marker="httpd#prop_server_tokens">server_tokens</seealso>.
+ <seeerl marker="httpd#prop_server_tokens">server_tokens</seeerl>.
The value of the server header, which was previously hard-coded
(at compile time), is now possible to manipulate through the means
of the
- <seealso marker="httpd#prop_server_tokens">server_tokens</seealso>
+ <seeerl marker="httpd#prop_server_tokens">server_tokens</seeerl>
config option. </p>
<p>Own Id: OTP-9805</p>
</item>
@@ -2148,16 +2329,16 @@
<item>
<p>[httpc] Add function for retrieving current options,
- <seealso marker="httpc#get_options-1">get_options/[1,2]</seealso>. </p>
+ <seemfa marker="httpc#get_options/1">get_options/[1,2]</seemfa>. </p>
<p>Own Id: OTP-9979</p>
</item>
<item>
<p>Utility module
- <seealso marker="http_uri">http_uri</seealso>
+ <seeerl marker="http_uri">http_uri</seeerl>
now officially supported. </p>
<p>Also, the
- <seealso marker="http_uri#parse">parse</seealso>
+ <seeerl marker="http_uri#parse">parse</seeerl>
function has been extended with more
scheme support and a way to provide your own scheme info. </p>
<p>Own Id: OTP-9983</p>
@@ -2266,7 +2447,7 @@
<list>
<item>
<p>[ftpc] Add a config option to specify a
- <seealso marker="ftp:ftp#dtimeout">data connect timeout</seealso>.
+ <seeerl marker="ftp:ftp#dtimeout">data connect timeout</seeerl>.
That is how long the ftp client will wait for the server to connect
to the data socket. If this timeout occurs, an error will be
returned to the caller and the ftp client process will be
@@ -2282,7 +2463,7 @@
A workaround for this is to use headers_as_is and provide the host
header with the requst call.
To solve this a new option has been added,
- <seealso marker="httpc#ipv6_host_with_brackets">ipv6_host_with_brackets</seealso>.
+ <seeerl marker="httpc#ipv6_host_with_brackets">ipv6_host_with_brackets</seeerl>.
This option specifies if the host value of the host header shall
include the brackets or not. By default, it does not (as before).
</p>
@@ -2329,7 +2510,7 @@
<item>
<p>[httpc] Deprecated interface module <c>http</c> has been removed.
It has (long) been replaced by http client interface module
- <seealso marker="httpc">httpc</seealso>. </p>
+ <seeerl marker="httpc">httpc</seeerl>. </p>
<p>Own Id: OTP-9359</p>
</item>
@@ -2394,7 +2575,7 @@
<item>
<p>[httpc] Deprecated interface module <c>http</c> has been removed.
It has (long) been replaced by http client interface module
- <seealso marker="httpc#">httpc</seealso>. </p>
+ <seeerl marker="httpc#">httpc</seeerl>. </p>
<p>Own Id: OTP-9359</p>
</item>
@@ -2523,7 +2704,7 @@
<p>[httpc] Add support for upload body streaming (PUT and POST).</p>
<p>For more info,
see the definition of the <c>Body</c> argument of the
- <seealso marker="httpc#request-4">request/[4,5]</seealso>
+ <seemfa marker="httpc#request/4">request/[4,5]</seemfa>
function. </p>
<p>Filipe David Manana</p>
<p>Own Id: OTP-9094</p>
@@ -2536,7 +2717,7 @@
<item>
<p>[httpd]
- <seealso marker="mod_esi#deliver-2">mod_esi:deliver/2</seealso>
+ <seemfa marker="mod_esi#deliver/2">mod_esi:deliver/2</seemfa>
made to accept binary data. </p>
<p>Bernard Duggan</p>
<p>Own Id: OTP-9123</p>
@@ -2564,7 +2745,7 @@
for using file descriptors has been improved.
It is now possible to add the file descriptor to the config
(option fd) when calling the
- <seealso marker="inets#start-2">inets:start(httpd, ...)</seealso>
+ <seemfa marker="inets#start/2">inets:start(httpd, ...)</seemfa>
function. </p>
<p>Attila Rajmund Nohl</p>
<p>Own Id: OTP-9202</p>
@@ -2576,9 +2757,9 @@
<p><c>ossl</c> will work for as long as the ssl application
supports it. </p>
<p>See the httpd
- <seealso marker="httpd#props_comm">socket_type</seealso>
+ <seeerl marker="httpd#props_comm">socket_type</seeerl>
communication property or the httpc
- <seealso marker="httpc#request-4">request/[4,5]</seealso> function
+ <seemfa marker="httpc#request/4">request/[4,5]</seemfa> function
for more info. </p>
<p>Own Id: OTP-9230</p>
<p>*** POTENTIAL INCOMPATIBILITY ***</p>
@@ -2595,7 +2776,7 @@
<list>
<item>
<p>[httpd] Wrong
- <seealso marker="httpd#props_sec">security property</seealso>
+ <seeerl marker="httpd#props_sec">security property</seeerl>
names used in documentation. </p>
<p><c>security_data_file</c> used instead of <c>data_file</c>. </p>
<p><c>security_max_retries</c> used instead of <c>max_retries</c>. </p>
@@ -2778,8 +2959,8 @@
<p>[httpc|httpd] - Now allow the use of the "new" ssl, by using
the <c>essl</c> tag instead. </p>
<p>See the <c>http_option</c> option in the
- <seealso marker="httpc#request-4">request/[4,5]</seealso> or
- the <seealso marker="httpd#props_comm">socket-type</seealso>
+ <seemfa marker="httpc#request/4">request/[4,5]</seemfa> or
+ the <seeerl marker="httpd#props_comm">socket-type</seeerl>
section of the Communication properties chapter for more info, </p>
<p>Own Id: OTP-7907</p>
</item>
@@ -2796,9 +2977,9 @@
<p>[httpd] - Improved mod_alias.
Now able to do better URL rewrites. </p>
<p>See
- <seealso marker="httpd#props_alias">URL aliasing properties</seealso>
+ <seeerl marker="httpd#props_alias">URL aliasing properties</seeerl>
and the
- <seealso marker="httpd#props_cgi">CGI properties</seealso>
+ <seeerl marker="httpd#props_cgi">CGI properties</seeerl>
section(s) for more info, </p>
<p>Own Id: OTP-8573</p>
</item>
@@ -2838,8 +3019,8 @@
<p>[httpc] - Allow users to pass socket options to the transport
module when making requests. </p>
<p>See the <c>socket_opts</c> option in the
- <seealso marker="httpc#request2">request/4</seealso> or
- <seealso marker="httpc#set_options">set_options/1,2</seealso>
+ <seeerl marker="httpc#request2">request/4</seeerl> or
+ <seeerl marker="httpc#set_options">set_options/1,2</seeerl>
for more info, </p>
<p>Own Id: OTP-8352</p>
</item>
@@ -2871,7 +3052,7 @@
<item>
<p>[httpd] - Added support (again) for the documented debugging
features. See the User's Guide
- <seealso marker="http_server#config">Configuration</seealso>
+ <seeguide marker="http_server#config">Configuration</seeguide>
chapter for more info. </p>
<p>Own Id: OTP-8624</p>
</item>
@@ -2893,8 +3074,8 @@
<p>[httpc] - Allow users to pass socket options to the transport
module when making requests. </p>
<p>See the <c>socket_opts</c> option in the
- <seealso marker="httpc#request2">request/4</seealso> or
- <seealso marker="httpc#set_options">set_options/1,2</seealso>
+ <seeerl marker="httpc#request2">request/4</seeerl> or
+ <seeerl marker="httpc#set_options">set_options/1,2</seeerl>
for more info, </p>
<p>Own Id: OTP-8352</p>
</item>
@@ -2945,8 +3126,8 @@
<p>[httpc] - Allow users to pass socket options to the transport
module when making requests. </p>
<p>See the <c>socket_opts</c> option in the
- <seealso marker="httpc#request2">request/4</seealso> or
- <seealso marker="httpc#set_options">set_options/1,2</seealso>
+ <seeerl marker="httpc#request2">request/4</seeerl> or
+ <seeerl marker="httpc#set_options">set_options/1,2</seeerl>
for more info, </p>
<p>Own Id: OTP-8352</p>
</item>
@@ -3008,8 +3189,8 @@
<p>[httpc] - Allow users to pass socket options to the transport
module when making requests. </p>
<p>See the <c>socket_opts</c> option in the
- <seealso marker="httpc#request-4">request/4</seealso> or
- <seealso marker="httpc#set_options-1">set_options/[1,2]</seealso>
+ <seemfa marker="httpc#request/4">request/4</seemfa> or
+ <seemfa marker="httpc#set_options/1">set_options/[1,2]</seemfa>
for more info, </p>
<p>Own Id: OTP-8352</p>
</item>
@@ -3050,7 +3231,7 @@
deliver an async reply to more receivers then the calling
process. </p>
<p>See the
- <seealso marker="httpc#request-2">receiver</seealso>
+ <seemfa marker="httpc#request/2">receiver</seemfa>
option for more info, </p>
<p>Own Id: OTP-8106</p>
</item>
@@ -3089,7 +3270,7 @@
</list>
<p>As a side-effect of these changes, some modules was also
renamed, and a new api module,
- <seealso marker="httpc">httpc</seealso>, has been introduced
+ <seeerl marker="httpc">httpc</seeerl>, has been introduced
(the old module <c>http</c> is <em>not</em> removed, but is
now just wrapper for <c>httpc</c>). </p>
<p>Own Id: OTP-8016</p>
@@ -3149,10 +3330,10 @@
<item>
<p>It is now also possible to start a standalone FTP client
process using the re-introduced
- <seealso marker="ftp:ftp#open">ftp:open</seealso>
+ <seeerl marker="ftp:ftp#open">ftp:open</seeerl>
function. </p>
<p>This is an alternative to starting the client using the
- <seealso marker="ftp:ftp#service_start">inets service framework</seealso>. </p>
+ <seeerl marker="ftp:ftp#service_start">inets service framework</seeerl>. </p>
<p>The old <c>ftp:open/1</c>, undocumented, function,
caused the client to be hooken into the inets service
supervision framework. This is <em>no</em> longer the
@@ -3165,10 +3346,10 @@
flag), and only used IPv4 if this did not work.
This has now been <em>changed</em>. </p>
<p>A new option,
- <seealso marker="ftp:ftp#ipfamily">ipfamily</seealso>,
+ <seeerl marker="ftp:ftp#ipfamily">ipfamily</seeerl>,
has been introduced, with the default value
<c>inet</c> (IPv4). </p>
- <p>See <seealso marker="ftp:ftp#open">ftp:open</seealso>
+ <p>See <seeerl marker="ftp:ftp#open">ftp:open</seeerl>
for more info.</p>
<p>*** POTENTIAL INCOMPATIBILITY ***</p>
</item>
@@ -3202,9 +3383,9 @@
<item>
<p>[ftpc] - The
- <seealso marker="ftp:ftp#ls2">ls/2</seealso> function (LIST command)
+ <seeerl marker="ftp:ftp#ls2">ls/2</seeerl> function (LIST command)
and the
- <seealso marker="ftp:ftp#nlist2">nlist/2</seealso> function
+ <seeerl marker="ftp:ftp#nlist2">nlist/2</seeerl> function
(NLST command)
with wildcards did
not work properly. </p>
@@ -3317,7 +3498,7 @@
request, when the client connects to the server. Default
value is that of the <c>timeout</c> option. </p>
<p>See the
- <seealso marker="httpc#request-4">request/[4,5]</seealso>
+ <seemfa marker="httpc#request/4">request/[4,5]</seemfa>
function for more info. </p>
<p>Own Id: OTP-7298</p>
<!-- <p>Aux Id: seq11086</p> -->
@@ -3380,7 +3561,7 @@
<p>Default is <c>inet6fb4</c> which emulates the
behaviour of the previous version. </p>
<p>See the
- <seealso marker="httpd#props_comm">Communication properties</seealso>
+ <seeerl marker="httpd#props_comm">Communication properties</seeerl>
section for more info. </p>
<p>Own Id: OTP-8069</p>
<p>Aux Id: seq11086</p>
@@ -3424,7 +3605,7 @@
the client connects to the server. </p>
<p>As a side-effect of this, the option <c>ipv6</c> has been
removed and replaced by the <c>ipfamily</c> option. </p>
- <p>See <seealso marker="httpc#set_options-1">http:set_options/[1,2]</seealso>
+ <p>See <seemfa marker="httpc#set_options/1">http:set_options/[1,2]</seemfa>
for more info. </p>
<p>*** POTENTIAL INCOMPATIBILITY ***</p>
<p>Own Id: OTP-8004</p>
diff --git a/lib/inets/doc/src/notes_history.xml b/lib/inets/doc/src/notes_history.xml
index 1523827db9..39a7b9da53 100644
--- a/lib/inets/doc/src/notes_history.xml
+++ b/lib/inets/doc/src/notes_history.xml
@@ -529,7 +529,7 @@
<list type="bulleted">
<item>
<p>[tftp] Added documentation (manual page) for
- <seealso marker="tftp">TFTP</seealso>.</p>
+ <seeerl marker="tftp">TFTP</seeerl>.</p>
<p>Own Id: OTP-6082</p>
</item>
</list>
diff --git a/lib/inets/doc/src/part.xml b/lib/inets/doc/src/part.xml
index b9c8ed674c..eca680cdb6 100644
--- a/lib/inets/doc/src/part.xml
+++ b/lib/inets/doc/src/part.xml
@@ -33,7 +33,7 @@
<p>The <c>Inets</c> application provides a set of
Internet-related services as follows:</p>
<list type="bulleted">
- <item>An <term id="HTTP"></term> client and server</item>
+ <item>An HTTP client and server</item>
</list>
<p>The HTTP client and server are HTTP 1.1 compliant as
defined in
diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl
index 1e46fa955b..28dd97b155 100644
--- a/lib/inets/src/http_client/httpc.erl
+++ b/lib/inets/src/http_client/httpc.erl
@@ -503,7 +503,7 @@ service_info(Pid) ->
normalize_and_parse_url(Url) ->
case uri_string:normalize(Url) of
{error, _, _} = Error -> Error;
- UriString -> uri_string:parse(UriString)
+ UriString -> uri_string:parse(unicode:characters_to_list(UriString))
end.
handle_request(Method, Url,
diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl
index 3f91ae062c..9135935567 100644
--- a/lib/inets/src/http_client/httpc_handler.erl
+++ b/lib/inets/src/http_client/httpc_handler.erl
@@ -602,12 +602,18 @@ do_handle_info({Proto, Socket, Data},
{noreply, State};
%% The Server may close the connection to indicate that the
-%% whole body is now sent instead of sending an length
-%% indicator.
-do_handle_info({tcp_closed, _}, State = #state{mfa = {_, whole_body, Args}}) ->
- handle_response(State#state{body = hd(Args)});
-do_handle_info({ssl_closed, _}, State = #state{mfa = {_, whole_body, Args}}) ->
- handle_response(State#state{body = hd(Args)});
+%% whole body is now sent instead of sending a lengh
+%% indicator. In this case the lengh indicator will be
+%% -1.
+do_handle_info({Info, _}, State = #state{mfa = {_, whole_body, Args}})
+ when Info =:= tcp_closed orelse
+ Info =:= ssl_closed ->
+ case lists:last(Args) of
+ Length when Length =< 0 ->
+ handle_response(State#state{body = hd(Args)});
+ _Else ->
+ {stop, {shutdown, server_closed}, State}
+ end;
%%% Server closes idle pipeline
do_handle_info({tcp_closed, _}, State = #state{request = undefined}) ->
@@ -831,8 +837,7 @@ connect_and_send_first_request(Address, Request, #state{options = Options0} = St
headers = undefined,
body = undefined,
status = new},
- http_transport:setopts(SocketType,
- Socket, [{active, once}]),
+ activate_once(Session),
NewState = activate_request_timeout(TmpState),
{ok, NewState};
{error, Reason} ->
@@ -966,16 +971,16 @@ handle_http_body(_, #state{status = {ssl_tunnel, Request},
%% terminated by the first empty line after the header fields.
%% This implies that chunked encoding MUST NOT be used for these
%% status codes.
-handle_http_body(<<>>, #state{headers = Headers,
+handle_http_body(Body, #state{headers = Headers,
status_line = {_,StatusCode, _}} = State)
when Headers#http_response_h.'transfer-encoding' =/= "chunked" andalso
(StatusCode =:= 204 orelse %% No Content
StatusCode =:= 304 orelse %% Not Modified
100 =< StatusCode andalso StatusCode =< 199) -> %% Informational
- handle_response(State#state{body = <<>>});
+ handle_response(State#state{body = Body});
-
-handle_http_body(<<>>, #state{request = #request{method = head}} = State) ->
+%% Ignore the body of response to a HEAD method
+handle_http_body(_Body, #state{request = #request{method = head}} = State) ->
handle_response(State#state{body = <<>>});
handle_http_body(Body, #state{headers = Headers,
@@ -1238,7 +1243,12 @@ case_insensitive_header(Str) ->
Str.
activate_once(#session{socket = Socket, socket_type = SocketType}) ->
- http_transport:setopts(SocketType, Socket, [{active, once}]).
+ case http_transport:setopts(SocketType, Socket, [{active, once}]) of
+ ok ->
+ ok;
+ {error, _} -> %% inet can return einval instead of closed
+ self() ! {http_transport:close_tag(SocketType), Socket}
+ end.
close_socket(#session{socket = {remote_close,_}}) ->
ok;
@@ -1581,8 +1591,7 @@ send_raw(SocketType, Socket, ProcessBody, Acc) ->
end
end.
-tls_tunnel(Address, Request, #state{session = #session{socket = Socket,
- socket_type = SocketType} = Session} = State,
+tls_tunnel(Address, Request, #state{session = #session{} = Session} = State,
ErrorHandler) ->
UpgradeRequest = tls_tunnel_request(Request),
case httpc_request:send(Address, Session, UpgradeRequest) of
@@ -1594,8 +1603,7 @@ tls_tunnel(Address, Request, #state{session = #session{socket = Socket,
init_status_line(UpgradeRequest),
headers = undefined,
body = undefined},
- http_transport:setopts(SocketType,
- Socket, [{active, once}]),
+ activate_once(Session),
NewState = activate_request_timeout(TmpState),
{ok, NewState#state{status = {ssl_tunnel, Request}}};
{error, Reason} ->
@@ -1661,7 +1669,7 @@ tls_upgrade(#state{status =
type = SessionType,
client_close = ClientClose},
httpc_request:send(Address, Session, Request),
- http_transport:setopts(SocketType, TLSSocket, [{active, once}]),
+ activate_once(Session),
NewState = State#state{session = Session,
request = Request,
mfa = init_mfa(Request, State),
diff --git a/lib/inets/src/http_client/httpc_response.erl b/lib/inets/src/http_client/httpc_response.erl
index bb6b76da89..538cdd0e87 100644
--- a/lib/inets/src/http_client/httpc_response.erl
+++ b/lib/inets/src/http_client/httpc_response.erl
@@ -91,7 +91,13 @@ result(Response = {{_, Code,_}, _, _},
when ((Code =:= 200) orelse (Code =:= 206)) andalso (Stream =/= none) ->
stream_end(Response, Request);
-result(Response = {{_,100,_}, _, _}, Request) ->
+%% Ignore the body of response with status code 204 or 304
+result({{_, Code, _} = StatusLine, Headers, _Body}, Request)
+ when Code =:= 204 orelse Code =:= 304 ->
+ transparent({StatusLine, Headers, <<>>}, Request);
+
+result(Response = {{_, Code, _}, _, _}, Request)
+ when (100 =< Code andalso Code =< 199) ->
status_continue(Response, Request);
%% In redirect loop
@@ -356,7 +362,7 @@ status_continue(_, #request{headers =
status_continue({_,_, Data}, _) ->
%% The data in the body in this case is actually part of the real
- %% response sent after the "fake" 100-continue.
+ %% response.
{ignore, Data}.
status_service_unavailable(Response = {_, Headers, _}, Request) ->
diff --git a/lib/inets/src/http_lib/http_request.erl b/lib/inets/src/http_lib/http_request.erl
index c49402cc1f..2510cdede2 100644
--- a/lib/inets/src/http_lib/http_request.erl
+++ b/lib/inets/src/http_lib/http_request.erl
@@ -103,9 +103,11 @@ is_absolut_uri(_) ->
%% Description: returns a normalized Host header value, with the port
%% number omitted for well-known ports
%%-------------------------------------------------------------------------
-normalize_host(https, Host, 443 = _Port) ->
+normalize_host(https, Host, Port) when Port =:= 443 orelse
+ Port =:= undefined ->
Host;
-normalize_host(http, Host, 80 = _Port) ->
+normalize_host(http, Host, Port) when Port =:= 80 orelse
+ Port =:= undefined ->
Host;
normalize_host(_Scheme, Host, Port) ->
Host ++ ":" ++ integer_to_list(Port).
diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl
index d5e1d71336..d2148a3a8a 100644
--- a/lib/inets/src/http_lib/http_transport.erl
+++ b/lib/inets/src/http_lib/http_transport.erl
@@ -27,6 +27,7 @@
listen/4, listen/5,
accept/2, accept/3,
close/2,
+ close_tag/1,
send/3,
controlling_process/3,
setopts/3, getopts/2, getopts/3,
@@ -459,6 +460,11 @@ ipv6_name({A, B, C, D, E, F, G, H}) ->
http_util:integer_to_hexlist(H).
+close_tag(ip_comm) ->
+ tcp_closed;
+close_tag(_) ->
+ ssl_closed.
+
%%%========================================================================
%%% Internal functions
%%%========================================================================
diff --git a/lib/inets/src/http_lib/http_uri.erl b/lib/inets/src/http_lib/http_uri.erl
index 6805b0293d..8b3eaf1930 100644
--- a/lib/inets/src/http_lib/http_uri.erl
+++ b/lib/inets/src/http_lib/http_uri.erl
@@ -61,6 +61,14 @@
scheme_defaults/0,
encode/1, decode/1]).
+
+-deprecated({parse, 1, "use uri_string functions instead"}).
+-deprecated({parse, 2, "use uri_string functions instead"}).
+-deprecated({encode, 1, "use uri_string functions instead"}).
+-deprecated({decode, 1, "use uri_string functions instead"}).
+-deprecated({scheme_defaults, 0, "use uri_string functions instead"}).
+
+
-export_type([uri/0,
user_info/0,
scheme/0, default_scheme_port_number/0,
diff --git a/lib/inets/src/http_server/Makefile b/lib/inets/src/http_server/Makefile
index 9848fd4b35..da9549406f 100644
--- a/lib/inets/src/http_server/Makefile
+++ b/lib/inets/src/http_server/Makefile
@@ -74,14 +74,12 @@ MODULES = \
mod_auth_dets \
mod_auth_mnesia \
mod_auth_server \
- mod_browser \
mod_cgi \
mod_dir \
mod_disk_log \
mod_esi \
mod_get \
mod_head \
- mod_htaccess \
mod_log \
mod_range \
mod_responsecontrol \
diff --git a/lib/inets/src/http_server/httpd.erl b/lib/inets/src/http_server/httpd.erl
index b4d569491a..ae35f109ed 100644
--- a/lib/inets/src/http_server/httpd.erl
+++ b/lib/inets/src/http_server/httpd.erl
@@ -41,9 +41,13 @@
reload_config/2,
info/1,
info/2,
- info/3
+ info/3,
+ info/4
]).
+-deprecated({parse_query, 1,
+ "use uri_string:dissect_query/1 instead"}).
+
%%%========================================================================
%%% API
%%%========================================================================
@@ -57,15 +61,7 @@ reload_config(ConfigFile, Mode) ->
try file:consult(ConfigFile) of
{ok, [PropList]} ->
%% Erlang terms format
- do_reload_config(PropList, Mode);
- {error, _ } ->
- %% Apache format
- case httpd_conf:load(ConfigFile) of
- {ok, ConfigList} ->
- do_reload_config(ConfigList, Mode);
- Error ->
- Error
- end
+ do_reload_config(PropList, Mode)
catch
exit:_ ->
throw({error, {could_not_consult_proplist_file, ConfigFile}})
diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl
index d42fc7c607..e85455178a 100644
--- a/lib/inets/src/http_server/httpd_conf.erl
+++ b/lib/inets/src/http_server/httpd_conf.erl
@@ -21,321 +21,35 @@
-module(httpd_conf).
%% Application internal API
--export([load/1, load/2, load_mime_types/1, store/1, store/2,
+-export([load_mime_types/1, store/1, store/2,
remove/1, remove_all/1, get_config/3, get_config/4,
lookup_socket_type/1,
lookup/2, lookup/3, lookup/4,
validate_properties/1, white_space_clean/1]).
-%% Deprecated
--export([is_directory/1, is_file/1, make_integer/1, clean/1,
- custom_clean/3, check_enum/2]).
-
--deprecated({is_directory, 1, next_major_release}).
--deprecated({is_file, 1, next_major_release}).
--deprecated({make_integer, 1, next_major_release}).
--deprecated({clean, 1, next_major_release}).
--deprecated({custom_clean, 3, next_major_release}).
--deprecated({check_enum, 2, next_major_release}).
-
-define(VMODULE,"CONF").
-include("httpd_internal.hrl").
-include("httpd.hrl").
-include_lib("inets/src/http_lib/http_internal.hrl").
+%% Removed functions
+
+-removed([{check_enum,2,"use lists:member/2 instead"},
+ {clean,1,"use sting:strip/1 instead or possibly the re module"},
+ {custom_clean,3,"use sting:strip/1 instead or possibly the re module"},
+ {is_directory,1,"use filelib:is_dir/1 instead"},
+ {is_file,1,"use filelib:is_file/1 instead"},
+ {make_integer,1,"use erlang:list_to_integer/1 instead"}]).
+
%%%=========================================================================
%%% Application internal API
%%%=========================================================================
-%% The configuration data is handled in three (3) phases:
-%% 1. Parse the config file and put all directives into a key-vale
-%% tuple list (load/1).
-%% 2. Traverse the key-value tuple list store it into an ETS table.
+%% The configuration data is handled in three (2) phases:
+%% 1. Traverse the key-value tuple list store it into an ETS table.
%% Directives depending on other directives are taken care of here
%% (store/1).
%% 3. Traverse the ETS table and do a complete clean-up (remove/1).
-%% Phase 1: Load
-load(ConfigFile) ->
- case read_config_file(ConfigFile) of
- {ok, Config} ->
- case bootstrap(Config) of
- {error, Reason} ->
- {error, Reason};
- {ok, Modules} ->
- load_config(Config, lists:append(Modules, [?MODULE]))
- end;
- {error, Reason} ->
- {error, ?NICE("Error while reading config file: "++Reason)}
- end.
-
-load(eof, []) ->
- eof;
-
-load("MaxHeaderSize " ++ MaxHeaderSize, []) ->
- case make_integer(MaxHeaderSize) of
- {ok, Integer} ->
- {ok, [], {max_header_size,Integer}};
- {error, _} ->
- {error, ?NICE(string:strip(MaxHeaderSize)++
- " is an invalid number of MaxHeaderSize")}
- end;
-
-load("MaxURISize " ++ MaxHeaderSize, []) ->
- case make_integer(MaxHeaderSize) of
- {ok, Integer} ->
- {ok, [], {max_uri_size, Integer}};
- {error, _} ->
- {error, ?NICE(string:strip(MaxHeaderSize)++
- " is an invalid number of MaxHeaderSize")}
- end;
-
-load("MaxContentLength " ++ Max, []) ->
- case make_integer(Max) of
- {ok, Integer} ->
- {ok, [], {max_content_length, Integer}};
- {error, _} ->
- {error, ?NICE(string:strip(Max) ++
- " is an invalid number of MaxContentLength")}
- end;
-
-load("ServerName " ++ ServerName, []) ->
- {ok,[], {server_name, string:strip(ServerName)}};
-
-load("ServerTokens " ++ ServerTokens, []) ->
- %% These are the valid *plain* server tokens:
- %% none, prod, major, minor, minimum, os, full
- %% It can also be a "private" server token: private:<any string>
- case string:tokens(ServerTokens, [$:]) of
- ["private", Private] ->
- {ok,[], {server_tokens, string:strip(Private)}};
- [TokStr] ->
- Tok = list_to_atom(string:strip(TokStr)),
- case lists:member(Tok, [none, prod, major, minor, minimum, os, full]) of
- true ->
- {ok,[], {server_tokens, Tok}};
- false ->
- {error, ?NICE(string:strip(ServerTokens) ++
- " is an invalid ServerTokens")}
- end;
- _ ->
- {error, ?NICE(string:strip(ServerTokens) ++ " is an invalid ServerTokens")}
- end;
-
-load("SocketType " ++ SocketType, []) ->
- %% ssl is the same as HTTP_DEFAULT_SSL_KIND
- %% essl is the pure Erlang-based ssl (the "new" ssl)
- case check_enum(string:strip(SocketType), ["ssl", "essl", "ip_comm"]) of
- {ok, ValidSocketType} ->
- {ok, [], {socket_type, ValidSocketType}};
- {error,_} ->
- {error, ?NICE(string:strip(SocketType) ++ " is an invalid SocketType")}
- end;
-
-load("Port " ++ Port, []) ->
- case make_integer(Port) of
- {ok, Integer} ->
- {ok, [], {port, Integer}};
- {error, _} ->
- {error, ?NICE(string:strip(Port)++" is an invalid Port")}
- end;
-
-load("BindAddress " ++ Address0, []) ->
- %% If an ipv6 address is provided in URL-syntax strip the
- %% url specific part e.i. "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]"
- %% -> "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210"
-
- try
- begin
- {Address, IpFamily} =
- case string:tokens(Address0, [$|]) of
- [Address1] ->
- {clean_address(Address1), inet};
- [Address1, IpFamilyStr] ->
- {clean_address(Address1), make_ipfamily(IpFamilyStr)};
- _Bad ->
- throw({error, {bad_bind_address, Address0}})
- end,
-
- case Address of
- "*" ->
- {ok, [], [{bind_address, any}, {ipfamily, IpFamily}]};
- _ ->
- case httpd_util:ip_address(Address, IpFamily) of
- {ok, IPAddr} ->
- Entries = [{bind_address, IPAddr},
- {ipfamily, IpFamily}],
- {ok, [], Entries};
- {error, _} ->
- {error, ?NICE(Address ++ " is an invalid address")}
- end
- end
- end
- catch
- throw:{error, {bad_bind_address, _}} ->
- {error, ?NICE(Address0 ++ " is an invalid address")};
- throw:{error, {bad_ipfamily, _}} ->
- {error, ?NICE(Address0 ++ " has an invalid ipfamily")}
- end;
-
-load("KeepAlive " ++ OnorOff, []) ->
- case list_to_atom(string:strip(OnorOff)) of
- off ->
- {ok, [], {keep_alive, false}};
- _ ->
- {ok, [], {keep_alive, true}}
- end;
-
-load("MaxKeepAliveRequests " ++ MaxRequests, []) ->
- case make_integer(MaxRequests) of
- {ok, Integer} ->
- {ok, [], {max_keep_alive_request, Integer}};
- {error, _} ->
- {error, ?NICE(string:strip(MaxRequests) ++
- " is an invalid MaxKeepAliveRequests")}
- end;
-
-%% This clause is kept for backwards compatibility
-load("MaxKeepAliveRequest " ++ MaxRequests, []) ->
- case make_integer(MaxRequests) of
- {ok, Integer} ->
- {ok, [], {max_keep_alive_request, Integer}};
- {error, _} ->
- {error, ?NICE(string:strip(MaxRequests) ++
- " is an invalid MaxKeepAliveRequest")}
- end;
-
-load("KeepAliveTimeout " ++ Timeout, []) ->
- case make_integer(Timeout) of
- {ok, Integer} ->
- {ok, [], {keep_alive_timeout, Integer}};
- {error, _} ->
- {error, ?NICE(string:strip(Timeout)++" is an invalid KeepAliveTimeout")}
- end;
-
-load("Modules " ++ Modules, []) ->
- ModuleList = re:split(Modules," ", [{return, list}]),
- {ok, [], {modules,[list_to_atom(X) || X <- ModuleList]}};
-
-load("ServerAdmin " ++ ServerAdmin, []) ->
- {ok, [], {server_admin,string:strip(ServerAdmin)}};
-
-load("ServerRoot " ++ ServerRoot, []) ->
- case is_directory(string:strip(ServerRoot)) of
- {ok, Directory} ->
- {ok, [], [{server_root,string:strip(Directory,right,$/)}]};
- {error, _} ->
- {error, ?NICE(string:strip(ServerRoot)++" is an invalid ServerRoot")}
- end;
-
-load("MimeTypes " ++ MimeTypes, []) ->
- case load_mime_types(white_space_clean(MimeTypes)) of
- {ok, MimeTypesList} ->
- {ok, [], [{mime_types, MimeTypesList}]};
- {error, Reason} ->
- {error, Reason}
- end;
-
-load("MaxClients " ++ MaxClients, []) ->
- case make_integer(MaxClients) of
- {ok, Integer} ->
- {ok, [], {max_clients,Integer}};
- {error, _} ->
- {error, ?NICE(string:strip(MaxClients) ++
- " is an invalid number of MaxClients")}
- end;
-load("DocumentRoot " ++ DocumentRoot,[]) ->
- case is_directory(string:strip(DocumentRoot)) of
- {ok, Directory} ->
- {ok, [], {document_root,string:strip(Directory,right,$/)}};
- {error, _} ->
- {error, ?NICE(string:strip(DocumentRoot)++" is an invalid DocumentRoot")}
- end;
-load("DefaultType " ++ DefaultType, []) ->
- {ok, [], {default_type,string:strip(DefaultType)}};
-load("SSLCertificateFile " ++ SSLCertificateFile, []) ->
- case is_file(string:strip(SSLCertificateFile)) of
- {ok, File} ->
- {ok, [], {ssl_certificate_file,File}};
- {error, _} ->
- {error, ?NICE(string:strip(SSLCertificateFile)++
- " is an invalid SSLCertificateFile")}
- end;
-load("SSLLogLevel " ++ SSLLogAlert, []) ->
- case SSLLogAlert of
- "none" ->
- {ok, [], {ssl_log_alert, false}};
- _ ->
- {ok, [], {ssl_log_alert, true}}
- end;
-load("SSLCertificateKeyFile " ++ SSLCertificateKeyFile, []) ->
- case is_file(string:strip(SSLCertificateKeyFile)) of
- {ok, File} ->
- {ok, [], {ssl_certificate_key_file,File}};
- {error, _} ->
- {error, ?NICE(string:strip(SSLCertificateKeyFile)++
- " is an invalid SSLCertificateKeyFile")}
- end;
-load("SSLVerifyClient " ++ SSLVerifyClient, []) ->
- case make_integer(string:strip(SSLVerifyClient)) of
- {ok, Integer} when (Integer >=0) andalso (Integer =< 2) ->
- {ok, [], {ssl_verify_client,Integer}};
- {ok, _Integer} ->
- {error,?NICE(string:strip(SSLVerifyClient) ++
- " is an invalid SSLVerifyClient")};
- {error, nomatch} ->
- {error,?NICE(string:strip(SSLVerifyClient) ++
- " is an invalid SSLVerifyClient")}
- end;
-load("SSLVerifyDepth " ++ SSLVerifyDepth, []) ->
- case make_integer(string:strip(SSLVerifyDepth)) of
- {ok, Integer} when Integer > 0 ->
- {ok, [], {ssl_verify_client_depth,Integer}};
- {ok, _Integer} ->
- {error,?NICE(string:strip(SSLVerifyDepth) ++
- " is an invalid SSLVerifyDepth")};
- {error, nomatch} ->
- {error,?NICE(string:strip(SSLVerifyDepth) ++
- " is an invalid SSLVerifyDepth")}
- end;
-load("SSLCiphers " ++ SSLCiphers, []) ->
- {ok, [], {ssl_ciphers, string:strip(SSLCiphers)}};
-load("SSLCACertificateFile " ++ SSLCACertificateFile, []) ->
- case is_file(string:strip(SSLCACertificateFile)) of
- {ok, File} ->
- {ok, [], {ssl_ca_certificate_file,File}};
- {error, _} ->
- {error, ?NICE(string:strip(SSLCACertificateFile)++
- " is an invalid SSLCACertificateFile")}
- end;
-load("SSLPasswordCallbackModule " ++ SSLPasswordCallbackModule, []) ->
- {ok, [], {ssl_password_callback_module,
- list_to_atom(string:strip(SSLPasswordCallbackModule))}};
-load("SSLPasswordCallbackFunction " ++ SSLPasswordCallbackFunction, []) ->
- {ok, [], {ssl_password_callback_function,
- list_to_atom(string:strip(SSLPasswordCallbackFunction))}};
-load("SSLPasswordCallbackArguments " ++ SSLPasswordCallbackArguments, []) ->
- {ok, [], {ssl_password_callback_arguments,
- SSLPasswordCallbackArguments}};
-load("DisableChunkedTransferEncodingSend " ++ TrueOrFalse, []) ->
- case list_to_atom(string:strip(TrueOrFalse)) of
- true ->
- {ok, [], {disable_chunked_transfer_encoding_send, true}};
- _ ->
- {ok, [], {disable_chunked_transfer_encoding_send, false}}
- end;
-load("LogFormat " ++ LogFormat, []) ->
- {ok,[],{log_format, list_to_atom(string:strip(LogFormat))}};
-load("ErrorLogFormat " ++ LogFormat, []) ->
- {ok,[],{error_log_format, list_to_atom(string:strip(LogFormat))}}.
-
-
-clean_address(Addr) ->
- string:strip(string:strip(string:strip(Addr), left, $[), right, $]).
-
-
-make_ipfamily(IpFamilyStr) ->
- validate_ipfamily(list_to_atom(IpFamilyStr)).
-
validate_ipfamily(inet) ->
inet;
validate_ipfamily(inet6) ->
@@ -861,139 +575,9 @@ ssl_config(ConfigDB) ->
ssl_ca_certificate_file(ConfigDB) ++
ssl_log_level(ConfigDB).
-
-
%%%========================================================================
%%% Internal functions
%%%========================================================================
-%%% Phase 1 Load:
-bootstrap([]) ->
- {ok, ?DEFAULT_MODS};
-bootstrap([Line|Config]) ->
- case Line of
- "Modules " ++ Modules ->
- ModuleList = re:split(Modules," ", [{return, list}]),
- TheMods = [list_to_atom(X) || X <- ModuleList],
- case verify_modules(TheMods) of
- ok ->
- {ok, TheMods};
- {error, Reason} ->
- {error, Reason}
- end;
- _ ->
- bootstrap(Config)
- end.
-
-load_config(Config, Modules) ->
- %% Create default contexts for all modules
- Contexts = lists:duplicate(length(Modules), []),
- load_config(Config, Modules, Contexts, []).
-
-load_config([], _Modules, _Contexts, ConfigList) ->
- {ok, ConfigList};
-
-load_config([Line|Config], Modules, Contexts, ConfigList) ->
- case load_traverse(Line, Contexts, Modules, [], ConfigList, no) of
- {ok, NewContexts, NewConfigList} ->
- load_config(Config, Modules, NewContexts, NewConfigList);
- {error, Reason} ->
- {error, Reason}
- end.
-
-
-%% This loads the config file into each module specified by Modules
-%% Each module has its own context that is passed to and (optionally)
-%% returned by the modules load function. The module can also return
-%% a ConfigEntry, which will be added to the global configuration
-%% list.
-%% All configuration directives are guaranteed to be passed to all
-%% modules. Each module only implements the function clauses of
-%% the load function for the configuration directives it supports,
-%% it's ok if an apply returns {'EXIT', {function_clause, ..}}.
-load_traverse(Line, [], [], _NewContexts, _ConfigList, no) ->
- {error, ?NICE("Configuration directive not recognized: "++Line)};
-load_traverse(_Line, [], [], NewContexts, ConfigList, yes) ->
- {ok, lists:reverse(NewContexts), ConfigList};
-load_traverse(Line, [Context|Contexts], [Module|Modules], NewContexts,
- ConfigList, State) ->
- case catch apply(Module, load, [Line, Context]) of
- {'EXIT', {function_clause, _FC}} ->
- load_traverse(Line, Contexts, Modules,
- [Context|NewContexts], ConfigList, State);
-
- {'EXIT', {undef, _}} ->
- load_traverse(Line, Contexts, Modules,
- [Context|NewContexts], ConfigList, yes);
-
- {'EXIT', Reason} ->
- error_logger:error_report({'EXIT', Reason}),
- load_traverse(Line, Contexts, Modules,
- [Context|NewContexts], ConfigList, State);
-
- ok ->
- load_traverse(Line, Contexts, Modules,
- [Context|NewContexts], ConfigList, yes);
-
- {ok, NewContext} ->
- load_traverse(Line, Contexts, Modules,
- [NewContext|NewContexts], ConfigList, yes);
-
- {ok, NewContext, ConfigEntry} when is_tuple(ConfigEntry) ->
- load_traverse(Line, Contexts,
- Modules, [NewContext|NewContexts],
- [ConfigEntry|ConfigList], yes);
-
- {ok, NewContext, ConfigEntry} when is_list(ConfigEntry) ->
- load_traverse(Line, Contexts, Modules, [NewContext|NewContexts],
- lists:append(ConfigEntry, ConfigList), yes);
-
- {error, Reason} ->
- {error, Reason}
- end.
-
-%% Verifies that all specified modules are available.
-verify_modules([]) ->
- ok;
-verify_modules([Mod|Rest]) ->
- case code:which(Mod) of
- non_existing ->
- {error, ?NICE(string:strip(atom_to_list(Mod), right, $\n) ++" does not exist")};
- _Path ->
- verify_modules(Rest)
- end.
-
-%% Reads the entire configuration file and returns list of strings or
-%% and error.
-read_config_file(FileName) ->
- case file:open(FileName, [read]) of
- {ok, Stream} ->
- read_config_file(Stream, []);
- {error, _Reason} ->
- {error, ?NICE("Cannot open "++FileName)}
- end.
-read_config_file(Stream, SoFar) ->
- case io:get_line(Stream, []) of
- eof ->
- file:close(Stream),
- {ok, lists:reverse(SoFar)};
- {error, Reason} ->
- file:close(Stream),
- {error, Reason};
- [$#|_Rest] ->
- %% Ignore commented lines for efficiency later ..
- read_config_file(Stream, SoFar);
- Line ->
- NewLine = re:replace(white_space_clean(Line),
- "[\t\r\f ]"," ", [{return,list}, global]),
- case NewLine of
- [] ->
- %% Also ignore empty lines ..
- read_config_file(Stream, SoFar);
- _Other ->
- read_config_file(Stream, [NewLine|SoFar])
- end
- end.
-
parse_mime_types(Stream,MimeTypesList) ->
Line=
case io:get_line(Stream,'') of
@@ -1191,63 +775,7 @@ validate_logger([{error, Domain}]) when is_atom(Domain) ->
validate_logger(List) ->
throw({logger, List}).
-%%%=========================================================================
-%%% Deprecated remove in 19
-%%%=========================================================================
-is_directory(Directory) ->
- case file:read_file_info(Directory) of
- {ok,FileInfo} ->
- #file_info{type = Type, access = Access} = FileInfo,
- is_directory(Type,Access,FileInfo,Directory);
- {error,Reason} ->
- {error,Reason}
- end.
-is_directory(directory,read,_FileInfo,Directory) ->
- {ok,Directory};
-is_directory(directory,read_write,_FileInfo,Directory) ->
- {ok,Directory};
-is_directory(_Type,_Access,FileInfo,_Directory) ->
- {error,FileInfo}.
-
-is_file(File) ->
- case file:read_file_info(File) of
- {ok,FileInfo} ->
- #file_info{type = Type, access = Access} = FileInfo,
- is_file(Type,Access,FileInfo,File);
- {error,Reason} ->
- {error,Reason}
- end.
-is_file(regular,read,_FileInfo,File) ->
- {ok,File};
-is_file(regular,read_write,_FileInfo,File) ->
- {ok,File};
-is_file(_Type,_Access,FileInfo,_File) ->
- {error,FileInfo}.
-
-make_integer(String) ->
- case re:run(string:strip(String),"[0-9]+", [{capture, none}]) of
- match ->
- {ok, list_to_integer(string:strip(String))};
- nomatch ->
- {error, nomatch}
- end.
-
-clean(String) ->
- re:replace(String, "^[ \t\n\r\f]*|[ \t\n\r\f]*\$","",
- [{return,list}, global]).
-
-custom_clean(String,MoreBefore,MoreAfter) ->
- re:replace(String,
- "^[ \t\n\r\f"++MoreBefore++
- "]*|[ \t\n\r\f"++MoreAfter++"]*\$","",
- [{return,list}, global]).
-check_enum(_Enum,[]) ->
- {error, not_valid};
-check_enum(Enum,[Enum|_Rest]) ->
- {ok, list_to_atom(Enum)};
-check_enum(Enum, [_NotValid|Rest]) ->
- check_enum(Enum, Rest).
diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl
index 78b781aa96..bb048e2410 100644
--- a/lib/inets/src/http_server/httpd_example.erl
+++ b/lib/inets/src/http_server/httpd_example.erl
@@ -22,7 +22,8 @@
-export([print/3,
get/3,
- put/3,
+ put/3,
+ patch/3,
post/3,
yahoo/3,
test1/3,
@@ -84,6 +85,16 @@ do_put(Env,{Input,_Body}) ->
default(Env,Input);
do_put(Env,Input) ->
default(Env,Input).
+
+%% ------------------------------------------------------
+patch(SessionID, Env, Input) ->
+ mod_esi:deliver(SessionID, do_patch(Env, Input)).
+
+do_patch(Env,{Input,_Body}) ->
+ default(Env,Input);
+do_patch(Env,Input) ->
+ default(Env,Input).
+
%% ------------------------------------------------------
get_bin(SessionID, Env, Input) ->
Header = header(),
diff --git a/lib/inets/src/http_server/httpd_instance_sup.erl b/lib/inets/src/http_server/httpd_instance_sup.erl
index b77aa174ca..d5750f21ad 100644
--- a/lib/inets/src/http_server/httpd_instance_sup.erl
+++ b/lib/inets/src/http_server/httpd_instance_sup.erl
@@ -53,23 +53,8 @@ start_link([{_, _}| _] = Config, AcceptTimeout, Debug) ->
{error, Reason} ->
error_logger:error_report(Reason),
{stop, Reason}
- end;
-
-start_link(ConfigFile, AcceptTimeout, Debug) ->
- case file_2_config(ConfigFile) of
- {ok, ConfigList, Address, Port} ->
- Profile = proplists:get_value(profile, ConfigList, ?DEFAULT_PROFILE),
- Name = make_name(Address, Port, Profile),
- SupName = {local, Name},
- supervisor:start_link(SupName, ?MODULE,
- [ConfigFile, ConfigList, AcceptTimeout,
- Debug, Address, Port]);
- {error, Reason} ->
- error_logger:error_report(Reason),
- {stop, Reason}
end.
-
start_link([{_, _}| _] = Config, AcceptTimeout, ListenInfo, Debug) ->
case (catch httpd_conf:validate_properties(Config)) of
{ok, Config2} ->
@@ -84,23 +69,8 @@ start_link([{_, _}| _] = Config, AcceptTimeout, ListenInfo, Debug) ->
{error, Reason} ->
error_logger:error_report(Reason),
{stop, Reason}
- end;
-
-start_link(ConfigFile, AcceptTimeout, ListenInfo, Debug) ->
- case file_2_config(ConfigFile) of
- {ok, ConfigList, Address, Port} ->
- Profile = proplists:get_value(profile, ConfigList, ?DEFAULT_PROFILE),
- Name = make_name(Address, Port, Profile),
- SupName = {local, Name},
- supervisor:start_link(SupName, ?MODULE,
- [ConfigFile, ConfigList, AcceptTimeout,
- Debug, Address, Port, ListenInfo]);
- {error, Reason} ->
- error_logger:error_report(Reason),
- {stop, Reason}
end.
-
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
@@ -183,18 +153,3 @@ worker_spec(WorkerModule, Address, Port, Profile, ListenInfo, ConfigFile,
make_name(Address, Port, Profile) ->
httpd_util:make_name("httpd_instance_sup", Address, Port, Profile).
-
-file_2_config(ConfigFile) ->
- case httpd_conf:load(ConfigFile) of
- {ok, ConfigList} ->
- case (catch httpd_conf:validate_properties(ConfigList)) of
- {ok, Config} ->
- Address = proplists:get_value(bind_address, ConfigList),
- Port = proplists:get_value(port, ConfigList),
- {ok, Config, Address, Port};
- Error ->
- Error
- end;
- Error ->
- Error
- end.
diff --git a/lib/inets/src/http_server/httpd_manager.erl b/lib/inets/src/http_server/httpd_manager.erl
index 7cb39937e8..617e03a68f 100644
--- a/lib/inets/src/http_server/httpd_manager.erl
+++ b/lib/inets/src/http_server/httpd_manager.erl
@@ -364,25 +364,16 @@ handle_block(non_disturbing, Timeout,
handle_reload(undefined, #state{config_file = undefined} = State) ->
{continue, {error, undefined_config_file}, State};
-handle_reload(undefined, #state{config_file = ConfigFile} = State) ->
- case load_config(ConfigFile) of
- {ok, Config} ->
- do_reload(Config, State);
- {error, Reason} ->
- error_logger:error_msg("Bad config file: ~p~n", [Reason]),
- {continue, {error, Reason}, State}
+handle_reload(undefined, #state{config_file = ConfigFile, admin_state = AdminState} = State) ->
+ try httpd:reload_config(ConfigFile, AdminState) of
+ Result ->
+ Result
+ catch throw:Err ->
+ {config_file, Err, State}
end;
handle_reload(Config, State) ->
do_reload(Config, State).
-load_config(ConfigFile) ->
- case httpd_conf:load(ConfigFile) of
- {ok, Config} ->
- httpd_conf:validate_properties(Config);
- Error ->
- Error
- end.
-
do_reload(Config, #state{config_db = Db} = State) ->
case (catch check_constant_values(Db, Config)) of
ok ->
diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl
index e82b1c46e9..9c5544ff10 100644
--- a/lib/inets/src/http_server/httpd_request_handler.erl
+++ b/lib/inets/src/http_server/httpd_request_handler.erl
@@ -161,9 +161,7 @@ continue_init(Manager, ConfigDB, SocketType, Socket, Peername, Sockname,
max_keep_alive_request = NrOfRequest,
mfa = MFA,
chunk = chunk_start(MaxChunk)},
-
- http_transport:setopts(SocketType, Socket,
- [binary, {packet, 0}, {active, once}]),
+ setopts(Socket, SocketType, [binary, {packet, 0}, {active, once}]),
NewState = data_receive_counter(activate_request_timeout(State), httpd_util:lookup(ConfigDB, minimum_bytes_per_second, false)),
gen_server:enter_loop(?MODULE, [], NewState).
@@ -242,7 +240,7 @@ handle_info({Proto, Socket, Data},
NewState = handle_chunk(Module, Function, Args, State),
{noreply, NewState};
NewMFA ->
- http_transport:setopts(SockType, Socket, [{active, once}]),
+ setopts(Socket, SockType, [{active, once}]),
case NewDataSize of
undefined ->
{noreply, State#state{mfa = NewMFA}};
@@ -363,9 +361,7 @@ handle_msg({{continue, Chunk}, Module, Function, Args}, #state{chunk = {_, CbSta
handle_internal_chunk(State#state{chunk = {continue, CbState},
body = Chunk}, Module, Function, Args);
handle_msg({continue, Module, Function, Args}, #state{mod = ModData} = State) ->
- http_transport:setopts(ModData#mod.socket_type,
- ModData#mod.socket,
- [{active, once}]),
+ setopts(ModData#mod.socket, ModData#mod.socket_type, [{active, once}]),
{noreply, State#state{mfa = {Module, Function, Args}}};
handle_msg({last, Body}, #state{headers = Headers, chunk = {_, CbState}} = State) ->
NewHeaders = Headers#http_request_h{'content-length' = integer_to_list(size(Body))},
@@ -471,9 +467,7 @@ handle_body(#state{headers = Headers, body = Body,
"chunked" ->
try http_chunk:decode(Body, MaxBodySize, MaxHeaderSize) of
{Module, Function, Args} ->
- http_transport:setopts(ModData#mod.socket_type,
- ModData#mod.socket,
- [{active, once}]),
+ setopts(ModData#mod.socket, ModData#mod.socket_type, [{active, once}]),
{noreply, State#state{mfa =
{Module, Function, Args},
chunk = chunk_start(MaxChunk)}};
@@ -501,18 +495,14 @@ handle_body(#state{headers = Headers, body = Body,
case httpd_request:body_chunk_first(Body, Length, MaxChunk) of
%% This is the case that the we need more data to complete
%% the body but chunking to the mod_esi user is not enabled.
- {Module, add_chunk = Function, Args} ->
- http_transport:setopts(ModData#mod.socket_type,
- ModData#mod.socket,
- [{active, once}]),
+ {Module, add_chunk = Function, Args} ->
+ setopts(ModData#mod.socket, ModData#mod.socket_type, [{active, once}]),
{noreply, State#state{mfa =
{Module, Function, Args}}};
%% Chunking to mod_esi user is enabled
{ok, {continue, Module, Function, Args}} ->
- http_transport:setopts(ModData#mod.socket_type,
- ModData#mod.socket,
- [{active, once}]),
- {noreply, State#state{mfa =
+ setopts(ModData#mod.socket, ModData#mod.socket_type, [{active, once}]),
+ {noreply, State#state{mfa =
{Module, Function, Args}}};
{ok, {{continue, Chunk}, Module, Function, Args}} ->
handle_internal_chunk(State#state{chunk = chunk_start(MaxChunk),
@@ -588,7 +578,7 @@ handle_chunk(http_chunk = Module, decode_data = Function,
socket = Socket} = ModData} = State) ->
{continue, NewCbState} = httpd_response:handle_continuation(ModData#mod{entity_body =
{continue, BodySoFar, CbState}}),
- http_transport:setopts(SockType, Socket, [{active, once}]),
+ setopts(Socket, SockType, [{active, once}]),
State#state{chunk = {continue, NewCbState}, mfa = {Module, Function, [ChunkSize, TotalChunk, {MaxBodySize, <<>>, 0, MaxHeaderSize}]}};
handle_chunk(http_chunk = Module, decode_size = Function,
@@ -597,11 +587,11 @@ handle_chunk(http_chunk = Module, decode_size = Function,
mod = #mod{socket_type = SockType,
socket = Socket} = ModData} = State) ->
{continue, NewCbState} = httpd_response:handle_continuation(ModData#mod{entity_body = {continue, BodySoFar, CbState}}),
- http_transport:setopts(SockType, Socket, [{active, once}]),
+ setopts(Socket, SockType, [{active, once}]),
State#state{chunk = {continue, NewCbState}, mfa = {Module, Function, [Data, HexList, 0, {MaxBodySize, <<>>, 0, MaxHeaderSize}]}};
handle_chunk(Module, Function, Args, #state{mod = #mod{socket_type = SockType,
socket = Socket}} = State) ->
- http_transport:setopts(SockType, Socket, [{active, once}]),
+ setopts(Socket, SockType, [{active, once}]),
State#state{mfa = {Module, Function, Args}}.
handle_internal_chunk(#state{chunk = {ChunkState, CbState}, body = Chunk,
@@ -611,7 +601,7 @@ handle_internal_chunk(#state{chunk = {ChunkState, CbState}, body = Chunk,
{continue, NewCbState} = httpd_response:handle_continuation(ModData#mod{entity_body = Bodychunk}),
case Args of
[<<>> | _] ->
- http_transport:setopts(SockType, Socket, [{active, once}]),
+ setopts(Socket, SockType, [{active, once}]),
{noreply, State#state{chunk = {continue, NewCbState}, mfa = {Module, Function, Args}}};
_ ->
handle_info({dummy, Socket, <<>>}, State#state{chunk = {continue, NewCbState},
@@ -675,8 +665,7 @@ handle_next_request(#state{mod = #mod{connection = true} = ModData,
case Data of
<<>> ->
- http_transport:setopts(ModData#mod.socket_type,
- ModData#mod.socket, [{active, once}]),
+ setopts(ModData#mod.socket, ModData#mod.socket_type, [{active, once}]),
{noreply, NewState};
_ ->
handle_info({dummy, ModData#mod.socket, Data}, NewState)
@@ -751,3 +740,11 @@ body_chunk(first, _, Chunk) ->
{first, Chunk};
body_chunk(ChunkState, CbState, Chunk) ->
{ChunkState, Chunk, CbState}.
+
+setopts(Socket, SocketType, Options) ->
+ case http_transport:setopts(SocketType, Socket, Options) of
+ ok ->
+ ok;
+ {error, _} -> %% inet can return einval instead of closed
+ self() ! {http_transport:close_tag(SocketType), Socket}
+ end.
diff --git a/lib/inets/src/http_server/httpd_sup.erl b/lib/inets/src/http_server/httpd_sup.erl
index d1216e01b0..76fa15f991 100644
--- a/lib/inets/src/http_server/httpd_sup.erl
+++ b/lib/inets/src/http_server/httpd_sup.erl
@@ -135,23 +135,18 @@ child_spec(HttpdService) ->
httpd_child_spec(Config, AcceptTimeout, Debug).
httpd_config([Value| _] = Config) when is_tuple(Value) ->
- case proplists:get_value(file, Config) of
- undefined ->
- case proplists:get_value(proplist_file, Config) of
- undefined ->
- httpd_conf:validate_properties(Config);
- File ->
- try file:consult(File) of
- {ok, [PropList]} ->
- httpd_conf:validate_properties(PropList)
- catch
- exit:_ ->
- throw({error,
- {could_not_consult_proplist_file, File}})
- end
- end;
- File ->
- {ok, File}
+ case proplists:get_value(proplist_file, Config) of
+ undefined ->
+ httpd_conf:validate_properties(Config);
+ File ->
+ try file:consult(File) of
+ {ok, [PropList]} ->
+ httpd_conf:validate_properties(PropList)
+ catch
+ exit:_ ->
+ throw({error,
+ {could_not_consult_proplist_file, File}})
+ end
end.
httpd_child_spec([Value| _] = Config, AcceptTimeout, Debug)
@@ -159,30 +154,8 @@ httpd_child_spec([Value| _] = Config, AcceptTimeout, Debug)
Address = proplists:get_value(bind_address, Config, any),
Port = proplists:get_value(port, Config, 80),
Profile = proplists:get_value(profile, Config, ?DEFAULT_PROFILE),
- httpd_child_spec(Config, AcceptTimeout, Debug, Address, Port, Profile);
-
-%% In this case the AcceptTimeout and Debug will only have default values...
-httpd_child_spec(ConfigFile, AcceptTimeoutDef, DebugDef) ->
- case httpd_conf:load(ConfigFile) of
- {ok, ConfigList} ->
- case (catch httpd_conf:validate_properties(ConfigList)) of
- {ok, Config} ->
- Address = proplists:get_value(bind_address, Config, any),
- Port = proplists:get_value(port, Config, 80),
- Profile = proplists:get_value(profile, Config, ?DEFAULT_PROFILE),
- AcceptTimeout =
- proplists:get_value(accept_timeout, Config,
- AcceptTimeoutDef),
- Debug =
- proplists:get_value(debug, Config, DebugDef),
- httpd_child_spec([{file, ConfigFile} | Config],
- AcceptTimeout, Debug, Address, Port, Profile);
- Error ->
- Error
- end;
- Error ->
- Error
- end.
+ httpd_child_spec(Config, AcceptTimeout, Debug, Address, Port, Profile).
+
httpd_child_spec(Config, AcceptTimeout, Debug, Addr, Port, Profile) ->
case get_fd(Port) of
diff --git a/lib/inets/src/http_server/httpd_util.erl b/lib/inets/src/http_server/httpd_util.erl
index 05cff30243..4ef2d8b34c 100644
--- a/lib/inets/src/http_server/httpd_util.erl
+++ b/lib/inets/src/http_server/httpd_util.erl
@@ -33,6 +33,8 @@
dir_validate/2, file_validate/2, mime_type_validate/1,
mime_types_validate/1, custom_date/0, error_log/2]).
+-compile({nowarn_deprecated_function, [{http_uri, encode, 1}]}).
+-compile({nowarn_deprecated_function, [{http_uri, decode, 1}]}).
-export([encode_hex/1, decode_hex/1]).
-include_lib("kernel/include/file.hrl").
-include_lib("inets/include/httpd.hrl").
@@ -167,7 +169,7 @@ reason_phrase(_) -> "Internal Server Error".
%% message
message(301,URL,_) ->
- "The document has moved <A HREF=\""++ html_encode(uri_string:normalize(URL)) ++"\">here</A>.";
+ "The document has moved <A HREF=\""++ html_encode(URL) ++"\">here</A>.";
message(304, _URL,_) ->
"The document has not been changed.";
message(400, none, _) ->
@@ -184,11 +186,11 @@ browser doesn't understand how to supply
the credentials required.";
message(403,RequestURI,_) ->
"You don't have permission to access " ++
- html_encode(uri_string:normalize(RequestURI)) ++
+ html_encode(RequestURI) ++
" on this server.";
message(404,RequestURI,_) ->
"The requested URL " ++
- html_encode(uri_string:normalize(RequestURI)) ++
+ html_encode(RequestURI) ++
" was not found on this server.";
message(408, Timeout, _) ->
Timeout;
@@ -203,7 +205,7 @@ message(500,_,ConfigDB) ->
"The server encountered an internal error or "
"misconfiguration and was unable to complete "
"your request.<P>Please contact the server administrator "
- ++ html_encode(ServerAdmin) ++
+ ++ html_encode(ServerAdmin) ++
", and inform them of the time the error occurred "
"and anything you might have done that may have caused the error.";
@@ -212,12 +214,12 @@ message(501,{Method, RequestURI, HTTPVersion}, _ConfigDB) ->
is_atom(Method) ->
atom_to_list(Method) ++
" to " ++
- html_encode(uri_string:normalize(RequestURI)) ++
+ html_encode(RequestURI) ++
" (" ++ HTTPVersion ++ ") not supported.";
is_list(Method) ->
Method ++
" to " ++
- html_encode(RequestURI) ++
+ html_encode(RequestURI) ++
" (" ++ HTTPVersion ++ ") not supported."
end;
@@ -406,10 +408,11 @@ flatlength([_H|T],L) ->
flatlength([],L) ->
L.
-%% split_path
+%% split_path, URI has been decoded once when validate
+%% and should only be decoded once(RFC3986, 2.4).
split_path(URI) ->
- case uri_string:normalize(URI, [return_map]) of
+ case uri_string:parse(URI) of
#{fragment := Fragment,
path := Path,
query := Query} ->
@@ -439,11 +442,12 @@ split_path([$/|Rest],SoFar) ->
split_path([C|Rest],SoFar) ->
split_path(Rest,[C|SoFar]).
-%% split_script_path
+%% split_script_path, URI has been decoded once when validate
+%% and should only be decoded once(RFC3986, 2.4).
split_script_path(URI) ->
- case uri_string:normalize(URI, [return_map]) of
+ case uri_string:parse(URI) of
#{fragment := _Fragment,
path := _Path,
query := _Query} ->
diff --git a/lib/inets/src/http_server/mod_actions.erl b/lib/inets/src/http_server/mod_actions.erl
index b5449f20ee..dcc2b87961 100644
--- a/lib/inets/src/http_server/mod_actions.erl
+++ b/lib/inets/src/http_server/mod_actions.erl
@@ -19,7 +19,7 @@
%%
%%
-module(mod_actions).
--export([do/1,load/2, store/2]).
+-export([do/1, store/2]).
-include("httpd.hrl").
-include("httpd_internal.hrl").
@@ -78,23 +78,6 @@ script(RequestURI, Method, [_ | Rest]) ->
%% Configuration
%%
-%% load
-
-load("Action "++ Action, []) ->
- case re:split(Action, " ", [{return, list}]) of
- [MimeType, CGIScript] ->
- {ok,[],{action, {MimeType, CGIScript}}};
- _ ->
- {error,?NICE(string:strip(Action)++" is an invalid Action")}
- end;
-load("Script " ++ Script,[]) ->
- case re:split(Script, " ", [{return, list}]) of
- [Method, CGIScript] ->
- {ok,[],{script, {Method, CGIScript}}};
- _ ->
- {error,?NICE(string:strip(Script)++" is an invalid Script")}
- end.
-
store({action, {MimeType, CGIScript}} = Conf, _) when is_list(MimeType),
is_list(CGIScript) ->
{ok, Conf};
diff --git a/lib/inets/src/http_server/mod_alias.erl b/lib/inets/src/http_server/mod_alias.erl
index fac59ab93c..cb2d2c1ef4 100644
--- a/lib/inets/src/http_server/mod_alias.erl
+++ b/lib/inets/src/http_server/mod_alias.erl
@@ -24,7 +24,6 @@
real_name/3,
real_script_name/3,
default_index/2,
- load/2,
store/2,
path/3]).
@@ -99,13 +98,12 @@ get_protocol(_) ->
%% real_name
real_name(ConfigDB, RequestURI, []) ->
- DocumentRoot = which_document_root(ConfigDB),
+ {Prefix, DocumentRoot} = which_document_root(ConfigDB),
RealName = DocumentRoot ++ RequestURI,
{ShortPath, _AfterPath} = httpd_util:split_path(RealName),
{Path, AfterPath} =
httpd_util:split_path(default_index(ConfigDB, RealName)),
- {ShortPath, Path, AfterPath};
-
+ {Prefix ++ ShortPath, Prefix ++ Path, AfterPath};
real_name(ConfigDB, RequestURI, [{MP,Replacement}| _] = Aliases)
when element(1, MP) =:= re_pattern ->
case longest_match(Aliases, RequestURI) of
@@ -197,64 +195,33 @@ append_index(RealName, [Index | Rest]) ->
%% path
-path(Data, ConfigDB, RequestURI) ->
+path(Data, ConfigDB, RequestURI0) ->
case proplists:get_value(real_name, Data) of
undefined ->
- DocumentRoot = which_document_root(ConfigDB),
- {Path, _AfterPath} =
- httpd_util:split_path(DocumentRoot ++ RequestURI),
- Path;
+ {Prefix, DocumentRoot} = which_document_root(ConfigDB),
+ RequestURI = percent_decode_path(RequestURI0),
+ {Path, _AfterPath} =
+ httpd_util:split_path(DocumentRoot ++ RequestURI),
+ Prefix ++ Path;
{Path, _AfterPath} ->
Path
end.
+percent_decode_path(InitPath) ->
+ case uri_string:percent_decode(InitPath) of
+ {error, _} ->
+ InitPath;
+ Path0 -> %% Protect against vulnerabilities
+ case uri_string:normalize(Path0) of
+ {error, _, _} ->
+ InitPath;
+ Path ->
+ Path
+ end
+ end.
%%
%% Configuration
%%
-
-%% load
-
-load("DirectoryIndex " ++ DirectoryIndex, []) ->
- DirectoryIndexes = re:split(DirectoryIndex," ", [{return, list}]),
- {ok,[], {directory_index, DirectoryIndexes}};
-load("Alias " ++ Alias, []) ->
- case re:split(Alias," ", [{return, list}]) of
- [FakeName, RealName] ->
- {ok,[],{alias,{FakeName,RealName}}};
- _ ->
- {error,?NICE(string:strip(Alias)++" is an invalid Alias")}
- end;
-load("ReWrite " ++ Rule, Acc) ->
- load_re_write(Rule, Acc, "ReWrite", re_write);
-load("ScriptAlias " ++ ScriptAlias, []) ->
- case re:split(ScriptAlias, " ", [{return, list}]) of
- [FakeName, RealName] ->
- %% Make sure the path always has a trailing slash..
- RealName1 = filename:join(filename:split(RealName)),
- {ok, [], {script_alias, {FakeName, RealName1++"/"}}};
- _ ->
- {error, ?NICE(string:strip(ScriptAlias)++
- " is an invalid ScriptAlias")}
- end;
-load("ScriptReWrite " ++ Rule, Acc) ->
- load_re_write(Rule, Acc, "ScriptReWrite", script_re_write).
-
-load_re_write(Rule0, Acc, Type, Tag) ->
- case lists:dropwhile(
- fun ($\s) -> true; ($\t) -> true; (_) -> false end,
- Rule0) of
- "" ->
- {error, ?NICE(string:strip(Rule0)++" is an invalid "++Type)};
- Rule ->
- case string:chr(Rule, $\s) of
- 0 ->
- {ok, Acc, {Tag, {Rule, ""}}};
- N ->
- {Re, [_|Replacement]} = lists:split(N-1, Rule),
- {ok, Acc, {Tag, {Re, Replacement}}}
- end
- end.
-
store({directory_index, Value} = Conf, _) when is_list(Value) ->
case is_directory_index_list(Value) of
true ->
@@ -315,7 +282,13 @@ which_port(ConfigDB) ->
httpd_util:lookup(ConfigDB, port, 80).
which_document_root(ConfigDB) ->
- httpd_util:lookup(ConfigDB, document_root, "").
+ Root = httpd_util:lookup(ConfigDB, document_root, ""),
+ case string:tokens(Root, ":") of
+ [Prefix, Path] ->
+ {Prefix ++ ":", Path};
+ [Path] ->
+ {"", Path}
+ end.
which_directory_index(ConfigDB) ->
httpd_util:lookup(ConfigDB, directory_index, []).
diff --git a/lib/inets/src/http_server/mod_auth.erl b/lib/inets/src/http_server/mod_auth.erl
index fba94df176..89d6e966cb 100644
--- a/lib/inets/src/http_server/mod_auth.erl
+++ b/lib/inets/src/http_server/mod_auth.erl
@@ -22,7 +22,7 @@
%% The functions that the webbserver call on startup stop
%% and when the server traverse the modules.
--export([do/1, load/2, store/2, remove/1]).
+-export([do/1, store/2, remove/1]).
%% User entries to the gen-server.
-export([add_user/2, add_user/5, add_user/6,
@@ -127,97 +127,6 @@ do(Info) ->
%% will be returned as a ConfigList and the context will return to the
%% state it was previously.
-load("<Directory " ++ Directory,[]) ->
- Dir = string:strip(string:strip(Directory),right, $>),
- {ok,[{directory, {Dir, [{path, Dir}]}}]};
-load(eof,[{directory, {Directory, _DirData}}|_]) ->
- {error, ?NICE("Premature end-of-file in "++ Directory)};
-
-load("AuthName " ++ AuthName, [{directory, {Directory, DirData}}|Rest]) ->
- {ok, [{directory, {Directory,
- [{auth_name, string:strip(AuthName)} | DirData]}}
- | Rest ]};
-load("AuthUserFile " ++ AuthUserFile0,
- [{directory, {Directory, DirData}}|Rest]) ->
- AuthUserFile = string:strip(AuthUserFile0),
- {ok, [{directory, {Directory,
- [{auth_user_file, AuthUserFile}|DirData]}} | Rest ]};
-load("AuthGroupFile " ++ AuthGroupFile0,
- [{directory, {Directory, DirData}}|Rest]) ->
- AuthGroupFile = string:strip(AuthGroupFile0),
- {ok,[{directory, {Directory,
- [{auth_group_file, AuthGroupFile}|DirData]}} | Rest]};
-
-load("AuthAccessPassword " ++ AuthAccessPassword0,
- [{directory, {Directory, DirData}}|Rest]) ->
- AuthAccessPassword = string:strip(AuthAccessPassword0),
- {ok,[{directory, {Directory,
- [{auth_access_password, AuthAccessPassword}|DirData]}} | Rest]};
-
-load("AuthDBType " ++ Type,
- [{directory, {Dir, DirData}}|Rest]) ->
- case string:strip(Type) of
- "plain" ->
- {ok, [{directory, {Dir, [{auth_type, plain}|DirData]}} | Rest ]};
- "mnesia" ->
- {ok, [{directory, {Dir, [{auth_type, mnesia}|DirData]}} | Rest ]};
- "dets" ->
- {ok, [{directory, {Dir, [{auth_type, dets}|DirData]}} | Rest ]};
- _ ->
- {error, ?NICE(string:strip(Type)++" is an invalid AuthDBType")}
- end;
-
-load("require " ++ Require,[{directory, {Directory, DirData}}|Rest]) ->
- case re:split(Require," ", [{return, list}]) of
- ["user" | Users] ->
- {ok,[{directory, {Directory,
- [{require_user,Users}|DirData]}} | Rest]};
- ["group"|Groups] ->
- {ok,[{directory, {Directory,
- [{require_group,Groups}|DirData]}} | Rest]};
- _ ->
- {error,?NICE(string:strip(Require) ++" is an invalid require")}
- end;
-
-load("allow " ++ Allow,[{directory, {Directory, DirData}}|Rest]) ->
- case re:split(Allow," ", [{return, list}]) of
- ["from","all"] ->
- {ok,[{directory, {Directory,
- [{allow_from,all}|DirData]}} | Rest]};
- ["from"|Hosts] ->
- {ok,[{directory, {Directory,
- [{allow_from,Hosts}|DirData]}} | Rest]};
- _ ->
- {error,?NICE(string:strip(Allow) ++" is an invalid allow")}
- end;
-
-load("deny " ++ Deny,[{directory, {Directory, DirData}}|Rest]) ->
- case re:split(Deny," ", [{return, list}]) of
- ["from", "all"] ->
- {ok,[{{directory, Directory,
- [{deny_from, all}|DirData]}} | Rest]};
- ["from"|Hosts] ->
- {ok,[{{directory, Directory,
- [{deny_from, Hosts}|DirData]}} | Rest]};
- _ ->
- {error,?NICE(string:strip(Deny) ++" is an invalid deny")}
- end;
-
-load("</Directory>",[{directory, {Directory, DirData}}|Rest]) ->
- {ok, Rest, {directory, {Directory, DirData}}};
-
-load("AuthMnesiaDB " ++ AuthMnesiaDB,
- [{directory, {Dir, DirData}}|Rest]) ->
- case string:strip(AuthMnesiaDB) of
- "On" ->
- {ok,[{directory, {Dir,[{auth_type,mnesia}|DirData]}}|Rest]};
- "Off" ->
- {ok,[{directory, {Dir,[{auth_type,plain}|DirData]}}|Rest]};
- _ ->
- {error, ?NICE(string:strip(AuthMnesiaDB) ++
- " is an invalid AuthMnesiaDB")}
- end.
-
store({directory, {Directory, DirData}}, ConfigList)
when is_list(Directory) andalso is_list(DirData) ->
try directory_config_check(Directory, DirData) of
diff --git a/lib/inets/src/http_server/mod_browser.erl b/lib/inets/src/http_server/mod_browser.erl
deleted file mode 100644
index 1e8f860746..0000000000
--- a/lib/inets/src/http_server/mod_browser.erl
+++ /dev/null
@@ -1,251 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%
-%% ----------------------------------------------------------------------
-%%
-%% Browsers sends a string to the webbserver
-%% to identify themsevles. They are a bit nasty
-%% since the only thing that the specification really
-%% is strict about is that they shall be short
-%% some axamples:
-%%
-%% Netscape Mozilla/4.75 [en] (X11; U; SunOS 5.8 sun4u)
-%% Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.1) Gecko/20020823 Netscape/7.0
-%% Mozilla Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.1) Gecko/20020827
-%% Safari Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85 (KHTML, like Gecko) Safari/85
-%% IE5 Mozilla/4.0 (compatible; MSIE 5.0; SP1B; SunOS 5.8 sun4u; X11)
-%% Lynx Lynx/2.8.3rel.1 libwww-FM/2.142
-%%
-%% ----------------------------------------------------------------------
-
--module(mod_browser).
-
--export([do/1, test/0, getBrowser/1]).
-
-%% Remember that the order of the mozilla browsers are
-%% important since some browsers include others to behave
-%% as they were something else
--define(MOZILLA_BROWSERS,[{netscape, "netscape"},
- {opera, "opera"},
- {msie, "msie"},
- {safari, "safari"},
- {mozilla, "rv:"}]). % fallback, must be last
-
-
-%% If your operatingsystem is not recognized add it to this list.
--define(OPERATIVE_SYSTEMS,[{win3x, ["win16", "windows 3", "windows 16-bit"]},
- {win95, ["win95", "windows 95"]},
- {win98, ["win98", "windows 98"]},
- {winnt, ["winnt", "windows nt"]},
- {win2k, ["nt 5"]},
- {sunos4, ["sunos 4"]},
- {sunos5, ["sunos 5"]},
- {sun, ["sunos"]},
- {aix, ["aix"]},
- {linux, ["linux"]},
- {sco, ["sco", "unix_sv"]},
- {freebsd,["freebsd"]},
- {bsd, ["bsd"]},
- {macosx, ["mac os x"]}]).
-
--define(LYNX, lynx).
--define(MOZILLA, mozilla).
--define(EMACS, emacs).
--define(STAROFFICE, soffice).
--define(MOSAIC, mosaic).
--define(NETSCAPE, netscape).
--define(SAFARU, safari).
--define(UNKOWN, unknown).
-
--include("httpd.hrl").
-
--define(VMODULE,"BROWSER").
-
-do(Info) ->
- case proplists:get_value(status, Info#mod.data) of
- {_StatusCode, _PhraseArgs, _Reason} ->
- {proceed,Info#mod.data};
- undefined ->
- Browser = getBrowser1(Info),
- {proceed,[{'user-agent', Browser}|Info#mod.data]}
- end.
-
-getBrowser1(Info) ->
- PHead = Info#mod.parsed_header,
- case proplists:get_value("user-agent", PHead) of
- undefined ->
- undefined;
- AgentString ->
- getBrowser(AgentString)
- end.
-
-getBrowser(AgentString) ->
- LAgentString = http_util:to_lower(AgentString),
- case re:run(LAgentString,"^[^ ]*", [{capture, first}]) of
- {match,[{Start,Length}]} ->
- Browser = lists:sublist(LAgentString,Start+1,Length),
- case browserType(Browser) of
- {mozilla,Vsn} ->
- {getMozilla(LAgentString,
- ?MOZILLA_BROWSERS,{?NETSCAPE,Vsn}),
- operativeSystem(LAgentString)};
- AnyBrowser ->
- {AnyBrowser,operativeSystem(LAgentString)}
- end;
- nomatch ->
- browserType(LAgentString)
- end.
-
-browserType([$l,$y,$n,$x|Version]) ->
- {?LYNX,browserVersion(Version)};
-browserType([$m,$o,$z,$i,$l,$l,$a|Version]) ->
- {?MOZILLA,browserVersion(Version)};
-browserType([$e,$m,$a,$c,$s|Version]) ->
- {?EMACS,browserVersion(Version)};
-browserType([$s,$t,$a,$r,$o,$f,$f,$i,$c,$e|Version]) ->
- {?STAROFFICE,browserVersion(Version)};
-browserType([$m,$o,$s,$a,$i,$c|Version]) ->
- {?MOSAIC,browserVersion(Version)};
-browserType(_Unknown) ->
- unknown.
-
-
-browserVersion([$/|VsnString]) ->
- case catch list_to_float(VsnString) of
- Number when is_float(Number) ->
- Number;
- _Whatever ->
- case string:span(VsnString,"1234567890.") of
- 0 ->
- unknown;
- VLength ->
- Vsn = string:substr(VsnString,1,VLength),
- case string:tokens(Vsn,".") of
- [Number] ->
- list_to_float(Number++".0");
- [Major,Minor|_MinorMinor] ->
- list_to_float(Major++"."++Minor)
- end
- end
- end;
-browserVersion(VsnString) ->
- browserVersion([$/|VsnString]).
-
-operativeSystem(OpString) ->
- operativeSystem(OpString, ?OPERATIVE_SYSTEMS).
-
-operativeSystem(_OpString,[]) ->
- unknown;
-operativeSystem(OpString,[{RetVal,RegExps}|Rest]) ->
- case controlOperativeSystem(OpString,RegExps) of
- true ->
- RetVal;
- _ ->
- operativeSystem(OpString,Rest)
- end.
-
-controlOperativeSystem(_OpString,[]) ->
- false;
-controlOperativeSystem(OpString,[Regexp|Regexps]) ->
- case re:run(OpString,Regexp, [{capture, none}]) of
- match ->
- true;
- nomatch ->
- controlOperativeSystem(OpString,Regexps)
- end.
-
-
-%% OK this is ugly but thats the only way since
-%% all browsers dont conform to the name/vsn standard
-%% First we check if it is one of the browsers that
-%% are not the default mozillaborwser against the regexp
-%% for the different browsers. if no match, it is a mozilla
-%% browser i.e opera, netscape, ie or safari
-
-getMozilla(_AgentString,[],Default) ->
- Default;
-getMozilla(AgentString,[{Agent,AgentRegExp}|Rest],Default) ->
- case re:run(AgentString,AgentRegExp, [{capture, none}]) of
- match ->
- {Agent,getMozVersion(AgentString,AgentRegExp)};
- nomatch ->
- getMozilla(AgentString,Rest,Default)
- end.
-
-getMozVersion(AgentString, AgentRegExp) ->
- case re:run(AgentString,AgentRegExp++"[0-9\.\ \/]*",
- [{capture, first}]) of
- {match, [{Start,Length}]} when length(AgentRegExp) < Length ->
- %% Ok we got the number split it out
- RealStart = Start+1+length(AgentRegExp),
- RealLength = Length-length(AgentRegExp),
- VsnString = string:substr(AgentString,RealStart,RealLength),
- %% case string:strip(VsnString,both,$\ ) of
- case strip(VsnString) of
- [] ->
- unknown;
- [Y1,Y2,Y3,Y4,M1,M2,D1,D2] = DateVsn when
- Y1 =< $9, Y1 >= $0,
- Y2 =< $9, Y2 >= $0,
- Y3 =< $9, Y3 >= $0,
- Y4 =< $9, Y4 >= $0,
- M1 =< $9, M1 >= $0,
- M2 =< $9, M2 >= $0,
- D1 =< $9, D1 >= $0,
- D2 =< $9, D2 >= $0 ->
- list_to_integer(DateVsn);
- Vsn ->
- case string:tokens(Vsn,".") of
- [Number]->
- list_to_float(Number++".0");
- [Major,Minor|Rev] ->
- V = lists:flatten([Major,".",Minor,Rev]),
- list_to_float(V)
- end
- end;
- nomatch ->
- unknown
- end.
-
-strip(VsnString) ->
- strip2(strip1(VsnString)).
-
-strip1(VsnString) ->
- string:strip(VsnString,both,$\ ).
-
-strip2(VsnString) ->
- string:strip(VsnString,both,$/ ).
-
-test()->
- test("Mozilla/4.75 [en] (X11; U; SunOS 5.8 sun4u)"),
- test("Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.1) Gecko/20020823 Netscape/7.0"),
- test("Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.1) Gecko/20020827"),
- test("Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4) Gecko/20020827"),
- test("Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85 (KHTML, like Gecko) Safari/85"),
- test("Mozilla/4.0 (compatible; MSIE 5.0; SP1B; SunOS 5.8 sun4u; X11)"),
- test("Lynx/2.8.3rel.1 libwww-FM/2.142"),
- ok.
-
-test(Str) ->
- Browser = getBrowser(Str),
- io:format("~n--------------------------------------------------------~n"),
- io:format("~p",[Browser]),
- io:format("~n--------------------------------------------------------~n").
-
diff --git a/lib/inets/src/http_server/mod_dir.erl b/lib/inets/src/http_server/mod_dir.erl
index ad2ee1d994..3d287c4b18 100644
--- a/lib/inets/src/http_server/mod_dir.erl
+++ b/lib/inets/src/http_server/mod_dir.erl
@@ -100,6 +100,17 @@ dir(Path,RequestURI,ConfigDB) ->
file:format_error(Reason))}
end.
+encode_html_entity(FileName) ->
+ Enc = fun($&) -> "&amp;";
+ ($<) -> "&lt;";
+ ($>) -> "&gt;";
+ ($") -> "&quot;";
+ ($') -> "&#x27;";
+ ($/) -> "&#x2F;";
+ (C) -> C
+ end,
+ unicode:characters_to_list([Enc(C) || C <- FileName]).
+
%% header
header(Path,RequestURI) ->
@@ -124,7 +135,7 @@ format(Path,RequestURI) ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\">"
" <A HREF=\"~s\">Parent directory</A> "
" ~2.2.0w-~s-~w ~2.2.0w:~2.2.0w -\n",
- [icon(back),"DIR",RequestURI,Day,
+ [icon(back),"DIR",get_href(RequestURI),Day,
httpd_util:month(Month),Year,Hour,Minute]).
%% body
@@ -135,8 +146,9 @@ body(Path, RequestURI, ConfigDB, [Entry | Rest]) ->
[format(Path, RequestURI, ConfigDB, Entry)|
body(Path, RequestURI, ConfigDB, Rest)].
-format(Path,RequestURI,ConfigDB,Entry) ->
- case file:read_file_info(Path++"/"++Entry) of
+format(Path,RequestURI,ConfigDB,InitEntry) ->
+ Entry = encode_html_entity(InitEntry),
+ case file:read_file_info(Path++"/"++InitEntry) of
{ok,FileInfo} when FileInfo#file_info.type == directory ->
{{Year, Month, Day},{Hour, Minute, _Second}} =
FileInfo#file_info.mtime,
@@ -145,18 +157,18 @@ format(Path,RequestURI,ConfigDB,Entry) ->
EntryLength > 21 ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\"> "
"<A HREF=\"~s\">~-21.s..</A>"
- "~2.2.0w-~s-~w ~2.2.0w:~2.2.0w"
+ "~*.*c~2.2.0w-~s-~w ~2.2.0w:~2.2.0w"
" -\n", [icon(folder),"DIR",
- RequestURI++"/"++Entry++"/",
- Entry,
+ get_href(RequestURI++"/"++InitEntry++"/"),
+ Entry, 23-21, 23-21, $ ,
Day, httpd_util:month(Month),
Year,Hour,Minute]);
true ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\">"
" <A HREF=\"~s\">~s</A>~*.*c~2.2.0"
"w-~s-~w ~2.2.0w:~2.2.0w -\n",
- [icon(folder),"DIR",RequestURI ++ "/" ++
- Entry ++ "/",Entry,
+ [icon(folder),"DIR",get_href(RequestURI ++ "/" ++
+ InitEntry ++ "/"),Entry,
23-EntryLength,23-EntryLength,$ ,Day,
httpd_util:month(Month),Year,Hour,Minute])
end;
@@ -169,10 +181,10 @@ format(Path,RequestURI,ConfigDB,Entry) ->
if
EntryLength > 21 ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\">"
- " <A HREF=\"~s\">~-21.s..</A>~2.2.0"
+ " <A HREF=\"~s\">~-21.s..</A>~*.*c~2.2.0"
"w-~s-~w ~2.2.0w:~2.2.0w~8wk ~s\n",
- [icon(Suffix, MimeType), Suffix, RequestURI
- ++"/"++Entry, Entry,Day,
+ [icon(Suffix, MimeType), Suffix, get_href(RequestURI
+ ++"/"++InitEntry), Entry, 23-21, 23-21, $ , Day,
httpd_util:month(Month),Year,Hour,Minute,
trunc(FileInfo#file_info.size/1024+1),
MimeType]);
@@ -180,8 +192,8 @@ format(Path,RequestURI,ConfigDB,Entry) ->
io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\"> "
"<A HREF=\"~s\">~s</A>~*.*c~2.2.0w-~s-~w"
" ~2.2.0w:~2.2.0w~8wk ~s\n",
- [icon(Suffix, MimeType), Suffix, RequestURI
- ++ "/" ++ Entry, Entry, 23-EntryLength,
+ [icon(Suffix, MimeType), Suffix, get_href(RequestURI
+ ++ "/" ++ InitEntry), Entry, 23-EntryLength,
23-EntryLength, $ ,Day,
httpd_util:month(Month),Year,Hour,Minute,
trunc(FileInfo#file_info.size/1024+1),
@@ -191,6 +203,37 @@ format(Path,RequestURI,ConfigDB,Entry) ->
""
end.
+get_href(URI) ->
+ percent_encode(URI).
+
+percent_encode(URI) when is_list(URI) ->
+ Reserved = reserved(),
+ lists:append([uri_encode(Char, Reserved) || Char <- URI]);
+percent_encode(URI) when is_binary(URI) ->
+ Reserved = reserved(),
+ << <<(uri_encode_binary(Char, Reserved))/binary>> || <<Char>> <= URI >>.
+
+reserved() ->
+ sets:from_list([$;, $:, $@, $&, $=, $+, $,, $/, $?,
+ $#, $[, $], $<, $>, $\", ${, $}, $|, %"
+ $\\, $', $^, $%, $ ]).
+
+uri_encode(Char, Reserved) ->
+ case sets:is_element(Char, Reserved) of
+ true ->
+ [ $% | http_util:integer_to_hexlist(Char)];
+ false ->
+ [Char]
+ end.
+
+uri_encode_binary(Char, Reserved) ->
+ case sets:is_element(Char, Reserved) of
+ true ->
+ << $%, (integer_to_binary(Char, 16))/binary >>;
+ false ->
+ <<Char>>
+ end.
+
%% footer
footer(Path,FileList) ->
diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl
index c1331e1df0..3da503a5f2 100644
--- a/lib/inets/src/http_server/mod_esi.erl
+++ b/lib/inets/src/http_server/mod_esi.erl
@@ -27,7 +27,7 @@
-export([deliver/2]).
%% Callback API
--export([do/1, load/2, store/2]).
+-export([do/1, store/2]).
-include("httpd.hrl").
-include("httpd_internal.hrl").
@@ -83,61 +83,6 @@ do(ModData) ->
%%--------------------------------------------------------------------------
-%% load(Line, Context) -> eof | ok | {ok, NewContext} |
-%% {ok, NewContext, Directive} |
-%% {ok, NewContext, DirectiveList} | {error, Reason}
-%% Line = string()
-%% Context = NewContext = DirectiveList = [Directive]
-%% Directive = {DirectiveKey , DirectiveValue}
-%% DirectiveKey = DirectiveValue = term()
-%% Reason = term()
-%%
-%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
-%%-------------------------------------------------------------------------
-load("ErlScriptAlias " ++ ErlScriptAlias, []) ->
- try re:split(ErlScriptAlias," ", [{return, list}]) of
- [ErlName | StrModules] ->
- Modules = lists:map(fun(Str) ->
- list_to_atom(string:strip(Str))
- end, StrModules),
- {ok, [], {erl_script_alias, {ErlName, Modules}}}
- catch _:_ ->
- {error, ?NICE(string:strip(ErlScriptAlias) ++
- " is an invalid ErlScriptAlias")}
- end;
-load("EvalScriptAlias " ++ EvalScriptAlias, []) ->
- try re:split(EvalScriptAlias, " ", [{return, list}]) of
- [EvalName | StrModules] ->
- Modules = lists:map(fun(Str) ->
- list_to_atom(string:strip(Str))
- end, StrModules),
- {ok, [], {eval_script_alias, {EvalName, Modules}}}
- catch
- _:_ ->
- {error, ?NICE(string:strip(EvalScriptAlias) ++
- " is an invalid EvalScriptAlias")}
- end;
-load("ErlScriptTimeout " ++ Timeout, [])->
- case catch list_to_integer(string:strip(Timeout)) of
- TimeoutSec when is_integer(TimeoutSec) ->
- {ok, [], {erl_script_timeout, TimeoutSec}};
- _ ->
- {error, ?NICE(string:strip(Timeout) ++
- " is an invalid ErlScriptTimeout")}
- end;
-load("ErlScriptNoCache " ++ CacheArg, [])->
- case catch list_to_atom(string:strip(CacheArg)) of
- true ->
- {ok, [], {erl_script_nocache, true}};
- false ->
- {ok, [], {erl_script_nocache, false}};
- _ ->
- {error, ?NICE(string:strip(CacheArg)++
- " is an invalid ErlScriptNoCache directive")}
- end.
-
-
-%%--------------------------------------------------------------------------
%% store(Directive, DirectiveList) -> {ok, NewDirective} |
%% {ok, [NewDirective]} |
%% {error, Reason}
@@ -241,8 +186,9 @@ erl(#mod{method = Method} = ModData, ESIBody, Modules)
{proceed, [{status,{400, none, BadRequest}} | ModData#mod.data]}
end;
-erl(#mod{method = "PUT", entity_body = Body} = ModData,
- ESIBody, Modules) ->
+erl(#mod{method = Method, entity_body = Body} = ModData,
+ ESIBody, Modules) when Method =:= "PUT" orelse
+ Method =:= "PATCH" ->
case httpd_util:split(ESIBody,":|%3A|/",2) of
{ok, [ModuleName, FuncAndInput]} ->
case httpd_util:split(FuncAndInput,"[\?/]",2) of
@@ -272,15 +218,7 @@ erl(#mod{method = "POST", entity_body = Body} = ModData, ESIBody, Modules) ->
Function, Body, []);
{ok, BadRequest} ->
{proceed,[{status, {400, none, BadRequest}} | ModData#mod.data]}
- end;
-
-erl(#mod{request_uri = ReqUri,
- method = "PATCH",
- http_version = Version,
- data = Data}, _ESIBody, _Modules) ->
- {proceed, [{status,{501,{"PATCH", ReqUri, Version},
- ?NICE("Erl mechanism doesn't support method PATCH")}}|
- Data]}.
+ end.
generate_webpage(ModData, ESIBody, [all], Module, FunctionName,
Input, ScriptElements) ->
diff --git a/lib/inets/src/http_server/mod_head.erl b/lib/inets/src/http_server/mod_head.erl
index 25c11360eb..e90b089f94 100644
--- a/lib/inets/src/http_server/mod_head.erl
+++ b/lib/inets/src/http_server/mod_head.erl
@@ -64,6 +64,10 @@ do_head(Info) ->
Suffix = httpd_util:suffix(Path),
%% Does the file exists?
case file:read_file_info(Path) of
+ {ok, #file_info{type = directory}} ->
+ Status = httpd_file:handle_error(eacces, "access", Info, Path),
+ {proceed,
+ [{status, Status} | Info#mod.data]};
{ok, FileInfo} ->
MimeType =
httpd_util:lookup_mime_default(Info#mod.config_db,
diff --git a/lib/inets/src/http_server/mod_htaccess.erl b/lib/inets/src/http_server/mod_htaccess.erl
deleted file mode 100644
index 7b742bba24..0000000000
--- a/lib/inets/src/http_server/mod_htaccess.erl
+++ /dev/null
@@ -1,1071 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%
-
--module(mod_htaccess).
-
--export([do/1, load/2, store/2]).
-
--include("httpd.hrl").
--include("httpd_internal.hrl").
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Public methods that interface the eswapi %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%----------------------------------------------------------------------
-% Public method called by the webbserver to insert the data about
-% Names on accessfiles
-%----------------------------------------------------------------------
-load("AccessFileName" ++ FileNames, _Context)->
- CleanFileNames=string:strip(FileNames),
- {ok,[],{access_files,string:tokens(CleanFileNames," ")}}.
-
-store({access_files, Files} = Conf, _) when is_list(Files)->
- {ok, Conf};
-store({access_files, Value}, _) ->
- {error, {wrong_type, {access_files, Value}}}.
-
-%----------------------------------------------------------------------
-% Public method that the webbserver calls to control the page
-%----------------------------------------------------------------------
-do(Info)->
- case proplists:get_value(status, Info#mod.data) of
- {_Status_code, _PhraseArgs, _Reason}->
- {proceed,Info#mod.data};
- undefined ->
- control_path(Info)
- end.
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% The functions that start the control if there is a accessfile %%
-%% and if so controls if the dir is allowed or not %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%----------------------------------------------------------------------
-%Info = record mod as specified in httpd.hrl
-%returns either {proceed,Info#mod.data}
-%{proceed,[{status,403....}|Info#mod.data]}
-%{proceed,[{status,401....}|Info#mod.data]}
-%{proceed,[{status,500....}|Info#mod.data]}
-%----------------------------------------------------------------------
-control_path(Info) ->
- Path = mod_alias:path(Info#mod.data,
- Info#mod.config_db,
- Info#mod.request_uri),
- case isErlScriptOrNotAccessibleFile(Path,Info) of
- true->
- {proceed,Info#mod.data};
- false->
- case getHtAccessData(Path,Info)of
- {ok,public}->
- %%There was no restrictions on the page continue
- {proceed,Info#mod.data};
- {error, _Reason} ->
- %%Something got wrong continue or quit??????????????????/
- {proceed,Info#mod.data};
- {accessData,AccessData}->
- controlAllowedMethod(Info,AccessData)
- end
- end.
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% These methods controls that the method the client used in the %%
-%% request is one of the limited %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%----------------------------------------------------------------------
-%Control that if the accessmethod used is in the list of modes to challenge
-%
-%Info is the mod record as specified in httpd.hrl
-%AccessData is an ets table whit the data in the .htaccessfiles
-%----------------------------------------------------------------------
-controlAllowedMethod(Info,AccessData)->
- case allowedRequestMethod(Info,AccessData) of
- allow->
- %%The request didnt use one of the limited methods
- ets:delete(AccessData),
- {proceed,Info#mod.data};
- challenge->
- authenticateUser(Info,AccessData)
- end.
-
-%----------------------------------------------------------------------
-%Check the specified access method in the .htaccessfile
-%----------------------------------------------------------------------
-allowedRequestMethod(Info,AccessData)->
- case ets:lookup(AccessData,limit) of
- [{limit,all}]->
- challenge;
- [{limit,Methods}]->
- isLimitedRequestMethod(Info,Methods)
- end.
-
-
-%----------------------------------------------------------------------
-%Check the specified accessmethods in the .htaccesfile against the users
-%accessmethod
-%
-%Info is the record from the do call
-%Methods is a list of the methods specified in the .htaccessfile
-%----------------------------------------------------------------------
-isLimitedRequestMethod(Info,Methods)->
- case lists:member(Info#mod.method,Methods) of
- true->
- challenge;
- false ->
- allow
- end.
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% These methods controls that the user comes from an allowwed net %%
-%% and if so wheather its a valid user or a challenge shall be %%
-%% generated %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%----------------------------------------------------------------------
-%The first thing to control is that the user is from a network
-%that has access to the page
-%----------------------------------------------------------------------
-authenticateUser(Info,AccessData)->
- case controlNet(Info,AccessData) of
- allow->
- %the network is ok control that it is an allowed user
- authenticateUser2(Info,AccessData);
- deny->
- %The user isnt allowed to access the pages from that network
- ets:delete(AccessData),
- {proceed,[{status,{403,Info#mod.request_uri,
- "Restricted area not allowed from your network"}}|Info#mod.data]}
- end.
-
-
-%----------------------------------------------------------------------
-%The network the user comes from is allowed to view the resources
-%control whether the user needsto supply a password or not
-%----------------------------------------------------------------------
-authenticateUser2(Info,AccessData)->
- case ets:lookup(AccessData,require) of
- [{require,AllowedUsers}]->
- case ets:lookup(AccessData,auth_name) of
- [{auth_name,Realm}]->
- authenticateUser2(Info,AccessData,Realm,AllowedUsers);
- _NoAuthName->
- ets:delete(AccessData),
- {break,[{status,{500,none,
- ?NICE("mod_htaccess:AuthName directive "
- "not specified")}}]}
- end;
- [] ->
- %%No special user is required the network is ok so let
- %%the user in
- ets:delete(AccessData),
- {proceed,Info#mod.data}
- end.
-
-
-%----------------------------------------------------------------------
-%The user must send a userId and a password to get the resource
-%Control if its already in the http-request
-%if the file with users is bad send an 500 response
-%----------------------------------------------------------------------
-authenticateUser2(Info,AccessData,Realm,AllowedUsers)->
- case authenticateUser(Info,AccessData,AllowedUsers) of
- allow ->
- ets:delete(AccessData),
- {user,Name, _Pwd} = getAuthenticatingDataFromHeader(Info),
- {proceed, [{remote_user_name,Name}|Info#mod.data]};
- challenge->
- ets:delete(AccessData),
- ReasonPhrase = httpd_util:reason_phrase(401),
- Message = httpd_util:message(401,none,Info#mod.config_db),
- {proceed,
- [{response,
- {401,
- ["WWW-Authenticate: Basic realm=\"",Realm,
- "\"\r\n\r\n","<HTML>\n<HEAD>\n<TITLE>",
- ReasonPhrase,"</TITLE>\n",
- "</HEAD>\n<BODY>\n<H1>",ReasonPhrase,
- "</H1>\n",Message,"\n</BODY>\n</HTML>\n"]}}|
- Info#mod.data]};
- deny->
- ets:delete(AccessData),
- {break,[{status,{500,none,
- ?NICE("mod_htaccess:Bad path to user "
- "or group file")}}]}
- end.
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% Methods that validate the netwqork the user comes from %%
-%% according to the allowed networks %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%---------------------------------------------------------------------
-%Controls the users networkaddress agains the specifed networks to
-%allow or deny
-%
-%returns either allow or deny
-%----------------------------------------------------------------------
-controlNet(Info,AccessData)->
- UserNetwork=getUserNetworkAddress(Info),
- case getAllowDenyOrder(AccessData) of
- {_deny,[],_allow,[]}->
- allow;
- {deny,[],allow,AllowedNetworks}->
- controlIfAllowed(AllowedNetworks,UserNetwork,allow,deny);
- {allow,AllowedNetworks,deny,[]}->
- controlIfAllowed(AllowedNetworks,UserNetwork,allow,deny);
-
- {deny,DeniedNetworks,allow,[]}->
- controlIfAllowed(DeniedNetworks,UserNetwork,allow,deny);
- {allow,[],deny,DeniedNetworks}->
- controlIfAllowed(DeniedNetworks,UserNetwork,allow,deny);
-
- {deny,DeniedNetworks,allow,AllowedNetworks}->
- controlDenyAllow(DeniedNetworks,AllowedNetworks,UserNetwork);
- {allow,AllowedNetworks,deny,DeniedNetworks}->
- controlAllowDeny(AllowedNetworks,DeniedNetworks,UserNetwork)
- end.
-
-
-%----------------------------------------------------------------------
-%Returns the users IP-Number
-%----------------------------------------------------------------------
-getUserNetworkAddress(Info)->
- {_Socket,Address}=(Info#mod.init_data)#init_data.peername,
- Address.
-
-
-%----------------------------------------------------------------------
-%Control the users Ip-number against the ip-numbers in the .htaccessfile
-%----------------------------------------------------------------------
-controlIfAllowed(AllowedNetworks,UserNetwork,IfAllowed,IfDenied)->
- case AllowedNetworks of
- [{allow,all}]->
- IfAllowed;
- [{deny,all}]->
- IfDenied;
- [{deny,Networks}]->
- memberNetwork(Networks,UserNetwork,IfDenied,IfAllowed);
- [{allow,Networks}]->
- memberNetwork(Networks,UserNetwork,IfAllowed,IfDenied);
- _Error->
- IfDenied
- end.
-
-
-%---------------------------------------------------------------------%
-%The Denycontrol isn't neccessary to preform since the allow control %
-%override the deny control %
-%---------------------------------------------------------------------%
-controlDenyAllow(_DeniedNetworks, AllowedNetworks, UserNetwork)->
- case AllowedNetworks of
- [{allow, all}]->
- allow;
- [{allow, Networks}]->
- case memberNetwork(Networks, UserNetwork) of
- true->
- allow;
- false->
- deny
- end
- end.
-
-
-%----------------------------------------------------------------------%
-%Control that the user is in the allowed list if so control that the %
-%network is in the denied list
-%----------------------------------------------------------------------%
-controlAllowDeny(AllowedNetworks,DeniedNetworks,UserNetwork)->
- case controlIfAllowed(AllowedNetworks,UserNetwork,allow,deny) of
- allow->
- controlIfAllowed(DeniedNetworks,UserNetwork,deny,allow);
- deny ->
- deny
- end.
-
-%----------------------------------------------------------------------
-%Controls if the users Ipnumber is in the list of either denied or
-%allowed networks
-%----------------------------------------------------------------------
-memberNetwork(Networks,UserNetwork,IfTrue,IfFalse)->
- case memberNetwork(Networks,UserNetwork) of
- true->
- IfTrue;
- false->
- IfFalse
- end.
-
-
-%----------------------------------------------------------------------
-%regexp match the users ip-address against the networks in the list of
-%ipadresses or subnet addresses.
-memberNetwork(Networks,UserNetwork)->
- case lists:filter(fun(Net)->
- case re:run(UserNetwork,
- formatRegexp(Net), [{capture, first}]) of
- {match,[{0,_}]}->
- true;
- _NotSubNet ->
- false
- end
- end,Networks) of
- []->
- false;
- _MemberNetWork ->
- true
- end.
-
-
-%----------------------------------------------------------------------
-%Creates a regexp from an ip-number i.e "127.0.0-> "^127[.]0[.]0.*"
-%"127.0.0.-> "^127[.]0[.]0[.].*"
-%----------------------------------------------------------------------
-formatRegexp(Net)->
- [SubNet1|SubNets]=string:tokens(Net,"."),
- NetRegexp=lists:foldl(fun(SubNet,Newnet)->
- Newnet ++ "[.]" ++SubNet
- end,"^"++SubNet1,SubNets),
- case string:len(Net)-string:rchr(Net,$.) of
- 0->
- NetRegexp++"[.].*";
- _->
- NetRegexp++".*"
- end.
-
-%----------------------------------------------------------------------
-%If the user has specified if the allow or deny check shall be preformed
-%first get that order if no order is specified take
-%allow - deny since its harder that deny - allow
-%----------------------------------------------------------------------
-getAllowDenyOrder(AccessData)->
- case ets:lookup(AccessData,order) of
- [{order,{deny,allow}}]->
- {deny,ets:lookup(AccessData,deny),
- allow,ets:lookup(AccessData,allow)};
- _DefaultOrder->
- {allow,ets:lookup(AccessData,allow),
- deny,ets:lookup(AccessData,deny)}
- end.
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% The methods that validates the user %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%----------------------------------------------------------------------
-%Control if there is anyu autheticating data in threquest header
-%if so it controls it against the users in the list Allowed Users
-%----------------------------------------------------------------------
-authenticateUser(Info,AccessData,AllowedUsers)->
- case getAuthenticatingDataFromHeader(Info) of
- {user,User,PassWord}->
- authenticateUser(Info,AccessData,AllowedUsers,
- {user,User,PassWord});
- {error,nouser}->
- challenge;
- {error, _BadData}->
- challenge
- end.
-
-
-%----------------------------------------------------------------------
-%Returns the Autheticating data in the http-request
-%----------------------------------------------------------------------
-getAuthenticatingDataFromHeader(Info)->
- PrsedHeader=Info#mod.parsed_header,
- case proplists:get_value("authorization", PrsedHeader) of
- undefined->
- {error,nouser};
- [$B,$a,$s,$i,$c,$\ |EncodedString] = Credentials ->
- case (catch base64:decode_to_string(EncodedString)) of
- {'EXIT',{function_clause, _}} ->
- {error, Credentials};
- UnCodedString ->
- case httpd_util:split(UnCodedString,":",2) of
- {ok,[User,PassWord]}->
- {user,User,PassWord};
- Other ->
- {error, Other}
- end
- end;
- BadCredentials ->
- {error,BadCredentials}
- end.
-
-%----------------------------------------------------------------------
-%Returns a list of all members of the allowed groups
-%----------------------------------------------------------------------
-getGroupMembers(Groups,AllowedGroups)->
- Allowed=lists:foldl(fun({group,Name,Members},AllowedMembers)->
- case lists:member(Name,AllowedGroups) of
- true->
- AllowedMembers++Members;
- false ->
- AllowedMembers
- end
- end,[],Groups),
- {ok,Allowed}.
-
-authenticateUser(Info,AccessData,{{users,[]},{groups,Groups}},User)->
- authenticateUser(Info,AccessData,{groups,Groups},User);
-authenticateUser(Info,AccessData,{{users,Users},{groups,[]}},User)->
- authenticateUser(Info,AccessData,{users,Users},User);
-
-authenticateUser(Info,AccessData,{{users,Users},{groups,Groups}},User)->
- AllowUser=authenticateUser(Info,AccessData,{users,Users},User),
- AllowGroup=authenticateUser(Info,AccessData,{groups,Groups},User),
- case {AllowGroup,AllowUser} of
- {_,allow}->
- allow;
- {allow,_}->
- allow;
- {challenge,_}->
- challenge;
- {_,challenge}->
- challenge;
- {_deny,_deny}->
- deny
- end;
-
-
-%----------------------------------------------------------------------
-%Controls that the user is a member in one of the allowed group
-%----------------------------------------------------------------------
-authenticateUser(Info,AccessData,{groups,AllowedGroups},{user,User,PassWord})->
- case getUsers(AccessData,group_file) of
- {group_data,Groups}->
- {ok, Members } = getGroupMembers(Groups,AllowedGroups),
- authenticateUser(Info,AccessData,{users,Members},
- {user,User,PassWord});
- {error, _BadData}->
- deny
- end;
-
-
-%----------------------------------------------------------------------
-%Control that the user is one of the allowed users and that the passwd is ok
-%----------------------------------------------------------------------
-authenticateUser(_Info,AccessData,{users,AllowedUsers},{user,User,PassWord})->
- case lists:member(User,AllowedUsers) of
- true->
- %Get the usernames and passwords from the file
- case getUsers(AccessData,user_file) of
- {error, _BadData}->
- deny;
- {user_data,Users}->
- %Users is a list of the users in
- %the userfile [{user,User,Passwd}]
- checkPassWord(Users,{user,User,PassWord})
- end;
- false ->
- challenge
- end.
-
-
-%----------------------------------------------------------------------
-%Control that the user User={user,"UserName","PassWd"} is
-%member of the list of Users
-%----------------------------------------------------------------------
-checkPassWord(Users,User)->
- case lists:member(User,Users) of
- true->
- allow;
- false->
- challenge
- end.
-
-
-%----------------------------------------------------------------------
-%Get the users in the specified file
-%UserOrGroup is an atom that specify if its a group file or a user file
-%i.e. group_file or user_file
-%----------------------------------------------------------------------
-getUsers({file,FileName},UserOrGroup)->
- case file:open(FileName,[read]) of
- {ok,AccessFileHandle} ->
- getUsers({stream,AccessFileHandle},[],UserOrGroup);
- {error,Reason} ->
- {error,{Reason,FileName}}
- end;
-
-
-%----------------------------------------------------------------------
-%The method that starts the lokkong for user files
-%----------------------------------------------------------------------
-
-getUsers(AccessData,UserOrGroup)->
- case ets:lookup(AccessData,UserOrGroup) of
- [{UserOrGroup,File}]->
- getUsers({file,File},UserOrGroup);
- _ ->
- {error,noUsers}
- end.
-
-
-%----------------------------------------------------------------------
-%Reads data from the filehandle File to the list FileData and when its
-%reach the end it returns the list in a tuple {user_file|group_file,FileData}
-%----------------------------------------------------------------------
-getUsers({stream,File},FileData,UserOrGroup)->
- case io:get_line(File,[]) of
- eof when UserOrGroup =:= user_file ->
- {user_data,FileData};
- eof when UserOrGroup =:= group_file ->
- {group_data,FileData};
- Line ->
- getUsers({stream,File},
- formatUser(Line,FileData,UserOrGroup),UserOrGroup)
- end.
-
-
-%----------------------------------------------------------------------
-%If the line is a comment remove it
-%----------------------------------------------------------------------
-formatUser([$#|_UserDataComment],FileData,_UserOrgroup)->
- FileData;
-
-
-%----------------------------------------------------------------------
-%The user name in the file is Username:Passwd\n
-%Remove the newline sign and split the user name in
-%UserName and Password
-%----------------------------------------------------------------------
-formatUser(UserData,FileData,UserOrGroup)->
- case string:tokens(UserData," \r\n")of
- [User| _Whitespace] when UserOrGroup =:= user_file ->
- case string:tokens(User,":") of
- [Name,PassWord]->
- [{user,Name,PassWord}|FileData];
- _Error->
- FileData
- end;
- GroupData when UserOrGroup =:= group_file ->
- parseGroupData(GroupData,FileData);
- _Error ->
- FileData
- end.
-
-
-%----------------------------------------------------------------------
-%if everything is right GroupData is on the form
-% ["groupName:", "Member1", "Member2", "Member2"
-%----------------------------------------------------------------------
-parseGroupData([GroupName|GroupData],FileData)->
- [{group,formatGroupName(GroupName),GroupData}|FileData].
-
-
-%----------------------------------------------------------------------
-%the line in the file is GroupName: Member1 Member2 .....MemberN
-%Remove the : from the group name
-%----------------------------------------------------------------------
-formatGroupName(GroupName)->
- string:strip(GroupName,right,$:).
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% Functions that parses the accessfiles %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%----------------------------------------------------------------------
-%Control that the asset is a real file and not a request for an virtual
-%asset
-%----------------------------------------------------------------------
-isErlScriptOrNotAccessibleFile(Path, _Info)->
- case file:read_file_info(Path) of
- {ok,_fileInfo}->
- false;
- {error,_Reason} ->
- true
- end.
-
-
-%----------------------------------------------------------------------
-%Path=PathToTheRequestedFile=String
-%Innfo=record#mod
-%----------------------------------------------------------------------
-getHtAccessData(Path,Info)->
- HtAccessFileNames=getHtAccessFileNames(Info),
- case getData(Path,Info,HtAccessFileNames) of
- {ok,public}->
- {ok,public};
- {accessData,AccessData}->
- {accessData,AccessData};
- {error,Reason} ->
- {error,Reason}
- end.
-
-
-%----------------------------------------------------------------------
-%returns the names of the accessfiles
-%----------------------------------------------------------------------
-getHtAccessFileNames(Info)->
- case httpd_util:lookup(Info#mod.config_db,access_files) of
- undefined->
- [".htaccess"];
- Files->
- Files
- end.
-%----------------------------------------------------------------------
-%HtAccessFileNames=["accessfileName1",..."AccessFileName2"]
-%----------------------------------------------------------------------
-getData(Path,Info,HtAccessFileNames)->
- SplittedPath = re:split(Path, "/", [{return, list}]),
- getData2(HtAccessFileNames,SplittedPath,Info).
-
-%----------------------------------------------------------------------
-%Add to together the data in the Splittedpath up to the path
-%that is the alias or the document root
-%Since we do not need to control after any accessfiles before here
-%----------------------------------------------------------------------
-getData2(HtAccessFileNames,SplittedPath,Info)->
- case getRootPath(SplittedPath,Info) of
- {error,Path}->
- {error,Path};
- {ok,StartPath,RestOfSplittedPath} ->
- getData2(HtAccessFileNames,StartPath,RestOfSplittedPath,Info)
- end.
-
-
-%----------------------------------------------------------------------
-%HtAccessFilenames is a list the names the accesssfiles can have
-%Path is the shortest match agains all alias and documentroot
-%rest of splitted path is a list of the parts of the path
-%Info is the mod recod from the server
-%----------------------------------------------------------------------
-getData2(HtAccessFileNames, StartPath, RestOfSplittedPath, _Info)->
- case getHtAccessFiles(HtAccessFileNames,StartPath,RestOfSplittedPath) of
- []->
- %No accessfile qiut its a public directory
- {ok,public};
- Files ->
- loadAccessFilesData(Files)
- end.
-
-
-%----------------------------------------------------------------------
-%Loads the data in the accessFiles specifiied by
-% AccessFiles=["/hoem/public/html/accefile",
-% "/home/public/html/priv/accessfile"]
-%----------------------------------------------------------------------
-loadAccessFilesData(AccessFiles)->
- loadAccessFilesData(AccessFiles,ets:new(accessData,[])).
-
-
-%----------------------------------------------------------------------
-%Returns the found data
-%----------------------------------------------------------------------
-contextToValues(AccessData)->
- case ets:lookup(AccessData,context) of
- [{context,Values}]->
- ets:delete(AccessData,context),
- insertContext(AccessData,Values),
- {accessData,AccessData};
- _Error->
- {error,errorInAccessFile}
- end.
-
-
-insertContext(_AccessData, [])->
- ok;
-
-insertContext(AccessData,[{allow,From}|Values])->
- insertDenyAllowContext(AccessData,{allow,From}),
- insertContext(AccessData,Values);
-
-insertContext(AccessData,[{deny,From}|Values])->
- insertDenyAllowContext(AccessData,{deny,From}),
- insertContext(AccessData,Values);
-
-insertContext(AccessData,[{require,{GrpOrUsr,Members}}|Values])->
- case ets:lookup(AccessData,require) of
- [] when GrpOrUsr =:= users ->
- ets:insert(AccessData,{require,{{users,Members},{groups,[]}}});
-
- [{require,{{users,Users},{groups,Groups}}}] when GrpOrUsr =:= users ->
- ets:insert(AccessData,{require,{{users,Users++Members},
- {groups,Groups}}});
- [] when GrpOrUsr =:= groups ->
- ets:insert(AccessData,{require,{{users,[]},{groups,Members}}});
-
- [{require,{{users,Users},{groups,Groups}}}] when GrpOrUsr =:= groups ->
- ets:insert(AccessData,{require,{{users,Users},
- {groups,Groups++Members}}})
- end,
- insertContext(AccessData,Values);
-
-
-
-%%limit and order directive need no transforming they areis just to insert
-insertContext(AccessData,[Elem|Values])->
- ets:insert(AccessData,Elem),
- insertContext(AccessData,Values).
-
-
-insertDenyAllowContext(AccessData,{AllowDeny,From})->
- case From of
- all ->
- ets:insert(AccessData,{AllowDeny,all});
- _AllowedSubnets ->
- case ets:lookup(AccessData,AllowDeny) of
- []->
- ets:insert(AccessData,{AllowDeny,From});
- [{AllowDeny,all}]->
- ok;
- [{AllowDeny,Networks}]->
- ets:insert(AccessData,{allow,Networks++From})
- end
- end.
-
-loadAccessFilesData([],AccessData)->
- %preform context to limits
- contextToValues(AccessData),
- {accessData,AccessData};
-
-%----------------------------------------------------------------------
-%Takes each file in the list and load the data to the ets table
-%AccessData
-%----------------------------------------------------------------------
-loadAccessFilesData([FileName|FileNames],AccessData)->
- case loadAccessFileData({file,FileName},AccessData) of
- overRide->
- loadAccessFilesData(FileNames,AccessData);
- noOverRide ->
- {accessData,AccessData};
- error->
- ets:delete(AccessData),
- {error,errorInAccessFile}
- end.
-
-%----------------------------------------------------------------------
-%opens the filehandle to the specified file
-%----------------------------------------------------------------------
-loadAccessFileData({file,FileName},AccessData)->
- case file:open(FileName,[read]) of
- {ok,AccessFileHandle}->
- loadAccessFileData({stream,AccessFileHandle},AccessData,[]);
- {error, _Reason} ->
- overRide
- end.
-
-%----------------------------------------------------------------------
-%%look att each line in the file and add them to the database
-%%When end of file is reached control i overrride is allowed
-%% if so return
-%----------------------------------------------------------------------
-loadAccessFileData({stream,File},AccessData,FileData)->
- case io:get_line(File,[]) of
- eof->
- insertData(AccessData,FileData),
- case ets:match_object(AccessData,{'_',error}) of
- []->
- %Case we got no error control that we can override a
- %at least some of the values
- case ets:match_object(AccessData,
- {allow_over_ride,none}) of
- []->
- overRide;
- _NoOverride->
- noOverRide
- end;
- _ ->
- error
- end;
- Line ->
- loadAccessFileData({stream,File},AccessData,
- insertLine(string:strip(Line,left),FileData))
- end.
-
-%----------------------------------------------------------------------
-%AccessData is a ets table where the previous found data is inserted
-%FileData is a list of the directives in the last parsed file
-%before insertion a control is done that the directive is allowed to
-%override
-%----------------------------------------------------------------------
-insertData(AccessData,{{context,Values},FileData})->
- insertData(AccessData,[{context,Values}|FileData]);
-
-insertData(AccessData,FileData)->
- case ets:lookup(AccessData,allow_over_ride) of
- [{allow_over_ride,all}]->
- lists:foreach(fun(Elem)->
- ets:insert(AccessData,Elem)
- end,FileData);
- []->
- lists:foreach(fun(Elem)->
- ets:insert(AccessData,Elem)
- end,FileData);
- [{allow_over_ride,Directives}] when is_list(Directives)->
- lists:foreach(fun({Key,Value}) ->
- case lists:member(Key,Directives) of
- true->
- ok;
- false ->
- ets:insert(AccessData,{Key,Value})
- end
- end,FileData);
- [{allow_over_ride,_}]->
- %Will never appear if the user
- %aint doing very strang econfig files
- ok
- end.
-%----------------------------------------------------------------------
-%Take a line in the accessfile and transform it into a tuple that
-%later can be inserted in to the ets:table
-%----------------------------------------------------------------------
-%%%Here is the alternatives that resides inside the limit context
-
-insertLine("order"++ Order, {{context, Values}, FileData})->
- {{context,[{order,getOrder(Order)}|Values]},FileData};
-%%Let the user place a tab in the beginning
-insertLine([$\t,$o,$r,$d,$e,$r|Order],{{context,Values},FileData})->
- {{context,[{order,getOrder(Order)}|Values]},FileData};
-
-insertLine("allow" ++ Allow, {{context, Values}, FileData})->
- {{context,[{allow,getAllowDenyData(Allow)}|Values]},FileData};
-insertLine([$\t,$a,$l,$l,$o,$w|Allow],{{context,Values},FileData})->
- {{context,[{allow,getAllowDenyData(Allow)}|Values]},FileData};
-
-insertLine("deny" ++ Deny, {{context,Values}, FileData})->
- {{context,[{deny,getAllowDenyData(Deny)}|Values]},FileData};
-insertLine([$\t, $d,$e,$n,$y|Deny],{{context,Values},FileData})->
- {{context,[{deny,getAllowDenyData(Deny)}|Values]},FileData};
-
-insertLine("require" ++ Require, {{context, Values}, FileData})->
- {{context,[{require,getRequireData(Require)}|Values]},FileData};
-insertLine([$\t,$r,$e,$q,$u,$i,$r,$e|Require],{{context,Values},FileData})->
- {{context,[{require,getRequireData(Require)}|Values]},FileData};
-
-insertLine("</Limit" ++ _EndLimit, {Context,FileData})->
- [Context | FileData];
-insertLine("<Limit" ++ Limit, FileData)->
- {{context,[{limit,getLimits(Limit)}]}, FileData};
-
-insertLine([$A,$u,$t,$h,$U,$s,$e,$r,$F,$i,$l,$e,$\ |AuthUserFile],FileData)->
- [{user_file,string:strip(AuthUserFile,right,$\n)}|FileData];
-
-insertLine([$A,$u,$t,$h,$G,$r,$o,$u,$p,$F,$i,$l,$e,$\ |AuthGroupFile],
- FileData)->
- [{group_file,string:strip(AuthGroupFile,right,$\n)}|FileData];
-
-insertLine("AllowOverRide" ++ AllowOverRide, FileData)->
- [{allow_over_ride,getAllowOverRideData(AllowOverRide)}
- | FileData];
-
-insertLine([$A,$u,$t,$h,$N,$a,$m,$e,$\ |AuthName],FileData)->
- [{auth_name,string:strip(AuthName,right,$\n)}|FileData];
-
-insertLine("AuthType" ++ AuthType,FileData)->
- [{auth_type,getAuthorizationType(AuthType)}|FileData];
-
-insertLine(_BadDirectiveOrComment,FileData)->
- FileData.
-
-%----------------------------------------------------------------------
-%transform the Data specified about override to a form that is ieasier
-%handled later
-%Override data="all"|"md5"|"Directive1 .... DirectioveN"
-%----------------------------------------------------------------------
-
-getAllowOverRideData(OverRideData)->
- case string:tokens(OverRideData," \r\n") of
- ["all" ++ _] ->
- all;
- ["none" ++ _]->
- none;
- Directives ->
- getOverRideDirectives(Directives)
- end.
-
-getOverRideDirectives(Directives)->
- lists:map(fun(Directive)->
- transformDirective(Directive)
- end,Directives).
-transformDirective("AuthUserFile" ++ _)->
- user_file;
-transformDirective("AuthGroupFile" ++ _) ->
- group_file;
-transformDirective("AuthName" ++ _)->
- auth_name;
-transformDirective("AuthType" ++ _)->
- auth_type;
-transformDirective(_UnAllowedOverRideDirective) ->
- unallowed.
-%----------------------------------------------------------------------
-%Replace the string that specify which method to use for authentication
-%and replace it with the atom for easier mathing
-%----------------------------------------------------------------------
-getAuthorizationType(AuthType)->
- [Arg | _Crap] = string:tokens(AuthType,"\n\r\ "),
- case Arg of
- "Basic"->
- basic;
- "MD5" ->
- md5;
- _What ->
- error
- end.
-%----------------------------------------------------------------------
-%Returns a list of the specified methods to limit or the atom all
-%----------------------------------------------------------------------
-getLimits(Limits)->
- case re:split(Limits,">", [{return, list}])of
- [_NoEndOnLimit]->
- error;
- [Methods | _Crap]->
- case re:split(Methods," ", [{return, list}]) of
- [[]]->
- all;
- SplittedMethods ->
- SplittedMethods
- end
- end.
-
-
-%----------------------------------------------------------------------
-% Transform the order to prefrom deny allow control to a tuple of atoms
-%----------------------------------------------------------------------
-getOrder(Order)->
- [First | _Rest]=lists:map(fun(Part)->
- list_to_atom(Part)
- end,string:tokens(Order," \n\r")),
- case First of
- deny->
- {deny,allow};
- allow->
- {allow,deny};
- _Error->
- error
- end.
-
-%----------------------------------------------------------------------
-% The string AllowDeny is "from all" or "from Subnet1 Subnet2...SubnetN"
-%----------------------------------------------------------------------
-getAllowDenyData(AllowDeny)->
- case string:tokens(AllowDeny," \n\r") of
- [_From|AllowDenyData] when length(AllowDenyData)>=1 ->
- case lists:nth(1,AllowDenyData) of
- "all" ->
- all;
- _Hosts->
- AllowDenyData
- end;
- _ ->
- error
- end.
-%----------------------------------------------------------------------
-% Fix the string that describes who is allowed to se the page
-%----------------------------------------------------------------------
-getRequireData(Require)->
- [UserOrGroup|UserData]=string:tokens(Require," \n\r"),
- case UserOrGroup of
- "user"->
- {users,UserData};
- "group" ->
- {groups,UserData};
- _Whatever ->
- error
- end.
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% Methods that collects the searchways to the accessfiles %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%----------------------------------------------------------------------
-% Get the whole path to the different accessfiles
-%----------------------------------------------------------------------
-getHtAccessFiles(HtAccessFileNames,Path,RestOfSplittedPath)->
- getHtAccessFiles(HtAccessFileNames,Path,RestOfSplittedPath,[]).
-
-getHtAccessFiles(HtAccessFileNames,Path,[[]],HtAccessFiles)->
- HtAccessFiles ++ accessFilesOfPath(HtAccessFileNames,Path++"/");
-
-getHtAccessFiles(_HtAccessFileNames, _Path, [], HtAccessFiles)->
- HtAccessFiles;
-getHtAccessFiles(HtAccessFileNames,Path,[NextDir|RestOfSplittedPath],
- AccessFiles)->
- getHtAccessFiles(HtAccessFileNames,Path++"/"++NextDir,RestOfSplittedPath,
- AccessFiles ++
- accessFilesOfPath(HtAccessFileNames,Path++"/")).
-
-
-%----------------------------------------------------------------------
-%Control if therer are any accessfies in the path
-%----------------------------------------------------------------------
-accessFilesOfPath(HtAccessFileNames,Path)->
- lists:foldl(fun(HtAccessFileName,Files)->
- case file:read_file_info(Path++HtAccessFileName) of
- {ok, _}->
- [Path++HtAccessFileName|Files];
- {error,_Error} ->
- Files
- end
- end,[],HtAccessFileNames).
-
-
-%----------------------------------------------------------------------
-%Sake the splitted path and joins it up to the documentroot or the alias
-%that match first
-%----------------------------------------------------------------------
-
-getRootPath(SplittedPath, Info)->
- DocRoot=httpd_util:lookup(Info#mod.config_db,document_root,"/"),
- PresumtiveRootPath=
- [DocRoot|lists:map(fun({_Alias,RealPath})->
- RealPath
- end,
- httpd_util:multi_lookup(Info#mod.config_db,alias))],
- getRootPath(PresumtiveRootPath,SplittedPath,Info).
-
-
-getRootPath(PresumtiveRootPath,[[],Splittedpath],Info)->
- getRootPath(PresumtiveRootPath,["/",Splittedpath],Info);
-
-
-getRootPath(PresumtiveRootPath,[Part,NextPart|SplittedPath],Info)->
- case lists:member(Part,PresumtiveRootPath)of
- true->
- {ok,Part,[NextPart|SplittedPath]};
- false ->
- getRootPath(PresumtiveRootPath,
- [Part++"/"++NextPart|SplittedPath],Info)
- end;
-
-getRootPath(PresumtiveRootPath, [Part], _Info)->
- case lists:member(Part,PresumtiveRootPath)of
- true->
- {ok,Part,[]};
- false ->
- {error,Part}
- end.
diff --git a/lib/inets/src/inets_app/inets.app.src b/lib/inets/src/inets_app/inets.app.src
index ac5a2dd2d5..54b60ee1f7 100644
--- a/lib/inets/src/inets_app/inets.app.src
+++ b/lib/inets/src/inets_app/inets.app.src
@@ -83,14 +83,12 @@
mod_auth_mnesia,
mod_auth_plain,
mod_auth_server,
- mod_browser,
mod_cgi,
mod_dir,
mod_disk_log,
mod_esi,
mod_get,
mod_head,
- mod_htaccess,
mod_log,
mod_range,
mod_responsecontrol,
diff --git a/lib/inets/test/Makefile b/lib/inets/test/Makefile
index 6ab9771a8f..84ccecb5e3 100644
--- a/lib/inets/test/Makefile
+++ b/lib/inets/test/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2018. All Rights Reserved.
+# Copyright Ericsson AB 1997-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -166,7 +166,7 @@ INETS_FILES = inets.config $(INETS_SPECS)
INETS_DATADIRS = inets_SUITE_data inets_socketwrap_SUITE_data
-HTTPD_DATADIRS = httpd_test_data httpd_SUITE_data httpd_basic_SUITE_data old_httpd_SUITE_data httpd_bench_SUITE_data
+HTTPD_DATADIRS = httpd_test_data httpd_SUITE_data httpd_basic_SUITE_data httpd_bench_SUITE_data
HTTPC_DATADIRS = httpc_SUITE_data httpc_proxy_SUITE_data
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
index 8c9bbbe831..bfe3980727 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -108,7 +108,9 @@ real_requests()->
invalid_headers,
invalid_body,
no_scheme,
- invalid_uri
+ invalid_uri,
+ undefined_port,
+ binary_url
].
real_requests_esi() ->
@@ -1235,6 +1237,12 @@ invalid_body(Config) ->
ok
end.
+%%-------------------------------------------------------------------------
+
+binary_url(Config) ->
+ URL = uri_string:normalize(url(group_name(Config), "/dummy.html", Config)),
+ {ok, _Response} = httpc:request(unicode:characters_to_binary(URL)).
+
%%-------------------------------------------------------------------------
@@ -1252,6 +1260,12 @@ invalid_uri(Config) ->
{error, invalid_uri} = httpc:request(URL),
ok.
+%%-------------------------------------------------------------------------
+
+undefined_port(_Config) ->
+ {error, {failed_connect, _Reason}} = httpc:request("http://:"),
+ ok.
+
%%-------------------------------------------------------------------------
remote_socket_close(Config) when is_list(Config) ->
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index 0634f6b63d..1445ff89ac 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -68,8 +68,6 @@ all() ->
{group, https_auth_api_dets},
{group, http_auth_api_mnesia},
{group, https_auth_api_mnesia},
- {group, http_htaccess},
- {group, https_htaccess},
{group, http_security},
{group, https_security},
{group, http_reload},
@@ -84,8 +82,7 @@ all() ->
mime_types_format,
erl_script_timeout_default,
erl_script_timeout_option,
- erl_script_timeout_proplist,
- erl_script_timeout_apache
+ erl_script_timeout_proplist
].
groups() ->
@@ -104,8 +101,6 @@ groups() ->
{https_auth_api_dets, [], [{group, auth_api_dets}]},
{http_auth_api_mnesia, [], [{group, auth_api_mnesia}]},
{https_auth_api_mnesia, [], [{group, auth_api_mnesia}]},
- {http_htaccess, [], [{group, htaccess}]},
- {https_htaccess, [], [{group, htaccess}]},
{http_security, [], [{group, security}]},
{https_security, [], [{group, security}]},
{http_logging, [], [{group, logging}]},
@@ -136,14 +131,13 @@ groups() ->
]},
{auth_api_mnesia, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9
]},
- {htaccess, [], [htaccess_1_1, htaccess_1_0, htaccess_0_9]},
{security, [], [security_1_1, security_1_0]}, %% Skip 0.9 as causes timing issus in test code
{logging, [], [disk_log_internal, disk_log_exists,
disk_log_bad_size, disk_log_bad_file]},
{http_1_1, [],
[host, chunked, expect, cgi, cgi_chunked_encoding_test,
trace, range, if_modified_since, mod_esi_chunk_timeout,
- esi_put, esi_post, esi_proagate] ++ http_head() ++ http_get() ++ load()},
+ esi_put, esi_patch, esi_post, esi_proagate] ++ http_head() ++ http_get() ++ load()},
{http_1_0, [], [host, cgi, trace] ++ http_head() ++ http_get() ++ load()},
{http_0_9, [], http_head() ++ http_get() ++ load()},
{http_rel_path_script_alias, [], [cgi]},
@@ -161,6 +155,7 @@ http_head() ->
http_get() ->
[alias,
get,
+ bad_dot_paths,
%%actions, Add configuration so that this test mod_action
esi,
bad_hex,
@@ -261,24 +256,6 @@ init_per_group(http_0_9, Config) ->
_ ->
[{http_version, "HTTP/0.9"} | Config]
end;
-init_per_group(http_htaccess = Group, Config) ->
- Path = proplists:get_value(doc_root, Config),
- catch remove_htaccess(Path),
- create_htaccess_data(Path, proplists:get_value(address, Config)),
- ok = start_apps(Group),
- init_httpd(Group, [{type, ip_comm} | Config]);
-init_per_group(https_htaccess = Group, Config) ->
- Path = proplists:get_value(doc_root, Config),
- catch remove_htaccess(Path),
- create_htaccess_data(Path, proplists:get_value(address, Config)),
- catch crypto:stop(),
- try crypto:start() of
- ok ->
- init_ssl(Group, Config)
- catch
- _:_ ->
- {skip, "Crypto did not start"}
- end;
init_per_group(auth_api, Config) ->
[{auth_prefix, ""} | Config];
init_per_group(auth_api_dets, Config) ->
@@ -305,7 +282,6 @@ end_per_group(Group, _Config) when Group == http_basic;
Group == http_auth_api;
Group == http_auth_api_dets;
Group == http_auth_api_mnesia;
- Group == http_htaccess;
Group == http_security;
Group == http_reload;
Group == http_post;
@@ -318,7 +294,6 @@ end_per_group(Group, _Config) when Group == https_basic;
Group == https_auth_api;
Group == https_auth_api_dets;
Group == https_auth_api_mnesia;
- Group == https_htaccess;
Group == https_security;
Group == https_reload
->
@@ -398,7 +373,9 @@ init_per_testcase(disk_log_bad_file, Config0) ->
init_per_testcase(erl_script_timeout_default, Config) ->
ct:timetrap({seconds, 60}),
dbg(erl_script_timeout_default, Config, init);
-
+init_per_testcase(medium = Case, Config) ->
+ ct:timetrap({seconds, 150}),
+ dbg(Case, Config, init);
init_per_testcase(Case, Config) ->
ct:timetrap({seconds, 20}),
dbg(Case, Config, init).
@@ -452,9 +429,20 @@ head(Config) when is_list(Config) ->
Version = proplists:get_value(http_version, Config),
Host = proplists:get_value(host, Config),
ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
- proplists:get_value(port, Config), proplists:get_value(node, Config),
+ proplists:get_value(port, Config),
+ proplists:get_value(node, Config),
http_request("HEAD /index.html ", Version, Host),
- [{statuscode, head_status(Version)},
+ [{statuscode, head_status(Version, 200)},
+ {version, Version}]),
+
+ ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
+ proplists:get_value(port, Config),
+ proplists:get_value(node, Config),
+ http_request("HEAD /open/ ", Version, Host),
+ [{statuscode, head_status(Version, 403)},
+ {header, "Content-Type", "text/html"},
+ {header, "Date"},
+ {header, "Server"},
{version, Version}]).
get() ->
@@ -484,6 +472,61 @@ get(Config) when is_list(Config) ->
{header, "Content-Type", "text/html"},
{header, "Date"},
{header, "Server"},
+ {version, Version}]),
+
+ ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
+ proplists:get_value(port, Config),
+ transport_opts(Type, Config),
+ proplists:get_value(node, Config),
+ http_request("GET /.%252e/.%252e/.%252e/.%252e/.%252e/home/ ", Version, Host),
+ [{statuscode, 404},
+ {header, "Content-Type", "text/html"},
+ {header, "Date"},
+ {header, "Server"},
+ {version, Version}]).
+
+bad_dot_paths() ->
+ [{doc, "Do not allow ..-paths to acesse files outside of doc root"}].
+bad_dot_paths(Config) when is_list(Config) ->
+ Version = proplists:get_value(http_version, Config),
+ Host = proplists:get_value(host, Config),
+ Type = proplists:get_value(type, Config),
+
+ BadDotPath0 = "/..%2f..%2f...%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd ",
+ BadDotPath1 = "/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc/passwd ",
+ BadDotPath2 = "/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd ",
+
+ ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
+ proplists:get_value(port, Config),
+ transport_opts(Type, Config),
+ proplists:get_value(node, Config),
+ http_request("GET " ++ BadDotPath0 , Version, Host),
+ [{statuscode, 404},
+ {header, "Content-Type", "text/html"},
+ {header, "Date"},
+ {header, "Server"},
+ {version, Version}]),
+
+ ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
+ proplists:get_value(port, Config),
+ transport_opts(Type, Config),
+ proplists:get_value(node, Config),
+ http_request("GET " ++ BadDotPath1, Version, Host),
+ [{statuscode, 404},
+ {header, "Content-Type", "text/html"},
+ {header, "Date"},
+ {header, "Server"},
+ {version, Version}]),
+
+ ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host,
+ proplists:get_value(port, Config),
+ transport_opts(Type, Config),
+ proplists:get_value(node, Config),
+ http_request("GET " ++ BadDotPath2, Version, Host),
+ [{statuscode, 404},
+ {header, "Content-Type", "text/html"},
+ {header, "Date"},
+ {header, "Server"},
{version, Version}]).
basic_auth_1_1(Config) when is_list(Config) ->
@@ -798,139 +841,15 @@ post_204(Config) ->
ct:fail({connect_error, ConnectError,
[SockType, Host, Port, TranspOpts]})
catch
- T:E ->
+ T:E:Stk ->
ct:fail({connect_failure,
[{type, T},
{error, E},
- {stacktrace, erlang:get_stacktrace()},
+ {stacktrace, Stk},
{args, [SockType, Host, Port, TranspOpts]}]})
end.
%%-------------------------------------------------------------------------
-htaccess_1_1(Config) when is_list(Config) ->
- htaccess([{http_version, "HTTP/1.1"} | Config]).
-
-htaccess_1_0(Config) when is_list(Config) ->
- htaccess([{http_version, "HTTP/1.0"} | Config]).
-
-htaccess_0_9(Config) when is_list(Config) ->
- htaccess([{http_version, "HTTP/0.9"} | Config]).
-
-htaccess() ->
- [{doc, "Test mod_auth API"}].
-
-htaccess(Config) when is_list(Config) ->
- Version = proplists:get_value(http_version, Config),
- Host = proplists:get_value(host, Config),
- Type = proplists:get_value(type, Config),
- Port = proplists:get_value(port, Config),
- Node = proplists:get_value(node, Config),
- %% Control that authentication required!
- %% Control that the pages that shall be
- %% authenticated really need authenticatin
- ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
- http_request("GET /ht/open/ ", Version, Host),
- [{statuscode, 401},
- {version, Version},
- {header, "WWW-Authenticate"}]),
- ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
- http_request("GET /ht/secret/ ", Version, Host),
- [{statuscode, 401},
- {version, Version},
- {header, "WWW-Authenticate"}]),
- ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
- http_request("GET /ht/secret/top_secret/ ",
- Version, Host),
- [{statuscode, 401},
- {version, Version},
- {header, "WWW-Authenticate"}]),
-
- %% Make sure Authenticate header is received even the second time
- %% we try a incorrect password! Otherwise a browser client will hang!
- ok = auth_status(auth_request("/ht/open/",
- "dummy", "WrongPassword", Version, Host), Config,
- [{statuscode, 401},
- {header, "WWW-Authenticate"}]),
- ok = auth_status(auth_request("/ht/open/",
- "dummy", "WrongPassword", Version, Host), Config,
- [{statuscode, 401},
- {header, "WWW-Authenticate"}]),
-
- %% Control that not just the first user in the list is valid
- %% Control the first user
- %% Authennticating ["one:OnePassword" user first in user list]
- ok = auth_status(auth_request("/ht/open/dummy.html", "one", "OnePassword",
- Version, Host), Config,
- [{statuscode, 200}]),
-
- %% Control the second user
- %% Authentication OK and a directory listing is supplied!
- %% ["Aladdin:open sesame" user second in user list]
- ok = auth_status(auth_request("/ht/open/","Aladdin",
- "AladdinPassword", Version, Host), Config,
- [{statuscode, 200}]),
-
- %% Contro that bad passwords and userids get a good denial
- %% User correct but wrong password! ["one:one" user first in user list]
- ok = auth_status(auth_request("/ht/open/", "one", "one", Version, Host), Config,
- [{statuscode, 401}]),
- %% Neither user or password correct! ["dummy:dummy"]
- ok = auth_status(auth_request("/ht/open/", "dummy", "dummy", Version, Host), Config,
- [{statuscode, 401}]),
-
- %% Control that authetication still works, even if its a member in a group
- %% Authentication OK! ["two:TwoPassword" user in first group]
- ok = auth_status(auth_request("/ht/secret/dummy.html", "two",
- "TwoPassword", Version, Host), Config,
- [{statuscode, 200}]),
-
- %% Authentication OK and a directory listing is supplied!
- %% ["three:ThreePassword" user in second group]
- ok = auth_status(auth_request("/ht/secret/", "three",
- "ThreePassword", Version, Host), Config,
- [{statuscode, 200}]),
-
- %% Deny users with bad passwords even if the user is a group member
- %% User correct but wrong password! ["two:two" user in first group]
- ok = auth_status(auth_request("/ht/secret/", "two", "two", Version, Host), Config,
- [{statuscode, 401}]),
- %% Neither user or password correct! ["dummy:dummy"]
- ok = auth_status(auth_request("/ht/secret/", "dummy", "dummy", Version, Host), Config,
- [{statuscode, 401}]),
-
- %% control that we deny the users that are in subnet above the allowed
- ok = auth_status(auth_request("/ht/blocknet/dummy.html", "four",
- "FourPassword", Version, Host), Config,
- [{statuscode, 403}]),
- %% Control that we only applies the rules to the right methods
- ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
- http_request("HEAD /ht/blocknet/dummy.html ", Version, Host),
- [{statuscode, head_status(Version)},
- {version, Version}]),
-
- %% Control that the rerquire directive can be overrideen
- ok = auth_status(auth_request("/ht/secret/top_secret/ ", "Aladdin", "AladdinPassword",
- Version, Host), Config,
- [{statuscode, 401}]),
-
- %% Authentication still required!
- ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
- http_request("GET /ht/open/ ", Version, Host),
- [{statuscode, 401},
- {version, Version},
- {header, "WWW-Authenticate"}]),
- ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
- http_request("GET /ht/secret/ ", Version, Host),
- [{statuscode, 401},
- {version, Version},
- {header, "WWW-Authenticate"}]),
- ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
- http_request("GET /ht/secret/top_secret/ ", Version, Host),
- [{statuscode, 401},
- {version, Version},
- {header, "WWW-Authenticate"}]).
-
-%%-------------------------------------------------------------------------
host() ->
[{doc, "Test host header"}].
@@ -1022,6 +941,15 @@ esi_put() ->
esi_put(Config) when is_list(Config) ->
ok = http_status("PUT /cgi-bin/erl/httpd_example/put/123342234123 ",
Config, [{statuscode, 200}]).
+
+%%-------------------------------------------------------------------------
+esi_patch() ->
+ [{doc, "Test mod_esi PATCH"}].
+
+esi_patch(Config) when is_list(Config) ->
+ ok = http_status("PATCH /cgi-bin/erl/httpd_example/patch/1234567890 ",
+ Config, [{statuscode, 200}]).
+
%%-------------------------------------------------------------------------
esi_post() ->
[{doc, "Test mod_esi POST"}].
@@ -1677,22 +1605,11 @@ reload_config_file(Config) when is_list(Config) ->
"{server_root,\"" ++ ServerRoot ++ "\"}," ++
"{document_root,\"" ++ proplists:get_value(doc_root, Config) ++ "\"}" ++
"].",
- NewConfigApache =
- "BindAddress localhost\n" ++
- "Port " ++ integer_to_list(Port) ++ "\n" ++
- "ServerName httpd_test_new_apache\n" ++
- "ServerRoot " ++ ServerRoot ++ "\n" ++
- "DocumentRoot " ++ proplists:get_value(doc_root, Config) ++ "\n",
-
+
%% Test Erlang term format
ok = file:write_file(HttpdConf, NewConfig),
ok = httpd:reload_config(HttpdConf, non_disturbing),
- "httpd_test_new" = proplists:get_value(server_name, httpd:info(Server)),
-
- %% Test Apache format
- ok = file:write_file(HttpdConf, NewConfigApache),
- ok = httpd:reload_config(HttpdConf, non_disturbing),
- "httpd_test_new_apache" = proplists:get_value(server_name, httpd:info(Server)).
+ "httpd_test_new" = proplists:get_value(server_name, httpd:info(Server)).
%%-------------------------------------------------------------------------
mime_types_format(Config) when is_list(Config) ->
@@ -1887,47 +1804,6 @@ erl_script_timeout_proplist(Config) when is_list(Config) ->
verify_body(Body, 3000),
inets:stop().
-erl_script_timeout_apache(Config) when is_list(Config) ->
- HttpdConf = filename:join(get_tmp_dir(Config),
- "httpd_erl_script_timeout.conf"),
- MimeTypes = filename:join(get_tmp_dir(Config),
- "erl_script_timeout_mime_types.conf"),
-
- MimeTypesConf =
- "html\n" ++
- "text/html\n",
-
- ok = file:write_file(MimeTypes, MimeTypesConf),
-
- ServerConfig =
- "Port 0\n" ++
- "ServerName localhost\n" ++
- "ServerRoot ./\n" ++
- "DocumentRoot ./\n" ++
- "BindAddress 0.0.0.0\n" ++
- "MimeTypes " ++ MimeTypes ++ "\n" ++
- "Modules mod_esi\n" ++
- "ErlScriptTimeout 8\n" ++
- "ErlScriptAlias /erl httpd_example\n",
-
- ok = file:write_file(HttpdConf, ServerConfig),
-
- inets:start(),
- {ok, Pid} = inets:start(httpd,
- [{file, HttpdConf}]),
- Info = httpd:info(Pid),
- verify_timeout(Info, 8),
-
- Port = proplists:get_value(port, Info),
-
- %% Verify: 6 =< erl_script_timeout =< 10
- Url = http_get_url(Port, 500, 6000, 4000),
-
- {ok, {_, _, Body}} = httpc:request(Url),
- ct:log("Response: ~p~n", [Body]),
- verify_body(Body, 6000),
- inets:stop().
-
tls_alert(Config) when is_list(Config) ->
SSLOpts = proplists:get_value(client_alert_conf, Config),
Port = proplists:get_value(port, Config),
@@ -2061,7 +1937,6 @@ start_apps(Group) when Group == https_basic;
Group == https_auth_api;
Group == https_auth_api_dets;
Group == https_auth_api_mnesia;
- Group == https_htaccess;
Group == https_security;
Group == https_reload;
Group == https_not_sup;
@@ -2075,7 +1950,6 @@ start_apps(Group) when Group == http_basic;
Group == http_auth_api;
Group == http_auth_api_dets;
Group == http_auth_api_mnesia;
- Group == http_htaccess;
Group == http_security;
Group == http_logging;
Group == http_reload;
@@ -2168,10 +2042,6 @@ server_config(http_auth_api_mnesia, Config) ->
server_config(https_auth_api_mnesia, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
auth_api_conf(ServerRoot, mnesia) ++ server_config(https, Config);
-server_config(http_htaccess, Config) ->
- auth_access_conf() ++ server_config(http, Config);
-server_config(https_htaccess, Config) ->
- auth_access_conf() ++ server_config(https, Config);
server_config(http_security, Config) ->
ServerRoot = proplists:get_value(server_root, Config),
tl(auth_conf(ServerRoot)) ++ security_conf(ServerRoot) ++ server_config(http, Config);
@@ -2266,10 +2136,11 @@ http_request_missing_CR(Request, "HTTP/1.1" = Version, Host) ->
http_request_missing_CR(Request, Version, _) ->
Request ++ Version ++ "\r\n\n".
-head_status("HTTP/0.9") ->
+head_status("HTTP/0.9", _) ->
501; %% Not implemented in HTTP/0.9
-head_status(_) ->
- 200.
+head_status(_, Expected) ->
+ Expected.
+
basic_conf() ->
[{modules, [mod_alias, mod_range, mod_responsecontrol,
@@ -2290,8 +2161,7 @@ not_sup_conf() ->
[{modules, [mod_get]}].
auth_access_conf() ->
- [{modules, [mod_alias, mod_htaccess, mod_dir, mod_get, mod_head]},
- {access_files, [".htaccess"]}].
+ [{modules, [mod_alias, mod_dir, mod_get, mod_head]}].
auth_conf(Root) ->
[{modules, [mod_alias, mod_auth, mod_dir, mod_get, mod_head]},
@@ -2522,103 +2392,6 @@ create_range_data(Path) ->
ok
end.
-%%% mod_htaccess
-create_htaccess_data(Path, IpAddress)->
- create_htaccess_dirs(Path),
-
- create_html_file(filename:join([Path,"ht/open/dummy.html"])),
- create_html_file(filename:join([Path,"ht/blocknet/dummy.html"])),
- create_html_file(filename:join([Path,"ht/secret/dummy.html"])),
- create_html_file(filename:join([Path,"ht/secret/top_secret/dummy.html"])),
-
- create_htaccess_file(filename:join([Path,"ht/open/.htaccess"]),
- Path, "user one Aladdin"),
- create_htaccess_file(filename:join([Path,"ht/secret/.htaccess"]),
- Path, "group group1 group2"),
- create_htaccess_file(filename:join([Path,
- "ht/secret/top_secret/.htaccess"]),
- Path, "user four"),
- create_htaccess_file(filename:join([Path,"ht/blocknet/.htaccess"]),
- Path, nouser, IpAddress),
-
- create_user_group_file(filename:join([Path,"ht","users.file"]),
- "one:OnePassword\ntwo:TwoPassword\nthree:"
- "ThreePassword\nfour:FourPassword\nAladdin:"
- "AladdinPassword"),
- create_user_group_file(filename:join([Path,"ht","groups.file"]),
- "group1: two one\ngroup2: two three").
-
-create_html_file(PathAndFileName)->
- file:write_file(PathAndFileName,list_to_binary(
- "<html><head><title>test</title></head>
- <body>testar</body></html>")).
-
-create_htaccess_file(PathAndFileName, BaseDir, RequireData)->
- file:write_file(PathAndFileName,
- list_to_binary(
- "AuthUserFile "++ BaseDir ++
- "/ht/users.file\nAuthGroupFile "++ BaseDir
- ++ "/ht/groups.file\nAuthName Test\nAuthType"
- " Basic\n<Limit>\nrequire " ++ RequireData ++
- "\n</Limit>")).
-
-create_htaccess_file(PathAndFileName, BaseDir, nouser, IpAddress)->
- file:write_file(PathAndFileName,list_to_binary(
- "AuthUserFile "++ BaseDir ++
- "/ht/users.file\nAuthGroupFile " ++
- BaseDir ++ "/ht/groups.file\nAuthName"
- " Test\nAuthType"
- " Basic\n<Limit GET>\n\tallow from " ++
- format_ip(IpAddress,
- string:rchr(IpAddress,$.)) ++
- "\n</Limit>")).
-
-create_user_group_file(PathAndFileName, Data)->
- file:write_file(PathAndFileName, list_to_binary(Data)).
-
-create_htaccess_dirs(Path)->
- ok = file:make_dir(filename:join([Path,"ht"])),
- ok = file:make_dir(filename:join([Path,"ht/open"])),
- ok = file:make_dir(filename:join([Path,"ht/blocknet"])),
- ok = file:make_dir(filename:join([Path,"ht/secret"])),
- ok = file:make_dir(filename:join([Path,"ht/secret/top_secret"])).
-
-remove_htaccess_dirs(Path)->
- file:del_dir(filename:join([Path,"ht/secret/top_secret"])),
- file:del_dir(filename:join([Path,"ht/secret"])),
- file:del_dir(filename:join([Path,"ht/blocknet"])),
- file:del_dir(filename:join([Path,"ht/open"])),
- file:del_dir(filename:join([Path,"ht"])).
-
-format_ip(IpAddress,Pos)when Pos > 0->
- case lists:nth(Pos,IpAddress) of
- $.->
- case lists:nth(Pos-2,IpAddress) of
- $.->
- format_ip(IpAddress,Pos-3);
- _->
- lists:sublist(IpAddress,Pos-2) ++ "."
- end;
- _ ->
- format_ip(IpAddress,Pos-1)
- end;
-
-format_ip(IpAddress, _Pos)->
- "1" ++ IpAddress.
-
-remove_htaccess(Path)->
- file:delete(filename:join([Path,"ht/open/dummy.html"])),
- file:delete(filename:join([Path,"ht/secret/dummy.html"])),
- file:delete(filename:join([Path,"ht/secret/top_secret/dummy.html"])),
- file:delete(filename:join([Path,"ht/blocknet/dummy.html"])),
- file:delete(filename:join([Path,"ht/blocknet/.htaccess"])),
- file:delete(filename:join([Path,"ht/open/.htaccess"])),
- file:delete(filename:join([Path,"ht/secret/.htaccess"])),
- file:delete(filename:join([Path,"ht/secret/top_secret/.htaccess"])),
- file:delete(filename:join([Path,"ht","users.file"])),
- file:delete(filename:join([Path,"ht","groups.file"])),
- remove_htaccess_dirs(Path).
-
dos_hostname(Type, Port, Host, Node, Version, Max) ->
TooLongHeader = lists:append(lists:duplicate(Max + 1, "a")),
diff --git a/lib/inets/test/httpd_SUITE_data/cgi_echo.c b/lib/inets/test/httpd_SUITE_data/cgi_echo.c
index 580f860e96..e90b125a00 100644
--- a/lib/inets/test/httpd_SUITE_data/cgi_echo.c
+++ b/lib/inets/test/httpd_SUITE_data/cgi_echo.c
@@ -4,6 +4,8 @@
#if defined __WIN32__
#include <windows.h>
#include <fcntl.h>
+#else
+#include <unistd.h>
#endif
static int read_exact(char *buffer, int len);
diff --git a/lib/inets/test/httpd_bench_SUITE.erl b/lib/inets/test/httpd_bench_SUITE.erl
index 087516f56c..1cba527687 100644
--- a/lib/inets/test/httpd_bench_SUITE.erl
+++ b/lib/inets/test/httpd_bench_SUITE.erl
@@ -87,7 +87,8 @@ init_per_suite(Config) ->
{Node, Host} = setup(Config, node()),
init_ssl(Config),
[{iter, 10}, {server_node, Node}, {server_host, Host} | Config]
- catch _:_ ->
+ catch E:R:ST ->
+ ct:pal("~p:~p:~p",[E,R,ST]),
{skipped, "Benchmark machines only"}
end.
diff --git a/lib/inets/test/httpd_mod_SUITE.erl b/lib/inets/test/httpd_mod_SUITE.erl
index 5ec4e0856d..ebef7eea6c 100644
--- a/lib/inets/test/httpd_mod_SUITE.erl
+++ b/lib/inets/test/httpd_mod_SUITE.erl
@@ -53,7 +53,6 @@ groups() ->
{mod_actions, [], []},
{mod_security, [], []},
{mod_auth, [], []},
- {mod_htaccess, [], []},
{mod_cgi, [], []},
{mod_esi, [], []},
{mod_head, [], []},
@@ -66,7 +65,6 @@ all_version_groups ()->
{group, mod_actions},
{group, mod_security},
{group, mod_auth},
- {group, mod_htaccess},
{group, mod_cgi},
{group, mod_esi},
{group, mod_head}
diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl
index c5efe98555..31a9c72e9c 100644
--- a/lib/inets/test/httpd_test_lib.erl
+++ b/lib/inets/test/httpd_test_lib.erl
@@ -119,11 +119,11 @@ verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, Ti
ct:fail({connect_error, ConnectError,
[SocketType, Host, Port, TranspOpts]})
catch
- T:E ->
+ T:E:Stk ->
ct:fail({connect_failure,
[{type, T},
{error, E},
- {stacktrace, erlang:get_stacktrace()},
+ {stacktrace, Stk},
{args, [SocketType, Host, Port, TranspOpts]}]})
end.
@@ -136,11 +136,11 @@ verify_request_N(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options,
ct:fail({connect_error, ConnectError,
[SocketType, Host, Port, TranspOpts]})
catch
- T:E ->
+ T:E:Stk ->
ct:fail({connect_failure,
[{type, T},
{error, E},
- {stacktrace, erlang:get_stacktrace()},
+ {stacktrace, Stk},
{args, [SocketType, Host, Port, TranspOpts]}]})
end.
diff --git a/lib/inets/test/inets_SUITE.erl b/lib/inets/test/inets_SUITE.erl
index e7964ff7f1..0ec3ce86ad 100644
--- a/lib/inets/test/inets_SUITE.erl
+++ b/lib/inets/test/inets_SUITE.erl
@@ -246,52 +246,6 @@ start_httpd(Config) when is_list(Config) ->
ok = inets:start(),
(?NUM_DEFAULT_SERVICES + 1) = length(inets:services()),
application:unset_env(inets, services),
- ok = inets:stop(),
-
- File1 = filename:join(PrivDir, "httpd_apache.conf"),
-
- {ok, Fd1} = file:open(File1, [write]),
- file:write(Fd1, "ServerName httpd_test\r\n"),
- file:write(Fd1, "ServerRoot " ++ PrivDir ++ "\r\n"),
- file:write(Fd1, "DocumentRoot " ++ PrivDir ++" \r\n"),
- file:write(Fd1, "BindAddress *|inet\r\n"),
- file:write(Fd1, "Port 0\r\n"),
- file:close(Fd1),
-
- application:load(inets),
- application:set_env(inets,
- services, [{httpd, [{file, File1}]}]),
- ok = inets:start(),
- (?NUM_DEFAULT_SERVICES + 1) = length(inets:services()),
- application:unset_env(inets, services),
- ok = inets:stop(),
-
- %% OLD format
- application:load(inets),
- application:set_env(inets,
- services, [{httpd, File1}]),
- ok = inets:start(),
- (?NUM_DEFAULT_SERVICES + 1) = length(inets:services()),
- application:unset_env(inets, services),
- ok = inets:stop(),
- ok = inets:start(),
- {error, {missing_property, server_name}} =
- inets:start(httpd, [{port, 0},
- {server_root, PrivDir},
- {document_root, PrivDir},
- {bind_address, "localhost"}]),
- {error, {missing_property, document_root}} =
- inets:start(httpd, [{port, 0},
- {server_name, "httpd_test"},
- {server_root, PrivDir},
- {bind_address, "localhost"}]),
- {error, {missing_property, server_root}} =
- inets:start(httpd, [{port, 0},
- {server_name, "httpd_test"},
- {document_root, PrivDir},
- {bind_address, "localhost"}]),
- {error, {missing_property, port}} =
- inets:start(httpd, HttpdConf),
ok = inets:stop().
%%-------------------------------------------------------------------------
@@ -337,48 +291,4 @@ httpd_reload(Config) when is_list(Config) ->
[{document_root, PrivDir}] = httpd:info(Pid0, [document_root]),
ok = inets:stop(httpd, Pid0),
- ok = inets:stop(),
-
- File = filename:join(PrivDir, "httpd_apache.conf"),
-
- {ok, Fd0} = file:open(File, [write]),
- file:write(Fd0, "ServerName httpd_test\r\n"),
- file:write(Fd0, "ServerRoot " ++ PrivDir ++ "\r\n"),
- file:write(Fd0, "DocumentRoot " ++ PrivDir ++" \r\n"),
- file:write(Fd0, "BindAddress *\r\n"),
- file:write(Fd0, "Port 0\r\n"),
- file:close(Fd0),
-
- application:load(inets),
- application:set_env(inets,
- services, [{httpd, [{file, File}]}]),
-
- ok = inets:start(),
- [Pid1] = [HttpdPid || {httpd, HttpdPid} <- inets:services()],
- [{server_name, "httpd_test"}] = httpd:info(Pid1, [server_name]),
- [{port, Port1}] = httpd:info(Pid1, [port]),
- {ok, Fd1} = file:open(File, [write]),
- file:write(Fd1, "ServerName httpd_test2\r\n"),
- file:write(Fd1, "ServerRoot " ++ PrivDir ++ "\r\n"),
- file:write(Fd1, "DocumentRoot " ++ PrivDir ++" \r\n"),
- file:write(Fd1, "BindAddress *\r\n"),
- file:write(Fd1, "Port " ++ integer_to_list(Port1) ++ "\r\n"),
- file:close(Fd1),
-
- ok = httpd:reload_config(File, non_disturbing),
- [{server_name, "httpd_test2"}] = httpd:info(Pid1, [server_name]),
-
- {ok, Fd2} = file:open(File, [write]),
- file:write(Fd2, "ServerName httpd_test\r\n"),
- file:write(Fd2, "ServerRoot " ++ PrivDir ++ "\r\n"),
- file:write(Fd2, "DocumentRoot " ++ PrivDir ++" \r\n"),
- file:write(Fd2, "BindAddress *\r\n"),
- file:write(Fd2, "Port " ++ integer_to_list(Port1) ++ "\r\n"),
- file:close(Fd2),
- ok = httpd:reload_config(File, disturbing),
- [{server_name, "httpd_test"}] = httpd:info(Pid1, [server_name]),
-
- ok = inets:stop(httpd, Pid1),
- application:unset_env(inets, services),
ok = inets:stop().
-
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index b0ae8655d2..211c662218 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 7.1.3
+INETS_VSN = 7.3.2
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/jinterface/Makefile b/lib/jinterface/Makefile
index 9cf5f3e94c..dd22d743a5 100644
--- a/lib/jinterface/Makefile
+++ b/lib/jinterface/Makefile
@@ -39,3 +39,4 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/jinterface/doc/src/Makefile b/lib/jinterface/doc/src/Makefile
index f5cba9d074..789a343988 100644
--- a/lib/jinterface/doc/src/Makefile
+++ b/lib/jinterface/doc/src/Makefile
@@ -53,14 +53,9 @@ BOOK_FILES = book.xml
XML_FILES = $(BOOK_FILES) $(XML_APP_FILES) $(XML_REF3_FILES) \
$(XML_PART_FILES) $(XML_CHAPTER_FILES)
-GIF_FILES =
-#------------------------------------------------------
-
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+NO_CHUNKS=$(XML_REF3_FILES)
+#------------------------------------------------------
JAVADOC = javadoc
JAVADOC_PKGS = com.ericsson.otp.erlang
@@ -75,86 +70,13 @@ include $(JAVA_SRC_PATH)/java_files
JAVA_SRC_FILES = $(JAVA_FILES:%=$(JAVA_SRC_PATH)/%.java)
-JAVA_DOC_FILES = \
- overview-tree.html \
- index-all.html \
- deprecated-list.html \
- allclasses-frame.html \
- index.html \
- serialized-form.html \
- package-list \
- stylesheet.css \
- help-doc.html
-
-INFO_FILE = ../../info
-JAVA_EXTRA_FILES = $(JAVA_DOC_FILES:%=$(HTMLDIR)/java/%)
-
-JAVA_GEN_FILES = \
- $(JAVA_FILES:%=$(JAVADOC_DEST)/$(JAVA_PKG_PATH)/%.html) \
- $(JAVADOC_DEST)/$(JAVA_PKG_PATH)/package-summary.html \
- $(JAVADOC_DEST)/$(JAVA_PKG_PATH)/package-tree.html \
- $(JAVADOC_DEST)/$(JAVA_PKG_PATH)/package-frame.html
-
-
-# ----------------------------------------------------
-
-
-HTML_FILES = \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-TOP_HTML_FILES = $(INDEX_TARGET)
-
-INDEX_FILE = index.html
-INDEX_TARGET = $(DOCDIR)/$(INDEX_FILE)
-
# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html jdoc man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-jdoc:$(JAVA_SRC_FILES)
+../html/java/index.html: $(JAVA_SRC_FILES)
(cd ../../java_src;$(JAVADOC) -sourcepath . -d $(JAVADOC_DEST) \
-windowtitle $(JAVADOC_TITLE) $(JAVADOC_PKGS))
- touch jdoc
-
-man:
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR_DATA) $(HTMLDIR) "$(RELSYSDIR)/doc/html"
+html: ../html/java/index.html
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/jinterface/doc/src/jinterface_users_guide.xml b/lib/jinterface/doc/src/jinterface_users_guide.xml
index 504c77f339..c3794e8dbe 100644
--- a/lib/jinterface/doc/src/jinterface_users_guide.xml
+++ b/lib/jinterface/doc/src/jinterface_users_guide.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>jinterface_users_guide.xml</file>
</header>
- <p>The <seealso marker="java/com/ericsson/otp/erlang/package-summary">Jinterface</seealso> package provides
+ <p>The <seefile marker="java/com/ericsson/otp/erlang/package-summary">Jinterface</seefile> package provides
a set of tools for communication with Erlang processes. It can also be used for communication with
other Java processes using the same package, as well as C processes using the Erl_Interface library. </p>
<p>The set of classes in the package can be divided into two categories:
@@ -48,7 +48,7 @@
assumed that even Erlang processes are included, unless specified
otherwise.
The classes in
- <seealso marker="java/com/ericsson/otp/erlang/package-summary">Jinterface</seealso> support the following:</p>
+ <seefile marker="java/com/ericsson/otp/erlang/package-summary">Jinterface</seefile> support the following:</p>
<list type="bulleted">
<item>manipulation of data represented as Erlang data types</item>
<item>conversion of data between Java and Erlang formats</item>
@@ -78,47 +78,47 @@
</row>
<row>
<cell align="left" valign="middle">atom</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangAtom">OtpErlangAtom</seealso></cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangAtom">OtpErlangAtom</seefile></cell>
</row>
<row>
<cell align="left" valign="middle">binary</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangBinary">OtpErlangBinary</seealso></cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangBinary">OtpErlangBinary</seefile></cell>
</row>
<row>
<cell align="left" valign="middle">floating point types</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangFloat">OtpErlangFloat</seealso>or <seealso marker="java/com/ericsson/otp/erlang/OtpErlangDouble">OtpErlangDouble</seealso>, depending on the floating point value size</cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangFloat">OtpErlangFloat</seefile>or <seefile marker="java/com/ericsson/otp/erlang/OtpErlangDouble">OtpErlangDouble</seefile>, depending on the floating point value size</cell>
</row>
<row>
<cell align="left" valign="middle">integral types</cell>
- <cell align="left" valign="middle">One of <seealso marker="java/com/ericsson/otp/erlang/OtpErlangByte">OtpErlangByte</seealso>,<seealso marker="java/com/ericsson/otp/erlang/OtpErlangChar">OtpErlangChar</seealso>,<seealso marker="java/com/ericsson/otp/erlang/OtpErlangShort">OtpErlangShort</seealso>,<seealso marker="java/com/ericsson/otp/erlang/OtpErlangUShort">OtpErlangUShort</seealso>,<seealso marker="java/com/ericsson/otp/erlang/OtpErlangInt">OtpErlangInt</seealso>,<seealso marker="java/com/ericsson/otp/erlang/OtpErlangUInt">OtpErlangUInt</seealso>or<seealso marker="java/com/ericsson/otp/erlang/OtpErlangLong">OtpErlangLong</seealso>, depending on the integral value size and sign</cell>
+ <cell align="left" valign="middle">One of <seefile marker="java/com/ericsson/otp/erlang/OtpErlangByte">OtpErlangByte</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangChar">OtpErlangChar</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangShort">OtpErlangShort</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangUShort">OtpErlangUShort</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangInt">OtpErlangInt</seefile>,<seefile marker="java/com/ericsson/otp/erlang/OtpErlangUInt">OtpErlangUInt</seefile>or<seefile marker="java/com/ericsson/otp/erlang/OtpErlangLong">OtpErlangLong</seefile>, depending on the integral value size and sign</cell>
</row>
<row>
<cell align="left" valign="middle">list</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangList">OtpErlangList</seealso></cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangList">OtpErlangList</seefile></cell>
</row>
<row>
<cell align="left" valign="middle">pid</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangPid">OtpErlangPid</seealso></cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangPid">OtpErlangPid</seefile></cell>
</row>
<row>
<cell align="left" valign="middle">port</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangPort">OtpErlangPort</seealso></cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangPort">OtpErlangPort</seefile></cell>
</row>
<row>
<cell align="left" valign="middle">ref</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangRef">OtpErlangRef</seealso></cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangRef">OtpErlangRef</seefile></cell>
</row>
<row>
<cell align="left" valign="middle">tuple</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangTuple">OtpErlangTuple</seealso></cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangTuple">OtpErlangTuple</seefile></cell>
</row>
<row>
<cell align="left" valign="middle">map</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangMap">OtpErlangMap</seealso></cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangMap">OtpErlangMap</seefile></cell>
</row>
<row>
<cell align="left" valign="middle">term</cell>
- <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangObject">OtpErlangObject</seealso></cell>
+ <cell align="left" valign="middle"><seefile marker="java/com/ericsson/otp/erlang/OtpErlangObject">OtpErlangObject</seefile></cell>
</row>
<tcaption>Mapping of Erlang basic types to Java</tcaption>
</table>
@@ -127,9 +127,9 @@
<section>
<title>Special Mapping Issues</title>
<p>The atoms <c>true</c> and <c>false</c> are special atoms, used as boolean values.
- The class <seealso marker="java/com/ericsson/otp/erlang/OtpErlangBoolean">OtpErlangBoolean</seealso> can be used to represent these.</p>
+ The class <seefile marker="java/com/ericsson/otp/erlang/OtpErlangBoolean">OtpErlangBoolean</seefile> can be used to represent these.</p>
<p>Lists in Erlang are also used to describe sequences of printable characters (strings).
- A convenience class <seealso marker="java/com/ericsson/otp/erlang/OtpErlangString">OtpErlangString</seealso>
+ A convenience class <seefile marker="java/com/ericsson/otp/erlang/OtpErlangString">OtpErlangString</seefile>
is provided to represent Erlang strings.</p>
</section>
@@ -140,11 +140,11 @@
unique name in the form of an identifier composed partly of the
hostname on which the node is running, e.g "gurka@sallad.com". Several
such nodes can run on the same host as long as their names are unique.
- The class <seealso marker="java/com/ericsson/otp/erlang/OtpNode">OtpNode</seealso>
+ The class <seefile marker="java/com/ericsson/otp/erlang/OtpNode">OtpNode</seefile>
represents an Erlang node. It is created with a name
and optionally a port number on which it listens for incoming
connections. Before creating an instance of
- <seealso marker="java/com/ericsson/otp/erlang/OtpNode">OtpNode</seealso>,
+ <seefile marker="java/com/ericsson/otp/erlang/OtpNode">OtpNode</seefile>,
ensure that Epmd is running on the host machine. See the Erlang documentation
for more information about Epmd. In this example, the host name is appended
automatically to the identifier, and the port number is chosen by the
@@ -161,11 +161,11 @@ OtpNode node = new OtpNode("gurka"); </code>
receive messages; the mailbox is identified with the pid of the
process.</p>
<p>Jinterface provides a similar mechanism with the class
- <seealso marker="java/com/ericsson/otp/erlang/OtpMbox">OtpMbox</seealso>, a
+ <seefile marker="java/com/ericsson/otp/erlang/OtpMbox">OtpMbox</seefile>, a
mailbox that can be used to send and receive messages asynchronously.
Each OtpMbox is identified with a unique pid and , optionally, a registered
name unique within the
- <seealso marker="java/com/ericsson/otp/erlang/OtpNode">OtpNode</seealso>. </p>
+ <seefile marker="java/com/ericsson/otp/erlang/OtpNode">OtpNode</seefile>. </p>
<p>Applications are free to create mailboxes as necessary. This is done
as follows:</p>
<code type="none">
@@ -226,7 +226,7 @@ OtpNode node = new OtpNode("gurka"); </code>
<section>
<title>Transport Factory</title>
<p>All necessary connections are made using methods of
- <seealso marker="java/com/ericsson/otp/erlang/OtpTransportFactory">OtpTransportFactory</seealso>
+ <seefile marker="java/com/ericsson/otp/erlang/OtpTransportFactory">OtpTransportFactory</seefile>
interface. Default OtpTransportFactory implementation is based on standard Socket class.
User may provide custom transport factory as needed. See java doc for details.</p>
</section>
@@ -234,7 +234,7 @@ OtpNode node = new OtpNode("gurka"); </code>
<section>
<title>Sending and Receiving Messages</title>
<p>Messages sent with this package must be instances of
- <seealso marker="java/com/ericsson/otp/erlang/OtpErlangObject">OtpErlangObject</seealso>
+ <seefile marker="java/com/ericsson/otp/erlang/OtpErlangObject">OtpErlangObject</seefile>
or one of its subclasses. Message can be sent to processes or pids,
either by specifying the pid of the remote, or its registered name and
node.</p>
@@ -295,7 +295,7 @@ OtpNode node = new OtpNode("gurka"); </code>
desirable to send other data types.</p>
<p>The simplest way to do this is to encapsulate arbitrary data in
messages of type
- <seealso marker="java/com/ericsson/otp/erlang/OtpErlangBinary">OtpErlangBinary</seealso>.
+ <seefile marker="java/com/ericsson/otp/erlang/OtpErlangBinary">OtpErlangBinary</seefile>.
The OtpErlangBinary class can be created from arbitrary Java objects that implement the
Serializable or Externalizable interface:</p>
<code type="none">
@@ -363,23 +363,23 @@ OtpNode node = new OtpNode("gurka"); </code>
another node, it first contacts epmd in order to find out the correct
port number to connect to.</p>
<p>The basic interaction with EPMD is done through instances of
- <seealso marker="java/com/ericsson/otp/erlang/OtpEpmd">OtpEpmd</seealso> class.
+ <seefile marker="java/com/ericsson/otp/erlang/OtpEpmd">OtpEpmd</seefile> class.
Nodes wishing to contact other nodes must first request information
from Epmd before a connection can be set up, however this is done automatically
- by <seealso marker="java/com/ericsson/otp/erlang/OtpSelf#connect-com.ericsson.otp.erlang.OtpPeer-">OtpSelf.connect()</seealso> when necessary. </p>
- <p>When you use <seealso marker="java/com/ericsson/otp/erlang/OtpSelf#connect-com.ericsson.otp.erlang.OtpPeer-">OtpSelf.connect()</seealso> to connect to an Erlang node,
+ by <seefile marker="java/com/ericsson/otp/erlang/OtpSelf#connect(com.ericsson.otp.erlang.OtpPeer)">OtpSelf.connect()</seefile> when necessary. </p>
+ <p>When you use <seefile marker="java/com/ericsson/otp/erlang/OtpSelf#connect(com.ericsson.otp.erlang.OtpPeer)">OtpSelf.connect()</seefile> to connect to an Erlang node,
a connection is first made to epmd and, if the node is known, a
connection is then made to the Erlang node.</p>
<p>Java nodes can also register themselves with epmd if they want other
nodes in the system to be able to find and connect to them.
- This is done by call to method <seealso marker="java/com/ericsson/otp/erlang/OtpEpmd#publishPort-com.ericsson.otp.erlang.OtpLocalNode-">OtpEpmd.publishPort()</seealso>.</p>
+ This is done by call to method <seefile marker="java/com/ericsson/otp/erlang/OtpEpmd#publishPort(com.ericsson.otp.erlang.OtpLocalNode)">OtpEpmd.publishPort()</seefile>.</p>
<p>Be aware that on some systems (such as VxWorks), a failed node will
not be detected by this mechanism since the operating system does not
automatically close descriptors that were left open when the node
failed. If a node has failed in this way, epmd will prevent you from
registering a new node with the old name, since it thinks that the old
name is still in use. In this case, you must unregister the name
- explicitly, by using <seealso marker="java/com/ericsson/otp/erlang/OtpEpmd#unPublishPort-com.ericsson.otp.erlang.OtpLocalNode-">OtpEpmd.unPublishPort()</seealso></p>
+ explicitly, by using <seefile marker="java/com/ericsson/otp/erlang/OtpEpmd#unPublishPort(com.ericsson.otp.erlang.OtpLocalNode)">OtpEpmd.unPublishPort()</seefile></p>
<p>This will cause epmd to close the connection from the far end. Note
that if the name was in fact still in use by a node, the results of
this operation are unpredictable. Also, doing this does not cause the
@@ -392,9 +392,9 @@ OtpNode node = new OtpNode("gurka"); </code>
typically sends a request and waits for a reply. Such a request is
included in a function call at a remote node and is called a remote
procedure call. Remote procedure calls are supported through the class
- <seealso marker="java/com/ericsson/otp/erlang/OtpConnection">OtpConnection</seealso>.
+ <seefile marker="java/com/ericsson/otp/erlang/OtpConnection">OtpConnection</seefile>.
The following example shows how the
- <seealso marker="java/com/ericsson/otp/erlang/OtpConnection">OtpConnection</seealso>
+ <seefile marker="java/com/ericsson/otp/erlang/OtpConnection">OtpConnection</seefile>
class is used for remote procedure calls:</p>
<code type="none">
@@ -411,7 +411,7 @@ OtpErlangObject received = connection.receiveRPC();
<section>
<title>Compiling and Loading Your Code</title>
- <p>In order to use any of the <seealso marker="java/com/ericsson/otp/erlang/package-summary">Jinterface</seealso>
+ <p>In order to use any of the <seefile marker="java/com/ericsson/otp/erlang/package-summary">Jinterface</seefile>
classes, include the following line in your code:</p>
<code type="none">
import com.ericsson.otp.erlang.*; </code>
@@ -445,7 +445,7 @@ $ java ".:/usr/local/otp/lib/jinterface-1.2/priv/OtpErlang.jar" myclass </cod
The value system property "OtpConnection.trace" is the default trace
level for all connections. Normally the default trace level is zero,
i.e. no tracing is performed. By setting
- <seealso marker="java/com/ericsson/otp/erlang/OtpConnection">OtpConnection.trace</seealso>
+ <seefile marker="java/com/ericsson/otp/erlang/OtpConnection">OtpConnection.trace</seefile>
to some non-zero value, the communication protocol can be shown in more or
less detail. The valid values are:</p>
<list type="bulleted">
diff --git a/lib/jinterface/doc/src/notes.xml b/lib/jinterface/doc/src/notes.xml
index 433534207a..8603433fbd 100644
--- a/lib/jinterface/doc/src/notes.xml
+++ b/lib/jinterface/doc/src/notes.xml
@@ -31,6 +31,60 @@
</header>
<p>This document describes the changes made to the Jinterface application.</p>
+<section><title>Jinterface 1.11.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A <seeguide
+ marker="erts:erl_dist_protocol#new_link_protocol">new
+ link protocol</seeguide> has been introduced which
+ prevents links from ending up in an inconsistent state
+ where one participant considers itself linked while the
+ other doesn't. This bug has always existed in the
+ distributed case, but has since OTP 21 also existed in
+ the node local case since the distributed link protocol
+ then was adopted also for node local links. The bug
+ could, however, only trigger if both participants
+ operated on the link simultaneously.</p>
+ <p>
+ Own Id: OTP-17127</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Jinterface 1.11</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Increased size of node incarnation numbers (aka
+ "creation"), from 2 bits to 32 bits. This will reduce the
+ risk of pids/ports/refs, from different node incarnation
+ with the same name, being mixed up.</p>
+ <p>
+ Own Id: OTP-15603</p>
+ </item>
+ <item>
+ <p>
+ Improved node connection setup handshake protocol. Made
+ possible to agree on protocol version without dependence
+ on <c>epmd</c> or other prior knowledge of peer node
+ version. Also added exchange of node incarnation
+ ("creation") values and expanded the distribution
+ capability flag field from 32 to 64 bits.</p>
+ <p>
+ Own Id: OTP-16229</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Jinterface 1.10.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -144,8 +198,8 @@
by <c>term_to_binary()</c> using latin1 encoding. Note
that all atoms will by default be encoded using utf8 in a
future Erlang/OTP release. For more information see the
- documentation of <seealso
- marker="erts:erlang#term_to_binary/2"><c>erlang:term_to_binary/2</c></seealso>.</p>
+ documentation of <seemfa
+ marker="erts:erlang#term_to_binary/2"><c>erlang:term_to_binary/2</c></seemfa>.</p>
<p>
Own Id: OTP-14337</p>
</item>
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 0bf3ca2a67..fb7c6869b7 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java
@@ -76,6 +76,8 @@ public abstract class AbstractConnection extends Thread {
protected static final int exitTTTag = 13;
protected static final int regSendTTTag = 16;
protected static final int exit2TTTag = 18;
+ protected static final int unlinkIdTag = 35;
+ protected static final int unlinkIdAckTag = 36;
// MD5 challenge messsage tags
protected static final int ChallengeReply = 'r';
@@ -147,21 +149,6 @@ public abstract class AbstractConnection extends Thread {
if (traceLevel >= handshakeThreshold) {
System.out.println("<- ACCEPT FROM " + s);
}
-
- // get his info
- recvName(peer);
-
- // now find highest common dist value
- if (peer.proto != self.proto || self.distHigh < peer.distLow
- || self.distLow > peer.distHigh) {
- close();
- throw new IOException(
- "No common protocol found - cannot accept connection");
- }
- // highest common version: min(peer.distHigh, self.distHigh)
- peer.distChoose = peer.distHigh > self.distHigh ? self.distHigh
- : peer.distHigh;
-
doAccept();
name = peer.node();
}
@@ -370,10 +357,8 @@ public abstract class AbstractConnection extends Thread {
// link to pid
/**
- * Create a link between the local node and the specified process on the
- * remote node. If the link is still active when the remote process
- * terminates, an exit signal will be sent to this connection. Use
- * {@link #sendUnlink unlink()} to remove the link.
+ *
+ * Send link signal to remote process.
*
* @param dest
* the Erlang PID of the remote process.
@@ -408,9 +393,8 @@ public abstract class AbstractConnection extends Thread {
}
/**
- * Remove a link between the local node and the specified process on the
- * remote node. This method deactivates links created with {@link #sendLink
- * link()}.
+ *
+ * Send unlink signal to remote process.
*
* @param dest
* the Erlang PID of the remote process.
@@ -419,7 +403,8 @@ public abstract class AbstractConnection extends Thread {
* if the connection is not active or a communication error
* occurs.
*/
- protected void sendUnlink(final OtpErlangPid from, final OtpErlangPid dest)
+ protected void sendUnlink(final OtpErlangPid from, final OtpErlangPid dest,
+ long unlink_id)
throws IOException {
if (!connected) {
throw new IOException("Not connected");
@@ -432,11 +417,29 @@ public abstract class AbstractConnection extends Thread {
header.write1(passThrough);
header.write1(version);
- // header
- header.write_tuple_head(3);
- header.write_long(unlinkTag);
- header.write_any(from);
- header.write_any(dest);
+ if ((peer.flags & AbstractNode.dFlagUnlinkId) != 0) {
+ // header
+ header.write_tuple_head(4);
+ header.write_long(unlinkIdTag);
+ header.write_long(unlink_id);
+ header.write_any(from);
+ header.write_any(dest);
+ }
+ else {
+ /*
+ * A node that isn't capable of talking the new link protocol.
+ *
+ * Send an old unlink op, and send ourselves an unlink-ack. We may
+ * end up in an inconsistent state as we could before the new link
+ * protocol was introduced...
+ */
+ // header
+ header.write_tuple_head(3);
+ header.write_long(unlinkTag);
+ header.write_any(from);
+ header.write_any(dest);
+ deliver(new OtpMsg(unlinkIdAckTag, dest, from, unlink_id));
+ }
// fix up length in preamble
header.poke4BE(0, header.size() - 4);
@@ -444,6 +447,45 @@ public abstract class AbstractConnection extends Thread {
do_send(header);
}
+ /**
+ * Send unlink acknowledgment signal to remote process.
+ *
+ * @param dest
+ * the Erlang PID of the remote process.
+ *
+ * @exception java.io.IOException
+ * if the connection is not active or a communication error
+ * occurs.
+ */
+ protected void sendUnlinkAck(final OtpErlangPid from, final OtpErlangPid dest,
+ long unlink_id)
+ throws IOException {
+ if (!connected) {
+ throw new IOException("Not connected");
+ }
+ if ((peer.flags & AbstractNode.dFlagUnlinkId) != 0) {
+ @SuppressWarnings("resource")
+ final OtpOutputStream header = new OtpOutputStream(headerLen);
+
+ // preamble: 4 byte length + "passthrough" tag
+ header.write4BE(0); // reserve space for length
+ header.write1(passThrough);
+ header.write1(version);
+
+ // header
+ header.write_tuple_head(4);
+ header.write_long(unlinkIdAckTag);
+ header.write_long(unlink_id);
+ header.write_any(from);
+ header.write_any(dest);
+ // fix up length in preamble
+ header.poke4BE(0, header.size() - 4);
+
+ do_send(header);
+ }
+
+ }
+
/* used internally when "processes" terminate */
protected void sendExit(final OtpErlangPid from, final OtpErlangPid dest,
final OtpErlangObject reason) throws IOException {
@@ -700,7 +742,21 @@ public abstract class AbstractConnection extends Thread {
from = (OtpErlangPid) head.elementAt(1);
to = (OtpErlangPid) head.elementAt(2);
- deliver(new OtpMsg(tag, from, to));
+ deliver(new OtpMsg(tag, from, to, 0));
+ break;
+
+ case unlinkIdTag: // { UNLINK_ID, UnlinkId, FromPid, ToPid}
+ case unlinkIdAckTag: // { UNLINK_ID_Ack, UnlinkId, FromPid, ToPid}
+ if (traceLevel >= ctrlThreshold) {
+ System.out.println("<- " + headerType(head) + " "
+ + head);
+ }
+
+ long unlink_id = ((OtpErlangLong) head.elementAt(1)).longValue();
+ from = (OtpErlangPid) head.elementAt(2);
+ to = (OtpErlangPid) head.elementAt(3);
+
+ deliver(new OtpMsg(tag, from, to, unlink_id));
break;
// absolutely no idea what to do with these, so we ignore
@@ -892,6 +948,12 @@ public abstract class AbstractConnection extends Thread {
case unlinkTag:
return "UNLINK";
+ case unlinkIdTag:
+ return "UNLINK_ID";
+
+ case unlinkIdAckTag:
+ return "UNLINK_ID_ACK";
+
case regSendTag:
return "REG_SEND";
@@ -953,10 +1015,12 @@ public abstract class AbstractConnection extends Thread {
}
protected void doAccept() throws IOException, OtpAuthException {
+ final int send_name_tag = recvName(peer);
try {
sendStatus("ok");
final int our_challenge = genChallenge();
- sendChallenge(peer.distChoose, localNode.flags, our_challenge);
+ sendChallenge(peer.flags, localNode.flags, our_challenge);
+ recvComplement(send_name_tag);
final int her_challenge = recvChallengeReply(our_challenge);
final byte[] our_digest = genDigest(her_challenge,
localNode.cookie());
@@ -992,12 +1056,14 @@ public abstract class AbstractConnection extends Thread {
System.out.println("-> MD5 CONNECT TO " + peer.host() + ":"
+ port);
}
- sendName(peer.distChoose, localNode.flags);
+ final int send_name_tag = sendName(peer.distChoose, localNode.flags,
+ localNode.creation);
recvStatus();
final int her_challenge = recvChallenge();
final byte[] our_digest = genDigest(her_challenge,
localNode.cookie());
final int our_challenge = genChallenge();
+ sendComplement(send_name_tag);
sendChallengeReply(our_challenge, our_digest);
recvChallengeAck(our_challenge);
cookieOk = true;
@@ -1070,17 +1136,31 @@ public abstract class AbstractConnection extends Thread {
return res;
}
- protected void sendName(final int dist, final int aflags)
+ protected int sendName(final int dist, final long aflags,
+ final int creation)
throws IOException {
@SuppressWarnings("resource")
final OtpOutputStream obuf = new OtpOutputStream();
final String str = localNode.node();
- obuf.write2BE(str.length() + 7); // 7 bytes + nodename
- obuf.write1(AbstractNode.NTYPE_R6);
- obuf.write2BE(dist);
- obuf.write4BE(aflags);
- obuf.write(str.getBytes());
+ int send_name_tag;
+ if (dist == 5) {
+ obuf.write2BE(1+2+4 + str.length());
+ send_name_tag = 'n';
+ obuf.write1(send_name_tag);
+ obuf.write2BE(dist);
+ obuf.write4BE(aflags);
+ obuf.write(str.getBytes());
+ }
+ else {
+ obuf.write2BE(1+8+4+2 + str.length());
+ send_name_tag = 'N';
+ obuf.write1(send_name_tag);
+ obuf.write8BE(aflags);
+ obuf.write4BE(creation);
+ obuf.write2BE(str.length());
+ obuf.write(str.getBytes());
+ }
obuf.writeToAndFlush(socket.getOutputStream());
@@ -1088,26 +1168,61 @@ public abstract class AbstractConnection extends Thread {
System.out.println("-> " + "HANDSHAKE sendName" + " flags="
+ aflags + " dist=" + dist + " local=" + localNode);
}
+ return send_name_tag;
+ }
+
+ protected void sendComplement(final int send_name_tag)
+ throws IOException {
+
+ if (send_name_tag == 'n' &&
+ (peer.flags & AbstractNode.dFlagHandshake23) != 0) {
+ @SuppressWarnings("resource")
+ final OtpOutputStream obuf = new OtpOutputStream();
+ obuf.write2BE(1+4+4);
+ obuf.write1('c');
+ final int flagsHigh = (int)(localNode.flags >> 32);
+ obuf.write4BE(flagsHigh);
+ obuf.write4BE(localNode.creation);
+
+ obuf.writeToAndFlush(socket.getOutputStream());
+
+ if (traceLevel >= handshakeThreshold) {
+ System.out.println("-> " + "HANDSHAKE sendComplement" +
+ " flagsHigh=" + flagsHigh +
+ " creation=" + localNode.creation);
+ }
+ }
}
- protected void sendChallenge(final int dist, final int aflags,
- final int challenge) throws IOException {
+ protected void sendChallenge(final long her_flags, final long our_flags,
+ final int challenge) throws IOException {
@SuppressWarnings("resource")
final OtpOutputStream obuf = new OtpOutputStream();
final String str = localNode.node();
- obuf.write2BE(str.length() + 11); // 11 bytes + nodename
- obuf.write1(AbstractNode.NTYPE_R6);
- obuf.write2BE(dist);
- obuf.write4BE(aflags);
- obuf.write4BE(challenge);
- obuf.write(str.getBytes());
+ if ((her_flags & AbstractNode.dFlagHandshake23) == 0) {
+ obuf.write2BE(1+2+4+4 + str.length());
+ obuf.write1('n');
+ obuf.write2BE(5);
+ obuf.write4BE(our_flags & 0xffffffff);
+ obuf.write4BE(challenge);
+ obuf.write(str.getBytes());
+ }
+ else {
+ obuf.write2BE(1+8+4+4+2 + str.length());
+ obuf.write1('N');
+ obuf.write8BE(our_flags);
+ obuf.write4BE(challenge);
+ obuf.write4BE(localNode.creation);
+ obuf.write2BE(str.length());
+ obuf.write(str.getBytes());
+ }
obuf.writeToAndFlush(socket.getOutputStream());
if (traceLevel >= handshakeThreshold) {
System.out.println("-> " + "HANDSHAKE sendChallenge" + " flags="
- + aflags + " dist=" + dist + " challenge=" + challenge
+ + our_flags + " challenge=" + challenge
+ " local=" + localNode);
}
}
@@ -1127,8 +1242,8 @@ public abstract class AbstractConnection extends Thread {
return tmpbuf;
}
- protected void recvName(final OtpPeer apeer) throws IOException {
-
+ protected int recvName(final OtpPeer apeer) throws IOException {
+ int send_name_tag;
String hisname = "";
try {
@@ -1137,25 +1252,31 @@ public abstract class AbstractConnection extends Thread {
final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0);
byte[] tmpname;
final int len = tmpbuf.length;
- apeer.ntype = ibuf.read1();
- if (apeer.ntype != AbstractNode.NTYPE_R6) {
- throw new IOException("Unknown remote node type");
- }
- apeer.distLow = apeer.distHigh = ibuf.read2BE();
- if (apeer.distLow < 5) {
+ send_name_tag = ibuf.read1();
+ switch (send_name_tag) {
+ case 'n':
+ apeer.distLow = apeer.distHigh = ibuf.read2BE();
+ if (apeer.distLow != 5)
+ throw new IOException("Invalid handshake version");
+ apeer.flags = ibuf.read4BE();
+ tmpname = new byte[len - 7];
+ ibuf.readN(tmpname);
+ hisname = OtpErlangString.newString(tmpname);
+ break;
+ case 'N':
+ apeer.distLow = apeer.distHigh = 6;
+ apeer.flags = ibuf.read8BE();
+ if ((apeer.flags & AbstractNode.dFlagHandshake23) == 0)
+ throw new IOException("Missing DFLAG_HANDSHAKE_23");
+ apeer.creation = ibuf.read4BE();
+ int namelen = ibuf.read2BE();
+ tmpname = new byte[namelen];
+ ibuf.readN(tmpname);
+ hisname = OtpErlangString.newString(tmpname);
+ break;
+ default:
throw new IOException("Unknown remote node type");
}
- 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 ((apeer.flags & AbstractNode.dFlagPublished) != 0) {
- apeer.ntype = AbstractNode.NTYPE_R4_ERLANG;
- } else {
- apeer.ntype = AbstractNode.NTYPE_R4_HIDDEN;
- }
if ((apeer.flags & AbstractNode.dFlagExtendedReferences) == 0) {
throw new IOException(
@@ -1180,6 +1301,7 @@ public abstract class AbstractConnection extends Thread {
System.out.println("<- " + "HANDSHAKE" + " ntype=" + apeer.ntype
+ " dist=" + apeer.distHigh + " remote=" + apeer);
}
+ return send_name_tag;
}
protected int recvChallenge() throws IOException {
@@ -1190,14 +1312,31 @@ public abstract class AbstractConnection extends Thread {
final byte[] buf = read2BytePackage();
@SuppressWarnings("resource")
final OtpInputStream ibuf = new OtpInputStream(buf, 0);
- peer.ntype = ibuf.read1();
- if (peer.ntype != AbstractNode.NTYPE_R6) {
+ int namelen;
+ switch (ibuf.read1()) {
+ case 'n':
+ if (peer.distChoose != 5)
+ throw new IOException("Old challenge wrong version");
+ peer.distLow = peer.distHigh = ibuf.read2BE();
+ peer.flags = ibuf.read4BE();
+ if ((peer.flags & AbstractNode.dFlagHandshake23) != 0)
+ throw new IOException("Old challenge unexpected DFLAG_HANDHAKE_23");
+ challenge = ibuf.read4BE();
+ namelen = buf.length - (1+2+4+4);
+ break;
+ case 'N':
+ peer.distLow = peer.distHigh = peer.distChoose = 6;
+ peer.flags = ibuf.read8BE();
+ if ((peer.flags & AbstractNode.dFlagHandshake23) == 0)
+ throw new IOException("New challenge missing DFLAG_HANDHAKE_23");
+ challenge = ibuf.read4BE();
+ peer.creation = ibuf.read4BE();
+ namelen = ibuf.read2BE();
+ break;
+ default:
throw new IOException("Unexpected peer type");
}
- peer.distLow = peer.distHigh = ibuf.read2BE();
- peer.flags = ibuf.read4BE();
- challenge = ibuf.read4BE();
- final byte[] tmpname = new byte[buf.length - 11];
+ final byte[] tmpname = new byte[namelen];
ibuf.readN(tmpname);
final String hisname = OtpErlangString.newString(tmpname);
if (!hisname.equals(peer.node)) {
@@ -1228,6 +1367,27 @@ public abstract class AbstractConnection extends Thread {
return challenge;
}
+ protected void recvComplement(int send_name_tag) throws IOException {
+
+ if (send_name_tag == 'n' &&
+ (peer.flags & AbstractNode.dFlagHandshake23) != 0) {
+ try {
+ final byte[] tmpbuf = read2BytePackage();
+ @SuppressWarnings("resource")
+ final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0);
+ if (ibuf.read1() != 'c')
+ throw new IOException("Not a complement tag");
+
+ final long flagsHigh = ibuf.read4BE();
+ peer.flags |= flagsHigh << 32;
+ peer.creation = ibuf.read4BE();
+
+ } catch (final OtpErlangDecodeException e) {
+ throw new IOException("Handshake failed - not enough data");
+ }
+ }
+ }
+
protected void sendChallengeReply(final int challenge, final byte[] digest)
throws IOException {
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 c3f71a84f0..09add55819 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java
@@ -74,10 +74,7 @@ public class AbstractNode implements OtpTransportFactory {
static String defaultCookie = null;
final OtpTransportFactory transportFactory;
- // Node types
static final int NTYPE_R6 = 110; // 'n' post-r5, all nodes
- static final int NTYPE_R4_ERLANG = 109; // 'm' Only for source compatibility
- static final int NTYPE_R4_HIDDEN = 104; // 'h' Only for source compatibility
// Node capability flags
static final int dFlagPublished = 1;
@@ -96,17 +93,21 @@ public class AbstractNode implements OtpTransportFactory {
static final int dFlagUtf8Atoms = 0x10000;
static final int dFlagMapTag = 0x20000;
static final int dFlagBigCreation = 0x40000;
+ static final int dFlagHandshake23 = 0x1000000;
+ static final int dFlagUnlinkId = 0x2000000;
int ntype = NTYPE_R6;
int proto = 0; // tcp/ip
- int distHigh = 5; // Cannot talk to nodes before R6
+ int distHigh = 6;
int distLow = 5; // Cannot talk to nodes before R6
int creation = 0;
- int flags = dFlagExtendedReferences | dFlagExtendedPidsPorts
+ long flags = dFlagExtendedReferences | dFlagExtendedPidsPorts
| dFlagBitBinaries | dFlagNewFloats | dFlagFunTags
| dflagNewFunTags | dFlagUtf8Atoms | dFlagMapTag
| dFlagExportPtrTag
- | dFlagBigCreation;
+ | dFlagBigCreation
+ | dFlagHandshake23
+ | dFlagUnlinkId;
/* initialize hostname and default cookie */
static {
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 18aa825759..78890d1cde 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java
@@ -23,11 +23,14 @@ package com.ericsson.otp.erlang;
class Link {
private final OtpErlangPid local;
private final OtpErlangPid remote;
+ private long unlinking = 0;
private int hashCodeValue = 0;
public Link(final OtpErlangPid local, final OtpErlangPid remote) {
this.local = local;
this.remote = remote;
+ this.unlinking = 0;
+
}
public OtpErlangPid local() {
@@ -47,6 +50,14 @@ class Link {
|| local.equals(aremote) && remote.equals(alocal);
}
+ public long getUnlinking() {
+ return this.unlinking;
+ }
+
+ public void setUnlinking(long unlink_id) {
+ this.unlinking = unlink_id;
+ }
+
@Override
public int hashCode() {
if (hashCodeValue == 0) {
diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/Links.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/Links.java
index 5f1bd40e76..032c7480af 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/Links.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/Links.java
@@ -23,6 +23,7 @@ package com.ericsson.otp.erlang;
class Links {
Link[] links;
int count;
+ int active;
Links() {
this(10);
@@ -31,68 +32,132 @@ class Links {
Links(final int initialSize) {
links = new Link[initialSize];
count = 0;
+ active = 0;
}
- synchronized void addLink(final OtpErlangPid local,
- final OtpErlangPid remote) {
- if (find(local, remote) == -1) {
+ // Try add link and return if it was added or not...
+ // If already existing it will not be added again.
+ // If force is true it is added even if it is
+ // currently unlinking; otherwise not.
+ synchronized boolean addLink(final OtpErlangPid local,
+ final OtpErlangPid remote,
+ final boolean force) {
+ int i = find(local, remote);
+ if (i != -1) {
+ if (links[i].getUnlinking() != 0 && force) {
+ links[i].setUnlinking(0);
+ active++;
+ return true;
+ }
+ return false;
+ }
+ else {
if (count >= links.length) {
final Link[] tmp = new Link[count * 2];
System.arraycopy(links, 0, tmp, 0, count);
links = tmp;
}
links[count++] = new Link(local, remote);
+ active++;
+ return true;
}
}
- synchronized void removeLink(final OtpErlangPid local,
- final OtpErlangPid remote) {
+ // Try remove link and return whether it was active or not...
+ synchronized boolean removeLink(final OtpErlangPid local,
+ final OtpErlangPid remote) {
int i;
if ((i = find(local, remote)) != -1) {
+ long unlinking = links[i].getUnlinking();
count--;
links[i] = links[count];
links[count] = null;
+ if (unlinking == 0) {
+ active--;
+ return true;
+ }
}
+ return false;
}
- synchronized boolean exists(final OtpErlangPid local,
- final OtpErlangPid remote) {
- return find(local, remote) != -1;
+ // Try remove active link and return whether it was removed or not...
+ synchronized boolean removeActiveLink(final OtpErlangPid local,
+ final OtpErlangPid remote) {
+ int i;
+
+ if ((i = find(local, remote)) != -1) {
+ long unlinking = links[i].getUnlinking();
+ if (unlinking != 0)
+ return false;
+ count--;
+ active--;
+ links[i] = links[count];
+ links[count] = null;
+ return true;
+ }
+ return false;
}
- synchronized int find(final OtpErlangPid local, final OtpErlangPid remote) {
- for (int i = 0; i < count; i++) {
- if (links[i].equals(local, remote)) {
- return i;
- }
+ // Remove link if unlink_id match and return whether it was removed or not...
+ synchronized boolean removeUnlinkingLink(final OtpErlangPid local,
+ final OtpErlangPid remote,
+ final long unlink_id) {
+ int i;
+
+ if (unlink_id == 0) {
+ return false;
}
- return -1;
+
+ if ((i = find(local, remote)) != -1) {
+ long unlinking = links[i].getUnlinking();
+ if (unlinking != unlink_id)
+ return false;
+ count--;
+ links[i] = links[count];
+ links[count] = null;
+ return true;
+ }
+ return false;
}
- int count() {
- return count;
+ synchronized boolean setUnlinking(final OtpErlangPid local,
+ final OtpErlangPid remote,
+ final long unlink_id) {
+ int i;
+
+ if (unlink_id == 0) {
+ return false;
+ }
+
+ if ((i = find(local, remote)) != -1) {
+ if (links[i].getUnlinking() == 0) {
+ links[i].setUnlinking(unlink_id);
+ active--;
+ return true;
+ }
+ }
+ return false;
}
- /* all local pids get notified about broken connection */
- synchronized OtpErlangPid[] localPids() {
- OtpErlangPid[] ret = null;
- if (count != 0) {
- ret = new OtpErlangPid[count];
- for (int i = 0; i < count; i++) {
- ret[i] = links[i].local();
+ synchronized int find(final OtpErlangPid local, final OtpErlangPid remote) {
+ for (int i = 0; i < count; i++) {
+ if (links[i].equals(local, remote)) {
+ return i;
}
}
- return ret;
+ return -1;
}
- /* all remote pids get notified about failed pid */
synchronized OtpErlangPid[] remotePids() {
OtpErlangPid[] ret = null;
- if (count != 0) {
- ret = new OtpErlangPid[count];
- for (int i = 0; i < count; i++) {
- ret[i] = links[i].remote();
+ if (active != 0) {
+ int a = 0;
+ ret = new OtpErlangPid[active];
+ for (int i = 0; a < active; i++) {
+ if (links[i].getUnlinking() == 0) {
+ ret[a++] = links[i].remote();
+ }
}
}
return ret;
@@ -112,13 +177,4 @@ class Links {
return ret;
}
- /* returns a copy of the link table */
- synchronized Link[] links() {
- Link[] ret = null;
- if (count != 0) {
- ret = new Link[count];
- System.arraycopy(links, 0, ret, 0, count);
- }
- return ret;
- }
}
diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile b/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile
index ee616f3d7e..bcbb206db6 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile
@@ -67,8 +67,11 @@ JARFILE= OtpErlang.jar
# ----------------------------------------------------
# Programs and Flags
# ----------------------------------------------------
-
-JAR= jar
+ifeq ($(TARGET),win32)
+ JAR=jar.exe
+else
+ JAR=jar
+endif
CLASSPATH = $(JAVA_SRC_ROOT)
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 eb3eaa1f15..0cfb1bb39d 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java
@@ -49,6 +49,8 @@ import java.io.IOException;
public class OtpConnection extends AbstractConnection {
protected OtpSelf self;
protected GenericQueue queue; // messages get delivered here
+ protected Links links;
+ private long unlink_id;
/*
* Accept an incoming connection from a remote node. Used by {@link
@@ -96,7 +98,58 @@ public class OtpConnection extends AbstractConnection {
@Override
public void deliver(final OtpMsg msg) {
- queue.put(msg);
+ switch (msg.type()) {
+ case OtpMsg.exitTag:
+ case OtpMsg.linkTag:
+ case OtpMsg.unlinkTag:
+ case AbstractConnection.unlinkIdTag:
+ case AbstractConnection.unlinkIdAckTag:
+ handle_link_operation(msg);
+ break;
+ default:
+ queue.put(msg);
+ break;
+ }
+ }
+
+ private synchronized void handle_link_operation(final OtpMsg m) {
+ final OtpErlangPid remote = m.getSenderPid();
+ switch (m.type()) {
+ case OtpMsg.linkTag:
+ // only queue up link-message if link was added...
+ if (links.addLink(self.pid(), remote, false)) {
+ queue.put(m);
+ }
+ break;
+
+ case OtpMsg.unlinkTag:
+ case AbstractConnection.unlinkIdTag: {
+ final long unlink_id = m.getUnlinkId();
+ // only queue up unlink-message if link was removed...
+ if (links.removeActiveLink(self.pid(), remote)) {
+ // Use old unlinkTag without unlink id for
+ // backwards compatibility...
+ queue.put(new OtpMsg(OtpMsg.unlinkTag, self.pid(),
+ remote));
+ }
+ try {
+ super.sendUnlinkAck(self.pid(), remote, unlink_id);
+ } catch (final Exception e) {
+ }
+ break;
+ }
+
+ case AbstractConnection.unlinkIdAckTag:
+ links.removeUnlinkingLink(self.pid(), remote, m.getUnlinkId());
+ break;
+
+ case OtpMsg.exitTag:
+ // only queue up exit-message if link was removed...
+ if (links.removeActiveLink(self.pid(), remote)) {
+ queue.put(m);
+ }
+ break;
+ }
}
/**
@@ -544,7 +597,14 @@ public class OtpConnection extends AbstractConnection {
* occurs.
*/
public void link(final OtpErlangPid dest) throws IOException {
- super.sendLink(self.pid(), dest);
+ if (links.addLink(self.pid(), dest, true)) {
+ try {
+ super.sendLink(self.pid(), dest);
+ } catch (final IOException e) {
+ links.removeLink(self.pid(), dest); // restore...
+ throw e;
+ }
+ }
}
/**
@@ -560,7 +620,17 @@ public class OtpConnection extends AbstractConnection {
* occurs.
*/
public void unlink(final OtpErlangPid dest) throws IOException {
- super.sendUnlink(self.pid(), dest);
+ long unlink_id = this.unlink_id++;
+ if (unlink_id == 0)
+ unlink_id = this.unlink_id++;
+ if (links.setUnlinking(self.pid(), dest, unlink_id)) {
+ try {
+ super.sendUnlink(self.pid(), dest, unlink_id);
+ } catch (final IOException e) {
+ links.addLink(self.pid(), dest, true); // restore...
+ throw e;
+ }
+ }
}
/**
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 011709beab..70ecc5b695 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java
@@ -122,9 +122,7 @@ public class OtpCookedConnection extends AbstractConnection {
switch (msg.type()) {
case OtpMsg.linkTag:
- if (delivered) {
- links.addLink(msg.getRecipientPid(), msg.getSenderPid());
- } else {
+ if (!delivered) {
try {
// no such pid - send exit to sender
super.sendExit(msg.getRecipientPid(), msg.getSenderPid(),
@@ -133,13 +131,7 @@ public class OtpCookedConnection extends AbstractConnection {
}
}
break;
-
- case OtpMsg.unlinkTag:
- case OtpMsg.exitTag:
- links.removeLink(msg.getRecipientPid(), msg.getSenderPid());
- break;
-
- case OtpMsg.exit2Tag:
+ default:
break;
}
@@ -200,30 +192,39 @@ public class OtpCookedConnection extends AbstractConnection {
}
}
- /*
- * snoop for outgoing links and update own table
- */
- synchronized void link(final OtpErlangPid from, final OtpErlangPid to)
- throws OtpErlangExit {
+ void link(final OtpErlangPid from, final OtpErlangPid to)
+ throws OtpErlangExit {
try {
super.sendLink(from, to);
- links.addLink(from, to);
} catch (final IOException e) {
throw new OtpErlangExit("noproc", to);
}
}
- /*
- * snoop for outgoing unlinks and update own table
- */
- synchronized void unlink(final OtpErlangPid from, final OtpErlangPid to) {
- links.removeLink(from, to);
+ void unlink(final OtpErlangPid from, final OtpErlangPid to, final long unlink_id) {
+ try {
+ super.sendUnlink(from, to , unlink_id);
+ } catch (final IOException e) {
+ }
+ }
+
+ void unlink_ack(final OtpErlangPid from, final OtpErlangPid to, final long unlink_id) {
try {
- super.sendUnlink(from, to);
+ super.sendUnlinkAck(from, to , unlink_id);
} catch (final IOException e) {
}
}
+ synchronized void node_link(OtpErlangPid local, OtpErlangPid remote, boolean add)
+ {
+ if (add) {
+ links.addLink(local, remote, true);
+ }
+ else {
+ links.removeLink(local, remote);
+ }
+ }
+
/*
* When the connection fails - send exit to all local pids with links
* through this connection
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 fffb8475d3..008ee9727e 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java
@@ -74,8 +74,9 @@ public class OtpEpmd {
private static final byte port4req = (byte) 122;
private static final byte port4resp = (byte) 119;
- private static final byte publish4req = (byte) 120;
- private static final byte publish4resp = (byte) 121;
+ private static final byte ALIVE2_REQ = (byte) 120;
+ private static final byte ALIVE2_RESP = (byte) 121;
+ private static final byte ALIVE2_X_RESP = (byte) 118;
private static final byte names4req = (byte) 110;
private static int traceLevel = 0;
@@ -287,7 +288,7 @@ public class OtpEpmd {
obuf.write2BE(node.alive().length() + 13);
- obuf.write1(publish4req);
+ obuf.write1(ALIVE2_REQ);
obuf.write2BE(node.port());
obuf.write1(node.type());
@@ -322,10 +323,11 @@ public class OtpEpmd {
final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0);
final int response = ibuf.read1();
- if (response == publish4resp) {
+ if (response == ALIVE2_RESP || response == ALIVE2_X_RESP) {
final int result = ibuf.read1();
if (result == 0) {
- node.creation = ibuf.read2BE();
+ node.creation = (response == ALIVE2_RESP
+ ? ibuf.read2BE() : ibuf.read4BE());
if (traceLevel >= traceThreshold) {
System.out.println("<- OK");
}
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 9cbd735751..3abdf9535f 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java
@@ -27,7 +27,6 @@ public class OtpErlangPid extends OtpErlangObject implements Comparable<Object>
// don't change this!
private static final long serialVersionUID = 1664394142301803659L;
- private final int tag;
private final String node;
private final int id;
private final int serial;
@@ -45,7 +44,6 @@ public class OtpErlangPid extends OtpErlangObject implements Comparable<Object>
public OtpErlangPid(final OtpLocalNode self) {
final OtpErlangPid p = self.createPid();
- tag = p.tag;
id = p.id;
serial = p.serial;
creation = p.creation;
@@ -67,7 +65,6 @@ public class OtpErlangPid extends OtpErlangObject implements Comparable<Object>
throws OtpErlangDecodeException {
final OtpErlangPid p = buf.read_pid();
- tag = p.tag;
node = p.node();
id = p.id();
serial = p.serial();
@@ -118,7 +115,6 @@ public class OtpErlangPid extends OtpErlangObject implements Comparable<Object>
*/
protected OtpErlangPid(final int tag, final String node, final int id,
final int serial, final int creation) {
- this.tag = tag;
this.node = node;
if (tag == OtpExternal.pidTag) {
this.id = id & 0x7fff; // 15 bits
@@ -133,7 +129,7 @@ public class OtpErlangPid extends OtpErlangObject implements Comparable<Object>
}
protected int tag() {
- return tag;
+ return OtpExternal.newPidTag;
}
/**
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 79b5d2736c..c8648d7aa3 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java
@@ -26,7 +26,6 @@ public class OtpErlangPort extends OtpErlangObject {
// don't change this!
private static final long serialVersionUID = 4037115468007644704L;
- private final int tag;
private final String node;
private final int id;
private final int creation;
@@ -43,7 +42,6 @@ public class OtpErlangPort extends OtpErlangObject {
private OtpErlangPort(final OtpSelf self) {
final OtpErlangPort p = self.createPort();
- tag = p.tag;
id = p.id;
creation = p.creation;
node = p.node;
@@ -64,7 +62,6 @@ public class OtpErlangPort extends OtpErlangObject {
throws OtpErlangDecodeException {
final OtpErlangPort p = buf.read_port();
- tag = p.tag;
node = p.node();
id = p.id();
creation = p.creation();
@@ -105,7 +102,6 @@ public class OtpErlangPort extends OtpErlangObject {
*/
public OtpErlangPort(final int tag, final String node, final int id,
final int creation) {
- this.tag = tag;
this.node = node;
if (tag == OtpExternal.portTag) {
this.id = id & 0xfffffff; // 28 bits
@@ -118,7 +114,7 @@ public class OtpErlangPort extends OtpErlangObject {
}
protected int tag() {
- return tag;
+ return OtpExternal.newPortTag;
}
/**
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 2165397013..2bf8d9a56b 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java
@@ -28,7 +28,6 @@ public class OtpErlangRef extends OtpErlangObject {
// don't change this!
private static final long serialVersionUID = -7022666480768586521L;
- private final int tag;
private final String node;
private final int creation;
@@ -49,7 +48,6 @@ public class OtpErlangRef extends OtpErlangObject {
public OtpErlangRef(final OtpLocalNode self) {
final OtpErlangRef r = self.createRef();
- tag = r.tag;
ids = r.ids;
creation = r.creation;
node = r.node;
@@ -70,7 +68,6 @@ public class OtpErlangRef extends OtpErlangObject {
throws OtpErlangDecodeException {
final OtpErlangRef r = buf.read_ref();
- tag = r.tag;
node = r.node();
creation = r.creation();
@@ -90,7 +87,6 @@ public class OtpErlangRef extends OtpErlangObject {
* another arbitrary number.
*/
public OtpErlangRef(final String node, final int id, final int creation) {
- this.tag = OtpExternal.newRefTag;
this.node = node;
ids = new int[1];
ids[0] = id & 0x3ffff; // 18 bits
@@ -138,7 +134,6 @@ public class OtpErlangRef extends OtpErlangObject {
*/
public OtpErlangRef(final int tag, final String node, final int[] ids,
final int creation) {
- this.tag = tag;
this.node = node;
// use at most 3 words
@@ -162,7 +157,7 @@ public class OtpErlangRef extends OtpErlangObject {
}
protected int tag() {
- return tag;
+ return OtpExternal.newerRefTag;
}
/**
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 6d81ce630b..8cc5b3c21d 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java
@@ -239,6 +239,20 @@ public class OtpInputStream extends ByteArrayInputStream {
}
/**
+ * Read a eight byte big endian integer from the stream.
+ *
+ * @return the bytes read, converted from big endian to a long integer.
+ *
+ * @exception OtpErlangDecodeException
+ * if the next byte cannot be read.
+ */
+ public long read8BE() throws OtpErlangDecodeException {
+ long high = read4BE();
+ long low = read4BE();
+ return (high << 32) | (low & 0xffffffff);
+ }
+
+ /**
* Read a two byte little endian integer from the stream.
*
* @return the bytes read, converted from little endian to an integer.
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 29a8bc1540..3d46d21d60 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java
@@ -84,6 +84,7 @@ public class OtpMbox {
GenericQueue queue;
String name;
Links links;
+ private long unlink_id;
// package constructor: called by OtpNode:createMbox(name)
// to create a named mbox
@@ -91,6 +92,7 @@ public class OtpMbox {
this.self = self;
this.home = home;
this.name = name;
+ this.unlink_id = 1;
queue = new GenericQueue();
links = new Links(10);
}
@@ -516,7 +518,10 @@ public class OtpMbox {
* or could not be reached.
*
*/
- public void link(final OtpErlangPid to) throws OtpErlangExit {
+ public synchronized void link(final OtpErlangPid to) throws OtpErlangExit {
+ if (!links.addLink(self, to, true))
+ return; /* Already linked... */
+
try {
final String node = to.node();
if (node.equals(home.node())) {
@@ -526,17 +531,18 @@ public class OtpMbox {
} else {
final OtpCookedConnection conn = home.getConnection(node);
if (conn != null) {
- conn.link(self, to);
+ conn.link(self, to); // may throw 'noproc'
+ conn.node_link(self, to, true);
} else {
throw new OtpErlangExit("noproc", to);
}
}
} catch (final OtpErlangExit e) {
+ links.removeLink(self, to);
throw e;
} catch (final Exception e) {
}
- links.addLink(self, to);
}
/**
@@ -552,25 +558,41 @@ public class OtpMbox {
* from.
*
*/
- public void unlink(final OtpErlangPid to) {
- links.removeLink(self, to);
-
- try {
- final String node = to.node();
- if (node.equals(home.node())) {
- home.deliver(new OtpMsg(OtpMsg.unlinkTag, self, to));
- } else {
- final OtpCookedConnection conn = home.getConnection(node);
- if (conn != null) {
- conn.unlink(self, to);
+ public synchronized void unlink(final OtpErlangPid to) {
+ long unlink_id = this.unlink_id++;
+ if (unlink_id == 0)
+ unlink_id = this.unlink_id++;
+ if (links.setUnlinking(self, to, unlink_id)) {
+ try {
+ final String node = to.node();
+ if (node.equals(home.node())) {
+ home.deliver(new OtpMsg(OtpMsg.unlinkTag, self, to));
+ } else {
+ final OtpCookedConnection conn = home.getConnection(node);
+ if (conn != null) {
+ conn.unlink(self, to, unlink_id);
+ }
}
+ } catch (final Exception e) {
}
- } catch (final Exception e) {
}
}
/**
* <p>
+ * Get information about all processes and/or mail boxes currently
+ * linked to this mail box.
+ * </p>
+ *
+ * @return an array of all pids currently linked to this mail box.
+ *
+ */
+ public synchronized OtpErlangPid[] linked() {
+ return links.remotePids();
+ }
+
+ /**
+ * <p>
* Create a connection to a remote node.
* </p>
*
@@ -688,40 +710,92 @@ public class OtpMbox {
* called by OtpNode to deliver message to this mailbox.
*
* About exit and exit2: both cause exception to be raised upon receive().
- * However exit (not 2) causes any link to be removed as well, while exit2
- * leaves any links intact.
+ * However exit (not 2) only has an effect if there exist a link.
*/
void deliver(final OtpMsg m) {
switch (m.type()) {
+ case OtpMsg.exitTag:
case OtpMsg.linkTag:
- links.addLink(self, m.getSenderPid());
+ case OtpMsg.unlinkTag:
+ case AbstractConnection.unlinkIdTag:
+ case AbstractConnection.unlinkIdAckTag:
+ handle_link_operation(m);
+ break;
+ default:
+ queue.put(m);
+ break;
+ }
+ }
+
+ private synchronized void handle_link_operation(final OtpMsg m) {
+ final OtpErlangPid remote = m.getSenderPid();
+ final String node = remote.node();
+ final boolean is_local = node.equals(home.node());
+ final OtpCookedConnection conn = is_local ? null : home.getConnection(node);
+
+ switch (m.type()) {
+ case OtpMsg.linkTag:
+ if (links.addLink(self, remote, false)) {
+ if (!is_local) {
+ if (conn != null)
+ conn.node_link(self, remote, true);
+ else {
+ links.removeLink(self, remote);
+ queue.put(new OtpMsg(OtpMsg.exitTag, remote, self,
+ new OtpErlangAtom("noconnection")));
+ }
+ }
+ }
break;
case OtpMsg.unlinkTag:
- links.removeLink(self, m.getSenderPid());
+ case AbstractConnection.unlinkIdTag: {
+ final long unlink_id = m.getUnlinkId();
+ final boolean removed = links.removeActiveLink(self, remote);
+ try {
+ if (is_local) {
+ home.deliver(new OtpMsg(AbstractConnection.unlinkIdAckTag,
+ self, remote, unlink_id));
+ } else if (conn != null) {
+ if (removed)
+ conn.node_link(self, remote, false);
+ conn.unlink_ack(self, remote, unlink_id);
+ }
+ } catch (final Exception e) {
+ }
break;
+ }
- case OtpMsg.exitTag:
- links.removeLink(self, m.getSenderPid());
- queue.put(m);
+ case AbstractConnection.unlinkIdAckTag:
+ links.removeUnlinkingLink(self, m.getSenderPid(), m.getUnlinkId());
break;
- case OtpMsg.exit2Tag:
- default:
- queue.put(m);
+ case OtpMsg.exitTag:
+ if (links.removeActiveLink(self, m.getSenderPid())) {
+ queue.put(m);
+ }
break;
}
}
// used to break all known links to this mbox
- void breakLinks(final OtpErlangObject reason) {
+ synchronized void breakLinks(final OtpErlangObject reason) {
final Link[] l = links.clearLinks();
if (l != null) {
final int len = l.length;
for (int i = 0; i < len; i++) {
- exit(1, l[i].remote(), reason);
+ if (l[i].getUnlinking() == 0) {
+ OtpErlangPid remote = l[i].remote();
+ final String node = remote.node();
+ if (!node.equals(home.node())) {
+ final OtpCookedConnection conn = home.getConnection(node);
+ if (conn != null)
+ conn.node_link(self, remote, false);
+ }
+ exit(1, remote, reason);
+ }
}
}
}
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 5bbcf2ab9e..0f883d5deb 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java
@@ -68,6 +68,7 @@ public class OtpMsg {
protected OtpErlangPid from;
protected OtpErlangPid to;
protected String toName;
+ protected long unlink_id;
// send has receiver pid but no sender information
OtpMsg(final OtpErlangPid to, final OtpInputStream paybuf) {
@@ -77,6 +78,7 @@ public class OtpMsg {
toName = null;
this.paybuf = paybuf;
payload = null;
+ this.unlink_id = 0;
}
// send has receiver pid but no sender information
@@ -87,6 +89,7 @@ public class OtpMsg {
toName = null;
paybuf = null;
this.payload = payload;
+ this.unlink_id = 0;
}
// send_reg has sender pid and receiver name
@@ -98,6 +101,7 @@ public class OtpMsg {
to = null;
this.paybuf = paybuf;
payload = null;
+ this.unlink_id = 0;
}
// send_reg has sender pid and receiver name
@@ -109,6 +113,7 @@ public class OtpMsg {
to = null;
paybuf = null;
this.payload = payload;
+ this.unlink_id = 0;
}
// exit (etc) has from, to, reason
@@ -117,8 +122,10 @@ public class OtpMsg {
this.tag = tag;
this.from = from;
this.to = to;
+ this.unlink_id = 0;
paybuf = null;
payload = reason;
+ this.unlink_id = 0;
}
// special case when reason is an atom (i.e. most of the time)
@@ -129,19 +136,52 @@ public class OtpMsg {
this.to = to;
paybuf = null;
payload = new OtpErlangAtom(reason);
+ this.unlink_id = 0;
}
- // other message types (link, unlink)
+ // other message types (link and old unlink)
OtpMsg(final int tag, final OtpErlangPid from, final OtpErlangPid to) {
// convert TT-tags to equiv non-TT versions
- int atag = tag;
- if (tag > 10) {
- atag -= 10;
- }
+ this.tag = drop_tt_tag(tag);
+ this.from = from;
+ this.to = to;
+ this.unlink_id = 0;
+ }
- this.tag = atag;
+ // unlink
+ OtpMsg(final int tag, final OtpErlangPid from, final OtpErlangPid to,
+ final long unlink_id) {
+ // convert TT-tags to equiv non-TT versions
+ this.tag = drop_tt_tag(tag);
this.from = from;
this.to = to;
+ this.unlink_id = unlink_id;
+ }
+
+ private int drop_tt_tag(final int tag) {
+ switch (tag) {
+ case AbstractConnection.sendTTTag:
+ return OtpMsg.sendTag;
+ case AbstractConnection.exitTTTag:
+ return OtpMsg.exitTag;
+ case AbstractConnection.regSendTTTag:
+ return OtpMsg.regSendTag;
+ case AbstractConnection.exit2TTTag:
+ return OtpMsg.exit2Tag;
+ default:
+ return tag;
+ }
+ }
+
+ /**
+ * Get unlink identifier of an unlink or unlink acknowledgment
+ * message. For package internal use only.
+ *
+ * @return the serialized Erlang term contained in this message.
+ *
+ */
+ long getUnlinkId() {
+ return this.unlink_id;
}
/**
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 5e777c1164..917e5baf3a 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java
@@ -713,7 +713,7 @@ public class OtpOutputStream extends ByteArrayOutputStream {
*/
public void write_pid(final String node, final int id, final int serial,
final int creation) {
- write1(OtpExternal.pidTag);
+ write1(OtpExternal.newPidTag);
write_atom(node);
write4BE(id & 0x7fff); // 15 bits
write4BE(serial & 0x1fff); // 13 bits
@@ -727,20 +727,11 @@ public class OtpOutputStream extends ByteArrayOutputStream {
* the pid
*/
public void write_pid(OtpErlangPid pid) {
- write1(pid.tag());
+ write1(OtpExternal.newPidTag);
write_atom(pid.node());
write4BE(pid.id());
write4BE(pid.serial());
- switch (pid.tag()) {
- case OtpExternal.pidTag:
- write1(pid.creation());
- break;
- case OtpExternal.newPidTag:
- write4BE(pid.creation());
- break;
- default:
- throw new AssertionError("Invalid pid tag " + pid.tag());
- }
+ write4BE(pid.creation());
}
@@ -758,7 +749,7 @@ public class OtpOutputStream extends ByteArrayOutputStream {
* be used.
*/
public void write_port(final String node, final int id, final int creation) {
- write1(OtpExternal.portTag);
+ write1(OtpExternal.newPortTag);
write_atom(node);
write4BE(id & 0xfffffff); // 28 bits
write1(creation & 0x3); // 2 bits
@@ -771,19 +762,10 @@ public class OtpOutputStream extends ByteArrayOutputStream {
* the port.
*/
public void write_port(OtpErlangPort port) {
- write1(port.tag());
+ write1(OtpExternal.newPortTag);
write_atom(port.node());
write4BE(port.id());
- switch (port.tag()) {
- case OtpExternal.portTag:
- write1(port.creation());
- break;
- case OtpExternal.newPortTag:
- write4BE(port.creation());
- break;
- default:
- throw new AssertionError("Invalid port tag " + port.tag());
- }
+ write4BE(port.creation());
}
/**
@@ -829,7 +811,7 @@ public class OtpOutputStream extends ByteArrayOutputStream {
arity = 3; // max 3 words in ref
}
- write1(OtpExternal.newRefTag);
+ write1(OtpExternal.newerRefTag);
// how many id values
write2BE(arity);
@@ -857,24 +839,12 @@ public class OtpOutputStream extends ByteArrayOutputStream {
int[] ids = ref.ids();
int arity = ids.length;
- write1(ref.tag());
+ write1(OtpExternal.newerRefTag);
write2BE(arity);
write_atom(ref.node());
+ write4BE(ref.creation());
- switch (ref.tag()) {
- case OtpExternal.newRefTag:
- write1(ref.creation());
- write4BE(ids[0] & 0x3ffff); // first word gets truncated to 18 bits
- break;
- case OtpExternal.newerRefTag:
- write4BE(ref.creation());
- write4BE(ids[0]); // full first word
- break;
- default:
- throw new AssertionError("Invalid ref tag " + ref.tag());
- }
-
- for (int i = 1; i < arity; i++) {
+ for (int i = 0; i < arity; i++) {
write4BE(ids[i]);
}
}
diff --git a/lib/jinterface/test/jinterface_SUITE.erl b/lib/jinterface/test/jinterface_SUITE.erl
index 468981a557..1602f4a8b2 100644
--- a/lib/jinterface/test/jinterface_SUITE.erl
+++ b/lib/jinterface/test/jinterface_SUITE.erl
@@ -31,7 +31,12 @@
java_internal_send_receive_different_nodes/1,
java_internal_send_receive_self/1,
java_link_and_exit/1, erl_link_and_exit/1,
- erl_link_java_exit/1, java_link_erl_exit/1,
+ erl_link_unlink_link_and_exit/1,
+ erl_link_java_unlink_link_and_exit/1,
+ simultaneous_erl_link_java_link_unlink/1,
+ simultaneous_erl_link_unlink_java_link/1,
+ erl_link_java_exit/1,
+ java_link_erl_exit/1,
internal_link_linking_exits/1, internal_link_linked_exits/1,
internal_unlink_linking_exits/1, internal_unlink_linked_exits/1,
normal_exit/1, kill_mbox/1,kill_erl_proc_from_java/1,
@@ -77,6 +82,10 @@
-define(kill_mbox_from_erlang,12).
-define(erl_exit_with_reason_any_term,13).
-define(java_exit_with_reason_any_term,14).
+-define(erl_link_unlink_link_and_exit, 15).
+-define(erl_link_java_unlink_link_and_exit, 16).
+-define(simultaneous_erl_link_java_link_unlink, 17).
+-define(simultaneous_erl_link_unlink_java_link, 18).
%% Test cases in NodeStatusHandler.java
@@ -148,6 +157,10 @@ link_unlink() ->
%% Implemented in MboxLinkUnlink.java
java_link_and_exit,
erl_link_and_exit,
+ erl_link_unlink_link_and_exit,
+ erl_link_java_unlink_link_and_exit,
+ simultaneous_erl_link_java_link_unlink,
+ simultaneous_erl_link_unlink_java_link,
erl_link_java_exit,
java_link_erl_exit,
internal_link_linking_exits,
@@ -393,6 +406,77 @@ erl_link_and_exit(Config) when is_list(Config) ->
end,
erl_java_link(LinkFun,erl_link_and_exit,Config).
+erl_link_unlink_link_and_exit(Config) when is_list(Config) ->
+ LinkFun = fun(Mbox) ->
+ link(Mbox),
+ unlink(Mbox),
+ link(Mbox),
+ Mbox ! {?erl_link_unlink_link_and_exit,self(),?link_test_reason},
+ receive ok -> ok end,
+ exit(?link_test_reason)
+ end,
+ erl_java_link(LinkFun,erl_link_unlink_link_and_exit,Config).
+
+erl_link_java_unlink_link_and_exit(Config) when is_list(Config) ->
+ LinkFun = fun(Mbox) ->
+ link(Mbox),
+ Mbox ! {?erl_link_java_unlink_link_and_exit,self(),?link_test_reason},
+ receive ok -> ok end,
+ exit(?link_test_reason)
+ end,
+ erl_java_link(LinkFun,erl_link_java_unlink_link_and_exit,Config).
+
+simultaneous_erl_link_java_link_unlink(Config) when is_list(Config) ->
+ LinkFun = fun(Mbox) ->
+ GoTime = os:system_time(millisecond) + 500,
+ Mbox ! {?simultaneous_erl_link_java_link_unlink, self(), GoTime},
+ spin_wait_until(fun () ->
+ os:system_time(millisecond) >= GoTime
+ end),
+ link(Mbox),
+ receive check_link -> ok end,
+ %% We now know the unlink should have reached us and we
+ %% should have sent the unlink ack...
+ Mbox ! check_link,
+ {links, Links} = process_info(self(), links),
+ Expect = case lists:member(Mbox, Links) of
+ true -> linked;
+ false -> not_linked
+ end,
+ io:format("Expect = ~p~n", [Expect]),
+ receive
+ MboxResult ->
+ MboxResult = Expect
+ end,
+ exit(?link_test_reason)
+ end,
+ erl_java_link(LinkFun,simultaneous_erl_link_java_link_unlink,Config).
+
+simultaneous_erl_link_unlink_java_link(Config) when is_list(Config) ->
+ LinkFun = fun(Mbox) ->
+ GoTime = os:system_time(millisecond) + 500,
+ Mbox ! {?simultaneous_erl_link_unlink_java_link, self(), GoTime},
+ spin_wait_until(fun () ->
+ os:system_time(millisecond) >= GoTime
+ end),
+ link(Mbox),
+ unlink(Mbox),
+ Mbox ! check_link,
+ receive check_link -> ok end,
+ {links, Links} = process_info(self(), links),
+ Expect = case lists:member(Mbox, Links) of
+ true -> linked;
+ false -> not_linked
+ end,
+ io:format("Expect = ~p~n", [Expect]),
+ receive
+ MboxResult ->
+ MboxResult = Expect
+ end,
+ exit(?link_test_reason)
+ end,
+ erl_java_link(LinkFun,simultaneous_erl_link_unlink_java_link,Config).
+
%%%-----------------------------------------------------------------
erl_link_java_exit(doc) ->
["MboxLinkUnlink.java: "
@@ -856,3 +940,11 @@ erl_status_server([{Tag,NodeName,Up}|Rest],_) ->
end;
erl_status_server([],From) ->
From ! done.
+
+spin_wait_until(Fun) ->
+ case Fun() of
+ true -> ok;
+ _ -> spin_wait_until(Fun)
+ end.
+
+
diff --git a/lib/jinterface/test/jinterface_SUITE_data/MboxLinkUnlink.java b/lib/jinterface/test/jinterface_SUITE_data/MboxLinkUnlink.java
index 8cb4aa2ed6..a0018eee9c 100644
--- a/lib/jinterface/test/jinterface_SUITE_data/MboxLinkUnlink.java
+++ b/lib/jinterface/test/jinterface_SUITE_data/MboxLinkUnlink.java
@@ -50,6 +50,10 @@ class MboxLinkUnlink {
private static final int kill_mbox_from_erlang = 12;
private static final int erl_exit_with_reason_any_term = 13;
private static final int java_exit_with_reason_any_term = 14;
+ private static final int erl_link_unlink_link_and_exit = 15;
+ private static final int erl_link_java_unlink_link_and_exit = 16;
+ private static final int simultaneous_erl_link_java_link_unlink = 17;
+ private static final int simultaneous_erl_link_unlink_java_link = 18;
private static boolean dbg = true;
@@ -94,15 +98,74 @@ class MboxLinkUnlink {
break;
case erl_exit_with_reason_any_term:
case erl_link_and_exit:
+ case erl_link_unlink_link_and_exit:
dbg("Java got \"erl_link_and_exit\" or " +
- "\"erl_exit_with_reason_any_term\"");
+ "\"erl_exit_with_reason_any_term\" or " +
+ "\"erl_link_unlink_link_and_exit\"");
+ if (!is_linked(mbox, (OtpErlangPid)tuple.elementAt(1)))
+ System.exit(17);
mbox.send((OtpErlangPid)tuple.elementAt(1),
new OtpErlangAtom("ok"));
waiting = true;
expected = tuple.elementAt(2);
mbox.receive(1000);
System.exit(2);
- break;
+ break;
+ case erl_link_java_unlink_link_and_exit:
+ dbg("Java got \"erl_link_java_unlink_link_and_exit\"");
+ mbox.unlink((OtpErlangPid)tuple.elementAt(1));
+ mbox.unlink((OtpErlangPid)tuple.elementAt(1));
+ mbox.link((OtpErlangPid)tuple.elementAt(1));
+ if (!is_linked(mbox, (OtpErlangPid)tuple.elementAt(1)))
+ System.exit(16);
+ mbox.send((OtpErlangPid)tuple.elementAt(1),
+ new OtpErlangAtom("ok"));
+ waiting = true;
+ expected = tuple.elementAt(2);
+ mbox.receive(1000);
+ System.exit(2);
+ break;
+ case simultaneous_erl_link_java_link_unlink: {
+ dbg("Java got \"simultaneous_erl_link_java_link_unlink\"");
+ OtpErlangPid remote = (OtpErlangPid) tuple.elementAt(1);
+ long go_time = ((OtpErlangLong) tuple.elementAt(2)).longValue();
+ spin_wait_until(go_time);
+ mbox.link(remote);
+ mbox.unlink(remote);
+ OtpErlangAtom check_link = new OtpErlangAtom("check_link");
+ mbox.send(remote, check_link);
+ OtpErlangObject chk_msg = mbox.receive(2000);
+ if (chk_msg == null)
+ System.exit(14);
+ else if (!((OtpErlangAtom) chk_msg).equals(check_link))
+ System.exit(15);
+ mbox.send(remote, new OtpErlangAtom(is_linked(mbox, remote)
+ ? "linked"
+ : "not_linked"));
+ mbox.close();
+ break;
+ }
+ case simultaneous_erl_link_unlink_java_link: {
+ dbg("Java got \"simultaneous_erl_link_unlink_java_link\"");
+ OtpErlangPid remote = (OtpErlangPid) tuple.elementAt(1);
+ long go_time = ((OtpErlangLong) tuple.elementAt(2)).longValue();
+ spin_wait_until(go_time);
+ mbox.link(remote);
+ OtpErlangAtom check_link = new OtpErlangAtom("check_link");
+ OtpErlangObject chk_msg = mbox.receive(2000);
+ if (chk_msg == null)
+ System.exit(14);
+ else if (!((OtpErlangAtom) chk_msg).equals(check_link))
+ System.exit(15);
+ // We now know the unlink should have reached us and we
+ // should have sent the unlink ack...
+ mbox.send(remote, check_link);
+ mbox.send(remote, new OtpErlangAtom(is_linked(mbox, remote)
+ ? "linked"
+ : "not_linked"));
+ mbox.close();
+ break;
+ }
case erl_link_java_exit:
dbg("Java got \"erl_link_java_exit\"");
mbox.exit(tuple.elementAt(2));
@@ -213,4 +276,23 @@ class MboxLinkUnlink {
if (dbg) System.out.println(str);
}
+ private static void spin_wait_until(final long ms) {
+ long time;
+ do {
+ time = System.currentTimeMillis();
+ } while (time < ms);
+ }
+
+ private static boolean is_linked(OtpMbox mbox, OtpErlangPid pid) {
+ final OtpErlangPid[] linked = mbox.linked();
+ if (linked != null) {
+ final int len = linked.length;
+ for (int i = 0; i < len; i++) {
+ if (pid.equals(linked[i]))
+ return true;
+ }
+ }
+ return false;
+ }
+
}
diff --git a/lib/jinterface/vsn.mk b/lib/jinterface/vsn.mk
index f15a3f323b..5209c63af3 100644
--- a/lib/jinterface/vsn.mk
+++ b/lib/jinterface/vsn.mk
@@ -1 +1 @@
-JINTERFACE_VSN = 1.10.1
+JINTERFACE_VSN = 1.11.1
diff --git a/lib/kernel/Makefile b/lib/kernel/Makefile
index b956f5eaf5..35d20fee0d 100644
--- a/lib/kernel/Makefile
+++ b/lib/kernel/Makefile
@@ -34,3 +34,7 @@ SPECIAL_TARGETS =
# Default Subdir Targets
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+
+DIA_PLT_APPS=crypto
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/kernel/doc/src/Makefile b/lib/kernel/doc/src/Makefile
index fe3dc9dab5..d02e954ba9 100644
--- a/lib/kernel/doc/src/Makefile
+++ b/lib/kernel/doc/src/Makefile
@@ -28,11 +28,6 @@ VSN=$(KERNEL_VSN)
APPLICATION=kernel
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -46,6 +41,7 @@ XML_REF3_FILES = application.xml \
erl_epmd.xml \
erl_prim_loader_stub.xml \
erlang_stub.xml \
+ erpc.xml \
error_handler.xml \
error_logger.xml \
file.xml \
@@ -67,12 +63,15 @@ XML_REF3_FILES = application.xml \
net_adm.xml \
net_kernel.xml \
os.xml \
+ pg.xml \
pg2.xml \
rpc.xml \
seq_trace.xml \
+ socket.xml \
wrap_log_reader.xml \
user.xml \
- zlib_stub.xml
+ zlib_stub.xml \
+ $(XML_REF3_ESOCK_EFILES)
XML_REF4_FILES = app.xml config.xml
@@ -82,8 +81,10 @@ XML_PART_FILES = part.xml
XML_CHAPTER_FILES = \
notes.xml \
introduction_chapter.xml \
+ socket_usage.xml \
logger_chapter.xml \
- logger_cookbook.xml
+ logger_cookbook.xml \
+ eep48_chapter.xml
BOOK_FILES = book.xml
@@ -96,86 +97,8 @@ XML_FILES = \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF4_FILES)\
$(XML_REF6_FILES) $(XML_APPLICATION_FILES)
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN4_FILES = $(XML_REF4_FILES:%.xml=$(MAN4DIR)/%.4)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
-
TOP_SPECS_FILE = specs.xml
-
-# ----------------------------------------------------
-# FIGURES
-# ----------------------------------------------------
-# In order to update the figures you have to have both dia
-# and imagemagick installed.
-# The generated .png file must be committed.
-
-update_png:
- dia --export=logger_arch.eps logger_arch.dia
- convert logger_arch.eps -resize 65% logger_arch.png
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-SPECS_ESRC = ../../src
-
-SPECS_FLAGS = -I../../include
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%: %
- $(INSTALL_DATA) $< $@
-
-docs: man pdf html
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: images $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES) $(MAN4_FILES) $(MAN6_FILES)
-
-images: $(IMAGE_FILES:%=$(HTMLDIR)/%)
-
-info:
- @echo "XML_APPLICATION_FILES: $(XML_APPLICATION_FILES)"
- @echo "XML_REF3_ESOCK_FILES: $(XML_REF3_ESOCK_FILES)"
- @echo "XML_REF3_FILES: $(XML_REF3_FILES)"
- @echo "XML_REF4_FILES: $(XML_REF4_FILES)"
- @echo "XML_REF6_FILES: $(XML_REF6_FILES)"
- @echo "XML_PART_FILES: $(XML_PART_FILES)"
- @echo "XML_CHAPTER_FILES: $(XML_CHAPTER_FILES)"
- @echo "BOOK_FILES: $(BOOK_FILES)"
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(MAN4DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECDIR)/*
- rm -f errs core *~ *.eps
-
$(SPECDIR)/specs_erl_prim_loader_stub.xml:
$(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) \
-o$(dir $@) -module erl_prim_loader_stub
@@ -189,24 +112,17 @@ $(SPECDIR)/specs_zlib_stub.xml:
$(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) \
-o$(dir $@) -module zlib_stub
+NO_CHUNKS = erl_prim_loader_stub.xml erlang_stub.xml init_stub.xml zlib_stub.xml
# ----------------------------------------------------
-# Release Target
+# FIGURES
# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
+# In order to update the figures you have to have both dia
+# and imagemagick installed.
+# The generated .png file must be committed.
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man4"
- $(INSTALL_DATA) $(MAN4_FILES) "$(RELEASE_PATH)/man/man4"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
+update_png:
+ dia --export=logger_arch.eps logger_arch.dia
+ convert logger_arch.eps -resize 65% logger_arch.png
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/kernel/doc/src/app.xml b/lib/kernel/doc/src/app.xml
index d2e9390d7e..8b8ac4c010 100644
--- a/lib/kernel/doc/src/app.xml
+++ b/lib/kernel/doc/src/app.xml
@@ -159,14 +159,14 @@ ApplicationVersion = string()</code>
of a configuration parameter is retrieved by calling
<c>application:get_env/1,2</c>. The values in the application
resource file can be overridden by values in a configuration
- file (see <seealso marker="config"><c>config(4)</c></seealso>)
+ file (see <seefile marker="config"><c>config(4)</c></seefile>)
or by command-line flags (see
- <seealso marker="erts:erl"><c>erts:erl(1)</c></seealso>).</p>
+ <seecom marker="erts:erl"><c>erts:erl(1)</c></seecom>).</p>
</item>
<tag><c>mod</c></tag>
<item>
<p>Specifies the application callback module and a start argument, see
- <seealso marker="application"><c>application(3)</c></seealso>.</p>
+ <seeerl marker="application"><c>application(3)</c></seeerl>.</p>
<p>Key <c>mod</c> is necessary for an application
implemented as a supervision tree, otherwise the application
controller does not know how to start it. <c>mod</c>
@@ -197,7 +197,7 @@ ApplicationVersion = string()</code>
<p>This implies that for an included application, the set of
start phases must be a subset of the set of phases defined
for the primary application. For more information, see
- <seealso marker="doc/design_principles:applications">OTP Design Principles</seealso>.
+ <seeguide marker="system/design_principles:applications">OTP Design Principles</seeguide>.
</p>
</item>
<tag>
@@ -211,7 +211,7 @@ ApplicationVersion = string()</code>
application version than the one specified in the
dependency satisfies the requirement. For information about
how to compare application versions, see section
- <seealso marker="doc/system_principles:versions">Versions</seealso>
+ <seeguide marker="system/system_principles:versions">Versions</seeguide>
in the System Principles User's Guide.</p>
<p>Notice that the application version
specifies a source code version. One more, indirect,
@@ -235,8 +235,8 @@ ApplicationVersion = string()</code>
<section>
<title>See Also</title>
- <p><seealso marker="application"><c>application(3)</c></seealso>,
- <seealso marker="sasl:systools"><c>systools(3)</c></seealso></p>
+ <p><seeerl marker="application"><c>application(3)</c></seeerl>,
+ <seeerl marker="sasl:systools"><c>systools(3)</c></seeerl></p>
</section>
</fileref>
diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml
index d402987673..81d97c41bc 100644
--- a/lib/kernel/doc/src/application.xml
+++ b/lib/kernel/doc/src/application.xml
@@ -44,14 +44,14 @@
<em>application resource file</em> named <c>Application.app</c>,
where <c>Application</c> is the application name. For details
about the application specification, see
- <seealso marker="app"><c>app(4)</c></seealso>.</p>
+ <seefile marker="app"><c>app(4)</c></seefile>.</p>
<p>This module can also be viewed as a behaviour for an application
implemented according to the OTP design principles as a
supervision tree. The definition of how to start and stop
the tree is to be located in an <em>application callback module</em>,
exporting a predefined set of functions.</p>
<p>For details about applications and behaviours, see
- <seealso marker="doc/design_principles:des_princ">OTP Design Principles</seealso>.</p>
+ <seeguide marker="system/design_principles:des_princ">OTP Design Principles</seeguide>.</p>
</description>
<datatypes>
<datatype>
@@ -67,22 +67,25 @@
</datatypes>
<funcs>
<func>
- <name name="ensure_all_started" arity="1" since="OTP R16B02"/>
- <name name="ensure_all_started" arity="2" since="OTP R16B02"/>
- <fsummary>Load and start an application and its dependencies, recursively.</fsummary>
- <desc>
- <p>Equivalent to calling
- <seealso marker="#start/1"><c>start/1,2</c></seealso>
- repeatedly on all dependencies that are not yet started for an application.</p>
- <p>Returns <c>{ok, AppNames}</c> for a successful start or for an already started
- application (which is, however, omitted from the <c>AppNames</c> list).</p>
- <p>The function reports <c>{error, {AppName,Reason}}</c> for errors, where
- <c>Reason</c> is any possible reason returned by
- <seealso marker="#start/1"><c>start/1,2</c></seealso>
- when starting a specific dependency.</p>
- <p>If an error occurs, the applications started by the function are stopped
- to bring the set of running applications back to its initial state.</p>
- </desc>
+ <name name="ensure_all_started" arity="1" since="OTP R16B02"/>
+ <name name="ensure_all_started" arity="2" since="OTP R16B02"/>
+ <fsummary>Load and start an application and its dependencies, recursively.</fsummary>
+ <desc>
+ <p>Equivalent to calling
+ <seemfa marker="#start/1"><c>start/1,2</c></seemfa>
+ repeatedly on all dependencies that are not yet started for an
+ application that is not yet started.</p>
+ <p>Returns <c>{ok, AppNames}</c>, where <c>AppNames</c> is a list of the application names
+ that was actually started by this call.
+ The list might be empty, or not contain all dependencies if the application
+ or some of its dependencies are already started.</p>
+ <p>The function reports <c>{error, {AppName,Reason}}</c> for errors, where
+ <c>Reason</c> is any possible reason returned by
+ <seemfa marker="#start/1"><c>start/1,2</c></seemfa>
+ when starting a specific dependency.</p>
+ <p>If an error occurs, the applications started by the function are stopped
+ to bring the set of running applications back to its initial state.</p>
+ </desc>
</func>
<func>
<name name="ensure_started" arity="1" since="OTP R16B01"/>
@@ -90,7 +93,7 @@
<fsummary>Load and start an application.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#start/1"><c>start/1,2</c></seealso>
+ <seemfa marker="#start/1"><c>start/1,2</c></seemfa>
except it returns <c>ok</c> for already started applications.</p>
</desc>
</func>
@@ -156,7 +159,7 @@
<name name="get_env" arity="3" since="OTP R16B"/>
<fsummary>Get the value of a configuration parameter using a default.</fsummary>
<desc>
- <p>Works like <seealso marker="#get_env/2"><c>get_env/2</c></seealso> but returns
+ <p>Works like <seemfa marker="#get_env/2"><c>get_env/2</c></seemfa> but returns
value <c><anno>Def</anno></c> when configuration parameter
<c><anno>Par</anno></c> does not exist.</p>
</desc>
@@ -197,7 +200,7 @@
<p>The application specification can also be specified directly as a
tuple <c><anno>AppSpec</anno></c>, having the format and
contents as described in
- <seealso marker="app"><c>app(4)</c></seealso>.</p>
+ <seefile marker="app"><c>app(4)</c></seefile>.</p>
<p>If <c><anno>Distributed</anno> == {<anno>Application</anno>,[<anno>Time</anno>,]<anno>Nodes</anno>}</c>,
the application becomes distributed. The argument overrides
the value for the application in the Kernel configuration
@@ -244,7 +247,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
<desc>
<p>Sets the configuration <c><anno>Config</anno></c> for multiple
applications. It is equivalent to calling <c>set_env/4</c> on
- each application individially, except it is more efficient.
+ each application individually, except it is more efficient.
The given <c><anno>Config</anno></c> is validated before the
configuration is set.</p>
<p><c>set_env/2</c> uses the standard <c>gen_server</c> time-out
@@ -291,7 +294,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
<c>false</c> means that the application will be started at, or
moved to, another node according to how its distribution is
configured
- (see <seealso marker="#load/2"><c>load/2</c></seealso>).</p>
+ (see <seemfa marker="#load/2"><c>load/2</c></seemfa>).</p>
<p>The function does not return until the application is
started, stopped, or successfully moved to another node.
However, in some cases where permission is set to <c>true</c>,
@@ -396,7 +399,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
determine the start type, which is <c><anno>StartType</anno></c> or
<c>local</c>.</p>
<p>For a description of <c><anno>StartType</anno></c>, see
- <seealso marker="#start_type"><c>Module:start/2</c></seealso>.</p>
+ <seeerl marker="#start_type"><c>Module:start/2</c></seeerl>.</p>
<p><c>local</c> is returned if only parts of the application are
restarted (by a supervisor), or if the function is
called outside a startup.</p>
@@ -411,7 +414,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
<p>Stops <c><anno>Application</anno></c>. The application master calls
<c>Module:prep_stop/1</c>, if such a function is defined, and
then tells the top supervisor of the application to shut down
- (see <seealso marker="stdlib:supervisor"><c>supervisor(3)</c></seealso>).
+ (see <seeerl marker="stdlib:supervisor"><c>supervisor(3)</c></seeerl>).
This means that the entire
supervision tree, including included applications, is
terminated in reversed start order. After the shutdown,
@@ -455,7 +458,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
be stopped entirely, at least the top supervisor
must remain alive.</p>
<p>For a description of <c>Type</c>, see
- <seealso marker="#start/1"><c>start/1,2</c></seealso>.</p>
+ <seemfa marker="#start/1"><c>start/1,2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -481,7 +484,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
specified if another time-out value is useful, for example, in
situations where the application controller is heavily loaded.</p>
<p><c>unset_env/3</c> also allows the persistent option to be passed
- (see <seealso marker="#set_env/4"><c>set_env/4</c></seealso>).</p>
+ (see <seemfa marker="#set_env/4"><c>set_env/4</c></seemfa>).</p>
<warning>
<p>Use this function only if you know what you are doing,
that is, on your own applications. It is very
@@ -512,17 +515,18 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</func>
</funcs>
- <section>
- <title>Callback Module</title>
- <p>The following functions are to be exported from an
- <c>application</c> callback module.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Callback Module</title>
+ <p>The following functions are to be exported from an
+ <c>application</c> callback module.</p>
+ </fsdescription>
<func>
<name since="">Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}</name>
<fsummary>Start an application.</fsummary>
<type>
- <v>StartType = <seealso marker="#type-start_type"><c>start_type()</c></seealso></v>
+ <v>StartType = <seetype marker="#start_type"><c>start_type()</c></seetype></v>
<v>StartArgs = term()</v>
<v>Pid = pid()</v>
<v>State = term()</v>
@@ -565,7 +569,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
<fsummary>Extended start of an application.</fsummary>
<type>
<v>Phase = atom()</v>
- <v>StartType = <seealso marker="#type-start_type"><c>start_type()</c></seealso></v>
+ <v>StartType = <seetype marker="#start_type"><c>start_type()</c></seetype></v>
<v>PhaseArgs = term()</v>
<v>Pid = pid()</v>
<v>State = state()</v>
@@ -582,7 +586,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
the primary application) for the primary application and all
included applications, for which the start phase is defined.</p>
<p>For a description of <c>StartType</c>, see
- <seealso marker="#Module:start/2"><c>Module:start/2</c></seealso>.</p>
+ <seemfa marker="#Module:start/2"><c>Module:start/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -643,8 +647,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
<section>
<title>See Also</title>
- <p><seealso marker="doc/design_principles:des_princ">OTP Design Principles</seealso>,
- <seealso marker="kernel_app">kernel(6)</seealso>,
- <seealso marker="app">app(4)</seealso></p>
+ <p><seeguide marker="system/design_principles:des_princ">OTP Design Principles</seeguide>,
+ <seeapp marker="kernel_app">kernel(6)</seeapp>,
+ <seefile marker="app">app(4)</seefile></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/auth.xml b/lib/kernel/doc/src/auth.xml
index 4dc65bccc0..7117ecd749 100644
--- a/lib/kernel/doc/src/auth.xml
+++ b/lib/kernel/doc/src/auth.xml
@@ -33,7 +33,7 @@
<description>
<p>This module is deprecated. For a description of the Magic
Cookie system, refer to
- <seealso marker="doc/reference_manual:distributed">Distributed Erlang</seealso> in the Erlang Reference Manual.</p>
+ <seeguide marker="system/reference_manual:distributed">Distributed Erlang</seeguide> in the Erlang Reference Manual.</p>
</description>
<datatypes>
<datatype>
@@ -46,7 +46,7 @@
<fsummary>Magic cookie for local node (deprecated).</fsummary>
<desc>
<p>Use
- <seealso marker="erts:erlang#get_cookie/0"><c>erlang:get_cookie()</c></seealso>
+ <seemfa marker="erts:erlang#get_cookie/0"><c>erlang:get_cookie()</c></seemfa>
in ERTS instead.</p>
</desc>
</func>
@@ -58,8 +58,8 @@
</type_desc>
<desc>
<p>Use
- <seealso marker="erts:erlang#set_cookie/2"><c>erlang:set_cookie(node(), <anno>Cookie</anno>)</c>
- in ERTS</seealso> instead.</p>
+ <seemfa marker="erts:erlang#set_cookie/2"><c>erlang:set_cookie(node(), <anno>Cookie</anno>)</c>
+ in ERTS</seemfa> instead.</p>
</desc>
</func>
<func>
@@ -71,7 +71,7 @@
is established in this case. Returns <c>no</c> if <c><anno>Node</anno></c>
does not exist or communication is not authorized (it has
another cookie than <c>auth</c> thinks it has).</p>
- <p>Use <seealso marker="net_adm#ping/1"><c>net_adm:ping(<anno>Node</anno>)</c></seealso>
+ <p>Use <seemfa marker="net_adm#ping/1"><c>net_adm:ping(<anno>Node</anno>)</c></seemfa>
instead.</p>
</desc>
</func>
@@ -80,11 +80,11 @@
<fsummary>Set the magic cookie for a node and verify authorization (deprecated).</fsummary>
<type>
<v>Node = node()</v>
- <v>Cookie = <seealso marker="#type-cookie"><c>cookie()</c></seealso></v>
+ <v>Cookie = <seetype marker="#cookie"><c>cookie()</c></seetype></v>
</type>
<desc>
<p>Equivalent to
- <seealso marker="#node_cookie/2"><c>node_cookie(Node, Cookie)</c></seealso>.</p>
+ <seemfa marker="#node_cookie/2"><c>node_cookie(Node, Cookie)</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -94,8 +94,8 @@
<p>Sets the magic cookie of <c><anno>Node</anno></c> to
<c><anno>Cookie</anno></c> and verifies the status of the authorization.
Equivalent to calling
- <seealso marker="erts:erlang#set_cookie/2"><c>erlang:set_cookie(<anno>Node</anno>, <anno>Cookie</anno>)</c></seealso>, followed by
- <seealso marker="#is_auth/1"><c>auth:is_auth(<anno>Node</anno>)</c></seealso>.</p>
+ <seemfa marker="erts:erlang#set_cookie/2"><c>erlang:set_cookie(<anno>Node</anno>, <anno>Cookie</anno>)</c></seemfa>, followed by
+ <seemfa marker="#is_auth/1"><c>auth:is_auth(<anno>Node</anno>)</c></seemfa>.</p>
</desc>
</func>
</funcs>
diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml
index 4aa9e8b9d2..967b868b5e 100644
--- a/lib/kernel/doc/src/code.xml
+++ b/lib/kernel/doc/src/code.xml
@@ -87,7 +87,7 @@
directory described above, except that directories without
an <c>ebin</c> directory are ignored.</p>
<p>All application directories found in the additional directories
- appears before the standard OTP applications, except for the
+ appear before the standard OTP applications, except for the
Kernel and STDLIB applications, which are placed before
any additional applications. In other words, modules found in any
of the additional library directories override modules with
@@ -110,7 +110,7 @@
experimental. The purpose of releasing it before it is ready
is to obtain early feedback. The file format, semantics,
interfaces, and so on, can be changed in a future release. The function
- <seealso marker="#lib_dir/2"><c>lib_dir/2</c></seealso>
+ <seemfa marker="#lib_dir/2"><c>lib_dir/2</c></seemfa>
and flag <c>-code_path_choice</c> are also
experimental.</p></warning>
@@ -160,7 +160,7 @@ zip:create("mnesia-4.4.7.ez",
example, the call
<c>erl_prim_loader:list_dir( "/otp/root/lib/mnesia-4.4.7.ez/mnesia-4.4.7/examples/bench)"</c>
would list the contents of a directory inside an archive.
- See <seealso marker="erts:erl_prim_loader"><c>erl_prim_loader(3)</c></seealso>.</p>
+ See <seeerl marker="erts:erl_prim_loader"><c>erl_prim_loader(3)</c></seeerl>.</p>
<p>An application archive file and a regular application directory
can coexist. This can be useful when it is needed to have
@@ -197,7 +197,7 @@ zip:create("mnesia-4.4.7.ez",
level in the archive. At startup, the top directory in the
embedded archive and all (second level) <c>ebin</c>
directories in the embedded archive are added to the code path.
- See <seealso marker="erts:escript"><c>erts:escript(1)</c></seealso>.</p>
+ See <seecom marker="erts:escript"><c>erts:escript(1)</c></seecom>.</p>
<p>When the choice of directories in the code path is
<c>strict</c>, the directory that ends up in the code path is
@@ -221,8 +221,8 @@ zip:create("mnesia-4.4.7.ez",
script</c> can be <c>strict</c> or <c>relaxed</c>. It is
particularly useful to set the flag to <c>relaxed</c> when
elaborating with code loading from archives without editing the
- <c>boot script</c>. The default is <c>relaxed</c>. See <seealso
- marker="erts:init"><c>erts:init(3)</c></seealso>.</p></section>
+ <c>boot script</c>. The default is <c>relaxed</c>. See <seeerl
+ marker="erts:init"><c>erts:init(3)</c></seeerl>.</p></section>
<section>
@@ -249,7 +249,7 @@ zip:create("mnesia-4.4.7.ez",
<p>For more information about old and current code, and how to
make a process switch from old to current code, see section
Compilation and Code Loading in the
- <seealso marker="doc/reference_manual:code_loading">Erlang Reference Manual</seealso>.</p>
+ <seeguide marker="system/reference_manual:code_loading">Erlang Reference Manual</seeguide>.</p>
</section>
<section>
@@ -296,7 +296,7 @@ zip:create("mnesia-4.4.7.ez",
<tag><c>on_load_failure</c></tag>
<item>
<p>The module has an
- <seealso marker="doc/reference_manual:code_loading#on_load">-on_load function</seealso>
+ <seeguide marker="system/reference_manual:code_loading#on_load">-on_load function</seeguide>
that failed when it was called.</p>
</item>
@@ -315,6 +315,9 @@ zip:create("mnesia-4.4.7.ez",
<name name="load_error_rsn"/>
</datatype>
<datatype>
+ <name name="module_status"/>
+ </datatype>
+ <datatype>
<name name="prepared_code"/>
<desc><p>An opaque term holding prepared code.</p></desc>
</datatype>
@@ -451,12 +454,12 @@ zip:create("mnesia-4.4.7.ez",
example, <c><anno>Module</anno>.beam</c>. The loading fails if the module
name found in the object code differs from the name
<c><anno>Module</anno></c>.
- <seealso marker="#load_binary/3"><c>load_binary/3</c></seealso> must
+ <seemfa marker="#load_binary/3"><c>load_binary/3</c></seemfa> must
be used to load object code with a module name that is
different from the file name.</p>
<p>Returns <c>{module, <anno>Module</anno>}</c> if successful, or
<c>{error, Reason}</c> if loading fails.
- See <seealso marker="#error_reasons">Error Reasons for Code-Loading Functions</seealso> for a description of the possible error reasons.</p>
+ See <seeerl marker="#error_reasons">Error Reasons for Code-Loading Functions</seeerl> for a description of the possible error reasons.</p>
</desc>
</func>
<func>
@@ -470,7 +473,7 @@ zip:create("mnesia-4.4.7.ez",
<c><anno>Filename</anno></c> is an absolute or
relative filename. The code path is not searched. It returns
a value in the same way as
- <seealso marker="#load_file/1"><c>load_file/1</c></seealso>. Notice
+ <seemfa marker="#load_file/1"><c>load_file/1</c></seemfa>. Notice
that <c><anno>Filename</anno></c> must not contain the extension (for
example, <c>.beam</c>) because <c>load_abs/1</c> adds the correct
extension.</p>
@@ -481,11 +484,11 @@ zip:create("mnesia-4.4.7.ez",
<fsummary>Ensure that a module is loaded.</fsummary>
<desc>
<p>Tries to load a module in the same way as
- <seealso marker="#load_file/1"><c>load_file/1</c></seealso>,
+ <seemfa marker="#load_file/1"><c>load_file/1</c></seemfa>,
unless the module is already loaded.
However, in embedded mode it does not load a module that is not
already loaded, but returns <c>{error, embedded}</c> instead.
- See <seealso marker="#error_reasons">Error Reasons for Code-Loading Functions</seealso> for a description of other possible error reasons.</p>
+ See <seeerl marker="#error_reasons">Error Reasons for Code-Loading Functions</seeerl> for a description of other possible error reasons.</p>
</desc>
</func>
<func>
@@ -503,7 +506,7 @@ zip:create("mnesia-4.4.7.ez",
the code server.</p>
<p>Returns <c>{module, <anno>Module</anno>}</c> if successful, or
<c>{error, Reason}</c> if loading fails.
- See <seealso marker="#error_reasons">Error Reasons for Code-Loading Functions</seealso> for a description of the possible error reasons.</p>
+ See <seeerl marker="#error_reasons">Error Reasons for Code-Loading Functions</seeerl> for a description of the possible error reasons.</p>
</desc>
</func>
<func>
@@ -529,7 +532,7 @@ zip:create("mnesia-4.4.7.ez",
<tag><c>on_load_not_allowed</c></tag>
<item>
<p>A module contains an
- <seealso marker="doc/reference_manual:code_loading#on_load">-on_load function</seealso>.</p>
+ <seeguide marker="system/reference_manual:code_loading#on_load">-on_load function</seeguide>.</p>
</item>
<tag><c>duplicated</c></tag>
<item>
@@ -553,9 +556,9 @@ zip:create("mnesia-4.4.7.ez",
</taglist>
<p>If it is important to minimize the time that an application
is inactive while changing code, use
- <seealso marker="#prepare_loading/1">prepare_loading/1</seealso>
+ <seemfa marker="#prepare_loading/1">prepare_loading/1</seemfa>
and
- <seealso marker="#finish_loading/1">finish_loading/1</seealso>
+ <seemfa marker="#finish_loading/1">finish_loading/1</seemfa>
instead of <c>atomic_load/1</c>. Here is an example:</p>
<pre>
{ok,Prepared} = code:prepare_loading(Modules),
@@ -572,7 +575,7 @@ ok = code:finish_loading(Prepared),
<p>Prepares to load the modules in the list
<c><anno>Modules</anno></c>.
Finish the loading by calling
- <seealso marker="#finish_loading/1">finish_loading(Prepared)</seealso>.</p>
+ <seemfa marker="#finish_loading/1">finish_loading(Prepared)</seemfa>.</p>
<p>This function can fail with one of the following error reasons:</p>
<taglist>
<tag><c>badfile</c></tag>
@@ -587,7 +590,7 @@ ok = code:finish_loading(Prepared),
<tag><c>on_load_not_allowed</c></tag>
<item>
<p>A module contains an
- <seealso marker="doc/reference_manual:code_loading#on_load">-on_load function</seealso>.</p>
+ <seeguide marker="system/reference_manual:code_loading#on_load">-on_load function</seeguide>.</p>
</item>
<tag><c>duplicated</c></tag>
<item>
@@ -603,7 +606,7 @@ ok = code:finish_loading(Prepared),
<desc>
<p>Tries to load code for all modules that have been previously
prepared by
- <seealso marker="#prepare_loading/1">prepare_loading/1</seealso>.
+ <seemfa marker="#prepare_loading/1">prepare_loading/1</seemfa>.
The loading occurs atomically, meaning that
either all modules are loaded at the same time, or
none of the modules are loaded.</p>
@@ -632,10 +635,10 @@ ok = code:finish_loading(Prepared),
<desc>
<p>Tries to load any modules not already loaded in the list
<c><anno>Modules</anno></c> in the same way as
- <seealso marker="#load_file/1">load_file/1</seealso>.</p>
+ <seemfa marker="#load_file/1">load_file/1</seemfa>.</p>
<p>Returns <c>ok</c> if successful, or
<c>{error,[{Module,Reason}]}</c> if loading of some modules fails.
- See <seealso marker="#error_reasons">Error Reasons for Code-Loading Functions</seealso> for a description of other possible error reasons.</p>
+ See <seeerl marker="#error_reasons">Error Reasons for Code-Loading Functions</seeerl> for a description of other possible error reasons.</p>
</desc>
</func>
<func>
@@ -661,7 +664,7 @@ ok = code:finish_loading(Prepared),
<note><p>As of ERTS version 9.0, a process is only considered
to be lingering in the code if it has direct references to the code.
For more information see documentation of
- <seealso marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>,
+ <seemfa marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seemfa>,
which is used in order to determine this.</p></note>
<p>Returns <c>true</c> if successful and any process is needed to
be killed, otherwise <c>false</c>.</p>
@@ -676,7 +679,7 @@ ok = code:finish_loading(Prepared),
<note><p>As of ERTS version 9.0, a process is only considered
to be lingering in the code if it has direct references to the code.
For more information see documentation of
- <seealso marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>,
+ <seemfa marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seemfa>,
which is used in order to determine this.</p></note>
<p>Returns <c>false</c> if the module cannot be purged because
of processes lingering in old code, otherwise <c>true</c>.</p>
@@ -695,13 +698,28 @@ ok = code:finish_loading(Prepared),
<p>Normally, <c><anno>Loaded</anno></c> is the absolute filename
<c>Filename</c> from which the code is obtained. If the module
is preloaded (see
- <seealso marker="sasl:script"><c>script(4)</c></seealso>),
+ <seefile marker="sasl:script"><c>script(4)</c></seefile>),
<c>Loaded==preloaded</c>. If the module is Cover-compiled (see
- <seealso marker="tools:cover"><c>cover(3)</c></seealso>),
+ <seeerl marker="tools:cover"><c>cover(3)</c></seeerl>),
<c>Loaded==cover_compiled</c>.</p>
</desc>
</func>
<func>
+ <name name="all_available" arity="0" since="OTP 23.0"/>
+ <fsummary>Get all available modules.</fsummary>
+ <type name="loaded_filename"/>
+ <type name="loaded_ret_atoms"/>
+ <type_desc name="loaded_filename"><c><anno>Filename</anno></c> is an absolute
+ filename.</type_desc>
+ <desc>
+ <p>Returns a list of tuples <c>{<anno>Module</anno>, <anno>Filename</anno>,
+ <anno>Loaded</anno>}</c> for all available modules. A module is considered
+ to be available if it either is loaded or would be loaded if called.
+ <c><anno>Filename</anno></c> is normally the absolute filename, as described for
+ <seemfa marker="#is_loaded/1"><c>is_loaded/1</c></seemfa>.</p>
+ </desc>
+ </func>
+ <func>
<name name="all_loaded" arity="0" since=""/>
<fsummary>Get all loaded modules.</fsummary>
<type name="loaded_filename"/>
@@ -712,12 +730,13 @@ ok = code:finish_loading(Prepared),
<p>Returns a list of tuples <c>{<anno>Module</anno>, <anno>Loaded</anno>}</c> for all
loaded modules. <c><anno>Loaded</anno></c> is normally the absolute filename,
as described for
- <seealso marker="#is_loaded/1"><c>is_loaded/1</c></seealso>.</p>
+ <seemfa marker="#is_loaded/1"><c>is_loaded/1</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="which" arity="1" since=""/>
<fsummary>The object code file of a module.</fsummary>
+ <type name="loaded_filename"/>
<type name="loaded_ret_atoms"/>
<desc>
<p>If the module is not loaded, this function searches the code
@@ -750,6 +769,21 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
+ <name name="get_doc" arity="1" since="OTP 23.0"/>
+ <fsummary>Gets the documentation for a module.</fsummary>
+ <desc>
+ <p>Searches the code path for EEP-48 style documentation and returns it
+ if available. If no documentation can be found the function tries to
+ generate documentation from the debug information in the module.
+ If no debug information is available, this function will return
+ <c>{error,missing}</c>.
+ </p>
+ <p>For more information about the documentation chunk see
+ <seeguide marker="kernel:eep48_chapter">Documentation Storage and Format</seeguide>
+ in Kernel's User's Guide.</p>
+ </desc>
+ </func>
+ <func>
<name name="root_dir" arity="0" since=""/>
<fsummary>Root directory of Erlang/OTP.</fsummary>
<desc>
@@ -901,10 +935,20 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
+ <name name="module_status" arity="0" since="OTP 23.0"/>
+ <fsummary>Return the statuses of all loaded modules.</fsummary>
+ <type name="module_status"/>
+ <desc>
+ <p>See <seemfa marker="#module_status/1"><c>module_status/1</c></seemfa> and <seemfa marker="#all_loaded/0"><c>all_loaded/0</c></seemfa> for details.</p>
+ </desc>
+ </func>
+ <func>
<name name="module_status" arity="1" since="OTP 20.0"/>
- <fsummary>Return the status of the module in relation to object file on disk.</fsummary>
+ <fsummary>Return the status of a module or modules in relation to the
+ object files on disk.</fsummary>
+ <type name="module_status"/>
<desc>
- <p>Returns:</p>
+ <p>The status of a module can be one of:</p>
<taglist>
<tag><c>not_loaded</c></tag>
<item><p>If <c><anno>Module</anno></c> is not currently loaded.</p></item>
@@ -925,12 +969,12 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
(which is the convention for auto-generated code) will only be
reported as <c>loaded</c> or <c>not_loaded</c>.</p>
<p>For modules that have native code loaded (see
- <seealso marker="#is_module_native/1"><c>is_module_native/1</c></seealso>),
+ <seemfa marker="#is_module_native/1"><c>is_module_native/1</c></seemfa>),
the MD5 sum of the native code in the object file is used for the
comparison, if it exists; the Beam code in the file is ignored.
Reversely, for modules that do not currently have native code
loaded, any native code in the file will be ignored.</p>
- <p>See also <seealso marker="#modified_modules/0"><c>modified_modules/0</c></seealso>.</p>
+ <p>See also <seemfa marker="#modified_modules/0"><c>modified_modules/0</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -938,8 +982,8 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
<fsummary>Return a list of all modules modified on disk.</fsummary>
<desc>
<p>Returns the list of all currently loaded modules for which
- <seealso marker="#module_status/1"><c>module_status/1</c></seealso>
- returns <c>modified</c>. See also <seealso marker="#all_loaded/0"><c>all_loaded/0</c></seealso>.</p>
+ <seemfa marker="#module_status/1"><c>module_status/1</c></seemfa>
+ returns <c>modified</c>. See also <seemfa marker="#all_loaded/0"><c>all_loaded/0</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -970,7 +1014,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
an IDE) provides additional code for a running node. If the code server is
in interactive mode, it only has to add the path to the code. If the code server
is in embedded mode, the code must be loaded with
- <seealso marker="#load_binary/3"><c>load_binary/3</c></seealso>.</p>
+ <seemfa marker="#load_binary/3"><c>load_binary/3</c></seemfa>.</p>
</desc>
</func>
</funcs>
diff --git a/lib/kernel/doc/src/config.xml b/lib/kernel/doc/src/config.xml
index 714af93f4d..adb3241306 100644
--- a/lib/kernel/doc/src/config.xml
+++ b/lib/kernel/doc/src/config.xml
@@ -37,10 +37,10 @@
data in the system configuration file <c>Name.config</c>.</p>
<p>Configuration parameter values in the configuration file
override the values in the application resource files (see
- <seealso marker="app"><c>app(4)</c></seealso>).
+ <seefile marker="app"><c>app(4)</c></seefile>).
The values in the configuration file can be
overridden by command-line flags (see
- <seealso marker="erts:erl"><c>erts:erl(1)</c></seealso>).</p>
+ <seecom marker="erts:erl"><c>erts:erl(1)</c></seecom>).</p>
<p>The value of a configuration parameter is retrieved by calling
<c>application:get_env/1,2</c>.</p>
</description>
@@ -122,9 +122,9 @@ myconfig.config:
<section>
<title>See Also</title>
- <p><seealso marker="app"><c>app(4)</c></seealso>,
- <seealso marker="erts:erl"><c>erts:erl(1)</c></seealso>,
- <seealso marker="doc/design_principles:des_princ">OTP Design Principles</seealso></p>
+ <p><seefile marker="app"><c>app(4)</c></seefile>,
+ <seecom marker="erts:erl"><c>erts:erl(1)</c></seecom>,
+ <seeguide marker="system/design_principles:des_princ">OTP Design Principles</seeguide></p>
</section>
</fileref>
diff --git a/lib/kernel/doc/src/disk_log.xml b/lib/kernel/doc/src/disk_log.xml
index ca0681ef58..5a33796005 100644
--- a/lib/kernel/doc/src/disk_log.xml
+++ b/lib/kernel/doc/src/disk_log.xml
@@ -68,7 +68,7 @@
<p>For each open disk log, one process handles requests
made to the disk log. This process is created when
- <seealso marker="#open/1"><c>open/1</c></seealso>
+ <seemfa marker="#open/1"><c>open/1</c></seemfa>
is called, provided there exists no process handling the disk log.
A process that opens a disk log can be an <em>owner</em>
or an anonymous <em>user</em> of the disk log. Each owner is
@@ -79,7 +79,7 @@
messages of the form <c>{disk_log, Node, Log, Info}</c>, which are sent
from the disk log process when certain events occur, see
the functions and in particular the <c>open/1</c> option
- <seealso marker="#notify"><c>notify</c></seealso>.
+ <seeerl marker="#notify"><c>notify</c></seeerl>.
A log can have many owners, but a process cannot own a
log more than once. However, the same process can open the log
as a user more than once.</p>
@@ -89,10 +89,10 @@
and there must not be any users left when the disk log process terminates.
</p>
<p>Items can be logged <em>synchronously</em> by using functions
- <seealso marker="#log/2"><c>log/2</c></seealso>,
- <seealso marker="#blog/2"><c>blog/2</c></seealso>,
- <seealso marker="#log_terms/2"><c>log_terms/2</c></seealso>, and
- <seealso marker="#blog_terms/2"><c>blog_terms/2</c></seealso>.
+ <seemfa marker="#log/2"><c>log/2</c></seemfa>,
+ <seemfa marker="#blog/2"><c>blog/2</c></seemfa>,
+ <seemfa marker="#log_terms/2"><c>log_terms/2</c></seemfa>, and
+ <seemfa marker="#blog_terms/2"><c>blog_terms/2</c></seemfa>.
For each of these functions, the caller is put
on hold until the items are logged (but not necessarily
written, use <c>sync/1</c> to ensure that). By adding an <c>a</c>
@@ -102,10 +102,10 @@
return the control to the caller more or less immediately.
</p>
<p>When using the internal format for logs, use functions
- <seealso marker="#log/2"><c>log/2</c></seealso>,
- <seealso marker="#log_terms/2"><c>log_terms/2</c></seealso>,
- <seealso marker="#alog/2"><c>alog/2</c></seealso>, and
- <seealso marker="#alog_terms/2"><c>alog_terms/2</c></seealso>.
+ <seemfa marker="#log/2"><c>log/2</c></seemfa>,
+ <seemfa marker="#log_terms/2"><c>log_terms/2</c></seemfa>,
+ <seemfa marker="#alog/2"><c>alog/2</c></seemfa>, and
+ <seemfa marker="#alog_terms/2"><c>alog_terms/2</c></seemfa>.
These functions log one or more Erlang terms.
By prefixing each of the functions with a <c>b</c> (for "binary"),
we get the corresponding <c>blog()</c> functions for the external format.
@@ -117,16 +117,20 @@
<p>The <c>blog()</c> functions can also be used for internally formatted
logs, but in this case they must be called with binaries constructed
with calls to
- <seealso marker="erts:erlang#term_to_binary/1"><c>term_to_binary/1</c></seealso>.
+ <seemfa marker="erts:erlang#term_to_binary/1"><c>term_to_binary/1</c></seemfa>.
There is no check to ensure
this, it is entirely the responsibility of the caller. If these
functions are called with binaries that do not correspond to
Erlang terms, the
- <seealso marker="#chunk/2"><c>chunk/2,3</c></seealso>
+ <seemfa marker="#chunk/2"><c>chunk/2,3</c></seemfa>
and automatic repair
functions fail. The corresponding terms (not the binaries)
are returned when <c>chunk/2,3</c> is called.
</p>
+ <note><p>
+ The distributed disk log feature has been deprecated. This
+ feature has also been scheduled for removal in OTP 24.
+ </p></note>
<p>A collection of open disk logs with the same name running on
different nodes is said to be a <em>distributed disk log</em>
if requests made to any of the logs are automatically made to
@@ -138,11 +142,11 @@
request was made before written to the other nodes. However,
a few functions do not make requests to all
members of distributed disk logs, namely
- <seealso marker="#info/1"><c>info/1</c></seealso>,
- <seealso marker="#chunk/2"><c>chunk/2,3</c></seealso>,
- <seealso marker="#bchunk/2"><c>bchunk/2,3</c></seealso>,
- <seealso marker="#chunk_step/3"><c>chunk_step/3</c></seealso>, and
- <seealso marker="#lclose/1"><c>lclose/1,2</c></seealso>.</p>
+ <seemfa marker="#info/1"><c>info/1</c></seemfa>,
+ <seemfa marker="#chunk/2"><c>chunk/2,3</c></seemfa>,
+ <seemfa marker="#bchunk/2"><c>bchunk/2,3</c></seemfa>,
+ <seemfa marker="#chunk_step/3"><c>chunk_step/3</c></seemfa>, and
+ <seemfa marker="#lclose/1"><c>lclose/1,2</c></seemfa>.</p>
<p>An open disk log that is not a distributed disk
log is said to be a <em>local disk log</em>. A local disk log is
only accessible from the node where the disk log process runs,
@@ -177,10 +181,10 @@
receive an <c>error_status</c> message.
</p>
<p>The <c>disk_log</c> module does not report errors to the
- <seealso marker="error_logger"><c>error_logger</c></seealso>
+ <seeerl marker="error_logger"><c>error_logger</c></seeerl>
module. It is up to the caller to decide
whether to employ the error logger. Function
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>
can be used to produce readable messages from error replies.
However, information events are sent to the error logger in two
situations, namely when a log is repaired, or when a file is missing
@@ -192,12 +196,12 @@
</p>
<note>
<p>If an attempt to reopen or truncate a log fails (see
- <seealso marker="#reopen/2"><c>reopen/2,3</c></seealso>
+ <seemfa marker="#reopen/2"><c>reopen/2,3</c></seemfa>
and
- <seealso marker ="#truncate/1"><c>truncate/1,2</c></seealso>)
+ <seemfa marker="#truncate/1"><c>truncate/1,2</c></seemfa>)
the disk log process terminates immediately. Before the process
terminates, links to owners and blocking processes (see
- <seealso marker="#block/1"><c>block/1,2</c></seealso>) are removed.
+ <seemfa marker="#block/1"><c>block/1,2</c></seemfa>) are removed.
The effect is that the links work in one direction only. Any
process using a disk log must check for error message
<c>no_such_log</c> if some other process truncates or
@@ -261,7 +265,7 @@
for externally formatted logs. <c>balog/2</c> can also be used
for internally formatted logs if the binary is
constructed with a call to
- <seealso marker="erts:erlang#term_to_binary/1"><c>term_to_binary/1</c></seealso>.
+ <seemfa marker="erts:erlang#term_to_binary/1"><c>term_to_binary/1</c></seemfa>.
</p>
<p>Owners subscribing to notifications receive
message <c>read_only</c>, <c>blocked_log</c>,
@@ -289,7 +293,7 @@
for externally formatted logs. <c>balog_terms/2</c> can also be used
for internally formatted logs if the binaries are
constructed with calls to
- <seealso marker="erts:erlang#term_to_binary/1"><c>term_to_binary/1</c></seealso>.
+ <seemfa marker="erts:erlang#term_to_binary/1"><c>term_to_binary/1</c></seemfa>.
</p>
<p>Owners subscribing to notifications receive
message <c>read_only</c>, <c>blocked_log</c>,
@@ -378,7 +382,7 @@
</p>
<p>If the log size is decreased, for example, to save space,
function
- <seealso marker="#inc_wrap_file/1"><c>inc_wrap_file/1</c></seealso>
+ <seemfa marker="#inc_wrap_file/1"><c>inc_wrap_file/1</c></seemfa>
can be used to force the log to wrap.
</p>
</desc>
@@ -512,7 +516,7 @@
this function returns a descriptive string
of the error in English. For file errors, function
<c>format_error/1</c> in module
- <seealso marker="file#format_error/1"><c>file</c></seealso>
+ <seemfa marker="file#format_error/1"><c>file</c></seemfa>
is called.</p>
</desc>
</func>
@@ -589,7 +593,7 @@
<item>
<p><c><anno>Users</anno></c> is the number
of anonymous users of the log, see the <c>open/1</c> option
- <seealso marker="#linkto"><c>linkto</c></seealso>.</p>
+ <seeerl marker="#linkto"><c>linkto</c></seeerl>.</p>
</item>
<tag><c>{status, <anno>Status</anno>}</c></tag>
<item>
@@ -609,6 +613,10 @@
the current node, <c><anno>Dist</anno></c> has the value <c>local</c>,
otherwise all nodes where the log is distributed
are returned as a list.</p>
+ <warning><p>
+ The distributed disk log feature has been deprecated. This
+ feature has also been scheduled for removal in OTP 24.
+ </p></warning>
</item>
</taglist>
<p>The following pairs are returned for all logs opened in
@@ -696,7 +704,7 @@
specified node if the node is not the current one.</p>
<p><c>lclose(<anno>Log</anno>)</c> is equivalent to
<c>lclose(<anno>Log</anno>,&nbsp;node())</c>.
- See also <seealso marker="#close_1"><c>close/1</c></seealso>.
+ See also <seeerl marker="#close_1"><c>close/1</c></seeerl>.
</p>
<p>If no log with the specified name exist on the specified node,
<c>no_such_log</c> is returned.
@@ -722,7 +730,7 @@
is written to disk, it can linger in
the operating system kernel for a while. To ensure that the
item is written to disk, function
- <seealso marker="#sync/1"><c>sync/1</c></seealso>
+ <seemfa marker="#sync/1"><c>sync/1</c></seemfa>
must be called.
</p>
<p><c>log/2</c> is used for internally formatted logs,
@@ -730,8 +738,8 @@
<c>blog/2</c> can also be used
for internally formatted logs if the binary is
constructed with a call to
- <seealso marker="erts:erlang#term_to_binary/1">
- <c>term_to_binary/1</c></seealso>.</p>
+ <seemfa marker="erts:erlang#term_to_binary/1">
+ <c>term_to_binary/1</c></seemfa>.</p>
<p>Owners subscribing to notifications are notified
of an error with an <c>error_status</c> message if the error
reason tag is <c>invalid_header</c> or <c>file_error</c>.
@@ -759,8 +767,8 @@
<c>blog_terms/2</c> can also be used
for internally formatted logs if the binaries are
constructed with calls to
- <seealso marker="erts:erlang#term_to_binary/1">
- <c>term_to_binary/1</c></seealso>.</p>
+ <seemfa marker="erts:erlang#term_to_binary/1">
+ <c>term_to_binary/1</c></seemfa>.</p>
<p>Owners subscribing to notifications are notified
of an error with an <c>error_status</c> message if the error
reason tag is <c>invalid_header</c> or <c>file_error</c>.
@@ -871,7 +879,11 @@
adding members to a distributed disk log.
Defaults to <c>[]</c>, which means that
the log is local on the current node.
- </p>
+ </p>
+ <warning><p>
+ The distributed disk log feature has been deprecated. This
+ feature has also been scheduled for removal in OTP 24.
+ </p></warning>
</item>
<tag><c>{notify, boolean()}</c><marker id="notify"></marker></tag>
<item>
@@ -946,7 +958,7 @@
written first on the log file. If the log is a wrap
log, the item <c><anno>Head</anno></c> is written first in each new file.
<c><anno>Head</anno></c> is to be a term if the format is
- <c>internal</c>, otherwise a sequence of bytes.
+ <c>internal</c>, otherwise an <c>iodata()</c>.
Defaults to <c>none</c>, which means that
no header is written first on the file.
</p>
@@ -958,7 +970,7 @@
The call <c>M:F(A)</c> is assumed to return <c>{ok, Head}</c>.
The item <c>Head</c> is written first in each file.
<c>Head</c> is to be a term if the format is
- <c>internal</c>, otherwise a sequence of bytes.
+ <c>internal</c>, otherwise an <c>iodata()</c>.
</p>
</item>
<tag><c>{mode, <anno>Mode</anno>}</c></tag>
@@ -1113,7 +1125,10 @@
The header argument is used only once. Next time a wrap log file
is opened, the header given to <c>open/1</c> is used.
</p>
- <p><c>truncate/1,2</c> are used for internally
+ <p><c>truncate/1</c> is used for both internally and externally
+ formatted logs.
+ </p>
+ <p><c>truncate/2</c> is used for internally
formatted logs, and <c>btruncate/2</c> for externally formatted
logs.
</p>
@@ -1142,8 +1157,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="file"><c>file(3)</c></seealso>,
- <seealso marker="pg2"><c>pg2(3)</c></seealso>,
- <seealso marker="wrap_log_reader"><c>wrap_log_reader(3)</c></seealso></p>
+ <p><seeerl marker="file"><c>file(3)</c></seeerl>,
+ <seeerl marker="pg2"><c>pg2(3)</c></seeerl>,
+ <seeerl marker="wrap_log_reader"><c>wrap_log_reader(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/eep48_chapter.xml b/lib/kernel/doc/src/eep48_chapter.xml
new file mode 100644
index 0000000000..2173dfd949
--- /dev/null
+++ b/lib/kernel/doc/src/eep48_chapter.xml
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2020</year><year>2020</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>EEP-48: Documentation storage and format</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>eep48_chapter.xml</file>
+ </header>
+
+ <p>This User's Guide describes the documentation storage format initially described in
+ <url href="https://www.erlang.org/erlang-enhancement-proposals/eep-0048.html">EEP-48</url>.
+ By standardizing how API documentation is stored, it will be possible to write tools that
+ work across languages.</p>
+
+ <p>To fetch the EEP-48 documentation for a module you can use
+ <seemfa marker="code#get_doc/1"><c>code:get_doc/1</c></seemfa>.</p>
+
+ <p>To render the EEP-48 documentation for an Erlang module you can use
+ <seemfa marker="stdlib:shell_docs#render/2"><c>shell_docs:render/2</c></seemfa>.</p>
+
+ <section>
+ <title>the "Docs" storage</title>
+ <p>To look for documentation for a module name example, a tool should:</p>
+
+ <p>Look for <c>example.beam</c> in the code path, parse the BEAM file and
+ retrieve the <c>Docs</c> chunk. If the chunk is not available, it should look
+ for "example.beam" in the code path and find the <c>doc/chunks/example.chunk</c>
+ file in the application that defines the <c>example</c> module. If a .chunk file is not
+ available, then documentation is not available.</p>
+
+ <p>The choice of using a chunk or the filesystem is completely up to the language or library.
+ In both cases, the documentation can be added or removed at any moment by stripping
+ the <c>Docs</c> chunk or by removing the doc/chunks directory.</p>
+
+ <p>For example, languages like Elixir and LFE attach the <c>Docs</c> chunk at
+ compilation time, which can be controlled via a compiler flag. On the other hand,
+ projects like OTP itself will likely generate the doc/chunks entries on a separate
+ command, completely unrelated from code compilation.</p>
+ </section>
+
+ <section>
+ <title>the "Docs" format</title>
+ <p>In both storages, the documentation is written in the exactly same format:
+ an Erlang term serialized to binary via <seemfa marker="erts:erlang#term_to_binary/1">
+ <c>term_to_binary/1</c></seemfa>. The term may be optionally compressed when serialized.
+ It must follow the type specification below:</p>
+
+ <code>
+{docs_v1,
+ Anno :: erl_anno:anno(),
+ BeamLanguage :: atom(),
+ Format :: binary(),
+ ModuleDoc :: #{DocLanguage := DocValue} | none | hidden,
+ Metadata :: map(),
+ Docs ::
+ [{{Kind, Name, Arity},
+ Anno :: erl_anno:anno(),
+ Signature :: [binary()],
+ Doc :: #{DocLanguage := DocValue} | none | hidden,
+ Metadata :: map()
+ }]} when DocLanguage :: binary(),
+ DocValue :: binary() | term()
+ </code>
+ <p>where in the root tuple we have:</p>
+ <taglist>
+ <tag>Anno</tag>
+ <item>annotation (line, column, file) of the definition itself (see
+ <seeerl marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl>)</item>
+
+ <tag>BeamLanguage</tag>
+ <item>an atom representing the language, for example: erlang, elixir, lfe, alpaca, etc</item>
+
+ <tag>Format</tag>
+ <item>the mime type of the documentation, such as &lt;&lt;"text/markdown"&gt;&gt; or
+ &lt;&lt;"application/erlang+html"&gt;&gt;. For details of the format used by Erlang
+ see the <seeguide marker="erl_docgen:doc_storage"><c>EEP-48 Chapter</c></seeguide>
+ in Erl_Docgen's User's Guide.</item>
+
+ <tag>ModuleDoc</tag>
+ <item>a map with the documentation language as key, such as <c>&lt;&lt;"en"&gt;&gt;</c> or
+ <c>&lt;&lt;"pt_BR"&gt;&gt;</c>, and the documentation as a binary value. It may be the
+ atom <c>none</c> in case there is no documentation or the atom <c>hidden</c> if documentation
+ has been explicitly disabled for this entry.</item>
+
+ <tag>Metadata</tag>
+ <item>a map of atom keys with any term as value. This can be used to add annotations like the
+ <c>authors</c> of a module, <c>deprecated</c>, or anything else a language or documentation
+ tool may find relevant.</item>
+
+ <tag>Docs</tag>
+ <item>a list of documentation for other entities (such as functions and types)
+ in the module.</item>
+ </taglist>
+
+ <p>For each entry in Docs, we have:</p>
+
+ <taglist>
+ <tag>{Kind, Name, Arity}</tag>
+ <item>the kind, name and arity identifying the function, callback, type, etc.
+ The official entities are: <c>function</c>, <c>type</c> and <c>callback</c>.
+ Other languages will add their own. For instance, Elixir and LFE may add macro.</item>
+
+ <tag>Anno</tag>
+ <item>annotation (line, column, file) of the module documentation or of the definition itself
+ (see <seeerl marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl>).</item>
+
+ <tag>Signature</tag>
+ <item>the signature of the entity. It is is a list of binaries. Each entry represents a
+ binary in the signature that can be joined with a whitespace or a newline. For example,
+ <c>[&lt;&lt;"binary_to_atom(Binary, Encoding)"&gt;&gt;,
+ &lt;&lt;"when is_binary(Binary)"&gt;&gt;]</c> may be rendered as
+ a single line or two lines. It exists exclusively for exhibition purposes.</item>
+
+ <tag>Doc</tag>
+ <item>a map with the documentation language as key, such as &lt;&lt;"en"&gt;&gt; or
+ &lt;&lt;"pt_BR"&gt;&gt;, and the documentation as a value. The documentation may
+ either be a binary or any Erlang term, both described by <c>Format</c>. If it is an
+ Erlang term, then the Format must be &lt;&lt;"application/erlang+SUFFIX",&gt;&gt;
+ such as &lt;&lt;"application/erlang+html"&gt;&gt; when the documentation is an Erlang
+ representation of an HTML document. The Doc may also be atom <c>none</c> in case there is
+ no documentation or the atom <c>hidden</c> if documentation has been explicitly
+ disabled for this entry.</item>
+
+ <tag>Metadata</tag>
+ <item>a map of atom keys with any term as value.</item>
+ </taglist>
+
+ <p>This shared format is the heart of the EEP as it is what effectively
+ allows cross-language collaboration.</p>
+
+ <p>The Metadata field exists to allow languages, tools and libraries to add
+ custom information to each entry. This EEP documents the following metadata keys:</p>
+
+ <taglist>
+ <tag>authors := [binary()]</tag>
+ <item>a list of authors as binaries.</item>
+
+ <tag>cross_references := [module() | {module(), {Kind, Name, Arity}}]</tag>
+ <item>a list of modules or module entries that can be used as cross references
+ when generating documentation.</item>
+
+ <tag>deprecated := binary()</tag>
+ <item>when present, it means the current entry is deprecated with a binary
+ that represents the reason for deprecation and a recommendation to replace
+ the deprecated code.</item>
+
+ <tag>since := binary()</tag>
+ <item>a binary representing the version such entry was added, such
+ as &lt;&lt;"1.3.0"&gt;&gt; or &lt;&lt;"20.0"&gt;&gt;.</item>
+
+ <tag>edit_url := binary()</tag>
+ <item>a binary representing a URL to change to change the documentation itself.</item>
+ </taglist>
+
+ <p>Any key may be added to Metadata at any time. Keys that are frequently
+ used by the community can be standardized in future versions.</p>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p>
+ <seeerl marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl>,
+ <seeerl marker="stdlib:shell_docs"><c>shell_docs(3)</c></seeerl>,
+ <seeguide marker="erl_docgen:doc_storage"><c>EEP-48 Chapter in Erl_Docgen's User's Guide</c></seeguide>,
+ <seemfa marker="code#get_doc/1"><c>code:get_doc/1</c></seemfa>
+ </p>
+ </section>
+</chapter>
diff --git a/lib/kernel/doc/src/erl_boot_server.xml b/lib/kernel/doc/src/erl_boot_server.xml
index 1d4166b1ad..eccf58ee1f 100644
--- a/lib/kernel/doc/src/erl_boot_server.xml
+++ b/lib/kernel/doc/src/erl_boot_server.xml
@@ -41,9 +41,9 @@
<p>This server can be started with the Kernel configuration
parameter <c>start_boot_server</c>.</p>
<p>The <c>erl_boot_server</c> can read regular files and
- files in archives. See <seealso marker="code"><c>code(3)</c></seealso>
+ files in archives. See <seeerl marker="code"><c>code(3)</c></seeerl>
and
- <seealso marker="erts:erl_prim_loader"><c>erl_prim_loader(3)</c></seealso>
+ <seeerl marker="erts:erl_prim_loader"><c>erl_prim_loader(3)</c></seeerl>
in ERTS.</p>
<warning><p>The support for loading code from archive files is
experimental. It is released before it is ready
@@ -94,8 +94,8 @@
</funcs>
<section>
<title>SEE ALSO</title>
- <p><seealso marker="erts:init"><c>erts:init(3)</c></seealso>,
- <seealso marker="erts:erl_prim_loader"><c>erts:erl_prim_loader(3)</c></seealso></p>
+ <p><seeerl marker="erts:init"><c>erts:init(3)</c></seeerl>,
+ <seeerl marker="erts:erl_prim_loader"><c>erts:erl_prim_loader(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/erl_ddll.xml b/lib/kernel/doc/src/erl_ddll.xml
index 9c2a583578..798789c2af 100644
--- a/lib/kernel/doc/src/erl_ddll.xml
+++ b/lib/kernel/doc/src/erl_ddll.xml
@@ -37,8 +37,8 @@
<p>This is a large reference document. For casual use of this
module, and for most real world applications, the
descriptions of functions
- <seealso marker="#load/2"><c>load/2</c></seealso> and
- <seealso marker="#unload/1"><c>unload/1</c></seealso>
+ <seemfa marker="#load/2"><c>load/2</c></seemfa> and
+ <seemfa marker="#unload/1"><c>unload/1</c></seemfa>
are enough to getting started.</p>
</note>
<p>The driver is to be provided as a dynamically linked library
@@ -48,7 +48,7 @@
specific interfaces to the emulator, so this module is not
designed for loading arbitrary dynamic libraries. For more
information about Erlang drivers, see
- <seealso marker="erts:erl_driver"><c>erts:erl_driver</c></seealso>
+ <seecref marker="erts:erl_driver"><c>erts:erl_driver</c></seecref>
.</p>
<marker id="users"></marker>
<p>When describing a set of functions (that is, a module, a part of a
@@ -75,15 +75,15 @@
<tag><em>Load and Unload on a "When Needed Basis"</em></tag>
<item>
<p>This (most common) scenario simply supports that each
- <seealso marker="#users">user</seealso> of the driver loads
+ <seeerl marker="#users">user</seeerl> of the driver loads
it when needed and unloads it when no longer needed.
The driver is always reference counted and as long as a
process keeping the driver loaded is still alive, the driver
is present in the system.</p>
- <p>Each <seealso marker="#users">user</seealso> of the driver
+ <p>Each <seeerl marker="#users">user</seeerl> of the driver
use <em>literally</em> the same pathname for the driver when
demanding load, but the
- <seealso marker="#users">users</seealso> are not concerned
+ <seeerl marker="#users">users</seeerl> are not concerned
with if the driver is already loaded from the file system or
if the object code must be loaded from file system.</p>
<p>The following two pairs of functions support this scenario:</p>
@@ -94,7 +94,7 @@
driver is not unloaded until the
<em>last port</em> using the driver is closed. Function
<c>unload/1</c> can return immediately, as the
- <seealso marker="#users">users</seealso>
+ <seeerl marker="#users">users</seeerl>
have no interrest in when the unloading occurs. The
driver is unloaded when no one needs it any longer.</p>
<p>If a process having the driver loaded dies, it has
@@ -109,9 +109,9 @@
<item>
<p>These interfaces are intended to be used when it is considered an
error that ports are open to a driver that no
- <seealso marker="#users">user</seealso>
+ <seeerl marker="#users">user</seeerl>
has loaded. The ports that are still open when the
- last <seealso marker="#users">user</seealso> calls
+ last <seeerl marker="#users">user</seeerl> calls
<c>unload_driver/1</c> or when the last process having the
driver loaded dies, are killed with reason
<c>driver_unloaded</c>.</p>
@@ -128,7 +128,7 @@
emulator. Implementing driver code replacement is a little
more tedious than Beam code replacement, as one driver
cannot be loaded as both "old" and "new" code. All
- <seealso marker="#users">users</seealso> of a driver must have it
+ <seeerl marker="#users">users</seeerl> of a driver must have it
closed (no open ports) before the old code can be unloaded
and the new code can be loaded.</p>
<p>The unloading/loading is done as one atomic
@@ -139,18 +139,18 @@
the process starts, the driver is loaded. When replacement
is required, the driver is reloaded. Unload is probably never
done, or done when the process exits. If more than one
- <seealso marker="#users">user</seealso> has a driver
+ <seeerl marker="#users">user</seeerl> has a driver
loaded when code replacement is demanded, the replacement cannot
occur until the last "other"
- <seealso marker="#users">user</seealso> has
+ <seeerl marker="#users">user</seeerl> has
unloaded the driver.</p>
<p>Demanding reload when a reload is already in progress is
always an error. Using the high-level functions, it is also
an error to demand reloading when more than one
- <seealso marker="#users">user</seealso> has the driver loaded.</p>
+ <seeerl marker="#users">user</seeerl> has the driver loaded.</p>
<p>To simplify driver replacement, avoid designing your system so
that more than one
- <seealso marker="#users">user</seealso> has the driver loaded.</p>
+ <seeerl marker="#users">user</seeerl> has the driver loaded.</p>
<p>The two functions for reloading drivers are to be used
together with corresponding load functions to support the two
different behaviors concerning open ports:</p>
@@ -166,7 +166,7 @@
infinite waiting for reload. Time-outs must be provided
outside of the process demanding the reload or by using
the low-level interface
- <seealso marker="#try_load/3"><c>try_load/3</c></seealso>
+ <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa>
in combination with driver monitors.</p>
</item>
<tag><em>load_driver/2 and reload_driver/2</em></tag>
@@ -179,7 +179,7 @@
calling <c>reload_driver</c> returns error code
<c>pending_process</c>. As stated earlier,
the recommended design is to not allow other
- <seealso marker="#users">users</seealso> than the "driver
+ <seeerl marker="#users">users</seeerl> than the "driver
reloader" to demand loading of the driver in question.</p>
</item>
</taglist>
@@ -200,13 +200,13 @@
<fsummary>Remove a monitor for a driver.</fsummary>
<desc>
<p>Removes a driver monitor in much the same way as
- <seealso marker="erts:erlang#demonitor/1"><c>erlang:demonitor/1</c></seealso>
+ <seemfa marker="erts:erlang#demonitor/1"><c>erlang:demonitor/1</c></seemfa>
in ERTS
does with process monitors. For details about how to create
driver monitors, see
- <seealso marker="#monitor/2"><c>monitor/2</c></seealso>,
- <seealso marker="#try_load/3"><c>try_load/3</c></seealso>, and
- <seealso marker="#try_unload/2"><c>try_unload/2</c></seealso>.</p>
+ <seemfa marker="#monitor/2"><c>monitor/2</c></seemfa>,
+ <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa>, and
+ <seemfa marker="#try_unload/2"><c>try_unload/2</c></seemfa>.</p>
<p>The function throws a <c>badarg</c> exception if the
parameter is not a <c>reference()</c>.</p>
</desc>
@@ -234,7 +234,7 @@
<desc>
<p>Returns a list of tuples <c>{<anno>DriverName</anno>, <anno>InfoList</anno>}</c>,
where <c><anno>InfoList</anno></c> is the result of calling
- <seealso marker="#info/1"><c>info/1</c></seealso> for that
+ <seemfa marker="#info/1"><c>info/1</c></seemfa> for that
<c><anno>DriverName</anno></c>. Only dynamically linked-in drivers are
included in the list.</p>
</desc>
@@ -246,7 +246,7 @@
<p>Returns a list of tuples <c>{<anno>Tag</anno>, <anno>Value</anno>}</c>,
where <c><anno>Tag</anno></c> is the information item and
<c><anno>Value</anno></c> is the result of calling
- <seealso marker="#info/2"><c>info/2</c></seealso> with this driver
+ <seemfa marker="#info/2"><c>info/2</c></seemfa> with this driver
name and this tag. The result is a tuple list containing all information
available about a driver.</p>
<p>The following tags appears in the list:</p>
@@ -260,7 +260,7 @@
<item><c>awaiting_unload</c></item>
</list>
<p>For a detailed description of each value, see
- <seealso marker="#info/2"><c>info/2</c></seealso>.</p>
+ <seemfa marker="#info/2"><c>info/2</c></seemfa>.</p>
<p>The function throws a <c>badarg</c> exception if the driver
is not present in the system.</p>
</desc>
@@ -277,7 +277,7 @@
<tag><c>processes</c></tag>
<item>
<p>Returns all processes containing
- <seealso marker="#users">users</seealso> of the specific drivers
+ <seeerl marker="#users">users</seeerl> of the specific drivers
as a list of tuples <c>{pid(),integer() >= 0}</c>, where
<c>integer()</c> denotes the number of users in process
<c>pid()</c>.</p>
@@ -350,11 +350,11 @@
<c>load/2</c> stops the unloading and keeps the driver
(as long as <c><anno>Path</anno></c> is the same), and <c>ok</c> is
returned. If you really want the object code to be
- reloaded, use <seealso marker="#reload/2"><c>reload/2</c></seealso>
+ reloaded, use <seemfa marker="#reload/2"><c>reload/2</c></seemfa>
or the low-level interface
- <seealso marker="#try_load/3"><c>try_load/3</c></seealso> instead.
+ <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa> instead.
See also the description of
- <seealso marker="#scenarios"><c>different scenarios</c></seealso> for
+ <seeerl marker="#scenarios"><c>different scenarios</c></seeerl> for
loading/unloading in the introduction.</p>
<p>If more than one process tries to load an already loaded
driver with the same <c><anno>Path</anno></c>, or if the same process
@@ -381,9 +381,9 @@
failure, the return value is <c>{error,<anno>ErrorDesc</anno>}</c>,
where <c><anno>ErrorDesc</anno></c> is an opaque term to be
translated into human readable form by function
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>.</p>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>.</p>
<p>For more control over the error handling, use the
- <seealso marker="#try_load/3"><c>try_load/3</c></seealso>
+ <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa>
interface instead.</p>
<p>The function throws a <c>badarg</c> exception if the
parameters are not specified as described here.</p>
@@ -398,14 +398,14 @@
driver are killed with reason <c>driver_unloaded</c>
when the driver is to be unloaded.</p>
<p>The number of loads and unloads by different
- <seealso marker="#users">users</seealso> influences the loading
+ <seeerl marker="#users">users</seeerl> influences the loading
and unloading of a driver file. The port killing
therefore only occurs when the <em>last</em>
- <seealso marker="#users">user</seealso> unloads the driver,
+ <seeerl marker="#users">user</seeerl> unloads the driver,
or when the last process having loaded the driver exits.</p>
<p>This interface (or at least the name of the functions) is
kept for backward compatibility.
- Using <seealso marker="#try_load/3"><c>try_load/3</c></seealso> with
+ Using <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa> with
<c>{driver_options,[kill_ports]}</c> in the option list
gives the same effect regarding the port killing.</p>
<p>The function throws a <c>badarg</c> exception if the
@@ -421,7 +421,7 @@
<p>The driver names are returned as a list of strings rather
than a list of atoms for historical reasons.</p>
<p>For more information about drivers, see
- <seealso marker="#info/0"><c>info</c></seealso>.</p>
+ <seemfa marker="#info/0"><c>info</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -430,7 +430,7 @@
<desc>
<p>Creates a driver monitor and works in many
ways as
- <seealso marker="erts:erlang#monitor/2"><c>erlang:monitor/2</c></seealso>
+ <seemfa marker="erts:erlang#monitor/2"><c>erlang:monitor/2</c></seemfa>
in ERTS,
does for processes. When a driver changes state, the monitor
results in a monitor message that is sent to the calling
@@ -440,9 +440,9 @@
generates <em>one single message</em>. The monitor is
"destroyed" after the message is sent, so it is then not
needed to call
- <seealso marker="#demonitor/1"><c>demonitor/1</c></seealso>.</p>
+ <seemfa marker="#demonitor/1"><c>demonitor/1</c></seemfa>.</p>
<p><c><anno>MonitorRef</anno></c> can also be used in subsequent calls
- to <seealso marker="#demonitor/1"><c>demonitor/1</c></seealso> to
+ to <seemfa marker="#demonitor/1"><c>demonitor/1</c></seemfa> to
remove a monitor.</p>
<p>The function accepts the following parameters:</p>
<taglist>
@@ -471,7 +471,7 @@
<c>DOWN</c> message sent immediately.
Monitoring for loading is therefore most useful when
triggered by function
- <seealso marker="#try_load/3"><c>try_load/3</c></seealso>,
+ <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa>,
where the monitor is created <em>because</em> the
driver is in such a pending state.</p>
<p>Setting a driver monitor for <c>loading</c>
@@ -484,7 +484,7 @@
driver is already loaded and no reloading is
pending, or when reloading is executed if
reloading is pending. </p>
- <p>The <seealso marker="#users">user</seealso> is
+ <p>The <seeerl marker="#users">user</seeerl> is
expected to know if reloading is demanded before
creating a monitor for loading.</p>
</item>
@@ -500,9 +500,9 @@
<item>
<p>This message arrives if reloading was
underway, but the requesting
- <seealso marker="#users">user</seealso>
+ <seeerl marker="#users">user</seeerl>
cancelled it by dying or calling
- <seealso marker="#try_unload/2"><c>try_unload/2</c></seealso>
+ <seemfa marker="#try_unload/2"><c>try_unload/2</c></seemfa>
(or <c>unload/1</c>/<c>unload_driver/1</c>)
again before it was reloaded.</p>
</item>
@@ -512,9 +512,9 @@
underway but the loading for some reason
failed. The <c>Failure</c> term is one of the
errors that can be returned from
- <seealso marker="#try_load/3"><c>try_load/3</c></seealso>.
+ <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa>.
The error term can be passed to
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>
for translation into human readable form. Notice
that the translation must be done in the same
running Erlang virtual machine as the error
@@ -545,15 +545,15 @@
<p>This message is sent if unloading was
expected, but while the driver was waiting for
all ports to get closed, a new
- <seealso marker="#users">user</seealso> of the driver
+ <seeerl marker="#users">user</seeerl> of the driver
appeared, and the unloading was cancelled.</p>
<p>This message appears if <c>{ok, pending_driver}</c>
was returned from
- <seealso marker="#try_unload/2"><c>try_unload/2</c></seealso>
- for the last <seealso marker="#users">user</seealso>
+ <seemfa marker="#try_unload/2"><c>try_unload/2</c></seemfa>
+ for the last <seeerl marker="#users">user</seeerl>
of the driver, and then <c>{ok, already_loaded}</c> is returned
from a call to
- <seealso marker="#try_load/3"><c>try_load/3</c></seealso>.</p>
+ <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa>.</p>
<p>If one <em>really</em> wants to monitor when the
driver gets unloaded, this message distorts
the picture, because no unloading was done.
@@ -594,32 +594,32 @@
<p>Reloads the driver named <c><anno>Name</anno></c> from a possibly
different <c><anno>Path</anno></c> than previously used. This
function is used in the code change
- <seealso marker="#scenarios"><c>scenario</c></seealso> described in the
+ <seeerl marker="#scenarios"><c>scenario</c></seeerl> described in the
introduction.</p>
- <p>If there are other <seealso marker="#users">users</seealso>
+ <p>If there are other <seeerl marker="#users">users</seeerl>
of this driver, the function returns <c>{error, pending_process}</c>,
but if there are no other users, the function call hangs until all
open ports are closed.</p>
<note>
<p>Avoid mixing multiple
- <seealso marker="#users">users</seealso>
+ <seeerl marker="#users">users</seeerl>
with driver reload requests.</p>
</note>
<p>To avoid hanging on open ports, use function
- <seealso marker="#try_load/3"><c>try_load/3</c></seealso>
+ <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa>
instead.</p>
<p>The <c><anno>Name</anno></c> and <c><anno>Path</anno></c> parameters
have exactly the same meaning as when calling the plain function
- <seealso marker="#load/2"><c>load/2</c></seealso>.</p>
+ <seemfa marker="#load/2"><c>load/2</c></seemfa>.</p>
<p>On success, the function returns <c>ok</c>. On
failure, the function returns an opaque error,
except the <c>pending_process</c> error described
earlier. The opaque errors are to be translated into human
readable form by function
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>.</p>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>.</p>
<p>For more control over the error handling, use the
- <seealso marker="#try_load/3"><c>try_load/3</c></seealso>
+ <seemfa marker="#try_load/3"><c>try_load/3</c></seemfa>
interface instead.</p>
<p>The function throws a <c>badarg</c> exception if the
parameters are not specified as described here.</p>
@@ -629,16 +629,16 @@
<name name="reload_driver" arity="2" since=""/>
<fsummary>Replace a driver.</fsummary>
<desc>
- <p>Works exactly as <seealso marker="#reload/2"><c>reload/2</c></seealso>,
+ <p>Works exactly as <seemfa marker="#reload/2"><c>reload/2</c></seemfa>,
but for drivers loaded with the
- <seealso marker="#load_driver/2"><c>load_driver/2</c></seealso> interface.</p>
+ <seemfa marker="#load_driver/2"><c>load_driver/2</c></seemfa> interface.</p>
<p>As this interface implies that ports are killed when
the last user disappears, the function does not hang waiting for
ports to get closed.</p>
<p>For more details, see
- <seealso marker="#scenarios"><c>scenarios</c></seealso> in this module
+ <seeerl marker="#scenarios"><c>scenarios</c></seeerl> in this module
description and the function description for
- <seealso marker="#reload/2"><c>reload/2</c></seealso>.</p>
+ <seemfa marker="#reload/2"><c>reload/2</c></seemfa>.</p>
<p>The function throws a <c>badarg</c> exception if the
parameters are not specified as described here.</p>
</desc>
@@ -676,7 +676,7 @@
<c>{reload,pending_driver}</c> or
<c>{reload,pending}</c> are used, but
<em>can</em> occur when another
- <seealso marker="#users">user</seealso> is unloading a
+ <seeerl marker="#users">user</seeerl> is unloading a
driver in parallel and driver option <c>kill_ports</c> is set.
In other words, this return value always needs
to be handled.</p>
@@ -686,7 +686,7 @@
<p>The load request is registered, but the loading is
delayed because an earlier instance of the
driver is still waiting to get unloaded by another
- <seealso marker="#users">user</seealso> (not only by a
+ <seeerl marker="#users">user</seeerl> (not only by a
port, in which case <c>{ok,pending_driver}</c> would
have been returned). Still, unload is expected when you
are done with the driver. This return value
@@ -705,7 +705,7 @@
and the process then gets a monitor message later, when the
driver gets loaded. The monitor message to expect is described in
the function description of
- <seealso marker="#monitor/2"><c>monitor/2</c></seealso>.</p>
+ <seemfa marker="#monitor/2"><c>monitor/2</c></seemfa>.</p>
<note>
<p>In case of loading, monitoring can <em>not</em> only get
triggered by using option <c>{reload, <anno>ReloadOption</anno>}</c>,
@@ -728,12 +728,12 @@
into a sequence of characters.</p>
<p>The (possibly flattened) <c><anno>Path</anno></c> parameter must be
consistent throughout the system. A driver is to, by
- all <seealso marker="#users">users</seealso>, be loaded
+ all <seeerl marker="#users">users</seeerl>, be loaded
using the same <em>literal</em> <c><anno>Path</anno></c>.
The exception is when <em>reloading</em> is requested,
in which case <c><anno>Path</anno></c> can be specified
differently. Notice that all
- <seealso marker="#users">users</seealso> trying to load the
+ <seeerl marker="#users">users</seeerl> trying to load the
driver later need to use the
<em>new</em> <c><anno>Path</anno></c> if <c><anno>Path</anno></c>
is changed using a <c>reload</c> option. This is yet another reason
@@ -744,7 +744,7 @@
<item>
<p>This parameter is the name of the driver
to be used in subsequent calls to function
- <seealso marker="erts:erlang#open_port/2"><c>erlang:open_port</c></seealso>
+ <seemfa marker="erts:erlang#open_port/2"><c>erlang:open_port</c></seemfa>
in ERTS.
The name can be specified as an <c>iolist()</c> or
an <c>atom()</c>. The name specified when loading is used
@@ -774,8 +774,8 @@
to the driver are killed with exit reason
<c>driver_unloaded</c> when no process any longer
has the driver loaded. This situation arises either
- when the last <seealso marker="#users">user</seealso> calls
- <seealso marker="#try_unload/2"><c>try_unload/2</c></seealso>, or
+ when the last <seeerl marker="#users">user</seeerl> calls
+ <seemfa marker="#try_unload/2"><c>try_unload/2</c></seemfa>, or
when the last process having loaded the driver exits.</p>
</item>
<tag><c>{monitor, <anno>MonitorOption</anno>}</c></tag>
@@ -810,7 +810,7 @@
useful to specify option <c>monitor</c>, as
forced unloads (driver option <c>kill_ports</c> or
option <c>kill_ports</c> to
- <seealso marker="#try_unload/2"><c>try_unload/2</c></seealso>)
+ <seemfa marker="#try_unload/2"><c>try_unload/2</c></seemfa>)
trigger a transient state where driver loading
cannot be performed until all closing ports are
closed. Thus, as <c>try_unload</c> can, in
@@ -828,7 +828,7 @@
the driver.</p>
<p>To reload a driver, the process must have loaded the driver
before, that is, there must be an active
- <seealso marker="#users">user</seealso> of the driver
+ <seeerl marker="#users">user</seeerl> of the driver
in the process.</p>
<p>The <c>reload</c> option can be either of the following:</p>
<taglist>
@@ -839,22 +839,22 @@
ports opened to the driver are closed. The driver
replacement in this case takes
place regardless if there are still
- pending <seealso marker="#users">users</seealso>
+ pending <seeerl marker="#users">users</seeerl>
having the driver loaded.</p>
<p>The option also triggers port-killing (if driver
option <c>kill_ports</c> is used) although
there are pending users, making it usable for forced
driver replacement, but laying much
responsibility on the driver
- <seealso marker="#users">users</seealso>.
+ <seeerl marker="#users">users</seeerl>.
The pending option is seldom used as one does not want other
- <seealso marker="#users">users</seealso> to have loaded
+ <seeerl marker="#users">users</seeerl> to have loaded
the driver when code change is underway.</p></item>
<tag><c>pending_driver</c></tag>
<item>
<p>This option is more useful. Here, reloading is queued
if the driver is <em>not</em> loaded by any other
- <seealso marker="#users">users</seealso>, but the
+ <seeerl marker="#users">users</seeerl>, but the
driver has opened ports, in which case
<c>{ok, pending_driver}</c> is returned
(a <c>monitor</c> option is recommended).</p></item>
@@ -871,7 +871,7 @@
can only be returned given a certain combination of options.</p>
<p>Some errors are opaque and can only be interpreted by
passing them to function
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>,
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>,
but some can be interpreted directly:</p>
<taglist>
<tag><c>{error,linked_in_driver}</c></tag>
@@ -897,20 +897,20 @@
<tag><c>{error, pending_process}</c></tag>
<item>
<p>The driver is loaded by other
- <seealso marker="#users">users</seealso> when
+ <seeerl marker="#users">users</seeerl> when
option <c>{reload, pending_driver}</c> was specified.</p>
</item>
<tag><c>{error, pending_reload}</c></tag>
<item>
<p>Driver reload is already requested by another
- <seealso marker="#users">user</seealso> when option
+ <seeerl marker="#users">user</seeerl> when option
<c>{reload, <anno>ReloadOption</anno>}</c> was specified.</p>
</item>
<tag><c>{error, not_loaded_by_this_process}</c></tag>
<item>
<p>Appears when option <c>reload</c> is specified. The
driver <c><anno>Name</anno></c> is present in the system, but there
- is no <seealso marker="#users">user</seealso> of it in this
+ is no <seeerl marker="#users">user</seeerl> of it in this
process.</p>
</item>
<tag><c>{error, not_loaded}</c></tag>
@@ -921,7 +921,7 @@
</item>
</taglist>
<p>All other error codes are to be translated by function
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>.
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>.
Notice that calls to <c>format_error</c> are to be
performed from the same running instance of the Erlang
virtual machine as the error is detected in, because of
@@ -939,12 +939,12 @@
killing, in much the same way as the driver option
<c>kill_ports</c> implicitly does. Also, it can trigger a
monitor either because other
- <seealso marker="#users">users</seealso> still have the driver
+ <seeerl marker="#users">users</seeerl> still have the driver
loaded or because open ports use the driver.</p>
<p>Unloading can be described as the process of telling the
emulator that this particular part of the code in this
particular process (that is, this
- <seealso marker="#users">user</seealso>) no longer needs
+ <seeerl marker="#users">user</seeerl>) no longer needs
the driver. That can, if there are no other users, trigger
unloading of the driver, in which case the driver name
disappears from the system and (if possible) the memory
@@ -953,17 +953,17 @@
<c>kill_ports</c> is specified as an option to this
function, all pending ports using this driver are
killed when unloading is done by the last
- <seealso marker="#users">user</seealso>. If no port-killing
+ <seeerl marker="#users">user</seeerl>. If no port-killing
is involved and there are open ports, the unloading
is delayed until no more open ports use the
driver. If, in this case, another
- <seealso marker="#users">user</seealso> (or even this user)
+ <seeerl marker="#users">user</seeerl> (or even this user)
loads the driver again before the driver is unloaded, the
unloading never takes place.</p>
- <p>To allow the <seealso marker="#users">user</seealso> to
+ <p>To allow the <seeerl marker="#users">user</seeerl> to
<em>request unloading</em> to wait for <em>actual unloading</em>,
<c>monitor</c> triggers can be specified in much the same way as
- when loading. However, as <seealso marker="#users">users</seealso>
+ when loading. However, as <seeerl marker="#users">users</seeerl>
of this function seldom are interested in more than decrementing the
reference counts, monitoring is seldom needed.</p>
<note><p> If option <c>kill_ports</c> is used, monitor trigging is crucial,
@@ -972,7 +972,7 @@
case.</p></note>
<p>The possible monitor messages to expect are the
same as when using option <c>unloaded</c> to function
- <seealso marker="#monitor/2"><c>monitor/2</c></seealso>.</p>
+ <seemfa marker="#monitor/2"><c>monitor/2</c></seemfa>.</p>
<p>The function returns one of the following statuses upon
success:</p>
<taglist>
@@ -984,17 +984,17 @@
driver object code is now reclaimed.</p>
<p>The driver can only be unloaded when there are no open
ports using it and no more
- <seealso marker="#users">users</seealso> require it to be
+ <seeerl marker="#users">users</seeerl> require it to be
loaded.</p>
</item>
<tag><c>{ok, pending_driver}</c>or
<c>{ok, pending_driver, reference()}</c></tag>
<item>
<p>Indicates that this call removed the last
- <seealso marker="#users">user</seealso> from the
+ <seeerl marker="#users">user</seeerl> from the
driver, but there are still open ports using it.
When all ports are closed and no new
- <seealso marker="#users">users</seealso> have arrived,
+ <seeerl marker="#users">users</seeerl> have arrived,
the driver is reloaded and the name and memory
reclaimed.</p>
<p>This return value is valid even if option <c>kill_ports</c>
@@ -1007,16 +1007,16 @@
<c>{ok, pending_process, reference()}</c></tag>
<item>
<p>The unload request is registered, but
- other <seealso marker="#users">users</seealso> still hold
+ other <seeerl marker="#users">users</seeerl> still hold
the driver. Notice that the term <c>pending_process</c>
can refer to the running process; there can be more
- than one <seealso marker="#users">user</seealso> in the
+ than one <seeerl marker="#users">user</seeerl> in the
same process.</p>
<p>This is a normal, healthy, return value if the call was
just placed to inform the emulator that you have no
further use of the driver. It is the most
common return value in the most common
- <seealso marker="#scenarios"><c>scenario</c></seealso>
+ <seeerl marker="#scenarios"><c>scenario</c></seeerl>
described in the introduction.</p>
</item>
</taglist>
@@ -1038,12 +1038,12 @@
<item>
<p>Forces killing of all ports opened using this driver,
with exit reason <c>driver_unloaded</c>, if you are
- the <em>last</em> <seealso marker="#users">user</seealso>
+ the <em>last</em> <seeerl marker="#users">user</seeerl>
of the driver.</p>
- <p>If other <seealso marker="#users">users</seealso>
+ <p>If other <seeerl marker="#users">users</seeerl>
have the driver loaded, this option has no effect.</p>
<p>To get the consistent behavior of killing ports
- when the last <seealso marker="#users">user</seealso>
+ when the last <seeerl marker="#users">user</seeerl>
unloads, use driver option
<c>kill_ports</c> when loading the driver instead.</p>
</item>
@@ -1096,7 +1096,7 @@
<tag><c>{error, not_loaded_by_this_process}</c></tag>
<item>
<p>The driver <c><anno>Name</anno></c> is present in the system, but
- there is no <seealso marker="#users">user</seealso> of
+ there is no <seeerl marker="#users">user</seeerl> of
it in this process. </p>
<p>As a special case, drivers can be unloaded from
processes that have done no corresponding call to
@@ -1121,22 +1121,22 @@
<desc>
<p>Unloads, or at least dereferences the driver named
<c><anno>Name</anno></c>. If the caller is the last
- <seealso marker="#users">user</seealso> of the driver,
+ <seeerl marker="#users">user</seeerl> of the driver,
and no more open ports use the driver, the driver
gets unloaded. Otherwise, unloading
is delayed until all ports are closed and no
- <seealso marker="#users">users</seealso> remain.</p>
- <p>If there are other <seealso marker="#users">users</seealso>
+ <seeerl marker="#users">users</seeerl> remain.</p>
+ <p>If there are other <seeerl marker="#users">users</seeerl>
of the driver, the reference counts of the driver is merely decreased,
so that the caller is no longer considered a
- <seealso marker="#users">user</seealso> of the driver. For use
- scenarios, see the <seealso marker="#scenarios"><c>description</c></seealso>
+ <seeerl marker="#users">user</seeerl> of the driver. For use
+ scenarios, see the <seeerl marker="#scenarios"><c>description</c></seeerl>
in the beginning of this module.</p>
<p>The <c><anno>ErrorDesc</anno></c> returned is an opaque value to be
passed further on to function
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>.
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>.
For more control over the operation, use the
- <seealso marker="#try_unload/2"><c>try_unload/2</c></seealso>
+ <seemfa marker="#try_unload/2"><c>try_unload/2</c></seemfa>
interface.</p>
<p>The function throws a <c>badarg</c> exception if the
parameters are not specified as described here.</p>
@@ -1148,22 +1148,22 @@
<desc>
<p>Unloads, or at least dereferences the driver named
<c><anno>Name</anno></c>. If the caller is the last
- <seealso marker="#users">user</seealso> of the driver, all
+ <seeerl marker="#users">user</seeerl> of the driver, all
remaining open ports using the driver are killed with
reason <c>driver_unloaded</c> and the driver
eventually gets unloaded.</p>
- <p>If there are other <seealso marker="#users">users</seealso>
+ <p>If there are other <seeerl marker="#users">users</seeerl>
of the driver, the reference counts of the driver is merely
decreased, so that the caller is no longer considered a
- <seealso marker="#users">user</seealso>. For
+ <seeerl marker="#users">user</seeerl>. For
use scenarios, see the
- <seealso marker="#scenarios"><c>description</c></seealso> in the
+ <seeerl marker="#scenarios"><c>description</c></seeerl> in the
beginning of this module.</p>
<p>The <c><anno>ErrorDesc</anno></c> returned is an opaque value to be
passed further on to function
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>.
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>.
For more control over the operation, use the
- <seealso marker="#try_unload/2"><c>try_unload/2</c></seealso>
+ <seemfa marker="#try_unload/2"><c>try_unload/2</c></seemfa>
interface.</p>
<p>The function throws a <c>badarg</c> exception if the
parameters are not specified as described here.</p>
@@ -1172,7 +1172,7 @@
</funcs>
<section>
<title>See Also</title>
- <p><seealso marker="erts:erl_driver"><c>erts:erl_driver(4)</c></seealso>,
- <seealso marker="erts:driver_entry"><c>erts:driver_entry(4)</c></seealso></p>
+ <p><seecref marker="erts:erl_driver"><c>erts:erl_driver(4)</c></seecref>,
+ <seecref marker="erts:driver_entry"><c>erts:driver_entry(4)</c></seecref></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/erl_epmd.xml b/lib/kernel/doc/src/erl_epmd.xml
index 31f49a05cb..f6fe3c0a9e 100644
--- a/lib/kernel/doc/src/erl_epmd.xml
+++ b/lib/kernel/doc/src/erl_epmd.xml
@@ -33,10 +33,10 @@
Erlang interface towards epmd
</modulesummary>
<description>
- <p>This module communicates with the EPMD daemon, see <seealso
- marker="erts:epmd">epmd</seealso>. To implement your own epmd module please
- see <seealso marker="erts:alt_disco">ERTS User's Guide: How to Implement an
- Alternative Node Discovery for Erlang Distribution</seealso></p>
+ <p>This module communicates with the EPMD daemon, see <seecom
+ marker="erts:epmd">epmd</seecom>. To implement your own epmd module please
+ see <seeguide marker="erts:alt_disco">ERTS User's Guide: How to Implement an
+ Alternative Node Discovery for Erlang Distribution</seeguide></p>
</description>
<funcs>
@@ -56,8 +56,10 @@
<desc>
<p>Registers the node with <c>epmd</c> and tells epmd what port will be
used for the current node. It returns a creation number. This number is
- incremented on each register to help with identifying if a node is
- reconnecting to epmd.</p>
+ incremented on each register to help differentiate a new node instance
+ connecting to epmd with the same name.</p>
+ <p>After the node has successfully registered with epmd it will automatically
+ attempt reconnect to the daemon if the connection is broken.</p>
</desc>
</func>
@@ -73,13 +75,24 @@
</func>
<func>
+ <name name="listen_port_please" arity="2" since="OTP 23.0"/>
+ <fsummary>Returns the port number for the local node.</fsummary>
+ <desc>
+ <p>Called by the distribution module to get which port the
+ local node should listen to when accepting new distribution
+ requests.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="address_please" arity="3" since="OTP 21.0"/>
<fsummary>Returns address and port.</fsummary>
<desc>
- <p>Called by the distribution module. Resolves the <c>Host</c> to an IP
- address.</p>
- <p>Another epmd module may return port and distribution protocol version
- as well.</p>
+ <p>Called by the distribution module to resolves the <c>Host</c> to an IP
+ address of a remote node.</p>
+ <p>As an optimization this function may also return the port and version
+ of the remote node. If port and version are returned <seemfa marker="#port_please/3">
+ <c>port_please/3</c></seemfa> will not be called.</p>
</desc>
</func>
@@ -87,7 +100,7 @@
<name name="names" arity="1" since="OTP 21.0"/>
<fsummary>Names of Erlang nodes at a host.</fsummary>
<desc>
- <p>Called by <seealso marker="net_adm"><c>net_adm:names/0</c></seealso>.
+ <p>Called by <seeerl marker="net_adm"><c>net_adm:names/0</c></seeerl>.
<c>Host</c> defaults to the localhost. Returns the names and associated
port numbers of the Erlang nodes that <c>epmd</c> registered at the
specified host. Returns <c>{error, address}</c> if <c>epmd</c> is not
diff --git a/lib/kernel/doc/src/erl_prim_loader_stub.xml b/lib/kernel/doc/src/erl_prim_loader_stub.xml
index f3189e2a66..030c7095ae 100644
--- a/lib/kernel/doc/src/erl_prim_loader_stub.xml
+++ b/lib/kernel/doc/src/erl_prim_loader_stub.xml
@@ -35,8 +35,8 @@
<description><p>
The module erl_prim_loader is moved to the runtime system
- application. Please see <seealso
- marker="erts:erl_prim_loader">erl_prim_loader(3)</seealso> in the
+ application. Please see <seeerl
+ marker="erts:erl_prim_loader">erl_prim_loader(3)</seeerl> in the
ERTS reference manual instead.
</p></description>
diff --git a/lib/kernel/doc/src/erlang_stub.xml b/lib/kernel/doc/src/erlang_stub.xml
index afd353e438..b1c9f96b01 100644
--- a/lib/kernel/doc/src/erlang_stub.xml
+++ b/lib/kernel/doc/src/erlang_stub.xml
@@ -35,8 +35,8 @@
<description><p>
The module erlang is moved to the runtime system
- application. Please see <seealso
- marker="erts:erlang">erlang(3)</seealso> in the
+ application. Please see <seeerl
+ marker="erts:erlang">erlang(3)</seeerl> in the
ERTS reference manual instead.
</p></description>
diff --git a/lib/kernel/doc/src/erpc.xml b/lib/kernel/doc/src/erpc.xml
new file mode 100644
index 0000000000..8b15c38a56
--- /dev/null
+++ b/lib/kernel/doc/src/erpc.xml
@@ -0,0 +1,695 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2020</year><year>2020</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>erpc</title>
+ <prepared>Rickard Green</prepared>
+ <docno>1</docno>
+ <date>2020-02-20</date>
+ <rev>A</rev>
+ </header>
+ <module since="OTP 23.0">erpc</module>
+ <modulesummary>Enhanced Remote Procedure Call</modulesummary>
+ <description>
+ <p>
+ This module provide services similar to Remote Procedure Calls.
+ A remote procedure call is a method to call a function on a remote
+ node and collect the answer. It is used for collecting information
+ on a remote node, or for running a function with some specific side
+ effects on the remote node.
+ </p>
+ <p>
+ This is an enhanced subset of the operations provided by the
+ <seeerl marker="rpc"><c>rpc</c></seeerl> module. Enhanced in the
+ sense that it makes it possible to distinguish between returned
+ value, raised exceptions, and other errors. <c>erpc</c> also has
+ better performance and scalability than the original <c>rpc</c>
+ implementation. However, current <c>rpc</c> module will utilize
+ <c>erpc</c> in order to also provide these properties when
+ possible.
+ </p>
+ <p>
+ In order for an <c>erpc</c> operation to succeed, the remote
+ node also needs to support <c>erpc</c>. Typically only ordinary
+ Erlang nodes as of OTP 23 have <c>erpc</c> support.
+ </p>
+ <p>
+ Note that it is up to the user to ensure that correct code to
+ execute via <c>erpc</c> is available on the involved nodes.
+ </p>
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="request_id"/>
+ <desc>
+ <p>
+ An opaque type of call request identifiers. For more
+ information see
+ <seemfa marker="#send_request/4"><c>send_request/4</c></seemfa>.
+ </p>
+ </desc>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+
+ <func>
+ <name name="call" arity="2" since="OTP 23.0"/>
+ <name name="call" arity="3" since="OTP 23.0"/>
+ <fsummary>Evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ The same as calling
+ <seemfa marker="#call/5"><c>erpc:call(<anno>Node</anno>,erlang,apply,[<anno>Fun</anno>,[]],<anno>Timeout</anno>)</c></seemfa>.
+ May raise all the same exceptions as <c>erpc:call/5</c>
+ plus an <c>{erpc, badarg}</c> <c>error</c>
+ exception if <c><anno>Fun</anno></c> is not a fun of
+ zero arity.
+ </p>
+ <p>
+ The call <c>erpc:call(<anno>Node</anno>,<anno>Fun</anno>)</c>
+ is the same as the call
+ <c>erpc:call(<anno>Node</anno>,<anno>Fun</anno>,infinity)</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="call" arity="4" since="OTP 23.0"/>
+ <name name="call" arity="5" since="OTP 23.0"/>
+ <fsummary>Evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>)</c> on node <c><anno>Node</anno></c> and returns
+ the corresponding value <c><anno>Result</anno></c>.
+ <c><anno>Timeout</anno></c> is an integer representing
+ the timeout in milliseconds or the atom <c>infinity</c>
+ which prevents the operation from ever timing out.
+ </p>
+ <p>The call <c>erpc:call(<anno>Node</anno>, <anno>Module</anno>,
+ <anno>Function</anno>, <anno>Args</anno>)</c> is equivalent
+ to the call <c>erpc:call(<anno>Node</anno>, <anno>Module</anno>,
+ <anno>Function</anno>, <anno>Args</anno>, infinity)</c></p>
+ <p>
+ The <c>call()</c> function only returns if the applied
+ function successfully returned without raising any uncaught
+ exceptions, the operation did not time out, and no failures
+ occurred. In all other cases an exception is raised. The
+ following exceptions, listed by exception class, can
+ currently be raised by <c>erpc:call()</c>:
+ </p>
+ <taglist>
+ <tag><c>throw</c></tag>
+ <item><p>
+ The applied function called <c>throw(Value)</c>
+ and did not catch this exception. The exception
+ reason <c>Value</c> equals the argument passed to
+ <c>throw/1</c>.
+ </p></item>
+
+ <tag><c>exit</c></tag>
+ <item><p>
+ Exception reason:
+ </p>
+ <taglist>
+ <tag><c>{exception, ExitReason}</c></tag>
+ <item><p>
+ The applied function called <c>exit(ExitReason)</c>
+ and did not catch this exception. The exit
+ reason <c>ExitReason</c> equals the argument passed
+ to <c>exit/1</c>.
+ </p></item>
+ <tag><c>{signal, ExitReason}</c></tag>
+ <item><p>
+ The process that applied the function received an
+ exit signal and terminated due to this signal. The
+ process terminated with exit reason <c>ExitReason</c>.
+ </p></item>
+ </taglist>
+ </item>
+
+ <tag><c>error</c></tag>
+ <item><p>
+ Exception reason:
+ </p>
+ <taglist>
+
+ <tag><c>{exception, ErrorReason, StackTrace}</c></tag>
+ <item><p>
+ A runtime error occurred which raised and error
+ exception while applying the function,
+ and the applied function did not catch the
+ exception. The error reason <c>ErrorReason</c>
+ indicates the type of error that occurred.
+ <c>StackTrace</c> is formatted as when caught in a
+ <c>try/catch</c> construct. The <c>StackTrace</c>
+ is limited to the applied function and functions
+ called by it.
+ </p></item>
+
+ <tag><c>{erpc, ERpcErrorReason}</c></tag>
+ <item><p>
+ The <c>erpc</c> operation failed. The following
+ <c>ERpcErrorReason</c>s are the most common ones:
+ </p>
+
+ <taglist>
+ <tag><c>badarg</c></tag>
+ <item>
+ <p>If any one of these are true:</p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Args</anno></c> is not a list.
+ Note that the list is not verified to be
+ a proper list at the client side.</p></item>
+ <item><p><c><anno>Timeout</anno></c> is not the
+ atom <c>infinity</c> or an integer in valid
+ range.</p></item>
+ </list>
+ </item>
+
+ <tag><c>noconnection</c></tag>
+ <item><p>
+ The connection to <c>Node</c> was lost or could
+ not be established. The function may or may not
+ be applied.
+ </p></item>
+
+ <tag><c>system_limit</c></tag>
+ <item><p>
+ The <c>erpc</c> operation failed due to some system
+ limit being reached. This typically due to failure
+ to create a process on the remote node <c>Node</c>,
+ but can be other things as well.
+ </p></item>
+
+ <tag><c>timeout</c></tag>
+ <item><p>
+ The <c>erpc</c> operation timed out. The function may
+ or may not be applied.
+ </p></item>
+
+ <tag><c>notsup</c></tag>
+ <item><p>
+ The remote node <c>Node</c> does not support
+ this <c>erpc</c> operation.
+ </p>
+ </item>
+
+ </taglist>
+ </item>
+
+ </taglist>
+ </item>
+ </taglist>
+
+ <p>
+ If the <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a timeout or
+ a connection loss), the caller will not receive any
+ further information about the result if/when the applied
+ function completes. If the applied function explicitly
+ communicates with the calling process, such communication
+ may, of course, reach the calling process.
+ </p>
+
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be the calling process itself, a server, or a freshly
+ spawned process.
+ </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="cast" arity="2" since="OTP 23.0"/>
+ <fsummary>Evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ The same as calling
+ <seemfa marker="#cast/4"><c>erpc:cast(<anno>Node</anno>,erlang,apply,[<anno>Fun</anno>,[]])</c></seemfa>.
+ </p>
+ <p><c>erpc:cast/2</c> fails with an <c>{erpc, badarg}</c>
+ <c>error</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Fun</anno></c> is not a a fun of zero arity.</p></item>
+ </list>
+ </desc>
+ </func>
+
+ <func>
+ <name name="cast" arity="4" since="OTP 23.0"/>
+ <fsummary>Evaluate a function call on a node ignoring the result.</fsummary>
+ <desc>
+ <p>
+ Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>)</c> on node
+ <c><anno>Node</anno></c>. No response is delivered to the
+ calling process. <c>erpc:cast()</c> returns immediately
+ after the cast request has been sent. Any failures beside
+ bad arguments are silently ignored.
+ </p>
+ <p><c>erpc:cast/4</c> fails with an <c>{erpc, badarg}</c>
+ <c>error</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Args</anno></c> is not a list. Note that
+ the list is not verified to be a proper list at the client
+ side.</p></item>
+ </list>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be a server, or a freshly spawned process.
+ </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="check_response" arity="2" since="OTP 23.0"/>
+ <fsummary>Check if a message is a response corresponding to a
+ previously sent call request.</fsummary>
+ <desc>
+ <p>
+ Check if a message is a response to a <c>call</c> request
+ previously made by the calling process using
+ <seemfa marker="#send_request/4"><c>erpc:send_request/4</c></seemfa>.
+ <c><anno>RequestId</anno></c> should be the value returned
+ from the previously made <c>erpc:send_request()</c> call,
+ and the corresponding response should not already have been
+ received and handled to completion by <c>erpc:check_response()</c>,
+ <seemfa marker="#receive_response/2"><c>erpc:receive_response()</c></seemfa>, or
+ <seemfa marker="#wait_response/2"><c>erpc:wait_response()</c></seemfa>.
+ <c><anno>Message</anno></c> is the message to check.
+ </p>
+ <p>
+ If <c><anno>Message</anno></c> does not correspond to the
+ response, the atom <c>no_response</c> is returned. If
+ <c><anno>Message</anno></c> corresponds to the response, the
+ <c>call</c> operation is completed and either the result is
+ returned as <c>{response, Result}</c> where <c>Result</c>
+ corresponds to the value returned from the applied function
+ or an exception is raised. The exceptions that can be raised
+ corresponds to the same exceptions as can be raised by
+ <seemfa marker="#call/4"><c>erpc:call/4</c></seemfa>.
+ That is, no <c>{erpc, timeout}</c> <c>error</c> exception
+ can be raised. <c>erpc:check_response()</c> will fail with
+ an <c>{erpc, badarg}</c> exception if/when an invalid
+ <c><anno>RequestId</anno></c> is detected.
+ </p>
+ <p>
+ If the <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a connection loss),
+ the caller will not receive any further information about the
+ result if/when the applied function completes. If the applied
+ function explicitly communicates with the calling process,
+ such communication may, of course, reach the calling process.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="multicall" arity="2" since="OTP 23.0"/>
+ <name name="multicall" arity="3" since="OTP 23.0"/>
+ <fsummary>Evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ The same as calling
+ <seemfa marker="#multicall/5"><c>erpc:multicall(<anno>Nodes</anno>,erlang,apply,[<anno>Fun</anno>,[]],<anno>Timeout</anno>)</c></seemfa>.
+ May raise all the same exceptions as <c>erpc:multicall/5</c>
+ plus an <c>{erpc, badarg}</c> <c>error</c>
+ exception if <c><anno>Fun</anno></c> is not a fun of
+ zero arity.
+ </p>
+ <p>
+ The call <c>erpc:multicall(<anno>Nodes</anno>,<anno>Fun</anno>)</c>
+ is the same as the call
+ <c>erpc:multicall(<anno>Nodes</anno>,<anno>Fun</anno>, infinity)</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="multicall" arity="4" since="OTP 23.0"/>
+ <name name="multicall" arity="5" since="OTP 23.0"/>
+ <fsummary>Evaluate a function call on a number of nodes.</fsummary>
+ <type name="caught_call_exception"/>
+ <type name="stack_item"/>
+ <desc>
+ <p>
+ Performs multiple <c>call</c> operations in parallel
+ on multiple nodes. That is, evaluates
+ <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>)</c> on the nodes
+ <c><anno>Nodes</anno></c> in parallel.
+ <c><anno>Timeout</anno></c> is an integer representing
+ the timeout in milliseconds or the atom <c>infinity</c>
+ which prevents the operation from ever timing out.
+ The result is returned as a list where
+ the result from each node is placed at the same position
+ as the node name is placed in <c><anno>Nodes</anno></c>.
+ Each item in the resulting list is formatted as either:
+ </p>
+ <taglist>
+ <tag><c>{ok, Result}</c></tag>
+ <item><p>
+ The <c>call</c> operation for this specific node
+ returned <c>Result</c>.
+ </p></item>
+ <tag><c>{Class, ExceptionReason}</c></tag>
+ <item><p>
+ The <c>call</c> operation for this specific node
+ raised an exception of class <c>Class</c> with
+ exception reason <c>ExceptionReason</c>. These
+ corresponds the the exceptions that
+ <seemfa marker="#call/5"><c>erpc:call/5</c></seemfa>
+ can raise.
+ </p></item>
+ </taglist>
+ <p><c>erpc:multicall/5</c> fails with an <c>{erpc, badarg}</c>
+ <c>error</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Nodes</anno></c> is not a proper list of
+ atoms. Note that some requests may already have
+ been sent when the failure occurs. That is, the function
+ may or may not be applied on some nodes.</p></item>
+ <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Args</anno></c> is not a list. Note that the
+ list is not verified to be a proper list at the client side.</p></item>
+ </list>
+ <p>
+ The call <c>erpc:multicall(<anno>Nodes</anno>, <anno>Module</anno>,
+ <anno>Function</anno>, <anno>Args</anno>)</c> is equivalent
+ to the call <c>erpc:multicall(<anno>Nodes</anno>, <anno>Module</anno>,
+ <anno>Function</anno>, <anno>Args</anno>, infinity)</c>. These
+ calls are also equivalent to calling <c>my_multicall(Nodes, Module,
+ Function, Args)</c> if one disregard performance and failure
+ behavior:
+ </p>
+ <pre>
+my_multicall(Nodes, Module, Function, Args) ->
+ ReqIds = lists:map(fun (Node) ->
+ <seemfa marker="#send_request/4">erpc:send_request(Node, Module, Function, Args)</seemfa>
+ end,
+ Nodes),
+ lists:map(fun (ReqId) ->
+ try
+ {ok, <seemfa marker="#receive_response/2">erpc:receive_response(ReqId, infinity)</seemfa>}
+ catch
+ Class:Reason ->
+ {Class, Reason}
+ end
+ end,
+ ReqIds).
+</pre>
+
+ <p>
+ The <c><anno>Timeout</anno></c> value in milliseconds
+ sets an upper time limit for all <c>call</c> operations
+ to complete.
+ </p>
+
+ <p>
+ If an <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a timeout,
+ connection loss, or an improper <c><anno>Nodes</anno></c>
+ list), the caller will not receive any further information
+ about the result if/when the applied function completes.
+ If the applied function communicates
+ with the calling process, such communication may, of
+ course, reach the calling process.
+ </p>
+
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be the calling process itself, a server, or a freshly
+ spawned process.
+ </p>
+ </note>
+ </desc>
+
+ </func>
+
+ <func>
+ <name name="multicast" arity="2" since="OTP 23.0"/>
+ <fsummary>Evaluate a function call on a set nodes.</fsummary>
+ <desc>
+ <p>
+ The same as calling
+ <seemfa marker="#multicast/4"><c>erpc:multicast(<anno>Nodes</anno>,erlang,apply,[<anno>Fun</anno>,[]])</c></seemfa>.
+ </p>
+ <p><c>erpc:multicast/2</c> fails with an <c>{erpc, badarg}</c>
+ <c>error</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Nodes</anno></c> is not a proper list of atoms.</p></item>
+ <item><p><c><anno>Fun</anno></c> is not a a fun of zero arity.</p></item>
+ </list>
+ </desc>
+ </func>
+
+ <func>
+ <name name="multicast" arity="4" since="OTP 23.0"/>
+ <fsummary>Evaluate a function call on a set of nodes ignoring the result.</fsummary>
+ <desc>
+ <p>
+ Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>)</c> on the nodes
+ <c><anno>Nodes</anno></c>. No response is delivered to the
+ calling process. <c>erpc:multicast()</c> returns immediately
+ after the cast requests have been sent. Any failures beside
+ bad arguments are silently ignored.
+ </p>
+ <p><c>erpc:multicast/4</c> fails with an <c>{erpc, badarg}</c>
+ <c>error</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Nodes</anno></c> is not a proper list of
+ atoms. Note that some requests may already have
+ been sent when the failure occurs. That is, the function
+ may or may not be applied on some nodes.</p></item>
+ <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Args</anno></c> is not a list. Note that the
+ list is not verified to be a proper list at the client side.</p></item>
+ </list>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be a server, or a freshly spawned process.
+ </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="receive_response" arity="1" since="OTP 23.0"/>
+ <name name="receive_response" arity="2" since="OTP 23.0"/>
+ <fsummary>Receive a call response corresponding to a
+ previously sent call request.</fsummary>
+ <desc>
+ <p>
+ Receive a response to a <c>call</c> request previously
+ made by the calling process using
+ <seemfa marker="#send_request/4"><c>erpc:send_request/4</c></seemfa>.
+ <c><anno>RequestId</anno></c> should be the value returned from
+ the previously made <c>erpc:send_request()</c> call, and
+ the corresponding response should not already have been received
+ and handled to completion by
+ <seemfa marker="#check_response/2"><c>erpc:check_response()</c></seemfa>,
+ <c>erpc:receive_response()</c>, or
+ <seemfa marker="#wait_response/2"><c>erpc:wait_response()</c></seemfa>.
+ <c><anno>Timeout</anno></c> is an integer representing
+ the timeout in milliseconds or the atom <c>infinity</c>
+ which prevents the operation from ever timing out. The <c>call</c>
+ operation is completed once the <c>erpc:receive_response()</c> call
+ returns or raise an exception.
+ </p>
+ <p>
+ The call <c>erpc:receive_response(<anno>RequestId</anno>)</c> is
+ equivalent to the call
+ <c>erpc:receive_response(<anno>RequestId</anno>, infinity)</c>.
+ </p>
+ <p>
+ A call to the function
+ <c>my_call(Node, Module, Function, Args, Timeout)</c>
+ below is equivalent to the call
+ <seemfa marker="#call/5"><c>erpc:call(Node, Module, Function, Args,
+ Timeout)</c></seemfa> if one disregards performance. <c>erpc:call()</c>
+ can utilize a message queue optimization which removes the need to scan
+ the whole message queue which the combination
+ <c>erpc:send_request()/erpc:receive_response()</c> cannot.
+ </p>
+ <pre>
+my_call(Node, Module, Function, Args, Timeout) ->
+ RequestId = <seemfa marker="#send_request/4">erpc:send_request(Node, Module, Function, Args)</seemfa>,
+ erpc:receive_response(RequestId, Timeout).
+</pre>
+ <p>
+ If the <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a timeout, or
+ a connection loss), the caller will not receive any
+ further information about the result if/when the applied
+ function completes. If the applied function explicitly
+ communicates with the calling process, such communication
+ may, of course, reach the calling process.
+ </p>
+
+ <p>
+ <c>erpc:receive_response()</c> will return or raise exceptions the
+ same way as <seemfa marker="#call/5"><c>erpc:call/5</c></seemfa>
+ does with the exception of <c>{erpc, badarg}</c>. An
+ <c>{erpc, badarg}</c> exception will be raised if/when an invalid
+ <c><anno>RequestId</anno></c> is detected or if an invalid
+ <c><anno>Timeout</anno></c> is passed.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="send_request" arity="2" since="OTP 23.0"/>
+ <fsummary>Send a request to evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ The same as calling
+ <seemfa marker="#send_request/4"><c>erpc:send_request(<anno>Node</anno>,erlang,apply,[<anno>Fun</anno>,[]])</c></seemfa>.
+ </p>
+ <p><c>erpc:send_request/2</c> fails with an <c>{erpc, badarg}</c>
+ <c>error</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Fun</anno></c> is not a fun of
+ zero arity.</p></item>
+ </list>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be a server, or a freshly spawned process.
+ </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="send_request" arity="4" since="OTP 23.0"/>
+ <fsummary>Send a request to evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ Send an asynchronous <c>call</c> request to the node
+ <c><anno>Node</anno></c>. <c>erpc:send_request()</c>
+ returns a request identifier that later is to be passed
+ as argument to either
+ <seemfa marker="#receive_response/1"><c>erpc:receive_response()</c></seemfa>,
+ <seemfa marker="#wait_response/1"><c>erpc:wait_response()</c></seemfa>,
+ or,
+ <seemfa marker="#check_response/2"><c>erpc:check_response()</c></seemfa>
+ in order to get the response of the call request.
+ </p>
+ <p><c>erpc:send_request()</c> fails with an <c>{erpc, badarg}</c>
+ <c>error</c> exception if:</p>
+ <list>
+ <item><p><c><anno>Node</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Module</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Function</anno></c> is not an atom.</p></item>
+ <item><p><c><anno>Args</anno></c> is not a list. Note that the
+ list is not verified to be a proper list at the client side.</p></item>
+ </list>
+ </desc>
+ </func>
+
+ <func>
+ <name name="wait_response" arity="1" since="OTP 23.0"/>
+ <name name="wait_response" arity="2" since="OTP 23.0"/>
+ <fsummary>Wait or poll for a call response corresponding to a previously
+ sent call request.</fsummary>
+ <desc>
+ <p>
+ Wait or poll for a response message to a <c>call</c> request
+ previously made by the calling process using
+ <seemfa marker="#send_request/4"><c>erpc:send_request/4</c></seemfa>.
+ <c><anno>RequestId</anno></c> should be the value returned from
+ the previously made <c>erpc:send_request()</c> call, and the
+ corresponding response should not already have been received and handled
+ to completion by
+ <seemfa marker="#check_response/2"><c>erpc:check_response()</c></seemfa>,
+ <seemfa marker="#receive_response/2"><c>erpc:receive_response()</c></seemfa>,
+ or <c>erpc:wait_response()</c>. <c><anno>WaitTime</anno></c> equals the
+ time to wait in milliseconds (or the atom <c>infinity</c>) during the wait.
+ <c><anno>WaitTime</anno></c> is an integer representing time to wait
+ in milliseconds or the atom <c>infinity</c> which will cause
+ <c>wait_response/2</c> to wait for a response until it appears
+ regardless of how long time that is.
+ </p>
+ <p>
+ The call <c>erpc:wait_response(<anno>RequestId</anno>)</c> is equivalent
+ to the call <c>erpc:wait_response(<anno>RequestId</anno>, 0)</c>. That is,
+ poll for a response message to a <c>call</c> request previously made by
+ the calling process.
+ </p>
+ <p>
+ If no response is received before <c><anno>WaitTime</anno></c> milliseconds,
+ the atom <c>no_response</c> is returned. It is valid to continue waiting
+ for a response as many times as needed up until a response has
+ been received and completed by <c>erpc:check_response()</c>,
+ <c>erpc:receive_response()</c>, or <c>erpc:wait_response()</c>. If a
+ response is received, the <c>call</c> operation is completed and either
+ the result is returned as <c>{response, Result}</c> where <c>Result</c>
+ corresponds to the value returned from the applied function or an
+ exception is raised. The exceptions that can be raised corresponds to the
+ same exceptions as can be raised by
+ <seemfa marker="#call/4"><c>erpc:call/4</c></seemfa>.
+ That is, no <c>{erpc, timeout}</c> <c>error</c> exception can be raised.
+ <c>erpc:wait_response()</c> will fail with an <c>{erpc, badarg}</c>
+ exception if/when an invalid <c><anno>RequestId</anno></c> is detected
+ or if an invalid <c><anno>WaitTime</anno></c> is passed.
+ </p>
+ <p>
+ If the <c>erpc</c> operation fails, but it is unknown if
+ the function is/will be applied (that is, a too large wait time
+ value, or a connection loss), the caller will not receive any
+ further information about the result if/when the applied function
+ completes. If the applied function explicitly communicates with the
+ calling process, such communication may, of course, reach the
+ calling process.
+ </p>
+ </desc>
+ </func>
+
+ </funcs>
+</erlref>
+
diff --git a/lib/kernel/doc/src/error_logger.xml b/lib/kernel/doc/src/error_logger.xml
index c170b4fa34..cd1b0069dd 100644
--- a/lib/kernel/doc/src/error_logger.xml
+++ b/lib/kernel/doc/src/error_logger.xml
@@ -41,19 +41,19 @@
automatically started when an event handler is added
with <c>error_logger:add_report_handler/1,2</c>. The <c>error_logger</c>
module is then also added as a handler to the new logger.</p>
- <p>See <seealso marker="logger"><c>logger(3)</c></seealso> and
- the <seealso marker="logger_chapter">Logging</seealso> chapter
+ <p>See <seeerl marker="logger"><c>logger(3)</c></seeerl> and
+ the <seeguide marker="logger_chapter">Logging</seeguide> chapter
in the User's Guide for more information.</p>
</note>
<p>The Erlang <em>error logger</em> is an event manager (see
- <seealso marker="doc/design_principles:des_princ">OTP Design Principles</seealso> and
- <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>),
+ <seeguide marker="system/design_principles:des_princ">OTP Design Principles</seeguide> and
+ <seeerl marker="stdlib:gen_event"><c>gen_event(3)</c></seeerl>),
registered as <c>error_logger</c>.</p>
<p>Error logger is no longer started by default, but is
automatically started when an event handler is added
- with <seealso marker="#add_report_handler/1">
- <c>add_report_handler/1,2</c></seealso>. The <c>error_logger</c>
+ with <seemfa marker="#add_report_handler/1">
+ <c>add_report_handler/1,2</c></seemfa>. The <c>error_logger</c>
module is then also added as a handler to the new logger,
causing log events to be forwarded from logger to error logger,
and consequently to all installed error logger event
@@ -83,13 +83,13 @@
<p>Adds a new event handler to the error logger. The event
handler must be implemented as a <c>gen_event</c> callback
module, see
- <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:gen_event"><c>gen_event(3)</c></seeerl>.</p>
<p><c><anno>Handler</anno></c> is typically the name of the callback module
and <c><anno>Args</anno></c> is an optional term (defaults to []) passed
to the initialization callback function <c><anno>Handler</anno>:init/1</c>.
The function returns <c>ok</c> if successful.</p>
<p>The event handler must be able to handle the events in this module, see
- section <seealso marker="#events">Events</seealso>.</p>
+ section <seeerl marker="#events">Events</seeerl>.</p>
<p>The first time this function is called,
<c>error_logger</c> is added as a Logger handler, and
the <c>error_logger</c> process is started.</p>
@@ -101,7 +101,7 @@
<desc>
<p>Deletes an event handler from the error logger by calling
<c>gen_event:delete_handler(error_logger, <anno>Handler</anno>, [])</c>,
- see <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>.</p>
+ see <seeerl marker="stdlib:gen_event"><c>gen_event(3)</c></seeerl>.</p>
<p>If no more event handlers exist after the deletion,
<c>error_logger</c> is removed as a Logger handler, and
the <c>error_logger</c> process is stopped.</p>
@@ -116,16 +116,16 @@
<p>Log a standard error event. The <c><anno>Format</anno></c>
and <c><anno>Data</anno></c> arguments are the same as the
arguments of
- <seealso marker="stdlib:io#format/2"><c>io:format/2</c></seealso>
+ <seemfa marker="stdlib:io#format/2"><c>io:format/2</c></seemfa>
in STDLIB.</p>
<p>Error logger forwards the event to Logger, including
metadata that allows backwards compatibility with legacy
error logger event handlers.</p>
<p>The event is handled by the default Logger handler.</p>
<p>These functions are kept for backwards compatibility and
- must not be used by new code. Use the <seealso marker="logger#macros">
- <c>?LOG_ERROR</c></seealso> macro or
- <seealso marker="logger#error-1"><c>logger:error/1,2,3</c></seealso>
+ must not be used by new code. Use the <seeerl marker="logger#macros">
+ <c>?LOG_ERROR</c></seeerl> macro or
+ <seemfa marker="logger#error/1"><c>logger:error/1,2,3</c></seemfa>
instead.</p>
<p><em>Example:</em></p>
<pre>
@@ -150,9 +150,9 @@ ok</pre>
compatibility with legacy error logger event handlers.</p>
<p>The event is handled by the default Logger handler.</p>
<p>This functions is kept for backwards compatibility and
- must not be used by new code. Use the <seealso marker="logger#macros">
- <c>?LOG_ERROR</c></seealso> macro or
- <seealso marker="logger#error-1"><c>logger:error/1,2,3</c></seealso>
+ must not be used by new code. Use the <seeerl marker="logger#macros">
+ <c>?LOG_ERROR</c></seeerl> macro or
+ <seemfa marker="logger#error/1"><c>logger:error/1,2,3</c></seemfa>
instead.</p>
<p><em>Example:</em></p>
<pre>
@@ -182,11 +182,11 @@ ok</pre>
event handler, must be added to handle this event.</p>
<p>It is recommended that <c><anno>Report</anno></c> follows the same
structure as for
- <seealso marker="#error_report/1"><c>error_report/1</c></seealso>.</p>
+ <seemfa marker="#error_report/1"><c>error_report/1</c></seemfa>.</p>
<p>This functions is kept for backwards compatibility and
- must not be used by new code. Use the <seealso marker="logger#macros">
- <c>?LOG_ERROR</c></seealso> macro or
- <seealso marker="logger#error-1"><c>logger:error/1,2,3</c></seealso>
+ must not be used by new code. Use the <seeerl marker="logger#macros">
+ <c>?LOG_ERROR</c></seeerl> macro or
+ <seemfa marker="logger#error/1"><c>logger:error/1,2,3</c></seemfa>
instead.</p>
</desc>
</func>
@@ -201,9 +201,9 @@ ok</pre>
<c>unlimited</c> is returned.</p>
<note>
<p>The <c>error_logger_format_depth</c> variable
- is <seealso marker="kernel_app#deprecated-configuration-parameters">
- deprecated</seealso> since
- the <seealso marker="logger">Logger API</seealso> was
+ is <seeapp marker="kernel_app#deprecated-configuration-parameters">
+ deprecated</seeapp> since
+ the <seeerl marker="logger">Logger API</seeerl> was
introduced in Erlang/OTP 21.0. The variable, and this
function, are kept for backwards compatibility since they
still might be used by legacy report handlers.</p>
@@ -218,16 +218,16 @@ ok</pre>
<p>Log a standard information event. The <c><anno>Format</anno></c>
and <c><anno>Data</anno></c> arguments are the same as the
arguments of
- <seealso marker="stdlib:io#format/2"><c>io:format/2</c></seealso>
+ <seemfa marker="stdlib:io#format/2"><c>io:format/2</c></seemfa>
in STDLIB.</p>
<p>Error logger forwards the event to Logger, including
metadata that allows backwards compatibility with legacy
error logger event handlers.</p>
<p>The event is handled by the default Logger handler.</p>
<p>These functions are kept for backwards compatibility and
- must not be used by new code. Use the <seealso marker="logger#macros">
- <c>?LOG_INFO</c></seealso> macro or
- <seealso marker="logger#info-1"><c>logger:info/1,2,3</c></seealso>
+ must not be used by new code. Use the <seeerl marker="logger#macros">
+ <c>?LOG_INFO</c></seeerl> macro or
+ <seemfa marker="logger#info/1"><c>logger:info/1,2,3</c></seemfa>
instead.</p>
<p><em>Example:</em></p>
<pre>
@@ -252,9 +252,9 @@ ok</pre>
compatibility with legacy error logger event handlers.</p>
<p>The event is handled by the default Logger handler.</p>
<p>This functions is kept for backwards compatibility and
- must not be used by new code. Use the <seealso marker="logger#macros">
- <c>?LOG_INFO</c></seealso> macro or
- <seealso marker="logger#info-1"><c>logger:info/1,2,3</c></seealso>
+ must not be used by new code. Use the <seeerl marker="logger#macros">
+ <c>?LOG_INFO</c></seeerl> macro or
+ <seemfa marker="logger#info/1"><c>logger:info/1,2,3</c></seemfa>
instead.</p>
<p><em>Example:</em></p>
<pre>
@@ -285,11 +285,11 @@ ok</pre>
event handler, must be added to handle this event.</p>
<p>It is recommended that <c><anno>Report</anno></c> follows the same
structure as for
- <seealso marker="#info_report/1"><c>info_report/1</c></seealso>.</p>
+ <seemfa marker="#info_report/1"><c>info_report/1</c></seemfa>.</p>
<p>This functions is kept for backwards compatibility and
- must not be used by new code. Use the <seealso marker="logger#macros">
- <c>?LOG_INFO</c></seealso> macro or
- <seealso marker="logger#info-1"><c>logger:info/1,2,3</c></seealso>
+ must not be used by new code. Use the <seeerl marker="logger#macros">
+ <c>?LOG_INFO</c></seeerl> macro or
+ <seemfa marker="logger#info/1"><c>logger:info/1,2,3</c></seemfa>
instead.</p>
</desc>
</func>
@@ -316,9 +316,9 @@ ok</pre>
<p>This function is useful as a shortcut during development
and testing, but must not be used in a production
system. See
- section <seealso marker="logger_chapter">Logging</seealso>
+ section <seeguide marker="logger_chapter">Logging</seeguide>
in the Kernel User's Guide, and
- the <seealso marker="logger"><c>logger(3)</c></seealso>
+ the <seeerl marker="logger"><c>logger(3)</c></seeerl>
manual page for information about how to configure Logger
for live systems.</p>
<p><c>Request</c> is one of the following:</p>
@@ -355,9 +355,9 @@ ok</pre>
<p>This is done by manipulating the Logger configuration. The
function is useful as a shortcut during development and
testing, but must not be used in a production system. See
- section <seealso marker="logger_chapter">Logging</seealso>
+ section <seeguide marker="logger_chapter">Logging</seeguide>
in the Kernel User's Guide, and
- the <seealso marker="logger"><c>logger(3)</c></seealso>
+ the <seeerl marker="logger"><c>logger(3)</c></seeerl>
manual page for information about how to configure Logger
for live systems.</p>
</desc>
@@ -407,18 +407,18 @@ ok</pre>
<p>Log a standard warning event. The <c><anno>Format</anno></c>
and <c><anno>Data</anno></c> arguments are the same as the
arguments of
- <seealso marker="stdlib:io#format/2"><c>io:format/2</c></seealso>
+ <seemfa marker="stdlib:io#format/2"><c>io:format/2</c></seemfa>
in STDLIB.</p>
<p>Error logger forwards the event to Logger, including
metadata that allows backwards compatibility with legacy
error logger event handlers.</p>
<p>The event is handled by the default Logger handler. The log
level can be changed to error or info, see
- <seealso marker="#warning_map/0"><c>warning_map/0</c></seealso>.</p>
+ <seemfa marker="#warning_map/0"><c>warning_map/0</c></seemfa>.</p>
<p>These functions are kept for backwards compatibility and
- must not be used by new code. Use the <seealso marker="logger#macros">
- <c>?LOG_WARNING</c></seealso> macro or
- <seealso marker="logger#warning-1"><c>logger:warning/1,2,3</c></seealso>
+ must not be used by new code. Use the <seeerl marker="logger#macros">
+ <c>?LOG_WARNING</c></seeerl> macro or
+ <seemfa marker="logger#warning/1"><c>logger:warning/1,2,3</c></seemfa>
instead.</p>
<warning>
<p>If the Unicode translation modifier (<c>t</c>) is used in
@@ -437,11 +437,11 @@ ok</pre>
compatibility with legacy error logger event handlers.</p>
<p>The event is handled by the default Logger handler. The log
level can be changed to error or info, see
- <seealso marker="#warning_map/0"><c>warning_map/0</c></seealso>.</p>
+ <seemfa marker="#warning_map/0"><c>warning_map/0</c></seemfa>.</p>
<p>This functions is kept for backwards compatibility and
- must not be used by new code. Use the <seealso marker="logger#macros">
- <c>?LOG_WARNING</c></seealso> macro or
- <seealso marker="logger#warning-1"><c>logger:warning/1,2,3</c></seealso>
+ must not be used by new code. Use the <seeerl marker="logger#macros">
+ <c>?LOG_WARNING</c></seeerl> macro or
+ <seemfa marker="logger#warning/1"><c>logger:warning/1,2,3</c></seemfa>
instead.</p>
</desc>
</func>
@@ -458,14 +458,14 @@ ok</pre>
the event. A different Logger handler, or an error logger
event handler, must be added to handle this event.</p>
<p>The log level can be changed to error or info, see
- <seealso marker="#warning_map/0"><c>warning_map/0</c></seealso>.</p>
+ <seemfa marker="#warning_map/0"><c>warning_map/0</c></seemfa>.</p>
<p>It is recommended that <c><anno>Report</anno></c> follows the same
structure as for
- <seealso marker="#warning_report/1"><c>warning_report/1</c></seealso>.</p>
+ <seemfa marker="#warning_report/1"><c>warning_report/1</c></seemfa>.</p>
<p>This functions is kept for backwards compatibility and
- must not be used by new code. Use the <seealso marker="logger#macros">
- <c>?LOG_WARNING</c></seealso> macro or
- <seealso marker="logger#warning-1"><c>logger:warning/1,2,3</c></seealso>
+ must not be used by new code. Use the <seeerl marker="logger#macros">
+ <c>?LOG_WARNING</c></seeerl> macro or
+ <seemfa marker="logger#warning/1"><c>logger:warning/1,2,3</c></seemfa>
instead.</p>
</desc>
</func>
@@ -529,11 +529,11 @@ ok</pre>
</section>
<section>
<title>See Also</title>
- <p><seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>,
- <seealso marker="kernel:logger"><c>logger(3)</c></seealso>,
- <seealso marker="stdlib:log_mf_h"><c>log_mf_h(3)</c></seealso>,
- <seealso marker="kernel_app"><c>kernel(6)</c></seealso>,
- <seealso marker="sasl:sasl_app"><c>sasl(6)</c></seealso></p>
+ <p><seeerl marker="stdlib:gen_event"><c>gen_event(3)</c></seeerl>,
+ <seeerl marker="kernel:logger"><c>logger(3)</c></seeerl>,
+ <seeerl marker="stdlib:log_mf_h"><c>log_mf_h(3)</c></seeerl>,
+ <seeapp marker="kernel_app"><c>kernel(6)</c></seeapp>,
+ <seeapp marker="sasl:sasl_app"><c>sasl(6)</c></seeapp></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml
index fc25e83d40..9f369e63ff 100644
--- a/lib/kernel/doc/src/file.xml
+++ b/lib/kernel/doc/src/file.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2018</year>
+ <year>1996</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -38,13 +38,13 @@
through the same file server. A NIF or other OS process may observe
intermediate steps on certain operations on some operating systems,
eg. renaming an existing file on Windows, or
- <seealso marker="#write_file_info/2"><c>write_file_info/2</c>
- </seealso> on any OS at the time of writing.</p>
+ <seemfa marker="#write_file_info/2"><c>write_file_info/2</c>
+ </seemfa> on any OS at the time of writing.</p>
</warning>
<p>Regarding filename encoding, the Erlang VM can operate in
two modes. The current mode can be queried using function
- <seealso marker="#native_name_encoding/0"><c>native_name_encoding/0</c></seealso>.
+ <seemfa marker="#native_name_encoding/0"><c>native_name_encoding/0</c></seemfa>.
It returns <c>latin1</c> or <c>utf8</c>.</p>
<p>In <c>latin1</c> mode, the Erlang VM does not change the
@@ -53,8 +53,8 @@
converts filenames back and forth to the native filename encoding
(usually UTF-8, but UTF-16 on Windows).</p>
- <p>The default mode depends on the operating system. Windows and
- MacOS X enforce consistent filename encoding and therefore the
+ <p>The default mode depends on the operating system. Windows, MacOS X
+ and Android enforce consistent filename encoding and therefore the
VM uses <c>utf8</c> mode.</p>
<p>On operating systems with transparent naming (for example, all Unix
@@ -62,7 +62,7 @@
terminal supports UTF-8, otherwise <c>latin1</c>. The default can
be overridden using <c>+fnl</c> (to force <c>latin1</c> mode)
or <c>+fnu</c> (to force <c>utf8</c> mode) when starting
- <seealso marker="erts:erl"><c>erl</c></seealso>.</p>
+ <seecom marker="erts:erl"><c>erl</c></seecom>.</p>
<p>On operating systems with transparent naming, files can be
inconsistently named, for example, some files are encoded in UTF-8 while
@@ -75,14 +75,14 @@
systems with transparent naming.</p>
<p>When running in <c>utf8</c> mode, functions
- <seealso marker="#list_dir/1"><c>list_dir/1</c></seealso> and
- <seealso marker="#read_link/1"><c>read_link/1</c></seealso>
+ <seemfa marker="#list_dir/1"><c>list_dir/1</c></seemfa> and
+ <seemfa marker="#read_link/1"><c>read_link/1</c></seemfa>
never return raw filenames. To return all filenames including raw filenames,
use functions
- <seealso marker="#list_dir_all"><c>list_dir_all/1</c></seealso> and
- <seealso marker="#read_link_all"><c>read_link_all/1</c></seealso>.</p>
+ <seeerl marker="#list_dir_all"><c>list_dir_all/1</c></seeerl> and
+ <seeerl marker="#read_link_all"><c>read_link_all/1</c></seeerl>.</p>
- <p>See also section <seealso marker="stdlib:unicode_usage#notes-about-raw-filenames">Notes About Raw Filenames</seealso> in the STDLIB User's Guide.</p>
+ <p>See also section <seeguide marker="stdlib:unicode_usage#notes-about-raw-filenames">Notes About Raw Filenames</seeguide> in the STDLIB User's Guide.</p>
<note><p>
File operations used to accept filenames containing
@@ -104,7 +104,7 @@
<name>fd()</name>
<desc>
<p>A file descriptor representing a file opened in
- <seealso marker="#raw"><c>raw</c></seealso> mode.</p>
+ <seeerl marker="#raw"><c>raw</c></seeerl> mode.</p>
</desc>
</datatype>
<datatype>
@@ -112,7 +112,7 @@
<desc>
<p>
See also the documentation of the
- <seealso marker="#type-name_all"><c>name_all()</c></seealso> type.
+ <seetype marker="#name_all"><c>name_all()</c></seetype> type.
</p>
</desc>
</datatype>
@@ -121,7 +121,7 @@
<desc>
<p>
See also the documentation of the
- <seealso marker="#type-name_all"><c>name_all()</c></seealso> type.
+ <seetype marker="#name_all"><c>name_all()</c></seetype> type.
</p>
</desc>
</datatype>
@@ -129,7 +129,7 @@
<name name="io_device"/>
<desc>
<p>As returned by
- <seealso marker="#open/2"><c>open/2</c></seealso>;
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>;
<c>pid()</c> is a process handling I/O-protocols.</p>
</desc>
</datatype>
@@ -138,7 +138,7 @@
<desc>
<p>If VM is in Unicode filename mode, <c>string()</c> and <c>char()</c>
are allowed to be &gt; 255. See also the documentation of the
- <seealso marker="#type-name_all"><c>name_all()</c></seealso> type.
+ <seetype marker="#name_all"><c>name_all()</c></seetype> type.
</p>
</desc>
</datatype>
@@ -202,10 +202,7 @@
<desc>
<p><c>allocate/3</c> can be used to preallocate space for a file.</p>
<p>This function only succeeds in platforms that provide this
- feature. When it succeeds, space is preallocated for the file but
- the file size might not be updated. This behaviour depends on the
- preallocation implementation. To guarantee that the file size is updated,
- truncate the file to the new size.</p>
+ feature.</p>
</desc>
</func>
<func>
@@ -213,7 +210,7 @@
<fsummary>Change group of a file.</fsummary>
<desc>
<p>Changes group of a file. See
- <seealso marker="#write_file_info/2"><c>write_file_info/2</c></seealso>.</p>
+ <seemfa marker="#write_file_info/2"><c>write_file_info/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -221,7 +218,7 @@
<fsummary>Change permissions of a file.</fsummary>
<desc>
<p>Changes permissions of a file. See
- <seealso marker="#write_file_info/2"><c>write_file_info/2</c></seealso>.</p>
+ <seemfa marker="#write_file_info/2"><c>write_file_info/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -229,7 +226,7 @@
<fsummary>Change owner of a file.</fsummary>
<desc>
<p>Changes owner of a file. See
- <seealso marker="#write_file_info/2"><c>write_file_info/2</c></seealso>.</p>
+ <seemfa marker="#write_file_info/2"><c>write_file_info/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -237,7 +234,7 @@
<fsummary>Change owner and group of a file.</fsummary>
<desc>
<p>Changes owner and group of a file. See
- <seealso marker="#write_file_info/2"><c>write_file_info/2</c></seealso>.</p>
+ <seemfa marker="#write_file_info/2"><c>write_file_info/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -245,7 +242,7 @@
<fsummary>Change the modification time of a file.</fsummary>
<desc>
<p>Changes the modification and access times of a file. See
- <seealso marker="#write_file_info/2"><c>write_file_info/2</c></seealso>.</p>
+ <seemfa marker="#write_file_info/2"><c>write_file_info/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -253,7 +250,7 @@
<fsummary>Change the modification and last access time of a file.</fsummary>
<desc>
<p>Changes the modification and last access times of a file. See
- <seealso marker="#write_file_info/2"><c>write_file_info/2</c></seealso>.</p>
+ <seemfa marker="#write_file_info/2"><c>write_file_info/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -266,7 +263,7 @@
<p>Notice that if option <c>delayed_write</c> was
used when opening the file, <c>close/1</c> can return an
old write error and not even try to close the file. See
- <seealso marker="#open/2"><c>open/2</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -284,7 +281,7 @@
<item>
<p>An error occurred when opening the file or reading it.
For a list of typical error codes, see
- <seealso marker="#open/2"><c>open/2</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
</item>
<tag><c>{error, {<anno>Line</anno>, <anno>Mod</anno>,
<anno>Term</anno>}}</c></tag>
@@ -292,7 +289,7 @@
<p>An error occurred when interpreting the Erlang terms in
the file. To convert the three-element tuple to an English
description of the error, use
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>.</p>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>.</p>
</item>
</taglist>
<p><em>Example:</em></p>
@@ -304,7 +301,7 @@ f.txt: {person, "kalle", 25}.
{ok,[{person,"kalle",25},{person,"pelle",30}]}</pre>
<p>The encoding of <c><anno>Filename</anno></c> can be set
by a comment, as described in
- <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:epp#encoding"><c>epp(3)</c></seeerl>.</p>
</desc>
</func>
<func>
@@ -319,7 +316,7 @@ f.txt: {person, "kalle", 25}.
<c><anno>ByteCount</anno></c> defaults to <c>infinity</c>, denoting an
infinite number of bytes.</p>
<p>Argument <c><anno>Modes</anno></c> is a list of possible modes,
- see <seealso marker="#open/2"><c>open/2</c></seealso>, and defaults to
+ see <seemfa marker="#open/2"><c>open/2</c></seemfa>, and defaults to
<c>[]</c>.</p>
<p>If both <c><anno>Source</anno></c> and
<c><anno>Destination</anno></c> refer to
@@ -339,10 +336,10 @@ f.txt: {person, "kalle", 25}.
encountered on the source. If the operation fails,
<c>{error, <anno>Reason</anno>}</c> is returned.</p>
<p>Typical error reasons: as for
- <seealso marker="#open/2"><c>open/2</c></seealso> if a file
+ <seemfa marker="#open/2"><c>open/2</c></seemfa> if a file
had to be opened, and as for
- <seealso marker="#read/2"><c>read/2</c></seealso> and
- <seealso marker="#write/2"><c>write/2</c></seealso>.</p>
+ <seemfa marker="#read/2"><c>read/2</c></seemfa> and
+ <seemfa marker="#write/2"><c>write/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -405,6 +402,28 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
+ <name name="del_dir_r" arity="1" since="OTP 23.0"/>
+ <fsummary>Delete a file or directory.</fsummary>
+ <desc>
+ <p>Deletes file or directory <c><anno>File</anno></c>.
+ If <c><anno>File</anno></c> is a directory, its contents
+ is first recursively deleted. Returns:</p>
+ <taglist>
+ <tag><c>ok</c></tag>
+ <item>
+ <p>The operation completed without errors.</p>
+ </item>
+ <tag><c>{error, posix()}</c></tag>
+ <item>
+ <p>An error occurred when accessing or deleting <c><anno>File</anno></c>.
+ If some file or directory under <c><anno>File</anno></c> could not be
+ deleted, <c><anno>File</anno></c> cannot be deleted as it is non-empty,
+ and <c>{error, eexist}</c> is returned.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
<name name="delete" arity="1" since=""/>
<fsummary>Delete a file.</fsummary>
<desc>
@@ -459,7 +478,7 @@ f.txt: {person, "kalle", 25}.
<item>
<p>An error occurred when opening the file or reading it.
For a list of typical error codes, see
- <seealso marker="#open/2"><c>open/2</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
</item>
<tag><c>{error, {<anno>Line</anno>, <anno>Mod</anno>,
<anno>Term</anno>}}</c></tag>
@@ -467,12 +486,12 @@ f.txt: {person, "kalle", 25}.
<p>An error occurred when interpreting the Erlang
expressions in the file. To convert the three-element tuple
to an English description of the error, use
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>.</p>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>.</p>
</item>
</taglist>
<p>The encoding of <c><anno>Filename</anno></c> can be set
by a comment, as described in
- <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:epp#encoding"><c>epp(3)</c></seeerl>.</p>
</desc>
</func>
<func>
@@ -482,7 +501,7 @@ f.txt: {person, "kalle", 25}.
<p>The same as <c>eval/1</c>, but the variable bindings
<c><anno>Bindings</anno></c> are used in the evaluation. For information
about the variable bindings, see
- <seealso marker="stdlib:erl_eval"><c>erl_eval(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:erl_eval"><c>erl_eval(3)</c></seeerl>.</p>
</desc>
</func>
<func>
@@ -759,9 +778,9 @@ f.txt: {person, "kalle", 25}.
<item><p>The functions in the <c>io</c> module cannot be used,
as they can only talk to an Erlang process.
Instead, use functions
- <seealso marker="#read/2"><c>read/2</c></seealso>,
- <seealso marker="#read_line/1"><c>read_line/1</c></seealso>, and
- <seealso marker="#write/2"><c>write/2</c></seealso>.</p></item>
+ <seemfa marker="#read/2"><c>read/2</c></seemfa>,
+ <seemfa marker="#read_line/1"><c>read_line/1</c></seemfa>, and
+ <seemfa marker="#write/2"><c>write/2</c></seemfa>.</p></item>
<item><p>Especially if <c>read_line/1</c> is to be used on a <c>raw</c>
file, it is recommended to combine this option with option
<c>{read_ahead, Size}</c> as line-oriented I/O is inefficient
@@ -838,7 +857,7 @@ f.txt: {person, "kalle", 25}.
files. Option <c>compressed</c> must be combined
with <c>read</c> or <c>write</c>, but not both.
Notice that the file size obtained with
- <seealso marker="#read_file_info/1"><c>read_file_info/1</c></seealso>
+ <seemfa marker="#read_file_info/1"><c>read_file_info/1</c></seemfa>
does probably not match the number of bytes that can be
read from a compressed file.</p>
</item>
@@ -847,9 +866,9 @@ f.txt: {person, "kalle", 25}.
<p>Makes the file perform automatic translation of characters to
and from a specific (Unicode) encoding. Notice that the data supplied
to
- <seealso marker="#write/2"><c>write/2</c></seealso>
+ <seemfa marker="#write/2"><c>write/2</c></seemfa>
or returned by
- <seealso marker="#read/2"><c>read/2</c></seealso>
+ <seemfa marker="#read/2"><c>read/2</c></seemfa>
still is byte-oriented; this option
denotes only how data is stored in the disk file.</p>
<p>Depending on the encoding, different methods of reading and writing
@@ -857,7 +876,7 @@ f.txt: {person, "kalle", 25}.
this module (<c>file</c>) for reading and writing data as the interfaces
provided here work with byte-oriented data. Using other (Unicode)
encodings makes the
- <seealso marker="stdlib:io"><c>io(3)</c></seealso> functions
+ <seeerl marker="stdlib:io"><c>io(3)</c></seeerl> functions
<c>get_chars</c>, <c>get_line</c>, and <c>put_chars</c> more suitable,
as they can work with the full Unicode range.</p>
<p>If data is sent to an <c>io_device()</c> in a format that cannot be
@@ -869,12 +888,12 @@ f.txt: {person, "kalle", 25}.
<tag><c>latin1</c></tag>
<item>
<p>The default encoding. Bytes supplied to the file, that is,
- <seealso marker="#write/2"><c>write/2</c></seealso>
+ <seemfa marker="#write/2"><c>write/2</c></seemfa>
are written "as is" on the file. Likewise, bytes read from the file,
that is,
- <seealso marker="#read/2"><c>read/2</c></seealso> are
+ <seemfa marker="#read/2"><c>read/2</c></seemfa> are
returned "as is". If module
- <seealso marker="stdlib:io"><c>io(3)</c></seealso> is used for
+ <seeerl marker="stdlib:io"><c>io(3)</c></seeerl> is used for
writing, the file can only cope with Unicode characters up to code point
255 (the ISO Latin-1 range).</p>
</item>
@@ -883,12 +902,12 @@ f.txt: {person, "kalle", 25}.
<p>Characters are translated to and from UTF-8 encoding before they are
written to or read from the file. A file opened in this way can be
readable using function
- <seealso marker="#read/2"><c>read/2</c></seealso>,
+ <seemfa marker="#read/2"><c>read/2</c></seemfa>,
as long as no data stored on
the file lies beyond the ISO Latin-1 range (0..255), but failure occurs
if the data contains Unicode code points beyond that range. The file is
best read with the functions in the Unicode aware module
- <seealso marker="stdlib:io"><c>io(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:io"><c>io(3)</c></seeerl>.</p>
<p>Bytes written to the file by any means are translated to UTF-8 encoding
before being stored on the disk file.</p>
</item>
@@ -914,11 +933,11 @@ f.txt: {person, "kalle", 25}.
</item>
</taglist>
<p>The Encoding can be changed for a file "on the fly" by using function
- <seealso marker="stdlib:io#setopts/2"><c>io:setopts/2</c></seealso>.
+ <seemfa marker="stdlib:io#setopts/2"><c>io:setopts/2</c></seemfa>.
So a file can be analyzed in latin1 encoding for, for example, a BOM,
positioned beyond the BOM and then be set for the right encoding before
further reading. For functions identifying BOMs, see module
- <seealso marker="stdlib:unicode"><c>unicode(3)</c></seealso>. </p>
+ <seeerl marker="stdlib:unicode"><c>unicode(3)</c></seeerl>. </p>
<p>This option is not allowed on <c>raw</c> files.</p>
</item>
<tag><c>ram</c></tag>
@@ -939,6 +958,10 @@ f.txt: {person, "kalle", 25}.
support for POSIX <c>O_SYNC</c> or equivalent, use of the <c>sync</c>
flag causes <c>open</c> to return <c>{error, enotsup}</c>.</p>
</item>
+ <tag><c>directory</c></tag>
+ <item>
+ <p>Allows <c>open</c> to work on directories.</p>
+ </item>
</taglist>
<p>Returns:</p>
<taglist>
@@ -953,13 +976,12 @@ f.txt: {person, "kalle", 25}.
</item>
</taglist>
<p><c><anno>IoDevice</anno></c> is really the pid of the process that
- handles the file. This process is linked to the process
- that originally opened the file. If any process to which
- the <c><anno>IoDevice</anno></c> is linked terminates, the file is
- closed and the process itself is terminated.
+ handles the file. This process monitors the process that originally
+ opened the file (the owner process). If the owner process terminates,
+ the file is closed and the process itself terminates too.
An <c><anno>IoDevice</anno></c> returned from this call can be used
as an argument to the I/O functions (see
- <seealso marker="stdlib:io"><c>io(3)</c></seealso>).</p>
+ <seeerl marker="stdlib:io"><c>io(3)</c></seeerl>).</p>
<note>
<p>In previous versions of <c>file</c>, modes were specified
as one of the atoms <c>read</c>, <c>write</c>, or
@@ -985,8 +1007,10 @@ f.txt: {person, "kalle", 25}.
</item>
<tag><c>enotdir</c></tag>
<item>
- <p>A component of the filename is not a directory. On some
- platforms, <c>enoent</c> is returned instead.</p>
+ <p>A component of the filename is not a directory, or the
+ filename itself is not a directory if <c>directory</c>
+ mode was specified. On some platforms, <c>enoent</c> is
+ returned instead.</p>
</item>
<tag><c>enospc</c></tag>
<item>
@@ -1021,21 +1045,21 @@ f.txt: {person, "kalle", 25}.
<item>
<p>An error occurred when opening the file or reading it.
For a list of typical error codes, see
- <seealso marker="#open/2"><c>open/2</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
</item>
<tag><c>{error, {<anno>Line</anno>, <anno>Mod</anno>,
<anno>Term</anno>}}</c></tag>
<item>
<p>An error occurred when interpreting the Erlang terms in
the file. Use
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>
to convert the three-element tuple to an English description of
the error.</p>
</item>
</taglist>
<p>The encoding of <c><anno>Filename</anno></c> can be set
by a comment as described in
- <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:epp#encoding"><c>epp(3)</c></seeerl>.</p>
</desc>
</func>
<func>
@@ -1067,21 +1091,21 @@ f.txt: {person, "kalle", 25}.
<item>
<p>An error occurred when opening the file or reading it.
For a list of typical error codes, see
- <seealso marker="#open/2"><c>open/2</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
</item>
<tag><c>{error, {<anno>Line</anno>, <anno>Mod</anno>,
<anno>Term</anno>}}</c></tag>
<item>
<p>An error occurred when interpreting the Erlang
expressions in the file. Use
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>
to convert the three-element tuple to an English description
of the error.</p>
</item>
</taglist>
<p>The encoding of <c><anno>Filename</anno></c> can be set
by a comment as described in
- <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:epp#encoding"><c>epp(3)</c></seeerl>.</p>
</desc>
</func>
<func>
@@ -1140,21 +1164,21 @@ f.txt: {person, "kalle", 25}.
<item>
<p>An error occurred when opening the file or reading it.
For a list of typical error codes, see
- <seealso marker="#open/2"><c>open/2</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
</item>
<tag><c>{error, {<anno>Line</anno>, <anno>Mod</anno>,
<anno>Term</anno>}}</c></tag>
<item>
<p>An error occurred when interpreting the Erlang
expressions in the file. Use
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>
to convert the three-element tuple to an English description
of the error.</p>
</item>
</taglist>
<p>The encoding of <c><anno>Filename</anno></c> can be set
by a comment as described in
- <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:epp#encoding"><c>epp(3)</c></seeerl>.</p>
</desc>
</func>
<func>
@@ -1163,7 +1187,7 @@ f.txt: {person, "kalle", 25}.
<desc>
<p>The same as <c>path_script/2</c> but the variable bindings
<c><anno>Bindings</anno></c> are used in the evaluation. See
- <seealso marker="stdlib:erl_eval"><c>erl_eval(3)</c></seealso> about
+ <seeerl marker="stdlib:erl_eval"><c>erl_eval(3)</c></seeerl> about
variable bindings.</p>
</desc>
</func>
@@ -1268,15 +1292,12 @@ f.txt: {person, "kalle", 25}.
<desc>
<p>Combines <c>position/2</c> and <c>read/2</c> in one
operation, which is more efficient than calling them one at a
- time. If <c><anno>IoDevice</anno></c> is opened in <c>raw</c> mode,
- some restrictions apply:</p>
- <list type="bulleted">
- <item><c><anno>Location</anno></c> is only allowed to be an
- integer.</item>
- <item>The current position of the file is undefined after the
- operation.</item>
- </list>
- <p>As the position is specified as a byte-offset, take special caution
+ time.</p>
+ <p><c><anno>Location</anno></c> is only allowed to be an
+ integer for <c>raw</c> and <c>ram</c> modes.</p>
+ <p>The current position of the file after the operation
+ is undefined for <c>raw</c> mode and unchanged for <c>ram</c> mode.</p>
+ <p>As the position is specified as a byte-offset, take special caution
when working with files where <c>encoding</c> is set to something else
than <c>latin1</c>, as not every byte position is a valid character
boundary on such a file.</p>
@@ -1294,7 +1315,7 @@ f.txt: {person, "kalle", 25}.
before the failure.</p>
<p>When positioning in a file with other <c>encoding</c> than <c>latin1</c>,
caution must be taken to set the position on a correct character boundary.
- For details, see <seealso marker="#position/2"><c>position/2</c></seealso>.</p>
+ For details, see <seemfa marker="#position/2"><c>position/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -1303,17 +1324,15 @@ f.txt: {person, "kalle", 25}.
<desc>
<p>Combines <c>position/2</c> and <c>write/2</c> in one
operation, which is more efficient than calling them one at a
- time. If <c><anno>IoDevice</anno></c> has been opened in <c>raw</c> mode,
- some restrictions apply:</p>
- <list type="bulleted">
- <item><c><anno>Location</anno></c> is only allowed to be an
- integer.</item>
- <item>The current position of the file is undefined after the
- operation.</item>
- </list>
+ time.
+ </p>
+ <p><c><anno>Location</anno></c> is only allowed to be an
+ integer for <c>raw</c> and <c>ram</c> modes.</p>
+ <p>The current position of the file after the operation
+ is undefined for <c>raw</c> mode and unchanged for <c>ram</c> mode.</p>
<p>When positioning in a file with other <c>encoding</c> than <c>latin1</c>,
caution must be taken to set the position on a correct character boundary.
- For details, see <seealso marker="#position/2"><c>position/2</c></seealso>.</p>
+ For details, see <seemfa marker="#position/2"><c>position/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -1322,9 +1341,9 @@ f.txt: {person, "kalle", 25}.
<desc>
<p>Reads <c><anno>Number</anno></c> bytes/characters from the file
referenced by <c><anno>IoDevice</anno></c>. The functions
- <seealso marker="#read/2"><c>read/2</c></seealso>,
- <seealso marker="#pread/3"><c>pread/3</c></seealso>, and
- <seealso marker="#read_line/1"><c>read_line/1</c></seealso>
+ <seemfa marker="#read/2"><c>read/2</c></seemfa>,
+ <seemfa marker="#pread/3"><c>pread/3</c></seemfa>, and
+ <seemfa marker="#read_line/1"><c>read_line/1</c></seemfa>
are the only ways to read from a file opened in <c>raw</c> mode
(although they work for normally opened files, too).</p>
<p>For files where <c>encoding</c> is set to something else than <c>latin1</c>,
@@ -1334,7 +1353,7 @@ f.txt: {person, "kalle", 25}.
this number when reading a Unicode file.</p>
<p>Also, if <c>encoding</c> is set to something else than <c>latin1</c>,
the <c>read/3</c> call fails if the data contains characters larger than 255,
- which is why module <seealso marker="stdlib:io"><c>io(3)</c></seealso>
+ which is why module <seeerl marker="stdlib:io"><c>io(3)</c></seeerl>
is to be preferred when reading such a file.</p>
<p>The function returns:</p>
<taglist>
@@ -1437,8 +1456,12 @@ f.txt: {person, "kalle", 25}.
only information about local files is returned. Note that this will
break this module's atomicity guarantees as it can race with a
concurrent call to
- <seealso marker="#write_file_info/2"><c>write_file_info/1,2</c>
- </seealso></p>
+ <seemfa marker="#write_file_info/2"><c>write_file_info/1,2</c>
+ </seemfa>.</p>
+ <p>This option has no effect when the function is
+ given an I/O device instead of a file name. Use
+ <seemfa marker="#open/2"><c>open/2</c></seemfa> with the
+ <c>raw</c> mode to obtain a file descriptor first.</p>
<note>
<p>As file times are stored in POSIX time on most OS, it is faster to
query file information with option <c>posix</c>.</p>
@@ -1459,19 +1482,19 @@ f.txt: {person, "kalle", 25}.
<p>The current system access to the file.</p>
</item>
<tag><c>atime = </c>
- <seealso marker="#type-date_time"><c>date_time()</c></seealso><c> |
+ <seetype marker="#date_time"><c>date_time()</c></seetype><c> |
integer() >= 0</c></tag>
<item>
<p>The last time the file was read.</p>
</item>
<tag><c>mtime = </c>
- <seealso marker="#type-date_time"><c>date_time()</c></seealso><c> |
+ <seetype marker="#date_time"><c>date_time()</c></seetype><c> |
integer() >= 0</c></tag>
<item>
<p>The last time the file was written.</p>
</item>
<tag><c>ctime = </c>
- <seealso marker="#type-date_time"><c>date_time()</c></seealso><c> |
+ <seetype marker="#date_time"><c>date_time()</c></seetype><c> |
integer() >=0</c></tag>
<item>
<p>The interpretation of this time field depends on
@@ -1572,7 +1595,7 @@ f.txt: {person, "kalle", 25}.
return is silently ignored). The line is returned <em>including</em> the LF,
but excluding any CR immediately followed by an LF. This behaviour is
consistent with the behaviour of
- <seealso marker="stdlib:io#get_line/2"><c>io:get_line/2</c></seealso>.
+ <seemfa marker="stdlib:io#get_line/2"><c>io:get_line/2</c></seemfa>.
If end of file is reached without any LF ending the last line, a line with no
trailing LF is returned.</p>
<p>The function can be used on files opened in <c>raw</c> mode. However, it is
@@ -1582,7 +1605,7 @@ f.txt: {person, "kalle", 25}.
raw line-oriented reading.</p>
<p>If <c>encoding</c> is set to something else than <c>latin1</c>, the
<c>read_line/1</c> call fails if the data contains characters larger than 255,
- why module <seealso marker="stdlib:io"><c>io(3)</c></seealso> is to be
+ why module <seeerl marker="stdlib:io"><c>io(3)</c></seeerl> is to be
preferred when reading such a file.</p>
<p>The function returns:</p>
<taglist>
@@ -1682,7 +1705,7 @@ f.txt: {person, "kalle", 25}.
<fsummary>Retrieve information about a link or file.</fsummary>
<desc>
<p>Works like
- <seealso marker="#read_file_info/2"><c>read_file_info/1,2</c></seealso>
+ <seemfa marker="#read_file_info/2"><c>read_file_info/1,2</c></seemfa>
except that if <c><anno>Name</anno></c> is a symbolic link, information
about the link is returned in the <c>file_info</c> record and
the <c>type</c> field of the record is set to <c>symlink</c>.</p>
@@ -1690,8 +1713,8 @@ f.txt: {person, "kalle", 25}.
only information about local files is returned. Note that this will
break this module's atomicity guarantees as it can race with a
concurrent call to
- <seealso marker="#write_file_info/2"><c>write_file_info/1,2</c>
- </seealso></p>
+ <seemfa marker="#write_file_info/2"><c>write_file_info/1,2</c>
+ </seemfa></p>
<p>If <c><anno>Name</anno></c> is not a symbolic link, this function returns
the same result as <c>read_file_info/1</c>.
On platforms that do not support symbolic links, this function
@@ -1779,21 +1802,21 @@ f.txt: {person, "kalle", 25}.
<item>
<p>An error occurred when opening the file or reading it.
For a list of typical error codes, see
- <seealso marker="#open/2"><c>open/2</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
</item>
<tag><c>{error, {<anno>Line</anno>, <anno>Mod</anno>,
<anno>Term</anno>}}</c></tag>
<item>
<p>An error occurred when interpreting the Erlang
expressions in the file. Use
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>
to convert the three-element tuple to an English description
of the error.</p>
</item>
</taglist>
<p>The encoding of <c><anno>Filename</anno></c> can be set
by a comment as described in
- <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso>.</p>
+ <seeerl marker="stdlib:epp#encoding"><c>epp(3)</c></seeerl>.</p>
</desc>
</func>
<func>
@@ -1802,7 +1825,7 @@ f.txt: {person, "kalle", 25}.
<desc>
<p>The same as <c>script/1</c> but the variable bindings
<c><anno>Bindings</anno></c> are used in the evaluation. See
- <seealso marker="stdlib:erl_eval"><c>erl_eval(3)</c></seealso> about
+ <seeerl marker="stdlib:erl_eval"><c>erl_eval(3)</c></seeerl> about
variable bindings.</p>
</desc>
</func>
@@ -1828,10 +1851,10 @@ f.txt: {person, "kalle", 25}.
<c>0</c> all data after the specified <c>Offset</c> is sent.</p>
<p>The file used must be opened using the <c>raw</c> flag, and the process
calling <c>sendfile</c> must be the controlling process of the socket.
- See <seealso marker="gen_tcp#controlling_process-2"><c>gen_tcp:controlling_process/2</c></seealso>.</p>
+ See <seemfa marker="gen_tcp#controlling_process/2"><c>gen_tcp:controlling_process/2</c></seemfa>.</p>
<p>If the OS used does not support non-blocking <c>sendfile</c>, an
- Erlang fallback using <seealso marker="#read/2"><c>read/2</c></seealso>
- and <seealso marker="gen_tcp#send/2"><c>gen_tcp:send/2</c></seealso> is
+ Erlang fallback using <seemfa marker="#read/2"><c>read/2</c></seemfa>
+ and <seemfa marker="gen_tcp#send/2"><c>gen_tcp:send/2</c></seemfa> is
used.</p>
<p>The option list can contain the following options:</p>
<taglist>
@@ -1851,7 +1874,7 @@ f.txt: {person, "kalle", 25}.
<p>The functions in the module <c>file</c> usually treat binaries
as raw filenames, that is, they are passed "as is" even when the
encoding of the binary does not agree with
- <seealso marker="#native_name_encoding/0"><c>native_name_encoding()</c></seealso>.
+ <seemfa marker="#native_name_encoding/0"><c>native_name_encoding()</c></seemfa>.
However, this function expects binaries to be encoded according to the
value returned by <c>native_name_encoding()</c>.</p>
<p>Typical error reasons are:</p>
@@ -1983,7 +2006,7 @@ f.txt: {person, "kalle", 25}.
<desc>
<p>Same as <c>write_file/2</c>, but takes a third argument
<c><anno>Modes</anno></c>, a list of possible modes, see
- <seealso marker="#open/2"><c>open/2</c></seealso>. The mode flags
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>. The mode flags
<c>binary</c> and <c>write</c> are implicit, so they are
not to be used.</p>
</desc>
@@ -2020,19 +2043,19 @@ f.txt: {person, "kalle", 25}.
specified:</p>
<taglist>
<tag><c>atime = </c>
- <seealso marker="#type-date_time"><c>date_time()</c></seealso><c> |
+ <seetype marker="#date_time"><c>date_time()</c></seetype><c> |
integer() >= 0</c></tag>
<item>
<p>The last time the file was read.</p>
</item>
<tag><c>mtime = </c>
- <seealso marker="#type-date_time"><c>date_time()</c></seealso><c> |
+ <seetype marker="#date_time"><c>date_time()</c></seetype><c> |
integer() >= 0</c></tag>
<item>
<p>The last time the file was written.</p>
</item>
<tag><c>ctime = </c>
- <seealso marker="#type-date_time"><c>date_time()</c></seealso><c> |
+ <seetype marker="#date_time"><c>date_time()</c></seetype><c> |
integer() >= 0</c></tag>
<item>
<p>On Unix, any value specified for this field is ignored
@@ -2145,7 +2168,7 @@ f.txt: {person, "kalle", 25}.
<title>Performance</title>
<p>For increased performance, raw files are recommended.</p>
<p>A normal file is really a process so it can be used as an I/O
- device (see <seealso marker="stdlib:io"><c>io</c></seealso>).
+ device (see <seeerl marker="stdlib:io"><c>io</c></seeerl>).
Therefore, when data is written to a normal file, the sending of the
data to the file process, copies all data that are not binaries. Opening
the file in binary mode and writing binaries is therefore recommended.
@@ -2159,7 +2182,7 @@ f.txt: {person, "kalle", 25}.
they can have different file systems.
However, this is seldom a problem.</p>
</note>
- <p><seealso marker="#open/2"><c>open/2</c></seealso> can be given the
+ <p><seemfa marker="#open/2"><c>open/2</c></seemfa> can be given the
options <c>delayed_write</c> and <c>read_ahead</c> to turn on caching,
which will reduce the number of operating system calls and greatly
improve performance for small reads and writes. However, the overhead
@@ -2180,7 +2203,7 @@ create_file_slow_1(Fd, M) ->
create_file_slow_1(Fd, M - 1).]]></code>
<p>The following functionally equivalent code writes 128 bytes per call
- to <seealso marker="#write/2"><c>write/2</c></seealso> and so does the
+ to <seemfa marker="#write/2"><c>write/2</c></seemfa> and so does the
same work in 0.08 seconds, which is roughly 30 times faster:</p>
<code type="none"><![CDATA[
@@ -2209,7 +2232,7 @@ create_file_1(Fd, M) ->
kernel.</p>
<warning>
<p>If an error occurs when accessing an open file with module
- <seealso marker="stdlib:io"><c>io</c></seealso>, the process
+ <seeerl marker="stdlib:io"><c>io</c></seeerl>, the process
handling the file exits. The dead file process can hang if a process
tries to access it later. This will be fixed in a future release.
</p>
@@ -2218,7 +2241,7 @@ create_file_1(Fd, M) ->
<section>
<title>See Also</title>
- <p><seealso marker="stdlib:filename"><c>filename(3)</c></seealso></p>
+ <p><seeerl marker="stdlib:filename"><c>filename(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml
index 954f480b74..9d5a4152a8 100644
--- a/lib/kernel/doc/src/gen_sctp.xml
+++ b/lib/kernel/doc/src/gen_sctp.xml
@@ -58,7 +58,7 @@
</list>
<p>This module was written for one-to-many style sockets
(type <c>seqpacket</c>). With the addition of
- <seealso marker="#peeloff/2"><c>peeloff/2</c></seealso>,
+ <seemfa marker="#peeloff/2"><c>peeloff/2</c></seemfa>,
one-to-one style sockets (type <c>stream</c>) were introduced.</p>
<p>Record definitions for this module can be found using:</p>
<pre>
@@ -82,7 +82,7 @@
<name name="option"/>
<desc>
<p>One of the
- <seealso marker="#options">SCTP Socket Options</seealso>.</p>
+ <seeerl marker="#options">SCTP Socket Options</seeerl>.</p>
</desc>
</datatype>
<datatype>
@@ -92,7 +92,7 @@
<name>sctp_socket()</name>
<desc>
<p>Socket identifier returned from
- <seealso marker="#open/0"><c>open/*</c></seealso>.</p>
+ <seemfa marker="#open/0"><c>open/*</c></seemfa>.</p>
<marker id="exports"></marker>
</desc>
</datatype>
@@ -117,11 +117,11 @@
<fsummary>Close the socket and all associations on it.</fsummary>
<desc>
<p>Closes the socket and all associations on it. The unsent
- data is flushed as in <seealso marker="#eof/2"><c>eof/2</c></seealso>.
+ data is flushed as in <seemfa marker="#eof/2"><c>eof/2</c></seemfa>.
The <c>close/1</c> call
is blocking or otherwise depending of the value of
- the <seealso marker="inet#option-linger"><c>linger</c></seealso>
- socket <seealso marker="#options">option</seealso>.
+ the <seeerl marker="inet#option-linger"><c>linger</c></seeerl>
+ socket <seeerl marker="#options">option</seeerl>.
If <c>close</c> does not linger or linger time-out expires,
the call returns and the data is flushed in the background.</p>
</desc>
@@ -155,17 +155,17 @@
relevant for associations sharing the same <c><anno>Socket</anno></c>
(that is, source address and port), as the controlling process
blocks until <c>connect/*</c> returns.
- <seealso marker="#connect_init/4"><c>connect_init/*</c></seealso>
+ <seemfa marker="#connect_init/4"><c>connect_init/*</c></seemfa>
provides an alternative without this limitation.</p>
</warning>
<p><marker id="record-sctp_assoc_change"></marker>
The result of <c>connect/*</c> is an <c>#sctp_assoc_change{}</c>
event that contains, in particular, the new
- <seealso marker="#type-assoc_id">Association ID</seealso>:</p>
+ <seetype marker="#assoc_id">Association ID</seetype>:</p>
<pre>
#sctp_assoc_change{
state = atom(),
- error = atom(),
+ error = integer(),
outbound_streams = integer(),
inbound_streams = integer(),
assoc_id = assoc_id()
@@ -197,7 +197,7 @@ connect(Socket, Ip, Port>,
<p>Other states do not normally occur in the output from
<c>connect/*</c>. Rather, they can occur in
<c>#sctp_assoc_change{}</c> events received instead of data in
- <seealso marker="#recv/1"><c>recv/*</c></seealso> calls.
+ <seemfa marker="#recv/1"><c>recv/*</c></seemfa> calls.
All of them indicate losing the association because of various error
conditions, and are listed here for the sake of completeness:</p>
<taglist>
@@ -208,7 +208,9 @@ connect(Socket, Ip, Port>,
<tag><c>shutdown_comp</c></tag>
<item></item>
</taglist>
- <p>Field <c>error</c> can provide more detailed diagnostics.</p>
+ <p>Field <c>error</c> can provide more detailed diagnostics.
+ The <c>error</c> field value can be converted into a string using
+ <seemfa marker="#error_string/1"><c>error_string/1</c></seemfa>.</p>
</desc>
</func>
@@ -234,13 +236,13 @@ connect(Socket, Ip, Port>,
underlying OS <c>connect(2)</c> system call. If <c>ok</c> is returned,
the result of the association establishment is received
by the calling process as an
- <seealso marker="#record-sctp_assoc_change"><c>#sctp_assoc_change{}</c></seealso>
+ <seeerl marker="#record-sctp_assoc_change"><c>#sctp_assoc_change{}</c></seeerl>
event. The calling process must be prepared to receive this, or
poll for it using
- <seealso marker="#recv/1"><c>recv/*</c></seealso>,
+ <seemfa marker="#recv/1"><c>recv/*</c></seemfa>,
depending on the value of the active option.</p>
<p>The parameters are as described in
- <seealso marker="#connect/5"><c>connect/*</c></seealso>,
+ <seemfa marker="#connect/5"><c>connect/*</c></seemfa>,
except the <c><anno>Timeout</anno></c> value.</p>
<p>The timer associated with <c><anno>Timeout</anno></c> only supervises
IP resolution of <c><anno>Addr</anno></c>.</p>
@@ -253,7 +255,7 @@ connect(Socket, Ip, Port>,
<desc>
<p>Assigns a new controlling process <c><anno>Pid</anno></c> to
<c><anno>Socket</anno></c>. Same implementation as
- <seealso marker="gen_udp#controlling_process/2"><c>gen_udp:controlling_process/2</c></seealso>.
+ <seemfa marker="gen_udp#controlling_process/2"><c>gen_udp:controlling_process/2</c></seemfa>.
</p>
</desc>
</func>
@@ -324,23 +326,23 @@ connect(Socket, Ip, Port>,
<p>Sets up the socket for IPv4. This is the default.</p>
</item>
</taglist>
- <p>A default set of socket <seealso marker="#options">options</seealso>
+ <p>A default set of socket <seeerl marker="#options">options</seeerl>
is used. In particular, the socket is opened in
- <seealso marker="#option-binary">binary</seealso> and
- <seealso marker="#option-active">passive</seealso> mode,
+ <seeerl marker="#option-binary">binary</seeerl> and
+ <seeerl marker="#option-active">passive</seeerl> mode,
with <anno>SockType</anno> <c>seqpacket</c>, and with reasonably large
- <seealso marker="inet#option-sndbuf">kernel</seealso> and driver
- <seealso marker="inet#option-buffer">buffers</seealso>.</p>
+ <seeerl marker="inet#option-sndbuf">kernel</seeerl> and driver
+ <seeerl marker="inet#option-buffer">buffers</seeerl>.</p>
<p>
If the socket is in
- <seealso marker="#option-active">passive</seealso>
+ <seeerl marker="#option-active">passive</seeerl>
mode data can be received through the
- <seealso marker="#recv/1"><c>recv/1,2</c></seealso>
+ <seemfa marker="#recv/1"><c>recv/1,2</c></seemfa>
calls.
</p>
<p>
If the socket is in
- <seealso marker="#option-active">active</seealso>
+ <seeerl marker="#option-active">active</seeerl>
mode data received data is delivered to the controlling process
as messages:
</p>
@@ -349,16 +351,16 @@ connect(Socket, Ip, Port>,
</code>
<p>
See
- <seealso marker="#recv/1"><c>recv/1,2</c></seealso>
+ <seemfa marker="#recv/1"><c>recv/1,2</c></seemfa>
for a description of the message fields.
</p>
<note>
<p>
This message format unfortunately differs slightly from the
- <seealso marker="gen_udp#open/1"><c>gen_udp</c></seealso>
+ <seemfa marker="gen_udp#open/1"><c>gen_udp</c></seemfa>
message format with ancillary data,
and from the
- <seealso marker="#recv/1"><c>recv/1,2</c></seealso>
+ <seemfa marker="#recv/1"><c>recv/1,2</c></seemfa>
return tuple format.
</p>
</note>
@@ -377,10 +379,10 @@ connect(Socket, Ip, Port>,
(one-to-one style).</p>
<p>The existing association argument <c><anno>Assoc</anno></c>
can be either a
- <seealso marker="#record-sctp_assoc_change"><c>#sctp_assoc_change{}</c></seealso>
+ <seeerl marker="#record-sctp_assoc_change"><c>#sctp_assoc_change{}</c></seeerl>
record as returned from, for example,
- <seealso marker="#recv-2"><c>recv/*</c></seealso>,
- <seealso marker="#connect-5"><c>connect/*</c></seealso>, or
+ <seemfa marker="#recv/2"><c>recv/*</c></seemfa>,
+ <seemfa marker="#connect/5"><c>connect/*</c></seemfa>, or
from a listening socket in active mode. It can also be just
the field <c>assoc_id</c> integer from such a record.</p>
</desc>
@@ -400,28 +402,28 @@ connect(Socket, Ip, Port>,
<p><c><anno>AncData</anno></c> is a list of ancillary data items that
can be received along with the main <c><anno>Data</anno></c>.
This list can be empty, or contain a single
- <seealso marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seealso>
+ <seeerl marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seeerl>
record if receiving of such ancillary data is enabled (see option
- <seealso marker="#option-sctp_events"><c>sctp_events</c></seealso>).
+ <seeerl marker="#option-sctp_events"><c>sctp_events</c></seeerl>).
It is enabled by default, as such ancillary data
provides an easy way of determining the association and stream
over which the message is received.
(An alternative way is to get the association ID from
<c><anno>FromIP</anno></c> and <c><anno>FromPort</anno></c> using
socket option
- <seealso marker="#option-sctp_get_peer_addr_info"><c>sctp_get_peer_addr_info</c></seealso>,
+ <seeerl marker="#option-sctp_get_peer_addr_info"><c>sctp_get_peer_addr_info</c></seeerl>,
but this does still not produce the stream number).</p>
<p>
<c><anno>AncData</anno></c> may also contain
- <seealso marker="inet#type-ancillary_data">
+ <seetype marker="inet#ancillary_data">
ancillary data
- </seealso>
+ </seetype>
from the socket
- <seealso marker="#type-option">options</seealso>
- <seealso marker="inet#option-recvtos"><c>recvtos</c></seealso>,
- <seealso marker="inet#option-recvtclass"><c>recvtclass</c></seealso>
+ <seetype marker="#option">options</seetype>
+ <seeerl marker="inet#option-recvtos"><c>recvtos</c></seeerl>,
+ <seeerl marker="inet#option-recvtclass"><c>recvtclass</c></seeerl>
or
- <seealso marker="inet#option-recvttl"><c>recvttl</c></seealso>,
+ <seeerl marker="inet#option-recvttl"><c>recvttl</c></seeerl>,
if that is supported by the platform for the socket.
</p>
<p>The <c><anno>Data</anno></c> received can be a <c>binary()</c>
@@ -431,10 +433,10 @@ connect(Socket, Ip, Port>,
<p>Possible SCTP events:</p>
<list type="bulleted">
<item>
- <seealso marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seealso>
+ <seeerl marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seeerl>
</item>
<item>
- <seealso marker="#record-sctp_assoc_change"><c>#sctp_assoc_change{}</c></seealso>
+ <seeerl marker="#record-sctp_assoc_change"><c>#sctp_assoc_change{}</c></seeerl>
</item>
<item>
<pre>
@@ -466,9 +468,9 @@ connect(Socket, Ip, Port>,
field <c>error</c> provides more diagnostics. In such cases,
event <c>#sctp_paddr_change{}</c> is automatically
converted into an <c>error</c> term returned by
- <seealso marker="#recv/1"><c>recv</c></seealso>.
+ <seemfa marker="#recv/1"><c>recv</c></seemfa>.
The <c>error</c> field value can be converted into a string using
- <seealso marker="#error_string/1"><c>error_string/1</c></seealso>.
+ <seemfa marker="#error_string/1"><c>error_string/1</c></seemfa>.
</p>
</item>
<item>
@@ -487,13 +489,13 @@ connect(Socket, Ip, Port>,
over the wire.</p></item>
<tag><c>error</c></tag>
<item><p>Provides extended diagnostics, use
- <seealso marker="#error_string/1"><c>error_string/1</c>.</seealso></p>
+ <seemfa marker="#error_string/1"><c>error_string/1</c>.</seemfa></p>
</item>
<tag><c>info</c></tag>
<item><p>The original
- <seealso marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seealso>
+ <seeerl marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seeerl>
record used in the failed
- <seealso marker="#send/3"><c>send/*</c>.</seealso></p>
+ <seemfa marker="#send/3"><c>send/*</c>.</seemfa></p>
</item>
<tag><c>data</c></tag>
<item><p>The whole original data chunk attempted to be sent.</p>
@@ -502,7 +504,7 @@ connect(Socket, Ip, Port>,
<p>In the current implementation of the Erlang/SCTP binding,
this event is internally converted into an <c>error</c> term
returned by
- <seealso marker="#recv/1"><c>recv/*</c></seealso>.</p>
+ <seemfa marker="#recv/1"><c>recv/*</c></seemfa>.</p>
</item>
<item>
<pre>
@@ -512,7 +514,7 @@ connect(Socket, Ip, Port>,
}</pre>
<p>Delivered when a peer sends an adaptation layer indication
parameter (configured through option
- <seealso marker="#option-sctp_adaptation_layer"><c>sctp_adaptation_layer</c></seealso>).
+ <seeerl marker="#option-sctp_adaptation_layer"><c>sctp_adaptation_layer</c></seeerl>).
Notice that with the current implementation of
the Erlang/SCTP binding, this event is disabled by default.</p>
</item>
@@ -525,7 +527,7 @@ connect(Socket, Ip, Port>,
<p>A partial delivery failure. In the current implementation of
the Erlang/SCTP binding, this event is internally converted
into an <c>error</c> term returned by
- <seealso marker="#recv/1"><c>recv/*</c></seealso>.</p>
+ <seemfa marker="#recv/1"><c>recv/*</c></seemfa>.</p>
</item>
</list>
</desc>
@@ -537,7 +539,7 @@ connect(Socket, Ip, Port>,
<desc>
<p>Sends the <c><anno>Data</anno></c> message with all sending
parameters from a
- <seealso marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seealso>
+ <seeerl marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seeerl>
record. This way, the user can specify the PPID (passed to the remote
end) and context (passed to the local SCTP layer), which can be used,
for example, for error identification.
@@ -564,19 +566,19 @@ connect(Socket, Ip, Port>,
orthogonal to the sets of TCP, UDP, and generic <c>inet</c> options.
Only options listed here are allowed
for SCTP sockets. Options can be set on the socket using
- <seealso marker="#open/1"><c>open/1,2</c></seealso> or
- <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso>,
+ <seemfa marker="#open/1"><c>open/1,2</c></seemfa> or
+ <seemfa marker="inet#setopts/2"><c>inet:setopts/2</c></seemfa>,
retrieved using
- <seealso marker="inet#getopts/2"><c>inet:getopts/2</c></seealso>.
+ <seemfa marker="inet#getopts/2"><c>inet:getopts/2</c></seemfa>.
Options can be changed when calling
- <seealso marker="#connect/4"><c>connect/4,5</c></seealso>.</p>
+ <seemfa marker="#connect/4"><c>connect/4,5</c></seemfa>.</p>
<marker id="option-binary"></marker>
<marker id="option-list"></marker>
<taglist>
<tag><c>{mode, list|binary}</c> or just <c>list</c> or <c>binary</c></tag>
<item>
<p>Determines the type of data returned from
- <seealso marker="#recv/1"><c>recv/1,2</c></seealso>.</p>
+ <seemfa marker="#recv/1"><c>recv/1,2</c></seemfa>.</p>
<marker id="option-active"></marker>
</item>
<tag><c>{active, true|false|once|N}</c></tag>
@@ -585,14 +587,14 @@ connect(Socket, Ip, Port>,
<item>
<p>If <c>false</c> (passive mode, the default),
the caller must do an explicit
- <seealso marker="#recv/1"><c>recv</c></seealso> call
+ <seemfa marker="#recv/1"><c>recv</c></seemfa> call
to retrieve the available data from the socket.</p>
</item>
<item>
<p>
If <c>true|once|N</c> (active modes)
received data or events are sent to the owning process.
- See <seealso marker="#open/0"><c>open/0..2</c></seealso>
+ See <seemfa marker="#open/0"><c>open/0..2</c></seemfa>
for the message format.
</p>
</item>
@@ -623,13 +625,13 @@ connect(Socket, Ip, Port>,
addition is negative, the count is set to <c>0</c>. Once the
count reaches <c>0</c>, either through the delivery of messages
or by being explicitly set with
- <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso>,
+ <seemfa marker="inet#setopts/2"><c>inet:setopts/2</c></seemfa>,
the socket mode is automatically reset to passive (<c>{active,
false}</c>). When a socket in this active mode transitions to
passive mode, the message <c>{sctp_passive, Socket}</c> is sent
to the controlling process to notify it that if it wants to
receive more data messages from the socket, it must call
- <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso>
+ <seemfa marker="inet#setopts/2"><c>inet:setopts/2</c></seemfa>
to set the socket back into an active mode.</p>
</item>
</list>
@@ -725,12 +727,12 @@ connect(Socket, Ip, Port>,
<p>Determines the default parameters that this socket tries
to negotiate with its peer while establishing an association with it.
Is to be set after
- <seealso marker="#open/1"><c>open/*</c></seealso>
+ <seemfa marker="#open/1"><c>open/*</c></seemfa>
but before the first
- <seealso marker="#connect/4"><c>connect/*</c></seealso>.
+ <seemfa marker="#connect/4"><c>connect/*</c></seemfa>.
<c>#sctp_initmsg{}</c> can also be used
as ancillary data with the first call of
- <seealso marker="#send/3"><c>send/*</c></seealso> to
+ <seemfa marker="#send/3"><c>send/*</c></seemfa> to
a new peer (when a new association is created).</p>
<taglist>
<tag><c>num_ostreams</c></tag>
@@ -886,7 +888,7 @@ Port = port_number()</pre>
<p><c>#sctp_sndrcvinfo{}</c> is used both in this socket option, and as
ancillary data while sending or receiving SCTP messages. When
set as an option, it provides default values for subsequent
- <seealso marker="#send/3"><c>send</c></seealso>
+ <seemfa marker="#send/3"><c>send</c></seemfa>
calls on the association specified by
<c>assoc_id</c>.</p>
<p><c>assoc_id = 0</c> (default) indicates
@@ -903,7 +905,7 @@ Port = port_number()</pre>
<item>The message is to be sent unordered</item>
<tag><c>addr_over</c></tag>
<item>The address specified in
- <seealso marker="#send/3"><c>send</c></seealso>
+ <seemfa marker="#send/3"><c>send</c></seemfa>
overwrites the primary peer address</item>
<tag><c>abort</c></tag>
<item>Aborts the current association without flushing any unsent
@@ -935,12 +937,12 @@ Port = port_number()</pre>
adaptation_layer_event = true | false
}</pre>
<p>This option determines which
- <seealso marker="#sctp_events">SCTP Events</seealso> are to be
+ <seeerl marker="#sctp_events">SCTP Events</seeerl> are to be
received (through
- <seealso marker="#recv/1"><c>recv/*</c></seealso>)
+ <seemfa marker="#recv/1"><c>recv/*</c></seemfa>)
along with the data. The only exception is <c>data_io_event</c>,
which enables or disables receiving of
- <seealso marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seealso>
+ <seeerl marker="#record-sctp_sndrcvinfo"><c>#sctp_sndrcvinfo{}</c></seeerl>
ancillary data, not events.
By default, all flags except <c>adaptation_layer_event</c> are
enabled, although <c>sctp_data_io_event</c> and
@@ -1184,9 +1186,9 @@ client_loop(S, Peer1, Port1, AssocId1, Peer2, Port2, AssocId2) -&gt;
<section>
<marker id="seealso"></marker>
<title>See Also</title>
- <p><seealso marker="gen_tcp"><c>gen_tcp(3)</c></seealso>,
- <seealso marker="gen_udp"><c>gen_udp(3)</c></seealso>,
- <seealso marker="inet"><c>inet(3)</c></seealso>,
+ <p><seeerl marker="gen_tcp"><c>gen_tcp(3)</c></seeerl>,
+ <seeerl marker="gen_udp"><c>gen_udp(3)</c></seeerl>,
+ <seeerl marker="inet"><c>inet(3)</c></seeerl>,
<url href="http://www.rfc-archive.org/getrfc.php?rfc=2960">RFC 2960</url>
(Stream Control Transmission Protocol),
<url href="http://tools.ietf.org/html/draft-ietf-tsvwg-sctpsocket-13">Sockets
diff --git a/lib/kernel/doc/src/gen_tcp.xml b/lib/kernel/doc/src/gen_tcp.xml
index 1093c33bdb..b90cc9d104 100644
--- a/lib/kernel/doc/src/gen_tcp.xml
+++ b/lib/kernel/doc/src/gen_tcp.xml
@@ -62,7 +62,7 @@ do_recv(Sock, Bs) ->
{ok, list_to_binary(Bs)}
end.</code>
<p>For more examples, see section
- <seealso marker="#examples">Examples</seealso>.</p>
+ <seeerl marker="#examples">Examples</seeerl>.</p>
</description>
<datatypes>
@@ -77,9 +77,9 @@ do_recv(Sock, Bs) ->
<c>IP_PKTOPTIONS</c>, or the IPv6 option
<c>IPV6_PKTOPTIONS</c> or <c>IPV6_2292PKTOPTIONS</c> for the socket
this value is returned from
- <seealso marker="inet#getopts/2"><c>inet:getopts/2</c></seealso>
+ <seemfa marker="inet#getopts/2"><c>inet:getopts/2</c></seemfa>
when called with the option name
- <seealso marker="#type-option_name"><c>pktoptions</c></seealso>.
+ <seetype marker="#option_name"><c>pktoptions</c></seetype>.
</p>
<note>
<p>
@@ -107,8 +107,8 @@ do_recv(Sock, Bs) ->
<datatype>
<name>socket()</name>
<desc><p>As returned by
- <seealso marker="#accept/1"><c>accept/1,2</c></seealso> and
- <seealso marker="#connect/3"><c>connect/3,4</c></seealso>.</p>
+ <seemfa marker="#accept/1"><c>accept/1,2</c></seemfa> and
+ <seemfa marker="#connect/3"><c>connect/3,4</c></seemfa>.</p>
<marker id="connect"></marker>
</desc>
</datatype>
@@ -120,12 +120,12 @@ do_recv(Sock, Bs) ->
<name name="accept" arity="2" since=""/>
<fsummary>Accept an incoming connection request on a listening socket.</fsummary>
<type_desc variable="ListenSocket">Returned by
- <seealso marker="#listen/2"><c>listen/2</c></seealso>.
+ <seemfa marker="#listen/2"><c>listen/2</c></seemfa>.
</type_desc>
<desc>
<p>Accepts an incoming connection request on a listening socket.
<c><anno>Socket</anno></c> must be a socket returned from
- <seealso marker="#listen/2"><c>listen/2</c></seealso>.
+ <seemfa marker="#listen/2"><c>listen/2</c></seemfa>.
<c><anno>Timeout</anno></c> specifies a time-out value in
milliseconds. Defaults to <c>infinity</c>.</p>
<p>Returns:</p>
@@ -139,16 +139,16 @@ do_recv(Sock, Bs) ->
<item><p><c>{error, system_limit}</c> if all available ports in the
Erlang emulator are in use</p></item>
<item><p>A POSIX error value if something else goes wrong, see
- <seealso marker="inet"><c>inet(3)</c></seealso> for possible
+ <seeerl marker="inet"><c>inet(3)</c></seeerl> for possible
error values</p></item>
</list>
<p>Packets can be sent to the returned socket <c><anno>Socket</anno></c>
using
- <seealso marker="#send/2"><c>send/2</c></seealso>.
+ <seemfa marker="#send/2"><c>send/2</c></seemfa>.
Packets sent from the peer are delivered as messages (unless
<c>{active, false}</c> is specified in the option list for the
listening socket, in which case packets are retrieved by calling
- <seealso marker="#recv/2"><c>recv/2</c></seealso>):</p>
+ <seemfa marker="#recv/2"><c>recv/2</c></seemfa>):</p>
<code type="none">
{tcp, Socket, Data}</code>
<note>
@@ -173,13 +173,13 @@ do_recv(Sock, Bs) ->
delivery of the data to the recipient there are two common ways to
achieve this.</p>
<list type="ordered">
- <item><p>Use <seealso marker="#shutdown/2">
- <c>gen_tcp:shutdown(Sock, write)</c></seealso> to signal that
+ <item><p>Use <seemfa marker="#shutdown/2">
+ <c>gen_tcp:shutdown(Sock, write)</c></seemfa> to signal that
no more data is to be sent and wait for the read side of the
socket to be closed.</p>
</item>
- <item><p>Use the socket option <seealso marker="inet#packet">
- <c>{packet, N}</c></seealso> (or something similar) to make
+ <item><p>Use the socket option <seeerl marker="inet#packet">
+ <c>{packet, N}</c></seeerl> (or something similar) to make
it possible for the receiver to close the connection when it
knowns it has received all the data.</p>
</item>
@@ -220,9 +220,9 @@ do_recv(Sock, Bs) ->
<item>
<p>
Sets up a Unix Domain Socket. See
- <seealso marker="inet#type-local_address">
+ <seetype marker="inet#local_address">
<c>inet:local_address()</c>
- </seealso>
+ </seetype>
</p>
</item>
<tag><c>{port, Port}</c></tag>
@@ -232,16 +232,16 @@ do_recv(Sock, Bs) ->
<c>inet_tcp</c> for IPv4 and <c>inet6_tcp</c> for IPv6.</p></item>
<tag><c>Opt</c></tag>
<item><p>See
- <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso>.</p>
+ <seemfa marker="inet#setopts/2"><c>inet:setopts/2</c></seemfa>.</p>
</item>
</taglist>
<p>Packets can be sent to the returned socket <c><anno>Socket</anno></c>
- using <seealso marker="#send/2"><c>send/2</c></seealso>.
+ using <seemfa marker="#send/2"><c>send/2</c></seemfa>.
Packets sent from the peer are delivered as messages:</p>
<code type="none">
{tcp, Socket, Data}</code>
<p>If the socket is in <c>{active, N}</c> mode (see
- <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso>
+ <seemfa marker="inet#setopts/2"><c>inet:setopts/2</c></seemfa>
for details) and its message counter drops to <c>0</c>, the following
message is delivered to indicate that the
socket has transitioned to passive (<c>{active, false}</c>) mode:</p>
@@ -253,7 +253,7 @@ do_recv(Sock, Bs) ->
<p>If an error occurs on the socket, the following message is delivered
(unless <c>{active, false}</c> is specified in the option list for
the socket, in which case packets are retrieved by calling
- <seealso marker="#recv/2"><c>recv/2</c></seealso>):</p>
+ <seemfa marker="#recv/2"><c>recv/2</c></seemfa>):</p>
<code type="none">
{tcp_error, Socket, Reason}</code>
<p>The optional <c><anno>Timeout</anno></c> parameter specifies a
@@ -268,7 +268,7 @@ do_recv(Sock, Bs) ->
<p>The default values for options specified to <c>connect</c> can
be affected by the Kernel configuration parameter
<c>inet_default_connect_options</c>. For details, see
- <seealso marker="inet"><c>inet(3)</c></seealso>.</p>
+ <seeerl marker="inet"><c>inet(3)</c></seeerl>.</p>
</note>
</desc>
</func>
@@ -292,7 +292,7 @@ do_recv(Sock, Bs) ->
If any other process is interacting with the socket while
the transfer is happening, the transfer may not work correctly
and messages may remain in the caller's mailbox. For instance
- changing the sockets active mode before the transfere is complete
+ changing the sockets active mode before the transfer is complete
may cause this.</p>
</desc>
</func>
@@ -305,7 +305,7 @@ do_recv(Sock, Bs) ->
the local host.</p>
<p>If <c><anno>Port</anno> == 0</c>, the underlying OS assigns an
available port number, use
- <seealso marker="inet#port/1"><c>inet:port/1</c></seealso>
+ <seemfa marker="inet#port/1"><c>inet:port/1</c></seemfa>
to retrieve it.</p>
<p>The following options are available:</p>
<taglist>
@@ -339,17 +339,17 @@ do_recv(Sock, Bs) ->
<c>inet_tcp</c> for IPv4 and <c>inet6_tcp</c> for IPv6.</p></item>
<tag><c>Opt</c></tag>
<item><p>See
- <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso>.
+ <seemfa marker="inet#setopts/2"><c>inet:setopts/2</c></seemfa>.
</p></item>
</taglist>
<p>The returned socket <c><anno>ListenSocket</anno></c> should be used
- in calls to <seealso marker="#accept/1"><c>accept/1,2</c></seealso> to
+ in calls to <seemfa marker="#accept/1"><c>accept/1,2</c></seemfa> to
accept incoming connection requests.</p>
<note>
<p>The default values for options specified to <c>listen</c> can
be affected by the Kernel configuration parameter
<c>inet_default_listen_options</c>. For details, see
- <seealso marker="inet"><c>inet(3)</c></seealso>.</p>
+ <seeerl marker="inet"><c>inet(3)</c></seeerl>.</p>
</note>
</desc>
</func>
@@ -360,7 +360,7 @@ do_recv(Sock, Bs) ->
<fsummary>Receive a packet from a passive socket.</fsummary>
<type_desc variable="HttpPacket">See the description of
<c>HttpPacket</c> in
- <seealso marker="erts:erlang#decode_packet/3"><c>erlang:decode_packet/3</c></seealso>
+ <seemfa marker="erts:erlang#decode_packet/3"><c>erlang:decode_packet/3</c></seemfa>
in ERTS.
</type_desc>
<desc>
@@ -387,7 +387,7 @@ do_recv(Sock, Bs) ->
<p>Sends a packet on a socket.</p>
<p>There is no <c>send</c> call with a time-out option, use socket
option <c>send_timeout</c> if time-outs are desired. See section
- <seealso marker="#examples">Examples</seealso>.</p>
+ <seeerl marker="#examples">Examples</seeerl>.</p>
</desc>
</func>
@@ -406,8 +406,8 @@ do_recv(Sock, Bs) ->
to shutdown the socket is postponed until that data is written to the
kernel socket send buffer. If any errors are encountered, the socket
is closed and <c>{error, closed}</c> is returned on the next
- <seealso marker="#recv/2"><c>recv/2</c></seealso> or
- <seealso marker="#send/2"><c>send/2</c></seealso>.</p>
+ <seemfa marker="#recv/2"><c>recv/2</c></seemfa> or
+ <seemfa marker="#send/2"><c>send/2</c></seemfa>.</p>
<p>Option <c>{exit_on_close, false}</c> is useful if the peer has done
a shutdown on the write side.</p>
</desc>
diff --git a/lib/kernel/doc/src/gen_udp.xml b/lib/kernel/doc/src/gen_udp.xml
index 798d53fcf1..ff65d30cb9 100644
--- a/lib/kernel/doc/src/gen_udp.xml
+++ b/lib/kernel/doc/src/gen_udp.xml
@@ -46,7 +46,7 @@
<name>socket()</name>
<desc>
<p>As returned by
- <seealso marker="#open/1"><c>open/1,2</c></seealso>.</p>
+ <seemfa marker="#open/1"><c>open/1,2</c></seemfa>.</p>
</desc>
</datatype>
</datatypes>
@@ -112,9 +112,9 @@
<item>
<p>
Sets up a Unix Domain Socket. See
- <seealso marker="inet#type-local_address">
+ <seetype marker="inet#local_address">
<c>inet:local_address()</c>
- </seealso>
+ </seetype>
</p>
</item>
<tag><c>{udp_module, module()}</c></tag>
@@ -137,12 +137,12 @@
<item><p>Leaves a multicast group.</p></item>
<tag><c>Opt</c></tag>
<item><p>See
- <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso>.
+ <seemfa marker="inet#setopts/2"><c>inet:setopts/2</c></seemfa>.
</p></item>
</taglist>
<p>The returned socket <c><anno>Socket</anno></c> is used to send
packets from this port with
- <seealso marker="#send/4"><c>send/4</c></seealso>.
+ <seemfa marker="#send/4"><c>send/4</c></seemfa>.
When UDP packets arrive
at the opened port, if the socket is in an active mode, the packets
are delivered as messages to the controlling process:</p>
@@ -153,23 +153,23 @@
<p>
The message contains an <c>AncData</c> field
if any of the socket
- <seealso marker="#type-option">options</seealso>
- <seealso marker="inet#option-recvtos"><c>recvtos</c></seealso>,
- <seealso marker="inet#option-recvtclass"><c>recvtclass</c></seealso>
+ <seetype marker="#option">options</seetype>
+ <seeerl marker="inet#option-recvtos"><c>recvtos</c></seeerl>,
+ <seeerl marker="inet#option-recvtclass"><c>recvtclass</c></seeerl>
or
- <seealso marker="inet#option-recvttl"><c>recvttl</c></seealso>
+ <seeerl marker="inet#option-recvttl"><c>recvttl</c></seeerl>
are active, otherwise it does not.
</p>
<p>
</p>
<p>If the socket is not in an active mode, data can be
retrieved through the
- <seealso marker="#recv/2"><c>recv/2,3</c></seealso> calls.
+ <seemfa marker="#recv/2"><c>recv/2,3</c></seemfa> calls.
Notice that arriving UDP packets that are longer than
the receive buffer option specifies can be truncated
without warning.</p>
<p>When a socket in <c>{active, N}</c> mode (see
- <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso>
+ <seemfa marker="inet#setopts/2"><c>inet:setopts/2</c></seemfa>
for details), transitions to passive (<c>{active, false}</c>) mode,
the controlling process is notified by a message of the following
form:</p>
@@ -183,7 +183,7 @@
<c>{recbuf, 8192}</c>.</p>
<p>If <c><anno>Port</anno> == 0</c>, the underlying OS assigns a free
UDP port, use
- <seealso marker="inet#port/1"><c>inet:port/1</c></seealso>
+ <seemfa marker="inet#port/1"><c>inet:port/1</c></seemfa>
to retrieve it.</p>
</desc>
</func>
@@ -200,11 +200,11 @@
</p>
<p>
If any of the socket
- <seealso marker="#type-option">options</seealso>
- <seealso marker="inet#option-recvtos"><c>recvtos</c></seealso>,
- <seealso marker="inet#option-recvtclass"><c>recvtclass</c></seealso>
+ <seetype marker="#option">options</seetype>
+ <seeerl marker="inet#option-recvtos"><c>recvtos</c></seeerl>,
+ <seeerl marker="inet#option-recvtclass"><c>recvtclass</c></seeerl>
or
- <seealso marker="inet#option-recvttl"><c>recvttl</c></seealso>
+ <seeerl marker="inet#option-recvttl"><c>recvttl</c></seeerl>
are active, the <c><anno>RecvData</anno></c> tuple contains an
<c><anno>AncData</anno></c> field,
otherwise it does not.
@@ -221,7 +221,7 @@
</p>
<p>
This function is equivalent to
- <seealso marker="#send-4-AncData"><c>send(<anno>Socket</anno>, <anno>Destination</anno>, [], <anno>Packet</anno>)</c></seealso>.
+ <seeerl marker="#send-4-AncData"><c>send(<anno>Socket</anno>, <anno>Destination</anno>, [], <anno>Packet</anno>)</c></seeerl>.
</p>
</desc>
</func>
@@ -236,7 +236,7 @@
</p>
<p>
This clause is equivalent to
- <seealso marker="#send/5"><c>send(<anno>Socket</anno>, <anno>Host</anno>, <anno>Port</anno>, [], <anno>Packet</anno>)</c></seealso>.
+ <seemfa marker="#send/5"><c>send(<anno>Socket</anno>, <anno>Host</anno>, <anno>Port</anno>, [], <anno>Packet</anno>)</c></seemfa>.
</p>
</desc>
</func>
@@ -278,7 +278,7 @@
<c><anno>Destination</anno> = {local, Binary}</c>
where <c><anno>PortZero</anno></c> is superfluous.
It is equivalent to
- <seealso marker="#send-4-AncData"><c>send(<anno>Socket</anno>, <anno>Destination</anno>, [], <anno>Packet</anno>)</c></seealso>, the clause right above here.
+ <seeerl marker="#send-4-AncData"><c>send(<anno>Socket</anno>, <anno>Destination</anno>, [], <anno>Packet</anno>)</c></seeerl>, the clause right above here.
</p>
</desc>
</func>
@@ -299,10 +299,9 @@
or a service name atom.
These are resolved into a <c>Destination</c> and after that
this function is equivalent to
- <seealso marker="#send-4-AncData"><c>send(<anno>Socket</anno>, Destination, <anno>AncData</anno>, <anno>Packet</anno>)</c></seealso>, read there about ancillary data.
+ <seeerl marker="#send-4-AncData"><c>send(<anno>Socket</anno>, Destination, <anno>AncData</anno>, <anno>Packet</anno>)</c></seeerl>, read there about ancillary data.
</p>
</desc>
</func>
</funcs>
</erlref>
-
diff --git a/lib/kernel/doc/src/global.xml b/lib/kernel/doc/src/global.xml
index 28b59c20bd..9c4ac2db34 100644
--- a/lib/kernel/doc/src/global.xml
+++ b/lib/kernel/doc/src/global.xml
@@ -70,14 +70,14 @@
sent from module <c>net_kernel</c>. Relevant Kernel
application variables in this context are <c>net_setuptime</c>,
<c>net_ticktime</c>, and <c>dist_auto_connect</c>. See also
- <seealso marker="kernel_app#net_setuptime"><c>kernel(6)</c></seealso>.</p>
+ <seeapp marker="kernel_app#net_setuptime"><c>kernel(6)</c></seeapp>.</p>
<p>The name server also maintains a fully connected network. For
example, if node <c>N1</c> connects to node <c>N2</c> (which is
already connected to <c>N3</c>), the global name servers on the
nodes <c>N1</c> and <c>N3</c> ensure that also <c>N1</c>
and <c>N3</c> are connected. If this is not desired,
command-line flag <c>-connect_all false</c> can be used (see also
- <seealso marker="erts:erl#connect_all"><c>erl(1)</c></seealso>).
+ <seecom marker="erts:erl#connect_all"><c>erl(1)</c></seecom>).
In this case, the name registration service cannot be used, but the
lock mechanism still works.</p>
<p>If the global name server fails to connect nodes (<c>N1</c> and
@@ -113,9 +113,9 @@
<fsummary>Name resolving function that notifies both pids.</fsummary>
<desc>
<p>Can be used as a name resolving function for
- <seealso marker="#register_name/3"><c>register_name/3</c></seealso>
+ <seemfa marker="#register_name/3"><c>register_name/3</c></seemfa>
and
- <seealso marker="#re_register_name/3"><c>re_register_name/3</c></seealso>.</p>
+ <seemfa marker="#re_register_name/3"><c>re_register_name/3</c></seemfa>.</p>
<p>The function unregisters both pids and sends the message
<c>{global_name_conflict, <anno>Name</anno>, OtherPid}</c> to both
processes.</p>
@@ -127,9 +127,9 @@
<fsummary>Name resolving function that kills one pid.</fsummary>
<desc>
<p>Can be used as a name resolving function for
- <seealso marker="#register_name/3"><c>register_name/3</c></seealso>
+ <seemfa marker="#register_name/3"><c>register_name/3</c></seemfa>
and
- <seealso marker="#re_register_name/3"><c>re_register_name/3</c></seealso>.</p>
+ <seemfa marker="#re_register_name/3"><c>re_register_name/3</c></seemfa>.</p>
<p>The function randomly selects one of the pids for registration and
kills the other one.</p>
</desc>
@@ -140,9 +140,9 @@
<fsummary>Name resolving function that notifies one pid.</fsummary>
<desc>
<p>Can be used as a name resolving function for
- <seealso marker="#register_name/3"><c>register_name/3</c></seealso>
+ <seemfa marker="#register_name/3"><c>register_name/3</c></seemfa>
and
- <seealso marker="#re_register_name/3"><c>re_register_name/3</c></seealso>.</p>
+ <seemfa marker="#re_register_name/3"><c>re_register_name/3</c></seemfa>.</p>
<p>The function randomly selects one of the pids for registration, and
sends the message <c>{global_name_conflict, <anno>Name</anno>}</c> to
the other pid.</p>
@@ -161,7 +161,7 @@
<p>Atomically changes the registered name <c><anno>Name</anno></c> on
all nodes to refer to <c><anno>Pid</anno></c>.</p>
<p>Function <c><anno>Resolve</anno></c> has the same behavior as in
- <seealso marker="#register_name/2"><c>register_name/2,3</c></seealso>.
+ <seemfa marker="#register_name/2"><c>register_name/2,3</c></seemfa>.
</p>
</desc>
</func>
@@ -310,7 +310,7 @@
<type name="trans_fun"/>
<desc>
<p>Sets a lock on <c><anno>Id</anno></c> (using
- <seealso marker="#set_lock/3"><c>set_lock/3</c></seealso>).
+ <seemfa marker="#set_lock/3"><c>set_lock/3</c></seemfa>).
If this succeeds, <c><anno>Fun</anno>()</c> is evaluated and the
result <c><anno>Res</anno></c>
is returned. Returns <c>aborted</c> if the lock attempt fails.
@@ -343,8 +343,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="global_group"><c>global_group(3)</c></seealso>,
- <seealso marker="net_kernel"><c>net_kernel(3)</c></seealso></p>
+ <p><seeerl marker="global_group"><c>global_group(3)</c></seeerl>,
+ <seeerl marker="net_kernel"><c>net_kernel(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/global_group.xml b/lib/kernel/doc/src/global_group.xml
index 4ede70d875..49af2dd24b 100644
--- a/lib/kernel/doc/src/global_group.xml
+++ b/lib/kernel/doc/src/global_group.xml
@@ -33,15 +33,15 @@
<description>
<p>This module makes it possible to partition the nodes of a
system into <em>global groups</em>. Each global group has its own
- global namespace, see <seealso marker="global">
- <c>global(3)</c></seealso>.</p>
+ global namespace, see <seeerl marker="global">
+ <c>global(3)</c></seeerl>.</p>
<p>The main advantage of dividing systems into global groups is that
the background load decreases while the number of nodes to be
updated is reduced when manipulating globally registered names.</p>
<p>The Kernel configuration parameter <c>global_groups</c> defines
the global groups (see also
- <seealso marker="kernel_app#global_groups"><c>kernel(6)</c></seealso>
- and <seealso marker="config"><c>config(4)</c></seealso>):</p>
+ <seeapp marker="kernel_app#global_groups"><c>kernel(6)</c></seeapp>
+ and <seefile marker="config"><c>config(4)</c></seefile>):</p>
<code type="none">
{global_groups, [GroupTuple :: group_tuple()]}</code>
<p>For the processes and nodes to run smoothly using the global
@@ -81,7 +81,7 @@
<name name="publish_type"/>
<desc>
<p>A node started with command-line flag <c>-hidden</c> (see
- <seealso marker="erts:erl"><c>erl(1)</c></seealso>) is said
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom>) is said
to be a <em>hidden</em> node. A hidden node establishes hidden
connections to nodes not part of the same global group, but
normal (visible) connections to nodes part of the same global
@@ -233,7 +233,7 @@
on any known node not part of the group.</p>
<p>If synchronization is not possible, an error report is sent
to the error logger (see also
- <seealso marker="error_logger"><c>error_logger(3)</c></seealso>.
+ <seeerl marker="error_logger"><c>error_logger(3)</c></seeerl>.
</p>
<p>Returns <c>{error, {'invalid global_groups definition', Bad}}</c>
if configuration parameter <c>global_groups</c> has an
@@ -269,7 +269,7 @@
have inaccurate information about registered names in its
global group.</p></item>
<item><p>Function
- <seealso marker="#send/2"><c>send/2,3</c></seealso>
+ <seemfa marker="#send/2"><c>send/2,3</c></seemfa>
is not secure.</p></item>
<item><p>Distribution of applications is highly dependent of the global
group definitions. It is not recommended that an application is
@@ -282,8 +282,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="global"><c>global(3)</c></seealso>,
- <seealso marker="erts:erl"><c>erl(1)</c></seealso></p>
+ <p><seeerl marker="global"><c>global(3)</c></seeerl>,
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/heart.xml b/lib/kernel/doc/src/heart.xml
index 4243b1ffe8..133cbd48c3 100644
--- a/lib/kernel/doc/src/heart.xml
+++ b/lib/kernel/doc/src/heart.xml
@@ -40,7 +40,7 @@
(defaults to 60 seconds), the system can be rebooted.</p>
<p>An Erlang runtime system to be monitored by a heart program
is to be started with command-line flag <c>-heart</c> (see
- also <seealso marker="erts:erl"><c>erl(1)</c></seealso>).
+ also <seecom marker="erts:erl"><c>erl(1)</c></seecom>).
The <c>heart</c> process is then started automatically:</p>
<pre>
% <input>erl -heart ...</input></pre>
@@ -130,7 +130,7 @@
<p>Limitations: Command string <c><anno>Cmd</anno></c> is sent to the
<c>heart</c> program as an ISO Latin-1 or UTF-8 encoded binary,
depending on the filename encoding mode of the emulator (see
- <seealso marker="kernel:file#native_name_encoding/0"><c>file:native_name_encoding/0</c></seealso>).
+ <seemfa marker="kernel:file#native_name_encoding/0"><c>file:native_name_encoding/0</c></seemfa>).
The size of the encoded binary must be less than 2047 bytes.</p>
</desc>
</func>
diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml
index d9f8209cf9..8387b01930 100644
--- a/lib/kernel/doc/src/inet.xml
+++ b/lib/kernel/doc/src/inet.xml
@@ -33,8 +33,8 @@
<description>
<p>This module provides access to TCP/IP protocols.</p>
<p>See also
- <seealso marker="erts:inet_cfg">ERTS User's Guide:
- Inet Configuration</seealso> for more information about how to
+ <seeguide marker="erts:inet_cfg">ERTS User's Guide:
+ Inet Configuration</seeguide> for more information about how to
configure an Erlang runtime system for IP communication.</p>
<p>The following two Kernel configuration parameters affect the
behavior of all sockets opened on an Erlang node:</p>
@@ -80,9 +80,9 @@ Address ip_address()
3ffe:b80:1f8d:2:204:acff:fe17:bf38
{16#3ffe,16#b80,16#1f8d,16#2,16#204,16#acff,16#fe17,16#bf38}
fe80::204:acff:fe17:bf38
- {16#fe80,0,0,0,0,16#204,16#acff,16#fe17,16#bf38}</code>
+ {16#fe80,0,0,0,16#204,16#acff,16#fe17,16#bf38}</code>
<p>Function
- <seealso marker="#parse_address/1"><c>parse_address/1</c></seealso>
+ <seemfa marker="#parse_address/1"><c>parse_address/1</c></seemfa>
can be useful:</p>
<pre>
1> <input>inet:parse_address("192.168.42.2").</input>
@@ -167,9 +167,9 @@ fe80::204:acff:fe17:bf38
<p>
A <c>binary()</c> is passed as is to the operating system,
but a <c>string()</c> is encoded according to the
- <seealso marker="file#native_name_encoding/0">
+ <seemfa marker="file#native_name_encoding/0">
system filename encoding mode.
- </seealso>
+ </seemfa>
</p>
<p>
Other addresses are possible, for example Linux implements
@@ -197,14 +197,14 @@ fe80::204:acff:fe17:bf38
<desc>
<p>
Addresses besides
- <seealso marker="#type-ip_address">
+ <seetype marker="#ip_address">
<c>ip_address()</c>
- </seealso>
+ </seetype>
ones that are returned from socket API functions.
See in particular
- <seealso marker="#type-local_address">
+ <seetype marker="#local_address">
<c>local_address()</c>.
- </seealso>
+ </seetype>
The <c>unspec</c> family corresponds to AF_UNSPEC and can
occur if the other side has no socket address.
The <c>undefined</c> family can only occur in the unlikely
@@ -218,22 +218,22 @@ fe80::204:acff:fe17:bf38
<p>
Ancillary data received with the data packet,
read with the socket option
- <seealso marker="gen_tcp#type-pktoptions_value">
+ <seetype marker="gen_tcp#pktoptions_value">
<c>pktoptions</c>
- </seealso>
+ </seetype>
from a TCP socket,
or to set in a call to
- <seealso marker="gen_udp#send-4-AncData"><c>gen_udp:send/4</c></seealso>
+ <seeerl marker="gen_udp#send-4-AncData"><c>gen_udp:send/4</c></seeerl>
or
- <seealso marker="gen_udp#send/5"><c>gen_udp:send/5</c></seealso>.
+ <seemfa marker="gen_udp#send/5"><c>gen_udp:send/5</c></seemfa>.
</p>
<p>
The value(s) correspond to the currently active socket
- <seealso marker="#type-socket_setopt">options</seealso>
- <seealso marker="inet#option-recvtos"><c>recvtos</c></seealso>,
- <seealso marker="inet#option-recvtclass"><c>recvtclass</c></seealso>
+ <seetype marker="#socket_setopt">options</seetype>
+ <seeerl marker="inet#option-recvtos"><c>recvtos</c></seeerl>,
+ <seeerl marker="inet#option-recvtclass"><c>recvtclass</c></seeerl>
and
- <seealso marker="inet#option-recvttl"><c>recvttl</c></seealso>,
+ <seeerl marker="inet#option-recvttl"><c>recvttl</c></seeerl>,
or for a single send operation the option(s) to override
the currently active socket option(s).
</p>
@@ -244,7 +244,7 @@ fe80::204:acff:fe17:bf38
<desc>
<p>
Interface address description list returned from
- <seealso marker="#getifaddrs/0"><c>getifaddrs/0,1</c></seealso>
+ <seemfa marker="#getifaddrs/0"><c>getifaddrs/0,1</c></seemfa>
for a named interface, translated from the returned
data of the POSIX API function <c>getaddrinfo()</c>.
</p>
@@ -317,16 +317,16 @@ fe80::204:acff:fe17:bf38
<desc>
<p>An atom that is named from the POSIX error codes used in Unix,
and in the runtime libraries of most C compilers. See section
- <seealso marker="#error_codes">POSIX Error Codes</seealso>.</p>
+ <seeerl marker="#error_codes">POSIX Error Codes</seeerl>.</p>
</desc>
</datatype>
<datatype>
<name>socket()</name>
<desc>
<p>See
- <seealso marker="gen_tcp#type-socket"><c>gen_tcp:type-socket</c></seealso>
+ <seetype marker="gen_tcp#socket"><c>gen_tcp:type-socket</c></seetype>
and
- <seealso marker="gen_udp#type-socket"><c>gen_udp:type-socket</c></seealso>.
+ <seetype marker="gen_udp#socket"><c>gen_udp:type-socket</c></seetype>.
</p>
</desc>
</datatype>
@@ -356,7 +356,7 @@ fe80::204:acff:fe17:bf38
<desc>
<p>Returns a diagnostic error string. For possible POSIX values and
corresponding strings, see section
- <seealso marker="#error_codes">POSIX Error Codes</seealso>.</p>
+ <seeerl marker="#error_codes">POSIX Error Codes</seeerl>.</p>
</desc>
</func>
@@ -367,8 +367,8 @@ fe80::204:acff:fe17:bf38
<p>
Returns the state of the <c>Inet</c> configuration database in
form of a list of recorded configuration parameters. For more
- information, see <seealso marker="erts:inet_cfg">ERTS User's Guide:
- Inet Configuration</seealso>.
+ information, see <seeguide marker="erts:inet_cfg">ERTS User's Guide:
+ Inet Configuration</seeguide>.
</p>
<p>
Only actual parameters with other than default values
@@ -452,9 +452,9 @@ fe80::204:acff:fe17:bf38
<p>
The interface address description tuples
are documented under the type of the
- <seealso marker="#type-getifaddrs_ifopts">
+ <seetype marker="#getifaddrs_ifopts">
<c><anno>Ifopts</anno></c>
- </seealso>
+ </seetype>
value.
</p>
</desc>
@@ -471,34 +471,34 @@ fe80::204:acff:fe17:bf38
</v>
<v>
Namespace =
- <seealso marker="file#type-filename_all">
+ <seetype marker="file#filename_all">
file:filename_all()
- </seealso>
+ </seetype>
</v>
<v>Ifname = string()</v>
<v>
Ifopts =
- <seealso marker="#type-getifaddrs_ifopts">
+ <seetype marker="#getifaddrs_ifopts">
getifaddrs_ifopts()
- </seealso>
+ </seetype>
</v>
- <v>Posix = <seealso marker="#type-posix">posix()</seealso></v>
+ <v>Posix = <seetype marker="#posix">posix()</seetype></v>
</type>
<desc>
<p>
The same as
- <seealso marker="#getifaddrs/0"><c>getifaddrs/0</c></seealso>
+ <seemfa marker="#getifaddrs/0"><c>getifaddrs/0</c></seemfa>
but the <c>Option</c>
<c>{netns, Namespace}</c> sets a network namespace
for the OS call, on platforms that supports that feature.
</p>
<p>
See the socket option
- <seealso marker="#option-netns">
+ <seeerl marker="#option-netns">
<c>{netns, Namespace}</c>
- </seealso>
+ </seeerl>
under
- <seealso marker="#setopts/2"><c>setopts/2</c></seealso>.
+ <seemfa marker="#setopts/2"><c>setopts/2</c></seemfa>.
</p>
</desc>
</func>
@@ -509,11 +509,11 @@ fe80::204:acff:fe17:bf38
<desc>
<p>Gets one or more options for a socket. For a list of available
options, see
- <seealso marker="#setopts/2"><c>setopts/2</c></seealso>.
+ <seemfa marker="#setopts/2"><c>setopts/2</c></seemfa>.
See also the description for the type
- <seealso marker="gen_tcp#type-pktoptions_value">
+ <seetype marker="gen_tcp#pktoptions_value">
<c>gen_tcp:pktoptions_value()</c>
- </seealso>.</p>
+ </seetype>.</p>
<p>The number of elements in the returned
<c><anno>OptionValues</anno></c>
list does not necessarily correspond to the number of options
@@ -690,7 +690,7 @@ get_tcpi_sacked(Sock) ->
<fsummary>Convert IPv6/IPV4 address to ASCII.</fsummary>
<desc>
<p>Parses an
- <seealso marker="#type-ip_address"><c>ip_address()</c></seealso>
+ <seetype marker="#ip_address"><c>ip_address()</c></seetype>
and returns an IPv4 or IPv6 address string.</p>
</desc>
</func>
@@ -700,8 +700,8 @@ get_tcpi_sacked(Sock) ->
<fsummary>Parse an IPv4 or IPv6 address.</fsummary>
<desc>
<p>Parses an IPv4 or IPv6 address string and returns an
- <seealso marker="#type-ip4_address"><c>ip4_address()</c></seealso> or
- <seealso marker="#type-ip6_address"><c>ip6_address()</c></seealso>.
+ <seetype marker="#ip4_address"><c>ip4_address()</c></seetype> or
+ <seetype marker="#ip6_address"><c>ip6_address()</c></seetype>.
Accepts a shortened IPv4 address string.</p>
</desc>
</func>
@@ -711,7 +711,7 @@ get_tcpi_sacked(Sock) ->
<fsummary>Parse an IPv4 address.</fsummary>
<desc>
<p>Parses an IPv4 address string and returns an
- <seealso marker="#type-ip4_address"><c>ip4_address()</c></seealso>.
+ <seetype marker="#ip4_address"><c>ip4_address()</c></seetype>.
Accepts a shortened IPv4 address string.</p>
</desc>
</func>
@@ -722,7 +722,7 @@ get_tcpi_sacked(Sock) ->
<desc>
<p>Parses an IPv4 address string containing four fields, that is,
<em>not</em> shortened, and returns an
- <seealso marker="#type-ip4_address"><c>ip4_address()</c></seealso>.
+ <seetype marker="#ip4_address"><c>ip4_address()</c></seetype>.
</p>
</desc>
</func>
@@ -732,7 +732,7 @@ get_tcpi_sacked(Sock) ->
<fsummary>Parse an IPv6 address.</fsummary>
<desc>
<p>Parses an IPv6 address string and returns an
- <seealso marker="#type-ip6_address"><c>ip6_address()</c></seealso>.
+ <seetype marker="#ip6_address"><c>ip6_address()</c></seetype>.
If an IPv4 address string is specified, an IPv4-mapped IPv6 address
is returned.</p>
</desc>
@@ -743,7 +743,7 @@ get_tcpi_sacked(Sock) ->
<fsummary>Parse an IPv6 address strict.</fsummary>
<desc>
<p>Parses an IPv6 address string and returns an
- <seealso marker="#type-ip6_address"><c>ip6_address()</c></seealso>.
+ <seetype marker="#ip6_address"><c>ip6_address()</c></seetype>.
Does <em>not</em> accept IPv4 addresses.</p>
</desc>
</func>
@@ -766,8 +766,8 @@ get_tcpi_sacked(Sock) ->
<fsummary>Parse an IPv4 or IPv6 address strict.</fsummary>
<desc>
<p>Parses an IPv4 or IPv6 address string and returns an
- <seealso marker="#type-ip4_address"><c>ip4_address()</c></seealso> or
- <seealso marker="#type-ip6_address"><c>ip6_address()</c></seealso>.
+ <seetype marker="#ip4_address"><c>ip4_address()</c></seetype> or
+ <seetype marker="#ip6_address"><c>ip6_address()</c></seetype>.
Does <em>not</em> accept a shortened IPv4 address string.</p>
</desc>
</func>
@@ -780,7 +780,7 @@ get_tcpi_sacked(Sock) ->
<p>Returns the address and port for the other end of a connection.</p>
<p>Notice that for SCTP sockets, this function returns only
one of the peer addresses of the socket. Function
- <seealso marker="#peernames/1"><c>peernames/1,2</c></seealso>
+ <seemfa marker="#peernames/1"><c>peernames/1,2</c></seemfa>
returns all.</p>
</desc>
</func>
@@ -791,7 +791,7 @@ get_tcpi_sacked(Sock) ->
connection.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#peernames/2"><c>peernames(<anno>Socket</anno>, 0)</c></seealso>.
+ <seemfa marker="#peernames/2"><c>peernames(<anno>Socket</anno>, 0)</c></seemfa>.
</p>
<p>Notice that the behavior of this function for an SCTP
one-to-many style socket is not defined by the
@@ -840,9 +840,9 @@ get_tcpi_sacked(Sock) ->
messages to the receiving process.</p>
<p>If the value is <c>false</c> (passive mode), the process must
explicitly receive incoming data by calling
- <seealso marker="gen_tcp#recv/2"><c>gen_tcp:recv/2,3</c></seealso>,
- <seealso marker="gen_udp#recv/2"><c>gen_udp:recv/2,3</c></seealso>,
- or <seealso marker="gen_sctp#recv/1"><c>gen_sctp:recv/1,2</c></seealso>
+ <seemfa marker="gen_tcp#recv/2"><c>gen_tcp:recv/2,3</c></seemfa>,
+ <seemfa marker="gen_udp#recv/2"><c>gen_udp:recv/2,3</c></seemfa>,
+ or <seemfa marker="gen_sctp#recv/1"><c>gen_sctp:recv/1,2</c></seemfa>
(depending on the type of socket).</p>
<p>If the value is <c>once</c> (<c>{active, once}</c>),
<em>one</em> data message from the socket is sent
@@ -915,7 +915,7 @@ get_tcpi_sacked(Sock) ->
maximum when <c>recbuf</c> is set.
However, as the size set for <c>recbuf</c>
usually become larger, you are encouraged to use
- <seealso marker="#getopts/2"><c>getopts/2</c></seealso>
+ <seemfa marker="#getopts/2"><c>getopts/2</c></seemfa>
to analyze the behavior of your operating system.</p>
<p>Note that this is also the maximum amount of data that can be
received from a single recv call. If you are using higher than
@@ -951,7 +951,7 @@ get_tcpi_sacked(Sock) ->
<p>The only reason to set it to <c>false</c> is if you want
to continue sending data to the socket after a close is
detected, for example, if the peer uses
- <seealso marker="gen_tcp#shutdown/2"><c>gen_tcp:shutdown/2</c></seealso>
+ <seemfa marker="gen_tcp#shutdown/2"><c>gen_tcp:shutdown/2</c></seemfa>
to shut down the write side.</p>
</item>
<tag><c>{header, Size}</c></tag>
@@ -980,7 +980,7 @@ get_tcpi_sacked(Sock) ->
<p>Notice that distribution sockets disable the use of
<c>high_msgq_watermark</c> and <c>low_msgq_watermark</c>.
Instead use the
- <seealso marker="erts:erlang#system_info_dist_buf_busy_limit">distribution buffer busy limit</seealso>,
+ <seeerl marker="erts:erlang#system_info_dist_buf_busy_limit">distribution buffer busy limit</seeerl>,
which is a similar feature.</p>
</item>
<tag><c>{high_watermark, Size}</c> (TCP/IP sockets)</tag>
@@ -1003,7 +1003,7 @@ get_tcpi_sacked(Sock) ->
before associating it to an address. It is therefore only
reasonable to specify it when creating the socket and not
to use it when calling function
- (<seealso marker="#setopts/2"><c>setopts/2</c></seealso>)
+ (<seemfa marker="#setopts/2"><c>setopts/2</c></seemfa>)
containing this description.</p>
<p>The behavior of a socket with this option set to
<c>true</c> is the only portable one. The original
@@ -1025,7 +1025,7 @@ get_tcpi_sacked(Sock) ->
in this case.</p>
<p>Setting this option on platforms where it does not exist
is ignored. Getting this option with
- <seealso marker="#getopts/2"><c>getopts/2</c></seealso>
+ <seemfa marker="#getopts/2"><c>getopts/2</c></seemfa>
returns no value, that is, the returned list does not contain an
<c>{ipv6_v6only,_}</c> tuple. On Windows, the option
does not exist, but it is emulated as a
@@ -1036,7 +1036,7 @@ get_tcpi_sacked(Sock) ->
have customized the kernel to only allow <c>false</c>,
which can be doable (but awkward) on, for example, OpenBSD.</p>
<p>If you read back the option value using
- <seealso marker="#getopts/2"><c>getopts/2</c></seealso>
+ <seemfa marker="#getopts/2"><c>getopts/2</c></seemfa>
and get no value, the option does not exist in the host
operating system. The behavior of both an IPv6 and an IPv4
socket listening on the same port, and for an IPv6 socket
@@ -1098,8 +1098,8 @@ get_tcpi_sacked(Sock) ->
<p>Notice that distribution sockets disable the use of
<c>high_msgq_watermark</c> and <c>low_msgq_watermark</c>.
Instead they use the
- <seealso marker="erts:erlang#system_info_dist_buf_busy_limit">distribution
- buffer busy limit</seealso>, which is a similar feature.</p>
+ <seeerl marker="erts:erlang#system_info_dist_buf_busy_limit">distribution
+ buffer busy limit</seeerl>, which is a similar feature.</p>
</item>
<tag><c>{low_watermark, Size}</c> (TCP/IP sockets)</tag>
<item>
@@ -1130,13 +1130,13 @@ get_tcpi_sacked(Sock) ->
example, <c>"/var/run/netns/example"</c>, typically created by
command <c>ip netns add example</c>. This option must be used in
a function call that creates a socket, that is,
- <seealso marker="gen_tcp#connect/3"><c>gen_tcp:connect/3,4</c></seealso>,
- <seealso marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seealso>,
- <seealso marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seealso>
+ <seemfa marker="gen_tcp#connect/3"><c>gen_tcp:connect/3,4</c></seemfa>,
+ <seemfa marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seemfa>,
+ <seemfa marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seemfa>
or
- <seealso marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seealso>,
+ <seemfa marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seemfa>,
and also
- <seealso marker="#getifaddrs/1"><c>getifaddrs/1</c></seealso>.
+ <seemfa marker="#getifaddrs/1"><c>getifaddrs/1</c></seemfa>.
</p>
<p>This option uses the Linux-specific syscall
<c>setns()</c>, such as in Linux kernel 3.0 or later,
@@ -1161,27 +1161,27 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
network namespaces.</p>
<p><c>Namespace</c> is a filename and is encoded
and decoded as discussed in module
- <seealso marker="file">file</seealso>, with the
+ <seeerl marker="file">file</seeerl>, with the
following exceptions:</p>
<list type="bulleted">
<item><p>Emulator flag <c>+fnu</c> is ignored.</p></item>
- <item><p><seealso marker="#getopts/2"><c>getopts/2</c></seealso>
+ <item><p><seemfa marker="#getopts/2"><c>getopts/2</c></seemfa>
for this option returns a binary for the filename if the stored
filename cannot be decoded. This is only to occur if you set the
option using a binary that cannot be decoded with the emulator's
filename encoding:
- <seealso marker="file#native_name_encoding/0"><c>file:native_name_encoding/0</c></seealso>.</p></item>
+ <seemfa marker="file#native_name_encoding/0"><c>file:native_name_encoding/0</c></seemfa>.</p></item>
</list>
</item>
<tag><c>{bind_to_device, Ifname :: binary()}</c></tag>
<item>
<p>Binds a socket to a specific network interface. This option
must be used in a function call that creates a socket, that is,
- <seealso marker="gen_tcp#connect/3"><c>gen_tcp:connect/3,4</c></seealso>,
- <seealso marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seealso>,
- <seealso marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seealso>, or
- <seealso marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seealso>.</p>
- <p>Unlike <seealso marker="#getifaddrs/0"><c>getifaddrs/0</c></seealso>, Ifname
+ <seemfa marker="gen_tcp#connect/3"><c>gen_tcp:connect/3,4</c></seemfa>,
+ <seemfa marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seemfa>,
+ <seemfa marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seemfa>, or
+ <seemfa marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seemfa>.</p>
+ <p>Unlike <seemfa marker="#getifaddrs/0"><c>getifaddrs/0</c></seemfa>, Ifname
is encoded a binary. In the unlikely case that a system is using
non-7-bit-ASCII characters in network device names, special care
has to be taken when encoding this argument.</p>
@@ -1190,7 +1190,7 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
and therefore only exists when the runtime system
is compiled for such an operating system.</p>
<p>Before Linux 3.8, this socket option could be set, but could not retrieved
- with <seealso marker="#getopts/2"><c>getopts/2</c></seealso>. Since Linux 3.8,
+ with <seemfa marker="#getopts/2"><c>getopts/2</c></seemfa>. Since Linux 3.8,
it is readable.</p>
<p>The virtual machine also needs elevated privileges, either
running as superuser or (for Linux) having capability
@@ -1271,8 +1271,8 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
<p>The Hypertext Transfer Protocol. The packets
are returned with the format according to <c>HttpPacket</c>
described in
- <seealso marker="erts:erlang#decode_packet/3">
- <c>erlang:decode_packet/3</c></seealso> in ERTS.
+ <seemfa marker="erts:erlang#decode_packet/3">
+ <c>erlang:decode_packet/3</c></seemfa> in ERTS.
A socket in passive
mode returns <c>{ok, HttpPacket}</c> from <c>gen_tcp:recv</c>
while an active socket sends messages like
@@ -1324,7 +1324,7 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
<item>
<p>The minimum size of the receive buffer to use for
the socket. You are encouraged to use
- <seealso marker="#getopts/2"><c>getopts/2</c></seealso>
+ <seemfa marker="#getopts/2"><c>getopts/2</c></seemfa>
to retrieve the size set by your operating system.</p>
<marker id="option-recvtclass"></marker>
</item>
@@ -1346,16 +1346,16 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
(<c>gen_udp</c> and <c>gen_sctp</c>),
the <c>TCLASS</c> value is returned
in an extended return tuple contained in an
- <seealso marker="inet#type-ancillary_data">
+ <seetype marker="inet#ancillary_data">
ancillary data
- </seealso>
+ </seetype>
list.
For stream oriented sockets (<c>gen_tcp</c>)
the only way to get the <c>TCLASS</c>
value is if the platform supports the
- <seealso marker="gen_tcp#type-pktoptions_value">
+ <seetype marker="gen_tcp#pktoptions_value">
<c>pktoptions</c>
- </seealso>
+ </seetype>
option.
</p>
<marker id="option-recvtos"></marker>
@@ -1377,16 +1377,16 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
(<c>gen_udp</c> and <c>gen_sctp</c>),
the <c>TOS</c> value is returned
in an extended return tuple contained in an
- <seealso marker="inet#type-ancillary_data">
+ <seetype marker="inet#ancillary_data">
ancillary data
- </seealso>
+ </seetype>
list.
For stream oriented sockets (<c>gen_tcp</c>)
the only way to get the <c>TOS</c>
value is if the platform supports the
- <seealso marker="gen_tcp#type-pktoptions_value">
+ <seetype marker="gen_tcp#pktoptions_value">
<c>pktoptions</c>
- </seealso>
+ </seetype>
option.
</p>
<marker id="option-recvttl"></marker>
@@ -1408,16 +1408,16 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
(<c>gen_udp</c> and <c>gen_sctp</c>),
the <c>TTL</c> value is returned
in an extended return tuple contained in an
- <seealso marker="inet#type-ancillary_data">
+ <seetype marker="inet#ancillary_data">
ancillary data
- </seealso>
+ </seetype>
list.
For stream oriented sockets (<c>gen_tcp</c>)
the only way to get the <c>TTL</c>
value is if the platform supports the
- <seealso marker="gen_tcp#type-pktoptions_value">
+ <seetype marker="gen_tcp#pktoptions_value">
<c>pktoptions</c>
- </seealso>
+ </seetype>
option.
</p>
</item>
@@ -1452,7 +1452,7 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
<p>When this option is set to <c>false</c>, which is
default, an RST received from the TCP peer is treated
as a normal close (as though an FIN was sent). A caller to
- <seealso marker="gen_tcp#recv/2"><c>gen_tcp:recv/2</c></seealso>
+ <seemfa marker="gen_tcp#recv/2"><c>gen_tcp:recv/2</c></seemfa>
gets <c>{error, closed}</c>. In active
mode, the controlling process receives a
<c>{tcp_closed, Socket}</c> message, indicating that the
@@ -1461,17 +1461,17 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
distinguish between a connection that was closed normally,
and one that was aborted (intentionally or unintentionally)
by the TCP peer. A call to
- <seealso marker="gen_tcp#recv/2"><c>gen_tcp:recv/2</c></seealso>
+ <seemfa marker="gen_tcp#recv/2"><c>gen_tcp:recv/2</c></seemfa>
returns <c>{error, econnreset}</c>. In
active mode, the controlling process receives a
<c>{tcp_error, Socket, econnreset}</c> message
before the usual <c>{tcp_closed, Socket}</c>, as is
the case for any other socket error. Calls to
- <seealso marker="gen_tcp#send/2"><c>gen_tcp:send/2</c></seealso>
+ <seemfa marker="gen_tcp#send/2"><c>gen_tcp:send/2</c></seemfa>
also returns <c>{error, econnreset}</c> when it
is detected that a TCP peer has sent an RST.</p>
<p>A connected socket returned from
- <seealso marker="gen_tcp#accept/1"><c>gen_tcp:accept/1</c></seealso>
+ <seemfa marker="gen_tcp#accept/1"><c>gen_tcp:accept/1</c></seemfa>
inherits the <c>show_econnreset</c> setting from the
listening socket.</p>
<marker id="option-show_econnreset"></marker>
@@ -1480,7 +1480,7 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp</code>
<item>
<p>The minimum size of the send buffer to use for the socket.
You are encouraged to use
- <seealso marker="#getopts/2"><c>getopts/2</c></seealso>,
+ <seemfa marker="#getopts/2"><c>getopts/2</c></seemfa>,
to retrieve the size set by your operating system.</p>
</item>
<tag><c>{priority, Integer}</c></tag>
@@ -1559,7 +1559,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code>
<p>Returns the local address and port number for a socket.</p>
<p>Notice that for SCTP sockets this function returns only
one of the socket addresses. Function
- <seealso marker="#socknames/1"><c>socknames/1,2</c></seealso>
+ <seemfa marker="#socknames/1"><c>socknames/1,2</c></seemfa>
returns all.</p>
</desc>
</func>
@@ -1569,7 +1569,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code>
<fsummary>Return all local address/port numbers for a socket.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#socknames/2"><c>socknames(<anno>Socket</anno>, 0)</c></seealso>.
+ <seemfa marker="#socknames/2"><c>socknames(<anno>Socket</anno>, 0)</c></seemfa>.
</p>
</desc>
</func>
diff --git a/lib/kernel/doc/src/inet_res.xml b/lib/kernel/doc/src/inet_res.xml
index 1904e371f7..690d1c61d1 100644
--- a/lib/kernel/doc/src/inet_res.xml
+++ b/lib/kernel/doc/src/inet_res.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2009</year><year>2018</year>
+ <year>2009</year><year>2021</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -33,12 +33,12 @@
<description>
<p>This module performs DNS name resolving to recursive name servers.</p>
<p>See also
- <seealso marker="erts:inet_cfg">ERTS User's Guide: Inet Configuration</seealso>
+ <seeguide marker="erts:inet_cfg">ERTS User's Guide: Inet Configuration</seeguide>
for more information about how to configure an Erlang runtime system
for IP communication, and how to enable this DNS client by defining
<c><![CDATA['dns']]></c> as a lookup method.
The DNS client then acts as a backend for the resolving functions in
- <seealso marker="kernel:inet"><c>inet</c></seealso>.</p>
+ <seeerl marker="kernel:inet"><c>inet</c></seeerl>.</p>
<p>This DNS client can resolve DNS records even if it
is not used for normal name resolving in the node.</p>
<p>This is not a full-fledged resolver, only a
@@ -48,31 +48,45 @@
<section>
<title>Name Resolving</title>
<p>UDP queries are used unless resolver option
- <c>usevc</c> is <c>true</c>, which forces TCP queries.
- If the query is too large for UDP, TCP is used instead.
- For regular DNS queries, 512 bytes is the size limit.</p>
+ <c>usevc</c> is <c>true</c>, which forces TCP queries.
+ If the query is too large for UDP, TCP is used instead.
+ For regular DNS queries, 512 bytes is the size limit.</p>
+
<p>When EDNS is enabled (resolver option
- <c>edns</c> is set to the EDNS version (that is, <c>0</c>
- instead of <c>false</c>), resolver option
- <c>udp_payload_size</c> sets the limit. If a name server
- replies with the TC bit set (truncation), indicating that
- the answer is incomplete, the query is retried
- to that name server using TCP. Resolver option
- <c>udp_payload_size</c> also sets the advertised
- size for the maximum allowed reply size, if EDNS is
- enabled, otherwise the name server uses the limit
- 512 bytes. If the reply is larger, it gets truncated,
- forcing a TCP requery.</p>
+ <c>edns</c> is set to the EDNS version (that is, <c>0</c>
+ instead of <c>false</c>), resolver option
+ <c>udp_payload_size</c> sets the limit. If a name server
+ replies with the TC bit set (truncation), indicating that
+ the answer is incomplete, the query is retried
+ to that name server using TCP. Resolver option
+ <c>udp_payload_size</c> also sets the advertised
+ size for the maximum allowed reply size, if EDNS is
+ enabled, otherwise the name server uses the limit
+ 512 bytes. If the reply is larger, it gets truncated,
+ forcing a TCP requery.</p>
+
<p>For UDP queries, resolver options <c>timeout</c>
- and <c>retry</c> control retransmission.
- Each name server in the <c>nameservers</c> list is
- tried with a time-out of <c>timeout</c>/<c>retry</c>.
- Then all name servers are tried again, doubling the
- time-out, for a total of <c>retry</c> times.</p>
+ and <c>retry</c> control retransmission.
+ Each name server in the <c>nameservers</c> list is
+ tried with a time-out of <c>timeout</c>/<c>retry</c>.
+ Then all name servers are tried again, doubling the
+ time-out, for a total of <c>retry</c> times.</p>
+
+ <marker id="servfail_retry_timeout"/>
+ <p>But before all name servers are tried again, there is a
+ (user configurable) timeout, <c>servfail_retry_timeout</c>.
+ The point of this is to prevent the new query to be handled by
+ a server's servfail cache (a client that is to eager will
+ actually only get what is in the servfail cache).
+ If there is too little time left
+ of the resolver call's timeout to do a retry,
+ the resolver call may return
+ before the call's timeout has expired. </p>
+
<p>For queries not using the <c>search</c> list,
- if the query to all <c>nameservers</c> results in
- <c>{error,nxdomain}</c> or an empty answer, the same
- query is tried for <c>alt_nameservers</c>.</p>
+ if the query to all <c>nameservers</c> results in
+ <c>{error,nxdomain}</c> or an empty answer, the same
+ query is tried for <c>alt_nameservers</c>.</p>
</section>
<section>
@@ -211,7 +225,7 @@ inet_dns:record_type(_) -> undefined.</pre>
address.</fsummary>
<desc>
<p>Backend functions used by
- <seealso marker="kernel:inet#gethostbyaddr/1"><c>inet:gethostbyaddr/1</c></seealso>.
+ <seemfa marker="kernel:inet#gethostbyaddr/1"><c>inet:gethostbyaddr/1</c></seemfa>.
</p>
</desc>
</func>
@@ -224,10 +238,10 @@ inet_dns:record_type(_) -> undefined.</pre>
</fsummary>
<desc>
<p>Backend functions used by
- <seealso marker="kernel:inet#gethostbyname/1"><c>inet:gethostbyname/1,2</c></seealso>.
+ <seemfa marker="kernel:inet#gethostbyname/1"><c>inet:gethostbyname/1,2</c></seemfa>.
</p>
<p>This function uses resolver option <c>search</c> just like
- <seealso marker="#getbyname/2"><c>getbyname/2,3</c></seealso>.
+ <seemfa marker="#getbyname/2"><c>getbyname/2,3</c></seemfa>.
</p>
<p>If resolver option <c>inet6</c> is <c>true</c>,
an IPv6 address is looked up.</p>
@@ -250,7 +264,7 @@ inet_dns:record_type(_) -> undefined.</pre>
specific types that are not <c>any</c>. An empty answer
or a failed lookup returns an empty list.</p>
<p>Calls
- <seealso marker="#resolve/3"><c>resolve/*</c></seealso>
+ <seemfa marker="#resolve/3"><c>resolve/*</c></seemfa>
with the same arguments and filters the result, so
<c><anno>Opts</anno></c> is described for those functions.</p>
</desc>
@@ -266,7 +280,7 @@ inet_dns:record_type(_) -> undefined.</pre>
<p>Resolves a DNS record of the specified type and class for the
specified name. The returned <c>dns_msg()</c> can be examined using
access functions in <c>inet_db</c>, as described in section
- in <seealso marker="#dns_types">DNS Types</seealso>.</p>
+ in <seeerl marker="#dns_types">DNS Types</seeerl>.</p>
<p>If <c><anno>Name</anno></c> is an <c>ip_address()</c>, the domain
name to query for is generated as the standard reverse
<c>".IN-ADDR.ARPA."</c> name for an IPv4 address, or the
@@ -282,7 +296,7 @@ inet_dns:record_type(_) -> undefined.</pre>
function, it is used.</p>
<p>Option <c>verbose</c> (or rather <c>{verbose,true}</c>)
causes diagnostics printout through
- <seealso marker="stdlib:io#format/3"><c>io:format/2</c></seealso>
+ <seemfa marker="stdlib:io#format/3"><c>io:format/2</c></seemfa>
of queries, replies retransmissions, and so on, similar
to from utilities, such as <c>dig</c> and <c>nslookup</c>.</p>
<p>If <c><anno>Opt</anno></c> is any atom, it is interpreted
@@ -300,9 +314,9 @@ inet_dns:record_type(_) -> undefined.</pre>
<section>
<title>Example</title>
<p>This access functions example shows how
- <seealso marker="#lookup/3"><c>lookup/3</c></seealso>
+ <seemfa marker="#lookup/3"><c>lookup/3</c></seemfa>
can be implemented using
- <seealso marker="#resolve/3"><c>resolve/3</c></seealso>
+ <seemfa marker="#resolve/3"><c>resolve/3</c></seemfa>
from outside the module:</p>
<code type="none">
example_lookup(Name, Class, Type) ->
@@ -317,14 +331,15 @@ example_lookup(Name, Class, Type) ->
end.</code>
</section>
- <section>
- <title>Legacy Functions</title>
- <p>These are deprecated because the annoying double
- meaning of the name servers/time-out argument, and
- because they have no decent place for a resolver options list.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Legacy Functions</title>
+ <p>These are deprecated because the annoying double
+ meaning of the name servers/time-out argument, and
+ because they have no decent place for a resolver options list.</p>
+ </fsdescription>
<func>
<name name="nslookup" arity="3" since=""/>
<name name="nslookup" arity="4" clause_i="1" since=""/>
diff --git a/lib/kernel/doc/src/init_stub.xml b/lib/kernel/doc/src/init_stub.xml
index 1297c8264d..136ac37732 100644
--- a/lib/kernel/doc/src/init_stub.xml
+++ b/lib/kernel/doc/src/init_stub.xml
@@ -34,6 +34,6 @@
<modulesummary>Coordination of system startup.</modulesummary>
<description>
<p>This module is moved to the
- <seealso marker="erts:init">ERTS</seealso> application.</p>
+ <seeerl marker="erts:init">ERTS</seeerl> application.</p>
</description>
</erlref>
diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml
index 49e007aa09..d4f707951d 100644
--- a/lib/kernel/doc/src/kernel_app.xml
+++ b/lib/kernel/doc/src/kernel_app.xml
@@ -53,18 +53,18 @@
<title>Logger Handlers</title>
<p>Two standard logger handlers are defined in
the Kernel application. These are described in the
- <seealso marker="logger_chapter">Kernel User's Guide</seealso>,
- and in the <seealso marker="logger_std_h"><c>logger_std_h(3)</c></seealso>
- and <seealso marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c>
- </seealso> manual pages.</p>
+ <seeguide marker="logger_chapter">Kernel User's Guide</seeguide>,
+ and in the <seeerl marker="logger_std_h"><c>logger_std_h(3)</c></seeerl>
+ and <seeerl marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c>
+ </seeerl> manual pages.</p>
</section>
<section>
<marker id="erl_signal_server"/>
<title>OS Signal Event Handler</title>
<p>Asynchronous OS signals may be subscribed to via the Kernel applications event manager
- (see <seealso marker="doc/design_principles:des_princ">OTP Design Principles</seealso> and
- <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>) registered as <c>erl_signal_server</c>.
+ (see <seeguide marker="system/design_principles:des_princ">OTP Design Principles</seeguide> and
+ <seeerl marker="stdlib:gen_event"><c>gen_event(3)</c></seeerl>) registered as <c>erl_signal_server</c>.
A default signal handler is installed which handles the following signals:</p>
<taglist>
<tag><c>sigusr1</c></tag>
@@ -110,7 +110,7 @@
<item><p>Stop typed at terminal</p></item>
</taglist>
- <p>Setting OS signals are described in <seealso marker="os#set_signal/2"><c>os:set_signal/2</c></seealso>.</p>
+ <p>Setting OS signals are described in <seemfa marker="os#set_signal/2"><c>os:set_signal/2</c></seemfa>.</p>
</section>
</section>
@@ -119,7 +119,7 @@
<title>Configuration</title>
<p>The following configuration parameters are defined for the Kernel
application. For more information about configuration parameters,
- see file <seealso marker="app"><c>app(4)</c></seealso>.</p>
+ see file <seefile marker="app"><c>app(4)</c></seefile>.</p>
<taglist>
<tag><c>distributed = [Distrib]</c></tag>
<item>
@@ -132,9 +132,9 @@
<item><c>Nodes = [node() | {node(),...,node()}]</c></item>
</list>
<p>The parameter is described in
- <seealso marker="application#load/2"><c>application:load/2</c></seealso>.</p>
+ <seemfa marker="application#load/2"><c>application:load/2</c></seemfa>.</p>
</item>
- <tag><c>dist_auto_connect = Value</c></tag>
+ <tag><marker id="dist_auto_connect"/><c>dist_auto_connect = Value</c></tag>
<item>
<p>Specifies when nodes are automatically connected. If
this parameter is not specified, a node is always
@@ -144,14 +144,20 @@
<tag><c>never</c></tag>
<item><p>Connections are never automatically established, they
must be explicitly connected. See
- <seealso marker="net_kernel"><c>net_kernel(3)</c></seealso>.</p></item>
+ <seeerl marker="net_kernel"><c>net_kernel(3)</c></seeerl>.</p></item>
<tag><c>once</c></tag>
<item><p>Connections are established automatically, but only
once per node. If a node goes down, it must thereafter be
explicitly connected. See
- <seealso marker="net_kernel"><c>net_kernel(3)</c></seealso>.</p></item>
+ <seeerl marker="net_kernel"><c>net_kernel(3)</c></seeerl>.</p></item>
</taglist>
</item>
+ <tag><marker id="dist_listen"/><c>dist_listen = boolean()</c></tag>
+ <item>
+ <p>Specifies whether this node should be listening for incoming
+ distribution connections. Using this option implies that the node
+ also is <seecom marker="erts:erl#hidden"><c>-hidden</c></seecom>.</p>
+ </item>
<tag><c>permissions = [Perm]</c></tag>
<item>
<p>Specifies the default permission for applications when they
@@ -162,36 +168,36 @@
<item><c>Bool = boolean()</c></item>
</list>
<p>Permissions are described in
- <seealso marker="application#permit/2"><c>application:permit/2</c></seealso>.</p>
+ <seemfa marker="application#permit/2"><c>application:permit/2</c></seemfa>.</p>
</item>
<tag><marker id="logger"/><c>logger = [Config]</c></tag>
<item>
<p>Specifies the configuration
- for <seealso marker="logger">Logger</seealso>, except the
+ for <seeerl marker="logger">Logger</seeerl>, except the
primary log level, which is specified
- with <seealso marker="#logger_level"><c>logger_level</c></seealso>,
+ with <seeapp marker="#logger_level"><c>logger_level</c></seeapp>,
and the compatibility
- with <seealso marker="sasl:error_logging">SASL Error
- Logging</seealso>, which is specified
- with <seealso marker="#logger_sasl_compatible">
- <c>logger_sasl_compatible</c></seealso>.</p>
+ with <seeguide marker="sasl:error_logging">SASL Error
+ Logging</seeguide>, which is specified
+ with <seeapp marker="#logger_sasl_compatible">
+ <c>logger_sasl_compatible</c></seeapp>.</p>
<p>The <c>logger </c> parameter is described in
- section <seealso marker="logger_chapter#logger_parameter">
- Logging</seealso> in the Kernel User's Guide.</p>
+ section <seeguide marker="logger_chapter#logger_parameter">
+ Logging</seeguide> in the Kernel User's Guide.</p>
</item>
<tag><marker id="logger_level"/><c>logger_level = Level</c></tag>
<item>
<p>Specifies the primary log level for Logger. Log events with
the same, or a more severe level, pass through the primary
log level check. See
- section <seealso marker="logger_chapter">Logging</seealso>
+ section <seeguide marker="logger_chapter">Logging</seeguide>
in the Kernel User's Guide for more information about Logger
and log levels.</p>
<p><c>Level = emergency | alert | critical | error | warning |
notice | info | debug | all | none</c></p>
<p>To change the primary log level at runtime, use
- <seealso marker="logger#set_primary_config/2">
- <c>logger:set_primary_config(level, Level)</c></seealso>.</p>
+ <seemfa marker="logger#set_primary_config/2">
+ <c>logger:set_primary_config(level, Level)</c></seemfa>.</p>
<p>Defaults to <c>notice</c>.</p>
</item>
<tag><marker id="logger_sasl_compatible"/>
@@ -208,14 +214,14 @@
parameter <c>sasl_error_logger</c>
and <c>sasl_errlog_type</c>.</p>
<p>See section
- <seealso marker="sasl:sasl_app#deprecated_error_logger_config">
+ <seeapp marker="sasl:sasl_app#deprecated_error_logger_config">
Deprecated Error Logger Event Handlers and
- Configuration</seealso> in the sasl(6) manual page for
+ Configuration</seeapp> in the sasl(6) manual page for
information about the SASL configuration parameters.</p>
- <p>See section <seealso marker="sasl:error_logging">SASL Error
- Logging</seealso> in the SASL User's Guide, and
- section <seealso marker="logger_chapter#compatibility">Backwards
- Compatibility with error_logger</seealso> in the Kernel
+ <p>See section <seeguide marker="sasl:error_logging">SASL Error
+ Logging</seeguide> in the SASL User's Guide, and
+ section <seeguide marker="logger_chapter#compatibility">Backwards
+ Compatibility with error_logger</seeguide> in the Kernel
User's Guide for information about the SASL error logging
functionality, and how Logger can be backwards compatible
with this.</p>
@@ -236,7 +242,7 @@
<item>
<marker id="global_groups"></marker>
<p>Defines global groups, see
- <seealso marker="global_group"><c>global_group(3)</c></seealso>.
+ <seeerl marker="global_group"><c>global_group(3)</c></seeerl>.
In this parameter:</p>
<list type="bulleted">
<item><p><c>GroupTuple = {GroupName, [Node]} | {GroupName, PublishType, [Node]}</c></p></item>
@@ -248,19 +254,19 @@
<tag><c>inet_default_connect_options = [{Opt, Val}]</c></tag>
<item>
<p>Specifies default options for <c>connect</c> sockets,
- see <seealso marker="inet"><c>inet(3)</c></seealso>.</p>
+ see <seeerl marker="inet"><c>inet(3)</c></seeerl>.</p>
</item>
<tag><c>inet_default_listen_options = [{Opt, Val}]</c></tag>
<item>
<p>Specifies default options for <c>listen</c> (and
- <c>accept</c>) sockets, see <seealso marker="inet"><c>inet(3)</c></seealso>.</p>
+ <c>accept</c>) sockets, see <seeerl marker="inet"><c>inet(3)</c></seeerl>.</p>
</item>
<tag><c>{inet_dist_use_interface, ip_address()}</c></tag>
<item>
<p>If the host of an Erlang node has many network interfaces,
this parameter specifies which one to listen on. For the type definition
of <c>ip_address()</c>,
- see <seealso marker="inet"><c>inet(3)</c></seealso>.</p>
+ see <seeerl marker="inet"><c>inet(3)</c></seeerl>.</p>
</item>
<tag><c>{inet_dist_listen_min, First}</c> and <c>{inet_dist_listen_max, Last}</c></tag>
<item>
@@ -272,14 +278,14 @@
<marker id="inet_dist_listen_options"></marker>
<p>Defines a list of extra socket options to be used when opening the
listening socket for a distributed Erlang node.
- See <seealso marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seealso>.</p>
+ See <seemfa marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seemfa>.</p>
</item>
<tag><c>{inet_dist_connect_options, Opts}</c></tag>
<item>
<marker id="inet_dist_connect_options"></marker>
<p>Defines a list of extra socket options to be used when connecting to
other distributed Erlang nodes.
- See <seealso marker="gen_tcp#connect/4"><c>gen_tcp:connect/4</c></seealso>.</p>
+ See <seemfa marker="gen_tcp#connect/4"><c>gen_tcp:connect/4</c></seemfa>.</p>
</item>
<tag><c>inet_parse_error_log = silent</c></tag>
<item>
@@ -291,7 +297,7 @@
<item>
<p>The name (string) of an Inet user configuration file. For details,
see section
- <seealso marker="erts:inet_cfg"><c>Inet Configuration</c></seealso>
+ <seeguide marker="erts:inet_cfg"><c>Inet Configuration</c></seeguide>
in the ERTS User's Guide.</p>
</item>
<tag><c>net_setuptime = SetupTime</c></tag>
@@ -389,7 +395,7 @@ MaxT = TickTime + TickTime / 4</code>
<tag><c>start_boot_server = true | false</c></tag>
<item>
<p>Starts the <c>boot_server</c> if the parameter is <c>true</c>
- (see <seealso marker="erl_boot_server"><c>erl_boot_server(3)</c></seealso>).
+ (see <seeerl marker="erl_boot_server"><c>erl_boot_server(3)</c></seeerl>).
This parameter is to be set to <c>true</c> in an embedded system
using this service.</p>
<p>Defaults to <c>false</c>.</p>
@@ -409,16 +415,25 @@ MaxT = TickTime + TickTime / 4</code>
<tag><c>start_disk_log = true | false</c></tag>
<item>
<p>Starts the <c>disk_log_server</c> if the parameter is
- <c>true</c> (see <seealso marker="disk_log"><c>disk_log(3)</c></seealso>).
+ <c>true</c> (see <seeerl marker="disk_log"><c>disk_log(3)</c></seeerl>).
This parameter is to be set to <c>true</c> in an embedded system
using this service.</p>
<p>Defaults to <c>false</c>.</p>
</item>
+ <tag><c>start_pg = true | false</c></tag>
+ <item>
+ <marker id="start_pg"></marker>
+ <p>Starts the default <c>pg</c> scope server (see
+ <seeerl marker="pg"><c>pg(3)</c></seeerl>) if
+ the parameter is <c>true</c>. This parameter is to be set to
+ <c>true</c> in an embedded system that uses this service.</p>
+ <p>Defaults to <c>false</c>.</p>
+ </item>
<tag><c>start_pg2 = true | false</c></tag>
<item>
<marker id="start_pg2"></marker>
<p>Starts the <c>pg2</c> server (see
- <seealso marker="pg2"><c>pg2(3)</c></seealso>) if
+ <seeerl marker="pg2"><c>pg2(3)</c></seeerl>) if
the parameter is <c>true</c>. This parameter is to be set to
<c>true</c> in an embedded system that uses this service.</p>
<p>Defaults to <c>false</c>.</p>
@@ -426,7 +441,7 @@ MaxT = TickTime + TickTime / 4</code>
<tag><c>start_timer = true | false</c></tag>
<item>
<p>Starts the <c>timer_server</c> if the parameter is
- <c>true</c> (see <seealso marker="stdlib:timer"><c>timer(3)</c></seealso>).
+ <c>true</c> (see <seeerl marker="stdlib:timer"><c>timer(3)</c></seeerl>).
This parameter is to be set to <c>true</c> in an embedded system
using this service.</p>
<p>Defaults to <c>false</c>.</p>
@@ -445,7 +460,7 @@ MaxT = TickTime + TickTime / 4</code>
</item>
<tag><c>shell_history_file_bytes = integer()</c></tag>
<item>
- <p>how many bytes the shell should remember. By default, the
+ <p>How many bytes the shell should remember. By default, the
value is set to 512kb, and the minimal value is 50kb.</p>
</item>
<tag><c>shell_history_path = string()</c></tag>
@@ -481,10 +496,10 @@ MaxT = TickTime + TickTime / 4</code>
<item><c>SrcSuffix = string()</c></item>
</list>
<p>Specifies a list of rules for use by
- <seealso marker="stdlib:filelib#find_file/2">
- <c>filelib:find_file/2</c></seealso>
- <seealso marker="stdlib:filelib#find_source/2">
- <c>filelib:find_source/2</c></seealso>
+ <seemfa marker="stdlib:filelib#find_file/2">
+ <c>filelib:find_file/2</c></seemfa>
+ <seemfa marker="stdlib:filelib#find_source/2">
+ <c>filelib:find_source/2</c></seemfa>
If this is set to some other value
than the empty list, it replaces the default rules. Rules can be
simple pairs of directory suffixes, such as <c>{"ebin",
@@ -498,8 +513,8 @@ MaxT = TickTime + TickTime / 4</code>
object is located matches <c>ObjDirSuffix</c>, then the
name created by replacing <c>ObjDirSuffix</c> with
<c>SrcDirSuffix</c> is expanded by calling
- <seealso marker="stdlib:filelib#wildcard/1">
- <c>filelib:wildcard/1</c></seealso>, and the first regular
+ <seemfa marker="stdlib:filelib#wildcard/1">
+ <c>filelib:wildcard/1</c></seemfa>, and the first regular
file found among the matches is the source file.
</p>
@@ -518,48 +533,49 @@ MaxT = TickTime + TickTime / 4</code>
parameters for Logger are not set.</p>
<taglist>
<tag><c>error_logger</c></tag>
- <item>Replaced by setting the <seealso
- marker="logger_std_h#type"><c>type</c></seealso>, and possibly
- <seealso marker="logger_std_h#file"><c>file</c></seealso> and
- <seealso marker="logger_std_h#modes"><c>modes</c></seealso>
+ <item>Replaced by setting the <seeerl
+ marker="logger_std_h#type"><c>type</c></seeerl>, and possibly
+ <seeerl marker="logger_std_h#file"><c>file</c></seeerl> and
+ <seeerl marker="logger_std_h#modes"><c>modes</c></seeerl>
parameters of the default <c>logger_std_h</c> handler. Example:
<code type="none">
erl -kernel logger '[{handler,default,logger_std_h,#{config=>#{file=>"/tmp/erlang.log"}}}]'
</code>
</item>
<tag><c>error_logger_format_depth</c></tag>
- <item>Replaced by setting the <seealso marker="logger_formatter#depth"><c>depth</c></seealso>
+ <item>Replaced by setting the <seeerl marker="logger_formatter#depth"><c>depth</c></seeerl>
parameter of the default handlers formatter. Example:
<code type="none">
erl -kernel logger '[{handler,default,logger_std_h,#{formatter=>{logger_formatter,#{legacy_header=>true,template=>[{logger_formatter,header},"\n",msg,"\n"],depth=>10}}}]'
</code>
</item>
</taglist>
- <p>See <seealso marker="logger_chapter#compatibility">Backwards
- compatibility with error_logger</seealso> for more
+ <p>See <seeguide marker="logger_chapter#compatibility">Backwards
+ compatibility with error_logger</seeguide> for more
information.</p>
</section>
<section>
<title>See Also</title>
- <p><seealso marker="app"><c>app(4)</c></seealso>,
- <seealso marker="application"><c>application(3)</c></seealso>,
- <seealso marker="code"><c>code(3)</c></seealso>,
- <seealso marker="disk_log"><c>disk_log(3)</c></seealso>,
- <seealso marker="erl_boot_server"><c>erl_boot_server(3)</c></seealso>,
- <seealso marker="erl_ddll"><c>erl_ddll(3)</c></seealso>,
- <seealso marker="file"><c>file(3)</c></seealso>,
- <seealso marker="global"><c>global(3)</c></seealso>,
- <seealso marker="global_group"><c>global_group(3)</c></seealso>,
- <seealso marker="heart"><c>heart(3)</c></seealso>,
- <seealso marker="inet"><c>inet(3)</c></seealso>,
- <seealso marker="logger"><c>logger(3)</c></seealso>,
- <seealso marker="net_kernel"><c>net_kernel(3)</c></seealso>,
- <seealso marker="os"><c>os(3)</c></seealso>,
- <seealso marker="pg2"><c>pg2(3)</c></seealso>,
- <seealso marker="rpc"><c>rpc(3)</c></seealso>,
- <seealso marker="seq_trace"><c>seq_trace(3)</c></seealso>,
- <seealso marker="user"><c>user(3)</c></seealso>,
- <seealso marker="stdlib:timer"><c>timer(3)</c></seealso></p>
+ <p><seefile marker="app"><c>app(4)</c></seefile>,
+ <seeerl marker="application"><c>application(3)</c></seeerl>,
+ <seeerl marker="code"><c>code(3)</c></seeerl>,
+ <seeerl marker="disk_log"><c>disk_log(3)</c></seeerl>,
+ <seeerl marker="erl_boot_server"><c>erl_boot_server(3)</c></seeerl>,
+ <seeerl marker="erl_ddll"><c>erl_ddll(3)</c></seeerl>,
+ <seeerl marker="file"><c>file(3)</c></seeerl>,
+ <seeerl marker="global"><c>global(3)</c></seeerl>,
+ <seeerl marker="global_group"><c>global_group(3)</c></seeerl>,
+ <seeerl marker="heart"><c>heart(3)</c></seeerl>,
+ <seeerl marker="inet"><c>inet(3)</c></seeerl>,
+ <seeerl marker="logger"><c>logger(3)</c></seeerl>,
+ <seeerl marker="net_kernel"><c>net_kernel(3)</c></seeerl>,
+ <seeerl marker="os"><c>os(3)</c></seeerl>,
+ <seeerl marker="pg"><c>pg(3)</c></seeerl>,
+ <seeerl marker="pg2"><c>pg2(3)</c></seeerl>,
+ <seeerl marker="rpc"><c>rpc(3)</c></seeerl>,
+ <seeerl marker="seq_trace"><c>seq_trace(3)</c></seeerl>,
+ <seeerl marker="user"><c>user(3)</c></seeerl>,
+ <seeerl marker="stdlib:timer"><c>timer(3)</c></seeerl></p>
</section>
</appref>
diff --git a/lib/kernel/doc/src/logger.xml b/lib/kernel/doc/src/logger.xml
index a906f0deff..801a2c1193 100644
--- a/lib/kernel/doc/src/logger.xml
+++ b/lib/kernel/doc/src/logger.xml
@@ -39,18 +39,18 @@
<description>
<p>This module implements the main API for logging in
Erlang/OTP. To create a log event, use the
- <seealso marker="#logging_API">API functions</seealso> or the
+ <seeerl marker="#logging_API">API functions</seeerl> or the
log
- <seealso marker="#macros">macros</seealso>, for example:</p>
+ <seeerl marker="#macros">macros</seeerl>, for example:</p>
<code>
?LOG_ERROR("error happened because: ~p", [Reason]). % With macro
logger:error("error happened because: ~p", [Reason]). % Without macro
</code>
<p>To configure the Logger backend,
- use <seealso marker="kernel_app#logger">Kernel configuration
- parameters</seealso>
- or <seealso marker="#configuration_API">configuration
- functions</seealso> in the Logger API.</p>
+ use <seeapp marker="kernel_app#logger">Kernel configuration
+ parameters</seeapp>
+ or <seeerl marker="#configuration_API">configuration
+ functions</seeerl> in the Logger API.</p>
<p>By default, the Kernel application installs one log handler at
system start. This handler is named <c>default</c>. It receives
@@ -61,7 +61,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<p>If you want your systems logs to be printed to a file instead,
you must configure the default handler to do so. The simplest
way is to include the following in
- your <seealso marker="config"><c>sys.config</c></seealso>:</p>
+ your <seefile marker="config"><c>sys.config</c></seefile>:</p>
<code>
[{kernel,
[{logger,
@@ -73,18 +73,18 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</p>
<list type="bulleted">
<item>the Logger facility in general, see
- the <seealso marker="logger_chapter">User's
- Guide</seealso>.</item>
+ the <seeguide marker="logger_chapter">User's
+ Guide</seeguide>.</item>
<item>how to configure Logger, see
- the <seealso marker="logger_chapter#configuration">Configuration</seealso>
+ the <seeguide marker="logger_chapter#configuration">Configuration</seeguide>
section in the User's Guide.</item>
<item>the built-in handlers,
- see <seealso marker="logger_std_h">logger_std_h</seealso> and
- <seealso marker="logger_disk_log_h">logger_disk_log_h</seealso>.</item>
+ see <seeerl marker="logger_std_h">logger_std_h</seeerl> and
+ <seeerl marker="logger_disk_log_h">logger_disk_log_h</seeerl>.</item>
<item>the built-in formatter,
- see <seealso marker="logger_formatter">logger_formatter</seealso>.</item>
+ see <seeerl marker="logger_formatter">logger_formatter</seeerl>.</item>
<item>built-in filters,
- see <seealso marker="logger_filters">logger_filters</seealso>.</item>
+ see <seeerl marker="logger_filters">logger_filters</seeerl>.</item>
</list>
<note>
@@ -126,8 +126,8 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<name name="formatter_config"/>
<desc>
<p>Configuration data for the
- formatter. See <seealso marker="logger_formatter">
- <c>logger_formatter(3)</c></seealso>
+ formatter. See <seeerl marker="logger_formatter">
+ <c>logger_formatter(3)</c></seeerl>
for an example of a formatter implementation.</p>
</desc>
</datatype>
@@ -145,7 +145,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<p>In addition to these, the following fields are
automatically inserted by Logger, values taken from the
two first parameters
- to <seealso marker="#add_handler-3"><c>add_handler/3</c></seealso>:</p>
+ to <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa>:</p>
<list>
<item><c>id => HandlerId</c></item>
<item><c>module => Module</c></item>
@@ -154,12 +154,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<p>Handler specific configuration data is inserted by the
handler callback itself, in a sub structure associated with
the field named <c>config</c>. See
- the <seealso marker="logger_std_h"><c>logger_std_h(3)</c></seealso>
- and <seealso marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seealso>
- manual pages for information about the specifc configuration
+ the <seeerl marker="logger_std_h"><c>logger_std_h(3)</c></seeerl>
+ and <seeerl marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seeerl>
+ manual pages for information about the specific configuration
for these handlers.</p>
- <p>See the <seealso marker="logger_formatter#type-config">
- <c>logger_formatter(3)</c></seealso> manual page for
+ <p>See the <seetype marker="logger_formatter#config">
+ <c>logger_formatter(3)</c></seetype> manual page for
information about the default configuration for this
formatter.</p>
</desc>
@@ -202,10 +202,10 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<p>You can add custom metadata, either by specifying a map as
the last parameter to any of the log macros or the API
functions, or by setting process metadata
- with <seealso marker="#set_process_metadata-1">
- <c>set_process_metadata/1</c></seealso>
- or <seealso marker="#update_process_metadata-1">
- <c>update_process_metadata/1</c></seealso>.</p>
+ with <seemfa marker="#set_process_metadata/1">
+ <c>set_process_metadata/1</c></seemfa>
+ or <seemfa marker="#update_process_metadata/1">
+ <c>update_process_metadata/1</c></seemfa>.</p>
<p>Logger merges all the metadata maps before forwarding the
log event to the handlers. If the same keys occur, values
from the log call overwrite process metadata, which in turn
@@ -217,22 +217,22 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<p>The value associated with this key is used by filters
for grouping log events originating from, for example,
specific functional
- areas. See <seealso marker="logger_filters#domain-2">
- <c>logger_filters:domain/2</c></seealso>
+ areas. See <seemfa marker="logger_filters#domain/2">
+ <c>logger_filters:domain/2</c></seemfa>
for a description of how this field can be used.</p>
</item>
<tag><c>report_cb</c></tag>
<item>
<p>If the log message is specified as
- a <seealso marker="#type-report"><c>report()</c></seealso>,
+ a <seetype marker="#report"><c>report()</c></seetype>,
the <c>report_cb</c> key can be associated with a fun
(report callback) that converts the report to a format
string and arguments, or directly to a string. See the
type definition
- of <seealso marker="#type-report_cb"><c>report_cb()</c></seealso>,
+ of <seetype marker="#report_cb"><c>report_cb()</c></seetype>,
and
- section <seealso marker="logger_chapter#log_message">Log
- Message</seealso> in the User's Guide for more
+ section <seeguide marker="logger_chapter#log_message">Log
+ Message</seeguide> in the User's Guide for more
information about report callbacks.</p>
</item>
</taglist>
@@ -271,10 +271,10 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<datatype>
<name name="report_cb"/>
<desc>
- <p>A fun which converts a <seealso marker="#type-report"><c>report()</c>
- </seealso> to a format string and arguments, or directly to a string.
- See section <seealso marker="logger_chapter#log_message">Log
- Message</seealso> in the User's Guide for more
+ <p>A fun which converts a <seetype marker="#report"><c>report()</c>
+ </seetype> to a format string and arguments, or directly to a string.
+ See section <seeguide marker="logger_chapter#log_message">Log
+ Message</seeguide> in the User's Guide for more
information.</p>
</desc>
</datatype>
@@ -288,8 +288,8 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<name name="timestamp"/>
<desc>
<p>A timestamp produced
- with <seealso marker="#timestamp-0">
- <c>logger:timestamp()</c></seealso>.</p>
+ with <seemfa marker="#timestamp/0">
+ <c>logger:timestamp()</c></seemfa>.</p>
</desc>
</datatype>
</datatypes>
@@ -326,7 +326,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
taken from the macro name, or from the first argument in the
case of the <c>?LOG</c> macro. Location data is added to the
metadata as described under
- the <seealso marker="#type-metadata"><c>metadata()</c></seealso>
+ the <seetype marker="#metadata"><c>metadata()</c></seetype>
type definition.</p>
<p>The call is wrapped in a case statement and will be evaluated
@@ -334,11 +334,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
level.</p>
</section>
- <section>
- <marker id="logging_API"/>
- <title>Logging API functions</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <marker id="logging_API"/>
+ <title>Logging API functions</title>
+ </fsdescription>
<func>
<name since="OTP 21.0">emergency(StringOrReport[,Metadata])</name>
<name since="OTP 21.0">emergency(Format,Args[,Metadata])</name>
@@ -346,7 +347,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<fsummary>Logs the given message as level <c>emergency</c>.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#log-2"><c>log(emergency,...)</c></seealso>.</p>
+ <seemfa marker="#log/2"><c>log(emergency,...)</c></seemfa>.</p>
</desc>
</func>
@@ -357,7 +358,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<fsummary>Logs the given message as level <c>alert</c>.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#log-2"><c>log(alert,...)</c></seealso>.</p>
+ <seemfa marker="#log/2"><c>log(alert,...)</c></seemfa>.</p>
</desc>
</func>
@@ -368,7 +369,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<fsummary>Logs the given message as level <c>critical</c>.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#log-2"><c>log(critical,...)</c></seealso>.</p>
+ <seemfa marker="#log/2"><c>log(critical,...)</c></seemfa>.</p>
</desc>
</func>
@@ -379,7 +380,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<fsummary>Logs the given message as level <c>error</c>.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#log-2"><c>log(error,...)</c></seealso>.</p>
+ <seemfa marker="#log/2"><c>log(error,...)</c></seemfa>.</p>
</desc>
</func>
@@ -390,7 +391,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<fsummary>Logs the given message as level <c>warning</c>.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#log-2"><c>log(warning,...)</c></seealso>.</p>
+ <seemfa marker="#log/2"><c>log(warning,...)</c></seemfa>.</p>
</desc>
</func>
@@ -401,7 +402,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<fsummary>Logs the given message as level <c>notice</c>.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#log-2"><c>log(notice,...)</c></seealso>.</p>
+ <seemfa marker="#log/2"><c>log(notice,...)</c></seemfa>.</p>
</desc>
</func>
@@ -412,7 +413,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<fsummary>Logs the given message as level <c>info</c>.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#log-2"><c>log(info,...)</c></seealso>.</p>
+ <seemfa marker="#log/2"><c>log(info,...)</c></seemfa>.</p>
</desc>
</func>
@@ -423,7 +424,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<fsummary>Logs the given message as level <c>debug</c>.</fsummary>
<desc>
<p>Equivalent to
- <seealso marker="#log-2"><c>log(debug,...)</c></seealso>.</p>
+ <seemfa marker="#log/2"><c>log(debug,...)</c></seemfa>.</p>
</desc>
</func>
@@ -448,11 +449,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
</funcs>
- <section>
- <marker id="configuration_API"/>
- <title>Configuration API functions</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <marker id="configuration_API"/>
+ <title>Configuration API functions</title>
+ </fsdescription>
<func>
<name name="add_handler" arity="3" since="OTP 21.0"/>
<fsummary>Add a handler with the given configuration.</fsummary>
@@ -498,10 +500,10 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</item>
</taglist>
<p>See
- section <seealso marker="logger_chapter#filters">Filters</seealso>
+ section <seeguide marker="logger_chapter#filters">Filters</seeguide>
in the User's Guide for more information about filters.</p>
<p>Some built-in filters exist. These are defined in
- <seealso marker="logger_filters"><c>logger_filters(3)</c></seealso>.</p>
+ <seeerl marker="logger_filters"><c>logger_filters(3)</c></seeerl>.</p>
</desc>
</func>
@@ -539,7 +541,7 @@ start(_, []) ->
the <c>my_app</c> application and starts the configured
handlers. The contents of the configuration use the same
rules as the
- <seealso marker="logger_chapter#handler-configuration">logger handler configuration</seealso>.
+ <seeguide marker="logger_chapter#handler-configuration">logger handler configuration</seeguide>.
</p>
<p>If the handler is meant to replace the default handler, the Kernel's
default handler have to be disabled before the new handler is added.
@@ -591,11 +593,11 @@ start(_, []) ->
forwarded to the handler part.</p>
</item>
</taglist>
- <p>See section <seealso marker="logger_chapter#filters">
- Filters</seealso> in the User's Guide for more information
+ <p>See section <seeguide marker="logger_chapter#filters">
+ Filters</seeguide> in the User's Guide for more information
about filters.</p>
<p>Some built-in filters exist. These are defined
- in <seealso marker="logger_filters"><c>logger_filters(3)</c></seealso>.</p>
+ in <seeerl marker="logger_filters"><c>logger_filters(3)</c></seeerl>.</p>
</desc>
</func>
@@ -647,8 +649,8 @@ start(_, []) ->
<desc>
<p>Look up the current configuration for the Logger proxy.</p>
<p>For more information about the proxy, see
- section <seealso marker="logger_chapter#proxy">Logger
- Proxy</seealso> in the Kernel User's Guide.</p>
+ section <seeguide marker="logger_chapter#proxy">Logger
+ Proxy</seeguide> in the Kernel User's Guide.</p>
</desc>
</func>
@@ -659,8 +661,8 @@ start(_, []) ->
<p>Look up all current module levels. Returns a list
containing one <c>{Module,Level}</c> element for each module
for which the module level was previously set
- with <seealso marker="#set_module_level-2">
- <c>set_module_level/2</c></seealso>.</p>
+ with <seemfa marker="#set_module_level/2">
+ <c>set_module_level/2</c></seemfa>.</p>
</desc>
</func>
@@ -671,8 +673,8 @@ start(_, []) ->
<p>Look up the current level for the given modules. Returns a
list containing one <c>{Module,Level}</c> element for each
of the given modules for which the module level was
- previously set with <seealso marker="#set_module_level-2">
- <c>set_module_level/2</c></seealso>.</p>
+ previously set with <seemfa marker="#set_module_level/2">
+ <c>set_module_level/2</c></seemfa>.</p>
</desc>
</func>
@@ -681,10 +683,10 @@ start(_, []) ->
<fsummary>Retrieve data set with set_process_metadata/1.</fsummary>
<desc>
<p>Retrieve data set
- with <seealso marker="#set_process_metadata-1">
- <c>set_process_metadata/1</c></seealso> or
- <seealso marker="#update_process_metadata-1">
- <c>update_process_metadata/1</c></seealso>.</p>
+ with <seemfa marker="#set_process_metadata/1">
+ <c>set_process_metadata/1</c></seemfa> or
+ <seemfa marker="#update_process_metadata/1">
+ <c>update_process_metadata/1</c></seemfa>.</p>
</desc>
</func>
@@ -730,7 +732,7 @@ start(_, []) ->
<desc>
<p>Set the log level for all the modules of the specified application.</p>
<p>This function is a convenience function that calls
- <seealso marker="#set_module_level/2">logger:set_module_level/2</seealso>
+ <seemfa marker="#set_module_level/2">logger:set_module_level/2</seemfa>
for each module associated with an application.</p>
</desc>
</func>
@@ -742,11 +744,11 @@ start(_, []) ->
<p>Set configuration data for the specified handler. This
overwrites the current handler configuration.</p>
<p>To modify the existing configuration,
- use <seealso marker="#update_handler_config-2">
- <c>update_handler_config/2</c></seealso>, or, if a more
+ use <seemfa marker="#update_handler_config/2">
+ <c>update_handler_config/2</c></seemfa>, or, if a more
complex merge is needed, read the current configuration
- with <seealso marker="#get_handler_config-1"><c>get_handler_config/1</c>
- </seealso>, then do the merge before writing the new
+ with <seemfa marker="#get_handler_config/1"><c>get_handler_config/1</c>
+ </seemfa>, then do the merge before writing the new
configuration back with this function.</p>
<p>If a key is removed compared to the current configuration,
and the key is known by Logger, the default value is used. If
@@ -783,11 +785,11 @@ start(_, []) ->
handlers in the Kernel application, unspecified data for
the <c>config</c> key is set to default values. To update
only specified data, and keep the existing configuration for
- the rest, use <seealso marker="#update_handler_config-3">
- <c>update_handler_config/3</c></seealso>.</p>
+ the rest, use <seemfa marker="#update_handler_config/3">
+ <c>update_handler_config/3</c></seemfa>.</p>
<p>See the definition of
- the <seealso marker="#type-handler_config">
- <c>handler_config()</c></seealso> type for more
+ the <seetype marker="#handler_config">
+ <c>handler_config()</c></seetype> type for more
information about the different parameters.</p>
</desc>
</func>
@@ -799,11 +801,11 @@ start(_, []) ->
<p>Set primary configuration data for Logger. This
overwrites the current configuration.</p>
<p>To modify the existing configuration,
- use <seealso marker="#update_primary_config-1">
- <c>update_primary_config/1</c></seealso>, or, if a more
+ use <seemfa marker="#update_primary_config/1">
+ <c>update_primary_config/1</c></seemfa>, or, if a more
complex merge is needed, read the current configuration
- with <seealso marker="#get_primary_config-0"><c>get_primary_config/0</c>
- </seealso>, then do the merge before writing the new
+ with <seemfa marker="#get_primary_config/0"><c>get_primary_config/0</c>
+ </seemfa>, then do the merge before writing the new
configuration back with this function.</p>
<p>If a key is removed compared to the current configuration,
the default value is used.</p>
@@ -835,15 +837,15 @@ start(_, []) ->
specified in the <c><anno>Config</anno></c> map gets default
values.</p>
<p>To modify the existing configuration,
- use <seealso marker="#update_proxy_config-1">
- <c>update_proxy_config/1</c></seealso>, or, if a more
+ use <seemfa marker="#update_proxy_config/1">
+ <c>update_proxy_config/1</c></seemfa>, or, if a more
complex merge is needed, read the current configuration
- with <seealso marker="#get_proxy_config-0"><c>get_proxy_config/0</c>
- </seealso>, then do the merge before writing the new
+ with <seemfa marker="#get_proxy_config/0"><c>get_proxy_config/0</c>
+ </seemfa>, then do the merge before writing the new
configuration back with this function.</p>
<p>For more information about the proxy, see
- section <seealso marker="logger_chapter#proxy">Logger
- Proxy</seealso> in the Kernel User's Guide.</p>
+ section <seeguide marker="logger_chapter#proxy">Logger
+ Proxy</seeguide> in the Kernel User's Guide.</p>
</desc>
</func>
@@ -868,12 +870,12 @@ start(_, []) ->
handler <c>h1</c>.</p>
<p>Debug events from other modules are still not logged.</p>
<p>To change the primary log level for Logger, use
- <seealso marker="#set_primary_config/2">
- <c>set_primary_config(level, Level)</c></seealso>.</p>
+ <seemfa marker="#set_primary_config/2">
+ <c>set_primary_config(level, Level)</c></seemfa>.</p>
<p>To change the log level for a handler, use
- <seealso marker="#set_handler_config/3">
+ <seemfa marker="#set_handler_config/3">
<c>set_handler_config(HandlerId, level, Level)</c>
- </seealso>.</p>
+ </seemfa>.</p>
<note>
<p>The originating module for a log event is only detected
if the key <c>mfa</c> exists in the metadata, and is
@@ -901,8 +903,8 @@ start(_, []) ->
overwrite values from the location data.</p>
<p>Subsequent calls to this function overwrites previous data
set. To update existing data instead of overwriting it,
- see <seealso marker="#update_process_metadata-1">
- <c>update_process_metadata/1</c></seealso>.</p>
+ see <seemfa marker="#update_process_metadata/1">
+ <c>update_process_metadata/1</c></seemfa>.</p>
</desc>
</func>
@@ -911,8 +913,8 @@ start(_, []) ->
<fsummary>Unset the log level for all modules in the specified application.</fsummary>
<desc>
<p>Unset the log level for all the modules of the specified application.</p>
- <p>This function is a convinience function that calls
- <seealso marker="#unset_module_level/1">logger:unset_module_level/2</seealso>
+ <p>This function is a utility function that calls
+ <seemfa marker="#unset_module_level/1">logger:unset_module_level/2</seemfa>
for each module associated with an application.</p>
</desc>
</func>
@@ -941,10 +943,10 @@ start(_, []) ->
<fsummary>Delete data set with set_process_metadata/1.</fsummary>
<desc>
<p>Delete data set
- with <seealso marker="#set_process_metadata-1">
- <c>set_process_metadata/1</c></seealso> or
- <seealso marker="#update_process_metadata-1">
- <c>update_process_metadata/1</c></seealso>.</p>
+ with <seemfa marker="#set_process_metadata/1">
+ <c>set_process_metadata/1</c></seemfa> or
+ <seemfa marker="#update_process_metadata/1">
+ <c>update_process_metadata/1</c></seemfa>.</p>
</desc>
</func>
@@ -958,8 +960,8 @@ start(_, []) ->
<p>To overwrite the existing configuration without any merge,
use</p>
<pre>
-<seealso marker="#set_handler_config-3">set_handler_config(HandlerId, formatter,
- {FormatterModule, FormatterConfig})</seealso>.</pre>
+<seemfa marker="#set_handler_config/3">set_handler_config(HandlerId, formatter,
+ {FormatterModule, FormatterConfig})</seemfa>.</pre>
</desc>
</func>
@@ -970,7 +972,7 @@ start(_, []) ->
<p>Update the formatter configuration for the specified handler.</p>
<p>This is equivalent to</p>
<pre>
-<seealso marker="#update_formatter_config-2">update_formatter_config(<anno>HandlerId</anno>, #{<anno>Key</anno> => <anno>Value</anno>})</seealso></pre>
+<seemfa marker="#update_formatter_config/2">update_formatter_config(<anno>HandlerId</anno>, #{<anno>Key</anno> => <anno>Value</anno>})</seemfa></pre>
</desc>
</func>
@@ -985,8 +987,8 @@ start(_, []) ->
logger:set_handler_config(HandlerId, maps:merge(Old, Config)).
</code>
<p>To overwrite the existing configuration without any merge,
- use <seealso marker="#set_handler_config-2"><c>set_handler_config/2</c>
- </seealso>.</p>
+ use <seemfa marker="#set_handler_config/2"><c>set_handler_config/2</c>
+ </seemfa>.</p>
</desc>
</func>
@@ -1017,11 +1019,11 @@ logger:set_handler_config(HandlerId, maps:merge(Old, Config)).
handlers in the Kernel application, unspecified data for
the <c>config</c> key is not changed. To reset unspecified
data to default values,
- use <seealso marker="#set_handler_config-3">
- <c>set_handler_config/3</c></seealso>.</p>
+ use <seemfa marker="#set_handler_config/3">
+ <c>set_handler_config/3</c></seemfa>.</p>
<p>See the definition of
- the <seealso marker="#type-handler_config">
- <c>handler_config()</c></seealso> type for more
+ the <seetype marker="#handler_config">
+ <c>handler_config()</c></seetype> type for more
information about the different parameters.</p>
</desc>
</func>
@@ -1037,8 +1039,8 @@ Old = logger:get_primary_config(),
logger:set_primary_config(maps:merge(Old, Config)).
</code>
<p>To overwrite the existing configuration without any merge,
- use <seealso marker="#set_primary_config-1"><c>set_primary_config/1</c>
- </seealso>.</p>
+ use <seemfa marker="#set_primary_config/1"><c>set_primary_config/1</c>
+ </seemfa>.</p>
</desc>
</func>
@@ -1055,9 +1057,9 @@ logger:set_primary_config(maps:merge(Old, Config)).
logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</code>
<p>If no process metadata exists, the function behaves as
- <seealso marker="#set_process_metadata-1">
+ <seemfa marker="#set_process_metadata/1">
<c>set_process_metadata/1</c>
- </seealso>.</p>
+ </seemfa>.</p>
</desc>
</func>
@@ -1072,20 +1074,21 @@ Old = logger:get_proxy_config(),
logger:set_proxy_config(maps:merge(Old, Config)).
</code>
<p>To overwrite the existing configuration without any merge,
- use <seealso marker="#set_proxy_config-1"><c>set_proxy_config/1</c>
- </seealso>.</p>
+ use <seemfa marker="#set_proxy_config/1"><c>set_proxy_config/1</c>
+ </seemfa>.</p>
<p>For more information about the proxy, see
- section <seealso marker="logger_chapter#proxy">Logger
- Proxy</seealso> in the Kernel User's Guide.</p>
+ section <seeguide marker="logger_chapter#proxy">Logger
+ Proxy</seeguide> in the Kernel User's Guide.</p>
</desc>
</func>
</funcs>
- <section>
- <marker id="misc_API"/>
- <title>Miscellaneous API functions</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <marker id="misc_API"/>
+ <title>Miscellaneous API functions</title>
+ </fsdescription>
<func>
<name name="compare_levels" arity="2" since="OTP 21.0"/>
<fsummary>Compare the severity of two log levels.</fsummary>
@@ -1103,11 +1106,11 @@ logger:set_proxy_config(maps:merge(Old, Config)).
<desc>
<p>Convert a log message on report form to <c>{Format,
Args}</c>. This is the default report callback used
- by <seealso marker="logger_formatter">
- <c>logger_formatter(3)</c></seealso> when no custom report
+ by <seeerl marker="logger_formatter">
+ <c>logger_formatter(3)</c></seeerl> when no custom report
callback is found. See
- section <seealso marker="logger_chapter#log_message">Log
- Message</seealso> in the Kernel User's Guide for
+ section <seeguide marker="logger_chapter#log_message">Log
+ Message</seeguide> in the Kernel User's Guide for
information about report callbacks and valid forms of log
messages.</p>
<p>The function produces lines of <c>Key: Value</c> from
@@ -1125,8 +1128,8 @@ logger:set_proxy_config(maps:merge(Old, Config)).
<desc>
<p>Return a timestamp that can be inserted as the <c>time</c>
field in the meta data for a log event. It is produced with
- <seealso marker="kernel:os#system_time-1">
- <c>os:system_time(microsecond)</c></seealso>.</p>
+ <seemfa marker="kernel:os#system_time/1">
+ <c>os:system_time(microsecond)</c></seemfa>.</p>
<p>Notice that Logger automatically inserts a timestamp in the
meta data unless it already exists. This function is
exported for the rare case when the timestamp must be taken
@@ -1137,26 +1140,27 @@ logger:set_proxy_config(maps:merge(Old, Config)).
</funcs>
- <section>
- <marker id="handler_callback_functions"/>
- <title>Handler Callback Functions</title>
- <p>The following functions are to be exported from a handler
- callback module.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <marker id="handler_callback_functions"/>
+ <title>Handler Callback Functions</title>
+ <p>The following functions are to be exported from a handler
+ callback module.</p>
+ </fsdescription>
<func>
<name since="OTP 21.0">HModule:adding_handler(Config1) -> {ok, Config2} | {error,
Reason}</name>
<fsummary>An instance of this handler is about to be added.</fsummary>
<type>
<v>Config1 = Config2 =
- <seealso marker="#type-handler_config">handler_config()</seealso></v>
+ <seetype marker="#handler_config">handler_config()</seetype></v>
<v>Reason = term()</v>
</type>
<desc>
<p>This callback function is optional.</p>
- <p>The function is called on a temporary process when an new
+ <p>The function is called on a temporary process when a new
handler is about to be added. The purpose is to verify the
configuration and initiate all resources needed by the
handler.</p>
@@ -1177,7 +1181,7 @@ logger:set_proxy_config(maps:merge(Old, Config)).
<type>
<v>SetOrUpdate = set | update</v>
<v>OldConfig = NewConfig = Config =
- <seealso marker="#type-handler_config">handler_config()</seealso></v>
+ <seetype marker="#handler_config">handler_config()</seetype></v>
<v>Reason = term()</v>
</type>
<desc>
@@ -1191,11 +1195,11 @@ logger:set_proxy_config(maps:merge(Old, Config)).
in <c>OldConfig</c>.</p>
<p><c>SetOrUpdate</c> has the value <c>set</c> if the
configuration change originates from a call to
- <seealso marker="#set_handler_config-2">
- <c>set_handler_config/2,3</c></seealso>, and <c>update</c>
- if it originates from <seealso marker="#update_handler_config-2">
- <c>update_handler_config/2,3</c></seealso>. The handler can
- use this parameteter to decide how to update the value of
+ <seemfa marker="#set_handler_config/2">
+ <c>set_handler_config/2,3</c></seemfa>, and <c>update</c>
+ if it originates from <seemfa marker="#update_handler_config/2">
+ <c>update_handler_config/2,3</c></seemfa>. The handler can
+ use this parameter to decide how to update the value of
the <c>config</c> field, that is, the handler specific
configuration data. Typically, if <c>SetOrUpdate</c>
equals <c>set</c>, values that are not specified must be
@@ -1214,15 +1218,15 @@ logger:set_proxy_config(maps:merge(Old, Config)).
<fsummary>Remove internal data from configuration.</fsummary>
<type>
<v>Config = FilteredConfig =
- <seealso marker="#type-handler_config">handler_config()</seealso></v>
+ <seetype marker="#handler_config">handler_config()</seetype></v>
</type>
<desc>
<p>This callback function is optional.</p>
<p>The function is called when one of the Logger API functions
for fetching the handler configuration is called, for
example
- <seealso marker="#get_handler_config-1">
- <c>logger:get_handler_config/1</c></seealso>.</p>
+ <seemfa marker="#get_handler_config/1">
+ <c>logger:get_handler_config/1</c></seemfa>.</p>
<p>It allows the handler to remove internal data fields from
its configuration data before it is returned to the
caller.</p>
@@ -1234,9 +1238,9 @@ logger:set_proxy_config(maps:merge(Old, Config)).
<fsummary>Log the given log event.</fsummary>
<type>
<v>LogEvent =
- <seealso marker="#type-log_event">log_event()</seealso></v>
+ <seetype marker="#log_event">log_event()</seetype></v>
<v>Config =
- <seealso marker="#type-handler_config">handler_config()</seealso></v>
+ <seetype marker="#handler_config">handler_config()</seetype></v>
</type>
<desc>
<p>This callback function is mandatory.</p>
@@ -1257,7 +1261,7 @@ logger:set_proxy_config(maps:merge(Old, Config)).
<fsummary>The given handler is about to be removed.</fsummary>
<type>
<v>Config =
- <seealso marker="#type-handler_config">handler_config()</seealso></v>
+ <seetype marker="#handler_config">handler_config()</seetype></v>
</type>
<desc>
<p>This callback function is optional.</p>
@@ -1272,20 +1276,21 @@ logger:set_proxy_config(maps:merge(Old, Config)).
</funcs>
- <section>
- <marker id="formatter_callback_functions"/>
- <title>Formatter Callback Functions</title>
- <p>The following functions are to be exported from a formatter
- callback module.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <marker id="formatter_callback_functions"/>
+ <title>Formatter Callback Functions</title>
+ <p>The following functions are to be exported from a formatter
+ callback module.</p>
+ </fsdescription>
<func>
<name since="OTP 21.0">FModule:check_config(FConfig) -> ok | {error, Reason}</name>
<fsummary>Validate the given formatter configuration.</fsummary>
<type>
<v>FConfig =
- <seealso marker="#type-formatter_config">formatter_config()</seealso></v>
+ <seetype marker="#formatter_config">formatter_config()</seetype></v>
<v>Reason = term()</v>
</type>
<desc>
@@ -1296,17 +1301,17 @@ logger:set_proxy_config(maps:merge(Old, Config)).
is correct, and <c>{error,Reason}</c> if it is faulty.</p>
<p>The following Logger API functions can trigger this callback:</p>
<list>
- <item><seealso marker="logger#add_handler-3">
- <c>logger:add_handler/3</c></seealso></item>
- <item><seealso marker="logger#set_handler_config-2">
- <c>logger:set_handler_config/2,3</c></seealso></item>
- <item><seealso marker="logger#update_handler_config-2">
- <c>logger:update_handler_config/2,3</c></seealso></item>
- <item><seealso marker="logger#update_formatter_config-2">
- <c>logger:update_formatter_config/2</c></seealso></item>
+ <item><seemfa marker="logger#add_handler/3">
+ <c>logger:add_handler/3</c></seemfa></item>
+ <item><seemfa marker="logger#set_handler_config/2">
+ <c>logger:set_handler_config/2,3</c></seemfa></item>
+ <item><seemfa marker="logger#update_handler_config/2">
+ <c>logger:update_handler_config/2,3</c></seemfa></item>
+ <item><seemfa marker="logger#update_formatter_config/2">
+ <c>logger:update_formatter_config/2</c></seemfa></item>
</list>
- <p>See <seealso marker="logger_formatter">
- <c>logger_formatter(3)</c></seealso>
+ <p>See <seeerl marker="logger_formatter">
+ <c>logger_formatter(3)</c></seeerl>
for an example implementation. <c>logger_formatter</c> is the
default formatter used by Logger.</p>
</desc>
@@ -1316,21 +1321,21 @@ logger:set_proxy_config(maps:merge(Old, Config)).
<fsummary>Format the given log event.</fsummary>
<type>
<v>LogEvent =
- <seealso marker="#type-log_event">log_event()</seealso></v>
+ <seetype marker="#log_event">log_event()</seetype></v>
<v>FConfig =
- <seealso marker="#type-formatter_config">formatter_config()</seealso></v>
+ <seetype marker="#formatter_config">formatter_config()</seetype></v>
<v>FormattedLogEntry =
- <seealso marker="unicode#type-chardata">unicode:chardata()</seealso></v>
+ <seetype marker="unicode#chardata">unicode:chardata()</seetype></v>
</type>
<desc>
<p>This callback function is mandatory.</p>
<p>The function can be called by a log handler to convert a
log event term to a printable string. The returned value
can, for example, be printed as a log entry to the console
- or a file using <seealso marker="stdlib:io#put_chars-1">
- <c>io:put_chars/1,2</c></seealso>.</p>
- <p>See <seealso marker="logger_formatter">
- <c>logger_formatter(3)</c></seealso>
+ or a file using <seemfa marker="stdlib:io#put_chars/1">
+ <c>io:put_chars/1,2</c></seemfa>.</p>
+ <p>See <seeerl marker="logger_formatter">
+ <c>logger_formatter(3)</c></seeerl>
for an example implementation. <c>logger_formatter</c> is the
default formatter used by Logger.</p>
</desc>
@@ -1340,14 +1345,14 @@ logger:set_proxy_config(maps:merge(Old, Config)).
<section>
<title>See Also</title>
<p>
- <seealso marker="config"><c>config(4)</c></seealso>,
- <seealso marker="erts:erlang"><c>erlang(3)</c></seealso>,
- <seealso marker="stdlib:io"><c>io(3)</c></seealso>,
- <seealso marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seealso>,
- <seealso marker="logger_filters"><c>logger_filters(3)</c></seealso>,
- <seealso marker="logger_formatter"><c>logger_formatter(3)</c></seealso>,
- <seealso marker="logger_std_h"><c>logger_std_h(3)</c></seealso>,
- <seealso marker="stdlib:unicode"><c>unicode(3)</c></seealso>
+ <seefile marker="config"><c>config(4)</c></seefile>,
+ <seeerl marker="erts:erlang"><c>erlang(3)</c></seeerl>,
+ <seeerl marker="stdlib:io"><c>io(3)</c></seeerl>,
+ <seeerl marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seeerl>,
+ <seeerl marker="logger_filters"><c>logger_filters(3)</c></seeerl>,
+ <seeerl marker="logger_formatter"><c>logger_formatter(3)</c></seeerl>,
+ <seeerl marker="logger_std_h"><c>logger_std_h(3)</c></seeerl>,
+ <seeerl marker="stdlib:unicode"><c>unicode(3)</c></seeerl>
</p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/logger_chapter.xml b/lib/kernel/doc/src/logger_chapter.xml
index 7782227157..b082607751 100644
--- a/lib/kernel/doc/src/logger_chapter.xml
+++ b/lib/kernel/doc/src/logger_chapter.xml
@@ -43,7 +43,7 @@
terminal.</p>
<p>You can also configure the system so that the default handler
prints log events to a single file, or to a set of wrap logs
- via <seealso marker="disk_log"><c>disk_log</c></seealso>.</p>
+ via <seeerl marker="disk_log"><c>disk_log</c></seeerl>.</p>
<p>By configuration, you can also modify or disable the default
handler, replace it by a custom handler, and install additional
handlers.</p>
@@ -80,8 +80,8 @@
log level check if the integer value of its log level is less
than or equal to the currently configured log level. That is,
the check passes if the event is equally or more severe than the
- configured level. See section <seealso marker="#log_level">Log
- Level</seealso> for a listing and description of all log
+ configured level. See section <seeguide marker="#log_level">Log
+ Level</seeguide> for a listing and description of all log
levels.</p>
<p>The primary log level can be overridden by a log level
configured per module. This is to, for instance, allow more
@@ -90,13 +90,13 @@
than the log level check provides. A filter function can stop or
pass a log event, based on any of the event's contents. It can
also modify all parts of the log event. See section
- <seealso marker="#filters">Filters</seealso> for more
+ <seeguide marker="#filters">Filters</seeguide> for more
details.</p>
<p>If a log event passes through all primary filters and all
handler filters for a specific handler, Logger forwards the
event to the <em>handler callback</em>. The handler formats and
prints the event to its destination. See
- section <seealso marker="#handlers">Handlers</seealso> for more
+ section <seeguide marker="#handlers">Handlers</seeguide> for more
details.</p>
<p>Everything up to and including the call to the handler
callbacks is executed on the client process, that is, the
@@ -109,11 +109,11 @@
<marker id="logger_api"/>
<title>Logger API</title>
<p>The API for logging consists of a set
- of <seealso marker="logger#macros">macros</seealso>, and a set
+ of <seeerl marker="logger#macros">macros</seeerl>, and a set
of functions on the form <c>logger:Level/1,2,3</c>, which are
all shortcuts
- for <seealso marker="logger#log-2">
- <c>logger:log(Level,Arg1[,Arg2[,Arg3]])</c></seealso>.</p>
+ for <seemfa marker="logger#log/2">
+ <c>logger:log(Level,Arg1[,Arg2[,Arg3]])</c></seemfa>.</p>
<p>The macros are defined in <c>logger.hrl</c>, which is included
in a module with the directive</p>
<code>-include_lib("kernel/include/logger.hrl").</code>
@@ -183,8 +183,8 @@
<p>Notice that the integer value is only used internally in
Logger. In the API, you must always use the atom. To compare
the severity of two log levels,
- use <seealso marker="logger#compare_levels-2">
- <c>logger:compare_levels/2</c></seealso>.</p>
+ use <seemfa marker="logger#compare_levels/2">
+ <c>logger:compare_levels/2</c></seemfa>.</p>
</section>
<section>
<marker id="log_message"/>
@@ -194,9 +194,9 @@
two separate parameters in the Logger API), a string or a
report. The latter, which is either a map or a key-value list,
can be accompanied by a <em>report callback</em> specified in
- the log event's <seealso marker="#metadata">metadata</seealso>.
+ the log event's <seeguide marker="#metadata">metadata</seeguide>.
The report callback is a convenience function that
- the <seealso marker="#formatters">formatter</seealso> can use
+ the <seeguide marker="#formatters">formatter</seeguide> can use
to convert the report to a format string and arguments, or
directly to a string. The
formatter can also use its own conversion function, if no
@@ -205,11 +205,11 @@
<p>The report callback must be a fun with one or two
arguments. If it takes one argument, this is the report
itself, and the fun returns a format string and arguments:</p>
- <pre>fun((<seealso marker="logger#type-report"><c>logger:report()</c></seealso>) -> {<seealso marker="stdlib:io#type-format"><c>io:format()</c></seealso>,[term()]})</pre>
+ <pre>fun((<seetype marker="logger#report"><c>logger:report()</c></seetype>) -> {<seetype marker="stdlib:io#format"><c>io:format()</c></seetype>,[term()]})</pre>
<p>If it takes two arguments, the first is the report, and the
second is a map containing extra data that allows direct
coversion to a string:</p>
- <pre>fun((<seealso marker="logger#type-report"><c>logger:report()</c></seealso>,<seealso marker="logger#type-report_cb_config"><c>logger:report_cb_config()</c></seealso>) -> <seealso marker="stdlib:unicode#type-chardata"><c>unicode:chardata()</c></seealso>)
+ <pre>fun((<seetype marker="logger#report"><c>logger:report()</c></seetype>,<seetype marker="logger#report_cb_config"><c>logger:report_cb_config()</c></seetype>) -> <seetype marker="stdlib:unicode#chardata"><c>unicode:chardata()</c></seetype>)
</pre>
<p>The fun must obey the <c>depth</c> and <c>chars_limit</c>
parameters provided in the second argument, as the formatter
@@ -242,10 +242,10 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<tag>Set process metadata</tag>
<item>
<p>Process metadata is set and updated
- with <seealso marker="logger#set_process_metadata-1">
- <c>logger:set_process_metadata/1</c></seealso>
- and <seealso marker="logger#update_process_metadata-1">
- <c>logger:update_process_metadata/1</c></seealso>,
+ with <seemfa marker="logger#set_process_metadata/1">
+ <c>logger:set_process_metadata/1</c></seemfa>
+ and <seemfa marker="logger#update_process_metadata/1">
+ <c>logger:update_process_metadata/1</c></seemfa>,
respectively. This metadata applies to the process on
which these calls are made, and Logger adds the metadata
to all log events issued on that process.</p>
@@ -259,8 +259,8 @@ logger:debug(#{got => connection_request, id => Id, state => State},
</item>
</taglist>
<p>See the description of
- the <seealso marker="logger#type-metadata">
- <c>logger:metadata()</c></seealso> type for information
+ the <seetype marker="logger#metadata">
+ <c>logger:metadata()</c></seetype> type for information
about which default keys Logger inserts, and how the different
metadata maps are merged.</p>
</section>
@@ -279,8 +279,8 @@ logger:debug(#{got => connection_request, id => Id, state => State},
and <c>Extra</c> is any term. When applying the filter, Logger
calls the function with the log event as the first argument,
and the value of <c>Extra</c> as the second
- argument. See <seealso marker="logger#type-filter">
- <c>logger:filter()</c></seealso> for type definitions.</p>
+ argument. See <seetype marker="logger#filter">
+ <c>logger:filter()</c></seetype> for type definitions.</p>
<p>The filter function can return <c>stop</c>, <c>ignore</c> or
the (possibly modified) log event.</p>
<p>If <c>stop</c> is returned, the log event is immediately
@@ -306,58 +306,58 @@ logger:debug(#{got => connection_request, id => Id, state => State},
callback. If <c>filter_default</c> is set to <c>stop</c>, Logger
discards such events.</p>
<p>Primary filters are added
- with <seealso marker="logger#add_primary_filter-2">
- <c>logger:add_primary_filter/2</c></seealso>
+ with <seemfa marker="logger#add_primary_filter/2">
+ <c>logger:add_primary_filter/2</c></seemfa>
and removed
- with <seealso marker="logger#remove_primary_filter-1">
- <c>logger:remove_primary_filter/1</c></seealso>. They can also
+ with <seemfa marker="logger#remove_primary_filter/1">
+ <c>logger:remove_primary_filter/1</c></seemfa>. They can also
be added at system start via the Kernel configuration
- parameter <seealso marker="#logger_parameter"><c>logger</c></seealso>.</p>
+ parameter <seeguide marker="#logger_parameter"><c>logger</c></seeguide>.</p>
<p>Handler filters are added
- with <seealso marker="logger#add_handler_filter-3">
- <c>logger:add_handler_filter/3</c></seealso>
+ with <seemfa marker="logger#add_handler_filter/3">
+ <c>logger:add_handler_filter/3</c></seemfa>
and removed
- with <seealso marker="logger#remove_handler_filter-2">
- <c>logger:remove_handler_filter/2</c></seealso>. They can also
+ with <seemfa marker="logger#remove_handler_filter/2">
+ <c>logger:remove_handler_filter/2</c></seemfa>. They can also
be specified directly in the configuration when adding a handler
- with <seealso marker="logger#add_handler/3">
- <c>logger:add_handler/3</c></seealso>
+ with <seemfa marker="logger#add_handler/3">
+ <c>logger:add_handler/3</c></seemfa>
or via the Kernel configuration
- parameter <seealso marker="#logger_parameter"><c>logger</c></seealso>.</p>
+ parameter <seeguide marker="#logger_parameter"><c>logger</c></seeguide>.</p>
<p>To see which filters are currently installed in the system,
- use <seealso marker="logger#get_config-0">
- <c>logger:get_config/0</c></seealso>,
- or <seealso marker="logger#get_primary_config-0">
- <c>logger:get_primary_config/0</c></seealso>
- and <seealso marker="logger#get_handler_config-1">
- <c>logger:get_handler_config/1</c></seealso>. Filters are
+ use <seemfa marker="logger#get_config/0">
+ <c>logger:get_config/0</c></seemfa>,
+ or <seemfa marker="logger#get_primary_config/0">
+ <c>logger:get_primary_config/0</c></seemfa>
+ and <seemfa marker="logger#get_handler_config/1">
+ <c>logger:get_handler_config/1</c></seemfa>. Filters are
listed in the order they are applied, that is, the first
filter in the list is applied first, and so on.</p>
<p>For convenience, the following built-in filters exist:</p>
<taglist>
- <tag><seealso marker="logger_filters#domain-2">
- <c>logger_filters:domain/2</c></seealso></tag>
+ <tag><seemfa marker="logger_filters#domain/2">
+ <c>logger_filters:domain/2</c></seemfa></tag>
<item>
<p>Provides a way of filtering log events based on a
<c>domain</c> field in <c>Metadata</c>.</p>
</item>
- <tag><seealso marker="logger_filters#level-2">
- <c>logger_filters:level/2</c></seealso></tag>
+ <tag><seemfa marker="logger_filters#level/2">
+ <c>logger_filters:level/2</c></seemfa></tag>
<item>
<p>Provides a way of filtering log events based on the log
level.</p>
</item>
- <tag><seealso marker="logger_filters#progress-2">
- <c>logger_filters:progress/2</c></seealso></tag>
+ <tag><seemfa marker="logger_filters#progress/2">
+ <c>logger_filters:progress/2</c></seemfa></tag>
<item>
<p>Stops or allows progress reports from <c>supervisor</c>
and <c>application_controller</c>.</p>
</item>
- <tag><seealso marker="logger_filters#remote_gl-2">
- <c>logger_filters:remote_gl/2</c></seealso></tag>
+ <tag><seemfa marker="logger_filters#remote_gl/2">
+ <c>logger_filters:remote_gl/2</c></seemfa></tag>
<item>
<p>Stops or allows log events originating from a process
that has its group leader on a remote node.</p>
@@ -371,7 +371,7 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<p>A handler is defined as a module exporting at least the
following callback function:</p>
- <pre><seealso marker="logger#HModule:log-2">log(LogEvent, Config) -> void()</seealso></pre>
+ <pre><seemfa marker="logger#HModule:log/2">log(LogEvent, Config) -> void()</seemfa></pre>
<p>This function is called when a log event has passed through all
primary filters, and all handler filters attached to the handler
@@ -389,8 +389,8 @@ logger:debug(#{got => connection_request, id => Id, state => State},
handler module can export the optional callback
functions <c>adding_handler/1</c>, <c>changing_config/3</c>,
<c>filter_config/1</c>, and <c>removing_handler/1</c>. See
- section <seealso marker="logger#handler_callback_functions">Handler
- Callback Functions</seealso> in the logger(3) manual page for
+ section <seeerl marker="logger#handler_callback_functions">Handler
+ Callback Functions</seeerl> in the logger(3) manual page for
more information about these function.</p>
<p>The following built-in handlers exist:</p>
@@ -406,7 +406,7 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<tag><c>logger_disk_log_h</c></tag>
<item>
<p>This handler behaves much like <c>logger_std_h</c>, except it uses
- <seealso marker="disk_log"><c>disk_log</c></seealso> as its
+ <seeerl marker="disk_log"><c>disk_log</c></seeerl> as its
destination.</p>
</item>
@@ -416,8 +416,8 @@ logger:debug(#{got => connection_request, id => Id, state => State},
only. It is not started by default, but will be
automatically started the first time an <c>error_logger</c>
event handler is added
- with <seealso marker="error_logger#add_report_handler-1">
- <c>error_logger:add_report_handler/1,2</c></seealso>.</p>
+ with <seemfa marker="error_logger#add_report_handler/1">
+ <c>error_logger:add_report_handler/1,2</c></seemfa>.</p>
<p>The old <c>error_logger</c> event handlers in STDLIB and
SASL still exist, but they are not added by Erlang/OTP 21.0
@@ -434,31 +434,31 @@ logger:debug(#{got => connection_request, id => Id, state => State},
handler's destination. The handler callback receives the
formatter information as part of the handler configuration,
which is passed as the second argument
- to <seealso marker="logger#HModule:log-2">
- <c>HModule:log/2</c></seealso>.</p>
+ to <seemfa marker="logger#HModule:log/2">
+ <c>HModule:log/2</c></seemfa>.</p>
<p>The formatter information consist of a formatter
module, <c>FModule</c> and its
configuration, <c>FConfig</c>. <c>FModule</c> must export the
following function, which can be called by the handler:</p>
- <pre><seealso marker="logger#FModule:format-2">format(LogEvent,FConfig)
- -> FormattedLogEntry</seealso></pre>
+ <pre><seemfa marker="logger#FModule:format/2">format(LogEvent,FConfig)
+ -> FormattedLogEntry</seemfa></pre>
<p>The formatter information for a handler is set as a part of its
configuration when the handler is added. It can also be changed
during runtime
- with <seealso marker="logger#set_handler_config-3">
+ with <seemfa marker="logger#set_handler_config/3">
<c>logger:set_handler_config(HandlerId,formatter,{FModule,FConfig})</c>
- </seealso>, which overwrites the current formatter information,
- or with <seealso marker="logger#update_formatter_config-2">
- <c>logger:update_formatter_config/2,3</c></seealso>, which
+ </seemfa>, which overwrites the current formatter information,
+ or with <seemfa marker="logger#update_formatter_config/2">
+ <c>logger:update_formatter_config/2,3</c></seemfa>, which
only modifies the formatter configuration.</p>
<p>If the formatter module exports the optional callback
- function <seealso marker="logger#FModule:check_config-1">
- <c>check_config(FConfig)</c></seealso>, Logger calls this
+ function <seemfa marker="logger#FModule:check_config/1">
+ <c>check_config(FConfig)</c></seemfa>, Logger calls this
function when the formatter information is set or modified, to
verify the validity of the formatter configuration.</p>
<p>If no formatter information is specified for a handler, Logger
uses <c>logger_formatter</c> as default. See
- the <seealso marker="logger_formatter"><c>logger_formatter(3)</c></seealso>
+ the <seeerl marker="logger_formatter"><c>logger_formatter(3)</c></seeerl>
manual page for more information about this module.</p>
</section>
@@ -468,71 +468,71 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<p>At system start, Logger is configured through Kernel
configuration parameters. The parameters that apply to Logger
are described in
- section <seealso marker="#kernel_config_params">Kernel
- Configuration Parameters</seealso>. Examples are found in
- section <seealso marker="#config_examples">Configuration
- Examples</seealso>.</p>
+ section <seeguide marker="#kernel_config_params">Kernel
+ Configuration Parameters</seeguide>. Examples are found in
+ section <seeguide marker="#config_examples">Configuration
+ Examples</seeguide>.</p>
<p>During runtime, Logger configuration is changed via API
functions. See
- section <seealso marker="logger#configuration_API">Configuration
- API Functions</seealso> in the <c>logger(3)</c> manual page.</p>
+ section <seeerl marker="logger#configuration_API">Configuration
+ API Functions</seeerl> in the <c>logger(3)</c> manual page.</p>
<section>
<title>Primary Logger Configuration</title>
<p>Logger API functions that apply to the primary Logger
configuration are:</p>
<list>
- <item><seealso marker="logger#get_primary_config-0">
- <c>get_primary_config/0</c></seealso></item>
- <item><seealso marker="logger#set_primary_config-1">
- <c>set_primary_config/1,2</c></seealso></item>
- <item><seealso marker="logger#update_primary_config-1">
- <c>update_primary_config/1</c></seealso></item>
- <item><seealso marker="logger#add_primary_filter-2">
- <c>add_primary_filter/2</c></seealso></item>
- <item><seealso marker="logger#remove_primary_filter-1">
- <c>remove_primary_filter/1</c></seealso></item>
+ <item><seemfa marker="logger#get_primary_config/0">
+ <c>get_primary_config/0</c></seemfa></item>
+ <item><seemfa marker="logger#set_primary_config/1">
+ <c>set_primary_config/1,2</c></seemfa></item>
+ <item><seemfa marker="logger#update_primary_config/1">
+ <c>update_primary_config/1</c></seemfa></item>
+ <item><seemfa marker="logger#add_primary_filter/2">
+ <c>add_primary_filter/2</c></seemfa></item>
+ <item><seemfa marker="logger#remove_primary_filter/1">
+ <c>remove_primary_filter/1</c></seemfa></item>
</list>
<p>The primary Logger configuration is a map with the following
keys:</p>
<taglist>
<tag><marker id="primary_level"/>
- <c>level = </c><seealso marker="logger#type-level">
- <c>logger:level()</c></seealso><c> | all | none</c></tag>
+ <c>level = </c><seetype marker="logger#level">
+ <c>logger:level()</c></seetype><c> | all | none</c></tag>
<item>
<p>Specifies the primary log level, that is, log event that
are equally or more severe than this level, are forwarded
to the primary filters. Less severe log events are
immediately discarded.</p>
- <p>See section <seealso marker="#log_level">Log
- Level</seealso> for a listing and description of
+ <p>See section <seeguide marker="#log_level">Log
+ Level</seeguide> for a listing and description of
possible log levels.</p>
<p>The initial value of this option is set by the Kernel
- configuration parameter <seealso marker="#logger_level">
- <c>logger_level</c></seealso>. It is changed during
- runtime with <seealso marker="logger#set_primary_config-2">
- <c>logger:set_primary_config(level,Level)</c></seealso>.</p>
+ configuration parameter <seeguide marker="#logger_level">
+ <c>logger_level</c></seeguide>. It is changed during
+ runtime with <seemfa marker="logger#set_primary_config/2">
+ <c>logger:set_primary_config(level,Level)</c></seemfa>.</p>
<p>Defaults to <c>notice</c>.</p>
</item>
<tag><c>filters = [{FilterId,Filter}]</c></tag>
<item>
<p>Specifies the primary filters.</p>
<list>
- <item><c>FilterId = </c><seealso marker="logger#type-filter_id">
- <c>logger:filter_id()</c></seealso></item>
- <item><c>Filter = </c><seealso marker="logger#type-filter">
- <c>logger:filter()</c></seealso></item>
+ <item><c>FilterId = </c><seetype marker="logger#filter_id">
+ <c>logger:filter_id()</c></seetype></item>
+ <item><c>Filter = </c><seetype marker="logger#filter">
+ <c>logger:filter()</c></seetype></item>
</list>
<p>The initial value of this option is set by the Kernel
configuration
- parameter <seealso marker="#logger_parameter"><c>logger</c></seealso>.
+ parameter <seeguide marker="#logger_parameter"><c>logger</c></seeguide>.
During runtime, primary filters are added and removed with
- <seealso marker="logger#add_primary_filter-2">
- <c>logger:add_primary_filter/2</c></seealso> and
- <seealso marker="logger#remove_primary_filter-1">
- <c>logger:remove_primary_filter/1</c></seealso>,
+ <seemfa marker="logger#add_primary_filter/2">
+ <c>logger:add_primary_filter/2</c></seemfa> and
+ <seemfa marker="logger#remove_primary_filter/1">
+ <c>logger:remove_primary_filter/1</c></seemfa>,
respectively.</p>
- <p>See section <seealso marker="#filters">Filters</seealso>
+ <p>See section <seeguide marker="#filters">Filters</seeguide>
for more detailed information.</p>
<p>Defaults to <c>[]</c>.</p>
</item>
@@ -540,7 +540,7 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<item>
<p>Specifies what happens to a log event if all filters
return <c>ignore</c>, or if no filters exist.</p>
- <p>See section <seealso marker="#filters">Filters</seealso>
+ <p>See section <seeguide marker="#filters">Filters</seeguide>
for more information about how this option is used.</p>
<p>Defaults to <c>log</c>.</p>
</item>
@@ -553,23 +553,23 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<p>Logger API functions that apply to handler configuration
are:</p>
<list>
- <item><seealso marker="logger#get_handler_config-0">
- <c>get_handler_config/0,1</c></seealso></item>
- <item><seealso marker="logger#set_handler_config-2">
- <c>set_handler_config/2,3</c></seealso></item>
- <item><seealso marker="logger#update_handler_config-2">
- <c>update_handler_config/2,3</c></seealso></item>
- <item><seealso marker="logger#add_handler_filter-3">
- <c>add_handler_filter/3</c></seealso></item>
- <item><seealso marker="logger#remove_handler_filter-2">
- <c>remove_handler_filter/2</c></seealso></item>
- <item><seealso marker="logger#update_formatter_config-2">
- <c>update_formatter_config/2,3</c></seealso></item>
+ <item><seemfa marker="logger#get_handler_config/0">
+ <c>get_handler_config/0,1</c></seemfa></item>
+ <item><seemfa marker="logger#set_handler_config/2">
+ <c>set_handler_config/2,3</c></seemfa></item>
+ <item><seemfa marker="logger#update_handler_config/2">
+ <c>update_handler_config/2,3</c></seemfa></item>
+ <item><seemfa marker="logger#add_handler_filter/3">
+ <c>add_handler_filter/3</c></seemfa></item>
+ <item><seemfa marker="logger#remove_handler_filter/2">
+ <c>remove_handler_filter/2</c></seemfa></item>
+ <item><seemfa marker="logger#update_formatter_config/2">
+ <c>update_formatter_config/2,3</c></seemfa></item>
</list>
<p>The configuration for a handler is a map with the following keys:</p>
<taglist>
- <tag><c>id = </c><seealso marker="logger#type-handler_id">
- <c>logger:handler_id()</c></seealso></tag>
+ <tag><c>id = </c><seetype marker="logger#handler_id">
+ <c>logger:handler_id()</c></seetype></tag>
<item>
<p>Automatically inserted by Logger. The value is the same
as the <c>HandlerId</c> specified when adding the handler,
@@ -581,20 +581,20 @@ logger:debug(#{got => connection_request, id => Id, state => State},
as the <c>Module</c> specified when adding the handler,
and it cannot be changed.</p>
</item>
- <tag><c>level = </c><seealso marker="logger#type-level">
- <c>logger:level()</c></seealso><c> | all | none</c></tag>
+ <tag><c>level = </c><seetype marker="logger#level">
+ <c>logger:level()</c></seetype><c> | all | none</c></tag>
<item>
<p>Specifies the log level for the handler, that is, log
events that are equally or more severe than this level,
are forwarded to the handler filters for this
handler.</p>
- <p>See section <seealso marker="#log_level">Log
- Level</seealso> for a listing and description of
+ <p>See section <seeguide marker="#log_level">Log
+ Level</seeguide> for a listing and description of
possible log levels.</p>
<p>The log level is specified when adding the handler, or
changed during runtime with, for
- instance, <seealso marker="logger#set_handler_config/3">
- <c>logger:set_handler_config(HandlerId,level,Level)</c></seealso>.
+ instance, <seemfa marker="logger#set_handler_config/3">
+ <c>logger:set_handler_config(HandlerId,level,Level)</c></seemfa>.
</p>
<p>Defaults to <c>all</c>.</p>
</item>
@@ -602,19 +602,19 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<item>
<p>Specifies the handler filters.</p>
<list>
- <item><c>FilterId = </c><seealso marker="logger#type-filter_id">
- <c>logger:filter_id()</c></seealso></item>
- <item><c>Filter = </c><seealso marker="logger#type-filter">
- <c>logger:filter()</c></seealso></item>
+ <item><c>FilterId = </c><seetype marker="logger#filter_id">
+ <c>logger:filter_id()</c></seetype></item>
+ <item><c>Filter = </c><seetype marker="logger#filter">
+ <c>logger:filter()</c></seetype></item>
</list>
<p>Handler filters are specified when adding the handler,
or added or removed during runtime with
- <seealso marker="logger#add_handler_filter-3">
- <c>logger:add_handler_filter/3</c></seealso> and
- <seealso marker="logger#remove_handler_filter-2">
- <c>logger:remove_handler_filter/2</c></seealso>,
+ <seemfa marker="logger#add_handler_filter/3">
+ <c>logger:add_handler_filter/3</c></seemfa> and
+ <seemfa marker="logger#remove_handler_filter/2">
+ <c>logger:remove_handler_filter/2</c></seemfa>,
respectively.</p>
- <p>See <seealso marker="#filters">Filters</seealso> for more
+ <p>See <seeguide marker="#filters">Filters</seeguide> for more
detailed information.</p>
<p>Defaults to <c>[]</c>.</p>
</item>
@@ -622,7 +622,7 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<item>
<p>Specifies what happens to a log event if all filters
return <c>ignore</c>, or if no filters exist.</p>
- <p>See section <seealso marker="#filters">Filters</seealso>
+ <p>See section <seeguide marker="#filters">Filters</seeguide>
for more information about how this option is used.</p>
<p>Defaults to <c>log</c>.</p>
</item>
@@ -633,25 +633,25 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<list>
<item><c>FormatterModule = module()</c></item>
<item><c>FormatterConfig = </c>
- <seealso marker="logger#type-formatter_config">
- <c>logger:formatter_config()</c></seealso></item>
+ <seetype marker="logger#formatter_config">
+ <c>logger:formatter_config()</c></seetype></item>
</list>
<p>The formatter information is specified when adding the
handler. The formatter configuration can be changed during
runtime
- with <seealso marker="logger#update_formatter_config-2">
- <c>logger:update_formatter_config/2,3</c></seealso>,
+ with <seemfa marker="logger#update_formatter_config/2">
+ <c>logger:update_formatter_config/2,3</c></seemfa>,
or the complete formatter information can be overwritten
with, for
- instance, <seealso marker="logger#set_handler_config-3">
- <c>logger:set_handler_config/3</c></seealso>.</p>
+ instance, <seemfa marker="logger#set_handler_config/3">
+ <c>logger:set_handler_config/3</c></seemfa>.</p>
<p>See
- section <seealso marker="#formatters">Formatters</seealso>
+ section <seeguide marker="#formatters">Formatters</seeguide>
for more detailed information.</p>
<p>Defaults
to <c>{logger_formatter,DefaultFormatterConfig}</c>. See
- the <seealso marker="logger_formatter">
- <c>logger_formatter(3)</c></seealso> manual page for
+ the <seeerl marker="logger_formatter">
+ <c>logger_formatter(3)</c></seeerl> manual page for
information about this formatter and its default
configuration.</p>
</item>
@@ -661,10 +661,10 @@ logger:debug(#{got => connection_request, id => Id, state => State},
data related to a specific handler implementation.</p>
<p>The configuration for the built-in handlers is described
in
- the <seealso marker="logger_std_h"><c>logger_std_h(3)</c></seealso>
+ the <seeerl marker="logger_std_h"><c>logger_std_h(3)</c></seeerl>
and
- <seealso marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c>
- </seealso> manual pages.</p>
+ <seeerl marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c>
+ </seeerl> manual pages.</p>
</item>
</taglist>
@@ -684,18 +684,18 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<tag><marker id="logger_parameter"/><c>logger = [Config]</c></tag>
<item>
<p>Specifies the configuration
- for <seealso marker="logger">Logger</seealso>, except the
+ for <seeerl marker="logger">Logger</seeerl>, except the
primary log level, which is specified
- with <seealso marker="#logger_level"><c>logger_level</c></seealso>,
+ with <seeguide marker="#logger_level"><c>logger_level</c></seeguide>,
and the compatibility
- with <seealso marker="sasl:error_logging">SASL Error
- Logging</seealso>, which is specified
- with <seealso marker="#logger_sasl_compatible">
- <c>logger_sasl_compatible</c></seealso>.</p>
+ with <seeguide marker="sasl:error_logging">SASL Error
+ Logging</seeguide>, which is specified
+ with <seeguide marker="#logger_sasl_compatible">
+ <c>logger_sasl_compatible</c></seeguide>.</p>
<p>With this parameter, you can modify or disable the default
handler, add custom handlers and primary logger filters, set
log levels per module, and modify
- the <seealso marker="#proxy">proxy</seealso>
+ the <seeguide marker="#proxy">proxy</seeguide>
configuration.</p>
<p><c>Config</c> is any (zero or more) of the following:</p>
<taglist>
@@ -709,18 +709,18 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<item>
<p>If <c>HandlerId</c> is <c>default</c>, then this entry
modifies the default handler, equivalent to calling</p>
- <pre><seealso marker="logger#remove_handler-1">
+ <pre><seemfa marker="logger#remove_handler/1">
logger:remove_handler(default)
- </seealso></pre>
+ </seemfa></pre>
<p>followed by</p>
- <pre><seealso marker="logger#add_handler-3">
+ <pre><seemfa marker="logger#add_handler/3">
logger:add_handler(default, Module, HandlerConfig)
- </seealso></pre>
+ </seemfa></pre>
<p>For all other values of <c>HandlerId</c>, this entry
adds a new handler, equivalent to calling</p>
- <pre><seealso marker="logger#add_handler/3">
+ <pre><seemfa marker="logger#add_handler/3">
logger:add_handler(HandlerId, Module, HandlerConfig)
- </seealso></pre>
+ </seemfa></pre>
<p>Multiple entries of this type are allowed.</p></item>
<tag><c>{filters, FilterDefault, [Filter]}</c></tag>
<item>
@@ -730,53 +730,53 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<item><c>Filter = {FilterId, {FilterFun, FilterConfig}}</c></item>
</list>
<p>Equivalent to calling</p>
- <pre><seealso marker="logger#add_primary_filter/2">
+ <pre><seemfa marker="logger#add_primary_filter/2">
logger:add_primary_filter(FilterId, {FilterFun, FilterConfig})
- </seealso></pre>
+ </seemfa></pre>
<p>for each <c>Filter</c>.</p>
<p><c>FilterDefault</c> specifies the behaviour if all
primary filters return <c>ignore</c>, see
- section <seealso marker="#filters">Filters</seealso>.</p>
+ section <seeguide marker="#filters">Filters</seeguide>.</p>
<p>Only one entry of this type is allowed.</p>
</item>
<tag><c>{module_level, Level, [Module]}</c></tag>
<item>
<p>Sets module log level for the given modules. Equivalent
to calling</p>
- <pre><seealso marker="logger#set_module_level/2">
- logger:set_module_level(Module, Level)</seealso></pre>
+ <pre><seemfa marker="logger#set_module_level/2">
+ logger:set_module_level(Module, Level)</seemfa></pre>
<p>for each <c>Module</c>.</p>
<p>Multiple entries of this type are allowed.</p>
</item>
<tag><c>{proxy, ProxyConfig}</c></tag>
<item>
<p>Sets the proxy configuration, equivalent to calling</p>
- <pre><seealso marker="logger#set_proxy_config/1">
+ <pre><seemfa marker="logger#set_proxy_config/1">
logger:set_proxy_config(ProxyConfig)
- </seealso></pre>
+ </seemfa></pre>
<p>Only one entry of this type is allowed.</p>
</item>
</taglist>
<p>See
- section <seealso marker="#config_examples">Configuration
- Examples</seealso> for examples using the <c>logger</c>
+ section <seeguide marker="#config_examples">Configuration
+ Examples</seeguide> for examples using the <c>logger</c>
parameter for system configuration.</p>
</item>
<tag><marker id="logger_level"/>
<c>logger_level = Level</c></tag>
<item>
<p>Specifies the primary log level. See
- the <seealso marker="kernel_app#logger_level"><c>kernel(6)</c></seealso>
+ the <seeapp marker="kernel_app#logger_level"><c>kernel(6)</c></seeapp>
manual page for more information about this parameter.</p>
</item>
<tag><marker id="logger_sasl_compatible"/>
<c>logger_sasl_compatible = true | false</c></tag>
<item>
<p>Specifies Logger's compatibility
- with <seealso marker="sasl:error_logging">SASL Error
- Logging</seealso>. See
- the <seealso marker="kernel_app#logger_sasl_compatible">
- <c>kernel(6)</c></seealso> manual page for more
+ with <seeguide marker="sasl:error_logging">SASL Error
+ Logging</seeguide>. See
+ the <seeapp marker="kernel_app#logger_sasl_compatible">
+ <c>kernel(6)</c></seeapp> manual page for more
information about this parameter.</p>
</item>
</taglist>
@@ -790,7 +790,7 @@ logger:debug(#{got => connection_request, id => Id, state => State},
command line when starting an erlang node, but as the term
grows, a better approach is to use the system configuration
file. See
- the <seealso marker="config"><c>config(4)</c></seealso> manual
+ the <seefile marker="config"><c>config(4)</c></seefile> manual
page for more information about this file.</p>
<p>Each of the following examples shows a simple system
configuration file that configures Logger according to the
@@ -854,22 +854,22 @@ logger:debug(#{got => connection_request, id => Id, state => State},
be used by legacy code. It will be removed in a later
release.</p>
<p>Calls
- to <seealso marker="error_logger#error_report-1">
- <c>error_logger:error_report/1,2</c></seealso>,
- <seealso marker="error_logger#error_msg-1">
- <c>error_logger:error_msg/1,2</c></seealso>, and
+ to <seemfa marker="error_logger#error_report/1">
+ <c>error_logger:error_report/1,2</c></seemfa>,
+ <seemfa marker="error_logger#error_msg/1">
+ <c>error_logger:error_msg/1,2</c></seemfa>, and
corresponding functions for warning and info messages, are
all forwarded to Logger as calls
- to <seealso marker="logger#log-3">
- <c>logger:log(Level,Report,Metadata)</c></seealso>.</p>
+ to <seemfa marker="logger#log/3">
+ <c>logger:log(Level,Report,Metadata)</c></seemfa>.</p>
<p><c>Level = error | warning | info</c> and is taken
from the function name. <c>Report</c> contains the actual
log message, and <c>Metadata</c> contains additional
information which can be used for creating backwards
compatible events for legacy <c>error_logger</c> event
handlers, see
- section <seealso marker="#legacy_event_handlers">Legacy
- Event Handlers</seealso>.</p>
+ section <seeguide marker="#legacy_event_handlers">Legacy
+ Event Handlers</seeguide>.</p>
</item>
<tag>Output Format</tag>
<item>
@@ -895,8 +895,8 @@ logger:debug(#{got => connection_request, id => Id, state => State},
trough SASL's own event handlers <c>sasl_report_tty_h</c>
and <c>sasl_report_file_h</c>.</p>
<p>The destination of these log events was configured by
- <seealso marker="sasl:sasl_app#deprecated_error_logger_config">SASL
- configuration parameters</seealso>.</p>
+ <seeapp marker="sasl:sasl_app#deprecated_error_logger_config">SASL
+ configuration parameters</seeapp>.</p>
<p>Due to the specific event handlers, the output format
slightly differed from other log events.</p>
<p>As of Erlang/OTP 21.0, the concept of SASL reports is
@@ -912,25 +912,25 @@ logger:debug(#{got => connection_request, id => Id, state => State},
events, and since the default primary log level
is <c>notice</c>, these are not logged by default. To
enable printing of progress reports, set
- the <seealso marker="#primary_level">primary log
- level</seealso> to <c>info</c>.</item>
+ the <seeguide marker="#primary_level">primary log
+ level</seeguide> to <c>info</c>.</item>
<item>The output format is the same for all log
events.</item>
</list>
<p>If the old behaviour is preferred, the Kernel configuration
- parameter <seealso marker="kernel_app#logger_sasl_compatible">
- <c>logger_sasl_compatible</c></seealso> can be set
+ parameter <seeapp marker="kernel_app#logger_sasl_compatible">
+ <c>logger_sasl_compatible</c></seeapp> can be set
to <c>true</c>. The
- <seealso marker="sasl:sasl_app#deprecated_error_logger_config">SASL
- configuration parameters</seealso> can then be used as
+ <seeapp marker="sasl:sasl_app#deprecated_error_logger_config">SASL
+ configuration parameters</seeapp> can then be used as
before, and the SASL reports will only be printed if the
SASL application is running, through a second log handler
named <c>sasl</c>.</p>
<p>All SASL reports have a metadata field <c>domain</c> which
is set to <c>[otp,sasl]</c>. This field can be
used by filters to stop or allow the log events.</p>
- <p>See section <seealso marker="sasl:error_logging">SASL User's
- Guide</seealso> for more information about the old SASL
+ <p>See section <seeguide marker="sasl:error_logging">SASL User's
+ Guide</seeguide> for more information about the old SASL
error logging functionality.</p>
</item>
<tag><marker id="legacy_event_handlers"/>Legacy Event Handlers</tag>
@@ -974,8 +974,8 @@ error_logger:add_report_handler/1,2.
filter or handler in question from the configuration, and print
a short error message to the terminal. A debug event containing
the crash reason and other details is also issued.</p>
- <p>See section <seealso marker="#log_message">Log
- Message</seealso> for more information about report callbacks
+ <p>See section <seeguide marker="#log_message">Log
+ Message</seeguide> for more information about report callbacks
and valid forms of log messages.</p>
</section>
@@ -1003,7 +1003,7 @@ ok</pre>
ok</pre>
<p>Then, add a new handler which prints to file. You can use the
handler
- module <seealso marker="logger_std_h"><c>logger_std_h</c></seealso>,
+ module <seeerl marker="logger_std_h"><c>logger_std_h</c></seeerl>,
and configure it to log to file:</p>
<pre>
4> <input>Config = #{config => #{file => "./info.log"}, level => info}.</input>
@@ -1014,14 +1014,14 @@ ok</pre>
handler now receives all log events. If you want info events
only in the file, you must add a filter to stop all non-info
events. The built-in
- filter <seealso marker="logger_filters#level-2">
- <c>logger_filters:level/2</c></seealso>
+ filter <seemfa marker="logger_filters#level/2">
+ <c>logger_filters:level/2</c></seemfa>
can do this:</p>
<pre>
6> <input>logger:add_handler_filter(myhandler, stop_non_info,
{fun logger_filters:level/2, {stop, neq, info}}).</input>
ok</pre>
- <p>See section <seealso marker="#filters">Filters</seealso> for
+ <p>See section <seeguide marker="#filters">Filters</seeguide> for
more information about the filters and the <c>filter_default</c>
configuration parameter.</p>
@@ -1029,8 +1029,8 @@ ok</pre>
<section>
<title>Example: Implement a handler</title>
- <p>Section <seealso marker="logger#handler_callback_functions">Handler
- Callback Functions</seealso> in the logger(3) manual page
+ <p>Section <seeerl marker="logger#handler_callback_functions">Handler
+ Callback Functions</seeerl> in the logger(3) manual page
describes the callback functions that can be implemented for a
Logger handler.</p>
<p>A handler callback module must export:</p>
@@ -1045,8 +1045,8 @@ ok</pre>
<item><c>filter_config(Config)</c></item>
</list>
<p>When a handler is added, by for example a call
- to <seealso marker="logger#add_handler-3">
- <c>logger:add_handler(Id, HModule, Config)</c></seealso>,
+ to <seemfa marker="logger#add_handler/3">
+ <c>logger:add_handler(Id, HModule, Config)</c></seemfa>,
Logger first calls <c>HModule:adding_handler(Config)</c>. If
this function returns <c>{ok,Config1}</c>, Logger
writes <c>Config1</c> to the configuration database, and
@@ -1054,22 +1054,22 @@ ok</pre>
handler is installed and must be ready to receive log events as
calls to <c>HModule:log/2</c>.</p>
<p>A handler can be removed by calling
- <seealso marker="logger#remove_handler-1">
- <c>logger:remove_handler(Id)</c></seealso>. Logger calls
+ <seemfa marker="logger#remove_handler/1">
+ <c>logger:remove_handler(Id)</c></seemfa>. Logger calls
<c>HModule:removing_handler(Config)</c>, and removes the
handler's configuration from the configuration database.</p>
- <p>When <seealso marker="logger#set_handler_config-2">
- <c>logger:set_handler_config/2,3</c></seealso>
- or <seealso marker="logger#update_handler_config/2">
- <c>logger:update_handler_config/2,3</c></seealso> is called,
+ <p>When <seemfa marker="logger#set_handler_config/2">
+ <c>logger:set_handler_config/2,3</c></seemfa>
+ or <seemfa marker="logger#update_handler_config/2">
+ <c>logger:update_handler_config/2,3</c></seemfa> is called,
Logger
calls <c>HModule:changing_config(SetOrUpdate, OldConfig, NewConfig)</c>. If
this function returns <c>{ok,NewConfig1}</c>, Logger
writes <c>NewConfig1</c> to the configuration database.</p>
- <p>When <seealso marker="logger#get_config-0">
- <c>logger:get_config/0</c></seealso> or
- <seealso marker="logger#get_handler_config-0">
- <c>logger:get_handler_config/0,1</c></seealso> is called,
+ <p>When <seemfa marker="logger#get_config/0">
+ <c>logger:get_config/0</c></seemfa> or
+ <seemfa marker="logger#get_handler_config/0">
+ <c>logger:get_handler_config/0,1</c></seemfa> is called,
Logger calls <c>HModule:filter_config(Config)</c>. This function
must return the handler configuration where internal data is
removed.</p>
@@ -1089,11 +1089,11 @@ log(LogEvent, #{formatter := {FModule, FConfig}}) ->
client process.</p>
<p>For information and examples of overload protection, please
refer to
- section <seealso marker="#overload_protection">Protecting the
- Handler from Overload</seealso>, and the implementation
- of <seealso marker="logger_std_h"><c>logger_std_h</c></seealso>
- and <seealso marker="logger_disk_log_h"><c>logger_disk_log_h</c>
- </seealso>.</p>
+ section <seeguide marker="#overload_protection">Protecting the
+ Handler from Overload</seeguide>, and the implementation
+ of <seeerl marker="logger_std_h"><c>logger_std_h</c></seeerl>
+ and <seeerl marker="logger_disk_log_h"><c>logger_disk_log_h</c>
+ </seeerl>.</p>
<p>The following is a simpler example of a handler which logs to a
file through one single process:</p>
<code>
@@ -1136,9 +1136,9 @@ do_log(Fd, LogEvent, #{formatter := {FModule, FConfig}}) ->
<section>
<marker id="overload_protection"/>
<title>Protecting the Handler from Overload</title>
- <p>The default handlers, <seealso marker="logger_std_h">
- <c>logger_std_h</c></seealso> and <seealso marker="logger_disk_log_h">
- <c>logger_disk_log_h</c></seealso>, feature an overload protection
+ <p>The default handlers, <seeerl marker="logger_std_h">
+ <c>logger_std_h</c></seeerl> and <seeerl marker="logger_disk_log_h">
+ <c>logger_disk_log_h</c></seeerl>, feature an overload protection
mechanism, which makes it possible for the handlers to survive,
and stay responsive, during periods of high load (when huge
numbers of incoming log requests must be handled).
@@ -1161,7 +1161,7 @@ do_log(Fd, LogEvent, #{formatter := {FModule, FConfig}}) ->
<p>As long as the length of the message queue is lower than this
value, all log events are handled asynchronously. This means that
the client process sending the log event, by calling a log function
- in the <seealso marker="logger_chapter#logger_api">Logger API</seealso>,
+ in the <seeguide marker="logger_chapter#logger_api">Logger API</seeguide>,
does not wait for a response from the handler but continues
executing immediately after the event is sent. It is not affected
by the time it takes the handler to print the event to the log
@@ -1337,7 +1337,7 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h,
<note>
<p>The sizes of the log events affect the memory needs of the handler.
For information about how to limit the size of log events, see the
- <seealso marker="logger_formatter"><c>logger_formatter(3)</c></seealso>
+ <seeerl marker="logger_formatter"><c>logger_formatter(3)</c></seeerl>
manual page.</p>
</note>
</section>
@@ -1361,8 +1361,8 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h,
remote node, the proxy calls the Logger API to log the event.</p>
<p>The proxy process is overload protected in the same way as
described in
- section <seealso marker="#overload_protection">Protecting the
- Handler from Overload</seealso>, but with the following default
+ section <seeguide marker="#overload_protection">Protecting the
+ Handler from Overload</seeguide>, but with the following default
values:</p>
<code>
#{sync_mode_qlen => 500,
@@ -1376,8 +1376,8 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h,
the <c>system_logger</c> to <c>undefined</c>, forcing the
emulator to drop events until it is set back to the proxy pid
again.</p>
- <p>The proxy uses <seealso marker="erts:erlang#send_nosuspend/2">
- <c>erlang:send_nosuspend/2</c></seealso> when sending log
+ <p>The proxy uses <seemfa marker="erts:erlang#send_nosuspend/2">
+ <c>erlang:send_nosuspend/2</c></seemfa> when sending log
events to a remote node. If the message could not be sent
without suspending the sender, it is dropped. This is to avoid
blocking the proxy process.</p>
@@ -1386,14 +1386,14 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h,
<section>
<title>See Also</title>
<p>
- <seealso marker="disk_log"><c>disk_log(3)</c></seealso>,
- <seealso marker="erts:erlang"><c>erlang(3)</c></seealso>,
- <seealso marker="error_logger"><c>error_logger(3)</c></seealso>,
- <seealso marker="logger"><c>logger(3)</c></seealso>,
- <seealso marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seealso>,
- <seealso marker="logger_filters"><c>logger_filters(3)</c></seealso>,
- <seealso marker="logger_formatter"><c>logger_formatter(3)</c></seealso>,
- <seealso marker="logger_std_h"><c>logger_std_h(3)</c></seealso>,
- <seealso marker="sasl:sasl_app"><c>sasl(6)</c></seealso></p>
+ <seeerl marker="disk_log"><c>disk_log(3)</c></seeerl>,
+ <seeerl marker="erts:erlang"><c>erlang(3)</c></seeerl>,
+ <seeerl marker="error_logger"><c>error_logger(3)</c></seeerl>,
+ <seeerl marker="logger"><c>logger(3)</c></seeerl>,
+ <seeerl marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seeerl>,
+ <seeerl marker="logger_filters"><c>logger_filters(3)</c></seeerl>,
+ <seeerl marker="logger_formatter"><c>logger_formatter(3)</c></seeerl>,
+ <seeerl marker="logger_std_h"><c>logger_std_h(3)</c></seeerl>,
+ <seeapp marker="sasl:sasl_app"><c>sasl(6)</c></seeapp></p>
</section>
</chapter>
diff --git a/lib/kernel/doc/src/logger_cookbook.xml b/lib/kernel/doc/src/logger_cookbook.xml
index f682fa3c10..fc6b0202b6 100644
--- a/lib/kernel/doc/src/logger_cookbook.xml
+++ b/lib/kernel/doc/src/logger_cookbook.xml
@@ -53,13 +53,13 @@ Primary configuration:
Filters:
(none)</code>
<p>It is also possible to fetch the configuration using
- <seealso marker="logger#get_primary_config-0"><c>logger:get_primary_config()</c></seealso>.
+ <seemfa marker="logger#get_primary_config/0"><c>logger:get_primary_config()</c></seemfa>.
</p>
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger#i-0">logger:i()</seealso></item>
- <item><seealso marker="logger_chapter#configuration">Configuration</seealso> in the Logging User&apos;s Guide</item>
+ <item><seemfa marker="logger#i/0">logger:i()</seemfa></item>
+ <item><seeguide marker="logger_chapter#configuration">Configuration</seeguide> in the Logging User&apos;s Guide</item>
</list>
</section>
</section>
@@ -101,16 +101,16 @@ Handler configuration:
type: standard_io
</code>
<p>You can also print the configuration of a specific handler using
- <seealso marker="logger#i-1"><c>logger:i(HandlerName)</c></seealso>,
+ <seemfa marker="logger#i/1"><c>logger:i(HandlerName)</c></seemfa>,
or fetch the configuration using
- <seealso marker="logger#get_handler_config-0"><c>logger:get_handler_config()</c></seealso>,
- or <seealso marker="logger#get_handler_config-1"><c>logger:get_handler_config(HandlerName)</c></seealso>
+ <seemfa marker="logger#get_handler_config/0"><c>logger:get_handler_config()</c></seemfa>,
+ or <seemfa marker="logger#get_handler_config/1"><c>logger:get_handler_config(HandlerName)</c></seemfa>
for a specific handler.</p>
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger#i-0"><c>logger:i()</c></seealso></item>
- <item><seealso marker="logger_chapter#configuration">Configuration</seealso> in the Logging User&apos;s Guide</item>
+ <item><seemfa marker="logger#i/0"><c>logger:i()</c></seemfa></item>
+ <item><seeguide marker="logger_chapter#configuration">Configuration</seeguide> in the Logging User&apos;s Guide</item>
</list>
</section>
</section>
@@ -167,9 +167,9 @@ ok
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger_formatter#type-config">logger_formatter&apos;s Configuration</seealso></item>
- <item><seealso marker="logger_chapter#formatters">Formatters</seealso> in the Logging User&apos;s Guide</item>
- <item><seealso marker="logger#set_handler_config-3"><c>logger:set_handler_config/3</c></seealso></item>
+ <item><seetype marker="logger_formatter#config">logger_formatter&apos;s Configuration</seetype></item>
+ <item><seeguide marker="logger_chapter#formatters">Formatters</seeguide> in the Logging User&apos;s Guide</item>
+ <item><seemfa marker="logger#set_handler_config/3"><c>logger:set_handler_config/3</c></seemfa></item>
</list>
</section>
</section>
@@ -187,17 +187,17 @@ Eshell V10.5.1 (abort with ^G)
1> logger:error("Oh noes, more errors",#{ file => "shell.erl", line => 1 }).
1962-10-05T07:37:44.104241+02:00 shell.erl:1 error: Oh noes, more errors</code>
<p>Note that file and line have to be added in the metadata by the caller of
- <seealso marker="logger#log-3"><c>logger:log/3</c></seealso> as otherwise
+ <seemfa marker="logger#log/3"><c>logger:log/3</c></seemfa> as otherwise
Logger will not know from where it was called. The file and line number
are automatically added if you use the <c>?LOG_ERROR</c> macros in
<c>kernel/include/logger.hrl</c>.</p>
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger_formatter#type-config">logger_formatter&apos;s Configuration</seealso></item>
- <item><seealso marker="logger_formatter#type-template">logger_formatter&apos;s Template</seealso></item>
- <item><seealso marker="logger#macros">Logger Macros</seealso></item>
- <item><seealso marker="logger_chapter#metadata">Metadata</seealso> in the Logging User&apos;s Guide</item>
+ <item><seetype marker="logger_formatter#config">logger_formatter&apos;s Configuration</seetype></item>
+ <item><seetype marker="logger_formatter#template">logger_formatter&apos;s Template</seetype></item>
+ <item><seeerl marker="logger#macros">Logger Macros</seeerl></item>
+ <item><seeguide marker="logger_chapter#metadata">Metadata</seeguide> in the Logging User&apos;s Guide</item>
</list>
</section>
</section>
@@ -226,8 +226,8 @@ $ cat log/erlang.log
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger_std_h#description">logger_std_h&apos;s Description</seealso></item>
- <item><seealso marker="logger_chapter#handlers">Handlers</seealso> in the Logging User&apos;s Guide</item>
+ <item><seeerl marker="logger_std_h#description">logger_std_h&apos;s Description</seeerl></item>
+ <item><seeguide marker="logger_chapter#handlers">Handlers</seeguide> in the Logging User&apos;s Guide</item>
</list>
</section>
</section>
@@ -280,8 +280,8 @@ ok
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger_std_h#description">logger_std_h&apos;s Description</seealso></item>
- <item><seealso marker="logger_chapter#filters">Filters</seealso> in the Logging User&apos;s Guide</item>
+ <item><seeerl marker="logger_std_h#description">logger_std_h&apos;s Description</seeerl></item>
+ <item><seeguide marker="logger_chapter#filters">Filters</seeguide> in the Logging User&apos;s Guide</item>
</list>
</section>
</section>
@@ -314,7 +314,7 @@ ok
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger_chapter#log_message">Log Message</seealso> in the Logging User&apos;s Guide</item>
+ <item><seeguide marker="logger_chapter#log_message">Log Message</seeguide> in the Logging User&apos;s Guide</item>
</list>
</section>
</section>
@@ -346,8 +346,8 @@ Dst: 192.121.151.106 (erlang.org)
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger_chapter#log_message">Log Message</seealso> in the Logging User&apos;s Guide</item>
- <item><seealso marker="logger#type-report_cb">Logger Report Callbacks</seealso></item>
+ <item><seeguide marker="logger_chapter#log_message">Log Message</seeguide> in the Logging User&apos;s Guide</item>
+ <item><seetype marker="logger#report_cb">Logger Report Callbacks</seetype></item>
</list>
</section>
</section>
@@ -391,9 +391,9 @@ logger:debug("Debug should be logged").
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger_chapter#filters">Filters</seealso> in the Logging User&apos;s Guide</item>
- <item><seealso marker="logger_filters#level-2"><c>logger_filters:level/2</c></seealso></item>
- <item><seealso marker="logger#set_primary_config-2"><c>logger:set_primary_config/2</c></seealso></item>
+ <item><seeguide marker="logger_chapter#filters">Filters</seeguide> in the Logging User&apos;s Guide</item>
+ <item><seemfa marker="logger_filters#level/2"><c>logger_filters:level/2</c></seemfa></item>
+ <item><seemfa marker="logger#set_primary_config/2"><c>logger:set_primary_config/2</c></seemfa></item>
</list>
</section>
</section>
@@ -413,9 +413,9 @@ logger:debug("Debug should be logged").
<section>
<title>See also</title>
<list>
- <item><seealso marker="logger_chapter#filters">Filters</seealso> in the Logging User&apos;s Guide</item>
- <item><seealso marker="logger_filters#domain-2"><c>logger_filters:domain/2</c></seealso></item>
- <item><seealso marker="logger#set_primary_config-2"><c>logger:set_primary_config/2</c></seealso></item>
+ <item><seeguide marker="logger_chapter#filters">Filters</seeguide> in the Logging User&apos;s Guide</item>
+ <item><seemfa marker="logger_filters#domain/2"><c>logger_filters:domain/2</c></seemfa></item>
+ <item><seemfa marker="logger#set_primary_config/2"><c>logger:set_primary_config/2</c></seemfa></item>
</list>
</section>
</section>
diff --git a/lib/kernel/doc/src/logger_disk_log_h.xml b/lib/kernel/doc/src/logger_disk_log_h.xml
index de344ab30b..b408b482bc 100644
--- a/lib/kernel/doc/src/logger_disk_log_h.xml
+++ b/lib/kernel/doc/src/logger_disk_log_h.xml
@@ -37,26 +37,26 @@
<description>
<p>This is a handler for Logger that offers circular
- (wrapped) logs by using <seealso marker="disk_log"><c>disk_log</c></seealso>.
+ (wrapped) logs by using <seeerl marker="disk_log"><c>disk_log</c></seeerl>.
Multiple instances of this handler can be added to Logger, and each instance
prints to its own disk log file, created with the name and settings specified
in the handler configuration.</p>
<p>The default standard handler,
- <seealso marker="logger_std_h"><c>logger_std_h</c></seealso>, can be
+ <seeerl marker="logger_std_h"><c>logger_std_h</c></seeerl>, can be
replaced by a disk_log handler at startup of the Kernel application.
See an example of this below.</p>
<p>The handler has an overload protection mechanism that keeps the handler
process and the Kernel application alive during high loads of log
events. How overload protection works, and how to configure it, is
described in the
- <seealso marker="logger_chapter#overload_protection"><c>User's Guide</c>
- </seealso>.</p>
+ <seeguide marker="logger_chapter#overload_protection"><c>User's Guide</c>
+ </seeguide>.</p>
<p>To add a new instance of the disk_log handler, use
- <seealso marker="logger#add_handler-3"><c>logger:add_handler/3</c>
- </seealso>. The handler configuration argument is a map which can contain
+ <seemfa marker="logger#add_handler/3"><c>logger:add_handler/3</c>
+ </seemfa>. The handler configuration argument is a map which can contain
general configuration parameters, as documented in the
- <seealso marker="logger_chapter#handler_configuration"><c>User's Guide</c>
- </seealso>, and handler specific parameters. The specific data
+ <seeguide marker="logger_chapter#handler_configuration"><c>User's Guide</c>
+ </seeguide>, and handler specific parameters. The specific data
is stored in a sub map with the key <c>config</c>, and can contain the
following parameters:</p>
<taglist>
@@ -64,7 +64,7 @@
<item>
<p>This is the full name of the disk log file. The option
corresponds to the <c>name</c> property in the
- <seealso marker="disk_log#open-1"><c>dlog_option()</c></seealso>
+ <seemfa marker="disk_log#open/1"><c>dlog_option()</c></seemfa>
datatype.</p>
<p>The value is set when the handler is added, and it cannot
be changed in runtime.</p>
@@ -75,7 +75,7 @@
<item>
<p>This is the disk log type, <c>wrap</c> or <c>halt</c>. The option
corresponds to the <c>type</c> property in the
- <seealso marker="disk_log#open-1"><c>dlog_option()</c></seealso>
+ <seemfa marker="disk_log#open/1"><c>dlog_option()</c></seemfa>
datatype.</p>
<p>The value is set when the handler is added, and it cannot
be changed in runtime.</p>
@@ -86,7 +86,7 @@
<p>This is the maximum number of files that disk_log uses
for its circular logging. The option
corresponds to the <c>MaxNoFiles</c> element in the <c>size</c> property in the
- <seealso marker="disk_log#open-1"><c>dlog_option()</c></seealso>
+ <seemfa marker="disk_log#open/1"><c>dlog_option()</c></seemfa>
datatype.</p>
<p>The value is set when the handler is added, and it cannot
be changed in runtime.</p>
@@ -99,7 +99,7 @@
a log file before disk_log proceeds with the next file in order, or
generates an error in case of a full halt log. The option
corresponds to the <c>MaxNoBytes</c> element in the <c>size</c> property in the
- <seealso marker="disk_log#open-1"><c>dlog_option()</c></seealso>
+ <seemfa marker="disk_log#open/1"><c>dlog_option()</c></seemfa>
datatype.</p>
<p>The value is set when the handler is added, and it cannot
be changed in runtime.</p>
@@ -115,15 +115,15 @@
<p>Defaults to <c>5000</c> milliseconds.</p>
<p>If <c>no_repeat</c> is set as value, the repeated sync operation
is disabled. The user can also call the
- <seealso marker="logger_disk_log_h#filesync-1"><c>filesync/1</c>
- </seealso> function to perform a disk_log sync.</p>
+ <seemfa marker="logger_disk_log_h#filesync/1"><c>filesync/1</c>
+ </seemfa> function to perform a disk_log sync.</p>
</item>
</taglist>
<p>Other configuration parameters exist, to be used for customizing
the overload protection behaviour. The same parameters are used both in the
standard handler and the disk_log handler, and are documented in the
- <seealso marker="logger_chapter#overload_protection"><c>User's Guide</c>
- </seealso>.</p>
+ <seeguide marker="logger_chapter#overload_protection"><c>User's Guide</c>
+ </seeguide>.</p>
<p>Notice that when changing the configuration of the handler in runtime, the
disk_log options (<c>file</c>, <c>type</c>, <c>max_no_files</c>,
<c>max_no_bytes</c>) must not be modified.</p>
@@ -159,9 +159,9 @@ erl -kernel logger '[{handler,default,logger_disk_log_h,
<section>
<title>See Also</title>
- <p><seealso marker="logger"><c>logger(3)</c></seealso>,
- <seealso marker="logger_std_h"><c>logger_std_h(3)</c></seealso>,
- <seealso marker="disk_log"><c>disk_log(3)</c></seealso></p>
+ <p><seeerl marker="logger"><c>logger(3)</c></seeerl>,
+ <seeerl marker="logger_std_h"><c>logger_std_h(3)</c></seeerl>,
+ <seeerl marker="disk_log"><c>disk_log(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/logger_filters.xml b/lib/kernel/doc/src/logger_filters.xml
index 0a02342864..ad3ccf7398 100644
--- a/lib/kernel/doc/src/logger_filters.xml
+++ b/lib/kernel/doc/src/logger_filters.xml
@@ -38,15 +38,15 @@
<description>
<p>All functions exported from this module can be used as primary
or handler
- filters. See <seealso marker="logger#add_primary_filter-2">
- <c>logger:add_primary_filter/2</c></seealso>
- and <seealso marker="logger#add_handler_filter-3">
- <c>logger:add_handler_filter/3</c></seealso> for more information
+ filters. See <seemfa marker="logger#add_primary_filter/2">
+ <c>logger:add_primary_filter/2</c></seemfa>
+ and <seemfa marker="logger#add_handler_filter/3">
+ <c>logger:add_handler_filter/3</c></seemfa> for more information
about how filters are added.</p>
- <p>Filters are removed with <seealso marker="logger#remove_primary_filter-1">
- <c>logger:remove_primary_filter/1</c></seealso>
- and <seealso marker="logger#remove_handler_filter-2">
- <c>logger:remove_handler_filter/2</c></seealso>.</p>
+ <p>Filters are removed with <seemfa marker="logger#remove_primary_filter/1">
+ <c>logger:remove_primary_filter/1</c></seemfa>
+ and <seemfa marker="logger#remove_handler_filter/2">
+ <c>logger:remove_handler_filter/2</c></seemfa>.</p>
</description>
<funcs>
@@ -81,15 +81,15 @@ D2 = [otp, sasl]</pre>
Erlang/OTP. D1 specifies that the log event comes from
Erlang/OTP in general, and D2 indicates that the log event
is a so
- called <seealso marker="logger_chapter#sasl_reports">SASL
- report</seealso>.</p>
+ called <seeguide marker="logger_chapter#sasl_reports">SASL
+ report</seeguide>.</p>
<p>The <c><anno>Extra</anno></c> parameter to
the <c>domain/2</c> function is specified when adding the
- filter via <seealso marker="logger#add_primary_filter-2">
- <c>logger:add_primary_filter/2</c></seealso>
- or <seealso marker="logger#add_handler_filter-3">
- <c>logger:add_handler_filter/3</c></seealso>.</p>
+ filter via <seemfa marker="logger#add_primary_filter/2">
+ <c>logger:add_primary_filter/2</c></seemfa>
+ or <seemfa marker="logger#add_handler_filter/3">
+ <c>logger:add_handler_filter/3</c></seemfa>.</p>
<p>The filter compares the value of the <c>domain</c> field in
the log event's metadata (<c>Domain</c>) against
@@ -161,15 +161,15 @@ ok</code>
<p>The <c><anno>Extra</anno></c> parameter is specified when
adding the filter
- via <seealso marker="logger#add_primary_filter-2">
- <c>logger:add_primary_filter/2</c></seealso>
- or <seealso marker="logger#add_handler_filter-3">
- <c>logger:add_handler_filter/3</c></seealso>.</p>
+ via <seemfa marker="logger#add_primary_filter/2">
+ <c>logger:add_primary_filter/2</c></seemfa>
+ or <seemfa marker="logger#add_handler_filter/3">
+ <c>logger:add_handler_filter/3</c></seemfa>.</p>
<p>The filter compares the value of the event's log level
(<c>Level</c>) to <c><anno>MatchLevel</anno></c> by
- calling <seealso marker="logger#compare_levels-2">
- <c>logger:compare_levels(Level, MatchLevel)</c></seealso>.
+ calling <seemfa marker="logger#compare_levels/2">
+ <c>logger:compare_levels(Level, MatchLevel)</c></seemfa>.
The filter matches if the value
of <c><anno>Operator</anno></c> is:</p>
@@ -246,7 +246,7 @@ ok</code>
<section>
<title>See Also</title>
<p>
- <seealso marker="logger"><c>logger(3)</c></seealso>
+ <seeerl marker="logger"><c>logger(3)</c></seeerl>
</p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/logger_formatter.xml b/lib/kernel/doc/src/logger_formatter.xml
index 6dc83d24e1..e613632e91 100644
--- a/lib/kernel/doc/src/logger_formatter.xml
+++ b/lib/kernel/doc/src/logger_formatter.xml
@@ -39,11 +39,11 @@
<p>Each Logger handler has a configured formatter specified as a
module and a configuration term. The purpose of the formatter is
to translate the log events to a final printable string
- (<seealso marker="stdlib:unicode#type-chardata"><c>unicode:chardata()</c>
- </seealso>) which can be written to the output device of the
+ (<seetype marker="stdlib:unicode#chardata"><c>unicode:chardata()</c>
+ </seetype>) which can be written to the output device of the
handler. See
- sections <seealso marker="logger_chapter#handlers">Handlers</seealso>
- and <seealso marker="logger_chapter#formatters">Formatters</seealso>
+ sections <seeguide marker="logger_chapter#handlers">Handlers</seeguide>
+ and <seeguide marker="logger_chapter#formatters">Formatters</seeguide>
in the Kernel User's Guide for more information.</p>
<p><c>logger_formatter</c> is the default formatter used by
Logger.</p>
@@ -55,7 +55,7 @@
<name name="config"/>
<desc>
<p>The configuration term for <c>logger_formatter</c> is a
- <seealso marker="stdlib:maps">map</seealso>, and the
+ <seeerl marker="stdlib:maps">map</seeerl>, and the
following keys can be set as configuration parameters:</p>
<taglist>
<tag><marker id="chars_limit"/>
@@ -63,8 +63,8 @@
<item>
<p>A positive integer representing the value of the option
with the same name to be used when calling
- <seealso marker="stdlib:io_lib#format-3">
- <c>io_lib:format/3</c></seealso>.
+ <seemfa marker="stdlib:io_lib#format/3">
+ <c>io_lib:format/3</c></seemfa>.
This value limits the total number of characters printed
for each log event. Notice that this is a soft limit. For a
hard truncation limit, see option <c>max_size</c>.</p>
@@ -78,7 +78,7 @@
format controls ~p and ~w are replaced with ~P and ~W,
respectively, and the value is used as the depth
parameter. For details, see
- <seealso marker="stdlib:io#format-2"><c>io:format/2,3</c></seealso>
+ <seemfa marker="stdlib:io#format/2"><c>io:format/2,3</c></seemfa>
in STDLIB.</p>
<p>Defaults to <c>unlimited</c>.</p>
</item>
@@ -88,11 +88,11 @@
logger_formatter's part of <c>Metadata</c>. The value of
this field is a string similar to the header created by
the
- old <seealso marker="error_logger"><c>error_logger</c></seealso>
+ old <seeerl marker="error_logger"><c>error_logger</c></seeerl>
event handlers. It can be included in the log event by
adding the list <c>[logger_formatter,header]</c> to the
template. See the description of
- the <seealso marker="#type-template"><c>template()</c></seealso>
+ the <seetype marker="#template"><c>template()</c></seetype>
type for more information.</p>
<p>Defaults to <c>false</c>.</p>
</item>
@@ -105,16 +105,16 @@
by <c>chars_limit</c> or <c>depth</c>, it is truncated.</p>
<p>Defaults to <c>unlimited</c>.</p>
</item>
- <tag><c>report_cb = </c><seealso marker="logger#type-report_cb">
- <c>logger:report_cb()</c></seealso></tag>
+ <tag><c>report_cb = </c><seetype marker="logger#report_cb">
+ <c>logger:report_cb()</c></seetype></tag>
<item>
<p>A report callback is used by the formatter to transform
log messages on report form to a format string and
arguments. The report callback can be specified in the
metadata for the log event. If no report callback exists
in metadata, <c>logger_formatter</c> will
- use <seealso marker="logger#format_report-1">
- <c>logger:format_report/1</c></seealso> as default
+ use <seemfa marker="logger#format_report/1">
+ <c>logger:format_report/1</c></seemfa> as default
callback.</p>
<p>If this configuration parameter is set, it replaces
both the default report callback, and any report
@@ -127,8 +127,8 @@
single line. To achieve this, <c>logger_formatter</c>
sets the field width to <c>0</c> for all <c>~p</c>
and <c>~P</c> control sequences in the format a string
- (see <seealso marker="stdlib:io#format-2">
- <c>io:format/2</c></seealso>), and replaces all
+ (see <seemfa marker="stdlib:io#format/2">
+ <c>io:format/2</c></seemfa>), and replaces all
newlines in the message with <c>", "</c>. White spaces
following directly after newlines are removed. Notice
that newlines added by the <c>template</c> parameter are
@@ -136,13 +136,13 @@
<p>Defaults to <c>true</c>.</p>
</item>
<tag><marker id="template"/>
- <c>template = </c><seealso marker="#type-template"><c>template()</c>
- </seealso></tag>
+ <c>template = </c><seetype marker="#template"><c>template()</c>
+ </seetype></tag>
<item>
<p>The template describes how the formatted string is
composed by combining different data values from the log
event. See the description of
- the <seealso marker="#type-template"><c>template()</c></seealso>
+ the <seetype marker="#template"><c>template()</c></seetype>
type for more information about this.</p>
</item>
<tag><c>time_designator = byte()</c></tag>
@@ -153,8 +153,8 @@
<p>Defaults to <c>$T</c>.</p>
<p>The value of this parameter is used as
the <c>time_designator</c> option
- to <seealso marker="stdlib:calendar#system_time_to_rfc3339-2">
- <c>calendar:system_time_to_rcf3339/2</c></seealso>.</p>
+ to <seemfa marker="stdlib:calendar#system_time_to_rfc3339/2">
+ <c>calendar:system_time_to_rcf3339/2</c></seemfa>.</p>
</item>
<tag><c>time_offset = integer() | [byte()]</c></tag>
<item>
@@ -172,14 +172,14 @@
<p>Defaults to an empty string, meaning that timestamps
are displayed in local time. However, for backwards
compatibility, if the SASL configuration
- parameter <seealso marker="sasl:sasl_app#utc_log">
- <c>utc_log</c></seealso><c>=true</c>, the default is
+ parameter <seeapp marker="sasl:sasl_app#utc_log">
+ <c>utc_log</c></seeapp><c>=true</c>, the default is
changed to <c>"Z"</c>, meaning that timestamps are displayed
in UTC.</p>
<p>The value of this parameter is used as
the <c>offset</c> option
- to <seealso marker="stdlib:calendar#system_time_to_rfc3339-2">
- <c>calendar:system_time_to_rcf3339/2</c></seealso>.</p>
+ to <seemfa marker="stdlib:calendar#system_time_to_rfc3339/2">
+ <c>calendar:system_time_to_rcf3339/2</c></seemfa>.</p>
</item>
</taglist>
</desc>
@@ -244,7 +244,7 @@ exit_reason: "It crashed"</code>
<p>Notice that all eight levels can occur in the heading,
not only <c>ERROR</c>, <c>WARNING</c> or <c>INFO</c> as
- <seealso marker="error_logger"><c>error_logger</c></seealso>
+ <seeerl marker="error_logger"><c>error_logger</c></seeerl>
produces. And microseconds are added at the end of the
timestamp.</p>
</item>
@@ -298,14 +298,14 @@ exit_reason: "It crashed"</code>
and <c>{error,term()}</c> if it is faulty.</p>
<p>The following Logger API functions can trigger this callback:</p>
<list>
- <item><seealso marker="logger#add_handler-3">
- <c>logger:add_handler/3</c></seealso></item>
- <item><seealso marker="logger#set_handler_config-2">
- <c>logger:set_handler_config/2,3</c></seealso></item>
- <item><seealso marker="logger#update_handler_config-2">
- <c>logger:update_handler_config/2</c></seealso></item>
- <item><seealso marker="logger#update_formatter_config-2">
- <c>logger:update_formatter_config/2</c></seealso></item>
+ <item><seemfa marker="logger#add_handler/3">
+ <c>logger:add_handler/3</c></seemfa></item>
+ <item><seemfa marker="logger#set_handler_config/2">
+ <c>logger:set_handler_config/2,3</c></seemfa></item>
+ <item><seemfa marker="logger#update_handler_config/2">
+ <c>logger:update_handler_config/2</c></seemfa></item>
+ <item><seemfa marker="logger#update_formatter_config/2">
+ <c>logger:update_formatter_config/2</c></seemfa></item>
</list>
</desc>
</func>
@@ -318,19 +318,19 @@ exit_reason: "It crashed"</code>
<list>
<item>If the message is on report form, it is converted to
<c>{Format,Args}</c> by calling the report callback. See
- section <seealso marker="logger_chapter#log_message">Log
- Message</seealso> in the Kernel User's Guide for more
+ section <seeguide marker="logger_chapter#log_message">Log
+ Message</seeguide> in the Kernel User's Guide for more
information about report callbacks and valid forms of log
messages.</item>
<item>The message size is limited according to the values of
- configuration parameters <seealso marker="#chars_limit">
- <c>chars_limit</c></seealso>
- and <seealso marker="#depth"><c>depth</c></seealso>.</item>
+ configuration parameters <seeerl marker="#chars_limit">
+ <c>chars_limit</c></seeerl>
+ and <seeerl marker="#depth"><c>depth</c></seeerl>.</item>
<item>The full log entry is composed according to
- the <seealso marker="#template"><c>template</c></seealso>.</item>
+ the <seeerl marker="#template"><c>template</c></seeerl>.</item>
<item>If the final string is too long, it is truncated
according to the value of configuration
- parameter <seealso marker="#max_size"><c>max_size</c></seealso>.</item>
+ parameter <seeerl marker="#max_size"><c>max_size</c></seeerl>.</item>
</list>
</desc>
</func>
@@ -339,14 +339,14 @@ exit_reason: "It crashed"</code>
<section>
<title>See Also</title>
<p>
- <seealso marker="stdlib:calendar"><c>calendar(3)</c></seealso>,
- <seealso marker="error_logger"><c>error_logger(3)</c></seealso>,
- <seealso marker="stdlib:io"><c>io(3)</c></seealso>,
- <seealso marker="stdlib:io_lib"><c>io_lib(3)</c></seealso>,
- <seealso marker="logger"><c>logger(3)</c></seealso>,
- <seealso marker="stdlib:maps"><c>maps(3)</c></seealso>,
- <seealso marker="sasl:sasl_app"><c>sasl(6)</c></seealso>,
- <seealso marker="stdlib:unicode"><c>unicode(3)</c></seealso>
+ <seeerl marker="stdlib:calendar"><c>calendar(3)</c></seeerl>,
+ <seeerl marker="error_logger"><c>error_logger(3)</c></seeerl>,
+ <seeerl marker="stdlib:io"><c>io(3)</c></seeerl>,
+ <seeerl marker="stdlib:io_lib"><c>io_lib(3)</c></seeerl>,
+ <seeerl marker="logger"><c>logger(3)</c></seeerl>,
+ <seeerl marker="stdlib:maps"><c>maps(3)</c></seeerl>,
+ <seeapp marker="sasl:sasl_app"><c>sasl(6)</c></seeapp>,
+ <seeerl marker="stdlib:unicode"><c>unicode(3)</c></seeerl>
</p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/logger_std_h.xml b/lib/kernel/doc/src/logger_std_h.xml
index b7d7105fd9..bda052edad 100644
--- a/lib/kernel/doc/src/logger_std_h.xml
+++ b/lib/kernel/doc/src/logger_std_h.xml
@@ -44,27 +44,27 @@
process and the Kernel application alive during high loads of log
events. How overload protection works, and how to configure it, is
described in the
- <seealso marker="logger_chapter#overload_protection"><c>User's Guide</c>
- </seealso>.</p>
+ <seeguide marker="logger_chapter#overload_protection"><c>User's Guide</c>
+ </seeguide>.</p>
<p>To add a new instance of the standard handler, use
- <seealso marker="logger#add_handler-3"><c>logger:add_handler/3</c>
- </seealso>. The handler configuration argument is a map which can contain
+ <seemfa marker="logger#add_handler/3"><c>logger:add_handler/3</c>
+ </seemfa>. The handler configuration argument is a map which can contain
general configuration parameters, as documented in the
- <seealso marker="logger_chapter#handler_configuration"><c>User's Guide</c>
- </seealso>, and handler specific parameters. The specific data
+ <seeguide marker="logger_chapter#handler_configuration"><c>User's Guide</c>
+ </seeguide>, and handler specific parameters. The specific data
is stored in a sub map with the key <c>config</c>, and can contain the
following parameters:</p>
<taglist>
- <tag><marker id="type"/><c>type = standard_io | standard_error | file</c></tag>
+ <tag><marker id="type"/><c>type = standard_io | standard_error | file | {device, io:device()}</c></tag>
<item>
<p>Specifies the log destination.</p>
<p>The value is set when the handler is added, and it cannot
be changed in runtime.</p>
<p>Defaults to <c>standard_io</c>, unless
- parameter <seealso marker="#file"><c>file</c></seealso> is
+ parameter <seeerl marker="#file"><c>file</c></seeerl> is
given, in which case it defaults to <c>file</c>.</p>
</item>
- <tag><marker id="file"/><c>file = </c><seealso marker="file#type-filename"><c>file:filename()</c></seealso></tag>
+ <tag><marker id="file"/><c>file = </c><seetype marker="file#filename"><c>file:filename()</c></seetype></tag>
<item>
<p>This specifies the name of the log file when the handler is
of type <c>file</c>.</p>
@@ -73,11 +73,11 @@
<p>Defaults to the same name as the handler identity, in the
current directory.</p>
</item>
- <tag><marker id="modes"/><c>modes = [</c><seealso marker="file#type-mode"><c>file:mode()</c></seealso><c>]</c></tag>
+ <tag><marker id="modes"/><c>modes = [</c><seetype marker="file#mode"><c>file:mode()</c></seetype><c>]</c></tag>
<item>
<p>This specifies the file modes to use when opening the log
file,
- see <seealso marker="file#open-2"><c>file:open/2</c></seealso>.
+ see <seemfa marker="file#open/2"><c>file:open/2</c></seemfa>.
If <c>modes</c> are not specified, the default list used
is <c>[raw,append,delayed_write]</c>. If <c>modes</c> are
specified, the list replaces the default modes list with the
@@ -111,7 +111,7 @@
<item>
<p>This parameter specifies the number of rotated log file
archives to keep. This has meaning only
- if <seealso marker="#max_no_bytes"><c>max_no_bytes</c></seealso>
+ if <seeerl marker="#max_no_bytes"><c>max_no_bytes</c></seeerl>
is set to an integer value.</p>
<p>The log archives are
named <c>FileName.0</c>, <c>FileName.1</c>,
@@ -129,8 +129,8 @@
shall be compressed or not. If set to <c>true</c>, all
archives are compressed with <c>gzip</c>, and renamed
to <c>FileName.N.gz</c></p>
- <p><c>compress_on_rotate</c> has no meaning if <seealso
- marker="#max_no_bytes"><c>max_no_bytes</c></seealso> has the
+ <p><c>compress_on_rotate</c> has no meaning if <seeerl
+ marker="#max_no_bytes"><c>max_no_bytes</c></seeerl> has the
value <c>infinity</c>.</p>
<p>Defaults to <c>false</c>.</p>
</item>
@@ -162,7 +162,7 @@
<p>If <c>no_repeat</c> is set as value, the repeated file sync operation
is disabled, and it is the operating system settings that determine
how quickly or slowly data is written to disk. The user can also call
- the <seealso marker="logger_std_h#filesync-1"><c>filesync/1</c></seealso>
+ the <seemfa marker="logger_std_h#filesync/1"><c>filesync/1</c></seemfa>
function to perform a file sync.</p>
<p>Defaults to <c>5000</c> milliseconds.</p>
</item>
@@ -170,8 +170,8 @@
<p>Other configuration parameters exist, to be used for customizing
the overload protection behaviour. The same parameters are used both in the
standard handler and the disk_log handler, and are documented in the
- <seealso marker="logger_chapter#overload_protection"><c>User's Guide</c>
- </seealso>.</p>
+ <seeguide marker="logger_chapter#overload_protection"><c>User's Guide</c>
+ </seeguide>.</p>
<p>Notice that if changing the configuration of the handler in
runtime, the <c>type</c>, <c>file</c>, or <c>modes</c> parameters
must not be modified.</p>
@@ -190,7 +190,7 @@ erl -kernel logger '[{handler,default,logger_std_h,
</code>
<p>An example of how to replace the standard handler with a disk_log handler
at startup is found in the
- <seealso marker="logger_disk_log_h"><c>logger_disk_log_h</c></seealso>
+ <seeerl marker="logger_disk_log_h"><c>logger_disk_log_h</c></seeerl>
manual.</p>
</description>
@@ -208,9 +208,9 @@ erl -kernel logger '[{handler,default,logger_std_h,
<section>
<title>See Also</title>
- <p><seealso marker="logger"><c>logger(3)</c></seealso>,
- <seealso marker="logger_disk_log_h">
- <c>logger_disk_log_h(3)</c></seealso></p>
+ <p><seeerl marker="logger"><c>logger(3)</c></seeerl>,
+ <seeerl marker="logger_disk_log_h">
+ <c>logger_disk_log_h(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/net.xml b/lib/kernel/doc/src/net.xml
index 5c64b2b18d..d63df8e8ca 100644
--- a/lib/kernel/doc/src/net.xml
+++ b/lib/kernel/doc/src/net.xml
@@ -122,7 +122,7 @@
<funcs>
<func>
- <name name="gethostname" arity="0"/>
+ <name name="gethostname" arity="0" since="OTP 22.0"/>
<fsummary>Get hostname.</fsummary>
<desc>
<p>Returns the name of the current host.</p>
@@ -136,7 +136,7 @@
<desc>
<p>Address-to-name translation in a protocol-independant manner.</p>
<p>This function is the inverse of
- <seealso marker="#getaddrinfo/1"><c>getaddrinfo</c></seealso>.
+ <seemfa marker="#getaddrinfo/1"><c>getaddrinfo</c></seemfa>.
It converts a socket address to a corresponding host and service.</p>
</desc>
</func>
@@ -150,7 +150,7 @@
<desc>
<p>Network address and service translation.</p>
<p>This function is the inverse of
- <seealso marker="#getnameinfo/1"><c>getnameinfo</c></seealso>.
+ <seemfa marker="#getnameinfo/1"><c>getnameinfo</c></seemfa>.
It converts host and service to a corresponding socket address.</p>
<p>One of the <c>Host</c> and <c>Service</c> may be <c>undefined</c>
but <em>not</em> both.</p>
@@ -201,4 +201,3 @@
</funcs>
</erlref>
-
diff --git a/lib/kernel/doc/src/net_adm.xml b/lib/kernel/doc/src/net_adm.xml
index c3bf9e5476..cbf8222b3f 100644
--- a/lib/kernel/doc/src/net_adm.xml
+++ b/lib/kernel/doc/src/net_adm.xml
@@ -41,7 +41,7 @@
<desc>
<p>Returns the official name of <c><anno>Host</anno></c>, or
<c>{error, <anno>Host</anno>}</c> if no such name is found. See also
- <seealso marker="inet"><c>inet(3)</c></seealso>.</p>
+ <seeerl marker="inet"><c>inet(3)</c></seeerl>.</p>
</desc>
</func>
@@ -50,7 +50,7 @@
<fsummary>Read file <c>.hosts.erlang</c>.</fsummary>
<desc>
<p>Reads file <c>.hosts.erlang</c>, see section
- <seealso marker="#files">Files</seealso>. Returns the hosts in this
+ <seeerl marker="#files">Files</seeerl>. Returns the hosts in this
file as a list. Returns <c>{error, <anno>Reason</anno>}</c> if the
file cannot be read or the Erlang terms on the file cannot be
interpreted.</p>
@@ -73,7 +73,7 @@
<fsummary>Names of Erlang nodes at a host.</fsummary>
<desc>
<p>Similar to <c>epmd -names</c>, see
- <seealso marker="erts:epmd"><c>erts:epmd(1)</c></seealso>.
+ <seecom marker="erts:epmd"><c>erts:epmd(1)</c></seecom>.
<c><anno>Host</anno></c> defaults to the local host. Returns the
names and associated port numbers of the Erlang nodes that
<c>epmd</c> registered at the specified host. Returns
@@ -122,7 +122,7 @@
<fsummary>Lookup and connect to all nodes at specified hosts.</fsummary>
<type name="verbosity"/>
<desc>
- <p>Same as <seealso marker="#world/1"><c>world/0,1</c></seealso>,
+ <p>Same as <seemfa marker="#world/1"><c>world/0,1</c></seemfa>,
but the hosts are specified as argument
instead of being read from <c>.hosts.erlang</c>.</p>
</desc>
diff --git a/lib/kernel/doc/src/net_kernel.xml b/lib/kernel/doc/src/net_kernel.xml
index 419d3cad84..df517517d5 100644
--- a/lib/kernel/doc/src/net_kernel.xml
+++ b/lib/kernel/doc/src/net_kernel.xml
@@ -52,28 +52,28 @@ $ <input>erl -sname foobar</input></pre>
If started with flag <c>-name</c>, the node name is <c>foobar@Host</c>,
where <c>Host</c> is the fully qualified domain name.
For more information, see
- <seealso marker="erts:erl"><c>erl</c></seealso>.</p>
+ <seecom marker="erts:erl"><c>erl</c></seecom>.</p>
<p>Normally, connections are established automatically when
another node is referenced. This functionality can be disabled
by setting Kernel configuration parameter
<c>dist_auto_connect</c> to <c>never</c>, see
- <seealso marker="kernel_app"><c>kernel(6)</c></seealso>. In this case,
+ <seeapp marker="kernel_app"><c>kernel(6)</c></seeapp>. In this case,
connections must be established explicitly by calling
- <seealso marker="#connect_node/1"><c>connect_node/1</c></seealso>.</p>
+ <seemfa marker="#connect_node/1"><c>connect_node/1</c></seemfa>.</p>
<p>Which nodes that are allowed to communicate with each other is handled
by the magic cookie system, see section
- <seealso marker="doc/reference_manual:distributed">Distributed Erlang</seealso>
+ <seeguide marker="system/reference_manual:distributed">Distributed Erlang</seeguide>
in the Erlang Reference Manual.</p>
<warning>
<p>
Starting a distributed node without also specifying
- <seealso marker="erts:erl#proto_dist"><c>-proto_dist inet_tls</c></seealso>
+ <seecom marker="erts:erl#proto_dist"><c>-proto_dist inet_tls</c></seecom>
will expose the node to attacks that may give the attacker
complete access to the node and in extension the cluster.
When using un-secure distributed nodes, make sure that the
network is configured to keep potential attackers out.
- See the <seealso marker="ssl:ssl_distribution">
- Using SSL for Erlang Distribution</seealso> User's Guide
+ See the <seeguide marker="ssl:ssl_distribution">
+ Using SSL for Erlang Distribution</seeguide> User's Guide
for details on how to setup a secure distributed node.
</p>
</warning>
@@ -114,7 +114,7 @@ $ <input>erl -sname foobar</input></pre>
<fsummary>Get <c>net_ticktime</c>.</fsummary>
<desc>
<p>Gets <c>net_ticktime</c> (see
- <seealso marker="kernel_app"><c>kernel(6)</c></seealso>).</p>
+ <seeapp marker="kernel_app"><c>kernel(6)</c></seeapp>).</p>
<p>Defined return values (<c><anno>Res</anno></c>):</p>
<taglist>
<tag><c><anno>NetTicktime</anno></c></tag>
@@ -138,7 +138,7 @@ $ <input>erl -sname foobar</input></pre>
connected to <c><anno>Node</anno></c>.</p>
<p>If <c><anno>Node</anno></c> is a connected node
the return value is the same as from
- <seealso marker="inet#getopts/2"><c>inet:getopts(Sock, Options)</c></seealso>
+ <seemfa marker="inet#getopts/2"><c>inet:getopts(Sock, Options)</c></seemfa>
where <c>Sock</c> is the distribution socket for <c><anno>Node</anno></c>.</p>
<p>Returns <c>ignored</c> if the local node is not alive or
<c>{error, noconnection}</c> if <c><anno>Node</anno></c> is not connected.</p>
@@ -205,7 +205,7 @@ $ <input>erl -sname foobar</input></pre>
<c><anno>Options</anno></c>, see below.</p>
<p>Also, when <c>OptionList == []</c>, only visible nodes, that
is, nodes that appear in the result of
- <seealso marker="erts:erlang#nodes/0"><c>erlang:nodes/0</c></seealso>,
+ <seemfa marker="erts:erlang#nodes/0"><c>erlang:nodes/0</c></seemfa>,
are monitored.</p>
<p><c><anno>Option</anno></c> can be any of the following:</p>
<taglist>
@@ -272,7 +272,7 @@ $ <input>erl -sname foobar</input></pre>
<fsummary>Set <c>net_ticktime</c>.</fsummary>
<desc>
<p>Sets <c>net_ticktime</c> (see
- <seealso marker="kernel_app"><c>kernel(6)</c></seealso>) to
+ <seeapp marker="kernel_app"><c>kernel(6)</c></seeapp>) to
<c><anno>NetTicktime</anno></c> seconds.
<c><anno>TransitionPeriod</anno></c> defaults to <c>60</c>.</p>
<p>Some definitions:</p>
@@ -332,14 +332,14 @@ $ <input>erl -sname foobar</input></pre>
or the atom <c>new</c> to affect the distribution sockets of all
future connected nodes.</p>
<p>The return value is the same as from
- <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso>
+ <seemfa marker="inet#setopts/2"><c>inet:setopts/2</c></seemfa>
or <c>{error, noconnection}</c> if <c><anno>Node</anno></c> is not
a connected node or <c>new</c>.</p>
<p>If <c><anno>Node</anno></c> is <c>new</c> the <c><anno>Options</anno></c>
will then also be added to kernel configration parameters
- <seealso marker="kernel:kernel_app#inet_dist_listen_options">inet_dist_listen_options</seealso>
+ <seeapp marker="kernel:kernel_app#inet_dist_listen_options">inet_dist_listen_options</seeapp>
and
- <seealso marker="kernel:kernel_app#inet_dist_connect_options">inet_dist_connect_options</seealso>.</p>
+ <seeapp marker="kernel:kernel_app#inet_dist_connect_options">inet_dist_connect_options</seeapp>.</p>
<p>Returns <c>ignored</c> if the local node is not alive.</p>
</desc>
</func>
@@ -370,7 +370,7 @@ $ <input>erl -sname foobar</input></pre>
<p>Turns a distributed node into a non-distributed node. For
other nodes in the network, this is the same as the node
going down. Only possible when the net kernel was started using
- <seealso marker="#start/1"><c>start/1</c></seealso>,
+ <seemfa marker="#start/1"><c>start/1</c></seemfa>,
otherwise <c>{error, not_allowed}</c> is returned. Returns
<c>{error, not_found}</c> if the local node is not alive.</p>
</desc>
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 56656d0909..e6703354c5 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -31,6 +31,662 @@
</header>
<p>This document describes the changes made to the Kernel application.</p>
+<section><title>Kernel 7.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The range check for compression pointers in DNS encoding
+ was faulty, which caused incorrect label compression
+ encoding for very large DNS messages; larger than about
+ 16 kBytes, such as AXFR responses. This more than 11 year
+ old bug has now been corrected.</p>
+ <p>
+ Own Id: OTP-13641 Aux Id: PR-2959 </p>
+ </item>
+ <item>
+ <p>
+ Fix of internal links in the <c>erpc</c> documentation.</p>
+ <p>
+ Own Id: OTP-17202 Aux Id: PR-4516 </p>
+ </item>
+ <item>
+ <p>
+ Fix bug where complex seq_trace tokens (that is lists,
+ tuples, maps etc) could becomes corrupted by the GC. The
+ bug was introduced in OTP-21.</p>
+ <p>
+ Own Id: OTP-17209 Aux Id: PR-3039 </p>
+ </item>
+ <item>
+ <p>When running Xref in the <c>modules</c> mode, the
+ Debugger application would show up as a depency for the
+ Kernel applications.</p>
+ <p>
+ Own Id: OTP-17223 Aux Id: GH-4546, PR-4554 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ <seeerl marker="erl_epmd"><c>erl_epmd</c></seeerl> (the
+ epmd client) will now try to reconnect to the local EPMD
+ if the connection is broken.</p>
+ <p>
+ Own Id: OTP-17178 Aux Id: PR-3003 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 7.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ When using the DNS resolver option
+ <c>servfail_retry_timeout</c> it did not honour the
+ overall call time-out in e.g <c>inet_res:getbyname/3</c>.
+ This misbehaviour has now been fixed. Also, the
+ <c>servfail_retry_timeout</c> behaviour has been improved
+ to only be enforced for servers that gives a servfail
+ answer.</p>
+ <p>
+ Own Id: OTP-12960 Aux Id: ERIERL-598, PR-4509 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 7.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The <c>apply</c> call's in <c>logger.hrl</c> are now
+ called with <c>erlang</c> prefix to avoid clashed with
+ local <c>apply/3</c> functions.</p>
+ <p>
+ Own Id: OTP-16976 Aux Id: PR-2807 </p>
+ </item>
+ <item>
+ <p>
+ Fix memory leak in <c>pg</c>.</p>
+ <p>
+ Own Id: OTP-17034 Aux Id: PR-2866 </p>
+ </item>
+ <item>
+ <p>
+ Fix crash in <c>logger_proxy</c> due to stray
+ <c>gen_server:call</c> replies not being handled. The
+ stray replies come when logger is under heavy load and
+ the flow control mechanism is reaching its limit.</p>
+ <p>
+ Own Id: OTP-17038</p>
+ </item>
+ <item>
+ <p>
+ Fixed a bug in <c>erl_epmd:names()</c> that caused it to
+ return the illegal return value <c>noport</c> instead of
+ <c>{error, Reason}</c> where <c>Reason</c> is the actual
+ error reason. This bug also propagated to
+ <c>net_adm:names()</c>.</p>
+ <p>
+ This bug was introduced in <c>kernel</c> version 7.1 (OTP
+ 23.1).</p>
+ <p>
+ Own Id: OTP-17054 Aux Id: ERL-1424 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add export of some resolver documented types.</p>
+ <p>
+ Own Id: OTP-16954 Aux Id: ERIERL-544 </p>
+ </item>
+ <item>
+ <p>
+ Add configurable retry timeout for resolver lookups.</p>
+ <p>
+ Own Id: OTP-16956 Aux Id: ERIERL-547 </p>
+ </item>
+ <item>
+ <p>
+ <c>gen_server:multi_call()</c> has been optimized in the
+ special case of only calling the local node with timeout
+ set to <c>infinity</c>.</p>
+ <p>
+ Own Id: OTP-17058 Aux Id: PR-2887 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 7.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A fallback has been implemented for file:sendfile when
+ using inet_backend socket</p>
+ <p>
+ Own Id: OTP-15187 Aux Id: ERL-1293 </p>
+ </item>
+ <item>
+ <p>
+ Make default TCP distribution honour option
+ <c>backlog</c> in <c>inet_dist_listen_options</c>.</p>
+ <p>
+ Own Id: OTP-16694 Aux Id: PR-2625 </p>
+ </item>
+ <item>
+ <p>
+ Raw option handling for the experimental
+ <c>gen_tcp_socket</c> backend was broken so that all raw
+ options were ignored by for example
+ <c>gen_tcp:listen/2</c>, a bug that now has been fixed.
+ Reported by Jan Uhlig.</p>
+ <p>
+ Own Id: OTP-16743 Aux Id: ERL-1287 </p>
+ </item>
+ <item>
+ <p>
+ Accept fails with inet-backend socket.</p>
+ <p>
+ Own Id: OTP-16748 Aux Id: ERL-1284 </p>
+ </item>
+ <item>
+ <p>
+ Fixed various minor errors in the socket backend of
+ gen_tcp.</p>
+ <p>
+ Own Id: OTP-16754</p>
+ </item>
+ <item>
+ <p>
+ Correct <c>disk_log:truncate/1</c> to count the header.
+ Also correct the documentation to state that
+ <c>disk_log:truncate/1</c> can be used with external disk
+ logs.</p>
+ <p>
+ Own Id: OTP-16768 Aux Id: ERL-1312 </p>
+ </item>
+ <item>
+ <p>
+ Fix erl_epmd:port_please/2,3 type specs to include all
+ possible error values.</p>
+ <p>
+ Own Id: OTP-16783</p>
+ </item>
+ <item>
+ <p>
+ Fix <c>erl -erl_epmd_port</c> to work properly. Before
+ this fix it did not work at all.</p>
+ <p>
+ Own Id: OTP-16785</p>
+ </item>
+ <item>
+ <p>
+ Fix typespec for internal function
+ <c>erlang:seq_trace_info/1</c> to allow <c>term()</c> as
+ returned label. This in turn fixes so that calls to
+ <c>seq_trace:get_token/1</c> can be correctly analyzer by
+ dialyzer.</p>
+ <p>
+ Own Id: OTP-16823 Aux Id: PR-2722 </p>
+ </item>
+ <item>
+ <p>
+ Fix erroneous double registration of processes in
+ <c>pg</c> when distribution is dynamically started.</p>
+ <p>
+ Own Id: OTP-16832 Aux Id: PR-2738 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Make (use of) the socket registry optional (still enabled
+ by default). Its now possible to build OTP with the
+ socket registry turned off, turn it off by setting an
+ environment variable and controlling in runtime (via
+ function calls and arguments when creating sockets).</p>
+ <p>
+ Own Id: OTP-16763</p>
+ </item>
+ <item>
+ <p>
+ <c>erl -remsh nodename</c> no longer requires the
+ hostname to be given when used together with dynamic
+ nodenames.</p>
+ <p>
+ Own Id: OTP-16784</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 7.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix race condition during shutdown when
+ <c>shell_history</c> is enabled. The race condition would
+ trigger crashes in <c>disk_log</c>.</p>
+ <p>
+ Own Id: OTP-16008 Aux Id: PR-2302 </p>
+ </item>
+ <item>
+ <p>
+ Fix the Erlang distribution to handle the scenario when a
+ node connects that can handle message fragmentation but
+ can not handle the atom cache. This bug only affects
+ users that have implemented a custom distribution
+ carrier. It has been present since OTP-21.</p>
+ <p>
+ The <c>DFLAG_FRAGMENT</c> distribution flag was added to
+ the set of flags that can be rejected by a distribution
+ implementation.</p>
+ <p>
+ Own Id: OTP-16284</p>
+ </item>
+ <item>
+ <p>
+ Fix bug where a binary was not allowed to be the format
+ string in calls to <c>logger:log</c>.</p>
+ <p>
+ Own Id: OTP-16395 Aux Id: PR-2444 </p>
+ </item>
+ <item>
+ <p>
+ Fix bug where <c>logger</c> would end up in an infinite
+ loop when trying to log the crash of a handler or
+ formatter.</p>
+ <p>
+ Own Id: OTP-16489 Aux Id: ERL-1134 </p>
+ </item>
+ <item>
+ <p>
+ <c>code:lib_dir/1</c> has been fixed to also return the
+ lib dir for <c>erts</c>.</p>
+ <p>
+ This is been marked as an incompatibility for any
+ application that depended on <c>{error,bad_name}</c> to
+ be returned for <c>erts</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16502</p>
+ </item>
+ <item>
+ <p>
+ The application <c>stop/1</c> callback was not called if
+ the application master of the application terminated.</p>
+ <p>
+ Own Id: OTP-16504 Aux Id: PR-2328 </p>
+ </item>
+ <item>
+ <p>
+ Fix bug in <c>application:loaded_applications/0</c> that
+ could cause it to fail with <c>badarg</c> if for example
+ a concurrent upgrade/downgrade is running.</p>
+ <p>
+ Own Id: OTP-16627 Aux Id: PR-2601 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>A new module <seeerl
+ marker="kernel:erpc"><c>erpc</c></seeerl> has been
+ introduced in the <c>kernel</c> application. The
+ <c>erpc</c> module implements an enhanced subset of the
+ operations provided by the <seeerl
+ marker="kernel:rpc"><c>rpc</c></seeerl> module. Enhanced
+ in the sense that it makes it possible to distinguish
+ between returned value, raised exceptions, and other
+ errors. <c>erpc</c> also has better performance and
+ scalability than the original <c>rpc</c> implementation.
+ This by utilizing the newly introduced <seemfa
+ marker="erts:erlang#spawn_request/5"><c>spawn_request()</c></seemfa>
+ BIF. Also the <c>rpc</c> module benefits from these
+ improvements by utilizing <c>erpc</c> when it is
+ possible. </p><p> This change has been marked as a
+ potential incompatibility since <seemfa
+ marker="kernel:rpc#block_call/5"><c>rpc:block_call()</c></seemfa>
+ now only is guaranteed to block other <c>block_call()</c>
+ operations. The documentation previously claimed that it
+ would block all <c>rpc</c> operations. This has however
+ never been the case. It previously did not block
+ node-local <c>block_call()</c> operations.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-13450 Aux Id: OTP-15251 </p>
+ </item>
+ <item>
+ <p>A client node can receive its node name dynamically
+ from the node that it first connects to. This featured
+ can by used by</p> <list> <item><p>starting with <c>erl
+ -sname undefined</c></p></item> <item><p>erl_interface
+ functions <c>ei_connect_init</c> and friends</p></item>
+ <item><p><c>erl_call -R</c></p></item> </list>
+ <p>
+ Own Id: OTP-13812</p>
+ </item>
+ <item>
+ <p>
+ Improved the printout of single line logger events for
+ most of the OTP behaviours in STDLIB and Kernel. This
+ includes <c>proc_lib</c>, <c>gen_server</c>,
+ <c>gen_event</c>, <c>gen_statem</c>, <c>gen_fsm</c>,
+ <c>supervisor</c>, <c>supervisor_bridge</c> and
+ <c>application</c>.</p>
+ <p>
+ Improved the <seeerl
+ marker="kernel:logger_formatter#chars_limit"><c>chars_limit</c></seeerl>
+ and <seeerl
+ marker="kernel:logger_formatter#depth"><c>depth</c></seeerl>
+ handling in <c>proc_lib</c> and when formatting of
+ exceptions.</p>
+ <p>
+ Own Id: OTP-15299</p>
+ </item>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>Directories can now be opened by <c>file:open/2</c>
+ when passing the <c>directory</c> option.</p>
+ <p>
+ Own Id: OTP-15835 Aux Id: PR-2212 </p>
+ </item>
+ <item>
+ <p>
+ The check of whether to log or not based on the log level
+ in <c>logger</c> has been optimized by using
+ <c>persistent_term</c> to store the log level.</p>
+ <p>
+ Own Id: OTP-15948 Aux Id: PR-2356 </p>
+ </item>
+ <item>
+ <p><c>file:read_file_info/2</c> can now be used on opened
+ files and directories.</p>
+ <p>
+ Own Id: OTP-15956 Aux Id: PR-2231 </p>
+ </item>
+ <item>
+ <p>
+ The <c>-config</c> option to <c>erl</c> now can take
+ multiple config files without repeating the
+ <c>-config</c> option. Example:</p>
+ <p>
+ erl -config sys local</p>
+ <p>
+ Own Id: OTP-16148 Aux Id: PR-2373 </p>
+ </item>
+ <item>
+ <p>
+ Improved node connection setup handshake protocol. Made
+ possible to agree on protocol version without dependence
+ on <c>epmd</c> or other prior knowledge of peer node
+ version. Also added exchange of node incarnation
+ ("creation") values and expanded the distribution
+ capability flag field from 32 to 64 bits.</p>
+ <p>
+ Own Id: OTP-16229</p>
+ </item>
+ <item>
+ <p>The possibility to run Erlang distribution without
+ relying on EPMD has been extended. To achieve this a
+ couple of new options to the inet distribution has been
+ added.</p> <taglist> <tag>-dist_listen false</tag>
+ <item>Setup the distribution channel, but do not listen
+ for incoming connection. This is useful when you want to
+ use the current node to interact with another node on the
+ same machine without it joining the entire
+ cluster.</item> <tag>-erl_epmd_port Port</tag>
+ <item>Configure a default port that the built-in EPMD
+ client should return. This allows the local node to know
+ the port to connect to for any other node in the
+ cluster.</item> </taglist> <p>The <c>erl_epmd</c>
+ callback API has also been extended to allow returning
+ <c>-1</c> as the creation which means that a random
+ creation will be created by the node.</p>
+ <p>In addition a new callback function called
+ <c>listen_port_please</c> has been added that allows the
+ callback to return which listen port the distribution
+ should use. This can be used instead of
+ <c>inet_dist_listen_min/max</c> if the listen port is to
+ be fetched from an external service.</p>
+ <p>
+ Own Id: OTP-16250</p>
+ </item>
+ <item>
+ <p>
+ A first EXPERIMENTAL module that is a <c>socket</c>
+ backend to <c>gen_tcp</c> and <c>inet</c> has been
+ implemented. Others will follow. Feedback will be
+ appreciated.</p>
+ <p>
+ Own Id: OTP-16260 Aux Id: OTP-15403 </p>
+ </item>
+ <item>
+ <p>
+ The new experimental <c>socket</c> module has been moved
+ to the Kernel application.</p>
+ <p>
+ Own Id: OTP-16312</p>
+ </item>
+ <item>
+ <p>
+ Replace usage of deprecated function in the <c>group</c>
+ module.</p>
+ <p>
+ Own Id: OTP-16345</p>
+ </item>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ <item>
+ <p>
+ Update of <seeerl
+ marker="kernel:seq_trace#whatis">sequential
+ tracing</seeerl> to also support other information
+ transfers than message passing.</p>
+ <p>
+ Own Id: OTP-16370 Aux Id: OTP-15251, OTP-15232 </p>
+ </item>
+ <item>
+ <p><c>code:module_status/1</c> now accepts a list of
+ modules. <c>code:module_status/0</c>, which returns the
+ statuses for all loaded modules, has been added.</p>
+ <p>
+ Own Id: OTP-16402</p>
+ </item>
+ <item>
+ <p><c>filelib:wildcard/1,2</c> is now twice as fast when
+ a double star (<c>**</c>) is part of the pattern.</p>
+ <p>
+ Own Id: OTP-16419</p>
+ </item>
+ <item>
+ <p> A new implementation of distributed named process
+ groups has been introduced. It is available in the
+ <seeerl marker="kernel:pg"><c>pg</c></seeerl> module.
+ </p><p> Note that this <c>pg</c> module only has the name
+ in common with the experimental <c>pg</c> module that was
+ present in <c>stdlib</c> up until OTP 17. </p><p> Thanks
+ to Maxim Fedorov for the implementation. </p>
+ <p>
+ Own Id: OTP-16453 Aux Id: PR-2524 </p>
+ </item>
+ <item>
+ <p> The <seeerl marker="kernel:pg2"><c>pg2</c></seeerl>
+ module has been deprecated. It has also been scheduled
+ for removal in OTP 24. </p><p> You are advised to replace
+ the usage of <c>pg2</c> with the newly introduced <seeerl
+ marker="kernel:pg"><c>pg</c></seeerl> module. <c>pg</c>
+ has a similar API, but with a more scalable
+ implementation. </p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16455</p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ The internal hosts file resolver cache <c>inet_hosts</c>
+ has been rewritten to behave better when the hosts file
+ changes. For example the cache is updated per entry
+ instead of cleared and reloaded so lookups do not
+ temporarily fail during reloading, and; when multiple
+ processes simultaneously request reload these are now
+ folded into one instead of all done in sequence. Reported
+ and first solution suggestion by Maxim Fedorov.</p>
+ <p>
+ Own Id: OTP-16487 Aux Id: PR-2516 </p>
+ </item>
+ <item>
+ <p>
+ Add <c>code:all_available/0</c> that can be used to get
+ all available modules.</p>
+ <p>
+ Own Id: OTP-16494</p>
+ </item>
+ <item>
+ <p>
+ As of OTP 23, the distributed <seeerl
+ marker="kernel:disk_log"><c>disk_log</c></seeerl> feature
+ has been deprecated. It has also been scheduled for
+ removal in OTP 24.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16495</p>
+ </item>
+ <item>
+ <p>
+ Add the function <c>code:fetch_docs/1</c> for fetching
+ embedded documentation for aa Erlang module.</p>
+ <p>
+ Own Id: OTP-16499</p>
+ </item>
+ <item>
+ <p>
+ Improve configure for the net nif, which should increase
+ portability.</p>
+ <p>
+ Own Id: OTP-16530 Aux Id: OTP-16464 </p>
+ </item>
+ <item>
+ <p>
+ socket: Socket counters and socket global counters are
+ now represented as maps (instead of property lists).</p>
+ <p>
+ Own Id: OTP-16535</p>
+ </item>
+ <item>
+ <p>
+ The experimental socket module has gotten restrictions
+ removed so now the 'seqpacket' socket type should work
+ for any communication domain (protocol family) where the
+ OS supports it, typically the Unix Domain.</p>
+ <p>
+ Own Id: OTP-16550 Aux Id: ERIERL-476 </p>
+ </item>
+ <item>
+ <p>
+ Allow using custom IO devices in <c>logger_std_h</c>.</p>
+ <p>
+ Own Id: OTP-16563 Aux Id: PR-2523 </p>
+ </item>
+ <item>
+ <p>Added <c>file:del_dir_r/1</c> which deletes a
+ directory together with all of its contents, similar to
+ <c>rm -rf</c> on Unix systems.</p>
+ <p>
+ Own Id: OTP-16570 Aux Id: PR-2565 </p>
+ </item>
+ <item>
+ <p>
+ socket: By default the socket options rcvtimeo and
+ sndtimeo are now disabled. To enable these, OTP now has
+ to be built with the configure option
+ --enable-esock-rcvsndtimeo</p>
+ <p>
+ Own Id: OTP-16620</p>
+ </item>
+ <item>
+ <p>
+ The experimental gen_tcp compatibility code utilizing the
+ socket module could loose buffered data when receiving a
+ specified number of bytes. This bug has been fixed.
+ Reported by Maksim Lapshin on bugs.erlang.org ERL-1234</p>
+ <p>
+ Own Id: OTP-16632 Aux Id: ERL-1234 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 6.5.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix bug in <c>application:loaded_applications/0</c> that
+ could cause it to fail with <c>badarg</c> if for example
+ a concurrent upgrade/downgrade is running.</p>
+ <p>
+ Own Id: OTP-16627 Aux Id: PR-2601 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 6.5.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -103,9 +759,9 @@
<item>
<p>
Fixed a bug causing actual nodedown reason reported by
- <seealso
+ <seemfa
marker="kernel:net_kernel#monitor_nodes/2"><c>net_kernel:monitor_nodes(true,
- [nodedown_reason])</c></seealso> to be lost and replaced
+ [nodedown_reason])</c></seemfa> to be lost and replaced
by the reason <c>killed</c>.</p>
<p>
Own Id: OTP-16216</p>
@@ -881,18 +1537,18 @@
Own Id: OTP-14501</p>
</item>
<item>
- <p> File operations used to accept <seealso
- marker="kernel:file#type-name_all">filenames</seealso>
+ <p> File operations used to accept <seetype
+ marker="kernel:file#name_all">filenames</seetype>
containing null characters (integer value zero). This
caused the name to be truncated and in some cases
arguments to primitive operations to be mixed up.
Filenames containing null characters inside the filename
are now <em>rejected</em> and will cause primitive file
operations to fail. </p> <p> Also environment variable
- operations used to accept <seealso
- marker="kernel:os#type-env_var_name">names</seealso> and
- <seealso
- marker="kernel:os#type-env_var_value">values</seealso> of
+ operations used to accept <seetype
+ marker="kernel:os#env_var_name">names</seetype> and
+ <seetype
+ marker="kernel:os#env_var_value">values</seetype> of
environment variables containing null characters (integer
value zero). This caused operations to silently produce
erroneous results. Environment variable names and values
@@ -903,12 +1559,12 @@
character in environment variable names causing various
problems. <c>$=</c> characters in environment variable
names are now also <em>rejected</em>. </p> <p>Also
- <seealso
- marker="kernel:os#cmd/1"><c>os:cmd/1</c></seealso> now
- reject null characters inside its <seealso
- marker="kernel:os#type-os_command">command</seealso>.
- </p> <p><seealso
- marker="erts:erlang#open_port/2"><c>erlang:open_port/2</c></seealso>
+ <seemfa
+ marker="kernel:os#cmd/1"><c>os:cmd/1</c></seemfa> now
+ reject null characters inside its <seetype
+ marker="kernel:os#os_command">command</seetype>.
+ </p> <p><seemfa
+ marker="erts:erlang#open_port/2"><c>erlang:open_port/2</c></seemfa>
will also reject null characters inside the port name
from now on.</p>
<p>
@@ -989,10 +1645,10 @@
<list>
<item>
<p>A new logging API is added to Erlang/OTP, see the
- <seealso
- marker="kernel:logger"><c>logger(3)</c></seealso> manual
- page, and section <seealso
- marker="kernel:logger_chapter">Logging</seealso> in the
+ <seeerl
+ marker="kernel:logger"><c>logger(3)</c></seeerl> manual
+ page, and section <seeguide
+ marker="kernel:logger_chapter">Logging</seeguide> in the
Kernel User's Guide.</p>
<p>Calls to <c>error_logger</c> are automatically
redirected to the new API, and legacy error logger event
@@ -1059,10 +1715,10 @@
This mainly consists of support for usage of distribution
controller processes (previously only ports could be used
as distribution controllers). For more information see
- <seealso marker="erts:alt_dist#distribution_module">ERTS
+ <seeguide marker="erts:alt_dist#distribution_module">ERTS
User's Guide ➜ How to implement an Alternative Carrier
for the Erlang Distribution ➜ Distribution
- Module</seealso>.</p>
+ Module</seeguide>.</p>
<p>
Own Id: OTP-14459</p>
</item>
@@ -1094,11 +1750,11 @@
<p>The callback module passed as <c>-epmd_module</c> to
erl has been expanded to be able to do name and port
resolving.</p> <p>Documentation has also been added in
- the <seealso
- marker="kernel:erl_epmd"><c>erl_epmd</c></seealso>
- reference manual and ERTS User's Guide <seealso
+ the <seeerl
+ marker="kernel:erl_epmd"><c>erl_epmd</c></seeerl>
+ reference manual and ERTS User's Guide <seeguide
marker="erts:alt_disco">How to Implement an Alternative
- Node Discovery for Erlang Distribution</seealso>.</p>
+ Node Discovery for Erlang Distribution</seeguide>.</p>
<p>
Own Id: OTP-15086 Aux Id: PR-1694 </p>
</item>
@@ -1330,9 +1986,9 @@
Own Id: OTP-13805</p>
</item>
<item>
- <p>Replaced usage of deprecated symbolic <seealso
- marker="erts:erlang#type-time_unit"><c>time
- unit</c></seealso> representations.</p>
+ <p>Replaced usage of deprecated symbolic <seetype
+ marker="erts:erlang#time_unit"><c>time
+ unit</c></seetype> representations.</p>
<p>
Own Id: OTP-13831 Aux Id: OTP-13735 </p>
</item>
@@ -1715,8 +2371,8 @@
</item>
<item>
<p>
- Added <seealso
- marker="kernel:os#perf_counter/1">os:perf_counter/1</seealso>.</p>
+ Added <seemfa
+ marker="kernel:os#perf_counter/1">os:perf_counter/1</seemfa>.</p>
<p>
The perf_counter is a very very cheap and high resolution
timer that can be used to timestamp system events. It
@@ -1827,13 +2483,13 @@
</item>
<item>
<p>
- <seealso
- marker="kernel:gen_tcp#accept/2"><c>gen_tcp:accept/2</c></seealso>
- was not <seealso
+ <seemfa
+ marker="kernel:gen_tcp#accept/2"><c>gen_tcp:accept/2</c></seemfa>
+ was not <seeguide
marker="erts:time_correction#Time_Warp_Safe_Code">time
- warp safe</seealso>. This since it used the same time as
- returned by <seealso
- marker="erts:erlang#now/0"><c>erlang:now/0</c></seealso>
+ warp safe</seeguide>. This since it used the same time as
+ returned by <seemfa
+ marker="erts:erlang#now/0"><c>erlang:now/0</c></seemfa>
when calculating timeout. This has now been fixed.</p>
<p>
Own Id: OTP-13254 Aux Id: OTP-11997, OTP-13222 </p>
@@ -2471,10 +3127,10 @@
and yield without having to set up the heap in a state
that can be garbage collected.</p>
<p>
- The <seealso
- marker="erts:erlang#garbage_collect/2"><c>garbage_collect/2</c></seealso>,
- and <seealso
- marker="erts:erlang#check_process_code/3"><c>check_process_code/3</c></seealso>
+ The <seemfa
+ marker="erts:erlang#garbage_collect/2"><c>garbage_collect/2</c></seemfa>,
+ and <seemfa
+ marker="erts:erlang#check_process_code/3"><c>check_process_code/3</c></seemfa>
BIFs have been introduced. Both taking an option list as
last argument. Using these, one can issue asynchronous
requests.</p>
@@ -2565,14 +3221,14 @@
</item>
<item>
<p>
- Under rare circumstances a process calling <seealso
- marker="kernel:inet#close/1"><c>inet:close/1</c></seealso>,
- <seealso
- marker="kernel:gen_tcp#close/1"><c>gen_tcp:close/1</c></seealso>,
- <seealso
- marker="kernel:gen_udp#close/1"><c>gen_udp:close/1</c></seealso>,
- or <seealso
- marker="kernel:gen_sctp#close/1"><c>gen_sctp:close/1</c></seealso>
+ Under rare circumstances a process calling <seemfa
+ marker="kernel:inet#close/1"><c>inet:close/1</c></seemfa>,
+ <seemfa
+ marker="kernel:gen_tcp#close/1"><c>gen_tcp:close/1</c></seemfa>,
+ <seemfa
+ marker="kernel:gen_udp#close/1"><c>gen_udp:close/1</c></seemfa>,
+ or <seemfa
+ marker="kernel:gen_sctp#close/1"><c>gen_sctp:close/1</c></seemfa>
could hang in the call indefinitely.</p>
<p>
Own Id: OTP-11491</p>
diff --git a/lib/kernel/doc/src/os.xml b/lib/kernel/doc/src/os.xml
index 0500e4cfb3..fc172b4306 100644
--- a/lib/kernel/doc/src/os.xml
+++ b/lib/kernel/doc/src/os.xml
@@ -37,27 +37,11 @@
use, these functions can be of help in enabling a program to run on
most platforms.</p>
- <note>
- <p>
- File operations used to accept filenames containing
- null characters (integer value zero). This caused
- the name to be truncated and in some cases arguments
- to primitive operations to be mixed up. Filenames
- containing null characters inside the filename
- are now <em>rejected</em> and will cause primitive
- file operations to fail.
- </p>
- <p>
- Also environment variable operations used to accept
- names and values of environment variables containing
- null characters (integer value zero). This caused
- operations to silently produce erroneous results.
- Environment variable names and values containing
- null characters inside the name or value are now
- <em>rejected</em> and will cause environment variable
- operations to fail.
- </p>
- </note>
+ <note>
+ <p>The functions in this module will raise a <c>badarg</c> exception
+ if their arguments contain invalid characters according to the
+ description in the "Data Types" section.</p>
+ </note>
</description>
<datatypes>
@@ -66,12 +50,10 @@
<desc>
<p>A string containing valid characters on the specific
OS for environment variable names using
- <seealso marker="file#native_name_encoding/0"><c>file:native_name_encoding()</c></seealso>
- encoding. Note that specifically null characters (integer
- value zero) and <c>$=</c> characters are not allowed.
- However, note that not all invalid characters necessarily
- will cause the primitiv operations to fail, but may instead
- produce invalid results.
+ <seemfa marker="file#native_name_encoding/0"><c>file:native_name_encoding()</c></seemfa>
+ encoding. Null characters (integer value zero) are not allowed. On Unix,
+ <c>=</c> characters are not allowed. On Windows, a <c>=</c> character is only
+ allowed as the very first character in the string.
</p>
</desc>
</datatype>
@@ -80,11 +62,8 @@
<desc>
<p>A string containing valid characters on the specific
OS for environment variable values using
- <seealso marker="file#native_name_encoding/0"><c>file:native_name_encoding()</c></seealso>
- encoding. Note that specifically null characters (integer
- value zero) are not allowed. However, note that not all
- invalid characters necessarily will cause the primitiv
- operations to fail, but may instead produce invalid results.
+ <seemfa marker="file#native_name_encoding/0"><c>file:native_name_encoding()</c></seemfa>
+ encoding. Null characters (integer value zero) are not allowed.
</p>
</desc>
</datatype>
@@ -95,8 +74,8 @@
Assuming that environment variables has been correctly
set, a strings containing valid characters on the specific
OS for environment variable names and values using
- <seealso marker="file#native_name_encoding/0"><c>file:native_name_encoding()</c></seealso>
- encoding. The first <c>$=</c> characters appearing in
+ <seemfa marker="file#native_name_encoding/0"><c>file:native_name_encoding()</c></seemfa>
+ encoding. The first <c>=</c> characters appearing in
the string separates environment variable name (on the
left) from environment variable value (on the right).
</p>
@@ -105,26 +84,23 @@
<datatype>
<name name="os_command"/>
<desc>
- <p>All characters needs to be valid characters on the
- specific OS using
- <seealso marker="file#native_name_encoding/0"><c>file:native_name_encoding()</c></seealso>
- encoding. Note that specifically null characters (integer
- value zero) are not allowed. However, note that not all
- invalid characters not necessarily will cause
- <seealso marker="#cmd/1"><c>os:cmd/1</c></seealso>
- to fail, but may instead produce invalid results.
+ <p>All characters needs to be valid characters on the specific
+ OS using <seemfa
+ marker="file#native_name_encoding/0"><c>file:native_name_encoding()</c></seemfa>
+ encoding. Null characters (integer value zero) are not
+ allowed.
</p>
</desc>
</datatype>
<datatype>
<name name="os_command_opts"/>
<desc>
- <p>Options for <seealso marker="#cmd/2"><c>os:cmd/2</c></seealso></p>
+ <p>Options for <seemfa marker="#cmd/2"><c>os:cmd/2</c></seemfa></p>
<taglist>
<tag><c>max_size</c></tag>
<item>
- <p>The maximum size of the data returned by the <c>os:cmd</c> call.
- See the <seealso marker="#cmd/2"><c>os:cmd/2</c></seealso>
+ <p>The maximum size of the data returned by the <c>os:cmd/2</c> call.
+ See the <seemfa marker="#cmd/2"><c>os:cmd/2</c></seemfa>
documentation for more details.</p>
</item>
</taglist>
@@ -141,11 +117,6 @@
<p>Executes <c><anno>Command</anno></c> in a command shell of the
target OS, captures the standard output of the command,
and returns this result as a string.</p>
- <warning><p>Previous implementation used to allow all characters
- as long as they were integer values greater than or equal to zero.
- This sometimes lead to unwanted results since null characters
- (integer value zero) often are interpreted as string termination. The
- current implementation rejects these.</p></warning>
<p><em>Examples:</em></p>
<code type="none">
LsOut = os:cmd("ls"), % on unix platform
@@ -198,8 +169,8 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
the format <c>"VarName=Value"</c>, where <c>VarName</c> is
the name of the variable and <c>Value</c> its value.</p>
<p>If Unicode filename encoding is in effect (see the
- <seealso marker="erts:erl#file_name_encoding"><c>erl</c> manual
- page</seealso>), the strings can contain characters with
+ <seecom marker="erts:erl#file_name_encoding"><c>erl</c> manual
+ page</seecom>), the strings can contain characters with
codepoints &gt; 255.</p>
</desc>
</func>
@@ -212,8 +183,8 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
<c><anno>VarName</anno></c>, or <c>false</c> if the environment
variable is undefined.</p>
<p>If Unicode filename encoding is in effect (see the
- <seealso marker="erts:erl#file_name_encoding"><c>erl</c> manual
- page</seealso>), the strings <c><anno>VarName</anno></c> and
+ <seecom marker="erts:erl#file_name_encoding"><c>erl</c> manual
+ page</seecom>), the strings <c><anno>VarName</anno></c> and
<c><anno>Value</anno></c> can contain characters with
codepoints &gt; 255.</p>
</desc>
@@ -227,8 +198,8 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
<c><anno>VarName</anno></c>, or <c>DefaultValue</c> if the
environment variable is undefined.</p>
<p>If Unicode filename encoding is in effect (see the
- <seealso marker="erts:erl#file_name_encoding"><c>erl</c> manual
- page</seealso>), the strings <c><anno>VarName</anno></c> and
+ <seecom marker="erts:erl#file_name_encoding"><c>erl</c> manual
+ page</seecom>), the strings <c><anno>VarName</anno></c> and
<c><anno>Value</anno></c> can contain characters with
codepoints &gt; 255.</p>
</desc>
@@ -257,22 +228,13 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
<p>Sets a new <c><anno>Value</anno></c> for environment variable
<c><anno>VarName</anno></c>.</p>
<p>If Unicode filename encoding is in effect (see the
- <seealso marker="erts:erl#file_name_encoding"><c>erl</c> manual
- page</seealso>), the strings <c><anno>VarName</anno></c> and
+ <seecom marker="erts:erl#file_name_encoding"><c>erl</c> manual
+ page</seecom>), the strings <c><anno>VarName</anno></c> and
<c><anno>Value</anno></c> can contain characters with
codepoints &gt; 255.</p>
<p>On Unix platforms, the environment is set using UTF-8 encoding
if Unicode filename translation is in effect. On Windows, the
environment is set using wide character interfaces.</p>
- <note>
- <p>
- <c><anno>VarName</anno></c> is not allowed to contain
- an <c>$=</c> character. Previous implementations used
- to just let the <c>$=</c> character through which
- silently caused erroneous results. Current implementation
- will instead throw a <c>badarg</c> exception.
- </p>
- </note>
</desc>
</func>
@@ -296,7 +258,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
<tag><c>handle</c></tag>
<item>
This signal will notify
- <seealso marker="kernel_app#erl_signal_server"><c>erl_signal_server</c></seealso>
+ <seeapp marker="kernel_app#erl_signal_server"><c>erl_signal_server</c></seeapp>
when it is received by the Erlang runtime system.
</item>
</taglist>
@@ -308,9 +270,9 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
<fsummary>Current OS system time.</fsummary>
<desc>
<p>Returns the current
- <seealso marker="erts:time_correction#OS_System_Time">OS system time</seealso>
+ <seeguide marker="erts:time_correction#OS_System_Time">OS system time</seeguide>
in <c>native</c>
- <seealso marker="erts:erlang#type_time_unit">time unit</seealso>.</p>
+ <seeerl marker="erts:erlang#type_time_unit">time unit</seeerl>.</p>
<note><p>This time is <em>not</em> a monotonically increasing time.</p>
</note>
</desc>
@@ -321,10 +283,10 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
<fsummary>Current OS system time.</fsummary>
<desc>
<p>Returns the current
- <seealso marker="erts:time_correction#OS_System_Time">OS system time</seealso>
+ <seeguide marker="erts:time_correction#OS_System_Time">OS system time</seeguide>
converted into the <c><anno>Unit</anno></c> passed as argument.</p>
<p>Calling <c>os:system_time(<anno>Unit</anno>)</c> is equivalent to
- <seealso marker="erts:erlang#convert_time_unit/3"><c>erlang:convert_time_unit</c></seealso>(<seealso marker="#system_time/0"><c>os:system_time()</c></seealso><c>,
+ <seemfa marker="erts:erlang#convert_time_unit/3"><c>erlang:convert_time_unit</c></seemfa>(<seemfa marker="#system_time/0"><c>os:system_time()</c></seemfa><c>,
native, <anno>Unit</anno>)</c>.</p>
<note><p>This time is <em>not</em> a monotonically increasing time.</p>
</note>
@@ -337,12 +299,12 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
<type_desc variable="Timestamp">Timestamp = {MegaSecs, Secs, MicroSecs}</type_desc>
<desc>
<p>Returns the current
- <seealso marker="erts:time_correction#OS_System_Time">OS system time</seealso>
+ <seeguide marker="erts:time_correction#OS_System_Time">OS system time</seeguide>
in the same format as
- <seealso marker="erts:erlang#timestamp/0"><c>erlang:timestamp/0</c></seealso>.
+ <seemfa marker="erts:erlang#timestamp/0"><c>erlang:timestamp/0</c></seemfa>.
The tuple can be used together with function
- <seealso marker="stdlib:calendar#now_to_universal_time/1"><c>calendar:now_to_universal_time/1</c></seealso>
- or <seealso marker="stdlib:calendar#now_to_local_time/1"><c>calendar:now_to_local_time/1</c></seealso>
+ <seemfa marker="stdlib:calendar#now_to_universal_time/1"><c>calendar:now_to_universal_time/1</c></seemfa>
+ or <seemfa marker="stdlib:calendar#now_to_local_time/1"><c>calendar:now_to_local_time/1</c></seemfa>
to get calendar time. Using the calendar time, together with the
<c>MicroSecs</c> part of the return tuple from this function, allows
you to log time stamps in high resolution and consistent with the
@@ -367,8 +329,8 @@ calendar:now_to_universal_time(TS),
1> <input>io:format("~s~n",[print_time:format_utc_timestamp()]).</input>
29 Apr 2009 9:55:30.051711</pre>
<p>OS system time can also be retreived by
- <seealso marker="#system_time/0"><c>system_time/0</c></seealso> and
- <seealso marker="#system_time/1"><c>system_time/1</c></seealso>.</p>
+ <seemfa marker="#system_time/0"><c>system_time/0</c></seemfa> and
+ <seemfa marker="#system_time/1"><c>system_time/1</c></seemfa>.</p>
</desc>
</func>
@@ -377,7 +339,7 @@ calendar:now_to_universal_time(TS),
<fsummary>Returns a performance counter</fsummary>
<desc>
<p>Returns the current performance counter value in <c>perf_counter</c>
- <seealso marker="erts:erlang#type_time_unit">time unit</seealso>.
+ <seeerl marker="erts:erlang#type_time_unit">time unit</seeerl>.
This is a highly optimized call that might not be traceable.
</p>
</desc>
@@ -409,7 +371,7 @@ calendar:now_to_universal_time(TS),
<p>On Windows, <c><anno>Osname</anno></c> is <c>nt</c>.</p>
<note>
<p>Think twice before using this function. Use module
- <seealso marker="stdlib:filename"><c>filename</c></seealso>
+ <seeerl marker="stdlib:filename"><c>filename</c></seeerl>
if you want to inspect or build filenames in a portable way.
Avoid matching on atom <c><anno>Osname</anno></c>.</p>
</note>
@@ -422,8 +384,8 @@ calendar:now_to_universal_time(TS),
<desc>
<p>Deletes the environment variable <c><anno>VarName</anno></c>.</p>
<p>If Unicode filename encoding is in effect (see the
- <seealso marker="erts:erl#file_name_encoding"><c>erl</c> manual
- page</seealso>), the string <c><anno>VarName</anno></c> can
+ <seecom marker="erts:erl#file_name_encoding"><c>erl</c> manual
+ page</seecom>), the string <c><anno>VarName</anno></c> can
contain characters with codepoints &gt; 255.</p>
</desc>
</func>
diff --git a/lib/kernel/doc/src/part.xml b/lib/kernel/doc/src/part.xml
index 7a7a92b067..dd5d955a78 100644
--- a/lib/kernel/doc/src/part.xml
+++ b/lib/kernel/doc/src/part.xml
@@ -32,6 +32,8 @@
<p></p>
</description>
<xi:include href="introduction_chapter.xml"/>
+ <xi:include href="socket_usage.xml"/>
<xi:include href="logger_chapter.xml"/>
<xi:include href="logger_cookbook.xml"/>
+ <xi:include href="eep48_chapter.xml"/>
</part>
diff --git a/lib/kernel/doc/src/pg.xml b/lib/kernel/doc/src/pg.xml
new file mode 100644
index 0000000000..3ef660e4f3
--- /dev/null
+++ b/lib/kernel/doc/src/pg.xml
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<!-- %ExternalCopyright% -->
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2020</year><year>2020</year>
+ <holder>Maxim Fedorov, WhatsApp Inc.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>pg</title>
+ <prepared>maximfca@gmail.com</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date></date>
+ <rev>A</rev>
+ <file>pg.xml</file>
+ </header>
+ <module since="OTP 23.0">pg</module>
+ <modulesummary>Distributed named process groups.</modulesummary>
+ <description>
+ <p>This module implements process groups. A message can be sent
+ to one, some, or all group members.</p>
+
+ <p>Up until OTP 17 there used to exist an experimental <c>pg</c>
+ module in <c>stdlib</c>. This <c>pg</c> module is not the same
+ module as that experimental <c>pg</c> module, and only share
+ the same module name.</p>
+
+ <p>A group of processes can be accessed by a common name. For
+ example, if there is a group named <c>foobar</c>, there can be a
+ set of processes (which can be located on different nodes) that
+ are all members of the group <c>foobar</c>. There are no special
+ functions for sending a message to the group. Instead, client
+ functions are to be written with the functions
+ <seemfa marker="#get_members/1"><c>get_members/1</c></seemfa> and
+ <seemfa marker="#get_local_members/1"><c>get_local_members/1</c></seemfa>
+ to determine which processes are members of the group.
+ Then the message can be sent to one or more group members.</p>
+ <p>If a member terminates, it is automatically removed from the group.</p>
+
+ <p>A process may join multiple groups. It may join the same group multiple times.
+ It is only allowed to join processes running on local node.
+ </p>
+
+ <p>Process Groups implement strong eventual consistency.
+ Unlike <seeerl marker="pg2"><c>pg2</c></seeerl>, that provides
+ strong ordering guarantees, Process Groups membership view may temporarily
+ diverge. For example, when processes on <c>node1</c> and <c>node2</c>
+ join concurrently, <c>node3</c> and <c>node4</c> may receive updates in
+ a different order.</p>
+
+ <p> Membership view is not transitive. If <c>node1</c> is not directly
+ connected to <c>node2</c>, they will not see each other groups. But if
+ both are connected to <c>node3</c>, <c>node3</c> will have the full view. </p>
+
+ <p>Groups are automatically created when any process joins,
+ and are removed when all processes leave the group. Non-existing group is
+ considered empty (containing no processes).</p>
+
+ <p>Process groups can be organised into multiple scopes. Scopes are
+ completely independent of each other. A process may join any
+ number of groups in any number of scopes. Scopes are designed to
+ decouple single mesh into a set of overlay networks, reducing
+ amount of traffic required to propagate group membership
+ information. Default scope <c>pg</c> is started automatically
+ when <seeapp marker="kernel_app#start_pg"><c>kernel(6)</c></seeapp>
+ is configured to do so.
+ </p>
+
+ <note><p>
+ Scope name is used to register process locally, and to name an ETS table.
+ If there is another process registered under this name, or another ETS table
+ exists, scope fails to start.</p>
+ <p>Local membership is not preserved if scope process exits and
+ restarts. This behaviour is different from
+ <seeerl marker="pg2"><c>pg2</c></seeerl>, that recovers
+ local membership from remote nodes.
+ </p></note>
+
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="group"/>
+ <desc><p>The identifier of a process group.</p></desc>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+
+ <func>
+ <name name="start_link" arity="0" since="OTP 23.0"/>
+ <fsummary>Start the default <c>pg</c> scope.</fsummary>
+ <desc>
+ <p>Starts the default <c>pg</c> scope within supervision tree.
+ Kernel may be configured to do it automatically, see
+ <seeapp marker="kernel_app#start_pg"><c>kernel(6)</c></seeapp>
+ configuration manual.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="start" arity="1" since="OTP 23.0"/>
+ <name name="start_link" arity="1" since="OTP 23.0"/>
+ <fsummary>Start additional scope.</fsummary>
+ <desc>
+ <p>Starts additional scope.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="join" arity="2" since="OTP 23.0"/>
+ <name name="join" arity="3" since="OTP 23.0"/>
+ <fsummary>Join a process or a list of processes to a group.</fsummary>
+ <desc>
+ <p>Joins single process or multiple processes to the
+ group <c>Name</c>. A process can join a group many times and
+ must then leave the group the same number of times.</p>
+ <p><c>PidOrPids</c> may contain the same process multiple times.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="leave" arity="2" since="OTP 23.0"/>
+ <name name="leave" arity="3" since="OTP 23.0"/>
+ <fsummary>Make a process leave a group.</fsummary>
+ <desc>
+ <p>Makes the process <c>PidOrPids</c> leave the group <c>Name</c>.
+ If the process is not a member of the group, <c>not_joined</c> is
+ returned.</p>
+ <p>When list of processes is passed as <c>PidOrPids</c>, function
+ returns <c>not_joined</c> only when all processes of the list
+ are not joined.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="get_local_members" arity="1" since="OTP 23.0"/>
+ <name name="get_local_members" arity="2" since="OTP 23.0"/>
+ <fsummary>Return all local processes in a group.</fsummary>
+ <desc>
+ <p>Returns all processes running on the local node in the
+ group <c>Name</c>. Processes are returned in no specific order.
+ This function is optimised for speed.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="get_members" arity="1" since="OTP 23.0"/>
+ <name name="get_members" arity="2" since="OTP 23.0"/>
+ <fsummary>Return all processes in a group.</fsummary>
+ <desc>
+ <p>Returns all processes in the group <c>Name</c>.
+ Processes are returned in no specific order.
+ This function is optimised for speed.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="which_groups" arity="0" since="OTP 23.0"/>
+ <name name="which_groups" arity="1" since="OTP 23.0"/>
+ <fsummary>Return a list of all known groups.</fsummary>
+ <desc>
+ <p>Returns a list of all known groups.</p>
+ </desc>
+ </func>
+
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seeapp marker="kernel_app"><c>kernel(6)</c></seeapp></p>
+ </section>
+</erlref>
+
diff --git a/lib/kernel/doc/src/pg2.xml b/lib/kernel/doc/src/pg2.xml
index 3f2897b884..0b2f0c6a00 100644
--- a/lib/kernel/doc/src/pg2.xml
+++ b/lib/kernel/doc/src/pg2.xml
@@ -35,6 +35,16 @@
<module since="">pg2</module>
<modulesummary>Distributed named process groups.</modulesummary>
<description>
+ <warning>
+ <p>
+ The <c>pg2</c> module is deprecated as of OTP 23 and scheduled
+ for removal in OTP 24. You are advised to replace the usage of
+ <c>pg2</c> with <seeerl marker="kernel:pg">pg</seeerl>.
+ <c>pg</c> has a similar API, but with an implementation that
+ is more scalable. See the documentation of <c>pg</c> for more
+ information about differences.
+ </p>
+ </warning>
<p>This module implements process groups. Each message can be sent
to one, some, or all group members.</p>
<p>A group of processes can be accessed by a common name. For
@@ -43,14 +53,14 @@
are all members of the group <c>foobar</c>. There are no special
functions for sending a message to the group. Instead, client
functions are to be written with the functions
- <seealso marker="#get_members/1"><c>get_members/1</c></seealso> and
- <seealso marker="#get_local_members/1"><c>get_local_members/1</c></seealso>
+ <seemfa marker="#get_members/1"><c>get_members/1</c></seemfa> and
+ <seemfa marker="#get_local_members/1"><c>get_local_members/1</c></seemfa>
to determine which processes are members of the group.
Then the message can be sent to one or more group members.</p>
<p>If a member terminates, it is automatically removed from the group.</p>
<warning>
<p>This module is used by module
- <seealso marker="disk_log"><c>disk_log</c></seealso> for
+ <seeerl marker="disk_log"><c>disk_log</c></seeerl> for
managing distributed disk logs. The disk log names are used as
group names, which means that some action can be needed
to avoid name clashes.</p>
@@ -143,7 +153,7 @@
is needed. This is useful during development, but in a
target system the server is to be started explicitly. Use the
configuration parameters for
- <seealso marker="kernel_app#start_pg2"><c>kernel(6)</c></seealso>
+ <seeapp marker="kernel_app#start_pg2"><c>kernel(6)</c></seeapp>
for this.</p>
</desc>
</func>
@@ -159,7 +169,7 @@
<section>
<title>See Also</title>
- <p><seealso marker="kernel_app"><c>kernel(6)</c></seealso></p>
+ <p><seeapp marker="kernel_app"><c>kernel(6)</c></seeapp></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/ref_man.xml b/lib/kernel/doc/src/ref_man.xml
index 9df51dee22..333cb83bee 100644
--- a/lib/kernel/doc/src/ref_man.xml
+++ b/lib/kernel/doc/src/ref_man.xml
@@ -43,6 +43,7 @@
<xi:include href="erl_epmd.xml"/>
<xi:include href="erl_prim_loader_stub.xml"/>
<xi:include href="erlang_stub.xml"/>
+ <xi:include href="erpc.xml"/>
<xi:include href="error_handler.xml"/>
<xi:include href="error_logger.xml"/>
<xi:include href="file.xml"/>
@@ -64,9 +65,11 @@
<xi:include href="net_adm.xml"/>
<xi:include href="net_kernel.xml"/>
<xi:include href="os.xml"/>
+ <xi:include href="pg.xml"/>
<xi:include href="pg2.xml"/>
<xi:include href="rpc.xml"/>
<xi:include href="seq_trace.xml"/>
+ <xi:include href="socket.xml"/>
<xi:include href="user.xml"/>
<xi:include href="wrap_log_reader.xml"/>
<xi:include href="zlib_stub.xml"/>
diff --git a/lib/kernel/doc/src/rpc.xml b/lib/kernel/doc/src/rpc.xml
index 8dd0ea0093..a3e96e99f4 100644
--- a/lib/kernel/doc/src/rpc.xml
+++ b/lib/kernel/doc/src/rpc.xml
@@ -37,14 +37,30 @@
a function on a remote node and collect the answer. It is used
for collecting information on a remote node, or for running a
function with some specific side effects on the remote node.</p>
+ <note><p>
+ <c>rpc:call()</c> and friends makes it quite hard to distinguish
+ between successful results, raised exceptions, and other errors.
+ This cannot be changed due to compatibility reasons. As of OTP 23,
+ a new module <seeerl marker="erpc"><c>erpc</c></seeerl> was
+ introduced in order to provide an API that makes it possible
+ to distingush between the different results. The <c>erpc</c>
+ module provides a subset (however, the central subset) of the
+ functionality available in the <c>rpc</c> module. The <c>erpc</c>
+ implementation also provides a more scalable implementation with
+ better performance than the original <c>rpc</c> implementation.
+ However, since the introduction of <c>erpc</c>, the <c>rpc</c>
+ module implements large parts of its central functionality using
+ <c>erpc</c>, so the <c>rpc</c> module wont not suffer scalability
+ wise and performance wise compared to <c>erpc</c>.
+ </p></note>
</description>
<datatypes>
<datatype>
<name name="key"/>
<desc>
- <p>As returned by
- <seealso marker="#async_call/4"><c>async_call/4</c></seealso>.</p>
+ <p>Opaque value returned by
+ <seemfa marker="#async_call/4"><c>async_call/4</c></seemfa>.</p>
</desc>
</datatype>
</datatypes>
@@ -83,50 +99,68 @@
promise to deliver the answer.</p>
<p>In this case, the key <c><anno>Key</anno></c> is returned, which
can be used in a subsequent call to
- <seealso marker="#yield/1"><c>yield/1</c></seealso> or
- <seealso marker="#nb_yield/1"><c>nb_yield/1,2</c></seealso>
+ <seemfa marker="#yield/1"><c>yield/1</c></seemfa> or
+ <seemfa marker="#nb_yield/1"><c>nb_yield/1,2</c></seemfa>
to retrieve the value of evaluating <c>apply(<anno>Module</anno>,
<anno>Function</anno>, <anno>Args</anno>)</c> on node
- <c><anno>Node</anno></c>.</p>
+ <c><anno>Node</anno></c>.</p>
+ <note>
+ <p>
+ If you want the ability to distinguish between results,
+ you may want to consider using the
+ <seemfa marker="erpc#send_request/4"><c>erpc:send_request()</c></seemfa>
+ function from the <c>erpc</c> module instead. This also
+ gives you the ability retrieve the results in other useful
+ ways.
+ </p>
+ </note>
<note>
- <p><seealso marker="#yield/1"><c>yield/1</c></seealso> and
- <seealso marker="#nb_yield/1"><c>nb_yield/1,2</c></seealso>
+ <p><seemfa marker="#yield/1"><c>yield/1</c></seemfa> and
+ <seemfa marker="#nb_yield/1"><c>nb_yield/1,2</c></seemfa>
must be called by the same process from which this function
was made otherwise they will never yield correctly.</p>
</note>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be an <c>rpc</c> server, another server, or a freshly
+ spawned process.
+ </p>
+ </note>
</desc>
</func>
<func>
<name name="block_call" arity="4" since=""/>
- <fsummary>Evaluate a function call on a node in the RPC server's
- context.</fsummary>
- <desc>
- <p>Same as <seealso marker="#call/4"><c>call/4</c></seealso>,
- but the RPC server at <c><anno>Node</anno></c> does
- not create a separate process to handle the call. Thus,
- this function can be used if the intention of the call is to
- block the RPC server from any other incoming requests until
- the request has been handled. The function can also be used
- for efficiency reasons when very small fast functions are
- evaluated, for example, BIFs that are guaranteed not to
- suspend.</p>
- <p>See the note in <seealso marker="#call/4"><c>call/4</c></seealso>
- for more details of the return value.</p>
+ <fsummary>Evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>
+ The same as calling
+ <seemfa marker="#block_call/5"><c>rpc:block_call(<anno>Node</anno>,
+ <anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>, infinity)</c></seemfa>.
+ </p>
</desc>
</func>
<func>
<name name="block_call" arity="5" since=""/>
- <fsummary>Evaluate a function call on a node in the RPC server's
- context.</fsummary>
+ <fsummary>Evaluate a function call on a node.</fsummary>
<desc>
- <p>Same as
- <seealso marker="#block_call/4"><c>block_call/4</c></seealso>,
- but with a time-out value in the same manner as
- <seealso marker="#call/5"><c>call/5</c></seealso>.</p>
- <p>See the note in <seealso marker="#call/4"><c>call/4</c></seealso>
- for more details of the return value.</p>
+ <p>
+ The same as calling
+ <seemfa marker="#call/5"><c>rpc:call(<anno>Node</anno>,
+ <anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>, <anno>Timeout</anno>)</c></seemfa> with
+ the exception that it also blocks other <c>rpc:block_call()</c>
+ operations from executing concurrently on the node
+ <c><anno>Node</anno></c>.
+ </p>
+ <warning><p>
+ Note that it also blocks other operations than just
+ <c>rpc:block_call()</c> operations, so use it with care.
+ </p></warning>
</desc>
</func>
@@ -135,9 +169,38 @@
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>)</c> on node <c><anno>Node</anno></c> and returns
+ the corresponding value <c><anno>Res</anno></c>, or
+ <c>{badrpc, <anno>Reason</anno>}</c> if the call fails.
+ The same as calling
+ <seemfa marker="#call/5"><c>rpc:call(<anno>Node</anno>,
+ <anno>Module</anno>, <anno>Function</anno>,
+ <anno>Args</anno>, infinity)</c></seemfa>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="call" arity="5" since=""/>
+ <fsummary>Evaluate a function call on a node.</fsummary>
+ <desc>
+ <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
<anno>Args</anno>)</c> on node <c><anno>Node</anno></c> and returns
the corresponding value <c><anno>Res</anno></c>, or
- <c>{badrpc, <anno>Reason</anno>}</c> if the call fails.</p>
+ <c>{badrpc, <anno>Reason</anno>}</c> if the call fails.
+ <c><anno>Timeout</anno></c> is
+ a time-out value in milliseconds. If the call times out,
+ <c><anno>Reason</anno></c> is <c>timeout</c>.</p>
+ <p>If the reply arrives after the call times out, no message
+ contaminates the caller's message queue.</p>
+ <note>
+ <p>
+ If you want the ability to distinguish between results,
+ you may want to consider using the
+ <seemfa marker="erpc#call/4"><c>erpc:call()</c></seemfa>
+ function from the <c>erpc</c> module instead.
+ </p>
+ </note>
<note>
<p>Here follows the details of what exactly is returned.</p>
<p><c>{badrpc, <anno>Reason</anno>}</c> will be returned in the
@@ -159,28 +222,14 @@
<strong>not</strong> match <c>{'EXIT',_}</c>.</item>
</list>
</note>
- </desc>
- </func>
-
- <func>
- <name name="call" arity="5" since=""/>
- <fsummary>Evaluate a function call on a node.</fsummary>
- <desc>
- <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
- <anno>Args</anno>)</c> on node <c><anno>Node</anno></c> and returns
- the corresponding value <c><anno>Res</anno></c>, or
- <c>{badrpc, <anno>Reason</anno>}</c> if the call fails.
- <c><anno>Timeout</anno></c> is
- a time-out value in milliseconds. If the call times out,
- <c><anno>Reason</anno></c> is <c>timeout</c>.
- See the note in <seealso marker="#call/4"><c>call/4</c></seealso>
- for more details of the return value.</p>
- <p>If the reply arrives after the call times out, no message
- contaminates the caller's message queue, as this
- function spawns off a middleman process to act as (a void)
- destination for such an orphan reply. This feature also makes
- this function more expensive than <c>call/4</c> at
- the caller's end.</p>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be the calling process itself, an <c>rpc</c> server,
+ another server, or a freshly spawned process.
+ </p>
+ </note>
</desc>
</func>
@@ -193,7 +242,15 @@
<c><anno>Node</anno></c>. No response is delivered and the calling
process is not suspended until the evaluation is complete, as
is the case with
- <seealso marker="#call/4"><c>call/4,5</c></seealso>.</p>
+ <seemfa marker="#call/4"><c>call/4,5</c></seemfa>.</p>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be an <c>rpc</c> server, another server, or a
+ freshly spawned process.
+ </p>
+ </note>
</desc>
</func>
@@ -226,7 +283,7 @@
<anno>Name</anno>, <anno>Msg</anno>)</c>.</p>
</desc>
</func>
-
+
<func>
<name name="multi_server_call" arity="3" since=""/>
<fsummary>Interact with the servers on a number of nodes.</fsummary>
@@ -311,6 +368,22 @@
{ResL, _} = rpc:multicall(code, load_binary, [Mod, File, Bin]),
%% and then maybe check the ResL list.</code>
+ <note>
+ <p>
+ If you want the ability to distinguish between results,
+ you may want to consider using the
+ <seemfa marker="erpc#multicall/4"><c>erpc:multicall()</c></seemfa>
+ function from the <c>erpc</c> module instead.
+ </p>
+ </note>
+ <note>
+ <p>
+ You cannot make <em>any</em> assumptions about the
+ process that will perform the <c>apply()</c>. It may
+ be the calling process itself, an <c>rpc</c> server,
+ another server, or a freshly spawned process.
+ </p>
+ </note>
</desc>
</func>
@@ -329,15 +402,15 @@
(non-blocking).</fsummary>
<desc>
<p>Non-blocking version of
- <seealso marker="#yield/1"><c>yield/1</c></seealso>. It returns
+ <seemfa marker="#yield/1"><c>yield/1</c></seemfa>. It returns
the tuple <c>{value, <anno>Val</anno>}</c> when the computation is
finished, or <c>timeout</c> when <c><anno>Timeout</anno></c>
milliseconds has elapsed.</p>
- <p>See the note in <seealso marker="#call/4"><c>call/4</c></seealso>
+ <p>See the note in <seemfa marker="#call/4"><c>call/4</c></seemfa>
for more details of <anno>Val</anno>.</p>
<note>
<p>This function must be called by the same process from which
- <seealso marker="#async_call/4"><c>async_call/4</c></seealso>
+ <seemfa marker="#async_call/4"><c>async_call/4</c></seemfa>
was made otherwise it will only return <c>timeout</c>.</p>
</note>
</desc>
@@ -361,7 +434,7 @@
<fsummary>Information about a process.</fsummary>
<desc>
<p>Location transparent version of the BIF
- <seealso marker="erts:erlang#process_info/1"><c>erlang:process_info/1</c></seealso> in ERTS.</p>
+ <seemfa marker="erts:erlang#process_info/1"><c>erlang:process_info/1</c></seemfa> in ERTS.</p>
</desc>
</func>
@@ -371,7 +444,7 @@
<fsummary>Information about a process.</fsummary>
<desc>
<p>Location transparent version of the BIF
- <seealso marker="erts:erlang#process_info/2"><c>erlang:process_info/2</c></seealso> in ERTS.</p>
+ <seemfa marker="erts:erlang#process_info/2"><c>erlang:process_info/2</c></seemfa> in ERTS.</p>
</desc>
</func>
@@ -444,16 +517,16 @@
(blocking).</fsummary>
<desc>
<p>Returns the promised answer from a previous
- <seealso marker="#async_call/4"><c>async_call/4</c></seealso>.
+ <seemfa marker="#async_call/4"><c>async_call/4</c></seemfa>.
If the answer is available, it is
returned immediately. Otherwise, the calling process is
suspended until the answer arrives from <c>Node</c>.</p>
<note>
<p>This function must be called by the same process from which
- <seealso marker="#async_call/4"><c>async_call/4</c></seealso>
+ <seemfa marker="#async_call/4"><c>async_call/4</c></seemfa>
was made otherwise it will never return.</p>
</note>
- <p>See the note in <seealso marker="#call/4"><c>call/4</c></seealso>
+ <p>See the note in <seemfa marker="#call/4"><c>call/4</c></seemfa>
for more details of the return value.</p>
</desc>
</func>
diff --git a/lib/kernel/doc/src/seq_trace.xml b/lib/kernel/doc/src/seq_trace.xml
index f7a9e4d5df..b20a1f2f9d 100644
--- a/lib/kernel/doc/src/seq_trace.xml
+++ b/lib/kernel/doc/src/seq_trace.xml
@@ -29,14 +29,15 @@
<rev>A</rev>
</header>
<module since="">seq_trace</module>
- <modulesummary>Sequential tracing of messages.</modulesummary>
+ <modulesummary>Sequential tracing of information transfers.</modulesummary>
<description>
- <p>Sequential tracing makes it possible to trace all messages
- resulting from one initial message. Sequential tracing is
+ <p>Sequential tracing makes it possible to trace information
+ flows between processes resulting from one initial transfer
+ of information. Sequential tracing is
independent of the ordinary tracing in Erlang, which
is controlled by the <c>erlang:trace/3</c> BIF. For more information
about what sequential tracing is and how it can be used, see section
- <seealso marker="#whatis">Sequential Tracing</seealso>.</p>
+ <seeerl marker="#whatis">Sequential Tracing</seeerl>.</p>
<p><c>seq_trace</c> provides functions that control all aspects of
sequential tracing. There are functions for activation,
deactivation, inspection, and for collection of the trace output.</p>
@@ -104,13 +105,13 @@ seq_trace:set_token(OldToken), % activate the trace token again
<tag><c>set_token(send, <anno>Bool</anno>)</c></tag>
<item>
<p>A trace token flag (<c>true | false</c>) which
- enables/disables tracing on message sending. Default is
+ enables/disables tracing on information sending. Default is
<c>false</c>.</p>
</item>
<tag><c>set_token('receive', <anno>Bool</anno>)</c></tag>
<item>
<p>A trace token flag (<c>true | false</c>) which
- enables/disables tracing on message reception. Default is
+ enables/disables tracing on information reception. Default is
<c>false</c>.</p>
</item>
<tag><c>set_token(print, <anno>Bool</anno>)</c></tag>
@@ -131,8 +132,8 @@ seq_trace:set_token(OldToken), % activate the trace token again
enables/disables a strict monotonic timestamp to be generated
for each traced event. Default is <c>false</c>. Timestamps will
consist of
- <seealso marker="erts:time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso> and a monotonically increasing
+ <seeguide marker="erts:time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide> and a monotonically increasing
integer. The time-stamp has the same format and value
as produced by <c>{erlang:monotonic_time(nanosecond),
erlang:unique_integer([monotonic])}</c>.</p>
@@ -143,8 +144,8 @@ seq_trace:set_token(OldToken), % activate the trace token again
enables/disables a strict monotonic timestamp to be generated
for each traced event. Default is <c>false</c>. Timestamps
will use
- <seealso marker="erts:time_correction#Erlang_Monotonic_Time">Erlang
- monotonic time</seealso>. The time-stamp has the same
+ <seeguide marker="erts:time_correction#Erlang_Monotonic_Time">Erlang
+ monotonic time</seeguide>. The time-stamp has the same
format and value as produced by
<c>erlang:monotonic_time(nanosecond)</c>.</p>
</item>
@@ -177,7 +178,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
<desc>
<p>Returns the value of the trace token component
<c>Component</c>. See
- <seealso marker="#set_token/2">set_token/2</seealso> for
+ <seemfa marker="#set_token/2">set_token/2</seemfa> for
possible values of <c>Component</c> and <c>Val</c>.</p>
</desc>
</func>
@@ -218,7 +219,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
<type name="tracer"/>
<desc>
<p>Sets the system tracer. The system tracer can be either a
- process, port or <seealso marker="erts:erl_tracer">tracer module</seealso>
+ process, port or <seeerl marker="erts:erl_tracer">tracer module</seeerl>
denoted by <c><anno>Tracer</anno></c>.
Returns the previous value (which can be <c>false</c> if no system
tracer is active).</p>
@@ -257,12 +258,26 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
<tag><c>{send, Serial, From, To, Message}</c></tag>
<item>
<p>Used when a process <c>From</c> with its trace token flag
- <c>send</c> set to <c>true</c> has sent a message.</p>
+ <c>send</c> set to <c>true</c> has sent information. <c>To</c>
+ may be a process identifier, a registered name on a node
+ represented as <c>{NameAtom, NodeAtom}</c>, or a node name
+ represented as an atom. <c>From</c> may be a process identifier
+ or a node name represented as an atom. <c>Message</c> contains
+ the information passed along in this information transfer. If
+ the transfer is done via message passing, it is the actual
+ message.
+ </p>
</item>
<tag><c>{'receive', Serial, From, To, Message}</c></tag>
<item>
- <p>Used when a process <c>To</c> receives a message with a
- trace token that has flag <c>'receive'</c> set to <c>true</c>.</p>
+ <p>Used when a process <c>To</c> receives information with a
+ trace token that has flag <c>'receive'</c> set to <c>true</c>.
+ <c>To</c> may be a process identifier, or a node name
+ represented as an atom. <c>From</c> may be a process identifier
+ or a node name represented as an atom. <c>Message</c> contains
+ the information passed along in this information transfer. If
+ the transfer is done via message passing, it is the actual
+ message.</p>
</item>
<tag><c>{print, Serial, From, _, Info}</c></tag>
<item>
@@ -276,7 +291,7 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
where:</p>
<list type="bulleted">
<item><p>Integer <c>PreviousSerial</c> denotes the serial
- counter passed in the last received message that carried a trace
+ counter passed in the last received information that carried a trace
token. If the process is the first in a new sequential trace,
<c>PreviousSerial</c> is set to the value of the process internal
"trace clock".</p></item>
@@ -290,22 +305,32 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
<section>
<marker id="whatis"></marker>
<title>Sequential Tracing</title>
- <p>Sequential tracing is a way to trace a sequence of messages sent
- between different local or remote processes, where the sequence
- is initiated by a single message. In short, it works as follows:</p>
+ <p>Sequential tracing is a way to trace a sequence of information
+ transfers between different local or remote processes, where the
+ sequence is initiated by a single transfer. The typical information
+ transfer is an ordinary Erlang message passed between two processes,
+ but information is transferred also in other ways. In short, it works
+ as follows:</p>
<p>Each process has a <em>trace token</em>, which can be empty or
not empty. When not empty, the trace token can be seen as
the tuple <c>{Label, Flags, Serial, From}</c>. The trace token is
- passed invisibly with each message.</p>
+ passed invisibly when information is passed between processes.
+ In most cases the information is passed in ordinary messages
+ between processes, but information is also passed between processes
+ by other means. For example, by spawning a new process. An information
+ transfer between two processes is represented by a send event and a
+ receive event regardless of how it is passed.
+ </p>
<p>To start a sequential trace, the user must explicitly set
- the trace token in the process that will send the first message
+ the trace token in the process that will send the first information
in a sequence.</p>
<p>The trace token of a process is set each time the process
+ receives information. This is typically when the process
matches a message in a receive statement, according to the trace
token carried by the received message, empty or not.</p>
<p>On each Erlang node, a process can be set as the <em>system tracer</em>.
This process will receive trace messages each time
- a message with a trace token is sent or received (if the trace
+ information with a trace token is sent or received (if the trace
token flag <c>send</c> or <c>'receive'</c> is set). The system
tracer can then print each trace event, write it to a file, or
whatever suitable.</p>
@@ -321,11 +346,58 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
</section>
<section>
+ <title>Different Information Transfers</title>
+ <p>
+ Information flows between processes in a lot of different
+ ways. Not all flows of information will be covered by
+ sequential tracing. One example is information passed via
+ ETS tables. Below is a list of information paths that are
+ covered by sequential tracing:</p>
+ <taglist>
+ <tag>Message Passing</tag>
+ <item><p>
+ All ordinary messages passed between Erlang processes.
+ </p></item>
+ <tag>Exit signals</tag>
+ <item><p>
+ An exit signal is represented as an <c>{'EXIT', Pid, Reason}</c>
+ tuple.
+ </p></item>
+ <tag>Process Spawn</tag>
+ <item><p>
+ A process spawn is represented as multiple information
+ transfers. At least one spawn request and one spawn reply. The
+ actual amount of information transfers depends on what type
+ of spawn it is and may also change in future implementations.
+ Note that this is more or less an internal protocol that you
+ are peeking at. The spawn request will be represented as a
+ tuple with the first element containing the atom
+ <c>spawn_request</c>, but this is more or less all that you
+ can depend on.
+ </p></item>
+ </taglist>
+ <note>
+ <p>
+ If you do ordinary <c>send</c> or <c>receive</c>
+ trace on the system, you will only see ordinary message
+ passing, not the other information transfers listed above.
+ </p>
+ </note>
+ <note>
+ <p>
+ When a send event and corresponding receive event do not
+ both correspond to ordinary Erlang messages, the <c>Message</c>
+ part of the trace messages may not be identical. This since
+ all information not necessarily are available when generating
+ the trace messages.
+ </p>
+ </note>
+ </section>
+
+ <section>
<title>Trace Token</title>
- <p>Each process has a current trace token. Initially, the token is
- empty. When the process sends a message to another process, a
- copy of the current token is sent "invisibly" along with
- the message.</p>
+ <p>Each process has a current trace token which is "invisibly" passed
+ from the parent process on creation of the process.</p>
<p>The current token of a process is set in one of the following two
ways:</p>
<list type="bulleted">
@@ -334,7 +406,9 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
<c>seq_trace:set_token/1,2</c></p>
</item>
<item>
- <p>When a message is received</p>
+ <p>When information is received. This is typically when
+ a received message is matched out in a receive expression,
+ but also when information is received in other ways.</p>
</item>
</list>
<p>In both cases, the current token is set. In particular, if
@@ -354,12 +428,16 @@ TimeStamp = {Seconds, Milliseconds, Microseconds}
<p>The algorithm for updating <c>Serial</c> can be described as
follows:</p>
<p>Let each process have two counters, <c>prev_cnt</c> and
- <c>curr_cnt</c>, both are set to <c>0</c> when a process is created.
- The counters are updated at the following occasions:</p>
+ <c>curr_cnt</c>, both are set to <c>0</c> when a process is created
+ outside of a trace sequence. The counters are updated at the following
+ occasions:</p>
<list type="bulleted">
<item>
- <p><em>When the process is about to send a message and the trace token
- is not empty.</em></p>
+ <p><em>When the process is about to pass along information to
+ another process and the trace token is not empty.</em> This
+ typically occurs when sending a message, but also, for example,
+ when spawning another process.
+ </p>
<p>Let the serial of the trace token be <c>tprev</c> and
<c>tcurr</c>.</p>
<pre>
@@ -367,7 +445,7 @@ curr_cnt := curr_cnt + 1
tprev := prev_cnt
tcurr := curr_cnt</pre>
<p>The trace token with <c>tprev</c> and <c>tcurr</c> is then
- passed along with the message.</p>
+ passed along with the information passed to the other process.</p>
</item>
<item>
<p><em>When the process calls</em> <c>seq_trace:print(Label, Info)</c>,
@@ -376,8 +454,9 @@ tcurr := curr_cnt</pre>
<p>The algorithm is the same as for send above.</p>
</item>
<item>
- <p><em>When a message is received and contains a non-empty trace
- token.</em></p>
+ <p><em>When information is received that also contains a non-empty
+ trace token. For example, when a message is matched out in a
+ receive expression, or when a new process is spawned.</em></p>
<p>The process trace token is set to the trace token from
the message.</p>
<p>Let the serial of the trace token be <c>tprev</c> and
@@ -487,9 +566,9 @@ tracer() ->
print_trace(Label,TraceInfo,false);
{seq_trace,Label,TraceInfo,Ts} ->
print_trace(Label,TraceInfo,Ts);
- Other -> ignore
+ _Other -> ignore
end,
- tracer().
+ tracer().
print_trace(Label,TraceInfo,false) ->
io:format("~p:",[Label]),
@@ -504,7 +583,7 @@ print_trace({'receive',Serial,From,To,Message}) ->
io:format("~p Received ~p FROM ~p WITH~n~p~n",
[To,Serial,From,Message]);
print_trace({send,Serial,From,To,Message}) ->
- io:format("~p Sent ~p TO ~p WITH~n~p~n",
+ io:format("~p Sent ~p TO ~p WITH~n~p~n",
[From,Serial,To,Message]).</code>
<p>The code that creates a process that runs this tracer function
and sets that process as the system tracer can look like this:</p>
diff --git a/erts/doc/src/socket.xml b/lib/kernel/doc/src/socket.xml
index e5a0be2879..66c149ebaa 100644
--- a/erts/doc/src/socket.xml
+++ b/lib/kernel/doc/src/socket.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2018</year><year>2019</year>
+ <year>2018</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -38,19 +38,19 @@
<p>The intent is that it shall be as "close as possible" to the OS
level socket interface. The only significant addition is that some of
the functions,
- e.g. <seealso marker="#recv/3"><c>recv/3</c></seealso>,
+ e.g. <seemfa marker="#recv/3"><c>recv/3</c></seemfa>,
has a timeout argument. </p>
<note>
<p>Some functions allow for an <i>asynchronous</i> call.
This is achieved by setting the <c>Timeout</c> argument to
<c>nowait</c>. For instance, if calling the
- <seealso marker="#recv_async"><c>recv/3</c></seealso>
+ <seeerl marker="#recv_async"><c>recv/3</c></seeerl>
function with Timeout set to <c>nowait</c> (<c>recv(Sock, 0, nowait)</c>)
when there is actually nothing to read, it will return with
<c>{select, </c>
- <seealso marker="#type-select_info"><c>SelectInfo</c></seealso><c>}</c>
+ <seetype marker="#select_info"><c>SelectInfo</c></seetype><c>}</c>
(<c>SelectInfo</c> contains the
- <seealso marker="socket#type-select_ref">SelectRef</seealso>).
+ <seetype marker="socket#select_ref">SelectRef</seetype>).
When data eventually arrives a 'select' message
will be sent to the caller: </p>
<taglist>
@@ -94,8 +94,8 @@
<datatype>
<name>socket()</name>
<desc><p>As returned by
- <seealso marker="#open/2"><c>open/2,3,4</c></seealso> and
- <seealso marker="#accept/1"><c>accept/1,2</c></seealso>.</p>
+ <seemfa marker="#open/1"><c>open/1,2,3,4</c></seemfa> and
+ <seemfa marker="#accept/1"><c>accept/1,2</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -117,9 +117,6 @@
<name name="socket_counters"/>
</datatype>
<datatype>
- <name name="socket_counter"/>
- </datatype>
- <datatype>
<name name="socket_info"/>
</datatype>
<datatype>
@@ -129,9 +126,6 @@
<name name="ip6_address"/>
</datatype>
<datatype>
- <name name="ip_address"/>
- </datatype>
- <datatype>
<name name="sockaddr"/>
</datatype>
<datatype>
@@ -302,31 +296,29 @@
<name name="int32"/>
</datatype>
<datatype>
- <name name="supports_options_socket"/>
- </datatype>
- <datatype>
- <name name="supports_options_ip"/>
- </datatype>
- <datatype>
- <name name="supports_options_ipv6"/>
- </datatype>
- <datatype>
- <name name="supports_options_tcp"/>
- </datatype>
- <datatype>
- <name name="supports_options_udp"/>
- </datatype>
- <datatype>
- <name name="supports_options_sctp"/>
- </datatype>
- <datatype>
- <name name="supports_options"/>
- </datatype>
- <datatype>
- <name name="supports_send_flags"/>
- </datatype>
- <datatype>
- <name name="supports_recv_flags"/>
+ <name name="errcode"/>
+ <desc>
+ <p>
+ The POSIX error codes are mostly come from the
+ OS level socket interface,
+ but this module may generate some appropriate
+ POSIX codes.
+ </p>
+ <p>
+ The other values come from this module's lower levels
+ and are all fairly fatal internal errors:
+ </p>
+ <taglist>
+ <tag><c>exalloc</c></tag>
+ <item>Memory allocation failed</item>
+ <tag><c>exmonitor</c></tag>
+ <item>Failed to set a monitor on a process</item>
+ <tag><c>exselect</c></tag>
+ <item>Select operation failed</item>
+ <tag><c>exself</c></tag>
+ <item>Failed to get current process</item>
+ </taglist>
+ </desc>
</datatype>
</datatypes>
@@ -359,7 +351,7 @@
will return with the <c>SelectInfo</c>. The caller can then await a
select message, <c>{'$socket', Socket, select, Info}</c> (where
<c>Info</c> is the
- <seealso marker="socket#type-select_ref"><c>ref</c></seealso>
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
field from the <c>SelectInfo</c>),
when a client connects (a subsequent call to accept will then return
the socket). </p>
@@ -372,7 +364,7 @@
<desc>
<p>Bind a name to a socket.</p>
<p>When a socket is created
- (with <seealso marker="#open/2"><c>open</c></seealso>),
+ (with <seemfa marker="#open/2"><c>open</c></seemfa>),
it has no address assigned to it. <c>bind</c> assigns the
address specified by the <c>Addr</c> argument.</p>
<p>The rules used for name binding vary between domains.</p>
@@ -387,7 +379,7 @@
<p>Call this function in order to cancel a previous
asynchronous call to, e.g.
- <seealso marker="#recv/3"><c>recv/3</c></seealso>. </p>
+ <seemfa marker="#recv/3"><c>recv/3</c></seemfa>. </p>
</desc>
</func>
@@ -402,7 +394,7 @@
doing a close does not guarantee that any data sent is delivered to
the recipient before the close is detected at the remote side. </p>
<p>One way to handle this is to use the
- <seealso marker="#shutdown/2"><c>shutdown</c></seealso>
+ <seemfa marker="#shutdown/2"><c>shutdown</c></seemfa>
function
(<c>socket:shutdown(Socket, write)</c>) to signal that no more data is
to be sent and then wait for the read side of the socket to be closed.</p>
@@ -429,11 +421,11 @@
<p>In the case when its not possible to immediately establish a
connection, the function will return with the
- <seealso marker="#type-select_info"><c>SelectInfo</c></seealso>.
+ <seetype marker="#select_info"><c>SelectInfo</c></seetype>.
The caller can then await a
select message, <c>{'$socket', Socket, select, Info}</c> (where
<c>Info</c> is the
- <seealso marker="socket#type-select_ref"><c>ref</c></seealso>
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
field from the <c>SelectInfo</c>,
a subsequent call to connect will then
establish the connection). </p>
@@ -456,7 +448,7 @@
<c>protocol</c>).</p>
<p>See the
- <seealso marker="socket_usage#socket_options">socket options</seealso>
+ <seeguide marker="socket_usage#socket_options">socket options</seeguide>
chapter of the users guide for more info. </p>
<note><p>Not all options are valid on all platforms. That is,
@@ -477,7 +469,7 @@
know how to interpret the result.</p>
<p>For more info, see
- <seealso marker="#getopt/3">getopt</seealso> above. </p>
+ <seemfa marker="#getopt/3">getopt</seemfa> above. </p>
</desc>
</func>
@@ -513,7 +505,67 @@
</func>
<func>
- <name name="open" arity="2" since="OTP 22.0"/>
+ <name name="open" arity="1" since="OTP 23.0"/>
+ <name name="open" arity="2" clause_i="1" since="OTP 23.0"/>
+ <fsummary>Create an endpoint for communication.</fsummary>
+ <desc>
+ <p>Create an endpoint (socket) for communication based on an
+ already existing file descriptor.
+ The function attempts to retrieve domain, type and protocol from
+ the system. This is however not possible on all platforms, and
+ in those cases it expects it in <c>Opts</c>. </p>
+
+ <p>The <c>Opts</c> argument is intended for providing extra
+ information for the open call:</p>
+ <taglist>
+ <tag><c><![CDATA[dup: boolean()]]></c></tag>
+ <item>
+ <p>Shall the provided descriptor be duplicated (dup) or not.
+ <br/>Defaults to <c>true</c>. </p>
+ </item>
+
+ <tag><c><![CDATA[debug: boolean()]]></c></tag>
+ <item>
+ <p>Enable or disable debug during the open call.
+ <br/>Defaults to <c>false</c>. </p>
+ </item>
+
+ <tag><c><![CDATA[domain: socket:domain()]]></c></tag>
+ <item>
+ <p>Which domain is the descriptor of. </p>
+ </item>
+
+ <tag><c><![CDATA[type: socket:type()]]></c></tag>
+ <item>
+ <p>Which type is the descriptor of. </p>
+ </item>
+
+ <tag><c><![CDATA[protocol: socket:protocol()]]></c></tag>
+ <item>
+ <p>Which protocol is the descriptor of. </p>
+ </item>
+
+ <tag><c><![CDATA[use_registry: boolean()]]></c></tag>
+ <item>
+ <p>Enable or disable use of the socket registry for this socket.
+ This overrides the global value.
+ <br/>Defaults to the global value, see
+ <seemfa marker="#use_registry/1"><c>use_registry/1</c></seemfa>.
+ </p>
+ </item>
+
+ </taglist>
+
+ <note>
+ <p>This function should be used with care! </p>
+ <p>On some platforms its <em>necessary</em> to provide the
+ <c>protocol</c> as its impossible to retrieve it. </p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="open" arity="2" clause_i="2" since="OTP 22.0"/>
<name name="open" arity="3" since="OTP 22.0"/>
<name name="open" arity="4" since="OTP 22.0"/>
<fsummary>Create an endpoint for communication.</fsummary>
@@ -526,9 +578,32 @@
And for <c>Domain = local</c>, if a protocol <em>is</em> pecified,
it <em>must</em> be <c>default</c>. </p>
- <p>The <c>Extra</c> argument is intended for "obscure" options.
- Currently the only supported option is <c>netns</c>, which
- is only supported on the linux platform.</p>
+ <p>The <c>Opts</c> argument is intended for "other" options.
+ The supported option(s) are described below:</p>
+
+ <taglist>
+ <tag><c><![CDATA[netns: string()]]></c></tag>
+ <item>
+ <p>Used to set the network namespace during the open call.
+ Only supported on the Linux platform. </p>
+ </item>
+
+ <tag><c><![CDATA[debug: boolean()]]></c></tag>
+ <item>
+ <p>Enable or disable debug during the open call.
+ <br/>Defaults to <c>false</c>. </p>
+ </item>
+
+ <tag><c><![CDATA[use_registry: boolean()]]></c></tag>
+ <item>
+ <p>Enable or disable use of the socket registry for this socket.
+ This overrides the global value.
+ <br/>Defaults to the global value, see
+ <seemfa marker="#use_registry/1"><c>use_registry/1</c></seemfa>.
+ </p>
+ </item>
+
+ </taglist>
<note>
<p>It may not be possible to specify the default protocol (except
@@ -577,7 +652,7 @@
will return with the <c>SelectInfo</c>. The caller can then await a
select message, <c>{'$socket', Socket, select, Info}</c> (where
<c>Info</c> is the
- <seealso marker="socket#type-select_ref"><c>ref</c></seealso>
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
field from the <c>SelectInfo</c>),
when data has arrived (a subsequent call to recv will then return
the data). </p>
@@ -585,7 +660,7 @@
of that amount of data is available, the function will return with
that data <em>and</em> the <c>SelectInfo</c> (if the caller don't
want to wait for the remaining data, it must immediately call
- the <seealso marker="#cancel/2"><c>cancel/2</c></seealso> function.)</p>
+ the <seemfa marker="#cancel/2"><c>cancel/2</c></seemfa> function.)</p>
</desc>
</func>
@@ -638,7 +713,7 @@
will return with the <c>SelectInfo</c>. The caller can then await a
select message, <c>{'$socket', Socket, select, Info}</c> (where
<c>Info</c> is the
- <seealso marker="socket#type-select_ref"><c>ref</c></seealso>
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
field from the <c>SelectInfo</c>),
when data has arrived (a subsequent call to recvfrom will then return
the data). </p>
@@ -716,7 +791,7 @@
will return with the <c>SelectInfo</c>. The caller can then await a
select message, <c>{'$socket', Socket, select, Info}</c> (where
<c>Info</c> is the
- <seealso marker="socket#type-select_ref"><c>ref</c></seealso>
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
field from the <c>SelectInfo</c>),
when data has arrived (a subsequent call to recvmsg will then return
the data). </p>
@@ -746,7 +821,7 @@
can then await a select message,
<c>{'$socket', Socket, select, Info}</c>
(where <c>Info</c> is the
- <seealso marker="socket#type-select_ref"><c>ref</c></seealso>
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
field from the
<c>SelectInfo</c>), when there is room for more data (a subsequent
call to send will then send the data). </p>
@@ -754,7 +829,7 @@
with the remaining data <em>and</em> the <c>SelectInfo</c>
(if the caller don't
want to wait to be able to send the rest, it should immediately call
- the <seealso marker="#cancel/2"><c>cancel/2</c></seealso> function.)</p>
+ the <seemfa marker="#cancel/2"><c>cancel/2</c></seemfa> function.)</p>
</desc>
</func>
@@ -771,7 +846,7 @@
The <c>MsgHdr</c> may also contain an list of optional <c>cmsghdr_send()</c>
(depends on what the protocol and platform supports).</p>
- <p>Unlike the <seealso marker="#send/2"><c>send</c></seealso> function,
+ <p>Unlike the <seemfa marker="#send/2"><c>send</c></seemfa> function,
this one sends <em>one message</em>.
This means that if, for whatever reason, its not possible to send the
message in one go, the function will instead return with the
@@ -793,7 +868,7 @@
The <c>MsgHdr</c> may also contain an list of optional <c>cmsghdr_send()</c>
(depends on what the protocol and platform supports).</p>
- <p>Unlike the <seealso marker="#send/2"><c>send</c></seealso> function,
+ <p>Unlike the <seemfa marker="#send/2"><c>send</c></seemfa> function,
this one sends <em>one message</em>.
This means that if, for whatever reason, its not possible to send the
message in one go, the function will instead return with the
@@ -806,7 +881,7 @@
can then await a select message,
<c>{'$socket', Socket, select, Info}</c>
(where <c>Info</c> is the
- <seealso marker="socket#type-select_ref"><c>ref</c></seealso>
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
field from the
<c>SelectInfo</c>), when there is room for more data (a subsequent
call to sendmsg will then send the data). </p>
@@ -836,7 +911,7 @@
can then await a select message,
<c>{'$socket', Socket, select, Info}</c>
(where <c>Info</c> is the
- <seealso marker="socket#type-select_ref"><c>ref</c></seealso>
+ <seetype marker="socket#select_ref"><c>ref</c></seetype>
field from the
<c>SelectInfo</c>), when there is room for more data (a subsequent
call to sendto will then send the data). </p>
@@ -851,15 +926,16 @@
<name name="setopt" arity="4" clause_i="5" since="OTP 22.0"/>
<name name="setopt" arity="4" clause_i="6" since="OTP 22.0"/>
<name name="setopt" arity="4" clause_i="7" since="OTP 22.0"/>
- <fsummary>Set options on a socket.</fsummary>
+ <name name="setopt" arity="4" clause_i="8" since="OTP 22.0"/>
+ <fsummary>Set an option on a socket.</fsummary>
<desc>
- <p>Set options on a socket.</p>
- <p>What properties are valid depend both on <c>Level</c> and on
+ <p>Set an option on a socket.</p>
+ <p>What options are valid depend both on <c>Level</c> and on
what kind of socket it is (<c>domain</c>, <c>type</c> and
<c>protocol</c>).</p>
<p>See the
- <seealso marker="socket_usage#socket_options">socket options</seealso>
+ <seeguide marker="socket_usage#socket_options">socket options</seeguide>
chapter of the users guide for more info. </p>
<note><p>Not all options are valid on all platforms. That is,
@@ -883,7 +959,7 @@
know how to encode the <c>Value</c>.</p>
<p>For more info, see
- <seealso marker="#setopt/4">setopt</seealso> above. </p>
+ <seemfa marker="#setopt/4">setopt</seemfa> above. </p>
</desc>
</func>
@@ -904,35 +980,193 @@
</func>
<func>
+ <name since="OTP 22.0">
+ supports() -> Supports
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options) -> SupportsOptions
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: send_flags) -> SupportsSendFlags
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: recv_flags) -> SupportsRecvFlags
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: socket) -> SupportsOptionsSocket
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: ip) -> SupportsOptionsIP
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: ipv6) -> SupportsOptionsIPv6
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: tcp) -> SupportsOptionsTCP
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: udp) -> SupportsOptionsUDP
+ </name>
+ <name since="OTP 22.0">
+ supports(Key1 :: options, Key2 :: sctp) -> SupportsOptionsSCTP
+ </name>
+ <!--
<name name="supports" arity="0" since="OTP 22.0"/>
- <name name="supports" arity="1" clause_i="1" since="OTP 22.0"/>
- <name name="supports" arity="1" clause_i="2" since="OTP 22.0"/>
- <name name="supports" arity="1" clause_i="3" since="OTP 22.0"/>
- <name name="supports" arity="1" clause_i="4" since="OTP 22.0"/>
- <name name="supports" arity="1" clause_i="5" since="OTP 22.3"/>
- <name name="supports" arity="1" clause_i="6" since="OTP 22.3"/>
- <name name="supports" arity="1" clause_i="7" since="OTP 22.0"/>
- <name name="supports" arity="2" clause_i="1" since="OTP 22.0"/>
- <name name="supports" arity="2" clause_i="2" since="OTP 22.0"/>
- <name name="supports" arity="2" clause_i="3" since="OTP 22.0"/>
- <name name="supports" arity="2" clause_i="4" since="OTP 22.0"/>
- <name name="supports" arity="2" clause_i="5" since="OTP 22.0"/>
- <name name="supports" arity="2" clause_i="6" since="OTP 22.0"/>
- <name name="supports" arity="2" clause_i="7" since="OTP 22.3"/>
- <name name="supports" arity="2" clause_i="8" since="OTP 22.3"/>
- <name name="supports" arity="2" clause_i="9" since="OTP 22.0"/>
- <name name="supports" arity="3" clause_i="1" since="OTP 22.0"/>
- <name name="supports" arity="3" clause_i="2" since="OTP 22.0"/>
- <name name="supports" arity="3" clause_i="3" since="OTP 22.0"/>
- <name name="supports" arity="3" clause_i="4" since="OTP 22.0"/>
- <name name="supports" arity="3" clause_i="5" since="OTP 22.0"/>
- <name name="supports" arity="3" clause_i="6" since="OTP 22.0"/>
- <name name="supports" arity="3" clause_i="7" since="OTP 22.0"/>
+ <name name="supports" arity="1" since="OTP 22.0"/>
+ <name name="supports" arity="2" since="OTP 22.0"/>
+ -->
+ <fsummary>Report info about what the platform supports.</fsummary>
+ <type>
+ <v>
+ Supports :: [{Feature,&nbsp;boolean()}
+ &nbsp;&nbsp;|&nbsp;{send_flags,&nbsp;SupportsSendFlags}
+ &nbsp;&nbsp;|&nbsp;{recv_flags,&nbsp;SupportsRecvFlags}
+ &nbsp;&nbsp;|&nbsp;{options,&nbsp;SupportsOptions}]
+ </v>
+ <v>Feature :: sctp | ipv6 | local | netns</v>
+ <v>
+ SupportsSendFlags ::
+ [{<seetype marker="#send_flag">send_flag()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsRecvFlags ::
+ [{<seetype marker="#recv_flag">recv_flag()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptions ::
+ [{socket,&nbsp;SupportsOptionsSocket}
+ &nbsp;&nbsp;|&nbsp;{ip,&nbsp;SupportsOptionsIP}
+ &nbsp;&nbsp;|&nbsp;{ipv6,&nbsp;SupportsOptionsIPv6}
+ &nbsp;&nbsp;|&nbsp;{tcp,&nbsp;SupportsOptionsTCP}
+ &nbsp;&nbsp;|&nbsp;{udp,&nbsp;SupportsOptionsUDP}
+ &nbsp;&nbsp;|&nbsp;{sctp,&nbsp;SupportsOptionsSCTP}]
+ </v>
+ <v>
+ SupportsOptionsSocket ::
+ [{<seetype marker="#socket_option">socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsIP ::
+ [{<seetype marker="#ip_socket_option">ip_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsIPv6 ::
+ [{<seetype marker="#ipv6_socket_option">ipv6_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsTCP ::
+ [{<seetype marker="#tcp_socket_option">tcp_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsUDP ::
+ [{<seetype marker="#udp_socket_option">udp_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ <v>
+ SupportsOptionsSCTP ::
+ [{<seetype marker="#sctp_socket_option">sctp_socket_option()</seetype>,&nbsp;boolean()}]
+ </v>
+ </type>
+ <desc>
+ <p>
+ This function retreives information about what the
+ platform supports, such as if SCTP is supported,
+ or which socket options are supported.
+ </p>
+ <p>
+ For keys other than the known the empty list is returned,
+ Note that in a future version or on a different platform
+ there might be more supported items.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: sctp | ipv6 | local | netns) -> boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: send_flags, Key2 :: SendFlag) -> boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: recv_flags, Key2 :: RecvFlag) -> boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: socket, Key3 :: SocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: ip, Key3 :: IPSocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: ipv6, Key3 :: IPv6SocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: tcp, Key3 :: TCPSocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: udp, Key3 :: UDPSocketOption) ->
+ boolean()
+ </name>
+ <name since="OTP 23.0">
+ is_supported(Key1 :: options, Key2 :: sctp, Key3 :: SCTPSocketOption) ->
+ boolean()
+ </name>
<fsummary>Report info about what the platform supports.</fsummary>
+ <type>
+ <v>
+ SocketOption ::
+ <seetype marker="#socket_option">socket_option()</seetype>
+ </v>
+ <v>
+ IPSocketOption ::
+ <seetype marker="#ip_socket_option">ip_socket_option()</seetype>
+ </v>
+ <v>
+ IPv6SocketOption ::
+ <seetype marker="#ipv6_socket_option">ipv6_socket_option()</seetype>
+ </v>
+ <v>
+ TCPSocketOption ::
+ <seetype marker="#tcp_socket_option">tcp_socket_option()</seetype>
+ </v>
+ <v>
+ UDPSocketOption ::
+ <seetype marker="#udp_socket_option">udp_socket_option()</seetype>
+ </v>
+ <v>
+ SCTPSocketOption ::
+ <seetype marker="#sctp_socket_option">sctp_socket_option()</seetype>
+ </v>
+ </type>
+ <desc>
+ <p>
+ This function retreives information about what the
+ platform supports, such as if SCTP is supported,
+ or which socket options are supported.
+ </p>
+ <p>
+ For keys other than the known <c>false</c> is returned.
+ Note that in a future version or on a different platform
+ there might be more supported items.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="use_registry" arity="1" since="OTP 23.1"/>
+ <fsummary>Globally change the use of the socket registry.</fsummary>
<desc>
- <p>This function intends to retreive information about what the
- platform supports. Such as if SCTP is supported. Or which socket
- options are supported. </p>
+ <p>Globally change if the socket registry is to be used or not.
+ Note that its still possible to override this explicitly when
+ creating an individual sockets,
+ see
+ <seemfa marker="socket#open/2"><c>open/2</c></seemfa>
+ or
+ <seemfa marker="socket#open/4"><c>open/4</c></seemfa>
+ for more info (use the Extra argument). </p>
</desc>
</func>
@@ -1014,4 +1248,3 @@ server(Addr, Port) ->
</code>
</section>
</erlref>
-
diff --git a/erts/doc/src/socket_usage.xml b/lib/kernel/doc/src/socket_usage.xml
index 76d44977a9..d132c7541f 100644
--- a/erts/doc/src/socket_usage.xml
+++ b/lib/kernel/doc/src/socket_usage.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2018</year><year>2019</year>
+ <year>2018</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -32,7 +32,7 @@
<section>
<title>Introduction</title>
- <p>The socket interface (module) is basically an "thin" layer on top of
+ <p>The socket interface (module) is basically a "thin" layer on top of
the OS socket interface. It is assumed that, unless you have special needs,
gen_[tcp|udp|sctp] should be sufficent (when they become available). </p>
<p>Note that just because we have a documented and described option,
@@ -42,24 +42,24 @@
<section>
<title>Asynchronous calls</title>
<p>Some functions allow for an <i>asynchronous</i> call
- (<seealso marker="socket#accept_async"><c>accept/2</c></seealso>,
- <seealso marker="socket#connect_async"><c>connect/3</c></seealso>,
- <seealso marker="socket#recv_async"><c>recv/3,4</c></seealso>,
- <seealso marker="socket#recvfrom_async"><c>recvfrom/3,4</c></seealso>,
- <seealso marker="socket#recvmsg_async"><c>recvmsg/2,3,5</c></seealso>,
- <seealso marker="socket#send_async"><c>send/3,4</c></seealso>,
- <seealso marker="socket#sendmsg_async"><c>sendmsg/3,4</c></seealso> and
- <seealso marker="socket#sendto_async"><c>sendto/4,5</c></seealso>).
+ (<seeerl marker="socket#accept_async"><c>accept/2</c></seeerl>,
+ <seeerl marker="socket#connect_async"><c>connect/3</c></seeerl>,
+ <seeerl marker="socket#recv_async"><c>recv/3,4</c></seeerl>,
+ <seeerl marker="socket#recvfrom_async"><c>recvfrom/3,4</c></seeerl>,
+ <seeerl marker="socket#recvmsg_async"><c>recvmsg/2,3,5</c></seeerl>,
+ <seeerl marker="socket#send_async"><c>send/3,4</c></seeerl>,
+ <seeerl marker="socket#sendmsg_async"><c>sendmsg/3,4</c></seeerl> and
+ <seeerl marker="socket#sendto_async"><c>sendto/4,5</c></seeerl>).
This is achieved by setting the <c>Timeout</c> argument to
<c>nowait</c>. For instance, if calling the
- <seealso marker="socket#recv_async"><c>recv/3</c></seealso>
+ <seeerl marker="socket#recv_async"><c>recv/3</c></seeerl>
function with Timeout set to <c>nowait</c> (i.e.
<c>recv(Sock, 0, nowait)</c>)
when there is actually nothing to read, it will return with
<c>{select, </c>
- <seealso marker="socket#type-select_info"><c>SelectInfo</c></seealso><c>}</c>
+ <seetype marker="socket#select_info"><c>SelectInfo</c></seetype><c>}</c>
(<c>SelectInfo</c> contains the
- <seealso marker="socket#type-select_ref">SelectRef</seealso>).
+ <seetype marker="socket#select_ref">SelectRef</seetype>).
When data eventually arrives a 'select message'
will be sent to the caller:</p>
<taglist>
@@ -72,7 +72,7 @@
<p>Note that all other users are <em>locked out</em> until the
'current user' has called the function (recv in this case). So either
immediately call the function or
- <seealso marker="socket#cancel/2"><c>cancel</c></seealso>. </p>
+ <seemfa marker="socket#cancel/2"><c>cancel</c></seemfa>. </p>
<p>The user must also be prepared to receive an abort message: </p>
<taglist>
<!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL -->
@@ -107,11 +107,45 @@
<tcaption>socket message info value type</tcaption>
</table>
<p>The <c>select_ref()</c> is the same as was received in the
- <seealso marker="socket#type-select_info"><c>SelectInfo</c></seealso>. </p>
+ <seetype marker="socket#select_info"><c>SelectInfo</c></seetype>. </p>
</section>
</section>
<section>
+ <title>Socket Registry</title>
+ <p>The <em>socket registry</em> is how we keep track of sockets.
+ There are two functions that can be used for interaction:
+ <seemfa marker="socket#number_of/0"><c>socket:number_of/0</c></seemfa>
+ and
+ <seemfa marker="socket#which_sockets/1"><c>socket:which_sockets/1</c></seemfa>.
+ </p>
+ <p>In systems which create and delete <em>many</em> sockets
+ dynamically, it (the socket registry) could become a bottleneck.
+ For such systems, there are a couple of
+ ways to control the use of the socket registry. </p>
+
+ <p>Firstly, its possible to effect the global default value when
+ building OTP from source with the two configure options:</p>
+ <code type="none">--enable-esock-socket-registry (default) | --disable-esock-socket-registry</code>
+
+ <p>Second, its possible to effect the global default value by
+ setting the environment variable <c>ESOCK_USE_SOCKET_REGISTRY</c>
+ (boolean) before starting the erlang. </p>
+
+ <p>Third, its possible to alter the global default value in runtime
+ by calling the function
+ <seemfa marker="socket#use_registry/1"><c>use_registry/1</c></seemfa>.</p>
+
+ <p>And finally, its possible to override the global default when creating
+ a socket (with <seemfa marker="socket#open/2"><c>open/2</c></seemfa> and
+ <seemfa marker="socket#open/4"><c>open/4</c></seemfa>) by providing
+ the attribute <c>use_registry</c> (boolean) in the their <c>Opts</c>
+ argument (which effects <em>that</em> specific
+ socket).</p>
+
+ </section>
+
+ <section>
<marker id="socket_options"></marker>
<title>Socket Options</title>
@@ -181,6 +215,15 @@
<cell>yes</cell>
<cell>none</cell>
</row>
+ <row>
+ <cell>use_registry</cell>
+ <cell>boolean()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>the value is set when the socket is created, by a call to
+ <seemfa marker="socket#open/2"><c>open/2</c></seemfa> or
+ <seemfa marker="socket#open/4"><c>open/4</c></seemfa>.</cell>
+ </row>
<tcaption>option levels</tcaption>
</table>
@@ -302,13 +345,17 @@
<cell>yes</cell>
<cell>yes</cell>
<cell>
+ This option is not normally supported (see why below).
+ OTP has to be explicitly built with the
+ <c>--enable-esock-rcvsndtime</c> configure option for this
+ to be available.
Since our implementation is <em>nonblocking</em>,
its unknown if and how this option works, or even if
it may cause malfunctions.
Therefor, we do not recommend setting this option.
Instead, use the <c>Timeout</c> argument to, for instance,
the
- <seealso marker="socket#recv/3"><c>recv/3</c></seealso>
+ <seemfa marker="socket#recv/3"><c>recv/3</c></seemfa>
function.
</cell>
</row>
@@ -346,13 +393,17 @@
<cell>yes</cell>
<cell>yes</cell>
<cell>
+ This option is not normally supported (see why below).
+ OTP has to be explicitly built with the
+ <c>--enable-esock-rcvsndtime</c> configure option for this
+ to be available.
Since our implementation is <em>nonblocking</em>,
its unknown if and how this option works, or even if
it may cause malfunctions.
Therefor, we do not recommend setting this option.
Instead, use the <c>Timeout</c> argument to, for instance,
the
- <seealso marker="socket#send/3"><c>send/3</c></seealso>
+ <seemfa marker="socket#send/3"><c>send/3</c></seemfa>
function.
</cell>
</row>
diff --git a/lib/kernel/doc/src/specs.xml b/lib/kernel/doc/src/specs.xml
index 9e258910db..990bc85782 100644
--- a/lib/kernel/doc/src/specs.xml
+++ b/lib/kernel/doc/src/specs.xml
@@ -9,6 +9,7 @@
<xi:include href="../specs/specs_erl_epmd.xml"/>
<xi:include href="../specs/specs_erl_prim_loader_stub.xml"/>
<xi:include href="../specs/specs_erlang_stub.xml"/>
+ <xi:include href="../specs/specs_erpc.xml"/>
<xi:include href="../specs/specs_error_handler.xml"/>
<xi:include href="../specs/specs_error_logger.xml"/>
<xi:include href="../specs/specs_file.xml"/>
@@ -30,9 +31,11 @@
<xi:include href="../specs/specs_net_adm.xml"/>
<xi:include href="../specs/specs_net_kernel.xml"/>
<xi:include href="../specs/specs_os.xml"/>
+ <xi:include href="../specs/specs_pg.xml"/>
<xi:include href="../specs/specs_pg2.xml"/>
<xi:include href="../specs/specs_rpc.xml"/>
<xi:include href="../specs/specs_seq_trace.xml"/>
+ <xi:include href="../specs/specs_socket.xml"/>
<xi:include href="../specs/specs_user.xml"/>
<xi:include href="../specs/specs_wrap_log_reader.xml"/>
<xi:include href="../specs/specs_zlib_stub.xml"/>
diff --git a/lib/kernel/doc/src/wrap_log_reader.xml b/lib/kernel/doc/src/wrap_log_reader.xml
index 02424f5b6f..c1a8475046 100644
--- a/lib/kernel/doc/src/wrap_log_reader.xml
+++ b/lib/kernel/doc/src/wrap_log_reader.xml
@@ -38,11 +38,11 @@
<description>
<p>This module makes it possible to read internally formatted
wrap disk logs, see
- <seealso marker="disk_log"><c>disk_log(3)</c></seealso>.
+ <seeerl marker="disk_log"><c>disk_log(3)</c></seeerl>.
<c>wrap_log_reader</c> does not
interfere with <c>disk_log</c> activities; there is however a bug in this
version of the <c>wrap_log_reader</c>, see section
- <seealso marker="#bugs">Known Limitations</seealso>.</p>
+ <seeerl marker="#bugs">Known Limitations</seeerl>.</p>
<p>A wrap disk log file consists of many files, called index files. A log
file can be opened and closed. Also, a single index file can be opened
separately. If a non-existent or non-internally formatted file is opened,
@@ -146,7 +146,7 @@
<section>
<title>See Also</title>
- <p><seealso marker="disk_log"><c>disk_log(3)</c></seealso></p>
+ <p><seeerl marker="disk_log"><c>disk_log(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/kernel/doc/src/zlib_stub.xml b/lib/kernel/doc/src/zlib_stub.xml
index 9ab9c4eb62..598ad3cde7 100644
--- a/lib/kernel/doc/src/zlib_stub.xml
+++ b/lib/kernel/doc/src/zlib_stub.xml
@@ -34,6 +34,6 @@
<modulesummary>Zlib compression interface.</modulesummary>
<description>
<p>This module is moved to the
- <seealso marker="erts:zlib">ERTS</seealso> application.</p>
+ <seeerl marker="erts:zlib">ERTS</seeerl> application.</p>
</description>
</erlref>
diff --git a/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl b/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl
index bc2e07ddf5..f4eb12818d 100644
--- a/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl
+++ b/lib/kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl
@@ -400,6 +400,10 @@ is_node_name(_Node) ->
hs_data_common(DistCtrl) ->
TickHandler = call_ctrlr(DistCtrl, tick_handler),
Socket = call_ctrlr(DistCtrl, socket),
+ RejectFlags = case init:get_argument(gen_tcp_dist_reject_flags) of
+ {ok,[[Flags]]} -> list_to_integer(Flags);
+ _ -> #hs_data{}#hs_data.reject_flags
+ end,
#hs_data{f_send = send_fun(),
f_recv = recv_fun(),
f_setopts_pre_nodeup = setopts_pre_nodeup_fun(),
@@ -410,7 +414,8 @@ hs_data_common(DistCtrl) ->
mf_setopts = setopts_fun(DistCtrl, Socket),
mf_getopts = getopts_fun(DistCtrl, Socket),
mf_getstat = getstat_fun(DistCtrl, Socket),
- mf_tick = tick_fun(DistCtrl, TickHandler)}.
+ mf_tick = tick_fun(DistCtrl, TickHandler),
+ reject_flags = RejectFlags}.
%%% ------------------------------------------------------------
%%% Distribution controller processes
diff --git a/lib/kernel/include/dist.hrl b/lib/kernel/include/dist.hrl
index f06fc328d7..c59bd78523 100644
--- a/lib/kernel/include/dist.hrl
+++ b/lib/kernel/include/dist.hrl
@@ -44,7 +44,19 @@
-define(DFLAG_BIG_SEQTRACE_LABELS, 16#100000).
%% -define(DFLAG_NO_MAGIC, 16#200000). %% Used internally only
-define(DFLAG_EXIT_PAYLOAD, 16#400000).
--define(DFLAG_FRAGMENTS, 16#800000).
+-define(DFLAG_FRAGMENTS, 16#00800000).
+-define(DFLAG_HANDSHAKE_23, 16#01000000).
+-define(DFLAG_RESERVED, 16#fe000000).
+-define(DFLAG_SPAWN, 16#100000000).
+-define(DFLAG_NAME_ME, 16#200000000).
%% Also update dflag2str() in ../src/dist_util.erl
%% when adding flags...
+
+
+-define(ERL_DIST_VER_5, 5). % OTP-22 or (much) older
+-define(ERL_DIST_VER_6, 6). % OTP-23 (or maybe newer?)
+
+-define(ERL_DIST_VER_LOW, ?ERL_DIST_VER_5).
+-define(ERL_DIST_VER_HIGH, ?ERL_DIST_VER_6).
+
diff --git a/lib/kernel/include/dist_util.hrl b/lib/kernel/include/dist_util.hrl
index 56f775f060..1eebb56030 100644
--- a/lib/kernel/include/dist_util.hrl
+++ b/lib/kernel/include/dist_util.hrl
@@ -84,7 +84,11 @@
f_handshake_complete, %% Notify handshake complete
add_flags, %% dflags to add
reject_flags, %% dflags not to use (not all can be rejected)
- require_flags %% dflags that are required
+ require_flags, %% dflags that are required
+
+ %% New in kernel-@master@ (OTP-23.0)
+ this_creation,
+ other_creation
}).
diff --git a/lib/kernel/include/eep48.hrl b/lib/kernel/include/eep48.hrl
new file mode 100644
index 0000000000..2ce9a1430a
--- /dev/null
+++ b/lib/kernel/include/eep48.hrl
@@ -0,0 +1,14 @@
+-define(NATIVE_FORMAT,<<"application/erlang+html">>).
+-define(CURR_DOC_VERSION, {1,0,0}).
+-record(docs_v1, {anno,
+ beam_language = erlang,
+ format = ?NATIVE_FORMAT,
+ module_doc,
+ metadata = #{ otp_doc_vsn => ?CURR_DOC_VERSION },
+ docs}).
+
+-record(docs_v1_entry, {kind_name_arity,
+ anno,
+ signature,
+ doc,
+ metadata}).
diff --git a/lib/kernel/include/logger.hrl b/lib/kernel/include/logger.hrl
index b09977e0f2..bd17f7efc4 100644
--- a/lib/kernel/include/logger.hrl
+++ b/lib/kernel/include/logger.hrl
@@ -46,7 +46,7 @@
-define(DO_LOG(Level,Args),
case logger:allow(Level,?MODULE) of
true ->
- apply(logger,macro_log,[?LOCATION,Level|Args]);
+ erlang:apply(logger,macro_log,[?LOCATION,Level|Args]);
false ->
ok
end).
diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile
index 2d2b84c206..6c75bcffee 100644
--- a/lib/kernel/src/Makefile
+++ b/lib/kernel/src/Makefile
@@ -73,6 +73,7 @@ MODULES = \
erl_epmd \
erl_reply \
erl_signal_handler \
+ erpc \
erts_debug \
error_handler \
error_logger \
@@ -80,6 +81,7 @@ MODULES = \
file_io_server \
file_server \
gen_tcp \
+ gen_tcp_socket \
gen_udp \
gen_sctp \
global \
@@ -128,10 +130,12 @@ MODULES = \
net_adm \
net_kernel \
os \
+ pg \
pg2 \
ram_file \
rpc \
seq_trace \
+ socket \
standard_error \
user \
user_drv \
@@ -147,7 +151,8 @@ MODULES = \
HRL_FILES= ../include/file.hrl ../include/inet.hrl ../include/inet_sctp.hrl \
../include/dist.hrl ../include/dist_util.hrl \
- ../include/net_address.hrl ../include/logger.hrl
+ ../include/net_address.hrl ../include/logger.hrl ../include/eep48.hrl
+
INTERNAL_HRL_FILES= application_master.hrl disk_log.hrl \
erl_epmd.hrl file_int.hrl hipe_ext_format.hrl \
diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl
index 895911236f..869e9be1e8 100644
--- a/lib/kernel/src/application_controller.erl
+++ b/lib/kernel/src/application_controller.erl
@@ -37,6 +37,9 @@
-export([handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3, init_starter/4, get_loaded/1]).
+%% logger callback
+-export([format_log/1, format_log/2]).
+
%% Test exports, only to be used from the test suites
-export([test_change_apps/2]).
@@ -268,13 +271,12 @@ which_applications(Timeout) ->
gen_server:call(?AC, which_applications, Timeout).
loaded_applications() ->
- ets:filter(ac_tab,
- fun([{{loaded, AppName}, #appl{descr = Descr, vsn = Vsn}}]) ->
- {true, {AppName, Descr, Vsn}};
- (_) ->
- false
- end,
- []).
+ ets:select(ac_tab,
+ [{
+ {{loaded, '$1'}, #appl{descr = '$2', vsn = '$3', _ = '_'}},
+ [],
+ [{{'$1', '$2', '$3'}}]
+ }]).
%% Returns some debug info
info() ->
@@ -1804,7 +1806,7 @@ check_conf() ->
case init:get_argument(config) of
{ok, Files} ->
{ok, lists:foldl(
- fun([File], Env) ->
+ fun(File, Env) ->
BFName = filename:basename(File,".config"),
FName = filename:join(filename:dirname(File),
BFName ++ ".config"),
@@ -1836,7 +1838,7 @@ check_conf() ->
{error, {Line, _Mod, Str}} ->
throw({error, {FName, Line, Str}})
end
- end, [], Files)};
+ end, [], lists:append(Files))};
_ -> {ok, []}
end.
@@ -1930,9 +1932,12 @@ info_started(Name, Node) ->
report=>[{application, Name},
{started_at, Node}]},
#{domain=>[otp,sasl],
- report_cb=>fun logger:format_otp_report/1,
+ report_cb=>fun application_controller:format_log/2,
logger_formatter=>#{title=>"PROGRESS REPORT"},
- error_logger=>#{tag=>info_report,type=>progress}}).
+ error_logger=>#{tag=>info_report,
+ type=>progress,
+ report_cb=>
+ fun application_controller:format_log/1}}).
info_exited(Name, Reason, Type) ->
?LOG_NOTICE(#{label=>{application_controller,exit},
@@ -1940,8 +1945,140 @@ info_exited(Name, Reason, Type) ->
{exited, Reason},
{type, Type}]},
#{domain=>[otp],
- report_cb=>fun logger:format_otp_report/1,
- error_logger=>#{tag=>info_report,type=>std_info}}).
+ report_cb=>fun application_controller:format_log/2,
+ error_logger=>#{tag=>info_report,
+ type=>std_info,
+ report_cb=>
+ fun application_controller:format_log/1}}).
+
+%% format_log/1 is the report callback used by Logger handler
+%% error_logger only. It is kept for backwards compatibility with
+%% legacy error_logger event handlers. This function must always
+%% return {Format,Args} compatible with the arguments in this module's
+%% calls to error_logger prior to OTP-21.0.
+format_log(LogReport) ->
+ Depth = error_logger:get_format_depth(),
+ FormatOpts = #{chars_limit => unlimited,
+ depth => Depth,
+ single_line => false,
+ encoding => utf8},
+ format_log_multi(limit_report(LogReport, Depth), FormatOpts).
+
+limit_report(LogReport, unlimited) ->
+ LogReport;
+limit_report(#{label:={application_controller,progress},
+ report:=[{application,_}=Application,
+ {started_at,Node}]}=LogReport,
+ Depth) ->
+ LogReport#{report=>[Application,
+ {started_at,io_lib:limit_term(Node, Depth)}]};
+limit_report(#{label:={application_controller,exit},
+ report:=[{application,_}=Application,
+ {exited,Reason},{type,Type}]}=LogReport,
+ Depth) ->
+ LogReport#{report=>[Application,
+ {exited,io_lib:limit_term(Reason, Depth)},
+ {type,io_lib:limit_term(Type, Depth)}]}.
+
+%% format_log/2 is the report callback for any Logger handler, except
+%% error_logger.
+format_log(Report, FormatOpts0) ->
+ Default = #{chars_limit => unlimited,
+ depth => unlimited,
+ single_line => false,
+ encoding => utf8},
+ FormatOpts = maps:merge(Default, FormatOpts0),
+ IoOpts =
+ case FormatOpts of
+ #{chars_limit:=unlimited} ->
+ [];
+ #{chars_limit:=Limit} ->
+ [{chars_limit,Limit}]
+ end,
+ {Format,Args} = format_log_single(Report, FormatOpts),
+ io_lib:format(Format, Args, IoOpts).
+
+format_log_single(#{label:={application_controller,progress},
+ report:=[{application,Name},{started_at,Node}]},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format = "Application: "++P++". Started at: "++P++".",
+ Args =
+ case Depth of
+ unlimited ->
+ [Name,Node];
+ _ ->
+ [Name,Depth,Node,Depth]
+ end,
+ {Format,Args};
+format_log_single(#{label:={application_controller,exit},
+ report:=[{application,Name},
+ {exited,Reason},
+ {type,Type}]},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format = lists:append(["Application: ",P,". Exited: ",P,
+ ". Type: ",P,"."]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Name,Reason,Type];
+ _ ->
+ [Name,Depth,Reason,Depth,Type,Depth]
+ end,
+ {Format,Args};
+format_log_single(Report,FormatOpts) ->
+ format_log_multi(Report,FormatOpts).
+
+format_log_multi(#{label:={application_controller,progress},
+ report:=[{application,Name},
+ {started_at,Node}]},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ lists:append(
+ [" application: ",P,"~n",
+ " started_at: ",P,"~n"]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Name,Node];
+ _ ->
+ [Name,Depth,Node,Depth]
+ end,
+ {Format,Args};
+format_log_multi(#{label:={application_controller,exit},
+ report:=[{application,Name},
+ {exited,Reason},
+ {type,Type}]},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ lists:append(
+ [" application: ",P,"~n",
+ " exited: ",P,"~n",
+ " type: ",P,"~n"]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Name,Reason,Type];
+ _ ->
+ [Name,Depth,Reason,Depth,Type,Depth]
+ end,
+ {Format,Args}.
+
+p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
+ "~"++single(Single)++mod(Enc)++p(Depth);
+p(unlimited) ->
+ "p";
+p(_Depth) ->
+ "P".
+
+single(true) -> "0";
+single(false) -> "".
+
+mod(latin1) -> "";
+mod(_) -> "t".
%%-----------------------------------------------------------------
%% Reply to all processes waiting this application to be started.
diff --git a/lib/kernel/src/application_master.erl b/lib/kernel/src/application_master.erl
index 8697143dfb..16e453f99f 100644
--- a/lib/kernel/src/application_master.erl
+++ b/lib/kernel/src/application_master.erl
@@ -365,10 +365,10 @@ loop_it(Parent, Child, Mod, AppState) ->
NewAppState = prep_stop(Mod, AppState),
exit(Child, Reason),
receive
- {'EXIT', Child, Reason2} ->
- exit(Reason2)
+ {'EXIT', Child, _} -> ok
end,
- catch Mod:stop(NewAppState);
+ catch Mod:stop(NewAppState),
+ exit(Reason);
{'EXIT', Child, Reason} -> % forward *all* exit reasons (inc. normal)
NewAppState = prep_stop(Mod, AppState),
catch Mod:stop(NewAppState),
diff --git a/lib/kernel/src/auth.erl b/lib/kernel/src/auth.erl
index 4d18daf9e4..e552758a0d 100644
--- a/lib/kernel/src/auth.erl
+++ b/lib/kernel/src/auth.erl
@@ -24,7 +24,11 @@
%% Old documented interface - deprecated
-export([is_auth/1, cookie/0, cookie/1, node_cookie/1, node_cookie/2]).
--deprecated([{is_auth,1}, {cookie,'_'}, {node_cookie, '_'}]).
+-deprecated([{is_auth,1,"use net_adm:ping/1 instead"},
+ {cookie,0,"use erlang:get_cookie/0 instead"},
+ {cookie,1,"use erlang:set_cookie/2 instead"},
+ {node_cookie, '_',
+ "use erlang:set_cookie/2 and net_adm:ping/1 instead"}]).
%% New interface - meant for internal use within kernel only
-export([get_cookie/0, get_cookie/1,
@@ -104,7 +108,7 @@ get_cookie() ->
-spec get_cookie(Node :: node()) -> 'nocookie' | cookie().
-get_cookie(_Node) when node() =:= nonode@nohost ->
+get_cookie(Node) when Node =:= nonode@nohost ->
nocookie;
get_cookie(Node) ->
gen_server:call(auth, {get_cookie, Node}, infinity).
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
index 7ac09daae1..7b0ef8cf38 100644
--- a/lib/kernel/src/code.erl
+++ b/lib/kernel/src/code.erl
@@ -20,6 +20,7 @@
-module(code).
-include_lib("kernel/include/logger.hrl").
+-include("eep48.hrl").
%% This is the interface module to the code server. It also contains
%% some implementation details. See also related modules: code_*.erl
@@ -44,6 +45,7 @@
soft_purge/1,
is_loaded/1,
all_loaded/0,
+ all_available/0,
stop/0,
root_dir/0,
lib_dir/0,
@@ -68,18 +70,21 @@
rehash/0,
start_link/0,
which/1,
+ get_doc/1,
where_is_file/1,
where_is_file/2,
set_primary_archive/4,
clash/0,
+ module_status/0,
module_status/1,
modified_modules/0,
get_mode/0]).
--deprecated({rehash,0,next_major_release}).
+-deprecated({rehash,0,"the code path cache feature has been removed"}).
-export_type([load_error_rsn/0, load_ret/0]).
-export_type([prepared_code/0]).
+-export_type([module_status/0]).
-include_lib("kernel/include/file.hrl").
@@ -218,6 +223,53 @@ get_object_code(Mod) when is_atom(Mod) -> call({get_object_code, Mod}).
Loaded :: loaded_filename().
all_loaded() -> call(all_loaded).
+-spec all_available() -> [{Module, Filename, Loaded}] when
+ Module :: string(),
+ Filename :: loaded_filename(),
+ Loaded :: boolean().
+all_available() ->
+ case code:get_mode() of
+ interactive ->
+ all_available(get_path(), #{});
+ embedded ->
+ all_available([], #{})
+ end.
+all_available([Path|Tail], Acc) ->
+ case erl_prim_loader:list_dir(Path) of
+ {ok, Files} ->
+ all_available(Tail, all_available(Path, Files, Acc));
+ _Error ->
+ all_available(Tail, Acc)
+ end;
+all_available([], AllModules) ->
+ AllLoaded = [{atom_to_list(M),Path,true} || {M,Path} <- all_loaded()],
+ AllAvailable =
+ maps:fold(
+ fun(File, Path, Acc) ->
+ [{filename:rootname(File), filename:append(Path, File), false} | Acc]
+ end, [], AllModules),
+ OrderFun = fun F({A,_,_},{B,_,_}) ->
+ F(A,B);
+ F(A,B) ->
+ A =< B
+ end,
+ lists:umerge(OrderFun, lists:sort(OrderFun, AllLoaded), lists:sort(OrderFun, AllAvailable)).
+
+all_available(Path, [File | T], Acc) ->
+ case filename:extension(File) of
+ ".beam" ->
+ case maps:is_key(File, Acc) of
+ false ->
+ all_available(Path, T, Acc#{ File => Path });
+ true ->
+ all_available(Path, T, Acc)
+ end;
+ _Else ->
+ all_available(Path, T, Acc)
+ end;
+all_available(_Path, [], Acc) ->
+ Acc.
+
-spec stop() -> no_return().
stop() -> call(stop).
@@ -734,7 +786,7 @@ start_get_mode() ->
-spec which(Module) -> Which when
Module :: module(),
- Which :: file:filename() | loaded_ret_atoms() | non_existing.
+ Which :: loaded_filename() | non_existing.
which(Module) when is_atom(Module) ->
case is_loaded(Module) of
false ->
@@ -783,6 +835,91 @@ where_is_file(Tail, File, Path, Files) ->
where_is_file(Tail, File)
end.
+-spec get_doc(Mod) -> {ok, Res} | {error, Reason} when
+ Mod :: module(),
+ Res :: #docs_v1{},
+ Reason :: non_existing | missing | file:posix().
+get_doc(Mod) when is_atom(Mod) ->
+ case which(Mod) of
+ preloaded ->
+ Fn = filename:join([code:lib_dir(erts),"ebin",atom_to_list(Mod) ++ ".beam"]),
+ get_doc_chunk(Fn, Mod);
+ Error when is_atom(Error) ->
+ {error, Error};
+ Fn ->
+ get_doc_chunk(Fn, Mod)
+ end.
+
+get_doc_chunk(Filename, Mod) when is_atom(Mod) ->
+ case beam_lib:chunks(Filename, ["Docs"]) of
+ {error,beam_lib,{missing_chunk,_,_}} ->
+ case get_doc_chunk(Filename, atom_to_list(Mod)) of
+ {error,missing} ->
+ get_doc_chunk_from_ast(Filename);
+ Error ->
+ Error
+ end;
+ {error,beam_lib,{file_error,_Filename,enoent}} ->
+ get_doc_chunk(Filename, atom_to_list(Mod));
+ {ok, {Mod, [{"Docs",Bin}]}} ->
+ {ok,binary_to_term(Bin)}
+ end;
+get_doc_chunk(Filename, Mod) ->
+ case filename:dirname(Filename) of
+ Filename ->
+ {error,missing};
+ Dir ->
+ ChunkFile = filename:join([Dir,"doc","chunks",Mod ++ ".chunk"]),
+ case file:read_file(ChunkFile) of
+ {ok, Bin} ->
+ {ok, binary_to_term(Bin)};
+ {error,enoent} ->
+ get_doc_chunk(Dir, Mod);
+ {error,Reason} ->
+ {error,Reason}
+ end
+ end.
+
+get_doc_chunk_from_ast(Filename) ->
+ case beam_lib:chunks(Filename, [abstract_code]) of
+ {error,beam_lib,{missing_chunk,_,_}} ->
+ {error,missing};
+ {ok, {_Mod, [{abstract_code,
+ {raw_abstract_v1, AST}}]}} ->
+ Docs = get_function_docs_from_ast(AST),
+ {ok, #docs_v1{ anno = 0, beam_language = erlang,
+ module_doc = none,
+ metadata = #{ generated => true, otp_doc_vsn => ?CURR_DOC_VERSION },
+ docs = Docs }};
+ {ok, {_Mod, [{abstract_code,no_abstract_code}]}} ->
+ {error,missing};
+ Error ->
+ Error
+ end.
+
+get_function_docs_from_ast(AST) ->
+ lists:flatmap(fun(E) -> get_function_docs_from_ast(E, AST) end, AST).
+get_function_docs_from_ast({function,Anno,Name,Arity,_Code}, AST) ->
+ Signature = io_lib:format("~p/~p",[Name,Arity]),
+ Specs = lists:filter(
+ fun({attribute,_Ln,spec,{FA,_}}) ->
+ case FA of
+ {F,A} ->
+ F =:= Name andalso A =:= Arity;
+ {_, F, A} ->
+ F =:= Name andalso A =:= Arity
+ end;
+ (_) -> false
+ end, AST),
+ SpecMd = case Specs of
+ [S] -> #{ signature => [S] };
+ [] -> #{}
+ end,
+ [{{function, Name, Arity}, Anno,
+ [unicode:characters_to_binary(Signature)], none, SpecMd}];
+get_function_docs_from_ast(_, _) ->
+ [].
+
-spec set_primary_archive(ArchiveFile :: file:filename(),
ArchiveBin :: binary(),
FileInfo :: file:file_info(),
@@ -804,7 +941,7 @@ set_primary_archive(ArchiveFile0, ArchiveBin, #file_info{} = FileInfo,
{error, _Reason} = Error ->
Error
end.
-
+
%% Search the entire path system looking for name clashes
-spec clash() -> 'ok'.
@@ -915,8 +1052,19 @@ load_all_native_1([{Mod,BeamFilename}|T], ChunkTag) ->
load_all_native_1([], _) ->
ok.
+-type module_status() :: not_loaded | loaded | modified | removed.
+
+%% Returns the list of all loaded modules and their current status
+-spec module_status() -> [{module(), module_status()}].
+module_status() ->
+ module_status([M || {M, _} <- all_loaded()]).
+
%% Returns the status of the module in relation to object file on disk.
--spec module_status(Module :: module()) -> not_loaded | loaded | modified | removed.
+-spec module_status (Module :: module() | [module()]) ->
+ module_status() | [{module(), module_status()}].
+module_status(Modules) when is_list(Modules) ->
+ PathFiles = path_files(),
+ [{M, module_status(M, PathFiles)} || M <- Modules];
module_status(Module) ->
module_status(Module, code:get_path()).
@@ -991,9 +1139,7 @@ get_beam_chunk(Path, Chunk) ->
%% Returns a list of all modules modified on disk.
-spec modified_modules() -> [module()].
modified_modules() ->
- PathFiles = path_files(),
- [M || {M, _} <- code:all_loaded(),
- module_status(M, PathFiles) =:= modified].
+ [M || {M, modified} <- module_status()].
%% prefetch the directory contents of code path directories
path_files() ->
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index 126de66d13..4c4823eb38 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -93,7 +93,7 @@ init(Ref, Parent, [Root,Mode]) ->
root = Root,
path = Path,
moddb = Db,
- namedb = init_namedb(Path),
+ namedb = create_namedb(Path, Root),
mode = Mode},
Parent ! {Ref,{ok,self()}},
@@ -265,8 +265,8 @@ handle_call({add_paths,Where,Dirs0}, _From,
{reply,Resp,S#state{path=Path}};
handle_call({set_path,PathList}, _From,
- #state{path=Path0,namedb=Namedb}=S) ->
- {Resp,Path,NewDb} = set_path(PathList, Path0, Namedb),
+ #state{root=Root,path=Path0,namedb=Namedb}=S) ->
+ {Resp,Path,NewDb} = set_path(PathList, Path0, Namedb, Root),
{reply,Resp,S#state{path=Path,namedb=NewDb}};
handle_call({del_path,Name}, _From,
@@ -755,12 +755,12 @@ update(Dir, NameDb) ->
%%
%% Set a completely new path.
%%
-set_path(NewPath0, OldPath, NameDb) ->
+set_path(NewPath0, OldPath, NameDb, Root) ->
NewPath = normalize(NewPath0),
case check_path(NewPath) of
{ok, NewPath2} ->
ets:delete(NameDb),
- NewDb = init_namedb(NewPath2),
+ NewDb = create_namedb(NewPath2, Root),
{true, NewPath2, NewDb};
Error ->
{Error, OldPath, NameDb}
@@ -788,11 +788,27 @@ normalize(Other) ->
%% Handle a table of name-directory pairs.
%% The priv_dir/1 and lib_dir/1 functions will have
%% an O(1) lookup.
-init_namedb(Path) ->
- Db = ets:new(code_names,[private]),
+create_namedb(Path, Root) ->
+ Db = ets:new(code_names,[named_table, public]),
init_namedb(lists:reverse(Path), Db),
+
+ case lookup_name("erts", Db) of
+ {ok, _, _, _} ->
+ %% erts is part of code path
+ ok;
+ false ->
+ %% No erts in code path, check if this is a source
+ %% repo and if so use that.
+ ErtsDir = filename:join(Root, "erts"),
+ case erl_prim_loader:read_file_info(ErtsDir) of
+ error ->
+ ok;
+ _ ->
+ do_insert_name("erts", ErtsDir, Db)
+ end
+ end,
Db.
-
+
init_namedb([P|Path], Db) ->
insert_dir(P, Db),
init_namedb(Path, Db);
@@ -997,7 +1013,6 @@ lookup_name(Name, Db) ->
_ -> false
end.
-
%%
%% Fetch a directory.
%%
diff --git a/lib/kernel/src/disk_log.erl b/lib/kernel/src/disk_log.erl
index 99ea8dc384..3e1d0a2224 100644
--- a/lib/kernel/src/disk_log.erl
+++ b/lib/kernel/src/disk_log.erl
@@ -133,7 +133,7 @@ log(Log, Term) ->
blog(Log, Bytes) ->
req(Log, {log, external, [ensure_binary(Bytes)]}).
--spec log_terms(Log, TermList) -> ok | {error, Resaon :: log_error_rsn()} when
+-spec log_terms(Log, TermList) -> ok | {error, Reason :: log_error_rsn()} when
Log :: log(),
TermList :: [term()].
log_terms(Log, Terms) ->
@@ -763,7 +763,7 @@ handle({From, {truncate, Head, F, A}}=Message, S) ->
ok ->
erase(is_full),
notify_owners({truncated, S#state.cnt}),
- N = if Head =:= none -> 0; true -> 1 end,
+ N = if H =:= none -> 0; true -> 1 end,
reply(From, ok, (state_ok(S))#state{cnt = N});
Error ->
do_exit(S, From, Error, ?failure(Error, F, A))
diff --git a/lib/kernel/src/disk_log_server.erl b/lib/kernel/src/disk_log_server.erl
index 78c15d0ad8..2e22f28b14 100644
--- a/lib/kernel/src/disk_log_server.erl
+++ b/lib/kernel/src/disk_log_server.erl
@@ -38,6 +38,12 @@
-record(state, {pending = [] :: [#pending{}]}).
+-compile({nowarn_deprecated_function, [{pg2, create, 1}]}).
+-compile({nowarn_deprecated_function, [{pg2, join, 2}]}).
+-compile({nowarn_deprecated_function, [{pg2, leave, 2}]}).
+-compile({nowarn_deprecated_function, [{pg2, which_groups, 0}]}).
+-compile({nowarn_deprecated_function, [{pg2, get_members, 1}]}).
+
%%%-----------------------------------------------------------------
%%% This module implements the disk_log server. Its primary purpose
%%% is to keep the ets table 'disk_log_names' updated and to handle
diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl
index 6a4fac115a..75a48e8ac4 100644
--- a/lib/kernel/src/dist_util.erl
+++ b/lib/kernel/src/dist_util.erl
@@ -27,7 +27,7 @@
%%-compile(export_all).
-export([handshake_we_started/1, handshake_other_started/1,
- strict_order_flags/0,
+ strict_order_flags/0, rejectable_flags/0,
start_timer/1, setup_timer/2,
reset_timer/1, cancel_timer/1,
is_node_name/1, split_node/1, is_allowed/2,
@@ -70,6 +70,8 @@
-define(u32(X3,X2,X1,X0),
(((X3) bsl 24) bor ((X2) bsl 16) bor ((X1) bsl 8) bor (X0))).
+-define(CREATION_UNKNOWN,0).
+
-record(tick, {read = 0,
write = 0,
tick = 0,
@@ -120,6 +122,12 @@ dflag2str(?DFLAG_EXIT_PAYLOAD) ->
"EXIT_PAYLOAD";
dflag2str(?DFLAG_FRAGMENTS) ->
"FRAGMENTS";
+dflag2str(?DFLAG_HANDSHAKE_23) ->
+ "HANDSHAKE_23";
+dflag2str(?DFLAG_SPAWN) ->
+ "SPAWN";
+dflag2str(?DFLAG_NAME_ME) ->
+ "NAME_ME";
dflag2str(_) ->
"UNKNOWN".
@@ -127,9 +135,11 @@ dflag2str(_) ->
adjust_flags(ThisFlags, OtherFlags) ->
ThisFlags band OtherFlags.
-publish_flag(hidden, _) ->
+publish_flag(_, NameMeFlg, _) when (NameMeFlg band ?DFLAG_NAME_ME) =/= 0 ->
+ ?DFLAG_NAME_ME;
+publish_flag(hidden, _, _) ->
0;
-publish_flag(_, OtherNode) ->
+publish_flag(_, _, OtherNode) when is_atom(OtherNode) ->
case net_kernel:publish_on_node(OtherNode) of
true ->
?DFLAG_PUBLISHED;
@@ -137,6 +147,13 @@ publish_flag(_, OtherNode) ->
0
end.
+name_type(Flags) ->
+ case (Flags band ?DFLAG_NAME_ME) of
+ 0 ->
+ static;
+ ?DFLAG_NAME_ME ->
+ dynamic
+ end.
%% Sync with dist.c
-record(erts_dflags, {
@@ -152,8 +169,13 @@ strict_order_flags() ->
EDF = erts_internal:get_dflags(),
EDF#erts_dflags.strict_order.
+-spec rejectable_flags() -> integer().
+rejectable_flags() ->
+ EDF = erts_internal:get_dflags(),
+ EDF#erts_dflags.rejectable.
+
make_this_flags(RequestType, AddFlags, RejectFlags, OtherNode,
- #erts_dflags{}=EDF) ->
+ #erts_dflags{}=EDF, NameMeFlg) ->
case RejectFlags band (bnot EDF#erts_dflags.rejectable) of
0 -> ok;
Rerror -> exit({"Rejecting non rejectable flags", Rerror})
@@ -163,7 +185,7 @@ make_this_flags(RequestType, AddFlags, RejectFlags, OtherNode,
Aerror -> exit({"Adding non addable flags", Aerror})
end,
Flgs0 = EDF#erts_dflags.default,
- Flgs1 = Flgs0 bor publish_flag(RequestType, OtherNode),
+ Flgs1 = Flgs0 bor publish_flag(RequestType, NameMeFlg, OtherNode),
Flgs2 = Flgs1 bor AddFlags,
Flgs2 band (bnot RejectFlags).
@@ -174,30 +196,37 @@ handshake_other_started(#hs_data{request_type=ReqType,
AddFlgs = convert_flags(AddFlgs0),
RejFlgs = convert_flags(RejFlgs0),
ReqFlgs = convert_flags(ReqFlgs0),
- {PreOtherFlags,Node,Version} = recv_name(HSData0),
+ {PreOtherFlags,NodeOrHost,Creation,SendNameVersion} = recv_name(HSData0),
EDF = erts_internal:get_dflags(),
- PreThisFlags = make_this_flags(ReqType, AddFlgs, RejFlgs, Node, EDF),
- ChosenFlags = adjust_flags(PreThisFlags, PreOtherFlags),
- HSData = HSData0#hs_data{this_flags=ChosenFlags,
- other_flags=ChosenFlags,
- other_version=Version,
- other_node=Node,
- other_started=true,
- add_flags=AddFlgs,
- reject_flags=RejFlgs,
- require_flags=ReqFlgs},
- check_dflags(HSData, EDF),
- ?debug({"MD5 connection from ~p (V~p)~n",
- [Node, HSData#hs_data.other_version]}),
- mark_pending(HSData),
+ PreThisFlags = make_this_flags(ReqType, AddFlgs, RejFlgs, NodeOrHost, EDF,
+ PreOtherFlags),
+ HSData1 = HSData0#hs_data{this_flags=PreThisFlags,
+ other_flags=PreOtherFlags,
+ other_version=flags_to_version(PreOtherFlags),
+ other_node=NodeOrHost,
+ other_started=true,
+ other_creation=Creation,
+ add_flags=AddFlgs,
+ reject_flags=RejFlgs,
+ require_flags=ReqFlgs},
+ check_dflags(HSData1, EDF),
+ ?debug({"MD5 connection from ~p~n", [NodeOrHost]}),
+ HSData2 = mark_pending(HSData1),
+ Node = HSData2#hs_data.other_node,
{MyCookie,HisCookie} = get_cookies(Node),
ChallengeA = gen_challenge(),
- send_challenge(HSData, ChallengeA),
- reset_timer(HSData#hs_data.timer),
- ChallengeB = recv_challenge_reply(HSData, ChallengeA, MyCookie),
- send_challenge_ack(HSData, gen_digest(ChallengeB, HisCookie)),
+ send_challenge(HSData2, ChallengeA),
+ reset_timer(HSData2#hs_data.timer),
+ HSData3 = recv_complement(HSData2, SendNameVersion),
+ check_dflags(HSData3, EDF),
+ ChosenFlags = adjust_flags(HSData3#hs_data.this_flags,
+ HSData3#hs_data.other_flags),
+ HSData4 = HSData3#hs_data{this_flags = ChosenFlags,
+ other_flags = ChosenFlags},
+ ChallengeB = recv_challenge_reply(HSData4, ChallengeA, MyCookie),
+ send_challenge_ack(HSData4, gen_digest(ChallengeB, HisCookie)),
?debug({dist_util, self(), accept_connection, Node}),
- connection(HSData);
+ connection(HSData4);
handshake_other_started(OldHsData) when element(1,OldHsData) =:= hs_data ->
handshake_other_started(convert_old_hsdata(OldHsData)).
@@ -256,16 +285,23 @@ mark_pending(#hs_data{kernel_pid=Kernel,
other_node=Node,
this_node=MyNode}=HSData) ->
case do_mark_pending(Kernel, MyNode, Node,
- (HSData#hs_data.f_address)(HSData#hs_data.socket,
- Node),
HSData#hs_data.other_flags) of
ok ->
send_status(HSData, ok),
- reset_timer(HSData#hs_data.timer);
+ reset_timer(HSData#hs_data.timer),
+ HSData;
+
+ {ok, DynNodeName, Creation} ->
+ HSData1 = HSData#hs_data{other_node = DynNodeName,
+ other_creation = Creation},
+ send_status(HSData1, named),
+ reset_timer(HSData1#hs_data.timer),
+ HSData1;
ok_pending ->
send_status(HSData, ok_simultaneous),
- reset_timer(HSData#hs_data.timer);
+ reset_timer(HSData#hs_data.timer),
+ HSData;
nok_pending ->
send_status(HSData, nok),
@@ -281,7 +317,8 @@ mark_pending(#hs_data{kernel_pid=Kernel,
%% and goes up again and contact us before we have
%% detected that the socket was closed.
wait_pending(Kernel),
- reset_timer(HSData#hs_data.timer);
+ reset_timer(HSData#hs_data.timer),
+ HSData;
already_pending ->
%% FIXME: is this a case ?
@@ -305,18 +342,18 @@ wait_pending(Kernel) ->
do_alive(#hs_data{other_node = Node} = HSData) ->
send_status(HSData, alive),
- case recv_status(HSData) of
+ case recv_status_reply(HSData) of
true -> true;
false -> ?shutdown(Node)
end.
-do_mark_pending(Kernel, MyNode, Node, Address, Flags) ->
- Kernel ! {self(), {accept_pending,MyNode,Node,Address,
+do_mark_pending(Kernel, MyNode, Node, Flags) ->
+ Kernel ! {self(), {accept_pending,MyNode,Node,
publish_type(Flags)}},
receive
{Kernel,{accept_pending,Ret}} ->
?trace("do_mark_pending(~p,~p,~p,~p) -> ~p~n",
- [Kernel,Node,Address,Flags,Ret]),
+ [Kernel, MyNode, Node, Flags, Ret]),
Ret
end.
@@ -350,6 +387,7 @@ shutdown(_Module, _Line, _Data, Reason) ->
%% exit({Reason, ?MODULE, _Line, _Data, erlang:timestamp()}).
handshake_we_started(#hs_data{request_type=ReqType,
+ this_node=MyNode,
other_node=Node,
add_flags=AddFlgs0,
reject_flags=RejFlgs0,
@@ -358,26 +396,41 @@ handshake_we_started(#hs_data{request_type=ReqType,
RejFlgs = convert_flags(RejFlgs0),
ReqFlgs = convert_flags(ReqFlgs0),
EDF = erts_internal:get_dflags(),
- PreThisFlags = make_this_flags(ReqType, AddFlgs, RejFlgs, Node, EDF),
- HSData = PreHSData#hs_data{this_flags = PreThisFlags,
+ {NameMeFlg, NameToSend, MinVersion} =
+ case node() of
+ nonode@nohost ->
+ {node, "undefined", Host} = split_node(MyNode),
+ false = net_kernel:dist_listen(),
+ {?DFLAG_NAME_ME, Host, ?ERL_DIST_VER_6};
+
+ _ ->
+ {0, MyNode, PreHSData#hs_data.other_version}
+ end,
+ PreThisFlags = make_this_flags(ReqType, AddFlgs, RejFlgs, Node, EDF, NameMeFlg),
+ HSData = PreHSData#hs_data{this_node = NameToSend,
+ this_flags = PreThisFlags,
+ other_version = MinVersion,
add_flags = AddFlgs,
reject_flags = RejFlgs,
require_flags = ReqFlgs},
- send_name(HSData),
- recv_status(HSData),
- {PreOtherFlags,ChallengeA} = recv_challenge(HSData),
+ SendNameVersion = send_name(HSData),
+ HSData1 = recv_status(HSData),
+ {PreOtherFlags, ChallengeA, Creation} = recv_challenge(HSData1),
ChosenFlags = adjust_flags(PreThisFlags, PreOtherFlags),
- NewHSData = HSData#hs_data{this_flags = ChosenFlags,
+ HSData2 = HSData1#hs_data{this_flags = ChosenFlags,
other_flags = ChosenFlags,
- other_started = false},
- check_dflags(NewHSData, EDF),
+ other_started = false,
+ other_version = flags_to_version(PreOtherFlags),
+ other_creation = Creation},
+ check_dflags(HSData2, EDF),
MyChallenge = gen_challenge(),
{MyCookie,HisCookie} = get_cookies(Node),
- send_challenge_reply(NewHSData,MyChallenge,
+ send_complement(HSData2, SendNameVersion),
+ send_challenge_reply(HSData2,MyChallenge,
gen_digest(ChallengeA,HisCookie)),
- reset_timer(NewHSData#hs_data.timer),
- recv_challenge_ack(NewHSData, MyChallenge, MyCookie),
- connection(NewHSData);
+ reset_timer(HSData2#hs_data.timer),
+ recv_challenge_ack(HSData2, MyChallenge, MyCookie),
+ connection(HSData2);
handshake_we_started(OldHsData) when element(1,OldHsData) =:= hs_data ->
handshake_we_started(convert_old_hsdata(OldHsData)).
@@ -393,6 +446,16 @@ convert_flags(Flags) when is_integer(Flags) ->
convert_flags(_Undefined) ->
0.
+flags_to_version(Flags) ->
+ case Flags band ?DFLAG_HANDSHAKE_23 of
+ 0 ->
+ ?ERL_DIST_VER_5;
+ ?DFLAG_HANDSHAKE_23 ->
+ ?ERL_DIST_VER_6
+ end.
+
+
+
%% --------------------------------------------------------------
%% The connection has been established.
%% --------------------------------------------------------------
@@ -406,9 +469,9 @@ connection(#hs_data{other_node = Node,
PType = publish_type(HSData#hs_data.other_flags),
case FPreNodeup(Socket) of
ok ->
- DHandle = do_setnode(HSData), % Succeeds or exits the process.
+ {DHandle,NamedMe} = do_setnode(HSData), % Succeeds or exits the process.
Address = FAddress(Socket,Node),
- mark_nodeup(HSData,Address),
+ mark_nodeup(HSData,Address,NamedMe),
case FPostNodeup(Socket) of
ok ->
case HSData#hs_data.f_handshake_complete of
@@ -469,16 +532,27 @@ get_cookies(Node) ->
%% No error return; either succeeds or terminates the process.
do_setnode(#hs_data{other_node = Node, socket = Socket,
+ this_node = MyNode,
other_flags = Flags, other_version = Version,
- f_getll = GetLL}) ->
+ f_getll = GetLL,
+ other_creation = Creation}=HSData) ->
case GetLL(Socket) of
{ok,Port} ->
- ?trace("setnode(md5,~p ~p ~p)~n",
- [Node, Port, {publish_type(Flags),
- '(', Flags, ')',
- Version}]),
+ NamedMe = case node() of
+ nonode@nohost ->
+ dynamic = name_type(HSData#hs_data.this_flags),
+ erlang:setnode(MyNode, HSData#hs_data.this_creation),
+ true;
+ MyNode ->
+ false
+ end,
+ ?trace("setnode: node=~p port=~p flags=~p(~p) ver=~p creation=~p~n",
+ [Node, Port, Flags, publish_type(Flags), Version, Creation]),
+
+ MyNode = node(), % ASSERT
try
- erlang:setnode(Node, Port, {Flags, Version, '', ''})
+ DHandle = erlang:setnode(Node, Port, {Flags, Version, Creation}),
+ {DHandle, NamedMe}
catch
error:system_limit ->
error_msg("** Distribution system limit reached, "
@@ -499,9 +573,8 @@ mark_nodeup(#hs_data{kernel_pid = Kernel,
other_node = Node,
other_flags = Flags,
other_started = OtherStarted},
- Address) ->
- Kernel ! {self(), {nodeup,Node,Address,publish_type(Flags),
- true}},
+ Address, NamedMe) ->
+ Kernel ! {self(), {nodeup,Node,Address,publish_type(Flags),NamedMe}},
receive
{Kernel, inserted} ->
ok;
@@ -585,21 +658,85 @@ send_name(#hs_data{socket = Socket, this_node = Node,
f_send = FSend,
this_flags = Flags,
other_version = Version}) ->
- ?trace("send_name: node=~w, version=~w\n",
- [Node,Version]),
- ?to_port(FSend, Socket,
- [$n, ?int16(Version), ?int32(Flags), atom_to_list(Node)]).
+ NameBin = to_binary(Node),
+ if Version =:= undefined;
+ Version =:= ?ERL_DIST_VER_5 ->
+ %% We treat "5" the same as 'undefined' as there are
+ %% custom made epmd modules out there with a hardcoded "5".
+ %%
+ %% Send old 'n' message but with DFLAG_HANDSHAKE_23
+ %% Old nodes will ignore DFLAG_HANDSHAKE_23 and reply old 'n' challenge.
+ %% New nodes will see DFLAG_HANDSHAKE_23 and reply new 'N' challenge.
+ ?trace("send_name: 'n' node=~p, version=~w\n",
+ [Node, ?ERL_DIST_VER_5]),
+ _ = ?to_port(FSend, Socket,
+ [<<$n, ?ERL_DIST_VER_5:16, Flags:32>>, NameBin]),
+ ?ERL_DIST_VER_5;
+
+ is_integer(Version), Version >= ?ERL_DIST_VER_6 ->
+ Creation = case name_type(Flags) of
+ static -> erts_internal:get_creation();
+ dynamic -> 0
+ end,
+ NameLen = byte_size(NameBin),
+ ?trace("send_name: 'N' node=~p creation=~w\n",
+ [Node, Creation]),
+ _ = ?to_port(FSend, Socket,
+ [<<$N, Flags:64, Creation:32, NameLen:16>>, NameBin]),
+ ?ERL_DIST_VER_6
+ end.
+
+to_binary(Atom) when is_atom(Atom) ->
+ atom_to_binary(Atom, latin1);
+to_binary(List) when is_list(List) ->
+ list_to_binary(List).
send_challenge(#hs_data{socket = Socket, this_node = Node,
- other_version = Version,
- this_flags = Flags,
+ this_flags = ThisFlags,
+ other_flags = OtherFlags,
f_send = FSend},
Challenge ) ->
- ?trace("send: challenge=~w version=~w\n",
- [Challenge,Version]),
- ?to_port(FSend, Socket, [$n,?int16(Version), ?int32(Flags),
- ?int32(Challenge),
- atom_to_list(Node)]).
+ case OtherFlags band ?DFLAG_HANDSHAKE_23 of
+ 0 ->
+ %% Reply with old 'n' message
+ ?trace("send: 'n' challenge=~w\n", [Challenge]),
+
+ ?to_port(FSend, Socket, [<<$n,
+ ?ERL_DIST_VER_5:16, % echo same Version back
+ ThisFlags:32,
+ Challenge:32>>,
+ atom_to_list(Node)]);
+
+ ?DFLAG_HANDSHAKE_23 ->
+ %% Reply with new 'N' message
+ Creation = erts_internal:get_creation(),
+ NodeName = atom_to_binary(Node, latin1),
+ NameLen = byte_size(NodeName),
+ ?trace("send: 'N' challenge=~w creation=~w\n",
+ [Challenge,Creation]),
+ ?to_port(FSend, Socket, [<<$N,
+ ThisFlags:64,
+ Challenge:32,
+ Creation:32,
+ NameLen:16>>, NodeName])
+ end.
+
+send_complement(#hs_data{socket = Socket,
+ f_send = FSend,
+ this_flags = Flags,
+ other_flags = Flags},
+ SendNameVersion) ->
+ if SendNameVersion =:= ?ERL_DIST_VER_5,
+ (Flags band ?DFLAG_HANDSHAKE_23) =/= 0 ->
+ %% We sent an old 'n' name message and need to complement
+ %% with creation value.
+ Creation = erts_internal:get_creation(),
+ FlagsHigh = Flags bsr 32,
+ ?trace("send_complement: 'c' flags_high=~w creation=~w\n", [FlagsHigh,Creation]),
+ ?to_port(FSend, Socket, [<<$c, FlagsHigh:32, Creation:32>>]);
+ true->
+ ok % no complement msg needed
+ end.
send_challenge_reply(#hs_data{socket = Socket, f_send = FSend},
Challenge, Digest) ->
@@ -614,34 +751,67 @@ send_challenge_ack(#hs_data{socket = Socket, f_send = FSend},
%%
-%% Get the name of the other side.
+%% Receive first handshake message sent from connecting side.
%% Close the connection if invalid data.
-%% The IP address sent is not interesting (as in the old
-%% tcp_drv.c which used it to detect simultaneous connection
-%% attempts).
%%
recv_name(#hs_data{socket = Socket, f_recv = Recv} = HSData) ->
case Recv(Socket, 0, infinity) of
- {ok,
- [$n,VersionA, VersionB, Flag1, Flag2, Flag3, Flag4
- | OtherNode] = Data} ->
- case is_node_name(OtherNode) of
- true ->
- Flags = ?u32(Flag1, Flag2, Flag3, Flag4),
- Version = ?u16(VersionA,VersionB),
- is_allowed(HSData, Flags, OtherNode, Version);
- false ->
- ?shutdown(Data)
- end;
+ {ok, [$n | _] = Data} ->
+ recv_name_old(HSData, Data);
+ {ok, [$N | _] = Data} ->
+ recv_name_new(HSData, Data);
_ ->
?shutdown(no_node)
end.
-is_node_name(OtherNodeName) ->
- case string:split(OtherNodeName, "@", all) of
- [Name,Host] ->
- (not string:is_empty(Name))
- andalso (not string:is_empty(Host));
+recv_name_old(HSData,
+ [$n, V1, V0, F3, F2, F1, F0 | Node] = Data) ->
+ <<_Version:16>> = <<V1,V0>>,
+ <<Flags:32>> = <<F3,F2,F1,F0>>,
+ ?trace("recv_name: 'n' node=~p version=~w\n", [Node, _Version]),
+ case is_node_name(Node) of
+ true ->
+ check_allowed(HSData, Node),
+ {Flags, list_to_atom(Node), ?CREATION_UNKNOWN, ?ERL_DIST_VER_5};
+ false ->
+ ?shutdown(Data)
+ end.
+
+recv_name_new(HSData,
+ [$N, F7,F6,F5,F4,F3,F2,F1,F0, Cr3,Cr2,Cr1,Cr0,
+ NL1, NL0 | Rest] = Data) ->
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
+ <<NameLen:16>> = <<NL1,NL0>>,
+ {Name, _Residue} = lists:split(NameLen, Rest),
+ ?trace("recv_name: 'N' name=~p creation=~w\n", [Name, Creation]),
+ case is_name_ok(Name, Flags) of
+ true ->
+ check_allowed(HSData, Name),
+ NodeOrHost = case name_type(Flags) of
+ static ->
+ list_to_atom(Name);
+ dynamic ->
+ %% Keep host name as string
+ Name
+ end,
+ {Flags, NodeOrHost, Creation, ?ERL_DIST_VER_6};
+ false ->
+ ?shutdown(Data)
+ end.
+
+is_node_name(NodeName) ->
+ is_name_ok(NodeName, 0).
+
+is_name_ok(NodeOrHost, Flags) ->
+ case {string:split(NodeOrHost, "@", all), name_type(Flags)} of
+ {[Name,Host], static} ->
+ (not string:is_empty(Host))
+ andalso (not string:is_empty(Name));
+
+ {[Host], dynamic} ->
+ not string:is_empty(Host);
+
_ ->
false
end.
@@ -674,12 +844,12 @@ split_node(Node) ->
%% with allow-node-scheme. An empty allowed list
%% allows all nodes.
%%
-is_allowed(#hs_data{allowed = []}, Flags, Node, Version) ->
- {Flags,list_to_atom(Node),Version};
-is_allowed(#hs_data{allowed = Allowed} = HSData, Flags, Node, Version) ->
+check_allowed(#hs_data{allowed = []}, _Node) ->
+ ok;
+check_allowed(#hs_data{allowed = Allowed} = HSData, Node) ->
case is_allowed(Node, Allowed) of
true ->
- {Flags,list_to_atom(Node),Version};
+ ok;
false ->
send_status(HSData#hs_data{other_node = Node}, not_allowed),
error_msg("** Connection attempt from "
@@ -742,7 +912,10 @@ is_allowed(Node, [AllowedNode|Allowed]) ->
listify(Atom) when is_atom(Atom) ->
atom_to_list(Atom);
listify(Node) when is_list(Node) ->
- Node.
+ Node;
+listify(Bin) when is_binary(Bin) ->
+ binary_to_list(Bin).
+
publish_type(Flags) ->
case Flags band ?DFLAG_PUBLISHED of
@@ -753,25 +926,91 @@ publish_type(Flags) ->
end.
%% wait for challenge after connect
-recv_challenge(#hs_data{socket=Socket,other_node=Node,
- other_version=Version,f_recv=Recv}) ->
+recv_challenge(#hs_data{socket=Socket, f_recv=Recv}=HSData) ->
case Recv(Socket, 0, infinity) of
- {ok,[$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
- Flags = ?u32(Fl1,Fl2,Fl3,Fl4),
- try {list_to_existing_atom(Ns),?u16(V1,V0)} of
- {Node,Version} ->
- Challenge = ?u32(CA3,CA2,CA1,CA0),
- ?trace("recv: node=~w, challenge=~w version=~w\n",
- [Node, Challenge,Version]),
- {Flags,Challenge};
- _ ->
- ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
- catch
- error:badarg ->
- ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
- end;
+ {ok, [$n | _]=Msg} ->
+ recv_challenge_old(HSData, Msg);
+ {ok,[$N | _]=Msg} ->
+ recv_challenge_new(HSData, Msg);
Other ->
- ?shutdown2(no_node, {recv_challenge_failed, Other})
+ ?shutdown2(no_node, {recv_challenge_failed, Other})
+ end.
+
+recv_challenge_old(#hs_data{other_node=Node},
+ [$n, V1,V0, F3,F2,F1,F0, C3,C2,C1,C0 | Ns]=Msg) ->
+ <<_Version:16>> = <<V1,V0>>,
+ <<Flags:32>> = <<F3,F2,F1,F0>>,
+ <<Challenge:32>> = <<C3,C2,C1,C0>>,
+ ?trace("recv: 'n' node=~p, challenge=~w version=~w\n",
+ [Ns, Challenge, _Version]),
+ try {list_to_existing_atom(Ns), Flags band ?DFLAG_HANDSHAKE_23} of
+ {Node, 0} ->
+ {Flags, Challenge, ?CREATION_UNKNOWN};
+ _ ->
+ ?shutdown2(no_node, {recv_challenge_failed, version, Msg})
+ catch
+ error:badarg ->
+ ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
+ end;
+recv_challenge_old(_, Other) ->
+ ?shutdown2(no_node, {recv_challenge_failed, Other}).
+
+recv_challenge_new(#hs_data{other_node=Node},
+ [$N,
+ F7,F6,F5,F4,F3,F2,F1,F0,
+ Ch3,Ch2,Ch1,Ch0,
+ Cr3,Cr2,Cr1,Cr0,
+ NL1,NL0 | Rest] = Msg) ->
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ <<Challenge:32>> = <<Ch3,Ch2,Ch1,Ch0>>,
+ <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
+ <<NameLen:16>> = <<NL1,NL0>>,
+ {Ns, _Residue} =
+ try
+ lists:split(NameLen, Rest)
+ catch
+ error:badarg ->
+ ?shutdown2(no_node, {recv_challenge_failed, no_node, Msg})
+ end,
+ ?trace("recv: 'N' node=~p, challenge=~w creation=~w\n",
+ [Ns, Challenge, Creation]),
+
+ case Flags band ?DFLAG_HANDSHAKE_23 of
+ ?DFLAG_HANDSHAKE_23 ->
+ try list_to_existing_atom(Ns) of
+ Node ->
+ {Flags, Challenge, Creation};
+ _ ->
+ ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
+ catch
+ error:badarg ->
+ ?shutdown2(no_node, {recv_challenge_failed, no_node, Ns})
+ end;
+ 0 ->
+ ?shutdown2(no_node, {recv_challenge_failed, version, Msg})
+ end;
+recv_challenge_new(_, Other) ->
+ ?shutdown2(no_node, {recv_challenge_failed, Other}).
+
+
+recv_complement(#hs_data{socket = Socket,
+ f_recv = Recv,
+ other_flags = Flags} = HSData,
+ SendNameVersion) ->
+ if SendNameVersion =:= ?ERL_DIST_VER_5,
+ (Flags band ?DFLAG_HANDSHAKE_23) =/= 0 ->
+ case Recv(Socket, 0, infinity) of
+ {ok, [$c, F7,F6,F5,F4, Cr3,Cr2,Cr1,Cr0]} ->
+ <<FlagsHigh:32>> = <<F7,F6,F5,F4>>,
+ <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
+ ?trace("recv_complement: creation=~w\n", [Creation]),
+ HSData#hs_data{other_creation = Creation,
+ other_flags = Flags bor (FlagsHigh bsl 32)};
+ Other ->
+ ?shutdown2(no_node, {recv_complement_failed, Other})
+ end;
+ true ->
+ HSData
end.
@@ -793,8 +1032,8 @@ recv_challenge_reply(#hs_data{socket = Socket,
SumA ->
ChallengeB;
_ ->
- error_msg("** Connection attempt from "
- "disallowed node ~w ** ~n", [NodeB]),
+ error_msg("** Connection attempt from node ~w rejected."
+ " Invalid challenge reply. **~n", [NodeB]),
?shutdown2(NodeB, {recv_challenge_reply_failed, bad_cookie})
end;
Other ->
@@ -813,8 +1052,8 @@ recv_challenge_ack(#hs_data{socket = Socket, f_recv = FRecv,
SumA ->
ok;
_ ->
- error_msg("** Connection attempt to "
- "disallowed node ~w ** ~n", [NodeB]),
+ error_msg("** Connection attempt to node ~w cancelled."
+ " Invalid challenge ack. **~n", [NodeB]),
?shutdown2(NodeB, {recv_challenge_ack_failed, bad_cookie})
end;
Other ->
@@ -822,29 +1061,51 @@ recv_challenge_ack(#hs_data{socket = Socket, f_recv = FRecv,
end.
recv_status(#hs_data{kernel_pid = Kernel, socket = Socket,
+ this_flags = MyFlgs,
other_node = Node, f_recv = Recv} = HSData) ->
case Recv(Socket, 0, infinity) of
- {ok, [$s|StrStat]} ->
- Stat = list_to_atom(StrStat),
+ {ok, "snamed:"++Rest} ->
+ <<NameLen:16,
+ NodeName:NameLen/binary,
+ Creation:32,
+ _/binary>> = list_to_binary(Rest),
+ dynamic = name_type(MyFlgs),
+ {node, _Name, Host} = split_node(NodeName),
+ Host = HSData#hs_data.this_node,
+ HSData#hs_data{this_node = binary_to_atom(NodeName, utf8),
+ this_creation = Creation};
+
+ {ok, [$s|Stat]} ->
?debug({dist_util,self(),recv_status, Node, Stat}),
- case Stat of
- not_allowed -> ?shutdown2(Node, {recv_status_failed, not_allowed});
- nok ->
- %% wait to be killed by net_kernel
- receive
- after infinity -> ok
- end;
- alive ->
- Reply = is_pending(Kernel, Node),
- ?debug({is_pending,self(),Reply}),
- send_status(HSData, Reply),
- if not Reply ->
- ?shutdown(Node);
- Reply ->
- Stat
- end;
- _ -> Stat
- end;
+ case {Stat, name_type(MyFlgs)} of
+ {"not_allowed", _} ->
+ ?shutdown2(Node, {recv_status_failed, not_allowed});
+ {_, dynamic} ->
+ ?shutdown2(Node, {recv_status_failed, unexpected, Stat});
+ _ ->
+ continue
+ end,
+ case Stat of
+ "nok" ->
+ %% wait to be killed by net_kernel
+ receive
+ after infinity -> ok
+ end;
+ "alive" ->
+ Reply = is_pending(Kernel, Node),
+ ?debug({is_pending,self(),Reply}),
+ send_status(HSData, Reply),
+ if not Reply ->
+ ?shutdown(Node);
+ Reply ->
+ HSData
+ end;
+ "ok" -> HSData;
+ "ok_simultaneous" -> HSData;
+ Other ->
+ ?shutdown2(Node, {recv_status_failed, unknown, Other})
+ end;
+
Error ->
?debug({dist_util,self(),recv_status_error,
Node, Error}),
@@ -852,6 +1113,42 @@ recv_status(#hs_data{kernel_pid = Kernel, socket = Socket,
end.
+recv_status_reply(#hs_data{socket = Socket,
+ other_node = Node,
+ f_recv = Recv}) ->
+ case Recv(Socket, 0, infinity) of
+ {ok, [$s|Stat]} ->
+ ?debug({dist_util,self(),recv_status, Node, Stat}),
+ case Stat of
+ "true" -> true;
+ "false" -> false;
+ Other ->
+ ?shutdown2(Node, {recv_status_failed, unexpected, Other})
+ end;
+
+ Error ->
+ ?debug({dist_util,self(),recv_status_error,
+ Node, Error}),
+ ?shutdown2(Node, {recv_status_failed, Error})
+ end.
+
+
+send_status(#hs_data{socket = Socket,
+ other_node = Node,
+ other_creation = Creation,
+ f_send = FSend},
+ named) ->
+ ?debug({dist_util, self(), send_status, Node}),
+ NameBin = atom_to_binary(Node, utf8),
+ NameLen = byte_size(NameBin),
+ case FSend(Socket, [$s, "named:",
+ <<NameLen:16, NameBin/binary>>,
+ <<Creation:32>>]) of
+ {error, _} ->
+ ?shutdown(Node);
+ _ ->
+ true
+ end;
send_status(#hs_data{socket = Socket, other_node = Node,
f_send = FSend}, Stat) ->
?debug({dist_util,self(),send_status, Node, Stat}),
diff --git a/lib/kernel/src/erl_distribution.erl b/lib/kernel/src/erl_distribution.erl
index f07bd351eb..a0d55d62f6 100644
--- a/lib/kernel/src/erl_distribution.erl
+++ b/lib/kernel/src/erl_distribution.erl
@@ -23,7 +23,7 @@
-include_lib("kernel/include/logger.hrl").
--export([start_link/0,start_link/2,init/1,start/1,stop/0]).
+-export([start_link/0,start_link/3,init/1,start/1,stop/0]).
-define(DBG,erlang:display([?MODULE,?LINE])).
@@ -36,8 +36,12 @@ start_link() ->
%% system has already started.
start(Args) ->
- C = {net_sup_dynamic, {?MODULE,start_link,[Args,false]}, permanent,
- 1000, supervisor, [erl_distribution]},
+ C = #{id => net_sup_dynamic,
+ start => {?MODULE,start_link,[Args,false,net_sup_dynamic]},
+ restart => permanent,
+ shutdown => 1000,
+ type => supervisor,
+ modules => [erl_distribution]},
supervisor:start_child(kernel_sup, C).
%% Stop distribution.
@@ -62,8 +66,8 @@ stop() ->
%% Helper start function.
-start_link(Args, CleanHalt) ->
- supervisor:start_link({local,net_sup}, ?MODULE, [Args,CleanHalt]).
+start_link(Args, CleanHalt, NetSup) ->
+ supervisor:start_link({local,net_sup}, ?MODULE, [Args,CleanHalt,NetSup]).
init(NetArgs) ->
Epmd =
@@ -72,23 +76,39 @@ init(NetArgs) ->
[];
_ ->
EpmdMod = net_kernel:epmd_module(),
- [{EpmdMod,{EpmdMod,start_link,[]},
- permanent,2000,worker,[EpmdMod]}]
+ [#{id => EpmdMod,
+ start => {EpmdMod,start_link,[]},
+ restart => permanent,
+ shutdown => 2000,
+ type => worker,
+ modules => [EpmdMod]}]
end,
- Auth = {auth,{auth,start_link,[]},permanent,2000,worker,[auth]},
- Kernel = {net_kernel,{net_kernel,start_link,NetArgs},
- permanent,2000,worker,[net_kernel]},
+ Auth = #{id => auth,
+ start => {auth,start_link,[]},
+ restart => permanent,
+ shutdown => 2000,
+ type => worker,
+ modules => [auth]},
+ Kernel = #{id => net_kernel,
+ start => {net_kernel,start_link,NetArgs},
+ restart => permanent,
+ shutdown => 2000,
+ type => worker,
+ modules => [net_kernel]},
EarlySpecs = net_kernel:protocol_childspecs(),
- {ok,{{one_for_all,0,1}, EarlySpecs ++ Epmd ++ [Auth,Kernel]}}.
+ SupFlags = #{strategy => one_for_all,
+ intensity => 0,
+ period => 1},
+ {ok, {SupFlags, EarlySpecs ++ Epmd ++ [Auth,Kernel]}}.
do_start_link([{Arg,Flag}|T]) ->
case init:get_argument(Arg) of
{ok,[[Name]]} ->
- start_link([list_to_atom(Name),Flag|ticktime()], true);
+ start_link([list_to_atom(Name),Flag|ticktime()], true, net_sup);
{ok,[[Name]|_Rest]} ->
?LOG_WARNING("Multiple -~p given to erl, using the first, ~p",
[Arg, Name]),
- start_link([list_to_atom(Name),Flag|ticktime()], true);
+ start_link([list_to_atom(Name),Flag|ticktime()], true, net_sup);
_ ->
do_start_link(T)
end;
diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl
index 63a51bd874..96806ae3e7 100644
--- a/lib/kernel/src/erl_epmd.erl
+++ b/lib/kernel/src/erl_epmd.erl
@@ -29,19 +29,22 @@
-define(port_please_failure2(Term), noop).
-endif.
+-include("dist.hrl").
+
-ifndef(erlang_daemon_port).
-define(erlang_daemon_port, 4369).
-endif.
-ifndef(epmd_dist_high).
--define(epmd_dist_high, 4370).
+-define(epmd_dist_high, ?ERL_DIST_VER_HIGH).
-endif.
-ifndef(epmd_dist_low).
--define(epmd_dist_low, 4370).
+-define(epmd_dist_low, ?ERL_DIST_VER_LOW).
-endif.
%% External exports
--export([start/0, start_link/0, stop/0, port_please/2,
- port_please/3, names/0, names/1,
+-export([start/0, start_link/0, stop/0,
+ port_please/2, port_please/3, listen_port_please/2,
+ names/0, names/1,
register_node/2, register_node/3, address_please/3, open/0, open/1, open/2]).
%% gen_server callbacks
@@ -50,12 +53,14 @@
-import(lists, [reverse/1]).
--record(state, {socket, port_no = -1, name = ""}).
+-record(state, {socket, port_no = -1, name = "", family}).
-type state() :: #state{}.
-include("inet_int.hrl").
-include("erl_epmd.hrl").
+-include_lib("kernel/include/inet.hrl").
+-define(RECONNECT_TIME, 2000).
%%%----------------------------------------------------------------------
%%% API
@@ -76,7 +81,7 @@ stop() ->
%% return {port, P, Version} | noport
%%
--spec port_please(Name, Host) -> {ok, Port, Version} | noport when
+-spec port_please(Name, Host) -> {ok, Port, Version} | noport | closed | {error, term()} when
Name :: atom() | string(),
Host :: atom() | string() | inet:ip_address(),
Port :: non_neg_integer(),
@@ -85,36 +90,61 @@ stop() ->
port_please(Node, Host) ->
port_please(Node, Host, infinity).
--spec port_please(Name, Host, Timeout) -> {ok, Port, Version} | noport when
+-spec port_please(Name, Host, Timeout) -> {port, Port, Version} | noport | closed | {error, term()} when
Name :: atom() | string(),
Host :: atom() | string() | inet:ip_address(),
Timeout :: non_neg_integer() | infinity,
Port :: non_neg_integer(),
Version :: non_neg_integer().
-port_please(Node,HostName, Timeout) when is_atom(HostName) ->
- port_please1(Node,atom_to_list(HostName), Timeout);
-port_please(Node,HostName, Timeout) when is_list(HostName) ->
- port_please1(Node,HostName, Timeout);
-port_please(Node, EpmdAddr, Timeout) ->
- get_port(Node, EpmdAddr, Timeout).
-
-
-
-port_please1(Node,HostName, Timeout) ->
- Family = case inet_db:res_option(inet6) of
- true ->
- inet6;
- false ->
- inet
- end,
- case inet:gethostbyname(HostName, Family, Timeout) of
- {ok,{hostent, _Name, _ , _Af, _Size, [EpmdAddr | _]}} ->
- get_port(Node, EpmdAddr, Timeout);
- _Else ->
- ?port_please_failure2(_Else),
- noport
- end.
+port_please(Node, HostName, Timeout) ->
+ case listen_port_please(Node, HostName) of
+ {ok, 0} ->
+ case getepmdbyname(HostName, Timeout) of
+ {ok, EpmdAddr} ->
+ get_port(Node, EpmdAddr, Timeout);
+ _Error ->
+ ?port_please_failure2(_Error),
+ noport
+ end;
+ {ok, Prt} ->
+ %% We don't know which dist version the other node is running
+ %% so return the low version so that we can talk to older nodes
+ {port, Prt, ?epmd_dist_low}
+ end.
+
+getepmdbyname(HostName, Timeout) when is_atom(HostName) ->
+ getepmdbyname(atom_to_list(HostName), Timeout);
+getepmdbyname(HostName, Timeout) when is_list(HostName) ->
+ Family = case inet_db:res_option(inet6) of
+ true ->
+ inet6;
+ false ->
+ inet
+ end,
+ case inet:gethostbyname(HostName, Family, Timeout) of
+ {ok,#hostent{ h_addr_list = [EpmdAddr | _]}} ->
+ {ok, EpmdAddr};
+ Else ->
+ Else
+ end;
+getepmdbyname(HostName, _Timeout) ->
+ {ok, HostName}.
+
+-spec listen_port_please(Name, Host) -> {ok, Port} when
+ Name :: atom(),
+ Host :: string() | inet:ip_address(),
+ Port :: non_neg_integer().
+listen_port_please(_Name, _Host) ->
+ try
+ %% Should come up with a new name for this as ERL_EPMD_PORT describes what
+ %% port epmd runs on which could easily be confused with this.
+ {ok, [[StringPort]]} = init:get_argument(erl_epmd_port),
+ Port = list_to_integer(StringPort),
+ {ok, Port}
+ catch error:_ ->
+ {ok, 0}
+ end.
-spec names() -> {ok, [{Name, Port}]} | {error, Reason} when
Name :: string(),
@@ -131,15 +161,13 @@ names() ->
Port :: non_neg_integer(),
Reason :: address | file:posix().
-names(HostName) when is_atom(HostName); is_list(HostName) ->
- case inet:gethostbyname(HostName) of
- {ok,{hostent, _Name, _ , _Af, _Size, [EpmdAddr | _]}} ->
- get_names(EpmdAddr);
- Else ->
- Else
- end;
-names(EpmdAddr) ->
- get_names(EpmdAddr).
+names(HostName) ->
+ case getepmdbyname(HostName, infinity) of
+ {ok,EpmdAddr} ->
+ get_names(EpmdAddr);
+ Else ->
+ Else
+ end.
-spec register_node(Name, Port) -> Result when
Name :: string(),
@@ -154,7 +182,7 @@ register_node(Name, PortNo) ->
Name :: string(),
Port :: non_neg_integer(),
Driver :: inet_tcp | inet6_tcp | inet | inet6,
- Creation :: non_neg_integer(),
+ Creation :: non_neg_integer() | -1,
Result :: {ok, Creation} | {error, already_registered} | term().
register_node(Name, PortNo, inet_tcp) ->
@@ -202,10 +230,18 @@ handle_call({register, Name, PortNo, Family}, _From, State) ->
{alive, Socket, Creation} ->
S = State#state{socket = Socket,
port_no = PortNo,
- name = Name},
+ name = Name,
+ family = Family},
{reply, {ok, Creation}, S};
- Error ->
- {reply, Error, State}
+ Error ->
+ case init:get_argument(erl_epmd_port) of
+ {ok, _} ->
+ {reply, {ok, -1}, State#state{ socket = -1,
+ port_no = PortNo,
+ name = Name} };
+ error ->
+ {reply, Error, State}
+ end
end;
_ ->
{reply, {error, already_registered}, State}
@@ -230,7 +266,17 @@ handle_cast(_, State) ->
-spec handle_info(term(), state()) -> {'noreply', state()}.
handle_info({tcp_closed, Socket}, State) when State#state.socket =:= Socket ->
+ erlang:send_after(?RECONNECT_TIME, self(), reconnect),
{noreply, State#state{socket = -1}};
+handle_info(reconnect, State) when State#state.socket =:= -1 ->
+ case do_register_node(State#state.name, State#state.port_no, State#state.family) of
+ {alive, Socket, _Creation} ->
+ %% ignore the received creation
+ {noreply, State#state{socket = Socket}};
+ _Error ->
+ erlang:send_after(?RECONNECT_TIME, self(), reconnect),
+ {noreply, State}
+ end;
handle_info(_, State) ->
{noreply, State}.
@@ -310,7 +356,7 @@ do_register_node(NodeName, TcpPort, Family) ->
Error
end;
Error ->
- Error
+ Error
end.
epmd_dist_high() ->
@@ -347,6 +393,13 @@ wait_for_reg_reply(Socket, SoFar) ->
receive
{tcp, Socket, Data0} ->
case SoFar ++ Data0 of
+ [$v, Result, A, B, C, D] ->
+ case Result of
+ 0 ->
+ {alive, Socket, ?u32(A, B, C, D)};
+ _ ->
+ {error, duplicate_name}
+ end;
[$y, Result, A, B] ->
case Result of
0 ->
@@ -383,9 +436,8 @@ get_port(Node, EpmdAddress, Timeout) ->
?port_please_failure2(_Error),
noport
end;
- _Error ->
- ?port_please_failure2(_Error),
- noport
+ _Error ->
+ noport
end.
diff --git a/lib/kernel/src/erpc.erl b/lib/kernel/src/erpc.erl
new file mode 100644
index 0000000000..89b286f479
--- /dev/null
+++ b/lib/kernel/src/erpc.erl
@@ -0,0 +1,626 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Author: Rickard Green
+%%
+
+-module(erpc).
+
+%% Exported API
+
+-export([call/2,
+ call/3,
+ call/4,
+ call/5,
+ cast/2,
+ cast/4,
+ send_request/2,
+ send_request/4,
+ receive_response/1,
+ receive_response/2,
+ wait_response/1,
+ wait_response/2,
+ check_response/2,
+ multicall/2,
+ multicall/3,
+ multicall/4,
+ multicall/5,
+ multicast/2,
+ multicast/4]).
+
+-export_type([request_id/0]).
+
+%% Internal exports (also used by the 'rpc' module)
+
+-export([execute_call/4,
+ execute_call/3,
+ execute_cast/3,
+ is_arg_error/4,
+ trim_stack/4,
+ call_result/4]).
+
+%%------------------------------------------------------------------------
+
+-compile({inline,[{result,4}]}). %% Nicer error stack trace...
+
+-define(MAX_INT_TIMEOUT, 4294967295).
+-define(TIMEOUT_TYPE, 0..?MAX_INT_TIMEOUT | 'infinity').
+-define(IS_VALID_TMO_INT(TI_), (is_integer(TI_)
+ andalso (0 =< TI_)
+ andalso (TI_ =< ?MAX_INT_TIMEOUT))).
+-define(IS_VALID_TMO(T_), ((T_ == infinity) orelse ?IS_VALID_TMO_INT(T_))).
+
+%%------------------------------------------------------------------------
+%% Exported API
+%%------------------------------------------------------------------------
+
+-spec call(Node, Fun) -> Result when
+ Node :: node(),
+ Fun :: function(),
+ Result :: term().
+
+call(N, Fun) ->
+ call(N, Fun, infinity).
+
+-spec call(Node, Fun, Timeout) -> Result when
+ Node :: node(),
+ Fun :: function(),
+ Timeout :: ?TIMEOUT_TYPE,
+ Result :: term().
+
+call(N, Fun, Timeout) when is_function(Fun, 0) ->
+ call(N, erlang, apply, [Fun, []], Timeout);
+call(_N, _Fun, _Timeout) ->
+ error({?MODULE, badarg}).
+
+-spec call(Node, Module, Function, Args) -> Result when
+ Node :: node(),
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()],
+ Result :: term().
+
+call(N, M, F, A) ->
+ call(N, M, F, A, infinity).
+
+-dialyzer([{nowarn_function, call/5}, no_return]).
+
+-spec call(Node, Module, Function, Args, Timeout) -> Result when
+ Node :: node(),
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()],
+ Timeout :: ?TIMEOUT_TYPE,
+ Result :: term().
+
+call(N, M, F, A, infinity) when node() =:= N, %% Optimize local call
+ is_atom(M),
+ is_atom(F),
+ is_list(A) ->
+ try
+ {return, Return} = execute_call(M,F,A),
+ Return
+ catch
+ exit:Reason ->
+ exit({exception, Reason});
+ error:Reason:Stack ->
+ case is_arg_error(Reason, M, F, A) of
+ true ->
+ error({?MODULE, Reason});
+ false ->
+ ErpcStack = trim_stack(Stack, M, F, A),
+ error({exception, Reason, ErpcStack})
+ end
+ end;
+call(N, M, F, A, T) when is_atom(N),
+ is_atom(M),
+ is_atom(F),
+ is_list(A),
+ ?IS_VALID_TMO(T) ->
+ Res = make_ref(),
+ ReqId = spawn_request(N, ?MODULE, execute_call, [Res, M, F, A],
+ [{reply, error_only}, monitor]),
+ receive
+ {spawn_reply, ReqId, error, Reason} ->
+ result(spawn_reply, ReqId, Res, Reason);
+ {'DOWN', ReqId, process, _Pid, Reason} ->
+ result(down, ReqId, Res, Reason)
+ after T ->
+ result(timeout, ReqId, Res, undefined)
+ end;
+call(_N, _M, _F, _A, _T) ->
+ error({?MODULE, badarg}).
+
+%% Asynchronous call
+
+-opaque request_id() :: {reference(), reference()}.
+
+-spec send_request(Node, Fun) -> RequestId when
+ Node :: node(),
+ Fun :: function(),
+ RequestId :: request_id().
+
+send_request(N, F) when is_function(F, 0) ->
+ send_request(N, erlang, apply, [F, []]);
+send_request(_N, _F) ->
+ error({?MODULE, badarg}).
+
+-spec send_request(Node, Module, Function, Args) -> RequestId when
+ Node :: node(),
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()],
+ RequestId :: request_id().
+
+send_request(N, M, F, A) when is_atom(N),
+ is_atom(M),
+ is_atom(F),
+ is_list(A) ->
+ Res = make_ref(),
+ ReqId = spawn_request(N, ?MODULE, execute_call, [Res, M, F, A],
+ [{reply, error_only}, monitor]),
+ {Res, ReqId};
+send_request(_N, _M, _F, _A) ->
+ error({?MODULE, badarg}).
+
+-spec receive_response(RequestId) -> Result when
+ RequestId :: request_id(),
+ Result :: term().
+
+receive_response({Res, ReqId} = RId) when is_reference(Res),
+ is_reference(ReqId) ->
+ receive_response(RId, infinity);
+receive_response(_) ->
+ error({?MODULE, badarg}).
+
+-dialyzer([{nowarn_function, receive_response/2}, no_return]).
+
+-spec receive_response(RequestId, Timeout) -> Result when
+ RequestId :: request_id(),
+ Timeout :: ?TIMEOUT_TYPE,
+ Result :: term().
+
+receive_response({Res, ReqId}, Tmo) when is_reference(Res),
+ is_reference(ReqId),
+ ?IS_VALID_TMO(Tmo) ->
+ receive
+ {spawn_reply, ReqId, error, Reason} ->
+ result(spawn_reply, ReqId, Res, Reason);
+ {'DOWN', ReqId, process, _Pid, Reason} ->
+ result(down, ReqId, Res, Reason)
+ after Tmo ->
+ result(timeout, ReqId, Res, undefined)
+ end;
+receive_response(_, _) ->
+ error({?MODULE, badarg}).
+
+-spec wait_response(RequestId) -> {'response', Result} | 'no_response' when
+ RequestId :: request_id(),
+ Result :: term().
+
+wait_response({Res, ReqId} = RId) when is_reference(Res),
+ is_reference(ReqId) ->
+ wait_response(RId, 0).
+
+-dialyzer([{nowarn_function, wait_response/2}, no_return]).
+
+-spec wait_response(RequestId, WaitTime) ->
+ {'response', Result} | 'no_response' when
+ RequestId :: request_id(),
+ WaitTime :: ?TIMEOUT_TYPE,
+ Result :: term().
+
+wait_response({Res, ReqId}, WT) when is_reference(Res),
+ is_reference(ReqId),
+ ?IS_VALID_TMO(WT) ->
+ receive
+ {spawn_reply, ReqId, error, Reason} ->
+ result(spawn_reply, ReqId, Res, Reason);
+ {'DOWN', ReqId, process, _Pid, Reason} ->
+ {response, result(down, ReqId, Res, Reason)}
+ after WT ->
+ no_response
+ end;
+wait_response(_, _) ->
+ error({?MODULE, badarg}).
+
+-dialyzer([{nowarn_function, check_response/2}, no_return]).
+
+-spec check_response(Message, RequestId) ->
+ {'response', Result} | 'no_response' when
+ Message :: term(),
+ RequestId :: request_id(),
+ Result :: term().
+
+check_response({spawn_reply, ReqId, error, Reason},
+ {Res, ReqId}) when is_reference(Res),
+ is_reference(ReqId) ->
+ result(spawn_reply, ReqId, Res, Reason);
+check_response({'DOWN', ReqId, process, _Pid, Reason},
+ {Res, ReqId}) when is_reference(Res),
+ is_reference(ReqId) ->
+ {response, result(down, ReqId, Res, Reason)};
+check_response(_Msg, {Res, ReqId}) when is_reference(Res),
+ is_reference(ReqId) ->
+ no_response;
+check_response(_, _) ->
+ error({?MODULE, badarg}).
+
+-type stack_item() ::
+ {Module :: atom(),
+ Function :: atom(),
+ Arity :: arity() | (Args :: [term()]),
+ Location :: [{file, Filename :: string()} |
+ {line, Line :: pos_integer()}]}.
+
+-type caught_call_exception() ::
+ {throw, Throw :: term()}
+ | {exit, {exception, Reason :: term()}}
+ | {error, {exception, Reason :: term(), StackTrace :: [stack_item()]}}
+ | {exit, {signal, Reason :: term()}}
+ | {error, {?MODULE, Reason :: term()}}.
+
+
+-spec multicall(Nodes, Fun) -> Result when
+ Nodes :: [atom()],
+ Fun :: function(),
+ Result :: term().
+
+multicall(Ns, Fun) ->
+ multicall(Ns, Fun, infinity).
+
+-spec multicall(Nodes, Fun, Timeout) -> Result when
+ Nodes :: [atom()],
+ Fun :: function(),
+ Timeout :: ?TIMEOUT_TYPE,
+ Result :: term().
+
+multicall(Ns, Fun, Timeout) when is_function(Fun, 0) ->
+ multicall(Ns, erlang, apply, [Fun, []], Timeout);
+multicall(_Ns, _Fun, _Timeout) ->
+ error({?MODULE, badarg}).
+
+-spec multicall(Nodes, Module, Function, Args) -> Result when
+ Nodes :: [atom()],
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()],
+ Result :: [{ok, ReturnValue :: term()} | caught_call_exception()].
+
+multicall(Ns, M, F, A) ->
+ multicall(Ns, M, F, A, infinity).
+
+-spec multicall(Nodes, Module, Function, Args, Timeout) -> Result when
+ Nodes :: [atom()],
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()],
+ Timeout :: ?TIMEOUT_TYPE,
+ Result :: [{ok, ReturnValue :: term()} | caught_call_exception()].
+
+multicall(Ns, M, F, A, T) ->
+ try
+ true = is_atom(M),
+ true = is_atom(F),
+ true = is_list(A),
+ Deadline = deadline(T),
+ {ReqIds, LC} = mcall_send_requests(Ns, M, F, A, [], T, false),
+ LRes = case LC of
+ false ->
+ undefined;
+ true ->
+ %% Timeout infinity and call on local node wanted;
+ %% excecute local call in this process...
+ try
+ {return, Return} = execute_call(M, F, A),
+ {ok, Return}
+ catch
+ throw:Thrown ->
+ {throw, Thrown};
+ exit:Reason ->
+ {exit, {exception, Reason}};
+ error:Reason:Stack ->
+ case is_arg_error(Reason, M, F, A) of
+ true ->
+ {error, {?MODULE, Reason}};
+ false ->
+ ErpcStack = trim_stack(Stack, M, F, A),
+ {error, {exception, Reason, ErpcStack}}
+ end
+ end
+ end,
+ mcall_receive_replies(ReqIds, [], LRes, Deadline)
+ catch
+ error:NotIErr when NotIErr /= internal_error ->
+ error({?MODULE, badarg})
+ end.
+
+-spec multicast(Nodes, Fun) -> 'ok' when
+ Nodes :: [node()],
+ Fun :: function().
+
+multicast(N, Fun) ->
+ multicast(N, erlang, apply, [Fun, []]).
+
+-spec multicast(Nodes, Module, Function, Args) -> 'ok' when
+ Nodes :: [node()],
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()].
+
+multicast(Nodes, Mod, Fun, Args) ->
+ try
+ true = is_atom(Mod),
+ true = is_atom(Fun),
+ true = is_list(Args),
+ multicast_send_requests(Nodes, Mod, Fun, Args)
+ catch
+ error:_ ->
+ error({?MODULE, badarg})
+ end.
+
+multicast_send_requests([], _Mod, _Fun, _Args) ->
+ ok;
+multicast_send_requests([Node|Nodes], Mod, Fun, Args) ->
+ _ = spawn_request(Node, ?MODULE, execute_cast, [Mod, Fun, Args],
+ [{reply, no}]),
+ multicast_send_requests(Nodes, Mod, Fun, Args).
+
+-spec cast(Node, Fun) -> 'ok' when
+ Node :: node(),
+ Fun :: function().
+
+cast(N, Fun) ->
+ cast(N, erlang, apply, [Fun, []]).
+
+-spec cast(Node, Module, Function, Args) -> 'ok' when
+ Node :: node(),
+ Module :: atom(),
+ Function :: atom(),
+ Args :: [term()].
+
+cast(Node, Mod, Fun, Args) when is_atom(Node),
+ is_atom(Mod),
+ is_atom(Fun),
+ is_list(Args) ->
+ _ = spawn_request(Node, ?MODULE, execute_cast, [Mod, Fun, Args],
+ [{reply, no}]),
+ ok;
+cast(_Node, _Mod, _Fun, _Args) ->
+ error({?MODULE, badarg}).
+
+%%------------------------------------------------------------------------
+%% Exported internals
+%%------------------------------------------------------------------------
+
+%% Note that most of these are used by 'rpc' as well...
+
+execute_call(Ref, M, F, A) ->
+ Reply = try
+ {Ref, return, apply(M, F, A)}
+ catch
+ throw:Reason ->
+ {Ref, throw, Reason};
+ exit:Reason ->
+ {Ref, exit, Reason};
+ error:Reason:Stack ->
+ case is_arg_error(Reason, M, F, A) of
+ true ->
+ {Ref, error, {?MODULE, Reason}};
+ false ->
+ ErpcStack = trim_stack(Stack, M, F, A),
+ {Ref, error, Reason, ErpcStack}
+ end
+ end,
+ exit(Reply).
+
+execute_call(M,F,A) ->
+ {return, apply(M, F, A)}.
+
+execute_cast(M, F, A) ->
+ try
+ apply(M, F, A)
+ catch
+ error:Reason:Stack ->
+ %% Produce error reports with error
+ %% exceptions produced for calls...
+ case is_arg_error(Reason, M, F, A) of
+ true ->
+ error({?MODULE, Reason});
+ false ->
+ ErpcStack = trim_stack(Stack, M, F, A),
+ error({exception, {Reason, ErpcStack}})
+ end
+ end.
+
+call_result(Type, ReqId, Res, Reason) ->
+ result(Type, ReqId, Res, Reason).
+
+is_arg_error(system_limit, _M, _F, A) ->
+ try
+ apply(?MODULE, nonexisting, A),
+ false
+ catch
+ error:system_limit -> true;
+ _:_ -> false
+ end;
+is_arg_error(_R, _M, _F, _A) ->
+ false.
+
+-define(IS_CUT_FRAME(F),
+ ((element(1, (F)) == ?MODULE)
+ andalso ((element(2, (F)) == execute_call)
+ orelse ((element(2, (F)) == execute_cast))))).
+
+trim_stack([CF | _], M, F, A) when ?IS_CUT_FRAME(CF) ->
+ [{M, F, A, []}];
+trim_stack([{M, F, A, _} = SF, CF | _], M, F, A) when ?IS_CUT_FRAME(CF) ->
+ [SF];
+trim_stack(S, M, F, A) ->
+ try
+ trim_stack_aux(S, M, F, A)
+ catch
+ throw:use_all -> S
+ end.
+
+%%------------------------------------------------------------------------
+%% Internals
+%%------------------------------------------------------------------------
+
+trim_stack_aux([], _M, _F, _A) ->
+ throw(use_all);
+trim_stack_aux([{M, F, AL, _} = SF, CF | _], M, F, A) when ?IS_CUT_FRAME(CF),
+ AL == length(A) ->
+ [SF];
+trim_stack_aux([CF | _], M, F, A) when ?IS_CUT_FRAME(CF) ->
+ try
+ [{M, F, length(A), []}]
+ catch
+ _:_ ->
+ []
+ end;
+trim_stack_aux([SF|SFs], M, F, A) ->
+ [SF|trim_stack_aux(SFs, M, F, A)].
+
+call_abandon(ReqId) ->
+ case spawn_request_abandon(ReqId) of
+ true -> true;
+ false -> erlang:demonitor(ReqId, [info])
+ end.
+
+-dialyzer([{nowarn_function, result/4}, no_return]).
+
+-spec result('down', ReqId, Res, Reason) -> term() when
+ ReqId :: reference(),
+ Res :: reference(),
+ Reason :: term();
+ ('spawn_reply', ReqId, Res, Reason) -> no_return() when
+ ReqId :: reference(),
+ Res :: reference(),
+ Reason :: term();
+ ('timeout', ReqId, Res, Reason) -> term() when
+ ReqId :: reference(),
+ Res :: reference(),
+ Reason :: term().
+
+result(down, _ReqId, Res, {Res, return, Return}) ->
+ Return;
+result(down, _ReqId, Res, {Res, throw, Throw}) ->
+ throw(Throw);
+result(down, _ReqId, Res, {Res, exit, Exit}) ->
+ exit({exception, Exit});
+result(down, _ReqId, Res, {Res, error, Error, Stack}) ->
+ error({exception, Error, Stack});
+result(down, _ReqId, Res, {Res, error, {?MODULE, _} = ErpcErr}) ->
+ error(ErpcErr);
+result(down, _ReqId, _Res, noconnection) ->
+ error({?MODULE, noconnection});
+result(down, _ReqId, _Res, Reason) ->
+ exit({signal, Reason});
+result(spawn_reply, _ReqId, _Res, Reason) ->
+ error({?MODULE, Reason});
+result(timeout, ReqId, Res, _Reason) ->
+ case call_abandon(ReqId) of
+ true ->
+ error({?MODULE, timeout});
+ false ->
+ %% Spawn error or DOWN has arrived. Return
+ %% a result instead of a timeout since we
+ %% just got the result...
+ receive
+ {spawn_reply, ReqId, error, Reason} ->
+ result(spawn_reply, ReqId, Res, Reason);
+ {'DOWN', ReqId, process, _Pid, Reason} ->
+ result(down, ReqId, Res, Reason)
+ after
+ 0 ->
+ %% Invalid request id...
+ error({?MODULE, badarg})
+ end
+ end.
+
+deadline(infinity) ->
+ infinity;
+deadline(?MAX_INT_TIMEOUT) ->
+ erlang:convert_time_unit(erlang:monotonic_time(millisecond)
+ + ?MAX_INT_TIMEOUT,
+ millisecond,
+ native);
+deadline(T) when ?IS_VALID_TMO_INT(T) ->
+ Now = erlang:monotonic_time(),
+ NativeTmo = erlang:convert_time_unit(T, millisecond, native),
+ Now + NativeTmo.
+
+time_left(infinity) ->
+ infinity;
+time_left(Deadline) ->
+ case Deadline - erlang:monotonic_time() of
+ TimeLeft when TimeLeft =< 0 ->
+ 0;
+ TimeLeft ->
+ erlang:convert_time_unit(TimeLeft-1, native, millisecond) + 1
+ end.
+
+mcall_send_requests([], _M, _F, _A, RIDs, _T, LC) ->
+ {RIDs, LC};
+mcall_send_requests([N|Ns], M, F, A, RIDs,
+ infinity, false) when N == node() ->
+ mcall_send_requests(Ns, M, F, A, [local_call|RIDs], infinity, true);
+mcall_send_requests([N|Ns], M, F, A, RIDs, T, LC) ->
+ RID = try
+ send_request(N, M, F, A)
+ catch
+ _:_ ->
+ %% Bad arguments... Abandon
+ %% requests we've already sent
+ %% and then fail...
+ mcall_failure_abandon(RIDs)
+ end,
+ mcall_send_requests(Ns, M, F, A, [RID|RIDs], T, LC);
+mcall_send_requests(_, _, _, _, RIDs, _T, _LC) ->
+ %% Bad nodes list... Abandon requests we've
+ %% already sent and then fail...
+ mcall_failure_abandon(RIDs).
+
+mcall_failure_abandon([]) ->
+ error(badarg);
+mcall_failure_abandon([local_call|RIDs]) ->
+ mcall_failure_abandon(RIDs);
+mcall_failure_abandon([RID|RIDs]) ->
+ try
+ _ = receive_response(RID, 0),
+ ok
+ catch
+ _:_ ->
+ ok
+ end,
+ mcall_failure_abandon(RIDs).
+
+mcall_receive_replies([], Replies, undefined, _Deadline) ->
+ Replies;
+mcall_receive_replies([local_call|RIDs], Replies, LRes, Deadline) ->
+ mcall_receive_replies(RIDs, [LRes|Replies], undefined, Deadline);
+mcall_receive_replies([RID|RIDs], Replies, LRes, Deadline) ->
+ Reply = try
+ {ok, receive_response(RID, time_left(Deadline))}
+ catch
+ Class:Reason ->
+ {Class, Reason}
+ end,
+ mcall_receive_replies(RIDs, [Reply|Replies], LRes, Deadline).
diff --git a/lib/kernel/src/error_handler.erl b/lib/kernel/src/error_handler.erl
index a89ef83261..a1ea460a45 100644
--- a/lib/kernel/src/error_handler.erl
+++ b/lib/kernel/src/error_handler.erl
@@ -24,6 +24,10 @@
%% "error_handler: add no_native compiler directive"
-compile(no_native).
+%% See the comment before the int/0 function for an explanation
+%% why this option is needed.
+-compile(no_module_opt).
+
%% Callbacks called from the run-time system.
-export([undefined_function/3,undefined_lambda/3,breakpoint/3]).
@@ -84,7 +88,10 @@ raise_undef_exception(Module, Func, Args) ->
crash({Module,Func,Args,[]}).
%% Used to make the call to the 'int' module a "weak" one, to avoid
-%% building strong components in xref or dialyzer.
+%% making Kernel a visible dependency to Debugger in xref. (To ensure
+%% that the call in breakpoint/3 is kept as an apply to an unknown
+%% module, this module must be compiled with the 'no_module_opt'
+%% option to turn off inter-function type analysis.)
int() -> int.
diff --git a/lib/kernel/src/error_logger.erl b/lib/kernel/src/error_logger.erl
index e324be5290..37771f4e60 100644
--- a/lib/kernel/src/error_logger.erl
+++ b/lib/kernel/src/error_logger.erl
@@ -147,15 +147,15 @@ do_log(Level,{report,Msg},#{?MODULE:=#{tag:=Tag}}=Meta) ->
_ ->
%% From logger call which added error_logger data to
%% obtain backwards compatibility with error_logger:*_msg/1,2
- case maps:get(report_cb,Meta,fun logger:format_report/1) of
+ case get_report_cb(Meta) of
RCBFun when is_function(RCBFun,1) ->
try RCBFun(Msg) of
{F,A} when is_list(F), is_list(A) ->
{F,A};
Other ->
{"REPORT_CB ERROR: ~tp; Returned: ~tp",[Msg,Other]}
- catch C:R ->
- {"REPORT_CB CRASH: ~tp; Reason: ~tp",[Msg,{C,R}]}
+ catch C:R:S ->
+ {"REPORT_CB CRASH: ~tp; Reason: ~tp",[Msg,{C,R,S}]}
end;
RCBFun when is_function(RCBFun,2) ->
try RCBFun(Msg,#{depth=>get_format_depth(),
@@ -216,6 +216,13 @@ fix_warning_type(error,std_warning) -> std_error;
fix_warning_type(info,std_warning) -> std_info;
fix_warning_type(_,Type) -> Type.
+get_report_cb(#{?MODULE:=#{report_cb:=RBFun}}) ->
+ RBFun;
+get_report_cb(#{report_cb:=RBFun}) ->
+ RBFun;
+get_report_cb(_) ->
+ fun logger:format_report/1.
+
%%-----------------------------------------------------------------
%% These two simple old functions generate events tagged 'error'
%% Used for simple messages; error or information.
diff --git a/lib/kernel/src/erts_debug.erl b/lib/kernel/src/erts_debug.erl
index a69e74a3c1..9a03dd8d75 100644
--- a/lib/kernel/src/erts_debug.erl
+++ b/lib/kernel/src/erts_debug.erl
@@ -40,6 +40,15 @@
lc_graph/0, lc_graph_to_dot/2, lc_graph_merge/2,
alloc_blocks_size/1]).
+%% Reroutes calls to the given MFA to error_handler:breakpoint/3
+%%
+%% Note that this is potentially unsafe as compiled code may assume that the
+%% targeted function returns a specific type, triggering undefined behavior if
+%% this function were to return something else.
+%%
+%% For reference, the debugger avoids the issue by purging the affected module
+%% and interpreting all functions in the module, ensuring that no assumptions
+%% are made with regard to return or argument types.
-spec breakpoint(MFA, Flag) -> non_neg_integer() when
MFA :: {Module :: module(),
Function :: atom(),
@@ -92,7 +101,7 @@ copy_shared(_) ->
-spec get_internal_state(W) -> term() when
W :: reds_left | node_and_dist_references | monitoring_nodes
- | next_pid | 'DbTable_words' | check_io_debug
+ | next_pid | 'DbTable_words' | check_io_debug | lc_graph
| process_info_args | processes | processes_bif_info
| max_atom_out_cache_index | nbalance | available_internal_state
| force_heap_frags | memory
@@ -517,43 +526,38 @@ alloc_blocks_size_1([], _Type, 0) ->
undefined;
alloc_blocks_size_1([{_Type, false} | Rest], Type, Acc) ->
alloc_blocks_size_1(Rest, Type, Acc);
-alloc_blocks_size_1([{Type, Instances} | Rest], Type, Acc0) ->
- F = fun ({instance, _, L}, Acc) ->
+alloc_blocks_size_1([{_Type, Instances} | Rest], Type, Acc) ->
+ F = fun ({instance, _, L}, Acc0) ->
MBCSPool = case lists:keyfind(mbcs_pool, 1, L) of
{_, Pool} -> Pool;
false -> []
end,
{_,MBCS} = lists:keyfind(mbcs, 1, L),
{_,SBCS} = lists:keyfind(sbcs, 1, L),
- Acc +
- sum_block_sizes(MBCSPool) +
- sum_block_sizes(MBCS) +
- sum_block_sizes(SBCS)
+ Acc1 = sum_block_sizes(MBCSPool, Type, Acc0),
+ Acc2 = sum_block_sizes(MBCS, Type, Acc1),
+ sum_block_sizes(SBCS, Type, Acc2)
end,
- alloc_blocks_size_1(Rest, Type, lists:foldl(F, Acc0, Instances));
-alloc_blocks_size_1([{_Type, Instances} | Rest], Type, Acc0) ->
- F = fun ({instance, _, L}, Acc) ->
- Acc + sum_foreign_sizes(Type, L)
- end,
- alloc_blocks_size_1(Rest, Type, lists:foldl(F, Acc0, Instances));
+ alloc_blocks_size_1(Rest, Type, lists:foldl(F, Acc, Instances));
alloc_blocks_size_1([], _Type, Acc) ->
Acc.
-sum_foreign_sizes(Type, L) ->
- case lists:keyfind(mbcs_pool, 1, L) of
- {_,Pool} ->
- {_,ForeignBlocks} = lists:keyfind(foreign_blocks, 1, Pool),
- case lists:keyfind(Type, 1, ForeignBlocks) of
- {_,TypeSizes} -> sum_block_sizes(TypeSizes);
- false -> 0
- end;
- _ ->
- 0
- end.
+sum_block_sizes([{blocks, List} | Rest], Type, Acc) ->
+ sum_block_sizes(Rest, Type, sum_block_sizes_1(List, Type, Acc));
+sum_block_sizes([_ | Rest], Type, Acc) ->
+ sum_block_sizes(Rest, Type, Acc);
+sum_block_sizes([], _Type, Acc) ->
+ Acc.
+
+sum_block_sizes_1([{Type, L} | Rest], Type, Acc0) ->
+ Acc = lists:foldl(fun({size, Sz,_,_}, Sz0) -> Sz0+Sz;
+ ({size, Sz}, Sz0) -> Sz0+Sz;
+ (_, Sz) -> Sz
+ end, Acc0, L),
+ sum_block_sizes_1(Rest, Type, Acc);
+sum_block_sizes_1([_ | Rest], Type, Acc) ->
+ sum_block_sizes_1(Rest, Type, Acc);
+sum_block_sizes_1([], _Type, Acc) ->
+ Acc.
+
-sum_block_sizes(Blocks) ->
- lists:foldl(
- fun({blocks_size, Sz,_,_}, Sz0) -> Sz0+Sz;
- ({blocks_size, Sz}, Sz0) -> Sz0+Sz;
- (_, Sz) -> Sz
- end, 0, Blocks).
diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl
index 1d4e37196c..b34e9272b1 100644
--- a/lib/kernel/src/file.erl
+++ b/lib/kernel/src/file.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
-export([format_error/1]).
%% File system and metadata.
-export([get_cwd/0, get_cwd/1, set_cwd/1, delete/1, rename/2,
- make_dir/1, del_dir/1, list_dir/1, list_dir_all/1,
+ make_dir/1, del_dir/1, del_dir_r/1, list_dir/1, list_dir_all/1,
read_file_info/1, read_file_info/2,
write_file_info/2, write_file_info/3,
altname/1,
@@ -239,20 +239,49 @@ make_dir(Name) ->
del_dir(Name) ->
check_and_call(del_dir, [file_name(Name)]).
--spec read_file_info(Filename) -> {ok, FileInfo} | {error, Reason} when
- Filename :: name_all(),
+-spec del_dir_r(File) -> ok | {error, Reason} when
+ File :: name_all(),
+ Reason :: posix() | badarg.
+
+del_dir_r(File) -> % rm -rf File
+ case read_link_info(File) of
+ {ok, #file_info{type = directory}} ->
+ case list_dir_all(File) of
+ {ok, Names} ->
+ lists:foreach(fun(Name) ->
+ del_dir_r(filename:join(File, Name))
+ end, Names);
+ {error, _Reason} -> ok
+ end,
+ del_dir(File);
+ {ok, _FileInfo} -> delete(File);
+ {error, _Reason} = Error -> Error
+ end.
+
+-spec read_file_info(File) -> {ok, FileInfo} | {error, Reason} when
+ File :: name_all() | io_device(),
FileInfo :: file_info(),
Reason :: posix() | badarg.
+read_file_info(IoDevice)
+ when is_pid(IoDevice); is_record(IoDevice, file_descriptor) ->
+ read_file_info(IoDevice, []);
+
read_file_info(Name) ->
check_and_call(read_file_info, [file_name(Name)]).
--spec read_file_info(Filename, Opts) -> {ok, FileInfo} | {error, Reason} when
- Filename :: name_all(),
+-spec read_file_info(File, Opts) -> {ok, FileInfo} | {error, Reason} when
+ File :: name_all() | io_device(),
Opts :: [file_info_option()],
FileInfo :: file_info(),
Reason :: posix() | badarg.
+read_file_info(IoDevice, Opts) when is_pid(IoDevice), is_list(Opts) ->
+ file_request(IoDevice, {read_handle_info, Opts});
+
+read_file_info(#file_descriptor{module = Module} = Handle, Opts) when is_list(Opts) ->
+ Module:read_handle_info(Handle, Opts);
+
read_file_info(Name, Opts) when is_list(Opts) ->
Args = [file_name(Name), Opts],
case check_args(Args) of
@@ -460,7 +489,7 @@ raw_write_file_info(Name, #file_info{} = Info) ->
-spec open(File, Modes) -> {ok, IoDevice} | {error, Reason} when
File :: Filename | iodata(),
Filename :: name_all(),
- Modes :: [mode() | ram],
+ Modes :: [mode() | ram | directory],
IoDevice :: io_device(),
Reason :: posix() | badarg | system_limit.
@@ -545,7 +574,7 @@ allocate(#file_descriptor{module = Module} = Handle, Offset, Length) ->
| {no_translation, unicode, latin1}.
read(File, Sz) when (is_pid(File) orelse is_atom(File)), is_integer(Sz), Sz >= 0 ->
- case io:request(File, {get_chars, '', Sz}) of
+ case io:request(File, {get_chars, latin1, '', Sz}) of
Data when is_list(Data); is_binary(Data) ->
{ok, Data};
Other ->
@@ -566,7 +595,7 @@ read(_, _) ->
| {no_translation, unicode, latin1}.
read_line(File) when (is_pid(File) orelse is_atom(File)) ->
- case io:request(File, {get_line, ''}) of
+ case io:request(File, {get_line, latin1, ''}) of
Data when is_list(Data); is_binary(Data) ->
{ok, Data};
Other ->
@@ -1143,7 +1172,7 @@ path_script(Path, File, Bs) ->
{ok, IoDevice, FullName} | {error, Reason} when
Path :: [Dir :: name_all()],
Filename :: name_all(),
- Modes :: [mode()],
+ Modes :: [mode() | directory],
IoDevice :: io_device(),
FullName :: filename_all(),
Reason :: posix() | badarg | system_limit.
@@ -1279,14 +1308,23 @@ sendfile(Filename, Sock) ->
%% Internal sendfile functions
sendfile(#file_descriptor{ module = Mod } = Fd, Sock, Offset, Bytes,
ChunkSize, Headers, Trailers, Opts)
- when is_port(Sock) ->
- case Mod:sendfile(Fd, Sock, Offset, Bytes, ChunkSize, Headers, Trailers,
- Opts) of
- {error, enotsup} ->
- sendfile_fallback(Fd, Sock, Offset, Bytes, ChunkSize,
- Headers, Trailers);
- Else ->
- Else
+ when is_integer(Offset), is_integer(Bytes) ->
+ case Sock of
+ {'$inet', _, _} ->
+ sendfile_fallback(
+ Fd, Sock, Offset, Bytes, ChunkSize,
+ Headers, Trailers);
+ _ when is_port(Sock) ->
+ case Mod:sendfile(
+ Fd, Sock, Offset, Bytes, ChunkSize,
+ Headers, Trailers, Opts) of
+ {error, enotsup} ->
+ sendfile_fallback(
+ Fd, Sock, Offset, Bytes, ChunkSize,
+ Headers, Trailers);
+ Else ->
+ Else
+ end
end;
sendfile(_,_,_,_,_,_,_,_) ->
{error, badarg}.
@@ -1319,12 +1357,19 @@ sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, Headers, Trailers) ->
end.
-sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize) ->
+sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize)
+ when 0 =< Bytes ->
{ok, CurrPos} = file:position(File, {cur, 0}),
- {ok, _NewPos} = file:position(File, {bof, Offset}),
- Res = sendfile_fallback_int(File, Sock, Bytes, ChunkSize, 0),
- _ = file:position(File, {bof, CurrPos}),
- Res.
+ case file:position(File, {bof, Offset}) of
+ {ok, _NewPos} ->
+ Res = sendfile_fallback_int(File, Sock, Bytes, ChunkSize, 0),
+ _ = file:position(File, {bof, CurrPos}),
+ Res;
+ Error ->
+ Error
+ end;
+sendfile_fallback(_, _, _, _, _) ->
+ {error, einval}.
sendfile_fallback_int(File, Sock, Bytes, ChunkSize, BytesSent)
diff --git a/lib/kernel/src/file_io_server.erl b/lib/kernel/src/file_io_server.erl
index 34d5497a4a..c03fbb548a 100644
--- a/lib/kernel/src/file_io_server.erl
+++ b/lib/kernel/src/file_io_server.erl
@@ -314,6 +314,14 @@ file_request(truncate,
Reply ->
std_reply(Reply, State)
end;
+file_request({read_handle_info, Opts},
+ #state{handle=Handle}=State) ->
+ case ?CALL_FD(Handle, read_handle_info, [Opts]) of
+ {error,Reason}=Reply ->
+ {stop,Reason,Reply,State};
+ Reply ->
+ {reply,Reply,State}
+ end;
file_request(Unknown,
#state{}=State) ->
Reason = {request, Unknown},
diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl
index 27b335bedc..b44144d88a 100644
--- a/lib/kernel/src/gen_tcp.erl
+++ b/lib/kernel/src/gen_tcp.erl
@@ -130,7 +130,7 @@
{netns, file:filename_all()} |
{bind_to_device, binary()} |
option().
--type socket() :: port().
+-type socket() :: inet:socket().
-export_type([option/0, option_name/0, connect_option/0, listen_option/0,
socket/0, pktoptions_value/0]).
@@ -158,15 +158,20 @@ connect(Address, Port, Opts) ->
Socket :: socket(),
Reason :: timeout | inet:posix().
-connect(Address, Port, Opts, Time) ->
- Timer = inet:start_timer(Time),
- Res = (catch connect1(Address,Port,Opts,Timer)),
- _ = inet:stop_timer(Timer),
- case Res of
- {ok,S} -> {ok,S};
- {error, einval} -> exit(badarg);
- {'EXIT',Reason} -> exit(Reason);
- Error -> Error
+connect(Address, Port, Opts0, Time) ->
+ case inet:gen_tcp_module(Opts0) of
+ {?MODULE, Opts} ->
+ Timer = inet:start_timer(Time),
+ Res = (catch connect1(Address,Port,Opts,Timer)),
+ _ = inet:stop_timer(Timer),
+ case Res of
+ {ok,S} -> {ok,S};
+ {error, einval} -> exit(badarg);
+ {'EXIT',Reason} -> exit(Reason);
+ Error -> Error
+ end;
+ {GenTcpMod, Opts} ->
+ GenTcpMod:connect(Address, Port, Opts, Time)
end.
connect1(Address, Port, Opts0, Timer) ->
@@ -204,13 +209,18 @@ try_connect([], _Port, _Opts, _Timer, _Mod, Err) ->
Reason :: system_limit | inet:posix().
listen(Port, Opts0) ->
- {Mod, Opts} = inet:tcp_module(Opts0),
- case Mod:getserv(Port) of
- {ok,TP} ->
- Mod:listen(TP, Opts);
- {error,einval} ->
- exit(badarg);
- Other -> Other
+ case inet:gen_tcp_module(Opts0) of
+ {?MODULE, Opts1} ->
+ {Mod, Opts} = inet:tcp_module(Opts1),
+ case Mod:getserv(Port) of
+ {ok,TP} ->
+ Mod:listen(TP, Opts);
+ {error,einval} ->
+ exit(badarg);
+ Other -> Other
+ end;
+ {GenTcpMod, Opts} ->
+ GenTcpMod:listen(Port, Opts)
end.
%%
@@ -222,7 +232,9 @@ listen(Port, Opts0) ->
Socket :: socket(),
Reason :: closed | system_limit | inet:posix().
-accept(S) ->
+accept({'$inet', GenTcpMod, _} = S) when is_atom(GenTcpMod) ->
+ GenTcpMod:?FUNCTION_NAME(S, infinity);
+accept(S) when is_port(S) ->
case inet_db:lookup_socket(S) of
{ok, Mod} ->
Mod:accept(S);
@@ -236,6 +248,8 @@ accept(S) ->
Socket :: socket(),
Reason :: closed | timeout | system_limit | inet:posix().
+accept({'$inet', GenTcpMod, _} = S, Time) when is_atom(GenTcpMod) ->
+ GenTcpMod:?FUNCTION_NAME(S, Time);
accept(S, Time) when is_port(S) ->
case inet_db:lookup_socket(S) of
{ok, Mod} ->
@@ -253,6 +267,8 @@ accept(S, Time) when is_port(S) ->
How :: read | write | read_write,
Reason :: inet:posix().
+shutdown({'$inet', GenTcpMod, _} = S, How) when is_atom(GenTcpMod) ->
+ GenTcpMod:?FUNCTION_NAME(S, How);
shutdown(S, How) when is_port(S) ->
case inet_db:lookup_socket(S) of
{ok, Mod} ->
@@ -268,6 +284,8 @@ shutdown(S, How) when is_port(S) ->
-spec close(Socket) -> ok when
Socket :: socket().
+close({'$inet', GenTcpMod, _} = S) when is_atom(GenTcpMod) ->
+ GenTcpMod:?FUNCTION_NAME(S);
close(S) ->
inet:tcp_close(S).
@@ -280,6 +298,8 @@ close(S) ->
Packet :: iodata(),
Reason :: closed | inet:posix().
+send({'$inet', GenTcpMod, _} = S, Packet) when is_atom(GenTcpMod) ->
+ GenTcpMod:?FUNCTION_NAME(S, Packet);
send(S, Packet) when is_port(S) ->
case inet_db:lookup_socket(S) of
{ok, Mod} ->
@@ -299,6 +319,8 @@ send(S, Packet) when is_port(S) ->
Reason :: closed | inet:posix(),
HttpPacket :: term().
+recv({'$inet', GenTcpMod, _} = S, Length) when is_atom(GenTcpMod) ->
+ GenTcpMod:?FUNCTION_NAME(S, Length, infinity);
recv(S, Length) when is_port(S) ->
case inet_db:lookup_socket(S) of
{ok, Mod} ->
@@ -315,6 +337,8 @@ recv(S, Length) when is_port(S) ->
Reason :: closed | timeout | inet:posix(),
HttpPacket :: term().
+recv({'$inet', GenTcpMod, _} = S, Length, Time) when is_atom(GenTcpMod) ->
+ GenTcpMod:?FUNCTION_NAME(S, Length, Time);
recv(S, Length, Time) when is_port(S) ->
case inet_db:lookup_socket(S) of
{ok, Mod} ->
@@ -323,6 +347,8 @@ recv(S, Length, Time) when is_port(S) ->
Error
end.
+unrecv({'$inet', GenTcpMod, _} = S, Data) when is_atom(GenTcpMod) ->
+ GenTcpMod:?FUNCTION_NAME(S, Data);
unrecv(S, Data) when is_port(S) ->
case inet_db:lookup_socket(S) of
{ok, Mod} ->
@@ -340,6 +366,9 @@ unrecv(S, Data) when is_port(S) ->
Pid :: pid(),
Reason :: closed | not_owner | badarg | inet:posix().
+controlling_process({'$inet', GenTcpMod, _} = S, NewOwner)
+ when is_atom(GenTcpMod) ->
+ GenTcpMod:?FUNCTION_NAME(S, NewOwner);
controlling_process(S, NewOwner) ->
case inet_db:lookup_socket(S) of
{ok, _Mod} -> % Just check that this is an open socket
@@ -354,5 +383,10 @@ controlling_process(S, NewOwner) ->
%% Create a port/socket from a file descriptor
%%
fdopen(Fd, Opts0) ->
- {Mod, Opts} = inet:tcp_module(Opts0),
- Mod:fdopen(Fd, Opts).
+ case inet:gen_tcp_module(Opts0) of
+ {?MODULE, Opts1} ->
+ {Mod, Opts} = inet:tcp_module(Opts1),
+ Mod:fdopen(Fd, Opts);
+ {GenTcpMod, Opts} ->
+ GenTcpMod:fdopen(Fd, Opts)
+ end.
diff --git a/lib/kernel/src/gen_tcp_socket.erl b/lib/kernel/src/gen_tcp_socket.erl
new file mode 100644
index 0000000000..4c832be12e
--- /dev/null
+++ b/lib/kernel/src/gen_tcp_socket.erl
@@ -0,0 +1,2135 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(gen_tcp_socket).
+-behaviour(gen_statem).
+
+%% gen_tcp
+-export([connect/4, listen/2, accept/2,
+ send/2, recv/3,
+ shutdown/2, close/1, controlling_process/2]).
+%% inet
+-export([setopts/2, getopts/2,
+ sockname/1, peername/1,
+ getstat/2]).
+
+-ifdef(undefined).
+-export([unrecv/2]).
+-export([fdopen/2]).
+-endif.
+
+%% gen_statem callbacks
+-export([init/1, callback_mode/0, terminate/3]).
+-export([handle_event/4]).
+
+-include("inet_int.hrl").
+
+-define(DBG(T), erlang:display({{self(), ?MODULE, ?LINE, ?FUNCTION_NAME}, T})).
+
+%% -------------------------------------------------------------------------
+
+%% Construct a "socket" as in this module's API
+-define(module_socket(Server, Socket),
+ {'$inet', ?MODULE, {Server, Socket}}).
+
+%% Standard length before data header for packet,1|2|4
+-define(header(Packet, Size),
+ (Size):(Packet)/unit:8-integer-big-unsigned).
+
+-define(badarg_exit(Error),
+ case begin Error end of
+ {error, badarg} -> exit(badarg);
+ OTHER__ -> OTHER__
+ end).
+
+-define(badarg_einval_exit(Error),
+ case begin Error end of
+ {error, badarg} -> exit(badarg);
+ {error, einval} -> exit(badarg);
+ OTHER__ -> OTHER__
+ end).
+
+-define(socket_abort(Socket, SelectRef, Reason),
+ {'$socket', (Socket), abort, {(SelectRef), (Reason)}}).
+-define(socket_select(Socket, SelectRef),
+ {'$socket', (Socket), select, (SelectRef)}).
+-define(socket_counter_wrap(Socket, Counter),
+ {'$socket', (Socket), counter_wrap, (Counter)}).
+-define(select_info(SelectRef),
+ {select_info, _, (SelectRef)}).
+
+%%% ========================================================================
+%%% API
+%%%
+
+connect(Address, Port, Opts, Timeout) ->
+ Timer = inet:start_timer(Timeout),
+ try
+ connect_lookup(Address, Port, Opts, Timer)
+ after
+ _ = inet:stop_timer(Timer)
+ end.
+
+%% Helpers -------
+
+connect_lookup(Address, Port, Opts, Timer) ->
+ %% ?DBG({Address, Port, Opts, Timer}),
+ {EinvalOpts, Opts_1} = setopts_split(einval, Opts),
+ EinvalOpts =:= [] orelse exit(badarg),
+ {Mod, Opts_2} = inet:tcp_module(Opts_1, Address),
+ Domain = domain(Mod),
+ {StartOpts, Opts_3} = setopts_split(start, Opts_2),
+ ErrRef = make_ref(),
+ try
+ IPs = val(ErrRef, Mod:getaddrs(Address, Timer)),
+ TP = val(ErrRef, Mod:getserv(Port)),
+ CO = val(ErrRef, inet:connect_options(Opts_3, Mod)),
+ {sockaddrs(IPs, TP, Domain), CO}
+ of
+ {Addrs,
+ #connect_opts{
+ fd = Fd,
+ ifaddr = BindIP,
+ port = BindPort,
+ opts = ConnectOpts}} ->
+ %%
+ %% ?DBG({Domain, BindIP}),
+ BindAddr =
+ case Domain of
+ local ->
+ {local, Path} = BindIP,
+ #{family => Domain,
+ path => Path};
+ _ ->
+ #{family => Domain,
+ addr => BindIP,
+ port => BindPort}
+ end,
+ connect_open(
+ Addrs, Domain, ConnectOpts, StartOpts, Fd, Timer, BindAddr)
+ catch
+ throw : {ErrRef, Reason} ->
+ ?badarg_exit({error, Reason})
+ end.
+
+connect_open(Addrs, Domain, ConnectOpts, Opts, Fd, Timer, BindAddr) ->
+ %% ?DBG({Addrs, Domain, ConnectOpts, Opts, Fd, Timer, BindAddr}),
+ %%
+ %% The {netns, File} option is passed in Fd by inet:connect_options/2.
+ %% The {debug, Bool} option is passed in Opts since it is
+ %% subversively classified as both start and socket option.
+ %%
+ ExtraOpts =
+ if
+ Fd =:= -1 -> [];
+ is_integer(Fd) -> [{fd, Fd}];
+ %% This is an **ugly** hack.
+ %% inet:connect_options/2 has the bad taste to use this
+ %% for [{netns,NS}] if that option is used...
+ is_list(Fd) -> Fd
+ end,
+ {SocketOpts, StartOpts} = setopts_split(socket, Opts),
+ case
+ start_server(
+ Domain, ExtraOpts,
+ [{timeout, inet:timeout(Timer)} | start_opts(StartOpts)])
+ of
+ {ok, Server} ->
+ {Setopts, _} =
+ setopts_split(
+ #{socket => [], server_read => [], server_write => []},
+ ConnectOpts),
+ ErrRef = make_ref(),
+ try
+ ok(ErrRef, call(Server, {setopts, SocketOpts ++ Setopts})),
+ ok(ErrRef, call(Server, {bind, BindAddr})),
+ DefaultError = {error, einval},
+ Socket =
+ val(ErrRef,
+ connect_loop(Addrs, Server, DefaultError, Timer)),
+ {ok, ?module_socket(Server, Socket)}
+ catch
+ throw : {ErrRef, Reason} ->
+ close_server(Server),
+ ?badarg_exit({error, Reason})
+ end;
+ {error, _} = Error ->
+ ?badarg_exit(Error)
+ end.
+
+connect_loop([], _Server, Error, _Timer) -> Error;
+connect_loop([Addr | Addrs], Server, _Error, Timer) ->
+ Result = call(Server, {connect, Addr, inet:timeout(Timer)}),
+ case Result of
+ {ok, _Socket} -> Result;
+ {error, badarg} -> Result;
+ {error, einval} -> Result;
+ {error, timeout} -> Result;
+ {error, _} ->
+ connect_loop(Addrs, Server, Result, Timer)
+ end.
+
+%% -------------------------------------------------------------------------
+
+listen(Port, Opts) ->
+ %% ?DBG({Port, Opts}),
+ {EinvalOpts, Opts_1} = setopts_split(einval, Opts),
+ EinvalOpts =:= [] orelse exit(badarg),
+ {Mod, Opts_2} = inet:tcp_module(Opts_1),
+ {StartOpts, Opts_3} = setopts_split(start, Opts_2),
+ case Mod:getserv(Port) of
+ {ok, TP} ->
+ case inet:listen_options([{port, TP} | Opts_3], Mod) of
+ {error, badarg} ->
+ exit(badarg);
+ {ok,
+ #listen_opts{
+ fd = Fd,
+ ifaddr = BindIP,
+ port = BindPort,
+ opts = ListenOpts,
+ backlog = Backlog}} ->
+ %%
+ Domain = domain(Mod),
+ %% ?DBG({Domain, BindIP}),
+ BindAddr =
+ case Domain of
+ local ->
+ {local, Path} = BindIP,
+ #{family => Domain,
+ path => Path};
+ _ ->
+ #{family => Domain,
+ addr => BindIP,
+ port => BindPort}
+ end,
+ listen_open(
+ Domain, ListenOpts, StartOpts, Fd, Backlog, BindAddr)
+ end;
+ {error, _} = Error ->
+ ?badarg_exit(Error)
+ end.
+
+%% Helpers -------
+
+listen_open(Domain, ListenOpts, Opts, Fd, Backlog, BindAddr) ->
+ %% ?DBG({Domain, ListenOpts, Opts, Fd, Backlog, BindAddr}),
+ ExtraOpts =
+ if
+ Fd =:= -1 -> [];
+ is_integer(Fd) -> [{fd, Fd}];
+ %% This is an **ugly** hack.
+ %% inet:connect_options/2 has the bad taste to use this
+ %% for [{netns,NS}] if that option is used...
+ is_list(Fd) -> Fd
+ end,
+ {SocketOpts, StartOpts} = setopts_split(socket, Opts),
+ case
+ start_server(
+ Domain, ExtraOpts,
+ [{timeout, infinity} | start_opts(StartOpts)])
+ of
+ {ok, Server} ->
+ {Setopts, _} =
+ setopts_split(
+ #{socket => [], server_read => [], server_write => []},
+ ListenOpts),
+ ErrRef = make_ref(),
+ try
+ ok(ErrRef,
+ call(
+ Server,
+ {setopts,
+ [{start_opts, StartOpts}] ++ SocketOpts ++ Setopts})),
+ ok(ErrRef, call(Server, {bind, BindAddr})),
+ Socket = val(ErrRef, call(Server, {listen, Backlog})),
+ {ok, ?module_socket(Server, Socket)}
+ catch
+ throw : {ErrRef, Reason} ->
+ close_server(Server),
+ ?badarg_exit({error, Reason})
+ end;
+ {error, {shutdown, Reason}} ->
+ ?badarg_exit({error, Reason});
+ {error, _} = Error ->
+ ?badarg_exit(Error)
+ end.
+
+%% -------------------------------------------------------------------------
+
+accept(?module_socket(ListenServer, ListenSocket), Timeout) ->
+ %%
+ Timer = inet:start_timer(Timeout),
+ ErrRef = make_ref(),
+ try
+ #{start_opts := StartOpts} = ServerData =
+ val(ErrRef, call(ListenServer, get_server_opts)),
+ Server =
+ val(ErrRef,
+ start_server(
+ ServerData,
+ [{timeout, inet:timeout(Timer)} | start_opts(StartOpts)])),
+ Socket =
+ val({ErrRef, Server},
+ call(Server, {accept, ListenSocket, inet:timeout(Timer)})),
+ {ok, ?module_socket(Server, Socket)}
+ catch
+ throw : {{ErrRef, Srv}, Reason} ->
+ stop_server(Srv),
+ ?badarg_exit({error, Reason});
+ throw : {ErrRef, Reason} ->
+ ?badarg_exit({error, Reason})
+ after
+ _ = inet:stop_timer(Timer)
+ end.
+
+%% -------------------------------------------------------------------------
+
+send(?module_socket(Server, Socket), Data) ->
+ case socket:getopt(Socket, otp, meta) of
+ {ok,
+ #{packet := Packet,
+ send_timeout := SendTimeout} = Meta} ->
+ if
+ Packet =:= 1;
+ Packet =:= 2;
+ Packet =:= 4 ->
+ Size = iolist_size(Data),
+ Header = <<?header(Packet, Size)>>,
+ Result =
+ socket_send(Socket, [Header, Data], SendTimeout),
+ send_result(Server, Meta, Result);
+
+ true ->
+ Result = socket_send(Socket, Data, SendTimeout),
+ send_result(Server, Meta, Result)
+ end;
+ {ok, _BadMeta} ->
+ exit(badarg);
+ {error, _} = Error ->
+ Error
+ end.
+%%
+send_result(Server, Meta, Result) ->
+ case Result of
+ {error, {Reason, _RestData}} ->
+ %% To handle RestData we would have to pass
+ %% all writes through a single process that buffers
+ %% the write data, which would be a bottleneck
+ %%
+ %% Since send data may have been lost, and there is no room
+ %% in this API to inform the caller, we at least close
+ %% the socket in the write direction
+%%% ?DBG(Result),
+ case Reason of
+ econnreset ->
+ case maps:get(show_econnreset, Meta) of
+ true -> {error, econnreset};
+ false -> {error, closed}
+ end;
+ timeout ->
+ _ = maps:get(send_timeout_close, Meta)
+ andalso close_server(Server),
+ {error, Reason};
+ _ ->
+ ?badarg_exit({error, Reason})
+ end;
+ ok ->
+ ok
+ end.
+%%% send_error(Server, Meta, {error, Reason});
+%%% {error, _} = Error ->
+%%% send_error(Server, Meta, Error);
+%%% ok -> ok
+%%% end.
+
+%%%send_error(Server, Meta, Error) ->
+%%% %% Since send data may have been lost, and there is no room
+%%% %% in this API to inform the caller, we at least close
+%%% %% the socket in the write direction
+%%%%%% ?DBG(Error),
+%%% case Error of
+%%% {error, econnreset} ->
+%%% case maps:get(show_econnreset, Meta) of
+%%% true -> ?badarg_exit(Error);
+%%% false -> {error, closed}
+%%% end;
+%%% {error, timeout} ->
+%%% _ = maps:get(send_timeout_close, Meta)
+%%% andalso close_server(Server),
+%%% ?badarg_exit(Error);
+%%% _ ->
+%%% ?badarg_exit(Error)
+%%% end.
+
+%% -------------------------------------------------------------------------
+
+recv(?module_socket(Server, _Socket), Length, Timeout) ->
+ ?badarg_exit(call(Server, {recv, Length, Timeout})).
+
+%% -------------------------------------------------------------------------
+
+shutdown(?module_socket(Server, Socket), How) ->
+ Result =
+ case How of
+ write ->
+ socket:shutdown(Socket, How);
+ read ->
+ call(Server, shutdown_read);
+ read_write ->
+ close_server(Server)
+ end,
+ ?badarg_exit(Result).
+
+%% -------------------------------------------------------------------------
+
+close(?module_socket(Server, _Socket)) ->
+ ?badarg_exit(close_server(Server)).
+
+%% Helpers -------
+
+close_server(Server) ->
+ Result = call(Server, close),
+ stop_server(Server),
+ Result.
+
+%% -------------------------------------------------------------------------
+
+controlling_process(?module_socket(Server, _Socket) = S, NewOwner)
+ when is_pid(NewOwner) ->
+ case call(Server, {controlling_process, NewOwner}) of
+ ok -> ok;
+ transfer -> controlling_process(S, NewOwner, Server);
+ {error, _} = Error -> Error
+ end.
+%%
+%% Helpers -------
+%%
+%% Transfer all queued socket messages to new owner
+controlling_process(S, NewOwner, Server) ->
+ receive
+ {tcp, S, _Data} = Msg ->
+ controlling_process(S, NewOwner, Server, Msg);
+ {tcp_closed, S} = Msg ->
+ controlling_process(S, NewOwner, Server, Msg);
+ {S, {data, _Data}} = Msg ->
+ controlling_process(S, NewOwner, Server, Msg)
+ after 0 ->
+ call(Server, controlling_process)
+ end.
+%% Loop
+controlling_process(S, NewOwner, Server, Msg) ->
+ NewOwner ! Msg,
+ controlling_process(S, NewOwner, Server).
+
+%% -------------------------------------------------------------------------
+%% Module inet backends
+%% -------------------------------------------------------------------------
+
+setopts(?module_socket(Server, _Socket), Opts) when is_list(Opts) ->
+ call(Server, {setopts, Opts}).
+
+%% -------------------------------------------------------------------------
+
+getopts(?module_socket(Server, _Socket), Opts) when is_list(Opts) ->
+ call(Server, {getopts, Opts}).
+
+%% -------------------------------------------------------------------------
+
+sockname(?module_socket(_Server, Socket)) ->
+ case socket:sockname(Socket) of
+ {ok, SockAddr} -> {ok, address(SockAddr)};
+ {error, _} = Error -> Error
+ end.
+
+%% -------------------------------------------------------------------------
+
+peername(?module_socket(_Server, Socket)) ->
+ case socket:peername(Socket) of
+ {ok, SockAddr} -> {ok, address(SockAddr)};
+ {error, _} = Error -> Error
+ end.
+
+%% -------------------------------------------------------------------------
+
+getstat(?module_socket(Server, _Socket), What) when is_list(What) ->
+ call(Server, {getstat, What}).
+
+%%% ========================================================================
+%%% Socket glue code
+%%%
+
+-compile({inline, [socket_send/3]}).
+socket_send(Socket, Opts, Timeout) ->
+ Result = socket:send(Socket, Opts, Timeout),
+ case Result of
+ {error, {epipe, Rest}} -> {error, {econnreset, Rest}};
+ {error, {_Reason, _Rest}} -> Result;
+ {select, _} -> Result;
+ {ok, _} -> Result;
+ ok -> ok
+ end.
+
+-compile({inline, [socket_recv_peek/2]}).
+socket_recv_peek(Socket, Length) ->
+ Options = [peek],
+ Result = socket:recv(Socket, Length, Options, nowait),
+%%% ?DBG({Socket, Length, Options, Result}),
+ Result.
+-compile({inline, [socket_recv/2]}).
+socket_recv(Socket, Length) ->
+ Result = socket:recv(Socket, Length, nowait),
+%%% ?DBG({Socket, Length, Result}),
+ Result.
+
+-compile({inline, [socket_close/1]}).
+socket_close(Socket) ->
+ %% XXX Should we set the meta option to closed here,
+ %% for the send operation to detect without calling
+ %% the NIF???
+ case socket:close(Socket) of
+ ok -> ok;
+ {error, closed} -> ok
+ end.
+
+-compile({inline, [socket_cancel/2]}).
+socket_cancel(Socket, SelectInfo) ->
+ case socket:cancel(Socket, SelectInfo) of
+ ok -> ok;
+ {error, closed} -> ok
+ end.
+
+%%% ========================================================================
+%%% API Helpers
+%%%
+
+%% Deep return helpers
+
+ok(_ErrRef, ok) -> ok;
+ok(ErrRef, {error, Reason}) -> throw({ErrRef, Reason}).
+
+val(_ErrRef, {ok, Val}) -> Val;
+val(ErrRef, {error, Reason}) -> throw({ErrRef, Reason}).
+
+
+address(SockAddr) ->
+ case SockAddr of
+ #{family := inet, addr := IP, port := Port} ->
+ {IP, Port};
+ #{family := inet6, addr := IP, port := Port} ->
+ {IP, Port};
+ #{family := local, path := Path} when is_list(Path) ->
+ {local, prim_socket:encode_path(Path)};
+ #{family := local, path := Path} when is_binary(Path) ->
+ {local, Path}
+ end.
+
+-ifdef(undefined).
+chain([F | Fs], Fail) ->
+ chain(Fs, Fail, [], F()).
+%%
+chain([F | Fs], Fail, Values) ->
+ chain(Fs, Fail, Values, F(Values)).
+%%
+chain([], _Fail, _Values, Ret) -> Ret;
+chain(Fs, Fail, Values, Ret) ->
+ case Ret of
+ {error, _} -> Fail(Ret);
+ ok -> chain(Fs, Fail, Values);
+ {ok, Value} -> chain(Fs, Fail, [Value | Values])
+ end.
+-endif.
+
+%% -------------------------------------------------------------------------
+
+-compile({inline, [domain/1]}).
+domain(Mod) ->
+ case Mod of
+ inet_tcp -> inet;
+ inet6_tcp -> inet6;
+ local_tcp -> local
+ end.
+
+%% -------------------------------------------------------------------------
+
+sockaddrs([], _TP, _Domain) -> [];
+sockaddrs([{local, Path} | IPs], TP, Domain) when (Domain =:= local) ->
+ [#{family => Domain, path => Path}
+ | sockaddrs(IPs, TP, Domain)];
+sockaddrs([IP | IPs], TP, Domain) ->
+ [#{family => Domain, addr => IP, port => TP}
+ | sockaddrs(IPs, TP, Domain)].
+
+%% -------------------------------------------------------------------------
+
+setopts_split(FilterTags, Opts) ->
+ setopts_split(FilterTags, Opts, [], []).
+%%
+setopts_split(_FilterTags, [], True, False) ->
+ {reverse(True), reverse(False)};
+setopts_split(FilterTags, [Opt | Opts], True, False) ->
+ Opt_1 = conv_setopt(Opt),
+ case member(FilterTags, setopt_categories(Opt_1)) of
+ true ->
+ setopts_split(FilterTags, Opts, [Opt_1 | True], False);
+ false ->
+ setopts_split(FilterTags, Opts, True, [Opt_1 | False])
+ end.
+
+
+%% Set operation on atom sets that are atoms or maps with atom tags.
+%% Returns true if sets have at least one common member, false otherwise.
+%% X is atom() or map(), Y is map().
+member(X, Y) when is_atom(X), is_map(Y) ->
+ case Y of
+ #{X := _} -> true;
+ #{} -> false
+ end;
+member(X, Y) when is_map(X), is_map(Y) ->
+ maps:fold(
+ fun (_, _, true) -> true;
+ (Key, _, false) -> maps:is_key(Key, Y)
+ end, false, X).
+
+
+conv_setopt(binary) -> {mode, binary};
+conv_setopt(list) -> {mode, list};
+conv_setopt(inet) -> {tcp_module, inet_tcp};
+conv_setopt(inet6) -> {tcp_module, inet6_tcp};
+conv_setopt(local) -> {tcp_module, local_tcp};
+conv_setopt(Other) -> Other.
+
+%% Socket options
+
+socket_setopt(Socket, {raw, Level, Key, Value}) ->
+ socket:setopt(Socket, Level, Key, Value);
+socket_setopt(Socket, {raw, {Level, Key, Value}}) ->
+ socket:setopt(Socket, Level, Key, Value);
+socket_setopt(Socket, {Tag, Value}) ->
+ case socket_opt() of
+ #{Tag := {Level, Key}} ->
+ socket:setopt(
+ Socket, Level, Key,
+ socket_setopt_value(Tag, Value));
+ #{} -> {error, einval}
+ end.
+
+socket_setopt_value(_Tag, Value) -> Value.
+
+socket_getopt(Socket, {raw, Level, Key, _Placeholder}) ->
+ socket:getopt(Socket, Level, Key);
+socket_getopt(Socket, {raw, {Level, Key, _Placeholder}}) ->
+ socket:getopt(Socket, Level, Key);
+socket_getopt(Socket, Tag) when is_atom(Tag) ->
+ case socket_opt() of
+ #{Tag := {Level, Key}} ->
+ socket_getopt_value(
+ Tag, socket:getopt(Socket, Level, Key));
+ #{} -> {error, einval}
+ end.
+
+socket_getopt_value(_Tag, {ok, _Value} = Ok) -> Ok;
+socket_getopt_value(_Tag, {error, _} = Error) -> Error.
+
+socket_copy_opt(Socket, Tag, TargetSocket) when is_atom(Tag) ->
+ case socket_opt() of
+ #{Tag := {Level, Key}} ->
+ case socket:is_supported(Level, Key) of
+ true ->
+ case socket:getopt(Socket, Level, Key) of
+ {ok, Value} ->
+ socket:setopt(TargetSocket, Level, Key, Value);
+ {error, _Reason} = Error ->
+ Error
+ end;
+ false ->
+ ok
+ end;
+ #{} = _X ->
+ {error, einval}
+ end.
+
+start_opts([{sys_debug, D} | Opts]) ->
+ [{debug, D} | start_opts(Opts)];
+start_opts([Opt | Opts]) ->
+ [Opt | start_opts(Opts)];
+start_opts([]) -> [].
+
+
+%% Categories: socket, ignore, start, server_read, server_write, einval
+%% returns a maps set
+
+setopt_categories(Opt) ->
+ case Opt of
+ {raw, _, _, _} -> #{socket => []};
+ {raw, {_, _, _}} -> #{socket => []};
+ {Tag, _} -> opt_categories(Tag);
+ _ -> ignore
+ end.
+
+getopt_categories(Opt) ->
+ case Opt of
+ {raw, _, _, _} -> #{socket => []};
+ {raw, {_, _, _}} -> #{socket => []};
+ _ -> opt_categories(Opt)
+ end.
+
+%% setopt and getopt category
+opt_categories(Tag) when is_atom(Tag) ->
+ case Tag of
+ sys_debug -> #{start => []};
+ debug -> #{socket => [], start => []};
+ _ ->
+ case maps:is_key(Tag, socket_opt()) of
+ true -> #{socket => []};
+ false ->
+ case maps:is_key(Tag, ignore_opt()) of
+ true ->
+ #{ignore => []};
+ false ->
+ maps:merge(
+ case maps:is_key(Tag, server_read_opts()) of
+ true ->
+ #{server_read => []};
+ false ->
+ #{}
+ end,
+ case maps:is_key(Tag, server_write_opts()) of
+ true ->
+ #{server_write => []};
+ false ->
+ #{}
+ end)
+ end
+ end
+ end.
+
+-compile({inline, [ignore_opt/0]}).
+ignore_opt() ->
+ #{
+ %% Handled by inet:tcp_module/2
+ tcp_module => [],
+ %% Handled by inet:connect_options/2 and inet:listen_options/2
+ ip => [],
+ backlog => [],
+ %% XXX Some of these must probably be handled one day...
+ high_msgq_watermark => [],
+ high_watermark => [],
+ low_msgq_watermark => [],
+ nopush => []
+ }.
+
+%% Category 'socket'
+%%
+%% Translation to level and type
+-compile({inline, [socket_opt/0]}).
+socket_opt() ->
+ #{%% Level: otp
+ buffer => {otp, rcvbuf},
+ debug => {otp, debug},
+ fd => {otp, fd},
+ %%
+ %% Level: socket
+ bind_to_device => {socket, bindtodevice},
+ dontroute => {socket, dontroute},
+ keepalive => {socket, keepalive},
+ linger => {socket, linger},
+ low_watermark => {socket, rcvlowat},
+ priority => {socket, priority},
+ recbuf => {socket, rcvbuf},
+ reuseaddr => {socket, reuseaddr},
+ sndbuf => {socket, sndbuf},
+ %%
+ %% Level: tcp
+ nodelay => {tcp, nodelay},
+ %%
+ %% Level: ip
+ recvtos => {ip, recvtos},
+ recvttl => {ip, recvttl},
+ tos => {ip, tos},
+ ttl => {ip, ttl},
+ %%
+ %% Level: ipv6
+ recvtclass => {ipv6, recvtclass},
+ ipv6_v6only => {ipv6, v6only}
+ }.
+
+-compile({inline, [socket_inherit_opts/0]}).
+socket_inherit_opts() ->
+ [priority].
+
+-compile({inline, [server_read_write_opts/0]}).
+server_read_write_opts() ->
+ %% Common for read and write side
+ #{packet => raw,
+ packet_size => 16#4000000, % 64 MByte
+ show_econnreset => false}.
+-compile({inline, [server_read_opts/0]}).
+server_read_opts() ->
+ %% Read side only opts
+ maps:merge(
+ #{active => true,
+ mode => list,
+ header => 0,
+ deliver => term,
+ start_opts => [], % Just to make it settable
+ %% XXX not implemented yet
+ exit_on_close => true,
+ line_delimiter => $\n},
+ server_read_write_opts()).
+-compile({inline, [server_write_opts/0]}).
+server_write_opts() ->
+ %% Write side only opts
+ maps:merge(
+ #{send_timeout => infinity,
+ send_timeout_close => false,
+ %% XXX not implemented yet
+ delay_send => false},
+ server_read_write_opts()).
+%% Category 'server'
+%%
+%% Default values
+-compile({inline, [server_opts/0]}).
+server_opts() ->
+ maps:merge(server_read_opts(), server_write_opts()).
+
+-compile({inline, [meta/1]}).
+meta(D) -> maps:with(maps:keys(server_write_opts()), D).
+
+%%% ========================================================================
+%%% State Machine
+%%%
+
+%% State Machine Engine Call Interface
+
+%% Start for connect or listen - create a socket
+start_server(Domain, ExtraOpts, StartOpts) ->
+ Owner = self(),
+ Arg = {open, Domain, ExtraOpts, Owner},
+ case gen_statem:start(?MODULE, Arg, StartOpts) of
+ {ok, Server} -> {ok, Server};
+ {error, _} = Error -> Error
+ end.
+
+%% Start for accept - have no socket yet
+start_server(ServerData, StartOpts) ->
+ Owner = self(),
+ Arg = {prepare, ServerData, Owner},
+ case gen_statem:start(?MODULE, Arg, StartOpts) of
+ {ok, Server} -> {ok, Server};
+ {error, _} = Error -> Error
+ end.
+
+call(Server, Call) ->
+ try gen_statem:call(Server, Call)
+ catch exit:{noproc, {gen_statem, call, _Args}} -> {error, closed}
+ end.
+
+stop_server(Server) ->
+ try gen_statem:stop(Server) of
+ _ -> ok
+ catch
+ _:_ -> ok
+ end.
+
+%% reply(From, Reply) ->
+%% gen_statem:reply(From, Reply).
+
+%% -------------------------------------------------------------------------
+%% Statem Machine Engine Callbacks
+
+callback_mode() -> handle_event_function.
+
+%% States:
+%%
+-record(controlling_process,
+ {owner :: pid(),
+ state :: term()}).
+%% A super state that encapsulates any other state
+%% and postpones all events but get_server_opts/0
+%% and Owner 'DOWN'
+
+%% 'accept'
+-record(accept,
+ {info :: socket:select_info(),
+ from :: gen_statem:from(),
+ listen_socket :: socket:socket()}).
+%% Socket is not created
+
+%% 'connect' % A listen socket stays here
+-record(connect,
+ {info :: socket:select_info(),
+ from :: gen_statem:from(),
+ addr :: socket:sockaddr()}).
+
+%% 'connected'
+-record(recv,
+ {info :: socket:select_info()}).
+
+%% 'closed_read'
+%% 'closed' % Socket is closed or not created
+
+
+-record(params,
+ {socket :: undefined | socket:socket(),
+ owner :: pid(),
+ owner_mon :: reference()}).
+
+init({open, Domain, ExtraOpts, Owner}) ->
+ %% Listen or Connect
+ %%
+ process_flag(trap_exit, true),
+ OwnerMon = monitor(process, Owner),
+ Extra = maps:from_list(ExtraOpts),
+ Proto = if (Domain =:= local) -> default; true -> tcp end,
+ case socket:open(Domain, stream, Proto, Extra) of
+ {ok, Socket} ->
+ D = server_opts(),
+ ok = socket:setopt(Socket, otp, iow, true),
+ ok = socket:setopt(Socket, otp, meta, meta(D)),
+ P =
+ #params{
+ socket = Socket,
+ owner = Owner,
+ owner_mon = OwnerMon},
+ {ok, connect, {P, D#{buffer => <<>>}}};
+ {error, Reason} -> {stop, {shutdown, Reason}}
+ end;
+init({prepare, D, Owner}) ->
+ %% Accept
+ %%
+ process_flag(trap_exit, true),
+ OwnerMon = monitor(process, Owner),
+ P =
+ #params{
+ owner = Owner,
+ owner_mon = OwnerMon},
+ {ok, accept, {P, D#{buffer => <<>>}}};
+init(Arg) ->
+ error_logger:error_report([{badarg, {?MODULE, init, [Arg]}}]),
+ error(badarg, [Arg]).
+
+terminate(_Reason, State, P_D) ->
+ case State of
+ #controlling_process{state = OldState} ->
+ terminate(OldState, P_D);
+ _ ->
+ terminate(State, P_D)
+ end.
+%%
+terminate(State, {#params{socket = Socket} = P, D}) ->
+ case State of
+ 'closed' -> ok;
+ 'closed_read' ->
+ _ = socket_close(Socket),
+ ok;
+ _ ->
+ case State of
+ 'accept' -> ok;
+ #accept{} -> ok;
+ _ ->
+ _ = socket_close(Socket),
+ ok
+ end,
+ {_D_1, ActionsR} =
+ case State of
+ #controlling_process{state = OldState} ->
+ cleanup_close_read(P, D, OldState, closed);
+ _ ->
+ cleanup_close_read(P, D, State, closed)
+ end,
+ [gen_statem:reply(Reply)
+ || {reply, _From, _Msg} = Reply <- reverse(ActionsR)],
+ ok
+ end,
+ void.
+
+%% -------------------------------------------------------------------------
+%% Helpers
+
+%% Construct a "socket" as in this module's API
+module_socket(#params{socket = Socket}) ->
+ ?module_socket(self(), Socket).
+
+%% -------------------------------------------------------------------------
+%% Event Handler (callback)
+
+%% -type packet_option_value() ::
+%% 0 | 1 | 2 | 4 | raw | sunrm | asn1 |
+%% cdr | fcgi | line | tpkt | http | httph | http_bin | httph_bin.
+
+-compile({inline, [is_packet_option_value/1]}).
+is_packet_option_value(Value) ->
+ case Value of
+ 0 -> true; 1 -> true; 2 -> true; 4 -> true;
+ raw -> true;
+ sunrm -> true;
+ asn1 -> true;
+ cdr -> true;
+ fcgi -> true;
+ line -> true;
+ tpkt -> true;
+ http -> true;
+ httph -> true;
+ http_bin -> true;
+ httph_bin -> true;
+ _ -> false
+ end.
+
+%% Any state:
+
+%% Call: get_server_opts/0
+handle_event({call, From}, get_server_opts, _State, {_P, D}) ->
+ ServerData = maps:with(maps:keys(server_opts()), D),
+ {keep_state_and_data,
+ [{reply, From, {ok, ServerData}}]};
+
+%% Event: Owner 'DOWN'
+handle_event(
+ info, {'DOWN', OwnerMon, _, _, Reason}, _State,
+ {#params{owner_mon = OwnerMon} = _P, _D} = P_D) ->
+ %%
+ {stop, {shutdown, Reason}, P_D};
+
+%% Event: ?socket_counter_wrap/2
+handle_event(
+ info, ?socket_counter_wrap(Socket, Counter),
+ 'connected' = _State, {#params{socket = Socket} = P, D}) ->
+ {keep_state, {P, wrap_counter(Counter, D)}};
+handle_event(
+ info, ?socket_counter_wrap(Socket, Counter),
+ #recv{} = _State, {#params{socket = Socket} = P, D}) ->
+ {keep_state, {P, wrap_counter(Counter, D)}};
+handle_event(
+ info, ?socket_counter_wrap(_Socket, _Counter), _State, _P_D) ->
+ {keep_state_and_data,
+ [postpone]};
+
+%% Call: controlling_process/1
+handle_event(
+ {call, {Caller, _} = From}, {controlling_process, NewOwner},
+ State, {P, _D} = P_D) ->
+ %%
+ case P of
+ #params{owner = NewOwner} ->
+ {keep_state_and_data,
+ [{reply, From, ok}]};
+ #params{owner = Caller} ->
+ {next_state,
+ #controlling_process{owner = NewOwner, state = State},
+ P_D,
+ [{reply, From, transfer}]};
+ #params{} ->
+ {keep_state_and_data,
+ [{reply, From, {error, not_owner}}]}
+ end;
+%%
+%% State: #controlling_process{}
+%%
+%% Call: controlling_process/0
+handle_event(
+ {call, {Owner, _} = From}, controlling_process,
+ #controlling_process{owner = NewOwner, state = State},
+ {#params{owner = Owner, owner_mon = OwnerMon} = P, D}) ->
+ %%
+ NewOwnerMon = monitor(process, NewOwner),
+ true = demonitor(OwnerMon, [flush]),
+ {next_state, State,
+ {P#params{owner = NewOwner, owner_mon = NewOwnerMon}, D},
+ [{reply, From, ok}]};
+%%
+%% Postpone all events but the ones above controlling_process/1
+%% until the controlling process has been changed
+handle_event(
+ _Type, _Content,
+ #controlling_process{},
+ _StateData) ->
+ %%
+ {keep_state_and_data, [postpone]};
+%% Handled state: #controlling_process{}
+
+%% Call: close/0
+handle_event({call, From}, close, State, {P, D} = P_D) ->
+ case State of
+ 'closed_read' ->
+ {next_state, 'closed', P_D,
+ [{reply, From, socket_close(P#params.socket)}]};
+ 'closed' ->
+ {keep_state_and_data,
+ [{reply, From, ok}]};
+ _ ->
+ next_state(
+ P, cleanup_close_read(P, D#{active := false}, State, closed),
+ 'closed',
+ [{reply, From, socket_close(P#params.socket)}])
+ end;
+
+%% Call: getopts/1
+handle_event({call, From}, {getopts, Opts}, State, {P, D}) ->
+ Result = state_getopts(P, D, State, Opts),
+ {keep_state_and_data,
+ [{reply, From, Result}]};
+
+%% Call: setopts/1
+handle_event({call, From}, {setopts, Opts}, State, {P, D}) ->
+ {Result, D_1} = state_setopts(P, D, State, Opts),
+ ok = socket:setopt(P#params.socket, otp, meta, meta(D_1)),
+ Reply = {reply, From, Result},
+ case State of
+ 'connected' ->
+ handle_connected(
+ P, D_1,
+ [Reply]);
+ _ ->
+ {keep_state, {P, D_1},
+ [Reply]}
+ end;
+
+%% Call: getstat/2
+handle_event({call, From}, {getstat, What}, State, {P, D}) ->
+ case State of
+ 'closed' ->
+ {keep_state_and_data,
+ [{reply, From, {error, closed}}]};
+ _ ->
+ {D_1, Result} = getstat(P#params.socket, D, What),
+ {keep_state, {P, D_1},
+ [{reply, From, {ok, Result}}]}
+ end;
+
+%% State: 'closed' - what is not handled above
+handle_event(Type, Content, 'closed' = State, P_D) ->
+ handle_closed(Type, Content, State, P_D);
+%% Handled state: 'closed'
+
+%% Call: shutdown/1
+handle_event({call, From}, shutdown_read, State, {P, D}) ->
+ case State of
+ 'closed_read' ->
+ {keep_state_and_data,
+ [{reply, From, ok}]};
+ _ ->
+ next_state(
+ P,
+ cleanup_close_read(P, D#{active := false}, State, closed),
+ 'closed_read',
+ [{reply, From, socket:shutdown(P#params.socket, read)}])
+ end;
+%% State: 'closed_read' - what is not handled in
+%% close/0 and shutdown/1 above
+handle_event(Type, Content, 'closed_read' = State, P_D) ->
+ handle_closed(Type, Content, State, P_D);
+
+
+
+%% State: 'accept'
+handle_event(
+ {call, From}, {accept, ListenSocket, Timeout},
+ 'accept' = _State, {P, D}) ->
+ handle_accept(P, D, From, ListenSocket, Timeout);
+handle_event(Type, Content, 'accept' = State, P_D) ->
+ handle_unexpected(Type, Content, State, P_D);
+%%
+%% State: #accept{}
+handle_event(
+ info, ?socket_select(ListenSocket, SelectRef),
+ #accept{
+ info = ?select_info(SelectRef), from = From,
+ listen_socket = ListenSocket},
+ {P, D}) ->
+ handle_accept(P, D, From, ListenSocket, update);
+handle_event(
+ info, ?socket_abort(ListenSocket, SelectRef, Reason),
+ #accept{
+ info = ?select_info(SelectRef), from = From,
+ listen_socket = ListenSocket},
+ {P, D}) ->
+ {next_state, 'closed', {P, D},
+ [{reply, From, {error, Reason}}]};
+handle_event(
+ {timeout, accept}, accept,
+ #accept{
+ info = SelectInfo, from = From,
+ listen_socket = ListenSocket},
+ {P, D}) ->
+ socket_cancel(ListenSocket, SelectInfo),
+ {next_state, 'closed', {P, D},
+ [{reply, From, {error, timeout}}]};
+handle_event(Type, Content, #accept{} = State, P_D) ->
+ handle_unexpected(Type, Content, State, P_D);
+%% Handled states: 'accept' | #accept{}
+
+%% ------- Socket is defined from here on -----------------------------------
+
+%% Call: bind/1
+handle_event({call, From}, {bind, BindAddr}, _State, {P, _D}) ->
+ Result =
+ case socket:bind(P#params.socket, BindAddr) of
+ %% XXX Should we store Port with BindAddr as sockname?
+ %% Should bind return port?
+ %% There is no port for domain = unix
+ {ok, _Port} -> ok;
+ {error, _} = Error -> Error
+ end,
+ {keep_state_and_data,
+ [{reply, From, Result}]};
+
+%% It is a bit arbitrary that {listen, _} returns {ok, Socket},
+%% since Socket is known since start_server, but has not been returned
+%% to listen/1 yet. It could be returned from {bind, _},
+%% or from a separate get_socket call, but piggy-backing it
+%% on {listen, _} is convenient.
+
+%% Call: listen/1
+handle_event(
+ {call, From}, {listen, Backlog},
+ _State, {#params{socket = Socket} = _P, _D}) ->
+ Result =
+ case socket:listen(Socket, Backlog) of
+ ok -> {ok, Socket};
+ {error, _} = Error -> Error
+ end,
+ {keep_state_and_data,
+ [{reply, From, Result}]};
+
+%% Call: recv/2 - active socket
+handle_event(
+ {call, From}, {recv, _Length, _Timeout},
+ _State, {_P, #{active := Active} = _D})
+ when Active =/= false ->
+ {keep_state_and_data,
+ [{reply, From, {error, einval}}]};
+
+%% State: 'connect'
+%%
+%% Call: connect/2
+handle_event(
+ {call, From}, {connect, Addr, Timeout}, 'connect' = _State, {P, D}) ->
+ handle_connect(P, D, From, Addr, Timeout);
+%%
+%% Call: recv/2 - not connected
+handle_event(
+ {call, From}, {recv, _Length, _Timeout}, 'connect' = _State, _P_D) ->
+ {keep_state_and_data,
+ [{reply, From, {error, enotconn}}]};
+handle_event(Type, Content, 'connect' = State, P_D) ->
+ handle_unexpected(Type, Content, State, P_D);
+%%
+%% State: #connect{}
+handle_event(
+ info, ?socket_select(Socket, SelectRef),
+ #connect{
+ info = ?select_info(SelectRef), from = From, addr = Addr} = _State,
+ {#params{socket = Socket} = P, D}) ->
+ handle_connect(P, D, From, Addr, update);
+handle_event(
+ info, ?socket_abort(Socket, SelectRef, Reason),
+ #connect{info = ?select_info(SelectRef), from = From} = _State,
+ {#params{socket = Socket} = _P, _D} = P_D) ->
+ _ = socket_close(Socket),
+ {next_state, 'closed', P_D,
+ [{reply, From, {error, Reason}}]};
+handle_event(
+ {timeout, connect}, connect,
+ #connect{info = SelectInfo, from = From},
+ {#params{socket = Socket} = _P, _D} = P_D) ->
+ socket_cancel(Socket, SelectInfo),
+ _ = socket_close(Socket),
+ {next_state, 'closed', P_D,
+ [{reply, From, {error, timeout}}]};
+%%
+%% Call: recv/2 - not connected
+handle_event(
+ {call, From}, {recv, _Length, _Timeout}, #connect{} = _State, _P_D) ->
+ {keep_state_and_data,
+ [{reply, From, {error, enotconn}}]};
+handle_event(Type, Content, #connect{} = State, P_D) ->
+ handle_unexpected(Type, Content, State, P_D);
+%% Handled states: 'connect' | #connect{}
+
+
+
+%% Remaining states: 'connected' | #recv{}
+
+%% Call: recv/2 - last part
+handle_event(
+ {call, From}, {recv, Length, Timeout}, State, {P, D}) ->
+ case State of
+ 'connected' ->
+ handle_recv_start(P, D, From, Length, Timeout);
+ #recv{} ->
+ %% Receive in progress
+ {keep_state_and_data,
+ [postpone]}
+ end;
+
+%% State: #recv{}
+%%
+%% Handle select done - try recv again
+handle_event(
+ info, ?socket_select(Socket, SelectRef),
+ #recv{info = ?select_info(SelectRef)},
+ {#params{socket = Socket} = P, D}) ->
+ %%
+ handle_recv(P, D, []);
+%%
+handle_event(
+ info, ?socket_abort(Socket, SelectRef, Reason),
+ #recv{info = ?select_info(SelectRef)},
+ {#params{socket = Socket} = P, D}) ->
+ %%
+ handle_connected(P, cleanup_recv_reply(P, D, [], Reason));
+%%
+%% Timeout on recv in non-active mode
+handle_event(
+ {timeout, recv}, recv, #recv{} = State, {P, D}) ->
+ %%
+ handle_connected(P, cleanup_recv(P, D, State, timeout));
+
+%% Catch-all
+handle_event(Type, Content, State, P_D) ->
+ handle_unexpected(Type, Content, State, P_D).
+
+%% End of event handler
+%% -------------------------------------------------------------------------
+%% Event handler helpers
+
+handle_unexpected(Type, Content, State, {P, _D}) ->
+ error_logger:warning_report(
+ [{module, ?MODULE}, {socket, P#params.socket},
+ {unknown_event, {Type, Content}}, {state, State}]),
+ case Type of
+ {call, From} ->
+ {keep_state_and_data,
+ [{reply, From, {error, einval}}]};
+ _ ->
+ keep_state_and_data
+ end.
+
+handle_closed(Type, Content, State, {P, _D}) ->
+ case Type of
+ {call, From} ->
+ {keep_state_and_data,
+ [{reply, From, {error, closed}}]};
+ _ ->
+ error_logger:warning_report(
+ [{module, ?MODULE}, {socket, P#params.socket},
+ {unknown_event, {Type, Content}}, {state, State}]),
+ keep_state_and_data
+ end.
+
+%% State transition helpers -------
+
+handle_connect(
+ #params{socket = Socket} = P, D, From, Addr, Timeout) ->
+ %%
+ case socket:connect(Socket, Addr, nowait) of
+ ok ->
+ handle_connected(
+ P, D,
+ [{{timeout, connect}, cancel},
+ {reply, From, {ok, Socket}}]);
+ {select, SelectInfo} ->
+ {next_state,
+ #connect{info = SelectInfo, from = From, addr = Addr},
+ {P, D},
+ [{{timeout, connect}, Timeout, connect}]};
+ {error, _} = Error ->
+ {next_state,
+ 'connect', {P, D},
+ [{{timeout, connect}, cancel},
+ {reply, From, Error}]}
+ end.
+
+handle_accept(P, D, From, ListenSocket, Timeout) ->
+ case socket:accept(ListenSocket, nowait) of
+ {ok, Socket} ->
+ ok = socket:setopt(Socket, otp, iow, true),
+ ok = socket:setopt(Socket, otp, meta, meta(D)),
+ [ok = socket_copy_opt(ListenSocket, Opt, Socket)
+ || Opt <- socket_inherit_opts()],
+ handle_connected(
+ P#params{socket = Socket}, D,
+ [{{timeout, accept}, cancel},
+ {reply, From, {ok, Socket}}]);
+ {select, SelectInfo} ->
+ {next_state,
+ #accept{
+ info = SelectInfo, from = From,
+ listen_socket = ListenSocket},
+ {P, D},
+ [{{timeout, accept}, Timeout, accept}]};
+ {error, _} = Error ->
+ {next_state,
+ 'accept', {P, D},
+ [{{timeout, accept}, cancel},
+ {reply, From, Error}]}
+ end.
+
+handle_connected(P, {D, ActionsR}) ->
+ handle_connected(P, D, ActionsR).
+%%
+handle_connected(P, D, ActionsR) ->
+ case D of
+ #{active := false} ->
+ {next_state, 'connected',
+ {P, D},
+ reverse(ActionsR)};
+ #{active := _} ->
+ handle_recv(P, recv_start(D), ActionsR)
+ end.
+
+handle_recv_start(
+ P, #{packet := Packet, buffer := Buffer} = D, From, Length, Timeout)
+ when Packet =:= raw, 0 < Length;
+ Packet =:= 0, 0 < Length ->
+ Size = iolist_size(Buffer),
+ if
+ Length =< Size ->
+ {Data, NewBuffer} =
+ split_binary(condense_buffer(Buffer), Length),
+ handle_recv_deliver(
+ P,
+ D#{recv_length => Length, % Redundant
+ recv_from => From,
+ buffer := NewBuffer},
+ [], Data);
+ true ->
+ N = Length - Size,
+ handle_recv(
+ P, D#{recv_length => N, recv_from => From},
+ [{{timeout, recv}, Timeout, recv}])
+ end;
+handle_recv_start(P, D, From, _Length, Timeout) ->
+ handle_recv(
+ P, D#{recv_length => 0, recv_from => From},
+ [{{timeout, recv}, Timeout, recv}]).
+
+handle_recv(P, #{packet := Packet, recv_length := Length} = D, ActionsR) ->
+ if
+ 0 < Length ->
+ handle_recv_length(P, D, ActionsR, Length);
+ Packet =:= raw;
+ Packet =:= 0 ->
+ handle_recv_length(P, D, ActionsR, Length);
+ Packet =:= 1;
+ Packet =:= 2;
+ Packet =:= 4 ->
+ handle_recv_peek(P, D, ActionsR, Packet);
+ true ->
+ handle_recv_packet(P, D, ActionsR)
+ end.
+
+handle_recv_peek(P, D, ActionsR, Packet) ->
+ %% Peek Packet bytes
+ case D of
+ #{buffer := Buffer} when is_list(Buffer) ->
+ Data = condense_buffer(Buffer),
+ handle_recv_peek(P, D#{buffer := Data}, ActionsR, Packet);
+ #{buffer := <<Data:Packet/binary, _Rest/binary>>} ->
+ handle_recv_peek(P, D, ActionsR, Packet, Data);
+ #{buffer := <<ShortData/binary>>} ->
+ N = Packet - byte_size(ShortData),
+ case socket_recv_peek(P#params.socket, N) of
+ {ok, <<FinalData/binary>>} ->
+ handle_recv_peek(
+ P, D, ActionsR, Packet,
+ <<ShortData/binary, FinalData/binary>>);
+ {ok, {_, SelectInfo}} ->
+ {next_state,
+ #recv{info = SelectInfo},
+ {P, D},
+ reverse(ActionsR)};
+ {select, SelectInfo} ->
+ {next_state,
+ #recv{info = SelectInfo},
+ {P, D},
+ reverse(ActionsR)};
+ {error, {Reason, <<_Data/binary>>}} ->
+ handle_recv_error(P, D, ActionsR, Reason);
+ {error, Reason} ->
+ handle_recv_error(P, D, ActionsR, Reason)
+ end
+ end.
+
+handle_recv_peek(P, D, ActionsR, Packet, Data) ->
+ <<?header(Packet, N)>> = Data,
+ #{packet_size := PacketSize} = D,
+ if
+ 0 < PacketSize, PacketSize < N ->
+ handle_recv_error(P, D, ActionsR, emsgsize);
+ true ->
+ handle_recv_length(P, D, ActionsR, Packet + N)
+ end.
+
+handle_recv_packet(P, D, ActionsR) ->
+ case D of
+ #{buffer := Buffer} when is_list(Buffer) ->
+ Data = condense_buffer(Buffer),
+ handle_recv_decode(P, D, ActionsR, Data);
+ #{buffer := Data} when is_binary(Data) ->
+ handle_recv_more(P, D, ActionsR, Data)
+ end.
+
+handle_recv_length(P, #{buffer := Buffer} = D, ActionsR, Length) ->
+ handle_recv_length(P, D, ActionsR, Length, Buffer).
+%%
+%% Here and downwards until handle_recv_deliver() all buffered data
+%% is the last argument binary and D#{buffer} is not updated
+%%
+handle_recv_length(P, D, ActionsR, Length, Buffer) when 0 < Length ->
+ case socket_recv(P#params.socket, Length) of
+ {ok, <<Data/binary>>} ->
+ handle_recv_deliver(
+ P, D#{buffer := <<>>}, ActionsR,
+ condense_buffer([Data | Buffer]));
+ {ok, {Data, SelectInfo}} ->
+ N = Length - byte_size(Data),
+ {next_state,
+ #recv{info = SelectInfo},
+ {P, D#{buffer := [Data | Buffer], recv_length := N}},
+ reverse(ActionsR)};
+ {select, SelectInfo} ->
+ {next_state,
+ #recv{info = SelectInfo},
+ {P, D#{buffer := Buffer}},
+ reverse(ActionsR)};
+ {error, {Reason, <<Data/binary>>}} ->
+ %% Error before all data
+ handle_recv_error(
+ P, D#{buffer := [Data | Buffer]}, ActionsR, Reason);
+ {error, Reason} ->
+ handle_recv_error(P, D#{buffer := Buffer}, ActionsR, Reason)
+ end;
+handle_recv_length(P, D, ActionsR, _0, Buffer) ->
+ case Buffer of
+ <<>> ->
+ %% We should not need to update the buffer field here
+ %% since the only way to get here with empty Buffer
+ %% is when Buffer comes from the buffer field
+ Socket = P#params.socket,
+ case socket_recv(Socket, 0) of
+ {ok, <<Data/binary>>} ->
+ handle_recv_deliver(P, D, ActionsR, Data);
+ {ok, {Data, SelectInfo}} ->
+ case socket:cancel(Socket, SelectInfo) of
+ ok ->
+ handle_recv_deliver(P, D, ActionsR, Data);
+ {error, Reason} ->
+ handle_recv_error(P, D, ActionsR, Reason, Data)
+ end;
+ {select, SelectInfo} ->
+ {next_state,
+ #recv{info = SelectInfo},
+ {P, D},
+ reverse(ActionsR)};
+ {error, {Reason, <<Data/binary>>}} ->
+ handle_recv_error(P, D, ActionsR, Reason, Data);
+ {error, Reason} ->
+ handle_recv_error(P, D, ActionsR, Reason)
+ end;
+ <<Data/binary>> ->
+ handle_recv_deliver(P, D#{buffer := <<>>}, ActionsR, Data);
+ _ when is_list(Buffer) ->
+ Data = condense_buffer(Buffer),
+ handle_recv_deliver(P, D#{buffer := <<>>}, ActionsR, Data)
+ end.
+
+handle_recv_decode(P, #{packet_size := PacketSize} = D, ActionsR, Data) ->
+ case
+ erlang:decode_packet(
+ decode_packet(D), Data,
+ [{packet_size, PacketSize},
+ {line_length, PacketSize}])
+ of
+ {ok, Decoded, Rest} ->
+ %% is_list(Buffer) -> try to decode first
+ %% is_binary(Buffer) -> get more data first
+ Buffer =
+ case Rest of
+ <<>> -> Rest;
+ <<_/binary>> -> [Rest]
+ end,
+ handle_recv_deliver(P, D#{buffer := Buffer}, ActionsR, Decoded);
+ {more, undefined} ->
+ handle_recv_more(P, D, ActionsR, Data);
+ {more, Length} ->
+ N = Length - byte_size(Data),
+ handle_recv_length(P, D, ActionsR, N, Data);
+ {error, Reason} ->
+ handle_recv_error(
+ P, D#{buffer := Data}, ActionsR,
+ case Reason of
+ invalid -> emsgsize;
+ _ -> Reason
+ end)
+ end.
+
+handle_recv_error_decode(
+ P, #{packet_size := PacketSize} = D, ActionsR, Reason, Data) ->
+ %%
+ case
+ erlang:decode_packet(
+ decode_packet(D), Data,
+ [{packet_size, PacketSize},
+ {line_length, PacketSize}])
+ of
+ {ok, Decoded, Rest} ->
+ %% is_list(Buffer) -> try to decode first
+ %% is_binary(Buffer) -> get more data first
+ Buffer =
+ case Rest of
+ <<>> -> Rest;
+ <<_/binary>> -> [Rest]
+ end,
+ handle_recv_error(
+ P, D#{buffer := Buffer}, ActionsR, Reason, Decoded);
+ {more, _} ->
+ handle_recv_error(P, D#{buffer := Data}, ActionsR, Reason);
+ {error, Reason} ->
+ handle_recv_error(
+ P, D#{buffer := Data}, ActionsR,
+ case Reason of
+ invalid -> emsgsize;
+ _ -> Reason
+ end)
+ end.
+
+handle_recv_more(P, D, ActionsR, BufferedData) ->
+ case socket_recv(P#params.socket, 0) of
+ {ok, <<MoreData/binary>>} ->
+ Data = catbin(BufferedData, MoreData),
+ handle_recv_decode(P, D, ActionsR, Data);
+ {select, SelectInfo} ->
+ {next_state,
+ #recv{info = SelectInfo},
+ {P, D#{buffer := BufferedData}},
+ reverse(ActionsR)};
+ {error, {Reason, <<MoreData/binary>>}} ->
+ Data = catbin(BufferedData, MoreData),
+ handle_recv_error_decode(P, D, ActionsR, Reason, Data);
+ {error, Reason} ->
+ handle_recv_error(
+ P, D#{buffer := BufferedData}, ActionsR, Reason)
+ end.
+
+%% Here D#{buffer} is supposed to be updated again
+
+handle_recv_deliver(P, D, ActionsR, Data) ->
+ handle_connected(P, recv_data_deliver(P, D, ActionsR, Data)).
+
+handle_recv_error(P, D, ActionsR, Reason, Data) ->
+ %% Deliver, then error
+ {D_1, ActionsR_1} = recv_data_deliver(P, D, ActionsR, Data),
+ handle_recv_error(P, D_1, ActionsR_1, Reason).
+%%
+handle_recv_error(P, D, ActionsR, Reason) ->
+%%% ?DBG(Reason),
+ {D_1, ActionsR_1} =
+ cleanup_recv_reply(P, D#{buffer := <<>>}, ActionsR, Reason),
+ case Reason of
+ closed ->
+ {next_state, 'closed_read', {P, D_1}, reverse(ActionsR_1)};
+ econnreset ->
+ _ = socket_close(P#params.socket),
+ {next_state, 'closed', {P, D_1}, reverse(ActionsR_1)};
+ emsgsize ->
+ {next_state, 'connected',
+ {P, recv_stop(D#{active := false})},
+ reverse(ActionsR_1)}
+ end.
+
+%% -------------------------------------------------------------------------
+%% Callback Helpers
+
+next_state(P, {D, ActionsR}, State, Actions) ->
+ {next_state, State, {P, D}, reverse(ActionsR, Actions)}.
+
+cleanup_close_read(P, D, State, Reason) ->
+ case State of
+ #accept{
+ info = SelectInfo, from = From, listen_socket = ListenSocket} ->
+ socket_cancel(ListenSocket, SelectInfo),
+ {D,
+ [{reply, From, {error, Reason}}]};
+ #connect{info = SelectInfo, from = From} ->
+ socket_cancel(P#params.socket, SelectInfo),
+ {D,
+ [{reply, From, {error, Reason}}]};
+ _ ->
+ cleanup_recv(P, D, State, Reason)
+ end.
+
+cleanup_recv(P, D, State, Reason) ->
+ case State of
+ #recv{info = SelectInfo} ->
+ socket_cancel(P#params.socket, SelectInfo),
+ cleanup_recv_reply(P, D, [], Reason);
+ _ ->
+ cleanup_recv_reply(P, D, [], Reason)
+ end.
+
+cleanup_recv_reply(
+ P, #{show_econnreset := ShowEconnreset} = D, ActionsR, Reason) ->
+ case D of
+ #{active := false} -> ok;
+ #{active := _} ->
+ ModuleSocket = module_socket(P),
+ Owner = P#params.owner,
+%%% ?DBG({ModuleSocket, Reason}),
+ case Reason of
+ timeout ->
+ Owner ! {tcp_error, ModuleSocket, Reason},
+ ok;
+ closed ->
+ Owner ! {tcp_closed, ModuleSocket},
+ ok;
+ emsgsize ->
+ Owner ! {tcp_error, ModuleSocket, Reason},
+ ok;
+ econnreset when ShowEconnreset =:= false ->
+ Owner ! {tcp_closed, ModuleSocket},
+ ok;
+ _ ->
+ Owner ! {tcp_error, ModuleSocket, Reason},
+ Owner ! {tcp_closed, ModuleSocket},
+ ok
+ end
+ end,
+ {recv_stop(D#{active := false}),
+ case D of
+ #{recv_from := From} ->
+ Reason_1 =
+ case Reason of
+ econnreset when ShowEconnreset =:= false -> closed;
+ _ -> Reason
+ end,
+ [{reply, From, {error, Reason_1}},
+ {{timeout, recv}, cancel}
+ | ActionsR];
+ #{} ->
+ ActionsR
+ end}.
+
+%% Initialize packet recv state
+recv_start(D) ->
+ D#{recv_length => 0}.
+
+recv_stop(D) ->
+ maps:without([recv_from, recv_length], D).
+
+decode_packet(#{packet := Packet} = D) ->
+ case D of
+ #{packet := http, recv_httph := true} -> httph;
+ #{packet := http_bin, recv_httph := true} -> httph_bin;
+ #{packet := Packet} -> Packet
+ end.
+
+%% Deliver data and update the active state
+%% -> {NewD, NewActionsR}
+recv_data_deliver(
+ #params{owner = Owner} = P,
+ #{mode := Mode, header := Header, deliver := Deliver,
+ packet := Packet} = D,
+ ActionsR, Data) ->
+ %%
+ DeliverData = deliver_data(Data, Mode, Header, Packet),
+ case D of
+ #{recv_from := From} ->
+ {recv_stop(next_packet(D, Packet, Data)),
+ [{reply, From, {ok, DeliverData}},
+ {{timeout, recv}, cancel}
+ | ActionsR]};
+ #{active := false} ->
+ D_1 = D#{buffer := unrecv_buffer(Data, maps:get(buffer, D))},
+ {recv_stop(next_packet(D_1, Packet, Data)),
+ ActionsR};
+ #{active := Active} ->
+ ModuleSocket = module_socket(P),
+ Owner !
+ case Deliver of
+ term ->
+ {tag(Packet), ModuleSocket, DeliverData};
+ port ->
+ {ModuleSocket, {data, DeliverData}}
+ end,
+ case Active of
+ true ->
+ {recv_start(next_packet(D, Packet, Data)),
+ ActionsR};
+ once ->
+ {recv_stop(next_packet(D, Packet, Data, false)),
+ ActionsR};
+ 1 ->
+ Owner ! {tcp_passive, ModuleSocket},
+ {recv_stop(next_packet(D, Packet, Data, false)),
+ ActionsR};
+ N when is_integer(N) ->
+ {recv_start(next_packet(D, Packet, Data, Active - 1)),
+ ActionsR}
+ end
+ end.
+
+next_packet(D, Packet, Data) ->
+ if
+ Packet =:= http;
+ Packet =:= http_bin ->
+ case Data of
+ {http_request, _HttpMethod, _HttpUri, _HttpVersion} ->
+ D#{recv_httph => true};
+ {http_response, _HttpVersion, _Integer, _HttpString} ->
+ D#{recv_httph => true};
+ {http_header, _Integer, _HttpField, _Reserver, _Value} -> D;
+ http_eoh ->
+ D#{recv_httph => false};
+ {http_error, _HttpString} -> D
+ end;
+ true -> D
+ end.
+
+next_packet(D, Packet, Data, Active) ->
+ if
+ Packet =:= http;
+ Packet =:= http_bin ->
+ case Data of
+ {http_request, _HttpMethod, _HttpUri, _HttpVersion} ->
+ D#{recv_httph => true, active => Active};
+ {http_response, _HttpVersion, _Integer, _HttpString} ->
+ D#{recv_httph => true, active => Active};
+ {http_header, _Integer, _HttpField, _Reserver, _Value} ->
+ D#{active => Active};
+ http_eoh ->
+ D#{recv_httph => false, active => Active};
+ {http_error, _HttpString} ->
+ D#{active => Active}
+ end;
+ true ->
+ D#{active => Active}
+ end.
+
+catbin(<<>>, Bin) when is_binary(Bin) -> Bin;
+catbin(Bin, <<>>) when is_binary(Bin) -> Bin;
+catbin(Bin1, Bin2) when is_binary(Bin1), is_binary(Bin2) ->
+ <<Bin1/binary, Bin2/binary>>.
+
+unrecv_buffer(Data, Buffer) ->
+ case Buffer of
+ <<>> ->
+ Data;
+ _ when is_binary(Buffer) ->
+ [Data, Buffer];
+ _ ->
+ [Data | Buffer]
+ end.
+
+condense_buffer([Bin]) when is_binary(Bin) -> Bin;
+condense_buffer(Buffer) ->
+ iolist_to_binary(reverse_improper(Buffer, [])).
+
+deliver_data(Data, Mode, Header, Packet) ->
+ if
+ Packet =:= 1;
+ Packet =:= 2;
+ Packet =:= 4 ->
+ <<?header(Packet, _Size), Payload/binary>> = Data,
+ deliver_data(Payload, Mode, Header);
+ Packet =:= http;
+ Packet =:= http_bin;
+ Packet =:= httph;
+ Packet =:= httph_bin ->
+ Data;
+ true ->
+ deliver_data(Data, Mode, Header)
+ end.
+
+deliver_data(Data, list, _N) -> binary_to_list(Data);
+deliver_data(Data, binary, 0) -> Data;
+deliver_data(Data, binary, N) ->
+ case Data of
+ <<_:N/binary>> -> binary_to_list(Data);
+ <<Header:N/binary, Payload/binary>> ->
+ binary_to_list(Header) ++ Payload
+ end.
+
+tag(Packet) ->
+ if
+ Packet =:= http;
+ Packet =:= http_bin;
+ Packet =:= httph;
+ Packet =:= httph_bin ->
+ http;
+ true ->
+ tcp
+ end.
+
+%% -> {ok, NewD} | {{error, Reason}, D}
+state_setopts(_P, D, _State, []) ->
+ {ok, D};
+state_setopts(P, D, State, [Opt | Opts]) ->
+ Opt_1 = conv_setopt(Opt),
+ case setopt_categories(Opt_1) of
+ #{socket := _} ->
+ case P#params.socket of
+ undefined ->
+ {{error, closed}, D};
+ Socket ->
+ case socket_setopt(Socket, Opt_1) of
+ ok ->
+ state_setopts(P, D, State, Opts);
+ {error, _} = Error ->
+ {Error, D}
+ end
+ end;
+ %%
+ #{server_write := _} when State =:= 'closed' ->
+ {{error, einval}, D};
+ #{server_write := _} ->
+ state_setopts_server(P, D, State, Opts, Opt_1);
+ %%
+ #{server_read := _} when State =:= 'closed' ->
+ {{error, einval}, D};
+ #{server_read := _} when State =:= 'closed_read' ->
+ {{error, einval}, D};
+ #{server_read := _} ->
+ state_setopts_server(P, D, State, Opts, Opt_1);
+ %%
+ #{ignore := _} ->
+ state_setopts(P, D, State, Opts);
+ #{} -> % extra | einval
+ {{error, einval}, D}
+ end.
+
+state_setopts_server(P, D, State, Opts, {Tag, Value}) ->
+ case Tag of
+ active ->
+ state_setopts_active(P, D, State, Opts, Value);
+ packet ->
+ case is_packet_option_value(Value) of
+ true ->
+ case D of
+ #{recv_httph := _} ->
+ state_setopts(
+ P,
+ maps:remove(
+ recv_httph, D#{packet => Value}),
+ State, Opts);
+ #{} ->
+ state_setopts(
+ P, D#{packet => Value}, State, Opts)
+ end;
+ false ->
+ {{error, einval}, D}
+ end;
+ _ ->
+ state_setopts(P, D#{Tag => Value}, State, Opts)
+ end.
+
+state_setopts_active(P, D, State, Opts, Active) ->
+ if
+ Active =:= once;
+ Active =:= true ->
+ state_setopts(P, D#{active := Active}, State, Opts);
+ Active =:= false ->
+ case D of
+ #{active := OldActive} when is_integer(OldActive) ->
+ P#params.owner ! {tcp_passive, module_socket(P)},
+ ok;
+ #{active := _OldActive} -> ok
+ end,
+ state_setopts(P, D#{active := Active}, State, Opts);
+ is_integer(Active), -32768 =< Active, Active =< 32767 ->
+ N =
+ case D of
+ #{active := OldActive} when is_integer(OldActive) ->
+ OldActive + Active;
+ #{active := _OldActive} ->
+ Active
+ end,
+ if
+ 32767 < N ->
+ {{error, einval}, D};
+ N =< 0 ->
+ P#params.owner ! {tcp_passive, module_socket(P)},
+ state_setopts(P, D#{active := false}, State, Opts);
+ true ->
+ state_setopts(P, D#{active := N}, State, Opts)
+ end;
+ true ->
+ {{error, einval}, D}
+ end.
+
+%% -> {ok, [Options]} | {error, einval}
+state_getopts(P, D, State, Opts) ->
+ state_getopts(P, D, State, Opts, []).
+%%
+state_getopts(_P, _D, _State, [], Acc) ->
+ {ok, reverse(Acc)};
+state_getopts(P, D, State, [Tag | Tags], Acc) ->
+ case getopt_categories(Tag) of
+ #{socket := _} ->
+ case P#params.socket of
+ undefined ->
+ {error, closed};
+ Socket ->
+ case socket_getopt(Socket, Tag) of
+ {ok, Value} ->
+ state_getopts(
+ P, D, State, Tags, [{Tag, Value} | Acc]);
+ {error, _} ->
+ state_getopts(P, D, State, Tags, Acc)
+ end
+ end;
+ #{server_write := _} when State =:= 'closed' ->
+ {error, einval};
+ #{server_write := _} ->
+ Value = maps:get(Tag, D),
+ state_getopts(P, D, State, Tags, [{Tag, Value} | Acc]);
+ #{server_read := _} when State =:= 'closed' ->
+ {error, einval};
+ #{server_read := _} when State =:= 'closed_read' ->
+ {error, einval};
+ #{server_read := _} ->
+ Value = maps:get(Tag, D),
+ state_getopts(P, D, State, Tags, [{Tag, Value} | Acc]);
+ #{} -> % extra | einval
+ {error, einval}
+ end.
+
+
+getstat(Socket, D, What) ->
+ %% Read counters
+ Counters_1 = socket_info_counters(Socket),
+ %% Check for recent wraps
+ {D_1, Wrapped} = receive_counter_wrap(Socket, D, []),
+ %%
+ %% Assumption: a counter that we just now got a wrap message from
+ %% will not wrap again before we read the updated value
+ %%
+ %% Update wrapped counters
+ Counters_2 = socket_info_counters(Socket),
+ Counters_3 = maps:merge(Counters_1, maps:with(Wrapped, Counters_2)),
+ %% Go ahead with wrap updated counters
+ {D_1, getstat_what(What, D_1, Counters_3)}.
+
+getstat_what([], _D, _C) -> [];
+getstat_what([Tag | What], D, C) ->
+ Val =
+ case Tag of
+ recv_oct ->
+ counter_value(read_byte, D, C);
+ recv_cnt ->
+ counter_value(read_pkg, D, C);
+ recv_max ->
+ getstat_avg(read_byte, D, C, read_pkg);
+ recv_avg ->
+ getstat_avg(read_byte, D, C, read_pkg);
+ recv_dvi -> 0;
+ %%
+ send_oct ->
+ counter_value(write_byte, D, C);
+ send_cnt ->
+ counter_value(write_pkg, D, C);
+ send_max ->
+ getstat_avg(write_byte, D, C, write_pkg);
+ send_avg ->
+ getstat_avg(write_byte, D, C, write_pkg);
+ send_pend -> 0
+ end,
+ [{Tag, Val} | getstat_what(What, D, C)].
+
+getstat_avg(SumTag, D, C, CntTag) ->
+ Cnt = counter_value(CntTag, D, C),
+ if
+ Cnt =:= 0 ->
+ counter_value(SumTag, D, C);
+ true ->
+ round(counter_value(SumTag, D, C) / Cnt)
+ end.
+
+socket_info_counters(Socket) ->
+ #{counters := Counters} = socket:info(Socket),
+ Counters.
+
+receive_counter_wrap(Socket, D, Wrapped) ->
+ receive
+ ?socket_counter_wrap(Socket, Counter) ->
+ receive_counter_wrap(
+ Socket, wrap_counter(Counter, D) , [Counter | Wrapped])
+ after 0 ->
+ {D, Wrapped}
+ end.
+
+wrap_counter(Counter, D) ->
+ case D of
+ #{Counter := N} ->
+ D#{Counter := N + 1};
+ #{} ->
+ D#{Counter => 1}
+ end.
+
+-define(COUNTER_BITS, 32).
+counter_value(Counter, D, Counters) ->
+ case D of
+ #{Counter := Wraps} ->
+ (Wraps bsl ?COUNTER_BITS) + maps:get(Counter, Counters);
+ #{} ->
+ maps:get(Counter, Counters)
+ end.
+
+
+
+-compile({inline, [reverse/1]}).
+reverse([]) -> [];
+reverse([_] = L) -> L;
+reverse([A, B]) -> [B, A];
+reverse(L) -> lists:reverse(L).
+
+-compile({inline, [reverse/2]}).
+reverse([], L) -> L;
+reverse([A], L) -> [A | L];
+reverse([A, B], L) -> [B, A | L];
+reverse(L1, L2) -> lists:reverse(L1, L2).
+
+%% Reverse but allow improper list
+reverse_improper([H | T], Acc) ->
+ reverse_improper(T, [H | Acc]);
+reverse_improper([], Acc) -> Acc;
+reverse_improper(T, Acc) -> [T | Acc].
+
+
+%% -------------------------------------------------------------------------
+-ifdef(undefined).
+
+%% Better, Leaner, Faster, Smarter, than inet:timeout
+
+end_time(infinity) -> infinity;
+end_time(Timeout) when is_integer(Timeout), 0 =< Timeout ->
+ erlang:monotonic_time(millisecond) + Timeout.
+
+timeout(infinity) -> infinity;
+timeout(EndTime) ->
+ Time = erlang:monotonic_time(millisecond),
+ if
+ Time < EndTime ->
+ EndTime - Time;
+ true -> 0
+ end.
+
+-endif.
diff --git a/lib/kernel/src/global.erl b/lib/kernel/src/global.erl
index 3875074d74..ff6674cd08 100644
--- a/lib/kernel/src/global.erl
+++ b/lib/kernel/src/global.erl
@@ -909,7 +909,7 @@ handle_info({nodeup, Node}, S0) when S0#state.connect_all ->
end;
handle_info({whereis, Name, From}, S) ->
- do_whereis(Name, From),
+ _ = do_whereis(Name, From),
{noreply, S};
handle_info(known, S) ->
diff --git a/lib/kernel/src/global_group.erl b/lib/kernel/src/global_group.erl
index f5ead2a4c5..09b058af2f 100644
--- a/lib/kernel/src/global_group.erl
+++ b/lib/kernel/src/global_group.erl
@@ -1312,16 +1312,20 @@ get_own_nodes() ->
%%% -hidden command line argument
%%%====================================================================================
publish_arg() ->
- case init:get_argument(hidden) of
- {ok,[[]]} ->
- hidden;
- {ok,[["true"]]} ->
- hidden;
- _ ->
- normal
+ case net_kernel:dist_listen() of
+ false ->
+ hidden;
+ _ ->
+ case init:get_argument(hidden) of
+ {ok,[[]]} ->
+ hidden;
+ {ok,[["true"]]} ->
+ hidden;
+ _ ->
+ normal
+ end
end.
-
%%%====================================================================================
%%% Own group publication type and nodes
%%%====================================================================================
diff --git a/lib/kernel/src/group.erl b/lib/kernel/src/group.erl
index 5625ae6eb7..8410c1a4b5 100644
--- a/lib/kernel/src/group.erl
+++ b/lib/kernel/src/group.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -73,7 +73,7 @@ get_pids([], _Found, false) ->
start_shell({Mod,Func,Args}) ->
start_shell1(Mod, Func, Args);
start_shell({Node,Mod,Func,Args}) ->
- start_shell1(net, call, [Node,Mod,Func,Args]);
+ start_shell1(rpc, call, [Node,Mod,Func,Args]);
start_shell(Shell) when is_atom(Shell) ->
start_shell1(Shell, start, []);
start_shell(Shell) when is_function(Shell) ->
@@ -795,12 +795,6 @@ save_line({stack, U, _L, D}, Line) ->
{stack, U, Line, D}.
get_lines(Ls) -> get_all_lines(Ls).
-%get_lines({stack, U, {}, []}) ->
-% U;
-%get_lines({stack, U, {}, D}) ->
-% tl(lists:reverse(D, U));
-%get_lines({stack, U, L, D}) ->
-% get_lines({stack, U, {}, [L|D]}).
%% There's a funny behaviour whenever the line stack doesn't have a "\n"
%% at its end -- get_lines() seemed to work on the assumption it *will* be
diff --git a/lib/kernel/src/group_history.erl b/lib/kernel/src/group_history.erl
index 9745848992..1fab2ba14e 100644
--- a/lib/kernel/src/group_history.erl
+++ b/lib/kernel/src/group_history.erl
@@ -44,7 +44,7 @@ load() ->
wait_for_kernel_safe_sup(),
case history_status() of
enabled ->
- case open_log() of
+ try open_log() of
{ok, ?LOG_NAME} ->
read_full_log(?LOG_NAME);
{repaired, ?LOG_NAME, {recovered, Good}, {badbytes, Bad}} ->
@@ -68,6 +68,10 @@ load() ->
handle_open_error(Reason),
disable_history(),
[]
+ catch
+ % disk_log shut down abruptly, possibly because
+ % the node is shutting down. Ignore it.
+ exit:_ -> []
end;
_ ->
[]
@@ -127,11 +131,15 @@ repair_log(Name) ->
%% Return whether the shell history is enabled or not
-spec history_status() -> enabled | disabled.
history_status() ->
- case is_user() orelse application:get_env(kernel, shell_history) of
- true -> disabled; % don't run for user proc
- {ok, enabled} -> enabled;
- undefined -> ?DEFAULT_STATUS;
- _ -> disabled
+ %% Don't run for user proc or if the emulator's tearing down
+ Skip = is_user() orelse not init_running(),
+ case application:get_env(kernel, shell_history) of
+ {ok, enabled} when not Skip ->
+ enabled;
+ undefined when not Skip ->
+ ?DEFAULT_STATUS;
+ _ ->
+ disabled
end.
%% Return whether the user process is running this
@@ -142,6 +150,14 @@ is_user() ->
_ -> false
end.
+%% Return if the system is running (not stopping)
+-spec init_running() -> boolean().
+init_running() ->
+ case init:get_status() of
+ {stopping, _} -> false;
+ _ -> true
+ end.
+
%% Open a disk_log file while ensuring the required path is there.
open_log() ->
Opts = log_options(),
@@ -322,7 +338,7 @@ show_unexpected_warning({M,F,A}, Term) ->
show_unexpected_close_warning() ->
show('$#erlang-history-unexpected-close',
- "The shell log file has mysteriousy closed. Ignoring "
+ "The shell log file has mysteriously closed. Ignoring "
"currently unread history.~n", []).
show_size_warning(_Current, _New) ->
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index ba425df354..6ae5351652 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -39,6 +39,7 @@
-export([connect_options/2, listen_options/2, udp_options/2, sctp_options/2]).
-export([udp_module/1, tcp_module/1, tcp_module/2, sctp_module/1]).
+-export([gen_tcp_module/1]).
-export([i/0, i/1, i/2]).
@@ -82,6 +83,8 @@
%% imports
-import(lists, [append/1, duplicate/2, filter/2, foldl/3]).
+-define(DEFAULT_KERNEL_INET_BACKEND, inet). % inet | socket
+
%% Record Signature
-define(RS(Record),
{Record, record_info(size, Record)}).
@@ -125,7 +128,8 @@
'etimedout' |
'ewouldblock' |
'exbadport' | 'exbadseq' | file:posix().
--type socket() :: port().
+-type module_socket() :: {'$inet', Handler :: module(), Handle :: term()}.
+-type socket() :: port() | module_socket().
-type socket_setopt() ::
gen_sctp:option() | gen_tcp:option() | gen_udp:option().
@@ -208,6 +212,8 @@ close(Socket) ->
returned_non_ip_address()} |
{error, posix()}.
+peername({'$inet', GenSocketMod, _} = Socket) when is_atom(GenSocketMod) ->
+ GenSocketMod:?FUNCTION_NAME(Socket);
peername(Socket) ->
prim_inet:peername(Socket).
@@ -250,6 +256,8 @@ peernames(Socket, Assoc) ->
returned_non_ip_address()} |
{error, posix()}.
+sockname({'$inet', GenSocketMod, _} = Socket) when is_atom(GenSocketMod) ->
+ GenSocketMod:?FUNCTION_NAME(Socket);
sockname(Socket) ->
prim_inet:sockname(Socket).
@@ -290,6 +298,11 @@ socknames(Socket, Assoc) ->
Socket :: socket(),
Port :: port_number().
+port({'$inet', GenSocketMod, _} = Socket) when is_atom(GenSocketMod) ->
+ case GenSocketMod:sockname(Socket) of
+ {ok, {_, Port}} -> {ok, Port};
+ {error, _} = Error -> Error
+ end;
port(Socket) ->
case prim_inet:sockname(Socket) of
{ok, {_,Port}} -> {ok, Port};
@@ -306,6 +319,8 @@ send(Socket, Packet) ->
Socket :: socket(),
Options :: [socket_setopt()].
+setopts({'$inet', GenSocketMod, _} = Socket, Opts) when is_atom(GenSocketMod) ->
+ GenSocketMod:?FUNCTION_NAME(Socket, Opts);
setopts(Socket, Opts) ->
SocketOpts =
[case Opt of
@@ -322,6 +337,9 @@ setopts(Socket, Opts) ->
Options :: [socket_getopt()],
OptionValues :: [socket_setopt() | gen_tcp:pktoptions_value()].
+getopts({'$inet', GenSocketMod, _} = Socket, Opts)
+ when is_atom(GenSocketMod) ->
+ GenSocketMod:?FUNCTION_NAME(Socket, Opts);
getopts(Socket, Opts) ->
case prim_inet:getopts(Socket, Opts) of
{ok,OptionValues} ->
@@ -479,6 +497,8 @@ popf(_Socket) ->
-spec gethostname() -> {'ok', Hostname} when
Hostname :: string().
+%%% XXX gethostname() -> net:gethostname().
+
gethostname() ->
case inet_udp:open(0,[]) of
{ok,U} ->
@@ -502,7 +522,7 @@ gethostname(Socket) ->
OptionValues :: [{stat_option(), integer()}].
getstat(Socket) ->
- prim_inet:getstat(Socket, stats()).
+ getstat(Socket, stats()).
-spec getstat(Socket, Options) ->
{ok, OptionValues} | {error, posix()} when
@@ -510,7 +530,10 @@ getstat(Socket) ->
Options :: [stat_option()],
OptionValues :: [{stat_option(), integer()}].
-getstat(Socket,What) ->
+getstat({'$inet', GenSocketMod, _} = Socket, What)
+ when is_atom(GenSocketMod) ->
+ GenSocketMod:?FUNCTION_NAME(Socket, What);
+getstat(Socket, What) ->
prim_inet:getstat(Socket, What).
-spec gethostbyname(Hostname) -> {ok, Hostent} | {error, posix()} when
@@ -908,6 +931,19 @@ tcp_module_1(Opts, Address) ->
Opts, tcp_module, Address,
#{inet => inet_tcp, inet6 => inet6_tcp, local => local_tcp}).
+gen_tcp_module([{inet_backend, Flag}|Opts]) ->
+ gen_tcp_module(Opts, Flag);
+gen_tcp_module(Opts) ->
+ gen_tcp_module(
+ Opts,
+ persistent_term:get(
+ {kernel, inet_backend}, ?DEFAULT_KERNEL_INET_BACKEND)).
+%%
+gen_tcp_module(Opts, inet) ->
+ {gen_tcp, Opts};
+gen_tcp_module(Opts, socket) ->
+ {gen_tcp_socket, Opts}.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Available options for udp:open
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1177,7 +1213,6 @@ mod(Opts, Tag, Address, Map, undefined, Acc, M) ->
mod(Opts, Tag, Address, Map, Mod, Acc, _M) ->
mod(Opts, Tag, Address, Map, Mod, Acc).
-
getaddrs_tm({A,B,C,D} = IP, Fam, _) ->
%% Only "syntactic" validation and check of family.
if
@@ -1399,7 +1434,7 @@ gethostbyaddr_tm_native(Addr, Timer, Opts) ->
Family :: address_family(),
Type :: socket_type(),
Module :: atom()) ->
- {'ok', socket()} | {'error', posix()}.
+ {'ok', port()} | {'error', posix()}.
open(FdO, Addr, Port, Opts, Protocol, Family, Type, Module)
when is_integer(FdO), FdO < 0;
diff --git a/lib/kernel/src/inet6_tcp_dist.erl b/lib/kernel/src/inet6_tcp_dist.erl
index 9b6c2745d5..f89d37a9ab 100644
--- a/lib/kernel/src/inet6_tcp_dist.erl
+++ b/lib/kernel/src/inet6_tcp_dist.erl
@@ -21,8 +21,8 @@
%% Handles the connection setup phase with other Erlang nodes.
--export([listen/1, accept/1, accept_connection/5,
- setup/5, close/1, select/1, is_node_name/1]).
+-export([listen/2, accept/1, accept_connection/5,
+ setup/5, close/1, select/1, address/0, is_node_name/1]).
-export([setopts/2, getopts/2]).
@@ -35,12 +35,20 @@ select(Node) ->
inet_tcp_dist:gen_select(inet6_tcp, Node).
%% ------------------------------------------------------------
+%% Get address family
+%% address() => #net_address{}
+%% ------------------------------------------------------------
+
+address() ->
+ inet_tcp_dist:gen_address(inet6_tcp).
+
+%% ------------------------------------------------------------
%% Create the listen socket, i.e. the port that this erlang
%% node is accessible through.
%% ------------------------------------------------------------
-listen(Name) ->
- inet_tcp_dist:gen_listen(inet6_tcp, Name).
+listen(Name, Host) ->
+ inet_tcp_dist:gen_listen(inet6_tcp, Name, Host).
%% ------------------------------------------------------------
%% Accepts new connection attempts from other Erlang nodes.
diff --git a/lib/kernel/src/inet6_udp.erl b/lib/kernel/src/inet6_udp.erl
index 7ebdb3d301..b8c33e91ec 100644
--- a/lib/kernel/src/inet6_udp.erl
+++ b/lib/kernel/src/inet6_udp.erl
@@ -44,10 +44,10 @@ getaddr(Address, Timer) -> inet:getaddr(Address, ?FAMILY, Timer).
%% inet_udp special this side addresses
translate_ip(IP) -> inet:translate_ip(IP, ?FAMILY).
--spec open(_) -> {ok, inet:socket()} | {error, atom()}.
+-spec open(_) -> {ok, port()} | {error, atom()}.
open(Port) -> open(Port, []).
--spec open(_, _) -> {ok, inet:socket()} | {error, atom()}.
+-spec open(_, _) -> {ok, port()} | {error, atom()}.
open(Port, Opts) ->
case inet:udp_options(
[{port,Port} | Opts],
@@ -91,7 +91,7 @@ recv(S, Len) ->
recv(S, Len, Time) ->
prim_inet:recvfrom(S, Len, Time).
--spec close(inet:socket()) -> ok.
+-spec close(port()) -> ok.
close(S) ->
inet:udp_close(S).
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
index 630ef5d2f7..ff5bdd8f26 100644
--- a/lib/kernel/src/inet_db.erl
+++ b/lib/kernel/src/inet_db.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -44,7 +44,8 @@
del_socks_methods/1, del_socks_methods/0,
add_socks_noproxy/1, del_socks_noproxy/1]).
-export([set_cache_size/1, set_cache_refresh/1]).
--export([set_timeout/1, set_retry/1, set_inet6/1, set_usevc/1]).
+-export([set_timeout/1, set_retry/1, set_servfail_retry_timeout/1,
+ set_inet6/1, set_usevc/1]).
-export([set_edns/1, set_udp_payload_size/1]).
-export([set_resolv_conf/1, set_hosts_file/1, get_hosts_file/0]).
-export([tcp_module/0, set_tcp_module/1]).
@@ -221,6 +222,9 @@ set_timeout(Time) -> res_option(timeout, Time).
set_retry(N) -> res_option(retry, N).
+set_servfail_retry_timeout(Time) when is_integer(Time) andalso (Time >= 0) ->
+ res_option(servfail_retry_timeout, Time).
+
set_inet6(Bool) -> res_option(inet6, Bool).
set_usevc(Bool) -> res_option(usevc, Bool).
@@ -229,12 +233,14 @@ set_edns(Version) -> res_option(edns, Version).
set_udp_payload_size(Size) -> res_option(udp_payload_size, Size).
-set_resolv_conf(Fname) -> res_option(resolv_conf, Fname).
+set_resolv_conf(Fname) when is_list(Fname) ->
+ res_option(resolv_conf, Fname).
-set_hosts_file(Fname) -> res_option(hosts_file, Fname).
+set_hosts_file(Fname) when is_list(Fname) ->
+ res_option(hosts_file, Fname).
get_hosts_file() ->
- get_rc_hosts([], [], inet_hosts_file_byname).
+ get_rc_hosts([], [], inet_hosts_file_byaddr).
%% set socks options
set_socks_server(Server) -> call({set_socks_server, Server}).
@@ -311,42 +317,104 @@ valid_lookup() -> [dns, file, yp, nis, nisplus, native].
%% Reconstruct an inetrc sturcture from inet_db
get_rc() ->
get_rc([hosts, domain, nameservers, search, alt_nameservers,
- timeout, retry, inet6, usevc,
+ timeout, retry, servfail_retry_timeout, inet6, usevc,
edns, udp_payload_size, resolv_conf, hosts_file,
socks5_server, socks5_port, socks5_methods, socks5_noproxy,
udp, sctp, tcp, host, cache_size, cache_refresh, lookup], []).
get_rc([K | Ks], Ls) ->
case K of
- hosts -> get_rc_hosts(Ks, Ls, inet_hosts_byname);
- domain -> get_rc(domain, res_domain, "", Ks, Ls);
- nameservers -> get_rc_ns(db_get(res_ns),nameservers,Ks,Ls);
- alt_nameservers -> get_rc_ns(db_get(res_alt_ns),alt_nameservers,Ks,Ls);
- search -> get_rc(search, res_search, [], Ks, Ls);
- timeout -> get_rc(timeout,res_timeout,?RES_TIMEOUT, Ks,Ls);
- retry -> get_rc(retry, res_retry, ?RES_RETRY, Ks, Ls);
- inet6 -> get_rc(inet6, res_inet6, false, Ks, Ls);
- usevc -> get_rc(usevc, res_usevc, false, Ks, Ls);
- edns -> get_rc(edns, res_edns, false, Ks, Ls);
- udp_payload_size -> get_rc(udp_payload_size, res_udp_payload_size,
- ?DNS_UDP_PAYLOAD_SIZE, Ks, Ls);
- resolv_conf -> get_rc(resolv_conf, res_resolv_conf, undefined, Ks, Ls);
- hosts_file -> get_rc(hosts_file, res_hosts_file, undefined, Ks, Ls);
- tcp -> get_rc(tcp, tcp_module, ?DEFAULT_TCP_MODULE, Ks, Ls);
- udp -> get_rc(udp, udp_module, ?DEFAULT_UDP_MODULE, Ks, Ls);
- sctp -> get_rc(sctp, sctp_module, ?DEFAULT_SCTP_MODULE, Ks, Ls);
- lookup -> get_rc(lookup, res_lookup, [native,file], Ks, Ls);
- cache_size -> get_rc(cache_size, cache_size, ?CACHE_LIMIT, Ks, Ls);
- cache_refresh ->
- get_rc(cache_refresh, cache_refresh_interval,?CACHE_REFRESH,Ks,Ls);
- socks5_server -> get_rc(socks5_server, socks5_server, "", Ks, Ls);
- socks5_port -> get_rc(socks5_port,socks5_port,?IPPORT_SOCKS,Ks,Ls);
- socks5_methods -> get_rc(socks5_methods,socks5_methods,[none],Ks,Ls);
- socks5_noproxy ->
- case db_get(socks5_noproxy) of
- [] -> get_rc(Ks, Ls);
- NoProxy -> get_rc_noproxy(NoProxy, Ks, Ls)
- end;
+ hosts -> get_rc_hosts(Ks, Ls, inet_hosts_byaddr);
+ domain -> get_rc(domain,
+ res_domain,
+ "",
+ Ks, Ls);
+ nameservers -> get_rc_ns(db_get(res_ns),
+ nameservers,
+ Ks, Ls);
+ alt_nameservers -> get_rc_ns(db_get(res_alt_ns),
+ alt_nameservers,
+ Ks, Ls);
+ search -> get_rc(search,
+ res_search,
+ [],
+ Ks, Ls);
+ timeout -> get_rc(timeout,
+ res_timeout,
+ ?RES_TIMEOUT,
+ Ks, Ls);
+ retry -> get_rc(retry,
+ res_retry,
+ ?RES_RETRY,
+ Ks, Ls);
+ servfail_retry_timeout -> get_rc(servfail_retry_timeout,
+ res_servfail_retry_timeout,
+ ?RES_SERVFAIL_RETRY_TO,
+ Ks, Ls);
+ inet6 -> get_rc(inet6,
+ res_inet6,
+ false,
+ Ks, Ls);
+ usevc -> get_rc(usevc,
+ res_usevc,
+ false,
+ Ks, Ls);
+ edns -> get_rc(edns,
+ res_edns,
+ false,
+ Ks, Ls);
+ udp_payload_size -> get_rc(udp_payload_size,
+ res_udp_payload_size,
+ ?DNS_UDP_PAYLOAD_SIZE,
+ Ks, Ls);
+ resolv_conf -> get_rc(resolv_conf,
+ res_resolv_conf,
+ undefined,
+ Ks, Ls);
+ hosts_file -> get_rc(hosts_file,
+ res_hosts_file,
+ undefined,
+ Ks, Ls);
+ tcp -> get_rc(tcp,
+ tcp_module,
+ ?DEFAULT_TCP_MODULE,
+ Ks, Ls);
+ udp -> get_rc(udp,
+ udp_module,
+ ?DEFAULT_UDP_MODULE,
+ Ks, Ls);
+ sctp -> get_rc(sctp,
+ sctp_module,
+ ?DEFAULT_SCTP_MODULE,
+ Ks, Ls);
+ lookup -> get_rc(lookup,
+ res_lookup,
+ [native, file],
+ Ks, Ls);
+ cache_size -> get_rc(cache_size,
+ cache_size,
+ ?CACHE_LIMIT,
+ Ks, Ls);
+ cache_refresh -> get_rc(cache_refresh,
+ cache_refresh_interval,
+ ?CACHE_REFRESH,
+ Ks, Ls);
+ socks5_server -> get_rc(socks5_server,
+ socks5_server,
+ "",
+ Ks, Ls);
+ socks5_port -> get_rc(socks5_port,
+ socks5_port,
+ ?IPPORT_SOCKS,
+ Ks, Ls);
+ socks5_methods -> get_rc(socks5_methods,
+ socks5_methods,
+ [none],
+ Ks, Ls);
+ socks5_noproxy -> case db_get(socks5_noproxy) of
+ [] -> get_rc(Ks, Ls);
+ NoProxy -> get_rc_noproxy(NoProxy, Ks, Ls)
+ end;
_ ->
get_rc(Ks, Ls)
end;
@@ -371,18 +439,11 @@ get_rc_ns([], _Tag, Ks, Ls) ->
get_rc(Ks, Ls).
get_rc_hosts(Ks, Ls, Tab) ->
- case lists:keysort(3, ets:tab2list(Tab)) of
+ case ets:tab2list(Tab) of
[] -> get_rc(Ks, Ls);
- [{N,_,IP}|Hosts] -> get_rc_hosts(Ks, Ls, IP, Hosts, [N])
+ Hosts -> get_rc(Ks, [ [{host, IP, Names} || {{_Fam, IP}, Names} <- Hosts] | Ls])
end.
-get_rc_hosts(Ks, Ls, IP, [], Ns) ->
- get_rc(Ks, [{host,IP,lists:reverse(Ns)}|Ls]);
-get_rc_hosts(Ks, Ls, IP, [{N,_,IP}|Hosts], Ns) ->
- get_rc_hosts(Ks, Ls, IP, Hosts, [N|Ns]);
-get_rc_hosts(Ks, Ls, IP, [{N,_,NewIP}|Hosts], Ns) ->
- [{host,IP,lists:reverse(Ns)}|get_rc_hosts(Ks, Ls, NewIP, Hosts, [N])].
-
%%
%% Resolver options
%%
@@ -420,6 +481,7 @@ res_optname(lookup) -> res_lookup;
res_optname(recurse) -> res_recurse;
res_optname(search) -> res_search;
res_optname(retry) -> res_retry;
+res_optname(servfail_retry_timeout) -> res_servfail_retry_timeout;
res_optname(timeout) -> res_timeout;
res_optname(inet6) -> res_inet6;
res_optname(usevc) -> res_usevc;
@@ -453,6 +515,7 @@ res_check_option(recurse, R) when is_boolean(R) -> true;
res_check_option(search, SearchList) ->
res_check_list(SearchList, fun res_check_search/1);
res_check_option(retry, N) when is_integer(N), N > 0 -> true;
+res_check_option(servfail_retry_timeout, T) when is_integer(T), T >= 0 -> true;
res_check_option(timeout, T) when is_integer(T), T > 0 -> true;
res_check_option(inet6, Bool) when is_boolean(Bool) -> true;
res_check_option(usevc, Bool) when is_boolean(Bool) -> true;
@@ -502,49 +565,26 @@ socks_option(noproxy) -> db_get(socks5_noproxy).
gethostname() -> db_get(hostname).
res_update_conf() ->
- res_update(res_resolv_conf, res_resolv_conf_tm, res_resolv_conf_info,
- set_resolv_conf_tm, fun set_resolv_conf/1).
+ res_update(resolv_conf, res_resolv_conf_tm).
res_update_hosts() ->
- res_update(res_hosts_file, res_hosts_file_tm, res_hosts_file_info,
- set_hosts_file_tm, fun set_hosts_file/1).
+ res_update(hosts_file, res_hosts_file_tm).
-res_update(Tag, TagTm, TagInfo, TagSetTm, SetFun) ->
+res_update(Option, TagTm) ->
case db_get(TagTm) of
undefined -> ok;
- TM ->
+ Tm ->
case times() of
- Now when Now >= TM + ?RES_FILE_UPDATE_TM ->
- case db_get(Tag) of
- undefined ->
- SetFun("");
- "" ->
- SetFun("");
- File ->
- case erl_prim_loader:read_file_info(File) of
- {ok, Finfo0} ->
- Finfo =
- Finfo0#file_info{access = undefined,
- atime = undefined},
- case db_get(TagInfo) of
- Finfo ->
- call({TagSetTm, Now});
- _ ->
- SetFun(File)
- end;
- _ ->
- call({TagSetTm, Now}),
- error
- end
- end;
+ Now when Now >= Tm + ?RES_FILE_UPDATE_TM ->
+ %% Enough time has passed - request server to update
+ res_option(Option, Tm);
_ -> ok
end
end.
db_get(Name) ->
- case ets:lookup(inet_db, Name) of
- [] -> undefined;
- [{_,Val}] -> Val
+ try ets:lookup_element(inet_db, Name, 2)
+ catch error:badarg -> undefined
end.
add_rr(RR) ->
@@ -815,6 +855,7 @@ lookup_socket(Socket) when is_port(Socket) ->
%% res_usevc Bool - use tcp only
%% res_id Integer - NS query identifier
%% res_retry Integer - Retry count for UDP query
+%% res_servfail_retry_timeout Integer - Timeout to next query after a failure
%% res_timeout Integer - UDP query timeout before retry
%% res_inet6 Bool - address family inet6 for gethostbyname/1
%% res_usevc Bool - use Virtual Circuit (TCP)
@@ -849,16 +890,21 @@ lookup_socket(Socket) when is_port(Socket) ->
init([]) ->
process_flag(trap_exit, true),
+ case application:get_env(kernel, inet_backend) of
+ {ok, Flag}
+ when Flag =:= inet;
+ Flag =:= socket ->
+ persistent_term:put({kernel, inet_backend}, Flag);
+ _ -> ok
+ end,
Db = ets:new(inet_db, [public, named_table]),
reset_db(Db),
CacheOpts = [public, bag, {keypos,#dns_rr.domain}, named_table],
Cache = ets:new(inet_cache, CacheOpts),
- BynameOpts = [protected, bag, named_table, {keypos,1}],
- ByaddrOpts = [protected, bag, named_table, {keypos,3}],
- HostsByname = ets:new(inet_hosts_byname, BynameOpts),
- HostsByaddr = ets:new(inet_hosts_byaddr, ByaddrOpts),
- HostsFileByname = ets:new(inet_hosts_file_byname, BynameOpts),
- HostsFileByaddr = ets:new(inet_hosts_file_byaddr, ByaddrOpts),
+ HostsByname = ets:new(inet_hosts_byname, [named_table]),
+ HostsByaddr = ets:new(inet_hosts_byaddr, [named_table]),
+ HostsFileByname = ets:new(inet_hosts_file_byname, [named_table]),
+ HostsFileByaddr = ets:new(inet_hosts_file_byaddr, [named_table]),
{ok, #state{db = Db,
cache = Cache,
hosts_byname = HostsByname,
@@ -868,29 +914,32 @@ init([]) ->
cache_timer = init_timer() }}.
reset_db(Db) ->
- ets:insert(Db, {hostname, []}),
- ets:insert(Db, {res_ns, []}),
- ets:insert(Db, {res_alt_ns, []}),
- ets:insert(Db, {res_search, []}),
- ets:insert(Db, {res_domain, ""}),
- ets:insert(Db, {res_lookup, []}),
- ets:insert(Db, {res_recurse, true}),
- ets:insert(Db, {res_usevc, false}),
- ets:insert(Db, {res_id, 0}),
- ets:insert(Db, {res_retry, ?RES_RETRY}),
- ets:insert(Db, {res_timeout, ?RES_TIMEOUT}),
- ets:insert(Db, {res_inet6, false}),
- ets:insert(Db, {res_edns, false}),
- ets:insert(Db, {res_udp_payload_size, ?DNS_UDP_PAYLOAD_SIZE}),
- ets:insert(Db, {cache_size, ?CACHE_LIMIT}),
- ets:insert(Db, {cache_refresh_interval,?CACHE_REFRESH}),
- ets:insert(Db, {socks5_server, ""}),
- ets:insert(Db, {socks5_port, ?IPPORT_SOCKS}),
- ets:insert(Db, {socks5_methods, [none]}),
- ets:insert(Db, {socks5_noproxy, []}),
- ets:insert(Db, {tcp_module, ?DEFAULT_TCP_MODULE}),
- ets:insert(Db, {udp_module, ?DEFAULT_UDP_MODULE}),
- ets:insert(Db, {sctp_module, ?DEFAULT_SCTP_MODULE}).
+ ets:insert(
+ Db,
+ [{hostname, []},
+ {res_ns, []},
+ {res_alt_ns, []},
+ {res_search, []},
+ {res_domain, ""},
+ {res_lookup, []},
+ {res_recurse, true},
+ {res_usevc, false},
+ {res_id, 0},
+ {res_retry, ?RES_RETRY},
+ {res_servfail_retry_timeout, ?RES_SERVFAIL_RETRY_TO},
+ {res_timeout, ?RES_TIMEOUT},
+ {res_inet6, false},
+ {res_edns, false},
+ {res_udp_payload_size, ?DNS_UDP_PAYLOAD_SIZE},
+ {cache_size, ?CACHE_LIMIT},
+ {cache_refresh_interval,?CACHE_REFRESH},
+ {socks5_server, ""},
+ {socks5_port, ?IPPORT_SOCKS},
+ {socks5_methods, [none]},
+ {socks5_noproxy, []},
+ {tcp_module, ?DEFAULT_TCP_MODULE},
+ {udp_module, ?DEFAULT_UDP_MODULE},
+ {sctp_module, ?DEFAULT_SCTP_MODULE}]).
%%----------------------------------------------------------------------
%% Func: handle_call/3
@@ -908,22 +957,7 @@ reset_db(Db) ->
handle_call(Request, From, #state{db=Db}=State) ->
case Request of
{load_hosts_file,IPNmAs} when is_list(IPNmAs) ->
- NIPs =
- lists:flatten(
- [ [{N,
- if tuple_size(IP) =:= 4 -> inet;
- tuple_size(IP) =:= 8 -> inet6
- end,IP} || N <- [Nm|As]]
- || {IP,Nm,As} <- IPNmAs]),
- Byname = State#state.hosts_file_byname,
- Byaddr = State#state.hosts_file_byaddr,
- ets:delete_all_objects(Byname),
- ets:delete_all_objects(Byaddr),
- %% Byname has lowercased names while Byaddr keep the name casing.
- %% This is to be able to reconstruct the original
- %% /etc/hosts entry.
- ets:insert(Byname, [{tolower(N),Type,IP} || {N,Type,IP} <- NIPs]),
- ets:insert(Byaddr, NIPs),
+ load_hosts_list(IPNmAs, State#state.hosts_file_byname, State#state.hosts_file_byaddr),
{reply, ok, State};
{add_host,{A,B,C,D}=IP,[N|As]=Names}
@@ -969,7 +1003,7 @@ handle_call(Request, From, #state{db=Db}=State) ->
case res_check_option(Opt, El) of
true ->
Optname = res_optname(Opt),
- [{_,Es}] = ets:lookup(Db, Optname),
+ Es = ets:lookup_element(Db, Optname, 2),
NewEs = case Op of
ins -> [E | lists_delete(E, Es)];
add -> lists_delete(E, Es) ++ El;
@@ -1003,12 +1037,12 @@ handle_call(Request, From, #state{db=Db}=State) ->
Option, Fname, res_resolv_conf_tm, res_resolv_conf_info,
undefined, From, State);
- {res_set, hosts_file=Option, Fname} ->
+ {res_set, hosts_file=Option, Fname_or_Tm} ->
handle_set_file(
- Option, Fname, res_hosts_file_tm, res_hosts_file_info,
- fun (Bin) ->
+ Option, Fname_or_Tm, res_hosts_file_tm, res_hosts_file_info,
+ fun (File, Bin) ->
case inet_parse:hosts(
- Fname, {chars,Bin}) of
+ File, {chars,Bin}) of
{ok,Opts} ->
[{load_hosts_file,Opts}];
_ -> error
@@ -1016,12 +1050,12 @@ handle_call(Request, From, #state{db=Db}=State) ->
end,
From, State);
%%
- {res_set, resolv_conf=Option, Fname} ->
+ {res_set, resolv_conf=Option, Fname_or_Tm} ->
handle_set_file(
- Option, Fname, res_resolv_conf_tm, res_resolv_conf_info,
- fun (Bin) ->
+ Option, Fname_or_Tm, res_resolv_conf_tm, res_resolv_conf_info,
+ fun (File, Bin) ->
case inet_parse:resolv(
- Fname, {chars,Bin}) of
+ File, {chars,Bin}) of
{ok,Opts} ->
Search =
lists:foldl(
@@ -1075,13 +1109,13 @@ handle_call(Request, From, #state{db=Db}=State) ->
{reply, ok, State};
{add_socks_methods, Ls} ->
- [{_,As}] = ets:lookup(Db, socks5_methods),
+ As = ets:lookup_element(Db, socks5_methods, 2),
As1 = lists_subtract(As, Ls),
ets:insert(Db, {socks5_methods, As1 ++ Ls}),
{reply, ok, State};
{del_socks_methods, Ls} ->
- [{_,As}] = ets:lookup(Db, socks5_methods),
+ As = ets:lookup_element(Db, socks5_methods, 2),
As1 = lists_subtract(As, Ls),
case lists:member(none, As1) of
false -> ets:insert(Db, {socks5_methods, As1 ++ [none]});
@@ -1095,12 +1129,12 @@ handle_call(Request, From, #state{db=Db}=State) ->
{add_socks_noproxy, {{A,B,C,D},{MA,MB,MC,MD}}}
when ?ip(A,B,C,D), ?ip(MA,MB,MC,MD) ->
- [{_,As}] = ets:lookup(Db, socks5_noproxy),
+ As = ets:lookup_element(Db, socks5_noproxy, 2),
ets:insert(Db, {socks5_noproxy, As++[{{A,B,C,D},{MA,MB,MC,MD}}]}),
{reply, ok, State};
{del_socks_noproxy, {A,B,C,D}=IP} when ?ip(A,B,C,D) ->
- [{_,As}] = ets:lookup(Db, socks5_noproxy),
+ As = ets:lookup_element(Db, socks5_noproxy, 2),
ets:insert(Db, {socks5_noproxy, lists_keydelete(IP, 1, As)}),
{reply, ok, State};
@@ -1194,68 +1228,283 @@ terminate(_Reason, State) ->
%%% Internal functions
%%%----------------------------------------------------------------------
-handle_set_file(Option, Fname, TagTm, TagInfo, ParseFun, From,
- #state{db=Db}=State) ->
+handle_set_file(
+ Option, Tm, TagTm, TagInfo, ParseFun, From, #state{db=Db}=State)
+ when is_integer(Tm) ->
+ %%
+ %% Maybe update file content
+ %%
+ try ets:lookup_element(Db, TagTm, 2) of
+ Tm ->
+ %% Current update request
+ File = ets:lookup_element(Db, res_optname(Option), 2),
+ Finfo = ets:lookup_element(Db, TagInfo, 2),
+ handle_update_file(
+ Finfo, File, TagTm, TagInfo, ParseFun, From, State);
+ _ ->
+ %% Late request - ignore update
+ {reply, ok, State}
+ catch error:badarg ->
+ %% Option no longer set - ignore update
+ {reply, ok, State}
+ end;
+handle_set_file(
+ Option, Fname, TagTm, TagInfo, ParseFun, From, #state{db=Db}=State) ->
case res_check_option(Option, Fname) of
true when Fname =:= "" ->
+ %% Delete file content and monitor
ets:insert(Db, {res_optname(Option), Fname}),
ets:delete(Db, TagInfo),
ets:delete(Db, TagTm),
- handle_set_file(ParseFun, <<>>, From, State);
+ handle_set_file(ParseFun, Fname, <<>>, From, State);
true when ParseFun =:= undefined ->
+ %% Set file name and monitor
File = filename:flatten(Fname),
ets:insert(Db, {res_optname(Option), File}),
ets:insert(Db, {TagInfo, undefined}),
- TimeZero = - (?RES_FILE_UPDATE_TM + 1), % Early enough
+ TimeZero = times() - (?RES_FILE_UPDATE_TM + 1), % Early enough
ets:insert(Db, {TagTm, TimeZero}),
{reply,ok,State};
true ->
+ %% Set file name and monitor, read content
File = filename:flatten(Fname),
ets:insert(Db, {res_optname(Option), File}),
- Bin =
- case erl_prim_loader:read_file_info(File) of
- {ok, Finfo0} ->
- Finfo = Finfo0#file_info{access = undefined,
- atime = undefined},
- ets:insert(Db, {TagInfo, Finfo}),
- ets:insert(Db, {TagTm, times()}),
- case erl_prim_loader:get_file(File) of
- {ok, B, _} -> B;
- _ -> <<>>
- end;
- _ ->
- ets:insert(Db, {TagInfo, undefined}),
- TimeZero = - (?RES_FILE_UPDATE_TM + 1), % Early enough
- ets:insert(Db, {TagTm, TimeZero}),
- <<>>
- end,
- handle_set_file(ParseFun, Bin, From, State);
+ handle_update_file(
+ undefined, File, TagTm, TagInfo, ParseFun, From, State);
false -> {reply,error,State}
end.
-handle_set_file(ParseFun, Bin, From, State) ->
- case ParseFun(Bin) of
+handle_set_file(ParseFun, File, Bin, From, State) ->
+ case ParseFun(File, Bin) of
error ->
{reply,error,State};
Opts ->
handle_rc_list(Opts, From, State)
end.
+handle_update_file(
+ Finfo, File, TagTm, TagInfo, ParseFun, From, #state{db = Db} = State) ->
+ %%
+ %% Update file content if file has been updated
+ %%
+ case erl_prim_loader:read_file_info(File) of
+ {ok, Finfo} ->
+ %% No file update - we are done
+ {reply, ok, State};
+ {ok, Finfo_1} ->
+ %% File updated - read content
+ ets:insert(Db, {TagInfo, Finfo_1}),
+ ets:insert(Db, {TagTm, times()}),
+ Bin =
+ case erl_prim_loader:get_file(File) of
+ {ok, B, _} -> B;
+ _ -> <<>>
+ end,
+ handle_set_file(ParseFun, File, Bin, From, State);
+ _ ->
+ %% No file - clear content and reset monitor
+ ets:insert(Db, {TagInfo, undefined}),
+ ets:insert(Db, {TagTm, times()}),
+ handle_set_file(ParseFun, File, <<>>, From, State)
+ end.
+
%% Byname has lowercased names while Byaddr keep the name casing.
%% This is to be able to reconstruct the original /etc/hosts entry.
do_add_host(Byname, Byaddr, Names, Type, IP) ->
- do_del_host(Byname, Byaddr, IP),
- ets:insert(Byname, [{tolower(N),Type,IP} || N <- Names]),
- ets:insert(Byaddr, [{N,Type,IP} || N <- Names]),
+ Nms = [tolower(Nm) || Nm <- Names],
+ add_ip_bynms(Byname, Type, IP, Nms, Names),
+ Key = {Type, IP},
+ try ets:lookup_element(Byaddr, Key, 2) of
+ Names_0 ->
+ %% Delete IP address from byname entries
+ NmsSet = % Set of new tolower(Name)s
+ lists:foldl(
+ fun (Nm, Set) ->
+ maps:put(Nm, [], Set)
+ end, #{}, Nms),
+ del_ip_bynms(
+ Byname, Type, IP,
+ [Nm || Nm <- [tolower(Name) || Name <- Names_0],
+ not maps:is_key(Nm, NmsSet)])
+ catch error:badarg ->
+ ok
+ end,
+ %% Replace the entry in the byaddr table
+ ets:insert(Byaddr, {Key, Names}),
ok.
do_del_host(Byname, Byaddr, IP) ->
- _ =
- [ets:delete_object(Byname, {tolower(Name),Type,Addr}) ||
- {Name,Type,Addr} <- ets:lookup(Byaddr, IP)],
- ets:delete(Byaddr, IP),
+ Fam = inet_family(IP),
+ Key = {Fam, IP},
+ try ets:lookup_element(Byaddr, Key, 2) of
+ Names ->
+ %% Delete IP address from byname entries
+ del_ip_bynms(
+ Byname, Fam, IP,
+ [tolower(Name) || Name <- Names]),
+ %% Delete from byaddr table
+ true = ets:delete(Byaddr, Key),
+ ok
+ catch error:badarg ->
+ ok
+ end.
+
+
+add_ip_bynms(Byname, Fam, IP, Nms, Names) ->
+ lists:foreach(
+ fun (Nm) ->
+ Key = {Fam, Nm},
+ case ets:lookup(Byname, Key) of
+ [{_Key, [IP | _] = IPs, _Names_1}] ->
+ %% Replace names in the byname entry
+ true =
+ ets:insert(
+ Byname,
+ {Key, IPs, Names});
+ [{_Key, IPs, Names_0}] ->
+ case lists:member(IP, IPs) of
+ true ->
+ ok;
+ false ->
+ %% Add the IP address
+ true =
+ ets:insert(
+ Byname,
+ {Key, IPs ++ [IP], Names_0})
+ end;
+ [] ->
+ %% Create a new byname entry
+ true =
+ ets:insert(Byname, {Key, [IP], Names})
+ end
+ end, Nms).
+
+del_ip_bynms(Byname, Fam, IP, Nms) ->
+ lists:foreach(
+ fun (Nm) ->
+ Key = {Fam, Nm},
+ case ets:lookup(Byname, Key) of
+ [{_Key, [IP], _Names}] ->
+ %% Delete whole entry
+ true = ets:delete(Byname, Key);
+ [{_Key, IPs_0, Names_0}] ->
+ case lists:member(IP, IPs_0) of
+ true ->
+ %% Delete the IP address from list
+ IPs = lists:delete(IP, IPs_0),
+ true =
+ ets:insert(
+ Byname, {Key, IPs, Names_0});
+ false ->
+ ok
+ end;
+ [] ->
+ ok
+ end
+ end, Nms).
+
+
+inet_family(T) when tuple_size(T) =:= 4 -> inet;
+inet_family(T) when tuple_size(T) =:= 8 -> inet6.
+
+
+%% Hosts = [ {IP, Name, Aliases}, ... ]
+%% ByaddrMap = #{ {Fam, IP} := rev(Names) }
+%% BynameMap = #{ {Fam, tolower(Name)} := {rev([IP, ...]), Names}}
+
+%% Synchronises internal tables with .hosts/aliases file
+load_hosts_list(Hosts, Byname, Byaddr) ->
+ %% Create byaddr and byname maps
+ {ByaddrMap, BynameMap} = load_hosts_list(Hosts),
+ %% Insert or overwrite existing keys
+ ets:insert(
+ Byaddr,
+ [{Addr, lists:reverse(NamesR)}
+ || {Addr, NamesR} <- maps:to_list(ByaddrMap)]),
+ ets:insert(
+ Byname,
+ [{Fam_Nm, lists:reverse(IPsR), Names}
+ || {Fam_Nm, {IPsR, Names}} <- maps:to_list(BynameMap)]),
+ %% Delete no longer existing keys
+ ets_clean_map_keys(Byaddr, ByaddrMap),
+ ets_clean_map_keys(Byname, BynameMap).
+
+load_hosts_list(Hosts) ->
+ load_hosts_list_byaddr(Hosts, #{}, []).
+
+load_hosts_list_byaddr(
+ [], ByaddrMap, Addrs) ->
+ %% Now for the byname table...
+ load_hosts_list_byname(lists:reverse(Addrs), ByaddrMap, #{});
+%% Traverse hosts list, create byaddr map and insertion order list
+load_hosts_list_byaddr(
+ [{IP, Name, Aliases} | Hosts], ByaddrMap, Addrs) ->
+ Addr = {inet_family(IP), IP},
+ case ByaddrMap of
+ #{Addr := NamesR} ->
+ %% Concatenate names to existing IP address entry
+ load_hosts_list_byaddr(
+ Hosts,
+ ByaddrMap#{Addr := lists:reverse(Aliases, [Name | NamesR])},
+ Addrs);
+ #{} ->
+ %% First entry for an IP address
+ load_hosts_list_byaddr(
+ Hosts,
+ ByaddrMap#{Addr => lists:reverse(Aliases, [Name])},
+ [Addr | Addrs])
+ end.
+
+%% Traverse in insertion order from byaddr pass
+load_hosts_list_byname(
+ [], ByaddrMap, BynameMap) ->
+ {ByaddrMap, BynameMap};
+load_hosts_list_byname(
+ [{Fam, IP} = Addr | Addrs], ByaddrMap, BynameMap) ->
+ Names = lists:reverse(maps:get(Addr, ByaddrMap)),
+ %% Traverse all names for this IP address
+ load_hosts_list_byname(
+ Addrs, ByaddrMap,
+ load_hosts_list_byname(Fam, IP, BynameMap, Names, Names)).
+
+load_hosts_list_byname(_Fam, _IP, BynameMap, _Names_0, []) ->
+ BynameMap;
+load_hosts_list_byname(
+ Fam, IP, BynameMap, Names_0, [Name | Names]) ->
+ Key = {Fam, tolower(Name)},
+ case BynameMap of
+ #{Key := {IPsR, Names_1}} ->
+ %% Add IP address to existing name entry
+ load_hosts_list_byname(
+ Fam, IP,
+ BynameMap#{Key := {[IP | IPsR], Names_1}},
+ Names_0, Names);
+ #{} ->
+ %% First entry for a name
+ load_hosts_list_byname(
+ Fam, IP,
+ BynameMap#{Key => {[IP], Names_0}},
+ Names_0, Names)
+ end.
+
+ets_clean_map_keys(Tab, Map) ->
+ true = ets:safe_fixtable(Tab, true),
+ ets_clean_map_keys(Tab, Map, ets:first(Tab)),
+ true = ets:safe_fixtable(Tab, false),
ok.
+%%
+ets_clean_map_keys(_Tab, _Map, '$end_of_table') ->
+ ok;
+ets_clean_map_keys(Tab, Map, Key) ->
+ case maps:is_key(Key, Map) of
+ true ->
+ ets_clean_map_keys(Tab, Map, ets:next(Tab, Key));
+ false ->
+ true = ets:delete(Tab, Key),
+ ets_clean_map_keys(Tab, Map, ets:next(Tab, Key))
+ end.
+
%% Loop over .inetrc option list and call handle_call/3 for each
%%
@@ -1347,6 +1596,7 @@ rc_reqname(_) -> undefined.
is_res_set(domain) -> true;
is_res_set(lookup) -> true;
is_res_set(timeout) -> true;
+is_res_set(servfail_retry_timeout) -> true;
is_res_set(retry) -> true;
is_res_set(inet6) -> true;
is_res_set(usevc) -> true;
@@ -1383,8 +1633,9 @@ cache_rr(_Db, Cache, RR) ->
ets:insert(Cache, RR).
times() ->
- erlang:convert_time_unit(erlang:monotonic_time() - erlang:system_info(start_time),
- native, second).
+ erlang:monotonic_time(second).
+ %% erlang:convert_time_unit(erlang:monotonic_time() - erlang:system_info(start_time),
+ %% native, second).
%% lookup and remove old entries
@@ -1518,12 +1769,12 @@ do_refresh_cache(Key, CacheDb, Now, OldestT) ->
%% -------------------------------------------------------------------
alloc_entry(Db, CacheDb, TM) ->
CurSize = ets:info(CacheDb, size),
- case ets:lookup(Db, cache_size) of
- [{cache_size, Size}] when Size =< CurSize, Size > 0 ->
+ case ets:lookup_element(Db, cache_size, 2) of
+ Size when Size =< CurSize, Size > 0 ->
alloc_entry(CacheDb, CurSize, TM, trunc(Size * 0.1) + 1);
- [{cache_size, Size}] when Size =< 0 ->
+ Size when Size =< 0 ->
false;
- _ ->
+ _Size ->
true
end.
diff --git a/lib/kernel/src/inet_dns.erl b/lib/kernel/src/inet_dns.erl
index e03f124fe6..18103771dc 100644
--- a/lib/kernel/src/inet_dns.erl
+++ b/lib/kernel/src/inet_dns.erl
@@ -697,7 +697,7 @@ encode_labels(Bin, Comp0, Pos, [L|Ls]=Labels)
when 1 =< byte_size(L), byte_size(L) =< 63 ->
case gb_trees:lookup(Labels, Comp0) of
none ->
- Comp = if Pos < (3 bsl 14) ->
+ Comp = if Pos < (1 bsl 14) ->
%% Just in case - compression
%% pointers cannot reach further
gb_trees:insert(Labels, Pos, Comp0);
diff --git a/lib/kernel/src/inet_hosts.erl b/lib/kernel/src/inet_hosts.erl
index fc653bf0d3..6dce48cf42 100644
--- a/lib/kernel/src/inet_hosts.erl
+++ b/lib/kernel/src/inet_hosts.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -41,12 +41,10 @@ gethostbyname(_) -> {error, formerr}.
gethostbyname(Name, Type) when is_list(Name), is_atom(Type) ->
%% Byname has lowercased names while Byaddr keep the name casing.
%% This is to be able to reconstruct the original /etc/hosts entry.
- N = inet_db:tolower(Name),
- case gethostbyname(N, Type, inet_hosts_byname, inet_hosts_byaddr) of
+ Nm = inet_db:tolower(Name),
+ case gethostbyname(Nm, Type, inet_hosts_byname) of
false ->
- case gethostbyname(N, Type,
- inet_hosts_file_byname,
- inet_hosts_file_byaddr) of
+ case gethostbyname(Nm, Type, inet_hosts_file_byname) of
false -> {error,nxdomain};
Hostent -> {ok,Hostent}
end;
@@ -56,15 +54,12 @@ gethostbyname(Name, Type) when is_atom(Name), is_atom(Type) ->
gethostbyname(atom_to_list(Name), Type);
gethostbyname(_, _) -> {error, formerr}.
-gethostbyname(Name, Type, Byname, Byaddr) ->
+gethostbyname(Nm, Type, Byname) ->
inet_db:res_update_hosts(),
- case [I || [I] <- ets:match(Byname, {Name,Type,'$1'})] of
+ case ets:lookup(Byname, {Type, Nm}) of
[] -> false;
- [IP|_]=IPs ->
- %% Use the primary IP address to generate aliases
- [Nm|As] = [N || [N] <- ets:match(Byaddr,
- {'$1',Type,IP})],
- make_hostent(Nm, IPs, As, Type)
+ [{_, IPs, [Primary | Aliases]}] ->
+ make_hostent(Primary, IPs, Aliases, Type)
end.
@@ -97,13 +92,12 @@ gethostbyaddr(IP, Type) ->
gethostbyaddr(IP, Type, Byaddr) ->
inet_db:res_update_hosts(),
- case [N || [N] <- ets:match(Byaddr, {'$1',Type,IP})] of
+ case ets:lookup(Byaddr, {Type, IP}) of
[] -> false;
- [Nm|As] -> make_hostent(Nm, [IP], As, Type)
+ [{_, [Primary | Aliases]}] -> make_hostent(Primary, [IP], Aliases, Type)
end.
-
make_hostent(Name, Addrs, Aliases, inet) ->
#hostent {
h_name = Name,
diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl
index 7886ef83ac..e323ad0caf 100644
--- a/lib/kernel/src/inet_res.erl
+++ b/lib/kernel/src/inet_res.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -37,6 +37,10 @@
-export([nslookup/3, nslookup/4]).
-export([nnslookup/4, nnslookup/5]).
+-export_type([res_option/0,
+ res_error/0,
+ nameserver/0]).
+
-include_lib("kernel/include/inet.hrl").
-include("inet_res.hrl").
-include("inet_dns.hrl").
@@ -259,7 +263,7 @@ do_nslookup(Name, Class, Type, Opts, Timeout) ->
%%
-record(options, { % These must be sorted!
alt_nameservers,edns,inet6,nameservers,recurse,
- retry,timeout,udp_payload_size,usevc,
+ retry,servfail_retry_timeout,timeout,udp_payload_size,usevc,
verbose}). % this is a local option, not in inet_db
%%
%% Opts when is_list(Opts) -> #options{}
@@ -692,15 +696,15 @@ udp_send(#sock{inet=I}, {A,B,C,D}=IP, Port, Buffer)
udp_recv(#sock{inet6=I}, {A,B,C,D,E,F,G,H}=IP, Port, Timeout, Decode)
when ?ip6(A,B,C,D,E,F,G,H), ?port(Port), 0 =< Timeout ->
- do_udp_recv(I, IP, Port, Timeout, Decode, deadline(Timeout), Timeout);
+ do_udp_recv(I, IP, Port, Timeout, Decode, time(Timeout), Timeout);
udp_recv(#sock{inet=I}, {A,B,C,D}=IP, Port, Timeout, Decode)
when ?ip(A,B,C,D), ?port(Port), 0 =< Timeout ->
- do_udp_recv(I, IP, Port, Timeout, Decode, deadline(Timeout), Timeout).
+ do_udp_recv(I, IP, Port, Timeout, Decode, time(Timeout), Timeout).
-do_udp_recv(_I, _IP, _Port, 0, _Decode, _Deadline, PollCnt)
+do_udp_recv(_I, _IP, _Port, 0, _Decode, _Time, PollCnt)
when PollCnt =< 0 ->
timeout;
-do_udp_recv(I, IP, Port, Timeout, Decode, Deadline, PollCnt) ->
+do_udp_recv(I, IP, Port, Timeout, Decode, Time, PollCnt) ->
case gen_udp:recv(I, 0, Timeout) of
{ok,Reply} ->
case Decode(Reply) of
@@ -716,10 +720,10 @@ do_udp_recv(I, IP, Port, Timeout, Decode, Deadline, PollCnt) ->
%% an infinite loop here.
%%
do_udp_recv(
- I, IP, Port, Timeout, Decode, Deadline, PollCnt-50);
+ I, IP, Port, Timeout, Decode, Time, PollCnt-50);
false ->
- T = timeout(Deadline),
- do_udp_recv(I, IP, Port, T, Decode, Deadline, PollCnt);
+ do_udp_recv(
+ I, IP, Port, timeout(Time), Decode, Time, PollCnt);
Result ->
Result
end;
@@ -763,36 +767,45 @@ do_query(#q{options=#options{retry=Retry}}=Q, NSs, Timer) ->
%% so a failure will be a timeout,
%% unless a name server says otherwise
Reason = timeout,
+ %% Verify that the nameservers list contains only 2-tuples
+ %% to protect our internal servfail_retry mechanism from surprises
+ lists:all(
+ fun (NS) when tuple_size(NS) =:= 2 -> true;
+ (_) -> false
+ end, NSs) orelse
+ erlang:error(badarg, [Q,NSs,Timer]),
query_retries(Q, NSs, Timer, Retry, 0, #sock{}, Reason).
-%% Loop until out of name servers or retries
+%% Loop until out of retries or name servers
%%
-query_retries(_Q, _NSs, _Timer, Retry, Retry, S, Reason) ->
+query_retries(_Q, _NSs, _Timer, Retry, I, S, Reason) when Retry =:= I ->
query_retries_error(S, Reason);
query_retries(_Q, [], _Timer, _Retry, _I, S, Reason) ->
query_retries_error(S, Reason);
-query_retries(Q, NSs, Timer, Retry, I, S_0, Reason) ->
- query_nss(Q, NSs, Timer, Retry, I, S_0, Reason, NSs).
+query_retries(Q, NSs, Timer, Retry, I, S, Reason) ->
+ query_nss(Q, NSs, Timer, Retry, I, S, Reason, []).
-%% Loop for all name servers, for each:
+%% For each name server:
%% If EDNS is enabled, try that first,
%% and for selected failures fall back to plain DNS.
%%
-query_nss(Q, NSs, Timer, Retry, I, S, Reason, []) ->
+query_nss(Q, [], Timer, Retry, I, S, Reason, RetryNSs) ->
%% End of name servers list, do a new retry
- query_retries(Q, NSs, Timer, Retry, I+1, S, Reason);
-query_nss(#q{edns = undefined}=Q, NSs, Timer, Retry, I, S, Reason, TryNSs) ->
- query_nss_dns(Q, NSs, Timer, Retry, I, S, Reason, TryNSs);
-query_nss(Q, NSs, Timer, Retry, I, S, Reason, TryNSs) ->
- query_nss_edns(Q, NSs, Timer, Retry, I, S, Reason, TryNSs).
+ %% with the remaining name servers
+ query_retries(Q, lists:reverse(RetryNSs), Timer, Retry, I+1, S, Reason);
+query_nss(#q{edns = undefined}=Q, NSs, Timer, Retry, I, S, Reason, RetryNSs) ->
+ query_nss_dns(Q, NSs, Timer, Retry, I, S, Reason, RetryNSs);
+query_nss(Q, NSs, Timer, Retry, I, S, Reason, RetryNSs) ->
+ query_nss_edns(Q, NSs, Timer, Retry, I, S, Reason, RetryNSs).
query_nss_edns(
#q{options =
#options{
udp_payload_size = PSz}=Options,
edns = {Id,Buffer}}=Q,
- NSs, Timer, Retry, I, S_0, Reason, [{IP,Port}=NS|TryNSs]=TryNSs_0) ->
+ [NsSpec|NSs], Timer, Retry, I, S_0, Reason, RetryNSs) ->
%%
+ {IP,Port} = NS = servfail_retry_wait(NsSpec),
{S,Result} =
query_ns(
S_0, Id, Buffer, IP, Port, Timer, Retry, I, Options, PSz),
@@ -802,19 +815,25 @@ query_nss_edns(
E =:= notimp;
E =:= servfail;
E =:= badvers ->
- %% The server did not like that,
- %% ignore that error and try plain DNS
+ %% The server did not like that.
+ %% Ignore that error and try plain DNS.
+ %%
+ %% We ignore the servfail_retry_timeout here,
+ %% assuming that if the servfail was due to us using EDNS,
+ %% a DNS query might work, therefore we do not
+ %% count this failure as a try.
query_nss_dns(
- Q, NSs, Timer, Retry, I, S, Reason, TryNSs_0);
+ Q, [NS|NSs], Timer, Retry, I, S, Reason, RetryNSs);
_ ->
query_nss_result(
- Q, NSs, Timer, Retry, I, S, Reason, TryNSs, NS, Result)
+ Q, NSs, Timer, Retry, I, S, Reason, RetryNSs, NS, Result)
end.
query_nss_dns(
#q{dns = Qdns}=Q_0,
- NSs, Timer, Retry, I, S_0, Reason, [{IP,Port}=NS|TryNSs]) ->
+ [NsSpec|NSs], Timer, Retry, I, S_0, Reason, RetryNSs) ->
%%
+ {IP,Port} = NS = servfail_retry_wait(NsSpec),
#q{options = Options,
dns = {Id,Buffer}}=Q =
if
@@ -825,51 +844,89 @@ query_nss_dns(
query_ns(
S_0, Id, Buffer, IP, Port, Timer, Retry, I, Options, ?PACKETSZ),
query_nss_result(
- Q, NSs, Timer, Retry, I, S, Reason, TryNSs, NS, Result).
+ Q, NSs, Timer, Retry, I, S, Reason, RetryNSs, NS, Result).
+
-query_nss_result(Q, NSs, Timer, Retry, I, S, Reason, TryNSs, NS, Result) ->
+%% servfail_retry NsSpec handling.
+%%
+%% A NsSpec is either a NS = {IP, Port},
+%% or for a servfail_retry_timeout, the nameserver wrapped
+%% in a tuple with the earliest time to contact
+%% the nameserver again.
+%%
+%% When unwrapping; wait until it is time before returning
+%% the nameserver.
+
+%% Wrap with retry time
+servfail_retry_time(RetryTimeout, NS) ->
+ {servfail_retry, time(RetryTimeout), NS}.
+
+%% Unwrap and wait
+servfail_retry_wait(NsSpec) ->
+ case NsSpec of
+ {servfail_retry, Time, NS} ->
+ wait(timeout(Time)),
+ NS;
+ {_IP,_Port} = NS->
+ NS
+ end.
+
+
+query_nss_result(Q, NSs, Timer, Retry, I, S, Reason, RetryNSs, NS, Result) ->
case Result of
{ok,_} ->
_ = udp_close(S),
Result;
timeout -> % Out of total time timeout
query_retries_error(S, Reason); % The best reason we have
- {error,timeout} -> % Query timeout
- %% Try next server, may retry this server later
- query_nss(Q, NSs, Timer, Retry, I, S, Reason, TryNSs);
- {error,{nxdomain,_}=NewReason} ->
- query_retries_error(S, NewReason); % Definite answer
+ {error,{nxdomain=E,_}} ->
+ query_retries_error(S, E); % Definite answer
{error,{E,_}=NewReason}
when E =:= qfmterror;
E =:= notimp;
E =:= refused;
- E =:= badvers ->
+ E =:= badvers;
+ E =:= unknown ->
%% The server did not like that.
- %% Remove this server from retry list since
+ %% Do not retry this server since
%% it will not answer differently on the next retry.
- NewNSs = lists:delete(NS, NSs),
- query_nss(Q, NewNSs, Timer, Retry, I, S, NewReason, TryNSs);
+ query_nss(Q, NSs, Timer, Retry, I, S, NewReason, RetryNSs);
{error,E=NewReason}
when E =:= formerr;
E =:= enetunreach;
E =:= econnrefused ->
%% Could not decode answer, or network problem.
- %% Remove this server from retry list.
- NewNSs = lists:delete(NS, NSs),
- query_nss(Q, NewNSs, Timer, Retry, I, S, NewReason, TryNSs);
+ %% Do not retry this server.
+ query_nss(Q, NSs, Timer, Retry, I, S, NewReason, RetryNSs);
+ {error,timeout} -> % Query timeout
+ %% Try next server, may retry this server
+ query_nss(Q, NSs, Timer, Retry, I, S, Reason, [NS|RetryNSs]);
+ {error,{servfail,_}=NewReason} ->
+ RetryTimeout = Q#q.options#options.servfail_retry_timeout,
+ case inet:timeout(RetryTimeout, Timer) of
+ RetryTimeout ->
+ NsSpec = servfail_retry_time(RetryTimeout, NS),
+ query_nss(
+ Q, NSs, Timer, Retry, I, S, NewReason,
+ [NsSpec|RetryNSs]);
+ _ ->
+ %% No time for a new retry with this server
+ %% - do not retry this server
+ query_nss(
+ Q, NSs, Timer, Retry, I, S, NewReason, RetryNSs)
+ end;
{error,NewReason} ->
- %% Try next server, may retry this server later
- query_nss(Q, NSs, Timer, Retry, I, S, NewReason, TryNSs)
+ %% NewReason =
+ %% {error,badid} |
+ %% {error,{noquery,Msg}} |
+ %% {error,OtherSocketError}
+ %% Try next server, may retry this server
+ query_nss(Q, NSs, Timer, Retry, I, S, NewReason, [NS|RetryNSs])
end.
query_retries_error(S, Reason) ->
_ = udp_close(S),
- case Reason of
- {nxdomain, _} ->
- {error, nxdomain};
- _ ->
- {error, Reason}
- end.
+ {error, Reason}.
query_ns(S0, Id, Buffer, IP, Port, Timer, Retry, I,
@@ -878,18 +935,22 @@ query_ns(S0, Id, Buffer, IP, Port, Timer, Retry, I,
case UseVC orelse iolist_size(Buffer) > PSz of
true ->
TcpTimeout = inet:timeout(Tm*5, Timer),
- {S0,query_tcp(TcpTimeout, Id, Buffer, IP, Port, Verbose)};
+ {S0,
+ query_tcp(TcpTimeout, Id, Buffer, IP, Port, Verbose)};
false ->
case udp_open(S0, IP) of
{ok,S} ->
- Timeout =
+ UdpTimeout =
inet:timeout( (Tm * (1 bsl I)) div Retry, Timer),
- case query_udp(
- S, Id, Buffer, IP, Port, Timeout, Verbose) of
+ case
+ query_udp(
+ S, Id, Buffer, IP, Port, UdpTimeout, Verbose)
+ of
{ok,#dns_rec{header=H}} when H#dns_header.tc ->
TcpTimeout = inet:timeout(Tm*5, Timer),
- {S, query_tcp(
- TcpTimeout, Id, Buffer, IP, Port, Verbose)};
+ {S,
+ query_tcp(
+ TcpTimeout, Id, Buffer, IP, Port, Verbose)};
{error, econnrefused} = Err ->
ok = udp_close(S),
{#sock{}, Err};
@@ -1003,10 +1064,10 @@ decode_answer(Answer, Id, Verbose) ->
?NOTIMP -> {error,{notimp,Msg}};
?REFUSED -> {error,{refused,Msg}};
?BADVERS -> {error,{badvers,Msg}};
- _ -> {error,{unknown,Msg}}
+ _ -> {error,{unknown,Msg}}
end;
- Error ->
- ?verbose(Verbose, "Got reply: ~p~n", [Error]),
+ {error, formerr} = Error ->
+ ?verbose(Verbose, "Got reply: decode format error~n", []),
Error
end.
@@ -1084,11 +1145,32 @@ dns_msg(Msg) ->
{Type,dns_msg(Fields)}
end.
--compile({inline, [deadline/1, timeout/1]}).
-deadline(Timeout) -> % When is the deadline? [ms]
+
+
+-compile({inline, [time/1, timeout/1, wait/1]}).
+
+%% What Time is the Timeout? [ms]
+%%
+time(Timeout) ->
erlang:monotonic_time(1000) + Timeout.
-timeout(Deadline) -> % How long to deadline? [ms] >= 0
- case Deadline - erlang:monotonic_time(1000) of
- Timeout when 0 =< Timeout -> Timeout;
- _ -> 0
+
+%% How long Timeout to Time? [ms] >= 0
+%%
+timeout(Time) ->
+ TimeNow = erlang:monotonic_time(1000),
+ if
+ TimeNow < Time ->
+ Time - TimeNow;
+ true ->
+ 0
+ end.
+
+%% receive after Timeout but do not yield for 0
+%%
+wait(0) ->
+ ok;
+wait(Timeout) ->
+ receive
+ after Timeout ->
+ ok
end.
diff --git a/lib/kernel/src/inet_res.hrl b/lib/kernel/src/inet_res.hrl
index 774b4074a5..c812550328 100644
--- a/lib/kernel/src/inet_res.hrl
+++ b/lib/kernel/src/inet_res.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,23 +21,43 @@
%% Dns & resolver defintions
%%
--define(RES_TIMEOUT, 2000). %% milli second between retries
--define(RES_RETRY, 3). %% number of retry
--define(RES_FILE_UPDATE_TM, 5). %% seconds between file_info
+%% milli second for requests
+-define(RES_TIMEOUT, 2000).
--define(CACHE_LIMIT, 100). %% number of cached dns_rr
--define(CACHE_REFRESH, 60*60*1000). %% refresh interval
+%% milli second to wait before next request after a failure
+-define(RES_SERVFAIL_RETRY_TO, 1500).
+
+%% number of retry
+-define(RES_RETRY, 3).
+
+%% seconds between file_info
+-define(RES_FILE_UPDATE_TM, 5).
+
+%% number of cached dns_rr
+-define(CACHE_LIMIT, 100).
+
+%% refresh interval
+-define(CACHE_REFRESH, 60*60*1000).
+
+%% maximum packet size
+-define(PACKETSZ, 512).
+
+%% maximum domain name
+-define(MAXDNAME, 256).
+
+%% maximum compressed domain name
+-define(MAXCDNAME, 255).
+
+%% maximum length of domain label
+-define(MAXLABEL, 63).
--define(PACKETSZ, 512). %% maximum packet size
--define(MAXDNAME, 256). %% maximum domain name
--define(MAXCDNAME, 255). %% maximum compressed domain name
--define(MAXLABEL, 63). %% maximum length of domain label
%% Number of bytes of fixed size data in query structure
--define(QFIXEDSZ, 4).
+-define(QFIXEDSZ, 4).
+
%% number of bytes of fixed size data in resource record
--define(RRFIXEDSZ, 10).
+-define(RRFIXEDSZ, 10).
%%
%% Internet nameserver port number
%%
--define(NAMESERVER_PORT, 53).
+-define(NAMESERVER_PORT, 53).
diff --git a/lib/kernel/src/inet_tcp_dist.erl b/lib/kernel/src/inet_tcp_dist.erl
index c5a114a9ef..ef0f06aef7 100644
--- a/lib/kernel/src/inet_tcp_dist.erl
+++ b/lib/kernel/src/inet_tcp_dist.erl
@@ -21,15 +21,15 @@
%% Handles the connection setup phase with other Erlang nodes.
--export([listen/1, accept/1, accept_connection/5,
- setup/5, close/1, select/1, is_node_name/1]).
+-export([listen/1, listen/2, accept/1, accept_connection/5,
+ setup/5, close/1, select/1, address/0, is_node_name/1]).
%% Optional
-export([setopts/2, getopts/2]).
%% Generalized dist API
--export([gen_listen/2, gen_accept/2, gen_accept_connection/6,
- gen_setup/6, gen_select/2]).
+-export([gen_listen/3, gen_accept/2, gen_accept_connection/6,
+ gen_setup/6, gen_select/2, gen_address/1]).
%% internal exports
@@ -64,19 +64,32 @@ gen_select(Driver, Node) ->
end.
%% ------------------------------------------------------------
+%% Get the address family that this distribution uses
+%% ------------------------------------------------------------
+address() ->
+ gen_address(inet_tcp).
+gen_address(Driver) ->
+ get_tcp_address(Driver).
+
+%% ------------------------------------------------------------
%% Create the listen socket, i.e. the port that this erlang
%% node is accessible through.
%% ------------------------------------------------------------
+listen(Name, Host) ->
+ gen_listen(inet_tcp, Name, Host).
+
+%% Keep this clause for third-party dist controllers reusing this API
listen(Name) ->
- gen_listen(inet_tcp, Name).
+ {ok, Host} = inet:gethostname(),
+ listen(Name, Host).
-gen_listen(Driver, Name) ->
- case do_listen(Driver, [{active, false}, {packet,2}, {reuseaddr, true}]) of
+gen_listen(Driver, Name, Host) ->
+ ErlEpmd = net_kernel:epmd_module(),
+ case gen_listen(ErlEpmd, Name, Host, Driver, [{active, false}, {packet,2}, {reuseaddr, true}]) of
{ok, Socket} ->
TcpAddress = get_tcp_address(Driver, Socket),
{_,Port} = TcpAddress#net_address.address,
- ErlEpmd = net_kernel:epmd_module(),
case ErlEpmd:register_node(Name, Port, Driver) of
{ok, Creation} ->
{ok, {Socket, TcpAddress, Creation}};
@@ -87,20 +100,28 @@ gen_listen(Driver, Name) ->
Error
end.
-do_listen(Driver, Options) ->
- {First,Last} = case application:get_env(kernel,inet_dist_listen_min) of
- {ok,N} when is_integer(N) ->
- case application:get_env(kernel,
- inet_dist_listen_max) of
- {ok,M} when is_integer(M) ->
- {N,M};
- _ ->
- {N,N}
- end;
- _ ->
- {0,0}
- end,
- do_listen(Driver, First, Last, listen_options([{backlog,128}|Options])).
+gen_listen(ErlEpmd, Name, Host, Driver, Options) ->
+ ListenOptions = listen_options(Options),
+ case call_epmd_function(ErlEpmd, listen_port_please, [Name, Host]) of
+ {ok, 0} ->
+ {First,Last} = get_port_range(),
+ do_listen(Driver, First, Last, ListenOptions);
+ {ok, Prt} ->
+ do_listen(Driver, Prt, Prt, ListenOptions)
+ end.
+
+get_port_range() ->
+ case application:get_env(kernel,inet_dist_listen_min) of
+ {ok,N} when is_integer(N) ->
+ case application:get_env(kernel,inet_dist_listen_max) of
+ {ok,M} when is_integer(M) ->
+ {N,M};
+ _ ->
+ {N,N}
+ end;
+ _ ->
+ {0,0}
+ end.
do_listen(_Driver, First,Last,_) when First > Last ->
{error,eaddrinuse};
@@ -122,9 +143,14 @@ listen_options(Opts0) ->
end,
case application:get_env(kernel, inet_dist_listen_options) of
{ok,ListenOpts} ->
- ListenOpts ++ Opts1;
+ case proplists:is_defined(backlog, ListenOpts) of
+ true ->
+ ListenOpts ++ Opts1;
+ false ->
+ ListenOpts ++ [{backlog, 128} | Opts1]
+ end;
_ ->
- Opts1
+ [{backlog, 128} | Opts1]
end.
@@ -285,9 +311,8 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
[Name, Address] = splitnode(Driver, Node, LongOrShortNames),
AddressFamily = Driver:family(),
ErlEpmd = net_kernel:epmd_module(),
- {ARMod, ARFun} = get_address_resolver(ErlEpmd),
Timer = dist_util:start_timer(SetupTime),
- case ARMod:ARFun(Name, Address, AddressFamily) of
+ case call_epmd_function(ErlEpmd,address_please,[Name, Address, AddressFamily]) of
{ok, Ip, TcpPort, Version} ->
?trace("address_please(~p) -> version ~p~n",
[Node,Version]),
@@ -301,8 +326,7 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
do_setup_connect(Driver, Kernel, Node, Address, AddressFamily,
Ip, TcpPort, Version, Type, MyNode, Timer);
_ ->
- ?trace("port_please (~p) "
- "failed.~n", [Node]),
+ ?trace("port_please (~p) failed.~n", [Node]),
?shutdown(Node)
end;
_Other ->
@@ -436,22 +460,24 @@ split_node([], _, Ack) -> [lists:reverse(Ack)].
%% ------------------------------------------------------------
get_tcp_address(Driver, Socket) ->
{ok, Address} = inet:sockname(Socket),
+ NetAddr = get_tcp_address(Driver),
+ NetAddr#net_address{ address = Address }.
+get_tcp_address(Driver) ->
{ok, Host} = inet:gethostname(),
#net_address {
- address = Address,
host = Host,
protocol = tcp,
family = Driver:family()
}.
%% ------------------------------------------------------------
-%% Determine if EPMD module supports address resolving. Default
-%% is to use inet:getaddr/2.
+%% Determine if EPMD module supports the called functions.
+%% If not call the builtin erl_epmd
%% ------------------------------------------------------------
-get_address_resolver(EpmdModule) ->
- case erlang:function_exported(EpmdModule, address_please, 3) of
- true -> {EpmdModule, address_please};
- _ -> {erl_epmd, address_please}
+call_epmd_function(Mod, Fun, Args) ->
+ case erlang:function_exported(Mod, Fun, length(Args)) of
+ true -> apply(Mod,Fun,Args);
+ _ -> apply(erl_epmd, Fun, Args)
end.
%% ------------------------------------------------------------
diff --git a/lib/kernel/src/inet_udp.erl b/lib/kernel/src/inet_udp.erl
index ace095f30c..00ebd4bb1d 100644
--- a/lib/kernel/src/inet_udp.erl
+++ b/lib/kernel/src/inet_udp.erl
@@ -45,10 +45,10 @@ getaddr(Address, Timer) -> inet:getaddr(Address, ?FAMILY, Timer).
%% inet_udp special this side addresses
translate_ip(IP) -> inet:translate_ip(IP, ?FAMILY).
--spec open(_) -> {ok, inet:socket()} | {error, atom()}.
+-spec open(_) -> {ok, port()} | {error, atom()}.
open(Port) -> open(Port, []).
--spec open(_, _) -> {ok, inet:socket()} | {error, atom()}.
+-spec open(_, _) -> {ok, port()} | {error, atom()}.
open(Port, Opts) ->
case inet:udp_options(
[{port,Port}, {recbuf, ?RECBUF} | Opts],
@@ -92,7 +92,7 @@ recv(S, Len) ->
recv(S, Len, Time) ->
prim_inet:recvfrom(S, Len, Time).
--spec close(inet:socket()) -> ok.
+-spec close(port()) -> ok.
close(S) ->
inet:udp_close(S).
diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src
index 35c0c3f88e..e9f6049d5f 100644
--- a/lib/kernel/src/kernel.app.src
+++ b/lib/kernel/src/kernel.app.src
@@ -36,6 +36,7 @@
erl_distribution,
erl_reply,
erl_signal_handler,
+ erpc,
error_handler,
error_logger,
file,
@@ -95,6 +96,7 @@
gen_tcp,
gen_udp,
gen_sctp,
+ gen_tcp_socket,
inet,
inet_db,
inet_dns,
@@ -103,6 +105,7 @@
inet_tcp,
inet_udp,
inet_sctp,
+ pg,
pg2,
raw_file_io,
raw_file_io_compressed,
@@ -112,6 +115,7 @@
raw_file_io_list,
raw_file_io_raw,
seq_trace,
+ socket,
standard_error,
wrap_log_reader]},
{registered, [application_controller,
@@ -143,12 +147,14 @@
ddll_server,
erl_epmd,
inet_db,
+ pg,
pg2]},
{applications, []},
{env, [{logger_level, notice},
- {logger_sasl_compatible, false}
+ {logger_sasl_compatible, false},
+ {shell_docs_ansi,auto}
]},
{mod, {kernel, []}},
- {runtime_dependencies, ["erts-10.6", "stdlib-3.5", "sasl-3.0"]}
+ {runtime_dependencies, ["erts-11.0", "stdlib-3.13", "sasl-3.0"]}
]
}.
diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src
index f42dd8ca6e..796bec68b3 100644
--- a/lib/kernel/src/kernel.appup.src
+++ b/lib/kernel/src/kernel.appup.src
@@ -21,6 +21,7 @@
%% versions from the following OTP releases:
%% - OTP 21
%% - OTP 22
+%% - OTP 23
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
@@ -44,7 +45,15 @@
{<<"^6\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.5$">>,[restart_new_emulator]},
{<<"^6\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.5\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^7\\.0$">>,[restart_new_emulator]},
+ {<<"^7\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^7\\.1$">>,[restart_new_emulator]},
+ {<<"^7\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^7\\.2$">>,[restart_new_emulator]},
+ {<<"^7\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^7\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
[{<<"^6\\.0$">>,[restart_new_emulator]},
{<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -62,4 +71,12 @@
{<<"^6\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.5$">>,[restart_new_emulator]},
{<<"^6\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
+ {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.5\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^7\\.0$">>,[restart_new_emulator]},
+ {<<"^7\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^7\\.1$">>,[restart_new_emulator]},
+ {<<"^7\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^7\\.2$">>,[restart_new_emulator]},
+ {<<"^7\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^7\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/kernel/src/kernel.erl b/lib/kernel/src/kernel.erl
index bc4031b9e7..cc995c94fb 100644
--- a/lib/kernel/src/kernel.erl
+++ b/lib/kernel/src/kernel.erl
@@ -64,7 +64,7 @@ config_change(Changed, New, Removed) ->
%%% (file,code, | erl_dist (A)| | safe_sup (1)|
%%% rpc, ...) ------------- -------------
%%% | |
-%%% (net_kernel, (disk_log, pg2,
+%%% (net_kernel, (disk_log, pg,
%%% auth, ...) ...)
%%%
%%% The rectangular boxes are supervisors. All supervisors except
@@ -180,7 +180,7 @@ init(safe) ->
Boot = start_boot_server(),
DiskLog = start_disk_log(),
- Pg2 = start_pg2(),
+ Pg = start_pg2() ++ start_pg(),
%% Run the on_load handlers for all modules that have been
%% loaded so far. Running them at this point means that
@@ -188,7 +188,7 @@ init(safe) ->
%% (and in particular call code:priv_dir/1 or code:lib_dir/1).
init:run_on_load_handlers(),
- {ok, {SupFlags, Boot ++ DiskLog ++ Pg2}}.
+ {ok, {SupFlags, Boot ++ DiskLog ++ Pg}}.
start_distribution() ->
Rpc = #{id => rex,
@@ -279,6 +279,19 @@ start_disk_log() ->
[]
end.
+start_pg() ->
+ case application:get_env(kernel, start_pg) of
+ {ok, true} ->
+ [#{id => pg,
+ start => {pg, start_link, []},
+ restart => permanent,
+ shutdown => 1000,
+ type => worker,
+ modules => [pg]}];
+ _ ->
+ []
+ end.
+
start_pg2() ->
case application:get_env(kernel, start_pg2) of
{ok, true} ->
diff --git a/lib/kernel/src/logger.erl b/lib/kernel/src/logger.erl
index cf721909f1..167637b8b4 100644
--- a/lib/kernel/src/logger.erl
+++ b/lib/kernel/src/logger.erl
@@ -256,9 +256,8 @@ log(Level, FunOrFormat, Args, Metadata) ->
-spec allow(Level,Module) -> boolean() when
Level :: level(),
Module :: module().
-allow(Level,Module) when ?IS_LEVEL(Level), is_atom(Module) ->
- logger_config:allow(?LOGGER_TABLE,Level,Module).
-
+allow(Level,Module) when is_atom(Module) ->
+ logger_config:allow(Level,Module).
-spec macro_log(Location,Level,StringOrReport) -> ok when
Location :: location(),
@@ -571,8 +570,8 @@ set_application_level(App,Level) ->
{error, {not_loaded, App}}
end.
--spec unset_application_level(Application) -> ok | {error, not_loaded} when
- Application :: atom().
+-spec unset_application_level(Application) ->
+ ok | {error, {not_loaded, Application}} when Application :: atom().
unset_application_level(App) ->
case application:get_key(App, modules) of
{ok, Modules} ->
@@ -595,7 +594,7 @@ get_module_level(Modules) when is_list(Modules) ->
Module :: module(),
Level :: level() | all | none.
get_module_level() ->
- logger_config:get_module_level(?LOGGER_TABLE).
+ logger_config:get_module_level().
%%%-----------------------------------------------------------------
%%% Misc
@@ -1027,14 +1026,14 @@ get_logger_env(App) ->
%%%-----------------------------------------------------------------
%%% Internal
do_log(Level,Msg,#{mfa:={Module,_,_}}=Meta) ->
- case logger_config:allow(?LOGGER_TABLE,Level,Module) of
+ case logger_config:allow(Level,Module) of
true ->
log_allowed(#{},Level,Msg,Meta);
false ->
ok
end;
do_log(Level,Msg,Meta) ->
- case logger_config:allow(?LOGGER_TABLE,Level) of
+ case logger_config:allow(Level) of
true ->
log_allowed(#{},Level,Msg,Meta);
false ->
@@ -1051,7 +1050,7 @@ do_log(Level,Msg,Meta) ->
Meta :: metadata().
log_allowed(Location,Level,{Fun,FunArgs},Meta) when is_function(Fun,1) ->
try Fun(FunArgs) of
- Msg={Format,Args} when is_list(Format), is_list(Args) ->
+ Msg={Format,Args} when ?IS_FORMAT(Format), is_list(Args) ->
log_allowed(Location,Level,Msg,Meta);
Report when ?IS_REPORT(Report) ->
log_allowed(Location,Level,Report,Meta);
@@ -1086,7 +1085,7 @@ log_allowed(Location,Level,Msg,Meta0) when is_map(Meta0) ->
do_log_allowed(Level,{Format,Args}=Msg,Meta,Tid)
when ?IS_LEVEL(Level),
- is_list(Format),
+ ?IS_FORMAT(Format),
is_list(Args),
is_map(Meta) ->
logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},Tid);
diff --git a/lib/kernel/src/logger_backend.erl b/lib/kernel/src/logger_backend.erl
index 432c671afd..7003d2268e 100644
--- a/lib/kernel/src/logger_backend.erl
+++ b/lib/kernel/src/logger_backend.erl
@@ -56,6 +56,7 @@ call_handlers(#{level:=Level}=Log,[Id|Handlers],Tid) ->
error,{removed_failing_handler,Id}),
?LOG_INTERNAL(
debug,
+ Log1,
[{logger,removed_failing_handler},
{handler,{Id,Module}},
{log_event,Log1},
@@ -68,6 +69,7 @@ call_handlers(#{level:=Level}=Log,[Id|Handlers],Tid) ->
{error,Reason} ->
?LOG_INTERNAL(
debug,
+ Log1,
[{logger,remove_handler_failed},
{reason,Reason}])
end
@@ -119,6 +121,7 @@ handle_filter_failed({Id,_}=Filter,Owner,Log,Reason) ->
ok ->
logger:internal_log(error,{removed_failing_filter,Id}),
?LOG_INTERNAL(debug,
+ Log,
[{logger,removed_failing_filter},
{filter,Filter},
{owner,Owner},
diff --git a/lib/kernel/src/logger_config.erl b/lib/kernel/src/logger_config.erl
index 5597ee6a23..9e94fdc6a1 100644
--- a/lib/kernel/src/logger_config.erl
+++ b/lib/kernel/src/logger_config.erl
@@ -21,120 +21,142 @@
-export([new/1,delete/2,
exist/2,
- allow/2,allow/3,
+ allow/1,allow/2,
get/2, get/3,
create/3, set/3,
- set_module_level/3,unset_module_level/2,
- get_module_level/1,cache_module_level/2,
+ set_module_level/2,unset_module_level/1,
+ get_module_level/0,
level_to_int/1]).
-include("logger_internal.hrl").
+-compile({inline,[get_primary_level/0,level_to_int/1]}).
+
+-define(LEVEL_TO_CACHE(Level),Level + 16#10).
+-define(PRIMARY_TO_CACHE(Level),Level).
+-define(IS_CACHED(Level),(Level =< ?LOG_ALL)).
+-define(CACHE_TO_LEVEL(Level),if ?IS_CACHED(Level) -> Level; true -> Level - 16#10 end).
+
+-define(IS_MODULE(Module),is_atom(Module) andalso Module =/= ?PRIMARY_KEY).
+
new(Name) ->
_ = ets:new(Name,[set,protected,named_table,
{read_concurrency,true},
{write_concurrency,true}]),
ets:whereis(Name).
-delete(Tid,Id) ->
- ets:delete(Tid,table_key(Id)).
-
-allow(Tid,Level,Module) ->
- LevelInt = level_to_int(Level),
- try ets:lookup(Tid,Module) of
- [{Module,{ModLevel,cached}}] when is_integer(ModLevel),
- LevelInt =< ModLevel ->
- true;
- [{Module,ModLevel}] when is_integer(ModLevel),
- LevelInt =< ModLevel ->
- true;
- [] ->
- logger_server:cache_module_level(Module),
- allow(Tid,Level);
- _ ->
- false
- catch error:badarg ->
- true
- end.
-
-allow(Tid,Level) ->
- try ets:lookup_element(Tid,?PRIMARY_KEY,2) of
- GlobalLevelInt ->
- level_to_int(Level) =< GlobalLevelInt
- catch error:badarg ->
- true
- end.
+delete(Tid,What) ->
+ persistent_term:put({?MODULE,table_key(What)},undefined),
+ ets:delete(Tid,table_key(What)).
+
+%% Optimized for speed.
+allow(Level,Module) ->
+ ModLevel =
+ case persistent_term:get({?MODULE,Module},undefined) of
+ undefined ->
+ %% This is where the module cache takes place. We insert the module level
+ %% plus 16 into the pt and then when looking it up we need to do a check
+ %% and subtraction.
+ %% The reason why we do this dance and not just wrap it all in a tuple
+ %% is because updates of immediates (i.e. small ints in this case)
+ %% is cheap even in pt, so we cannot put any complex terms in there.
+ IntLevel = get_primary_level(),
+ persistent_term:put({?MODULE,Module},?PRIMARY_TO_CACHE(IntLevel)),
+ IntLevel;
+ IntLevel ->
+ ?CACHE_TO_LEVEL(IntLevel)
+ end,
+ less_or_equal_level(Level,ModLevel).
+
+allow(Level) ->
+ PrimaryLevelInt = get_primary_level(),
+ less_or_equal_level(Level,PrimaryLevelInt).
+
+less_or_equal_level(emergency,ModLevel) -> ?EMERGENCY =< ModLevel;
+less_or_equal_level(alert,ModLevel) -> ?ALERT =< ModLevel;
+less_or_equal_level(critical,ModLevel) -> ?CRITICAL =< ModLevel;
+less_or_equal_level(error,ModLevel) -> ?ERROR =< ModLevel;
+less_or_equal_level(warning,ModLevel) -> ?WARNING =< ModLevel;
+less_or_equal_level(notice,ModLevel) -> ?NOTICE =< ModLevel;
+less_or_equal_level(info,ModLevel) -> ?INFO =< ModLevel;
+less_or_equal_level(debug,ModLevel) -> ?DEBUG =< ModLevel.
exist(Tid,What) ->
ets:member(Tid,table_key(What)).
+get_primary_level() ->
+ persistent_term:get({?MODULE,?PRIMARY_KEY},?NOTICE).
+
get(Tid,What) ->
case ets:lookup(Tid,table_key(What)) of
- [{_,_,Config}] ->
- {ok,Config};
- [{_,Config}] when What=:=proxy ->
+ [{_,Config}] ->
{ok,Config};
[] ->
{error,{not_found,What}}
end.
get(Tid,What,Level) ->
- MS = [{{table_key(What),'$1','$2'},
- [{'>=','$1',level_to_int(Level)}],
- ['$2']}],
- case ets:select(Tid,MS) of
- [] -> error;
- [Data] -> {ok,Data}
+ TableKey = table_key(What),
+ case persistent_term:get({?MODULE,TableKey},undefined) of
+ undefined ->
+ %% The handler is not installed at the moment
+ {error,{not_found,What}};
+ ConfLevel ->
+ case less_or_equal_level(Level,ConfLevel) of
+ true ->
+ get(Tid, What);
+ false ->
+ error
+ end
end.
create(Tid,proxy,Config) ->
ets:insert(Tid,{table_key(proxy),Config});
create(Tid,What,Config) ->
LevelInt = level_to_int(maps:get(level,Config)),
- ets:insert(Tid,{table_key(What),LevelInt,Config}).
+ ok = persistent_term:put({?MODULE,table_key(What)}, LevelInt),
+ ets:insert(Tid,{table_key(What),Config}).
set(Tid,proxy,Config) ->
ets:insert(Tid,{table_key(proxy),Config}),
ok;
set(Tid,What,Config) ->
LevelInt = level_to_int(maps:get(level,Config)),
- %% Should do this only if the level has actually changed. Possibly
- %% overwrite instead of delete?
+ ok = persistent_term:put({?MODULE,table_key(What)}, LevelInt),
case What of
primary ->
- _ = ets:select_delete(Tid,[{{'_',{'$1',cached}},
- [{'=/=','$1',LevelInt}],
- [true]}]),
+ [persistent_term:put(Key,?PRIMARY_TO_CACHE(LevelInt))
+ || {{?MODULE,Module} = Key,Level} <- persistent_term:get(),
+ ?IS_MODULE(Module), ?IS_CACHED(Level)],
ok;
_ ->
ok
end,
- ets:update_element(Tid,table_key(What),[{2,LevelInt},{3,Config}]),
+ ets:insert(Tid,{table_key(What),Config}),
ok.
-set_module_level(Tid,Modules,Level) ->
+set_module_level(Modules,Level) ->
LevelInt = level_to_int(Level),
- [ets:insert(Tid,{Module,LevelInt}) || Module <- Modules],
+ [persistent_term:put({?MODULE,Module},?LEVEL_TO_CACHE(LevelInt)) || Module <- Modules],
ok.
-%% should possibly overwrite instead of delete?
-unset_module_level(Tid,all) ->
- MS = [{{'$1','$2'},[{is_atom,'$1'},{is_integer,'$2'}],[true]}],
- _ = ets:select_delete(Tid,MS),
+%% We overwrite instead of delete because that is more efficient
+%% when using persistent_term
+unset_module_level(all) ->
+ PrimaryLevel = get_primary_level(),
+ [persistent_term:put(Key, ?PRIMARY_TO_CACHE(PrimaryLevel))
+ || {{?MODULE, Module} = Key,_} <- persistent_term:get(), ?IS_MODULE(Module)],
ok;
-unset_module_level(Tid,Modules) ->
- [ets:delete(Tid,Module) || Module <- Modules],
+unset_module_level(Modules) ->
+ PrimaryLevel = get_primary_level(),
+ [persistent_term:put({?MODULE,Module}, ?PRIMARY_TO_CACHE(PrimaryLevel)) || Module <- Modules],
ok.
-get_module_level(Tid) ->
- MS = [{{'$1','$2'},[{is_atom,'$1'},{is_integer,'$2'}],[{{'$1','$2'}}]}],
- Modules = ets:select(Tid,MS),
- lists:sort([{M,int_to_level(L)} || {M,L} <- Modules]).
-
-cache_module_level(Tid,Module) ->
- GlobalLevelInt = ets:lookup_element(Tid,?PRIMARY_KEY,2),
- ets:insert_new(Tid,{Module,{GlobalLevelInt,cached}}),
- ok.
+get_module_level() ->
+ lists:sort(
+ [{Module,int_to_level(?CACHE_TO_LEVEL(Level))}
+ || {{?MODULE, Module},Level} <- persistent_term:get(),
+ ?IS_MODULE(Module), not ?IS_CACHED(Level)]).
level_to_int(none) -> ?LOG_NONE;
level_to_int(emergency) -> ?EMERGENCY;
diff --git a/lib/kernel/src/logger_formatter.erl b/lib/kernel/src/logger_formatter.erl
index ef5066e5d9..a1e6f67e94 100644
--- a/lib/kernel/src/logger_formatter.erl
+++ b/lib/kernel/src/logger_formatter.erl
@@ -323,7 +323,7 @@ timestamp_to_datetimemicro(SysTime,Config) when is_integer(SysTime) ->
{Date,Time,Micro,UtcStr}.
format_mfa({M,F,A},_) when is_atom(M), is_atom(F), is_integer(A) ->
- atom_to_list(M)++":"++atom_to_list(F)++"/"++integer_to_list(A);
+ io_lib:fwrite("~tw:~tw/~w", [M, F, A]);
format_mfa({M,F,A},Config) when is_atom(M), is_atom(F), is_list(A) ->
format_mfa({M,F,length(A)},Config);
format_mfa(MFA,Config) ->
diff --git a/lib/kernel/src/logger_h_common.erl b/lib/kernel/src/logger_h_common.erl
index 40af4fb8ce..e96d2b28f3 100644
--- a/lib/kernel/src/logger_h_common.erl
+++ b/lib/kernel/src/logger_h_common.erl
@@ -392,26 +392,26 @@ do_log_to_binary(Log,Config) ->
String = try_format(Log,Formatter,FormatterConfig),
try string_to_binary(String)
catch C2:R2:S2 ->
- ?LOG_INTERNAL(debug,[{formatter_error,Formatter},
- {config,FormatterConfig},
- {log_event,Log},
- {bad_return_value,String},
- {catched,{C2,R2,S2}}]),
- <<"FORMATTER ERROR: bad return value">>
+ ?LOG_INTERNAL(debug,Log,[{formatter_error,Formatter},
+ {config,FormatterConfig},
+ {log_event,Log},
+ {bad_return_value,String},
+ {catched,{C2,R2,S2}}]),
+ <<"FORMATTER ERROR: bad return value\n">>
end.
try_format(Log,Formatter,FormatterConfig) ->
try Formatter:format(Log,FormatterConfig)
catch
C:R:S ->
- ?LOG_INTERNAL(debug,[{formatter_crashed,Formatter},
- {config,FormatterConfig},
- {log_event,Log},
- {reason,
- {C,R,logger:filter_stacktrace(?MODULE,S)}}]),
+ ?LOG_INTERNAL(debug,Log,[{formatter_crashed,Formatter},
+ {config,FormatterConfig},
+ {log_event,Log},
+ {reason,
+ {C,R,logger:filter_stacktrace(?MODULE,S)}}]),
case {?DEFAULT_FORMATTER,#{}} of
{Formatter,FormatterConfig} ->
- "DEFAULT FORMATTER CRASHED";
+ "DEFAULT FORMATTER CRASHED\n";
{DefaultFormatter,DefaultConfig} ->
try_format(Log#{msg=>{"FORMATTER CRASH: ~tp",
[maps:get(msg,Log)]}},
diff --git a/lib/kernel/src/logger_handler_watcher.erl b/lib/kernel/src/logger_handler_watcher.erl
index b75c74c643..6f622b169c 100644
--- a/lib/kernel/src/logger_handler_watcher.erl
+++ b/lib/kernel/src/logger_handler_watcher.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -51,42 +51,25 @@ register_handler(Id,Pid) ->
%%% gen_server callbacks
%%%===================================================================
--spec init(Args :: term()) -> {ok, State :: term()} |
- {ok, State :: term(), Timeout :: timeout()} |
- {ok, State :: term(), hibernate} |
- {stop, Reason :: term()} |
- ignore.
+-spec init(Args :: term()) -> {ok, State :: term()}.
init([]) ->
process_flag(trap_exit, true),
{ok, #state{handlers=[]}}.
-spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) ->
- {reply, Reply :: term(), NewState :: term()} |
- {reply, Reply :: term(), NewState :: term(), Timeout :: timeout()} |
- {reply, Reply :: term(), NewState :: term(), hibernate} |
- {noreply, NewState :: term()} |
- {noreply, NewState :: term(), Timeout :: timeout()} |
- {noreply, NewState :: term(), hibernate} |
- {stop, Reason :: term(), Reply :: term(), NewState :: term()} |
- {stop, Reason :: term(), NewState :: term()}.
+ {reply, ok, NewState :: term()}.
handle_call({register,Id,Pid}, _From, #state{handlers=Hs}=State) ->
Ref = erlang:monitor(process,Pid),
Hs1 = lists:keystore(Id,1,Hs,{Id,Ref}),
{reply, ok, State#state{handlers=Hs1}}.
-spec handle_cast(Request :: term(), State :: term()) ->
- {noreply, NewState :: term()} |
- {noreply, NewState :: term(), Timeout :: timeout()} |
- {noreply, NewState :: term(), hibernate} |
- {stop, Reason :: term(), NewState :: term()}.
+ {noreply, NewState :: term()}.
handle_cast(_Request, State) ->
{noreply, State}.
-spec handle_info(Info :: timeout() | term(), State :: term()) ->
- {noreply, NewState :: term()} |
- {noreply, NewState :: term(), Timeout :: timeout()} |
- {noreply, NewState :: term(), hibernate} |
- {stop, Reason :: normal | term(), NewState :: term()}.
+ {noreply, NewState :: term()}.
handle_info({'DOWN',Ref,process,_,shutdown}, #state{handlers=Hs}=State) ->
case lists:keytake(Ref,2,Hs) of
{value,{Id,Ref},Hs1} ->
@@ -108,6 +91,6 @@ handle_info(_Other,State) ->
{noreply,State}.
-spec terminate(Reason :: normal | shutdown | {shutdown, term()} | term(),
- State :: term()) -> any().
+ State :: term()) -> ok.
terminate(_Reason, _State) ->
ok.
diff --git a/lib/kernel/src/logger_internal.hrl b/lib/kernel/src/logger_internal.hrl
index a041cd53da..9f28157585 100644
--- a/lib/kernel/src/logger_internal.hrl
+++ b/lib/kernel/src/logger_internal.hrl
@@ -41,14 +41,14 @@
-define(DEFAULT_LOGGER_CALL_TIMEOUT, infinity).
--define(LOG_INTERNAL(Level,Report),?DO_LOG_INTERNAL(Level,[Report])).
--define(LOG_INTERNAL(Level,Format,Args),?DO_LOG_INTERNAL(Level,[Format,Args])).
--define(DO_LOG_INTERNAL(Level,Data),
+-define(LOG_INTERNAL(Level,Log,Report),
+ ?DO_LOG_INTERNAL(Level,Log,[Report])).
+-define(LOG_INTERNAL(Level,Log,Format,Args),
+ ?DO_LOG_INTERNAL(Level,Log,[Format,Args])).
+-define(DO_LOG_INTERNAL(Level,Log,Data),
case logger:allow(Level,?MODULE) of
true ->
- %% Spawn this to avoid deadlocks
- _ = spawn(logger,macro_log,[?LOCATION,Level|Data]++
- [logger:add_default_metadata(#{})]),
+ _ = logger_server:do_internal_log(Level,?LOCATION,Log,Data),
ok;
false ->
ok
@@ -91,7 +91,7 @@
-define(IS_LEVEL_ALL(L),
?IS_LEVEL(L) orelse
L=:=all orelse
- L=:=none ).
+ L=:=none ).
-define(IS_MSG(Msg),
((is_tuple(Msg) andalso tuple_size(Msg)==2)
@@ -107,3 +107,6 @@
-define(IS_STRING(String),
(is_list(String) orelse is_binary(String))).
+
+-define(IS_FORMAT(Format),
+ (?IS_STRING(Format) orelse is_atom(Format))).
diff --git a/lib/kernel/src/logger_olp.erl b/lib/kernel/src/logger_olp.erl
index 70842de7e6..068fbe447b 100644
--- a/lib/kernel/src/logger_olp.erl
+++ b/lib/kernel/src/logger_olp.erl
@@ -77,7 +77,7 @@ load({_Name,Pid,ModeRef},Msg) ->
%% synchronous instead of asynchronous (slows down the tempo of a
%% process causing much load). If the process is choked, drop mode
%% is set and no message is sent.
- try ?get_mode(ModeRef) of
+ case get_mode(ModeRef) of
async ->
gen_server:cast(Pid, {'$olp_load',Msg});
sync ->
@@ -86,17 +86,11 @@ load({_Name,Pid,ModeRef},Msg) ->
ok;
_Other ->
%% dropped or {error,busy}
- ?observe(_Name,{dropped,1}),
- ok
+ ?observe(_Name,{dropped,1})
end;
drop ->
?observe(_Name,{dropped,1})
- catch
- %% if the ETS table doesn't exist (maybe because of a
- %% process restart), we can only drop the event
- _:_ -> ?observe(_Name,{dropped,1})
- end,
- ok.
+ end.
-spec info(Olp) -> map() | {error, busy} when
Olp :: atom() | pid() | olp_ref().
@@ -147,8 +141,8 @@ restart(Fun) ->
catch C:R:S ->
{error,{restart_failed,Fun,C,R,S}}
end,
- ?LOG_INTERNAL(debug,[{logger_olp,restart},
- {result,Result}]),
+ ?LOG_INTERNAL(debug,#{},[{logger_olp,restart},
+ {result,Result}]),
ok.
-spec get_ref() -> olp_ref().
@@ -174,40 +168,32 @@ init([Name,Module,Args,Options]) ->
?start_observation(Name),
- try ets:new(Name, [public]) of
- ModeRef ->
- OlpRef = {Name,self(),ModeRef},
- put(olp_ref,OlpRef),
- try Module:init(Args) of
- {ok,CBState} ->
- ?set_mode(ModeRef, async),
- T0 = ?timestamp(),
- proc_lib:init_ack({ok,self(),OlpRef}),
- %% Storing options in state to avoid copying
- %% (sending) the option data with each message
- State0 = ?merge_with_stats(
- Options#{id => Name,
- idle=> true,
- module => Module,
- mode_ref => ModeRef,
- mode => async,
- last_qlen => 0,
- last_load_ts => T0,
- burst_win_ts => T0,
- burst_msg_count => 0,
- cb_state => CBState}),
- State = reset_restart_flag(State0),
- gen_server:enter_loop(?MODULE, [], State);
- Error ->
- _ = ets:delete(ModeRef),
- unregister(Name),
- proc_lib:init_ack(Error)
- catch
- _:Error ->
- _ = ets:delete(ModeRef),
- unregister(Name),
- proc_lib:init_ack(Error)
- end
+ ModeRef = {?MODULE, Name},
+ OlpRef = {Name,self(),ModeRef},
+ put(olp_ref,OlpRef),
+ try Module:init(Args) of
+ {ok,CBState} ->
+ set_mode(ModeRef, async),
+ T0 = ?timestamp(),
+ proc_lib:init_ack({ok,self(),OlpRef}),
+ %% Storing options in state to avoid copying
+ %% (sending) the option data with each message
+ State0 = ?merge_with_stats(
+ Options#{id => Name,
+ idle=> true,
+ module => Module,
+ mode_ref => ModeRef,
+ mode => async,
+ last_qlen => 0,
+ last_load_ts => T0,
+ burst_win_ts => T0,
+ burst_msg_count => 0,
+ cb_state => CBState}),
+ State = reset_restart_flag(State0),
+ gen_server:enter_loop(?MODULE, [], State);
+ Error ->
+ unregister(Name),
+ proc_lib:init_ack(Error)
catch
_:Error ->
unregister(Name),
@@ -274,11 +260,11 @@ handle_cast(Msg, #{module:=Module, cb_state:=CBState} = State) ->
{stop, Reason, State#{cb_state=>CBState1}}
end.
-handle_info(timeout, #{mode_ref:=_ModeRef, mode:=Mode} = State) ->
+handle_info(timeout, #{mode_ref:=ModeRef} = State) ->
State1 = notify(idle,State),
State2 = maybe_notify_mode_change(async,State1),
{noreply, State2#{idle => true,
- mode => ?change_mode(_ModeRef, Mode, async),
+ mode => set_mode(ModeRef, async),
burst_msg_count => 0}};
handle_info(Msg, #{module := Module, cb_state := CBState} = State) ->
case try_callback_call(Module,handle_info,[Msg, CBState]) of
@@ -357,7 +343,7 @@ do_load(Msg, CallOrCast, State) ->
%% this function is called by do_load/3 after an overload check
%% has been performed, where QLen > FlushQLen
-flush(T1, State=#{id := _Name, mode := Mode, last_load_ts := _T0, mode_ref := ModeRef}) ->
+flush(T1, State=#{id := _Name, last_load_ts := _T0, mode_ref := ModeRef}) ->
%% flush load messages in the mailbox (a limited number in order
%% to not cause long delays)
NewFlushed = flush_load(?FLUSH_MAX_N),
@@ -378,7 +364,7 @@ flush(T1, State=#{id := _Name, mode := Mode, last_load_ts := _T0, mode_ref := Mo
State3 = ?update_max_qlen(QLen1,State2),
State4 = maybe_notify_mode_change(async,State3),
{dropped,?update_other(flushed,FLUSHED,NewFlushed,
- State4#{mode => ?change_mode(ModeRef,Mode,async),
+ State4#{mode => set_mode(ModeRef,async),
last_qlen => QLen1,
last_load_ts => T1})}.
@@ -507,11 +493,11 @@ check_load(State = #{id:=_Name, mode_ref := ModeRef, mode := Mode,
%% be dropped on the client side (never sent to
%% the olp process).
IncDrops = if Mode == drop -> 0; true -> 1 end,
- {?change_mode(ModeRef, Mode, drop), IncDrops,0};
+ {set_mode(ModeRef, drop), IncDrops,0};
QLen >= SyncModeQLen ->
- {?change_mode(ModeRef, Mode, sync), 0,0};
+ {set_mode(ModeRef, sync), 0,0};
true ->
- {?change_mode(ModeRef, Mode, async), 0,0}
+ {set_mode(ModeRef, async), 0,0}
end,
State1 = ?update_other(drops,DROPS,_NewDrops,State),
State2 = ?update_max_qlen(QLen,State1),
@@ -576,7 +562,7 @@ flush_load(N, Limit) ->
{log,_,_,_,_} ->
flush_load(N+1, Limit);
{log,_,_,_} ->
- flush_load(N+1, Limit)
+ flush_load(N+1, Limit)
after
0 -> N
end.
@@ -587,6 +573,11 @@ overload_levels_ok(Options) ->
FQL = maps:get(flush_qlen, Options, ?FLUSH_QLEN),
(DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL).
+get_mode(Ref) -> persistent_term:get(Ref, async).
+
+set_mode(Ref, M) ->
+ true = is_atom(M), persistent_term:put(Ref, M), M.
+
maybe_notify_mode_change(drop,#{mode:=Mode0}=State)
when Mode0=/=drop ->
notify({mode_change,Mode0,drop},State);
diff --git a/lib/kernel/src/logger_olp.hrl b/lib/kernel/src/logger_olp.hrl
index 4ce469d205..52d4fd8e0f 100644
--- a/lib/kernel/src/logger_olp.hrl
+++ b/lib/kernel/src/logger_olp.hrl
@@ -72,25 +72,8 @@
-define(timestamp(), erlang:monotonic_time(microsecond)).
--define(get_mode(Tid),
- case ets:lookup(Tid, mode) of
- [{mode,M}] -> M;
- _ -> async
- end).
-
--define(set_mode(Tid, M),
- begin ets:insert(Tid, {mode,M}), M end).
-
--define(change_mode(Tid, M0, M1),
- if M0 == M1 ->
- M0;
- true ->
- ets:insert(Tid, {mode,M1}),
- M1
- end).
-
-define(max(X1, X2),
- if
+ if
X2 == undefined -> X1;
X2 > X1 -> X2;
true -> X1
diff --git a/lib/kernel/src/logger_proxy.erl b/lib/kernel/src/logger_proxy.erl
index e75b7defa0..fb5296c262 100644
--- a/lib/kernel/src/logger_proxy.erl
+++ b/lib/kernel/src/logger_proxy.erl
@@ -134,7 +134,11 @@ handle_load({log,Level,Report,Meta},State) ->
%% Log event sent to this process e.g. from the emulator - it is really load
handle_info(Log,State) when is_tuple(Log), element(1,Log)==log ->
- {load,State}.
+ {load,State};
+handle_info(_Log,State) ->
+ %% Handle stray reply messages from sync try_log, not needed after OTP-24
+ %% as then aliases will prevent late messages.
+ State.
terminate(overloaded, _State) ->
_ = erlang:system_flag(system_logger,undefined),
@@ -151,13 +155,13 @@ notify({mode_change,Mode0,Mode1},State) ->
true ->
ok
end,
- ?LOG_INTERNAL(notice,"~w switched from ~w to ~w mode",[?MODULE,Mode0,Mode1]),
+ ?LOG_INTERNAL(notice,#{},"~w switched from ~w to ~w mode",[?MODULE,Mode0,Mode1]),
State;
notify({flushed,Flushed},State) ->
- ?LOG_INTERNAL(notice, "~w flushed ~w log events",[?MODULE,Flushed]),
+ ?LOG_INTERNAL(notice,#{},"~w flushed ~w log events",[?MODULE,Flushed]),
State;
notify(restart,State) ->
- ?LOG_INTERNAL(notice, "~w restarted", [?MODULE]),
+ ?LOG_INTERNAL(notice,#{},"~w restarted", [?MODULE]),
State;
notify(_Note,State) ->
State.
@@ -167,7 +171,7 @@ notify(_Note,State) ->
try_log(Args) ->
try apply(logger,log,Args)
catch C:R:S ->
- ?LOG_INTERNAL(debug,[{?MODULE,log_failed},
- {log,Args},
- {reason,{C,R,S}}])
+ ?LOG_INTERNAL(debug,#{},[{?MODULE,log_failed},
+ {log,Args},
+ {reason,{C,R,S}}])
end.
diff --git a/lib/kernel/src/logger_server.erl b/lib/kernel/src/logger_server.erl
index 6024786e6a..6bdac471ec 100644
--- a/lib/kernel/src/logger_server.erl
+++ b/lib/kernel/src/logger_server.erl
@@ -25,13 +25,13 @@
-export([start_link/0, add_handler/3, remove_handler/1,
add_filter/2, remove_filter/2,
set_module_level/2, unset_module_level/0,
- unset_module_level/1, cache_module_level/1,
+ unset_module_level/1,
set_config/2, set_config/3,
update_config/2, update_config/3,
update_formatter_config/2]).
%% Helper
--export([diff_maps/2]).
+-export([diff_maps/2,do_internal_log/4]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -104,9 +104,6 @@ unset_module_level(Modules) when is_list(Modules) ->
unset_module_level(Modules) ->
{error,{not_a_list_of_modules,Modules}}.
-cache_module_level(Module) ->
- gen_server:cast(?SERVER,{cache_module_level,Module}).
-
set_config(Owner,Key,Value) ->
case sanity_check(Owner,Key,Value) of
ok ->
@@ -325,18 +322,15 @@ handle_call({update_formatter_config,HandlerId,NewFConfig},_From,
{error,{not_found,HandlerId}}
end,
{reply,Reply,State};
-handle_call({set_module_level,Modules,Level}, _From, #state{tid=Tid}=State) ->
- Reply = logger_config:set_module_level(Tid,Modules,Level),
+handle_call({set_module_level,Modules,Level}, _From, State) ->
+ Reply = logger_config:set_module_level(Modules,Level),
{reply,Reply,State};
-handle_call({unset_module_level,Modules}, _From, #state{tid=Tid}=State) ->
- Reply = logger_config:unset_module_level(Tid,Modules),
+handle_call({unset_module_level,Modules}, _From, State) ->
+ Reply = logger_config:unset_module_level(Modules),
{reply,Reply,State}.
handle_cast({async_req_reply,_Ref,_Reply} = Reply,State) ->
- call_h_reply(Reply,State);
-handle_cast({cache_module_level,Module}, #state{tid=Tid}=State) ->
- logger_config:cache_module_level(Tid,Module),
- {noreply, State}.
+ call_h_reply(Reply,State).
%% Interface for those who can't call the API - e.g. the emulator, or
%% places related to code loading.
@@ -359,12 +353,14 @@ handle_info(Unexpected,State) when element(1,Unexpected) == 'EXIT' ->
%% The simple handler will send an 'EXIT' message when it is replaced
%% We may as well ignore all 'EXIT' messages that we get
?LOG_INTERNAL(debug,
+ #{},
[{logger,got_unexpected_message},
{process,?SERVER},
{message,Unexpected}]),
{noreply,State};
handle_info(Unexpected,State) ->
?LOG_INTERNAL(info,
+ #{},
[{logger,got_unexpected_message},
{process,?SERVER},
{message,Unexpected}]),
@@ -550,6 +546,7 @@ call_h(Module, Function, Args, DefRet) ->
_ ->
ST = logger:filter_stacktrace(?MODULE,S),
?LOG_INTERNAL(error,
+ #{},
[{logger,callback_crashed},
{process,?SERVER},
{reason,{C,R,ST}}]),
@@ -592,6 +589,7 @@ call_h_reply({'DOWN',Ref,_Proc,Pid,Reason}, #state{ async_req = {Ref,_PostFun,_F
%% to the spawned process. It is only here to make sure that the logger_server does
%% not deadlock if that happens.
?LOG_INTERNAL(error,
+ #{},
[{logger,process_exited},
{process,Pid},
{reason,Reason}]),
@@ -600,6 +598,7 @@ call_h_reply({'DOWN',Ref,_Proc,Pid,Reason}, #state{ async_req = {Ref,_PostFun,_F
State);
call_h_reply(Unexpected,State) ->
?LOG_INTERNAL(info,
+ #{},
[{logger,got_unexpected_message},
{process,?SERVER},
{message,Unexpected}]),
@@ -615,3 +614,18 @@ diffs([{K,V1}|T1],[{K,V2}|T2],D1,D2) ->
diffs(T1,T2,D1#{K=>V1},D2#{K=>V2});
diffs([],[],D1,D2) ->
{D1,D2}.
+
+do_internal_log(Level,Location,Log,[Report] = Data) ->
+ do_internal_log(Level,Location,Log,Data,{report,Report});
+do_internal_log(Level,Location,Log,[Fmt,Args] = Data) ->
+ do_internal_log(Level,Location,Log,Data,{Fmt,Args}).
+do_internal_log(Level,Location,Log,Data,Msg) ->
+ Meta = logger:add_default_metadata(maps:merge(Location,maps:get(meta,Log,#{}))),
+ %% Spawn these to avoid deadlocks
+ case Log of
+ #{ meta := #{ internal_log_event := true } } ->
+ _ = spawn(logger_simple_h,log,[#{level=>Level,msg=>Msg,meta=>Meta},#{}]);
+ _ ->
+ _ = spawn(logger,macro_log,[Location,Level|Data]++
+ [Meta#{internal_log_event=>true}])
+ end.
diff --git a/lib/kernel/src/logger_simple_h.erl b/lib/kernel/src/logger_simple_h.erl
index deef6c521e..03cbdae437 100644
--- a/lib/kernel/src/logger_simple_h.erl
+++ b/lib/kernel/src/logger_simple_h.erl
@@ -61,15 +61,20 @@ log(#{meta:=#{error_logger:=#{tag:=info_report,type:=Type}}},_Config)
%% Skip info reports that are not 'std_info' (ref simple logger in
%% error_logger)
ok;
-log(#{msg:=_,meta:=#{time:=_}}=Log,_Config) ->
+log(#{msg:=_,meta:=#{time:=_}=M}=Log,_Config) ->
_ = case whereis(?MODULE) of
undefined ->
%% Is the node on the way down? Real emergency?
%% Log directly from client just to get it out
- do_log(
- #{level=>error,
- msg=>{report,{error,simple_handler_process_dead}},
- meta=>#{time=>logger:timestamp()}}),
+ case maps:get(internal_log_event, M, false) of
+ false ->
+ do_log(
+ #{level=>error,
+ msg=>{report,{error,simple_handler_process_dead}},
+ meta=>#{time=>logger:timestamp()}});
+ true ->
+ ok
+ end,
do_log(Log);
_ ->
?MODULE ! {log,Log}
diff --git a/lib/kernel/src/logger_std_h.erl b/lib/kernel/src/logger_std_h.erl
index c50cab01fd..13fe00b72d 100644
--- a/lib/kernel/src/logger_std_h.erl
+++ b/lib/kernel/src/logger_std_h.erl
@@ -150,6 +150,8 @@ check_h_config(Type,[{type,Type} | Config]) when Type =:= standard_io;
Type =:= standard_error;
Type =:= file ->
check_h_config(Type,Config);
+check_h_config({device,Device},[{type,{device,Device}} | Config]) ->
+ check_h_config({device,Device},Config);
check_h_config(file,[{file,File} | Config]) when is_list(File) ->
check_h_config(file,Config);
check_h_config(file,[{modes,Modes} | Config]) when is_list(Modes) ->
@@ -330,13 +332,22 @@ open_log_file(HandlerName,#{type:=file,
end.
close_log_file(#{fd:=Fd}) ->
- _ = file:datasync(Fd),
+ _ = file:datasync(Fd), %% file:datasync may return error as it will flush the delayed_write buffer
_ = file:close(Fd),
ok;
close_log_file(_) ->
ok.
-
+%% A special close that closes the FD properly when the delayed write close failed
+delayed_write_close(#{fd:=Fd}) ->
+ case file:close(Fd) of
+ %% We got an error while closing, could be a delayed write failing
+ %% So we close again in order to make sure the file is closed.
+ {error, _} ->
+ file:close(Fd);
+ Res ->
+ Res
+ end.
%%%-----------------------------------------------------------------
%%% File control process
@@ -410,6 +421,9 @@ file_ctrl_init(HandlerName,
{error,Reason} ->
Starter ! {self(),{error,{open_failed,FileName,Reason}}}
end;
+file_ctrl_init(HandlerName, #{type:={device,Dev}}, Starter) ->
+ Starter ! {self(),ok},
+ file_ctrl_loop(#{handler_name=>HandlerName,dev=>Dev});
file_ctrl_init(HandlerName, #{type:=StdDev}, Starter) ->
Starter ! {self(),ok},
file_ctrl_loop(#{handler_name=>HandlerName,dev=>StdDev}).
@@ -556,10 +570,9 @@ maybe_rotate_file(AddSize,#{rotation:=#{size:=RotSize,
maybe_rotate_file(_Bin,State) ->
State.
-rotate_file(#{fd:=Fd0,file_name:=FileName,modes:=Modes,rotation:=Rotation}=State) ->
+rotate_file(#{file_name:=FileName,modes:=Modes,rotation:=Rotation}=State) ->
State1 = sync_dev(State),
- _ = file:close(Fd0),
- _ = file:close(Fd0),
+ _ = delayed_write_close(State),
rotate_files(FileName,maps:get(count,Rotation),maps:get(compress,Rotation)),
case file:open(FileName,Modes) of
{ok,Fd} ->
diff --git a/lib/kernel/src/net.erl b/lib/kernel/src/net.erl
index fcc78d6971..905114ab7c 100644
--- a/lib/kernel/src/net.erl
+++ b/lib/kernel/src/net.erl
@@ -69,12 +69,12 @@
]).
--deprecated({call, 4, eventually}).
--deprecated({cast, 4, eventually}).
--deprecated({broadcast, 3, eventually}).
--deprecated({ping, 1, eventually}).
--deprecated({relay, 1, eventually}).
--deprecated({sleep, 1, eventually}).
+-deprecated({call, 4, "use rpc:call/4 instead"}).
+-deprecated({cast, 4, "use rpc:cast/4 instead"}).
+-deprecated({broadcast, 3, "use rpc:eval_everywhere/3 instead"}).
+-deprecated({ping, 1, "use net_adm:ping/1 instead"}).
+-deprecated({relay, 1, "use slave:relay/1 instead"}).
+-deprecated({sleep, 1, "use 'receive after T -> ok end' instead"}).
-type ifaddrs_flag() :: up | broadcast | debug | loopback | pointopoint |
@@ -152,7 +152,6 @@ relay(X) -> slave:relay(X).
info() ->
prim_net:info().
-else.
--dialyzer({nowarn_function, info/0}).
info() ->
erlang:error(notsup).
-endif.
@@ -164,7 +163,6 @@ info() ->
command(Cmd) ->
prim_net:command(Cmd).
-else.
--dialyzer({nowarn_function, command/1}).
command(_Cmd) ->
erlang:error(notsup).
-endif.
@@ -191,7 +189,6 @@ command(_Cmd) ->
gethostname() ->
prim_net:gethostname().
-else.
--dialyzer({nowarn_function, gethostname/0}).
gethostname() ->
erlang:error(notsup).
-endif.
@@ -208,6 +205,8 @@ gethostname() ->
Info :: name_info(),
Reason :: term().
+-dialyzer({no_return, getnameinfo/1}).
+
getnameinfo(SockAddr) ->
getnameinfo(SockAddr, undefined).
@@ -226,25 +225,18 @@ getnameinfo(SockAddr) ->
-endif.
-ifdef(USE_ESOCK).
-getnameinfo(SockAddr, [] = _Flags) ->
- getnameinfo(SockAddr, undefined);
-getnameinfo(#{family := Fam, addr := _Addr} = SockAddr, Flags)
- when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso
- (is_list(Flags) orelse (Flags =:= undefined)) ->
- prim_net:getnameinfo(socket:ensure_sockaddr(SockAddr), Flags);
-getnameinfo(#{family := Fam, path := _Path} = SockAddr, Flags)
- when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) ->
+getnameinfo(SockAddr, Flags)
+ when is_map(SockAddr), is_list(Flags);
+ is_map(SockAddr), Flags =:= undefined ->
prim_net:getnameinfo(SockAddr, Flags).
+
-else.
--dialyzer({nowarn_function, getnameinfo/2}).
-getnameinfo(SockAddr, [] = _Flags) ->
- getnameinfo(SockAddr, undefined);
-getnameinfo(#{family := Fam, addr := _Addr} = _SockAddr, Flags)
- when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso
- (is_list(Flags) orelse (Flags =:= undefined)) ->
- erlang:error(notsup);
-getnameinfo(#{family := Fam, path := _Path} = _SockAddr, Flags)
- when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) ->
+
+-dialyzer({no_return, getnameinfo/2}).
+
+getnameinfo(SockAddr, Flags)
+ when is_map(SockAddr), is_list(Flags);
+ is_map(SockAddr), Flags =:= undefined ->
erlang:error(notsup).
-endif.
@@ -260,6 +252,8 @@ getnameinfo(#{family := Fam, path := _Path} = _SockAddr, Flags)
Info :: [address_info()],
Reason :: term().
+-dialyzer({no_return, getaddrinfo/1}).
+
getaddrinfo(Host) when is_list(Host) ->
getaddrinfo(Host, undefined).
@@ -285,7 +279,6 @@ getaddrinfo(Host, Service)
(not ((Service =:= undefined) andalso (Host =:= undefined))) ->
prim_net:getaddrinfo(Host, Service).
-else.
--dialyzer({nowarn_function, getaddrinfo/2}).
getaddrinfo(Host, Service)
when (is_list(Host) orelse (Host =:= undefined)) andalso
(is_list(Service) orelse (Service =:= undefined)) andalso
@@ -332,7 +325,6 @@ getifaddrs(Filter) when is_function(Filter, 1) ->
getifaddrs(Namespace) when is_list(Namespace) ->
prim_net:getifaddrs(#{netns => Namespace}).
-else.
--dialyzer({nowarn_function, getifaddrs/1}).
getifaddrs(Filter) when is_atom(Filter) orelse
is_map(Filter) orelse
is_function(Filter) ->
@@ -348,6 +340,8 @@ getifaddrs(Namespace) when is_list(Namespace) ->
IfAddrs :: [ifaddrs()],
Reason :: term().
+-dialyzer({no_return, getifaddrs/2}).
+
getifaddrs(Filter, Namespace)
when (is_atom(Filter) orelse is_map(Filter)) andalso is_list(Namespace) ->
do_getifaddrs(getifaddrs_filter_map(Filter),
@@ -356,6 +350,8 @@ getifaddrs(Filter, Namespace)
when is_function(Filter, 1) andalso is_list(Namespace) ->
do_getifaddrs(Filter, fun() -> getifaddrs(Namespace) end).
+-dialyzer({no_return, do_getifaddrs/2}).
+
do_getifaddrs(Filter, GetIfAddrs) ->
case GetIfAddrs() of
{ok, IfAddrs0} when is_function(Filter) ->
@@ -395,6 +391,8 @@ getifaddrs_filter_map_inet6() ->
getifaddrs_filter_map_packet() ->
#{family => packet, flags => any}.
+-compile({nowarn_unused_function, getifaddrs_filter/2}).
+
getifaddrs_filter(#{family := FFamily, flags := FFlags},
#{addr := #{family := Family}, flags := Flags} = _Entry)
when (FFamily =:= default) andalso
@@ -419,6 +417,8 @@ getifaddrs_filter(#{family := FFamily, flags := FFlags},
getifaddrs_filter(_Filter, _Entry) ->
false.
+-compile({nowarn_unused_function, getifaddrs_filter_flags/2}).
+
getifaddrs_filter_flags(any, _Flags) ->
true;
getifaddrs_filter_flags(FilterFlags, Flags) ->
@@ -442,7 +442,6 @@ getifaddrs_filter_flags(FilterFlags, Flags) ->
if_name2index(If) when is_list(If) ->
prim_net:if_name2index(If).
-else.
--dialyzer({nowarn_function, if_name2index/1}).
if_name2index(If) when is_list(If) ->
erlang:error(notsup).
-endif.
@@ -465,7 +464,6 @@ if_name2index(If) when is_list(If) ->
if_index2name(Idx) when is_integer(Idx) ->
prim_net:if_index2name(Idx).
-else.
--dialyzer({nowarn_function, if_index2name/1}).
if_index2name(Idx) when is_integer(Idx) ->
erlang:error(notsup).
-endif.
@@ -488,7 +486,6 @@ if_index2name(Idx) when is_integer(Idx) ->
if_names() ->
prim_net:if_names().
-else.
--dialyzer({nowarn_function, if_names/0}).
if_names() ->
erlang:error(notsup).
-endif.
diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl
index b857000a80..8dfae3a505 100644
--- a/lib/kernel/src/net_kernel.erl
+++ b/lib/kernel/src/net_kernel.erl
@@ -64,11 +64,13 @@
%% Exports for internal use.
--export([start_link/2,
+-export([start_link/3,
kernel_apply/3,
longnames/0,
+ nodename/0,
protocol_childspecs/0,
- epmd_module/0]).
+ epmd_module/0,
+ dist_listen/0]).
-export([disconnect/1, passive_cnct/1]).
-export([hidden_connect_node/1]).
@@ -95,8 +97,9 @@
-import(error_logger,[error_msg/2]).
+-type node_name_type() :: static | dynamic.
+
-record(state, {
- name, %% The node name
node, %% The node name including hostname
type, %% long or short names
tick, %% tick information
@@ -108,7 +111,9 @@
listen, %% list of #listen
allowed, %% list of allowed nodes in a restricted system
verbose = 0, %% level of verboseness
- publish_on_nodes = undefined
+ publish_on_nodes = undefined,
+ dyn_name_pool = #{}, %% Reusable remote node names: #{Host => [{Name,Creation}]}
+ supervisor %% Our supervisor (net_sup | net_sup_dynamic | {restart,Restarter})
}).
-record(listen, {
@@ -134,9 +139,12 @@
owner :: pid() | '_', %% owner pid
ctrlr, %% Controller port or pid
pending_owner :: pid() | '_' | undefined, %% possible new owner
- address :: #net_address{} | '_',
+ address = #net_address{} :: #net_address{} | '_',
waiting = [], %% queued processes
- type :: connection_type() | '_'
+ type :: connection_type() | '_',
+ remote_name_type :: node_name_type() | '_',
+ creation :: integer() | '_' | undefined, %% only set if remote_name_type == dynamic
+ named_me = false :: boolean() | '_' %% did peer gave me my node name?
}).
-record(barred_connection, {
@@ -180,9 +188,16 @@ allowed() -> request(allowed).
longnames() -> request(longnames).
+nodename() -> request(nodename).
+
-spec stop() -> ok | {error, Reason} when
Reason :: not_allowed | not_found.
-stop() -> erl_distribution:stop().
+stop() ->
+ case persistent_term:get(net_kernel, undefined) of
+ undefined -> ok;
+ _ -> persistent_term:erase(net_kernel)
+ end,
+ erl_distribution:stop().
-type node_info() ::
{address, #net_address{}} |
@@ -321,11 +336,33 @@ passive_connect_monitor(From, Node) ->
%% kernel, thus basically accepting them :-)
request(Req) ->
case whereis(net_kernel) of
- P when is_pid(P) ->
- gen_server:call(net_kernel,Req,infinity);
- _ -> ignored
+ P when is_pid(P) ->
+ try
+ gen_server:call(net_kernel,Req,infinity)
+ catch
+ exit:{Reason,_} when Reason =:= noproc;
+ Reason =:= shutdown;
+ Reason =:= killed ->
+ retry_request_maybe(Req)
+ end;
+ _ ->
+ retry_request_maybe(Req)
+ end.
+
+retry_request_maybe(Req) ->
+ case persistent_term:get(net_kernel, undefined) of
+ dynamic_node_name ->
+ %% net_kernel must be restarting due to lost connection
+ %% toward the node that named us.
+ %% We want reconnection attempts to succeed so we wait and retry.
+ receive after 100 -> ok end,
+ request(Req);
+
+ _ ->
+ ignored
end.
+
%% This function is used to dynamically start the
%% distribution.
@@ -335,13 +372,13 @@ start(Args) ->
%% This is the main startup routine for net_kernel (only for internal
%% use by the Kernel application.
-start_link([Name], CleanHalt) ->
- start_link([Name, longnames], CleanHalt);
-start_link([Name, LongOrShortNames], CleanHalt) ->
- start_link([Name, LongOrShortNames, 15000], CleanHalt);
+start_link([Name], CleanHalt, NetSup) ->
+ start_link([Name, longnames], CleanHalt, NetSup);
+start_link([Name, LongOrShortNames], CleanHalt, NetSup) ->
+ start_link([Name, LongOrShortNames, 15000], CleanHalt, NetSup);
-start_link([Name, LongOrShortNames, Ticktime], CleanHalt) ->
- Args = {Name, LongOrShortNames, Ticktime, CleanHalt},
+start_link([Name, LongOrShortNames, Ticktime], CleanHalt, NetSup) ->
+ Args = {Name, LongOrShortNames, Ticktime, CleanHalt, NetSup},
case gen_server:start_link({local, net_kernel}, ?MODULE,
Args, []) of
{ok, Pid} ->
@@ -352,15 +389,14 @@ start_link([Name, LongOrShortNames, Ticktime], CleanHalt) ->
exit(nodistribution)
end.
-init({Name, LongOrShortNames, TickT, CleanHalt}) ->
+init({Name, LongOrShortNames, TickT, CleanHalt, NetSup}) ->
process_flag(trap_exit,true),
case init_node(Name, LongOrShortNames, CleanHalt) of
{ok, Node, Listeners} ->
process_flag(priority, max),
Ticktime = to_integer(TickT),
Ticker = spawn_link(net_kernel, ticker, [self(), Ticktime]),
- {ok, #state{name = Name,
- node = Node,
+ {ok, #state{node = Node,
type = LongOrShortNames,
tick = #tick{ticker = Ticker, time = Ticktime},
connecttime = connecttime(),
@@ -370,7 +406,8 @@ init({Name, LongOrShortNames, TickT, CleanHalt}) ->
{keypos, #connection.node}]),
listen = Listeners,
allowed = [],
- verbose = 0
+ verbose = 0,
+ supervisor = NetSup
}};
Error ->
{stop, Error}
@@ -483,6 +520,8 @@ handle_call({passive_cnct, Node}, From, State) ->
%%
handle_call({connect, _, Node}, From, State) when Node =:= node() ->
async_reply({reply, true, State}, From);
+handle_call({connect, _Type, _Node}, _From, #state{supervisor = {restart,_}}=State) ->
+ {noreply, State};
handle_call({connect, Type, Node}, From, State) ->
verbose({connect, Type, Node}, 1, State),
ConnLookup = ets:lookup(sys_dist, Node),
@@ -570,6 +609,9 @@ handle_call({apply,_Mod,_Fun,_Args}, {From,Tag}, State)
handle_call(longnames, From, State) ->
async_reply({reply, get(longnames), State}, From);
+handle_call(nodename, From, State) ->
+ async_reply({reply, State#state.node, State}, From);
+
handle_call({update_publish_nodes, Ns}, From, State) ->
async_reply({reply, ok, State#state{publish_on_nodes = Ns}}, From);
@@ -686,18 +728,21 @@ code_change(_OldVsn, State, _Extra) ->
%% terminate.
%% ------------------------------------------------------------
-terminate(no_network, State) ->
- lists:foreach(
- fun(Node) -> ?nodedown(Node, State)
- end, get_nodes_up_normal() ++ [node()]);
-terminate(_Reason, State) ->
- lists:foreach(
- fun(#listen {listen = Listen,module = Mod}) ->
- Mod:close(Listen)
- end, State#state.listen),
- lists:foreach(
- fun(Node) -> ?nodedown(Node, State)
- end, get_nodes_up_normal() ++ [node()]).
+terminate(Reason, State) ->
+ case Reason of
+ no_network ->
+ ok;
+ _ ->
+ lists:foreach(
+ fun(#listen {listen = Listen,module = Mod}) ->
+ case Listen of
+ undefined -> ignore;
+ _ -> Mod:close(Listen)
+ end
+ end, State#state.listen)
+ end,
+ lists:foreach(fun(Node) -> ?nodedown(Node, State)
+ end, get_nodes_up_normal() ++ [node()]).
%% ------------------------------------------------------------
%% handle_info.
@@ -726,12 +771,11 @@ handle_info({auto_connect,Node, DHandle}, State) ->
%% accept a new connection.
%%
handle_info({accept,AcceptPid,Socket,Family,Proto}, State) ->
- MyNode = State#state.node,
case get_proto_mod(Family,Proto,State#state.listen) of
{ok, Mod} ->
Pid = Mod:accept_connection(AcceptPid,
Socket,
- MyNode,
+ State#state.node,
State#state.allowed,
State#state.connecttime),
AcceptPid ! {self(), controller, Pid},
@@ -763,18 +807,23 @@ handle_info({dist_ctrlr, Ctrlr, Node, SetupPid} = Msg,
%%
%% A node has successfully been connected.
%%
-handle_info({SetupPid, {nodeup,Node,Address,Type,Immediate}}, State) ->
- case {Immediate, ets:lookup(sys_dist, Node)} of
- {true, [Conn]} when (Conn#connection.state =:= pending)
- andalso (Conn#connection.owner =:= SetupPid)
- andalso (Conn#connection.ctrlr /= undefined) ->
+handle_info({SetupPid, {nodeup,Node,Address,Type,NamedMe}}, State) ->
+ case ets:lookup(sys_dist, Node) of
+ [Conn] when (Conn#connection.state =:= pending)
+ andalso (Conn#connection.owner =:= SetupPid)
+ andalso (Conn#connection.ctrlr /= undefined) ->
ets:insert(sys_dist, Conn#connection{state = up,
address = Address,
waiting = [],
- type = Type}),
+ type = Type,
+ named_me = NamedMe}),
SetupPid ! {self(), inserted},
reply_waiting(Node,Conn#connection.waiting, true),
- {noreply, State};
+ State1 = case NamedMe of
+ true -> State#state{node = node()};
+ false -> State
+ end,
+ {noreply, State1};
_ ->
SetupPid ! {self(), bad_request},
{noreply, State}
@@ -783,8 +832,10 @@ handle_info({SetupPid, {nodeup,Node,Address,Type,Immediate}}, State) ->
%%
%% Mark a node as pending (accept) if not busy.
%%
-handle_info({AcceptPid, {accept_pending,MyNode,Node,Address,Type}}, State) ->
- case ets:lookup(sys_dist, Node) of
+handle_info({AcceptPid, {accept_pending,MyNode,NodeOrHost,Type}}, State0) ->
+ {NameType, Node, Creation,
+ ConnLookup, State} = ensure_node_name(NodeOrHost, State0),
+ case ConnLookup of
[#connection{state=pending}=Conn] ->
if
MyNode > Node ->
@@ -823,9 +874,14 @@ handle_info({AcceptPid, {accept_pending,MyNode,Node,Address,Type}}, State) ->
conn_id = ConnId,
state = pending,
owner = AcceptPid,
- address = Address,
- type = Type}),
- AcceptPid ! {self(),{accept_pending,ok}},
+ type = Type,
+ remote_name_type = NameType,
+ creation = Creation}),
+ Ret = case NameType of
+ static -> ok;
+ dynamic-> {ok, Node, Creation}
+ end,
+ AcceptPid ! {self(),{accept_pending,Ret}},
Owners = State#state.conn_owners,
{noreply, State#state{conn_owners = Owners#{AcceptPid => Node}}}
catch
@@ -905,6 +961,43 @@ handle_info(X, State) ->
error_msg("Net kernel got ~tw~n",[X]),
{noreply,State}.
+ensure_node_name(Node, State) when is_atom(Node) ->
+ {static, Node, undefined, ets:lookup(sys_dist, Node), State};
+ensure_node_name(Host, State0) when is_list(Host) ->
+ case string:split(Host, "@", all) of
+ [Host] ->
+ {Node, Creation, State1} = generate_node_name(Host, State0),
+ case ets:lookup(sys_dist, Node) of
+ [#connection{}] ->
+ %% Either a static named connection setup used a recycled
+ %% dynamic name or we have an unlikely random dynamic
+ %% name clash. Either way try again.
+ ensure_node_name(Host, State1);
+
+ ConnLookup ->
+ {dynamic, Node, Creation, ConnLookup, State1}
+ end;
+
+ _ ->
+ {error, Host, undefined, [], State0}
+ end.
+
+generate_node_name(Host, State0) ->
+ NamePool = State0#state.dyn_name_pool,
+ case maps:get(Host, NamePool, []) of
+ [] ->
+ Name = integer_to_list(rand:uniform(1 bsl 64), 36),
+ {list_to_atom(Name ++ "@" ++ Host),
+ create_creation(),
+ State0};
+
+ [{Node,Creation} | Rest] ->
+ {Node, Creation,
+ State0#state{dyn_name_pool = NamePool#{Host => Rest}}}
+ end.
+
+
+
%% -----------------------------------------------------------
%% Handle exit signals.
%% We have 6 types of processes to handle.
@@ -929,6 +1022,7 @@ do_handle_exit(Pid, Reason, State) ->
dist_ctrlr_exit(Pid, Reason, State),
pending_own_exit(Pid, State),
ticker_exit(Pid, State),
+ restarter_exit(Pid, State),
{noreply,State}.
listen_exit(Pid, State) ->
@@ -995,6 +1089,16 @@ ticker_exit(Pid, #state{tick = #tick_change{ticker = Pid,
ticker_exit(_, _) ->
false.
+restarter_exit(Pid, State) ->
+ case State#state.supervisor of
+ {restart, Pid} ->
+ error_msg("** Distribution restart failed, net_kernel terminating... **\n", []),
+ throw({stop, restarter_exit, State});
+ _ ->
+ false
+ end.
+
+
%% -----------------------------------------------------------
%% A node has gone down !!
%% nodedown(Owner, Node, Reason, State) -> State'
@@ -1034,33 +1138,32 @@ nodedown(Conn, Exited, Node, Reason, Type, State) ->
pending_nodedown(#connection{owner = Owner,
waiting = Waiting,
- conn_id = CID},
- Exited, Node, Type, State) when Owner =:= Exited ->
+ conn_id = CID} = Conn,
+ Exited, Node, Type, State0) when Owner =:= Exited ->
%% Owner exited!
- case erts_internal:abort_pending_connection(Node, CID) of
- false ->
- %% Just got connected but that message has not
- %% reached us yet. Wait for controller to exit and
- %% handle this then...
- ok;
- true ->
- %% Don't bar connections that have never been alive, i.e.
- %% no 'mark_sys_dist_nodedown(Node)'; instead just delete
- %% the node:
- ets:delete(sys_dist, Node),
- reply_waiting(Node, Waiting, false),
- case Type of
- normal ->
- ?nodedown(Node, State);
- _ ->
- ok
- end
+ State2 = case erts_internal:abort_pending_connection(Node, CID) of
+ false ->
+ %% Just got connected but that message has not
+ %% reached us yet. Wait for controller to exit and
+ %% handle this then...
+ State0;
+ true ->
+ %% Don't bar connections that have never been alive
+ State1 = delete_connection(Conn, false, State0),
+ reply_waiting(Node, Waiting, false),
+ case Type of
+ normal ->
+ ?nodedown(Node, State1);
+ _ ->
+ ok
+ end,
+ State1
end,
- delete_owner(Owner, State);
+ delete_owner(Owner, State2);
pending_nodedown(#connection{owner = Owner,
ctrlr = Ctrlr,
- waiting = Waiting},
- Exited, Node, Type, State) when Ctrlr =:= Exited ->
+ waiting = Waiting} = Conn,
+ Exited, Node, Type, State0) when Ctrlr =:= Exited ->
%% Controller exited!
%%
%% Controller has been registered but crashed
@@ -1068,15 +1171,15 @@ pending_nodedown(#connection{owner = Owner,
%%
%% 'nodeup' messages has been sent by the emulator,
%% so bar the connection...
- mark_sys_dist_nodedown(Node),
+ State1 = delete_connection(Conn, true, State0),
reply_waiting(Node,Waiting, true),
case Type of
normal ->
- ?nodedown(Node, State);
+ ?nodedown(Node, State1);
_ ->
ok
end,
- delete_owner(Owner, delete_ctrlr(Ctrlr, State));
+ delete_owner(Owner, delete_ctrlr(Ctrlr, State1));
pending_nodedown(_Conn, _Exited, _Node, _Type, State) ->
State.
@@ -1107,15 +1210,15 @@ up_pending_nodedown(_Conn, _Exited, _Node, _Reason, _Type, State) ->
State.
up_nodedown(#connection{owner = Owner,
- ctrlr = Ctrlr},
- Exited, Node, _Reason, Type, State) when Ctrlr =:= Exited ->
+ ctrlr = Ctrlr} = Conn,
+ Exited, Node, _Reason, Type, State0) when Ctrlr =:= Exited ->
%% Controller exited!
- mark_sys_dist_nodedown(Node),
+ State1 = delete_connection(Conn, true, State0),
case Type of
- normal -> ?nodedown(Node, State);
+ normal -> ?nodedown(Node, State1);
_ -> ok
end,
- delete_owner(Owner, delete_ctrlr(Ctrlr, State));
+ delete_owner(Owner, delete_ctrlr(Ctrlr, State1));
up_nodedown(#connection{owner = Owner},
Exited, _Node, _Reason,
_Type, State) when Owner =:= Exited ->
@@ -1124,17 +1227,49 @@ up_nodedown(#connection{owner = Owner},
up_nodedown(_Conn, _Exited, _Node, _Reason, _Type, State) ->
State.
-mark_sys_dist_nodedown(Node) ->
- case application:get_env(kernel, dist_auto_connect) of
- {ok, once} ->
+delete_connection(#connection{named_me = true}, _, State) ->
+ restart_distr(State);
+
+delete_connection(#connection{node = Node}=Conn, MayBeBarred, State) ->
+ BarrIt = MayBeBarred andalso
+ case application:get_env(kernel, dist_auto_connect) of
+ {ok, once} ->
+ true;
+ _ ->
+ false
+ end,
+ case BarrIt of
+ true ->
ets:insert(sys_dist, #barred_connection{node = Node});
_ ->
ets:delete(sys_dist, Node)
+ end,
+ case Conn#connection.remote_name_type of
+ dynamic ->
+ %% Return remote node name to pool
+ [_Name,Host] = string:split(atom_to_list(Node), "@", all),
+ NamePool0 = State#state.dyn_name_pool,
+ DynNames = maps:get(Host, NamePool0, []),
+ false = lists:keyfind(Node, 1, DynNames), % ASSERT
+ FreeName = {Node, next_creation(Conn#connection.creation)},
+ NamePool1 = NamePool0#{Host => [FreeName | DynNames]},
+ State#state{dyn_name_pool = NamePool1};
+
+ static ->
+ State
end.
-%% -----------------------------------------------------------
-%% End handle_exit/2 !!
-%% -----------------------------------------------------------
+restart_distr(State) ->
+ Restarter = spawn_link(fun() -> restart_distr_do(State#state.supervisor) end),
+ State#state{supervisor = {restart, Restarter}}.
+
+restart_distr_do(NetSup) ->
+ process_flag(trap_exit,true),
+ ok = supervisor:terminate_child(kernel_sup, NetSup),
+ case supervisor:restart_child(kernel_sup, NetSup) of
+ {ok, Pid} when is_pid(Pid) ->
+ ok
+ end.
%% -----------------------------------------------------------
%% monitor_nodes/[1,2] errors
@@ -1233,7 +1368,12 @@ disconnect_ctrlr(Ctrlr, State) ->
%% Return a list of all nodes that are 'up' and not hidden.
get_nodes_up_normal() ->
- ets:select(sys_dist, [{#connection{node = '$1', state = up, type = normal, _ = '_'}, [], ['$1']}]).
+ ets:select(sys_dist, [{#connection{node = '$1',
+ state = up,
+ type = normal,
+ _ = '_'},
+ [],
+ ['$1']}]).
ticker(Kernel, Tick) when is_integer(Tick) ->
process_flag(priority, max),
@@ -1363,7 +1503,8 @@ setup(Node, ConnId, Type, From, State) ->
owner = Pid,
waiting = Waiting,
address = Addr,
- type = normal}),
+ type = normal,
+ remote_name_type = static}),
{ok, Pid};
Error ->
Error
@@ -1397,7 +1538,8 @@ select_mod(Node, []) ->
get_proto_mod(Family,Protocol,[L|Ls]) ->
A = L#listen.address,
- if A#net_address.family =:= Family,
+ if L#listen.accept =/= undefined,
+ A#net_address.family =:= Family,
A#net_address.protocol =:= Protocol ->
{ok, L#listen.module};
true ->
@@ -1409,11 +1551,9 @@ get_proto_mod(_Family, _Protocol, []) ->
%% -------- Initialisation functions ------------------------
init_node(Name, LongOrShortNames, CleanHalt) ->
- {NameWithoutHost0,_Host} = split_node(Name),
case create_name(Name, LongOrShortNames, 1) of
{ok,Node} ->
- NameWithoutHost = list_to_atom(NameWithoutHost0),
- case start_protos(NameWithoutHost, Node, CleanHalt) of
+ case start_protos(Node, CleanHalt) of
{ok, Ls} ->
{ok, Node, Ls};
Error ->
@@ -1450,9 +1590,7 @@ create_name(Name, LongOrShortNames, Try) ->
{error, badarg};
{error,Type} ->
error_logger:info_msg(
- lists:concat(["Can\'t set ",
- Type,
- " node name!\n"
+ lists:concat(["Can't set ", Type, " node name!\n"
"Please check your configuration\n"])),
{error,badarg}
end.
@@ -1542,19 +1680,39 @@ epmd_module() ->
end.
%%
+%% dist_listen() -> whether the erlang distribution should listen for connections
+%%
+dist_listen() ->
+ case persistent_term:get(net_kernel, undefined) of
+ dynamic_node_name ->
+ false;
+ _ ->
+ case init:get_argument(dist_listen) of
+ {ok,[[DoListen]]} ->
+ list_to_atom(DoListen) =/= false;
+ _ ->
+ true
+ end
+ end.
+
+%%
%% Start all protocols
%%
-start_protos(Name, Node, CleanHalt) ->
+start_protos(Node, CleanHalt) ->
case init:get_argument(proto_dist) of
{ok, [Protos]} ->
- start_protos(Name, Protos, Node, CleanHalt);
+ start_protos(Node, Protos, CleanHalt);
_ ->
- start_protos(Name, ["inet_tcp"], Node, CleanHalt)
+ start_protos(Node, ["inet_tcp"], CleanHalt)
end.
-start_protos(Name, Ps, Node, CleanHalt) ->
- case start_protos(Name, Ps, Node, [], CleanHalt) of
+start_protos(Node, Ps, CleanHalt) ->
+ Listeners = case dist_listen() of
+ false -> start_protos_no_listen(Node, Ps, [], CleanHalt);
+ _ -> start_protos_listen(Node, Ps, CleanHalt)
+ end,
+ case Listeners of
[] ->
case CleanHalt of
true -> halt(1);
@@ -1564,42 +1722,98 @@ start_protos(Name, Ps, Node, CleanHalt) ->
{ok, Ls}
end.
-start_protos(Name, [Proto | Ps], Node, Ls, CleanHalt) ->
+start_protos_no_listen(Node, [Proto | Ps], Ls, CleanHalt) ->
+ {Name, "@"++_Host} = split_node(Node),
+ Ok = case Name of
+ "undefined" ->
+ persistent_term:put(net_kernel,dynamic_node_name),
+ true;
+ _ ->
+ (set_node(Node, create_creation()) =:= ok)
+ end,
+ case Ok of
+ true ->
+ auth:sync_cookie(),
+ Mod = list_to_atom(Proto ++ "_dist"),
+ L = #listen {
+ listen = undefined,
+ address = Mod:address(),
+ accept = undefined,
+ module = Mod },
+ start_protos_no_listen(Node, Ps, [L|Ls], CleanHalt);
+ false ->
+ S = "invalid node name: " ++ atom_to_list(Node),
+ proto_error(CleanHalt, Proto, S),
+ start_protos_no_listen(Node, Ps, Ls, CleanHalt)
+ end;
+start_protos_no_listen(_Node, [], Ls, _CleanHalt) ->
+ Ls.
+
+create_creation() ->
+ Cr = try binary:decode_unsigned(crypto:strong_rand_bytes(4)) of
+ Creation ->
+ Creation
+ catch _:_ ->
+ rand:uniform((1 bsl 32)-1)
+ end,
+ wrap_creation(Cr).
+
+next_creation(Creation) ->
+ wrap_creation(Creation + 1).
+
+%% Avoid small creations 0,1,2,3
+wrap_creation(Cr) when Cr >= 4 andalso Cr < (1 bsl 32) ->
+ Cr;
+wrap_creation(Cr) ->
+ wrap_creation((Cr + 4) band ((1 bsl 32) - 1)).
+
+
+start_protos_listen(Node, Ps, CleanHalt) ->
+ case split_node(Node) of
+ {"undefined", _} ->
+ start_protos_no_listen(Node, Ps, [], CleanHalt);
+ {Name, "@"++Host} ->
+ start_protos_listen(list_to_atom(Name), Host, Node, Ps, [], CleanHalt)
+ end.
+start_protos_listen(Name, Host, Node, [Proto | Ps], Ls, CleanHalt) ->
Mod = list_to_atom(Proto ++ "_dist"),
- case catch Mod:listen(Name) of
- {ok, {Socket, Address, Creation}} ->
- case set_node(Node, Creation) of
- ok ->
- AcceptPid = Mod:accept(Socket),
- auth:sync_cookie(),
- L = #listen {
- listen = Socket,
- address = Address,
- accept = AcceptPid,
- module = Mod },
- start_protos(Name,Ps, Node, [L|Ls], CleanHalt);
- _ ->
- Mod:close(Socket),
- S = "invalid node name: " ++ atom_to_list(Node),
- proto_error(CleanHalt, Proto, S),
- start_protos(Name, Ps, Node, Ls, CleanHalt)
- end;
- {'EXIT', {undef,_}} ->
- proto_error(CleanHalt, Proto, "not supported"),
- start_protos(Name, Ps, Node, Ls, CleanHalt);
- {'EXIT', Reason} ->
- register_error(CleanHalt, Proto, Reason),
- start_protos(Name, Ps, Node, Ls, CleanHalt);
- {error, duplicate_name} ->
- S = "the name " ++ atom_to_list(Node) ++
- " seems to be in use by another Erlang node",
- proto_error(CleanHalt, Proto, S),
- start_protos(Name, Ps, Node, Ls, CleanHalt);
- {error, Reason} ->
- register_error(CleanHalt, Proto, Reason),
- start_protos(Name, Ps, Node, Ls, CleanHalt)
+ try try Mod:listen(Name,Host)
+ catch error:undef ->
+ Mod:listen(Name)
+ end of
+ {ok, {Socket, Address, Creation}} ->
+ case set_node(Node, Creation) of
+ ok ->
+ AcceptPid = Mod:accept(Socket),
+ auth:sync_cookie(),
+ L = #listen{
+ listen = Socket,
+ address = Address,
+ accept = AcceptPid,
+ module = Mod },
+ start_protos_listen(Name, Host, Node, Ps, [L|Ls], CleanHalt);
+ _ ->
+ Mod:close(Socket),
+ S = "invalid node name: " ++ atom_to_list(Node),
+ proto_error(CleanHalt, Proto, S),
+ start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt)
+ end;
+ {error, duplicate_name} ->
+ S = "the name " ++ atom_to_list(Node) ++
+ " seems to be in use by another Erlang node",
+ proto_error(CleanHalt, Proto, S),
+ start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt);
+ {error, Reason} ->
+ register_error(CleanHalt, Proto, Reason),
+ start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt)
+ catch error:undef ->
+ proto_error(CleanHalt, Proto, "not supported"),
+ start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt);
+ error:Reason ->
+ register_error(CleanHalt, Proto, Reason),
+ start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt)
end;
-start_protos(_, [], _Node, Ls, _CleanHalt) ->
+start_protos_listen(_Name, _Host, _Node, [], Ls, _CleanHalt) ->
Ls.
register_error(false, Proto, Reason) ->
@@ -1619,12 +1833,14 @@ proto_error(CleanHalt, Proto, String) ->
erlang:display_string(S)
end.
+set_node(Node, Creation) when Creation < 0 ->
+ set_node(Node, create_creation());
set_node(Node, Creation) when node() =:= nonode@nohost ->
case catch erlang:setnode(Node, Creation) of
- true ->
- ok;
- {'EXIT',Reason} ->
- {error,Reason}
+ true ->
+ ok;
+ {'EXIT',Reason} ->
+ {error,Reason}
end;
set_node(Node, _Creation) when node() =:= Node ->
ok.
@@ -1660,9 +1876,9 @@ get_node_info(Node) ->
end;
[#connection{owner = Owner, state = State, address = Addr, type = Type}] ->
{ok, [{owner, Owner}, {state, State}, {address, Addr},
- {type, Type}, {in, 0}, {out, 0}]};
- _ ->
- {error, bad_node}
+ {type, Type}, {in, 0}, {out, 0}]};
+ _ ->
+ {error, bad_node}
end.
get_node_info(Node, Key) ->
diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl
index 29a26674ba..b1488f1390 100644
--- a/lib/kernel/src/os.erl
+++ b/lib/kernel/src/os.erl
@@ -293,7 +293,11 @@ mk_cmd({win32,Wtype}, Cmd) ->
{Command, [], [], <<>>};
mk_cmd(_,Cmd) ->
%% Have to send command in like this in order to make sh commands like
- %% cd and ulimit available
+ %% cd and ulimit available.
+ %%
+ %% We use an absolute path here because we do not want the path to be
+ %% searched in case a stale NFS handle is somewhere in the path before
+ %% the sh command.
{"/bin/sh -s unix:cmd", [out],
%% We insert a new line after the command, in case the command
%% contains a comment character.
diff --git a/lib/kernel/src/pg.erl b/lib/kernel/src/pg.erl
new file mode 100644
index 0000000000..37265a5db0
--- /dev/null
+++ b/lib/kernel/src/pg.erl
@@ -0,0 +1,539 @@
+%%
+%%
+%% Copyright WhatsApp Inc. and its affiliates. All rights reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%%
+%%-------------------------------------------------------------------
+%%
+%% @author Maxim Fedorov <maximfca@gmail.com>
+%% Process Groups with eventually consistent membership.
+%%
+%% Differences (compared to pg2):
+%% * non-existent and empty group treated the same (empty list of pids),
+%% thus create/1 and delete/1 have no effect (and not implemented).
+%% which_groups() return only non-empty groups
+%% * no cluster lock required, and no dependency on global
+%% * all join/leave operations require local process (it's not possible to join
+%% a process from a different node)
+%% * multi-join: join/leave several processes with a single call
+%%
+%% Why empty groups are not supported:
+%% Unlike a process, group does not have originating node. So it's possible
+%% that during net split one node deletes the group, that still exists for
+%% another partition. pg2 will recover the group, as soon as net
+%% split converges, which is quite unexpected.
+%%
+%% Exchange protocol:
+%% * when pg process starts, it broadcasts
+%% 'discover' message to all nodes in the cluster
+%% * when pg server receives 'discover', it responds with 'sync' message
+%% containing list of groups with all local processes, and starts to
+%% monitor process that sent 'discover' message (assuming it is a part
+%% of an overlay network)
+%% * every pg process monitors 'nodeup' messages to attempt discovery for
+%% nodes that are (re)joining the cluster
+%%
+%% Leave/join operations:
+%% * processes joining the group are monitored on the local node
+%% * when process exits (without leaving groups prior to exit), local
+%% instance of pg scoped process detects this and sends 'leave' to
+%% all nodes in an overlay network (no remote monitoring done)
+%% * all leave/join operations are serialised through pg server process
+%%
+-module(pg).
+
+%% API: default scope
+-export([
+ start_link/0,
+
+ join/2,
+ leave/2,
+ get_members/1,
+ get_local_members/1,
+ which_groups/0,
+ which_local_groups/0
+]).
+
+%% Scoped API: overlay networks
+-export([
+ start/1,
+ start_link/1,
+
+ join/3,
+ leave/3,
+ get_members/2,
+ get_local_members/2,
+ which_groups/1,
+ which_local_groups/1
+]).
+
+%% gen_server callbacks
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2
+]).
+
+%% Types
+-type group() :: any().
+
+%% Default scope started by kernel app
+-define(DEFAULT_SCOPE, ?MODULE).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server and links it to calling process.
+%% Uses default scope, which is the same as as the module name.
+-spec start_link() -> {ok, pid()} | {error, any()}.
+start_link() ->
+ start_link(?DEFAULT_SCOPE).
+
+%% @doc
+%% Starts the server outside of supervision hierarchy.
+-spec start(Scope :: atom()) -> {ok, pid()} | {error, any()}.
+start(Scope) when is_atom(Scope) ->
+ gen_server:start({local, Scope}, ?MODULE, [Scope], []).
+
+%% @doc
+%% Starts the server and links it to calling process.
+%% Scope name is supplied.
+-spec start_link(Scope :: atom()) -> {ok, pid()} | {error, any()}.
+start_link(Scope) when is_atom(Scope) ->
+ gen_server:start_link({local, Scope}, ?MODULE, [Scope], []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Joins a single or a list of processes.
+%% Group is created automatically.
+%% Processes must be local to this node.
+-spec join(Group :: group(), PidOrPids :: pid() | [pid()]) -> ok.
+join(Group, PidOrPids) ->
+ join(?DEFAULT_SCOPE, Group, PidOrPids).
+
+-spec join(Scope :: atom(), Group :: group(), PidOrPids :: pid() | [pid()]) -> ok.
+join(Scope, Group, PidOrPids) when is_pid(PidOrPids); is_list(PidOrPids) ->
+ ok = ensure_local(PidOrPids),
+ gen_server:call(Scope, {join_local, Group, PidOrPids}, infinity).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Single or list of processes leaving the group.
+%% Processes must be local to this node.
+-spec leave(Group :: group(), PidOrPids :: pid() | [pid()]) -> ok.
+leave(Group, PidOrPids) ->
+ leave(?DEFAULT_SCOPE, Group, PidOrPids).
+
+-spec leave(Scope :: atom(), Group :: group(), PidOrPids :: pid() | [pid()]) -> ok | not_joined.
+leave(Scope, Group, PidOrPids) when is_pid(PidOrPids); is_list(PidOrPids) ->
+ ok = ensure_local(PidOrPids),
+ gen_server:call(Scope, {leave_local, Group, PidOrPids}, infinity).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns all processes in a group
+-spec get_members(Group :: group()) -> [pid()].
+get_members(Group) ->
+ get_members(?DEFAULT_SCOPE, Group).
+
+-spec get_members(Scope :: atom(), Group :: group()) -> [pid()].
+get_members(Scope, Group) ->
+ try
+ ets:lookup_element(Scope, Group, 2)
+ catch
+ error:badarg ->
+ []
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns processes in a group, running on local node.
+-spec get_local_members(Group :: group()) -> [pid()].
+get_local_members(Group) ->
+ get_local_members(?DEFAULT_SCOPE, Group).
+
+-spec get_local_members(Scope :: atom(), Group :: group()) -> [pid()].
+get_local_members(Scope, Group) ->
+ try
+ ets:lookup_element(Scope, Group, 3)
+ catch
+ error:badarg ->
+ []
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns a list of all known groups.
+-spec which_groups() -> [Group :: group()].
+which_groups() ->
+ which_groups(?DEFAULT_SCOPE).
+
+-spec which_groups(Scope :: atom()) -> [Group :: group()].
+which_groups(Scope) when is_atom(Scope) ->
+ [G || [G] <- ets:match(Scope, {'$1', '_', '_'})].
+
+%%--------------------------------------------------------------------
+%% @private
+%% Returns a list of groups that have any local processes joined.
+-spec which_local_groups() -> [Group :: group()].
+which_local_groups() ->
+ which_local_groups(?DEFAULT_SCOPE).
+
+-spec which_local_groups(Scope :: atom()) -> [Group :: group()].
+which_local_groups(Scope) when is_atom(Scope) ->
+ ets:select(Scope, [{{'$1', '_', '$2'}, [{'=/=', '$2', []}], ['$1']}]).
+
+%%--------------------------------------------------------------------
+%% Internal implementation
+
+%% gen_server implementation
+-record(state, {
+ %% ETS table name, and also the registered process name (self())
+ scope :: atom(),
+ %% monitored local processes and groups they joined
+ monitors = #{} :: #{pid() => {MRef :: reference(), Groups :: [group()]}},
+ %% remote node: scope process monitor and map of groups to pids for fast sync routine
+ nodes = #{} :: #{pid() => {reference(), #{group() => [pid()]}}}
+}).
+
+-type state() :: #state{}.
+
+-spec init([Scope :: atom()]) -> {ok, state()}.
+init([Scope]) ->
+ ok = net_kernel:monitor_nodes(true),
+ %% discover all nodes in the cluster
+ broadcast([{Scope, Node} || Node <- nodes()], {discover, self()}),
+ Scope = ets:new(Scope, [set, protected, named_table, {read_concurrency, true}]),
+ {ok, #state{scope = Scope}}.
+
+-spec handle_call(Call :: {join_local, Group :: group(), Pid :: pid()}
+ | {leave_local, Group :: group(), Pid :: pid()},
+ From :: {pid(),Tag :: any()},
+ State :: state()) -> {reply, ok | not_joined, state()}.
+
+handle_call({join_local, Group, PidOrPids}, _From, #state{scope = Scope, monitors = Monitors, nodes = Nodes} = State) ->
+ NewMons = join_monitors(PidOrPids, Group, Monitors),
+ join_local_group(Scope, Group, PidOrPids),
+ broadcast(maps:keys(Nodes), {join, self(), Group, PidOrPids}),
+ {reply, ok, State#state{monitors = NewMons}};
+
+handle_call({leave_local, Group, PidOrPids}, _From, #state{scope = Scope, monitors = Monitors, nodes = Nodes} = State) ->
+ case leave_monitors(PidOrPids, Group, Monitors) of
+ Monitors ->
+ {reply, not_joined, State};
+ NewMons ->
+ leave_local_group(Scope, Group, PidOrPids),
+ broadcast(maps:keys(Nodes), {leave, self(), PidOrPids, [Group]}),
+ {reply, ok, State#state{monitors = NewMons}}
+ end;
+
+handle_call(_Request, _From, _S) ->
+ error(badarg).
+
+-spec handle_cast(
+ {sync, Peer :: pid(), Groups :: [{group(), [pid()]}]},
+ State :: state()) -> {noreply, state()}.
+
+handle_cast({sync, Peer, Groups}, #state{scope = Scope, nodes = Nodes} = State) ->
+ {noreply, State#state{nodes = handle_sync(Scope, Peer, Nodes, Groups)}};
+
+handle_cast(_, _State) ->
+ error(badarg).
+
+-spec handle_info(
+ {discover, Peer :: pid()} |
+ {join, Peer :: pid(), group(), pid() | [pid()]} |
+ {leave, Peer :: pid(), pid() | [pid()], [group()]} |
+ {'DOWN', reference(), process, pid(), term()} |
+ {nodedown, node()} | {nodeup, node()}, State :: state()) -> {noreply, state()}.
+
+%% remote pid or several pids joining the group
+handle_info({join, Peer, Group, PidOrPids}, #state{scope = Scope, nodes = Nodes} = State) ->
+ case maps:get(Peer, Nodes, []) of
+ {MRef, RemoteGroups} ->
+ join_remote(Scope, Group, PidOrPids),
+ %% store remote group => pids map for fast sync operation
+ NewRemoteGroups = join_remote_map(Group, PidOrPids, RemoteGroups),
+ {noreply, State#state{nodes = Nodes#{Peer => {MRef, NewRemoteGroups}}}};
+ [] ->
+ %% handle possible race condition, when remote node is flickering up/down,
+ %% and remote join can happen after the node left overlay network
+ %% It also handles the case when node outside of overlay network sends
+ %% unexpected join request.
+ {noreply, State}
+ end;
+
+%% remote pid leaving (multiple groups at once)
+handle_info({leave, Peer, PidOrPids, Groups}, #state{scope = Scope, nodes = Nodes} = State) ->
+ case maps:get(Peer, Nodes, []) of
+ {MRef, RemoteMap} ->
+ _ = leave_remote(Scope, PidOrPids, Groups),
+ NewRemoteMap = lists:foldl(
+ fun (Group, Acc) ->
+ case maps:get(Group, Acc) of
+ PidOrPids ->
+ maps:remove(Group, Acc);
+ [PidOrPids] ->
+ maps:remove(Group, Acc);
+ Existing when is_pid(PidOrPids) ->
+ Acc#{Group => lists:delete(PidOrPids, Existing)};
+ Existing ->
+ Acc#{Group => Existing-- PidOrPids}
+ end
+ end, RemoteMap, Groups),
+ {noreply, State#state{nodes = Nodes#{Peer => {MRef, NewRemoteMap}}}};
+ [] ->
+ %% Handle race condition: remote node disconnected, but scope process
+ %% of the remote node was just about to send 'leave' message. In this
+ %% case, local node handles 'DOWN' first, but then connection is
+ %% restored, and 'leave' message gets delivered when it's not expected.
+ %% It also handles the case when node outside of overlay network sends
+ %% unexpected leave request.
+ {noreply, State}
+ end;
+
+%% we're being discovered, let's exchange!
+handle_info({discover, Peer}, #state{scope = Scope, nodes = Nodes} = State) ->
+ gen_server:cast(Peer, {sync, self(), all_local_pids(Scope)}),
+ %% do we know who is looking for us?
+ case maps:is_key(Peer, Nodes) of
+ true ->
+ {noreply, State};
+ false ->
+ MRef = monitor(process, Peer),
+ erlang:send(Peer, {discover, self()}, [noconnect]),
+ {noreply, State#state{nodes = Nodes#{Peer => {MRef, #{}}}}}
+ end;
+
+%% handle local process exit
+handle_info({'DOWN', MRef, process, Pid, _Info}, #state{scope = Scope, monitors = Monitors, nodes = Nodes} = State) when node(Pid) =:= node() ->
+ case maps:take(Pid, Monitors) of
+ error ->
+ %% this can only happen when leave request and 'DOWN' are in pg queue
+ {noreply, State};
+ {{MRef, Groups}, NewMons} ->
+ [leave_local_group(Scope, Group, Pid) || Group <- Groups],
+ %% send update to all nodes
+ broadcast(maps:keys(Nodes), {leave, self(), Pid, Groups}),
+ {noreply, State#state{monitors = NewMons}}
+ end;
+
+%% handle remote node down or leaving overlay network
+handle_info({'DOWN', MRef, process, Pid, _Info}, #state{scope = Scope, nodes = Nodes} = State) ->
+ {{MRef, RemoteMap}, NewNodes} = maps:take(Pid, Nodes),
+ _ = maps:map(fun (Group, Pids) -> leave_remote(Scope, Pids, [Group]) end, RemoteMap),
+ {noreply, State#state{nodes = NewNodes}};
+
+%% nodedown: ignore, and wait for 'DOWN' signal for monitored process
+handle_info({nodedown, _Node}, State) ->
+ {noreply, State};
+
+%% nodeup: discover if remote node participates in the overlay network
+handle_info({nodeup, Node}, State) when Node =:= node() ->
+ {noreply, State};
+handle_info({nodeup, Node}, #state{scope = Scope} = State) ->
+ {Scope, Node} ! {discover, self()},
+ {noreply, State};
+
+handle_info(_Info, _State) ->
+ error(badarg).
+
+-spec terminate(Reason :: any(), State :: state()) -> true.
+terminate(_Reason, #state{scope = Scope}) ->
+ true = ets:delete(Scope).
+
+%%--------------------------------------------------------------------
+%% Internal implementation
+
+%% Ensures argument is either a node-local pid or a list of such, or it throws an error
+ensure_local(Pid) when is_pid(Pid), node(Pid) =:= node() ->
+ ok;
+ensure_local(Pids) when is_list(Pids) ->
+ lists:foreach(
+ fun
+ (Pid) when is_pid(Pid), node(Pid) =:= node() ->
+ ok;
+ (Bad) ->
+ error({nolocal, Bad})
+ end, Pids);
+ensure_local(Bad) ->
+ error({nolocal, Bad}).
+
+%% Override all knowledge of the remote node with information it sends
+%% to local node. Current implementation must do the full table scan
+%% to remove stale pids (just as for 'nodedown').
+handle_sync(Scope, Peer, Nodes, Groups) ->
+ %% can't use maps:get() because it evaluates 'default' value first,
+ %% and in this case monitor() call has side effect.
+ {MRef, RemoteGroups} =
+ case maps:find(Peer, Nodes) of
+ error ->
+ {monitor(process, Peer), #{}};
+ {ok, MRef0} ->
+ MRef0
+ end,
+ %% sync RemoteMap and transform ETS table
+ _ = sync_groups(Scope, RemoteGroups, Groups),
+ Nodes#{Peer => {MRef, maps:from_list(Groups)}}.
+
+sync_groups(Scope, RemoteGroups, []) ->
+ %% leave all missing groups
+ [leave_remote(Scope, Pids, [Group]) || {Group, Pids} <- maps:to_list(RemoteGroups)];
+sync_groups(Scope, RemoteGroups, [{Group, Pids} | Tail]) ->
+ case maps:take(Group, RemoteGroups) of
+ {Pids, NewRemoteGroups} ->
+ sync_groups(Scope, NewRemoteGroups, Tail);
+ {OldPids, NewRemoteGroups} ->
+ [{Group, AllOldPids, LocalPids}] = ets:lookup(Scope, Group),
+ %% should be really rare...
+ AllNewPids = Pids ++ AllOldPids -- OldPids,
+ true = ets:insert(Scope, {Group, AllNewPids, LocalPids}),
+ sync_groups(Scope, NewRemoteGroups, Tail);
+ error ->
+ join_remote(Scope, Group, Pids),
+ sync_groups(Scope, RemoteGroups, Tail)
+ end.
+
+join_monitors(Pid, Group, Monitors) when is_pid(Pid) ->
+ case maps:find(Pid, Monitors) of
+ {ok, {MRef, Groups}} ->
+ maps:put(Pid, {MRef, [Group | Groups]}, Monitors);
+ error ->
+ MRef = erlang:monitor(process, Pid),
+ Monitors#{Pid => {MRef, [Group]}}
+ end;
+join_monitors([], _Group, Monitors) ->
+ Monitors;
+join_monitors([Pid | Tail], Group, Monitors) ->
+ join_monitors(Tail, Group, join_monitors(Pid, Group, Monitors)).
+
+join_local_group(Scope, Group, Pid) when is_pid(Pid) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, [Pid | All], [Pid | Local]});
+ [] ->
+ ets:insert(Scope, {Group, [Pid], [Pid]})
+ end;
+join_local_group(Scope, Group, Pids) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, Pids ++ All, Pids ++ Local});
+ [] ->
+ ets:insert(Scope, {Group, Pids, Pids})
+ end.
+
+join_remote(Scope, Group, Pid) when is_pid(Pid) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, [Pid | All], Local});
+ [] ->
+ ets:insert(Scope, {Group, [Pid], []})
+ end;
+join_remote(Scope, Group, Pids) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, Pids ++ All, Local});
+ [] ->
+ ets:insert(Scope, {Group, Pids, []})
+ end.
+
+join_remote_map(Group, Pid, RemoteGroups) when is_pid(Pid) ->
+ maps:update_with(Group, fun (List) -> [Pid | List] end, [Pid], RemoteGroups);
+join_remote_map(Group, Pids, RemoteGroups) ->
+ maps:update_with(Group, fun (List) -> Pids ++ List end, Pids, RemoteGroups).
+
+leave_monitors(Pid, Group, Monitors) when is_pid(Pid) ->
+ case maps:find(Pid, Monitors) of
+ {ok, {MRef, [Group]}} ->
+ erlang:demonitor(MRef),
+ maps:remove(Pid, Monitors);
+ {ok, {MRef, Groups}} ->
+ case lists:member(Group, Groups) of
+ true ->
+ maps:put(Pid, {MRef, lists:delete(Group, Groups)}, Monitors);
+ false ->
+ Monitors
+ end;
+ _ ->
+ Monitors
+ end;
+leave_monitors([], _Group, Monitors) ->
+ Monitors;
+leave_monitors([Pid | Tail], Group, Monitors) ->
+ leave_monitors(Tail, Group, leave_monitors(Pid, Group, Monitors)).
+
+leave_local_group(Scope, Group, Pid) when is_pid(Pid) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, [Pid], [Pid]}] ->
+ ets:delete(Scope, Group);
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, lists:delete(Pid, All), lists:delete(Pid, Local)});
+ [] ->
+ %% rare race condition when 'DOWN' from monitor stays in msg queue while process is leave-ing.
+ true
+ end;
+leave_local_group(Scope, Group, Pids) ->
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ case All -- Pids of
+ [] ->
+ ets:delete(Scope, Group);
+ NewAll ->
+ ets:insert(Scope, {Group, NewAll, Local -- Pids})
+ end;
+ [] ->
+ true
+ end.
+
+leave_remote(Scope, Pid, Groups) when is_pid(Pid) ->
+ _ = [
+ case ets:lookup(Scope, Group) of
+ [{Group, [Pid], []}] ->
+ ets:delete(Scope, Group);
+ [{Group, All, Local}] ->
+ ets:insert(Scope, {Group, lists:delete(Pid, All), Local});
+ [] ->
+ true
+ end ||
+ Group <- Groups];
+leave_remote(Scope, Pids, Groups) ->
+ _ = [
+ case ets:lookup(Scope, Group) of
+ [{Group, All, Local}] ->
+ case All -- Pids of
+ [] when Local =:= [] ->
+ ets:delete(Scope, Group);
+ NewAll ->
+ ets:insert(Scope, {Group, NewAll, Local})
+ end;
+ [] ->
+ true
+ end ||
+ Group <- Groups].
+
+all_local_pids(Scope) ->
+ %% selector: ets:fun2ms(fun({N,_,L}) when L =/=[] -> {N,L}end).
+ ets:select(Scope, [{{'$1','_','$2'},[{'=/=','$2',[]}],[{{'$1','$2'}}]}]).
+
+%% Works as gen_server:abcast(), but accepts a list of processes
+%% instead of nodes list.
+broadcast([], _Msg) ->
+ ok;
+broadcast([Dest | Tail], Msg) ->
+ %% do not use 'nosuspend', as it will lead to missing
+ %% join/leave messages when dist buffer is full
+ erlang:send(Dest, Msg, [noconnect]),
+ broadcast(Tail, Msg).
diff --git a/lib/kernel/src/pg2.erl b/lib/kernel/src/pg2.erl
index c4732f37ee..c7f033640a 100644
--- a/lib/kernel/src/pg2.erl
+++ b/lib/kernel/src/pg2.erl
@@ -25,6 +25,8 @@
-export([start/0,start_link/0,init/1,handle_call/3,handle_cast/2,handle_info/2,
terminate/2]).
+-deprecated([{'_','_',"use 'pg' instead"}]).
+
%%% As of R13B03 monitors are used instead of links.
%%%
diff --git a/lib/kernel/src/raw_file_io_compressed.erl b/lib/kernel/src/raw_file_io_compressed.erl
index ef40447ef7..b723883986 100644
--- a/lib/kernel/src/raw_file_io_compressed.erl
+++ b/lib/kernel/src/raw_file_io_compressed.erl
@@ -21,7 +21,8 @@
-export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,
position/2, write/2, pwrite/2, pwrite/3,
- read_line/1, read/2, pread/2, pread/3]).
+ read_line/1, read/2, pread/2, pread/3,
+ read_handle_info/2]).
%% OTP internal.
-export([ipread_s32bu_p32bu/3, sendfile/8]).
@@ -118,6 +119,9 @@ ipread_s32bu_p32bu(Fd, Offset, MaxSize) ->
sendfile(_,_,_,_,_,_,_,_) ->
{error, enotsup}.
+read_handle_info(Fd, Opts) ->
+ wrap_call(Fd, [Opts]).
+
wrap_call(Fd, Command) ->
{_Owner, Pid} = get_fd_data(Fd),
try gen_statem:call(Pid, Command, infinity) of
diff --git a/lib/kernel/src/raw_file_io_delayed.erl b/lib/kernel/src/raw_file_io_delayed.erl
index cf62a38a26..9891505491 100644
--- a/lib/kernel/src/raw_file_io_delayed.erl
+++ b/lib/kernel/src/raw_file_io_delayed.erl
@@ -23,7 +23,8 @@
-export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,
position/2, write/2, pwrite/2, pwrite/3,
- read_line/1, read/2, pread/2, pread/3]).
+ read_line/1, read/2, pread/2, pread/3,
+ read_handle_info/2]).
%% OTP internal.
-export([ipread_s32bu_p32bu/3, sendfile/8]).
@@ -304,6 +305,9 @@ ipread_s32bu_p32bu(Fd, Offset, MaxSize) ->
sendfile(_,_,_,_,_,_,_,_) ->
{error, enotsup}.
+read_handle_info(Fd, Opts) ->
+ wrap_call(Fd, [Opts]).
+
wrap_call(Fd, Command) ->
#{ pid := Pid } = get_fd_data(Fd),
try gen_statem:call(Pid, Command, infinity) of
diff --git a/lib/kernel/src/raw_file_io_list.erl b/lib/kernel/src/raw_file_io_list.erl
index 2e16e63f0e..e4fe434e13 100644
--- a/lib/kernel/src/raw_file_io_list.erl
+++ b/lib/kernel/src/raw_file_io_list.erl
@@ -21,7 +21,8 @@
-export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,
position/2, write/2, pwrite/2, pwrite/3,
- read_line/1, read/2, pread/2, pread/3]).
+ read_line/1, read/2, pread/2, pread/3,
+ read_handle_info/2]).
%% OTP internal.
-export([ipread_s32bu_p32bu/3, sendfile/8]).
@@ -126,3 +127,7 @@ sendfile(Fd, Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags) ->
Args = [Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags],
PrivateFd = Fd#file_descriptor.data,
?CALL_FD(PrivateFd, sendfile, Args).
+
+read_handle_info(Fd, Opts) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, read_handle_info, [Opts]).
diff --git a/lib/kernel/src/rpc.erl b/lib/kernel/src/rpc.erl
index d197de942f..29b6d40592 100644
--- a/lib/kernel/src/rpc.erl
+++ b/lib/kernel/src/rpc.erl
@@ -19,6 +19,14 @@
%%
-module(rpc).
+%%
+%% Implementations inside '-ifdef(SERVER_SIDE_ERPC_IS_MANDATORY).'
+%% below require 'erpc' support to be mandatory on server side.
+%% 'erpc' was introduced in OTP 23, so it should be possible to
+%% enable this as of OTP 25:
+%% -define(SERVER_SIDE_ERPC_IS_MANDATORY, yes).
+%%
+
%% General rpc, broadcast,multicall, promise and parallel evaluator
%% facility
@@ -26,6 +34,7 @@
%% a separate module.
-define(NAME, rex).
+-define(TAB_NAME, rex_nodes_observer).
-behaviour(gen_server).
@@ -61,12 +70,23 @@
-export_type([key/0]).
+%% Removed functions
+
+-removed([{safe_multi_server_call,2,"use rpc:multi_server_call/2 instead"},
+ {safe_multi_server_call,3,"use rpc:multi_server_call/3 instead"}]).
+
%%------------------------------------------------------------------------
-type state() :: map().
%%------------------------------------------------------------------------
+-define(MAX_INT_TIMEOUT, 4294967295).
+-define(TIMEOUT_TYPE, 0..?MAX_INT_TIMEOUT | 'infinity').
+-define(IS_VALID_TMO_INT(TI_), (is_integer(TI_)
+ andalso (0 =< TI_)
+ andalso (TI_ =< ?MAX_INT_TIMEOUT))).
+-define(IS_VALID_TMO(T_), ((T_ == infinity) orelse ?IS_VALID_TMO_INT(T_))).
%% The rex server may receive a huge amount of
%% messages. Make sure that they are stored off heap to
@@ -101,7 +121,7 @@ stop(Rpc) ->
init([]) ->
process_flag(trap_exit, true),
- {ok, maps:new()}.
+ {ok, #{nodes_observer => start_nodes_observer()}}.
-spec handle_call(term(), term(), state()) ->
{'noreply', state()} |
@@ -109,17 +129,23 @@ init([]) ->
{'stop', 'normal', 'stopped', state()}.
handle_call({call, Mod, Fun, Args, Gleader}, To, S) ->
- handle_call_call(Mod, Fun, Args, Gleader, To, S);
+ %% Spawn not to block the rex server.
+ ExecCall = fun () ->
+ set_group_leader(Gleader),
+ Reply = execute_call(Mod, Fun, Args),
+ gen_server:reply(To, Reply)
+ end,
+ try
+ {_,Mon} = spawn_monitor(ExecCall),
+ {noreply, maps:put(Mon, To, S)}
+ catch
+ error:system_limit ->
+ {reply, {badrpc, {'EXIT', system_limit}}, S}
+ end;
handle_call({block_call, Mod, Fun, Args, Gleader}, _To, S) ->
MyGL = group_leader(),
set_group_leader(Gleader),
- Reply =
- case catch apply(Mod,Fun,Args) of
- {'EXIT', _} = Exit ->
- {badrpc, Exit};
- Other ->
- Other
- end,
+ Reply = execute_call(Mod, Fun, Args),
group_leader(MyGL, self()), % restore
{reply, Reply, S};
handle_call(stop, _To, S) ->
@@ -130,25 +156,32 @@ handle_call(_, _To, S) ->
-spec handle_cast(term(), state()) -> {'noreply', state()}.
handle_cast({cast, Mod, Fun, Args, Gleader}, S) ->
- spawn(fun() ->
- set_group_leader(Gleader),
- apply(Mod, Fun, Args)
- end),
+ _ = try
+ spawn(fun() ->
+ set_group_leader(Gleader),
+ erpc:execute_cast(Mod, Fun, Args)
+ end)
+ catch
+ error:system_limit ->
+ ok
+ end,
{noreply, S};
handle_cast(_, S) ->
{noreply, S}. % Ignore !
-spec handle_info(term(), state()) -> {'noreply', state()}.
-handle_info({'DOWN', _, process, Caller, normal}, S) ->
- {noreply, maps:remove(Caller, S)};
-handle_info({'DOWN', _, process, Caller, Reason}, S) ->
- case maps:get(Caller, S, undefined) of
+handle_info({'DOWN', M, process, P, _}, #{nodes_observer := {P,M}} = S) ->
+ {noreply, S#{nodes_observer => start_nodes_observer()}};
+handle_info({'DOWN', M, process, _, normal}, S) ->
+ {noreply, maps:remove(M, S)};
+handle_info({'DOWN', M, process, _, Reason}, S) ->
+ case maps:get(M, S, undefined) of
undefined ->
{noreply, S};
{_, _} = To ->
gen_server:reply(To, {badrpc, {'EXIT', Reason}}),
- {noreply, maps:remove(Caller, S)}
+ {noreply, maps:remove(M, S)}
end;
handle_info({From, {sbcast, Name, Msg}}, S) ->
_ = case catch Name ! Msg of %% use catch to get the printout
@@ -166,9 +199,19 @@ handle_info({From, {send, Name, Msg}}, S) ->
ok %% It's up to Name to respond !!!!!
end,
{noreply, S};
-handle_info({From, {call,Mod,Fun,Args,Gleader}}, S) ->
+handle_info({From, {call, _Mod, _Fun, _Args, _Gleader} = Request}, S) ->
%% Special for hidden C node's, uugh ...
- handle_call_call(Mod, Fun, Args, Gleader, {From,?NAME}, S);
+ To = {From, ?NAME},
+ case handle_call(Request, To, S) of
+ {noreply, _NewS} = Return ->
+ Return;
+ {reply, Reply, NewS} ->
+ gen_server:reply(To, Reply),
+ {noreply, NewS}
+ end;
+handle_info({From, features_request}, S) ->
+ From ! {features_reply, node(), [erpc]},
+ {noreply, S};
handle_info(_, S) ->
{noreply, S}.
@@ -182,31 +225,28 @@ terminate(_, _S) ->
code_change(_, S, _) ->
{ok, S}.
-%%
-%% Auxiliary function to avoid a false dialyzer warning -- do not inline
-%%
-handle_call_call(Mod, Fun, Args, Gleader, To, S) ->
- %% Spawn not to block the rpc server.
- {Caller,_} =
- erlang:spawn_monitor(
- fun () ->
- set_group_leader(Gleader),
- Reply =
- %% in case some sucker rex'es
- %% something that throws
- case catch apply(Mod, Fun, Args) of
- {'EXIT', _} = Exit ->
- {badrpc, Exit};
- Result ->
- Result
- end,
- gen_server:reply(To, Reply)
- end),
- {noreply, maps:put(Caller, To, S)}.
-
%% RPC aid functions ....
+execute_call(Mod, Fun, Args) ->
+ try
+ {return, Return} = erpc:execute_call(Mod, Fun, Args),
+ Return
+ catch
+ throw:Result ->
+ Result;
+ exit:Reason ->
+ {badrpc, {'EXIT', Reason}};
+ error:Reason:Stack ->
+ case erpc:is_arg_error(Reason, Mod, Fun, Args) of
+ true ->
+ {badrpc, {'EXIT', Reason}};
+ false ->
+ RpcStack = erpc:trim_stack(Stack, Mod, Fun, Args),
+ {badrpc, {'EXIT', {Reason, RpcStack}}}
+ end
+ end.
+
set_group_leader(Gleader) when is_pid(Gleader) ->
group_leader(Gleader, self());
set_group_leader(user) ->
@@ -254,9 +294,84 @@ proxy_user_flush() ->
end,
proxy_user_flush().
+start_nodes_observer() ->
+ Init = fun () ->
+ process_flag(priority, high),
+ process_flag(trap_exit, true),
+ Tab = ets:new(?TAB_NAME,
+ [{read_concurrency, true},
+ protected]),
+ persistent_term:put(?TAB_NAME, Tab),
+ ok = net_kernel:monitor_nodes(true),
+ lists:foreach(fun (N) ->
+ self() ! {nodeup, N}
+ end,
+ [node()|nodes()]),
+ nodes_observer_loop(Tab)
+ end,
+ spawn_monitor(Init).
+
+nodes_observer_loop(Tab) ->
+ receive
+ {nodeup, nonode@nohost} ->
+ ok;
+ {nodeup, N} ->
+ {?NAME, N} ! {self(), features_request};
+ {nodedown, N} ->
+ ets:delete(Tab, N);
+ {features_reply, N, FeatureList} ->
+ try
+ SpawnRpc = lists:member(erpc, FeatureList),
+ ets:insert(Tab, {N, SpawnRpc})
+ catch
+ _:_ -> ets:insert(Tab, {N, false})
+ end;
+ _ ->
+ ignore
+ end,
+ nodes_observer_loop(Tab).
+
+-ifdef(SERVER_SIDE_ERPC_IS_MANDATORY).
+
+%% Currently node_has_feature() is only needed if
+%% it is unknown if 'erpc' is supported by server
+%% side or not...
+
+-else. %% ! define SERVER_SIDE_ERPC_IS_MANDATORY
+
+-dialyzer([{nowarn_function, node_has_feature/2}, no_match]).
+
+-spec node_has_feature(Node :: atom(), Feature :: term()) -> boolean().
+
+node_has_feature(N, erpc) when N == node() ->
+ true;
+node_has_feature(N, erpc) ->
+ try
+ Tab = persistent_term:get(?TAB_NAME),
+ ets:lookup_element(Tab, N, 2)
+ catch
+ _:_ -> false
+ end;
+node_has_feature(_N, _Feature) ->
+ false.
+
+-endif.
%% THE rpc client interface
+%% Call
+
+-define(RPCIFY(ERPC_),
+ try ERPC_ of
+ {'EXIT', _} = BadRpc_ ->
+ {badrpc, BadRpc_};
+ Result_ ->
+ Result_
+ catch
+ Class_:Reason_ ->
+ rpcify_exception(Class_, Reason_)
+ end).
+
-spec call(Node, Module, Function, Args) -> Res | {badrpc, Reason} when
Node :: node(),
Module :: module(),
@@ -265,10 +380,8 @@ proxy_user_flush() ->
Res :: term(),
Reason :: term().
-call(N,M,F,A) when node() =:= N -> %% Optimize local call
- local_call(M, F, A);
call(N,M,F,A) ->
- do_call(N, {call,M,F,A,group_leader()}, infinity).
+ call(N,M,F,A,infinity).
-spec call(Node, Module, Function, Args, Timeout) ->
Res | {badrpc, Reason} when
@@ -278,14 +391,35 @@ call(N,M,F,A) ->
Args :: [term()],
Res :: term(),
Reason :: term(),
- Timeout :: timeout().
+ Timeout :: ?TIMEOUT_TYPE.
+
+-ifdef(SERVER_SIDE_ERPC_IS_MANDATORY).
+
+call(N,M,F,A,T) ->
+ ?RPCIFY(erpc:call(N, M, F, A, T)).
+
+-else. %% ! defined SERVER_SIDE_ERPC_IS_MANDATORY
+
+call(N,M,F,A,T) ->
+ DL = try
+ deadline(T)
+ catch
+ error:_ ->
+ error(badarg)
+ end,
+ case ?RPCIFY(erpc:call(N, M, F, A, T)) of
+ {badrpc, notsup} ->
+ case time_left(DL) of
+ 0 ->
+ {badrpc, timeout};
+ Timeout ->
+ do_srv_call(N, {call,M,F,A,group_leader()}, Timeout)
+ end;
+ Res ->
+ Res
+ end.
-call(N,M,F,A,infinity) when node() =:= N -> %% Optimize local call
- local_call(M,F,A);
-call(N,M,F,A,infinity) ->
- do_call(N, {call,M,F,A,group_leader()}, infinity);
-call(N,M,F,A,Timeout) when is_integer(Timeout), Timeout >= 0 ->
- do_call(N, {call,M,F,A,group_leader()}, Timeout).
+-endif. %% ! defined SERVER_SIDE_ERPC_IS_MANDATORY
-spec block_call(Node, Module, Function, Args) -> Res | {badrpc, Reason} when
Node :: node(),
@@ -295,10 +429,8 @@ call(N,M,F,A,Timeout) when is_integer(Timeout), Timeout >= 0 ->
Res :: term(),
Reason :: term().
-block_call(N,M,F,A) when node() =:= N -> %% Optimize local call
- local_call(M,F,A);
block_call(N,M,F,A) ->
- do_call(N, {block_call,M,F,A,group_leader()}, infinity).
+ block_call(N,M,F,A,infinity).
-spec block_call(Node, Module, Function, Args, Timeout) ->
Res | {badrpc, Reason} when
@@ -308,24 +440,45 @@ block_call(N,M,F,A) ->
Args :: [term()],
Res :: term(),
Reason :: term(),
- Timeout :: timeout().
-
-block_call(N,M,F,A,_Timeout) when node() =:= N -> %% Optimize local call
- local_call(M, F, A);
-block_call(N,M,F,A,infinity) ->
- do_call(N, {block_call,M,F,A,group_leader()}, infinity);
-block_call(N,M,F,A,Timeout) when is_integer(Timeout), Timeout >= 0 ->
- do_call(N, {block_call,M,F,A,group_leader()}, Timeout).
-
-local_call(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
- case catch apply(M, F, A) of
- {'EXIT',_}=V -> {badrpc, V};
- Other -> Other
- end.
-
-do_call(Node, Request, infinity) ->
+ Timeout :: ?TIMEOUT_TYPE.
+
+block_call(N,M,F,A,Timeout) when is_atom(N),
+ is_atom(M),
+ is_list(A),
+ ?IS_VALID_TMO(Timeout) ->
+ do_srv_call(N, {block_call,M,F,A,group_leader()}, Timeout).
+
+
+%% call() implementation utilizing erpc:call()...
+
+rpcify_exception(throw, {'EXIT', _} = BadRpc) ->
+ {badrpc, BadRpc};
+rpcify_exception(throw, Return) ->
+ Return;
+rpcify_exception(exit, {exception, Exit}) ->
+ {badrpc, {'EXIT', Exit}};
+rpcify_exception(exit, {signal, Reason}) ->
+ {badrpc, {'EXIT', Reason}};
+rpcify_exception(exit, Reason) ->
+ exit(Reason);
+rpcify_exception(error, {exception, Error, Stack}) ->
+ {badrpc, {'EXIT', {Error, Stack}}};
+rpcify_exception(error, {erpc, badarg}) ->
+ error(badarg);
+rpcify_exception(error, {erpc, noconnection}) ->
+ {badrpc, nodedown};
+rpcify_exception(error, {erpc, timeout}) ->
+ {badrpc, timeout};
+rpcify_exception(error, {erpc, notsup}) ->
+ {badrpc, notsup};
+rpcify_exception(error, {erpc, Error}) ->
+ {badrpc, {'EXIT', Error}};
+rpcify_exception(error, Reason) ->
+ error(Reason).
+
+do_srv_call(Node, Request, infinity) ->
rpc_check(catch gen_server:call({?NAME,Node}, Request, infinity));
-do_call(Node, Request, Timeout) ->
+do_srv_call(Node, Request, Timeout) ->
Tag = make_ref(),
{Receiver,Mref} =
erlang:spawn_monitor(
@@ -346,6 +499,7 @@ do_call(Node, Request, Timeout) ->
end.
rpc_check_t({'EXIT', {timeout,_}}) -> {badrpc, timeout};
+rpc_check_t({'EXIT', {timeout_value,_}}) -> error(badarg);
rpc_check_t(X) -> rpc_check(X).
rpc_check({'EXIT', {{nodedown,_},_}}) ->
@@ -395,13 +549,42 @@ server_call(Node, Name, ReplyWrapper, Msg)
Function :: atom(),
Args :: [term()].
-cast(Node, Mod, Fun, Args) when Node =:= node() ->
- catch spawn(Mod, Fun, Args),
- true;
+-ifdef(SERVER_SIDE_ERPC_IS_MANDATORY).
+
cast(Node, Mod, Fun, Args) ->
- gen_server:cast({?NAME,Node}, {cast,Mod,Fun,Args,group_leader()}),
+ try
+ ok = erpc:cast(Node, Mod, Fun, Args)
+ catch
+ error:{erpc, badarg} ->
+ error(badarg)
+ end,
true.
+-else.
+
+cast(Node, Mod, Fun, Args) when is_atom(Node),
+ is_atom(Mod),
+ is_atom(Fun),
+ is_list(Args) ->
+ _ = case node_has_feature(Node, erpc) of
+ false ->
+ gen_server:cast({?NAME,Node},
+ {cast,Mod,Fun,Args,group_leader()});
+ true ->
+ try
+ ok = erpc:cast(Node, Mod, Fun, Args)
+ catch
+ error:{erpc, badarg} ->
+ error(badarg)
+ end
+ end,
+ true;
+cast(_, _, _, _) ->
+ error(badarg).
+
+
+-endif.
+
%% Asynchronous broadcast, returns nothing, it's just send 'n' pray
-spec abcast(Name, Msg) -> abcast when
@@ -464,8 +647,11 @@ eval_everywhere(Mod, Fun, Args) ->
Args :: [term()].
eval_everywhere(Nodes, Mod, Fun, Args) ->
- gen_server:abcast(Nodes, ?NAME, {cast,Mod,Fun,Args,group_leader()}).
-
+ lists:foreach(fun (Node) ->
+ cast(Node, Mod, Fun, Args)
+ end,
+ Nodes),
+ abcast.
send_nodes([Node|Tail], Name, Msg, Monitors) when is_atom(Node) ->
Monitor = start_monitor(Node, Name),
@@ -489,7 +675,6 @@ start_monitor(Node, Name) ->
{Node,erlang:monitor(process, {Name, Node})}
end.
-
%% Call apply(M,F,A) on all nodes in parallel
-spec multicall(Module, Function, Args) -> {ResL, BadNodes} when
Module :: module(),
@@ -501,6 +686,7 @@ start_monitor(Node, Name) ->
multicall(M, F, A) ->
multicall(M, F, A, infinity).
+
-spec multicall(Nodes, Module, Function, Args) -> {ResL, BadNodes} when
Nodes :: [node()],
Module :: module(),
@@ -512,7 +698,7 @@ multicall(M, F, A) ->
Module :: module(),
Function :: atom(),
Args :: [term()],
- Timeout :: timeout(),
+ Timeout :: ?TIMEOUT_TYPE,
ResL :: [Res :: term() | {'badrpc', Reason :: term()}],
BadNodes :: [node()].
@@ -527,24 +713,296 @@ multicall(M, F, A, Timeout) ->
Module :: module(),
Function :: atom(),
Args :: [term()],
- Timeout :: timeout(),
+ Timeout :: ?TIMEOUT_TYPE,
ResL :: [Res :: term() | {'badrpc', Reason :: term()}],
BadNodes :: [node()].
-multicall(Nodes, M, F, A, infinity)
- when is_list(Nodes), is_atom(M), is_atom(F), is_list(A) ->
- do_multicall(Nodes, M, F, A, infinity);
-multicall(Nodes, M, F, A, Timeout)
- when is_list(Nodes), is_atom(M), is_atom(F), is_list(A), is_integer(Timeout),
- Timeout >= 0 ->
- do_multicall(Nodes, M, F, A, Timeout).
+-ifdef(SERVER_SIDE_ERPC_IS_MANDATORY).
+
+%%
+%% Use this more efficient implementation of multicall()
+%% when 'erpc' support can be made mandatory for server
+%% side.
+%%
+multicall(Nodes, M, F, A, Timeout) ->
+ %%
+ %% We want to use erpc:multicall() and then convert the result
+ %% instead of using erpc:send_request()/erpc:receive_response()
+ %% directly. This since it is expected that erpc:multicall()
+ %% will be able to utilize a future message queue optimization
+ %% that erpc:send_request()/erpc:receive_response() most likely
+ %% wont be able to (only the future will tell...).
+ %%
+ ERpcRes = try
+ erpc:multicall(Nodes, M, F, A, Timeout)
+ catch
+ error:{erpc, badarg} ->
+ error(badarg)
+ end,
+ rpcmulticallify(Nodes, ERpcRes, [], []).
+
+
+rpcmulticallify([], [], Ok, Err) ->
+ {lists:reverse(Ok), lists:reverse(Err)};
+rpcmulticallify([_N|Ns], [{ok, {'EXIT', _} = Exit}|Rlts], Ok, Err) ->
+ rpcmulticallify(Ns, Rlts, [{badrpc, Exit}|Ok], Err);
+rpcmulticallify([_N|Ns], [{ok, Return}|Rlts], Ok, Err) ->
+ rpcmulticallify(Ns, Rlts, [Return|Ok], Err);
+rpcmulticallify([N|Ns], [{error, {erpc, Reason}}|Rlts], Ok, Err)
+ when Reason == timeout; Reason == noconnection ->
+ rpcmulticallify(Ns, Rlts, Ok, [N|Err]);
+rpcmulticallify([_N|Ns], [{Class, Reason}|Rlts], Ok, Err) ->
+ rpcmulticallify(Ns, Rlts, [rpcify_exception(Class, Reason)|Ok], Err).
+
+-else. %% ! defined SERVER_SIDE_ERPC_IS_MANDATORY
+
+%%
+%% Currently used implementation for multicall(). When
+%% 'erpc' support can be required for server side,
+%% replace with the implementation above...
+%%
+
+multicall(Nodes, M, F, A, Timeout) ->
+ try
+ true = is_atom(M),
+ true = is_atom(F),
+ true = is_list(A),
+ Deadline = deadline(Timeout),
+ Res = make_ref(),
+ MFA = {M, F, A},
+ {NRs, ReqMap0} = mc_requests(Res, Nodes, M, F, A, [], #{}),
+ ReqMap1 = mc_spawn_replies(Res, maps:size(ReqMap0), ReqMap0,
+ MFA, Deadline),
+ mc_results(Res, NRs, [], [], ReqMap1, MFA, Deadline)
+ catch
+ error:NotIError when NotIError /= internal_error ->
+ error(badarg)
+ end.
-do_multicall(Nodes, M, F, A, Timeout) ->
- {Rep,Bad} = gen_server:multi_call(Nodes, ?NAME,
- {call, M,F,A, group_leader()},
- Timeout),
- {lists:map(fun({_,R}) -> R end, Rep), Bad}.
+mc_requests(_Res, [], _M, _F, _A, NRs, ReqMap) ->
+ {NRs, ReqMap};
+mc_requests(Res, [N|Ns], M, F, A, NRs, ReqMap) ->
+ ReqId = try
+ spawn_request(N, erpc, execute_call,
+ [Res, M, F, A],
+ [{reply_tag, {spawn_reply, Res, N}},
+ monitor])
+ catch
+ _:_ ->
+ mc_fail_requests(Res, NRs)
+ end,
+ NR = {N, ReqId},
+ mc_requests(Res, Ns, M, F, A, [NR|NRs], ReqMap#{ReqId => spawn_request});
+mc_requests(Res, _Error, _M, _F, _A, NRs, _ReqMap) ->
+ mc_fail_requests(Res, NRs).
+
+%% Abandon any requests sent then fail...
+mc_fail_requests(_Res, []) ->
+ error(badarg);
+mc_fail_requests(Res, [{Node, ReqId} | NRs]) ->
+ case spawn_request_abandon(ReqId) of
+ true ->
+ ok;
+ false ->
+ receive
+ {{spawn_reply, Res, Node}, ReqId, error, _} ->
+ ok;
+ {{spawn_reply, Res, Node}, ReqId, ok, Pid} ->
+ case erlang:demonitor(ReqId, [info]) of
+ true ->
+ ok;
+ false ->
+ receive
+ {'DOWN', ReqId, process, Pid, _} ->
+ ok
+ after
+ 0 ->
+ error(internal_error)
+ end
+ end
+ after
+ 0 ->
+ error(internal_error)
+ end
+ end,
+ mc_fail_requests(Res, NRs).
+
+mc_spawn_replies(_Res, 0, ReqMap, _MFA, _Deadline) ->
+ ReqMap;
+mc_spawn_replies(Res, Outstanding, ReqMap, MFA, Deadline) ->
+ Timeout = time_left(Deadline),
+ receive
+ {{spawn_reply, Res, _}, _, _, _} = Reply ->
+ NewReqMap = mc_handle_spawn_reply(Reply, ReqMap, MFA, Deadline),
+ mc_spawn_replies(Res, Outstanding-1, NewReqMap, MFA, Deadline)
+ after
+ Timeout ->
+ ReqMap
+ end.
+
+mc_handle_spawn_reply({{spawn_reply, _Res, _Node}, ReqId, ok, Pid},
+ ReqMap, _MFA, _Deadline) ->
+ ReqMap#{ReqId => {spawn, Pid}};
+mc_handle_spawn_reply({{spawn_reply, _Res, Node}, ReqId, error, notsup},
+ ReqMap, MFA, infinity) ->
+ {M, F, A} = MFA,
+ SrvReqId = gen_server:send_request({?NAME, Node},
+ {call, M,F,A,
+ group_leader()}),
+ ReqMap#{ReqId => {server, SrvReqId}};
+mc_handle_spawn_reply({{spawn_reply, Res, Node}, ReqId, error, notsup},
+ ReqMap, MFA, Deadline) ->
+ {M, F, A} = MFA,
+ try
+ {Pid, Mon} = spawn_monitor(fun () ->
+ process_flag(trap_exit, true),
+ Request = {call, M,F,A,
+ group_leader()},
+ Timeout = time_left(Deadline),
+ Result = gen_server:call({?NAME,
+ Node},
+ Request,
+ Timeout),
+ exit({Res, Result})
+ end),
+ ReqMap#{ReqId => {spawn_server, Mon, Pid}}
+ catch
+ error:system_limit ->
+ ReqMap#{ReqId => {error, {badrpc, {'EXIT', system_limit}}}}
+ end;
+mc_handle_spawn_reply({{spawn_reply, _Res, _Node}, ReqId, error, noconnection},
+ ReqMap, _MFA, _Deadline) ->
+ ReqMap#{ReqId => {error, badnode}};
+mc_handle_spawn_reply({{spawn_reply, _Res, _Node}, ReqId, error, Reason},
+ ReqMap, _MFA, _Deadline) ->
+ ReqMap#{ReqId => {error, {badrpc, {'EXIT', Reason}}}}.
+
+mc_results(_Res, [], OkAcc, ErrAcc, _ReqMap, _MFA, _Deadline) ->
+ {OkAcc, ErrAcc};
+mc_results(Res, [{N,ReqId}|NRs] = OrigNRs, OkAcc, ErrAcc,
+ ReqMap, MFA, Deadline) ->
+ case maps:get(ReqId, ReqMap) of
+ {error, badnode} ->
+ mc_results(Res, NRs, OkAcc, [N|ErrAcc], ReqMap, MFA, Deadline);
+ {error, BadRpc} ->
+ mc_results(Res, NRs, [BadRpc|OkAcc], ErrAcc, ReqMap,
+ MFA, Deadline);
+ spawn_request ->
+ %% We timed out waiting for spawn replies...
+ case spawn_request_abandon(ReqId) of
+ true ->
+ %% Timed out request...
+ mc_results(Res, NRs, OkAcc, [N|ErrAcc], ReqMap,
+ MFA, Deadline);
+ false ->
+ %% Reply has been delivered now; handle it...
+ receive
+ {{spawn_reply, Res, _}, ReqId, _, _} = Reply ->
+ NewReqMap = mc_handle_spawn_reply(Reply, ReqMap,
+ MFA, Deadline),
+ mc_results(Res, OrigNRs, OkAcc, ErrAcc,
+ NewReqMap, MFA, Deadline)
+ after 0 ->
+ error(internal_error)
+ end
+ end;
+ {spawn, Pid} ->
+ Timeout = time_left(Deadline),
+ receive
+ {'DOWN', ReqId, process, Pid, Reason} ->
+ case ?RPCIFY(erpc:call_result(down, ReqId, Res, Reason)) of
+ {badrpc, nodedown} ->
+ mc_results(Res, NRs, OkAcc, [N|ErrAcc],
+ ReqMap, MFA, Deadline);
+ CallRes ->
+ mc_results(Res, NRs, [CallRes|OkAcc],
+ ErrAcc, ReqMap, MFA, Deadline)
+ end
+ after
+ Timeout ->
+ case erlang:demonitor(ReqId, [info]) of
+ true ->
+ mc_results(Res, NRs, OkAcc, [N|ErrAcc],
+ ReqMap, MFA, Deadline);
+ false ->
+ receive
+ {'DOWN', ReqId, process, Pid, Reason} ->
+ case ?RPCIFY(erpc:call_result(down,
+ ReqId,
+ Res,
+ Reason)) of
+ {badrpc, nodedown} ->
+ mc_results(Res, NRs, OkAcc,
+ [N|ErrAcc], ReqMap,
+ MFA, Deadline);
+ CallRes ->
+ mc_results(Res, NRs,
+ [CallRes|OkAcc],
+ ErrAcc, ReqMap,
+ MFA, Deadline)
+ end
+ after 0 ->
+ error(internal_error)
+ end
+ end
+ end;
+ {spawn_server, Mon, Pid} ->
+ %% Old node with timeout on the call...
+ Result = receive
+ {'DOWN', Mon, process, Pid, {Res, CallRes}} ->
+ rpc_check(CallRes);
+ {'DOWN', Mon, process, Pid, Reason} ->
+ rpc_check_t({'EXIT',Reason})
+ end,
+ case Result of
+ {badrpc, BadRpcReason} when BadRpcReason == timeout;
+ BadRpcReason == nodedown ->
+ mc_results(Res, NRs, OkAcc, [N|ErrAcc],
+ ReqMap, MFA, Deadline);
+ _ ->
+ mc_results(Res, NRs, [Result|OkAcc], ErrAcc, ReqMap,
+ MFA, Deadline)
+ end;
+ {server, SrvReqId} ->
+ %% Old node without timeout on the call...
+ case gen_server:wait_response(SrvReqId, infinity) of
+ {reply, Reply} ->
+ Result = rpc_check(Reply),
+ mc_results(Res, NRs, [Result|OkAcc], ErrAcc,
+ ReqMap, MFA, Deadline);
+ {error, {noconnection, _}} ->
+ mc_results(Res, NRs, OkAcc, [N|ErrAcc],
+ ReqMap, MFA, Deadline);
+ {error, {Reason, _}} ->
+ BadRpc = {badrpc, {'EXIT', Reason}},
+ mc_results(Res, NRs, [BadRpc|OkAcc], ErrAcc,
+ ReqMap, MFA, Deadline)
+ end
+ end.
+
+deadline(infinity) ->
+ infinity;
+deadline(?MAX_INT_TIMEOUT) ->
+ erlang:convert_time_unit(erlang:monotonic_time(millisecond)
+ + ?MAX_INT_TIMEOUT,
+ millisecond,
+ native);
+deadline(T) when ?IS_VALID_TMO_INT(T) ->
+ Now = erlang:monotonic_time(),
+ NativeTmo = erlang:convert_time_unit(T, millisecond, native),
+ Now + NativeTmo.
+
+time_left(infinity) ->
+ infinity;
+time_left(Deadline) ->
+ case Deadline - erlang:monotonic_time() of
+ TimeLeft when TimeLeft =< 0 ->
+ 0;
+ TimeLeft ->
+ erlang:convert_time_unit(TimeLeft-1, native, millisecond) + 1
+ end.
+-endif. %% ! defined SERVER_SIDE_ERPC_IS_MANDATORY
%% Send Msg to Name on all nodes, and collect the answers.
%% Return {Replies, Badnodes} where Badnodes is a list of the nodes
@@ -603,7 +1061,15 @@ rec_nodes(Name, [{N,R} | Tail], Badnodes, Replies) ->
%% rpc's towards the same node. I.e. it returns immediately and
%% it returns a Key that can be used in a subsequent yield(Key).
--opaque key() :: pid().
+-ifdef(SERVER_SIDE_ERPC_IS_MANDATORY).
+
+%%
+%% Use this more efficient implementation of async_call()
+%% when 'erpc' support can be made mandatory for server
+%% side.
+%%
+
+-opaque key() :: erpc:request_id().
-spec async_call(Node, Module, Function, Args) -> Key when
Node :: node(),
@@ -613,49 +1079,118 @@ rec_nodes(Name, [{N,R} | Tail], Badnodes, Replies) ->
Key :: key().
async_call(Node, Mod, Fun, Args) ->
- ReplyTo = self(),
- spawn(
- fun() ->
- R = call(Node, Mod, Fun, Args), %% proper rpc
- ReplyTo ! {self(), {promise_reply, R}} %% self() is key
- end).
+ try
+ erpc:send_request(Node, Mod, Fun, Args)
+ catch
+ error:{erpc, badarg} ->
+ error(badarg)
+ end.
-spec yield(Key) -> Res | {badrpc, Reason} when
Key :: key(),
Res :: term(),
Reason :: term().
-yield(Key) when is_pid(Key) ->
- {value,R} = do_yield(Key, infinity),
- R.
+yield(Key) ->
+ ?RPCIFY(erpc:receive_response(Key)).
-spec nb_yield(Key, Timeout) -> {value, Val} | timeout when
Key :: key(),
- Timeout :: timeout(),
+ Timeout :: ?TIMEOUT_TYPE,
Val :: (Res :: term()) | {badrpc, Reason :: term()}.
-nb_yield(Key, infinity=Inf) when is_pid(Key) ->
- do_yield(Key, Inf);
-nb_yield(Key, Timeout) when is_pid(Key), is_integer(Timeout), Timeout >= 0 ->
- do_yield(Key, Timeout).
+nb_yield(Key, Tmo) ->
+ case ?RPCIFY(erpc:wait_response(Key, Tmo)) of
+ no_response ->
+ timeout;
+ {response, {'EXIT', _} = BadRpc} ->
+ %% RPCIFY() cannot handle this case...
+ {value, {badrpc, BadRpc}};
+ {response, R} ->
+ {value, R};
+ BadRpc ->
+ %% An exception converted by RPCIFY()...
+ {value, BadRpc}
+ end.
-spec nb_yield(Key) -> {value, Val} | timeout when
Key :: key(),
Val :: (Res :: term()) | {badrpc, Reason :: term()}.
-nb_yield(Key) when is_pid(Key) ->
- do_yield(Key, 0).
+nb_yield(Key) ->
+ nb_yield(Key, 0).
+
+-else. %% ! defined SERVER_SIDE_ERPC_IS_MANDATORY
+
+%%
+%% Currently used implementation for async_call(). When
+%% 'erpc' support can be required for server side,
+%% replace with the implementation above...
+%%
+
+-opaque key() :: {pid(), reference()}.
--spec do_yield(pid(), timeout()) -> {'value', _} | 'timeout'.
+-spec async_call(Node, Module, Function, Args) -> Key when
+ Node :: node(),
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Key :: key().
+
+async_call(Node, Mod, Fun, Args) ->
+ try
+ true = is_atom(Node),
+ true = is_atom(Mod),
+ true = is_atom(Fun),
+ true = is_integer(length(Args))
+ catch
+ _:_ ->
+ error(badarg)
+ end,
+ Caller = self(),
+ spawn_monitor(fun() ->
+ process_flag(trap_exit, true),
+ R = call(Node, Mod, Fun, Args),
+ exit({async_call_result, Caller, R})
+ end).
+
+-spec yield(Key) -> Res | {badrpc, Reason} when
+ Key :: key(),
+ Res :: term(),
+ Reason :: term().
+
+yield({Pid, Ref} = Key) when is_pid(Pid),
+ is_reference(Ref) ->
+ {value,R} = nb_yield(Key, infinity),
+ R.
+
+-spec nb_yield(Key) -> {value, Val} | timeout when
+ Key :: key(),
+ Val :: (Res :: term()) | {badrpc, Reason :: term()}.
+
+nb_yield({Pid, Ref} = Key) when is_pid(Pid),
+ is_reference(Ref) ->
+ nb_yield(Key, 0).
+
+-spec nb_yield(Key, Timeout) -> {value, Val} | timeout when
+ Key :: key(),
+ Timeout :: ?TIMEOUT_TYPE,
+ Val :: (Res :: term()) | {badrpc, Reason :: term()}.
-do_yield(Key, Timeout) ->
+nb_yield({Proxy, Mon}, Tmo) when is_pid(Proxy),
+ is_reference(Mon),
+ ?IS_VALID_TMO(Tmo) ->
+ Me = self(),
receive
- {Key,{promise_reply,R}} ->
- {value,R}
- after Timeout ->
+ {'DOWN', Mon, process, Proxy, {async_call_result, Me, R}} ->
+ {value,R};
+ {'DOWN', Mon, process, Proxy, Reason} ->
+ {value, {badrpc, {'EXIT', Reason}}}
+ after Tmo ->
timeout
end.
+-endif. %% ! defined SERVER_SIDE_ERPC_IS_MANDATORY
%% A parallel network evaluator
%% ArgL === [{M,F,Args},........]
diff --git a/lib/kernel/src/seq_trace.erl b/lib/kernel/src/seq_trace.erl
index 90b00192bb..d537e21239 100644
--- a/lib/kernel/src/seq_trace.erl
+++ b/lib/kernel/src/seq_trace.erl
@@ -20,12 +20,13 @@
-module(seq_trace).
--define(SEQ_TRACE_SEND, 1). %(1 << 0)
--define(SEQ_TRACE_RECEIVE, 2). %(1 << 1)
--define(SEQ_TRACE_PRINT, 4). %(1 << 2)
--define(SEQ_TRACE_NOW_TIMESTAMP, 8). %(1 << 3)
--define(SEQ_TRACE_STRICT_MON_TIMESTAMP, 16). %(1 << 4)
--define(SEQ_TRACE_MON_TIMESTAMP, 32). %(1 << 5)
+%% Don't forget to update seq_trace_SUITE after changing these.
+-define(SEQ_TRACE_SEND, 1). %(1 << 0)
+-define(SEQ_TRACE_RECEIVE, 2). %(1 << 1)
+-define(SEQ_TRACE_PRINT, 4). %(1 << 2)
+-define(SEQ_TRACE_NOW_TIMESTAMP, 8). %(1 << 3)
+-define(SEQ_TRACE_STRICT_MON_TIMESTAMP, 16). %(1 << 4)
+-define(SEQ_TRACE_MON_TIMESTAMP, 32). %(1 << 5)
-export([set_token/1,
set_token/2,
@@ -39,7 +40,8 @@
%%---------------------------------------------------------------------------
--type flag() :: 'send' | 'receive' | 'print' | 'timestamp' | 'monotonic_timestamp' | 'strict_monotonic_timestamp'.
+-type flag() :: 'send' | 'receive' | 'print' | 'timestamp' |
+ 'monotonic_timestamp' | 'strict_monotonic_timestamp'.
-type component() :: 'label' | 'serial' | flag().
-type value() :: (Label :: term())
| {Previous :: non_neg_integer(),
diff --git a/lib/kernel/src/socket.erl b/lib/kernel/src/socket.erl
new file mode 100644
index 0000000000..e7c73252d1
--- /dev/null
+++ b/lib/kernel/src/socket.erl
@@ -0,0 +1,2582 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket).
+
+-compile({no_auto_import,[error/1]}).
+
+%% Administrative and "global" utility functions
+-export([
+ number_of/0,
+ which_sockets/0, which_sockets/1,
+
+ debug/1, socket_debug/1, use_registry/1,
+ info/0, info/1,
+ supports/0, supports/1, supports/2,
+ is_supported/1, is_supported/2, is_supported/3
+ ]).
+
+-export([
+ open/1, open/2, open/3, open/4,
+ bind/2, bind/3,
+ connect/1, connect/2, connect/3,
+ listen/1, listen/2,
+ accept/1, accept/2,
+
+ send/2, send/3, send/4,
+ sendto/3, sendto/4, sendto/5,
+ sendmsg/2, sendmsg/3, sendmsg/4,
+
+ recv/1, recv/2, recv/3, recv/4,
+ recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4,
+ recvmsg/1, recvmsg/2, recvmsg/3, recvmsg/5,
+
+ close/1,
+ shutdown/2,
+
+ setopt/4,
+ getopt/3,
+
+ sockname/1,
+ peername/1,
+
+ cancel/2
+ ]).
+
+-export_type([
+ socket/0,
+
+ select_tag/0,
+ select_ref/0,
+ select_info/0,
+
+ socket_counters/0,
+ socket_info/0,
+
+ domain/0,
+ type/0,
+ protocol/0,
+
+ port_number/0,
+ ip4_address/0,
+ ip6_address/0,
+ sockaddr/0,
+ sockaddr_in4/0,
+ sockaddr_in6/0,
+ sockaddr_un/0,
+ sockaddr_ll/0,
+
+ send_flags/0,
+ send_flag/0,
+
+ recv_flags/0,
+ recv_flag/0,
+
+ shutdown_how/0,
+
+ sockopt_level/0,
+ otp_socket_option/0,
+ socket_option/0,
+ ip_socket_option/0,
+ ipv6_socket_option/0,
+ tcp_socket_option/0,
+ udp_socket_option/0,
+ sctp_socket_option/0,
+ raw_socket_option/0,
+
+ timeval/0,
+ ip_tos/0,
+ ip_mreq/0,
+ ip_mreq_source/0,
+ ip_pmtudisc/0,
+ ip_msfilter_mode/0,
+ ip_msfilter/0,
+ ip_pktinfo/0,
+ ipv6_mreq/0,
+ ipv6_pmtudisc/0,
+ ipv6_pktinfo/0,
+ in6_flow_info/0,
+ in6_scope_id/0,
+ sctp_assoc_id/0,
+ sctp_sndrcvinfo/0,
+ sctp_event_subscribe/0,
+ sctp_assocparams/0,
+ sctp_initmsg/0,
+ sctp_rtoinfo/0,
+
+
+ msghdr_flag/0,
+ msghdr_flags/0,
+ msghdr/0,
+ cmsghdr_level/0,
+ cmsghdr_type/0,
+ %% cmsghdr_data/0,
+ cmsghdr_recv/0, cmsghdr_send/0,
+
+ ee_origin/0,
+ icmp_dest_unreach/0,
+ icmpv6_dest_unreach/0,
+ extended_err/0,
+
+ uint8/0,
+ uint16/0,
+ uint20/0,
+ uint32/0,
+ int32/0
+ ]).
+
+%% Also in prim_socket
+-define(REGISTRY, socket_registry).
+
+
+-type socket_counters() :: #{read_byte := non_neg_integer(),
+ read_fails := non_neg_integer(),
+ read_pkg := non_neg_integer(),
+ read_pkg_max := non_neg_integer(),
+ read_tries := non_neg_integer(),
+ read_waits := non_neg_integer(),
+ write_byte := non_neg_integer(),
+ write_fails := non_neg_integer(),
+ write_pkg := non_neg_integer(),
+ write_pkg_max := non_neg_integer(),
+ write_tries := non_neg_integer(),
+ write_waits := non_neg_integer(),
+ acc_success := non_neg_integer(),
+ acc_fails := non_neg_integer(),
+ acc_tries := non_neg_integer(),
+ acc_waits := non_neg_integer()}.
+-type socket_info() :: #{domain := domain(),
+ type := type(),
+ protocol := protocol(),
+ ctrl := pid(),
+ ctype := normal | fromfd | {fromfd, integer()},
+ counters := socket_counters(),
+ num_readers := non_neg_integer(),
+ num_writers := non_neg_integer(),
+ num_acceptors := non_neg_integer(),
+ writable := boolean(),
+ readable := boolean()}.
+
+-type uint8() :: 0..16#FF.
+-type uint16() :: 0..16#FFFF.
+-type uint20() :: 0..16#FFFFF.
+-type uint32() :: 0..16#FFFFFFFF.
+-type int32() :: -2147483648..2147483647.
+
+
+%% We support only a subset of all domains.
+-type domain() :: local | inet | inet6.
+
+%% We support only a subset of all types.
+%% RDM - Reliably Delivered Messages
+-type type() :: stream | dgram | raw | rdm | seqpacket.
+
+%% We support only a subset of all protocols:
+%% Note that the '{raw, integer()}' construct is intended
+%% to be used with type = raw.
+%% Note also that only the "superuser" can create a raw socket.
+-type protocol() :: ip | tcp | udp | sctp | icmp | igmp | {raw, integer()}.
+
+-type port_number() :: 0..65535.
+
+-type ip4_address() :: {0..255, 0..255, 0..255, 0..255}.
+
+-type in6_flow_info() :: uint20().
+-type in6_scope_id() :: uint32().
+
+-type ip6_address() ::
+ {0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535}.
+
+-type timeval() :: #{sec := integer(),
+ usec := integer()}.
+
+-type ip_pktinfo() :: #{
+ ifindex := non_neg_integer(), % Interface Index
+ spec_dst := ip4_address(), % Local Address
+ addr := ip4_address() % Header Destination address
+ }.
+
+%% If the integer value is used, its up to the caller to ensure its valid!
+-type ip_tos() :: lowdelay |
+ throughput |
+ reliability |
+ mincost |
+ integer().
+
+%% This type is used when requesting to become member of a multicast
+%% group with a call to setopt. Example:
+%%
+%% socket:setopt(Socket, ip, add_membership, #{multiaddr => Addr,
+%% interface => any}).
+%%
+%% Its also used when removing from a multicast group. Example:
+%%
+%% socket:setopt(Socket, ip, drop_membership, #{multiaddr => Addr,
+%% interface => any}).
+%%
+
+-type ip_mreq() :: #{multiaddr := ip4_address(),
+ interface := any | ip4_address()}.
+%% -type ip_mreqn() :: #{multiaddr := ip4_address(),
+%% address := any | ip4_address(),
+%% ifindex := integer()}.
+
+-type ip_mreq_source() :: #{multiaddr := ip4_address(),
+ interface := ip4_address(),
+ sourceaddr := ip4_address()}.
+
+-type ip_pmtudisc() :: want | dont | do | probe.
+
+%% multiaddr: Multicast group address
+%% interface: Address of local interface
+%% mode: Filter mode
+%% slist: List of source addresses
+-type ip_msfilter_mode() :: include | exclude.
+
+-type ip_msfilter() :: #{multiaddr := ip4_address(),
+ interface := ip4_address(),
+ mode := ip_msfilter_mode(),
+ slist := [ip4_address()]}.
+
+-type ipv6_mreq() :: #{multiaddr := ip6_address(),
+ interface := non_neg_integer()}.
+
+-type ipv6_pmtudisc() :: ip_pmtudisc().
+
+-type ipv6_pktinfo() :: #{
+ addr := ip6_address(),
+ ifindex := integer()
+ }.
+
+
+-type sctp_assoc_id() :: int32().
+-type sctp_sndrcvinfo() :: #{
+ stream := uint16(),
+ ssn := uint16(),
+ flags := uint16(),
+ ppid := uint16(),
+ context := uint16(),
+ timetolive := uint16(),
+ tsn := uint16(),
+ cumtsn := uint16(),
+ assoc_id := sctp_assoc_id()
+ }.
+
+-type sctp_event_subscribe() :: #{data_in := boolean(),
+ association := boolean(),
+ address := boolean(),
+ send_failure := boolean(),
+ peer_error := boolean(),
+ shutdown := boolean(),
+ partial_delivery := boolean(),
+ adaptation_layer := boolean(),
+ authentication := boolean(),
+ sender_dry := boolean()}.
+
+-type sctp_assocparams() :: #{assoc_id := sctp_assoc_id(),
+ max_rxt := uint16(),
+ num_peer_dests := uint16(),
+ peer_rwnd := uint32(),
+ local_rwnd := uint32(),
+ cookie_life := uint32()}.
+
+-type sctp_initmsg() :: #{num_outstreams := uint16(),
+ max_instreams := uint16(),
+ max_attempts := uint16(),
+ max_init_timeo := uint16()
+ }.
+
+-type sctp_rtoinfo() :: #{assoc_id := sctp_assoc_id(),
+ initial := uint32(),
+ max := uint32(),
+ min := uint32()}.
+
+-type sockaddr_un() :: #{family := local,
+ path := binary() | string()}.
+-type sockaddr_in4() :: #{family := inet,
+ port := port_number(),
+ %% The 'broadcast' here is the "limited broadcast"
+ addr := any | broadcast | loopback | ip4_address()}.
+-type sockaddr_in6() :: #{family := inet6,
+ port := port_number(),
+ addr := any | loopback | ip6_address(),
+ flowinfo := in6_flow_info(),
+ scope_id := in6_scope_id()}.
+-type sockaddr_ll() :: #{family := packet,
+ protocol := non_neg_integer(),
+ ifindex := integer(),
+ pkttype := packet_type(),
+ hatype := non_neg_integer(),
+ addr := binary()}.
+-type packet_type() :: host | broadcast | multicast | otherhost |
+ outgoing | loopback | user | kernel | fastroute |
+ non_neg_integer().
+-type sockaddr() :: sockaddr_in4() |
+ sockaddr_in6() |
+ sockaddr_un() |
+ sockaddr_ll().
+
+%% otp - This option is internal to our (OTP) implementation.
+%% socket - The socket layer (SOL_SOCKET).
+%% ip - The IP layer (SOL_IP or is it IPPROTO_IP?).
+%% ipv6 - The IPv6 layer (SOL_IPV6).
+%% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP).
+%% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP).
+%% sctp - The SCTP (Stream Control Transmission Protocol) layer (IPPROTO_SCTP).
+%% Int - Raw level, sent down and used "as is".
+%% Its up to the caller to make sure this is correct!
+-type sockopt_level() :: otp |
+ socket |
+ ip | ipv6 | tcp | udp | sctp |
+ non_neg_integer().
+
+%% There are some options that are 'read-only'.
+%% Should those be included here or in a special list?
+%% Should we just document it and leave it to the user?
+%% Or catch it in the encode functions?
+%% A setopt for a readonly option leads to einval?
+%% Do we really need a sndbuf?
+
+-type otp_socket_option() :: debug |
+ use_registry |
+ iow |
+ controlling_process |
+ rcvbuf | % sndbuf |
+ rcvctrlbuf |
+ sndctrlbuf |
+ meta |
+ fd.
+%% Shall we have special treatment of linger??
+%% read-only options:
+%% domain | protocol | type.
+%% FreeBSD (only?): acceptfilter
+-type socket_option() :: acceptconn |
+ acceptfilter |
+ bindtodevice |
+ broadcast |
+ busy_poll |
+ debug |
+ domain |
+ dontroute |
+ error |
+ keepalive |
+ linger |
+ mark |
+ oobinline |
+ passcred |
+ peek_off |
+ peercred |
+ priority |
+ protocol |
+ rcvbuf |
+ rcvbufforce |
+ rcvlowat |
+ rcvtimeo |
+ reuseaddr |
+ reuseport |
+ rxq_ovfl |
+ setfib |
+ sndbuf |
+ sndbufforce |
+ sndlowat |
+ sndtimeo |
+ timestamp |
+ type.
+
+%% Read-only options:
+%% mtu
+%%
+%% Options only valid on FreeBSD?:
+%% dontfrag
+%% Options only valid for RAW sockets:
+%% nodefrag (linux only?)
+-type ip_socket_option() :: add_membership |
+ add_source_membership |
+ block_source |
+ dontfrag |
+ drop_membership |
+ drop_source_membership |
+ freebind |
+ hdrincl |
+ minttl |
+ msfilter |
+ mtu |
+ mtu_discover |
+ multicast_all |
+ multicast_if |
+ multicast_loop |
+ multicast_ttl |
+ nodefrag |
+ options |
+ pktinfo |
+ recverr |
+ recvif |
+ recvdstaddr |
+ recvopts |
+ recvorigdstaddr |
+ recvtos |
+ recvttl |
+ retopts |
+ router_alert |
+ sndsrcaddr |
+ tos |
+ transparent |
+ ttl |
+ unblock_source.
+-type ipv6_socket_option() ::
+ addrform |
+ add_membership |
+ authhdr |
+ auth_level |
+ checksum |
+ drop_membership |
+ dstopts |
+ esp_trans_level |
+ esp_network_level |
+ faith |
+ flowinfo |
+ hopopts |
+ ipcomp_level |
+ join_group |
+ leave_group |
+ mtu |
+ mtu_discover |
+ multicast_hops |
+ multicast_if |
+ multicast_loop |
+ portrange |
+ pktoptions |
+ recverr |
+ recvhoplimit | hoplimit |
+ recvpktinfo | pktinfo |
+ recvtclass |
+ router_alert |
+ rthdr |
+ tclass |
+ unicast_hops |
+ use_min_mtu |
+ v6only.
+
+-type tcp_socket_option() :: congestion |
+ cork |
+ info |
+ keepcnt |
+ keepidle |
+ keepintvl |
+ maxseg |
+ md5sig |
+ nodelay |
+ noopt |
+ nopush |
+ syncnt |
+ user_timeout.
+
+-type udp_socket_option() :: cork.
+
+-type sctp_socket_option() ::
+ adaption_layer |
+ associnfo |
+ auth_active_key |
+ auth_asconf |
+ auth_chunk |
+ auth_key |
+ auth_delete_key |
+ autoclose |
+ context |
+ default_send_params |
+ delayed_ack_time |
+ disable_fragments |
+ hmac_ident |
+ events |
+ explicit_eor |
+ fragment_interleave |
+ get_peer_addr_info |
+ initmsg |
+ i_want_mapped_v4_addr |
+ local_auth_chunks |
+ maxseg |
+ maxburst |
+ nodelay |
+ partial_delivery_point |
+ peer_addr_params |
+ peer_auth_chunks |
+ primary_addr |
+ reset_streams |
+ rtoinfo |
+ set_peer_primary_addr |
+ status |
+ use_ext_recvinfo.
+
+-type raw_socket_option() :: filter.
+
+%% -type plain_socket_option() :: integer().
+%% -type sockopt() :: otp_socket_option() |
+%% socket_option() |
+%% ip_socket_option() |
+%% ipv6_socket_option() |
+%% tcp_socket_option() |
+%% udp_socket_option() |
+%% sctp_socket_option() |
+%% raw_socket_option() |
+%% plain_socket_option().
+
+%% The names of these macros match the names of corresponding
+%%C functions in the NIF code, so a search will match both
+%%
+-define(socket_tag, '$socket').
+%%
+%% Our socket abstract data type
+-define(mk_socket(Ref), {?socket_tag, (Ref)}).
+%%
+%% Messages sent from the nif-code to erlang processes:
+-define(mk_socket_msg(Socket, Tag, Info), {?socket_tag, (Socket), (Tag), (Info)}).
+
+-opaque socket() :: ?mk_socket(reference()).
+
+-type send_flags() :: [send_flag()].
+-type send_flag() :: confirm |
+ dontroute |
+ eor |
+ more |
+ nosignal |
+ oob.
+
+%% Note that not all of these flags are useful for every recv function!
+%%
+-type recv_flags() :: [recv_flag()].
+-type recv_flag() :: cmsg_cloexec |
+ errqueue |
+ oob |
+ peek |
+ trunc.
+
+-type shutdown_how() :: read | write | read_write.
+
+-type msghdr_flag() :: ctrunc | eor | errqueue | oob | trunc.
+-type msghdr_flags() :: [msghdr_flag()].
+-type msghdr() :: #{
+ %% *Optional* target address
+ %% Used on an unconnected socket to specify the
+ %% target address for a datagram.
+ addr := sockaddr(),
+
+ iov := [binary()],
+
+ %% The maximum size of the control buffer is platform
+ %% specific. It is the users responsibility to ensure
+ %% that its not exceeded.
+ ctrl := [cmsghdr_recv()] | [cmsghdr_send()],
+
+ %% Only valid with recvmsg
+ flags := msghdr_flags()
+ }.
+%% We are able to (completely) decode *some* control message headers.
+%% Even if we are able to decode both level and type, we may not be
+%% able to decode the data, in which case it will be a binary.
+
+-type cmsghdr_level() :: socket | ip | ipv6 | integer().
+-type cmsghdr_type() :: credentials |
+ hoplevel |
+ origdstaddr |
+ pktinfo |
+ recvtos |
+ rights |
+ timestamp |
+ tos |
+ ttl |
+ integer().
+-type cmsghdr_recv() ::
+ #{level := socket, type := timestamp, data := timeval()} |
+ #{level := socket, type := rights, data := binary()} |
+ #{level := socket, type := credentials, data := binary()} |
+ #{level := socket, type := integer(), data := binary()} |
+ #{level := ip, type := tos, data := ip_tos()} |
+ #{level := ip, type := recvtos, data := ip_tos()} |
+ #{level := ip, type := ttl, data := integer()} |
+ #{level := ip, type := recvttl, data := integer()} |
+ #{level := ip, type := pktinfo, data := ip_pktinfo()} |
+ #{level := ip, type := origdstaddr, data := sockaddr_in4()} |
+ #{level := ip, type := recverr, data := extended_err() | binary()} |
+ #{level := ip, type := integer(), data := binary()} |
+ #{level := ipv6, type := hoplevel, data := integer()} |
+ #{level := ipv6, type := pktinfo, data := ipv6_pktinfo()} |
+ #{level := ipv6, type := recverr, data := extended_err() | binary()} |
+ #{level := ipv6, type := tclass, data := integer()} |
+ #{level := ipv6, type := integer(), data := binary()} |
+ #{level := integer(), type := integer(), data := binary()}.
+-type cmsghdr_send() ::
+ #{level := socket, type := timestamp, data := binary()} |
+ #{level := socket, type := rights, data := binary()} |
+ #{level := socket, type := credentials, data := binary()} |
+ #{level := socket, type := integer(), data := binary()} |
+ #{level := ip, type := tos, data := ip_tos() | binary()} |
+ #{level := ip, type := ttl, data := integer() | binary()} |
+ #{level := ip, type := integer(), data := binary()} |
+ #{level := ipv6, type := tclass, data := integer()} |
+ #{level := ipv6, type := integer(), data := binary()} |
+ #{level := udp, type := integer(), data := binary()} |
+ #{level := integer(), type := integer(), data := binary()}.
+
+-type ee_origin() :: none | local | icmp | icmp6 | uint8().
+-type icmp_dest_unreach() :: net_unreach | host_unreach | port_unreach | frag_needed |
+ net_unknown | host_unknown | uint8().
+-type icmpv6_dest_unreach() :: noroute | adm_prohibited | not_neighbour | addr_unreach |
+ port_unreach | policy_fail | reject_route | uint8().
+-type extended_err() ::
+ #{error := term(),
+ origin := icmp,
+ type := dest_unreach,
+ code := icmp_dest_unreach(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()} |
+ #{error := term(),
+ origin := icmp,
+ type := time_exceeded | uint8(),
+ code := uint8(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()} |
+ #{error := term(),
+ origin := icmp6,
+ type := dest_unreach,
+ code := icmpv6_dest_unreach(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()} |
+ #{error := term(),
+ origin := icmp6,
+ type := pkt_toobig | time_exceeded | uint8(),
+ code := uint8(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()} |
+ #{error := term(),
+ origin := ee_origin(),
+ type := uint8(),
+ code := uint8(),
+ info := uint32(),
+ data := uint32(),
+ offender := undefined | sockaddr()}.
+
+-type errcode() ::
+ inet:posix() | % closed | timeout | not_owner |
+ exalloc | exmonitor | exselect | exself.
+
+%% ===========================================================================
+%%
+%% Interface term formats
+%%
+
+-opaque select_tag() :: atom().
+-opaque select_ref() :: reference().
+
+-type select_info() :: {select_info, select_tag(), select_ref()}.
+
+-define(SELECT_INFO(T, R), {select_info, T, R}).
+-define(SELECT(T, R), {select, ?SELECT_INFO(T, R)}).
+
+
+%% ===========================================================================
+%%
+%% Defaults
+%%
+
+-define(ESOCK_LISTEN_BACKLOG_DEFAULT, 5).
+
+-define(ESOCK_ACCEPT_TIMEOUT_DEFAULT, infinity).
+
+-define(ESOCK_SEND_FLAGS_DEFAULT, []).
+-define(ESOCK_SEND_TIMEOUT_DEFAULT, infinity).
+-define(ESOCK_SENDTO_FLAGS_DEFAULT, []).
+-define(ESOCK_SENDTO_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
+-define(ESOCK_SENDMSG_FLAGS_DEFAULT, []).
+-define(ESOCK_SENDMSG_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
+
+-define(ESOCK_RECV_FLAGS_DEFAULT, []).
+-define(ESOCK_RECV_TIMEOUT_DEFAULT, infinity).
+
+
+%% ===========================================================================
+%%
+%% Administrative and utility API
+%%
+%% ===========================================================================
+
+%% *** number_of ***
+%%
+%% Interface function to the socket registry
+%% returns the number of existing (and "alive") sockets.
+%%
+-spec number_of() -> non_neg_integer().
+
+number_of() ->
+ ?REGISTRY:number_of().
+
+
+%% *** which_sockets/0,1 ***
+%%
+%% Interface function to the socket registry
+%% Returns a list of all the sockets, accoring to the filter rule.
+%%
+-spec which_sockets() -> [socket()].
+
+which_sockets() ->
+ ?REGISTRY:which_sockets(fun(_) -> true end).
+
+-spec which_sockets(FilterRule) -> [socket()] when
+ FilterRule :: inet | inet6 |
+ stream | dgram | seqpacket |
+ sctp | tcp | udp |
+ pid() |
+ fun((socket_info()) -> boolean()).
+
+which_sockets(Domain)
+ when ((Domain =:= inet) orelse (Domain =:= inet6)) ->
+ ?REGISTRY:which_sockets(fun(#{domain := D}) when (D =:= Domain) -> true;
+ (_) -> false end);
+which_sockets(Type)
+ when ((Type =:= stream) orelse (Type =:= dgram) orelse (Type =:= seqpacket)) ->
+ ?REGISTRY:which_sockets(fun(#{type := T}) when (T =:= Type) -> true;
+ (_) -> false end);
+which_sockets(Proto)
+ when ((Proto =:= sctp) orelse (Proto =:= tcp) orelse (Proto =:= udp)) ->
+ ?REGISTRY:which_sockets(fun(#{protocol := P}) when (P =:= Proto) -> true;
+ (_) -> false end);
+which_sockets(CTRL)
+ when is_pid(CTRL) ->
+ ?REGISTRY:which_sockets(fun(#{ctrl := C}) when (C =:= CTRL) -> true;
+ (_) -> false end);
+which_sockets(Filter) when is_function(Filter, 1) ->
+ ?REGISTRY:which_sockets(Filter).
+
+
+%% ===========================================================================
+%%
+%% Debug features
+%%
+%% ===========================================================================
+
+
+-spec info() -> map().
+%%
+info() ->
+ prim_socket:info().
+
+
+-spec debug(D :: boolean()) -> ok.
+%%
+debug(D) when is_boolean(D) ->
+ prim_socket:debug(D).
+
+
+-spec socket_debug(D :: boolean()) -> ok.
+%%
+socket_debug(D) when is_boolean(D) ->
+ prim_socket:socket_debug(D).
+
+
+-spec use_registry(D :: boolean()) -> ok.
+%%
+use_registry(D) when is_boolean(D) ->
+ prim_socket:use_registry(D).
+
+
+%% ===========================================================================
+%%
+%% info - Get miscellaneous information about a socket.
+%%
+%% Generates a list of various info about the socket, such as counter values.
+%%
+%% Do *not* call this function often.
+%%
+%% ===========================================================================
+
+-spec info(Socket) -> socket_info() when
+ Socket :: socket().
+%%
+info(?mk_socket(SockRef)) when is_reference(SockRef) ->
+ prim_socket:info(SockRef);
+info(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% ===========================================================================
+%%
+%% supports - get information about what the platform "supports".
+%%
+%% Generates a list of various info about what the plaform can support.
+%% The most obvious case is 'options'.
+%%
+%% Each item in a 'supports'-list will appear only *one* time.
+%%
+%% ===========================================================================
+
+-spec supports() -> [{Key1 :: term(),
+ boolean() | [{Key2 :: term(),
+ boolean() | [{Key3 :: term(),
+ boolean()}]}]}].
+supports() ->
+ [{Key1, supports(Key1)}
+ || Key1 <- [options, send_flags, recv_flags]]
+ ++ prim_socket:supports().
+
+-spec supports(Key1 :: term()) ->
+ [{Key2 :: term(),
+ boolean() | [{Key3 :: term(),
+ boolean()}]}].
+%%
+supports(options) ->
+ [{Level, supports(options, Level)}
+ || Level <- [socket, ip, ipv6, tcp, udp, sctp]];
+supports(Key) ->
+ prim_socket:supports(Key).
+
+-spec supports(Key1 :: term(), Key2 :: term()) ->
+ [{Key3 :: term(),
+ boolean()}].
+%%
+supports(Key1, Key2) ->
+ prim_socket:supports(Key1, Key2).
+
+
+-spec is_supported(Key1 :: term()) ->
+ boolean().
+is_supported(Key1) ->
+ get_is_supported(Key1, supports()).
+%%
+-spec is_supported(Key1 :: term(), Key2 :: term()) ->
+ boolean().
+is_supported(Key1, Key2) ->
+ get_is_supported(Key2, supports(Key1)).
+%%
+-spec is_supported(Key1 :: term(), Key2 :: term(), Key3 :: term()) ->
+ boolean().
+is_supported(Key1, Key2, Key3) ->
+ get_is_supported(Key3, supports(Key1, Key2)).
+
+
+get_is_supported(Key, Supported) ->
+ case lists:keyfind(Key, 1, Supported) of
+ false ->
+ false;
+ {_, Value} ->
+ if
+ is_boolean(Value) ->
+ Value;
+ is_list(Value) ->
+ false
+ end
+ end.
+
+
+%% ===========================================================================
+%%
+%% The proper socket API
+%%
+%% ===========================================================================
+
+%% ===========================================================================
+%%
+%% <KOLLA>
+%%
+%% The nif sets up a monitor to this process, and if it dies the socket
+%% is closed. It is also used if someone wants to monitor the socket.
+%%
+%% We may therefor need monitor function(s):
+%%
+%% socket:monitor(Socket)
+%% socket:demonitor(Socket)
+%%
+%% </KOLLA>
+%%
+
+%% ===========================================================================
+%%
+%% open - create an endpoint for communication
+%%
+
+-spec open(FD) -> {ok, Socket} | {error, Reason} when
+ FD :: integer(),
+ Socket :: socket(),
+ Reason :: errcode().
+
+open(FD) when is_integer(FD) ->
+ open(FD, #{});
+open(FD) ->
+ erlang:error(badarg, [FD]).
+
+-spec open(FD, Opts) -> {ok, Socket} | {error, Reason} when
+ FD :: integer(),
+ Opts ::
+ #{domain => domain(),
+ type => type(),
+ protocol => protocol(),
+ dup => boolean(),
+ debug => boolean(),
+ use_registry => boolean()},
+ Socket :: socket(),
+ Reason :: errcode();
+
+ (Domain, Type) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Socket :: socket(),
+ Reason :: errcode().
+
+open(FD, Opts) when is_integer(FD), is_map(Opts) ->
+ case prim_socket:open(FD, Opts) of
+ {ok, SockRef} ->
+ Socket = ?mk_socket(SockRef),
+ {ok, Socket};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+open(Domain, Type) ->
+ open(Domain, Type, default).
+
+-spec open(Domain, Type, Protocol) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: default | protocol(),
+ Socket :: socket(),
+ Reason :: errcode().
+
+open(Domain, Type, Protocol) ->
+ open(Domain, Type, Protocol, #{}).
+
+-spec open(Domain, Type, Protocol, Opts) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: default | protocol(),
+ Opts ::
+ #{netns => string(),
+ debug => boolean(),
+ use_registry => boolean()},
+ Socket :: socket(),
+ Reason :: errcode().
+
+open(Domain, Type, Protocol, Opts) when is_map(Opts) ->
+ case prim_socket:open(Domain, Type, Protocol, Opts) of
+ {ok, SockRef} ->
+ Socket = ?mk_socket(SockRef),
+ {ok, Socket};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+open(Domain, Type, Protocol, Opts) ->
+ erlang:error(badarg, [Domain, Type, Protocol, Opts]).
+
+
+%% ===========================================================================
+%%
+%% bind - bind a name (an address) to a socket
+%%
+%% Note that the short (atom) addresses only work for some domains,
+%% and that the nif will reject 'broadcast' for other domains than 'inet'
+%%
+
+-spec bind(Socket, Addr) -> {ok, Port} | {error, Reason} when
+ Socket :: socket(),
+ Addr :: sockaddr() | any | broadcast | loopback,
+ Port :: port_number(),
+ Reason :: inet:posix() | closed.
+
+bind(?mk_socket(SockRef) = Socket, Addr) when is_reference(SockRef) ->
+ if
+ is_map(Addr) ->
+ prim_socket:bind(SockRef, Addr);
+ %%
+ Addr =:= any;
+ Addr =:= broadcast;
+ Addr =:= loopback ->
+ case prim_socket:getopt(SockRef, otp, domain) of
+ {ok, Domain}
+ when Domain =:= inet;
+ Domain =:= inet6 ->
+ prim_socket:bind(
+ SockRef, #{family => Domain, addr => Addr});
+ {ok, _Domain} ->
+ {error, eafnosupport};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ %%
+ true ->
+ erlang:error(badarg, [Socket, Addr])
+ end;
+bind(Socket, Addr) ->
+ erlang:error(badarg, [Socket, Addr]).
+
+
+%% ===========================================================================
+%%
+%% bind - Add or remove a bind addresses on a socket
+%%
+%% Calling this function is only valid if the socket is:
+%% type = seqpacket
+%% protocol = sctp
+%%
+%% If the domain is inet, then all addresses *must* be IPv4.
+%% If the domain is inet6, the addresses can be aither IPv4 or IPv6.
+%%
+
+-spec bind(Socket, Addrs, Action) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Addrs :: [sockaddr()],
+ Action :: add | remove,
+ Reason :: inet:posix() | closed.
+
+bind(?mk_socket(SockRef), Addrs, Action)
+ when is_reference(SockRef)
+ andalso is_list(Addrs)
+ andalso (Action =:= add
+ orelse Action =:= remove) ->
+ prim_socket:bind(SockRef, Addrs, Action);
+bind(Socket, Addrs, Action) ->
+ erlang:error(badarg, [Socket, Addrs, Action]).
+
+
+%% ===========================================================================
+%%
+%% connect - initiate a connection on a socket
+%%
+
+-spec connect(Socket) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Reason :: errcode() | closed.
+
+%% Finalize connect after connect(,, nowait) and received
+%% select message - see connect_deadline/3
+%%
+connect(?mk_socket(SockRef))
+ when is_reference(SockRef) ->
+ prim_socket:connect(SockRef);
+connect(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+-spec connect(Socket, SockAddr) -> ok | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Reason :: errcode() | closed.
+
+connect(Socket, SockAddr) ->
+ connect(Socket, SockAddr, infinity).
+
+
+-spec connect(Socket, SockAddr, nowait) ->
+ ok | {select, SelectInfo} | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, SockAddr, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Timeout :: timeout(),
+ Reason :: errcode() | closed | timeout.
+
+%% <KOLLA>
+%% Is it possible to connect with family = local for the (dest) sockaddr?
+%% </KOLLA>
+connect(?mk_socket(SockRef) = Socket, SockAddr, Timeout)
+ when is_reference(SockRef) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, SockAddr, Timeout]);
+ nowait ->
+ connect_nowait(SockRef, SockAddr);
+ Deadline ->
+ connect_deadline(SockRef, SockAddr, Deadline)
+ end;
+connect(Socket, SockAddr, Timeout) ->
+ erlang:error(badarg, [Socket, SockAddr, Timeout]).
+
+connect_nowait(SockRef, SockAddr) ->
+ case prim_socket:connect(SockRef, SockAddr) of
+ {select, Ref} ->
+ ?SELECT(connect, Ref);
+ Result ->
+ Result
+ end.
+
+connect_deadline(SockRef, SockAddr, Deadline) ->
+ case prim_socket:connect(SockRef, SockAddr) of
+ {select, Ref} ->
+ %% Connecting...
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(_Socket, select, Ref) ->
+ prim_socket:connect(SockRef);
+ ?mk_socket_msg(_Socket, abort, {Ref, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ cancel(SockRef, connect, Ref),
+ {error, timeout}
+ end;
+ Result ->
+ Result
+ end.
+
+
+%% ===========================================================================
+%%
+%% listen - listen for connections on a socket
+%%
+
+-spec listen(Socket) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Reason :: inet:posix() | closed.
+
+listen(Socket) ->
+ listen(Socket, ?ESOCK_LISTEN_BACKLOG_DEFAULT).
+
+-spec listen(Socket, Backlog) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Backlog :: integer(),
+ Reason :: inet:posix() | closed.
+
+listen(?mk_socket(SockRef), Backlog)
+ when is_reference(SockRef), is_integer(Backlog) ->
+ prim_socket:listen(SockRef, Backlog);
+listen(Socket, Backlog) ->
+ erlang:error(badarg, [Socket, Backlog]).
+
+
+%% ===========================================================================
+%%
+%% accept, accept4 - accept a connection on a socket
+%%
+
+-spec accept(LSocket) -> {ok, Socket} | {error, Reason} when
+ LSocket :: socket(),
+ Socket :: socket(),
+ Reason :: errcode() | closed.
+
+accept(Socket) ->
+ accept(Socket, ?ESOCK_ACCEPT_TIMEOUT_DEFAULT).
+
+-spec accept(LSocket, nowait) ->
+ {ok, Socket} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ LSocket :: socket(),
+ Socket :: socket(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (LSocket, Timeout) -> {ok, Socket} | {error, Reason} when
+ LSocket :: socket(),
+ Timeout :: timeout(),
+ Socket :: socket(),
+ Reason :: errcode() | closed | timeout.
+
+accept(?mk_socket(LSockRef) = Socket, Timeout)
+ when is_reference(LSockRef) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, Timeout]);
+ nowait ->
+ accept_nowait(LSockRef);
+ Deadline ->
+ accept_deadline(LSockRef, Deadline)
+ end;
+accept(Socket, Timeout) ->
+ erlang:error(badarg, [Socket, Timeout]).
+
+accept_nowait(LSockRef) ->
+ AccRef = make_ref(),
+ case prim_socket:accept(LSockRef, AccRef) of
+ select ->
+ ?SELECT(accept, AccRef);
+ Result ->
+ accept_result(LSockRef, AccRef, Result)
+ end.
+
+accept_deadline(LSockRef, Deadline) ->
+ AccRef = make_ref(),
+ case prim_socket:accept(LSockRef, AccRef) of
+ select ->
+ %% Each call is non-blocking, but even then it takes
+ %% *some* time, so just to be sure, recalculate before
+ %% the receive.
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(LSockRef), select, AccRef) ->
+ accept_deadline(LSockRef, Deadline);
+ ?mk_socket_msg(_Socket, abort, {AccRef, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ cancel(LSockRef, accept, AccRef),
+ {error, timeout}
+ end;
+ Result ->
+ accept_result(LSockRef, AccRef, Result)
+ end.
+
+accept_result(LSockRef, AccRef, Result) ->
+ case Result of
+ {ok, SockRef} ->
+ Socket = ?mk_socket(SockRef),
+ {ok, Socket};
+ {error, _} = ERROR ->
+ cancel(LSockRef, accept, AccRef), % Just to be on the safe side...
+ ERROR
+ end.
+
+
+%% ===========================================================================
+%%
+%% send, sendto, sendmsg - send a message on a socket
+%%
+
+-spec send(Socket, Data) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Reason :: term().
+
+send(Socket, Data) ->
+ send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
+
+-spec send(Socket, Data, Flags) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: send_flags(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Timeout :: nowait) ->
+ ok |
+ {ok, {binary(), SelectInfo}} |
+ {select, SelectInfo} |
+ {ok, {RestData, SelectInfo}} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Timeout :: timeout(),
+ Reason :: {errcode() | closed | timeout,
+ Remaining :: pos_integer()}.
+
+send(Socket, Data, Flags) when is_list(Flags) ->
+ send(Socket, Data, Flags, ?ESOCK_SEND_TIMEOUT_DEFAULT);
+send(Socket, Data, Timeout) ->
+ send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, Timeout).
+
+-spec send(Socket, Data, Flags, nowait) -> ok |
+ {select, SelectInfo} |
+ {ok, {RestData, SelectInfo}} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: send_flags(),
+ RestData :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Flags, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: send_flags(),
+ Timeout :: timeout(),
+ Reason :: {errcode() | closed | timeout,
+ Remaining :: pos_integer()}.
+
+send(Socket, Data, Flags, Timeout) when is_list(Data) ->
+ Bin = erlang:list_to_binary(Data),
+ send(Socket, Bin, Flags, Timeout);
+send(?mk_socket(SockRef) = Socket, Data, Flags, Timeout)
+ when is_reference(SockRef), is_binary(Data), is_list(Flags) ->
+ To = undefined,
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, Data, Flags, Timeout]);
+ nowait ->
+ send_common_nowait(SockRef, Data, To, Flags, send);
+ Deadline ->
+ send_common_deadline(SockRef, Data, To, Flags, Deadline, send)
+ end;
+send(Socket, Data, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, Data, Flags, Timeout]).
+
+send_common_nowait(SockRef, Data, To, Flags, SendName) ->
+ SendRef = make_ref(),
+ case
+ case SendName of
+ send ->
+ prim_socket:send(SockRef, SendRef, Data, Flags);
+ sendto ->
+ prim_socket:sendto(SockRef, SendRef, Data, To, Flags)
+ end
+ of
+ {ok, Written} ->
+ %% We are partially done, but the user don't want to wait (here)
+ %% for completion
+ <<_:Written/binary, Rest/binary>> = Data,
+ {ok, {Rest, ?SELECT_INFO(SendName, SendRef)}};
+ select ->
+ ?SELECT(SendName, SendRef);
+ Result ->
+ send_common_result(Data, Result)
+ end.
+
+send_common_deadline(SockRef, Data, To, Flags, Deadline, SendName) ->
+ SendRef = make_ref(),
+ case
+ case SendName of
+ send ->
+ prim_socket:send(SockRef, SendRef, Data, Flags);
+ sendto ->
+ prim_socket:sendto(SockRef, SendRef, Data, To, Flags)
+ end
+ of
+ {ok, Written} ->
+ %% We are partially done, wait for continuation
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(_Socket, select, SendRef)
+ when (Written > 0) ->
+ <<_:Written/binary, Rest/binary>> = Data,
+ send_common_deadline(
+ SockRef, Rest, To, Flags, Deadline, SendName);
+ ?mk_socket_msg(_Socket, select, SendRef) ->
+ send_common_deadline(
+ SockRef, Data, To, Flags, Deadline, SendName);
+ ?mk_socket_msg(_Socket, abort, {SendRef, Reason}) ->
+ {error, {Reason, byte_size(Data)}}
+ after Timeout ->
+ _ = cancel(SockRef, SendName, SendRef),
+ {error, {timeout, byte_size(Data)}}
+ end;
+ select ->
+ %% Wait for continuation
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(_Socket, select, SendRef) ->
+ send_common_deadline(
+ SockRef, Data, To, Flags, Deadline, SendName);
+ ?mk_socket_msg(_Socket, abort, {SendRef, Reason}) ->
+ {error, {Reason, byte_size(Data)}}
+ after Timeout ->
+ _ = cancel(SockRef, SendName, SendRef),
+ {error, {timeout, byte_size(Data)}}
+ end;
+ %%
+ {error, ealready = Reason} ->
+ %% Internal error:
+ %% we called send, got eagain, and called send again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ Result ->
+ send_common_result(Data, Result)
+ end.
+
+send_common_result(Data, Result) ->
+ case Result of
+ ok ->
+ ok;
+ {error, Reason} ->
+ {error, {Reason, byte_size(Data)}}
+ end.
+
+
+%% ---------------------------------------------------------------------------
+%%
+
+-spec sendto(Socket, Data, Dest) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: sockaddr(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()}.
+
+sendto(Socket, Data, Dest) ->
+ sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT).
+
+-spec sendto(Socket, Data, Dest, Flags) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: sockaddr(),
+ Flags :: send_flags(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Dest, Timeout :: nowait) ->
+ ok |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: sockaddr(),
+ SelectInfo :: select_info(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Dest, Timeout) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: sockaddr(),
+ Timeout :: timeout(),
+ Reason :: {errcode() | closed | timeout,
+ Remaining :: pos_integer()}.
+
+sendto(Socket, Data, Dest, Flags) when is_list(Flags) ->
+ sendto(Socket, Data, Dest, Flags, ?ESOCK_SENDTO_TIMEOUT_DEFAULT);
+sendto(Socket, Data, Dest, Timeout) ->
+ sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT, Timeout).
+
+
+-spec sendto(Socket, Data, Dest, Flags, nowait) ->
+ ok |
+ {ok, {binary(), SelectInfo}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: sockaddr(),
+ Flags :: send_flags(),
+ SelectInfo :: select_info(),
+ Reason :: {errcode() | closed,
+ Remaining :: pos_integer()};
+
+ (Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: sockaddr(),
+ Flags :: send_flags(),
+ Timeout :: timeout(),
+ Reason :: {errcode() | closed | timeout,
+ Remaining :: pos_integer()}.
+
+sendto(Socket, Data, Dest, Flags, Timeout) when is_list(Data) ->
+ Bin = erlang:list_to_binary(Data),
+ sendto(Socket, Bin, Dest, Flags, Timeout);
+sendto(?mk_socket(SockRef) = Socket, Data, Dest, Flags, Timeout)
+ when is_reference(SockRef), is_binary(Data), is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, Data, Dest, Flags, Timeout]);
+ nowait ->
+ send_common_nowait(SockRef, Data, Dest, Flags, sendto);
+ Deadline ->
+ send_common_deadline(
+ SockRef, Data, Dest, Flags, Deadline, sendto)
+ end;
+sendto(Socket, Data, Dest, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, Data, Dest, Flags, Timeout]).
+
+
+%% ---------------------------------------------------------------------------
+%%
+%% The only part of the msghdr() that *must* exist (a connected
+%% socket need not specify the addr field) is the iov.
+%% The ctrl field is optional, and the addr and flags are not
+%% used when sending.
+%%
+
+-spec sendmsg(Socket, MsgHdr) ->
+ ok |
+ {ok, Remaining} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Remaining :: erlang:iovec(),
+ Reason :: term().
+
+sendmsg(Socket, MsgHdr) ->
+ sendmsg(Socket, MsgHdr,
+ ?ESOCK_SENDMSG_FLAGS_DEFAULT, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT).
+
+
+-spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Flags :: send_flags(),
+ Reason :: errcode() | closed;
+
+ (Socket, MsgHdr, Timeout :: nowait) ->
+ ok |
+ {ok, Remaining} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Remaining :: erlang:iovec(),
+ Reason :: errcode() | closed;
+
+ (Socket, MsgHdr, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Timeout :: timeout(),
+ Reason :: errcode() | closed | timeout.
+
+sendmsg(Socket, MsgHdr, Flags) when is_list(Flags) ->
+ sendmsg(Socket, MsgHdr, Flags, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT);
+sendmsg(Socket, MsgHdr, Timeout) ->
+ sendmsg(Socket, MsgHdr, ?ESOCK_SENDMSG_FLAGS_DEFAULT, Timeout).
+
+
+-spec sendmsg(Socket, MsgHdr, Flags, nowait) ->
+ ok |
+ {ok, Remaining} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Flags :: send_flags(),
+ Remaining :: erlang:iovec(),
+ Reason :: errcode() | closed;
+
+ (Socket, MsgHdr, Flags, Timeout) ->
+ ok |
+ {ok, Remaining} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Flags :: send_flags(),
+ Timeout :: timeout(),
+ Remaining :: erlang:iovec(),
+ Reason :: errcode() | closed | timeout.
+
+sendmsg(?mk_socket(SockRef) = Socket, MsgHdr, Flags, Timeout)
+ when is_reference(SockRef), is_map(MsgHdr), is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, MsgHdr, Flags, Timeout]);
+ Deadline ->
+ sendmsg_loop(SockRef, MsgHdr, Flags, Deadline)
+ end;
+sendmsg(Socket, MsgHdr, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, MsgHdr, Flags, Timeout]).
+
+sendmsg_loop(SockRef, MsgHdr, Flags, Deadline) ->
+ SendRef = make_ref(),
+ case prim_socket:sendmsg(SockRef, SendRef, MsgHdr, Flags) of
+ ok ->
+ %% We are done
+ ok;
+ %%
+ {ok, Written} when is_integer(Written) andalso (Written > 0) ->
+ %% We should not retry here since the protocol may not
+ %% be able to handle a message being split. Leave it to
+ %% the caller to figure out (call again with the rest).
+ %%
+ %% We need to cancel this partial write.
+ %%
+ _ = cancel(SockRef, sendmsg, SendRef),
+ {ok, sendmsg_rest(maps:get(iov, MsgHdr), Written)};
+ %%
+ select when (Deadline =:= nowait) ->
+ ?SELECT(sendmsg, SendRef);
+ select ->
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, SendRef) ->
+ sendmsg_loop(SockRef, MsgHdr, Flags, Deadline);
+ ?mk_socket_msg(_Socket, abort, {SendRef, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ _ = cancel(SockRef, sendmsg, SendRef),
+ {error, timeout}
+ end;
+ %%
+ {error, ealready = Reason} when Deadline =/= nowait ->
+ %% Internal error:
+ %% we called send, got eagain, and called send again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+sendmsg_rest([B|IOVec], Written) when Written >= byte_size(B) ->
+ sendmsg_rest(IOVec, Written - byte_size(B));
+sendmsg_rest([B|IOVec], Written) ->
+ <<_:Written/binary, Rest/binary>> = B,
+ [Rest|IOVec].
+
+
+%% ===========================================================================
+%%
+%% recv, recvfrom, recvmsg - receive a message from a socket
+%%
+%% Description:
+%% There is a special case for the argument Length. If its set to zero (0),
+%% it means "give me everything you have".
+%%
+%% Returns: {ok, Binary} | {error, Reason}
+%% Binary - The received data as a binary
+%% Reason - The error reason:
+%% timeout | {timeout, AccData} |
+%% posix() | {posix(), AccData} |
+%% atom() | {atom(), AccData}
+%% AccData - The data (as a binary) that we did manage to receive
+%% before the timeout.
+%%
+%% Arguments:
+%% Socket - The socket to read from.
+%% Length - The number of bytes to read.
+%% Flags - A list of "options" for the read.
+%% Timeout - Time-out in milliseconds.
+
+-spec recv(Socket) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()}.
+
+recv(Socket) ->
+ recv(Socket, 0).
+
+-spec recv(Socket, Length) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()}.
+
+recv(Socket, Length) ->
+ recv(Socket, Length,
+ ?ESOCK_RECV_FLAGS_DEFAULT,
+ ?ESOCK_RECV_TIMEOUT_DEFAULT).
+
+-spec recv(Socket, Length, Flags) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()};
+
+ (Socket, Length, Timeout :: nowait) ->
+ {ok, Data} |
+ {ok, {Data, SelectInfo}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()};
+
+ (Socket, Length, Timeout) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Timeout :: timeout(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed | timeout |
+ {errcode() | closed | timeout, Data :: binary()}.
+
+recv(Socket, Length, Flags) when is_list(Flags) ->
+ recv(Socket, Length, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
+recv(Socket, Length, Timeout) ->
+ recv(Socket, Length, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
+
+-spec recv(Socket, Length, Flags, nowait) ->
+ {ok, Data} |
+ {ok, {Data, SelectInfo}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason ::
+ errcode() | closed |
+ {errcode() | closed, Data :: binary()};
+
+ (Socket, Length, Flags, Timeout) ->
+ {ok, Data} |
+ {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ Data :: binary(),
+ Reason ::
+ errcode() | closed | timeout |
+ {errcode() | closed | timeout, Data :: binary()}.
+
+recv(?mk_socket(SockRef) = Socket, Length, Flags, Timeout)
+ when is_reference(SockRef),
+ is_integer(Length), Length >= 0,
+ is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, Length, Flags, Timeout]);
+ nowait ->
+ recv_nowait(SockRef, Length, Flags, <<>>);
+ Deadline ->
+ recv_deadline(SockRef, Length, Flags, Deadline, <<>>)
+ end;
+recv(Socket, Length, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, Length, Flags, Timeout]).
+
+%% We will only recurse with Length == 0 if Length is 0,
+%% so Length == 0 means to return all available data also when recursing
+
+recv_nowait(SockRef, Length, Flags, Acc) ->
+ RecvRef = make_ref(),
+ case prim_socket:recv(SockRef, RecvRef, Length, Flags) of
+ {more, Bin} ->
+ %% We got what we requested but will not waste more time
+ %% although there might be more data available
+ {ok, bincat(Acc, Bin)};
+ {select, Bin} ->
+ %% We got less than requested so the caller will
+ %% get a select message when there might be more to read
+ {ok, {bincat(Acc, Bin), ?SELECT_INFO(recv, RecvRef)}};
+ select ->
+ %% The caller will get a select message when there
+ %% might me data to read
+ if
+ byte_size(Acc) =:= 0 ->
+ ?SELECT(recv, RecvRef);
+ true ->
+ {ok, {Acc, ?SELECT_INFO(recv, RecvRef)}}
+ end;
+ Result ->
+ recv_result(Acc, Result)
+ end.
+
+recv_deadline(SockRef, Length, Flags, Deadline, Acc) ->
+ RecvRef = make_ref(),
+ case prim_socket:recv(SockRef, RecvRef, Length, Flags) of
+ {more, Bin} ->
+ %% There is more data readily available
+ %% - repeat unless time's up
+ Timeout = timeout(Deadline),
+ if
+ 0 < Timeout ->
+ %% Recv more
+ recv_deadline(
+ SockRef, Length, Flags, Deadline, bincat(Acc, Bin));
+ true ->
+ {ok, bincat(Acc, Bin)}
+ end;
+ %%
+ {select, Bin} ->
+ %% We got less than requested
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) ->
+ if
+ 0 < Timeout ->
+ %% Recv more
+ recv_deadline(
+ SockRef, Length - byte_size(Bin), Flags,
+ Deadline, bincat(Acc, Bin));
+ true ->
+ {error, {timeout, bincat(Acc, Bin)}}
+ end;
+ ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) ->
+ {error, {Reason, bincat(Acc, Bin)}}
+ after Timeout ->
+ cancel(SockRef, recv, RecvRef),
+ {error, {timeout, bincat(Acc, Bin)}}
+ end;
+ %%
+ select when Length =:= 0, 0 < byte_size(Acc) ->
+ %% We first got some data and are then asked to wait,
+ %% but we only want the first that comes
+ %% - cancel and return what we have
+ cancel(SockRef, recv, RecvRef),
+ {ok, Acc};
+ select ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a select message).
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) ->
+ if
+ 0 < Timeout ->
+ %% Retry
+ recv_deadline(
+ SockRef, Length, Flags, Deadline, Acc);
+ true ->
+ recv_error(Acc, timeout)
+ end;
+ ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) ->
+ recv_error(Acc, Reason)
+ after Timeout ->
+ cancel(SockRef, recv, RecvRef),
+ recv_error(Acc, timeout)
+ end;
+ %%
+ {error, ealready = Reason} ->
+ %% Internal error:
+ %% we called recv, got eagain, and called recv again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ Result ->
+ recv_result(Acc, Result)
+ end.
+
+recv_result(Acc, Result) ->
+ case Result of
+ {ok, Bin} ->
+ {ok, bincat(Acc, Bin)};
+ {error, _} = ERROR when byte_size(Acc) =:= 0 ->
+ ERROR;
+ {error, Reason} ->
+ {error, {Reason, Acc}}
+ end.
+
+recv_error(Acc, Reason) ->
+ if
+ byte_size(Acc) =:= 0 ->
+ {error, Reason};
+ true ->
+ {error, {Reason, Acc}}
+ end.
+
+%% ---------------------------------------------------------------------------
+%%
+%% With recvfrom we get messages, which means that regardless of how
+%% much we want to read, we return when we get a message.
+%% The MaxSize argument basically defines the size of our receive
+%% buffer. By setting the size to zero (0), we use the configured
+%% size (see setopt).
+%% It may be impossible to know what (buffer) size is appropriate
+%% "in advance", and in those cases it may be convenient to use the
+%% (recv) 'peek' flag. When this flag is provided the message is *not*
+%% "consumed" from the underlying (OS) buffers, so another recvfrom call
+%% is needed, possibly with a then adjusted buffer size.
+%%
+
+-spec recvfrom(Socket) -> {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed.
+
+recvfrom(Socket) ->
+ recvfrom(Socket, 0).
+
+-spec recvfrom(Socket, BufSz) -> {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed.
+
+recvfrom(Socket, BufSz) ->
+ recvfrom(Socket, BufSz,
+ ?ESOCK_RECV_FLAGS_DEFAULT,
+ ?ESOCK_RECV_TIMEOUT_DEFAULT).
+
+-spec recvfrom(Socket, Flags, nowait) ->
+ {ok, {Source, Data}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, Flags, Timeout) ->
+ {ok, {Source, Data}} |
+ {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed | timeout;
+
+ (Socket, BufSz, Flags) ->
+ {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed;
+
+ (Socket, BufSz, nowait) ->
+ {ok, {Source, Data}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, BufSz, Timeout) ->
+ {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Timeout :: timeout(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed | timeout.
+
+recvfrom(Socket, Flags, Timeout) when is_list(Flags) ->
+ recvfrom(Socket, 0, Flags, Timeout);
+recvfrom(Socket, BufSz, Flags) when is_list(Flags) ->
+ recvfrom(Socket, BufSz, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
+recvfrom(Socket, BufSz, Timeout) ->
+ recvfrom(Socket, BufSz, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
+
+-spec recvfrom(Socket, BufSz, Flags, nowait) ->
+ {ok, {Source, Data}} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, BufSz, Flags, Timeout) ->
+ {ok, {Source, Data}} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: errcode() | closed | timeout.
+
+recvfrom(?mk_socket(SockRef) = Socket, BufSz, Flags, Timeout)
+ when is_reference(SockRef),
+ is_integer(BufSz), 0 =< BufSz,
+ is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, BufSz, Flags, Timeout]);
+ nowait ->
+ recvfrom_nowait(SockRef, BufSz, Flags);
+ Deadline ->
+ recvfrom_deadline(SockRef, BufSz, Flags, Deadline)
+ end;
+recvfrom(Socket, BufSz, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, BufSz, Flags, Timeout]).
+
+recvfrom_nowait(SockRef, BufSz, Flags) ->
+ RecvRef = make_ref(),
+ case prim_socket:recvfrom(SockRef, RecvRef, BufSz, Flags) of
+ select ->
+ ?SELECT(recvfrom, RecvRef);
+ Result ->
+ recvfrom_result(Result)
+ end.
+
+recvfrom_deadline(SockRef, BufSz, Flags, Deadline) ->
+ RecvRef = make_ref(),
+ case prim_socket:recvfrom(SockRef, RecvRef, BufSz, Flags) of
+ select ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a select message).
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) ->
+ recvfrom_deadline(SockRef, BufSz, Flags, Deadline);
+ ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ cancel(SockRef, recvfrom, RecvRef),
+ {error, timeout}
+ end;
+ {error, ealready = Reason} ->
+ %% Internal error:
+ %% we called recvfrom, got eagain, and called recvfrom again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ Result ->
+ recvfrom_result(Result)
+ end.
+
+recvfrom_result(Result) ->
+ case Result of
+ {ok, {_Source, _NewData}} = OK ->
+ OK;
+ {error, _Reason} = ERROR ->
+ ERROR
+ end.
+
+
+%% ---------------------------------------------------------------------------
+%%
+
+-spec recvmsg(Socket) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed.
+
+recvmsg(Socket) ->
+ recvmsg(Socket, 0, 0,
+ ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
+
+-spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed;
+
+ (Socket, Timeout :: nowait) -> {ok, MsgHdr} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, Timeout) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ Timeout :: timeout(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed | timeout.
+
+recvmsg(Socket, Flags) when is_list(Flags) ->
+ recvmsg(Socket, 0, 0, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
+recvmsg(Socket, Timeout) ->
+ recvmsg(Socket, 0, 0, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
+
+-spec recvmsg(Socket, Flags, nowait) -> {ok, MsgHdr} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ MsgHdr :: msghdr(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed | timeout;
+
+ (Socket, BufSz, CtrlSz) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed.
+
+recvmsg(Socket, Flags, Timeout) when is_list(Flags) ->
+ recvmsg(Socket, 0, 0, Flags, Timeout);
+recvmsg(Socket, BufSz, CtrlSz) when is_integer(BufSz), is_integer(CtrlSz) ->
+ recvmsg(Socket, BufSz, CtrlSz,
+ ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
+
+
+-spec recvmsg(Socket, BufSz, CtrlSz, Flags, nowait) ->
+ {ok, MsgHdr} |
+ {select, SelectInfo} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ MsgHdr :: msghdr(),
+ SelectInfo :: select_info(),
+ Reason :: errcode() | closed;
+
+ (Socket, BufSz, CtrlSz, Flags, Timeout) ->
+ {ok, MsgHdr} |
+ {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ MsgHdr :: msghdr(),
+ Reason :: errcode() | closed | timeout.
+
+recvmsg(?mk_socket(SockRef) = Socket, BufSz, CtrlSz, Flags, Timeout)
+ when is_reference(SockRef),
+ is_integer(BufSz), 0 =< BufSz,
+ is_integer(CtrlSz), 0 =< CtrlSz,
+ is_list(Flags) ->
+ case deadline(Timeout) of
+ badarg = Reason ->
+ erlang:error(Reason, [Socket, BufSz, CtrlSz, Flags, Timeout]);
+ nowait ->
+ recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags);
+ Deadline ->
+ recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline)
+ end;
+recvmsg(Socket, BufSz, CtrlSz, Flags, Timeout) ->
+ erlang:error(badarg, [Socket, BufSz, CtrlSz, Flags, Timeout]).
+
+recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags) ->
+ RecvRef = make_ref(),
+ case prim_socket:recvmsg(SockRef, RecvRef, BufSz, CtrlSz, Flags) of
+ select ->
+ ?SELECT(recvmsg, RecvRef);
+ Result ->
+ recvmsg_result(Result)
+ end.
+
+recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline) ->
+ RecvRef = make_ref(),
+ case prim_socket:recvmsg(SockRef, RecvRef, BufSz, CtrlSz, Flags) of
+ select ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a select message).
+ Timeout = timeout(Deadline),
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) ->
+ recvmsg_deadline(
+ SockRef, BufSz, CtrlSz, Flags, Deadline);
+ ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) ->
+ {error, Reason}
+ after Timeout ->
+ cancel(SockRef, recvmsg, RecvRef),
+ {error, timeout}
+ end;
+ %%
+ {error, ealready = Reason} ->
+ %% Internal error:
+ %% we called recvmsg, got eagain, and called recvmsg again
+ %% - without waiting for select message
+ erlang:error(Reason);
+ Result ->
+ recvmsg_result(Result)
+ end.
+
+recvmsg_result(Result) ->
+ case Result of
+ {ok, _MsgHdr} = OK ->
+ OK;
+ {error, _Reason} = ERROR ->
+ ERROR
+ end.
+
+
+%% ===========================================================================
+%%
+%% close - close a file descriptor
+%%
+%% Closing a socket is a two stage rocket (because of linger).
+%% We need to perform the actual socket close while in BLOCKING mode.
+%% But that would hang the entire VM, so what we do is divide the
+%% close in two steps:
+%% 1) prim_socket:nif_close + the socket_stop (nif) callback function
+%% This is for everything that can be done safely NON-BLOCKING.
+%% 2) prim_socket:nif_finalize_close which is executed by a *dirty* scheduler
+%% Before we call the socket close function, we set the socket
+%% BLOCKING. Thereby linger is handled properly.
+
+-spec close(Socket) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Reason :: errcode() | closed | timeout.
+
+close(?mk_socket(SockRef))
+ when is_reference(SockRef) ->
+ case prim_socket:close(SockRef) of
+ ok ->
+ prim_socket:finalize_close(SockRef);
+ {ok, CloseRef} ->
+ %% We must wait for the socket_stop callback function to
+ %% complete its work
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), close, CloseRef) ->
+ prim_socket:finalize_close(SockRef)
+ end;
+ {error, _} = ERROR ->
+ ERROR
+ end;
+close(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+
+%% ===========================================================================
+%%
+%% shutdown - shut down part of a full-duplex connection
+%%
+
+-spec shutdown(Socket, How) -> ok | {error, Reason} when
+ Socket :: socket(),
+ How :: shutdown_how(),
+ Reason :: inet:posix() | closed.
+
+shutdown(?mk_socket(SockRef), How)
+ when is_reference(SockRef) ->
+ prim_socket:shutdown(SockRef, How);
+shutdown(Socket, How) ->
+ erlang:error(badarg, [Socket, How]).
+
+
+%% ===========================================================================
+%%
+%% setopt - manipulate individual properties of a socket
+%%
+%% What properties are valid depend on what kind of socket it is
+%% (domain, type and protocol)
+%% If its an "invalid" option (or value), we should not crash but return some
+%% useful error...
+%%
+%% <KOLLA>
+%%
+%% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING
+%% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!!
+%%
+%% </KOLLA>
+
+-spec setopt(Socket, otp, otp_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: errcode() | closed | not_owner;
+
+ (Socket, socket, socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, ip, ip_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, ipv6, ipv6_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, tcp, tcp_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, udp, udp_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, sctp, sctp_socket_option(), Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, Level, Key, Value) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Level :: non_neg_integer(),
+ Key :: non_neg_integer(),
+ Value :: binary(),
+ Reason :: inet:posix() | closed.
+
+setopt(?mk_socket(SockRef), Level, Key, Value)
+ when is_reference(SockRef) ->
+ prim_socket:setopt(SockRef, Level, Key, Value);
+setopt(Socket, Level, Key, Value) ->
+ erlang:error(badarg, [Socket, Level, Key, Value]).
+
+
+%% ===========================================================================
+%%
+%% getopt - retrieve individual properties of a socket
+%%
+%% What properties are valid depend on what kind of socket it is
+%% (domain, type and protocol).
+%% If its an "invalid" option, we should not crash but return some
+%% useful error...
+%%
+%% When specifying level as an integer, and therefor using "native mode",
+%% we should make it possible to specify common types instead of the
+%% value size. Example: int | bool | {string, pos_integer()} | non_neg_integer()
+%%
+
+-spec getopt(Socket, otp, otp_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: einval | closed;
+
+ (Socket, socket, socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, ip, ip_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, ipv6, ipv6_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, tcp, tcp_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, udp, udp_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, sctp, sctp_socket_option()) ->
+ {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: inet:posix() | closed;
+
+ (Socket, Level, Key) ->
+ ok | {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Level :: integer(),
+ Key :: {NativeOpt, ValueSize},
+ NativeOpt :: integer(),
+ ValueSize :: int | bool | non_neg_integer(),
+ Value :: term(),
+ Reason :: inet:posix() | closed.
+
+getopt(?mk_socket(SockRef), Level, Key)
+ when is_reference(SockRef) ->
+ prim_socket:getopt(SockRef, Level, Key);
+getopt(Socket, Level, Key) ->
+ erlang:error(badarg, [Socket, Level, Key]).
+
+
+%% ===========================================================================
+%%
+%% sockname - return the current address of the socket.
+%%
+%%
+
+-spec sockname(Socket) -> {ok, SockAddr} | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Reason :: inet:posix() | closed.
+
+sockname(?mk_socket(SockRef))
+ when is_reference(SockRef) ->
+ prim_socket:sockname(SockRef);
+sockname(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% ===========================================================================
+%%
+%% peername - return the address of the peer *connected* to the socket.
+%%
+%%
+
+-spec peername(Socket) -> {ok, SockAddr} | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Reason :: inet:posix() | closed.
+
+peername(?mk_socket(SockRef))
+ when is_reference(SockRef) ->
+ prim_socket:peername(SockRef);
+peername(Socket) ->
+ erlang:error(badarg, [Socket]).
+
+
+%% ===========================================================================
+%%
+%% cancel - cancel an operation resulting in a select
+%%
+%% A call to accept, recv/recvfrom/recvmsg and send/sendto/sendmsg
+%% can result in a select if they are called with the Timeout argument
+%% set to nowait. This is indicated by the return of the select-info.
+%% Such a operation can be cancelled by calling this function.
+%%
+
+-spec cancel(Socket, SelectInfo) -> ok | {error, Reason} when
+ Socket :: socket(),
+ SelectInfo :: select_info(),
+ Reason :: einval | closed | exself.
+
+cancel(?mk_socket(SockRef), ?SELECT_INFO(Tag, Ref))
+ when is_reference(SockRef) ->
+ cancel(SockRef, Tag, Ref);
+cancel(Socket, SelectInfo) ->
+ erlang:error(badarg, [Socket, SelectInfo]).
+
+
+cancel(SockRef, Op, OpRef) ->
+ case prim_socket:cancel(SockRef, Op, OpRef) of
+ %% The select has already completed
+ {error, select_sent} ->
+ flush_select_msg(SockRef, OpRef),
+ _ = flush_abort_msg(SockRef, OpRef),
+ ok;
+ {error, not_found} ->
+ _ = flush_abort_msg(SockRef, OpRef),
+ {error, einval};
+ Other ->
+ _ = flush_abort_msg(SockRef, OpRef),
+ Other
+ end.
+
+flush_select_msg(SockRef, Ref) ->
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), select, Ref) ->
+ ok
+ after 0 ->
+ ok
+ end.
+
+flush_abort_msg(SockRef, Ref) ->
+ receive
+ ?mk_socket_msg(?mk_socket(SockRef), abort, {Ref, Reason}) ->
+ Reason
+ after 0 ->
+ ok
+ end.
+
+
+%% ===========================================================================
+%%
+%% Misc utility functions
+%%
+%% ===========================================================================
+
+%% formated_timestamp() ->
+%% format_timestamp(os:timestamp()).
+
+%% format_timestamp(Now) ->
+%% N2T = fun(N) -> calendar:now_to_local_time(N) end,
+%% format_timestamp(Now, N2T, true).
+
+%% format_timestamp({_N1, _N2, N3} = N, N2T, true) ->
+%% FormatExtra = ".~.2.0w",
+%% ArgsExtra = [N3 div 10000],
+%% format_timestamp(N, N2T, FormatExtra, ArgsExtra);
+%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) ->
+%% FormatExtra = "",
+%% ArgsExtra = [],
+%% format_timestamp(N, N2T, FormatExtra, ArgsExtra).
+
+%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) ->
+%% {Date, Time} = N2T(N),
+%% {YYYY,MM,DD} = Date,
+%% {Hour,Min,Sec} = Time,
+%% FormatDate =
+%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra,
+%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra),
+%% lists:flatten(FormatDate).
+
+
+deadline(Timeout) ->
+ case Timeout of
+ nowait ->
+ Timeout;
+ infinity ->
+ Timeout;
+ 0 ->
+ zero;
+ _ when is_integer(Timeout), 0 < Timeout ->
+ timestamp() + Timeout;
+ _ ->
+ badarg
+ end.
+
+timeout(Deadline) ->
+ case Deadline of
+ infinity ->
+ Deadline;
+ zero ->
+ 0;
+ _ ->
+ Now = timestamp(),
+ if
+ Deadline > Now ->
+ Deadline - Now;
+ true ->
+ 0
+ end
+ end.
+
+timestamp() ->
+ erlang:monotonic_time(milli_seconds).
+
+
+-compile({inline, [bincat/2]}).
+bincat(<<>>, <<_/binary>> = B) -> B;
+bincat(<<_/binary>> = A, <<>>) -> A;
+bincat(<<_/binary>> = A, <<_/binary>> = B) ->
+ <<A/binary, B/binary>>.
+
+
+%% p(F) ->
+%% p(F, []).
+
+%% p(F, A) ->
+%% p(get(sname), F, A).
+
+%% p(undefined, F, A) ->
+%% p("***", F, A);
+%% p(SName, F, A) ->
+%% TS = formated_timestamp(),
+%% io:format(user,"[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]),
+%% io:format("[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]).
diff --git a/lib/kernel/src/user.erl b/lib/kernel/src/user.erl
index 93920dca50..67c2eafdbe 100644
--- a/lib/kernel/src/user.erl
+++ b/lib/kernel/src/user.erl
@@ -537,54 +537,6 @@ get_line_doit(Prompt, Port, Q, Accu, Enc) ->
binrev(L, T) ->
list_to_binary(lists:reverse(L, T)).
-%% is_cr_at(Pos,Bin) ->
-%% case Bin of
-%% <<_:Pos/binary,$\r,_/binary>> ->
-%% true;
-%% _ ->
-%% false
-%% end.
-
-%% collect_line_bin_re(Bin,_Data,Stack,_) ->
-%% case re:run(Bin,<<"\n">>) of
-%% nomatch ->
-%% X = byte_size(Bin)-1,
-%% case is_cr_at(X,Bin) of
-%% true ->
-%% <<D:X/binary,_/binary>> = Bin,
-%% [<<$\r>>,D|Stack];
-%% false ->
-%% [Bin|Stack]
-%% end;
-%% {match,[{Pos,1}]} ->
-%% PosPlus = Pos + 1,
-%% case Stack of
-%% [] ->
-%% case is_cr_at(Pos - 1,Bin) of
-%% false ->
-%% <<Head:PosPlus/binary,Tail/binary>> = Bin,
-%% {stop, Head, Tail};
-%% true ->
-%% PosMinus = Pos - 1,
-%% <<Head:PosMinus/binary,_,_,Tail/binary>> = Bin,
-%% {stop, binrev([],[Head,$\n]),Tail}
-%% end;
-%% [<<$\r>>|Stack1] when Pos =:= 0 ->
-
-%% <<_:PosPlus/binary,Tail/binary>> = Bin,
-%% {stop,binrev(Stack1, [$\n]),Tail};
-%% _ ->
-%% case is_cr_at(Pos - 1,Bin) of
-%% false ->
-%% <<Head:PosPlus/binary,Tail/binary>> = Bin,
-%% {stop,binrev(Stack, [Head]),Tail};
-%% true ->
-%% PosMinus = Pos - 1,
-%% <<Head:PosMinus/binary,_,_,Tail/binary>> = Bin,
-%% {stop, binrev(Stack,[Head,$\n]),Tail}
-%% end
-%% end
-%% end.
%% get_chars(Prompt, Module, Function, XtraArg, Port, Queue, Encoding)
%% Gets characters from the input port until the applied function
%% returns {stop,Result,RestBuf}. Does not block output until input
@@ -618,9 +570,6 @@ get_chars(Prompt, M, F, Xa, Port, Q, State, Enc) ->
{Port, eof} ->
put(eof, true),
{ok, eof, queue:new()};
- %%{io_request,From,ReplyAs,Request} when is_pid(From) ->
- %% get_chars_req(Prompt, M, F, Xa, Port, queue:new(), State,
- %% Request, From, ReplyAs);
{io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) ->
do_io_request(Req, From, ReplyAs, Port,
queue:new()), %Keep Q over this call
diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl
index 3ef6bc5533..6d4163e744 100644
--- a/lib/kernel/src/user_drv.erl
+++ b/lib/kernel/src/user_drv.erl
@@ -120,7 +120,20 @@ server1(Iport, Oport, Shell) ->
{Curr,Shell1} =
case init:get_argument(remsh) of
{ok,[[Node]]} ->
- ANode = list_to_atom(append_hostname(Node)),
+ ANode =
+ if
+ node() =:= nonode@nohost ->
+ %% We try to connect to the node if the current node is not
+ %% a distributed node yet. If this succeeds it means that we
+ %% are running using "-sname undefined".
+ _ = net_kernel:start([undefined, shortnames]),
+ NodeName = append_hostname(Node, net_kernel:nodename()),
+ true = net_kernel:connect_node(NodeName),
+ NodeName;
+ true ->
+ append_hostname(Node, node())
+ end,
+
RShell = {ANode,shell,start,[]},
RGr = group:start(self(), RShell, rem_sh_opts(ANode)),
{RGr,RShell};
@@ -139,10 +152,12 @@ server1(Iport, Oport, Shell) ->
%% Enter the server loop.
server_loop(Iport, Oport, Curr, User, Gr, {false, queue:new()}).
-append_hostname(Node) ->
- case string:find(Node, "@") of
- nomatch -> Node ++ string:find(atom_to_list(node()), "@");
- _ -> Node
+append_hostname(Node, LocalNode) ->
+ case string:find(Node,"@") of
+ nomatch ->
+ list_to_atom(Node ++ string:find(atom_to_list(LocalNode),"@"));
+ _ ->
+ list_to_atom(Node)
end.
rem_sh_opts(Node) ->
diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile
index 308e7e75b2..15691ed0f4 100644
--- a/lib/kernel/test/Makefile
+++ b/lib/kernel/test/Makefile
@@ -24,7 +24,24 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# Target Specs
# ----------------------------------------------------
+SOCKET_MODULES = \
+ socket_test_lib \
+ socket_test_logger \
+ socket_test_evaluator \
+ socket_test_ttest_lib \
+ socket_test_ttest_tcp_gen \
+ socket_test_ttest_tcp_socket \
+ socket_test_ttest_tcp_client \
+ socket_test_ttest_tcp_client_gen \
+ socket_test_ttest_tcp_client_socket \
+ socket_test_ttest_tcp_server \
+ socket_test_ttest_tcp_server_gen \
+ socket_test_ttest_tcp_server_socket \
+ socket_SUITE
+
MODULES= \
+ kernel_test_lib \
+ erpc_SUITE \
rpc_SUITE \
pdict_SUITE \
bif_SUITE \
@@ -85,8 +102,10 @@ MODULES= \
logger_test_lib \
net_SUITE \
os_SUITE \
+ pg_SUITE \
pg2_SUITE \
seq_trace_SUITE \
+ $(SOCKET_MODULES) \
wrap_log_reader_SUITE \
cleanup \
ignore_cores \
@@ -111,6 +130,11 @@ APP_FILES = \
topApp3.app
ERL_FILES= $(MODULES:%=%.erl) code_a_test.erl
+HRL_FILES= \
+ kernel_test_lib.hrl \
+ socket_test_evaluator.hrl \
+ socket_test_ttest.hrl \
+ socket_test_ttest_client.hrl
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
INSTALL_PROGS= $(TARGET_FILES)
@@ -133,6 +157,7 @@ ERL_COMPILE_FLAGS +=
EBIN = .
TARGETS = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR))
# ----------------------------------------------------
@@ -159,6 +184,7 @@ clean:
docs:
targets: $(TARGETS)
+socket_targets: $(SOCKET_TARGETS)
# ----------------------------------------------------
@@ -170,7 +196,7 @@ release_spec: opt
release_tests_spec: make_emakefile
$(INSTALL_DIR) "$(RELSYSDIR)"
- $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(APP_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) \
kernel.spec kernel_smoke.spec kernel_bench.spec logger.spec \
diff --git a/lib/kernel/test/application_SUITE.erl b/lib/kernel/test/application_SUITE.erl
index 07ab298510..65c691b630 100644
--- a/lib/kernel/test/application_SUITE.erl
+++ b/lib/kernel/test/application_SUITE.erl
@@ -33,13 +33,14 @@
start_phases/1, get_key/1, get_env/1,
set_env/1, set_env_persistent/1, set_env_errors/1,
permit_false_start_local/1, permit_false_start_dist/1, script_start/1,
- nodedown_start/1, init2973/0, loop2973/0, loop5606/1]).
+ nodedown_start/1, init2973/0, loop2973/0, loop5606/1, otp_16504/1]).
-export([config_change/1, persistent_env/1,
distr_changed_tc1/1, distr_changed_tc2/1,
ensure_started/1, ensure_all_started/1,
shutdown_func/1, do_shutdown/1, shutdown_timeout/1, shutdown_deadlock/1,
- config_relative_paths/1]).
+ config_relative_paths/1, handle_many_config_files/1,
+ format_log_1/1, format_log_2/1]).
-define(TESTCASE, testcase_name).
-define(testcase, proplists:get_value(?TESTCASE, Config)).
@@ -59,13 +60,13 @@ all() ->
set_env, set_env_persistent, set_env_errors,
{group, distr_changed}, config_change, shutdown_func, shutdown_timeout,
shutdown_deadlock, config_relative_paths,
- persistent_env].
+ persistent_env, handle_many_config_files, format_log_1, format_log_2].
groups() ->
[{reported_bugs, [],
[otp_1586, otp_2078, otp_2012, otp_2718, otp_2973,
otp_3002, otp_3184, otp_4066, otp_4227, otp_5363,
- otp_5606]},
+ otp_5606, otp_16504]},
{distr_changed, [],
[distr_changed_tc1, distr_changed_tc2]}].
@@ -1568,7 +1569,24 @@ loop5606(Pid) ->
Res = application:start(app1),
Pid ! {self(), Res}
end.
-
+
+otp_16504(Config) when is_list(Config) ->
+ {ok, Fd} = file:open("app1.app", [write]),
+ w_app1(Fd),
+ file:close(Fd),
+ register(test_application_stop_called, self()),
+
+ ok = application:ensure_started(app1),
+ Master = application_controller:get_master(app1),
+ exit(Master, kill),
+ receive
+ {stop_called, _} ->
+ ok
+ after 3000 ->
+ ct:fail(stop_not_called)
+ end,
+ ok.
+
%% Tests get_env/* functions.
get_env(Conf) when is_list(Conf) ->
ok = application:set_env(kernel, new_var, new_val),
@@ -2076,6 +2094,31 @@ persistent_env(Conf) when is_list(Conf) ->
%% Clean up
ok = application:unload(appinc).
+
+%% Test more than one config file defined by one -config parameter:
+handle_many_config_files(Conf) when is_list(Conf) ->
+
+ %% Write a config file
+ Dir = proplists:get_value(priv_dir, Conf),
+ {ok, Fd} = file:open(filename:join(Dir, "sys.config"), [write]),
+ io:format(Fd, "[].~n", []),
+ file:close(Fd),
+ NodeName = node_name(n1, Conf),
+ Config = filename:join(Dir, "sys"),
+
+ %% Node will be started with two -config
+ %% First one has one argument and second one has two arguments.
+ {ok, Node} = start_node(
+ NodeName,
+ Config,
+ " -config " ++ Config ++ " " ++ Config
+ ),
+ case rpc:call(Node, init, get_argument, [config]) of
+ {ok, [[Config], [Config, Config]]} -> ok;
+ {ok, [[Config], [Config], [Config]]} -> ok %% This happens on windows
+ end,
+ stop_node_nice(Node).
+
%%%-----------------------------------------------------------------
%%% Tests the 'shutdown_func' kernel config parameter
%%%-----------------------------------------------------------------
@@ -3008,6 +3051,188 @@ distr_changed_prep(Conf) when is_list(Conf) ->
{value, {kernel, OldKernel}} = lists:keysearch(kernel, 1, OldEnv),
{OldKernel, OldEnv, {Cp1, Cp2, Cp3}, {Ncp1, Ncp2, Ncp3}, Config2}.
+%% Test report callback for Logger handler error_logger
+format_log_1(_Config) ->
+ FD = application:get_env(kernel, error_logger_format_depth),
+ application:unset_env(kernel, error_logger_format_depth),
+ Term = lists:seq(1, 15),
+ Application = my_application,
+ Error = exit,
+ Node = Term,
+ Report = #{label=>{application_controller,Error},
+ report=>[{application,Application},
+ {exited,Term},
+ {type,Term}]},
+ {F1, A1} = application_controller:format_log(Report),
+ FExpected1 = " application: ~tp~n"
+ " exited: ~tp~n"
+ " type: ~tp~n",
+ ct:log("F1: ~ts~nA1: ~tp", [F1,A1]),
+ FExpected1 = F1,
+ [Application,Term,Term] = A1,
+
+ Progress = #{label=>{application_controller,progress},
+ report=>[{application,Application},{started_at,Node}]},
+ {PF1,PA1} = application_controller:format_log(Progress),
+ PFExpected1 = " application: ~tp~n started_at: ~tp~n",
+ ct:log("PF1: ~ts~nPA1: ~tp", [PF1,PA1]),
+ PFExpected1 = PF1,
+ [Application,Node] = PA1,
+
+ Depth = 10,
+ ok = application:set_env(kernel, error_logger_format_depth, Depth),
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ {F2,A2} = application_controller:format_log(Report),
+ FExpected2 = " application: ~tP~n"
+ " exited: ~tP~n"
+ " type: ~tP~n",
+ ct:log("F2: ~ts~nA2: ~tp", [F2,A2]),
+ FExpected2 = F2,
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ [Application,Depth,Limited,Depth,Limited,Depth] = A2,
+
+ {PF2,PA2} = application_controller:format_log(Progress),
+ PFExpected2 = " application: ~tP~n started_at: ~tP~n",
+ ct:log("PF2: ~ts~nPA2: ~tp", [PF2,PA2]),
+ PFExpected2 = PF2,
+ [Application,Depth,Limited,Depth] = PA2,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel, error_logger_format_depth);
+ _ ->
+ application:set_env(kernel, error_logger_format_depth, FD)
+ end,
+ ok.
+
+%% Test report callback for any Logger handler
+format_log_2(_Config) ->
+ Term = lists:seq(1, 15),
+ Application = my_application,
+ Error = exit,
+ Node = Term,
+ Report = #{label=>{application_controller,Error},
+ report=>[{application,Application},
+ {exited,Term},
+ {type,Term}]},
+ FormatOpts1 = #{},
+ Str1 = flatten_format_log(Report, FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = " application: my_application\n"
+ " exited: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ " type: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("Str1: ~ts", [Str1]),
+ ct:log("length(Str1): ~p", [L1]),
+ true = Expected1 =:= Str1,
+
+ Progress = #{label=>{application_controller,progress},
+ report=>[{application,Application},{started_at,Node}]},
+ PStr1 = flatten_format_log(Progress, FormatOpts1),
+ PL1 = length(PStr1),
+ PExpected1 = " application: my_application\n"
+ " started_at: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("PStr1: ~ts", [PStr1]),
+ ct:log("length(PStr1): ~p", [PL1]),
+ true = PExpected1 =:= PStr1,
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_format_log(Report, FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = " application: my_application\n"
+ " exited: [1,2,3,4,5,6,7,8,9|...]\n"
+ " type: [1,2,3,4,5,6,7,8,9|...]\n",
+ ct:log("Str2: ~ts", [Str2]),
+ ct:log("length(Str2): ~p", [L2]),
+ true = Expected2 =:= Str2,
+
+ PStr2 = flatten_format_log(Progress, FormatOpts2),
+ PL2 = length(PStr2),
+ PExpected2 = " application: my_application\n"
+ " started_at: [1,2,3,4,5,6,7,8,9|...]\n",
+ ct:log("PStr2: ~ts", [PStr2]),
+ ct:log("length(PStr2): ~p", [PL2]),
+ true = PExpected2 =:= PStr2,
+
+ FormatOpts3 = #{chars_limit=>200},
+ Str3 = flatten_format_log(Report, FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = " application: my_application\n"
+ " exited: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ " type: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("Str3: ~ts", [Str3]),
+ ct:log("length(Str3): ~p", [L3]),
+ true = Expected3 =:= Str3,
+
+ PFormatOpts3 = #{chars_limit=>80},
+ PStr3 = flatten_format_log(Progress, PFormatOpts3),
+ PL3 = length(PStr3),
+ PExpected3 = " application: my_application\n"
+ " started_at:",
+ ct:log("PStr3: ~ts", [PStr3]),
+ ct:log("length(PStr3): ~p", [PL3]),
+ true = lists:prefix(PExpected3, PStr3),
+ true = PL3 < PL1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_format_log(Report, FormatOpts4),
+ L4 = length(Str4),
+
+ Expected4 = "Application: my_application. "
+ "Exited: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "Type: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].",
+ ct:log("Str4: ~ts", [Str4]),
+ ct:log("length(Str4): ~p", [L4]),
+ true = Expected4 =:= Str4,
+
+ PStr4 = flatten_format_log(Progress, FormatOpts4),
+ PL4 = length(PStr4),
+ PExpected4 = "Application: my_application. "
+ "Started at: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].",
+ ct:log("PStr4: ~ts", [PStr4]),
+ ct:log("length(PStr4): ~p", [PL4]),
+ true = PExpected4 =:= PStr4,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_format_log(Report, FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "Application: my_application. "
+ "Exited: [1,2,3,4,5,6,7,8,9|...]. "
+ "Type: [1,2,3,4,5,6,7,8,9|...].",
+ ct:log("Str5: ~ts", [Str5]),
+ ct:log("length(Str5): ~p", [L5]),
+ true = Expected5 =:= Str5,
+
+ PStr5 = flatten_format_log(Progress, FormatOpts5),
+ PL5 = length(PStr5),
+ PExpected5 = "Application: my_application. "
+ "Started at: [1,2,3,4,5,6,7,8,9|...].",
+ ct:log("PStr5: ~ts", [PStr5]),
+ ct:log("length(PStr5): ~p", [PL5]),
+ true = PExpected5 =:= PStr5,
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>100},
+ Str6 = flatten_format_log(Report, FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "Application: my_application. Exited:",
+ ct:log("Str6: ~ts", [Str6]),
+ ct:log("length(Str6): ~p", [L6]),
+ true = lists:prefix(Expected6, Str6),
+ true = L6 < L4,
+
+ PFormatOpts6 = #{single_line=>true, chars_limit=>60},
+ PStr6 = flatten_format_log(Progress, PFormatOpts6),
+ PL6 = length(PStr6),
+ PExpected6 = "Application: my_application. Started at:",
+ ct:log("PStr6: ~ts", [PStr6]),
+ ct:log("length(PStr6): ~p", [PL6]),
+ true = lists:prefix(PExpected6, PStr6),
+ true = PL6 < PL4,
+
+ ok.
+
+flatten_format_log(Report, Format) ->
+ lists:flatten(application_controller:format_log(Report, Format)).
%%% Copied from init_SUITE.erl.
is_real_system(KernelVsn, StdlibVsn) ->
diff --git a/lib/kernel/test/ch_sup.erl b/lib/kernel/test/ch_sup.erl
index 1d7f17fc95..f0ec2d1e72 100644
--- a/lib/kernel/test/ch_sup.erl
+++ b/lib/kernel/test/ch_sup.erl
@@ -32,7 +32,13 @@ start(_Type, {_AppN, Low, High}) ->
lists:seq(Low, High)),
{ok, P, []}.
-stop(_) -> ok.
+stop(State) ->
+ case whereis(test_application_stop_called) of
+ undefined ->
+ ok;
+ Pid ->
+ Pid ! {stop_called, State}
+ end.
start_phase(_Phase, _Type, _Args) ->
ok.
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index b58c74e862..df2e0ee64a 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -26,7 +26,7 @@
-export([set_path/1, get_path/1, add_path/1, add_paths/1, del_path/1,
replace_path/1, load_file/1, load_abs/1, ensure_loaded/1,
delete/1, purge/1, purge_many_exits/0, purge_many_exits/1,
- soft_purge/1, is_loaded/1, all_loaded/1,
+ soft_purge/1, is_loaded/1, all_loaded/1, all_available/1,
load_binary/1, dir_req/1, object_code/1, set_path_file/1,
upgrade/0, upgrade/1,
sticky_dir/1, pa_pz_option/1, add_del_path/1,
@@ -61,7 +61,7 @@ all() ->
[set_path, get_path, add_path, add_paths, del_path,
replace_path, load_file, load_abs, ensure_loaded,
delete, purge, purge_many_exits, soft_purge, is_loaded, all_loaded,
- load_binary, dir_req, object_code, set_path_file,
+ all_available, load_binary, dir_req, object_code, set_path_file,
upgrade,
sticky_dir, pa_pz_option, add_del_path, dir_disappeared,
ext_mod_dep, clash, where_is_file,
@@ -507,6 +507,47 @@ all_unique([]) -> ok;
all_unique([_]) -> ok;
all_unique([{X,_}|[{Y,_}|_]=T]) when X < Y -> all_unique(T).
+all_available(Config) when is_list(Config) ->
+ case test_server:is_cover() of
+ true -> {skip,"Cover is running"};
+ false -> all_available_1(Config)
+ end.
+
+all_available_1(Config) ->
+
+ %% Add an ez dir to make sure the modules in there are found
+ DDir = proplists:get_value(data_dir,Config)++"clash/",
+ true = code:add_path(DDir++"foobar-0.1.ez/foobar-0.1/ebin"),
+
+ Available = code:all_available(),
+
+ %% Test that baz and blarg that are part of the .ez archive are found
+ {value, _} =
+ lists:search(fun({Name,_,Loaded}) -> not Loaded andalso Name =:= "baz" end, Available),
+ {value, _} =
+ lists:search(fun({Name,_,Loaded}) -> not Loaded andalso Name =:= "blarg" end, Available),
+
+ %% Test that all loaded are part of all available
+ Loaded = [{atom_to_list(M),P,true} || {M,P} <- code:all_loaded()],
+ [] = Loaded -- Available,
+
+ {value, {ModStr,_Path,false} = NotLoaded} =
+ lists:search(fun({Name,_,Loaded}) -> not is_atom(Name) end, Available),
+ ct:log("Testing with ~p",[NotLoaded]),
+
+ Mod = list_to_atom(ModStr),
+
+ %% Test that the module is actually not loaded
+ false = code:is_loaded(Mod),
+
+ %% Load it
+ Mod:module_info(),
+
+ {value, {ModStr,_Path,true}} =
+ lists:search(fun({Name,_,_}) -> Name =:= ModStr end, code:all_available()),
+
+ ok.
+
load_binary(Config) when is_list(Config) ->
TestDir = test_dir(),
File = TestDir ++ "/code_b_test" ++ code:objfile_extension(),
@@ -1883,6 +1924,11 @@ module_status() ->
loaded = code:module_status(erlang), % preloaded
loaded = code:module_status(?MODULE), % normal known loaded
+ %% module_status/0 returns status for each loaded module
+ true = (lists:sort([{M, code:module_status(M)}
+ || {M, _} <- code:all_loaded()])
+ =:= lists:sort(code:module_status())),
+
non_existing = code:which(?TESTMOD), % verify dummy name not in path
code:purge(?TESTMOD), % ensure no previous version in memory
code:delete(?TESTMOD),
@@ -1895,6 +1941,11 @@ module_status() ->
"" = code:which(?TESTMOD), % verify empty string for source file
loaded = code:module_status(?TESTMOD),
+ %% module_status/1 also accepts a list of modules
+ [] = code:module_status([]),
+ [{erlang, loaded},{?MODULE,loaded},{?TESTMOD,loaded}] =
+ code:module_status([erlang, ?MODULE, ?TESTMOD]),
+
%% deleting generated code
true = code:delete(?TESTMOD),
non_existing = code:which(?TESTMOD), % verify still not in path
diff --git a/lib/kernel/test/disk_log_SUITE.erl b/lib/kernel/test/disk_log_SUITE.erl
index dc72c304cc..99711bcf75 100644
--- a/lib/kernel/test/disk_log_SUITE.erl
+++ b/lib/kernel/test/disk_log_SUITE.erl
@@ -89,7 +89,7 @@
dist_terminate/1, dist_accessible/1, dist_deadlock/1,
dist_open2/1, other_groups/1,
- otp_6278/1, otp_10131/1]).
+ otp_6278/1, otp_10131/1, otp_16768/1]).
-export([head_fun/1, hf/0, lserv/1,
measure/0, init_m/1, xx/0, head_exit/0, slow_header/1]).
@@ -121,7 +121,7 @@
[halt_int, wrap_int, halt_ext, wrap_ext, read_mode, head,
notif, new_idx_vsn, reopen, block, unblock, open, close,
error, chunk, truncate, many_users, info, change_size,
- change_attribute, distribution, otp_6278, otp_10131]).
+ change_attribute, distribution, otp_6278, otp_10131, otp_16768]).
%% These test cases should be skipped if the VxWorks card is
%% configured without NFS cache.
@@ -147,7 +147,7 @@ all() ->
{group, open}, {group, close}, {group, error}, chunk,
truncate, many_users, {group, info},
{group, change_size}, change_attribute,
- {group, distribution}, otp_6278, otp_10131].
+ {group, distribution}, otp_6278, otp_10131, otp_16768].
groups() ->
[{halt_int, [], [halt_int_inf, {group, halt_int_sz}]},
@@ -4706,6 +4706,41 @@ otp_10131(Conf) when is_list(Conf) ->
ok = disk_log:close(Log),
ok.
+%% OTP-16768. Bad number of items with truncate/1. ERL-1312, ERL-1313.
+otp_16768(Conf) when is_list(Conf) ->
+ Dir = ?privdir(Conf),
+ Log = otp_16768,
+ File = filename:join(Dir, Log),
+ Header = <<"123456789\n">>,
+ head_count(Log, File, Header, external, 25),
+ head_count(Log, File, none, external, 20),
+ head_count(Log, File, Header, internal, 30),
+ head_count(Log, File, none, internal, 20),
+ ok.
+
+head_count(Log, File, Header, Format, Expected) ->
+ del(File, 10),
+ Content = <<"1234567890123456789\n">>,
+ HeaderSize = case Header of
+ none -> 0;
+ _ -> byte_size(Header)
+ end,
+ %% 5 files for the external format, more for the internal format
+ MaxSizePerFile = HeaderSize + (5 * byte_size(Content)) - 1,
+ {ok, Log} = disk_log:open([{file, File},
+ {name, Log},
+ {format, Format},
+ {head, Header},
+ {size, {MaxSizePerFile, 999}},
+ {type, wrap}
+ ]),
+ ok = disk_log:truncate(Log),
+ lists:foreach(fun(_I) -> disk_log:blog(Log, Content) end,
+ lists:seq(1, 20)),
+ DiskLogInfo = disk_log:info(Log),
+ Expected = proplists:get_value(no_items, DiskLogInfo),
+ ok = disk_log:close(Log).
+
mark(FileName, What) ->
{ok,Fd} = file:open(FileName, [raw, binary, read, write]),
{ok,_} = file:position(Fd, 4),
diff --git a/lib/kernel/test/erl_boot_server_SUITE.erl b/lib/kernel/test/erl_boot_server_SUITE.erl
index 1eaa2cf500..f0200e7c56 100644
--- a/lib/kernel/test/erl_boot_server_SUITE.erl
+++ b/lib/kernel/test/erl_boot_server_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
-module(erl_boot_server_SUITE).
-include_lib("common_test/include/ct.hrl").
+-include("kernel_test_lib.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2]).
@@ -44,11 +45,36 @@ all() ->
groups() ->
[].
-init_per_suite(Config) ->
- Config.
+init_per_suite(Config0) ->
-end_per_suite(_Config) ->
- ok.
+ ?P("init_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ case ?LIB:init_per_suite([{allow_skip, false} | Config0]) of
+ {skip, _} = SKIP ->
+ SKIP;
+
+ Config1 when is_list(Config1) ->
+
+ ?P("init_per_suite -> end when "
+ "~n Config: ~p", [Config1]),
+
+ Config1
+ end.
+
+end_per_suite(Config0) ->
+
+ ?P("end_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ Config1 = ?LIB:end_per_suite(Config0),
+
+ ?P("end_per_suite -> "
+ "~n Nodes: ~p", [erlang:nodes()]),
+
+ Config1.
init_per_group(_GroupName, Config) ->
Config.
@@ -61,7 +87,7 @@ end_per_group(_GroupName, Config) ->
%% Tests the erl_boot_server:start/1 function.
start(Config) when is_list(Config) ->
- [Host1, Host2|_] = good_hosts(Config),
+ [Host1, Host2] = ?GOOD_HOSTS(2),
%% Bad arguments.
BadHost = "bad__host",
@@ -90,7 +116,7 @@ start(Config) when is_list(Config) ->
%% Tests the erl_boot_server:start_link/1 function.
start_link(Config) when is_list(Config) ->
- [Host1, Host2|_] = good_hosts(Config),
+ [Host1, Host2] = ?GOOD_HOSTS(2),
OldFlag = process_flag(trap_exit, true),
{error, {badarg, {}}} = erl_boot_server:start_link({}),
@@ -114,7 +140,7 @@ start_link(Config) when is_list(Config) ->
%% Tests that no processes are left if a boot server is killed.
stop(Config) when is_list(Config) ->
- [Host1|_] = good_hosts(Config),
+ [Host1] = ?GOOD_HOSTS(1),
%% Start a boot server and kill it. Make sure that any helper processes
%% dies.
@@ -139,7 +165,7 @@ add(Config) when is_list(Config) ->
OldFlag = process_flag(trap_exit, true),
{ok, Pid1} = erl_boot_server:start_link([]),
[] = erl_boot_server:which_slaves(),
- [Host1, Host2, Host3|_] = good_hosts(Config),
+ [Host1, Host2, Host3] = ?GOOD_HOSTS(3),
%% Try bad values.
{error, {badarg, {}}} = erl_boot_server:add_slave({}),
@@ -181,7 +207,7 @@ add(Config) when is_list(Config) ->
delete(Config) when is_list(Config) ->
OldFlag = process_flag(trap_exit, true),
- [Host1, Host2, Host3|_] = good_hosts(Config),
+ [Host1, Host2, Host3] = ?GOOD_HOSTS(3),
{ok, Ip1} = inet:getaddr(Host1, inet),
{ok, Ip2} = inet:getaddr(Host2, inet),
{ok, Ip3} = inet:getaddr(Host3, inet),
@@ -238,7 +264,7 @@ responses(Config) when is_list(Config) ->
{ok,BootPid} = erl_boot_server:start_link([Host]),
%% Send junk
- S1 = open_udp(),
+ S1 = open_udp(Ip),
prim_inet:sendto(S1, Ip, EBOOT_PORT, ["0"]),
receive
What ->
@@ -249,10 +275,10 @@ responses(Config) when is_list(Config) ->
end,
%% Req from a slave with same erlang vsn.
- S2 = open_udp(),
+ S2 = open_udp(Ip),
prim_inet:sendto(S2, Ip, EBOOT_PORT, [EBOOT_REQUEST,ThisVer]),
receive
- {udp,S2,Ip,_Port1,Resp1} ->
+ {udp,S2,_Ip,_Port1,Resp1} ->
close_udp(S2),
EBOOT_REPLY = string:substr(Resp1, 1, length(EBOOT_REPLY)),
Rest1 = string:substr(Resp1, length(EBOOT_REPLY)+1, length(Resp1)),
@@ -263,7 +289,7 @@ responses(Config) when is_list(Config) ->
end,
%% Req from a slave with other erlang vsn.
- S3 = open_udp(),
+ S3 = open_udp(Ip),
prim_inet:sendto(S3, Ip, EBOOT_PORT, [EBOOT_REQUEST,"1.0"]),
receive
Anything ->
@@ -284,7 +310,7 @@ responses(Config) when is_list(Config) ->
{ok,BootPid2} = erl_boot_server:start_link(["127.0.0.1"]),
%% Req from slave with invalid ip address.
- S4 = open_udp(),
+ S4 = open_udp(Ip),
Ret =
case Ip of
{127,0,0,1} ->
@@ -328,19 +354,12 @@ shutdown(Pid) ->
ct:fail(shutdown)
end.
-good_hosts(_Config) ->
- %% XXX The hostnames should not be hard-coded like this. Really!
-
- {ok, GoodHost1} = inet:gethostname(),
- GoodHost2 = "gandalf",
- GoodHost3 = "sauron",
- [GoodHost1, GoodHost2, GoodHost3].
-
-open_udp() ->
+
+open_udp(Ip) ->
{ok, S} = prim_inet:open(udp, inet, dgram),
ok = prim_inet:setopts(S, [{mode,list},{active,true},
{deliver,term},{broadcast,true}]),
- {ok,_} = prim_inet:bind(S, {0,0,0,0}, 0),
+ {ok,_} = prim_inet:bind(S, Ip, 0),
S.
close_udp(S) ->
diff --git a/lib/kernel/test/erl_distribution_SUITE.erl b/lib/kernel/test/erl_distribution_SUITE.erl
index 6f0064f0fa..1c7b067375 100644
--- a/lib/kernel/test/erl_distribution_SUITE.erl
+++ b/lib/kernel/test/erl_distribution_SUITE.erl
@@ -20,6 +20,7 @@
-module(erl_distribution_SUITE).
-include_lib("common_test/include/ct.hrl").
+-include_lib("kernel/include/dist.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
@@ -28,6 +29,8 @@
connect_node/1,
nodenames/1, hostnames/1,
illegal_nodenames/1, hidden_node/1,
+ dyn_node_name/1,
+ epmd_reconnect/1,
setopts/1,
table_waste/1, net_setuptime/1,
inet_dist_options_options/1,
@@ -41,23 +44,30 @@
monitor_nodes_combinations/1,
monitor_nodes_cleanup/1,
monitor_nodes_many/1,
- dist_ctrl_proc_smoke/1]).
+ monitor_nodes_down_up/1,
+ dist_ctrl_proc_smoke/1,
+ dist_ctrl_proc_reject/1,
+ erl_1424/1]).
%% Performs the test at another node.
-export([get_socket_priorities/0,
tick_cli_test/1, tick_cli_test1/1,
tick_serv_test/2, tick_serv_test1/1,
run_remote_test/1,
+ dyn_node_name_do/2,
+ epmd_reconnect_do/2,
setopts_do/2,
keep_conn/1, time_ping/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
--export([dist_cntrlr_output_test/2]).
+-export([dist_cntrlr_output_test_size/2]).
-export([pinger/1]).
-define(DUMMY_NODE,dummy@test01).
+-define(ALT_EPMD_PORT, "12321").
+-define(ALT_EPMD_CMD, "epmd -port "++?ALT_EPMD_PORT).
%%-----------------------------------------------------------------
%% The distribution is mainly tested in the big old test_suite.
@@ -72,11 +82,15 @@ suite() ->
all() ->
[dist_ctrl_proc_smoke,
+ dist_ctrl_proc_reject,
tick, tick_change, nodenames, hostnames, illegal_nodenames,
connect_node,
+ dyn_node_name,
+ epmd_reconnect,
hidden_node, setopts,
table_waste, net_setuptime, inet_dist_options_options,
- {group, monitor_nodes}].
+ {group, monitor_nodes},
+ erl_1424].
groups() ->
[{monitor_nodes, [],
@@ -85,13 +99,16 @@ groups() ->
monitor_nodes_node_type, monitor_nodes_misc,
monitor_nodes_otp_6481, monitor_nodes_errors,
monitor_nodes_combinations, monitor_nodes_cleanup,
- monitor_nodes_many]}].
+ monitor_nodes_many,
+ monitor_nodes_down_up]}].
init_per_suite(Config) ->
+ start_gen_tcp_dist_test_type_server(),
Config.
end_per_suite(_Config) ->
[slave:stop(N) || N <- nodes()],
+ kill_gen_tcp_dist_test_type_server(),
ok.
init_per_group(_GroupName, Config) ->
@@ -105,9 +122,15 @@ init_per_testcase(TC, Config) when TC == hostnames;
file:make_dir("hostnames_nodedir"),
file:write_file("hostnames_nodedir/ignore_core_files",""),
Config;
+init_per_testcase(epmd_reconnect, Config) ->
+ [] = os:cmd(?ALT_EPMD_CMD++" -relaxed_command_check -daemon"),
+ Config;
init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
Config.
+end_per_testcase(epmd_reconnect, _Config) ->
+ os:cmd(?ALT_EPMD_CMD++" -kill"),
+ ok;
end_per_testcase(_Func, _Config) ->
ok.
@@ -206,7 +229,7 @@ nodenames(Config) when is_list(Config) ->
legal("a_1@b"),
%% Test that giving two -sname works as it should
- test_node("a_1@b", false, long_or_short() ++ "a_0@b"),
+ started = test_node("a_1@b", false, long_or_short() ++ "a_0@b"),
illegal("cdé@a"),
illegal("te欢st@a").
@@ -264,8 +287,8 @@ test_node(Name, Illigal) ->
test_node(Name, Illigal, "").
test_node(Name, Illigal, ExtraArgs) ->
ProgName = ct:get_progname(),
- Command = ProgName ++ " -noinput " ++ ExtraArgs ++
- long_or_short() ++ Name ++
+ Command = ProgName ++ " -noinput " ++
+ long_or_short() ++ Name ++ ExtraArgs ++
" -eval \"net_adm:ping('" ++ atom_to_list(node()) ++ "')\"" ++
case Illigal of
true ->
@@ -275,7 +298,7 @@ test_node(Name, Illigal, ExtraArgs) ->
end,
net_kernel:monitor_nodes(true),
BinCommand = unicode:characters_to_binary(Command, utf8),
- Prt = open_port({spawn, BinCommand}, [stream,{cd,"hostnames_nodedir"}]),
+ _Prt = open_port({spawn, BinCommand}, [stream,{cd,"hostnames_nodedir"}]),
Node = list_to_atom(Name),
receive
{nodeup, Node} ->
@@ -415,6 +438,136 @@ tick_cli_test1(Node) ->
end
end.
+epmd_reconnect(Config) when is_list(Config) ->
+ NodeNames = [N1,N2,N3] = get_nodenames(3, ?FUNCTION_NAME),
+ Nodes = [atom_to_list(full_node_name(NN)) || NN <- NodeNames],
+
+ DCfg = "-epmd_port "++?ALT_EPMD_PORT,
+
+ {_N1F,Port1} = start_node_unconnected(DCfg, N1, ?MODULE, run_remote_test,
+ ["epmd_reconnect_do", atom_to_list(node()), "1" | Nodes]),
+ {_N2F,Port2} = start_node_unconnected(DCfg, N2, ?MODULE, run_remote_test,
+ ["epmd_reconnect_do", atom_to_list(node()), "2" | Nodes]),
+ {_N3F,Port3} = start_node_unconnected(DCfg, N3, ?MODULE, run_remote_test,
+ ["epmd_reconnect_do", atom_to_list(node()), "3" | Nodes]),
+ Ports = [Port1, Port2, Port3],
+
+ ok = reap_ports(Ports),
+
+ ok.
+
+reap_ports([]) ->
+ ok;
+reap_ports(Ports) ->
+ case (receive M -> M end) of
+ {Port, Message} ->
+ case lists:member(Port, Ports) andalso Message of
+ {data,String} ->
+ io:format("~p: ~s\n", [Port, String]),
+ reap_ports(Ports);
+ {exit_status,0} ->
+ reap_ports(Ports -- [Port])
+ end
+ end.
+
+epmd_reconnect_do(_Node, ["1", Node1, Node2, Node3]) ->
+ Names = [Name || Name <- [hd(string:tokens(Node, "@")) || Node <- [Node1, Node2, Node3]]],
+ %% wait until all nodes are registered
+ ok = wait_for_names(Names),
+ "Killed" ++_ = os:cmd(?ALT_EPMD_CMD++" -kill"),
+ open_port({spawn, ?ALT_EPMD_CMD}, []),
+ %% check that all nodes reregister with epmd
+ ok = wait_for_names(Names),
+ lists:foreach(fun(Node) ->
+ ANode = list_to_atom(Node),
+ pong = net_adm:ping(ANode),
+ {epmd_reconnect_do, ANode} ! {stop, Node1, Node}
+ end, [Node2, Node3]),
+ ok;
+epmd_reconnect_do(_Node, ["2", Node1, Node2, _Node3]) ->
+ register(epmd_reconnect_do, self()),
+ receive {stop, Node1, Node2} ->
+ ok
+ after 7000 ->
+ exit(timeout)
+ end;
+epmd_reconnect_do(_Node, ["3", Node1, _Node2, Node3]) ->
+ register(epmd_reconnect_do, self()),
+ receive {stop, Node1, Node3} ->
+ ok
+ after 7000 ->
+ exit(timeout)
+ end.
+
+wait_for_names(Names) ->
+ %% wait for up to 3 seconds (the current retry timer in erl_epmd is 2s)
+ wait_for_names(lists:sort(Names), 30, 100).
+
+wait_for_names(Names, N, Wait) when N > 0 ->
+ try
+ {ok, Info} = erl_epmd:names(),
+ Names = lists:sort([Name || {Name, _Port} <- Info]),
+ ok
+ catch
+ error:{badmatch, _} ->
+ timer:sleep(Wait),
+ wait_for_names(Names, N-1, Wait)
+ end.
+
+
+dyn_node_name(Config) when is_list(Config) ->
+ %%run_dist_configs(fun dyn_node_name/2, Config).
+ dyn_node_name("", Config).
+
+dyn_node_name(DCfg, _Config) ->
+ {_N1F,Port1} = start_node_unconnected(DCfg ++ " -dist_listen false",
+ undefined, ?MODULE, run_remote_test,
+ ["dyn_node_name_do", atom_to_list(node())]),
+ 0 = wait_for_port_exit(Port1),
+ ok.
+
+dyn_node_name_do(TestNode, _Args) ->
+ nonode@nohost = node(),
+ [] = nodes(),
+ [] = nodes(hidden),
+ net_kernel:monitor_nodes(true, [{node_type,all}]),
+ net_kernel:connect_node(TestNode),
+ [] = nodes(),
+ [TestNode] = nodes(hidden),
+ MyName = node(),
+ check([MyName], rpc:call(TestNode, erlang, nodes, [hidden])),
+
+ {nodeup, MyName, [{node_type, visible}]} = receive_any(0),
+ {nodeup, TestNode, [{node_type, hidden}]} = receive_any(0),
+
+ true = net_kernel:disconnect(TestNode),
+
+ {nodedown, TestNode, [{node_type, hidden}]} = receive_any(0),
+ [] = nodes(hidden),
+ {nodedown, MyName, [{node_type, visible}]} = receive_any(1000),
+
+ nonode@nohost = node(),
+
+ net_kernel:connect_node(TestNode),
+ [] = nodes(),
+ [TestNode] = nodes(hidden),
+ MyName = node(),
+ check([MyName], rpc:call(TestNode, erlang, nodes, [hidden])),
+
+ {nodeup, MyName, [{node_type, visible}]} = receive_any(0),
+ {nodeup, TestNode, [{node_type, hidden}]} = receive_any(0),
+
+ true = rpc:cast(TestNode, net_kernel, disconnect, [MyName]),
+
+ {nodedown, TestNode, [{node_type, hidden}]} = receive_any(1000),
+ [] = nodes(hidden),
+ {nodedown, MyName, [{node_type, visible}]} = receive_any(1000),
+ nonode@nohost = node(),
+
+ ok.
+
+check(X, X) -> ok.
+
setopts(Config) when is_list(Config) ->
run_dist_configs(fun setopts/2, Config).
@@ -1456,89 +1609,273 @@ monitor_nodes_many(DCfg, _Config) ->
MonNodeState = monitor_node_state(),
ok.
+%% Test order of messages nodedown and nodeup.
+monitor_nodes_down_up(Config) when is_list(Config) ->
+ [An] = get_nodenames(1, monitor_nodeup),
+ {ok, A} = ct_slave:start(An),
+
+ try
+ monitor_nodes_yoyo(A)
+ after
+ catch ct_slave:stop(A)
+ end.
+
+monitor_nodes_yoyo(A) ->
+ net_kernel:monitor_nodes(true),
+ Papa = self(),
+
+ %% Spawn lots of processes doing one erlang:monitor_node(A,true) each
+ %% just to get lots of other monitors to fire when connection goes down
+ %% and thereby give time for {nodeup,A} to race before {nodedown,A}.
+ NodeMonCnt = 10000,
+ NodeMons = [my_spawn_opt(fun F() ->
+ monitor_node = receive_any(),
+ monitor_node(A, true),
+ Papa ! ready,
+ {nodedown, A} = receive_any(),
+ F()
+ end,
+ [link, monitor, {priority, low}])
+ ||
+ _ <- lists:seq(1, NodeMonCnt)],
+
+ %% Spawn message spamming process to trigger new connection setups
+ %% as quick as possible.
+ Spammer = my_spawn_opt(fun F() ->
+ {dummy, A} ! trigger_auto_connect,
+ F()
+ end,
+ [link, monitor]),
+
+ %% Now bring connection down and verify we get {nodedown,A} before {nodeup,A}.
+ Yoyos = 20,
+ [begin
+ [P ! monitor_node || P <- NodeMons],
+ [receive ready -> ok end || _ <- NodeMons],
+
+ Owner = get_conn_owner(A),
+ exit(Owner, kill),
+
+ {nodedown, A} = receive_any(),
+ {nodeup, A} = receive_any()
+ end
+ || _ <- lists:seq(1,Yoyos)],
+
+ unlink(Spammer),
+ exit(Spammer, die),
+ receive {'DOWN',_,process,Spammer,_} -> ok end,
+
+ [begin unlink(P), exit(P, die) end || P <- NodeMons],
+ [receive {'DOWN',_,process,P,_} -> ok end || P <- NodeMons],
+
+ net_kernel:monitor_nodes(false),
+ ok.
+
+receive_any() ->
+ receive_any(infinity).
+
+receive_any(Timeout) ->
+ receive
+ M -> M
+ after
+ Timeout -> timeout
+ end.
+
+my_spawn_opt(Fun, Opts) ->
+ case spawn_opt(Fun, Opts) of
+ {Pid, _Mref} -> Pid;
+ Pid -> Pid
+ end.
+
+get_conn_owner(Node) ->
+ {ok, NodeInfo} = net_kernel:node_info(Node),
+ {value,{owner, Owner}} = lists:keysearch(owner, 1, NodeInfo),
+ Owner.
+
dist_ctrl_proc_smoke(Config) when is_list(Config) ->
+ dist_ctrl_proc_test(get_nodenames(2, ?FUNCTION_NAME)).
+
+dist_ctrl_proc_reject(Config) when is_list(Config) ->
+ ToReject = combinations(dist_util:rejectable_flags()),
+ lists:map(fun(Flags) ->
+ ct:log("Try to reject ~p",[Flags]),
+ dist_ctrl_proc_test(get_nodenames(2, ?FUNCTION_NAME),
+ "-gen_tcp_dist_reject_flags " ++ integer_to_list(Flags))
+ end, ToReject).
+
+combinations([H | T]) ->
+ lists:flatten([[(1 bsl H) bor C || C <- combinations(T)] | combinations(T)]);
+combinations([]) ->
+ [0];
+combinations(BitField) ->
+ lists:sort(combinations(bits(BitField, 0))).
+
+bits(0, _) ->
+ [];
+bits(BitField, Cnt) when BitField band 1 == 1 ->
+ [Cnt | bits(BitField bsr 1, Cnt + 1)];
+bits(BitField, Cnt) ->
+ bits(BitField bsr 1, Cnt + 1).
+
+dist_ctrl_proc_test(Nodes) ->
+ dist_ctrl_proc_test(Nodes,"").
+
+dist_ctrl_proc_test([Name1,Name2], Extra) ->
ThisNode = node(),
- [Name1, Name2] = get_nodenames(2, dist_ctrl_proc_example_smoke),
- GetSizeArg = " -gen_tcp_dist_output_loop "
- ++ atom_to_list(?MODULE) ++ " "
- ++ "dist_cntrlr_output_test",
+ GenTcpOptProlog = "-proto_dist gen_tcp "
+ "-gen_tcp_dist_output_loop " ++ atom_to_list(?MODULE) ++ " " ++
+ "dist_cntrlr_output_test_size " ++ Extra,
{ok, Node1} = start_node("", Name1, "-proto_dist gen_tcp"),
- {ok, Node2} = start_node("", Name2, "-proto_dist gen_tcp" ++ GetSizeArg),
- pong = rpc:call(Node1, net_adm, ping, [Node2]),
- NL1 = lists:sort([ThisNode, Node2]),
- NL2 = lists:sort([ThisNode, Node1]),
- NL1 = lists:sort(rpc:call(Node1, erlang, nodes, [])),
- NL2 = lists:sort(rpc:call(Node2, erlang, nodes, [])),
+ {ok, Node2} = start_node("", Name2, GenTcpOptProlog),
+ NL = lists:sort([ThisNode, Node1, Node2]),
+ wait_until(fun () ->
+ NL == lists:sort([node()|nodes()])
+ end),
+ wait_until(fun () ->
+ NL == lists:sort([rpc:call(Node1,erlang, node, [])
+ | rpc:call(Node1, erlang, nodes, [])])
+ end),
+ wait_until(fun () ->
+ NL == lists:sort([rpc:call(Node2,erlang, node, [])
+ | rpc:call(Node2, erlang, nodes, [])])
+ end),
+
+ smoke_communicate(Node1, gen_tcp_dist, dist_cntrlr_output_loop),
+ smoke_communicate(Node2, erl_distribution_SUITE, dist_cntrlr_output_loop_size),
+ stop_node(Node1),
+ stop_node(Node2),
+ ok.
+
+smoke_communicate(Node, OLoopMod, OLoopFun) ->
%% Verify that we actually are executing the distribution
%% module we expect and also massage message passing over
- %% it a bit...
- Ps1 = rpc:call(Node1, erlang, processes, []),
+ %% the connection a bit...
+ Ps = rpc:call(Node, erlang, processes, []),
try
lists:foreach(
fun (P) ->
- case rpc:call(Node1, erlang, process_info, [P, current_stacktrace]) of
+ case rpc:call(Node, erlang, process_info, [P, current_stacktrace]) of
undefined ->
ok;
{current_stacktrace, StkTrace} ->
- lists:foreach(fun ({gen_tcp_dist,
- dist_cntrlr_output_loop,
- 2, _}) ->
+ lists:foreach(fun ({Mod, Fun, 2, _}) when Mod == OLoopMod,
+ Fun == OLoopFun ->
io:format("~p ~p~n", [P, StkTrace]),
throw(found_it);
(_) ->
ok
end, StkTrace)
end
- end, Ps1),
- exit({missing, dist_cntrlr_output_loop})
+ end, Ps),
+ exit({missing, {OLoopMod, OLoopFun}})
catch
throw:found_it -> ok
end,
+ Bin = list_to_binary(lists:duplicate(1000,100)),
+ BitStr = <<0:7999>>,
+ List = [[Bin], atom, [BitStr|Bin], make_ref(), [[[BitStr|"hopp"]]],
+ 4711, 111122222211111111111111,"hej", fun () -> ok end, BitStr,
+ self(), fun erlang:node/1],
+ Pid = spawn_link(Node, fun () -> receive {From1, Msg1} -> From1 ! Msg1 end,
+ receive {From2, Msg2} -> From2 ! Msg2 end
+ end),
+ R = make_ref(),
+ Pid ! {self(), [R, List]},
+ receive [R, L1] -> List = L1 end,
+
+ %% Send a huge message in order to trigger message fragmentation if enabled
+ FragBin = <<0:(2*(1024*64*8))>>,
+ Pid ! {self(), [R, List, FragBin]},
+ receive [R, L2, B] -> List = L2, FragBin = B end,
+
+ unlink(Pid),
+ exit(Pid, kill),
+ ok.
- Ps2 = rpc:call(Node2, erlang, processes, []),
- try
- lists:foreach(
- fun (P) ->
- case rpc:call(Node2, erlang, process_info, [P, current_stacktrace]) of
- undefined ->
- ok;
- {current_stacktrace, StkTrace} ->
- lists:foreach(fun ({erl_distribution_SUITE,
- dist_cntrlr_output_loop,
- 2, _}) ->
- io:format("~p ~p~n", [P, StkTrace]),
- throw(found_it);
- (_) ->
- ok
- end, StkTrace)
- end
- end, Ps2),
- exit({missing, dist_cntrlr_output_loop})
- catch
- throw:found_it -> ok
- end,
- stop_node(Node1),
- stop_node(Node2),
- ok.
+erl_1424(Config) when is_list(Config) ->
+ {error, Reason} = erl_epmd:names("."),
+ {comment, lists:flatten(io_lib:format("Reason: ~p", [Reason]))}.
%% Misc. functions
run_dist_configs(Func, Config) ->
- GetSizeArg = " -gen_tcp_dist_output_loop "
- ++ atom_to_list(?MODULE) ++ " "
- ++ "dist_cntrlr_output_test",
+ GetOptProlog = "-proto_dist gen_tcp -gen_tcp_dist_output_loop "
+ ++ atom_to_list(?MODULE) ++ " ",
+ GenTcpDistTest = case get_gen_tcp_dist_test_type() of
+ default ->
+ {"gen_tcp_dist", "-proto_dist gen_tcp"};
+ size ->
+ {"gen_tcp_dist (get_size)",
+ GetOptProlog ++ "dist_cntrlr_output_test_size"}
+ end,
lists:map(fun ({DCfgName, DCfg}) ->
io:format("~n~n=== Running ~s configuration ===~n~n",
[DCfgName]),
Func(DCfg, Config)
end,
- [{"default", ""},
- {"gen_tcp_dist", "-proto_dist gen_tcp"},
- {"gen_tcp_dist (get_size)", "-proto_dist gen_tcp" ++ GetSizeArg}]).
+ [{"default", ""}, GenTcpDistTest]).
-dist_cntrlr_output_test(DHandle, Socket) ->
+start_gen_tcp_dist_test_type_server() ->
+ Me = self(),
+ Go = make_ref(),
+ io:format("STARTING: gen_tcp_dist_test_type_server~n",[]),
+ {P, M} = spawn_monitor(fun () ->
+ register(gen_tcp_dist_test_type_server, self()),
+ Me ! Go,
+ gen_tcp_dist_test_type_server()
+ end),
+ receive
+ Go ->
+ ok;
+ {'DOWN', M, process, P, _} ->
+ start_gen_tcp_dist_test_type_server()
+ end.
+
+kill_gen_tcp_dist_test_type_server() ->
+ case whereis(gen_tcp_dist_test_type_server) of
+ undefined ->
+ ok;
+ Pid ->
+ exit(Pid,kill),
+ %% Sync death, before continuing...
+ false = erlang:is_process_alive(Pid)
+ end.
+
+gen_tcp_dist_test_type_server() ->
+ Type = case abs(erlang:monotonic_time(second)) rem 2 of
+ 0 -> default;
+ 1 -> size
+ end,
+ gen_tcp_dist_test_type_server(Type).
+
+gen_tcp_dist_test_type_server(Type) ->
+ receive
+ {From, Ref} ->
+ From ! {Ref, Type},
+ NewType = case Type of
+ default -> size;
+ size -> default
+ end,
+ gen_tcp_dist_test_type_server(NewType)
+ end.
+
+get_gen_tcp_dist_test_type() ->
+ Ref = make_ref(),
+ try
+ gen_tcp_dist_test_type_server ! {self(), Ref},
+ receive
+ {Ref, Type} ->
+ Type
+ end
+ catch
+ error:badarg ->
+ start_gen_tcp_dist_test_type_server(),
+ get_gen_tcp_dist_test_type()
+ end.
+
+dist_cntrlr_output_test_size(DHandle, Socket) ->
false = erlang:dist_ctrl_get_opt(DHandle, get_size),
false = erlang:dist_ctrl_set_opt(DHandle, get_size, true),
true = erlang:dist_ctrl_get_opt(DHandle, get_size),
@@ -1546,27 +1883,33 @@ dist_cntrlr_output_test(DHandle, Socket) ->
false = erlang:dist_ctrl_get_opt(DHandle, get_size),
false = erlang:dist_ctrl_set_opt(DHandle, get_size, true),
true = erlang:dist_ctrl_get_opt(DHandle, get_size),
- dist_cntrlr_output_loop(DHandle, Socket).
-
-dist_cntrlr_send_data(DHandle, Socket) ->
+ dist_cntrlr_output_loop_size(DHandle, Socket).
+
+dist_cntrlr_output_loop_size(DHandle, Socket) ->
+ receive
+ dist_data ->
+ %% Outgoing data from this node...
+ dist_cntrlr_send_data_size(DHandle, Socket);
+ _ ->
+ ok %% Drop garbage message...
+ end,
+ dist_cntrlr_output_loop_size(DHandle, Socket).
+
+dist_cntrlr_send_data_size(DHandle, Socket) ->
case erlang:dist_ctrl_get_data(DHandle) of
none ->
erlang:dist_ctrl_get_data_notification(DHandle);
{Size, Data} ->
+ ok = ensure_iovec(Data),
Size = erlang:iolist_size(Data),
ok = gen_tcp:send(Socket, Data),
- dist_cntrlr_send_data(DHandle, Socket)
+ dist_cntrlr_send_data_size(DHandle, Socket)
end.
-dist_cntrlr_output_loop(DHandle, Socket) ->
- receive
- dist_data ->
- %% Outgoing data from this node...
- dist_cntrlr_send_data(DHandle, Socket);
- _ ->
- ok %% Drop garbage message...
- end,
- dist_cntrlr_output_loop(DHandle, Socket).
+ensure_iovec([]) ->
+ ok;
+ensure_iovec([X|Y]) when is_binary(X) ->
+ ensure_iovec(Y).
monitor_node_state() ->
erts_debug:set_internal_state(available_internal_state, true),
@@ -1593,7 +1936,7 @@ print_my_messages() ->
sleep(T) -> receive after T * 1000 -> ok end.
-start_node(DCfg, Name, Param, this) ->
+start_node(_DCfg, Name, Param, this) ->
NewParam = Param ++ " -pa " ++ filename:dirname(code:which(?MODULE)),
test_server:start_node(Name, peer, [{args, NewParam}, {erl, [this]}]);
start_node(DCfg, Name, Param, "this") ->
@@ -1671,3 +2014,5 @@ block_emu(Ms) ->
Res = erts_debug:set_internal_state(block, Ms),
erts_debug:set_internal_state(available_internal_state, false),
Res.
+
+
diff --git a/lib/kernel/test/erl_distribution_wb_SUITE.erl b/lib/kernel/test/erl_distribution_wb_SUITE.erl
index b2dd4754b4..3f45860a7e 100644
--- a/lib/kernel/test/erl_distribution_wb_SUITE.erl
+++ b/lib/kernel/test/erl_distribution_wb_SUITE.erl
@@ -28,15 +28,6 @@
-export([init_per_testcase/2, end_per_testcase/2, whitebox/1,
switch_options/1, missing_compulsory_dflags/1]).
-%% 1)
-%%
-%% Connections are now always set up symmetrically with respect to
-%% publication. If connecting node doesn't send DFLAG_PUBLISHED
-%% the other node wont send DFLAG_PUBLISHED. If the connecting
-%% node send DFLAG_PUBLISHED but the other node doesn't send
-%% DFLAG_PUBLISHED, the connecting node should consider its
-%% DFLAG_PUBLISHED as dropped, i.e the connecting node wont be
-%% published on the other node.
-define(to_port(Socket, Data),
case inet_tcp:send(Socket, Data) of
@@ -47,6 +38,9 @@
R
end).
+-define(DIST_VER_HIGH, 6).
+-define(DIST_VER_LOW, 5).
+
-define(DFLAG_PUBLISHED,1).
-define(DFLAG_ATOM_CACHE,2).
-define(DFLAG_EXTENDED_REFERENCES,4).
@@ -57,15 +51,19 @@
-define(DFLAG_NEW_FUN_TAGS,16#80).
-define(DFLAG_EXTENDED_PIDS_PORTS,16#100).
-define(DFLAG_UTF8_ATOMS, 16#10000).
+-define(DFLAG_BIG_CREATION, 16#40000).
+-define(DFLAG_HANDSHAKE_23, 16#01000000).
%% From R9 and forward extended references is compulsory
%% From R10 and forward extended pids and ports are compulsory
%% From R20 and forward UTF8 atoms are compulsory
%% From R21 and forward NEW_FUN_TAGS is compulsory (no more tuple fallback {fun, ...})
+%% From R23 and forward BIG_CREATION is compulsory
-define(COMPULSORY_DFLAGS, (?DFLAG_EXTENDED_REFERENCES bor
?DFLAG_EXTENDED_PIDS_PORTS bor
?DFLAG_UTF8_ATOMS bor
- ?DFLAG_NEW_FUN_TAGS)).
+ ?DFLAG_NEW_FUN_TAGS bor
+ ?DFLAG_BIG_CREATION)).
-define(PASS_THROUGH, $p).
@@ -131,9 +129,18 @@ whitebox(Config) when is_list(Config) ->
{ok, Node} = start_node(?MODULE,""),
Cookie = erlang:get_cookie(),
{_,Host} = split(node()),
- ok = pending_up_md5(Node, join(ccc,Host), Cookie),
- ok = simultaneous_md5(Node, join('A',Host), Cookie),
- ok = simultaneous_md5(Node, join(zzzzzzzzzzzzzz,Host), Cookie),
+ [begin
+ io:format("Test OurVersion=~p and TrustEpmd=~p\n",
+ [OurVersion, TrustEpmd]),
+ ok = pending_up_md5(Node, join(ccc,Host), OurVersion,
+ TrustEpmd, Cookie),
+ ok = simultaneous_md5(Node, join('A',Host), OurVersion,
+ TrustEpmd, Cookie),
+ ok = simultaneous_md5(Node, join(zzzzzzzzzzzzzz,Host),
+ OurVersion, TrustEpmd, Cookie)
+ end
+ || OurVersion <- lists:seq(?DIST_VER_LOW, ?DIST_VER_HIGH),
+ TrustEpmd <- [true, false]],
stop_node(Node),
ok.
@@ -202,17 +209,22 @@ test_switch_active_and_packet() ->
%%
%% Handshake tests
%%
-pending_up_md5(Node,OurName,Cookie) ->
+pending_up_md5(Node,OurName,OurVersion,TrustEpmd,Cookie) ->
{NA,NB} = split(Node),
- {port,PortNo,_} = erl_epmd:port_please(NA,NB),
+ {port,PortNo,EpmdSaysVersion} = erl_epmd:port_please(NA,NB),
{ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
[{active,false},
{packet,2}]),
- send_name(SocketA,OurName,5),
+ AssumedVersion = case TrustEpmd of
+ true -> EpmdSaysVersion;
+ false -> ?DIST_VER_LOW
+ end,
+ SentNameMsg = send_name(SocketA,OurName, OurVersion, AssumedVersion),
ok = recv_status(SocketA),
- {hidden,Node,5,HisChallengeA} = recv_challenge(SocketA), % See 1)
+ {Node,ChallengeMsg,HisChallengeA} = recv_challenge(SocketA,OurVersion),
OurChallengeA = gen_challenge(),
OurDigestA = gen_digest(HisChallengeA, Cookie),
+ send_complement(SocketA, SentNameMsg, ChallengeMsg, OurVersion),
send_challenge_reply(SocketA, OurChallengeA, OurDigestA),
ok = recv_challenge_ack(SocketA, OurChallengeA, Cookie),
%%%
@@ -224,13 +236,14 @@ pending_up_md5(Node,OurName,Cookie) ->
{ok, SocketB} = gen_tcp:connect(atom_to_list(NB),PortNo,
[{active,false},
{packet,2}]),
- send_name(SocketB,OurName,5),
+ SentNameMsg = send_name(SocketB,OurName, OurVersion, AssumedVersion),
alive = recv_status(SocketB),
send_status(SocketB, true),
gen_tcp:close(SocketA),
- {hidden,Node,5,HisChallengeB} = recv_challenge(SocketB), % See 1)
+ {Node,ChallengeMsg,HisChallengeB} = recv_challenge(SocketB,OurVersion),
OurChallengeB = gen_challenge(),
OurDigestB = gen_digest(HisChallengeB, Cookie),
+ send_complement(SocketB, SentNameMsg, ChallengeMsg, OurVersion),
send_challenge_reply(SocketB, OurChallengeB, OurDigestB),
ok = recv_challenge_ack(SocketB, OurChallengeB, Cookie),
%%%
@@ -246,7 +259,7 @@ pending_up_md5(Node,OurName,Cookie) ->
gen_tcp:close(SocketB),
ok.
-simultaneous_md5(Node, OurName, Cookie) when OurName < Node ->
+simultaneous_md5(Node, OurName, OurVersion, TrustEpmd, Cookie) when OurName < Node ->
pong = net_adm:ping(Node),
LSocket = case gen_tcp:listen(0, [{active, false}, {packet,2}]) of
{ok, Socket} ->
@@ -254,15 +267,19 @@ simultaneous_md5(Node, OurName, Cookie) when OurName < Node ->
Else ->
exit(Else)
end,
- EpmdSocket = register(OurName, LSocket, 1, 5),
+ EpmdSocket = register_node(OurName, LSocket, ?DIST_VER_LOW, ?DIST_VER_LOW),
{NA, NB} = split(Node),
rpc:cast(Node, net_adm, ping, [OurName]),
receive after 1000 -> ok end,
- {port, PortNo, _} = erl_epmd:port_please(NA,NB),
+ {port, PortNo, EpmdSaysVersion} = erl_epmd:port_please(NA,NB),
{ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
[{active,false},
{packet,2}]),
- send_name(SocketA,OurName,5),
+ AssumedVersion = case TrustEpmd of
+ true -> EpmdSaysVersion;
+ false -> ?DIST_VER_LOW
+ end,
+ send_name(SocketA,OurName, OurVersion, AssumedVersion),
%% We are still not marked up on the other side, as our first message
%% is not sent.
SocketB = case gen_tcp:accept(LSocket) of
@@ -275,11 +292,13 @@ simultaneous_md5(Node, OurName, Cookie) when OurName < Node ->
%% Now we are expected to close A
gen_tcp:close(SocketA),
%% But still Socket B will continue
- {normal,Node,5} = recv_name(SocketB), % See 1)
+ {Node,GotNameMsg,GotFlags} = recv_name(SocketB),
+ true = (GotFlags band ?DFLAG_HANDSHAKE_23) =/= 0,
send_status(SocketB, ok_simultaneous),
MyChallengeB = gen_challenge(),
- send_challenge(SocketB, OurName, MyChallengeB,5),
- HisChallengeB = recv_challenge_reply(SocketB, MyChallengeB, Cookie),
+ send_challenge(SocketB, OurName, MyChallengeB, OurVersion, GotFlags),
+ recv_complement(SocketB, GotNameMsg, OurVersion),
+ {ok,HisChallengeB} = recv_challenge_reply(SocketB, MyChallengeB, Cookie),
DigestB = gen_digest(HisChallengeB,Cookie),
send_challenge_ack(SocketB, DigestB),
inet:setopts(SocketB, [{active, false},
@@ -293,7 +312,7 @@ simultaneous_md5(Node, OurName, Cookie) when OurName < Node ->
gen_tcp:close(EpmdSocket),
ok;
-simultaneous_md5(Node, OurName, Cookie) when OurName > Node ->
+simultaneous_md5(Node, OurName, OurVersion, TrustEpmd, Cookie) when OurName > Node ->
pong = net_adm:ping(Node),
LSocket = case gen_tcp:listen(0, [{active, false}, {packet,2}]) of
{ok, Socket} ->
@@ -301,11 +320,12 @@ simultaneous_md5(Node, OurName, Cookie) when OurName > Node ->
Else ->
exit(Else)
end,
- EpmdSocket = register(OurName, LSocket, 1, 5),
+ EpmdSocket = register_node(OurName, LSocket,
+ ?DIST_VER_LOW, ?DIST_VER_LOW),
{NA, NB} = split(Node),
rpc:cast(Node, net_adm, ping, [OurName]),
receive after 1000 -> ok end,
- {port, PortNo, _} = erl_epmd:port_please(NA,NB),
+ {port, PortNo, EpmdSaysVersion} = erl_epmd:port_please(NA,NB),
{ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
[{active,false},
{packet,2}]),
@@ -315,16 +335,22 @@ simultaneous_md5(Node, OurName, Cookie) when OurName > Node ->
Else2 ->
exit(Else2)
end,
- send_name(SocketA,OurName,5),
+ AssumedVersion = case TrustEpmd of
+ true -> EpmdSaysVersion;
+ false -> ?DIST_VER_LOW
+ end,
+ SentNameMsg = send_name(SocketA,OurName, OurVersion, AssumedVersion),
ok_simultaneous = recv_status(SocketA),
%% Socket B should die during this
case catch begin
- {normal,Node,5} = recv_name(SocketB), % See 1)
+ {Node,GotNameMsg,GotFlagsB} = recv_name(SocketB),
+ true = (GotFlagsB band ?DFLAG_HANDSHAKE_23) =/= 0,
send_status(SocketB, ok_simultaneous),
MyChallengeB = gen_challenge(),
send_challenge(SocketB, OurName, MyChallengeB,
- 5),
- HisChallengeB = recv_challenge_reply(
+ OurVersion, GotFlagsB),
+ recv_complement(SocketB, GotNameMsg, OurVersion),
+ {ok,HisChallengeB} = recv_challenge_reply(
SocketB,
MyChallengeB,
Cookie),
@@ -346,9 +372,10 @@ simultaneous_md5(Node, OurName, Cookie) when OurName > Node ->
end,
gen_tcp:close(SocketB),
%% But still Socket A will continue
- {hidden,Node,5,HisChallengeA} = recv_challenge(SocketA), % See 1)
+ {Node,ChallengeMsg,HisChallengeA} = recv_challenge(SocketA,OurVersion),
OurChallengeA = gen_challenge(),
OurDigestA = gen_digest(HisChallengeA, Cookie),
+ send_complement(SocketA, SentNameMsg, ChallengeMsg, OurVersion),
send_challenge_reply(SocketA, OurChallengeA, OurDigestA),
ok = recv_challenge_ack(SocketA, OurChallengeA, Cookie),
@@ -368,13 +395,16 @@ missing_compulsory_dflags(Config) when is_list(Config) ->
{ok, Node} = start_node(Name1,""),
{NA,NB} = split(Node),
{port,PortNo,_} = erl_epmd:port_please(NA,NB),
- {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
- [{active,false},
- {packet,2}]),
- BadNode = list_to_atom(atom_to_list(Name2)++"@"++atom_to_list(NB)),
- send_name(SocketA,BadNode,5,0),
- not_allowed = recv_status(SocketA),
- gen_tcp:close(SocketA),
+ [begin
+ {ok, SocketA} = gen_tcp:connect(atom_to_list(NB),PortNo,
+ [{active,false},
+ {packet,2}]),
+ BadNode = list_to_atom(atom_to_list(Name2)++"@"++atom_to_list(NB)),
+ send_name(SocketA,BadNode, Version, Version, 0),
+ not_allowed = recv_status(SocketA),
+ gen_tcp:close(SocketA)
+ end
+ || Version <- lists:seq(?DIST_VER_LOW, ?DIST_VER_HIGH)],
stop_node(Node),
ok.
@@ -486,46 +516,91 @@ recv_status(Socket) ->
exit(Bad)
end.
-send_challenge(Socket, Node, Challenge, Version) ->
- send_challenge(Socket, Node, Challenge, Version, ?COMPULSORY_DFLAGS).
-send_challenge(Socket, Node, Challenge, Version, Flags) ->
- {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
- ?to_port(Socket, [$n,?int16(Version),?int32(Flags),
- ?int32(Challenge), atom_to_list(Node)]).
+send_challenge(Socket, Node, Challenge, Version, GotFlags) ->
+ send_challenge(Socket, Node, Challenge, Version, GotFlags, ?COMPULSORY_DFLAGS).
-recv_challenge(Socket) ->
- case gen_tcp:recv(Socket, 0) of
- {ok,[$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
+send_challenge(Socket, Node, Challenge, ?DIST_VER_LOW, _GotFlags, Flags) ->
+ {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
+ ?to_port(Socket, [$n,<<?DIST_VER_LOW:16>>,<<Flags:32>>,
+ <<Challenge:32>>, atom_to_list(Node)]);
+send_challenge(Socket, Node, Challenge, ?DIST_VER_HIGH, GotFlags, Flags) ->
+ true = (GotFlags band ?DFLAG_HANDSHAKE_23) =/= 0,
+ {ok, {{_Ip1,_Ip2,_Ip3,_Ip4}, _}} = inet:sockname(Socket),
+ NodeName = atom_to_list(Node),
+ Nlen = length(NodeName),
+ Creation = erts_internal:get_creation(),
+ ?to_port(Socket, [$N, <<(Flags bor ?DFLAG_HANDSHAKE_23):64>>,
+ <<Challenge:32>>, <<Creation:32>>,
+ <<Nlen:16>>, NodeName
+ ]).
+
+recv_challenge(Socket, OurVersion) ->
+ {ok, Msg} = gen_tcp:recv(Socket, 0),
+ %%io:format("recv_challenge Msg=~p\n", [Msg]),
+ case {OurVersion, Msg} of
+ {?DIST_VER_LOW,
+ [$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
Flags = ?u32(Fl1,Fl2,Fl3,Fl4),
- Type = case Flags band ?DFLAG_PUBLISHED of
- 0 ->
- hidden;
- _ ->
- normal
- end,
+ true = (Flags band ?COMPULSORY_DFLAGS) =:= ?COMPULSORY_DFLAGS,
Node =list_to_atom(Ns),
- Version = ?u16(V1,V0),
+ ?DIST_VER_LOW = ?u16(V1,V0),
+ Challenge = ?u32(CA3,CA2,CA1,CA0),
+ {Node,$n,Challenge};
+
+ {?DIST_VER_HIGH,
+ [$N, F7,F6,F5,F4,F3,F2,F1,F0, CA3,CA2,CA1,CA0,
+ Cr3,Cr2,Cr1,Cr0, NL1,NL0 | Ns]} ->
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ true = (Flags band ?COMPULSORY_DFLAGS) =:= ?COMPULSORY_DFLAGS,
+ <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>,
+ true = (Creation =/= 0),
+ <<NameLen:16>> = <<NL1,NL0>>,
+ NameLen = length(Ns),
+ Node = list_to_atom(Ns),
Challenge = ?u32(CA3,CA2,CA1,CA0),
- {Type,Node,Version,Challenge};
+ {Node,$N,Challenge};
+
_ ->
?shutdown(no_node)
end.
+send_complement(Socket, SentNameMsg, ChallengeMsg, OurVersion) ->
+ case {SentNameMsg,ChallengeMsg} of
+ {$n,$N} ->
+ FlagsHigh = our_flags(?COMPULSORY_DFLAGS, OurVersion) bsr 32,
+ ?to_port(Socket, [$c,
+ <<FlagsHigh:32>>,
+ ?int32(erts_internal:get_creation())]);
+ {Same,Same} ->
+ ok
+ end.
+
+recv_complement(Socket, $n, OurVersion) when OurVersion > ?DIST_VER_LOW ->
+ case gen_tcp:recv(Socket, 0) of
+ {ok,[$c,Cr3,Cr2,Cr1,Cr0]} ->
+ Creation = ?u32(Cr3,Cr2,Cr1,Cr0),
+ true = (Creation =/= 0);
+ Err ->
+ {error,Err}
+ end;
+recv_complement(_, _ , _) ->
+ ok.
+
send_challenge_reply(Socket, Challenge, Digest) ->
?to_port(Socket, [$r,?int32(Challenge),Digest]).
recv_challenge_reply(Socket, ChallengeA, Cookie) ->
case gen_tcp:recv(Socket, 0) of
- {ok,[$r,CB3,CB2,CB1,CB0 | SumB]} when length(SumB) == 16 ->
+ {ok,[$r,CB3,CB2,CB1,CB0 | SumB]=Data} when length(SumB) == 16 ->
SumA = gen_digest(ChallengeA, Cookie),
ChallengeB = ?u32(CB3,CB2,CB1,CB0),
if SumB == SumA ->
- ChallengeB;
+ {ok,ChallengeB};
true ->
- ?shutdown(bad_challenge_reply)
+ {error,Data}
end;
- _ ->
- ?shutdown(no_node)
+ Err ->
+ {error,Err}
end.
send_challenge_ack(Socket, Digest) ->
@@ -539,20 +614,34 @@ recv_challenge_ack(Socket, ChallengeB, CookieA) ->
ok;
true ->
?shutdown(bad_challenge_ack)
- end;
- _ ->
- ?shutdown(bad_challenge_ack)
+ end
end.
-send_name(Socket, MyNode0, Version) ->
- send_name(Socket, MyNode0, Version, ?COMPULSORY_DFLAGS).
-send_name(Socket, MyNode0, Version, Flags) ->
+send_name(Socket, MyNode0, OurVersion, AssumedVersion) ->
+ send_name(Socket, MyNode0, OurVersion, AssumedVersion, ?COMPULSORY_DFLAGS).
+
+send_name(Socket, MyNode0, OurVersion, AssumedVersion, Flags) ->
MyNode = atom_to_list(MyNode0),
- ok = ?to_port(Socket, [<<$n,Version:16,Flags:32>>|MyNode]).
+ if (OurVersion =:= ?DIST_VER_LOW) or
+ (AssumedVersion =:= ?DIST_VER_LOW) ->
+ OurFlags = our_flags(Flags,OurVersion),
+ ok = ?to_port(Socket, [<<$n,OurVersion:16,OurFlags:32>>|MyNode]),
+ $n;
+
+ (OurVersion > ?DIST_VER_LOW) and
+ (AssumedVersion > ?DIST_VER_LOW) ->
+ Creation = erts_internal:get_creation(),
+ NameLen = length(MyNode),
+ ok = ?to_port(Socket, [<<$N, (Flags bor ?DFLAG_HANDSHAKE_23):64,
+ Creation:32,NameLen:16>>|MyNode]),
+ $N
+ end.
+
+our_flags(Flags, ?DIST_VER_LOW) ->
+ Flags;
+our_flags(Flags, OurVersion) when OurVersion > ?DIST_VER_LOW ->
+ Flags bor ?DFLAG_HANDSHAKE_23.
-%%
-%% recv_name is common for both old and new handshake.
-%%
recv_name(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok,Data} ->
@@ -561,19 +650,18 @@ recv_name(Socket) ->
?shutdown({no_node,Res})
end.
-get_name([$m,VersionA,VersionB,_Ip1,_Ip2,_Ip3,_Ip4|OtherNode]) ->
- {normal, list_to_atom(OtherNode), ?u16(VersionA,VersionB)};
-get_name([$h,VersionA,VersionB,_Ip1,_Ip2,_Ip3,_Ip4|OtherNode]) ->
- {hidden, list_to_atom(OtherNode), ?u16(VersionA,VersionB)};
-get_name([$n,VersionA, VersionB, Flag1, Flag2, Flag3, Flag4 | OtherNode]) ->
- Type = case ?u32(Flag1, Flag2, Flag3, Flag4) band ?DFLAG_PUBLISHED of
- 0 ->
- hidden;
- _ ->
- normal
- end,
- {Type, list_to_atom(OtherNode),
- ?u16(VersionA,VersionB)};
+get_name([$n, V1,V0, F3,F2,F1,F0 | OtherNode]) ->
+ <<Version:16>> = <<V1,V0>>,
+ 5 = Version,
+ <<Flags:32>> = <<F3,F2,F1,F0>>,
+ {list_to_atom(OtherNode), $n, Flags};
+get_name([$N, F7,F6,F5,F4,F3,F2,F1,F0,
+ _C3,_C2,_C1,_C0, NLen1,NLen2 | OtherNode]) ->
+ <<Flags:64>> = <<F7,F6,F5,F4,F3,F2,F1,F0>>,
+ true = (Flags band ?DFLAG_HANDSHAKE_23) =/= 0,
+ <<NameLen:16>> = <<NLen1,NLen2>>,
+ NameLen = length(OtherNode),
+ {list_to_atom(OtherNode), $N, Flags};
get_name(Data) ->
?shutdown(Data).
@@ -620,6 +708,13 @@ wait_for_reg_reply(Socket, SoFar) ->
receive
{tcp, Socket, Data0} ->
case SoFar ++ Data0 of
+ [$v, Result, A, B, C, D] ->
+ case Result of
+ 0 ->
+ {alive, Socket, ?u32(A, B, C, D)};
+ _ ->
+ {error, duplicate_name}
+ end;
[$y, Result, A, B] ->
case Result of
0 ->
@@ -640,7 +735,7 @@ wait_for_reg_reply(Socket, SoFar) ->
end.
-register(NodeName, ListenSocket, VLow, VHigh) ->
+register_node(NodeName, ListenSocket, VLow, VHigh) ->
{ok,{_,TcpPort}} = inet:sockname(ListenSocket),
case do_register_node(NodeName, TcpPort, VLow, VHigh) of
{alive, Socket, _Creation} ->
diff --git a/lib/kernel/test/erpc_SUITE.erl b/lib/kernel/test/erpc_SUITE.erl
new file mode 100644
index 0000000000..1c969ad183
--- /dev/null
+++ b/lib/kernel/test/erpc_SUITE.erl
@@ -0,0 +1,1282 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erpc_SUITE).
+
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2]).
+-export([call/1, call_reqtmo/1, call_against_old_node/1, cast/1,
+ send_request/1, send_request_fun/1,
+ send_request_receive_reqtmo/1,
+ send_request_wait_reqtmo/1,
+ send_request_check_reqtmo/1,
+ send_request_against_old_node/1,
+ multicall/1, multicall_reqtmo/1,
+ multicast/1,
+ timeout_limit/1]).
+-export([init_per_testcase/2, end_per_testcase/2]).
+
+-export([call_func1/1, call_func2/1, call_func4/4]).
+
+-export([f/0, f2/0]).
+
+-include_lib("common_test/include/ct.hrl").
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,2}}].
+
+all() ->
+ [call,
+ call_reqtmo,
+ call_against_old_node,
+ cast,
+ send_request,
+ send_request_fun,
+ send_request_receive_reqtmo,
+ send_request_wait_reqtmo,
+ send_request_check_reqtmo,
+ send_request_against_old_node,
+ multicall,
+ multicall_reqtmo,
+ multicast,
+ timeout_limit].
+
+groups() ->
+ [].
+
+init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
+ [{testcase, Func}|Config].
+
+end_per_testcase(_Func, _Config) ->
+ ok.
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+call(Config) when is_list(Config) ->
+ call_test(Config).
+
+call_test(Config) ->
+ call_test(node(), 10000),
+ call_test(node(), infinity),
+ try
+ erpc:call(node(), timer, sleep, [100], 10),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} ->
+ ok
+ end,
+ try
+ erpc:call(node(), fun () -> timer:sleep(100) end, 10),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} ->
+ ok
+ end,
+ {ok, Node} = start_node(Config),
+ call_test(Node, 10000),
+ call_test(Node, infinity),
+ try
+ erpc:call(Node, timer, sleep, [100], 10),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} ->
+ ok
+ end,
+ try
+ erpc:call(Node, fun () -> timer:sleep(100) end, 10),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, halt, []),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} ->
+ ok
+ end,
+ try
+ erpc:call(Node, fun () -> erlang:node() end),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, node, []),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} ->
+ ok
+ end,
+ try
+ erpc:call(badnodename, erlang, node, []),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} ->
+ ok
+ end,
+
+ receive after 1000 -> ok end,
+ [] = flush([]),
+ ok.
+
+call_test(Node, Timeout) ->
+ io:format("call_test(~p, ~p)~n", [Node, Timeout]),
+ Node = erpc:call(Node, erlang, node, [], Timeout),
+ try
+ erpc:call(Node, erlang, error, [oops|invalid], Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{exception, badarg, [{erlang,apply,[erlang,error,[oops|invalid]],_}]} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, error, [oops], Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{exception, oops, [{erlang,error,[oops],_}]} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, exit, [oops], Timeout),
+ ct:fail(unexpected)
+ catch
+ exit:{exception, oops} ->
+ ok
+ end,
+ try
+ erpc:call(Node, fun () -> erlang:exit(oops) end, Timeout),
+ ct:fail(unexpected)
+ catch
+ exit:{exception, oops} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, throw, [oops], Timeout),
+ ct:fail(unexpected)
+ catch
+ throw:oops ->
+ ok
+ end,
+ try
+ erpc:call(Node, fun () -> erlang:throw(oops) end, Timeout),
+ ct:fail(unexpected)
+ catch
+ throw:oops ->
+ ok
+ end,
+ case {node() == Node, Timeout == infinity} of
+ {true, true} ->
+ %% This would kill the test since local calls
+ %% without timeout is optimized to execute in
+ %% calling process itself...
+ ok;
+ _ ->
+ ExitSignal = fun () ->
+ exit(self(), oops),
+ receive after infinity -> ok end
+ end,
+ try
+ erpc:call(Node, ExitSignal, Timeout),
+ ct:fail(unexpected)
+ catch
+ exit:{signal, oops} ->
+ ok
+ end,
+ try
+ erpc:call(Node, erlang, apply, [ExitSignal, []], Timeout),
+ ct:fail(unexpected)
+ catch
+ exit:{signal, oops} ->
+ ok
+ end
+ end,
+ try
+ erpc:call(Node, ?MODULE, call_func1, [boom], Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{exception,
+ {exception,
+ boom,
+ [{?MODULE, call_func3, A2, _},
+ {?MODULE, call_func2, 1, _}]},
+ [{erpc, call, A1, _},
+ {?MODULE, call_func1, 1, _}]}
+ when ((A1 == 5)
+ orelse (A1 == [Node, ?MODULE, call_func2, [boom]]))
+ andalso ((A2 == 1)
+ orelse (A2 == [boom])) ->
+ ok
+ end,
+ try
+ erpc:call(Node, fun () -> ?MODULE:call_func1(boom) end, Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{exception,
+ {exception,
+ boom,
+ [{?MODULE, call_func3, A4, _},
+ {?MODULE, call_func2, 1, _}]},
+ [{erpc, call, A3, _},
+ {?MODULE, call_func1, 1, _},
+ {erlang, apply, 2, _}]}
+ when ((A3 == 5)
+ orelse (A3 == [Node, ?MODULE, call_func2, [boom]]))
+ andalso ((A4 == 1)
+ orelse (A4 == [boom])) ->
+ ok
+ end,
+ try
+ call_func4(Node, node(), 10, Timeout),
+ ct:fail(unexpected)
+ catch
+ error:Error1 ->
+%%% io:format("Error1=~p~n", [Error1]),
+ check_call_func4_error(Error1, 10)
+ end,
+ try
+ call_func4(node(), Node, 5, Timeout),
+ ct:fail(unexpected)
+ catch
+ error:Error2 ->
+%%% io:format("Error2=~p~n", [Error2]),
+ check_call_func4_error(Error2, 5)
+ end,
+ ok.
+
+check_call_func4_error({exception,
+ badarg,
+ [{?MODULE, call_func5, _, _},
+ {?MODULE, call_func4, _, _}]},
+ 0) ->
+ ok;
+check_call_func4_error({exception,
+ Exception,
+ [{erpc, call, _, _},
+ {?MODULE, call_func5, _, _},
+ {?MODULE, call_func4, _, _}]},
+ N) ->
+ check_call_func4_error(Exception, N-1).
+
+call_func1(X) ->
+ erpc:call(node(), ?MODULE, call_func2, [X]),
+ ok.
+
+call_func2(X) ->
+ call_func3(X),
+ ok.
+
+call_func3(X) ->
+ erlang:error(X, [X]).
+
+call_func4(A, B, N, T) ->
+ call_func5(A, B, N, T),
+ ok.
+
+call_func5(A, B, N, T) when N >= 0 ->
+ erpc:call(A, ?MODULE, call_func4, [B, A, N-1, T], T),
+ ok;
+call_func5(_A, _B, _N, _T) ->
+ erlang:error(badarg).
+
+call_against_old_node(Config) ->
+ case start_22_node(Config) of
+ {ok, Node22} ->
+ try
+ erpc:call(Node22, erlang, node, []),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, notsup} ->
+ ok
+ end,
+ stop_node(Node22),
+ ok;
+ _ ->
+ {skipped, "No OTP 22 available"}
+ end.
+
+call_reqtmo(Config) when is_list(Config) ->
+ Fun = fun (Node, SendMe, Timeout) ->
+ try
+ erpc:call(Node, erlang, send,
+ [self(), SendMe], Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} -> ok
+ end
+ end,
+ reqtmo_test(Config, Fun).
+
+reqtmo_test(Config, Test) ->
+ %% Tests that we time out in time also when the request itself
+ %% does not get through. A typical issue we have had
+ %% in the past, is that the timeout has not triggered until
+ %% the request has gotten through...
+
+ Timeout = 500,
+ WaitBlock = 100,
+ BlockTime = 1000,
+
+ {ok, Node} = start_node(Config),
+
+ erpc:call(Node, erts_debug, set_internal_state, [available_internal_state,
+ true]),
+
+ SendMe = make_ref(),
+
+ erpc:cast(Node, erts_debug, set_internal_state, [block, BlockTime]),
+ receive after WaitBlock -> ok end,
+
+ Start = erlang:monotonic_time(),
+
+ Test(Node, SendMe, Timeout),
+
+ Stop = erlang:monotonic_time(),
+ Time = erlang:convert_time_unit(Stop-Start, native, millisecond),
+ io:format("Actual time: ~p ms~n", [Time]),
+ true = Time >= Timeout,
+ true = Time =< Timeout + 200,
+
+ receive SendMe -> ok end,
+
+ receive UnexpectedMsg -> ct:fail({unexpected_message, UnexpectedMsg})
+ after 0 -> ok
+ end,
+
+ stop_node(Node),
+
+ {comment,
+ "Timeout = " ++ integer_to_list(Timeout)
+ ++ " Actual = " ++ integer_to_list(Time)}.
+
+cast(Config) when is_list(Config) ->
+ %% silently fail
+ ok = erpc:cast(badnodename, erlang, send, [hej]),
+
+ try
+ erpc:cast(<<"node">>, erlang, send, [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:cast(node(), erlang, send, hej),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:cast(node(), "erlang", send, [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:cast(node(), erlang, make_ref(), [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+
+ erpc:cast(node(), erlang, send, [self()|blupp]), %% silent remote error...
+
+ Me = self(),
+ Ok1 = make_ref(),
+ ok = erpc:cast(node(), erlang, send, [Me, {mfa, Ok1}]),
+ receive
+ {mfa, Ok1} -> ok
+ end,
+ ok = erpc:cast(node(), fun () -> Me ! {a_fun, Ok1} end),
+ receive
+ {a_fun, Ok1} -> ok
+ end,
+ {ok, Node} = start_node(Config),
+ Ok2 = make_ref(),
+ ok = erpc:cast(Node, erlang, send, [Me, {mfa, Ok2}]),
+ receive
+ {mfa, Ok2} -> ok
+ end,
+ ok = erpc:cast(Node, fun () -> Me ! {a_fun, Ok2} end),
+ receive
+ {a_fun, Ok2} -> ok
+ end,
+
+ ok = erpc:cast(Node, erlang, halt, []),
+
+ monitor_node(Node, true),
+ receive {nodedown, Node} -> ok end,
+
+ ok = erpc:cast(Node, erlang, send, [Me, wont_reach_me]),
+
+ receive after 1000 -> ok end,
+ [] = flush([]),
+
+ case start_22_node(Config) of
+ {ok, Node22} ->
+ ok = erpc:cast(Node, erlang, send, [Me, wont_reach_me]),
+ ok = erpc:cast(Node, fun () -> Me ! wont_reach_me end),
+ receive
+ Msg -> ct:fail({unexpected_message, Msg})
+ after
+ 2000 -> ok
+ end,
+ stop_node(Node22),
+ {comment, "Tested against OTP 22 as well"};
+ _ ->
+ {comment, "No tested against OTP 22"}
+ end.
+
+send_request(Config) when is_list(Config) ->
+ %% Note: First part of nodename sets response delay in seconds.
+ PA = filename:dirname(code:which(?MODULE)),
+ NodeArgs = [{args,"-pa "++ PA}],
+ {ok,Node1} = test_server:start_node('1_erpc_SUITE_call', slave, NodeArgs),
+ {ok,Node2} = test_server:start_node('10_erpc_SUITE_call', slave, NodeArgs),
+ {ok,Node3} = test_server:start_node('20_erpc_SUITE_call', slave, NodeArgs),
+ ReqId1 = erpc:send_request(Node1, ?MODULE, f, []),
+ ReqId2 = erpc:send_request(Node2, ?MODULE, f, []),
+ ReqId3 = erpc:send_request(Node3, ?MODULE, f, []),
+ ReqId4 = erpc:send_request(Node1, erlang, error, [bang]),
+ ReqId5 = erpc:send_request(Node1, ?MODULE, f, []),
+
+ try
+ erpc:receive_response(ReqId4, 1000)
+ catch
+ error:{exception, bang, [{erlang,error,[bang],_}]} ->
+ ok
+ end,
+ try
+ erpc:receive_response(ReqId5, 10)
+ catch
+ error:{erpc, timeout} ->
+ ok
+ end,
+
+ %% Test fast timeouts.
+ no_response = erpc:wait_response(ReqId2),
+ no_response = erpc:wait_response(ReqId2, 10),
+
+ %% Let Node1 finish its work before yielding.
+ ct:sleep({seconds,2}),
+ {hej,_,Node1} = erpc:receive_response(ReqId1),
+
+ %% Wait for the Node2 and Node3.
+ {response,{hej,_,Node2}} = erpc:wait_response(ReqId2, infinity),
+ {hej,_,Node3} = erpc:receive_response(ReqId3),
+
+ try
+ erpc:receive_response(ReqId5, 10),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+
+ receive
+ Msg0 -> ct:fail(Msg0)
+ after 0 -> ok
+ end,
+
+ stop_node(Node1),
+ stop_node(Node2),
+ stop_node(Node3),
+
+ [] = flush([]),
+
+ {ok, Node4} = start_node(Config),
+
+ ReqId6 = erpc:send_request(Node4, erlang, node, []),
+
+ no_response = erpc:check_response({response, Node1}, ReqId6),
+ no_response = erpc:check_response(ReqId6, ReqId6),
+ receive
+ Msg1 ->
+ {response, Node4} = erpc:check_response(Msg1, ReqId6)
+ end,
+
+ ReqId7 = erpc:send_request(Node4, erlang, halt, []),
+ try
+ erpc:receive_response(ReqId7),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} -> ok
+ end,
+
+ ReqId8 = erpc:send_request(Node4, erlang, node, []),
+ receive
+ Msg2 ->
+ try
+ erpc:check_response(Msg2, ReqId8),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} ->
+ ok
+ end
+ end,
+
+ [] = flush([]),
+
+ case start_22_node(Config) of
+ {ok, Node5} ->
+ ReqId9 = erpc:send_request(Node5, erlang, node, []),
+ try
+ erpc:receive_response(ReqId9),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, notsup} -> ok
+ end,
+
+ stop_node(Node5),
+
+ [] = flush([]),
+
+ ok;
+ _ ->
+ {comment, "No test against OTP 22 node performed"}
+ end.
+
+send_request_fun(Config) when is_list(Config) ->
+ %% Note: First part of nodename sets response delay in seconds.
+ PA = filename:dirname(code:which(?MODULE)),
+ NodeArgs = [{args,"-pa "++ PA}],
+ {ok,Node1} = test_server:start_node('1_erpc_SUITE_call', slave, NodeArgs),
+ {ok,Node2} = test_server:start_node('10_erpc_SUITE_call', slave, NodeArgs),
+ {ok,Node3} = test_server:start_node('20_erpc_SUITE_call', slave, NodeArgs),
+ ReqId1 = erpc:send_request(Node1, fun () -> ?MODULE:f() end),
+ ReqId2 = erpc:send_request(Node2, fun () -> ?MODULE:f() end),
+ ReqId3 = erpc:send_request(Node3, fun () -> ?MODULE:f() end),
+ ReqId4 = erpc:send_request(Node1, fun () -> erlang:error(bang) end),
+ ReqId5 = erpc:send_request(Node1, fun () -> ?MODULE:f() end),
+
+ try
+ erpc:receive_response(ReqId4, 1000)
+ catch
+ error:{exception, bang, [{?MODULE, _, _, _},
+ {erlang,apply,2,_}]} ->
+ ok
+ end,
+ try
+ erpc:receive_response(ReqId5, 10)
+ catch
+ error:{erpc, timeout} ->
+ ok
+ end,
+
+ %% Test fast timeouts.
+ no_response = erpc:wait_response(ReqId2),
+ no_response = erpc:wait_response(ReqId2, 10),
+
+ %% Let Node1 finish its work before yielding.
+ ct:sleep({seconds,2}),
+ {hej,_,Node1} = erpc:receive_response(ReqId1),
+
+ %% Wait for the Node2 and Node3.
+ {response,{hej,_,Node2}} = erpc:wait_response(ReqId2, infinity),
+ {hej,_,Node3} = erpc:receive_response(ReqId3),
+
+ try
+ erpc:receive_response(ReqId5, 10),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+
+ receive
+ Msg0 -> ct:fail(Msg0)
+ after 0 -> ok
+ end,
+
+ stop_node(Node1),
+ stop_node(Node2),
+ stop_node(Node3),
+
+ {ok, Node4} = start_node(Config),
+
+ ReqId6 = erpc:send_request(Node4, fun () -> erlang:node() end),
+
+ no_response = erpc:check_response({response, Node1}, ReqId6),
+ no_response = erpc:check_response(ReqId6, ReqId6),
+ receive
+ Msg1 ->
+ {response, Node4} = erpc:check_response(Msg1, ReqId6)
+ end,
+
+ ReqId7 = erpc:send_request(Node4, erlang, halt, []),
+ try
+ erpc:receive_response(ReqId7),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} -> ok
+ end,
+
+ ReqId8 = erpc:send_request(Node4, erlang, node, []),
+ receive
+ Msg2 ->
+ try
+ erpc:check_response(Msg2, ReqId8),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, noconnection} ->
+ ok
+ end
+ end,
+
+ [] = flush([]),
+
+ case start_22_node(Config) of
+ {ok, Node5} ->
+ ReqId9 = erpc:send_request(Node5, fun () -> erlang:node() end),
+ try
+ erpc:receive_response(ReqId9),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, notsup} -> ok
+ end,
+
+ stop_node(Node5),
+
+ [] = flush([]),
+
+ ok;
+ _ ->
+ {comment, "No test against OTP 22 node performed"}
+ end.
+
+
+send_request_receive_reqtmo(Config) when is_list(Config) ->
+ Fun = fun (Node, SendMe, Timeout) ->
+ RID = erpc:send_request(Node, erlang, send,
+ [self(), SendMe]),
+ try
+ erpc:receive_response(RID, Timeout),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} -> ok
+ end
+ end,
+ reqtmo_test(Config, Fun).
+
+send_request_wait_reqtmo(Config) when is_list(Config) ->
+ Fun = fun (Node, SendMe, Timeout) ->
+ RID = erpc:send_request(Node, erlang, send,
+ [self(), SendMe]),
+ no_response = erpc:wait_response(RID, 0),
+ no_response = erpc:wait_response(RID, Timeout),
+ %% Cleanup...
+ try
+ erpc:receive_response(RID, 0),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} -> ok
+ end
+ end,
+ reqtmo_test(Config, Fun).
+
+send_request_check_reqtmo(Config) when is_list(Config) ->
+ Fun = fun (Node, SendMe, Timeout) ->
+ RID = erpc:send_request(Node, erlang, send,
+ [self(), SendMe]),
+ receive Msg -> erpc:check_response(Msg, RID)
+ after Timeout -> ok
+ end,
+ %% Cleanup...
+ try
+ erpc:receive_response(RID, 0),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, timeout} -> ok
+ end
+ end,
+ reqtmo_test(Config, Fun).
+
+send_request_against_old_node(Config) when is_list(Config) ->
+ case start_22_node(Config) of
+ {ok, Node22} ->
+ RID1 = erpc:send_request(Node22, erlang, node, []),
+ RID2 = erpc:send_request(Node22, erlang, node, []),
+ RID3 = erpc:send_request(Node22, erlang, node, []),
+ try
+ erpc:receive_response(RID1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, notsup} ->
+ ok
+ end,
+ try
+ erpc:wait_response(RID2, infinity),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, notsup} ->
+ ok
+ end,
+ try
+ receive
+ Msg ->
+ erpc:check_response(Msg, RID3),
+ ct:fail(unexpected)
+ end
+ catch
+ error:{erpc, notsup} ->
+ ok
+ end,
+ stop_node(Node22),
+ ok;
+ _ ->
+ {skipped, "No OTP 22 available"}
+ end.
+
+multicall(Config) ->
+ {ok, Node1} = start_node(Config),
+ {ok, Node2} = start_node(Config),
+ {Node3, Node3Res} = case start_22_node(Config) of
+ {ok, N3} ->
+ {N3, {error, {erpc, notsup}}};
+ _ ->
+ {ok, N3} = start_node(Config),
+ stop_node(N3),
+ {N3, {error, {erpc, noconnection}}}
+ end,
+ {ok, Node4} = start_node(Config),
+ {ok, Node5} = start_node(Config),
+ stop_node(Node2),
+
+ ThisNode = node(),
+ Nodes = [ThisNode, Node1, Node2, Node3, Node4, Node5],
+
+ [{ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5},
+ {error, {erpc, noconnection}}]
+ = erpc:multicall(Nodes ++ [badnodename], erlang, node, []),
+
+ [{ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5},
+ {error, {erpc, noconnection}}]
+ = erpc:multicall(Nodes ++ [badnodename], fun () -> erlang:node() end),
+
+ try
+ erpc:multicall(Nodes ++ [<<"badnodename">>], erlang, node, []),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+
+ try
+ erpc:multicall([Node1, Node2, Node3, Node4, Node5 | node()], erlang, node, []),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+
+
+ try
+ erpc:multicall(Nodes ++ [<<"badnodename">>], fun () -> erlang:node() end),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+
+ try
+ erpc:multicall(Nodes, fun (X) -> X end),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+
+ [{ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5}]
+ = erpc:multicall(Nodes, erlang, node, []),
+
+ [{ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5}]
+ = erpc:multicall(Nodes, erlang, node, [], 60000),
+
+ [{throw, ThisNode},
+ {throw, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {throw, Node4},
+ {throw, Node5}]
+ = erpc:multicall(Nodes, fun () -> throw(erlang:node()) end),
+
+ [{throw, ThisNode},
+ {throw, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {throw, Node4},
+ {throw, Node5}]
+ = erpc:multicall(Nodes, fun () -> throw(erlang:node()) end, 60000),
+
+ S0 = erlang:monotonic_time(millisecond),
+ [{error, {erpc, timeout}},
+ {error, {erpc, timeout}},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {error, {erpc, timeout}},
+ {error, {erpc, timeout}},
+ {error, {erpc, timeout}},
+ {error, {erpc, timeout}},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {error, {erpc, timeout}},
+ {error, {erpc, timeout}}]
+ = erpc:multicall(Nodes++Nodes, timer, sleep, [2000], 500),
+ E0 = erlang:monotonic_time(millisecond),
+ T0 = E0 - S0,
+ io:format("T0=~p~n", [T0]),
+ true = T0 < 1000,
+
+ S1 = erlang:monotonic_time(millisecond),
+ [{ok, ok},
+ {ok, ok},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, ok},
+ {ok, ok},
+ {ok, ok},
+ {ok, ok},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, ok},
+ {ok, ok}]
+ = erpc:multicall(Nodes++Nodes, timer, sleep, [2000]),
+ E1 = erlang:monotonic_time(millisecond),
+ T1 = E1 - S1,
+ io:format("T1=~p~n", [T1]),
+ true = T1 < 3000,
+
+ S2 = erlang:monotonic_time(millisecond),
+ [{ok, ok},
+ {ok, ok},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, ok},
+ {ok, ok},
+ {ok, ok},
+ {ok, ok},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, ok},
+ {ok, ok}]
+ = erpc:multicall(Nodes++Nodes, timer, sleep, [2000], 3000),
+ E2 = erlang:monotonic_time(millisecond),
+ T2 = E2 - S2,
+ io:format("T2=~p~n", [T2]),
+ true = T2 < 3000,
+
+ [{ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5},
+ {ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5}]
+ = erpc:multicall(Nodes++Nodes, erlang, node, []),
+
+ [{ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5},
+ {ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5}]
+ = erpc:multicall(Nodes++Nodes, erlang, node, [], 60000),
+
+ [{ok, ThisNode},
+ {ok, Node1},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {ok, Node4},
+ {ok, Node5}]
+ = erpc:multicall(Nodes, fun () -> erlang:node() end),
+
+ [BlingError,
+ BlingError,
+ {error, {erpc, noconnection}},
+ Node3Res,
+ BlingError,
+ BlingError]
+ = erpc:multicall(Nodes, ?MODULE, call_func2, [bling]),
+
+ [BlingError,
+ BlingError,
+ {error, {erpc, noconnection}},
+ Node3Res,
+ BlingError,
+ BlingError]
+ = erpc:multicall(Nodes, ?MODULE, call_func2, [bling], 60000),
+
+ {error, {exception,
+ bling,
+ [{?MODULE, call_func3, A, _},
+ {?MODULE, call_func2, 1, _}]}} = BlingError,
+ true = (A == 1) orelse (A == [bling]),
+
+ [{error, {exception, blong, [{erlang, error, [blong], _}]}},
+ {error, {exception, blong, [{erlang, error, [blong], _}]}},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {error, {exception, blong, [{erlang, error, [blong], _}]}},
+ {error, {exception, blong, [{erlang, error, [blong], _}]}}]
+ = erpc:multicall(Nodes, erlang, error, [blong]),
+
+ [{error, {exception, blong, [{erlang, error, [blong], _}]}},
+ {error, {exception, blong, [{erlang, error, [blong], _}]}},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {error, {exception, blong, [{erlang, error, [blong], _}]}},
+ {error, {exception, blong, [{erlang, error, [blong], _}]}}]
+ = erpc:multicall(Nodes, erlang, error, [blong], 60000),
+
+ SlowNode4 = fun () ->
+ case node() of
+ Node4 ->
+ receive after 1000 -> ok end,
+ slow;
+ ThisNode ->
+ throw(fast);
+ _ ->
+ fast
+ end
+ end,
+
+ Start1 = erlang:monotonic_time(),
+ [{throw, fast},
+ {ok, fast},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {error, {erpc, timeout}},
+ {ok, fast}]
+ = erpc:multicall(Nodes, erlang, apply, [SlowNode4, []], 500),
+
+ End1 = erlang:monotonic_time(),
+
+ Time1 = erlang:convert_time_unit(End1-Start1, native, millisecond),
+ io:format("Time1 = ~p~n",[Time1]),
+ true = Time1 >= 500,
+ true = Time1 =< 1000,
+
+ SlowThisNode = fun () ->
+ case node() of
+ ThisNode ->
+ receive after 1000 -> ok end,
+ slow;
+ Node4 ->
+ throw(fast);
+ Node5 ->
+ exit(fast);
+ _ ->
+ fast
+ end
+ end,
+
+ Start2 = erlang:monotonic_time(),
+ [{error, {erpc, timeout}},
+ {ok, fast},
+ {error, {erpc, noconnection}},
+ Node3Res,
+ {throw, fast},
+ {exit, {exception, fast}}] = erpc:multicall(Nodes, SlowThisNode, 500),
+
+ End2 = erlang:monotonic_time(),
+
+ Time2 = erlang:convert_time_unit(End2-Start2, native, millisecond),
+ io:format("Time2 = ~p~n",[Time2]),
+ true = Time2 >= 500,
+ true = Time2 =< 1000,
+
+ %% We should not get any stray messages due to timed out operations...
+ receive Msg -> ct:fail({unexpected, Msg})
+ after 1000 -> ok
+ end,
+
+ [{error, {erpc, noconnection}},
+ Node3Res,
+ {error, {erpc, noconnection}},
+ {error, {erpc, noconnection}}]
+ = erpc:multicall([Node2, Node3, Node4, Node5], erlang, halt, []),
+
+ [] = flush([]),
+
+ stop_node(Node3),
+ case Node3Res of
+ {error, {erpc, notsup}} ->
+ {comment, "Tested against an OTP 22 node as well"};
+ _ ->
+ {comment, "No OTP 22 node available; i.e., only testing against current release"}
+ end.
+
+multicall_reqtmo(Config) when is_list(Config) ->
+ {ok, QuickNode1} = start_node(Config),
+ {ok, QuickNode2} = start_node(Config),
+ Fun = fun (Node, SendMe, Timeout) ->
+ Me = self(),
+ SlowSend = fun () ->
+ if node() == Node ->
+ Me ! SendMe,
+ done;
+ true ->
+ done
+ end
+ end,
+ [{ok, done},{error,{erpc,timeout}},{ok, done}]
+ = erpc:multicall([QuickNode1, Node, QuickNode2],
+ erlang, apply, [SlowSend, []],
+ Timeout)
+ end,
+ Res = reqtmo_test(Config, Fun),
+ stop_node(QuickNode1),
+ stop_node(QuickNode2),
+ Res.
+
+
+multicast(Config) when is_list(Config) ->
+ {ok, Node} = start_node(Config),
+ Nodes = [node(), Node],
+ try
+ erpc:multicast(Nodes, erlang, send, hej),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:multicast(node(), erlang, send, [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:multicast([<<"node">>, Node], erlang, send, [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:multicast(Nodes, "erlang", send, [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+ try
+ erpc:multicast(Nodes, erlang, make_ref(), [hej]),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} -> ok
+ end,
+
+ %% Silently fail...
+ erpc:multicast([badnodename], erlang, send, [self(), blupp]),
+ %% silent remote error...
+ erpc:multicast(Nodes, erlang, send, [self()|blupp]),
+
+ Me = self(),
+ Ok1 = make_ref(),
+ ok = erpc:multicast(Nodes, erlang, send, [Me, {mfa, Ok1}]),
+ receive
+ {mfa, Ok1} -> ok
+ end,
+ receive
+ {mfa, Ok1} -> ok
+ end,
+ ok = erpc:multicast(Nodes, fun () -> Me ! {a_fun, Ok1} end),
+ receive
+ {a_fun, Ok1} -> ok
+ end,
+ receive
+ {a_fun, Ok1} -> ok
+ end,
+
+ ok = erpc:multicast([Node], erlang, halt, []),
+
+ monitor_node(Node, true),
+ receive {nodedown, Node} -> ok end,
+
+ ok = erpc:multicast([Node], erlang, send, [Me, wont_reach_me]),
+
+ receive after 1000 -> ok end,
+
+ [] = flush([]),
+ case start_22_node(Config) of
+ {ok, Node22} ->
+ ok = erpc:multicast([Node], erlang, send, [Me, wont_reach_me]),
+ ok = erpc:multicast([Node], fun () -> Me ! wont_reach_me end),
+ receive
+ Msg -> ct:fail({unexpected_message, Msg})
+ after
+ 2000 -> ok
+ end,
+ stop_node(Node22),
+ {comment, "Tested against OTP 22 as well"};
+ _ ->
+ {comment, "No tested against OTP 22"}
+ end.
+
+timeout_limit(Config) when is_list(Config) ->
+ Node = node(),
+ MaxTmo = (1 bsl 32) - 1,
+ erlang:send_after(100, self(), dummy_message),
+ try
+ receive
+ M ->
+ M
+ after MaxTmo + 1 ->
+ ok
+ end,
+ ct:fail("The ?MAX_INT_TIMEOUT define in erpc.erl needs "
+ "to be updated to reflect max timeout value "
+ "in a receive/after...")
+ catch
+ error:timeout_value ->
+ ok
+ end,
+ Node = erpc:call(Node, erlang, node, [], MaxTmo),
+ try
+ erpc:call(node(), erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ [{ok,Node}] = erpc:multicall([Node], erlang, node, [], MaxTmo),
+ try
+ erpc:multicall([Node], erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ ReqId1 = erpc:send_request(Node, erlang, node, []),
+ Node = erpc:receive_response(ReqId1, MaxTmo),
+ ReqId2 = erpc:send_request(Node, erlang, node, []),
+ try
+ erpc:receive_response(ReqId2, MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ ReqId3 = erpc:send_request(Node, erlang, node, []),
+ Node = erpc:receive_response(ReqId3, MaxTmo),
+ ReqId4 = erpc:send_request(Node, erlang, node, []),
+ try
+ erpc:receive_response(ReqId4, MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:{erpc, badarg} ->
+ ok
+ end,
+ ok.
+
+
+%%%
+%%% Utility functions.
+%%%
+
+start_node(Config) ->
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-" ++ integer_to_list(erlang:system_time(second))
+ ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))),
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{args, "-pa " ++ Pa}]).
+
+start_22_node(Config) ->
+ Rel = "22_latest",
+ case test_server:is_release_available(Rel) of
+ false ->
+ notsup;
+ true ->
+ Cookie = atom_to_list(erlang:get_cookie()),
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-" ++ integer_to_list(erlang:system_time(second))
+ ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))),
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name,
+ peer,
+ [{args, "-pa " ++ Pa ++ " -setcookie "++Cookie},
+ {erl, [{release, Rel}]}])
+ end.
+
+stop_node(Node) ->
+ test_server:stop_node(Node).
+
+flush(L) ->
+ receive
+ M ->
+ flush([M|L])
+ after 0 ->
+ L
+ end.
+
+t() ->
+ [N | _] = string:tokens(atom_to_list(node()), "_"),
+ 1000*list_to_integer(N).
+
+f() ->
+ timer:sleep(T=t()),
+ spawn(?MODULE, f2, []),
+ {hej,T,node()}.
+
+f2() ->
+ timer:sleep(500),
+ halt().
diff --git a/erts/emulator/test/esock_misc/esock_iow_client.erl b/lib/kernel/test/esock_misc/esock_iow_client.erl
index 3e48a8f300..3e48a8f300 100644
--- a/erts/emulator/test/esock_misc/esock_iow_client.erl
+++ b/lib/kernel/test/esock_misc/esock_iow_client.erl
diff --git a/erts/emulator/test/esock_misc/esock_iow_lib.erl b/lib/kernel/test/esock_misc/esock_iow_lib.erl
index 6fc1365dca..6fc1365dca 100644
--- a/erts/emulator/test/esock_misc/esock_iow_lib.erl
+++ b/lib/kernel/test/esock_misc/esock_iow_lib.erl
diff --git a/erts/emulator/test/esock_misc/esock_iow_server.erl b/lib/kernel/test/esock_misc/esock_iow_server.erl
index 4b364a6ca6..4b364a6ca6 100644
--- a/erts/emulator/test/esock_misc/esock_iow_server.erl
+++ b/lib/kernel/test/esock_misc/esock_iow_server.erl
diff --git a/erts/emulator/test/esock_misc/socket_client.erl b/lib/kernel/test/esock_misc/socket_client.erl
index 891f7e67ab..891f7e67ab 100644
--- a/erts/emulator/test/esock_misc/socket_client.erl
+++ b/lib/kernel/test/esock_misc/socket_client.erl
diff --git a/erts/emulator/test/esock_misc/socket_lib.erl b/lib/kernel/test/esock_misc/socket_lib.erl
index e401a3195c..e401a3195c 100644
--- a/erts/emulator/test/esock_misc/socket_lib.erl
+++ b/lib/kernel/test/esock_misc/socket_lib.erl
diff --git a/erts/emulator/test/esock_misc/socket_server.erl b/lib/kernel/test/esock_misc/socket_server.erl
index 161ea17027..161ea17027 100644
--- a/erts/emulator/test/esock_misc/socket_server.erl
+++ b/lib/kernel/test/esock_misc/socket_server.erl
diff --git a/erts/emulator/test/esock_ttest/.gitignore b/lib/kernel/test/esock_ttest/.gitignore
index e69de29bb2..e69de29bb2 100644
--- a/erts/emulator/test/esock_ttest/.gitignore
+++ b/lib/kernel/test/esock_ttest/.gitignore
diff --git a/erts/emulator/test/esock_ttest/esock-ttest b/lib/kernel/test/esock_ttest/esock-ttest
index 2ded557484..2ded557484 100755
--- a/erts/emulator/test/esock_ttest/esock-ttest
+++ b/lib/kernel/test/esock_ttest/esock-ttest
diff --git a/erts/emulator/test/esock_ttest/esock-ttest-client b/lib/kernel/test/esock_ttest/esock-ttest-client
index 5ae05d03b8..5ae05d03b8 100755
--- a/erts/emulator/test/esock_ttest/esock-ttest-client
+++ b/lib/kernel/test/esock_ttest/esock-ttest-client
diff --git a/erts/emulator/test/esock_ttest/esock-ttest-server-gen b/lib/kernel/test/esock_ttest/esock-ttest-server-gen
index c29184772e..c29184772e 100755
--- a/erts/emulator/test/esock_ttest/esock-ttest-server-gen
+++ b/lib/kernel/test/esock_ttest/esock-ttest-server-gen
diff --git a/erts/emulator/test/esock_ttest/esock-ttest-server-sock b/lib/kernel/test/esock_ttest/esock-ttest-server-sock
index c443d42e64..c443d42e64 100755
--- a/erts/emulator/test/esock_ttest/esock-ttest-server-sock
+++ b/lib/kernel/test/esock_ttest/esock-ttest-server-sock
diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl
index a88170f638..52ef711008 100644
--- a/lib/kernel/test/file_SUITE.erl
+++ b/lib/kernel/test/file_SUITE.erl
@@ -47,7 +47,7 @@
init_per_group/2,end_per_group/2,
init_per_testcase/2, end_per_testcase/2,
read_write_file/1, names/1]).
--export([cur_dir_0/1, cur_dir_1/1, make_del_dir/1,
+-export([cur_dir_0/1, cur_dir_1/1, make_del_dir/1, make_del_dir_r/1,
list_dir/1,list_dir_error/1,
untranslatable_names/1, untranslatable_names_error/1,
pos1/1, pos2/1, pos3/1]).
@@ -58,6 +58,8 @@
-export([ file_info_basic_file/1, file_info_basic_directory/1,
file_info_bad/1, file_info_times/1, file_write_file_info/1,
file_wfi_helpers/1]).
+-export([ file_handle_info_basic_file/1, file_handle_info_basic_directory/1,
+ file_handle_info_times/1]).
-export([rename/1, access/1, truncate/1, datasync/1, sync/1,
read_write/1, pread_write/1, append/1, exclusive/1]).
-export([ e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
@@ -141,7 +143,7 @@ all() ->
].
groups() ->
- [{dirs, [], [make_del_dir, cur_dir_0, cur_dir_1,
+ [{dirs, [], [make_del_dir, make_del_dir_r, cur_dir_0, cur_dir_1,
list_dir, list_dir_error, untranslatable_names,
untranslatable_names_error]},
{files, [],
@@ -155,7 +157,10 @@ groups() ->
{pos, [], [pos1, pos2, pos3]},
{file_info, [],
[file_info_basic_file, file_info_basic_directory,
- file_info_bad, file_info_times, file_write_file_info,
+ file_info_bad, file_info_times,
+ file_handle_info_basic_file, file_handle_info_basic_directory,
+ file_handle_info_times,
+ file_write_file_info,
file_wfi_helpers]},
{consult, [], [consult1, path_consult]},
{eval, [], [eval1, path_eval]},
@@ -275,11 +280,11 @@ mini_server(Parent) ->
Parent ! {io_request,From,To,{put_chars,Data}},
From ! {io_reply, To, ok},
mini_server(Parent);
- {io_request,From,To,{get_chars,'',N}} ->
+ {io_request,From,To,{get_chars,_Encoding,'',N}} ->
Parent ! {io_request,From,To,{get_chars,'',N}},
From ! {io_reply, To, {ok, lists:duplicate(N,$a)}},
mini_server(Parent);
- {io_request,From,To,{get_line,''}} ->
+ {io_request,From,To,{get_line,_Encoding,''}} ->
Parent ! {io_request,From,To,{get_line,''}},
From ! {io_reply, To, {ok, "hej\n"}},
mini_server(Parent)
@@ -632,6 +637,41 @@ make_del_dir(Config) when is_list(Config) ->
end,
ok.
+make_del_dir_r(Config) when is_list(Config) ->
+ RootDir = proplists:get_value(priv_dir, Config),
+ NewDir = filename:join(RootDir, ?MODULE_STRING ++ "_make_del_dir_r"),
+ ok = ?FILE_MODULE:make_dir(NewDir),
+
+ %% Create:
+ %% newdir/
+ %% file1
+ %% dir/
+ %% file2
+ %% link -> /newdir/file1
+ File1 = test_server:temp_name(filename:join(NewDir, "file1")),
+ ok = ?FILE_MODULE:write_file(File1, <<"file1">>),
+ Dir = test_server:temp_name(filename:join(NewDir, "dir")),
+ ok = ?FILE_MODULE:make_dir(Dir),
+ File2 = test_server:temp_name(filename:join(Dir, "file2")),
+ ok = ?FILE_MODULE:write_file(File2, <<"file2">>),
+ Link = test_server:temp_name(filename:join(Dir, "link")),
+ case ?FILE_MODULE:make_symlink(File1, Link) of
+ {error, enotsup} -> ok; % symlinks not supported
+ {error, eperm} -> {win32, _} = os:type(), ok; % not allowed to make symlinks
+ ok -> ok
+ end,
+
+ %% check that del_dir_r/1 succeeds on non-empty dir
+ ok = ?FILE_MODULE:del_dir_r(Dir),
+ {error, enoent} = ?FILE_MODULE:read_file_info(Dir),
+ %% check that the symlink wasn't followed
+ {ok, _} = ?FILE_MODULE:read_file_info(File1),
+
+ %% clean up
+ ?FILE_MODULE:del_dir_r(NewDir),
+ [] = flush(),
+ ok.
+
cur_dir_0(Config) when is_list(Config) ->
%% Find out the current dir, and cd to it ;-)
{ok,BaseDir} = ?FILE_MODULE:get_cwd(),
@@ -989,6 +1029,14 @@ new_modes(Config) when is_list(Config) ->
ok
end,
+ % open directory
+ {ok, Fd9} = ?FILE_MODULE:open(NewDir, [directory]),
+ ok = ?FILE_MODULE:close(Fd9),
+
+ % open raw directory
+ {ok, Fd10} = ?FILE_MODULE:open(NewDir, [raw, directory]),
+ ok = ?FILE_MODULE:close(Fd10),
+
[] = flush(),
ok.
@@ -1238,6 +1286,9 @@ open_errors(Config) when is_list(Config) ->
{error, E4} = ?FILE_MODULE:open(DataDirSlash, [write]),
{eisdir,eisdir,eisdir,eisdir} = {E1,E2,E3,E4},
+ Real = filename:join(DataDir, "realmen.html"),
+ {error, enotdir} = ?FILE_MODULE:open(Real, [directory]),
+
[] = flush(),
ok.
@@ -1408,7 +1459,8 @@ file_info_basic_directory(Config) when is_list(Config) ->
{win32, _} ->
test_directory("/", read_write),
test_directory("c:/", read_write),
- test_directory("c:\\", read_write);
+ test_directory("c:\\", read_write),
+ test_directory("\\\\localhost\\c$", read_write);
_ ->
test_directory("/", read)
end,
@@ -1540,6 +1592,180 @@ filter_atime(Atime, Config) ->
Atime
end.
+%% Test read_file_info on I/O devices.
+
+file_handle_info_basic_file(Config) when is_list(Config) ->
+ RootDir = proplists:get_value(priv_dir, Config),
+
+ %% Create a short file.
+ Name = filename:join(RootDir,
+ atom_to_list(?MODULE)
+ ++"_basic_test.fil"),
+ {ok,Fd1} = ?FILE_MODULE:open(Name, write),
+ io:put_chars(Fd1, "foo bar"),
+ ok = ?FILE_MODULE:close(Fd1),
+
+ %% Test that the file has the expected attributes.
+ %% The times are tricky, so we will save them to a separate test case.
+
+ {ok, Fd} = ?FILE_MODULE:open(Name, read),
+ {ok,FileInfo} = ?FILE_MODULE:read_file_info(Fd),
+ ok = ?FILE_MODULE:close(Fd),
+
+ {ok, FdRaw} = ?FILE_MODULE:open(Name, [read, raw]),
+ {ok,FileInfoRaw} = ?FILE_MODULE:read_file_info(FdRaw),
+ ok = ?FILE_MODULE:close(FdRaw),
+
+ #file_info{size=Size,type=Type,access=Access,
+ atime=AccessTime,mtime=ModifyTime} = FileInfo = FileInfoRaw,
+ io:format("Access ~p, Modify ~p", [AccessTime, ModifyTime]),
+ Size = 7,
+ Type = regular,
+ read_write = Access,
+ true = abs(time_dist(filter_atime(AccessTime, Config),
+ filter_atime(ModifyTime,
+ Config))) < 5,
+ all_integers(tuple_to_list(AccessTime) ++ tuple_to_list(ModifyTime)),
+
+ [] = flush(),
+ ok.
+
+file_handle_info_basic_directory(Config) when is_list(Config) ->
+ %% Note: filename:join/1 removes any trailing slash,
+ %% which is essential for ?FILE_MODULE:file_info/1 to work on
+ %% platforms such as Windows95.
+ RootDir = filename:join([proplists:get_value(priv_dir, Config)]),
+
+ %% Test that the RootDir directory has the expected attributes.
+ test_directory_handle(RootDir, read_write),
+
+ %% Note that on Windows file systems,
+ %% "/" or "c:/" are *NOT* directories.
+ %% Therefore, test that ?FILE_MODULE:file_info/1 behaves as if they were
+ %% directories.
+ case os:type() of
+ {win32, _} ->
+ test_directory_handle("/", read_write),
+ test_directory_handle("c:/", read_write),
+ test_directory_handle("c:\\", read_write),
+ test_directory_handle("\\\\localhost\\c$", read_write);
+ _ ->
+ test_directory_handle("/", read)
+ end,
+ ok.
+
+test_directory_handle(Name, ExpectedAccess) ->
+ {ok, DirFd} = file:open(Name, [read, directory]),
+ try
+ {ok,FileInfo} = ?FILE_MODULE:read_file_info(DirFd),
+ {ok,FileInfo} = ?FILE_MODULE:read_file_info(DirFd, [raw]),
+ #file_info{size=Size,type=Type,access=Access,
+ atime=AccessTime,mtime=ModifyTime} = FileInfo,
+ io:format("Testing directory ~s", [Name]),
+ io:format("Directory size is ~p", [Size]),
+ io:format("Access ~p", [Access]),
+ io:format("Access time ~p; Modify time~p",
+ [AccessTime, ModifyTime]),
+ Type = directory,
+ Access = ExpectedAccess,
+ all_integers(tuple_to_list(AccessTime) ++ tuple_to_list(ModifyTime)),
+ [] = flush(),
+ ok
+ after
+ file:close(DirFd)
+ end.
+
+%% Test that the file times behave as they should.
+
+file_handle_info_times(Config) when is_list(Config) ->
+ %% We have to try this twice, since if the test runs across the change
+ %% of a month the time diff calculations will fail. But it won't happen
+ %% if you run it twice in succession.
+ test_server:m_out_of_n(
+ 1,2,
+ fun() -> file_handle_info_int(Config) end),
+ ok.
+
+file_handle_info_int(Config) ->
+ %% Note: filename:join/1 removes any trailing slash,
+ %% which is essential for ?FILE_MODULE:file_info/1 to work on
+ %% platforms such as Windows95.
+
+ RootDir = filename:join([proplists:get_value(priv_dir, Config)]),
+ io:format("RootDir = ~p", [RootDir]),
+
+ Name = filename:join(RootDir,
+ atom_to_list(?MODULE)
+ ++"_file_info.fil"),
+ {ok,Fd1} = ?FILE_MODULE:open(Name, write),
+ io:put_chars(Fd1,"foo"),
+ {ok,FileInfo1} = ?FILE_MODULE:read_file_info(Fd1),
+ ok = ?FILE_MODULE:close(Fd1),
+
+ {ok,Fd1Raw} = ?FILE_MODULE:open(Name, [read, raw]),
+ {ok,FileInfo1Raw} = ?FILE_MODULE:read_file_info(Fd1Raw),
+ ok = ?FILE_MODULE:close(Fd1Raw),
+
+ %% We assert that everything but the size is the same, on some OSs the
+ %% size may not have been flushed to disc and we do not want to do a
+ %% sync to force it.
+ FileInfo1Raw = FileInfo1#file_info{ size = FileInfo1Raw#file_info.size },
+
+ #file_info{type=regular,atime=AccTime1,mtime=ModTime1} = FileInfo1,
+
+ Now = erlang:localtime(), %???
+ io:format("Now ~p",[Now]),
+ io:format("Open file Acc ~p Mod ~p",[AccTime1,ModTime1]),
+ true = abs(time_dist(filter_atime(Now, Config),
+ filter_atime(AccTime1,
+ Config))) < 8,
+ true = abs(time_dist(Now,ModTime1)) < 8,
+
+ %% Sleep until we can be sure the seconds value has changed.
+ %% Note: FAT-based filesystem (like on Windows 95) have
+ %% a resolution of 2 seconds.
+ timer:sleep(2200),
+
+ %% close the file, and watch the modify date change
+
+ {ok,Fd2} = ?FILE_MODULE:open(Name, read),
+ {ok,FileInfo2} = ?FILE_MODULE:read_file_info(Fd2),
+ ok = ?FILE_MODULE:close(Fd2),
+
+ {ok,Fd2Raw} = ?FILE_MODULE:open(Name, [read, raw]),
+ {ok,FileInfo2Raw} = ?FILE_MODULE:read_file_info(Fd2Raw),
+ ok = ?FILE_MODULE:close(Fd2Raw),
+
+ #file_info{size=Size,type=regular,access=Access,
+ atime=AccTime2,mtime=ModTime2} = FileInfo2 = FileInfo2Raw,
+ io:format("Closed file Acc ~p Mod ~p",[AccTime2,ModTime2]),
+ true = time_dist(ModTime1,ModTime2) >= 0,
+
+ %% this file is supposed to be binary, so it'd better keep it's size
+ Size = 3,
+ Access = read_write,
+
+ %% Do some directory checking
+
+ {ok,Fd3} = ?FILE_MODULE:open(RootDir, [read, directory]),
+ {ok,FileInfo3} = ?FILE_MODULE:read_file_info(Fd3),
+ ok = ?FILE_MODULE:close(Fd3),
+
+ {ok,Fd3Raw} = ?FILE_MODULE:open(RootDir, [read, directory, raw]),
+ {ok,FileInfo3Raw} = ?FILE_MODULE:read_file_info(Fd3Raw),
+ ok = ?FILE_MODULE:close(Fd3Raw),
+
+ #file_info{size=DSize,type=directory,access=DAccess,
+ atime=AccTime3,mtime=ModTime3} = FileInfo3 = FileInfo3Raw,
+ %% this dir was modified only a few secs ago
+ io:format("Dir Acc ~p; Mod ~p; Now ~p", [AccTime3, ModTime3, Now]),
+ true = abs(time_dist(Now,ModTime3)) < 5,
+ DAccess = read_write,
+ io:format("Dir size is ~p",[DSize]),
+
+ [] = flush(),
+ ok.
+
%% Test the write_file_info/2 function.
file_write_file_info(Config) when is_list(Config) ->
@@ -2042,24 +2268,33 @@ allocate_and_assert(Fd, Offset, Length) ->
allocate_file_size(Config) when is_list(Config) ->
case os:type() of
{unix, darwin} ->
- PrivDir = proplists:get_value(priv_dir, Config),
- Allocate = filename:join(PrivDir, atom_to_list(?MODULE)++"_allocate_file"),
-
- {ok, Fd} = ?FILE_MODULE:open(Allocate, [write]),
- ok = ?FILE_MODULE:allocate(Fd, 0, 1024),
- {ok, 1024} = ?FILE_MODULE:position(Fd, eof),
- ok = ?FILE_MODULE:close(Fd),
-
- [] = flush(),
- ok;
+ do_allocate_file_size(Config);
{unix, linux} ->
- {skip, "file:allocate/3 on Linux does not change file size"};
+ do_allocate_file_size(Config);
{win32, _} ->
{skip, "Windows does not support file:allocate/3"};
_ ->
{skip, "Support for allocate/3 is spotty in our test platform at the moment."}
end.
+do_allocate_file_size(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Allocate = filename:join(PrivDir, atom_to_list(?MODULE)++"_allocate_file"),
+
+ {ok, Fd} = ?FILE_MODULE:open(Allocate, [write]),
+ Result =
+ case ?FILE_MODULE:allocate(Fd, 0, 1024) of
+ ok ->
+ {ok, 1024} = ?FILE_MODULE:position(Fd, eof),
+ ok;
+ {error, enotsup} ->
+ {skip, "Filesystem does not support file:allocate/3"}
+ end,
+ ok = ?FILE_MODULE:close(Fd),
+
+ [] = flush(),
+ Result.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
delete(Config) when is_list(Config) ->
@@ -2893,7 +3128,7 @@ symlinks(Config) when is_list(Config) ->
{ok, Name} = ?FILE_MODULE:read_link(Alias),
{ok, Name} = ?FILE_MODULE:read_link_all(Alias),
%% If all is good, delete dir again (avoid hanging dir on windows)
- rm_rf(?FILE_MODULE,NewDir),
+ file:del_dir_r(NewDir),
ok
end,
@@ -4599,18 +4834,3 @@ disc_free(Path) ->
memsize() ->
{Tot,_Used,_} = memsup:get_memory_data(),
Tot.
-
-%%%-----------------------------------------------------------------
-%%% Utilities
-rm_rf(Mod,Dir) ->
- case Mod:read_link_info(Dir) of
- {ok, #file_info{type = directory}} ->
- {ok, Content} = Mod:list_dir_all(Dir),
- [ rm_rf(Mod,filename:join(Dir,C)) || C <- Content ],
- Mod:del_dir(Dir),
- ok;
- {ok, #file_info{}} ->
- Mod:delete(Dir);
- _ ->
- ok
- end.
diff --git a/lib/kernel/test/file_name_SUITE.erl b/lib/kernel/test/file_name_SUITE.erl
index 91dce39d12..7166064558 100644
--- a/lib/kernel/test/file_name_SUITE.erl
+++ b/lib/kernel/test/file_name_SUITE.erl
@@ -634,9 +634,9 @@ hopeless_darwin() ->
case {os:type(),os:version()} of
{{unix,darwin},{Major,_,_}} ->
%% icky file names worked between 10 and 17, but started returning
- %% EILSEQ in 18. The check against 18 is exact in case newer
+ %% EILSEQ in 18. The check against 18..19 is exact in case newer
%% versions of Darwin support them again.
- Major < 9 orelse Major =:= 18;
+ Major < 9 orelse (Major >= 18 andalso Major =< 19);
_ ->
false
end.
diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl
index 65183d83cc..a77e9cb856 100644
--- a/lib/kernel/test/gen_sctp_SUITE.erl
+++ b/lib/kernel/test/gen_sctp_SUITE.erl
@@ -21,6 +21,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet_sctp.hrl").
+-include("kernel_test_lib.hrl").
%%-compile(export_all).
@@ -41,7 +42,8 @@
peeloff_active_once/1, peeloff_active_true/1, peeloff_active_n/1,
buffers/1,
names_unihoming_ipv4/1, names_unihoming_ipv6/1,
- names_multihoming_ipv4/1, names_multihoming_ipv6/1]).
+ names_multihoming_ipv4/1, names_multihoming_ipv6/1,
+ recv_close/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -56,10 +58,25 @@ all() ->
{group,G}].
groups() ->
- [{smoke,[],[basic,basic_stream]},
- {old_solaris,[],[skip_old_solaris]},
- {extensive,[],
- [api_open_close, api_listen, api_connect_init,
+ [
+ {smoke, [], smoke_cases()},
+ {old_solaris, [], old_solaris_cases()},
+ {extensive, [], extensive_cases()}
+ ].
+
+smoke_cases() ->
+ [
+ basic,
+ basic_stream
+ ].
+
+old_solaris_cases() ->
+ [
+ skip_old_solaris
+ ].
+
+extensive_cases() ->
+ [api_open_close, api_listen, api_connect_init,
api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6,
open_multihoming_ipv4_socket,
open_unihoming_ipv6_socket,
@@ -68,7 +85,8 @@ groups() ->
xfer_stream_min, peeloff_active_once,
peeloff_active_true, peeloff_active_n, buffers,
names_unihoming_ipv4, names_unihoming_ipv6,
- names_multihoming_ipv4, names_multihoming_ipv6]}].
+ names_multihoming_ipv4, names_multihoming_ipv6,
+ recv_close].
init_per_suite(_Config) ->
case gen_sctp:open() of
@@ -161,8 +179,8 @@ xfer_min(Config) when is_list(Config) ->
case log_ok(gen_sctp:recv(Sb, infinity)) of
{Loopback,
Pa,
- [#sctp_sndrcvinfo{stream=Stream,
- assoc_id=SbAssocId}],
+ [#sctp_sndrcvinfo{stream = Stream,
+ assoc_id = SbAssocId}],
Data} -> ok;
Event1 ->
case recv_event(Event1) of
@@ -539,20 +557,6 @@ getopt(S, Opt, Param) ->
setopt(S, Opt, Val) ->
inet:setopts(S, [{Opt,Val}]).
-log_ok(X) -> log(ok(X)).
-
-ok({ok,X}) -> X.
-
-err([], Result) ->
- erlang:error(Result);
-err([Reason|_], {error,Reason}) ->
- ok;
-err([_|Reasons], Result) ->
- err(Reasons, Result).
-
-log(X) ->
- io:format("LOG[~w]: ~p~n", [self(),X]),
- X.
flush() ->
receive
@@ -696,24 +700,24 @@ api_connect_init(Config) when is_list(Config) ->
ok = gen_sctp:close(Sb),
ok.
-recv_event({Addr,Port,[],#sctp_assoc_change{}=AssocChange}) ->
- {Addr,Port,AssocChange};
-recv_event({Addr,Port,
- [#sctp_sndrcvinfo{assoc_id=Assoc}],
- #sctp_assoc_change{assoc_id=Assoc}=AssocChange}) ->
- {Addr,Port,AssocChange};
-recv_event({Addr,Port,[],#sctp_paddr_change{}=PaddrChange}) ->
- {Addr,Port,PaddrChange};
+recv_event({Addr, Port, [], #sctp_assoc_change{} = AssocChange}) ->
+ {Addr, Port, AssocChange};
recv_event({Addr,Port,
- [#sctp_sndrcvinfo{assoc_id=Assoc}],
- #sctp_paddr_change{assoc_id=Assoc}=PaddrChange}) ->
- {Addr,Port,PaddrChange};
-recv_event({Addr,Port,[],#sctp_shutdown_event{}=ShutdownEvent}) ->
- {Addr,Port,ShutdownEvent};
-recv_event({Addr,Port,
- [#sctp_sndrcvinfo{assoc_id=Assoc}],
- #sctp_shutdown_event{assoc_id=Assoc}=ShutdownEvent}) ->
- {Addr,Port,ShutdownEvent}.
+ [#sctp_sndrcvinfo{assoc_id = Assoc}],
+ #sctp_assoc_change{assoc_id = Assoc} = AssocChange}) ->
+ {Addr, Port, AssocChange};
+recv_event({Addr, Port, [], #sctp_paddr_change{} = PaddrChange}) ->
+ {Addr, Port, PaddrChange};
+recv_event({Addr, Port,
+ [#sctp_sndrcvinfo{assoc_id = Assoc}],
+ #sctp_paddr_change{assoc_id = Assoc} = PaddrChange}) ->
+ {Addr, Port, PaddrChange};
+recv_event({Addr, Port, [], #sctp_shutdown_event{} = ShutdownEvent}) ->
+ {Addr, Port, ShutdownEvent};
+recv_event({Addr, Port,
+ [#sctp_sndrcvinfo{assoc_id = Assoc}],
+ #sctp_shutdown_event{assoc_id = Assoc} = ShutdownEvent}) ->
+ {Addr, Port, ShutdownEvent}.
%% Test socket options.
api_opts(Config) when is_list(Config) ->
@@ -730,72 +734,165 @@ api_opts(Config) when is_list(Config) ->
ok = inet:setopts(S, [{sndbuf,Sndbuf}]),
ok = inet:setopts(S, [{recbuf,Recbuf}]),
case inet:getopts(S, [sndbuf]) of
- {ok,[{sndbuf,SB}]} when SB >= Sndbuf -> ok
+ {ok, [{sndbuf,SB}]} when SB >= Sndbuf -> ok
end,
case inet:getopts(S, [recbuf]) of
- {ok,[{recbuf,RB}]} when RB >= Recbuf -> ok
+ {ok, [{recbuf, RB}]} when (RB >= Recbuf) -> ok
end.
+%% What is this *actually* supposed to test?
implicit_inet6(Config) when is_list(Config) ->
- Hostname = log_ok(inet:gethostname()),
+ ?TC_TRY(implicit_inet6, fun() -> do_implicit_inet6(Config) end).
+
+do_implicit_inet6(_Config) ->
+ ?P("begin"),
+ %% First
+ ?P("try create server socket (1)"),
case gen_sctp:open(0, [inet6]) of
- {ok,S1} ->
- case inet:getaddr(Hostname, inet6) of
- {ok,Host} ->
- Loopback = {0,0,0,0,0,0,0,1},
- io:format("~s ~p~n", ["Loopback",Loopback]),
- implicit_inet6(S1, Loopback),
- ok = gen_sctp:close(S1),
- %%
- Localhost =
- log_ok(inet:getaddr("localhost", inet6)),
- io:format("~s ~p~n", ["localhost",Localhost]),
- S2 =
- log_ok(gen_sctp:open(0, [{ip,Localhost}])),
- implicit_inet6(S2, Localhost),
- ok = gen_sctp:close(S2),
- %%
- io:format("~s ~p~n", [Hostname,Host]),
- S3 =
- log_ok(gen_sctp:open(0, [{ifaddr,Host}])),
- implicit_inet6(S3, Host),
- ok = gen_sctp:close(S1);
- {error,eafnosupport} ->
- ok = gen_sctp:close(S1),
- {skip,"Can not look up IPv6 address"}
- end;
- _ ->
- {skip,"IPv6 not supported"}
+ {ok, S1} ->
+ Loopback = {0,0,0,0,0,0,0,1},
+ ?P("*** ~s: ~p ***", ["Loopback", Loopback]),
+ implicit_inet6(S1, Loopback),
+ ok = gen_sctp:close(S1),
+
+ %% Second
+ ?P("try create server socket (2)"),
+ Localhost = log_ok(inet:getaddr("localhost", inet6)),
+ S2 = log_ok(gen_sctp:open(0, [{ip,Localhost}])),
+ ?P("*** ~s: ~p ***", ["localhost", Localhost]),
+ implicit_inet6(S2, Localhost),
+ ok = gen_sctp:close(S2),
+
+ %% Third
+ ?P("try create server socket (3)"),
+ Hostname = log_ok(inet:gethostname()),
+ Addr = case inet:getaddr(Hostname, inet6) of
+ {ok, A} ->
+ A;
+ {error, eafnosupport} ->
+ ok = gen_sctp:close(S1),
+ ?SKIPT("Can not look up IPv6 address")
+ end,
+ S3 = log_ok(gen_sctp:open(0, [{ifaddr, Addr}])),
+ ?P("*** ~s: ~p ***", [Hostname, Addr]),
+ implicit_inet6(S3, Addr),
+ ok = gen_sctp:close(S1),
+ ?P("done"),
+ ok;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(open_failed_str(Reason));
+ _ ->
+ {skip, "IPv6 not supported"}
end.
+
implicit_inet6(S1, Addr) ->
+ ?P("make (server) listen socket"),
ok = gen_sctp:listen(S1, true),
- P1 = log_ok(inet:port(S1)),
- S2 = log_ok(gen_sctp:open(0, [inet6])),
- P2 = log_ok(inet:port(S2)),
- #sctp_assoc_change{state=comm_up} =
- log_ok(gen_sctp:connect(S2, Addr, P1, [])),
- case recv_event(log_ok(gen_sctp:recv(S1))) of
- {Addr,P2,#sctp_assoc_change{state=comm_up}} ->
- ok;
- {Addr,P2,#sctp_paddr_change{state=addr_confirmed,
- addr={Addr,P2},
- error=0}} ->
- {Addr,P2,#sctp_assoc_change{state=comm_up}} =
- recv_event(log_ok(gen_sctp:recv(S1)))
+ ServerPortNo = log_ok(inet:port(S1)),
+ ?P("try create (client) socket"),
+ S2 = case gen_sctp:open(0, [inet6, {ifaddr, Addr}]) of
+ {ok, Sock} ->
+ ?P("client socket created: ~p", [Sock]),
+ Sock;
+ {error, eaddrnotavail = Reason} ->
+ ?P("could not create (client) socket with ifaddr: "
+ "~n ~p", [Addr]),
+ ?SKIPT(open_failed_str(Reason))
end,
+ {ClientAddr, ClientPortNo} = log_ok(inet:sockname(S2)),
+ ?P("try connect"
+ "~n from (connector): ~p, ~p (~p)"
+ "~n to: ~p, ~p",
+ [ClientAddr, ClientPortNo, S2, Addr, ServerPortNo]),
+ #sctp_assoc_change{state = comm_up} =
+ log_ok(gen_sctp:connect(S2, Addr, ServerPortNo, [])),
+ ?P("connect success: await events"),
+ implicit_inet6_await_ac_comm_up(S1, ClientAddr, ClientPortNo),
+ ?P("verify server sockname"),
case log_ok(inet:sockname(S1)) of
- {Addr,P1} -> ok;
- {{0,0,0,0,0,0,0,0},P1} -> ok
+ {Addr, ServerPortNo} -> ok;
+ {{0,0,0,0,0,0,0,0}, ServerPortNo} -> ok
end,
+ ?P("verify client sockname"),
case log_ok(inet:sockname(S2)) of
- {Addr,P2} -> ok;
- {{0,0,0,0,0,0,0,0},P2} -> ok
+ {Addr, ClientPortNo} -> ok;
+ {{0,0,0,0,0,0,0,0}, ClientPortNo} -> ok
end,
- ok = gen_sctp:close(S2).
+ ?P("client client socket"),
+ ok = gen_sctp:close(S2),
+ ?P("verification complete"),
+ ok.
+
+
+implicit_inet6_await_ac_comm_up(Sock, Addr, PortNo) ->
+ {_OsFam, OsName} = os:type(),
+ implicit_inet6_await_ac_comm_up(Sock, Addr, PortNo, OsName).
+
+implicit_inet6_await_ac_comm_up(Sock, Addr, PortNo, OsName) ->
+ case recv_event(log_ok(gen_sctp:recv(Sock))) of
+ {Addr, PortNo, #sctp_assoc_change{state = comm_up}} ->
+ ?P("received assoc-change:comm-up event => done"),
+ ok;
+ {Addr, PortNo, #sctp_paddr_change{state = addr_confirmed,
+ addr = {Addr, PortNo},
+ error = 0}} ->
+ ?P("received paddr-change:addr-confirmed event - "
+ "try recv assoc-change:comm-up"),
+ implicit_inet6_await_ac_comm_up(Sock, Addr, PortNo, OsName);
+
+ {Addr2, PortNo2, #sctp_assoc_change{state = comm_up}}
+ when (OsName =:= freebsd) ->
+ ?P("Expected (assoc-change:comm-up) event from unexpected address: "
+ "~n Unexpected Address: ~p, ~p"
+ "~n Expected Address: ~p, ~p"
+ "~n => RETRY"
+ "~n", [Addr2, PortNo2, Addr, PortNo]),
+ implicit_inet6_await_ac_comm_up(Sock, Addr, PortNo, OsName);
+ {Addr2, PortNo2, #sctp_paddr_change{state = addr_confirmed}}
+ when (OsName =:= freebsd) ->
+ ?P("Expected paddr-change:addr-confirmed event from "
+ "UNEXPECTED ADDRESS: "
+ "~n UNEXPECTED Address: ~p, ~p"
+ "~n Expected Address: ~p, ~p"
+ "~n => RETRY"
+ "~n", [Addr2, PortNo2, Addr, PortNo]),
+ implicit_inet6_await_ac_comm_up(Sock, Addr, PortNo, OsName);
+
+ {Addr2, PortNo2, #sctp_assoc_change{state = comm_up} = CX} = UNEX ->
+ ?P("Expected (assoc-change:comm-up) event from UNEXPECTED ADDRESS: "
+ "~n UNEXPECTED Address: ~p, ~p"
+ "~n Expected Address: ~p, ~p"
+ "~n Assoc Change: ~p"
+ "~n", [Addr2, PortNo2, Addr, PortNo, CX]),
+ exit({unexpected_event, UNEX});
+
+ {AX, PX, #sctp_paddr_change{state = addr_confirmed} = CX} = UNEX ->
+ ?P("Expected paddr-change:addr-confirmed event from "
+ "UNEXPECTED ADDRESS: "
+ "~n UNEXPECTED Address: ~p, ~p"
+ "~n Expected Address: ~p, ~p"
+ "~n PAddr Change: ~p"
+ "~n", [AX, PX, Addr, PortNo, CX]),
+ exit({unexpected_event, UNEX});
+
+ {AX, PX, CX} = UNEX ->
+ ?P("UNEXPECTED EVENT: "
+ "~n ~p"
+ "~n UNEXPECTED ADDRESS: ~p, ~p"
+ "~n Expected Address: ~p, ~p"
+ "~n", [CX, AX, PX, Addr, PortNo]),
+ exit({unexpected_event, UNEX})
+ end.
-%% Verify {active,N} socket management.
+
+%% Verify {active, N} socket management.
+%% This is difficult to do since we do not just receive data messages.
+%% Also, how do we know that sctp behaves the same way on all platforms?
active_n(Config) when is_list(Config) ->
+ ?TC_TRY(active_n, fun() -> do_active_n(Config) end).
+
+do_active_n(_Config) ->
N = 3,
S1 = ok(gen_sctp:open([{active,N}])),
[{active,N}] = ok(inet:getopts(S1, [active])),
@@ -851,33 +948,8 @@ active_n(Config) when is_list(Config) ->
S2 = ok(gen_sctp:open(0, [{active,false}])),
Assoc = ok(gen_sctp:connect(S2, "localhost", S1Port, [])),
ok = inet:setopts(S1, [{active,N}]),
- [{active,N}] = ok(inet:getopts(S1, [active])),
- LoopFun = fun(Count, Count, _Fn) ->
- receive
- {sctp_passive,S1} ->
- ok
- after
- 5000 ->
- exit({error,timeout})
- end;
- (I, Count, Fn) ->
- Msg = list_to_binary("message "++integer_to_list(I)),
- ok = gen_sctp:send(S2, Assoc, 0, Msg),
- receive
- {sctp,S1,_,_,{[SR],Msg}} when is_record(SR, sctp_sndrcvinfo) ->
- Fn(I+1, Count, Fn);
- {sctp,S1,_,_,_} ->
- %% ignore non-data messages
- ok = inet:setopts(S1, [{active,1}]),
- Fn(I, Count, Fn);
- Other ->
- exit({unexpected, Other})
- after
- 5000 ->
- exit({error,timeout})
- end
- end,
- ok = LoopFun(1, N, LoopFun),
+ active_n_flush_connect_msgs(S1),
+ active_n_send_loop(N, S2, Assoc, S1),
S3 = ok(gen_sctp:open([{active,0}])),
receive
{sctp_passive,S3} ->
@@ -891,6 +963,122 @@ active_n(Config) when is_list(Config) ->
ok = gen_sctp:close(S1),
ok.
+
+%% There is no way to know how many addresses this host has,
+%% and if there is "too many" (more then N = 3), then the
+%% socket may already be passive. In this case the send-
+%% loop will fail.
+%% So, if we get a passive-message here, we just give up (=skip).
+active_n_flush_connect_msgs(Sock) ->
+ %% This seems only to be needed on certain platforms
+ active_n_flush_connect_msgs(os:type(), Sock).
+
+active_n_flush_connect_msgs(_, Sock) ->
+ do_active_n_flush_connect_msgs(Sock).
+%% active_n_flush_connect_msgs({unix, freebsd}, Sock) ->
+%% do_active_n_flush_connect_msgs(Sock);
+%% active_n_flush_connect_msgs(_, _) ->
+%% ok.
+
+do_active_n_flush_connect_msgs(Sock) ->
+ receive
+ {sctp_passive, Sock} ->
+ ?P("connect-flush-loop -> premature passive"),
+ ?SKIPT("Too many addresses (premature passive)");
+
+ {sctp, Sock,
+ _FromIP, _FromPort,
+ {[], #sctp_assoc_change{state = comm_up}}} ->
+ ?P("connect-flush-loop -> "
+ "connect message discard - assoc change : comm-up"),
+ ok = inet:setopts(Sock, [{active, 1}]),
+ do_active_n_flush_connect_msgs(Sock);
+
+ {sctp, Sock,
+ _FromIP, _FromPort,
+ {[], #sctp_paddr_change{state = addr_confirmed,
+ addr = Addr,
+ error = Error,
+ assoc_id = AID}}} ->
+ ?P("connect-flush-loop -> "
+ "connect message discard - paddr change : addr-confirmed:"
+ "~n Addr: ~p"
+ "~n Error: ~p"
+ "~n AssocID: ~p", [Addr, Error, AID]),
+ ok = inet:setopts(Sock, [{active, 1}]),
+ do_active_n_flush_connect_msgs(Sock)
+
+ after 5000 ->
+ ok
+ end.
+
+active_n_send_loop(Count, SrcSock, SndAssoc, DstSock) ->
+ active_n_send_loop(0, Count, SrcSock, SndAssoc, DstSock).
+
+active_n_send_loop(Count, Count, _SndSock, _SndAssoc, RcvSock) ->
+ ?P("send-loop -> we are done - wait for passive"),
+ receive
+ {sctp_passive, RcvSock} ->
+ ?P("received passive"),
+ ok
+ after
+ 5000 ->
+ ?P("UNEXPECTED TIMEOUT: "
+ "~n Message Queue: ~p"
+ "~n Active: ~p",
+ [process_info(self(), messages),
+ inet:getopts(RcvSock, [active])]),
+ exit({error, timeout})
+ end;
+
+active_n_send_loop(Sent, Count, SndSock, SndAssoc, RcvSock) ->
+ Msg = list_to_binary("message " ++ integer_to_list(Sent+1)),
+ ?P("send-loop(~w,~w) -> send message (on ~p)", [Sent, Count, SndSock]),
+ ok = gen_sctp:send(SndSock, SndAssoc, 0, Msg),
+ receive
+ {sctp, RcvSock, FromIP, FromPort, {[SR], Msg}}
+ when is_record(SR, sctp_sndrcvinfo) ->
+ ?P("send-loop(~w,~w) -> "
+ "recv (expected) data message (on ~p):"
+ "~n Msg: ~p"
+ "~n From: ~p, ~p",
+ [Sent, Count,
+ RcvSock, Msg, FromIP, FromPort]),
+ active_n_send_loop(Sent+1, Count, SndSock, SndAssoc, RcvSock);
+
+ {sctp, RcvSock, _FromIP, _FromPort, {_AncData, _Data}} ->
+ %% ignore non-data messages
+ %% we should not get any here because of the flush loop,
+ %% but just in case...
+ ?P("send-loop(~w,~w) -> "
+ "ignore non-data messages (on ~p):"
+ "~n From: ~p:~p"
+ "~n AncData: ~p"
+ "~n Data: ~p",
+ [Sent, Count,
+ RcvSock, _FromIP, _FromPort, _AncData, _Data]),
+
+ %% It may be too late to update here,
+ %% the socket may already have gone passive
+ %% and generated a passive message!
+
+ ok = inet:setopts(RcvSock, [{active, 1}]),
+
+ active_n_send_loop(Sent, Count, SndSock, SndAssoc, RcvSock);
+
+ Other ->
+ ?P("send-loop(~w,~w) -> "
+ "UNEXPECTED: "
+ "~n Other: ~p"
+ "~n Send Sock: ~p"
+ "~n Recv Sock: ~p", [Sent, Count,
+ Other, SndSock, RcvSock]),
+ exit({unexpected, Other})
+ after
+ 5000 ->
+ exit({error,timeout})
+ end.
+
%% Hello world stream socket.
basic_stream(Config) when is_list(Config) ->
{ok,S} = gen_sctp:open([{type,stream}]),
@@ -903,6 +1091,7 @@ basic_stream(Config) when is_list(Config) ->
%% Minimal data transfer.
xfer_stream_min(Config) when is_list(Config) ->
+ {_, OSName} = os:type(),
Stream = 0,
Data = <<"The quick brown fox jumps over a lazy dog 0123456789">>,
Loopback = {127,0,0,1},
@@ -961,33 +1150,92 @@ xfer_stream_min(Config) when is_list(Config) ->
case log_ok(gen_sctp:recv(Sb, infinity)) of
{Loopback,
Pa,
- [#sctp_sndrcvinfo{stream=Stream,
- assoc_id=SbAssocId}],
- Data} -> ok;
+ [#sctp_sndrcvinfo{stream = Stream,
+ assoc_id = SbAssocId}],
+ Data} ->
+ ?P("[1] received expected data with ancillary data => done"),
+ ok;
+
{Loopback,
- Pa,[],
- #sctp_paddr_change{addr = {Loopback,_},
- state = addr_available,
- error = 0,
+ Pa,
+ [],
+ #sctp_paddr_change{addr = {Loopback,_},
+ state = addr_available,
+ error = 0,
assoc_id = SbAssocId}} ->
+ ?P("[2] received paddr change => recv again"),
+ Res2 = log_ok(gen_sctp:recv(Sb, infinity)),
+ ?P("[2] recv ok => "
+ "~n ~p", [Res2]),
{Loopback,
Pa,
- [#sctp_sndrcvinfo{stream=Stream,
- assoc_id=SbAssocId}],
- Data} = log_ok(gen_sctp:recv(Sb, infinity));
+ [#sctp_sndrcvinfo{stream = Stream,
+ assoc_id = SbAssocId}],
+ Data} = Res2,
+ ?P("[2] received expected data with ancillary data => done"),
+ Res2;
+
{Loopback,
Pa,
- [#sctp_sndrcvinfo{stream=Stream,
- assoc_id=SbAssocId}],
- #sctp_paddr_change{addr = {Loopback,_},
- state = addr_confirmed,
- error = 0,
+ [#sctp_sndrcvinfo{stream = Stream,
+ assoc_id = SbAssocId}],
+ #sctp_paddr_change{addr = {Loopback,_},
+ state = addr_confirmed,
+ error = 0,
assoc_id = SbAssocId}} ->
+ ?P("[3] received paddr change with ancillary data => recv again"),
+ Res3 = log_ok(gen_sctp:recv(Sb, infinity)),
+ ?P("[3] recv ok => "
+ "~n ~p", [Res3]),
{Loopback,
Pa,
- [#sctp_sndrcvinfo{stream=Stream,
- assoc_id=SbAssocId}],
- Data} = log_ok(gen_sctp:recv(Sb, infinity))
+ [#sctp_sndrcvinfo{stream = Stream,
+ assoc_id = SbAssocId}],
+ Data} = Res3,
+ ?P("[3] received expected data with ancillary data => done"),
+ Res3;
+
+ %% It seems that on FreeBSD (for instance) we don't get any
+ %% AncData with this.
+ {Loopback,
+ Pa,
+ [],
+ #sctp_paddr_change{addr = {Loopback,_},
+ state = addr_confirmed,
+ error = 0,
+ assoc_id = SbAssocId}} when (OSName =:= freebsd) ->
+ ?P("[4] received paddr change without ancillary data => "
+ "recv again"),
+ Res4 = log_ok(gen_sctp:recv(Sb, infinity)),
+ ?P("[4] recv ok => "
+ "~n ~p", [Res4]),
+ {Loopback,
+ Pa,
+ [#sctp_sndrcvinfo{stream = Stream,
+ assoc_id = SbAssocId}],
+ Data} = Res4,
+ ?P("[4] received expected data with ancillary data => done"),
+ Res4;
+
+ {FromIPX, FromPortX, AncDataX, DataX} = Other1 ->
+ ?P("UNEXPECTED: "
+ "~n FromIP: ~p"
+ "~n FromPort: ~p"
+ "~n AncData: ~p"
+ "~n Data: ~p"
+ "~nwhen"
+ "~n Loopback: ~p"
+ "~n Pa: ~p",
+ [FromIPX, FromPortX, AncDataX, DataX, Loopback, Pa]),
+ exit({unexpected, Other1});
+ Other2 ->
+ ?P("UNEXPECTED: "
+ "~n Other: ~p"
+ "~nwhen"
+ "~n Loopback: ~p"
+ "~n Pa: ~p",
+ [Other2, Loopback, Pa]),
+ exit({unexpected, Other2})
end,
ok =
do_from_other_process(
@@ -1394,11 +1642,12 @@ get_addrs_by_family(Family, NumAddrs) ->
end;
Os ->
Reason = if Family =:= inet_and_inet6 ->
- f("Mixing ipv4 and ipv6 addresses for multihoming "
- " has not been verified on ~p", [Os]);
+ ?F("Mixing ipv4 and ipv6 addresses for "
+ " multihoming has not been verified on ~p",
+ [Os]);
true ->
- f("Multihoming for ~p has not been verified on ~p",
- [Family, Os])
+ ?F("Multihoming for ~p has not been verified "
+ "on ~p", [Family, Os])
end,
{error, Reason}
end.
@@ -1407,19 +1656,19 @@ get_addrs_by_family_aux(Family, NumAddrs) when Family =:= inet;
Family =:= inet6 ->
case inet:getaddr(localhost, Family) of
{error,eafnosupport} ->
- {skip, f("No support for ~p", Family)};
+ {skip, ?F("No support for ~p", Family)};
{ok, _} ->
IfAddrs = ok(inet:getifaddrs()),
case filter_addrs_by_family(IfAddrs, Family) of
Addrs when length(Addrs) >= NumAddrs ->
{ok, lists:sublist(Addrs, NumAddrs)};
[] ->
- {error, f("Need ~p ~p address(es) found none~n",
- [NumAddrs, Family])};
+ {error, ?F("Need ~p ~p address(es) found none~n",
+ [NumAddrs, Family])};
Addrs ->
{error,
- f("Need ~p ~p address(es) found only ~p: ~p~n",
- [NumAddrs, Family, length(Addrs), Addrs])}
+ ?F("Need ~p ~p address(es) found only ~p: ~p~n",
+ [NumAddrs, Family, length(Addrs), Addrs])}
end
end;
get_addrs_by_family_aux(inet_and_inet6, NumAddrs) ->
@@ -1451,9 +1700,6 @@ ipv4_map_addrs(InetAddrs) ->
{0, 0, 0, 0, 0, 16#ffff, AB, CD}
end || {A,B,C,D} <- InetAddrs].
-f(F, A) ->
- lists:flatten(io_lib:format(F, A)).
-
do_open_and_connect(ServerAddresses, AddressToConnectTo) ->
Fun = fun (_, _, _, _, _, _) -> ok end,
do_open_and_connect(ServerAddresses, AddressToConnectTo, Fun).
@@ -1511,6 +1757,111 @@ recv_comm_up_eventually(S) ->
recv_comm_up_eventually(S)
end.
+
+%%
+recv_close(Config) when is_list(Config) ->
+ ?P("create server socket (and listen)"),
+ {ok, S} = gen_sctp:open(),
+ gen_sctp:listen(S, true),
+ {ok, SPort} = inet:port(S),
+
+ ?P("create client socket (and connect)"),
+ {ok, C} = gen_sctp:open(),
+ {ok, _} = gen_sctp:connect(C, localhost, SPort, []),
+
+ TC = self(),
+ RECV = fun() ->
+ ?P("try setup recv(s)"),
+ ok = recv_close_setup_recv(S),
+ ?P("announce ready"),
+ TC ! {self(), ready},
+ ?P("try data recv"),
+ Res = gen_sctp:recv(S),
+ ?P("recv res: "
+ "~n ~p", [Res]),
+ exit(Res)
+ end,
+ ?P("spawn reader - then await reader ready"),
+ {Pid, MRef} = spawn_monitor(RECV),
+ receive
+ {'DOWN', MRef, process, Pid, PreReason} ->
+ %% Make sure it does not die for some other reason...
+ ?P("unexpected reader termination:"
+ "~n ~p", [PreReason]),
+ (catch gen_sctp:close(S)),
+ (catch gen_sctp:close(C)),
+ ?line ct:fail("Unexpected pre close from reader (~p): ~p",
+ [Pid, PreReason]);
+ {Pid, ready} ->
+ ?P("reader ready"),
+ ok
+ after 30000 -> % Just in case...
+ %% This is **extreme**, but there is no way to know
+ %% how long it will take to iterate through all the
+ %% addresses of a host...
+ ?P("reader ready timeout"),
+ (catch gen_sctp:close(S)),
+ (catch gen_sctp:close(C)),
+ ?line ct:fail("Unexpected pre close timeout (~p)", [Pid])
+ end,
+
+ ?P("\"ensure\" reader reading..."),
+ receive
+ Any ->
+ ?P("Received unexpected message: "
+ "~n ~p", [Any]),
+ (catch gen_sctp:close(S)),
+ (catch gen_sctp:close(C)),
+ ?line ct:fail("Unexpected message: ~p", [Any])
+ after 5000 ->
+ ok
+ end,
+
+ ?P("close server socket"),
+ ok = gen_sctp:close(S),
+ ?P("await reader termination"),
+ receive
+ {'DOWN', MRef, process, Pid, {error, closed}} ->
+ ?P("expected reader termination result"),
+ (catch gen_sctp:close(C)),
+ ok;
+ {'DOWN', MRef, process, Pid, PostReason} ->
+ ?P("unexpected reader termination: "
+ "~n ~p", [PostReason]),
+ (catch gen_sctp:close(C)),
+ ?line ct:fail("Unexpected post close from reader (~p): ~p",
+ [Pid, PostReason])
+ after 5000 ->
+ ?P("unexpected reader termination timeout"),
+ demonitor(MRef, [flush]),
+ (catch gen_sctp:close(C)),
+ exit(Pid, kill),
+ ?line ct:fail("Reader (~p) termination timeout", [Pid])
+ end,
+ ?P("close client socket"),
+ (catch gen_sctp:close(C)),
+ ?P("done"),
+ ok.
+
+
+recv_close_setup_recv(S) ->
+ recv_close_setup_recv(S, 1).
+
+recv_close_setup_recv(S, N) ->
+ ?P("try setup recv ~w", [N]),
+ case gen_sctp:recv(S, 5000) of
+ {ok, {Addr,
+ Port,
+ _AncData,
+ Data}} when is_tuple(Addr) andalso is_integer(Port) ->
+ ?P("setup recv ~w: "
+ "~n ~p", [N, Data]),
+ recv_close_setup_recv(S, N+1);
+ {error, timeout} ->
+ ok
+ end.
+
+
%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% socket gen_server ultra light
@@ -1743,5 +2094,42 @@ match_unless_solaris(A, B) ->
_ -> A = B
end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
timestamp() ->
erlang:monotonic_time().
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+log_ok(X) ->
+ log(ok(X)).
+
+ok({ok, X}) ->
+ X;
+ok({error, X}) ->
+ ?P("ERROR: ~p", [X]),
+ ?line ct:fail({unexpected_error, X});
+ok(X) ->
+ ?P("UNEXPECTED: ~p", [X]),
+ ?line ct:fail({unexpected, X}).
+
+
+log(X) ->
+ ?P("LOG: ~p", [X]),
+ X.
+
+err([], Result) ->
+ erlang:error(Result);
+err([Reason|_], {error,Reason}) ->
+ ok;
+err([_|Reasons], Result) ->
+ err(Reasons, Result).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+open_failed_str(Reason) ->
+ ?F("Open failed: ~w", [Reason]).
+
diff --git a/lib/kernel/test/gen_tcp_api_SUITE.erl b/lib/kernel/test/gen_tcp_api_SUITE.erl
index 1be016444f..9e92919bf0 100644
--- a/lib/kernel/test/gen_tcp_api_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_api_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,10 +25,14 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet.hrl").
+-include("kernel_test_lib.hrl").
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+-export([
+ all/0, suite/0, groups/0,
+ init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
init_per_testcase/2, end_per_testcase/2,
+
t_connect_timeout/1, t_accept_timeout/1,
t_connect_bad/1,
t_recv_timeout/1, t_recv_eof/1, t_recv_delim/1,
@@ -38,49 +42,256 @@
t_local_basic/1, t_local_unbound/1, t_local_fdopen/1,
t_local_fdopen_listen/1, t_local_fdopen_listen_unbound/1,
t_local_fdopen_connect/1, t_local_fdopen_connect_unbound/1,
- t_local_abstract/1, t_accept_inet6_tclass/1]).
+ t_local_abstract/1, t_accept_inet6_tclass/1,
+ s_accept_with_explicit_socket_backend/1
+ ]).
--export([getsockfd/0,closesockfd/1]).
+-export([getsockfd/0, closesockfd/1]).
-suite() ->
- [{ct_hooks,[ts_install_cth]},
- {timetrap,{minutes,1}}].
-all() ->
- [{group, t_accept}, {group, t_connect}, {group, t_recv},
- t_shutdown_write, t_shutdown_both, t_shutdown_error,
- t_shutdown_async, t_fdopen, t_fdconnect, t_implicit_inet6,
- t_accept_inet6_tclass,
- {group, t_local}].
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+suite() ->
+ [
+ {ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}
+ ].
+
+all() ->
+ %% This is a temporary messure to ensure that we can
+ %% test the socket backend without effecting *all*
+ %% applications on *all* machines.
+ %% This flag is set only for *one* host.
+ case ?TEST_INET_BACKENDS() of
+ true ->
+ [
+ {group, inet_backend_default},
+ {group, inet_backend_inet},
+ {group, inet_backend_socket},
+ {group, s_misc}
+ ];
+ _ ->
+ [
+ {group, inet_backend_default},
+ {group, s_misc}
+ ]
+ end.
groups() ->
- [{t_accept, [], [t_accept_timeout]},
- {t_connect, [], [t_connect_timeout, t_connect_bad]},
- {t_recv, [], [t_recv_timeout, t_recv_eof, t_recv_delim]},
- {t_local, [],
- [t_local_basic, t_local_unbound, t_local_fdopen,
- t_local_fdopen_listen, t_local_fdopen_listen_unbound,
- t_local_fdopen_connect, t_local_fdopen_connect_unbound,
- t_local_abstract]}].
+ [
+ {inet_backend_default, [], inet_backend_default_cases()},
+ {inet_backend_inet, [], inet_backend_inet_cases()},
+ {inet_backend_socket, [], inet_backend_socket_cases()},
+ {t_accept, [], t_accept_cases()},
+ {t_connect, [], t_connect_cases()},
+ {t_recv, [], t_recv_cases()},
+ {t_shutdown, [], t_shutdown_cases()},
+ {t_misc, [], t_misc_cases()},
+ {t_local, [], t_local_cases()},
+ {s_misc, [], s_misc_cases()}
+ ].
+
+inet_backend_default_cases() ->
+ [
+ {group, t_accept},
+ {group, t_connect},
+ {group, t_recv},
+ {group, t_shutdown},
+ {group, t_misc},
+ {group, t_local}
+ ].
+
+inet_backend_inet_cases() ->
+ inet_backend_default_cases().
+
+inet_backend_socket_cases() ->
+ inet_backend_default_cases().
+
+t_accept_cases() ->
+ [
+ t_accept_timeout
+ ].
+
+t_connect_cases() ->
+ [
+ t_connect_timeout,
+ t_connect_bad
+ ].
+
+t_recv_cases() ->
+ [
+ t_recv_timeout,
+ t_recv_eof,
+ t_recv_delim
+ ].
+
+t_shutdown_cases() ->
+ [
+ t_shutdown_write,
+ t_shutdown_both,
+ t_shutdown_error,
+ t_shutdown_async
+ ].
+
+t_misc_cases() ->
+ [
+ t_fdopen,
+ t_fdconnect,
+ t_implicit_inet6,
+ t_accept_inet6_tclass
+ ].
+
+t_local_cases() ->
+ [
+ t_local_basic,
+ t_local_unbound,
+ t_local_fdopen,
+ t_local_fdopen_listen,
+ t_local_fdopen_listen_unbound,
+ t_local_fdopen_connect,
+ t_local_fdopen_connect_unbound,
+ t_local_abstract
+ ].
+
+s_misc_cases() ->
+ [
+ s_accept_with_explicit_socket_backend
+ ].
+
+init_per_suite(Config0) ->
+
+ ?P("init_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ case ?LIB:init_per_suite(Config0) of
+ {skip, _} = SKIP ->
+ SKIP;
+
+ Config1 when is_list(Config1) ->
+
+ ?P("init_per_suite -> end when "
+ "~n Config: ~p", [Config1]),
+
+ Config1
+ end.
+end_per_suite(Config0) ->
-init_per_suite(Config) ->
- Config.
+ ?P("end_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
-end_per_suite(_Config) ->
- ok.
+ Config1 = ?LIB:end_per_suite(Config0),
-init_per_group(t_local, Config) ->
- case gen_tcp:connect({local,<<"/">>}, 0, []) of
- {error,eafnosupport} ->
- {skip, "AF_LOCAL not supported"};
- {error,_} ->
- Config
+ ?P("end_per_suite -> "
+ "~n Nodes: ~p", [erlang:nodes()]),
+
+ Config1.
+
+
+init_per_group(inet_backend_default = _GroupName, Config) ->
+ [{socket_create_opts, []} | Config];
+init_per_group(inet_backend_inet = _GroupName, Config) ->
+ case ?EXPLICIT_INET_BACKEND() of
+ true ->
+ %% The environment trumps us,
+ %% so only the default group should be run!
+ {skip, "explicit inet backend"};
+ false ->
+ [{socket_create_opts, [{inet_backend, inet}]} | Config]
+ end;
+init_per_group(inet_backend_socket = _GroupName, Config) ->
+ case ?EXPLICIT_INET_BACKEND() of
+ true ->
+ %% The environment trumps us,
+ %% so only the default group should be run!
+ {skip, "explicit inet backend"};
+ false ->
+ [{socket_create_opts, [{inet_backend, socket}]} | Config]
+ end;
+init_per_group(t_local = _GroupName, Config) ->
+ %% A specific inet-backend can be enabled by the environment
+ case lists:keysearch(socket_create_opts, 1, Config) of
+ {value, {socket_create_opts, []}} ->
+ %% Default
+ %% Currently, default is inet, so unless the user has set
+ %% the environment variable ERL_FLAGS to use socket, we
+ %% use inet.
+ case application:get_all_env(kernel) of
+ Env when is_list(Env) ->
+ case lists:keysearch(inet_backend, 1, Env) of
+ {value, {inet_backend, socket}} ->
+ try is_local_socket_supported() of
+ true ->
+ Config;
+ false ->
+ {skip, "AF_LOCAL not supported"}
+ catch
+ _:_:_ ->
+ {skip, "AF_LOCAL not supported"}
+ end;
+ _ ->
+ try is_local_inet_supported() of
+ true ->
+ Config;
+ false ->
+ {skip, "AF_LOCAL not supported"}
+ catch
+ _:_:_ ->
+ {skip, "AF_LOCAL not supported"}
+ end
+ end;
+ _ ->
+ try is_local_inet_supported() of
+ true ->
+ Config;
+ false ->
+ {skip, "AF_LOCAL not supported"}
+ catch
+ _:_:_ ->
+ {skip, "AF_LOCAL not supported"}
+ end
+ end;
+
+ {value, {socket_create_opts, [{inet_backend, inet}]}} ->
+ try is_local_inet_supported() of
+ true ->
+ Config;
+ false ->
+ {skip, "AF_LOCAL not supported"}
+ catch
+ _:_:_ ->
+ {skip, "AF_LOCAL not supported"}
+ end;
+
+ {value, {socket_create_opts, [{inet_backend, socket}]}} ->
+ try is_local_socket_supported() of
+ true ->
+ Config;
+ false ->
+ {skip, "AF_LOCAL not supported"}
+ catch
+ _:_:_ ->
+ {skip, "AF_LOCAL not supported"}
+ end
end;
init_per_group(_GroupName, Config) ->
Config.
+
+is_local_socket_supported() ->
+ socket:is_supported(local).
+
+is_local_inet_supported() ->
+ case gen_tcp:connect({local,<<"/">>}, 0, []) of
+ {error, eafnosupport} ->
+ false;
+ {error,_} ->
+ true
+ end.
+
end_per_group(t_local, _Config) ->
delete_local_filenames();
end_per_group(_, _Config) ->
@@ -107,7 +318,7 @@ end_per_testcase(_Func, _Config) ->
%% Test that gen_tcp:accept/2 (with timeout) works.
t_accept_timeout(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, []),
+ {ok, L} = gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config)),
timeout({gen_tcp, accept, [L, 200]}, 0.2, 1.0).
%%% gen_tcp:connect/X
@@ -115,22 +326,27 @@ t_accept_timeout(Config) when is_list(Config) ->
%% Test that gen_tcp:connect/4 (with timeout) works.
t_connect_timeout(Config) when is_list(Config) ->
+ ?TC_TRY(t_connect_timeout, fun() -> do_connect_timeout(Config) end).
+
+do_connect_timeout(Config)->
%%BadAddr = {134,138,177,16},
%%TcpPort = 80,
{ok, BadAddr} = unused_ip(),
TcpPort = 45638,
- ok = io:format("Connecting to ~p, port ~p", [BadAddr, TcpPort]),
- connect_timeout({gen_tcp,connect,[BadAddr,TcpPort,[],200]}, 0.2, 5.0).
+ ok = ?P("Connecting to ~p, port ~p", [BadAddr, TcpPort]),
+ connect_timeout({gen_tcp,connect, [BadAddr,TcpPort, ?INET_BACKEND_OPTS(Config),200]}, 0.2, 5.0).
%% Test that gen_tcp:connect/3 handles non-existings hosts, and other
%% invalid things.
t_connect_bad(Config) when is_list(Config) ->
NonExistingPort = 45638, % Not in use, I hope.
- {error, Reason1} = gen_tcp:connect(localhost, NonExistingPort, []),
+ {error, Reason1} = gen_tcp:connect(localhost, NonExistingPort,
+ ?INET_BACKEND_OPTS(Config)),
io:format("Error for connection attempt to port not in use: ~p",
[Reason1]),
- {error, Reason2} = gen_tcp:connect("non-existing-host-xxx", 7, []),
+ {error, Reason2} = gen_tcp:connect("non-existing-host-xxx", 7,
+ ?INET_BACKEND_OPTS(Config)),
io:format("Error for connection attempt to non-existing host: ~p",
[Reason2]),
ok.
@@ -141,17 +357,21 @@ t_connect_bad(Config) when is_list(Config) ->
%% Test that gen_tcp:recv/3 (with timeout works).
t_recv_timeout(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, []),
+ {ok, L} = gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config)),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ {ok, Client} = gen_tcp:connect(localhost, Port,
+ ?INET_BACKEND_OPTS(Config) ++
+ [{active, false}]),
{ok, _A} = gen_tcp:accept(L),
timeout({gen_tcp, recv, [Client, 0, 200]}, 0.2, 5.0).
%% Test that end of file on a socket is reported correctly.
t_recv_eof(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, []),
+ {ok, L} = gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config)),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ {ok, Client} = gen_tcp:connect(localhost, Port,
+ ?INET_BACKEND_OPTS(Config) ++
+ [{active, false}]),
{ok, A} = gen_tcp:accept(L),
ok = gen_tcp:close(A),
{error, closed} = gen_tcp:recv(Client, 0),
@@ -159,68 +379,110 @@ t_recv_eof(Config) when is_list(Config) ->
%% Test using message delimiter $X.
t_recv_delim(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, []),
+ ?TC_TRY(t_recv_delim, fun() -> do_recv_delim(Config) end).
+
+do_recv_delim(Config) ->
+ ?P("init"),
+ {ok, L} = gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config)),
{ok, Port} = inet:port(L),
- Opts = [{active,false},{packet,line},{line_delimiter,$X}],
+ Opts = ?INET_BACKEND_OPTS(Config) ++
+ [{active,false}, {packet,line}, {line_delimiter,$X}],
{ok, Client} = gen_tcp:connect(localhost, Port, Opts),
{ok, A} = gen_tcp:accept(L),
+ ?P("send the data"),
ok = gen_tcp:send(A, "abcXefgX"),
- {ok, "abcX"} = gen_tcp:recv(Client, 0, 200),
- {ok, "efgX"} = gen_tcp:recv(Client, 0, 200),
+ %% Why do we need a timeout?
+ %% Sure, normally there would be no delay,
+ %% but this testcase has nothing to do with timeouts?
+ ?P("read the first chunk"),
+ {ok, "abcX"} = gen_tcp:recv(Client, 0), %, 200),
+ ?P("read the first chunk"),
+ {ok, "efgX"} = gen_tcp:recv(Client, 0), %, 200),
+ ?P("cleanup"),
ok = gen_tcp:close(Client),
ok = gen_tcp:close(A),
+ ?P("done"),
ok.
%%% gen_tcp:shutdown/2
t_shutdown_write(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, []),
+ {ok, L} = gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config)),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ {ok, Client} = gen_tcp:connect(localhost, Port,
+ ?INET_BACKEND_OPTS(Config) ++
+ [{active, false}]),
{ok, A} = gen_tcp:accept(L),
ok = gen_tcp:shutdown(A, write),
{error, closed} = gen_tcp:recv(Client, 0),
ok.
t_shutdown_both(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, []),
+ {ok, L} = gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config)),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ {ok, Client} = gen_tcp:connect(localhost, Port,
+ ?INET_BACKEND_OPTS(Config) ++
+ [{active, false}]),
{ok, A} = gen_tcp:accept(L),
ok = gen_tcp:shutdown(A, read_write),
{error, closed} = gen_tcp:recv(Client, 0),
ok.
t_shutdown_error(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, []),
+ ?TC_TRY(t_shutdown_error, fun() -> do_shutdown_error(Config) end).
+
+do_shutdown_error(Config) ->
+ ?P("create listen socket"),
+ {ok, L} = gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config)),
+ ?P("shutdown socket (with How = read_write)"),
{error, enotconn} = gen_tcp:shutdown(L, read_write),
+ ?P("close socket"),
ok = gen_tcp:close(L),
+ ?P("shutdown socket again (with How = read_write)"),
{error, closed} = gen_tcp:shutdown(L, read_write),
+ ?P("done"),
ok.
t_shutdown_async(Config) when is_list(Config) ->
+ ?TC_TRY(t_shutdown_async, fun() -> do_shutdown_async(Config) end).
+
+do_shutdown_async(Config) ->
{OS, _} = os:type(),
- {ok, L} = gen_tcp:listen(0, [{sndbuf, 4096}]),
+ ?P("create listen socket"),
+ {ok, L} = gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config) ++ [{sndbuf, 4096}]),
{ok, Port} = inet:port(L),
+ ?P("connect"),
{ok, Client} = gen_tcp:connect(localhost, Port,
- [{recbuf, 4096},
- {active, false}]),
+ ?INET_BACKEND_OPTS(Config) ++
+ [{recbuf, 4096},
+ {active, false}]),
+ ?P("accept connection"),
{ok, S} = gen_tcp:accept(L),
+ ?P("create payload"),
PayloadSize = 1024 * 1024,
Payload = lists:duplicate(PayloadSize, $.),
+ ?P("send payload"),
ok = gen_tcp:send(S, Payload),
+ ?P("verify queue size"),
case erlang:port_info(S, queue_size) of
{queue_size, N} when N > 0 -> ok;
{queue_size, 0} when OS =:= win32 -> ok;
{queue_size, 0} = T -> ct:fail({unexpected, T})
end,
+ ?P("shutdown(write) accepted socket"),
ok = gen_tcp:shutdown(S, write),
+ ?P("recv from connected socket"),
{ok, Buf} = gen_tcp:recv(Client, PayloadSize),
+ ?P("recv(0) from connected socket (expect closed)"),
{error, closed} = gen_tcp:recv(Client, 0),
+ ?P("verify recv data"),
case length(Buf) of
- PayloadSize -> ok;
- Sz -> ct:fail({payload_size,
+ PayloadSize -> ?P("done"), ok;
+ Sz -> ?P("ERROR: "
+ "~n extected: ~p"
+ "~n received: ~p", [PayloadSize, Sz]),
+ ct:fail({payload_size,
{expected, PayloadSize},
{received, Sz}})
end.
@@ -229,34 +491,50 @@ t_shutdown_async(Config) when is_list(Config) ->
%%% gen_tcp:fdopen/2
t_fdopen(Config) when is_list(Config) ->
- Question = "Aaaa... Long time ago in a small town in Germany,",
+ Question = "Aaaa... Long time ago in a small town in Germany,",
Question1 = list_to_binary(Question),
Question2 = [<<"Aaaa">>, "... ", $L, <<>>, $o, "ng time ago ",
["in ", [], <<"a small town">>, [" in Germany,", <<>>]]],
Question1 = iolist_to_binary(Question2),
- Answer = "there was a shoemaker, Schumacher was his name.",
- {ok, L} = gen_tcp:listen(0, [{active, false}]),
- {ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
- {ok, A} = gen_tcp:accept(L),
- {ok, FD} = prim_inet:getfd(A),
- {ok, Server} = gen_tcp:fdopen(FD, []),
- ok = gen_tcp:send(Client, Question),
- {ok, Question} = gen_tcp:recv(Server, length(Question), 2000),
- ok = gen_tcp:send(Client, Question1),
- {ok, Question} = gen_tcp:recv(Server, length(Question), 2000),
- ok = gen_tcp:send(Client, Question2),
- {ok, Question} = gen_tcp:recv(Server, length(Question), 2000),
- ok = gen_tcp:send(Server, Answer),
- {ok, Answer} = gen_tcp:recv(Client, length(Answer), 2000),
- ok = gen_tcp:close(Client),
- {error,closed} = gen_tcp:recv(A, 1, 2000),
- ok = gen_tcp:close(Server),
- ok = gen_tcp:close(A),
- ok = gen_tcp:close(L),
+ Answer = "there was a shoemaker, Schumacher was his name.",
+ {ok, L} = gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config) ++ [{active, false}]),
+ {ok, Port} = inet:port(L),
+ {ok, Client} = gen_tcp:connect(localhost, Port,
+ ?INET_BACKEND_OPTS(Config) ++
+ [{active, false}]),
+ {A, FD} = case gen_tcp:accept(L) of
+ {ok, ASock} when is_port(ASock) ->
+ {ok, FileDesc} = prim_inet:getfd(ASock),
+ {ASock, FileDesc};
+ {ok, ASock} -> % socket
+ {ok, [{fd, FileDesc}]} =
+ gen_tcp_socket:getopts(ASock, [fd]),
+ {ASock, FileDesc}
+ end,
+ ?P("fdopen -> accepted: "
+ "~n A: ~p"
+ "~n FD: ~p", [A, FD]),
+ {ok, Server} = gen_tcp:fdopen(FD, []),
+ ok = gen_tcp:send(Client, Question),
+ {ok, Question} = gen_tcp:recv(Server, length(Question), 2000),
+ ok = gen_tcp:send(Client, Question1),
+ {ok, Question} = gen_tcp:recv(Server, length(Question), 2000),
+ ok = gen_tcp:send(Client, Question2),
+ {ok, Question} = gen_tcp:recv(Server, length(Question), 2000),
+ ok = gen_tcp:send(Server, Answer),
+ {ok, Answer} = gen_tcp:recv(Client, length(Answer), 2000),
+ ok = gen_tcp:close(Client),
+ {error, closed} = gen_tcp:recv(A, 1, 2000),
+ ok = gen_tcp:close(Server),
+ ok = gen_tcp:close(A),
+ ok = gen_tcp:close(L),
ok.
+
t_fdconnect(Config) when is_list(Config) ->
+ ?TC_TRY(t_fdconnect, fun() -> do_t_fdconnect(Config) end).
+
+do_t_fdconnect(Config) ->
Question = "Aaaa... Long time ago in a small town in Germany,",
Question1 = list_to_binary(Question),
Question2 = [<<"Aaaa">>, "... ", $L, <<>>, $o, "ng time ago ",
@@ -265,13 +543,54 @@ t_fdconnect(Config) when is_list(Config) ->
Answer = "there was a shoemaker, Schumacher was his name.",
Path = proplists:get_value(data_dir, Config),
Lib = "gen_tcp_api_SUITE",
- ok = erlang:load_nif(filename:join(Path,Lib), []),
- {ok, L} = gen_tcp:listen(0, [{active, false}]),
+ ?P("try load util nif lib"),
+ case erlang:load_nif(filename:join(Path, Lib), []) of
+ ok ->
+ ok;
+ {error, {reload, ReasonStr}} ->
+ ?P("already loaded: "
+ "~n ~s", [ReasonStr]),
+ ok;
+ {error, Reason} ->
+ ?P("UNEXPECTED - failed loading util nif lib: "
+ "~n ~p", [Reason]),
+ ?SKIPT("failed loading util nif lib")
+ end,
+ ?P("try create listen socket"),
+ L = case gen_tcp:listen(0,
+ ?INET_BACKEND_OPTS(Config) ++ [{active, false}]) of
+ {ok, LSock} ->
+ LSock;
+ {error, eaddrnotavail = LReason} ->
+ ?SKIPT(listen_failed_str(LReason))
+ end,
{ok, Port} = inet:port(L),
+ ?P("try create file descriptor"),
FD = gen_tcp_api_SUITE:getsockfd(),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{fd,FD},{port,20002},
- {active,false}]),
- {ok, Server} = gen_tcp:accept(L),
+ ?P("try connect using file descriptor (~w)", [FD]),
+ Client = case gen_tcp:connect(localhost, Port,
+ ?INET_BACKEND_OPTS(Config) ++
+ [{fd, FD},
+ {port, 20002},
+ {active, false}]) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = CReason} ->
+ gen_tcp:close(L),
+ gen_tcp_api_SUITE:closesockfd(FD),
+ ?SKIPT(connect_failed_str(CReason))
+ end,
+ ?P("try accept connection"),
+ Server = case gen_tcp:accept(L) of
+ {ok, ASock} ->
+ ASock;
+ {error, eaddrnotavail = AReason} ->
+ gen_tcp:close(Client),
+ gen_tcp:close(L),
+ gen_tcp_api_SUITE:closesockfd(FD),
+ ?SKIPT(accept_failed_str(AReason))
+ end,
+ ?P("begin validation"),
ok = gen_tcp:send(Client, Question),
{ok, Question} = gen_tcp:recv(Server, length(Question), 2000),
ok = gen_tcp:send(Client, Question1),
@@ -285,48 +604,82 @@ t_fdconnect(Config) when is_list(Config) ->
{error,closed} = gen_tcp:recv(Server, 1, 2000),
ok = gen_tcp:close(Server),
ok = gen_tcp:close(L),
+ ?P("done"),
ok.
%%% implicit inet6 option to api functions
t_implicit_inet6(Config) when is_list(Config) ->
+ ?TC_TRY(t_implicit_inet6, fun() -> do_t_implicit_inet6(Config) end).
+
+do_t_implicit_inet6(Config) ->
+ ?P("try get hostname"),
Host = ok(inet:gethostname()),
+ ?P("try get address for host ~p", [Host]),
case inet:getaddr(Host, inet6) of
- {ok,Addr} ->
- t_implicit_inet6(Host, Addr);
- {error,Reason} ->
+ {ok, Addr} ->
+ ?P("address: ~p", [Addr]),
+ t_implicit_inet6(Config, Host, Addr);
+ {error, Reason} ->
{skip,
"Can not look up IPv6 address: "
++atom_to_list(Reason)}
end.
-t_implicit_inet6(Host, Addr) ->
+t_implicit_inet6(Config, Host, Addr) ->
Loopback = {0,0,0,0,0,0,0,1},
- case gen_tcp:listen(0, [inet6, {ip,Loopback}]) of
- {ok,S1} ->
- io:format("~s ~p~n", ["::1",Loopback]),
- implicit_inet6(S1, Loopback),
+ InetBackendOpts = ?INET_BACKEND_OPTS(Config),
+ case gen_tcp:listen(0, InetBackendOpts ++ [inet6, {ip,Loopback}]) of
+ {ok, S1} ->
+ ?P("try ~s ~p", ["::1", Loopback]),
+ implicit_inet6(Config, S1, Loopback),
ok = gen_tcp:close(S1),
%%
- Localaddr = ok(get_localaddr()),
- S2 = ok(gen_tcp:listen(0, [{ip,Localaddr}])),
- implicit_inet6(S2, Localaddr),
+ LocalAddr = ok(get_localaddr()),
+ S2 = case gen_tcp:listen(0, InetBackendOpts ++ [{ip, LocalAddr}]) of
+ {ok, LSock2} ->
+ LSock2;
+ {error, Reason2} ->
+ ?P("Listen failed (ip):"
+ "~n Reason2: ~p", [Reason2]),
+ ?SKIPT(listen_failed_str(Reason2))
+ end,
+ implicit_inet6(Config, S2, LocalAddr),
ok = gen_tcp:close(S2),
%%
- io:format("~s ~p~n", [Host,Addr]),
- S3 = ok(gen_tcp:listen(0, [{ifaddr,Addr}])),
- implicit_inet6(S3, Addr),
- ok = gen_tcp:close(S3);
- {error,_} ->
- {skip,"IPv6 not supported"}
+ ?P("try ~s ~p", [Host, Addr]),
+ S3 = case gen_tcp:listen(0, InetBackendOpts ++ [{ifaddr,Addr}]) of
+ {ok, LSock3} ->
+ LSock3;
+ {error, Reason3} ->
+ ?P("Listen failed (ifaddr):"
+ "~n Reason3: ~p", [Reason3]),
+ ?SKIPT(listen_failed_str(Reason3))
+ end,
+ implicit_inet6(Config, S3, Addr),
+ ok = gen_tcp:close(S3),
+ ?P("done"),
+ ok;
+ {error, Reason1} ->
+ ?SKIPT(listen_failed_str(Reason1))
end.
-implicit_inet6(S, Addr) ->
+implicit_inet6(Config, S, Addr) ->
P = ok(inet:port(S)),
- S2 = ok(gen_tcp:connect(Addr, P, [])),
+ S2 = case gen_tcp:connect(Addr, P, ?INET_BACKEND_OPTS(Config)) of
+ {ok, CSock} ->
+ CSock;
+ {error, CReason} ->
+ ?SKIPT(connect_failed_str(CReason))
+ end,
P2 = ok(inet:port(S2)),
- S1 = ok(gen_tcp:accept(S)),
+ S1 = case gen_tcp:accept(S) of
+ {ok, ASock} ->
+ ASock;
+ {error, AReason} ->
+ ?SKIPT(accept_failed_str(AReason))
+ end,
P1 = P = ok(inet:port(S1)),
{Addr,P2} = ok(inet:peername(S1)),
{Addr,P1} = ok(inet:peername(S2)),
@@ -336,80 +689,156 @@ implicit_inet6(S, Addr) ->
ok = gen_tcp:close(S1).
-
-t_local_basic(_Config) ->
+t_local_basic(Config) ->
SFile = local_filename(server),
- SAddr = {local,bin_filename(SFile)},
+ SAddr = {local, bin_filename(SFile)},
CFile = local_filename(client),
CAddr = {local,bin_filename(CFile)},
_ = file:delete(SFile),
_ = file:delete(CFile),
%%
+ ?P("try create listen socket"),
+ InetBackendOpts = ?INET_BACKEND_OPTS(Config),
L =
ok(
- gen_tcp:listen(0, [{ifaddr,{local,SFile}},{active,false}])),
+ gen_tcp:listen(0, InetBackendOpts ++
+ [{ifaddr,{local,SFile}},{active,false}])),
+ ?P("try connect"),
C =
ok(
gen_tcp:connect(
- {local,SFile}, 0, [{ifaddr,{local,CFile}},{active,false}])),
+ {local,SFile}, 0, InetBackendOpts ++
+ [{ifaddr,{local,CFile}},{active,false}])),
+ ?P("try accept connection"),
S = ok(gen_tcp:accept(L)),
- SAddr = ok(inet:sockname(L)),
- {error,enotconn} = inet:peername(L),
+ ?P("try get sockname for listen socket"),
+ %% SAddr = ok(inet:sockname(L)),
+ case inet:sockname(L) of
+ {ok, SAddr} ->
+ ok;
+ {ok, SAddr2} ->
+ ?P("Invalid sockname: "
+ "~n Expected: ~p"
+ "~n Actual: ~p", [SAddr, SAddr2]),
+ exit({sockename, SAddr, SAddr2});
+ {error, Reason} ->
+ exit({sockname, Reason})
+ end,
+ ?P("try get peername for listen socket"),
+ {error, enotconn} = inet:peername(L),
+ ?P("try handshake"),
local_handshake(S, SAddr, C, CAddr),
+ ?P("try close listen socket"),
ok = gen_tcp:close(L),
+ ?P("try close accept socket"),
ok = gen_tcp:close(S),
+ ?P("try close connect socket"),
ok = gen_tcp:close(C),
%%
+ ?P("try 'local' files"),
ok = file:delete(SFile),
ok = file:delete(CFile),
+ ?P("done"),
ok.
-t_local_unbound(_Config) ->
+
+t_local_unbound(Config) ->
+ ?TC_TRY(t_local_unbound, fun() -> do_local_unbound(Config) end).
+
+do_local_unbound(Config) ->
+ ?P("create local (server) filename"),
SFile = local_filename(server),
SAddr = {local,bin_filename(SFile)},
_ = file:delete(SFile),
%%
- L = ok(gen_tcp:listen(0, [{ifaddr,SAddr},{active,false}])),
- C = ok(gen_tcp:connect(SAddr, 0, [{active,false}])),
+ InetBackendOpts = ?INET_BACKEND_OPTS(Config),
+ ?P("create listen socket with ifaddr ~p", [SAddr]),
+ L = ok(gen_tcp:listen(0, InetBackendOpts ++
+ [{ifaddr,SAddr},{active,false}])),
+ ?P("listen socket created: ~p"
+ "~n => try connect", [L]),
+ C = ok(gen_tcp:connect(SAddr, 0,
+ InetBackendOpts ++ [{active,false}])),
+ ?P("connected: ~p"
+ "~n => try accept", [C]),
S = ok(gen_tcp:accept(L)),
+ ?P("accepted: ~p"
+ "~n => sockname", [S]),
SAddr = ok(inet:sockname(L)),
- {error,enotconn} = inet:peername(L),
+ ?P("sockname: ~p"
+ "~n => peername (expect enotconn)", [SAddr]),
+ {error, enotconn} = inet:peername(L),
+ ?P("try local handshake"),
local_handshake(S, SAddr, C, {local,<<>>}),
+ ?P("close listen socket"),
ok = gen_tcp:close(L),
+ ?P("close accepted socket"),
ok = gen_tcp:close(S),
+ ?P("close connected socket"),
ok = gen_tcp:close(C),
+ ?P("delete (local) file"),
ok = file:delete(SFile),
+ ?P("done"),
ok.
-t_local_fdopen(_Config) ->
+
+t_local_fdopen(Config) ->
+ ?TC_TRY(t_local_fdopen, fun() -> do_local_fdopen(Config) end).
+
+do_local_fdopen(Config) ->
+ ?P("create local (server) filename"),
SFile = local_filename(server),
SAddr = {local,bin_filename(SFile)},
_ = file:delete(SFile),
%%
- L = ok(gen_tcp:listen(0, [{ifaddr,SAddr},{active,false}])),
- C0 = ok(gen_tcp:connect(SAddr, 0, [{active,false}])),
+ InetBackendOpts = ?INET_BACKEND_OPTS(Config),
+ ?P("create listen socket with ifaddr ~p", [SAddr]),
+ L = ok(gen_tcp:listen(0, InetBackendOpts ++ [{ifaddr,SAddr},{active,false}])),
+ ?P("listen socket created: ~p"
+ "~n => try connect", [L]),
+ C0 = ok(gen_tcp:connect(SAddr, 0, InetBackendOpts ++ [{active,false}])),
+ ?P("connected: ~p"
+ "~n => get fd", [C0]),
Fd = ok(prim_inet:getfd(C0)),
+ ?P("FD: ~p"
+ "~n => ignore fd", [Fd]),
ok = prim_inet:ignorefd(C0, true),
+ ?P("ignored fd:"
+ "~n => try fdopen (local)"),
C = ok(gen_tcp:fdopen(Fd, [local])),
+ ?P("fd open: ~p"
+ "~n => try accept", [C]),
S = ok(gen_tcp:accept(L)),
+ ?P("accepted: ~p"
+ "~n => sockname", [S]),
SAddr = ok(inet:sockname(L)),
+ ?P("sockname: ~p"
+ "~n => peername (expect enotconn)", [SAddr]),
{error,enotconn} = inet:peername(L),
+ ?P("try local handshake"),
local_handshake(S, SAddr, C, {local,<<>>}),
+ ?P("close listen socket"),
ok = gen_tcp:close(L),
+ ?P("close accepted socket"),
ok = gen_tcp:close(S),
+ ?P("close connected socket (final)"),
ok = gen_tcp:close(C),
+ ?P("close connected socket (pre)"),
ok = gen_tcp:close(C0),
+ ?P("delete (local) file"),
ok = file:delete(SFile),
+ ?P("done"),
ok.
-t_local_fdopen_listen(_Config) ->
+t_local_fdopen_listen(Config) ->
SFile = local_filename(server),
SAddr = {local,bin_filename(SFile)},
_ = file:delete(SFile),
- L0 = ok(gen_tcp:listen(0, [{ifaddr,SAddr},{active,false}])),
+ InetBackendOpts = ?INET_BACKEND_OPTS(Config),
+ L0 = ok(gen_tcp:listen(0, InetBackendOpts ++ [{ifaddr,SAddr},{active,false}])),
Fd = ok(prim_inet:getfd(L0)),
- L = ok(gen_tcp:listen(0, [{fd,Fd},local,{active,false}])),
- C = ok(gen_tcp:connect(SAddr, 0, [{active,false}])),
+ L = ok(gen_tcp:listen(0, InetBackendOpts ++ [{fd,Fd},local,{active,false}])),
+ C = ok(gen_tcp:connect(SAddr, 0, InetBackendOpts ++ [{active,false}])),
S = ok(gen_tcp:accept(L)),
SAddr = ok(inet:sockname(L)),
{error,enotconn} = inet:peername(L),
@@ -421,16 +850,17 @@ t_local_fdopen_listen(_Config) ->
ok = file:delete(SFile),
ok.
-t_local_fdopen_listen_unbound(_Config) ->
+t_local_fdopen_listen_unbound(Config) ->
SFile = local_filename(server),
SAddr = {local,bin_filename(SFile)},
_ = file:delete(SFile),
P = ok(prim_inet:open(tcp, local, stream)),
Fd = ok(prim_inet:getfd(P)),
+ InetBackendOpts = ?INET_BACKEND_OPTS(Config),
L =
ok(gen_tcp:listen(
- 0, [{fd,Fd},{ifaddr,SAddr},{active,false}])),
- C = ok(gen_tcp:connect(SAddr, 0, [{active,false}])),
+ 0, InetBackendOpts ++ [{fd,Fd},{ifaddr,SAddr},{active,false}])),
+ C = ok(gen_tcp:connect(SAddr, 0, InetBackendOpts ++ [{active,false}])),
S = ok(gen_tcp:accept(L)),
SAddr = ok(inet:sockname(L)),
{error,enotconn} = inet:peername(L),
@@ -442,19 +872,21 @@ t_local_fdopen_listen_unbound(_Config) ->
ok = file:delete(SFile),
ok.
-t_local_fdopen_connect(_Config) ->
+t_local_fdopen_connect(Config) ->
SFile = local_filename(server),
SAddr = {local,bin_filename(SFile)},
CFile = local_filename(client),
CAddr = {local,bin_filename(CFile)},
_ = file:delete(SFile),
_ = file:delete(CFile),
- L = ok(gen_tcp:listen(0, [{ifaddr,SAddr},{active,false}])),
+ InetBackendOpts = ?INET_BACKEND_OPTS(Config),
+ L = ok(gen_tcp:listen(0, InetBackendOpts ++ [{ifaddr,SAddr},{active,false}])),
P = ok(prim_inet:open(tcp, local, stream)),
Fd = ok(prim_inet:getfd(P)),
C =
ok(gen_tcp:connect(
- SAddr, 0, [{fd,Fd},{ifaddr,CAddr},{active,false}])),
+ SAddr, 0, InetBackendOpts ++
+ [{fd,Fd},{ifaddr,CAddr},{active,false}])),
S = ok(gen_tcp:accept(L)),
SAddr = ok(inet:sockname(L)),
{error,enotconn} = inet:peername(L),
@@ -466,14 +898,15 @@ t_local_fdopen_connect(_Config) ->
ok = file:delete(SFile),
ok.
-t_local_fdopen_connect_unbound(_Config) ->
+t_local_fdopen_connect_unbound(Config) ->
SFile = local_filename(server),
SAddr = {local,bin_filename(SFile)},
_ = file:delete(SFile),
- L = ok(gen_tcp:listen(0, [{ifaddr,SAddr},{active,false}])),
+ InetBackendOpts = ?INET_BACKEND_OPTS(Config),
+ L = ok(gen_tcp:listen(0, InetBackendOpts ++ [{ifaddr,SAddr},{active,false}])),
P = ok(prim_inet:open(tcp, local, stream)),
Fd = ok(prim_inet:getfd(P)),
- C = ok(gen_tcp:connect(SAddr, 0, [{fd,Fd},{active,false}])),
+ C = ok(gen_tcp:connect(SAddr, 0, InetBackendOpts ++ [{fd,Fd},{active,false}])),
S = ok(gen_tcp:accept(L)),
SAddr = ok(inet:sockname(L)),
{error,enotconn} = inet:peername(L),
@@ -485,26 +918,49 @@ t_local_fdopen_connect_unbound(_Config) ->
ok = file:delete(SFile),
ok.
-t_local_abstract(_Config) ->
+t_local_abstract(Config) ->
+ ?TC_TRY(t_local_abstract, fun() -> do_local_abstract(Config) end).
+
+do_local_abstract(Config) ->
+ ?P("only run on linux"),
case os:type() of
- {unix,linux} ->
+ {unix, linux} ->
AbstAddr = {local,<<>>},
+ InetBackendOpts = ?INET_BACKEND_OPTS(Config),
+ ?P("create listen socket"),
L =
ok(gen_tcp:listen(
- 0, [{ifaddr,AbstAddr},{active,false}])),
- {local,_} = SAddr = ok(inet:sockname(L)),
+ 0, InetBackendOpts ++ [{ifaddr,AbstAddr},{active,false}])),
+ ?P("listen socket created: ~p"
+ "~n => sockname", [L]),
+ {local, _} = SAddr = ok(inet:sockname(L)),
+ ?P("(listen socket) sockname verified"
+ "~n => try connect"),
C =
ok(gen_tcp:connect(
- SAddr, 0, [{ifaddr,AbstAddr},{active,false}])),
+ SAddr, 0,
+ InetBackendOpts ++ [{ifaddr,AbstAddr},{active,false}])),
+ ?P("connected: ~p"
+ "~n => sockname", [C]),
{local,_} = CAddr = ok(inet:sockname(C)),
+ ?P("(connected socket) sockname verified"
+ "~n => try accept"),
S = ok(gen_tcp:accept(L)),
+ ?P("accepted: ~p"
+ "~n => peername (expect enotconn)", [S]),
{error,enotconn} = inet:peername(L),
+ ?P("try local handshake"),
local_handshake(S, SAddr, C, CAddr),
+ ?P("close listen socket"),
ok = gen_tcp:close(L),
+ ?P("close accepted socket"),
ok = gen_tcp:close(S),
+ ?P("close connected socket"),
ok = gen_tcp:close(C),
+ ?P("done"),
ok;
_ ->
+ ?P("skip (unless linux)"),
{skip,"AF_LOCAL Abstract Addresses only supported on Linux"}
end.
@@ -523,25 +979,72 @@ local_handshake(S, SAddr, C, CAddr) ->
ok.
t_accept_inet6_tclass(Config) when is_list(Config) ->
+ ?TC_TRY(t_accept_inet6_tclass, fun() -> do_accept_inet6_tclass(Config) end).
+
+do_accept_inet6_tclass(Config) ->
TClassOpt = {tclass,8#56 bsl 2}, % Expedited forwarding
Loopback = {0,0,0,0,0,0,0,1},
- case gen_tcp:listen(0, [inet6, {ip, Loopback}, TClassOpt]) of
- {ok,L} ->
+ ?P("create listen socket with tclass: ~p", [TClassOpt]),
+ case gen_tcp:listen(0, ?INET_BACKEND_OPTS(Config) ++ [inet6, {ip, Loopback}, TClassOpt]) of
+ {ok, L} ->
+ ?P("listen socket created: "
+ "~n ~p", [L]),
LPort = ok(inet:port(L)),
- Sa = ok(gen_tcp:connect(Loopback, LPort, [])),
+ ?P("try to connect to port ~p", [LPort]),
+ Sa = ok(gen_tcp:connect(Loopback, LPort, ?INET_BACKEND_OPTS(Config))),
+ ?P("connected: ~p"
+ "~n => accept connection", [Sa]),
Sb = ok(gen_tcp:accept(L)),
+ ?P("accepted: ~p"
+ "~n => getopts (tclass)", [Sb]),
[TClassOpt] = ok(inet:getopts(Sb, [tclass])),
+ ?P("tclass verified => close accepted socket"),
ok = gen_tcp:close(Sb),
+ ?P("close connected socket"),
ok = gen_tcp:close(Sa),
+ ?P("close listen socket"),
ok = gen_tcp:close(L),
+ ?P("done"),
ok;
- {error,_} ->
+ {error, _Reason} ->
+ ?P("ERROR: Failed create listen socket"
+ "~n ~p", [_Reason]),
{skip,"IPv6 TCLASS not supported"}
end.
+%% On MacOS (maybe more), accepting a connection resulted in a crash.
+%% Note that since 'socket' currently does not work on windows
+%% we have to skip on that platform.
+s_accept_with_explicit_socket_backend(Config) when is_list(Config) ->
+ ?TC_TRY(s_accept_with_explicit_socket_backend,
+ fun() -> is_not_windows() end,
+ fun() -> do_s_accept_with_explicit_socket_backend() end).
+
+do_s_accept_with_explicit_socket_backend() ->
+ {ok, S} = gen_tcp:listen(0, [{inet_backend, socket}]),
+ {ok, {_, Port}} = inet:sockname(S),
+ ClientF = fun() ->
+ {ok, _} = gen_tcp:connect("localhost", Port, []),
+ receive die -> exit(normal) after infinity -> ok end
+ end,
+ Client = spawn_link(ClientF),
+ {ok, _} = gen_tcp:accept(S),
+ Client ! die,
+ ok.
+
+
%%% Utilities
+is_not_windows() ->
+ case os:type() of
+ {win32, _} ->
+ {skip, "Windows not supported"};
+ _ ->
+ ok
+ end.
+
+
%% Calls M:F/length(A), which should return a timeout error, and complete
%% within the given time.
@@ -561,11 +1064,13 @@ connect_timeout({M,F,A}, Lower, Upper) ->
case test_server:timecall(M, F, A) of
{Time, Result} when Time < Lower ->
case Result of
- {error,econnrefused=E} ->
- {comment,"Not tested -- got error "++atom_to_list(E)};
- {error,enetunreach=E} ->
- {comment,"Not tested -- got error "++atom_to_list(E)};
- {ok,Socket} -> % What the...
+ {error, econnrefused = E} ->
+ {skip, "Not tested -- got error " ++ atom_to_list(E)};
+ {error, enetunreach = E} ->
+ {skip, "Not tested -- got error " ++ atom_to_list(E)};
+ {error, ehostunreach = E} ->
+ {skip, "Not tested -- got error " ++ atom_to_list(E)};
+ {ok, Socket} -> % What the...
Pinfo = erlang:port_info(Socket),
Db = inet_db:lookup_socket(Socket),
Peer = inet:peername(Socket),
@@ -591,13 +1096,35 @@ unused_ip() ->
%% Note: In our net, addresses below 16 are reserved for routers and
%% other strange creatures.
IP = unused_ip(A, B, C, 16),
- io:format("we = ~p, unused_ip = ~p~n", [Hent, IP]),
+ if
+ (IP =:= error) ->
+ %% This is not supported on all platforms (yet), so...
+ try net:getifaddrs() of
+ {ok, IfAddrs} ->
+ ?P("~n we = ~p"
+ "~n unused_ip = ~p"
+ "~n ~p", [Hent, IP, IfAddrs]);
+ {error, _} ->
+ ?P("~n we: ~p"
+ "~n unused_ip: ~p", [Hent, IP])
+ catch
+ _:_:_ ->
+ ?P("~n we: ~p"
+ "~n unused_ip: ~p", [Hent, IP])
+ end;
+ true ->
+ ?P("~n we: ~p"
+ "~n unused_ip: ~p", [Hent, IP])
+ end,
IP.
-unused_ip(_, _, _, 255) -> error;
+unused_ip(255, 255, 255, 255) -> error;
+unused_ip(255, B, C, D) -> unused_ip(1, B + 1, C, D);
+unused_ip(A, 255, C, D) -> unused_ip(A, 1, C + 1, D);
+unused_ip(A, B, 255, D) -> unused_ip(A, B, 1, D + 1);
unused_ip(A, B, C, D) ->
case inet:gethostbyaddr({A, B, C, D}) of
- {ok, _} -> unused_ip(A, B, C, D+1);
+ {ok, _} -> unused_ip(A + 1, B, C, D);
{error, _} -> {ok, {A, B, C, D}}
end.
@@ -618,7 +1145,7 @@ get_localaddr([]) ->
get_localaddr([Localhost|Ls]) ->
case inet:getaddr(Localhost, inet6) of
{ok, LocalAddr} ->
- io:format("~s ~p~n", [Localhost, LocalAddr]),
+ ?P("~s ~p", [Localhost, LocalAddr]),
{ok, LocalAddr};
_ ->
get_localaddr(Ls)
@@ -640,3 +1167,17 @@ delete_local_filenames() ->
filelib:wildcard(
"/tmp/" ?MODULE_STRING "_" ++ os:getpid() ++ "_*")],
ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+connect_failed_str(Reason) ->
+ ?F("Connect failed: ~w", [Reason]).
+
+listen_failed_str(Reason) ->
+ ?F("Listen failed: ~w", [Reason]).
+
+accept_failed_str(Reason) ->
+ ?F("Accept failed: ~w", [Reason]).
+
+
diff --git a/lib/kernel/test/gen_tcp_echo_SUITE.erl b/lib/kernel/test/gen_tcp_echo_SUITE.erl
index 57525f8015..e2b17d374b 100644
--- a/lib/kernel/test/gen_tcp_echo_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_echo_SUITE.erl
@@ -212,6 +212,7 @@ echo_packet0(Echo, Type, EchoFun, SlowEcho, Opts) ->
{value,{packet_size,_}} -> 10;
false -> 0
end,
+ ct:log("echo_packet0[~w] ~p", [self(), PacketSize]),
%% Echo small packets first.
echo_packet1(Echo, Type, EchoFun, 0),
echo_packet1(Echo, Type, EchoFun, 1),
@@ -245,7 +246,18 @@ echo_packet0(Echo, Type, EchoFun, SlowEcho, Opts) ->
echo_packet1(Echo, Type, EchoFun, infinite);
true -> ok
end,
- gen_tcp:close(Echo),
+ PacketSize =:= 0 andalso
+ begin
+ %% Switch to raw mode and echo one byte
+ ok = inet:setopts(Echo, [{packet, raw}, {active, false}]),
+ ok = gen_tcp:send(Echo, <<"$">>),
+ case gen_tcp:recv(Echo, 1) of
+ {ok, <<"$">>} -> ok;
+ {ok, "$"} -> ok
+ end
+ end,
+ _CloseResult = gen_tcp:close(Echo),
+ ct:log("echo_packet0[~w] close: ~p", [self(), _CloseResult]),
ok.
echo_packet1(EchoSock, Type, EchoFun, Size) ->
@@ -294,11 +306,13 @@ active_recv(Sock, Type, [PacketEcho|Tail]) ->
{Tag, Sock, PacketEcho} ->
active_recv(Sock, Type, Tail);
{Tag, Sock, Bad} ->
- ct:fail({wrong_data, Bad, expected, PacketEcho});
+ ct:log("Packet: ~p", [inet:getopts(Sock, [packet])]),
+ ct:fail({wrong_data, Bad, PacketEcho});
{tcp_error, Sock, Reason} ->
{error, Reason};
Other ->
- ct:fail({unexpected_message, Other, Tag})
+ ct:log("Packet: ~p", [inet:getopts(Sock, [packet])]),
+ ct:fail({unexpected_message, Other, {Tag, Sock, PacketEcho}})
end.
passive_echo(Sock, _Type, Packet, PacketEchos) ->
@@ -315,7 +329,7 @@ passive_recv(Sock, [PacketEcho | Tail]) ->
passive_recv(Sock, Tail);
{ok, Bad} ->
io:format("Expected: ~p\nGot: ~p\n",[PacketEcho,Bad]),
- ct:fail({wrong_data, Bad});
+ ct:fail({wrong_data, Bad, PacketEcho});
{error,PacketEcho} ->
passive_recv(Sock, Tail); % expected error
{error, _}=Error ->
@@ -341,11 +355,13 @@ active_once_recv(Sock, Type, [PacketEcho | Tail]) ->
inet:setopts(Sock, [{active, once}]),
active_once_recv(Sock, Type, Tail);
{Tag, Sock, Bad} ->
- ct:fail({wrong_data, Bad});
+ ct:log("Packet: ~p", [inet:getopts(Sock, [packet])]),
+ ct:fail({wrong_data, Bad, PacketEcho});
{tcp_error, Sock, Reason} ->
{error, Reason};
Other ->
- ct:fail({unexpected_message, Other, expected, {Tag, Sock, PacketEcho}})
+ ct:log("Packet: ~p", [inet:getopts(Sock, [packet])]),
+ ct:fail({unexpected_message, Other, {Tag, Sock, PacketEcho}})
end.
%%% Building of random packets.
diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl
index cd1bc2e0d1..2c2725ad30 100644
--- a/lib/kernel/test/gen_tcp_misc_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
-module(gen_tcp_misc_SUITE).
-include_lib("common_test/include/ct.hrl").
+-include("kernel_test_lib.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
@@ -31,7 +32,7 @@
get_status/1,
passive_sockets/1, accept_closed_by_other_process/1,
init_per_testcase/2, end_per_testcase/2,
- otp_3924/1, otp_3924_sender/4, closed_socket/1,
+ otp_3924/1, closed_socket/1,
shutdown_active/1, shutdown_passive/1, shutdown_pending/1,
show_econnreset_active/1, show_econnreset_active_once/1,
show_econnreset_passive/1, econnreset_after_sync_send/1,
@@ -55,12 +56,18 @@
active_once_closed/1, send_timeout/1, send_timeout_active/1,
otp_7731/1, zombie_sockets/1, otp_7816/1, otp_8102/1,
wrapping_oct/0, wrapping_oct/1, otp_9389/1, otp_13939/1,
- otp_12242/1, delay_send_error/1]).
+ otp_12242/1, delay_send_error/1, bidirectional_traffic/1]).
%% Internal exports.
--export([sender/3, not_owner/1, passive_sockets_server/2, priority_server/1,
+-export([sender/4,
+ not_owner/1,
+ passive_sockets_server/3,
+ priority_server/2,
oct_acceptor/1,
- otp_7731_server/1, zombie_server/2, do_iter_max_socks/2]).
+ otp_3924_sender/5,
+ otp_7731_server/2,
+ zombie_server/3,
+ do_iter_max_socks/3]).
init_per_testcase(_Func, Config) ->
Config.
@@ -72,8 +79,43 @@ suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,4}}].
-all() ->
- [controlling_process, controlling_process_self, no_accept,
+all() ->
+ %% This is a temporary messure to ensure that we can
+ %% test the socket backend without effecting *all*
+ %% applications on *all* machines.
+ %% This flag is set only for *one* host.
+ case ?TEST_INET_BACKENDS() of
+ true ->
+ [
+ {group, inet_backend_default},
+ {group, inet_backend_inet},
+ {group, inet_backend_socket}
+ ];
+ _ ->
+ [
+ {group, inet_backend_default}
+ ]
+ end.
+
+groups() ->
+ [
+ {inet_backend_default, [], inet_backend_default_cases()},
+ {inet_backend_inet, [], inet_backend_inet_cases()},
+ {inet_backend_socket, [], inet_backend_socket_cases()}
+ ].
+
+inet_backend_default_cases() ->
+ all_cases().
+
+inet_backend_inet_cases() ->
+ all_cases().
+
+inet_backend_socket_cases() ->
+ all_cases().
+
+all_cases() ->
+ [
+ controlling_process, controlling_process_self, no_accept,
close_with_pending_output, data_before_close,
iter_max_socks, passive_sockets, active_n, active_n_closed,
accept_closed_by_other_process, otp_3924, closed_socket,
@@ -100,17 +142,60 @@ all() ->
active_once_closed, send_timeout, send_timeout_active, otp_7731,
wrapping_oct,
zombie_sockets, otp_7816, otp_8102, otp_9389,
- otp_12242, delay_send_error].
+ otp_12242, delay_send_error, bidirectional_traffic].
-groups() ->
- [].
-init_per_suite(Config) ->
- Config.
+init_per_suite(Config0) ->
-end_per_suite(_Config) ->
- ok.
+ ?P("init_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ case ?LIB:init_per_suite(Config0) of
+ {skip, _} = SKIP ->
+ SKIP;
+
+ Config1 when is_list(Config1) ->
+
+ ?P("init_per_suite -> end when "
+ "~n Config: ~p", [Config1]),
+
+ Config1
+ end.
+
+end_per_suite(Config0) ->
+
+ ?P("end_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ Config1 = ?LIB:end_per_suite(Config0),
+ ?P("end_per_suite -> "
+ "~n Nodes: ~p", [erlang:nodes()]),
+
+ Config1.
+
+init_per_group(inet_backend_default = _GroupName, Config) ->
+ [{socket_create_opts, []} | Config];
+init_per_group(inet_backend_inet = _GroupName, Config) ->
+ case ?EXPLICIT_INET_BACKEND() of
+ true ->
+ %% The environment trumps us,
+ %% so only the default group should be run!
+ {skip, "explicit inet backend"};
+ false ->
+ [{socket_create_opts, [{inet_backend, inet}]} | Config]
+ end;
+init_per_group(inet_backend_socket = _GroupName, Config) ->
+ case ?EXPLICIT_INET_BACKEND() of
+ true ->
+ %% The environment trumps us,
+ %% so only the default group should be run!
+ {skip, "explicit inet backend"};
+ false ->
+ [{socket_create_opts, [{inet_backend, socket}]} | Config]
+ end;
init_per_group(_GroupName, Config) ->
Config.
@@ -125,74 +210,78 @@ end_per_group(_GroupName, Config) ->
%% Tests kernel application variables inet_default_listen_options and
%% inet_default_connect_options.
default_options(Config) when is_list(Config) ->
+ ?TC_TRY(default_options, fun() -> do_default_options(Config) end).
+
+do_default_options(Config) ->
%% First check the delay_send option
- {true,true,true}=do_delay_send_1(),
- {false,false,false}=do_delay_send_2(),
- {true,false,false}=do_delay_send_3(),
- {false,false,false}=do_delay_send_4(),
- {false,false,false}=do_delay_send_5(),
- {false,true,true}=do_delay_send_6(),
+ {true,true,true}=do_delay_send_1(Config),
+ {false,false,false}=do_delay_send_2(Config),
+ {true,false,false}=do_delay_send_3(Config),
+ {false,false,false}=do_delay_send_4(Config),
+ {false,false,false}=do_delay_send_5(Config),
+ {false,true,true}=do_delay_send_6(Config),
%% Now lets start some nodes with different combinations of options:
- {true,true,true} = do_delay_on_other_node("", fun do_delay_send_1/0),
+ {true,true,true} =
+ do_delay_on_other_node("", fun() -> do_delay_send_1(Config) end),
{true,false,false} =
do_delay_on_other_node("-kernel inet_default_connect_options "
"\"[{delay_send,true}]\"",
- fun do_delay_send_2/0),
+ fun() -> do_delay_send_2(Config) end),
{false,true,true} =
do_delay_on_other_node("-kernel inet_default_listen_options "
"\"[{delay_send,true}]\"",
- fun do_delay_send_2/0),
+ fun() -> do_delay_send_2(Config) end),
{true,true,true} =
do_delay_on_other_node("-kernel inet_default_listen_options "
"\"[{delay_send,true}]\"",
- fun do_delay_send_3/0),
+ fun() -> do_delay_send_3(Config) end),
{true,true,true} =
do_delay_on_other_node("-kernel inet_default_connect_options "
"\"[{delay_send,true}]\"",
- fun do_delay_send_6/0),
+ fun() -> do_delay_send_6(Config) end),
{false,false,false} =
do_delay_on_other_node("-kernel inet_default_connect_options "
"\"[{delay_send,true}]\"",
- fun do_delay_send_5/0),
+ fun() -> do_delay_send_5(Config) end),
{false,true,true} =
do_delay_on_other_node("-kernel inet_default_connect_options "
"\"[{delay_send,true}]\" "
"-kernel inet_default_listen_options "
"\"[{delay_send,true}]\"",
- fun do_delay_send_5/0),
+ fun() -> do_delay_send_5(Config) end),
{true,false,false} =
do_delay_on_other_node("-kernel inet_default_connect_options "
"\"[{delay_send,true}]\" "
"-kernel inet_default_listen_options "
"\"[{delay_send,true}]\"",
- fun do_delay_send_4/0),
+ fun() -> do_delay_send_4(Config) end),
{true,true,true} =
do_delay_on_other_node("-kernel inet_default_connect_options "
"\"{delay_send,true}\" "
"-kernel inet_default_listen_options "
"\"{delay_send,true}\"",
- fun do_delay_send_2/0),
+ fun() -> do_delay_send_2(Config) end),
%% Active is to dangerous and is supressed
{true,true,true} =
do_delay_on_other_node("-kernel inet_default_connect_options "
"\"{active,false}\" "
"-kernel inet_default_listen_options "
"\"{active,false}\"",
- fun do_delay_send_7/0),
+ fun() -> do_delay_send_7(Config) end),
{true,true,true} =
do_delay_on_other_node("-kernel inet_default_connect_options "
"\"[{active,false},{delay_send,true}]\" "
"-kernel inet_default_listen_options "
"\"[{active,false},{delay_send,true}]\"",
- fun do_delay_send_7/0),
+ fun() -> do_delay_send_7(Config) end),
{true,true,true} =
do_delay_on_other_node("-kernel inet_default_connect_options "
"\"[{active,false},{delay_send,true}]\" "
"-kernel inet_default_listen_options "
"\"[{active,false},{delay_send,true}]\"",
- fun do_delay_send_2/0),
+ fun() -> do_delay_send_2(Config) end),
ok.
@@ -204,10 +293,15 @@ do_delay_on_other_node(XArgs, Function) ->
test_server:stop_node(Node),
Res.
-do_delay_send_1() ->
- {ok,LS}=gen_tcp:listen(0,[{delay_send,true}]),
+do_delay_send_1(Config) ->
+ {ok,LS} = ?LISTEN(Config, 0, [{delay_send,true}]),
{ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
- {ok,S}=gen_tcp:connect("localhost",PortNum,[{delay_send,true}]),
+ S = case ?CONNECT(Config, "localhost", PortNum, [{delay_send,true}]) of
+ {ok, Sock} ->
+ Sock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
{ok,S2}= gen_tcp:accept(LS),
{ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
{ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
@@ -217,23 +311,23 @@ do_delay_send_1() ->
gen_tcp:close(LS),
{B1,B2,B3}.
-do_delay_send_2() ->
- {ok,LS}=gen_tcp:listen(0,[]),
- {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
- {ok,S}=gen_tcp:connect("localhost",PortNum,[]),
- {ok,S2}= gen_tcp:accept(LS),
- {ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
- {ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
- {ok,[{delay_send,B3}]}=inet:getopts(S2,[delay_send]),
+do_delay_send_2(Config) ->
+ {ok, LS} = ?LISTEN(Config, 0, []),
+ {ok, {{0,0,0,0},PortNum}} = inet:sockname(LS),
+ {ok, S} = ?CONNECT(Config, "localhost",PortNum,[]),
+ {ok, S2} = gen_tcp:accept(LS),
+ {ok, [{delay_send,B1}]} = inet:getopts(S,[delay_send]),
+ {ok, [{delay_send,B2}]} = inet:getopts(LS,[delay_send]),
+ {ok, [{delay_send,B3}]} = inet:getopts(S2,[delay_send]),
gen_tcp:close(S2),
gen_tcp:close(S),
gen_tcp:close(LS),
{B1,B2,B3}.
-do_delay_send_3() ->
- {ok,LS}=gen_tcp:listen(0,[]),
+do_delay_send_3(Config) ->
+ {ok, LS} = ?LISTEN(Config, 0, []),
{ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
- {ok,S}=gen_tcp:connect("localhost",PortNum,[{delay_send,true}]),
+ {ok,S} = ?CONNECT(Config, "localhost", PortNum, [{delay_send,true}]),
{ok,S2}= gen_tcp:accept(LS),
{ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
{ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
@@ -243,10 +337,10 @@ do_delay_send_3() ->
gen_tcp:close(LS),
{B1,B2,B3}.
-do_delay_send_4() ->
- {ok,LS}=gen_tcp:listen(0,[{delay_send,false}]),
+do_delay_send_4(Config) ->
+ {ok,LS} = ?LISTEN(Config, 0, [{delay_send,false}]),
{ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
- {ok,S}=gen_tcp:connect("localhost",PortNum,[]),
+ {ok,S} = ?CONNECT(Config, "localhost", PortNum, []),
{ok,S2}= gen_tcp:accept(LS),
{ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
{ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
@@ -256,10 +350,10 @@ do_delay_send_4() ->
gen_tcp:close(LS),
{B1,B2,B3}.
-do_delay_send_5() ->
- {ok,LS}=gen_tcp:listen(0,[]),
+do_delay_send_5(Config) ->
+ {ok,LS} = ?LISTEN(Config, 0, []),
{ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
- {ok,S}=gen_tcp:connect("localhost",PortNum,[{delay_send,false}]),
+ {ok,S} = ?CONNECT(Config, "localhost",PortNum,[{delay_send,false}]),
{ok,S2}= gen_tcp:accept(LS),
{ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
{ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
@@ -269,10 +363,10 @@ do_delay_send_5() ->
gen_tcp:close(LS),
{B1,B2,B3}.
-do_delay_send_6() ->
- {ok,LS}=gen_tcp:listen(0,[{delay_send,true}]),
+do_delay_send_6(Config) ->
+ {ok,LS} = ?LISTEN(Config, 0, [{delay_send,true}]),
{ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
- {ok,S}=gen_tcp:connect("localhost",PortNum,[]),
+ {ok,S} = ?CONNECT(Config, "localhost", PortNum, []),
{ok,S2}= gen_tcp:accept(LS),
{ok,[{delay_send,B1}]}=inet:getopts(S,[delay_send]),
{ok,[{delay_send,B2}]}=inet:getopts(LS,[delay_send]),
@@ -282,11 +376,11 @@ do_delay_send_6() ->
gen_tcp:close(LS),
{B1,B2,B3}.
-do_delay_send_7() ->
- {ok,LS}=gen_tcp:listen(0,[]),
+do_delay_send_7(Config) ->
+ {ok,LS} = ?LISTEN(Config, 0, []),
{ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
- {ok,S}=gen_tcp:connect("localhost",PortNum,[]),
- {ok,S2}= gen_tcp:accept(LS),
+ {ok,S} = ?CONNECT(Config, "localhost", PortNum, []),
+ {ok,S2} = gen_tcp:accept(LS),
{ok,[{active,B1}]}=inet:getopts(S,[active]),
{ok,[{active,B2}]}=inet:getopts(LS,[active]),
{ok,[{active,B3}]}=inet:getopts(S2,[active]),
@@ -299,8 +393,8 @@ do_delay_send_7() ->
%% The result should be ok of done by the owner process,
%% Otherwise is should return {error,not_owner} or similar.
controlling_process(Config) when is_list(Config) ->
- {ok,S} = gen_tcp:listen(0,[]),
- Pid2 = spawn(?MODULE,not_owner,[S]),
+ {ok, S} = ?LISTEN(Config, 0,[]),
+ Pid2 = spawn(?MODULE, not_owner, [S]),
Pid2 ! {self(),2,control},
{error, E} = receive {2,_E} ->
_E
@@ -332,7 +426,7 @@ controlling_process_self(Config) when is_list(Config) ->
S = self(),
process_flag(trap_exit,true),
spawn_link(fun() ->
- {ok,Sock} = gen_tcp:listen(0,[]),
+ {ok,Sock} = ?LISTEN(Config, 0, []),
S ! {socket, Sock},
ok = gen_tcp:controlling_process(Sock,self()),
S ! done
@@ -343,7 +437,12 @@ controlling_process_self(Config) when is_list(Config) ->
{socket,Sock} ->
process_flag(trap_exit,false),
%% Make sure the port is invalid after process crash
- {error,einval} = inet:port(Sock)
+ receive after 500 -> ok end,
+ case inet:port(Sock) of
+ {error,einval} -> ok;
+ {error,closed} -> ok % XXX gen_tcp_socket
+ end
+
end;
Msg when element(1,Msg) /= socket ->
process_flag(trap_exit,false),
@@ -355,9 +454,9 @@ controlling_process_self(Config) when is_list(Config) ->
%% without doing any accept. The connected socket should receive
%% a tcp_closed message.
no_accept(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, []),
+ {ok, L} = ?LISTEN(Config, 0, []),
{ok, {_, Port}} = inet:sockname(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, []),
+ {ok, Client} = ?CONNECT(Config, localhost, Port, []),
ok = gen_tcp:close(L),
receive
{tcp_closed, Client} ->
@@ -370,14 +469,14 @@ no_accept(Config) when is_list(Config) ->
%% Send several packets to a socket and close it. All packets should
%% arrive to the other end.
close_with_pending_output(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, [binary, {active, false}]),
+ {ok, L} = ?LISTEN(Config, 0, [binary, {active, false}]),
{ok, {_, Port}} = inet:sockname(L),
Packets = 16,
Total = 2048*Packets,
case start_remote(close_pending) of
{ok, Node} ->
{ok, Host} = inet:gethostname(),
- spawn_link(Node, ?MODULE, sender, [Port, Packets, Host]),
+ spawn_link(Node, ?MODULE, sender, [Config, Port, Packets, Host]),
{ok, A} = gen_tcp:accept(L),
case gen_tcp:recv(A, Total) of
{ok, Bin} when byte_size(Bin) == Total ->
@@ -396,12 +495,12 @@ close_with_pending_output(Config) when is_list(Config) ->
ct:fail({failed_to_start_slave_node, Other})
end.
-sender(Port, Packets, Host) ->
+sender(Config, Port, Packets, Host) ->
X256 = lists:seq(0, 255),
X512 = [X256|X256],
X1K = [X512|X512],
Bin = list_to_binary([X1K|X1K]),
- {ok, Sock} = gen_tcp:connect(Host, Port, []),
+ {ok, Sock} = ?CONNECT(Config, Host, Port, []),
send_loop(Sock, Bin, Packets),
ok = gen_tcp:close(Sock).
@@ -413,8 +512,11 @@ send_loop(Sock, Data, Left) ->
%% Test {active,N} option
%% Verify operation of the {active,N} option.
active_n(Config) when is_list(Config) ->
+ ?TC_TRY(active_n, fun() -> do_active_n(Config) end).
+
+do_active_n(Config) ->
N = 3,
- LS = ok(gen_tcp:listen(0, [{active,N}])),
+ LS = ok(?LISTEN(Config, 0, [{active,N}])),
[{active,N}] = ok(inet:getopts(LS, [active])),
ok = inet:setopts(LS, [{active,-N}]),
receive
@@ -464,7 +566,12 @@ active_n(Config) when is_list(Config) ->
ok = inet:setopts(LS, [{active,false}]),
[{active,false}] = ok(inet:getopts(LS, [active])),
Port = ok(inet:port(LS)),
- C = ok(gen_tcp:connect("localhost", Port, [{active,N}])),
+ C = case ?CONNECT(Config, "localhost", Port, [{active,N}]) of
+ {ok, CS} ->
+ CS;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
[{active,N}] = ok(inet:getopts(C, [active])),
S = ok(gen_tcp:accept(LS)),
ok = inet:setopts(S, [{active,N}]),
@@ -502,7 +609,7 @@ active_n(Config) when is_list(Config) ->
5000 ->
exit({error,tcp_passive})
end,
- LS2 = ok(gen_tcp:listen(0, [{active,0}])),
+ LS2 = ok(?LISTEN(Config, 0, [{active,0}])),
receive
{tcp_passive,LS2} ->
[{active,false}] = ok(inet:getopts(LS2, [active]))
@@ -533,28 +640,28 @@ otp_3924(Config) when is_list(Config) ->
{_, true} -> 2;
_ -> 1
end * ?OTP_3924_MAX_DELAY),
- otp_3924_1(MaxDelay).
+ otp_3924_1(Config, MaxDelay).
-otp_3924_1(MaxDelay) ->
+otp_3924_1(Config, MaxDelay) ->
{ok, Node} = start_node(otp_3924),
DataLen = 100*1024,
Data = otp_3924_data(DataLen),
%% Repeat the test a couple of times to prevent the test from passing
%% by chance.
repeat(10, fun(N) ->
- ok = otp_3924(MaxDelay, Node, Data, DataLen, N)
+ ok = otp_3924(Config, MaxDelay, Node, Data, DataLen, N)
end),
test_server:stop_node(Node),
ok.
-otp_3924(MaxDelay, Node, Data, DataLen, N) ->
- {ok, L} = gen_tcp:listen(0, [list, {active, false}]),
+otp_3924(Config, MaxDelay, Node, Data, DataLen, N) ->
+ {ok, L} = ?LISTEN(Config, 0, [list, {active, false}]),
{ok, {_, Port}} = inet:sockname(L),
{ok, Host} = inet:gethostname(),
Sender = spawn_link(Node,
?MODULE,
otp_3924_sender,
- [self(), Host, Port, Data]),
+ [Config, self(), Host, Port, Data]),
Data = otp_3924_receive_data(L, Sender, MaxDelay, DataLen, N),
ok = gen_tcp:close(L).
@@ -638,10 +745,10 @@ otp_3924_data(_, Acc, 0, SingleLeft) ->
otp_3924_data(Block, Acc, BlockLeft, SingleLeft) ->
otp_3924_data(Block, [Block|Acc], BlockLeft-1, SingleLeft).
-otp_3924_sender(Receiver, Host, Port, Data) ->
+otp_3924_sender(Config, Receiver, Host, Port, Data) ->
receive
start ->
- {ok, Sock} = gen_tcp:connect(Host, Port, [list]),
+ {ok, Sock} = ?CONNECT(Config, Host, Port, [list]),
gen_tcp:send(Sock, Data),
ok = gen_tcp:close(Sock),
unlink(Receiver)
@@ -650,10 +757,10 @@ otp_3924_sender(Receiver, Host, Port, Data) ->
%% Tests that a huge amount of data can be received before a close.
data_before_close(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, [binary]),
+ {ok, L} = ?LISTEN(Config, 0, [binary]),
{ok, {_, TcpPort}} = inet:sockname(L),
Bytes = 256*1024,
- spawn_link(fun() -> huge_sender(TcpPort, Bytes) end),
+ spawn_link(fun() -> huge_sender(Config, TcpPort, Bytes) end),
{ok, A} = gen_tcp:accept(L),
case count_bytes_recv(A, 0) of
{Bytes, Result} ->
@@ -672,8 +779,8 @@ count_bytes_recv(Sock, Total) ->
{Total, Other}
end.
-huge_sender(TcpPort, Bytes) ->
- {ok, Client} = gen_tcp:connect(localhost, TcpPort, []),
+huge_sender(Config, TcpPort, Bytes) ->
+ {ok, Client} = ?CONNECT(Config, localhost, TcpPort, []),
receive after 500 -> ok end,
gen_tcp:send(Client, make_zero_packet(Bytes)),
gen_tcp:close(Client).
@@ -689,7 +796,7 @@ make_zero_packet(N) ->
%% OTP-2924. Test that the socket process does not crash when
%% sys:get_status(Pid) is called.
get_status(Config) when is_list(Config) ->
- {ok,{socket,Pid,_,_}} = gen_tcp:listen(5678,[]),
+ {ok,{socket,Pid,_,_}} = ?LISTEN(Config, 5678,[]),
{status,Pid,_,_} = sys:get_status(Pid).
-define(RECOVER_SLEEP, 60000).
@@ -701,40 +808,80 @@ iter_max_socks() ->
%% Open as many sockets as possible. Do this several times and check
%% that we get the same number of sockets every time.
iter_max_socks(Config) when is_list(Config) ->
- N = case os:type() of {win32,_} -> 10; _ -> 20 end,
+ %% This is not *nearly* enough
+ %% We have some crap machines, which we need to "handle with care"...
+ Tries =
+ case os:type() of
+ {win32, _} ->
+ 10;
+ {unix, darwin} ->
+ 10;
+ _ ->
+ 20
+ end,
%% Run on a different node in order to limit the effect if this test fails.
Dir = filename:dirname(code:which(?MODULE)),
- {ok,Node} = test_server:start_node(test_iter_max_socks,slave,
- [{args,"+Q 2048 -pa " ++ Dir}]),
- L = rpc:call(Node,?MODULE,do_iter_max_socks,[N, initalize]),
+ {ok, Node} = test_server:start_node(test_iter_max_socks,slave,
+ [{args,"+Q 2048 -pa " ++ Dir}]),
+ %% L = rpc:call(Node,?MODULE,do_iter_max_socks,[N, initalize]),
+ L = iter_max_socks_run(Node,
+ fun() ->
+ exit(do_iter_max_socks(Config, Tries, initalize))
+ end),
test_server:stop_node(Node),
- io:format("Result: ~p",[L]),
+ io:format("Result: ~p", [L]),
all_equal(L),
{comment, "Max sockets: " ++ integer_to_list(hd(L))}.
-do_iter_max_socks(0, _) ->
+iter_max_socks_run(Node, F) ->
+ try erlang:spawn_opt(Node, F, [monitor]) of
+ {Pid, MRef} when is_pid(Pid) andalso is_reference(MRef) ->
+ receive
+ {'DOWN', MRef, process, Pid, Res} ->
+ Res
+ end;
+ _Any ->
+ ?P("Unexpected process start result: "
+ "~n ~p", [_Any]),
+ {skip, "Failed starting iterator (slave) process"}
+ catch
+ C:E:S ->
+ ?P("Failed starting iterator (slave) process: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {skip, "Failed starting iterator (slave) process"}
+ end.
+
+
+do_iter_max_socks(_Config, 0, _) ->
+ ?P("do_iter_max_socks(0,-) -> done"),
[];
-do_iter_max_socks(N, initalize) ->
- MS = max_socks(),
- [MS|do_iter_max_socks(N-1, MS)];
-do_iter_max_socks(N, failed) ->
- MS = max_socks(),
- [MS|do_iter_max_socks(N-1, failed)];
-do_iter_max_socks(N, First) when is_integer(First) ->
- MS = max_socks(),
- if MS == First ->
- [MS|do_iter_max_socks(N-1, First)];
+do_iter_max_socks(Config, N, initalize = First) ->
+ ?P("do_iter_max_socks(~w,~w) -> entry", [N, First]),
+ MS = max_socks(Config),
+ [MS|do_iter_max_socks(Config, N-1, MS)];
+do_iter_max_socks(Config, N, failed = First) ->
+ ?P("do_iter_max_socks(~w,~w) -> entry", [N, First]),
+ MS = max_socks(Config),
+ [MS|do_iter_max_socks(Config, N-1, failed)];
+do_iter_max_socks(Config, N, First) when is_integer(First) ->
+ ?P("do_iter_max_socks(~w,~w) -> entry", [N, First]),
+ MS = max_socks(Config),
+ if
+ (MS =:= First) ->
+ [MS|do_iter_max_socks(Config, N-1, First)];
true ->
- io:format("Sleeping for ~p seconds...~n",
- [?RETRY_SLEEP/1000]),
+ ?P("~w =/= ~w => sleeping for ~p seconds...",
+ [MS, First, ?RETRY_SLEEP/1000]),
ct:sleep(?RETRY_SLEEP),
- io:format("Trying again...~n", []),
- RetryMS = max_socks(),
+ ?P("Trying again...", []),
+ RetryMS = max_socks(Config),
if RetryMS == First ->
- [RetryMS|do_iter_max_socks(N-1, First)];
- true ->
- [RetryMS|do_iter_max_socks(N-1, failed)]
+ [RetryMS|do_iter_max_socks(Config, N-1, First)];
+ true ->
+ [RetryMS|do_iter_max_socks(Config, N-1, failed)]
end
end.
@@ -747,39 +894,39 @@ all_equal(Rule, [Rule | T]) ->
all_equal(Rule, T);
all_equal(_, [_ | _]) ->
ct:sleep(?RECOVER_SLEEP), % Wait a while and *hope* that we'll
- %% recover so other tests won't be
- %% affected.
+ % recover so other tests won't be
+ % affected.
ct:fail(max_socket_mismatch);
all_equal(_Rule, []) ->
ok.
-max_socks() ->
- Socks = open_socks(),
+max_socks(Config) ->
+ Socks = open_socks(Config),
N = length(Socks),
lists:foreach(fun(S) -> ok = gen_tcp:close(S) end, Socks),
- io:format("Got ~p sockets", [N]),
+ ?P("Got ~p sockets", [N]),
N.
-open_socks() ->
- case gen_tcp:listen(0, []) of
+open_socks(Config) ->
+ case ?LISTEN(Config, 0, []) of
{ok, L} ->
{ok, {_, Port}} = inet:sockname(L),
- [L| connect_accept(L, Port)];
+ [L| connect_accept(Config, L, Port)];
_ ->
[]
end.
-connect_accept(L, Port) ->
- case gen_tcp:connect(localhost, Port, []) of
+connect_accept(Config, L, Port) ->
+ case ?CONNECT(Config, localhost, Port, []) of
{ok, C} ->
- [C| do_accept(L, Port)];
+ [C| do_accept(Config, L, Port)];
_ ->
[]
end.
-do_accept(L, Port) ->
+do_accept(Config, L, Port) ->
case gen_tcp:accept(L) of
- {ok, A} -> [A| connect_accept(L, Port)];
+ {ok, A} -> [A| connect_accept(Config, L, Port)];
_ -> []
end.
@@ -792,17 +939,19 @@ start_remote(Name) ->
test_server:start_node(Name, slave, [{remote, true}, {args, "-pa " ++ Pa}]).
%% Tests that when 'the other side' on a passive socket closes, the
-%% connecting, side still can read until the end of data.
+%% connecting side can still read until the end of data.
passive_sockets(Config) when is_list(Config) ->
spawn_link(?MODULE, passive_sockets_server,
- [[{active,false}],self()]),
+ [Config, [{active, false}], self()]),
receive
{socket,Port} -> ok
end,
ct:sleep(500),
- case gen_tcp:connect("localhost", Port, [{active, false}]) of
+ case ?CONNECT(Config, "localhost", Port, [{active, false}]) of
{ok, Sock} ->
passive_sockets_read(Sock);
+ {error, eaddrnotavail = Reason} ->
+ {skip, connect_failed_str(Reason)};
Error ->
ct:fail({"Could not connect to server", Error})
end.
@@ -823,8 +972,8 @@ passive_sockets_read(Sock) ->
ct:fail({"Did not get {error, closed} before other error", Error})
end.
-passive_sockets_server(Opts, Parent) ->
- case gen_tcp:listen(0, Opts) of
+passive_sockets_server(Config, Opts, Parent) ->
+ case ?LISTEN(Config, 0, Opts) of
{ok, LSock} ->
{ok,{_,Port}} = inet:sockname(LSock),
Parent ! {socket,Port},
@@ -861,7 +1010,7 @@ passive_sockets_server_send(Socket, X) ->
%% the socket is closed from another process. (OTP-3817)
accept_closed_by_other_process(Config) when is_list(Config) ->
Parent = self(),
- {ok, ListenSocket} = gen_tcp:listen(0, []),
+ {ok, ListenSocket} = ?LISTEN(Config, 0, []),
Child =
spawn_link(
fun() ->
@@ -888,7 +1037,7 @@ repeat(_, _, _) ->
%% Tests the response when using a closed socket as argument.
closed_socket(Config) when is_list(Config) ->
- {ok, LS1} = gen_tcp:listen(0, []),
+ {ok, LS1} = ?LISTEN(Config, 0, []),
erlang:yield(),
ok = gen_tcp:close(LS1),
%% If the following delay is uncommented, the result error values
@@ -906,10 +1055,10 @@ closed_socket(Config) when is_list(Config) ->
{error, R_controlling_process} =
gen_tcp:controlling_process(LS1, self()),
%%
- ok = io:format("R_send = ~p~n", [R_send]),
- ok = io:format("R_recv = ~p~n", [R_recv]),
- ok = io:format("R_accept = ~p~n", [R_accept]),
- ok = io:format("R_controlling_process = ~p~n", [R_controlling_process]),
+ ok = ?P("R_send = ~p", [R_send]),
+ ok = ?P("R_recv = ~p", [R_recv]),
+ ok = ?P("R_accept = ~p", [R_accept]),
+ ok = ?P("R_controlling_process = ~p", [R_controlling_process]),
ok.
%%%
@@ -917,46 +1066,59 @@ closed_socket(Config) when is_list(Config) ->
%%%
shutdown_active(Config) when is_list(Config) ->
- shutdown_common(true).
+ ?TC_TRY(shutdown_active, fun() -> shutdown_common(Config, true) end).
shutdown_passive(Config) when is_list(Config) ->
- shutdown_common(false).
-
-shutdown_common(Active) ->
- P = sort_server(Active),
- io:format("Sort server port: ~p\n", [P]),
-
- do_sort(P, []),
- do_sort(P, ["glurf"]),
- do_sort(P, ["abc","nisse","dum"]),
-
- do_sort(P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(25, 255)]),
- do_sort(P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(77, 999)]),
- do_sort(P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(25, 55)]),
- do_sort(P, []),
- do_sort(P, ["apa"]),
- do_sort(P, ["kluns","gorilla"]),
- do_sort(P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(25, 1233)]),
- do_sort(P, []),
+ ?TC_TRY(shutdown_passive, fun() -> shutdown_common(Config, false) end).
+
+shutdown_common(Config, Active) ->
+ ?P("start sort server"),
+ P = sort_server(Config, Active),
+ ?P("Sort server port: ~p", [P]),
+
+
+ do_sort(Config, P, []),
+ do_sort(Config, P, ["glurf"]),
+ do_sort(Config, P, ["abc","nisse","dum"]),
+
+ do_sort(Config, P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(25, 255)]),
+ do_sort(Config, P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(77, 999)]),
+ do_sort(Config, P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(25, 55)]),
+ do_sort(Config, P, []),
+ do_sort(Config, P, ["apa"]),
+ do_sort(Config, P, ["kluns","gorilla"]),
+ do_sort(Config, P, [lists:reverse(integer_to_list(I)) || I <- lists:seq(25, 1233)]),
+ do_sort(Config, P, []),
receive
Any ->
ct:fail({unexpected_message,Any})
after 0 -> ok
end.
-do_sort(P, List0) ->
+do_sort(Config, P, List0) ->
+ ?P("Sort: "
+ "~n ~p", [List0]),
List = [El++"\n" || El <- List0],
- {ok,S} = gen_tcp:connect(localhost, P, [{packet,line}]),
+ S = case ?CONNECT(Config, localhost, P, [{packet,line}]) of
+ {ok, Socket} ->
+ Socket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
send_lines(S, List),
- gen_tcp:shutdown(S, write),
+ ok = gen_tcp:shutdown(S, write),
Lines = collect_lines(S, true),
- io:format("~p\n", [Lines]),
- Lines = lists:sort(List),
+ ?P("Collected: "
+ "~n ~p", [Lines]),
+ SortedLines = lists:sort(List),
+ ?P("Sorted: "
+ "~n ~p", [SortedLines]),
+ Lines = SortedLines,
ok = gen_tcp:close(S).
-sort_server(Active) ->
+sort_server(Config, Active) ->
Opts = [{exit_on_close,false},{packet,line},{active,Active}],
- {ok,L} = gen_tcp:listen(0, Opts),
+ {ok,L} = ?LISTEN(Config, 0, Opts),
Go = make_ref(),
Pid = spawn_link(fun() ->
receive Go -> sort_server_1(L, Active) end
@@ -987,8 +1149,12 @@ collect_lines(S, false) ->
collect_lines_1(S, Acc) ->
receive
- {tcp,S,Line} -> collect_lines_1(S, [Line|Acc]);
- {tcp_closed,S} -> lists:reverse(Acc)
+ {tcp,S,Line} ->
+ ?P("collect_lines_1(~w): ~p", [S, Line]),
+ collect_lines_1(S, [Line|Acc]);
+ {tcp_closed,S} ->
+ ?P("collect_lines_1(~w): tcp_closed", [S]),
+ lists:reverse(Acc)
end.
passive_collect_lines_1(S, Acc) ->
@@ -1000,7 +1166,8 @@ passive_collect_lines_1(S, Acc) ->
send_lines(S, Lines) ->
lists:foreach(fun(Line) ->
- gen_tcp:send(S, Line)
+ ?P("send_line(~w): ~p", [S, Line]),
+ ok = gen_tcp:send(S, Line)
end, Lines).
%%%
@@ -1008,23 +1175,52 @@ send_lines(S, Lines) ->
%%%
shutdown_pending(Config) when is_list(Config) ->
+ ?TC_TRY(shutdown_pending, fun() -> do_shutdown_pending(Config) end).
+
+do_shutdown_pending(Config) ->
N = 512*1024+17,
- io:format("~p\n", [N]),
+ ?P("N: ~p", [N]),
Data = [<<N:32>>,ones(N),42],
- P = a_server(),
- io:format("Server port: ~p\n", [P]),
- {ok,S} = gen_tcp:connect(localhost, P, []),
+ {Port, Pid} = a_server(Config),
+ ?P("try connect to server (port: ~p)", [Port]),
+ S = case ?CONNECT(Config, localhost, Port, []) of
+ {ok, Socket} ->
+ ?P("connected"),
+ Socket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("send"),
gen_tcp:send(S, Data),
+ ?P("shutdown(write)"),
gen_tcp:shutdown(S, write),
- receive
- {tcp,S,Msg} ->
- io:format("~p\n", [Msg]),
- N = list_to_integer(Msg) - 5;
- Other ->
- ct:fail({unexpected,Other})
+ ?P("await data message"),
+ case sp_await_data(Pid, S) of
+ N ->
+ ok;
+ InvalidN ->
+ ?P("Invalid message: "
+ "~n Expected: ~p"
+ "~n Received: ~p", [N, InvalidN]),
+ ct:fail({unexpected_msg, N, InvalidN})
end,
+ ?P("done"),
ok.
+sp_await_data(Pid, Sock) ->
+ receive
+ {tcp, Sock, Msg} ->
+ ?P("got tcp (data) message: ~p", [Msg]),
+ list_to_integer(Msg) - 5;
+ {'EXIT', Pid, normal} ->
+ ?P("server exited normal"),
+ sp_await_data(Pid, Sock);
+ Other ->
+ ?P("UNEXPECTED: "
+ "~n ~p", [Other]),
+ ct:fail({unexpected, Other})
+ end.
+
ones(0) -> [];
ones(1) -> [1];
ones(N) ->
@@ -1035,14 +1231,14 @@ shutdown_pending(Config) when is_list(Config) ->
_ -> [1,Ones|Ones]
end.
- a_server() ->
- {ok,L} = gen_tcp:listen(0, [{exit_on_close,false},{active,false}]),
- Pid = spawn_link(fun() -> a_server(L) end),
- ok = gen_tcp:controlling_process(L, Pid),
- {ok,Port} = inet:port(L),
- Port.
+ a_server(Config) ->
+ {ok, L} = ?LISTEN(Config, 0, [{exit_on_close,false},{active,false}]),
+ Pid = spawn_link(fun() -> a_server2(L) end),
+ ok = gen_tcp:controlling_process(L, Pid),
+ {ok, Port} = inet:port(L),
+ {Port, Pid}.
- a_server(L) ->
+a_server2(L) ->
{ok,S} = gen_tcp:accept(L),
do_recv(S, []).
@@ -1061,19 +1257,29 @@ shutdown_pending(Config) when is_list(Config) ->
%%
show_econnreset_active(Config) when is_list(Config) ->
+ ?TC_TRY(show_econnreset_active,
+ fun() -> do_show_econnreset_active(Config) end).
+
+do_show_econnreset_active(Config) ->
%% First confirm everything works with option turned off.
- {ok, L} = gen_tcp:listen(0, []),
- {ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
- {ok, S} = gen_tcp:accept(L),
- ok = gen_tcp:close(L),
- ok = inet:setopts(Client, [{linger, {true, 0}}]),
- ok = gen_tcp:close(Client),
+ ?P("test with option switched off (default)"),
+ {ok, L0} = ?LISTEN(Config, 0, []),
+ {ok, Port0} = inet:port(L0),
+ Client0 = case ?CONNECT(Config, localhost, Port0, [{active, false}]) of
+ {ok, CSock0} ->
+ CSock0;
+ {error, eaddrnotavail = Reason0} ->
+ ?SKIPT(connect_failed_str(Reason0))
+ end,
+ {ok, S0} = gen_tcp:accept(L0),
+ ok = gen_tcp:close(L0),
+ ok = inet:setopts(Client0, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(Client0),
receive
- {tcp_closed, S} ->
+ {tcp_closed, S0} ->
ok;
- Other ->
- ct:fail({unexpected1, Other})
+ Other0 ->
+ ct:fail({unexpected, off, closed, Other0})
after 1000 ->
ct:fail({timeout, {server, no_tcp_closed}})
end,
@@ -1081,9 +1287,15 @@ show_econnreset_active(Config) when is_list(Config) ->
%% Now test with option switched on.
%% Note: We are also testing that the show_econnreset option is
%% inherited from the listening socket by the accepting socket.
- {ok, L1} = gen_tcp:listen(0, [{show_econnreset, true}]),
+ ?P("test with option explicitly switched on"),
+ {ok, L1} = ?LISTEN(Config, 0, [{show_econnreset, true}]),
{ok, Port1} = inet:port(L1),
- {ok, Client1} = gen_tcp:connect(localhost, Port1, [{active, false}]),
+ Client1 = case ?CONNECT(Config, localhost, Port1, [{active, false}]) of
+ {ok, CSock1} ->
+ CSock1;
+ {error, eaddrnotavail = Reason1} ->
+ ?SKIPT(connect_failed_str(Reason1))
+ end,
{ok, S1} = gen_tcp:accept(L1),
ok = gen_tcp:close(L1),
ok = inet:setopts(Client1, [{linger, {true, 0}}]),
@@ -1092,25 +1304,41 @@ show_econnreset_active(Config) when is_list(Config) ->
{tcp_error, S1, econnreset} ->
receive
{tcp_closed, S1} ->
+ ?P("done"),
ok;
Other1 ->
- ct:fail({unexpected2, Other1})
+ ?P("UNEXPECTED (expected closed):"
+ "~n ~p", [Other1]),
+ ct:fail({unexpected, on, closed, Other1})
after 1 ->
- ct:fail({timeout, {server, no_tcp_closed}})
+ ?P("UNEXPECTED timeout (expected closed)"),
+ ct:fail({timeout, {server, no_tcp_closed}})
end;
Other2 ->
- ct:fail({unexpected3, Other2})
+ ?P("UNEXPECTED (expected error:econnreset):"
+ "~n ~p", [Other2]),
+ ct:fail({unexpected, on, econnreset, Other2})
after 1000 ->
- ct:fail({timeout, {server, no_tcp_error}})
+ ?P("UNEXPECTED timeout (expected error:econnreset)"),
+ ct:fail({timeout, {server, no_tcp_error}})
end.
show_econnreset_active_once(Config) when is_list(Config) ->
+ ?TC_TRY(show_econnreset_active_once,
+ fun() -> do_show_econnreset_active_once(Config) end).
+
+do_show_econnreset_active_once(Config) ->
%% Now test using {active, once}
- {ok, L} = gen_tcp:listen(0,
+ {ok, L} = ?LISTEN(Config, 0,
[{active, false},
{show_econnreset, true}]),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ Client = case ?CONNECT(Config, localhost, Port, [{active, false}]) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
{ok, S} = gen_tcp:accept(L),
ok = gen_tcp:close(L),
ok = inet:setopts(Client, [{linger, {true, 0}}]),
@@ -1135,10 +1363,19 @@ show_econnreset_active_once(Config) when is_list(Config) ->
end.
show_econnreset_passive(Config) when is_list(Config) ->
+ ?TC_TRY(show_econnreset_passive,
+ fun() -> do_show_econnreset_passive(Config) end).
+
+do_show_econnreset_passive(Config) ->
%% First confirm everything works with option turned off.
- {ok, L} = gen_tcp:listen(0, [{active, false}]),
+ {ok, L} = ?LISTEN(Config, 0, [{active, false}]),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ Client = case ?CONNECT(Config, localhost, Port, [{active, false}]) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
{ok, S} = gen_tcp:accept(L),
ok = gen_tcp:close(L),
ok = inet:setopts(S, [{linger, {true, 0}}]),
@@ -1147,11 +1384,16 @@ show_econnreset_passive(Config) when is_list(Config) ->
{error, closed} = gen_tcp:recv(Client, 0),
%% Now test with option switched on.
- {ok, L1} = gen_tcp:listen(0, [{active, false}]),
+ {ok, L1} = ?LISTEN(Config, 0, [{active, false}]),
{ok, Port1} = inet:port(L1),
- {ok, Client1} = gen_tcp:connect(localhost, Port1,
- [{active, false},
- {show_econnreset, true}]),
+ Client1 =
+ case ?CONNECT(Config, localhost, Port1,
+ [{active, false}, {show_econnreset, true}]) of
+ {ok, CSock1} ->
+ CSock1;
+ {error, eaddrnotavail = Reason1} ->
+ ?SKIPT(connect_failed_str(Reason1))
+ end,
{ok, S1} = gen_tcp:accept(L1),
ok = gen_tcp:close(L1),
ok = inet:setopts(S1, [{linger, {true, 0}}]),
@@ -1160,10 +1402,20 @@ show_econnreset_passive(Config) when is_list(Config) ->
{error, econnreset} = gen_tcp:recv(Client1, 0).
econnreset_after_sync_send(Config) when is_list(Config) ->
+ ?TC_TRY(econnreset_after_sync_send,
+ fun() -> do_econnreset_after_sync_send(Config) end).
+
+do_econnreset_after_sync_send(Config) ->
%% First confirm everything works with option turned off.
- {ok, L} = gen_tcp:listen(0, [{active, false}]),
+ ?P("test with option switched off (default)"),
+ {ok, L} = ?LISTEN(Config, 0, [{active, false}]),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ Client = case ?CONNECT(Config, localhost, Port, [{active, false}]) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
{ok, S} = gen_tcp:accept(L),
ok = gen_tcp:close(L),
ok = inet:setopts(S, [{linger, {true, 0}}]),
@@ -1172,11 +1424,17 @@ econnreset_after_sync_send(Config) when is_list(Config) ->
{error, closed} = gen_tcp:send(Client, "Whatever"),
%% Now test with option switched on.
- {ok, L1} = gen_tcp:listen(0, [{active, false}]),
+ ?P("test with option explicitly switched on"),
+ {ok, L1} = ?LISTEN(Config, 0, [{active, false}]),
{ok, Port1} = inet:port(L1),
- {ok, Client1} = gen_tcp:connect(localhost, Port1,
- [{active, false},
- {show_econnreset, true}]),
+ Client1 =
+ case ?CONNECT(Config, localhost, Port1,
+ [{active, false}, {show_econnreset, true}]) of
+ {ok, CSock1} ->
+ CSock1;
+ {error, eaddrnotavail = Reason1} ->
+ ?SKIPT(connect_failed_str(Reason1))
+ end,
{ok, S1} = gen_tcp:accept(L1),
ok = gen_tcp:close(L1),
ok = inet:setopts(S1, [{linger, {true, 0}}]),
@@ -1185,215 +1443,426 @@ econnreset_after_sync_send(Config) when is_list(Config) ->
{error, econnreset} = gen_tcp:send(Client1, "Whatever").
econnreset_after_async_send_active(Config) when is_list(Config) ->
+ ?TC_TRY(econnreset_after_async_send_active,
+ fun() -> do_econnreset_after_async_send_active(Config) end).
+
+do_econnreset_after_async_send_active(Config) ->
{OS, _} = os:type(),
Payload = lists:duplicate(1024 * 1024, $.),
%% First confirm everything works with option turned off.
- {ok, L} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ ?P("create listen socket (server) with active = false"),
+ {ok, L} = ?LISTEN(Config, 0, [{active, false}, {recbuf, 4096}]),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port, [{sndbuf, 4096}]),
+ ?P("[client] create connect socket (default = off)"),
+ Client = case ?CONNECT(Config, localhost, Port, [{sndbuf, 4096}]) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("create accept socket (server)"),
{ok, S} = gen_tcp:accept(L),
+ ?P("close listen socket"),
ok = gen_tcp:close(L),
+ ?P("[client] send payload"),
ok = gen_tcp:send(Client, Payload),
+ ?P("[client] verify socket queue size"),
case erlang:port_info(Client, queue_size) of
{queue_size, N} when N > 0 -> ok;
{queue_size, 0} when OS =:= win32 -> ok;
{queue_size, 0} = T -> ct:fail(T)
end,
+ ?P("[server] send something"),
ok = gen_tcp:send(S, "Whatever"),
+ ?P("sleep some"),
ok = ct:sleep(20),
+ ?P("[server] set linger true:0"),
ok = inet:setopts(S, [{linger, {true, 0}}]),
+ ?P("[server] close socket"),
ok = gen_tcp:close(S),
+ ?P("sleep some"),
ok = ct:sleep(20),
+ ?P("[client] await server data"),
receive
{tcp, Client, "Whatever"} ->
+ ?P("[client] received server data - now await socket closed"),
receive
{tcp_closed, Client} ->
+ ?P("[client] received socket closed"),
ok;
Other1 ->
+ ?P("[client] awaiting socket closed - received upexpected: "
+ "~n ~p", [Other1]),
ct:fail({unexpected1, Other1})
end;
Other2 ->
+ ?P("[client] awaiting socket data - received upexpected: "
+ "~n ~p", [Other2]),
ct:fail({unexpected2, Other2})
end,
%% Now test with option switched on.
- {ok, L1} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ ?P("create listen socket (server) with active = false (default)"),
+ {ok, L1} = ?LISTEN(Config, 0, [{active, false}, {recbuf, 4096}]),
{ok, Port1} = inet:port(L1),
- {ok, Client1} = gen_tcp:connect(localhost, Port1,
- [{sndbuf, 4096},
- {show_econnreset, true}]),
+ ?P("[client] create connect socket (on)"),
+ Client1 =
+ case ?CONNECT(Config, localhost, Port1,
+ [{sndbuf, 4096}, {show_econnreset, true}]) of
+ {ok, CSock1} ->
+ CSock1;
+ {error, eaddrnotavail = Reason1} ->
+ ?SKIPT(connect_failed_str(Reason1))
+ end,
+ ?P("create accept socket (server)"),
{ok, S1} = gen_tcp:accept(L1),
+ ?P("close listen socket"),
ok = gen_tcp:close(L1),
+ ?P("[client] send payload"),
ok = gen_tcp:send(Client1, Payload),
+ ?P("[client] verify socket queue size"),
case erlang:port_info(Client1, queue_size) of
{queue_size, N1} when N1 > 0 -> ok;
{queue_size, 0} when OS =:= win32 -> ok;
{queue_size, 0} = T1 -> ct:fail(T1)
end,
+ ?P("[server] send something"),
ok = gen_tcp:send(S1, "Whatever"),
+ ?P("sleep some"),
ok = ct:sleep(20),
+ ?P("[server] set linger true:0"),
ok = inet:setopts(S1, [{linger, {true, 0}}]),
+ ?P("[server] close socket"),
ok = gen_tcp:close(S1),
+ ?P("sleep some"),
ok = ct:sleep(20),
+ ?P("[client] await server data"),
receive
{tcp, Client1, "Whatever"} ->
+ ?P("[client] received server data - now await socket error"),
receive
{tcp_error, Client1, econnreset} ->
+ ?P("[client] received socket error - now await socket closed"),
receive
{tcp_closed, Client1} ->
+ ?P("[client] received socket closed"),
ok;
Other3 ->
+ ?P("[client] awaiting socket closed - "
+ "received upexpected: "
+ "~n ~p", [Other3]),
ct:fail({unexpected3, Other3})
end;
Other4 ->
+ ?P("[client] awaiting socket error - received upexpected: "
+ "~n ~p", [Other4]),
ct:fail({unexpected4, Other4})
end;
Other5 ->
+ ?P("[client] awaiting socket data - received upexpected: "
+ "~n ~p", [Other5]),
ct:fail({unexpected5, Other5})
- end.
+ end,
+ ?P("done"),
+ ok.
econnreset_after_async_send_active_once(Config) when is_list(Config) ->
+ ?TC_TRY(econnreset_after_async_send_active_once,
+ fun() -> do_econnreset_after_async_send_active_once(Config) end).
+
+do_econnreset_after_async_send_active_once(Config) ->
{OS, _} = os:type(),
- {ok, L} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ ?P("create listen socket with active = false"),
+ {ok, L} = ?LISTEN(Config, 0, [{active, false}, {recbuf, 4096}]),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port,
- [{active, false},
- {sndbuf, 4096},
- {show_econnreset, true}]),
+ ?P("create connect socket (~w)", [Port]),
+ Client = case ?CONNECT(Config, localhost, Port,
+ [{active, false},
+ {sndbuf, 4096},
+ {show_econnreset, true}]) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("create accept socket"),
{ok,S} = gen_tcp:accept(L),
+ ?P("close listen socket"),
ok = gen_tcp:close(L),
+ ?P("create payload"),
Payload = lists:duplicate(1024 * 1024, $.),
+ ?P("[connect] send payload"),
ok = gen_tcp:send(Client, Payload),
+ ?P("[connect] verify socket queue size"),
case erlang:port_info(Client, queue_size) of
{queue_size, N} when N > 0 -> ok;
{queue_size, 0} when OS =:= win32 -> ok;
{queue_size, 0} = T -> ct:fail(T)
end,
+ ?P("[accept] send something"),
ok = gen_tcp:send(S, "Whatever"),
+ ?P("sleep some"),
ok = ct:sleep(20),
+ ?P("[accept] set socket option linger: {true, 0}"),
ok = inet:setopts(S, [{linger, {true, 0}}]),
+ ?P("[accept] close socket"),
ok = gen_tcp:close(S),
+ ?P("sleep some"),
ok = ct:sleep(20),
+ ?P("receive 'unexpected message'"),
ok = receive Msg -> {unexpected_msg, Msg} after 0 -> ok end,
+ ?P("[connect] set socket option active: once"),
ok = inet:setopts(Client, [{active, once}]),
+ ?P("[connect] expect econreset"),
receive
{tcp_error, Client, econnreset} ->
+ ?P("[connect] received econreset -> expect socket close message"),
receive
{tcp_closed, Client} ->
+ ?P("[connect] received socket close message - done"),
ok;
Other ->
+ ?P("[connect] received unexpected message: "
+ "~n ~p", [Other]),
ct:fail({unexpected1, Other})
end;
Other ->
+ ?P("[connect] received unexpected message: "
+ "~n ~p", [Other]),
ct:fail({unexpected2, Other})
end.
econnreset_after_async_send_passive(Config) when is_list(Config) ->
+ ?TC_TRY(econnreset_after_async_send_passive,
+ fun() -> do_econnreset_after_async_send_passive(Config) end).
+
+do_econnreset_after_async_send_passive(Config) ->
{OS, _} = os:type(),
Payload = lists:duplicate(1024 * 1024, $.),
%% First confirm everything works with option turned off.
- {ok, L} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ ?P("create listen socket *** with option switched off (default)"),
+ {ok, L} = ?LISTEN(Config, 0, [{active, false}, {recbuf, 4096}]),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port,
- [{active, false},
- {sndbuf, 4096}]),
+ ?P("create connect socket"),
+ Client = case ?CONNECT(Config, localhost, Port,
+ [{active, false}, {sndbuf, 4096}]) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("accept connection"),
{ok, S} = gen_tcp:accept(L),
+ ?P("close listen socket"),
ok = gen_tcp:close(L),
+ ?P("[server] set linger: true:0"),
ok = inet:setopts(S, [{linger, {true, 0}}]),
+ ?P("[server] send some data to client"),
ok = gen_tcp:send(S, "Whatever"),
+ ?P("[client] send some data to server"),
ok = gen_tcp:send(Client, Payload),
+ ?P("[client] verify (port) queue-size"),
case erlang:port_info(Client, queue_size) of
{queue_size, N} when N > 0 -> ok;
{queue_size, 0} when OS =:= win32 -> ok;
{queue_size, 0} = T -> ct:fail(T)
end,
+ ?P("[server] close socket"),
ok = gen_tcp:close(S),
+ ?P("sleep some"),
ok = ct:sleep(20),
+ ?P("[client] attempt receive and expect error (closed): "
+ "~n Port Info: ~p"
+ "~n Socket Status: ~s",
+ [try erlang:port_info(Client)
+ catch
+ _:_:_ ->
+ "-"
+ end,
+ try prim_inet:getstatus(Client) of
+ {ok, CStatus} -> ?F("~p", [CStatus]);
+ _ -> "-"
+ catch
+ _:_:_ ->
+ "-"
+ end]),
{error, closed} = gen_tcp:recv(Client, 0),
%% Now test with option switched on.
- {ok, L1} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ ?P("create listen socket *** with option explicitly switched on"),
+ {ok, L1} = ?LISTEN(Config, 0, [{active, false}, {recbuf, 4096}]),
{ok, Port1} = inet:port(L1),
- {ok, Client1} = gen_tcp:connect(localhost, Port1,
+ ?P("create connect socket"),
+ Client1 = case ?CONNECT(Config, localhost, Port1,
[{active, false},
{sndbuf, 4096},
- {show_econnreset, true}]),
+ {show_econnreset, true}]) of
+ {ok, CSock1} ->
+ CSock1;
+ {error, eaddrnotavail = Reason1} ->
+ ?SKIPT(connect_failed_str(Reason1))
+ end,
+ ?P("accept connection"),
{ok, S1} = gen_tcp:accept(L1),
+ ?P("close listen socket"),
ok = gen_tcp:close(L1),
+ ?P("[server] set linger: true:0"),
ok = inet:setopts(S1, [{linger, {true, 0}}]),
+ ?P("[server] send some data to client"),
ok = gen_tcp:send(S1, "Whatever"),
+ ?P("[client] send some data to server"),
ok = gen_tcp:send(Client1, Payload),
+ ?P("[server] close socket"),
ok = gen_tcp:close(S1),
+ ?P("sleep some"),
ok = ct:sleep(20),
- {error, econnreset} = gen_tcp:recv(Client1, 0).
+ ?P("[client] attempt receive and expect error (econnreset): "
+ "~n Port Info: ~p"
+ "~n Socket Status: ~s",
+ [try erlang:port_info(Client)
+ catch
+ _:_:_ ->
+ "-"
+ end,
+ try prim_inet:getstatus(Client) of
+ {ok, CStatus1} -> ?F("~p", [CStatus1]);
+ _ -> "-"
+ catch
+ _:_:_ ->
+ "-"
+ end]),
+ {error, econnreset} = gen_tcp:recv(Client1, 0),
+ ?P("done"),
+ ok.
%%
%% Test {linger {true, 0}} aborts a connection
%%
linger_zero(Config) when is_list(Config) ->
+ ?TC_TRY(linger_zero, fun() -> do_linger_zero(Config) end).
+
+do_linger_zero(Config) ->
%% All the econnreset tests will prove that {linger, {true, 0}} aborts
%% a connection when the driver queue is empty. We will test here
%% that it also works when the driver queue is not empty.
{OS, _} = os:type(),
- {ok, L} = gen_tcp:listen(0, [{active, false},
+ ?P("create listen socket"),
+ {ok, L} = ?LISTEN(Config, 0, [{active, false},
{recbuf, 4096},
{show_econnreset, true}]),
{ok, Port} = inet:port(L),
- {ok, Client} = gen_tcp:connect(localhost, Port,
- [{active, false},
- {sndbuf, 4096}]),
+ ?P("connect (create client socket)"),
+ Client = case ?CONNECT(Config, localhost, Port,
+ [{active, false}, {sndbuf, 4096}]) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("accept"),
{ok, S} = gen_tcp:accept(L),
+ ?P("close listen socket"),
ok = gen_tcp:close(L),
+ ?P("create payload"),
PayloadSize = 1024 * 1024,
Payload = lists:duplicate(PayloadSize, $.),
- ok = gen_tcp:send(Client, Payload),
- case erlang:port_info(Client, queue_size) of
- {queue_size, N} when N > 0 -> ok;
- {queue_size, 0} when OS =:= win32 -> ok;
- {queue_size, 0} = T -> ct:fail(T)
- end,
+ ?P("ensure empty queue"),
+ lz_ensure_non_empty_queue(Client, Payload, OS),
+ ?P("linger: {true, 0}"),
ok = inet:setopts(Client, [{linger, {true, 0}}]),
+ ?P("close client socket"),
ok = gen_tcp:close(Client),
+ ?P("sleep some"),
ok = ct:sleep(1),
+ ?P("verify client socket (port) not connected"),
+
+ %% THIS WILL NOT WORK IF THE NEW 'SOCKET'
+ %% WE NEED TO FIGURE OUT A NEW WAY TO DO THIS OR JUST SKIP IT?
undefined = erlang:port_info(Client, connected),
+
+
+ ?P("try (and fail) recv (on accepted socket)"),
{error, econnreset} = gen_tcp:recv(S, PayloadSize),
+ ?P("done"),
ok.
+%% THIS DOES NOT WORK FOR 'SOCKET'
+lz_ensure_non_empty_queue(Sock, Payload, OS) when is_port(Sock) ->
+ lz_ensure_non_empty_queue(Sock, Payload, OS, 1).
+
+-define(LZ_MAX_SENDS, 3).
+
+lz_ensure_non_empty_queue(Sock, _Payload, _OS, N) when (N > ?LZ_MAX_SENDS) ->
+ ?P("queue size verification failed - port info: "
+ "~n Socket: ~p"
+ "~n Socket info: ~p", [Sock, erlang:port_info(Sock)]),
+ ct:fail("Queue size verification failed");
+lz_ensure_non_empty_queue(Sock, Payload, OS, N) ->
+ ?P("try send payload ~w (on client socket) when port info:"
+ "~n ~p", [N, erlang:port_info(Sock)]),
+ ok = gen_tcp:send(Sock, Payload),
+ ?P("try verify client socket queue size"),
+ case erlang:port_info(Sock, queue_size) of
+ {queue_size, QSz} when QSz > 0 ->
+ ?P("queue size verification *successfull* (~p) - port info: "
+ "~n ~p", [QSz, erlang:port_info(Sock)]),
+ ok;
+ {queue_size, 0} when OS =:= win32 ->
+ ?P("queue size verification *successfull* - port info: "
+ "~n ~p", [erlang:port_info(Sock)]),
+ ok;
+ {queue_size, 0} ->
+ ?P("queue size verification failed - port info: "
+ "~n ~p", [erlang:port_info(Sock)]),
+ lz_ensure_non_empty_queue(Sock, Payload, OS, N+1)
+ end.
+
linger_zero_sndbuf(Config) when is_list(Config) ->
+ ?TC_TRY(linger_zero_sndbuf, fun() -> do_linger_zero_sndbuf(Config) end).
+
+do_linger_zero_sndbuf(Config) ->
%% All the econnreset tests will prove that {linger, {true, 0}} aborts
%% a connection when the driver queue is empty. We will test here
%% that it also works when the driver queue is not empty
%% and the linger zero option is set on the listen socket.
{OS, _} = os:type(),
+ ?P("create listen socket"),
{ok, Listen} =
- gen_tcp:listen(0, [{active, false},
+ ?LISTEN(Config, 0, [{active, false},
{recbuf, 4096},
{show_econnreset, true},
{linger, {true, 0}}]),
{ok, Port} = inet:port(Listen),
- {ok, Client} =
- gen_tcp:connect(localhost, Port,
- [{active, false},
- {sndbuf, 4096}]),
+ ?P("connect (create client socket)"),
+ Client =
+ case ?CONNECT(Config, localhost, Port,
+ [{active, false}, {sndbuf, 4096}]) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("accept"),
{ok, Server} = gen_tcp:accept(Listen),
+ ?P("close listen socket"),
ok = gen_tcp:close(Listen),
PayloadSize = 1024 * 1024,
Payload = binary:copy(<<"0123456789ABCDEF">>, 256 * 1024), % 1 MB
- ok = gen_tcp:send(Server, Payload),
- case erlang:port_info(Server, queue_size) of
- {queue_size, N} when N > 0 -> ok;
- {queue_size, 0} when OS =:= win32 -> ok;
- {queue_size, 0} = T -> ct:fail(T)
- end,
+ lz_ensure_non_empty_queue(Client, Payload, OS),
+ ?P("verify linger: {true, 0}"),
{ok, [{linger, {true, 0}}]} = inet:getopts(Server, [linger]),
+ ?P("close client socket"),
ok = gen_tcp:close(Server),
ok = ct:sleep(1),
+ ?P("verify client socket (port) not connected"),
undefined = erlang:port_info(Server, connected),
+ ?P("try (and fail) recv (on client socket)"),
{error, closed} = gen_tcp:recv(Client, PayloadSize),
+ ?P("done"),
ok.
@@ -1401,12 +1870,12 @@ linger_zero_sndbuf(Config) when is_list(Config) ->
%% corrupt data. The testcase will be killed by the timetrap timeout
%% if the bug is present.
http_bad_packet(Config) when is_list(Config) ->
- {ok,L} = gen_tcp:listen(0, [{active, false},
+ {ok,L} = ?LISTEN(Config, 0, [{active, false},
binary,
{reuseaddr, true},
{packet, http}]),
{ok,Port} = inet:port(L),
- spawn_link(fun() -> erlang:yield(), http_bad_client(Port) end),
+ spawn_link(fun() -> erlang:yield(), http_bad_client(Config, Port) end),
case gen_tcp:accept(L) of
{ok,S} ->
http_worker(S);
@@ -1423,8 +1892,8 @@ http_worker(S) ->
http_worker(S)
end.
-http_bad_client(Port) ->
- {ok,S} = gen_tcp:connect("localhost", Port, [{active,false}, binary]),
+http_bad_client(Config, Port) ->
+ {ok,S} = ?CONNECT(Config, "localhost", Port, [{active,false}, binary]),
ok = gen_tcp:send(S, "\r\n"),
ok = gen_tcp:close(S).
@@ -1432,53 +1901,124 @@ http_bad_client(Port) ->
%% Fill send queue and then start receiving.
%%
busy_send(Config) when is_list(Config) ->
- Master = self(),
- Msg = <<"the quick brown fox jumps over a lazy dog~n">>,
- Server =
- spawn_link(fun () ->
- {ok,L} = gen_tcp:listen
- (0, [{active,false},binary,
- {reuseaddr,true},{packet,0}]),
- {ok,Port} = inet:port(L),
- Master ! {self(),client,
- busy_send_client(Port, Master, Msg)},
- busy_send_srv(L, Master, Msg)
- end),
- io:format("~p Server~n", [Server]),
+ ?TC_TRY(busy_send, fun() -> do_busy_send(Config) end).
+
+do_busy_send(Config) ->
+ OldFlag = process_flag(trap_exit, true),
+ Master = self(),
+ Msg = <<"the quick brown fox jumps over a lazy dog~n">>,
+ ?P("[master] start server"),
+ ServerF =
+ fun() ->
+ ?P("[server] create listen socket"),
+ case ?LISTEN(Config, 0, [{active,false},binary,
+ {reuseaddr,true},{packet,0}]) of
+ {ok, L} ->
+ ?P("[server] listen socket created"),
+ {ok, Port} = inet:port(L),
+ Master ! {self(), listen_port, Port},
+ ?P("[server] await continue"),
+ receive
+ {Master, continue} ->
+ ?P("[server] continue"),
+ busy_send_srv(L, Master, Msg)
+ end;
+ {error, Reason} ->
+ ?P("[server] UNEXPECTED: ~w", [Reason]),
+ ?SKIPE(listen_failed_str(Reason))
+ end
+ end,
+ Server = spawn_link(ServerF),
+ ?P("[master] server: ~p", [Server]),
+ ListenPort =
+ receive
+ {'EXIT', Server, {skip, _} = SKIP} ->
+ throw(SKIP);
+
+ {'EXIT', Server, Reason} ->
+ exit({server, Reason});
+
+ {Server, listen_port, LP} ->
+ ?P("listen port: ~p", [LP]),
+ LP
+ end,
+ ?P("[master] start client"),
+ ClientF =
+ fun () ->
+ ?P("[client] await (connect) server port"),
+ Port =
+ receive
+ {Master, connect, P} ->
+ P
+ end,
+ ?P("[client] connect to ~w", [Port]),
+ case ?CONNECT(Config, "localhost", Port,
+ [{active,false},binary,{packet,0}]) of
+ {ok, Socket} ->
+ Master ! {self(), connected},
+ ?P("[client] connected - await recv"),
+ receive
+ {Master, recv, N} ->
+ ?P("[client] received recv:~w", [N]),
+ busy_send_client_loop(Socket, Master, Msg, N)
+ end;
+ {error, eaddrnotavail = CReason} ->
+ ?P("[client] UNEXPECTED: ~w", [CReason]),
+ ?SKIPE(connect_failed_str(CReason))
+ end
+ end,
+ Client = spawn_link(ClientF),
+ ?P("[master] client: ~p", [Client]),
+ Server ! {self(), continue},
+ Client ! {self(), connect, ListenPort},
receive
- {Server,client,Client} ->
- io:format("~p Client~n", [Client]),
- busy_send_loop(Server, Client, 0)
- end.
+ {Client, connected} ->
+ ?P("[master] client connected"),
+ ok
+ end,
+ busy_send_loop(Server, Client, 0),
+ process_flag(trap_exit, OldFlag),
+ ?P("[master] done"),
+ ok.
busy_send_loop(Server, Client, N) ->
%% Master
%%
- receive {Server,send} ->
- busy_send_loop(Server, Client, N+1)
- after 2000 ->
- %% Send queue full, sender blocked
- %% -> stop sender and release client
- io:format("Send timeout, time to receive...~n", []),
- Server ! {self(),close},
- Client ! {self(),recv,N+1},
- receive
- {Server,send} ->
- busy_send_2(Server, Client, N+1)
- after 10000 ->
- %% If this happens, see busy_send_srv
- ct:fail({timeout,{server,not_send,flush([])}})
- end
+ receive
+ {Server, send} ->
+ busy_send_loop(Server, Client, N+1)
+
+ after 2000 ->
+ ?P("[master] send timeout: server send queue full (N = ~w+1)", [N]),
+ %% Send queue full, sender blocked
+ %% -> stop sender and release client
+ ?P("Send timeout, time to receive..."),
+ Server ! {self(), close},
+ Client ! {self(), recv, N+1},
+ receive
+ {Server, send} ->
+ busy_send_2(Server, Client, N+1)
+ after 10000 ->
+ %% If this happens, see busy_send_srv
+ ?P("UNEXPECTED: server send timeout"),
+ ct:fail({timeout,{server,not_send,flush([])}})
+ end
end.
busy_send_2(Server, Client, _N) ->
%% Master
%%
receive
- {Server,[closed]} ->
- receive {Client,[0,{error,closed}]} -> ok end
+ {Server, [closed]} ->
+ ?P("[master] received expected (server) closed"),
+ receive
+ {Client, [0,{error,closed}]} ->
+ ?P("[master] received expected (client) closed"),
+ ok
+ end
after 10000 ->
- ct:fail({timeout,{server,not_closed,flush([])}})
+ ?P("UNEXPECTED: server closed timeout"),
+ ct:fail({timeout, {server, not_closed, flush([])}})
end.
busy_send_srv(L, Master, Msg) ->
@@ -1486,45 +2026,38 @@ busy_send_srv(L, Master, Msg) ->
%% Sometimes this accept does not return, do not really know why
%% but is causes the timeout error in busy_send_loop to be
%% triggered. Only happens on OS X Leopard?!?
- {ok,Socket} = gen_tcp:accept(L),
- busy_send_srv_loop(Socket, Master, Msg).
+ case gen_tcp:accept(L) of
+ {ok, Socket} ->
+ ?P("[server] accepted"),
+ busy_send_srv_loop(Socket, Master, Msg);
+ {error, Reason} ->
+ ?P("UNEXPECTED: server accept failure: ~p", [Reason]),
+ ?SKIPE(accept_failed_str(Reason))
+ end.
busy_send_srv_loop(Socket, Master, Msg) ->
%% Server
%%
receive
- {Master,close} ->
+ {Master, close} ->
+ ?P("[server] received close"),
ok = gen_tcp:close(Socket),
Master ! {self(),flush([closed])}
after 0 ->
ok = gen_tcp:send(Socket, Msg),
- Master ! {self(),send},
+ Master ! {self(), send},
busy_send_srv_loop(Socket, Master, Msg)
end.
-busy_send_client(Port, Master, Msg) ->
- %% Client
- %%
- spawn_link(
- fun () ->
- {ok,Socket} = gen_tcp:connect(
- "localhost", Port,
- [{active,false},binary,{packet,0}]),
- receive
- {Master,recv, N} ->
- busy_send_client_loop(Socket, Master, Msg, N)
- end
- end).
-
busy_send_client_loop(Socket, Master, Msg, N) ->
%% Client
%%
Size = byte_size(Msg),
case gen_tcp:recv(Socket, Size) of
- {ok,Msg} ->
+ {ok, Msg} ->
busy_send_client_loop(Socket, Master, Msg, N-1);
Other ->
- Master ! {self(),flush([Other,N])}
+ Master ! {self(), flush([Other,N])}
end.
%%%
@@ -1534,18 +2067,28 @@ busy_send_client_loop(Socket, Master, Msg, N) ->
%%%
busy_disconnect_passive(Config) when is_list(Config) ->
+ ?TC_TRY(busy_disconnect_passive,
+ fun() -> do_busy_disconnect_passive(Config) end).
+
+do_busy_disconnect_passive(Config) ->
+ ?P("[passive] begin"),
MuchoData = list_to_binary(ones(64*1024)),
- [do_busy_disconnect_passive(MuchoData) || _ <- lists:seq(1, 10)],
+ [do_busy_disconnect_passive2(Config, MuchoData, N) || N <- lists:seq(1, 10)],
+ ?P("[passive] done"),
ok.
-do_busy_disconnect_passive(MuchoData) ->
- S = busy_disconnect_prepare_server([{active,false}]),
+do_busy_disconnect_passive2(Config, MuchoData, N) ->
+ ?P("[passive,~w] *** prepare server *** ", [N]),
+ {_Server, S} = busy_disconnect_prepare_server(Config, [{active,false}]),
+ ?P("[passive,~w] server prepared - start sending", [N]),
busy_disconnect_passive_send(S, MuchoData).
busy_disconnect_passive_send(S, Data) ->
case gen_tcp:send(S, Data) of
- ok -> busy_disconnect_passive_send(S, Data);
- {error,closed} -> ok
+ ok ->
+ busy_disconnect_passive_send(S, Data);
+ {error, closed} ->
+ ok
end.
%%%
@@ -1554,40 +2097,103 @@ busy_disconnect_passive_send(S, Data) ->
%%% a {tcp_closed,Socket} message. (Active mode.)
%%%
busy_disconnect_active(Config) when is_list(Config) ->
+ ?TC_TRY(busy_disconnect_active,
+ fun() -> do_busy_disconnect_active(Config) end).
+
+do_busy_disconnect_active(Config) ->
+ ?P("[active] begin"),
MuchoData = list_to_binary(ones(64*1024)),
- [do_busy_disconnect_active(MuchoData) || _ <- lists:seq(1, 10)],
+ [do_busy_disconnect_active2(Config, MuchoData, N) || N <- lists:seq(1, 10)],
+ ?P("[active] done"),
ok.
-do_busy_disconnect_active(MuchoData) ->
- S = busy_disconnect_prepare_server([{active,true}]),
- busy_disconnect_active_send(S, MuchoData).
+do_busy_disconnect_active2(Config, MuchoData, N) ->
+ ?P("[active,~w] *** prepare server *** ", [N]),
+ {Server, S} = busy_disconnect_prepare_server(Config, [{active,true}]),
+ ?P("[active,~w] server prepared - start sending", [N]),
+ busy_disconnect_active_send(Server, S, MuchoData).
-busy_disconnect_active_send(S, Data) ->
+busy_disconnect_active_send(Server, S, Data) ->
case gen_tcp:send(S, Data) of
- ok -> busy_disconnect_active_send(S, Data);
- {error,closed} ->
- receive
- {tcp_closed,S} -> ok;
- _Other -> ct:fail(failed)
- end
+ ok ->
+ busy_disconnect_active_send(Server, S, Data);
+ {error, closed} ->
+ ?P("[active-sender] send failed with closed - await tcp-closed"),
+ busy_disconnect_active_send_await_closed(Server, S);
+ {error, einval = Reason} ->
+ ?P("[active-sender] UNEXPECTED send failure:"
+ "~n ~p", [Reason]),
+ ?SKIPT(send_failed_str(Reason));
+
+ {error, Reason} ->
+ ?P("[active-sender] UNEXPECTED send failure:"
+ "~n ~p", [Reason]),
+ ct:fail({unexpected_send_result, Reason})
end.
+busy_disconnect_active_send_await_closed(Server, S) ->
+ busy_disconnect_active_send_await_closed(Server, S, false, false).
+busy_disconnect_active_send_await_closed(Server, S, Closed, Stopped) ->
+ receive
+ {tcp_closed, S} when (Stopped =:= true) ->
+ ?P("[active-sender] received tcp-closed - done"),
+ ok;
+
+ {tcp_closed, S} ->
+ ?P("[active-sender] received tcp-closed"),
+ busy_disconnect_active_send_await_closed(Server, S, true, Stopped);
+
+ {'EXIT', Server, normal} when (Closed =:= true) ->
+ ?P("[active-sender] received server (normal) exit - done"),
+ ok;
+
+ {'EXIT', Server, normal} ->
+ ?P("[active-sender] received server (normal) exit"),
+ busy_disconnect_active_send_await_closed(Server, S, Closed, true);
+
+ Other ->
+ ?P("[active-sender] received UNEXPECTED message:"
+ "~n Expected tcp-close of ~p"
+ "~n Server: ~p"
+ "~n Unexpected message: ~p", [S, Server, Other]),
+ ct:fail({unexpected, Other, S, flush([])})
+ end.
+
-busy_disconnect_prepare_server(ConnectOpts) ->
+busy_disconnect_prepare_server(Config, ConnectOpts) ->
Sender = self(),
- Server = spawn_link(fun() -> busy_disconnect_server(Sender) end),
- receive {port,Server,Port} -> ok end,
- {ok,S} = gen_tcp:connect(localhost, Port, ConnectOpts),
- Server ! {Sender,sending},
- S.
-
-busy_disconnect_server(Sender) ->
- {ok,L} = gen_tcp:listen(0, [{active,false},binary,{reuseaddr,true},{packet,0}]),
+ ?P("[prep-server] create server"),
+ Server = spawn_link(fun() -> busy_disconnect_server(Config, Sender) end),
+ ?P("[prep-server] await port (from server)"),
+ receive {port, Server, Port} -> ok end,
+ ?P("[prep-server] connect to ~w", [Port]),
+ case ?CONNECT(Config, localhost, Port, ConnectOpts) of
+ {ok, S} ->
+ ?P("[prep-server] connected - order server start sending"),
+ Server ! {Sender, sending},
+ ?P("[prep-server] done"),
+ {Server, S};
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end.
+
+busy_disconnect_server(Config, Sender) ->
+ ?P("[server] create listen socket"),
+ {ok, L} = ?LISTEN(Config, 0,
+ [{active,false},
+ binary,
+ {reuseaddr,true},
+ {packet,0}]),
+ ?P("[server] created - get port number"),
{ok,Port} = inet:port(L),
+ ?P("[server] send port ~w (to sender)", [Port]),
Sender ! {port,self(),Port},
+ ?P("[server] try accept"),
{ok,S} = gen_tcp:accept(L),
+ ?P("[server] connection accepted"),
receive
- {Sender,sending} ->
+ {Sender, sending} ->
+ ?P("[server] received sending (from sender)"),
busy_disconnect_server_wait_for_busy(Sender, S)
end.
@@ -1595,184 +2201,287 @@ busy_disconnect_server(Sender) ->
%% a busy port.
busy_disconnect_server_wait_for_busy(Sender, S) ->
case process_info(Sender, status) of
- {status,waiting} ->
+ {status, waiting = Status} ->
%% We KNOW that the sender will be in state 'waiting' only
%% if the port has become busy. (Fallback solution if the
%% implementation changes: Watch Sender's reduction count;
%% when it stops changing, wait 2 seconds and then close.)
+ ?P("[server] sender status ~p => close socket", [Status]),
gen_tcp:close(S);
- _Other ->
- io:format("~p\n", [_Other]),
+ {status, Status} ->
+ ?P("[server] sender status ~p", [Status]),
+ timer:sleep(100),
+ busy_disconnect_server_wait_for_busy(Sender, S);
+ Other ->
+ ?P("[server] sender status ~p", [Other]),
timer:sleep(100),
busy_disconnect_server_wait_for_busy(Sender, S)
end.
+
%%%
%%% Fill send queue
%%%
fill_sendq(Config) when is_list(Config) ->
- Master = self(),
- Server =
- spawn_link(fun () ->
- {ok,L} = gen_tcp:listen(0, [{active,false},binary,
- {reuseaddr,true},{packet,0}]),
- {ok,Port} = inet:port(L),
- Master ! {self(),client,
- fill_sendq_client(Port, Master)},
- fill_sendq_srv(L, Master)
- end),
- io:format("~p Server~n", [Server]),
- receive
- {Server,client,Client} ->
- io:format("~p Client~n", [Client]),
- receive
- {Server,reader,Reader} ->
- io:format("~p Reader~n", [Reader]),
- fill_sendq_loop(Server, Client, Reader)
- end
- end.
+ ?TC_TRY(fill_sendq, fun() -> do_fill_sendq(Config) end).
+
+do_fill_sendq(Config) ->
+ OldFlag = process_flag(trap_exit, true),
+ Master = self(),
+ ServerF =
+ fun () ->
+ ?P("[server] try listen"),
+ case ?LISTEN(Config, 0, [{active,false},binary,
+ {reuseaddr,true},{packet,0}]) of
+ {ok, L} ->
+ ?P("[server] try port"),
+ case inet:port(L) of
+ {ok, Port} ->
+ Master ! {self(), listen_port, Port},
+ fill_sendq_srv(L, Master);
+ {error, PReason} ->
+ ?SKIPE(port_failed_str(PReason))
+ end;
+ {error, LReason} ->
+ ?SKIPE(listen_failed_str(LReason))
+ end
+ end,
+ Server = spawn_link(ServerF),
+ ?P("[master] server: ~p", [Server]),
+ ClientF =
+ fun () ->
+ ?P("[client] await server port"),
+ ServerPort =
+ receive
+ {Master, server_port, SP} ->
+ ?P("[client] server port: ~w", [SP]),
+ SP
+ end,
+ %% Just close on order
+ ?P("[client] try connect"),
+ case ?CONNECT(Config,
+ "localhost", ServerPort,
+ [{active,false},binary,{packet,0}]) of
+ {ok, S} ->
+ ?P("[client] connected"),
+ Master ! {self(), connected},
+ receive
+ {Master, close} ->
+ ?P("[client] received close"),
+ ok = gen_tcp:close(S)
+ end;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPE(connect_failed_str(Reason))
+ end
+ end,
+ Client = spawn_link(ClientF),
+ ?P("[master] client: ~p", [client]),
+ ListenPort =
+ receive
+ {Server, listen_port, LP} ->
+ ?P("[master] listen port: ~w", [LP]),
+ LP
+ end,
+ Client ! {self(), server_port, ListenPort},
+ ?P("[master] await client connected"),
+ receive {Client, connected} -> ok end,
+ ?P("[master] await reader"),
+ Res = receive
+ {Server, reader, Reader} ->
+ ?P("[master] reader: ~p", [Reader]),
+ fill_sendq_loop(Server, Client, Reader)
+ end,
+ process_flag(trap_exit, OldFlag),
+ ?P("[master] done"),
+ Res.
fill_sendq_loop(Server, Client, Reader) ->
%% Master
%%
receive
- {Server,send} ->
+ {Server, send} ->
fill_sendq_loop(Server, Client, Reader)
after 2000 ->
%% Send queue full, sender blocked -> close client.
- io:format("Send timeout, closing Client...~n", []),
- Client ! {self(),close},
+ ?P("[master] send timeout, closing client"),
+ Client ! {self(), close},
receive
- {Server,[{error,closed}]} ->
- io:format("Got server closed.~n"),
+ {Server, [{error,closed}] = SErrors} ->
+ ?P("[master] got expected server closed"),
receive
- {Reader,[{error,closed}]} ->
- io:format("Got reader closed.~n"),
- ok
+ {Reader, [{error,closed}]} ->
+ ?P("[master] got expected reader closed"),
+ ok;
+ {Reader, RErrors} when is_list(RErrors) ->
+ ct:fail([{server, SErrors}, {reader, RErrors}])
after 3000 ->
ct:fail({timeout,{closed,reader}})
end;
- {Reader,[{error,closed}]} ->
- io:format("Got reader closed.~n"),
+
+ {Server, SErrors} when is_list(SErrors) ->
+ ?P("UNEXPECTED SERVER ERROR(S): "
+ "~n ~p"
+ "~n ~p", [SErrors, flush([])]),
+ ct:fail([{server, SErrors}, {reader, []}]);
+
+ {Reader, [{error,closed}] = RErrors} ->
+ ?P("[master] got expected reader closed"),
receive
- {Server,[{error,closed}]} ->
- io:format("Got server closed~n"),
- ok
+ {Server, [{error,closed}]} ->
+ ?P("[master] got expected server closed"),
+ ok;
+ {Server, SErrors} when is_list(SErrors) ->
+ ct:fail([{server, SErrors}, {reader, RErrors}])
after 3000 ->
ct:fail({timeout,{closed,server}})
- end
+ end;
+
+ {Reader, RErrors} when is_list(RErrors) ->
+ ?P("UNEXPECTED READER ERROR(S): "
+ "~n ~p"
+ "~n ~p", [RErrors, flush([])]),
+ ct:fail([{server, []}, {reader, RErrors}])
+
after 3000 ->
- ct:fail({timeout,{closed,[server,reader]}})
+ Msgs = flush([]),
+ ct:fail({timeout,{closed,[server,reader]}, Msgs})
end
end.
fill_sendq_srv(L, Master) ->
%% Server
%%
+ ?P("[server] await accept"),
case gen_tcp:accept(L) of
- {ok,S} ->
- Master ! {self(),reader,
+ {ok, S} ->
+ ?P("[server] accepted ~p", [S]),
+ Master ! {self(), reader,
spawn_link(fun () -> fill_sendq_read(S, Master) end)},
Msg = "the quick brown fox jumps over a lazy dog~n",
fill_sendq_write(S, Master, [Msg,Msg,Msg,Msg,Msg,Msg,Msg,Msg]);
- Error ->
- io:format("~p error: ~p.~n", [self(),Error]),
- Master ! {self(),flush([Error])}
+ E ->
+ Error = flush([E]),
+ ?P("[server] accept error: ~p", [E]),
+ Master ! {self(), Error}
end.
fill_sendq_write(S, Master, Msg) ->
%% Server
%%
- %%io:format("~p sending...~n", [self()]),
- Master ! {self(),send},
+ %% ?P("[server] sending..."),
case gen_tcp:send(S, Msg) of
ok ->
- %%io:format("~p ok.~n", [self()]),
+ Master ! {self(), send},
+ %% ?P("[server] ok."),
fill_sendq_write(S, Master, Msg);
- E ->
+ {error, _} = E ->
Error = flush([E]),
- io:format("~p error: ~p.~n", [self(),Error]),
- Master ! {self(),Error}
+ ?P("[server] send error: ~p", [Error]),
+ Master ! {self(), Error}
end.
fill_sendq_read(S, Master) ->
%% Reader
%%
- io:format("~p read infinity...~n", [self()]),
+ ?P("[reader] read infinity..."),
case gen_tcp:recv(S, 0, infinity) of
- {ok,Data} ->
- io:format("~p got: ~p.~n", [self(),Data]),
+ {ok, Data} ->
+ ?P("[reader] recv: ~p", [Data]),
fill_sendq_read(S, Master);
E ->
Error = flush([E]),
- io:format("~p error: ~p.~n", [self(),Error]),
- Master ! {self(),Error}
+ ?P("[reader] recv error: ~p", [Error]),
+ Master ! {self(), Error}
end.
-fill_sendq_client(Port, Master) ->
- %% Client
- %%
- spawn_link(fun () ->
- %% Just close on order
- {ok,S} = gen_tcp:connect(
- "localhost", Port,
- [{active,false},binary,{packet,0}]),
- receive
- {Master,close} ->
- ok = gen_tcp:close(S)
- end
- end).
%%% Try to receive more than available number of bytes from
%%% a closed socket.
%%%
partial_recv_and_close(Config) when is_list(Config) ->
+ try do_partial_recv_and_close(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_partial_recv_and_close(Config) ->
Msg = "the quick brown fox jumps over a lazy dog 0123456789\n",
Len = length(Msg),
- {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ {ok,L} = ?LISTEN(Config, 0, [{active,false}]),
{ok,P} = inet:port(L),
- {ok,S} = gen_tcp:connect("localhost", P, [{active,false}]),
- {ok,A} = gen_tcp:accept(L),
- ok = gen_tcp:send(S, Msg),
- ok = gen_tcp:close(S),
- {error,closed} = gen_tcp:recv(A, Len+1),
+ Sock =
+ case ?CONNECT(Config, "localhost", P, [{active,false}]) of
+ {ok, S} ->
+ S;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ {ok, A} = gen_tcp:accept(L),
+ ok = gen_tcp:send(Sock, Msg),
+ ok = gen_tcp:close(Sock),
+ {error, closed} = gen_tcp:recv(A, Len+1),
ok.
%%% Try to receive more than available number of bytes from
%%% a closed socket, this time waiting in the recv before closing.
%%%
partial_recv_and_close_2(Config) when is_list(Config) ->
+ OldFlag = process_flag(trap_exit, true),
+ Res = try do_partial_recv_and_close_2(Config)
+ catch
+ exit:{skip, _} = SKIP ->
+ SKIP
+ end,
+ process_flag(trap_exit, OldFlag),
+ Res.
+
+do_partial_recv_and_close_2(Config) ->
Msg = "the quick brown fox jumps over a lazy dog 0123456789\n",
Len = length(Msg),
- {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ {ok,L} = ?LISTEN(Config, 0, [{active,false}]),
{ok,P} = inet:port(L),
Server = self(),
Client =
spawn_link(
fun () ->
receive after 2000 -> ok end,
- {ok,S} = gen_tcp:connect("localhost", P, [{active,false}]),
- ok = gen_tcp:send(S, Msg),
- receive {Server,close} -> ok end,
- receive after 2000 -> ok end,
- ok = gen_tcp:close(S)
+ case ?CONNECT(Config, "localhost", P, [{active,false}]) of
+ {ok, S} ->
+ ok = gen_tcp:send(S, Msg),
+ receive {Server,close} -> ok end,
+ receive after 2000 -> ok end,
+ ok = gen_tcp:close(S);
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPE(connect_failed_str(Reason))
+ end
end),
- {ok,A} = gen_tcp:accept(L),
- Client ! {Server,close},
- {error,closed} = gen_tcp:recv(A, Len+1),
+ {ok, A} = gen_tcp:accept(L),
+ Client ! {Server, close},
+ {error, closed} = gen_tcp:recv(A, Len+1),
ok.
%%% Here we tests that gen_tcp:recv/2 will return {error,closed} following
%%% a send operation of a huge amount data when the other end closed the socket.
%%%
partial_recv_and_close_3(Config) when is_list(Config) ->
- [do_partial_recv_and_close_3() || _ <- lists:seq(0, 20)],
+ OldFlag = process_flag(trap_exit, true),
+ Res = try do_partial_recv_and_close_3(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end,
+ process_flag(trap_exit, OldFlag),
+ Res.
+
+do_partial_recv_and_close_3(Config) ->
+ [do_partial_recv_and_close_4(Config) || _ <- lists:seq(0, 20)],
ok.
-do_partial_recv_and_close_3() ->
+do_partial_recv_and_close_4(Config) ->
Parent = self(),
spawn_link(fun() ->
- {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ {ok,L} = ?LISTEN(Config, 0, [{active,false}]),
{ok,{_,Port}} = inet:sockname(L),
Parent ! {port,Port},
{ok,S} = gen_tcp:accept(L),
@@ -1783,91 +2492,158 @@ do_partial_recv_and_close_3() ->
{port,Port} -> ok
end,
Much = ones(8*64*1024),
- {ok,S} = gen_tcp:connect(localhost, Port, [{active,false}]),
+ S = case ?CONNECT(Config, localhost, Port, [{active, false}]) of
+ {ok, Sock} ->
+ Sock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
- %% Send a lot of data (most of it will be queued). The receiver will read one byte
- %% and close the connection. The write operation will fail.
+ %% Send a lot of data (most of it will be queued).
+ %% The receiver will read one byte and close the connection.
+ %% The write operation will fail.
gen_tcp:send(S, Much),
%% We should always get {error,closed} here.
- {error,closed} = gen_tcp:recv(S, 0).
+ {error, closed} = gen_tcp:recv(S, 0).
-test_prio_put_get() ->
+test_prio_put_get(Config) ->
Tos = 3 bsl 5,
- {ok,L1} = gen_tcp:listen(0, [{active,false}]),
+ ?P("test_prio_put_get -> create listen socket"),
+ {ok,L1} = ?LISTEN(Config, 0, [{active,false}]),
+ ?P("test_prio_put_get -> set opts prio (= 3)"),
ok = inet:setopts(L1,[{priority,3}]),
+ ?P("test_prio_put_get -> set opts tos (= ~p)", [Tos]),
ok = inet:setopts(L1,[{tos,Tos}]),
+ ?P("test_prio_put_get -> verify opts prio and tos"),
{ok,[{priority,3},{tos,Tos}]} = inet:getopts(L1,[priority,tos]),
+ ?P("test_prio_put_get -> set opts prio (= 3)"),
ok = inet:setopts(L1,[{priority,3}]), % Dont destroy each other
+ ?P("test_prio_put_get -> verify opts prio and tos"),
{ok,[{priority,3},{tos,Tos}]} = inet:getopts(L1,[priority,tos]),
+ ?P("test_prio_put_get -> set opts reuseaddr (= true)"),
ok = inet:setopts(L1,[{reuseaddr,true}]), % Dont let others destroy
+ ?P("test_prio_put_get -> verify opts prio and tos"),
{ok,[{priority,3},{tos,Tos}]} = inet:getopts(L1,[priority,tos]),
+ ?P("test_prio_put_get -> close listen socket"),
gen_tcp:close(L1),
+ ?P("test_prio_put_get -> done"),
ok.
-test_prio_accept() ->
- {ok,Sock}=gen_tcp:listen(0,[binary,{packet,0},{active,false},
- {reuseaddr,true},{priority,4}]),
- {ok,Port} = inet:port(Sock),
- {ok,Sock2}=gen_tcp:connect("localhost",Port,[binary,{packet,0},
- {active,false},
- {reuseaddr,true},
- {priority,4}]),
- {ok,Sock3}=gen_tcp:accept(Sock),
- {ok,[{priority,4}]} = inet:getopts(Sock,[priority]),
- {ok,[{priority,4}]} = inet:getopts(Sock2,[priority]),
- {ok,[{priority,4}]} = inet:getopts(Sock3,[priority]),
+
+test_prio_accept(Config) ->
+ ?P("test_prio_accept -> create listen socket"),
+ {ok, Sock} = ?LISTEN(Config, 0, [binary,{packet,0},{active,false},
+ {reuseaddr,true},{priority,4}]),
+ ?P("test_prio_accept -> get port number of listen socket"),
+ {ok, Port} = inet:port(Sock),
+ ?P("test_prio_accept -> connect to port ~p", [Port]),
+ Sock2 = case ?CONNECT(Config, "localhost",Port,[binary,{packet,0},
+ {active,false},
+ {reuseaddr,true},
+ {priority,4}]) of
+ {ok, S2} ->
+ S2;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("test_prio_accept -> connected => accept connection"),
+ {ok, Sock3} = gen_tcp:accept(Sock),
+ ?P("test_prio_accept -> accepted => getopts prio for listen socket"),
+ {ok, [{priority,4}]} = inet:getopts(Sock, [priority]),
+ ?P("test_prio_accept -> getopts prio for connected socket"),
+ {ok, [{priority,4}]} = inet:getopts(Sock2, [priority]),
+ ?P("test_prio_accept -> getopts prio for accepted socket"),
+ {ok, [{priority,4}]} = inet:getopts(Sock3, [priority]),
+ ?P("test_prio_accept -> close listen socket"),
gen_tcp:close(Sock),
+ ?P("test_prio_accept -> close connected socket"),
gen_tcp:close(Sock2),
+ ?P("test_prio_accept -> close accepted socket"),
gen_tcp:close(Sock3),
+ ?P("test_prio_accept -> done"),
ok.
-test_prio_accept2() ->
+test_prio_accept2(Config) ->
Tos1 = 4 bsl 5,
Tos2 = 3 bsl 5,
- {ok,Sock}=gen_tcp:listen(0,[binary,{packet,0},{active,false},
- {reuseaddr,true},{priority,4},
- {tos,Tos1}]),
- {ok,Port} = inet:port(Sock),
- {ok,Sock2}=gen_tcp:connect("localhost",Port,[binary,{packet,0},
- {active,false},
- {reuseaddr,true},
- {priority,4},
- {tos,Tos2}]),
- {ok,Sock3}=gen_tcp:accept(Sock),
+ ?P("test_prio_accept2 -> create listen socket"),
+ {ok, Sock} = ?LISTEN(Config, 0,[binary,{packet,0},{active,false},
+ {reuseaddr,true},{priority,4},
+ {tos,Tos1}]),
+ ?P("test_prio_accept2 -> get port number of listen socket"),
+ {ok, Port} = inet:port(Sock),
+ ?P("test_prio_accept2 -> connect to port ~p", [Port]),
+ Sock2 = case ?CONNECT(Config, "localhost",Port,[binary,{packet,0},
+ {active,false},
+ {reuseaddr,true},
+ {priority,4},
+ {tos,Tos2}]) of
+ {ok, S2} ->
+ S2;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("test_prio_accept2 -> connected => accept connection"),
+ {ok, Sock3} = gen_tcp:accept(Sock),
+ ?P("test_prio_accept2 -> accepted => getopts prio and tos for listen socket"),
{ok,[{priority,4},{tos,Tos1}]} = inet:getopts(Sock,[priority,tos]),
+ ?P("test_prio_accept2 -> getopts prio and tos for connected socket"),
{ok,[{priority,4},{tos,Tos2}]} = inet:getopts(Sock2,[priority,tos]),
+ ?P("test_prio_accept2 -> getopts prio and tos for accepted socket"),
{ok,[{priority,4},{tos,Tos1}]} = inet:getopts(Sock3,[priority,tos]),
+ ?P("test_prio_accept2 -> close listen socket"),
gen_tcp:close(Sock),
+ ?P("test_prio_accept2 -> close connected socket"),
gen_tcp:close(Sock2),
+ ?P("test_prio_accept2 -> close accepted socket"),
gen_tcp:close(Sock3),
+ ?P("test_prio_accept2 -> done"),
ok.
-test_prio_accept3() ->
+test_prio_accept3(Config) ->
Tos1 = 4 bsl 5,
Tos2 = 3 bsl 5,
- {ok,Sock}=gen_tcp:listen(0,[binary,{packet,0},{active,false},
- {reuseaddr,true},
- {tos,Tos1}]),
+ ?P("test_prio_accept3 -> create listen socket"),
+ {ok, Sock} = ?LISTEN(Config, 0,[binary,{packet,0},{active,false},
+ {reuseaddr,true},
+ {tos,Tos1}]),
+ ?P("test_prio_accept3 -> get port number of listen socket"),
{ok,Port} = inet:port(Sock),
- {ok,Sock2}=gen_tcp:connect("localhost",Port,[binary,{packet,0},
- {active,false},
- {reuseaddr,true},
- {tos,Tos2}]),
- {ok,Sock3}=gen_tcp:accept(Sock),
- {ok,[{priority,0},{tos,Tos1}]} = inet:getopts(Sock,[priority,tos]),
- {ok,[{priority,0},{tos,Tos2}]} = inet:getopts(Sock2,[priority,tos]),
- {ok,[{priority,0},{tos,Tos1}]} = inet:getopts(Sock3,[priority,tos]),
+ ?P("test_prio_accept3 -> connect to port ~p", [Port]),
+ Sock2 = case ?CONNECT(Config, "localhost",Port,[binary,{packet,0},
+ {active,false},
+ {reuseaddr,true},
+ {tos,Tos2}]) of
+ {ok, S2} ->
+ S2;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("test_prio_accept3 -> connected => accept connection"),
+ {ok, Sock3} = gen_tcp:accept(Sock),
+ ?P("test_prio_accept3 -> "
+ "accepted => getopts prio and tos for listen socket"),
+ {ok, [{priority,0},{tos,Tos1}]} = inet:getopts(Sock, [priority,tos]),
+ ?P("test_prio_accept3 -> getopts prio and tos for connected socket"),
+ {ok, [{priority,0},{tos,Tos2}]} = inet:getopts(Sock2, [priority,tos]),
+ ?P("test_prio_accept3 -> getopts prio and tos for accepted socket"),
+ {ok, [{priority,0},{tos,Tos1}]} = inet:getopts(Sock3, [priority,tos]),
+ ?P("test_prio_accept3 -> close listen socket"),
gen_tcp:close(Sock),
+ ?P("test_prio_accept3 -> close connected socket"),
gen_tcp:close(Sock2),
+ ?P("test_prio_accept3 -> close accepted socket"),
gen_tcp:close(Sock3),
+ ?P("test_prio_accept3 -> done"),
ok.
-test_prio_accept_async() ->
+test_prio_accept_async(Config) ->
Tos1 = 4 bsl 5,
Tos2 = 3 bsl 5,
Ref = make_ref(),
- spawn(?MODULE,priority_server,[{self(),Ref}]),
+ ?P("test_prio_accept_async -> create prio server"),
+ spawn(?MODULE, priority_server, [Config, {self(),Ref}]),
Port = receive
{Ref,P} -> P
after 5000 -> ct:fail({error,"helper process timeout"})
@@ -1875,11 +2651,19 @@ test_prio_accept_async() ->
receive
after 3000 -> ok
end,
- {ok,Sock2}=gen_tcp:connect("localhost",Port,[binary,{packet,0},
- {active,false},
- {reuseaddr,true},
- {priority,4},
- {tos,Tos2}]),
+ ?P("test_prio_accept_async -> connect to port ~p", [Port]),
+ Sock2 = case ?CONNECT(Config, "localhost",Port,[binary,{packet,0},
+ {active,false},
+ {reuseaddr,true},
+ {priority,4},
+ {tos,Tos2}]) of
+ {ok, S2} ->
+ S2;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(connect_failed_str(Reason))
+ end,
+ ?P("test_prio_accept_async -> "
+ "connected => await prio and tos for listen socket"),
receive
{Ref,{ok,[{priority,4},{tos,Tos1}]}} ->
ok;
@@ -1887,6 +2671,7 @@ test_prio_accept_async() ->
ct:fail({missmatch,Error})
after 5000 -> ct:fail({error,"helper process timeout"})
end,
+ ?P("test_prio_accept_async -> await prio and tos for accepted socket"),
receive
{Ref,{ok,[{priority,4},{tos,Tos1}]}} ->
ok;
@@ -1895,13 +2680,16 @@ test_prio_accept_async() ->
after 5000 -> ct:fail({error,"helper process timeout"})
end,
- {ok,[{priority,4},{tos,Tos2}]} = inet:getopts(Sock2,[priority,tos]),
+ ?P("test_prio_accept_async -> getopts prio and tos for connected socket"),
+ {ok,[{priority,4},{tos,Tos2}]} = inet:getopts(Sock2, [priority,tos]),
+ ?P("test_prio_accept_async -> close connected socket"),
catch gen_tcp:close(Sock2),
+ ?P("test_prio_accept_async -> done"),
ok.
-priority_server({Parent,Ref}) ->
+priority_server(Config, {Parent,Ref}) ->
Tos1 = 4 bsl 5,
- {ok,Sock}=gen_tcp:listen(0,[binary,{packet,0},{active,false},
+ {ok,Sock}=?LISTEN(Config, 0,[binary,{packet,0},{active,false},
{reuseaddr,true},{priority,4},
{tos,Tos1}]),
{ok,Port} = inet:port(Sock),
@@ -1911,40 +2699,58 @@ priority_server({Parent,Ref}) ->
Parent ! {Ref, inet:getopts(Sock3,[priority,tos])},
ok.
-test_prio_fail() ->
- {ok,L} = gen_tcp:listen(0, [{active,false}]),
+test_prio_fail(Config) ->
+ ?P("test_prio_fail -> create listen socket"),
+ {ok,L} = ?LISTEN(Config, 0, [{active,false}]),
+ ?P("test_prio_fail -> try set (and fail) opts prio (= 1000)"),
{error,_} = inet:setopts(L,[{priority,1000}]),
+ ?P("test_prio_fail -> close listen socket"),
gen_tcp:close(L),
+ ?P("test_prio_fail -> done"),
ok.
test_prio_udp() ->
Tos = 3 bsl 5,
+ ?P("test_prio_udp -> create UDP socket (open)"),
{ok,S} = gen_udp:open(0,[{active,false},binary,{tos, Tos},
{priority,3}]),
+ ?P("test_prio_udp -> getopts prio and tos"),
{ok,[{priority,3},{tos,Tos}]} = inet:getopts(S,[priority,tos]),
+ ?P("test_prio_fail -> close socket"),
gen_udp:close(S),
+ ?P("test_prio_fail -> done"),
ok.
%% Tests the so_priority and ip_tos options on sockets when applicable.
so_priority(Config) when is_list(Config) ->
- {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ ?TC_TRY(so_priority, fun() -> do_so_priority(Config) end).
+
+do_so_priority(Config) ->
+ ?P("create listen socket"),
+ {ok,L} = ?LISTEN(Config, 0, [{active,false}]),
+ ?P("set opts on listen socket: prio to 1"),
ok = inet:setopts(L,[{priority,1}]),
+ ?P("verify prio"),
case inet:getopts(L,[priority]) of
{ok,[{priority,1}]} ->
+ ?P("close listen socket"),
gen_tcp:close(L),
- test_prio_put_get(),
- test_prio_accept(),
- test_prio_accept2(),
- test_prio_accept3(),
- test_prio_accept_async(),
- test_prio_fail(),
+ test_prio_put_get(Config),
+ test_prio_accept(Config),
+ test_prio_accept2(Config),
+ test_prio_accept3(Config),
+ test_prio_accept_async(Config),
+ test_prio_fail(Config),
test_prio_udp(),
+ ?P("done"),
ok;
- _ ->
+ _X ->
case os:type() of
{unix,linux} ->
case os:version() of
{X,Y,_} when (X > 2) or ((X =:= 2) and (Y >= 4)) ->
+ ?P("so prio should work on this version: "
+ "~n ~p", [_X]),
ct:fail({error,
"so_priority should work on this "
"OS, but does not"});
@@ -1971,27 +2777,30 @@ so_priority(Config) when is_list(Config) ->
%% It is only used for recvtclass that is an IPv6 option
%% and there we get valid values from both socket ends.
-recvtos(_Config) ->
+recvtos(Config) ->
test_pktoptions(
+ Config,
inet, [{recvtos,tos,96}],
fun recvtos_ok/2,
false).
-recvtosttl(_Config) ->
+recvtosttl(Config) ->
test_pktoptions(
+ Config,
inet, [{recvtos,tos,96},{recvttl,ttl,33}],
fun (OSType, OSVer) ->
recvtos_ok(OSType, OSVer) andalso recvttl_ok(OSType, OSVer)
end,
false).
-recvttl(_Config) ->
+recvttl(Config) ->
test_pktoptions(
+ Config,
inet, [{recvttl,ttl,33}],
fun recvttl_ok/2,
false).
-recvtclass(_Config) ->
+recvtclass(Config) ->
{ok,IFs} = inet:getifaddrs(),
case
[Name ||
@@ -2000,6 +2809,7 @@ recvtclass(_Config) ->
of
[_] ->
test_pktoptions(
+ Config,
inet6, [{recvtclass,tclass,224}],
fun recvtclass_ok/2,
true);
@@ -2019,10 +2829,11 @@ recvtclass(_Config) ->
%% platforms - change {unix,_} to false?
%% pktoptions is not supported for IPv4
-recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0});
-recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0});
+recvtos_ok({unix,netbsd}, _OSVer) -> false;
+recvtos_ok({unix,openbsd}, _OSVer) -> false; % not semver_lt(OSVer, {6,9,0});
+recvtos_ok({unix,darwin}, OSVer) -> false; % not semver_lt(OSVer, {19,6,0});
%% Using the option returns einval, so it is not implemented.
-recvtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0});
+recvtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,2,0});
recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0});
%% Does not return any value - not implemented for pktoptions
recvtos_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {3,1,0});
@@ -2031,10 +2842,11 @@ recvtos_ok({unix,_}, _) -> true;
recvtos_ok(_, _) -> false.
%% pktoptions is not supported for IPv4
-recvttl_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0});
-recvttl_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0});
+recvttl_ok({unix,netbsd}, _OSVer) -> false;
+recvttl_ok({unix,openbsd}, _OSVer) -> false; % not semver_lt(OSVer, {6,9,0});
+recvttl_ok({unix,darwin}, OSVer) -> false; % not semver_lt(OSVer, {19,6,0});
%% Using the option returns einval, so it is not implemented.
-recvttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0});
+recvttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,2,0});
recvttl_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0});
%% Does not return any value - not implemented for pktoptions
recvttl_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {2,7,0});
@@ -2043,41 +2855,57 @@ recvttl_ok({unix,_}, _) -> true;
recvttl_ok(_, _) -> false.
%% pktoptions is not supported for IPv6
-recvtclass_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0});
-recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0});
+recvtclass_ok({unix,netbsd}, _OSVer) -> false;
+recvtclass_ok({unix,openbsd}, _OSVer) -> false; % not semver_lt(OSVer, {6,9,0});
+recvtclass_ok({unix,darwin}, OSVer) -> false; % not semver_lt(OSVer, {19,6,0});
recvtclass_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0});
%% Using the option returns einval, so it is not implemented.
-recvtclass_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0});
+recvtclass_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,2,0});
%% Does not return any value - not implemented for pktoptions
recvtclass_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {3,1,0});
%%
recvtclass_ok({unix,_}, _) -> true;
recvtclass_ok(_, _) -> false.
-semver_lt({X1,Y1,Z1}, {X2,Y2,Z2}) ->
+semver_lt({X1,Y1,Z1} = V1, {X2,Y2,Z2} = V2) ->
+ ?P("semver_lt -> OS version check:"
+ "~n Version 1: ~p"
+ "~n Version 2: ~p", [V1, V2]),
if
- X1 > X2 -> false;
- X1 < X2 -> true;
- Y1 > Y2 -> false;
- Y1 < Y2 -> true;
- Z1 > Z2 -> false;
- Z1 < Z2 -> true;
- true -> false
+ X1 > X2 -> ?P("semver_lt -> X1 > X2: ~p > ~p", [X1, X2]), false;
+ X1 < X2 -> ?P("semver_lt -> X1 < X2: ~p < ~p", [X1, X2]), true;
+ Y1 > Y2 -> ?P("semver_lt -> Y1 > Y2: ~p > ~p", [Y1, Y2]), false;
+ Y1 < Y2 -> ?P("semver_lt -> Y1 < Y2: ~p < ~p", [Y1, Y2]), true;
+ Z1 > Z2 -> ?P("semver_lt -> Z1 > Z2: ~p > ~p", [Z1, Z2]), false;
+ Z1 < Z2 -> ?P("semver_lt -> Z1 < Z2: ~p < ~p", [Z1, Z2]), true;
+ true -> ?P("semver_lt -> default"), false
end;
-semver_lt(_, {_,_,_}) -> false.
+semver_lt(V1, {_,_,_} = V2) ->
+ ?P("semver_lt -> fallback OS version check when: "
+ "~n Version 1: ~p"
+ "~n Version 2: ~p", [V1, V2]),
+ false.
-test_pktoptions(Family, Spec, OSFilter, CheckConnect) ->
+test_pktoptions(Config, Family, Spec, OSFilter, CheckConnect) ->
OSType = os:type(),
- OSVer = os:version(),
+ OSVer = os:version(),
case OSFilter(OSType, OSVer) of
true ->
- io:format("Os: ~p, ~p~n", [OSType,OSVer]),
- test_pktoptions(Family, Spec, CheckConnect, OSType, OSVer);
+ ?P("OS: ~p, ~p", [OSType, OSVer]),
+ test_pktoptions(Config, Family, Spec, CheckConnect, OSType, OSVer);
false ->
{skip,{not_supported_for_os_version,{OSType,OSVer}}}
end.
%%
-test_pktoptions(Family, Spec, CheckConnect, OSType, OSVer) ->
+test_pktoptions(Config, Family, Spec, CheckConnect, OSType, OSVer) ->
+ ?P("test_pktoptions -> begin test with"
+ "~n Config: ~p"
+ "~n Family: ~p"
+ "~n Spec: ~p"
+ "~n CheckConnect: ~p"
+ "~n OSType: ~p"
+ "~n OSVer: ~p",
+ [Config, Family, Spec, CheckConnect, OSType, OSVer]),
Timeout = 5000,
RecvOpts = [RecvOpt || {RecvOpt,_,_} <- Spec],
TrueRecvOpts = [{RecvOpt,true} || {RecvOpt,_,_} <- Spec],
@@ -2093,28 +2921,47 @@ test_pktoptions(Family, Spec, CheckConnect, OSType, OSVer) ->
end,
%%
%% Set RecvOpts on listen socket
+ ?P("create listen socket with"
+ "~n ~p", [TrueRecvOpts]),
{ok,L} =
- gen_tcp:listen(
+ ?LISTEN(Config,
0,
[Family,binary,{active,false},{send_timeout,Timeout}
|TrueRecvOpts]),
{ok,P} = inet:port(L),
+ ?P("get (recv) options for listen socket: "
+ "~n ~p", [RecvOpts]),
{ok,TrueRecvOpts} = inet:getopts(L, RecvOpts),
+ ?P("get options for listen socket: "
+ "~n ~p", [Opts]),
{ok,OptsValsDefault} = inet:getopts(L, Opts),
+
%%
%% Set RecvOpts and Option values on connect socket
+ ?P("create connect socket with"
+ "~n ~p", [TrueRecvOpts ++ OptsVals]),
{ok,S2} =
- gen_tcp:connect(
- Address, P,
- [Family,binary,{active,false},{send_timeout,Timeout}
- |TrueRecvOpts ++ OptsVals],
- Timeout),
+ ?CONNECT(Config,
+ Address, P,
+ [Family,binary,{active,false},{send_timeout,Timeout}
+ |TrueRecvOpts ++ OptsVals],
+ Timeout),
+ ?P("get (recv) options for connect socket: "
+ "~n ~p", [RecvOpts]),
{ok,TrueRecvOpts} = inet:getopts(S2, RecvOpts),
+ ?P("get options for connect socket: "
+ "~n ~p", [Opts]),
{ok,OptsVals} = inet:getopts(S2, Opts),
+
%%
%% Accept socket inherits the options from listen socket
+ ?P("create accept socket"),
{ok,S1} = gen_tcp:accept(L, Timeout),
+ ?P("get (recv) options for accept socket: "
+ "~n ~p", [RecvOpts]),
{ok,TrueRecvOpts} = inet:getopts(S1, RecvOpts),
+ ?P("get options for accept socket: "
+ "~n ~p", [Opts]),
{ok,OptsValsDefault} = inet:getopts(S1, Opts),
%%% %%
%%% %% Handshake
@@ -2130,9 +2977,9 @@ test_pktoptions(Family, Spec, CheckConnect, OSType, OSVer) ->
{ok, [{pktoptions, PktOpts1}]} ->
PktOpts1;
{ok, UnexpOK1} ->
- io:format("Unexpected OK (~w): "
- "~n ~p"
- "~n", [Role, UnexpOK1]),
+ ?P("Unexpected OK (~w): "
+ "~n ~p"
+ "~n", [Role, UnexpOK1]),
exit({unexpected_getopts_ok,
Role,
Spec,
@@ -2141,9 +2988,9 @@ test_pktoptions(Family, Spec, CheckConnect, OSType, OSVer) ->
OptsValsDefault,
UnexpOK1});
{error, UnexpERR1} ->
- io:format("Unexpected ERROR (~w): "
- "~n ~p"
- "~n", [Role, UnexpERR1]),
+ ?P("Unexpected ERROR (~w): "
+ "~n ~p"
+ "~n", [Role, UnexpERR1]),
exit({unexpected_getopts_failure,
Role,
Spec,
@@ -2153,53 +3000,71 @@ test_pktoptions(Family, Spec, CheckConnect, OSType, OSVer) ->
UnexpERR1})
end
end,
+ ?P("verify dest (accept)"),
OptsVals1 = VerifyRemOpts(S1, dest),
+ ?P("verify orig (connect)"),
OptsVals2 = VerifyRemOpts(S2, orig),
%% {ok,[{pktoptions,OptsVals1}]} = inet:getopts(S1, [pktoptions]),
%% {ok,[{pktoptions,OptsVals2}]} = inet:getopts(S2, [pktoptions]),
(Result1 = sets_eq(OptsVals1, OptsVals))
- orelse io:format(
- "Accept differs: ~p neq ~p~n", [OptsVals1,OptsVals]),
+ orelse ?P("Accept differs: ~p neq ~p", [OptsVals1,OptsVals]),
(Result2 = sets_eq(OptsVals2, OptsValsDefault))
- orelse io:format(
- "Connect differs: ~p neq ~p~n",
- [OptsVals2,OptsValsDefault]),
+ orelse ?P("Connect differs: ~p neq ~p",
+ [OptsVals2, OptsValsDefault]),
%%
+ ?P("close connect socket"),
ok = gen_tcp:close(S2),
+ ?P("close accept socket"),
ok = gen_tcp:close(S1),
%%
%%
%% Clear RecvOpts on listen socket and set Option values
+ ?P("clear (recv) options on listen socket"),
ok = inet:setopts(L, FalseRecvOpts ++ OptsVals),
{ok,FalseRecvOpts} = inet:getopts(L, RecvOpts),
+ ?P("set options on listen socket"),
{ok,OptsVals} = inet:getopts(L, Opts),
+
%%
%% Set RecvOpts on connecting socket
%%
+ ?P("create connect socket with"
+ "~n ~p", [TrueRecvOpts]),
{ok,S4} =
- gen_tcp:connect(
+ ?CONNECT(Config,
Address, P,
[Family,binary,{active,false},{send_timeout,Timeout}
|TrueRecvOpts],
Timeout),
+ ?P("get (recv) options on connect socket"),
{ok,TrueRecvOpts} = inet:getopts(S4, RecvOpts),
+ ?P("get options on connect socket"),
{ok,OptsValsDefault} = inet:getopts(S4, Opts),
+
%%
%% Accept socket inherits the options from listen socket
+ ?P("create accept socket"),
{ok,S3} = gen_tcp:accept(L, Timeout),
+ ?P("get (recv) options on accept socket"),
{ok,FalseRecvOpts} = inet:getopts(S3, RecvOpts),
{ok,OptsVals} = inet:getopts(S3, Opts),
%%
%% Verify returned remote options
+ ?P("verify pktoptions on accept socket"),
{ok,[{pktoptions,[]}]} = inet:getopts(S3, [pktoptions]),
+ ?P("verify pktoptions on connect socket"),
{ok,[{pktoptions,OptsVals4}]} = inet:getopts(S4, [pktoptions]),
+ ?P("verify options set"),
(Result3 = sets_eq(OptsVals4, OptsVals))
- orelse io:format(
- "Accept2 differs: ~p neq ~p~n", [OptsVals4,OptsVals]),
+ orelse ?P("Accept2 differs: ~p neq ~p", [OptsVals4, OptsVals]),
%%
+ ?P("close connect socket"),
ok = gen_tcp:close(S4),
+ ?P("close accept socket"),
ok = gen_tcp:close(S3),
+ ?P("close listen socket"),
ok = gen_tcp:close(L),
+ ?P("verify final result"),
(Result1 and ((not CheckConnect) or (Result2 and Result3)))
orelse
exit({failed,
@@ -2207,6 +3072,7 @@ test_pktoptions(Family, Spec, CheckConnect, OSType, OSVer) ->
{OptsVals2,OptsValsDefault}],
{OSType,OSVer}}),
%% exit({{OSType,OSVer},success}), % In search for the truth
+ ?P("done"),
ok.
sets_eq(L1, L2) ->
@@ -2223,9 +3089,16 @@ collect_accepts(0,_) -> [];
collect_accepts(N,Tmo) ->
A = millis(),
receive
- {accepted,P,Msg} ->
+ {accepted, P, {error, eaddrnotavail = Reason}} ->
+ ?P("~p Failed accept: ~p", [P, Reason]),
+ ?SKIPT(accept_failed_str(Reason));
+
+ {accepted,P,Msg} ->
+ ?P("received accepted from ~p: "
+ "~n ~p", [P, Msg]),
NextN = if N =:= infinity -> N; true -> N - 1 end,
[{P,Msg}] ++ collect_accepts(NextN, Tmo - (millis()-A))
+
after Tmo ->
[]
end.
@@ -2243,8 +3116,15 @@ collect_accepts(N,Tmo) ->
collect_connects(Tmo) ->
A = millis(),
receive
+ {connected, P, {error, eaddrnotavail = Reason}} ->
+ ?P("~p Failed connect: ~p", [P, Reason]),
+ ?SKIPT(connect_failed_str(Reason));
+
{connected,P,Msg} ->
+ ?P("received connected from ~p: "
+ "~n ~p", [P, Msg]),
[{P,Msg}] ++ collect_connects(Tmo-(millis() - A))
+
after Tmo ->
[]
end.
@@ -2265,12 +3145,30 @@ mktmofun(Tmo,Parent,LS) ->
%% Accept tests
%% Test singular accept.
primitive_accept(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
- {ok,PortNo}=inet:port(LS),
+ try do_primitive_accept(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_primitive_accept(Config) ->
+ LSock =
+ case ?LISTEN(Config, 0,[]) of
+ {ok, LS} ->
+ LS;
+ {error, LReason} ->
+ ?SKIPT(listen_failed_str(LReason))
+ end,
+ {ok, PortNo} = inet:port(LSock),
Parent = self(),
- F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
+ F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LSock)} end,
P = spawn(F),
- gen_tcp:connect("localhost",PortNo,[]),
+ case ?CONNECT(Config, "localhost", PortNo, []) of
+ {ok, _} ->
+ ok;
+ {error, eaddrnotavail = CReason} ->
+ ?SKIPT(connect_failed_str(CReason))
+ end,
receive
{accepted,P,{ok,P0}} when is_port(P0) ->
ok;
@@ -2283,20 +3181,54 @@ primitive_accept(Config) when is_list(Config) ->
%% Closing listen socket when multi-accepting.
multi_accept_close_listen(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ ?TC_TRY(multi_accept_close_listen,
+ fun() -> do_multi_accept_close_listen(Config) end).
+
+do_multi_accept_close_listen(Config) ->
+ ?P("try create listen socket"),
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
- F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
+ F = fun() ->
+ ?P("started"),
+ Accepted = gen_tcp:accept(LS),
+ ?P("accept result: ~p", [Accepted]),
+ Parent ! {accepted,self(),Accepted}
+ end,
+ ?P("create acceptor processes"),
spawn(F),
spawn(F),
spawn(F),
spawn(F),
+ ?P("sleep some"),
+ ct:sleep(?SECS(2)),
+ ?P("close (listen) socket"),
gen_tcp:close(LS),
+ ?P("await accepts"),
ok = ?EXPECT_ACCEPTS([{_,{error,closed}},{_,{error,closed}},
- {_,{error,closed}},{_,{error,closed}}],4,500).
+ {_,{error,closed}},{_,{error,closed}}],4,500),
+ ?P("done"),
+ ok.
%% Single accept with timeout.
accept_timeout(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_accept_timeout(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_accept_timeout(Config) ->
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS,1000)} end,
P = spawn(F),
@@ -2304,7 +3236,19 @@ accept_timeout(Config) when is_list(Config) ->
%% Check that multi-accept timeouts happen in the correct order.
accept_timeouts_in_order(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_accept_timeouts_in_order(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_accept_timeouts_in_order(Config) ->
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
P1 = spawn(mktmofun(1000,Parent,LS)),
P2 = spawn(mktmofun(1200,Parent,LS)),
@@ -2315,7 +3259,19 @@ accept_timeouts_in_order(Config) when is_list(Config) ->
%% Check that multi-accept timeouts happen in the correct order (more).
accept_timeouts_in_order2(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_accept_timeouts_in_order2(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_accept_timeouts_in_order2(Config) ->
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
P1 = spawn(mktmofun(1400,Parent,LS)),
P2 = spawn(mktmofun(1300,Parent,LS)),
@@ -2326,7 +3282,19 @@ accept_timeouts_in_order2(Config) when is_list(Config) ->
%% Check that multi-accept timeouts happen in the correct order (even more).
accept_timeouts_in_order3(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_accept_timeouts_in_order3(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_accept_timeouts_in_order3(Config) ->
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
P1 = spawn(mktmofun(1200,Parent,LS)),
P2 = spawn(mktmofun(1400,Parent,LS)),
@@ -2338,7 +3306,19 @@ accept_timeouts_in_order3(Config) when is_list(Config) ->
%% Check that multi-accept timeouts happen in the correct order after
%% mixing millsec and sec timeouts.
accept_timeouts_in_order4(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_accept_timeouts_in_order4(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_accept_timeouts_in_order4(Config) ->
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
P1 = spawn(mktmofun(200,Parent,LS)),
P2 = spawn(mktmofun(400,Parent,LS)),
@@ -2350,7 +3330,19 @@ accept_timeouts_in_order4(Config) when is_list(Config) ->
%% Check that multi-accept timeouts happen in the correct order after
%% mixing millsec and sec timeouts (more).
accept_timeouts_in_order5(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_accept_timeouts_in_order5(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_accept_timeouts_in_order5(Config) ->
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
P1 = spawn(mktmofun(400,Parent,LS)),
P2 = spawn(mktmofun(1000,Parent,LS)),
@@ -2362,7 +3354,19 @@ accept_timeouts_in_order5(Config) when is_list(Config) ->
%% Check that multi-accept timeouts happen in the correct order after
%% mixing millsec and sec timeouts (even more).
accept_timeouts_in_order6(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_accept_timeouts_in_order6(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_accept_timeouts_in_order6(Config) ->
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
P1 = spawn(mktmofun(1000,Parent,LS)),
P2 = spawn(mktmofun(400,Parent,LS)),
@@ -2374,7 +3378,19 @@ accept_timeouts_in_order6(Config) when is_list(Config) ->
%% Check that multi-accept timeouts happen in the correct order after
%% mixing millsec and sec timeouts (even more++).
accept_timeouts_in_order7(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_accept_timeouts_in_order7(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_accept_timeouts_in_order7(Config) ->
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
P1 = spawn(mktmofun(1000,Parent,LS)),
P2 = spawn(mktmofun(200,Parent,LS)),
@@ -2391,42 +3407,118 @@ accept_timeouts_in_order7(Config) when is_list(Config) ->
%% Check that multi-accept timeouts behave correctly when mixed with successful timeouts.
accept_timeouts_mixed(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_accept_timeouts_mixed(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_accept_timeouts_mixed(Config) ->
+ ?P("create listen socket"),
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
{ok,PortNo}=inet:port(LS),
+ ?P("create acceptor process 1 (with timeout 1000)"),
P1 = spawn(mktmofun(1000,Parent,LS)),
+ ?P("await ~p accepting", [P1]),
wait_until_accepting(P1,500),
+ ?P("create acceptor process 2 (with timeout 2000)"),
P2 = spawn(mktmofun(2000,Parent,LS)),
+ ?P("await ~p accepting", [P2]),
wait_until_accepting(P2,500),
+ ?P("create acceptor process 3 (with timeout 3000)"),
P3 = spawn(mktmofun(3000,Parent,LS)),
+ ?P("await ~p accepting", [P3]),
wait_until_accepting(P3,500),
+ ?P("create acceptor process 4 (with timeout 4000)"),
P4 = spawn(mktmofun(4000,Parent,LS)),
+ ?P("await ~p accepting", [P4]),
wait_until_accepting(P4,500),
+ ?P("expect accept from 1 (~p) with timeout", [P1]),
ok = ?EXPECT_ACCEPTS([{P1,{error,timeout}}],infinity,1500),
- {ok,_}=gen_tcp:connect("localhost",PortNo,[]),
+ ?P("connect"),
+ case ?CONNECT(Config, "localhost", PortNo, []) of
+ {ok, _} ->
+ ok;
+ {error, eaddrnotavail = Reason1} ->
+ ?SKIPT(connect_failed_str(Reason1))
+ end,
+ ?P("expect accept from 2 (~p) with success", [P2]),
ok = ?EXPECT_ACCEPTS([{P2,{ok,Port0}}] when is_port(Port0),infinity,100),
+ ?P("expect accept from 3 (~p) with timeout", [P3]),
ok = ?EXPECT_ACCEPTS([{P3,{error,timeout}}],infinity,2000),
- gen_tcp:connect("localhost",PortNo,[]),
- ok = ?EXPECT_ACCEPTS([{P4,{ok,Port1}}] when is_port(Port1),infinity,100).
+ ?P("connect"),
+ case ?CONNECT(Config, "localhost", PortNo, []) of
+ {error, eaddrnotavail = Reason2} ->
+ ?SKIPT(connect_failed_str(Reason2));
+ _ ->
+ ok
+ end,
+ ?P("expect accept from 4 (~p) with success", [P4]),
+ ok = ?EXPECT_ACCEPTS([{P4,{ok,Port1}}] when is_port(Port1),infinity,100),
+ ?P("done"),
+ ok.
%% Check that single acceptor behaves as expected when killed.
-killing_acceptor(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
- Pid = spawn(fun() -> erlang:display({accepted,self(),gen_tcp:accept(LS)}) end),
+killing_acceptor(Config) when is_list(Config) ->
+ try do_killing_acceptor(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_killing_acceptor(Config) ->
+ ?P("create listen socket"),
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
+ ?P("create acceptor process"),
+ Pid = spawn(
+ fun() ->
+ erlang:display({accepted,self(),gen_tcp:accept(LS)})
+ end),
+ ?P("sleep some"),
receive after 100 -> ok
end,
+ ?P("get status for listen socket"),
{ok,L1} = prim_inet:getstatus(LS),
+ ?P("verify listen socket accepting"),
true = lists:member(accepting, L1),
+ ?P("kill acceptor"),
exit(Pid,kill),
+ ?P("sleep some"),
receive after 100 -> ok
end,
+ ?P("get status for listen socket"),
{ok,L2} = prim_inet:getstatus(LS),
+ ?P("verify listen socket *not* accepting"),
false = lists:member(accepting, L2),
+ ?P("done"),
ok.
%% Check that multi acceptors behaves as expected when killed.
-killing_multi_acceptors(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+killing_multi_acceptors(Config) when is_list(Config) ->
+ try do_killing_multi_acceptors(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_killing_multi_acceptors(Config) ->
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
F2 = mktmofun(1000,Parent,LS),
@@ -2448,52 +3540,108 @@ killing_multi_acceptors(Config) when is_list(Config) ->
%% Check that multi acceptors behaves as expected when killed (more).
killing_multi_acceptors2(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ try do_killing_multi_acceptors2(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_killing_multi_acceptors2(Config) ->
+ ?P("create listen socket"),
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
- {ok,PortNo}=inet:port(LS),
+ ?P("get port number for listen socket"),
+ {ok, PortNo} = inet:port(LS),
F = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
F2 = mktmofun(1000,Parent,LS),
+ ?P("create acceptor process 1"),
Pid = spawn(F),
+ ?P("create acceptor process 2"),
Pid2 = spawn(F),
+ ?P("wait some"),
receive after 100 -> ok
end,
- {ok,L1} = prim_inet:getstatus(LS),
+ ?P("get status for listen socket"),
+ {ok, L1} = prim_inet:getstatus(LS),
+ ?P("verify listen socket *is* accepting"),
true = lists:member(accepting, L1),
+ ?P("kill acceptor process 1"),
exit(Pid,kill),
+ ?P("wait some"),
receive after 100 -> ok
end,
- {ok,L2} = prim_inet:getstatus(LS),
+ ?P("get status for listen socket"),
+ {ok, L2} = prim_inet:getstatus(LS),
+ ?P("verify listen socket *is* accepting"),
true = lists:member(accepting, L2),
+ ?P("kill acceptor process 1"),
exit(Pid2,kill),
+ ?P("wait some"),
receive after 100 -> ok
end,
- {ok,L3} = prim_inet:getstatus(LS),
+ ?P("get status for listen socket"),
+ {ok, L3} = prim_inet:getstatus(LS),
+ ?P("verify listen socket is *not* accepting"),
false = lists:member(accepting, L3),
+ ?P("create acceptor process 3"),
Pid3 = spawn(F2),
+ ?P("wait some"),
receive after 100 -> ok
end,
+ ?P("get status for listen socket"),
{ok,L4} = prim_inet:getstatus(LS),
+ ?P("verify listen socket *is* accepting"),
true = lists:member(accepting, L4),
- gen_tcp:connect("localhost",PortNo,[]),
+ ?P("connect to port ~p", [PortNo]),
+ ?CONNECT(Config, "localhost", PortNo,[]),
+ ?P("accepts"),
ok = ?EXPECT_ACCEPTS([{Pid3,{ok,Port}}] when is_port(Port),1,100),
- {ok,L5} = prim_inet:getstatus(LS),
+ ?P("get status for listen socket"),
+ {ok, L5} = prim_inet:getstatus(LS),
+ ?P("verify listen socket *is* accepting"),
false = lists:member(accepting, L5),
+ ?P("done"),
ok.
%% Checks that multi-accept works when more than one accept can be
%% done at once (wb test of inet_driver).
several_accepts_in_one_go(Config) when is_list(Config) ->
- {ok,LS}=gen_tcp:listen(0,[]),
+ ?TC_TRY(several_accepts_in_one_go,
+ fun() -> do_several_accepts_in_one_go(Config) end).
+
+do_several_accepts_in_one_go(Config) ->
+ ?P("create listen socket"),
+ LS = case ?LISTEN(Config, 0,[]) of
+ {ok, LSock} ->
+ LSock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
Parent = self(),
- {ok,PortNo}=inet:port(LS),
- F1 = fun() -> Parent ! {accepted,self(),gen_tcp:accept(LS)} end,
- F2 = fun() -> Parent ! {connected,self(),gen_tcp:connect("localhost",PortNo,[])} end,
+ {ok, PortNo} = inet:port(LS),
+ F1 = fun() -> ?P("acceptor starting"),
+ Parent ! {accepted,self(),gen_tcp:accept(LS)}
+ end,
+ F2 = fun() -> ?P("connector starting"),
+ Parent ! {connected,self(),?CONNECT(Config, "localhost",PortNo,[])}
+ end,
Ns = lists:seq(1,8),
+ ?P("start acceptors"),
_ = [spawn(F1) || _ <- Ns],
+ ?P("await accept timeouts"),
ok = ?EXPECT_ACCEPTS([],1,500), % wait for tmo
+ ?P("start connectors"),
_ = [spawn(F2) || _ <- Ns],
+ ?P("await accepts"),
ok = ?EXPECT_ACCEPTS([{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}}],8,15000),
+ ?P("await connects"),
ok = ?EXPECT_CONNECTS([{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}},{_,{ok,_}}],1000),
+ ?P("done"),
ok.
flush(Msgs) ->
@@ -2527,41 +3675,73 @@ wait_until_accepting(Proc,N) ->
%% Check that accept returns {error, system_limit}
%% (and not {error, enfile}) when running out of ports.
accept_system_limit(Config) when is_list(Config) ->
- {ok, LS} = gen_tcp:listen(0, []),
+ OldFlag = process_flag(trap_exit, true),
+ Res = try do_accept_system_limit(Config)
+ catch
+ exit:{skip, _} = SKIP ->
+ SKIP
+ end,
+ process_flag(trap_exit, OldFlag),
+ Res.
+
+do_accept_system_limit(Config) ->
+ ?P("create listen socket"),
+ LS = case ?LISTEN(Config, 0, []) of
+ {ok, LSocket} ->
+ LSocket;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPT(listen_failed_str(Reason))
+ end,
{ok, TcpPort} = inet:port(LS),
Me = self(),
- Connector = spawn_link(fun () -> connector(TcpPort, Me) end),
+ ?P("create connector"),
+ Connector = spawn_link(fun () -> connector(Config, TcpPort, Me) end),
+ ?P("sync with connector (~p)", [Connector]),
receive {Connector, sync} -> Connector ! {self(), continue} end,
- ok = acceptor(LS, false, []),
+ ?P("begin accepting"),
+ ok = acceptor(Connector, LS, false, []),
+ ?P("stop connector (~p)", [Connector]),
Connector ! stop,
+ ?P("done"),
ok.
-acceptor(LS, GotSL, A) ->
+acceptor(Connector, LS, GotSL, A) ->
case gen_tcp:accept(LS, 1000) of
- {ok, S} ->
- acceptor(LS, GotSL, [S|A]);
- {error, system_limit} ->
- acceptor(LS, true, A);
- {error, timeout} when GotSL ->
- ok;
- {error, timeout} ->
- error
+ {ok, S} ->
+ acceptor(Connector, LS, GotSL, [S|A]);
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPE(accept_failed_str(Reason));
+ {error, system_limit} ->
+ ?P("acceptor: system limit => *almost* done (~w)", [length(A)]),
+ acceptor(Connector, LS, true, A);
+ {error, timeout} when GotSL ->
+ ?P("acceptor: timeout (with system limit) => done (~w)",
+ [length(A)]),
+ ok;
+ {error, timeout} ->
+ error
end.
-connector(TcpPort, Tester) ->
+connector(Config, TcpPort, Tester) ->
+ ?P("[connector] start"),
ManyPorts = open_ports([]),
Tester ! {self(), sync},
+ ?P("[connector] sync with tester (~p)", [Tester]),
receive {Tester, continue} -> timer:sleep(100) end,
+ ?P("[connector] begin connecting"),
ConnF = fun (Port) ->
- case catch gen_tcp:connect({127,0,0,1}, TcpPort, []) of
- {ok, Sock} ->
- Sock;
- _Error ->
- port_close(Port)
- end
- end,
+ case (catch ?CONNECT(Config, {127,0,0,1}, TcpPort, [])) of
+ {ok, Sock} ->
+ Sock;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPE(connect_failed_str(Reason));
+ _Error ->
+ port_close(Port)
+ end
+ end,
R = [ConnF(Port) || Port <- lists:sublist(ManyPorts, 10)],
- receive stop -> R end.
+ ?P("[connector] await stop"),
+ receive stop -> ?P("[connector] stop (~w)", [length(R)]), R end.
open_ports(L) ->
case catch open_port({spawn_driver, "ram_file_drv"}, []) of
@@ -2576,35 +3756,46 @@ open_ports(L) ->
%% Check that active once and tcp_close messages behave as expected.
active_once_closed(Config) when is_list(Config) ->
+ try do_active_once_closed(Config)
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+do_active_once_closed(Config) ->
+ ?P("stage 1"),
(fun() ->
- {Loop,A} = setup_closed_ao(),
+ {Loop,A} = setup_closed_ao(Config),
Loop({{error,closed},{error,econnaborted}},
- fun() -> gen_tcp:send(A,"Hello") end),
+ fun() -> gen_tcp:send(A,"Hello") end),
ok = inet:setopts(A,[{active,once}]),
ok = receive {tcp_closed, A} -> ok after 1000 -> error end,
{error,einval} = inet:setopts(A,[{active,once}]),
ok = receive {tcp_closed, A} -> error after 1000 -> ok end
end)(),
+ ?P("stage 2"),
(fun() ->
- {Loop,A} = setup_closed_ao(),
+ {Loop,A} = setup_closed_ao(Config),
Loop({{error,closed},{error,econnaborted}},
- fun() -> gen_tcp:send(A,"Hello") end),
+ fun() -> gen_tcp:send(A,"Hello") end),
ok = inet:setopts(A,[{active,true}]),
ok = receive {tcp_closed, A} -> ok after 1000 -> error end,
{error,einval} = inet:setopts(A,[{active,true}]),
ok = receive {tcp_closed, A} -> error after 1000 -> ok end
end)(),
- (fun() ->
- {Loop,A} = setup_closed_ao(),
+ ?P("stage 3"),
+ (fun() ->
+ {Loop,A} = setup_closed_ao(Config),
Loop({{error,closed},{error,econnaborted}},
- fun() -> gen_tcp:send(A,"Hello") end),
+ fun() -> gen_tcp:send(A,"Hello") end),
ok = inet:setopts(A,[{active,true}]),
ok = receive {tcp_closed, A} -> ok after 1000 -> error end,
{error,einval} = inet:setopts(A,[{active,once}]),
ok = receive {tcp_closed, A} -> error after 1000 -> ok end
end)(),
+ ?P("stage 4"),
(fun() ->
- {Loop,A} = setup_closed_ao(),
+ {Loop,A} = setup_closed_ao(Config),
Loop({{error,closed},{error,econnaborted}},
fun() -> gen_tcp:send(A,"Hello") end),
ok = inet:setopts(A,[{active,once}]),
@@ -2612,170 +3803,407 @@ active_once_closed(Config) when is_list(Config) ->
{error,einval} = inet:setopts(A,[{active,true}]),
ok = receive {tcp_closed, A} -> error after 1000 -> ok end
end)(),
+ ?P("stage 5"),
(fun() ->
- {Loop,A} = setup_closed_ao(),
+ {Loop,A} = setup_closed_ao(Config),
Loop({{error,closed},{error,econnaborted}},
fun() -> gen_tcp:send(A,"Hello") end),
ok = inet:setopts(A,[{active,false}]),
ok = receive {tcp_closed, A} -> error after 1000 -> ok end,
ok = inet:setopts(A,[{active,once}]),
ok = receive {tcp_closed, A} -> ok after 1000 -> error end
- end)().
+ end)(),
+ ?P("done"),
+ ok.
%% Check that active n and tcp_close messages behave as expected.
active_n_closed(Config) when is_list(Config) ->
- {ok, L} = gen_tcp:listen(0, [binary, {active, false}]),
+ ?TC_TRY(active_n_closed, fun() -> do_active_n_closed(Config) end).
+
+do_active_n_closed(Config) ->
+ ?P("create listen socket"),
+ {ok, L} = ?LISTEN(Config, 0, [binary, {active, false}]),
P = self(),
- {ok,Port} = inet:port(L),
+ {ok, Port} = inet:port(L),
- spawn_link(fun() ->
- Payload = <<0:50000/unit:8>>,
- Cnt = 10000,
- P ! {size,Cnt * byte_size(Payload)},
- {ok, S} = gen_tcp:connect("localhost", Port, [binary, {active, false}]),
- _ = [gen_tcp:send(S, Payload) || _ <- lists:seq(1, Cnt)],
- gen_tcp:close(S)
- end),
+ ClientF =
+ fun() ->
+ ?P("[client] started"),
+ Payload = <<0:50000/unit:8>>,
+ Cnt = 10000,
+ ?P("[client] send size"),
+ P ! {size, Cnt * byte_size(Payload)},
+ ?P("[client] try connect"),
+ S = case ?CONNECT(Config, "localhost", Port,
+ [binary, {active, false}]) of
+ {ok, CS} ->
+ ?P("[client] connected"),
+ P ! {continue, self()},
+ CS;
+ {error, eaddrnotavail = Reason} ->
+ ?SKIPE(connect_failed_str(Reason))
+ end,
+ ?P("[client] send payload"),
+ _ = [gen_tcp:send(S, Payload) || _ <- lists:seq(1, Cnt)],
+ ?P("[client] close socket"),
+ gen_tcp:close(S),
+ ?P("[client] done"),
+ exit(ok)
+ end,
+ ?P("create client process"),
+ {Pid, MRef} = spawn_monitor(ClientF),
- receive {size,SendSize} -> SendSize end,
+ ?P("await size"),
+ receive {size, SendSize} -> SendSize end,
+ ?P("await continue or down"),
+ receive
+ {continue, Pid} ->
+ ?P("got continue"),
+ ok;
+ {'DOWN', MRef, process, Pid, {skip, _} = SKIP} ->
+ ?P("got *unexpected* skip"),
+ gen_tcp:close(L),
+ throw(SKIP);
+ {'DOWN', MRef, process, Pid, ConnectRes} ->
+ ?P("got *unexpected* crash: "
+ "~n ~p", [ConnectRes]),
+ exit({unexpected, connect, ConnectRes})
+ end,
{ok, S} = gen_tcp:accept(L),
inet:setopts(S, [{active, 10}]),
- RecvSize =
- (fun Server(Size) ->
- receive
- {tcp, S, Bin} ->
- Server(byte_size(Bin) + Size);
- {tcp_closed, S} ->
- Size;
- {tcp_passive, S} ->
- inet:setopts(S, [{active, 10}]),
- Server(Size);
- Msg ->
- io:format("~p~n", [Msg]),
- Server(Size)
- end
- end)(0),
-
+ ?P("start collecting data"),
+ RecvSize = anc_await_closed_and_down(S, Pid, MRef),
+ %% {RecvSize, Down} =
+ %% (fun Server(Size) ->
+ %% receive
+ %% {tcp, S, Bin} ->
+ %% %% ?P("got a chunk (~w) of data", [byte_size(Bin)]),
+ %% Server(byte_size(Bin) + Size);
+ %% {tcp_closed, S} ->
+ %% ?P("got closed -> we are done: ~w", [Size]),
+ %% Size;
+ %% {tcp_passive, S} ->
+ %% %% ?P("got passive -> active"),
+ %% inet:setopts(S, [{active, 10}]),
+ %% Server(Size);
+ %% {'DOWN', MRef, process, Pid, Reason} ->
+ %% ?P("Received UNEXPECTED down message regarding client:"
+ %% "~n Reason: ~p"
+ %% "~n S: ~p"
+ %% "~n Port Info: ~p",
+ %% [Reason, ]),
+ %% ct:fail({unexpected_client_down, Reason});
+ %% Msg ->
+ %% ?P("ignore: ~p", [Msg]),
+ %% Server(Size)
+ %% end
+ %% end)(0),
+ %% ?P("await client process termination"),
+ %% receive
+ %% {'DOWN', MRef, process, Pid, ok} ->
+ %% ok;
+ %% {'DOWN', MRef, process, Pid, CloseRes} ->
+ %% exit({unexpected, close, CloseRes})
+ %% end,
+
+ ?P("close listen socket"),
gen_tcp:close(L),
+ ?P("validate size"),
if SendSize =:= RecvSize ->
+ ?P("done"),
ok;
true ->
- ct:fail("Send and Recv size not equal: ~p ~p",[SendSize, RecvSize])
+ ct:fail("Send and Recv size not equal: ~p ~p", [SendSize, RecvSize])
+ end.
+
+
+anc_await_closed_and_down(S, Pid, MRef) ->
+ anc_await_closed_and_down(S, Pid, MRef, 0, false, false).
+
+anc_await_closed_and_down(_S, _Pid, _MRef, Size, true, true) ->
+ Size;
+anc_await_closed_and_down(S, Pid, MRef, Size, Closed, Down) ->
+ receive
+ {tcp, S, Bin} ->
+ %% ?P("got a chunk (~w) of data", [byte_size(Bin)]),
+ anc_await_closed_and_down(S, Pid, MRef,
+ byte_size(Bin) + Size, Closed, Down);
+ {tcp_closed, S} ->
+ ?P("got closed -> we are done: ~w", [Size]),
+ anc_await_closed_and_down(S, Pid, MRef, Size, true, Down);
+ {tcp_passive, S} ->
+ %% ?P("got passive -> active"),
+ inet:setopts(S, [{active, 10}]),
+ anc_await_closed_and_down(S, Pid, MRef, Size, Closed, true);
+ {'DOWN', MRef, process, Pid, ok} ->
+ ?P("Received expected down message regarding client"),
+ anc_await_closed_and_down(S, Pid, MRef, Size, Closed, true);
+
+ {'DOWN', MRef, process, Pid, Reason} ->
+ ?P("Received UNEXPECTED down message regarding client:"
+ "~n Reason: ~p"
+ "~n Port Info: ~p",
+ [Reason, (catch erlang:port_info(S))]),
+ ct:fail({unexpected_client_down, Reason});
+
+ Msg ->
+ ?P("ignore: ~p", [Msg]),
+ anc_await_closed_and_down(S, Pid, MRef, Size, Closed, Down)
end.
%% Test the send_timeout socket option.
send_timeout(Config) when is_list(Config) ->
+ ?P("begin"),
Dir = filename:dirname(code:which(?MODULE)),
+ ?P("create (slave) node"),
{ok,RNode} = test_server:start_node(?UNIQ_NODE_NAME, slave,
[{args,"-pa " ++ Dir}]),
%% Basic
- send_timeout_basic(false, RNode),
- send_timeout_basic(true, RNode),
+ ?P("basic check wo autoclose"),
+ send_timeout_basic(Config, false, RNode),
+ ?P("basic check w autoclose"),
+ send_timeout_basic(Config, true, RNode),
BinData = <<1:10000>>,
%% Check timeout length.
+ ?P("check timeout length"),
Self = self(),
- Pid = spawn(fun() ->
- A = setup_timeout_sink(RNode, 1000, true),
- Send = fun() ->
- Res = gen_tcp:send(A, BinData),
- Self ! Res,
- Res
- end,
- {error,timeout} = timeout_sink_loop(Send)
- end),
+ {Pid, Mon} = spawn_monitor(
+ fun() ->
+ A = setup_timeout_sink(Config, RNode, 1000, true),
+ Send = fun() ->
+ Res = gen_tcp:send(A, BinData),
+ Self ! Res,
+ Res
+ end,
+ {{error, timeout}, _} = timeout_sink_loop(Send)
+ end),
Diff = get_max_diff(),
- io:format("Max time for send: ~p~n",[Diff]),
+ ?P("Max time for send: ~p",[Diff]),
true = (Diff > 500) and (Diff < 1500),
%% Wait for the process to die.
- Mon = erlang:monitor(process, Pid),
- receive {'DOWN',Mon,process,Pid,_} -> ok end,
+ ?P("await (timeout checker) process death"),
+ receive {'DOWN', Mon, process, Pid, _} -> ok end,
%% Check that parallell writers do not hang forever
- send_timeout_para(false, RNode),
- send_timeout_para(true, RNode),
+ ?P("check parallell writers wo autoclose"),
+ send_timeout_para(Config, false, RNode),
+ ?P("check parallell writers w autoclose"),
+ send_timeout_para(Config, true, RNode),
+ ?P("stop (slave) node"),
test_server:stop_node(RNode),
+ ?P("done"),
ok.
-send_timeout_basic(AutoClose, RNode) ->
+send_timeout_basic(Config, AutoClose, RNode) ->
BinData = <<1:10000>>,
- A = setup_timeout_sink(RNode, 1000, AutoClose),
+ A = setup_timeout_sink(Config, RNode, 1000, AutoClose),
Send = fun() -> gen_tcp:send(A, BinData) end,
- {error,timeout} = timeout_sink_loop(Send),
+ {{error, timeout}, _} = timeout_sink_loop(Send),
%% Check that the socket is not busy/closed...
- Error = after_send_timeout(AutoClose),
{error,Error} = gen_tcp:send(A, <<"Hej">>),
+ after_send_timeout(AutoClose, Error),
ok.
-send_timeout_para(AutoClose, RNode) ->
+send_timeout_para(Config, AutoClose, RNode) ->
BinData = <<1:10000>>,
- A = setup_timeout_sink(RNode, 1000, AutoClose),
+ ?P("[para] sink"),
+ A = setup_timeout_sink(Config, RNode, 1000, AutoClose),
Self = self(),
SenderFun = fun() ->
+ ?P("[para:sender] start"),
Send = fun() -> gen_tcp:send(A, BinData) end,
- {error,Error} = timeout_sink_loop(Send),
- Self ! {error,Error}
+ Self ! {self(), timeout_sink_loop(Send)}
end,
- spawn_link(SenderFun),
- spawn_link(SenderFun),
-
+ Info = fun() ->
+ {(catch erlang:port_info(A)),
+ try inet:getopts(A, [send_timeout]) of
+ {ok, [V2]} ->
+ V2;
+ {error, R2} ->
+ ?F("ERROR: ~p", [R2]);
+ X2 ->
+ ?F("UNKNOWN: ~p", [X2])
+ catch
+ C2:E2:S2 ->
+ ?F("CATCHED: ~p, ~p, ~p", [C2, E2, S2])
+ end,
+ try inet:getstat(A) of
+ {ok, S3} ->
+ S3;
+ {error, R3} ->
+ ?F("ERROR: ~p", [R3]);
+ X3 ->
+ ?F("UNKNOWN: ~p", [X3])
+ catch
+ C3:E3:S3 ->
+ ?F("CATCHED: ~p, ~p, ~p", [C3, E3, S3])
+ end,
+ try prim_inet:getstatus(A) of
+ {ok, S4} ->
+ S4;
+ {error, R4} ->
+ ?F("ERROR: ~p", [R4]);
+ X4 ->
+ ?F("UNKNOWN: ~p", [X4])
+ catch
+ C4:E4:S4 ->
+ ?F("CATCHED: ~p, ~p, ~p", [C4, E4, S4])
+ end}
+ end,
+ ?P("[para] spawn process 1 with sender fun"),
+ Snd1 = spawn_link(SenderFun),
+ ?P("[para] spawn process 2 with sender fun"),
+ Snd2 = spawn_link(SenderFun),
+
+ ?P("[para] await sender timeout when"
+ "~n Sender 1: ~p"
+ "~n Sender 2: ~p", [Snd1, Snd2]),
+ First =
+ receive
+ {Snd1, {{error, timeout}, N}} ->
+ ?P("[para] timeout received from sender 1 (~p, ~p)", [Snd1, N]),
+ 1;
+ {Snd2, {{error, timeout}, N}} ->
+ ?P("[para] timeout received from sender 2 (~p, ~p)", [Snd2, N]),
+ 2
+ after 20000 ->
+ {PortStatus1, SockOpts1, SockStat1, SockStatus1} = Info(),
+ ?P("[para] UNEXPECTED timeout(1,~w) when:"
+ "~n Sender 1 Info: ~p"
+ "~n Sender 2 Info: ~p"
+ "~n Port Status: ~p"
+ "~n Send Timeout: ~p"
+ "~n Socket Stats: ~p"
+ "~n Socket Status: ~p"
+ "~n Message Queue: ~p",
+ [AutoClose,
+ (catch process_info(Snd1)),
+ (catch process_info(Snd2)),
+ PortStatus1, SockOpts1, SockStat1, SockStatus1,
+ flush([])]),
+ Snd1 ! {info_and_die, Info},
+ Snd2 ! {info_and_die, Info},
+ ct:sleep(?SECS(1)),
+ exit({timeout, AutoClose})
+ end,
+
+ Second = if (First =:= 1) -> 2; true -> 1 end,
+ ?P("await sender ~w error", [Second]),
receive
- {error,timeout} -> ok
+ {Snd1, {{error, Error_1}, N_1}} ->
+ ?P("[para] error (~p) received from sender 1 (~p, ~p)",
+ [Error_1, Snd1, N_1]),
+ after_send_timeout(AutoClose, Error_1);
+ {Snd2, {{error, Error_1}, N_1}} ->
+ ?P("[para] error (~p) received from sender 2 (~p, ~p)",
+ [Error_1, Snd2, N_1]),
+ after_send_timeout(AutoClose, Error_1)
after 10000 ->
- exit(timeout)
+ if (Second =:= 1) ->
+ {PortStatus21, SockOpts21, SockStat21, SockStatus21} =
+ Info(),
+ ?P("[para] UNEXPECTED timeout(2,~w):"
+ "~n Sender 1 Info: ~p"
+ "~n Port Status: ~p"
+ "~n Send Timeout: ~p"
+ "~n Socket Stats: ~p"
+ "~n Socket Status: ~p"
+ "~n Message Queue: ~p",
+ [AutoClose,
+ (catch process_info(Snd1)),
+ PortStatus21, SockOpts21, SockStat21, SockStatus21,
+ flush([])]),
+ Snd1 ! {info_and_die, Info};
+ true ->
+ {PortStatus22, SockOpts22, SockStat22, SockStatus22} =
+ Info(),
+ ?P("[para] UNEXPECTED timeout(2,~w):"
+ "~n Sender 2 Info: ~p"
+ "~n Port Status: ~p"
+ "~n Send Timeout: ~p"
+ "~n Socket Stats: ~p"
+ "~n Socket Status: ~p"
+ "~n Message Queue: ~p",
+ [AutoClose,
+ (catch process_info(Snd2)),
+ PortStatus22, SockOpts22, SockStat22, SockStatus22,
+ flush([])]),
+ Snd2 ! {info_and_die, Info}
+ end,
+ ct:sleep(?SECS(1)),
+ exit({timeout, AutoClose, Second})
end,
+ {error,Error_2} = gen_tcp:send(A, <<"Hej">>),
+ after_send_timeout(AutoClose, Error_2),
+ ?P("[para] done"),
+ ok.
+
- NextErr = after_send_timeout(AutoClose),
+get_max_diff() ->
receive
- {error,NextErr} -> ok
+ ok ->
+ get_max_diff(0)
after 10000 ->
exit(timeout)
- end,
-
- {error,NextErr} = gen_tcp:send(A, <<"Hej">>),
- ok.
-
-mad_sender(S) ->
- U = rand:uniform(1000000),
- case gen_tcp:send(S, integer_to_list(U)) of
- ok ->
- mad_sender(S);
- Err ->
- Err
end.
-
-flush() ->
+get_max_diff(Max) ->
+ T1 = millis(),
receive
- _X ->
- flush()
- after 0 ->
- ok
+ ok ->
+ Diff = millis() - T1,
+ if
+ Diff > Max ->
+ get_max_diff(Diff);
+ true ->
+ get_max_diff(Max)
+ end;
+ {error,timeout} ->
+ Diff = millis() - T1,
+ if
+ Diff > Max ->
+ Diff;
+ true ->
+ Max
+ end
+ after 10000 ->
+ exit(timeout)
end.
+
+after_send_timeout(AutoClose, Reason) ->
+ case Reason of
+ timeout when AutoClose =:= false -> ok;
+ enotconn when AutoClose =:= true -> ok
+%%% timeout -> ok;
+%%% enotconn when AutoClose -> ok;
+%%% closed when AutoClose -> ok
+ end.
+
%% Test the send_timeout socket option for active sockets.
send_timeout_active(Config) when is_list(Config) ->
Dir = filename:dirname(code:which(?MODULE)),
{ok,RNode} = test_server:start_node(?UNIQ_NODE_NAME, slave,
[{args,"-pa " ++ Dir}]),
- do_send_timeout_active(false, RNode),
- do_send_timeout_active(true, RNode),
+ do_send_timeout_active(Config, false, RNode),
+ do_send_timeout_active(Config, true, RNode),
test_server:stop_node(RNode),
ok.
-do_send_timeout_active(AutoClose, RNode) ->
- {A,C} = setup_active_timeout_sink(RNode, 1, AutoClose),
+do_send_timeout_active(Config, AutoClose, RNode) ->
+ {A,C} = setup_active_timeout_sink(Config, RNode, 1, AutoClose),
inet:setopts(A, [{active, once}]),
Mad = spawn_link(RNode, fun() -> mad_sender(C) end),
ListData = lists:duplicate(1000, $a),
@@ -2790,55 +4218,48 @@ do_send_timeout_active(AutoClose, RNode) ->
Err
end
end,
- {error,timeout} = timeout_sink_loop(F),
+ {{error, timeout}, _} = timeout_sink_loop(F),
unlink(Mad),
exit(Mad, kill),
flush(),
ok.
-after_send_timeout(AutoClose) ->
- case AutoClose of
- true -> enotconn;
- false -> timeout
+mad_sender(S) ->
+ U = rand:uniform(1000000),
+ case gen_tcp:send(S, integer_to_list(U)) of
+ ok ->
+ mad_sender(S);
+ Err ->
+ Err
end.
-get_max_diff() ->
+flush() ->
receive
- ok ->
- get_max_diff(0)
- after 10000 ->
- exit(timeout)
+ _X ->
+ flush()
+ after 0 ->
+ ok
end.
-get_max_diff(Max) ->
- T1 = millis(),
- receive
- ok ->
- Diff = millis() - T1,
- if
- Diff > Max ->
- get_max_diff(Diff);
- true ->
- get_max_diff(Max)
- end;
- {error,timeout} ->
- Diff = millis() - T1,
- if
- Diff > Max ->
- Diff;
- true ->
- Max
- end
- after 10000 ->
- exit(timeout)
- end.
-
-setup_closed_ao() ->
+setup_closed_ao(Config) ->
Dir = filename:dirname(code:which(?MODULE)),
- {ok,R} = test_server:start_node(?UNIQ_NODE_NAME, slave,
- [{args,"-pa " ++ Dir}]),
+ ?P("[setup] start slave node"),
+ R = case test_server:start_node(?UNIQ_NODE_NAME, slave,
+ [{args,"-pa " ++ Dir}]) of
+ {ok, Slave} ->
+ Slave;
+ {error, Reason} ->
+ ?SKIPT(?F("failed starting slave node: ~p", [Reason]))
+ end,
Host = get_hostname(node()),
- {ok, L} = gen_tcp:listen(0, [{active,false},{packet,2}]),
+ ?P("[setup] create listen socket"),
+ L = case ?LISTEN(Config, 0, [{active,false},{packet,2}]) of
+ {ok, LSock} ->
+ LSock;
+ {error, eaddrnotavail = LReason} ->
+ (catch test_server:stop_node(R)),
+ ?SKIPT(listen_failed_str(LReason))
+ end,
Fun = fun(F) ->
receive
{From,X} when is_function(X) ->
@@ -2846,20 +4267,37 @@ setup_closed_ao() ->
die -> ok
end
end,
- Pid = rpc:call(R,erlang,spawn,[fun() -> Fun(Fun) end]),
+ ?P("[setup] create remote runner"),
+ Pid = rpc:call(R,erlang,spawn,[fun() -> Fun(Fun) end]),
{ok, Port} = inet:port(L),
Remote = fun(Fu) ->
Pid ! {self(), Fu},
- receive {Pid,X} -> X
- end
+ receive {Pid,X} -> X end
end,
- {ok, C} = Remote(fun() ->
- gen_tcp:connect(Host,Port,
- [{active,false},{packet,2}])
- end),
- {ok,A} = gen_tcp:accept(L),
+ Connect = fun() ->
+ ?CONNECT(Config, Host, Port,
+ [{active, false}, {packet, 2}])
+ end,
+ ?P("[setup] create (remote) connection"),
+ C = case Remote(Connect) of
+ {ok, CSock} ->
+ CSock;
+ {error, eaddrnotavail = CReason} ->
+ (catch test_server:stop_node(R)),
+ ?SKIPT(connect_failed_str(CReason))
+ end,
+ ?P("[setup] accept (local) connection"),
+ A = case gen_tcp:accept(L) of
+ {ok, ASock} ->
+ ASock;
+ {error, eaddrnotavail = AReason} ->
+ (catch test_server:stop_node(R)),
+ ?SKIPT(accept_failed_str(AReason))
+ end,
+ ?P("[setup] send message"),
gen_tcp:send(A,"Hello"),
{ok, "Hello"} = Remote(fun() -> gen_tcp:recv(C,0) end),
+ ?P("[setup] close (remote) connection"),
ok = Remote(fun() -> gen_tcp:close(C) end),
Loop2 = fun(_,_,_,0) ->
{failure, timeout};
@@ -2867,20 +4305,24 @@ setup_closed_ao() ->
case F2() of
MA -> MA;
MB -> MB;
- Other -> io:format("~p~n",[Other]),
+ Other -> ?P("~p",[Other]),
receive after 1000 -> ok end,
L2(L2,{MA,MB},F2,N-1)
end
end,
Loop = fun(Match2,F3) -> Loop2(Loop2,Match2,F3,10) end,
+ ?P("[setup] stop slave node"),
test_server:stop_node(R),
+ ?P("[setup] done"),
{Loop,A}.
-setup_timeout_sink(RNode, Timeout, AutoClose) ->
+setup_timeout_sink(Config, RNode, Timeout, AutoClose) ->
Host = get_hostname(node()),
- {ok, L} = gen_tcp:listen(0, [{active,false},{packet,2},
- {send_timeout,Timeout},
- {send_timeout_close,AutoClose}]),
+ ?P("[sink] create listen socket"),
+ {ok, L} = ?LISTEN(Config, 0, [{active, false},
+ {packet, 2},
+ {send_timeout, Timeout},
+ {send_timeout_close, AutoClose}]),
Fun = fun(F) ->
receive
{From,X} when is_function(X) ->
@@ -2888,6 +4330,7 @@ setup_timeout_sink(RNode, Timeout, AutoClose) ->
die -> ok
end
end,
+ ?P("[sink] start remote runner process (on ~p)", [RNode]),
Pid = rpc:call(RNode, erlang, spawn, [fun() -> Fun(Fun) end]),
{ok, Port} = inet:port(L),
Remote = fun(Fu) ->
@@ -2895,21 +4338,26 @@ setup_timeout_sink(RNode, Timeout, AutoClose) ->
receive {Pid,X} -> X
end
end,
+ ?P("[sink] connect from remote node (~p)", [RNode]),
{ok, C} = Remote(fun() ->
- gen_tcp:connect(Host,Port,
+ ?CONNECT(Config, Host,Port,
[{active,false},{packet,2}])
end),
- {ok,A} = gen_tcp:accept(L),
+ ?P("[sink] accept"),
+ {ok, A} = gen_tcp:accept(L),
+ ?P("[sink] send message"),
gen_tcp:send(A,"Hello"),
+ ?P("[sink] recv message on remote node (~p)", [RNode]),
{ok, "Hello"} = Remote(fun() -> gen_tcp:recv(C,0) end),
+ ?P("[sink] done"),
A.
-setup_active_timeout_sink(RNode, Timeout, AutoClose) ->
+setup_active_timeout_sink(Config, RNode, Timeout, AutoClose) ->
Host = get_hostname(node()),
ListenOpts = [binary,{active,false},{packet,0},
{nodelay,true},{keepalive,true},
{send_timeout,Timeout},{send_timeout_close,AutoClose}],
- {ok, L} = gen_tcp:listen(0, ListenOpts),
+ {ok, L} = ?LISTEN(Config, 0, ListenOpts),
Fun = fun(F) ->
receive
{From,X} when is_function(X) ->
@@ -2926,21 +4374,53 @@ setup_active_timeout_sink(RNode, Timeout, AutoClose) ->
end
end,
{ok, C} = Remote(fun() ->
- gen_tcp:connect(Host, Port, [{active,false}])
+ ?CONNECT(Config, Host, Port, [{active,false}])
end),
- {ok,A} = gen_tcp:accept(L),
+ {ok, A} = gen_tcp:accept(L),
gen_tcp:send(A, "Hello"),
{ok, "H"++_} = Remote(fun() -> gen_tcp:recv(C, 0) end),
- {A,C}.
+ {A, C}.
timeout_sink_loop(Action) ->
- Ret = Action(),
+ put(action, nothing),
+ put(sent, 0),
+ put(elapsed, 0),
+ timeout_sink_loop(Action, 0).
+
+timeout_sink_loop(Action, N) ->
+ put(action, send),
+ Start = erlang:monotonic_time(),
+ Ret = Action(),
+ Stop = erlang:monotonic_time(),
+ Elapsed = get(elapsed),
+ put(elapsed, Elapsed + (Stop - Start)),
+ put(action, sent),
+ N2 = N + 1,
+ put(sent, N2),
case Ret of
ok ->
- receive after 1 -> ok end,
- timeout_sink_loop(Action);
+ receive
+ {info_and_die, Info} ->
+ {PortStatus, SockOpts, SockStat, SockStatus} = Info(),
+ ?P("[sink-loop] info and die: "
+ "~n Port Status: ~p"
+ "~n Send Timeout: ~p"
+ "~n Socket Stats: ~p"
+ "~n Socket Status: ~p",
+ [PortStatus, SockOpts, SockStat, SockStatus]),
+ exit(normal)
+ after 1 -> ok
+ end,
+ timeout_sink_loop(Action, N+1);
Other ->
- Other
+ ?P("[sink-loop] action result: "
+ "~n Number of actions: ~p"
+ "~n Elapsed time: ~p msec"
+ "~n Result: ~p",
+ [N2,
+ erlang:convert_time_unit(get(elapsed), native, millisecond),
+ Other]),
+ {Other, N2}
end.
has_superfluous_schedulers() ->
@@ -2955,18 +4435,18 @@ has_superfluous_schedulers() ->
%% Leaking message from inet_drv {inet_reply,P,ok}
%% when a socket sending resumes working after a send_timeout.
otp_7731(Config) when is_list(Config) ->
- ServerPid = spawn_link(?MODULE, otp_7731_server, [self()]),
+ ServerPid = spawn_link(?MODULE, otp_7731_server, [Config, self()]),
receive {ServerPid, ready, PortNum} -> ok end,
- {ok, Socket} = gen_tcp:connect("localhost", PortNum,
- [binary, {active, false}, {packet, raw},
- {send_timeout, 1000}]),
+ {ok, Socket} = ?CONNECT(Config, "localhost", PortNum,
+ [binary, {active, false}, {packet, raw},
+ {send_timeout, 1000}]),
otp_7731_send(Socket),
- io:format("Sending complete...\n",[]),
+ ?P("Sending complete..."),
ServerPid ! {self(), recv},
receive {ServerPid, ok} -> ok end,
- io:format("Client waiting for leaking messages...\n",[]),
+ ?P("Client waiting for leaking messages..."),
%% Now make sure inet_drv does not leak any internal messages.
receive Msg ->
@@ -2974,7 +4454,7 @@ otp_7731(Config) when is_list(Config) ->
after 1000 ->
ok
end,
- io:format("No leaking messages. Done.\n",[]),
+ ?P("No leaking messages. Done."),
gen_tcp:close(Socket).
otp_7731_send(Socket) ->
@@ -2985,37 +4465,37 @@ otp_7731_send(Socket) ->
{error,timeout} -> ok
end.
-otp_7731_server(ClientPid) ->
- {ok, LSocket} = gen_tcp:listen(0, [binary, {packet, raw},
+otp_7731_server(Config, ClientPid) ->
+ {ok, LSocket} = ?LISTEN(Config, 0, [binary, {packet, raw},
{active, false}]),
{ok, {_, PortNum}} = inet:sockname(LSocket),
- io:format("Listening on ~w with port number ~p\n", [LSocket, PortNum]),
+ ?P("Listening on ~w with port number ~p", [LSocket, PortNum]),
ClientPid ! {self(), ready, PortNum},
{ok, CSocket} = gen_tcp:accept(LSocket),
gen_tcp:close(LSocket),
- io:format("Server got connection, wait for recv order...\n",[]),
+ ?P("Server got connection, wait for recv order..."),
receive {ClientPid, recv} -> ok end,
- io:format("Server start receiving...\n",[]),
+ ?P("Server start receiving..."),
otp_7731_recv(CSocket),
ClientPid ! {self(), ok},
- io:format("Server finished, closing...\n",[]),
+ ?P("Server finished, closing..."),
gen_tcp:close(CSocket).
otp_7731_recv(Socket) ->
case gen_tcp:recv(Socket, 0, 1000) of
{ok, Bin} ->
- io:format("Server received ~p bytes\n",[size(Bin)]),
+ ?P("Server received ~p bytes", [size(Bin)]),
otp_7731_recv(Socket);
{error,timeout} ->
- io:format("Server got receive timeout\n",[]),
+ ?P("Server got receive timeout"),
ok
end.
@@ -3027,38 +4507,47 @@ otp_7731_recv(Socket) ->
zombie_sockets(Config) when is_list(Config) ->
register(zombie_collector,self()),
Calls = 10,
- Server = spawn_link(?MODULE, zombie_server,[self(), Calls]),
+ ?P("create zombie server"),
+ Server = spawn_link(?MODULE, zombie_server, [Config, self(), Calls]),
{Server, ready, PortNum} = receive Msg -> Msg end,
- io:format("Ports before = ~p\n",[lists:sort(erlang:ports())]),
- zombie_client_loop(Calls, PortNum),
- Ports = lists:sort(zombie_collector(Calls,[])),
+ ?P("Ports before = ~p",[lists:sort(erlang:ports())]),
+ zombie_client_loop(Config, Calls, PortNum),
+ Ports = lists:sort(zombie_collector(Calls, [])),
Server ! terminate,
- io:format("Collected ports = ~p\n",[Ports]),
+ ?P("Collected ports = ~p", [Ports]),
[] = zombies_alive(Ports, 10),
timer:sleep(1000),
+ ?P("done"),
ok.
-zombie_client_loop(0, _) -> ok;
-zombie_client_loop(N, PortNum) when is_integer(PortNum) ->
- {ok, Socket} = gen_tcp:connect("localhost", PortNum,
- [binary, {active, false}, {packet, raw}]),
+zombie_client_loop(_Config, 0, _) ->
+ ?P("[zombie client] done"),
+ ok;
+zombie_client_loop(Config, N, PortNum) when is_integer(PortNum) ->
+ ?P("[zombie client][~w] try connect", [N]),
+ {ok, Socket} = ?CONNECT(Config, "localhost", PortNum,
+ [binary, {active, false}, {packet, raw}]),
+ ?P("[zombie client] connected - now close ~p", [Socket]),
gen_tcp:close(Socket), % to make server recv fail
- zombie_client_loop(N-1, PortNum).
+ zombie_client_loop(Config, N-1, PortNum).
-zombie_collector(0,Acc) ->
+zombie_collector(0, Acc) ->
+ ?P("[zombie collector] done: "
+ "~n ~p", [Acc]),
Acc;
-zombie_collector(N,Acc) ->
+zombie_collector(N, Acc) ->
receive
{closed, Socket} ->
- zombie_collector(N-1,[Socket|Acc]);
+ ?P("[zombie collector] ~p closed", [Socket]),
+ zombie_collector(N-1, [Socket|Acc]);
E ->
{unexpected, E, Acc}
end.
zombies_alive(Ports, WaitSec) ->
Alive = lists:sort(erlang:ports()),
- io:format("Alive = ~p\n",[Alive]),
+ ?P("[zombies alive][~w] Alive: ~p", [WaitSec, Alive]),
Zombies = lists:filter(fun(P) -> lists:member(P, Alive) end, Ports),
case Zombies of
[] -> [];
@@ -3070,48 +4559,70 @@ zombies_alive(Ports, WaitSec) ->
end
end.
-zombie_server(Pid, Calls) ->
- {ok, LSocket} = gen_tcp:listen(0, [binary, {packet, raw},
- {active, false}, {backlog, Calls}]),
- {ok, {_, PortNum}} = inet:sockname(LSocket),
- io:format("Listening on ~w with port number ~p\n", [LSocket, PortNum]),
+zombie_server(Config, Pid, Calls) ->
+ ?P("[zombie server] try create listen socket with backlog: ~w", [Calls]),
+ {ok, LSock} = ?LISTEN(Config, 0, [binary, {packet, raw},
+ {active, false}, {backlog, Calls}]),
+ {ok, {_, PortNum}} = inet:sockname(LSock),
+ ?P("[zombie server] Listening on ~w with port number ~p", [LSock, PortNum]),
BigBin = list_to_binary(lists:duplicate(100*1024, 77)),
+ ?P("[zombie server] send ready"),
Pid ! {self(), ready, PortNum},
- zombie_accept_loop(LSocket, BigBin, Calls),
- terminate = receive Msg -> Msg end.
+ zombie_accept_loop(LSock, BigBin, Calls),
+ ?P("[zombie server] await terminate"),
+ terminate = receive Msg -> Msg end,
+ ?P("[zombie server] terminating"),
+ ok.
zombie_accept_loop(_, _, 0) ->
+ ?P("[zombie server] accept loop done"),
ok;
zombie_accept_loop(Socket, BigBin, Calls) ->
+ ?P("[zombie server][~w] try accept", [Calls]),
case gen_tcp:accept(Socket) of
{ok, NewSocket} ->
- spawn_link(fun() -> zombie_serve_client(NewSocket, BigBin) end),
+ ?P("[zombie server][~w] accepted ~p - create handler",
+ [Calls, NewSocket]),
+ spawn_link(fun() -> zombie_server_handler(NewSocket, BigBin) end),
zombie_accept_loop(Socket, BigBin, Calls-1);
E ->
E
end.
-zombie_serve_client(Socket, Bin) ->
- %%io:format("Got connection on ~p\n",[Socket]),
+zombie_server_handler(Socket, Bin) ->
+ ?P("[zombie server handler] got connection on ~p - attempt send", [Socket]),
gen_tcp:send(Socket, Bin),
- %%io:format("Sent data, waiting for reply on ~p\n",[Socket]),
+ ?P("[zombie server handler] Sent data, waiting for reply on ~p", [Socket]),
case gen_tcp:recv(Socket, 4) of
- {error,closed} -> ok;
- {error,econnaborted} -> ok % may be returned on Windows
+ {error, closed} ->
+ ?P("[zombie server handler] recv: closed (expected)"),
+ ok;
+ {error, econnaborted} -> % may be returned on Windows
+ ?P("[zombie server handler] recv: econnaborted (expected)"),
+ ok;
+ {error, Reason} ->
+ ?P("[zombie server handler] UNEXPECTED recv failure reason: ~p",
+ [Reason]),
+ exit({unexpected_recv_error_reason, Reason});
+ {ok, _} ->
+ ?P("[zombie server handler] UNEXPECTED recv success"),
+ exit(unexpected_recv_success)
end,
- %%io:format("Closing ~p\n",[Socket]),
+ ?P("[zombie server handler] close socket ~p", [Socket]),
gen_tcp:close(Socket),
+ ?P("[zombie server handler] socket closed: inform collector"),
zombie_collector ! {closed, Socket}.
+
%% Hanging send on windows when sending iolist with more than 16 binaries.
otp_7816(Config) when is_list(Config) ->
Client = self(),
- Server = spawn_link(fun()-> otp_7816_server(Client) end),
+ Server = spawn_link(fun()-> otp_7816_server(Config, Client) end),
receive {Server, ready, PortNum} -> ok end,
- {ok, Socket} = gen_tcp:connect("localhost", PortNum,
- [binary, {active, false}, {packet, 4},
- {send_timeout, 10}]),
+ {ok, Socket} = ?CONNECT(Config, "localhost", PortNum,
+ [binary, {active, false}, {packet, 4},
+ {send_timeout, 10}]),
%% We use the undocumented feature that sending can be resumed after
%% a send_timeout without any data loss if the peer starts to receive data.
%% Unless of course the 7816-bug is in affect, in which case the write event
@@ -3145,28 +4656,28 @@ otp_7816_send_data(Socket, Data, Loops) ->
end.
-otp_7816_server(Client) ->
- {ok, LSocket} = gen_tcp:listen(0, [binary, {packet, 4},
- {active, false}]),
+otp_7816_server(Config, Client) ->
+ {ok, LSocket} = ?LISTEN(Config, 0, [binary, {packet, 4},
+ {active, false}]),
{ok, {_, PortNum}} = inet:sockname(LSocket),
- io:format("Listening on ~w with port number ~p\n", [LSocket, PortNum]),
+ ?P("Listening on ~w with port number ~p", [LSocket, PortNum]),
Client ! {self(), ready, PortNum},
{ok, CSocket} = gen_tcp:accept(LSocket),
- io:format("Server got connection...\n",[]),
+ ?P("Server got connection..."),
gen_tcp:close(LSocket),
otp_7816_server_loop(CSocket),
- io:format("Server terminating.\n",[]).
+ ?P("Server terminating.").
otp_7816_server_loop(CSocket) ->
- io:format("Server waiting for order...\n",[]),
+ ?P("Server waiting for order..."),
receive
{Client, recv, RecvBytes} ->
- io:format("Server start receiving...\n",[]),
+ ?P("Server start receiving..."),
ok = otp_7816_recv(CSocket, RecvBytes),
@@ -3194,11 +4705,11 @@ otp_7816_recv(CSocket, BytesLeft) ->
%% Receive a packet with a faulty packet header.
otp_8102(Config) when is_list(Config) ->
- {ok, LSocket} = gen_tcp:listen(0, []),
+ {ok, LSocket} = ?LISTEN(Config, 0, []),
{ok, {_, PortNum}} = inet:sockname(LSocket),
- io:format("Listening on ~w with port number ~p\n", [LSocket, PortNum]),
+ ?P("Listening on ~w with port number ~p", [LSocket, PortNum]),
- [otp_8102_do(LSocket, PortNum, otp_8102_packet(Type,Size))
+ [otp_8102_do(Config, LSocket, PortNum, otp_8102_packet(Type,Size))
|| Size <- lists:seq(-10,-1),
Type <- [4, {cdr,big}, {cdr,little}]],
@@ -3212,31 +4723,31 @@ otp_8102_packet({cdr,big}, Size) ->
otp_8102_packet({cdr,little}, Size) ->
{<<"GIOP",0,0,1,0,Size:32/little>>, cdr}.
-otp_8102_do(LSocket, PortNum, {Bin,PType}) ->
+otp_8102_do(Config, LSocket, PortNum, {Bin,PType}) ->
- io:format("Connect with packet option ~p ...\n",[PType]),
- {ok, RSocket} = gen_tcp:connect("localhost", PortNum, [binary,
- {packet,PType},
- {active,true}]),
+ ?P("Connect with packet option ~p ...",[PType]),
+ {ok, RSocket} = ?CONNECT(Config, "localhost", PortNum, [binary,
+ {packet,PType},
+ {active,true}]),
{ok, SSocket} = gen_tcp:accept(LSocket),
- io:format("Got connection, sending ~p...\n",[Bin]),
+ ?P("Got connection, sending ~p...",[Bin]),
ok = gen_tcp:send(SSocket, Bin),
- io:format("Sending complete...\n",[]),
+ ?P("Sending complete...",[]),
{tcp_error,RSocket,emsgsize} = receive M -> M end,
- io:format("Got error msg, ok.\n",[]),
+ ?P("Got error msg, ok."),
gen_tcp:close(SSocket),
gen_tcp:close(RSocket).
%% Verify packet_size handles long HTTP header lines.
otp_9389(Config) when is_list(Config) ->
- {ok, LS} = gen_tcp:listen(0, [{active,false}]),
+ {ok, LS} = ?LISTEN(Config, 0, [{active,false}]),
{ok, {_, PortNum}} = inet:sockname(LS),
- io:format("Listening on ~w with port number ~p\n", [LS, PortNum]),
+ ?P("Listening on ~w with port number ~p", [LS, PortNum]),
OrigLinkHdr = "/" ++ string:chars($S, 8192),
_Server = spawn_link(
fun() ->
@@ -3245,7 +4756,7 @@ otp_9389(Config) when is_list(Config) ->
ok = otp_9389_loop(S, OrigLinkHdr),
ok = gen_tcp:close(S)
end),
- {ok, S} = gen_tcp:connect("localhost", PortNum,
+ {ok, S} = ?CONNECT(Config, "localhost", PortNum,
[binary, {active, false}]),
Req = "GET / HTTP/1.1\r\n"
++ "Host: localhost\r\n"
@@ -3296,17 +4807,17 @@ wrapping_oct() ->
%% Check that 64bit octet counters work.
wrapping_oct(Config) when is_list(Config) ->
- {ok,Sock} = gen_tcp:listen(0,[{active,false},{mode,binary}]),
+ {ok,Sock} = ?LISTEN(Config, 0,[{active,false},{mode,binary}]),
{ok,Port} = inet:port(Sock),
spawn_link(?MODULE,oct_acceptor,[Sock]),
- Res = oct_datapump(Port,16#10000FFFF),
+ Res = oct_datapump(Config, Port, 16#10000FFFF),
gen_tcp:close(Sock),
ok = Res,
ok.
-oct_datapump(Port,N) ->
- {ok,Sock} = gen_tcp:connect("localhost",Port,
- [{active,false},{mode,binary}]),
+oct_datapump(Config, Port, N) ->
+ {ok,Sock} = ?CONNECT(Config, "localhost", Port,
+ [{active,false},{mode,binary}]),
oct_pump(Sock,N,binary:copy(<<$a:8>>,100000),0).
oct_pump(S,N,_,_) when N =< 0 ->
@@ -3371,12 +4882,12 @@ otp_13939(suite) ->
otp_13939(Config) when is_list(Config) ->
{Pid, Ref} = spawn_opt(
fun() ->
- {ok, Listener} = gen_tcp:listen(0, [{exit_on_close, false}]),
+ {ok, Listener} = ?LISTEN(Config, 0, [{exit_on_close, false}]),
{ok, Port} = inet:port(Listener),
spawn_link(
fun() ->
- {ok, Client} = gen_tcp:connect("localhost", Port,
+ {ok, Client} = ?CONNECT(Config, "localhost", Port,
[{active, false}]),
ok = gen_tcp:close(Client)
end),
@@ -3442,13 +4953,13 @@ otp_12242(Config) when is_list(Config) ->
end])
of
[Addr|_] ->
- otp_12242(Addr);
+ otp_12242(Config, Addr);
Other ->
{skipped,{no_external_address,Other}}
end
- end;
+ end.
%%
-otp_12242(Addr) when tuple_size(Addr) =:= 4 ->
+otp_12242(Config, Addr) when tuple_size(Addr) =:= 4 ->
ct:timetrap(30000),
ct:pal("Using address ~p~n", [Addr]),
Bufsize = 16 * 1024,
@@ -3472,7 +4983,7 @@ otp_12242(Addr) when tuple_size(Addr) =:= 4 ->
spawn(
ListenerNode,
fun () ->
- {ok,L} = gen_tcp:listen(0, LOpts),
+ {ok,L} = ?LISTEN(Config, 0, LOpts),
{ok,LPort} = inet:port(L),
Tester ! {self(),port,LPort},
{ok,A} = gen_tcp:accept(L),
@@ -3484,7 +4995,7 @@ otp_12242(Addr) when tuple_size(Addr) =:= 4 ->
end),
ListenerMref = monitor(process, Listener),
LPort = receive {Listener,port,P} -> P end,
- {ok,C} = gen_tcp:connect(Addr, LPort, COpts, infinity),
+ {ok,C} = ?CONNECT(Config, Addr, LPort, COpts, infinity),
{ok,ReadCOpts} = inet:getopts(C, [recbuf,sndbuf,buffer]),
ct:pal("ReadCOpts ~p~n", [ReadCOpts]),
%%
@@ -3544,34 +5055,197 @@ wait(Mref) ->
%% OTP-15536
%% Test that send error works correctly for delay_send
-delay_send_error(_Config) ->
+delay_send_error(Config) ->
+ ?P("create listen socket"),
{ok, L} =
- gen_tcp:listen(
+ ?LISTEN(Config,
0, [{reuseaddr, true}, {packet, 1}, {active, false}]),
{ok,{{0,0,0,0},PortNum}}=inet:sockname(L),
+ ?P("try connect - with delay_send:true"),
{ok, C} =
- gen_tcp:connect(
+ ?CONNECT(Config,
"localhost", PortNum,
[{packet, 1}, {active, false}, {delay_send, true}]),
+ ?P("try accept"),
{ok, S} = gen_tcp:accept(L),
%% Do a couple of sends first to see that it works
+ ?P("send data"),
ok = gen_tcp:send(C, "hello"),
+ ?P("send data"),
ok = gen_tcp:send(C, "hello"),
+ ?P("send data"),
ok = gen_tcp:send(C, "hello"),
%% Close the receiver
+ ?P("close receiver (accepted socket)"),
ok = gen_tcp:close(S),
%%
+ ?P("send data"),
case gen_tcp:send(C, "hello") of
ok ->
+ ?P("send data"),
case gen_tcp:send(C, "hello") of
ok ->
- timer:sleep(1000), %% Sleep in order for delay_send to have time to trigger
- %% This used to result in a double free
- {error, closed} = gen_tcp:send(C, "hello");
+ delay_send_error2(C);
{error, closed} ->
+ ?P("closed (expected)"),
ok
end;
{error, closed} ->
+ ?P("closed (expected)"),
ok
end,
ok = gen_tcp:close(C).
+
+
+delay_send_error2(Sock) ->
+ delay_send_error2(Sock, 3).
+
+delay_send_error2(Sock, 0) ->
+ gen_tcp:close(Sock),
+ ct:fail("Unxpected send success");
+delay_send_error2(Sock, N) ->
+ %% Sleep in order for delay_send to have time to trigger
+ %% This used to result in a double free
+ timer:sleep(1000),
+ case gen_tcp:send(Sock, "hello") of
+ ok ->
+ delay_send_error2(Sock, N-1);
+ {error, closed} ->
+ ?P("closed (expected, ~w)", [N]),
+ ok;
+ {error, Reason} ->
+ ct:fail(?F("Unexpected send error: ~p", [Reason]))
+ end.
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+send_failed_str(Reason) ->
+ ?F("Send failed: ~w", [Reason]).
+
+connect_failed_str(Reason) ->
+ ?F("Connect failed: ~w", [Reason]).
+
+listen_failed_str(Reason) ->
+ ?F("Listen failed: ~w", [Reason]).
+
+accept_failed_str(Reason) ->
+ ?F("Accept failed: ~w", [Reason]).
+
+port_failed_str(Reason) ->
+ ?F("Port failed: ~w", [Reason]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+-define(ACTIVE_N, 20).
+
+%% 30-second test for gen_tcp in {active, N} mode, ensuring it does not get stuck.
+%% Verifies that erl_check_io properly handles extra EPOLLIN signals.
+bidirectional_traffic(Config) when is_list(Config) ->
+ ?TC_TRY(bidirectional_traffic,
+ fun() -> do_bidirectional_traffic(Config) end).
+
+do_bidirectional_traffic(_Config) ->
+ ?P("begin"),
+ Workers = erlang:system_info(schedulers_online) * 2,
+ ?P("Use ~w workers", [Workers]),
+ Payload = crypto:strong_rand_bytes(32),
+ ?P("create listen socket"),
+ {ok, LSock} = gen_tcp:listen(0, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]),
+ %% get all sockets to know failing ends
+ {ok, Port} = inet:port(LSock),
+ ?P("listen socket port number ~w", [Port]),
+ Control = self(),
+ ?P("create ~w receivers", [Workers]),
+ Receivers = [spawn_link(fun () -> exchange(LSock, Port, Payload, Control) end) || _ <- lists:seq(1, Workers)],
+ ?P("await the result"),
+ Result =
+ receive
+ {timeout, Socket, Total} ->
+ ?P("timeout msg for ~p: ~w", [Socket, Total]),
+ {fail, {timeout, Socket, Total}};
+ {error, Socket, Reason} ->
+ ?P("error msg for ~p: ~p", [Socket, Reason]),
+ {fail, {error, Socket, Reason}}
+ after 30000 ->
+ %% if it does not fail in 30 seconds, it most likely works
+ ?P("timeout => success?"),
+ ok
+ end,
+ ?P("terminate receivers"),
+ [begin unlink(Rec), exit(Rec, kill) end || Rec <- Receivers],
+ ?P("done"),
+ Result.
+
+exchange(LSock, Port, Payload, Control) ->
+ %% spin up client
+ _ClntRcv = spawn(
+ fun () ->
+ ?P("connect"),
+ {ok, Client} =
+ gen_tcp:connect("localhost",
+ Port,
+ [binary, {packet, 0}, {active, ?ACTIVE_N}]),
+ ?P("connected: ~p", [Client]),
+ send_recv_loop(Client, Payload, Control)
+ end),
+ ?P("accept"),
+ {ok, Socket} = gen_tcp:accept(LSock),
+ ?P("accepted: ~p", [Socket]),
+ %% sending process
+ send_recv_loop(Socket, Payload, Control).
+
+send_recv_loop(Socket, Payload, Control) ->
+ %% {active, N} must be set to active > 12 to trigger the issue
+ %% {active, 30} seems to trigger it quite often & reliably
+ ?P("set (initial) active: ~p", [?ACTIVE_N]),
+ inet:setopts(Socket, [{active, ?ACTIVE_N}]),
+ ?P("spawn sender"),
+ _Snd = spawn_link(
+ fun Sender() ->
+ _ = gen_tcp:send(Socket, Payload),
+ Sender()
+ end),
+ ?P("begin recv"),
+ recv(Socket, 0, Control).
+
+recv(Socket, Total, Control) ->
+ receive
+ {tcp, Socket, Data} ->
+ recv(Socket, Total + byte_size(Data), Control);
+ {tcp_passive, Socket} ->
+ inet:setopts(Socket, [{active, ?ACTIVE_N}]),
+ recv(Socket, Total, Control);
+ {tcp_closed, Socket} ->
+ ?P("[recv] closed when total received: ~w", [Total]),
+ exit(terminate);
+ Other ->
+ ?P("[recv] received unexpected when total received: ~w"
+ "~n ~p"
+ "~n Socket: ~p"
+ "~n Port stat: ~p"
+ "~n Port status: ~p"
+ "~n Port Info: ~p",
+ [Total, Other,
+ Socket,
+ (catch inet:getstat(Socket)),
+ (catch prim_inet:getstatus(Socket)),
+ (catch erlang:port_info(Socket))]),
+ Control ! {error, Socket, Other}
+ after 2000 ->
+ %% no data received in 2 seconds, test failed
+ ?P("[recv] received nothing when total received: ~w"
+ "~n Socket: ~p"
+ "~n Port stat: ~p"
+ "~n Port status: ~p"
+ "~n Port Info: ~p",
+ [Total,
+ Socket,
+ (catch inet:getstat(Socket)),
+ (catch prim_inet:getstatus(Socket)),
+ (catch erlang:port_info(Socket))]),
+ Control ! {timeout, Socket, Total}
+ end.
diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl
index 2720d3cc77..a52f70933e 100644
--- a/lib/kernel/test/gen_udp_SUITE.erl
+++ b/lib/kernel/test/gen_udp_SUITE.erl
@@ -23,7 +23,9 @@
%% because udp is not deterministic.
%%
-module(gen_udp_SUITE).
+
-include_lib("common_test/include/ct.hrl").
+-include("kernel_test_lib.hrl").
%% XXX - we should pick a port that we _know_ is closed. That's pretty hard.
@@ -40,31 +42,79 @@
recvtos/1, recvtosttl/1, recvttl/1, recvtclass/1,
sendtos/1, sendtosttl/1, sendttl/1, sendtclass/1,
local_basic/1, local_unbound/1,
- local_fdopen/1, local_fdopen_unbound/1, local_abstract/1]).
+ local_fdopen/1, local_fdopen_unbound/1, local_abstract/1,
+ recv_close/1]).
+
+-define(TRY_TC(F), try_tc(F)).
+
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,1}}].
all() ->
- [send_to_closed, buffer_size, binary_passive_recv, max_buffer_size,
- bad_address, read_packets, recv_poll_after_active_once,
- open_fd, connect,
- implicit_inet6, active_n,
+ [
+ send_to_closed,
+ buffer_size,
+ binary_passive_recv,
+ max_buffer_size,
+ bad_address,
+ read_packets,
+ recv_poll_after_active_once,
+ open_fd,
+ connect,
+ implicit_inet6,
+ active_n,
recvtos, recvtosttl, recvttl, recvtclass,
sendtos, sendtosttl, sendttl, sendtclass,
- {group, local}].
+ {group, local},
+ recv_close
+ ].
groups() ->
- [{local, [],
- [local_basic, local_unbound,
- local_fdopen, local_fdopen_unbound, local_abstract]}].
+ [
+ {local, [], local_cases()}
+ ].
+
+local_cases() ->
+ [
+ local_basic,
+ local_unbound,
+ local_fdopen,
+ local_fdopen_unbound,
+ local_abstract
+ ].
+
+init_per_suite(Config0) ->
+
+ ?P("init_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ case ?LIB:init_per_suite(Config0) of
+ {skip, _} = SKIP ->
+ SKIP;
+
+ Config1 when is_list(Config1) ->
+
+ ?P("init_per_suite -> end when "
+ "~n Config: ~p", [Config1]),
+
+ Config1
+ end.
-init_per_suite(Config) ->
- Config.
+end_per_suite(Config0) ->
-end_per_suite(_Config) ->
- ok.
+ ?P("end_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ Config1 = ?LIB:end_per_suite(Config0),
+
+ ?P("end_per_suite -> "
+ "~n Nodes: ~p", [erlang:nodes()]),
+
+ Config1.
init_per_group(local, Config) ->
case gen_udp:open(0, [local]) of
@@ -83,6 +133,9 @@ end_per_group(_GroupName, Config) ->
Config.
+init_per_testcase(read_packets, Config) ->
+ ct:timetrap({minutes, 2}),
+ Config;
init_per_testcase(_Case, Config) ->
Config.
@@ -310,24 +363,33 @@ bad_address(Config) when is_list(Config) ->
%% OTP-6249 UDP option for number of packet reads.
read_packets(Config) when is_list(Config) ->
- N1 = 5,
- N2 = 1,
+ N1 = 5,
+ N2 = 1,
Msgs = 30000,
- {ok,R} = gen_udp:open(0, [{read_packets,N1}]),
- {ok,RP} = inet:port(R),
+ ?P("open socket (with read-packets: ~p)", [N1]),
+ {ok, R} = gen_udp:open(0, [{read_packets,N1}]),
+ {ok, RP} = inet:port(R),
+ ?P("create slave node"),
{ok,Node} = start_node(gen_udp_SUITE_read_packets),
%%
+ ?P("perform read-packets test"),
{V1, Trace1} = read_packets_test(R, RP, Msgs, Node),
+ ?P("verify read-packets (to ~w)", [N1]),
{ok,[{read_packets,N1}]} = inet:getopts(R, [read_packets]),
%%
- ok = inet:setopts(R, [{read_packets,N2}]),
+ ?P("set new read-packets: ~p", [N2]),
+ ok = inet:setopts(R, [{read_packets, N2}]),
+ ?P("perform read-packets test"),
{V2, Trace2} = read_packets_test(R, RP, Msgs, Node),
+ ?P("verify read-packets (to ~w)", [N2]),
{ok,[{read_packets,N2}]} = inet:getopts(R, [read_packets]),
%%
+ ?P("stop slave node"),
stop_node(Node),
- ct:log("N1=~p, V1=~p vs N2=~p, V2=~p",[N1,V1,N2,V2]),
+ ?P("dump trace 1"),
dump_terms(Config, "trace1.terms", Trace1),
+ ?P("dump trace 2"),
dump_terms(Config, "trace2.terms", Trace2),
%% Because of the inherit racy-ness of the feature it is
@@ -337,6 +399,12 @@ read_packets(Config) when is_list(Config) ->
%% the max number of executions a port is allowed to
%% do before being re-scheduled is N * 20
+ ?P("read-packets test verification when: "
+ "~n N1: ~p"
+ "~n V1: ~p"
+ "~n vs"
+ "~n N2: ~p"
+ "~n V2: ~p", [N1, V1, N2, V2]),
if
V1 > (N1 * 20) ->
ct:fail("Got ~p msgs, max was ~p", [V1, N1]);
@@ -344,7 +412,9 @@ read_packets(Config) when is_list(Config) ->
ct:fail("Got ~p msgs, max was ~p", [V2, N2]);
true ->
ok
- end.
+ end,
+ ?P("done"),
+ ok.
dump_terms(Config, Name, Terms) ->
FName = filename:join(proplists:get_value(priv_dir, Config),Name),
@@ -407,7 +477,7 @@ read_packets_recv(N) ->
read_packets_verify(R, SP, Trace) ->
[Max | _] = Pkts = lists:reverse(lists:sort(read_packets_verify(R, SP, Trace, 0))),
- ct:pal("~p",[lists:sublist(Pkts,10)]),
+ ?P("read-packets verify: ~p", [lists:sublist(Pkts,10)]),
Max.
read_packets_verify(R, SP, [{trace,R,OutIn,_}|Trace], M)
@@ -624,11 +694,17 @@ recvtclass(_Config) ->
sendtos(_Config) ->
+ ?TC_TRY(sendtos, fun() -> do_sendtos() end).
+
+do_sendtos() ->
test_recv_opts(
inet, [{recvtos,tos,96}], true,
fun sendtos_ok/2).
sendtosttl(_Config) ->
+ ?TC_TRY(sendtosttl, fun() -> do_sendtosttl() end).
+
+do_sendtosttl() ->
test_recv_opts(
inet, [{recvtos,tos,96},{recvttl,ttl,33}], true,
fun (OSType, OSVer) ->
@@ -636,11 +712,17 @@ sendtosttl(_Config) ->
end).
sendttl(_Config) ->
+ ?TC_TRY(sendttl, fun() -> do_sendttl() end).
+
+do_sendttl() ->
test_recv_opts(
inet, [{recvttl,ttl,33}], true,
fun sendttl_ok/2).
sendtclass(_Config) ->
+ ?TC_TRY(sendtclass, fun() -> do_sendtclass() end).
+
+do_sendtclass() ->
{ok,IFs} = inet:getifaddrs(),
case
[Name ||
@@ -652,9 +734,10 @@ sendtclass(_Config) ->
inet6, [{recvtclass,tclass,224}], true,
fun sendtclass_ok/2);
[] ->
- {skip,ipv6_not_supported,IFs}
+ {skip, {ipv6_not_supported, IFs}}
end.
+
%% These version numbers are just above the highest noted in daily tests
%% where the test fails for a plausible reason, that is the lowest
%% where we can expect that the test might succeed, so
@@ -669,7 +752,9 @@ sendtclass(_Config) ->
%% Using the option returns einval, so it is not implemented.
recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0});
%% Using the option returns einval, so it is not implemented.
-recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0});
+recvtos_ok({unix,openbsd}, _OSVer) -> false; % not semver_lt(OSVer, {6,9,0});
+%% Using the option returns einval, so it is not implemented.
+recvtos_ok({unix,netbsd}, _OSVer) -> false;
%% Using the option returns einval, so it is not implemented.
recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0});
%%
@@ -698,22 +783,23 @@ recvtclass_ok(_, _) -> false.
%% Using the option returns einval, so it is not implemented.
sendtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0});
-sendtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0});
+sendtos_ok({unix,netbsd}, _OSVer) -> false;
+sendtos_ok({unix,openbsd}, _OSVer) -> false; % not semver_lt(OSVer, {6,9,0});
sendtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0});
sendtos_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {4,0,0});
-sendtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0});
+sendtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,2,0});
%%
sendtos_ok({unix,_}, _) -> true;
sendtos_ok(_, _) -> false.
%% Using the option returns einval, so it is not implemented.
-sendttl_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0});
+sendttl_ok({unix,darwin}, OSVer) -> false; % not semver_lt(OSVer, {19,6,0});
sendttl_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {4,0,0});
%% Using the option returns enoprotoopt, so it is not implemented.
-sendttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0});
+sendttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,2,0});
%% Option has no effect
sendttl_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0});
-sendttl_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0});
+sendttl_ok({unix,openbsd}, _OSVer) -> false; % not semver_lt(OSVer, {6,9,0});
%%
sendttl_ok({unix,_}, _) -> true;
sendttl_ok(_, _) -> false.
@@ -728,24 +814,31 @@ sendtclass_ok({unix,_}, _) -> true;
sendtclass_ok(_, _) -> false.
-semver_lt({X1,Y1,Z1}, {X2,Y2,Z2}) ->
+semver_lt({X1,Y1,Z1} = V1, {X2,Y2,Z2} = V2) ->
+ ?P("semver_lt -> OS version check:"
+ "~n Version 1: ~p"
+ "~n Version 2: ~p", [V1, V2]),
if
- X1 > X2 -> false;
- X1 < X2 -> true;
- Y1 > Y2 -> false;
- Y1 < Y2 -> true;
- Z1 > Z2 -> false;
- Z1 < Z2 -> true;
- true -> false
+ X1 > X2 -> ?P("semver_lt -> X1 > X2: ~p > ~p", [X1, X2]), false;
+ X1 < X2 -> ?P("semver_lt -> X1 < X2: ~p < ~p", [X1, X2]), true;
+ Y1 > Y2 -> ?P("semver_lt -> Y1 > Y2: ~p > ~p", [Y1, Y2]), false;
+ Y1 < Y2 -> ?P("semver_lt -> Y1 < Y2: ~p < ~p", [Y1, Y2]), true;
+ Z1 > Z2 -> ?P("semver_lt -> Z1 > Z2: ~p > ~p", [Z1, Z2]), false;
+ Z1 < Z2 -> ?P("semver_lt -> Z1 < Z2: ~p < ~p", [Z1, Z2]), true;
+ true -> ?P("semver_lt -> default"), false
end;
-semver_lt(_, {_,_,_}) -> false.
+semver_lt(V1, {_,_,_} = V2) ->
+ ?P("semver_lt -> fallback OS version check when: "
+ "~n Version 1: ~p"
+ "~n Version 2: ~p", [V1, V2]),
+ false.
test_recv_opts(Family, Spec, TestSend, OSFilter) ->
OSType = os:type(),
- OSVer = os:version(),
+ OSVer = os:version(),
case OSFilter(OSType, OSVer) of
true ->
- io:format("Os: ~p, ~p~n", [OSType,OSVer]),
+ ?P("OS: ~p, ~p", [OSType, OSVer]),
test_recv_opts(Family, Spec, TestSend, OSType, OSVer);
false ->
{skip,{not_supported_for_os_version,{OSType,OSVer}}}
@@ -791,8 +884,18 @@ test_recv_opts(Family, Spec, TestSend, _OSType, _OSVer) ->
ok = gen_udp:send(S1, Addr, P2, <<"fghij">>),
TestSend andalso
begin
- ok = gen_udp:send(S2, Addr, P1, OptsVals, <<"ABCDE">>),
- ok = gen_udp:send(S2, {Addr,P1}, OptsVals, <<"12345">>)
+ case gen_udp:send(S2, Addr, P1, OptsVals, <<"ABCDE">>) of
+ ok ->
+ ok;
+ {error, enoprotoopt = Reason1} ->
+ ?SKIPT(?F("send (1) failed: ~p", [Reason1]))
+ end,
+ case gen_udp:send(S2, {Addr,P1}, OptsVals, <<"12345">>) of
+ ok ->
+ ok;
+ {error, enoprotoopt = Reason2} ->
+ ?SKIPT(?F("send (2) failed: ~p", [Reason2]))
+ end
end,
{ok,{_,P2,OptsVals3,<<"abcde">>}} = gen_udp:recv(S1, 0, Timeout),
verify_sets_eq(OptsVals3, OptsVals2),
@@ -969,45 +1072,93 @@ local_handshake(S, SAddr, C, CAddr) ->
end.
-%%
-%% Utils
-%%
-start_node(Name) ->
- Pa = filename:dirname(code:which(?MODULE)),
- test_server:start_node(Name, slave, [{args, "-pa " ++ Pa}]).
-stop_node(Node) ->
- test_server:stop_node(Node).
+%%-------------------------------------------------------------
+%% Open a passive socket. Create a socket that reads from it.
+%% Then close the socket.
+recv_close(Config) when is_list(Config) ->
+ ?P("begin"),
+ {ok, Sock} = gen_udp:open(0, [{active, false}]),
+ RECV = fun() ->
+ ?P("try recv"),
+ Res = gen_udp:recv(Sock, 0),
+ ?P("recv res: ~p", [Res]),
+ exit(Res)
+ end,
+ ?P("spawn reader"),
+ {Pid, MRef} = spawn_monitor(RECV),
+ receive
+ {'DOWN', MRef, process, Pid, PreReason} ->
+ %% Make sure id does not die for some other reason...
+ ?line ct:fail("Unexpected pre close from reader (~p): ~p",
+ [Pid, PreReason])
+ after 5000 -> % Just in case...
+ ok
+ end,
+ ?P("close socket"),
+ ok = gen_udp:close(Sock),
+ ?P("await reader termination"),
+ receive
+ {'DOWN', MRef, process, Pid, {error, closed}} ->
+ ?P("expected reader termination result"),
+ ok;
+ {'DOWN', MRef, process, Pid, PostReason} ->
+ ?P("unexpected reader termination: ~p", [PostReason]),
+ ?line ct:fail("Unexpected post close from reader (~p): ~p",
+ [Pid, PostReason])
+ after 5000 ->
+ ?P("unexpected reader termination timeout"),
+ demonitor(MRef, [flush]),
+ exit(Pid, kill),
+ ?line ct:fail("Reader (~p) termination timeout", [Pid])
+ end,
+ ?P("done"),
+ ok.
+
+
%% Test that connect/3 has effect.
connect(Config) when is_list(Config) ->
Addr = {127,0,0,1},
+ ?P("try create first socket"),
{ok,S1} = gen_udp:open(0),
{ok,P1} = inet:port(S1),
+ ?P("try create second socket"),
{ok,S2} = gen_udp:open(0),
+ ?P("try set second socket active: false"),
ok = inet:setopts(S2, [{active,false}]),
+ ?P("try close first socket"),
ok = gen_udp:close(S1),
+ ?P("try connect second socket to: ~p, ~p", [Addr, P1]),
ok = gen_udp:connect(S2, Addr, P1),
+ ?P("try send on second socket"),
ok = gen_udp:send(S2, <<16#deadbeef:32>>),
+ ?P("try recv on second socket - expect failure"),
ok = case gen_udp:recv(S2, 0, 500) of
- {error,econnrefused} -> ok;
- {error,econnreset} -> ok;
- Other -> Other
+ {error, econnrefused} -> ok;
+ {error, econnreset} -> ok;
+ Other ->
+ ?P("UNEXPECTED failure: ~p", [Other]),
+ Other
end,
+ ?P("done"),
ok.
implicit_inet6(Config) when is_list(Config) ->
+ ?TC_TRY(implicit_inet6, fun() -> do_implicit_inet6(Config) end).
+
+do_implicit_inet6(_Config) ->
Host = ok(inet:gethostname()),
case inet:getaddr(Host, inet6) of
- {ok,{16#fe80,0,0,0,_,_,_,_} = Addr} ->
+ {ok, {16#fe80,0,0,0,_,_,_,_} = Addr} ->
{skip,
"Got link local IPv6 address: "
++inet:ntoa(Addr)};
- {ok,Addr} ->
+ {ok, Addr} ->
implicit_inet6(Host, Addr);
- {error,Reason} ->
+ {error, Reason} ->
{skip,
"Can not look up IPv6 address: "
++atom_to_list(Reason)}
@@ -1016,40 +1167,75 @@ implicit_inet6(Config) when is_list(Config) ->
implicit_inet6(Host, Addr) ->
Active = {active,false},
Loopback = {0,0,0,0,0,0,0,1},
- case gen_udp:open(0, [inet6,Active,{ip, Loopback}]) of
- {ok,S1} ->
- io:format("~s ~p~n", ["::1",Loopback]),
- implicit_inet6(S1, Active, Loopback),
- ok = gen_udp:close(S1),
- %%
- Localaddr = ok(get_localaddr()),
- S2 = ok(gen_udp:open(0, [{ip,Localaddr},Active])),
- implicit_inet6(S2, Active, Localaddr),
- ok = gen_udp:close(S2),
- %%
- io:format("~s ~p~n", [Host,Addr]),
- S3 = ok(gen_udp:open(0, [{ifaddr,Addr},Active])),
- implicit_inet6(S3, Active, Addr),
- ok = gen_udp:close(S3);
- _ ->
- {skip,"IPv6 not supported"}
- end.
+ ?P("try 1 with explit inet6 on loopback"),
+ S1 = case gen_udp:open(0, [inet6, Active, {ip, Loopback}]) of
+ {ok, Sock1} ->
+ Sock1;
+ {error, eaddrnotavail = Reason1} ->
+ ?SKIPT(open_failed_str(Reason1));
+ _ ->
+ ?SKIPT("IPv6 not supported")
+ end,
+ implicit_inet6(S1, Active, Loopback),
+ ok = gen_udp:close(S1),
+ %%
+ Localaddr = ok(get_localaddr()),
+ ?P("try 2 on local addr (~p)", [Localaddr]),
+ S2 = case gen_udp:open(0, [{ip, Localaddr}, Active]) of
+ {ok, Sock2} ->
+ Sock2;
+ {error, eaddrnotavail = Reason2} ->
+ ?SKIPT(open_failed_str(Reason2))
+ end,
+ implicit_inet6(S2, Active, Localaddr),
+ ok = gen_udp:close(S2),
+ %%
+ ?P("try 3 on addr ~p (~p)", [Addr, Host]),
+ S3 = case gen_udp:open(0, [{ifaddr, Addr}, Active]) of
+ {ok, Sock3} ->
+ Sock3;
+ {error, eaddrnotavail = Reason3} ->
+ ?SKIPT(open_failed_str(Reason3))
+ end,
+ implicit_inet6(S3, Active, Addr),
+ ok = gen_udp:close(S3),
+ ok.
implicit_inet6(S1, Active, Addr) ->
+ ?P("get (\"local\") port number"),
P1 = ok(inet:port(S1)),
- S2 = ok(gen_udp:open(0, [inet6,Active])),
+ ?P("open \"remote\" socket"),
+ S2 = case gen_udp:open(0, [inet6, Active]) of
+ {ok, Sock2} ->
+ Sock2;
+ {error, eaddrnotavail = Reason3} ->
+ ?SKIPT(open_failed_str(Reason3))
+ end,
+ ?P("get (\"remote\") port number"),
P2 = ok(inet:port(S2)),
+ ?P("connect (\"remote\") socket (to ~p:~p)", [Addr, P1]),
ok = gen_udp:connect(S2, Addr, P1),
+ ?P("connect (\"local\") socket (to ~p:~p)", [Addr, P2]),
ok = gen_udp:connect(S1, Addr, P2),
+ ?P("peername of \"local\" socket"),
{Addr,P2} = ok(inet:peername(S1)),
+ ?P("peername of \"remote\" socket"),
{Addr,P1} = ok(inet:peername(S2)),
+ ?P("sockname of \"local\" socket"),
{Addr,P1} = ok(inet:sockname(S1)),
+ ?P("sockname of \"remote\" socket"),
{Addr,P2} = ok(inet:sockname(S2)),
+ ?P("send ping on \"local\" socket (to ~p:~p)", [Addr, P2]),
ok = gen_udp:send(S1, Addr, P2, "ping"),
+ ?P("recv ping on \"remote\" socket (from ~p:~p)", [Addr, P1]),
{Addr,P1,"ping"} = ok(gen_udp:recv(S2, 1024, 1000)),
+ ?P("send pong on \"remote\" socket (to ~p:~p)", [Addr, P1]),
ok = gen_udp:send(S2, Addr, P1, "pong"),
+ ?P("recv ping on \"local\" socket (from ~p:~p)", [Addr, P2]),
{Addr,P2,"pong"} = ok(gen_udp:recv(S1, 1024)),
- ok = gen_udp:close(S2).
+ ?P("close \"remote\" socket"),
+ ok = gen_udp:close(S2),
+ ok.
ok({ok,V}) -> V;
ok(NotOk) ->
@@ -1084,8 +1270,28 @@ get_localaddr([]) ->
get_localaddr([Localhost|Ls]) ->
case inet:getaddr(Localhost, inet6) of
{ok, LocalAddr} ->
- io:format("~s ~p~n", [Localhost, LocalAddr]),
+ ?P("found local address: ~s ~p", [Localhost, LocalAddr]),
{ok, LocalAddr};
_ ->
get_localaddr(Ls)
end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%
+%% Utils
+%%
+
+start_node(Name) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{args, "-pa " ++ Pa}]).
+
+stop_node(Node) ->
+ test_server:stop_node(Node).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+open_failed_str(Reason) ->
+ ?F("Open failed: ~w", [Reason]).
diff --git a/lib/kernel/test/global_SUITE.erl b/lib/kernel/test/global_SUITE.erl
index 5bff9cc292..22db756d97 100644
--- a/lib/kernel/test/global_SUITE.erl
+++ b/lib/kernel/test/global_SUITE.erl
@@ -43,7 +43,7 @@
-export([global_load/3, lock_global/2, lock_global2/2]).
-export([]).
--export([mass_spawn/1]).
+-export([init_mass_spawn/1]).
-export([start_tracer/0, stop_tracer/0, get_trace/0]).
@@ -3738,13 +3738,17 @@ start_node(Name, How, Config) ->
start_node(Name0, How, Args, Config) ->
Name = node_name(Name0, Config),
Pa = filename:dirname(code:which(?MODULE)),
- R = test_server:start_node(Name, How, [{args,
- Args ++ " " ++
- "-kernel net_setuptime 100 "
- %% "-noshell "
- "-pa " ++ Pa},
- {linked, false}
- ]),
+ R = test_server:start_node(
+ Name, How, [{args,
+ Args ++
+ " -kernel net_setuptime 100 " ++
+ %% Limit the amount of threads so that we
+ %% don't run into the maximum allowed
+ " +S 1 +SDio 1 " ++
+ %% "-noshell "
+ "-pa " ++ Pa},
+ {linked, false}
+ ]),
%% {linked,false} only seems to work for slave nodes.
%% ct:sleep(1000),
record_started_node(R).
@@ -3761,12 +3765,16 @@ start_node_rel(Name0, Rel, Config) ->
end,
Env = [],
Pa = filename:dirname(code:which(?MODULE)),
- Res = test_server:start_node(Name, peer,
- [{args,
- Compat ++
- " -kernel net_setuptime 100 "
- " -pa " ++ Pa},
- {erl, Release}] ++ Env),
+ Res = test_server:start_node(
+ Name, peer,
+ [{args,
+ Compat ++
+ " -kernel net_setuptime 100 " ++
+ %% Limit the amount of threads so that we
+ %% don't run into the maximum allowed
+ " +S 1 +SDio 1 " ++
+ "-pa " ++ Pa},
+ {erl, Release}] ++ Env),
record_started_node(Res).
record_started_node({ok, Node}) ->
@@ -3887,7 +3895,7 @@ mass_death(Config) when is_list(Config) ->
io:format("Nodes: ~p~n", [Nodes]),
Ns = lists:seq(1, 40),
%% Start processes with globally registered names on the nodes
- {Pids,[]} = rpc:multicall(Nodes, ?MODULE, mass_spawn, [Ns]),
+ {Pids,[]} = rpc:multicall(Nodes, ?MODULE, init_mass_spawn, [Ns]),
io:format("Pids: ~p~n", [Pids]),
%% Wait...
ct:sleep(10000),
@@ -3924,6 +3932,11 @@ wait_mass_death(Nodes, OrigNames, Then, Config) ->
wait_mass_death(Nodes, OrigNames, Then, Config)
end.
+init_mass_spawn(N) ->
+ Pid = mass_spawn(N),
+ unlink(Pid),
+ Pid.
+
mass_spawn([]) ->
ok;
mass_spawn([N|T]) ->
@@ -3937,7 +3950,10 @@ mass_spawn([N|T]) ->
Parent ! self(),
loop()
end),
- receive Pid -> Pid end.
+ receive
+ Pid ->
+ Pid
+ end.
mass_names([], _) ->
[];
diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl
index e5e4074565..8f8a10103f 100644
--- a/lib/kernel/test/inet_SUITE.erl
+++ b/lib/kernel/test/inet_SUITE.erl
@@ -23,9 +23,14 @@
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/src/inet_res.hrl").
-include_lib("kernel/src/inet_dns.hrl").
+-include("kernel_test_lib.hrl").
+
+-export([
+ all/0, suite/0, groups/0,
+ init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2,
+ init_per_testcase/2, end_per_testcase/2,
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
- init_per_group/2,end_per_group/2,
t_gethostbyaddr/0, t_gethostbyaddr/1,
t_getaddr/0, t_getaddr/1,
t_gethostbyname/0, t_gethostbyname/1,
@@ -35,7 +40,8 @@
ipv4_to_ipv6/0, ipv4_to_ipv6/1,
host_and_addr/0, host_and_addr/1,
t_gethostnative/1,
- gethostnative_parallell/1, cname_loop/1, missing_hosts_reload/1,
+ gethostnative_parallell/1, cname_loop/1,
+ missing_hosts_reload/1, hosts_file_quirks/1,
gethostnative_soft_restart/0, gethostnative_soft_restart/1,
gethostnative_debug_level/0, gethostnative_debug_level/1,
lookup_bad_search_option/1,
@@ -43,29 +49,48 @@
getif_ifr_name_overflow/1,getservbyname_overflow/1, getifaddrs/1,
parse_strict_address/1, ipv4_mapped_ipv6_address/1,
simple_netns/1, simple_netns_open/1,
- simple_bind_to_device/1, simple_bind_to_device_open/1]).
+ add_del_host/1, add_del_host_v6/1,
+ simple_bind_to_device/1, simple_bind_to_device_open/1
+ ]).
+
+-export([
+ get_hosts/1, get_ipv6_hosts/1, parse_hosts/1, parse_address/1,
+ kill_gethost/0, parallell_gethost/0, test_netns/0
+ ]).
+
--export([get_hosts/1, get_ipv6_hosts/1, parse_hosts/1, parse_address/1,
- kill_gethost/0, parallell_gethost/0, test_netns/0]).
--export([init_per_testcase/2, end_per_testcase/2]).
suite() ->
- [{ct_hooks,[ts_install_cth]},
- {timetrap,{minutes,1}}].
+ [
+ {ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}
+ ].
all() ->
- [t_gethostbyaddr, t_gethostbyname, t_getaddr,
+ [
+ t_gethostbyaddr, t_gethostbyname, t_getaddr,
t_gethostbyaddr_v6, t_gethostbyname_v6, t_getaddr_v6,
ipv4_to_ipv6, host_and_addr, {group, parse},
- t_gethostnative, gethostnative_parallell, cname_loop, missing_hosts_reload,
+ t_gethostnative, gethostnative_parallell, cname_loop,
+ missing_hosts_reload, hosts_file_quirks,
gethostnative_debug_level, gethostnative_soft_restart,
lookup_bad_search_option,
getif, getif_ifr_name_overflow, getservbyname_overflow,
getifaddrs, parse_strict_address, simple_netns, simple_netns_open,
- simple_bind_to_device, simple_bind_to_device_open].
+ add_del_host, add_del_host_v6,
+ simple_bind_to_device, simple_bind_to_device_open
+ ].
groups() ->
- [{parse, [], [parse_hosts, parse_address]}].
+ [
+ {parse, [], parse_cases()}
+ ].
+
+parse_cases() ->
+ [
+ parse_hosts,
+ parse_address
+ ].
%% Required configuaration
required(v4) ->
@@ -83,11 +108,38 @@ required(hosts) ->
[{require, test_hosts}]
end.
-init_per_suite(Config) ->
- Config.
-end_per_suite(_Config) ->
- ok.
+init_per_suite(Config0) ->
+
+ ?P("init_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ case ?LIB:init_per_suite([{allow_skip, false} | Config0]) of
+ {skip, _} = SKIP ->
+ SKIP;
+
+ Config1 when is_list(Config1) ->
+
+ ?P("init_per_suite -> end when "
+ "~n Config: ~p", [Config1]),
+
+ Config1
+ end.
+
+end_per_suite(Config0) ->
+
+ ?P("end_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ Config1 = ?LIB:end_per_suite(Config0),
+
+ ?P("end_per_suite -> "
+ "~n Nodes: ~p", [erlang:nodes()]),
+
+ Config1.
+
init_per_group(_GroupName, Config) ->
Config.
@@ -95,6 +147,12 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
+init_per_testcase(gethostnative_debug_level, Config) ->
+ ?TT(?MINS(2)),
+ Config;
+init_per_testcase(gethostnative_soft_restart, Config) ->
+ ?TT(?MINS(2)),
+ Config;
init_per_testcase(lookup_bad_search_option, Config) ->
Db = inet_db,
Key = res_lookup,
@@ -103,7 +161,7 @@ init_per_testcase(lookup_bad_search_option, Config) ->
Prev = ets:lookup(Db, Key),
ets:delete(Db, Key),
ets:insert(Db, {Key,[lookup_bad_search_option]}),
- io:format("Misconfigured resolver lookup order", []),
+ ?P("init_per_testcase -> Misconfigured resolver lookup order"),
[{Key,Prev}|Config];
init_per_testcase(_Func, Config) ->
Config.
@@ -114,7 +172,7 @@ end_per_testcase(lookup_bad_search_option, Config) ->
Prev = proplists:get_value(Key, Config),
ets:delete(Db, Key),
ets:insert(Db, Prev),
- io:format("Restored resolver lookup order", []);
+ ?P("end_per_testcase -> Restored resolver lookup order");
end_per_testcase(_Func, _Config) ->
ok.
@@ -375,18 +433,21 @@ ipv4_to_ipv6(Config) when is_list(Config) ->
end,
ok.
-host_and_addr() ->
- [{timetrap,{minutes,5}}|required(hosts)].
%% Test looking up hosts and addresses. Use 'ypcat hosts'
%% or the local eqivalent to find all hosts.
+
+host_and_addr() ->
+ ?P("host_and_addr -> entry"),
+ [{timetrap,{minutes,5}}|required(hosts)].
+
host_and_addr(Config) when is_list(Config) ->
lists:foreach(fun try_host/1, get_hosts(Config)),
ok.
try_host({Ip0, Host}) ->
- {ok,Ip} = inet:getaddr(Ip0, inet),
- {ok,{hostent, _, _, inet, _, Ips1}} = inet:gethostbyaddr(Ip),
+ {ok, Ip} = inet:getaddr(Ip0, inet),
+ {ok,{hostent, _, _, inet, _, Ips1}} = inet:gethostbyaddr(Ip),
{ok,{hostent, _, _, inet, _, _Ips2}} = inet:gethostbyname(Host),
true = lists:member(Ip, Ips1),
ok.
@@ -698,6 +759,8 @@ t_gethostnative(Config) when is_list(Config) ->
{error,notfound} ->
ok;
{error,no_data} ->
+ ok;
+ {error,try_again} ->
ok
end.
@@ -868,41 +931,240 @@ missing_hosts_reload(Config) when is_list(Config) ->
% cleanup
true = test_server:stop_node(TestNode).
+
+%% The /etc/hosts file format and limitations is quite undocumented.
+%%
+%% Our implementation of the hosts file resolver tries to
+%% do the right thing. Here is an attempt to define "the right thing",
+%% and this test case tries to check most of these rules:
+%%
+%% * A hosts file consists of entries with one IP address,
+%% and a list of host names. The IP address is IPv4 or IPv6.
+%% The first host name is the primary host name
+%% and the others are aliases.
+%%
+%% * A lookup for an IP address should return one #hostent{} record
+%% with the one IP address from the query, and the host names
+%% from all entries with the same IP address concatenated.
+%% The first host name from the first hosts file entry
+%% with the requested IP address will be the primary host name
+%% and all others are aliases. All host names are returned
+%% as in the hosts file entries i.e character case is preserved.
+%%
+%% * A lookup for a host name is character case insensitive.
+%%
+%% * A lookup for a host name should return one #hostent{} record
+%% with the host name list from the first hosts file entry
+%% with an IP address of the requested address family
+%% that has a matching host name, as it is in the hosts file
+%% i.e character case is preserved. The IP addresses in the
+%% returned #hostent{} record should be the first from the
+%% same matching hosts file entry, followed by all others
+%% for which there is a matching host name and address family.
+%% There should be no duplicates among the returned IP addresses.
+%%
+%% * These rules are of the opinion that if duplicate host names
+%% with the same character casing occurs for the same IP
+%% address, it is a configuration error, so it is not tested for
+%% and there is no preferred behaviour.
+hosts_file_quirks(Config) when is_list(Config) ->
+ Records = [R1, R2, R3, R4, R5] =
+ [#hostent{
+ h_name = h_ex(Name),
+ h_aliases = [h_ex(Alias) || Alias <- Aliases],
+ h_addrtype = Fam,
+ h_length =
+ case Fam of
+ inet -> 4;
+ inet6 -> 16
+ end,
+ h_addr_list =
+ [case Fam of
+ inet -> inet_ex(N);
+ inet6 -> inet6_ex(N)
+ end]}
+ || {{Fam,N}, Name, Aliases} <-
+ [{{inet,1}, "a", ["B"]},
+ {{inet,2}, "D", []},
+ {{inet6,3}, "A", ["c"]},
+ {{inet,1}, "c", []},
+ {{inet,5}, "A", []}]],
+ true = R1#hostent.h_addr_list =:= R4#hostent.h_addr_list,
+ R14 =
+ R1#hostent{
+ h_aliases =
+ R1#hostent.h_aliases ++
+ [R4#hostent.h_name | R4#hostent.h_aliases]},
+ R145 =
+ R14#hostent{
+ h_addr_list =
+ R1#hostent.h_addr_list ++ R5#hostent.h_addr_list},
+ %%
+ RootDir = proplists:get_value(priv_dir,Config),
+ HostsFile = filename:join(RootDir, atom_to_list(?MODULE) ++ "-quirks.hosts"),
+ InetRc = filename:join(RootDir, "quirks.inetrc"),
+ ok = file:write_file(HostsFile, hostents_to_list(Records)),
+ ok = file:write_file(InetRc, "{hosts_file, \"" ++ HostsFile ++ "\"}.\n"),
+ %%
+ %% start a node
+ Pa = filename:dirname(code:which(?MODULE)),
+ {ok, TestNode} = test_server:start_node(?MODULE, slave,
+ [{args, "-pa " ++ Pa ++ " -kernel inetrc '\"" ++ InetRc ++ "\"'"}]),
+ %% ensure it has our RC
+ Rc = rpc:call(TestNode, inet_db, get_rc, []),
+ {hosts_file, HostsFile} = lists:keyfind(hosts_file, 1, Rc),
+ %%
+ %% check entries
+ io:format("Check hosts file contents~n", []),
+ V1 =
+ [{R14, inet_ex(1)},
+ {R2, inet_ex(2)},
+ {R3, inet6_ex(3)},
+ {R5, inet_ex(5)},
+ {R145, h_ex("a"), inet},
+ {R14, h_ex("b"), inet},
+ {R14, h_ex("c"), inet},
+ {R2, h_ex("d"), inet},
+ {R3, h_ex("a"), inet6},
+ {R3, h_ex("c"), inet6}
+ ],
+ hosts_file_quirks_verify(TestNode, V1),
+ %%
+ %% test add and del
+ ok =
+ rpc:call(
+ TestNode, inet_db, add_host,
+ [inet_ex(1), [h_ex("a"), h_ex("B")]]),
+ io:format("Check after add host~n", []),
+ hosts_file_quirks_verify(
+ TestNode,
+ [{R1, inet_ex(1)},
+ {R2, inet_ex(2)},
+ {R3, inet6_ex(3)},
+ {R5, inet_ex(5)},
+ {R1, h_ex("a"), inet},
+ {R1, h_ex("b"), inet},
+ {R14, h_ex("c"), inet},
+ {R2, h_ex("d"), inet},
+ {R3, h_ex("a"), inet6},
+ {R3, h_ex("c"), inet6}
+ ]),
+ ok = rpc:call(TestNode, inet_db, del_host, [inet_ex(1)]),
+ io:format("Check after del host~n", []),
+ hosts_file_quirks_verify(TestNode, V1),
+ %%
+ %% cleanup
+ true = test_server:stop_node(TestNode).
+
+hosts_file_quirks_verify(_TestNode, Vs) ->
+ hosts_file_quirks_verify(_TestNode, Vs, true).
+%%
+hosts_file_quirks_verify(_TestNode, [], Ok) ->
+ case Ok of
+ true -> ok;
+ false -> error(verify_failed)
+ end;
+hosts_file_quirks_verify(TestNode, [V | Vs], Ok) ->
+ case
+ case V of
+ {R, Addr} ->
+ {R, rpc:call(TestNode, inet_hosts, gethostbyaddr, [Addr])};
+ {R, Host, Fam} ->
+ {R, rpc:call(TestNode, inet_hosts, gethostbyname, [Host, Fam])}
+ end
+ of
+ {nxdomain, {error, nxdomain}} ->
+ hosts_file_quirks_verify(TestNode, Vs, Ok);
+ {_R_1, {error, nxdomain}} ->
+ io:format("Verify failed ~p: nxdomain~n", [V]),
+ hosts_file_quirks_verify(TestNode, Vs, false);
+ {R_1, {ok, R_1}} ->
+ hosts_file_quirks_verify(TestNode, Vs, Ok);
+ {_R_1, {ok, R_2}} ->
+ io:format("Verify failed ~p: ~p~n", [V, R_2]),
+ hosts_file_quirks_verify(TestNode, Vs, false)
+ end.
+
+%% Expand entry
+h_ex(H) -> H ++ ".example.com".
+inet_ex(N) -> {127,17,17,N}.
+inet6_ex(N) -> {0,0,0,0,17,17,17,N}.
+
+hostents_to_list([]) -> [];
+hostents_to_list([R | Rs]) ->
+ #hostent{
+ h_name = Name,
+ h_aliases = Aliases,
+ h_addr_list = [IP]} = R,
+ [inet:ntoa(IP), $\t,
+ lists:join($\s, [Name | Aliases]), $\r, $\n
+ | hostents_to_list(Rs)].
+
+
%% These must be run in the whole suite since they need
%% the host list and require inet_gethost_native to be started.
%%
-record(gethostnative_control, {control_seq,
- control_interval=100,
- lookup_delay=10,
- lookup_count=300,
- lookup_processes=20}).
+ control_interval = 100,
+ lookup_delay = 10,
+ lookup_count = 300,
+ lookup_processes = 20}).
gethostnative_soft_restart() -> required(hosts).
%% Check that no name lookups fails during soft restart
%% of inet_gethost_native.
gethostnative_soft_restart(Config) when is_list(Config) ->
- gethostnative_control(Config,
- #gethostnative_control{
- control_seq=[soft_restart]}).
-
+ Opts = gethostnative_adjusted_opts(Config,
+ [soft_restart]),
+ gethostnative_control(Config, Opts).
gethostnative_debug_level() -> required(hosts).
%% Check that no name lookups fails during debug level change
%% of inet_gethost_native.
gethostnative_debug_level(Config) when is_list(Config) ->
- gethostnative_control(Config,
- #gethostnative_control{
- control_seq=[{debug_level,1},
- {debug_level,0}]}).
-
-gethostnative_control(Config, Optrec) ->
+ Opts = gethostnative_adjusted_opts(Config,
+ [{debug_level, 1},
+ {debug_level, 0}]),
+ gethostnative_control(Config, Opts).
+
+gethostnative_adjusted_opts(Config, CtrlSeq) ->
+ Factor = ?config(kernel_factor, Config),
+ gethostnative_adjusted_opts2(Factor, CtrlSeq).
+
+gethostnative_adjusted_opts2(1, CtrlSeq) ->
+ #gethostnative_control{control_seq = CtrlSeq};
+gethostnative_adjusted_opts2(F, CtrlSeq) ->
+ Opts = #gethostnative_control{control_seq = CtrlSeq},
+ gethostnative_adjusted_opts3(F, Opts).
+
+gethostnative_adjusted_opts3(
+ F,
+ #gethostnative_control{lookup_count = Cnt,
+ lookup_processes = NProc} = Opts) ->
+ Adjust = fun(X) ->
+ if
+ F > 10 ->
+ X div 4;
+ F > 5 ->
+ X div 3;
+ F > 2 ->
+ (2 * X) div 3;
+ true ->
+ X
+ end
+ end,
+ Opts#gethostnative_control{lookup_count = Adjust(Cnt),
+ lookup_processes = Adjust(NProc)}.
+
+
+gethostnative_control(Config, Opts) ->
case inet_db:res_option(lookup) of
[native] ->
case whereis(inet_gethost_native) of
Pid when is_pid(Pid) ->
- gethostnative_control_1(Config, Optrec);
+ gethostnative_control_1(Config, Opts);
_ ->
{skipped, "Not running native gethostbyname"}
end;
@@ -910,54 +1172,63 @@ gethostnative_control(Config, Optrec) ->
{skipped, "Native not only lookup metod"}
end.
-gethostnative_control_1(Config,
- #gethostnative_control{
- control_seq=Seq,
- control_interval=Interval,
- lookup_delay=Delay,
- lookup_count=Cnt,
- lookup_processes=N}) ->
+gethostnative_control_1(
+ Config,
+ #gethostnative_control{control_seq = Seq,
+ control_interval = Interval,
+ lookup_delay = Delay,
+ lookup_count = Cnt,
+ lookup_processes = N}) ->
+ ?P("gethostnative control -> begin with"
+ "~n Ctrl Seq: ~p"
+ "~n Ctrl interval: ~p"
+ "~n Lookup delay: ~p"
+ "~n Lookup count: ~p"
+ "~n Lookup procs: ~p", [Seq, Interval, Delay, Cnt, N]),
{ok, Hostname} = inet:gethostname(),
- {ok, _} = inet:gethostbyname(Hostname),
- Hosts =
- [Hostname|[H || {_,H} <- get_hosts(Config)]
- ++[H++D || H <- ["www.","www1.","www2.",""],
- D <- ["erlang.org","erlang.se"]]
- ++[H++"cslab.ericsson.net" || H <- ["morgoth.","hades.","styx."]]],
+ {ok, _} = inet:gethostbyname(Hostname),
+ Hosts =
+ [Hostname|[H || {_,H} <- get_hosts(Config)]
+ ++[H++D || H <- ["www.","www1.","www2.",""],
+ D <- ["erlang.org","erlang.se"]]
+ ++[H++"cslab.ericsson.net" || H <- ["morgoth.","hades.","styx."]]],
+ ?P("ctrl-1: Hosts: "
+ "~n ~p", [Hosts]),
%% Spawn some processes to do parallel lookups while
%% I repeatedly do inet_gethost_native:control/1.
TrapExit = process_flag(trap_exit, true),
gethostnative_control_2([undefined], Interval, Delay, Cnt, N, Hosts),
- io:format(
- "First intermission: now starting control sequence ~w\n",
- [Seq]),
erlang:display(first_intermission),
+ ?P("ctrl-1: First intermission: "
+ "~n Now starting control sequence ~p", [Seq]),
gethostnative_control_2(Seq, Interval, Delay, Cnt, N, Hosts),
erlang:display(second_intermission),
- io:format(
- "Second intermission: now stopping control sequence ~w\n",
- [Seq]),
+ ?P("ctrl-1: Second intermission: "
+ "~n Now stopping control sequence ~p", [Seq]),
gethostnative_control_2([undefined], Interval, Delay, Cnt, N, Hosts),
true = process_flag(trap_exit, TrapExit),
+ ?P("control-1: done"),
ok.
gethostnative_control_2(Seq, Interval, Delay, Cnt, N, Hosts) ->
- Tag = make_ref(),
- Parent = self(),
- Lookupers =
- [spawn_link(
- fun () ->
- lookup_loop(Hosts, Delay, Tag, Parent, Cnt, Hosts)
- end)
- || _ <- lists:seq(1, N)],
+ Tag = make_ref(),
+ Parent = self(),
+ Lookup = fun() ->
+ ?P("lookuper starting"),
+ lookup_loop(Hosts, Delay, Tag, Parent, Cnt, Hosts)
+ end,
+ Lookupers = [spawn_link(Lookup) || _ <- lists:seq(1, N)],
control_loop(Seq, Interval, Tag, Lookupers, Seq),
gethostnative_control_3(Tag, ok).
gethostnative_control_3(Tag, Reason) ->
receive
- {Tag,Error} ->
+ {Tag, Lookuper, Error} ->
+ ?P("control-3: received lookup error from ~p: ~p",
+ [Lookuper, Error]),
gethostnative_control_3(Tag, Error)
after 0 ->
+ ?P("control-3: no (more) lookup errors"),
Reason
end.
@@ -971,19 +1242,41 @@ control_loop([Op|Ops], Interval, Tag, Lookupers, Seq) ->
Seq).
control_loop_1(Op, Interval, Tag, Lookupers) ->
+ ?P("ctrl-loop-1: await lookuper exit"),
receive
- {'EXIT',Pid,Reason} ->
+ {'EXIT', _Pid, {timetrap_timeout, _, _}} ->
+ ?P("ctrl-loop-1: "
+ "timetrap timeout while ~w lookupers remaining: "
+ "~n Op: ~p"
+ "~n Interval: ~p"
+ "~n Tag: ~p",
+ [length(Lookupers), Op, Interval, Tag]),
+ if
+ (Op =/= undefined) ->
+ exit({timetrap, {Op, Tag, length(Lookupers)}});
+ true ->
+ exit({timetrap, {Tag, length(Lookupers)}})
+ end;
+
+ {'EXIT', Pid, Reason} ->
case Reason of
Tag -> % Done
+ ?P("ctrl-loop-1: "
+ "received expected exit from lookuper ~p", [Pid]),
control_loop_1
(Op, Interval, Tag,
lists:delete(Pid, Lookupers));
_ ->
- io:format("Lookuper ~p died: ~p",
- [Pid,Reason]),
- ct:fail("Lookuper died")
+ ?P("ctrl-loop-1: "
+ "received unexpected exit from ~p: "
+ "~n ~p", [Pid, Reason]),
+ ct:fail(?F("Unexpected exit from ~p", [Pid]))
end
+
after Interval ->
+ ?P("ctrl-loop-1: "
+ "timeout => (maybe) attempt control ~p when"
+ "~n Lookupers: ~p", [Op, Lookupers]),
if Op =/= undefined ->
ok = inet_gethost_native:control(Op);
true ->
@@ -993,19 +1286,25 @@ control_loop_1(Op, Interval, Tag, Lookupers) ->
end.
lookup_loop(_, _Delay, Tag, _Parent, 0, _Hosts) ->
+ ?P("lookup-loop: done with ~p", [Tag]),
exit(Tag);
lookup_loop([], Delay, Tag, Parent, Cnt, Hosts) ->
+ ?P("lookup-loop: begin lookup (~p) again when count = ~p", [Tag, Cnt]),
lookup_loop(Hosts, Delay, Tag, Parent, Cnt, Hosts);
lookup_loop([H|Hs], Delay, Tag, Parent, Cnt, Hosts) ->
+ ?P("lookup-loop: try lookup (~p, ~p) ~p", [Tag, Cnt, H]),
case inet:gethostbyname(H) of
- {ok,_Hent} -> ok;
- {error,nxdomain} -> ok;
+ {ok, _Hent} ->
+ ?P("lookup-loop: lookup => found"),
+ ok;
+ {error, nxdomain} ->
+ ?P("lookup-loop: lookup => nxdomain"),
+ ok;
Error ->
- io:format("Name lookup error for ~p for ~p: ~p",
- [self(),H,Error]),
- Parent ! {Tag,Error}
+ ?P("loopkup-loop: lookup => error for ~p: ~p", [H, Error]),
+ Parent ! {Tag, self(), Error}
end,
- receive
+ receive
after rand:uniform(Delay) ->
lookup_loop(Hs, Delay, Tag, Parent, Cnt-1, Hosts)
end.
@@ -1413,3 +1712,46 @@ jog_bind_to_device_opt(S) ->
ok = inet:setopts(S, [{bind_to_device,<<"lo">>}]),
{ok,[{bind_to_device,<<"lo">>}]} = inet:getopts(S, [bind_to_device]),
ok.
+
+add_del_host(_Config) ->
+ Name = "foo.com",
+ Alias = "bar.org",
+ Ip = {69,89,31,226},
+ HostEnt = #hostent{
+ h_name = Name,
+ h_aliases = [Alias],
+ h_addrtype = inet,
+ h_length = 4,
+ h_addr_list = [Ip]
+ },
+ {error, nxdomain} = inet_hosts:gethostbyname(Name, inet),
+ ok = inet_db:add_host(Ip, [Name, Alias]),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Name, inet),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Alias, inet),
+ ok = inet_db:del_host(Ip),
+ {error, nxdomain} = inet_hosts:gethostbyname(Name, inet),
+ {error, nxdomain} = inet_hosts:gethostbyname(Alias, inet),
+ ok = inet_db:add_host(Ip, [Name, Alias]),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Name, inet).
+
+add_del_host_v6(_Config) ->
+ Name = "foo.com",
+ Alias = "bar.org",
+ Ip = {32,1,219,8,10,11,18,240},
+ HostEnt = #hostent{
+ h_name = Name,
+ h_aliases = [Alias],
+ h_addrtype = inet6,
+ h_length = 16,
+ h_addr_list = [Ip]
+ },
+ {error, nxdomain} = inet_hosts:gethostbyname(Name, inet6),
+ ok = inet_db:add_host(Ip, [Name, Alias]),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Name, inet6),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Alias, inet6),
+ ok = inet_db:del_host(Ip),
+ {error, nxdomain} = inet_hosts:gethostbyname(Name, inet6),
+ {error, nxdomain} = inet_hosts:gethostbyname(Alias, inet6),
+ ok = inet_db:add_host(Ip, [Name, Alias]),
+ {ok, HostEnt} = inet_hosts:gethostbyname(Name, inet6).
+
diff --git a/lib/kernel/test/inet_res_SUITE.erl b/lib/kernel/test/inet_res_SUITE.erl
index 8b3f1aa2a9..54686c326a 100644
--- a/lib/kernel/test/inet_res_SUITE.erl
+++ b/lib/kernel/test/inet_res_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -24,11 +24,18 @@
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/src/inet_dns.hrl").
+-include("kernel_test_lib.hrl").
+
+
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
- init_per_testcase/2, end_per_testcase/2]).
+ init_per_testcase/2, end_per_testcase/2
+ ]).
-export([basic/1, resolve/1, edns0/1, txt_record/1, files_monitor/1,
- last_ms_answer/1, intermediate_error/1]).
+ last_ms_answer/1, intermediate_error/1,
+ servfail_retry_timeout_default/1, servfail_retry_timeout_1000/1,
+ label_compression_limit/1
+ ]).
-export([
gethostbyaddr/0, gethostbyaddr/1,
gethostbyaddr_v6/0, gethostbyaddr_v6/1,
@@ -64,7 +71,10 @@ suite() ->
all() ->
[basic, resolve, edns0, txt_record, files_monitor,
- last_ms_answer, intermediate_error,
+ last_ms_answer,
+ intermediate_error,
+ servfail_retry_timeout_default, servfail_retry_timeout_1000,
+ label_compression_limit,
gethostbyaddr, gethostbyaddr_v6, gethostbyname,
gethostbyname_v6, getaddr, getaddr_v6, ipv4_to_ipv6,
host_and_addr].
@@ -72,11 +82,37 @@ all() ->
groups() ->
[].
-init_per_suite(Config) ->
- Config.
+init_per_suite(Config0) ->
+
+ ?P("init_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ case ?LIB:init_per_suite(Config0) of
+ {skip, _} = SKIP ->
+ SKIP;
+
+ Config1 when is_list(Config1) ->
+
+ ?P("init_per_suite -> end when "
+ "~n Config: ~p", [Config1]),
+
+ Config1
+ end.
+
+end_per_suite(Config0) ->
+
+ ?P("end_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n Nodes: ~p", [Config0, erlang:nodes()]),
+
+ Config1 = ?LIB:end_per_suite(Config0),
+
+ ?P("end_per_suite -> "
+ "~n Nodes: ~p", [erlang:nodes()]),
+
+ Config1. %% We don't actually need to update or return config
-end_per_suite(_Config) ->
- ok.
init_per_group(_GroupName, Config) ->
Config.
@@ -86,36 +122,56 @@ end_per_group(_GroupName, Config) ->
zone_dir(TC) ->
case TC of
- basic -> otptest;
- resolve -> otptest;
- edns0 -> otptest;
- files_monitor -> otptest;
- last_ms_answer -> otptest;
+ basic -> otptest;
+ resolve -> otptest;
+ edns0 -> otptest;
+ files_monitor -> otptest;
+ last_ms_answer -> otptest;
intermediate_error ->
{internal,
#{rcode => ?REFUSED}};
+ servfail_retry_timeout_default ->
+ {internal,
+ #{rcode => ?SERVFAIL, etd => 1500}};
+ servfail_retry_timeout_1000 ->
+ {internal,
+ #{rcode => ?SERVFAIL, etd => 1000}};
_ -> undefined
end.
init_per_testcase(Func, Config) ->
+
+ ?P("init_per_testcase -> entry with"
+ "~n Func: ~p"
+ "~n Config: ~p", [Func, Config]),
+
PrivDir = proplists:get_value(priv_dir, Config),
DataDir = proplists:get_value(data_dir, Config),
try ns_init(zone_dir(Func), PrivDir, DataDir) of
NsSpec ->
+ ?P("init_per_testcase -> get resolver lookup"),
Lookup = inet_db:res_option(lookup),
+ ?P("init_per_testcase -> set file:dns"),
inet_db:set_lookup([file,dns]),
case NsSpec of
{_,{IP,Port},_} ->
+ ?P("init_per_testcase -> insert alt nameserver ~p:~w",
+ [IP, Port]),
inet_db:ins_alt_ns(IP, Port);
_ -> ok
end,
%% dbg:tracer(),
%% dbg:p(all, c),
%% dbg:tpl(inet_res, query_nss_res, cx),
- [{nameserver,NsSpec},{res_lookup,Lookup}|Config]
+ ?P("init_per_testcase -> done:"
+ "~n NsSpec: ~p"
+ "~n Lookup: ~p", [NsSpec, Lookup]),
+ [{nameserver, NsSpec}, {res_lookup, Lookup} | Config]
catch
SkipReason ->
- {skip,SkipReason}
+ ?P("init_per_testcase -> catched:"
+ "~n SkipReason: ~p", [SkipReason]),
+ {skip, SkipReason}
end.
end_per_testcase(_Func, Config) ->
@@ -129,6 +185,7 @@ end_per_testcase(_Func, Config) ->
%% dbg:stop(),
ns_end(NsSpec, proplists:get_value(priv_dir, Config)).
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Nameserver control
@@ -137,12 +194,20 @@ ns(Config) ->
NS.
ns_init(ZoneDir, PrivDir, DataDir) ->
+
+ ?P("ns_init -> entry with"
+ "~n ZoneDir: ~p"
+ "~n PrivDir: ~p"
+ "~n DataDir: ~p", [ZoneDir, PrivDir, DataDir]),
+
case {os:type(),ZoneDir} of
{_,{internal,ServerSpec}} ->
ns_start_internal(ServerSpec);
{{unix,_},undefined} ->
+ ?P("ns_init -> nothing"),
undefined;
{{unix,_},otptest} ->
+ ?P("ns_init -> prepare start"),
PortNum = case {os:type(),os:version()} of
{{unix,solaris},{M,V,_}} when M =< 5, V < 10 ->
11895 + rand:uniform(100);
@@ -152,8 +217,11 @@ ns_init(ZoneDir, PrivDir, DataDir) ->
gen_udp:close(S),
PNum
end,
+ ?P("ns_init -> use port number ~p", [PortNum]),
RunNamed = filename:join(DataDir, ?RUN_NAMED),
+ ?P("ns_init -> use named ~p", [RunNamed]),
NS = {{127,0,0,1},PortNum},
+ ?P("ns_init -> try open port (exec)"),
P = erlang:open_port({spawn_executable,RunNamed},
[{cd,PrivDir},
{line,80},
@@ -162,25 +230,39 @@ ns_init(ZoneDir, PrivDir, DataDir) ->
atom_to_list(ZoneDir)]},
stderr_to_stdout,
eof]),
+ ?P("ns_init -> port ~p", [P]),
ns_start(ZoneDir, PrivDir, NS, P);
_ ->
throw("Only run on Unix")
end.
ns_start(ZoneDir, PrivDir, NS, P) ->
+
+ ?P("ns_start -> await message"),
+
case ns_collect(P) of
eof ->
+ ?P("ns_start -> eof"),
erlang:error(eof);
"Running: "++_ ->
+ ?P("ns_start -> running"),
{ZoneDir,NS,P};
"Error: "++Error ->
+ ?P("ns_start -> error: "
+ "~n ~p", [Error]),
ns_printlog(filename:join([PrivDir,ZoneDir,"named.log"])),
throw(Error);
- _ ->
+ _X ->
+ ?P("ns_start -> retry"),
ns_start(ZoneDir, PrivDir, NS, P)
end.
+
ns_start_internal(ServerSpec) ->
+
+ ?P("ns_start_internal -> entry with"
+ "~n ServerSpec: ~p", [ServerSpec]),
+
Parent = self(),
Tag = make_ref(),
{P,Mref} =
@@ -197,9 +279,12 @@ ns_start_internal(ServerSpec) ->
end),
receive
{Tag,_NS,P} = NsSpec ->
+ ?P("ns_start_internal -> ~p started", [P]),
demonitor(Mref, [flush]),
NsSpec;
{'DOWN',Mref,_,_,Reason} ->
+ ?P("ns_start_internal -> failed start:"
+ "~n ~p", [Reason]),
exit({ns_start_internal,Reason})
end.
@@ -232,7 +317,7 @@ ns_collect(P, Buf) ->
receive
{P,{data,{eol,L}}} ->
Line = lists:flatten(lists:reverse(Buf, [L])),
- io:format("~s", [Line]),
+ ?P("collected: ~s", [Line]),
Line;
{P,{data,{noeol,L}}} ->
ns_collect(P, [L|Buf]);
@@ -241,7 +326,7 @@ ns_collect(P, Buf) ->
end.
ns_printlog(Fname) ->
- io:format("Name server log file contents:~n", []),
+ ?P("Name server log file contents:"),
case file:read_file(Fname) of
{ok,Bin} ->
io:format("~s~n", [Bin]);
@@ -253,39 +338,81 @@ ns_printlog(Fname) ->
%% Internal name server
ns_internal(ServerSpec, Mref, Tag, S) ->
+ ?P("ns-internal -> await message"),
receive
{'DOWN',Mref,_,_,Reason} ->
+ ?P("ns-internal -> received DOWN: "
+ "~n ~p", [Reason]),
exit(Reason);
Tag ->
+ ?P("ns-internal -> received tag: done"),
ok;
{udp,S,IP,Port,Data} ->
+ ?P("ns-internal -> received UDP message"),
Req = ok(inet_dns:decode(Data)),
- Resp = ns_internal(ServerSpec, Req),
+ {Resp, ServerSpec2} = ns_internal(ServerSpec, Req),
RespData = inet_dns:encode(Resp),
_ = ok(gen_udp:send(S, IP, Port, RespData)),
_ = ok(inet:setopts(S, [{active,once}])),
- ns_internal(ServerSpec, Mref, Tag, S)
+ ns_internal(ServerSpec2, Mref, Tag, S)
end.
-ns_internal(#{rcode := Rcode}, Req) ->
- Hdr = inet_dns:msg(Req, header),
+ns_internal(#{rcode := Rcode,
+ ts := TS0,
+ etd := ETD} = ServerSpec, Req) ->
+ ?P("ns-internal -> request received (time validation)"),
+ TS1 = timestamp(),
+ Hdr = inet_dns:msg(Req, header),
Opcode = inet_dns:header(Hdr, opcode),
- Id = inet_dns:header(Hdr, id),
- Rd = inet_dns:header(Hdr, rd),
+ Id = inet_dns:header(Hdr, id),
+ Rd = inet_dns:header(Hdr, rd),
%%
Qdlist = inet_dns:msg(Req, qdlist),
- inet_dns:make_msg(
- [{header,
- inet_dns:make_header(
- [{id,Id},
- {qr,true},
- {opcode,Opcode},
- {aa,true},
- {tc,false},
- {rd,Rd},
- {ra,false},
- {rcode,Rcode}])},
- {qdlist,Qdlist}]).
+ ?P("ns-internal -> time validation: "
+ "~n ETD: ~w"
+ "~n TS1 - TS0: ~w", [ETD, TS1 - TS0]),
+ RC = if ((TS1 - TS0) >= ETD) ->
+ ?P("ns-internal -> time validated"),
+ ?NOERROR;
+ true ->
+ ?P("ns-internal -> time validation failed"),
+ Rcode
+ end,
+ Resp = inet_dns:make_msg(
+ [{header,
+ inet_dns:make_header(
+ [{id, Id},
+ {qr, true},
+ {opcode, Opcode},
+ {aa, true},
+ {tc, false},
+ {rd, Rd},
+ {ra, false},
+ {rcode, RC}])},
+ {qdlist, Qdlist}]),
+ {Resp, ServerSpec#{ts => timestamp()}};
+ns_internal(#{rcode := Rcode} = ServerSpec, Req) ->
+ ?P("ns-internal -> request received"),
+ Hdr = inet_dns:msg(Req, header),
+ Opcode = inet_dns:header(Hdr, opcode),
+ Id = inet_dns:header(Hdr, id),
+ Rd = inet_dns:header(Hdr, rd),
+ %%
+ Qdlist = inet_dns:msg(Req, qdlist),
+ Resp = inet_dns:make_msg(
+ [{header,
+ inet_dns:make_header(
+ [{id,Id},
+ {qr,true},
+ {opcode,Opcode},
+ {aa,true},
+ {tc,false},
+ {rd,Rd},
+ {ra,false},
+ {rcode,Rcode}])},
+ {qdlist,Qdlist}]),
+ {Resp, ServerSpec#{ts => timestamp()}}.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Behaviour modifying nameserver proxy
@@ -297,10 +424,13 @@ proxy_start(TC, {NS,P}) ->
spawn_link(
fun () ->
try proxy_start(TC, NS, P, Parent, Tag)
- catch C:X:Stacktrace ->
- io:format(
- "~w: ~w:~p ~p~n",
- [self(),C,X,Stacktrace])
+ catch
+ C:X:Stacktrace ->
+ ?P("~p Failed starting proxy: "
+ "~n Class: ~w"
+ "~n Error: ~p"
+ "~n Stacktrace: ~p",
+ [self(), C, X, Stacktrace])
end
end),
receive {started,Tag,Port} ->
@@ -369,11 +499,13 @@ proxy_wait({proxy,Pid,_,_}) ->
proxy_ns({proxy,_,_,ProxyNS}) -> ProxyNS.
+
%%
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Lookup an A record with different API functions.
basic(Config) when is_list(Config) ->
+ ?P("begin"),
NS = ns(Config),
Name = "ns.otptest",
NameC = caseflip(Name),
@@ -381,7 +513,7 @@ basic(Config) when is_list(Config) ->
%%
%% nslookup
{ok,Msg1} = inet_res:nslookup(Name, in, a, [NS]),
- io:format("~p~n", [Msg1]),
+ ?P("nslookup with ~p: ~n ~p", [Name, Msg1]),
[RR1] = inet_dns:msg(Msg1, anlist),
IP = inet_dns:rr(RR1, data),
Bin1 = inet_dns:encode(Msg1),
@@ -389,7 +521,7 @@ basic(Config) when is_list(Config) ->
{ok,Msg1} = inet_dns:decode(Bin1),
%% Now with scrambled case
{ok,Msg1b} = inet_res:nslookup(NameC, in, a, [NS]),
- io:format("~p~n", [Msg1b]),
+ ?P("nslookup with ~p: ~n ~p", [NameC, Msg1b]),
[RR1b] = inet_dns:msg(Msg1b, anlist),
IP = inet_dns:rr(RR1b, data),
Bin1b = inet_dns:encode(Msg1b),
@@ -401,7 +533,7 @@ basic(Config) when is_list(Config) ->
%%
%% resolve
{ok,Msg2} = inet_res:resolve(Name, in, a, [{nameservers,[NS]},verbose]),
- io:format("~p~n", [Msg2]),
+ ?P("resolve with ~p: ~n ~p", [Name, Msg2]),
[RR2] = inet_dns:msg(Msg2, anlist),
IP = inet_dns:rr(RR2, data),
Bin2 = inet_dns:encode(Msg2),
@@ -409,7 +541,7 @@ basic(Config) when is_list(Config) ->
{ok,Msg2} = inet_dns:decode(Bin2),
%% Now with scrambled case
{ok,Msg2b} = inet_res:resolve(NameC, in, a, [{nameservers,[NS]},verbose]),
- io:format("~p~n", [Msg2b]),
+ ?P("resolve with ~p: ~n ~p", [NameC, Msg2b]),
[RR2b] = inet_dns:msg(Msg2b, anlist),
IP = inet_dns:rr(RR2b, data),
Bin2b = inet_dns:encode(Msg2b),
@@ -420,22 +552,28 @@ basic(Config) when is_list(Config) ->
=:= tolower(inet_dns:rr(RR2b, domain))),
%%
%% lookup
+ ?P("lookup"),
[IP] = inet_res:lookup(Name, in, a, [{nameservers,[NS]},verbose]),
[IP] = inet_res:lookup(NameC, in, a, [{nameservers,[NS]},verbose]),
%%
%% gethostbyname
+ ?P("gethostbyname"),
{ok,#hostent{h_addr_list=[IP]}} = inet_res:gethostbyname(Name),
{ok,#hostent{h_addr_list=[IP]}} = inet_res:gethostbyname(NameC),
%%
%% getbyname
+ ?P("getbyname"),
{ok,#hostent{h_addr_list=[IP]}} = inet_res:getbyname(Name, a),
{ok,#hostent{h_addr_list=[IP]}} = inet_res:getbyname(NameC, a),
+ ?P("end"),
ok.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Lookup different records using resolve/2..4.
resolve(Config) when is_list(Config) ->
+ ?P("begin"),
Class = in,
NS = ns(Config),
Domain = "otptest",
@@ -474,16 +612,25 @@ resolve(Config) when is_list(Config) ->
{hinfo,{"BEAM","Erlang/OTP"}}],
undefined}
],
+ ?P("resolve -> with edns 0"),
resolve(Class, [{edns,0},{nameservers,[NS]}], L),
+ ?P("resolve -> with edns false"),
resolve(Class, [{edns,false},{nameservers,[NS]}], L),
%% Again, to see ensure the cache does not mess things up
+ ?P("resolve -> with edns 0 (again)"),
resolve(Class, [{edns,0},{nameservers,[NS]}], L),
- resolve(Class, [{edns,false},{nameservers,[NS]}], L).
+ ?P("resolve -> with edns false (again)"),
+ Res = resolve(Class, [{edns,false},{nameservers,[NS]}], L),
+ ?P("resolve -> done: ~p", [Res]),
+ Res.
resolve(_Class, _Opts, []) ->
+ ?P("resolve -> done"),
ok;
resolve(Class, Opts, [{Type,Nm,Answers,Authority}=Q|Qs]) ->
- io:format("Query: ~p~nOptions: ~p~n", [Q,Opts]),
+ ?P("resolve ->"
+ "~n Query: ~p"
+ "~n Options: ~p", [Q, Opts]),
{Name,NameC} =
case erlang:phash2(Q) band 4 of
0 ->
@@ -505,10 +652,13 @@ resolve(Class, Opts, [{Type,Nm,Answers,Authority}=Q|Qs]) ->
true ->
undefined
end,
+ ?P("resolve -> resolve with ~p", [Name]),
{ok,Msg} = inet_res:resolve(Name, Class, Type, Opts),
check_msg(Class, Type, Msg, AnList, NsList),
+ ?P("resolve -> resolve with ~p", [NameC]),
{ok,MsgC} = inet_res:resolve(NameC, Class, Type, Opts),
check_msg(Class, Type, MsgC, AnList, NsList),
+ ?P("resolve -> next"),
resolve(Class, Opts, Qs).
@@ -534,7 +684,9 @@ normalize_answer(Answer) ->
Answer.
check_msg(Class, Type, Msg, AnList, NsList) ->
- io:format("check_msg Type: ~p, Msg: ~p~n.", [Type,Msg]),
+ ?P("check_msg ->"
+ "~n Type: ~p"
+ "~n Msg: ~p", [Type,Msg]),
case {normalize_answers(
[begin
Class = inet_dns:rr(RR, class),
@@ -562,10 +714,12 @@ check_msg(Class, Type, Msg, AnList, NsList) ->
{ok,Msg} = inet_dns:decode(Buf),
ok.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test EDNS and truncation.
edns0(Config) when is_list(Config) ->
+ ?P("begin"),
NS = ns(Config),
Domain = "otptest",
Filler = "-5678901234567890123456789012345678.",
@@ -600,52 +754,66 @@ edns0(Config) when is_list(Config) ->
MXs = lists:sort(inet_res_filter(inet_dns:msg(Msg2, anlist), in, mx)),
Buf2 = inet_dns:encode(Msg2),
{ok,Msg2} = inet_dns:decode(Buf2),
- case [RR || RR <- inet_dns:msg(Msg2, arlist),
- inet_dns:rr(RR, type) =:= opt] of
- [OptRR] ->
- io:format("~p~n", [inet_dns:rr(OptRR)]),
- ok;
- [] ->
- case os:type() of
- {unix,sunos} ->
- case os:version() of
- {M,V,_} when M < 5; M == 5, V =< 8 ->
- %% In our test park only known platform
- %% with an DNS resolver that cannot do
- %% EDNS0.
- {comment,"No EDNS0"}
- end
- end
- end.
+ Res = case [RR || RR <- inet_dns:msg(Msg2, arlist),
+ inet_dns:rr(RR, type) =:= opt] of
+ [OptRR] ->
+ ?P("opt rr:"
+ "~n ~p", [inet_dns:rr(OptRR)]),
+ ok;
+ [] ->
+ case os:type() of
+ {unix,sunos} ->
+ case os:version() of
+ {M,V,_} when M < 5; M == 5, V =< 8 ->
+ %% In our test park only known platform
+ %% with an DNS resolver that cannot do
+ %% EDNS0.
+ {comment,"No EDNS0"}
+ end;
+ _ ->
+ ok
+ end
+ end,
+ ?P("done"),
+ Res.
inet_res_filter(Anlist, Class, Type) ->
[inet_dns:rr(RR, data) || RR <- Anlist,
inet_dns:rr(RR, type) =:= Type,
inet_dns:rr(RR, class) =:= Class].
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Tests TXT records.
txt_record(Config) when is_list(Config) ->
+ ?P("begin"),
D1 = "cslab.ericsson.net",
D2 = "mail1.cslab.ericsson.net",
+ ?P("try nslookup of ~p", [D1]),
{ok,#dns_rec{anlist=[RR1]}} =
inet_res:nslookup(D1, in, txt),
- io:format("~p~n", [RR1]),
+ ?P("RR1:"
+ "~n ~p", [RR1]),
+ ?P("try nslookup of ~p", [D2]),
{ok,#dns_rec{anlist=[RR2]}} =
inet_res:nslookup(D2, in, txt),
- io:format("~p~n", [RR2]),
+ ?P("RR2:"
+ "~n ~p", [RR2]),
#dns_rr{domain=D1, class=in, type=txt, data=A1} = RR1,
#dns_rr{domain=D2, class=in, type=txt, data=A2} = RR2,
case [lists:flatten(A2)] of
A1 = [[_|_]] -> ok
end,
+ ?P("done"),
ok.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Tests monitoring of /etc/hosts and /etc/resolv.conf, but not them.
files_monitor(Config) when is_list(Config) ->
+ ?P("begin"),
Search = inet_db:res_option(search),
HostsFile = inet_db:res_option(hosts_file),
ResolvConf = inet_db:res_option(resolv_conf),
@@ -656,12 +824,14 @@ files_monitor(Config) when is_list(Config) ->
inet_db:res_option(resolv_conf, ResolvConf),
inet_db:res_option(hosts_file, HostsFile),
inet_db:res_option(inet6, Inet6)
- end.
+ end,
+ ?P("done"),
+ ok.
do_files_monitor(Config) ->
Dir = proplists:get_value(priv_dir, Config),
{ok,Hostname} = inet:gethostname(),
- io:format("Hostname = ~p.~n", [Hostname]),
+ ?P("Hostname: ~p", [Hostname]),
FQDN =
case inet_db:res_option(domain) of
"" ->
@@ -669,7 +839,7 @@ do_files_monitor(Config) ->
_ ->
Hostname++"."++inet_db:res_option(domain)
end,
- io:format("FQDN = ~p.~n", [FQDN]),
+ ?P("FQDN: ~p", [FQDN]),
HostsFile = filename:join(Dir, "files_monitor_hosts"),
ResolvConf = filename:join(Dir, "files_monitor_resolv.conf"),
ok = inet_db:res_option(resolv_conf, ResolvConf),
@@ -750,23 +920,138 @@ last_ms_answer(Config) when is_list(Config) ->
proxy_wait(PSpec),
ok.
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% First name server answers ?REFUSED, second does not answer.
%% Check that we get the error code from the first server.
intermediate_error(Config) when is_list(Config) ->
- NS = ns(Config),
- Name = "ns.otptest",
- IP = {127,0,0,1},
+ NS = ns(Config),
+ Name = "ns.otptest",
+ Class = in,
+ Type = a,
+ IP = {127,0,0,1},
%% A "name server" that does not respond
- S = ok(gen_udp:open(0, [{ip,IP},{active,false}])),
- Port = ok(inet:port(S)),
- NSs = [NS,{IP,Port}],
- {error,{refused,_}} =
- inet_res:resolve(Name, in, a, [{nameservers,NSs},verbose], 500),
+ S = ok(gen_udp:open(0, [{ip,IP},{active,false}])),
+ Port = ok(inet:port(S)),
+ NSs = [NS,{IP,Port}],
+ Opts = [{nameservers, NSs}, verbose],
+ Timeout = 500,
+ {error, {refused,_}} = inet_res:resolve(Name, Class, Type, Opts, Timeout),
_ = gen_udp:close(S),
ok.
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% A name server that firstanswers ?SERVFAIL, the second try *if* the retry
+%% is not received *too soon* (etd) answers noerror.
+
+servfail_retry_timeout_default(Config) when is_list(Config) ->
+ NS = ns(Config),
+ Name = "ns.otptest",
+ Class = in,
+ Type = a,
+ Opts = [{nameservers,[NS]}, verbose],
+ ?P("try resolve"),
+ {ok, Rec} = inet_res:resolve(Name, Class, Type, Opts),
+ ?P("resolved: "
+ "~n ~p", [Rec]),
+ ok.
+
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% A name server that firstanswers ?SERVFAIL, the second try *if* the retry
+%% is not received *too soon* (etd) answers noerror.
+
+servfail_retry_timeout_1000(Config) when is_list(Config) ->
+ NS = ns(Config),
+ Name = "ns.otptest",
+ Class = in,
+ Type = a,
+ Opts = [{nameservers,[NS]}, {servfail_retry_timeout, 1000}, verbose],
+ ?P("try resolve"),
+ {ok, Rec} = inet_res:resolve(Name, Class, Type, Opts),
+ ?P("resolved: "
+ "~n ~p", [Rec]),
+ ok.
+
+
+%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Test that label encoding compression limits at 14 bits pointer size
+
+label_compression_limit(Config) when is_list(Config) ->
+ FirstSz = 8,
+ Count = 512,
+ Sz = 20,
+ %% We create a DNS message with an answer list containing
+ %% 1+512+1 RR:s. The first label is 8 chars that with message
+ %% and RR overhead places the second label on offset 32.
+ %% All other labels are 20 chars that with RR overhead
+ %% places them on offsets of N * 32.
+ %%
+ %% The labels are: "ZZZZZZZZ", then; "AAAAAAAAAAAAAAAAAAAA",
+ %% "AAAAAAAAAAAAAAAAAAAB", incrementing, so no one is
+ %% equal and can not be compressed, until the last one
+ %% that refers to the second to last one, so it could be compressed.
+ %%
+ %% However, the second to last label lands on offset 512 * 32 = 16384
+ %% which is out of reach for compression since compression uses
+ %% a 14 bit reference from the start of the message.
+ %%
+ %% The last label can only be compressed when we instead
+ %% generate a message with one less char in the first label,
+ %% placing the second to last label on offset 16383.
+ %%
+ %% So, MsgShort can use compression for the last RR
+ %% by referring to the second to last RR, but MsgLong can not.
+ %%
+ %% Disclaimer:
+ %% All offsets and overheads are deduced
+ %% through trial and observation
+ %%
+ [D | Domains] = gen_domains(Count, lists:duplicate(Sz, $A), []),
+ LastD = "Y." ++ D,
+ DomainsShort =
+ [lists:duplicate(FirstSz-1, $Z) |
+ lists:reverse(Domains, [D, LastD])],
+ DomainsLong =
+ [lists:duplicate(FirstSz, $Z) |
+ lists:reverse(Domains, [D, LastD])],
+ MsgShort = gen_msg(DomainsShort),
+ MsgLong = gen_msg(DomainsLong),
+ DataShort = inet_dns:encode(MsgShort),
+ DataShortSz = byte_size(DataShort),
+ ?P("DataShort[~w]:~n ~p~n", [DataShortSz, DataShort]),
+ DataLong = inet_dns:encode(MsgLong),
+ DataLongSz = byte_size(DataLong),
+ ?P("DataLong[~w]:~n ~p~n", [DataLongSz, DataLong]),
+ %% When we increase the first RR size by 1, the compressed
+ %% label that occupied a 2 bytes reference instead becomes
+ %% a label with 1 byte size and a final empty label size 1
+ 0 = DataLongSz - (DataShortSz+1 - 2 + 1+Sz+1),
+ ok.
+
+gen_msg(Domains) ->
+ inet_dns:make_msg(
+ [{header, inet_dns:make_header()},
+ {anlist, gen_rrs(Domains)}]).
+
+gen_rrs(Domains) ->
+ [inet_dns:make_rr([{class,in},{type,a},{domain,D}]) ||
+ D <- Domains].
+
+gen_domains(0, _Domain, Acc) ->
+ Acc;
+gen_domains(N, Domain, Acc) ->
+ gen_domains(
+ N - 1, incr_domain(Domain), [lists:reverse(Domain) | Acc]).
+
+incr_domain([$Z | Domain]) ->
+ [$A | incr_domain(Domain)];
+incr_domain([Char | Domain]) ->
+ [Char+1 | Domain].
+
+
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Compatibility tests. Call the inet_SUITE tests, but with
%% lookup = [file,dns] instead of [native]
@@ -789,6 +1074,9 @@ host_and_addr() -> inet_SUITE:host_and_addr().
host_and_addr(Config) -> inet_SUITE:host_and_addr(Config).
+timestamp() ->
+ erlang:monotonic_time(milli_seconds).
+
%% Case flip helper
diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl
index 9d35611d93..47e44200bf 100644
--- a/lib/kernel/test/init_SUITE.erl
+++ b/lib/kernel/test/init_SUITE.erl
@@ -25,7 +25,7 @@
init_per_group/2,end_per_group/2]).
-export([get_arguments/1, get_argument/1, boot_var/1, restart/1,
- many_restarts/0, many_restarts/1,
+ many_restarts/0, many_restarts/1, restart_with_mode/1,
get_plain_arguments/1,
reboot/1, stop_status/1, stop/1, get_status/1, script_id/1,
find_system_processes/0]).
@@ -48,7 +48,7 @@ suite() ->
all() ->
[get_arguments, get_argument, boot_var,
- many_restarts,
+ many_restarts, restart_with_mode,
get_plain_arguments, restart, stop_status, get_status, script_id,
{group, boot}].
@@ -348,6 +348,29 @@ wait_for(N,Node,EHPid) ->
wait_for(N-1,Node,EHPid)
end.
+restart_with_mode(Config) when is_list(Config) ->
+ %% We cannot use loose_node because it doesn't run in
+ %% embedded mode so we quickly start one that exits after restarting
+ {ok,[[Erl]]} = init:get_argument(progname),
+ ModPath = filename:dirname(code:which(?MODULE)),
+
+ Quote = case os:type() of
+ {win32,_} ->
+ [$"];
+ {unix,_} ->
+ [$']
+ end,
+
+ Eval1 = Quote ++ "Mode=code:get_mode(), io:fwrite(Mode), case Mode of interactive -> init:restart([{mode,embedded}]); embedded -> erlang:halt() end" ++ Quote,
+ Cmd1 = Erl ++ " -mode interactive -noshell -eval " ++ Eval1,
+ "interactiveembedded" = os:cmd(Cmd1),
+
+ Eval2 = Quote ++ "Mode=code:get_mode(), io:fwrite(Mode), case Mode of embedded -> init:restart([{mode,interactive}]); interactive -> erlang:halt() end" ++ Quote,
+ Cmd2 = Erl ++ " -mode embedded -noshell -eval " ++ Eval2,
+ "embeddedinteractive" = os:cmd(Cmd2),
+
+ ok.
+
%% ------------------------------------------------
%% Slave executes erlang:halt() on master nodedown.
%% Therefore the slave process must be killed
diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl
index 58b7e4a105..3993f99ed0 100644
--- a/lib/kernel/test/interactive_shell_SUITE.erl
+++ b/lib/kernel/test/interactive_shell_SUITE.erl
@@ -22,9 +22,10 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
get_columns_and_rows/1, exit_initial/1, job_control_local/1,
- job_control_remote/1,
+ job_control_remote/1,stop_during_init/1,
job_control_remote_noshell/1,ctrl_keys/1,
- get_columns_and_rows_escript/1]).
+ get_columns_and_rows_escript/1,
+ remsh/1, remsh_longnames/1, remsh_no_epmd/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
%% For spawn
@@ -44,7 +45,8 @@ all() ->
[get_columns_and_rows_escript,get_columns_and_rows,
exit_initial, job_control_local,
job_control_remote, job_control_remote_noshell,
- ctrl_keys].
+ ctrl_keys, stop_during_init,
+ remsh, remsh_longnames, remsh_no_epmd].
groups() ->
[].
@@ -205,6 +207,22 @@ exit_initial(Config) when is_list(Config) ->
{getline_re,"35"}],[])
end.
+stop_during_init(Config) when is_list(Config) ->
+ case get_progs() of
+ {error,_Reason} ->
+ {skip,"No runerl present"};
+ {RunErl,_ToErl,Erl} ->
+ case create_tempdir() of
+ {error, Reason2} ->
+ {skip, Reason2};
+ Tempdir ->
+ XArg = " -kernel shell_history true -s init stop",
+ start_runerl_command(RunErl, Tempdir, "\\\""++Erl++"\\\""++XArg),
+ {ok, Binary} = file:read_file(filename:join(Tempdir, "erlang.log.1")),
+ nomatch = binary:match(Binary, <<"*** ERROR: Shell process terminated! ***">>)
+ end
+ end.
+
%% Tests that local shell can be started by means of job control.
job_control_local(Config) when is_list(Config) ->
case proplists:get_value(default_shell,Config) of
@@ -381,10 +399,110 @@ wordRight(Chars) ->
[{putline,"world"++Home++"\"hello "++Chars++"\"."},
{getline,"\"hello world\""}].
+%% Test that -remsh works
+remsh(Config) when is_list(Config) ->
+ case proplists:get_value(default_shell,Config) of
+ old -> {skip,"Not supported in old shell"};
+ new ->
+ NodeStr = lists:flatten(io_lib:format("~p",[node()])),
+ [_Name,Host] = string:split(atom_to_list(node()),"@"),
+ Cmds = [{kill_emulator_command,sigint},
+ {putline,""},
+ {putline,"node()."},
+ {getline,NodeStr}],
+
+ %% Test that remsh works with explicit -sname
+ rtnode(Cmds ++ [{putline,"nodes()."},
+ {getline,"['Remshtest@"++Host++"']"}], [],
+ [], " -sname Remshtest -remsh " ++ NodeStr),
+
+ %% Test that remsh works without -sname
+ rtnode(Cmds, [], [], " -remsh " ++ NodeStr)
+
+
+ end.
+
+%% Test that -remsh works with long names
+remsh_longnames(Config) when is_list(Config) ->
+
+ case proplists:get_value(default_shell,Config) of
+ old -> {skip,"Not supported in old shell"};
+ new ->
+ %% If we cannot resolve the domain, we need to add localhost to the longname
+ Domain =
+ case inet_db:res_option(domain) of
+ [] ->
+ "@127.0.0.1";
+ _ -> ""
+ end,
+ case rtstart(" -name " ++ atom_to_list(?FUNCTION_NAME)++Domain) of
+ {ok, _SRPid, _STPid, SState} ->
+ {ok, _CRPid, CTPid, CState} =
+ rtstart("-name undefined" ++ Domain ++
+ " -remsh " ++ atom_to_list(?FUNCTION_NAME)),
+ try
+ ok = get_and_put(
+ CTPid,
+ [{kill_emulator_command,sigint},
+ {putline,""},
+ {putline,"node()."},
+ {getline_re,atom_to_list(?FUNCTION_NAME)}], 1)
+ after
+ rtstop(CState), %% Stop client before server
+ rtstop(SState)
+ end;
+ Else ->
+ Else
+ end
+ end.
+
+%% Test that -remsh works without epmd
+remsh_no_epmd(Config) when is_list(Config) ->
+
+ case proplists:get_value(default_shell,Config) of
+ old -> {skip,"Not supported in old shell"};
+ new ->
+ EPMD_ARGS = "-start_epmd false -erl_epmd_port 12345 ",
+ case rtstart([],"ERL_EPMD_PORT=12345 ",
+ EPMD_ARGS ++ " -sname " ++ atom_to_list(?FUNCTION_NAME)) of
+ {ok, _SRPid, _STPid, SState} ->
+ {ok, _CRPid, CTPid, CState} =
+ rtstart([],"ERL_EPMD_PORT=12345 ",
+ EPMD_ARGS ++ " -remsh "++atom_to_list(?FUNCTION_NAME)),
+ try
+ ok = get_and_put(
+ CTPid,
+ [{kill_emulator_command,sigint},
+ {putline,""},
+ {putline,"node()."},
+ {getline_re,atom_to_list(?FUNCTION_NAME)}], 1)
+ after
+ rtstop(CState), %% Stop client before server
+ rtstop(SState)
+ end;
+ Else ->
+ Else
+ end
+ end.
rtnode(C,N) ->
rtnode(C,N,[]).
rtnode(Commands,Nodename,ErlPrefix) ->
+ rtnode(Commands,Nodename,ErlPrefix,[]).
+rtnode(Commands,Nodename,ErlPrefix,Args) ->
+ case rtstart(Nodename,ErlPrefix,Args) of
+ {ok, _SPid, CPid, RTState} ->
+ erase(getline_skipped),
+ Res = (catch get_and_put(CPid, Commands, 1)),
+ rtstop(RTState),
+ ok = Res;
+ Skip ->
+ Skip
+ end.
+
+rtstart(Args) ->
+ rtstart([],[],Args).
+rtstart(Nodename,ErlPrefix,Args) ->
case get_progs() of
{error,_Reason} ->
{skip,"No runerl present"};
@@ -395,37 +513,36 @@ rtnode(Commands,Nodename,ErlPrefix) ->
Tempdir ->
SPid =
start_runerl_node(RunErl,ErlPrefix++"\\\""++Erl++"\\\"",
- Tempdir,Nodename),
+ Tempdir,Nodename,Args),
CPid = start_toerl_server(ToErl,Tempdir),
- erase(getline_skipped),
- Res =
- (catch get_and_put(CPid, Commands,1)),
- case stop_runerl_node(CPid) of
- {error,_} ->
- CPid2 =
- start_toerl_server
- (ToErl,Tempdir),
- erase(getline_skipped),
- ok = get_and_put
- (CPid2,
- [{putline,[7]},
- {sleep,
- timeout(short)},
- {putline,""},
- {getline," -->"},
- {putline,"s"},
- {putline,"c"},
- {putline,""}],1),
- stop_runerl_node(CPid2);
- _ ->
- ok
- end,
- wait_for_runerl_server(SPid),
- ok = rm_rf(Tempdir),
- ok = Res
- end
+ {ok, SPid, CPid, {CPid, SPid, ToErl, Tempdir}}
+ end
end.
+rtstop({CPid, SPid, ToErl, Tempdir}) ->
+ case stop_runerl_node(CPid) of
+ {error,_} ->
+ CPid2 =
+ start_toerl_server(ToErl,Tempdir),
+ erase(getline_skipped),
+ ok = get_and_put
+ (CPid2,
+ [{putline,[7]},
+ {sleep,
+ timeout(short)},
+ {putline,""},
+ {getline," -->"},
+ {putline,"s"},
+ {putline,"c"},
+ {putline,""}],1),
+ stop_runerl_node(CPid2);
+ _ ->
+ ok
+ end,
+ wait_for_runerl_server(SPid),
+ file:del_dir_r(Tempdir),
+ ok.
+
timeout(long) ->
2 * timeout(normal);
timeout(short) ->
@@ -442,24 +559,6 @@ start_noshell_node(Name) ->
stop_noshell_node(Node) ->
test_server:stop_node(Node).
-
-rm_rf(Dir) ->
- try
- {ok,List} = file:list_dir(Dir),
- Files = [filename:join([Dir,X]) || X <- List],
- [case file:list_dir(Y) of
- {error, enotdir} ->
- ok = file:delete(Y);
- _ ->
- ok = rm_rf(Y)
- end || Y <- Files],
- ok = file:del_dir(Dir),
- ok
- catch
- _:Exception -> {error, {Exception,Dir}}
- end.
-
-
get_and_put(_CPid,[],_) ->
ok;
get_and_put(CPid, [{sleep, X}|T],N) ->
@@ -468,6 +567,13 @@ get_and_put(CPid, [{sleep, X}|T],N) ->
after X ->
get_and_put(CPid,T,N+1)
end;
+get_and_put(CPid, [{kill_emulator_command, Cmd}|T],N) ->
+ ?dbg({kill_emulator_command, Cmd}),
+ CPid ! {self(), {kill_emulator_command, Cmd}},
+ receive
+ {kill_emulator_command,_Res} ->
+ get_and_put(CPid,T,N)
+ end;
get_and_put(CPid, [{getline, Match}|T],N) ->
?dbg({getline, Match}),
CPid ! {self(), {get_line, timeout(normal)}},
@@ -646,7 +752,7 @@ create_nodename(X) ->
end.
-start_runerl_node(RunErl,Erl,Tempdir,Nodename) ->
+start_runerl_node(RunErl,Erl,Tempdir,Nodename,Args) ->
XArg = case Nodename of
[] ->
[];
@@ -655,11 +761,13 @@ start_runerl_node(RunErl,Erl,Tempdir,Nodename) ->
true -> Nodename
end)++
" -setcookie "++atom_to_list(erlang:get_cookie())
- end,
- spawn(fun() ->
- os:cmd("\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++" \""++
- Erl++XArg++"\"")
- end).
+ end ++ " " ++ Args,
+ spawn(fun() -> start_runerl_command(RunErl, Tempdir, Erl++XArg) end).
+
+start_runerl_command(RunErl, Tempdir, Cmd) ->
+ FullCmd = "\""++RunErl++"\" "++Tempdir++"/ "++Tempdir++" \""++Cmd++"\"",
+ ct:pal("~ts",[FullCmd]),
+ os:cmd(FullCmd).
start_toerl_server(ToErl,Tempdir) ->
Pid = spawn(?MODULE,toerl_server,[self(),ToErl,Tempdir]),
@@ -751,8 +859,19 @@ toerl_loop(Port,Acc) ->
Port ! {self(),{command, Data7++"\n"}},
Pid ! {send_line, ok},
toerl_loop(Port,Acc);
+ {Pid, {kill_emulator_command, Cmd}} ->
+ put(kill_emulator_command, Cmd),
+ Pid ! {kill_emulator_command, ok},
+ toerl_loop(Port,Acc);
{_Pid, kill_emulator} ->
- Port ! {self(),{command, "init:stop().\n"}},
+ case get(kill_emulator_command) of
+ undefined ->
+ Port ! {self(),{command, "init:stop().\n"}};
+ sigint ->
+ Port ! {self(),{command, [3]}},
+ timer:sleep(200),
+ Port ! {self(),{command, "a\n"}}
+ end,
Timeout1 = timeout(long),
receive
{Port,eof} ->
@@ -802,13 +921,13 @@ get_data_within(Port, Timeout, Acc) ->
get_default_shell() ->
try
- rtnode([{putline,""},
- {putline, "whereis(user_drv)."},
- {getline, "undefined"}],[]),
- old
+ rtnode([{putline,""},
+ {putline, "whereis(user_drv)."},
+ {getline, "undefined"}],[]),
+ old
catch _E:_R ->
- ?dbg({_E,_R}),
- new
+ ?dbg({_E,_R}),
+ new
end.
atom2list(A) ->
diff --git a/lib/kernel/test/kernel_SUITE.erl b/lib/kernel/test/kernel_SUITE.erl
index 3e5ed855b5..1a88c70963 100644
--- a/lib/kernel/test/kernel_SUITE.erl
+++ b/lib/kernel/test/kernel_SUITE.erl
@@ -23,6 +23,7 @@
-module(kernel_SUITE).
-include_lib("common_test/include/ct.hrl").
+-compile(r21).
%% Test server specific exports
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
@@ -106,7 +107,7 @@ appup_tests(App,{OkVsns0,NokVsns}) ->
create_test_vsns(App) ->
ThisMajor = erlang:system_info(otp_release),
FirstMajor = previous_major(ThisMajor),
- SecondMajor = previous_major(FirstMajor),
+ SecondMajor = previous_major(previous_major(FirstMajor)),
Ok = app_vsn(App,[ThisMajor,FirstMajor]),
Nok0 = app_vsn(App,[SecondMajor]),
Nok = case Ok of
diff --git a/lib/kernel/test/kernel_test_lib.erl b/lib/kernel/test/kernel_test_lib.erl
new file mode 100644
index 0000000000..c4de916b1a
--- /dev/null
+++ b/lib/kernel/test/kernel_test_lib.erl
@@ -0,0 +1,1762 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(kernel_test_lib).
+
+-export([init_per_suite/1,
+ end_per_suite/1]).
+-export([tc_try/3]).
+-export([listen/3,
+ connect/4, connect/5,
+ inet_backend_opts/1,
+ explicit_inet_backend/0,
+ test_inet_backends/0]).
+-export([f/2,
+ print/1, print/2]).
+-export([good_hosts/1,
+ lookup/3]).
+
+-include("kernel_test_lib.hrl").
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init_per_suite([{allow_skip, Allow}|Config]) ->
+ init_per_suite(Allow, Config);
+init_per_suite(Config) ->
+ init_per_suite(true, Config).
+
+init_per_suite(AllowSkip, Config) when is_boolean(AllowSkip) ->
+
+ ct:timetrap(timer:minutes(2)),
+
+ try analyze_and_print_host_info() of
+ {Factor, HostInfo} when (AllowSkip =:= true) andalso
+ is_integer(Factor) ->
+ try maybe_skip(HostInfo) of
+ true ->
+ {skip, "Unstable host and/or os (or combo thererof)"};
+ false ->
+ [{kernel_factor, Factor} | Config]
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end;
+
+ {Factor, _HostInfo} when (AllowSkip =:= false) andalso
+ is_integer(Factor) ->
+ [{kernel_factor, Factor} | Config]
+
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+
+end_per_suite(Config) when is_list(Config) ->
+ Config.
+
+analyze_and_print_host_info() ->
+ {OsFam, OsName} = os:type(),
+ Version =
+ case os:version() of
+ {Maj, Min, Rel} ->
+ f("~w.~w.~w", [Maj, Min, Rel]);
+ VStr ->
+ VStr
+ end,
+ case {OsFam, OsName} of
+ {unix, linux} ->
+ analyze_and_print_linux_host_info(Version);
+ {unix, openbsd} ->
+ analyze_and_print_openbsd_host_info(Version);
+ {unix, freebsd} ->
+ analyze_and_print_freebsd_host_info(Version);
+ {unix, netbsd} ->
+ analyze_and_print_netbsd_host_info(Version);
+ {unix, darwin} ->
+ analyze_and_print_darwin_host_info(Version);
+ {unix, sunos} ->
+ analyze_and_print_solaris_host_info(Version);
+ {win32, nt} ->
+ analyze_and_print_win_host_info(Version);
+ _ ->
+ io:format("OS Family: ~p"
+ "~n OS Type: ~p"
+ "~n Version: ~p"
+ "~n Num Online Schedulers: ~s"
+ "~n", [OsFam, OsName, Version, str_num_schedulers()]),
+ {num_schedulers_to_factor(), []}
+ end.
+
+linux_which_distro(Version) ->
+ case file:read_file_info("/etc/issue") of
+ {ok, _} ->
+ case [string:trim(S) ||
+ S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of
+ [DistroStr|_] ->
+ io:format("Linux: ~s"
+ "~n ~s"
+ "~n",
+ [Version, DistroStr]),
+ case DistroStr of
+ "Wind River Linux" ++ _ ->
+ wind_river;
+ "MontaVista" ++ _ ->
+ montavista;
+ "Yellow Dog" ++ _ ->
+ yellow_dog;
+ _ ->
+ other
+ end;
+ X ->
+ io:format("Linux: ~s"
+ "~n ~p"
+ "~n",
+ [Version, X]),
+ other
+ end;
+ _ ->
+ io:format("Linux: ~s"
+ "~n", [Version]),
+ other
+ end.
+
+analyze_and_print_linux_host_info(Version) ->
+ Distro =
+ case file:read_file_info("/etc/issue") of
+ {ok, _} ->
+ linux_which_distro(Version);
+ _ ->
+ io:format("Linux: ~s"
+ "~n", [Version]),
+ other
+ end,
+ Factor =
+ case (catch linux_which_cpuinfo(Distro)) of
+ {ok, {CPU, BogoMIPS}} ->
+ io:format("CPU: "
+ "~n Model: ~s"
+ "~n BogoMIPS: ~w"
+ "~n Num Online Schedulers: ~s"
+ "~n", [CPU, BogoMIPS, str_num_schedulers()]),
+ if
+ (BogoMIPS > 20000) ->
+ 1;
+ (BogoMIPS > 10000) ->
+ 2;
+ (BogoMIPS > 5000) ->
+ 3;
+ (BogoMIPS > 2000) ->
+ 5;
+ (BogoMIPS > 1000) ->
+ 8;
+ true ->
+ 10
+ end;
+ {ok, CPU} ->
+ io:format("CPU: "
+ "~n Model: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n", [CPU, str_num_schedulers()]),
+ NumChed = erlang:system_info(schedulers),
+ if
+ (NumChed > 2) ->
+ 2;
+ true ->
+ 5
+ end;
+ _ ->
+ 5
+ end,
+ %% Check if we need to adjust the factor because of the memory
+ try linux_which_meminfo() of
+ AddFactor ->
+ io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ {Factor + AddFactor, []}
+ catch
+ _:_:_ ->
+ io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ {Factor, []}
+ end.
+
+linux_cpuinfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/cpuinfo").
+
+linux_cpuinfo_cpu() ->
+ case linux_cpuinfo_lookup("cpu") of
+ [Model] ->
+ Model;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_motherboard() ->
+ case linux_cpuinfo_lookup("motherboard") of
+ [MB] ->
+ MB;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_bogomips() ->
+ case linux_cpuinfo_lookup("bogomips") of
+ BMips when is_list(BMips) ->
+ try lists:sum([bogomips_to_int(BM) || BM <- BMips])
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _X ->
+ "-"
+ end.
+
+linux_cpuinfo_total_bogomips() ->
+ case linux_cpuinfo_lookup("total bogomips") of
+ [TBM] ->
+ try bogomips_to_int(TBM)
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+bogomips_to_int(BM) ->
+ try list_to_float(BM) of
+ F ->
+ floor(F)
+ catch
+ _:_:_ ->
+ try list_to_integer(BM) of
+ I ->
+ I
+ catch
+ _:_:_ ->
+ throw(noinfo)
+ end
+ end.
+
+linux_cpuinfo_model() ->
+ case linux_cpuinfo_lookup("model") of
+ [M] ->
+ M;
+ _X ->
+ "-"
+ end.
+
+linux_cpuinfo_platform() ->
+ case linux_cpuinfo_lookup("platform") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_model_name() ->
+ case linux_cpuinfo_lookup("model name") of
+ [P|_] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_processor() ->
+ case linux_cpuinfo_lookup("Processor") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_which_cpuinfo(montavista) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(yellow_dog) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ {ok, CPU};
+
+linux_which_cpuinfo(wind_river) ->
+ CPU =
+ case linux_cpuinfo_model() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_platform() of
+ "-" ->
+ Model;
+ Platform ->
+ Model ++ " (" ++ Platform ++ ")"
+ end
+ end,
+ case linux_cpuinfo_total_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+%% Check for x86 (Intel or AMD)
+linux_which_cpuinfo(other) ->
+ CPU =
+ case linux_cpuinfo_model_name() of
+ "-" ->
+ %% ARM (at least some distros...)
+ case linux_cpuinfo_processor() of
+ "-" ->
+ case linux_cpuinfo_model() of
+ "-" ->
+ %% Ok, we give up
+ throw(noinfo);
+ Model ->
+ Model
+ end;
+ Proc ->
+ Proc
+ end;
+ ModelName ->
+ ModelName
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end.
+
+linux_meminfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/meminfo").
+
+linux_meminfo_memtotal() ->
+ case linux_meminfo_lookup("MemTotal") of
+ [X] ->
+ X;
+ _ ->
+ "-"
+ end.
+
+%% We *add* the value this return to the Factor.
+linux_which_meminfo() ->
+ case linux_meminfo_memtotal() of
+ "-" ->
+ 0;
+ MemTotal ->
+ io:format("Memory:"
+ "~n ~s"
+ "~n", [MemTotal]),
+ case string:tokens(MemTotal, [$ ]) of
+ [MemSzStr, MemUnit] ->
+ MemSz2 = list_to_integer(MemSzStr),
+ MemSz3 =
+ case string:to_lower(MemUnit) of
+ "kb" ->
+ MemSz2;
+ "mb" ->
+ MemSz2*1024;
+ "gb" ->
+ MemSz2*1024*1024;
+ _ ->
+ throw(noinfo)
+ end,
+ if
+ (MemSz3 >= 8388608) ->
+ 0;
+ (MemSz3 >= 4194304) ->
+ 1;
+ (MemSz3 >= 2097152) ->
+ 3;
+ true ->
+ 5
+ end;
+ _X ->
+ 0
+ end
+ end.
+
+%% Just to be clear: This is ***not*** scientific...
+analyze_and_print_openbsd_host_info(Version) ->
+ io:format("OpenBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ Extract =
+ fun(Key) ->
+ string:tokens(string:trim(os:cmd("sysctl " ++ Key)), [$=])
+ end,
+ try
+ begin
+ CPU =
+ case Extract("hw.model") of
+ ["hw.model", Model] ->
+ string:trim(Model);
+ _ ->
+ "-"
+ end,
+ CPUSpeed =
+ case Extract("hw.cpuspeed") of
+ ["hw.cpuspeed", Speed] ->
+ list_to_integer(Speed);
+ _ ->
+ -1
+ end,
+ NCPU =
+ case Extract("hw.ncpufound") of
+ ["hw.ncpufound", N] ->
+ list_to_integer(N);
+ _ ->
+ -1
+ end,
+ Memory =
+ case Extract("hw.physmem") of
+ ["hw.physmem", PhysMem] ->
+ list_to_integer(PhysMem) div 1024;
+ _ ->
+ -1
+ end,
+ io:format("CPU:"
+ "~n Model: ~s"
+ "~n Speed: ~w"
+ "~n N: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n", [CPU, CPUSpeed, NCPU, Memory]),
+ io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ {CPUFactor + MemAddFactor, []}
+ end
+ catch
+ _:_:_ ->
+ io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ {2, []}
+ end.
+
+analyze_and_print_freebsd_host_info(Version) ->
+ io:format("FreeBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ %% This test require that the program 'sysctl' is in the path.
+ %% First test with 'which sysctl', if that does not work
+ %% try with 'which /sbin/sysctl'. If that does not work either,
+ %% we skip the test...
+ try
+ begin
+ SysCtl =
+ case string:trim(os:cmd("which sysctl")) of
+ [] ->
+ case string:trim(os:cmd("which /sbin/sysctl")) of
+ [] ->
+ throw(sysctl);
+ SC2 ->
+ SC2
+ end;
+ SC1 ->
+ SC1
+ end,
+ Extract =
+ fun(Key) ->
+ string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
+ [$:])
+ end,
+ CPU = analyze_freebsd_cpu(Extract),
+ CPUSpeed = analyze_freebsd_cpu_speed(Extract),
+ NCPU = analyze_freebsd_ncpu(Extract),
+ Memory = analyze_freebsd_memory(Extract),
+ io:format("CPU:"
+ "~n Model: ~s"
+ "~n Speed: ~w"
+ "~n N: ~w"
+ "~n Num Online Schedulers: ~s"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n",
+ [CPU, CPUSpeed, NCPU, str_num_schedulers(), Memory]),
+ io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU =:= -1) ->
+ 1;
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ {CPUFactor + MemAddFactor, []}
+ end
+ catch
+ _:_:_ ->
+ io:format("CPU:"
+ "~n Num Online Schedulers: ~s"
+ "~n", [str_num_schedulers()]),
+ io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
+ end.
+
+
+analyze_freebsd_cpu(Extract) ->
+ analyze_freebsd_item(Extract, "hw.model", fun(X) -> X end, "-").
+
+analyze_freebsd_cpu_speed(Extract) ->
+ analyze_freebsd_item(Extract,
+ "hw.clockrate",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_freebsd_ncpu(Extract) ->
+ analyze_freebsd_item(Extract,
+ "hw.ncpu",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_freebsd_memory(Extract) ->
+ analyze_freebsd_item(Extract,
+ "hw.physmem",
+ fun(X) -> list_to_integer(X) div 1024 end,
+ -1).
+
+analyze_freebsd_item(Extract, Key, Process, Default) ->
+ try
+ begin
+ case Extract(Key) of
+ [Key, Model] ->
+ Process(string:trim(Model));
+ _ ->
+ Default
+ end
+ end
+ catch
+ _:_:_ ->
+ Default
+ end.
+
+analyze_and_print_netbsd_host_info(Version) ->
+ io:format("NetBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ %% This test require that the program 'sysctl' is in the path.
+ %% First test with 'which sysctl', if that does not work
+ %% try with 'which /sbin/sysctl'. If that does not work either,
+ %% we skip the test...
+ try
+ begin
+ SysCtl =
+ case string:trim(os:cmd("which sysctl")) of
+ [] ->
+ case string:trim(os:cmd("which /sbin/sysctl")) of
+ [] ->
+ throw(sysctl);
+ SC2 ->
+ SC2
+ end;
+ SC1 ->
+ SC1
+ end,
+ Extract =
+ fun(Key) ->
+ [string:trim(S) ||
+ S <-
+ string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
+ [$=])]
+ end,
+ CPU = analyze_netbsd_cpu(Extract),
+ Machine = analyze_netbsd_machine(Extract),
+ Arch = analyze_netbsd_machine_arch(Extract),
+ CPUSpeed = analyze_netbsd_cpu_speed(Extract),
+ NCPU = analyze_netbsd_ncpu(Extract),
+ Memory = analyze_netbsd_memory(Extract),
+ io:format("CPU:"
+ "~n Model: ~s (~s, ~s)"
+ "~n Speed: ~w MHz"
+ "~n N: ~w"
+ "~n Num Schedulers: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n",
+ [CPU, Machine, Arch, CPUSpeed, NCPU,
+ erlang:system_info(schedulers), Memory]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU =:= -1) ->
+ 1;
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ {CPUFactor + MemAddFactor, []}
+ end
+ catch
+ _:_:_ ->
+ io:format("CPU:"
+ "~n Num Schedulers: ~w"
+ "~n", [erlang:system_info(schedulers)]),
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
+ end.
+
+analyze_netbsd_cpu(Extract) ->
+ analyze_netbsd_item(Extract, "hw.model", fun(X) -> X end, "-").
+
+analyze_netbsd_machine(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine", fun(X) -> X end, "-").
+
+analyze_netbsd_machine_arch(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine_arch", fun(X) -> X end, "-").
+
+analyze_netbsd_cpu_speed(Extract) ->
+ analyze_netbsd_item(Extract, "machdep.dmi.processor-frequency",
+ fun(X) -> case string:tokens(X, [$\ ]) of
+ [MHz, "MHz"] ->
+ list_to_integer(MHz);
+ _ ->
+ -1
+ end
+ end, "-").
+
+analyze_netbsd_ncpu(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.ncpu",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_netbsd_memory(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.physmem64",
+ fun(X) -> list_to_integer(X) div 1024 end,
+ -1).
+
+analyze_netbsd_item(Extract, Key, Process, Default) ->
+ analyze_freebsd_item(Extract, Key, Process, Default).
+
+%% Model Identifier: Macmini7,1
+%% Processor Name: Intel Core i5
+%% Processor Speed: 2,6 GHz
+%% Number of Processors: 1
+%% Total Number of Cores: 2
+%% L2 Cache (per Core): 256 KB
+%% L3 Cache: 3 MB
+%% Hyper-Threading Technology: Enabled
+%% Memory: 16 GB
+
+analyze_and_print_darwin_host_info(Version) ->
+ %% This stuff is for macOS.
+ %% If we ever tested on a pure darwin machine,
+ %% we need to find some other way to find some info...
+ %% Also, I suppose its possible that we for some other
+ %% reason *fail* to get the info...
+ case analyze_darwin_software_info() of
+ [] ->
+ io:format("Darwin:"
+ "~n Version: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n", [Version, str_num_schedulers()]),
+ {num_schedulers_to_factor(), []};
+ SwInfo when is_list(SwInfo) ->
+ SystemVersion = analyze_darwin_sw_system_version(SwInfo),
+ KernelVersion = analyze_darwin_sw_kernel_version(SwInfo),
+ HwInfo = analyze_darwin_hardware_info(),
+ ModelName = analyze_darwin_hw_model_name(HwInfo),
+ ModelId = analyze_darwin_hw_model_identifier(HwInfo),
+ ProcName = analyze_darwin_hw_processor_name(HwInfo),
+ ProcSpeed = analyze_darwin_hw_processor_speed(HwInfo),
+ NumProc = analyze_darwin_hw_number_of_processors(HwInfo),
+ NumCores = analyze_darwin_hw_total_number_of_cores(HwInfo),
+ Memory = analyze_darwin_hw_memory(HwInfo),
+ io:format("Darwin:"
+ "~n System Version: ~s"
+ "~n Kernel Version: ~s"
+ "~n Model: ~s (~s)"
+ "~n Processor: ~s (~s, ~s, ~s)"
+ "~n Memory: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n", [SystemVersion, KernelVersion,
+ ModelName, ModelId,
+ ProcName, ProcSpeed, NumProc, NumCores,
+ Memory,
+ str_num_schedulers()]),
+ CPUFactor = analyze_darwin_cpu_to_factor(ProcName,
+ ProcSpeed,
+ NumProc,
+ NumCores),
+ MemFactor = analyze_darwin_memory_to_factor(Memory),
+ if (MemFactor =:= 1) ->
+ {CPUFactor, []};
+ true ->
+ {CPUFactor + MemFactor, []}
+ end
+ end.
+
+analyze_darwin_sw_system_version(SwInfo) ->
+ proplists:get_value("system version", SwInfo, "-").
+
+analyze_darwin_sw_kernel_version(SwInfo) ->
+ proplists:get_value("kernel version", SwInfo, "-").
+
+analyze_darwin_software_info() ->
+ analyze_darwin_system_profiler("SPSoftwareDataType").
+
+analyze_darwin_hw_model_name(HwInfo) ->
+ proplists:get_value("model name", HwInfo, "-").
+
+analyze_darwin_hw_model_identifier(HwInfo) ->
+ proplists:get_value("model identifier", HwInfo, "-").
+
+analyze_darwin_hw_processor_name(HwInfo) ->
+ proplists:get_value("processor name", HwInfo, "-").
+
+analyze_darwin_hw_processor_speed(HwInfo) ->
+ proplists:get_value("processor speed", HwInfo, "-").
+
+analyze_darwin_hw_number_of_processors(HwInfo) ->
+ proplists:get_value("number of processors", HwInfo, "-").
+
+analyze_darwin_hw_total_number_of_cores(HwInfo) ->
+ proplists:get_value("total number of cores", HwInfo, "-").
+
+analyze_darwin_hw_memory(HwInfo) ->
+ proplists:get_value("memory", HwInfo, "-").
+
+analyze_darwin_hardware_info() ->
+ analyze_darwin_system_profiler("SPHardwareDataType").
+
+%% This basically has the structure: "Key: Value"
+%% But could also be (for example):
+%% "Something:" (which we ignore)
+%% "Key: Value1:Value2"
+analyze_darwin_system_profiler(DataType) ->
+ %% First, make sure the program actually exist:
+ case os:cmd("which system_profiler") of
+ [] ->
+ [];
+ _ ->
+ D0 = os:cmd("system_profiler " ++ DataType),
+ D1 = string:tokens(D0, [$\n]),
+ D2 = [string:trim(S1) || S1 <- D1],
+ D3 = [string:tokens(S2, [$:]) || S2 <- D2],
+ analyze_darwin_system_profiler2(D3)
+ end.
+
+analyze_darwin_system_profiler2(L) ->
+ analyze_darwin_system_profiler2(L, []).
+
+analyze_darwin_system_profiler2([], Acc) ->
+ [{string:to_lower(K), V} || {K, V} <- lists:reverse(Acc)];
+analyze_darwin_system_profiler2([[_]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, Acc);
+analyze_darwin_system_profiler2([[H1,H2]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, [{H1, string:trim(H2)}|Acc]);
+analyze_darwin_system_profiler2([[H|TH0]|T], Acc) ->
+ %% Some value parts has ':' in them, so put them together
+ TH1 = colonize(TH0),
+ analyze_darwin_system_profiler2(T, [{H, string:trim(TH1)}|Acc]).
+
+%% This is only called if the length is at least 2
+colonize([L1, L2]) ->
+ L1 ++ ":" ++ L2;
+colonize([H|T]) ->
+ H ++ ":" ++ colonize(T).
+
+%% The memory looks like this "<size> <unit>". Example: "2 GB"
+analyze_darwin_memory_to_factor(Mem) ->
+ case [string:to_lower(S) || S <- string:tokens(Mem, [$\ ])] of
+ [_SzStr, "tb"] ->
+ 1;
+ [SzStr, "gb"] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz < 2 ->
+ 20;
+ Sz when Sz < 4 ->
+ 10;
+ Sz when Sz < 8 ->
+ 5;
+ Sz when Sz < 16 ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 20
+ end;
+ [_SzStr, "mb"] ->
+ 20;
+ _ ->
+ 20
+ end.
+
+%% The speed is a string: "<speed> <unit>"
+%% the speed may be a float, which we transforms into an integer of MHz.
+%% To calculate a factor based on processor speed, number of procs
+%% and number of cores is ... not an exact ... science ...
+analyze_darwin_cpu_to_factor(_ProcName,
+ ProcSpeedStr, NumProcStr, NumCoresStr) ->
+ Speed =
+ case [string:to_lower(S) || S <- string:tokens(ProcSpeedStr, [$\ ])] of
+ [SpeedStr, "mhz"] ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ SpeedI
+ catch
+ _:_:_ ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(SpeedF)
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ [SpeedStr, "ghz"] ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(1000*SpeedF)
+ catch
+ _:_:_ ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ 1000*SpeedI
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ _ ->
+ -1
+ end,
+ NumProc = try list_to_integer(NumProcStr) of
+ NumProcI ->
+ NumProcI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ NumCores = try list_to_integer(NumCoresStr) of
+ NumCoresI ->
+ NumCoresI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ if
+ (Speed > 3000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 5;
+ (NumCores < 4) ->
+ 3;
+ (NumCores < 6) ->
+ 2;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ (Speed > 2000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 8;
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 6) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 8) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ true ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 10;
+ (NumCores < 4) ->
+ 7;
+ (NumCores < 6) ->
+ 5;
+ (NumCores < 8) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 8;
+ (NumCores < 8) ->
+ 4;
+ true ->
+ 1
+ end
+ end
+ end.
+
+analyze_and_print_solaris_host_info(Version) ->
+ Release =
+ case file:read_file_info("/etc/release") of
+ {ok, _} ->
+ case [string:trim(S) || S <- string:tokens(os:cmd("cat /etc/release"), [$\n])] of
+ [Rel | _] ->
+ Rel;
+ _ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end,
+ %% Display the firmware device tree root properties (prtconf -b)
+ Props = [list_to_tuple([string:trim(PS) || PS <- Prop]) ||
+ Prop <- [string:tokens(S, [$:]) ||
+ S <- string:tokens(os:cmd("prtconf -b"), [$\n])]],
+ BannerName = case lists:keysearch("banner-name", 1, Props) of
+ {value, {_, BN}} ->
+ string:trim(BN);
+ _ ->
+ "-"
+ end,
+ InstructionSet =
+ case string:trim(os:cmd("isainfo -k")) of
+ "Pseudo-terminal will not" ++ _ ->
+ "-";
+ IS ->
+ IS
+ end,
+ PtrConf = [list_to_tuple([string:trim(S) || S <- Items]) || Items <- [string:tokens(S, [$:]) || S <- string:tokens(os:cmd("prtconf"), [$\n])], length(Items) > 1],
+ SysConf =
+ case lists:keysearch("System Configuration", 1, PtrConf) of
+ {value, {_, SC}} ->
+ SC;
+ _ ->
+ "-"
+ end,
+ %% Because we count the lines of the output (which may contain
+ %% any number of extra crap lines) we need to ensure we only
+ %% count the "proper" stdout. So send it to a tmp file first
+ %% and then count its number of lines...
+ NumPhysCPU =
+ try
+ begin
+ File1 = f("/tmp/psrinfo_p.~s.~w", [os:getpid(), os:system_time()]),
+ os:cmd("psrinfo -p > " ++ File1),
+ string:trim(os:cmd("cat " ++ File1))
+ end
+ catch
+ _:_:_ ->
+ "-"
+ end,
+ %% Because we count the lines of the output (which may contain
+ %% any number of extra crap lines) we need to ensure we only
+ %% count the "proper" stdout. So send it to a tmp file first
+ %% and then count its number of lines...
+ NumVCPU =
+ try
+ begin
+ File2 = f("/tmp/psrinfo.~s.~w", [os:getpid(), os:system_time()]),
+ os:cmd("psrinfo > " ++ File2),
+ [NumVCPUStr | _] = string:tokens(os:cmd("wc -l " ++ File2), [$\ ]),
+ NumVCPUStr
+ end
+ catch
+ _:_:_ ->
+ "-"
+ end,
+ MemSz =
+ case lists:keysearch("Memory size", 1, PtrConf) of
+ {value, {_, MS}} ->
+ MS;
+ _ ->
+ "-"
+ end,
+ io:format("Solaris: ~s"
+ "~n Release: ~s"
+ "~n Banner Name: ~s"
+ "~n Instruction Set: ~s"
+ "~n CPUs: ~s (~s)"
+ "~n System Config: ~s"
+ "~n Memory Size: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n~n", [Version, Release, BannerName, InstructionSet,
+ NumPhysCPU, NumVCPU,
+ SysConf, MemSz,
+ str_num_schedulers()]),
+ io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ MemFactor =
+ try string:tokens(MemSz, [$ ]) of
+ [SzStr, "Mega" ++ _] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz > 8192 ->
+ 0;
+ Sz when Sz > 4096 ->
+ 1;
+ Sz when Sz > 2048 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ [SzStr, "Giga" ++ _] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz > 8 ->
+ 0;
+ Sz when Sz > 4 ->
+ 1;
+ Sz when Sz > 2 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ _ ->
+ 10
+ catch
+ _:_:_ ->
+ 10
+ end,
+ {try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end + MemFactor, []}.
+
+analyze_and_print_win_host_info(Version) ->
+ SysInfo = which_win_system_info(),
+ OsName = win_sys_info_lookup(os_name, SysInfo),
+ OsVersion = win_sys_info_lookup(os_version, SysInfo),
+ SysMan = win_sys_info_lookup(system_manufacturer, SysInfo),
+ SysMod = win_sys_info_lookup(system_model, SysInfo),
+ NumProcs = win_sys_info_lookup(num_processors, SysInfo),
+ TotPhysMem = win_sys_info_lookup(total_phys_memory, SysInfo),
+ io:format("Windows: ~s"
+ "~n OS Version: ~s (~p)"
+ "~n System Manufacturer: ~s"
+ "~n System Model: ~s"
+ "~n Number of Processor(s): ~s"
+ "~n Total Physical Memory: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n~n", [OsName, OsVersion, Version,
+ SysMan, SysMod, NumProcs, TotPhysMem,
+ str_num_schedulers()]),
+ io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
+ MemFactor =
+ try
+ begin
+ [MStr, MUnit|_] =
+ string:tokens(lists:delete($,, TotPhysMem), [$\ ]),
+ case string:to_lower(MUnit) of
+ "gb" ->
+ try list_to_integer(MStr) of
+ M when M > 8 ->
+ 0;
+ M when M > 4 ->
+ 1;
+ M when M > 2 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ "mb" ->
+ try list_to_integer(MStr) of
+ M when M > 8192 ->
+ 0;
+ M when M > 4096 ->
+ 1;
+ M when M > 2048 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ _ ->
+ 10
+ end
+ end
+ catch
+ _:_:_ ->
+ 10
+ end,
+ CPUFactor =
+ case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {CPUFactor + MemFactor, SysInfo}.
+
+win_sys_info_lookup(Key, SysInfo) ->
+ win_sys_info_lookup(Key, SysInfo, "-").
+
+win_sys_info_lookup(Key, SysInfo, Def) ->
+ case lists:keysearch(Key, 1, SysInfo) of
+ {value, {Key, Value}} ->
+ Value;
+ false ->
+ Def
+ end.
+
+%% This function only extracts the prop we actually care about!
+which_win_system_info() ->
+ F = fun() ->
+ try
+ begin
+ SysInfo = os:cmd("systeminfo"),
+ process_win_system_info(
+ string:tokens(SysInfo, [$\r, $\n]), [])
+ end
+ catch
+ C:E:S ->
+ io:format("Failed get or process System info: "
+ " Error Class: ~p"
+ " Error: ~p"
+ " Stack: ~p"
+ "~n", [C, E, S]),
+ []
+ end
+ end,
+ proxy_call(F, timer:minutes(1), []).
+
+process_win_system_info([], Acc) ->
+ Acc;
+process_win_system_info([H|T], Acc) ->
+ case string:tokens(H, [$:]) of
+ [Key, Value] ->
+ case string:to_lower(Key) of
+ "os name" ->
+ process_win_system_info(T,
+ [{os_name, string:trim(Value)}|Acc]);
+ "os version" ->
+ process_win_system_info(T,
+ [{os_version, string:trim(Value)}|Acc]);
+ "system manufacturer" ->
+ process_win_system_info(T,
+ [{system_manufacturer, string:trim(Value)}|Acc]);
+ "system model" ->
+ process_win_system_info(T,
+ [{system_model, string:trim(Value)}|Acc]);
+ "processor(s)" ->
+ [NumProcStr|_] = string:tokens(Value, [$\ ]),
+ T2 = lists:nthtail(list_to_integer(NumProcStr), T),
+ process_win_system_info(T2,
+ [{num_processors, NumProcStr}|Acc]);
+ "total physical memory" ->
+ process_win_system_info(T,
+ [{total_phys_memory, string:trim(Value)}|Acc]);
+ _ ->
+ process_win_system_info(T, Acc)
+ end;
+ _ ->
+ process_win_system_info(T, Acc)
+ end.
+
+str_num_schedulers() ->
+ try erlang:system_info(schedulers_online) of
+ N -> f("~w", [N])
+ catch
+ _:_:_ -> "-"
+ end.
+
+num_schedulers_to_factor() ->
+ try erlang:system_info(schedulers_online) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end.
+
+
+linux_info_lookup(Key, File) ->
+ LKey = string:to_lower(Key),
+ try [string:trim(S) || S <- string:tokens(os:cmd("grep -i " ++ "\"" ++ LKey ++ "\"" ++ " " ++ File), [$:,$\n])] of
+ Info ->
+ linux_info_lookup_collect(LKey, Info, [])
+ catch
+ _:_:_ ->
+ "-"
+ end.
+
+linux_info_lookup_collect(_Key, [], Values) ->
+ lists:reverse(Values);
+linux_info_lookup_collect(Key, [Key, Value|Rest], Values) ->
+ linux_info_lookup_collect(Key, Rest, [Value|Values]);
+linux_info_lookup_collect(Key1, [Key2, Value|Rest], Values) ->
+ case string:to_lower(Key2) of
+ Key1 ->
+ linux_info_lookup_collect(Key1, Rest, [Value|Values]);
+ _ ->
+ lists:reverse(Values)
+ end;
+linux_info_lookup_collect(_, _, Values) ->
+ lists:reverse(Values).
+
+maybe_skip(_HostInfo) ->
+
+ %% We have some crap machines that causes random test case failures
+ %% for no obvious reason. So, attempt to identify those without actually
+ %% checking for the host name...
+
+ LinuxVersionVerify =
+ fun(V) when (V > {3,6,11}) ->
+ false; % OK - No skip
+ (V) when (V =:= {3,6,11}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Fedora release 16 " ++ _ -> % Stone age Fedora => Skip
+ true;
+ _ ->
+ false
+ end;
+ (V) when (V =:= {3,4,20}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Wind River Linux 5.0.1.0" ++ _ -> % *Old* Wind River => skip
+ true;
+ _ ->
+ false
+ end;
+ (V) when (V =:= {2,6,32}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Debian GNU/Linux 6.0 " ++ _ -> % Stone age Debian => Skip
+ true;
+ _ ->
+ false
+ end;
+ (V) when (V > {2,6,24}) ->
+ false; % OK - No skip
+ (V) when (V =:= {2,6,10}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "MontaVista" ++ _ -> % Stone age MontaVista => Skip
+ %% The real problem is that the machine is *very* slow
+ true;
+ _ ->
+ false
+ end;
+ (_) ->
+ %% We are specifically checking for
+ %% a *really* old gento...
+ case string:find(string:strip(os:cmd("uname -a")), "gentoo") of
+ nomatch ->
+ false;
+ _ -> % Stone age gentoo => Skip
+ true
+ end
+ end,
+ DarwinVersionVerify =
+ fun(V) when (V > {9, 8, 0}) ->
+ %% This version is OK: No Skip
+ false;
+ (_V) ->
+ %% This version is *not* ok: Skip
+ true
+ end,
+ SkipWindowsOnVirtual =
+ %% fun() ->
+ %% SysMan = win_sys_info_lookup(system_manufacturer, HostInfo),
+ %% case string:to_lower(SysMan) of
+ %% "vmware" ++ _ ->
+ %% true;
+ %% _ ->
+ %% false
+ %% end
+ %% end,
+ fun() ->
+ %% The host has been replaced and the VM has been reinstalled
+ %% so for now we give it a chance...
+ false
+ end,
+ COND = [{unix, [{linux, LinuxVersionVerify},
+ {darwin, DarwinVersionVerify}]},
+ {win32, SkipWindowsOnVirtual}],
+ os_cond_skip(COND).
+
+os_cond_skip(any) ->
+ true;
+os_cond_skip(Skippable) when is_list(Skippable) ->
+ os_cond_skip(Skippable, os:type());
+os_cond_skip(_Crap) ->
+ false.
+
+os_cond_skip(Skippable, {OsFam, OsName}) ->
+ os_cond_skip(Skippable, OsFam, OsName);
+os_cond_skip(Skippable, OsFam) ->
+ os_cond_skip(Skippable, OsFam, undefined).
+
+os_cond_skip(Skippable, OsFam, OsName) ->
+ %% Check if the entire family is to be skipped
+ %% Example: [win32, unix]
+ case lists:member(OsFam, Skippable) of
+ true ->
+ true;
+ false ->
+ %% Example: [{unix, freebsd}] | [{unix, [freebsd, darwin]}]
+ case lists:keysearch(OsFam, 1, Skippable) of
+ {value, {OsFam, OsName}} ->
+ true;
+ {value, {OsFam, OsNames}} when is_list(OsNames) ->
+ %% OsNames is a list of:
+ %% [atom()|{atom(), function/0 | function/1}]
+ case lists:member(OsName, OsNames) of
+ true ->
+ true;
+ false ->
+ os_cond_skip_check(OsName, OsNames)
+ end;
+ {value, {OsFam, Check}} when is_function(Check, 0) ->
+ Check();
+ {value, {OsFam, Check}} when is_function(Check, 1) ->
+ Check(os:version());
+ _ ->
+ false
+ end
+ end.
+
+%% Performs a check via a provided fun with arity 0 or 1.
+%% The argument is the result of os:version().
+os_cond_skip_check(OsName, OsNames) ->
+ case lists:keysearch(OsName, 1, OsNames) of
+ {value, {OsName, Check}} when is_function(Check, 0) ->
+ Check();
+ {value, {OsName, Check}} when is_function(Check, 1) ->
+ Check(os:version());
+ _ ->
+ false
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+lookup(Key, Config, Default) ->
+ case lists:keysearch(Key, 1, Config) of
+ {value, {Key, Val}} ->
+ Val;
+ _ ->
+ Default
+ end.
+
+
+%% Extract N number of hosts from the config.
+%% Prepend with the own host.
+%% If not N number of hosts are available, issue a skip exit.
+good_hosts(N) when is_integer(N) andalso (N > 0) ->
+ GoodHosts = ct:get_config(test_hosts, []),
+ {ok, CurrentHost} = inet:gethostname(),
+ GoodHosts2 = [CurrentHost] ++ (GoodHosts -- [CurrentHost]),
+ good_hosts2(GoodHosts2, N).
+
+good_hosts2(GoodHosts, N) ->
+ good_hosts2(GoodHosts, N, []).
+
+good_hosts2(_GoodHosts, N, Acc) when (N =:= 0) ->
+ lists:reverse(Acc);
+good_hosts2([], _N, _Acc) ->
+ ?SKIPE("Not enough good hosts");
+good_hosts2([H|T], N, Acc) ->
+ case inet:gethostbyname(H) of
+ {ok, _} ->
+ good_hosts2(T, N-1, [H|Acc]);
+ {error, _} ->
+ ?P("Host not found: ~p", [H]),
+ good_hosts2(T, N, Acc)
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+set_tc_name(N) when is_atom(N) ->
+ set_tc_name(atom_to_list(N));
+set_tc_name(N) when is_list(N) ->
+ put(tc_name, N).
+
+%% get_tc_name() ->
+%% get(tc_name).
+
+tc_begin(TC) ->
+ OldVal = process_flag(trap_exit, true),
+ put(old_trap_exit, OldVal),
+ set_tc_name(TC),
+ tc_print("begin ***",
+ "~n----------------------------------------------------~n", "").
+
+tc_end(Result) when is_list(Result) ->
+ OldVal = erase(old_trap_exit),
+ process_flag(trap_exit, OldVal),
+ tc_print("done: ~s", [Result],
+ "", "----------------------------------------------------~n~n"),
+ ok.
+
+%% *** tc_try/2,3 ***
+%% Case: Basically the test case name
+%% Cond: A fun that is evaluated before the actual test case
+%% The point of this is that it can performs checks to
+%% see if we shall run the test case at all.
+%% For instance, the test case may only work in specific
+%% conditions.
+%% TC: The test case fun
+tc_try(Case, Cond, TC)
+ when is_atom(Case) andalso
+ is_function(Cond, 0) andalso
+ is_function(TC, 0) ->
+ tc_begin(Case),
+ try Cond() of
+ ok ->
+ try
+ begin
+ TC(),
+ ?SLEEP(?SECS(1)),
+ tc_end("ok")
+ end
+ catch
+ C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) ->
+ %% i("catched[tc] (skip): "
+ %% "~n C: ~p"
+ %% "~n SKIP: ~p"
+ %% "~n", [C, SKIP]),
+ tc_end( f("skipping(catched,~w,tc)", [C]) ),
+ SKIP;
+ C:E:S ->
+ %% i("catched[tc]: "
+ %% "~n C: ~p"
+ %% "~n E: ~p"
+ %% "~n S: ~p"
+ %% "~n", [C, E, S]),
+ tc_end( f("failed(catched,~w,tc)", [C]) ),
+ erlang:raise(C, E, S)
+ end;
+ {skip, _} = SKIP ->
+ tc_end("skipping(tc)"),
+ SKIP;
+ {error, Reason} ->
+ tc_end("failed(tc)"),
+ exit({tc_cond_failed, Reason})
+ catch
+ C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) ->
+ %% i("catched[cond] (skip): "
+ %% "~n C: ~p"
+ %% "~n SKIP: ~p"
+ %% "~n", [C, SKIP]),
+ tc_end( f("skipping(catched,~w,cond)", [C]) ),
+ SKIP;
+ C:E:S ->
+ %% i("catched[cond]: "
+ %% "~n C: ~p"
+ %% "~n E: ~p"
+ %% "~n S: ~p"
+ %% "~n", [C, E, S]),
+ tc_end( f("failed(catched,~w,cond)", [C]) ),
+ erlang:raise(C, E, S)
+ end.
+
+
+tc_print(F, Before, After) ->
+ tc_print(F, [], Before, After).
+
+tc_print(F, A, Before, After) ->
+ Name = tc_which_name(),
+ FStr = f("*** [~s][~s][~p] " ++ F ++ "~n",
+ [formated_timestamp(),Name,self()|A]),
+ io:format(user, Before ++ FStr ++ After, []).
+
+tc_which_name() ->
+ case get(tc_name) of
+ undefined ->
+ case get(sname) of
+ undefined ->
+ "";
+ SName when is_list(SName) ->
+ SName
+ end;
+ Name when is_list(Name) ->
+ Name
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+timetrap_scale_factor() ->
+ case (catch test_server:timetrap_scale_factor()) of
+ {'EXIT', _} ->
+ 1;
+ N ->
+ N
+ end.
+
+
+proxy_call(F, Timeout, Default)
+ when is_function(F, 0) andalso is_integer(Timeout) andalso (Timeout > 0) ->
+ {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end),
+ receive
+ {'DOWN', M, process, P, Reply} ->
+ Reply
+ after Timeout ->
+ erlang:demonitor(M, [flush]),
+ exit(P, kill),
+ Default
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+listen(Config, Port, Opts) ->
+ InetBackendOpts = inet_backend_opts(Config),
+ gen_tcp:listen(Port, InetBackendOpts ++ Opts).
+
+connect(Config, Host, Port, Opts) ->
+ InetBackendOpts = inet_backend_opts(Config),
+ gen_tcp:connect(Host, Port, InetBackendOpts ++ Opts).
+
+connect(Config, Host, Port, Opts, Timeout) ->
+ InetBackendOpts = inet_backend_opts(Config),
+ gen_tcp:connect(Host, Port, InetBackendOpts ++ Opts, Timeout).
+
+
+inet_backend_opts(Config) when is_list(Config) ->
+ case lists:keysearch(socket_create_opts, 1, Config) of
+ {value, {socket_create_opts, InetBackendOpts}} ->
+ InetBackendOpts;
+ false ->
+ []
+ end.
+
+
+explicit_inet_backend() ->
+ case application:get_all_env(kernel) of
+ Env when is_list(Env) ->
+ case lists:keysearch(inet_backend, 1, Env) of
+ {value, {inet_backend, _}} ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end.
+
+
+test_inet_backends() ->
+ case application:get_all_env(kernel) of
+ Env when is_list(Env) ->
+ case lists:keysearch(test_inet_backends, 1, Env) of
+ {value, {test_inet_backends, true}} ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp({_N1, _N2, N3} = TS) ->
+ {_Date, Time} = calendar:now_to_local_time(TS),
+ {Hour, Min, Sec} = Time,
+ FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.3.0w",
+ [Hour, Min, Sec, N3 div 1000]),
+ lists:flatten(FormatTS).
+
+print(F) ->
+ print(F, []).
+
+print(F, A) ->
+ io:format("~s ~p " ++ F ++ "~n", [formated_timestamp(), self() | A]).
diff --git a/lib/kernel/test/kernel_test_lib.hrl b/lib/kernel/test/kernel_test_lib.hrl
new file mode 100644
index 0000000000..ec0ca05573
--- /dev/null
+++ b/lib/kernel/test/kernel_test_lib.hrl
@@ -0,0 +1,55 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-ifndef(kernel_test_lib_hrl).
+-define(kernel_test_lib_hrl, true).
+
+-define(LIB, kernel_test_lib).
+
+-define(LOOKUP(__Key__, __Config__, __Default__),
+ ?LIB:lookup(__Key__, __Config__, __Default__)).
+-define(GOOD_HOSTS(__N__), ?LIB:good_hosts(__N__)).
+
+-define(SKIPT(R), throw({skip, R})).
+-define(SKIPE(R), exit({skip, R})).
+
+-define(TC_TRY(Case, TC), ?TC_TRY(Case, fun() -> ok end, TC)).
+-define(TC_TRY(Case, Cond, TC), ?LIB:tc_try(Case, Cond, TC)).
+
+-define(LISTEN(C, P, O), ?LIB:listen(C, P, O)).
+-define(CONNECT(__C__, __H__, __P__, __O__),
+ ?LIB:connect(__C__, __H__, __P__, __O__)).
+-define(CONNECT(__C__, __H__, __P__, __O__, __T__),
+ ?LIB:connect(__C__, __H__, __P__, __O__, __T__)).
+-define(INET_BACKEND_OPTS(C), ?LIB:inet_backend_opts(C)).
+-define(EXPLICIT_INET_BACKEND(), ?LIB:explicit_inet_backend()).
+-define(TEST_INET_BACKENDS(), ?LIB:test_inet_backends()).
+
+-define(F(FORMAT, ARGS), ?LIB:f(FORMAT, ARGS)).
+-define(P(F), ?LIB:print(F)).
+-define(P(F,A), ?LIB:print(F, A)).
+
+-define(SECS(I), timer:seconds(I)).
+-define(MINS(I), timer:minutes(I)).
+
+-define(SLEEP(T), ct:sleep(T)).
+-define(TT(T), ct:timetrap(T)).
+
+-endif. % -ifdef(kernel_test_lib_hrl).
diff --git a/lib/kernel/test/logger_SUITE.erl b/lib/kernel/test/logger_SUITE.erl
index 19647b984d..8513cf2936 100644
--- a/lib/kernel/test/logger_SUITE.erl
+++ b/lib/kernel/test/logger_SUITE.erl
@@ -68,7 +68,7 @@ init_per_testcase(_TestCase, Config) ->
[{logger_config,PC}|Config].
end_per_testcase(Case, Config) ->
- try apply(?MODULE,Case,[cleanup,Config])
+ try erlang:apply(?MODULE,Case,[cleanup,Config])
catch error:undef -> ok
end,
ok.
@@ -276,7 +276,7 @@ change_config(_Config) ->
logger:get_primary_config(),
3 = maps:size(PC1),
%% Check that internal 'handlers' field has not been changed
- MS = [{{{?HANDLER_KEY,'$1'},'_','_'},[],['$1']}],
+ MS = [{{{?HANDLER_KEY,'$1'},'_'},[],['$1']}],
HIds1 = lists:sort(ets:select(?LOGGER_TABLE,MS)), % dirty, internal data
HIds2 = lists:sort(logger:get_handler_ids()),
HIds1 = HIds2,
@@ -478,14 +478,15 @@ set_application_level(cleanup,_Config) ->
ok.
cache_module_level(_Config) ->
- ok = logger:unset_module_level(?MODULE),
- [] = ets:lookup(?LOGGER_TABLE,?MODULE), %dirty - add API in logger_config?
+
+ %% This test does a lot of whitebox tests so be prepared for that
+ persistent_term:erase({logger_config,?MODULE}),
+
+ primary = persistent_term:get({logger_config,?MODULE}, primary),
?LOG_NOTICE(?map_rep),
- %% Caching is done asynchronously, so wait a bit for the update
- timer:sleep(100),
- [_] = ets:lookup(?LOGGER_TABLE,?MODULE), %dirty - add API in logger_config?
- ok = logger:unset_module_level(?MODULE),
- [] = ets:lookup(?LOGGER_TABLE,?MODULE), %dirty - add API in logger_config?
+ 5 = persistent_term:get({logger_config,?MODULE}, primary),
+ logger:set_primary_config(level, info),
+ 6 = persistent_term:get({logger_config,?MODULE}, primary),
ok.
cache_module_level(cleanup,_Config) ->
@@ -569,6 +570,7 @@ filter_failed(cleanup,_Config) ->
ok.
handler_failed(_Config) ->
+ logger:set_primary_config(level,all),
register(callback_receiver,self()),
{error,{invalid_id,1}} = logger:add_handler(1,?MODULE,#{}),
{error,{invalid_module,"nomodule"}} = logger:add_handler(h1,"nomodule",#{}),
@@ -612,7 +614,7 @@ handler_failed(_Config) ->
logger:add_handler(h1,?MODULE,#{add_call=>KillHandler}),
check_no_log(),
- ok = logger:add_handler(h1,?MODULE,#{}),
+ ok = logger:add_handler(h1,?MODULE,#{tc_proc=>self()}),
{error,{attempting_syncronous_call_to_self,_}} =
logger:set_handler_config(h1,#{conf_call=>CallAddHandler}),
{error,{callback_crashed,_}} =
@@ -628,7 +630,8 @@ handler_failed(_Config) ->
logger:set_handler_config(h1,conf_call,KillHandler),
ok = logger:remove_handler(h1),
- [add,remove] = test_server:messages_get(),
+ [add,{#{level:=error},_},{#{level:=error},_},
+ {#{level:=error},_},{#{level:=error},_},remove] = test_server:messages_get(),
check_no_log(),
ok = logger:add_handler(h1,?MODULE,#{rem_call=>CallAddHandler}),
@@ -644,6 +647,7 @@ handler_failed(_Config) ->
handler_failed(cleanup,_Config) ->
logger:remove_handler(h1),
logger:remove_handler(h2),
+ logger:set_primary_config(level,info),
ok.
config_sanity_check(_Config) ->
@@ -1146,7 +1150,7 @@ kernel_config(Config) ->
ok.
-pretty_print(Config) ->
+pretty_print(_Config) ->
ok = logger:add_handler(?FUNCTION_NAME,logger_std_h,#{}),
ok = logger:set_module_level([module1,module2],debug),
@@ -1280,11 +1284,21 @@ test_api(Level) ->
ok = check_logged(Level,#{Level=>rep},#{my=>meta}),
logger:Level("~w: ~w",[Level,fa]),
ok = check_logged(Level,"~w: ~w",[Level,fa],#{}),
+ logger:Level('~w: ~w',[Level,fa]),
+ ok = check_logged(Level,'~w: ~w',[Level,fa],#{}),
+ logger:Level(<<"~w: ~w">>,[Level,fa]),
+ ok = check_logged(Level,<<"~w: ~w">>,[Level,fa],#{}),
logger:Level("~w: ~w ~w",[Level,fa,meta],#{my=>meta}),
ok = check_logged(Level,"~w: ~w ~w",[Level,fa,meta],#{my=>meta}),
logger:Level(fun(x) -> {"~w: ~w ~w",[Level,fun_to_fa,meta]} end,x,
#{my=>meta}),
ok = check_logged(Level,"~w: ~w ~w",[Level,fun_to_fa,meta],#{my=>meta}),
+ logger:Level(fun(x) -> {<<"~w: ~w ~w">>,[Level,fun_to_fa,meta]} end,x,
+ #{my=>meta}),
+ ok = check_logged(Level,<<"~w: ~w ~w">>,[Level,fun_to_fa,meta],#{my=>meta}),
+ logger:Level(fun(x) -> {'~w: ~w ~w',[Level,fun_to_fa,meta]} end,x,
+ #{my=>meta}),
+ ok = check_logged(Level,'~w: ~w ~w',[Level,fun_to_fa,meta],#{my=>meta}),
logger:Level(fun(x) -> #{Level=>fun_to_r,meta=>true} end,x,
#{my=>meta}),
ok = check_logged(Level,#{Level=>fun_to_r,meta=>true},#{my=>meta}),
@@ -1310,6 +1324,12 @@ test_log_function(Level) ->
logger:log(Level,fun(x) -> {"~w: ~w ~w",[Level,fun_to_fa,meta]} end,
x, #{my=>meta}),
ok = check_logged(Level,"~w: ~w ~w",[Level,fun_to_fa,meta],#{my=>meta}),
+ logger:log(Level,fun(x) -> {<<"~w: ~w ~w">>,[Level,fun_to_fa,meta]} end,
+ x, #{my=>meta}),
+ ok = check_logged(Level,<<"~w: ~w ~w">>,[Level,fun_to_fa,meta],#{my=>meta}),
+ logger:log(Level,fun(x) -> {'~w: ~w ~w',[Level,fun_to_fa,meta]} end,
+ x, #{my=>meta}),
+ ok = check_logged(Level,'~w: ~w ~w',[Level,fun_to_fa,meta],#{my=>meta}),
logger:log(Level,fun(x) -> #{Level=>fun_to_r,meta=>true} end,
x, #{my=>meta}),
ok = check_logged(Level,#{Level=>fun_to_r,meta=>true},#{my=>meta}),
@@ -1388,3 +1408,9 @@ check_config(crash) ->
erlang:error({badmatch,3});
check_config(_) ->
ok.
+
+%% this function is also a test. When logger.hrl used non-qualified
+%% apply/3 call, any module that was implementing apply/3 could
+%% not use any logging macro
+apply(_Any, _Any, _Any) ->
+ ok.
diff --git a/lib/kernel/test/logger_formatter_SUITE.erl b/lib/kernel/test/logger_formatter_SUITE.erl
index 8acd9126e5..8de13c6551 100644
--- a/lib/kernel/test/logger_formatter_SUITE.erl
+++ b/lib/kernel/test/logger_formatter_SUITE.erl
@@ -568,6 +568,11 @@ format_mfa(_Config) ->
ct:log(String4),
"othermfa" = String4,
+ Meta5 = #{mfa=>{'m o d','a\x{281}b',[' ']}},
+ String5 = format(info,{"~p",[term]},Meta5,#{template=>Template}),
+ ct:log(String5),
+ "'m o d':'a\x{281}b'/1" = String5,
+
ok.
format_time(_Config) ->
diff --git a/lib/kernel/test/logger_legacy_SUITE.erl b/lib/kernel/test/logger_legacy_SUITE.erl
index c3cab07d81..0e46ec3ee3 100644
--- a/lib/kernel/test/logger_legacy_SUITE.erl
+++ b/lib/kernel/test/logger_legacy_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -137,9 +137,9 @@ gen_event(_Config) ->
ok = gen_event:add_handler(Pid,?MODULE,gen_event),
Msg = fun() -> erlang:error({badmatch,b}) end,
Pid ! Msg,
- ?check({warning_msg,"** Undefined handle_info in ~tp"++_,[?MODULE,Msg]}),
+ ?check({warning_msg,"** Undefined handle_info in ~p"++_,[?MODULE,Msg]}),
gen_event:notify(Pid,Msg),
- ?check({error,"** gen_event handler ~p crashed."++_,
+ ?check({error,"** gen_event handler ~tp crashed."++_,
[?MODULE,Pid,Msg,gen_event,{{badmatch,b},_}]}).
gen_fsm(_Config) ->
diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl
index bb60e92e2d..3e15c205f0 100644
--- a/lib/kernel/test/logger_std_h_SUITE.erl
+++ b/lib/kernel/test/logger_std_h_SUITE.erl
@@ -118,6 +118,7 @@ all() ->
[add_remove_instance_tty,
add_remove_instance_standard_io,
add_remove_instance_standard_error,
+ add_remove_instance_custom_device,
add_remove_instance_file1,
add_remove_instance_file2,
add_remove_instance_file3,
@@ -175,6 +176,11 @@ add_remove_instance_standard_io(_Config) ->
add_remove_instance_standard_io(cleanup,_Config) ->
logger_std_h_remove().
+add_remove_instance_custom_device(_Config) ->
+ add_remove_instance_nofile({device,user}).
+add_remove_instance_custom_device(cleanup,_Config) ->
+ logger_std_h_remove().
+
add_remove_instance_standard_error(_Config) ->
add_remove_instance_nofile(standard_error).
add_remove_instance_standard_error(cleanup,_Config) ->
@@ -285,9 +291,16 @@ errors(Config) ->
_ ->
NoDir = lists:concat(["/",?MODULE,"_dir"]),
{error,
- {handler_not_added,{open_failed,NoDir,eacces}}} =
+ {handler_not_added,{open_failed,NoDir,Error}}} =
logger:add_handler(myh2,logger_std_h,
- #{config=>#{type=>{file,NoDir}}})
+ #{config=>#{type=>{file,NoDir}}}),
+ case Error of
+ erofs ->
+ %% Happens on OS X
+ ok;
+ eacces ->
+ ok
+ end
end,
{error,
@@ -305,6 +318,8 @@ formatter_fail(Config) ->
Dir = ?config(priv_dir,Config),
Log = filename:join(Dir,?FUNCTION_NAME),
+ logger:set_primary_config(level,notice),
+
%% no formatter
ok = logger:add_handler(?MODULE,
logger_std_h,
@@ -346,6 +361,7 @@ formatter_fail(Config) ->
ok.
formatter_fail(cleanup,_Config) ->
+ logger:set_primary_config(level,info),
logger:remove_handler(?MODULE).
config_fail(_Config) ->
@@ -1759,7 +1775,7 @@ rotation_opts_restart_handler(Config) ->
HConfig3#{config=>StdHConfig3#{max_no_bytes=>75,
max_no_files=>1,
compress_on_rotate=>true}}),
- timer:sleep(100),
+ timer:sleep(500),
{ok,#file_info{size=0}} = file:read_file_info(Log),
{ok,#file_info{size=29}} = file:read_file_info(Log++".0.gz"),
[_] = filelib:wildcard(Log++".*"),
@@ -2200,10 +2216,14 @@ check_tracer(T,TimeoutFun) ->
TimeoutFun()
end.
-escape([$+|Rest]) ->
- [$\\,$+|escape(Rest)];
-escape([H|T]) ->
- [H|escape(T)];
+escape([C|Rest]) ->
+ %% The characters that have to be escaped in a regex
+ case lists:member(C,"[-[\]{}()*+?.,\\^$|#\s]") of
+ true ->
+ [$\\,C|escape(Rest)];
+ false ->
+ [C|escape(Rest)]
+ end;
escape([]) ->
[].
diff --git a/lib/kernel/test/net_SUITE.erl b/lib/kernel/test/net_SUITE.erl
index bac31dc0ec..5e6ee054f7 100644
--- a/lib/kernel/test/net_SUITE.erl
+++ b/lib/kernel/test/net_SUITE.erl
@@ -68,6 +68,7 @@
-define(SLEEP(T), receive after T -> ok end).
-define(FAIL(R), exit(R)).
+-define(SKIP(R), throw({skip, R})).
-define(MINS(M), timer:minutes(M)).
-define(SECS(S), timer:seconds(S)).
@@ -131,18 +132,13 @@ api_basic_cases() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_per_suite(Config) ->
- %% We test on the socket module for simplicity
- case lists:member(socket, erlang:loaded()) of
- true ->
- case os:type() of
- {win32, _} ->
- not_yet_implemented();
- _ ->
- %% ?LOGGER:start(),
- Config
- end;
- false ->
- {skip, "esock disabled"}
+ try net:info() of
+ #{} ->
+ %% ?LOGGER:start(),
+ Config
+ catch
+ error : notsup ->
+ {skip, "esock not supported"}
end.
end_per_suite(_) ->
@@ -220,6 +216,7 @@ api_b_getifaddrs() ->
"~n ~p", [IfAddrs]),
ok;
{error, enotsup = Reason} ->
+ i("getifaddrs not supported - skipping"),
skip(Reason);
{error, Reason} ->
?FAIL(Reason)
@@ -254,6 +251,9 @@ api_b_name_and_addr_info() ->
Name;
{ok, BadNameInfo} ->
?FAIL({getnameinfo, SA, BadNameInfo});
+ {error, enotsup = ReasonNI} ->
+ i("getnameinfo not supported - skipping"),
+ ?SKIP({getnameinfo, ReasonNI});
{error, Reason1} ->
?FAIL({getnameinfo, SA, Reason1})
end,
@@ -264,6 +264,9 @@ api_b_name_and_addr_info() ->
verify_addr_info(AddrInfos, Domain);
{ok, BadAddrInfo} ->
?FAIL({getaddrinfo, Hostname, BadAddrInfo});
+ {error, enotsup = ReasonAI} ->
+ i("getaddrinfo not supported - skipping"),
+ ?SKIP({getaddrinfo, ReasonAI});
{error, Reason2} ->
?FAIL({getaddrinfo, Hostname, Reason2})
end.
@@ -316,6 +319,9 @@ api_b_name_and_index() ->
case net:if_names() of
{ok, N} when is_list(N) andalso (N =/= []) ->
N;
+ {error, enotsup = Reason} ->
+ i("if_names not supported - skipping"),
+ ?SKIP({if_names, Reason});
{error, Reason} ->
?FAIL({if_names, Reason})
end,
@@ -328,17 +334,23 @@ verify_if_names([{Index, Name}|T]) ->
{ok, Index} ->
ok;
{ok, BadIndex} ->
- ?FAIL({name2index, Name, Index, BadIndex});
- {error, ReasonN2I} ->
- ?FAIL({name2index, Name, ReasonN2I})
+ ?FAIL({if_name2index, Name, Index, BadIndex});
+ {error, enotsup = Reason_N2I1} ->
+ i("if_name2index not supported - skipping"),
+ ?SKIP({if_name2index, Reason_N2I1});
+ {error, Reason_N2I2} ->
+ ?FAIL({if_name2index, Name, Reason_N2I2})
end,
case net:if_index2name(Index) of
{ok, Name} ->
ok;
{ok, BadName} ->
- ?FAIL({index2name, Index, Name, BadName});
- {error, ReasonI2N} ->
- ?FAIL({index2name, Index, ReasonI2N})
+ ?FAIL({if_index2name, Index, Name, BadName});
+ {error, enotsup = Reason_I2N1} ->
+ i("if_index2name not supported - skipping"),
+ ?SKIP({if_index2name, Reason_I2N1});
+ {error, Reason_I2N2} ->
+ ?FAIL({if_index2name, Index, Reason_I2N2})
end,
verify_if_names(T).
@@ -346,20 +358,6 @@ verify_if_names([{Index, Name}|T]) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% local_host() ->
-%% try net_adm:localhost() of
-%% Host when is_list(Host) ->
-%% %% Convert to shortname if long
-%% case string:tokens(Host, [$.]) of
-%% [H|_] ->
-%% list_to_atom(H)
-%% end
-%% catch
-%% C:E:S ->
-%% erlang:raise(C, E, S)
-%% end.
-
-
%% This gets the local address (not 127.0...)
%% We should really implement this using the (new) net module,
%% but until that gets the necessary functionality...
@@ -510,8 +508,8 @@ f(F, A) ->
lists:flatten(io_lib:format(F, A)).
-%% i(F) ->
-%% i(F, []).
+i(F) ->
+ i(F, []).
i(F, A) ->
FStr = f("[~s] " ++ F, [formated_timestamp()|A]),
diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl
index 710b9b115c..e952dec625 100644
--- a/lib/kernel/test/os_SUITE.erl
+++ b/lib/kernel/test/os_SUITE.erl
@@ -324,14 +324,18 @@ close_stdin(Config) ->
"-1" = os:cmd(Fds).
max_size_command(_Config) ->
+ WSL = case os:getenv("WSLENV") of
+ false -> "";
+ _ -> "wsl "
+ end,
- Res20 = os:cmd("cat /dev/zero", #{ max_size => 20 }),
+ Res20 = os:cmd(WSL ++ "cat /dev/zero", #{ max_size => 20 }),
20 = length(Res20),
- Res0 = os:cmd("cat /dev/zero", #{ max_size => 0 }),
+ Res0 = os:cmd(WSL ++ "cat /dev/zero", #{ max_size => 0 }),
0 = length(Res0),
- Res32768 = os:cmd("cat /dev/zero", #{ max_size => 32768 }),
+ Res32768 = os:cmd(WSL ++ "cat /dev/zero", #{ max_size => 32768 }),
32768 = length(Res32768),
ResHello = string:trim(os:cmd("echo hello", #{ max_size => 20 })),
diff --git a/lib/kernel/test/os_SUITE_data/my_echo.c b/lib/kernel/test/os_SUITE_data/my_echo.c
index 712c828bb5..439f812af1 100644
--- a/lib/kernel/test/os_SUITE_data/my_echo.c
+++ b/lib/kernel/test/os_SUITE_data/my_echo.c
@@ -1,3 +1,4 @@
+#include <stdio.h>
#ifdef __WIN32__
#include <windows.h>
@@ -25,8 +26,6 @@ int wmain(int argc, wchar_t **argv)
}
#else
-#include <stdio.h>
-
int
main(int argc, char** argv)
{
diff --git a/lib/kernel/test/os_SUITE_data/my_fds.c b/lib/kernel/test/os_SUITE_data/my_fds.c
index 704a4d1e1d..8b1ce13822 100644
--- a/lib/kernel/test/os_SUITE_data/my_fds.c
+++ b/lib/kernel/test/os_SUITE_data/my_fds.c
@@ -1,5 +1,9 @@
#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
int
main(int argc, char** argv)
{
diff --git a/lib/kernel/test/pg_SUITE.erl b/lib/kernel/test/pg_SUITE.erl
new file mode 100644
index 0000000000..f03b8a6a39
--- /dev/null
+++ b/lib/kernel/test/pg_SUITE.erl
@@ -0,0 +1,793 @@
+%%
+%%
+%% Copyright WhatsApp Inc. and its affiliates. All rights reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%%-------------------------------------------------------------------
+%% @author Maxim Fedorov <maximfca@gmail.com>
+%% Process Groups smoke test.
+-module(pg_SUITE).
+-author("maximfca@gmail.com").
+
+%% Test server callbacks
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ stop_proc/1,
+ ensure_peers_info/2
+]).
+
+%% Test cases exports
+-export([
+ pg/0, pg/1,
+ errors/0, errors/1,
+ leave_exit_race/0, leave_exit_race/1,
+ dyn_distribution/0, dyn_distribution/1,
+ process_owner_check/0, process_owner_check/1,
+ overlay_missing/0, overlay_missing/1,
+ single/0, single/1,
+ two/1,
+ empty_group_by_remote_leave/0, empty_group_by_remote_leave/1,
+ thundering_herd/0, thundering_herd/1,
+ initial/1,
+ netsplit/1,
+ trisplit/1,
+ foursplit/1,
+ exchange/1,
+ nolocal/1,
+ double/1,
+ scope_restart/1,
+ missing_scope_join/1,
+ disconnected_start/1,
+ forced_sync/0, forced_sync/1,
+ group_leave/1
+]).
+
+-export([
+ control/1,
+ controller/3
+]).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
+
+suite() ->
+ [{timetrap, {seconds, 60}}].
+
+init_per_suite(Config) ->
+ case erlang:is_alive() of
+ false ->
+ %% verify epmd running (otherwise next call fails)
+ (erl_epmd:names("localhost") =:= {error, address}) andalso ([] = os:cmd("epmd -daemon")),
+ %% start a random node name
+ NodeName = list_to_atom(lists:concat([atom_to_list(?MODULE), "_", os:getpid()])),
+ {ok, Pid} = net_kernel:start([NodeName, shortnames]),
+ [{distribution, Pid} | Config];
+ true ->
+ Config
+ end.
+
+end_per_suite(Config) ->
+ is_pid(proplists:get_value(distribution, Config)) andalso net_kernel:stop().
+
+init_per_testcase(TestCase, Config) ->
+ {ok, _Pid} = pg:start_link(TestCase),
+ Config.
+
+end_per_testcase(TestCase, _Config) ->
+ gen_server:stop(TestCase),
+ ok.
+
+all() ->
+ [dyn_distribution, {group, basic}, {group, cluster}, {group, performance}].
+
+groups() ->
+ [
+ {basic, [parallel], [errors, pg, leave_exit_race, single, overlay_missing]},
+ {performance, [sequential], [thundering_herd]},
+ {cluster, [parallel], [process_owner_check, two, initial, netsplit, trisplit, foursplit,
+ exchange, nolocal, double, scope_restart, missing_scope_join, empty_group_by_remote_leave,
+ disconnected_start, forced_sync, group_leave]}
+ ].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+
+pg() ->
+ [{doc, "This test must be names pg, to stay inline with default scope"}].
+
+pg(_Config) ->
+ ?assertNotEqual(undefined, whereis(?FUNCTION_NAME)), %% ensure scope was started
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, self())),
+ ?assertEqual([self()], pg:get_local_members(?FUNCTION_NAME)),
+ ?assertEqual([?FUNCTION_NAME], pg:which_groups()),
+ ?assertEqual([?FUNCTION_NAME], pg:which_local_groups()),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, self())),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME)),
+ ?assertEqual([], pg:which_groups(?FUNCTION_NAME)),
+ ?assertEqual([], pg:which_local_groups(?FUNCTION_NAME)).
+
+errors() ->
+ [{doc, "Tests that errors are handled as expected, for example pg server crashes when it needs to"}].
+
+errors(_Config) ->
+ %% kill with 'info' and 'cast'
+ ?assertException(error, badarg, pg:handle_info(garbage, garbage)),
+ ?assertException(error, badarg, pg:handle_cast(garbage, garbage)),
+ %% kill with call
+ {ok, _Pid} = pg:start(second),
+ ?assertException(exit, {{badarg, _}, _}, gen_server:call(second, garbage, 100)).
+
+leave_exit_race() ->
+ [{doc, "Tests that pg correctly handles situation when leave and 'DOWN' messages are both in pg queue"}].
+
+leave_exit_race(Config) when is_list(Config) ->
+ process_flag(priority, high),
+ [
+ begin
+ Pid = spawn(fun () -> ok end),
+ pg:join(leave_exit_race, test, Pid),
+ pg:leave(leave_exit_race, test, Pid)
+ end
+ || _ <- lists:seq(1, 100)].
+
+single() ->
+ [{doc, "Tests single node groups"}, {timetrap, {seconds, 5}}].
+
+single(Config) when is_list(Config) ->
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, self())),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [self(), self()])),
+ ?assertEqual([self(), self(), self()], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual([self(), self(), self()], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual(not_joined, pg:leave(?FUNCTION_NAME, '$missing$', self())),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, ?FUNCTION_NAME, [self(), self()])),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, ?FUNCTION_NAME, self())),
+ ?assertEqual([], pg:which_groups(?FUNCTION_NAME)),
+ ?assertEqual([], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ %% double
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, self())),
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, Pid)),
+ Expected = lists:sort([Pid, self()]),
+ ?assertEqual(Expected, lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ ?assertEqual(Expected, lists:sort(pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+
+ stop_proc(Pid),
+ sync(?FUNCTION_NAME),
+ ?assertEqual([self()], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, ?FUNCTION_NAME, self())),
+ ok.
+
+dyn_distribution() ->
+ [{doc, "Tests that local node when distribution is started dynamically is not treated as remote node"}].
+
+dyn_distribution(Config) when is_list(Config) ->
+ %% When distribution is started or stopped dynamically,
+ %% there is a nodeup/nodedown message delivered to pg
+ %% It is possible but non-trivial to simulate this
+ %% behaviour with starting slave nodes being not
+ %% distributed, and calling net_kernel:start/1, however
+ %% the effect is still the same as simply sending nodeup,
+ %% which is also documented.
+ ?FUNCTION_NAME ! {nodeup, node()},
+ %%
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, self())),
+ ?assertEqual([self()], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ok.
+
+process_owner_check() ->
+ [{doc, "Tests that process owner is local node"}].
+
+process_owner_check(Config) when is_list(Config) ->
+ {TwoPeer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ %% spawn remote process
+ LocalPid = erlang:spawn(forever()),
+ RemotePid = erlang:spawn(TwoPeer, forever()),
+ %% check they can't be joined locally
+ ?assertException(error, {nolocal, _}, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid)),
+ ?assertException(error, {nolocal, _}, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [RemotePid, RemotePid])),
+ ?assertException(error, {nolocal, _}, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [LocalPid, RemotePid])),
+ %% check that non-pid also triggers error
+ ?assertException(error, function_clause, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, undefined)),
+ ?assertException(error, {nolocal, _}, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [undefined])),
+ %% stop the peer
+ stop_node(TwoPeer, Socket),
+ ok.
+
+overlay_missing() ->
+ [{doc, "Tests that scope process that is not a part of overlay network does not change state"}].
+
+overlay_missing(_Config) ->
+ {TwoPeer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ %% join self (sanity check)
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, group, self())),
+ %% remember pid from remote
+ PgPid = rpc:call(TwoPeer, erlang, whereis, [?FUNCTION_NAME]),
+ RemotePid = erlang:spawn(TwoPeer, forever()),
+ %% stop remote scope
+ gen_server:stop(PgPid),
+ %% craft white-box request: ensure it's rejected
+ ?FUNCTION_NAME ! {join, PgPid, group, RemotePid},
+ %% rejected!
+ ?assertEqual([self()], pg:get_members(?FUNCTION_NAME, group)),
+ %% ... reject leave too
+ ?FUNCTION_NAME ! {leave, PgPid, RemotePid, [group]},
+ ?assertEqual([self()], pg:get_members(?FUNCTION_NAME, group)),
+ %% join many times on remote
+ %RemotePids = [erlang:spawn(TwoPeer, forever()) || _ <- lists:seq(1, 1024)],
+ %?assertEqual(ok, rpc:call(TwoPeer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, Pid2])),
+ %% check they can't be joined locally
+ %?assertException(error, {nolocal, _}, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid)),
+ %?assertException(error, {nolocal, _}, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [RemotePid, RemotePid])),
+ %?assertException(error, {nolocal, _}, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [LocalPid, RemotePid])),
+ %% check that non-pid also triggers error
+ %?assertException(error, function_clause, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, undefined)),
+ %?assertException(error, {nolocal, _}, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [undefined])),
+ %% stop the peer
+ stop_node(TwoPeer, Socket).
+
+
+two(Config) when is_list(Config) ->
+ {TwoPeer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, Pid)),
+ ?assertEqual([Pid], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ %% first RPC must be serialised
+ sync({?FUNCTION_NAME, TwoPeer}),
+ ?assertEqual([Pid], rpc:call(TwoPeer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ ?assertEqual([], rpc:call(TwoPeer, pg, get_local_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ stop_proc(Pid),
+ %% again, must be serialised
+ sync(?FUNCTION_NAME),
+ ?assertEqual([], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ ?assertEqual([], rpc:call(TwoPeer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+
+ Pid2 = erlang:spawn(TwoPeer, forever()),
+ Pid3 = erlang:spawn(TwoPeer, forever()),
+ ?assertEqual(ok, rpc:call(TwoPeer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, Pid2])),
+ ?assertEqual(ok, rpc:call(TwoPeer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, Pid3])),
+ %% serialise through the *other* node
+ sync({?FUNCTION_NAME, TwoPeer}),
+ ?assertEqual(lists:sort([Pid2, Pid3]),
+ lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ %% stop the peer
+ stop_node(TwoPeer, Socket),
+ %% hope that 'nodedown' comes before we route our request
+ sync(?FUNCTION_NAME),
+ ok.
+
+empty_group_by_remote_leave() ->
+ [{doc, "Empty group should be deleted from nodes."}].
+
+empty_group_by_remote_leave(Config) when is_list(Config) ->
+ {TwoPeer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ RemoteNode = rpc:call(TwoPeer, erlang, whereis, [?FUNCTION_NAME]),
+ RemotePid = erlang:spawn(TwoPeer, forever()),
+ % remote join
+ ?assertEqual(ok, rpc:call(TwoPeer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ sync({?FUNCTION_NAME, TwoPeer}),
+ ?assertEqual([RemotePid], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ % inspecting internal state is not best practice, but there's no other way to check if the state is correct.
+ {state, _, _, #{RemoteNode := {_, RemoteMap}}} = sys:get_state(?FUNCTION_NAME),
+ ?assertEqual(#{?FUNCTION_NAME => [RemotePid]}, RemoteMap),
+ % remote leave
+ ?assertEqual(ok, rpc:call(TwoPeer, pg, leave, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ sync({?FUNCTION_NAME, TwoPeer}),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ {state, _, _, #{RemoteNode := {_, NewRemoteMap}}} = sys:get_state(?FUNCTION_NAME),
+ % empty group should be deleted.
+ ?assertEqual(#{}, NewRemoteMap),
+
+ stop_node(TwoPeer, Socket),
+ ok.
+
+thundering_herd() ->
+ [{doc, "Thousands of overlay network nodes sending sync to us, and we time out!"}, {timetrap, {seconds, 5}}].
+
+thundering_herd(Config) when is_list(Config) ->
+ GroupCount = 10000,
+ SyncCount = 2000,
+ %% make up a large amount of groups
+ [pg:join(?FUNCTION_NAME, {group, Seq}, self()) || Seq <- lists:seq(1, GroupCount)],
+ %% initiate a few syncs - and those are really slow...
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ PeerPid = erlang:spawn(Peer, forever()),
+ PeerPg = rpc:call(Peer, erlang, whereis, [?FUNCTION_NAME], 1000),
+ %% WARNING: code below acts for white-box! %% WARNING
+ FakeSync = [{{group, 1}, [PeerPid, PeerPid]}],
+ [gen_server:cast(?FUNCTION_NAME, {sync, PeerPg, FakeSync}) || _ <- lists:seq(1, SyncCount)],
+ %% next call must not timetrap, otherwise test fails
+ pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, self()),
+ stop_node(Peer, Socket).
+
+initial(Config) when is_list(Config) ->
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, Pid)),
+ ?assertEqual([Pid], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ %% first RPC must be serialised
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual([Pid], rpc:call(Peer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+
+ ?assertEqual([], rpc:call(Peer, pg, get_local_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ stop_proc(Pid),
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual([], rpc:call(Peer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ stop_node(Peer, Socket),
+ ok.
+
+netsplit(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ ?assertEqual(Peer, rpc(Socket, erlang, node, [])), %% just to test RPC
+ RemoteOldPid = erlang:spawn(Peer, forever()),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, '$invisible', RemoteOldPid])),
+ %% hohoho, partition!
+ disconnect_nodes([Peer]),
+ ?assertEqual(Peer, rpc(Socket, erlang, node, [])), %% just to ensure RPC still works
+ RemotePid = rpc(Socket, erlang, spawn, [forever()]),
+ ?assertEqual([], rpc(Socket, erlang, nodes, [])),
+ ?assertNot(lists:member(Peer, nodes())), %% should be no nodes in the cluster
+ ?assertEqual(ok, rpc(Socket, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])), %% join - in a partition!
+
+ ?assertEqual(ok, rpc(Socket, pg, leave, [?FUNCTION_NAME, '$invisible', RemoteOldPid])),
+ ?assertEqual(ok, rpc(Socket, pg, join, [?FUNCTION_NAME, '$visible', RemoteOldPid])),
+ ?assertEqual([RemoteOldPid], rpc(Socket, pg, get_local_members, [?FUNCTION_NAME, '$visible'])),
+ %% join locally too
+ LocalPid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, LocalPid)),
+
+ ?assertNot(lists:member(Peer, nodes())), %% should be no nodes in the cluster
+
+ pong = net_adm:ping(Peer),
+ %% now ensure sync happened
+ Pids = lists:sort([RemotePid, LocalPid]),
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual(Pids, lists:sort(rpc:call(Peer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME]))),
+ ?assertEqual([RemoteOldPid], pg:get_members(?FUNCTION_NAME, '$visible')),
+ stop_node(Peer, Socket),
+ ok.
+
+trisplit(Config) when is_list(Config) ->
+ {Peer, Socket1} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ _PeerPid1 = erlang:spawn(Peer, forever()),
+ PeerPid2 = erlang:spawn(Peer, forever()),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, three, PeerPid2])),
+ disconnect_nodes([Peer]),
+ ?assertEqual(true, net_kernel:connect_node(Peer)),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, one, PeerPid2])),
+ %% now ensure sync happened
+ {Peer2, Socket2} = spawn_node(?FUNCTION_NAME, trisplit_second),
+ ?assertEqual(true, rpc:call(Peer2, net_kernel, connect_node, [Peer])),
+ ?assertEqual(lists:sort([node(), Peer]), lists:sort(rpc:call(Peer2, erlang, nodes, []))),
+ ok = rpc:call(Peer2, ?MODULE, ensure_peers_info, [?FUNCTION_NAME, [node(), Peer]]),
+ ?assertEqual([PeerPid2], rpc:call(Peer2, pg, get_members, [?FUNCTION_NAME, one])),
+ stop_node(Peer, Socket1),
+ stop_node(Peer2, Socket2),
+ ok.
+
+foursplit(Config) when is_list(Config) ->
+ Pid = erlang:spawn(forever()),
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, one, Pid)),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, two, Pid)),
+ PeerPid1 = spawn(Peer, forever()),
+ ?assertEqual(ok, pg:leave(?FUNCTION_NAME, one, Pid)),
+ ?assertEqual(not_joined, pg:leave(?FUNCTION_NAME, three, Pid)),
+ disconnect_nodes([Peer]),
+ ?assertEqual(ok, rpc(Socket, ?MODULE, stop_proc, [PeerPid1])),
+ ?assertEqual(not_joined, pg:leave(?FUNCTION_NAME, three, Pid)),
+ ?assertEqual(true, net_kernel:connect_node(Peer)),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME, one)),
+ ?assertEqual([], rpc(Socket, pg, get_members, [?FUNCTION_NAME, one])),
+ stop_node(Peer, Socket),
+ ok.
+
+exchange(Config) when is_list(Config) ->
+ {Peer1, Socket1} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ {Peer2, Socket2} = spawn_node(?FUNCTION_NAME, exchange_second),
+ Pids10 = [rpc(Socket1, erlang, spawn, [forever()]) || _ <- lists:seq(1, 10)],
+ Pids2 = [rpc(Socket2, erlang, spawn, [forever()]) || _ <- lists:seq(1, 10)],
+ Pids11 = [rpc(Socket1, erlang, spawn, [forever()]) || _ <- lists:seq(1, 10)],
+ %% kill first 3 pids from node1
+ {PidsToKill, Pids1} = lists:split(3, Pids10),
+
+ ?assertEqual(ok, rpc(Socket1, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, Pids10])),
+ sync({?FUNCTION_NAME, Peer1}), %% Join broadcast have reached local
+ sync(?FUNCTION_NAME), %% Join broadcast has been processed by local
+ ?assertEqual(lists:sort(Pids10), lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ [rpc(Socket1, ?MODULE, stop_proc, [Pid]) || Pid <- PidsToKill],
+ sync(?FUNCTION_NAME),
+ sync({?FUNCTION_NAME, Peer1}),
+
+ Pids = lists:sort(Pids1 ++ Pids2 ++ Pids11),
+ ?assert(lists:all(fun erlang:is_pid/1, Pids)),
+
+ disconnect_nodes([Peer1, Peer2]),
+
+ sync(?FUNCTION_NAME), %% Processed nodedowns...
+ ?assertEqual([], lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+
+ [?assertEqual(ok, rpc(Socket2, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, Pid])) || Pid <- Pids2],
+ [?assertEqual(ok, rpc(Socket1, pg, join, [?FUNCTION_NAME, second, Pid])) || Pid <- Pids11],
+ ?assertEqual(ok, rpc(Socket1, pg, join, [?FUNCTION_NAME, third, Pids11])),
+ %% rejoin
+ ?assertEqual(true, net_kernel:connect_node(Peer1)),
+ ?assertEqual(true, net_kernel:connect_node(Peer2)),
+ %% need to sleep longer to ensure both nodes made the exchange
+ ensure_peers_info(?FUNCTION_NAME, [Peer1, Peer2]),
+ ?assertEqual(Pids, lists:sort(pg:get_members(?FUNCTION_NAME, second) ++ pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ ?assertEqual(lists:sort(Pids11), lists:sort(pg:get_members(?FUNCTION_NAME, third))),
+
+ {Left, Stay} = lists:split(3, Pids11),
+ ?assertEqual(ok, rpc(Socket1, pg, leave, [?FUNCTION_NAME, third, Left])),
+ sync({?FUNCTION_NAME, Peer1}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual(lists:sort(Stay), lists:sort(pg:get_members(?FUNCTION_NAME, third))),
+ ?assertEqual(not_joined, rpc(Socket1, pg, leave, [?FUNCTION_NAME, left, Stay])),
+ ?assertEqual(ok, rpc(Socket1, pg, leave, [?FUNCTION_NAME, third, Stay])),
+ sync({?FUNCTION_NAME, Peer1}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual([], lists:sort(pg:get_members(?FUNCTION_NAME, third))),
+ sync({?FUNCTION_NAME, Peer1}),
+ sync(?FUNCTION_NAME),
+
+ stop_node(Peer1, Socket1),
+ stop_node(Peer2, Socket2),
+ ok.
+
+nolocal(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ RemotePid = spawn(Peer, forever()),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ ?assertEqual([], pg:get_local_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ stop_node(Peer, Socket),
+ ok.
+
+double(Config) when is_list(Config) ->
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, Pid)),
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [Pid])),
+ ?assertEqual([Pid, Pid], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ sync(?FUNCTION_NAME),
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual([Pid, Pid], rpc:call(Peer, pg, get_members, [?FUNCTION_NAME, ?FUNCTION_NAME])),
+ stop_node(Peer, Socket),
+ ok.
+
+scope_restart(Config) when is_list(Config) ->
+ Pid = erlang:spawn(forever()),
+ ?assertEqual(ok, pg:join(?FUNCTION_NAME, ?FUNCTION_NAME, [Pid, Pid])),
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ RemotePid = spawn(Peer, forever()),
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ sync({?FUNCTION_NAME, Peer}),
+ ?assertEqual(lists:sort([RemotePid, Pid, Pid]), lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ %% stop scope locally, and restart
+ gen_server:stop(?FUNCTION_NAME),
+ pg:start(?FUNCTION_NAME),
+ %% ensure remote pids joined, local are missing
+ sync(?FUNCTION_NAME),
+ sync({?FUNCTION_NAME, Peer}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual([RemotePid], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+ stop_node(Peer, Socket),
+ ok.
+
+missing_scope_join(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ ?assertEqual(ok, rpc:call(Peer, gen_server, stop, [?FUNCTION_NAME])),
+ RemotePid = spawn(Peer, forever()),
+ ?assertMatch({badrpc, {'EXIT', {noproc, _}}}, rpc:call(Peer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ ?assertMatch({badrpc, {'EXIT', {noproc, _}}}, rpc:call(Peer, pg, leave, [?FUNCTION_NAME, ?FUNCTION_NAME, RemotePid])),
+ stop_node(Peer, Socket),
+ ok.
+
+disconnected_start(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ disconnect_nodes([Peer]),
+ ?assertEqual(ok, rpc(Socket, gen_server, stop, [?FUNCTION_NAME])),
+ ?assertMatch({ok, _Pid}, rpc(Socket, pg, start,[?FUNCTION_NAME])),
+ ?assertEqual(ok, rpc(Socket, gen_server, stop, [?FUNCTION_NAME])),
+ RemotePid = rpc(Socket, erlang, spawn, [forever()]),
+ ?assert(is_pid(RemotePid)),
+ stop_node(Peer, Socket),
+ ok.
+
+forced_sync() ->
+ [{doc, "This test was added when lookup_element was erroneously used instead of lookup, crashing pg with badmatch, and it tests rare out-of-order sync operations"}].
+
+forced_sync(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ Pid = erlang:spawn(forever()),
+ RemotePid = spawn(Peer, forever()),
+ Expected = lists:sort([Pid, RemotePid]),
+ pg:join(?FUNCTION_NAME, one, Pid),
+
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, one, RemotePid])),
+ RemoteScopePid = rpc:call(Peer, erlang, whereis, [?FUNCTION_NAME]),
+ ?assert(is_pid(RemoteScopePid)),
+ %% hohoho, partition!
+ disconnect_nodes([Peer]),
+ ?assertEqual(true, net_kernel:connect_node(Peer)),
+ ensure_peers_info(?FUNCTION_NAME, [Peer]),
+ ?assertEqual(Expected, lists:sort(pg:get_members(?FUNCTION_NAME, one))),
+ %% WARNING: this code uses pg as white-box, exploiting internals,
+ %% only to simulate broken 'sync'
+ %% Fake Groups: one should disappear, one should be replaced, one stays
+ %% This tests handle_sync function.
+ FakeGroups = [{one, [RemotePid, RemotePid]}, {?FUNCTION_NAME, [RemotePid, RemotePid]}],
+ gen_server:cast(?FUNCTION_NAME, {sync, RemoteScopePid, FakeGroups}),
+ %% ensure it is broken well enough
+ sync(?FUNCTION_NAME),
+ ?assertEqual(lists:sort([RemotePid, RemotePid]), lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ ?assertEqual(lists:sort([RemotePid, RemotePid, Pid]), lists:sort(pg:get_members(?FUNCTION_NAME, one))),
+ %% simulate force-sync via 'discover' - ask peer to send sync to us
+ {?FUNCTION_NAME, Peer} ! {discover, whereis(?FUNCTION_NAME)},
+ sync({?FUNCTION_NAME, Peer}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual(Expected, lists:sort(pg:get_members(?FUNCTION_NAME, one))),
+ ?assertEqual([], lists:sort(pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME))),
+ %% and simulate extra sync
+ sync({?FUNCTION_NAME, Peer}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual(Expected, lists:sort(pg:get_members(?FUNCTION_NAME, one))),
+
+ stop_node(Peer, Socket),
+ ok.
+
+group_leave(Config) when is_list(Config) ->
+ {Peer, Socket} = spawn_node(?FUNCTION_NAME, ?FUNCTION_NAME),
+ RemotePid = erlang:spawn(Peer, forever()),
+ Total = lists:duplicate(16, RemotePid),
+ {Left, Remain} = lists:split(4, Total),
+ %% join 16 times!
+ ?assertEqual(ok, rpc:call(Peer, pg, join, [?FUNCTION_NAME, two, Total])),
+ ?assertEqual(ok, rpc:call(Peer, pg, leave, [?FUNCTION_NAME, two, Left])),
+
+ sync({?FUNCTION_NAME, Peer}),
+ sync(?FUNCTION_NAME),
+ ?assertEqual(Remain, pg:get_members(?FUNCTION_NAME, two)),
+ stop_node(Peer, Socket),
+ sync(?FUNCTION_NAME),
+ ?assertEqual([], pg:get_members(?FUNCTION_NAME, two)),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Test Helpers - start/stop additional Erlang nodes
+
+sync(GS) ->
+ _ = sys:log(GS, get).
+
+ensure_peers_info(Scope, Peers) ->
+ %% Ensures that pg server on local node has gotten info from
+ %% pg servers on all Peer nodes passed as argument (assuming
+ %% no connection failures).
+ %%
+ %% This function assumes that all nodeup messages has been
+ %% delivered to all local recipients (pg server) when called.
+ %%
+ %% Note that this relies on current ERTS implementation; not
+ %% language guarantees.
+ %%
+
+ sync(Scope),
+ %% Known: nodup handled and discover sent to Peer
+
+ lists:foreach(fun (Peer) -> sync({Scope, Peer}) end, Peers),
+ %% Known: nodeup handled by Peers and discover sent to local
+ %% Known: discover received/handled by Peers and sync sent to local
+ %% Known: discover received from Peer
+ %% Known: sync received from Peer
+
+ sync(Scope),
+ %% Known: discover handled from Peers and sync sent to Peers
+ %% Known: sync from Peers handled
+ ok.
+
+-ifdef(CURRENTLY_UNUSED_BUT_SERVES_AS_DOC).
+
+ensure_synced(Scope, Peers) ->
+ %% Ensures that the pg server on local node have synced
+ %% with pg servers on all Peer nodes (assuming no connection
+ %% failures).
+ %%
+ %% This function assumes that all nodeup messages has been
+ %% delivered to all local recipients (pg server) when called.
+ %%
+ %% Note that this relies on current ERTS implementation; not
+ %% language guarantees.
+ %%
+ ensure_peer_info(Scope, Peer),
+ %% Known: local has gotten info from all Peers
+ %% Known: discover from Peers handled and sync sent to Peers
+ lists:foreach(fun (Peer) -> sync({Scope, Peer}) end, Peers),
+ %% Known: sync from local handled by Peers
+ ok.
+
+-endif.
+
+disconnect_nodes(Nodes) ->
+ %% The following is not a language guarantee, but internal
+ %% knowledged about current implementation of ERTS and pg.
+ %%
+ %% The pg server reacts on 'DOWN's via process monitors of
+ %% its peers. These are delivered before 'nodedown's from
+ %% net_kernel:monitor_nodes(). That is, by waiting for
+ %% 'nodedown' from net_kernel:monitor_nodes() we know that
+ %% the 'DOWN' has been delivered to the pg server.
+ %%
+ %% We do this in a separate process to avoid stray
+ %% nodeup/nodedown messages in the test process after
+ %% the operation...
+ F = fun () ->
+ ok = net_kernel:monitor_nodes(true),
+ lists:foreach(fun (Node) ->
+ true = erlang:disconnect_node(Node)
+ end,
+ Nodes),
+ lists:foreach(fun (Node) ->
+ receive {nodedown, Node} -> ok end
+ end,
+ Nodes)
+ end,
+ {Pid, Mon} = spawn_monitor(F),
+ receive
+ {'DOWN', Mon, process, Pid, Reason} ->
+ normal = Reason
+ end,
+ ok.
+
+-define (LOCALHOST, {127, 0, 0, 1}).
+
+%% @doc Kills process Pid and waits for it to exit using monitor,
+%% and yields after (for 1 ms).
+-spec stop_proc(pid()) -> ok.
+stop_proc(Pid) ->
+ monitor(process, Pid),
+ erlang:exit(Pid, kill),
+ receive
+ {'DOWN', _MRef, process, Pid, _Info} ->
+ timer:sleep(1)
+ end.
+
+%% @doc Executes remote call on the node via TCP socket
+%% Used when dist connection is not available, or
+%% when it's undesirable to use one.
+-spec rpc(gen_tcp:socket(), module(), atom(), [term()]) -> term().
+rpc(Sock, M, F, A) ->
+ ok = gen_tcp:send(Sock, term_to_binary({call, M, F, A})),
+ inet:setopts(Sock, [{active, once}]),
+ receive
+ {tcp, Sock, Data} ->
+ case binary_to_term(Data) of
+ {ok, Val} ->
+ Val;
+ {error, Error} ->
+ {badrpc, Error}
+ end;
+ {tcp_closed, Sock} ->
+ error(closed)
+ end.
+
+%% @doc starts peer node on this host.
+%% Returns spawned node name, and a gen_tcp socket to talk to it using ?MODULE:rpc.
+-spec spawn_node(Scope :: atom(), Node :: atom()) -> {node(), gen_tcp:socket()}.
+spawn_node(Scope, Name) ->
+ Self = self(),
+ Controller = erlang:spawn(?MODULE, controller, [Name, Scope, Self]),
+ receive
+ {'$node_started', Node, Port} ->
+ {ok, Socket} = gen_tcp:connect(?LOCALHOST, Port, [{active, false}, {mode, binary}, {packet, 4}]),
+ Controller ! {socket, Socket},
+ {Node, Socket};
+ Other ->
+ error({start_node, Name, Other})
+ after 60000 ->
+ error({start_node, Name, timeout})
+ end.
+
+%% @private
+-spec controller(atom(), atom(), pid()) -> ok.
+controller(Name, Scope, Self) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ Pa2 = filename:dirname(code:which(pg)),
+ Args = lists:concat(["-setcookie ", erlang:get_cookie(),
+ "-connect_all false -kernel dist_auto_connect never -noshell -pa ", Pa, " -pa ", Pa2]),
+ {ok, Node} = test_server:start_node(Name, peer, [{args, Args}]),
+ case rpc:call(Node, ?MODULE, control, [Scope], 5000) of
+ {badrpc, nodedown} ->
+ Self ! {badrpc, Node},
+ ok;
+ {Port, _PgPid} ->
+ Self ! {'$node_started', Node, Port},
+ controller_wait()
+ end.
+
+controller_wait() ->
+ Port =
+ receive
+ {socket, Port0} ->
+ Port0
+ end,
+ MRef = monitor(port, Port),
+ receive
+ {'DOWN', MRef, port, Port, _Info} ->
+ ok
+ end.
+
+%% @doc Stops the node previously started with spawn_node,
+%% and also closes the RPC socket.
+-spec stop_node(node(), gen_tcp:socket()) -> true.
+stop_node(Node, Socket) when Node =/= node() ->
+ true = test_server:stop_node(Node),
+ Socket =/= undefined andalso gen_tcp:close(Socket),
+ true.
+
+forever() ->
+ fun() -> receive after infinity -> ok end end.
+
+
+-spec control(Scope :: atom()) -> {Port :: integer(), pid()}.
+control(Scope) ->
+ Control = self(),
+ erlang:spawn(fun () -> server(Control, Scope) end),
+ receive
+ {port, Port, PgPid} ->
+ {Port, PgPid};
+ Other ->
+ error({error, Other})
+ end.
+
+server(Control, Scope) ->
+ try
+ {ok, Pid} = if Scope =:= undefined -> {ok, undefined}; true -> pg:start(Scope) end,
+ {ok, Listen} = gen_tcp:listen(0, [{mode, binary}, {packet, 4}, {ip, ?LOCALHOST}]),
+ {ok, Port} = inet:port(Listen),
+ Control ! {port, Port, Pid},
+ {ok, Sock} = gen_tcp:accept(Listen),
+ server_loop(Sock)
+ catch
+ Class:Reason:Stack ->
+ Control ! {error, {Class, Reason, Stack}}
+ end.
+
+server_loop(Sock) ->
+ inet:setopts(Sock, [{active, once}]),
+ receive
+ {tcp, Sock, Data} ->
+ {call, M, F, A} = binary_to_term(Data),
+ Ret =
+ try
+ erlang:apply(M, F, A) of
+ Res ->
+ {ok, Res}
+ catch
+ exit:Reason ->
+ {error, {'EXIT', Reason}};
+ error:Reason ->
+ {error, {'EXIT', Reason}}
+ end,
+ ok = gen_tcp:send(Sock, term_to_binary(Ret)),
+ server_loop(Sock);
+ {tcp_closed, Sock} ->
+ erlang:halt(1)
+ end.
diff --git a/lib/kernel/test/prim_file_SUITE.erl b/lib/kernel/test/prim_file_SUITE.erl
index 2f465a15bc..11c3bb15ae 100644
--- a/lib/kernel/test/prim_file_SUITE.erl
+++ b/lib/kernel/test/prim_file_SUITE.erl
@@ -30,7 +30,8 @@
file_read_file_info_opts/1, file_write_file_info_opts/1,
file_write_read_file_info_opts/1]).
-export([rename/1, access/1, truncate/1, datasync/1, sync/1,
- read_write/1, pread_write/1, append/1, exclusive/1]).
+ read_write/1, pread_write/1, append/1, exclusive/1,
+ read_file_rename_race/1]).
-export([e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
-export([make_link/1, read_link_info_for_non_link/1,
@@ -67,7 +68,7 @@ groups() ->
truncate, sync, datasync, advise, large_write, allocate]},
{open, [],
[open1, modes, close, access, read_write, pread_write,
- append, exclusive]},
+ append, exclusive, read_file_rename_race]},
{pos, [], [pos1, pos2]},
{file_info, [],
[file_info_basic_file,file_info_basic_directory, file_info_bad,
@@ -568,6 +569,60 @@ exclusive(Config) when is_list(Config) ->
ok = ?PRIM_FILE:close(Fd),
ok.
+%% Test read_file with concurrent renames and size changes.
+
+-define(RFRR_DATA1, <<"gazonk">>).
+-define(RFRR_DATA2, <<"fubar">>). % shorter than and not a prefix of DATA1
+
+read_file_rename_race(Config) when is_list(Config) ->
+ %% This test reportedly fails on Windows and Darwin 9.8.0.
+ Supported =
+ case os:type() of
+ {win32, _} -> false;
+ {unix, darwin} -> os:version() > {9,8,0};
+ {unix, _} -> true
+ end,
+ if Supported ->
+ Dir = proplists:get_value(priv_dir, Config),
+ Name = filename:join(Dir, "filename"),
+ rfrr_write_file(Name, ?RFRR_DATA2),
+ Mutator = spawn_link(fun() -> rfrr_mutator(Name, ?RFRR_DATA1, ?RFRR_DATA2) end),
+ Result = rfrr_reader(Name, 1, _N = 5000),
+ unlink(Mutator),
+ exit(Mutator, kill),
+ ok = Result;
+ true ->
+ {skipped, "Not supported on Windows, or Darwin =< 9.8.0"}
+ end.
+
+rfrr_reader(Name, I, N) when I < N ->
+ case rfrr_read_file(Name, I) of
+ ok -> rfrr_reader(Name, I + 1, N);
+ error -> error
+ end;
+rfrr_reader(_Name, _I, _N) -> ok.
+
+rfrr_read_file(Name, I) ->
+ case prim_file:read_file(Name) of
+ {ok, ?RFRR_DATA1} -> ok;
+ {ok, ?RFRR_DATA2} -> ok;
+ Other ->
+ io:format(standard_error, "rfrr_read_file #~p got ~p\n", [I, Other]),
+ error
+ end.
+
+%% Correctness of read_file must not depend on the mutator using the
+%% file server in the reader's Erlang VM, so we use prim_file here.
+rfrr_mutator(Name, Data1, Data2) ->
+ rfrr_write_file(Name, Data1),
+ rfrr_mutator(Name, Data2, Data1).
+
+%% Atomically replace Name with a new file containing Data.
+rfrr_write_file(Name, Data) ->
+ NameTmp = Name ++ ".tmp", % must be in same volume as Name
+ ok = prim_file:write_file(NameTmp, Data),
+ ok = prim_file:rename(NameTmp, Name).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/kernel/test/rpc_SUITE.erl b/lib/kernel/test/rpc_SUITE.erl
index a89a7600a2..0012216638 100644
--- a/lib/kernel/test/rpc_SUITE.erl
+++ b/lib/kernel/test/rpc_SUITE.erl
@@ -22,13 +22,26 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
-export([off_heap/1,
- call/1, block_call/1, multicall/1, multicall_timeout/1,
- multicall_dies/1, multicall_node_dies/1,
- called_dies/1, called_node_dies/1,
- called_throws/1, call_benchmark/1, async_call/1]).
+ call/1, call_reqtmo/1, block_call/1, multicall/1,
+ multicall_timeout/1, multicall_reqtmo/1, multicall_dies/1,
+ multicall_node_dies/1, called_dies/1, called_node_dies/1,
+ called_throws/1, call_benchmark/1, async_call/1,
+ call_against_old_node/1,
+ multicall_mix/1,
+ timeout_limit/1,
+ call_old_against_new/1,
+ multicall_old_against_new/1,
+ cast_old_against_new/1]).
+-export([init_per_testcase/2, end_per_testcase/2]).
+
+-export([call_func1/1]).
-export([suicide/2, suicide/3, f/0, f2/0]).
+-export([call_old_against_new_test/2]).
+-export([multicall_old_against_new_test/2]).
+-export([cast_old_against_new_test/2]).
+
-include_lib("common_test/include/ct.hrl").
suite() ->
@@ -36,14 +49,23 @@ suite() ->
{timetrap,{minutes,2}}].
all() ->
- [off_heap, call, block_call, multicall, multicall_timeout,
- multicall_dies, multicall_node_dies, called_dies,
- called_node_dies, called_throws, call_benchmark,
- async_call].
+ [off_heap, call, call_reqtmo, block_call, multicall,
+ multicall_timeout, call_reqtmo, multicall_dies,
+ multicall_node_dies, called_dies, called_node_dies,
+ called_throws, call_benchmark, async_call,
+ call_against_old_node,
+ multicall_mix, timeout_limit, call_old_against_new,
+ multicall_old_against_new, cast_old_against_new].
groups() ->
[].
+init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
+ [{testcase, Func}|Config].
+
+end_per_testcase(_Func, _Config) ->
+ ok.
+
init_per_suite(Config) ->
Config.
@@ -84,12 +106,65 @@ call(Config) when is_list(Config) ->
receive after 6000 -> ok end,
[] = flush([]),
{hej,_,N4} = rpc:call(N4, ?MODULE, f, []),
+ {badrpc, nodedown} = rpc:call(gurka, ?MODULE, f, []),
test_server:stop_node(N1),
test_server:stop_node(N2),
test_server:stop_node(N3),
test_server:stop_node(N4),
ok.
+
+call_reqtmo(Config) when is_list(Config) ->
+ Fun = fun (Node, SendMe, Timeout) ->
+ {badrpc, timeout} = rpc:call(Node, erlang, send,
+ [self(), SendMe],
+ Timeout)
+ end,
+ reqtmo_test(Config, Fun).
+
+reqtmo_test(Config, Test) ->
+ %% Tests that we time out in time also when the request itself
+ %% does not get through. A typical issue we have had
+ %% in the past, is that the timeout has not triggered until
+ %% the request has gotten through...
+
+ Timeout = 500,
+ WaitBlock = 100,
+ BlockTime = 1000,
+
+ {ok, Node} = start_node(Config),
+
+ erpc:call(Node, erts_debug, set_internal_state, [available_internal_state,
+ true]),
+
+ SendMe = make_ref(),
+
+ erpc:cast(Node, erts_debug, set_internal_state, [block, BlockTime]),
+ receive after WaitBlock -> ok end,
+
+ Start = erlang:monotonic_time(),
+
+ Test(Node, SendMe, Timeout),
+
+ Stop = erlang:monotonic_time(),
+ Time = erlang:convert_time_unit(Stop-Start, native, millisecond),
+ io:format("Actual time: ~p ms~n", [Time]),
+ true = Time >= Timeout,
+ true = Time =< Timeout + 200,
+
+ receive SendMe -> ok end,
+
+ receive UnexpectedMsg -> ct:fail({unexpected_message, UnexpectedMsg})
+ after 0 -> ok
+ end,
+
+ stop_node(Node),
+
+ {comment,
+ "Timeout = " ++ integer_to_list(Timeout)
+ ++ " Actual = " ++ integer_to_list(Time)}.
+
+
%% Test different rpc calls.
block_call(Config) when is_list(Config) ->
PA = filename:dirname(code:which(?MODULE)),
@@ -156,6 +231,30 @@ multicall_timeout(Config) when is_list(Config) ->
test_server:stop_node(N4),
ok.
+multicall_reqtmo(Config) when is_list(Config) ->
+ {ok, QuickNode1} = start_node(Config),
+ {ok, QuickNode2} = start_node(Config),
+ Fun = fun (Node, SendMe, Timeout) ->
+ Me = self(),
+ SlowSend = fun () ->
+ if node() == Node ->
+ Me ! SendMe,
+ done;
+ true ->
+ done
+ end
+ end,
+ {[done, done], [Node]}
+ = rpc:multicall([QuickNode1, Node, QuickNode2],
+ erlang, apply, [SlowSend, []],
+ Timeout)
+ end,
+ Res = reqtmo_test(Config, Fun),
+ stop_node(QuickNode1),
+ stop_node(QuickNode2),
+ Res.
+
+
multicall_dies(Config) when is_list(Config) ->
PA = filename:dirname(code:which(?MODULE)),
{ok, N1} = test_server:start_node('rpc_SUITE_multicall_dies_1', slave,
@@ -218,13 +317,23 @@ do_multicall_2_nodes_dies(Mod, Func, Args) ->
{ok, N2} = test_server:start_node('rcp_SUITE_multicall_node_dies_2', slave,
[{args, "-pa " ++ PA}]),
Nodes = [N1, N2],
- {[], Nodes} = rpc:multicall(Nodes, Mod, Func, Args),
+ case {Mod, Func, rpc:multicall(Nodes, Mod, Func, Args)} of
+ {_, _, {[], Nodes}} ->
+ ok;
+ {init, stop, {OkNs, ErrNs}} ->
+ %% The killed reason might reach us before the nodedown...
+ Killed = {badrpc, {'EXIT', killed}},
+ case length(ErrNs) of
+ 1 -> [Killed] = OkNs;
+ 0 -> [Killed, Killed] = OkNs
+ end
+ end,
+
Msgs = flush([]),
[] = Msgs,
ok.
-
%% OTP-3766.
called_dies(Config) when is_list(Config) ->
PA = filename:dirname(code:which(?MODULE)),
@@ -254,20 +363,23 @@ called_dies(Config) when is_list(Config) ->
%%
TrapExit = process_flag(trap_exit, true),
%%
- rep(fun (Tag, Call, Args=[Node|_]) when Node == node() ->
+ rep(fun (Tag, call, Args=[Node|_]) when Node == node() ->
{Tag,timeout} =
- {Tag,apply(rpc, Call, Args)},
+ {Tag,apply(rpc, call, Args)},
{Tag,flush,[{'EXIT',_,normal}]} =
{Tag,flush,flush([])};
(Tag, Call, Args) ->
{Tag,timeout} =
{Tag,apply(rpc, Call, Args)}
end, N, ?MODULE, suicide, [link,normal]),
- rep(fun (Tag, Call, Args=[Node|_]) when Node == node() ->
+ rep(fun (Tag, call, Args=[Node|_]) when Node == node() ->
{Tag,timeout} =
- {Tag,apply(rpc, Call, Args)},
+ {Tag,apply(rpc, call, Args)},
{Tag,flush,[{'EXIT',_,abnormal}]} =
{Tag,flush,flush([])};
+ (Tag, Call, Args=[Node|_]) when Node == node() ->
+ {Tag,timeout} =
+ {Tag,apply(rpc, Call, Args)};
(Tag, block_call, Args) ->
{Tag,timeout} =
{Tag,apply(rpc, block_call, Args)};
@@ -275,20 +387,23 @@ called_dies(Config) when is_list(Config) ->
{Tag,{badrpc,{'EXIT',abnormal}}} =
{Tag,apply(rpc, Call, Args)}
end, N, ?MODULE, suicide, [link,abnormal]),
- rep(fun (Tag, Call, Args=[Node|_]) when Node == node() ->
+ rep(fun (Tag, call, Args=[Node|_]) when Node == node() ->
{Tag,timeout} =
- {Tag,apply(rpc, Call, Args)},
+ {Tag,apply(rpc, call, Args)},
{Tag,flush,[{'EXIT',_,normal}]} =
{Tag,flush,flush([])};
(Tag, Call, Args) ->
{Tag,timeout} =
{Tag,apply(rpc, Call, Args)}
end, N, ?MODULE, suicide, [exit,normal]),
- rep(fun (Tag, Call, Args=[Node|_]) when Node == node() ->
+ rep(fun (Tag, call, Args=[Node|_]) when Node == node() ->
{Tag,timeout} =
- {Tag,apply(rpc, Call, Args)},
+ {Tag,apply(rpc, call, Args)},
{Tag,flush,[{'EXIT',_,abnormal}]} =
{Tag,flush,flush([])};
+ (Tag, Call, Args=[Node|_]) when Node == node() ->
+ {Tag,timeout} =
+ {Tag,apply(rpc, Call, Args)};
(Tag, block_call, Args) ->
{Tag,timeout} =
{Tag,apply(rpc, block_call, Args)};
@@ -370,8 +485,10 @@ called_node_dies(Config) when is_list(Config) ->
PA, ?MODULE, suicide, [init,stop,[]]),
node_rep(
- fun (Call, Args=[_|_]) ->
- {badrpc,{'EXIT',{killed,_}}} = apply(rpc, Call, Args)
+ fun (block_call, Args=[_|_]) ->
+ {badrpc,{'EXIT',{killed,_}}} = apply(rpc, block_call, Args);
+ (call, Args=[_|_]) ->
+ {badrpc,nodedown} = apply(rpc, call, Args)
end, "rpc_SUITE_called_node_dies_3",
PA, ?MODULE, suicide, [erlang,exit,[rex,kill]]),
@@ -380,7 +497,7 @@ called_node_dies(Config) when is_list(Config) ->
%% Cannot block call rpc - will hang
ok;
(Call, Args=[_|_]) ->
- {badrpc,{'EXIT',{normal,_}}} = apply(rpc, Call, Args)
+ {badrpc,nodedown} = apply(rpc, Call, Args)
end, "rpc_SUITE_called_node_dies_4",
PA, ?MODULE, suicide, [rpc,stop,[]]),
@@ -474,10 +591,319 @@ async_call(Config) when is_list(Config) ->
ok.
+call_against_old_node(Config) ->
+ case start_22_node(Config) of
+ {ok, Node22} ->
+ Node22 = rpc:call(Node22, erlang, node, []),
+ stop_node(Node22),
+ ok;
+ _ ->
+ {skipped, "No OTP 22 available"}
+ end.
+
+multicall_mix(Config) ->
+ {ok, Node1} = start_node(Config),
+ {ok, Node2} = start_node(Config),
+ {Node3, OldNodeTest} = case start_22_node(Config) of
+ {ok, N3} ->
+ {N3, true};
+ _ ->
+ {ok, N3} = start_node(Config),
+ {N3, false}
+ end,
+ {ok, Node4} = start_node(Config),
+ {ok, Node5} = start_node(Config),
+ stop_node(Node2),
+
+ [] = flush([]),
+
+ ThisNode = node(),
+ Nodes = [ThisNode, Node1, Node2, Node3, Node4, Node5],
+
+ {[ThisNode,
+ Node1,
+ Node3,
+ Node4,
+ Node5],
+ [Node2]}
+ = rpc:multicall(Nodes, erlang, node, []),
+
+ [] = flush([]),
+
+ {[Node5,
+ ThisNode,
+ Node1,
+ Node3,
+ Node4,
+ Node5,
+ Node1,
+ ThisNode],
+ [Node2]}
+ = rpc:multicall([Node5|Nodes]++[Node1, ThisNode], erlang, node, []),
+
+ [] = flush([]),
+
+ {[BlingError,
+ BlingError,
+ {badrpc, {'EXIT', _}},
+ BlingError,
+ BlingError],
+ [Node2]}
+ = rpc:multicall(Nodes, ?MODULE, call_func1, [bling]),
+
+ [] = flush([]),
+
+ {badrpc, {'EXIT',
+ {bling,
+ [{?MODULE, call_func2, A, _},
+ {?MODULE, call_func1, 1, _}]}}} = BlingError,
+ true = (A == 1) orelse (A == [bling]),
+
+ {[], Nodes}
+ = rpc:multicall(Nodes, timer, sleep, [100], 50),
+
+ OtherNodes = Nodes -- [ThisNode],
+
+ [] = flush([]),
+
+
+ {[ThisNode,
+ Node1,
+ Node3,
+ Node4,
+ Node5],
+ [Node2, badnodename]}
+ = rpc:multicall(Nodes ++ [badnodename], erlang, node, []),
+
+ try
+ rpc:multicall(Nodes ++ [<<"badnodename">>], erlang, node, []),
+ ct:fail(unexpected)
+ catch
+ error:_ ->
+ ok
+ end,
+
+ [] = flush([]),
+
+ try
+ rpc:multicall([Node1, Node2, Node3, Node4, Node5 | ThisNode], erlang, node, []),
+ ct:fail(unexpected)
+ catch
+ error:_ ->
+ ok
+ end,
+
+ [] = flush([]),
+
+ try
+ rpc:multicall(Nodes, erlang, node, [], (1 bsl 32)),
+ ct:fail(unexpected)
+ catch
+ error:_ ->
+ ok
+ end,
+
+ [] = flush([]),
+
+ {[], OtherNodes}
+ = rpc:multicall(OtherNodes, erlang, halt, []),
+
+ [] = flush([]),
+
+ case OldNodeTest of
+ true -> {comment, "Test with OTP 22 node as well"};
+ false -> {comment, "Test without OTP 22"}
+ end.
+
+call_func1(X) ->
+ call_func2(X),
+ ok.
+
+call_func2(X) ->
+ erlang:error(X, [X]).
+
+timeout_limit(Config) when is_list(Config) ->
+ Node = node(),
+ MaxTmo = (1 bsl 32) - 1,
+ erlang:send_after(100, self(), dummy_message),
+ try
+ receive
+ M ->
+ M
+ after MaxTmo + 1 ->
+ ok
+ end,
+ ct:fail("The ?MAX_INT_TIMEOUT define in rpc.erl needs "
+ "to be updated to reflect max timeout value "
+ "in a receive/after...")
+ catch
+ error:timeout_value ->
+ ok
+ end,
+ Node = rpc:call(Node, erlang, node, [], MaxTmo),
+ try
+ {badrpc, _} = rpc:call(Node, erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:_ ->
+ ok
+ end,
+ Node = rpc:block_call(Node, erlang, node, [], MaxTmo),
+ try
+ {badrpc, _} = rpc:block_call(Node, erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:_ ->
+ ok
+ end,
+ {[Node],[]} = rpc:multicall([Node], erlang, node, [], MaxTmo),
+ try
+ rpc:multicall([Node], erlang, node, [], MaxTmo+1),
+ ct:fail(unexpected)
+ catch
+ error:_ ->
+ ok
+ end,
+ ok.
+
+
+call_old_against_new(Config) ->
+ case test_server:is_release_available("22_latest") of
+ false ->
+ {skipped, "No OTP 22 available"};
+ true ->
+ test_on_22_node(Config, call_old_against_new_test, 1, 1)
+ end.
+
+call_old_against_new_test([Node22], [NodeCurr]) ->
+ %% Excecuted on an OTP 22 node
+
+ Node22 = rpc:call(Node22, erlang, node, []),
+ NodeCurr = rpc:call(NodeCurr, erlang, node, []),
+
+ {badrpc, {'EXIT', bang}} = rpc:call(Node22, erlang, exit, [bang]),
+ {badrpc, {'EXIT', bang}} = rpc:call(NodeCurr, erlang, exit, [bang]),
+
+ {badrpc, {'EXIT', {blong, _}}} = rpc:call(Node22, erlang, error, [blong]),
+ {badrpc, {'EXIT', {blong, _}}} = rpc:call(NodeCurr, erlang, error, [blong]),
+
+ bling = rpc:call(Node22, erlang, throw, [bling]),
+ bling = rpc:call(NodeCurr, erlang, throw, [bling]),
+
+ {badrpc, timeout} = rpc:call(Node22, timer, sleep, [1000], 100),
+ {badrpc, timeout} = rpc:call(NodeCurr, timer, sleep, [1000], 100),
+
+ {badrpc, nodedown} = rpc:call(Node22, erlang, halt, []),
+ {badrpc, nodedown} = rpc:call(NodeCurr, erlang, halt, []),
+
+ {badrpc, nodedown} = rpc:call(Node22, erlang, node, []),
+ {badrpc, nodedown} = rpc:call(NodeCurr, erlang, node, []),
+
+ ok.
+
+multicall_old_against_new(Config) ->
+ case test_server:is_release_available("22_latest") of
+ false ->
+ {skipped, "No OTP 22 available"};
+ true ->
+ test_on_22_node(Config, multicall_old_against_new_test, 2, 2)
+ end.
+
+multicall_old_against_new_test([Node22A, Node22B], [NodeCurrA, NodeCurrB]) ->
+ %% Excecuted on an OTP 22 node
+
+ AllNodes = [NodeCurrA, Node22A, NodeCurrB, Node22B],
+ NoNodes = length(AllNodes),
+
+ {AllNodes, []} = rpc:multicall(AllNodes, erlang, node, []),
+
+ Bang = lists:duplicate(NoNodes, {badrpc, {'EXIT', bang}}),
+ {Bang, []} = rpc:multicall(AllNodes, erlang, exit, [bang]),
+
+ {[{badrpc, {'EXIT', {blong, _}}},
+ {badrpc, {'EXIT', {blong, _}}},
+ {badrpc, {'EXIT', {blong, _}}},
+ {badrpc, {'EXIT', {blong, _}}}], []} = rpc:multicall(AllNodes, erlang, error, [blong]),
+
+ Bling = lists:duplicate(NoNodes, bling),
+ {Bling, []} = rpc:multicall(AllNodes, erlang, throw, [bling]),
+
+ {[], AllNodes} = rpc:multicall(AllNodes, timer, sleep, [1000], 100),
+
+ {AllNodes, []} = rpc:multicall(AllNodes, erlang, node, []),
+
+ {[], AllNodes} = rpc:multicall(AllNodes, erlang, halt, []),
+
+ {[], AllNodes} = rpc:multicall(AllNodes, erlang, node, []),
+
+ ok.
+
+cast_old_against_new(Config) ->
+ case test_server:is_release_available("22_latest") of
+ false ->
+ {skipped, "No OTP 22 available"};
+ true ->
+ test_on_22_node(Config, cast_old_against_new_test, 1, 1)
+ end.
+
+cast_old_against_new_test([Node22], [NodeCurr]) ->
+ %% Excecuted on an OTP 22 node
+
+ Me = self(),
+ Ref = make_ref(),
+ true = rpc:cast(Node22, erlang, send, [Me, {Ref, 1}]),
+ receive {Ref, 1} -> ok end,
+ true = rpc:cast(NodeCurr, erlang, send, [Me, {Ref, 2}]),
+ receive {Ref, 2} -> ok end,
+
+ true = rpc:cast(Node22, erlang, halt, []),
+ true = rpc:cast(NodeCurr, erlang, halt, []),
+
+ monitor_node(Node22, true),
+ receive {nodedown, Node22} -> ok end,
+ monitor_node(NodeCurr, true),
+ receive {nodedown, NodeCurr} -> ok end,
+
+ true = rpc:cast(Node22, erlang, send, [Me, {Ref, 3}]),
+ true = rpc:cast(NodeCurr, erlang, send, [Me, {Ref, 4}]),
+
+ receive Msg -> error({unexcpected_message, Msg})
+ after 1000 -> ok
+ end.
+
%%%
%%% Utility functions.
%%%
+start_node(Config) ->
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-" ++ integer_to_list(erlang:system_time(second))
+ ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))),
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{args, "-pa " ++ Pa}]).
+
+start_22_node(Config) ->
+ Rel = "22_latest",
+ case test_server:is_release_available(Rel) of
+ false ->
+ notsup;
+ true ->
+ Cookie = atom_to_list(erlang:get_cookie()),
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-" ++ integer_to_list(erlang:system_time(second))
+ ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))),
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name,
+ peer,
+ [{args, "-pa " ++ Pa ++ " -setcookie "++Cookie},
+ {erl, [{release, Rel}]}])
+ end.
+
+stop_node(Node) ->
+ test_server:stop_node(Node).
+
flush(L) ->
receive
M ->
@@ -498,3 +924,34 @@ f() ->
f2() ->
timer:sleep(500),
halt().
+
+test_on_22_node(Config, Test, No22, NoCurr) ->
+ Nodes22 = lists:map(fun (_) ->
+ {ok, N} = start_22_node(Config),
+ N
+ end,
+ lists:seq(1, No22+1)),
+ NodesCurr = lists:map(fun (_) ->
+ {ok, N} = start_node(Config),
+ N
+ end,
+ lists:seq(1, NoCurr)),
+
+ %% Recompile rpc_SUITE on OTP 22 node and load it on all OTP 22 nodes...
+ SrcFile = filename:rootname(code:which(?MODULE)) ++ ".erl",
+ {ok, ?MODULE, BeamCode} = rpc:call(hd(Nodes22), compile, file, [SrcFile, [binary]]),
+ LoadResult = lists:duplicate(length(Nodes22), {module, ?MODULE}),
+ {LoadResult, []} = rpc:multicall(Nodes22, code, load_binary, [?MODULE, SrcFile, BeamCode]),
+ try
+ %% Excecute test on first OTP 22 node...
+ Pid = spawn_link(hd(Nodes22), ?MODULE, Test, [tl(Nodes22), NodesCurr]),
+ Mon = erlang:monitor(process, Pid),
+ receive
+ {'DOWN', Mon, process, Pid, Reason} when Reason == normal; Reason == noproc ->
+ ok
+ end
+ after
+ lists:foreach(fun (N) -> stop_node(N) end, Nodes22),
+ lists:foreach(fun (N) -> stop_node(N) end, NodesCurr)
+ end,
+ ok.
diff --git a/lib/kernel/test/sendfile_SUITE.erl b/lib/kernel/test/sendfile_SUITE.erl
index 36e6ca555f..a2a19caf35 100644
--- a/lib/kernel/test/sendfile_SUITE.erl
+++ b/lib/kernel/test/sendfile_SUITE.erl
@@ -23,7 +23,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
--export([all/0, init_per_suite/1, end_per_suite/1, init_per_testcase/2]).
+-export([all/0, init_per_suite/1, end_per_suite/1, init_per_testcase/2, end_per_testcase/2]).
-export([sendfile_server/2, sendfile_do_recv/2, init/1, handle_event/2]).
@@ -107,7 +107,27 @@ init_per_testcase(TC,Config) when TC == t_sendfile_recvduring;
{skip,"Not supported"}
end;
init_per_testcase(_TC,Config) ->
- Config.
+ case read_fd_info() of
+ {ok, NumFDs, FDDetails} ->
+ [{fds,NumFDs},{details,FDDetails}|Config];
+ {error,_Reason} ->
+ Config
+ end.
+
+end_per_testcase(_TC,Config) ->
+ case proplists:get_value(fds, Config) of
+ undefined ->
+ ok;
+ NumOldFDs ->
+ case read_fd_info() of
+ {ok, NumFDs, FDDetails} when NumFDs =/= NumOldFDs ->
+ ct:log("FDs: ~n~ts~nOldFDs: ~n~ts~n",
+ [FDDetails,proplists:get_value(details,Config)]),
+ {fail,"Too many (or too few) fds open"};
+ _ ->
+ ok
+ end
+ end.
t_sendfile_small(Config) when is_list(Config) ->
Filename = proplists:get_value(small_file, Config),
@@ -171,7 +191,8 @@ t_sendfile_big_size(Config) ->
{ok, #file_info{size = Size}} =
file:read_file_info(Filename),
{ok,D} = file:open(Filename,[read|FileOpts]),
- {ok, Size} = file:sendfile(D, Sock,0,Size,SendfileOpts),
+ {ok,Size} = file:sendfile(D,Sock,0,Size,SendfileOpts),
+ ok = file:close(D),
Size
end,
@@ -507,6 +528,18 @@ sendfile(Filename,Sock,Opts) ->
Res
end.
+%% This function returns the number of open fds on a system
+%% and also a string representing more detailed information
+%% for debugging.
+%% It only supports linux for now.
+read_fd_info() ->
+ ProcFd = "/proc/" ++ os:getpid() ++ "/fd",
+ case file:list_dir(ProcFd) of
+ {ok, FDs} ->
+ {ok, length(FDs), os:cmd("ls -l " ++ ProcFd)};
+ Error ->
+ Error
+ end.
%% Error handler
diff --git a/lib/kernel/test/seq_trace_SUITE.erl b/lib/kernel/test/seq_trace_SUITE.erl
index 718b5390cb..4a00b8d3d0 100644
--- a/lib/kernel/test/seq_trace_SUITE.erl
+++ b/lib/kernel/test/seq_trace_SUITE.erl
@@ -26,12 +26,13 @@
init_per_group/2,end_per_group/2,
init_per_testcase/2,end_per_testcase/2]).
-export([token_set_get/1, tracer_set_get/1, print/1,
- old_heap_token/1,
+ old_heap_token/1,mature_heap_token/1,
send/1, distributed_send/1, recv/1, distributed_recv/1,
trace_exit/1, distributed_exit/1, call/1, port/1,
port_clean_token/1,
match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1,
- send_literal/1]).
+ send_literal/1,inherit_on_spawn/1,inherit_on_dist_spawn/1,
+ dist_spawn_error/1]).
%% internal exports
-export([simple_tracer/2, one_time_receiver/0, one_time_receiver/1,
@@ -53,10 +54,11 @@ suite() ->
all() ->
[token_set_get, tracer_set_get, print, send, send_literal,
distributed_send, recv, distributed_recv, trace_exit,
- old_heap_token,
+ old_heap_token, mature_heap_token,
distributed_exit, call, port, match_set_seq_token,
port_clean_token,
- gc_seq_token, label_capability_mismatch].
+ gc_seq_token, label_capability_mismatch,
+ inherit_on_spawn, inherit_on_dist_spawn, dist_spawn_error].
groups() ->
[].
@@ -86,14 +88,27 @@ token_set_get(Config) when is_list(Config) ->
do_token_set_get(timestamp),
do_token_set_get(monotonic_timestamp),
do_token_set_get(strict_monotonic_timestamp).
-
+
+-define(SEQ_TRACE_SEND, 1). %(1 << 0)
+-define(SEQ_TRACE_RECEIVE, 2). %(1 << 1)
+-define(SEQ_TRACE_PRINT, 4). %(1 << 2)
+-define(SEQ_TRACE_NOW_TIMESTAMP, 8). %(1 << 3)
+-define(SEQ_TRACE_STRICT_MON_TIMESTAMP, 16). %(1 << 4)
+-define(SEQ_TRACE_MON_TIMESTAMP, 32). %(1 << 5)
+
do_token_set_get(TsType) ->
- io:format("Testing ~p~n", [TsType]),
+ BaseOpts = ?SEQ_TRACE_SEND bor
+ ?SEQ_TRACE_RECEIVE bor
+ ?SEQ_TRACE_PRINT,
Flags = case TsType of
- timestamp -> 15;
- strict_monotonic_timestamp -> 23;
- monotonic_timestamp -> 39
- end,
+ timestamp ->
+ BaseOpts bor ?SEQ_TRACE_NOW_TIMESTAMP;
+ strict_monotonic_timestamp ->
+ BaseOpts bor ?SEQ_TRACE_STRICT_MON_TIMESTAMP;
+ monotonic_timestamp ->
+ BaseOpts bor ?SEQ_TRACE_MON_TIMESTAMP
+ end,
+ ct:pal("Type ~p, flags = ~p~n", [TsType, Flags]),
Self = self(),
seq_trace:reset_trace(),
%% Test that initial seq_trace is disabled
@@ -208,9 +223,9 @@ do_send_literal(Msg) ->
seq_trace:reset_trace(),
start_tracer(),
Label = make_ref(),
+ Receiver = spawn_link(fun() -> receive ok -> ok end end),
seq_trace:set_token(label,Label),
set_token_flags([send, 'receive', no_timestamp]),
- Receiver = spawn_link(fun() -> receive ok -> ok end end),
[Receiver ! Msg || _ <- lists:seq(1, N)],
erlang:garbage_collect(Receiver),
[Receiver ! Msg || _ <- lists:seq(1, N)],
@@ -482,8 +497,6 @@ call(Config) when is_list(Config) ->
1 =
erlang:trace(Self, true,
[call, set_on_spawn, {tracer, TrB(pid)}]),
- Label = 17,
- seq_trace:set_token(label, Label), % Token enters here!!
RefB = make_ref(),
Pid2B = spawn_link(
fun() ->
@@ -497,6 +510,12 @@ call(Config) when is_list(Config) ->
RefB = call_tracee_1(RefB),
Pid2B ! {self(), msg, RefB}
end),
+
+ %% The token is set *AFTER* spawning to make sure we're testing that the
+ %% token follows on send and not that it inherits on spawn.
+ Label = 17,
+ seq_trace:set_token(label, Label),
+
Pid1B ! {Self, msg, RefB},
%% The message is passed Self -> Pid1B -> Pid2B -> Self, and the
%% seq_trace token follows invisibly. Traced functions are
@@ -517,6 +536,390 @@ call(Config) when is_list(Config) ->
seq_trace:reset_trace(),
ok.
+%% The token should follow spawn, just like it follows messages.
+inherit_on_spawn(Config) when is_list(Config) ->
+ lists:foreach(
+ fun (Test) ->
+ lists:foreach(
+ fun (TraceFlags) ->
+ inherit_on_spawn_test(Test, TraceFlags)
+ end,
+ combinations(spawn_trace_flags()))
+ end,
+ [spawn, spawn_link, spawn_monitor,
+ spawn_opt, spawn_request]),
+ ok.
+
+inherit_on_spawn_test(Spawn, TraceFlags) ->
+ io:format("Testing ~p() with ~p trace flags~n", [Spawn, TraceFlags]),
+
+ seq_trace:reset_trace(),
+ start_tracer(),
+ start_spawn_tracer(TraceFlags),
+
+ Ref = make_ref(),
+ seq_trace:set_token(label,Ref),
+ set_token_flags([send,'receive',strict_monotonic_timestamp]),
+
+ Self = self(),
+ GurkaMsg = {gurka,Ref},
+ SpawnFun = fun() -> Self ! GurkaMsg, receive after infinity -> ok end end,
+ {Other, Tag, KnownReqId, KnownSpawnReply}
+ = case Spawn of
+ spawn ->
+ {spawn(SpawnFun), spawn_reply, undefined, undefined};
+ spawn_link ->
+ {spawn_link(SpawnFun), spawn_reply, undefined, undefined};
+ spawn_monitor ->
+ {P, _} = spawn_monitor(SpawnFun),
+ {P, spawn_reply, undefined, undefined};
+ spawn_opt ->
+ {spawn_opt(SpawnFun, [link]), spawn_reply, undefined, undefined};
+ spawn_request ->
+ SReply = make_ref(),
+ RID = spawn_request(SpawnFun, [link, {reply_tag, SReply}]),
+ receive
+ {SReply, RID, ok, P} = M ->
+ {P, SReply, RID, M}
+ end
+ end,
+
+ receive {gurka,Ref} -> ok end,
+ seq_trace:reset_trace(),
+ erlang:trace(self(),false,[procs|TraceFlags]),
+
+ Sequence = lists:keysort(3, stop_tracer(6)),
+ io:format("Sequence: ~p~n", [Sequence]),
+ [SSpawnRequest, RSpawnRequest, SSpawnReply, RSpawnReply,
+ SGurkaMsg, RGurkaMsg] = Sequence,
+
+ %% Spawn request...
+ {Ref,
+ {send,
+ {0,1},
+ Self,Other,
+ ReqMessage},
+ _} = SSpawnRequest,
+
+ spawn_request = element(1, ReqMessage),
+ ReqId = element(2, ReqMessage),
+ case KnownReqId of
+ undefined -> ok;
+ ReqId -> ok
+ end,
+
+ {Ref,
+ {'receive',
+ {0,1},
+ Self,Other,
+ ReqMessage},
+ _} = RSpawnRequest,
+
+ %% Spawn reply...
+ SpawnReply = {Tag,ReqId,ok,Other},
+ {Ref,
+ {send,
+ {1,2},
+ Other,Self,
+ SpawnReply},
+ _} = SSpawnReply,
+
+ case KnownSpawnReply of
+ undefined -> ok;
+ SpawnReply -> ok
+ end,
+
+ {Ref,
+ {'receive',
+ {1,2},
+ Other,Self,
+ SpawnReply},
+ _} = RSpawnReply,
+
+ %% Gurka message...
+ {Ref,
+ {send,
+ {1,3},
+ Other, Self,
+ GurkaMsg},
+ _} = SGurkaMsg,
+
+ {Ref,
+ {'receive',
+ {1,3},
+ Other, Self,
+ GurkaMsg},
+ _} = RGurkaMsg,
+
+
+ Links = not(spawn =:= spawn orelse Spawn =:= spawn_monitor),
+ SoL = lists:member(set_on_link,TraceFlags) orelse
+ lists:member(set_on_first_link,TraceFlags),
+ SoS = lists:member(set_on_spawn,TraceFlags) orelse
+ lists:member(set_on_first_sapwn,TraceFlags),
+
+ NoTraceMessages =
+ if
+ SoS andalso Links ->
+ 4;
+ SoS andalso not Links ->
+ 2;
+ SoL andalso Links ->
+ 4;
+ SoL andalso not Links->
+ 1;
+ Links andalso not SoL andalso not SoS ->
+ 2;
+ not Links andalso not SoL andalso not SoS ->
+ 1
+ end,
+
+ TraceMessages = stop_spawn_tracer(NoTraceMessages),
+
+ unlink(Other),
+ exit(Other, kill),
+
+
+ ok.
+
+inherit_on_dist_spawn(Config) when is_list(Config) ->
+ lists:foreach(fun (Test) ->
+ inherit_on_dist_spawn_test(Test)
+ end,
+ [spawn, spawn_link, spawn_monitor,
+ spawn_opt, spawn_request]),
+ ok.
+
+inherit_on_dist_spawn_test(Spawn) ->
+ io:format("Testing ~p()~n", [Spawn]),
+ Pa = "-pa "++filename:dirname(code:which(?MODULE)),
+ {ok, Node} = start_node(seq_trace_dist_spawn, Pa),
+ %% ensure module is loaded on remote node...
+ _ = rpc:call(Node, ?MODULE, module_info, []),
+
+ io:format("Self=~p~n",[self()]),
+
+ seq_trace:reset_trace(),
+ start_tracer(),
+ rpc:call(Node, seq_trace, reset_trace, []),
+ start_tracer(Node),
+
+ Ref = make_ref(),
+ io:format("Ref=~p~n",[Ref]),
+
+ seq_trace:set_token(label,Ref),
+ set_token_flags([send,'receive',strict_monotonic_timestamp]),
+
+ Self = self(),
+
+ GurkaMsg = {gurka,Ref},
+ SpawnFun = fun() -> Self ! GurkaMsg, receive after infinity -> ok end end,
+ {Other, Tag, KnownReqId, KnownSpawnReply}
+ = case Spawn of
+ spawn ->
+ {spawn(Node, SpawnFun), spawn_reply, undefined, undefined};
+ spawn_link ->
+ {spawn_link(Node, SpawnFun), spawn_reply, undefined, undefined};
+ spawn_monitor ->
+ {P, _} = spawn_monitor(Node, SpawnFun),
+ {P, spawn_reply, undefined, undefined};
+ spawn_opt ->
+ {spawn_opt(Node, SpawnFun, [link]), spawn_reply, undefined, undefined};
+ spawn_request ->
+ SReply = make_ref(),
+ RID = spawn_request(Node, SpawnFun, [{reply_tag, SReply}, link]),
+ receive
+ {SReply, RID, ok, P} = M ->
+ {P, SReply, RID, M}
+ end
+ end,
+
+ receive GurkaMsg -> ok end,
+ seq_trace:reset_trace(),
+
+ Sequence = lists:keysort(3,stop_tracer(4)),
+ io:format("Sequence: ~p~n", [Sequence]),
+ [StSpawnRequest, StAList, StSpawnReply, StGurkaMsg] = Sequence,
+
+ %% Spawn request...
+ {Ref,
+ {send,
+ {0,1},
+ Self,Node,
+ ReqMessage},
+ _} = StSpawnRequest,
+
+ spawn_request = element(1, ReqMessage),
+ ReqId = element(2, ReqMessage),
+ case KnownReqId of
+ undefined -> ok;
+ ReqId -> ok
+ end,
+
+ {Ref,
+ {send,
+ {0,2},
+ Self,Node,
+ ArgList},
+ _} = StAList,
+
+ %% Spawn reply...
+ SpawnReply = {Tag,ReqId,ok,Other},
+ case KnownSpawnReply of
+ undefined -> ok;
+ SpawnReply -> ok
+ end,
+
+ {Ref,
+ {'receive',
+ {1,2},
+ Other,Self,
+ SpawnReply},
+ _} = StSpawnReply,
+
+ %% Gurka message...
+ {Ref,
+ {'receive',
+ {2,3},
+ Other, Self,
+ GurkaMsg},
+ _} = StGurkaMsg,
+
+ SequenceNode = lists:keysort(3,stop_tracer(Node, 4)),
+ io:format("SequenceNode: ~p~n", [SequenceNode]),
+ [StSpawnRequestNode, StSpawnReplyNode, StAListNode, StGurkaMsgNode] = SequenceNode,
+
+
+ %% Spawn request...
+ {Ref,
+ {'receive',
+ {0,1},
+ Self,Other,
+ ReqMessage},
+ _} = StSpawnRequestNode,
+
+ %% Spawn reply...
+ {Ref,
+ {send,
+ {1,2},
+ Other,Self,
+ {spawn_reply, ReqId, ok, Other}},
+ _} = StSpawnReplyNode,
+
+ {Ref,
+ {'receive',
+ {0,2},
+ Self,Other,
+ ArgList},
+ _} = StAListNode,
+
+ %% Gurka message...
+ {Ref,
+ {send,
+ {2,3},
+ Other, Self,
+ GurkaMsg},
+ _} = StGurkaMsgNode,
+
+ unlink(Other),
+
+ stop_node(Node),
+
+ ok.
+
+dist_spawn_error(Config) when is_list(Config) ->
+ Pa = "-pa "++filename:dirname(code:which(?MODULE)),
+ {ok, Node} = start_node(seq_trace_dist_spawn, Pa),
+ %% ensure module is loaded on remote node...
+ _ = rpc:call(Node, ?MODULE, module_info, []),
+
+ io:format("Self=~p~n",[self()]),
+
+ seq_trace:reset_trace(),
+ start_tracer(),
+ rpc:call(Node, seq_trace, reset_trace, []),
+ start_tracer(Node),
+
+ Ref = make_ref(),
+ io:format("Ref=~p~n",[Ref]),
+
+ seq_trace:set_token(label,Ref),
+ set_token_flags([send,'receive',strict_monotonic_timestamp]),
+
+ Self = self(),
+ SpawnReplyTag = make_ref(),
+ GurkaMsg = {gurka,Ref},
+ ReqId = spawn_request(Node,
+ fun () ->
+ Self ! GurkaMsg,
+ receive after infinity -> ok end
+ end,
+ [lunk, {reply_tag, SpawnReplyTag}, link]),
+
+ receive
+ {SpawnReplyTag, ReqId, ResType, Err} ->
+ error = ResType,
+ badopt = Err
+ end,
+
+ seq_trace:reset_trace(),
+
+ Sequence = lists:keysort(3,stop_tracer(3)),
+ io:format("Sequence: ~p~n", [Sequence]),
+ [StSpawnRequest, StAList, StSpawnReply] = Sequence,
+
+ %% Spawn request...
+ {Ref,
+ {send,
+ {0,1},
+ Self,Node,
+ ReqMessage},
+ _} = StSpawnRequest,
+
+ spawn_request = element(1, ReqMessage),
+ ReqId = element(2, ReqMessage),
+
+ {Ref,
+ {send,
+ {0,2},
+ Self,Node,
+ _ArgList},
+ _} = StAList,
+
+ %% Spawn reply...
+ ReplyMessage = {SpawnReplyTag,ReqId,error,badopt},
+ {Ref,
+ {'receive',
+ {1,2},
+ Node,Self,
+ ReplyMessage},
+ _} = StSpawnReply,
+
+ SequenceNode = lists:keysort(3,stop_tracer(Node, 2)),
+ io:format("SequenceNode: ~p~n", [SequenceNode]),
+ [StSpawnRequestNode, StSpawnReplyNode] = SequenceNode,
+
+ %% Spawn request...
+ {Ref,
+ {'receive',
+ {0,1},
+ Self,Node,
+ ReqMessage},
+ _} = StSpawnRequestNode,
+
+ %% Spawn reply...
+ {Ref,
+ {send,
+ {1,2},
+ Node,Self,
+ {spawn_reply, ReqId, error, badopt}},
+ _} = StSpawnReplyNode,
+
+ stop_node(Node),
+
+ ok.
+
+
%% Send trace messages to a port.
port(Config) when is_list(Config) ->
lists:foreach(fun (TsType) -> do_port(TsType, Config) end,
@@ -601,6 +1004,24 @@ old_heap_token(Config) when is_list(Config) ->
{label,NewLabel} = seq_trace:get_token(label),
ok.
+%% Verify changing label on existing token when it resides on mature heap.
+%% Bug caused faulty ref from old to new heap.
+mature_heap_token(Config) when is_list(Config) ->
+
+ seq_trace:set_token(label, 1),
+ erlang:garbage_collect(self(), [{type, minor}]),
+ %% Now token should be on mature heap
+ %% Set a new non-literal label which should reside on new-heap.
+ NewLabel = {self(), "new label"},
+ seq_trace:set_token(label, NewLabel),
+
+ %% If bug, we now have a ref from mature to new heap. If we now GC
+ %% twice the token will refer to deallocated memory.
+ erlang:garbage_collect(self(), [{type, minor}]),
+ erlang:garbage_collect(self(), [{type, minor}]),
+ {label,NewLabel} = seq_trace:get_token(label),
+ ok.
+
match_set_seq_token(doc) ->
["Tests that match spec function set_seq_token does not "
@@ -951,24 +1372,51 @@ simple_tracer(Data, DN) ->
end.
stop_tracer(N) when is_integer(N) ->
- case catch (seq_trace_SUITE_tracer ! {stop,N,self()}) of
- {'EXIT', _} ->
- {error, not_started};
- _ ->
- receive
- {tracerlog,Data} ->
- Data
- after 1000 ->
- {error,timeout}
- end
+ stop_tracer(node(), N).
+
+stop_tracer(Node, N) when is_integer(N) ->
+ case rpc:call(Node,erlang,whereis,[seq_trace_SUITE_tracer]) of
+ Pid when is_pid(Pid) ->
+ unlink(Pid),
+ Mon = erlang:monitor(process, Pid),
+ Pid ! {stop,N,self()},
+ receive
+ {'DOWN', Mon, process, Pid, noproc} ->
+ {error, not_started};
+ {'DOWN', Mon, process, Pid, Reason} ->
+ {error, Reason};
+ {tracerlog,Data} ->
+ erlang:demonitor(Mon, [flush]),
+ Data
+ after 5000 ->
+ erlang:demonitor(Mon, [flush]),
+ {error,timeout}
+ end;
+ _ ->
+ {error, not_started}
end.
start_tracer() ->
- stop_tracer(0),
- Pid = spawn(?MODULE,simple_tracer,[[], 0]),
- register(seq_trace_SUITE_tracer,Pid),
- seq_trace:set_system_tracer(Pid),
- Pid.
+ start_tracer(node()).
+
+start_tracer(Node) ->
+ Me = self(),
+ Ref = make_ref(),
+ Pid = spawn_link(Node,
+ fun () ->
+ Self = self(),
+ stop_tracer(0),
+ register(seq_trace_SUITE_tracer,Self),
+ seq_trace:set_system_tracer(Self),
+ Self = seq_trace:get_system_tracer(),
+ Me ! Ref,
+ simple_tracer([], 0)
+ end),
+ receive
+ Ref ->
+ unlink(Pid),
+ Pid
+ end.
set_token_flags([]) ->
ok;
@@ -981,6 +1429,51 @@ set_token_flags([Flag|Flags]) ->
seq_trace:set_token(Flag, true),
set_token_flags(Flags).
+start_spawn_tracer(TraceFlags) ->
+
+ %% Disable old trace flags
+ erlang:trace(self(), false, spawn_trace_flags()),
+
+ Me = self(),
+ Ref = make_ref(),
+ Pid = spawn_link(
+ fun () ->
+ register(spawn_tracer, self()),
+ Me ! Ref,
+ (fun F(Data) ->
+ receive
+ {get, N, StopRef, Pid} when N =< length(Data) ->
+ Pid ! {lists:reverse(Data), StopRef};
+ M when element(1,M) =:= trace ->
+ F([M|Data])
+ end
+ end)([])
+ end),
+ receive
+ Ref ->
+ erlang:trace(self(),true,[{tracer,Pid}, procs | TraceFlags])
+ end.
+
+stop_spawn_tracer(N) ->
+ Ref = make_ref(),
+ spawn_tracer ! {get, N, Ref, self()},
+ receive
+ {Data, Ref} ->
+ Data
+ end.
+
+spawn_trace_flags() ->
+ [set_on_spawn, set_on_link, set_on_spawn,
+ set_on_first_link, set_on_first_spawn].
+
+combinations(Flags) ->
+ %% Do a bit of sofs magic to create a list of lists with
+ %% all the combinations of all the flags above
+ Set = sofs:from_term(Flags),
+ Product = sofs:product(list_to_tuple(lists:duplicate(length(Flags),Set))),
+ Combinations = [lists:usort(tuple_to_list(T)) || T <- sofs:to_external(Product)],
+ [[] | lists:usort(Combinations)].
+
check_ts(no_timestamp, Ts) ->
try
no_timestamp = Ts
@@ -1020,7 +1513,7 @@ check_ts(strict_monotonic_timestamp, Ts) ->
ok.
start_node(Name, Param) ->
- test_server:start_node(Name, slave, [{args, Param}]).
+ test_server:start_node(Name, peer, [{args, Param}]).
stop_node(Node) ->
test_server:stop_node(Node).
diff --git a/erts/emulator/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl
index 27f7eb5281..55097abf1f 100644
--- a/erts/emulator/test/socket_SUITE.erl
+++ b/lib/kernel/test/socket_SUITE.erl
@@ -83,6 +83,7 @@
api_b_open_and_close_tcp6/1,
api_b_open_and_close_udpL/1,
api_b_open_and_close_tcpL/1,
+ api_b_open_and_close_seqpL/1,
api_b_open_and_close_sctp4/1,
api_b_open_and_maybe_close_raw/1,
api_b_sendto_and_recvfrom_udp4/1,
@@ -91,10 +92,31 @@
api_b_sendmsg_and_recvmsg_udpL/1,
api_b_send_and_recv_tcp4/1,
api_b_send_and_recv_tcpL/1,
+ api_b_send_and_recv_seqpL/1,
api_b_sendmsg_and_recvmsg_tcp4/1,
api_b_sendmsg_and_recvmsg_tcpL/1,
+ api_b_sendmsg_and_recvmsg_seqpL/1,
api_b_sendmsg_and_recvmsg_sctp4/1,
+ %% *** API socket from FD ***
+ api_ffd_open_wod_and_info_udp4/1,
+ api_ffd_open_wod_and_info_udp6/1,
+ api_ffd_open_wod_and_info_tcp4/1,
+ api_ffd_open_wod_and_info_tcp6/1,
+ api_ffd_open_wd_and_info_udp4/1,
+ api_ffd_open_wd_and_info_udp6/1,
+ api_ffd_open_wd_and_info_tcp4/1,
+ api_ffd_open_wd_and_info_tcp6/1,
+ api_ffd_open_and_open_wod_and_send_udp4/1,
+ api_ffd_open_and_open_wod_and_send_udp6/1,
+ api_ffd_open_and_open_wd_and_send_udp4/1,
+ api_ffd_open_and_open_wd_and_send_udp6/1,
+ api_ffd_open_connect_and_open_wod_and_send_tcp4/1,
+ api_ffd_open_connect_and_open_wod_and_send_tcp6/1,
+ api_ffd_open_connect_and_open_wd_and_send_tcp4/1,
+ api_ffd_open_connect_and_open_wd_and_send_tcp6/1,
+
+
%% *** API async ***
api_a_connect_tcp4/1,
api_a_connect_tcp6/1,
@@ -206,6 +228,8 @@
%% Socket Registry
reg_s_single_open_and_close_and_count/1,
+ reg_s_optional_open_and_close_and_count/1,
+
%% *** Socket Closure ***
sc_cpe_socket_cleanup_tcp4/1,
@@ -649,17 +673,22 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
suite() ->
- [{ct_hooks,[ts_install_cth]},
- {timetrap,{minutes,1}}].
+ [{ct_hooks, [ts_install_cth]},
+ {timetrap, {minutes,1}}].
all() ->
Groups = [{api, "ESOCK_TEST_API", include},
+ {reg, undefined, include},
{socket_close, "ESOCK_TEST_SOCK_CLOSE", include},
{traffic, "ESOCK_TEST_TRAFFIC", include},
{ttest, "ESOCK_TEST_TTEST", exclude},
{tickets, "ESOCK_TEST_TICKETS", include}],
[use_group(Group, Env, Default) || {Group, Env, Default} <- Groups].
+use_group(_Group, undefined, exclude) ->
+ [];
+use_group(Group, undefined, _Default) ->
+ [{group, Group}];
use_group(Group, Env, Default) ->
case os:getenv(Env) of
false when (Default =:= include) ->
@@ -682,6 +711,7 @@ groups() ->
[{api, [], api_cases()},
{api_misc, [], api_misc_cases()},
{api_basic, [], api_basic_cases()},
+ {api_from_fd, [], api_from_fd_cases()},
{api_async, [], api_async_cases()},
{api_options, [], api_options_cases()},
{api_options_otp, [], api_options_otp_cases()},
@@ -800,6 +830,7 @@ api_basic_cases() ->
api_b_open_and_close_tcp6,
api_b_open_and_close_udpL,
api_b_open_and_close_tcpL,
+ api_b_open_and_close_seqpL,
api_b_open_and_close_sctp4,
api_b_open_and_maybe_close_raw,
api_b_sendto_and_recvfrom_udp4,
@@ -808,11 +839,33 @@ api_basic_cases() ->
api_b_sendmsg_and_recvmsg_udpL,
api_b_send_and_recv_tcp4,
api_b_send_and_recv_tcpL,
+ api_b_send_and_recv_seqpL,
api_b_sendmsg_and_recvmsg_tcp4,
api_b_sendmsg_and_recvmsg_tcpL,
+ api_b_sendmsg_and_recvmsg_seqpL,
api_b_sendmsg_and_recvmsg_sctp4
].
+api_from_fd_cases() ->
+ [
+ api_ffd_open_wod_and_info_udp4,
+ api_ffd_open_wod_and_info_udp6,
+ api_ffd_open_wod_and_info_tcp4,
+ api_ffd_open_wod_and_info_tcp6,
+ api_ffd_open_wd_and_info_udp4,
+ api_ffd_open_wd_and_info_udp6,
+ api_ffd_open_wd_and_info_tcp4,
+ api_ffd_open_wd_and_info_tcp6,
+ api_ffd_open_and_open_wod_and_send_udp4,
+ api_ffd_open_and_open_wod_and_send_udp6,
+ api_ffd_open_and_open_wd_and_send_udp4,
+ api_ffd_open_and_open_wd_and_send_udp6,
+ api_ffd_open_connect_and_open_wod_and_send_tcp4,
+ api_ffd_open_connect_and_open_wod_and_send_tcp6,
+ api_ffd_open_connect_and_open_wd_and_send_tcp4,
+ api_ffd_open_connect_and_open_wd_and_send_tcp6
+ ].
+
api_async_cases() ->
[
api_a_connect_tcp4,
@@ -1008,7 +1061,8 @@ api_op_with_timeout_cases() ->
%% Socket Registry "simple" test cases
reg_simple_cases() ->
[
- reg_s_single_open_and_close_and_count
+ reg_s_single_open_and_close_and_count,
+ reg_s_optional_open_and_close_and_count
].
@@ -1865,25 +1919,23 @@ otp16359_cases() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_per_suite(Config) ->
+ ct:timetrap(?MINS(2)),
Factor = analyze_and_print_host_info(),
- case lists:member(socket, erlang:loaded()) of
- true ->
- case os:type() of
- {win32, _} ->
- (catch not_yet_implemented());
- _ ->
- case quiet_mode(Config) of
- default ->
- ?LOGGER:start(),
- [{esock_factor, Factor} | Config];
- Quiet ->
- ?LOGGER:start(Quiet),
- [{esock_factor, Factor},
- {esock_test_quiet, Quiet} | Config]
- end
- end;
- false ->
- {skip, "esock disabled"}
+ try socket:info() of
+ #{} ->
+ socket:use_registry(false),
+ case quiet_mode(Config) of
+ default ->
+ ?LOGGER:start(),
+ [{esock_factor, Factor} | Config];
+ Quiet ->
+ ?LOGGER:start(Quiet),
+ [{esock_factor, Factor},
+ {esock_test_quiet, Quiet} | Config]
+ end
+ catch
+ error : notsup ->
+ {skip, "esock not supported"}
end.
end_per_suite(_) ->
@@ -1891,349 +1943,6 @@ end_per_suite(_) ->
ok.
-%% This function prints various host info, which might be usefull
-%% when analyzing the test suite (results).
-%% It also returns a "factor" that can be used to when deciding
-%% the load for some tst cases (traffic). Such as run time or
-%% number of iteraions. This only works for some OSes (linux).
-%% Also, mostly it just returns the factor 1.
-%% At this time we just look at BogoMIPS!
-analyze_and_print_host_info() ->
- {OsFam, OsName} = os:type(),
- Version =
- case os:version() of
- {Maj, Min, Rel} ->
- ?F("~w.~w.~w", [Maj, Min, Rel]);
- VStr ->
- VStr
- end,
- case {OsFam, OsName} of
- {unix, linux} ->
- analyze_and_print_linux_host_info(Version);
- {unix, openbsd} ->
- analyze_and_print_openbsd_host_info(Version);
- {unix, freebsd} ->
- analyze_and_print_freebsd_host_info(Version);
- {unix, sunos} ->
- io:format("Solaris: ~s"
- "~n", [Version]),
- 1;
- _ ->
- io:format("OS Family: ~p"
- "~n OS Type: ~p"
- "~n Version: ~p"
- "~n", [OsFam, OsName, Version]),
- 1
- end.
-
-analyze_and_print_linux_host_info(Version) ->
- case file:read_file_info("/etc/issue") of
- {ok, _} ->
- io:format("Linux: ~s"
- "~n ~s"
- "~n",
- [Version, string:trim(os:cmd("cat /etc/issue"))]);
- _ ->
- io:format("Linux: ~s"
- "~n", [Version])
- end,
- Factor =
- case (catch linux_which_cpuinfo()) of
- {ok, {CPU, BogoMIPS}} ->
- io:format("CPU: "
- "~n Model: ~s"
- "~n BogoMIPS: ~s"
- "~n", [CPU, BogoMIPS]),
- %% We first assume its a float, and if not try integer
- try list_to_float(string:trim(BogoMIPS)) of
- F when F > 1000 ->
- 1;
- F when F > 1000 ->
- 2;
- _ ->
- 3
- catch
- _:_:_ ->
- %%
- try list_to_integer(string:trim(BogoMIPS)) of
- I when I > 1000 ->
- 1;
- I when I > 1000 ->
- 2;
- _ ->
- 3
- catch
- _:_:_ ->
- 1
- end
- end;
- _ ->
- 1
- end,
- %% Check if we need to adjust the factor because of the memory
- try linux_which_meminfo() of
- AddFactor ->
- Factor + AddFactor
- catch
- _:_:_ ->
- Factor
- end.
-
-linux_which_cpuinfo() ->
- %% Check for x86 (Intel or AMD)
- CPU =
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"model name\" /proc/cpuinfo"), [$:,$\n])] of
- ["model name", ModelName | _] ->
- ModelName;
- _ ->
- %% ARM (at least some distros...)
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"Processor\" /proc/cpuinfo"), [$:,$\n])] of
- ["Processor", Proc | _] ->
- Proc;
- _ ->
- %% Ok, we give up
- throw(noinfo)
- catch
- _:_:_ ->
- throw(noinfo)
- end
- catch
- _:_:_ ->
- throw(noinfo)
- end,
- try [string:trim(S) || S <- string:tokens(os:cmd("grep -i \"bogomips\" /proc/cpuinfo"), [$:,$\n])] of
- [_, BMips | _] ->
- {ok, {CPU, BMips}};
- _ ->
- {ok, CPU}
- catch
- _:_:_ ->
- {ok, CPU}
- end.
-
-%% We *add* the value this return to the Factor.
-linux_which_meminfo() ->
- try [string:trim(S) || S <- string:tokens(os:cmd("grep MemTotal /proc/meminfo"), [$:])] of
- [_, MemTotal] ->
- io:format("Memory:"
- "~n ~s"
- "~n", [MemTotal]),
- case string:tokens(MemTotal, [$ ]) of
- [MemSzStr, MemUnit] ->
- MemSz2 = list_to_integer(MemSzStr),
- MemSz3 =
- case string:to_lower(MemUnit) of
- "kb" ->
- MemSz2;
- "mb" ->
- MemSz2*1024;
- "gb" ->
- MemSz2*1024*1024;
- _ ->
- throw(noinfo)
- end,
- if
- (MemSz3 >= 8388608) ->
- 0;
- (MemSz3 >= 4194304) ->
- 1;
- (MemSz3 >= 2097152) ->
- 2;
- true ->
- 3
- end;
- _X ->
- 0
- end;
- _ ->
- 0
- catch
- _:_:_ ->
- 0
- end.
-
-
-%% Just to be clear: This is ***not*** scientific...
-analyze_and_print_openbsd_host_info(Version) ->
- io:format("OpenBSD:"
- "~n Version: ~p"
- "~n", [Version]),
- Extract =
- fun(Key) ->
- string:tokens(string:trim(os:cmd("sysctl " ++ Key)), [$=])
- end,
- try
- begin
- CPU =
- case Extract("hw.model") of
- ["hw.model", Model] ->
- string:trim(Model);
- _ ->
- "-"
- end,
- CPUSpeed =
- case Extract("hw.cpuspeed") of
- ["hw.cpuspeed", Speed] ->
- list_to_integer(Speed);
- _ ->
- -1
- end,
- NCPU =
- case Extract("hw.ncpufound") of
- ["hw.ncpufound", N] ->
- list_to_integer(N);
- _ ->
- -1
- end,
- Memory =
- case Extract("hw.physmem") of
- ["hw.physmem", PhysMem] ->
- list_to_integer(PhysMem) div 1024;
- _ ->
- -1
- end,
- io:format("CPU:"
- "~n Model: ~s"
- "~n Speed: ~w"
- "~n N: ~w"
- "~nMemory:"
- "~n ~w KB"
- "~n", [CPU, CPUSpeed, NCPU, Memory]),
- CPUFactor =
- if
- (CPUSpeed =:= -1) ->
- 1;
- (CPUSpeed >= 2000) ->
- if
- (NCPU >= 4) ->
- 1;
- (NCPU >= 2) ->
- 2;
- true ->
- 3
- end;
- true ->
- if
- (NCPU >= 4) ->
- 2;
- (NCPU >= 2) ->
- 3;
- true ->
- 4
- end
- end,
- MemAddFactor =
- if
- (Memory =:= -1) ->
- 0;
- (Memory >= 8388608) ->
- 0;
- (Memory >= 4194304) ->
- 1;
- (Memory >= 2097152) ->
- 2;
- true ->
- 3
- end,
- CPUFactor + MemAddFactor
- end
- catch
- _:_:_ ->
- 1
- end.
-
-
-analyze_and_print_freebsd_host_info(Version) ->
- io:format("FreeBSD:"
- "~n Version: ~p"
- "~n", [Version]),
- Extract =
- fun(Key) ->
- string:tokens(string:trim(os:cmd("sysctl " ++ Key)), [$:])
- end,
- try
- begin
- CPU =
- case Extract("hw.model") of
- ["hw.model", Model] ->
- string:trim(Model);
- _ ->
- "-"
- end,
- CPUSpeed =
- case Extract("hw.clockrate") of
- ["hw.clockrate", Speed] ->
- list_to_integer(string:trim(Speed));
- _ ->
- -1
- end,
- NCPU =
- case Extract("hw.ncpu") of
- ["hw.ncpu", N] ->
- list_to_integer(string:trim(N));
- _ ->
- -1
- end,
- Memory =
- case Extract("hw.physmem") of
- ["hw.physmem", PhysMem] ->
- list_to_integer(string:trim(PhysMem)) div 1024;
- _ ->
- -1
- end,
- io:format("CPU:"
- "~n Model: ~s"
- "~n Speed: ~w"
- "~n N: ~w"
- "~nMemory:"
- "~n ~w KB"
- "~n", [CPU, CPUSpeed, NCPU, Memory]),
- CPUFactor =
- if
- (CPUSpeed =:= -1) ->
- 1;
- (CPUSpeed >= 2000) ->
- if
- (NCPU >= 4) ->
- 1;
- (NCPU >= 2) ->
- 2;
- true ->
- 3
- end;
- true ->
- if
- (NCPU >= 4) ->
- 2;
- (NCPU >= 2) ->
- 3;
- true ->
- 4
- end
- end,
- MemAddFactor =
- if
- (Memory =:= -1) ->
- 0;
- (Memory >= 8388608) ->
- 0;
- (Memory >= 4194304) ->
- 1;
- (Memory >= 2097152) ->
- 2;
- true ->
- 3
- end,
- CPUFactor + MemAddFactor
- end
- catch
- _:_:_ ->
- 1
- end.
-
-
-
-
init_per_group(GroupName, Config)
when (GroupName =:= sc_remote_close) orelse
(GroupName =:= sc_remote_shutdown) orelse
@@ -2686,6 +2395,26 @@ api_b_open_and_close_tcpL(_Config) when is_list(_Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Basically open (create) and close an Unix Domain dgram (UDP) socket.
+%% With some extra checks...
+api_b_open_and_close_seqpL(suite) ->
+ [];
+api_b_open_and_close_seqpL(doc) ->
+ [];
+api_b_open_and_close_seqpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(?FUNCTION_NAME,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ InitState = #{domain => local,
+ type => seqpacket,
+ protocol => default},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
%% Basically open (create) and close an IPv4 SCTP (seqpacket) socket.
%% With some extra checks...
api_b_open_and_close_sctp4(suite) ->
@@ -2720,6 +2449,12 @@ api_b_open_and_close(InitState) ->
cmd => fun({S, {ok, Sock}}) ->
NewS = S#{socket => Sock},
{ok, NewS};
+ ({_, {error, epfnosupport = Reason}}) ->
+ {skip, Reason};
+ ({_, {error, eprotonosupport = Reason}}) ->
+ {skip, Reason};
+ ({_, {error, esocktnosupport = Reason}}) ->
+ {skip, Reason};
({_, {error, _} = ERROR}) ->
ERROR
end},
@@ -2754,7 +2489,7 @@ api_b_open_and_close(InitState) ->
end},
#{desc => "get protocol",
cmd => fun(#{socket := Sock} = State) ->
- case socket:supports(options, socket, protocol) of
+ case socket:is_supported(options, socket, protocol) of
true ->
Res = socket:getopt(Sock, socket, protocol),
{ok, {State, Res}};
@@ -3194,10 +2929,11 @@ api_b_send_and_recv_tcp4(_Config) when is_list(_Config) ->
socket:recv(Sock)
end,
InitState = #{domain => inet,
+ type => stream,
proto => tcp,
send => Send,
recv => Recv},
- ok = api_b_send_and_recv_tcp(InitState)
+ ok = api_b_send_and_recv_conn(InitState)
end).
@@ -3221,10 +2957,39 @@ api_b_send_and_recv_tcpL(_Config) when is_list(_Config) ->
socket:recv(Sock)
end,
InitState = #{domain => local,
+ type => stream,
+ proto => default,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_conn(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the "common" functions (send and recv)
+%% on an Unix Domain seqpacket socket.
+api_b_send_and_recv_seqpL(suite) ->
+ [];
+api_b_send_and_recv_seqpL(doc) ->
+ [];
+api_b_send_and_recv_seqpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(?FUNCTION_NAME,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ InitState = #{domain => local,
+ type => seqpacket,
proto => default,
send => Send,
recv => Recv},
- ok = api_b_send_and_recv_tcp(InitState)
+ ok = api_b_send_and_recv_conn(InitState)
end).
@@ -3254,10 +3019,11 @@ api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
end
end,
InitState = #{domain => inet,
+ type => stream,
proto => tcp,
send => Send,
recv => Recv},
- ok = api_b_send_and_recv_tcp(InitState)
+ ok = api_b_send_and_recv_conn(InitState)
end).
@@ -3303,16 +3069,68 @@ api_b_sendmsg_and_recvmsg_tcpL(_Config) when is_list(_Config) ->
end
end,
InitState = #{domain => local,
+ type => stream,
proto => default,
send => Send,
recv => Recv},
- ok = api_b_send_and_recv_tcp(InitState)
+ ok = api_b_send_and_recv_conn(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the msg functions (sendmsg and recvmsg)
+%% on an Unix Domain (stream) socket (TCP).
+api_b_sendmsg_and_recvmsg_seqpL(suite) ->
+ [];
+api_b_sendmsg_and_recvmsg_seqpL(doc) ->
+ [];
+api_b_sendmsg_and_recvmsg_seqpL(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(?FUNCTION_NAME,
+ fun() -> has_support_unix_domain_socket() end,
+ fun() ->
+ Send =
+ fun(Sock, Data) ->
+ MsgHdr =
+ #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ %% On some platforms, the address
+ %% is *not* provided (e.g. FreeBSD)
+ {ok,
+ #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ %% On some platforms, the address
+ %% *is* provided (e.g. linux)
+ {ok,
+ #{addr := #{family := local},
+ iov := [Data]}} ->
+ socket:setopt(
+ Sock, otp, debug, false),
+ {ok, Data};
+ {error, _} = ERROR ->
+ socket:setopt(
+ Sock, otp, debug, false),
+ ERROR
+ end
+ end,
+ InitState =
+ #{domain => local,
+ type => seqpacket,
+ proto => default,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_conn(InitState)
end).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-api_b_send_and_recv_tcp(InitState) ->
+api_b_send_and_recv_conn(InitState) ->
process_flag(trap_exit, true),
ServerSeq =
[
@@ -3336,10 +3154,13 @@ api_b_send_and_recv_tcp(InitState) ->
end},
#{desc => "create listen socket",
cmd => fun(#{domain := Domain,
+ type := Type,
proto := Proto} = State) ->
- case socket:open(Domain, stream, Proto) of
+ case socket:open(Domain, Type, Proto) of
{ok, Sock} ->
{ok, State#{lsock => Sock}};
+ {error, eprotonosupport = Reason} ->
+ {skip, Reason};
{error, _} = ERROR ->
ERROR
end
@@ -3497,8 +3318,9 @@ api_b_send_and_recv_tcp(InitState) ->
end},
#{desc => "create socket",
cmd => fun(#{domain := Domain,
+ type := Type,
proto := Proto} = State) ->
- case socket:open(Domain, stream, Proto) of
+ case socket:open(Domain, Type, Proto) of
{ok, Sock} ->
{ok, State#{sock => Sock}};
{error, _} = ERROR ->
@@ -4256,6 +4078,1890 @@ api_b_send_and_recv_sctp(_InitState) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API FROM FD %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 UDP (dgram) socket.
+%% With some extra checks...
+%% IPv4
+%% Without dup
+api_ffd_open_wod_and_info_udp4(suite) ->
+ [];
+api_ffd_open_wod_and_info_udp4(doc) ->
+ [];
+api_ffd_open_wod_and_info_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wod_and_info_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => false},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv6 UDP (dgram) socket.
+%% With some extra checks...
+%% IPv6
+%% Without dup
+api_ffd_open_wod_and_info_udp6(suite) ->
+ [];
+api_ffd_open_wod_and_info_udp6(doc) ->
+ [];
+api_ffd_open_wod_and_info_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wod_and_info_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp,
+ dup => false},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 UDP (dgram) socket.
+%% With some extra checks...
+%% IPv4
+%% With dup
+api_ffd_open_wd_and_info_udp4(suite) ->
+ [];
+api_ffd_open_wd_and_info_udp4(doc) ->
+ [];
+api_ffd_open_wd_and_info_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_wd_open_and_info_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => true},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 UDP (dgram) socket.
+%% With some extra checks...
+%% IPv6
+%% With dup
+api_ffd_open_wd_and_info_udp6(suite) ->
+ [];
+api_ffd_open_wd_and_info_udp6(doc) ->
+ [];
+api_ffd_open_wd_and_info_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_wd_open_and_info_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => true},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 TCP (stream) socket.
+%% With some extra checks...
+%% IPv6
+%% Without dup
+api_ffd_open_wod_and_info_tcp4(suite) ->
+ [];
+api_ffd_open_wod_and_info_tcp4(doc) ->
+ [];
+api_ffd_open_wod_and_info_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wod_and_info_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ dup => false},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv6 TCP (stream) socket.
+%% With some extra checks...
+%% IPv6
+%% Without dup
+api_ffd_open_wod_and_info_tcp6(suite) ->
+ [];
+api_ffd_open_wod_and_info_tcp6(doc) ->
+ [];
+api_ffd_open_wod_and_info_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wod_and_info_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ dup => false},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv4 TCP (stream) socket.
+%% With some extra checks...
+%% IPv6
+%% With dup
+api_ffd_open_wd_and_info_tcp4(suite) ->
+ [];
+api_ffd_open_wd_and_info_tcp4(doc) ->
+ [];
+api_ffd_open_wd_and_info_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wd_and_info_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ dup => true},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) a socket from an already existing
+%% file descriptor (FD) and info of an IPv6 TCP (stream) socket.
+%% With some extra checks...
+%% IPv6
+%% With dup
+api_ffd_open_wd_and_info_tcp6(suite) ->
+ [];
+api_ffd_open_wd_and_info_tcp6(doc) ->
+ [];
+api_ffd_open_wd_and_info_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_wd_and_info_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ dup => true},
+ ok = api_ffd_open_and_info(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_ffd_open_and_info(InitState) ->
+ Seq =
+ [
+ #{desc => "open",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ case socket:open(Domain, Type, Protocol) of
+ {ok, Sock1} ->
+ {ok, State#{sock1 => Sock1}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get socket (1) FD",
+ cmd => fun(#{sock1 := Sock1} = State) ->
+ case socket:getopt(Sock1, otp, fd) of
+ {ok, FD} ->
+ ?SEV_IPRINT("FD: ~w", [FD]),
+ {ok, State#{fd => FD}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed get FD: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "check if we need to provide protocol or not",
+ cmd => fun(#{sock1 := Sock1} = State) ->
+ case socket:getopt(Sock1, socket, protocol) of
+ {ok, _} ->
+ ?SEV_IPRINT("protocol accessible"),
+ {ok, State#{provide_protocol => false}};
+ {error, Reason} ->
+ ?SEV_IPRINT("failed get protocol: "
+ "~n ~p", [Reason]),
+ {ok, State#{provide_protocol => true}}
+ end
+ end},
+ #{desc => "open with FD",
+ cmd => fun(#{fd := FD,
+ dup := DUP,
+ provide_protocol := true,
+ protocol := Protocol} = State) ->
+ case socket:open(FD, #{dup => DUP,
+ protocol => Protocol}) of
+ {ok, Sock2} ->
+ ?SEV_IPRINT("socket 2 open"),
+ {ok, State#{sock2 => Sock2}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed open socket with FD (~w): "
+ "~n ~p", [FD, Reason]),
+ ERROR
+ end;
+ (#{fd := FD,
+ dup := DUP,
+ provide_protocol := false} = State) ->
+ case socket:open(FD, #{dup => DUP}) of
+ {ok, Sock2} ->
+ ?SEV_IPRINT("socket 2 open"),
+ {ok, State#{sock2 => Sock2}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed open socket with FD (~w): "
+ "~n ~p", [FD, Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "get socket (1) info",
+ cmd => fun(#{sock1 := Sock} = State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ Info = socket:info(Sock),
+ %% socket:setopt(Sock, otp, debug, false),
+ ?SEV_IPRINT("Got Info: "
+ "~n ~p", [Info]),
+ {ok, State#{info1 => Info}}
+ end},
+ #{desc => "get socket (2) info",
+ cmd => fun(#{sock2 := Sock} = State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ Info = socket:info(Sock),
+ %% socket:setopt(Sock, otp, debug, false),
+ ?SEV_IPRINT("Got Info: "
+ "~n ~p", [Info]),
+ {ok, State#{info2 => Info}}
+ end},
+ #{desc => "validate socket (1) info",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ info1 := #{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ ctype := normal,
+ counters := _,
+ num_readers := 0,
+ num_writers := 0,
+ num_acceptors := 0}}) ->
+ ok;
+ (#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ info := Info}) ->
+ ?SEV_EPRINT("Unexpected Info for socket 1: "
+ "~n (expected) Domain: ~p"
+ "~n (expected) Type: ~p"
+ "~n (expected) Protocol: ~p"
+ "~n (expected) Create Type: ~p"
+ "~n ~p",
+ [Domain, Type, Protocol, normal, Info]),
+ {error, unexpected_infio}
+ end},
+ #{desc => "validate socket (2) info",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ fd := _FD,
+ dup := false,
+ info2 := #{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ ctype := fromfd,
+ counters := _,
+ num_readers := 0,
+ num_writers := 0,
+ num_acceptors := 0}}) ->
+ ok;
+ (#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ fd := _FD,
+ dup := false,
+ info := Info}) ->
+ ?SEV_EPRINT("Unexpected Info for socket 2: "
+ "~n (expected) Domain: ~p"
+ "~n (expected) Type: ~p"
+ "~n (expected) Protocol: ~p"
+ "~n (expected) Create Type: ~p"
+ "~n ~p",
+ [Domain, Type, Protocol,
+ fromfd, Info]),
+ {error, unexpected_info};
+ (#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ fd := FD,
+ dup := true,
+ info2 := #{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ ctype := {fromfd, FD},
+ counters := _,
+ num_readers := 0,
+ num_writers := 0,
+ num_acceptors := 0}}) ->
+ ok;
+ (#{domain := Domain,
+ type := Type,
+ protocol := Protocol,
+ fd := FD,
+ dup := true,
+ info := Info}) ->
+ ?SEV_EPRINT("Unexpected Info for socket 2: "
+ "~n (expected) Domain: ~p"
+ "~n (expected) Type: ~p"
+ "~n (expected) Protocol: ~p"
+ "~n (expected) Create Type: ~p"
+ "~n ~p",
+ [Domain, Type, Protocol,
+ {fromfd, FD}, Info]),
+ {error, unexpected_info}
+ end},
+ #{desc => "close socket (1)",
+ cmd => fun(#{sock1 := Sock} = _State) ->
+ socket:close(Sock)
+ end},
+ #{desc => "close socket (2)",
+ cmd => fun(#{sock2 := Sock} = _State) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1) and then create another socket (2) from
+%% its file descriptor *without* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv4 UDP (dgram) socket.
+%%
+%% <WARNING>
+%%
+%% This is *not* how its intended to be used.
+%% That an erlang process creating a socket and then handing over the
+%% file descriptor to another erlang process. *But* its a convient way
+%% to test it!
+%%
+%% </WARNING>
+%%
+api_ffd_open_and_open_wod_and_send_udp4(suite) ->
+ [];
+api_ffd_open_and_open_wod_and_send_udp4(doc) ->
+ [];
+api_ffd_open_and_open_wod_and_send_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_and_open_wod_and_send_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => false},
+ ok = api_ffd_open_and_open_and_send_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1) and then create another socket (2) from
+%% its file descriptor *without* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv6 UDP (dgram) socket.
+%%
+%% <WARNING>
+%%
+%% This is *not* how its intended to be used.
+%% That an erlang process creating a socket and then handing over the
+%% file descriptor to another erlang process. *But* its a convient way
+%% to test it!
+%%
+%% </WARNING>
+%%
+api_ffd_open_and_open_wod_and_send_udp6(suite) ->
+ [];
+api_ffd_open_and_open_wod_and_send_udp6(doc) ->
+ [];
+api_ffd_open_and_open_wod_and_send_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_and_open_wod_and_send_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp,
+ dup => false},
+ ok = api_ffd_open_and_open_and_send_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1) and then create another socket (2) from
+%% its file descriptor *with* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv4 UDP (dgram) socket.
+%%
+api_ffd_open_and_open_wd_and_send_udp4(suite) ->
+ [];
+api_ffd_open_and_open_wd_and_send_udp4(doc) ->
+ [];
+api_ffd_open_and_open_wd_and_send_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_and_open_wd_and_send_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ dup => true},
+ ok = api_ffd_open_and_open_and_send_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1) and then create another socket (2) from
+%% its file descriptor *with* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv6 UDP (dgram) socket.
+%%
+api_ffd_open_and_open_wd_and_send_udp6(suite) ->
+ [];
+api_ffd_open_and_open_wd_and_send_udp6(doc) ->
+ [];
+api_ffd_open_and_open_wd_and_send_udp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_and_open_wd_and_send_udp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp,
+ dup => true},
+ ok = api_ffd_open_and_open_and_send_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_ffd_open_and_open_and_send_udp(InitState) ->
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest)
+ end,
+ Recv = fun(Sock) ->
+ socket:recvfrom(Sock)
+ end,
+ api_ffd_open_and_open_and_send_udp2(InitState#{send => Send,
+ recv => Recv}).
+
+api_ffd_open_and_open_and_send_udp2(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, dgram, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, lsa := LSA} = State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{port => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, port := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ #{desc => "await request 1 (recv)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, {Source, ?BASIC_REQ}} ->
+ ?SEV_IPRINT("received request (1) from: "
+ "~n ~p", [Source]),
+ {ok, State#{source => Source}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 1 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 1 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 1",
+ cmd => fun(#{sock := Sock, send := Send, source := Source}) ->
+ Send(Sock, ?BASIC_REP, Source)
+ end},
+ #{desc => "announce ready 1 (send reply)",
+ cmd => fun(#{tester := Tester} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ {ok, maps:remove(source, State)}
+ end},
+
+ #{desc => "await request 2 (recv)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, {Source, ?BASIC_REQ}} ->
+ ?SEV_IPRINT("received request (2) from: "
+ "~n ~p", [Source]),
+ {ok, State#{source => Source}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 2 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 2 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 2",
+ cmd => fun(#{sock := Sock, send := Send, source := Source}) ->
+ Send(Sock, ?BASIC_REP, Source)
+ end},
+ #{desc => "announce ready 2 (send reply)",
+ cmd => fun(#{tester := Tester} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ {ok, maps:remove(source, State)}
+ end},
+
+ #{desc => "await request 3 (recv)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, {Source, ?BASIC_REQ}} ->
+ ?SEV_IPRINT("received request (2) from: "
+ "~n ~p", [Source]),
+ {ok, State#{source => Source}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 3 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 3 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 3",
+ cmd => fun(#{sock := Sock, send := Send, source := Source}) ->
+ Send(Sock, ?BASIC_REP, Source)
+ end},
+ #{desc => "announce ready 3 (send reply)",
+ cmd => fun(#{tester := Tester} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ {ok, maps:remove(source, State)}
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Client1Seq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, dgram, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get socket FD",
+ cmd => fun(#{sock := Sock} = State) ->
+ case socket:getopt(Sock, otp, fd) of
+ {ok, FD} ->
+ ?SEV_IPRINT("FD: ~w", [FD]),
+ {ok, State#{fd => FD}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed get FD: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ fd := FD}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, FD),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (send request 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 1 (to server)",
+ cmd => fun(#{sock := Sock, send := Send, server_sa := SSA}) ->
+ Send(Sock, ?BASIC_REQ, SSA)
+ end},
+ #{desc => "announce ready (send request 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 1 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, {_, ?BASIC_REP}} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ #{desc => "await continue (send request 3)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 3 (to server)",
+ cmd => fun(#{sock := Sock, send := Send, server_sa := SSA}) ->
+ Send(Sock, ?BASIC_REQ, SSA)
+ end},
+ #{desc => "announce ready (send request 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 3 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, {_, ?BASIC_REP}} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Client2Seq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, {Port, FD}} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_port => Port,
+ fd => FD}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{fd := FD,
+ dup := DUP} = State) ->
+ case socket:open(FD, #{dup => DUP}) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (send request 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 2 (to server)",
+ cmd => fun(#{sock := Sock, send := Send, server_sa := SSA}) ->
+ Send(Sock, ?BASIC_REQ, SSA)
+ end},
+ #{desc => "announce ready (send request 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 2 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, {_, ?BASIC_REP}} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 1",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 2",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client 1
+ #{desc => "order client 1 start",
+ cmd => fun(#{client1 := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client 1 ready (init)",
+ cmd => fun(#{client1 := Pid} = State) ->
+ case ?SEV_AWAIT_READY(Pid, client1, init) of
+ {ok, FD} ->
+ {ok, State#{fd => FD}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Client 1 init error: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% Start the client 2
+ #{desc => "order client 2 start",
+ cmd => fun(#{client2 := Pid,
+ server_port := Port,
+ fd := FD} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {Port, FD}),
+ ok
+ end},
+ #{desc => "await client 2 ready (init)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+
+ %% *** The actual test ***
+
+ #{desc => "order client 1 to continue (with send request 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 1 ready (with send request 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, send_req)
+ end},
+ #{desc => "await server ready (request recv 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 1 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 1 ready (reply recv 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, recv_reply)
+ end},
+
+
+ #{desc => "order client 2 to continue (with send request 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 2 ready (with send request 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client2, send_req)
+ end},
+ #{desc => "await server ready (request recv 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 2 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 2 ready (reply recv 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client2, recv_reply)
+ end},
+
+
+ #{desc => "order client 2 to terminate",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client 2 termination",
+ cmd => fun(#{client2 := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client2, State),
+ {ok, State1}
+ end},
+
+
+ #{desc => "order client 1 to continue (with send request 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 1 ready (with send request 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, send_req)
+ end},
+ #{desc => "await server ready (request recv 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 3 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 1 ready (reply recv 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, recv_reply)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client 1 to terminate",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client 1 termination",
+ cmd => fun(#{client1 := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client1, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, maps:remove(dup, InitState)),
+
+ i("start (socket origin) client 1 evaluator"),
+ Client1 = ?SEV_START("client-1", Client1Seq, maps:remove(dup, InitState)),
+ i("await evaluator(s)"),
+
+ i("start client 2 evaluator"),
+ Client2 = ?SEV_START("client-2", Client2Seq, InitState),
+ i("await evaluator(s)"),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client1, Client2, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1), connect to a server and then create
+%% another socket (2) from its file descriptor *without* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv4 TCP (stream) socket.
+%%
+%% <WARNING>
+%%
+%% This is *not* how its intended to be used.
+%% That an erlang process creating a socket and then handing over the
+%% file descriptor to another erlang process. *But* its a convient way
+%% to test it!
+%%
+%% </WARNING>
+%%
+api_ffd_open_connect_and_open_wod_and_send_tcp4(suite) ->
+ [];
+api_ffd_open_connect_and_open_wod_and_send_tcp4(doc) ->
+ [];
+api_ffd_open_connect_and_open_wod_and_send_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_connect_and_open_wod_and_send_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ dup => false},
+ ok = api_ffd_open_connect_and_open_and_send_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1), connect to a server and then create
+%% another socket (2) from its file descriptor *without* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv6 TCP (stream) socket.
+%%
+%% <WARNING>
+%%
+%% This is *not* how its intended to be used.
+%% That an erlang process creating a socket and then handing over the
+%% file descriptor to another erlang process. *But* its a convient way
+%% to test it!
+%%
+%% </WARNING>
+%%
+api_ffd_open_connect_and_open_wod_and_send_tcp6(suite) ->
+ [];
+api_ffd_open_connect_and_open_wod_and_send_tcp6(doc) ->
+ [];
+api_ffd_open_connect_and_open_wod_and_send_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_connect_and_open_wod_and_send_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ dup => false},
+ ok = api_ffd_open_connect_and_open_and_send_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1), connect to a server and then create
+%% another socket (2) from its file descriptor *with* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv4 TCP (stream) socket.
+api_ffd_open_connect_and_open_wd_and_send_tcp4(suite) ->
+ [];
+api_ffd_open_connect_and_open_wd_and_send_tcp4(doc) ->
+ [];
+api_ffd_open_connect_and_open_wd_and_send_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_connect_and_open_wd_and_send_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ dup => true},
+ ok = api_ffd_open_connect_and_open_and_send_tcp(InitState)
+ end).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open a socket (1), connect to a server and then create
+%% another socket (2) from its file descriptor *with* dup.
+%% Exchange som data from via both "client" sockets.
+%% Finally close the second socket. Ensure that the original socket
+%% has not been closed (test by sending some data).
+%% IPv6 TCP (stream) socket.
+api_ffd_open_connect_and_open_wd_and_send_tcp6(suite) ->
+ [];
+api_ffd_open_connect_and_open_wd_and_send_tcp6(doc) ->
+ [];
+api_ffd_open_connect_and_open_wd_and_send_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_ffd_open_connect_and_open_wd_and_send_tcp6,
+ fun() -> has_support_ipv6() end,
+ fun() ->
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ dup => true},
+ ok = api_ffd_open_connect_and_open_and_send_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_ffd_open_connect_and_open_and_send_tcp(InitState) ->
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ api_ffd_open_connect_and_open_and_send_tcp2(InitState#{send => Send,
+ recv => Recv}).
+
+api_ffd_open_connect_and_open_and_send_tcp2(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+ case sock_bind(LSock, LSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to port: ~w", [Port]),
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ %% This is actually not used for unix domain socket
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await request 1 (recv)",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 1 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 1 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 1",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready 1 (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ #{desc => "await request 2 (recv)",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 2 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 2 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 2",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready 2 (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ #{desc => "await request 3 (recv)",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready 3 (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue 3 (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply 3",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready 3 (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Client1Seq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LSA = which_local_socket_addr(Domain),
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, stream, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case sock_bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "get socket FD",
+ cmd => fun(#{sock := Sock} = State) ->
+ case socket:getopt(Sock, otp, fd) of
+ {ok, FD} ->
+ ?SEV_IPRINT("FD: ~w", [FD]),
+ {ok, State#{fd => FD}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("failed get FD: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester,
+ fd := FD}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect, FD),
+ ok
+ end},
+
+
+ %% *** The actual test ***
+ #{desc => "await continue (send request 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 1 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 1 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, ?BASIC_REP} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ #{desc => "await continue (send request 3)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 3 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 3 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, ?BASIC_REP} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ Client2Seq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, FD} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, fd => FD}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "create socket",
+ cmd => fun(#{fd := FD,
+ dup := DUP} = State) ->
+ case socket:open(FD, #{dup => DUP}) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (send request 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request 2 (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply 2 (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, ?BASIC_REP} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ ok = socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 1",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 2",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client 1
+ #{desc => "order client 1 start",
+ cmd => fun(#{client1 := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client 1 ready (init)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, init)
+ end},
+
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client 1 to continue (with connect)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client 1 ready (connect)",
+ cmd => fun(#{client1 := Pid} = State) ->
+ {ok, FD} = ?SEV_AWAIT_READY(Pid, client1, connect),
+ {ok, State#{fd => FD}}
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+ %% Start the client 2
+ #{desc => "order client 2 start",
+ cmd => fun(#{client2 := Pid, fd := FD} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, FD),
+ ok
+ end},
+ #{desc => "await client 2 ready (init)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+
+ %% *** The actual test ***
+
+ #{desc => "order client 1 to continue (with send request 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 1 ready (with send request 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, send_req)
+ end},
+ #{desc => "await server ready (request recv 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 1)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 1 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 1 ready (reply recv 1)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, recv_reply)
+ end},
+
+
+ #{desc => "order client 2 to continue (with send request 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 2 ready (with send request 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client2, send_req)
+ end},
+ #{desc => "await server ready (request recv 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 2)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 2 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 2 ready (reply recv 2)",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client2, recv_reply)
+ end},
+
+
+ #{desc => "order client 2 to terminate",
+ cmd => fun(#{client2 := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client 2 termination",
+ cmd => fun(#{client2 := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client2, State),
+ {ok, State1}
+ end},
+
+
+ #{desc => "order client 1 to continue (with send request 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client 1 ready (with send request 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, send_req)
+ end},
+ #{desc => "await server ready (request recv 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply 3)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply 3 sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client 1 ready (reply recv 3)",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client1, recv_reply)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client 1 to terminate",
+ cmd => fun(#{client1 := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client 1 termination",
+ cmd => fun(#{client1 := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client1, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, maps:remove(dup, InitState)),
+
+ i("start (socket origin) client 1 evaluator"),
+ Client1 = ?SEV_START("client-1", Client1Seq, maps:remove(dup, InitState)),
+ i("await evaluator(s)"),
+
+ i("start client 2 evaluator"),
+ Client2 = ?SEV_START("client-2", Client2Seq, InitState),
+ i("await evaluator(s)"),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client1, Client2, Tester]).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API ASYNC %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Basically establish a TCP connection via an async connect. IPv4.
@@ -15439,6 +17145,7 @@ api_opt_sock_timestamp_tcp4(_Config) when is_list(_Config) ->
is_good_enough_linux({4,4,120}),
is_not_freebsd(),
is_not_openbsd(),
+ is_not_netbsd(),
is_not_darwin()
end,
fun() ->
@@ -15884,16 +17591,25 @@ api_opt_sock_timestamp_tcp(InitState) ->
"expected timestamp: "
"~n ~p", [TS]),
ok;
+ {ok, {[#{level := socket,
+ type := timestamp,
+ data := UTS}], BadData}} ->
+ ?SEV_EPRINT("received reply *with* "
+ "unexpected timestamp:"
+ "~n ~p"
+ "Current timestamp value:"
+ "~n ~p",
+ [UTS, Get(Sock)]),
+ {error, {unexpected_reply_data, BadData}};
{ok, {BadCMsgHdrs, ?BASIC_REP}} ->
- ?SEV_EPRINT("Current timestamp value:"
- " ~p", [Get(Sock)]),
+ ?SEV_EPRINT("received reply *with* "
+ "unexpected cmsg headers:"
+ "~n ~p"
+ "Current timestamp value: "
+ "~n ~p",
+ [BadCMsgHdrs, Get(Sock)]),
{error, {unexpected_reply_cmsghdrs,
BadCMsgHdrs}};
- {ok, {[#{level := socket,
- type := timestamp,
- data := _TS}], BadData}} ->
- {error, {unexpected_reply_data,
- BadData}};
{ok, BadReply} ->
{error, {unexpected_reply, BadReply}};
{error, _} = ERROR ->
@@ -16990,7 +18706,7 @@ api_opt_ip_recvopts_udp(InitState) ->
#{desc => "test for ip:recvtos",
cmd => fun(State) ->
?SEV_IPRINT("test for ip:recvtos"),
- case socket:supports(options, ip, recvtos) of
+ case socket:is_supported(options, ip, recvtos) of
true ->
?SEV_IPRINT("use ip:recvtos"),
{ok, State#{recvtos => true}};
@@ -17002,7 +18718,7 @@ api_opt_ip_recvopts_udp(InitState) ->
#{desc => "test for socket:timestamp",
cmd => fun(State) ->
?SEV_IPRINT("test for socket:timestamp"),
- case socket:supports(options, socket, timestamp) of
+ case socket:is_supported(options, socket, timestamp) of
true ->
?SEV_IPRINT("use socket:timestamp"),
{ok, State#{timestamp => true}};
@@ -18948,25 +20664,25 @@ api_opt_ip_mopts_udp4(_Config) when is_list(_Config) ->
%% 'control message header type',
%% default | value()}]
Opts =
- case socket:supports(options, socket, timestamp) of
+ case socket:is_supported(options, socket, timestamp) of
true ->
[{socket, timestamp, timestamp, default}];
false ->
[]
end ++
- case socket:supports(options, ip, pktinfo) of
+ case socket:is_supported(options, ip, pktinfo) of
true ->
[{ip, pktinfo, pktinfo, default}];
false ->
[]
end ++
- case socket:supports(options, ip, recvorigdstaddr) of
+ case socket:is_supported(options, ip, recvorigdstaddr) of
true ->
[{ip, recvorigdstaddr, origdstaddr, default}];
false ->
[]
end ++
- case socket:supports(options, ip, recvtos) of
+ case socket:is_supported(options, ip, recvtos) of
true ->
%% It seems that sending any of the
%% TOS or TTL values will fail on:
@@ -19003,18 +20719,22 @@ api_opt_ip_mopts_udp4(_Config) when is_list(_Config) ->
{unix, darwin} ->
[];
_ ->
- case socket:supports(options, ip, recvttl) of
+ case socket:is_supported(options, ip, recvttl) of
true ->
%% It seems that sending any of the
%% TOS or TTL values will fail on:
- %% FreeBSD
+ %% FreeBSD and NetBSD
%% Linux when
%% version =< 3.12.60 (at least)
%% so don't!
%% See recvtos above for more info.
[{ip, recvttl, ttl,
case os:type() of
- {unix, freebsd} ->
+ {unix, BSD}
+ when (BSD =:= freebsd) orelse
+ (BSD =:= netbsd) ->
+ default;
+ {unix, netbsd} ->
default;
{unix, linux} ->
case os:version() of
@@ -19791,11 +21511,12 @@ api_opt_ipv6_hoplimit_udp6(_Config) when is_list(_Config) ->
fun() ->
has_support_ipv6(),
has_support_ipv6_hoplimit_or_recvhoplimit(),
- is_good_enough_darwin({9,8,0})
+ is_good_enough_darwin({9,8,0}),
+ is_good_enough_montavista("4.0.1")
end,
fun() ->
%% Begin by choosing which of the options we shall use
- Opt = case socket:supports(options, ipv6, recvhoplimit) of
+ Opt = case socket:is_supported(options, ipv6, recvhoplimit) of
true -> recvhoplimit;
false -> hoplimit
end,
@@ -20084,7 +21805,7 @@ api_opt_ipv6_tclass_udp6(_Config) when is_list(_Config) ->
end,
fun() ->
%% Begin by choosing which of the options we shall use
- Opt = case socket:supports(options, ipv6, recvtclass) of
+ Opt = case socket:is_supported(options, ipv6, recvtclass) of
true -> recvtclass;
false -> tclass
end,
@@ -20452,7 +22173,8 @@ api_opt_ipv6_mopts_udp6(_Config) when is_list(_Config) ->
%% The problem here is hoplimit on darwin 9.8.0,
%% but I can't be bothered to adjust the test case,
%% just skip on that machine (there is only one)...
- is_good_enough_darwin({9,8,0})
+ is_good_enough_darwin({9,8,0}),
+ is_good_enough_montavista("4.0.1")
end,
fun() ->
%% If we get this far, we *know* that at least one of the
@@ -20462,40 +22184,40 @@ api_opt_ipv6_mopts_udp6(_Config) when is_list(_Config) ->
%% control message header type(s):
%% [{'ipv6 socket option', 'control message header type'}]
Opts =
- case socket:supports(options, socket, timestamp) of
+ case socket:is_supported(options, socket, timestamp) of
true ->
[{socket, timestamp, timestamp, default}];
false ->
[]
end ++
- case socket:supports(options, ipv6, recvpktinfo) of
+ case socket:is_supported(options, ipv6, recvpktinfo) of
true ->
[{ipv6, recvpktinfo, pktinfo, default}];
false ->
[]
end ++
- case socket:supports(options, ipv6, flowinfo) of
+ case socket:is_supported(options, ipv6, flowinfo) of
true ->
[{ipv6, flowinfo, flowinfo, default}];
false ->
[]
end ++
- case socket:supports(options, ipv6, recvhoplimit) of
+ case socket:is_supported(options, ipv6, recvhoplimit) of
true ->
[{ipv6, recvhoplimit, hoplimit, default}];
false ->
- case socket:supports(options, ipv6, hoplimit) of
+ case socket:is_supported(options, ipv6, hoplimit) of
true ->
[{ipv6, hoplimit, hoplimit, default}];
false ->
[]
end
end ++
- case socket:supports(options, ipv6, recvtclass) of
+ case socket:is_supported(options, ipv6, recvtclass) of
true ->
[{ipv6, recvtclass, tclass, 42}];
false ->
- case socket:supports(options, ipv6, tclass) of
+ case socket:is_supported(options, ipv6, tclass) of
true ->
[{ipv6, tclass, tclass, 42}];
false ->
@@ -22594,7 +24316,7 @@ api_to_recv_tcp6(_Config) when is_list(_Config) ->
tc_try(api_to_recv_tcp6,
fun() -> has_support_ipv6() end,
fun() ->
- case socket:supports(ipv6) of
+ case socket:is_supported(ipv6) of
true ->
Recv = fun(Sock, To) ->
socket:recv(Sock, 0, To)
@@ -23141,6 +24863,15 @@ reg_s_single_open_and_close_and_count(_Config) when is_list(_Config) ->
reg_s_single_open_and_close_and_count() ->
+ %% We may have some sockets already existing.
+ %% Make sure we dont count them when we test.
+ case socket:number_of() of
+ 0 ->
+ socket:use_registry(true),
+ ok;
+ N ->
+ exit({unexpected_socket_registry_use, N, socket:which_sockets()})
+ end,
SupportsIPV6 =
case (catch has_support_ipv6()) of
ok ->
@@ -23211,7 +24942,7 @@ reg_s_single_open_and_close_and_count() ->
false ->
[]
end,
-
+
i("open sockets"),
Socks =
[fun({Domain, Type, Proto}) ->
@@ -23219,7 +24950,7 @@ reg_s_single_open_and_close_and_count() ->
{ok, Sock} = socket:open(Domain, Type, Proto),
Sock
end(InitSockInfo) || InitSockInfo <- InitSockInfos],
-
+
?SLEEP(1000),
@@ -23228,12 +24959,13 @@ reg_s_single_open_and_close_and_count() ->
NumSocks1 = length(Socks),
NumberOf1 = socket:number_of(),
- i("verify (total) number of sockets(1): ~w, ~w", [NumSocks1, NumberOf1]),
+ i("verify (total) number of sockets(1): ~w, ~w",
+ [NumSocks1, NumberOf1]),
case (NumSocks1 =:= NumberOf1) of
true ->
ok;
false ->
- exit({wrong_number_of_sockets1, NumSocks1, NumberOf1})
+ reg_si_fail(wrong_number_of_sockets1, {NumSocks1, NumberOf1})
end,
@@ -23248,7 +24980,7 @@ reg_s_single_open_and_close_and_count() ->
true ->
ok;
false ->
- exit({wrong_number_of_ipv4_tcp_sockets, SiNumTCP, SrNumTCP})
+ reg_si_fail(wrong_number_of_ipv4_tcp_sockets, {SiNumTCP, SrNumTCP})
end,
@@ -23278,7 +25010,7 @@ reg_s_single_open_and_close_and_count() ->
true ->
ok;
false ->
- exit({wrong_number_of_sctp_sockets, SiNumSCTP, SrNumSCTP})
+ reg_si_fail(wrong_number_of_sctp_sockets, {SiNumSCTP, SrNumSCTP})
end,
@@ -23293,7 +25025,7 @@ reg_s_single_open_and_close_and_count() ->
true ->
ok;
false ->
- exit({wrong_number_of_ipv4_sockets, SiNumINET, SrNumINET})
+ reg_si_fail(wrong_number_of_ipv4_sockets, {SiNumINET, SrNumINET})
end,
@@ -23308,7 +25040,7 @@ reg_s_single_open_and_close_and_count() ->
true ->
ok;
false ->
- exit({wrong_number_of_ipv6_sockets, SiNumINET6, SrNumINET6})
+ reg_si_fail(wrong_number_of_ipv6_sockets, {SiNumINET6, SrNumINET6})
end,
@@ -23324,12 +25056,12 @@ reg_s_single_open_and_close_and_count() ->
true ->
ok;
false ->
- exit({wrong_number_of_local_sockets, SiNumLOCAL, SrNumLOCAL})
+ reg_si_fail(wrong_number_of_local_sockets, {SiNumLOCAL, SrNumLOCAL})
end,
%% **** Close *all* Sockets then verify Number Of Sockets ****
-
+
i("close sockets"),
lists:foreach(fun(S) ->
i("close socket"),
@@ -23340,18 +25072,22 @@ reg_s_single_open_and_close_and_count() ->
NumSocks2 = 0,
NumberOf2 = socket:number_of(),
-
+
i("verify number of sockets(2): ~w, ~w", [NumSocks2, NumberOf2]),
case (NumSocks2 =:= NumberOf2) of
true ->
ok;
false ->
- exit({wrong_number_of_sockets2, NumSocks2, NumberOf2})
+ reg_si_fail(wrong_number_of_sockets2, {NumSocks2, NumberOf2})
end,
-
+ socket:use_registry(false),
ok.
+reg_si_fail(Reason, Extra) ->
+ socket:use_registry(false),
+ exit({Reason, Extra}).
+
reg_si_num(SocksInfo, Domain)
when ((Domain =:= inet) orelse (Domain =:= inet6) orelse (Domain =:= local)) ->
reg_si_num(SocksInfo, Domain, undefined, undefined);
@@ -23429,6 +25165,111 @@ reg_sr_num2(F) ->
length(socket:which_sockets(F)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% We create a bunch of different sockets and ensure that the registry
+%% has the correct info.
+
+reg_s_optional_open_and_close_and_count(suite) ->
+ [];
+reg_s_optional_open_and_close_and_count(doc) ->
+ [];
+reg_s_optional_open_and_close_and_count(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(reg_s_optional_open_and_close_and_count,
+ fun() ->
+ ok = reg_s_optional_open_and_close_and_count()
+ end).
+
+
+reg_s_optional_open_and_close_and_count() ->
+ i("Make sure use of socket registry is enabled (regardless of default)"),
+ socket:use_registry(true),
+ #{use_registry := true} = socket:info(),
+
+ i("get current socket base count"),
+ Base = socket:number_of(),
+
+ i("create a socket and ensure its counted"),
+ {ok, S1} = socket:open(inet, dgram, udp),
+ Base1 = Base + 1,
+ case socket:number_of() of
+ Base1 ->
+ ok;
+ Invalid1 ->
+ exit({wrong_number_of_sockets1, Invalid1, Base + 1})
+ end,
+ i("close the socket and ensure its counted (back to base)"),
+ ok = socket:close(S1),
+ case socket:number_of() of
+ Base ->
+ ok;
+ Invalid2 ->
+ exit({wrong_number_of_sockets2, Invalid2, Base})
+ end,
+
+ i("create a socket with use_registry explicitly off "
+ "and ensure its not counted"),
+ {ok, S2} = socket:open(inet, dgram, udp, #{use_registry => false}),
+ case socket:number_of() of
+ Base ->
+ ok;
+ Invalid3 ->
+ exit({wrong_number_of_sockets3, Invalid3, Base})
+ end,
+ i("close the socket and ensure its not counted"),
+ ok = socket:close(S2),
+ case socket:number_of() of
+ Base ->
+ ok;
+ Invalid4 ->
+ exit({wrong_number_of_sockets4, Invalid4, Base})
+ end,
+
+ i("Globally disable use of registry"),
+ socket:use_registry(false),
+ #{use_registry := false} = socket:info(),
+ i("create a socket and ensure its not counted"),
+ {ok, S3} = socket:open(inet, dgram, udp),
+ case socket:number_of() of
+ Base ->
+ ok;
+ Invalid5 ->
+ exit({wrong_number_of_sockets5, Invalid5, Base})
+ end,
+ i("close the socket and ensure its not counted"),
+ ok = socket:close(S3),
+ case socket:number_of() of
+ Base ->
+ ok;
+ Invalid6 ->
+ exit({wrong_number_of_sockets6, Invalid6, Base})
+ end,
+
+ i("create a socket with use_registry explicitly on "
+ "and ensure its counted"),
+ {ok, S4} = socket:open(inet, dgram, udp, #{use_registry => true}),
+ case socket:number_of() of
+ Base1 ->
+ ok;
+ Invalid7 ->
+ exit({wrong_number_of_sockets7, Invalid7, Base + 1})
+ end,
+ i("close the socket and ensure counted (back to base)"),
+ ok = socket:close(S4),
+ case socket:number_of() of
+ Base ->
+ ok;
+ Invalid8 ->
+ exit({wrong_number_of_sockets8, Invalid8, Base})
+ end,
+
+ socket:use_registry(false),
+ i("done"),
+ ok.
+
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%
@@ -28556,18 +30397,18 @@ format_counters(Type, Counters) when (Type =:= listen) orelse (Type =:= traffic)
format_counters(" ", Type, Counters).
format_counters(Prefix, traffic, Counters) ->
- ReadByte = proplists:get_value(read_byte, Counters, -1),
- ReadFails = proplists:get_value(read_fails, Counters, -1),
- ReadPkg = proplists:get_value(read_pkg, Counters, -1),
- ReadPkgMax = proplists:get_value(read_pkg_max, Counters, -1),
- ReadTries = proplists:get_value(read_tries, Counters, -1),
- ReadWaits = proplists:get_value(read_waits, Counters, -1),
- WriteByte = proplists:get_value(write_byte, Counters, -1),
- WriteFails = proplists:get_value(write_fails, Counters, -1),
- WritePkg = proplists:get_value(write_pkg, Counters, -1),
- WritePkgMax = proplists:get_value(write_pkg_max, Counters, -1),
- WriteTries = proplists:get_value(write_tries, Counters, -1),
- WriteWaits = proplists:get_value(write_waits, Counters, -1),
+ ReadByte = maps:get(read_byte, Counters, -1),
+ ReadFails = maps:get(read_fails, Counters, -1),
+ ReadPkg = maps:get(read_pkg, Counters, -1),
+ ReadPkgMax = maps:get(read_pkg_max, Counters, -1),
+ ReadTries = maps:get(read_tries, Counters, -1),
+ ReadWaits = maps:get(read_waits, Counters, -1),
+ WriteByte = maps:get(write_byte, Counters, -1),
+ WriteFails = maps:get(write_fails, Counters, -1),
+ WritePkg = maps:get(write_pkg, Counters, -1),
+ WritePkgMax = maps:get(write_pkg_max, Counters, -1),
+ WriteTries = maps:get(write_tries, Counters, -1),
+ WriteWaits = maps:get(write_waits, Counters, -1),
f("~n~sNumber Of Read Bytes: ~p"
"~n~sNumber Of Read Fails: ~p"
"~n~sNumber Of Read Packages: ~p"
@@ -28594,10 +30435,10 @@ format_counters(Prefix, traffic, Counters) ->
Prefix, WritePkgMax]);
format_counters(Prefix, listen, Counters) ->
- AccSuccess = proplists:get_value(acc_success, Counters, -1),
- AccFails = proplists:get_value(acc_fails, Counters, -1),
- AccTries = proplists:get_value(acc_tries, Counters, -1),
- AccWaits = proplists:get_value(acc_waits, Counters, -1),
+ AccSuccess = maps:get(acc_success, Counters, -1),
+ AccFails = maps:get(acc_fails, Counters, -1),
+ AccTries = maps:get(acc_tries, Counters, -1),
+ AccWaits = maps:get(acc_waits, Counters, -1),
f("~n~sNumber Of Successful Accepts: ~p"
"~n~sNumber Of Failed Accepts: ~p"
"~n~sNumber Of Accept Attempts: ~p"
@@ -28652,10 +30493,17 @@ ensure_counters([{Cnt, Val}|DefCounters], Counters, Acc) ->
end.
traffic_sar_counters_validation(Counters) ->
- traffic_sar_counters_validation2(Counters, zero_counters()).
+ %% ?SEV_IPRINT("traffic_sar_counters_validation -> entry with"
+ %% "~n Counters: ~p", [Counters]),
+ traffic_sar_counters_validation2(maps:to_list(Counters),
+ zero_counters()).
traffic_sar_counters_validation(Counters, ValidateCounters) ->
- traffic_sar_counters_validation2(Counters, ensure_counters(ValidateCounters)).
+ %% ?SEV_IPRINT("traffic_sar_counters_validation -> entry with"
+ %% "~n Counters: ~p"
+ %% "~n Validate Counters: ~p", [Counters, ValidateCounters]),
+ traffic_sar_counters_validation2(maps:to_list(Counters),
+ ensure_counters(ValidateCounters)).
traffic_sar_counters_validation2(Counters, []) ->
%% ?SEV_IPRINT("traffic_sar_counters_validation2 -> Remaining Counters: "
@@ -28682,7 +30530,8 @@ traffic_sar_counters_validation2(Counters, [{Cnt, Val}|ValidateCounters]) ->
Counters2 = lists:keydelete(Cnt, 1, Counters),
traffic_sar_counters_validation2(Counters2, ValidateCounters);
{value, {Cnt, InvVal}} ->
- ?SEV_EPRINT("traffic_sar_counters_validation2 -> ~w validation failed: "
+ ?SEV_EPRINT("traffic_sar_counters_validation2 -> "
+ "~w validation failed: "
"~n Expected Value: ~p"
"~n Actual Value: ~p", [Cnt, Val, InvVal]),
{error, {invalid_counter, Cnt, InvVal, Val}};
@@ -41134,8 +42983,8 @@ has_support_ip_recvtos() ->
has_support_socket_option_ip(recvtos).
has_support_ip_recvtos_and_or_sock_timestamp() ->
- case (socket:supports(options, ip, recvtos) orelse
- socket:supports(options, socket, timestamp)) of
+ case (socket:is_supported(options, ip, recvtos) orelse
+ socket:is_supported(options, socket, timestamp)) of
true ->
ok;
false ->
@@ -41159,8 +43008,8 @@ has_support_ipv6_flowinfo() ->
has_support_socket_option_ipv6(flowinfo).
has_support_ipv6_hoplimit_or_recvhoplimit() ->
- %% case (socket:supports(options, ipv6, recvhoplimit) orelse
- %% socket:supports(options, ipv6, hoplimit)) of
+ %% case (socket:is_supported(options, ipv6, recvhoplimit) orelse
+ %% socket:is_supported(options, ipv6, hoplimit)) of
case is_any_options_supported([{ipv6, recvhoplimit}, {ipv6, hoplimit}]) of
true ->
ok;
@@ -41225,7 +43074,7 @@ has_support_socket_option_udp(Opt) ->
has_support_socket_option(Level, Option) ->
- case socket:supports(options, Level, Option) of
+ case socket:is_supported(options, Level, Option) of
true ->
ok;
false ->
@@ -41233,7 +43082,7 @@ has_support_socket_option(Level, Option) ->
end.
is_any_options_supported(Options) ->
- Pred = fun({Level, Option}) -> socket:supports(options, Level, Option) end,
+ Pred = fun({Level, Option}) -> socket:is_supported(options, Level, Option) end,
lists:any(Pred, Options).
@@ -41255,7 +43104,7 @@ has_support_recv_flag(Flag) ->
has_support_send_or_recv_flag("Recv", recv_flags, Flag).
has_support_send_or_recv_flag(Pre, Key, Flag) ->
- case socket:supports(Key, Flag) of
+ case socket:is_supported(Key, Flag) of
true ->
ok;
false ->
@@ -41271,6 +43120,28 @@ is_good_enough_linux(CondVsn) ->
is_good_enough_darwin(CondVsn) ->
is_good_enough_platform(unix, darwin, CondVsn).
+is_good_enough_montavista(_Vsn) ->
+ %% We have *one* old M, which have kernel version 2.6.10.
+ %% So if its that kernel version, we only need to check
+ %% if its M (no need to figure out the version of M).
+ case os:type() of
+ {unix, linux} ->
+ case os:version() of
+ {2,6,10} ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "MontaVista" ++ _ = V -> % Stone age MontaVista => Skip
+ skip(V);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
+
is_good_enough_platform(Family, Name, CondVsn) ->
case os:type() of
{Family, Name} ->
@@ -41291,6 +43162,9 @@ is_not_freebsd() ->
is_not_openbsd() ->
is_not_platform(openbsd, "OpenBSD").
+is_not_netbsd() ->
+ is_not_platform(netbsd, "NetBSD").
+
is_not_darwin() ->
is_not_platform(darwin, "Darwin").
@@ -41317,7 +43191,7 @@ has_support_unix_domain_socket() ->
{win32, _} ->
skip("Not supported");
_ ->
- case socket:supports(local) of
+ case socket:is_supported(local) of
true ->
ok;
false ->
@@ -41330,7 +43204,7 @@ has_support_sctp() ->
{win32, _} ->
skip("Not supported");
_ ->
- case socket:supports(sctp) of
+ case socket:is_supported(sctp) of
true ->
ok;
false ->
@@ -41539,6 +43413,1020 @@ tc_which_name() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This function prints various host info, which might be usefull
+%% when analyzing the test suite (results).
+%% It also returns a "factor" that can be used when deciding
+%% the load for some test cases (traffic). Such as run time or
+%% number of iterations. This only works for some OSes (linux).
+%% Also, mostly it just returns the factor 1.
+%% At this time we just look at BogoMIPS!
+analyze_and_print_host_info() ->
+ {OsFam, OsName} = os:type(),
+ Version =
+ case os:version() of
+ {Maj, Min, Rel} ->
+ f("~w.~w.~w", [Maj, Min, Rel]);
+ VStr ->
+ VStr
+ end,
+ case {OsFam, OsName} of
+ {unix, linux} ->
+ analyze_and_print_linux_host_info(Version);
+ {unix, openbsd} ->
+ analyze_and_print_openbsd_host_info(Version);
+ {unix, freebsd} ->
+ analyze_and_print_freebsd_host_info(Version);
+ {unix, netbsd} ->
+ analyze_and_print_netbsd_host_info(Version);
+ {unix, sunos} ->
+ analyze_and_print_solaris_host_info(Version);
+ {win32, nt} ->
+ analyze_and_print_win_host_info(Version);
+ _ ->
+ io:format("OS Family: ~p"
+ "~n OS Type: ~p"
+ "~n Version: ~p"
+ "~n Num Schedulers: ~s"
+ "~n", [OsFam, OsName, Version, str_num_schedulers()]),
+ num_schedulers_to_factor()
+ end.
+
+str_num_schedulers() ->
+ try erlang:system_info(schedulers) of
+ N -> f("~w", [N])
+ catch
+ _:_:_ -> "-"
+ end.
+
+num_schedulers_to_factor() ->
+ try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end.
+
+
+
+%% --- Linux ---
+
+linux_which_distro(Version) ->
+ case file:read_file_info("/etc/issue") of
+ {ok, _} ->
+ case [string:trim(S) ||
+ S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of
+ [DistroStr|_] ->
+ io:format("Linux: ~s"
+ "~n ~s"
+ "~n",
+ [Version, DistroStr]),
+ case DistroStr of
+ "Wind River Linux" ++ _ ->
+ wind_river;
+ "MontaVista" ++ _ ->
+ montavista;
+ "Yellow Dog" ++ _ ->
+ yellow_dog;
+ _ ->
+ other
+ end;
+ X ->
+ io:format("Linux: ~s"
+ "~n ~p"
+ "~n",
+ [Version, X]),
+ other
+ end;
+ _ ->
+ io:format("Linux: ~s"
+ "~n", [Version]),
+ other
+ end.
+
+
+analyze_and_print_linux_host_info(Version) ->
+ Distro = linux_which_distro(Version),
+ Factor =
+ case (catch linux_which_cpuinfo(Distro)) of
+ {ok, {CPU, BogoMIPS}} ->
+ io:format("CPU: "
+ "~n Model: ~s"
+ "~n BogoMIPS: ~w"
+ "~n Num Schedulers: ~s"
+ "~n", [CPU, BogoMIPS, str_num_schedulers()]),
+ if
+ (BogoMIPS > 20000) ->
+ 1;
+ (BogoMIPS > 10000) ->
+ 2;
+ (BogoMIPS > 5000) ->
+ 3;
+ (BogoMIPS > 2000) ->
+ 5;
+ (BogoMIPS > 1000) ->
+ 8;
+ true ->
+ 10
+ end;
+ {ok, CPU} ->
+ io:format("CPU: "
+ "~n Model: ~s"
+ "~n Num Schedulers: ~s"
+ "~n", [CPU, str_num_schedulers()]),
+ num_schedulers_to_factor();
+ _ ->
+ 5
+ end,
+ %% Check if we need to adjust the factor because of the memory
+ try linux_which_meminfo() of
+ AddFactor ->
+ Factor + AddFactor
+ catch
+ _:_:_ ->
+ Factor
+ end.
+
+
+linux_cpuinfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/cpuinfo").
+
+linux_cpuinfo_cpu() ->
+ case linux_cpuinfo_lookup("cpu") of
+ [Model] ->
+ Model;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_motherboard() ->
+ case linux_cpuinfo_lookup("motherboard") of
+ [MB] ->
+ MB;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_bogomips() ->
+ case linux_cpuinfo_lookup("bogomips") of
+ BMips when is_list(BMips) ->
+ try lists:sum([bogomips_to_int(BM) || BM <- BMips])
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_total_bogomips() ->
+ case linux_cpuinfo_lookup("total bogomips") of
+ [TBM] ->
+ try bogomips_to_int(TBM)
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+bogomips_to_int(BM) ->
+ try list_to_float(BM) of
+ F ->
+ floor(F)
+ catch
+ _:_:_ ->
+ try list_to_integer(BM) of
+ I ->
+ I
+ catch
+ _:_:_ ->
+ throw(noinfo)
+ end
+ end.
+
+linux_cpuinfo_model() ->
+ case linux_cpuinfo_lookup("model") of
+ [M] ->
+ M;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_platform() ->
+ case linux_cpuinfo_lookup("platform") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_model_name() ->
+ case linux_cpuinfo_lookup("model name") of
+ [P|_] ->
+ P;
+ _X ->
+ "-"
+ end.
+
+linux_cpuinfo_processor() ->
+ case linux_cpuinfo_lookup("Processor") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_which_cpuinfo(montavista) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(yellow_dog) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ {ok, CPU};
+
+linux_which_cpuinfo(wind_river) ->
+ CPU =
+ case linux_cpuinfo_model() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_platform() of
+ "-" ->
+ Model;
+ Platform ->
+ Model ++ " (" ++ Platform ++ ")"
+ end
+ end,
+ case linux_cpuinfo_total_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(other) ->
+ %% Check for x86 (Intel or AMD)
+ CPU =
+ case linux_cpuinfo_model_name() of
+ "-" ->
+ %% ARM (at least some distros...)
+ case linux_cpuinfo_processor() of
+ "-" ->
+ %% Ok, we give up
+ throw(noinfo);
+ Proc ->
+ Proc
+ end;
+ ModelName ->
+ ModelName
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end.
+
+linux_meminfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/meminfo").
+
+linux_meminfo_memtotal() ->
+ case linux_meminfo_lookup("MemTotal") of
+ [X] ->
+ X;
+ _ ->
+ "-"
+ end.
+
+%% We *add* the value this return to the Factor.
+linux_which_meminfo() ->
+ case linux_meminfo_memtotal() of
+ "-" ->
+ 0;
+ MemTotal ->
+ io:format("Memory:"
+ "~n ~s"
+ "~n", [MemTotal]),
+ case string:tokens(MemTotal, [$ ]) of
+ [MemSzStr, MemUnit] ->
+ MemSz2 = list_to_integer(MemSzStr),
+ MemSz3 =
+ case string:to_lower(MemUnit) of
+ "kb" ->
+ MemSz2;
+ "mb" ->
+ MemSz2*1024;
+ "gb" ->
+ MemSz2*1024*1024;
+ _ ->
+ throw(noinfo)
+ end,
+ if
+ (MemSz3 >= 8388608) ->
+ 0;
+ (MemSz3 >= 4194304) ->
+ 1;
+ (MemSz3 >= 2097152) ->
+ 3;
+ true ->
+ 5
+ end;
+ _X ->
+ 0
+ end
+ end.
+
+
+linux_info_lookup(Key, File) ->
+ try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of
+ Info ->
+ linux_info_lookup_collect(Key, Info, [])
+ catch
+ _:_:_ ->
+ "-"
+ end.
+
+linux_info_lookup_collect(_Key, [], Values) ->
+ lists:reverse(Values);
+linux_info_lookup_collect(Key, [Key, Value|Rest], Values) ->
+ linux_info_lookup_collect(Key, Rest, [Value|Values]);
+linux_info_lookup_collect(_, _, Values) ->
+ lists:reverse(Values).
+
+
+%% --- OpenBSD ---
+
+%% Just to be clear: This is ***not*** scientific...
+analyze_and_print_openbsd_host_info(Version) ->
+ io:format("OpenBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ Extract =
+ fun(Key) ->
+ string:tokens(string:trim(os:cmd("sysctl " ++ Key)), [$=])
+ end,
+ try
+ begin
+ CPU =
+ case Extract("hw.model") of
+ ["hw.model", Model] ->
+ string:trim(Model);
+ _ ->
+ "-"
+ end,
+ CPUSpeed =
+ case Extract("hw.cpuspeed") of
+ ["hw.cpuspeed", Speed] ->
+ list_to_integer(Speed);
+ _ ->
+ -1
+ end,
+ NCPU =
+ case Extract("hw.ncpufound") of
+ ["hw.ncpufound", N] ->
+ list_to_integer(N);
+ _ ->
+ -1
+ end,
+ Memory =
+ case Extract("hw.physmem") of
+ ["hw.physmem", PhysMem] ->
+ list_to_integer(PhysMem) div 1024;
+ _ ->
+ -1
+ end,
+ io:format("CPU:"
+ "~n Model: ~s"
+ "~n Speed: ~w"
+ "~n N: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n", [CPU, CPUSpeed, NCPU, Memory]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ CPUFactor + MemAddFactor
+ end
+ catch
+ _:_:_ ->
+ 1
+ end.
+
+
+%% --- FreeBSD ---
+
+analyze_and_print_freebsd_host_info(Version) ->
+ io:format("FreeBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ %% This test require that the program 'sysctl' is in the path.
+ %% First test with 'which sysctl', if that does not work
+ %% try with 'which /sbin/sysctl'. If that does not work either,
+ %% we skip the test...
+ try
+ begin
+ SysCtl =
+ case string:trim(os:cmd("which sysctl")) of
+ [] ->
+ case string:trim(os:cmd("which /sbin/sysctl")) of
+ [] ->
+ throw(sysctl);
+ SC2 ->
+ SC2
+ end;
+ SC1 ->
+ SC1
+ end,
+ Extract =
+ fun(Key) ->
+ string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
+ [$:])
+ end,
+ CPU = analyze_freebsd_cpu(Extract),
+ CPUSpeed = analyze_freebsd_cpu_speed(Extract),
+ NCPU = analyze_freebsd_ncpu(Extract),
+ Memory = analyze_freebsd_memory(Extract),
+ io:format("CPU:"
+ "~n Model: ~s"
+ "~n Speed: ~w"
+ "~n N: ~w"
+ "~n Num Schedulers: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n",
+ [CPU, CPUSpeed, NCPU,
+ erlang:system_info(schedulers), Memory]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU =:= -1) ->
+ 1;
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ CPUFactor + MemAddFactor
+ end
+ catch
+ _:_:_ ->
+ io:format("CPU:"
+ "~n Num Schedulers: ~w"
+ "~n", [erlang:system_info(schedulers)]),
+ case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end
+ end.
+
+analyze_freebsd_cpu(Extract) ->
+ analyze_freebsd_item(Extract, "hw.model", fun(X) -> X end, "-").
+
+analyze_freebsd_cpu_speed(Extract) ->
+ analyze_freebsd_item(Extract,
+ "hw.clockrate",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_freebsd_ncpu(Extract) ->
+ analyze_freebsd_item(Extract,
+ "hw.ncpu",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_freebsd_memory(Extract) ->
+ analyze_freebsd_item(Extract,
+ "hw.physmem",
+ fun(X) -> list_to_integer(X) div 1024 end,
+ -1).
+
+analyze_freebsd_item(Extract, Key, Process, Default) ->
+ try
+ begin
+ case Extract(Key) of
+ [Key, Model] ->
+ Process(string:trim(Model));
+ _ ->
+ Default
+ end
+ end
+ catch
+ _:_:_ ->
+ Default
+ end.
+
+
+%% --- NetBSD ---
+
+analyze_and_print_netbsd_host_info(Version) ->
+ io:format("NetBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ %% This test require that the program 'sysctl' is in the path.
+ %% First test with 'which sysctl', if that does not work
+ %% try with 'which /sbin/sysctl'. If that does not work either,
+ %% we skip the test...
+ try
+ begin
+ SysCtl =
+ case string:trim(os:cmd("which sysctl")) of
+ [] ->
+ case string:trim(os:cmd("which /sbin/sysctl")) of
+ [] ->
+ throw(sysctl);
+ SC2 ->
+ SC2
+ end;
+ SC1 ->
+ SC1
+ end,
+ Extract =
+ fun(Key) ->
+ [string:trim(S) ||
+ S <-
+ string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
+ [$=])]
+ end,
+ CPU = analyze_netbsd_cpu(Extract),
+ Machine = analyze_netbsd_machine(Extract),
+ Arch = analyze_netbsd_machine_arch(Extract),
+ CPUSpeed = analyze_netbsd_cpu_speed(Extract),
+ NCPU = analyze_netbsd_ncpu(Extract),
+ Memory = analyze_netbsd_memory(Extract),
+ io:format("CPU:"
+ "~n Model: ~s (~s, ~s)"
+ "~n Speed: ~w MHz"
+ "~n N: ~w"
+ "~n Num Schedulers: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n",
+ [CPU, Machine, Arch, CPUSpeed, NCPU,
+ erlang:system_info(schedulers), Memory]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU =:= -1) ->
+ 1;
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ CPUFactor + MemAddFactor
+ end
+ catch
+ _:_:_ ->
+ io:format("CPU:"
+ "~n Num Schedulers: ~w"
+ "~n", [erlang:system_info(schedulers)]),
+ case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end
+ end.
+
+analyze_netbsd_cpu(Extract) ->
+ analyze_netbsd_item(Extract, "hw.model", fun(X) -> X end, "-").
+
+analyze_netbsd_machine(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine", fun(X) -> X end, "-").
+
+analyze_netbsd_machine_arch(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine_arch", fun(X) -> X end, "-").
+
+analyze_netbsd_cpu_speed(Extract) ->
+ analyze_netbsd_item(Extract, "machdep.dmi.processor-frequency",
+ fun(X) -> case string:tokens(X, [$\ ]) of
+ [MHz, "MHz"] ->
+ list_to_integer(MHz);
+ _ ->
+ -1
+ end
+ end, "-").
+
+analyze_netbsd_ncpu(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.ncpu",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_netbsd_memory(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.physmem64",
+ fun(X) -> list_to_integer(X) div 1024 end,
+ -1).
+
+analyze_netbsd_item(Extract, Key, Process, Default) ->
+ analyze_freebsd_item(Extract, Key, Process, Default).
+
+
+
+%% --- Solaris ---
+
+analyze_and_print_solaris_host_info(Version) ->
+ Release =
+ case file:read_file_info("/etc/release") of
+ {ok, _} ->
+ case [string:trim(S) || S <- string:tokens(os:cmd("cat /etc/release"), [$\n])] of
+ [Rel | _] ->
+ Rel;
+ _ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end,
+ %% Display the firmware device tree root properties (prtconf -b)
+ Props = [list_to_tuple([string:trim(PS) || PS <- Prop]) ||
+ Prop <- [string:tokens(S, [$:]) ||
+ S <- string:tokens(os:cmd("prtconf -b"), [$\n])]],
+ BannerName = case lists:keysearch("banner-name", 1, Props) of
+ {value, {_, BN}} ->
+ string:trim(BN);
+ _ ->
+ "-"
+ end,
+ InstructionSet =
+ case string:trim(os:cmd("isainfo -k")) of
+ "Pseudo-terminal will not" ++ _ ->
+ "-";
+ IS ->
+ IS
+ end,
+ PtrConf = [list_to_tuple([string:trim(S) || S <- Items]) || Items <- [string:tokens(S, [$:]) || S <- string:tokens(os:cmd("prtconf"), [$\n])], length(Items) > 1],
+ SysConf =
+ case lists:keysearch("System Configuration", 1, PtrConf) of
+ {value, {_, SC}} ->
+ SC;
+ _ ->
+ "-"
+ end,
+ NumPhysProc =
+ begin
+ NPPStr = string:trim(os:cmd("psrinfo -p")),
+ try list_to_integer(NPPStr) of
+ _ ->
+ NPPStr
+ catch
+ _:_:_ ->
+ "-"
+ end
+ end,
+ NumProc = try integer_to_list(length(string:tokens(os:cmd("psrinfo"), [$\n]))) of
+ NPStr ->
+ NPStr
+ catch
+ _:_:_ ->
+ "-"
+ end,
+ MemSz =
+ case lists:keysearch("Memory size", 1, PtrConf) of
+ {value, {_, MS}} ->
+ MS;
+ _ ->
+ "-"
+ end,
+ io:format("Solaris: ~s"
+ "~n Release: ~s"
+ "~n Banner Name: ~s"
+ "~n Instruction Set: ~s"
+ "~n CPUs: ~s (~s)"
+ "~n System Config: ~s"
+ "~n Memory Size: ~s"
+ "~n Num Schedulers: ~s"
+ "~n~n", [Version, Release, BannerName, InstructionSet,
+ NumPhysProc, NumProc,
+ SysConf, MemSz,
+ str_num_schedulers()]),
+ MemFactor =
+ try string:tokens(MemSz, [$ ]) of
+ [SzStr, "Mega" ++ _] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz > 8192 ->
+ 0;
+ Sz when Sz > 4096 ->
+ 1;
+ Sz when Sz > 2048 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ [SzStr, "Giga" ++ _] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz > 8 ->
+ 0;
+ Sz when Sz > 4 ->
+ 1;
+ Sz when Sz > 2 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ _ ->
+ 10
+ catch
+ _:_:_ ->
+ 10
+ end,
+ try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end + MemFactor.
+
+
+%% --- Windows ---
+
+analyze_and_print_win_host_info(Version) ->
+ SysInfo = which_win_system_info(),
+ OsName = win_sys_info_lookup(os_name, SysInfo),
+ OsVersion = win_sys_info_lookup(os_version, SysInfo),
+ SysMan = win_sys_info_lookup(system_manufacturer, SysInfo),
+ SysMod = win_sys_info_lookup(system_model, SysInfo),
+ NumProcs = win_sys_info_lookup(num_processors, SysInfo),
+ TotPhysMem = win_sys_info_lookup(total_phys_memory, SysInfo),
+ io:format("Windows: ~s"
+ "~n OS Version: ~s (~p)"
+ "~n System Manufacturer: ~s"
+ "~n System Model: ~s"
+ "~n Number of Processor(s): ~s"
+ "~n Total Physical Memory: ~s"
+ "~n Num Schedulers: ~s"
+ "~n", [OsName, OsVersion, Version,
+ SysMan, SysMod, NumProcs, TotPhysMem,
+ str_num_schedulers()]),
+ MemFactor =
+ try
+ begin
+ [MStr, MUnit|_] =
+ string:tokens(lists:delete($,, TotPhysMem), [$\ ]),
+ case string:to_lower(MUnit) of
+ "gb" ->
+ try list_to_integer(MStr) of
+ M when M > 8 ->
+ 0;
+ M when M > 4 ->
+ 1;
+ M when M > 2 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ "mb" ->
+ try list_to_integer(MStr) of
+ M when M > 8192 ->
+ 0;
+ M when M > 4096 ->
+ 1;
+ M when M > 2048 ->
+ 2;
+ _ ->
+ 5
+ catch
+ _:_:_ ->
+ 10
+ end;
+ _ ->
+ 10
+ end
+ end
+ catch
+ _:_:_ ->
+ 10
+ end,
+ CPUFactor =
+ case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ CPUFactor + MemFactor.
+
+win_sys_info_lookup(Key, SysInfo) ->
+ win_sys_info_lookup(Key, SysInfo, "-").
+
+win_sys_info_lookup(Key, SysInfo, Def) ->
+ case lists:keysearch(Key, 1, SysInfo) of
+ {value, {Key, Value}} ->
+ Value;
+ false ->
+ Def
+ end.
+
+%% This function only extracts the prop(s) we actually care about!
+%% On some hosts this (systeminfo) takes a *long time* (several minutes).
+%% And since there is no way to provide a timeout to the os command call,
+%% we have to wrap it in a process.
+which_win_system_info() ->
+ F = fun() ->
+ try
+ begin
+ SysInfo = os:cmd("systeminfo"),
+ process_win_system_info(
+ string:tokens(SysInfo, [$\r, $\n]), [])
+ end
+ catch
+ C:E:S ->
+ io:format("Failed get or process System info: "
+ " Error Class: ~p"
+ " Error: ~p"
+ " Stack: ~p"
+ "~n", [C, E, S]),
+ []
+ end
+ end,
+ ?LIB:pcall(F, ?MINS(1), []).
+
+process_win_system_info([], Acc) ->
+ Acc;
+process_win_system_info([H|T], Acc) ->
+ case string:tokens(H, [$:]) of
+ [Key, Value] ->
+ case string:to_lower(Key) of
+ "os name" ->
+ process_win_system_info(T,
+ [{os_name, string:trim(Value)}|Acc]);
+ "os version" ->
+ process_win_system_info(T,
+ [{os_version, string:trim(Value)}|Acc]);
+ "system manufacturer" ->
+ process_win_system_info(T,
+ [{system_manufacturer, string:trim(Value)}|Acc]);
+ "system model" ->
+ process_win_system_info(T,
+ [{system_model, string:trim(Value)}|Acc]);
+ "processor(s)" ->
+ [NumProcStr|_] = string:tokens(Value, [$\ ]),
+ T2 = lists:nthtail(list_to_integer(NumProcStr), T),
+ process_win_system_info(T2,
+ [{num_processors, NumProcStr}|Acc]);
+ "total physical memory" ->
+ process_win_system_info(T,
+ [{total_phys_memory, string:trim(Value)}|Acc]);
+ _ ->
+ process_win_system_info(T, Acc)
+ end;
+ _ ->
+ process_win_system_info(T, Acc)
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
l2a(S) when is_list(S) ->
list_to_atom(S).
@@ -41551,46 +44439,6 @@ b2l(B) when is_binary(B) ->
f(F, A) ->
lists:flatten(io_lib:format(F, A)).
-%% p(F) ->
-%% p(F, []).
-
-%% p(F, A) ->
-%% p(F, A, "", "").
-
-%% p(F, A, Before, After) when is_list(Before) andalso is_list(After) ->
-%% TcName =
-%% case get(tc_name) of
-%% undefined ->
-%% case get(sname) of
-%% undefined ->
-%% "";
-%% SName when is_list(SName) ->
-%% SName
-%% end;
-%% Name when is_list(Name) ->
-%% Name
-%% end,
-%% FStr = f("*** [~s][~s][~p] " ++ F ++ "~n",
-%% [formated_timestamp(),TcName,self()|A]),
-%% i(Before ++ FStr ++ After, []).
-
-
-%% d(F, A) ->
-%% d(get(dbg_fd), F, A).
-
-%% d(undefined, F, A) ->
-%% [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]),
-%% DbgFileName = f("~s-dbg.txt", [NodeNameStr]),
-%% case file:open(DbgFileName, [write]) of
-%% {ok, FD} ->
-%% put(dbg_fd, FD),
-%% d(FD, F, A);
-%% {error, Reason} ->
-%% exit({failed_open_dbg_file, Reason})
-%% end;
-%% d(FD, F, A) ->
-%% io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]).
-
i(F) ->
i(F, []).
diff --git a/erts/emulator/test/socket_test_evaluator.erl b/lib/kernel/test/socket_test_evaluator.erl
index 694f0d5f1e..694f0d5f1e 100644
--- a/erts/emulator/test/socket_test_evaluator.erl
+++ b/lib/kernel/test/socket_test_evaluator.erl
diff --git a/erts/emulator/test/socket_test_evaluator.hrl b/lib/kernel/test/socket_test_evaluator.hrl
index 5be49dc022..5be49dc022 100644
--- a/erts/emulator/test/socket_test_evaluator.hrl
+++ b/lib/kernel/test/socket_test_evaluator.hrl
diff --git a/erts/emulator/test/socket_test_lib.erl b/lib/kernel/test/socket_test_lib.erl
index 6440788651..13d4a4ba8e 100644
--- a/erts/emulator/test/socket_test_lib.erl
+++ b/lib/kernel/test/socket_test_lib.erl
@@ -21,8 +21,12 @@
-module(socket_test_lib).
-export([
+ %% Process info
pi/1, pi/2, pi/3,
+ %% Proxy call
+ pcall/3,
+
%% Time stuff
timestamp/0,
tdiff/2,
@@ -67,6 +71,21 @@ pi(Node, Pid, Item) when is_pid(Pid) andalso is_atom(Item) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+pcall(F, Timeout, Default)
+ when is_function(F, 0) andalso is_integer(Timeout) andalso (Timeout > 0) ->
+ {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end),
+ receive
+ {'DOWN', M, process, P, Reply} ->
+ Reply
+ after Timeout ->
+ erlang:demonitor(M, [flush]),
+ exit(P, kill),
+ Default
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
timestamp() ->
os:timestamp().
@@ -100,7 +119,7 @@ f(F, A) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
has_support_ipv6() ->
- case socket:supports(ipv6) of
+ case socket:is_supported(ipv6) of
true ->
ok;
false ->
diff --git a/erts/emulator/test/socket_test_logger.erl b/lib/kernel/test/socket_test_logger.erl
index f5d4c8c7b2..f5d4c8c7b2 100644
--- a/erts/emulator/test/socket_test_logger.erl
+++ b/lib/kernel/test/socket_test_logger.erl
diff --git a/erts/emulator/test/socket_test_ttest.hrl b/lib/kernel/test/socket_test_ttest.hrl
index 1a004a9a7a..1a004a9a7a 100644
--- a/erts/emulator/test/socket_test_ttest.hrl
+++ b/lib/kernel/test/socket_test_ttest.hrl
diff --git a/erts/emulator/test/socket_test_ttest_client.hrl b/lib/kernel/test/socket_test_ttest_client.hrl
index 84e736cc34..84e736cc34 100644
--- a/erts/emulator/test/socket_test_ttest_client.hrl
+++ b/lib/kernel/test/socket_test_ttest_client.hrl
diff --git a/erts/emulator/test/socket_test_ttest_lib.erl b/lib/kernel/test/socket_test_ttest_lib.erl
index ebce16dcfa..ebce16dcfa 100644
--- a/erts/emulator/test/socket_test_ttest_lib.erl
+++ b/lib/kernel/test/socket_test_ttest_lib.erl
diff --git a/erts/emulator/test/socket_test_ttest_tcp_client.erl b/lib/kernel/test/socket_test_ttest_tcp_client.erl
index f28819ca69..f28819ca69 100644
--- a/erts/emulator/test/socket_test_ttest_tcp_client.erl
+++ b/lib/kernel/test/socket_test_ttest_tcp_client.erl
diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl b/lib/kernel/test/socket_test_ttest_tcp_client_gen.erl
index 65a3a94d38..65a3a94d38 100644
--- a/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl
+++ b/lib/kernel/test/socket_test_ttest_tcp_client_gen.erl
diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_client_socket.erl
index 2fb1242028..2fb1242028 100644
--- a/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl
+++ b/lib/kernel/test/socket_test_ttest_tcp_client_socket.erl
diff --git a/erts/emulator/test/socket_test_ttest_tcp_gen.erl b/lib/kernel/test/socket_test_ttest_tcp_gen.erl
index 5d20e49359..5d20e49359 100644
--- a/erts/emulator/test/socket_test_ttest_tcp_gen.erl
+++ b/lib/kernel/test/socket_test_ttest_tcp_gen.erl
diff --git a/erts/emulator/test/socket_test_ttest_tcp_server.erl b/lib/kernel/test/socket_test_ttest_tcp_server.erl
index 2394dc7924..2394dc7924 100644
--- a/erts/emulator/test/socket_test_ttest_tcp_server.erl
+++ b/lib/kernel/test/socket_test_ttest_tcp_server.erl
diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl b/lib/kernel/test/socket_test_ttest_tcp_server_gen.erl
index fdf40f1369..fdf40f1369 100644
--- a/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl
+++ b/lib/kernel/test/socket_test_ttest_tcp_server_gen.erl
diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_server_socket.erl
index 4045bf4e4e..4045bf4e4e 100644
--- a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl
+++ b/lib/kernel/test/socket_test_ttest_tcp_server_socket.erl
diff --git a/erts/emulator/test/socket_test_ttest_tcp_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_socket.erl
index a1e08e605c..a1e08e605c 100644
--- a/erts/emulator/test/socket_test_ttest_tcp_socket.erl
+++ b/lib/kernel/test/socket_test_ttest_tcp_socket.erl
diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk
index e578f3dde4..c04299ae88 100644
--- a/lib/kernel/vsn.mk
+++ b/lib/kernel/vsn.mk
@@ -1 +1 @@
-KERNEL_VSN = 6.5.2
+KERNEL_VSN = 7.3
diff --git a/lib/megaco/Makefile b/lib/megaco/Makefile
index 1099c2a634..624c4b0619 100644
--- a/lib/megaco/Makefile
+++ b/lib/megaco/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2019. All Rights Reserved.
+# Copyright Ericsson AB 1999-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -110,7 +110,7 @@ DIA_ANALYSIS = $(basename $(DIA_PLT)).dialyzer_analysis
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
-.PHONY: reconf conf dconf econf configure setup info version \
+.PHONY: reconf conf dconf econf configure setup info_megaco version \
app_install dialyzer
reconf:
@@ -136,16 +136,15 @@ configure: configure.in
setup:
(cd src && $(MAKE) $@)
-info:
+info_megaco:
@echo "APP_RELEASE_DIR: $(APP_RELEASE_DIR)"
@echo "APP_DIR: $(APP_DIR)"
@echo "APP_TAR_FILE: $(APP_TAR_FILE)"
@echo "OTP_INSTALL_DIR: $(OTP_INSTALL_DIR)"
@echo "APP_INSTALL_DIR: $(APP_INSTALL_DIR)"
@echo ""
- @echo "DIA_PLT: $(DIA_PLT)"
- @echo "DIA_ANALYSIS: $(DIA_ANALYSIS)"
- @echo ""
+
+info: info_megaco
version:
@echo "$(VSN)"
@@ -204,31 +203,6 @@ tar: $(APP_TAR_FILE)
$(APP_TAR_FILE): $(APP_DIR)
(cd "$(APP_RELEASE_DIR)"; gtar zcf "$(subst $(space),\ ,$@)" $(DIR_NAME))
-dialyzer_plt: $(DIA_PLT)
-
-$(DIA_PLT): Makefile
- @echo "Building $(APPLICATION) plt file"
- @dialyzer --build_plt \
- --output_plt $@ \
- -r ../$(APPLICATION)/ebin \
- ../../lib/kernel/ebin \
- ../../lib/stdlib/ebin \
- ../../lib/runtime_tools/ebin \
- ../../lib/syntax_tools/ebin \
- ../../lib/asn1/ebin \
- ../../lib/debugger/ebin \
- ../../lib/et/ebin \
- ../../lib/mnesia/ebin \
- ../../lib/crypto/ebin \
- ../../lib/compiler/ebin \
- ../../lib/wx/ebin \
- ../../lib/hipe/ebin \
- ../../erts/preloaded/ebin \
- --output $(DIA_ANALYSIS) \
- --verbose
-
-dialyzer: $(DIA_PLT)
- @echo "Running dialyzer on $(APPLICATION)"
- @dialyzer --plt $< \
- ../$(APPLICATION)/ebin \
- --verbose
+DIA_PLT_APPS=asn1 runtime_tools et debugger
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/megaco/configure.in b/lib/megaco/configure.in
index bf22776ae0..789fbbed04 100644
--- a/lib/megaco/configure.in
+++ b/lib/megaco/configure.in
@@ -25,12 +25,7 @@ dnl define([AC_CACHE_SAVE], )dnl
AC_INIT(vsn.mk)
-if test -z "$ERL_TOP" || test ! -d $ERL_TOP ; then
- AC_CONFIG_AUX_DIRS(autoconf)
-else
- erl_top=${ERL_TOP}
- AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
-fi
+AC_CONFIG_AUX_DIRS(${ERL_TOP}/erts/autoconf)
if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
AC_CANONICAL_HOST
diff --git a/lib/megaco/doc/src/Makefile b/lib/megaco/doc/src/Makefile
index 5e085b60b0..b8488b4a13 100644
--- a/lib/megaco/doc/src/Makefile
+++ b/lib/megaco/doc/src/Makefile
@@ -27,11 +27,6 @@ include ../../vsn.mk
VSN=$(MEGACO_VSN)
APPLICATION=megaco
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
# ----------------------------------------------------
# Target Specs
@@ -39,130 +34,23 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
include files.mk
-
# ----------------------------------------------------
XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) \
$(XML_PART_FILES) $(XML_CHAPTER_FILES)
-INTERNAL_HTML_FILES = $(TECHNICAL_DESCR_FILES:%.xml=$(HTMLDIR)/%.html)
-
-HTML_APP_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html)
-HTML_EXTRA_FILES = $(XML_EXTRA_FILES:%.xml=$(HTMLDIR)/%.html)
-HTML_PART_FILES = $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-HTML_FILES = $(HTML_APP_FILES) $(HTML_EXTRA_FILES) $(HTML_PART_FILES)
-
-INFO_FILE = ../../info
-
-HTML_REF3_FILES = $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html)
-HTML_CHAPTER_FILES = $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-EXTRA_FILES = \
- $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
- $(HTML_REF3_FILES) \
- $(HTML_CHAPTER_FILES)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
STANDARD_DIR = ../standard
STANDARDS = $(STANDARD_DIR)/rfc3525.txt \
$(STANDARD_DIR)/rfc4234.txt \
$(STANDARD_DIR)/rfc4566.txt \
$(STANDARD_DIR)/implementors_guide_v10-13.pdf
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-$(HTMLDIR)/%.jpg: %.jpg
- $(INSTALL_DATA) $< $@
-
-$(HTMLDIR)/%.png: %.png
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-ldocs: local_docs
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: imgs $(HTML_REF_MAN_FILE)
-
-clean clean_docs: clean_html clean_man
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-clean_man:
- rm -f $(MAN3DIR)/*
-
-clean_html:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
-
-imgs: $(IMG_FILES:%=$(HTMLDIR)/%)
-
-man: $(MAN3_FILES)
-
-debug opt:
-
-info:
- @echo "->Makefile<-"
- @echo ""
- @echo "HTML_REF_MAN_FILE = $(HTML_REF_MAN_FILE)"
- @echo ""
- @echo "XML_APPLICATION_FILES = $(XML_APPLICATION_FILES)"
- @echo "XML_PART_FILES = $(XML_PART_FILES)"
- @echo "XML_REF3_FILES = $(XML_REF3_FILES)"
- @echo "XML_CHAPTER_FILES = $(XML_CHAPTER_FILES)"
- @echo ""
- @echo "IMG_FILES = $(IMG_FILES)"
- @echo ""
- @echo "MAN3_FILES = $(MAN3_FILES)"
- @echo ""
- @echo "HTML_FILES = $(HTML_FILES)"
- @echo "TOP_HTML_FILES = $(TOP_HTML_FILES)"
- @echo ""
- @echo "DEFAULT_HTML_FILES = $(DEFAULT_HTML_FILES)"
- @echo "DEFAULT_GIF_FILES = $(DEFAULT_GIF_FILES)"
- @echo ""
- @echo ""
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/standard"
- $(INSTALL_DATA) $(STANDARDS) "$(RELSYSDIR)/doc/standard"
+NO_CHUNKS = megaco_edist_compress.xml megaco_user.xml megaco_encoder.xml \
+ megaco_transport.xml megaco_tcp.xml megaco_udp.xml \
+ megaco_codec_meas.xml megaco_codec_mstone1.xml \
+ megaco_codec_mstone2.xml megaco_codec_transform.xml
-release_spec:
+include $(ERL_TOP)/make/doc.mk
$(HTMLDIR)/megaco_architecture.html: megaco_architecture.xml
$(HTMLDIR)/megaco_codec_meas.html: megaco_codec_meas.xml
diff --git a/lib/megaco/doc/src/definitions/cite.defs b/lib/megaco/doc/src/definitions/cite.defs
deleted file mode 100644
index 41fffc1460..0000000000
--- a/lib/megaco/doc/src/definitions/cite.defs
+++ /dev/null
@@ -1,26 +0,0 @@
-[
-{"4711","CCITT","CCITT, Red Book Vol.-FASCILE VIII.7, Recomm. X400-X.430, Geneva, 1985","jocke"},
-{"X.680","ITU-T X.680","ITU-T Recommendation X.680 (1994) | ISO/IEC 8824-1: 1995, Abstract Syntax Notation One (ASN.1): Specification of Basic Notation"},
-{"X.682","ITU-T X.682","ITU-T Recommendation X.682 (1994) | ISO/IEC 8824-3: 1995, Abstract Syntax Notation One (ASN.1): Constraint Specification"},
-{"X.690","ITU-T X.690","ITU-T Recommendation X.690 (1994) | ISO/IEC 8825-1: 1995, ASN.1 Encoding Rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER)"},
-{"X.691","ITU-T X.691","ITU-T Recommendation X.691 (04/95) | ISO/IEC 8825-2: 1995, ASN.1 Encoding Rules: Specification of Packed Encoding Rules (PER)"},
-{"X.681","ITU-T X.681","ITU-T Recommendation X.681 (1994) | ISO/IEC 8824-2: 1995, Abstract Syntax Notation One (ASN.1): Information Object Specification"},
-{"X.683","ITU-T X.683","ITU-T Recommendation X.683 (1994) | ISO/IEC 8824-4: 1995, Abstract Syntax Notation One (ASN.1): Parameterization of ASN.1 Specifications"},
-{
- "DUBUISSON",
- "ASN.1 Communication between Heterogeneous Systems",
- "Oliver Dubuisson, ASN.1 Communication between Heterogeneous Systems, "
- "June 2000 ISBN 0-126333361-0", "nibe"
- },
- {
- "erlbook2",
- "Concurrent Programming in ERLANG",
- "J. Armstrong, R. Virding, C. Wikstrom, M. Williams, "
- "Concurrent Programming in ERLANG, Prentice Hall, 1996, ISBN 0-13-508301-X",
- "kent"
- },
- {"practsgml", "Practical SGML",
- "Eric van Herwijnen: Practical SGML, Kluwer Academic Publishers, 1990.",
- "peter"
-}
-].
diff --git a/lib/megaco/doc/src/definitions/cite.defs.xml b/lib/megaco/doc/src/definitions/cite.defs.xml
deleted file mode 100644
index 8fb91e2b44..0000000000
--- a/lib/megaco/doc/src/definitions/cite.defs.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE cites SYSTEM "cites.dtd">
-
-<cites>
- <cite>
- <id>4711</id>
- <shortdef>CCITT</shortdef>
- <def>
-CCITT, Red Book Vol.-FASCILE VIII.7, Recomm. X400-X.430, Geneva, 1985. </def>
- <resp>jocke</resp>
- </cite>
- <cite>
- <id>X.680</id>
- <shortdef>ITU-T X.680</shortdef>
- <def>
-ITU-T Recommendation X.680 (1994) | ISO/IEC 8824-1: 1995, Abstract Syntax Notation One (ASN.1): Specification of Basic Notation. </def>
- </cite>
- <cite>
- <id>X.682</id>
- <shortdef>ITU-T X.682</shortdef>
- <def>
-ITU-T Recommendation X.682 (1994) | ISO/IEC 8824-3: 1995, Abstract Syntax Notation One (ASN.1): Constraint Specification. </def>
- </cite>
- <cite>
- <id>X.690</id>
- <shortdef>ITU-T X.690</shortdef>
- <def>
-ITU-T Recommendation X.690 (1994) | ISO/IEC 8825-1: 1995, ASN.1 Encoding Rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER). </def>
- </cite>
- <cite>
- <id>X.691</id>
- <shortdef>ITU-T X.691</shortdef>
- <def>
-ITU-T Recommendation X.691 (04/95) | ISO/IEC 8825-2: 1995, ASN.1 Encoding Rules: Specification of Packed Encoding Rules (PER). </def>
- </cite>
- <cite>
- <id>X.681</id>
- <shortdef>ITU-T X.681</shortdef>
- <def>
-ITU-T Recommendation X.681 (1994) | ISO/IEC 8824-2: 1995, Abstract Syntax Notation One (ASN.1): Information Object Specification. </def>
- </cite>
- <cite>
- <id>X.683</id>
- <shortdef>ITU-T X.683</shortdef>
- <def>
-ITU-T Recommendation X.683 (1994) | ISO/IEC 8824-4: 1995, Abstract Syntax Notation One (ASN.1): Parameterization of ASN.1 Specifications. </def>
- </cite>
- <cite>
- <id>DUBUISSON</id>
- <shortdef>ASN.1 Communication between Heterogeneous Systems</shortdef>
- <def>
-Oliver Dubuisson, ASN.1 Communication between Heterogeneous Systems, June 2000 ISBN 0-126333361-0. </def>
- <resp>nibe</resp>
- </cite>
- <cite>
- <id>erlbook2</id>
- <shortdef>Concurrent Programming in ERLANG</shortdef>
- <def>
-J. Armstrong, R. Virding, C. Wikstrom, M. Williams, Concurrent Programming in ERLANG, Prentice Hall, 1996, ISBN 0-13-508301-X. </def>
- <resp>kent</resp>
- </cite>
- <cite>
- <id>practsgml</id>
- <shortdef>Practical SGML</shortdef>
- <def>
-Eric van Herwijnen: Practical SGML, Kluwer Academic Publishers, 1990. </def>
- <resp>peter</resp>
- </cite>
-</cites>
-
diff --git a/lib/megaco/doc/src/definitions/term.defs b/lib/megaco/doc/src/definitions/term.defs
deleted file mode 100644
index 16212923ea..0000000000
--- a/lib/megaco/doc/src/definitions/term.defs
+++ /dev/null
@@ -1,211 +0,0 @@
-[{"agent","agent","An entity that terminates a management protocol in the Network Element.","mbj"},
-{"API","API","Application Programming Interface. The interface towards an application. Usually this is a set of functions available, but can also be a set of messages sent to or from an application.","mbj"},
-{"application","application","A collection of resources which is required to offer a specific service.","mbj"},
-{"Appmon","Appmon","Application name for the Application Monitor within Erlang/OTP. A graphical node and process viewer.","mbj"},
-{"app callback","application callback module","A module which is called when the application is started, and when it has stopped. Every application has one application callback module.","mbj"},
-{"AC","application controller","A process which coordinates all operations on applications.","mbj"},
-{"app master","application master","The application master is a process that monitors the application. It is provided by the Erlang run-time system. Every application has an application master process.","mbj"},
-{".app file","application resource file","Specifies the resources required by the application and how the application should be started. Every application has one application resource file, called AppName.app.","mbj"},
-{"arity","arity","Denotes the number of arguments to a function.","jocke"},
-{"ASN.1","ASN.1","Abstract Syntax Notation One - an ITU-T and ISO standard notation for describing data formats used in communication protocols.","kenneth"},
-{"ASN.1 Compiler","ASN.1 Compiler","The Erlang/OTP ASN.1 Compiler translates an ASN.1 module into a corresponding Erlang module with encode and decode functions.","kenneth"},
-{"atom","atom","An atom is a constant. Atoms always starts with a lower case letter (a-z) and are terminated by a non-alphanumeric character - otherwise they must be quoted (enclosed in \' \'). An atom is a data type in Erlang, used to enhance the legibility of programs.","kenneth"},
-{"atomicity","atomicity","Atomicity refers to the \"all or nothing\" property. If a transaction succeeds (i.e. commits), then all its effects on the data is captured in the database. If the transaction does not succeed (i.e. aborts), then none of its effect on the data is captured in the database. In other words, the transaction processing algorithm guarantees that the database will not reflect a partitial effect of a transaction.","hakan"},
-{"attach","attach","The debugger may attach to a process. When attached, the debugger may show process details, such as message queues and variable bindings.","olin"},
-{"behaviour","behaviour","A \"pattern of design\" which can be used to build applications and processes in an applications.","mbj"},
-{"BIF","BIF","Built-In Functions which perform operations that are impossible or inefficient to program in Erlang itself. Are defined in
-the module Erlang in the application kernel","kenneth"},
-{"binary","binary","A data type in Erlang which is used to store an area of untyped memory. Binaries are used for efficiently handling large quantities of untyped data.","kenneth"},
-{"boolean","boolean","A common data type in programming and specification languages. The value can be either true or false.","kenneth"},
-{"boot file","boot file","A binary file with extension .boot which is read during start of an Erlang node. See SASL User's Guide for more info.","kenneth"},
-{"break point","break point","By setting a break point using the debugger, the user specifies a position in the source code of a module where execution is to be suspended and control transferred to the debugger.","olin"},
-{"CAshort","CA","See Certification Authority.","helen"},
-{"CA certificate","CA certificate","A certificate containing a CA's public key. Network entities use this public key to verify certificates signed with the CA's private key.","helen"},
-{"callback function","callback function","A callback function is a function exported from a callback module, that a generic behaviour calls to perform a specific task.", "mbj"},
-{"callback module","callback module","A callback module is a module that implements the specific parts of a generic behaviour. The generic behaviour specifies which callback functions must be exported from the module.","mbj"},
-{"certificate","certificate","A file used for authenticating network entities under the SSL protocol. A certificate contains information about its owner (called the subject) and issuer, plus the owner's public key and a signature made by a CA. Network entities verify these signatures using CA certificates.","helen"},
-{"CAlong","Certification Authority (CA)","A trusted third party whose purpose is to sign certificates for network entities it has authenticated using secure means. Other network entities can check the signature to verify that a CA has authenticated the bearer of a certificate.","helen"},
-{"CSRlong","Certificate Signing Request (CSR)","An unsigned certificate for submission to a Certification Authority, which signs it with its private key. Once the CSR is signed, it becomes a certificate.","helen"},
-{"child","child","A supervised process. See also permanent, transient, temporary child.","mbj"},
-{"cipher","cipher","A system of encryption.","helen"},
-{"ClearCase","ClearCase","A configuration management system from Rational Software Corporation.","lars"},
-{"client-server model","client-server model","A model where there is a server, which manages some resource, and a number of clients which send requests to the server to access the resource. The client-server model is one of the basic programming techniques for coordinating the activities of several parallel processes.","mbj"},
-{"cover","Coverage Analyser","Module name for the coverage analyser tool, located in the Tools application.","gunilla"},
-{"CORBAlong","Common Object Request Broker Architecture (CORBA)","A specification of an architecture for a distributed object system","lars"},{"CORBA","Common Object Request Broker Architecture (CORBA)","A specification of an architecture for a distributed object system","lars"},
-{"compiler","compiler","A compiler is a translator. A common type of compilers are those who takes source code for a programming language and translates it into code that is executable on a specific platform. E.g. the Erlang compiler translates Erlang source code to an intermediary code that is executable by the Erlang Run Time System.","kenneth"},
-{"consistency","consistency","Consistency refers to the requirement that, given a consistent initial database state, the state of the database after the successful execution of a transaction is also consistent; that is, a transaction transforms the database from a consistent state to another consistent state. Database consistency may be defined as a set of rules or constraints. If the execution of a transaction causes the consistency constraints to be violated, the transaction is not accepted (and thus aborted) by the system.","hakan"},
-{"cookie","cookie","A magic cookie is a secret atom assigned to each Erlang node. The Erlang nodes in a distributed system must know each others cookies in order to authorize each other for communication","kenneth"},
-{"CORBAshort","CORBA","See Common Object RequestBroker Architecture.","lars"},
-{"Coverage Analyser","Coverage Analyser","A tool which provides a set of functions for coverage analysis of Erlang programs, i.e observing how many times each line or function are executed. See also cover.","olin"},
-{"coverage analysis","coverage analysis","The task of determining which lines, or how many lines of code, has actually been executed. Useful for determining the completeness of test suites.","olin"},
-{"cross reference tool","cross reference tool","A tool that can be used for finding dependencies between functions, modules, applications and releases. The Erlang/OTP cross reference tool is called xref and is part of the Tools application.","gunilla"},
-{"CSRshort","CSR","See Certificate Signing Request.","helen"},
-{"data type","data type","The data types in Erlang are numbers, atoms, tuples, lists, pids, funs, records, ports, references and binaries. The values of Erlang data types can be stored in variables.","kenneth"},
-{"DBMSlong","Database Management System (DBMS)","A database is a collection of data and a DBMS is a system which manages the database. Applications accesses the database through the database management system.","hakan"},
-{"DBMSshort","DBMS","See Database Management System.","hakan"},
-{"Debugger","Debugger","An Erlang/OTP tool which provides mechanisms which makes it possible to see what happens during the execution of code in specified modules, or when processes crash.","olin"},
-{"dets","dets","A module within the stdlib application, which provides a term storage, and which is used as the underlying file storage mechanism by the Mnesia DBMS.","hakan"},
-{"dirty operations","dirty operations","Functions which manipulate data in a DBMS, without using transactions.","hakan"},
-{"distributed application","distributed application","An application which runs on one of several nodes. May be restarted on another node. (See local application.)","mbj"},
-{"DNSshort","DNS","See Domain Name System.","lars"},
-{"Docbuilder","Docbuilder","The documentation system used in Erlang/OTP.","jocke"},
-{"DNSlong","Domain Name System (DNS)","DNS is a service that map names to internet addresses","lars"},
-{"DTD","DTD","Document Type Definition as defined in SGML.","jocke"},
-{"durability","durability","If a transaction succeeds, then its effect on the data is persistently captured, and will survive subsequent system failures resulting in loss of data in volatile memory. Durability is usually enforced by first writing modified data to some non-volatile memory (usually disc), before a transaction is allowed to commit. If there is a system failure, the state of the non-volatile memory must be recovered to reflect the effect of all and only committed transactions.","hakan"},
-{"Emacs","Emacs"," A widely used text editor which allows customization of its behaviour. An Erlang mode for Emacs is included in the Erlang deliverables.","kenneth"},
-{"Emacs for Erlang","Emacs for Erlang","A tool which provides a major mode for editing Erlang source files in Emacs.","olin"},
-{"encaps","encapsulation","Data can be encapsulated into another data element.","kent"},
-{"eprof","eprof","A module in the tools application. See Profiler.","olin"},
-{"erl","erl","The command which starts an Erlang run-time system.","kenneth"},
-{"erl_interface","erl_interface library","A thread safe library with C-functions which makes it possible to write a C-program which appears as one of the nodes in a system of distributed Erlang nodes.","kenneth"},
-{"Erlang","Erlang","Erlang is a functional programming language intended for designing large industrial soft real time systems.","kenneth"},
-{"Erlang emulator","Erlang emulator","Another word for Erlang Virtual Machine.","kenneth"},
-{"ERTSlong","Erlang Run Time System","A fundamental part of Erlang/OTP which contains the Erlang Virtual Machine, the kernel and stdlib applications. The Erlang Run Time System is a mandatory part which all other Erlang applications are dependent upon.","kenneth"},
-{"Erlang VM","Erlang Virtual Machine","The virtual machine, which makes Erlang/OTP work together with a specific OS/HW platform. The Erlang Virtual Machine is available on several different platforms. The Erlang Virtual Machine is the glue which makes it possible to run an Erlang application on any platform without change.","kenneth"},
-{"ERTSshort","ERTS","See Erlang Run Time System.","kenneth"},
-{"ETS","ETS","Erlang Term Storage tables.","olin"},
-{"EVAshort","EVA","See Event and Alarm handling application","mbj"},
-{"EVAlong","Event and Alarm handling application (EVA)","An application that consists of Fault Management functionality, such as sending and logging of events and alarms.","mbj"},
-{"event handler","event handler","A module exporting functions which can process events sent to an event manager process. The event handler is a behaviour of type gen_event.","mbj"},
-{"event manager","event manager","A process to which events of a certain category is sent. gen_event handler can be installed in the event manager.","mbj"},
-{"exit signal","exit signal","A signal which is sent from a terminating process to the processes and ports it is linked to. An EXIT signal has the following format: {'EXIT', Exiting_Process_Id, Reason}.","kent"},
-{"foo","foo","Algebraic place holder.","kent"},
-{"FSM","FSM","Finite State Machine.","kenneth"},
-{"fun","fun","A data type, introduced in Erlang 4.4, which represent functional objects.","kenneth"},
-{"function","function","Erlang programs are written entirely in terms of modules with functions. A function can have arguments and does always return a result. A function can be exported which makes it available for calls from other modules. Non exported functions can only be called internally within the module.","kenneth"},
-{"Gateway","gateway","A server which acts as an intermediary for some other server. Unlike a proxy, a gateway receives requests as if it were the origin server for the requested resource; the requesting client may not be aware that it is communicating with a gateway.","jocke"},
-{"gen_event","gen_event","A behaviour used for programming event handling mechanisms, such as alarm handlers, error loggers, and plug-and-play handlers.","mbj"},
-{"gen_fsm","gen_fsm","A behaviour used for programming finite state machines.","mbj"},
-{"gen_server","gen_server","A behaviour used for programming client-server processes.","mbj"},
-{"gterm","Global Glossary Database","A glossary database used to list common acronymns and defintions etc.","jocke"},
-{"xref","xref","A cross reference tool that can be used for finding dependencies between functions, modules, applications and releases. Part of the Tools application.","gunilla"},
-{"GSlong","Graphics System","A library module which provides a graphics interface for Erlang.","mbj"},
-{"grid","grid","A multi-column object which is used to display tables. (Graphics System.)","mbj"},
-{"GSshort","GS","See Graphics System.","olin"},
-{"GS Contributions","GS Contributions","Unsupported user supplied tools which are included with the Erlang/OTP software release.","olin"},
-{"GUI","GUI","Graphical User Interface","mbj"},
-{"Home Directory","Home Directory","The position of a user account in the file system. The Home Directory is automatically passed to the Erlang run-time system at startup. On Unix the contents of the environment variable \"HOME\" is passed. Om Win32 the concatenation of the environment variables \"HOMEDRIVE\" and \"HOMEPATH\" is passed, or if these variables are not set, the value returned by the Win32 API function \"GetWindowsDirectory\" is passed.","kenneth"},
-{"host name","host name","The name of a machine on a network, e.g. erlang.ericsson.se.","kent"},
-{"HTML","HTML","Hypertext Markup Language.","jocke"},
-{"HTTP","HTTP","Hypertext Transfer Protocol.","jocke"},
-{"HTTPS","HTTPS","The Hypertext Transport Protocol, Secure, the standard SSL communication mechanism of the World Wide Web.","helen"},
-{"IDLshort","IDL","See Interface Description Language.","lars"},
-{"IDLlong","Interface Description Language (IDL)","The interface specification language created by OMG.","lars"},
-{"indexing","indexing","Fast lookup using an (usually enumerated) key.","kent"},
-{"I1","INETS","The Internet Services application","jocke"},
-{"initial call","initial call","The first call to an interpreted function (when using the Interpreter).","kent"},
-{"instrumentation function","instrumentation function","A function used to implement a Managed Object, i.e. give access to the real resources behind an MO.","mbj"},
-{"IDLlong","Interface Description Language (IDL)","The interface specification language created by OMG.","lars"},
-{"interpreter","interpreter","An application which provides mechanisms which make it possible to see what happens during the execution of code in specified (interpreted) modules, or when processes crash.","kent"},
-{"isolation","isolation","A transaction executes as if no other concurrent transactions are executing, and thus its execution results are equivalent to those obtained by executing database transactions serially. A system which maintains transaction isolation is also said to be enforcing serializability.","hakan"},
-{"kernel","kernel","An application which contains file servers, code servers and other code necessary for the Erlang run-time system.","kenneth"},
-{"key","key","A file containing the value that must be fed into an algorithm in order to encrypt or decrypt a message.","helen"},
-{"key pair","key pair","A set of two keys used in public key cryptography. One is the public key used to encrypt data, and the other is the private key necessary to decrypt the same data.","helen"},
-{"list","list","Terms separated by commas and enclosed in square brackets [ ] are called lists. A list is a data type in Erlang, used for storing a variable number of terms. It is dynamically sized. The first element of the list is referred to as the head of the list, and the remainer of the list as the tail.","kenneth"},
-{"list box","list box ","A list of labels with optional scroll bars attached. (Graphics System.)","mbj"},
-{"lc","list comprehension","A language construct in Erlang which are analogous to set comprehensions in Zermelo-Frankel set theory. Analogous to the 'setof' and 'findall' predicates in Prolog.","kenneth"},
-{"local application","local application","An application which runs on one node and which are always started at the local node only. (See distributed application.)","mbj"},
-{"manager","manager","An entity that terminates a management protocol in the Network Management Station.","mbj"},
-{"Master Agent","Master Agent","The SNMP agent system consists of one Master Agent which terminates the SNMP protocol","mbj"}, {"MIB","Management Information Base (MIB)","An abstract definition of the management information available through a management interface in a system.","mbj"},
-{"matching","matching","See pattern matching.","kenneth"},
-{"message queue","message queue","The queue of not yet received messages that are in the mailbox of a process.","olin"},
-{"Mnesia","Mnesia","Mnesia is a distributed Database Management System, appropriate for telecommunications applications and other applications with need of continuous operation and soft real-time properties.","hakan"},
-{"MIBshort","MIB","See Management Information Base.","mbj"},
-{"MIME","MIME","Multi-purpose Internet Mail Extensions.","jocke"},
-{"MOlong","Managed Object (MO)","The abstract management information defined in a MIB.","mbj"}, {"MO", "MO","Managed Object; The abstract management information defined in a MIB.","nibe"},
-{"MOshort","MO","See Managed Object.","mbj"},
-{"module","module","Module is the unit for compilation and for loading in Erlang. A Module contains a module declaration, export declarations and code representing the functions in the module.","kenneth"},
-{"NCSA","NCSA","The National Center for Supercomputing Applications.","jocke"},
-{"NEshort","NE","See Network Element.","mbj"},
-{"NElong", "Network Element","In OTP, the Network Element is the entire distributed OTP system, meaning that the distributed OTP system is managed as one entity.","mbj"}, {"NE", "NE","Network Element; In OTP, the Network Element is the entire distributed OTP system, meaning that the distributed OTP system is managed as one entity.","mbj"},
-{"NMSlong","Network Management Station (NMS)","The place where the operator manages the network.","mbj"}, {"NMS","NMS","Network Management Station; The place where the operator manages the network.","nibe"},
-{"NMSshort","NMS","See Network Management Station.","mbj"},
-{"node","node","An executing Erlang run-time system which can communicate with other Erlang run-time systems.","kenneth"},
-{"node name","node name","A node name is an atom constructed as the concatenation of a name supplied by the user, an \"@\" character, and the name of the host where the node is executing.","kenneth"},
-{"notation","notation","How things are written.","kent"},
-{"notification","notification","Information of an event.","kent"},
-{"NROFF","NROFF","A text formatting language for line printer quality output devices that runs on the UNIX operating system.","jocke"},
-{"number","number","A data type in Erlang. Are subdivided into integers, for storing natural numbers, or floats, for storing real numbers.","kenneth"},
-{"OMGlong","Object Managment Group (OMG)","A standardisation group for all specifications in the area of CORBA.","lars"},
-{"OMGshort","OMG","Object Managment Group.","lars"},
-{"OTP","OTP","Open Telecom Platform","mike"},
-{"os_mon","os_mon","An application which monitors the behaviour of the underlying operating system","mbj"},
-{"parser generator","parser generator","A tool for making compilers which takes a grammar description as input and generates a complete program (a parser) which recognizes input which complies with the grammar. YECC is a parser generator included in the Erlang/OTP.","kenneth"},
-{"pass phrase","pass phrase","The word or phrase which authenticates the user who is authorized to use private key file. The pass phrase prevents unauthorized users from starting, restarting, or reconfiguring the server.","helen"},
-{"pattern matching","pattern matching","A basic mechanism in Erlang for assigning values to variables and for controlling the flow of a program.","kenneth"},
-{"permanent child","permanent child","A supervised process which always is restarted when it dies.","mbj"},
-{"Pid","Pid","Process Identifier. A data type in Erlang for storing process references. The process identity of the process displayed in the line.","kenneth"},
-{"point","point","A unit used to indicate the size of a typeface. Equal to 1/72 inches.","jocke"},
-{"pointer","pointer","A pointer tells where data is stored. Memory pointers are not used in Erlang.","kent"},
-{"port","port","A data type in Erlang. Ports provide the basic mechanism for communication with the external world.","peterl"},
-{"port controller","port controller","An Erlang process which controls a port program. A port has exactly one port controller.","peterl"},
-{"port program","port program","A program that runs as an external program in the operating system and which the Erlang run-time system can start and communicate with by means of the Erlang port mechanism.","kenneth"},
-{"PostScript","PostScript","A language describing a fully laid-out page in terms of fonts, lines, grey scales, and so on, in a way that is interpretable by a printer. The language was developed by Adobe Systems.","jocke"},
-{"pretty-printed","pretty-printed","Nicely formatted code or data, e.g. C or Erlang, with indents and tabs etc.","jocke"},
-{"primitive","primitive","The basic elements in a programming language.","kent"},
-{"private key","private key","The secret key in a pair, used to decrypt incoming messages and sign outgoing ones.","helen"},
-{"process","process","A process is a self-contained separate unit of execution which exists concurrently with other processes in the system. The BIF \"spawn/3\" creates and starts the execution of a new process.","kenneth"},
-{"process dictionary","process dictionary","Each process has an associated dictionary which provides the process with simple destructive storage capabilities.","kenneth"},
-{"Process Manager","Process Manager","Obsolete name for the Process Trace Tool.","olin"},
-{"Process Trace Tool","Process Trace Tool","A tool which gives an overview of the processes in the Erlang run-time system. See also Pman.","olin"},
-{"Profiler","Profiler","Another name for eprof, a tool used to profile a system in order to find out how much time is spent in various segments of a program.","olin"},
-{"program","program","Routines which can be executed by a computer.","kent"},
-{"Proxy","proxy","An intermediary program which acts as both a server and a client for the purpose of making requests on behalf of other clients.","jocke"},
-{"public key","public key","The publicly available key in a key pair, used to encrypt messages bound for its owner and to decrypt signatures made by its owner.","helen"},
-{"query","query","Queries are used for accessing the data in a Database Management System. The query specify a maybe complicated relation that should hold for all of the selected data. This could involve several tables as well as conditions like for instance less then and greater then.","hakan"},
-{"query language","query language","A language which is specially designed to express database queries. Examples of query languages are QLC and SQL.","hakan"},
-{"receive","receive","A primitive for message processing in Erlang, receives a message from a process.","kenneth"},
-{"record","record","A data structure intended for storing a fixed number of related Erlang terms, it is similar to a \"struct\" in C or a \"record\" in Pascal.","kenneth"},
-{"recursion","recursion","A function is recursive if it calls itself until the result desired is attained. Recursion is the heart of functional programming.","kenneth"},
-{"reference","reference","A data type in Erlang for storing system unique references.","kenneth"},
-{"release handler","release handler","A SASL process which handles software upgrade.","mbj"},
-{"relup","release upgrade script","A script with instructions to the release handler of how the release should be installed in the system.","mbj"},
-{"RPC","Remote Proceedure Call","A technique for evaluating a function transparently on a remote node.","kenneth"},
-{"resource","resource","The actual resource to be managed. A resource is represented by a Managed Object. Each resource is mapped to one or several resources.","mbj"},{"resources","resources","The actual resources to be managed. A resource is represented by a Managed Object. Each resource is mapped to one or several resources.","nibe"},
-{"RFC","RFC","A \"Request for Comments\" used as a proposed standard by IETF.","jocke"},
-{"SASLshort","SASL","See System Architecture Support Libraries.","mbj"},
-{"schema","schema","The schema contains the definitions and whereabouts for all tables. In Mnesia it is realized as a special table named \"schema\".","hakan"},
-{"schema functions","schema functions","The functions which are available for managing schemas.","hakan"},
-{"SDL","SDL","Specification and Description Language. A ITU-T standard specification language which is used to specify the behaviour of switching systems.","kenneth"},
-{"send","send","A primitive for message processing in Erlang, sends a message to a process.","kenneth"},
-{"shell","shell"," The shell is an interactive front-end to an Erlang node where Erlang expressions can be evaluated. ","kenneth"},
-{"shell prompt","shell prompt","The text or symbol shown on the screen when the shell is ready to receive commands.","kent"},
-{"SNMPEAlong","Simple Network Management Protocol Extensible Agent (SNMPEA).","An Erlang/OTP application that includes a bilingual extensible SNMP agent.","mbj"},
-{"single assignment","single assignment","Means that once a variable has been assigned a value, the value can never be changed. Erlang is a single assignment language.","kenneth"},
-{"single step","single step","Single stepping is a function provided by the debugger. By single stepping the developer may use the debugger to follow the execution of a process and see what actually happens at each function call.","olin"},
-{"slave","slave","Not in control, can never take over by himself.","kent"},
-{"SSLlong","Secure Sockets Layer (SSL)","A protocol created by Netscape Communications Corporation for authentication and encryption over TCP/IP networks, including Web.","helen"},
-{"signature","signature","An encrypted text block that validates a certificate or other file. A Certification Authority (CA) creates a signature by generating a hash of the public key embedded in a certificate, then encrypting the hash with its own private key. Only the CA's public key can decrypt the signature, verifying that the CA has authenticated the network entity that owns the certificate.","helen"},
-{"SNMPshort","SNMP","Simple Network Management Protocol.","mbj"},
-{"SNMPshort","SNMPEA","See Simple Network Management Protocol Extensible Agent.","mbj"},
-{"spawn","spawn","A primitive for multiprocessing in Erlang, that starts a parallel computation (called a process). The creation of a new process","kenneth"},
-{"SSLshort","SSL","See Secure Sockets Layer.","helen"},
-{"SSLeay","SSLeay","An SSL library developed by Eric Yong (eay@mincom.oz.au).","helen"},
-{"SSLTOP","SSLTOP","The path to your SSL directory, a subdirectory of ServerRoot.","helen"},
-{"start script","start script","A start script is a file with .script extension which is the source when a boot file is created. See SASL User's Guide for more info.","kenneth"},
-{"stdlib","stdlib","An application within Erlang/OTP which contains modules for manipulating lists, strings, files, etc.","kenneth"},
-{"sticky directory","sticky directory","A directory containing Erlang object code that is part of the runtime system.","kent"},
-{"sticky lock","sticky lock","A lock which lingers at a node after the transaction which first acquired the lock has terminated. Once a process has obtained a sticky lock on a node, subsequent locks acquired by processes on the same node, can be set without need of involving remote nodes.","hakan"},
-{"string","string","The ASCII or ISO-8859-1 representation of the list of characters occurring within quotation marks in Erlang code.","kent"},
-{"Subagent","Subagent","The SNMP agent system consists of one Master Agent (See Master Agent) and zero or more Subagents which can be used to distribute the SNMP agent system on several nodes.","mbj"},
-{"supervision tree","supervision tree","A hierarcial tree of processes used to program fault tolerant systems.","mbj"},
-{"supervisor","supervisor","A behaviour to stucture fault tolerant computations, and program supervision trees with.","mbj"},
-{"sup_bridge","supervisor bridge"," A behaviour used to connect a process, or subsystem, to a supervisor tree.","mbj"},
-{"SASLlong","System Architecture Support Libraries (SASL)","An Erlang/OTP application which contains services for error logging, release handling and report browsing.","mbj"}, {".config","system configuration file","A file which specifies configuration parameters for the applications in the system.","mbj"},
-{"table lock","table lock","Table locks are locks which are set on whole tables. They may either be read locks or write locks.","hakan"},
-{"Table Visualizer","Table Visualizer","A tool which enables the user to examine ETS and Mnesia tables.","olin"},
-{"temporary child","temporary child","A supervised process which is never restarted when it dies.","mbj"},
-{"term","term","The super type of all Erlang types.","kenneth"},
-{"tools","tools","An application within Erlang/OTP which contains the tools which are not applications themselves.","olin"},
-{"transaction","transaction","Transactions groups a set of database accesses into an atomic unit. All transactions has the ACID (atomicity, concistency, isolation and durability) properties.","hakan"},
-{"transient child","transient child","A supervised process which is restarted if it dies non-normally.","mbj"},
-{"trigger","trigger","The Interpreter. A break point that is reached by a process triggers if it is active, and the execution of the process is stopped.","olin"},
-{"tty","tty","tty is a simple command line interface program where keystrokes are collected and interpreted. Originally meant teletypewriter equipment. Now it usually means the user console/terminal/shell window.","kent"},
-{"tuple","tuple","A tuple is a data type in Erlang. Tuples are used as place holders for complex data structures. Tuples may contain anything of any size, and are written as sequences of terms separated by commas, and enclosed in curly brackets { }.","kenneth"},
-{"variable","variable","An alias for a memory position, in which a value can be put. Erlang variables always start with an upper case letter.","kenneth"},
-{"workers","workers","The lower nodes in a supervision tree. These are the processes that actually performs some real work, e.g. servers.","mbj"},
-{"YECC","YECC","A LALR-1 parser generator included in Erlang/OTP. It is written in Erlang and generates a parser as an Erlang module.","kenneth"}].
-
-
-
-
diff --git a/lib/megaco/doc/src/definitions/term.defs.xml b/lib/megaco/doc/src/definitions/term.defs.xml
deleted file mode 100644
index 096720af84..0000000000
--- a/lib/megaco/doc/src/definitions/term.defs.xml
+++ /dev/null
@@ -1,1518 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE terms SYSTEM "terms.dtd">
-
-<terms>
- <term>
- <id>agent</id>
- <shortdef>agent</shortdef>
- <def>
-An entity that terminates a management protocol in the Network Element. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>API</id>
- <shortdef>API</shortdef>
- <def>
-Application Programming Interface. The interface towards an application. Usually this is a set of functions available, but can also be a set of messages sent to or from an application. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>application</id>
- <shortdef>application</shortdef>
- <def>
-A collection of resources which is required to offer a specific service. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>appmon</id>
- <shortdef>Application Monitor</shortdef>
- <def>
-A graphical node and application process tree viewer. See also appmon. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>Appmon</id>
- <shortdef>Appmon</shortdef>
- <def>
-Application name for the Application Monitor within Erlang/OTP. A graphical node and process viewer. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>app callback</id>
- <shortdef>application callback module</shortdef>
- <def>
-A module which is called when the application is started, and when it has stopped. Every application has one application callback module. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>AC</id>
- <shortdef>application controller</shortdef>
- <def>
-A process which coordinates all operations on applications. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>app master</id>
- <shortdef>application master</shortdef>
- <def>
-The application master is a process that monitors the application. It is provided by the Erlang run-time system. Every application has an application master process. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>.app file</id>
- <shortdef>application resource file</shortdef>
- <def>
-Specifies the resources required by the application and how the application should be started. Every application has one application resource file, called AppName.app. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>arity</id>
- <shortdef>arity</shortdef>
- <def>
-Denotes the number of arguments to a function. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>ASN.1</id>
- <shortdef>ASN.1</shortdef>
- <def>
-Abstract Syntax Notation One - an ITU-T and ISO standard notation for describing data formats used in communication protocols. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>ASN.1 Compiler</id>
- <shortdef>ASN.1 Compiler</shortdef>
- <def>
-The Erlang/OTP ASN.1 Compiler translates an ASN.1 module into a corresponding Erlang module with encode and decode functions. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>atom</id>
- <shortdef>atom</shortdef>
- <def>
-An atom is a constant. Atoms always starts with a lower case letter (a-z) and are terminated by a non-alphanumeric character - otherwise they must be quoted (enclosed in ' '). An atom is a data type in Erlang, used to enhance the legibility of programs. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>atomicity</id>
- <shortdef>atomicity</shortdef>
- <def>
-Atomicity refers to the "all or nothing" property. If a transaction succeeds (i.e. commits), then all its effects on the data is captured in the database. If the transaction does not succeed (i.e. aborts), then none of its effect on the data is captured in the database. In other words, the transaction processing algorithm guarantees that the database will not reflect a partitial effect of a transaction. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>attach</id>
- <shortdef>attach</shortdef>
- <def>
-The debugger may attach to a process. When attached, the debugger may show process details, such as message queues and variable bindings. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>behaviour</id>
- <shortdef>behaviour</shortdef>
- <def>
-A "pattern of design" which can be used to build applications and processes in an applications. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>BIF</id>
- <shortdef>BIF</shortdef>
- <def>
-Built-In Functions which perform operations that are impossible or inefficient to program in Erlang itself. Are defined in the module Erlang in the application kernel </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>binary</id>
- <shortdef>binary</shortdef>
- <def>
-A data type in Erlang which is used to store an area of untyped memory. Binaries are used for efficiently handling large quantities of untyped data. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>boolean</id>
- <shortdef>boolean</shortdef>
- <def>
-A common data type in programming and specification languages. The value can be either true or false. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>boot file</id>
- <shortdef>boot file</shortdef>
- <def>
-A binary file with extension .boot which is read during start of an Erlang node. See SASL User's Guide for more info. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>break point</id>
- <shortdef>break point</shortdef>
- <def>
-By setting a break point using the debugger, the user specifies a position in the source code of a module where execution is to be suspended and control transferred to the debugger. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>CAshort</id>
- <shortdef>CA</shortdef>
- <def>
-See Certification Authority. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>CA certificate</id>
- <shortdef>CA certificate</shortdef>
- <def>
-A certificate containing a CA's public key. Network entities use this public key to verify certificates signed with the CA's private key. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>callback function</id>
- <shortdef>callback function</shortdef>
- <def>
-A callback function is a function exported from a callback module, that a generic behaviour calls to perform a specific task. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>callback module</id>
- <shortdef>callback module</shortdef>
- <def>
-A callback module is a module that implements the specific parts of a generic behaviour. The generic behaviour specifies which callback functions must be exported from the module. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>certificate</id>
- <shortdef>certificate</shortdef>
- <def>
-A file used for authenticating network entities under the SSL protocol. A certificate contains information about its owner (called the subject) and issuer, plus the owner's public key and a signature made by a CA. Network entities verify these signatures using CA certificates. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>CAlong</id>
- <shortdef>Certification Authority (CA)</shortdef>
- <def>
-A trusted third party whose purpose is to sign certificates for network entities it has authenticated using secure means. Other network entities can check the signature to verify that a CA has authenticated the bearer of a certificate. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>CSRlong</id>
- <shortdef>Certificate Signing Request (CSR)</shortdef>
- <def>
-An unsigned certificate for submission to a Certification Authority, which signs it with its private key. Once the CSR is signed, it becomes a certificate. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>child</id>
- <shortdef>child</shortdef>
- <def>
-A supervised process. See also permanent, transient, temporary child. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>cipher</id>
- <shortdef>cipher</shortdef>
- <def>
-A system of encryption. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>ClearCase</id>
- <shortdef>ClearCase</shortdef>
- <def>
-A configuration management system from Rational Software Corporation. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>client-server model</id>
- <shortdef>client-server model</shortdef>
- <def>
-A model where there is a server, which manages some resource, and a number of clients which send requests to the server to access the resource. The client-server model is one of the basic programming techniques for coordinating the activities of several parallel processes. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>cover</id>
- <shortdef>Coverage Analyser</shortdef>
- <def>
-Module name for the coverage analyser tool, located in the Tools application. </def>
- <resp>gunilla</resp>
- </term>
- <term>
- <id>CORBAlong</id>
- <shortdef>Common Object Request Broker Architecture (CORBA)</shortdef>
- <def>
-A specification of an architecture for a distributed object system </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>CORBA</id>
- <shortdef>Common Object Request Broker Architecture (CORBA)</shortdef>
- <def>
-A specification of an architecture for a distributed object system </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>compiler</id>
- <shortdef>compiler</shortdef>
- <def>
-A compiler is a translator. A common type of compilers are those who takes source code for a programming language and translates it into code that is executable on a specific platform. E.g. the Erlang compiler translates Erlang source code to an intermediary code that is executable by the Erlang Run Time System. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>consistency</id>
- <shortdef>consistency</shortdef>
- <def>
-Consistency refers to the requirement that, given a consistent initial database state, the state of the database after the successful execution of a transaction is also consistent; that is, a transaction transforms the database from a consistent state to another consistent state. Database consistency may be defined as a set of rules or constraints. If the execution of a transaction causes the consistency constraints to be violated, the transaction is not accepted (and thus aborted) by the system. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>cookie</id>
- <shortdef>cookie</shortdef>
- <def>
-A magic cookie is a secret atom assigned to each Erlang node. The Erlang nodes in a distributed system must know each others cookies in order to authorize each other for communication </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>CORBAshort</id>
- <shortdef>CORBA</shortdef>
- <def>
-See Common Object RequestBroker Architecture. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>Coverage Analyser</id>
- <shortdef>Coverage Analyser</shortdef>
- <def>
-A tool which provides a set of functions for coverage analysis of Erlang programs, i.e observing how many times each line or function are executed. See also cover. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>coverage analysis</id>
- <shortdef>coverage analysis</shortdef>
- <def>
-The task of determining which lines, or how many lines of code, has actually been executed. Useful for determining the completeness of test suites. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>cross reference tool</id>
- <shortdef>cross reference tool</shortdef>
- <def>
-A tool that can be used for finding dependencies between functions, modules, applications and releases. The Erlang/OTP cross reference tool is called xref and is part of the Tools application. </def>
- <resp>gunilla</resp>
- </term>
- <term>
- <id>CSRshort</id>
- <shortdef>CSR</shortdef>
- <def>
-See Certificate Signing Request. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>data type</id>
- <shortdef>data type</shortdef>
- <def>
-The data types in Erlang are numbers, atoms, tuples, lists, pids, funs, records, ports, references and binaries. The values of Erlang data types can be stored in variables. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>DBMSlong</id>
- <shortdef>Database Management System (DBMS)</shortdef>
- <def>
-A database is a collection of data and a DBMS is a system which manages the database. Applications accesses the database through the database management system. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>DBMSshort</id>
- <shortdef>DBMS</shortdef>
- <def>
-See Database Management System. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>Debugger</id>
- <shortdef>Debugger</shortdef>
- <def>
-An Erlang/OTP tool which provides mechanisms which makes it possible to see what happens during the execution of code in specified modules, or when processes crash. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>dets</id>
- <shortdef>dets</shortdef>
- <def>
-A module within the stdlib application, which provides a term storage, and which is used as the underlying file storage mechanism by the Mnesia DBMS. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>dirty operations</id>
- <shortdef>dirty operations</shortdef>
- <def>
-Functions which manipulate data in a DBMS, without using transactions. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>distributed application</id>
- <shortdef>distributed application</shortdef>
- <def>
-An application which runs on one of several nodes. May be restarted on another node. (See local application.) </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>DNSshort</id>
- <shortdef>DNS</shortdef>
- <def>
-See Domain Name System. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>Docbuilder</id>
- <shortdef>Docbuilder</shortdef>
- <def>
-The documentation system used in Erlang/OTP. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>DNSlong</id>
- <shortdef>Domain Name System (DNS)</shortdef>
- <def>
-DNS is a service that map names to internet addresses </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>DTD</id>
- <shortdef>DTD</shortdef>
- <def>
-Document Type Definition as defined in SGML. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>durability</id>
- <shortdef>durability</shortdef>
- <def>
-If a transaction succeeds, then its effect on the data is persistently captured, and will survive subsequent system failures resulting in loss of data in volatile memory. Durability is usually enforced by first writing modified data to some non-volatile memory (usually disc), before a transaction is allowed to commit. If there is a system failure, the state of the non-volatile memory must be recovered to reflect the effect of all and only committed transactions. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>Emacs</id>
- <shortdef>Emacs</shortdef>
- <def>
-A widely used text editor which allows customization of its behaviour. An Erlang mode for Emacs is included in the Erlang deliverables. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Emacs for Erlang</id>
- <shortdef>Emacs for Erlang</shortdef>
- <def>
-A tool which provides a major mode for editing Erlang source files in Emacs. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>encaps</id>
- <shortdef>encapsulation</shortdef>
- <def>
-Data can be encapsulated into another data element. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>eprof</id>
- <shortdef>eprof</shortdef>
- <def>
-A module in the tools application. See Profiler. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>erl</id>
- <shortdef>erl</shortdef>
- <def>
-The command which starts an Erlang run-time system. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>erl_interface</id>
- <shortdef>erl_interface library</shortdef>
- <def>
-A thread safe library with C-functions which makes it possible to write a C-program which appears as one of the nodes in a system of distributed Erlang nodes. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Erlang</id>
- <shortdef>Erlang</shortdef>
- <def>
-Erlang is a functional programming language intended for designing large industrial soft real time systems. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Erlang emulator</id>
- <shortdef>Erlang emulator</shortdef>
- <def>
-Another word for Erlang Virtual Machine. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>ERTSlong</id>
- <shortdef>Erlang Run Time System</shortdef>
- <def>
-A fundamental part of Erlang/OTP which contains the Erlang Virtual Machine, the kernel and stdlib applications. The Erlang Run Time System is a mandatory part which all other Erlang applications are dependent upon. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Erlang VM</id>
- <shortdef>Erlang Virtual Machine</shortdef>
- <def>
-The virtual machine, which makes Erlang/OTP work together with a specific OS/HW platform. The Erlang Virtual Machine is available on several different platforms. The Erlang Virtual Machine is the glue which makes it possible to run an Erlang application on any platform without change. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>ERTSshort</id>
- <shortdef>ERTS</shortdef>
- <def>
-See Erlang Run Time System. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>ETS</id>
- <shortdef>ETS</shortdef>
- <def>
-Erlang Term Storage tables. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>EVAshort</id>
- <shortdef>EVA</shortdef>
- <def>
-See Event and Alarm handling application </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>EVAlong</id>
- <shortdef>Event and Alarm handling application (EVA)</shortdef>
- <def>
-An application that consists of Fault Management functionality, such as sending and logging of events and alarms. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>event handler</id>
- <shortdef>event handler</shortdef>
- <def>
-A module exporting functions which can process events sent to an event manager process. The event handler is a behaviour of type gen_event. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>event manager</id>
- <shortdef>event manager</shortdef>
- <def>
-A process to which events of a certain category is sent. gen_event handler can be installed in the event manager. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>exit signal</id>
- <shortdef>exit signal</shortdef>
- <def>
-A signal which is sent from a terminating process to the processes and ports it is linked to. An EXIT signal has the following format: {'EXIT', Exiting_Process_Id, Reason}. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>foo</id>
- <shortdef>foo</shortdef>
- <def>
-Algebraic place holder. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>FSM</id>
- <shortdef>FSM</shortdef>
- <def>
-Finite State Machine. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>fun</id>
- <shortdef>fun</shortdef>
- <def>
-A data type, introduced in Erlang 4.4, which represent functional objects. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>function</id>
- <shortdef>function</shortdef>
- <def>
-Erlang programs are written entirely in terms of modules with functions. A function can have arguments and does always return a result. A function can be exported which makes it available for calls from other modules. Non exported functions can only be called internally within the module. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Gateway</id>
- <shortdef>gateway</shortdef>
- <def>
-A server which acts as an intermediary for some other server. Unlike a proxy, a gateway receives requests as if it were the origin server for the requested resource; the requesting client may not be aware that it is communicating with a gateway. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>gen_event</id>
- <shortdef>gen_event</shortdef>
- <def>
-A behaviour used for programming event handling mechanisms, such as alarm handlers, error loggers, and plug-and-play handlers. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>gen_fsm</id>
- <shortdef>gen_fsm</shortdef>
- <def>
-A behaviour used for programming finite state machines. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>gen_server</id>
- <shortdef>gen_server</shortdef>
- <def>
-A behaviour used for programming client-server processes. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>gterm</id>
- <shortdef>Global Glossary Database</shortdef>
- <def>
-A glossary database used to list common acronymns and defintions etc. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>xref</id>
- <shortdef>xref</shortdef>
- <def>
-A cross reference tool that can be used for finding dependencies between functions, modules, applications and releases. Part of the Tools application. </def>
- <resp>gunilla</resp>
- </term>
- <term>
- <id>GSlong</id>
- <shortdef>Graphics System</shortdef>
- <def>
-A library module which provides a graphics interface for Erlang. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>grid</id>
- <shortdef>grid</shortdef>
- <def>
-A multi-column object which is used to display tables. (Graphics System.) </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>GSshort</id>
- <shortdef>GS</shortdef>
- <def>
-See Graphics System. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>GS Contributions</id>
- <shortdef>GS Contributions</shortdef>
- <def>
-Unsupported user supplied tools which are included with the Erlang/OTP software release. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>GUI</id>
- <shortdef>GUI</shortdef>
- <def>
-Graphical User Interface </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>Home Directory</id>
- <shortdef>Home Directory</shortdef>
- <def>
-The position of a user account in the file system. The Home Directory is automatically passed to the Erlang run-time system at startup. On Unix the contents of the environment variable "HOME" is passed. Om Win32 the concatenation of the environment variables "HOMEDRIVE" and "HOMEPATH" is passed, or if these variables are not set, the value returned by the Win32 API function "GetWindowsDirectory" is passed. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>host name</id>
- <shortdef>host name</shortdef>
- <def>
-The name of a machine on a network, e.g. erlang.ericsson.se. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>HTML</id>
- <shortdef>HTML</shortdef>
- <def>
-Hypertext Markup Language. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>HTTP</id>
- <shortdef>HTTP</shortdef>
- <def>
-Hypertext Transfer Protocol. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>HTTPS</id>
- <shortdef>HTTPS</shortdef>
- <def>
-The Hypertext Transport Protocol, Secure, the standard SSL communication mechanism of the World Wide Web. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>IDLshort</id>
- <shortdef>IDL</shortdef>
- <def>
-See Interface Description Language. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>IDLlong</id>
- <shortdef>Interface Description Language (IDL)</shortdef>
- <def>
-The interface specification language created by OMG. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>indexing</id>
- <shortdef>indexing</shortdef>
- <def>
-Fast lookup using an (usually enumerated) key. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>I1</id>
- <shortdef>INETS</shortdef>
- <def>
-The Internet Services application </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>initial call</id>
- <shortdef>initial call</shortdef>
- <def>
-The first call to an interpreted function (when using the Interpreter). </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>instrumentation function</id>
- <shortdef>instrumentation function</shortdef>
- <def>
-A function used to implement a Managed Object, i.e. give access to the real resources behind an MO. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>IDLlong</id>
- <shortdef>Interface Description Language (IDL)</shortdef>
- <def>
-The interface specification language created by OMG. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>interpreter</id>
- <shortdef>interpreter</shortdef>
- <def>
-An application which provides mechanisms which make it possible to see what happens during the execution of code in specified (interpreted) modules, or when processes crash. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>isolation</id>
- <shortdef>isolation</shortdef>
- <def>
-A transaction executes as if no other concurrent transactions are executing, and thus its execution results are equivalent to those obtained by executing database transactions serially. A system which maintains transaction isolation is also said to be enforcing serializability. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>kernel</id>
- <shortdef>kernel</shortdef>
- <def>
-An application which contains file servers, code servers and other code necessary for the Erlang run-time system. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>key</id>
- <shortdef>key</shortdef>
- <def>
-A file containing the value that must be fed into an algorithm in order to encrypt or decrypt a message. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>key pair</id>
- <shortdef>key pair</shortdef>
- <def>
-A set of two keys used in public key cryptography. One is the public key used to encrypt data, and the other is the private key necessary to decrypt the same data. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>list</id>
- <shortdef>list</shortdef>
- <def>
-Terms separated by commas and enclosed in square brackets [ ] are called lists. A list is a data type in Erlang, used for storing a variable number of terms. It is dynamically sized. The first element of the list is referred to as the head of the list, and the remainer of the list as the tail. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>list box</id>
- <shortdef>list box </shortdef>
- <def>
-A list of labels with optional scroll bars attached. (Graphics System.) </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>lc</id>
- <shortdef>list comprehension</shortdef>
- <def>
-A language construct in Erlang which are analogous to set comprehensions in Zermelo-Frankel set theory. Analogous to the 'setof' and 'findall' predicates in Prolog. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>local application</id>
- <shortdef>local application</shortdef>
- <def>
-An application which runs on one node and which are always started at the local node only. (See distributed application.) </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>manager</id>
- <shortdef>manager</shortdef>
- <def>
-An entity that terminates a management protocol in the Network Management Station. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>Master Agent</id>
- <shortdef>Master Agent</shortdef>
- <def>
-The SNMP agent system consists of one Master Agent which terminates the SNMP protocol </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>MIB</id>
- <shortdef>Management Information Base (MIB)</shortdef>
- <def>
-An abstract definition of the management information available through a management interface in a system. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>matching</id>
- <shortdef>matching</shortdef>
- <def>
-See pattern matching. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>message queue</id>
- <shortdef>message queue</shortdef>
- <def>
-The queue of not yet received messages that are in the mailbox of a process. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>Mnesia</id>
- <shortdef>Mnesia</shortdef>
- <def>
-Mnesia is a distributed Database Management System, appropriate for telecommunications applications and other applications with need of continuous operation and soft real-time properties. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>MIBshort</id>
- <shortdef>MIB</shortdef>
- <def>
-See Management Information Base. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>MIME</id>
- <shortdef>MIME</shortdef>
- <def>
-Multi-purpose Internet Mail Extensions. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>MOlong</id>
- <shortdef>Managed Object (MO)</shortdef>
- <def>
-The abstract management information defined in a MIB. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>MO</id>
- <shortdef>MO</shortdef>
- <def>
-Managed Object; The abstract management information defined in a MIB. </def>
- <resp>nibe</resp>
- </term>
- <term>
- <id>MOshort</id>
- <shortdef>MO</shortdef>
- <def>
-See Managed Object. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>module</id>
- <shortdef>module</shortdef>
- <def>
-Module is the unit for compilation and for loading in Erlang. A Module contains a module declaration, export declarations and code representing the functions in the module. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>NCSA</id>
- <shortdef>NCSA</shortdef>
- <def>
-The National Center for Supercomputing Applications. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>NEshort</id>
- <shortdef>NE</shortdef>
- <def>
-See Network Element. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>NElong</id>
- <shortdef>Network Element</shortdef>
- <def>
-In OTP, the Network Element is the entire distributed OTP system, meaning that the distributed OTP system is managed as one entity. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>NE</id>
- <shortdef>NE</shortdef>
- <def>
-Network Element; In OTP, the Network Element is the entire distributed OTP system, meaning that the distributed OTP system is managed as one entity. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>NMSlong</id>
- <shortdef>Network Management Station (NMS)</shortdef>
- <def>
-The place where the operator manages the network. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>NMS</id>
- <shortdef>NMS</shortdef>
- <def>
-Network Management Station; The place where the operator manages the network. </def>
- <resp>nibe</resp>
- </term>
- <term>
- <id>NMSshort</id>
- <shortdef>NMS</shortdef>
- <def>
-See Network Management Station. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>node</id>
- <shortdef>node</shortdef>
- <def>
-An executing Erlang run-time system which can communicate with other Erlang run-time systems. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>node name</id>
- <shortdef>node name</shortdef>
- <def>
-A node name is an atom constructed as the concatenation of a name supplied by the user, an "@" character, and the name of the host where the node is executing. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>notation</id>
- <shortdef>notation</shortdef>
- <def>
-How things are written. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>notification</id>
- <shortdef>notification</shortdef>
- <def>
-Information of an event. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>NROFF</id>
- <shortdef>NROFF</shortdef>
- <def>
-A text formatting language for line printer quality output devices that runs on the UNIX operating system. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>number</id>
- <shortdef>number</shortdef>
- <def>
-A data type in Erlang. Are subdivided into integers, for storing natural numbers, or floats, for storing real numbers. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>OMGlong</id>
- <shortdef>Object Managment Group (OMG)</shortdef>
- <def>
-A standardisation group for all specifications in the area of CORBA. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>OMGshort</id>
- <shortdef>OMG</shortdef>
- <def>
-Object Managment Group. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>OTP</id>
- <shortdef>OTP</shortdef>
- <def>
-Open Telecom Platform </def>
- <resp>mike</resp>
- </term>
- <term>
- <id>os_mon</id>
- <shortdef>os_mon</shortdef>
- <def>
-An application which monitors the behaviour of the underlying operating system </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>parser generator</id>
- <shortdef>parser generator</shortdef>
- <def>
-A tool for making compilers which takes a grammar description as input and generates a complete program (a parser) which recognizes input which complies with the grammar. YECC is a parser generator included in the Erlang/OTP. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>pass phrase</id>
- <shortdef>pass phrase</shortdef>
- <def>
-The word or phrase which authenticates the user who is authorized to use private key file. The pass phrase prevents unauthorized users from starting, restarting, or reconfiguring the server. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>pattern matching</id>
- <shortdef>pattern matching</shortdef>
- <def>
-A basic mechanism in Erlang for assigning values to variables and for controlling the flow of a program. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>permanent child</id>
- <shortdef>permanent child</shortdef>
- <def>
-A supervised process which always is restarted when it dies. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>Pid</id>
- <shortdef>Pid</shortdef>
- <def>
-Process Identifier. A data type in Erlang for storing process references. The process identity of the process displayed in the line. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Pman</id>
- <shortdef>Pman</shortdef>
- <def>
-Module and application name for the Process Trace Tool. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>point</id>
- <shortdef>point</shortdef>
- <def>
-A unit used to indicate the size of a typeface. Equal to 1/72 inches. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>pointer</id>
- <shortdef>pointer</shortdef>
- <def>
-A pointer tells where data is stored. Memory pointers are not used in Erlang. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>port</id>
- <shortdef>port</shortdef>
- <def>
-A data type in Erlang. Ports provide the basic mechanism for communication with the external world. </def>
- <resp>peterl</resp>
- </term>
- <term>
- <id>port controller</id>
- <shortdef>port controller</shortdef>
- <def>
-An Erlang process which controls a port program. A port has exactly one port controller. </def>
- <resp>peterl</resp>
- </term>
- <term>
- <id>port program</id>
- <shortdef>port program</shortdef>
- <def>
-A program that runs as an external program in the operating system and which the Erlang run-time system can start and communicate with by means of the Erlang port mechanism. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>PostScript</id>
- <shortdef>PostScript</shortdef>
- <def>
-A language describing a fully laid-out page in terms of fonts, lines, grey scales, and so on, in a way that is interpretable by a printer. The language was developed by Adobe Systems. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>pretty-printed</id>
- <shortdef>pretty-printed</shortdef>
- <def>
-Nicely formatted code or data, e.g. C or Erlang, with indents and tabs etc. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>primitive</id>
- <shortdef>primitive</shortdef>
- <def>
-The basic elements in a programming language. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>private key</id>
- <shortdef>private key</shortdef>
- <def>
-The secret key in a pair, used to decrypt incoming messages and sign outgoing ones. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>process</id>
- <shortdef>process</shortdef>
- <def>
-A process is a self-contained separate unit of execution which exists concurrently with other processes in the system. The BIF "spawn/3" creates and starts the execution of a new process. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>process dictionary</id>
- <shortdef>process dictionary</shortdef>
- <def>
-Each process has an associated dictionary which provides the process with simple destructive storage capabilities. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Process Manager</id>
- <shortdef>Process Manager</shortdef>
- <def>
-Obsolete name for the Process Trace Tool. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>Process Trace Tool</id>
- <shortdef>Process Trace Tool</shortdef>
- <def>
-A tool which gives an overview of the processes in the Erlang run-time system. See also Pman. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>Profiler</id>
- <shortdef>Profiler</shortdef>
- <def>
-Another name for eprof, a tool used to profile a system in order to find out how much time is spent in various segments of a program. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>program</id>
- <shortdef>program</shortdef>
- <def>
-Routines which can be executed by a computer. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>Proxy</id>
- <shortdef>proxy</shortdef>
- <def>
-An intermediary program which acts as both a server and a client for the purpose of making requests on behalf of other clients. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>public key</id>
- <shortdef>public key</shortdef>
- <def>
-The publicly available key in a key pair, used to encrypt messages bound for its owner and to decrypt signatures made by its owner. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>query</id>
- <shortdef>query</shortdef>
- <def>
-Queries are used for accessing the data in a Database Management System. The query specify a maybe complicated relation that should hold for all of the selected data. This could involve several tables as well as conditions like for instance less then and greater then. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>query language</id>
- <shortdef>query language</shortdef>
- <def>
-A language which is specially designed to express database queries. Examples of query languages are QLC and SQL. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>receive</id>
- <shortdef>receive</shortdef>
- <def>
-A primitive for message processing in Erlang, receives a message from a process. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>record</id>
- <shortdef>record</shortdef>
- <def>
-A data structure intended for storing a fixed number of related Erlang terms, it is similar to a "struct" in C or a "record" in Pascal. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>recursion</id>
- <shortdef>recursion</shortdef>
- <def>
-A function is recursive if it calls itself until the result desired is attained. Recursion is the heart of functional programming. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>reference</id>
- <shortdef>reference</shortdef>
- <def>
-A data type in Erlang for storing system unique references. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>release handler</id>
- <shortdef>release handler</shortdef>
- <def>
-A SASL process which handles software upgrade. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>relup</id>
- <shortdef>release upgrade script</shortdef>
- <def>
-A script with instructions to the release handler of how the release should be installed in the system. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>RPC</id>
- <shortdef>Remote Proceedure Call</shortdef>
- <def>
-A technique for evaluating a function transparently on a remote node. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>resource</id>
- <shortdef>resource</shortdef>
- <def>
-The actual resource to be managed. A resource is represented by a Managed Object. Each resource is mapped to one or several resources. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>resources</id>
- <shortdef>resources</shortdef>
- <def>
-The actual resources to be managed. A resource is represented by a Managed Object. Each resource is mapped to one or several resources. </def>
- <resp>nibe</resp>
- </term>
- <term>
- <id>RFC</id>
- <shortdef>RFC</shortdef>
- <def>
-A "Request for Comments" used as a proposed standard by IETF. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>SASLshort</id>
- <shortdef>SASL</shortdef>
- <def>
-See System Architecture Support Libraries. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>schema</id>
- <shortdef>schema</shortdef>
- <def>
-The schema contains the definitions and whereabouts for all tables. In Mnesia it is realized as a special table named "schema". </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>schema functions</id>
- <shortdef>schema functions</shortdef>
- <def>
-The functions which are available for managing schemas. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>SDL</id>
- <shortdef>SDL</shortdef>
- <def>
-Specification and Description Language. A ITU-T standard specification language which is used to specify the behaviour of switching systems. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>send</id>
- <shortdef>send</shortdef>
- <def>
-A primitive for message processing in Erlang, sends a message to a process. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>shell</id>
- <shortdef>shell</shortdef>
- <def>
-The shell is an interactive front-end to an Erlang node where Erlang expressions can be evaluated. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>shell prompt</id>
- <shortdef>shell prompt</shortdef>
- <def>
-The text or symbol shown on the screen when the shell is ready to receive commands. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>SNMPEAlong</id>
- <shortdef>Simple Network Management Protocol Extensible Agent (SNMPEA).</shortdef>
- <def>
-An Erlang/OTP application that includes a bilingual extensible SNMP agent. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>single assignment</id>
- <shortdef>single assignment</shortdef>
- <def>
-Means that once a variable has been assigned a value, the value can never be changed. Erlang is a single assignment language. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>single step</id>
- <shortdef>single step</shortdef>
- <def>
-Single stepping is a function provided by the debugger. By single stepping the developer may use the debugger to follow the execution of a process and see what actually happens at each function call. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>slave</id>
- <shortdef>slave</shortdef>
- <def>
-Not in control, can never take over by himself. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>SSLlong</id>
- <shortdef>Secure Sockets Layer (SSL)</shortdef>
- <def>
-A protocol created by Netscape Communications Corporation for authentication and encryption over TCP/IP networks, including Web. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>signature</id>
- <shortdef>signature</shortdef>
- <def>
-An encrypted text block that validates a certificate or other file. A Certification Authority (CA) creates a signature by generating a hash of the public key embedded in a certificate, then encrypting the hash with its own private key. Only the CA's public key can decrypt the signature, verifying that the CA has authenticated the network entity that owns the certificate. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>SNMPshort</id>
- <shortdef>SNMP</shortdef>
- <def>
-Simple Network Management Protocol. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>SNMPshort</id>
- <shortdef>SNMPEA</shortdef>
- <def>
-See Simple Network Management Protocol Extensible Agent. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>spawn</id>
- <shortdef>spawn</shortdef>
- <def>
-A primitive for multiprocessing in Erlang, that starts a parallel computation (called a process). The creation of a new process </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>SSLshort</id>
- <shortdef>SSL</shortdef>
- <def>
-See Secure Sockets Layer. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>SSLeay</id>
- <shortdef>SSLeay</shortdef>
- <def>
-An SSL library developed by Eric Yong (eay@mincom.oz.au). </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>SSLTOP</id>
- <shortdef>SSLTOP</shortdef>
- <def>
-The path to your SSL directory, a subdirectory of ServerRoot. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>start script</id>
- <shortdef>start script</shortdef>
- <def>
-A start script is a file with .script extension which is the source when a boot file is created. See SASL User's Guide for more info. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>stdlib</id>
- <shortdef>stdlib</shortdef>
- <def>
-An application within Erlang/OTP which contains modules for manipulating lists, strings, files, etc. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>sticky directory</id>
- <shortdef>sticky directory</shortdef>
- <def>
-A directory containing Erlang object code that is part of the runtime system. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>sticky lock</id>
- <shortdef>sticky lock</shortdef>
- <def>
-A lock which lingers at a node after the transaction which first acquired the lock has terminated. Once a process has obtained a sticky lock on a node, subsequent locks acquired by processes on the same node, can be set without need of involving remote nodes. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>string</id>
- <shortdef>string</shortdef>
- <def>
-The ASCII or ISO-8859-1 representation of the list of characters occurring within quotation marks in Erlang code. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>Subagent</id>
- <shortdef>Subagent</shortdef>
- <def>
-The SNMP agent system consists of one Master Agent (See Master Agent) and zero or more Subagents which can be used to distribute the SNMP agent system on several nodes. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>supervision tree</id>
- <shortdef>supervision tree</shortdef>
- <def>
-A hierarcial tree of processes used to program fault tolerant systems. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>supervisor</id>
- <shortdef>supervisor</shortdef>
- <def>
-A behaviour to stucture fault tolerant computations, and program supervision trees with. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>sup_bridge</id>
- <shortdef>supervisor bridge</shortdef>
- <def>
-A behaviour used to connect a process, or subsystem, to a supervisor tree. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>SASLlong</id>
- <shortdef>System Architecture Support Libraries (SASL)</shortdef>
- <def>
-An Erlang/OTP application which contains services for error logging, release handling and report browsing. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>.config</id>
- <shortdef>system configuration file</shortdef>
- <def>
-A file which specifies configuration parameters for the applications in the system. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>table lock</id>
- <shortdef>table lock</shortdef>
- <def>
-Table locks are locks which are set on whole tables. They may either be read locks or write locks. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>Table Visualizer</id>
- <shortdef>Table Visualizer</shortdef>
- <def>
-A tool which enables the user to examine ETS and Mnesia tables. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>temporary child</id>
- <shortdef>temporary child</shortdef>
- <def>
-A supervised process which is never restarted when it dies. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>term</id>
- <shortdef>term</shortdef>
- <def>
-The super type of all Erlang types. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Toolbar</id>
- <shortdef>Toolbar</shortdef>
- <def>
-A tool that provides an simplistic interface to the other various Erlang/OTP tools </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>tools</id>
- <shortdef>tools</shortdef>
- <def>
-An application within Erlang/OTP which contains the tools which are not applications themselves. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>transaction</id>
- <shortdef>transaction</shortdef>
- <def>
-Transactions groups a set of database accesses into an atomic unit. All transactions has the ACID (atomicity, concistency, isolation and durability) properties. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>transient child</id>
- <shortdef>transient child</shortdef>
- <def>
-A supervised process which is restarted if it dies non-normally. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>trigger</id>
- <shortdef>trigger</shortdef>
- <def>
-The Interpreter. A break point that is reached by a process triggers if it is active, and the execution of the process is stopped. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>tty</id>
- <shortdef>tty</shortdef>
- <def>
-tty is a simple command line interface program where keystrokes are collected and interpreted. Originally meant teletypewriter equipment. Now it usually means the user console/terminal/shell window. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>tuple</id>
- <shortdef>tuple</shortdef>
- <def>
-A tuple is a data type in Erlang. Tuples are used as place holders for complex data structures. Tuples may contain anything of any size, and are written as sequences of terms separated by commas, and enclosed in curly brackets { }. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>variable</id>
- <shortdef>variable</shortdef>
- <def>
-An alias for a memory position, in which a value can be put. Erlang variables always start with an upper case letter. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>workers</id>
- <shortdef>workers</shortdef>
- <def>
-The lower nodes in a supervision tree. These are the processes that actually performs some real work, e.g. servers. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>YECC</id>
- <shortdef>YECC</shortdef>
- <def>
-A LALR-1 parser generator included in Erlang/OTP. It is written in Erlang and generates a parser as an Erlang module. </def>
- <resp>kenneth</resp>
- </term>
-</terms>
-
diff --git a/lib/megaco/doc/src/files.mk b/lib/megaco/doc/src/files.mk
index e40889c3fb..6b7eaab531 100644
--- a/lib/megaco/doc/src/files.mk
+++ b/lib/megaco/doc/src/files.mk
@@ -55,7 +55,7 @@ XML_CHAPTER_FILES = \
BOOK_FILES = book.xml
-IMG_FILES = \
+IMAGE_FILES = \
single_node_config.gif \
distr_node_config.gif \
megaco_sys_arch.gif \
diff --git a/lib/megaco/doc/src/megaco.xml b/lib/megaco/doc/src/megaco.xml
index 55bcf43fe6..e44c061f48 100644
--- a/lib/megaco/doc/src/megaco.xml
+++ b/lib/megaco/doc/src/megaco.xml
@@ -339,17 +339,17 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>Specifies the timeout time for the request-keep-alive timer. </p>
<p>This timer is started when the <em>first</em> reply to an asynchronous
request (issued using the
- <seealso marker="megaco#cast">megaco:cast/3</seealso> function)
+ <seeerl marker="megaco#cast">megaco:cast/3</seeerl> function)
arrives. As long as this timer is running, replies will
be delivered via the
- <seealso marker="megaco_user#trans_reply">handle_trans_reply/4,5</seealso>
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply/4,5</seeerl>
callback function, with their "arrival number"
(see <c><![CDATA[UserReply]]></c> of the
- <seealso marker="megaco_user#trans_reply">handle_trans_reply/4,5</seealso>
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply/4,5</seeerl>
callback function). </p>
<p>Replies arriving after the timer has expired, will be
delivered using the
- <seealso marker="megaco_user#unexpected_trans">handle_unexpected_trans/3,4</seealso>
+ <seeerl marker="megaco_user#unexpected_trans">handle_unexpected_trans/3,4</seeerl>
callback function. </p>
<p>The timeout time can have the values:
<c><![CDATA[plain | integer() >= 0]]></c>. </p>
@@ -362,13 +362,13 @@ megaco_incr_timer() = #megaco_incr_timer{}
<item>
<p>Timeout time for the call proxy. </p>
<p>When a request is sent using the
- <seealso marker="megaco#call">call/3</seealso> function,
+ <seeerl marker="megaco#call">call/3</seeerl> function,
a proxy process is started to handle
all replies. When the reply has been received and delivered
to the user, the proxy process continue to exist for as long
as this option specifies. Any received messages, is passed on
to the user via the
- <seealso marker="megaco_user#handle_unexpected_trans">handle_unexpected_trans</seealso>
+ <seeerl marker="megaco_user#handle_unexpected_trans">handle_unexpected_trans</seeerl>
callback function. </p>
<p>The timeout time is in milliseconds. A value of 0 (zero) means
that the proxy process will exit directly after the reply has
@@ -400,7 +400,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
(see <c><![CDATA[trans_ack_maxcount]]></c>, <c><![CDATA[trans_req_maxcount]]></c>,
<c><![CDATA[trans_req_maxsize]]></c>, <c><![CDATA[trans_ack_maxcount]]></c> and
<c><![CDATA[trans_timer]]></c>). </p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info.</p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info.</p>
<p>An <c><![CDATA[boolean]]></c>, defaults to <c><![CDATA[false]]></c>.</p>
<marker id="ui_trans_ack_maxcount"></marker>
@@ -411,7 +411,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>Maximum number of accumulated ack's. At most this many ack's
will be accumulated by the transaction sender (if started and
configured to accumulate ack's).</p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info. </p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info. </p>
<p>An <c><![CDATA[integer]]></c>, defaults to 10.</p>
<marker id="ui_trans_req"></marker>
@@ -428,7 +428,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
(see <c><![CDATA[trans_ack_maxcount]]></c>, <c><![CDATA[trans_req_maxcount]]></c>,
<c><![CDATA[trans_req_maxsize]]></c>, <c><![CDATA[trans_ack_maxcount]]></c> and
<c><![CDATA[trans_timer]]></c>). </p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info. </p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info. </p>
<p>An <c><![CDATA[boolean]]></c>, defaults to <c><![CDATA[false]]></c>.</p>
<marker id="ui_trans_req_maxcount"></marker>
@@ -439,7 +439,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>Maximum number of accumulated requests. At most this many
requests will be accumulated by the transaction sender
(if started and configured to accumulate requests). </p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info.</p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info.</p>
<p>An <c><![CDATA[integer]]></c>, defaults to 10.</p>
<marker id="ui_trans_req_maxsize"></marker>
</item>
@@ -448,7 +448,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>Maximum size of the accumulated requests. At most this much
requests will be accumulated by the transaction sender
(if started and configured to accumulate requests).</p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info.</p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info.</p>
<p>An <c><![CDATA[integer]]></c>, defaults to 2048.</p>
<marker id="ui_trans_timer"></marker>
@@ -466,7 +466,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
(which is depending on the values of <c><![CDATA[auto_ack]]></c>,
<c><![CDATA[trans_ack]]></c> and <c><![CDATA[trans_req]]></c>) will be accumulated,
for later sending. </p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info. </p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info. </p>
<p>An <c><![CDATA[integer]]></c>, defaults to 0.</p>
<marker id="ui_pending_timer"></marker>
@@ -489,7 +489,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
This parameter specifies how many pending messages that can
be sent (for a given received transaction request).
When the limit is exceeded, the transaction is aborted
- (see <seealso marker="megaco_user#request_abort">handle_trans_request_abort</seealso>) and an error message
+ (see <seeerl marker="megaco_user#request_abort">handle_trans_request_abort</seeerl>) and an error message
is sent to the other side. </p>
<p>Note that this has no effect on the actual sending of
pending transactions. This is either implicit (e.g. when
@@ -610,16 +610,16 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>This option indicates weather the transport module
should be told if a message send is a resend or not. </p>
<p>If <em>false</em>, megaco messages are sent using the
- <seealso marker="megaco_transport#send_message">send_message</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message</seeerl>
function. </p>
<p>If <em>true</em>, megaco message <em>re-sends</em> are made using the
- <seealso marker="megaco_transport#resend_message">resend_message</seealso>
+ <seeerl marker="megaco_transport#resend_message">resend_message</seeerl>
function. The initial message send is still done using the
- <seealso marker="megaco_transport#send_message">send_message</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message</seeerl>
function. </p>
<p>The special value <em>flag</em> instead indicates that the
function
- <seealso marker="megaco_transport#send_message">send_message/3</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message/3</seeerl>
shall be used. </p>
<p>A <c>resend_indication()</c>,
defaults to <c><![CDATA[false]]></c>.</p>
@@ -632,7 +632,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>This option specifies if the user shall be notified of received
segment replies or not. </p>
<p>See
- <seealso marker="megaco_user#segment_reply">handle_segment_reply</seealso>
+ <seeerl marker="megaco_user#segment_reply">handle_segment_reply</seeerl>
callback function for more information. </p>
<p>A <c><![CDATA[boolean]]></c>,
defaults to <c><![CDATA[false]]></c>. </p>
@@ -648,9 +648,9 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>When the timer finally expires, a "megaco segments not
received" (459) error message is sent to the other side
and the user is notified with a <c><![CDATA[segment timeout]]></c> <c><![CDATA[UserReply]]></c> in either the
- <seealso marker="megaco_user#trans_reply">handle_trans_reply</seealso> callback function or
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply</seeerl> callback function or
the return value of the
- <seealso marker="megaco#call">call</seealso> function. </p>
+ <seeerl marker="megaco#call">call</seeerl> function. </p>
<p>A Megaco Timer (see explanation above),
defaults to <c><![CDATA[10000]]></c>. </p>
@@ -840,17 +840,17 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>Specifies the timeout time for the request-keep-alive timer. </p>
<p>This timer is started when the <em>first</em> reply to an asynchronous
request (issued using the
- <seealso marker="megaco#cast">megaco:cast/3</seealso> function)
+ <seeerl marker="megaco#cast">megaco:cast/3</seeerl> function)
arrives. As long as this timer is running, replies will
be delivered via the
- <seealso marker="megaco_user#trans_reply">handle_trans_reply/4,5</seealso>
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply/4,5</seeerl>
callback function, with their "arrival number"
(see <c><![CDATA[UserReply]]></c> of the
- <seealso marker="megaco_user#trans_reply">handle_trans_reply/4,5</seealso>
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply/4,5</seeerl>
callback function). </p>
<p>Replies arriving after the timer has expired, will be
delivered using the
- <seealso marker="megaco_user#unexpected_trans">handle_unexpected_trans/3,4</seealso>
+ <seeerl marker="megaco_user#unexpected_trans">handle_unexpected_trans/3,4</seeerl>
callback function. </p>
<p>The timeout time can have the values:
<c><![CDATA[plain | integer() >= 0]]></c>. </p>
@@ -902,13 +902,13 @@ megaco_incr_timer() = #megaco_incr_timer{}
<item>
<p>Timeout time for the call proxy. </p>
<p>When a request is sent using the
- <seealso marker="megaco#call">call/3</seealso> function,
+ <seeerl marker="megaco#call">call/3</seeerl> function,
a proxy process is started to handle
all replies. When the reply has been received and delivered
to the user, the proxy process continue to exist for as long
as this option specifies. Any received messages, is passed on
to the user via the
- <seealso marker="megaco_user#handle_unexpected_trans">handle_unexpected_trans</seealso>
+ <seeerl marker="megaco_user#handle_unexpected_trans">handle_unexpected_trans</seeerl>
callback function. </p>
<p>The timeout time is in milliseconds. A value of 0 (zero) means
that the proxy process will exit directly after the reply has
@@ -939,7 +939,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
(see <c><![CDATA[trans_ack_maxcount]]></c>, <c><![CDATA[trans_req_maxcount]]></c>,
<c><![CDATA[trans_req_maxsize]]></c>, <c><![CDATA[trans_ack_maxcount]]></c> and
<c><![CDATA[trans_timer]]></c>). </p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info. </p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info. </p>
<p>An <c><![CDATA[boolean]]></c>, defaults to <c><![CDATA[false]]></c>.</p>
<marker id="ci_trans_ack_maxcount"></marker>
@@ -950,7 +950,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>Maximum number of accumulated ack's. At most this many ack's
will be accumulated by the transaction sender (if started and
configured to accumulate ack's).</p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info.</p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info.</p>
<p>An integer, defaults to 10.</p>
<marker id="ci_trans_req"></marker>
@@ -967,7 +967,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
(see <c><![CDATA[trans_ack_maxcount]]></c>, <c><![CDATA[trans_req_maxcount]]></c>,
<c><![CDATA[trans_req_maxsize]]></c>, <c><![CDATA[trans_ack_maxcount]]></c> and
<c><![CDATA[trans_timer]]></c>). </p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info. </p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info. </p>
<p>An <c><![CDATA[boolean]]></c>, defaults to <c><![CDATA[false]]></c>.</p>
<marker id="ci_trans_req_maxcount"></marker>
@@ -978,7 +978,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>Maximum number of accumulated requests. At most this many
requests will be accumulated by the transaction sender
(if started and configured to accumulate requests). </p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info. </p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info. </p>
<p>An <c><![CDATA[integer]]></c>, defaults to 10.</p>
<marker id="ci_trans_req_maxsize"></marker>
@@ -989,7 +989,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>Maximum size of the accumulated requests. At most this much
requests will be accumulated by the transaction sender
(if started and configured to accumulate requests). </p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info. </p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info. </p>
<p>An <c><![CDATA[integer]]></c>, defaults to 2048.</p>
<marker id="ci_trans_timer"></marker>
@@ -1006,7 +1006,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
(which is depending on the values of <c><![CDATA[auto_ack]]></c>,
<c><![CDATA[trans_ack]]></c> and <c><![CDATA[trans_req]]></c>) will be accumulated,
for later sending. </p>
- <p>See also <seealso marker="megaco_run#transaction_sender">transaction sender</seealso> for more info. </p>
+ <p>See also <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide> for more info. </p>
<p>An <c><![CDATA[integer]]></c>, defaults to 0.</p>
<marker id="ci_pending_timer"></marker>
@@ -1029,7 +1029,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
This parameter specifies how many pending messages that can
be sent (for a given received transaction request).
When the limit is exceeded, the transaction is aborted
- (see <seealso marker="megaco_user#request_abort">handle_trans_request_abort</seealso>) and an error message
+ (see <seeerl marker="megaco_user#request_abort">handle_trans_request_abort</seeerl>) and an error message
is sent to the other side. </p>
<p>Note that this has no effect on the actual sending of
pending transactions. This is either implicit (e.g. when
@@ -1128,16 +1128,16 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>This option indicates weather the transport module
should be told if a message send is a resend or not. </p>
<p>If <em>false</em>, megaco messages are sent using the
- <seealso marker="megaco_transport#send_message">send_message/2</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message/2</seeerl>
function. </p>
<p>If <em>true</em>, megaco message <em>re-sends</em> are made using the
- <seealso marker="megaco_transport#resend_message">resend_message</seealso>
+ <seeerl marker="megaco_transport#resend_message">resend_message</seeerl>
function. The initial message send is still done using the
- <seealso marker="megaco_transport#send_message">send_message</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message</seeerl>
function. </p>
<p>The special value <em>flag</em> instead indicates that the
function
- <seealso marker="megaco_transport#send_message">send_message/3</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message/3</seeerl>
shall be used. </p>
<p>A <c>resend_indication()</c>,
defaults to <c><![CDATA[false]]></c>.</p>
@@ -1149,7 +1149,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>This option specifies if the user shall be notified of received
segment replies or not. </p>
<p>See
- <seealso marker="megaco_user#segment_reply">handle_segment_reply</seealso>
+ <seeerl marker="megaco_user#segment_reply">handle_segment_reply</seeerl>
callback function for more information. </p>
<p>A <c><![CDATA[boolean]]></c>,
defaults to <c><![CDATA[false]]></c>. </p>
@@ -1167,10 +1167,10 @@ megaco_incr_timer() = #megaco_incr_timer{}
received" (459) error message is sent to the other side
and the user is notified with a
<c><![CDATA[segment timeout]]></c> <c><![CDATA[UserReply]]></c> in either the
- <seealso marker="megaco_user#trans_reply">handle_trans_reply</seealso>
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply</seeerl>
callback function or
the return value of the
- <seealso marker="megaco#call">call</seealso> function. </p>
+ <seeerl marker="megaco#call">call</seeerl> function. </p>
<p>A Megaco Timer (see explanation above),
defaults to <c><![CDATA[10000]]></c>. </p>
@@ -1300,10 +1300,10 @@ megaco_incr_timer() = #megaco_incr_timer{}
and their config, statistics and so on.</p>
<p>This information can be produced by the functions
- <seealso marker="#user_info">user_info</seealso>,
- <seealso marker="#conn_info">conn_info</seealso>,
- <seealso marker="#system_info">system_info</seealso> and
- <seealso marker="#get_stats">get_stats</seealso>
+ <seeerl marker="#user_info">user_info</seeerl>,
+ <seeerl marker="#conn_info">conn_info</seeerl>,
+ <seeerl marker="#system_info">system_info</seeerl> and
+ <seeerl marker="#get_stats">get_stats</seeerl>
but this is a simple way to get it all at once.</p>
<marker id="connect"></marker> <!-- Belongs to NEXT function(s) -->
@@ -1404,7 +1404,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>The connect is done in two steps: first an internal
<c>connection setup</c> and then by calling the user
- <seealso marker="megaco_user#connect">handle_connect</seealso>
+ <seeerl marker="megaco_user#connect">handle_connect</seeerl>
callback function. The first step could result in
an error with <c>Reason = connect_reason()</c> and the second
an error with <c>Reason = handle_connect_reason()</c>: </p>
@@ -1419,7 +1419,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<tag><c>handle_connect_reason()</c></tag>
<item>
<p>An error with this reason is caused by the user
- <seealso marker="megaco_user#connect">handle_connect</seealso>
+ <seeerl marker="megaco_user#connect">handle_connect</seeerl>
callback function either returning an error
or an invalid value.</p>
</item>
@@ -1429,7 +1429,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p><c><![CDATA[Extra]]></c> can be any <c><![CDATA[term()]]></c>
except the atom <c><![CDATA[ignore_extra]]></c>.
It is passed (back) to the user via the callback function
- <seealso marker="megaco_user#connect">handle_connect/3</seealso>. </p>
+ <seeerl marker="megaco_user#connect">handle_connect/3</seeerl>. </p>
<marker id="disconnect"></marker> <!-- Belongs to NEXT function(s) -->
</desc>
@@ -1519,7 +1519,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
replied with an explicit transactionError.</p>
<p>A <c><![CDATA[user_cancel_error()]]></c>, indicates that the request has been
canceled by the user. <c><![CDATA[reason_for_user_cancel()]]></c> is the reason
- given in the call to the <seealso marker="#cancel">cancel</seealso>
+ given in the call to the <seeerl marker="#cancel">cancel</seeerl>
function. </p>
<p>A <c><![CDATA[send_error()]]></c>, indicates that the send function of the
megaco transport callback module failed to send the request.
@@ -1528,14 +1528,14 @@ megaco_incr_timer() = #megaco_incr_timer{}
The first is the result of the send function returning
<c><![CDATA[{cancel, Reason}]]></c> and the second is some other kind of
erroneous return value. See the
- <seealso marker="megaco_transport#send_message">send_message</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message</seeerl>
function for more info. </p>
<p>An <c><![CDATA[other_error()]]></c>, indicates some other error such as
timeout.</p>
<p>For more info about the <c>extra()</c> part of the
result, see the
- <seealso marker="megaco_user#extra_argument">note</seealso>
+ <seeerl marker="megaco_user#extra_argument">note</seeerl>
in the user callback module documentation. </p>
@@ -1617,7 +1617,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<v>encoder_module() = megaco_pretty_text_encoder | megaco_compact_text_encoder | atom()</v>
<v>Version = int_version() | atom_version()</v>
<v>int_version() = 1 | 2 | 3</v>
- <v>atom_version() = v1 | v2 | v3 | prev3c | prev3b</v>
+ <v>atom_version() = v1 | v2 | v3</v>
<v>Result = string() | {error, Reason}</v>
<v>Reason = term()</v>
</type>
@@ -1682,12 +1682,12 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>The argument <c>Extra</c> is just an opaque data structure passed to the user
via the callback functions in the
- <seealso marker="megaco_user">user callback module</seealso>.
+ <seeerl marker="megaco_user">user callback module</seeerl>.
Note however that if <c>Extra</c> has the value
<c>extra_undefined</c> the argument will be ignored (same as if
<c>process_received_message/4</c> had been called).
See the documentation for the behaviour of the callback module,
- <seealso marker="megaco_user">megaco_user</seealso>, for more info. </p>
+ <seeerl marker="megaco_user">megaco_user</seeerl>, for more info. </p>
<p>Note that all processing is done in the context of the calling
process. A transport module could call this function via one of the
<c><![CDATA[spawn]]></c> functions (e.g. <c><![CDATA[spawn_opt]]></c>). See also
@@ -1775,7 +1775,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<c><![CDATA[process_received_message/4]]></c> function via a <c><![CDATA[spawn]]></c> to
perform the actual processing.</p>
<p>For further information see the
- <seealso marker="#process_received_message">process_received_message/4</seealso>
+ <seeerl marker="#process_received_message">process_received_message/4</seeerl>
function.</p>
<marker id="parse_digit_map"></marker>
@@ -1969,23 +1969,6 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name since="">get_sdp_record_from_PropertGroup(Type, PG) -> [sdp()]</name>
- <fsummary>Get all sdp records of a certain type from a property group</fsummary>
- <type>
- <v>Type = v | c | m | o | a | b | t | r | z | k | s | i | u | e | p</v>
- <v>PG = sdp_property_group()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>Retreive all the sdp records of type <c>Type</c> from the
- property group <c>PG</c>.</p>
-
- <marker id="versions1"></marker>
- <marker id="versions2"></marker>
- </desc>
- </func>
-
- <func>
<name since="">versions1() -> {ok, VersionInfo} | {error, Reason}</name>
<name since="">versions2() -> {ok, Info} | {error, Reason}</name>
<fsummary>Retreive various system and application info</fsummary>
@@ -2139,7 +2122,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<desc>
<p>Tests if the Actions argument is correctly composed.</p>
<p>This function is only intended for testing purposes. It's
- supposed to have a same kind of interface as the <seealso marker="#call">call</seealso> or <seealso marker="#cast">cast</seealso> functions (with the additions
+ supposed to have a same kind of interface as the <seeerl marker="#call">call</seeerl> or <seeerl marker="#cast">cast</seeerl> functions (with the additions
of the <c><![CDATA[EncodingMod]]></c> and <c><![CDATA[EncodingConfig]]></c>
arguments). It composes a complete megaco message end
attempts to encode it. The return value, will be a tuple of
@@ -2168,9 +2151,9 @@ megaco_incr_timer() = #megaco_incr_timer{}
<p>This function is only intended for testing purposes. It's
supposed to test the <c><![CDATA[actual_reply()]]></c> return value of
the callback functions
- <seealso marker="megaco_user#trans_request">handle_trans_request</seealso>
+ <seeerl marker="megaco_user#trans_request">handle_trans_request</seeerl>
and
- <seealso marker="megaco_user#trans_long_request">handle_trans_long_request</seealso>
+ <seeerl marker="megaco_user#trans_long_request">handle_trans_long_request</seeerl>
functions (with the additions of the <c><![CDATA[EncodingMod]]></c> and
<c><![CDATA[EncodingConfig]]></c> arguments). It composes a complete
megaco message end attempts to encode it. The return value,
diff --git a/lib/megaco/doc/src/megaco_debug.xml b/lib/megaco/doc/src/megaco_debug.xml
index 26fbfc002c..1988111274 100644
--- a/lib/megaco/doc/src/megaco_debug.xml
+++ b/lib/megaco/doc/src/megaco_debug.xml
@@ -44,9 +44,9 @@
our case tracing of calls to a given external function.</p>
<p>Event traces can be viewed in a generic message sequence chart
tool, <c>et</c>, or as standard output (events are written to stdio). </p>
- <p>See <seealso marker="megaco#enable_trace">enable_trace</seealso>,
- <seealso marker="megaco#disable_trace">disable_trace</seealso> and
- <seealso marker="megaco#set_trace">set_trace</seealso> for
+ <p>See <seeerl marker="megaco#enable_trace">enable_trace</seeerl>,
+ <seeerl marker="megaco#disable_trace">disable_trace</seeerl> and
+ <seeerl marker="megaco#set_trace">set_trace</seeerl> for
more info. </p>
</section>
@@ -213,7 +213,7 @@ message() = binary()
the <c>message_list()</c> has been encoded. </p>
<p>This file can be <c>exported</c> to a file structure by calling the
- <seealso marker="megaco_codec_transform#export_messages">export_messages</seealso>
+ <seeerl marker="megaco_codec_transform#export_messages">export_messages</seeerl>
function. This can be usefull if a measurement shall be done with
an external tool. Exporting the messages creates a directory tree
with the following structure:
diff --git a/lib/megaco/doc/src/megaco_encode.xml b/lib/megaco/doc/src/megaco_encode.xml
index 8bbf0f454c..0e8485762b 100644
--- a/lib/megaco/doc/src/megaco_encode.xml
+++ b/lib/megaco/doc/src/megaco_encode.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2000</year><year>2016</year>
+ <year>2000</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -254,7 +254,7 @@
standard distribution format after an internal transformation.
It is less verbose, but the total time of the encoding and
decoding will on the other hand be somewhat slower (see the
- <seealso marker="megaco_performance">performance</seealso>
+ <seeguide marker="megaco_performance">performance</seeguide>
chapter for more info).</p>
</item>
<item>
@@ -262,7 +262,7 @@
way as the megaco_compressed config parameter, only here the
user provide their own compress module. This module must
implement the
- <seealso marker="megaco_edist_compress">megaco_edist_compress</seealso>
+ <seeerl marker="megaco_edist_compress">megaco_edist_compress</seeerl>
behaviour.</p>
</item>
<item>
@@ -287,13 +287,13 @@
<item>
<p><c><![CDATA[[{flex, port()}]]]></c> - Use the flex scanner when
decoding (not optimized for SMP). See
- <seealso marker="megaco_run#initial_config">initial configuration</seealso>
+ <seeguide marker="megaco_run#initial_config">initial configuration</seeguide>
for more info.</p>
</item>
<item>
<p><c><![CDATA[[{flex, ports()}]]]></c> - Use the flex scanner when
decoding (optimized for SMP). See
- <seealso marker="megaco_run#initial_config">initial configuration</seealso>
+ <seeguide marker="megaco_run#initial_config">initial configuration</seeguide>
for more info.</p>
</item>
</list>
@@ -308,12 +308,12 @@
<item>
<p>Add the <c><![CDATA[{scanner, flex}]]></c> (or similar) directive to an
Erlang system config file for the megaco app (see
- <seealso marker="megaco_run#initial_config">initial configuration</seealso>
+ <seeguide marker="megaco_run#initial_config">initial configuration</seeguide>
chapter for details). </p>
</item>
<item>
<p>Retrieve the encoding-config using the
- <seealso marker="megaco#system_info">system_info</seealso>
+ <seeerl marker="megaco#system_info">system_info</seeerl>
function (with <c>Item = text_config</c>). </p>
</item>
<item>
@@ -379,28 +379,33 @@
<section>
<marker id="handling_versions"></marker>
<title>Handling megaco versions</title>
- <p>Since the version 3 implemented, in this version of the Megaco
- application, is preliminary, it is necessary to have a way
- to handle different version 3 implementations. For this reason
- the encoding config option <c><![CDATA[{version3, version3()}]]></c> has been
- introduced. This option, if present, has to be <em>first</em> in the
- encoding config list. Version 1 and 2 codec's ignore this option, if
- found. </p>
+ <note>
+ <p>This version (still) include three <c>pre version 3</c> variants of
+ the version 3 codec, beside the proper version 3.
+ These versions, <c>prev3a | prev3b | prev3c</c>,
+ are <em>deprecated</em>, and will be removed in OTP 24. </p>
+ </note>
+ <p>To be able to specify the different variants of version 3,
+ the encoding config option <c><![CDATA[{version3, version3()}]]></c>
+ <em>still</em> exists. This option, if present, has to be <em>first</em>
+ in the encoding config list. Version 1 and 2 codec's ignore this option,
+ if found. </p>
<code type="none"><![CDATA[
version3() -> prev3a | prev3b | prev3c | v3 ]]></code>
<list type="bulleted">
<item>
- <p><em>prev3a</em></p>
+ <p><em>DEPRECATED: prev3a</em></p>
<p>Preliminary version 3, based on TD-33</p>
</item>
<item>
- <p><em>prev3b</em></p>
+ <p><em>DEPRECATED: prev3b</em></p>
<p>Preliminary version 3, based on TD-33, but text encoding
updated with the final solution for priority in
- <c><![CDATA[contextProperty]]></c> (which is backward compatible with v2).</p>
+ <c><![CDATA[contextProperty]]></c> (which is backward compatible with
+v2).</p>
</item>
<item>
- <p><em>prev3c</em></p>
+ <p><em>DEPRECATED: prev3c</em></p>
<p>Preliminary version 3, based on the final version of the
v3-standard, but <em>excluding</em> segments!</p>
</item>
@@ -412,6 +417,8 @@ version3() -> prev3a | prev3b | prev3c | v3 ]]></code>
then v3 is assumed).</p>
</item>
</list>
+
+
<p>There are two ways to handle the different megaco encoding versions.
Either using <em>dynamic version detection</em> (only valid for
for incoming messages) or by <em>explicit version</em> setting in
@@ -437,9 +444,9 @@ version3() -> prev3a | prev3b | prev3c | v3 ]]></code>
megaco_receive_handle of the transport process (control_pid) to
version 2.
See
- <seealso marker="megaco_tcp#upgrade_receive_handle">megaco_tcp</seealso>
+ <seeerl marker="megaco_tcp#upgrade_receive_handle">megaco_tcp</seeerl>
and
- <seealso marker="megaco_udp#upgrade_receive_handle">megaco_udp</seealso>.
+ <seeerl marker="megaco_udp#upgrade_receive_handle">megaco_udp</seeerl>.
<br></br>Note that if <c><![CDATA[udp]]></c> is used, the same transport process
could be used for several connections. This could make upgrading
impossible.
@@ -458,8 +465,8 @@ version3() -> prev3a | prev3b | prev3c | v3 ]]></code>
<p>Override protocol version when sending a message by adding the
item <c><![CDATA[{protocol_version, integer()}]]></c> to the Options.
See
- <seealso marker="megaco#call">call</seealso> or
- <seealso marker="megaco#cast">cast</seealso>.
+ <seeerl marker="megaco#call">call</seeerl> or
+ <seeerl marker="megaco#cast">cast</seeerl>.
<br></br>Note that this does not effect the messages that are sent
autonomously by the stack. They use the protocol_version of the
connection info.</p>
@@ -470,7 +477,7 @@ version3() -> prev3a | prev3b | prev3c | v3 ]]></code>
<section>
<title>Encoder callback functions</title>
<p>The encoder callback interface is defined by the <c><![CDATA[megaco_encoder]]></c>
- behaviour, see <seealso marker="megaco_encoder">megaco_encoder</seealso>.</p>
+ behaviour, see <seeerl marker="megaco_encoder">megaco_encoder</seeerl>.</p>
</section>
</chapter>
diff --git a/lib/megaco/doc/src/megaco_encoder.xml b/lib/megaco/doc/src/megaco_encoder.xml
index 1a774fb01f..e8062ade5a 100644
--- a/lib/megaco/doc/src/megaco_encoder.xml
+++ b/lib/megaco/doc/src/megaco_encoder.xml
@@ -182,7 +182,7 @@ error_desc() = #'ErrorDescriptor'{}
<desc>
<p>Encode megaco action requests. This function is called when
the user calls the function
- <seealso marker="megaco#encode_actions">encode_actions/3</seealso>.
+ <seeerl marker="megaco#encode_actions">encode_actions/3</seeerl>.
If that function is never used or if the codec cannot support this
(the encoding of individual actions), then return with error reason
<c>not_implemented</c>. </p>
diff --git a/lib/megaco/doc/src/megaco_intro.xml b/lib/megaco/doc/src/megaco_intro.xml
index bd2192b69c..f43b86c633 100644
--- a/lib/megaco/doc/src/megaco_intro.xml
+++ b/lib/megaco/doc/src/megaco_intro.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2000</year><year>2016</year>
+ <year>2000</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -44,27 +44,11 @@
</item>
<item>
<p>version 2 - draft-ietf-megaco-h248v2-04 &amp; H.248.1
- v2 Corrigendum 1 (03/2004)</p>
+ v2 Corrigendum 1 (03/2004)</p>
</item>
<item>
- <p>version 3: </p>
- <list type="bulleted">
- <item>
- <p>prev3a - as defined by TD-33 (except segments)</p>
- </item>
- <item>
- <p>prev3b - TD-33 updated to be backward compatible with v2
- (except segments)</p>
- </item>
- <item>
- <p>prev3c - As defined by ITU H.248.1 (09/2005)
- (except segments)</p>
- </item>
- <item>
- <p>v3 - Full version 3 as defined by ITU H.248.1 (09/2005)
- (including segments)</p>
- </item>
- </list>
+ <p>version 3 - Full version 3 as defined by ITU H.248.1 (09/2005)
+ (including segments)</p>
</item>
</list>
<p>The semantics of the protocol has jointly been defined by two
@@ -148,9 +132,6 @@
<p><url href="http://www.erlang.org/project/megaco/standard/draft-ietf-megaco-h248v2-04.txt">version 2, draft-ietf-megaco-h248v2-04</url></p>
</item>
<item>
- <p><url href="http://www.itu.int/">TD-33 (Draft H.248.1 version 3)</url></p>
- </item>
- <item>
<p><url href="http://www.itu.int/">H.248.1 version 3</url></p>
</item>
<item>
diff --git a/lib/megaco/doc/src/megaco_mib.xml b/lib/megaco/doc/src/megaco_mib.xml
index e9bc34339c..96f84e19d2 100644
--- a/lib/megaco/doc/src/megaco_mib.xml
+++ b/lib/megaco/doc/src/megaco_mib.xml
@@ -50,8 +50,8 @@
<p>The implementation of the statistic counters is
lightweight. I.e. the statistic counters are handled
separately by different entities of the application. For
- instance our two transport module(s) (see <seealso marker="megaco_tcp#stats">megaco_tcp</seealso> and <seealso marker="megaco_udp#stats">megaco_udp</seealso>) maintain their
- own counters and the application engine (see <seealso marker="megaco#stats">megaco</seealso>) maintain its own
+ instance our two transport module(s) (see <seeerl marker="megaco_tcp#stats">megaco_tcp</seeerl> and <seeerl marker="megaco_udp#stats">megaco_udp</seeerl>) maintain their
+ own counters and the application engine (see <seeerl marker="megaco#stats">megaco</seeerl>) maintain its own
counters.</p>
<p>This also means that if a user implement their own transport
service then it has to maintain its own statistics.</p>
diff --git a/lib/megaco/doc/src/megaco_run.xml b/lib/megaco/doc/src/megaco_run.xml
index a20593fde2..4803b2d7d5 100644
--- a/lib/megaco/doc/src/megaco_run.xml
+++ b/lib/megaco/doc/src/megaco_run.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2000</year><year>2016</year>
+ <year>2000</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -197,17 +197,17 @@
<p>User info - Information related to megaco users. Read/Write. </p>
<p>A User is an entity identified by a MID, e.g. a MGC or a MG. </p>
<p>This information can be retrieved using
- <seealso marker="megaco#user_info">megaco:user_info</seealso>. </p>
+ <seeerl marker="megaco#user_info">megaco:user_info</seeerl>. </p>
</item>
<item>
<p>Connection info - Information regarding connections. Read/Write.</p>
<p>This information can be retrieved using
- <seealso marker="megaco#conn_info">megaco:conn_info</seealso>. </p>
+ <seeerl marker="megaco#conn_info">megaco:conn_info</seeerl>. </p>
</item>
<item>
<p>System info - System wide information. Read only.</p>
<p>This information can be retrieved using
- <seealso marker="megaco#system_info">megaco:system_info</seealso>. </p>
+ <seeerl marker="megaco#system_info">megaco:system_info</seeerl>. </p>
</item>
</list>
</section>
@@ -254,7 +254,7 @@
</list>
</item>
</list>
- <p>See also <seealso marker="megaco_encode#text_config">Configuration of text encoding module(s)</seealso>
+ <p>See also <seeguide marker="megaco_encode#text_config">Configuration of text encoding module(s)</seeguide>
for more info. </p>
</section>
@@ -262,8 +262,8 @@
<marker id="changing_config"></marker>
<title>Changing the configuration</title>
<p>The configuration can be changed during runtime. This is done with
- the functions <seealso marker="megaco#update_user_info">megaco:update_user_info</seealso> and
- <seealso marker="megaco#update_conn_info">megaco:update_conn_info</seealso></p>
+ the functions <seeerl marker="megaco#update_user_info">megaco:update_user_info</seeerl> and
+ <seeerl marker="megaco#update_conn_info">megaco:update_conn_info</seeerl></p>
</section>
<section>
@@ -271,8 +271,8 @@
<title>The transaction sender</title>
<p>The transaction sender is a process (one per connection), which handle
all transaction sending, if so configured (see
- <seealso marker="megaco#user_info">megaco:user_info</seealso> and
- <seealso marker="megaco#conn_info">megaco:conn_info</seealso>).</p>
+ <seeerl marker="megaco#user_info">megaco:user_info</seeerl> and
+ <seeerl marker="megaco#conn_info">megaco:conn_info</seeerl>).</p>
<p>The purpose of the transaction sender is to accumulate transactions
for a more efficient message sending. The transactions that are
accumulated are transaction request and transaction ack. For
@@ -324,32 +324,33 @@
<section>
<marker id="segment_reply"></marker>
<title>Segmentation of transaction replies</title>
- <p>In version 3 of the megaco standard the Segmentation package was
- introduced. Simply, this package defines a procedure to segment
- megaco messages (transaction replies) when using a transport that
- does not automatically do this (e.g. UDP). See also
- <seealso marker="megaco_encode#handling_versions">version3</seealso>.</p>
+ <p>In version 3 of the megaco standard, the concept of
+ <c>segmentation package</c> was introduced.
+ Simply, this package defines a procedure to segment
+ megaco messages (transaction replies) when using a transport that
+ does not automatically do this (e.g. UDP). See also
+ <seeguide marker="megaco_encode#handling_versions">version3</seeguide>.</p>
<p>Although it would be both pointless and counterproductive to use
- segmentation on a transport that already does this (e.g. TCP), the
- megaco application does not check this. Instead, it is up to the
- user to configure this properly. </p>
+ segmentation on a transport that already does this (e.g. TCP), the
+ megaco application does not check this. Instead, it is up to the
+ user to configure this properly. </p>
<list type="bulleted">
<item>
<p>Receiving segmented messages: </p>
<p>This is handled automatically by the megaco application.
There is however one thing that need to be configured by the user,
the
- <seealso marker="megaco#user_info">segment_recv_timer</seealso>
+ <seeerl marker="megaco#user_info">segment_recv_timer</seeerl>
option. </p>
<p>Note that the segments are delivered to the user differently
depending on which function is used to issue the original request.
When issuing the request using the
- <seealso marker="megaco#cast">megaco:cast</seealso> function,
+ <seeerl marker="megaco#cast">megaco:cast</seeerl> function,
the segments are delivered to the user via the
- <seealso marker="megaco_user#trans_reply">handle_trans_reply</seealso>
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply</seeerl>
callback function one at a time, as they arrive. But this obviously
doe not work for the
- <seealso marker="megaco#call">megaco:call</seealso> function.
+ <seeerl marker="megaco#call">megaco:call</seeerl> function.
In this case, the segments are accumulated and then delivered
all at once as the function returns.</p>
</item>
@@ -357,14 +358,14 @@
<p>Sending segmented messages: </p>
<p>This is also handled automatically by the megaco application.
First of all, segmentation is only attempted if so configured, see
- the <seealso marker="megaco#user_info">segment_send</seealso> option.
+ the <seeerl marker="megaco#user_info">segment_send</seeerl> option.
Secondly, megaco relies on the ability of the used codec to
encode action replies, which is the smallest component the
megaco application handles when segmenting. Thirdly, the
reply will be segmented only if the sum of the size of the
action replies (plus an arbitrary message header size) are greater
then the specified max message size (see the
- <seealso marker="megaco#user_info">max_pdu_size</seealso> option).
+ <seeerl marker="megaco#user_info">max_pdu_size</seeerl> option).
Finally, if segmentation is decided, then each action reply
will make up its own (segment) message.</p>
</item>
diff --git a/lib/megaco/doc/src/megaco_transport.xml b/lib/megaco/doc/src/megaco_transport.xml
index 6645446c71..fde9a037d4 100644
--- a/lib/megaco/doc/src/megaco_transport.xml
+++ b/lib/megaco/doc/src/megaco_transport.xml
@@ -39,13 +39,13 @@
<c><![CDATA[megaco_transport]]></c> callback module:</p>
<list type="bulleted">
<item>
- <p><seealso marker="#send_message">send_message/2</seealso> [<c><![CDATA[mandatory]]></c>]</p>
+ <p><seeerl marker="#send_message">send_message/2</seeerl> [<c><![CDATA[mandatory]]></c>]</p>
</item>
<item>
- <p><seealso marker="#send_message">send_message/3</seealso> [<c>optional</c>]</p>
+ <p><seeerl marker="#send_message">send_message/3</seeerl> [<c>optional</c>]</p>
</item>
<item>
- <p><seealso marker="#resend_message">resend_message/2</seealso> [<c><![CDATA[optional]]></c>]</p>
+ <p><seeerl marker="#resend_message">resend_message/2</seeerl> [<c><![CDATA[optional]]></c>]</p>
</item>
<item>
</item>
@@ -78,18 +78,18 @@
(after a successfull send). The information will be propagated
back to the user differently depending on how the request(s) where
issued: For requests issued using
- <seealso marker="megaco#call">megaco:call</seealso>, the info
+ <seeerl marker="megaco#call">megaco:call</seeerl>, the info
will be delivered in the return value. For requests issued
using <c><![CDATA[megaco:cast]]></c> the info will be delivered
via a call to the callback function
- <seealso marker="megaco_user#trans_reply">handle_trans_reply</seealso>. </p>
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply</seeerl>. </p>
<p>In the case of reply, megaco will cancel the reply and information
of this will be returned to the user via a call to the
callback function
- <seealso marker="megaco_user#trans_ack">handle_trans_ack</seealso>. </p>
+ <seeerl marker="megaco_user#trans_ack">handle_trans_ack</seeerl>. </p>
<p>The function <c>send_message/3</c> will only be called if the
- <seealso marker="megaco#ui_resend_indication">resend_indication</seealso>
+ <seeerl marker="megaco#ui_resend_indication">resend_indication</seeerl>
config option has been set to the value <c>flag</c>. The third
argument, <c>Resend</c> then indicates if the message send is
a resend or not. </p>
@@ -110,7 +110,7 @@
<desc>
<p>Re-send a megaco message. </p>
<p>Note that this function will only be called if the user has set the
- <seealso marker="megaco#ui_resend_indication">resend_indication</seealso>
+ <seeerl marker="megaco#ui_resend_indication">resend_indication</seeerl>
config option to <c><![CDATA[true]]></c><em>and</em> it is in fact a message
resend. If not <em>both</em> of these condition's are meet,
<c><![CDATA[send_message]]></c> will be called. </p>
@@ -125,15 +125,15 @@
(after a successfull send). The information will be propagated
back to the user differently depending on how the request(s) where
issued: For requests issued using
- <seealso marker="megaco#call">megaco:call</seealso>, the info
+ <seeerl marker="megaco#call">megaco:call</seeerl>, the info
will be delivered in the return value. For requests issued
using <c><![CDATA[megaco:cast]]></c> the info will be delivered via a call
to the callback function
- <seealso marker="megaco_user#trans_reply">handle_trans_reply</seealso>. </p>
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply</seeerl>. </p>
<p>In the case of reply, megaco will cancel the reply and information
of this will be returned to the user via a call to the
callback function
- <seealso marker="megaco_user#trans_ack">handle_trans_ack</seealso>. </p>
+ <seeerl marker="megaco_user#trans_ack">handle_trans_ack</seeerl>. </p>
</desc>
</func>
</funcs>
diff --git a/lib/megaco/doc/src/megaco_transport_mechanisms.xml b/lib/megaco/doc/src/megaco_transport_mechanisms.xml
index e2e32a8095..d2681a175f 100644
--- a/lib/megaco/doc/src/megaco_transport_mechanisms.xml
+++ b/lib/megaco/doc/src/megaco_transport_mechanisms.xml
@@ -51,7 +51,7 @@
</item>
</list>
<p>For more detail, see the
- <seealso marker="megaco_transport">megaco_transport</seealso>
+ <seeerl marker="megaco_transport">megaco_transport</seeerl>
behaviour definition.</p>
</section>
@@ -59,8 +59,8 @@
<title>Examples</title>
<p>The Megaco/H.248 application contains implementations
for the two protocols specified by the Megaco/H.248 standard;
- UDP, see <seealso marker="megaco_udp">megaco_udp</seealso>,
- and TCP/TPKT, see <seealso marker="megaco_tcp">megaco_tcp</seealso>. </p>
+ UDP, see <seeerl marker="megaco_udp">megaco_udp</seeerl>,
+ and TCP/TPKT, see <seeerl marker="megaco_tcp">megaco_tcp</seeerl>. </p>
</section>
</chapter>
diff --git a/lib/megaco/doc/src/megaco_user.xml b/lib/megaco/doc/src/megaco_user.xml
index 3752c9eeff..33532b9b4e 100644
--- a/lib/megaco/doc/src/megaco_user.xml
+++ b/lib/megaco/doc/src/megaco_user.xml
@@ -40,53 +40,53 @@
functions: </p>
<list type="bulleted">
<item>
- <p><seealso marker="#connect">handle_connect/2,3</seealso></p>
+ <p><seeerl marker="#connect">handle_connect/2,3</seeerl></p>
</item>
<item>
- <p><seealso marker="#disconnect">handle_disconnect/3</seealso></p>
+ <p><seeerl marker="#disconnect">handle_disconnect/3</seeerl></p>
</item>
<item>
- <p><seealso marker="#syntax_error">handle_syntax_error/3,4</seealso></p>
+ <p><seeerl marker="#syntax_error">handle_syntax_error/3,4</seeerl></p>
</item>
<item>
- <p><seealso marker="#message_error">handle_message_error/3,4</seealso></p>
+ <p><seeerl marker="#message_error">handle_message_error/3,4</seeerl></p>
</item>
<!--
<item>
- <p><seealso marker="#segment_error">handle_segment_error/4,5</seealso></p>
+ <p><seeerl marker="#segment_error">handle_segment_error/4,5</seeerl></p>
</item>
-->
<item>
- <p><seealso marker="#trans_request">handle_trans_request/3,4</seealso></p>
+ <p><seeerl marker="#trans_request">handle_trans_request/3,4</seeerl></p>
</item>
<item>
- <p><seealso marker="#trans_long_request">handle_trans_long_request/3,4</seealso></p>
+ <p><seeerl marker="#trans_long_request">handle_trans_long_request/3,4</seeerl></p>
</item>
<item>
- <p><seealso marker="#trans_reply">handle_trans_reply/4,5</seealso></p>
+ <p><seeerl marker="#trans_reply">handle_trans_reply/4,5</seeerl></p>
</item>
<item>
- <p><seealso marker="#trans_ack">handle_trans_ack/4,5</seealso></p>
+ <p><seeerl marker="#trans_ack">handle_trans_ack/4,5</seeerl></p>
</item>
<item>
- <p><seealso marker="#unexpected_trans">handle_unexpected_trans/3,4</seealso></p>
+ <p><seeerl marker="#unexpected_trans">handle_unexpected_trans/3,4</seeerl></p>
</item>
<item>
- <p><seealso marker="#request_abort">handle_trans_request_abort/4,5</seealso></p>
+ <p><seeerl marker="#request_abort">handle_trans_request_abort/4,5</seeerl></p>
</item>
<item>
- <p><seealso marker="#segment_reply">handle_segment_reply/5,6</seealso></p>
+ <p><seeerl marker="#segment_reply">handle_segment_reply/5,6</seeerl></p>
</item>
</list>
@@ -109,11 +109,11 @@
<marker id="extra_argument"></marker>
<note>
<p>Must of the functions below has an optional <c>Extra</c> argument (e.g.
- <seealso marker="#unexpected_trans">handle_unexpected_trans/4</seealso>).
+ <seeerl marker="#unexpected_trans">handle_unexpected_trans/4</seeerl>).
The functions which takes this argument will be called if and only if one
of the functions
- <seealso marker="megaco#receive_message">receive_message/5</seealso> or
- <seealso marker="megaco#process_received_message">process_received_message/5</seealso>
+ <seeerl marker="megaco#receive_message">receive_message/5</seeerl> or
+ <seeerl marker="megaco#process_received_message">process_received_message/5</seeerl>
was called with the <c>Extra</c> argument different than
<c>ignore_extra</c>. </p>
</note>
@@ -187,13 +187,13 @@ protocol_version() = integer() ]]></code>
reason "Connection refused by user" (this is also the case for all
unknown results, such as exit signals or throw).</p>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_message_error/4</c>. </p>
<p><c><![CDATA[handle_connect/3]]></c> (with <c><![CDATA[Extra]]></c>)
can also be called as a result of a call to the
- <seealso marker="megaco#connect">megaco:connect/5</seealso> function
+ <seeerl marker="megaco#connect">megaco:connect/5</seeerl> function
(if that function is called with the
<c>Extra</c> argument different than <c>ignore_extra</c>. </p>
@@ -249,7 +249,7 @@ protocol_version() = integer() ]]></code>
<p>Any other return values (including exit signals or throw) and the
<c><![CDATA[DefaultED]]></c> will be used. </p>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_syntax_error/4</c>. </p>
@@ -280,7 +280,7 @@ protocol_version() = integer() ]]></code>
that not will get any response (request -&gt; reply; reply -&gt;
ack).</p>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_message_error/4</c>. </p>
@@ -306,7 +306,7 @@ protocol_version() = integer() ]]></code>
<p><c><![CDATA[missing_segments]]></c> means that one or more
segments of a segmented message was not received in time.</p>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_segment_error/5</c>. </p>
@@ -366,7 +366,7 @@ protocol_version() = integer() ]]></code>
<p>If for some reason megaco is unable to deliver the reply,
the reason for this will be passed to the user via a call
to the callback function
- <seealso marker="#trans_ack">handle_trans_ack</seealso>,
+ <seeerl marker="#trans_ack">handle_trans_ack</seeerl>,
unless <c><![CDATA[ack_action() = discard_ack]]></c>. </p>
<p>The ack_action() is either:</p>
<taglist>
@@ -411,7 +411,7 @@ protocol_version() = integer() ]]></code>
result in an error descriptor with code 500 (internal gateway error)
and the module name (of the callback module) as reason. </p>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_trans_request/4</c>. </p>
@@ -451,7 +451,7 @@ protocol_version() = integer() ]]></code>
result in an error descriptor with code 500 (internal gateway error)
and the module name (of the callback module) as reason. </p>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_trans_long_request/4</c>. </p>
@@ -540,12 +540,12 @@ protocol_version() = integer() ]]></code>
<p>A <c><![CDATA[user_cancel_reason()]]></c>, indicates that the request
has been canceled by the user. <c><![CDATA[reason_for_user_cancel()]]></c>
is the reason given in the call to the
- <seealso marker="megaco#cancel">cancel</seealso>
+ <seeerl marker="megaco#cancel">cancel</seeerl>
function.</p>
</item>
<item>
<p>A <c><![CDATA[send_reason()]]></c>, indicates that the transport module
- <seealso marker="megaco_transport#send_message">send_message</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message</seeerl>
function did not send the message. The reason for this can be: </p>
<list type="bulleted">
<item>
@@ -553,7 +553,7 @@ protocol_version() = integer() ]]></code>
deliberately cancelled. <c><![CDATA[reason_for_send_cancel()]]></c>
is the reason given in the <c><![CDATA[cancel]]></c> return
from the
- <seealso marker="megaco_transport#send_message">send_message</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message</seeerl>
function. </p>
</item>
<item>
@@ -584,7 +584,7 @@ protocol_version() = integer() ]]></code>
</item>
</list>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_trans_reply/5</c>. </p>
@@ -638,7 +638,7 @@ protocol_version() = integer() ]]></code>
<tag>reply send failure</tag>
<item>
<p>When megaco fails to send the reply (see
- <seealso marker="#trans_reply">handle_trans_reply</seealso>),
+ <seeerl marker="#trans_reply">handle_trans_reply</seeerl>),
for whatever reason. </p>
</item>
<tag>cancel</tag>
@@ -648,7 +648,7 @@ protocol_version() = integer() ]]></code>
</item>
</taglist>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_trans_ack/5</c>. </p>
@@ -676,7 +676,7 @@ protocol_version() = integer() ]]></code>
The message is delivered to the "user" by calling this
function on the local node (the node which has the link).</p>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_unexpected_trans/4</c>. </p>
@@ -701,7 +701,7 @@ protocol_version() = integer() ]]></code>
has been exceeded. This usually means that a request has taken
abnormally long time to complete.</p>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_trans_request_abort/5</c>. </p>
@@ -724,11 +724,11 @@ protocol_version() = integer() ]]></code>
<desc>
<p>This function is called when a segment reply has been received
if the
- <seealso marker="megaco#conn_info">segment_reply_ind</seealso>
+ <seeerl marker="megaco#conn_info">segment_reply_ind</seeerl>
config option has been set to true.</p>
<p>This is in effect a progress report.</p>
- <p>See <seealso marker="#extra_argument">note</seealso>
+ <p>See <seeerl marker="#extra_argument">note</seeerl>
above about the <c>Extra</c> argument in
<c>handle_segment_reply/6</c>. </p>
diff --git a/lib/megaco/doc/src/notes.xml b/lib/megaco/doc/src/notes.xml
index 4d43adba71..7ea065ff72 100644
--- a/lib/megaco/doc/src/notes.xml
+++ b/lib/megaco/doc/src/notes.xml
@@ -37,7 +37,159 @@
section is the version number of Megaco.</p>
- <section><title>Megaco 3.18.8</title>
+ <section><title>Megaco 3.19.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed usage of <c>AC_CONFIG_AUX_DIRS()</c> macros in
+ configure script sources.</p>
+ <p>
+ Own Id: OTP-17093 Aux Id: ERL-1447, PR-2948 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.19.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Empty statistics descriptor (now) allowed in both encode
+ and decode for version 3.</p>
+ <p>
+ Own Id: OTP-17012 Aux Id: ERL-1405 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.19.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The expected number of warnings when (yecc) generating v2
+ and v3 (text) parser's was incorrect.</p>
+ <p>
+ Own Id: OTP-16836</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.19.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The v2 and v3 parsers could not properly decode some IPv6
+ addresses.</p>
+ <p>
+ Own Id: OTP-16818 Aux Id: ERIERL-526 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.19.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The mini parser could not properly decode some IPv6
+ addresses.</p>
+ <p>
+ Own Id: OTP-16631 Aux Id: ERIERL-491 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.19</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ The preliminary version 3 codec(s) prev3a, prev3b and
+ prev3c has been deprecated and will be *removed* in OTP
+ 24. The encoding config option 'version3' will continue
+ to work until OTP 24.</p>
+ <p>
+ Own Id: OTP-16531</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>Megaco 3.18.8.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Empty statistics descriptor (now) allowed in both encode
+ and decode for version 3.</p>
+ <p>
+ Own Id: OTP-17012 Aux Id: ERL-1405 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>Megaco 3.18.8.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The v2 and v3 parsers could not properly decode some IPv6
+ addresses.</p>
+ <p>
+ Own Id: OTP-16818 Aux Id: ERIERL-526 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>Megaco 3.18.8.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The mini parser could not properly decode some IPv6
+ addresses.</p>
+ <p>
+ Own Id: OTP-16631 Aux Id: ERIERL-491 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.18.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
@@ -351,7 +503,7 @@
The binary encoding option <c>driver</c> has been removed
since this (the use of the asn1 linked in driver) is
now default and there is now way to <em>not</em> use it.
- See <seealso marker="megaco_encode#binary_config">configuration of binary encoding</seealso> for more info. </p>
+ See <seeguide marker="megaco_encode#binary_config">configuration of binary encoding</seeguide> for more info. </p>
</item>
</list>
@@ -415,7 +567,7 @@
The binary encoding option <c>driver</c> has been removed
since this (the use of the asn1 linked in driver) is
now default and there is now way to <em>not</em> use it.
- See <seealso marker="megaco_encode#binary_config">configuration of binary encoding</seealso> for more info. </p>
+ See <seeguide marker="megaco_encode#binary_config">configuration of binary encoding</seeguide> for more info. </p>
</item>
</list>
@@ -701,7 +853,7 @@
<list type="bulleted">
<item>
<p>Updated the
- <seealso marker="megaco_performance">performance</seealso>
+ <seeguide marker="megaco_performance">performance</seeguide>
chapter. </p>
<p>Own Id: OTP-8696</p>
</item>
@@ -785,7 +937,7 @@
<list type="bulleted">
<item>
<p>Updated the
- <seealso marker="megaco_performance">performance</seealso>
+ <seeguide marker="megaco_performance">performance</seeguide>
chapter. </p>
<p>Own Id: OTP-8696</p>
</item>
diff --git a/lib/megaco/doc/src/notes_history.xml b/lib/megaco/doc/src/notes_history.xml
index 07e27542a5..7e6f04325a 100644
--- a/lib/megaco/doc/src/notes_history.xml
+++ b/lib/megaco/doc/src/notes_history.xml
@@ -66,7 +66,7 @@
<p>Segmenting a reply failed (with a badmatch) if the message
did not actually need to be segmented (e.g. was within the
size limit,
- <seealso marker="megaco#ui_max_pdu_size">max_pdu_size</seealso>). </p>
+ <seeerl marker="megaco#ui_max_pdu_size">max_pdu_size</seeerl>). </p>
<p>Own Id: OTP-7733</p>
<p>Aux Id: Seq 11168</p>
</item>
@@ -457,7 +457,7 @@
<list type="bulleted">
<item>
<p>Some minor documentation cleanup of the
- <seealso marker="megaco_run#transaction_sender">transaction sender</seealso>
+ <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide>
chapter of the User's Guide. </p>
<p>Own Id: OTP-7417</p>
<p>Aux Id: Seq 10989</p>
@@ -523,7 +523,7 @@
<list type="bulleted">
<item>
<p>Some minor documentation cleanup of the
- <seealso marker="megaco_run#transaction_sender">transaction sender</seealso>
+ <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide>
chapter of the User's Guide. </p>
<p>Own Id: OTP-7417</p>
<p>Aux Id: Seq 10989</p>
@@ -617,7 +617,7 @@
<p>Miscellaneous <em>text</em> codec(s) improvements. Both
regarding code size and <em>decode</em> performance. </p>
<p>See the
- <seealso marker="megaco_performance">performance</seealso>
+ <seeguide marker="megaco_performance">performance</seeguide>
chapter of the Users Guide for details.</p>
<p>Own Id: OTP-7228</p>
</item>
@@ -625,7 +625,7 @@
<item>
<p>Added the value <c>flag</c> to the resend_indication config
option. See
- <seealso marker="megaco#ui_resend_indication">resend_indication</seealso> for more info. </p>
+ <seeerl marker="megaco#ui_resend_indication">resend_indication</seeerl> for more info. </p>
<p>Own Id: OTP-7259</p>
<p>Aux Id: Seq 10901</p>
</item>
@@ -693,7 +693,7 @@
<list type="bulleted">
<item>
<p>Documentation of function
- <seealso marker="megaco#conn_info">megaco:conn_info/2</seealso>
+ <seeerl marker="megaco#conn_info">megaco:conn_info/2</seeerl>
has been updated to include known exit reasons. </p>
<p>Own Id: OTP-7286</p>
<p>Aux Id: Seq 10933</p>
@@ -759,13 +759,13 @@
<item>
<p>In order to better deal with codec's not implementing
the entire
- <seealso marker="megaco_encoder">megaco_encoder</seealso>
+ <seeerl marker="megaco_encoder">megaco_encoder</seeerl>
behaviour, translations of undefined (not implemented) functions
to the <c>{error, not_implemented}</c> return value has been
added for the functions
- <seealso marker="megaco_encoder#encode_transaction">encode_transaction/3</seealso>,
- <seealso marker="megaco_encoder#encode_action_requests">encode_action_requests/3</seealso> and
- <seealso marker="megaco_encoder#encode_action_reply">encode_action_reply/3</seealso>. </p>
+ <seeerl marker="megaco_encoder#encode_transaction">encode_transaction/3</seeerl>,
+ <seeerl marker="megaco_encoder#encode_action_requests">encode_action_requests/3</seeerl> and
+ <seeerl marker="megaco_encoder#encode_action_reply">encode_action_reply/3</seeerl>. </p>
<p>Own Id: OTP-7251</p>
<p>Aux Id: Seq 10879</p>
</item>
@@ -848,9 +848,9 @@
<p>The behaviour <c>megaco_encoder</c> was lacking three functions
which was made mandatory as of version 3.7. </p>
<p>See
- <seealso marker="megaco_encoder#encode_transaction">encode_transaction/3</seealso>,
- <seealso marker="megaco_encoder#encode_action_requests">encode_action_requests/3</seealso> and
- <seealso marker="megaco_encoder#encode_action_reply">encode_action_reply/3</seealso>
+ <seeerl marker="megaco_encoder#encode_transaction">encode_transaction/3</seeerl>,
+ <seeerl marker="megaco_encoder#encode_action_requests">encode_action_requests/3</seeerl> and
+ <seeerl marker="megaco_encoder#encode_action_reply">encode_action_reply/3</seeerl>
for more info</p>
<p>Own Id: OTP-7168</p>
<p>Aux Id: Seq 10867</p>
@@ -864,9 +864,9 @@
message but never actually sends the reply (or if
it is lost), the created pending counter data would
never be deleted if the
- <seealso marker="megaco#ui_long_request_timer">long_request_timer</seealso>
+ <seeerl marker="megaco#ui_long_request_timer">long_request_timer</seeerl>
was set to <c>infinity</c> (old default) and the
- <seealso marker="megaco#ui_recv_pending_limit">recv_pending_limit</seealso>
+ <seeerl marker="megaco#ui_recv_pending_limit">recv_pending_limit</seeerl>
was set to an integer value (default is <c>infinity</c>). </p>
<p>Own Id: OTP-7189</p>
<p>Aux Id: Seq 10879</p>
@@ -900,7 +900,7 @@
<item>
<p>The default value of the
- <seealso marker="megaco#ui_long_request_timer">long_request_timer</seealso>
+ <seeerl marker="megaco#ui_long_request_timer">long_request_timer</seeerl>
has been changed from <c>infinity</c> to <em>60 seconds</em>. </p>
<p>Own Id: OTP-7189</p>
<p>Aux Id: Seq 10879</p>
@@ -947,7 +947,7 @@
during a high load situations which could lead to
spurious (megaco internal) messages beeing sent to
user processes. When a request is issued using
- <seealso marker="megaco#call">megaco:call</seealso>,
+ <seeerl marker="megaco#call">megaco:call</seeerl>,
which returns only after a "reply" can
be delivered, the request timer might expire
during the cancelling of the request, which will cause
@@ -1105,9 +1105,9 @@
<p>Added support for the full v3-standard
(including segmentation).</p>
<p>See
- <seealso marker="megaco_run#segment_reply">segmentation of transaction replies</seealso>
+ <seeguide marker="megaco_run#segment_reply">segmentation of transaction replies</seeguide>
and
- <seealso marker="megaco_encode#handling_versions">handling megaco versions</seealso>
+ <seeguide marker="megaco_encode#handling_versions">handling megaco versions</seeguide>
for more info.</p>
<p>Note that segmentation is currently not supported by the
binary codec(s). </p>
@@ -1129,14 +1129,14 @@
<item>
<p>Added a way for the transport module to transfer
extra information to the user callback functions
- <seealso marker="megaco_user">callback functions</seealso>
+ <seeerl marker="megaco_user">callback functions</seeerl>
upon receipt of a message. This is done by adding an extra
argument when calling the (new) message delivery function(s)
- <seealso marker="megaco#process_received_message">process_received_message/5</seealso>
+ <seeerl marker="megaco#process_received_message">process_received_message/5</seeerl>
or
- <seealso marker="megaco#receive_message">receive_message/5</seealso>. </p>
+ <seeerl marker="megaco#receive_message">receive_message/5</seeerl>. </p>
<p>Similarly, the <c>UserReply</c> part of the return value for the
- <seealso marker="megaco#call">call</seealso> function can
+ <seeerl marker="megaco#call">call</seeerl> function can
now also include such extra information. </p>
<p>Own Id: OTP-6865</p>
<p>Aux Id: Seq 10559</p>
@@ -1152,10 +1152,10 @@
<item>
<p>Improve the utility functions for information retrieval:
- <seealso marker="megaco#info">megaco:info</seealso>,
- <seealso marker="megaco#system_info">megaco:system_info</seealso>,
- <seealso marker="megaco#conn_info">megaco:conn_info</seealso> and
- <seealso marker="megaco#user_info">megaco:user_info</seealso>.</p>
+ <seeerl marker="megaco#info">megaco:info</seeerl>,
+ <seeerl marker="megaco#system_info">megaco:system_info</seeerl>,
+ <seeerl marker="megaco#conn_info">megaco:conn_info</seeerl> and
+ <seeerl marker="megaco#user_info">megaco:user_info</seeerl>.</p>
<p>Own Id: OTP-6976</p>
<p>Aux Id: Seq 10804</p>
</item>
@@ -1233,7 +1233,7 @@
during a high load situations which could lead to
spurious (megaco internal) messages being sent to
user processes. When a request is issued using
- <seealso marker="megaco#call">megaco:call</seealso>,
+ <seeerl marker="megaco#call">megaco:call</seeerl>,
which returns only after a "reply" can
be delivered, the request timer might expire
during the cancelling of the request, which will cause
@@ -1270,7 +1270,7 @@
<item>
<p>The interface of the megaco_compressed callback module
has been changed, see the
- <seealso marker="megaco_edist_compress">megaco_edist_compress</seealso>
+ <seeerl marker="megaco_edist_compress">megaco_edist_compress</seeerl>
behaviour module for more details.
<p>Own Id: OTP-6609</p>
</item>
@@ -1329,7 +1329,7 @@
<item>
<p>The interface of the megaco_compressed callback module
has been changed, see the
- <seealso marker="megaco_edist_compress">megaco_edist_compress</seealso>
+ <seeerl marker="megaco_edist_compress">megaco_edist_compress</seeerl>
behaviour module for more details.
<p>Own Id: OTP-6609</p>
</item>
@@ -1359,7 +1359,7 @@
<item>
<p>An empty time-zone adjustment was incorrectly allowed
when decoding SDP using
- <seealso marker="megaco#decode_sdp">decode_sdp</seealso>. </p>
+ <seeerl marker="megaco#decode_sdp">decode_sdp</seeerl>. </p>
<p>Also the parsing of attributes was to restrictive. </p>
<p>Own Id: OTP-6803</p>
<p>Aux Id: Seq 10710</p>
@@ -1411,22 +1411,22 @@
<list type="bulleted">
<item>
<p>Improved erl_dist codec handling of
- <seealso marker="megaco_encode#erl_dist_config">megaco_compressed</seealso>
+ <seeguide marker="megaco_encode#erl_dist_config">megaco_compressed</seeguide>
codec config. Introduced behaviour,
- <seealso marker="megaco_edist_compress">megaco_edist_compress</seealso>,
+ <seeerl marker="megaco_edist_compress">megaco_edist_compress</seeerl>,
for the megaco_compressed callback of module. </p>
<p>Own Id: OTP-6609</p>
</item>
<item>
<p>The behaviour
- <seealso marker="megaco_transport">megaco_transport</seealso>
+ <seeerl marker="megaco_transport">megaco_transport</seeerl>
have been updated to include the <em>optional</em>
function
- <seealso marker="megaco_transport#resend_message">resend_message</seealso>.
+ <seeerl marker="megaco_transport#resend_message">resend_message</seeerl>.
This function is called instead of the standard
- <seealso marker="megaco_transport#send_message">send_message</seealso>
+ <seeerl marker="megaco_transport#send_message">send_message</seeerl>
if the new config option
- <seealso marker="megaco#ui_resend_indication">resend_indication</seealso>
+ <seeerl marker="megaco#ui_resend_indication">resend_indication</seeerl>
was set to <c><![CDATA[true]]></c><em>and</em> it is a message resend. </p>
<p>Own Id: OTP-6442</p>
<p>Aux Id: Seq 10393</p>
@@ -1434,7 +1434,7 @@
<item>
<p>Add ability to decrement the timeout time for
incremental timers, see
- <seealso marker="megaco#">reference manual</seealso>
+ <seeerl marker="megaco#">reference manual</seeerl>
for more info.</p>
<p>Own Id: OTP-6441</p>
<p>Aux Id: Seq 10349</p>
@@ -1452,7 +1452,7 @@
<list type="bulleted">
<item>
<p>Missing documentation link in module
- <seealso marker="megaco_user">megaco_user</seealso>. </p>
+ <seeerl marker="megaco_user">megaco_user</seeerl>. </p>
<p>Own Id: OTP-6578</p>
</item>
</list>
@@ -1464,7 +1464,7 @@
<item>
<p>The interface of the megaco_compressed callback module
has been changed, see the
- <seealso marker="megaco_edist_compress">megaco_edist_compress</seealso>
+ <seeerl marker="megaco_edist_compress">megaco_edist_compress</seeerl>
behaviour module for more details.</p>
<p>Own Id: OTP-6609</p>
</item>
@@ -1487,11 +1487,11 @@
<title>Fixed bugs and malfunctions</title>
<list type="bulleted">
<item>
- <p>Using the <seealso marker="megaco#call">call</seealso>
+ <p>Using the <seeerl marker="megaco#call">call</seeerl>
function at high load could cause replies from other
transactions to be returned also. </p>
<p>At high load, when a process calls the function
- <seealso marker="megaco#call">call</seealso>,
+ <seeerl marker="megaco#call">call</seeerl>,
which times out, then makes another
call, it is possible that the reply to the first
request arrives while the process is waiting for
@@ -1500,7 +1500,7 @@
this reply was also included in the result. </p>
<p>This problem has been fixed by calling the megaco_user
callback function
- <seealso marker="megaco_user#unexpected_trans">handle_unexpected_trans</seealso>
+ <seeerl marker="megaco_user#unexpected_trans">handle_unexpected_trans</seeerl>
if unexpected transactions are received. </p>
<p>Own Id: OTP-6549</p>
<p>Aux Id: Seq 10598</p>
@@ -1586,7 +1586,7 @@
service-change has not yet been received (505)
(either because it's been lost or because the MGC is
erroneous). This will now result in a call to
- <seealso marker="megaco_user#trans_reply">handle_trans_reply</seealso>
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply</seeerl>
with the UserReply: <c><![CDATA[{error, timeout}]]></c>. </p>
<p>Own Id: OTP-6275</p>
<p>Aux Id: Seq 10458</p>
@@ -1621,10 +1621,10 @@
of the megaco_transport callback module has been
extended (to allow the transport module to cancel the message
sending), see the functions
- <seealso marker="megaco#call">call</seealso>,
- <seealso marker="megaco_user#trans_reply">handle_trans_reply</seealso>,
- <seealso marker="megaco_user#trans_ack">handle_trans_ack</seealso>
- and <seealso marker="megaco_transport#send_message">send_message</seealso>
+ <seeerl marker="megaco#call">call</seeerl>,
+ <seeerl marker="megaco_user#trans_reply">handle_trans_reply</seeerl>,
+ <seeerl marker="megaco_user#trans_ack">handle_trans_ack</seeerl>
+ and <seeerl marker="megaco_transport#send_message">send_message</seeerl>
for more info.</p>
<p>Own Id: OTP-6253</p>
<p>Aux Id: Seq 10423</p>
@@ -1663,7 +1663,7 @@
<list type="bulleted">
<item>
<p>Improved documentation of
- <seealso marker="megaco#call">call</seealso> return values.</p>
+ <seeerl marker="megaco#call">call</seeerl> return values.</p>
<p>Own Id: OTP-6219</p>
</item>
</list>
@@ -1711,7 +1711,7 @@
<item>
<p>Added check for the illegal option reply_data in the Options
(third) argument to the
- <seealso marker="megaco#call">call</seealso>
+ <seeerl marker="megaco#call">call</seeerl>
function.
The valid options was documented but never checked.</p>
<p>Own Id: OTP-6171</p>
@@ -1725,7 +1725,7 @@
<list type="bulleted">
<item>
<p>A call to the function
- <seealso marker="megaco#call">call</seealso>
+ <seeerl marker="megaco#call">call</seeerl>
with the illegal option, reply_data,
will now result in an <c><![CDATA[{error, term()}]]></c> result.</p>
<p>Own Id: OTP-6171</p>
@@ -1819,11 +1819,11 @@
<p>Handling failure to send reply. Previously, when megaco
was unable to send a reply (built from the action list
returned by
- <seealso marker="megaco_user#trans_request">handle_trans_request</seealso>),
+ <seeerl marker="megaco_user#trans_request">handle_trans_request</seeerl>),
nothing was done, except for sending an error message.
This has now been changed so that the error will be returned
to the user via a call to the callback function,
- <seealso marker="megaco_user#trans_ack">handle_trans_ack</seealso>
+ <seeerl marker="megaco_user#trans_ack">handle_trans_ack</seeerl>
(unless <c><![CDATA[ack_action() = discard_ack]]></c>). </p>
<p>Own Id: OTP-6055</p>
</item>
@@ -1831,9 +1831,9 @@
<p>Add possibility to override send options when sending
reply messages. This is done by adding another return
alternative to the
- <seealso marker="megaco_user#trans_request">handle_trans_request</seealso>
+ <seeerl marker="megaco_user#trans_request">handle_trans_request</seeerl>
and
- <seealso marker="megaco_user#trans_long_request">handle_trans_long_request</seealso>
+ <seeerl marker="megaco_user#trans_long_request">handle_trans_long_request</seeerl>
callback function(s). </p>
<p>Own Id: OTP-6052</p>
<p>Aux Id: Seq 10284</p>
@@ -1845,8 +1845,8 @@
having received a pending message, until it receives
the reply or it gives up (timeout). </p>
<p>See
- <seealso marker="megaco#user_info">user_info</seealso> and
- <seealso marker="megaco#conn_info">conn_info</seealso>
+ <seeerl marker="megaco#user_info">user_info</seeerl> and
+ <seeerl marker="megaco#conn_info">conn_info</seeerl>
for more info. </p>
<p>Own Id: OTP-6051</p>
<p>Aux Id: Seq 10284</p>
@@ -1857,8 +1857,8 @@
is an intermediate timeout, the timer is restarted
(just as before) but the reply is also <em>resent</em>. </p>
<p>See
- <seealso marker="megaco#user_info">user_info</seealso> and
- <seealso marker="megaco#conn_info">conn_info</seealso>
+ <seeerl marker="megaco#user_info">user_info</seeerl> and
+ <seeerl marker="megaco#conn_info">conn_info</seeerl>
for more info. </p>
<p>Own Id: OTP-6048</p>
<p>Aux Id: Seq 10284</p>
@@ -1868,7 +1868,7 @@
immediate acknowledgement of a reply (when at least one
pending message has been sent). </p>
<p>This is done by adding another return value for the
- <seealso marker="megaco_user#trans_request">handle_trans_request</seealso>
+ <seeerl marker="megaco_user#trans_request">handle_trans_request</seeerl>
callback function. </p>
<p>Own Id: OTP-6030</p>
<p>Aux Id: Seq 10270</p>
@@ -1876,8 +1876,8 @@
<item>
<p>The SDP support has received a major updated. Added two new
function's to
- <seealso marker="megaco#decode_sdp">decode</seealso> and
- <seealso marker="megaco#encode_sdp">encode</seealso> SDP. </p>
+ <seeerl marker="megaco#decode_sdp">decode</seeerl> and
+ <seeerl marker="megaco#encode_sdp">encode</seeerl> SDP. </p>
<p>Own Id: OTP-5980</p>
</item>
<item>
@@ -2056,7 +2056,7 @@
<item>
<p>Added a utility function to facilitate pretty-printing of
messages, see
- <seealso marker="megaco#token_tag2string">token_tag2string</seealso>.</p>
+ <seeerl marker="megaco#token_tag2string">token_tag2string</seeerl>.</p>
<p>Own Id: OTP-5973</p>
<p>Aux Id: Seq 10224</p>
</item>
@@ -2178,8 +2178,8 @@
<list type="bulleted">
<item>
<p>Aligned the
- <seealso marker="megaco#user_info">user_info</seealso> and
- <seealso marker="megaco#conn_info">conn_info</seealso> functions
+ <seeerl marker="megaco#user_info">user_info</seeerl> and
+ <seeerl marker="megaco#conn_info">conn_info</seeerl> functions
regarding trans_id retrieval.</p>
<p>Own Id: OTP-5887</p>
<p>Aux Id: Seq 10184</p>
@@ -2192,7 +2192,7 @@
<list type="bulleted">
<item>
<p>The
- <seealso marker="megaco#conn_info">conn_info</seealso> function
+ <seeerl marker="megaco#conn_info">conn_info</seeerl> function
once again returns the <em>next</em> transaction id.</p>
<p>Own Id: OTP-5887</p>
<p>Aux Id: Seq 10184</p>
@@ -2217,19 +2217,19 @@
<list type="bulleted">
<item>
<p>Documentation of
- <seealso marker="megaco_udp#upgrade_receive_handle">upgrade_receive_handle</seealso> missing one argument (<c><![CDATA[NewHandle]]></c>).</p>
+ <seeerl marker="megaco_udp#upgrade_receive_handle">upgrade_receive_handle</seeerl> missing one argument (<c><![CDATA[NewHandle]]></c>).</p>
<p>Own Id: OTP-5867</p>
</item>
<item>
<p>Documentation of
- <seealso marker="megaco#conn_info">conn_info</seealso>
+ <seeerl marker="megaco#conn_info">conn_info</seeerl>
did not document legal <c><![CDATA[Item]]></c> value <c><![CDATA[local_mid]]></c>.</p>
<p>Own Id: OTP-5879</p>
<p>Aux Id: Seq 10176</p>
</item>
<item>
<p>Documentation of
- <seealso marker="megaco#conn_info">conn_info</seealso>
+ <seeerl marker="megaco#conn_info">conn_info</seeerl>
did not document legal <c><![CDATA[Item]]></c> value <c><![CDATA[remote_mid]]></c>.</p>
<p>Own Id: OTP-5880</p>
<p>Aux Id: Seq 10177</p>
@@ -2279,13 +2279,13 @@
<item>
<p>Assumed a more strict approach to the return values of
the callback functions of the
- <seealso marker="megaco_user">megaco_user</seealso> behaviour.
+ <seeerl marker="megaco_user">megaco_user</seeerl> behaviour.
Now, a return value other then what is described in
the behaviour documentation, will result in a warning
message. </p>
<p>Except for the
- <seealso marker="megaco_user#trans_request">handle_trans_request/3</seealso> and
- <seealso marker="megaco_user#trans_long_request">handle_trans_long_request/3</seealso>,
+ <seeerl marker="megaco_user#trans_request">handle_trans_request/3</seeerl> and
+ <seeerl marker="megaco_user#trans_long_request">handle_trans_long_request/3</seeerl>,
which still results in error messages.</p>
<p>Own Id: OTP-5830</p>
<p>Aux Id: Seq 10148</p>
@@ -2293,8 +2293,8 @@
<item>
<p>Introduced a <em>strict version control</em> (strict_version)
connection info and user info option. See the
- <seealso marker="megaco#user_info">user_info</seealso> and
- <seealso marker="megaco#conn_info">conn_info</seealso> functions
+ <seeerl marker="megaco#user_info">user_info</seeerl> and
+ <seeerl marker="megaco#conn_info">conn_info</seeerl> functions
for more info.</p>
<p>Own Id: OTP-5839</p>
</item>
@@ -2306,16 +2306,16 @@
<list type="bulleted">
<item>
<p>Digit Map: Improper handling of duration.
- <seealso marker="megaco#eval_digit_map">eval_digit_map</seealso>
+ <seeerl marker="megaco#eval_digit_map">eval_digit_map</seeerl>
and
- <seealso marker="megaco#test_digit_event">test_digit_event</seealso>.</p>
+ <seeerl marker="megaco#test_digit_event">test_digit_event</seeerl>.</p>
<p>Own Id: OTP-5826</p>
<p>Aux Id: Seq 10085</p>
</item>
<item>
<p>The documentation of the
- <seealso marker="megaco#call">call</seealso> and
- <seealso marker="megaco#cast">cast</seealso> functions
+ <seeerl marker="megaco#call">call</seeerl> and
+ <seeerl marker="megaco#cast">cast</seeerl> functions
was unclear regarding pre-encoded actions.</p>
<p>Own Id: OTP-5833</p>
<p>Aux Id: Seq 10146</p>
@@ -2334,8 +2334,8 @@
<list type="bulleted">
<item>
<p>The ok return value from
- <seealso marker="megaco#test_digit_event">test_digit_event</seealso> and
- <seealso marker="megaco#eval_digit_map">eval_digit_map</seealso>
+ <seeerl marker="megaco#test_digit_event">test_digit_event</seeerl> and
+ <seeerl marker="megaco#eval_digit_map">eval_digit_map</seeerl>
has been changed.</p>
<p>Own Id: OTP-5826</p>
<p>Aux Id: Seq 10085</p>
@@ -2361,9 +2361,9 @@
<item>
<p>Digit Map: Megaco does not handle "unexpected event"
according to chapter 7.1.14.5 point 5. See
- <seealso marker="megaco#eval_digit_map">eval_digit_map</seealso>
+ <seeerl marker="megaco#eval_digit_map">eval_digit_map</seeerl>
and
- <seealso marker="megaco#test_digit_event">test_digit_event</seealso>.</p>
+ <seeerl marker="megaco#test_digit_event">test_digit_event</seeerl>.</p>
<p>Own Id: OTP-5799</p>
<p>Aux Id: Seq 10085</p>
</item>
@@ -2400,8 +2400,8 @@
<list type="bulleted">
<item>
<p>The ok return value from
- <seealso marker="megaco#test_digit_event">test_digit_event</seealso> and
- <seealso marker="megaco#eval_digit_map">eval_digit_map</seealso>
+ <seeerl marker="megaco#test_digit_event">test_digit_event</seeerl> and
+ <seeerl marker="megaco#eval_digit_map">eval_digit_map</seeerl>
has been changed to accommodate for this problem.</p>
<p>Own Id: OTP-5799</p>
<p>Aux Id: Seq 10085</p>
@@ -2423,7 +2423,7 @@
<p>In order to allow the user to drop (ignore) a transaction
request a new return value, <c><![CDATA[ignore_trans_request]]></c>, has
been added to the
- <seealso marker="megaco_user#trans_request">handle_trans_request/3</seealso>
+ <seeerl marker="megaco_user#trans_request">handle_trans_request/3</seeerl>
callback function.</p>
<p>Own Id: OTP-5725</p>
<p>Aux Id: Seq 10084</p>
@@ -2474,8 +2474,8 @@
<list type="bulleted">
<item>
<p>The (digit map)
- <seealso marker="megaco#test_digit_event">test</seealso> and
- <seealso marker="megaco#eval_digit_map">eval</seealso> function(s)
+ <seeerl marker="megaco#test_digit_event">test</seeerl> and
+ <seeerl marker="megaco#eval_digit_map">eval</seeerl> function(s)
was lacking result info on what kind of completion
was done; <c><![CDATA[full]]></c> or <c><![CDATA[unambiguous]]></c>.</p>
<p>Own Id: OTP-5750</p>
@@ -2489,8 +2489,8 @@
<list type="bulleted">
<item>
<p>The (successfull) return value from the
- <seealso marker="megaco#test_digit_event">test_digit_event</seealso> and
- <seealso marker="megaco#eval_digit_map">eval_digit_map</seealso>
+ <seeerl marker="megaco#test_digit_event">test_digit_event</seeerl> and
+ <seeerl marker="megaco#eval_digit_map">eval_digit_map</seeerl>
has been changed.</p>
<p>Own Id: OTP-5750</p>
<p>Aux Id: Seq 10085</p>
@@ -2522,8 +2522,8 @@
<item>
<p>Added function format_versions/1 to print the extended version
info produced by the
- <seealso marker="megaco#versions1">versions1</seealso> and
- <seealso marker="megaco#versions2">versions2</seealso>
+ <seeerl marker="megaco#versions1">versions1</seeerl> and
+ <seeerl marker="megaco#versions2">versions2</seeerl>
functions.</p>
<p>Own Id: OTP-5664</p>
</item>
@@ -2585,13 +2585,13 @@
<item>
<p>Added utility functions to retrieve some system and application
info, see
- <seealso marker="megaco#versions1">versions1</seealso> and
- <seealso marker="megaco#versions2">versions2</seealso>.</p>
+ <seeerl marker="megaco#versions1">versions1</seeerl> and
+ <seeerl marker="megaco#versions2">versions2</seeerl>.</p>
<p>Own Id: OTP-5446</p>
</item>
<item>
<p>When the
- <seealso marker="megaco#enable_trace">enable_trace</seealso>
+ <seeerl marker="megaco#enable_trace">enable_trace</seeerl>
function is called with the <c><![CDATA[File]]></c> argument, it sets
up <c><![CDATA[dbg]]></c> so that trace events are written as plain text
to the given file (using <c><![CDATA[io:format]]></c>).</p>
@@ -2605,11 +2605,11 @@
<list type="bulleted">
<item>
<p>The <c><![CDATA[File]]></c> argument to the function
- <seealso marker="megaco#enable_trace">enable_trace</seealso> no
+ <seeerl marker="megaco#enable_trace">enable_trace</seeerl> no
longer sets up <c><![CDATA[dbg]]></c> to write the trace events directly to
file but instead to be written to a plain text file using
<c><![CDATA[io:format]]></c>. </p>
- <p>Also <seealso marker="megaco#enable_trace">enable_trace</seealso>
+ <p>Also <seeerl marker="megaco#enable_trace">enable_trace</seeerl>
no longer accepts the argument
<c><![CDATA[{io, Verbosity}]]></c>.</p>
<p>Own Id: OTP-5447</p>
@@ -2656,7 +2656,7 @@
</item>
<item>
<p>Added support for preliminary version 3. Based on TD-33.</p>
- <p>See chapter <seealso marker="megaco_encode#handling_versions">Handling megaco versions</seealso> on how to configure and
+ <p>See chapter <seeguide marker="megaco_encode#handling_versions">Handling megaco versions</seeguide> on how to configure and
use the preliminary version 3 (prev3a).</p>
<p>Own Id: OTP-5236</p>
</item>
@@ -2744,7 +2744,7 @@
</item>
<item>
<p>When the
- <seealso marker="megaco#cancel">megaco:cancel/2</seealso>
+ <seeerl marker="megaco#cancel">megaco:cancel/2</seeerl>
function is called, the
megaco application is supposed to perform a cleanup.
E.g. remove aut-dated request and reply records. For
@@ -3033,24 +3033,24 @@
of those previously encoded action requests as binaries
(as well as lists of action requests).
<br></br>See
- <seealso marker="megaco#encode_actions">encode_actions</seealso>,
- <seealso marker="megaco#call">call</seealso> and
- <seealso marker="megaco#cast">cast</seealso>.</p>
+ <seeerl marker="megaco#encode_actions">encode_actions</seeerl>,
+ <seeerl marker="megaco#call">call</seeerl> and
+ <seeerl marker="megaco#cast">cast</seeerl>.</p>
</item>
<item>
<p>Introduce a transaction sender process (one for each
connection) which will accumulate transactions and send
several in one message.
- <br></br>See <seealso marker="megaco_run#transaction_sender">transaction sender</seealso>,
- <seealso marker="megaco#user_info">user_info</seealso> and
- <seealso marker="megaco#conn_info">conn_info</seealso>.</p>
+ <br></br>See <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide>,
+ <seeerl marker="megaco#user_info">user_info</seeerl> and
+ <seeerl marker="megaco#conn_info">conn_info</seeerl>.</p>
</item>
<item>
<p>New encoding-config options for the erl_dist encoder which
makes it possible to compress the megaco messages. This makes
the erl_dist encoded message much more compact (about 1/3 of
the size).
- <br></br>See <seealso marker="megaco_encode#erl_dist_config">megaco_compressed</seealso>.</p>
+ <br></br>See <seeguide marker="megaco_encode#erl_dist_config">megaco_compressed</seeguide>.</p>
</item>
</list>
</section>
@@ -3108,9 +3108,9 @@
connection config items dealing with accumulating
transaction ack sending. These has all been renamed
due to the introduction of the transaction sender.
- <br></br>See <seealso marker="megaco_run#transaction_sender">transaction sender</seealso>,
- <seealso marker="megaco#user_info">user_info</seealso> and
- <seealso marker="megaco#conn_info">conn_info</seealso>.</p>
+ <br></br>See <seeguide marker="megaco_run#transaction_sender">transaction sender</seeguide>,
+ <seeerl marker="megaco#user_info">user_info</seeerl> and
+ <seeerl marker="megaco#conn_info">conn_info</seeerl>.</p>
</item>
</list>
</section>
@@ -3154,20 +3154,20 @@
<p>Support for both version 1 and 2 of the Megaco standard,
updated according with IGv10-13.</p>
<p>Version selection is described in chapter
- <seealso marker="megaco_encode#handling_versions">Handling megaco versions 1 &amp; 2</seealso>.</p>
+ <seeguide marker="megaco_encode#handling_versions">Handling megaco versions 1 &amp; 2</seeguide>.</p>
</item>
<item>
<p>It is now possible to use the ASN.1 linked in driver
for decode/encode of messages (encoding config <em>driver</em>).
<br></br>See chapter
- <seealso marker="megaco_encode#binary_config">Configuration of binary encoding module(s)</seealso>.</p>
+ <seeguide marker="megaco_encode#binary_config">Configuration of binary encoding module(s)</seeguide>.</p>
</item>
<item>
<p>Added a new configuration parameter, orig_pending_limit, to
support the xOriginatingPendingLimit (x = MG or MGC)
property in the root package.
<br></br>See the
- <seealso marker="megaco#user_info">orig_pending_limit</seealso>
+ <seeerl marker="megaco#user_info">orig_pending_limit</seeerl>
parameter of the user_info function (also conn_info).</p>
</item>
<item>
@@ -3176,18 +3176,18 @@
transaction requests in a message should be executed
in parallel (e.g. each in its own process).
<br></br>See the
- <seealso marker="megaco#user_info">threaded</seealso> parameter
+ <seeerl marker="megaco#user_info">threaded</seeerl> parameter
of the user_info function (also conn_info).</p>
</item>
<item>
<p>Added behaviour modules
- <seealso marker="megaco_transport">megaco_transport</seealso> and
- <seealso marker="megaco_encoder">megaco_encoder</seealso>.</p>
+ <seeerl marker="megaco_transport">megaco_transport</seeerl> and
+ <seeerl marker="megaco_encoder">megaco_encoder</seeerl>.</p>
</item>
<item>
<p>Added new (message) test functions to the megaco module, see
- <seealso marker="megaco#test_request">test_request</seealso> and
- <seealso marker="megaco#test_reply">test_reply</seealso>.</p>
+ <seeerl marker="megaco#test_request">test_request</seeerl> and
+ <seeerl marker="megaco#test_reply">test_reply</seeerl>.</p>
</item>
<item>
<p>Minor improvements to the tracing.</p>
@@ -3354,8 +3354,8 @@
<list type="bulleted">
<item>
<p>It is now possible to send more then one transaction (request)
- in a message. See <seealso marker="megaco#call">call</seealso>
- and <seealso marker="megaco#cast">cast</seealso>.</p>
+ in a message. See <seeerl marker="megaco#call">call</seeerl>
+ and <seeerl marker="megaco#cast">cast</seeerl>.</p>
<p>Req Id: M[4]-1</p>
<p>Own Id: OTP-4589</p>
</item>
@@ -3366,8 +3366,8 @@
whether the acks should be sent immediately or accumulated (and sent
later). Note that this has nothing to do with the
<c><![CDATA[immAckRequired]]></c>-flag in reply transactions.
- See <seealso marker="megaco#user_info">user_info</seealso>
- and <seealso marker="megaco#conn_info">conn_info</seealso>.</p>
+ See <seeerl marker="megaco#user_info">user_info</seeerl>
+ and <seeerl marker="megaco#conn_info">conn_info</seeerl>.</p>
<p>Own Id: OTP-4669</p>
</item>
</list>
@@ -3632,7 +3632,7 @@
<item>
<p>The user arguments was not supplied to the callback function
<c><![CDATA[handle_unexpected_trans]]></c> as described by
- <seealso marker="megaco_user">megaco_user</seealso>.</p>
+ <seeerl marker="megaco_user">megaco_user</seeerl>.</p>
<p>Own Id: OTP-4290</p>
</item>
</list>
@@ -3719,7 +3719,7 @@
<item>
<p>The megaco application now forward's unexpected replies.
This is done with a call to
- <seealso marker="megaco_user#handle_unexpected_trans">handle_unexpected_trans/3</seealso>.</p>
+ <seeerl marker="megaco_user#handle_unexpected_trans">handle_unexpected_trans/3</seeerl>.</p>
<p>Own Id: OTP-4212
<br></br>Aux Id: Seq 7181
</p>
diff --git a/lib/megaco/src/app/megaco.erl b/lib/megaco/src/app/megaco.erl
index 7381a7bfd1..5605036d17 100644
--- a/lib/megaco/src/app/megaco.erl
+++ b/lib/megaco/src/app/megaco.erl
@@ -101,8 +101,7 @@
formated_long_timestamp/0
]).
-%% This is for XREF
--deprecated([{format_versions, 1, eventually}]).
+-deprecated([{format_versions, 1, "use megaco:print_version_info/0,1 instead."}]).
-export_type([
void/0
diff --git a/lib/megaco/src/binary/Makefile b/lib/megaco/src/binary/Makefile
index b9643669f6..7fc90fd6d5 100644
--- a/lib/megaco/src/binary/Makefile
+++ b/lib/megaco/src/binary/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2000-2016. All Rights Reserved.
+# Copyright Ericsson AB 2000-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -47,7 +47,8 @@ include modules.mk
ASN1_SPECS = $(ASN1_V1_SPEC) \
$(ASN1_V2_SPEC) \
- $(ASN1_PREV3A_SPEC)
+ $(ASN1_V3_SPEC) \
+ $(ASN1_PREV3A_SPEC) $(ASN1_PREV3B_SPEC) $(ASN1_PREV3C_SPEC)
ASN1_FILES = $(ASN1_SPECS:%=%.asn)
V1_SPECS = $(BER_ASN1_V1_SPEC) \
diff --git a/lib/megaco/src/binary/depend.mk b/lib/megaco/src/binary/depend.mk
index 16901e39f0..d831e5fe54 100644
--- a/lib/megaco/src/binary/depend.mk
+++ b/lib/megaco/src/binary/depend.mk
@@ -2,7 +2,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2001-2016. All Rights Reserved.
+# Copyright Ericsson AB 2001-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -37,15 +37,19 @@ endif
BER_V1_FLAGS = $(ASN1_CT_OPTS) +asn1config
BER_V2_FLAGS = $(ASN1_CT_OPTS) +asn1config
+# <DEPRECATED>
BER_PREV3A_FLAGS = $(ASN1_CT_OPTS) +asn1config
BER_PREV3B_FLAGS = $(ASN1_CT_OPTS) +asn1config
BER_PREV3C_FLAGS = $(ASN1_CT_OPTS) +asn1config
+# </DEPRECATED>
BER_V3_FLAGS = $(ASN1_CT_OPTS) +asn1config
PER_V1_FLAGS = $(ASN1_CT_OPTS)
PER_V2_FLAGS = $(ASN1_CT_OPTS)
+# <DEPRECATED>
PER_PREV3A_FLAGS = $(ASN1_CT_OPTS)
PER_PREV3B_FLAGS = $(ASN1_CT_OPTS)
PER_PREV3C_FLAGS = $(ASN1_CT_OPTS)
+# </DEPRECATED>
PER_V3_FLAGS = $(ASN1_CT_OPTS)
@@ -94,6 +98,7 @@ $(EBIN)/$(PER_ASN1_V2_SPEC).$(EMULATOR): \
# -- (prev3a) --
+# <DEPRECATED>
$(BER_ASN1_PREV3A_SPEC).erl: \
$(BER_ASN1_PREV3A_SPEC).set.asn \
$(ASN1_PREV3A_SPEC).asn
@@ -111,10 +116,12 @@ $(PER_ASN1_PREV3A_SPEC).erl: \
$(EBIN)/$(PER_ASN1_PREV3A_SPEC).$(EMULATOR): \
$(PER_ASN1_PREV3A_SPEC).erl
+# </DEPRECATED>
# -- (prev3b) --
+# <DEPRECATED>
$(BER_ASN1_PREV3B_SPEC).erl: \
$(BER_ASN1_PREV3B_SPEC).set.asn \
$(ASN1_PREV3B_SPEC).asn
@@ -132,10 +139,12 @@ $(PER_ASN1_PREV3B_SPEC).erl: \
$(EBIN)/$(PER_ASN1_PREV3B_SPEC).$(EMULATOR): \
$(PER_ASN1_PREV3B_SPEC).erl
+# </DEPRECATED>
# -- (prev3c) --
+# <DEPRECATED>
$(BER_ASN1_PREV3C_SPEC).erl: \
$(BER_ASN1_PREV3C_SPEC).set.asn \
$(ASN1_PREV3C_SPEC).asn
@@ -153,6 +162,7 @@ $(PER_ASN1_PREV3C_SPEC).erl: \
$(EBIN)/$(PER_ASN1_PREV3C_SPEC).$(EMULATOR): \
$(PER_ASN1_PREV3C_SPEC).erl
+# </DEPRECATED>
# -- (v3) --
@@ -198,6 +208,7 @@ $(EBIN)/megaco_binary_name_resolver_v2.$(EMULATOR): \
megaco_binary_name_resolver_v2.erl \
../app/megaco_internal.hrl
+# <DEPRECATED>
$(EBIN)/megaco_binary_name_resolver_prev3a.$(EMULATOR): \
megaco_binary_name_resolver_prev3a.erl \
../app/megaco_internal.hrl
@@ -209,6 +220,7 @@ $(EBIN)/megaco_binary_name_resolver_prev3b.$(EMULATOR): \
$(EBIN)/megaco_binary_name_resolver_prev3c.$(EMULATOR): \
megaco_binary_name_resolver_prev3c.erl \
../app/megaco_internal.hrl
+# </DEPRECATED>
$(EBIN)/megaco_binary_name_resolver_v3.$(EMULATOR): \
megaco_binary_name_resolver_v3.erl
@@ -229,6 +241,7 @@ $(EBIN)/megaco_binary_transformer_v2.$(EMULATOR): \
$(MEGACO_INCLUDEDIR)/megaco.hrl \
$(MEGACO_INCLUDEDIR)/megaco_message_v2.hrl
+# <DEPRECATED>
$(EBIN)/megaco_binary_transformer_prev3a.$(EMULATOR): \
megaco_binary_transformer_prev3a.erl \
../app/megaco_internal.hrl \
@@ -246,6 +259,7 @@ $(EBIN)/megaco_binary_transformer_prev3c.$(EMULATOR): \
../app/megaco_internal.hrl \
$(MEGACO_INCLUDEDIR)/megaco.hrl \
$(MEGACO_INCLUDEDIR)/megaco_message_prev3c.hrl
+# </DEPRECATED>
$(EBIN)/megaco_binary_transformer_v3.$(EMULATOR): \
megaco_binary_transformer_v3.erl \
diff --git a/lib/megaco/src/binary/megaco_ber_encoder.erl b/lib/megaco/src/binary/megaco_ber_encoder.erl
index 1efe18c870..57f21f2a29 100644
--- a/lib/megaco/src/binary/megaco_ber_encoder.erl
+++ b/lib/megaco/src/binary/megaco_ber_encoder.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -45,16 +45,22 @@
-define(V1_ASN1_MOD, megaco_ber_media_gateway_control_v1).
-define(V2_ASN1_MOD, megaco_ber_media_gateway_control_v2).
-define(V3_ASN1_MOD, megaco_ber_media_gateway_control_v3).
+
+%% <DEPRECATED>
-define(PREV3A_ASN1_MOD, megaco_ber_media_gateway_control_prev3a).
-define(PREV3B_ASN1_MOD, megaco_ber_media_gateway_control_prev3b).
-define(PREV3C_ASN1_MOD, megaco_ber_media_gateway_control_prev3c).
+%% </DEPRECATED>
-define(V1_TRANS_MOD, megaco_binary_transformer_v1).
-define(V2_TRANS_MOD, megaco_binary_transformer_v2).
-define(V3_TRANS_MOD, megaco_binary_transformer_v3).
+
+%% <DEPRECATED>
-define(PREV3A_TRANS_MOD, megaco_binary_transformer_prev3a).
-define(PREV3B_TRANS_MOD, megaco_binary_transformer_prev3b).
-define(PREV3C_TRANS_MOD, megaco_binary_transformer_prev3c).
+%% </DEPRECATED>
-define(BIN_LIB, megaco_binary_encoder_lib).
@@ -64,9 +70,7 @@
%% Return {ok, Version} | {error, Reason}
%%----------------------------------------------------------------------
-version_of([{version3,v3}|EC], Binary) ->
- Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?V3_ASN1_MOD],
- ?BIN_LIB:version_of(EC, Binary, 1, Decoders);
+%% <DEPRECATED>
version_of([{version3,prev3c}|EC], Binary) ->
Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?PREV3C_ASN1_MOD],
?BIN_LIB:version_of(EC, Binary, 1, Decoders);
@@ -76,6 +80,14 @@ version_of([{version3,prev3b}|EC], Binary) ->
version_of([{version3,prev3a}|EC], Binary) ->
Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?PREV3A_ASN1_MOD],
?BIN_LIB:version_of(EC, Binary, 1, Decoders);
+%% <DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+version_of([{version3,v3}|EC], Binary) ->
+ Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?V3_ASN1_MOD],
+ ?BIN_LIB:version_of(EC, Binary, 1, Decoders);
+%% </BACKWARD-COMPAT-CLAUSE>
+
version_of(EC, Binary) ->
Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?V3_ASN1_MOD],
?BIN_LIB:version_of(EC, Binary, 1, Decoders).
@@ -93,10 +105,13 @@ encode_message(EC,
%% -- Version 1 --
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 1, MegaMsg) ->
AsnMod = ?V1_ASN1_MOD,
TransMod = ?V1_TRANS_MOD,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 1, MegaMsg) ->
AsnMod = ?V1_ASN1_MOD,
TransMod = ?V1_TRANS_MOD,
@@ -105,10 +120,13 @@ encode_message(EC, 1, MegaMsg) ->
%% -- Version 2 --
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 2, MegaMsg) ->
AsnMod = ?V2_ASN1_MOD,
TransMod = ?V2_TRANS_MOD,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 2, MegaMsg) ->
AsnMod = ?V2_ASN1_MOD,
TransMod = ?V2_TRANS_MOD,
@@ -117,10 +135,7 @@ encode_message(EC, 2, MegaMsg) ->
%% -- Version 3 --
-encode_message([{version3,v3}|EC], 3, MegaMsg) ->
- AsnMod = ?V3_ASN1_MOD,
- TransMod = ?V3_TRANS_MOD,
- ?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% <DEPRECATED>
encode_message([{version3,prev3c}|EC], 3, MegaMsg) ->
AsnMod = ?PREV3C_ASN1_MOD,
TransMod = ?PREV3C_TRANS_MOD,
@@ -133,6 +148,15 @@ encode_message([{version3,prev3a}|EC], 3, MegaMsg) ->
AsnMod = ?PREV3A_ASN1_MOD,
TransMod = ?PREV3A_TRANS_MOD,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_message([{version3,v3}|EC], 3, MegaMsg) ->
+ AsnMod = ?V3_ASN1_MOD,
+ TransMod = ?V3_TRANS_MOD,
+ ?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 3, MegaMsg) ->
AsnMod = ?V3_ASN1_MOD,
TransMod = ?V3_TRANS_MOD,
@@ -244,10 +268,13 @@ decode_message(EC, dynamic, Binary) ->
%% -- Version 1 --
+%% <BACKWARD-COMPAT-CLAUSE>
decode_message([{version3,_}|EC], 1, Binary) ->
AsnMod = ?V1_ASN1_MOD,
TransMod = ?V1_TRANS_MOD,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message(EC, 1, Binary) ->
AsnMod = ?V1_ASN1_MOD,
TransMod = ?V1_TRANS_MOD,
@@ -256,10 +283,13 @@ decode_message(EC, 1, Binary) ->
%% -- Version 2 --
+%% <BACKWARD-COMPAT-CLAUSE>
decode_message([{version3,_}|EC], 2, Binary) ->
AsnMod = ?V2_ASN1_MOD,
TransMod = ?V2_TRANS_MOD,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message(EC, 2, Binary) ->
AsnMod = ?V2_ASN1_MOD,
TransMod = ?V2_TRANS_MOD,
@@ -268,10 +298,7 @@ decode_message(EC, 2, Binary) ->
%% -- Version 3 --
-decode_message([{version3,v3}|EC], 3, Binary) ->
- AsnMod = ?V3_ASN1_MOD,
- TransMod = ?V3_TRANS_MOD,
- ?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% <DEPRECATED>
decode_message([{version3,prev3c}|EC], 3, Binary) ->
AsnMod = ?PREV3C_ASN1_MOD,
TransMod = ?PREV3C_TRANS_MOD,
@@ -284,17 +311,22 @@ decode_message([{version3,prev3a}|EC], 3, Binary) ->
AsnMod = ?PREV3A_ASN1_MOD,
TransMod = ?PREV3A_TRANS_MOD,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_message([{version3,v3}|EC], 3, Binary) ->
+ AsnMod = ?V3_ASN1_MOD,
+ TransMod = ?V3_TRANS_MOD,
+ ?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message(EC, 3, Binary) ->
AsnMod = ?V3_ASN1_MOD,
TransMod = ?V3_TRANS_MOD,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary).
-decode_mini_message([{version3,v3}|EC], dynamic, Bin) ->
- Mods = [?V1_ASN1_MOD,
- ?V2_ASN1_MOD,
- ?V3_ASN1_MOD],
- ?BIN_LIB:decode_mini_message_dynamic(EC, Bin, Mods, binary);
+%% <DEPRECATED>
decode_mini_message([{version3,prev3c}|EC], dynamic, Bin) ->
Mods = [?V1_ASN1_MOD,
?V2_ASN1_MOD,
@@ -310,27 +342,43 @@ decode_mini_message([{version3,prev3a}|EC], dynamic, Bin) ->
?V2_ASN1_MOD,
?PREV3A_ASN1_MOD],
?BIN_LIB:decode_mini_message_dynamic(EC, Bin, Mods, binary);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_mini_message([{version3,v3}|EC], dynamic, Bin) ->
+ Mods = [?V1_ASN1_MOD,
+ ?V2_ASN1_MOD,
+ ?V3_ASN1_MOD],
+ ?BIN_LIB:decode_mini_message_dynamic(EC, Bin, Mods, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_mini_message(EC, dynamic, Bin) ->
Mods = [?V1_ASN1_MOD,
?V2_ASN1_MOD,
?V3_ASN1_MOD],
?BIN_LIB:decode_mini_message_dynamic(EC, Bin, Mods, binary);
+%% <BACKWARD-COMPAT-CLAUSE>
decode_mini_message([{version3,_}|EC], 1, Bin) ->
AsnMod = ?V1_ASN1_MOD,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_mini_message(EC, 1, Bin) ->
AsnMod = ?V1_ASN1_MOD,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+
+%% <BACKWARD-COMPAT-CLAUSE>
decode_mini_message([{version3,_}|EC], 2, Bin) ->
AsnMod = ?V2_ASN1_MOD,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_mini_message(EC, 2, Bin) ->
AsnMod = ?V2_ASN1_MOD,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
-decode_mini_message([{version3,v3}|EC], 3, Bin) ->
- AsnMod = ?V3_ASN1_MOD,
- ?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+
+%% <DEPRECATED>
decode_mini_message([{version3,prev3c}|EC], 3, Bin) ->
AsnMod = ?PREV3C_ASN1_MOD,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
@@ -340,6 +388,15 @@ decode_mini_message([{version3,prev3b}|EC], 3, Bin) ->
decode_mini_message([{version3,prev3a}|EC], 3, Bin) ->
AsnMod = ?PREV3A_ASN1_MOD,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% </DEPRECATED>
+
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_mini_message([{version3,v3}|EC], 3, Bin) ->
+ AsnMod = ?V3_ASN1_MOD,
+ ?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_mini_message(EC, 3, Bin) ->
AsnMod = ?V3_ASN1_MOD,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary).
diff --git a/lib/megaco/src/binary/megaco_binary_encoder.erl b/lib/megaco/src/binary/megaco_binary_encoder.erl
index e0bcd8fe4d..40ee11c59d 100644
--- a/lib/megaco/src/binary/megaco_binary_encoder.erl
+++ b/lib/megaco/src/binary/megaco_binary_encoder.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -55,26 +55,31 @@ encode_message(EC,
#'MegacoMessage'{mess = #'Message'{version = V}} = MegaMsg) ->
encode_message(EC, V, MegaMsg).
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 1, MegaMsg) ->
AsnMod = megaco_ber_media_gateway_control_v1,
TransMod = megaco_binary_transformer_v1,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 1, MegaMsg) ->
AsnMod = megaco_ber_media_gateway_control_v1,
TransMod = megaco_binary_transformer_v1,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 2, MegaMsg) ->
AsnMod = megaco_ber_media_gateway_control_v2,
TransMod = megaco_binary_transformer_v2,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 2, MegaMsg) ->
AsnMod = megaco_ber_media_gateway_control_v2,
TransMod = megaco_binary_transformer_v2,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
-encode_message([{version3,v3}|EC], 3, MegaMsg) ->
- AsnMod = megaco_ber_media_gateway_control_v3,
- TransMod = megaco_binary_transformer_v3,
- ?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+
+%% <DEPRECATED>
encode_message([{version3,prev3c}|EC], 3, MegaMsg) ->
AsnMod = megaco_ber_media_gateway_control_prev3c,
TransMod = megaco_binary_transformer_prev3c,
@@ -87,6 +92,15 @@ encode_message([{version3,prev3a}|EC], 3, MegaMsg) ->
AsnMod = megaco_ber_media_gateway_control_prev3a,
TransMod = megaco_binary_transformer_prev3a,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_message([{version3,v3}|EC], 3, MegaMsg) ->
+ AsnMod = megaco_ber_media_gateway_control_v3,
+ TransMod = megaco_binary_transformer_v3,
+ ?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 3, MegaMsg) ->
AsnMod = megaco_ber_media_gateway_control_v3,
TransMod = megaco_binary_transformer_v3,
@@ -99,26 +113,31 @@ encode_message(EC, 3, MegaMsg) ->
%% Return {ok, Binary} | {error, Reason}
%%----------------------------------------------------------------------
+%% <BACKWARD-COMPAT-CLAUSE>
encode_transaction([{version3,_}|EC], 1, Trans) ->
AsnMod = megaco_ber_media_gateway_control_v1,
TransMod = megaco_binary_transformer_v1,
?BIN_LIB:encode_transaction(EC, Trans, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction(EC, 1, Trans) ->
AsnMod = megaco_ber_media_gateway_control_v1,
TransMod = megaco_binary_transformer_v1,
?BIN_LIB:encode_transaction(EC, Trans, AsnMod, TransMod, io_list);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_transaction([{version3,_}|EC], 2, Trans) ->
AsnMod = megaco_ber_media_gateway_control_v2,
TransMod = megaco_binary_transformer_v2,
?BIN_LIB:encode_transaction(EC, Trans, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction(EC, 2, Trans) ->
AsnMod = megaco_ber_media_gateway_control_v2,
TransMod = megaco_binary_transformer_v2,
?BIN_LIB:encode_transaction(EC, Trans, AsnMod, TransMod, io_list);
-encode_transaction([{version3,v3}|EC], 3, Trans) ->
- AsnMod = megaco_ber_media_gateway_control_v3,
- TransMod = megaco_binary_transformer_v3,
- ?BIN_LIB:encode_transaction(EC, Trans, AsnMod, TransMod, io_list);
+
+%% <DEPRECATED>
encode_transaction([{version3,prev3c}|EC], 3, Trans) ->
AsnMod = megaco_ber_media_gateway_control_prev3c,
TransMod = megaco_binary_transformer_prev3c,
@@ -131,6 +150,15 @@ encode_transaction([{version3,prev3a}|EC], 3, Trans) ->
AsnMod = megaco_ber_media_gateway_control_prev3a,
TransMod = megaco_binary_transformer_prev3a,
?BIN_LIB:encode_transaction(EC, Trans, AsnMod, TransMod, io_list);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_transaction([{version3,v3}|EC], 3, Trans) ->
+ AsnMod = megaco_ber_media_gateway_control_v3,
+ TransMod = megaco_binary_transformer_v3,
+ ?BIN_LIB:encode_transaction(EC, Trans, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction(EC, 3, Trans) ->
AsnMod = megaco_ber_media_gateway_control_v3,
TransMod = megaco_binary_transformer_v3,
@@ -141,29 +169,34 @@ encode_transaction(EC, 3, Trans) ->
%% Convert a list of ActionRequest record's into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_requests([{version3,_}|EC], 1, ActReqs)
when is_list(ActReqs) ->
AsnMod = megaco_ber_media_gateway_control_v1,
TransMod = megaco_binary_transformer_v1,
?BIN_LIB:encode_action_requests(EC, ActReqs, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests(EC, 1, ActReqs) when is_list(ActReqs) ->
AsnMod = megaco_ber_media_gateway_control_v1,
TransMod = megaco_binary_transformer_v1,
?BIN_LIB:encode_action_requests(EC, ActReqs, AsnMod, TransMod, io_list);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_requests([{version3,_}|EC], 2, ActReqs)
when is_list(ActReqs) ->
AsnMod = megaco_ber_media_gateway_control_v2,
TransMod = megaco_binary_transformer_v2,
?BIN_LIB:encode_action_requests(EC, ActReqs, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests(EC, 2, ActReqs) when is_list(ActReqs) ->
AsnMod = megaco_ber_media_gateway_control_v2,
TransMod = megaco_binary_transformer_v2,
?BIN_LIB:encode_action_requests(EC, ActReqs, AsnMod, TransMod, io_list);
-encode_action_requests([{version3,v3}|EC], 3, ActReqs)
- when is_list(ActReqs) ->
- AsnMod = megaco_ber_media_gateway_control_v3,
- TransMod = megaco_binary_transformer_v3,
- ?BIN_LIB:encode_action_requests(EC, ActReqs, AsnMod, TransMod, io_list);
+
+%% <DEPRECATED>
encode_action_requests([{version3,prev3c}|EC], 3, ActReqs)
when is_list(ActReqs) ->
AsnMod = megaco_ber_media_gateway_control_prev3c,
@@ -179,6 +212,16 @@ encode_action_requests([{version3,prev3a}|EC], 3, ActReqs)
AsnMod = megaco_ber_media_gateway_control_prev3a,
TransMod = megaco_binary_transformer_prev3a,
?BIN_LIB:encode_action_requests(EC, ActReqs, AsnMod, TransMod, io_list);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_action_requests([{version3,v3}|EC], 3, ActReqs)
+ when is_list(ActReqs) ->
+ AsnMod = megaco_ber_media_gateway_control_v3,
+ TransMod = megaco_binary_transformer_v3,
+ ?BIN_LIB:encode_action_requests(EC, ActReqs, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests(EC, 3, ActReqs) when is_list(ActReqs) ->
AsnMod = megaco_ber_media_gateway_control_v3,
TransMod = megaco_binary_transformer_v3,
@@ -189,26 +232,32 @@ encode_action_requests(EC, 3, ActReqs) when is_list(ActReqs) ->
%% Convert a ActionRequest record into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_request([{version3,_}|EC], 1, ActReq) ->
AsnMod = megaco_ber_media_gateway_control_v1,
TransMod = megaco_binary_transformer_v1,
?BIN_LIB:encode_action_request(EC, ActReq, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request(EC, 1, ActReq) ->
AsnMod = megaco_ber_media_gateway_control_v1,
TransMod = megaco_binary_transformer_v1,
?BIN_LIB:encode_action_request(EC, ActReq, AsnMod, TransMod, io_list);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_request([{version3,_}|EC], 2, ActReq) ->
AsnMod = megaco_ber_media_gateway_control_v2,
TransMod = megaco_binary_transformer_v2,
?BIN_LIB:encode_action_request(EC, ActReq, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request(EC, 2, ActReq) ->
AsnMod = megaco_ber_media_gateway_control_v2,
TransMod = megaco_binary_transformer_v2,
?BIN_LIB:encode_action_request(EC, ActReq, AsnMod, TransMod, io_list);
-encode_action_request([{version3,v3}|EC], 3, ActReq) ->
- AsnMod = megaco_ber_media_gateway_control_v3,
- TransMod = megaco_binary_transformer_v3,
- ?BIN_LIB:encode_action_request(EC, ActReq, AsnMod, TransMod, io_list);
+
+%% <DEPRECATED>
encode_action_request([{version3,prev3c}|EC], 3, ActReq) ->
AsnMod = megaco_ber_media_gateway_control_prev3c,
TransMod = megaco_binary_transformer_prev3c,
@@ -221,6 +270,15 @@ encode_action_request([{version3,prev3a}|EC], 3, ActReq) ->
AsnMod = megaco_ber_media_gateway_control_prev3a,
TransMod = megaco_binary_transformer_prev3a,
?BIN_LIB:encode_action_request(EC, ActReq, AsnMod, TransMod, io_list);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_action_request([{version3,v3}|EC], 3, ActReq) ->
+ AsnMod = megaco_ber_media_gateway_control_v3,
+ TransMod = megaco_binary_transformer_v3,
+ ?BIN_LIB:encode_action_request(EC, ActReq, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request(EC, 3, ActReq) ->
AsnMod = megaco_ber_media_gateway_control_v3,
TransMod = megaco_binary_transformer_v3,
@@ -242,11 +300,7 @@ encode_action_reply(_EC, _V, _AcionReply) ->
%% Return {ok, Version} | {error, Reason}
%%----------------------------------------------------------------------
-version_of([{version3,v3}|EC], Binary) ->
- Decoders = [megaco_ber_media_gateway_control_v1,
- megaco_ber_media_gateway_control_v2,
- megaco_ber_media_gateway_control_v3],
- ?BIN_LIB:version_of(EC, Binary, dynamic, Decoders);
+%% <DEPRECATED>
version_of([{version3,prev3c}|EC], Binary) ->
Decoders = [megaco_ber_media_gateway_control_v1,
megaco_ber_media_gateway_control_v2,
@@ -262,6 +316,16 @@ version_of([{version3,prev3a}|EC], Binary) ->
megaco_ber_media_gateway_control_v2,
megaco_ber_media_gateway_control_prev3a],
?BIN_LIB:version_of(EC, Binary, dynamic, Decoders);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+version_of([{version3,v3}|EC], Binary) ->
+ Decoders = [megaco_ber_media_gateway_control_v1,
+ megaco_ber_media_gateway_control_v2,
+ megaco_ber_media_gateway_control_v3],
+ ?BIN_LIB:version_of(EC, Binary, dynamic, Decoders);
+%% </BACKWARD-COMPAT-CLAUSE>
+
version_of(EC, Binary) ->
Decoders = [megaco_ber_media_gateway_control_v1,
megaco_ber_media_gateway_control_v2,
@@ -277,14 +341,7 @@ version_of(EC, Binary) ->
decode_message(EC, Binary) ->
decode_message(EC, 1, Binary).
-decode_message([{version3,v3}|EC], dynamic, Binary) ->
- Decoders = [{megaco_ber_media_gateway_control_v1,
- megaco_binary_transformer_v1},
- {megaco_ber_media_gateway_control_v2,
- megaco_binary_transformer_v2},
- {megaco_ber_media_gateway_control_v3,
- megaco_binary_transformer_v3}],
- ?BIN_LIB:decode_message_dynamic(EC, Binary, Decoders, binary);
+%% <DEPRECATED>
decode_message([{version3,prev3c}|EC], dynamic, Binary) ->
Decoders = [{megaco_ber_media_gateway_control_v1,
megaco_binary_transformer_v1},
@@ -309,6 +366,19 @@ decode_message([{version3,prev3a}|EC], dynamic, Binary) ->
{megaco_ber_media_gateway_control_prev3a,
megaco_binary_transformer_prev3a}],
?BIN_LIB:decode_message_dynamic(EC, Binary, Decoders, binary);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_message([{version3,v3}|EC], dynamic, Binary) ->
+ Decoders = [{megaco_ber_media_gateway_control_v1,
+ megaco_binary_transformer_v1},
+ {megaco_ber_media_gateway_control_v2,
+ megaco_binary_transformer_v2},
+ {megaco_ber_media_gateway_control_v3,
+ megaco_binary_transformer_v3}],
+ ?BIN_LIB:decode_message_dynamic(EC, Binary, Decoders, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message(EC, dynamic, Binary) ->
Decoders = [{megaco_ber_media_gateway_control_v1,
megaco_binary_transformer_v1},
@@ -321,10 +391,12 @@ decode_message(EC, dynamic, Binary) ->
%% -- Version 1 --
+%% <BACKWARD-COMPAT-CLAUSE>
decode_message([{version3,_}|EC], 1, Binary) ->
AsnMod = megaco_ber_media_gateway_control_v1,
TransMod = megaco_binary_transformer_v1,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
decode_message(EC, 1, Binary) ->
AsnMod = megaco_ber_media_gateway_control_v1,
@@ -334,10 +406,12 @@ decode_message(EC, 1, Binary) ->
%% -- Version 2 --
+%% <BACKWARD-COMPAT-CLAUSE>
decode_message([{version3,_}|EC], 2, Binary) ->
AsnMod = megaco_ber_media_gateway_control_v2,
TransMod = megaco_binary_transformer_v2,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
decode_message(EC, 2, Binary) ->
AsnMod = megaco_ber_media_gateway_control_v2,
@@ -347,10 +421,7 @@ decode_message(EC, 2, Binary) ->
%% -- Version 3 --
-decode_message([{version3,v3}|EC], 3, Binary) ->
- AsnMod = megaco_ber_media_gateway_control_v3,
- TransMod = megaco_binary_transformer_v3,
- ?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% <DEPRECATED>
decode_message([{version3,prev3c}|EC], 3, Binary) ->
AsnMod = megaco_ber_media_gateway_control_prev3c,
TransMod = megaco_binary_transformer_prev3c,
@@ -363,6 +434,14 @@ decode_message([{version3,prev3a}|EC], 3, Binary) ->
AsnMod = megaco_ber_media_gateway_control_prev3a,
TransMod = megaco_binary_transformer_prev3a,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_message([{version3,v3}|EC], 3, Binary) ->
+ AsnMod = megaco_ber_media_gateway_control_v3,
+ TransMod = megaco_binary_transformer_v3,
+ ?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
decode_message(EC, 3, Binary) ->
AsnMod = megaco_ber_media_gateway_control_v3,
@@ -370,11 +449,7 @@ decode_message(EC, 3, Binary) ->
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, binary).
-decode_mini_message([{version3,v3}|EC], dynamic, Bin) ->
- Mods = [megaco_ber_media_gateway_control_v1,
- megaco_ber_media_gateway_control_v2,
- megaco_ber_media_gateway_control_v3],
- ?BIN_LIB:decode_mini_message_dynamic(EC, Bin, Mods, binary);
+%% <DEPRECATED>
decode_mini_message([{version3,prev3c}|EC], dynamic, Bin) ->
Mods = [megaco_ber_media_gateway_control_v1,
megaco_ber_media_gateway_control_v2,
@@ -390,29 +465,43 @@ decode_mini_message([{version3,prev3a}|EC], dynamic, Bin) ->
megaco_ber_media_gateway_control_v2,
megaco_ber_media_gateway_control_prev3a],
?BIN_LIB:decode_mini_message_dynamic(EC, Bin, Mods, binary);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_mini_message([{version3,v3}|EC], dynamic, Bin) ->
+ Mods = [megaco_ber_media_gateway_control_v1,
+ megaco_ber_media_gateway_control_v2,
+ megaco_ber_media_gateway_control_v3],
+ ?BIN_LIB:decode_mini_message_dynamic(EC, Bin, Mods, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_mini_message(EC, dynamic, Bin) ->
Mods = [megaco_ber_media_gateway_control_v1,
megaco_ber_media_gateway_control_v2,
megaco_ber_media_gateway_control_v3],
?BIN_LIB:decode_mini_message_dynamic(EC, Bin, Mods, binary);
+%% <BACKWARD-COMPAT-CLAUSE>
decode_mini_message([{version3,_}|EC], 1, Bin) ->
AsnMod = megaco_ber_media_gateway_control_v1,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_mini_message(EC, 1, Bin) ->
AsnMod = megaco_ber_media_gateway_control_v1,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% <BACKWARD-COMPAT-CLAUSE>
decode_mini_message([{version3,_}|EC], 2, Bin) ->
AsnMod = megaco_ber_media_gateway_control_v2,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_mini_message(EC, 2, Bin) ->
AsnMod = megaco_ber_media_gateway_control_v2,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
-decode_mini_message([{version3,v3}|EC], 3, Bin) ->
- AsnMod = megaco_ber_media_gateway_control_v3,
- ?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% <DEPRECATED>
decode_mini_message([{version3,prev3c}|EC], 3, Bin) ->
AsnMod = megaco_ber_media_gateway_control_prev3c,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
@@ -422,6 +511,14 @@ decode_mini_message([{version3,prev3b}|EC], 3, Bin) ->
decode_mini_message([{version3,prev3a}|EC], 3, Bin) ->
AsnMod = megaco_ber_media_gateway_control_prev3a,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_mini_message([{version3,v3}|EC], 3, Bin) ->
+ AsnMod = megaco_ber_media_gateway_control_v3,
+ ?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_mini_message(EC, 3, Bin) ->
AsnMod = megaco_ber_media_gateway_control_v3,
?BIN_LIB:decode_mini_message(EC, Bin, AsnMod, binary).
diff --git a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3a.erl b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3a.erl
index f9e3fe39e3..d8440c6bc8 100644
--- a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3a.erl
+++ b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3a.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: Handle meta data about packages
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_binary_name_resolver_prev3a).
diff --git a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3b.erl b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3b.erl
index 141225f501..bb04eee3c2 100644
--- a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3b.erl
+++ b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3b.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: Handle meta data about packages
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_binary_name_resolver_prev3b).
diff --git a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3c.erl b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3c.erl
index b27a0be26e..5336f9a8ad 100644
--- a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3c.erl
+++ b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3c.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: Handle meta data about packages
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_binary_name_resolver_prev3c).
diff --git a/lib/megaco/src/binary/megaco_binary_transformer_prev3a.erl b/lib/megaco/src/binary/megaco_binary_transformer_prev3a.erl
index 81cf0065dd..be716188d3 100644
--- a/lib/megaco/src/binary/megaco_binary_transformer_prev3a.erl
+++ b/lib/megaco/src/binary/megaco_binary_transformer_prev3a.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: Transform internal form of Megaco/H.248 messages
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_binary_transformer_prev3a).
@@ -32,6 +37,7 @@
-export([tr_message/3, tr_transaction/3]).
+
-define(DEFAULT_NAME_RESOLVER, megaco_binary_name_resolver_prev3a).
-record(state, {mode, % verify | encode | decode
diff --git a/lib/megaco/src/binary/megaco_binary_transformer_prev3b.erl b/lib/megaco/src/binary/megaco_binary_transformer_prev3b.erl
index a6dab5d780..8b1cf91159 100644
--- a/lib/megaco/src/binary/megaco_binary_transformer_prev3b.erl
+++ b/lib/megaco/src/binary/megaco_binary_transformer_prev3b.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: Transform internal form of Megaco/H.248 messages
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_binary_transformer_prev3b).
@@ -32,6 +37,7 @@
-export([tr_message/3, tr_transaction/3]).
+
-define(DEFAULT_NAME_RESOLVER, megaco_binary_name_resolver_prev3b).
-record(state, {mode, % verify | encode | decode
diff --git a/lib/megaco/src/binary/megaco_binary_transformer_prev3c.erl b/lib/megaco/src/binary/megaco_binary_transformer_prev3c.erl
index 63714be31d..da6ea877ba 100644
--- a/lib/megaco/src/binary/megaco_binary_transformer_prev3c.erl
+++ b/lib/megaco/src/binary/megaco_binary_transformer_prev3c.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: Transform internal form of Megaco/H.248 messages
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_binary_transformer_prev3c).
@@ -32,6 +37,7 @@
-export([tr_message/3, tr_transaction/3]).
+
-define(DEFAULT_NAME_RESOLVER, megaco_binary_name_resolver_prev3c).
-record(state, {mode, % verify | encode | decode
diff --git a/lib/megaco/src/binary/megaco_per_encoder.erl b/lib/megaco/src/binary/megaco_per_encoder.erl
index 3de33b1d60..187e37d21b 100644
--- a/lib/megaco/src/binary/megaco_per_encoder.erl
+++ b/lib/megaco/src/binary/megaco_per_encoder.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -64,9 +64,7 @@
%% Return {ok, Version} | {error, Reason}
%%----------------------------------------------------------------------
-version_of([{version3,v3}|EC], Binary) ->
- Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?V3_ASN1_MOD],
- ?BIN_LIB:version_of(EC, Binary, 1, Decoders);
+%% <DEPRECATED>
version_of([{version3,prev3c}|EC], Binary) ->
Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?PREV3C_ASN1_MOD],
?BIN_LIB:version_of(EC, Binary, 1, Decoders);
@@ -76,6 +74,14 @@ version_of([{version3,prev3b}|EC], Binary) ->
version_of([{version3,prev3a}|EC], Binary) ->
Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?PREV3A_ASN1_MOD],
?BIN_LIB:version_of(EC, Binary, 1, Decoders);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+version_of([{version3,v3}|EC], Binary) ->
+ Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?V3_ASN1_MOD],
+ ?BIN_LIB:version_of(EC, Binary, 1, Decoders);
+%% </BACKWARD-COMPAT-CLAUSE>
+
version_of(EC, Binary) ->
Decoders = [?V1_ASN1_MOD, ?V2_ASN1_MOD, ?V3_ASN1_MOD],
?BIN_LIB:version_of(EC, Binary, 1, Decoders).
@@ -93,10 +99,13 @@ encode_message(EC,
%% -- Version 1 --
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 1, MegaMsg) ->
AsnMod = ?V1_ASN1_MOD,
TransMod = ?V1_TRANS_MOD,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 1, MegaMsg) ->
AsnMod = ?V1_ASN1_MOD,
TransMod = ?V1_TRANS_MOD,
@@ -105,10 +114,13 @@ encode_message(EC, 1, MegaMsg) ->
%% -- Version 2 --
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 2, MegaMsg) ->
AsnMod = ?V2_ASN1_MOD,
TransMod = ?V2_TRANS_MOD,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 2, MegaMsg) ->
AsnMod = ?V2_ASN1_MOD,
TransMod = ?V2_TRANS_MOD,
@@ -117,10 +129,7 @@ encode_message(EC, 2, MegaMsg) ->
%% -- Version 3 --
-encode_message([{version3,v3}|EC], 3, MegaMsg) ->
- AsnMod = ?V3_ASN1_MOD,
- TransMod = ?V3_TRANS_MOD,
- ?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% <DEPRECATED>
encode_message([{version3,prev3c}|EC], 3, MegaMsg) ->
AsnMod = ?PREV3C_ASN1_MOD,
TransMod = ?PREV3C_TRANS_MOD,
@@ -133,6 +142,15 @@ encode_message([{version3,prev3a}|EC], 3, MegaMsg) ->
AsnMod = ?PREV3A_ASN1_MOD,
TransMod = ?PREV3A_TRANS_MOD,
?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_message([{version3,v3}|EC], 3, MegaMsg) ->
+ AsnMod = ?V3_ASN1_MOD,
+ TransMod = ?V3_TRANS_MOD,
+ ?BIN_LIB:encode_message(EC, MegaMsg, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 3, MegaMsg) ->
AsnMod = ?V3_ASN1_MOD,
TransMod = ?V3_TRANS_MOD,
@@ -245,10 +263,13 @@ decode_message(EC, dynamic, Binary) ->
%% -- Version 1 --
+%% <BACKWARD-COMPAT-CLAUSE>
decode_message([{version3,_}|EC], 1, Binary) ->
AsnMod = ?V1_ASN1_MOD,
TransMod = ?V1_TRANS_MOD,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message(EC, 1, Binary) ->
AsnMod = ?V1_ASN1_MOD,
TransMod = ?V1_TRANS_MOD,
@@ -256,10 +277,13 @@ decode_message(EC, 1, Binary) ->
%% -- Version 2 --
+%% <BACKWARD-COMPAT-CLAUSE>
decode_message([{version3,_}|EC], 2, Binary) ->
AsnMod = ?V2_ASN1_MOD,
TransMod = ?V2_TRANS_MOD,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message(EC, 2, Binary) ->
AsnMod = ?V2_ASN1_MOD,
TransMod = ?V2_TRANS_MOD,
@@ -267,10 +291,7 @@ decode_message(EC, 2, Binary) ->
%% -- Version 3 --
-decode_message([{version3,v3}|EC], 3, Binary) ->
- AsnMod = ?V3_ASN1_MOD,
- TransMod = ?V3_TRANS_MOD,
- ?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, io_list);
+%% <DEPRECATED>
decode_message([{version3,prev3c}|EC], 3, Binary) ->
AsnMod = ?PREV3C_ASN1_MOD,
TransMod = ?PREV3C_TRANS_MOD,
@@ -283,6 +304,15 @@ decode_message([{version3,prev3a}|EC], 3, Binary) ->
AsnMod = ?PREV3A_ASN1_MOD,
TransMod = ?PREV3A_TRANS_MOD,
?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, io_list);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_message([{version3,v3}|EC], 3, Binary) ->
+ AsnMod = ?V3_ASN1_MOD,
+ TransMod = ?V3_TRANS_MOD,
+ ?BIN_LIB:decode_message(EC, Binary, AsnMod, TransMod, io_list);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message(EC, 3, Binary) ->
AsnMod = ?V3_ASN1_MOD,
TransMod = ?V3_TRANS_MOD,
diff --git a/lib/megaco/src/binary/modules.mk b/lib/megaco/src/binary/modules.mk
index bf5db0e243..485f0fe610 100644
--- a/lib/megaco/src/binary/modules.mk
+++ b/lib/megaco/src/binary/modules.mk
@@ -2,7 +2,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2001-2016. All Rights Reserved.
+# Copyright Ericsson AB 2001-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,62 +18,71 @@
#
# %CopyrightEnd%
+# <DEPRECATED>
+PREV3_MODULES = \
+ megaco_ber_media_gateway_control_prev3a \
+ megaco_ber_media_gateway_control_prev3b \
+ megaco_ber_media_gateway_control_prev3c \
+ megaco_per_media_gateway_control_prev3a \
+ megaco_per_media_gateway_control_prev3b \
+ megaco_per_media_gateway_control_prev3c \
+ megaco_binary_name_resolver_prev3a \
+ megaco_binary_name_resolver_prev3b \
+ megaco_binary_name_resolver_prev3c \
+ megaco_binary_transformer_prev3a \
+ megaco_binary_transformer_prev3b \
+ megaco_binary_transformer_prev3c
+# </DEPRECATED>
+
MODULES = \
megaco_binary_encoder \
megaco_binary_encoder_lib \
megaco_ber_encoder \
megaco_ber_media_gateway_control_v1 \
megaco_ber_media_gateway_control_v2 \
- megaco_ber_media_gateway_control_prev3a \
- megaco_ber_media_gateway_control_prev3b \
- megaco_ber_media_gateway_control_prev3c \
megaco_ber_media_gateway_control_v3 \
megaco_per_encoder \
megaco_per_media_gateway_control_v1 \
megaco_per_media_gateway_control_v2 \
- megaco_per_media_gateway_control_prev3a \
- megaco_per_media_gateway_control_prev3b \
- megaco_per_media_gateway_control_prev3c \
megaco_per_media_gateway_control_v3 \
megaco_binary_name_resolver_v1 \
megaco_binary_name_resolver_v2 \
- megaco_binary_name_resolver_prev3a \
- megaco_binary_name_resolver_prev3b \
- megaco_binary_name_resolver_prev3c \
megaco_binary_name_resolver_v3 \
megaco_binary_term_id \
megaco_binary_term_id_gen \
megaco_binary_transformer_v1 \
megaco_binary_transformer_v2 \
- megaco_binary_transformer_prev3a \
- megaco_binary_transformer_prev3b \
- megaco_binary_transformer_prev3c \
- megaco_binary_transformer_v3
+ megaco_binary_transformer_v3 \
+ $(PREV3_MODULES)
INTERNAL_HRL_FILES =
ASN1_V1_SPEC = MEDIA-GATEWAY-CONTROL-v1
ASN1_V2_SPEC = MEDIA-GATEWAY-CONTROL-v2
+ASN1_V3_SPEC = MEDIA-GATEWAY-CONTROL-v3
+# <DEPRECATED>
ASN1_PREV3A_SPEC = MEDIA-GATEWAY-CONTROL-prev3a
ASN1_PREV3B_SPEC = MEDIA-GATEWAY-CONTROL-prev3b
ASN1_PREV3C_SPEC = MEDIA-GATEWAY-CONTROL-prev3c
-ASN1_V3_SPEC = MEDIA-GATEWAY-CONTROL-v3
+# </DEPRECATED>
-BER_ASN1_V1_SPEC = megaco_ber_media_gateway_control_v1
-PER_ASN1_V1_SPEC = megaco_per_media_gateway_control_v1
+BER_ASN1_V1_SPEC = megaco_ber_media_gateway_control_v1
+PER_ASN1_V1_SPEC = megaco_per_media_gateway_control_v1
-BER_ASN1_V2_SPEC = megaco_ber_media_gateway_control_v2
-PER_ASN1_V2_SPEC = megaco_per_media_gateway_control_v2
+BER_ASN1_V2_SPEC = megaco_ber_media_gateway_control_v2
+PER_ASN1_V2_SPEC = megaco_per_media_gateway_control_v2
-BER_ASN1_PREV3A_SPEC = megaco_ber_media_gateway_control_prev3a
-PER_ASN1_PREV3A_SPEC = megaco_per_media_gateway_control_prev3a
+BER_ASN1_V3_SPEC = megaco_ber_media_gateway_control_v3
+PER_ASN1_V3_SPEC = megaco_per_media_gateway_control_v3
-BER_ASN1_PREV3B_SPEC = megaco_ber_media_gateway_control_prev3b
-PER_ASN1_PREV3B_SPEC = megaco_per_media_gateway_control_prev3b
+# <DEPRECATED>
+BER_ASN1_PREV3A_SPEC = megaco_ber_media_gateway_control_prev3a
+PER_ASN1_PREV3A_SPEC = megaco_per_media_gateway_control_prev3a
-BER_ASN1_PREV3C_SPEC = megaco_ber_media_gateway_control_prev3c
-PER_ASN1_PREV3C_SPEC = megaco_per_media_gateway_control_prev3c
+BER_ASN1_PREV3B_SPEC = megaco_ber_media_gateway_control_prev3b
+PER_ASN1_PREV3B_SPEC = megaco_per_media_gateway_control_prev3b
-BER_ASN1_V3_SPEC = megaco_ber_media_gateway_control_v3
-PER_ASN1_V3_SPEC = megaco_per_media_gateway_control_v3
+BER_ASN1_PREV3C_SPEC = megaco_ber_media_gateway_control_prev3c
+PER_ASN1_PREV3C_SPEC = megaco_per_media_gateway_control_prev3c
+# </DEPRECATED>
diff --git a/lib/megaco/src/engine/megaco_config.erl b/lib/megaco/src/engine/megaco_config.erl
index 1ceccf21d1..27d7673d74 100644
--- a/lib/megaco/src/engine/megaco_config.erl
+++ b/lib/megaco/src/engine/megaco_config.erl
@@ -1539,7 +1539,16 @@ verify_val(Item, Val) ->
send_mod when is_atom(Val) -> true;
send_handle -> true;
encoding_mod when is_atom(Val) -> true;
- encoding_config when is_list(Val) -> true;
+ encoding_config when is_list(Val) ->
+ case Val of
+ [{version3, V3}|_] when (V3 =/= v3) ->
+ warning_msg("Encoding Config version3 ~p is deprecated!~n"
+ "It will be removed in OTP 24. Use 'v3' instead!"),
+ ok;
+ _ ->
+ ok
+ end,
+ true;
protocol_version ->
megaco_config_misc:verify_strict_uint(Val);
auth_data -> true;
@@ -2165,6 +2174,8 @@ snmp_counters() ->
%%-----------------------------------------------------------------
+warning_msg(F) ->
+ warning_msg(F, []).
warning_msg(F, A) ->
?megaco_warning("Config server: " ++ F, A).
diff --git a/lib/megaco/src/engine/megaco_erl_dist_encoder.erl b/lib/megaco/src/engine/megaco_erl_dist_encoder.erl
index 64204cc056..5ecab5bc56 100644
--- a/lib/megaco/src/engine/megaco_erl_dist_encoder.erl
+++ b/lib/megaco/src/engine/megaco_erl_dist_encoder.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -56,8 +56,11 @@ encode_message(Config,
#'MegacoMessage'{mess = #'Message'{version = V}} = MegaMsg) ->
encode_message(Config, V, MegaMsg).
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3, _}|EC], Vsn, MegaMsg) ->
encode_message(EC, Vsn, MegaMsg);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message([megaco_compressed|Config], Vsn, MegaMsg)
when is_record(MegaMsg, 'MegacoMessage') ->
{ok, erlang:term_to_binary(?MC_MOD:encode(MegaMsg, Vsn), Config)};
@@ -76,8 +79,11 @@ encode_message(_Config, _Vsn, _MegaMsg) ->
%% Return {ok, Bin} | {error, Reason}
%%----------------------------------------------------------------------
+%% <BACKWARD-COMPAT-CLAUSE>
encode_transaction([{version3, _}|EC], Vsn, Trans) ->
encode_transaction(EC, Vsn, Trans);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction([megaco_compressed|Config], _Vsn, Trans) ->
{ok, erlang:term_to_binary(?MC_MOD:encode(Trans), Config)};
encode_transaction([{megaco_compressed, Mod}|Config], _Vsn, Trans) ->
@@ -90,8 +96,12 @@ encode_transaction(Config, _Vsn, Trans) ->
%% Convert a list of ActionRequest record's into a binary
%% Return {ok, Binary} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_requests([{version3, _}|EC], Vsn, ActReqs) ->
encode_action_requests(EC, Vsn, ActReqs);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests([megaco_compressed|Config], Vsn, ActReqs0)
when is_list(ActReqs0) ->
ActReqs = [?MC_MOD:encode(AR, Vsn) || AR <- ActReqs0],
@@ -110,8 +120,12 @@ encode_action_requests(Config, _Vsn, ActReqs)
%% Convert a ActionRequest record into a binary
%% Return {ok, Binary} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_request([{version3, _}|EC], Vsn, ActReq) ->
encode_action_request(EC, Vsn, ActReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request([megaco_compressed|Config], Vsn, ActReq)
when is_tuple(ActReq) ->
{ok, erlang:term_to_binary(?MC_MOD:encode(ActReq, Vsn), Config)};
@@ -128,8 +142,12 @@ encode_action_request(Config, _Vsn, ActReq)
%% Convert a CommandRequest record into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_command_request([{version3, _}|EC], Vsn, CmdReq) ->
encode_command_request(EC, Vsn, CmdReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_command_request([megaco_compressed|Config], Vsn, CmdReq)
when is_tuple(CmdReq) ->
{ok, erlang:term_to_binary(?MC_MOD:encode(CmdReq, Vsn), Config)};
@@ -145,8 +163,12 @@ encode_command_request(Config, _Vsn, CmdReq)
%% Convert a action reply into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_reply([{version3, _}|EC], Vsn, ActRep) ->
encode_action_reply(EC, Vsn, ActRep);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_reply([megaco_compressed|Config], Vsn, ActRep)
when is_tuple(ActRep) ->
{ok, erlang:term_to_binary(?MC_MOD:encode(ActRep, Vsn), Config)};
@@ -175,8 +197,11 @@ version_of(Config, Bin) when is_binary(Bin) ->
decode_message(Config, Bin) ->
decode_message(Config, 1, Bin).
+%% <BACKWARD-COMPAT-CLAUSE>
decode_message([{version3, _}|EC], V, Bin) ->
decode_message(EC, V, Bin);
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message([megaco_compressed = MC|_Config], Vsn, Bin) ->
case catch erlang:binary_to_term(Bin) of
Msg when is_tuple(Msg) ->
diff --git a/lib/megaco/src/text/Makefile b/lib/megaco/src/text/Makefile
index 8e7e82b276..3dd24b1df7 100644
--- a/lib/megaco/src/text/Makefile
+++ b/lib/megaco/src/text/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2000-2016. All Rights Reserved.
+# Copyright Ericsson AB 2000-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -113,16 +113,12 @@ info:
# Special Build Targets
# ----------------------------------------------------
-parser: parser_v1 parser_v2 parser_prev3a parser_prev3b
+parser: parser_v1 parser_v2
parser_v1: megaco_text_parser_v1.$(EMULATOR)
parser_v2: megaco_text_parser_v2.$(EMULATOR)
-parser_prev3a: megaco_text_parser_prev3a.$(EMULATOR)
-
-parser_prev3b: megaco_text_parser_prev3b.$(EMULATOR)
-
# ----------------------------------------------------
# Release Target
diff --git a/lib/megaco/src/text/depend.mk b/lib/megaco/src/text/depend.mk
index 5001250afd..39c68fc8a9 100644
--- a/lib/megaco/src/text/depend.mk
+++ b/lib/megaco/src/text/depend.mk
@@ -2,7 +2,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2001-2016. All Rights Reserved.
+# Copyright Ericsson AB 2001-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -27,6 +27,8 @@ megaco_text_parser_v2.erl: \
megaco_text_parser_v3.erl: \
megaco_text_parser_v3.yrl \
megaco_text_parser_v3.hrl
+
+# <DEPRECATED>
megaco_text_parser_prev3a.erl: \
megaco_text_parser_prev3a.yrl \
megaco_text_parser_prev3a.hrl
@@ -36,6 +38,7 @@ megaco_text_parser_prev3b.erl: \
megaco_text_parser_prev3c.erl: \
megaco_text_parser_prev3c.yrl \
megaco_text_parser_prev3c.hrl
+# </DEPRECATED>
megaco_text_mini_parser.erl: \
megaco_text_mini_parser.yrl \
@@ -68,6 +71,7 @@ $(EBIN)/megaco_compact_text_encoder_v3.$(EMULATOR): \
megaco_text_tokens.hrl \
megaco_text_gen_v3.hrl
+# <DEPRECATED>
$(EBIN)/megaco_compact_text_encoder_prev3a.$(EMULATOR): \
megaco_compact_text_encoder_prev3a.erl \
$(MEGACO_INCLUDEDIR)/megaco.hrl \
@@ -88,6 +92,7 @@ $(EBIN)/megaco_compact_text_encoder_prev3c.$(EMULATOR): \
$(MEGACO_INCLUDEDIR)/megaco_message_prev3c.hrl \
megaco_text_tokens.hrl \
megaco_text_gen_prev3c.hrl
+# </DEPRECATED>
$(EBIN)/megaco_pretty_text_encoder.$(EMULATOR): \
megaco_pretty_text_encoder.erl
@@ -113,6 +118,7 @@ $(EBIN)/megaco_pretty_text_encoder_v3.$(EMULATOR): \
megaco_text_tokens.hrl \
megaco_text_gen_v3.hrl
+# <DEPRECATED>
$(EBIN)/megaco_pretty_text_encoder_prev3a.$(EMULATOR): \
megaco_pretty_text_encoder_prev3a.erl \
$(MEGACO_INCLUDEDIR)/megaco.hrl \
@@ -133,6 +139,7 @@ $(EBIN)/megaco_pretty_text_encoder_prev3c.$(EMULATOR): \
$(MEGACO_INCLUDEDIR)/megaco_message_prev3c.hrl \
megaco_text_tokens.hrl \
megaco_text_gen_prev3c.hrl
+# </DEPRECATED>
$(EBIN)/megaco_text_parser_v1.$(EMULATOR): \
megaco_text_parser_v1.erl \
@@ -155,6 +162,7 @@ $(EBIN)/megaco_text_parser_v3.$(EMULATOR): \
megaco_text_tokens.hrl \
megaco_text_parser_v3.hrl
+# <DEPRECATED>
$(EBIN)/megaco_text_parser_prev3a.$(EMULATOR): \
megaco_text_parser_prev3a.erl \
$(MEGACO_INCLUDEDIR)/megaco.hrl \
@@ -175,6 +183,7 @@ $(EBIN)/megaco_text_parser_prev3c.$(EMULATOR): \
$(MEGACO_INCLUDEDIR)/megaco_message_prev3c.hrl \
megaco_text_tokens.hrl \
megaco_text_parser_prev3c.hrl
+# </DEPRECATED>
$(EBIN)/megaco_text_scanner.$(EMULATOR): megaco_text_scanner.erl \
$(MEGACO_INCLUDEDIR)/megaco.hrl \
diff --git a/lib/megaco/src/text/megaco_compact_text_encoder.erl b/lib/megaco/src/text/megaco_compact_text_encoder.erl
index 74638308da..ca1b9362fb 100644
--- a/lib/megaco/src/text/megaco_compact_text_encoder.erl
+++ b/lib/megaco/src/text/megaco_compact_text_encoder.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -63,22 +63,36 @@ encode_message(EncodingConfig,
#'MegacoMessage'{mess = #'Message'{version = V}} = MegaMsg) ->
encode_message(EncodingConfig, V, MegaMsg).
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 1, MegaMsg) ->
megaco_compact_text_encoder_v1:encode_message(EC, MegaMsg);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 1, MegaMsg) ->
megaco_compact_text_encoder_v1:encode_message(EC, MegaMsg);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 2, MegaMsg) ->
megaco_compact_text_encoder_v2:encode_message(EC, MegaMsg);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 2, MegaMsg) ->
megaco_compact_text_encoder_v2:encode_message(EC, MegaMsg);
-encode_message([{version3,v3}|EC], 3, MegaMsg) ->
- megaco_compact_text_encoder_v3:encode_message(EC, MegaMsg);
+
+%% <DEPRECATED>
encode_message([{version3,prev3c}|EC], 3, MegaMsg) ->
megaco_compact_text_encoder_prev3c:encode_message(EC, MegaMsg);
encode_message([{version3,prev3b}|EC], 3, MegaMsg) ->
megaco_compact_text_encoder_prev3b:encode_message(EC, MegaMsg);
encode_message([{version3,prev3a}|EC], 3, MegaMsg) ->
megaco_compact_text_encoder_prev3a:encode_message(EC, MegaMsg);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_message([{version3,v3}|EC], 3, MegaMsg) ->
+ megaco_compact_text_encoder_v3:encode_message(EC, MegaMsg);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 3, MegaMsg) ->
megaco_compact_text_encoder_v3:encode_message(EC, MegaMsg);
encode_message(_EC, V, _MegaMsg) ->
@@ -124,27 +138,8 @@ decode_message([], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{version3,v3}], _, Bin) when is_binary(Bin) ->
- %% d("decode_message(v3) -> entry"),
- case megaco_text_scanner:scan(Bin) of
- {ok, Tokens, 1, _LastLine} ->
- do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
-
- {ok, Tokens, 2, _LastLine} ->
- do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
-
- {ok, Tokens, 3, _LastLine} ->
- do_decode_message(?V3_PARSE_MOD, Tokens, Bin);
-
- {ok, _Tokens, V, _LastLine} ->
- {error, {unsupported_version, V}};
-
- {error, Reason, Tokens, Line} ->
- scan_error(Reason, Line, Tokens, Bin);
- {error, Reason, Line} -> %% OTP-4007
- scan_error(Reason, Line, Bin) %% OTP-4007
- end;
+%% <DEPRECATED>
decode_message([{version3,prev3c}], _, Bin) when is_binary(Bin) ->
%% d("decode_message(prev3c) -> entry"),
case megaco_text_scanner:scan(Bin) of
@@ -208,9 +203,12 @@ decode_message([{version3,prev3a}], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{flex, Port}], _, Bin) when is_binary(Bin) ->
- %% d("decode_message(flex) -> entry"),
- case megaco_flex_scanner:scan(Bin, Port) of
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_message([{version3,v3}], _, Bin) when is_binary(Bin) ->
+ %% d("decode_message(v3) -> entry"),
+ case megaco_text_scanner:scan(Bin) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -223,14 +221,16 @@ decode_message([{flex, Port}], _, Bin) when is_binary(Bin) ->
{ok, _Tokens, V, _LastLine} ->
{error, {unsupported_version, V}};
- %% {error, Reason, Tokens, Line} ->
- %% scan_error(Reason, Line, Tokens, Bin);
+ {error, Reason, Tokens, Line} ->
+ scan_error(Reason, Line, Tokens, Bin);
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{version3,v3},{flex, Port}], _, Bin) when is_binary(Bin) ->
- %% d("decode_message(v3,flex) -> entry"),
+%% </BACKWARD-COMPAT-CLAUSE>
+
+decode_message([{flex, Port}], _, Bin) when is_binary(Bin) ->
+ %% d("decode_message(flex) -> entry"),
case megaco_flex_scanner:scan(Bin, Port) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -250,6 +250,8 @@ decode_message([{version3,v3},{flex, Port}], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
+
+%% <DEPRECATED>
decode_message([{version3,prev3c},{flex, Port}], _, Bin) when is_binary(Bin) ->
%% d("decode_message(prev3c,flex) -> entry"),
case megaco_flex_scanner:scan(Bin, Port) of
@@ -313,6 +315,32 @@ decode_message([{version3,prev3a},{flex, Port}], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_message([{version3,v3},{flex, Port}], _, Bin) when is_binary(Bin) ->
+ %% d("decode_message(v3,flex) -> entry"),
+ case megaco_flex_scanner:scan(Bin, Port) of
+ {ok, Tokens, 1, _LastLine} ->
+ do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
+
+ {ok, Tokens, 2, _LastLine} ->
+ do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
+
+ {ok, Tokens, 3, _LastLine} ->
+ do_decode_message(?V3_PARSE_MOD, Tokens, Bin);
+
+ {ok, _Tokens, V, _LastLine} ->
+ {error, {unsupported_version, V}};
+
+ %% {error, Reason, Tokens, Line} ->
+ %% scan_error(Reason, Line, Tokens, Bin);
+
+ {error, Reason, Line} -> %% OTP-4007
+ scan_error(Reason, Line, Bin) %% OTP-4007
+ end;
+%% <BACKWARD-COMPAT-CLAUSE>
+
decode_message(EC, _, Bin) when is_binary(Bin) ->
{error, {bad_encoding_config, EC}};
decode_message(_EC, _, _BadBin) ->
@@ -387,22 +415,37 @@ l2i(L) when is_list(L) ->
%% Convert a transaction record into a deep io list
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_transaction([{version3,_}|EC], 1, Trans) ->
megaco_compact_text_encoder_v1:encode_transaction(EC, Trans);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction(EC, 1, Trans) ->
megaco_compact_text_encoder_v1:encode_transaction(EC, Trans);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_transaction([{version3,_}|EC], 2, Trans) ->
megaco_compact_text_encoder_v2:encode_transaction(EC, Trans);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction(EC, 2, Trans) ->
megaco_compact_text_encoder_v2:encode_transaction(EC, Trans);
+
+%% <DEPRECATED>
encode_transaction([{version3,prev3c}|EC], 3, Trans) ->
megaco_compact_text_encoder_prev3c:encode_transaction(EC, Trans);
encode_transaction([{version3,prev3b}|EC], 3, Trans) ->
megaco_compact_text_encoder_prev3b:encode_transaction(EC, Trans);
encode_transaction([{version3,prev3a}|EC], 3, Trans) ->
megaco_compact_text_encoder_prev3a:encode_transaction(EC, Trans);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_transaction([{version3,v3}|EC], 3, Trans) ->
megaco_compact_text_encoder_v3:encode_transaction(EC, Trans);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction(EC, 3, Trans) ->
megaco_compact_text_encoder_v3:encode_transaction(EC, Trans);
encode_transaction(_EC, V, _Trans) ->
@@ -413,16 +456,26 @@ encode_transaction(_EC, V, _Trans) ->
%% Convert a list of ActionRequest record's into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_requests([{version3,_}|EC], 1, ActReqs)
when is_list(ActReqs) ->
megaco_compact_text_encoder_v1:encode_action_requests(EC, ActReqs);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests(EC, 1, ActReqs) when is_list(ActReqs) ->
megaco_compact_text_encoder_v1:encode_action_requests(EC, ActReqs);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_requests([{version3,_}|EC], 2, ActReqs)
when is_list(ActReqs) ->
megaco_compact_text_encoder_v2:encode_action_requests(EC, ActReqs);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests(EC, 2, ActReqs) when is_list(ActReqs) ->
megaco_compact_text_encoder_v2:encode_action_requests(EC, ActReqs);
+
+%% <DEPRECATED>
encode_action_requests([{version3,prev3c}|EC], 3, ActReqs)
when is_list(ActReqs) ->
megaco_compact_text_encoder_prev3c:encode_action_requests(EC, ActReqs);
@@ -432,9 +485,14 @@ encode_action_requests([{version3,prev3b}|EC], 3, ActReqs)
encode_action_requests([{version3,prev3a}|EC], 3, ActReqs)
when is_list(ActReqs) ->
megaco_compact_text_encoder_prev3a:encode_action_requests(EC, ActReqs);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_requests([{version3,v3}|EC], 3, ActReqs)
when is_list(ActReqs) ->
megaco_compact_text_encoder_v3:encode_action_requests(EC, ActReqs);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests(EC, 3, ActReqs) when is_list(ActReqs) ->
megaco_compact_text_encoder_v3:encode_action_requests(EC, ActReqs);
encode_action_requests(_EC, V, _ActReqs) ->
@@ -445,22 +503,37 @@ encode_action_requests(_EC, V, _ActReqs) ->
%% Convert a ActionRequest record into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_request([{version3,_}|EC], 1, ActReq) ->
megaco_compact_text_encoder_v1:encode_action_request(EC, ActReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request(EC, 1, ActReq) ->
megaco_compact_text_encoder_v1:encode_action_request(EC, ActReq);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_request([{version3,_}|EC], 2, ActReq) ->
megaco_compact_text_encoder_v2:encode_action_request(EC, ActReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request(EC, 2, ActReq) ->
megaco_compact_text_encoder_v2:encode_action_request(EC, ActReq);
+
+%% <DEPRECATED>
encode_action_request([{version3,prev3c}|EC], 3, ActReq) ->
megaco_compact_text_encoder_prev3c:encode_action_request(EC, ActReq);
encode_action_request([{version3,prev3b}|EC], 3, ActReq) ->
megaco_compact_text_encoder_prev3b:encode_action_request(EC, ActReq);
encode_action_request([{version3,prev3a}|EC], 3, ActReq) ->
megaco_compact_text_encoder_prev3a:encode_action_request(EC, ActReq);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_request([{version3,v3}|EC], 3, ActReq) ->
megaco_compact_text_encoder_v3:encode_action_request(EC, ActReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request(EC, 3, ActReq) ->
megaco_compact_text_encoder_v3:encode_action_request(EC, ActReq);
encode_action_request(_EC, V, _ActReq) ->
@@ -471,22 +544,37 @@ encode_action_request(_EC, V, _ActReq) ->
%% Convert a CommandRequest record into a deep io list
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_command_request([{version3,_}|EC], 1, CmdReq) ->
megaco_compact_text_encoder_v1:encode_command_request(EC, CmdReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_command_request(EC, 1, CmdReq) ->
megaco_compact_text_encoder_v1:encode_command_request(EC, CmdReq);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_command_request([{version3,_}|EC], 2, CmdReq) ->
megaco_compact_text_encoder_v2:encode_command_request(EC, CmdReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_command_request(EC, 2, CmdReq) ->
megaco_compact_text_encoder_v2:encode_command_request(EC, CmdReq);
+
+%% <DEPRECATED>
encode_command_request([{version3,prev3c}|EC], 3, CmdReq) ->
megaco_compact_text_encoder_prev3c:encode_command_request(EC, CmdReq);
encode_command_request([{version3,prev3b}|EC], 3, CmdReq) ->
megaco_compact_text_encoder_prev3b:encode_command_request(EC, CmdReq);
encode_command_request([{version3,prev3a}|EC], 3, CmdReq) ->
megaco_compact_text_encoder_prev3a:encode_command_request(EC, CmdReq);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_command_request([{version3,v3}|EC], 3, CmdReq) ->
megaco_compact_text_encoder_v3:encode_command_request(EC, CmdReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_command_request(EC, 3, CmdReq) ->
megaco_compact_text_encoder_v3:encode_command_request(EC, CmdReq);
encode_command_request(_EC, V, _CmdReq) ->
@@ -497,22 +585,37 @@ encode_command_request(_EC, V, _CmdReq) ->
%% Convert a action reply into a deep io list
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_reply([{version3,_}|EC], 1, ActRep) ->
megaco_compact_text_encoder_v1:encode_action_reply(EC, ActRep);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_reply(EC, 1, ActRep) ->
megaco_compact_text_encoder_v1:encode_action_reply(EC, ActRep);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_reply([{version3,_}|EC], 2, ActRep) ->
megaco_compact_text_encoder_v2:encode_action_reply(EC, ActRep);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_reply(EC, 2, ActRep) ->
megaco_compact_text_encoder_v2:encode_action_reply(EC, ActRep);
+
+%% <DEPRECATED>
encode_action_reply([{version3,prev3c}|EC], 3, ActRep) ->
megaco_compact_text_encoder_prev3c:encode_action_reply(EC, ActRep);
encode_action_reply([{version3,prev3b}|EC], 3, ActRep) ->
megaco_compact_text_encoder_prev3b:encode_action_reply(EC, ActRep);
encode_action_reply([{version3,prev3a}|EC], 3, ActRep) ->
megaco_compact_text_encoder_prev3a:encode_action_reply(EC, ActRep);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_reply([{version3,v3}|EC], 3, ActRep) ->
megaco_compact_text_encoder_v3:encode_action_reply(EC, ActRep);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_reply(EC, 3, ActRep) ->
megaco_compact_text_encoder_v3:encode_action_reply(EC, ActRep);
encode_action_reply(_EC, V, _ActRep) ->
@@ -541,10 +644,14 @@ token_tag2string(Tag, 3) ->
token_tag2string(Tag, v3);
token_tag2string(Tag, v3) ->
megaco_compact_text_encoder_v3:token_tag2string(Tag);
+
+%% <DEPRECATED>
token_tag2string(Tag, prev3b) ->
megaco_compact_text_encoder_prev3b:token_tag2string(Tag);
token_tag2string(Tag, prev3c) ->
megaco_compact_text_encoder_prev3c:token_tag2string(Tag);
+%% </DEPRECATED>
+
token_tag2string(Tag, _Vsn) ->
token_tag2string(Tag, ?TT2S_BEST_VERSION).
diff --git a/lib/megaco/src/text/megaco_compact_text_encoder_prev3a.erl b/lib/megaco/src/text/megaco_compact_text_encoder_prev3a.erl
index c3a68d4ed7..50e2a65409 100644
--- a/lib/megaco/src/text/megaco_compact_text_encoder_prev3a.erl
+++ b/lib/megaco/src/text/megaco_compact_text_encoder_prev3a.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: Encode COMPACT Megaco/H.248 text messages from internal form
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_compact_text_encoder_prev3a).
diff --git a/lib/megaco/src/text/megaco_compact_text_encoder_prev3b.erl b/lib/megaco/src/text/megaco_compact_text_encoder_prev3b.erl
index f98d1ea9ed..d581d2fb16 100644
--- a/lib/megaco/src/text/megaco_compact_text_encoder_prev3b.erl
+++ b/lib/megaco/src/text/megaco_compact_text_encoder_prev3b.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: Encode COMPACT Megaco/H.248 text messages from internal form
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_compact_text_encoder_prev3b).
diff --git a/lib/megaco/src/text/megaco_compact_text_encoder_prev3c.erl b/lib/megaco/src/text/megaco_compact_text_encoder_prev3c.erl
index 14cf1adb9b..95e244a696 100644
--- a/lib/megaco/src/text/megaco_compact_text_encoder_prev3c.erl
+++ b/lib/megaco/src/text/megaco_compact_text_encoder_prev3c.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: Encode COMPACT Megaco/H.248 text messages from internal form
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_compact_text_encoder_prev3c).
diff --git a/lib/megaco/src/text/megaco_pretty_text_encoder.erl b/lib/megaco/src/text/megaco_pretty_text_encoder.erl
index 7c47876e8e..56e47a5f24 100644
--- a/lib/megaco/src/text/megaco_pretty_text_encoder.erl
+++ b/lib/megaco/src/text/megaco_pretty_text_encoder.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -74,22 +74,36 @@ encode_message(EncodingConfig,
encode_message(EncodingConfig, V, MegaMsg).
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 1, MegaMsg) ->
megaco_pretty_text_encoder_v1:encode_message(EC, MegaMsg);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 1, MegaMsg) ->
megaco_pretty_text_encoder_v1:encode_message(EC, MegaMsg);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,_}|EC], 2, MegaMsg) ->
megaco_pretty_text_encoder_v2:encode_message(EC, MegaMsg);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 2, MegaMsg) ->
megaco_pretty_text_encoder_v2:encode_message(EC, MegaMsg);
+
+%% <DEPRECATED>
encode_message([{version3,prev3c}|EC], 3, MegaMsg) ->
megaco_pretty_text_encoder_prev3c:encode_message(EC, MegaMsg);
encode_message([{version3,prev3b}|EC], 3, MegaMsg) ->
megaco_pretty_text_encoder_prev3b:encode_message(EC, MegaMsg);
encode_message([{version3,prev3a}|EC], 3, MegaMsg) ->
megaco_pretty_text_encoder_prev3a:encode_message(EC, MegaMsg);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3,v3}|EC], 3, MegaMsg) ->
megaco_pretty_text_encoder_v3:encode_message(EC, MegaMsg);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_message(EC, 3, MegaMsg) ->
megaco_pretty_text_encoder_v3:encode_message(EC, MegaMsg);
encode_message(_EC, V, _MegaMsg) ->
@@ -133,7 +147,9 @@ decode_message([], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{version3,v3}], _, Bin) when is_binary(Bin) ->
+
+%% <DEPRECATED>
+decode_message([{version3,prev3c}], _, Bin) when is_binary(Bin) ->
case megaco_text_scanner:scan(Bin) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -142,7 +158,7 @@ decode_message([{version3,v3}], _, Bin) when is_binary(Bin) ->
do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
{ok, Tokens, 3, _LastLine} ->
- do_decode_message(?V3_PARSE_MOD, Tokens, Bin);
+ do_decode_message(?PREV3C_PARSE_MOD, Tokens, Bin);
{ok, _Tokens, V, _LastLine} ->
{error, {unsupported_version, V}};
@@ -150,10 +166,10 @@ decode_message([{version3,v3}], _, Bin) when is_binary(Bin) ->
{error, Reason, Tokens, Line} ->
scan_error(Reason, Line, Tokens, Bin);
- {error, Reason, Line} -> %% OTP-4007
- scan_error(Reason, Line, Bin) %% OTP-4007
+ {error, Reason, Line} ->
+ scan_error(Reason, Line, Bin)
end;
-decode_message([{version3,prev3c}], _, Bin) when is_binary(Bin) ->
+decode_message([{version3,prev3b}], _, Bin) when is_binary(Bin) ->
case megaco_text_scanner:scan(Bin) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -162,7 +178,7 @@ decode_message([{version3,prev3c}], _, Bin) when is_binary(Bin) ->
do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
{ok, Tokens, 3, _LastLine} ->
- do_decode_message(?PREV3C_PARSE_MOD, Tokens, Bin);
+ do_decode_message(?PREV3B_PARSE_MOD, Tokens, Bin);
{ok, _Tokens, V, _LastLine} ->
{error, {unsupported_version, V}};
@@ -170,10 +186,10 @@ decode_message([{version3,prev3c}], _, Bin) when is_binary(Bin) ->
{error, Reason, Tokens, Line} ->
scan_error(Reason, Line, Tokens, Bin);
- {error, Reason, Line} ->
- scan_error(Reason, Line, Bin)
+ {error, Reason, Line} -> %% OTP-4007
+ scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{version3,prev3b}], _, Bin) when is_binary(Bin) ->
+decode_message([{version3,prev3a}], _, Bin) when is_binary(Bin) ->
case megaco_text_scanner:scan(Bin) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -182,7 +198,7 @@ decode_message([{version3,prev3b}], _, Bin) when is_binary(Bin) ->
do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
{ok, Tokens, 3, _LastLine} ->
- do_decode_message(?PREV3B_PARSE_MOD, Tokens, Bin);
+ do_decode_message(?PREV3A_PARSE_MOD, Tokens, Bin);
{ok, _Tokens, V, _LastLine} ->
{error, {unsupported_version, V}};
@@ -193,7 +209,10 @@ decode_message([{version3,prev3b}], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{version3,prev3a}], _, Bin) when is_binary(Bin) ->
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_message([{version3,v3}], _, Bin) when is_binary(Bin) ->
case megaco_text_scanner:scan(Bin) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -202,7 +221,7 @@ decode_message([{version3,prev3a}], _, Bin) when is_binary(Bin) ->
do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
{ok, Tokens, 3, _LastLine} ->
- do_decode_message(?PREV3A_PARSE_MOD, Tokens, Bin);
+ do_decode_message(?V3_PARSE_MOD, Tokens, Bin);
{ok, _Tokens, V, _LastLine} ->
{error, {unsupported_version, V}};
@@ -213,6 +232,8 @@ decode_message([{version3,prev3a}], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message([{flex, Port}], _, Bin) when is_binary(Bin) ->
case megaco_flex_scanner:scan(Bin, Port) of
{ok, Tokens, 1, _LastLine} ->
@@ -233,7 +254,9 @@ decode_message([{flex, Port}], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{version3,v3},{flex, Port}], _, Bin) when is_binary(Bin) ->
+
+%% <DEPRECATED>
+decode_message([{version3,prev3c},{flex, Port}], _, Bin) when is_binary(Bin) ->
case megaco_flex_scanner:scan(Bin, Port) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -242,7 +265,7 @@ decode_message([{version3,v3},{flex, Port}], _, Bin) when is_binary(Bin) ->
do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
{ok, Tokens, 3, _LastLine} ->
- do_decode_message(?V3_PARSE_MOD, Tokens, Bin);
+ do_decode_message(?PREV3C_PARSE_MOD, Tokens, Bin);
{ok, _Tokens, V, _LastLine} ->
{error, {unsupported_version, V}};
@@ -253,7 +276,7 @@ decode_message([{version3,v3},{flex, Port}], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{version3,prev3c},{flex, Port}], _, Bin) when is_binary(Bin) ->
+decode_message([{version3,prev3b},{flex, Port}], _, Bin) when is_binary(Bin) ->
case megaco_flex_scanner:scan(Bin, Port) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -262,7 +285,7 @@ decode_message([{version3,prev3c},{flex, Port}], _, Bin) when is_binary(Bin) ->
do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
{ok, Tokens, 3, _LastLine} ->
- do_decode_message(?PREV3C_PARSE_MOD, Tokens, Bin);
+ do_decode_message(?PREV3B_PARSE_MOD, Tokens, Bin);
{ok, _Tokens, V, _LastLine} ->
{error, {unsupported_version, V}};
@@ -273,7 +296,7 @@ decode_message([{version3,prev3c},{flex, Port}], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{version3,prev3b},{flex, Port}], _, Bin) when is_binary(Bin) ->
+decode_message([{version3,prev3a},{flex, Port}], _, Bin) when is_binary(Bin) ->
case megaco_flex_scanner:scan(Bin, Port) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -282,7 +305,7 @@ decode_message([{version3,prev3b},{flex, Port}], _, Bin) when is_binary(Bin) ->
do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
{ok, Tokens, 3, _LastLine} ->
- do_decode_message(?PREV3B_PARSE_MOD, Tokens, Bin);
+ do_decode_message(?PREV3A_PARSE_MOD, Tokens, Bin);
{ok, _Tokens, V, _LastLine} ->
{error, {unsupported_version, V}};
@@ -293,7 +316,10 @@ decode_message([{version3,prev3b},{flex, Port}], _, Bin) when is_binary(Bin) ->
{error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
-decode_message([{version3,prev3a},{flex, Port}], _, Bin) when is_binary(Bin) ->
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+decode_message([{version3,v3},{flex, Port}], _, Bin) when is_binary(Bin) ->
case megaco_flex_scanner:scan(Bin, Port) of
{ok, Tokens, 1, _LastLine} ->
do_decode_message(?V1_PARSE_MOD, Tokens, Bin);
@@ -302,7 +328,7 @@ decode_message([{version3,prev3a},{flex, Port}], _, Bin) when is_binary(Bin) ->
do_decode_message(?V2_PARSE_MOD, Tokens, Bin);
{ok, Tokens, 3, _LastLine} ->
- do_decode_message(?PREV3A_PARSE_MOD, Tokens, Bin);
+ do_decode_message(?V3_PARSE_MOD, Tokens, Bin);
{ok, _Tokens, V, _LastLine} ->
{error, {unsupported_version, V}};
@@ -310,9 +336,11 @@ decode_message([{version3,prev3a},{flex, Port}], _, Bin) when is_binary(Bin) ->
%% {error, Reason, Tokens, Line} ->
%% scan_error(Reason, Line, Tokens, Bin);
- {error, Reason, Line} -> %% OTP-4007
+ {error, Reason, Line} -> %% OTP-4007
scan_error(Reason, Line, Bin) %% OTP-4007
end;
+%% </BACKWARD-COMPAT-CLAUSE>
+
decode_message(EC, _, Bin) when is_binary(Bin) ->
{error, {bad_encoding_config, EC}};
decode_message(_EC, _, _BadBin) ->
@@ -386,22 +414,36 @@ l2i(L) when is_list(L) ->
encode_transaction(Trans) ->
encode_transaction([], 1, Trans).
+%% <BACKWARD-COMPAT-CLAUSE>
encode_transaction([{version3,_}|EC], 1, Trans) ->
megaco_pretty_text_encoder_v1:encode_transaction(EC, Trans);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction(EC, 1, Trans) ->
megaco_pretty_text_encoder_v1:encode_transaction(EC, Trans);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_transaction([{version3,_}|EC], 2, Trans) ->
megaco_pretty_text_encoder_v2:encode_transaction(EC, Trans);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction(EC, 2, Trans) ->
megaco_pretty_text_encoder_v2:encode_transaction(EC, Trans);
-encode_transaction([{version3,v3}|EC], 3, Trans) ->
- megaco_pretty_text_encoder_v3:encode_transaction(EC, Trans);
+
+%% <DEPRECATED>
encode_transaction([{version3,prev3c}|EC], 3, Trans) ->
megaco_pretty_text_encoder_prev3c:encode_transaction(EC, Trans);
encode_transaction([{version3,prev3b}|EC], 3, Trans) ->
megaco_pretty_text_encoder_prev3b:encode_transaction(EC, Trans);
encode_transaction([{version3,prev3a}|EC], 3, Trans) ->
megaco_pretty_text_encoder_prev3a:encode_transaction(EC, Trans);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_transaction([{version3,v3}|EC], 3, Trans) ->
+ megaco_pretty_text_encoder_v3:encode_transaction(EC, Trans);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_transaction(EC, 3, Trans) ->
megaco_pretty_text_encoder_v3:encode_transaction(EC, Trans);
encode_transaction(_EC, V, _Trans) ->
@@ -412,17 +454,24 @@ encode_transaction(_EC, V, _Trans) ->
%% Convert a list of ActionRequest record's into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_requests([{version3,_}|EC], 1, ActReqs) when is_list(ActReqs) ->
megaco_pretty_text_encoder_v1:encode_action_requests(EC, ActReqs);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests(EC, 1, ActReqs) when is_list(ActReqs) ->
megaco_pretty_text_encoder_v1:encode_action_requests(EC, ActReqs);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_requests([{version3,_}|EC], 2, ActReqs) when is_list(ActReqs) ->
megaco_pretty_text_encoder_v2:encode_action_requests(EC, ActReqs);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests(EC, 2, ActReqs) when is_list(ActReqs) ->
megaco_pretty_text_encoder_v2:encode_action_requests(EC, ActReqs);
-encode_action_requests([{version3,v3}|EC], 3, ActReqs)
- when is_list(ActReqs) ->
- megaco_pretty_text_encoder_v3:encode_action_requests(EC, ActReqs);
+
+%% <DEPRECATED>
encode_action_requests([{version3,prev3c}|EC], 3, ActReqs)
when is_list(ActReqs) ->
megaco_pretty_text_encoder_prev3c:encode_action_requests(EC, ActReqs);
@@ -432,6 +481,14 @@ encode_action_requests([{version3,prev3b}|EC], 3, ActReqs)
encode_action_requests([{version3,prev3a}|EC], 3, ActReqs)
when is_list(ActReqs) ->
megaco_pretty_text_encoder_prev3a:encode_action_requests(EC, ActReqs);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_action_requests([{version3,v3}|EC], 3, ActReqs)
+ when is_list(ActReqs) ->
+ megaco_pretty_text_encoder_v3:encode_action_requests(EC, ActReqs);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_requests(EC, 3, ActReqs) when is_list(ActReqs) ->
megaco_pretty_text_encoder_v3:encode_action_requests(EC, ActReqs);
encode_action_requests(_EC, V, _ActReqs) ->
@@ -442,22 +499,37 @@ encode_action_requests(_EC, V, _ActReqs) ->
%% Convert a ActionRequest record into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_request([{version3,_}|EC], 1, ActReq) ->
megaco_pretty_text_encoder_v1:encode_action_request(EC, ActReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request(EC, 1, ActReq) ->
megaco_pretty_text_encoder_v1:encode_action_request(EC, ActReq);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_request([{version3,_}|EC], 2, ActReq) ->
megaco_pretty_text_encoder_v2:encode_action_request(EC, ActReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request(EC, 2, ActReq) ->
megaco_pretty_text_encoder_v2:encode_action_request(EC, ActReq);
-encode_action_request([{version3,v3}|EC], 3, ActReq) ->
- megaco_pretty_text_encoder_v3:encode_action_request(EC, ActReq);
+
+%% <DEPRECATED>
encode_action_request([{version3,prev3c}|EC], 3, ActReq) ->
megaco_pretty_text_encoder_prev3c:encode_action_request(EC, ActReq);
encode_action_request([{version3,prev3b}|EC], 3, ActReq) ->
megaco_pretty_text_encoder_prev3b:encode_action_request(EC, ActReq);
encode_action_request([{version3,prev3a}|EC], 3, ActReq) ->
megaco_pretty_text_encoder_prev3a:encode_action_request(EC, ActReq);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_action_request([{version3,v3}|EC], 3, ActReq) ->
+ megaco_pretty_text_encoder_v3:encode_action_request(EC, ActReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_request(EC, 3, ActReq) ->
megaco_pretty_text_encoder_v3:encode_action_request(EC, ActReq);
encode_action_request(_EC, V, _ActReq) ->
@@ -471,22 +543,36 @@ encode_action_request(_EC, V, _ActReq) ->
encode_command_request(CmdReq) ->
encode_command_request([], 1, CmdReq).
+%% <BACKWARD-COMPAT-CLAUSE>
encode_command_request([{version3,_}|EC], 1, CmdReq) ->
megaco_pretty_text_encoder_v1:encode_command_request(EC, CmdReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_command_request(EC, 1, CmdReq) ->
megaco_pretty_text_encoder_v1:encode_command_request(EC, CmdReq);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_command_request([{version3,_}|EC], 2, CmdReq) ->
megaco_pretty_text_encoder_v2:encode_command_request(EC, CmdReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_command_request(EC, 2, CmdReq) ->
megaco_pretty_text_encoder_v2:encode_command_request(EC, CmdReq);
-encode_command_request([{version3,v3}|EC], 3, CmdReq) ->
- megaco_pretty_text_encoder_v3:encode_command_request(EC, CmdReq);
+
+%% <DEPRECATED>
encode_command_request([{version3,prev3c}|EC], 3, CmdReq) ->
megaco_pretty_text_encoder_prev3c:encode_command_request(EC, CmdReq);
encode_command_request([{version3,prev3b}|EC], 3, CmdReq) ->
megaco_pretty_text_encoder_prev3b:encode_command_request(EC, CmdReq);
encode_command_request([{version3,prev3a}|EC], 3, CmdReq) ->
megaco_pretty_text_encoder_prev3a:encode_command_request(EC, CmdReq);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_command_request([{version3,v3}|EC], 3, CmdReq) ->
+ megaco_pretty_text_encoder_v3:encode_command_request(EC, CmdReq);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_command_request(EC, 3, CmdReq) ->
megaco_pretty_text_encoder_v3:encode_command_request(EC, CmdReq);
encode_command_request(_EC, V, _CmdReq) ->
@@ -503,22 +589,36 @@ encode_action_reply(ActRep) ->
%% "~n", [?MODULE, ActRep]),
encode_action_reply([], 1, ActRep).
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_reply([{version3,_}|EC], 1, ActRep) ->
megaco_pretty_text_encoder_v1:encode_action_reply(EC, ActRep);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_reply(EC, 1, ActRep) ->
megaco_pretty_text_encoder_v1:encode_action_reply(EC, ActRep);
+
+%% <BACKWARD-COMPAT-CLAUSE>
encode_action_reply([{version3,_}|EC], 2, ActRep) ->
megaco_pretty_text_encoder_v2:encode_action_reply(EC, ActRep);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_reply(EC, 2, ActRep) ->
megaco_pretty_text_encoder_v2:encode_action_reply(EC, ActRep);
-encode_action_reply([{version3,v3}|EC], 3, ActRep) ->
- megaco_pretty_text_encoder_v3:encode_action_reply(EC, ActRep);
+
+%% <DEPRECATED>
encode_action_reply([{version3,prev3c}|EC], 3, ActRep) ->
megaco_pretty_text_encoder_prev3c:encode_action_reply(EC, ActRep);
encode_action_reply([{version3,prev3b}|EC], 3, ActRep) ->
megaco_pretty_text_encoder_prev3b:encode_action_reply(EC, ActRep);
encode_action_reply([{version3,prev3a}|EC], 3, ActRep) ->
megaco_pretty_text_encoder_prev3a:encode_action_reply(EC, ActRep);
+%% </DEPRECATED>
+
+%% <BACKWARD-COMPAT-CLAUSE>
+encode_action_reply([{version3,v3}|EC], 3, ActRep) ->
+ megaco_pretty_text_encoder_v3:encode_action_reply(EC, ActRep);
+%% </BACKWARD-COMPAT-CLAUSE>
+
encode_action_reply(EC, 3, ActRep) ->
megaco_pretty_text_encoder_v3:encode_action_reply(EC, ActRep);
encode_action_reply(_EC, V, _ActRep) ->
@@ -572,10 +672,14 @@ token_tag2string(Tag, 3) ->
token_tag2string(Tag, v3);
token_tag2string(Tag, v3) ->
megaco_pretty_text_encoder_v3:token_tag2string(Tag);
+
+%% <DEPRECATED>
token_tag2string(Tag, prev3b) ->
megaco_pretty_text_encoder_prev3b:token_tag2string(Tag);
token_tag2string(Tag, prev3c) ->
megaco_pretty_text_encoder_prev3c:token_tag2string(Tag);
+%% </DEPRECATED>
+
token_tag2string(Tag, _Vsn) ->
token_tag2string(Tag, ?TT2S_BEST_VERSION).
diff --git a/lib/megaco/src/text/megaco_pretty_text_encoder_prev3a.erl b/lib/megaco/src/text/megaco_pretty_text_encoder_prev3a.erl
index 78f916d928..dd9c43c7ea 100644
--- a/lib/megaco/src/text/megaco_pretty_text_encoder_prev3a.erl
+++ b/lib/megaco/src/text/megaco_pretty_text_encoder_prev3a.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,7 +20,12 @@
%%
%%----------------------------------------------------------------------
-%%% Purpose: Encode PRETTY Megaco/H.248 text messages from internal form
+%% Purpose: Encode PRETTY Megaco/H.248 text messages from internal form
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_pretty_text_encoder_prev3a).
diff --git a/lib/megaco/src/text/megaco_pretty_text_encoder_prev3b.erl b/lib/megaco/src/text/megaco_pretty_text_encoder_prev3b.erl
index 87e17dfe85..3df83b2160 100644
--- a/lib/megaco/src/text/megaco_pretty_text_encoder_prev3b.erl
+++ b/lib/megaco/src/text/megaco_pretty_text_encoder_prev3b.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,7 +20,12 @@
%%
%%----------------------------------------------------------------------
-%%% Purpose: Encode PRETTY Megaco/H.248 text messages from internal form
+%% Purpose: Encode PRETTY Megaco/H.248 text messages from internal form
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_pretty_text_encoder_prev3b).
diff --git a/lib/megaco/src/text/megaco_pretty_text_encoder_prev3c.erl b/lib/megaco/src/text/megaco_pretty_text_encoder_prev3c.erl
index 386567b959..c0228bef14 100644
--- a/lib/megaco/src/text/megaco_pretty_text_encoder_prev3c.erl
+++ b/lib/megaco/src/text/megaco_pretty_text_encoder_prev3c.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,7 +20,12 @@
%%
%%----------------------------------------------------------------------
-%%% Purpose: Encode PRETTY Megaco/H.248 text messages from internal form
+%% Purpose: Encode PRETTY Megaco/H.248 text messages from internal form
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
-module(megaco_pretty_text_encoder_prev3c).
diff --git a/lib/megaco/src/text/megaco_text_gen_v3.hrl b/lib/megaco/src/text/megaco_text_gen_v3.hrl
index e440ec6651..35500b521e 100644
--- a/lib/megaco/src/text/megaco_text_gen_v3.hrl
+++ b/lib/megaco/src/text/megaco_text_gen_v3.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1152,7 +1152,9 @@ enc_ammDescriptor({Tag, Desc}, State) ->
statisticsDescriptor -> enc_StatisticsDescriptor(Desc, State);
_ ->
error({invalid_ammDescriptor_tag, Tag})
- end.
+ end;
+enc_ammDescriptor(Tag, State) when is_atom(Tag) ->
+ enc_ammDescriptor({Tag, []}, State).
enc_AmmsReply(#'AmmsReply'{terminationID = TIDs,
terminationAudit = asn1_NOVALUE}, State) ->
@@ -3208,14 +3210,20 @@ enc_PackagesItem(Val, State)
enc_StatisticsDescriptor({'StatisticsDescriptor',Val}, State) ->
enc_StatisticsDescriptor(Val, State);
-enc_StatisticsDescriptor(List, State) when is_list(List) ->
+enc_StatisticsDescriptor(List, State)
+ when is_list(List) andalso (List =/= []) ->
[
?StatsToken,
?LBRKT_INDENT(State),
enc_list([{List, fun enc_StatisticsParameter/2}], ?INC_INDENT(State)),
?RBRKT_INDENT(State)
+ ];
+enc_StatisticsDescriptor([], _State) ->
+ [
+ ?StatsToken
].
+
enc_StatisticsParameter(Val, State)
when is_record(Val, 'StatisticsParameter') ->
PkgdName = ?META_ENC(statistics, Val#'StatisticsParameter'.statName),
diff --git a/lib/megaco/src/text/megaco_text_mini_parser.yrl b/lib/megaco/src/text/megaco_text_mini_parser.yrl
index af3050a05b..2a4041867e 100644
--- a/lib/megaco/src/text/megaco_text_mini_parser.yrl
+++ b/lib/megaco/src/text/megaco_text_mini_parser.yrl
@@ -77,7 +77,7 @@ Nonterminals
Terminals
- %% 'AddToken'
+ 'AddToken'
%% 'AndAUDITselectToken'
'AuditCapToken'
'AuditToken'
@@ -106,7 +106,7 @@ Terminals
%% 'EmergencyValueToken'
'ErrorToken'
%% 'EventBufferToken'
- %% 'EventsToken'
+ 'EventsToken'
%% 'ExternalToken'
'FailoverToken'
'ForcedToken'
@@ -273,7 +273,7 @@ pathName -> safeToken : ensure_pathName('$1') .
safeToken -> safeToken2 : make_safe_token('$1') .
-%% safeToken2 -> 'AddToken' : '$1' .
+safeToken2 -> 'AddToken' : '$1' .
safeToken2 -> 'AuditToken' : '$1' .
safeToken2 -> 'AuditCapToken' : '$1' .
safeToken2 -> 'AuditValueToken' : '$1' .
@@ -298,7 +298,7 @@ safeToken2 -> 'EmbedToken' : '$1' .
%% safeToken2 -> 'EmergencyOffToken' : '$1' .
safeToken2 -> 'ErrorToken' : '$1' .
%% safeToken2 -> 'EventBufferToken' : '$1' .
-%% safeToken2 -> 'EventsToken' : '$1' .
+safeToken2 -> 'EventsToken' : '$1' .
%% safeToken2 -> 'ExternalToken' : '$1' . % v3
safeToken2 -> 'FailoverToken' : '$1' .
safeToken2 -> 'ForcedToken' : '$1' .
diff --git a/lib/megaco/src/text/megaco_text_parser_prev3a.hrl b/lib/megaco/src/text/megaco_text_parser_prev3a.hrl
index d7e847993a..138b4cd728 100644
--- a/lib/megaco/src/text/megaco_text_parser_prev3a.hrl
+++ b/lib/megaco/src/text/megaco_text_parser_prev3a.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose : Define semantic text parser actions
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
@@ -29,6 +34,7 @@
-define(encoder_version_pre_prev3c,true).
-include("megaco_text_tokens.hrl").
+
-ifdef(megaco_parser_inline).
-compile({inline,[{make_safe_token,1}]}).
-endif.
diff --git a/lib/megaco/src/text/megaco_text_parser_prev3a.yrl b/lib/megaco/src/text/megaco_text_parser_prev3a.yrl
index 0a07fbec2b..9100800c3d 100644
--- a/lib/megaco/src/text/megaco_text_parser_prev3a.yrl
+++ b/lib/megaco/src/text/megaco_text_parser_prev3a.yrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: YECC grammar for text encoding of Megaco/H.248
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
diff --git a/lib/megaco/src/text/megaco_text_parser_prev3b.hrl b/lib/megaco/src/text/megaco_text_parser_prev3b.hrl
index 479f963c70..c38aaa1140 100644
--- a/lib/megaco/src/text/megaco_text_parser_prev3b.hrl
+++ b/lib/megaco/src/text/megaco_text_parser_prev3b.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose : Define semantic text parser actions
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
@@ -29,6 +34,7 @@
-define(encoder_version_pre_prev3c,true).
-include("megaco_text_tokens.hrl").
+
-ifdef(megaco_parser_inline).
-compile({inline,[{make_safe_token,1}]}).
-endif.
diff --git a/lib/megaco/src/text/megaco_text_parser_prev3b.yrl b/lib/megaco/src/text/megaco_text_parser_prev3b.yrl
index 133938d7f7..eed984cdc3 100644
--- a/lib/megaco/src/text/megaco_text_parser_prev3b.yrl
+++ b/lib/megaco/src/text/megaco_text_parser_prev3b.yrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: YECC grammar for text encoding of Megaco/H.248
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
diff --git a/lib/megaco/src/text/megaco_text_parser_prev3c.hrl b/lib/megaco/src/text/megaco_text_parser_prev3c.hrl
index 3ed9582308..2a215d739d 100644
--- a/lib/megaco/src/text/megaco_text_parser_prev3c.hrl
+++ b/lib/megaco/src/text/megaco_text_parser_prev3c.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose : Define semantic text parser actions
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
@@ -28,6 +33,7 @@
-include_lib("megaco/include/megaco_message_prev3c.hrl").
-include("megaco_text_tokens.hrl").
+
-ifdef(megaco_parser_inline).
-compile({inline,[{make_safe_token,1}]}).
-endif.
diff --git a/lib/megaco/src/text/megaco_text_parser_prev3c.yrl b/lib/megaco/src/text/megaco_text_parser_prev3c.yrl
index d78f4ff99c..42188923a0 100644
--- a/lib/megaco/src/text/megaco_text_parser_prev3c.yrl
+++ b/lib/megaco/src/text/megaco_text_parser_prev3c.yrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
%%
%%----------------------------------------------------------------------
%% Purpose: YECC grammar for text encoding of Megaco/H.248
+%%
+%% DEPRECATED
+%% DEPRECATED
+%% DEPRECATED
+%%
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
diff --git a/lib/megaco/src/text/megaco_text_parser_v2.yrl b/lib/megaco/src/text/megaco_text_parser_v2.yrl
index f8fe762ee1..627e6696b1 100644
--- a/lib/megaco/src/text/megaco_text_parser_v2.yrl
+++ b/lib/megaco/src/text/megaco_text_parser_v2.yrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@
%% This is ugly but...
%%----------------------------------------------------------------------
-Expect 135.
+Expect 137.
%%----------------------------------------------------------------------
@@ -1418,7 +1418,7 @@ safeToken2 -> 'EmergencyToken' : '$1' .
safeToken2 -> 'EmergencyOffToken' : '$1' .
safeToken2 -> 'ErrorToken' : '$1' .
%% v2-safeToken2 -> 'EventBufferToken' : '$1' .
-%% v2-safeToken2 -> 'EventsToken' : '$1' .
+safeToken2 -> 'EventsToken' : '$1' .
safeToken2 -> 'FailoverToken' : '$1' .
safeToken2 -> 'ForcedToken' : '$1' .
safeToken2 -> 'GracefulToken' : '$1' .
diff --git a/lib/megaco/src/text/megaco_text_parser_v3.hrl b/lib/megaco/src/text/megaco_text_parser_v3.hrl
index 7b8fff2080..9d0afaa11b 100644
--- a/lib/megaco/src/text/megaco_text_parser_v3.hrl
+++ b/lib/megaco/src/text/megaco_text_parser_v3.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1740,6 +1740,36 @@ do_merge_control_streamParms([{SubTag, SD} | T] = All, LCD) ->
do_merge_control_streamParms([], LCD) ->
LCD.
+
+%% This is to get around the two defs:
+%%
+%% statisticsDescriptor = StatsToken [LBRKT statisticsParameter *(COMMA
+%% statisticsParameter) RBRKT]
+%%
+%% and
+%%
+%% auditReturnParameter = (mediaDescriptor / modemDescriptor / muxDescriptor /
+%% eventsDescriptor / signalsDescriptor /
+%% digitMapDescriptor / observedEventsDescriptor /
+%% eventBufferDescriptor / statisticsDescriptor /
+%% packagesDescriptor / errorDescriptor /
+%% auditReturnItem)
+%% auditReturnItem = (MuxToken / ModemToken / MediaToken /
+%% DigitMapToken / StatsToken / ObservedEventsToken /
+%% PackagesToken)
+%%
+%% In both statisticsDescriptor and auditReturnItem the StatsToken
+%% can occure on its owm => conflict
+
+-ifdef(megaco_parser_inline).
+-compile({inline,[{ensure_arp_statisticsDescriptor,1}]}).
+-endif.
+ensure_arp_statisticsDescriptor([]) ->
+ {auditReturnItem, statsToken};
+ensure_arp_statisticsDescriptor(SPs) ->
+ {statisticsDescriptor, SPs}.
+
+
-ifdef(megaco_parser_inline).
-compile({inline,[{merge_terminationStateDescriptor,1}]}).
-endif.
diff --git a/lib/megaco/src/text/megaco_text_parser_v3.yrl b/lib/megaco/src/text/megaco_text_parser_v3.yrl
index 54739af82e..4954386e03 100644
--- a/lib/megaco/src/text/megaco_text_parser_v3.yrl
+++ b/lib/megaco/src/text/megaco_text_parser_v3.yrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -66,7 +66,7 @@
%% This is ugly but...
%%----------------------------------------------------------------------
-Expect 91.
+Expect 94.
%%----------------------------------------------------------------------
@@ -760,12 +760,13 @@ auditReturnParameter -> signalsDescriptor : {signalsDescriptor, '$1'} .
auditReturnParameter -> digitMapDescriptor : {digitMapDescriptor, '$1'} .
auditReturnParameter -> observedEventsDescriptor : {observedEventsDescriptor, '$1'} .
auditReturnParameter -> eventBufferDescriptor : {eventBufferDescriptor, '$1'} .
-auditReturnParameter -> statisticsDescriptor : {statisticsDescriptor, '$1'} .
+%% Conflict with 'empty' statisticsDescriptor and auditReturnItem (see below)
+auditReturnParameter -> statisticsDescriptor : ensure_arp_statisticsDescriptor('$1') .
auditReturnParameter -> packagesDescriptor : {packagesDescriptor, '$1'} .
auditReturnParameter -> errorDescriptor : {errorDescriptor, '$1'} .
auditReturnParameter -> auditReturnItem : {auditReturnItem, '$1'} .
-auditDescriptor -> 'AuditToken' 'LBRKT' auditDescriptorBody 'RBRKT' :
+auditDescriptor -> 'AuditToken' 'LBRKT' auditDescriptorBody 'RBRKT' :
merge_auditDescriptor('$3') .
auditDescriptorBody -> auditItem auditItemList : ['$1' | '$2'].
@@ -780,13 +781,16 @@ auditReturnItem -> 'MuxToken' : muxToken .
auditReturnItem -> 'ModemToken' : modemToken .
auditReturnItem -> 'MediaToken' : mediaToken .
auditReturnItem -> 'DigitMapToken' : digitMapToken .
-auditReturnItem -> 'StatsToken' : statsToken .
+%% Conflict with 'empty' statisticsDescriptor:
+%% auditReturnItem -> 'StatsToken' : statsToken .
auditReturnItem -> 'ObservedEventsToken' : observedEventsToken .
auditReturnItem -> 'PackagesToken' : packagesToken .
%% at-most-once, and DigitMapToken and PackagesToken are not allowed
%% in AuditCapabilities command
auditItem -> auditReturnItem : '$1' .
+%% Moved from ari above (conflict with 'empty' statisticsDescriptor)
+auditItem -> 'StatsToken' : statsToken.
auditItem -> 'SignalsToken' : signalsToken.
auditItem -> 'EventBufferToken' : eventBufferToken.
auditItem -> 'EventsToken' : eventsToken .
@@ -1095,11 +1099,13 @@ localParmList -> '$empty': [] .
terminationStateDescriptor -> 'TerminationStateToken'
'LBRKT' terminationStateParm
- terminationStateParms 'RBRKT'
- : merge_terminationStateDescriptor(['$3' | '$4']) .
+ terminationStateParms 'RBRKT' :
+ merge_terminationStateDescriptor(['$3' | '$4']) .
-terminationStateParms -> 'COMMA' terminationStateParm terminationStateParms : ['$2' | '$3'] .
-terminationStateParms -> '$empty' : [] .
+terminationStateParms -> 'COMMA' terminationStateParm terminationStateParms :
+ ['$2' | '$3'] .
+terminationStateParms -> '$empty' :
+ [] .
%% at-most-once per item except for propertyParm
localParm -> 'ReservedGroupToken' 'EQUAL' onOrOff : {group, '$3'} .
@@ -1504,6 +1510,7 @@ statisticsDescriptor -> 'StatsToken'
'LBRKT' statisticsParameter
statisticsParameters 'RBRKT'
: ['$3' | '$4'] .
+statisticsDescriptor -> 'StatsToken' : [] .
statisticsParameters -> 'COMMA' statisticsParameter statisticsParameters : ['$2' | '$3'] .
statisticsParameters -> '$empty' : [] .
@@ -1553,7 +1560,7 @@ value -> safeToken : ensure_value('$1').
safeToken -> safeToken2 : make_safe_token('$1') .
safeToken2 -> 'SafeChars' : '$1' .
-%% BMK BMK safeToken2 -> 'AddToken' : '$1' .
+safeToken2 -> 'AddToken' : '$1' .
safeToken2 -> 'AuditToken' : '$1' .
safeToken2 -> 'AuditCapToken' : '$1' .
safeToken2 -> 'AuditValueToken' : '$1' .
@@ -1579,7 +1586,7 @@ safeToken2 -> 'EmbedToken' : '$1' .
%% BMK BMK safeToken2 -> 'EmergencyOffToken' : '$1' .
safeToken2 -> 'ErrorToken' : '$1' .
%% v2-safeToken2 -> 'EventBufferToken' : '$1' .
-%% v2-safeToken2 -> 'EventsToken' : '$1' .
+safeToken2 -> 'EventsToken' : '$1' .
%% v3-safeToken2 -> 'ExternalToken' : '$1' . % v3
safeToken2 -> 'FailoverToken' : '$1' .
safeToken2 -> 'ForcedToken' : '$1' .
diff --git a/lib/megaco/src/text/modules.mk b/lib/megaco/src/text/modules.mk
index 46bf7d57ba..c0436755cc 100644
--- a/lib/megaco/src/text/modules.mk
+++ b/lib/megaco/src/text/modules.mk
@@ -2,7 +2,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2001-2016. All Rights Reserved.
+# Copyright Ericsson AB 2001-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,49 +18,62 @@
#
# %CopyrightEnd%
+# <DEPRECATED>
+PREV3_MODULES = \
+ megaco_compact_text_encoder_prev3a \
+ megaco_compact_text_encoder_prev3b \
+ megaco_compact_text_encoder_prev3c \
+ megaco_pretty_text_encoder_prev3a \
+ megaco_pretty_text_encoder_prev3b \
+ megaco_pretty_text_encoder_prev3c
+# </DEPRECATED>
+
MODULES = \
megaco_compact_text_encoder \
megaco_compact_text_encoder_v1 \
megaco_compact_text_encoder_v2 \
megaco_compact_text_encoder_v3 \
- megaco_compact_text_encoder_prev3a \
- megaco_compact_text_encoder_prev3b \
- megaco_compact_text_encoder_prev3c \
megaco_pretty_text_encoder \
megaco_pretty_text_encoder_v1 \
megaco_pretty_text_encoder_v2 \
megaco_pretty_text_encoder_v3 \
- megaco_pretty_text_encoder_prev3a \
- megaco_pretty_text_encoder_prev3b \
- megaco_pretty_text_encoder_prev3c \
megaco_text_mini_decoder \
- megaco_text_scanner
+ megaco_text_scanner \
+ $(PREV3_MODULES)
+# <DEPRECATED>
+PREV3_INTERNAL_HRL_FILES = \
+ megaco_text_gen_prev3a.hrl \
+ megaco_text_gen_prev3b.hrl \
+ megaco_text_gen_prev3c.hrl \
+ megaco_text_parser_prev3a.hrl \
+ megaco_text_parser_prev3b.hrl \
+ megaco_text_parser_prev3c.hrl
+# </DEPRECATED>
+
INTERNAL_HRL_FILES = \
megaco_text_gen_v1.hrl \
megaco_text_gen_v2.hrl \
megaco_text_gen_v3.hrl \
- megaco_text_gen_prev3a.hrl \
- megaco_text_gen_prev3b.hrl \
- megaco_text_gen_prev3c.hrl \
megaco_text_parser_v1.hrl \
megaco_text_parser_v2.hrl \
megaco_text_parser_v3.hrl \
- megaco_text_parser_prev3a.hrl \
- megaco_text_parser_prev3b.hrl \
- megaco_text_parser_prev3c.hrl \
megaco_text_mini_parser.hrl \
- megaco_text_tokens.hrl
+ megaco_text_tokens.hrl \
+ $(PREV3_INTERNAL_HRL_FILES)
+# <DEPRECATED>
+PREV3_INTERNAL_YRL_FILES = \
+ megaco_text_parser_prev3a.yrl \
+ megaco_text_parser_prev3b.yrl \
+ megaco_text_parser_prev3c.yrl
+# </DEPRECATED>
+
INTERNAL_YRL_FILES = \
megaco_text_parser_v1.yrl \
megaco_text_parser_v2.yrl \
megaco_text_parser_v3.yrl \
- megaco_text_parser_prev3a.yrl \
- megaco_text_parser_prev3b.yrl \
- megaco_text_parser_prev3c.yrl \
- megaco_text_mini_parser.yrl
-
-
+ megaco_text_mini_parser.yrl \
+ $(PREV3_INTERNAL_YRL_FILES)
diff --git a/lib/megaco/test/Makefile b/lib/megaco/test/Makefile
index 8a595b88af..2ef820718e 100644
--- a/lib/megaco/test/Makefile
+++ b/lib/megaco/test/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2019. All Rights Reserved.
+# Copyright Ericsson AB 1999-2020. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -360,31 +360,6 @@ generate_v2: make
-s megaco_codec_v2_SUITE generate_text_messages \
$(ESTOP)
-display_prev3a: make
- $(MERL) $(ARGS) -sname megaco_display_text_msgs_prev3a $(ERL_PATH) \
- -s megaco_codec_prev3a_SUITE display_text_messages \
- $(ESTOP)
-
-display_prev3b: make
- $(MERL) $(ARGS) -sname megaco_display_text_msgs_prev3b $(ERL_PATH) \
- -s megaco_codec_prev3b_SUITE display_text_messages \
- $(ESTOP)
-
-generate_prev3b: make
- $(MERL) $(ARGS) -sname megaco_generate_text_msgs_prev3b $(ERL_PATH) \
- -s megaco_codec_prev3b_SUITE generate_text_messages \
- $(ESTOP)
-
-display_prev3c: make
- $(MERL) $(ARGS) -sname megaco_display_text_msgs_prev3c $(ERL_PATH) \
- -s megaco_codec_prev3c_SUITE display_text_messages \
- $(ESTOP)
-
-generate_prev3c: make
- $(MERL) $(ARGS) -sname megaco_generate_text_msgs_prev3c $(ERL_PATH) \
- -s megaco_codec_prev3c_SUITE generate_text_messages \
- $(ESTOP)
-
display_v3: make
$(MERL) $(ARGS) -sname megaco_display_text_msgs_v3 $(ERL_PATH) \
-s megaco_codec_v3_SUITE display_text_messages \
diff --git a/lib/megaco/test/megaco_codec_mini_SUITE.erl b/lib/megaco/test/megaco_codec_mini_SUITE.erl
index 12113aae70..6c7f60db05 100644
--- a/lib/megaco/test/megaco_codec_mini_SUITE.erl
+++ b/lib/megaco/test/megaco_codec_mini_SUITE.erl
@@ -39,7 +39,32 @@
init_per_testcase/2, end_per_testcase/2,
otp7672_msg01/1,
- otp7672_msg02/1
+ otp7672_msg02/1,
+
+ otp16631_msg01/1,
+ otp16631_msg02/1,
+ otp16631_msg03/1,
+ otp16631_msg04/1,
+ otp16631_msg05/1,
+ otp16631_msg06/1,
+ otp16631_msg11/1,
+ otp16631_msg12/1,
+ otp16631_msg13/1,
+ otp16631_msg14/1,
+ otp16631_msg15/1,
+ otp16631_msg16/1,
+ otp16631_msg21/1,
+ otp16631_msg22/1,
+ otp16631_msg23/1,
+ otp16631_msg24/1,
+ otp16631_msg25/1,
+ otp16631_msg26/1,
+ otp16631_msg31/1,
+ otp16631_msg32/1,
+ otp16631_msg33/1,
+ otp16631_msg34/1,
+ otp16631_msg35/1,
+ otp16631_msg36/1
]).
@@ -62,15 +87,51 @@ all() ->
groups() ->
[
- {tickets, [], tickets_cases()}
+ {tickets, [], tickets_cases()},
+ {otp7672, [], otp7672_cases()},
+ {otp16631, [], otp16631_cases()}
].
tickets_cases() ->
[
+ {group, otp7672},
+ {group, otp16631}
+ ].
+
+otp7672_cases() ->
+ [
otp7672_msg01,
otp7672_msg02
].
+otp16631_cases() ->
+ [
+ otp16631_msg01,
+ otp16631_msg02,
+ otp16631_msg03,
+ otp16631_msg04,
+ otp16631_msg05,
+ otp16631_msg06,
+ otp16631_msg11,
+ otp16631_msg12,
+ otp16631_msg13,
+ otp16631_msg14,
+ otp16631_msg15,
+ otp16631_msg16,
+ otp16631_msg21,
+ otp16631_msg22,
+ otp16631_msg23,
+ otp16631_msg24,
+ otp16631_msg25,
+ otp16631_msg26,
+ otp16631_msg31,
+ otp16631_msg32,
+ otp16631_msg33,
+ otp16631_msg34,
+ otp16631_msg35,
+ otp16631_msg36
+ ].
+
%%
@@ -200,6 +261,342 @@ otp7672(Msg) ->
end.
+
+%% --------------------------------------------------------------
+%%
+
+otp16631_msg01(suite) ->
+ [];
+otp16631_msg01(Config) when is_list(Config) ->
+ d("otp16631_msg01 -> entry", []),
+ ok = otp16631( otp16631_msg01() ),
+ ok.
+
+otp16631_msg01() ->
+ otp16631_msg("a").
+
+
+%% --
+
+otp16631_msg02(suite) ->
+ [];
+otp16631_msg02(Config) when is_list(Config) ->
+ d("otp16631_msg02 -> entry", []),
+ ok = otp16631( otp16631_msg02() ),
+ ok.
+
+otp16631_msg02() ->
+ otp16631_msg("b").
+
+
+%% --
+
+otp16631_msg03(suite) ->
+ [];
+otp16631_msg03(Config) when is_list(Config) ->
+ d("otp16631_msg03 -> entry", []),
+ ok = otp16631( otp16631_msg03() ),
+ ok.
+
+otp16631_msg03() ->
+ otp16631_msg("c").
+
+
+%% --
+
+otp16631_msg04(suite) ->
+ [];
+otp16631_msg04(Config) when is_list(Config) ->
+ d("otp16631_msg04 -> entry", []),
+ ok = otp16631( otp16631_msg04() ),
+ ok.
+
+otp16631_msg04() ->
+ otp16631_msg("d").
+
+
+%% --
+
+otp16631_msg05(suite) ->
+ [];
+otp16631_msg05(Config) when is_list(Config) ->
+ d("otp16631_msg05 -> entry", []),
+ ok = otp16631( otp16631_msg05() ),
+ ok.
+
+otp16631_msg05() ->
+ otp16631_msg("e").
+
+
+%% --
+
+otp16631_msg06(suite) ->
+ [];
+otp16631_msg06(Config) when is_list(Config) ->
+ d("otp16631_msg06 -> entry", []),
+ ok = otp16631( otp16631_msg06() ),
+ ok.
+
+otp16631_msg06() ->
+ otp16631_msg("f").
+
+
+%% --
+
+otp16631_msg11(suite) ->
+ [];
+otp16631_msg11(Config) when is_list(Config) ->
+ d("otp16631_msg11 -> entry", []),
+ ok = otp16631( otp16631_msg11() ),
+ ok.
+
+otp16631_msg11() ->
+ otp16631_msg("000a").
+
+
+%% --
+
+otp16631_msg12(suite) ->
+ [];
+otp16631_msg12(Config) when is_list(Config) ->
+ d("otp16631_msg12 -> entry", []),
+ ok = otp16631( otp16631_msg12() ),
+ ok.
+
+otp16631_msg12() ->
+ otp16631_msg("000b").
+
+
+%% --
+
+otp16631_msg13(suite) ->
+ [];
+otp16631_msg13(Config) when is_list(Config) ->
+ d("otp16631_msg13 -> entry", []),
+ ok = otp16631( otp16631_msg13() ),
+ ok.
+
+otp16631_msg13() ->
+ otp16631_msg("000c").
+
+
+%% --
+
+otp16631_msg14(suite) ->
+ [];
+otp16631_msg14(Config) when is_list(Config) ->
+ d("otp16631_msg14 -> entry", []),
+ ok = otp16631( otp16631_msg14() ),
+ ok.
+
+otp16631_msg14() ->
+ otp16631_msg("000d").
+
+
+%% --
+
+otp16631_msg15(suite) ->
+ [];
+otp16631_msg15(Config) when is_list(Config) ->
+ d("otp16631_msg15 -> entry", []),
+ ok = otp16631( otp16631_msg15() ),
+ ok.
+
+otp16631_msg15() ->
+ otp16631_msg("000e").
+
+
+%% --
+
+otp16631_msg16(suite) ->
+ [];
+otp16631_msg16(Config) when is_list(Config) ->
+ d("otp16631_msg16 -> entry", []),
+ ok = otp16631( otp16631_msg16() ),
+ ok.
+
+otp16631_msg16() ->
+ otp16631_msg("000f").
+
+
+%% --
+
+otp16631_msg21(suite) ->
+ [];
+otp16631_msg21(Config) when is_list(Config) ->
+ d("otp16631_msg21 -> entry", []),
+ ok = otp16631( otp16631_msg21() ),
+ ok.
+
+otp16631_msg21() ->
+ otp16631_msg("0a12").
+
+
+%% --
+
+otp16631_msg22(suite) ->
+ [];
+otp16631_msg22(Config) when is_list(Config) ->
+ d("otp16631_msg22 -> entry", []),
+ ok = otp16631( otp16631_msg22() ),
+ ok.
+
+otp16631_msg22() ->
+ otp16631_msg("0b12").
+
+
+%% --
+
+otp16631_msg23(suite) ->
+ [];
+otp16631_msg23(Config) when is_list(Config) ->
+ d("otp16631_msg23 -> entry", []),
+ ok = otp16631( otp16631_msg23() ),
+ ok.
+
+otp16631_msg23() ->
+ otp16631_msg("0c12").
+
+
+%% --
+
+otp16631_msg24(suite) ->
+ [];
+otp16631_msg24(Config) when is_list(Config) ->
+ d("otp16631_msg24 -> entry", []),
+ ok = otp16631( otp16631_msg24() ),
+ ok.
+
+otp16631_msg24() ->
+ otp16631_msg("0d12").
+
+
+%% --
+
+otp16631_msg25(suite) ->
+ [];
+otp16631_msg25(Config) when is_list(Config) ->
+ d("otp16631_msg25 -> entry", []),
+ ok = otp16631( otp16631_msg25() ),
+ ok.
+
+otp16631_msg25() ->
+ otp16631_msg("0e12").
+
+
+%% --
+
+otp16631_msg26(suite) ->
+ [];
+otp16631_msg26(Config) when is_list(Config) ->
+ d("otp16631_msg26 -> entry", []),
+ ok = otp16631( otp16631_msg26() ),
+ ok.
+
+otp16631_msg26() ->
+ otp16631_msg("0f12").
+
+
+%% --
+
+otp16631_msg31(suite) ->
+ [];
+otp16631_msg31(Config) when is_list(Config) ->
+ d("otp16631_msg31 -> entry", []),
+ ok = otp16631( otp16631_msg31() ),
+ ok.
+
+otp16631_msg31() ->
+ otp16631_msg("a123").
+
+
+%% --
+
+otp16631_msg32(suite) ->
+ [];
+otp16631_msg32(Config) when is_list(Config) ->
+ d("otp16631_msg32 -> entry", []),
+ ok = otp16631( otp16631_msg32() ),
+ ok.
+
+otp16631_msg32() ->
+ otp16631_msg("b123").
+
+
+%% --
+
+otp16631_msg33(suite) ->
+ [];
+otp16631_msg33(Config) when is_list(Config) ->
+ d("otp16631_msg33 -> entry", []),
+ ok = otp16631( otp16631_msg33() ),
+ ok.
+
+otp16631_msg33() ->
+ otp16631_msg("c123").
+
+
+%% --
+
+otp16631_msg34(suite) ->
+ [];
+otp16631_msg34(Config) when is_list(Config) ->
+ d("otp16631_msg34 -> entry", []),
+ ok = otp16631( otp16631_msg34() ),
+ ok.
+
+otp16631_msg34() ->
+ otp16631_msg("d123").
+
+
+%% --
+
+otp16631_msg35(suite) ->
+ [];
+otp16631_msg35(Config) when is_list(Config) ->
+ d("otp16631_msg35 -> entry", []),
+ ok = otp16631( otp16631_msg35() ),
+ ok.
+
+otp16631_msg35() ->
+ otp16631_msg("e123").
+
+
+%% --
+
+otp16631_msg36(suite) ->
+ [];
+otp16631_msg36(Config) when is_list(Config) ->
+ d("otp16631_msg36 -> entry", []),
+ ok = otp16631( otp16631_msg36() ),
+ ok.
+
+otp16631_msg36() ->
+ otp16631_msg("f123").
+
+
+%% -----
+
+otp16631( Msg ) ->
+ Bin = erlang:list_to_binary(Msg),
+ try megaco_compact_text_encoder:decode_mini_message([], dynamic, Bin) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ catch
+ C:E:S ->
+ {error, {C, E, S}}
+ end.
+
+
+otp16631_msg(X) when is_list(X) ->
+ "!/1 [2409:8050:5005:1243:1011::" ++ X ++
+ "] T=2523{C=-{SC=ROOT{SV{MT=RS,RE=901,PF=ETSI_BGF/2,V=3}}}}".
+
+
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
p(F, A) ->
diff --git a/lib/megaco/test/megaco_codec_v2_SUITE.erl b/lib/megaco/test/megaco_codec_v2_SUITE.erl
index 8a381804c3..0a60f36d7c 100644
--- a/lib/megaco/test/megaco_codec_v2_SUITE.erl
+++ b/lib/megaco/test/megaco_codec_v2_SUITE.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
+%%
%% Copyright Ericsson AB 2003-2019. All Rights Reserved.
-%%
+%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
@@ -14,7 +14,7 @@
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -42,76 +42,76 @@
init_per_group/2, end_per_group/2,
init_per_testcase/2, end_per_testcase/2,
- pretty_test_msgs/1,
+ pretty_test_msgs/1,
+
+ compact_test_msgs/1,
- compact_test_msgs/1,
-
- flex_pretty_init/1,
- flex_pretty_finish/1,
+ flex_pretty_init/1,
+ flex_pretty_finish/1,
flex_pretty_test_msgs/1,
-
- flex_compact_init/1,
- flex_compact_finish/1,
+
+ flex_compact_init/1,
+ flex_compact_finish/1,
flex_compact_test_msgs/1,
- flex_compact_dm_timers1/1,
- flex_compact_dm_timers2/1,
- flex_compact_dm_timers3/1,
- flex_compact_dm_timers4/1,
- flex_compact_dm_timers5/1,
- flex_compact_dm_timers6/1,
- flex_compact_dm_timers7/1,
- flex_compact_dm_timers8/1,
-
+ flex_compact_dm_timers1/1,
+ flex_compact_dm_timers2/1,
+ flex_compact_dm_timers3/1,
+ flex_compact_dm_timers4/1,
+ flex_compact_dm_timers5/1,
+ flex_compact_dm_timers6/1,
+ flex_compact_dm_timers7/1,
+ flex_compact_dm_timers8/1,
+
bin_test_msgs/1,
- ber_test_msgs/1,
-
+ ber_test_msgs/1,
+
per_test_msgs/1,
-
+
erl_dist_m_test_msgs/1,
- compact_otp4011_msg1/1,
+ compact_otp4011_msg1/1,
compact_otp4011_msg2/1,
compact_otp4011_msg3/1,
- compact_otp4013_msg1/1,
- compact_otp4085_msg1/1,
- compact_otp4085_msg2/1,
- compact_otp4280_msg1/1,
- compact_otp4299_msg1/1,
- compact_otp4299_msg2/1,
- compact_otp4359_msg1/1,
- compact_otp4920_msg0/1,
- compact_otp4920_msg1/1,
- compact_otp4920_msg2/1,
- compact_otp4920_msg3/1,
- compact_otp4920_msg4/1,
- compact_otp4920_msg5/1,
- compact_otp4920_msg6/1,
- compact_otp4920_msg7/1,
- compact_otp4920_msg8/1,
- compact_otp4920_msg9/1,
- compact_otp4920_msg10/1,
- compact_otp4920_msg11/1,
- compact_otp4920_msg12/1,
- compact_otp4920_msg20/1,
- compact_otp4920_msg21/1,
- compact_otp4920_msg22/1,
- compact_otp4920_msg23/1,
- compact_otp4920_msg24/1,
- compact_otp4920_msg25/1,
- compact_otp5186_msg01/1,
- compact_otp5186_msg02/1,
- compact_otp5186_msg03/1,
- compact_otp5186_msg04/1,
- compact_otp5186_msg05/1,
- compact_otp5186_msg06/1,
- compact_otp5290_msg01/1,
- compact_otp5290_msg02/1,
+ compact_otp4013_msg1/1,
+ compact_otp4085_msg1/1,
+ compact_otp4085_msg2/1,
+ compact_otp4280_msg1/1,
+ compact_otp4299_msg1/1,
+ compact_otp4299_msg2/1,
+ compact_otp4359_msg1/1,
+ compact_otp4920_msg0/1,
+ compact_otp4920_msg1/1,
+ compact_otp4920_msg2/1,
+ compact_otp4920_msg3/1,
+ compact_otp4920_msg4/1,
+ compact_otp4920_msg5/1,
+ compact_otp4920_msg6/1,
+ compact_otp4920_msg7/1,
+ compact_otp4920_msg8/1,
+ compact_otp4920_msg9/1,
+ compact_otp4920_msg10/1,
+ compact_otp4920_msg11/1,
+ compact_otp4920_msg12/1,
+ compact_otp4920_msg20/1,
+ compact_otp4920_msg21/1,
+ compact_otp4920_msg22/1,
+ compact_otp4920_msg23/1,
+ compact_otp4920_msg24/1,
+ compact_otp4920_msg25/1,
+ compact_otp5186_msg01/1,
+ compact_otp5186_msg02/1,
+ compact_otp5186_msg03/1,
+ compact_otp5186_msg04/1,
+ compact_otp5186_msg05/1,
+ compact_otp5186_msg06/1,
+ compact_otp5290_msg01/1,
+ compact_otp5290_msg02/1,
compact_otp5793_msg01/1,
- compact_otp5993_msg01/1,
- compact_otp5993_msg02/1,
- compact_otp5993_msg03/1,
+ compact_otp5993_msg01/1,
+ compact_otp5993_msg02/1,
+ compact_otp5993_msg03/1,
compact_otp6017_msg01/1,
compact_otp6017_msg02/1,
compact_otp6017_msg03/1,
@@ -123,9 +123,33 @@
compact_otp7534_msg01/1,
compact_otp7576_msg01/1,
compact_otp7671_msg01/1,
-
- flex_compact_otp7138_msg01/1,
- flex_compact_otp7138_msg02/1,
+ compact_otp16818_msg01/1,
+ compact_otp16818_msg02/1,
+ compact_otp16818_msg03/1,
+ compact_otp16818_msg04/1,
+ compact_otp16818_msg05/1,
+ compact_otp16818_msg06/1,
+ compact_otp16818_msg11/1,
+ compact_otp16818_msg12/1,
+ compact_otp16818_msg13/1,
+ compact_otp16818_msg14/1,
+ compact_otp16818_msg15/1,
+ compact_otp16818_msg16/1,
+ compact_otp16818_msg21/1,
+ compact_otp16818_msg22/1,
+ compact_otp16818_msg23/1,
+ compact_otp16818_msg24/1,
+ compact_otp16818_msg25/1,
+ compact_otp16818_msg26/1,
+ compact_otp16818_msg31/1,
+ compact_otp16818_msg32/1,
+ compact_otp16818_msg33/1,
+ compact_otp16818_msg34/1,
+ compact_otp16818_msg35/1,
+ compact_otp16818_msg36/1,
+
+ flex_compact_otp7138_msg01/1,
+ flex_compact_otp7138_msg02/1,
flex_compact_otp7431_msg01/1,
flex_compact_otp7431_msg02/1,
flex_compact_otp7431_msg03/1,
@@ -133,67 +157,67 @@
flex_compact_otp7431_msg05/1,
flex_compact_otp7431_msg06/1,
flex_compact_otp7431_msg07/1,
- flex_compact_otp7457_msg01/1,
- flex_compact_otp7457_msg02/1,
- flex_compact_otp7457_msg03/1,
+ flex_compact_otp7457_msg01/1,
+ flex_compact_otp7457_msg02/1,
+ flex_compact_otp7457_msg03/1,
flex_compact_otp7534_msg01/1,
flex_compact_otp7573_msg01/1,
- flex_compact_otp7576_msg01/1,
- flex_compact_otp10998_msg01/1,
- flex_compact_otp10998_msg02/1,
- flex_compact_otp10998_msg03/1,
- flex_compact_otp10998_msg04/1,
-
- pretty_otp4632_msg1/1,
- pretty_otp4632_msg2/1,
- pretty_otp4632_msg3/1,
- pretty_otp4632_msg4/1,
- pretty_otp4710_msg1/1,
- pretty_otp4710_msg2/1,
- pretty_otp4945_msg1/1,
- pretty_otp4945_msg2/1,
- pretty_otp4945_msg3/1,
- pretty_otp4945_msg4/1,
- pretty_otp4945_msg5/1,
- pretty_otp4945_msg6/1,
- pretty_otp4949_msg1/1,
- pretty_otp4949_msg2/1,
- pretty_otp4949_msg3/1,
- pretty_otp5042_msg1/1,
- pretty_otp5068_msg1/1,
- pretty_otp5085_msg1/1,
- pretty_otp5085_msg2/1,
- pretty_otp5085_msg3/1,
- pretty_otp5085_msg4/1,
- pretty_otp5085_msg5/1,
- pretty_otp5085_msg6/1,
- pretty_otp5085_msg7/1,
- pretty_otp5600_msg1/1,
- pretty_otp5600_msg2/1,
- pretty_otp5601_msg1/1,
+ flex_compact_otp7576_msg01/1,
+ flex_compact_otp10998_msg01/1,
+ flex_compact_otp10998_msg02/1,
+ flex_compact_otp10998_msg03/1,
+ flex_compact_otp10998_msg04/1,
+
+ pretty_otp4632_msg1/1,
+ pretty_otp4632_msg2/1,
+ pretty_otp4632_msg3/1,
+ pretty_otp4632_msg4/1,
+ pretty_otp4710_msg1/1,
+ pretty_otp4710_msg2/1,
+ pretty_otp4945_msg1/1,
+ pretty_otp4945_msg2/1,
+ pretty_otp4945_msg3/1,
+ pretty_otp4945_msg4/1,
+ pretty_otp4945_msg5/1,
+ pretty_otp4945_msg6/1,
+ pretty_otp4949_msg1/1,
+ pretty_otp4949_msg2/1,
+ pretty_otp4949_msg3/1,
+ pretty_otp5042_msg1/1,
+ pretty_otp5068_msg1/1,
+ pretty_otp5085_msg1/1,
+ pretty_otp5085_msg2/1,
+ pretty_otp5085_msg3/1,
+ pretty_otp5085_msg4/1,
+ pretty_otp5085_msg5/1,
+ pretty_otp5085_msg6/1,
+ pretty_otp5085_msg7/1,
+ pretty_otp5600_msg1/1,
+ pretty_otp5600_msg2/1,
+ pretty_otp5601_msg1/1,
pretty_otp5793_msg01/1,
- pretty_otp5882_msg01/1,
- pretty_otp6490_msg01/1,
- pretty_otp6490_msg02/1,
- pretty_otp6490_msg03/1,
- pretty_otp6490_msg04/1,
- pretty_otp6490_msg05/1,
- pretty_otp6490_msg06/1,
+ pretty_otp5882_msg01/1,
+ pretty_otp6490_msg01/1,
+ pretty_otp6490_msg02/1,
+ pretty_otp6490_msg03/1,
+ pretty_otp6490_msg04/1,
+ pretty_otp6490_msg05/1,
+ pretty_otp6490_msg06/1,
pretty_otp7249_msg01/1,
pretty_otp7671_msg01/1,
pretty_otp7671_msg02/1,
pretty_otp7671_msg03/1,
pretty_otp7671_msg04/1,
pretty_otp7671_msg05/1,
-
- flex_pretty_otp5042_msg1/1,
- flex_pretty_otp5085_msg1/1,
- flex_pretty_otp5085_msg2/1,
- flex_pretty_otp5085_msg3/1,
- flex_pretty_otp5085_msg4/1,
- flex_pretty_otp5085_msg5/1,
- flex_pretty_otp5085_msg6/1,
- flex_pretty_otp5085_msg7/1,
+
+ flex_pretty_otp5042_msg1/1,
+ flex_pretty_otp5085_msg1/1,
+ flex_pretty_otp5085_msg2/1,
+ flex_pretty_otp5085_msg3/1,
+ flex_pretty_otp5085_msg4/1,
+ flex_pretty_otp5085_msg5/1,
+ flex_pretty_otp5085_msg6/1,
+ flex_pretty_otp5085_msg7/1,
flex_pretty_otp5600_msg1/1,
flex_pretty_otp5600_msg2/1,
flex_pretty_otp5601_msg1/1,
@@ -205,7 +229,7 @@
flex_pretty_otp7431_msg05/1,
flex_pretty_otp7431_msg06/1,
flex_pretty_otp7431_msg07/1
- ]).
+ ]).
-export([display_text_messages/0, generate_text_messages/0]).
@@ -215,7 +239,7 @@
profile_decode_compact_text_messages/0,
profile_decode_compact_flex_text_messages/0,
profile_decode_pretty_text_message/1,
- profile_decode_pretty_text_messages/0,
+ profile_decode_pretty_text_messages/0,
profile_decode_pretty_flex_text_messages/0,
%% Encode
@@ -251,18 +275,18 @@
%% Common Test interface functions
%%======================================================================
-suite() ->
+suite() ->
[{ct_hooks, [ts_install_cth]}].
-all() ->
+all() ->
[
- {group, text},
- {group, binary},
+ {group, text},
+ {group, binary},
{group, erl_dist},
{group, tickets}
].
-groups() ->
+groups() ->
[
{text, [], text_cases()},
{binary, [], binary_cases()},
@@ -271,7 +295,7 @@ groups() ->
{compact, [], compact_cases()},
{flex_pretty, [], flex_pretty_cases()},
{flex_compact, [], flex_compact_cases()},
- {bin, [], bin_cases()},
+ {bin, [], bin_cases()},
{ber, [], ber_cases()},
{per, [], per_cases()},
{erl_dist_m, [], erl_dist_m_cases()},
@@ -285,16 +309,16 @@ groups() ->
text_cases() ->
[
- {group, pretty},
+ {group, pretty},
{group, flex_pretty},
- {group, compact},
+ {group, compact},
{group, flex_compact}
].
binary_cases() ->
[
- {group, bin},
- {group, ber},
+ {group, bin},
+ {group, ber},
{group, per}
].
@@ -313,20 +337,20 @@ compact_cases() ->
compact_test_msgs
].
-flex_pretty_cases() ->
+flex_pretty_cases() ->
[
flex_pretty_test_msgs
].
-flex_compact_cases() ->
+flex_compact_cases() ->
[
- flex_compact_test_msgs,
+ flex_compact_test_msgs,
flex_compact_dm_timers1,
- flex_compact_dm_timers2,
+ flex_compact_dm_timers2,
flex_compact_dm_timers3,
- flex_compact_dm_timers4,
+ flex_compact_dm_timers4,
flex_compact_dm_timers5,
- flex_compact_dm_timers6,
+ flex_compact_dm_timers6,
flex_compact_dm_timers7,
flex_compact_dm_timers8
].
@@ -353,7 +377,7 @@ erl_dist_m_cases() ->
tickets_cases() ->
[
- {group, compact_tickets},
+ {group, compact_tickets},
{group, pretty_tickets},
{group, flex_compact_tickets},
{group, flex_pretty_tickets}
@@ -427,7 +451,32 @@ compact_tickets_cases() ->
compact_otp7576_msg01,
- compact_otp7671_msg01
+ compact_otp7671_msg01,
+
+ compact_otp16818_msg01,
+ compact_otp16818_msg02,
+ compact_otp16818_msg03,
+ compact_otp16818_msg04,
+ compact_otp16818_msg05,
+ compact_otp16818_msg06,
+ compact_otp16818_msg11,
+ compact_otp16818_msg12,
+ compact_otp16818_msg13,
+ compact_otp16818_msg14,
+ compact_otp16818_msg15,
+ compact_otp16818_msg16,
+ compact_otp16818_msg21,
+ compact_otp16818_msg22,
+ compact_otp16818_msg23,
+ compact_otp16818_msg24,
+ compact_otp16818_msg25,
+ compact_otp16818_msg26,
+ compact_otp16818_msg31,
+ compact_otp16818_msg32,
+ compact_otp16818_msg33,
+ compact_otp16818_msg34,
+ compact_otp16818_msg35,
+ compact_otp16818_msg36
].
flex_compact_tickets_cases() ->
@@ -436,7 +485,7 @@ flex_compact_tickets_cases() ->
flex_compact_otp7138_msg02,
flex_compact_otp7431_msg01,
flex_compact_otp7431_msg02,
- flex_compact_otp7431_msg03,
+ flex_compact_otp7431_msg03,
flex_compact_otp7431_msg04,
flex_compact_otp7431_msg05,
flex_compact_otp7431_msg06,
@@ -470,7 +519,7 @@ pretty_tickets_cases() ->
pretty_otp4945_msg4,
pretty_otp4945_msg5,
pretty_otp4945_msg6,
-
+
pretty_otp4949_msg1,
pretty_otp4949_msg2,
pretty_otp4949_msg3,
@@ -513,25 +562,25 @@ pretty_tickets_cases() ->
].
-flex_pretty_tickets_cases() ->
+flex_pretty_tickets_cases() ->
[
- flex_pretty_otp5042_msg1,
+ flex_pretty_otp5042_msg1,
flex_pretty_otp5085_msg1,
- flex_pretty_otp5085_msg2,
+ flex_pretty_otp5085_msg2,
flex_pretty_otp5085_msg3,
- flex_pretty_otp5085_msg4,
+ flex_pretty_otp5085_msg4,
flex_pretty_otp5085_msg5,
- flex_pretty_otp5085_msg6,
+ flex_pretty_otp5085_msg6,
flex_pretty_otp5085_msg7,
- flex_pretty_otp5600_msg1,
+ flex_pretty_otp5600_msg1,
flex_pretty_otp5600_msg2,
- flex_pretty_otp5601_msg1,
+ flex_pretty_otp5601_msg1,
flex_pretty_otp5793_msg01,
- flex_pretty_otp7431_msg01,
+ flex_pretty_otp7431_msg01,
flex_pretty_otp7431_msg02,
- flex_pretty_otp7431_msg03,
+ flex_pretty_otp7431_msg03,
flex_pretty_otp7431_msg04,
- flex_pretty_otp7431_msg05,
+ flex_pretty_otp7431_msg05,
flex_pretty_otp7431_msg06,
flex_pretty_otp7431_msg07
].
@@ -591,29 +640,29 @@ end_per_suite(Config0) when is_list(Config0) ->
%% -----
%%
-init_per_group(flex_pretty_tickets = Group, Config) ->
+init_per_group(flex_pretty_tickets = Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
flex_pretty_init(Config);
-init_per_group(flex_compact_tickets = Group, Config) ->
+init_per_group(flex_compact_tickets = Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
flex_compact_init(Config);
-init_per_group(flex_compact = Group, Config) ->
+init_per_group(flex_compact = Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
flex_compact_init(Config);
-init_per_group(flex_pretty = Group, Config) ->
+init_per_group(flex_pretty = Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
flex_pretty_init(Config);
init_per_group(Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
Config.
-end_per_group(flex_pretty_tickets = _Group, Config) ->
+end_per_group(flex_pretty_tickets = _Group, Config) ->
flex_pretty_finish(Config);
-end_per_group(flex_compact_tickets = _Group, Config) ->
+end_per_group(flex_compact_tickets = _Group, Config) ->
flex_compact_finish(Config);
-end_per_group(flex_compact = _Group, Config) ->
+end_per_group(flex_compact = _Group, Config) ->
flex_compact_finish(Config);
-end_per_group(flex_pretty = _Group, Config) ->
+end_per_group(flex_pretty = _Group, Config) ->
flex_pretty_finish(Config);
end_per_group(_Group, Config) ->
Config.
@@ -641,8 +690,8 @@ end_per_testcase(Case, Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
display_text_messages() ->
- Msgs =
- msgs4() ++
+ Msgs =
+ msgs4() ++
msgs5(),
megaco_codec_test_lib:display_text_messages(?VERSION, Msgs).
@@ -650,8 +699,8 @@ display_text_messages() ->
%% ----
generate_text_messages() ->
- Msgs =
- msgs4() ++
+ Msgs =
+ msgs4() ++
msgs5(),
megaco_codec_test_lib:generate_text_messages(?V2, ?VERSION, ?EC, Msgs).
@@ -680,7 +729,7 @@ profile_decode_pretty_text_message(MsgTag) ->
profile_decode_text_message(Codec, Config, MsgTag).
profile_decode_text_message(Codec, Config, MsgTag) ->
- Msgs = msgs4() ++ msgs5(),
+ Msgs = msgs4() ++ msgs5(),
case lists:keysearch(MsgTag, 1, Msgs) of
{value, Msg} ->
profile_decode_text_messages(Codec, Config, [Msg]);
@@ -692,14 +741,14 @@ profile_decode_text_message(Codec, Config, MsgTag) ->
%% (catch megaco_codec_v2_test:profile_decode_compact_text_messages()).
profile_decode_compact_text_messages() ->
Config = [],
- Slogan = decode_compact_v2,
+ Slogan = decode_compact_v2,
profile_decode_compact_text_messages(Slogan, Config).
%% (catch megaco_codec_v2_test:profile_decode_compact_flex_text_messages()).
profile_decode_compact_flex_text_messages() ->
Conf = flex_init([]),
Config = flex_scanner_conf(Conf),
- Slogan = decode_compact_flex_v2,
+ Slogan = decode_compact_flex_v2,
Res = profile_decode_compact_text_messages(Slogan, [Config]),
flex_finish(Conf),
Res.
@@ -711,14 +760,14 @@ profile_decode_compact_text_messages(Slogan, Config) ->
%% (catch megaco_codec_v2_test:profile_decode_pretty_text_messages()).
profile_decode_pretty_text_messages() ->
Config = [],
- Slogan = decode_pretty_v2,
+ Slogan = decode_pretty_v2,
profile_decode_pretty_text_messages(Slogan, Config).
%% (catch megaco_codec_v2_test:profile_decode_pretty_flex_text_messages()).
profile_decode_pretty_flex_text_messages() ->
Conf = flex_init([]),
Config = flex_scanner_conf(Conf),
- Slogan = decode_pretty_flex_v2,
+ Slogan = decode_pretty_flex_v2,
Res = profile_decode_pretty_text_messages(Slogan, [Config]),
flex_finish(Conf),
Res.
@@ -748,18 +797,18 @@ profile_decode_text_messages(Slogan, Codec, Config, Msgs0) ->
profile_encode_compact_text_messages() ->
Codec = megaco_compact_text_encoder,
Config = [],
- Slogan = encode_compact_v2,
+ Slogan = encode_compact_v2,
profile_encode_text_messages(Slogan, Codec, Config).
%% (catch megaco_codec_v2_test:profile_encode_pretty_text_messages()).
profile_encode_pretty_text_messages() ->
Codec = megaco_pretty_text_encoder,
Config = [],
- Slogan = encode_pretty_v2,
+ Slogan = encode_pretty_v2,
profile_encode_text_messages(Slogan, Codec, Config).
profile_encode_text_messages(Slogan, Codec, Config) ->
- Msgs = msgs4() ++ msgs5(),
+ Msgs = msgs4() ++ msgs5(),
profile_encode_text_messages(Slogan, Codec, Config, Msgs).
profile_encode_text_messages(Slogan, Codec, Config, Msgs0) ->
@@ -793,7 +842,7 @@ pretty_test_msgs(suite) ->
pretty_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
Msgs = msgs1() ++ msgs2() ++ msgs3() ++ msgs4() ++ msgs5(),
- %% Msgs = msgs5(),
+ %% Msgs = msgs5(),
DynamicDecode = false,
test_msgs(megaco_pretty_text_encoder, DynamicDecode, [], Msgs).
@@ -802,10 +851,10 @@ pretty_test_msgs(Config) when is_list(Config) ->
flex_pretty_init(Config) ->
flex_init(Config).
-
+
flex_pretty_finish(Config) ->
flex_finish(Config).
-
+
flex_pretty_test_msgs(suite) ->
[];
@@ -914,7 +963,7 @@ flex_pretty_otp5600_msg2(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
Conf = flex_scanner_conf(Config),
pretty_otp5600(ok, pretty_otp5600_msg2(), [Conf]).
-
+
flex_pretty_otp5601_msg1(suite) ->
[];
flex_pretty_otp5601_msg1(Config) when is_list(Config) ->
@@ -1000,7 +1049,7 @@ otp7431(Expected, Codec, Msg0, Conf) ->
case decode_message(Codec, false, Conf, Bin0) of
{ok, _Msg1} when Expected =:= ok ->
io:format(" decoded", []);
- {error, {bad_property_parm, Reason}} when (Expected =:= error) andalso
+ {error, {bad_property_parm, Reason}} when (Expected =:= error) andalso
is_list(Reason) ->
io:format("expected result: ~s", [Reason]),
ok;
@@ -1017,20 +1066,20 @@ flex_pretty_otp7431_msg1() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
- v=0
- o=- 2890844526 2890842807 IN IP4 124.124.124.222
- s=-
- t= 0 0
- c=IN IP4 124.124.124.222
- m=audio 2222 RTP/AVP 4
- a=ptime:30
- a=recvonly
+ Local {
+ v=0
+ o=- 2890844526 2890842807 IN IP4 124.124.124.222
+ s=-
+ t= 0 0
+ c=IN IP4 124.124.124.222
+ m=audio 2222 RTP/AVP 4
+ a=ptime:30
+ a=recvonly
} ; RTP profile for G.723.1 is 4
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg2() ->
@@ -1040,19 +1089,19 @@ flex_pretty_otp7431_msg2() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
- v=0
- o=- 2890844526 2890842807 IN IP4 124.124.124.222
- s=-
- t= 0 0
- c=IN IP4 124.124.124.222
- m=audio 2222 RTP/AVP 4
- a=ptime:30
+ Local {
+ v=0
+ o=- 2890844526 2890842807 IN IP4 124.124.124.222
+ s=-
+ t= 0 0
+ c=IN IP4 124.124.124.222
+ m=audio 2222 RTP/AVP 4
+ a=ptime:30
a= }
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg3() ->
@@ -1062,19 +1111,19 @@ flex_pretty_otp7431_msg3() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
- v=0
- o=- 2890844526 2890842807 IN IP4 124.124.124.222
- s=-
- t= 0 0
- c=IN IP4 124.124.124.222
- m=audio 2222 RTP/AVP 4
- a=ptime:30
+ Local {
+ v=0
+ o=- 2890844526 2890842807 IN IP4 124.124.124.222
+ s=-
+ t= 0 0
+ c=IN IP4 124.124.124.222
+ m=audio 2222 RTP/AVP 4
+ a=ptime:30
a }
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg4() ->
@@ -1084,19 +1133,19 @@ flex_pretty_otp7431_msg4() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
- v=0
- o=- 2890844526 2890842807 IN IP4 124.124.124.222
- s=-
- t= 0 0
- c=IN IP4 124.124.124.222
- m=audio 2222 RTP/AVP 4
- a=ptime:30
+ Local {
+ v=0
+ o=- 2890844526 2890842807 IN IP4 124.124.124.222
+ s=-
+ t= 0 0
+ c=IN IP4 124.124.124.222
+ m=audio 2222 RTP/AVP 4
+ a=ptime:30
a}
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg5() ->
@@ -1106,12 +1155,12 @@ flex_pretty_otp7431_msg5() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
+ Local {
v= }
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg6() ->
@@ -1121,12 +1170,12 @@ flex_pretty_otp7431_msg6() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
+ Local {
v }
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg7() ->
@@ -1136,12 +1185,12 @@ flex_pretty_otp7431_msg7() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
+ Local {
v}
}
}
}
- }
+ }
}".
@@ -1164,14 +1213,14 @@ flex_compact_init(Config) ->
flex_compact_finish(Config) ->
flex_finish(Config).
-
+
flex_compact_test_msgs(suite) ->
[];
flex_compact_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
Msgs = msgs1() ++ msgs2() ++ msgs3() ++ msgs4(),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
DynamicDecode = true,
test_msgs(megaco_compact_text_encoder, DynamicDecode, [Conf], Msgs).
@@ -1182,7 +1231,7 @@ flex_compact_dm_timers1(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("1", "2", "3"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
case decode_message(megaco_compact_text_encoder, false, [Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers1 -> "
@@ -1200,7 +1249,7 @@ flex_compact_dm_timers2(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("02", "03", "04"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
case decode_message(megaco_compact_text_encoder, false, [Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers2 -> "
@@ -1218,7 +1267,7 @@ flex_compact_dm_timers3(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("1", "02", "31"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
case decode_message(megaco_compact_text_encoder, false, [Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers3 -> "
@@ -1236,7 +1285,7 @@ flex_compact_dm_timers4(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("10", "21", "99"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
case decode_message(megaco_compact_text_encoder, false, [Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers4 -> "
@@ -1254,7 +1303,7 @@ flex_compact_dm_timers5(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("99", "23", "11"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
case decode_message(megaco_compact_text_encoder, false, [Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers5 -> "
@@ -1272,7 +1321,7 @@ flex_compact_dm_timers6(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("77", "09", "1"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
case decode_message(megaco_compact_text_encoder, false, [Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers6 -> "
@@ -1290,7 +1339,7 @@ flex_compact_dm_timers7(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("77", "09", "1", "99"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
case decode_message(megaco_compact_text_encoder, false, [Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers7 -> "
@@ -1308,7 +1357,7 @@ flex_compact_dm_timers8(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("01", "09", "01", "02"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
case decode_message(megaco_compact_text_encoder, false, [Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers8 -> "
@@ -1349,7 +1398,7 @@ get_dm_timers({transactions, T}) when is_list(T) ->
get_dm_timers(Other) ->
{error, {invalid_transactions, Other}}.
-get_dm_timers1([{transactionRequest,T}|Ts])
+get_dm_timers1([{transactionRequest,T}|Ts])
when is_record(T,'TransactionRequest') ->
case get_dm_timers2(T) of
{ok, Timers} ->
@@ -1429,7 +1478,7 @@ bin_test_msgs(Config) when is_list(Config) ->
DynamicDecode = false,
test_msgs(megaco_binary_encoder, DynamicDecode, [], Msgs).
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ber_test_msgs(suite) ->
@@ -1440,7 +1489,7 @@ ber_test_msgs(Config) when is_list(Config) ->
DynamicDecode = false,
test_msgs(megaco_ber_encoder, DynamicDecode, [], Msgs).
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
per_test_msgs(suite) ->
@@ -1460,7 +1509,7 @@ erl_dist_m_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
Msgs = msgs1() ++ msgs2() ++ msgs3() ++ msgs4(),
DynamicDecode = false,
- Conf = [megaco_compressed],
+ Conf = [megaco_compressed],
test_msgs(megaco_erl_dist_encoder, DynamicDecode, Conf, Msgs).
@@ -1526,11 +1575,11 @@ compact_otp4011(M) ->
d("compact_otp4011 -> expected error: "
"~n Reason: ~p", [Reason]),
case Reason of
- {0, megaco_text_parser_v2,
- {do_merge_control_streamParms, [A,B]}}
+ {0, megaco_text_parser_v2,
+ {do_merge_control_streamParms, [A,B]}}
when is_list(A) andalso is_record(B, 'LocalControlDescriptor') ->
case lists:keysearch(mode,1,A) of
- {value, {mode, Mode}}
+ {value, {mode, Mode}}
when B#'LocalControlDescriptor'.streamMode /= asn1_NOVALUE ->
d("compact_otp4011 -> "
"expected error [~w]",[Mode]),
@@ -1566,20 +1615,20 @@ compact_otp4013_msg1(Config) when is_list(Config) ->
{ok, _} ->
exit({decoded_erroneous_message,M});
{error, Reason} when is_list(Reason) ->
- {value, {reason, no_version_found, _}} =
+ {value, {reason, no_version_found, _}} =
lists:keysearch(reason, 1, Reason),
- {value, {token, [{'SafeChars',_,"megcao/2"}|_]}} =
+ {value, {token, [{'SafeChars',_,"megcao/2"}|_]}} =
lists:keysearch(token, 1, Reason),
ok;
Else ->
exit({unexpected_decode_result,Else})
end.
-
+
%% --------------------------------------------------------------
-%%
-%%
+%%
+%%
compact_otp4085_msg1(suite) ->
[];
compact_otp4085_msg1(Config) when is_list(Config) ->
@@ -1638,8 +1687,8 @@ compact_otp4085_msg2(Config) when is_list(Config) ->
%% This message lack the ending parentesis (}).
compact_otp4085_erroneous_msg() ->
- M = "!/"
- ?VERSION_STR
+ M = "!/"
+ ?VERSION_STR
" ML T=11223342{C=${A=${M{O{MO=SR,RV=OFF,RG=OFF},L{v=0,"
"c=ATM NSAP $ ,"
"a=eecid:$ ,"
@@ -1648,8 +1697,8 @@ compact_otp4085_erroneous_msg() ->
M.
%% --------------------------------------------------------------
-%%
-%%
+%%
+%%
compact_otp4280_msg1(suite) ->
[];
compact_otp4280_msg1(Config) when is_list(Config) ->
@@ -1659,7 +1708,7 @@ compact_otp4280_msg1(Config) when is_list(Config) ->
case decode_message(megaco_compact_text_encoder, false, [], Bin) of
{ok, _Msg} ->
ok;
- {error, Error} when is_list(Error) ->
+ {error, Error} when is_list(Error) ->
t("compact_otp4280_msg1 -> decode failed", []),
case lists:keysearch(reason, 1, Error) of
{value, {reason,{Line, Module, Reason} = R}} ->
@@ -1743,8 +1792,8 @@ compact_otp4299_msg2_init() ->
compact_otp4299_msg2_finish(Pid) ->
stop_flex_scanner(Pid).
-
-
+
+
compact_otp4299_msg() ->
M = ";KALLE\n"
"!/"
@@ -1763,8 +1812,8 @@ compact_otp4299_msg() ->
%% --------------------------------------------------------------
-%%
-%%
+%%
+%%
compact_otp4359_msg1(suite) ->
[];
compact_otp4359_msg1(Config) when is_list(Config) ->
@@ -1792,8 +1841,8 @@ compact_otp4359_msg() ->
%% --------------------------------------------------------------
-%%
-%%
+%%
+%%
compact_otp4920_msg0(suite) ->
[];
compact_otp4920_msg0(Config) when is_list(Config) ->
@@ -1973,7 +2022,7 @@ compact_otp4920_msg_2(M1, ExpectedReason) ->
ExpectedReason ->
ok;
_ ->
- exit({unexpected_decode_error_reason,
+ exit({unexpected_decode_error_reason,
ExpectedReason, Reason})
end;
{error, [{reason, {_Mod, Reason}}|_]} ->
@@ -1981,7 +2030,7 @@ compact_otp4920_msg_2(M1, ExpectedReason) ->
ExpectedReason ->
ok;
_ ->
- exit({unexpected_decode_error_reason,
+ exit({unexpected_decode_error_reason,
ExpectedReason, Reason})
end;
Else ->
@@ -2191,7 +2240,7 @@ compact_otp5186_msg_2(Msg1, EncodeExpect, DecodeExpect) ->
%% --
-
+
compact_otp5186_msg01() ->
"!/2 <mg5>\nP=67111298{C=2699{AV=mg5_ipeph/0x0f0001{}}}".
@@ -2331,7 +2380,7 @@ compact_otp5186_msg06() ->
compact_otp5186_check_megamsg(M1, M1) ->
ok;
compact_otp5186_check_megamsg(#'MegacoMessage'{authHeader = AH,
- mess = M1},
+ mess = M1},
#'MegacoMessage'{authHeader = AH,
mess = M2}) ->
compact_otp5186_check_mess(M1, M2);
@@ -2341,21 +2390,21 @@ compact_otp5186_check_megamsg(#'MegacoMessage'{authHeader = AH1},
compact_otp5186_check_mess(M, M) ->
ok;
-compact_otp5186_check_mess(#'Message'{version = V,
+compact_otp5186_check_mess(#'Message'{version = V,
mId = MId,
messageBody = B1},
- #'Message'{version = V,
+ #'Message'{version = V,
mId = MId,
messageBody = B2}) ->
compact_otp5186_check_body(B1, B2);
-compact_otp5186_check_mess(#'Message'{version = V,
+compact_otp5186_check_mess(#'Message'{version = V,
mId = MId1},
- #'Message'{version = V,
+ #'Message'{version = V,
mId = MId2}) ->
exit({not_equal, mId, MId1, MId2});
-compact_otp5186_check_mess(#'Message'{version = V1,
+compact_otp5186_check_mess(#'Message'{version = V1,
mId = MId},
- #'Message'{version = V2,
+ #'Message'{version = V2,
mId = MId}) ->
exit({not_equal, version, V1, V2}).
@@ -2393,12 +2442,12 @@ compact_otp5186_check_merr(#'ErrorDescriptor'{errorCode = EC1,
compact_otp5186_check_transaction(T, T) ->
ok;
-compact_otp5186_check_transaction({transactionReply, TR1},
+compact_otp5186_check_transaction({transactionReply, TR1},
{transactionReply, TR2}) ->
compact_otp5186_check_transRep(TR1, TR2);
compact_otp5186_check_transaction(T1, T2) ->
exit({unexpected_transactions, T1, T2}).
-
+
compact_otp5186_check_transRep(T, T) ->
ok;
compact_otp5186_check_transRep(#'TransactionReply'{transactionId = TId,
@@ -2413,7 +2462,7 @@ compact_otp5186_check_transRep(T1, T2) ->
compact_otp5186_check_transRes(TR, TR) ->
ok;
-compact_otp5186_check_transRes({actionReplies, AR1},
+compact_otp5186_check_transRes({actionReplies, AR1},
{actionReplies, AR2}) ->
compact_otp5186_check_actReps(AR1, AR2);
compact_otp5186_check_transRes(TR1, TR2) ->
@@ -2455,10 +2504,10 @@ compact_otp5186_check_cmdReps([CR1|CRs1], [CR2|CRs2]) ->
compact_otp5186_check_cmdRep(CR, CR) ->
ok;
-compact_otp5186_check_cmdRep({auditValueReply, AVR1},
+compact_otp5186_check_cmdRep({auditValueReply, AVR1},
{auditValueReply, AVR2}) ->
compact_otp5186_check_auditReply(AVR1, AVR2);
-compact_otp5186_check_cmdRep({addReply, AVR1},
+compact_otp5186_check_cmdRep({addReply, AVR1},
{addReply, AVR2}) ->
compact_otp5186_check_ammsReply(AVR1, AVR2);
compact_otp5186_check_cmdRep(CR1, CR2) ->
@@ -2466,7 +2515,7 @@ compact_otp5186_check_cmdRep(CR1, CR2) ->
compact_otp5186_check_auditReply(AR, AR) ->
ok;
-compact_otp5186_check_auditReply({auditResult, AR1},
+compact_otp5186_check_auditReply({auditResult, AR1},
{auditResult, AR2}) ->
compact_otp5186_check_auditRes(AR1, AR2);
compact_otp5186_check_auditReply(AR1, AR2) ->
@@ -2526,7 +2575,7 @@ compact_otp5186_check_termAudit([{emptyDescriptors,
compact_otp5186_check_termAudit(TAR1, []) ->
exit({not_equal, termAudit, TAR1, []});
%% An empty empty descriptor is removed
-compact_otp5186_check_termAudit([],
+compact_otp5186_check_termAudit([],
[{emptyDescriptors,
#'AuditDescriptor'{auditToken = asn1_NOVALUE,
auditPropertyToken = asn1_NOVALUE}}|TAR2]) ->
@@ -2539,7 +2588,7 @@ compact_otp5186_check_termAudit([ARP1|TAR1], [ARP2|TAR2]) ->
compact_otp5186_check_auditRetParm(ARP, ARP) ->
ok;
-compact_otp5186_check_auditRetParm({emptyDescriptors, AD1},
+compact_otp5186_check_auditRetParm({emptyDescriptors, AD1},
{emptyDescriptors, AD2}) ->
compact_otp5186_check_auditDesc(AD1, AD2);
compact_otp5186_check_auditRetParm(ARP1, ARP2) ->
@@ -2677,7 +2726,7 @@ compact_otp5993(Expected, Msg) ->
compact_otp5993_msg01() ->
MT = h221,
T = #megaco_term_id{id = ?A4444},
- TL = [T],
+ TL = [T],
MD = #'MuxDescriptor'{muxType = MT,
termList = TL},
compact_otp5993_msg(MD).
@@ -2686,14 +2735,14 @@ compact_otp5993_msg02() ->
MT = h223,
T1 = #megaco_term_id{id = ?A4445},
T2 = #megaco_term_id{id = ?A5556},
- TL = [T1, T2],
+ TL = [T1, T2],
MD = #'MuxDescriptor'{muxType = MT,
termList = TL},
compact_otp5993_msg(MD).
compact_otp5993_msg(MD) when is_record(MD, 'MuxDescriptor') ->
AmmDesc = {muxDescriptor, MD},
- AmmReq = #'AmmRequest'{terminationID = [hd(MD#'MuxDescriptor'.termList)],
+ AmmReq = #'AmmRequest'{terminationID = [hd(MD#'MuxDescriptor'.termList)],
descriptors = [AmmDesc]},
Cmd = {addReq, AmmReq},
CmdReq = #'CommandRequest'{command = Cmd},
@@ -2702,7 +2751,7 @@ compact_otp5993_msg(MD) when is_record(MD, 'MuxDescriptor') ->
TransReq = #'TransactionRequest'{transactionId = 3995,
actions = [ActReq]},
Trans = {transactionRequest, TransReq},
- Body = {transactions, [Trans]},
+ Body = {transactions, [Trans]},
Msg = #'Message'{version = ?VERSION,
mId = ?MG1_MID,
messageBody = Body},
@@ -2711,15 +2760,15 @@ compact_otp5993_msg(MD) when is_record(MD, 'MuxDescriptor') ->
compact_otp5993_msg03() ->
T1 = #megaco_term_id{id = ?A4445},
T2 = #megaco_term_id{id = ?A5556},
- TIDs = [T1, T2],
- AudRep = {contextAuditResult, TIDs},
- CmdRep = {auditValueReply, AudRep},
+ TIDs = [T1, T2],
+ AudRep = {contextAuditResult, TIDs},
+ CmdRep = {auditValueReply, AudRep},
ActRep = #'ActionReply'{contextId = 5993,
commandReply = [CmdRep]},
TransRes = {actionReplies, [ActRep]},
TransRep = #'TransactionReply'{transactionId = 3995,
transactionResult = TransRes},
- Trans = {transactionReply, TransRep},
+ Trans = {transactionReply, TransRep},
Body = {transactions, [Trans]},
Msg = #'Message'{version = ?VERSION,
mId = ?MG1_MID,
@@ -2835,7 +2884,7 @@ compact_otp7138(EC, BinMsg) ->
{error, Reason}
end.
-
+
compact_otp7457_msg01(suite) ->
[];
compact_otp7457_msg01(Config) when is_list(Config) ->
@@ -2929,8 +2978,8 @@ compact_otp7576_msg01(Config) when is_list(Config) ->
ok.
compact_otp7576_msg01() ->
- M = "!/"
- ?VERSION_STR
+ M = "!/"
+ ?VERSION_STR
"[130.100.144.37]:2944\nT=10032{C=${tp{*,*,is,st=1},pr=6,a=rtp/2/${m{st=1{o{mo=so,rv=ON},l{
v=0
c=IN IP4 $
@@ -2967,7 +3016,7 @@ compact_otp7576(EC, BinMsg) ->
d("compact_otp7138 -> encode successfull but result differ: "
"~n ~p", [binary_to_list(BinMsg2)]),
case decode_message(Codec, false, EC, BinMsg2) of
- {ok, Msg} ->
+ {ok, Msg} ->
d("compact_otp7138 -> "
"extra verification decode ok", []),
ok;
@@ -3017,6 +3066,338 @@ compact_otp7671_msg01() ->
pretty_otp7671_msg01().
+
+%% --------------------------------------------------------------
+%%
+compact_otp16818_msg01(suite) ->
+ [];
+compact_otp16818_msg01(Config) when is_list(Config) ->
+ d("compact_otp16818_msg01 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg01() ),
+ ok.
+
+compact_otp16818_msg01() ->
+ compact_otp16818_msg("a").
+
+
+%% --
+
+compact_otp16818_msg02(suite) ->
+ [];
+compact_otp16818_msg02(Config) when is_list(Config) ->
+ d("compact_otp16818_msg02 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg02() ),
+ ok.
+
+compact_otp16818_msg02() ->
+ compact_otp16818_msg("b").
+
+
+%% --
+
+compact_otp16818_msg03(suite) ->
+ [];
+compact_otp16818_msg03(Config) when is_list(Config) ->
+ d("compact_otp16818_msg03 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg03() ),
+ ok.
+
+compact_otp16818_msg03() ->
+ compact_otp16818_msg("c").
+
+
+%% --
+
+compact_otp16818_msg04(suite) ->
+ [];
+compact_otp16818_msg04(Config) when is_list(Config) ->
+ d("compact_otp16818_msg04 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg04() ),
+ ok.
+
+compact_otp16818_msg04() ->
+ compact_otp16818_msg("d").
+
+
+%% --
+
+compact_otp16818_msg05(suite) ->
+ [];
+compact_otp16818_msg05(Config) when is_list(Config) ->
+ d("compact_otp16818_msg05 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg05() ),
+ ok.
+
+compact_otp16818_msg05() ->
+ compact_otp16818_msg("e").
+
+
+%% --
+
+compact_otp16818_msg06(suite) ->
+ [];
+compact_otp16818_msg06(Config) when is_list(Config) ->
+ d("compact_otp16818_msg06 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg06() ),
+ ok.
+
+compact_otp16818_msg06() ->
+ compact_otp16818_msg("f").
+
+
+%% --
+
+compact_otp16818_msg11(suite) ->
+ [];
+compact_otp16818_msg11(Config) when is_list(Config) ->
+ d("compact_otp16818_msg11 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg11() ),
+ ok.
+
+compact_otp16818_msg11() ->
+ compact_otp16818_msg("000a").
+
+
+%% --
+
+compact_otp16818_msg12(suite) ->
+ [];
+compact_otp16818_msg12(Config) when is_list(Config) ->
+ d("compact_otp16818_msg12 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg12() ),
+ ok.
+
+compact_otp16818_msg12() ->
+ compact_otp16818_msg("000b").
+
+
+%% --
+
+compact_otp16818_msg13(suite) ->
+ [];
+compact_otp16818_msg13(Config) when is_list(Config) ->
+ d("compact_otp16818_msg13 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg13() ),
+ ok.
+
+compact_otp16818_msg13() ->
+ compact_otp16818_msg("000c").
+
+
+%% --
+
+compact_otp16818_msg14(suite) ->
+ [];
+compact_otp16818_msg14(Config) when is_list(Config) ->
+ d("compact_otp16818_msg14 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg14() ),
+ ok.
+
+compact_otp16818_msg14() ->
+ compact_otp16818_msg("000d").
+
+
+%% --
+
+compact_otp16818_msg15(suite) ->
+ [];
+compact_otp16818_msg15(Config) when is_list(Config) ->
+ d("compact_otp16818_msg15 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg15() ),
+ ok.
+
+compact_otp16818_msg15() ->
+ compact_otp16818_msg("000e").
+
+
+%% --
+
+compact_otp16818_msg16(suite) ->
+ [];
+compact_otp16818_msg16(Config) when is_list(Config) ->
+ d("compact_otp16818_msg16 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg16() ),
+ ok.
+
+compact_otp16818_msg16() ->
+ compact_otp16818_msg("000f").
+
+
+%% --
+
+compact_otp16818_msg21(suite) ->
+ [];
+compact_otp16818_msg21(Config) when is_list(Config) ->
+ d("compact_otp16818_msg21 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg21() ),
+ ok.
+
+compact_otp16818_msg21() ->
+ compact_otp16818_msg("0a12").
+
+
+%% --
+
+compact_otp16818_msg22(suite) ->
+ [];
+compact_otp16818_msg22(Config) when is_list(Config) ->
+ d("compact_otp16818_msg22 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg22() ),
+ ok.
+
+compact_otp16818_msg22() ->
+ compact_otp16818_msg("0b12").
+
+
+%% --
+
+compact_otp16818_msg23(suite) ->
+ [];
+compact_otp16818_msg23(Config) when is_list(Config) ->
+ d("compact_otp16818_msg23 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg23() ),
+ ok.
+
+compact_otp16818_msg23() ->
+ compact_otp16818_msg("0c12").
+
+
+%% --
+
+compact_otp16818_msg24(suite) ->
+ [];
+compact_otp16818_msg24(Config) when is_list(Config) ->
+ d("compact_otp16818_msg24 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg24() ),
+ ok.
+
+compact_otp16818_msg24() ->
+ compact_otp16818_msg("0d12").
+
+
+%% --
+
+compact_otp16818_msg25(suite) ->
+ [];
+compact_otp16818_msg25(Config) when is_list(Config) ->
+ d("compact_otp16818_msg25 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg25() ),
+ ok.
+
+compact_otp16818_msg25() ->
+ compact_otp16818_msg("0e12").
+
+
+%% --
+
+compact_otp16818_msg26(suite) ->
+ [];
+compact_otp16818_msg26(Config) when is_list(Config) ->
+ d("compact_otp16818_msg26 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg26() ),
+ ok.
+
+compact_otp16818_msg26() ->
+ compact_otp16818_msg("0f12").
+
+
+%% --
+
+compact_otp16818_msg31(suite) ->
+ [];
+compact_otp16818_msg31(Config) when is_list(Config) ->
+ d("compact_otp16818_msg31 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg31() ),
+ ok.
+
+compact_otp16818_msg31() ->
+ compact_otp16818_msg("a123").
+
+
+%% --
+
+compact_otp16818_msg32(suite) ->
+ [];
+compact_otp16818_msg32(Config) when is_list(Config) ->
+ d("compact_otp16818_msg32 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg32() ),
+ ok.
+
+compact_otp16818_msg32() ->
+ compact_otp16818_msg("b123").
+
+
+%% --
+
+compact_otp16818_msg33(suite) ->
+ [];
+compact_otp16818_msg33(Config) when is_list(Config) ->
+ d("compact_otp16818_msg33 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg33() ),
+ ok.
+
+compact_otp16818_msg33() ->
+ compact_otp16818_msg("c123").
+
+
+%% --
+
+compact_otp16818_msg34(suite) ->
+ [];
+compact_otp16818_msg34(Config) when is_list(Config) ->
+ d("compact_otp16818_msg34 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg34() ),
+ ok.
+
+compact_otp16818_msg34() ->
+ compact_otp16818_msg("d123").
+
+
+%% --
+
+compact_otp16818_msg35(suite) ->
+ [];
+compact_otp16818_msg35(Config) when is_list(Config) ->
+ d("compact_otp16818_msg35 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg35() ),
+ ok.
+
+compact_otp16818_msg35() ->
+ compact_otp16818_msg("e123").
+
+
+%% --
+
+compact_otp16818_msg36(suite) ->
+ [];
+compact_otp16818_msg36(Config) when is_list(Config) ->
+ d("compact_otp16818_msg36 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg36() ),
+ ok.
+
+compact_otp16818_msg36() ->
+ compact_otp16818_msg("f123").
+
+
+%% -----
+
+compact_otp16818( Msg ) ->
+ Bin = erlang:list_to_binary(Msg),
+ try megaco_compact_text_encoder:decode_message([], dynamic, Bin) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ catch
+ C:E:S ->
+ {error, {C, E, S}}
+ end.
+
+compact_otp16818_msg(X) when is_list(X) ->
+ "!/2 [2409:8050:5005:1243:1011::" ++ X ++
+ "] T=2523{C=-{SC=ROOT{SV{MT=RS,RE=901,PF=ETSI_BGF/2,V=3}}}}".
+
+
%% ==============================================================
%%
%% F l e x C o m p a c t T e s t c a s e s
@@ -3315,173 +3696,173 @@ flex_compact_otp7576_msg01(Config) when is_list(Config) ->
flex_compact_otp10998_msg01() ->
<<"!/2 stofmg0
P=25165898{C=34227581{AV=r01/03/01/38/22{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227613{AV=r01/03/01/38/12{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227744{AV=r01/03/01/38/11{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227755{AV=r01/03/01/38/2{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227768{AV=r01/03/01/38/14{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227774{AV=r01/03/01/38/15{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227775{AV=r01/03/01/38/16{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323119{AV=r01/03/01/38/9{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323122{AV=r01/03/01/38/7{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323156{AV=r01/03/01/38/8{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323260{AV=r01/03/01/38/13{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323272{AV=r01/03/01/38/30{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323273{AV=r01/03/01/38/29{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323275{AV=r01/03/01/38/25{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323276{AV=r01/03/01/38/28{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323279{AV=r01/03/01/38/26{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323280{AV=r01/03/01/38/24{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323281{AV=r01/03/01/38/27{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323284{AV=r01/03/01/38/23{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34129764{AV=r01/03/01/55/31{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227463{AV=r01/03/01/55/26{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227472{AV=r01/03/01/55/22{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227484{AV=r01/03/01/55/16{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227555{AV=r01/03/01/55/5{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227556{AV=r01/03/01/55/14{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227557{AV=r01/03/01/55/10{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227563{AV=r01/03/01/55/7{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227565{AV=r01/03/01/55/13{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227602{AV=r01/03/01/55/21{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227616{AV=r01/03/01/55/1{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227704{AV=r01/03/01/55/19{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227705{AV=r01/03/01/55/18{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34227715{AV=r01/03/01/55/20{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34322656{AV=r01/03/01/55/30{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34322804{AV=r01/03/01/55/24{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34322812{AV=r01/03/01/55/15{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34322825{AV=r01/03/01/55/4{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34322836{AV=r01/03/01/55/17{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323007{AV=r01/03/01/55/6{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323008{AV=r01/03/01/55/2{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323071{AV=r01/03/01/55/28{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}},C=34323075{AV=r01/03/01/55/29{M{TS{eri_terminfo/dev_state=norm,eri_terminfo/dev_type=cee1,eri_terminfo/law_conv=on,SI=IV},O{MO=SR,RV=OFF,semper/act=on,tdmc/ec=off,semper/termstatus=0x00},L{
-v=0
-c=TN RFC2543 -
-m=audio - TDM -
+v=0
+c=TN RFC2543 -
+m=audio - TDM -
}}}}}">>.
@@ -3990,7 +4371,7 @@ flex_compact_otp10998_msg01(Config) when is_list(Config) ->
d("flex_compact_otp10998_msg01 -> entry", []),
Msg = flex_compact_otp10998_msg01(),
d("flex_compact_otp10998_msg01 -> message created", []),
- Conf =
+ Conf =
try flex_scanner_conf(Config) of
C ->
C
@@ -4011,7 +4392,7 @@ flex_compact_otp10998_msg02(Config) when is_list(Config) ->
d("flex_compact_otp10998_msg02 -> entry", []),
Msg = flex_compact_otp10998_msg02(),
d("flex_compact_otp10998_msg02 -> message created", []),
- Conf =
+ Conf =
try flex_scanner_conf(Config) of
C ->
C
@@ -4032,7 +4413,7 @@ flex_compact_otp10998_msg03(Config) when is_list(Config) ->
d("flex_compact_otp10998_msg03 -> entry", []),
Msg = flex_compact_otp10998_msg03(),
d("flex_compact_otp10998_msg03 -> message created", []),
- Conf =
+ Conf =
try flex_scanner_conf(Config) of
C ->
C
@@ -4053,7 +4434,7 @@ flex_compact_otp10998_msg04(Config) when is_list(Config) ->
d("flex_compact_otp10998_msg04 -> entry", []),
Msg = flex_compact_otp10998_msg04(),
d("flex_compact_otp10998_msg04 -> message created", []),
- Conf =
+ Conf =
try flex_scanner_conf(Config) of
C ->
C
@@ -4078,7 +4459,7 @@ flex_compact_otp10998(EC, N, BinMsg) ->
"decode ~w failed: ~p", [No, Reason]),
throw({error, No, Reason})
end
- end,
+ end,
do_flex_compact_otp10998(N, Decode).
do_flex_compact_otp10998(N, Decode) when N > 0 ->
@@ -4102,7 +4483,7 @@ pretty_otp4632_msg1(Config) when is_list(Config) ->
Msg0 = pretty_otp4632_msg1(),
case encode_message(megaco_pretty_text_encoder, [], Msg0) of
{ok, BinMsg} when is_binary(BinMsg) ->
- {ok, Msg1} = decode_message(megaco_pretty_text_encoder, false,
+ {ok, Msg1} = decode_message(megaco_pretty_text_encoder, false,
[], BinMsg),
ok = chk_MegacoMessage(Msg0, Msg1);
Else ->
@@ -4122,7 +4503,7 @@ pretty_otp4632_msg2(Config) when is_list(Config) ->
Msg0 = pretty_otp4632_msg2(),
case encode_message(megaco_pretty_text_encoder, [], Msg0) of
{ok, BinMsg} when is_binary(BinMsg) ->
- {ok, Msg1} = decode_message(megaco_pretty_text_encoder, false,
+ {ok, Msg1} = decode_message(megaco_pretty_text_encoder, false,
[], BinMsg),
ok = chk_MegacoMessage(Msg0,Msg1);
Else ->
@@ -4142,7 +4523,7 @@ pretty_otp4632_msg3(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
Msg0 = pretty_otp4632_msg3(),
Bin0 = list_to_binary(Msg0),
- case decode_message(megaco_pretty_text_encoder,
+ case decode_message(megaco_pretty_text_encoder,
false, [], Bin0) of
{ok, Msg} when is_record(Msg, 'MegacoMessage') ->
{ok, Bin1} = encode_message(megaco_pretty_text_encoder, [], Msg),
@@ -4187,11 +4568,11 @@ pretty_otp4632_msg4() ->
pretty_otp4632_msg4_chk([], []) ->
- exit(messages_not_eq);
+ exit(messages_not_eq);
pretty_otp4632_msg4_chk([], Rest1) ->
- exit({messages_not_eq1, Rest1});
+ exit({messages_not_eq1, Rest1});
pretty_otp4632_msg4_chk(Rest0, []) ->
- exit({messages_not_eq0, Rest0});
+ exit({messages_not_eq0, Rest0});
pretty_otp4632_msg4_chk([$R,$e,$a,$s,$o,$n,$ ,$=,$ ,$9,$0,$1|_Rest0],
[$R,$e,$a,$s,$o,$n,$ ,$=,$ ,$",$9,$0,$1,$"|_Rest1]) ->
ok;
@@ -4207,7 +4588,7 @@ pretty_otp4710_msg1(Config) when is_list(Config) ->
Msg0 = pretty_otp4710_msg1(),
case encode_message(megaco_pretty_text_encoder, [], Msg0) of
{ok, Bin} when is_binary(Bin) ->
- {ok, Msg1} = decode_message(megaco_pretty_text_encoder, false,
+ {ok, Msg1} = decode_message(megaco_pretty_text_encoder, false,
[], Bin),
ok = chk_MegacoMessage(Msg0,Msg1);
Else ->
@@ -4294,13 +4675,13 @@ pretty_otp4945_msg1() ->
ServiceChange = ROOT {
Services {
Method = Restart,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp4945_msg2(suite) ->
[];
@@ -4331,14 +4712,14 @@ pretty_otp4945_msg2() ->
ServiceChange = ROOT {
Services {
Reason = 901,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp4945_msg3(suite) ->
[];
pretty_otp4945_msg3(Config) when is_list(Config) ->
@@ -4369,14 +4750,14 @@ pretty_otp4945_msg3() ->
Context = - {
ServiceChange = ROOT {
Services {
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp4945_msg4(suite) ->
[];
pretty_otp4945_msg4(Config) when is_list(Config) ->
@@ -4400,13 +4781,13 @@ pretty_otp4945_msg4() ->
Services {
Method = Restart,
Reason = 901,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp4945_msg5(suite) ->
[];
@@ -4440,13 +4821,13 @@ pretty_otp4945_msg5() ->
Method = Restart,
Reason = 901,
Profile = ResGW/1,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/2
}
}
- }
+ }
}".
-
+
pretty_otp4945_msg6(suite) ->
[];
@@ -4479,14 +4860,14 @@ pretty_otp4945_msg6() ->
Services {
Method = Restart,
Reason = 901,
- ServiceChangeAddress = 55555,
- MgcIdToTry = kalle,
+ ServiceChangeAddress = 55555,
+ MgcIdToTry = kalle,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp4949_msg1(suite) ->
[];
@@ -4509,13 +4890,13 @@ pretty_otp4949_msg1() ->
Context = - {
ServiceChange = ROOT {
Services {
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp4949_msg2(suite) ->
[];
@@ -4547,13 +4928,13 @@ pretty_otp4949_msg2() ->
ServiceChange = ROOT {
Services {
Profile = ResGW/1,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/2
}
}
- }
+ }
}".
-
+
pretty_otp4949_msg3(suite) ->
[];
@@ -4584,14 +4965,14 @@ pretty_otp4949_msg3() ->
Context = - {
ServiceChange = ROOT {
Services {
- ServiceChangeAddress = 55555,
- MgcIdToTry = kalle,
+ ServiceChangeAddress = 55555,
+ MgcIdToTry = kalle,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp5042_msg1(suite) ->
[];
@@ -4619,14 +5000,14 @@ pretty_otp5042_msg1(Config) when is_list(Config) ->
pretty_otp5042_msg1() ->
"MEGACO/" ?VERSION_STR " <CATAPULT>:2944
-Transaction = 102 {
-Context = 5 { Notify = MUX/1 { ObservedEvents = 1 {
+Transaction = 102 {
+Context = 5 { Notify = MUX/1 { ObservedEvents = 1 {
h245bh/h245msgin { Stream = 1
, h245enc =
0270020600088175000653401004100403E802E00180018001780680000034301160000700088175010101007A0100020001800001320000C0000219D005027F0070500100040100021080000319D005027F00504001008000041C001250000700088175010000400280010003000880000518AA027F400006850130008011020100000001030002000300040005000006
- } }
+ } }
} } }".
-
+
pretty_otp5068_msg1(suite) ->
[];
@@ -4681,7 +5062,7 @@ pretty_otp5068_msg1() ->
asn1_NOVALUE,
inSvc},
asn1_NOVALUE}}]}}}]}]}}}]}}}.
-
+
pretty_otp5085_msg1(suite) ->
@@ -4782,7 +5163,7 @@ pretty_otp5085_msg1() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
{'ErrorDescriptor',504,asn1_NOVALUE},
asn1_NOVALUE,
@@ -4809,7 +5190,7 @@ pretty_otp5085_msg2() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
asn1_NOVALUE,
asn1_NOVALUE,
@@ -4836,7 +5217,7 @@ pretty_otp5085_msg3() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
asn1_NOVALUE,
#'ContextRequest'{priority = 3},
@@ -4863,11 +5244,11 @@ pretty_otp5085_msg4() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
asn1_NOVALUE,
asn1_NOVALUE,
- [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
+ [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
{notifyReply, cre_NotifyRep([#megaco_term_id{id = ?A5555}])}]
}
]
@@ -4891,11 +5272,11 @@ pretty_otp5085_msg5() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
asn1_NOVALUE,
#'ContextRequest'{priority = 5},
- [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
+ [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
{notifyReply, cre_NotifyRep([#megaco_term_id{id = ?A5555}])}]
}
]
@@ -4919,11 +5300,11 @@ pretty_otp5085_msg6() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
{'ErrorDescriptor',504,asn1_NOVALUE},
#'ContextRequest'{priority = 6},
- [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
+ [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
{notifyReply, cre_NotifyRep([#megaco_term_id{id = ?A5555}])}]
}
]
@@ -4947,7 +5328,7 @@ pretty_otp5085_msg7() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
{'ErrorDescriptor',504,asn1_NOVALUE},
#'ContextRequest'{priority = 7},
@@ -5023,29 +5404,29 @@ pretty_otp5600(Expected, Msg, Conf) ->
pretty_otp5600_msg1() ->
SRE = #'SecondRequestedEvent'{ pkgdName = "al/on",
evParList = [] },
-
+
SED = #'SecondEventsDescriptor'{ requestID = 2,
eventList = [ SRE ] },
-
+
SIG = { signal, #'Signal'{ signalName = "cg/dt",
sigParList = [] } },
-
+
RA = #'RequestedActions'{ secondEvent = SED,
signalsDescriptor = [ SIG ] },
-
+
RE = #'RequestedEvent'{ pkgdName = "al/of",
eventAction = RA,
evParList = [] },
-
+
EV = #'EventsDescriptor'{ requestID = 1, eventList = [ RE ] },
-
+
TermID = {megaco_term_id, true, [[$*]] },
-
+
AMMR = #'AmmRequest'{ terminationID = [ TermID ],
descriptors = [ { eventsDescriptor, EV } ] },
-
+
CR = #'CommandRequest'{command = {modReq, AMMR}},
-
+
AR = #'ActionRequest'{contextId = ?megaco_null_context_id,
commandRequests = [CR]},
ARs = [AR],
@@ -5055,12 +5436,12 @@ pretty_otp5600_msg1() ->
mId = ?MGC_MID,
messageBody = {transactions, TRs}},
#'MegacoMessage'{mess = Mess}.
-
-
+
+
pretty_otp5600_msg2() ->
SIG = { signal, #'Signal'{ signalName = "cg/dt",
sigParList = [] } },
-
+
SRA = #'SecondRequestedActions'{ signalsDescriptor = [ SIG ] },
SRE = #'SecondRequestedEvent'{ pkgdName = "al/on",
@@ -5069,22 +5450,22 @@ pretty_otp5600_msg2() ->
SED = #'SecondEventsDescriptor'{ requestID = 2,
eventList = [ SRE ] },
-
+
RA = #'RequestedActions'{ secondEvent = SED },
-
+
RE = #'RequestedEvent'{ pkgdName = "al/of",
eventAction = RA,
evParList = [] },
-
+
EV = #'EventsDescriptor'{ requestID = 1, eventList = [ RE ] },
-
+
TermID = {megaco_term_id, true, [[$*]] },
-
+
AMMR = #'AmmRequest'{ terminationID = [ TermID ],
descriptors = [ { eventsDescriptor, EV } ] },
-
+
CR = #'CommandRequest'{command = {modReq, AMMR}},
-
+
AR = #'ActionRequest'{contextId = ?megaco_null_context_id,
commandRequests = [CR]},
ARs = [AR],
@@ -5094,7 +5475,7 @@ pretty_otp5600_msg2() ->
mId = ?MGC_MID,
messageBody = {transactions, TRs}},
#'MegacoMessage'{mess = Mess}.
-
+
pretty_otp5601_msg1(suite) ->
[];
@@ -5238,10 +5619,10 @@ pretty_otp5882_msg01(Config) when is_list(Config) ->
%% put(dbg,true),
pretty_otp5882().
-pretty_otp5882() ->
+pretty_otp5882() ->
otp5882(megaco_pretty_text_encoder, []).
-otp5882(Codec, Conf) ->
+otp5882(Codec, Conf) ->
Msg = pretty_otp5882_msg01(),
case (catch encode_message(Codec, Conf, Msg)) of
{error, {message_encode_failed, {error, {ActualReason, _}}, _}} ->
@@ -5256,7 +5637,7 @@ otp5882(Codec, Conf) ->
{ok, Bin} ->
exit({unexpected_encode_sucess, binary_to_list(Bin)})
end.
-
+
pretty_otp5882_msg01() ->
LCD = #'LocalControlDescriptor'{}, % Create illegal LCD
Parms = ?MSG_LIB:cre_StreamParms(LCD),
@@ -5275,10 +5656,10 @@ pretty_otp5882_msg01() ->
Mid = ?MG1_MID,
Mess = ?MSG_LIB:cre_Message(?VERSION, Mid, [Trans]),
?MSG_LIB:cre_MegacoMessage(Mess).
-
+
%% --------------------------------------------------------------
-%%
+%%
pretty_otp6490_msg01(suite) ->
[];
pretty_otp6490_msg01(Config) when is_list(Config) ->
@@ -5290,7 +5671,7 @@ pretty_otp6490_msg01(Config) when is_list(Config) ->
%% erase(dbg),
%% erase(severity),
ok.
-
+
pretty_otp6490_msg02(suite) ->
[];
pretty_otp6490_msg02(Config) when is_list(Config) ->
@@ -5302,7 +5683,7 @@ pretty_otp6490_msg02(Config) when is_list(Config) ->
%% erase(severity),
%% erase(dbg),
ok.
-
+
pretty_otp6490_msg03(suite) ->
[];
pretty_otp6490_msg03(Config) when is_list(Config) ->
@@ -5314,7 +5695,7 @@ pretty_otp6490_msg03(Config) when is_list(Config) ->
%% erase(severity),
%% erase(dbg),
ok.
-
+
pretty_otp6490_msg04(suite) ->
[];
pretty_otp6490_msg04(Config) when is_list(Config) ->
@@ -5326,7 +5707,7 @@ pretty_otp6490_msg04(Config) when is_list(Config) ->
%% erase(severity),
%% erase(dbg),
ok.
-
+
pretty_otp6490_msg05(suite) ->
[];
pretty_otp6490_msg05(Config) when is_list(Config) ->
@@ -5338,7 +5719,7 @@ pretty_otp6490_msg05(Config) when is_list(Config) ->
%% erase(severity),
%% erase(dbg),
ok.
-
+
pretty_otp6490_msg06(suite) ->
[];
pretty_otp6490_msg06(Config) when is_list(Config) ->
@@ -5350,7 +5731,7 @@ pretty_otp6490_msg06(Config) when is_list(Config) ->
%% erase(severity),
%% erase(dbg),
ok.
-
+
pretty_otp6490(Msg, Conf) ->
pretty_otp6490(Msg, Conf, ok).
@@ -5358,10 +5739,10 @@ pretty_otp6490(Msg, Conf, ExpectedEncode) ->
pretty_otp6490(Msg, Conf, ExpectedEncode, ok).
pretty_otp6490(Msg, Conf, ExpectedEncode, ExpectedDecode) ->
- otp6490(Msg, megaco_pretty_text_encoder, Conf,
+ otp6490(Msg, megaco_pretty_text_encoder, Conf,
ExpectedEncode, ExpectedDecode).
-otp6490(Msg, Codec, Conf, ExpectedEncode, ExpectedDecode) ->
+otp6490(Msg, Codec, Conf, ExpectedEncode, ExpectedDecode) ->
case (catch encode_message(Codec, Conf, Msg)) of
{error, _Reason} when ExpectedEncode == error ->
ok;
@@ -5385,7 +5766,7 @@ otp6490(Msg, Codec, Conf, ExpectedEncode, ExpectedDecode) ->
exit({unexpected_decode_failure, Msg, Reason})
end
end.
-
+
pretty_otp6490_msg(EBD) ->
AmmDesc = ?MSG_LIB:cre_AmmDescriptor(EBD),
@@ -5405,16 +5786,16 @@ pretty_otp6490_msg01() ->
EvSpecs = [], % This will result in an error
EBD = EvSpecs, % This is because the lib checks that the size is valid
pretty_otp6490_msg(EBD).
-
+
pretty_otp6490_msg02() ->
EvPar = ?MSG_LIB:cre_EventParameter("sune", ["mangs"]),
PkgdName = ?MSG_LIB:cre_PkgdName("foo", "a"),
EvName = ?MSG_LIB:cre_EventName(PkgdName),
EvSpec = ?MSG_LIB:cre_EventSpec(EvName, [EvPar]),
- EvSpecs = [EvSpec],
+ EvSpecs = [EvSpec],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
-
+
pretty_otp6490_msg03() ->
EvPar1 = ?MSG_LIB:cre_EventParameter("sune", ["mangs"]),
EvPar2 = ?MSG_LIB:cre_EventParameter("kalle", ["anka"]),
@@ -5422,10 +5803,10 @@ pretty_otp6490_msg03() ->
PkgdName = ?MSG_LIB:cre_PkgdName("foo", "a"),
EvName = ?MSG_LIB:cre_EventName(PkgdName),
EvSpec = ?MSG_LIB:cre_EventSpec(EvName, [EvPar1,EvPar2,EvPar3]),
- EvSpecs = [EvSpec],
+ EvSpecs = [EvSpec],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
-
+
pretty_otp6490_msg04() ->
EvPar1 = ?MSG_LIB:cre_EventParameter("sune", ["mangs"]),
EvPar2 = ?MSG_LIB:cre_EventParameter("kalle", ["anka"]),
@@ -5437,31 +5818,31 @@ pretty_otp6490_msg04() ->
PkgdName2 = ?MSG_LIB:cre_PkgdName("bar", "b"),
EvName2 = ?MSG_LIB:cre_EventName(PkgdName2),
EvSpec2 = ?MSG_LIB:cre_EventSpec(EvName2, [EvPar4]),
- EvSpecs = [EvSpec1,EvSpec2],
+ EvSpecs = [EvSpec1,EvSpec2],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
-
+
pretty_otp6490_msg05() ->
EvPar = ?MSG_LIB:cre_EventParameter("sune", ["mangs"]),
PkgdName = ?MSG_LIB:cre_PkgdName("foo", root),
EvName = ?MSG_LIB:cre_EventName(PkgdName),
EvSpec = ?MSG_LIB:cre_EventSpec(EvName, [EvPar]),
- EvSpecs = [EvSpec],
+ EvSpecs = [EvSpec],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
-
+
pretty_otp6490_msg06() ->
EvPar = ?MSG_LIB:cre_EventParameter("sune", ["mangs"]),
PkgdName = ?MSG_LIB:cre_PkgdName(root, root),
EvName = ?MSG_LIB:cre_EventName(PkgdName),
EvSpec = ?MSG_LIB:cre_EventSpec(EvName, [EvPar]),
- EvSpecs = [EvSpec],
+ EvSpecs = [EvSpec],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
-
+
%% --------------------------------------------------------------
-%%
+%%
pretty_otp7249_msg01(suite) ->
[];
pretty_otp7249_msg01(doc) ->
@@ -5478,15 +5859,15 @@ pretty_otp7249_msg01(Config) when is_list(Config) ->
%% erase(dbg),
%% erase(severity),
ok.
-
+
pretty_otp7249_msg01() ->
"MEGACO/2 <AGW95_DCT_2_DPNSS>\r\nTransaction = 500017 { \r\nContext = - { ServiceChange = ROOT { Services { \r\nMethod = Disconnected, Reason = 900, 20070116T15233997 } \r\n } } } \r\n".
pretty_otp7249(EncodedMsg) ->
- Codec = megaco_pretty_text_encoder,
- Conf = [],
- Bin = list_to_binary(EncodedMsg),
+ Codec = megaco_pretty_text_encoder,
+ Conf = [],
+ Bin = list_to_binary(EncodedMsg),
case decode_mini_message(Codec, Conf, Bin) of
{ok, Msg} when is_record(Msg, 'MegacoMessage') ->
%% io:format("Msg: ~n~p"
@@ -5577,12 +5958,12 @@ pretty_otp7671(Msg, Conf, ExpectedEncode, ExpectedDecode, Check) ->
ExpectedEncode, ExpectedDecode, Check).
otp7671(Msg, Codec, Conf, ExpectedEncode, ExpectedDecode) ->
- Check = fun(M1, M2) ->
+ Check = fun(M1, M2) ->
exit({unexpected_decode_result, M1, M2})
end,
otp7671(Msg, Codec, Conf, ExpectedEncode, ExpectedDecode, Check).
-otp7671(Msg, Codec, Conf, ExpectedEncode, ExpectedDecode, Check)
+otp7671(Msg, Codec, Conf, ExpectedEncode, ExpectedDecode, Check)
when is_function(Check) ->
case (catch encode_message(Codec, Conf, Msg)) of
{error, _Reason} when ExpectedEncode =:= error ->
@@ -5655,9 +6036,9 @@ pretty_otp7671_msg05() ->
asn1_NOVALUE}}}]}},
asn1_NOVALUE,asn1_NOVALUE}]}]}}]}}}.
-cmp_otp7671_msg05(#'MegacoMessage'{authHeader = asn1_NOVALUE,
- mess = M1},
- #'MegacoMessage'{authHeader = asn1_NOVALUE,
+cmp_otp7671_msg05(#'MegacoMessage'{authHeader = asn1_NOVALUE,
+ mess = M1},
+ #'MegacoMessage'{authHeader = asn1_NOVALUE,
mess = M2}) ->
#'Message'{messageBody = Body1} = M1,
#'Message'{messageBody = Body2} = M2,
@@ -5673,11 +6054,11 @@ cmp_otp7671_msg05(#'MegacoMessage'{authHeader = asn1_NOVALUE,
[#'CommandRequest'{command = Cmd2}] = CR2,
{modReq, #'AmmRequest'{descriptors = Descs1}} = Cmd1,
{modReq, #'AmmRequest'{descriptors = Descs2}} = Cmd2,
- [{digitMapDescriptor,
- #'DigitMapDescriptor'{digitMapName = Name,
+ [{digitMapDescriptor,
+ #'DigitMapDescriptor'{digitMapName = Name,
digitMapValue = Value1}}] = Descs1,
- [{digitMapDescriptor,
- #'DigitMapDescriptor'{digitMapName = Name,
+ [{digitMapDescriptor,
+ #'DigitMapDescriptor'{digitMapName = Name,
digitMapValue = Value2}}] = Descs2,
#'DigitMapValue'{startTimer = asn1_NOVALUE,
shortTimer = asn1_NOVALUE,
@@ -5686,7 +6067,7 @@ cmp_otp7671_msg05(#'MegacoMessage'{authHeader = asn1_NOVALUE,
durationTimer = asn1_NOVALUE} = Value1,
asn1_NOVALUE = Value2,
ok.
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -5728,7 +6109,7 @@ expect_codec(Expect, Codec, Msg, Conf) ->
exit({unexpected_encode_result, Else})
end.
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
msgs() ->
@@ -5736,83 +6117,83 @@ msgs() ->
[M || {_, M, _, _} <- Msgs].
msgs1() ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
- EC, M)
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ EC, M)
end,
[
- {msg01a, msg1a(), Plain, [{dbg,false}]},
+ {msg01a, msg1a(), Plain, [{dbg,false}]},
{msg01b, msg1b(), Plain, [{dbg,false}]},
- {msg02, msg2(), Plain, [{dbg,false}]},
- {msg03, msg3(), Plain, [{dbg,false}]},
- {msg04, msg4(), Plain, [{dbg,false}]},
- {msg05, msg5(), Plain, [{dbg,false}]},
- {msg06a, msg6a(), Plain, [{dbg,false}]},
- {msg06b, msg6b(), Plain, [{dbg,false}]},
- {msg07, msg7(), Plain, [{dbg,false}]},
- {msg08a, msg8a(), Plain, [{dbg,false}]},
- {msg08b, msg8b(), Plain, [{dbg,false}]},
- {msg09, msg9(), Plain, [{dbg,false}]},
- {msg10, msg10(), Plain, [{dbg,false}]},
- {msg11, msg11(), Plain, [{dbg,false}]},
- {msg12, msg12(), Plain, [{dbg,false}]},
- {msg13, msg13(), Plain, [{dbg,false}]},
- {msg14, msg14(), Plain, [{dbg,false}]},
- {msg15, msg15(), Plain, [{dbg,false}]},
- {msg16, msg16(), Plain, [{dbg,false}]},
- {msg17, msg17(), Plain, [{dbg,false}]},
- {msg18, msg18(), Plain, [{dbg,false}]},
- {msg19, msg19(), Plain, [{dbg,false}]},
- {msg20, msg20(), Plain, [{dbg,false}]},
- {msg21, msg21(), Plain, [{dbg,false}]},
- {msg22a, msg22a(), Plain, [{dbg,false}]},
- {msg22b, msg22b(), Plain, [{dbg,false}]},
- {msg22c, msg22c(), Plain, [{dbg,false}]},
- {msg22d, msg22d(), Plain, [{dbg,false}]},
- {msg22e, msg22e(), Plain, [{dbg,false}]},
- {msg22f, msg22f(), Plain, [{dbg,false}]},
- {msg23a, msg23a(), Plain, [{dbg,false}]},
- {msg23b, msg23b(), Plain, [{dbg,false}]},
- {msg23c, msg23c(), Plain, [{dbg,false}]},
- {msg23d, msg23d(), Plain, [{dbg,false}]},
- {msg24, msg24(), Plain, [{dbg,false}]},
- {msg25, msg25(), Plain, [{dbg,false}]},
- {msg30a, msg30a(), Plain, [{dbg,false}]},
- {msg30b, msg30b(), Plain, [{dbg,false}]},
- {msg30c, msg30c(), Plain, [{dbg,false}]},
+ {msg02, msg2(), Plain, [{dbg,false}]},
+ {msg03, msg3(), Plain, [{dbg,false}]},
+ {msg04, msg4(), Plain, [{dbg,false}]},
+ {msg05, msg5(), Plain, [{dbg,false}]},
+ {msg06a, msg6a(), Plain, [{dbg,false}]},
+ {msg06b, msg6b(), Plain, [{dbg,false}]},
+ {msg07, msg7(), Plain, [{dbg,false}]},
+ {msg08a, msg8a(), Plain, [{dbg,false}]},
+ {msg08b, msg8b(), Plain, [{dbg,false}]},
+ {msg09, msg9(), Plain, [{dbg,false}]},
+ {msg10, msg10(), Plain, [{dbg,false}]},
+ {msg11, msg11(), Plain, [{dbg,false}]},
+ {msg12, msg12(), Plain, [{dbg,false}]},
+ {msg13, msg13(), Plain, [{dbg,false}]},
+ {msg14, msg14(), Plain, [{dbg,false}]},
+ {msg15, msg15(), Plain, [{dbg,false}]},
+ {msg16, msg16(), Plain, [{dbg,false}]},
+ {msg17, msg17(), Plain, [{dbg,false}]},
+ {msg18, msg18(), Plain, [{dbg,false}]},
+ {msg19, msg19(), Plain, [{dbg,false}]},
+ {msg20, msg20(), Plain, [{dbg,false}]},
+ {msg21, msg21(), Plain, [{dbg,false}]},
+ {msg22a, msg22a(), Plain, [{dbg,false}]},
+ {msg22b, msg22b(), Plain, [{dbg,false}]},
+ {msg22c, msg22c(), Plain, [{dbg,false}]},
+ {msg22d, msg22d(), Plain, [{dbg,false}]},
+ {msg22e, msg22e(), Plain, [{dbg,false}]},
+ {msg22f, msg22f(), Plain, [{dbg,false}]},
+ {msg23a, msg23a(), Plain, [{dbg,false}]},
+ {msg23b, msg23b(), Plain, [{dbg,false}]},
+ {msg23c, msg23c(), Plain, [{dbg,false}]},
+ {msg23d, msg23d(), Plain, [{dbg,false}]},
+ {msg24, msg24(), Plain, [{dbg,false}]},
+ {msg25, msg25(), Plain, [{dbg,false}]},
+ {msg30a, msg30a(), Plain, [{dbg,false}]},
+ {msg30b, msg30b(), Plain, [{dbg,false}]},
+ {msg30c, msg30c(), Plain, [{dbg,false}]},
{msg30d, msg30d(), Plain, [{dbg,false}]}
].
msgs2() ->
- TransFirst =
+ TransFirst =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:trans_first_encode_decode(Codec, DD,
- Ver, EC, M)
+ megaco_codec_test_lib:trans_first_encode_decode(Codec, DD,
+ Ver, EC, M)
end,
- ActionsFirst =
+ ActionsFirst =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:actions_first_encode_decode(Codec, DD,
- Ver, EC, M)
+ megaco_codec_test_lib:actions_first_encode_decode(Codec, DD,
+ Ver, EC, M)
end,
- ActionFirst =
+ ActionFirst =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:action_first_encode_decode(Codec, DD,
- Ver, EC, M)
+ megaco_codec_test_lib:action_first_encode_decode(Codec, DD,
+ Ver, EC, M)
end,
[
- {msg01a_tf, msg1a(), TransFirst, [{dbg,false}]},
- {msg02_tf, msg2(), TransFirst, [{dbg,false}]},
- {msg10_tf, msg10(), TransFirst, [{dbg,false}]},
- {msg11_tf, msg11(), TransFirst, [{dbg,false}]},
- {msg23d_tf, msg23d(), TransFirst, [{dbg,false}]},
- {msg30b_tf, msg30b(), TransFirst, [{dbg,false}]},
- {msg30c_tf, msg30c(), TransFirst, [{dbg,false}]},
- {msg01a_asf, msg1a(), ActionsFirst, [{dbg,false}]},
- {msg02_asf, msg2(), ActionsFirst, [{dbg,false}]},
- {msg10_asf, msg10(), ActionsFirst, [{dbg,false}]},
- {msg23d_asf, msg23d(), ActionsFirst, [{dbg,false}]},
+ {msg01a_tf, msg1a(), TransFirst, [{dbg,false}]},
+ {msg02_tf, msg2(), TransFirst, [{dbg,false}]},
+ {msg10_tf, msg10(), TransFirst, [{dbg,false}]},
+ {msg11_tf, msg11(), TransFirst, [{dbg,false}]},
+ {msg23d_tf, msg23d(), TransFirst, [{dbg,false}]},
+ {msg30b_tf, msg30b(), TransFirst, [{dbg,false}]},
+ {msg30c_tf, msg30c(), TransFirst, [{dbg,false}]},
+ {msg01a_asf, msg1a(), ActionsFirst, [{dbg,false}]},
+ {msg02_asf, msg2(), ActionsFirst, [{dbg,false}]},
+ {msg10_asf, msg10(), ActionsFirst, [{dbg,false}]},
+ {msg23d_asf, msg23d(), ActionsFirst, [{dbg,false}]},
{msg01a_af, msg1a(), ActionFirst, [{dbg,false}]},
{msg02_af, msg2(), ActionFirst, [{dbg,false}]},
{msg10_af, msg10(), ActionFirst, [{dbg,false}]},
@@ -5821,10 +6202,10 @@ msgs2() ->
msgs3() ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
- EC, M)
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ EC, M)
end,
[{msgs3_name(Name), rfc3525_decode(M), Plain, [{dbg, false}]} ||
{Name, M} <- rfc3525_msgs()].
@@ -5844,10 +6225,10 @@ rfc3525_decode(M) when is_binary(M) ->
msgs4() ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
- EC, M)
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ EC, M)
end,
[
{msg51a, msg51a(), Plain, [{dbg, false}]},
@@ -5870,19 +6251,19 @@ msgs4() ->
{msg58a, msg58a(), Plain, [{dbg, false}]},
{msg58b, msg58b(), Plain, [{dbg, false}]}
].
-
-
+
+
msgs5() ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
EC, M)
- end,
+ end,
- PlainEDFail =
+ PlainEDFail =
fun(Codec, DD, Ver, EC, M) ->
- Res =
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ Res =
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
EC, M),
case Res of
{error, {message_encode_failed, Reason, _M}} ->
@@ -5896,11 +6277,11 @@ msgs5() ->
Res
end
end,
-
- PlainDE =
+
+ PlainDE =
fun(Codec, _DD, Ver, EC, B) ->
- Res =
- megaco_codec_test_lib:decode_message(Codec, false, Ver,
+ Res =
+ megaco_codec_test_lib:decode_message(Codec, false, Ver,
EC, B),
case Res of
{ok, M} ->
@@ -5919,7 +6300,7 @@ msgs5() ->
Res
end
end,
-
+
[
{msg61a, msg61a(), Plain, [{dbg, false}]},
{msg61b, msg61b(), Plain, [{dbg, false}]},
@@ -5927,8 +6308,8 @@ msgs5() ->
{msg62a, msg62a(), PlainEDFail, [{dbg, false}]},
{msg62b, msg62b(), PlainDE, [{dbg, false}]}
].
-
-
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
msg_actions([], Actions) ->
@@ -5942,7 +6323,7 @@ megaco_trans_req([], Transactions) ->
megaco_trans_req([{TransId, ActionInfo}|TransInfo], Transactions) ->
Actions = msg_actions(ActionInfo, []),
TR = ?MSG_LIB:cre_TransactionRequest(TransId, Actions),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
megaco_trans_req(TransInfo, [Trans|Transactions]).
megaco_message(Version, Mid, Body) ->
@@ -5957,7 +6338,7 @@ msg_request(Mid, TransId, ContextId, CmdReq) ->
Action = ?MSG_LIB:cre_ActionRequest(ContextId, CmdReq),
Actions = [Action],
TR = ?MSG_LIB:cre_TransactionRequest(TransId, Actions),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, Mid, [Trans]),
?MSG_LIB:cre_MegacoMessage(Mess).
@@ -5965,15 +6346,15 @@ msg_request(Auth, Mid, TransId, ContextId, CmdReq) ->
Action = ?MSG_LIB:cre_ActionRequest(ContextId, CmdReq),
Actions = [Action],
TR = ?MSG_LIB:cre_TransactionRequest(TransId, Actions),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, Mid, [Trans]),
?MSG_LIB:cre_MegacoMessage(Auth, Mess).
msg_reply(Mid, TransId, ContextId, CmdReply) ->
Action = cre_ActRep(ContextId, CmdReply),
- Actions = [Action],
+ Actions = [Action],
TR = cre_TransRep(TransId, Actions),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, Mid, [Trans]),
?MSG_LIB:cre_MegacoMessage(Mess).
@@ -6000,7 +6381,7 @@ make_tra(Ranges) ->
msg1(Mid, Tid) ->
Gain = cre_PropParm("tdmc/gain", "2"),
- Ec = cre_PropParm("tdmc/ec", "g165"),
+ Ec = cre_PropParm("tdmc/ec", "g165"),
LCD = cre_LocalControlDesc(sendRecv,[Gain, Ec]),
V = cre_PropParm("v", "0"),
%% C = cre_PropParm("c", "IN IP4 $ "),
@@ -6039,7 +6420,7 @@ msg2(Mid) ->
msg2(Mid, ?A4444).
msg2(Mid, Tid) ->
Gain = cre_PropParm("tdmc/gain", "2"),
- Ec = cre_PropParm("tdmc/ec", "g165"),
+ Ec = cre_PropParm("tdmc/ec", "g165"),
LCD = cre_LocalControlDesc(sendRecv,[Gain, Ec]),
V = cre_PropParm("v", "0"),
%% C = cre_PropParm("c", "IN IP4 $ "),
@@ -6276,7 +6657,7 @@ msg14() ->
msg14(?MGC_MID).
msg14(Mid) ->
%% Cmd 1)
- Signal = cre_Sig("cg/rt"),
+ Signal = cre_Sig("cg/rt"),
AmmReq1 = cre_AmmReq([#megaco_term_id{id = ?A4444}],
[{signalsDescriptor, [{signal, Signal}]}]),
CmdReq1 = cre_CmdReq({modReq, AmmReq1}),
@@ -6443,11 +6824,11 @@ msg22(Mid, N) ->
Audits = [{mediaDescriptor, Media},
{packagesDescriptor, [PackagesItem, PackagesItem2]},
{statisticsDescriptor, Statistics}],
- Reply = {auditResult,
+ Reply = {auditResult,
cre_AuditRes(#megaco_term_id{id = ?A5556},Audits)},
- msg_reply(Mid, 50007, ?megaco_null_context_id,
+ msg_reply(Mid, 50007, ?megaco_null_context_id,
lists:duplicate(N,{auditValueReply, Reply})).
-%% msg_reply(Mid, 50007, ?megaco_null_context_id,
+%% msg_reply(Mid, 50007, ?megaco_null_context_id,
%% lists.duplicate([{auditValueReply, Reply}]).
@@ -6548,7 +6929,7 @@ msg25(Mid) ->
Stats2 = [Stat21, Stat22, Stat23, Stat24, Stat25, Stat26, Stat27],
Reply2 = cre_AmmsReply([#megaco_term_id{id = ?A5556}],
[{statisticsDescriptor, Stats2}]),
- msg_reply(Mid, 50009, 5000,
+ msg_reply(Mid, 50009, 5000,
[{subtractReply, Reply1}, {subtractReply, Reply2}]).
@@ -6559,7 +6940,7 @@ msg30b() ->
msg_ack(?MG2_MID, [{9,13}]).
msg30c() ->
- msg_ack(?MG2_MID,
+ msg_ack(?MG2_MID,
[{9,13}, {15,15}, {33,40}, {50,60}, {70,80}, {85,90},
{101,105},{109,119},{121,130},{140,160},{170,175},{180,189},
{201,205},{209,219},{221,230},{240,260},{270,275},{280,289},
@@ -6571,7 +6952,7 @@ msg30c() ->
%% Don't think this will be used by the megaco stack, but since it
%% seem's to be a valid construction...
msg30d() ->
- msg_ack(?MG2_MID,
+ msg_ack(?MG2_MID,
[[{9,13}, {15,15}, {33,40}, {50,60}, {70,80}, {85,90}],
[{101,105},{109,119},{121,130},{140,160},{170,175},{180,189}],
[{201,205},{209,219},{221,230},{240,260},{270,275},{280,289}],
@@ -6581,7 +6962,7 @@ msg30d() ->
]).
-
+
msg40() ->
msg40(?MG1_MID_NO_PORT, "901 mg col boot").
msg40(Mid, Reason) when is_list(Reason) ->
@@ -6599,14 +6980,14 @@ msg50(Mid, APT) ->
Req = cre_AuditReq(#megaco_term_id{id = ?A5556},AD),
CmdReq = cre_CmdReq({auditValueRequest, Req}),
msg_request(Mid, 50007, ?megaco_null_context_id, [CmdReq]).
-
+
%% IndAudMediaDescriptor:
msg51(Mid, IATSDorStream) ->
IAMD = cre_IndAudMediaDesc(IATSDorStream),
IAP = cre_IndAudParam(IAMD),
APT = [IAP],
msg50(Mid, APT).
-
+
msg51a() ->
msg51a(?MG2_MID).
msg51a(Mid) ->
@@ -6638,7 +7019,7 @@ msg51d(Mid) ->
msg51e() ->
msg51e(?MG2_MID).
msg51e(Mid) ->
- IALCD = cre_IndAudLocalControlDesc('NULL', asn1_NOVALUE,
+ IALCD = cre_IndAudLocalControlDesc('NULL', asn1_NOVALUE,
asn1_NOVALUE, asn1_NOVALUE),
IASP = cre_IndAudStreamParms(IALCD),
msg51(Mid, IASP).
@@ -6646,7 +7027,7 @@ msg51e(Mid) ->
msg51f() ->
msg51f(?MG2_MID).
msg51f(Mid) ->
- IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, 'NULL',
+ IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, 'NULL',
asn1_NOVALUE, asn1_NOVALUE),
IASP = cre_IndAudStreamParms(IALCD),
msg51(Mid, IASP).
@@ -6654,7 +7035,7 @@ msg51f(Mid) ->
msg51g() ->
msg51g(?MG2_MID).
msg51g(Mid) ->
- IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, asn1_NOVALUE,
+ IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, asn1_NOVALUE,
'NULL', asn1_NOVALUE),
IASP = cre_IndAudStreamParms(IALCD),
msg51(Mid, IASP).
@@ -6664,7 +7045,7 @@ msg51h() ->
msg51h(Mid) ->
Name = "nt/jit",
IAPP = cre_IndAudPropertyParm(Name),
- IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, asn1_NOVALUE,
+ IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, asn1_NOVALUE,
asn1_NOVALUE, [IAPP]),
IASP = cre_IndAudStreamParms(IALCD),
SID = 123,
@@ -6679,7 +7060,7 @@ msg51i(Mid) ->
Name2 = "tdmc/ec",
IAPP = cre_IndAudPropertyParm(Name),
IAPP2 = cre_IndAudPropertyParm(Name2),
- IALCD = cre_IndAudLocalControlDesc('NULL', 'NULL', 'NULL',
+ IALCD = cre_IndAudLocalControlDesc('NULL', 'NULL', 'NULL',
[IAPP, IAPP2]),
IASP = cre_IndAudStreamParms(IALCD),
SID = 123,
@@ -6715,7 +7096,7 @@ msg54(Mid, Sig) ->
IAP = cre_IndAudParam(IASD),
APT = [IAP],
msg50(Mid, APT).
-
+
msg54a() ->
msg54a(?MG2_MID).
msg54a(Mid) ->
@@ -6757,7 +7138,7 @@ msg56(Mid) ->
IASD = cre_IndAudStatsDesc(SN),
IAP = cre_IndAudParam(IASD),
APT = [IAP],
- msg50(Mid, APT).
+ msg50(Mid, APT).
%% IndAudPackagesDescriptor:
msg57() ->
@@ -6768,30 +7149,30 @@ msg57(Mid) ->
IAPD = cre_IndAudPkgsDesc(PN, PV),
IAP = cre_IndAudParam(IAPD),
APT = [IAP],
- msg50(Mid, APT).
-
+ msg50(Mid, APT).
+
%% Sum it up:
msg58_iaMediaDesc_iap(IATSD) ->
IAMD = cre_IndAudMediaDesc(IATSD),
cre_IndAudParam(IAMD).
-
+
msg58_iaMediaDesc_iap_a() ->
PP = cre_IndAudPropertyParm("tdmc/gain"),
PPs = [PP],
IATSD = cre_IndAudTermStateDesc(PPs),
msg58_iaMediaDesc_iap(IATSD).
-
+
msg58_iaMediaDesc_iap_b() ->
IATSD = cre_IndAudTermStateDesc([], 'NULL', asn1_NOVALUE),
msg58_iaMediaDesc_iap(IATSD).
-
+
msg58_iaEvsDesc_iap() ->
RequestID = 1235,
PkgdName = "tonedet/std",
IAED = cre_IndAudEvsDesc(RequestID, PkgdName),
cre_IndAudParam(IAED).
-msg58_iaEvBufDesc_iap() ->
+msg58_iaEvBufDesc_iap() ->
EN = "tonedet/std",
SID = 1,
IAEBD = cre_IndAudEvBufDesc(EN, SID),
@@ -6805,7 +7186,7 @@ msg58_iaSigsDesc_iap_a() ->
SN = "tonegen/pt",
Sig = cre_IndAudSig(SN),
msg58_iaSigsDesc_iap(Sig).
-
+
msg58_iaSigsDesc_iap_b() ->
SN = "ct/ct",
Sig = cre_IndAudSig(SN),
@@ -6817,7 +7198,7 @@ msg58_iaDigMapDesc_iap() ->
DMN = "dialplan00",
IADMD = cre_IndAudDigitMapDesc(DMN),
cre_IndAudParam(IADMD).
-
+
msg58_iaStatsDesc_iap() ->
SN = "nt/dur",
IASD = cre_IndAudStatsDesc(SN),
@@ -6856,7 +7237,7 @@ msg58b(Mid) ->
msg50(Mid, APT).
-%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
%% Tests some of the changes in the v2 corr 1 (EmergencyOff and ModemDesc)
%% Emergency On/Off (optional) tests
@@ -6871,7 +7252,7 @@ msg61(EM) ->
ActReq = ?MSG_LIB:cre_ActionRequest(1, CtxReq, [CmdReq]),
Acts = [ActReq],
TR = ?MSG_LIB:cre_TransactionRequest(9898, Acts),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, ?MG1_MID, [Trans]),
?MSG_LIB:cre_MegacoMessage(Mess).
@@ -6897,12 +7278,12 @@ msg62a() ->
ActReq = ?MSG_LIB:cre_ActionRequest(2, [CmdReq]),
Acts = [ActReq],
TR = ?MSG_LIB:cre_TransactionRequest(9898, Acts),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, ?MG1_MID, [Trans]),
?MSG_LIB:cre_MegacoMessage(Mess).
msg62b() ->
- MP =
+ MP =
"MEGACO/" ?VERSION_STR " [124.124.124.222]:55555
Transaction = 9898 {
Context = 2 {
@@ -6913,11 +7294,11 @@ Transaction = 9898 {
}
}
}",
-% MC =
+% MC =
% "!/" ?VERSION_STR " [124.124.124.222]:55555\nT=9898{C=2{A=11111111/00000000/00000000{MD[V18]{tdmc/gain=2}}}}",
list_to_binary(MP).
-%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
%% Pretty RFC 3525 messages:
%% Added Reason
@@ -6928,11 +7309,11 @@ rfc3525_msg1() ->
Services {
Method = Restart,
Reason = 901,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
rfc3525_msg2() ->
@@ -6940,11 +7321,11 @@ rfc3525_msg2() ->
Context = - {
ServiceChange = ROOT {
Services {
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
- }
- }
- }
+ }
+ }
+ }
}".
@@ -6953,7 +7334,7 @@ rfc3525_msg3() ->
"MEGACO/" ?VERSION_STR " [123.123.123.4]:55555 Transaction = 9999 {
Context = - {
Modify = A4444 {
- Media {
+ Media {
Stream = 1 {
LocalControl {
Mode = SendReceive,
@@ -6985,15 +7366,15 @@ rfc3525_msg6() ->
19990729T22000000:al/of{init=false}
}
}
- }
+ }
}".
-
+
rfc3525_msg7() ->
"MEGACO/" ?VERSION_STR " [123.123.123.4]:55555 Reply = 10000 {
Context = - {
Notify = A4444
- }
+ }
}".
rfc3525_msg8() ->
@@ -7001,11 +7382,11 @@ rfc3525_msg8() ->
Context = - {
Modify = A4444 {
Events = 2223 {
- al/on {strict=state},
+ al/on {strict=state},
dd/ce {DigitMap=Dialplan0}
},
Signals {cg/dt},
- DigitMap = Dialplan0 {
+ DigitMap = Dialplan0 {
(0| 00|[1-7]xxx|8xxxxxxx|fxxxxxxx|exx|91xxxxxxxxxx|9011x.)
}
}
@@ -7030,7 +7411,7 @@ rfc3525_msg10() ->
}
}
}
- }
+ }
}".
@@ -7053,13 +7434,13 @@ rfc3525_msg12() ->
Mode = ReceiveOnly,
nt/jit=40 ; in ms
},
- Local {
+ Local {
v=0 c=IN IP4 $ m=audio $ RTP/AVP 4 a=ptime:30 v=0 c=IN IP4 $ m=audio $ RTP/AVP 0
}
}
}
}
- }
+ }
}".
%% Added ?
@@ -7070,28 +7451,28 @@ rfc3525_msg13() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
-v=0
-o=- 2890844526 2890842807 IN IP4 124.124.124.222
-s=-
-t= 0 0
-c=IN IP4 124.124.124.222
-m=audio 2222 RTP/AVP 4
-a=ptime:30
-a=recvonly
+ Local {
+v=0
+o=- 2890844526 2890842807 IN IP4 124.124.124.222
+s=-
+t= 0 0
+c=IN IP4 124.124.124.222
+m=audio 2222 RTP/AVP 4
+a=ptime:30
+a=recvonly
} ; RTP profile for G.723.1 is 4
}
}
}
- }
+ }
}".
-%%
+%%
%% Added ?
rfc3525_msg14() ->
"MEGACO/" ?VERSION_STR " [123.123.123.4]:55555 Transaction = 50003 {
Context = $ {
- Add = A5555 {
+ Add = A5555 {
Media {
Stream = 1 {
LocalControl {
@@ -7111,16 +7492,16 @@ rfc3525_msg14() ->
Mode = SendReceive,
nt/jit=40 ; in ms
},
- Local {
+ Local {
v=0 c=IN IP4 $ m=audio $ RTP/AVP 4 a=ptime:30
},
- Remote {
+ Remote {
v=0 c=IN IP4 124.124.124.222 m=audio 2222 RTP/AVP 4 a=ptime:30
} ; RTP profile for G.723.1 is 4
}
}
}
- }
+ }
}".
%% Added ?
@@ -7131,10 +7512,10 @@ rfc3525_msg15() ->
Add = A5556 {
Media {
Stream = 1 {
- Local {
- v=0 o=- 7736844526 7736842807 IN IP4 125.125.125.111 s=- t= 0 0 c=IN IP4 125.125.125.111 m=audio 1111 RTP/AVP 4
+ Local {
+ v=0 o=- 7736844526 7736842807 IN IP4 125.125.125.111 s=- t= 0 0 c=IN IP4 125.125.125.111 m=audio 1111 RTP/AVP 4
} ; RTP profile for G723.1 is 4
- }
+ }
}
}
}
@@ -7150,10 +7531,10 @@ rfc3525_msg16a() ->
Modify = A4445 {
Media {
Stream = 1 {
- Remote {
+ Remote {
v=0 o=- 7736844526 7736842807 IN IP4 125.125.125.111 s=- t= 0 0 c=IN IP4 125.125.125.111 m=audio 1111 RTP/AVP 4
} ; RTP profile for G723.1 is 4
- }
+ }
}
}
}
@@ -7162,7 +7543,7 @@ rfc3525_msg16a() ->
rfc3525_msg16b() ->
"MEGACO/" ?VERSION_STR " [124.124.124.222]:55555 Reply = 10005 {
Context = 2000 {
- Modify = A4444,
+ Modify = A4444,
Modify = A4445
}
}".
@@ -7219,7 +7600,7 @@ rfc3525_msg18a() ->
}
},
Modify = A4444 {
- Signals
+ Signals
}
}
}".
@@ -7227,7 +7608,7 @@ rfc3525_msg18a() ->
rfc3525_msg18b() ->
"MEGACO/" ?VERSION_STR " [124.124.124.222]:55555 Reply = 10006 {
Context = 2000 {
- Modify = A4445,
+ Modify = A4445,
Modify = A4444
}
}".
@@ -7246,22 +7627,22 @@ rfc3525_msg19() ->
%% Added ?
rfc3525_msg20() ->
"MEGACO/" ?VERSION_STR " [125.125.125.111]:55555 Reply = 50007 {
- Context = - {
+ Context = - {
AuditValue = A5556 {
Media {
- TerminationState {
+ TerminationState {
ServiceStates = InService,
- Buffer = OFF
+ Buffer = OFF
},
Stream = 1 {
- LocalControl {
+ LocalControl {
Mode = SendReceive,
- nt/jit=40
+ nt/jit=40
},
- Local {
+ Local {
v=0 o=- 7736844526 7736842807 IN IP4 125.125.125.111 s=- t= 0 0 c=IN IP4 125.125.125.111 m=audio 1111 RTP/AVP 4 a=ptime:30
},
- Remote {
+ Remote {
v=0 o=- 2890844526 2890842807 IN IP4 124.124.124.222 s=- t= 0 0 c=IN IP4 124.124.124.222 m=audio 2222 RTP/AVP 4 a=ptime:30
}
}
@@ -7270,7 +7651,7 @@ rfc3525_msg20() ->
Signals,
DigitMap,
Packages {nt-1, rtp-1},
- Statistics {
+ Statistics {
rtp/ps=1200, ; packets sent
nt/os=62300, ; octets sent
rtp/pr=700, ; packets received
@@ -7278,7 +7659,7 @@ rfc3525_msg20() ->
rtp/pl=0.2, ; % packet loss
rtp/jit=20,
rtp/delay=40 ; avg latency
- }
+ }
}
}
}".
@@ -7431,13 +7812,13 @@ skip(Reason) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
decode_message(Codec, DynamicDecode, Conf, Bin) ->
- megaco_codec_test_lib:decode_message(Codec, DynamicDecode, ?VERSION,
+ megaco_codec_test_lib:decode_message(Codec, DynamicDecode, ?VERSION,
Conf, Bin).
encode_message(Codec, Conf, Msg) ->
megaco_codec_test_lib:encode_message(Codec, ?VERSION, Conf, Msg).
test_msgs(Codec, DynamicDecode, Conf, Msgs) ->
- megaco_codec_test_lib:test_msgs(Codec, DynamicDecode, ?VERSION, Conf,
+ megaco_codec_test_lib:test_msgs(Codec, DynamicDecode, ?VERSION, Conf,
fun chk_MegacoMessage/2, Msgs).
@@ -7463,8 +7844,8 @@ cre_MegacoMessage(V, Mid, Body) ->
?MSG_LIB:cre_MegacoMessage(Mess).
cre_AuthHeader() ->
- SecParmIdx = [239, 205, 171, 137],
- SeqNum = [18, 52, 86, 120],
+ SecParmIdx = [239, 205, 171, 137],
+ SeqNum = [18, 52, 86, 120],
AD = [18, 52, 86, 120, 137, 171, 205, 239, 118, 84, 50, 16],
cre_AuthHeader(SecParmIdx, SeqNum, AD).
@@ -7491,7 +7872,7 @@ cre_TransRep(TransId, Actions) ->
cre_TransAck(First, Last) ->
?MSG_LIB:cre_TransactionAck(First, Last).
-
+
cre_ActReq(CtxId, CmdReqs) ->
?MSG_LIB:cre_ActionRequest(CtxId, CmdReqs).
@@ -7527,7 +7908,7 @@ cre_IndAudTermStateDesc(PP) ->
cre_IndAudTermStateDesc(PP, EBC, SS) ->
?MSG_LIB:cre_IndAudTerminationStateDescriptor(PP, EBC, SS).
-cre_IndAudEvsDesc(RID, PN)
+cre_IndAudEvsDesc(RID, PN)
when is_integer(RID) ->
?MSG_LIB:cre_IndAudEventsDescriptor(RID, PN).
@@ -7562,7 +7943,7 @@ cre_StatsParm(Name, Val) ->
?MSG_LIB:cre_StatisticsParameter(Name, [Val]).
-% Event related
+% Event related
cre_EvParm(Name, Val) ->
?MSG_LIB:cre_EventParameter(Name, Val).
@@ -7788,6 +8169,5 @@ p(_,_,_,_) ->
p(F, A) ->
io:format("*** [~s] ***"
- "~n " ++ F ++ "~n",
+ "~n " ++ F ++ "~n",
[?FTS() | A]).
-
diff --git a/lib/megaco/test/megaco_codec_v3_SUITE.erl b/lib/megaco/test/megaco_codec_v3_SUITE.erl
index 5f4951acce..56a92b5f0a 100644
--- a/lib/megaco/test/megaco_codec_v3_SUITE.erl
+++ b/lib/megaco/test/megaco_codec_v3_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -50,61 +50,61 @@
flex_pretty_finish/1,
flex_pretty_test_msgs/1,
- flex_compact_init/1,
- flex_compact_finish/1,
+ flex_compact_init/1,
+ flex_compact_finish/1,
flex_compact_test_msgs/1,
- flex_compact_dm_timers1/1,
- flex_compact_dm_timers2/1,
- flex_compact_dm_timers3/1,
- flex_compact_dm_timers4/1,
- flex_compact_dm_timers5/1,
- flex_compact_dm_timers6/1,
- flex_compact_dm_timers7/1,
- flex_compact_dm_timers8/1,
+ flex_compact_dm_timers1/1,
+ flex_compact_dm_timers2/1,
+ flex_compact_dm_timers3/1,
+ flex_compact_dm_timers4/1,
+ flex_compact_dm_timers5/1,
+ flex_compact_dm_timers6/1,
+ flex_compact_dm_timers7/1,
+ flex_compact_dm_timers8/1,
bin_test_msgs/1,
- ber_test_msgs/1,
+ ber_test_msgs/1,
per_test_msgs/1,
erl_dist_m_test_msgs/1,
- compact_otp4011_msg1/1,
+ compact_otp4011_msg1/1,
compact_otp4011_msg2/1,
compact_otp4011_msg3/1,
- compact_otp4013_msg1/1,
- compact_otp4085_msg1/1,
- compact_otp4085_msg2/1,
- compact_otp4280_msg1/1,
- compact_otp4299_msg1/1,
- compact_otp4359_msg1/1,
- compact_otp4920_msg0/1,
- compact_otp4920_msg1/1,
- compact_otp4920_msg2/1,
- compact_otp4920_msg3/1,
- compact_otp4920_msg4/1,
- compact_otp4920_msg5/1,
- compact_otp4920_msg6/1,
- compact_otp4920_msg7/1,
- compact_otp4920_msg8/1,
- compact_otp4920_msg9/1,
- compact_otp4920_msg10/1,
- compact_otp4920_msg11/1,
- compact_otp4920_msg12/1,
- compact_otp4920_msg20/1,
- compact_otp4920_msg21/1,
- compact_otp4920_msg22/1,
- compact_otp4920_msg23/1,
- compact_otp4920_msg24/1,
- compact_otp4920_msg25/1,
- compact_otp5186_msg01/1,
- compact_otp5186_msg02/1,
- compact_otp5186_msg03/1,
- compact_otp5186_msg04/1,
- compact_otp5186_msg05/1,
- compact_otp5186_msg06/1,
+ compact_otp4013_msg1/1,
+ compact_otp4085_msg1/1,
+ compact_otp4085_msg2/1,
+ compact_otp4280_msg1/1,
+ compact_otp4299_msg1/1,
+ compact_otp4359_msg1/1,
+ compact_otp4920_msg0/1,
+ compact_otp4920_msg1/1,
+ compact_otp4920_msg2/1,
+ compact_otp4920_msg3/1,
+ compact_otp4920_msg4/1,
+ compact_otp4920_msg5/1,
+ compact_otp4920_msg6/1,
+ compact_otp4920_msg7/1,
+ compact_otp4920_msg8/1,
+ compact_otp4920_msg9/1,
+ compact_otp4920_msg10/1,
+ compact_otp4920_msg11/1,
+ compact_otp4920_msg12/1,
+ compact_otp4920_msg20/1,
+ compact_otp4920_msg21/1,
+ compact_otp4920_msg22/1,
+ compact_otp4920_msg23/1,
+ compact_otp4920_msg24/1,
+ compact_otp4920_msg25/1,
+ compact_otp5186_msg01/1,
+ compact_otp5186_msg02/1,
+ compact_otp5186_msg03/1,
+ compact_otp5186_msg04/1,
+ compact_otp5186_msg05/1,
+ compact_otp5186_msg06/1,
compact_otp5793_msg01/1,
compact_otp5836_msg01/1,
compact_otp5993_msg01/1,
@@ -113,6 +113,32 @@
compact_otp6017_msg01/1,
compact_otp6017_msg02/1,
compact_otp6017_msg03/1,
+ compact_otp16818_msg01/1,
+ compact_otp16818_msg02/1,
+ compact_otp16818_msg03/1,
+ compact_otp16818_msg04/1,
+ compact_otp16818_msg05/1,
+ compact_otp16818_msg06/1,
+ compact_otp16818_msg11/1,
+ compact_otp16818_msg12/1,
+ compact_otp16818_msg13/1,
+ compact_otp16818_msg14/1,
+ compact_otp16818_msg15/1,
+ compact_otp16818_msg16/1,
+ compact_otp16818_msg21/1,
+ compact_otp16818_msg22/1,
+ compact_otp16818_msg23/1,
+ compact_otp16818_msg24/1,
+ compact_otp16818_msg25/1,
+ compact_otp16818_msg26/1,
+ compact_otp16818_msg31/1,
+ compact_otp16818_msg32/1,
+ compact_otp16818_msg33/1,
+ compact_otp16818_msg34/1,
+ compact_otp16818_msg35/1,
+ compact_otp16818_msg36/1,
+ compact_erl1405_msg01/1,
+ compact_erl1405_msg02/1,
flex_compact_otp4299_msg1/1,
flex_compact_otp7431_msg01/1,
@@ -122,32 +148,32 @@
flex_compact_otp7431_msg05/1,
flex_compact_otp7431_msg06/1,
flex_compact_otp7431_msg07/1,
-
- pretty_otp4632_msg1/1,
- pretty_otp4632_msg2/1,
- pretty_otp4632_msg3/1,
- pretty_otp4632_msg4/1,
- pretty_otp4710_msg1/1,
- pretty_otp4710_msg2/1,
- pretty_otp4945_msg1/1,
- pretty_otp4945_msg2/1,
- pretty_otp4945_msg3/1,
- pretty_otp4945_msg4/1,
- pretty_otp4945_msg5/1,
- pretty_otp4945_msg6/1,
- pretty_otp4949_msg1/1,
- pretty_otp4949_msg2/1,
- pretty_otp4949_msg3/1,
- pretty_otp5042_msg1/1,
- pretty_otp5068_msg1/1,
- pretty_otp5085_msg1/1,
- pretty_otp5085_msg2/1,
- pretty_otp5085_msg3/1,
- pretty_otp5085_msg4/1,
- pretty_otp5085_msg5/1,
- pretty_otp5085_msg6/1,
- pretty_otp5085_msg7/1,
- pretty_otp5085_msg8/1,
+
+ pretty_otp4632_msg1/1,
+ pretty_otp4632_msg2/1,
+ pretty_otp4632_msg3/1,
+ pretty_otp4632_msg4/1,
+ pretty_otp4710_msg1/1,
+ pretty_otp4710_msg2/1,
+ pretty_otp4945_msg1/1,
+ pretty_otp4945_msg2/1,
+ pretty_otp4945_msg3/1,
+ pretty_otp4945_msg4/1,
+ pretty_otp4945_msg5/1,
+ pretty_otp4945_msg6/1,
+ pretty_otp4949_msg1/1,
+ pretty_otp4949_msg2/1,
+ pretty_otp4949_msg3/1,
+ pretty_otp5042_msg1/1,
+ pretty_otp5068_msg1/1,
+ pretty_otp5085_msg1/1,
+ pretty_otp5085_msg2/1,
+ pretty_otp5085_msg3/1,
+ pretty_otp5085_msg4/1,
+ pretty_otp5085_msg5/1,
+ pretty_otp5085_msg6/1,
+ pretty_otp5085_msg7/1,
+ pretty_otp5085_msg8/1,
pretty_otp5600_msg1/1,
pretty_otp5600_msg2/1,
pretty_otp5601_msg1/1,
@@ -155,20 +181,22 @@
pretty_otp5803_msg01/1,
pretty_otp5803_msg02/1,
pretty_otp5805_msg01/1,
- pretty_otp5836_msg01/1,
- pretty_otp5882_msg01/1,
- pretty_otp6490_msg01/1,
- pretty_otp6490_msg02/1,
- pretty_otp6490_msg03/1,
- pretty_otp6490_msg04/1,
- pretty_otp6490_msg05/1,
- pretty_otp6490_msg06/1,
+ pretty_otp5836_msg01/1,
+ pretty_otp5882_msg01/1,
+ pretty_otp6490_msg01/1,
+ pretty_otp6490_msg02/1,
+ pretty_otp6490_msg03/1,
+ pretty_otp6490_msg04/1,
+ pretty_otp6490_msg05/1,
+ pretty_otp6490_msg06/1,
pretty_otp7671_msg01/1,
pretty_otp7671_msg02/1,
pretty_otp7671_msg03/1,
pretty_otp7671_msg04/1,
pretty_otp7671_msg05/1,
pretty_otp8114_msg01/1,
+ pretty_erl1405_msg01/1,
+ pretty_erl1405_msg02/1,
flex_pretty_otp5042_msg1/1,
flex_pretty_otp5085_msg1/1,
@@ -194,7 +222,8 @@
flex_pretty_otp7431_msg05/1,
flex_pretty_otp7431_msg06/1,
flex_pretty_otp7431_msg07/1
- ]).
+
+ ]).
-export([display_text_messages/0, generate_text_messages/0]).
@@ -230,18 +259,18 @@
%% Common Test interface functions
%%======================================================================
-suite() ->
+suite() ->
[{ct_hooks, [ts_install_cth]}].
-all() ->
+all() ->
[
- {group, text},
- {group, binary},
+ {group, text},
+ {group, binary},
{group, erl_dist},
{group, tickets}
].
-groups() ->
+groups() ->
[
{text, [], text_cases()},
{binary, [], binary_cases()},
@@ -250,7 +279,7 @@ groups() ->
{compact, [], compact_cases()},
{flex_pretty, [], flex_pretty_cases()},
{flex_compact, [], flex_compact_cases()},
- {bin, [], bin_cases()},
+ {bin, [], bin_cases()},
{ber, [], ber_cases()},
{per, [], per_cases()},
{erl_dist_m, [], erl_dist_m_cases()},
@@ -263,16 +292,16 @@ groups() ->
text_cases() ->
[
- {group, pretty},
+ {group, pretty},
{group, flex_pretty},
- {group, compact},
+ {group, compact},
{group, flex_compact}
].
binary_cases() ->
[
- {group, bin},
- {group, ber},
+ {group, bin},
+ {group, ber},
{group, per}
].
@@ -316,15 +345,15 @@ erl_dist_m_cases() ->
erl_dist_m_test_msgs
].
-flex_compact_cases() ->
+flex_compact_cases() ->
[
- flex_compact_test_msgs,
+ flex_compact_test_msgs,
flex_compact_dm_timers1,
- flex_compact_dm_timers2,
+ flex_compact_dm_timers2,
flex_compact_dm_timers3,
- flex_compact_dm_timers4,
+ flex_compact_dm_timers4,
flex_compact_dm_timers5,
- flex_compact_dm_timers6,
+ flex_compact_dm_timers6,
flex_compact_dm_timers7,
flex_compact_dm_timers8
].
@@ -332,7 +361,7 @@ flex_compact_cases() ->
tickets_cases() ->
[
{group, compact_tickets},
- {group, flex_compact_tickets},
+ {group, flex_compact_tickets},
{group, pretty_tickets},
{group, flex_pretty_tickets}
].
@@ -380,18 +409,44 @@ compact_tickets_cases() ->
compact_otp5993_msg03,
compact_otp6017_msg01,
compact_otp6017_msg02,
- compact_otp6017_msg03
+ compact_otp6017_msg03,
+ compact_otp16818_msg01,
+ compact_otp16818_msg02,
+ compact_otp16818_msg03,
+ compact_otp16818_msg04,
+ compact_otp16818_msg05,
+ compact_otp16818_msg06,
+ compact_otp16818_msg11,
+ compact_otp16818_msg12,
+ compact_otp16818_msg13,
+ compact_otp16818_msg14,
+ compact_otp16818_msg15,
+ compact_otp16818_msg16,
+ compact_otp16818_msg21,
+ compact_otp16818_msg22,
+ compact_otp16818_msg23,
+ compact_otp16818_msg24,
+ compact_otp16818_msg25,
+ compact_otp16818_msg26,
+ compact_otp16818_msg31,
+ compact_otp16818_msg32,
+ compact_otp16818_msg33,
+ compact_otp16818_msg34,
+ compact_otp16818_msg35,
+ compact_otp16818_msg36,
+ compact_erl1405_msg01,
+ compact_erl1405_msg02
].
-flex_compact_tickets_cases() ->
+flex_compact_tickets_cases() ->
[
- flex_compact_otp4299_msg1,
+ flex_compact_otp4299_msg1,
flex_compact_otp7431_msg01,
- flex_compact_otp7431_msg02,
+ flex_compact_otp7431_msg02,
flex_compact_otp7431_msg03,
- flex_compact_otp7431_msg04,
+ flex_compact_otp7431_msg04,
flex_compact_otp7431_msg05,
- flex_compact_otp7431_msg06,
+ flex_compact_otp7431_msg06,
flex_compact_otp7431_msg07
].
@@ -442,34 +497,36 @@ pretty_tickets_cases() ->
pretty_otp7671_msg03,
pretty_otp7671_msg04,
pretty_otp7671_msg05,
- pretty_otp8114_msg01
+ pretty_otp8114_msg01,
+ pretty_erl1405_msg01,
+ pretty_erl1405_msg02
].
flex_pretty_tickets_cases() ->
[
- flex_pretty_otp5042_msg1,
+ flex_pretty_otp5042_msg1,
flex_pretty_otp5085_msg1,
- flex_pretty_otp5085_msg2,
+ flex_pretty_otp5085_msg2,
flex_pretty_otp5085_msg3,
- flex_pretty_otp5085_msg4,
+ flex_pretty_otp5085_msg4,
flex_pretty_otp5085_msg5,
- flex_pretty_otp5085_msg6,
+ flex_pretty_otp5085_msg6,
flex_pretty_otp5085_msg7,
- flex_pretty_otp5085_msg8,
+ flex_pretty_otp5085_msg8,
flex_pretty_otp5600_msg1,
- flex_pretty_otp5600_msg2,
+ flex_pretty_otp5600_msg2,
flex_pretty_otp5601_msg1,
- flex_pretty_otp5793_msg01,
+ flex_pretty_otp5793_msg01,
flex_pretty_otp5803_msg01,
- flex_pretty_otp5803_msg02,
+ flex_pretty_otp5803_msg02,
flex_pretty_otp5805_msg01,
- flex_pretty_otp5836_msg01,
+ flex_pretty_otp5836_msg01,
flex_pretty_otp7431_msg01,
- flex_pretty_otp7431_msg02,
+ flex_pretty_otp7431_msg02,
flex_pretty_otp7431_msg03,
- flex_pretty_otp7431_msg04,
+ flex_pretty_otp7431_msg04,
flex_pretty_otp7431_msg05,
- flex_pretty_otp7431_msg06,
+ flex_pretty_otp7431_msg06,
flex_pretty_otp7431_msg07
].
@@ -527,29 +584,29 @@ end_per_suite(Config0) when is_list(Config0) ->
%% -----
%%
-init_per_group(flex_pretty_tickets = Group, Config) ->
+init_per_group(flex_pretty_tickets = Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
flex_pretty_init(Config);
-init_per_group(flex_compact_tickets = Group, Config) ->
+init_per_group(flex_compact_tickets = Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
flex_compact_init(Config);
-init_per_group(flex_compact = Group, Config) ->
+init_per_group(flex_compact = Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
flex_compact_init(Config);
-init_per_group(flex_pretty = Group, Config) ->
+init_per_group(flex_pretty = Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
flex_pretty_init(Config);
init_per_group(Group, Config) ->
?ANNOUNCE_GROUP_INIT(Group),
Config.
-end_per_group(flex_pretty_tickets = _Group, Config) ->
+end_per_group(flex_pretty_tickets = _Group, Config) ->
flex_pretty_finish(Config);
-end_per_group(flex_compact_tickets = _Group, Config) ->
+end_per_group(flex_compact_tickets = _Group, Config) ->
flex_compact_finish(Config);
-end_per_group(flex_compact = _Group, Config) ->
+end_per_group(flex_compact = _Group, Config) ->
flex_compact_finish(Config);
-end_per_group(flex_pretty = _Group, Config) ->
+end_per_group(flex_pretty = _Group, Config) ->
flex_pretty_finish(Config);
end_per_group(_Group, Config) ->
Config.
@@ -574,15 +631,15 @@ end_per_testcase(Case, Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
display_text_messages() ->
- Msgs =
- msgs7(text) ++
+ Msgs =
+ msgs7(text) ++
msgs8(text),
megaco_codec_test_lib:display_text_messages(?VERSION, ?EC, Msgs).
generate_text_messages() ->
- Msgs =
- msgs7(text) ++
+ Msgs =
+ msgs7(text) ++
msgs8(text),
megaco_codec_test_lib:generate_text_messages(?V3, ?VERSION, ?EC, Msgs).
@@ -593,20 +650,20 @@ pretty_test_msgs(suite) ->
[];
pretty_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
- Msgs =
- msgs1a(text) ++
- msgs1b(text) ++
- msgs3525(text) ++
- msgs5(text) ++
- msgs6(text) ++
- msgs7(text) ++
- msgs8(text),
+ Msgs =
+ msgs1a(text) ++
+ msgs1b(text) ++
+ msgs3525(text) ++
+ msgs5(text) ++
+ msgs6(text) ++
+ msgs7(text) ++
+ msgs8(text),
%% Msgs = msgs1a(text),
%% Msgs = msgs1b(text),
%% Msgs = msgs35525(text),
%% Msgs = msgs5(text),
%% Msgs = msgs6(text),
- %% Msgs = msgs7(text),
+ %% Msgs = msgs7(text),
DynamicDecode = false,
test_msgs(megaco_pretty_text_encoder, DynamicDecode, ?EC, Msgs).
@@ -619,18 +676,18 @@ flex_pretty_init(Config) ->
flex_pretty_finish(Config) ->
flex_finish(Config).
-
+
flex_pretty_test_msgs(suite) ->
[];
flex_pretty_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
- Msgs =
- msgs1a(text) ++
- msgs1b(text) ++
- msgs3525(text) ++
- msgs5(text) ++
- msgs6(text) ++
- msgs7(text) ++
+ Msgs =
+ msgs1a(text) ++
+ msgs1b(text) ++
+ msgs3525(text) ++
+ msgs5(text) ++
+ msgs6(text) ++
+ msgs7(text) ++
msgs8(text),
Conf = flex_scanner_conf(Config),
DynamicDecode = false,
@@ -643,13 +700,13 @@ compact_test_msgs(suite) ->
[];
compact_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
- Msgs =
- msgs1a(text) ++
- msgs1b(text) ++
- msgs3525(text) ++
- msgs5(text) ++
- msgs6(text) ++
- msgs7(text) ++
+ Msgs =
+ msgs1a(text) ++
+ msgs1b(text) ++
+ msgs3525(text) ++
+ msgs5(text) ++
+ msgs6(text) ++
+ msgs7(text) ++
msgs8(text),
%% Msgs = msgs7(text),
DynamicDecode = false,
@@ -663,21 +720,21 @@ flex_compact_init(Config) ->
flex_compact_finish(Config) ->
flex_finish(Config).
-
+
flex_compact_test_msgs(suite) ->
[];
flex_compact_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
- Msgs =
- msgs1a(text) ++
- msgs1b(text) ++
- msgs3525(text) ++
- msgs5(text) ++
- msgs6(text) ++
- msgs7(text) ++
+ Msgs =
+ msgs1a(text) ++
+ msgs1b(text) ++
+ msgs3525(text) ++
+ msgs5(text) ++
+ msgs6(text) ++
+ msgs7(text) ++
msgs8(text),
- Conf = flex_scanner_conf(Config),
+ Conf = flex_scanner_conf(Config),
DynamicDecode = true,
test_msgs(megaco_compact_text_encoder, DynamicDecode, [?EC_V3,Conf], Msgs).
@@ -688,8 +745,8 @@ flex_compact_dm_timers1(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("1", "2", "3"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
- case decode_message(megaco_compact_text_encoder, false,
+ Conf = flex_scanner_conf(Config),
+ case decode_message(megaco_compact_text_encoder, false,
[?EC_V3,Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers1 -> "
@@ -707,8 +764,8 @@ flex_compact_dm_timers2(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("02", "03", "04"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
- case decode_message(megaco_compact_text_encoder, false,
+ Conf = flex_scanner_conf(Config),
+ case decode_message(megaco_compact_text_encoder, false,
[?EC_V3,Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers2 -> "
@@ -726,8 +783,8 @@ flex_compact_dm_timers3(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("1", "02", "31"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
- case decode_message(megaco_compact_text_encoder, false,
+ Conf = flex_scanner_conf(Config),
+ case decode_message(megaco_compact_text_encoder, false,
[?EC_V3,Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers3 -> "
@@ -745,8 +802,8 @@ flex_compact_dm_timers4(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("10", "21", "99"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
- case decode_message(megaco_compact_text_encoder, false,
+ Conf = flex_scanner_conf(Config),
+ case decode_message(megaco_compact_text_encoder, false,
[?EC_V3,Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers4 -> "
@@ -764,8 +821,8 @@ flex_compact_dm_timers5(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("99", "23", "11"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
- case decode_message(megaco_compact_text_encoder, false,
+ Conf = flex_scanner_conf(Config),
+ case decode_message(megaco_compact_text_encoder, false,
[?EC_V3,Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers5 -> "
@@ -783,8 +840,8 @@ flex_compact_dm_timers6(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("77", "09", "1"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
- case decode_message(megaco_compact_text_encoder, false,
+ Conf = flex_scanner_conf(Config),
+ case decode_message(megaco_compact_text_encoder, false,
[?EC_V3,Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers6 -> "
@@ -802,8 +859,8 @@ flex_compact_dm_timers7(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("77", "09", "1", "99"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
- case decode_message(megaco_compact_text_encoder, false,
+ Conf = flex_scanner_conf(Config),
+ case decode_message(megaco_compact_text_encoder, false,
[?EC_V3,Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers7 -> "
@@ -821,8 +878,8 @@ flex_compact_dm_timers8(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
M = build_dm_timers_message("01", "09", "01", "02"),
B = list_to_binary(M),
- Conf = flex_scanner_conf(Config),
- case decode_message(megaco_compact_text_encoder, false,
+ Conf = flex_scanner_conf(Config),
+ case decode_message(megaco_compact_text_encoder, false,
[?EC_V3,Conf], B) of
{ok, M1} when is_record(M1,'MegacoMessage') ->
t("flex_compact_dm_timers8 -> "
@@ -863,7 +920,7 @@ get_dm_timers({transactions, T}) when is_list(T) ->
get_dm_timers(Other) ->
{error, {invalid_transactions, Other}}.
-get_dm_timers1([{transactionRequest,T}|Ts])
+get_dm_timers1([{transactionRequest,T}|Ts])
when is_record(T,'TransactionRequest') ->
case get_dm_timers2(T) of
{ok, Timers} ->
@@ -939,45 +996,45 @@ bin_test_msgs(suite) ->
[];
bin_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
- Msgs =
- msgs1a(binary) ++
- msgs5(binary) ++
- msgs6(binary) ++
- msgs7(binary) ++
+ Msgs =
+ msgs1a(binary) ++
+ msgs5(binary) ++
+ msgs6(binary) ++
+ msgs7(binary) ++
msgs8(binary),
- %% Msgs = msgs6(binary),
+ %% Msgs = msgs6(binary),
DynamicDecode = false,
test_msgs(megaco_binary_encoder, DynamicDecode, ?EC, Msgs).
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ber_test_msgs(suite) ->
[];
ber_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
- Msgs =
- msgs1a(binary) ++
- msgs5(binary) ++
- msgs6(binary) ++
- msgs7(binary) ++
+ Msgs =
+ msgs1a(binary) ++
+ msgs5(binary) ++
+ msgs6(binary) ++
+ msgs7(binary) ++
msgs8(binary),
%% Msgs = msgs7(binary),
DynamicDecode = false,
test_msgs(megaco_ber_encoder, DynamicDecode, ?EC, Msgs).
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
per_test_msgs(suite) ->
[];
per_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
- Msgs =
- msgs1a(binary) ++
- msgs5(binary) ++
- msgs6(binary) ++
- msgs7(binary) ++
+ Msgs =
+ msgs1a(binary) ++
+ msgs5(binary) ++
+ msgs6(binary) ++
+ msgs7(binary) ++
msgs8(binary),
DynamicDecode = false,
test_msgs(megaco_per_encoder, DynamicDecode, ?EC, Msgs).
@@ -989,16 +1046,16 @@ erl_dist_m_test_msgs(suite) ->
[];
erl_dist_m_test_msgs(Config) when is_list(Config) ->
?ACQUIRE_NODES(1, Config),
- Msgs =
- msgs1a(erlang) ++
- msgs1b(erlang) ++
- msgs3525(erlang) ++
- msgs5(erlang) ++
- msgs6(erlang) ++
- msgs7(erlang) ++
+ Msgs =
+ msgs1a(erlang) ++
+ msgs1b(erlang) ++
+ msgs3525(erlang) ++
+ msgs5(erlang) ++
+ msgs6(erlang) ++
+ msgs7(erlang) ++
msgs8(erlang),
DynamicDecode = false,
- Conf = [megaco_compressed],
+ Conf = [megaco_compressed],
test_msgs(megaco_erl_dist_encoder, DynamicDecode, Conf, Msgs).
@@ -1041,7 +1098,7 @@ compact_otp4011_msg2(Config) when is_list(Config) ->
%% --------------------------------------------------------------
%% Observe that this decode SHALL fail
-%%
+%%
compact_otp4011_msg3(suite) ->
[];
@@ -1068,7 +1125,7 @@ compact_otp4011(Msg, Conf) ->
{error, {unexpected_decode_result, Crap}}
end,
megaco_codec_test_lib:expect_decode(Msg, Decode, Check).
-
+
compact_otp4011_chk1(R1) ->
case lists:keysearch(reason, 1, R1) of
{value, {reason, R2}} ->
@@ -1076,13 +1133,13 @@ compact_otp4011_chk1(R1) ->
false ->
{error, {unexpected_result, R1}}
end.
-
-compact_otp4011_chk2({0, ParserMod, {ParserFunc, [A, B]}})
+
+compact_otp4011_chk2({0, ParserMod, {ParserFunc, [A, B]}})
when (ParserMod =:= megaco_text_parser_v3) andalso
(ParserFunc =:= do_merge_control_streamParms) andalso
is_list(A) andalso
is_record(B, 'LocalControlDescriptor') ->
- SM = B#'LocalControlDescriptor'.streamMode,
+ SM = B#'LocalControlDescriptor'.streamMode,
case lists:keysearch(mode, 1, A) of
{value, {mode, _Mode}} when SM /= asn1_NOVALUE ->
ok;
@@ -1114,7 +1171,7 @@ compact_otp4013_msg1(Config) when is_list(Config) ->
compact_otp4013(Msg) ->
compact_otp4013(Msg, ?EC).
-compact_otp4013(Msg, Conf) ->
+compact_otp4013(Msg, Conf) ->
Codec = megaco_compact_text_encoder,
Decode = fun(B) -> decode_message(Codec, false, Conf, B) end,
Check = fun(Reason) when is_list(Reason) ->
@@ -1143,8 +1200,8 @@ compact_otp4013_chk1(Reason) ->
%% --------------------------------------------------------------
-%%
-%%
+%%
+%%
compact_otp4085_msg1(suite) ->
[];
compact_otp4085_msg1(Config) when is_list(Config) ->
@@ -1173,9 +1230,9 @@ compact_otp4085_1(Msg, Conf) ->
compact_otp4085_1_chk1(Reason) ->
case lists:keysearch(reason, 1, Reason) of
- {value, {reason, {Line, Module, Crap}}} when is_integer(Line) and
+ {value, {reason, {Line, Module, Crap}}} when is_integer(Line) and
is_atom(Module) ->
- Crap2 =
+ Crap2 =
case (catch lists:flatten(Crap)) of
L when is_list(L) ->
L;
@@ -1187,7 +1244,7 @@ compact_otp4085_1_chk1(Reason) ->
"~n Module: ~p"
"~n Crap2: ~p", [Line, Module, Crap2]),
ok;
- {value, BadReason} ->
+ {value, BadReason} ->
e("compact_otp4085_1_chk1 -> error: "
"~n BadReason: ~p", [BadReason]),
{error, {unexpected_reason, Reason}};
@@ -1228,8 +1285,8 @@ compact_otp4085_2(Msg, Conf) ->
%% This message lack the ending parentesis (}).
compact_otp4085_erroneous_msg() ->
- M = "!/"
- ?VERSION_STR
+ M = "!/"
+ ?VERSION_STR
" ML T=11223342{C=${A=${M{O{MO=SR,RV=OFF,RG=OFF},L{v=0,"
"c=ATM NSAP $ ,"
"a=eecid:$ ,"
@@ -1238,8 +1295,8 @@ compact_otp4085_erroneous_msg() ->
M.
%% --------------------------------------------------------------
-%%
-%%
+%%
+%%
compact_otp4280_msg1(suite) ->
[];
compact_otp4280_msg1(Config) when is_list(Config) ->
@@ -1265,7 +1322,7 @@ compact_otp4280(Msg, Conf) ->
{error, {unexpected_decode_result, Crap}}
end,
megaco_codec_test_lib:expect_decode_only(Msg, Decode, Check).
-
+
compact_otp4280_msg() ->
M = "!/"
?VERSION_STR
@@ -1278,7 +1335,7 @@ compact_otp4280_msg() ->
%% --------------------------------------------------------------
%% This ticket is about comments in a message
-%%
+%%
compact_otp4299_msg1(suite) ->
[];
compact_otp4299_msg1(Config) when is_list(Config) ->
@@ -1323,7 +1380,7 @@ compact_otp4299_msg() ->
%% --------------------------------------------------------------
-%%
+%%
compact_otp4359_msg1(suite) ->
[];
@@ -1332,8 +1389,8 @@ compact_otp4359_msg1(Config) when is_list(Config) ->
%% put(dbg,true),
d("compact_otp4359_msg1 -> entry", []),
?ACQUIRE_NODES(1, Config),
- Msg = compact_otp4359_msg(),
- ok = compact_otp4359(Msg),
+ Msg = compact_otp4359_msg(),
+ ok = compact_otp4359(Msg),
%% erase(severity),
%% erase(dbg),
ok.
@@ -1354,7 +1411,7 @@ compact_otp4359(Msg, Conf) ->
{error, {unexpected_decode_result, Crap}}
end,
megaco_codec_test_lib:expect_decode_only(Msg, Decode, Check).
-
+
compact_otp4359_chk(#'MegacoMessage'{mess = Mess}) ->
case Mess#'Message'.messageBody of
{transactions, Trans} ->
@@ -1375,7 +1432,7 @@ compact_otp4359_chk(#'MegacoMessage'{mess = Mess}) ->
%% --------------------------------------------------------------
-%%
+%%
compact_otp4920_msg0(suite) ->
[];
compact_otp4920_msg0(Config) when is_list(Config) ->
@@ -1610,7 +1667,7 @@ compact_otp4920_msg25(Config) when is_list(Config) ->
%% erase(dbg),
ok.
-
+
compact_otp4920(Msg, ExpectedReason) ->
compact_otp4920(Msg, ?EC, ExpectedReason).
@@ -1631,7 +1688,7 @@ compact_otp4920_chk(Reason, ExpectedReason) ->
ExpectedReason ->
ok;
_ ->
- {error, {unexpected_decode_reason,
+ {error, {unexpected_decode_reason,
{ActualReason, ExpectedReason}}}
end;
{value, {reason, {_Mod, ActualReason}}} ->
@@ -1639,7 +1696,7 @@ compact_otp4920_chk(Reason, ExpectedReason) ->
ExpectedReason ->
ok;
_ ->
- {error, {unexpected_decode_reason,
+ {error, {unexpected_decode_reason,
{ActualReason, ExpectedReason}}}
end;
{value, UnknownReason} ->
@@ -1732,7 +1789,7 @@ compact_otp4920_msg25() ->
%% --------------------------------------------------------------
-%%
+%%
compact_otp5186_msg01(suite) ->
[];
@@ -1811,7 +1868,7 @@ compact_otp5186_msg06(Config) when is_list(Config) ->
ok.
%% --
-
+
compact_otp5186_msg01() ->
"!/" ?VERSION_STR " <mg5>\nP=67111298{C=2699{AV=mg5_ipeph/0x0f0001{}}}".
@@ -1959,7 +2016,7 @@ compact_otp5793_msg01(Config) when is_list(Config) ->
ok = ticket_compact_encode_decode_ok(pretty_otp5793_msg1()),
%% erase(severity),
%% erase(dbg),
- ok.
+ ok.
%% --------------------------------------------------------------
@@ -1983,8 +2040,8 @@ compact_otp5836_msg1() ->
{'Message',
?VERSION,
{deviceName,"bs_sbg_4/34"},
- {transactions,
- [{transactionReply,
+ {transactions,
+ [{transactionReply,
{'TransactionReply',
12,
asn1_NOVALUE,
@@ -2172,6 +2229,393 @@ compact_otp6017_msg(CID) when is_integer(CID) ->
"{SC=root{SV{MT=RS,RE=901}}}}".
+%% --------------------------------------------------------------
+%%
+compact_otp16818_msg01(suite) ->
+ [];
+compact_otp16818_msg01(Config) when is_list(Config) ->
+ d("compact_otp16818_msg01 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg01() ),
+ ok.
+
+compact_otp16818_msg01() ->
+ compact_otp16818_msg("a").
+
+
+%% --
+
+compact_otp16818_msg02(suite) ->
+ [];
+compact_otp16818_msg02(Config) when is_list(Config) ->
+ d("compact_otp16818_msg02 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg02() ),
+ ok.
+
+compact_otp16818_msg02() ->
+ compact_otp16818_msg("b").
+
+
+%% --
+
+compact_otp16818_msg03(suite) ->
+ [];
+compact_otp16818_msg03(Config) when is_list(Config) ->
+ d("compact_otp16818_msg03 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg03() ),
+ ok.
+
+compact_otp16818_msg03() ->
+ compact_otp16818_msg("c").
+
+
+%% --
+
+compact_otp16818_msg04(suite) ->
+ [];
+compact_otp16818_msg04(Config) when is_list(Config) ->
+ d("compact_otp16818_msg04 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg04() ),
+ ok.
+
+compact_otp16818_msg04() ->
+ compact_otp16818_msg("d").
+
+
+%% --
+
+compact_otp16818_msg05(suite) ->
+ [];
+compact_otp16818_msg05(Config) when is_list(Config) ->
+ d("compact_otp16818_msg05 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg05() ),
+ ok.
+
+compact_otp16818_msg05() ->
+ compact_otp16818_msg("e").
+
+
+%% --
+
+compact_otp16818_msg06(suite) ->
+ [];
+compact_otp16818_msg06(Config) when is_list(Config) ->
+ d("compact_otp16818_msg06 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg06() ),
+ ok.
+
+compact_otp16818_msg06() ->
+ compact_otp16818_msg("f").
+
+
+%% --
+
+compact_otp16818_msg11(suite) ->
+ [];
+compact_otp16818_msg11(Config) when is_list(Config) ->
+ d("compact_otp16818_msg11 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg11() ),
+ ok.
+
+compact_otp16818_msg11() ->
+ compact_otp16818_msg("000a").
+
+
+%% --
+
+compact_otp16818_msg12(suite) ->
+ [];
+compact_otp16818_msg12(Config) when is_list(Config) ->
+ d("compact_otp16818_msg12 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg12() ),
+ ok.
+
+compact_otp16818_msg12() ->
+ compact_otp16818_msg("000b").
+
+
+%% --
+
+compact_otp16818_msg13(suite) ->
+ [];
+compact_otp16818_msg13(Config) when is_list(Config) ->
+ d("compact_otp16818_msg13 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg13() ),
+ ok.
+
+compact_otp16818_msg13() ->
+ compact_otp16818_msg("000c").
+
+
+%% --
+
+compact_otp16818_msg14(suite) ->
+ [];
+compact_otp16818_msg14(Config) when is_list(Config) ->
+ d("compact_otp16818_msg14 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg14() ),
+ ok.
+
+compact_otp16818_msg14() ->
+ compact_otp16818_msg("000d").
+
+
+%% --
+
+compact_otp16818_msg15(suite) ->
+ [];
+compact_otp16818_msg15(Config) when is_list(Config) ->
+ d("compact_otp16818_msg15 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg15() ),
+ ok.
+
+compact_otp16818_msg15() ->
+ compact_otp16818_msg("000e").
+
+
+%% --
+
+compact_otp16818_msg16(suite) ->
+ [];
+compact_otp16818_msg16(Config) when is_list(Config) ->
+ d("compact_otp16818_msg16 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg16() ),
+ ok.
+
+compact_otp16818_msg16() ->
+ compact_otp16818_msg("000f").
+
+
+%% --
+
+compact_otp16818_msg21(suite) ->
+ [];
+compact_otp16818_msg21(Config) when is_list(Config) ->
+ d("compact_otp16818_msg21 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg21() ),
+ ok.
+
+compact_otp16818_msg21() ->
+ compact_otp16818_msg("0a12").
+
+
+%% --
+
+compact_otp16818_msg22(suite) ->
+ [];
+compact_otp16818_msg22(Config) when is_list(Config) ->
+ d("compact_otp16818_msg22 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg22() ),
+ ok.
+
+compact_otp16818_msg22() ->
+ compact_otp16818_msg("0b12").
+
+
+%% --
+
+compact_otp16818_msg23(suite) ->
+ [];
+compact_otp16818_msg23(Config) when is_list(Config) ->
+ d("compact_otp16818_msg23 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg23() ),
+ ok.
+
+compact_otp16818_msg23() ->
+ compact_otp16818_msg("0c12").
+
+
+%% --
+
+compact_otp16818_msg24(suite) ->
+ [];
+compact_otp16818_msg24(Config) when is_list(Config) ->
+ d("compact_otp16818_msg24 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg24() ),
+ ok.
+
+compact_otp16818_msg24() ->
+ compact_otp16818_msg("0d12").
+
+
+%% --
+
+compact_otp16818_msg25(suite) ->
+ [];
+compact_otp16818_msg25(Config) when is_list(Config) ->
+ d("compact_otp16818_msg25 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg25() ),
+ ok.
+
+compact_otp16818_msg25() ->
+ compact_otp16818_msg("0e12").
+
+
+%% --
+
+compact_otp16818_msg26(suite) ->
+ [];
+compact_otp16818_msg26(Config) when is_list(Config) ->
+ d("compact_otp16818_msg26 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg26() ),
+ ok.
+
+compact_otp16818_msg26() ->
+ compact_otp16818_msg("0f12").
+
+
+%% --
+
+compact_otp16818_msg31(suite) ->
+ [];
+compact_otp16818_msg31(Config) when is_list(Config) ->
+ d("compact_otp16818_msg31 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg31() ),
+ ok.
+
+compact_otp16818_msg31() ->
+ compact_otp16818_msg("a123").
+
+
+%% --
+
+compact_otp16818_msg32(suite) ->
+ [];
+compact_otp16818_msg32(Config) when is_list(Config) ->
+ d("compact_otp16818_msg32 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg32() ),
+ ok.
+
+compact_otp16818_msg32() ->
+ compact_otp16818_msg("b123").
+
+
+%% --
+
+compact_otp16818_msg33(suite) ->
+ [];
+compact_otp16818_msg33(Config) when is_list(Config) ->
+ d("compact_otp16818_msg33 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg33() ),
+ ok.
+
+compact_otp16818_msg33() ->
+ compact_otp16818_msg("c123").
+
+
+%% --
+
+compact_otp16818_msg34(suite) ->
+ [];
+compact_otp16818_msg34(Config) when is_list(Config) ->
+ d("compact_otp16818_msg34 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg34() ),
+ ok.
+
+compact_otp16818_msg34() ->
+ compact_otp16818_msg("d123").
+
+
+%% --
+
+compact_otp16818_msg35(suite) ->
+ [];
+compact_otp16818_msg35(Config) when is_list(Config) ->
+ d("compact_otp16818_msg35 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg35() ),
+ ok.
+
+compact_otp16818_msg35() ->
+ compact_otp16818_msg("e123").
+
+
+%% --
+
+compact_otp16818_msg36(suite) ->
+ [];
+compact_otp16818_msg36(Config) when is_list(Config) ->
+ d("compact_otp16818_msg36 -> entry", []),
+ ok = compact_otp16818( compact_otp16818_msg36() ),
+ ok.
+
+compact_otp16818_msg36() ->
+ compact_otp16818_msg("f123").
+
+
+%% -----
+
+compact_otp16818( Msg ) ->
+ Bin = erlang:list_to_binary(Msg),
+ try megaco_compact_text_encoder:decode_message([], dynamic, Bin) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ catch
+ C:E:S ->
+ {error, {C, E, S}}
+ end.
+
+compact_otp16818_msg(X) when is_list(X) ->
+ "!/3 [2409:8050:5005:1243:1011::" ++ X ++
+ "] T=2523{C=-{SC=ROOT{SV{MT=RS,RE=901,PF=ETSI_BGF/2,V=3}}}}".
+
+
+%% --------------------------------------------------------------
+
+compact_erl1405_msg01(suite) ->
+ [];
+compact_erl1405_msg01(Config) when is_list(Config) ->
+ put(severity,trc),
+ put(dbg,true),
+ d("compact_erl1405_msg01 -> entry", []),
+ ?ACQUIRE_NODES(1, Config),
+ ok = compact_erl1405(statisticsDescriptor),
+ erase(severity),
+ erase(dbg),
+ ok.
+
+compact_erl1405_msg02(suite) ->
+ [];
+compact_erl1405_msg02(Config) when is_list(Config) ->
+ put(severity,trc),
+ put(dbg,true),
+ d("compact_erl1405_msg02 -> entry", []),
+ ?ACQUIRE_NODES(1, Config),
+ ok = compact_erl1405({statisticsDescriptor, []}),
+ erase(severity),
+ erase(dbg),
+ ok.
+
+compact_erl1405(Descriptor) ->
+ ticket_compact_encode_decode_ok( erl1405_msg(Descriptor) ).
+
+erl1405_msg(Descriptor) ->
+ #'MegacoMessage'{
+ mess = #'Message'{
+ version = ?VERSION,
+ mId = {deviceName, "test"},
+ messageBody = {transactions, [
+ {transactionRequest, #'TransactionRequest'{
+ transactionId = 1,
+ actions = [
+ #'ActionRequest'{
+ contextId = ?megaco_choose_context_id,
+ commandRequests = [
+ #'CommandRequest'{
+ command = {addReq, #'AmmRequest'{
+ terminationID = [#megaco_term_id{id = ["test"]}],
+ descriptors = [Descriptor]
+ }}
+ }
+ ]
+ }
+ ]
+ }}
+ ]}
+ }
+ }.
+
+
%% ==============================================================
%%
%% F l e x C o m p a c t T e s t c a s e s
@@ -2191,7 +2635,6 @@ flex_compact_otp4299_msg1(Config) when is_list(Config) ->
%% erase(dbg),
ok.
-
flex_compact_otp7431_msg01(suite) ->
[];
flex_compact_otp7431_msg01(Config) when is_list(Config) ->
@@ -2394,8 +2837,8 @@ pretty_otp4632_msg4(Config) when is_list(Config) ->
%% put(dbg,true),
d("pretty_otp4632_msg4 -> entry", []),
?ACQUIRE_NODES(1, Config),
- Check = fun(B2, B1) -> pretty_otp4632_msg4_chk(B1, B2) end,
- ok = ticket_pretty_decode_encode_only(pretty_otp4632_msg4(), Check),
+ Check = fun(B2, B1) -> pretty_otp4632_msg4_chk(B1, B2) end,
+ ok = ticket_pretty_decode_encode_only(pretty_otp4632_msg4(), Check),
%% erase(severity),
%% erase(dbg),
ok.
@@ -2412,13 +2855,13 @@ pretty_otp4632_msg4_chk(B1, B2) when is_binary(B1) and is_binary(B2) ->
%% "S1: ~s~n"
%% "S2: ~s~n", [S1, S2]),
pretty_otp4632_msg4_chk(S1, S2);
-
+
pretty_otp4632_msg4_chk([], []) ->
- messages_not_eq;
+ messages_not_eq;
pretty_otp4632_msg4_chk([], Rest2) ->
- {messages_not_eq2, Rest2};
+ {messages_not_eq2, Rest2};
pretty_otp4632_msg4_chk(Rest1, []) ->
- {messages_not_eq1, Rest1};
+ {messages_not_eq1, Rest1};
pretty_otp4632_msg4_chk([$R,$e,$a,$s,$o,$n,$ ,$=,$ ,$",$9,$0,$1,$"|_Rest1],
[$R,$e,$a,$s,$o,$n,$ ,$=,$ ,$9,$0,$1|_Rest2]) ->
ok;
@@ -2427,7 +2870,7 @@ pretty_otp4632_msg4_chk([_H1|Rest1], [_H2|Rest2]) ->
%% --------------------------------------------------------------
-%%
+%%
pretty_otp4710_msg1(suite) ->
[];
pretty_otp4710_msg1(Config) when is_list(Config) ->
@@ -2451,8 +2894,8 @@ pretty_otp4710_msg2(Config) when is_list(Config) ->
%% put(dbg,true),
d("pretty_otp4710_msg2 -> entry", []),
?ACQUIRE_NODES(1, Config),
- Check = fun(B1, B2) -> pretty_otp4710_msg2_chk(B1, B2) end,
- ok = ticket_pretty_decode_encode_only(pretty_otp4710_msg2(), Check),
+ Check = fun(B1, B2) -> pretty_otp4710_msg2_chk(B1, B2) end,
+ ok = ticket_pretty_decode_encode_only(pretty_otp4710_msg2(), Check),
%% erase(severity),
%% erase(dbg),
ok.
@@ -2461,8 +2904,8 @@ pretty_otp4710_msg2() ->
"Authentication = 0xEFCDAB89:0x12345678:0x1234567889ABCDEF76543210\nMEGACO/" ?VERSION_STR " [124.124.124.222]\nTransaction = 9998 {\n\tContext = - {\n\t\tServiceChange = root {\n\t\t\tServices {\n\t\t\t\tMethod = Restart,\n\t\t\t\tServiceChangeAddress = 55555,\n\t\t\t\tProfile = resgw/1,\n\t\t\t\tReason = \"901 mg col boot\"\n\t\t\t}\n\t\t}\n\t}\n}".
pretty_otp4710_msg2_chk(B1, B2) when is_binary(B1) and is_binary(B2) ->
- S1 = binary_to_list(B1),
- S2 = binary_to_list(B2),
+ S1 = binary_to_list(B1),
+ S2 = binary_to_list(B2),
pretty_otp4710_msg2_chk(S1, S2);
pretty_otp4710_msg2_chk(Msg, Msg) ->
@@ -2489,7 +2932,7 @@ pretty_otp4710_msg2_chk_ah([C|R], Acc) ->
%% --------------------------------------------------------------
-%%
+%%
pretty_otp4945_msg1(suite) ->
[];
pretty_otp4945_msg1(Config) when is_list(Config) ->
@@ -2509,15 +2952,15 @@ pretty_otp4945_msg1() ->
ServiceChange = ROOT {
Services {
Method = Restart,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp4945_msg1_chk(R) when is_list(R) ->
- ExpMissing = [serviceChangeReason],
+ ExpMissing = [serviceChangeReason],
Check = fun(Reason) ->
pretty_otp4945_chk(Reason, ExpMissing)
end,
@@ -2543,20 +2986,20 @@ pretty_otp4945_msg2() ->
ServiceChange = ROOT {
Services {
Reason = 901,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
pretty_otp4945_msg2_chk(R) when is_list(R) ->
- ExpMissing = [serviceChangeMethod],
+ ExpMissing = [serviceChangeMethod],
Check = fun(Reason) ->
pretty_otp4945_chk(Reason, ExpMissing)
end,
ticket_check_decode_only_error_reason(R, Check).
-
+
pretty_otp4945_msg3(suite) ->
[];
@@ -2576,21 +3019,21 @@ pretty_otp4945_msg3() ->
Context = - {
ServiceChange = ROOT {
Services {
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
pretty_otp4945_msg3_chk(R) when is_list(R) ->
- ExpMissing = [serviceChangeReason, serviceChangeMethod],
+ ExpMissing = [serviceChangeReason, serviceChangeMethod],
Check = fun(Reason) ->
pretty_otp4945_chk(Reason, ExpMissing)
end,
ticket_check_decode_only_error_reason(R, Check).
-
+
pretty_otp4945_msg4(suite) ->
[];
pretty_otp4945_msg4(Config) when is_list(Config) ->
@@ -2610,13 +3053,13 @@ pretty_otp4945_msg4() ->
Services {
Method = Restart,
Reason = 901,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp4945_msg5(suite) ->
[];
@@ -2639,13 +3082,13 @@ pretty_otp4945_msg5() ->
Method = Restart,
Reason = 901,
Profile = ResGW/1,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/2
}
}
- }
+ }
}".
-
+
pretty_otp4945_msg5_chk(R) when is_list(R) ->
Check = fun({at_most_once_serviceChangeParm, {profile, _, _}}) ->
ok;
@@ -2675,12 +3118,12 @@ pretty_otp4945_msg6() ->
Services {
Method = Restart,
Reason = 901,
- ServiceChangeAddress = 55555,
- MgcIdToTry = kalle,
+ ServiceChangeAddress = 55555,
+ MgcIdToTry = kalle,
Profile = ResGW/1
}
}
- }
+ }
}".
pretty_otp4945_msg6_chk(R) when is_list(R) ->
@@ -2690,9 +3133,9 @@ pretty_otp4945_msg6_chk(R) when is_list(R) ->
{error, {unexpected_reason, Reason}}
end,
ticket_check_decode_only_error_reason(R, Check).
-
-pretty_otp4945_chk({missing_required_serviceChangeParm, Missing},
+
+pretty_otp4945_chk({missing_required_serviceChangeParm, Missing},
ExpMissing) when is_list(Missing) ->
case ExpMissing -- Missing of
[] ->
@@ -2705,7 +3148,7 @@ pretty_otp4945_chk(Reason, _ExpMissing) ->
%% --------------------------------------------------------------
-%%
+%%
pretty_otp4949_msg1(suite) ->
[];
pretty_otp4949_msg1(Config) when is_list(Config) ->
@@ -2723,13 +3166,13 @@ pretty_otp4949_msg1() ->
Context = - {
ServiceChange = ROOT {
Services {
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
-
+
pretty_otp4949_msg2(suite) ->
[];
@@ -2750,13 +3193,13 @@ pretty_otp4949_msg2() ->
ServiceChange = ROOT {
Services {
Profile = ResGW/1,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/2
}
}
- }
+ }
}".
-
+
pretty_otp4949_msg2_chk(R) when is_list(R) ->
Check = fun({at_most_once_servChgReplyParm, {profile, _, _}}) ->
ok;
@@ -2784,12 +3227,12 @@ pretty_otp4949_msg3() ->
Context = - {
ServiceChange = ROOT {
Services {
- ServiceChangeAddress = 55555,
- MgcIdToTry = kalle,
+ ServiceChangeAddress = 55555,
+ MgcIdToTry = kalle,
Profile = ResGW/1
}
}
- }
+ }
}".
pretty_otp4949_msg3_chk(R) when is_list(R) ->
@@ -2799,10 +3242,10 @@ pretty_otp4949_msg3_chk(R) when is_list(R) ->
{error, {unexpected_reason, Reason}}
end,
ticket_check_decode_only_error_reason(R, Check).
-
+
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5042_msg1(suite) ->
[];
pretty_otp5042_msg1(Config) when is_list(Config) ->
@@ -2817,17 +3260,17 @@ pretty_otp5042_msg1(Config) when is_list(Config) ->
pretty_otp5042_msg1() ->
"MEGACO/" ?VERSION_STR " <CATAPULT>:2944
-Transaction = 102 {
-Context = 5 { Notify = MUX/1 { ObservedEvents = 1 {
+Transaction = 102 {
+Context = 5 { Notify = MUX/1 { ObservedEvents = 1 {
h245bh/h245msgin { Stream = 1
, h245enc =
0270020600088175000653401004100403E802E00180018001780680000034301160000700088175010101007A0100020001800001320000C0000219D005027F0070500100040100021080000319D005027F00504001008000041C001250000700088175010000400280010003000880000518AA027F400006850130008011020100000001030002000300040005000006
- } }
+ } }
} } }".
-
+
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5068_msg1(suite) ->
[];
pretty_otp5068_msg1(Config) when is_list(Config) ->
@@ -2883,11 +3326,11 @@ pretty_otp5068_msg1() ->
}
}
}.
-
+
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5085_msg1(suite) ->
[];
pretty_otp5085_msg1(Config) when is_list(Config) ->
@@ -2912,7 +3355,7 @@ pretty_otp5085_msg1() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
{'ErrorDescriptor',504,asn1_NOVALUE},
asn1_NOVALUE,
@@ -2952,7 +3395,7 @@ pretty_otp5085_msg2() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
asn1_NOVALUE,
asn1_NOVALUE,
@@ -2992,7 +3435,7 @@ pretty_otp5085_msg3() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
asn1_NOVALUE,
#'ContextRequest'{priority = 3},
@@ -3032,11 +3475,11 @@ pretty_otp5085_msg4() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
asn1_NOVALUE,
asn1_NOVALUE,
- [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
+ [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
{notifyReply, cre_NotifyRep([#megaco_term_id{id = ?A5555}])}]
}
]
@@ -3073,11 +3516,11 @@ pretty_otp5085_msg5() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
asn1_NOVALUE,
#'ContextRequest'{priority = 5},
- [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
+ [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
{notifyReply, cre_NotifyRep([#megaco_term_id{id = ?A5555}])}]
}
]
@@ -3114,11 +3557,11 @@ pretty_otp5085_msg6() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
{'ErrorDescriptor',504,asn1_NOVALUE},
#'ContextRequest'{priority = 6},
- [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
+ [{addReply, cre_AmmsReply([#megaco_term_id{id = ?A4444}])},
{notifyReply, cre_NotifyRep([#megaco_term_id{id = ?A5555}])}]
}
]
@@ -3155,7 +3598,7 @@ pretty_otp5085_msg7() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
{'ErrorDescriptor',504,asn1_NOVALUE},
#'ContextRequest'{priority = 7},
@@ -3199,12 +3642,12 @@ pretty_otp5085_msg8() ->
230,
asn1_NOVALUE,
{actionReplies,
- [{'ActionReply',
+ [{'ActionReply',
400,
{'ErrorDescriptor',504,asn1_NOVALUE},
- #'ContextRequest'{priority = 8,
+ #'ContextRequest'{priority = 8,
emergency = true,
- topologyReq =
+ topologyReq =
[#'TopologyRequest'{terminationFrom = From1,
terminationTo = To1,
topologyDirection = bothway},
@@ -3227,7 +3670,7 @@ pretty_otp5085_msg8() ->
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5600_msg1(suite) ->
[];
pretty_otp5600_msg1(Config) when is_list(Config) ->
@@ -3239,17 +3682,17 @@ pretty_otp5600_msg1(Config) when is_list(Config) ->
%% erase(severity),
%% erase(dbg),
ok.
-
+
pretty_otp5600_msg1() ->
SRE = #'SecondRequestedEvent'{ pkgdName = "al/on",
evParList = [] },
-
+
SED = #'SecondEventsDescriptor'{ requestID = 2,
eventList = [ SRE ] },
-
+
SIG = { signal, #'Signal'{ signalName = "cg/dt",
sigParList = [] } },
-
+
RA = #'RequestedActions'{ secondEvent = SED,
signalsDescriptor = [ SIG ] },
@@ -3288,35 +3731,35 @@ pretty_otp5600_msg2(Config) when is_list(Config) ->
%% erase(severity),
%% erase(dbg),
ok.
-
+
pretty_otp5600_msg2() ->
SIG = { signal, #'Signal'{ signalName = "cg/dt",
sigParList = [] } },
SRA = #'SecondRequestedActions'{ signalsDescriptor = [ SIG ] },
-
+
SRE = #'SecondRequestedEvent'{ pkgdName = "al/on",
eventAction = SRA,
evParList = [] },
-
+
SED = #'SecondEventsDescriptor'{ requestID = 2,
eventList = [ SRE ] },
-
+
RA = #'RequestedActions'{ secondEvent = SED },
-
+
RE = #'RequestedEvent'{ pkgdName = "al/of",
eventAction = RA,
evParList = [] },
-
+
EV = #'EventsDescriptor'{ requestID = 1, eventList = [ RE ] },
-
+
TermID = {megaco_term_id, true, [[$*]] },
-
+
AMMR = #'AmmRequest'{ terminationID = [ TermID ],
descriptors = [ { eventsDescriptor, EV } ] },
-
+
CR = #'CommandRequest'{command = {modReq, AMMR}},
-
+
AR = #'ActionRequest'{contextId = ?megaco_null_context_id,
commandRequests = [CR]},
ARs = [AR],
@@ -3329,7 +3772,7 @@ pretty_otp5600_msg2() ->
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5601_msg1(suite) ->
[];
pretty_otp5601_msg1(Config) when is_list(Config) ->
@@ -3382,7 +3825,7 @@ pretty_otp5601_msg1() ->
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5793_msg01(suite) ->
[];
pretty_otp5793_msg01(Config) when is_list(Config) ->
@@ -3443,7 +3886,7 @@ pretty_otp5793_msg1() ->
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5803_msg01(suite) ->
[];
pretty_otp5803_msg01(Config) when is_list(Config) ->
@@ -3548,7 +3991,7 @@ pretty_otp5803_msg2() ->
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5805_msg01(suite) ->
[];
pretty_otp5805_msg01(Config) when is_list(Config) ->
@@ -3573,7 +4016,7 @@ Transaction=1{
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5836_msg01(suite) ->
[];
pretty_otp5836_msg01(Config) when is_list(Config) ->
@@ -3588,7 +4031,7 @@ pretty_otp5836_msg01(Config) when is_list(Config) ->
%% --------------------------------------------------------------
-%%
+%%
pretty_otp5882_msg01(suite) ->
[];
pretty_otp5882_msg01(Config) when is_list(Config) ->
@@ -3601,7 +4044,7 @@ pretty_otp5882_msg01(Config) when is_list(Config) ->
%% erase(severity),
%% erase(dbg),
ok.
-
+
pretty_otp5882_msg01_chk({message_encode_failed, {error, {Reason, _}}, _}) ->
case Reason of
{invalid_LocalControlDescriptor, empty} ->
@@ -3612,7 +4055,7 @@ pretty_otp5882_msg01_chk({message_encode_failed, {error, {Reason, _}}, _}) ->
pretty_otp5882_msg01_chk(Reason) ->
{error, {unexpected_reason, Reason}}.
-
+
pretty_otp5882_msg01() ->
LCD = #'LocalControlDescriptor'{}, % Create illegal LCD
Parms = cre_StreamParms(LCD),
@@ -3630,11 +4073,11 @@ pretty_otp5882_msg01() ->
Mid = ?MG1_MID,
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
-
+
%% --------------------------------------------------------------
-%%
+%%
pretty_otp6490_msg01(suite) ->
[];
pretty_otp6490_msg01(Config) when is_list(Config) ->
@@ -3731,7 +4174,7 @@ pretty_otp6490_msg02() ->
PkgdName = ?MSG_LIB:cre_PkgdName("foo", "a"),
EvName = ?MSG_LIB:cre_EventName(PkgdName),
EvSpec = ?MSG_LIB:cre_EventSpec(EvName, [EvPar]),
- EvSpecs = [EvSpec],
+ EvSpecs = [EvSpec],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
@@ -3742,7 +4185,7 @@ pretty_otp6490_msg03() ->
PkgdName = ?MSG_LIB:cre_PkgdName("foo", "a"),
EvName = ?MSG_LIB:cre_EventName(PkgdName),
EvSpec = ?MSG_LIB:cre_EventSpec(EvName, [EvPar1,EvPar2,EvPar3]),
- EvSpecs = [EvSpec],
+ EvSpecs = [EvSpec],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
@@ -3757,7 +4200,7 @@ pretty_otp6490_msg04() ->
PkgdName2 = ?MSG_LIB:cre_PkgdName("bar", "b"),
EvName2 = ?MSG_LIB:cre_EventName(PkgdName2),
EvSpec2 = ?MSG_LIB:cre_EventSpec(EvName2, [EvPar4]),
- EvSpecs = [EvSpec1,EvSpec2],
+ EvSpecs = [EvSpec1,EvSpec2],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
@@ -3766,7 +4209,7 @@ pretty_otp6490_msg05() ->
PkgdName = ?MSG_LIB:cre_PkgdName("foo", root),
EvName = ?MSG_LIB:cre_EventName(PkgdName),
EvSpec = ?MSG_LIB:cre_EventSpec(EvName, [EvPar]),
- EvSpecs = [EvSpec],
+ EvSpecs = [EvSpec],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
@@ -3775,7 +4218,7 @@ pretty_otp6490_msg06() ->
PkgdName = ?MSG_LIB:cre_PkgdName(root, root),
EvName = ?MSG_LIB:cre_EventName(PkgdName),
EvSpec = ?MSG_LIB:cre_EventSpec(EvName, [EvPar]),
- EvSpecs = [EvSpec],
+ EvSpecs = [EvSpec],
EBD = ?MSG_LIB:cre_EventBufferDescriptor(EvSpecs),
pretty_otp6490_msg(EBD).
@@ -3858,7 +4301,7 @@ pretty_otp7671(Msg, Conf, ExpectedEncode, ExpectedDecode, Check) ->
ExpectedEncode, ExpectedDecode, Check).
otp7671(Msg, Codec, Conf, ExpectedEncode, ExpectedDecode) ->
- Check = fun(M1, M2) ->
+ Check = fun(M1, M2) ->
exit({unexpected_decode_result, M1, M2})
end,
otp7671(Msg, Codec, Conf, ExpectedEncode, ExpectedDecode, Check).
@@ -3935,9 +4378,9 @@ pretty_otp7671_msg05() ->
asn1_NOVALUE}}}]}},
asn1_NOVALUE,asn1_NOVALUE}]}]}}]}}}.
-cmp_otp7671_msg05(#'MegacoMessage'{authHeader = asn1_NOVALUE,
- mess = M1},
- #'MegacoMessage'{authHeader = asn1_NOVALUE,
+cmp_otp7671_msg05(#'MegacoMessage'{authHeader = asn1_NOVALUE,
+ mess = M1},
+ #'MegacoMessage'{authHeader = asn1_NOVALUE,
mess = M2}) ->
#'Message'{messageBody = Body1} = M1,
#'Message'{messageBody = Body2} = M2,
@@ -3953,11 +4396,11 @@ cmp_otp7671_msg05(#'MegacoMessage'{authHeader = asn1_NOVALUE,
[#'CommandRequest'{command = Cmd2}] = CR2,
{modReq, #'AmmRequest'{descriptors = Descs1}} = Cmd1,
{modReq, #'AmmRequest'{descriptors = Descs2}} = Cmd2,
- [{digitMapDescriptor,
- #'DigitMapDescriptor'{digitMapName = Name,
+ [{digitMapDescriptor,
+ #'DigitMapDescriptor'{digitMapName = Name,
digitMapValue = Value1}}] = Descs1,
- [{digitMapDescriptor,
- #'DigitMapDescriptor'{digitMapName = Name,
+ [{digitMapDescriptor,
+ #'DigitMapDescriptor'{digitMapName = Name,
digitMapValue = Value2}}] = Descs2,
#'DigitMapValue'{startTimer = asn1_NOVALUE,
shortTimer = asn1_NOVALUE,
@@ -3966,12 +4409,11 @@ cmp_otp7671_msg05(#'MegacoMessage'{authHeader = asn1_NOVALUE,
durationTimer = asn1_NOVALUE} = Value1,
asn1_NOVALUE = Value2,
ok.
-
+
%% --------------------------------------------------------------
%%
-
pretty_otp8114_msg01(suite) ->
[];
pretty_otp8114_msg01(Config) when is_list(Config) ->
@@ -3990,12 +4432,12 @@ pretty_otp8114_msg01() ->
otp8114(InitialMessage, Codec, Conf) ->
Decode = fun(M) -> Codec:decode_message(Conf, M) end,
Encode = fun(B) -> Codec:encode_message(Conf, B) end,
- InitialData = InitialMessage,
- Instructions =
+ InitialData = InitialMessage,
+ Instructions =
[
%% List to binary
megaco_codec_test_lib:expect_instruction(
- "Convert (initial) message to a binary",
+ "Convert (initial) message to a binary",
fun(Msg) when is_list(Msg) ->
%% io:format("~s~n", [Msg]),
{ok, list_to_binary(Msg)};
@@ -4007,7 +4449,7 @@ otp8114(InitialMessage, Codec, Conf) ->
(Bad, _Msg) ->
{error, {failed_to_binary, Bad}}
end),
-
+
%% Initial decode
megaco_codec_test_lib:expect_instruction(
"Decode (initial) message",
@@ -4037,7 +4479,7 @@ otp8114(InitialMessage, Codec, Conf) ->
(Bad, _) ->
{error, {encode_failed, Bad}}
end),
-
+
%% Decode
megaco_codec_test_lib:expect_instruction(
"(final) Decode message",
@@ -4056,6 +4498,38 @@ otp8114(InitialMessage, Codec, Conf) ->
megaco_codec_test_lib:expect_exec(Instructions, InitialData).
+
+%% --------------------------------------------------------------
+%%
+
+pretty_erl1405_msg01(suite) ->
+ [];
+pretty_erl1405_msg01(Config) when is_list(Config) ->
+ put(severity,trc),
+ put(dbg,true),
+ d("pretty_erl1405_msg01 -> entry", []),
+ ?ACQUIRE_NODES(1, Config),
+ ok = pretty_erl1405(statisticsDescriptor),
+ erase(severity),
+ erase(dbg),
+ ok.
+
+pretty_erl1405_msg02(suite) ->
+ [];
+pretty_erl1405_msg02(Config) when is_list(Config) ->
+ put(severity,trc),
+ put(dbg,true),
+ d("pretty_erl1405_msg02 -> entry", []),
+ ?ACQUIRE_NODES(1, Config),
+ ok = pretty_erl1405({statisticsDescriptor, []}),
+ erase(severity),
+ erase(dbg),
+ ok.
+
+pretty_erl1405(Descriptor) ->
+ ticket_pretty_encode_decode_ok( erl1405_msg(Descriptor) ).
+
+
%% ==============================================================
%%
%% F l e x P r e t t y T e s t c a s e s
@@ -4075,7 +4549,7 @@ flex_pretty_otp5042_msg1(Config) when is_list(Config) ->
%% --------------------------------------------------------------
-%%
+%%
flex_pretty_otp5085_msg1(suite) ->
[];
flex_pretty_otp5085_msg1(Config) when is_list(Config) ->
@@ -4182,7 +4656,7 @@ flex_pretty_otp5085_msg8(Config) when is_list(Config) ->
%% --------------------------------------------------------------
-%%
+%%
flex_pretty_otp5600_msg1(suite) ->
[];
flex_pretty_otp5600_msg1(Config) when is_list(Config) ->
@@ -4211,7 +4685,7 @@ flex_pretty_otp5600_msg2(Config) when is_list(Config) ->
%% --------------------------------------------------------------
-%%
+%%
flex_pretty_otp5601_msg1(suite) ->
[];
flex_pretty_otp5601_msg1(Config) when is_list(Config) ->
@@ -4227,7 +4701,7 @@ flex_pretty_otp5601_msg1(Config) when is_list(Config) ->
%% --------------------------------------------------------------
-%%
+%%
flex_pretty_otp5793_msg01(suite) ->
[];
flex_pretty_otp5793_msg01(Config) when is_list(Config) ->
@@ -4243,7 +4717,7 @@ flex_pretty_otp5793_msg01(Config) when is_list(Config) ->
%% --------------------------------------------------------------
-%%
+%%
flex_pretty_otp5803_msg01(suite) ->
[];
flex_pretty_otp5803_msg01(Config) when is_list(Config) ->
@@ -4272,7 +4746,7 @@ flex_pretty_otp5803_msg02(Config) when is_list(Config) ->
%% --------------------------------------------------------------
-%%
+%%
flex_pretty_otp5805_msg01(suite) ->
[];
flex_pretty_otp5805_msg01(Config) when is_list(Config) ->
@@ -4288,7 +4762,7 @@ flex_pretty_otp5805_msg01(Config) when is_list(Config) ->
%% --------------------------------------------------------------
-%%
+%%
flex_pretty_otp5836_msg01(suite) ->
[];
flex_pretty_otp5836_msg01(Config) when is_list(Config) ->
@@ -4368,11 +4842,11 @@ flex_pretty_otp7431(Expected, Msg, Conf) ->
otp7431(Expected, Codec, Msg0, Conf0) ->
Bin0 = list_to_binary(Msg0),
- Conf = [?EC_V3|Conf0],
+ Conf = [?EC_V3|Conf0],
case decode_message(Codec, false, Conf, Bin0) of
{ok, _Msg1} when Expected =:= ok ->
io:format(" decoded", []);
- {error, {bad_property_parm, Reason}} when (Expected =:= error) andalso
+ {error, {bad_property_parm, Reason}} when (Expected =:= error) andalso
is_list(Reason) ->
io:format("expected result: ~s", [Reason]),
ok;
@@ -4389,20 +4863,20 @@ flex_pretty_otp7431_msg1() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
- v=0
- o=- 2890844526 2890842807 IN IP4 124.124.124.222
- s=-
- t= 0 0
- c=IN IP4 124.124.124.222
- m=audio 2222 RTP/AVP 4
- a=ptime:30
- a=recvonly
+ Local {
+ v=0
+ o=- 2890844526 2890842807 IN IP4 124.124.124.222
+ s=-
+ t= 0 0
+ c=IN IP4 124.124.124.222
+ m=audio 2222 RTP/AVP 4
+ a=ptime:30
+ a=recvonly
} ; RTP profile for G.723.1 is 4
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg2() ->
@@ -4412,19 +4886,19 @@ flex_pretty_otp7431_msg2() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
- v=0
- o=- 2890844526 2890842807 IN IP4 124.124.124.222
- s=-
- t= 0 0
- c=IN IP4 124.124.124.222
- m=audio 2222 RTP/AVP 4
- a=ptime:30
+ Local {
+ v=0
+ o=- 2890844526 2890842807 IN IP4 124.124.124.222
+ s=-
+ t= 0 0
+ c=IN IP4 124.124.124.222
+ m=audio 2222 RTP/AVP 4
+ a=ptime:30
a= }
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg3() ->
@@ -4434,19 +4908,19 @@ flex_pretty_otp7431_msg3() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
- v=0
- o=- 2890844526 2890842807 IN IP4 124.124.124.222
- s=-
- t= 0 0
- c=IN IP4 124.124.124.222
- m=audio 2222 RTP/AVP 4
- a=ptime:30
+ Local {
+ v=0
+ o=- 2890844526 2890842807 IN IP4 124.124.124.222
+ s=-
+ t= 0 0
+ c=IN IP4 124.124.124.222
+ m=audio 2222 RTP/AVP 4
+ a=ptime:30
a }
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg4() ->
@@ -4456,19 +4930,19 @@ flex_pretty_otp7431_msg4() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
- v=0
- o=- 2890844526 2890842807 IN IP4 124.124.124.222
- s=-
- t= 0 0
- c=IN IP4 124.124.124.222
- m=audio 2222 RTP/AVP 4
- a=ptime:30
+ Local {
+ v=0
+ o=- 2890844526 2890842807 IN IP4 124.124.124.222
+ s=-
+ t= 0 0
+ c=IN IP4 124.124.124.222
+ m=audio 2222 RTP/AVP 4
+ a=ptime:30
a}
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg5() ->
@@ -4478,12 +4952,12 @@ flex_pretty_otp7431_msg5() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
+ Local {
v= }
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg6() ->
@@ -4493,12 +4967,12 @@ flex_pretty_otp7431_msg6() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
+ Local {
v }
}
}
}
- }
+ }
}".
flex_pretty_otp7431_msg7() ->
@@ -4508,12 +4982,12 @@ flex_pretty_otp7431_msg7() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
+ Local {
v}
}
}
}
- }
+ }
}".
@@ -4523,92 +4997,92 @@ msgs() ->
[M || {_, M, _, _} <- msgs(text)].
msgs(Encoding) ->
- msgs1a(Encoding) ++
- msgs1b(Encoding) ++
- msgs3525(Encoding) ++
- msgs5(Encoding) ++
- msgs6(Encoding) ++
- msgs7(Encoding) ++
+ msgs1a(Encoding) ++
+ msgs1b(Encoding) ++
+ msgs3525(Encoding) ++
+ msgs5(Encoding) ++
+ msgs6(Encoding) ++
+ msgs7(Encoding) ++
msgs8(Encoding).
msgs1a(_) ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
- EC, M)
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ EC, M)
end,
[
- {msg01a, msg1a(), Plain, [{dbg,false}]},
+ {msg01a, msg1a(), Plain, [{dbg,false}]},
{msg01b, msg1b(), Plain, [{dbg,false}]},
- {msg02, msg2(), Plain, [{dbg,false}]},
- {msg03, msg3(), Plain, [{dbg,false}]},
- {msg04, msg4(), Plain, [{dbg,false}]},
- {msg05, msg5(), Plain, [{dbg,false}]},
- {msg06a, msg6a(), Plain, [{dbg,false}]},
- {msg06b, msg6b(), Plain, [{dbg,false}]},
- {msg07, msg7(), Plain, [{dbg,false}]},
- {msg08a, msg8a(), Plain, [{dbg,false}]},
- {msg08b, msg8b(), Plain, [{dbg,false}]},
- {msg09, msg9(), Plain, [{dbg,false}]},
- {msg10, msg10(), Plain, [{dbg,false}]},
- {msg11, msg11(), Plain, [{dbg,false}]},
- {msg12, msg12(), Plain, [{dbg,false}]},
- {msg13, msg13(), Plain, [{dbg,false}]},
- {msg14, msg14(), Plain, [{dbg,false}]},
- {msg15, msg15(), Plain, [{dbg,false}]},
- {msg16, msg16(), Plain, [{dbg,false}]},
- {msg17, msg17(), Plain, [{dbg,false}]},
- {msg18, msg18(), Plain, [{dbg,false}]},
- {msg19, msg19(), Plain, [{dbg,false}]},
- {msg20, msg20(), Plain, [{dbg,false}]},
+ {msg02, msg2(), Plain, [{dbg,false}]},
+ {msg03, msg3(), Plain, [{dbg,false}]},
+ {msg04, msg4(), Plain, [{dbg,false}]},
+ {msg05, msg5(), Plain, [{dbg,false}]},
+ {msg06a, msg6a(), Plain, [{dbg,false}]},
+ {msg06b, msg6b(), Plain, [{dbg,false}]},
+ {msg07, msg7(), Plain, [{dbg,false}]},
+ {msg08a, msg8a(), Plain, [{dbg,false}]},
+ {msg08b, msg8b(), Plain, [{dbg,false}]},
+ {msg09, msg9(), Plain, [{dbg,false}]},
+ {msg10, msg10(), Plain, [{dbg,false}]},
+ {msg11, msg11(), Plain, [{dbg,false}]},
+ {msg12, msg12(), Plain, [{dbg,false}]},
+ {msg13, msg13(), Plain, [{dbg,false}]},
+ {msg14, msg14(), Plain, [{dbg,false}]},
+ {msg15, msg15(), Plain, [{dbg,false}]},
+ {msg16, msg16(), Plain, [{dbg,false}]},
+ {msg17, msg17(), Plain, [{dbg,false}]},
+ {msg18, msg18(), Plain, [{dbg,false}]},
+ {msg19, msg19(), Plain, [{dbg,false}]},
+ {msg20, msg20(), Plain, [{dbg,false}]},
{msg21, msg21(), Plain, [{dbg,false}]},
- {msg22a, msg22a(), Plain, [{dbg,false}]},
- {msg22b, msg22b(), Plain, [{dbg,false}]},
- {msg22c, msg22c(), Plain, [{dbg,false}]},
- {msg22d, msg22d(), Plain, [{dbg,false}]},
- {msg22e, msg22e(), Plain, [{dbg,false}]},
- {msg22f, msg22f(), Plain, [{dbg,false}]},
- {msg23a, msg23a(), Plain, [{dbg,false}]},
- {msg23b, msg23b(), Plain, [{dbg,false}]},
- {msg23c, msg23c(), Plain, [{dbg,false}]},
- {msg23d, msg23d(), Plain, [{dbg,false}]},
- {msg24, msg24(), Plain, [{dbg,false}]},
- {msg25, msg25(), Plain, [{dbg,false}]},
- {msg30a, msg30a(), Plain, [{dbg,false}]},
- {msg30b, msg30b(), Plain, [{dbg,false}]},
- {msg30c, msg30c(), Plain, [{dbg,false}]},
+ {msg22a, msg22a(), Plain, [{dbg,false}]},
+ {msg22b, msg22b(), Plain, [{dbg,false}]},
+ {msg22c, msg22c(), Plain, [{dbg,false}]},
+ {msg22d, msg22d(), Plain, [{dbg,false}]},
+ {msg22e, msg22e(), Plain, [{dbg,false}]},
+ {msg22f, msg22f(), Plain, [{dbg,false}]},
+ {msg23a, msg23a(), Plain, [{dbg,false}]},
+ {msg23b, msg23b(), Plain, [{dbg,false}]},
+ {msg23c, msg23c(), Plain, [{dbg,false}]},
+ {msg23d, msg23d(), Plain, [{dbg,false}]},
+ {msg24, msg24(), Plain, [{dbg,false}]},
+ {msg25, msg25(), Plain, [{dbg,false}]},
+ {msg30a, msg30a(), Plain, [{dbg,false}]},
+ {msg30b, msg30b(), Plain, [{dbg,false}]},
+ {msg30c, msg30c(), Plain, [{dbg,false}]},
{msg30d, msg30d(), Plain, [{dbg,false}]}
].
msgs1b(_) ->
- TransFirst =
+ TransFirst =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:trans_first_encode_decode(Codec, DD,
- Ver, EC, M)
+ megaco_codec_test_lib:trans_first_encode_decode(Codec, DD,
+ Ver, EC, M)
end,
- ActionsFirst =
+ ActionsFirst =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:actions_first_encode_decode(Codec, DD,
- Ver, EC, M)
+ megaco_codec_test_lib:actions_first_encode_decode(Codec, DD,
+ Ver, EC, M)
end,
- ActionFirst =
+ ActionFirst =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:action_first_encode_decode(Codec, DD,
- Ver, EC, M)
+ megaco_codec_test_lib:action_first_encode_decode(Codec, DD,
+ Ver, EC, M)
end,
[
- {msg01a_tf, msg1a(), TransFirst, [{dbg,false}]},
- {msg02_tf, msg2(), TransFirst, [{dbg,false}]},
- {msg10_tf, msg10(), TransFirst, [{dbg,false}]},
- {msg11_tf, msg11(), TransFirst, [{dbg,false}]},
- {msg23d_tf, msg23d(), TransFirst, [{dbg,false}]},
- {msg30b_tf, msg30b(), TransFirst, [{dbg,false}]},
- {msg30c_tf, msg30c(), TransFirst, [{dbg,false}]},
- {msg01a_asf, msg1a(), ActionsFirst, [{dbg,false}]},
- {msg02_asf, msg2(), ActionsFirst, [{dbg,false}]},
- {msg10_asf, msg10(), ActionsFirst, [{dbg,false}]},
- {msg23d_asf, msg23d(), ActionsFirst, [{dbg,false}]},
+ {msg01a_tf, msg1a(), TransFirst, [{dbg,false}]},
+ {msg02_tf, msg2(), TransFirst, [{dbg,false}]},
+ {msg10_tf, msg10(), TransFirst, [{dbg,false}]},
+ {msg11_tf, msg11(), TransFirst, [{dbg,false}]},
+ {msg23d_tf, msg23d(), TransFirst, [{dbg,false}]},
+ {msg30b_tf, msg30b(), TransFirst, [{dbg,false}]},
+ {msg30c_tf, msg30c(), TransFirst, [{dbg,false}]},
+ {msg01a_asf, msg1a(), ActionsFirst, [{dbg,false}]},
+ {msg02_asf, msg2(), ActionsFirst, [{dbg,false}]},
+ {msg10_asf, msg10(), ActionsFirst, [{dbg,false}]},
+ {msg23d_asf, msg23d(), ActionsFirst, [{dbg,false}]},
{msg01a_af, msg1a(), ActionFirst, [{dbg,false}]},
{msg02_af, msg2(), ActionFirst, [{dbg,false}]},
{msg10_af, msg10(), ActionFirst, [{dbg,false}]},
@@ -4617,10 +5091,10 @@ msgs1b(_) ->
msgs3525(_) ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
- EC, M)
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ EC, M)
end,
[{msgs3_name(Name), rfc3525_decode(M), Plain, [{dbg, false}]} ||
{Name, M} <- rfc3525_msgs()].
@@ -4640,10 +5114,10 @@ rfc3525_decode(M) when is_binary(M) ->
msgs5(_) ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
- EC, M)
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ EC, M)
end,
[
{msg51a, msg51a(), Plain, [{dbg, false}]},
@@ -4666,19 +5140,19 @@ msgs5(_) ->
{msg58a, msg58a(), Plain, [{dbg, false}]},
{msg58b, msg58b(), Plain, [{dbg, false}]}
].
-
-
+
+
msgs6(Encoding) ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
EC, M)
- end,
+ end,
- PlainEDFail =
+ PlainEDFail =
fun(Codec, DD, Ver, EC, M) ->
- Res =
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ Res =
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
EC, M),
case Res of
{error, {message_encode_failed, Reason, _M}} ->
@@ -4692,11 +5166,11 @@ msgs6(Encoding) ->
Res
end
end,
-
- PlainDE =
+
+ PlainDE =
fun(Codec, _DD, Ver, EC, B) ->
- Res =
- megaco_codec_test_lib:decode_message(Codec, false, Ver,
+ Res =
+ megaco_codec_test_lib:decode_message(Codec, false, Ver,
EC, B),
case Res of
{ok, M} ->
@@ -4716,7 +5190,7 @@ msgs6(Encoding) ->
end
end,
- Msgs =
+ Msgs =
[
{msg61a, msg61a(), Plain, [{dbg,false}],[text,binary,erlang]},
{msg61b, msg61b(), Plain, [{dbg,false}],[text,binary,erlang]},
@@ -4725,16 +5199,16 @@ msgs6(Encoding) ->
{msg62b, msg62b(), PlainDE, [{dbg,false}],[text]}
],
[{N,M,F,C}||{N,M,F,C,E} <- Msgs,lists:member(Encoding,E)].
-
+
msgs7(Encoding) ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
EC, M)
- end,
+ end,
- Msgs =
+ Msgs =
[
{msg71a, msg71a(), Plain, [{dbg,false}],[text,binary,erlang]},
{msg71b01, msg71b01(), Plain, [{dbg,false}],[text,binary,erlang]},
@@ -4816,19 +5290,19 @@ msgs7(Encoding) ->
{msg78a07, msg78a07(), Plain, [{dbg,false}],[text,binary,erlang]},
{msg78a08, msg78a08(), Plain, [{dbg,false}],[text,binary,erlang]},
{msg78a09, msg78a09(), Plain, [{dbg,false}],[text,binary,erlang]},
- {msg79a01, msg79a01(), Plain, [{dbg,false}],[text,binary,erlang]}
+ {msg79a01, msg79a01(), Plain, [{dbg,true}],[text,binary,erlang]}
],
[{N,M,F,C}||{N,M,F,C,E} <- Msgs,lists:member(Encoding,E)].
-
-
+
+
msgs8(Encoding) ->
- Plain =
+ Plain =
fun(Codec, DD, Ver, EC, M) ->
- megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
+ megaco_codec_test_lib:plain_encode_decode(Codec, DD, Ver,
EC, M)
- end,
+ end,
- Msgs =
+ Msgs =
[
{msg80a01, msg80a01(), Plain, [{dbg,false}],[text,binary,erlang]},
{msg80a02, msg80a02(), Plain, [{dbg,false}],[text,binary,erlang]},
@@ -4844,9 +5318,9 @@ msgs8(Encoding) ->
{msg81b03, msg81b03(), Plain, [{dbg,false}],[text,binary,erlang]}
],
[{N,M,F,C}||{N,M,F,C,E} <- Msgs,lists:member(Encoding,E)].
-
-
-
+
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
msg_actions([], Actions) ->
@@ -4860,7 +5334,7 @@ megaco_trans_req([], Transactions) ->
megaco_trans_req([{TransId, ActionInfo}|TransInfo], Transactions) ->
Actions = msg_actions(ActionInfo, []),
TR = ?MSG_LIB:cre_TransactionRequest(TransId, Actions),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
megaco_trans_req(TransInfo, [Trans|Transactions]).
megaco_message(Version, Mid, Body) ->
@@ -4875,7 +5349,7 @@ msg_request(Mid, TransId, ContextId, CmdReq) ->
Action = ?MSG_LIB:cre_ActionRequest(ContextId, CmdReq),
Actions = [Action],
TR = ?MSG_LIB:cre_TransactionRequest(TransId, Actions),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, Mid, [Trans]),
cre_MegacoMessage(Mess).
@@ -4883,19 +5357,19 @@ msg_request(Auth, Mid, TransId, ContextId, CmdReq) ->
Action = ?MSG_LIB:cre_ActionRequest(ContextId, CmdReq),
Actions = [Action],
TR = ?MSG_LIB:cre_TransactionRequest(TransId, Actions),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, Mid, [Trans]),
cre_MegacoMessage(Auth, Mess).
msg_reply(Mid, TransId, Actions) ->
TR = cre_TransRep(TransId, Actions),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, Mid, [Trans]),
cre_MegacoMessage(Mess).
msg_reply(Mid, TransId, ContextId, CmdReply) ->
Action = cre_ActRep(ContextId, CmdReply),
- Actions = [Action],
+ Actions = [Action],
msg_reply(Mid, TransId, Actions).
msg_ack(Mid, [Range|_] = Ranges) when is_tuple(Range) ->
@@ -4921,7 +5395,7 @@ make_tra(Ranges) ->
msg1(Mid, Tid) ->
Gain = cre_PropParm("tdmc/gain", "2"),
- Ec = cre_PropParm("tdmc/ec", "g165"),
+ Ec = cre_PropParm("tdmc/ec", "g165"),
LCD = cre_LocalControlDesc(sendRecv,[Gain, Ec]),
V = cre_PropParm("v", "0"),
%% C = cre_PropParm("c", "IN IP4 $ "),
@@ -4961,7 +5435,7 @@ msg2(Mid) ->
msg2(Mid, ?A4444).
msg2(Mid, Tid) ->
Gain = cre_PropParm("tdmc/gain", "2"),
- Ec = cre_PropParm("tdmc/ec", "g165"),
+ Ec = cre_PropParm("tdmc/ec", "g165"),
LCD = cre_LocalControlDesc(sendRecv,[Gain, Ec]),
V = cre_PropParm("v", "0"),
%% C = cre_PropParm("c", "IN IP4 $ "),
@@ -5198,7 +5672,7 @@ msg13(Mid) ->
msg14() ->
msg14(?MGC_MID).
msg14(Mid) ->
- Signal = cre_Sig("cg/rt"),
+ Signal = cre_Sig("cg/rt"),
AmmReq1 = cre_AmmReq([#megaco_term_id{id = ?A4444}],
[{signalsDescriptor, [{signal, Signal}]}]),
CmdReq1 = cre_CmdReq({modReq, AmmReq1}),
@@ -5363,11 +5837,11 @@ msg22(Mid, N) ->
Audits = [{mediaDescriptor, Media},
{packagesDescriptor, [PackagesItem, PackagesItem2]},
{statisticsDescriptor, Statistics}],
- Reply = {auditResult,
+ Reply = {auditResult,
cre_AuditRes(#megaco_term_id{id = ?A5556},Audits)},
- msg_reply(Mid, 50007, ?megaco_null_context_id,
+ msg_reply(Mid, 50007, ?megaco_null_context_id,
lists:duplicate(N,{auditValueReply, Reply})).
-%% msg_reply(Mid, 50007, ?megaco_null_context_id,
+%% msg_reply(Mid, 50007, ?megaco_null_context_id,
%% lists.duplicate([{auditValueReply, Reply}]).
@@ -5468,7 +5942,7 @@ msg25(Mid) ->
Stats2 = [Stat21, Stat22, Stat23, Stat24, Stat25, Stat26, Stat27],
Reply2 = cre_AmmsReply([#megaco_term_id{id = ?A5556}],
[{statisticsDescriptor, Stats2}]),
- msg_reply(Mid, 50009, 5000,
+ msg_reply(Mid, 50009, 5000,
[{subtractReply, Reply1}, {subtractReply, Reply2}]).
@@ -5479,7 +5953,7 @@ msg30b() ->
msg_ack(?MG2_MID, [{9,13}]).
msg30c() ->
- msg_ack(?MG2_MID,
+ msg_ack(?MG2_MID,
[{9,13}, {15,15}, {33,40}, {50,60}, {70,80}, {85,90},
{101,105},{109,119},{121,130},{140,160},{170,175},{180,189},
{201,205},{209,219},{221,230},{240,260},{270,275},{280,289},
@@ -5491,7 +5965,7 @@ msg30c() ->
%% Don't think this will be used by the megaco stack, but since it
%% seem's to be a valid construction...
msg30d() ->
- msg_ack(?MG2_MID,
+ msg_ack(?MG2_MID,
[[{9,13}, {15,15}, {33,40}, {50,60}, {70,80}, {85,90}],
[{101,105},{109,119},{121,130},{140,160},{170,175},{180,189}],
[{201,205},{209,219},{221,230},{240,260},{270,275},{280,289}],
@@ -5501,7 +5975,7 @@ msg30d() ->
]).
-
+
msg40() ->
msg40(?MG1_MID_NO_PORT, "901 mg col boot").
msg40(Mid, Reason) when is_list(Reason) ->
@@ -5519,14 +5993,14 @@ msg50(Mid, APT) ->
Req = cre_AuditReq(#megaco_term_id{id = ?A5556},AD),
CmdReq = cre_CmdReq({auditValueRequest, Req}),
msg_request(Mid, 50007, ?megaco_null_context_id, [CmdReq]).
-
+
%% IndAudMediaDescriptor:
msg51(Mid, IATSDorStream) ->
IAMD = cre_IndAudMediaDesc(IATSDorStream),
IAP = cre_IndAudParam(IAMD),
APT = [IAP],
msg50(Mid, APT).
-
+
msg51a() ->
msg51a(?MG2_MID).
msg51a(Mid) ->
@@ -5558,7 +6032,7 @@ msg51d(Mid) ->
msg51e() ->
msg51e(?MG2_MID).
msg51e(Mid) ->
- IALCD = cre_IndAudLocalControlDesc('NULL', asn1_NOVALUE,
+ IALCD = cre_IndAudLocalControlDesc('NULL', asn1_NOVALUE,
asn1_NOVALUE, asn1_NOVALUE),
IASP = cre_IndAudStreamParms(IALCD),
msg51(Mid, IASP).
@@ -5566,7 +6040,7 @@ msg51e(Mid) ->
msg51f() ->
msg51f(?MG2_MID).
msg51f(Mid) ->
- IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, 'NULL',
+ IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, 'NULL',
asn1_NOVALUE, asn1_NOVALUE),
IASP = cre_IndAudStreamParms(IALCD),
msg51(Mid, IASP).
@@ -5574,7 +6048,7 @@ msg51f(Mid) ->
msg51g() ->
msg51g(?MG2_MID).
msg51g(Mid) ->
- IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, asn1_NOVALUE,
+ IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, asn1_NOVALUE,
'NULL', asn1_NOVALUE),
IASP = cre_IndAudStreamParms(IALCD),
msg51(Mid, IASP).
@@ -5584,7 +6058,7 @@ msg51h() ->
msg51h(Mid) ->
Name = "nt/jit",
IAPP = cre_IndAudPropertyParm(Name),
- IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, asn1_NOVALUE,
+ IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, asn1_NOVALUE,
asn1_NOVALUE, [IAPP]),
IASP = cre_IndAudStreamParms(IALCD),
SID = 123,
@@ -5599,7 +6073,7 @@ msg51i(Mid) ->
Name2 = "tdmc/ec",
IAPP = cre_IndAudPropertyParm(Name),
IAPP2 = cre_IndAudPropertyParm(Name2),
- IALCD = cre_IndAudLocalControlDesc('NULL', 'NULL', 'NULL',
+ IALCD = cre_IndAudLocalControlDesc('NULL', 'NULL', 'NULL',
[IAPP, IAPP2]),
IASP = cre_IndAudStreamParms(IALCD),
SID = 123,
@@ -5635,7 +6109,7 @@ msg54(Mid, Sig) ->
IAP = cre_IndAudParam(IASD),
APT = [IAP],
msg50(Mid, APT).
-
+
msg54a() ->
msg54a(?MG2_MID).
msg54a(Mid) ->
@@ -5677,7 +6151,7 @@ msg56(Mid) ->
IASD = cre_IndAudStatsDesc(SN),
IAP = cre_IndAudParam(IASD),
APT = [IAP],
- msg50(Mid, APT).
+ msg50(Mid, APT).
%% IndAudPackagesDescriptor:
msg57() ->
@@ -5688,30 +6162,30 @@ msg57(Mid) ->
IAPD = cre_IndAudPkgsDesc(PN, PV),
IAP = cre_IndAudParam(IAPD),
APT = [IAP],
- msg50(Mid, APT).
-
+ msg50(Mid, APT).
+
%% Sum it up:
msg58_iaMediaDesc_iap(IATSD) ->
IAMD = cre_IndAudMediaDesc(IATSD),
cre_IndAudParam(IAMD).
-
+
msg58_iaMediaDesc_iap_a() ->
PP = cre_IndAudPropertyParm("tdmc/gain"),
PPs = [PP],
IATSD = cre_IndAudTermStateDesc(PPs),
msg58_iaMediaDesc_iap(IATSD).
-
+
msg58_iaMediaDesc_iap_b() ->
IATSD = cre_IndAudTermStateDesc([], 'NULL', asn1_NOVALUE),
msg58_iaMediaDesc_iap(IATSD).
-
+
msg58_iaEvsDesc_iap() ->
RequestID = 1235,
PkgdName = "tonedet/std",
IAED = cre_IndAudEvsDesc(RequestID, PkgdName),
cre_IndAudParam(IAED).
-msg58_iaEvBufDesc_iap() ->
+msg58_iaEvBufDesc_iap() ->
EN = "tonedet/std",
SID = 1,
IAEBD = cre_IndAudEvBufDesc(EN, SID),
@@ -5725,7 +6199,7 @@ msg58_iaSigsDesc_iap_a() ->
SN = "tonegen/pt",
Sig = cre_IndAudSig(SN),
msg58_iaSigsDesc_iap(Sig).
-
+
msg58_iaSigsDesc_iap_b() ->
SN = "ct/ct",
Sig = cre_IndAudSig(SN),
@@ -5737,7 +6211,7 @@ msg58_iaDigMapDesc_iap() ->
DMN = "dialplan00",
IADMD = cre_IndAudDigitMapDesc(DMN),
cre_IndAudParam(IADMD).
-
+
msg58_iaStatsDesc_iap() ->
SN = "nt/dur",
IASD = cre_IndAudStatsDesc(SN),
@@ -5776,7 +6250,7 @@ msg58b(Mid) ->
msg50(Mid, APT).
-%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
%% Tests some of the changes in the v2 corr 1 (EmergencyOff and ModemDesc)
%% Emergency On/Off (optional) tests
@@ -5791,7 +6265,7 @@ msg61(EM) ->
ActReq = ?MSG_LIB:cre_ActionRequest(1, CtxReq, [CmdReq]),
Acts = [ActReq],
TR = ?MSG_LIB:cre_TransactionRequest(9898, Acts),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, ?MG1_MID, [Trans]),
cre_MegacoMessage(Mess).
@@ -5817,12 +6291,12 @@ msg62a() ->
ActReq = ?MSG_LIB:cre_ActionRequest(2, [CmdReq]),
Acts = [ActReq],
TR = ?MSG_LIB:cre_TransactionRequest(9898, Acts),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, ?MG1_MID, [Trans]),
cre_MegacoMessage(Mess).
msg62b() ->
- MP =
+ MP =
"MEGACO/" ?VERSION_STR " [124.124.124.222]:55555
Transaction = 9898 {
Context = 2 {
@@ -5833,7 +6307,7 @@ Transaction = 9898 {
}
}
}",
-% MC =
+% MC =
% "!/" ?VERSION_STR " [124.124.124.222]:55555\nT=9898{C=2{A=11111111/00000000/00000000{MD[V18]{tdmc/gain=2}}}}",
list_to_binary(MP).
@@ -5855,31 +6329,31 @@ msg71(CR, CAAR) ->
ActReq = ?MSG_LIB:cre_ActionRequest(1, CR, CAAR, [CR1, CR2]),
Acts = [ActReq],
TR = ?MSG_LIB:cre_TransactionRequest(9898, Acts),
- Trans = ?MSG_LIB:cre_Transaction(TR),
+ Trans = ?MSG_LIB:cre_Transaction(TR),
Mess = ?MSG_LIB:cre_Message(?VERSION, ?MG1_MID, [Trans]),
cre_MegacoMessage(Mess).
-
+
msg71a() ->
CR = cre_CtxReq(),
CAAR = cre_CtxAttrAuditReq(),
msg71(CR, CAAR).
-msg71b(CR) ->
+msg71b(CR) ->
CAAR = asn1_NOVALUE,
msg71(CR, CAAR).
msg71b01() ->
CR = cre_CtxReq(15),
msg71b(CR).
-
+
msg71b02() ->
CR = cre_CtxReq(true),
msg71b(CR).
-
+
msg71b03() ->
CR = cre_CtxReq(false),
msg71b(CR).
-
+
msg71b04() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -5892,15 +6366,15 @@ msg71b04() ->
Top = [Top1, Top2],
CR = cre_CtxReq(Top),
msg71b(CR).
-
+
msg71b05() ->
CR = cre_CtxReq(15, true),
msg71b(CR).
-
+
msg71b06() ->
CR = cre_CtxReq(15, false),
msg71b(CR).
-
+
msg71b07() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -5913,7 +6387,7 @@ msg71b07() ->
Top = [Top1, Top2],
CR = cre_CtxReq(15, Top),
msg71b(CR).
-
+
msg71b08() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -5926,7 +6400,7 @@ msg71b08() ->
Top = [Top1, Top2],
CR = cre_CtxReq(15, true, Top),
msg71b(CR).
-
+
msg71b09() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -5939,7 +6413,7 @@ msg71b09() ->
Top = [Top1, Top2],
CR = cre_CtxReq(15, false, Top),
msg71b(CR).
-
+
msg71b10() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -5952,7 +6426,7 @@ msg71b10() ->
Top = [Top1, Top2],
CR = cre_CtxReq(15, false, Top),
msg71b(CR).
-
+
msg71b11() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -5965,7 +6439,7 @@ msg71b11() ->
Top = [Top1, Top2],
CR = cre_CtxReq(15, false, Top),
msg71b(CR).
-
+
msg71b12() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -5978,7 +6452,7 @@ msg71b12() ->
Top = [Top1, Top2],
CR = cre_CtxReq(15, true, Top, true),
msg71b(CR).
-
+
msg71b13() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -5991,7 +6465,7 @@ msg71b13() ->
Top = [Top1, Top2],
CR = cre_CtxReq(15, true, Top, false),
msg71b(CR).
-
+
msg71b14() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -6006,7 +6480,7 @@ msg71b14() ->
Props = [PP],
CR = cre_CtxReq(15, true, Top, Props),
msg71b(CR).
-
+
msg71b15() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -6021,7 +6495,7 @@ msg71b15() ->
Props = [PP],
CR = cre_CtxReq(15, true, Top, Props),
msg71b(CR).
-
+
msg71b16() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -6032,11 +6506,11 @@ msg71b16() ->
Dir2 = bothway,
Top2 = cre_TopologyRequest(From2, To2, Dir2),
Top = [Top1, Top2],
- PP = cre_PropParm("tdmc/gain", ["2","10"], range, true),
+ PP = cre_PropParm("tdmc/gain", ["2","10"], range, true),
Props = [PP],
CR = cre_CtxReq(15, true, Top, Props),
msg71b(CR).
-
+
msg71b17() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -6051,7 +6525,7 @@ msg71b17() ->
Props = [PP],
CR = cre_CtxReq(15, true, Top, Props),
msg71b(CR).
-
+
msg71b18() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -6062,11 +6536,11 @@ msg71b18() ->
Dir2 = isolate,
Top2 = cre_TopologyRequest(From2, To2, Dir2),
Top = [Top1, Top2],
- PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
+ PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
Props = [PP],
CR = cre_CtxReq(15, true, Top, true, Props),
msg71b(CR).
-
+
msg71b19() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -6079,7 +6553,7 @@ msg71b19() ->
Top = [Top1, Top2],
CR = cre_CtxReq(15, true, Top, false),
msg71b(CR).
-
+
msg71b20() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -6090,11 +6564,11 @@ msg71b20() ->
Dir2 = isolate,
Top2 = cre_TopologyRequest(From2, To2, Dir2),
Top = [Top1, Top2],
- PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
+ PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
Props = [PP],
CR = cre_CtxReq(15, true, Top, false, Props),
msg71b(CR).
-
+
msg71b21() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -6110,7 +6584,7 @@ msg71b21() ->
CIDs = [CID1, CID2],
CR = cre_CtxReq(15, true, Top, false, CIDs),
msg71b(CR).
-
+
msg71b22() ->
From1 = #megaco_term_id{id = ["11111111", "00000000", "00000000"]},
To1 = #megaco_term_id{id = ["11111111", "00000000", "00001111"]},
@@ -6121,18 +6595,18 @@ msg71b22() ->
Dir2 = isolate,
Top2 = cre_TopologyRequest(From2, To2, Dir2),
Top = [Top1, Top2],
- PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
+ PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
Props = [PP],
CID1 = cre_CtxID(10191),
CID2 = cre_CtxID(10192),
CIDs = [CID1, CID2],
CR = cre_CtxReq(15, true, Top, false, Props, CIDs),
msg71b(CR).
-
+
msg71c(CAAR) ->
CR = asn1_NOVALUE,
msg71(CR, CAAR).
-
+
msg71c01() ->
CAAR = cre_CtxAttrAuditReq('NULL', 'NULL', 'NULL'),
msg71c(CAAR).
@@ -6198,7 +6672,7 @@ msg71c11() ->
CPA = [IAPP1, IAPP2],
SPrio = 10,
SEm = true,
- CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
+ CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
SPrio, SEm, asn1_NOVALUE, asn1_NOVALUE),
msg71c(CAAR).
@@ -6213,7 +6687,7 @@ msg71c12() ->
SPrio = 10,
SEm = true,
SIeps = false,
- CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
+ CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
SPrio, SEm, SIeps),
msg71c(CAAR).
@@ -6229,7 +6703,7 @@ msg71c13() ->
SEm = false,
SIeps = true,
SLog = cre_SelectLogic(andAUDITSelect),
- CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
+ CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
SPrio, SEm, SIeps, SLog),
msg71c(CAAR).
@@ -6245,7 +6719,7 @@ msg71c14() ->
SEm = true,
SIeps = true,
SLog = cre_SelectLogic(orAUDITSelect),
- CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
+ CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
SPrio, SEm, SIeps, SLog),
msg71c(CAAR).
@@ -6259,7 +6733,7 @@ msg71c15() ->
CPA = [IAPP1, IAPP2],
SPrio = 10,
SLog = cre_SelectLogic(orAUDITSelect),
- CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
+ CAAR = cre_CtxAttrAuditReq(Top, Em, Prio, Ieps, CPA,
SPrio, SLog),
msg71c(CAAR).
@@ -6290,7 +6764,7 @@ msg71d02() ->
IAPP2 = cre_IndAudPropertyParm("nt/jit"),
CPA = [IAPP1, IAPP2],
CAAR = cre_CtxAttrAuditReq(CAAR_Top, Em, Prio, Ieps, CPA),
-
+
msg71(CR, CAAR).
msg71d03() ->
@@ -6315,7 +6789,7 @@ msg71d03() ->
IAPP2 = cre_IndAudPropertyParm("nt/jit"),
CPA = [IAPP1, IAPP2],
CAAR = cre_CtxAttrAuditReq(CAAR_Top, Em, Prio, Ieps, CPA),
-
+
msg71(CR, CAAR).
msg71d04() ->
@@ -6335,7 +6809,7 @@ msg71d04() ->
CIDs = [CID1, CID2],
CR = cre_CtxReq(15, true, Top, false, Props, CIDs),
- CAAR_Top = 'NULL',
+ CAAR_Top = 'NULL',
Em = 'NULL',
Prio = 'NULL',
Ieps = 'NULL',
@@ -6346,7 +6820,7 @@ msg71d04() ->
SEm = true,
SIeps = true,
SLog = cre_SelectLogic(orAUDITSelect),
- CAAR = cre_CtxAttrAuditReq(CAAR_Top, Em, Prio, Ieps, CPA,
+ CAAR = cre_CtxAttrAuditReq(CAAR_Top, Em, Prio, Ieps, CPA,
SPrio, SEm, SIeps, SLog),
msg71(CR, CAAR).
@@ -6484,7 +6958,7 @@ msg72c03() ->
Dir2 = isolate,
Top2 = cre_TopologyRequest(From2, To2, Dir2),
Top = [Top1, Top2],
- PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
+ PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
Props = [PP],
CR = cre_CtxReq(15, true, Top, true, Props),
msg72c(CR).
@@ -6499,7 +6973,7 @@ msg72c04() ->
Dir2 = isolate,
Top2 = cre_TopologyRequest(From2, To2, Dir2),
Top = [Top1, Top2],
- PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
+ PP = cre_PropParm("tdmc/gain", ["2","4","8"], sublist, false),
Props = [PP],
CR = cre_CtxReq(15, true, Top, false, Props),
msg72c(CR).
@@ -6533,7 +7007,7 @@ msg73a() ->
Mid = ?MG1_MID,
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
-
+
%% StatisticsDescriptor in IndAudStreamParms
msg73b1() ->
@@ -6556,19 +7030,19 @@ msg73b2(IAMD) ->
Mid = ?MG1_MID,
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
-
+
msg73b01() ->
IASP = msg73b1(),
IAMD = cre_IndAudMediaDesc(IASP),
msg73b2(IAMD).
-
+
msg73b02() ->
IASP = msg73b1(),
SID = cre_StreamID(303),
IASD = cre_IndAudStreamDesc(SID, IASP),
IAMD = cre_IndAudMediaDesc([IASD]),
msg73b2(IAMD).
-
+
%% StatisticsDescriptor in StreamParms
msg73c1() ->
StatDesc = msg73(),
@@ -6626,7 +7100,7 @@ msg74a4(Sig) ->
SR = cre_SigReq(Sig),
SD = cre_SigsDesc([SR]),
AD = cre_AmmDesc(SD),
- TermIDs = [#megaco_term_id{id = ?A4444}],
+ TermIDs = [#megaco_term_id{id = ?A4444}],
AR = cre_AmmReq(TermIDs, [AD]),
Cmd = cre_Cmd(modReq, AR),
cre_CmdReq(Cmd).
@@ -6643,7 +7117,7 @@ msg74a01() ->
Mid = ?MG1_MID,
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
-
+
msg74a02() ->
Sig = msg74a1(both),
CR = msg74a4(Sig),
@@ -6656,7 +7130,7 @@ msg74a02() ->
Mid = ?MG1_MID,
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
-
+
msg74a03() ->
RID = cre_ReqID(7433),
Sig = msg74a2(external, RID),
@@ -6670,7 +7144,7 @@ msg74a03() ->
Mid = ?MG1_MID,
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
-
+
msg74a04() ->
RID = cre_ReqID(7434),
Sig = msg74a2(both, RID),
@@ -6684,7 +7158,7 @@ msg74a04() ->
Mid = ?MG1_MID,
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
-
+
msg74a05() ->
RID = cre_ReqID(7435),
Sig = msg74a3(both, RID),
@@ -6698,7 +7172,7 @@ msg74a05() ->
Mid = ?MG1_MID,
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
-
+
msg74a06() ->
RID = cre_ReqID(7436),
Sig = msg74a3(internal, RID),
@@ -6712,8 +7186,8 @@ msg74a06() ->
Mid = ?MG1_MID,
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
-
-
+
+
%% New ServiceChangeParm (serviceChangeIncompleteFlag); msg75
msg75a(IncFlag) ->
@@ -6722,7 +7196,7 @@ msg75a(IncFlag) ->
Reason = "901 mg col boot",
Profile = cre_SvcChProf("resgw",1),
Parm = cre_SvcChParm(Method, Address, [Reason], Profile, IncFlag),
- TermIDs = [?megaco_root_termination_id],
+ TermIDs = [?megaco_root_termination_id],
Req = cre_SvcChReq(TermIDs, Parm),
Cmd = cre_Cmd(serviceChangeReq, Req),
CR = cre_CmdReq(Cmd),
@@ -6764,22 +7238,22 @@ msg76a01() ->
Name2 = "tdmc/ec",
IAPP2 = cre_IndAudPropertyParm(Name2),
SMS = cre_StreamMode(recvOnly),
- IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, 'NULL', 'NULL',
+ IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, 'NULL', 'NULL',
[IAPP1, IAPP2], SMS),
msg76a(IALCD).
-
+
msg76a02() ->
Name1 = "tdmc/gain",
- PP1 = cre_PropParm("tdmc/gain", "2"),
+ PP1 = cre_PropParm("tdmc/gain", "2"),
IAPP1 = cre_IndAudPropertyParm(Name1, PP1),
Name2 = "tdmc/gain",
- PP2 = cre_PropParm("tdmc/gain", "3"),
+ PP2 = cre_PropParm("tdmc/gain", "3"),
IAPP2 = cre_IndAudPropertyParm(Name2, PP2),
SMS = cre_StreamMode(recvOnly),
- IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, 'NULL', 'NULL',
+ IALCD = cre_IndAudLocalControlDesc(asn1_NOVALUE, 'NULL', 'NULL',
[IAPP1, IAPP2], SMS),
msg76a(IALCD).
-
+
%% IndAudTerminationStateDescription + ServiceState
msg76b(IATSD) ->
IAMD = cre_IndAudMediaDesc(IATSD),
@@ -6802,9 +7276,9 @@ msg77a01() ->
SN = "tonegen/pt",
Sig = cre_IndAudSig(SN, 7701),
msg54(?MG2_MID, Sig).
-
-msg78a(Events) ->
+
+msg78a(Events) ->
EventsDesc = cre_EvsDesc(2223, Events),
Signal = cre_Sig("cg/rt"),
Name = "dialplan00",
@@ -6817,7 +7291,7 @@ msg78a(Events) ->
{digitMapDescriptor, DigMapDesc}]),
CmdReq = cre_CmdReq({modReq, AmmReq}),
msg_request(?MG2_MID, 10001, ?megaco_null_context_id, [CmdReq]).
-
+
msg78a01() ->
Name1 = "al/on",
Strict = cre_EvParm("strict", ["state"]),
@@ -6828,9 +7302,9 @@ msg78a01() ->
KA2 = true,
EDM2 = cre_EvDM("dialplan00"),
NB2 = cre_NotifBehav(notifyImmediate, 'NULL'),
- RED2 = asn1_NOVALUE,
+ RED2 = asn1_NOVALUE,
RA2 = cre_ReqActs(KA2, EDM2, asn1_NOVALUE, asn1_NOVALUE, NB2, RED2),
- EPL2 = EPL1,
+ EPL2 = EPL1,
RE2 = cre_ReqEv(Name2, SID2, RA2, EPL2),
msg78a([RE1, RE2]).
@@ -6844,9 +7318,9 @@ msg78a02() ->
KA2 = true,
EDM2 = cre_EvDM("dialplan00"),
NB2 = cre_NotifBehav(neverNotify, 'NULL'),
- RED2 = asn1_NOVALUE,
+ RED2 = asn1_NOVALUE,
RA2 = cre_ReqActs(KA2, EDM2, asn1_NOVALUE, asn1_NOVALUE, NB2, RED2),
- EPL2 = EPL1,
+ EPL2 = EPL1,
RE2 = cre_ReqEv(Name2, SID2, RA2, EPL2),
msg78a([RE1, RE2]).
@@ -6861,9 +7335,9 @@ msg78a03() ->
EDM2 = cre_EvDM("dialplan00"),
RED2 = cre_RegEmbedDesc(),
NB2 = cre_NotifBehav(notifyRegulated, RED2),
- REvD2 = asn1_NOVALUE,
+ REvD2 = asn1_NOVALUE,
RA2 = cre_ReqActs(KA2, EDM2, asn1_NOVALUE, asn1_NOVALUE, NB2, REvD2),
- EPL2 = EPL1,
+ EPL2 = EPL1,
RE2 = cre_ReqEv(Name2, SID2, RA2, EPL2),
msg78a([RE1, RE2]).
@@ -6879,9 +7353,9 @@ msg78a04() ->
SED2 = cre_SecEvsDesc(7814, [SRE2]),
RED2 = cre_RegEmbedDesc(SED2),
NB2 = cre_NotifBehav(notifyRegulated, RED2),
- REvD2 = asn1_NOVALUE,
+ REvD2 = asn1_NOVALUE,
RA2 = cre_ReqActs(KA2, EDM2, asn1_NOVALUE, asn1_NOVALUE, NB2, REvD2),
- EPL2 = EPL1,
+ EPL2 = EPL1,
RE2 = cre_ReqEv(Name2, 7824, RA2, EPL2),
msg78a([RE1, RE2]).
@@ -6897,9 +7371,9 @@ msg78a05() ->
SD2 = cre_SigsDesc(),
RED2 = cre_RegEmbedDesc(SD2),
NB2 = cre_NotifBehav(notifyRegulated, RED2),
- REvD2 = asn1_NOVALUE,
+ REvD2 = asn1_NOVALUE,
RA2 = cre_ReqActs(KA2, EDM2, asn1_NOVALUE, asn1_NOVALUE, NB2, REvD2),
- EPL2 = EPL1,
+ EPL2 = EPL1,
RE2 = cre_ReqEv(Name2, SID2, RA2, EPL2),
msg78a([RE1, RE2]).
@@ -6913,15 +7387,15 @@ msg78a06() ->
EDM2 = cre_EvDM("dialplan00"),
SRE2 = cre_SecReqEv("al/on"),
SED2 = cre_SecEvsDesc(7816, [SRE2]),
- Sig2 = cre_Sig("cg/rt", external, asn1_NOVALUE),
- SR2 = cre_SigReq(Sig2),
+ Sig2 = cre_Sig("cg/rt", external, asn1_NOVALUE),
+ SR2 = cre_SigReq(Sig2),
SRs2 = [SR2],
SD2 = cre_SigsDesc(SRs2),
RED2 = cre_RegEmbedDesc(SED2, SD2),
NB2 = cre_NotifBehav(notifyRegulated, RED2),
- REvD2 = asn1_NOVALUE,
+ REvD2 = asn1_NOVALUE,
RA2 = cre_ReqActs(KA2, EDM2, asn1_NOVALUE, asn1_NOVALUE, NB2, REvD2),
- EPL2 = EPL1,
+ EPL2 = EPL1,
RE2 = cre_ReqEv(Name2, 7826, RA2, EPL2),
msg78a([RE1, RE2]).
@@ -6935,15 +7409,15 @@ msg78a07() ->
EDM2 = cre_EvDM("dialplan00"),
SRE2 = cre_SecReqEv("al/on"),
SED2 = cre_SecEvsDesc(7817, [SRE2]),
- Sig2 = cre_Sig("cg/rt", external, asn1_NOVALUE),
- SR2 = cre_SigReq(Sig2),
+ Sig2 = cre_Sig("cg/rt", external, asn1_NOVALUE),
+ SR2 = cre_SigReq(Sig2),
SRs2 = [SR2],
SD2 = cre_SigsDesc(SRs2),
RED2 = cre_RegEmbedDesc(SED2, SD2),
NB2 = cre_NotifBehav(notifyRegulated, RED2),
- REvD2 = 'NULL',
+ REvD2 = 'NULL',
RA2 = cre_ReqActs(KA2, EDM2, asn1_NOVALUE, asn1_NOVALUE, NB2, REvD2),
- EPL2 = EPL1,
+ EPL2 = EPL1,
RE2 = cre_ReqEv(Name2, 7827, RA2, EPL2),
msg78a([RE1, RE2]).
@@ -6957,8 +7431,8 @@ msg78a08() ->
KA2 = true,
EDM2 = cre_EvDM("dialplan00"),
- Sig21 = cre_Sig("al/ri", both, asn1_NOVALUE),
- SR21 = cre_SigReq(Sig21),
+ Sig21 = cre_Sig("al/ri", both, asn1_NOVALUE),
+ SR21 = cre_SigReq(Sig21),
SRs21 = [SR21],
SD21 = cre_SigsDesc(SRs21),
RED21 = cre_RegEmbedDesc(SD21),
@@ -6966,17 +7440,17 @@ msg78a08() ->
SRA2 = cre_SecReqActs(KA2, EDM2, asn1_NOVALUE, NB21, 'NULL'),
SRE2 = cre_SecReqEv("al/of", 7816, SRA2),
SED2 = cre_SecEvsDesc(7826, [SRE2]),
-
- Sig22 = cre_Sig("cg/rt", external, asn1_NOVALUE),
- SR22 = cre_SigReq(Sig22),
+
+ Sig22 = cre_Sig("cg/rt", external, asn1_NOVALUE),
+ SR22 = cre_SigReq(Sig22),
SRs22 = [SR22],
SD22 = cre_SigsDesc(SRs22),
RED22 = cre_RegEmbedDesc(SED2, SD22),
NB22 = cre_NotifBehav(notifyRegulated, RED22),
- REvD2 = 'NULL',
+ REvD2 = 'NULL',
RA2 = cre_ReqActs(KA2, EDM2, asn1_NOVALUE, asn1_NOVALUE, NB22, REvD2),
- EPL2 = EPL1,
+ EPL2 = EPL1,
RE2 = cre_ReqEv(Name2, 7836, RA2, EPL2),
msg78a([RE1, RE2]).
@@ -6999,9 +7473,9 @@ msg78a09() ->
Dir21 = cre_SigDir(both),
RID21 = cre_ReqID(7829),
- Sig21 = cre_Sig("cg/rt", SID21, ST21, Dur21, NC21, KA21, SPL21, Dir21,
+ Sig21 = cre_Sig("cg/rt", SID21, ST21, Dur21, NC21, KA21, SPL21, Dir21,
RID21, 7839),
- SR21 = cre_SigReq(Sig21),
+ SR21 = cre_SigReq(Sig21),
SRs21 = [SR21],
SD21 = cre_SigsDesc(SRs21),
RED21 = cre_RegEmbedDesc(SD21),
@@ -7010,7 +7484,7 @@ msg78a09() ->
SRA2 = cre_SecReqActs(KA2, EDM2, asn1_NOVALUE, NB21, 'NULL'),
SRE2 = cre_SecReqEv("al/of", 7849, SRA2),
SED2 = cre_SecEvsDesc(7859, [SRE2]),
-
+
SID22 = cre_StreamID(7869),
ST22 = cre_SigType(brief),
Dur22 = 17809,
@@ -7020,18 +7494,18 @@ msg78a09() ->
Dir22 = cre_SigDir(external),
RID22 = cre_ReqID(7879),
- Sig22 = cre_Sig("cg/rt", SID22, ST22, Dur22, NC22, KA22, SPL22, Dir22,
+ Sig22 = cre_Sig("cg/rt", SID22, ST22, Dur22, NC22, KA22, SPL22, Dir22,
RID22, 7889),
- SR22 = cre_SigReq(Sig22),
+ SR22 = cre_SigReq(Sig22),
SRs22 = [SR22],
SD22 = cre_SigsDesc(SRs22),
RED22 = cre_RegEmbedDesc(SED2, SD22),
NB22 = cre_NotifBehav(notifyRegulated, RED22),
- REvD2 = 'NULL',
+ REvD2 = 'NULL',
RA2 = cre_ReqActs(KA2, EDM2, asn1_NOVALUE, asn1_NOVALUE, NB22, REvD2),
- EPL2 = EPL1,
+ EPL2 = EPL1,
RE2 = cre_ReqEv(Name2, 7899, RA2, EPL2),
msg78a([RE1, RE2]).
@@ -7039,7 +7513,7 @@ msg78a09() ->
msg79a01() ->
TID1 = #megaco_term_id{id = ?A4444},
TID2 = #megaco_term_id{id = ?A4445},
- TID3 = #megaco_term_id{id = ?A5555},
+ TID3 = #megaco_term_id{id = ?A5555},
TIDs = cre_TermIDList([TID1, TID2, TID3]),
ErC = cre_ErrCode(?megaco_not_ready),
ErD = cre_ErrDesc(ErC),
@@ -7069,7 +7543,7 @@ msg80() ->
{serviceChangeResParms, Parm}),
CmdRep = cre_CmdRep(serviceChangeReply, Reply),
cre_ActRep(80, [CmdRep]).
-
+
msg80a(Mid, SN) ->
TransId = 8000,
ActRep = msg80(),
@@ -7106,7 +7580,7 @@ msg80b03() ->
msg81a(Mid, SN) ->
TransId = 8101,
- SegReply = cre_SegRep(TransId, SN),
+ SegReply = cre_SegRep(TransId, SN),
Trans = cre_Trans(SegReply),
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
@@ -7122,7 +7596,7 @@ msg81a03() ->
msg81b(Mid, SN) ->
TransId = 8102,
- SegReply = cre_SegRep(TransId, SN, 'NULL'),
+ SegReply = cre_SegRep(TransId, SN, 'NULL'),
Trans = cre_Trans(SegReply),
Mess = cre_Msg(Mid, [Trans]),
cre_MegacoMessage(Mess).
@@ -7136,8 +7610,8 @@ msg81b02() ->
msg81b03() ->
msg81b(?MG3_MID, 65535).
-
-%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
%% Pretty RFC 3525 messages:
%% Added Reason
@@ -7148,11 +7622,11 @@ rfc3525_msg1() ->
Services {
Method = Restart,
Reason = 901,
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
}
}
- }
+ }
}".
rfc3525_msg2() ->
@@ -7160,11 +7634,11 @@ rfc3525_msg2() ->
Context = - {
ServiceChange = ROOT {
Services {
- ServiceChangeAddress = 55555,
+ ServiceChangeAddress = 55555,
Profile = ResGW/1
- }
- }
- }
+ }
+ }
+ }
}".
@@ -7173,7 +7647,7 @@ rfc3525_msg3() ->
"MEGACO/" ?VERSION_STR " [123.123.123.4]:55555 Transaction = 9999 {
Context = - {
Modify = A4444 {
- Media {
+ Media {
Stream = 1 {
LocalControl {
Mode = SendReceive,
@@ -7205,15 +7679,15 @@ rfc3525_msg6() ->
19990729T22000000:al/of{init=false}
}
}
- }
+ }
}".
-
+
rfc3525_msg7() ->
"MEGACO/" ?VERSION_STR " [123.123.123.4]:55555 Reply = 10000 {
Context = - {
Notify = A4444
- }
+ }
}".
rfc3525_msg8() ->
@@ -7221,11 +7695,11 @@ rfc3525_msg8() ->
Context = - {
Modify = A4444 {
Events = 2223 {
- al/on {strict=state},
+ al/on {strict=state},
dd/ce {DigitMap=Dialplan0}
},
Signals {cg/dt},
- DigitMap = Dialplan0 {
+ DigitMap = Dialplan0 {
(0| 00|[1-7]xxx|8xxxxxxx|fxxxxxxx|exx|91xxxxxxxxxx|9011x.)
}
}
@@ -7250,7 +7724,7 @@ rfc3525_msg10() ->
}
}
}
- }
+ }
}".
@@ -7273,13 +7747,13 @@ rfc3525_msg12() ->
Mode = ReceiveOnly,
nt/jit=40 ; in ms
},
- Local {
+ Local {
v=0 c=IN IP4 $ m=audio $ RTP/AVP 4 a=ptime:30 v=0 c=IN IP4 $ m=audio $ RTP/AVP 0
}
}
}
}
- }
+ }
}".
%% Added ?
@@ -7290,28 +7764,28 @@ rfc3525_msg13() ->
Add = A4445 {
Media {
Stream = 1 {
- Local {
-v=0
-o=- 2890844526 2890842807 IN IP4 124.124.124.222
-s=-
-t= 0 0
-c=IN IP4 124.124.124.222
-m=audio 2222 RTP/AVP 4
-a=ptime:30
-a=recvonly
+ Local {
+v=0
+o=- 2890844526 2890842807 IN IP4 124.124.124.222
+s=-
+t= 0 0
+c=IN IP4 124.124.124.222
+m=audio 2222 RTP/AVP 4
+a=ptime:30
+a=recvonly
} ; RTP profile for G.723.1 is 4
}
}
}
- }
+ }
}".
-%%
+%%
%% Added ?
rfc3525_msg14() ->
"MEGACO/" ?VERSION_STR " [123.123.123.4]:55555 Transaction = 50003 {
Context = $ {
- Add = A5555 {
+ Add = A5555 {
Media {
Stream = 1 {
LocalControl {
@@ -7331,16 +7805,16 @@ rfc3525_msg14() ->
Mode = SendReceive,
nt/jit=40 ; in ms
},
- Local {
+ Local {
v=0 c=IN IP4 $ m=audio $ RTP/AVP 4 a=ptime:30
},
- Remote {
+ Remote {
v=0 c=IN IP4 124.124.124.222 m=audio 2222 RTP/AVP 4 a=ptime:30
} ; RTP profile for G.723.1 is 4
}
}
}
- }
+ }
}".
%% Added ?
@@ -7351,10 +7825,10 @@ rfc3525_msg15() ->
Add = A5556 {
Media {
Stream = 1 {
- Local {
- v=0 o=- 7736844526 7736842807 IN IP4 125.125.125.111 s=- t= 0 0 c=IN IP4 125.125.125.111 m=audio 1111 RTP/AVP 4
+ Local {
+ v=0 o=- 7736844526 7736842807 IN IP4 125.125.125.111 s=- t= 0 0 c=IN IP4 125.125.125.111 m=audio 1111 RTP/AVP 4
} ; RTP profile for G723.1 is 4
- }
+ }
}
}
}
@@ -7370,10 +7844,10 @@ rfc3525_msg16a() ->
Modify = A4445 {
Media {
Stream = 1 {
- Remote {
+ Remote {
v=0 o=- 7736844526 7736842807 IN IP4 125.125.125.111 s=- t= 0 0 c=IN IP4 125.125.125.111 m=audio 1111 RTP/AVP 4
} ; RTP profile for G723.1 is 4
- }
+ }
}
}
}
@@ -7382,7 +7856,7 @@ rfc3525_msg16a() ->
rfc3525_msg16b() ->
"MEGACO/" ?VERSION_STR " [124.124.124.222]:55555 Reply = 10005 {
Context = 2000 {
- Modify = A4444,
+ Modify = A4444,
Modify = A4445
}
}".
@@ -7439,7 +7913,7 @@ rfc3525_msg18a() ->
}
},
Modify = A4444 {
- Signals
+ Signals
}
}
}".
@@ -7447,7 +7921,7 @@ rfc3525_msg18a() ->
rfc3525_msg18b() ->
"MEGACO/" ?VERSION_STR " [124.124.124.222]:55555 Reply = 10006 {
Context = 2000 {
- Modify = A4445,
+ Modify = A4445,
Modify = A4444
}
}".
@@ -7466,22 +7940,22 @@ rfc3525_msg19() ->
%% Added ?
rfc3525_msg20() ->
"MEGACO/" ?VERSION_STR " [125.125.125.111]:55555 Reply = 50007 {
- Context = - {
+ Context = - {
AuditValue = A5556 {
Media {
- TerminationState {
+ TerminationState {
ServiceStates = InService,
- Buffer = OFF
+ Buffer = OFF
},
Stream = 1 {
- LocalControl {
+ LocalControl {
Mode = SendReceive,
- nt/jit=40
+ nt/jit=40
},
- Local {
+ Local {
v=0 o=- 7736844526 7736842807 IN IP4 125.125.125.111 s=- t= 0 0 c=IN IP4 125.125.125.111 m=audio 1111 RTP/AVP 4 a=ptime:30
},
- Remote {
+ Remote {
v=0 o=- 2890844526 2890842807 IN IP4 124.124.124.222 s=- t= 0 0 c=IN IP4 124.124.124.222 m=audio 2222 RTP/AVP 4 a=ptime:30
}
}
@@ -7490,7 +7964,7 @@ rfc3525_msg20() ->
Signals,
DigitMap,
Packages {nt-1, rtp-1},
- Statistics {
+ Statistics {
rtp/ps=1200, ; packets sent
nt/os=62300, ; octets sent
rtp/pr=700, ; packets received
@@ -7498,7 +7972,7 @@ rfc3525_msg20() ->
rtp/pl=0.2, ; % packet loss
rtp/jit=20,
rtp/delay=40 ; avg latency
- }
+ }
}
}
}".
@@ -7657,14 +8131,14 @@ ticket_compact_encode_decode_ok(Msg) ->
ticket_compact_encode_decode_ok(Msg, []).
ticket_compact_encode_decode_ok(Msg, Conf) ->
- Codec = megaco_compact_text_encoder,
+ Codec = megaco_compact_text_encoder,
ticket_encode_decode_ok(Msg, Codec, Conf).
ticket_pretty_encode_decode_ok(Msg) ->
ticket_pretty_encode_decode_ok(Msg, []).
ticket_pretty_encode_decode_ok(Msg, Conf) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_encode_decode_ok(Msg, Codec, Conf).
ticket_encode_decode_ok(Msg, Codec, Conf0) ->
@@ -7674,26 +8148,26 @@ ticket_encode_decode_ok(Msg, Codec, Conf0) ->
Check = fun(M1, M2) -> chk_MegacoMessage(M1, M2) end,
megaco_codec_test_lib:expect_encode_decode(Msg, Encode, Decode, Check).
-%% --
+%% --
%% ticket_compact_encode_error(Msg) ->
%% ticket_compact_encode_error(Msg, []).
%% ticket_compact_encode_error(Msg, Conf) ->
-%% Codec = megaco_compact_text_encoder,
+%% Codec = megaco_compact_text_encoder,
%% ticket_encode_error(Msg, Codec, Conf).
%% ticket_pretty_encode_error(Msg) ->
%% ticket_pretty_encode_error(Msg, []).
ticket_pretty_encode_error(Msg, Conf) when is_list(Conf) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_encode_error(Msg, Codec, Conf);
ticket_pretty_encode_error(Msg, Check) when is_function(Check) ->
ticket_pretty_encode_error(Msg, [], Check).
ticket_pretty_encode_error(Msg, Conf, Check) when is_function(Check) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_encode_error(Msg, Codec, Conf, Check).
ticket_encode_error(Msg, Codec, Conf) when is_list(Conf) ->
@@ -7711,14 +8185,14 @@ ticket_compact_decode_encode_ok(Msg) ->
ticket_compact_decode_encode_ok(Msg, []).
ticket_compact_decode_encode_ok(Msg, Conf) ->
- Codec = megaco_compact_text_encoder,
+ Codec = megaco_compact_text_encoder,
ticket_decode_encode_ok(Msg, Codec, Conf).
ticket_pretty_decode_encode_ok(Msg) ->
ticket_pretty_decode_encode_ok(Msg, []).
ticket_pretty_decode_encode_ok(Msg, Conf) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_decode_encode_ok(Msg, Codec, Conf).
ticket_decode_encode_ok(Msg, Codec, Conf0) ->
@@ -7728,59 +8202,59 @@ ticket_decode_encode_ok(Msg, Codec, Conf0) ->
Check = fun(M1, M2) -> chk_MegacoMessage(M1, M2) end,
megaco_codec_test_lib:expect_decode_encode(Msg, Decode, Encode, Check).
-%% --
+%% --
ticket_pretty_decode_encode_only(Msg, Check) ->
ticket_pretty_decode_encode_only(Msg, Check, []).
ticket_pretty_decode_encode_only(Msg, Check, Conf) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_decode_encode_only(Msg, Codec, Check, Conf).
ticket_decode_encode_only(Msg, Codec, Check, Conf0) ->
Conf = [?EC_V3|Conf0],
Decode = fun(B) -> decode_message(Codec, false, Conf, B) end,
Encode = fun(M) -> encode_message(Codec, Conf, M) end,
- megaco_codec_test_lib:expect_decode_encode_only(Msg, Decode, Encode,
+ megaco_codec_test_lib:expect_decode_encode_only(Msg, Decode, Encode,
Check).
-%% --
+%% --
ticket_pretty_encode_decode_only(Msg) ->
ticket_pretty_encode_decode_only(Msg, []).
ticket_pretty_encode_decode_only(Msg, Conf) when is_list(Conf) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_encode_decode_only(Msg, Codec, Conf);
ticket_pretty_encode_decode_only(Msg, Check) when is_function(Check) ->
ticket_pretty_encode_decode_only(Msg, Check, []).
ticket_pretty_encode_decode_only(Msg, Check, Conf) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_encode_decode_only(Msg, Codec, Check, Conf).
ticket_encode_decode_only(Msg, Codec, Conf) ->
- Check = fun(_) -> ok end,
+ Check = fun(_) -> ok end,
ticket_encode_decode_only(Msg, Codec, Check, Conf).
ticket_encode_decode_only(Msg, Codec, Check, Conf0) ->
Conf = [?EC_V3|Conf0],
Encode = fun(M) -> encode_message(Codec, Conf, M) end,
Decode = fun(B) -> decode_message(Codec, false, Conf, B) end,
- megaco_codec_test_lib:expect_encode_decode_only(Msg, Encode, Decode,
+ megaco_codec_test_lib:expect_encode_decode_only(Msg, Encode, Decode,
Check).
-%% --
+%% --
ticket_pretty_decode_only(Msg) ->
ticket_pretty_decode_only(Msg, []).
ticket_pretty_decode_only(Msg, Conf) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_decode_only(Msg, Codec, Conf).
ticket_decode_only(Msg, Codec, Conf) ->
- Check = fun(_) -> ok end,
+ Check = fun(_) -> ok end,
ticket_decode_only(Msg, Codec, Check, Conf).
ticket_decode_only(Msg, Codec, Check, Conf0) ->
@@ -7788,7 +8262,7 @@ ticket_decode_only(Msg, Codec, Check, Conf0) ->
Decode = fun(B) -> decode_message(Codec, false, Conf, B) end,
megaco_codec_test_lib:expect_decode_only(Msg, Decode, Check).
-ticket_check_decode_only_error_reason(R, Check)
+ticket_check_decode_only_error_reason(R, Check)
when is_list(R) and is_function(Check) ->
case lists:keysearch(reason, 1, R) of
{value, {reason, Reason}} ->
@@ -7798,32 +8272,32 @@ ticket_check_decode_only_error_reason(R, Check)
end.
-%% --
+%% --
ticket_compact_decode_error(Msg) ->
ticket_compact_decode_error(Msg, []).
ticket_compact_decode_error(Msg, Conf) ->
- Codec = megaco_compact_text_encoder,
+ Codec = megaco_compact_text_encoder,
ticket_decode_error(Msg, Codec, Conf).
ticket_pretty_decode_error(Msg) ->
ticket_pretty_decode_error(Msg, []).
ticket_pretty_decode_error(Msg, Conf) when is_list(Conf) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_decode_error(Msg, Codec, Conf);
ticket_pretty_decode_error(Msg, Check) when is_function(Check) ->
ticket_pretty_decode_error(Msg, [], Check).
ticket_pretty_decode_error(Msg, Conf, Check) ->
- Codec = megaco_pretty_text_encoder,
+ Codec = megaco_pretty_text_encoder,
ticket_decode_error(Msg, Codec, Conf, Check).
ticket_decode_error(Msg, Codec, Conf) ->
- Check = fun(X) ->
+ Check = fun(X) ->
d("decode error reason: ~n~p~n", [X]),
- ok
+ ok
end, % Only called when decode failes
ticket_decode_error(Msg, Codec, Conf, Check).
@@ -7832,7 +8306,7 @@ ticket_decode_error(Msg, Codec, Conf0, Check) ->
Decode = fun(B) -> decode_message(Codec, false, Conf, B) end,
megaco_codec_test_lib:expect_decode(Msg, Decode, Check).
-%% --
+%% --
%% ticket_expect_exec(Instructions, Msg) ->
%% megaco_codec_test_lib:expect_exec(Instructions, Msg).
@@ -7850,7 +8324,7 @@ ticket_decode_error(Msg, Codec, Conf0, Check) ->
%% decode_message(megaco_compact_text_encoder, DynamicDecode, Conf, Bin).
decode_message(Codec, DynamicDecode, Conf, Bin) ->
- megaco_codec_test_lib:decode_message(Codec, DynamicDecode, ?VERSION,
+ megaco_codec_test_lib:decode_message(Codec, DynamicDecode, ?VERSION,
Conf, Bin).
%% pretty_encode_message(Conf, Msg) ->
@@ -7863,7 +8337,7 @@ encode_message(Codec, Conf, Msg) ->
megaco_codec_test_lib:encode_message(Codec, ?VERSION, Conf, Msg).
test_msgs(Codec, DynamicDecode, Conf, Msgs) ->
- megaco_codec_test_lib:test_msgs(Codec, DynamicDecode, ?VERSION, Conf,
+ megaco_codec_test_lib:test_msgs(Codec, DynamicDecode, ?VERSION, Conf,
fun chk_MegacoMessage/2, Msgs).
@@ -7886,8 +8360,8 @@ cre_MegacoMessage(V, Mid, Body) ->
cre_MegacoMessage(Mess).
cre_AuthHeader() ->
- SecParmIdx = [239, 205, 171, 137],
- SeqNum = [18, 52, 86, 120],
+ SecParmIdx = [239, 205, 171, 137],
+ SeqNum = [18, 52, 86, 120],
AD = [18, 52, 86, 120, 137, 171, 205, 239, 118, 84, 50, 16],
cre_AuthHeader(SecParmIdx, SeqNum, AD).
@@ -7926,7 +8400,7 @@ cre_SegRep(TransId, SN, SC) ->
cre_TransAck(First, Last) ->
?MSG_LIB:cre_TransactionAck(First, Last).
-
+
cre_ActReq(CtxId, CmdReqs) ->
?MSG_LIB:cre_ActionRequest(CtxId, CmdReqs).
@@ -8027,7 +8501,7 @@ cre_IndAudTermStateDesc(PP, EBC, SS) ->
cre_IndAudTermStateDesc(PP, EBC, SS, SSS) ->
?MSG_LIB:cre_IndAudTerminationStateDescriptor(PP, EBC, SS, SSS).
-cre_IndAudEvsDesc(RID, PN)
+cre_IndAudEvsDesc(RID, PN)
when is_integer(RID) ->
?MSG_LIB:cre_IndAudEventsDescriptor(RID, PN).
@@ -8074,7 +8548,7 @@ cre_StatsParm(Name, Val) ->
?MSG_LIB:cre_StatisticsParameter(Name, [Val]).
-% Event related
+% Event related
cre_EvParm(Name, Val) ->
?MSG_LIB:cre_EventParameter(Name, Val).
@@ -8103,7 +8577,7 @@ cre_SvcChParm(M, A, R, P) ->
?MSG_LIB:cre_ServiceChangeParm(M, A, P, R).
cre_SvcChParm(M, A, R, P, IF) ->
- ?MSG_LIB:cre_ServiceChangeParm(M, A, asn1_NOVALUE, P, R, asn1_NOVALUE,
+ ?MSG_LIB:cre_ServiceChangeParm(M, A, asn1_NOVALUE, P, R, asn1_NOVALUE,
asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, IF).
cre_SvcChResParm(A, P) ->
@@ -8277,8 +8751,8 @@ cre_SecReqEv(N) ->
cre_SecReqEv(N, EPL) ->
?MSG_LIB:cre_SecondRequestedEvent(N, EPL).
-cre_SecReqEv(N, SID, EA) when is_list(N) and
- is_integer(SID) and
+cre_SecReqEv(N, SID, EA) when is_list(N) and
+ is_integer(SID) and
is_record(EA, 'SecondRequestedActions') ->
cre_SecReqEv(N, SID, EA, []);
cre_SecReqEv(A, B, C) ->
@@ -8307,7 +8781,7 @@ cre_Sig(Name, Dir, RID) ->
cre_Sig(Name, [], Dir, RID).
cre_Sig(Name, SPL, Dir, RID) ->
- cre_Sig(Name, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE,
+ cre_Sig(Name, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE,
asn1_NOVALUE, SPL, Dir, RID).
cre_Sig(Name, SID, ST, Dur, NC, KA, SPL, Dir, RID) ->
@@ -8325,7 +8799,7 @@ cre_NotifBehav(Tag, Val) ->
cre_NotifCompl(NC) ->
?MSG_LIB:cre_NotifyCompletion(NC).
-cre_SigType(ST) ->
+cre_SigType(ST) ->
?MSG_LIB:cre_SignalType(ST).
@@ -8433,6 +8907,5 @@ p(_,_,_,_) ->
p(F, A) ->
io:format("*** [~s] ***"
- "~n " ++ F ++ "~n",
+ "~n " ++ F ++ "~n",
[?FTS() | A]).
-
diff --git a/lib/megaco/test/megaco_config_SUITE.erl b/lib/megaco/test/megaco_config_SUITE.erl
index 4d34e09d0d..557406f8c4 100644
--- a/lib/megaco/test/megaco_config_SUITE.erl
+++ b/lib/megaco/test/megaco_config_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -600,7 +600,7 @@ transaction_id_counter_mg(Config) when is_list(Config) ->
%% Await the counter worker procs termination
i("await the counter working procs completion"),
- await_completion_counter_working_procs(Pids),
+ ok = await_completion_counter_working_procs(Pids),
%% Verify result
i("verify counter result"),
@@ -656,15 +656,52 @@ start_counter_working_procs([Pid | Pids]) ->
Pid ! start,
start_counter_working_procs(Pids).
-await_completion_counter_working_procs([]) ->
- ok;
await_completion_counter_working_procs(Pids) ->
+ await_completion_counter_working_procs(Pids, [], []).
+
+await_completion_counter_working_procs([], _OKs, [] = _ERRs) ->
+ ok;
+await_completion_counter_working_procs([], _OKs, ERRs) ->
+ {error, ERRs};
+await_completion_counter_working_procs(Pids, OKs, ERRs) ->
receive
{'EXIT', Pid, normal} ->
+ %% i("counter working process completion[~w, ~w, ~w] -> "
+ %% "Expected exit from counter process: "
+ %% "~n Pid: ~p",
+ %% [length(Pids), length(OKs), length(ERRs), Pid]),
+ Pids2 = lists:delete(Pid, Pids),
+ await_completion_counter_working_procs(Pids2, [Pid | OKs], ERRs);
+ {'EXIT', Pid, Reason} ->
+ e("counter working process completion[~w, ~w, ~w] -> "
+ "Unexpected exit from counter process: "
+ "~n Pid: ~p"
+ "~n Reason: ~p",
+ [length(Pids), length(OKs), length(ERRs), Pid, Reason]),
Pids2 = lists:delete(Pid, Pids),
- await_completion_counter_working_procs(Pids2);
- _Any ->
+ await_completion_counter_working_procs(Pids2, OKs, [Pid | ERRs]);
+
+ Any ->
+ e("counter working process completion[~w, ~w, ~w] -> "
+ "Unexpected message: "
+ "~n ~p", [length(Pids), length(OKs), length(ERRs), Any]),
await_completion_counter_working_procs(Pids)
+
+ after 10000 ->
+ %% If nothing has happened for this long, something is wrong:
+ %% Check system events
+ case megaco_test_global_sys_monitor:events() of
+ [] ->
+ i("counter working process completion[~w, ~w, ~w] -> "
+ "idle", [length(Pids), length(OKs), length(ERRs)]),
+ await_completion_counter_working_procs(Pids);
+ SysEvs ->
+ e("counter working process completion[~w, ~w, ~w] -> "
+ "system event(s): "
+ "~n ~p",
+ [length(Pids), length(OKs), length(ERRs), SysEvs]),
+ ?SKIP("TC idle with system events")
+ end
end.
@@ -677,119 +714,123 @@ transaction_id_counter_mgc(doc) ->
"transaction counter handling of the application "
"in with several connections (MGC). "];
transaction_id_counter_mgc(Config) when is_list(Config) ->
- put(verbosity, ?TEST_VERBOSITY),
- put(sname, "TEST"),
- put(tc, transaction_id_counter_mgc),
- process_flag(trap_exit, true),
-
- i("starting"),
-
- {ok, _ConfigPid} = megaco_config:start_link(),
-
- %% Basic user data
- UserMid = {deviceName, "mgc"},
- UserConfig = [
- {min_trans_id, 1}
- ],
-
- %% Basic connection data
- RemoteMids =
- [
- {deviceName, "mg01"},
- {deviceName, "mg02"},
- {deviceName, "mg03"},
- {deviceName, "mg04"},
- {deviceName, "mg05"},
- {deviceName, "mg06"},
- {deviceName, "mg07"},
- {deviceName, "mg08"},
- {deviceName, "mg09"},
- {deviceName, "mg10"}
- ],
- RecvHandles =
- [
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE},
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE},
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE},
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE},
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE},
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE},
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE},
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE},
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE},
- #megaco_receive_handle{local_mid = UserMid,
- encoding_mod = ?MODULE,
- encoding_config = [],
- send_mod = ?MODULE}
- ],
- SendHandle = dummy_send_handle,
- ControlPid = self(),
+ Name = transaction_id_counter_mgc,
+ Pre = fun() ->
+ i("starting config server"),
+ {ok, _ConfigPid} = megaco_config:start_link(),
+
+ %% Basic user data
+ UserMid = {deviceName, "mgc"},
+ UserConfig = [
+ {min_trans_id, 1}
+ ],
+
+ %% Basic connection data
+ RemoteMids =
+ [
+ {deviceName, "mg01"},
+ {deviceName, "mg02"},
+ {deviceName, "mg03"},
+ {deviceName, "mg04"},
+ {deviceName, "mg05"},
+ {deviceName, "mg06"},
+ {deviceName, "mg07"},
+ {deviceName, "mg08"},
+ {deviceName, "mg09"},
+ {deviceName, "mg10"}
+ ],
+ RecvHandles =
+ [
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE},
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE},
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE},
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE},
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE},
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE},
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE},
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE},
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE},
+ #megaco_receive_handle{local_mid = UserMid,
+ encoding_mod = ?MODULE,
+ encoding_config = [],
+ send_mod = ?MODULE}
+ ],
+ SendHandle = dummy_send_handle,
+ ControlPid = self(),
- %% Start user
- i("start user"),
- ok = megaco_config:start_user(UserMid, UserConfig),
+ %% Start user
+ i("start user"),
+ ok = megaco_config:start_user(UserMid, UserConfig),
+
+ %% Create connection
+ i("create connection(s)"),
+ CDs = create_connections(RecvHandles,
+ RemoteMids,
+ SendHandle,
+ ControlPid),
+
+ %% Set counter limits
+ i("set counter max limit(s)"),
+ set_counter_max_limits(CDs, 1000),
+
+ {UserMid, CDs}
+ end,
+ Case = fun({_, CDs}) ->
+ %% Create the counter worker procs
+ i("create counter working procs"),
+ Pids = create_counter_working_procs(CDs, ?NUM_CNT_PROCS),
+
+ %% Start the counter worker procs
+ i("release the counter working procs"),
+ start_counter_working_procs(Pids),
+
+ %% Await the counter worker procs termination
+ i("await the counter working procs completion"),
+ ok = await_completion_counter_working_procs(Pids),
+
+ %% Verify result
+ i("verify counter result"),
+ verify_counter_results(CDs)
+ end,
+
+ Post = fun({UserMid, CDs}) ->
+ %% Stop test
+ i("disconnect"),
+ delete_connections(CDs),
+ i("stop user"),
+ ok = megaco_config:stop_user(UserMid),
+ i("stop megaco_config"),
+ ok = megaco_config:stop()
+ end,
+ try_tc(Name, Pre, Case, Post).
- %% Create connection
- i("create connection(s)"),
- CDs = create_connections(RecvHandles, RemoteMids, SendHandle, ControlPid),
-
- %% Set counter limits
- i("set counter max limit(s)"),
- set_counter_max_limits(CDs, 1000),
-
- %% Create the counter worker procs
- i("create counter working procs"),
- Pids = create_counter_working_procs(CDs, ?NUM_CNT_PROCS),
-
- %% Start the counter worker procs
- i("release the counter working procs"),
- start_counter_working_procs(Pids),
-
- %% Await the counter worker procs termination
- i("await the counter working procs completion"),
- await_completion_counter_working_procs(Pids),
-
- %% Verify result
- i("verify counter result"),
- verify_counter_results(CDs),
-
- %% Stop test
- i("disconnect"),
- delete_connections(CDs),
- i("stop user"),
- ok = megaco_config:stop_user(UserMid),
- i("stop megaco_config"),
- ok = megaco_config:stop(),
-
- i("done"),
- ok.
create_connections(RecvHandles, RemoteMids, SendHandle, ControlPid) ->
create_connections(RecvHandles, RemoteMids, SendHandle, ControlPid, []).
@@ -1218,6 +1259,15 @@ otp_8183(Config) when is_list(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+try_tc(TCName, Pre, Case, Post) ->
+ try_tc(TCName, "TEST", ?TEST_VERBOSITY, Pre, Case, Post).
+
+try_tc(TCName, Name, Verbosity, Pre, Case, Post) ->
+ ?TRY_TC(TCName, Name, Verbosity, Pre, Case, Post).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
p(F) ->
p(F, []).
@@ -1234,7 +1284,10 @@ i(F) ->
i(F, []).
i(F, A) ->
- print(info, get(verbosity), get(tc), "INF", F, A).
+ print(info, get(verbosity), get(tc), "INFO", F, A).
+
+e(F, A) ->
+ print(info, get(verbosity), get(tc), "ERROR", F, A).
printable(_, debug) -> true;
printable(info, info) -> true;
diff --git a/lib/megaco/test/megaco_mess_SUITE.erl b/lib/megaco/test/megaco_mess_SUITE.erl
index f3dfc053c6..be523f42a2 100644
--- a/lib/megaco/test/megaco_mess_SUITE.erl
+++ b/lib/megaco/test/megaco_mess_SUITE.erl
@@ -266,6 +266,7 @@
]).
-endif.
+-include_lib("common_test/include/ct.hrl").
-include_lib("megaco/include/megaco.hrl").
-include_lib("megaco/include/megaco_message_v1.hrl").
-include("megaco_test_lib.hrl").
@@ -511,18 +512,19 @@ init_per_testcase(Case, Config) ->
init_per_testcase2(otp_7189 = Case, Config) ->
C = lists:keydelete(tc_timeout, 1, Config),
- init_per_testcase3(Case, [{tc_timeout, min(2)} |C]);
+ init_per_testcase3(Case, [{tc_timeout, min(tfactor(2, Config))} |C]);
init_per_testcase2(request_and_no_reply = Case, Config) ->
C = lists:keydelete(tc_timeout, 1, Config),
- init_per_testcase3(Case, [{tc_timeout, min(2)} |C]);
+ init_per_testcase3(Case, [{tc_timeout, min(tfactor(2, Config))} |C]);
init_per_testcase2(Case, Config) ->
C = lists:keydelete(tc_timeout, 1, Config),
- init_per_testcase3(Case, [{tc_timeout, min(1)} |C]).
+ init_per_testcase3(Case, [{tc_timeout, min(tfactor(1, Config))} |C]).
init_per_testcase3(Case, Config) ->
megaco_test_global_sys_monitor:reset_events(),
megaco_test_lib:init_per_testcase(Case, Config).
-
+
+
end_per_testcase(Case, Config) ->
@@ -539,6 +541,13 @@ end_per_testcase(Case, Config) ->
min(M) -> ?MINS(M).
+tfactor(T, Config) ->
+ case ?config(megaco_factor, Config) of
+ Factor when is_integer(Factor) andalso (Factor > 1) ->
+ T * Factor;
+ _ ->
+ T
+ end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -10560,6 +10569,7 @@ otp_6442_resend_request2_mg_notify_request_ar(Rid, Tid, Cid) ->
otp_6442_resend_reply1(suite) ->
[];
otp_6442_resend_reply1(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
Pre = fun() ->
MgNode = make_node_name(mg),
d("start (MG) node: ~p", [MgNode]),
@@ -10567,14 +10577,14 @@ otp_6442_resend_reply1(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes, true),
Nodes
end,
- Case = fun do_otp_6442_resend_reply1/1,
+ Case = fun(Nodes) -> do_otp_6442_resend_reply1(Nodes, Factor) end,
Post = fun(Nodes) ->
d("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(request_and_no_reply, Pre, Case, Post).
-do_otp_6442_resend_reply1([MgNode]) ->
+do_otp_6442_resend_reply1([MgNode], Factor) ->
d("[MG] start the simulator "),
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
@@ -10591,7 +10601,7 @@ do_otp_6442_resend_reply1([MgNode]) ->
{ok, MgId} = megaco_test_megaco_generator:exec(Mg, MgEvSeq),
i("await the transport module service change send_message event"),
- Pid = otp_6442_expect(fun otp_6442_rsrp1_verify_scr_msg/1, 5000),
+ Pid = otp_6442_expect(fun otp_6442_rsrp1_verify_scr_msg/1, Factor * 5000),
i("wait some before issuing the service change reply"),
sleep(500),
@@ -10610,7 +10620,7 @@ do_otp_6442_resend_reply1([MgNode]) ->
i("await the transport module first notify-reply send_message event from MG: "
"ignore"),
- otp_6442_expect(fun otp_6442_rsrp1_verify_first_nr_msg/1, 5000),
+ otp_6442_expect(fun otp_6442_rsrp1_verify_first_nr_msg/1, Factor * 5000),
i("await the transport module second notify-reply send_message event from MG: "
"ack"),
@@ -10955,6 +10965,7 @@ otp_6442_resend_reply1_err_desc(T) ->
otp_6442_resend_reply2(suite) ->
[];
otp_6442_resend_reply2(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
Pre = fun() ->
MgNode = make_node_name(mg),
d("start (MG) node: ~p", [MgNode]),
@@ -10962,14 +10973,14 @@ otp_6442_resend_reply2(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes, true),
Nodes
end,
- Case = fun do_otp_6442_resend_reply2/1,
+ Case = fun(Nodes) -> do_otp_6442_resend_reply2(Nodes, Factor) end,
Post = fun(Nodes) ->
d("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(otp6442rrep2, Pre, Case, Post).
-do_otp_6442_resend_reply2([MgNode]) ->
+do_otp_6442_resend_reply2([MgNode], Factor) ->
d("[MG] start the simulator "),
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
@@ -10986,7 +10997,7 @@ do_otp_6442_resend_reply2([MgNode]) ->
{ok, MgId} = megaco_test_megaco_generator:exec(Mg, MgEvSeq),
i("await the transport module service change send_message event"),
- Pid = otp_6442_expect(fun otp_6442_rsrp2_verify_scr_msg/1, 5000),
+ Pid = otp_6442_expect(fun otp_6442_rsrp2_verify_scr_msg/1, Factor * 5000),
i("wait some before issuing the service change reply"),
sleep(500),
@@ -11004,7 +11015,7 @@ do_otp_6442_resend_reply2([MgNode]) ->
megaco_test_generic_transport:incomming_message(Pid, NotifyRequest),
i("await the transport module notify-reply send_message event from MG: ignore"),
- otp_6442_expect(otp_6442_rsrp2_verify_first_nr_msg_fun(), 5000),
+ otp_6442_expect(otp_6442_rsrp2_verify_first_nr_msg_fun(), Factor * 5000),
i("await the transport module notify-reply resend_message event from MG: ack"),
{TransId, _, _} =
@@ -13474,8 +13485,8 @@ otp_8183_r1_mgc_reply_msg(Mid, TransId, CR, Cid) ->
{?MODULE, otp_8183_r1_mg_verify_handle_connect, []}).
-define(otp_8183_r1_mg_verify_service_change_rep_fun(),
{?MODULE, otp_8183_r1_mg_verify_service_change_rep, []}).
--define(otp_8183_r1_mg_verify_notify_rep_fun(Nr
- {?MODULE, otp_8183_r1_mg_verify_notify_rep, [Nr).
+-define(otp_8183_r1_mg_verify_notify_rep_fun(Nr),
+ {?MODULE, otp_8183_r1_mg_verify_notify_rep, [Nr]}).
-else.
-define(otp_8183_r1_mg_verify_handle_connect_fun(),
otp_8183_r1_mg_verify_handle_connect_fun()).
diff --git a/lib/megaco/test/megaco_segment_SUITE.erl b/lib/megaco/test/megaco_segment_SUITE.erl
index 08b86606de..3763a20954 100644
--- a/lib/megaco/test/megaco_segment_SUITE.erl
+++ b/lib/megaco/test/megaco_segment_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -165,7 +165,7 @@ end_per_group(_Group, Config) ->
init_per_testcase(Case, Config) ->
process_flag(trap_exit, true),
- p("init_per_suite -> entry with"
+ p("init_per_testcase -> entry with"
"~n Config: ~p"
"~n Nodes: ~p", [Config, erlang:nodes()]),
@@ -175,7 +175,7 @@ init_per_testcase(Case, Config) ->
end_per_testcase(Case, Config) ->
process_flag(trap_exit, false),
- p("end_per_suite -> entry with"
+ p("end_per_testcase -> entry with"
"~n Config: ~p"
"~n Nodes: ~p", [Config, erlang:nodes()]),
@@ -806,17 +806,9 @@ send_segmented_msg_plain2(doc) ->
"Second plain test that it is possible to send segmented messages. "
"Send window = infinity. ";
send_segmented_msg_plain2(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(?MINS(1) + Factor * ?MINS(1)),
Pre = fun() ->
- %% We leave it commented out as test
- %% All the other changes to the framework
- %% may have "solved" the issues...
-
- %% <CONDITIONAL-SKIP>
- %% Skippable = [{unix, [linux]}],
- %% Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
- %% ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
- %% </CONDITIONAL-SKIP>
-
MgcNode = make_node_name(mgc),
MgNode = make_node_name(mg),
d("start nodes: "
@@ -827,20 +819,20 @@ send_segmented_msg_plain2(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes),
Nodes
end,
- Case = fun do_send_segmented_msg_plain2/1,
+ Case = fun(X) -> do_send_segmented_msg_plain2(Factor, X) end,
Post = fun(Nodes) ->
d("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(ssmp2, Pre, Case, Post).
-do_send_segmented_msg_plain2([MgcNode, MgNode]) ->
+do_send_segmented_msg_plain2(Factor, [MgcNode, MgNode]) ->
d("[MGC] start the simulator "),
{ok, Mgc} = megaco_test_tcp_generator:start_link("MGC", MgcNode),
d("[MGC] create the event sequence"),
- MgcEvSeq = ssmp2_mgc_event_sequence(text, tcp),
+ MgcEvSeq = ssmp2_mgc_event_sequence(Factor, text, tcp),
i("wait some time before starting the MGC simulation"),
sleep(1000),
@@ -855,7 +847,7 @@ do_send_segmented_msg_plain2([MgcNode, MgNode]) ->
{ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode),
d("[MG] create the event sequence"),
- MgEvSeq = ssmp2_mg_event_sequence(text, tcp),
+ MgEvSeq = ssmp2_mg_event_sequence(Factor, text, tcp),
i("wait some time before starting the MG simulation"),
sleep(1000),
@@ -883,7 +875,7 @@ do_send_segmented_msg_plain2([MgcNode, MgNode]) ->
%% MGC generator stuff
%%
-ssmp2_mgc_event_sequence(text, tcp) ->
+ssmp2_mgc_event_sequence(Factor, text, tcp) ->
DecodeFun = ssmp2_mgc_decode_msg_fun(megaco_pretty_text_encoder, []),
EncodeFun = ssmp2_mgc_encode_msg_fun(megaco_pretty_text_encoder, []),
Mid = {deviceName,"mgc"},
@@ -908,6 +900,7 @@ ssmp2_mgc_event_sequence(text, tcp) ->
SegmentRep1 = ssmp2_mgc_segment_reply_msg(Mid, TransId, 1, false),
SegmentRep2 = ssmp2_mgc_segment_reply_msg(Mid, TransId, 2, true),
TransAck = ssmp2_mgc_trans_ack_msg(Mid, TransId),
+ TO = fun(T) -> Factor * T end,
EvSeq = [{debug, true},
{decode, DecodeFun},
{encode, EncodeFun},
@@ -915,15 +908,17 @@ ssmp2_mgc_event_sequence(text, tcp) ->
{expect_accept, any},
{expect_receive, "service-change-request", {ScrVerifyFun, 5000}},
{send, "service-change-reply", ServiceChangeRep},
- {expect_nothing, timer:seconds(1)},
+ {expect_nothing, ?SECS(1)},
{send, "notify request", NotifyReq},
- {expect_receive, "notify reply: segment 1", {NrVerifyFun1, 2000}},
+ {expect_receive, "notify reply: segment 1",
+ {NrVerifyFun1, TO(?SECS(2))}},
{send, "segment reply 1", SegmentRep1},
- {expect_receive, "notify reply: segment 2", {NrVerifyFun2, 1000}},
+ {expect_receive, "notify reply: segment 2",
+ {NrVerifyFun2, TO(?SECS(1))}},
{send, "segment reply 2", SegmentRep2},
{sleep, 100}, % {expect_nothing, 500},
{send, "transaction-ack", TransAck},
- {expect_closed, timer:seconds(5)},
+ {expect_closed, TO(?SECS(5))},
disconnect
],
EvSeq.
@@ -1036,14 +1031,13 @@ ssmp2_mgc_verify_notify_reply_segment_msg_fun(SN, Last,
ssmp2_mgc_verify_notify_reply_segment(#'MegacoMessage'{mess = Mess} = M,
SN, Last, TransId, TermId, Cid) ->
- io:format("ssmp2_mgc_verify_notify_reply_segment -> entry with"
- "~n M: ~p"
- "~n SN: ~p"
- "~n Last: ~p"
- "~n TransId: ~p"
- "~n TermId: ~p"
- "~n Cid: ~p"
- "~n", [M, SN, Last, TransId, TermId, Cid]),
+ p("ssmp2_mgc_verify_notify_reply_segment -> entry with"
+ "~n M: ~p"
+ "~n SN: ~p"
+ "~n Last: ~p"
+ "~n TransId: ~p"
+ "~n TermId: ~p"
+ "~n Cid: ~p", [M, SN, Last, TransId, TermId, Cid]),
Body =
case Mess of
#'Message'{version = ?VERSION,
@@ -1173,7 +1167,7 @@ ssmp2_mgc_trans_ack_msg(Mid, TransId) ->
%%
%% MG generator stuff
%%
-ssmp2_mg_event_sequence(text, tcp) ->
+ssmp2_mg_event_sequence(Factor, text, tcp) ->
Mid = {deviceName,"mg"},
RI = [
{port, 2944},
@@ -1187,7 +1181,8 @@ ssmp2_mg_event_sequence(text, tcp) ->
Tid1 = #megaco_term_id{id = ["00000000","00000000","00000001"]},
Tid2 = #megaco_term_id{id = ["00000000","00000000","00000002"]},
NotifyReqVerify = ssmp2_mg_verify_notify_request_fun(Tid1, Tid2),
- AckVerify = ssmp2_mg_verify_ack_fun(),
+ AckVerify = ssmp2_mg_verify_ack_fun(),
+ SECS = fun(T) -> ?SECS(Factor * T) end,
EvSeq = [
{debug, true},
{megaco_trace, disable},
@@ -1206,12 +1201,12 @@ ssmp2_mg_event_sequence(text, tcp) ->
{megaco_update_conn_info, protocol_version, ?VERSION},
{megaco_update_conn_info, segment_send, infinity},
{megaco_update_conn_info, max_pdu_size, 128},
- {sleep, 1000},
+ {sleep, ?SECS(1)},
{megaco_callback, handle_trans_request, NotifyReqVerify},
- {megaco_callback, handle_trans_ack, AckVerify, 5000},
+ {megaco_callback, handle_trans_ack, AckVerify, SECS(5)},
megaco_stop_user,
megaco_stop,
- {sleep, 1000}
+ {sleep, ?SECS(1)}
],
EvSeq.
@@ -1220,12 +1215,12 @@ ssmp2_mg_verify_handle_connect_fun() ->
fun(Ev) -> ssmp2_mg_verify_handle_connect(Ev) end.
ssmp2_mg_verify_handle_connect({handle_connect, CH, 1}) ->
- io:format("ssmp2_mg_verify_handle_connect -> ok"
- "~n CH: ~p~n", [CH]),
+ p("ssmp2_mg_verify_handle_connect -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp2_mg_verify_handle_connect(Else) ->
- io:format("ssmp2_mg_verify_handle_connect -> unknown"
- "~n Else: ~p~n", [Else]),
+ p("ssmp2_mg_verify_handle_connect -> unknown"
+ "~n Else: ~p", [Else]),
{error, Else, ok}.
@@ -1235,14 +1230,13 @@ ssmp2_mg_verify_service_change_reply_fun() ->
ssmp2_mg_verify_scr({handle_trans_reply, _CH, 1, {ok, [AR]}, _}) ->
(catch ssmp2_mg_do_verify_scr(AR));
ssmp2_mg_verify_scr(Crap) ->
- io:format("ssmp2_mg_verify_scr -> error: "
- "~n Crap: ~p"
- "~n", [Crap]),
+ p("ssmp2_mg_verify_scr -> error: "
+ "~n Crap: ~p", [Crap]),
{error, Crap, ok}.
ssmp2_mg_do_verify_scr(AR) ->
- io:format("ssmp2_mg_do_verify_scr -> ok: "
- "~n AR: ~p~n", [AR]),
+ p("ssmp2_mg_do_verify_scr -> ok: "
+ "~n AR: ~p", [AR]),
CR =
case AR of
#'ActionReply'{commandReply = [CmdRep]} ->
@@ -1304,30 +1298,27 @@ ssmp2_mg_verify_notify_request(
{handle_trans_request, CH, V, ARs}, _Tid1, _Tid2) ->
{error, {invalid_trans_request, {CH, V, ARs}}, ok};
ssmp2_mg_verify_notify_request(Crap, _Tid1, _Tid2) ->
- io:format("ssmp2_mg_verify_notify_request -> unknown request"
- "~n Tid1: ~p"
- "~n Tid2: ~p"
- "~n Crap: ~p"
- "~n", [_Tid1, _Tid2, Crap]),
+ p("ssmp2_mg_verify_notify_request -> unknown request"
+ "~n Tid1: ~p"
+ "~n Tid2: ~p"
+ "~n Crap: ~p", [_Tid1, _Tid2, Crap]),
{error, {unexpected_event, Crap}, ok}.
ssmp2_mg_do_verify_notify_request(Tid1, Tid2, AR1, AR2) ->
- io:format("ssmp2_mg_do_verify_notify_request -> ok"
- "~n Tid1: ~p"
- "~n Tid2: ~p"
- "~n AR1: ~p"
- "~n AR2: ~p"
- "~n", [Tid1, Tid2, AR1, AR2]),
+ p("ssmp2_mg_do_verify_notify_request -> ok"
+ "~n Tid1: ~p"
+ "~n Tid2: ~p"
+ "~n AR1: ~p"
+ "~n AR2: ~p", [Tid1, Tid2, AR1, AR2]),
ActionReply1 = ssmp2_mg_do_verify_notify_request(Tid1, AR1),
ActionReply2 = ssmp2_mg_do_verify_notify_request(Tid2, AR2),
Reply = {{handle_ack, ssmp2}, [ActionReply1, ActionReply2]},
{ok, [AR1, AR2], Reply}.
ssmp2_mg_do_verify_notify_request(Tid, AR) ->
- io:format("ssmp2_mg_do_verify_notify_request -> ok"
- "~n Tid: ~p"
- "~n AR: ~p"
- "~n", [Tid, AR]),
+ p("ssmp2_mg_do_verify_notify_request -> ok"
+ "~n Tid: ~p"
+ "~n AR: ~p", [Tid, AR]),
{Cid, CR} =
case AR of
#'ActionRequest'{contextId = CtxId,
@@ -1375,9 +1366,8 @@ ssmp2_mg_verify_ack_fun() ->
fun(Event) -> ssmp2_mg_verify_ack(Event) end.
ssmp2_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, ssmp2}) ->
- io:format("ssmp2_mg_verify_ack -> ok"
- "~n CH: ~p"
- "~n", [CH]),
+ p("ssmp2_mg_verify_ack -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp2_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, CrapAckData}) ->
{error, {unknown_ack_data, CrapAckData, CH}, ok};
@@ -1412,6 +1402,8 @@ send_segmented_msg_plain3(doc) ->
"Third plain test that it is possible to send segmented messages. "
"Send window = 1. ";
send_segmented_msg_plain3(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(?MINS(1) + Factor * ?MINS(1)),
Pre = fun() ->
MgcNode = make_node_name(mgc),
MgNode = make_node_name(mg),
@@ -1612,9 +1604,8 @@ ssmp3_mgc_verify_service_change_req_msg_fun() ->
end.
ssmp3_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) ->
- io:format("ssmp3_mgc_verify_service_change_req -> entry with"
- "~n M: ~p"
- "~n", [M]),
+ p("ssmp3_mgc_verify_service_change_req -> entry with"
+ "~n M: ~p", [M]),
Body =
case Mess of
#'Message'{version = 1,
@@ -1704,14 +1695,13 @@ ssmp3_mgc_verify_notify_reply_segment_msg_fun(SN, Last,
ssmp3_mgc_verify_notify_reply_segment(#'MegacoMessage'{mess = Mess} = M,
SN, Last, TransId, TermId, Cid) ->
- io:format("ssmp3_mgc_verify_notify_reply_segment -> entry with"
- "~n M: ~p"
- "~n SN: ~p"
- "~n Last: ~p"
- "~n TransId: ~p"
- "~n TermId: ~p"
- "~n Cid: ~p"
- "~n", [M, SN, Last, TransId, TermId, Cid]),
+ p("ssmp3_mgc_verify_notify_reply_segment -> entry with"
+ "~n M: ~p"
+ "~n SN: ~p"
+ "~n Last: ~p"
+ "~n TransId: ~p"
+ "~n TermId: ~p"
+ "~n Cid: ~p", [M, SN, Last, TransId, TermId, Cid]),
Body =
case Mess of
#'Message'{version = ?VERSION,
@@ -1903,12 +1893,12 @@ ssmp3_mg_verify_handle_connect_fun() ->
fun(Ev) -> ssmp3_mg_verify_handle_connect(Ev) end.
ssmp3_mg_verify_handle_connect({handle_connect, CH, 1}) ->
- io:format("ssmp3_mg_verify_handle_connect -> ok"
- "~n CH: ~p~n", [CH]),
+ p("ssmp3_mg_verify_handle_connect -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp3_mg_verify_handle_connect(Else) ->
- io:format("ssmp3_mg_verify_handle_connect -> unknown"
- "~n Else: ~p~n", [Else]),
+ p("ssmp3_mg_verify_handle_connect -> unknown"
+ "~n Else: ~p", [Else]),
{error, Else, ok}.
@@ -1918,14 +1908,13 @@ ssmp3_mg_verify_service_change_reply_fun() ->
ssmp3_mg_verify_scr({handle_trans_reply, _CH, 1, {ok, [AR]}, _}) ->
(catch ssmp3_mg_do_verify_scr(AR));
ssmp3_mg_verify_scr(Crap) ->
- io:format("ssmp3_mg_verify_scr -> error: "
- "~n Crap: ~p"
- "~n", [Crap]),
+ p("ssmp3_mg_verify_scr -> error: "
+ "~n Crap: ~p", [Crap]),
{error, Crap, ok}.
ssmp3_mg_do_verify_scr(AR) ->
- io:format("ssmp3_mg_do_verify_scr -> ok: "
- "~n AR: ~p~n", [AR]),
+ p("ssmp3_mg_do_verify_scr -> ok: "
+ "~n AR: ~p", [AR]),
CR =
case AR of
#'ActionReply'{commandReply = [CmdRep]} ->
@@ -1988,21 +1977,18 @@ ssmp3_mg_verify_notify_request(
{handle_trans_request, CH, V, ARs}, _Tids) ->
{error, {invalid_trans_request, {CH, V, ARs}}, ok};
ssmp3_mg_verify_notify_request(Crap, _Tids) ->
- io:format("ssmp3_mg_verify_notify_request -> unknown request"
- "~n Crap: ~p"
- "~n Tids: ~p"
- "~n", [Crap, _Tids]),
+ p("ssmp3_mg_verify_notify_request -> unknown request"
+ "~n Crap: ~p"
+ "~n Tids: ~p", [Crap, _Tids]),
{error, {unexpected_event, Crap}, ok}.
ssmp3_mg_do_verify_notify_request(Tids, ARs) ->
- io:format("ssmp3_mg_do_verify_notify_request -> ok"
- "~n Tids: ~p"
- "~n ARs: ~p"
- "~n", [Tids, ARs]),
+ p("ssmp3_mg_do_verify_notify_request -> ok"
+ "~n Tids: ~p"
+ "~n ARs: ~p", [Tids, ARs]),
ActionReplies = ssmp3_mg_do_verify_notify_request_ars(Tids, ARs),
- io:format("ssmp3_mg_do_verify_notify_request -> ok"
- "~n ActionReplies: ~p"
- "~n", [ActionReplies]),
+ p("ssmp3_mg_do_verify_notify_request -> ok"
+ "~n ActionReplies: ~p", [ActionReplies]),
Reply = {{handle_ack, ssmp3}, ActionReplies},
{ok, ARs, Reply}.
@@ -2016,10 +2002,9 @@ ssmp3_mg_do_verify_notify_request_ars([Tid|Tids], [AR|ARs], Acc) ->
ssmp3_mg_do_verify_notify_request_ars(Tids, ARs, [ActionReply|Acc]).
ssmp3_mg_do_verify_notify_request_ar(Tid, AR) ->
- io:format("ssmp3_mg_do_verify_notify_request_ar -> ok"
- "~n Tid: ~p"
- "~n AR: ~p"
- "~n", [Tid, AR]),
+ p("ssmp3_mg_do_verify_notify_request_ar -> ok"
+ "~n Tid: ~p"
+ "~n AR: ~p", [Tid, AR]),
{Cid, CR} =
case AR of
#'ActionRequest'{contextId = CtxId,
@@ -2067,9 +2052,8 @@ ssmp3_mg_verify_ack_fun() ->
fun(Event) -> ssmp3_mg_verify_ack(Event) end.
ssmp3_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, ssmp3}) ->
- io:format("ssmp3_mg_verify_ack -> ok"
- "~n CH: ~p"
- "~n", [CH]),
+ p("ssmp3_mg_verify_ack -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp3_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, CrapAckData}) ->
{error, {unknown_ack_data, CrapAckData, CH}, ok};
@@ -2105,7 +2089,7 @@ send_segmented_msg_plain4(doc) ->
"Send window = 3. ";
send_segmented_msg_plain4(Config) when is_list(Config) ->
Factor = ?config(megaco_factor, Config),
- ct:timetrap(Factor * ?SECS(60)),
+ ct:timetrap(Factor * ?MINS(1)),
Pre = fun() ->
MgcNode = make_node_name(mgc),
MgNode = make_node_name(mg),
@@ -2302,9 +2286,8 @@ ssmp4_mgc_verify_service_change_req_msg_fun() ->
end.
ssmp4_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) ->
- io:format("ssmp4_mgc_verify_service_change_req -> entry with"
- "~n M: ~p"
- "~n", [M]),
+ p("ssmp4_mgc_verify_service_change_req -> entry with"
+ "~n M: ~p", [M]),
Body =
case Mess of
#'Message'{version = 1,
@@ -2394,14 +2377,13 @@ ssmp4_mgc_verify_notify_reply_segment_msg_fun(SN, Last,
ssmp4_mgc_verify_notify_reply_segment(#'MegacoMessage'{mess = Mess} = M,
SN, Last, TransId, TermId, Cid) ->
- io:format("ssmp4_mgc_verify_notify_reply_segment -> entry with"
- "~n M: ~p"
- "~n SN: ~p"
- "~n Last: ~p"
- "~n TransId: ~p"
- "~n TermId: ~p"
- "~n Cid: ~p"
- "~n", [M, SN, Last, TransId, TermId, Cid]),
+ p("ssmp4_mgc_verify_notify_reply_segment -> entry with"
+ "~n M: ~p"
+ "~n SN: ~p"
+ "~n Last: ~p"
+ "~n TransId: ~p"
+ "~n TermId: ~p"
+ "~n Cid: ~p", [M, SN, Last, TransId, TermId, Cid]),
Body =
case Mess of
#'Message'{version = ?VERSION,
@@ -2582,7 +2564,7 @@ ssmp4_mg_event_sequence(Factor, text, tcp) ->
{megaco_update_conn_info, max_pdu_size, 128},
{sleep, 1000},
{megaco_callback, handle_trans_request, NotifyReqVerify},
- {megaco_callback, handle_trans_ack, AckVerify, TO(15000)},
+ {megaco_callback, handle_trans_ack, AckVerify, TO(?SECS(15))},
megaco_stop_user,
megaco_stop,
{sleep, 1000}
@@ -2594,12 +2576,12 @@ ssmp4_mg_verify_handle_connect_fun() ->
fun(Ev) -> ssmp4_mg_verify_handle_connect(Ev) end.
ssmp4_mg_verify_handle_connect({handle_connect, CH, 1}) ->
- io:format("ssmp4_mg_verify_handle_connect -> ok"
- "~n CH: ~p~n", [CH]),
+ p("ssmp4_mg_verify_handle_connect -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp4_mg_verify_handle_connect(Else) ->
- io:format("ssmp4_mg_verify_handle_connect -> unknown"
- "~n Else: ~p~n", [Else]),
+ p("ssmp4_mg_verify_handle_connect -> unknown"
+ "~n Else: ~p", [Else]),
{error, Else, ok}.
@@ -2609,14 +2591,13 @@ ssmp4_mg_verify_service_change_reply_fun() ->
ssmp4_mg_verify_scr({handle_trans_reply, _CH, 1, {ok, [AR]}, _}) ->
(catch ssmp4_mg_do_verify_scr(AR));
ssmp4_mg_verify_scr(Crap) ->
- io:format("ssmp4_mg_verify_scr -> error: "
- "~n Crap: ~p"
- "~n", [Crap]),
+ p("ssmp4_mg_verify_scr -> error: "
+ "~n Crap: ~p", [Crap]),
{error, Crap, ok}.
ssmp4_mg_do_verify_scr(AR) ->
- io:format("ssmp4_mg_do_verify_scr -> ok: "
- "~n AR: ~p~n", [AR]),
+ p("ssmp4_mg_do_verify_scr -> ok: "
+ "~n AR: ~p", [AR]),
CR =
case AR of
#'ActionReply'{commandReply = [CmdRep]} ->
@@ -2761,9 +2742,8 @@ ssmp4_mg_verify_ack_fun() ->
fun(Event) -> ssmp4_mg_verify_ack(Event) end.
ssmp4_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, ssmp4}) ->
- io:format("ssmp4_mg_verify_ack -> ok"
- "~n CH: ~p"
- "~n", [CH]),
+ p("ssmp4_mg_verify_ack -> ok"
+ "~n CH: ~p", [CH]),
{ok, CH, ok};
ssmp4_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, CrapAckData}) ->
{error, {unknown_ack_data, CrapAckData, CH}, ok};
@@ -2847,6 +2827,42 @@ do_send_segmented_msg_ooo1([MgcNode, MgNode]) ->
d("[MG] start the simulation"),
{ok, MgId} = megaco_test_megaco_generator:exec(Mg, MgEvSeq),
+ %% Await MGC ready for segments
+ d("await MGC trigger event"),
+ MgcPid =
+ receive
+ {ready_for_segmented_msg, mgc, Pid1} ->
+ d("received MGC trigger event"),
+ Pid1
+ after 5000 ->
+ d("timeout waiting for MGC trigger event: ~p",
+ [megaco_test_lib:flush()]),
+ ?ERROR(timeout_MGC_trigger_event)
+ end,
+
+ %% Await MG ready for segments
+ d("await MG trigger event"),
+ MgPid =
+ receive
+ {ready_for_segmented_msg, mg, Pid2} ->
+ d("received MG trigger event"),
+ Pid2
+ after 5000 ->
+ d("timeout waiting for MG trigger event: ~p",
+ [megaco_test_lib:flush()]),
+ ?ERROR(timeout_MG_trigger_event)
+ end,
+
+ %% Instruct the MG to continue
+ d("send continue to MG"),
+ MgPid ! {continue_with_segmented_msg, self()},
+
+ sleep(500),
+
+ %% Instruct the MGC to continue
+ d("send continue to MGC"),
+ MgcPid ! {continue_with_segmented_msg, self()},
+
d("await the generator reply(s)"),
await_completion([MgcId, MgId]),
@@ -2873,6 +2889,8 @@ ssmo1_mgc_event_sequence(text, tcp) ->
Mid = {deviceName,"mgc"},
ScrVerifyFun = ssmo1_mgc_verify_service_change_req_msg_fun(),
ServiceChangeRep = ssmo1_mgc_service_change_reply_msg(Mid, 1),
+ AnnounceReadySegs = ssmo1_mgc_announce_ready_for_segmented_msg_fun(),
+ AwaitContinueSegs = ssmo1_mgc_continue_with_segmented_msg_fun(),
TermId1 =
#megaco_term_id{id = ["00000000","00000000","00000001"]},
CtxId1 = 1,
@@ -2943,7 +2961,13 @@ ssmo1_mgc_event_sequence(text, tcp) ->
{expect_accept, any},
{expect_receive, "service-change-request", {ScrVerifyFun, 5000}},
{send, "service-change-reply", ServiceChangeRep},
- {expect_nothing, 1000},
+
+ {trigger, "announce ready for segmented message",
+ AnnounceReadySegs},
+ {trigger, "await continue for segmented message",
+ AwaitContinueSegs},
+
+ %% {expect_nothing, 1000},
{send, "notify request", NotifyReq},
{expect_receive, "notify reply: segment 1", {NrVerifyFun1, 1000}},
{expect_receive, "notify reply: segment 2", {NrVerifyFun2, 1000}},
@@ -2992,9 +3016,8 @@ ssmo1_mgc_verify_service_change_req_msg_fun() ->
end.
ssmo1_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) ->
- io:format("ssmo1_mgc_verify_service_change_req -> entry with"
- "~n M: ~p"
- "~n", [M]),
+ p("ssmo1_mgc_verify_service_change_req -> entry with"
+ "~n M: ~p", [M]),
Body =
case Mess of
#'Message'{version = 1,
@@ -3074,6 +3097,23 @@ ssmo1_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) ->
{error, {invalid_serviceChangeParms, Parms}}
end.
+ssmo1_mgc_announce_ready_for_segmented_msg_fun() ->
+ TC = self(),
+ fun() ->
+ TC ! {ready_for_segmented_msg, mgc, self()}
+ end.
+
+ssmo1_mgc_continue_with_segmented_msg_fun() ->
+ TC = self(),
+ fun() ->
+ p("[MGC] await continue with segmented message"),
+ receive
+ {continue_with_segmented_msg, TC} ->
+ p("[MGC] received continue with segmented message"),
+ ok
+ end
+ end.
+
ssmo1_mgc_verify_notify_reply_segment_msg_fun(SN, Last,
TransId, TermId, Cid) ->
fun(Msg) ->
@@ -3240,6 +3280,8 @@ ssmo1_mg_event_sequence(text, tcp) ->
ConnectVerify = ssmo1_mg_verify_handle_connect_fun(),
ServiceChangeReq = ssmo1_mg_service_change_request_ar(Mid, 1),
ServiceChangeReplyVerify = ssmo1_mg_verify_service_change_reply_fun(),
+ AnnounceReadySegs = ssmo1_mg_announce_ready_for_segmented_msg_fun(),
+ AwaitContinueSegs = ssmo1_mg_continue_with_segmented_msg_fun(),
Tid1 = #megaco_term_id{id = ["00000000","00000000","00000001"]},
Tid2 = #megaco_term_id{id = ["00000000","00000000","00000002"]},
Tid3 = #megaco_term_id{id = ["00000000","00000000","00000003"]},
@@ -3268,8 +3310,13 @@ ssmo1_mg_event_sequence(text, tcp) ->
{megaco_callback, handle_trans_reply, ServiceChangeReplyVerify},
{megaco_update_conn_info, protocol_version, ?VERSION},
{megaco_update_conn_info, segment_send, 3},
- {megaco_update_conn_info, max_pdu_size, 128},
- {sleep, 1000},
+ {megaco_update_conn_info, max_pdu_size, 128},
+
+ {trigger, "announce ready for segmented message",
+ AnnounceReadySegs},
+ {trigger, "await continue for segmented message",
+ AwaitContinueSegs},
+
{megaco_callback, handle_trans_request, NotifyReqVerify},
{megaco_callback, handle_trans_ack, AckVerify, 15000},
megaco_stop_user,
@@ -3278,7 +3325,6 @@ ssmo1_mg_event_sequence(text, tcp) ->
],
EvSeq.
-
ssmo1_mg_verify_handle_connect_fun() ->
fun(Ev) -> ssmo1_mg_verify_handle_connect(Ev) end.
@@ -3354,6 +3400,23 @@ ssmo1_mg_do_verify_scr(AR) ->
{error, Reason6, ok}
end.
+ssmo1_mg_announce_ready_for_segmented_msg_fun() ->
+ TC = self(),
+ fun() ->
+ TC ! {ready_for_segmented_msg, mg, self()}
+ end.
+
+ssmo1_mg_continue_with_segmented_msg_fun() ->
+ TC = self(),
+ fun() ->
+ p("[MG] await continue with segmented message"),
+ receive
+ {continue_with_segmented_msg, TC} ->
+ p("[MG] received continue with segmented message"),
+ ok
+ end
+ end.
+
ssmo1_mg_verify_notify_request_fun(Tids) ->
fun(Req) -> ssmo1_mg_verify_notify_request(Req, Tids) end.
@@ -7896,12 +7959,12 @@ await_completion(Ids) ->
ok;
{error, {OK, ERROR}} ->
d("ERROR => "
- "~n OK: ~p"
- "~n ERROR: ~p", [OK, ERROR]),
+ "~n OK: ~p"
+ "~n ERROR: ~p", [OK, ERROR]),
?ERROR({failed, ERROR});
{error, Reply} ->
d("ERROR => "
- "~n Reply: ~p", [Reply]),
+ "~n Reply: ~p", [Reply]),
?ERROR({failed, Reply})
end.
@@ -7934,6 +7997,9 @@ try_tc(TCName, Name, Verbosity, Pre, Case, Post) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+p(F) ->
+ p(F, []).
+
p(F, A) ->
io:format("*** [~s] ~p ***"
"~n " ++ F ++ "~n",
diff --git a/lib/megaco/test/megaco_test_lib.erl b/lib/megaco/test/megaco_test_lib.erl
index 1e4b7841ee..a04b27a061 100644
--- a/lib/megaco/test/megaco_test_lib.erl
+++ b/lib/megaco/test/megaco_test_lib.erl
@@ -28,7 +28,10 @@
%% -compile(export_all).
+-compile({no_auto_import, [error/3]}).
+
-export([
+ proxy_call/3,
log/4,
error/3,
@@ -71,6 +74,31 @@
%% ----------------------------------------------------------------
+%% Proxy Call
+%% This is used when we need to assign a timeout to a call, but the
+%% call itself does not provide such an argument.
+%%
+%% This has nothing to to with the proxy_start and proxy_init
+%% functions below.
+
+proxy_call(F, Timeout, Default)
+ when is_function(F, 0) andalso
+ is_integer(Timeout) andalso (Timeout > 0) andalso
+ is_function(Default, 0) ->
+ {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end),
+ receive
+ {'DOWN', M, process, P, Reply} ->
+ Reply
+ after Timeout ->
+ erlang:demonitor(M, [flush]),
+ exit(P, kill),
+ Default()
+ end;
+proxy_call(F, Timeout, Default) ->
+ proxy_call(F, Timeout, fun() -> Default end).
+
+
+%% ----------------------------------------------------------------
%% Time related function
%%
@@ -164,6 +192,10 @@ os_base_skip(Skippable, OsFam, OsName) ->
case lists:keysearch(OsFam, 1, Skippable) of
{value, {OsFam, OsName}} ->
true;
+ {value, {OsFam, Check}} when is_function(Check, 0) ->
+ Check();
+ {value, {OsFam, Check}} when is_function(Check, 1) ->
+ Check(os:version());
{value, {OsFam, OsNames}} when is_list(OsNames) ->
%% OsNames is a list of:
%% [atom()|{atom(), function/0 | function/1}]
@@ -443,11 +475,31 @@ pprint(F, A) ->
init_per_suite(Config) ->
+ ct:timetrap(minutes(3)),
+
+ try analyze_and_print_host_info() of
+ {Factor, HostInfo} when is_integer(Factor) ->
+ try maybe_skip(HostInfo) of
+ true ->
+ {skip, "Unstable host and/or os (or combo thererof)"};
+ false ->
+ maybe_start_global_sys_monitor(Config),
+ [{megaco_factor, Factor} | Config]
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+maybe_skip(_HostInfo) ->
+
%% We have some crap machines that causes random test case failures
%% for no obvious reason. So, attempt to identify those without actually
%% checking for the host name...
- %% We have two "machines" we are checking for. Both are old installations
- %% running on really slow VMs (the host machines are old and tired).
+
LinuxVersionVerify =
fun(V) when (V > {3,6,11}) ->
false; % OK - No skip
@@ -458,6 +510,28 @@ init_per_suite(Config) ->
_ ->
false
end;
+ (V) when (V =:= {3,4,20}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Wind River Linux 5.0.1.0" ++ _ -> % *Old* Wind River => skip
+ true;
+ _ ->
+ false
+ end;
+ (V) when (V =:= {2,6,32}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Debian GNU/Linux 6.0 " ++ _ -> % Stone age Debian => Skip
+ true;
+ _ ->
+ false
+ end;
+ (V) when (V =:= {2,6,10}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "MontaVista" ++ _ -> % Stone age MontaVista => Skip
+ %% The real problem is that the machine is *very* slow
+ true;
+ _ ->
+ false
+ end;
(V) when (V > {2,6,24}) ->
false; % OK - No skip
(_) ->
@@ -478,37 +552,27 @@ init_per_suite(Config) ->
%% This version is *not* ok: Skip
true
end,
- %% We are "looking" for a specific machine (a VM)
- %% which are *old and crappy" and slow, because it
- %% causes a bunch of test cases to fail randomly.
- %% But we don not want to test for the host name...
- WinVersionVerify =
- fun(V) when (V =:= {6,2,9200}) ->
- try erlang:system_info(schedulers) of
- 2 ->
- true;
- _ ->
- false
- catch
- _:_:_ ->
- true
- end;
- (_) ->
+ SkipWindowsOnVirtual =
+ %% fun() ->
+ %% SysMan = win_sys_info_lookup(system_manufacturer, HostInfo),
+ %% case string:to_lower(SysMan) of
+ %% "vmware" ++ _ ->
+ %% true;
+ %% _ ->
+ %% false
+ %% end
+ %% end,
+ fun() ->
+ %% The host has been replaced and the VM has been reinstalled
+ %% so for now we give it a chance...
false
- end,
+ end,
COND = [
{unix, [{linux, LinuxVersionVerify},
- {darwin, DarwinVersionVerify}]}%% ,
- %% {win32, [{nt, WinVersionVerify}]}
+ {darwin, DarwinVersionVerify}]},
+ {win32, SkipWindowsOnVirtual}
],
- case os_based_skip(COND) of
- true ->
- {skip, "Unstable host and/or os (or combo thererof)"};
- false ->
- Factor = analyze_and_print_host_info(),
- maybe_start_global_sys_monitor(Config),
- [{megaco_factor, Factor} | Config]
- end.
+ os_based_skip(COND).
%% We start the global system monitor unless explicitly disabled
maybe_start_global_sys_monitor(Config) ->
@@ -569,10 +633,6 @@ end_per_testcase(_Case, Config) ->
%% the load for some test cases. Such as run time or number of
%% iteraions. This only works for some OSes.
%%
-%% We make some calculations on Linux, OpenBSD and FreeBSD.
-%% On SunOS we always set the factor to 2 (just to be on the safe side)
-%% On all other os:es (mostly windows) we check the number of schedulers,
-%% but at least the factor will be 2.
analyze_and_print_host_info() ->
{OsFam, OsName} = os:type(),
Version =
@@ -589,138 +649,309 @@ analyze_and_print_host_info() ->
analyze_and_print_openbsd_host_info(Version);
{unix, freebsd} ->
analyze_and_print_freebsd_host_info(Version);
+ {unix, netbsd} ->
+ analyze_and_print_netbsd_host_info(Version);
+ {unix, darwin} ->
+ analyze_and_print_darwin_host_info(Version);
{unix, sunos} ->
analyze_and_print_solaris_host_info(Version);
{win32, nt} ->
analyze_and_print_win_host_info(Version);
_ ->
io:format("OS Family: ~p"
- "~n OS Type: ~p"
- "~n Version: ~p"
- "~n Num Schedulers: ~s"
+ "~n OS Type: ~p"
+ "~n Version: ~p"
+ "~n Num Online Schedulers: ~s"
"~n", [OsFam, OsName, Version, str_num_schedulers()]),
- try erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- N when (N =< 6) ->
- 2;
- _ ->
- 1
- catch
- _:_:_ ->
- 10
- end
+ {num_schedulers_to_factor(), []}
end.
str_num_schedulers() ->
- try erlang:system_info(schedulers) of
+ try erlang:system_info(schedulers_online) of
N -> f("~w", [N])
catch
_:_:_ -> "-"
end.
+num_schedulers_to_factor() ->
+ try erlang:system_info(schedulers_online) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end.
-analyze_and_print_linux_host_info(Version) ->
+
+
+linux_which_distro(Version) ->
case file:read_file_info("/etc/issue") of
{ok, _} ->
- io:format("Linux: ~s"
- "~n ~s"
- "~n",
- [Version, string:trim(os:cmd("cat /etc/issue"))]);
+ case [string:trim(S) ||
+ S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of
+ [DistroStr|_] ->
+ io:format("Linux: ~s"
+ "~n ~s"
+ "~n",
+ [Version, DistroStr]),
+ case DistroStr of
+ "Wind River Linux" ++ _ ->
+ wind_river;
+ "MontaVista" ++ _ ->
+ montavista;
+ "Yellow Dog" ++ _ ->
+ yellow_dog;
+ _ ->
+ other
+ end;
+ X ->
+ io:format("Linux: ~s"
+ "~n ~p"
+ "~n",
+ [Version, X]),
+ other
+ end;
_ ->
io:format("Linux: ~s"
- "~n", [Version])
- end,
+ "~n", [Version]),
+ other
+ end.
+
+
+analyze_and_print_linux_host_info(Version) ->
+ Distro = linux_which_distro(Version),
Factor =
- case (catch linux_which_cpuinfo()) of
+ case (catch linux_which_cpuinfo(Distro)) of
{ok, {CPU, BogoMIPS}} ->
io:format("CPU: "
- "~n Model: ~s"
- "~n BogoMIPS: ~s"
- "~n Num Schedulers: ~s"
+ "~n Model: ~s"
+ "~n BogoMIPS: ~w"
+ "~n Num Online Schedulers: ~s"
"~n", [CPU, BogoMIPS, str_num_schedulers()]),
- %% We first assume its a float, and if not try integer
- try list_to_float(string:trim(BogoMIPS)) of
- F when F > 4000 ->
+ if
+ (BogoMIPS > 20000) ->
1;
- F when F > 1000 ->
+ (BogoMIPS > 10000) ->
2;
- F when F > 500 ->
+ (BogoMIPS > 5000) ->
3;
- _ ->
- 5
- catch
- _:_:_ ->
- try list_to_integer(string:trim(BogoMIPS)) of
- I when I > 4000 ->
- 1;
- I when I > 1000 ->
- 2;
- I when I > 500 ->
- 3;
- _ ->
- 5
- catch
- _:_:_ ->
- 5 % Be a "bit" conservative...
- end
+ (BogoMIPS > 2000) ->
+ 5;
+ (BogoMIPS > 1000) ->
+ 8;
+ true ->
+ 10
end;
{ok, CPU} ->
io:format("CPU: "
- "~n Model: ~s"
- "~n Num Schedulers: ~s"
+ "~n Model: ~s"
+ "~n Num Online Schedulers: ~s"
"~n", [CPU, str_num_schedulers()]),
- 2; % Be a "bit" conservative...
+ num_schedulers_to_factor();
_ ->
- 5 % Be a "bit" (more) conservative...
+ 5
end,
%% Check if we need to adjust the factor because of the memory
try linux_which_meminfo() of
AddFactor ->
- Factor + AddFactor
+ {Factor + AddFactor, []}
catch
_:_:_ ->
- Factor
+ {Factor, []}
+ end.
+
+
+linux_cpuinfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/cpuinfo").
+
+linux_cpuinfo_cpu() ->
+ case linux_cpuinfo_lookup("cpu") of
+ [Model] ->
+ Model;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_motherboard() ->
+ case linux_cpuinfo_lookup("motherboard") of
+ [MB] ->
+ MB;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_bogomips() ->
+ case linux_cpuinfo_lookup("bogomips") of
+ BMips when is_list(BMips) ->
+ try lists:sum([bogomips_to_int(BM) || BM <- BMips])
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_total_bogomips() ->
+ case linux_cpuinfo_lookup("total bogomips") of
+ [TBM] ->
+ try bogomips_to_int(TBM)
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+bogomips_to_int(BM) ->
+ try list_to_float(BM) of
+ F ->
+ floor(F)
+ catch
+ _:_:_ ->
+ try list_to_integer(BM) of
+ I ->
+ I
+ catch
+ _:_:_ ->
+ throw(noinfo)
+ end
+ end.
+
+linux_cpuinfo_model() ->
+ case linux_cpuinfo_lookup("model") of
+ [M] ->
+ M;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_platform() ->
+ case linux_cpuinfo_lookup("platform") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_model_name() ->
+ case linux_cpuinfo_lookup("model name") of
+ [P|_] ->
+ P;
+ _X ->
+ "-"
+ end.
+
+linux_cpuinfo_processor() ->
+ case linux_cpuinfo_lookup("Processor") of
+ [P] ->
+ P;
+ _ ->
+ "-"
end.
-linux_which_cpuinfo() ->
+linux_which_cpuinfo(montavista) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(yellow_dog) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ {ok, CPU};
+
+linux_which_cpuinfo(wind_river) ->
+ CPU =
+ case linux_cpuinfo_model() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_platform() of
+ "-" ->
+ Model;
+ Platform ->
+ Model ++ " (" ++ Platform ++ ")"
+ end
+ end,
+ case linux_cpuinfo_total_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(other) ->
%% Check for x86 (Intel or AMD)
CPU =
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"model name\" /proc/cpuinfo"), [$:,$\n])] of
- ["model name", ModelName | _] ->
- ModelName;
- _ ->
+ case linux_cpuinfo_model_name() of
+ "-" ->
%% ARM (at least some distros...)
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"Processor\" /proc/cpuinfo"), [$:,$\n])] of
- ["Processor", Proc | _] ->
- Proc;
- _ ->
+ case linux_cpuinfo_processor() of
+ "-" ->
%% Ok, we give up
- throw(noinfo)
- catch
- _:_:_ ->
- throw(noinfo)
- end
- catch
- _:_:_ ->
- throw(noinfo)
+ throw(noinfo);
+ Proc ->
+ Proc
+ end;
+ ModelName ->
+ ModelName
end,
- try [string:trim(S) || S <- string:tokens(os:cmd("grep -i \"bogomips\" /proc/cpuinfo"), [$:,$\n])] of
- [_, BMips | _] ->
- {ok, {CPU, BMips}};
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end.
+
+linux_meminfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/meminfo").
+
+linux_meminfo_memtotal() ->
+ case linux_meminfo_lookup("MemTotal") of
+ [X] ->
+ X;
_ ->
- {ok, CPU}
- catch
- _:_:_ ->
- {ok, CPU}
+ "-"
end.
%% We *add* the value this return to the Factor.
linux_which_meminfo() ->
- try [string:trim(S) || S <- string:tokens(os:cmd("grep MemTotal /proc/meminfo"), [$:])] of
- [_, MemTotal] ->
+ case linux_meminfo_memtotal() of
+ "-" ->
+ 0;
+ MemTotal ->
io:format("Memory:"
"~n ~s"
"~n", [MemTotal]),
@@ -750,12 +981,7 @@ linux_which_meminfo() ->
end;
_X ->
0
- end;
- _ ->
- 0
- catch
- _:_:_ ->
- 0
+ end
end.
@@ -841,11 +1067,11 @@ analyze_and_print_openbsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
- 1
+ {5, []}
end.
@@ -928,21 +1154,22 @@ analyze_and_print_freebsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
io:format("CPU:"
"~n Num Schedulers: ~w"
"~n", [erlang:system_info(schedulers)]),
- case erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- _ ->
- 2
- end
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
end.
analyze_freebsd_cpu(Extract) ->
@@ -982,6 +1209,426 @@ analyze_freebsd_item(Extract, Key, Process, Default) ->
end.
+analyze_and_print_netbsd_host_info(Version) ->
+ io:format("NetBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ %% This test require that the program 'sysctl' is in the path.
+ %% First test with 'which sysctl', if that does not work
+ %% try with 'which /sbin/sysctl'. If that does not work either,
+ %% we skip the test...
+ try
+ begin
+ SysCtl =
+ case string:trim(os:cmd("which sysctl")) of
+ [] ->
+ case string:trim(os:cmd("which /sbin/sysctl")) of
+ [] ->
+ throw(sysctl);
+ SC2 ->
+ SC2
+ end;
+ SC1 ->
+ SC1
+ end,
+ Extract =
+ fun(Key) ->
+ [string:trim(S) ||
+ S <-
+ string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
+ [$=])]
+ end,
+ CPU = analyze_netbsd_cpu(Extract),
+ Machine = analyze_netbsd_machine(Extract),
+ Arch = analyze_netbsd_machine_arch(Extract),
+ CPUSpeed = analyze_netbsd_cpu_speed(Extract),
+ NCPU = analyze_netbsd_ncpu(Extract),
+ Memory = analyze_netbsd_memory(Extract),
+ io:format("CPU:"
+ "~n Model: ~s (~s, ~s)"
+ "~n Speed: ~w MHz"
+ "~n N: ~w"
+ "~n Num Schedulers: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n",
+ [CPU, Machine, Arch, CPUSpeed, NCPU,
+ erlang:system_info(schedulers), Memory]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU =:= -1) ->
+ 1;
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ {CPUFactor + MemAddFactor, []}
+ end
+ catch
+ _:_:_ ->
+ io:format("CPU:"
+ "~n Num Schedulers: ~w"
+ "~n", [erlang:system_info(schedulers)]),
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
+ end.
+
+analyze_netbsd_cpu(Extract) ->
+ analyze_netbsd_item(Extract, "hw.model", fun(X) -> X end, "-").
+
+analyze_netbsd_machine(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine", fun(X) -> X end, "-").
+
+analyze_netbsd_machine_arch(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine_arch", fun(X) -> X end, "-").
+
+analyze_netbsd_cpu_speed(Extract) ->
+ analyze_netbsd_item(Extract, "machdep.dmi.processor-frequency",
+ fun(X) -> case string:tokens(X, [$\ ]) of
+ [MHz, "MHz"] ->
+ list_to_integer(MHz);
+ _ ->
+ -1
+ end
+ end, "-").
+
+analyze_netbsd_ncpu(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.ncpu",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_netbsd_memory(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.physmem64",
+ fun(X) -> list_to_integer(X) div 1024 end,
+ -1).
+
+analyze_netbsd_item(Extract, Key, Process, Default) ->
+ analyze_freebsd_item(Extract, Key, Process, Default).
+
+
+
+%% Model Identifier: Macmini7,1
+%% Processor Name: Intel Core i5
+%% Processor Speed: 2,6 GHz
+%% Number of Processors: 1
+%% Total Number of Cores: 2
+%% L2 Cache (per Core): 256 KB
+%% L3 Cache: 3 MB
+%% Hyper-Threading Technology: Enabled
+%% Memory: 16 GB
+
+analyze_and_print_darwin_host_info(Version) ->
+ %% This stuff is for macOS.
+ %% If we ever tested on a pure darwin machine,
+ %% we need to find some other way to find some info...
+ %% Also, I suppose its possible that we for some other
+ %% reason *fail* to get the info...
+ case analyze_darwin_software_info() of
+ [] ->
+ io:format("Darwin:"
+ "~n Version: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n", [Version, str_num_schedulers()]),
+ {num_schedulers_to_factor(), []};
+ SwInfo when is_list(SwInfo) ->
+ SystemVersion = analyze_darwin_sw_system_version(SwInfo),
+ KernelVersion = analyze_darwin_sw_kernel_version(SwInfo),
+ HwInfo = analyze_darwin_hardware_info(),
+ ModelName = analyze_darwin_hw_model_name(HwInfo),
+ ModelId = analyze_darwin_hw_model_identifier(HwInfo),
+ ProcName = analyze_darwin_hw_processor_name(HwInfo),
+ ProcSpeed = analyze_darwin_hw_processor_speed(HwInfo),
+ NumProc = analyze_darwin_hw_number_of_processors(HwInfo),
+ NumCores = analyze_darwin_hw_total_number_of_cores(HwInfo),
+ Memory = analyze_darwin_hw_memory(HwInfo),
+ io:format("Darwin:"
+ "~n System Version: ~s"
+ "~n Kernel Version: ~s"
+ "~n Model: ~s (~s)"
+ "~n Processor: ~s (~s, ~s, ~s)"
+ "~n Memory: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n", [SystemVersion, KernelVersion,
+ ModelName, ModelId,
+ ProcName, ProcSpeed, NumProc, NumCores,
+ Memory,
+ str_num_schedulers()]),
+ CPUFactor = analyze_darwin_cpu_to_factor(ProcName,
+ ProcSpeed,
+ NumProc,
+ NumCores),
+ MemFactor = analyze_darwin_memory_to_factor(Memory),
+ if (MemFactor =:= 1) ->
+ {CPUFactor, []};
+ true ->
+ {CPUFactor + MemFactor, []}
+ end
+ end.
+
+analyze_darwin_sw_system_version(SwInfo) ->
+ proplists:get_value("system version", SwInfo, "-").
+
+analyze_darwin_sw_kernel_version(SwInfo) ->
+ proplists:get_value("kernel version", SwInfo, "-").
+
+analyze_darwin_software_info() ->
+ analyze_darwin_system_profiler("SPSoftwareDataType").
+
+analyze_darwin_hw_model_name(HwInfo) ->
+ proplists:get_value("model name", HwInfo, "-").
+
+analyze_darwin_hw_model_identifier(HwInfo) ->
+ proplists:get_value("model identifier", HwInfo, "-").
+
+analyze_darwin_hw_processor_name(HwInfo) ->
+ proplists:get_value("processor name", HwInfo, "-").
+
+analyze_darwin_hw_processor_speed(HwInfo) ->
+ proplists:get_value("processor speed", HwInfo, "-").
+
+analyze_darwin_hw_number_of_processors(HwInfo) ->
+ proplists:get_value("number of processors", HwInfo, "-").
+
+analyze_darwin_hw_total_number_of_cores(HwInfo) ->
+ proplists:get_value("total number of cores", HwInfo, "-").
+
+analyze_darwin_hw_memory(HwInfo) ->
+ proplists:get_value("memory", HwInfo, "-").
+
+analyze_darwin_hardware_info() ->
+ analyze_darwin_system_profiler("SPHardwareDataType").
+
+%% This basically has the structure: "Key: Value"
+%% But could also be (for example):
+%% "Something:" (which we ignore)
+%% "Key: Value1:Value2"
+analyze_darwin_system_profiler(DataType) ->
+ %% First, make sure the program actually exist:
+ case os:cmd("which system_profiler") of
+ [] ->
+ [];
+ _ ->
+ D0 = os:cmd("system_profiler " ++ DataType),
+ D1 = string:tokens(D0, [$\n]),
+ D2 = [string:trim(S1) || S1 <- D1],
+ D3 = [string:tokens(S2, [$:]) || S2 <- D2],
+ analyze_darwin_system_profiler2(D3)
+ end.
+
+analyze_darwin_system_profiler2(L) ->
+ analyze_darwin_system_profiler2(L, []).
+
+analyze_darwin_system_profiler2([], Acc) ->
+ [{string:to_lower(K), V} || {K, V} <- lists:reverse(Acc)];
+analyze_darwin_system_profiler2([[_]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, Acc);
+analyze_darwin_system_profiler2([[H1,H2]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, [{H1, string:trim(H2)}|Acc]);
+analyze_darwin_system_profiler2([[H|TH0]|T], Acc) ->
+ %% Some value parts has ':' in them, so put them together
+ TH1 = colonize(TH0),
+ analyze_darwin_system_profiler2(T, [{H, string:trim(TH1)}|Acc]).
+
+%% This is only called if the length is at least 2
+colonize([L1, L2]) ->
+ L1 ++ ":" ++ L2;
+colonize([H|T]) ->
+ H ++ ":" ++ colonize(T).
+
+
+%% The memory looks like this "<size> <unit>". Example: "2 GB"
+analyze_darwin_memory_to_factor(Mem) ->
+ case [string:to_lower(S) || S <- string:tokens(Mem, [$\ ])] of
+ [_SzStr, "tb"] ->
+ 1;
+ [SzStr, "gb"] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz < 2 ->
+ 20;
+ Sz when Sz < 4 ->
+ 10;
+ Sz when Sz < 8 ->
+ 5;
+ Sz when Sz < 16 ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 20
+ end;
+ [_SzStr, "mb"] ->
+ 20;
+ _ ->
+ 20
+ end.
+
+
+%% The speed is a string: "<speed> <unit>"
+%% the speed may be a float, which we transforms into an integer of MHz.
+%% To calculate a factor based on processor speed, number of procs
+%% and number of cores is ... not an exact ... science ...
+analyze_darwin_cpu_to_factor(_ProcName,
+ ProcSpeedStr, NumProcStr, NumCoresStr) ->
+ Speed =
+ case [string:to_lower(S) || S <- string:tokens(ProcSpeedStr, [$\ ])] of
+ [SpeedStr, "mhz"] ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ SpeedI
+ catch
+ _:_:_ ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(SpeedF)
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ [SpeedStr, "ghz"] ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(1000*SpeedF)
+ catch
+ _:_:_ ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ 1000*SpeedI
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ _ ->
+ -1
+ end,
+ NumProc = try list_to_integer(NumProcStr) of
+ NumProcI ->
+ NumProcI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ NumCores = try list_to_integer(NumCoresStr) of
+ NumCoresI ->
+ NumCoresI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ if
+ (Speed > 3000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 5;
+ (NumCores < 4) ->
+ 3;
+ (NumCores < 6) ->
+ 2;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ (Speed > 2000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 8;
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 6) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 8) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ true ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 10;
+ (NumCores < 4) ->
+ 7;
+ (NumCores < 6) ->
+ 5;
+ (NumCores < 8) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 8;
+ (NumCores < 8) ->
+ 4;
+ true ->
+ 1
+ end
+ end
+ end.
+
+
analyze_and_print_solaris_host_info(Version) ->
Release =
case file:read_file_info("/etc/release") of
@@ -1046,13 +1693,13 @@ analyze_and_print_solaris_host_info(Version) ->
"-"
end,
io:format("Solaris: ~s"
- "~n Release: ~s"
- "~n Banner Name: ~s"
- "~n Instruction Set: ~s"
- "~n CPUs: ~s (~s)"
- "~n System Config: ~s"
- "~n Memory Size: ~s"
- "~n Num Schedulers: ~s"
+ "~n Release: ~s"
+ "~n Banner Name: ~s"
+ "~n Instruction Set: ~s"
+ "~n CPUs: ~s (~s)"
+ "~n System Config: ~s"
+ "~n Memory Size: ~s"
+ "~n Num Online Schedulers: ~s"
"~n~n", [Version, Release, BannerName, InstructionSet,
NumPhysProc, NumProc,
SysConf, MemSz,
@@ -1093,19 +1740,19 @@ analyze_and_print_solaris_host_info(Version) ->
_:_:_ ->
10
end,
- try erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- N when (N =< 6) ->
- 2;
- _ ->
- 1
- catch
- _:_:_ ->
- 10
- end + MemFactor.
+ {try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end + MemFactor, []}.
analyze_and_print_win_host_info(Version) ->
@@ -1113,14 +1760,19 @@ analyze_and_print_win_host_info(Version) ->
OsName = win_sys_info_lookup(os_name, SysInfo),
OsVersion = win_sys_info_lookup(os_version, SysInfo),
SysMan = win_sys_info_lookup(system_manufacturer, SysInfo),
+ SysMod = win_sys_info_lookup(system_model, SysInfo),
NumProcs = win_sys_info_lookup(num_processors, SysInfo),
TotPhysMem = win_sys_info_lookup(total_phys_memory, SysInfo),
io:format("Windows: ~s"
"~n OS Version: ~s (~p)"
"~n System Manufacturer: ~s"
+ "~n System Model: ~s"
"~n Number of Processor(s): ~s"
"~n Total Physical Memory: ~s"
- "~n", [OsName, OsVersion, Version, SysMan, NumProcs, TotPhysMem]),
+ "~n Num Online Schedulers: ~s"
+ "~n", [OsName, OsVersion, Version,
+ SysMan, SysMod, NumProcs, TotPhysMem,
+ str_num_schedulers()]),
MemFactor =
try
begin
@@ -1172,7 +1824,7 @@ analyze_and_print_win_host_info(Version) ->
_ ->
2
end,
- CPUFactor + MemFactor.
+ {CPUFactor + MemFactor, SysInfo}.
win_sys_info_lookup(Key, SysInfo) ->
win_sys_info_lookup(Key, SysInfo, "-").
@@ -1187,14 +1839,25 @@ win_sys_info_lookup(Key, SysInfo, Def) ->
%% This function only extracts the prop we actually care about!
which_win_system_info() ->
- SysInfo = os:cmd("systeminfo"),
- try process_win_system_info(string:tokens(SysInfo, [$\r, $\n]), [])
- catch
- _:_:_ ->
- io:format("Failed process System info: "
- "~s~n", [SysInfo]),
- []
- end.
+ F = fun() ->
+ try
+ begin
+ SysInfo = os:cmd("systeminfo"),
+ process_win_system_info(
+ string:tokens(SysInfo, [$\r, $\n]), [])
+ end
+ catch
+ C:E:S ->
+ io:format("Failed get or process System info: "
+ " Error Class: ~p"
+ " Error: ~p"
+ " Stack: ~p"
+ "~n", [C, E, S]),
+ []
+ end
+ end,
+ proxy_call(F, minutes(1),
+ fun() -> throw({skip, "System info timeout"}) end).
process_win_system_info([], Acc) ->
Acc;
@@ -1211,6 +1874,9 @@ process_win_system_info([H|T], Acc) ->
"system manufacturer" ->
process_win_system_info(T,
[{system_manufacturer, string:trim(Value)}|Acc]);
+ "system model" ->
+ process_win_system_info(T,
+ [{system_model, string:trim(Value)}|Acc]);
"processor(s)" ->
[NumProcStr|_] = string:tokens(Value, [$\ ]),
T2 = lists:nthtail(list_to_integer(NumProcStr), T),
@@ -1227,6 +1893,22 @@ process_win_system_info([H|T], Acc) ->
end.
+linux_info_lookup(Key, File) ->
+ try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of
+ Info ->
+ linux_info_lookup_collect(Key, Info, [])
+ catch
+ _:_:_ ->
+ "-"
+ end.
+
+linux_info_lookup_collect(_Key, [], Values) ->
+ lists:reverse(Values);
+linux_info_lookup_collect(Key, [Key, Value|Rest], Values) ->
+ linux_info_lookup_collect(Key, Rest, [Value|Values]);
+linux_info_lookup_collect(_, _, Values) ->
+ lists:reverse(Values).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Set kill timer
diff --git a/lib/megaco/test/megaco_test_lib.hrl b/lib/megaco/test/megaco_test_lib.hrl
index f6af199d4e..0777adbcd0 100644
--- a/lib/megaco/test/megaco_test_lib.hrl
+++ b/lib/megaco/test/megaco_test_lib.hrl
@@ -29,6 +29,8 @@
-define(LIB, megaco_test_lib).
+-define(PCALL(F, T, D), ?LIB:proxy_call(F, T, D)).
+
-define(APPLY(Proxy, Fun),
Proxy ! {apply, Fun}).
diff --git a/lib/megaco/test/megaco_test_megaco_generator.erl b/lib/megaco/test/megaco_test_megaco_generator.erl
index 4eedd8d731..f6ea57ab41 100644
--- a/lib/megaco/test/megaco_test_megaco_generator.erl
+++ b/lib/megaco/test/megaco_test_megaco_generator.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -313,6 +313,9 @@ handle_parse({megaco_callback, Verifiers0} = _Instruction, State)
handle_parse({trigger, Trigger} = Instruction, State)
when is_function(Trigger) ->
{ok, Instruction, State};
+handle_parse({trigger, Desc, Trigger} = Instruction, State)
+ when is_list(Desc) andalso is_function(Trigger) ->
+ {ok, Instruction, State};
handle_parse(Instruction, _State) ->
error({invalid_instruction, Instruction}).
@@ -770,6 +773,10 @@ handle_exec({trigger, Trigger}, State) when is_function(Trigger) ->
p("trigger"),
(catch Trigger()),
{ok, State};
+handle_exec({trigger, Desc, Trigger}, State) when is_function(Trigger) ->
+ p("trigger: ~s", [Desc]),
+ (catch Trigger()),
+ {ok, State};
handle_exec({sleep, To}, State) ->
p("sleep ~p", [To]),
diff --git a/lib/megaco/test/megaco_test_mgc.erl b/lib/megaco/test/megaco_test_mgc.erl
index 8a9b182368..1204dbba07 100644
--- a/lib/megaco/test/megaco_test_mgc.erl
+++ b/lib/megaco/test/megaco_test_mgc.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -411,7 +411,7 @@ loop(S) ->
server_reply(Parent, update_conn_info_ack, Res),
loop(evs(S, {uci, {Tag, Val}}));
- {{conn_info, Tag}, Parent} when S#mgc.parent == Parent ->
+ {{conn_info, Tag}, Parent} when S#mgc.parent =:= Parent ->
i("loop -> got conn_info request for ~w", [Tag]),
Conns = megaco:user_info(S#mgc.mid, connections),
Fun = fun(CH) ->
@@ -450,48 +450,63 @@ loop(S) ->
%% Give me statistics
{{statistics, 1}, Parent} when S#mgc.parent == Parent ->
- i("loop -> got request for statistics 1"),
+ i("loop(stats1) -> got request for statistics 1"),
{ok, Gen} = megaco:get_stats(),
- GetTrans =
+ i("loop(stats1) -> gen stats: "
+ "~n ~p", [Gen]),
+ GetTrans =
fun(CH) ->
+ i("loop(stats1):GetTrans -> "
+ "get stats for connection ~p", [CH]),
Reason = {statistics, CH},
Pid = megaco:conn_info(CH, control_pid),
+ i("loop(stats1):GetTrans -> control pid: ~p", [Pid]),
SendMod = megaco:conn_info(CH, send_mod),
+ i("loop(stats1):GetTrans -> "
+ "send module: ~p", [SendMod]),
SendHandle = megaco:conn_info(CH, send_handle),
+ i("loop(stats1):GetTrans -> "
+ "send handle: ~p", [SendHandle]),
{ok, Stats} =
case SendMod of
megaco_tcp -> megaco_tcp:get_stats(SendHandle);
megaco_udp -> megaco_udp:get_stats(SendHandle);
SendMod -> exit(Pid, Reason)
end,
+ i("loop(stats1):GetTrans -> stats: "
+ "~n ~p", [Stats]),
{SendHandle, Stats}
end,
- Mid = S#mgc.mid,
- Trans =
- lists:map(GetTrans, megaco:user_info(Mid, connections)),
+ Mid = S#mgc.mid,
+ Trans = lists:map(GetTrans, megaco:user_info(Mid, connections)),
Reply = {ok, [{gen, Gen}, {trans, Trans}]},
+ i("loop(stats1) -> send reply"),
server_reply(Parent, {statistics_reply, 1}, Reply),
+ i("loop(stats1) -> done"),
loop(evs(S, {stats, 1}));
{{statistics, 2}, Parent} when S#mgc.parent == Parent ->
- i("loop -> got request for statistics 2"),
+ i("loop(stats2) -> got request for statistics 2"),
{ok, Gen} = megaco:get_stats(),
#mgc{tcp_sup = TcpSup, udp_sup = UdpSup} = S,
TcpStats = get_trans_stats(TcpSup, megaco_tcp),
UdpStats = get_trans_stats(UdpSup, megaco_udp),
Reply = {ok, [{gen, Gen}, {trans, [TcpStats, UdpStats]}]},
+ i("loop(stats2) -> send reply"),
server_reply(Parent, {statistics_reply, 2}, Reply),
+ i("loop(stats2) -> done"),
loop(evs(S, {stats, 2}));
%% Megaco callback messages
{request, Request, From} ->
- d("loop -> received megaco request from ~p:"
+ d("loop(request) -> received megaco request from ~p:"
"~n ~p", [From, Request]),
{Reply, S1} = handle_megaco_request(Request, S),
- d("loop -> send request reply: ~n~p", [Reply]),
+ d("loop(request) -> send reply: ~n~p", [Reply]),
reply(From, Reply),
+ d("loop(request) -> done"),
loop(evs(S1, {req, Request}));
@@ -557,9 +572,14 @@ loop(S) ->
evs(#mgc{evs = EVS} = S, Ev) when (length(EVS) < ?EVS_MAX) ->
- S#mgc{evs = [{?FTS(), Ev}|EVS]};
+ echo_evs(S#mgc{evs = [{?FTS(), Ev}|EVS]});
evs(#mgc{evs = EVS} = S, Ev) ->
- S#mgc{evs = [{?FTS(), Ev}|lists:droplast(EVS)]}.
+ echo_evs(S#mgc{evs = [{?FTS(), Ev}|lists:droplast(EVS)]}).
+
+echo_evs(#mgc{evs = EVS} = S) ->
+ i("Events: "
+ "~n ~p", [EVS]),
+ S.
done(#mgc{evs = EVS}, Reason) ->
info_msg("Exiting with latest event(s): "
diff --git a/lib/megaco/test/megaco_test_msg_v3_lib.erl b/lib/megaco/test/megaco_test_msg_v3_lib.erl
index 5264791370..8bb821b475 100644
--- a/lib/megaco/test/megaco_test_msg_v3_lib.erl
+++ b/lib/megaco/test/megaco_test_msg_v3_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -3775,8 +3775,16 @@ is_AmmRequest_descriptors(Descs) ->
is_AmmRequest_descriptors([], _) ->
true;
-is_AmmRequest_descriptors([{Tag, _} = Desc|Descs], FoundDescs) ->
- d("is_AmmRequest_descriptors -> entry with"
+is_AmmRequest_descriptors([Desc|Descs], FoundDescs) ->
+ FoundDescs2 = is_AmmRequest_descriptor(Desc, FoundDescs),
+ is_AmmRequest_descriptors(Descs, FoundDescs2);
+is_AmmRequest_descriptors(Descs, _) ->
+ d("is_AmmRequest_descriptors -> entry with WRONG TYPE"
+ "~n Descs: ~p", [Descs]),
+ wrong_type('AmmRequest_descriptors', Descs).
+
+is_AmmRequest_descriptor({Tag, _} = Desc, FoundDescs) when is_atom(Tag) ->
+ d("is_AmmRequest_descriptor -> entry with"
"~n Tag: ~p"
"~n FoundDescs: ~p", [Tag, FoundDescs]),
case lists:member(Tag, FoundDescs) of
@@ -3785,15 +3793,13 @@ is_AmmRequest_descriptors([{Tag, _} = Desc|Descs], FoundDescs) ->
false ->
case is_AmmDescriptor(Desc) of
true ->
- is_AmmRequest_descriptors(Descs, [Tag|FoundDescs]);
+ [Tag|FoundDescs];
false ->
wrong_type('AmmRequest_descriptors', Desc)
end
end;
-is_AmmRequest_descriptors(Descs, _) ->
- d("is_AmmRequest_descriptors -> entry with WRONG TYPE"
- "~n Descs: ~p", [Descs]),
- wrong_type('AmmRequest_descriptors', Descs).
+is_AmmRequest_descriptor(Tag, FoundDescs) when is_atom(Tag) ->
+ is_AmmRequest_descriptor({Tag, []}, FoundDescs).
chk_AmmRequest(R, R) when is_record(R, 'AmmRequest') ->
@@ -3837,7 +3843,11 @@ chk_AmmRequest_descriptors([H|T1], [H|T2]) ->
wrong_type('AmmRequest_descriptors_val', H)
end;
chk_AmmRequest_descriptors([H1|T1], [H2|T2]) ->
- d("chk_AmmRequest_descriptors -> entry when not equal"),
+ d("chk_AmmRequest_descriptors -> entry when not equal: "
+ "~n H1: ~p"
+ "~n T1: ~p"
+ "~n H2: ~p"
+ "~n T2: ~p", [H1, T1, H2, T2]),
validate(fun() -> chk_AmmDescriptor(H1, H2) end,
'AmmRequest_descriptors_val'),
chk_AmmRequest_descriptors(T1, T2);
@@ -3885,6 +3895,11 @@ is_AmmDescriptor_val(statisticsDescriptor, D) ->
chk_AmmDescriptor(D, D) ->
chk_type(fun is_AmmDescriptor_tag/1, 'AmmDescriptor', D);
+%% There are two ways of spec an empty statisticsDescriptor:
+%% * statisticsDescriptor
+%% * {statisticsDescriptor, []}
+chk_AmmDescriptor(Tag, {Tag, []}) when (Tag =:= statisticsDescriptor) ->
+ ok;
chk_AmmDescriptor({Tag, Val1} = Cmd1, {Tag, Val2} = Cmd2) ->
case (is_AmmDescriptor_tag(Tag) andalso
is_AmmDescriptor_val(Tag, Val1) andalso
diff --git a/lib/megaco/test/megaco_trans_SUITE.erl b/lib/megaco/test/megaco_trans_SUITE.erl
index 1e281987b8..78ba1f0515 100644
--- a/lib/megaco/test/megaco_trans_SUITE.erl
+++ b/lib/megaco/test/megaco_trans_SUITE.erl
@@ -84,7 +84,8 @@
-define(MG, megaco_test_mg).
-define(MGC, megaco_test_mgc).
--define(MGC_START(Pid, Mid, ET, Verb), ?MGC:start(Pid, Mid, ET, Verb)).
+-define(MGC_START(Pid, Mid, ET, Verb),
+ mgc_start(Pid, Mid, ET, Verb)).
-define(MGC_STOP(Pid), ?MGC:stop(Pid)).
-define(MGC_GET_STATS(Pid, No), ?MGC:get_stats(Pid, No)).
-define(MGC_RESET_STATS(Pid), ?MGC:reset_stats(Pid)).
@@ -99,8 +100,8 @@
-define(MGC_ACK_INFO(Pid,To), ?MGC:ack_info(Pid,To)).
-define(MGC_REQ_INFO(Pid,To), ?MGC:req_info(Pid,To)).
--define(MG_START(Pid, Mid, Enc, Transp, Conf, Verb),
- ?MG:start(Pid, Mid, Enc, Transp, Conf, Verb)).
+-define(MG_START(Pid, Mid, Enc, Transp, Conf, Verb),
+ mg_start(Pid, Mid, Enc, Transp, Conf, Verb)).
-define(MG_STOP(Pid), ?MG:stop(Pid)).
-define(MG_GET_STATS(Pid), ?MG:get_stats(Pid)).
-define(MG_RESET_STATS(Pid), ?MG:reset_stats(Pid)).
@@ -330,14 +331,13 @@ do_single_ack([MgcNode, MgNode]) ->
%% Start the MGC and MGs
i("[MGC] start"),
ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}],
- {ok, Mgc} =
- ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
+ Mgc = ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
i("[MG] start"),
%% MgConf0 = [{MgNode, "mg", text, tcp, ?MG_VERBOSITY}],
MgMid = {deviceName, "mg"},
MgConfig = [{auto_ack, true}, {trans_timer, 5000}, {trans_ack, true}],
- {ok, Mg} = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
+ Mg = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
d("MG user info: ~p", [?MG_USER_INFO(Mg, all)]),
@@ -412,8 +412,7 @@ do_multi_ack_timeout([MgcNode, MgNode]) ->
%% Start the MGC and MGs
i("[MGC] start"),
ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}],
- {ok, Mgc} =
- ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
+ Mgc = ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
i("[MG] start"),
%% MgConf0 = [{MgNode, "mg", text, tcp, ?MG_VERBOSITY}],
@@ -422,7 +421,7 @@ do_multi_ack_timeout([MgcNode, MgNode]) ->
{trans_ack, true},
{trans_timer, 10000},
{trans_ack_maxcount, MaxCount + 10}],
- {ok, Mg} = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
+ Mg = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
d("MG user info: ~p", [?MG_USER_INFO(Mg, all)]),
@@ -496,8 +495,7 @@ do_multi_ack_maxcount([MgcNode, MgNode]) ->
%% Start the MGC and MGs
i("[MGC] start"),
ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}],
- {ok, Mgc} =
- ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
+ Mgc = ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY),
i("[MG] start"),
%% MgConf0 = [{MgNode, "mg", text, tcp, ?MG_VERBOSITY}],
@@ -506,7 +504,7 @@ do_multi_ack_maxcount([MgcNode, MgNode]) ->
%% {trans_timer, 120000},
%% {trans_ack_maxcount, MaxCount}
],
- {ok, Mg} = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
+ Mg = ?MG_START(MgNode, MgMid, text, tcp, MgConfig, ?MG_VERBOSITY),
d("MG user info: ~p", [?MG_USER_INFO(Mg, all)]),
@@ -9595,6 +9593,46 @@ await_completion(Ids, Timeout) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+mgc_start(Pid, Mid, ET, Verb) ->
+ try ?MGC:start(Pid, Mid, ET, Verb) of
+ {ok, MGC} ->
+ MGC;
+ {error, StartReason} ->
+ e("failed starting mgc (error): "
+ "~n ~p", [StartReason]),
+ ?SKIP({failed_starting, mgc, StartReason})
+ catch
+ exit:{error, timeout} ->
+ e("failed starting mgc (exit): timeout"),
+ ?SKIP({failed_starting, mgc, timeout});
+ exit:{failed_starting, _, StartExitReason} ->
+ e("failed starting mgc (exit): "
+ "~n ~p", [StartExitReason]),
+ ?SKIP({failed_starting, mgc, StartExitReason})
+ end.
+
+
+mg_start(Pid, Mid, Enc, Transp, Conf, Verb) ->
+ try ?MG:start(Pid, Mid, Enc, Transp, Conf, Verb) of
+ {ok, MG} ->
+ MG;
+ {error, Reason} ->
+ e("failed starting mg (error): "
+ "~n ~p", [Reason]),
+ ?SKIP({failed_starting, mgc, Reason})
+ catch
+ exit:{error, timeout} ->
+ e("failed starting mg (exit): timeout"),
+ ?SKIP({failed_starting, mg, timeout});
+ exit:{failed_starting, _, ExitReason} ->
+ e("failed starting mg (exit): "
+ "~n ~p", [ExitReason]),
+ ?SKIP({failed_starting, mg, ExitReason})
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
try_tc(TCName, Pre, Case, Post) ->
try_tc(TCName, "TEST", ?TEST_VERBOSITY, Pre, Case, Post).
@@ -9617,8 +9655,8 @@ p(F, A) ->
"~n " ++ F ++ "~n",
[?FTS(), self() | A]).
-%% e(F) ->
-%% e(F, []).
+e(F) ->
+ e(F, []).
e(F, A) ->
print(error, "ERROR", F, A).
diff --git a/lib/megaco/test/megaco_udp_SUITE.erl b/lib/megaco/test/megaco_udp_SUITE.erl
index 05910e50a9..b27bcbe83f 100644
--- a/lib/megaco/test/megaco_udp_SUITE.erl
+++ b/lib/megaco/test/megaco_udp_SUITE.erl
@@ -27,6 +27,7 @@
%%----------------------------------------------------------------------
%% Include files
%%----------------------------------------------------------------------
+-include_lib("common_test/include/ct.hrl").
-include_lib("megaco/src/udp/megaco_udp.hrl").
-include("megaco_test_lib.hrl").
@@ -239,6 +240,8 @@ start_and_stop(doc) ->
["This test case sets up a connection and then cloises it. "
"No data is sent. "];
start_and_stop(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(Factor * ?SECS(45)),
Pre = fun() ->
p("create nodes"),
ServerNode = make_node_name(server),
@@ -247,20 +250,22 @@ start_and_stop(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes),
Nodes
end,
- Case = fun do_start_and_stop/1,
+ Case = fun(X) -> do_start_and_stop(Factor, X) end,
Post = fun(Nodes) ->
p("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(start_and_stop, Pre, Case, Post).
-do_start_and_stop([ServerNode, ClientNode]) ->
+do_start_and_stop(Factor, [ServerNode, ClientNode]) ->
%% Create command sequences
+ TOCalc = fun(BaseTO) -> to_calc(Factor, BaseTO) end,
+ TO = TOCalc(?SECS(5)),
p("create command sequences"),
ServerPort = 2944,
ServerCmds = start_and_stop_server_commands(ServerPort),
{ok, ServerHost} = inet:gethostname(),
- ClientCmds = start_and_stop_client_commands(ServerPort, ServerHost),
+ ClientCmds = start_and_stop_client_commands(TO, ServerPort, ServerHost),
%% Start the test procs used in the test-case, one for each node
p("start command handlers"),
@@ -268,8 +273,8 @@ do_start_and_stop([ServerNode, ClientNode]) ->
p("server command handler started: ~p", [Server]),
Client = client_start_command_handler(ClientNode, ClientCmds),
p("client command handler started: ~p", [Client]),
-
- ok =
+
+ ok =
receive
{operational, Server} ->
p("received listening message from server [~p] => "
@@ -280,11 +285,11 @@ do_start_and_stop([ServerNode, ClientNode]) ->
?SKIP(Reason);
{'EXIT', Client, {skip, Reason}} ->
?SKIP(Reason)
- after 5000 ->
+ after TO ->
{error, server_timeout}
end,
- ok = await_command_handler_completion([Server, Client], ?SECS(20)),
+ ok = await_command_handler_completion([Server, Client], TOCalc(?SECS(20))),
p("done"),
ok.
@@ -337,7 +342,7 @@ start_and_stop_server_commands(Port) ->
].
-start_and_stop_client_commands(ServerPort, _ServerHost) ->
+start_and_stop_client_commands(TO, ServerPort, _ServerHost) ->
Opts = [{port, ServerPort}],
Self = self(),
[
@@ -362,7 +367,7 @@ start_and_stop_client_commands(ServerPort, _ServerHost) ->
#{id => 4,
desc => "Await continue",
cmd => fun(State) ->
- client_await_continue_signal(State, 5000)
+ client_await_continue_signal(State, TO)
end},
#{id => 5,
@@ -399,6 +404,8 @@ sendreceive(suite) ->
sendreceive(doc) ->
["Test send and receive with the UDP transport. "];
sendreceive(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(Factor * ?SECS(30)),
Pre = fun() ->
p("create nodes"),
ServerNode = make_node_name(server),
@@ -407,20 +414,22 @@ sendreceive(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes),
Nodes
end,
- Case = fun do_sendreceive/1,
+ Case = fun(X) -> do_sendreceive(Factor, X) end,
Post = fun(Nodes) ->
p("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(sendreceive, Pre, Case, Post).
-do_sendreceive([ServerNode, ClientNode]) ->
+do_sendreceive(Factor, [ServerNode, ClientNode]) ->
%% Create command sequences
p("create command sequences"),
+ TOCalc = fun(BaseTO) -> to_calc(Factor, BaseTO) end,
+ TO = TOCalc(?SECS(5)),
ServerPort = 2944,
- ServerCmds = sendreceive_server_commands(ServerPort),
+ ServerCmds = sendreceive_server_commands(TO, ServerPort),
{ok, ServerHost} = inet:gethostname(),
- ClientCmds = sendreceive_client_commands(ServerPort, ServerHost),
+ ClientCmds = sendreceive_client_commands(TO, ServerPort, ServerHost),
%% Start the test procs used in the test-case, one for each node
p("start command handlers"),
@@ -440,16 +449,16 @@ do_sendreceive([ServerNode, ClientNode]) ->
?SKIP(Reason);
{'EXIT', Client, {skip, Reason}} ->
?SKIP(Reason)
- after 5000 ->
+ after TO ->
{error, server_timeout}
end,
- ok = await_command_handler_completion([Server, Client], ?SECS(20)),
+ ok = await_command_handler_completion([Server, Client], TOCalc(?SECS(20))),
p("done"),
ok.
-sendreceive_server_commands(Port) ->
+sendreceive_server_commands(TO, Port) ->
Opts = [{port, Port}],
Self = self(),
[
@@ -480,7 +489,7 @@ sendreceive_server_commands(Port) ->
#{id => 5,
desc => "Await initial message (ping)",
cmd => fun(State) ->
- server_await_initial_message(State, "ping", 5000)
+ server_await_initial_message(State, "ping", TO)
end},
#{id => 6,
@@ -492,7 +501,7 @@ sendreceive_server_commands(Port) ->
#{id => 7,
desc => "Await nothing before sending a message (hejsan)",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 8,
@@ -504,13 +513,13 @@ sendreceive_server_commands(Port) ->
#{id => 9,
desc => "Await reply (hoppsan) to message",
cmd => fun(State) ->
- server_await_message(State, "hoppsan", 1000)
+ server_await_message(State, "hoppsan", TO div 5)
end},
#{id => 10,
desc => "Await nothing before closing",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 11,
@@ -522,7 +531,7 @@ sendreceive_server_commands(Port) ->
#{id => 12,
desc => "Await nothing before stopping transport",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 13,
@@ -532,7 +541,7 @@ sendreceive_server_commands(Port) ->
end}
].
-sendreceive_client_commands(ServerPort, ServerHost) ->
+sendreceive_client_commands(TO, ServerPort, ServerHost) ->
OwnPort = ServerPort+1,
Opts = [{port, OwnPort}],
Self = self(),
@@ -558,7 +567,7 @@ sendreceive_client_commands(ServerPort, ServerHost) ->
#{id => 4,
desc => "Await continue",
cmd => fun(State) ->
- client_await_continue_signal(State, 5000)
+ client_await_continue_signal(State, TO)
end},
#{id => 5,
@@ -576,13 +585,13 @@ sendreceive_client_commands(ServerPort, ServerHost) ->
#{id => 7,
desc => "Await reply (pong) to initial message",
cmd => fun(State) ->
- client_await_message(State, "pong", 1000)
+ client_await_message(State, "pong", TO div 5)
end},
#{id => 8,
desc => "Await message (hejsan)",
cmd => fun(State) ->
- client_await_message(State, "hejsan", 5000)
+ client_await_message(State, "hejsan", TO)
end},
#{id => 9,
@@ -594,7 +603,7 @@ sendreceive_client_commands(ServerPort, ServerHost) ->
#{id => 10,
desc => "Await nothing before closing",
cmd => fun(State) ->
- client_await_nothing(State, 1000)
+ client_await_nothing(State, TO div 5)
end},
#{id => 11,
@@ -606,7 +615,7 @@ sendreceive_client_commands(ServerPort, ServerHost) ->
#{id => 12,
desc => "Await nothing before stopping transport",
cmd => fun(State) ->
- client_await_nothing(State, 1000)
+ client_await_nothing(State, TO div 5)
end},
#{id => 13,
@@ -624,6 +633,8 @@ block_unblock(suite) ->
block_unblock(doc) ->
["Test the block/unblock functions of the UDP transport. "];
block_unblock(Config) when is_list(Config) ->
+ Factor = ?config(megaco_factor, Config),
+ ct:timetrap(Factor * ?MINS(1)),
Pre = fun() ->
p("create nodes"),
ServerNode = make_node_name(server),
@@ -632,20 +643,22 @@ block_unblock(Config) when is_list(Config) ->
ok = ?START_NODES(Nodes),
Nodes
end,
- Case = fun do_block_unblock/1,
+ Case = fun(X) -> do_block_unblock(Factor, X) end,
Post = fun(Nodes) ->
p("stop nodes"),
?STOP_NODES(lists:reverse(Nodes))
end,
try_tc(block_unblock, Pre, Case, Post).
-do_block_unblock([ServerNode, ClientNode]) ->
+do_block_unblock(Factor, [ServerNode, ClientNode]) ->
%% Create command sequences
p("create command sequences"),
+ TOCalc = fun(BaseTO) -> to_calc(Factor, BaseTO) end,
+ TO = TOCalc(?SECS(5)),
ServerPort = 2944,
- ServerCmds = block_unblock_server_commands(ServerPort),
+ ServerCmds = block_unblock_server_commands(TO, ServerPort),
{ok, ServerHost} = inet:gethostname(),
- ClientCmds = block_unblock_client_commands(ServerPort, ServerHost),
+ ClientCmds = block_unblock_client_commands(TO, ServerPort, ServerHost),
%% Start the test procs used in the test-case, one for each node
p("start command handlers"),
@@ -667,7 +680,7 @@ do_block_unblock([ServerNode, ClientNode]) ->
?SKIP(Reason1);
{'EXIT', Client, {skip, Reason2}} ->
?SKIP(Reason2)
- after 5000 ->
+ after TO ->
{error, server_timeout}
end,
@@ -684,16 +697,16 @@ do_block_unblock([ServerNode, ClientNode]) ->
?SKIP(Reason3);
{'EXIT', Client, {skip, Reason4}} ->
?SKIP(Reason4)
- after 5000 ->
+ after TO ->
{error, timeout}
end,
- ok = await_command_handler_completion([Server, Client], ?SECS(20)),
+ ok = await_command_handler_completion([Server, Client], TOCalc(?SECS(20))),
p("done"),
ok.
-block_unblock_server_commands(Port) ->
+block_unblock_server_commands(TO, Port) ->
Opts = [{port, Port}],
Self = self(),
[
@@ -724,7 +737,7 @@ block_unblock_server_commands(Port) ->
#{id => 5,
desc => "Await initial message (ping)",
cmd => fun(State) ->
- server_await_initial_message(State, "ping", 5000)
+ server_await_initial_message(State, "ping", TO)
end},
#{id => 6,
@@ -736,7 +749,7 @@ block_unblock_server_commands(Port) ->
#{id => 7,
desc => "Await continue",
cmd => fun(State) ->
- server_await_continue_signal(State, 5000)
+ server_await_continue_signal(State, TO)
end},
#{id => 8,
@@ -748,19 +761,19 @@ block_unblock_server_commands(Port) ->
#{id => 9,
desc => "Await nothing before receiving (hoppsan) reply",
cmd => fun(State) ->
- server_await_nothing(State, 4000)
+ server_await_nothing(State, TO)
end},
#{id => 10,
desc => "Await reply (hoppsan) to message",
cmd => fun(State) ->
- server_await_message(State, "hoppsan", 2000)
+ server_await_message(State, "hoppsan", TO div 2)
end},
#{id => 11,
desc => "Await nothing before closing",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 12,
@@ -772,7 +785,7 @@ block_unblock_server_commands(Port) ->
#{id => 13,
desc => "Await nothing before stopping transport",
cmd => fun(State) ->
- server_await_nothing(State, 1000)
+ server_await_nothing(State, TO div 5)
end},
#{id => 14,
@@ -783,7 +796,7 @@ block_unblock_server_commands(Port) ->
].
-block_unblock_client_commands(ServerPort, ServerHost) ->
+block_unblock_client_commands(TO, ServerPort, ServerHost) ->
OwnPort = ServerPort+1,
Opts = [{port, OwnPort}],
Self = self(),
@@ -809,7 +822,7 @@ block_unblock_client_commands(ServerPort, ServerHost) ->
#{id => 4,
desc => "Await continue",
cmd => fun(State) ->
- client_await_continue_signal(State, 5000)
+ client_await_continue_signal(State, TO)
end},
#{id => 5,
@@ -827,64 +840,138 @@ block_unblock_client_commands(ServerPort, ServerHost) ->
#{id => 7,
desc => "Await reply (pong) to initial message",
cmd => fun(State) ->
- client_await_message(State, "pong", 1000)
+ client_await_message(State, "pong", TO div 5)
end},
#{id => 8,
+ desc => "Pre-Block info",
+ cmd => fun(#{socket := Socket} = State) ->
+ p("Socket Info: "
+ "~n Port Info: ~p", [erlang:port_info(Socket)]),
+ {ok, State}
+ end},
+
+ #{id => 9,
desc => "Block",
cmd => fun(State) ->
client_block(State)
end},
- #{id => 9,
+ #{id => 10,
+ desc => "Post-Block info",
+ cmd => fun(#{socket := Socket} = State) ->
+ Active =
+ case inet:getopts(Socket, [active]) of
+ {ok, [{active, Act}]} ->
+ Act;
+ _ ->
+ undefined
+ end,
+ p("Socket Info: "
+ "~n Active: ~p"
+ "~n Port Info: ~p",
+ [Active, erlang:port_info(Socket)]),
+ {ok, State}
+ end},
+
+ #{id => 11,
desc => "Notify blocked",
cmd => fun(State) ->
client_notify_blocked(State)
end},
- #{id => 10,
+ #{id => 12,
desc => "Await nothing before unblocking",
- cmd => fun(State) ->
- client_await_nothing(State, 5000)
+ cmd => fun(#{socket := Socket} = State) ->
+ Fail =
+ fun(_) ->
+ Active =
+ case inet:getopts(Socket, [active]) of
+ {ok, [{active, Act}]} ->
+ Act;
+ _ ->
+ undefined
+ end,
+ p("Socket Info: "
+ "~n Active: ~p"
+ "~n Port Info: ~p",
+ [Active, erlang:port_info(Socket)]),
+ ok
+ end,
+ client_await_nothing(State, Fail, TO)
end},
- #{id => 11,
+ #{id => 13,
+ desc => "Pre-Unblock info",
+ cmd => fun(#{socket := Socket} = State) ->
+ Active =
+ case inet:getopts(Socket, [active]) of
+ {ok, [{active, Act}]} ->
+ Act;
+ _ ->
+ undefined
+ end,
+ p("Socket Info: "
+ "~n Active: ~p"
+ "~n Port Info: ~p",
+ [Active, erlang:port_info(Socket)]),
+ {ok, State}
+ end},
+
+ #{id => 14,
desc => "Unblock",
cmd => fun(State) ->
client_unblock(State)
end},
- #{id => 8,
+ #{id => 15,
+ desc => "Post-Unblock info",
+ cmd => fun(#{socket := Socket} = State) ->
+ Active =
+ case inet:getopts(Socket, [active]) of
+ {ok, [{active, Act}]} ->
+ Act;
+ _ ->
+ undefined
+ end,
+ p("Socket Info: "
+ "~n Active: ~p"
+ "~n Port Info: ~p",
+ [Active, erlang:port_info(Socket)]),
+ {ok, State}
+ end},
+
+ #{id => 16,
desc => "Await message (hejsan)",
cmd => fun(State) ->
- client_await_message(State, "hejsan", 5000)
+ client_await_message(State, "hejsan", TO)
end},
- #{id => 9,
+ #{id => 17,
desc => "Send reply (hoppsan) to message",
cmd => fun(State) ->
client_send_message(State, "hoppsan")
end},
- #{id => 10,
+ #{id => 18,
desc => "Await nothing before closing",
cmd => fun(State) ->
- client_await_nothing(State, 1000)
+ client_await_nothing(State, TO)
end},
- #{id => 11,
+ #{id => 19,
desc => "Close",
cmd => fun(State) ->
client_close(State)
end},
- #{id => 12,
+ #{id => 20,
desc => "Await nothing before stopping transport",
cmd => fun(State) ->
- client_await_nothing(State, 1000)
+ client_await_nothing(State, TO)
end},
- #{id => 13,
+ #{id => 21,
desc => "Stop transport",
cmd => fun(State) ->
client_stop_transport(State)
@@ -974,14 +1061,17 @@ server_start_transport(State) when is_map(State) ->
server_open(#{transport_ref := Ref} = State, Options)
when is_list(Options) ->
Opts = [{receive_handle, self()}, {module, ?MODULE} | Options],
- case (catch megaco_udp:open(Ref, Opts)) of
+ try megaco_udp:open(Ref, Opts) of
{ok, Socket, ControlPid} ->
{ok, State#{handle => {socket, Socket}, % Temporary
control_pid => ControlPid}};
{error, {could_not_open_udp_port, eaddrinuse}} ->
{skip, {server, eaddrinuse}};
- Error ->
- Error
+ {error, _} = ERROR ->
+ ERROR
+ catch
+ C:E:S ->
+ {error, {catched, C, E, S}}
end.
server_notify_operational(#{parent := Parent} = State) ->
@@ -1080,14 +1170,18 @@ client_start_transport(State) when is_map(State) ->
client_open(#{transport_ref := Ref} = State, Options)
when is_list(Options) ->
Opts = [{receive_handle, self()}, {module, ?MODULE} | Options],
- case (catch megaco_udp:open(Ref, Opts)) of
+ try megaco_udp:open(Ref, Opts) of
{ok, Socket, ControlPid} ->
- {ok, State#{handle => {socket, Socket},
+ {ok, State#{handle => {socket, Socket},
+ socket => Socket,
control_pid => ControlPid}};
{error, {could_not_open_udp_port, eaddrinuse}} ->
{skip, {client, eaddrinuse}};
- Error ->
- Error
+ {error, _} = ERROR ->
+ ERROR
+ catch
+ C:E:S ->
+ {error, {catched, C, E, S}}
end.
client_await_continue_signal(#{parent := Parent} = State, Timeout) ->
@@ -1102,11 +1196,15 @@ client_notify_blocked(#{parent := Parent} = State) ->
Parent ! {blocked, self()},
{ok, State}.
-client_await_nothing(State, Timeout)
- when is_map(State) ->
+client_await_nothing(State, Timeout) ->
+ client_await_nothing(State, fun(_) -> ok end, Timeout).
+
+client_await_nothing(State, Fail, Timeout)
+ when is_map(State) andalso is_function(Fail, 1) ->
receive
Any ->
p("received unexpected event: ~p", [Any]),
+ (catch Fail(Any)),
{error, {unexpected_event, Any}}
after Timeout ->
{ok, State}
@@ -1196,6 +1294,13 @@ make_node_name(Name) ->
end.
+to_calc(1 = _Factor, BaseTO) when is_integer(BaseTO) andalso (BaseTO > 0) ->
+ BaseTO;
+to_calc(Factor, BaseTO) when is_integer(Factor) andalso (Factor > 0) andalso
+ is_integer(BaseTO) andalso (BaseTO > 0) ->
+ trunc( ((Factor + 1) / 2) * BaseTO ).
+
+
p(F) ->
p(F, []).
diff --git a/lib/megaco/test/modules.mk b/lib/megaco/test/modules.mk
index 55b3003d6d..b45bd4b13c 100644
--- a/lib/megaco/test/modules.mk
+++ b/lib/megaco/test/modules.mk
@@ -25,6 +25,15 @@ COVER_SPEC_FILE = megaco.cover
BEHAVIOUR_MODULES = \
megaco_test_generator
+ifeq ($(INCLUDE_PREV3_MODULES),true)
+TEST_UTIL_PREV3_MODULES = \
+ megaco_test_msg_prev3a_lib \
+ megaco_test_msg_prev3b_lib \
+ megaco_test_msg_prev3c_lib
+else
+TEST_UTIL_PREV3_MODULES =
+endif
+
TEST_UTIL_MODULES = \
$(BEHAVIOUR_MODULES) \
megaco_codec_test_lib \
@@ -45,12 +54,20 @@ TEST_UTIL_MODULES = \
megaco_test_mg \
megaco_test_msg_v1_lib \
megaco_test_msg_v2_lib \
- megaco_test_msg_prev3a_lib \
- megaco_test_msg_prev3b_lib \
- megaco_test_msg_prev3c_lib \
+ $(TEST_UTIL_PREV3_MODULES) \
megaco_test_msg_v3_lib \
megaco_test_lib
+
+ifeq ($(INCLUDE_PREV3_MODULES),true)
+SUITE_PREV3_MODULES = \
+ megaco_codec_prev3a_SUITE \
+ megaco_codec_prev3b_SUITE \
+ megaco_codec_prev3c_SUITE
+else
+SUITE_PREV3_MODULES =
+endif
+
SUITE_MODULES = \
megaco_actions_SUITE \
megaco_app_SUITE \
@@ -59,9 +76,7 @@ SUITE_MODULES = \
megaco_codec_mini_SUITE \
megaco_codec_v1_SUITE \
megaco_codec_v2_SUITE \
- megaco_codec_prev3a_SUITE \
- megaco_codec_prev3b_SUITE \
- megaco_codec_prev3c_SUITE \
+ $(SUITE_PREV3_MODULES) \
megaco_codec_v3_SUITE \
megaco_config_SUITE \
megaco_digit_map_SUITE \
diff --git a/lib/megaco/vsn.mk b/lib/megaco/vsn.mk
index e9c21389bc..f416a0324a 100644
--- a/lib/megaco/vsn.mk
+++ b/lib/megaco/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = megaco
-MEGACO_VSN = 3.18.8
+MEGACO_VSN = 3.19.5
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(MEGACO_VSN)$(PRE_VSN)"
diff --git a/lib/mnesia/Makefile b/lib/mnesia/Makefile
index 810433c4d0..d0edd48af9 100644
--- a/lib/mnesia/Makefile
+++ b/lib/mnesia/Makefile
@@ -38,3 +38,4 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/mnesia/doc/misc/Makefile b/lib/mnesia/doc/misc/Makefile
index 29e2682967..0622a0b809 100644
--- a/lib/mnesia/doc/misc/Makefile
+++ b/lib/mnesia/doc/misc/Makefile
@@ -39,7 +39,7 @@ TEX_FILES= $(SGML_FILES:.sgml=.tex)
DVI_FILES= $(SGML_FILES:.sgml=.dvi)
PSFIG_FILES= $(FIG_FILES:.fig=.ps)
PS_FILES= $(SGML_FILES:.sgml=.ps)
-GIF_FILES= min_head.gif
+GIF_FILES=
ERL_FILES=
HRL_FILES=
DATA_FILES=
diff --git a/lib/mnesia/doc/specs/.gitignore b/lib/mnesia/doc/specs/.gitignore
new file mode 100644
index 0000000000..322eebcb06
--- /dev/null
+++ b/lib/mnesia/doc/specs/.gitignore
@@ -0,0 +1 @@
+specs_*.xml
diff --git a/lib/mnesia/doc/src/Makefile b/lib/mnesia/doc/src/Makefile
index d9647fc081..486993e76d 100644
--- a/lib/mnesia/doc/src/Makefile
+++ b/lib/mnesia/doc/src/Makefile
@@ -30,11 +30,6 @@ VSN=$(MNESIA_VSN)
APPLICATION=mnesia
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -70,81 +65,10 @@ XML_FILES = \
XML_GEN_FILES = $(XML_CHAPTER_GEN_FILES:%=$(XMLDIR)/%)
-GIF_FILES = \
+IMAGE_FILES = \
company.gif
-XML_HTML_FILES = \
- notes_history.xml
-
-
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_HTML_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-EXTRA_FILES = \
- $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-$(INDEX_TARGET): $(INDEX_SRC) ../../vsn.mk
- sed -e 's;%VSN%;$(VSN);' $< > $@
-
-debug opt:
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3_FILES) "$(RELEASE_PATH)/man/man3"
+SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+TOP_SPECS_FILE = specs.xml
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/mnesia/doc/src/Mnesia_chap1.xml b/lib/mnesia/doc/src/Mnesia_chap1.xml
index 035e934ed2..6e66132a52 100644
--- a/lib/mnesia/doc/src/Mnesia_chap1.xml
+++ b/lib/mnesia/doc/src/Mnesia_chap1.xml
@@ -32,72 +32,72 @@
<rev>C</rev>
<file>Mnesia_chap1.xml</file>
</header>
- <p>The Mnesia application provides a heavy duty real-time
+ <p>The Mnesia application provides a heavy-duty real-time
distributed database.</p>
<section>
<title>Scope</title>
<p>This User's Guide describes how to
- build Mnesia database applications, and how to integrate
+ build Mnesia-backed applications, and how to integrate
and use the Mnesia database management system with
OTP. Programming constructs are described, and numerous
programming examples are included to illustrate the use of
Mnesia.</p>
<p>This User's Guide is organized as follows:</p>
<list type="bulleted">
- <item><seealso marker="Mnesia_overview">Mnesia</seealso>
+ <item><seeguide marker="Mnesia_overview">Mnesia</seeguide>
provides an introduction to
Mnesia.
</item>
- <item><seealso marker="Mnesia_chap2">Getting Started</seealso>
+ <item><seeguide marker="Mnesia_chap2">Getting Started</seeguide>
introduces Mnesia with an example database. Examples
- are included how to start an Erlang session, specify a
+ are included on how to start an Erlang session, specify a
Mnesia database directory, initialize a database
schema, start Mnesia, and create tables. Initial
prototyping of record definitions is also discussed.
</item>
- <item><seealso marker="Mnesia_chap3">Build a Mnesia
- Database</seealso> more formally describes the steps
+ <item><seeguide marker="Mnesia_chap3">Build a Mnesia
+ Database</seeguide> more formally describes the steps
introduced in the previous section, namely the Mnesia
functions that define a database schema, start Mnesia,
and create the required tables.
</item>
- <item><seealso marker="Mnesia_chap4">Transactions and Other Access Contexts</seealso>
+ <item><seeguide marker="Mnesia_chap4">Transactions and Other Access Contexts</seeguide>
describes the transactions properties that make Mnesia into
- a fault tolerant, real-time distributed database management
+ a fault-tolerant, real-time distributed database management
system. This section also describes the concept of locking
to ensure consistency in tables, and "dirty
- operations", or short cuts, which bypass the transaction system
+ operations", or shortcuts, which bypass the transaction system
to improve speed and reduce overheads.
</item>
- <item><seealso marker="Mnesia_chap5">Miscellaneous Mnesia
- Features</seealso> describes features that enable the
+ <item><seeguide marker="Mnesia_chap5">Miscellaneous Mnesia
+ Features</seeguide> describes features that enable the
construction of more complex database applications. These
features include indexing, checkpoints, distribution and fault
- tolerance, disc-less nodes, replication manipulation, local
+ tolerance, disc-less nodes, replica manipulation, local
content tables, concurrency, and object-based programming in
Mnesia.
</item>
- <item><seealso marker="Mnesia_chap7">Mnesia System
- Information</seealso> describes the files contained in the
+ <item><seeguide marker="Mnesia_chap7">Mnesia System
+ Information</seeguide> describes the files contained in the
Mnesia database directory, database configuration data,
- core and table dumps, as well as the important subject of
- backup, fall-back, and disaster recovery principles.
+ core and table dumps, as well as the functions used for
+ backup, restore, fallback, and disaster recovery.
</item>
- <item><seealso marker="Mnesia_chap8">Combine Mnesia with
- SNMP</seealso> is a short section that outlines Mnesia
- integrated with SNMP.
+ <item><seeguide marker="Mnesia_chap8">Combine Mnesia with
+ SNMP</seeguide> is a short section that outlines
+ the integration between Mnesia and SNMP.
</item>
- <item><seealso marker="Mnesia_App_A">Appendix A: Backup
- Callback Interface</seealso> is a program listing of the
+ <item><seeguide marker="Mnesia_App_A">Appendix A: Backup
+ Callback Interface</seeguide> is a program listing of the
default implementation of this facility.
</item>
- <item><seealso marker="Mnesia_App_B">Appendix B: Activity
- Access Callback Interface</seealso> is a program outlining
+ <item><seeguide marker="Mnesia_App_B">Appendix B: Activity
+ Access Callback Interface</seeguide> is a program outlining
one possible implementation of this facility.
</item>
- <item><seealso marker="Mnesia_App_C">Appendix C: Fragmented
- Table Hashing Callback Interface</seealso> is a program
+ <item><seeguide marker="Mnesia_App_C">Appendix C: Fragmented
+ Table Hashing Callback Interface</seeguide> is a program
outlining one possible implementation of this facility.
</item>
</list>
@@ -110,4 +110,3 @@
database management systems.</p>
</section>
</chapter>
-
diff --git a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
index 8135e14301..3c41945285 100644
--- a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc
@@ -102,18 +102,18 @@
</item>
<item><em>Step 2:</em> A new empty schema is initialized on the local
node by evaluating
- <seealso marker="mnesia#create_schema/1">mnesia:create_schema([node()])</seealso>.
+ <seemfa marker="mnesia#create_schema/1">mnesia:create_schema([node()])</seemfa>.
The schema contains information about the database in general.
This is explained in detail later.
</item>
<item><em>Step 3:</em> The DBMS is started by evaluating
- <seealso marker="mnesia#start/0">mnesia:start()</seealso>.
+ <seemfa marker="mnesia#start/0">mnesia:start()</seemfa>.
</item>
<item><em>Step 4:</em> A first table is created, called <c>funky</c>,
by evaluating the expression <c>mnesia:create_table(funky, [])</c>.
The table is given default properties.
</item>
- <item><em>Step 5:</em> <seealso marker="mnesia#info/0">mnesia:info()</seealso>
+ <item><em>Step 5:</em> <seemfa marker="mnesia#info/0">mnesia:info()</seemfa>
is evaluated to
display information on the terminal about the status of the database.
</item>
@@ -163,11 +163,11 @@
<codeinclude file="company.hrl" tag="%0" type="erl"></codeinclude>
<p>The structure defines six tables in the database. In <c>Mnesia</c>,
the function
- <seealso marker="mnesia#create_table/2">mnesia:create_table(Name, ArgList)</seealso>
+ <seemfa marker="mnesia#create_table/2">mnesia:create_table(Name, ArgList)</seemfa>
creates tables. <c>Name</c> is the table name.</p>
<note><p>The current version of <c>Mnesia</c> does not require that
the name of the table is the same as the record name, see
- <seealso marker="Mnesia_chap4#recordnames_tablenames">Record Names versus Table Names.</seealso>.</p></note>
+ <seeguide marker="Mnesia_chap4#recordnames_tablenames">Record Names versus Table Names.</seeguide>.</p></note>
<p>For example, the table for employees is created with the
function <c>mnesia:create_table(employee,
[{attributes, record_info(fields, employee)}])</c>. The table
@@ -209,16 +209,16 @@
further input with the prompt <c>1></c>.
</item>
<item>
- <seealso marker="mnesia#create_schema/1">mnesia:create_schema([node()])</seealso>.
+ <seemfa marker="mnesia#create_schema/1">mnesia:create_schema([node()])</seemfa>.
This function
has the format <c>mnesia:create_schema(DiscNodeList)</c> and
initiates a new schema. In this example, a non-distributed system
using only one node is created. Schemas are fully explained in
- <seealso marker="Mnesia_chap3#def_schema">Define a Schema</seealso>.
+ <seeguide marker="Mnesia_chap3#def_schema">Define a Schema</seeguide>.
</item>
- <item><seealso marker="mnesia#start/0">mnesia:start()</seealso>.
+ <item><seemfa marker="mnesia#start/0">mnesia:start()</seemfa>.
This function starts <c>Mnesia</c> and is fully explained in
- <seealso marker="Mnesia_chap3#start_mnesia">Start Mnesia</seealso>.
+ <seeguide marker="Mnesia_chap3#start_mnesia">Start Mnesia</seeguide>.
</item>
</list>
<p>Continuing the dialogue with the Erlang shell produces the
@@ -259,10 +259,10 @@
ok
]]></pre>
<p>A set of tables is created. The function
- <seealso marker="mnesia#create_table/2">mnesia:create_table(Name, ArgList)</seealso>
+ <seemfa marker="mnesia#create_table/2">mnesia:create_table(Name, ArgList)</seemfa>
creates the required database tables. The
options available with <c>ArgList</c> are explained in
- <seealso marker="Mnesia_chap3#create_tables">Create New Tables</seealso>.</p>
+ <seeguide marker="Mnesia_chap3#create_tables">Create New Tables</seeguide>.</p>
<p>The function <c>company:init/0</c> creates the tables. Two tables
are of type <c>bag</c>. This is the <c>manager</c> relation as well
the <c>in_proj</c> relation. This is interpreted as: an
@@ -271,7 +271,7 @@
relation is <c>set</c>, as an employee can only work in one department.
In this data model, there are examples of relations that are 1-to-1
(<c>set</c>) and 1-to-many (<c>bag</c>).</p>
- <p><seealso marker="mnesia#info/0">mnesia:info()</seealso>
+ <p><seemfa marker="mnesia#info/0">mnesia:info()</seemfa>
now indicates that a database has seven
local tables, where six are the user-defined tables and one is
the schema. Six transactions have been committed, as six successful
@@ -298,7 +298,7 @@
<p>The function <c>insert_emp/3</c> creates a Functional Object (Fun).
<c>Fun</c> is passed
as a single argument to the function
- <seealso marker="mnesia#transaction/2">mnesia:transaction(Fun)</seealso>.
+ <seemfa marker="mnesia#transaction/1">mnesia:transaction(Fun)</seemfa>.
This means that <c>Fun</c> is
run as a transaction with the following properties:</p>
<list type="bulleted">
@@ -490,12 +490,12 @@
<title>Writing Queries</title>
<p>Retrieving data from DBMS is usually to be done with the
functions
- <seealso marker="mnesia#read/3">mnesia:read/3</seealso> or
- <seealso marker="mnesia#read/2">mnesia:read/1</seealso>.
+ <seemfa marker="mnesia#read/3">mnesia:read/3</seemfa> or
+ <seemfa marker="mnesia#read/2">mnesia:read/1</seemfa>.
The following function raises the salary:</p>
<codeinclude file="company.erl" tag="%5" type="erl"></codeinclude>
<p>Since it is desired to update the record using the function
- <seealso marker="mnesia#write/1">mnesia:write/1</seealso>
+ <seemfa marker="mnesia#write/1">mnesia:write/1</seemfa>
after the salary has been increased, a write
lock (third argument to <c>read</c>) is acquired when the record from
the table is read.</p>
@@ -528,7 +528,7 @@ mnesia:select(employee, [{#employee{sex = female, name = '$1', _ = '_'},[], ['$1
(klacke@gin)1> <input>company:all_females().</input>
{atomic, ["Carlsson Tuula", "Fedoriw Anna"]}</pre>
<p>For a description of <c>select</c> and its syntax, see
- <seealso marker="Mnesia_chap4#matching">Pattern Matching</seealso>.
+ <seeguide marker="Mnesia_chap4#matching">Pattern Matching</seeguide>.
</p>
</section>
@@ -536,7 +536,7 @@ mnesia:select(employee, [{#employee{sex = female, name = '$1', _ = '_'},[], ['$1
<title>Using QLC </title>
<p>This section contains simple introductory examples only. For
a full description of the QLC query language, see the
- <seealso marker="stdlib:qlc">qlc</seealso> manual page in
+ <seeerl marker="stdlib:qlc">qlc</seeerl> manual page in
<c>STDLIB</c>.</p>
<p>Using QLC can be more expensive than using <c>Mnesia</c>
functions directly but offers a nice syntax.</p>
diff --git a/lib/mnesia/doc/src/Mnesia_chap3.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap3.xmlsrc
index ffda739dfa..5b439f9280 100644
--- a/lib/mnesia/doc/src/Mnesia_chap3.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap3.xmlsrc
@@ -75,7 +75,7 @@
<title>Schema Functions</title>
<p>The schema functions are as follows:</p>
<list type="bulleted">
- <item><seealso marker="mnesia#create_schema/1">mnesia:create_schema(NodeList)</seealso>
+ <item><seemfa marker="mnesia#create_schema/1">mnesia:create_schema(NodeList)</seemfa>
initializes a new, empty schema. This is a mandatory requirement
before <c>Mnesia</c> can be started. <c>Mnesia</c> is a truly
distributed DBMS and the schema is a system table that is
@@ -87,19 +87,19 @@
Applications call this function only once, as
it is usually a one-time activity to initialize a new database.
</item>
- <item><seealso marker="mnesia#delete_schema/1">mnesia:delete_schema(DiscNodeList)</seealso>
+ <item><seemfa marker="mnesia#delete_schema/1">mnesia:delete_schema(DiscNodeList)</seemfa>
erases any old schemas on the nodes in
<c>DiscNodeList</c>. It also removes all old tables together
with all data. This function requires <c>Mnesia</c> to be stopped
on all <c>db_nodes</c>.
</item>
- <item><seealso marker="mnesia#delete_table/1">mnesia:delete_table(Tab)</seealso>
+ <item><seemfa marker="mnesia#delete_table/1">mnesia:delete_table(Tab)</seemfa>
permanently deletes all replicas of table <c>Tab</c>.
</item>
- <item><seealso marker="mnesia#clear_table/1">mnesia:clear_table(Tab)</seealso>
+ <item><seemfa marker="mnesia#clear_table/1">mnesia:clear_table(Tab)</seemfa>
permanently deletes all entries in table <c>Tab</c>.
</item>
- <item><seealso marker="mnesia#move_table_copy/3">mnesia:move_table_copy(Tab, From, To)</seealso>
+ <item><seemfa marker="mnesia#move_table_copy/3">mnesia:move_table_copy(Tab, From, To)</seemfa>
moves the copy of table <c>Tab</c> from node
<c>From</c> to node <c>To</c>. The table storage type
<c>{type}</c> is preserved, so if a RAM table is moved from
@@ -107,7 +107,7 @@
node. Other transactions can still perform read
and write operation to the table while it is being moved.
</item>
- <item><seealso marker="mnesia#add_table_copy/3">mnesia:add_table_copy(Tab, Node, Type)</seealso>
+ <item><seemfa marker="mnesia#add_table_copy/3">mnesia:add_table_copy(Tab, Node, Type)</seemfa>
creates a replica of table <c>Tab</c> at node
<c>Node</c>. Argument <c>Type</c> must be either of the
atoms <c>ram_copies</c>, <c>disc_copies</c>, or
@@ -117,13 +117,13 @@
extends the set of nodes that comprise this particular
<c>Mnesia</c> system.
</item>
- <item><seealso marker="mnesia#del_table_copy/2">mnesia:del_table_copy(Tab, Node)</seealso>
+ <item><seemfa marker="mnesia#del_table_copy/2">mnesia:del_table_copy(Tab, Node)</seemfa>
deletes the replica of table <c>Tab</c> at node <c>Node</c>.
When the last replica of a table is removed, the table is
deleted.
</item>
<item>
- <p><seealso marker="mnesia#transform_table/4">mnesia:transform_table(Tab, Fun, NewAttributeList, NewRecordName)</seealso>
+ <p><seemfa marker="mnesia#transform_table/4">mnesia:transform_table(Tab, Fun, NewAttributeList, NewRecordName)</seemfa>
changes the format on all records in table
<c>Tab</c>. It applies argument <c>Fun</c> to all
records in the table. <c>Fun</c> must be a function that
@@ -178,7 +178,7 @@ Transformer =
combination of the table name and a key is an arity two tuple
<c>{Tab, Key}</c> called the OID. For more information about
the relationship beween the record name and the table name, see
- <seealso marker="Mnesia_chap4#recordnames_tablenames">Record Names versus Table Names</seealso>.
+ <seeguide marker="Mnesia_chap4#recordnames_tablenames">Record Names versus Table Names</seeguide>.
</p>
<p>What makes the <c>Mnesia</c> data model an extended relational model
is the ability to store arbitrary Erlang terms in the attribute
@@ -198,11 +198,11 @@ Transformer =
<item>The Erlang system must be started.</item>
<item>Nodes with disc database schema must be defined and
implemented with the function
- <seealso marker="mnesia#create_schema/1">mnesia:create_schema(NodeList)</seealso>.</item>
+ <seemfa marker="mnesia#create_schema/1">mnesia:create_schema(NodeList)</seemfa>.</item>
</list>
<p>When running a distributed system with two or more
participating nodes, the function
- <seealso marker="mnesia#start/0">mnesia:start()</seealso>
+ <seemfa marker="mnesia#start/0">mnesia:start()</seemfa>
must be executed on each participating node. This would typically
be part of the boot script in an embedded environment.
In a test environment or an interactive environment,
@@ -213,7 +213,7 @@ Transformer =
<section>
<title>Initialize a Schema and Start Mnesia</title>
<p>Let us use the example database <c>Company</c>, described in
- <seealso marker="Mnesia_chap2#getting_started">Getting Started</seealso> to
+ <seeguide marker="Mnesia_chap2#getting_started">Getting Started</seeguide> to
illustrate how to run a database on two separate nodes,
called <c>a@gin</c> and <c>b@skeppet</c>. Each of these
nodes must have a <c>Mnesia</c> directory and an
@@ -251,7 +251,7 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
(a@gin)1><input>mnesia:create_schema([a@gin, b@skeppet]).</input></pre>
</item>
<item>The function
- <seealso marker="mnesia#start/0">mnesia:start()</seealso>
+ <seemfa marker="mnesia#start/0">mnesia:start()</seemfa>
is called on both nodes.
</item>
<item><p>To initialize the database, execute the following
@@ -266,7 +266,7 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
run the <c>Company</c> database, and therefore, initialize the
database. This is required only once when setting up. The next time
the system is started,
- <seealso marker="mnesia#start/0">mnesia:start()</seealso>
+ <seemfa marker="mnesia#start/0">mnesia:start()</seemfa>
is called
on both nodes, to initialize the system from disc.</p>
<p>In a system of <c>Mnesia</c> nodes, every node is aware of the
@@ -275,7 +275,7 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
data in the tables can be executed on either of the two nodes.
Code that manipulate <c>Mnesia</c> data behaves identically
regardless of where the data resides.</p>
- <p>The function <seealso marker="mnesia#stop/0">mnesia:stop()</seealso>
+ <p>The function <seemfa marker="mnesia#stop/0">mnesia:stop()</seemfa>
stops <c>Mnesia</c> on the node
where the function is executed. The functions <c>mnesia:start/0</c>
and <c>mnesia:stop/0</c> work on the "local" <c>Mnesia</c> system.
@@ -304,14 +304,14 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
nodes as soon as they are loaded.</item>
</list>
<p>Table initialization is asynchronous. The function
- call <seealso marker="mnesia#start/0">mnesia:start()</seealso>
+ call <seemfa marker="mnesia#start/0">mnesia:start()</seemfa>
returns the atom <c>ok</c> and
then starts to initialize the different tables. Depending on
the size of the database, this can take some time, and the
application programmer must wait for the tables that the
application needs before they can be used. This is achieved by
using the function
- <seealso marker="mnesia#wait_for_tables/2">mnesia:wait_for_tables(TabList, Timeout)</seealso>,
+ <seemfa marker="mnesia#wait_for_tables/2">mnesia:wait_for_tables(TabList, Timeout)</seemfa>,
which suspends the caller until all tables
specified in <c>TabList</c> are properly initiated.</p>
<p>A problem can arise if a replicated table on one node is
@@ -319,12 +319,12 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
replica is more recent than the replica existing on the
local node, and the initialization procedure does not proceed.
In this situation, a call to
- <seealso marker="mnesia#wait_for_tables/2">mnesia:wait_for_tables/2</seealso>,
+ <seemfa marker="mnesia#wait_for_tables/2">mnesia:wait_for_tables/2</seemfa>,
suspends the caller until the
remote node has initialized the table from its local disc and
the node has copied the table over the network to the local node.</p>
<p>However, this procedure can be time-consuming, the shortcut function
- <seealso marker="mnesia#force_load_table/1">mnesia:force_load_table(Tab)</seealso>
+ <seemfa marker="mnesia#force_load_table/1">mnesia:force_load_table(Tab)</seemfa>
loads all the tables from disc at a faster rate. The function forces
tables to be loaded from disc regardless of the network
situation.</p>
@@ -346,7 +346,7 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
inconsistent.</p>
</warning>
<p>If the startup procedure fails, the function
- <seealso marker="mnesia#start/0">mnesia:start()</seealso>
+ <seemfa marker="mnesia#start/0">mnesia:start()</seemfa>
returns the cryptic tuple
<c>{error,{shutdown, {mnesia_sup,start_link,[normal,[]]}}}</c>.
To get more information about the start failure, use
@@ -359,7 +359,7 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
<marker id="create_tables"></marker>
<title>Create Tables</title>
<p>The function
- <seealso marker="mnesia#create_table/2">mnesia:create_table(Name, ArgList)</seealso>
+ <seemfa marker="mnesia#create_table/2">mnesia:create_table(Name, ArgList)</seemfa>
creates tables. When executing this function, it returns one of
the following responses:</p>
<list type="bulleted">
@@ -436,7 +436,7 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
to create a table, it is located on the local node only.</p>
<p>Table replicas of type
<c>ram_copies</c> can be dumped to disc with the function
- <seealso marker="mnesia#dump_tables/1">mnesia:dump_tables(TabList)</seealso>.</p>
+ <seemfa marker="mnesia#dump_tables/1">mnesia:dump_tables(TabList)</seemfa>.</p>
</item>
<item><c>{disc_only_copies, NodeList}</c>. These table
replicas are stored on disc only and are therefore slower to
@@ -454,9 +454,9 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
</item>
<item><p><c>{snmp, SnmpStruct}</c>. <c>SnmpStruct</c> is
described in the
- <seealso marker="snmp:index">SNMP</seealso> User's Guide.
+ <seeapp marker="snmp:index">SNMP</seeapp> User's Guide.
Basically, if this attribute is present in <c>ArgList</c> of
- <seealso marker="mnesia#create_table/2">mnesia:create_table/2</seealso>,
+ <seemfa marker="mnesia#create_table/2">mnesia:create_table/2</seemfa>,
the table is immediately accessible the SNMP.</p>
<p>It is easy to design applications that use SNMP to
manipulate and control the system. <c>Mnesia</c> provides a
@@ -498,7 +498,7 @@ skeppet %<input>erl -sname b -mnesia dir '"/ldisc/scratch/Mnesia.company"'</inpu
the table must have this name as their first element.
<c>record_name</c> defaults to the name of the table.
For more information, see
- <seealso marker="Mnesia_chap4#recordnames_tablenames">Record Names versus Table Names</seealso>.</p>
+ <seeguide marker="Mnesia_chap4#recordnames_tablenames">Record Names versus Table Names</seeguide>.</p>
</item>
</list>
</item>
diff --git a/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc
index b8d86adbf1..335f09dd30 100644
--- a/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc
@@ -103,7 +103,7 @@
transaction. The function <c>raise/2</c>, shown in the previous
example, writes one record only. The function <c>insert_emp/3</c>,
shown in the program listing in
- <seealso marker="Mnesia_chap2#getting_started">Getting Started</seealso>, writes the record
+ <seeguide marker="Mnesia_chap2#getting_started">Getting Started</seeguide>, writes the record
<c>employee</c> as well as employee relations, such as
<c>at_dep</c> and <c>in_proj</c>, into the database. If this
latter code is run inside a transaction, the transaction
@@ -207,7 +207,7 @@
has terminated.</item>
</list>
<p><c>Mnesia</c> employs a strategy whereby functions, such as
- <seealso marker="mnesia#read/1">mnesia:read/1</seealso>
+ <seemfa marker="mnesia#read/1">mnesia:read/1</seemfa>
acquire the necessary locks dynamically as
the transactions execute. <c>Mnesia</c> automatically sets and
releases the locks and the programmer does not need to code these
@@ -220,7 +220,7 @@
forced to release all its locks and sleep for a while. The Fun
in the transaction is evaluated once more.</p>
<p>It is therefore important that the code inside the Fun given to
- <seealso marker="mnesia#transaction/2"><c>mnesia:transaction/1</c></seealso>
+ <seemfa marker="mnesia#transaction/1"><c>mnesia:transaction/1</c></seemfa>
is pure. Some strange results can
occur if, for example, messages are sent by the transaction
Fun. The following example illustrates this situation:</p>
@@ -252,11 +252,11 @@
transaction. If no enclosing transaction (or other enclosing
<c>Mnesia</c> activity) exists, they all fail.</p>
<list type="bulleted">
- <item><seealso marker="mnesia#transaction/2">mnesia:transaction(Fun) -> {aborted, Reason} |{atomic, Value}</seealso>
+ <item><seemfa marker="mnesia#transaction/1">mnesia:transaction(Fun) -> {aborted, Reason} |{atomic, Value}</seemfa>
executes one transaction with the
functional object <c>Fun</c> as the single parameter.
</item>
- <item><seealso marker="mnesia#read/1">mnesia:read({Tab, Key}) -> transaction abort | RecordList</seealso>
+ <item><seemfa marker="mnesia#read/1">mnesia:read({Tab, Key}) -> transaction abort | RecordList</seemfa>
reads all records with <c>Key</c>
as key from table <c>Tab</c>. This function has the same semantics
regardless of the location of <c>Table</c>. If the table is of
@@ -264,27 +264,27 @@
long list. If the table is of type <c>set</c>, the list is
either of length one or <c>[]</c>.
</item>
- <item><seealso marker="mnesia#wread/1">mnesia:wread({Tab, Key}) -> transaction abort | RecordList</seealso>
+ <item><seemfa marker="mnesia#wread/1">mnesia:wread({Tab, Key}) -> transaction abort | RecordList</seemfa>
behaves the same way as the
previously listed function <c>read/1</c>, except that it
acquires a write lock instead of a read lock. To execute a
transaction that reads a record, modifies the record, and then
writes the record, it is slightly more efficient to set the
- write lock immediately. When a <seealso marker="mnesia#read/1">mnesia:read/1</seealso>
+ write lock immediately. When a <seemfa marker="mnesia#read/1">mnesia:read/1</seemfa>
is issued, followed by a
- <seealso marker="mnesia#write/1">mnesia:write/1</seealso>
+ <seemfa marker="mnesia#write/1">mnesia:write/1</seemfa>
the first read lock must be upgraded to a write lock when the
write operation is executed.
</item>
- <item><seealso marker="mnesia#write/1">mnesia:write(Record) -> transaction abort | ok</seealso>
+ <item><seemfa marker="mnesia#write/1">mnesia:write(Record) -> transaction abort | ok</seemfa>
writes a record into the database. Argument
<c>Record</c> is an instance of a record. The function returns
<c>ok</c>, or terminates the transaction if an error occurs.
</item>
- <item><seealso marker="mnesia#delete/1">mnesia:delete({Tab, Key}) -> transaction abort | ok</seealso>
+ <item><seemfa marker="mnesia#delete/1">mnesia:delete({Tab, Key}) -> transaction abort | ok</seemfa>
deletes all records with the given key.
</item>
- <item><seealso marker="mnesia#delete_object/1">mnesia:delete_object(Record) -> transaction abort | ok</seealso>
+ <item><seemfa marker="mnesia#delete_object/1">mnesia:delete_object(Record) -> transaction abort | ok</seemfa>
deletes records with the OID <c>Record</c>. Use this function to
delete only some records in a table of type <c>bag</c>.</item>
</list>
@@ -327,9 +327,9 @@
end,
mnesia:transaction(F).</code>
<p>This code uses the function
- <seealso marker="mnesia#s_write/1">s_write/1</seealso>
+ <seemfa marker="mnesia#s_write/1">s_write/1</seemfa>
instead of the function
- <seealso marker="mnesia#write/1">write/1</seealso>
+ <seemfa marker="mnesia#write/1">write/1</seemfa>
The function <c>s_write/1</c> sets a
sticky lock instead of a normal lock. If the table is not
replicated, sticky locks have no special effect. If the table is
@@ -362,9 +362,9 @@
following two functions are used to set explicit table locks for
read and write operations:</p>
<list type="bulleted">
- <item><seealso marker="mnesia#read_lock_table/1">mnesia:read_lock_table(Tab)</seealso>
+ <item><seemfa marker="mnesia#read_lock_table/1">mnesia:read_lock_table(Tab)</seemfa>
sets a read lock on table <c>Tab</c>.</item>
- <item><seealso marker="mnesia#write_lock_table/1">mnesia:write_lock_table(Tab)</seealso>
+ <item><seemfa marker="mnesia#write_lock_table/1">mnesia:write_lock_table(Tab)</seemfa>
sets a write lock on table <c>Tab</c>.</item>
</list>
<p>Alternative syntax for acquisition of table locks is as
@@ -384,7 +384,7 @@
acquired on one node (the local one if a local
replica exists).</p>
<p>The function
- <seealso marker="mnesia#lock/2">mnesia:lock/2</seealso>
+ <seemfa marker="mnesia#lock/2">mnesia:lock/2</seemfa>
is intended to support table locks (as mentioned previously)
but also for situations when locks need to be
acquired regardless of how tables have been replicated:</p>
@@ -439,33 +439,33 @@
executed inside a transaction no locks are acquired. The
following functions are available:</p>
<list type="bulleted">
- <item><seealso marker="mnesia#dirty_read/1">mnesia:dirty_read({Tab, Key})</seealso>
+ <item><seemfa marker="mnesia#dirty_read/1">mnesia:dirty_read({Tab, Key})</seemfa>
reads one or more records from <c>Mnesia</c>.
</item>
- <item><seealso marker="mnesia#dirty_write/1">mnesia:dirty_write(Record)</seealso>
+ <item><seemfa marker="mnesia#dirty_write/1">mnesia:dirty_write(Record)</seemfa>
writes the record <c>Record</c>.
</item>
- <item><seealso marker="mnesia#dirty_delete/1">mnesia:dirty_delete({Tab, Key})</seealso>
+ <item><seemfa marker="mnesia#dirty_delete/1">mnesia:dirty_delete({Tab, Key})</seemfa>
deletes one or more records with key <c>Key</c>.
</item>
- <item><seealso marker="mnesia#dirty_delete_object/1">mnesia:dirty_delete_object(Record)</seealso>
+ <item><seemfa marker="mnesia#dirty_delete_object/1">mnesia:dirty_delete_object(Record)</seemfa>
is the dirty operation alternative to the function
- <seealso marker="mnesia#delete_object/1">delete_object/1</seealso>.
+ <seemfa marker="mnesia#delete_object/1">delete_object/1</seemfa>.
</item>
<item>
- <p><seealso marker="mnesia#dirty_first/1">mnesia:dirty_first(Tab)</seealso>
+ <p><seemfa marker="mnesia#dirty_first/1">mnesia:dirty_first(Tab)</seemfa>
returns the "first" key in table <c>Tab</c>.</p>
<p>Records in <c>set</c> or <c>bag</c> tables are not sorted.
However, there is a record order that is unknown to the user.
This means that a table can be traversed by this function
with the function
- <seealso marker="mnesia#dirty_next/2">mnesia:dirty_next/2</seealso>.</p>
+ <seemfa marker="mnesia#dirty_next/2">mnesia:dirty_next/2</seemfa>.</p>
<p>If there are no records in the table, this function
returns the atom <c>'$end_of_table'</c>. It is not
recommended to use this atom as the key for any user
records.</p>
</item>
- <item><p><seealso marker="mnesia#dirty_next/2">mnesia:dirty_next(Tab, Key)</seealso>
+ <item><p><seemfa marker="mnesia#dirty_next/2">mnesia:dirty_next(Tab, Key)</seemfa>
returns the "next" key in table <c>Tab</c>. This function makes it
possible to traverse a table and perform some operation on all
records in the table. When the end of the table is reached, the
@@ -475,22 +475,22 @@
<p>The behavior is undefined if any process performs a write
operation on the table while traversing the table with the
function
- <seealso marker="mnesia#dirty_next/2">dirty_next/2</seealso>
+ <seemfa marker="mnesia#dirty_next/2">dirty_next/2</seemfa>
This is because <c>write</c>
operations on a <c>Mnesia</c> table can lead to internal
reorganizations of the table itself. This is an implementation
detail, but remember that the dirty functions are low-level
functions.</p>
</item>
- <item><seealso marker="mnesia#dirty_last/1">mnesia:dirty_last(Tab)</seealso>
+ <item><seemfa marker="mnesia#dirty_last/1">mnesia:dirty_last(Tab)</seemfa>
works exactly like
- <seealso marker="mnesia#dirty_first/1">mnesia:dirty_first/1</seealso>
+ <seemfa marker="mnesia#dirty_first/1">mnesia:dirty_first/1</seemfa>
but returns the last object in
Erlang term order for the table type <c>ordered_set</c>. For
all other table types, <c>mnesia:dirty_first/1</c> and
<c>mnesia:dirty_last/1</c> are synonyms.
</item>
- <item><seealso marker="mnesia#dirty_prev/2">mnesia:dirty_prev(Tab, Key)</seealso>
+ <item><seemfa marker="mnesia#dirty_prev/2">mnesia:dirty_prev(Tab, Key)</seemfa>
works exactly like
<c>mnesia:dirty_next/2</c> but returns the previous object in
Erlang term order for the table type <c>ordered_set</c>. For
@@ -498,22 +498,14 @@
<c>mnesia:dirty_prev/2</c> are synonyms.
</item>
<item>
- <p><seealso marker="mnesia#dirty_slot/2">mnesia:dirty_slot(Tab, Slot)</seealso>
- returns the list of records that are associated with <c>Slot</c>
- in a table. It can be used to traverse a table in a manner
- similar to the function <c>dirty_next/2</c>. A table has a
- number of slots that range from zero to some unknown upper
- bound. The function <c>dirty_slot/2</c> returns the special
- atom <c>'$end_of_table'</c> when the end of the table is
- reached.</p>
<p>The behavior of this function is undefined if the
table is written on while being
traversed. The function
- <seealso marker="mnesia#read_lock_table/1">mnesia:read_lock_table(Tab)</seealso>
+ <seemfa marker="mnesia#read_lock_table/1">mnesia:read_lock_table(Tab)</seemfa>
can be used to ensure that no transaction-protected writes
are performed during the iteration.</p>
</item>
- <item><p><seealso marker="mnesia#dirty_update_counter/2">mnesia:dirty_update_counter({Tab,Key}, Val)</seealso>.
+ <item><p><seemfa marker="mnesia#dirty_update_counter/2">mnesia:dirty_update_counter({Tab,Key}, Val)</seemfa>.
Counters are positive integers with a value greater than or
equal to zero. Updating a counter adds <c>Val</c> and the
counter where <c>Val</c> is a positive or negative integer.</p>
@@ -529,7 +521,7 @@
<item>It is much more efficient.
</item>
<item>The funcion
- <seealso marker="mnesia#dirty_update_counter/2">dirty_update_counter/2</seealso>
+ <seemfa marker="mnesia#dirty_update_counter/2">dirty_update_counter/2</seemfa>
is performed as an atomic operation although it is not protected
by a transaction. Therfore no table update is lost if two
processes simultaneously execute the function
@@ -537,25 +529,25 @@
</item>
</list>
</item>
- <item><seealso marker="mnesia#dirty_match_object/2">mnesia:dirty_match_object(Pat)</seealso>
+ <item><seemfa marker="mnesia#dirty_match_object/2">mnesia:dirty_match_object(Pat)</seemfa>
is the dirty equivalent of
- <seealso marker="mnesia#match_object/1">mnesia:match_object/1</seealso>.
+ <seemfa marker="mnesia#match_object/1">mnesia:match_object/1</seemfa>.
</item>
- <item><seealso marker="mnesia#dirty_select/2">mnesia:dirty_select(Tab, Pat)</seealso>
+ <item><seemfa marker="mnesia#dirty_select/2">mnesia:dirty_select(Tab, Pat)</seemfa>
is the dirty equivalent of
- <seealso marker="mnesia#select/2"> mnesia:select/2</seealso>.
+ <seemfa marker="mnesia#select/2"> mnesia:select/2</seemfa>.
</item>
- <item><seealso marker="mnesia#dirty_index_match_object/2">mnesia:dirty_index_match_object(Pat, Pos)</seealso>
+ <item><seemfa marker="mnesia#dirty_index_match_object/2">mnesia:dirty_index_match_object(Pat, Pos)</seemfa>
is the dirty equivalent of
- <seealso marker="mnesia#index_match_object/2">mnesia:index_match_object/2</seealso>.
+ <seemfa marker="mnesia#index_match_object/2">mnesia:index_match_object/2</seemfa>.
</item>
- <item><seealso marker="mnesia#dirty_index_read/3">mnesia:dirty_index_read(Tab, SecondaryKey, Pos)</seealso>
+ <item><seemfa marker="mnesia#dirty_index_read/3">mnesia:dirty_index_read(Tab, SecondaryKey, Pos)</seemfa>
is the dirty equivalent of
- <seealso marker="mnesia#index_read/3">mnesia:index_read/3</seealso>.
+ <seemfa marker="mnesia#index_read/3">mnesia:index_read/3</seemfa>.
</item>
- <item><seealso marker="mnesia#dirty_all_keys/1">mnesia:dirty_all_keys(Tab)</seealso>
- is the dirty equivalent of <seealso marker="mnesia#all_keys/1">
-mnesia:all_keys/1</seealso>.
+ <item><seemfa marker="mnesia#dirty_all_keys/1">mnesia:dirty_all_keys(Tab)</seemfa>
+ is the dirty equivalent of <seemfa marker="mnesia#all_keys/1">
+mnesia:all_keys/1</seemfa>.
</item>
</list>
</section>
@@ -583,11 +575,11 @@ mnesia:all_keys/1</seealso>.
<p>To access such tables, simplified access functions
(as described earlier) cannot be used. For example,
writing a subscriber record into a table requires the function
- <seealso marker="mnesia#write/3">mnesia:write/3</seealso>
+ <seemfa marker="mnesia#write/3">mnesia:write/3</seemfa>
instead of the simplified functions
- <seealso marker="mnesia#write/1">mnesia:write/1</seealso>
+ <seemfa marker="mnesia#write/1">mnesia:write/1</seemfa>
and
- <seealso marker="mnesia#s_write/1">mnesia:s_write/1</seealso>:</p>
+ <seemfa marker="mnesia#s_write/1">mnesia:s_write/1</seemfa>:</p>
<code type="none">
mnesia:write(subscriber, #subscriber{}, write)
mnesia:write(my_subscriber, #subscriber{}, sticky_write)
@@ -664,58 +656,58 @@ mnesia:all_keys/1</seealso>.
<p>As previously described, a Functional Object (Fun) performing
table access operations, as listed here, can be passed
on as arguments to the function
- <seealso marker="mnesia#transaction/2">mnesia:transaction/1,2,3</seealso>:
+ <seemfa marker="mnesia#transaction/1">mnesia:transaction/1,2,3</seemfa>:
</p>
<list type="bulleted">
<item>
- <seealso marker="mnesia#write/3">mnesia:write/3 (write/1, s_write/1)</seealso>
+ <seemfa marker="mnesia#write/3">mnesia:write/3 (write/1, s_write/1)</seemfa>
</item>
<item>
- <seealso marker="mnesia#delete/3">mnesia:delete/3</seealso>
- (<seealso marker="mnesia#delete/1">mnesia:delete/1</seealso>,
- <seealso marker="mnesia#s_delete/1">mnesia:s_delete/1</seealso>)
+ <seemfa marker="mnesia#delete/3">mnesia:delete/3</seemfa>
+ (<seemfa marker="mnesia#delete/1">mnesia:delete/1</seemfa>,
+ <seemfa marker="mnesia#s_delete/1">mnesia:s_delete/1</seemfa>)
</item>
<item>
- <seealso marker="mnesia#delete_object/3">mnesia:delete_object/3</seealso>
- (<seealso marker="mnesia#delete_object/1">mnesia:delete_object/1</seealso>,
- <seealso marker="mnesia#s_delete_object/1">mnesia:s_delete_object/1</seealso>)
+ <seemfa marker="mnesia#delete_object/3">mnesia:delete_object/3</seemfa>
+ (<seemfa marker="mnesia#delete_object/1">mnesia:delete_object/1</seemfa>,
+ <seemfa marker="mnesia#s_delete_object/1">mnesia:s_delete_object/1</seemfa>)
</item>
<item>
- <seealso marker="mnesia#read/3">mnesia:read/3</seealso>
- (<seealso marker="mnesia#read/1">mnesia:read/1</seealso>,
- <seealso marker="mnesia#wread/1">mnesia:wread/1</seealso>)
+ <seemfa marker="mnesia#read/3">mnesia:read/3</seemfa>
+ (<seemfa marker="mnesia#read/1">mnesia:read/1</seemfa>,
+ <seemfa marker="mnesia#wread/1">mnesia:wread/1</seemfa>)
</item>
<item>
- <seealso marker="mnesia#match_object/3">mnesia:match_object/2</seealso>
- (<seealso marker="mnesia#match_object/1">mnesia:match_object/1</seealso>)
+ <seemfa marker="mnesia#match_object/3">mnesia:match_object/2</seemfa>
+ (<seemfa marker="mnesia#match_object/1">mnesia:match_object/1</seemfa>)
</item>
<item>
- <seealso marker="mnesia#select/2">mnesia:select/3</seealso>
- (<seealso marker="mnesia#select/2">mnesia:select/2</seealso>)
+ <seemfa marker="mnesia#select/2">mnesia:select/3</seemfa>
+ (<seemfa marker="mnesia#select/2">mnesia:select/2</seemfa>)
</item>
<item>
- <seealso marker="mnesia#foldl/3">mnesia:foldl/3</seealso>
+ <seemfa marker="mnesia#foldl/3">mnesia:foldl/3</seemfa>
(<c>mnesia:foldl/4</c>,
- <seealso marker="mnesia#foldr/3">mnesia:foldr/3</seealso>,
+ <seemfa marker="mnesia#foldr/3">mnesia:foldr/3</seemfa>,
<c>mnesia:foldr/4</c>)
</item>
<item>
- <seealso marker="mnesia#all_keys/1">mnesia:all_keys/1</seealso>
+ <seemfa marker="mnesia#all_keys/1">mnesia:all_keys/1</seemfa>
</item>
<item>
- <seealso marker="mnesia#index_match_object/4">mnesia:index_match_object/4</seealso>
- (<seealso marker="mnesia#index_match_object/2">mnesia:index_match_object/2</seealso>)
+ <seemfa marker="mnesia#index_match_object/4">mnesia:index_match_object/4</seemfa>
+ (<seemfa marker="mnesia#index_match_object/2">mnesia:index_match_object/2</seemfa>)
</item>
<item>
- <seealso marker="mnesia#index_read/3">mnesia:index_read/3</seealso>
+ <seemfa marker="mnesia#index_read/3">mnesia:index_read/3</seemfa>
</item>
<item>
- <seealso marker="mnesia#lock/2">mnesia:lock/2</seealso>
- (<seealso marker="mnesia#read_lock_table/1">mnesia:read_lock_table/1</seealso>,
- <seealso marker="mnesia#write_lock_table/1">mnesia:write_lock_table/1</seealso>)
+ <seemfa marker="mnesia#lock/2">mnesia:lock/2</seemfa>
+ (<seemfa marker="mnesia#read_lock_table/1">mnesia:read_lock_table/1</seemfa>,
+ <seemfa marker="mnesia#write_lock_table/1">mnesia:write_lock_table/1</seemfa>)
</item>
<item>
- <seealso marker="mnesia#table_info/2">mnesia:table_info/2</seealso>
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info/2</seemfa>
</item>
</list>
<p>These functions are performed in a
@@ -732,7 +724,7 @@ mnesia:all_keys/1</seealso>.
<item><c>ets</c></item>
</list>
<p>By passing the same "fun" as argument to the function
- <seealso marker="mnesia#sync_transaction/3">mnesia:sync_transaction(Fun [, Args])</seealso>
+ <seemfa marker="mnesia#sync_transaction/1">mnesia:sync_transaction(Fun [, Args])</seemfa>
it is performed
in synced transaction context. Synced transactions wait until all
active replicas has committed the transaction (to disc) before
@@ -750,7 +742,7 @@ mnesia:all_keys/1</seealso>.
that can overload <c>Mnesia</c> on other nodes.</item>
</list>
<p>By passing the same "fun" as argument to the function
- <seealso marker="mnesia#async_dirty/2">mnesia:async_dirty(Fun [, Args])</seealso>,
+ <seemfa marker="mnesia#async_dirty/1">mnesia:async_dirty(Fun [, Args])</seemfa>,
it is performed in dirty context. The function calls are mapped to
the corresponding dirty functions. This still involves logging,
replication, and subscriptions but no locking,
@@ -761,9 +753,9 @@ mnesia:all_keys/1</seealso>.
node but not the others. If the table resides locally, no waiting
occurs.</p>
<p>By passing the same "fun" as an argument to the function
- <seealso marker="mnesia#sync_dirty/2">mnesia:sync_dirty(Fun [, Args])</seealso>,
+ <seemfa marker="mnesia#sync_dirty/1">mnesia:sync_dirty(Fun [, Args])</seemfa>,
it is performed in almost the same context as the function
- <seealso marker="mnesia#async_dirty/2">mnesia:async_dirty/1,2</seealso>.
+ <seemfa marker="mnesia#async_dirty/1">mnesia:async_dirty/1,2</seemfa>.
The difference is that the operations are performed
synchronously. The caller waits for the updates to be
performed on all active replicas. Using <c>mnesia:sync_dirty/1,2</c>
@@ -778,7 +770,7 @@ mnesia:all_keys/1</seealso>.
</list>
<p>To check if your code is executed within a transaction, use
the function
- <seealso marker="mnesia#is_transaction/0">mnesia:is_transaction/0</seealso>.
+ <seemfa marker="mnesia#is_transaction/0">mnesia:is_transaction/0</seemfa>.
It returns <c>true</c> when called
inside a transaction context, otherwise <c>false</c>.</p>
<p><c>Mnesia</c> tables with storage type <c>RAM_copies</c> and
@@ -788,7 +780,7 @@ mnesia:all_keys/1</seealso>.
recommended if all options have been weighed and the possible
outcomes are understood. By passing the earlier mentioned "fun"
to the function
- <seealso marker="mnesia#ets/2">mnesia:ets(Fun [, Args])</seealso>,
+ <seemfa marker="mnesia#ets/1">mnesia:ets(Fun [, Args])</seemfa>,
it is performed but in a raw
context. The operations are performed directly on the
local <c>ets</c> tables, assuming that the local storage type is
@@ -799,7 +791,7 @@ mnesia:all_keys/1</seealso>.
tables are not to be updated with the <c>ets</c> function, as the
disc is not updated.</p>
<p>The Fun can also be passed as an argument to the function
- <seealso marker="mnesia#activity-4">mnesia:activity/2,3,4</seealso>,
+ <seemfa marker="mnesia#activity/2">mnesia:activity/2,3,4</seemfa>,
which enables use of customized
activity access callback modules. It can either be obtained
directly by stating the module name as argument, or implicitly
@@ -810,11 +802,11 @@ mnesia:all_keys/1</seealso>.
<p>The callback module does not have
to access real <c>Mnesia</c> tables, it is free to do whatever
it wants as long as the callback interface is fulfilled.</p>
- <p><seealso marker="Mnesia_App_B">Appendix B,
- Activity Access Callback Interface</seealso> provides the
+ <p><seeguide marker="Mnesia_App_B">Appendix B,
+ Activity Access Callback Interface</seeguide> provides the
source code, <c>mnesia_frag.erl</c>, for one alternative
implementation. The context-sensitive function
- <seealso marker="mnesia#table_info/2">mnesia:table_info/2</seealso>
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info/2</seemfa>
can be used to provide virtual
information about a table. One use of this is to perform
<c>QLC</c> queries within an activity context with a
@@ -893,7 +885,7 @@ mnesia:all_keys/1</seealso>.
<title>Pattern Matching</title>
<marker id="matching"></marker>
<p>When the function
- <seealso marker="mnesia#read/3">mnesia:read/3</seealso>
+ <seemfa marker="mnesia#read/3">mnesia:read/3</seemfa>
cannot be used, <c>Mnesia</c>
provides the programmer with several functions for matching
records against a pattern. The most useful ones
@@ -909,7 +901,7 @@ mnesia:all_keys/1</seealso>.
transaction abort | RecordList</code>
<p>These functions match a <c>Pattern</c> against all records in
table <c>Tab</c>. In a
- <seealso marker="mnesia#select/2">mnesia:select</seealso>
+ <seemfa marker="mnesia#select/2">mnesia:select</seemfa>
call, <c>Pattern</c> is
a part of <c>MatchSpecification</c> described in the following. It
is not necessarily performed as an exhaustive search of the entire
@@ -926,7 +918,7 @@ mnesia:all_keys/1</seealso>.
bind the first occurrence, and match the
coming occurrences of that variable against the bound value.</p>
<p>Use function
- <seealso marker="mnesia#table_info/2">mnesia:table_info(Tab, wild_pattern)</seealso>
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info(Tab, wild_pattern)</seemfa>
to obtain a basic pattern, which matches all records in a table,
or use the default value in record creation.
Do not make the pattern hard-coded, as this makes the code more
@@ -954,9 +946,9 @@ mnesia:all_keys/1</seealso>.
F = fun() -> mnesia:match_object(Pat) end,
Odd = mnesia:transaction(F).</code>
<p>The function
- <seealso marker="mnesia#match_object/3">mnesia:match_object/3</seealso>
+ <seemfa marker="mnesia#match_object/3">mnesia:match_object/3</seemfa>
lacks some important features that
- <seealso marker="mnesia#select/2">mnesia:select/3</seealso>
+ <seemfa marker="mnesia#select/2">mnesia:select/3</seemfa>
have. For example,
<c>mnesia:match_object/3</c> can only return the matching records,
and it cannot express constraints other than equality. To find
@@ -978,14 +970,14 @@ mnesia:all_keys/1</seealso>.
</list>
<p>For details about the match specifications, see
"Match Specifications in Erlang" in
- <seealso marker="erts:index">ERTS</seealso> User's Guide.
+ <seeapp marker="erts:index">ERTS</seeapp> User's Guide.
For more information, see the
- <seealso marker="stdlib:ets">ets</seealso> and
- <seealso marker="stdlib:dets">dets</seealso>
+ <seeerl marker="stdlib:ets">ets</seeerl> and
+ <seeerl marker="stdlib:dets">dets</seeerl>
manual pages in <c>STDLIB</c>.</p>
<p>The functions
- <seealso marker="mnesia#select/4">select/4</seealso> and
- <seealso marker="mnesia#select/2">select/1</seealso>
+ <seemfa marker="mnesia#select/4">select/4</seemfa> and
+ <seemfa marker="mnesia#select/2">select/1</seemfa>
are used to
get a limited number of results, where <c>Continuation</c>
gets the next chunk of results. <c>Mnesia</c> uses
@@ -998,8 +990,8 @@ mnesia:all_keys/1</seealso>.
<c>mnesia:select/[1|2|3|4]</c> after any modifying operation
is done on that table in the same transaction. That is, avoid
using
- <seealso marker="mnesia#write/1">mnesia:write/1</seealso> or
- <seealso marker="mnesia#delete/1">mnesia:delete/1</seealso>
+ <seemfa marker="mnesia#write/1">mnesia:write/1</seemfa> or
+ <seemfa marker="mnesia#delete/1">mnesia:delete/1</seemfa>
before <c>mnesia:select</c> in the same transaction.</p>
</warning>
<p>If the key attribute is bound in a pattern, the match operation
@@ -1008,13 +1000,13 @@ mnesia:all_keys/1</seealso>.
table must be searched for records that match. Hence if the table is
large, this can become a time-consuming operation, but it can be
remedied with indexes (see
- <seealso marker="Mnesia_chap5#indexing">Indexing</seealso>)
+ <seeguide marker="Mnesia_chap5#indexing">Indexing</seeguide>)
if the function
- <seealso marker="mnesia#match_object/1">mnesia:match_object</seealso>
+ <seemfa marker="mnesia#match_object/1">mnesia:match_object</seemfa>
is used.</p>
<p>QLC queries can also be used to search <c>Mnesia</c> tables. By
using the function
- <seealso marker="mnesia#table/1">mnesia:table/[1|2]</seealso>
+ <seemfa marker="mnesia#table/1">mnesia:table/[1|2]</seemfa>
as the generator inside a QLC
query, you let the query operate on a <c>Mnesia</c> table.
<c>Mnesia</c>-specific options to <c>mnesia:table/2</c> are
@@ -1029,7 +1021,7 @@ mnesia:all_keys/1</seealso>.
to use to traverse the table. Default <c>select</c> is used, but
by using <c>{traverse, {select, MatchSpecification}}</c> as an
option to
- <seealso marker="mnesia#table/1">mnesia:table/2</seealso>
+ <seemfa marker="mnesia#table/1">mnesia:table/2</seemfa>
the user can specify its own view of the table.</item>
</list>
<p>If no options are specified, a read lock is acquired, 100
@@ -1039,7 +1031,7 @@ mnesia:all_keys/1</seealso>.
mnesia:table(Tab) ->
mnesia:table(Tab, [{n_objects,100},{lock, read}, {traverse, select}]).</code>
<p>The function
- <seealso marker="mnesia#all_keys/1">mnesia:all_keys(Tab)</seealso>
+ <seemfa marker="mnesia#all_keys/1">mnesia:all_keys(Tab)</seemfa>
returns all keys in a table.</p>
</section>
@@ -1063,8 +1055,8 @@ mnesia:all_keys/1</seealso>.
the return value from the previous call is used as the
second argument. The term the last call to <c>Fun</c> returns
is the return value of the function
- <seealso marker="mnesia#foldl/3">mnesia:foldl/3</seealso> or
- <seealso marker="mnesia#foldr/3">mnesia:foldr/3</seealso>.</p>
+ <seemfa marker="mnesia#foldl/3">mnesia:foldl/3</seemfa> or
+ <seemfa marker="mnesia#foldr/3">mnesia:foldr/3</seemfa>.</p>
<p>The difference between these functions is the
order the table is accessed for <c>ordered_set</c> tables.
For other table types the functions are equivalent.</p>
@@ -1074,7 +1066,7 @@ mnesia:all_keys/1</seealso>.
lock is to be acquired.</p>
<p>These functions can be used to find records in a table
when it is impossible to write constraints for the function
- <seealso marker="mnesia#match_object/3">mnesia:match_object/3</seealso>,
+ <seemfa marker="mnesia#match_object/3">mnesia:match_object/3</seemfa>,
or when you want to perform some action on certain records.</p>
<p>For example, finding all the employees who have a salary
less than 10 can look as follows:</p>
@@ -1126,10 +1118,10 @@ mnesia:all_keys/1</seealso>.
<c>'$end_of_table'</c> is returned.</p>
<p>If records are written and deleted during the traversal, use
the function
- <seealso marker="mnesia#foldl">mnesia:foldl/3</seealso> or
- <seealso marker="mnesia#foldr">mnesia:foldr/3</seealso>
+ <seeerl marker="mnesia#foldl">mnesia:foldl/3</seeerl> or
+ <seeerl marker="mnesia#foldr">mnesia:foldr/3</seeerl>
with a <c>write</c> lock. Or the function
- <seealso marker="mnesia#write_lock_table/1">mnesia:write_lock_table/1</seealso>
+ <seemfa marker="mnesia#write_lock_table/1">mnesia:write_lock_table/1</seemfa>
when using <c>first</c> and <c>next</c>.</p>
<p>Writing or deleting in transaction context creates a local copy
of each modified record. Thus, modifying each record in a large
@@ -1144,12 +1136,12 @@ mnesia:all_keys/1</seealso>.
network traffic if the table has a replica on another node and
has all the other drawbacks that dirty operations
have. Especially for commands
- <seealso marker="mnesia#first/1">mnesia:first/1</seealso> and
- <seealso marker="mnesia#next/2">mnesia:next/2</seealso>,
+ <seemfa marker="mnesia#first/1">mnesia:first/1</seemfa> and
+ <seemfa marker="mnesia#next/2">mnesia:next/2</seemfa>,
the same drawbacks as described previously for
- <seealso marker="mnesia#dirty_first/1">mnesia:dirty_first/1</seealso>
+ <seemfa marker="mnesia#dirty_first/1">mnesia:dirty_first/1</seemfa>
and
- <seealso marker="mnesia#dirty_next/2">mnesia:dirty_next/2</seealso>
+ <seemfa marker="mnesia#dirty_next/2">mnesia:dirty_next/2</seemfa>
applies, that
is, no writing to the table is to be done during iteration.</p>
</section>
diff --git a/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc
index 481e6651e6..c6c6dc95c9 100644
--- a/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc
@@ -64,10 +64,10 @@
<p>The following two functions manipulate indexes on existing
tables:</p>
<list type="bulleted">
- <item><seealso marker="mnesia#add_table_index/2">mnesia:add_table_index(Tab, AttributeName)
- -> {aborted, R} |{atomic, ok}</seealso></item>
- <item><seealso marker="mnesia#del_table_index/2">mnesia:del_table_index(Tab, AttributeName)
- -> {aborted, R} |{atomic, ok}</seealso></item>
+ <item><seemfa marker="mnesia#add_table_index/2">mnesia:add_table_index(Tab, AttributeName)
+ -> {aborted, R} |{atomic, ok}</seemfa></item>
+ <item><seemfa marker="mnesia#del_table_index/2">mnesia:del_table_index(Tab, AttributeName)
+ -> {aborted, R} |{atomic, ok}</seemfa></item>
</list>
<p>These functions create or delete a table index on a field
defined by <c>AttributeName</c>. To illustrate this, add an
@@ -81,28 +81,28 @@
based on index entries in the database:</p>
<list type="bulleted">
<item>
- <seealso marker="mnesia#index_read/3">mnesia:index_read(Tab, SecondaryKey, AttributeName)
- -> transaction abort | RecordList</seealso>
+ <seemfa marker="mnesia#index_read/3">mnesia:index_read(Tab, SecondaryKey, AttributeName)
+ -> transaction abort | RecordList</seemfa>
avoids an exhaustive search of the entire table, by looking up
<c>SecondaryKey</c> in the index to find the primary keys.
</item>
<item>
- <seealso marker="mnesia#index_match_object/2">mnesia:index_match_object(Pattern, AttributeName)
- -> transaction abort | RecordList</seealso>
+ <seemfa marker="mnesia#index_match_object/2">mnesia:index_match_object(Pattern, AttributeName)
+ -> transaction abort | RecordList</seemfa>
avoids an exhaustive search of the entire table, by looking up
the secondary key in the index to find the primary keys.
The secondary key is found in field <c>AttributeName</c> of
<c>Pattern</c>. The secondary key must be bound.
</item>
<item>
- <seealso marker="mnesia#match_object/1">mnesia:match_object(Pattern)
- -> transaction abort | RecordList</seealso>
+ <seemfa marker="mnesia#match_object/1">mnesia:match_object(Pattern)
+ -> transaction abort | RecordList</seemfa>
uses indexes to avoid exhaustive search of the entire table.
Unlike the previous functions, this function can use
any index as long as the secondary key is bound.</item>
</list>
<p>These functions are further described and exemplified in
- <seealso marker="Mnesia_chap4#matching">Pattern Matching</seealso>.
+ <seeguide marker="Mnesia_chap4#matching">Pattern Matching</seeguide>.
</p>
</section>
@@ -149,7 +149,7 @@
following alternatives are available:</p>
<list type="ordered">
<item>The function
- <seealso marker="mnesia#dump_tables/1">mnesia:dump_tables/1</seealso>
+ <seemfa marker="mnesia#dump_tables/1">mnesia:dump_tables/1</seemfa>
can be used to dump RAM table replicas to disc.
</item>
<item>The table replicas can be backed up, either from
@@ -173,7 +173,7 @@
</list>
<p>In addition, table properties can be set and changed.
For details, see
- <seealso marker="Mnesia_chap3#def_schema">Define a Schema</seealso>.
+ <seeguide marker="Mnesia_chap3#def_schema">Define a Schema</seeguide>.
</p>
<p>There are basically two reasons for using more than one table
replica: fault tolerance and speed. Notice
@@ -215,7 +215,7 @@
<c>mnesia_frag</c>, which implements the <c>mnesia_access</c>
callback behavior. It is recommended to read the
documentation about the function
- <seealso marker="mnesia#activity/4">mnesia:activity/4</seealso>
+ <seemfa marker="mnesia#activity/4">mnesia:activity/4</seemfa>
to see how <c>mnesia_frag</c>
can be used as a <c>mnesia_access</c> callback module.</p>
<p>At each record access, <c>mnesia_frag</c> first computes
@@ -287,7 +287,7 @@ ok
<title>Fragmentation Properties</title>
<p>The table property <c>frag_properties</c> can be read with
the function
- <seealso marker="mnesia#table_info/2">mnesia:table_info(Tab, frag_properties)</seealso>.
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info(Tab, frag_properties)</seemfa>.
The fragmentation properties are a list of tagged tuples with
arity 2. By default the list is empty, but when it is
non-empty it triggers <c>Mnesia</c> to regard the table as
@@ -311,7 +311,7 @@ ok
the node pool. Hopefully all nodes end up with the
same number of replicas. <c>node_pool</c> defaults to the
return value from the function
- <seealso marker="mnesia#system_info/1">mnesia:system_info(db_nodes)</seealso>.</p>
+ <seemfa marker="mnesia#system_info/1">mnesia:system_info(db_nodes)</seemfa>.</p>
</item>
<tag><c>{n_ram_copies, Int}</c></tag>
<item>
@@ -361,7 +361,7 @@ ok
<item>
<p>Enables definition of an alternative hashing scheme.
The module must implement the
- <seealso marker="mnesia_frag_hash">mnesia_frag_hash</seealso>
+ <seeerl marker="mnesia_frag_hash">mnesia_frag_hash</seeerl>
callback behavior. This property can explicitly be set at
table creation. Default is <c>mnesia_frag_hash</c>.</p>
</item>
@@ -464,7 +464,7 @@ ok
rehashed in the same manner as for the main table.</p>
<p>Argument <c>NodesOrDist</c> can either be a list of
nodes or the result from the function
- <seealso marker="mnesia#table_info/2">mnesia:table_info(Tab, frag_dist)</seealso>.
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info(Tab, frag_dist)</seemfa>.
Argument <c>NodesOrDist</c> is
assumed to be a sorted list with the best nodes to
host new replicas first in the list. The new fragment
@@ -488,14 +488,14 @@ ok
<item>
<p>Adds a node to <c>node_pool</c>. The new
node pool affects the list returned from the function
- <seealso marker="mnesia#table_info/2">mnesia:table_info(Tab, frag_dist)</seealso>.
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info(Tab, frag_dist)</seemfa>.
</p>
</item>
<tag><c>{del_node, Node}</c></tag>
<item>
<p>Deletes a node from <c>node_pool</c>. The new
node pool affects the list returned from the function
- <seealso marker="mnesia#table_info/2">mnesia:table_info(Tab, frag_dist)</seealso>.
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info(Tab, frag_dist)</seemfa>.
</p>
</item>
</taglist>
@@ -504,16 +504,16 @@ ok
<section>
<title>Extensions of Existing Functions</title>
<p>The function
- <seealso marker="mnesia#create_table/2">mnesia:create_table/2</seealso>
+ <seemfa marker="mnesia#create_table/2">mnesia:create_table/2</seemfa>
creates a brand new fragmented table, by setting table
property <c>frag_properties</c> to some proper values.</p>
<p>The function
- <seealso marker="mnesia#delete_table/1">mnesia:delete_table/1</seealso>
+ <seemfa marker="mnesia#delete_table/1">mnesia:delete_table/1</seemfa>
deletes a fragmented table including all its
fragments. There must however not exist any other fragmented
tables that refer to this table in their foreign key.</p>
<p>The function
- <seealso marker="mnesia#table_info/2">mnesia:table_info/2</seealso>
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info/2</seemfa>
now understands item <c>frag_properties</c>.</p>
<p>If the function <c>mnesia:table_info/2</c> is started in
the activity context of module <c>mnesia_frag</c>,
@@ -539,11 +539,11 @@ ok
(for example, when adding new fragments) they are
determined by counting the number of each replica for
each storage type. This means that when the functions
- <seealso marker="mnesia#add_table_copy/3">mnesia:add_table_copy/3</seealso>,
+ <seemfa marker="mnesia#add_table_copy/3">mnesia:add_table_copy/3</seemfa>,
- <seealso marker="mnesia#del_table_copy/2">mnesia:del_table_copy/2</seealso>,
+ <seemfa marker="mnesia#del_table_copy/2">mnesia:del_table_copy/2</seemfa>,
and
- <seealso marker="mnesia#change_table_copy_type/3">mnesia:change_table_copy_type/2</seealso> are applied on the
+ <seemfa marker="mnesia#change_table_copy_type/3">mnesia:change_table_copy_type/2</seemfa> are applied on the
first fragment, it affects the settings on
<c>n_ram_copies</c>, <c>n_disc_copies</c>, and
<c>n_disc_only_copies</c>.</p>
@@ -633,10 +633,10 @@ ok
<p>Use the function
<c>mnesia:change_table_frag/2</c> to add new fragments
and apply the usual schema manipulation functions (such as
- <seealso marker="mnesia#add_table_copy/3">mnesia:add_table_copy/3</seealso>,
- <seealso marker="mnesia#del_table_copy/2">mnesia:del_table_copy/2</seealso>,
+ <seemfa marker="mnesia#add_table_copy/3">mnesia:add_table_copy/3</seemfa>,
+ <seemfa marker="mnesia#del_table_copy/2">mnesia:del_table_copy/2</seemfa>,
and
- <seealso marker="mnesia#change_table_copy_type/3">mnesia:change_table_copy_type/2</seealso>)
+ <seemfa marker="mnesia#change_table_copy_type/3">mnesia:change_table_copy_type/2</seemfa>)
on each fragment to perform the actual redistribution.</p>
</section>
</section>
@@ -681,7 +681,7 @@ ok
<c>-mnesia extra_db_nodes NodeList</c>. Without this
configuration parameter set, <c>Mnesia</c> starts as a single
node system. Also, the function
- <seealso marker="mnesia#change_config/2">mnesia:change_config/2</seealso>
+ <seemfa marker="mnesia#change_config/2">mnesia:change_config/2</seemfa>
can be used to assign a value to <c>extra_db_nodes</c> and force
a connection after <c>Mnesia</c> has been started, that is,
<c>mnesia:change_config(extra_db_nodes, NodeList)</c>.</p>
@@ -719,7 +719,7 @@ ok
</taglist>
<p>When <c>schema_location</c> is set to <c>opt_disc</c>, the
function
- <seealso marker="mnesia#change_table_copy_type/3">mnesia:change_table_copy_type/3</seealso>
+ <seemfa marker="mnesia#change_table_copy_type/3">mnesia:change_table_copy_type/3</seemfa>
can be used to change the storage type of the schema.
This is illustrated as follows:</p>
<pre>
@@ -728,7 +728,7 @@ ok
2> mnesia:change_table_copy_type(schema, node(), disc_copies).
{atomic, ok}</pre>
<p>Assuming that the call to
- <seealso marker="mnesia#start/0">mnesia:start/0</seealso> does not
+ <seemfa marker="mnesia#start/0">mnesia:start/0</seemfa> does not
find any schema to read on the disc, <c>Mnesia</c> starts
as a disc-less node, and then change it to a node that
use the disc to store the schema locally.</p>
@@ -739,9 +739,9 @@ ok
<p>Nodes can be added to and removed from a <c>Mnesia</c> system.
This can be done by adding a copy of the schema to those nodes.</p>
<p>The functions
- <seealso marker="mnesia#add_table_copy/3">mnesia:add_table_copy/3</seealso>
+ <seemfa marker="mnesia#add_table_copy/3">mnesia:add_table_copy/3</seemfa>
and
- <seealso marker="mnesia#del_table_copy/2">mnesia:del_table_copy/2</seealso>
+ <seemfa marker="mnesia#del_table_copy/2">mnesia:del_table_copy/2</seemfa>
can be used to add and delete
replicas of the schema table. Adding a node to the list of
nodes where the schema is replicated affects the following:</p>
@@ -759,7 +759,7 @@ ok
connect to that node again. Notice that if there is a disc resident
schema on node <c>mynode@host</c>, the entire <c>Mnesia</c>
directory is to be deleted. This is done with the function
- <seealso marker="mnesia#delete_schema/1">mnesia:delete_schema/1</seealso>.
+ <seemfa marker="mnesia#delete_schema/1">mnesia:delete_schema/1</seemfa>.
If <c>Mnesia</c> is started again
on node <c>mynode@host</c> and the directory has not been
cleared, the behavior of <c>Mnesia</c> is undefined.</p>
@@ -769,13 +769,13 @@ ok
use is enabled by changing the storage type of table
<c>schema</c> to <c>disc_copies</c>.</p>
<p>New schemas are created explicitly with the function
- <seealso marker="mnesia#create_schema/1">mnesia:create_schema/1</seealso>
+ <seemfa marker="mnesia#create_schema/1">mnesia:create_schema/1</seemfa>
or implicitly by starting
<c>Mnesia</c> without a disc resident schema. Whenever
a table (including the schema table) is created, it is
assigned its own unique cookie. The schema table is not created
with the function
- <seealso marker="mnesia#create_table/2">mnesia:create_table/2</seealso>
+ <seemfa marker="mnesia#create_table/2">mnesia:create_table/2</seemfa>
as normal tables.</p>
<p>At startup, <c>Mnesia</c> connects different nodes to each other,
then they exchange table definitions with each other, and the table
@@ -799,13 +799,13 @@ ok
time a RAM node connects to another node.</p>
<p>Further, the following applies:</p>
<list type ="bulleted">
- <item><seealso marker="mnesia#system_info/1">mnesia:system_info(schema_location)</seealso>
+ <item><seemfa marker="mnesia#system_info/1">mnesia:system_info(schema_location)</seemfa>
and
- <seealso marker="mnesia#system_info/1">mnesia:system_info(extra_db_nodes)</seealso>
+ <seemfa marker="mnesia#system_info/1">mnesia:system_info(extra_db_nodes)</seemfa>
can be used to determine the actual values of <c>schema_location</c>
and <c>extra_db_nodes</c>, respectively.
</item>
- <item><seealso marker="mnesia#system_info/1">mnesia:system_info(use_dir)</seealso>
+ <item><seemfa marker="mnesia#system_info/1">mnesia:system_info(use_dir)</seemfa>
can be used to determine whether <c>Mnesia</c> is actually
using the <c>Mnesia</c> directory.
</item>
@@ -813,7 +813,7 @@ ok
<c>Mnesia</c> is started.
</item>
</list>
- <p>The function <seealso marker="mnesia#info/0">mnesia:info/0</seealso>
+ <p>The function <seemfa marker="mnesia#info/0">mnesia:info/0</seemfa>
can now be used to print
some system information even before <c>Mnesia</c> is started.
When <c>Mnesia</c> is started, the function prints more
@@ -842,11 +842,11 @@ ok
<p>A user process can subscribe on the events generated by
<c>Mnesia</c>. The following two functions are provided:</p>
<taglist>
- <tag><seealso marker="mnesia#subscribe/1">mnesia:subscribe(Event-Category)</seealso>
+ <tag><seemfa marker="mnesia#subscribe/1">mnesia:subscribe(Event-Category)</seemfa>
</tag>
<item>Ensures that a copy of all events of type
<c>Event-Category</c> are sent to the calling process</item>
- <tag><seealso marker="mnesia#unsubscribe/1">mnesia:unsubscribe(Event-Category)</seealso>
+ <tag><seemfa marker="mnesia#unsubscribe/1">mnesia:unsubscribe(Event-Category)</seemfa>
</tag>
<item>Removes the subscription on events of type
<c>Event-Category</c>
@@ -868,7 +868,7 @@ ok
<p>The subscribe functions activate a subscription
of events. The events are delivered as messages to the process
evaluating the function
- <seealso marker="mnesia#subscribe/1">mnesia:subscribe/1</seealso>
+ <seemfa marker="mnesia#subscribe/1">mnesia:subscribe/1</seemfa>
The syntax is as follows:</p>
<list type="bulleted">
<item><c>{mnesia_system_event, Event}</c> for system events
@@ -885,11 +885,11 @@ ok
application parameter <c>event_module</c>. The value of this
parameter must be the name of a module implementing a complete
handler, as specified by the
- <seealso marker="stdlib:gen_event">gen_event</seealso> module
+ <seeerl marker="stdlib:gen_event">gen_event</seeerl> module
in <c>STDLIB</c>.</p>
- <p><seealso marker="mnesia#system_info/1">mnesia:system_info(subscribers)</seealso>
+ <p><seemfa marker="mnesia#system_info/1">mnesia:system_info(subscribers)</seemfa>
and
- <seealso marker="mnesia#table_info/2">mnesia:table_info(Tab, subscribers)</seealso>
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info(Tab, subscribers)</seemfa>
can be used to determine which processes are subscribed to
various events.</p>
@@ -910,7 +910,7 @@ ok
activated and the current node is involved in the
checkpoint. Checkpoints can be activated explicitly with
the function
- <seealso marker="mnesia#activate_checkpoint/1">mnesia:activate_checkpoint/1</seealso>
+ <seemfa marker="mnesia#activate_checkpoint/1">mnesia:activate_checkpoint/1</seemfa>
or implicitly at
backup, when adding table replicas, at internal transfer of
data between nodes, and so on. By default this event is
@@ -921,7 +921,7 @@ ok
deactivated and the current node is involved in the
checkpoint. Checkpoints can be deactivated explicitly with
the function
- <seealso marker="mnesia#deactivate_checkpoint/1">mnesia:deactivate/1</seealso>
+ <seemfa marker="mnesia#deactivate_checkpoint/1">mnesia:deactivate/1</seemfa>
or implicitly when the last
replica of a table (involved in the checkpoint) becomes
unavailable, for example, at node-down. By default this
@@ -964,9 +964,9 @@ ok
recover from the inconsistency. For example, by installing a
consistent backup as fallback and then restart the system.
An alternative is to pick a <c>MasterNode</c> from
- <seealso marker="mnesia#system_info/1">mnesia:system_info(db_nodes)</seealso>
+ <seemfa marker="mnesia#system_info/1">mnesia:system_info(db_nodes)</seemfa>
and invoke
- <seealso marker="mnesia#set_master_nodes/1">mnesia:set_master_node([MasterNode])</seealso>.
+ <seemfa marker="mnesia#set_master_nodes/1">mnesia:set_master_node([MasterNode])</seemfa>.
By default an error is reported to <c>error_logger</c>.
</item>
<tag><c>{mnesia_fatal, Format, Args, BinaryCore}</c></tag>
@@ -998,7 +998,7 @@ ok
</item>
<tag><c>{mnesia_user, Event}</c></tag>
<item>An application started the function
- <seealso marker="mnesia#report_event/1">mnesia:report_event(Event)</seealso>.
+ <seemfa marker="mnesia#report_event/1">mnesia:report_event(Event)</seemfa>.
<c>Event</c> can be
any Erlang data structure. When tracing a system of
<c>Mnesia</c> applications, it is useful to be able to
@@ -1058,7 +1058,7 @@ ok
</item>
<tag><c>{delete_object, OldRecord, ActivityId}</c></tag>
<item>A record has possibly been deleted with
- <seealso marker="mnesia#delete_object/1">mnesia:delete_object/1</seealso>.
+ <seemfa marker="mnesia#delete_object/1">mnesia:delete_object/1</seemfa>.
<c>OldRecord</c>
contains the value of the old record, as stated as argument
by the application. Notice that other records with the same
@@ -1111,7 +1111,7 @@ ok
and table load mechanisms work. Another source of
confusion can be the semantics of nested transactions.</p>
<p>The debug level of <c>Mnesia</c> is set by calling the function
- <seealso marker="mnesia#set_debug_level/1">mnesia:set_debug_level(Level)</seealso>,
+ <seemfa marker="mnesia#set_debug_level/1">mnesia:set_debug_level(Level)</seemfa>,
where <c>Level</c>is one of the following:</p>
<taglist>
<tag><c>none</c></tag>
@@ -1122,7 +1122,7 @@ ok
events generate <c>{mnesia_info, Format, Args}</c>
system events. Processes can subscribe to these events with
the function
- <seealso marker="mnesia#subscribe/1">mnesia:subscribe/1</seealso>.
+ <seemfa marker="mnesia#subscribe/1">mnesia:subscribe/1</seemfa>.
The events are always sent to the <c>Mnesia</c> event handler.
</item>
<tag><c>debug</c></tag>
@@ -1184,7 +1184,7 @@ ok
table and perform write operations to the table at the same
time. This is important for many applications that require
continuously available services. For more information, see
- <seealso marker="Mnesia_chap4#trans_prop">Transactions and Other Access Contexts</seealso>.
+ <seeguide marker="Mnesia_chap4#trans_prop">Transactions and Other Access Contexts</seeguide>.
</item>
</list>
</section>
@@ -1203,14 +1203,14 @@ ok
files.</p>
<list type="bulleted">
<item>
- <seealso marker="mnesia#load_textfile/1">mnesia:load_textfile(Filename)</seealso>
+ <seemfa marker="mnesia#load_textfile/1">mnesia:load_textfile(Filename)</seemfa>
loads a series of local table definitions and data found in the
file into <c>Mnesia</c>. This function also starts <c>Mnesia</c>
and possibly creates a new schema. The function operates
on the local node only.
</item>
<item>
- <seealso marker="mnesia#dump_to_textfile/1">mnesia:dump_to_textfile(Filename)</seealso>
+ <seemfa marker="mnesia#dump_to_textfile/1">mnesia:dump_to_textfile(Filename)</seemfa>
dumps all local
tables of a <c>Mnesia</c> system into a text file, which
can be edited (with a normal text editor) and later reloaded.
@@ -1229,7 +1229,7 @@ ok
{Typename, Attribute1, Attribute2 ....}.</pre>
<p><c>Options</c> is a list of <c>{Key,Value}</c> tuples conforming
to the options that you can give to
- <seealso marker="mnesia#create_table/2">mnesia:create_table/2</seealso>.
+ <seemfa marker="mnesia#create_table/2">mnesia:create_table/2</seemfa>.
</p>
<p>For example, to start playing with a small database for healthy
foods, enter the following data into file <c>FRUITS</c>:</p>
@@ -1279,7 +1279,7 @@ ok
<section>
<title>Object-Based Programming with Mnesia</title>
<p>The <c>Company</c> database, introduced in
- <seealso marker="Mnesia_chap2#getting_started">Getting Started</seealso>,
+ <seeguide marker="Mnesia_chap2#getting_started">Getting Started</seeguide>,
has three tables that store records (<c>employee</c>,
<c>dept</c>, <c>project</c>), and three tables that store
relationships (<c>manager</c>, <c>at_dep</c>, <c>in_proj</c>).
diff --git a/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc
index 451a10306e..ef5e136446 100644
--- a/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc
@@ -51,13 +51,13 @@
<p>The following two functions can be used to retrieve system
information. For details, see the Reference Manual.</p>
<list type="bulleted">
- <item><seealso marker="mnesia#table_info/2">mnesia:table_info(Tab, Key)
- -> Info | exit({aborted,Reason})</seealso>
+ <item><seemfa marker="mnesia#table_info/2">mnesia:table_info(Tab, Key)
+ -> Info | exit({aborted,Reason})</seemfa>
returns information about one table, for example,
the current size of the table and on which nodes it resides.
</item>
- <item><seealso marker="mnesia#system_info/1">mnesia:system_info(Key)
- -> Info | exit({aborted, Reason})</seealso>
+ <item><seemfa marker="mnesia#system_info/1">mnesia:system_info(Key)
+ -> Info | exit({aborted, Reason})</seemfa>
returns information about the <c>Mnesia</c> system,
for example, transaction statistics, <c>db_nodes</c>, and
configuration parameters.
@@ -81,7 +81,7 @@
memory only. However, these tables can be dumped to
disc, either at regular intervals or before the system is
shut down. The function
- <seealso marker="mnesia#dump_tables/1">mnesia:dump_tables(TabList)</seealso>
+ <seemfa marker="mnesia#dump_tables/1">mnesia:dump_tables(TabList)</seemfa>
dumps all replicas of a set of RAM tables to disc. The tables can be
accessed while being dumped to disc. To dump the tables to disc,
all replicas must have the storage type <c>ram_copies</c>.</p>
@@ -119,7 +119,7 @@
checkpoint survives as long as there is at least one active
checkpoint retainer attached to each table.</p>
<p>Checkpoints can be explicitly deactivated with the function
- <seealso marker="mnesia#deactivate_checkpoint/1">mnesia:deactivate_checkpoint(Name)</seealso>,
+ <seemfa marker="mnesia#deactivate_checkpoint/1">mnesia:deactivate_checkpoint(Name)</seemfa>,
where <c>Name</c> is
the name of an active checkpoint. This function returns
<c>ok</c> if successful or <c>{error, Reason}</c> if there is
@@ -132,7 +132,7 @@
degree of checkpoint retainer redundancy.</p>
<marker id="mnesia:chkpt(Args)"></marker>
<p>Checkpoints are activated with the function
- <seealso marker="mnesia#activate_checkpoint/1">mnesia:activate_checkpoint(Args)</seealso>,
+ <seemfa marker="mnesia#activate_checkpoint/1">mnesia:activate_checkpoint(Args)</seemfa>,
where <c>Args</c> is a list of the following tuples:</p>
<list type="bulleted">
<item><c>{name,Name}</c>, where <c>Name</c> specifies a temporary
@@ -148,7 +148,7 @@
updated by the applications. The checkpoint is more fault
tolerant if the tables have several replicas. When new
replicas are added by the schema manipulation function
- <seealso marker="mnesia#add_table_copy/3">mnesia:add_table_copy/3</seealso>
+ <seemfa marker="mnesia#add_table_copy/3">mnesia:add_table_copy/3</seemfa>
it also attaches a local checkpoint retainer.
</item>
<item><c>{min,MinTabs}</c>, where <c>MinTabs</c> is a list of
@@ -174,7 +174,7 @@
loaded on startup. Default is <c>false</c>.</item>
</list>
<p>The function
- <seealso marker="mnesia#activate_checkpoint/1">mnesia:activate_checkpoint(Args)</seealso>
+ <seemfa marker="mnesia#activate_checkpoint/1">mnesia:activate_checkpoint(Args)</seemfa>
returns one of the following values:</p>
<list type="bulleted">
<item><c>{ok, Name, Nodes}</c></item>
@@ -185,9 +185,9 @@
<p>A list of active checkpoints can be obtained with the following
functions:</p>
<list type="bulleted">
- <item><seealso marker="mnesia#system_info/1">mnesia:system_info(checkpoints)</seealso>
+ <item><seemfa marker="mnesia#system_info/1">mnesia:system_info(checkpoints)</seemfa>
returns all active checkpoints on the current node.</item>
- <item><seealso marker="mnesia#table_info/2">mnesia:table_info(Tab, checkpoints)</seealso>
+ <item><seemfa marker="mnesia#table_info/2">mnesia:table_info(Tab, checkpoints)</seemfa>
returns active checkpoints on a specific table.</item>
</list>
</section>
@@ -200,7 +200,7 @@
<section>
<title>Startup Files</title>
- <p><seealso marker="Mnesia_chap3#start_mnesia">Start Mnesia</seealso>
+ <p><seeguide marker="Mnesia_chap3#start_mnesia">Start Mnesia</seeguide>
states the following prerequisites
for starting <c>Mnesia</c>:</p>
<list type="bulleted">
@@ -208,7 +208,7 @@
directory must be specified for the database.
</item>
<item>A database schema must be initiated, using the function
- <seealso marker="mnesia#create_schema/1">mnesia:create_schema/1</seealso>.
+ <seemfa marker="mnesia#create_schema/1">mnesia:create_schema/1</seemfa>.
</item>
</list>
<p>The following example shows how these tasks are performed:</p>
@@ -233,7 +233,7 @@ Suspended</pre>
<p>The response shows that the file <c>FALLBACK.BUP</c> has
been created. This is called a backup file, and it contains
an initial schema. If more than one node in the function
- <seealso marker="mnesia#create_schema/1">mnesia:create_schema/1</seealso>
+ <seemfa marker="mnesia#create_schema/1">mnesia:create_schema/1</seemfa>
had been specified, identical
backup files would have been created on all nodes.</p>
<p><em>Step 3:</em> Start <c>Mnesia</c>:</p>
@@ -280,7 +280,7 @@ ok</pre>
The log format can vary with different implementations of
<c>Mnesia</c>. The <c>Mnesia</c> log is currently implemented
in the standard library module
- <seealso marker="kernel:disk_log">disk_log</seealso> in
+ <seeerl marker="kernel:disk_log">disk_log</seeerl> in
<c>Kernel</c>.</p>
<p>The log file grows continuously and must be dumped at
regular intervals. "Dumping the log file" means that <c>Mnesia</c>
@@ -318,7 +318,7 @@ ok</pre>
are used for the schema and for <c>disc_only_copies</c>
tables. The <c>Mnesia</c> data files are currently implemented
in the standard library module
- <seealso marker="stdlib:dets">dets</seealso> in
+ <seeerl marker="stdlib:dets">dets</seeerl> in
<c>STDLIB</c>.</p>
<p>All operations that can be performed on <c>dets</c> files
can also be performed on the <c>Mnesia</c> data files. For
@@ -380,12 +380,12 @@ dets:close(N).</pre>
and you must wait for the table to be loaded on one of these
nodes before receiving a fresh copy of the table.</p>
<p>Before an application makes its first access to a table,
- <seealso marker="mnesia#wait_for_tables/2">mnesia:wait_for_tables(TabList, Timeout)</seealso>
+ <seemfa marker="mnesia#wait_for_tables/2">mnesia:wait_for_tables(TabList, Timeout)</seemfa>
is to be executed
to ensure that the table is accessible from the local node. If
the function times out, the application can choose to force a
load of the local replica with
- <seealso marker="mnesia#force_load_table/1">mnesia:force_load_table(Tab)</seealso>
+ <seemfa marker="mnesia#force_load_table/1">mnesia:force_load_table(Tab)</seemfa>
and deliberately lose all
updates that can have been performed on the other nodes while
the local node was down. If <c>Mnesia</c>
@@ -394,7 +394,7 @@ dets:close(N).</pre>
avoid unnecessary inconsistency.</p>
<warning>
<p>Only one table is loaded by
- <seealso marker="mnesia#force_load_table/1">mnesia:force_load_table(Tab)</seealso>.
+ <seemfa marker="mnesia#force_load_table/1">mnesia:force_load_table(Tab)</seemfa>.
Since committed
transactions can have caused updates in several tables, the
tables can become inconsistent because of the forced load.</p>
@@ -402,8 +402,8 @@ dets:close(N).</pre>
<p>The allowed <c>AccessMode</c> of a table can be defined to be
<c>read_only</c> or <c>read_write</c>. It can be toggled with
the function
- <seealso marker="mnesia#change_table_access_mode/2">
- mnesia:change_table_access_mode(Tab, AccessMode)</seealso>
+ <seemfa marker="mnesia#change_table_access_mode/2">
+ mnesia:change_table_access_mode(Tab, AccessMode)</seemfa>
in runtime. <c>read_only</c> tables and
<c>local_content</c> tables are always loaded locally, as
there is no need for copying the table from other nodes. Other
@@ -427,8 +427,8 @@ dets:close(N).</pre>
is used. However, the load order
can be affected, by explicitly changing property
<c>load_order</c> for the tables, with the function
- <seealso marker="mnesia#change_table_load_order/2">
- mnesia:change_table_load_order(Tab, LoadOrder)</seealso>.
+ <seemfa marker="mnesia#change_table_load_order/2">
+ mnesia:change_table_load_order(Tab, LoadOrder)</seemfa>.
<c>LoadOrder</c> is by default <c>0</c> for all tables, but
it can be set to any integer. The table with the highest
<c>load_order</c> is loaded first. Changing the load order is
@@ -464,7 +464,7 @@ dets:close(N).</pre>
<p>If the application detects that there has been a communication
failure that can have caused an inconsistent database, it can
use the function
- <seealso marker="mnesia#set_master_nodes/2">mnesia:set_master_nodes(Tab, Nodes)</seealso>
+ <seemfa marker="mnesia#set_master_nodes/2">mnesia:set_master_nodes(Tab, Nodes)</seemfa>
to pinpoint from which nodes each table can be loaded.</p>
<p>At startup, the <c>Mnesia</c> normal table load algorithm is
bypassed and the table is loaded from one of the master
@@ -475,11 +475,11 @@ dets:close(N).</pre>
table is reset and the normal load mechanism is used at the
next restart.</p>
<p>The function
- <seealso marker="mnesia#set_master_nodes/1">mnesia:set_master_nodes(Nodes)</seealso>
+ <seemfa marker="mnesia#set_master_nodes/1">mnesia:set_master_nodes(Nodes)</seemfa>
sets master
nodes for all tables. For each table it determines its replica
nodes and starts
- <seealso marker="mnesia#set_master_nodes/2">mnesia:set_master_nodes(Tab, TabNodes)</seealso>
+ <seemfa marker="mnesia#set_master_nodes/2">mnesia:set_master_nodes(Tab, TabNodes)</seemfa>
with those replica nodes that are included in the <c>Nodes</c>
list (that is, <c>TabNodes</c> is the intersection of
<c>Nodes</c> and the replica nodes of the table). If the
@@ -487,9 +487,9 @@ dets:close(N).</pre>
the particular table is reset and the normal load mechanism
is used at the next restart.</p>
<p>The functions
- <seealso marker="mnesia#system_info/1">mnesia:system_info(master_node_tables)</seealso>
+ <seemfa marker="mnesia#system_info/1">mnesia:system_info(master_node_tables)</seemfa>
and
- <seealso marker="mnesia#table_info/2">mnesia:table_info(Tab, master_nodes)</seealso>
+ <seemfa marker="mnesia#table_info/2">mnesia:table_info(Tab, master_nodes)</seemfa>
can be used to
obtain information about the potential master nodes.</p>
<p>Determining what data to keep after a communication failure
@@ -501,7 +501,7 @@ dets:close(N).</pre>
reduction in service on the minority nodes. This would be a
tradeoff in favor of higher consistency guarantees.</p>
<p>The function
- <seealso marker="mnesia#force_load_table/1">mnesia:force_load_table(Tab)</seealso>
+ <seemfa marker="mnesia#force_load_table/1">mnesia:force_load_table(Tab)</seemfa>
can be used to force load the table regardless of which table
load mechanism that is activated.</p>
</section>
@@ -613,33 +613,33 @@ dets:close(N).</pre>
a backup as fallback, and for disaster recovery:</p>
<list type="bulleted">
<item>
- <seealso marker="mnesia#backup_checkpoint/2">mnesia:backup_checkpoint(Name, Opaque, [Mod])</seealso>
+ <seemfa marker="mnesia#backup_checkpoint/2">mnesia:backup_checkpoint(Name, Opaque, [Mod])</seemfa>
performs a backup of the tables included in the checkpoint.
</item>
<item>
- <seealso marker="mnesia#backup/1">mnesia:backup(Opaque, [Mod])</seealso>
+ <seemfa marker="mnesia#backup/1">mnesia:backup(Opaque, [Mod])</seemfa>
activates a new
checkpoint that covers all <c>Mnesia</c> tables and
performs a backup. It is performed with maximum degree of
redundancy (see also the function
- <seealso marker="#checkpoints">mnesia:activate_checkpoint(Args)</seealso>,
+ <seeguide marker="#checkpoints">mnesia:activate_checkpoint(Args)</seeguide>,
<c>{max, MaxTabs} and {min, MinTabs})</c>.
</item>
<item>
- <seealso marker="mnesia#traverse_backup/4">mnesia:traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc)</seealso>
+ <seemfa marker="mnesia#traverse_backup/4">mnesia:traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc)</seemfa>
can be used to read an existing backup, create a backup from an
existing one, or to copy a backup from one type media to another.
</item>
<item>
- <seealso marker="mnesia#uninstall_fallback/0">mnesia:uninstall_fallback()</seealso>
+ <seemfa marker="mnesia#uninstall_fallback/0">mnesia:uninstall_fallback()</seemfa>
removes previously installed fallback files.
</item>
<item>
- <seealso marker="mnesia#restore/2">mnesia:restore(Opaque, Args)</seealso>
+ <seemfa marker="mnesia#restore/2">mnesia:restore(Opaque, Args)</seemfa>
restores a set of tables from a previous backup.
</item>
<item>
- <seealso marker="mnesia#install_fallback/1">mnesia:install_fallback(Opaque, [Mod])</seealso>
+ <seemfa marker="mnesia#install_fallback/1">mnesia:install_fallback(Opaque, [Mod])</seemfa>
can be configured to restart <c>Mnesia</c> and the reload data
tables, and possibly the schema tables, from an existing
backup. This function is typically used for disaster recovery
@@ -647,7 +647,7 @@ dets:close(N).</pre>
</item>
</list>
<p>These functions are explained in the following sections.
- See also <seealso marker="#checkpoints">Checkpoints</seealso>,
+ See also <seeguide marker="#checkpoints">Checkpoints</seeguide>,
which describes the two functions used
to activate and deactivate checkpoints.</p>
@@ -656,13 +656,13 @@ dets:close(N).</pre>
<p>Backup operation are performed with the following functions:</p>
<list type="bulleted">
<item>
- <seealso marker="mnesia#backup_checkpoint/2">mnesia:backup_checkpoint(Name, Opaque, [Mod])</seealso>
+ <seemfa marker="mnesia#backup_checkpoint/2">mnesia:backup_checkpoint(Name, Opaque, [Mod])</seemfa>
</item>
<item>
- <seealso marker="mnesia#backup/1">mnesia:backup(Opaque, [Mod])</seealso>
+ <seemfa marker="mnesia#backup/1">mnesia:backup(Opaque, [Mod])</seemfa>
</item>
<item>
- <seealso marker="mnesia#traverse_backup/4">mnesia:traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc)</seealso>
+ <seemfa marker="mnesia#traverse_backup/4">mnesia:traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc)</seemfa>
</item>
</list>
<p>By default, the actual access to the backup media is
@@ -680,14 +680,14 @@ dets:close(N).</pre>
for this purpose.</p>
<p>The source for a backup is an activated checkpoint.
The backup function
- <seealso marker="mnesia#backup_checkpoint/2">mnesia:backup_checkpoint(Name, Opaque,[Mod])</seealso>
+ <seemfa marker="mnesia#backup_checkpoint/2">mnesia:backup_checkpoint(Name, Opaque,[Mod])</seemfa>
is most commonly used and returns <c>ok</c> or
<c>{error,Reason}</c>. It has the following arguments:</p>
<list type="bulleted">
<item><c>Name</c> is the name of an activated checkpoint.
For details on how to include table names in checkpoints,
see the function <c>mnesia:activate_checkpoint(ArgList)</c>
- in <seealso marker="#checkpoints">Checkpoints</seealso>.
+ in <seeguide marker="#checkpoints">Checkpoints</seeguide>.
</item>
<item><c>Opaque</c>. <c>Mnesia</c> does not interpret this
argument, but it is forwarded to the backup module. The
@@ -698,7 +698,7 @@ dets:close(N).</pre>
</item>
</list>
<p>The function
- <seealso marker="mnesia#backup/1">mnesia:backup(Opaque [,Mod])</seealso>
+ <seemfa marker="mnesia#backup/1">mnesia:backup(Opaque [,Mod])</seemfa>
activates a
new checkpoint that covers all <c>Mnesia</c> tables with
maximum degree of redundancy and performs a backup. Maximum
@@ -707,7 +707,7 @@ dets:close(N).</pre>
backed up as they look on the current node.</p>
<p>You can iterate over a backup, either to transform it
into a new backup, or only read it. The function
- <seealso marker="mnesia#traverse_backup/4">mnesia:traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc)</seealso>,
+ <seemfa marker="mnesia#traverse_backup/4">mnesia:traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc)</seemfa>,
which normally returns <c>{ok, LastAcc}</c>,
is used for both of these purposes.</p>
<p>Before the traversal starts, the source backup media is
@@ -748,7 +748,7 @@ dets:close(N).</pre>
</item>
<item><c>{schema, Tab, CreateList}</c> specifies a table to be
created. For more information about <c>CreateList</c>, see
- <seealso marker="mnesia#create_table/2">mnesia:create_table/2</seealso>.
+ <seemfa marker="mnesia#create_table/2">mnesia:create_table/2</seemfa>.
</item>
<item><c>{Tab, Key}</c> specifies the full identity of a record
to be deleted.
@@ -769,7 +769,7 @@ dets:close(N).</pre>
the backup. Each node where the schema table resides is
regarded as a <c>db_node</c>.</p>
<p>The following example shows how
- <seealso marker="mnesia#traverse_backup/4">mnesia:traverse_backup</seealso>
+ <seemfa marker="mnesia#traverse_backup/4">mnesia:traverse_backup</seemfa>
can be used to rename a <c>db_node</c> in a backup file:</p>
<codeinclude file="bup.erl" tag="%0" type="erl"></codeinclude>
</section>
@@ -779,7 +779,7 @@ dets:close(N).</pre>
<p>Tables can be restored online from a backup without
restarting <c>Mnesia</c>. A restore is performed with the
function
- <seealso marker="mnesia#restore/2">mnesia:restore(Opaque, Args)</seealso>,
+ <seemfa marker="mnesia#restore/2">mnesia:restore(Opaque, Args)</seemfa>,
where <c>Args</c> can contain the following tuples:</p>
<list type="bulleted">
<item><c>{module,Mod}</c>. The backup module <c>Mod</c> is
@@ -835,7 +835,7 @@ dets:close(N).</pre>
<section>
<title>Fallback</title>
<p>The function
- <seealso marker="mnesia#install_fallback/2">mnesia:install_fallback(Opaque, [Mod])</seealso>
+ <seemfa marker="mnesia#install_fallback/2">mnesia:install_fallback(Opaque, [Mod])</seemfa>
installs a backup as fallback. It uses the backup module
<c>Mod</c>, or the default backup module, to access the backup
media. The function returns <c>ok</c> if successful, or
@@ -858,7 +858,7 @@ dets:close(N).</pre>
on all <c>db_nodes</c> to restore the old database. The
fallback is automatically deinstalled after a successful
startup. The function
- <seealso marker="mnesia#uninstall_fallback/0">mnesia:uninstall_fallback()</seealso>
+ <seemfa marker="mnesia#uninstall_fallback/0">mnesia:uninstall_fallback()</seemfa>
can also be used to deinstall the fallback after a
successful system upgrade. Again, this is a distributed
operation that is either performed on all <c>db_nodes</c> or
@@ -889,7 +889,7 @@ dets:close(N).</pre>
<p>Configuration parameter
<c><![CDATA[-mnesia dump_log_update_in_place <bool>]]></c>
controls the safety level of the function
- <seealso marker="mnesia#dump_log/0">mnesia:dump_log()</seealso>
+ <seemfa marker="mnesia#dump_log/0">mnesia:dump_log()</seemfa>
By default, <c>Mnesia</c> dumps the
transaction log directly into the <c>DAT</c> files. If a power
failure occurs during the dump, this can cause the randomly
diff --git a/lib/mnesia/doc/src/Mnesia_overview.xml b/lib/mnesia/doc/src/Mnesia_overview.xml
index 63f2309284..a021181299 100644
--- a/lib/mnesia/doc/src/Mnesia_overview.xml
+++ b/lib/mnesia/doc/src/Mnesia_overview.xml
@@ -22,7 +22,7 @@
</legalnotice>
- <title>Mnesia</title>
+ <title>Overview</title>
<prepared>Claes Wikstr&ouml;m, Hans Nilsson and H&aring;kan Mattsson</prepared>
<responsible>Bjarne D&auml;cker</responsible>
<docno></docno>
@@ -33,36 +33,33 @@
<file>Mnesia_overview.xml</file>
</header>
- <p>The management of data in telecommunications system has many
- aspects, thereof some, but not all, are addressed by traditional
- commercial Database Management Systems (DBMSs). In particular the
- high level of fault tolerance that is required in many nonstop
+ <p>The management of data in telecommunications systems has many
+ aspects of which some, but not all, are addressed by traditional
+ Database Management Systems (DBMSs). In particular, the
+ high level of fault tolerance required in many nonstop
systems, combined with requirements on the DBMS to run in the same
- address space as the application, have led us to implement a new
+ address space as the applications, have led us to implement a new
DBMS, called Mnesia.</p>
- <p>Mnesia is implemented in, and tightly connected to Erlang.
+ <p>Mnesia is implemented in, and tightly coupled to Erlang.
It provides the functionality that is necessary for the
- implementation of fault tolerant telecommunications systems.</p>
- <p>Mnesia is a multiuser distributed DBMS specially made for
- industrial telecommunications applications written in Erlang,
+ implementation of fault-tolerant telecommunications systems.</p>
+ <p>Mnesia is a multiuser distributed DBMS specifically designed for
+ industrial-grade telecommunications applications written in Erlang,
which is also the intended target language.
- Mnesia tries to address all the data
- management issues required for typical telecommunications systems.
- It has a number of features that are not normally found in traditional
- databases.</p>
- <p>In telecommunications applications, there are different needs
- from the features provided by traditional DBMSs. The applications now
- implemented in Erlang need a mixture of a broad range
- of features, which generally are not satisfied by traditional DBMSs.
- Mnesia is designed with requirements like the following in
- mind:</p>
- <list type="ordered">
- <item>Fast real-time key/value lookup
+ Mnesia tries to address all the data management issues required for
+ typical telecommunications systems and has a number of features not
+ normally found in traditional DBMSs.</p>
+ <p>Telecommunications applications need a mix of a broad range
+ of features generally not provided by traditional DBMSs.
+ Mnesia is designed to meet requirements such as:</p>
+ <list type="bulleted">
+ <item>Fast real-time key-value lookup
</item>
- <item>Complicated non-real-time queries mainly for
- operation and maintenance
+ <item>Complex non-real-time queries
+ (mainly for operation and maintenance tasks)
</item>
- <item>Distributed data because of distributed applications
+ <item>Distributed data
+ (due to the distributed nature of the applications)
</item>
<item>High fault tolerance
</item>
@@ -71,34 +68,33 @@
<item>Complex objects
</item>
</list>
- <p>Mnesia is designed with the typical data management problems
- of telecommunications applications in mind. This sets Mnesia
- apart from most other DBMS. Hence Mnesia
- combines many concepts found in traditional databases such as
- transactions and queries with concepts found in data management
- systems for telecommunications applications, for example:</p>
+ <p>Mnesia addresses the typical data management issues required for
+ telecommunications applications which sets it apart from most other DBMSs.
+ It combines many concepts found in traditional DBMSs, such as transactions
+ and queries, with concepts found in data management systems for
+ telecommunications applications such as:</p>
<list type="bulleted">
<item>Fast real-time operations
</item>
- <item>Configurable degree of fault tolerance (by replication)
+ <item>Configurable replication for fault tolerance
</item>
- <item>The ability to reconfigure the system without stopping or
- suspending it.
+ <item>Dynamic reconfiguration without service disruption
</item>
</list>
- <p>Mnesia is also interesting because of its tight coupling to
- Erlang, thus almost turning Erlang into a database programming
- language. This has many benefits, the foremost is that
+ <p>Mnesia is also unique due to its tight coupling to
+ Erlang. It almost turns Erlang into a database programming
+ language, which yields many benefits. The foremost is that
the impedance mismatch between the data format used by the DBMS
and the data format used by the programming language, which is used
to manipulate the data, completely disappears.</p>
<section>
- <title>Mnesia Database Management System (DBMS)</title>
+ <title>The Mnesia Database Management System</title>
<section>
<title>Features</title>
- <p>Mnesia contains the following features that combine to
- produce a fault-tolerant, distributed DBMS written in Erlang:
+ <p>Mnesia has the following features that combine to
+ produce a fault-tolerant distributed database management
+ system (DBMS) written in Erlang:
</p>
<list type="bulleted">
<item>Database schema can be dynamically reconfigured at runtime.
@@ -107,85 +103,85 @@
replication, and persistence.
</item>
<item>Tables can be moved or replicated to several nodes to improve
- fault tolerance. The rest of the system can still access the tables
- to read, write, and delete records.
+ fault tolerance. Other nodes in the system can still access the
+ tables to read, write, and delete records.
</item>
<item>Table locations are transparent to the programmer.
Programs address table names and the system itself keeps track of
table locations.
</item>
- <item>Database transactions can be distributed, and many
- functions can be called within one transaction.
+ <item>Transactions can be distributed and multiple
+ operations can be executed within a single transaction.
</item>
- <item>Several transactions can run concurrently, and their execution
- is fully synchronized by the DBMS. Mnesia ensures that no
- two processes manipulate data simultaneously.
+ <item>Multiple transactions can run concurrently and their execution
+ is fully synchronized by Mnesia, ensuring that no
+ two processes manipulate the same data simultaneously.
</item>
<item>Transactions can be assigned the property of being executed on
- all nodes in the system, or on none. Transactions can also be
- bypassed in favor of running "dirty operations", which reduce
- overheads and run fast.
+ all nodes in the system, or on none.
+ </item>
+ <item>Transactions can be bypassed using dirty operations,
+ which reduce overheads and run fast.
</item>
</list>
- <p>Details of these features are described in the following sections.</p>
+ <p>All of the above features are described in detail
+ in the coming sections.</p>
</section>
<section>
- <title>Add-On Application</title>
+ <title>Query List Comprehension</title>
<p>Query List Comprehension (QLC) can be used with Mnesia
- to produce specialized functions that enhance the operational
- ability of Mnesia. QLC has its own documentation as part
- of the OTP documentation set. The main features of QLC
- when used with Mnesia are as follows:</p>
+ to produce specialized functions that enhance its operational
+ ability. QLC has its own documentation as part
+ of the OTP documentation set. The main QLC advantages
+ when used with Mnesia are:</p>
<list type="bulleted">
- <item>QLC can optimize the query compiler for the Mnesia
- DBMS, essentially making the DBMS more efficient.
+ <item>QLC can optimize the query compiler for Mnesia,
+ essentially making the system more efficient.
</item>
<item>QLC can be used as a database programming
- language for Mnesia. It includes a notation called "list
- comprehensions" and can be used to make complex database
+ language for Mnesia. It includes a notation called list
+ comprehensions which can be used to execute complex database
queries over a set of tables.
</item>
</list>
- <p>For information about QLC, see the
- <seealso marker="stdlib:qlc">qlc</seealso> manual page
+ <p>For more information about QLC, please see the
+ <seeerl marker="stdlib:qlc">qlc</seeerl> manual page
in STDLIB.</p>
</section>
<section>
<title>When to Use Mnesia</title>
- <p>Use Mnesia with the following types of applications:</p>
+ <p>Mnesia is a great fit for applications that:</p>
<list type="bulleted">
- <item>Applications that need to replicate data.
+ <item>Need to replicate data.
</item>
- <item>Applications that perform complicated searches on data.
+ <item>Perform complex data queries.
</item>
- <item>Applications that need to use atomic transactions to
- update several records simultaneously.
+ <item>Need to use atomic transactions to
+ safely update several records simultaneously.
</item>
- <item>Applications that use soft real-time characteristics.
+ <item>Require soft real-time characteristics.
</item>
</list>
- <p>Mnesia is not as appropriate with the
- following types of applications:</p>
+ <p>Mnesia is not as appropriate for applications that:</p>
<list type="bulleted">
- <item>Programs that process plain text or binary data files.
+ <item>Process plain text or binary data files.
</item>
- <item>Applications that merely need a look-up dictionary that
- can be stored to disc. Those applications use the standard
+ <item>Merely need a lookup dictionary that
+ can be stored on disc. Such applications may use the standard
library module <c>dets</c>, which is a disc-based version
- of the module <c>ets</c>. For information about <c>dets</c>,
- see the <seealso marker="stdlib:dets">dets</seealso>
+ of the <c>ets</c> module. For more information about <c>dets</c>,
+ please see the <seeerl marker="stdlib:dets">dets</seeerl>
manual page in STDLIB.
</item>
- <item>Applications that need disc logging facilities.
- Those applications can
- use the module <c>disk_log</c> by preference. For
- information about <c>disk_log</c>, see the
- <seealso marker="kernel:disk_log">disk_log</seealso>
+ <item>Need disc logging facilities.
+ Such applications may use the module <c>disk_log</c>.
+ For more information about <c>disk_log</c>, please see the
+ <seeerl marker="kernel:disk_log">disk_log</seeerl>
manual page in Kernel.
</item>
- <item>Hard real-time systems.
+ <item>Require hard real-time characteristics.
</item>
</list>
</section>
diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml
index 44721e730d..9be51a27b3 100644
--- a/lib/mnesia/doc/src/mnesia.xml
+++ b/lib/mnesia/doc/src/mnesia.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -68,7 +68,7 @@
<p>All functions in this Reference Manual can be used in any
combination with queries using the list comprehension notation.
For information about the query notation, see the
- <seealso marker="stdlib:qlc">qlc</seealso>
+ <seeerl marker="stdlib:qlc">qlc</seeerl>
manual page in STDLIB.</p>
<p>Data in Mnesia is organized as a set of tables. Each table
has a name that must be an atom. Each table is made up of
@@ -181,9 +181,67 @@
transaction before iterating over the table.</p>
</description>
+ <datatypes>
+ <datatype>
+ <name name="table"/>
+ </datatype>
+ <datatype>
+ <name name="activity"/>
+ </datatype>
+ <datatype>
+ <name name="create_option"/>
+ </datatype>
+ <datatype>
+ <name name="storage_type"/>
+ </datatype>
+ <datatype>
+ <name name="t_result" n_vars="1"/>
+ </datatype>
+ <datatype>
+ <name name="result"/>
+ </datatype>
+ <datatype>
+ <name name="index_attr"/>
+ </datatype>
+ <datatype>
+ <name name="write_locks"/>
+ </datatype>
+ <datatype>
+ <name name="read_locks"/>
+ </datatype>
+ <datatype>
+ <name name="lock_kind"/>
+ </datatype>
+ <datatype>
+ <name name="select_continuation"/>
+ </datatype>
+ <datatype>
+ <name name="snmp_struct"/>
+ </datatype>
+ <datatype>
+ <name name="snmp_type"/>
+ </datatype>
+ <datatype>
+ <name name="tuple_of" n_vars="1"/>
+ </datatype>
+ <datatype>
+ <name name="config_key"/>
+ </datatype>
+ <datatype>
+ <name name="config_value"/>
+ </datatype>
+ <datatype>
+ <name name="config_result"/>
+ </datatype>
+ <datatype>
+ <name name="debug_level"/>
+ </datatype>
+
+ </datatypes>
+
<funcs>
<func>
- <name since="">abort(Reason) -> transaction abort</name>
+ <name name="abort" arity="1" since=""/>
<fsummary>Terminates the current transaction.</fsummary>
<desc>
<p>Makes the transaction silently
@@ -195,7 +253,7 @@
</desc>
</func>
<func>
- <name since="">activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}</name>
+ <name name="activate_checkpoint" arity="1" since=""/>
<fsummary>Activates a checkpoint.</fsummary>
<desc>
<marker id="activate_checkpoint"></marker>
@@ -259,7 +317,8 @@
</desc>
</func>
<func>
- <name since="">activity(AccessContext, Fun [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name name="activity" arity="2" since=""/>
+ <!--<name name="activity" arity="3" since=""/>-->
<fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary>
<desc>
<marker id="activity_2_3"></marker>
@@ -271,7 +330,7 @@
</desc>
</func>
<func>
- <name since="">activity(AccessContext, Fun, Args, AccessMod) -> ResultOfFun | exit(Reason)</name>
+ <name name="activity" arity="4" since=""/>
<fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary>
<desc>
<marker id="activity_4"></marker>
@@ -403,7 +462,7 @@
</desc>
</func>
<func>
- <name since="">add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}</name>
+ <name name="add_table_copy" arity="3" since=""/>
<fsummary>Copies a table to a remote node.</fsummary>
<desc>
<marker id="add_table_copy"></marker>
@@ -420,7 +479,7 @@ mnesia:add_table_copy(person, Node, disc_copies)</code>
</desc>
</func>
<func>
- <name since="">add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
+ <name name="add_table_index" arity="2" since=""/>
<fsummary>Creates an index for a table.</fsummary>
<desc>
<marker id="add_table_index"></marker>
@@ -441,7 +500,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">all_keys(Tab) -> KeyList | transaction abort</name>
+ <name name="all_keys" arity="1" since=""/>
<fsummary>Returns all keys in a table.</fsummary>
<desc>
<marker id="all_keys"></marker>
@@ -453,7 +512,8 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">async_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name name="async_dirty" arity="1" since=""/>
+ <name name="async_dirty" arity="2" since=""/>
<fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary>
<desc>
<marker id="async_dirty"></marker>
@@ -493,7 +553,8 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">backup(Opaque [, BackupMod]) -> ok | {error,Reason}</name>
+ <name name="backup" arity="1" since=""/>
+ <name name="backup" arity="2" since=""/>
<fsummary>Backs up all tables in the database.</fsummary>
<desc>
<marker id="backup"></marker>
@@ -505,7 +566,8 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}</name>
+ <name name="backup_checkpoint" arity="2" since=""/>
+ <name name="backup_checkpoint" arity="3" since=""/>
<fsummary>Backs up all tables in a checkpoint.</fsummary>
<desc>
<marker id="backup_checkpoint"></marker>
@@ -520,7 +582,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">change_config(Config, Value) -> {error, Reason} | {ok, ReturnValue}</name>
+ <name name="change_config" arity="2" since=""/>
<fsummary>Changes a configuration parameter.</fsummary>
<desc>
<marker id="change_config"></marker>
@@ -544,8 +606,8 @@ mnesia:add_table_index(person, age)</code>
<tag><c>dc_dump_limit</c></tag>
<item>
<p><c>Value</c> is a number. See the description in
- <seealso marker="#configuration_parameters">Section
- Configuration Parameters</seealso>. <c>ReturnValue</c>
+ <seeerl marker="#configuration_parameters">Section
+ Configuration Parameters</seeerl>. <c>ReturnValue</c>
is the new value. Notice that this configuration
parameter is not persistent. It is lost when
Mnesia has stopped.</p>
@@ -554,7 +616,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}</name>
+ <name name="change_table_access_mode" arity="2" since=""/>
<fsummary>Changes the access mode for the table.</fsummary>
<desc>
<marker id="change_table_access_mode"></marker>
@@ -568,7 +630,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name since="">change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}</name>
+ <name name="change_table_copy_type" arity="3" since=""/>
<fsummary>Changes the storage type of a table.</fsummary>
<desc>
<marker id="change_table_copy_type"></marker>
@@ -585,7 +647,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="">change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}</name>
+ <name name="change_table_load_order" arity="2" since=""/>
<fsummary>Changes the load order priority for the table.</fsummary>
<desc>
<marker id="change_table_load_order"></marker>
@@ -595,7 +657,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="OTP R14B03">change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name>
+ <name name="change_table_majority" arity="2" since="OTP R14B03"/>
<fsummary>Changes the majority check setting for the table.</fsummary>
<desc>
<p><c>Majority</c> must be a boolean. Default is <c>false</c>.
@@ -607,7 +669,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="">clear_table(Tab) -> {aborted, R} | {atomic, ok}</name>
+ <name name="clear_table" arity="1" since=""/>
<fsummary>Deletes all entries in a table.</fsummary>
<desc>
<marker id="clear_table"></marker>
@@ -615,7 +677,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="">create_schema(DiscNodes) -> ok | {error,Reason}</name>
+ <name name="create_schema" arity="1" since=""/>
<fsummary>Creates a new schema on the specified nodes.</fsummary>
<desc>
<marker id="create_schema"></marker>
@@ -637,7 +699,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name since="">create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name>
+ <name name="create_table" arity="2" since=""/>
<fsummary>Creates a Mnesia table called <c>Name</c>with properties as described by argument <c>TabDef</c>.</fsummary>
<desc>
<marker id="create_table"></marker>
@@ -799,7 +861,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">deactivate_checkpoint(Name) -> ok | {error, Reason}</name>
+ <name name="deactivate_checkpoint" arity="1" since=""/>
<fsummary>Deactivates a checkpoint.</fsummary>
<desc>
<marker id="deactivate_checkpoint"></marker>
@@ -811,7 +873,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}</name>
+ <name name="del_table_copy" arity="2" since=""/>
<fsummary>Deletes the replica of table <c>Tab</c> at node <c>Node</c>.</fsummary>
<desc>
<marker id="del_table_copy"></marker>
@@ -825,7 +887,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
+ <name name="del_table_index" arity="2" since=""/>
<fsummary>Deletes an index in a table.</fsummary>
<desc>
<marker id="del_table_index"></marker>
@@ -834,16 +896,16 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete({Tab, Key}) -> transaction abort | ok</name>
+ <name name="delete" arity="1" since=""/>
<fsummary>Deletes all records in table <c>Tab</c> with the key <c>Key</c>.</fsummary>
<desc>
- <marker id="delete_2"></marker>
+ <marker id="delete_1"></marker>
<p>Calls <c>mnesia:delete(Tab, Key, write)</c>.</p>
</desc>
</func>
<func>
- <name since="">delete(Tab, Key, LockKind) -> transaction abort | ok</name>
- <fsummary>Deletes all records in table <c>Tab</c>with the key <c>Key</c>.</fsummary>
+ <name name="delete" arity="3" since=""/>
+ <fsummary>Deletes all records in table <c>Tab</c> with the key <c>Key</c>.</fsummary>
<desc>
<marker id="delete_3"></marker>
<p>Deletes all records in table <c>Tab</c> with the key
@@ -857,7 +919,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete_object(Record) -> transaction abort | ok</name>
+ <name name="delete_object" arity="1" since=""/>
<fsummary>Delete a record.</fsummary>
<desc>
<marker id="delete_object_1"></marker>
@@ -866,7 +928,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete_object(Tab, Record, LockKind) -> transaction abort | ok</name>
+ <name name="delete_object" arity="3" since=""/>
<fsummary>Deletes a record.</fsummary>
<desc>
<marker id="delete_object_3"></marker>
@@ -883,7 +945,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete_schema(DiscNodes) -> ok | {error,Reason}</name>
+ <name name="delete_schema" arity="1" since=""/>
<fsummary>Deletes the schema on the given nodes.</fsummary>
<desc>
<marker id="delete_schema"></marker>
@@ -904,7 +966,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">delete_table(Tab) -> {aborted, Reason} | {atomic, ok}</name>
+ <name name="delete_table" arity="1" since=""/>
<fsummary>Deletes permanently all replicas of table <c>Tab</c>.</fsummary>
<desc>
<marker id="delete_table"></marker>
@@ -912,7 +974,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})</name>
+ <name name="dirty_all_keys" arity="1" since=""/>
<fsummary>Dirty search for all record keys in table.</fsummary>
<desc>
<marker id="delete_all_keys"></marker>
@@ -920,7 +982,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_delete({Tab, Key}) -> ok | exit({aborted, Reason})</name>
+ <name name="dirty_delete" arity="1" since=""/>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<marker id="dirty_delete"></marker>
@@ -928,14 +990,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})</name>
+ <name name="dirty_delete" arity="2" since=""/>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:delete/3</c>.</p>
</desc>
</func>
<func>
- <name since="">dirty_delete_object(Record)</name>
+ <name name="dirty_delete_object" arity="1" since=""/>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<marker id="dirty_delete_object_1"></marker>
@@ -944,18 +1006,18 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_delete_object(Tab, Record)</name>
+ <name name="dirty_delete_object" arity="2" since=""/>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:delete_object/3</c>.</p>
</desc>
</func>
<func>
- <name since="">dirty_first(Tab) -> Key | exit({aborted, Reason})</name>
+ <name name="dirty_first" arity="1" since=""/>
<fsummary>Returns the key for the first record in a table.</fsummary>
<desc>
<marker id="dirty_first"></marker>
- <p>Records in <c>set</c> or <c>bag</c> tables are not ordered.
+ <p>Records in <c>set</c> or <c>bag</c> tables are not ordered.
However, there is an ordering of the records that is unknown
to the user. Therefore, a table can be traversed by this
function with the function <c>mnesia:dirty_next/2</c>.
@@ -967,7 +1029,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_index_match_object(Pattern, Pos)</name>
+ <name name="dirty_index_match_object" arity="2" since=""/>
<fsummary>Dirty pattern match using index.</fsummary>
<desc>
<marker id="dirty_index_match_object_2"></marker>
@@ -977,7 +1039,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_index_match_object(Tab, Pattern, Pos)</name>
+ <name name="dirty_index_match_object" arity="3" since=""/>
<fsummary>Dirty pattern match using index.</fsummary>
<desc>
<p>Dirty equivalent of the function
@@ -985,7 +1047,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_index_read(Tab, SecondaryKey, Pos)</name>
+ <name name="dirty_index_read" arity="3" since=""/>
<fsummary>Dirty read using index.</fsummary>
<desc>
<marker id="dirty_index_read"></marker>
@@ -994,7 +1056,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_last(Tab) -> Key | exit({aborted, Reason})</name>
+ <name name="dirty_last" arity="1" since=""/>
<fsummary>Returns the key for the last record in a table.</fsummary>
<desc>
<marker id="dirty_last"></marker>
@@ -1006,7 +1068,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_match_object(Pattern) -> RecordList | exit({aborted, Reason})</name>
+ <name name="dirty_match_object" arity="1" since=""/>
<fsummary>Dirty pattern match pattern.</fsummary>
<desc>
<marker id="dirty_match_object_1"></marker>
@@ -1015,7 +1077,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})</name>
+ <name name="dirty_match_object" arity="2" since=""/>
<fsummary>Dirty pattern match pattern.</fsummary>
<desc>
<p>Dirty equivalent of the function
@@ -1023,7 +1085,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_next(Tab, Key) -> Key | exit({aborted, Reason})</name>
+ <name name="dirty_next" arity="2" since=""/>
<fsummary>Return the next key in a table.</fsummary>
<desc>
<marker id="dirty_next"></marker>
@@ -1038,7 +1100,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})</name>
+ <name name="dirty_prev" arity="2" since=""/>
<fsummary>Returns the previous key in a table.</fsummary>
<desc>
<marker id="dirty_prev"></marker>
@@ -1050,7 +1112,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_read({Tab, Key}) -> ValueList | exit({aborted, Reason}</name>
+ <name name="dirty_read" arity="1" since=""/>
<fsummary>Dirty read of records.</fsummary>
<desc>
<marker id="dirty_read"></marker>
@@ -1058,14 +1120,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}</name>
+ <name name="dirty_read" arity="2" since=""/>
<fsummary>Dirty read of records.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:read/3</c>.</p>
</desc>
</func>
<func>
- <name since="">dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}</name>
+ <name name="dirty_select" arity="2" since=""/>
<fsummary>Dirty matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="dirty_select"></marker>
@@ -1073,23 +1135,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_slot(Tab, Slot) -> RecordList | exit({aborted, Reason})</name>
- <fsummary>Returns the list of records that are associated with <c>Slot</c> in a table.</fsummary>
- <desc>
- <marker id="dirty_slot"></marker>
- <p>Traverses a table in a
- manner similar to the function <c>mnesia:dirty_next/2</c>.
- A table has a number of slots that range from 0 (zero) to
- an unknown upper bound. The function
- <c>mnesia:dirty_slot/2</c> returns the special atom
- <c>'$end_of_table'</c> when the end of the table is reached.
- The behavior of this function is undefined if a write
- operation is performed on the table while it is being
- traversed.</p>
- </desc>
- </func>
- <func>
- <name since="">dirty_update_counter({Tab, Key}, Incr) -> NewVal | exit({aborted, Reason})</name>
+ <name name="dirty_update_counter" arity="2" since=""/>
<fsummary>Dirty update of a counter record.</fsummary>
<desc>
<marker id="dirty_update_counter"></marker>
@@ -1097,7 +1143,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name>
+ <name name="dirty_update_counter" arity="3" since=""/>
<fsummary>Dirty update of a counter record.</fsummary>
<desc>
<p>Mnesia has no special counter records. However,
@@ -1120,13 +1166,13 @@ mnesia:create_table(person,
simultaneously, both updates take effect without the
risk of losing one of the updates. The new value
<c>NewVal</c> of the counter is returned.</p>
- <p>If <c>Key</c> do not exists, a new record is created with
+ <p>If <c>Key</c> does not exist, a new record is created with
value <c>Incr</c> if it is larger than 0, otherwise it is
set to 0.</p>
</desc>
</func>
<func>
- <name since="">dirty_write(Record) -> ok | exit({aborted, Reason})</name>
+ <name name="dirty_write" arity="1" since=""/>
<fsummary>Dirty write of a record.</fsummary>
<desc>
<marker id="dirty_write_1"></marker>
@@ -1135,28 +1181,28 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dirty_write(Tab, Record) -> ok | exit({aborted, Reason})</name>
+ <name name="dirty_write" arity="2" since=""/>
<fsummary>Dirty write of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:write/3</c>.</p>
</desc>
</func>
<func>
- <name since="">dump_log() -> dumped</name>
+ <name name="dump_log" arity="0" since=""/>
<fsummary>Performs a user-initiated dump of the local log file.</fsummary>
<desc>
<marker id="dump_log"></marker>
<p>Performs a user-initiated dump of the local log file.
This is usually not necessary, as Mnesia by default
manages this automatically. See configuration parameters
- <seealso marker="#dump_log_time_threshold">dump_log_time_threshold</seealso>
+ <seeerl marker="#dump_log_time_threshold">dump_log_time_threshold</seeerl>
and
- <seealso marker="#dump_log_write_threshold">dump_log_write_threshold</seealso>.
+ <seeerl marker="#dump_log_write_threshold">dump_log_write_threshold</seeerl>.
</p>
</desc>
</func>
<func>
- <name since="">dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}</name>
+ <name name="dump_tables" arity="1" since=""/>
<fsummary>Dumps all RAM tables to disc.</fsummary>
<desc>
<marker id="dump_tables"></marker>
@@ -1168,7 +1214,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">dump_to_textfile(Filename)</name>
+ <name name="dump_to_textfile" arity="1" since=""/>
<fsummary>Dumps local tables into a text file.</fsummary>
<desc>
<marker id="dump_to_textfile"></marker>
@@ -1181,7 +1227,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">error_description(Error) -> String</name>
+ <name name="error_description" arity="1" since=""/>
<fsummary>Returns a string describing a particular Mnesia error.</fsummary>
<desc>
<marker id="error_description"></marker>
@@ -1247,11 +1293,11 @@ mnesia:create_table(person,
and the method to retrieve more detailed error information:</p>
<list type="bulleted">
<item>The function
- <seealso marker="#create_table/2">mnesia:create_table(bar, [{attributes, 3.14}])</seealso>
+ <seemfa marker="#create_table/2">mnesia:create_table(bar, [{attributes, 3.14}])</seemfa>
returns the tuple <c>{aborted,Reason}</c>, where <c>Reason</c> is
the tuple <c>{bad_type,bar,3.14000}</c>.</item>
<item>The function
- <seealso marker="#error_description/1">mnesia:error_description(Reason)</seealso>
+ <seemfa marker="#error_description/1">mnesia:error_description(Reason)</seemfa>
returns the term <c>{"Bad type on some provided
arguments",bar,3.14000}</c>, which is an error description
suitable for display.</item>
@@ -1259,7 +1305,8 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">ets(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name name="ets" arity="1" since=""/>
+ <name name="ets" arity="2" since=""/>
<fsummary>Calls the <c>Fun</c> in a raw context that is not protected by a transaction.</fsummary>
<desc>
<marker id="ets"></marker>
@@ -1278,7 +1325,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">first(Tab) -> Key | transaction abort</name>
+ <name name="first" arity="1" since=""/>
<fsummary>Returns the key for the first record in a table.</fsummary>
<desc>
<marker id="first"></marker>
@@ -1293,7 +1340,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">foldl(Function, Acc, Table) -> NewAcc | transaction abort</name>
+ <name name="foldl" arity="3" since=""/>
<fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary>
<desc>
<marker id="foldl"></marker>
@@ -1306,7 +1353,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">foldr(Function, Acc, Table) -> NewAcc | transaction abort</name>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary>
<desc>
<marker id="foldr"></marker>
@@ -1317,7 +1364,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">force_load_table(Tab) -> yes | ErrorDescription</name>
+ <name name="force_load_table" arity="1" since=""/>
<fsummary>Forces a table to be loaded into the system.</fsummary>
<desc>
<marker id="force_load_table"></marker>
@@ -1335,7 +1382,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">index_match_object(Pattern, Pos) -> transaction abort | ObjList</name>
+ <name name="index_match_object" arity="2" since=""/>
<fsummary>Matches records and uses index information.</fsummary>
<desc>
<marker id="index_match_object_2"></marker>
@@ -1345,7 +1392,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList</name>
+ <name name="index_match_object" arity="4" since=""/>
<fsummary>Matches records and uses index information.</fsummary>
<desc>
<marker id="index_match_object_4"></marker>
@@ -1377,7 +1424,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList</name>
+ <name name="index_read" arity="3" since=""/>
<fsummary>Reads records through index table.</fsummary>
<desc>
<marker id="index_read"></marker>
@@ -1397,7 +1444,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">info() -> ok</name>
+ <name name="info" arity="0" since=""/>
<fsummary>Prints system information on the terminal.</fsummary>
<desc>
<marker id="info"></marker>
@@ -1408,7 +1455,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">install_fallback(Opaque) -> ok | {error,Reason}</name>
+ <name name="install_fallback" arity="1" since=""/>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<marker id="install_fallback_1"></marker>
@@ -1417,7 +1464,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">install_fallback(Opaque), BackupMod) -> ok | {error,Reason}</name>
+ <name name="install_fallback" arity="1" since=""/>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<p>Calls <c>mnesia:install_fallback(Opaque, Args)</c>, where
@@ -1425,7 +1472,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">install_fallback(Opaque, Args) -> ok | {error,Reason}</name>
+ <name name="install_fallback" arity="2" since=""/>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<p>Installs a backup as fallback. The fallback is used to
@@ -1483,7 +1530,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">is_transaction() -> boolean</name>
+ <name name="is_transaction" arity="0" since=""/>
<fsummary>Checks if code is running in a transaction.</fsummary>
<desc>
<marker id="is_transaction"></marker>
@@ -1492,7 +1539,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">last(Tab) -> Key | transaction abort</name>
+ <name name="last" arity="1" since=""/>
<fsummary>Returns the key for the last record in a table.</fsummary>
<desc>
<p>Works exactly like
@@ -1503,7 +1550,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">load_textfile(Filename)</name>
+ <name name="load_textfile" arity="1" since=""/>
<fsummary>Loads tables from a text file.</fsummary>
<desc>
<marker id="load_textfile"></marker>
@@ -1516,7 +1563,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">lock(LockItem, LockKind) -> Nodes | ok | transaction abort</name>
+ <name name="lock" arity="2" since=""/>
<fsummary>Explicit grab lock.</fsummary>
<desc>
<marker id="lock"></marker>
@@ -1605,7 +1652,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">match_object(Pattern) -> transaction abort | RecList</name>
+ <name name="match_object" arity="1" since=""/>
<fsummary>Matches <c>Pattern</c> for records.</fsummary>
<desc>
<marker id="match_object_1"></marker>
@@ -1614,7 +1661,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">match_object(Tab, Pattern, LockKind) -> transaction abort | RecList</name>
+ <name name="match_object" arity="3" since=""/>
<fsummary>Matches <c>Pattern</c> for records.</fsummary>
<desc>
<marker id="match_object_3"></marker>
@@ -1639,7 +1686,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}</name>
+ <name name="move_table_copy" arity="3" since=""/>
<fsummary>Moves the copy of table <c>Tab</c> from node <c>From</c> to node <c>To</c>.</fsummary>
<desc>
<marker id="move_table_copy"></marker>
@@ -1653,7 +1700,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">next(Tab, Key) -> Key | transaction abort</name>
+ <name name="next" arity="2" since=""/>
<fsummary>Returns the next key in a table.</fsummary>
<desc>
<marker id="next"></marker>
@@ -1665,7 +1712,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">prev(Tab, Key) -> Key | transaction abort</name>
+ <name name="prev" arity="2" since=""/>
<fsummary>Returns the previous key in a table.</fsummary>
<desc>
<p>Works exactly like
@@ -1676,7 +1723,8 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">read({Tab, Key}) -> transaction abort | RecordList</name>
+ <name name="read" arity="1" since=""/>
+ <name name="read" arity="2" since=""/>
<fsummary>Reads records(s) with a given key.</fsummary>
<desc>
<marker id="read_2"></marker>
@@ -1684,14 +1732,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">read(Tab, Key) -> transaction abort | RecordList</name>
- <fsummary>Reads records(s) with a given key.</fsummary>
- <desc>
- <p>Calls function <c>mnesia:read(Tab, Key, read)</c>.</p>
- </desc>
- </func>
- <func>
- <name since="">read(Tab, Key, LockKind) -> transaction abort | RecordList</name>
+ <name name="read" arity="3" since=""/>
<fsummary>Reads records(s) with a given key.</fsummary>
<desc>
<marker id="read_3"></marker>
@@ -1716,7 +1757,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">read_lock_table(Tab) -> ok | transaction abort</name>
+ <name name="read_lock_table" arity="1" since=""/>
<fsummary>Sets a read lock on an entire table.</fsummary>
<desc>
<marker id="read_lock_table"></marker>
@@ -1725,7 +1766,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">report_event(Event) -> ok</name>
+ <name name="report_event" arity="1" since=""/>
<fsummary>Reports a user event to the Mnesia event handler.</fsummary>
<desc>
<marker id="report_event"></marker>
@@ -1743,7 +1784,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}</name>
+ <name name="restore" arity="2" since=""/>
<fsummary>Online restore of backup.</fsummary>
<desc>
<marker id="restore"></marker>
@@ -1803,7 +1844,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">s_delete({Tab, Key}) -> ok | transaction abort</name>
+ <name name="s_delete" arity="1" since=""/>
<fsummary>Sets sticky lock and delete records.</fsummary>
<desc>
<marker id="s_delete"></marker>
@@ -1812,7 +1853,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">s_delete_object(Record) -> ok | transaction abort</name>
+ <name name="s_delete_object" arity="1" since=""/>
<fsummary>Sets sticky lock and delete record.</fsummary>
<desc>
<marker id="s_delete_object"></marker>
@@ -1822,7 +1863,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">s_write(Record) -> ok | transaction abort</name>
+ <name name="s_write" arity="1" since=""/>
<fsummary>Writes <c>Record</c> and sets sticky lock.</fsummary>
<desc>
<marker id="s_write"></marker>
@@ -1832,27 +1873,28 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name since="">schema() -> ok</name>
+ <name name="schema" arity="0" since=""/>
<fsummary>Prints information about all table definitions on the terminal.</fsummary>
<desc>
<p>Prints information about all table definitions on the terminal.</p>
</desc>
</func>
<func>
- <name since="">schema(Tab) -> ok</name>
+ <name name="schema" arity="1" since=""/>
<fsummary>Prints information about one table definition on the terminal.</fsummary>
<desc>
<p>Prints information about one table definition on the terminal.</p>
</desc>
</func>
<func>
- <name since="">select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]</name>
+ <name name="select" arity="2" since=""/>
+ <name name="select" arity="3" since=""/>
<fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="select_2_3"></marker>
<p>Matches the objects in table <c>Tab</c> using a
<c>match_spec</c> as described in the
- <seealso marker="stdlib:ets#select/3">ets:select/3</seealso>.
+ <seemfa marker="stdlib:ets#select/3">ets:select/3</seemfa>.
Optionally a lock
<c>read</c> or <c>write</c> can be given as the third
argument. Default is <c>read</c>. The return value depends
@@ -1871,8 +1913,8 @@ mnesia:create_table(person,
<item><c>Result = "Term construct"</c></item>
</list>
<p>For a complete description of <c>select</c>, see the
- <seealso marker="erts:index">ERTS</seealso> User's Guide and the
- <seealso marker="stdlib:ets">ets</seealso> manual page in
+ <seeapp marker="erts:index">ERTS</seeapp> User's Guide and the
+ <seeerl marker="stdlib:ets">ets</seeerl> manual page in
STDLIB.</p>
<p>For example, to find the names of all male persons older
than 30 in table <c>Tab</c>:</p>
@@ -1884,13 +1926,13 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
+ <name name="select" arity="4" since=""/>
<fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="select_4"></marker>
<p>Matches the objects in table <c>Tab</c> using a
<c>match_spec</c> as described in the
- <seealso marker="erts:index">ERTS</seealso> User's Guide,
+ <seeapp marker="erts:index">ERTS</seeapp> User's Guide,
and returns a chunk of terms and a continuation.
The wanted number of returned terms is specified by
argument <c>NObjects</c>. The lock argument can be
@@ -1907,7 +1949,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
+ <name name="select" arity="1" since=""/>
<fsummary>Continues selecting objects.</fsummary>
<desc>
<p>Selects more objects with the match specification initiated
@@ -1919,18 +1961,18 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">set_debug_level(Level) -> OldLevel</name>
+ <name name="set_debug_level" arity="1" since=""/>
<fsummary>Changes the internal debug level of Mnesia.</fsummary>
<desc>
<marker id="set_debug_level"></marker>
<p>Changes the internal debug level of Mnesia.
For details, see
- <seealso marker="#configuration_parameters">Section
- Configuration Parameters</seealso>.</p>
+ <seeerl marker="#configuration_parameters">Section
+ Configuration Parameters</seeerl>.</p>
</desc>
</func>
<func>
- <name since="">set_master_nodes(MasterNodes) -> ok | {error, Reason}</name>
+ <name name="set_master_nodes" arity="1" since=""/>
<fsummary>Sets the master nodes for all tables.</fsummary>
<desc>
<marker id="set_master_nodes_1"></marker>
@@ -1943,7 +1985,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}</name>
+ <name name="set_master_nodes" arity="2" since=""/>
<fsummary>Sets the master nodes for a table.</fsummary>
<desc>
<marker id="set_master_nodes_2"></marker>
@@ -1968,14 +2010,14 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">snmp_close_table(Tab) -> {aborted, R} | {atomic, ok}</name>
+ <name name="snmp_close_table" arity="1" since=""/>
<fsummary>Removes the possibility for SNMP to manipulate the table.</fsummary>
<desc>
<p>Removes the possibility for SNMP to manipulate the table.</p>
</desc>
</func>
<func>
- <name since="">snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name>
+ <name name="snmp_get_mnesia_key" arity="2" since=""/>
<fsummary>Gets the corresponding Mnesia key from an SNMP index.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -1990,7 +2032,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">snmp_get_next_index(Tab, RowIndex) -> {ok, NextIndex} | endOfTable</name>
+ <name name="snmp_get_next_index" arity="2" since=""/>
<fsummary>Gets the index of the next lexicographical row.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2006,7 +2048,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">snmp_get_row(Tab, RowIndex) -> {ok, Row} | undefined</name>
+ <name name="snmp_get_row" arity="2" since=""/>
<fsummary>Retrieves a row indexed by an SNMP index.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2019,7 +2061,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name since="">snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name>
+ <name name="snmp_open_table" arity="2" since=""/>
<fsummary>Organizes a Mnesia table as an SNMP table.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2073,7 +2115,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">start() -> ok | {error, Reason}</name>
+ <name name="start" arity="0" since=""/>
<fsummary>Starts a local Mnesia system.</fsummary>
<desc>
<marker id="start"></marker>
@@ -2115,7 +2157,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">stop() -> stopped</name>
+ <name name="stop" arity="0" since=""/>
<fsummary>Stops Mnesia locally.</fsummary>
<desc>
<marker id="stop"></marker>
@@ -2124,17 +2166,18 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">subscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
+ <name name="subscribe" arity="1" since=""/>
<fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary>
<desc>
<marker id="subscribe"></marker>
<p>Ensures that a copy of all events of type
<c>EventCategory</c> is sent to the caller. The available
- event types are described in the <seealso marker="Mnesia_chap5#event_handling">User's Guide</seealso>.</p>
+ event types are described in the <seeguide marker="Mnesia_chap5#event_handling">User's Guide</seeguide>.</p>
</desc>
</func>
<func>
- <name since="">sync_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name name="sync_dirty" arity="1" since=""/>
+ <name name="sync_dirty" arity="2" since=""/>
<fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary>
<desc>
<marker id="sync_dirty"></marker>
@@ -2150,17 +2193,20 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="OTP 17.0">sync_log() -> ok | {error, Reason}</name>
+ <name name="sync_log" arity="0" since="OTP 17.0"/>
<fsummary>Performs a file sync of the local log file.</fsummary>
<desc>
<p>Ensures that the local transaction log file is synced to disk.
On a single node system, data written to disk tables since the
last dump can be lost if there is a power outage.
- See <seealso marker="#dump_log/0">dump_log/0</seealso>.</p>
+ See <seemfa marker="#dump_log/0">dump_log/0</seemfa>.</p>
</desc>
</func>
<func>
- <name since="">sync_transaction(Fun, [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
+ <name name="sync_transaction" arity="1" since=""/>
+ <name name="sync_transaction" arity="2" clause_i="1" since=""/>
+ <name name="sync_transaction" arity="2" clause_i="2" since=""/>
+ <name name="sync_transaction" arity="3" since=""/>
<fsummary>Synchronously executes a transaction.</fsummary>
<desc>
<marker id="sync_transaction"></marker>
@@ -2173,7 +2219,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">system_info(InfoKey) -> Info | exit({aborted, Reason})</name>
+ <name name="system_info" arity="1" since=""/>
<fsummary>Returns information about the Mnesia system.</fsummary>
<desc>
<marker id="system_info"></marker>
@@ -2360,12 +2406,13 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">table(Tab [,[Option]]) -> QueryHandle</name>
+ <name name="table" arity="1" since=""/>
+ <name name="table" arity="2" since=""/>
<fsummary>Return a QLC query handle.</fsummary>
<desc>
<marker id="table"></marker>
<p>Returns a Query List Comprehension (QLC) query handle,
- see the <seealso marker="stdlib:qlc">qlc(3)</seealso>
+ see the <seeerl marker="stdlib:qlc">qlc(3)</seeerl>
manual page in STDLIB. The module <c>qlc</c>
implements a query language that can use Mnesia
tables as sources of data. Calling
@@ -2415,7 +2462,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})</name>
+ <name name="table_info" arity="2" since=""/>
<fsummary>Returns local information about table.</fsummary>
<desc>
<marker id="table_info"></marker>
@@ -2497,8 +2544,12 @@ mnesia:create_table(employee,
table.</p>
</item>
<item>
- <p><c>memory</c>. Returns the number of
- words allocated to the table on this node.</p>
+ <p><c>memory</c>. Returns for <c>ram_copies</c> and
+ <c>disc_copies</c> tables the number of words allocated in
+ memory to the table on this node.
+ For <c>disc_only_copies</c> tables the number of bytes stored
+ on disc is returned.
+ </p>
</item>
<item>
<p><c>ram_copies</c>. Returns the nodes where a
@@ -2567,7 +2618,11 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name since="">transaction(Fun [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
+ <name name="transaction" arity="1" since=""/>
+ <name name="transaction" arity="2" clause_i="1" since=""/>
+ <name name="transaction" arity="2" clause_i="2" since=""/>
+ <name name="transaction" arity="3" since=""/>
+
<fsummary>Executes a transaction.</fsummary>
<desc>
<marker id="transaction"></marker>
@@ -2589,8 +2644,8 @@ mnesia:create_table(employee,
<code type="none">
add_family({family, F, M, Children}) ->
ChildOids = lists:map(fun oid/1, Children),
- Trans = fun() ->
- mnesia:write(F#person{children = ChildOids},
+ Trans = fun() ->
+ mnesia:write(F#person{children = ChildOids},
mnesia:write(M#person{children = ChildOids},
Write = fun(Child) -> mnesia:write(Child) end,
lists:foreach(Write, Children)
@@ -2635,7 +2690,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}</name>
+ <name name="transform_table" arity="4" since=""/>
<fsummary>Changes format on all records in table <c>Tab</c>.</fsummary>
<desc>
<marker id="transform_table_4"></marker>
@@ -2656,7 +2711,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}</name>
+ <name name="transform_table" arity="3" since=""/>
<fsummary>Changes format on all records in table <c>Tab</c>.</fsummary>
<desc>
<p>Calls <c>mnesia:transform_table(Tab, Fun,
@@ -2665,7 +2720,8 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}</name>
+ <name name="traverse_backup" arity="4" since=""/>
+ <name name="traverse_backup" arity="6" since=""/>
<fsummary>Traversal of a backup.</fsummary>
<desc>
<marker id="traverse_backup"></marker>
@@ -2687,7 +2743,7 @@ raise(Name, Amount) ->
<c>{BackupItems,NewAcc}</c>, where <c>BackupItems</c> is
a list of valid backup items, and <c>NewAcc</c> is a new
accumulator value. The returned backup items are written
- in the target backup.
+ in the target backup.
</item>
<item><c>LastAcc</c> is the last accumulator value. This is
the last <c>NewAcc</c> value that was returned by <c>Fun</c>.
@@ -2696,7 +2752,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">uninstall_fallback() -> ok | {error,Reason}</name>
+ <name name="uninstall_fallback" arity="0" since=""/>
<fsummary>Uninstalls a fallback.</fsummary>
<desc>
<marker id="uninstall_fallback_0"></marker>
@@ -2705,7 +2761,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">uninstall_fallback(Args) -> ok | {error,Reason}</name>
+ <name name="uninstall_fallback" arity="1" since=""/>
<fsummary>Uninstalls a fallback.</fsummary>
<desc>
<p>Deinstalls a fallback before it
@@ -2732,7 +2788,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">unsubscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
+ <name name="unsubscribe" arity="1" since=""/>
<fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary>
<desc>
<marker id="unsubscribe"></marker>
@@ -2742,7 +2798,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">wait_for_tables(TabList, Timeout) -> ok | {timeout, BadTabList} | {error, Reason}</name>
+ <name name="wait_for_tables" arity="2" since=""/>
<fsummary>Waits for tables to be accessible.</fsummary>
<desc>
<marker id="wait_for_tables"></marker>
@@ -2753,7 +2809,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">wread({Tab, Key}) -> transaction abort | RecordList</name>
+ <name name="wread" arity="1" since=""/>
<fsummary>Reads records with given key.</fsummary>
<desc>
<marker id="wread"></marker>
@@ -2761,7 +2817,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">write(Record) -> transaction abort | ok</name>
+ <name name="write" arity="1" since=""/>
<fsummary>Writes a record into the database.</fsummary>
<desc>
<marker id="write_1"></marker>
@@ -2770,7 +2826,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">write(Tab, Record, LockKind) -> transaction abort | ok</name>
+ <name name="write" arity="3" since=""/>
<fsummary>Writes a record into the database.</fsummary>
<desc>
<marker id="write_3"></marker>
@@ -2786,7 +2842,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name since="">write_lock_table(Tab) -> ok | transaction abort</name>
+ <name name="write_lock_table" arity="1" since=""/>
<fsummary>Sets write lock on an entire table.</fsummary>
<desc>
<marker id="write_lock_table"></marker>
@@ -2870,7 +2926,7 @@ raise(Name, Amount) ->
<item>
<p><c>-mnesia dc_dump_limit Number</c>. Controls how often
<c>disc_copies</c> tables are dumped from memory.
- Tables are dumped when
+ Tables are dumped when
<c>filesize(Log) > (filesize(Tab)/Dc_dump_limit)</c>.
Lower values reduce CPU overhead but increase disk space
and startup times. Default is 4.</p>
@@ -2903,7 +2959,7 @@ raise(Name, Amount) ->
on the original data file. Default is <c>true</c></p>
</item>
<item>
- <marker id=" dump_log_write_threshold"></marker>
+ <marker id="dump_log_write_threshold"></marker>
<p><c>-mnesia dump_log_write_threshold Max</c>.
<c>Max</c> is an integer that specifies the maximum
number of writes allowed to the transaction log before
@@ -2911,7 +2967,7 @@ raise(Name, Amount) ->
log writes.</p>
</item>
<item>
- <marker id=" dump_log_time_threshold"></marker>
+ <marker id="dump_log_time_threshold"></marker>
<p><c>-mnesia dump_log_time_threshold Max</c>.
<c>Max</c> is an integer that specifies the dump log
interval in milliseconds. Default is 3 minutes. If a
@@ -3026,13 +3082,13 @@ raise(Name, Amount) ->
<section>
<title>See Also</title>
- <p><seealso marker="kernel:application">application(3)</seealso>,
- <seealso marker="stdlib:dets">dets(3)</seealso>,
- <seealso marker="kernel:disk_log">disk_log(3)</seealso>,
- <seealso marker="stdlib:ets">ets(3)</seealso>,
- <seealso marker="mnesia:mnesia_registry">mnesia_registry(3)</seealso>,
- <seealso marker="stdlib:qlc">qlc(3)</seealso></p>
+ <p><seeerl marker="kernel:application">application(3)</seeerl>,
+ <seeerl marker="stdlib:dets">dets(3)</seeerl>,
+ <seeerl marker="kernel:disk_log">disk_log(3)</seeerl>,
+ <seeerl marker="stdlib:ets">ets(3)</seeerl>,
+ <seeerl marker="mnesia:mnesia_registry">mnesia_registry(3)</seeerl>,
+ <seeerl marker="stdlib:qlc">qlc(3)</seeerl></p>
</section>
-
+
</erlref>
diff --git a/lib/mnesia/doc/src/mnesia_frag_hash.xml b/lib/mnesia/doc/src/mnesia_frag_hash.xml
index ccaba412d0..3625970925 100644
--- a/lib/mnesia/doc/src/mnesia_frag_hash.xml
+++ b/lib/mnesia/doc/src/mnesia_frag_hash.xml
@@ -159,7 +159,7 @@
<section>
<title>See Also</title>
- <p><seealso marker="mnesia:mnesia">mnesia(3)</seealso></p>
+ <p><seeerl marker="mnesia:mnesia">mnesia(3)</seeerl></p>
</section>
</erlref>
diff --git a/lib/mnesia/doc/src/mnesia_registry.xml b/lib/mnesia/doc/src/mnesia_registry.xml
index 9318af6f6c..81b3301e2b 100644
--- a/lib/mnesia/doc/src/mnesia_registry.xml
+++ b/lib/mnesia/doc/src/mnesia_registry.xml
@@ -92,8 +92,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="erl_interface:index">erl_interface(3)</seealso>,
- <seealso marker="mnesia:mnesia">mnesia(3)</seealso></p>
+ <p><seeapp marker="erl_interface:index">erl_interface(3)</seeapp>,
+ <seeerl marker="mnesia:mnesia">mnesia(3)</seeerl></p>
</section>
</erlref>
diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml
index 5cda5ed72e..ba61efa7e9 100644
--- a/lib/mnesia/doc/src/notes.xml
+++ b/lib/mnesia/doc/src/notes.xml
@@ -39,7 +39,149 @@
thus constitutes one section in this document. The title of each
section is the version number of Mnesia.</p>
- <section><title>Mnesia 4.16.3</title>
+ <section><title>Mnesia 4.19</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed the type spec for <c>disc_only_copies</c>.</p>
+ <p>
+ Own Id: OTP-17249 Aux Id: PR-4578 </p>
+ </item>
+ <item>
+ <p>
+ Do not crash in <c>mnesia:change_config/2</c> if mnesia
+ is stopping or starting.</p>
+ <p>
+ Own Id: OTP-17274 Aux Id: GH-4616 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Optimized table loading time for tables that are updated
+ during the loading.</p>
+ <p>
+ Own Id: OTP-17271 Aux Id: PR-4575 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Mnesia 4.18.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid potential performance issue, if the input queue to
+ <c>mnesia_tm</c> is long.</p>
+ <p>
+ Own Id: OTP-17066 Aux Id: PR-2889 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Mnesia 4.18</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ FIx mnesia delete object handling in transaction storage.
+ In a transaction <c>mnesia:read/1</c> could indicate that
+ exiting objects did not exist after another object was
+ deleted.</p>
+ <p>
+ Own Id: OTP-16782 Aux Id: PR-2663 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Fixed crash during startup, which could happen if a table
+ was deleted on another node.</p>
+ <p>
+ Own Id: OTP-16815 Aux Id: ERIERL-500 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Mnesia 4.17</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Make <c>mnesia:create_table/2</c> return correct badarg
+ value.</p>
+ <p>
+ Own Id: OTP-16072 Aux Id: PR-2320 </p>
+ </item>
+ <item>
+ <p>
+ Fixed a bug where mnesia was sometimes not waiting during
+ start for a commit decision on asymmetric transactions.</p>
+ <p>
+ Own Id: OTP-16634 Aux Id: PR-2610 ERL-1227 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>
+ Avoid using <c>rpc</c> calls to do table reads, which
+ will reduce the load on rpc server and improve
+ performance.</p>
+ <p>
+ Own Id: OTP-16189</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>Mnesia 4.16.3.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Fixed crash during startup, which could happen if a table
+ was deleted on another node.</p>
+ <p>
+ Own Id: OTP-16815 Aux Id: ERIERL-500 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Mnesia 4.16.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/mnesia/doc/src/specs.xml b/lib/mnesia/doc/src/specs.xml
new file mode 100644
index 0000000000..3d33a1e611
--- /dev/null
+++ b/lib/mnesia/doc/src/specs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<specs xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include href="../specs/specs_mnesia.xml"/>
+ <xi:include href="../specs/specs_mnesia_frag_hash.xml"/>
+ <xi:include href="../specs/specs_mnesia_registry.xml"/>
+</specs>
diff --git a/lib/mnesia/info b/lib/mnesia/info
index bfd0816a62..2fc77fc444 100644
--- a/lib/mnesia/info
+++ b/lib/mnesia/info
@@ -1,2 +1,2 @@
group: dat Database Applications
-short: A heavy duty real-time distributed database
+short: A heavy-duty real-time distributed database
diff --git a/lib/mnesia/src/Makefile b/lib/mnesia/src/Makefile
index 7d316df263..90e8780754 100644
--- a/lib/mnesia/src/Makefile
+++ b/lib/mnesia/src/Makefile
@@ -65,6 +65,7 @@ MODULES= \
mnesia_monitor \
mnesia_recover \
mnesia_registry \
+ mnesia_rpc \
mnesia_schema\
mnesia_snmp_hook \
mnesia_subscr \
diff --git a/lib/mnesia/src/mnesia.app.src b/lib/mnesia/src/mnesia.app.src
index c755b4d4b9..77bd1a7816 100644
--- a/lib/mnesia/src/mnesia.app.src
+++ b/lib/mnesia/src/mnesia.app.src
@@ -2,36 +2,37 @@
[{description, "MNESIA CXC 138 12"},
{vsn, "%VSN%"},
{modules, [
- mnesia,
+ mnesia,
mnesia_app,
mnesia_backend_type,
- mnesia_backup,
- mnesia_bup,
- mnesia_checkpoint,
+ mnesia_backup,
+ mnesia_bup,
+ mnesia_checkpoint,
mnesia_checkpoint_sup,
mnesia_controller,
- mnesia_dumper,
- mnesia_event,
+ mnesia_dumper,
+ mnesia_event,
mnesia_ext_sup,
- mnesia_frag,
- mnesia_frag_hash,
- mnesia_index,
+ mnesia_frag,
+ mnesia_frag_hash,
+ mnesia_index,
mnesia_kernel_sup,
mnesia_late_loader,
- mnesia_lib,
- mnesia_loader,
- mnesia_locker,
- mnesia_log,
- mnesia_monitor,
+ mnesia_lib,
+ mnesia_loader,
+ mnesia_locker,
+ mnesia_log,
+ mnesia_monitor,
mnesia_recover,
mnesia_registry,
- mnesia_schema,
- mnesia_snmp_hook,
- mnesia_subscr,
- mnesia_sup,
+ mnesia_rpc,
+ mnesia_schema,
+ mnesia_snmp_hook,
+ mnesia_subscr,
+ mnesia_sup,
mnesia_sp,
mnesia_text,
- mnesia_tm
+ mnesia_tm
]},
{registered, [
mnesia_dumper_load_regulator,
@@ -39,12 +40,13 @@
mnesia_fallback,
mnesia_controller,
mnesia_kernel_sup,
- mnesia_late_loader,
- mnesia_locker,
+ mnesia_late_loader,
+ mnesia_locker,
mnesia_monitor,
mnesia_recover,
- mnesia_substr,
- mnesia_sup,
+ mnesia_rpc,
+ mnesia_substr,
+ mnesia_sup,
mnesia_tm
]},
{applications, [kernel, stdlib]},
diff --git a/lib/mnesia/src/mnesia.erl b/lib/mnesia/src/mnesia.erl
index 02bc884e36..f9e452cd59 100644
--- a/lib/mnesia/src/mnesia.erl
+++ b/lib/mnesia/src/mnesia.erl
@@ -142,7 +142,7 @@
{'access_mode', 'read_write' | 'read_only'} |
{'attributes', [atom()]} |
{'disc_copies', [node()]} |
- {'disc_only_copies', [node]} |
+ {'disc_only_copies', [node()]} |
{'index', [index_attr()]} |
{'load_order', non_neg_integer()} |
{'majority', boolean()} |
@@ -155,6 +155,7 @@
{'user_properties', proplists:proplist()}.
-type t_result(Res) :: {'atomic', Res} | {'aborted', Reason::term()}.
+-type result() :: 'ok' | {'error', Reason::term()}.
-type activity() :: 'ets' | 'async_dirty' | 'sync_dirty' | 'transaction' | 'sync_transaction' |
{'transaction', Retries::non_neg_integer()} |
{'sync_transaction', Retries::non_neg_integer()}.
@@ -168,9 +169,10 @@
-type snmp_struct() :: [{atom(), snmp_type() | tuple_of(snmp_type())}].
-type snmp_type() :: 'fix_string' | 'string' | 'integer'.
-type tuple_of(_T) :: tuple().
--type config_key() :: extra_db_nodes | dc_dump_limit.
+-type config_key() :: 'extra_db_nodes' | 'dc_dump_limit'.
-type config_value() :: [node()] | number().
--type config_result() :: {ok, config_value()} | {error, term()}.
+-type config_result() :: {'ok', config_value()} | {'error', term()}.
+-type debug_level() :: 'none' | 'verbose' | 'debug' | 'trace'.
-define(DEFAULT_ACCESS, ?MODULE).
@@ -230,7 +232,7 @@ e_has_var(X, Pos) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Start and stop
--spec start() -> 'ok' | {'error', term()}.
+-spec start() -> result().
start() ->
start([]).
@@ -250,7 +252,7 @@ start_() ->
{error, R}
end.
--spec start([{Option::atom(), Value::_}]) -> 'ok' | {'error', term()}.
+-spec start([{Option::atom(), Value::_}]) -> result().
start(ExtraEnv) when is_list(ExtraEnv) ->
case mnesia_lib:ensure_loaded(?APPLICATION) of
ok ->
@@ -281,8 +283,8 @@ stop() ->
Other -> Other
end.
--spec change_config(Config::config_key(), Value::config_value()) ->
- config_result().
+-spec change_config(Config, Value) -> config_result() when
+ Config :: config_key(), Value :: config_value().
change_config(extra_db_nodes, Ns) when is_list(Ns) ->
mnesia_controller:connect_nodes(Ns);
change_config(dc_dump_limit, N) when is_number(N), N > 0 ->
@@ -299,6 +301,10 @@ change_config(BadKey, _BadVal) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Debugging
+
+-spec set_debug_level(Level :: debug_level()) ->
+ OldLevel :: debug_level().
+
set_debug_level(Level) ->
mnesia_subscr:set_debug_level(Level).
@@ -371,7 +377,7 @@ transaction(Fun) ->
-spec transaction(Fun, Retries) -> t_result(Res) when
Fun :: fun(() -> Res),
Retries :: non_neg_integer() | 'infinity';
- (Fun, [Arg::_]) -> t_result(Res) when
+ (Fun, Args::[Arg::_]) -> t_result(Res) when
Fun :: fun((...) -> Res).
transaction(Fun, Retries) when is_integer(Retries), Retries >= 0 ->
transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, async);
@@ -392,9 +398,9 @@ sync_transaction(Fun) ->
transaction(get(mnesia_activity_state), Fun, [], infinity, ?DEFAULT_ACCESS, sync).
-spec sync_transaction(Fun, Retries) -> t_result(Res) when
- Fun :: fun(() -> Res),
+ Fun :: fun(() -> Res) | fun((...) -> Res),
Retries :: non_neg_integer() | 'infinity';
- (Fun, [Arg::_]) -> t_result(Res) when
+ (Fun, Args :: [Arg::_]) -> t_result(Res) when
Fun :: fun((...) -> Res).
sync_transaction(Fun, Retries) when is_integer(Retries), Retries >= 0 ->
transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, sync);
@@ -524,7 +530,7 @@ lock(LockItem, LockKind) ->
-spec lock_table(Tab::table(), LockKind) -> [MnesiaNode] | no_return() when
MnesiaNode :: node(),
- LockKind :: lock_kind() | load.
+ LockKind :: lock_kind() | 'load'.
lock_table(Tab, LockKind) ->
lock({table, Tab}, LockKind).
@@ -546,13 +552,13 @@ lock(Tid, Ts, LockItem, LockKind) ->
end.
%% Grab a read lock on a whole table
--spec read_lock_table(Tab::table()) -> ok.
+-spec read_lock_table(Tab::table()) -> 'ok'.
read_lock_table(Tab) ->
lock({table, Tab}, read),
ok.
%% Grab a write lock on a whole table
--spec write_lock_table(Tab::table()) -> ok.
+-spec write_lock_table(Tab::table()) -> 'ok'.
write_lock_table(Tab) ->
lock({table, Tab}, write),
ok.
@@ -782,7 +788,8 @@ do_delete_object(Tid, Ts, Tab, Val, LockKind) ->
?ets_match_delete(Store, {Oid, Val, '_'}),
?ets_insert(Store, {Oid, Val, delete_object});
_ ->
- case ?ets_match_object(Store, {Oid, '_', write}) of
+ case ?ets_match_object(Store, {Oid, '_', write}) ++
+ ?ets_match_object(Store, {Oid, '_', delete}) of
[] ->
?ets_match_delete(Store, {Oid, Val, '_'}),
?ets_insert(Store, {Oid, Val, delete_object});
@@ -1215,19 +1222,28 @@ add_written(Written, Tab, ObjsFun, LockKind) ->
add_written_to_bag(Written, ObjsFun(), []);
_ when LockKind == read;
LockKind == write ->
- add_written_to_set(Written);
+ add_written_to_set(Written, ObjsFun);
_ ->
- _ = ObjsFun(), % Fall back to request new lock and read from source
- add_written_to_set(Written)
+ %% Fall back to request new lock and read from source
+ add_written_to_set(Written, ObjsFun())
end.
-add_written_to_set(Ws) ->
+add_written_to_set(Ws, ObjsOrFun) ->
case lists:last(Ws) of
{_, _, delete} -> [];
{_, Val, write} -> [Val];
- {_, _, delete_object} -> []
+ {Oid, _, delete_object} ->
+ %% May be several 'delete_object' in Ws; need to check if any
+ %% deleted Val exists in source table; if not return whatever
+ %% is/is not in the source table (ie as the Val is only deleted
+ %% if matched at commit this needs to be reflected here)
+ [Val || Val <- get_objs(ObjsOrFun),
+ not lists:member({Oid, Val, delete_object}, Ws)]
end.
+get_objs(ObjsFun) when is_function(ObjsFun) -> ObjsFun();
+get_objs(Objs) when is_list(Objs) -> Objs.
+
add_written_to_bag([{_, Val, write} | Tail], Objs, Ack) ->
add_written_to_bag(Tail, lists:delete(Val, Objs), [Val | Ack]);
add_written_to_bag([], Objs, Ack) ->
@@ -2057,8 +2073,14 @@ dirty_rpc(Tab, M, F, Args) ->
do_dirty_rpc(_Tab, nowhere, _, _, Args) ->
mnesia:abort({no_exists, Args});
+do_dirty_rpc(_Tab, Local, M, F, Args) when Local =:= node() ->
+ try apply(M,F,Args)
+ catch
+ throw:Res -> Res;
+ _:_ -> mnesia:abort({badarg, Args})
+ end;
do_dirty_rpc(Tab, Node, M, F, Args) ->
- case rpc:call(Node, M, F, Args) of
+ case mnesia_rpc:call(Node, M, F, Args) of
{badrpc, Reason} ->
timer:sleep(20), %% Do not be too eager, and can't use yield on SMP
%% Sync with mnesia_monitor
@@ -2166,7 +2188,7 @@ raw_table_info(Tab, Item) ->
disc_only_copies ->
info_reply(dets:info(Tab, Item), Tab, Item);
{ext, Alias, Mod} ->
- info_reply(catch Mod:info(Alias, Tab, Item), Tab, Item);
+ info_reply(Mod:info(Alias, Tab, Item), Tab, Item);
unknown ->
bad_info_reply(Tab, Item)
end
@@ -2184,12 +2206,12 @@ bad_info_reply(_Tab, memory) -> 0;
bad_info_reply(Tab, Item) -> abort({no_exists, Tab, Item}).
%% Raw info about all tables
--spec schema() -> ok.
+-spec schema() -> 'ok'.
schema() ->
mnesia_schema:info().
%% Raw info about one tables
--spec schema(Tab::table()) -> ok.
+-spec schema(Tab::table()) -> 'ok'.
schema(Tab) ->
mnesia_schema:info(Tab).
@@ -2197,7 +2219,7 @@ schema(Tab) ->
error_description(Err) ->
mnesia_lib:error_desc(Err).
--spec info() -> ok.
+-spec info() -> 'ok'.
info() ->
case mnesia_lib:is_running() of
yes ->
@@ -2631,18 +2653,18 @@ load_mnesia_or_abort() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Database mgt
--spec create_schema(Ns::[node()]) -> 'ok' | {'error', Reason::term()}.
+-spec create_schema(Ns::[node()]) -> result().
create_schema(Ns) ->
create_schema(Ns, []).
--spec create_schema(Ns::[node()], [Prop]) -> 'ok' | {'error', Reason::term()} when
+-spec create_schema(Ns::[node()], [Prop]) -> result() when
Prop :: BackendType | IndexPlugin,
- BackendType :: {backend_types, [{Name::atom(), Module::module()}]},
- IndexPlugin :: {index_plugins, [{{Name::atom()}, Module::module(), Function::atom()}]}.
+ BackendType :: {'backend_types', [{Name::atom(), Module::module()}]},
+ IndexPlugin :: {'index_plugins', [{{Name::atom()}, Module::module(), Function::atom()}]}.
create_schema(Ns, Properties) ->
mnesia_bup:create_schema(Ns, Properties).
--spec delete_schema(Ns::[node()]) -> 'ok' | {'error', Reason::term()}.
+-spec delete_schema(Ns::[node()]) -> result().
delete_schema(Ns) ->
mnesia_schema:delete_schema(Ns).
@@ -2650,12 +2672,12 @@ delete_schema(Ns) ->
add_backend_type(Alias, Module) ->
mnesia_schema:add_backend_type(Alias, Module).
--spec backup(Dest::term()) -> 'ok' | {'error', Reason::term()}.
+-spec backup(Dest::term()) -> result().
backup(Opaque) ->
mnesia_log:backup(Opaque).
-spec backup(Dest::term(), Mod::module()) ->
- 'ok' | {'error', Reason::term()}.
+ result().
backup(Opaque, Mod) ->
mnesia_log:backup(Opaque, Mod).
@@ -2673,12 +2695,12 @@ traverse_backup(S, T, Fun, Acc) ->
traverse_backup(S, SM, T, TM, F, A) ->
mnesia_bup:traverse_backup(S, SM, T, TM, F, A).
--spec install_fallback(Src::term()) -> 'ok' | {'error', Reason::term()}.
+-spec install_fallback(Src::term()) -> result().
install_fallback(Opaque) ->
mnesia_bup:install_fallback(Opaque).
-spec install_fallback(Src::term(), Mod::module()|[Opt]) ->
- 'ok' | {'error', Reason::term()} when
+ result() when
Opt :: Module | Scope | Dir,
Module :: {'module', Mod::module()},
Scope :: {'scope', 'global' | 'local'},
@@ -2686,11 +2708,11 @@ install_fallback(Opaque) ->
install_fallback(Opaque, Mod) ->
mnesia_bup:install_fallback(Opaque, Mod).
--spec uninstall_fallback() -> 'ok' | {'error', Reason::term()}.
+-spec uninstall_fallback() -> result().
uninstall_fallback() ->
mnesia_bup:uninstall_fallback().
--spec uninstall_fallback(Args) -> 'ok' | {'error', Reason::term()} when
+-spec uninstall_fallback(Args) -> result() when
Args :: [{'mnesia_dir', Dir::string()}].
uninstall_fallback(Args) ->
mnesia_bup:uninstall_fallback(Args).
@@ -2701,16 +2723,17 @@ uninstall_fallback(Args) ->
activate_checkpoint(Args) ->
mnesia_checkpoint:activate(Args).
--spec deactivate_checkpoint(Name::_) -> 'ok' | {'error', Reason::term()}.
+-spec deactivate_checkpoint(Name::_) -> result().
deactivate_checkpoint(Name) ->
mnesia_checkpoint:deactivate(Name).
--spec backup_checkpoint(Name::_, Dest::_) -> 'ok' | {'error', Reason::term()}.
+-spec backup_checkpoint(Name, Dest) -> result() when
+ Name :: term(), Dest :: term().
backup_checkpoint(Name, Opaque) ->
mnesia_log:backup_checkpoint(Name, Opaque).
--spec backup_checkpoint(Name::_, Dest::_, Mod::module()) ->
- 'ok' | {'error', Reason::term()}.
+-spec backup_checkpoint(Name, Dest, Mod) -> result() when
+ Name :: term(), Dest :: term(), Mod :: module().
backup_checkpoint(Name, Opaque, Mod) ->
mnesia_log:backup_checkpoint(Name, Opaque, Mod).
@@ -2731,32 +2754,35 @@ create_table(Arg) ->
create_table(Name, Arg) when is_list(Arg) ->
mnesia_schema:create_table([{name, Name}| Arg]);
create_table(Name, Arg) ->
- {aborted, badarg, Name, Arg}.
+ {aborted, {badarg, Name, Arg}}.
-spec delete_table(Tab::table()) -> t_result('ok').
delete_table(Tab) ->
mnesia_schema:delete_table(Tab).
--spec add_table_copy(Tab::table(), N::node(), ST::storage_type()) -> t_result(ok).
+-spec add_table_copy(Tab, N, ST) -> t_result('ok') when
+ Tab :: table(), N::node(), ST::storage_type().
add_table_copy(Tab, N, S) ->
mnesia_schema:add_table_copy(Tab, N, S).
--spec del_table_copy(Tab::table(), N::node()) -> t_result(ok).
+-spec del_table_copy(Tab::table(), N::node()) -> t_result('ok').
del_table_copy(Tab, N) ->
mnesia_schema:del_table_copy(Tab, N).
--spec move_table_copy(Tab::table(), From::node(), To::node()) -> t_result(ok).
+-spec move_table_copy(Tab::table(), From::node(), To::node()) -> t_result('ok').
move_table_copy(Tab, From, To) ->
mnesia_schema:move_table(Tab, From, To).
--spec add_table_index(Tab::table(), I::index_attr()) -> t_result(ok).
+-spec add_table_index(Tab, I) -> t_result('ok') when
+ Tab :: table(), I :: index_attr().
add_table_index(Tab, Ix) ->
mnesia_schema:add_table_index(Tab, Ix).
--spec del_table_index(Tab::table(), I::index_attr()) -> t_result(ok).
+-spec del_table_index(Tab, I) -> t_result('ok') when
+ Tab::table(), I::index_attr().
del_table_index(Tab, Ix) ->
mnesia_schema:del_table_index(Tab, Ix).
--spec transform_table(Tab::table(), Fun, [Attr]) -> t_result(ok) when
+-spec transform_table(Tab::table(), Fun, [Attr]) -> t_result('ok') when
Attr :: atom(),
Fun:: fun((Record::tuple()) -> Transformed::tuple()) | ignore.
transform_table(Tab, Fun, NewA) ->
@@ -2766,18 +2792,18 @@ transform_table(Tab, Fun, NewA) ->
mnesia:abort(Reason)
end.
--spec transform_table(Tab::table(), Fun, [Attr], RecName) -> t_result(ok) when
+-spec transform_table(Tab::table(), Fun, [Attr], RecName) -> t_result('ok') when
RecName :: atom(),
Attr :: atom(),
Fun:: fun((Record::tuple()) -> Transformed::tuple()) | ignore.
transform_table(Tab, Fun, NewA, NewRN) ->
mnesia_schema:transform_table(Tab, Fun, NewA, NewRN).
--spec change_table_copy_type(Tab::table(), Node::node(), To::storage_type()) -> t_result(ok).
+-spec change_table_copy_type(Tab::table(), Node::node(), To::storage_type()) -> t_result('ok').
change_table_copy_type(T, N, S) ->
mnesia_schema:change_table_copy_type(T, N, S).
--spec clear_table(Tab::table()) -> t_result(ok).
+-spec clear_table(Tab::table()) -> t_result('ok').
clear_table(Tab) ->
case get(mnesia_activity_state) of
State = {Mod, Tid, _Ts} when element(1, Tid) =/= tid ->
@@ -2811,18 +2837,18 @@ clear_table(Tid, Ts, Tab, Obj) when element(1, Tid) =:= tid ->
read_table_property(Tab, PropKey) ->
val({Tab, user_property, PropKey}).
--spec write_table_property(Tab::table(), Prop::tuple()) -> t_result(ok).
+-spec write_table_property(Tab::table(), Prop::tuple()) -> t_result('ok').
write_table_property(Tab, Prop) ->
mnesia_schema:write_table_property(Tab, Prop).
--spec delete_table_property(Tab::table(), PropKey::term()) -> t_result(ok).
+-spec delete_table_property(Tab::table(), PropKey::term()) -> t_result('ok').
delete_table_property(Tab, PropKey) ->
mnesia_schema:delete_table_property(Tab, PropKey).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Table mgt - user properties
--spec change_table_frag(Tab::table(), FP::term()) -> t_result(ok).
+-spec change_table_frag(Tab::table(), FP::term()) -> t_result('ok').
change_table_frag(Tab, FragProp) ->
mnesia_schema:change_table_frag(Tab, FragProp).
@@ -2830,13 +2856,13 @@ change_table_frag(Tab, FragProp) ->
%% Table mgt - table load
%% Dump a ram table to disc
--spec dump_tables([Tab::table()]) -> t_result(ok).
+-spec dump_tables([Tab::table()]) -> t_result('ok').
dump_tables(Tabs) ->
mnesia_schema:dump_tables(Tabs).
%% allow the user to wait for some tables to be loaded
-spec wait_for_tables([Tab::table()], TMO::timeout()) ->
- 'ok' | {'timeout', [table()]} | {'error', Reason::term()}.
+ result() | {'timeout', [table()]}.
wait_for_tables(Tabs, Timeout) ->
mnesia_controller:wait_for_tables(Tabs, Timeout).
@@ -2847,21 +2873,21 @@ force_load_table(Tab) ->
Other -> Other
end.
--spec change_table_access_mode(Tab::table(), Mode) -> t_result(ok) when
+-spec change_table_access_mode(Tab::table(), Mode) -> t_result('ok') when
Mode :: 'read_only'|'read_write'.
change_table_access_mode(T, Access) ->
mnesia_schema:change_table_access_mode(T, Access).
--spec change_table_load_order(Tab::table(), Order) -> t_result(ok) when
+-spec change_table_load_order(Tab::table(), Order) -> t_result('ok') when
Order :: non_neg_integer().
change_table_load_order(T, O) ->
mnesia_schema:change_table_load_order(T, O).
--spec change_table_majority(Tab::table(), M::boolean()) -> t_result(ok).
+-spec change_table_majority(Tab::table(), M::boolean()) -> t_result('ok').
change_table_majority(T, M) ->
mnesia_schema:change_table_majority(T, M).
--spec set_master_nodes(Ns::[node()]) -> 'ok' | {'error', Reason::term()}.
+-spec set_master_nodes(Ns::[node()]) -> result().
set_master_nodes(Nodes) when is_list(Nodes) ->
UseDir = system_info(use_dir),
IsRunning = system_info(is_running),
@@ -2900,8 +2926,7 @@ log_valid_master_nodes(Cstructs, Nodes, UseDir, IsRunning) ->
Args = lists:map(Fun, Cstructs),
mnesia_recover:log_master_nodes(Args, UseDir, IsRunning).
--spec set_master_nodes(Tab::table(), Ns::[node()]) ->
- 'ok' | {'error', Reason::term()}.
+-spec set_master_nodes(Tab::table(), Ns::[node()]) -> result().
set_master_nodes(Tab, Nodes) when is_list(Nodes) ->
UseDir = system_info(use_dir),
IsRunning = system_info(is_running),
@@ -2956,7 +2981,7 @@ set_master_nodes(Tab, Nodes) ->
dump_log() ->
mnesia_controller:sync_dump_log(user).
--spec sync_log() -> 'ok' | {'error', Reason::term()}.
+-spec sync_log() -> result().
sync_log() ->
mnesia_monitor:sync_log(latest_log).
@@ -2976,11 +3001,11 @@ report_event(Event) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Snmp
--spec snmp_open_table(Tab::table(), Snmp::snmp_struct()) -> ok.
+-spec snmp_open_table(Tab::table(), Snmp::snmp_struct()) -> 'ok'.
snmp_open_table(Tab, Us) ->
mnesia_schema:add_snmp(Tab, Us).
--spec snmp_close_table(Tab::table()) -> ok.
+-spec snmp_close_table(Tab::table()) -> 'ok'.
snmp_close_table(Tab) ->
mnesia_schema:del_snmp(Tab).
@@ -3126,11 +3151,11 @@ snmp_filter_key(undefined, RowIndex, Tab, Store) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Textfile access
--spec load_textfile(File::file:filename()) -> t_result(ok) | {'error', term()}.
+-spec load_textfile(File::file:filename()) -> t_result('ok') | {'error', term()}.
load_textfile(F) ->
mnesia_text:load_textfile(F).
--spec dump_to_textfile(File :: file:filename()) -> 'ok' | 'error' | {'error', term()}.
+-spec dump_to_textfile(File :: file:filename()) -> result() | 'error'.
dump_to_textfile(F) ->
mnesia_text:dump_to_textfile(F).
@@ -3144,7 +3169,7 @@ table(Tab) ->
-spec table(Tab::table(), Options) -> qlc:query_handle() when
Options :: Option | [Option],
Option :: MnesiaOpt | QlcOption,
- MnesiaOpt :: {'traverse', SelectOp} | {lock, lock_kind()} | {n_objects, non_neg_integer()},
+ MnesiaOpt :: {'traverse', SelectOp} | {'lock', lock_kind()} | {'n_objects', non_neg_integer()},
SelectOp :: 'select' | {'select', ets:match_spec()},
QlcOption :: {'key_equality', '==' | '=:='}.
table(Tab,Opts) ->
diff --git a/lib/mnesia/src/mnesia.hrl b/lib/mnesia/src/mnesia.hrl
index fe48a6fe3d..4ddfc17a06 100644
--- a/lib/mnesia/src/mnesia.hrl
+++ b/lib/mnesia/src/mnesia.hrl
@@ -44,6 +44,8 @@
-define(SAFE(OP), try (OP) catch error:_ -> ok end).
-define(CATCH(OP), try (OP) catch _:_Reason -> {'EXIT', _Reason} end).
+-define(CATCHU(OP), fun() -> try (OP) catch _:_Reason -> {'EXIT', _Reason} end end()).
+
-define(catch_val(Var), (try ?ets_lookup_element(mnesia_gvar, Var, 2)
catch error:_ -> {'EXIT', {badarg, []}} end)).
diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl
index 5ee176f911..5bbd91184f 100644
--- a/lib/mnesia/src/mnesia_controller.erl
+++ b/lib/mnesia/src/mnesia_controller.erl
@@ -460,8 +460,6 @@ connect_nodes(Ns) ->
connect_nodes(Ns, UserFun) ->
case mnesia:system_info(is_running) of
- no ->
- {error, {node_not_running, node()}};
yes ->
Pid = spawn_link(?MODULE,connect_nodes2,[self(),Ns, UserFun]),
receive
@@ -478,7 +476,9 @@ connect_nodes(Ns, UserFun) ->
end;
{'EXIT', Pid, Reason} ->
{error, Reason}
- end
+ end;
+ _ -> %% no, starting or stopping not ready to make a connection yet
+ {error, {node_not_running, node()}}
end.
connect_nodes2(Father, Ns, UserFun) ->
@@ -1580,7 +1580,14 @@ initial_safe_loads() ->
end.
last_consistent_replica(Tab, Downs) ->
- Cs = val({Tab, cstruct}),
+ case ?catch_val({Tab, cstruct}) of
+ #cstruct{} = Cs ->
+ last_consistent_replica(Cs, Tab, Downs);
+ _ ->
+ false
+ end.
+
+last_consistent_replica(Cs, Tab, Downs) ->
Storage = mnesia_lib:cs_to_storage_type(node(), Cs),
Ram = Cs#cstruct.ram_copies,
Disc = Cs#cstruct.disc_copies,
diff --git a/lib/mnesia/src/mnesia_dumper.erl b/lib/mnesia/src/mnesia_dumper.erl
index 0a815b709a..1264efeb68 100644
--- a/lib/mnesia/src/mnesia_dumper.erl
+++ b/lib/mnesia/src/mnesia_dumper.erl
@@ -68,7 +68,10 @@ incr_log_writes() ->
Left = mnesia_lib:incr_counter(trans_log_writes_left, -1),
if
Left =:= 0 ->
- adjust_log_writes(true);
+ %% It doesn't matter which process adjusts counters and sends
+ %% cast to a dumper so to avoid potential lag on global:set_lock
+ %% we delegate it to new process
+ spawn(fun() -> adjust_log_writes(true) end);
true ->
ignore
end.
diff --git a/lib/mnesia/src/mnesia_event.erl b/lib/mnesia/src/mnesia_event.erl
index 49b3990086..0e96d5c73f 100644
--- a/lib/mnesia/src/mnesia_event.erl
+++ b/lib/mnesia/src/mnesia_event.erl
@@ -36,6 +36,8 @@
dumped_core = false, %% only dump fatal core once
args}).
+-include("mnesia.hrl").
+
%%%----------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------
@@ -131,14 +133,14 @@ handle_system_event({mnesia_down, Node}, State) ->
"must be restarted. Forcing shutdown "
"after mnesia_down from ~p...~n",
report_fatal(Msg, [Node], nocore, State#state.dumped_core),
- catch exit(whereis(mnesia_monitor), fatal),
+ ?SAFE(exit(whereis(mnesia_monitor), fatal)),
{ok, State};
{UserMod, UserFunc} ->
Msg = "Warning: A fallback is installed and Mnesia got mnesia_down "
"from ~p. ~n",
report_info(Msg, [Node]),
- case catch apply(UserMod, UserFunc, [Node]) of
- {'EXIT', {undef, _Reason}} ->
+ case ?CATCH(apply(UserMod, UserFunc, [Node])) of
+ {'EXIT', {undef, _R}} ->
%% Backward compatibility
apply(UserMod, UserFunc, []);
{'EXIT', Reason} ->
diff --git a/lib/mnesia/src/mnesia_kernel_sup.erl b/lib/mnesia/src/mnesia_kernel_sup.erl
index a761d5eed0..7f226d92c4 100644
--- a/lib/mnesia/src/mnesia_kernel_sup.erl
+++ b/lib/mnesia/src/mnesia_kernel_sup.erl
@@ -42,6 +42,7 @@ init([]) ->
worker_spec(mnesia_locker, timer:seconds(3), ProcLib),
worker_spec(mnesia_recover, timer:minutes(3), [gen_server]),
worker_spec(mnesia_tm, timer:seconds(30), ProcLib),
+ worker_spec(mnesia_rpc, timer:seconds(3), [gen_server]),
supervisor_spec(mnesia_checkpoint_sup),
worker_spec(mnesia_controller, timer:seconds(3), [gen_server]),
worker_spec(mnesia_late_loader, timer:seconds(3), ProcLib)
diff --git a/lib/mnesia/src/mnesia_lib.erl b/lib/mnesia/src/mnesia_lib.erl
index 6abc05fade..dd28686f78 100644
--- a/lib/mnesia/src/mnesia_lib.erl
+++ b/lib/mnesia/src/mnesia_lib.erl
@@ -675,41 +675,41 @@ mkcore(CrashInfo) ->
% dbg_out("Making a Mnesia core dump...~p~n", [CrashInfo]),
Nodes = [node() |nodes()],
%%TidLocks = (catch ets:tab2list(mnesia_tid_locks)),
- HeldLocks = (catch mnesia:system_info(held_locks)),
+ HeldLocks = ?CATCHU(mnesia:system_info(held_locks)),
Core = [
CrashInfo,
{time, {date(), time()}},
{self, proc_dbg_info(self())},
- {nodes, catch rpc:multicall(Nodes, ?MODULE, get_node_number, [])},
- {applications, catch lists:sort(application:loaded_applications())},
- {flags, catch init:get_arguments()},
- {code_path, catch code:get_path()},
- {code_loaded, catch lists:sort(code:all_loaded())},
- {etsinfo, catch ets_info(ets:all())},
-
- {version, catch mnesia:system_info(version)},
- {schema, catch ets:tab2list(schema)},
- {gvar, catch ets:tab2list(mnesia_gvar)},
- {master_nodes, catch mnesia_recover:get_master_node_info()},
-
- {processes, catch procs()},
- {relatives, catch relatives()},
- {workers, catch workers(mnesia_controller:get_workers(2000))},
- {locking_procs, catch locking_procs(HeldLocks)},
+ {nodes, ?CATCHU(rpc:multicall(Nodes, ?MODULE, get_node_number, []))},
+ {applications, ?CATCHU(lists:sort(application:loaded_applications()))},
+ {flags, ?CATCHU(init:get_arguments())},
+ {code_path, ?CATCHU(code:get_path())},
+ {code_loaded, ?CATCHU(lists:sort(code:all_loaded()))},
+ {etsinfo, ?CATCHU(ets_info(ets:all()))},
+
+ {version, ?CATCHU(mnesia:system_info(version))},
+ {schema, ?CATCHU(ets:tab2list(schema))},
+ {gvar, ?CATCHU(ets:tab2list(mnesia_gvar))},
+ {master_nodes, ?CATCHU(mnesia_recover:get_master_node_info())},
+
+ {processes, ?CATCHU(procs())},
+ {relatives, ?CATCHU(relatives())},
+ {workers, ?CATCHU(workers(mnesia_controller:get_workers(2000)))},
+ {locking_procs, ?CATCHU(locking_procs(HeldLocks))},
{held_locks, HeldLocks},
- {lock_queue, catch mnesia:system_info(lock_queue)},
- {load_info, catch mnesia_controller:get_info(2000)},
- {trans_info, catch mnesia_tm:get_info(2000)},
+ {lock_queue, ?CATCHU(mnesia:system_info(lock_queue))},
+ {load_info, ?CATCHU(mnesia_controller:get_info(2000))},
+ {trans_info, ?CATCHU(mnesia_tm:get_info(2000))},
- {schema_file, catch file:read_file(tab2dat(schema))},
- {dir_info, catch dir_info()},
- {logfile, catch {ok, read_log_files()}}
+ {schema_file, ?CATCHU(file:read_file(tab2dat(schema)))},
+ {dir_info, ?CATCHU(dir_info())},
+ {logfile, ?CATCHU({ok, read_log_files()})}
],
term_to_binary(Core).
procs() ->
- Fun = fun(P) -> {P, (catch lists:zf(fun proc_info/1, process_info(P)))} end,
+ Fun = fun(P) -> {P, (?CATCH(lists:zf(fun proc_info/1, process_info(P))))} end,
lists:map(Fun, processes()).
proc_info({registered_name, Val}) -> {true, Val};
@@ -730,7 +730,7 @@ have_majority(_Tab, AllNodes, HaveNodes) ->
length(Present) > length(Missing).
read_log_files() ->
- [{F, catch file:read_file(F)} || F <- mnesia_log:log_files()].
+ [{F, ?CATCH(file:read_file(F))} || F <- mnesia_log:log_files()].
dir_info() ->
{ok, Cwd} = file:get_cwd(),
@@ -739,7 +739,7 @@ dir_info() ->
{mnesia_dir, Dir, file:read_file_info(Dir)}] ++
case file:list_dir(Dir) of
{ok, Files} ->
- [{mnesia_file, F, catch file:read_file_info(dir(F))} || F <- Files];
+ [{mnesia_file, F, ?CATCH(file:read_file_info(dir(F)))} || F <- Files];
Other ->
[Other]
end.
@@ -854,7 +854,7 @@ vcore(Bin) when is_binary(Bin) ->
Core = binary_to_term(Bin),
Fun = fun({Item, Info}) ->
show("***** ~tp *****~n", [Item]),
- case catch vcore_elem({Item, Info}) of
+ case ?CATCHU(vcore_elem({Item, Info})) of
{'EXIT', Reason} ->
show("{'EXIT', ~tp}~n", [Reason]);
_ -> ok
@@ -1446,7 +1446,7 @@ eval_debug_fun(FunId, EvalContext, EvalFile, EvalLine) ->
ok
end
end
- catch error ->
+ catch _:_ ->
ok
end.
diff --git a/lib/mnesia/src/mnesia_loader.erl b/lib/mnesia/src/mnesia_loader.erl
index 2cdae0c906..a2fde5c808 100644
--- a/lib/mnesia/src/mnesia_loader.erl
+++ b/lib/mnesia/src/mnesia_loader.erl
@@ -320,6 +320,11 @@ start_remote_sender(Node,Tab,Storage) ->
table_init_fun(SenderPid, Storage) ->
fun(read) ->
+ % We want to store subscribed mnesia table events received during
+ % table copying for later processing to not let receiver message queue
+ % to grow too much (which in consequence would slow down the whole copying process)
+ SubscrCache = ets:new(subscr_cache, [private, ordered_set]),
+ put(mnesia_table_receiver_subscr_cache, SubscrCache),
Receiver = self(),
SenderPid ! {Receiver, more},
get_data(SenderPid, Receiver, Storage);
@@ -471,6 +476,10 @@ get_data(Pid, TabRec, Storage) ->
end;
{'EXIT', Pid, Reason} ->
handle_exit(Pid, Reason),
+ get_data(Pid, TabRec, Storage);
+ {mnesia_table_event, _} = SubscrEvent ->
+ SubscrCache = get(mnesia_table_receiver_subscr_cache),
+ ets:insert(SubscrCache, {erlang:unique_integer([monotonic]), SubscrEvent}),
get_data(Pid, TabRec, Storage)
end.
@@ -542,7 +551,7 @@ init_table(Tab, _, Fun, _DetsInfo,_) ->
finish_copy(Storage,Tab,Cs,SenderPid,DatBin,OrigTabRec) ->
TabRef = {Storage, Tab},
- subscr_receiver(TabRef, Cs#cstruct.record_name),
+ subscr_postprocess(TabRef, Cs#cstruct.record_name),
case handle_last(TabRef, Cs#cstruct.type, DatBin) of
ok ->
mnesia_index:init_index(Tab, Storage),
@@ -558,8 +567,37 @@ finish_copy(Storage,Tab,Cs,SenderPid,DatBin,OrigTabRec) ->
down(Tab, Storage)
end.
+subscr_postprocess(TabRef, RecName) ->
+ % process events received during table copying
+ case get(mnesia_table_receiver_subscr_cache) of
+ undefined ->
+ ok;
+ SubscrCache ->
+ ets:foldl(
+ fun({_, Event}, _Acc) ->
+ handle_subscr_event(Event, TabRef, RecName)
+ end, ok, SubscrCache),
+ ets:delete(SubscrCache)
+ end,
+ % and all remaining events
+ subscr_receiver(TabRef, RecName).
+
subscr_receiver(TabRef = {_, Tab}, RecName) ->
receive
+ {mnesia_table_event, {_Op, Val, _Tid}} = Event
+ when element(1, Val) =:= Tab; element(1, Val) =:= schema ->
+ handle_subscr_event(Event, TabRef, RecName),
+ subscr_receiver(TabRef, RecName);
+
+ {'EXIT', Pid, Reason} ->
+ handle_exit(Pid, Reason),
+ subscr_receiver(TabRef, RecName)
+ after 0 ->
+ ok
+ end.
+
+handle_subscr_event(Event, TabRef = {_, Tab}, RecName) ->
+ case Event of
{mnesia_table_event, {Op, Val, _Tid}}
when element(1, Val) =:= Tab ->
if
@@ -567,8 +605,7 @@ subscr_receiver(TabRef = {_, Tab}, RecName) ->
handle_event(TabRef, Op, Val);
true ->
handle_event(TabRef, Op, setelement(1, Val, RecName))
- end,
- subscr_receiver(TabRef, RecName);
+ end;
{mnesia_table_event, {Op, Val, _Tid}} when element(1, Val) =:= schema ->
%% clear_table is faked via two schema events
@@ -576,14 +613,7 @@ subscr_receiver(TabRef = {_, Tab}, RecName) ->
case Op of
delete -> handle_event(TabRef, clear_table, {Tab, all});
_ -> ok
- end,
- subscr_receiver(TabRef, RecName);
-
- {'EXIT', Pid, Reason} ->
- handle_exit(Pid, Reason),
- subscr_receiver(TabRef, RecName)
- after 0 ->
- ok
+ end
end.
handle_event(TabRef, write, Rec) ->
@@ -654,8 +684,8 @@ down(Tab, Storage) ->
mnesia_lib:dets_sync_close(Tab),
file:delete(TmpFile);
{ext, Alias, Mod} ->
- catch Mod:close_table(Alias, Tab),
- catch Mod:delete_table(Alias, Tab)
+ ?CATCHU(Mod:close_table(Alias, Tab)),
+ ?CATCHU(Mod:delete_table(Alias, Tab))
end,
mnesia_checkpoint:tm_del_copy(Tab, node()),
mnesia_controller:sync_del_table_copy_whereabouts(Tab, node()),
diff --git a/lib/mnesia/src/mnesia_monitor.erl b/lib/mnesia/src/mnesia_monitor.erl
index 3f2ee8d978..6ea8a0544b 100644
--- a/lib/mnesia/src/mnesia_monitor.erl
+++ b/lib/mnesia/src/mnesia_monitor.erl
@@ -83,9 +83,9 @@
going_down = [], tm_started = false, early_connects = [],
connecting, mq = [], remote_node_status = []}).
--define(current_protocol_version, {8,4}).
+-define(current_protocol_version, {8,5}).
--define(previous_protocol_version, {8,3}).
+-define(previous_protocol_version, {8,4}).
start() ->
gen_server:start_link({local, ?MODULE}, ?MODULE,
@@ -196,7 +196,7 @@ protocol_version() ->
%% A sorted list of acceptable protocols the
%% preferred protocols are first in the list
acceptable_protocol_versions() ->
- [protocol_version(), ?previous_protocol_version].
+ [protocol_version(), ?previous_protocol_version, {8,3}].
needs_protocol_conversion(Node) ->
case {?catch_val({protocol, Node}), protocol_version()} of
@@ -409,7 +409,7 @@ handle_call({unsafe_close_log, Name}, _From, State) ->
{reply, ok, State};
handle_call({unsafe_create_external, Tab, Alias, Mod, Cs}, _From, State) ->
- case catch Mod:create_table(Alias, Tab, mnesia_schema:cs2list(Cs)) of
+ case ?CATCH(Mod:create_table(Alias, Tab, mnesia_schema:cs2list(Cs))) of
{'EXIT', ExitReason} ->
{reply, {error, ExitReason}, State};
Reply ->
diff --git a/lib/mnesia/src/mnesia_recover.erl b/lib/mnesia/src/mnesia_recover.erl
index 2ccea1ea6d..8749b625a1 100644
--- a/lib/mnesia/src/mnesia_recover.erl
+++ b/lib/mnesia/src/mnesia_recover.erl
@@ -449,6 +449,9 @@ wait_for_decision(D, InitBy, N) ->
if
Outcome =:= committed -> {Tid, committed};
Outcome =:= aborted -> {Tid, aborted};
+ InitBy == startup ->
+ {ok, Res} = call({wait_for_decision, D}),
+ {Tid, Res};
Outcome =:= presume_abort ->
case N > Max of
true -> {Tid, aborted};
@@ -460,10 +463,7 @@ wait_for_decision(D, InitBy, N) ->
%% Wait a while for active transactions
%% to end and try again
timer:sleep(100),
- wait_for_decision(D, InitBy, N);
- InitBy == startup ->
- {ok, Res} = call({wait_for_decision, D}),
- {Tid, Res}
+ wait_for_decision(D, InitBy, N)
end.
still_pending([Tid | Pending]) ->
diff --git a/lib/mnesia/src/mnesia_rpc.erl b/lib/mnesia/src/mnesia_rpc.erl
new file mode 100644
index 0000000000..82b8ede020
--- /dev/null
+++ b/lib/mnesia/src/mnesia_rpc.erl
@@ -0,0 +1,119 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% Don't use the system rpc server since it may overload other
+%% applications when using a lot of dirty read operations.
+
+-module(mnesia_rpc).
+-behaviour(gen_server).
+
+-export([start/0,
+ call/4
+ ]).
+
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3
+ ]).
+
+-include("mnesia.hrl").
+
+start() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [self()],
+ [{timeout, infinity} %%, {debug, [trace]}
+ ]).
+
+call(Node, M, F, Args) ->
+ case ?catch_val({protocol, Node}) of
+ {Ver, _} when Ver > {8,4} ->
+ try gen_server:call({?MODULE, Node}, {apply, M, F, Args}, infinity)
+ catch
+ _:Reason -> {badrpc, {'EXIT', Reason}}
+ end;
+ _ ->
+ rpc:call(Node, M, F, Args)
+ end.
+
+init([_Parent]) ->
+ {ok, #{}}.
+
+handle_call({apply, mnesia_lib, db_get=Func, Args}, From, State) ->
+ apply_lib(Func, Args, From, State);
+handle_call({apply, mnesia_lib, db_last=Func, Args}, From, State) ->
+ apply_lib(Func, Args, From, State);
+handle_call({apply, mnesia_lib, db_first=Func, Args}, From, State) ->
+ apply_lib(Func, Args, From, State);
+handle_call({apply, mnesia_lib, db_next_key=Func, Args}, From, State) ->
+ apply_lib(Func, Args, From, State);
+handle_call({apply, mnesia_lib, db_prev_key=Func, Args}, From, State) ->
+ apply_lib(Func, Args, From, State);
+handle_call({apply, Mod, Func, Args}, From, State) ->
+ Fun = apply_fun(Mod, Func, Args, From),
+ _Pid = spawn_link(Fun),
+ {noreply, State};
+
+handle_call(Msg, _From, State) ->
+ mnesia_lib:error("~p got unexpected call: ~tp~n", [?MODULE, Msg]),
+ {reply, badop, State}.
+
+handle_cast(Msg, State) ->
+ mnesia_lib:error("~p got unexpected cast: ~tp~n", [?MODULE, Msg]),
+ {noreply, State}.
+
+handle_info(Msg, State) ->
+ mnesia_lib:error("~p got unexpected info: ~tp~n", [?MODULE, Msg]),
+ {noreply, State}.
+
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+%%%%
+
+apply_lib(Func, [Tab|_] = Args, From, State) ->
+ try
+ Ram = ?catch_val({Tab, storage_type}),
+ if Ram =:= ram_copies; Ram =:= disc_copies ->
+ {reply, apply(mnesia_lib, Func, [Ram|Args]), State};
+ true ->
+ Fun = apply_fun(mnesia_lib, Func, Args, From),
+ _Pid = spawn_link(Fun),
+ {noreply, State}
+ end
+ catch throw:Res -> {reply, Res, State};
+ _:Reason -> {reply, {badrpc, {'EXIT', Reason}}, State}
+ end.
+
+apply_fun(Mod, Func, Args, From) ->
+ fun() ->
+ Result = try apply(Mod, Func, Args)
+ catch throw:Res -> Res;
+ _:Reason -> {badrpc, {'EXIT', Reason}}
+ end,
+ gen_server:reply(From, Result)
+ end.
diff --git a/lib/mnesia/src/mnesia_schema.erl b/lib/mnesia/src/mnesia_schema.erl
index 5987c844d7..c0da0674f0 100644
--- a/lib/mnesia/src/mnesia_schema.erl
+++ b/lib/mnesia/src/mnesia_schema.erl
@@ -1271,7 +1271,7 @@ verify_nodes(Cs) ->
lists:foreach(AtomCheck, Nodes).
verify(Expected, Fun, Error) when is_function(Fun) ->
- do_verify(Expected, catch Fun(), Error);
+ do_verify(Expected, ?CATCH(Fun()), Error);
verify(Expected, Actual, Error) ->
do_verify(Expected, Actual, Error).
diff --git a/lib/mnesia/src/mnesia_sp.erl b/lib/mnesia/src/mnesia_sp.erl
index d65c659690..0c56107c41 100644
--- a/lib/mnesia/src/mnesia_sp.erl
+++ b/lib/mnesia/src/mnesia_sp.erl
@@ -30,12 +30,15 @@
init_proc(Who, Mod, Fun, Args) ->
mnesia_lib:verbose("~p starting: ~p~n", [Who, self()]),
- case catch apply(Mod, Fun, Args) of
- {'EXIT', Reason} ->
- mnesia_monitor:terminate_proc(Who, Reason, Args),
+ try
+ apply(Mod, Fun, Args)
+ catch
+ exit:Reason when Reason =:= shutdown; Reason =:= kill; Reason =:= normal ->
+ mnesia_monitor:terminate_proc(Who, Reason, Args),
exit(Reason);
- Other ->
- Other
+ _:Reason:ST ->
+ mnesia_monitor:terminate_proc(Who, {Reason,ST}, Args),
+ exit(Reason)
end.
diff --git a/lib/mnesia/src/mnesia_sup.erl b/lib/mnesia/src/mnesia_sup.erl
index 3e5792900b..fd5156d2a4 100644
--- a/lib/mnesia/src/mnesia_sup.erl
+++ b/lib/mnesia/src/mnesia_sup.erl
@@ -88,7 +88,7 @@ add_event_handler() ->
kill() ->
Mnesia = [mnesia_fallback | mnesia:ms()],
- Kill = fun(Name) -> catch exit(whereis(Name), kill) end,
+ Kill = fun(Name) -> try exit(whereis(Name), kill) catch _:_ -> ok end end,
lists:foreach(Kill, Mnesia),
lists:foreach(fun ensure_dead/1, Mnesia),
timer:sleep(10),
diff --git a/lib/mnesia/src/mnesia_text.erl b/lib/mnesia/src/mnesia_text.erl
index cc21621ff4..ee31fdfd27 100644
--- a/lib/mnesia/src/mnesia_text.erl
+++ b/lib/mnesia/src/mnesia_text.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -170,7 +170,7 @@ read_terms(Stream, File, Line, L) ->
end.
read_term_from_stream(Stream, File, Line) ->
- R = io:request(Stream, {get_until,'',erl_scan,tokens,[Line]}),
+ R = io:request(Stream, {get_until,latin1,'',erl_scan,tokens,[Line]}),
case R of
{ok,Toks,EndLine} ->
case erl_parse:parse_term(Toks) of
diff --git a/lib/mnesia/src/mnesia_tm.erl b/lib/mnesia/src/mnesia_tm.erl
index 0264e1f902..9e9661ff60 100644
--- a/lib/mnesia/src/mnesia_tm.erl
+++ b/lib/mnesia/src/mnesia_tm.erl
@@ -2082,9 +2082,9 @@ ask_commit(_Protocol, _Tid, [], _DiscNs, _RamNs, WaitFor, Local) ->
{WaitFor, Local}.
convert_old(sync_asym_trans, Node) ->
- case mnesia_monitor:needs_protocol_conversion(Node) of
- true -> asym_trans;
- false -> sync_asym_trans
+ case ?catch_val({protocol, Node}) of
+ {{8,3}, _} -> asym_trans;
+ _ -> sync_asym_trans
end;
convert_old(Protocol, _) ->
Protocol.
diff --git a/lib/mnesia/test/mnesia_isolation_test.erl b/lib/mnesia/test/mnesia_isolation_test.erl
index 76b0d07b17..a65bfd31e1 100644
--- a/lib/mnesia/test/mnesia_isolation_test.erl
+++ b/lib/mnesia/test/mnesia_isolation_test.erl
@@ -629,7 +629,12 @@ sticky_sync(Config) when is_list(Config) ->
end,
%% Fill 1000 dc records. At the end all dc records should have value 1.
- lists:foreach(TestFun, lists:seq(1,1000)),
+ {Time, ok} = timer:tc(fun() -> lists:foreach(TestFun, lists:seq(1,200)) end),
+ io:format("200 trans done in ~p ~n",[Time div (1000000)]),
+ case (Time div (1000000)) < 20 of
+ false -> lists:foreach(TestFun, lists:seq(201,1000));
+ true -> ignore %% Some virtual test machines are really slow..
+ end,
io:format("Written, check content~n",[]),
All = fun() -> mnesia:select(dc, [ {{dc, '_', 0}, [] ,['$_']} ]) end,
?match({atomic, []}, rpc:call(N1, mnesia, sync_transaction, [All])),
diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl
index 723a85fd2c..f488134391 100644
--- a/lib/mnesia/test/mnesia_trans_access_test.erl
+++ b/lib/mnesia/test/mnesia_trans_access_test.erl
@@ -26,7 +26,8 @@
init_per_group/2, end_per_group/2,
all/0, groups/0]).
--export([write/1, read/1, wread/1, delete/1, delete_object/1,
+-export([write/1, read/1, wread/1, delete/1,
+ delete_object_bag/1, delete_object_set/1,
match_object/1, select/1, select14/1, all_keys/1, transaction/1,
basic_nested/1, mix_of_nested_activities/1,
nested_trans_both_ok/1, nested_trans_child_dies/1,
@@ -63,7 +64,7 @@ end_per_testcase(Func, Conf) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
all() ->
- [write, read, wread, delete, delete_object,
+ [write, read, wread, delete, delete_object_bag, delete_object_set,
match_object, select, select14, all_keys, transaction,
{group, nested_activities}, {group, index_tabs},
{group, index_lifecycle}].
@@ -221,14 +222,26 @@ delete(Config) when is_list(Config) ->
%% Delete matching record
-delete_object(suite) -> [];
-delete_object(Config) when is_list(Config) ->
+delete_object_bag(suite) -> [];
+delete_object_bag(Config) when is_list(Config) ->
[Node1] = Nodes = ?acquire_nodes(1, Config),
+ ?match(ok, delete_object(Node1, bag)),
+ ?verify_mnesia(Nodes, []).
+
+delete_object_set(suite) -> [];
+delete_object_set(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ ?match(ok, delete_object(Node1, set)),
+ ?verify_mnesia(Nodes, []).
+
+delete_object(Node1, Type) ->
Tab = delete_object,
- Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ Schema = [{name, Tab}, {type, Type},
+ {attributes, [k, v]}, {ram_copies, [Node1]}],
?match({atomic, ok}, mnesia:create_table(Schema)),
OneRec = {Tab, 1, 2},
+ OtherRec = {Tab, 1, 3},
?match({aborted, {bad_type, _}},
mnesia:transaction(fun() -> mnesia:delete_object([]) end)),
?match({aborted, {bad_type, _}},
@@ -237,16 +250,88 @@ delete_object(Config) when is_list(Config) ->
mnesia:transaction(fun() -> mnesia:delete_object({Tab, 1}) end)),
?match({atomic, ok},
mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)),
+
+ %% Delete already existing object
?match({atomic, ok},
mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
?match({atomic, ok},
- mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)),
+ mnesia:transaction(fun() ->
+ [OneRec] = mnesia:read(Tab, 1),
+ ok = mnesia:delete_object(OneRec),
+ [] = mnesia:read(Tab, 1),
+ ok
+ end)),
+ ?match([], mnesia:dirty_read(Tab, 1)),
+
+ %% Delete already existing object (written twice)
?match({atomic, ok},
mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
?match({atomic, ok},
mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
?match({atomic, ok},
- mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)),
+ mnesia:transaction(fun() ->
+ [OneRec] = mnesia:read(Tab, 1),
+ ok = mnesia:delete_object(OneRec),
+ [] = mnesia:read(Tab, 1),
+ ok
+ end)),
+ ?match([], mnesia:dirty_read(Tab, 1)),
+
+ %% Delete object written in same transaction
+ ?match({atomic, ok},
+ mnesia:transaction(fun() ->
+ [] = mnesia:read(Tab, 1),
+ ok = mnesia:write(OneRec),
+ ok = mnesia:delete_object(OneRec),
+ [] = mnesia:read(Tab, 1),
+ ok
+ end)),
+ ?match([], mnesia:dirty_read(Tab, 1)),
+
+ %% Delete other object than written in same transaction
+ ?match({atomic, ok},
+ mnesia:transaction(fun() ->
+ [] = mnesia:read(Tab, 1),
+ ok = mnesia:write(OneRec),
+ ok = mnesia:delete_object(OtherRec),
+ [OneRec] = mnesia:read(Tab, 1),
+ ok
+ end)),
+ ?match([OneRec], mnesia:dirty_read(Tab, 1)),
+
+ %% Delete other object than already existing
+ ?match({atomic, ok},
+ mnesia:transaction(fun() ->
+ [OneRec] = mnesia:read(Tab, 1),
+ ok = mnesia:delete_object(OtherRec),
+ [OneRec] = mnesia:read(Tab, 1),
+ ok
+ end)),
+ ?match([OneRec], mnesia:dirty_read(Tab, 1)),
+
+ %% Delete object in combination with delete
+ ?match({atomic, ok},
+ mnesia:transaction(fun() ->
+ [OneRec] = mnesia:read(Tab, 1),
+ ok = mnesia:delete({Tab, 1}),
+ ok = mnesia:delete_object(OtherRec),
+ [] = mnesia:read(Tab, 1),
+ ok
+ end)),
+ ?match([], mnesia:dirty_read(Tab, 1)),
+
+ %% Several delete_object in same transaction (last on non existing record)
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() ->
+ [OneRec] = mnesia:read(Tab, 1),
+ ok = mnesia:delete_object(OneRec),
+ ok = mnesia:delete_object(OtherRec),
+ [] = mnesia:read(Tab, 1),
+ ok
+ end)),
+ ?match([], mnesia:dirty_read(Tab, 1)),
?match({'EXIT', {aborted, no_transaction}}, mnesia:delete_object(OneRec)),
@@ -255,7 +340,8 @@ delete_object(Config) when is_list(Config) ->
?match({aborted, {bad_type, Tab, _}},
mnesia:transaction(fun() -> mnesia:delete_object({Tab, {['$5']}, 21}) end)),
- ?verify_mnesia(Nodes, []).
+ ?match({atomic, ok}, mnesia:delete_table(Tab)),
+ ok.
%% Read matching records
diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk
index ae849f2771..a5832068a1 100644
--- a/lib/mnesia/vsn.mk
+++ b/lib/mnesia/vsn.mk
@@ -1 +1 @@
-MNESIA_VSN = 4.16.3
+MNESIA_VSN = 4.19
diff --git a/lib/observer/Makefile b/lib/observer/Makefile
index 8483922f76..ffd73051b9 100644
--- a/lib/observer/Makefile
+++ b/lib/observer/Makefile
@@ -36,4 +36,6 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=runtime_tools et wx
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/observer/doc/src/Makefile b/lib/observer/doc/src/Makefile
index e843772f0b..459aeeea0b 100644
--- a/lib/observer/doc/src/Makefile
+++ b/lib/observer/doc/src/Makefile
@@ -27,16 +27,11 @@ VSN=$(OBSERVER_VSN)
APPLICATION=observer
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
XML_REF1_FILES = \
- cdv.xml
+ cdv_cmd.xml
XML_REF3_FILES = \
crashdump.xml \
observer.xml \
@@ -57,91 +52,16 @@ XML_CHAPTER_FILES = \
BOOK_FILES = book.xml
-
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES) $(XML_REF1_FILES) $(XML_REF3_FILES) \
$(XML_APPLICATION_FILES) $(XML_REF6_FILES)
-ONLY_HTML_FILE =
-
-GIF_FILES = \
+IMAGE_FILES = \
et_processes.gif \
et_modsprocs.gif
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN1_FILES = $(XML_REF1_FILES:%.xml=$(MAN1DIR)/%.1)
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
+NO_CHUNKS = crashdump.xml
# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE) $(ONLY_HTML_FILE:%=$(HTMLDIR)/%)
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN1DIR)/*
- rm -f $(MAN3DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-
-$(HTMLDIR)/$(ONLY_HTML_FILE):
- $(INSTALL_DATA) $(ONLY_HTML_FILE) $@
-
-man: $(MAN1_FILES) $(MAN3_FILES) $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DATA) $(MAN1DIR)/* "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
-
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/observer/doc/src/cdv.xml b/lib/observer/doc/src/cdv_cmd.xml
index df1032780a..32f8f392cd 100644
--- a/lib/observer/doc/src/cdv.xml
+++ b/lib/observer/doc/src/cdv_cmd.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
diff --git a/lib/observer/doc/src/crashdump.xml b/lib/observer/doc/src/crashdump.xml
index 62c6ff1f25..2ea88bb3fd 100644
--- a/lib/observer/doc/src/crashdump.xml
+++ b/lib/observer/doc/src/crashdump.xml
@@ -42,7 +42,7 @@
crashdumps.</p>
<p>For details about how to get started with the Crashdump Viewer, see the
- <seealso marker="crashdump_ug"><c>User's Guide</c></seealso>.</p>
+ <seeguide marker="crashdump_ug"><c>User's Guide</c></seeguide>.</p>
</description>
<funcs>
<func>
diff --git a/lib/observer/doc/src/crashdump_ug.xml b/lib/observer/doc/src/crashdump_ug.xml
index 4ba057c3fb..026bfa48f8 100644
--- a/lib/observer/doc/src/crashdump_ug.xml
+++ b/lib/observer/doc/src/crashdump_ug.xml
@@ -50,10 +50,10 @@
<p>Under Windows, the batch file <c>cdv.bat</c> can be used.</p>
<p>Crashdump Viewer can also be started from
- an Erlang node by calling <seealso
- marker="crashdump_viewer#start/0">crashdump_viewer:start/0</seealso>
- or <seealso
- marker="crashdump_viewer#start/1">crashdump_viewer:start/1</seealso>.</p>
+ an Erlang node by calling <seemfa
+ marker="crashdump_viewer#start/0">crashdump_viewer:start/0</seemfa>
+ or <seemfa
+ marker="crashdump_viewer#start/1">crashdump_viewer:start/1</seemfa>.</p>
</section>
<section>
@@ -133,7 +133,7 @@
in the raw crashdump, or in some way differ from the fields in
the raw crashdump. For details about other fields, see
the
- <seealso marker="erts:users_guide">ERTS User's Guide</seealso>, section
+ <seeguide marker="erts:index">ERTS User's Guide</seeguide>, section
"How to interpret the Erlang crash dumps". That section can also
be opened from the <em>Help</em> menu in the main window.
There are also links from the following sections to related information
@@ -180,7 +180,7 @@
</taglist>
<p>For details, see
- <seealso marker="erts:crash_dump#general_info">General Information</seealso>
+ <seeguide marker="erts:crash_dump#general_info">General Information</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -204,7 +204,7 @@
<em>Properties for &lt;pid&gt;</em>.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#processes">Process Information</seealso>
+ <seeguide marker="erts:crash_dump#processes">Process Information</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -223,7 +223,7 @@
port.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#ports">Port Information</seealso>
+ <seeguide marker="erts:crash_dump#ports">Port Information</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -247,7 +247,7 @@
&lt;pid&gt;</em>.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#ets_tables">ETS Tables</seealso>
+ <seeguide marker="erts:crash_dump#ets_tables">ETS Tables</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -266,7 +266,7 @@
<p>Double-clicking a row in the <em>Timers</em> tab has no effect.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#timers">Timers</seealso>
+ <seeguide marker="erts:crash_dump#timers">Timers</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -283,7 +283,7 @@
'Identifier'</em>.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#scheduler">Scheduler Information</seealso>
+ <seeguide marker="erts:crash_dump#scheduler">Scheduler Information</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -302,7 +302,7 @@
<p>Double-clicking a row in the <em>Funs</em> tab has no effect.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#funs">Fun Information</seealso>
+ <seeguide marker="erts:crash_dump#funs">Fun Information</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -319,7 +319,7 @@
in the <em>Atoms</em> tab.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#atoms">Atoms</seealso>
+ <seeguide marker="erts:crash_dump#atoms">Atoms</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -360,7 +360,7 @@
emulator is debug-compiled) or error information.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#distribution_info">Distribution Information</seealso>
+ <seeguide marker="erts:crash_dump#distribution_info">Distribution Information</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -378,7 +378,7 @@
&lt;mod&gt;</em>.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#loaded_modules">Loaded Module Information</seealso>
+ <seeguide marker="erts:crash_dump#loaded_modules">Loaded Module Information</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
@@ -393,7 +393,7 @@
<taglist>
<tag><em>Memory</em></tag>
<item><p>See
- <seealso marker="erts:crash_dump#memory">Memory Information</seealso>
+ <seeguide marker="erts:crash_dump#memory">Memory Information</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.</p></item>
<tag><em>Allocator Summary</em></tag>
@@ -401,12 +401,12 @@
<tag><em>&lt;Allocator&gt;</em></tag>
<item><p>One entry per allocator. See
- <seealso marker="erts:crash_dump#allocator">Allocator</seealso>
+ <seeguide marker="erts:crash_dump#allocator">Allocator</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.</p></item>
<tag><em>Allocated Areas</em></tag>
<item><p>See
- <seealso marker="erts:crash_dump#allocated_areas">Allocated Areas</seealso>
+ <seeguide marker="erts:crash_dump#allocated_areas">Allocated Areas</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.</p></item>
</taglist>
@@ -421,7 +421,7 @@
or <em>Internal ETS Tables</em>.</p>
<p>For details, see
- <seealso marker="erts:crash_dump#internal_tables">Internal Table Information</seealso>
+ <seeguide marker="erts:crash_dump#internal_tables">Internal Table Information</seeguide>
in section "How to Interpret the Erlang Crash Dumps" in ERTS.
</p>
</section>
diff --git a/lib/observer/doc/src/etop.xml b/lib/observer/doc/src/etop.xml
index f0acc7b5d8..e0e016babb 100644
--- a/lib/observer/doc/src/etop.xml
+++ b/lib/observer/doc/src/etop.xml
@@ -93,7 +93,7 @@
</taglist>
<p>For details about Erlang Top, see the
- <seealso marker="etop_ug">User's Guide</seealso>.</p>
+ <seeguide marker="etop_ug">User's Guide</seeguide>.</p>
</description>
<funcs>
@@ -116,7 +116,7 @@
</type>
<desc>
<p>Starts <c>etop</c>. To view the possible options, use
- <seealso marker="#help/0"><c>help/0</c></seealso>.</p>
+ <seemfa marker="#help/0"><c>help/0</c></seemfa>.</p>
</desc>
</func>
<func>
diff --git a/lib/observer/doc/src/etop_ug.xml b/lib/observer/doc/src/etop_ug.xml
index d663b089c2..4ffe3de9d7 100644
--- a/lib/observer/doc/src/etop_ug.xml
+++ b/lib/observer/doc/src/etop_ug.xml
@@ -118,12 +118,12 @@ Pid Name or Initial Func Time Reds Memory MsgQ Current Func
% <input>etop -node tiger@durin -setcookie mycookie -lines 15</input></pre>
<p>A list of all valid Erlang Top configuration parameters is available in
- module <seealso marker="etop"><c>etop</c></seealso>.
+ module <seeerl marker="etop"><c>etop</c></seeerl>.
</p>
<p>The parameters <c>lines</c>, <c>interval</c>, <c>accumulate</c>,
and <c>sort</c> can be changed during runtime with function
- <seealso marker="etop#config/2"><c>etop:config/2</c></seealso>.
+ <seemfa marker="etop#config/2"><c>etop:config/2</c></seemfa>.
</p>
<p><em>Example:</em></p>
<p>Change configuration parameter <c>lines</c> with text-based presentation.
@@ -179,14 +179,14 @@ Pid Name or Initial Func Time Reds Memory MsgQ Current Func
<title>Print to File</title>
<p>At any time, the current Erlang Top display can be dumped to a
text file with function
- <seealso marker="etop#dump/1"><c>etop:dump/1</c></seealso>.
+ <seemfa marker="etop#dump/1"><c>etop:dump/1</c></seemfa>.
</p>
</section>
<section>
<title>Stop</title>
<p>To stop Erlang Top, use function
- <seealso marker="etop#stop/0"><c>etop:stop/0</c></seealso>.
+ <seemfa marker="etop#stop/0"><c>etop:stop/0</c></seemfa>.
</p>
</section>
</chapter>
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index b27de66984..5e752dda1f 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -32,6 +32,36 @@
<p>This document describes the changes made to the Observer
application.</p>
+<section><title>Observer 2.9.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix graph windows flickering on windows.</p>
+ <p>
+ Own Id: OTP-16778</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Observer 2.9.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Observer 2.9.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/observer/doc/src/observer.xml b/lib/observer/doc/src/observer.xml
index 7fb1dd044e..8a3d33da06 100644
--- a/lib/observer/doc/src/observer.xml
+++ b/lib/observer/doc/src/observer.xml
@@ -40,11 +40,11 @@
Erlang systems. The tool Observer displays system information, application
supervisor trees, process information, ETS tables, Mnesia tables,
and contains a front end for Erlang tracing with module
- <seealso marker="ttb"><c>ttb</c></seealso>.
+ <seeerl marker="ttb"><c>ttb</c></seeerl>.
</p>
<p>For details about how to get started, see the
- <seealso marker="observer_ug"><c>User's Guide</c></seealso>.</p>
+ <seeguide marker="observer_ug"><c>User's Guide</c></seeguide>.</p>
</description>
<funcs>
<func>
diff --git a/lib/observer/doc/src/observer_ug.xml b/lib/observer/doc/src/observer_ug.xml
index c9204f2bbe..5fa3470d2f 100644
--- a/lib/observer/doc/src/observer_ug.xml
+++ b/lib/observer/doc/src/observer_ug.xml
@@ -105,7 +105,7 @@
<p>Tab <em>Memory Allocators</em> displays detailed information of the carrier
size and current memory carriers. For details about memory carriers,
see module
- <seealso marker="erts:erts_alloc"><c>erts_alloc</c></seealso>
+ <seecref marker="erts:erts_alloc"><c>erts_alloc</c></seecref>
in application ERTS.</p>
<p>The <c>Max Carrier size</c> column shows the maximum value seen by observer
since the last node change or since the start of the application, i.e. switching
@@ -226,7 +226,7 @@
<tag>Name</tag>
<item><p>The registered name of the port, if any.</p></item>
<tag>Controls</tag>
- <item><p>The name of the command set by <seealso marker="erts:erlang#open_port-2"><c>erlang:open_port/2</c></seealso>.</p></item>
+ <item><p>The name of the command set by <seemfa marker="erts:erlang#open_port/2"><c>erlang:open_port/2</c></seemfa>.</p></item>
<tag>Slot</tag>
<item><p>The internal index of the port.</p></item>
</taglist>
@@ -245,7 +245,7 @@
that name are then traced on all traced nodes.</p>
<p>Option <em>Close</em>
- executes <seealso marker="erts:erlang#port_close-1"><c>erlang:port_close/1</c></seealso>
+ executes <seemfa marker="erts:erlang#port_close/1"><c>erlang:port_close/1</c></seemfa>
on the port under the mouse pointer.</p>
</section>
@@ -262,8 +262,8 @@
information, select the table and activate menu <em>View &gt;
Table information</em>, or right-click and select option <em>Table
info</em>.</p>
- <p>You can use <seealso marker="stdlib:re">regular
- expressions</seealso> and search for objects, and edit or delete them.
+ <p>You can use <seeerl marker="stdlib:re">regular
+ expressions</seeerl> and search for objects, and edit or delete them.
</p>
</section>
@@ -331,11 +331,11 @@
<p>
A few basic match specifications are provided in the tool, and
you can provide your own match specifications. The syntax of match
- specifications is described in the <seealso
- marker="erts:match_spec"><c>ERTS User's Guide</c></seealso>. To simplify
+ specifications is described in the <seeguide
+ marker="erts:match_spec"><c>ERTS User's Guide</c></seeguide>. To simplify
the writing of a match specification, they can also be written as
<c>fun/1</c>. For details, see module
- <seealso marker="stdlib:ms_transform">ms_transform</seealso>
+ <seeerl marker="stdlib:ms_transform">ms_transform</seeerl>
in application STDLIB.
</p>
@@ -346,12 +346,12 @@
The trace settings, including match specifications, can be saved to,
or loaded from, a file.
</p>
- <p>For details about tracing, see module <seealso
- marker="runtime_tools:dbg">dbg</seealso> in application Runtime_Tools
+ <p>For details about tracing, see module <seeerl
+ marker="runtime_tools:dbg">dbg</seeerl> in application Runtime_Tools
and in section "Match specifications in Erlang" in
- <seealso marker="erts:match_spec"><c>ERTS User's Guide</c></seealso>
+ <seeguide marker="erts:match_spec"><c>ERTS User's Guide</c></seeguide>
and in module
- <seealso marker="stdlib:ms_transform"><c>ms_transform</c></seealso>
+ <seeerl marker="stdlib:ms_transform"><c>ms_transform</c></seeerl>
in application STDLIB.
</p>
</section>
diff --git a/lib/observer/doc/src/ref_man.xml b/lib/observer/doc/src/ref_man.xml
index 73e7e0053a..8ba920ecfb 100644
--- a/lib/observer/doc/src/ref_man.xml
+++ b/lib/observer/doc/src/ref_man.xml
@@ -36,6 +36,6 @@
<xi:include href="ttb.xml"/>
<xi:include href="etop.xml"/>
<xi:include href="crashdump.xml"/>
- <xi:include href="cdv.xml"/>
+ <xi:include href="cdv_cmd.xml"/>
</application>
diff --git a/lib/observer/doc/src/ttb.xml b/lib/observer/doc/src/ttb.xml
index fee95e0b21..79452eacb1 100644
--- a/lib/observer/doc/src/ttb.xml
+++ b/lib/observer/doc/src/ttb.xml
@@ -159,7 +159,7 @@ ttb:p(all, call).</input></pre>
only useful if many <c>{drop,N}</c> trace messages are
received by the trace handler. It has no meaning if shell
or <c>{local,File}</c> is not used. See
- <seealso marker="runtime_tools:dbg#trace_port/2">dbg:trace_port/2</seealso>
+ <seemfa marker="runtime_tools:dbg#trace_port/2">dbg:trace_port/2</seemfa>
for more information about the ip trace driver.</p></item>
<tag><c>process_info</c></tag>
<item><p>Indicates if process
@@ -199,8 +199,8 @@ ttb:p(all, call).</input></pre>
<tag><c>flush</c></tag>
<item><p>Periodically flushes all file trace
port clients (see
- <seealso marker="runtime_tools:dbg#flush_trace_port/1">
- <c>dbg:flush_trace_port/1</c></seealso>). When enabled,
+ <seemfa marker="runtime_tools:dbg#flush_trace_port/1">
+ <c>dbg:flush_trace_port/1</c></seemfa>). When enabled,
the buffers are freed each <c>MSec</c> millisecond. This option is
not allowed with <c>{file, {local, File}}</c> tracing.</p></item>
<tag><c>{resume, FetchTimeout}</c></tag>
@@ -211,7 +211,7 @@ ttb:p(all, call).</input></pre>
scripts if the traced nodes run with embedded Erlang). If this is
not possible, resume can be performed manually by starting
<c>Runtime_Tools</c> remotely using
- <seealso marker="kernel:rpc#call/4"><c>rpc:call/4</c></seealso>.</p>
+ <seemfa marker="kernel:rpc#call/4"><c>rpc:call/4</c></seemfa>.</p>
<p><c>ttb</c> tries to fetch all logs from a reconnecting node before
reinitializing the trace. This must finish within <c>FetchTimeout</c>
milliseconds or is aborted.</p>
@@ -261,7 +261,7 @@ ttb:p(all, call).</input></pre>
or ports. Flag <c>timestamp</c> is always turned on.
</p>
<p>See the Reference Manual for module
- <seealso marker="runtime_tools:dbg"><c>dbg</c></seealso>
+ <seeerl marker="runtime_tools:dbg"><c>dbg</c></seeerl>
for the possible trace flags. Parameter
<c>MatchDesc</c> is the same as returned from
<c>dbg:p/2</c>.</p>
@@ -277,8 +277,21 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name since="">tp, tpl, ctp, ctpl, ctpg</name>
- <name since="OTP 19.0">tpe, ctpe</name>
+ <name since="">tp(Module [, Function [, Arity]], MatchSpec)</name>
+ <name since="">tp({Module, Function , Arity}, MatchSpec)</name>
+ <name since="">tpl(Module [, Function [, Arity]], MatchSpec)</name>
+ <name since="">tpl({Module, Function , Arity}, MatchSpec)</name>
+ <name since="">ctp()</name>
+ <name since="">ctp(Module [, Function [, Arity]])</name>
+ <name since="">ctp({Module, Function, Arity})</name>
+ <name since="">ctpl()</name>
+ <name since="">ctpl(Module [, Function [, Arity]])</name>
+ <name since="">ctpl({Module, Function, Arity})</name>
+ <name since="">ctpg()</name>
+ <name since="">ctpg(Module [, Function [, Arity]])</name>
+ <name since="">ctpg({Module, Function, Arity})</name>
+ <name since="OTP 19.0">tpe(Event,MatchSpec)</name>
+ <name since="OTP 19.0">ctpe(Event)</name>
<fsummary>Set and clear trace patterns.</fsummary>
<desc>
<p>These functions are to be used with trace
@@ -294,11 +307,11 @@ ttb:p(all, call).</input></pre>
<p>Trace patterns specify how to trace a function or a message
by using match specifications. Match specifications are
described in the
- <seealso marker="erts:match_spec"><c>ERTS User's Guide</c></seealso>.
+ <seeguide marker="erts:match_spec"><c>ERTS User's Guide</c></seeguide>.
</p>
<p>These functions are equivalent to the corresponding
functions in module
- <seealso marker="runtime_tools:dbg">dbg</seealso>,
+ <seeerl marker="runtime_tools:dbg">dbg</seeerl>,
but all calls are stored in the
history. The history buffer makes it easy to create configuration
files; the same trace environment can be set up many
@@ -496,10 +509,10 @@ ttb:p(all, call).</input></pre>
<p>If <c>Flags = all</c>, all possible flags are set.
</p>
<p>The possible values for <c>SeqTraceFlag</c> are available in
- <seealso marker="kernel:seq_trace"><c>seq_trace</c></seealso>.</p>
+ <seeerl marker="kernel:seq_trace"><c>seq_trace</c></seeerl>.</p>
<p>For a description of the <c>match_spec()</c> syntax,
see section
- <seealso marker="erts:match_spec"><c>Match Specifications in Erlang</c></seealso>
+ <seeguide marker="erts:match_spec"><c>Match Specifications in Erlang</c></seeguide>
in ERTS, which explains the general match specification "language".
</p>
<note>
@@ -643,7 +656,7 @@ ttb:p(all, call).</input></pre>
format one of the wrap logs in a set, specify the exact file name.
To format the whole set of wrap logs, specify the name
with <c>*</c> instead of the wrap count. For examples, see the
- <seealso marker="ttb_ug#format"><c>User's Guide</c></seealso>.
+ <seeguide marker="ttb_ug#format"><c>User's Guide</c></seeguide>.
</p>
</desc>
</func>
diff --git a/lib/observer/doc/src/ttb_ug.xml b/lib/observer/doc/src/ttb_ug.xml
index 34591ae8de..eb243dd869 100644
--- a/lib/observer/doc/src/ttb_ug.xml
+++ b/lib/observer/doc/src/ttb_ug.xml
@@ -63,14 +63,14 @@
Trace Tool Builder.</p>
<p>To get started, the least you need to do is to
start a tracer with
- <seealso marker="ttb#tracer/0"><c>ttb:tracer/0,1,2</c></seealso>,
+ <seemfa marker="ttb#tracer/0"><c>ttb:tracer/0,1,2</c></seemfa>,
and set the required
trace flags on the processes you want to trace with
- <seealso marker="ttb#p/2"><c>ttb:p/2</c></seealso>.</p>
+ <seemfa marker="ttb#p/2"><c>ttb:p/2</c></seemfa>.</p>
<p>When the tracing is completed, stop the tracer with
- <seealso marker="ttb#stop/0"><c>ttb:stop/0,1</c></seealso>
+ <seemfa marker="ttb#stop/0"><c>ttb:stop/0,1</c></seemfa>
and format the trace log with
- <seealso marker="ttb#format/1"><c>ttb:format/1,2</c></seealso>
+ <seemfa marker="ttb#format/1"><c>ttb:format/1,2</c></seemfa>
(if there is anything to format).
</p>
<p><em>Useful functions:</em></p>
@@ -88,13 +88,13 @@
<item><p>If you want to trace function calls (that is, if you have
trace flag <c>call</c> set on any process), you must
also set trace patterns on the required function(s) with
- <seealso marker="ttb#/0"><c>ttb:tp/2,3,4</c></seealso> or
- <seealso marker="ttb#/0"><c>ttb:tpl/2,3,4</c></seealso>.
+ <seemfa marker="ttb#tp/2"><c>ttb:tp/2,3,4</c></seemfa> or
+ <seemfa marker="ttb#tpl/2"><c>ttb:tpl/2,3,4</c></seemfa>.
A function is only traced
if it has a trace pattern. The trace pattern specifies how to trace the
function by using match specifications. Match specifications are
described in the
- <seealso marker="erts:users_guide">ERTS User's Guide</seealso>.</p></item>
+ <seeguide marker="erts:index">ERTS User's Guide</seeguide>.</p></item>
<tag><c>ttb:stop/0,1</c></tag>
<item><p>Stops tracing on all nodes, deletes all trace patterns, and
flushes the trace port buffer.</p></item>
@@ -236,7 +236,7 @@ do_print(Out,{trace_ts,P,return_from,{M,F,A},R,Ts},N) ->
[N,Ts,P,M,F,A,R]). ]]></code>
<p>To distinguish trace logs produced with this tool from other
logs, option <c>file</c> is used in
- <seealso marker="ttb#tracer/2"><c>tracer/2</c></seealso>. The
+ <seemfa marker="ttb#tracer/2"><c>tracer/2</c></seemfa>. The
logs are therefore fetched to a directory named
<c>ttb_upload_debug_log-YYYYMMDD-HHMMSS</c>
</p>
@@ -310,14 +310,14 @@ do_print(Out,{trace_ts,P,return_from,{M,F,A},R,Ts},N) ->
<c>{timer, TimerSpec}</c>. If <c>TimerSpec</c> has the
form of <c>MSec</c>, the trace is stopped after <c>MSec</c>
milliseconds using
- <seealso marker="ttb#stop/0"><c>ttb:stop/0</c></seealso>. If more
+ <seemfa marker="ttb#stop/0"><c>ttb:stop/0</c></seemfa>. If more
options are provided (<c>TimerSpec = {MSec, Opts}</c>),
- <seealso marker="ttb#stop/1"><c>ttb:stop/1</c></seealso>
+ <seemfa marker="ttb#stop/1"><c>ttb:stop/1</c></seemfa>
is called instead with <c>Opts</c> as argument.</p>
<p>The timer is started with
- <seealso marker="ttb#p/2"><c>ttb:p/2</c></seealso>, so any trace patterns
+ <seemfa marker="ttb#p/2"><c>ttb:p/2</c></seemfa>, so any trace patterns
must be set up in advance.
- <seealso marker="ttb#start_trace/4"><c>ttb:start_trace/4</c></seealso>
+ <seemfa marker="ttb#start_trace/4"><c>ttb:start_trace/4</c></seemfa>
always sets up all patterns before invoking <c>ttb:p/2</c>.</p>
<p>The following example shows how to set up a trace that is
automatically stopped and formatted after 5 seconds:
@@ -336,7 +336,7 @@ do_print(Out,{trace_ts,P,return_from,{M,F,A},R,Ts},N) ->
overload a node with too heavy tracing. <c>ttb</c> provides
option <c>overload</c> to address this problem.</p>
<p><c>{overload, MSec, Module, Function}</c> instructs the <c>ttb</c> back end
- (a part of the <seealso marker="runtime_tools:index">Runtime_Tools</seealso>
+ (a part of the <seeapp marker="runtime_tools:index">Runtime_Tools</seeapp>
application) to perform overload check every <c>MSec</c> millisecond.
If the check (named <c>Module:Function(check)</c>) returns
<c>true</c>, tracing is disabled on the selected node.</p>
@@ -401,7 +401,7 @@ check(stop) ->
storage and retrieval (<c>ttb_autostart_module</c>
environment variable of <c>runtime_tools</c>). For information
about the API, see module
- <seealso marker="ttb"><c>ttb</c></seealso>.
+ <seeerl marker="ttb"><c>ttb</c></seeerl>.
The following example shows the default handler:</p>
<code>
-module(ttb_autostart).
@@ -435,7 +435,7 @@ write_config(Data) ->
<title>dbg Mode</title>
<p>Option <c>{shell, ShellType}</c> allows making <c>ttb</c>
operation similar to
- <seealso marker="runtime_tools:dbg"><c>dbg</c></seealso>.
+ <seeerl marker="runtime_tools:dbg"><c>dbg</c></seeerl>.
Using <c>{shell, true}</c>
displays all trace messages in the shell before storing them.
<c>{shell, only}</c> additionally disables message storage
@@ -456,7 +456,7 @@ write_config(Data) ->
contains the process information, trace flags used, the name of
the node to which it belongs, and all information written with
function
- <seealso marker="ttb#write_trace_info/2"><c>ttb:write_trace_info/2</c></seealso>.
+ <seemfa marker="ttb#write_trace_info/2"><c>ttb:write_trace_info/2</c></seemfa>.
<c>.ti</c> files are always fetched with other logs when the trace is stopped.
</p>
<p>Except for the process information, everything in the trace
@@ -468,7 +468,7 @@ write_config(Data) ->
</p>
<p>Information to the trace information file by
can be added by calling
- <seealso marker="ttb#write_trace_info/2"><c>ttb:write_trace_info/2</c></seealso>.
+ <seemfa marker="ttb#write_trace_info/2"><c>ttb:write_trace_info/2</c></seemfa>.
Notice that <c>ValueList</c>
always is a list, and if you call <c>write_trace_info/2</c>
many times with the same <c>Key</c>, the <c>ValueList</c> is
@@ -497,7 +497,7 @@ write_config(Data) ->
a new one is created.
</p></note>
<p>Wrap logs can be formatted one by one or all at once. See
- <seealso marker="#format">Formatting</seealso>.
+ <seeguide marker="#format">Formatting</seeguide>.
</p>
</section>
@@ -506,7 +506,7 @@ write_config(Data) ->
<title>Formatting</title>
<p>Formatting can be done automatically when stopping <c>ttb</c>
(see section
- <seealso marker="#fetch_format">Automatically Collect and Format Logs from All Nodes</seealso>), or explicitly by calling function
+ <seeguide marker="#fetch_format">Automatically Collect and Format Logs from All Nodes</seeguide>), or explicitly by calling function
<c>ttb:format/1,2</c>.
</p>
<p>Formatting means to read a binary log and present it in a
@@ -515,7 +515,7 @@ write_config(Data) ->
write your own handler to make more complex interpretations of the
trace information. You can also use application ET to
present the trace log graphically (see section
- <seealso marker="#et_viewer">Presenting Trace Logs with Event Tracer</seealso>).
+ <seeguide marker="#et_viewer">Presenting Trace Logs with Event Tracer</seeguide>).
</p>
<p>The first argument to <c>ttb:format/1,2</c> specifies which
binary log(s) to format. This is usually the name of a directory
@@ -553,7 +553,7 @@ end. </code>
<p>Here, <c>Fd</c> is the file descriptor for the destination file, or
the atom <c>standard_io</c>. <c>_TraceInfo</c> contains information
from the trace information file (see section
- <seealso marker="#trace_info">Trace Information and File .ti</seealso>). <c>State</c> is a state variable for the format
+ <seeguide marker="#trace_info">Trace Information and File .ti</seeguide>). <c>State</c> is a state variable for the format
handler fun. The initial value of variable <c>State</c> is
specified with the handler option, for example:</p>
<code type="none">
@@ -578,20 +578,20 @@ end </code>
The possible values of <c>Trace</c> are the following:</p>
<list type="bulleted">
<item>All trace messages described in
- <seealso marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seealso>
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa>
</item>
<item><c>{drop, N}</c> if IP tracer is used (see
- <seealso marker="runtime_tools:dbg#trace_port/2"><c>dbg:trace_port/2</c></seealso>)
+ <seemfa marker="runtime_tools:dbg#trace_port/2"><c>dbg:trace_port/2</c></seemfa>)
</item>
<item><c>end_of_trace</c> received once when all trace messages are
processed</item>
</list>
<p>By giving the format handler
- <seealso marker="ttb#get_et_handler/0"><c>ttb:get_et_handler()</c></seealso>,
+ <seemfa marker="ttb#get_et_handler/0"><c>ttb:get_et_handler()</c></seemfa>,
you can have the trace
log presented graphically with <c>et_viewer</c> in the ET
application (see section
- <seealso marker="#et_viewer">Presenting Trace Logs with Event Tracer</seealso>).
+ <seeguide marker="#et_viewer">Presenting Trace Logs with Event Tracer</seeguide>).
</p>
<p>You can always decide not to format the whole trace data contained
in the fetch directory, but analyze single files instead. To do so,
@@ -638,10 +638,10 @@ ok
<marker id="et_viewer"></marker>
<title>Presenting Trace Logs with Event Tracer</title>
<p>For detailed information about the Event Tracer, see the
- <seealso marker="et:users_guide">ET</seealso> application.
+ <seeguide marker="et:index">ET</seeguide> application.
</p>
<p>By giving the format handler
- <seealso marker="ttb#get_et_handler/0"><c>ttb:get_et_handler()</c></seealso>,
+ <seemfa marker="ttb#get_et_handler/0"><c>ttb:get_et_handler()</c></seemfa>,
you can have the trace log presented graphically with
<c>et_viewer</c> in the ET application.
<c>ttb</c> provides filters that can be selected from the
@@ -745,7 +745,7 @@ f3() ->
</image>
<p>Notice that function
- <seealso marker="ttb#start_trace/4"><c>ttb:start_trace/4</c></seealso>
+ <seemfa marker="ttb#start_trace/4"><c>ttb:start_trace/4</c></seemfa>
can be used as help as follows:</p>
<pre>
(tiger@durin)1> <input>Pid = foo:start().</input>
@@ -764,7 +764,7 @@ f3() ->
<title>Automatically Collect and Format Logs from All Nodes</title>
<p>By default,
- <seealso marker="ttb#stop/1"><c>ttb:stop/1</c></seealso> fetches trace logs
+ <seemfa marker="ttb#stop/1"><c>ttb:stop/1</c></seemfa> fetches trace logs
and trace information files from all nodes. The logs are stored in a
new directory named <c>ttb_upload-Filename-Timestamp</c> under the
working directory of the trace control node. Fetching can be disabled
@@ -780,7 +780,7 @@ f3() ->
<section>
<title>History and Configuration Files</title>
<p>For the tracing functionality,
- <seealso marker="runtime_tools:dbg"><c>dbg</c></seealso>
+ <seeerl marker="runtime_tools:dbg"><c>dbg</c></seeerl>
can be used instead
of <c>ttb</c> for setting trace flags on processes and trace
patterns for call trace, that is, the functions
@@ -794,13 +794,13 @@ f3() ->
typing when using <c>ttb</c> from the Erlang shell.</item>
<item>Shortcuts are provided for the most common match
specifications (to not force you to use
- <seealso marker="runtime_tools:dbg#fun2ms/1"><c>dbg:fun2ms</c></seealso>
+ <seemfa marker="runtime_tools:dbg#fun2ms/1"><c>dbg:fun2ms</c></seemfa>
continually).</item>
</list>
<p>Use
- <seealso marker="ttb#list_history/0"><c>ttb:list_history/0</c></seealso>
+ <seemfa marker="ttb#list_history/0"><c>ttb:list_history/0</c></seemfa>
to see the content of the history buffer and
- <seealso marker="ttb#run_history/1"><c>ttb:run_history/1</c></seealso>
+ <seemfa marker="ttb#run_history/1"><c>ttb:run_history/1</c></seemfa>
to re-execute one of the entries.
</p>
<p>The main purpose of the history buffer is the possibility to
@@ -810,7 +810,7 @@ f3() ->
function call.
</p>
<p>A configuration file is created or extended with
- <seealso marker="ttb#write_config/2"><c>ttb:write_config/2,3</c></seealso>.
+ <seemfa marker="ttb#write_config/2"><c>ttb:write_config/2,3</c></seemfa>.
Configuration files are binary files
and can therefore only be read and written with functions provided
by <c>ttb</c>.
@@ -933,14 +933,14 @@ ok
<title>Sequential Tracing</title>
<p>To learn what sequential tracing is and how it can be used,
see the Reference Manual for
- <seealso marker="kernel:seq_trace"><c>seq_trace</c></seealso>.
+ <seeerl marker="kernel:seq_trace"><c>seq_trace</c></seeerl>.
</p>
<p>The support for sequential tracing provided by Trace Tool
Builder includes the following:</p>
<list type="bulleted">
<item>Initiation of the system tracer. This is automatically
done when a trace port is started with
- <seealso marker="ttb#tracer/0"><c>ttb:tracer/0,1,2</c></seealso>.</item>
+ <seemfa marker="ttb#tracer/0"><c>ttb:tracer/0,1,2</c></seemfa>.</item>
<item>Creation of match specifications that activates
sequential tracing.</item>
</list>
@@ -951,9 +951,9 @@ ok
<list type="bulleted">
<item>Through a trigger function with a match specification
created with
- <seealso marker="ttb#seq_trigger_ms/0"><c>ttb:seq_trigger_ms/0,1</c></seealso>.</item>
+ <seemfa marker="ttb#seq_trigger_ms/0"><c>ttb:seq_trigger_ms/0,1</c></seemfa>.</item>
<item>Directly by using module
- <seealso marker="kernel:seq_trace"><c>seq_trace</c></seealso>.</item>
+ <seeerl marker="kernel:seq_trace"><c>seq_trace</c></seeerl>.</item>
</list>
<p><em>Example 1:</em></p>
diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src
index d48b846ad2..ca65ee703e 100644
--- a/lib/observer/src/observer.app.src
+++ b/lib/observer/src/observer.app.src
@@ -66,7 +66,7 @@
{registered, []},
{applications, [kernel, stdlib]},
{env, []},
- {runtime_dependencies, ["wx-1.2","stdlib-3.5","runtime_tools-1.8.14",
- "kernel-3.0","et-1.5","erts-7.0"]}]}.
+ {runtime_dependencies, ["wx-1.2","stdlib-3.13","runtime_tools-1.8.14",
+ "kernel-7.0","et-1.5","erts-11.0"]}]}.
diff --git a/lib/observer/src/observer_alloc_wx.erl b/lib/observer/src/observer_alloc_wx.erl
index da47a30fb1..a7b55f0f72 100644
--- a/lib/observer/src/observer_alloc_wx.erl
+++ b/lib/observer/src/observer_alloc_wx.erl
@@ -158,7 +158,7 @@ handle_info({refresh, Seq},
State#state.active andalso (catch wxWindow:refresh(Panel)),
erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
if Seq =:= (trunc(DispF)-1) ->
- Req = rpc:async_call(Node, observer_backend, sys_info, []),
+ Req = request_info(Node),
{noreply, State#state{time=Ti#ti{tick=Next}, async=Req}};
true ->
{noreply, State#state{time=Ti#ti{tick=Next}}}
@@ -193,6 +193,13 @@ code_change(_, _, State) ->
%%%%%%%%%%
+request_info(Node) ->
+ ReplyTo = self(),
+ spawn(fun() ->
+ Res = rpc:call(Node, observer_backend, sys_info, []),
+ ReplyTo ! {self(), {promise_reply, Res}}
+ end).
+
restart_fetcher(Node, #state{panel=Panel, wins=Wins0, time=Ti} = State) ->
case rpc:call(Node, observer_backend, sys_info, []) of
{badrpc, _} -> State;
@@ -261,20 +268,31 @@ sum_alloc_instances([{_,_,Data}|Instances],BS,CS,TotalBS,TotalCS) ->
sum_alloc_instances([],BS,CS,TotalBS,TotalCS) ->
{BS,CS,TotalBS,TotalCS,true}.
-sum_alloc_one_instance([{sbmbcs,[{blocks_size,BS,_,_},{carriers_size,CS,_,_}]}|
- Rest],OldBS,OldCS,TotalBS,TotalCS) ->
- sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS,TotalCS);
-sum_alloc_one_instance([{_,[{blocks_size,BS,_,_},{carriers_size,CS,_,_}]}|
+sum_alloc_one_instance([{_,[{blocks,TypedBlocks},{carriers_size,CS,_,_}]}|
Rest],OldBS,OldCS,TotalBS,TotalCS) ->
+ %% OTP 23 and later.
+ BS = sum_alloc_block_list(TypedBlocks, 0),
sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS);
-sum_alloc_one_instance([{_,[{blocks_size,BS},{carriers_size,CS}]}|
+sum_alloc_one_instance([{_,[{blocks_size,BS,_,_},{carriers_size,CS,_,_}]}|
Rest],OldBS,OldCS,TotalBS,TotalCS) ->
+ %% OTP 22 and earlier.
sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS);
sum_alloc_one_instance([_|Rest],BS,CS,TotalBS,TotalCS) ->
sum_alloc_one_instance(Rest,BS,CS,TotalBS,TotalCS);
sum_alloc_one_instance([],BS,CS,TotalBS,TotalCS) ->
{BS,CS,TotalBS,TotalCS}.
+sum_alloc_block_list([{_Type, [{size, Current, _, _}]} | Rest], Acc) ->
+ %% We ignore the type since we're returning a summary of all blocks in the
+ %% carriers employed by a certain instance.
+ sum_alloc_block_list(Rest, Current + Acc);
+sum_alloc_block_list([{_Type, [{size, Current}]} | Rest], Acc) ->
+ sum_alloc_block_list(Rest, Current + Acc);
+sum_alloc_block_list([_ | Rest], Acc) ->
+ sum_alloc_block_list(Rest, Acc);
+sum_alloc_block_list([], Acc) ->
+ Acc.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
create_mem_info(Parent) ->
diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl
index c4527ba063..862772669c 100644
--- a/lib/observer/src/observer_app_wx.erl
+++ b/lib/observer/src/observer_app_wx.erl
@@ -93,7 +93,7 @@ init([Notebook, Parent, _Config]) ->
DrawingArea = wxScrolledWindow:new(P2, [{winid, ?DRAWAREA},
{style,?wxFULL_REPAINT_ON_RESIZE}]),
BG = wxWindow:getBackgroundColour(Apps),
- wxWindow:setBackgroundStyle(DrawingArea, ?wxBG_STYLE_SYSTEM),
+ wxWindow:setBackgroundStyle(DrawingArea, ?wxBG_STYLE_PAINT),
wxWindow:setVirtualSize(DrawingArea, 800, 800),
wxSplitterWindow:setMinimumPaneSize(Splitter,50),
wxSizer:add(Extra, DrawingArea, [{flag, ?wxEXPAND},{proportion, 1}]),
diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl
index 50a6d6a915..6a848a8d27 100644
--- a/lib/observer/src/observer_perf_wx.erl
+++ b/lib/observer/src/observer_perf_wx.erl
@@ -99,13 +99,9 @@ make_win(Name, Parent, Sizer, Border) ->
#win{name=Name, panel=Panel}.
setup_graph_drawing(Panels) ->
- IsWindows = element(1, os:type()) =:= win32,
- IgnoreCB = {callback, fun(_,_) -> ok end},
Do = fun(#win{panel=Panel}) ->
- wxWindow:setBackgroundStyle(Panel, ?wxBG_STYLE_SYSTEM),
- wxPanel:connect(Panel, paint, [callback]),
- IsWindows andalso
- wxPanel:connect(Panel, erase_background, [IgnoreCB])
+ wxWindow:setBackgroundStyle(Panel, ?wxBG_STYLE_PAINT),
+ wxPanel:connect(Panel, paint, [callback])
end,
_ = [Do(Panel) || Panel <- Panels],
UseGC = haveGC(),
diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl
index 10d88c994a..0f0947065a 100644
--- a/lib/observer/test/crashdump_helper.erl
+++ b/lib/observer/test/crashdump_helper.erl
@@ -23,7 +23,8 @@
dump_maps/0,create_maps/0,
create_binaries/0,create_sub_binaries/1,
dump_persistent_terms/0,
- create_persistent_terms/0]).
+ create_persistent_terms/0,
+ dump_global_literals/0]).
-compile(r20).
-include_lib("common_test/include/ct.hrl").
@@ -152,6 +153,7 @@ dump_maps() ->
Pid = spawn_link(F),
receive
{Pid,done} ->
+ unlink(Pid),
{ok,Pid}
end.
@@ -198,6 +200,7 @@ dump_persistent_terms() ->
Pid = spawn_link(F),
receive
{Pid,done} ->
+ unlink(Pid),
{ok,Pid}
end.
@@ -205,3 +208,23 @@ create_persistent_terms() ->
persistent_term:put({?MODULE,first}, {pid,42.0}),
persistent_term:put({?MODULE,second}, [1,2,3]),
{persistent_term:get({?MODULE,first}),persistent_term:get({?MODULE,second})}.
+
+%%%
+%%% Test dumping of global literals such as the tuple returned from os:type/0
+%%% (from OTP 23.1).
+%%%
+
+dump_global_literals() ->
+ Parent = self(),
+ F = fun() ->
+ register(aaaaaaaa_global_literals, self()),
+ put(global_literals, {os:type(),os:version()}),
+ Parent ! {self(),done},
+ receive _ -> ok end
+ end,
+ Pid = spawn_link(F),
+ receive
+ {Pid,done} ->
+ unlink(Pid),
+ {ok,Pid}
+ end.
diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl
index 31cf7011d4..098c649b87 100644
--- a/lib/observer/test/crashdump_viewer_SUITE.erl
+++ b/lib/observer/test/crashdump_viewer_SUITE.erl
@@ -619,6 +619,21 @@ special(File,Procs) ->
Pts = proplists:get_value(pts,Dict),
io:format(" persistent terms ok",[]),
ok;
+ ".global_literals" ->
+ %% I registered a process as aaaaaaaa_global_literals in
+ %% the dump to make sure it will be the first in the list
+ %% when sorted on names.
+ [#proc{pid=Pid0,name=Name}|_Rest] = lists:keysort(#proc.name,Procs),
+ "aaaaaaaa_global_literals" = Name,
+ Pid = pid_to_list(Pid0),
+ {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid),
+ io:format(" process details ok",[]),
+
+ #proc{dict=Dict} = ProcDetails,
+ Globals = proplists:get_value(global_literals,Dict),
+ Globals = {os:type(),os:version()},
+ io:format(" global_literals ok",[]),
+ ok;
_ ->
ok
end,
@@ -650,7 +665,7 @@ lookat_all_pids([#proc{pid=Pid0}|Procs],TruncAllowed,IncompAllowed) ->
_ when TruncAllowed ->
ok; % truncated dump
TWs ->
- ct:fail({unexpected_warning,TWs})
+ ct:fail({unexpected_warning,Pid,TWs})
end,
lookat_all_pids(Procs,TruncAllowed,IncompAllowed).
@@ -704,9 +719,10 @@ do_create_dumps(DataDir,Rel) ->
CD6 = dump_with_unicode_atoms(DataDir,Rel,"unicode"),
CD7 = dump_with_maps(DataDir,Rel,"maps"),
CD8 = dump_with_persistent_terms(DataDir,Rel,"persistent_terms"),
+ CD9 = dump_with_global_literals(DataDir,Rel,"global_literals"),
TruncDumpMod = truncate_dump_mod(CD1),
TruncatedDumpsBinary = truncate_dump_binary(CD1),
- {[CD1,CD2,CD3,CD4,CD5,CD6,CD7,CD8,
+ {[CD1,CD2,CD3,CD4,CD5,CD6,CD7,CD8,CD9,
TruncDumpMod|TruncatedDumpsBinary],
DosDump};
_ ->
@@ -886,6 +902,16 @@ dump_with_persistent_terms(DataDir,Rel,DumpName) ->
?t:stop_node(n1),
CD.
+dump_with_global_literals(DataDir,Rel,DumpName) ->
+ Opt = rel_opt(Rel),
+ Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"",
+ PzOpt = [{args,Pz}],
+ {ok,N1} = ?t:start_node(n1,peer,Opt ++ PzOpt),
+ {ok,_Pid} = rpc:call(N1,crashdump_helper,dump_global_literals,[]),
+ CD = dump(N1,DataDir,Rel,DumpName),
+ ?t:stop_node(n1),
+ CD.
+
dump(Node,DataDir,Rel,DumpName) ->
Crashdump = filename:join(DataDir, dump_prefix(Rel)++DumpName),
rpc:call(Node,os,putenv,["ERL_CRASH_DUMP",Crashdump]),
diff --git a/lib/observer/test/ttb_SUITE.erl b/lib/observer/test/ttb_SUITE.erl
index 33133dd78d..d0b812d0ce 100644
--- a/lib/observer/test/ttb_SUITE.erl
+++ b/lib/observer/test/ttb_SUITE.erl
@@ -658,11 +658,29 @@ seq_trace(Config) when is_list(Config) ->
?line ok = ttb:format(
[filename:join(Privdir,atom_to_list(Node)++"-seq_trace")]),
?line [{trace_ts,StartProc,call,{?MODULE,seq,[]},{_,_,_}},
- {seq_trace,0,{send,{0,1},StartProc,P1Proc,{Start,P2}}},
- {seq_trace,0,{send,{1,2},P1Proc,P2Proc,{P1,Start}}},
- {seq_trace,0,{send,{2,3},P2Proc,StartProc,{P2,P1}}},
+ {seq_trace,0,{send,{First, Seq0},StartProc,P1Proc,SpawnRequest1}},
+ {seq_trace,0,{send,{Seq0, Seq1},P1Proc,StartProc,SpawnReply1}},
+ {seq_trace,0,{send,{Seq2, Seq3},StartProc,P2Proc,SpawnRequest2}},
+ {seq_trace,0,{send,{Seq3, Seq4},P2Proc,StartProc,SpawnReply2}},
+ {seq_trace,0,{send,{Seq5, Seq6},StartProc,P1Proc,{Start,P2}}},
+ {seq_trace,0,{send,{Seq6, Seq7},P1Proc,P2Proc,{P1,Start}}},
+ {seq_trace,0,{send,{Seq7, Last},P2Proc,StartProc,{P2,P1}}},
end_of_trace] = flush(),
-
+ spawn_request = element(1, SpawnRequest1),
+ SReq1 = element(2, SpawnRequest1),
+ spawn_reply = element(1, SpawnReply1),
+ SReq1 = element(2, SpawnReply1),
+ spawn_request = element(1, SpawnRequest2),
+ SReq2 = element(2, SpawnRequest2),
+ spawn_reply = element(1, SpawnReply2),
+ SReq2 = element(2, SpawnReply2),
+ true = First < Seq0,
+ true = Seq0 < Seq1,
+ true = Seq1 < Seq2,
+ true = Seq2 < Seq3,
+ true = Seq4 < Seq5,
+ true = Seq6 < Seq7,
+ true = Seq7 < Last,
%% Additional test for metatrace
case StartProc of
{Start,_,_} -> ok;
@@ -720,7 +738,7 @@ diskless(Config) when is_list(Config) ->
?line {ok,[{matched,RemoteNode,1}]} = ttb:tp(?MODULE,foo,[]),
?line rpc:call(RemoteNode,?MODULE,foo,[]),
- ?line timer:sleep(500), % needed for the IP port to flush
+ ?line timer:sleep(5000), % needed for the IP port to flush
?line ttb:stop([nofetch]),
?line ok = ttb:format(filename:join(Privdir,
atom_to_list(RemoteNode)++"-diskless")),
@@ -749,7 +767,7 @@ diskless_wrap(Config) when is_list(Config) ->
?line {ok,[{matched,RemoteNode,1}]} = ttb:tp(?MODULE,foo,[]),
?line rpc:call(RemoteNode,?MODULE,foo,[]),
- ?line timer:sleep(500), % needed for the IP port to flush
+ ?line timer:sleep(5000), % needed for the IP port to flush
?line ttb:stop([nofetch]),
?line ok = ttb:format(filename:join(Privdir,
atom_to_list(RemoteNode)++"-diskless.*.wrp")),
diff --git a/lib/observer/test/ttb_helper.erl b/lib/observer/test/ttb_helper.erl
index 05f6d73aef..0f6b8e81b1 100644
--- a/lib/observer/test/ttb_helper.erl
+++ b/lib/observer/test/ttb_helper.erl
@@ -70,7 +70,10 @@ msgs(N) ->
msgs_ip(N) ->
[c(client, put, [test_msg]) || _ <- lists:seq(1, N)],
s(server, received, [a,b]),
- timer:sleep(500). %% allow trace messages to arrive over tcp/ip
+ %% This is a very high sleep, it is needed for some of the slower
+ %% test machines. Ideally we would want to be able to react to
+ %% when the actual tcp packets arrive, but I'm not sure that is possible..
+ timer:sleep(5000). %% allow trace messages to arrive over tcp/ip
run() ->
ttb({local, "A"}),
diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk
index 6b733687b8..b69de6c454 100644
--- a/lib/observer/vsn.mk
+++ b/lib/observer/vsn.mk
@@ -1 +1 @@
-OBSERVER_VSN = 2.9.3
+OBSERVER_VSN = 2.9.5
diff --git a/lib/odbc/Makefile b/lib/odbc/Makefile
index f7816c25fc..ee39992a84 100644
--- a/lib/odbc/Makefile
+++ b/lib/odbc/Makefile
@@ -69,12 +69,6 @@ do_configure: configure
configure: configure.in
autoconf
-.PHONY: info
-
-info:
- @echo "ODBC_VSN: $(ODBC_VSN)"
-
-
# ----------------------------------------------------
# Application (source) release targets
# ----------------------------------------------------
@@ -114,3 +108,5 @@ tar: $(APP_TAR_FILE)
$(APP_TAR_FILE): $(APP_DIR)
(cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME))
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/odbc/c_src/Makefile.in b/lib/odbc/c_src/Makefile.in
index 294d832797..3c16e7e294 100644
--- a/lib/odbc/c_src/Makefile.in
+++ b/lib/odbc/c_src/Makefile.in
@@ -54,14 +54,14 @@ RELSYSDIR = $(RELEASE_PATH)/lib/odbc-$(VSN)
EI_ROOT = $(ERL_TOP)/lib/erl_interface
EI_INCLUDE = -I$(EI_ROOT)/include -I$(EI_ROOT)/include/$(TARGET)
ifeq ($(findstring win32,$(TARGET)),win32)
-EI_LIB = -lerl_interface_md -lei_md
+EI_LIB = -lei_md
ENTRY_OBJ=$(ERL_TOP)/erts/obj/$(TARGET)/port_entry.o
PORT_ENTRY_POINT=erl_port_entry
ENTRY_LDFLAGS=-entry:$(PORT_ENTRY_POINT)
WIN32_TARGET = $(WIN_BIN_DIR)/odbcserver.exe
EXE_TARGET = $(WIN32_TARGET)
else
-EI_LIB = -lerl_interface -lei
+EI_LIB = -lei
UNIX_TARGET = $(BIN_DIR)/odbcserver
EXE_TARGET = $(UNIX_TARGET)
endif
diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c
index fb4f61417e..b22a6cb7af 100644
--- a/lib/odbc/c_src/odbcserver.c
+++ b/lib/odbc/c_src/odbcserver.c
@@ -178,7 +178,7 @@ static void encode_column_dyn(db_column column, int column_nr,
db_state *state);
static void encode_data_type(SQLSMALLINT sql_type, SQLINTEGER size,
SQLSMALLINT decimal_digits, db_state *state);
-static Boolean decode_params(db_state *state, byte *buffer, int *index, param_array **params,
+static Boolean decode_params(db_state *state, char *buffer, int *index, param_array **params,
int i, int j, int num_param_values);
/*------------- Erlang port communication functions ----------------------*/
@@ -222,7 +222,7 @@ static SQLLEN* alloc_strlen_indptr(int n, int val);
static void init_driver(int erl_auto_commit_mode, int erl_trace_driver,
db_state *state);
-static void init_param_column(param_array *params, byte *buffer, int *index,
+static void init_param_column(param_array *params, char *buffer, int *index,
int num_param_values, db_state* state);
static void init_param_statement(int cols,
@@ -235,7 +235,7 @@ static void map_dec_num_2_c_column(col_type *type, int precision,
static db_result_msg map_sql_2_c_column(db_column* column, db_state *state);
-static param_array * bind_parameter_arrays(byte *buffer, int *index,
+static param_array * bind_parameter_arrays(char *buffer, int *index,
int cols,
int num_param_values,
db_state *state);
@@ -259,10 +259,10 @@ static void str_tolower(char *str, int len);
/* ----------------------------- CODE ------------------------------------*/
#if defined(WIN32)
-# define DO_EXIT(code) do { ExitProcess((code)); exit((code));} while (0)
-/* exit() called only to avoid a warning */
+# define DO_EXIT(code) do { ExitProcess((code)); _exit((code));} while (0)
+/* _exit() called only to avoid a warning */
#else
-# define DO_EXIT(code) exit((code))
+# define DO_EXIT(code) _exit((code))
#endif
/* ----------------- Main functions --------------------------------------*/
@@ -278,7 +278,7 @@ int main(void)
msg = receive_erlang_port_msg();
- temp = strtok(msg, ";");
+ temp = strtok((char*)msg, ";");
if (temp == NULL)
DO_EXIT(EXIT_STDIN_BODY);
length = strlen(temp);
@@ -304,7 +304,7 @@ int main(void)
static void spawn_sup(const char *port)
{
DWORD threadId;
- (HANDLE)_beginthreadex(NULL, 0, supervise, port, 0, &threadId);
+ _beginthreadex(NULL, 0, supervise, port, 0, &threadId);
}
#elif defined(UNIX)
static void spawn_sup(const char *port)
@@ -509,7 +509,9 @@ static db_result_msg db_connect(byte *args, db_state *state)
diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state), extended_errors(state));
strcat((char *)diagnos.error_msg,
" Connection to database failed.");
- msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError );
+ msg = encode_error_message((char *)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError );
if(!sql_success(SQLFreeHandle(SQL_HANDLE_DBC,
connection_handle(state))))
@@ -544,7 +546,9 @@ static db_result_msg db_connect(byte *args, db_state *state)
diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state), extended_errors(state));
strcat((char *)diagnos.error_msg, " Set autocommit mode failed.");
- msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ msg = encode_error_message((char*)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
if(!sql_success(SQLFreeHandle(SQL_HANDLE_DBC,
connection_handle(state))))
@@ -576,7 +580,9 @@ static db_result_msg db_close_connection(db_state *state)
if (!sql_success(result)) {
diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state), extended_errors(state));
- return encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ return encode_error_message((char *)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
}
if(!sql_success(SQLFreeHandle(SQL_HANDLE_DBC,
@@ -603,7 +609,9 @@ static db_result_msg db_end_tran(byte compleationtype, db_state *state)
if (!sql_success(result)) {
diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state), extended_errors(state));
- return encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ return encode_error_message((char *)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
} else {
return encode_atom_message("ok");
}
@@ -645,7 +653,7 @@ static db_result_msg db_query(byte *sql, db_state *state)
it as we want a nice and clean Erlang API */
if((strcmp((char *)is_error, "error") == 0))
{
- msg = encode_error_message((char *)diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ msg = encode_error_message((char *)diagnos.error_msg,extended_error(state, diagnos.sqlState), diagnos.nativeError);
clean_state(state);
return msg;
}
@@ -686,7 +694,7 @@ static db_result_msg db_query(byte *sql, db_state *state)
ei_x_free(&(dynamic_buffer(state)));
return msg;
} else {
- msg.buffer = dynamic_buffer(state).buff;
+ msg.buffer = (byte*)dynamic_buffer(state).buff;
msg.length = dynamic_buffer(state).index;
msg.dyn_alloc = TRUE;
return msg;
@@ -722,7 +730,9 @@ static db_result_msg db_select_count(byte *sql, db_state *state)
if(!sql_success(SQLExecDirect(statement_handle(state), (SQLCHAR *)sql, SQL_NTS))) {
diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
clean_state(state);
- return encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ return encode_error_message((char *)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
}
if(!sql_success(SQLNumResultCols(statement_handle(state),
@@ -810,7 +820,7 @@ static db_result_msg db_select(byte *args, db_state *state)
ei_x_free(&(dynamic_buffer(state)));
return msg;
} else {
- msg.buffer = dynamic_buffer(state).buff;
+ msg.buffer = (byte*)dynamic_buffer(state).buff;
msg.length = dynamic_buffer(state).index;
msg.dyn_alloc = TRUE;
return msg;
@@ -819,9 +829,10 @@ static db_result_msg db_select(byte *args, db_state *state)
/* Description: Handles parameterized queries ex:
INSERT INTO FOO VALUES(?, ?) */
-static db_result_msg db_param_query(byte *buffer, db_state *state)
+static db_result_msg db_param_query(byte *byte_buffer, db_state *state)
{
- byte *sql;
+ char *sql;
+ char *buffer = (char *)byte_buffer;
db_result_msg msg;
SQLLEN num_param_values;
int i, ver = 0,
@@ -847,7 +858,7 @@ static db_result_msg db_param_query(byte *buffer, db_state *state)
ei_get_type(buffer, &index, &erl_type, &size);
- sql = (byte*)safe_malloc((sizeof(byte) * (size + 1)));
+ sql = safe_malloc((sizeof(byte) * (size + 1)));
ei_decode_string(buffer, &index, sql);
ei_decode_long(buffer, &index, &long_num_param_values);
@@ -871,7 +882,9 @@ static db_result_msg db_param_query(byte *buffer, db_state *state)
updates/deletes that affect no rows */
if(!sql_success(result) &&
!(result == SQL_NO_DATA && !strcmp((char *)diagnos.sqlState, INFO))) {
- msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ msg = encode_error_message((char*)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
} else {
for (i = 0; i < param_status.params_processed; i++) {
switch (param_status.param_status_array[i]) {
@@ -886,7 +899,9 @@ static db_result_msg db_param_query(byte *buffer, db_state *state)
default:
diagnos =
get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
- msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ msg = encode_error_message((char*)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
i = param_status.params_processed;
break;
}
@@ -899,7 +914,7 @@ static db_result_msg db_param_query(byte *buffer, db_state *state)
msg = encode_result(state);
}
if(msg.length == 0) {
- msg.buffer = dynamic_buffer(state).buff;
+ msg.buffer = (byte *)dynamic_buffer(state).buff;
msg.length = dynamic_buffer(state).index;
msg.dyn_alloc = TRUE;
} else { /* Error occurred */
@@ -956,7 +971,9 @@ static db_result_msg db_describe_table(byte *sql, db_state *state)
if (!sql_success(SQLPrepare(statement_handle(state), (SQLCHAR *)sql, SQL_NTS))){
diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
- msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ msg = encode_error_message((char *)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
clean_state(state);
return msg;
}
@@ -964,7 +981,9 @@ static db_result_msg db_describe_table(byte *sql, db_state *state)
if(!sql_success(SQLNumResultCols(statement_handle(state),
&num_of_columns))) {
diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
- msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ msg = encode_error_message((char *)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
clean_state(state);
return msg;
}
@@ -992,7 +1011,7 @@ static db_result_msg db_describe_table(byte *sql, db_state *state)
ei_x_encode_empty_list(&dynamic_buffer(state));
clean_state(state);
- msg.buffer = dynamic_buffer(state).buff;
+ msg.buffer = (byte *)dynamic_buffer(state).buff;
msg.length = dynamic_buffer(state).index;
msg.dyn_alloc = TRUE;
return msg;
@@ -1088,7 +1107,9 @@ static db_result_msg encode_result(db_state *state)
if(!sql_success(SQLNumResultCols(statement_handle(state),
&num_of_columns))) {
diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
- msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ msg = encode_error_message((char *)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
clean_state(state);
return msg;
}
@@ -1105,7 +1126,9 @@ static db_result_msg encode_result(db_state *state)
if(!sql_success(SQLRowCount(statement_handle(state), &RowCountPtr))) {
diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
- msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ msg = encode_error_message((char *)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
clean_state(state);
return msg;
}
@@ -1652,7 +1675,7 @@ static void encode_data_type(SQLSMALLINT sql_type, SQLINTEGER size,
}
}
-static Boolean decode_params(db_state *state, byte *buffer, int *index, param_array **params,
+static Boolean decode_params(db_state *state, char *buffer, int *index, param_array **params,
int i, int j, int num_param_values)
{
int erl_type, size;
@@ -1688,7 +1711,7 @@ static Boolean decode_params(db_state *state, byte *buffer, int *index, param_ar
if(erl_type != ERL_STRING_EXT) {
return FALSE;
}
- ei_decode_string(buffer, index, &(param->values.string[param->offset]));
+ ei_decode_string(buffer, index, (char*)&(param->values.string[param->offset]));
param->offset += param->type.len;
}
break;
@@ -1747,9 +1770,9 @@ static Boolean decode_params(db_state *state, byte *buffer, int *index, param_ar
return FALSE;
}
if (strncmp((char*)atomarray,"true",4) == 0)
- param->values.bool[j] = TRUE;
+ param->values.boolean[j] = TRUE;
else if (strncmp((char*)atomarray,"false",5) == 0)
- param->values.bool[j] = FALSE;
+ param->values.boolean[j] = FALSE;
else
return -1;
break;
@@ -2197,7 +2220,7 @@ static void init_driver(int erl_auto_commit_mode, int erl_trace_driver,
DO_EXIT(EXIT_CONNECTION);
}
-static void init_param_column(param_array *params, byte *buffer, int *index,
+static void init_param_column(param_array *params, char *buffer, int *index,
int num_param_values, db_state* state)
{
long user_type, precision, scale, length;
@@ -2337,7 +2360,7 @@ static void init_param_column(param_array *params, byte *buffer, int *index,
params->type.c = SQL_C_BIT;
params->type.len = sizeof(byte);
params->type.col_size = params->type.len;
- params->values.bool =
+ params->values.boolean =
(byte *)safe_malloc(num_param_values * params->type.len);
break;
}
@@ -2509,7 +2532,7 @@ static db_result_msg map_sql_2_c_column(db_column* column, db_state *state)
return msg;
}
-static param_array * bind_parameter_arrays(byte *buffer, int *index,
+static param_array * bind_parameter_arrays(char *buffer, int *index,
int cols, int num_param_values,
db_state *state)
{
@@ -2581,7 +2604,7 @@ static void * retrive_param_values(param_array *Param)
case SQL_C_DOUBLE:
return (void *)Param->values.floating;
case SQL_C_BIT:
- return (void *)Param->values.bool;
+ return (void *)Param->values.boolean;
default:
DO_EXIT(EXIT_FAILURE); /* Should not happen */
}
@@ -2669,7 +2692,7 @@ static db_result_msg retrive_scrollable_cursor_support_info(db_state *state)
ei_x_encode_atom(&dynamic_buffer(state), "false");
ei_x_encode_atom(&dynamic_buffer(state), "false");
}
- msg.buffer = dynamic_buffer(state).buff;
+ msg.buffer = (byte *)dynamic_buffer(state).buff;
msg.length = dynamic_buffer(state).index;
msg.dyn_alloc = TRUE;
return msg;
@@ -2700,7 +2723,9 @@ static db_result_msg more_result_sets(db_state *state)
diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state), extended_errors(state));
strcat((char *)diagnos.error_msg,
"Failed to create on of the result sets");
- msg = encode_error_message(diagnos.error_msg, extended_error(state, diagnos.sqlState), diagnos.nativeError);
+ msg = encode_error_message((char*)diagnos.error_msg,
+ extended_error(state, diagnos.sqlState),
+ diagnos.nativeError);
return msg;
}
}
diff --git a/lib/odbc/c_src/odbcserver.h b/lib/odbc/c_src/odbcserver.h
index 0461c57d1f..4dc950f67e 100644
--- a/lib/odbc/c_src/odbcserver.h
+++ b/lib/odbc/c_src/odbcserver.h
@@ -120,7 +120,7 @@
/*------------------------ TYPDEFS ----------------------------------*/
-typedef char byte;
+typedef unsigned char byte;
typedef int Boolean;
typedef struct {
@@ -158,7 +158,7 @@ typedef struct {
byte *string;
SQLINTEGER *integer;
double *floating;
- byte *bool;
+ byte *boolean;
}values;
} param_array;
diff --git a/lib/odbc/configure.in b/lib/odbc/configure.in
index c5cf2786ca..7b02f0d4df 100644
--- a/lib/odbc/configure.in
+++ b/lib/odbc/configure.in
@@ -25,12 +25,7 @@ dnl define([AC_CACHE_SAVE], )dnl
dnl Process this file with autoconf to produce a configure script.
AC_INIT(c_src/odbcserver.c)
-if test -z "$ERL_TOP" || test ! -d $ERL_TOP ; then
- AC_CONFIG_AUX_DIRS(autoconf)
-else
- erl_top=${ERL_TOP}
- AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
-fi
+AC_CONFIG_AUX_DIRS(${ERL_TOP}/erts/autoconf)
if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
AC_CANONICAL_HOST
@@ -106,6 +101,11 @@ if test "$erl_checkBoth" = 1; then
AC_CHECK_FUNC(accept, odbc_erl_checkNsl=0, [LIBS=$tk_oldLibs])
fi
AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, main, [LIBS="$LIBS -lnsl"]))
+case "$host_os" in
+ haiku*)
+ AC_CHECK_LIB(network, socket)
+ ;;
+esac
dnl Checks for header files.
AC_HEADER_STDC
@@ -153,6 +153,13 @@ AC_SUBST(TARGET_FLAGS)
AC_CHECK_LIB(iodbc, SQLAllocHandle,[ODBC_LIB="$ODBC_LIB -lodbc"; odbc_lib_link_success=yes])
;;
+ haiku*)
+ TARGET_FLAGS="-DUNIX"
+ ODBC_LIB= -L"/system/lib"
+ ODBC_INCLUDE="-I/system/develop/headers"
+ dnl Haiku's package manager will deal with this for us
+ AC_CHECK_LIB(odbc, SQLAllocHandle,[ODBC_LIB="$ODBC_LIB -lodbc"; odbc_lib_link_success=yes])
+ ;;
win32|cygwin)
TARGET_FLAGS="-DWIN32"
AC_CHECK_LIB(ws2_32, main)
diff --git a/lib/odbc/doc/src/Makefile b/lib/odbc/doc/src/Makefile
index a6311ceede..b66341f9eb 100644
--- a/lib/odbc/doc/src/Makefile
+++ b/lib/odbc/doc/src/Makefile
@@ -30,11 +30,6 @@ VSN=$(ODBC_VSN)
APPLICATION=odbc
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -57,75 +52,8 @@ BOOK_FILES = book.xml
XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) \
$(XML_PART_FILES) $(XML_CHAPTER_FILES)
-GIF_FILES = \
+IMAGE_FILES = \
odbc_app_arc.gif
# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_HTML_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) \
-
-INFO_FILE = ../../info
-EXTRA_FILES = $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-$(HTMLDIR)/%.gif: %.gif # Copy them to ../html
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%) # We depend just to copy them to ../html
-
-debug opt:
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/odbc/doc/src/databases.xml b/lib/odbc/doc/src/databases.xml
index 614c327a46..0f89d14f3b 100644
--- a/lib/odbc/doc/src/databases.xml
+++ b/lib/odbc/doc/src/databases.xml
@@ -103,9 +103,9 @@
odbc:sql_query(Ref, "INSERT INTO EMPLOYEE VALUES(1, 'Jane', 'Doe', 'F')").
</code>
</note>
- <p>You may also input data using <seealso marker="odbc#param_query">param_query/[3,4]</seealso> and then
+ <p>You may also input data using <seeerl marker="odbc#param_query">param_query/[3,4]</seeerl> and then
the input data will have the Erlang type corresponding to the
- ODBC type of the column.<seealso marker="#type">See ODBC to Erlang mapping</seealso></p>
+ ODBC type of the column.<seeguide marker="#type">See ODBC to Erlang mapping</seeguide></p>
<p> <marker id="type"></marker>
When selecting data from a table, all data
types are returned from the database to the ODBC driver as an
@@ -233,7 +233,7 @@ when p >= 16 </cell>
</table>
<note>
<p>To find out which data types will be returned for the
- columns in a table use the function <seealso marker="odbc#describe_table">describe_table/[2,3]</seealso></p>
+ columns in a table use the function <seeerl marker="odbc#describe_table">describe_table/[2,3]</seeerl></p>
</note>
</section>
@@ -280,7 +280,7 @@ when p >= 16 </cell>
with question marks and then provide lists of values for each
parameter. For instance you can use this to insert multiple
rows into the <c>EMPLOYEE</c> table while executing only a
- single SQL statement, for example code see <seealso marker="getting_started#param_query">"Using the Erlang API"</seealso> section in the "Getting Started" chapter.</p>
+ single SQL statement, for example code see <seeguide marker="getting_started#param_query">"Using the Erlang API"</seeguide> section in the "Getting Started" chapter.</p>
</section>
</section>
</chapter>
diff --git a/lib/odbc/doc/src/error_handling.xml b/lib/odbc/doc/src/error_handling.xml
index 8d794d2c99..40bf7f7f38 100644
--- a/lib/odbc/doc/src/error_handling.xml
+++ b/lib/odbc/doc/src/error_handling.xml
@@ -63,7 +63,7 @@
<title>Timeouts </title>
<p>All request made by the client to the connection are
synchronous. If the timeout is used and expires the client
- process will exit with reason timeout. Proably the right thing
+ process will exit with reason timeout. Probably the right thing
to do is let the client die and perhaps be restarted by its
supervisor. But if the client chooses to catch this timeout,
it is a good idea to wait a little while before trying
@@ -73,7 +73,7 @@
</section>
<section>
- <title>Gaurds </title>
+ <title>Guards </title>
<p>All API-functions are guarded and if you pass an argument of
the wrong type a runtime error will occur. All input parameters
to internal functions are trusted to be correct. It is a good
diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml
index 8d708162e4..b219f79b87 100644
--- a/lib/odbc/doc/src/notes.xml
+++ b/lib/odbc/doc/src/notes.xml
@@ -32,7 +32,84 @@
<p>This document describes the changes made to the odbc application.
</p>
- <section><title>ODBC 2.12.4</title>
+ <section><title>ODBC 2.13.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Make sure odbc c-process exits when erlang process orders
+ it to shutdown.</p>
+ <p>
+ Own Id: OTP-17188 Aux Id: ERL-1448 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>ODBC 2.13.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed usage of <c>AC_CONFIG_AUX_DIRS()</c> macros in
+ configure script sources.</p>
+ <p>
+ Own Id: OTP-17093 Aux Id: ERL-1447, PR-2948 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>ODBC 2.13.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Changes in order to build on the Haiku operating system.</p>
+ <p>
+ Thanks to Calvin Buckley</p>
+ <p>
+ Own Id: OTP-16707 Aux Id: PR-2638 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>ODBC 2.13</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix various compiler warnings on 64-bit Windows.</p>
+ <p>
+ Own Id: OTP-15800</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Rewrite due to the removal of <c>erl_interface</c> legacy
+ functions.</p>
+ <p>
+ Own Id: OTP-16544 Aux Id: OTP-16328 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>ODBC 2.12.4</title>
<section><title>Improvements and New Features</title>
<list>
diff --git a/lib/odbc/doc/src/odbc.xml b/lib/odbc/doc/src/odbc.xml
index 1a3063e6ce..f7d623d974 100644
--- a/lib/odbc/doc/src/odbc.xml
+++ b/lib/odbc/doc/src/odbc.xml
@@ -126,7 +126,7 @@
<section>
<title>ERROR HANDLING </title>
<p>The error handling strategy and possible errors sources are
- described in the Erlang ODBC <seealso marker="error_handling">User's Guide.</seealso></p>
+ described in the Erlang ODBC <seeguide marker="error_handling">User's Guide.</seeguide></p>
</section>
<funcs>
<func>
@@ -223,7 +223,7 @@
</list>
<note>
- <p>The current implementation spawns a port programm
+ <p>The current implementation spawns a port program
written in C that utilizes the actual ODBC driver. There
is a default timeout of 5000 msec for this port programm
to connect to the Erlang ODBC application. This timeout
@@ -339,7 +339,7 @@
</type>
<desc>
<p>Executes a parameterized SQL query. For an
- example see the <seealso marker="getting_started#param_query">"Using the Erlang API"</seealso> in the Erlang ODBC
+ example see the <seeguide marker="getting_started#param_query">"Using the Erlang API"</seeguide> in the Erlang ODBC
User's Guide.</p>
<note>
<p>Use the function describe_table/[2,3] to find out which
@@ -348,7 +348,7 @@
capital letters, alas it is not currently supported by the
param_query function. Too know which Erlang data type
corresponds to an ODBC data type see the Erlang to ODBC
- data type <seealso marker="databases#type">mapping</seealso> in the User's Guide.</p>
+ data type <seeguide marker="databases#type">mapping</seeguide> in the User's Guide.</p>
</note>
</desc>
</func>
@@ -383,7 +383,7 @@
<desc>
<p> Starts the odbc application. Default type
is temporary.
- <seealso marker="kernel:application">See application(3)</seealso>
+ <seeerl marker="kernel:application">See application(3)</seeerl>
</p>
</desc>
</func>
@@ -394,7 +394,7 @@
<desc>
<p> Stops the odbc application.
- <seealso marker="kernel:application">See application(3)</seealso>
+ <seeerl marker="kernel:application">See application(3)</seeerl>
</p>
</desc>
</func>
diff --git a/lib/odbc/test/README b/lib/odbc/test/README
index 0a8495afbb..5ae6073d9a 100644
--- a/lib/odbc/test/README
+++ b/lib/odbc/test/README
@@ -47,7 +47,7 @@ something like this:
--- Start example of .odbc.ini ----
-[Postgres]
+[PostgresLinux64Ubuntu]
Driver=/usr/lib/psqlodbc.so
Description=Postgres driver
ServerName=myhost
diff --git a/lib/odbc/test/postgres.erl b/lib/odbc/test/postgres.erl
index 1955358206..e055be9544 100644
--- a/lib/odbc/test/postgres.erl
+++ b/lib/odbc/test/postgres.erl
@@ -207,7 +207,7 @@ bit_true_selected() ->
%-------------------------------------------------------------------------
float_min() ->
- 1.79e-307.
+ 5.0e-324.
float_max() ->
1.79e+308.
@@ -215,7 +215,7 @@ create_float_table() ->
" (FIELD float)".
float_underflow() ->
- "1.80e-308".
+ "2.4e-324".
float_overflow() ->
"1.80e+308".
@@ -288,7 +288,7 @@ describe_string() ->
{"str4",{sql_varchar,10}}]}.
describe_floating() ->
- {ok,[{"f",sql_real},{"r",sql_real},{"d",{sql_float,15}}]}.
+ {ok,[{"f",sql_real},{"r",sql_real},{"d",{sql_float,17}}]}.
describe_dec_num() ->
{ok,[{"mydec",{sql_numeric,9,3}},{"mynum",{sql_numeric,9,2}}]}.
diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk
index df6db09f2f..717da2b77c 100644
--- a/lib/odbc/vsn.mk
+++ b/lib/odbc/vsn.mk
@@ -1 +1 @@
-ODBC_VSN = 2.12.4
+ODBC_VSN = 2.13.3
diff --git a/lib/os_mon/Makefile b/lib/os_mon/Makefile
index 40ce94e0c7..f45065a79d 100644
--- a/lib/os_mon/Makefile
+++ b/lib/os_mon/Makefile
@@ -35,3 +35,4 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/os_mon/c_src/memsup.c b/lib/os_mon/c_src/memsup.c
index 322e474c3b..949bfdc4e7 100644
--- a/lib/os_mon/c_src/memsup.c
+++ b/lib/os_mon/c_src/memsup.c
@@ -114,6 +114,13 @@
#include <sys/sysinfo.h>
#endif
+#if defined(__APPLE__)
+#include <mach/mach.h>
+static mach_port_t mach_host_port;
+static vm_size_t mach_page_size;
+static uint64_t total_memory_size;
+#endif
+
/* commands */
#include "memsup.h"
@@ -189,14 +196,18 @@ static void print_error(const char *,...);
#define F_MEM_SHARED (1 << 4)
#define F_SWAP_TOTAL (1 << 5)
#define F_SWAP_FREE (1 << 6)
+#define F_MEM_AVAIL (1 << 7)
+#define F_MEM_CACHED_X (1 << 8)
typedef struct {
unsigned int flag;
unsigned long pagesize;
unsigned long total;
unsigned long free;
+ unsigned long available;
unsigned long buffered;
unsigned long cached;
+ unsigned long cached_x;
unsigned long shared;
unsigned long total_swap;
unsigned long free_swap;
@@ -303,10 +314,15 @@ get_mem_procfs(memory_ext *me){
bp = strstr(buffer, "Buffers:");
if (bp != NULL && sscanf(bp, "Buffers: %lu kB\n", &(me->buffered))) me->flag |= F_MEM_BUFFERS;
-
+
bp = strstr(buffer, "Cached:");
if (bp != NULL && sscanf(bp, "Cached: %lu kB\n", &(me->cached))) me->flag |= F_MEM_CACHED;
+ bp = strstr(buffer, "SReclaimable:");
+ if (bp != NULL && sscanf(bp, "SReclaimable: %lu kB\n", &me->cached_x)) me->flag |= F_MEM_CACHED_X;
+
+ bp = strstr(buffer, "MemAvailable:");
+ if (bp != NULL && sscanf(bp, "MemAvailable: %lu kB\n", &me->available)) me->flag |= F_MEM_AVAIL;
/* Swap */
@@ -394,6 +410,48 @@ get_extended_mem_sgi(memory_ext *me) {
}
#endif
+#if defined(__APPLE__)
+static void
+init_apple(void) {
+ kern_return_t kr;
+ mach_msg_type_number_t count;
+ host_basic_info_data_t hinfo;
+
+ mach_host_port = mach_host_self();
+
+ count = HOST_BASIC_INFO_COUNT;
+ kr = host_info(mach_host_port, HOST_BASIC_INFO, (host_info_t) &hinfo, &count);
+ if (kr != KERN_SUCCESS) {
+ abort();
+ }
+ total_memory_size = hinfo.max_mem;
+
+ kr = host_page_size(mach_host_port, &mach_page_size);
+ if (kr != KERN_SUCCESS) {
+ abort();
+ }
+}
+
+static void
+get_extended_mem_apple(memory_ext *me) {
+ kern_return_t kr;
+ host_basic_info_data_t hinfo;
+ mach_msg_type_number_t count;
+ vm_statistics_data_t vm_stat;
+
+ count = HOST_VM_INFO_COUNT;
+ kr = host_statistics(mach_host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &count);
+ if (kr != KERN_SUCCESS) {
+ return;
+ }
+
+ me->free = vm_stat.free_count * mach_page_size;
+ me->total = total_memory_size;
+ me->pagesize = 1;
+ me->flag = F_MEM_TOTAL | F_MEM_FREE;
+}
+#endif
+
static void
get_extended_mem(memory_ext *me) {
/* android */
@@ -418,6 +476,9 @@ get_extended_mem(memory_ext *me) {
#if defined(_SC_AVPHYS_PAGES)
if (get_extended_mem_sysconf(me)) return;
+#elif defined(__APPLE__)
+ get_extended_mem_apple(me);
+
/* We fake the rest */
/* SunOS4 (for example) */
#else
@@ -471,6 +532,15 @@ fail:
print_error("%s", strerror(errno));
exit(1);
}
+#elif defined(__APPLE__)
+ {
+ memory_ext me;
+ me.free = 0;
+ get_extended_mem_apple(&me);
+ *used = me.total - me.free;
+ *tot = total_memory_size;
+ *pagesize = 1;
+ }
#else /* SunOS4 */
*used = (1<<27); /* Fake! 128 MB used */
*tot = (1<<28); /* Fake! 256 MB total */
@@ -500,7 +570,9 @@ extended_show_mem(void){
/* extensions */
if (me.flag & F_MEM_BUFFERS){ send_tag(MEM_BUFFERS); send(me.buffered, ps); }
if (me.flag & F_MEM_CACHED) { send_tag(MEM_CACHED); send(me.cached, ps); }
+ if (me.flag & F_MEM_CACHED_X){ send_tag(MEM_CACHED_X); send(me.cached_x, ps); }
if (me.flag & F_MEM_SHARED) { send_tag(MEM_SHARED); send(me.shared, ps); }
+ if (me.flag & F_MEM_AVAIL) { send_tag(MEM_AVAIL); send(me.available, ps); }
/* swap */
if (me.flag & F_SWAP_TOTAL) { send_tag(SWAP_TOTAL); send(me.total_swap, ps); }
@@ -567,7 +639,12 @@ message_loop(int erlin_fd)
int
MAIN(int argc, char **argv)
{
+#ifdef __APPLE__
+ init_apple();
+#endif
+
program_name = argv[0];
+
message_loop(ERLIN_FD);
return 0;
}
diff --git a/lib/os_mon/c_src/memsup.h b/lib/os_mon/c_src/memsup.h
index 49c54f2f77..cac8b75155 100644
--- a/lib/os_mon/c_src/memsup.h
+++ b/lib/os_mon/c_src/memsup.h
@@ -43,5 +43,7 @@
/*IG*/ #define MEM_SHARED 8
/*IG*/ #define SWAP_TOTAL 9
/*IG*/ #define SWAP_FREE 10
+/*IG*/ #define MEM_CACHED_X 11
+/*IG*/ #define MEM_AVAIL 12
#endif
diff --git a/lib/os_mon/c_src/nteventlog/elog_main.c b/lib/os_mon/c_src/nteventlog/elog_main.c
index c31e3ef1ff..f1baaa4084 100644
--- a/lib/os_mon/c_src/nteventlog/elog_main.c
+++ b/lib/os_mon/c_src/nteventlog/elog_main.c
@@ -266,11 +266,11 @@ BOOL output_record(char *category, EVENTLOGRECORD *event){
strlen(PIPE_LOG_FORMAT) +
PIPE_LOG_EXTRA);
sprintf(buff,PIPE_LOG_FORMAT,
- strlen(tbuff),tbuff,
- strlen(category), category,
- strlen(fac), fac,
- strlen(sev), sev,
- strlen(bigbuff), bigbuff);
+ (int)strlen(tbuff), tbuff,
+ (int)strlen(category), category,
+ (int)strlen(fac), fac,
+ (int)strlen(sev), sev,
+ (int)strlen(bigbuff), bigbuff);
ret = data_to_pipe(buff,ackbuff, ACK_MAX);
if(ret && strcmp(ackbuff,PIPE_LOG_ACK))
ret = FALSE;
diff --git a/lib/os_mon/doc/src/Makefile b/lib/os_mon/doc/src/Makefile
index 8e9a4c333c..d16f2b4831 100644
--- a/lib/os_mon/doc/src/Makefile
+++ b/lib/os_mon/doc/src/Makefile
@@ -28,11 +28,6 @@ VSN=$(OS_MON_VSN)
APPLICATION=os_mon
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -49,7 +44,7 @@ XML_CHAPTER_FILES = notes.xml
BOOK_FILES = book.xml
-GIF_FILES =
+IMAGE_FILES =
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
@@ -57,66 +52,4 @@ XML_FILES = \
# ----------------------------------------------------
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES) $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6DIR)/* "$(RELEASE_PATH)/man/man6"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/os_mon/doc/src/cpu_sup.xml b/lib/os_mon/doc/src/cpu_sup.xml
index b7adb2bcd2..493a8040c1 100644
--- a/lib/os_mon/doc/src/cpu_sup.xml
+++ b/lib/os_mon/doc/src/cpu_sup.xml
@@ -33,7 +33,7 @@
<description>
<p><c>cpu_sup</c> is a process which supervises the CPU load
and CPU utilization. It is part of the OS_Mon application, see
- <seealso marker="os_mon_app">os_mon(6)</seealso>. Available for Unix,
+ <seeapp marker="os_mon_app">os_mon(6)</seeapp>. Available for Unix,
although CPU utilization values (<c>util/0,1</c>) are only
available for Solaris, Linux and FreeBSD.</p>
<p>The load values are proportional to how long time a runnable
@@ -276,7 +276,7 @@
<section>
<title>See Also</title>
- <p><seealso marker="os_mon_app">os_mon(3)</seealso></p>
+ <p><seeapp marker="os_mon_app">os_mon(3)</seeapp></p>
</section>
</erlref>
diff --git a/lib/os_mon/doc/src/disksup.xml b/lib/os_mon/doc/src/disksup.xml
index 116a6dfd19..5a7eb4d27d 100644
--- a/lib/os_mon/doc/src/disksup.xml
+++ b/lib/os_mon/doc/src/disksup.xml
@@ -33,7 +33,7 @@
<description>
<p><c>disksup</c> is a process which supervises the available disk
space in the system. It is part of the OS_Mon application, see
- <seealso marker="os_mon_app">os_mon(6)</seealso>. Available for Unix
+ <seeapp marker="os_mon_app">os_mon(6)</seeapp>. Available for Unix
and Windows.</p>
<p>Periodically checks the disks. For each disk or partition which
uses more than a certain amount of the available space, the alarm
@@ -50,7 +50,7 @@
</item>
</taglist>
<p>Alarms are reported to the SASL alarm handler, see
- <seealso marker="sasl:alarm_handler">alarm_handler(3)</seealso>.
+ <seeerl marker="sasl:alarm_handler">alarm_handler(3)</seeerl>.
To set an alarm, <c>alarm_handler:set_alarm(Alarm)</c> is called
where <c>Alarm</c> is the alarm specified above.</p>
<p>The alarms are cleared automatically when the alarm cause is no
@@ -83,10 +83,10 @@
of Unix tools like <c>df</c>. The returned disk data and alarms
can be different when using this option.</p>
<p>The parameter is ignored on platforms that are known to not be
- posix compatible (Windows and SunOS).</p>
+ POSIX compatible (Windows and SunOS).</p>
</item>
</taglist>
- <p>See <seealso marker="kernel:config">config(4)</seealso> for
+ <p>See <seefile marker="kernel:config">config(4)</seefile> for
information about how to change the value of configuration
parameters.</p>
</section>
@@ -134,7 +134,7 @@
<p>The change will take effect after the next disk space check
and is non-persist. That is, in case of a process restart,
this value is forgotten and the default value will be used.
- See <seealso marker="#config">Configuration</seealso> above.</p>
+ See <seeerl marker="#config">Configuration</seeerl> above.</p>
</desc>
</func>
<func>
@@ -159,15 +159,15 @@
<p>The change will take effect during the next disk space check
and is non-persist. That is, in case of a process restart,
this value is forgotten and the default value will be used.
- See <seealso marker="#config">Configuration</seealso> above.</p>
+ See <seeerl marker="#config">Configuration</seeerl> above.</p>
</desc>
</func>
</funcs>
<section>
<title>See Also</title>
- <p><seealso marker="sasl:alarm_handler">alarm_handler(3)</seealso>,
- <seealso marker="os_mon_app">os_mon(3)</seealso></p>
+ <p><seeerl marker="sasl:alarm_handler">alarm_handler(3)</seeerl>,
+ <seeapp marker="os_mon_app">os_mon(3)</seeapp></p>
</section>
</erlref>
diff --git a/lib/os_mon/doc/src/memsup.xml b/lib/os_mon/doc/src/memsup.xml
index 51c78b07c2..a6f93fdba6 100644
--- a/lib/os_mon/doc/src/memsup.xml
+++ b/lib/os_mon/doc/src/memsup.xml
@@ -33,8 +33,8 @@
<description>
<p><c>memsup</c> is a process which supervises the memory usage for
the system and for individual processes. It is part of the OS_Mon
- application, see <seealso marker="os_mon_app">os_mon(6)</seealso>.
- Available for Unix, Windows and VxWorks.</p>
+ application, see <seeapp marker="os_mon_app">os_mon(6)</seeapp>.
+ Available for Unix and Windows.</p>
<p>Periodically performs a memory check:</p>
<list type="bulleted">
<item>
@@ -49,21 +49,21 @@
</item>
</list>
<p>Alarms are reported to the SASL alarm handler, see
- <seealso marker="sasl:alarm_handler">alarm_handler(3)</seealso>.
+ <seeerl marker="sasl:alarm_handler">alarm_handler(3)</seeerl>.
To set an alarm, <c>alarm_handler:set_alarm(Alarm)</c> is called
where <c>Alarm</c> is either of the alarms specified above.</p>
<p>The alarms are cleared automatically when the alarm cause is no
longer valid.</p>
<p>The function
- <seealso marker="#get_memory_data/0">get_memory_data()</seealso>
+ <seemfa marker="#get_memory_data/0">get_memory_data()</seemfa>
can be used to retrieve the result of the latest periodic memory
check.</p>
<p>There is also a interface to system dependent memory data,
- <seealso marker="#get_system_memory_data/0">get_system_memory_data()</seealso>.
+ <seemfa marker="#get_system_memory_data/0">get_system_memory_data()</seemfa>.
The result is highly dependent on the underlying operating
system and the interface is targeted primarily for systems
- without virtual memory (e.g. VxWorks). The output on other
- systems is however still valid, although sparse.</p>
+ without virtual memory. However, the output on other
+ systems is still valid, although sparse.</p>
<p>A call to <c>get_system_memory_data/0</c> is more costly
than a call to <c>get_memory_data/0</c> as data is collected
synchronously when this function is called.</p>
@@ -102,7 +102,8 @@
<item>
<p>A timeout, in seconds, for how long the <c>memsup</c>
process should wait for a result from a memory check. If
- the timeout expires, a warning message <c>"OS_MON (memsup) timeout"</c> is issued via <c>error_logger</c> and any
+ the timeout expires, a warning message <c>"OS_MON (memsup) timeout"</c>
+ is issued via <c>error_logger</c> and any
pending, synchronous client calls will return a dummy value.
Normally, this situation should not occur. There have been
cases on Linux, however, where the pseudo file from which
@@ -120,8 +121,27 @@
systems with many concurrent processes, as each process memory
check makes a traversal of the entire list of processes.</p>
</item>
+ <tag><marker id="memsup_improved_system_memory_data"/><c>memsup_improved_system_memory_data = bool()</c></tag>
+ <item>
+ <p>Determines the behaviour of the
+ <seemfa marker="#get_system_memory_data/0"><c>get_system_memory_data()</c></seemfa>
+ function. When this configuration parameter is <c>false</c>,
+ <c>get_system_memory_data()</c> behaves as it has done
+ up until the point of the introduction of the configuration
+ parameter. When set to <c>true</c> new tagged tuples are
+ allowed in the result. Such new tuples may be introduced at
+ any time without prior notice. The classification of
+ <c>cached_memory</c> on Linux systems will also change so that
+ more memory is classified as <c>cached_memory</c>.
+ </p>
+ <note><p>This configuration parameter defaults <c>false</c> and will
+ do so up until OTP 23. As of OTP 24 this configuration parameter
+ will be removed and <c>get_system_memory_data()</c> will begin
+ behaving as it does now when the configuration parameter has
+ been set to <c>true</c>.</p></note>
+ </item>
</taglist>
- <p>See <seealso marker="kernel:config">config(4)</seealso> for
+ <p>See <seefile marker="kernel:config">config(4)</seefile> for
information about how to change the value of configuration
parameters.</p>
</section>
@@ -178,13 +198,6 @@
<item>The amount of memory available to the whole operating
system. This may well be equal to <c>total_memory</c> but
not necessarily.</item>
- <tag><c>largest_free</c></tag>
- <item>The size of the largest contiguous free memory block
- available to the Erlang emulator.</item>
- <tag><c>number_of_free</c></tag>
- <item>The number of free blocks available to the Erlang runtime
- system. This gives a fair indication of how fragmented
- the memory is.</item>
<tag><c>buffered_memory</c></tag>
<item>
The amount of memory the system uses for temporary storing raw disk blocks.
@@ -204,15 +217,63 @@
</item>
</taglist>
+
+ <p>Note that the order of the tuples in the resulting list is undefined
+ and may change at any time.</p>
+
<p>All memory sizes are presented as number of <em>bytes</em>.</p>
- <p>The <c>largest_free</c> and <c>number_of_free</c> tags are
- currently only returned on a VxWorks system.</p>
<p>Returns the empty list [] if <c>memsup</c> is not available,
or if the memory check times out.</p>
<note><p>
- On linux the memory available to the emulator is <c>cached_memory</c> and <c>buffered_memory</c> in addition to
- <c>free_memory</c>.</p>
+ On Linux the memory available to the emulator is <c>cached_memory</c> and
+ <c>buffered_memory</c> in addition to <c>free_memory</c>.</p>
</note>
+
+ <p>The above describes how it works if the configuration parameter
+ <seeerl marker="#memsup_improved_system_memory_data"><c>memsup_improved_system_memory_data</c></seeerl>
+ has been set to <c>false</c> which currently also is the default
+ behavior. If the configuration parameter is set to <c>true</c> the
+ behavior is slightly changed:</p>
+ <list>
+ <item>
+ <p>
+ New tagged tuples may be added in the resulting
+ list at any time.
+ </p>
+ </item>
+ <item>
+ <p>
+ On Linux systems the following changes will be made:
+ </p>
+ <list>
+ <item>
+ <p>
+ A new tuple with the tag <c>available_memory</c> will be
+ added to the result when this value is provided by the
+ kernel. The <c>available_memory</c> value informs about
+ the amount memory that is available for use if there is
+ an increased memory need. This value is not based on a
+ calculation of the other provided values and should give
+ a better value of the amount of memory that actually is
+ available than calculating a value based on the other
+ values reported.
+ </p>
+ </item>
+ <item>
+ <p>The classification of <c>cached_memory</c> is changed.
+ Also memory marked as reclaimable in the kernel slab
+ allocator will be added to the value presented as
+ <c>cached_memory</c>.
+ </p>
+ </item>
+ </list>
+ </item>
+ </list>
+ <note><p>As of OTP 24 <c>memsup_improved_system_memory_data</c>
+ configuration parameter will be removed and
+ <c>get_system_memory_data()</c> will begin behaving as it does
+ now when the configuration parameter has been set to
+ <c>true</c>.</p></note>
</desc>
</func>
<func>
@@ -248,7 +309,7 @@
<p>The change will take effect after the next memory check and is
non-persistent. That is, in case of a process restart, this
value is forgotten and the default value will be used. See
- <seealso marker="#config">Configuration</seealso> above.</p>
+ <seeerl marker="#config">Configuration</seeerl> above.</p>
</desc>
</func>
<func>
@@ -268,7 +329,7 @@
<p>The change will take effect during the next periodic memory
check and is non-persistent. That is, in case of a process
restart, this value is forgotten and the default value will be
- used. See <seealso marker="#config">Configuration</seealso>
+ used. See <seeerl marker="#config">Configuration</seeerl>
above.</p>
</desc>
</func>
@@ -289,7 +350,7 @@
<p>The change will take effect during the next periodic memory
check and is non-persistent. That is, in case of a process
restart, this value is forgotten and the default value will be
- used. See <seealso marker="#config">Configuration</seealso>
+ used. See <seeerl marker="#config">Configuration</seeerl>
above.</p>
</desc>
</func>
@@ -315,15 +376,15 @@
<p>The change will take effect for the next memory check and is
non-persistent. That is, in the case of a process restart, this
value is forgotten and the default value will be used. See
- <seealso marker="#config">Configuration</seealso> above.</p>
+ <seeerl marker="#config">Configuration</seeerl> above.</p>
</desc>
</func>
</funcs>
<section>
<title>See Also</title>
- <p><seealso marker="sasl:alarm_handler">alarm_handler(3)</seealso>,
- <seealso marker="os_mon_app">os_mon(3)</seealso></p>
+ <p><seeerl marker="sasl:alarm_handler">alarm_handler(3)</seeerl>,
+ <seeapp marker="os_mon_app">os_mon(3)</seeapp></p>
</section>
</erlref>
diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml
index 63efa96e2f..e4d544cca4 100644
--- a/lib/os_mon/doc/src/notes.xml
+++ b/lib/os_mon/doc/src/notes.xml
@@ -31,6 +31,107 @@
</header>
<p>This document describes the changes made to the OS_Mon application.</p>
+<section><title>Os_Mon 2.6.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The configuration parameter
+ <c>memsup_improved_system_memory_data</c> has been
+ introduced. It can be used to modify the result returned
+ by <c>memsup:get_system_memory_data()</c>. For more
+ information see the <c>memsup</c> documentation.</p>
+ <p>
+ Note that the configuration parameter is intended to be
+ removed in OTP 24 and the modified result is intended to
+ be used as of OTP 24.</p>
+ <p>
+ Own Id: OTP-16906 Aux Id: ERIERL-532 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Os_Mon 2.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p><c>memsup</c> now returns the correct amount of system
+ memory on macOS.</p>
+ <p>
+ Own Id: OTP-16798 Aux Id: ERL-1327 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Fix memsup:get_os_wordsize/0 to return the current size
+ on aarch64.</p>
+ <p>
+ Own Id: OTP-16742</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Os_Mon 2.5.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix various compiler warnings on 64-bit Windows.</p>
+ <p>
+ Own Id: OTP-15800</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Os_Mon 2.5.1.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The configuration parameter
+ <c>memsup_improved_system_memory_data</c> has been
+ introduced. It can be used to modify the result returned
+ by <c>memsup:get_system_memory_data()</c>. For more
+ information see the <c>memsup</c> documentation.</p>
+ <p>
+ Note that the configuration parameter is intended to be
+ removed in OTP 24 and the modified result is intended to
+ be used as of OTP 24.</p>
+ <p>
+ Own Id: OTP-16906 Aux Id: ERIERL-532 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Os_Mon 2.5.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -706,7 +807,7 @@
<item>
<p> Memory information in
<c>memsup:get_system_memory_data/0</c> now has additional
- entries in its property list for linux.</p>
+ entries in its property list for Linux.</p>
<p>
Own Id: OTP-7409 Aux Id: seq10984 </p>
</item>
@@ -753,8 +854,8 @@
<list type="bulleted">
<item>
<p>
- Extended memsup memory probing on linux to use a port
- program to probe memory usage. This is faster then the
+ Extended memsup memory probing on Linux to use a port
+ program to probe memory usage. This is faster than the
previous implementation.</p>
<p>
Own Id: OTP-6860 Aux Id: seq10616 </p>
@@ -1053,4 +1154,3 @@
<p>This version is identical with 1.7.</p>
</section>
</chapter>
-
diff --git a/lib/os_mon/doc/src/nteventlog.xml b/lib/os_mon/doc/src/nteventlog.xml
index 08cf165a24..53712a67ec 100644
--- a/lib/os_mon/doc/src/nteventlog.xml
+++ b/lib/os_mon/doc/src/nteventlog.xml
@@ -33,12 +33,9 @@
<description>
<p><c>nteventlog</c> provides a generic interface to the Windows
event log. It is part of the OS_Mon application, see
- <seealso marker="os_mon_app">os_mon(6)</seealso>. Available for
- Windows versions where the event log is available. That is, not
- for Windows 98 and some other older Windows versions, but for most
- (all?) newer Windows versions.</p>
- <p>This module is used as the Windows backend for <c>os_sup</c>, see
- <seealso marker="os_sup">os_sup(3)</seealso>.</p>
+ <seeapp marker="os_mon_app">os_mon(6)</seeapp>.</p>
+ <p>This module is used as the Windows backend for <c>os_sup</c>. See
+ <seeerl marker="os_sup">os_sup(3)</seeerl>.</p>
<p>To retain backwards compatibility, this module can also be used
to start a standalone <c>nteventlog</c> process which is not part
of the OS_Mon supervision tree. When starting such a process,
@@ -55,7 +52,7 @@
sent to Erlang again after reboot).</p>
<p>If the event log is configured to wrap around automatically,
records that have arrived to the log and been overwritten when
- <c>nteventlog</c> was not running are lost. It however detects
+ <c>nteventlog</c> was not running are lost. However, it detects
this state and loses no records that are not overwritten.</p>
<p>The callback function works as described in <c>os_sup(3)</c>.</p>
</description>
@@ -97,8 +94,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="os_mon_app">os_mon(6)</seealso>,
- <seealso marker="os_sup">os_sup(3)</seealso></p>
+ <p><seeapp marker="os_mon_app">os_mon(6)</seeapp>,
+ <seeerl marker="os_sup">os_sup(3)</seeerl></p>
<p>Windows NT documentation</p>
</section>
</erlref>
diff --git a/lib/os_mon/doc/src/os_mon_app.xml b/lib/os_mon/doc/src/os_mon_app.xml
index c77a9d0411..35aff7503e 100644
--- a/lib/os_mon/doc/src/os_mon_app.xml
+++ b/lib/os_mon/doc/src/os_mon_app.xml
@@ -34,13 +34,13 @@
<p>The operating system monitor, OS_Mon, provides the following
services:</p>
<list type="bulleted">
- <item><seealso marker="cpu_sup">cpu_sup</seealso>
+ <item><seeerl marker="cpu_sup">cpu_sup</seeerl>
CPU load and utilization supervision (Unix)</item>
- <item><seealso marker="disksup">disksup</seealso>
+ <item><seeerl marker="disksup">disksup</seeerl>
Disk supervision(Unix, Windows)</item>
- <item><seealso marker="memsup">memsup</seealso>
- Memory supervision (Unix, Windows, VxWorks)</item>
- <item><seealso marker="os_sup">os_sup</seealso>
+ <item><seeerl marker="memsup">memsup</seeerl>
+ Memory supervision (Unix, Windows)</item>
+ <item><seeerl marker="os_sup">os_sup</seeerl>
Interface to OS system messages (Solaris, Windows)</item>
</list>
<p>To simplify usage of OS_Mon on distributed Erlang systems, it is
@@ -82,19 +82,19 @@
</taglist>
<p>Configuration parameters effecting the different OS_Mon services
are described in the respective man pages.</p>
- <p>See <seealso marker="kernel:config">config(4)</seealso> for
+ <p>See <seefile marker="kernel:config">config(4)</seefile> for
information about how to change the value of configuration
parameters.</p>
</section>
<section>
<title>See Also</title>
- <p><seealso marker="cpu_sup">cpu_sup(3)</seealso>,
- <seealso marker="disksup">disksup(3)</seealso>,
- <seealso marker="memsup">memsup(3)</seealso>,
- <seealso marker="os_sup">os_sup(3)</seealso>,
- <seealso marker="nteventlog">nteventlog(3)</seealso>,
- <seealso marker="snmp:snmp">snmp(3)</seealso>.</p>
+ <p><seeerl marker="cpu_sup">cpu_sup(3)</seeerl>,
+ <seeerl marker="disksup">disksup(3)</seeerl>,
+ <seeerl marker="memsup">memsup(3)</seeerl>,
+ <seeerl marker="os_sup">os_sup(3)</seeerl>,
+ <seeerl marker="nteventlog">nteventlog(3)</seeerl>,
+ <seeerl marker="snmp:snmp">snmp(3)</seeerl>.</p>
</section>
</appref>
diff --git a/lib/os_mon/doc/src/os_sup.xml b/lib/os_mon/doc/src/os_sup.xml
index 4a84165a6c..f8b99f4f70 100644
--- a/lib/os_mon/doc/src/os_sup.xml
+++ b/lib/os_mon/doc/src/os_sup.xml
@@ -34,7 +34,7 @@
<p><c>os_sup</c> is a process providing a message passing service
from the operating system to the error logger in the Erlang
runtime system. It is part of the OS_Mon application, see
- <seealso marker="os_mon_app">os_mon(6)</seealso>. Available for
+ <seeapp marker="os_mon_app">os_mon(6)</seeapp>. Available for
Solaris and Windows.</p>
<p>Messages received from the operating system results in an
user defined callback function being called. This function can do
@@ -45,15 +45,15 @@
<section>
<title>Solaris Operation</title>
<p>The Solaris (SunOS 5.x) messages are retrieved from
- the syslog-daemon, <c>syslogd</c>.</p>
+ the syslog daemon, <c>syslogd</c>.</p>
<p>Enabling the service includes actions which require root
privileges, such as change of ownership and file privileges of an
executable binary file, and creating a modified copy of
the configuration file for <c>syslogd</c>. When <c>os_sup</c> is
terminated, the service must be disabled, meaning the original
configuration must be restored. Enabling/disabling can be done
- either outside or inside <c>os_sup</c>, see
- <seealso marker="#config">Configuration</seealso> below.</p>
+ either outside or inside <c>os_sup</c>. See
+ <seeerl marker="#config">Configuration</seeerl> below.</p>
<warning>
<p>This process cannot run in multiple instances on the same
hardware. OS_Mon must be configured to start <c>os_sup</c> on
@@ -67,9 +67,9 @@
<title>Windows Operation</title>
<p>The Windows messages are retrieved from the eventlog file.</p>
<p>The <c>nteventlog</c> module is used to implement <c>os_sup</c>.
- See <seealso marker="nteventlog">nteventlog(3)</seealso>. Note
+ See <seeerl marker="nteventlog">nteventlog(3)</seeerl>. Note
that the start functions of <c>nteventlog</c> does not need to be
- used, in this case the process is started automatically as part of
+ used, as in this case the process is started automatically as part of
the OS_Mon supervision tree.</p>
<p>OS messages are formatted as a tuple
<c>{Time, Category, Facility, Severity, Message}</c>:</p>
@@ -123,7 +123,7 @@
called as <c>apply(Module, Function, [Msg | Args])</c>.</p>
<p>Default is <c>{os_sup, error_report, [Tag]}</c> which will
send the event to the error logger using
- <seealso marker="kernel:error_logger#error_report/2">error_logger:error_report(Tag, Msg)</seealso>. <c>Tag</c> is the value of
+ <seemfa marker="kernel:error_logger#error_report/2">error_logger:error_report(Tag, Msg)</seemfa>. <c>Tag</c> is the value of
<c>os_sup_errortag</c>, see below.</p>
</item>
<tag><c>os_sup_errortag = atom()</c></tag>
@@ -232,8 +232,8 @@
<section>
<title>See also</title>
- <p><seealso marker="kernel:error_logger">error_logger(3)</seealso>,
- <seealso marker="os_mon_app">os_mon(3)</seealso></p>
+ <p><seeerl marker="kernel:error_logger">error_logger(3)</seeerl>,
+ <seeapp marker="os_mon_app">os_mon(3)</seeapp></p>
<p><c>syslogd(1M)</c>, <c>syslog.conf(4)</c> in the Solaris
documentation.</p>
<p></p>
diff --git a/lib/os_mon/include/memsup.hrl b/lib/os_mon/include/memsup.hrl
index 30658f5b23..5c8dd98b45 100644
--- a/lib/os_mon/include/memsup.hrl
+++ b/lib/os_mon/include/memsup.hrl
@@ -39,6 +39,8 @@
-define( MEM_SHARED , 8 ).
-define( SWAP_TOTAL , 9 ).
-define( SWAP_FREE , 10 ).
+-define( MEM_CACHED_X , 11 ).
+-define( MEM_AVAIL , 12 ).
-endif.
diff --git a/lib/os_mon/src/Makefile b/lib/os_mon/src/Makefile
index 923a31f290..98c5ced068 100644
--- a/lib/os_mon/src/Makefile
+++ b/lib/os_mon/src/Makefile
@@ -34,7 +34,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/os_mon-$(VSN)
# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
-MODULES= disksup memsup cpu_sup os_mon os_sup os_mon_sysinfo nteventlog
+MODULES= disksup memsup cpu_sup os_mon os_mon_mib os_sup os_mon_sysinfo nteventlog
INCLUDE=../include
CSRC=../c_src
diff --git a/lib/os_mon/src/cpu_sup.erl b/lib/os_mon/src/cpu_sup.erl
index d28f229b3e..8c130e28ee 100644
--- a/lib/os_mon/src/cpu_sup.erl
+++ b/lib/os_mon/src/cpu_sup.erl
@@ -29,7 +29,7 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+ terminate/2]).
%% Internal protocol with the port program
-define(nprocs,"n").
@@ -199,23 +199,6 @@ handle_info(_Info, State) ->
terminate(_Reason, State) ->
exit(State#state.server, normal).
-%% os_mon-2.0
-%% For live downgrade to/upgrade from os_mon-1.8[.1]
-code_change(Vsn, PrevState, "1.8") ->
- case Vsn of
-
- %% Downgrade from this version
- {down, _Vsn} ->
- process_flag(trap_exit, false);
-
- %% Upgrade to this version
- _Vsn ->
- process_flag(trap_exit, true)
- end,
- {ok, PrevState};
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
%%----------------------------------------------------------------------
%% internal functions
%%----------------------------------------------------------------------
diff --git a/lib/os_mon/src/disksup.erl b/lib/os_mon/src/disksup.erl
index 4253067c90..dcc624212c 100644
--- a/lib/os_mon/src/disksup.erl
+++ b/lib/os_mon/src/disksup.erl
@@ -29,7 +29,7 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+ terminate/2]).
%% Other exports
-export([format_status/2, parse_df/2]).
@@ -170,30 +170,6 @@ terminate(_Reason, State) ->
end,
ok.
-%% os_mon-2.0.1
-%% For live downgrade to/upgrade from os_mon-1.8[.1]
-code_change(Vsn, PrevState, "1.8") ->
- case Vsn of
-
- %% Downgrade from this version
- {down, _Vsn} ->
- State = case PrevState#state.port of
- not_used -> PrevState#state{port=noport};
- _ -> PrevState
- end,
- {ok, State};
-
- %% Upgrade to this version
- _Vsn ->
- State = case PrevState#state.port of
- noport -> PrevState#state{port=not_used};
- _ -> PrevState
- end,
- {ok, State}
- end;
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
%%----------------------------------------------------------------------
%% Other exports
%%----------------------------------------------------------------------
diff --git a/lib/os_mon/src/memsup.erl b/lib/os_mon/src/memsup.erl
index b69d657aa7..fdd08eed63 100644
--- a/lib/os_mon/src/memsup.erl
+++ b/lib/os_mon/src/memsup.erl
@@ -32,7 +32,7 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+ terminate/2]).
%% Other exports
-export([format_status/2]).
@@ -57,8 +57,11 @@
ext_wd_timer, % undefined | TimerRef
pending = [], % [reg | {reg,From} | {ext,From}]
ext_pending = [] % [{ext,From}]
+%%% ismd = false % Improved system memory data enabled?
}).
+-define(EXT_MEM_MAP, memsup_ext_memory_type_map__).
+
%%----------------------------------------------------------------------
%% API
%%----------------------------------------------------------------------
@@ -146,6 +149,7 @@ dummy_reply(get_memory_data, true) ->
dummy_reply(get_memory_data, false) ->
{0,0,{self(),0}}.
+param_type(memsup_improved_system_memory_data, Val) when Val==true; Val==false -> true;
param_type(memsup_system_only, Val) when Val==true; Val==false -> true;
param_type(memory_check_interval, Val) when is_integer(Val),
Val>0 -> true;
@@ -159,6 +163,7 @@ param_type(process_memory_high_watermark, Val) when is_number(Val),
Val=<1 -> true;
param_type(_Param, _Val) -> false.
+param_default(memsup_improved_system_memory_data) -> false;
param_default(memsup_system_only) -> false;
param_default(memory_check_interval) -> 1;
param_default(memsup_helper_timeout) -> 30;
@@ -175,7 +180,7 @@ init([]) ->
OS = os:type(),
PortMode = case OS of
- {unix, darwin} -> false;
+ {unix, darwin} -> true;
{unix, freebsd} -> false;
{unix, dragonfly} -> false;
% Linux supports this.
@@ -189,9 +194,12 @@ init([]) ->
_ ->
exit({unsupported_os, OS})
end,
+
+ ISMD = os_mon:get_env(memsup, memsup_improved_system_memory_data),
+
Pid = if
PortMode ->
- spawn_link(fun() -> port_init() end);
+ spawn_link(fun() -> port_init(ISMD) end);
not PortMode ->
undefined
end,
@@ -203,6 +211,21 @@ init([]) ->
SysMem = os_mon:get_env(memsup, system_memory_high_watermark),
ProcMem = os_mon:get_env(memsup, process_memory_high_watermark),
+ persistent_term:put(?EXT_MEM_MAP,
+ #{?MEM_SYSTEM_TOTAL => system_total_memory,
+ ?MEM_TOTAL => total_memory,
+ ?MEM_AVAIL => available_memory,
+ ?MEM_FREE => free_memory,
+ ?MEM_BUFFERS => buffered_memory,
+ ?MEM_CACHED => cached_memory,
+ %% When included cached_x is same as cached...
+ ?MEM_CACHED_X => cached_memory,
+ ?MEM_SHARED => shared_memory,
+ ?MEM_LARGEST_FREE => largest_free,
+ ?MEM_NUMBER_OF_FREE => number_of_free,
+ ?SWAP_TOTAL => total_swap,
+ ?SWAP_FREE => free_swap}),
+
%% Initiate first data collection
self() ! time_to_collect,
@@ -213,6 +236,7 @@ init([]) ->
helper_timeout = sec_to_ms(HelperTimeout),
sys_mem_watermark = SysMem,
proc_mem_watermark = ProcMem,
+%%% ismd = ISMD,
pid=Pid}}.
@@ -510,152 +534,6 @@ terminate(_Reason, State) ->
clear_alarms(),
ok.
-%% os_mon-2.0.1
-%% For live downgrade to/upgrade from os_mon-1.8[.1] and -2.0
-code_change(Vsn, PrevState, "1.8") ->
- case Vsn of
-
- %% Downgrade from this version
- {down, _Vsn} ->
-
- %% Kill the helper process, if there is one,
- %% and flush messages from it
- case PrevState#state.pid of
- Pid when is_pid(Pid) ->
- unlink(Pid), % to prevent 'EXIT' message
- exit(Pid, cancel);
- undefined -> ignore
- end,
- flush(collected_sys),
- flush(collected_ext_sys),
-
- %% Cancel timers, flush timeout messages
- %% and send dummy replies to any pending clients
- case PrevState#state.wd_timer of
- undefined ->
- ignore;
- TimerRef1 ->
- ok = erlang:cancel_timer(TimerRef1, [{async,true}]),
- SysOnly = PrevState#state.sys_only,
- MemUsage = dummy_reply(get_memory_data, SysOnly),
- SysMemUsage1 = dummy_reply(get_system_memory_data),
- reply(PrevState#state.pending,MemUsage,SysMemUsage1)
- end,
- case PrevState#state.ext_wd_timer of
- undefined ->
- ignore;
- TimerRef2 ->
- ok = erlang:cancel_timer(TimerRef2, [{async,true}]),
- SysMemUsage2 = dummy_reply(get_system_memory_data),
- reply(PrevState#state.pending, undef, SysMemUsage2)
- end,
- flush(reg_collection_timeout),
- flush(ext_collection_timeout),
-
- %% Downgrade to old state record
- State = {state,
- PrevState#state.timeout,
- PrevState#state.mem_usage,
- PrevState#state.worst_mem_user,
- PrevState#state.sys_mem_watermark,
- PrevState#state.proc_mem_watermark,
- not PrevState#state.sys_only, % collect_procmem
- undefined, % wd_timer
- [], % pending
- undefined, % ext_wd_timer
- [], % ext_pending
- PrevState#state.helper_timeout},
- {ok, State};
-
- %% Upgrade to this version
- _Vsn ->
-
- %% Old state record
- {state,
- Timeout, MemUsage, WorstMemUser,
- SysMemWatermark, ProcMemWatermark, CollProc,
- WDTimer, Pending, ExtWDTimer, ExtPending,
- HelperTimeout} = PrevState,
- SysOnly = not CollProc,
-
- %% Flush memsup_helper messages
- flush(collected_sys),
- flush(collected_proc),
- flush(collected_ext_sys),
-
- %% Cancel timers, flush timeout messages
- %% and send dummy replies to any pending clients
- case WDTimer of
- undefined ->
- ignore;
- TimerRef1 ->
- ok = erlang:cancel_timer(TimerRef1, [{async,true}]),
- MemUsage = dummy_reply(get_memory_data, SysOnly),
- Pending2 = lists:map(fun(From) -> {reg,From} end,
- Pending),
- reply(Pending2, MemUsage, undef)
- end,
- case ExtWDTimer of
- undefined ->
- ignore;
- TimerRef2 ->
- ok = erlang:cancel_timer(TimerRef2, [{async,true}]),
- SysMemUsage = dummy_reply(get_system_memory_data),
- ExtPending2 = lists:map(fun(From) -> {ext,From} end,
- ExtPending),
- reply(ExtPending2, undef, SysMemUsage)
- end,
- flush(reg_collection_timeout),
- flush(ext_collection_timeout),
-
- OS = os:type(),
- PortMode = case OS of
- {unix, darwin} -> false;
- {unix, freebsd} -> false;
- {unix, dragonfly} -> false;
- {unix, linux} -> false;
- {unix, openbsd} -> true;
- {unix, netbsd} -> true;
- {unix, sunos} -> true;
- {win32, _OSname} -> false
- end,
- Pid = if
- PortMode -> spawn_link(fun() -> port_init() end);
- not PortMode -> undefined
- end,
-
- %% Upgrade to this state record
- State = #state{os = OS,
- port_mode = PortMode,
- mem_usage = MemUsage,
- worst_mem_user = WorstMemUser,
- sys_only = SysOnly,
- timeout = Timeout,
- helper_timeout = HelperTimeout,
- sys_mem_watermark = SysMemWatermark,
- proc_mem_watermark = ProcMemWatermark,
- pid = Pid,
- wd_timer = undefined,
- ext_wd_timer = undefined,
- pending = [],
- ext_pending = []},
- {ok, State}
- end;
-code_change(_Vsn, State, "2.0") ->
-
- %% Restart the port process (it must use new memsup code)
- Pid = case State#state.port_mode of
- true ->
- State#state.pid ! close,
- spawn_link(fun() -> port_init() end);
- false ->
- State#state.pid
- end,
- {ok, State#state{pid=Pid}};
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
%%----------------------------------------------------------------------
%% Other exports
%%----------------------------------------------------------------------
@@ -703,6 +581,7 @@ get_os_wordsize_with_uname() ->
"ppc64" -> 64;
"ppc64le" -> 64;
"s390x" -> 64;
+ "aarch64" -> 64;
_ -> 32
end.
@@ -725,22 +604,6 @@ reply(Pending, MemUsage, SysMemUsage) ->
%% get_memory_usage(OS) -> {Alloc, Total}
-%% Darwin:
-%% Uses vm_stat command.
-get_memory_usage({unix,darwin}) ->
- Str1 = os:cmd("/usr/bin/vm_stat"),
- PageSize = 4096,
-
- {[Free], Str2} = fread_value("Pages free:~d.", Str1),
- {[Active], Str3} = fread_value("Pages active:~d.", Str2),
- {[Inactive], Str4} = fread_value("Pages inactive:~d.", Str3),
- {[Speculative], Str5} = fread_value("Pages speculative:~d.", Str4),
- {[Wired], _} = fread_value("Pages wired down:~d.", Str5),
-
- NMemUsed = (Wired + Active + Inactive) * PageSize,
- NMemTotal = NMemUsed + (Free + Speculative) * PageSize,
- {NMemUsed,NMemTotal};
-
%% FreeBSD: Look in /usr/include/sys/vmmeter.h for the format of struct
%% vmmeter
get_memory_usage({unix,OSname}) when OSname == freebsd; OSname == dragonfly ->
@@ -760,16 +623,6 @@ get_memory_usage({win32,_OSname}) ->
io_lib:fread("~d~d~d~d~d~d~d", Result),
{TotPhys-AvailPhys, TotPhys}.
-fread_value(Format, Str0) ->
- case io_lib:fread(Format, skip_to_eol(Str0)) of
- {error, {fread, input}} -> {[0], Str0};
- {ok, Value, Str1} -> {Value, Str1}
- end.
-
-skip_to_eol([]) -> [];
-skip_to_eol([$\n | T]) -> T;
-skip_to_eol([_ | T]) -> skip_to_eol(T).
-
freebsd_sysctl(Def) ->
list_to_integer(os:cmd("/sbin/sysctl -n " ++ Def) -- "\n").
@@ -789,19 +642,16 @@ get_ext_memory_usage(OS, {Alloc, Total}) ->
{unix, dragonfly} ->
[{total_memory, Total}, {free_memory, Total-Alloc},
{system_total_memory, Total}];
- {unix, darwin} ->
- [{total_memory, Total}, {free_memory, Total-Alloc},
- {system_total_memory, Total}];
_ -> % OSs using a port
dummy % not sent anyway
end.
%%--Collect memory data, using port-------------------------------------
-port_init() ->
+port_init(ISMD) ->
process_flag(trap_exit, true),
Port = start_portprogram(),
- port_idle(Port).
+ port_idle(Port, ISMD).
start_portprogram() ->
os_mon:open_port("memsup",[{packet,1}]).
@@ -814,20 +664,20 @@ start_portprogram() ->
%% should still wait for port response (which should come
%% eventually!) but not receive any requests or cancellations
%% meanwhile to prevent getting out of synch.
-port_idle(Port) ->
+port_idle(Port, ISMD) ->
receive
{Memsup, collect_sys} ->
Port ! {self(), {command, [?SHOW_MEM]}},
- get_memory_usage(Port, undefined, Memsup);
+ get_memory_usage(Port, undefined, Memsup, ISMD);
{Memsup, collect_ext_sys} ->
Port ! {self(), {command, [?SHOW_SYSTEM_MEM]}},
- get_ext_memory_usage(Port, [], Memsup);
+ get_ext_memory_usage(Port, #{}, Memsup, ISMD);
cancel ->
%% Received after reply already has been delivered...
- port_idle(Port);
+ port_idle(Port, ISMD);
ext_cancel ->
%% Received after reply already has been delivered...
- port_idle(Port);
+ port_idle(Port, ISMD);
close ->
port_close(Port);
{Port, {data, Data}} ->
@@ -838,16 +688,16 @@ port_idle(Port) ->
port_close(Port)
end.
-get_memory_usage(Port, Alloc, Memsup) ->
+get_memory_usage(Port, Alloc, Memsup, ISMD) ->
receive
{Port, {data, Data}} when Alloc==undefined ->
- get_memory_usage(Port, erlang:list_to_integer(Data, 16), Memsup);
+ get_memory_usage(Port, erlang:list_to_integer(Data, 16), Memsup, ISMD);
{Port, {data, Data}} ->
Total = erlang:list_to_integer(Data, 16),
Memsup ! {collected_sys, {Alloc, Total}},
- port_idle(Port);
+ port_idle(Port, ISMD);
cancel ->
- get_memory_usage_cancelled(Port, Alloc);
+ get_memory_usage_cancelled(Port, Alloc, ISMD);
close ->
port_close(Port);
{'EXIT', Port, Reason} ->
@@ -855,12 +705,12 @@ get_memory_usage(Port, Alloc, Memsup) ->
{'EXIT', _Memsup, _Reason} ->
port_close(Port)
end.
-get_memory_usage_cancelled(Port, Alloc) ->
+get_memory_usage_cancelled(Port, Alloc, ISMD) ->
receive
{Port, {data, _Data}} when Alloc==undefined ->
- get_memory_usage_cancelled(Port, 0);
+ get_memory_usage_cancelled(Port, 0, ISMD);
{Port, {data, _Data}} ->
- port_idle(Port);
+ port_idle(Port, ISMD);
close ->
port_close(Port);
{'EXIT', Port, Reason} ->
@@ -869,32 +719,33 @@ get_memory_usage_cancelled(Port, Alloc) ->
port_close(Port)
end.
-get_ext_memory_usage(Port, Accum, Memsup) ->
- Tab = [
- {?MEM_SYSTEM_TOTAL, system_total_memory},
- {?MEM_TOTAL, total_memory},
- {?MEM_FREE, free_memory},
- {?MEM_BUFFERS, buffered_memory},
- {?MEM_CACHED, cached_memory},
- {?MEM_SHARED, shared_memory},
- {?MEM_LARGEST_FREE, largest_free},
- {?MEM_NUMBER_OF_FREE, number_of_free},
- {?SWAP_TOTAL, total_swap},
- {?SWAP_FREE, free_swap}
- ],
+tag2atag(Port, Tag) ->
+ Tab = persistent_term:get(?EXT_MEM_MAP),
+ try
+ maps:get(Tag, Tab)
+ catch
+ error:_ ->
+ exit({memsup_port_error, {Port,[Tag]}})
+ end.
+
+get_ext_memory_usage(Port, Accum, Memsup, ISMD) ->
receive
{Port, {data, [?SHOW_SYSTEM_MEM_END]}} ->
- Memsup ! {collected_ext_sys, Accum},
- port_idle(Port);
+ Memsup ! {collected_ext_sys, maps:to_list(Accum)},
+ port_idle(Port, ISMD);
+ {Port, {data, [?MEM_CACHED_X]}} when ISMD == false ->
+ %% Improved system memory data not enabled; drop value...
+ get_ext_memory_usage(tag2atag(Port, ?MEM_CACHED_X), Port,
+ Accum, Memsup, ISMD, true);
+ {Port, {data, [?MEM_AVAIL]}} when ISMD == false ->
+ %% Improved system memory data not enabled; drop value...
+ get_ext_memory_usage(tag2atag(Port, ?MEM_AVAIL), Port,
+ Accum, Memsup, ISMD, true);
{Port, {data, [Tag]}} ->
- case lists:keysearch(Tag, 1, Tab) of
- {value, {Tag, ATag}} ->
- get_ext_memory_usage(ATag, Port, Accum, Memsup);
- _ ->
- exit({memsup_port_error, {Port,[Tag]}})
- end;
+ get_ext_memory_usage(tag2atag(Port, Tag), Port, Accum,
+ Memsup, ISMD, false);
ext_cancel ->
- get_ext_memory_usage_cancelled(Port);
+ get_ext_memory_usage_cancelled(Port, ISMD);
close ->
port_close(Port);
{'EXIT', Port, Reason} ->
@@ -902,29 +753,13 @@ get_ext_memory_usage(Port, Accum, Memsup) ->
{'EXIT', _Memsup, _Reason} ->
port_close(Port)
end.
-get_ext_memory_usage_cancelled(Port) ->
- Tab = [
- {?MEM_SYSTEM_TOTAL, system_total_memory},
- {?MEM_TOTAL, total_memory},
- {?MEM_FREE, free_memory},
- {?MEM_BUFFERS, buffered_memory},
- {?MEM_CACHED, cached_memory},
- {?MEM_SHARED, shared_memory},
- {?MEM_LARGEST_FREE, largest_free},
- {?MEM_NUMBER_OF_FREE, number_of_free},
- {?SWAP_TOTAL, total_swap},
- {?SWAP_FREE, free_swap}
- ],
+get_ext_memory_usage_cancelled(Port, ISMD) ->
receive
{Port, {data, [?SHOW_SYSTEM_MEM_END]}} ->
- port_idle(Port);
+ port_idle(Port, ISMD);
{Port, {data, [Tag]}} ->
- case lists:keysearch(Tag, 1, Tab) of
- {value, {Tag, ATag}} ->
- get_ext_memory_usage_cancelled(ATag, Port);
- _ ->
- exit({memsup_port_error, {Port,[Tag]}})
- end;
+ get_ext_memory_usage_cancelled(tag2atag(Port, Tag),
+ Port, ISMD);
close ->
port_close(Port);
{'EXIT', Port, Reason} ->
@@ -933,13 +768,21 @@ get_ext_memory_usage_cancelled(Port) ->
port_close(Port)
end.
-get_ext_memory_usage(ATag, Port, Accum0, Memsup) ->
+get_ext_memory_usage(ATag, Port, Accum0, Memsup, ISMD, Drop) ->
receive
- {Port, {data, Data}} ->
- Accum = [{ATag,erlang:list_to_integer(Data, 16)}|Accum0],
- get_ext_memory_usage(Port, Accum, Memsup);
+ {Port, {data, _Data}} when Drop == true ->
+ get_ext_memory_usage(Port, Accum0, Memsup, ISMD);
+ {Port, {data, Data}} when Drop == false ->
+ Value = erlang:list_to_integer(Data, 16),
+ Accum = case maps:get(ATag, Accum0, undefined) of
+ undefined ->
+ maps:put(ATag, Value, Accum0);
+ PrevValue ->
+ maps:put(ATag, Value + PrevValue, Accum0)
+ end,
+ get_ext_memory_usage(Port, Accum, Memsup, ISMD);
cancel ->
- get_ext_memory_usage_cancelled(ATag, Port);
+ get_ext_memory_usage_cancelled(ATag, Port, ISMD);
close ->
port_close(Port);
{'EXIT', Port, Reason} ->
@@ -947,10 +790,10 @@ get_ext_memory_usage(ATag, Port, Accum0, Memsup) ->
{'EXIT', _Memsup, _Reason} ->
port_close(Port)
end.
-get_ext_memory_usage_cancelled(_ATag, Port) ->
+get_ext_memory_usage_cancelled(_ATag, Port, ISMD) ->
receive
{Port, {data, _Data}} ->
- get_ext_memory_usage_cancelled(Port);
+ get_ext_memory_usage_cancelled(Port, ISMD);
close ->
port_close(Port);
{'EXIT', Port, Reason} ->
diff --git a/lib/os_mon/src/nteventlog.erl b/lib/os_mon/src/nteventlog.erl
index 4b02d3ff7e..5e88a73510 100644
--- a/lib/os_mon/src/nteventlog.erl
+++ b/lib/os_mon/src/nteventlog.erl
@@ -25,7 +25,7 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+ terminate/2]).
-record(state, {port, mfa}).
@@ -92,31 +92,6 @@ terminate(_Reason, State) ->
end,
ok.
-%% os_mon-2.0
-%% For live downgrade to/upgrade from os_mon-1.8[.1]
-code_change(Vsn, PrevState, "1.8") ->
- case Vsn of
-
- %% Downgrade from this version
- {down, _Vsn} ->
- process_flag(trap_exit, false),
-
- %% Downgrade to old State tuple
- State = {PrevState#state.port, PrevState#state.mfa},
- {ok, State};
-
- %% Upgrade to this version
- _Vsn ->
- process_flag(trap_exit, true),
-
- %% Upgrade to this state record
- {Port, MFA} = PrevState,
- State = #state{port=Port, mfa=MFA},
- {ok, State}
- end;
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
%%----------------------------------------------------------------------
%% Internal functions
%%----------------------------------------------------------------------
diff --git a/lib/os_mon/src/os_mon.app.src b/lib/os_mon/src/os_mon.app.src
index 6c9b0d7576..894f63e227 100644
--- a/lib/os_mon/src/os_mon.app.src
+++ b/lib/os_mon/src/os_mon.app.src
@@ -21,7 +21,7 @@
{application, os_mon,
[{description, "CPO CXC 138 46"},
{vsn, "%VSN%"},
- {modules, [os_mon, os_sup,
+ {modules, [os_mon, os_mon_mib, os_sup,
disksup, memsup, cpu_sup, os_mon_sysinfo, nteventlog]},
{registered, [os_mon_sup, os_mon_sysinfo, disksup, memsup, cpu_sup,
os_sup_server]},
diff --git a/lib/os_mon/src/os_mon_mib.erl b/lib/os_mon/src/os_mon_mib.erl
new file mode 100644
index 0000000000..8a171b6071
--- /dev/null
+++ b/lib/os_mon/src/os_mon_mib.erl
@@ -0,0 +1,27 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% This module was removed entirely in OTP 22.0, it's only retained for its
+%% removal warning attribute.
+%%
+
+-module(os_mon_mib).
+-removed([{'_','_', "this module was removed in OTP 22.0"}]).
diff --git a/lib/os_mon/src/os_mon_sysinfo.erl b/lib/os_mon/src/os_mon_sysinfo.erl
index e87e597420..0694dab7e1 100644
--- a/lib/os_mon/src/os_mon_sysinfo.erl
+++ b/lib/os_mon/src/os_mon_sysinfo.erl
@@ -26,7 +26,7 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+ terminate/2]).
-define(DISK_INFO, $d).
-define(MEM_INFO, $m).
@@ -87,23 +87,6 @@ terminate(_Reason, State) ->
end,
ok.
-%% os_mon-2.0
-%% For live downgrade to/upgrade from os_mon-1.8[.1]
-code_change(Vsn, PrevState, "1.8") ->
- case Vsn of
-
- %% Downgrade from this version
- {down, _Vsn} ->
- process_flag(trap_exit, false);
-
- %% Upgrade to this version
- _Vsn ->
- process_flag(trap_exit, true)
- end,
- {ok, PrevState};
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
%%----------------------------------------------------------------------
%% Internal functions
%%----------------------------------------------------------------------
diff --git a/lib/os_mon/src/os_sup.erl b/lib/os_mon/src/os_sup.erl
index 71c9137588..77cd407a82 100644
--- a/lib/os_mon/src/os_sup.erl
+++ b/lib/os_mon/src/os_sup.erl
@@ -28,7 +28,7 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+ terminate/2]).
-record(state, {port, mfa, config, path, conf}).
@@ -159,69 +159,6 @@ terminate(_Reason, #state{port=Port} = State) ->
ok
end.
-%% os_mon-2.0
-%% For live downgrade to/upgrade from os_mon-1.8[.1]
-code_change(Vsn, PrevState, "1.8") ->
- case Vsn of
-
- %% Downgrade from this version
- {down, _Vsn} ->
-
- %% Find out the error tag used
- {DefM, DefF, _} = param_default(os_sup_mfa),
- Tag = case PrevState#state.mfa of
-
- %% Default callback function is used, then use
- %% the corresponding tag
- {DefM, DefF, [Tag0]} ->
- Tag0;
-
- %% Default callback function is *not* used
- %% (before the downgrade, that is)
- %% -- check the configuration parameter
- _ ->
- case application:get_env(os_mon,
- os_sup_errortag) of
- {ok, Tag1} ->
- Tag1;
-
- %% (actually, if it has no value,
- %% the process should terminate
- %% according to 1.8.1 version, but that
- %% seems too harsh here)
- _ ->
- std_error
- end
- end,
-
- %% Downgrade to old state record
- State = {state, PrevState#state.port, Tag},
- {ok, State};
-
- %% Upgrade to this version
- _Vsn ->
-
- {state, Port, Tag} = PrevState,
-
- {DefM, DefF, _} = param_default(os_sup_mfa),
- MFA = {DefM, DefF, [Tag]},
-
- %% We can safely assume the following configuration
- %% parameters are defined, otherwise os_sup would never had
- %% started in the first place.
- %% (We can *not* safely assume they haven't been changed,
- %% but that's a weakness inherited from the 1.8.1 version)
- Path = application:get_env(os_mon, os_sup_own),
- Conf = application:get_env(os_mon, os_sup_syslogconf),
-
- %% Upgrade to this state record
- State = #state{port=Port, mfa=MFA, config=true,
- path=Path, conf=Conf},
- {ok, State}
- end;
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
%%----------------------------------------------------------------------
%% Internal functions
%%----------------------------------------------------------------------
diff --git a/lib/os_mon/test/cpu_sup_SUITE.erl b/lib/os_mon/test/cpu_sup_SUITE.erl
index ba28f31f26..7a8065c591 100644
--- a/lib/os_mon/test/cpu_sup_SUITE.erl
+++ b/lib/os_mon/test/cpu_sup_SUITE.erl
@@ -155,46 +155,72 @@ tiny_diff(A, B) ->
-define(SPIN_TIME, 1000).
+spinner(Parent) ->
+ receive
+ stop -> Parent ! stopped
+ after 0 -> spinner(Parent)
+ end.
+
%% Test utilization values
util_values(Config) when is_list(Config) ->
-
+ NrOfProcessors =
+ case erlang:system_info(logical_processors_available) of
+ unknown -> 2;
+ X -> X
+ end,
Tester = self(),
Ref = make_ref(),
- Loop = fun (L) -> L(L) end,
Spinner = fun () ->
- Looper = spawn_link(fun () -> Loop(Loop) end),
+ Spinner = self(),
+ NrOfProcesses = NrOfProcessors,
+ Loopers =
+ [spawn_link(fun () -> spinner(Spinner) end)
+ || _ <- lists:seq(1,NrOfProcesses)],
receive after ?SPIN_TIME -> ok end,
- unlink(Looper),
- exit(Looper, kill),
+ [begin
+ Looper ! stop,
+ receive stopped -> ok end
+ end
+ || Looper <- Loopers],
Tester ! Ref
end,
-
+ Spin = fun () ->
+ spawn_link(Spinner),
+ receive Ref -> ok end
+ end,
cpu_sup:util(),
-
- spawn_link(Spinner),
- receive Ref -> ok end,
- HighUtil1 = cpu_sup:util(),
-
receive after ?SPIN_TIME -> ok end,
- LowUtil1 = cpu_sup:util(),
+ LowUtil0 = cpu_sup:util(),
+ case LowUtil0 of
+ U when U > ((100.0 / NrOfProcessors) * 0.33) ->
+ %% We cannot run this test if the system is doing other
+ %% work at the same time as the result will be unreliable
+ {skip, io_lib:format("CPU utilization was too high (~f%)", [LowUtil0])};
+ _ ->
+ cpu_sup:util(),
+ Spin(),
+ HighUtil1 = cpu_sup:util(),
- spawn_link(Spinner),
- receive Ref -> ok end,
- HighUtil2 = cpu_sup:util(),
+ receive after ?SPIN_TIME -> ok end,
+ LowUtil1 = cpu_sup:util(),
- receive after ?SPIN_TIME -> ok end,
- LowUtil2 = cpu_sup:util(),
+ Spin(),
+ HighUtil2 = cpu_sup:util(),
- Utils = [{high1,HighUtil1}, {low1,LowUtil1},
- {high2,HighUtil2}, {low2,LowUtil2}],
- io:format("Utils: ~p~n", [Utils]),
+ receive after ?SPIN_TIME -> ok end,
+ LowUtil2 = cpu_sup:util(),
- false = LowUtil1 > HighUtil1,
- false = LowUtil1 > HighUtil2,
- false = LowUtil2 > HighUtil1,
- false = LowUtil2 > HighUtil2,
+ Utils = [{high1,HighUtil1}, {low1,LowUtil1},
+ {high2,HighUtil2}, {low2,LowUtil2}],
+ io:format("Utils: ~p~n", [Utils]),
- ok.
+ false = LowUtil1 > HighUtil1,
+ false = LowUtil1 > HighUtil2,
+ false = LowUtil2 > HighUtil1,
+ false = LowUtil2 > HighUtil2,
+
+ ok
+ end.
% Outdated
diff --git a/lib/os_mon/test/memsup_SUITE.erl b/lib/os_mon/test/memsup_SUITE.erl
index e40ed574e7..97f97e21cc 100644
--- a/lib/os_mon/test/memsup_SUITE.erl
+++ b/lib/os_mon/test/memsup_SUITE.erl
@@ -28,7 +28,24 @@
%% Test cases
-export([api/1, alarm1/1, alarm2/1, process/1]).
-export([config/1, timeout/1, unavailable/1, port/1]).
--export([otp_5910/1]).
+-export([otp_5910/1, improved_system_memory_data/1]).
+
+
+-define(SYSTEM_MEMORY_DATA_TAGS,
+ [total_memory,
+ free_memory,
+ system_total_memory,
+ largest_free,
+ number_of_free,
+ free_swap,
+ total_swap,
+ cached_memory,
+ buffered_memory,
+ shared_memory]).
+
+-define(IMPROVED_SYSTEM_MEMORY_DATA_TAGS,
+ [available_memory | ?SYSTEM_MEMORY_DATA_TAGS]).
+
init_per_suite(Config) when is_list(Config) ->
ok = application:start(os_mon),
@@ -58,7 +75,7 @@ all() ->
_OS -> [api, alarm1, alarm2, process]
end,
Bugs = [otp_5910],
- All ++ Bugs.
+ All ++ Bugs ++ [improved_system_memory_data].
%% Test of API functions
@@ -80,16 +97,7 @@ api(Config) when is_list(Config) ->
%% get_system_memory_data()
ExtMemData = memsup:get_system_memory_data(),
- Tags = [total_memory,
- free_memory,
- system_total_memory,
- largest_free,
- number_of_free,
- free_swap,
- total_swap,
- cached_memory,
- buffered_memory,
- shared_memory],
+ Tags = ?SYSTEM_MEMORY_DATA_TAGS,
true = lists:all(fun({Tag,Value}) when is_atom(Tag),
is_integer(Value) ->
@@ -717,6 +725,47 @@ otp_5910(Config) when is_list(Config) ->
ok = application:start(os_mon),
ok.
+improved_system_memory_data(Config) ->
+ {ok, Node} = start_node(Config, "-os_mon memsup_improved_system_memory_data true"),
+ ok = rpc:call(Node, application, start, [sasl]),
+ ok = rpc:call(Node, application, start, [os_mon]),
+
+ ExtMemData = rpc:call(Node, memsup, get_system_memory_data, []),
+
+ stop_node(Node),
+
+ Tags = ?IMPROVED_SYSTEM_MEMORY_DATA_TAGS,
+ AvailableMemoryPresent
+ = lists:foldl(fun ({Tag,Value}, AMP) when is_atom(Tag),
+ is_integer(Value),
+ Value >= 0 ->
+ true = lists:member(Tag, Tags),
+ case Tag of
+ available_memory ->
+ false = AMP,
+ true;
+ _ ->
+ AMP
+ end
+ end,
+ false,
+ ExtMemData),
+
+ case os:type() of
+ {unix,linux} ->
+ {ok, Data} = file:read_file("/proc/meminfo"),
+ case re:run(Data, <<"MemAvailable">>) of
+ {match, _} ->
+ true = AvailableMemoryPresent,
+ {comment, "available_memory present in result"};
+ _ ->
+ {comment, "No available_memory present in result"}
+ end;
+ _ ->
+ ok
+ end.
+
+
%%----------------------------------------------------------------------
%% Auxiliary
%%----------------------------------------------------------------------
@@ -756,3 +805,20 @@ flush() ->
after 0 ->
ok
end.
+
+start_node(Config) ->
+ start_node(Config, "").
+
+start_node(Config, Args) when is_list(Config) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ Name = list_to_atom(atom_to_list(?MODULE)
+ ++ "-"
+ ++ atom_to_list(proplists:get_value(testcase, Config))
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(second))
+ ++ "-"
+ ++ integer_to_list(erlang:unique_integer([positive]))),
+ test_server:start_node(Name, slave, [{args, "-pa "++Pa++" "++Args}]).
+
+stop_node(Node) ->
+ test_server:stop_node(Node).
diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk
index 6081e181ff..b71478e75b 100644
--- a/lib/os_mon/vsn.mk
+++ b/lib/os_mon/vsn.mk
@@ -1 +1 @@
-OS_MON_VSN = 2.5.1
+OS_MON_VSN = 2.6.1
diff --git a/lib/parsetools/Makefile b/lib/parsetools/Makefile
index e9de5c43cb..2ddb06feb1 100644
--- a/lib/parsetools/Makefile
+++ b/lib/parsetools/Makefile
@@ -37,3 +37,4 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/parsetools/doc/src/Makefile b/lib/parsetools/doc/src/Makefile
index 2e8b232902..a4d7d4381b 100644
--- a/lib/parsetools/doc/src/Makefile
+++ b/lib/parsetools/doc/src/Makefile
@@ -29,11 +29,6 @@ VSN=$(PARSETOOLS_VSN)
APPLICATION=parsetools
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -48,72 +43,10 @@ XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES)
-GIF_FILES =
+IMAGE_FILES =
XML_HTML_FILES = \
notes_history.xml
# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_HTML_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/parsetools/doc/src/leex.xml b/lib/parsetools/doc/src/leex.xml
index 3b82f60201..9bfc5cf0af 100644
--- a/lib/parsetools/doc/src/leex.xml
+++ b/lib/parsetools/doc/src/leex.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2009</year><year>2017</year>
+ <year>2009</year><year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -139,15 +139,16 @@ Token = tuple()</code>
</funcs>
- <section>
- <title>GENERATED SCANNER EXPORTS</title>
- <p>The following functions are exported by the generated scanner.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>GENERATED SCANNER EXPORTS</title>
+ <p>The following functions are exported by the generated scanner.</p>
+ </fsdescription>
<func>
- <name since="">string(String) -> StringRet</name>
- <name since="">string(String, StartLine) -> StringRet</name>
+ <name since="">Module:string(String) -> StringRet</name>
+ <name since="">Module:string(String, StartLine) -> StringRet</name>
<fsummary>Generated by Leex</fsummary>
<type>
<v>String = string()</v>
@@ -164,9 +165,9 @@ Token = tuple()</code>
</func>
<func>
- <name since="">token(Cont, Chars) -> {more,Cont1} | {done,TokenRet,RestChars}
+ <name since="">Module:token(Cont, Chars) -> {more,Cont1} | {done,TokenRet,RestChars}
</name>
- <name since="">token(Cont, Chars, StartLine) -> {more,Cont1}
+ <name since="">Module:token(Cont, Chars, StartLine) -> {more,Cont1}
| {done,TokenRet,RestChars}
</name>
<fsummary>Generated by Leex</fsummary>
@@ -193,15 +194,15 @@ Token = tuple()</code>
but used through the i/o system where it can typically be
called in an application by:</p>
<code>
-io:request(InFile, {get_until,Prompt,Module,token,[Line]})
+io:request(InFile, {get_until,unicode,Prompt,Module,token,[Line]})
-> TokenRet</code>
</desc>
</func>
<func>
- <name since="">tokens(Cont, Chars) -> {more,Cont1} | {done,TokensRet,RestChars}
+ <name since="">Module:tokens(Cont, Chars) -> {more,Cont1} | {done,TokensRet,RestChars}
</name>
- <name since="">tokens(Cont, Chars, StartLine) ->
+ <name since="">Module:tokens(Cont, Chars, StartLine) ->
{more,Cont1} | {done,TokensRet,RestChars}
</name>
<fsummary>Generated by Leex</fsummary>
@@ -240,7 +241,7 @@ io:request(InFile, {get_until,Prompt,Module,token,[Line]})
but used through the i/o system where it can typically be
called in an application by:</p>
<code>
-io:request(InFile, {get_until,Prompt,Module,tokens,[Line]})
+io:request(InFile, {get_until,unicode,Prompt,Module,tokens,[Line]})
-> TokensRet</code>
</desc>
</func>
diff --git a/lib/parsetools/doc/src/notes.xml b/lib/parsetools/doc/src/notes.xml
index f8cd9b972d..3975c55c6c 100644
--- a/lib/parsetools/doc/src/notes.xml
+++ b/lib/parsetools/doc/src/notes.xml
@@ -31,6 +31,22 @@
</header>
<p>This document describes the changes made to the Parsetools application.</p>
+<section><title>Parsetools 2.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Parsetools 2.1.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/parsetools/doc/src/ref_man.xml b/lib/parsetools/doc/src/ref_man.xml
index a06221250f..a2c82eb711 100644
--- a/lib/parsetools/doc/src/ref_man.xml
+++ b/lib/parsetools/doc/src/ref_man.xml
@@ -31,9 +31,9 @@
</header>
<description>
<p>The <em>Parsetools</em> application contains utilities for
- parsing and scanning. Yecc is an <term id="LALR-1"></term>parser
- generator for Erlang, similar to yacc. Yecc takes a <term
- id="BNF"></term>grammar definition as input, and produces Erlang
+ parsing and scanning. Yecc is an LALR-1 parser
+ generator for Erlang, similar to yacc. Yecc takes a BNF grammar
+ definition as input, and produces Erlang
code for a parser as output. Leex is a regular expression based
lexical analyzer generator for Erlang, similar to lex or flex.</p>
</description>
diff --git a/lib/parsetools/src/yecc.erl b/lib/parsetools/src/yecc.erl
index 3343a4282b..f44b1da861 100644
--- a/lib/parsetools/src/yecc.erl
+++ b/lib/parsetools/src/yecc.erl
@@ -1345,7 +1345,7 @@ make_rule_pointer_info(StC, RpRhs, RuleIndex) ->
rp_info([], _SymbolTab, _LcTab, _RuleIndex) ->
[];
rp_info([Category | Followers], SymbolTab, LcTab, RuleIndex) ->
- case dict:find(Category, RuleIndex) of
+ case maps:find(Category, RuleIndex) of
error -> % terminal
[];
{ok, ExpandingRules} when Followers =:= [] ->
@@ -1364,7 +1364,7 @@ rp_info([Category | Followers], SymbolTab, LcTab, RuleIndex) ->
make_lookahead([], _, _, LA) ->
{empty, LA};
make_lookahead([Symbol | Symbols], SymbolTab, LcTab, LA) ->
- case dict:find(Symbol, LcTab) of
+ case maps:find(Symbol, LcTab) of
{ok, LeftCorner} -> % nonterminal
case empty_member(LeftCorner) of
true ->
@@ -1377,7 +1377,7 @@ make_lookahead([Symbol | Symbols], SymbolTab, LcTab, LA) ->
set_add(Symbol, LA)
end.
-%% -> dict-of({Nonterminal, [Terminal]}).
+%% -> map-of({Nonterminal, [Terminal]}).
%% The algorithm FIRST/1 from the Dragon Book.
%% Left corner table, all terminals (including '$empty') that can
%% begin strings generated by Nonterminal.
@@ -1386,15 +1386,15 @@ make_left_corner_table(#yecc{rules_list = RulesList} = St) ->
Rules = map(fun(#rule{symbols = [Lhs | Rhs]}) ->
{Lhs,{Lhs, Rhs}}
end, RulesList),
- LeftHandTab = dict:from_list(family(Rules)),
+ LeftHandTab = maps:from_list(family(Rules)),
X0 = [{S,H} || {H,{H,Rhs}} <- Rules,
S <- Rhs,
not is_terminal(SymbolTab, S)],
XL = family_with_domain(X0, St#yecc.nonterminals),
- X = dict:from_list(XL),
- Xref = fun(NT) -> dict:fetch(NT, X) end,
+ X = maps:from_list(XL),
+ Xref = fun(NT) -> maps:get(NT, X) end,
E = set_empty(),
- LC0 = dict:from_list([{H, E} || {H,_} <- XL]),
+ LC0 = maps:from_list([{H, E} || {H,_} <- XL]),
%% Handle H -> a S, where a is a terminal ('$empty' inclusive).
{Q, LC1} =
foldl(fun({H,{H,[S | _]}}, {Q0, LC}) ->
@@ -1413,7 +1413,7 @@ left_corners(Q0, LC0, LeftHandTab, SymbolTab, Xref) ->
[] ->
LC0;
Q1 ->
- Rs = flatmap(fun(NT) -> dict:fetch(NT, LeftHandTab) end, Q1),
+ Rs = flatmap(fun(NT) -> maps:get(NT, LeftHandTab) end, Q1),
{LC, Q} = left_corners2(Rs, LC0, [], SymbolTab, Xref),
left_corners(Q, LC, LeftHandTab, SymbolTab, Xref)
end.
@@ -1422,7 +1422,7 @@ left_corners2([], LC, Q, _SymbolTab, _Xref) ->
{LC, Q};
left_corners2([{Head,Rhs} | Rs], LC, Q0, SymbolTab, Xref) ->
Ts = left_corner_rhs(Rhs, Head, LC, set_empty(), SymbolTab),
- First0 = dict:fetch(Head, LC),
+ First0 = maps:get(Head, LC),
case set_is_subset(Ts, First0) of
true ->
left_corners2(Rs, LC, Q0, SymbolTab, Xref);
@@ -1432,14 +1432,14 @@ left_corners2([{Head,Rhs} | Rs], LC, Q0, SymbolTab, Xref) ->
end.
upd_first(NT, Ts, LC) ->
- dict:update(NT, fun(First) -> set_union(First, Ts) end, LC).
+ maps:update_with(NT, fun(First) -> set_union(First, Ts) end, LC).
left_corner_rhs([S | Ss], Head, LC, Ts, SymbolTab) ->
case ets:lookup(SymbolTab, S) of
[{_,Num}=SymbolAndNum] when Num >= 0 ->
set_add_terminal(SymbolAndNum, Ts);
[_NonTerminalSymbol] ->
- First = dict:fetch(S, LC),
+ First = maps:get(S, LC),
case empty_member(First) of
true ->
NTs = set_union(empty_delete(First), Ts),
@@ -1466,7 +1466,7 @@ make_rule_index(#yecc{nonterminals = Nonterminals,
Symbol2Rule = [{Foo,R} || #rule{symbols = Symbols}=R <- RulesListNoCodes,
Foo <- Symbols],
Pointer2Rule = [{I, R} || {{_Foo,R},I} <- count(1, Symbol2Rule)],
- {dict:from_list(IndexedTab), dict:from_list(Pointer2Rule)}.
+ {maps:from_list(IndexedTab), maps:from_list(Pointer2Rule)}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Computing parse action table from list of states and goto table:
@@ -2309,14 +2309,14 @@ function_name(St, Name, Suf) ->
rule(RulePointer, St) ->
#rule{n = N, anno = Anno, symbols = Symbols} =
- dict:fetch(RulePointer, St#yecc.rule_pointer2rule),
+ maps:get(RulePointer, St#yecc.rule_pointer2rule),
{Symbols, Anno, N}.
get_rule(RuleNmbr, St) ->
- dict:fetch(RuleNmbr, St#yecc.rule_pointer2rule).
+ maps:get(RuleNmbr, St#yecc.rule_pointer2rule).
tokens(RuleNmbr, St) ->
- Rule = dict:fetch(RuleNmbr, St#yecc.rule_pointer2rule),
+ Rule = maps:get(RuleNmbr, St#yecc.rule_pointer2rule),
Rule#rule.tokens.
goto(From, Symbol, St) ->
diff --git a/lib/parsetools/vsn.mk b/lib/parsetools/vsn.mk
index 1a5201ce5d..c18fcbe762 100644
--- a/lib/parsetools/vsn.mk
+++ b/lib/parsetools/vsn.mk
@@ -1 +1 @@
-PARSETOOLS_VSN = 2.1.8
+PARSETOOLS_VSN = 2.2
diff --git a/lib/public_key/Makefile b/lib/public_key/Makefile
index 7a5c1c1443..ed8901a8af 100644
--- a/lib/public_key/Makefile
+++ b/lib/public_key/Makefile
@@ -38,3 +38,6 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=asn crypto
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/public_key/asn1/OCSP-2013-88.asn1 b/lib/public_key/asn1/OCSP-2013-88.asn1
new file mode 100644
index 0000000000..32b1eed962
--- /dev/null
+++ b/lib/public_key/asn1/OCSP-2013-88.asn1
@@ -0,0 +1,149 @@
+-- OCSP definition from RFC6960, 1998 Syntax
+
+OCSP-2013-88 {
+ iso(1) identified-organization(3) dod(6) internet(1)
+ security(5) mechanisms(5) pkix(7) id-mod(0)
+ id-mod-ocsp-2013-88(81)
+}
+
+DEFINITIONS EXPLICIT TAGS ::=
+
+BEGIN
+
+IMPORTS
+
+ -- PKIX Certificate Extensions
+ AuthorityInfoAccessSyntax, CRLReason, GeneralName
+ FROM PKIX1Implicit88 { iso(1) identified-organization(3)
+ dod(6) internet(1) security(5) mechanisms(5) pkix(7)
+ id-mod(0) id-pkix1-implicit(19) }
+
+ Name, CertificateSerialNumber, Extensions,
+ id-kp, id-ad-ocsp, Certificate, AlgorithmIdentifier
+ FROM PKIX1Explicit88 { iso(1) identified-organization(3)
+ dod(6) internet(1) security(5) mechanisms(5) pkix(7)
+ id-mod(0) id-pkix1-explicit(18) };
+
+OCSPRequest ::= SEQUENCE {
+ tbsRequest TBSRequest,
+ optionalSignature [0] EXPLICIT Signature OPTIONAL }
+
+TBSRequest ::= SEQUENCE {
+ version [0] EXPLICIT Version DEFAULT v1,
+ requestorName [1] EXPLICIT GeneralName OPTIONAL,
+ requestList SEQUENCE OF Request,
+ requestExtensions [2] EXPLICIT Extensions OPTIONAL }
+
+Signature ::= SEQUENCE {
+ signatureAlgorithm AlgorithmIdentifier,
+ signature BIT STRING,
+ certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+
+Version ::= INTEGER { v1(0) }
+
+Request ::= SEQUENCE {
+ reqCert CertID,
+ singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL }
+
+CertID ::= SEQUENCE {
+ hashAlgorithm AlgorithmIdentifier,
+ issuerNameHash OCTET STRING, -- Hash of issuer's DN
+ issuerKeyHash OCTET STRING, -- Hash of issuer's public key
+ serialNumber CertificateSerialNumber }
+
+OCSPResponse ::= SEQUENCE {
+ responseStatus OCSPResponseStatus,
+ responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
+
+OCSPResponseStatus ::= ENUMERATED {
+ successful (0), -- Response has valid confirmations
+ malformedRequest (1), -- Illegal confirmation request
+ internalError (2), -- Internal error in issuer
+ tryLater (3), -- Try again later
+ -- (4) is not used
+ sigRequired (5), -- Must sign the request
+ unauthorized (6) -- Request unauthorized
+}
+
+ResponseBytes ::= SEQUENCE {
+ responseType OBJECT IDENTIFIER,
+ response OCTET STRING }
+
+BasicOCSPResponse ::= SEQUENCE {
+ tbsResponseData ResponseData,
+ signatureAlgorithm AlgorithmIdentifier,
+ signature BIT STRING,
+ certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+
+ResponseData ::= SEQUENCE {
+ version [0] EXPLICIT Version DEFAULT v1,
+ responderID ResponderID,
+ producedAt GeneralizedTime,
+ responses SEQUENCE OF SingleResponse,
+ responseExtensions [1] EXPLICIT Extensions OPTIONAL }
+
+ResponderID ::= CHOICE {
+ byName [1] Name,
+ byKey [2] KeyHash }
+
+KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
+ -- (i.e., the SHA-1 hash of the value of the
+ -- BIT STRING subjectPublicKey [excluding
+ -- the tag, length, and number of unused
+ -- bits] in the responder's certificate)
+
+SingleResponse ::= SEQUENCE {
+ certID CertID,
+ certStatus CertStatus,
+ thisUpdate GeneralizedTime,
+ nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
+ singleExtensions [1] EXPLICIT Extensions OPTIONAL }
+
+CertStatus ::= CHOICE {
+ good [0] IMPLICIT NULL,
+ revoked [1] IMPLICIT RevokedInfo,
+ unknown [2] IMPLICIT UnknownInfo }
+
+RevokedInfo ::= SEQUENCE {
+ revocationTime GeneralizedTime,
+ revocationReason [0] EXPLICIT CRLReason OPTIONAL }
+
+UnknownInfo ::= NULL
+
+ArchiveCutoff ::= GeneralizedTime
+
+AcceptableResponses ::= SEQUENCE OF OBJECT IDENTIFIER
+
+ServiceLocator ::= SEQUENCE {
+ issuer Name,
+ locator AuthorityInfoAccessSyntax }
+
+CrlID ::= SEQUENCE {
+ crlUrl [0] EXPLICIT IA5String OPTIONAL,
+ crlNum [1] EXPLICIT INTEGER OPTIONAL,
+ crlTime [2] EXPLICIT GeneralizedTime OPTIONAL }
+
+PreferredSignatureAlgorithms ::= SEQUENCE OF PreferredSignatureAlgorithm
+
+PreferredSignatureAlgorithm ::= SEQUENCE {
+ sigIdentifier AlgorithmIdentifier,
+ certIdentifier AlgorithmIdentifier OPTIONAL }
+
+Nonce ::= OCTET STRING
+
+-- Object Identifiers
+
+-- Already defined in PKIX1Implicit88
+--id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
+id-pkix-ocsp OBJECT IDENTIFIER ::= { id-ad-ocsp }
+id-pkix-ocsp-basic OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 }
+id-pkix-ocsp-nonce OBJECT IDENTIFIER ::= { id-pkix-ocsp 2 }
+id-pkix-ocsp-crl OBJECT IDENTIFIER ::= { id-pkix-ocsp 3 }
+id-pkix-ocsp-response OBJECT IDENTIFIER ::= { id-pkix-ocsp 4 }
+id-pkix-ocsp-nocheck OBJECT IDENTIFIER ::= { id-pkix-ocsp 5 }
+id-pkix-ocsp-archive-cutoff OBJECT IDENTIFIER ::= { id-pkix-ocsp 6 }
+id-pkix-ocsp-service-locator OBJECT IDENTIFIER ::= { id-pkix-ocsp 7 }
+id-pkix-ocsp-pref-sig-algs OBJECT IDENTIFIER ::= { id-pkix-ocsp 8 }
+id-pkix-ocsp-extended-revoke OBJECT IDENTIFIER ::= { id-pkix-ocsp 9 }
+
+END
diff --git a/lib/public_key/asn1/OTP-PKIX.asn1 b/lib/public_key/asn1/OTP-PKIX.asn1
index ff3250b383..e1d8f2e121 100644
--- a/lib/public_key/asn1/OTP-PKIX.asn1
+++ b/lib/public_key/asn1/OTP-PKIX.asn1
@@ -122,7 +122,9 @@ IMPORTS
sha224WithRSAEncryption,
sha256WithRSAEncryption,
sha384WithRSAEncryption,
- sha512WithRSAEncryption
+ sha512WithRSAEncryption,
+ id-RSASSA-PSS,
+ RSASSA-PSS-params
FROM PKCS-1 {
iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1)
@@ -341,6 +343,7 @@ SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= {
sha256-with-rsa-encryption |
sha384-with-rsa-encryption |
sha512-with-rsa-encryption |
+ rsassa-pss |
ecdsa-with-sha1 |
ecdsa-with-sha224 |
ecdsa-with-sha256 |
@@ -348,7 +351,7 @@ SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= {
ecdsa-with-sha512 }
SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
- dsa | rsa-encryption | dh | kea | ec-public-key }
+ dsa | rsa-encryption | rsa-pss | dh | kea | ec-public-key }
-- DSA Keys and Signatures
@@ -430,6 +433,11 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
ID sha512WithRSAEncryption
TYPE NULL }
+ rsassa-pss SIGNATURE-ALGORITHM-CLASS ::= {
+ ID id-RSASSA-PSS
+ TYPE RSASSA-PSS-params }
+
+
-- Certificate.signature
-- See PKCS #1 (RFC 2313). XXX
@@ -440,6 +448,11 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
TYPE NULL
PUBLIC-KEY-TYPE RSAPublicKey }
+ rsa-pss PUBLIC-KEY-ALGORITHM-CLASS ::= {
+ ID id-RSASSA-PSS
+ TYPE RSASSA-PSS-params
+ PUBLIC-KEY-TYPE RSAPublicKey }
+
--
-- Diffie-Hellman Keys
--
@@ -494,6 +507,7 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
ID ecdsa-with-SHA512
TYPE EcpkParameters } -- XXX Must be empty and not NULL
+
FIELD-ID-CLASS ::= CLASS {
&id OBJECT IDENTIFIER UNIQUE,
&Type }
diff --git a/lib/public_key/asn1/OTP-PUB-KEY.set.asn b/lib/public_key/asn1/OTP-PUB-KEY.set.asn
index 7ab1684ff3..74964a1c8f 100644
--- a/lib/public_key/asn1/OTP-PUB-KEY.set.asn
+++ b/lib/public_key/asn1/OTP-PUB-KEY.set.asn
@@ -11,4 +11,5 @@ PKCS-7.asn1
PKCS-10.asn1
RFC5639.asn1
CMSAesRsaesOaep.asn1
+OCSP-2013-88.asn1
diff --git a/lib/public_key/asn1/PKCS-1.asn1 b/lib/public_key/asn1/PKCS-1.asn1
index 117eacd8ad..6fb7ccb981 100644
--- a/lib/public_key/asn1/PKCS-1.asn1
+++ b/lib/public_key/asn1/PKCS-1.asn1
@@ -1,10 +1,15 @@
+-- PKCS #1 v2.2 ASN.1 Module
+-- Revised October 27, 2012
+-- (plain merged with previous version to support all that we need)
+
+-- This module has been checked for conformance with the
+-- ASN.1 standard by the OSS ASN.1 Tools
+
PKCS-1 {
iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1)
modules(0) pkcs-1(1)
}
--- $Revision: 1.1 $
-
DEFINITIONS EXPLICIT TAGS ::=
BEGIN
@@ -15,43 +20,74 @@ BEGIN
-- gov(101) csor(3) nistalgorithm(4) modules(0) sha2(1)
-- };
+-- ============================
+-- Basic object identifiers
+-- ============================
+-- The DER encoding of this in hexadecimal is:
+-- (0x)06 08
+-- 2A 86 48 86 F7 0D 01 01
+
pkcs-1 OBJECT IDENTIFIER ::= {
iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1
}
+--
+-- When rsaEncryption is used in an AlgorithmIdentifier the parameters
+-- MUST be present and MUST be NULL.
+--
rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 }
+--
+-- When id-RSAES-OAEP is used in an AlgorithmIdentifier the parameters MUST
+-- be present and MUST be RSAES-OAEP-params.
+--
id-RSAES-OAEP OBJECT IDENTIFIER ::= { pkcs-1 7 }
-id-pSpecified OBJECT IDENTIFIER ::= { pkcs-1 9 }
+--
+-- When id-pSpecified is used in an AlgorithmIdentifier the parameters MUST
+-- be an OCTET STRING.
+--
+id-pSpecified OBJECT IDENTIFIER ::= { pkcs-1 9 }
+--
+-- When id-RSASSA-PSS is used in an AlgorithmIdentifier the parameters MUST
+-- be present and MUST be RSASSA-PSS-params.
+--
id-RSASSA-PSS OBJECT IDENTIFIER ::= { pkcs-1 10 }
-md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 }
-md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 }
-sha1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 }
-sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 }
-sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 }
-sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 }
-sha224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 14 }
+--
+-- When the following OIDs are used in an AlgorithmIdentifier the parameters
+-- MUST be present and MUST be NULL.
+--
+md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 }
+md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 }
+sha1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 }
+sha224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 14 }
+sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 }
+sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 }
+sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 }
+sha512-224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 15 }
+sha512-256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 16 }
-- ISO oid - equvivalent to sha1WithRSAEncryption
sha-1WithRSAEncryption OBJECT IDENTIFIER ::= {
iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) sha-1WithRSAEncryption(29)}
-
-id-sha1 OBJECT IDENTIFIER ::= {
- iso(1) identified-organization(3) oiw(14) secsig(3)
- algorithms(2) 26
+--
+-- This OID really belongs in a module with the secsig OIDs.
+--
+id-sha1 OBJECT IDENTIFIER ::= {
+ iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26
}
-
+--
+-- OIDs for MD2 and MD5, allowed only in EMSA-PKCS1-v1_5.
+--
id-md2 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 2
-}
+ iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 2}
id-md5 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 5
-}
+ iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 5}
+-- Additional oids that where included previously...
id-hmacWithSHA224 OBJECT IDENTIFIER ::= {
iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 8
}
@@ -68,8 +104,6 @@ id-hmacWithSHA512 OBJECT IDENTIFIER ::= {
iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 11
}
-id-mgf1 OBJECT IDENTIFIER ::= { pkcs-1 8 }
-
id-sha224 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2)
country(16) us(840) organization(1) gov(101) csor(3)
nistalgorithm(4) hashalgs(2) 4 }
@@ -86,68 +120,255 @@ id-sha512 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2)
country(16) us(840) organization(1) gov(101) csor(3)
nistalgorithm(4) hashalgs(2) 3 }
+--
+-- When id-mgf1 is used in an AlgorithmIdentifier the parameters MUST be
+-- present and MUST be a HashAlgorithm, for example sha1.
+--
+id-mgf1 OBJECT IDENTIFIER ::= { pkcs-1 8 }
+
+-- ================
+-- Useful types
+-- ================
+ALGORITHM-IDENTIFIER ::= CLASS {
+ &id OBJECT IDENTIFIER UNIQUE,
+ &Type OPTIONAL
+}
-RSAPublicKey ::= SEQUENCE {
- modulus INTEGER, -- n
- publicExponent INTEGER -- e
+ WITH SYNTAX { OID &id [PARAMETERS &Type] }
+-- Note: the parameter InfoObjectSet in the following definitions allows a
+-- distinct information object set to be specified for sets of algorithms
+-- such as:
+-- DigestAlgorithms ALGORITHM-IDENTIFIER ::= {
+-- { OID id-md2 PARAMETERS NULL }|
+-- { OID id-md5 PARAMETERS NULL }|
+-- { OID id-sha1 PARAMETERS NULL }
+-- }
+--
+AlgorithmIdentifier-PKCS1 { ALGORITHM-IDENTIFIER:InfoObjectSet } ::= SEQUENCE {
+ algorithm
+ ALGORITHM-IDENTIFIER.&id({InfoObjectSet}),
+ parameters
+ ALGORITHM-IDENTIFIER.&Type({InfoObjectSet}{@.algorithm}) OPTIONAL
+}
+
+-- ==============
+-- Algorithms
+-- ==============
+--
+-- Allowed EME-OAEP and EMSA-PSS digest algorithms.
+--
+OAEP-PSSDigestAlgorithms ALGORITHM-IDENTIFIER ::= {
+ { OID id-sha1 PARAMETERS NULL }|
+ { OID id-sha256 PARAMETERS NULL }|
+ { OID id-sha384 PARAMETERS NULL }|
+ { OID id-sha512 PARAMETERS NULL },
+ ... -- Allows for future expansion --
+}
+--
+-- Allowed EMSA-PKCS1-v1_5 digest algorithms.
+--
+PKCS1-v1-5DigestAlgorithms ALGORITHM-IDENTIFIER ::= {
+ { OID id-md2 PARAMETERS NULL }|
+ { OID id-md5 PARAMETERS NULL }|
+ { OID id-sha1 PARAMETERS NULL }|
+ { OID id-sha256 PARAMETERS NULL }|
+ { OID id-sha384 PARAMETERS NULL }|
+ { OID id-sha512 PARAMETERS NULL }
+}
+
+-- When id-md2 and id-md5 are used in an AlgorithmIdentifier, the
+-- parameters field shall have a value of type NULL.
+
+-- When id-sha1, id-sha224, id-sha256, id-sha384, id-sha512,
+-- id-sha512-224, and id-sha512-256 are used in an
+-- AlgorithmIdentifier, the parameters (which are optional) SHOULD be
+-- omitted, but if present, they SHALL have a value of type NULL.
+-- However, implementations MUST accept AlgorithmIdentifier values
+-- both without parameters and with NULL parameters.
+
+-- Exception: When formatting the DigestInfoValue in EMSA-PKCS1-v1_5
+-- (see Section 9.2), the parameters field associated with id-sha1,
+-- id-sha224, id-sha256, id-sha384, id-sha512, id-sha512-224, and
+-- id-sha512-256 SHALL have a value of type NULL. This is to
+-- maintain compatibility with existing implementations and with the
+-- numeric information values already published for EMSA-PKCS1-v1_5,
+-- which are also reflected in IEEE 1363a.
+
+sha1 HashAlgorithm ::= {
+ algorithm id-sha1,
+ parameters SHA1Parameters : NULL
+}
+
+HashAlgorithm ::= AlgorithmIdentifier-PKCS1 { {OAEP-PSSDigestAlgorithms} }
+SHA1Parameters ::= NULL
+
+--
+-- Allowed mask generation function algorithms.
+-- If the identifier is id-mgf1, the parameters are a HashAlgorithm.
+--
+PKCS1MGFAlgorithms ALGORITHM-IDENTIFIER ::= {
+ { OID id-mgf1 PARAMETERS HashAlgorithm },
+ ... -- Allows for future expansion --
+}
+
+--
+-- Default AlgorithmIdentifier for id-RSAES-OAEP.maskGenAlgorithm and
+-- id-RSASSA-PSS.maskGenAlgorithm.
+--
+mgf1SHA1 MaskGenAlgorithm ::= {
+ algorithm id-mgf1,
+ parameters HashAlgorithm : sha1
}
+MaskGenAlgorithm ::= AlgorithmIdentifier-PKCS1 { {PKCS1MGFAlgorithms} }
+
+--
+-- Allowed algorithms for pSourceAlgorithm.
+--
+PKCS1PSourceAlgorithms ALGORITHM-IDENTIFIER ::= {
+ { OID id-pSpecified PARAMETERS EncodingParameters },
+ ... -- Allows for future expansion --
+}
+
+EncodingParameters ::= OCTET STRING(SIZE(0..MAX))
+
+--
+-- This identifier means that the label L is an empty string, so the digest
+-- of the empty string appears in the RSA block before masking.
+--
+pSpecifiedEmpty PSourceAlgorithm ::= {
+ algorithm id-pSpecified,
+ parameters EncodingParameters : emptyString
+}
+PSourceAlgorithm ::= AlgorithmIdentifier-PKCS1 { {PKCS1PSourceAlgorithms} }
+
+emptyString EncodingParameters ::= ''H
+
+--
+-- Type identifier definitions for the PKCS #1 OIDs.
+--
+PKCS1Algorithms ALGORITHM-IDENTIFIER ::= {
+ { OID rsaEncryption PARAMETERS NULL } |
+ { OID md2WithRSAEncryption PARAMETERS NULL } |
+ { OID md5WithRSAEncryption PARAMETERS NULL } |
+ { OID sha1WithRSAEncryption PARAMETERS NULL } |
+ { OID sha256WithRSAEncryption PARAMETERS NULL } |
+ { OID sha384WithRSAEncryption PARAMETERS NULL } |
+ { OID sha512WithRSAEncryption PARAMETERS NULL } |
+ { OID id-RSAES-OAEP PARAMETERS RSAES-OAEP-params } |
+ PKCS1PSourceAlgorithms |
+ { OID id-RSASSA-PSS PARAMETERS RSASSA-PSS-params } ,
+ ... -- Allows for future expansion --
+}
+-- ===================
+-- Main structures
+-- ===================
+RSAPublicKey ::= SEQUENCE {
+ modulus INTEGER, -- n
+ publicExponent INTEGER -- e
+}
+--
+-- Representation of RSA private key with information for the CRT algorithm.
+--
RSAPrivateKey ::= SEQUENCE {
- version Version,
- modulus INTEGER, -- n
- publicExponent INTEGER, -- e
- privateExponent INTEGER, -- d
- prime1 INTEGER, -- p
- prime2 INTEGER, -- q
- exponent1 INTEGER, -- d mod (p-1)
- exponent2 INTEGER, -- d mod (q-1)
- coefficient INTEGER, -- (inverse of q) mod p
- otherPrimeInfos OtherPrimeInfos OPTIONAL
+ version Version,
+ modulus INTEGER, -- n
+ publicExponent INTEGER, -- e
+ privateExponent INTEGER, -- d
+ prime1 INTEGER, -- p
+ prime2 INTEGER, -- q
+ exponent1 INTEGER, -- d mod (p-1)
+ exponent2 INTEGER, -- d mod (q-1)
+ coefficient INTEGER, -- (inverse of q) mod p
+ otherPrimeInfos OtherPrimeInfos OPTIONAL
}
Version ::= INTEGER { two-prime(0), multi(1) }
- (CONSTRAINED BY {
- -- version must be multi if otherPrimeInfos present --
- })
+ (CONSTRAINED BY {-- version must be multi if otherPrimeInfos present --})
OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
OtherPrimeInfo ::= SEQUENCE {
- prime INTEGER, -- ri
- exponent INTEGER, -- di
- coefficient INTEGER -- ti
+ prime INTEGER, -- ri
+ exponent INTEGER, -- di
+ coefficient INTEGER -- ti
}
-Algorithm ::= SEQUENCE {
- algorithm OBJECT IDENTIFIER,
- parameters ANY DEFINED BY algorithm OPTIONAL
+--
+-- AlgorithmIdentifier.parameters for id-RSAES-OAEP.
+-- Note that the tags in this Sequence are explicit.
+--
+
+RSAES-OAEP-params ::= SEQUENCE {
+ hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
+ maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
+ pSourceAlgorithm [2] PSourceAlgorithm DEFAULT pSpecifiedEmpty
}
-AlgorithmNull ::= SEQUENCE {
- algorithm OBJECT IDENTIFIER,
- parameters NULL
+--
+-- Identifier for default RSAES-OAEP algorithm identifier.
+-- The DER Encoding of this is in hexadecimal:
+-- (0x)30 0D
+-- 06 09
+-- 2A 86 48 86 F7 0D 01 01 07
+-- 30 00
+-- Notice that the DER encoding of default values is "empty".
+--
+rSAES-OAEP-Default-Identifier RSAES-AlgorithmIdentifier ::= {
+ algorithm id-RSAES-OAEP,
+ parameters RSAES-OAEP-params : {
+ hashAlgorithm sha1,
+ maskGenAlgorithm mgf1SHA1,
+ pSourceAlgorithm pSpecifiedEmpty
+ }
}
+RSAES-AlgorithmIdentifier ::= AlgorithmIdentifier-PKCS1 {
+ {PKCS1Algorithms}
+}
+--
+-- AlgorithmIdentifier.parameters for id-RSASSA-PSS.
+-- Note that the tags in this Sequence are explicit.
+--
RSASSA-PSS-params ::= SEQUENCE {
- hashAlgorithm [0] Algorithm, -- DEFAULT sha1,
- maskGenAlgorithm [1] Algorithm, -- DEFAULT mgf1SHA1,
- saltLength [2] INTEGER DEFAULT 20,
- trailerField [3] TrailerField DEFAULT trailerFieldBC
+ hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
+ maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
+ saltLength [2] INTEGER DEFAULT 20,
+ trailerField [3] TrailerField DEFAULT trailerFieldBC
}
TrailerField ::= INTEGER { trailerFieldBC(1) }
-DigestInfo ::= SEQUENCE {
- digestAlgorithm Algorithm,
- digest OCTET STRING
+--
+-- Identifier for default RSASSA-PSS algorithm identifier
+-- The DER Encoding of this is in hexadecimal:
+-- (0x)30 0D
+-- 06 09
+-- 2A 86 48 86 F7 0D 01 01 0A
+-- 30 00
+-- Notice that the DER encoding of default values is "empty".
+--
+rSASSA-PSS-Default-Identifier RSASSA-AlgorithmIdentifier ::= {
+ algorithm id-RSASSA-PSS,
+ parameters RSASSA-PSS-params : {
+ hashAlgorithm sha1,
+ maskGenAlgorithm mgf1SHA1,
+ saltLength 20,trailerField trailerFieldBC
+ }
+}
+RSASSA-AlgorithmIdentifier ::= AlgorithmIdentifier-PKCS1 {
+ {PKCS1Algorithms}
}
-DigestInfoNull ::= SEQUENCE {
- digestAlgorithm AlgorithmNull,
+--
+-- Syntax for the EMSA-PKCS1-v1_5 hash identifier.
+--
+DigestInfo ::= SEQUENCE {
+ digestAlgorithm DigestAlgorithm,
digest OCTET STRING
}
+DigestAlgorithm ::= AlgorithmIdentifier-PKCS1 { {PKCS1-v1-5DigestAlgorithms} }
-END -- PKCS1Definitions
-
+END -- PKCS1Definitions
diff --git a/lib/public_key/doc/src/Makefile b/lib/public_key/doc/src/Makefile
index c8647750af..6a694806bc 100644
--- a/lib/public_key/doc/src/Makefile
+++ b/lib/public_key/doc/src/Makefile
@@ -30,10 +30,6 @@ VSN=$(PUBLIC_KEY_VSN)
APPLICATION=public_key
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -52,107 +48,8 @@ BOOK_FILES = book.xml
XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) \
$(XML_REF6_FILES) $(XML_PART_FILES) $(XML_CHAPTER_FILES)
-GIF_FILES =
-
-# ----------------------------------------------------
-
-TOP_HTML_FILES =
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-EXTRA_FILES = \
- $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
-
TOP_SPECS_FILE = specs.xml
# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-SPECS_FLAGS = -I../../include -I../../src -I../../..
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECS_FILES)
- rm -f errs core *~
-
-man: $(MAN3_FILES) $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6DIR)/* "$(RELEASE_PATH)/man/man6"
-release_spec:
-info:
- @echo "GIF_FILES:\n$(GIF_FILES)"
- @echo ""
- @echo "EXTRA_FILES:\n$(EXTRA_FILES)"
- @echo ""
- @echo "HTML_FILES:\n$(HTML_FILES)"
- @echo ""
- @echo "TOP_HTML_FILES:\n$(TOP_HTML_FILES)"
- @echo ""
- @echo "DEFAULT_GIF_FILES:\n$(DEFAULT_GIF_FILES)"
- @echo ""
- @echo "DEFAULT_HTML_FILES:\n$(DEFAULT_HTML_FILES)"
- @echo ""
- @echo "XML_REF3_FILES:\n$(XML_REF3_FILES)"
- @echo ""
- @echo "XML_REF6_FILES:\n$(XML_REF6_FILES)"
- @echo ""
- @echo "XML_CHAPTER_FILES:\n$(XML_CHAPTER_FILES)"
- @echo ""
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml
index c182a28c53..e831773871 100644
--- a/lib/public_key/doc/src/notes.xml
+++ b/lib/public_key/doc/src/notes.xml
@@ -35,6 +35,145 @@
<file>notes.xml</file>
</header>
+<section><title>Public_Key 1.10</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed case insensitive hostname check.</p>
+ <p>
+ Own Id: OTP-17242 Aux Id: GH-4500 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add sanity check of trusted anchor certificate expiration
+ to pkix_path_validation/3. Although the anchor is
+ considered a trusted input this sanity check does provide
+ extra security for the users of the public_key
+ application as this property needs to be checked at time
+ of usage and fits very well with the other checks
+ performed here.</p>
+ <p>
+ Own Id: OTP-16907</p>
+ </item>
+ <item>
+ <p>
+ Adjust generation of test certificates to conform to RFC
+ 5280 rules for formatting of the certificates validity</p>
+ <p>
+ Own Id: OTP-17111</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Public_Key 1.9.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Corrected dialyzer spec for pkix_path_validation/3</p>
+ <p>
+ Own Id: OTP-17069</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Public_Key 1.9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix the issue that pem_decode will crash with an invalid
+ input.</p>
+ <p>
+ Own Id: OTP-16902 Aux Id: ERIERL-534 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Public_Key 1.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed an insignificant whitespace issue when decoding PEM
+ file.</p>
+ <p>
+ Own Id: OTP-16801 Aux Id: ERL-1309 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Experimental OCSP client support.</p>
+ <p>
+ Own Id: OTP-16448</p>
+ </item>
+ <item>
+ <p>
+ Use user returned path validation error for selfsigned
+ cert. It allows users of the ssl application to customize
+ the generated TLS alert, within the range of defined
+ alerts.</p>
+ <p>
+ Own Id: OTP-16592</p>
+ </item>
+ <item>
+ <p>
+ add API function to retrieve the subject-ID of an X509
+ certificate</p>
+ <p>
+ Own Id: OTP-16705</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Public_Key 1.8</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added support for RSA-PSS signature schemes</p>
+ <p>
+ Own Id: OTP-15247</p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Public_Key 1.7.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index 64f01e670b..76d50c7c63 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -36,7 +36,7 @@
<description>
<p>Provides functions to handle public-key infrastructure,
for details see
- <seealso marker="public_key_app">public_key(6)</seealso>.
+ <seeapp marker="public_key_app">public_key(6)</seeapp>.
</p>
</description>
@@ -46,8 +46,8 @@
<note><p>All records used in this Reference Manual
<!-- except #policy_tree_node{} -->
are generated from ASN.1 specifications
- and are documented in the User's Guide. See <seealso
- marker="public_key_records">Public-key Records</seealso>.
+ and are documented in the User's Guide. See <seeguide
+ marker="public_key_records">Public-key Records</seeguide>.
</p></note>
<p>Use the following include directive to get access to the
@@ -93,13 +93,14 @@
<desc>
<code>Cipher = "RC2-CBC" | "DES-CBC" | "DES-EDE3-CBC"</code>
<p><c>Salt</c> could be generated with
- <seealso marker="crypto:crypto#strong_rand_bytes-1"><c>crypto:strong_rand_bytes(8)</c></seealso>.</p>
+ <seemfa marker="crypto:crypto#strong_rand_bytes/1"><c>crypto:strong_rand_bytes(8)</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
<name name="public_key"/>
<name name="rsa_public_key"/>
+ <name name="rsa_pss_public_key"/>
<name name="dsa_public_key"/>
<name name="ec_public_key"/>
<name name="ecpk_parameters"/>
@@ -118,6 +119,7 @@
<datatype>
<name name="private_key"/>
<name name="rsa_private_key"/>
+ <name name="rsa_pss_private_key"/>
<name name="dsa_private_key"/>
<name name="ec_private_key"/>
<desc>
@@ -151,7 +153,7 @@
</datatype>
<datatype>
- <name name="issuer_id"/>
+ <name name="cert_id"/>
<desc>
</desc>
</datatype>
@@ -196,8 +198,8 @@
<name name="decrypt_private" arity="3" since="OTP R14B"/>
<fsummary>Public-key decryption.</fsummary>
<desc>
- <p>Public-key decryption using the private key. See also <seealso
- marker="crypto:crypto#private_decrypt/4">crypto:private_decrypt/4</seealso></p>
+ <p>Public-key decryption using the private key. See also <seemfa
+ marker="crypto:crypto#private_decrypt/4">crypto:private_decrypt/4</seemfa></p>
</desc>
</func>
@@ -206,8 +208,8 @@
<name name="decrypt_public" arity="3" since="OTP R14B"/>
<fsummary>Public-key decryption.</fsummary>
<desc>
- <p>Public-key decryption using the public key. See also <seealso
- marker="crypto:crypto#public_decrypt/4">crypto:public_decrypt/4</seealso></p>
+ <p>Public-key decryption using the public key. See also <seemfa
+ marker="crypto:crypto#public_decrypt/4">crypto:public_decrypt/4</seemfa></p>
</desc>
</func>
@@ -254,8 +256,8 @@
<fsummary>Public-key encryption using the private key.</fsummary>
<desc>
<p>Public-key encryption using the private key.
- See also <seealso
- marker="crypto:crypto#private_encrypt/4">crypto:private_encrypt/4</seealso>.</p>
+ See also <seemfa
+ marker="crypto:crypto#private_encrypt/4">crypto:private_encrypt/4</seemfa>.</p>
</desc>
</func>
@@ -264,8 +266,8 @@
<name name="encrypt_public" arity="3" since="OTP 21.1"/>
<fsummary>Public-key encryption using the public key.</fsummary>
<desc>
- <p>Public-key encryption using the public key. See also <seealso
- marker="crypto:crypto#public_encrypt/4">crypto:public_encrypt/4</seealso>.</p>
+ <p>Public-key encryption using the public key. See also <seemfa
+ marker="crypto:crypto#public_encrypt/4">crypto:public_encrypt/4</seemfa>.</p>
</desc>
</func>
@@ -275,7 +277,7 @@
<desc>
<p>Generates a new keypair. Note that except for Diffie-Hellman
the public key is included in the private key structure. See also
- <seealso marker="crypto:crypto#generate_key/2">crypto:generate_key/2</seealso>
+ <seemfa marker="crypto:crypto#generate_key/2">crypto:generate_key/2</seemfa>
</p>
</desc>
</func>
@@ -376,9 +378,9 @@
<func>
<name name="pkix_issuer_id" arity="2" since="OTP R14B"/>
- <fsummary>Returns the issuer id.</fsummary>
+ <fsummary>Returns the x509 certificater issuer id.</fsummary>
<desc>
- <p>Returns the issuer id.</p>
+ <p>Returns the x509 certificater issuer id, if it can be determined.</p>
</desc>
</func>
@@ -405,8 +407,8 @@
<v>CertChain = [der_encoded()]</v>
<d>A list of DER-encoded certificates in trust order ending with the peer certificate.</d>
<v>Options = proplists:proplist()</v>
- <v>PublicKeyInfo = {?'rsaEncryption' | ?'id-dsa',
- rsa_public_key() | integer(), 'NULL' | 'Dss-Parms'{}}</v>
+ <v>PublicKeyInfo = {?'rsaEncryption' | ?'id-RSASSA-PSS'| ?'id-dsa',
+ rsa_public_key() | integer(), 'NULL' | 'RSASSA-PSS-params'{} | 'Dss-Parms'{}}</v>
<v>PolicyTree = term()</v>
<d>At the moment this is always an empty list as policies are not currently supported.</d>
<v>Reason = cert_expired | invalid_issuer | invalid_signature | name_not_permitted |
@@ -417,8 +419,8 @@
<p>
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="#pkix_crls_validate-3">pkix_crls_validate/3 </seealso> and is to be called
+ However, CRL validation is done separately by <seemfa
+ marker="#pkix_crls_validate/3">pkix_crls_validate/3 </seemfa> and is to be called
from the supplied <c>verify_fun</c>.
</p>
@@ -448,6 +450,10 @@ fun(OtpCert :: #'OTPCertificate'{},
verifying application-specific extensions. If called with an
extension unknown to the user application, the return value
<c>{unknown, UserState}</c> is to be used.</p>
+ <warning><p>
+ Note that user defined custom <c>verify_fun</c> may alter original
+ path validation error (e.g <c>selfsigned_peer</c>). Use with caution.
+ </p></warning>
</item>
<tag>{max_path_length, integer()}</tag>
@@ -505,8 +511,8 @@ fun(OtpCert :: #'OTPCertificate'{},
<fsummary>Performs CRL validation.</fsummary>
<desc>
<p>Performs CRL validation. It is intended to be called from
- the verify fun of <seealso marker="#pkix_path_validation-3"> pkix_path_validation/3
- </seealso>.</p>
+ the verify fun of <seemfa marker="#pkix_path_validation/3"> pkix_path_validation/3
+ </seemfa>.</p>
<p>Available options:</p>
@@ -565,8 +571,8 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<fsummary>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.</fsummary>
<desc>
<p>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.
- Can be used as input to <seealso
- marker="#pkix_crls_validate-3">pkix_crls_validate/3 </seealso>
+ Can be used as input to <seemfa
+ marker="#pkix_crls_validate/3">pkix_crls_validate/3 </seemfa>
</p>
</desc>
</func>
@@ -578,7 +584,15 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<p> Extracts distribution points from the certificates extensions.</p>
</desc>
</func>
-
+
+ <func>
+ <name name="pkix_hash_type" arity="1" since="@master@"/>
+ <fsummary>Translates OID to Erlang digest type</fsummary>
+ <desc>
+ <p>Translates OID to Erlang digest type</p>
+ </desc>
+ </func>
+
<func>
<name name="pkix_match_dist_point" arity="2" since="OTP 19.0"/>
<fsummary>Checks whether the given distribution point matches the
@@ -626,19 +640,19 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<d>
A valid chain must have at least a ROOT and a peer cert.
The root cert can be given either as a cert pre-generated by
- <seealso marker="#pkix_test_root_cert-2">
+ <seemfa marker="#pkix_test_root_cert/2">
pkix_test_root_cert/2
- </seealso>, or as root cert generation options.
+ </seemfa>, or as root cert generation options.
</d>
<v>root_cert() = #{cert := der_encoded(), key := Key}</v>
<d>
A root certificate generated by
- <seealso marker="#pkix_test_root_cert-2">
+ <seemfa marker="#pkix_test_root_cert/2">
pkix_test_root_cert/2
- </seealso>.
+ </seemfa>.
</d>
<v>cert_opt() = {Key, Value}</v>
- <d>For available options see <seealso marker="#cert_opt"> cert_opt()</seealso> below.</d>
+ <d>For available options see <seeerl marker="#cert_opt"> cert_opt()</seeerl> below.</d>
<v>Config = #{server_config := [conf_opt()],
client_config := [conf_opt()]}</v>
@@ -646,9 +660,9 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<v>conf_opt() = {cert, der_encoded()} | {key, PrivateKey} |{cacerts, [der_encoded()]}</v>
<d>
This is a subset of the type
- <seealso marker="ssl:ssl#type-tls_option"> ssl:tls_option()</seealso>.
+ <seetype marker="ssl:ssl#tls_option"> ssl:tls_option()</seetype>.
<c>PrivateKey</c> is what
- <seealso marker="#generate_key-1">generate_key/1</seealso>
+ <seemfa marker="#generate_key/1">generate_key/1</seemfa>
returns.
</d>
</type>
@@ -675,9 +689,9 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
as root of the client certificate chain.
Vice versa applies to the <c>cacerts</c> returned for the client.
The root cert(s) can either be pre-generated with
- <seealso marker="#pkix_test_root_cert-2">
+ <seemfa marker="#pkix_test_root_cert/2">
pkix_test_root_cert/2
- </seealso>, or if options are specified; it is (they are)
+ </seemfa>, or if options are specified; it is (they are)
generated.
</p>
<p>
@@ -752,27 +766,35 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<v>Options = [cert_opt()]</v>
<d>
For available options see
- <seealso marker="#cert_opt">cert_opt()</seealso>
+ <seeerl marker="#cert_opt">cert_opt()</seeerl>
under
- <seealso marker="#pkix_test_data-1">pkix_test_data/1</seealso>.
+ <seemfa marker="#pkix_test_data/1">pkix_test_data/1</seemfa>.
</d>
<v>RootCert = #{cert := der_encoded(), key := Key}</v>
<d>
A root certificate and key. The <c>Key</c> is generated by
- <seealso marker="#generate_key-1">generate_key/1</seealso>.
+ <seemfa marker="#generate_key/1">generate_key/1</seemfa>.
</d>
</type>
<desc>
<p>
Generates a root certificate that can be used
in multiple calls to
- <seealso marker="#pkix_test_data-1">pkix_test_data/1</seealso>
+ <seemfa marker="#pkix_test_data/1">pkix_test_data/1</seemfa>
when you want the same root certificate for
several generated certificates.
</p>
</desc>
</func>
+ <func>
+ <name name="pkix_subject_id" arity="1" since="@maint@"/>
+ <fsummary>Returns the X509 certificate subject id.</fsummary>
+ <desc>
+ <p>Returns the X509 certificate subject id.</p>
+ </desc>
+ </func>
+
<func>
<name name="pkix_verify" arity="2" since="OTP R14B"/>
<fsummary>Verifies PKIX x.509 certificate signature.</fsummary>
@@ -803,20 +825,20 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<p>This function checks that the <i>Presented Identifier</i> (e.g hostname) in a peer certificate
is in agreement with at least one of the <i>Reference Identifier</i> that the client expects to be connected to.
The function is intended to be added as an extra client check of the peer certificate when performing
- <seealso marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso>
+ <seemfa marker="public_key:public_key#pkix_path_validation/3">public_key:pkix_path_validation/3</seemfa>
</p>
<p>See <url href="https://tools.ietf.org/html/rfc6125">RFC 6125</url>
for detailed information about hostname verification.
- The <seealso marker="using_public_key#verify_hostname">User's Guide</seealso>
+ The <seeguide marker="using_public_key#verify_hostname">User's Guide</seeguide>
and
- <seealso marker="using_public_key#verify_hostname_examples">code examples</seealso>
+ <seeguide marker="using_public_key#verify_hostname_examples">code examples</seeguide>
describes this function more detailed.
</p>
<p>The <c>{OtherRefId,term()}</c> is defined by the user and is passed to the <c>match_fun</c>, if defined.
If the term in <c>OtherRefId</c> is a binary, it will be converted to a string.
</p>
<p>The <c>ip</c> Reference ID takes an
- <seealso marker="kernel:inet#type-ip_address">inet:ip_address()</seealso>
+ <seetype marker="kernel:inet#ip_address">inet:ip_address()</seetype>
or an ip address in string format (E.g "10.0.1.1" or "1234::5678:9012") as second element.
</p>
<p>The options are:</p>
@@ -832,9 +854,9 @@ fun(....) -> true; % My special case
(_, _) -> default % all others falls back to the inherit tests
end
</code>
- <br/>See <seealso marker="#pkix_verify_hostname_match_fun-1">pkix_verify_hostname_match_fun/1</seealso> for a
+ <br/>See <seemfa marker="#pkix_verify_hostname_match_fun/1">pkix_verify_hostname_match_fun/1</seemfa> for a
function that takes a protocol name as argument and returns a <c>fun/2</c> suitable for this option and
- <seealso marker="using_public_key#redefining_match_op">Re-defining the match operation</seealso>
+ <seeguide marker="using_public_key#redefining_match_op">Re-defining the match operation</seeguide>
in the User's Guide for an example.
</item>
@@ -844,7 +866,7 @@ end
of such a function. This <c>fun/1</c> is called when no <c>ReferenceID</c> matches. The return value of the fun
(a <c>boolean()</c>) decides the outcome. If <c>true</c> the the certificate is accepted otherwise
it is rejected. See
- <seealso marker="using_public_key#-pinning--a-certificate">"Pinning" a Certificate</seealso>
+ <seeguide marker="using_public_key#-pinning--a-certificate">"Pinning" a Certificate</seeguide>
in the User's Guide.
</item>
@@ -858,7 +880,7 @@ end
will return <c>false</c>.</item>
</list>
<br/>For an example, see
- <seealso marker="using_public_key#hostname_extraction">Hostname extraction</seealso>
+ <seeguide marker="using_public_key#hostname_extraction">Hostname extraction</seeguide>
in the User's Guide.
</item>
</taglist>
@@ -874,15 +896,15 @@ end
<v>Protocol = https</v>
<d>The algorithm for wich the fun should implement the special matching rules</d>
<v>RefId</v>
- <d>See <seealso marker="#pkix_verify_hostname-3">pkix_verify_hostname/3</seealso>.</d>
+ <d>See <seemfa marker="#pkix_verify_hostname/3">pkix_verify_hostname/3</seemfa>.</d>
<v>FQDN</v>
- <d>See <seealso marker="#pkix_verify_hostname-3">pkix_verify_hostname/3</seealso>.</d>
+ <d>See <seemfa marker="#pkix_verify_hostname/3">pkix_verify_hostname/3</seemfa>.</d>
<v>PresentedID</v>
- <d>See <seealso marker="#pkix_verify_hostname-3">pkix_verify_hostname/3</seealso>.</d>
+ <d>See <seemfa marker="#pkix_verify_hostname/3">pkix_verify_hostname/3</seemfa>.</d>
</type>
<desc>
<p>The return value of calling this function is intended to be used in the <c>match_fun</c> option in
- <seealso marker="#pkix_verify_hostname-3">pkix_verify_hostname/3</seealso>.
+ <seemfa marker="#pkix_verify_hostname/3">pkix_verify_hostname/3</seemfa>.
</p>
<p>The returned fun augments the verify hostname matching according to the specific rules for
the protocol in the argument.
@@ -941,7 +963,7 @@ end
<desc>
<p>Encodes a list of SSH file entries (public keys and attributes) to a binary. Possible
attributes depend on the file type, see
- <seealso marker="#ssh_decode-2"> ssh_decode/2 </seealso>.
+ <seemfa marker="#ssh_decode/2"> ssh_decode/2 </seemfa>.
</p>
<p>If the <c>Type</c> is <c>ssh2_pubkey</c>, the <c>InData</c> shall be
<c>InData_ssh2_pubkey</c>. Otherwise it shall be <c>OtherInData</c>.
@@ -955,8 +977,8 @@ end
<name since="OTP 19.2">ssh_hostkey_fingerprint([DigestType], HostKey) -> [string()]</name>
<fsummary>Calculates a ssh fingerprint for a hostkey.</fsummary>
<type>
- <v>HostKey = <seealso marker="#type-public_key">public_key()</seealso></v>
- <v>DigestType = <seealso marker="#type-digest_type">digest_type()</seealso></v>
+ <v>HostKey = <seetype marker="#public_key">public_key()</seetype></v>
+ <v>DigestType = <seetype marker="#digest_type">digest_type()</seetype></v>
</type>
<desc>
<p>Calculates a ssh fingerprint from a public host key as openssh does.</p>
diff --git a/lib/public_key/doc/src/public_key_app.xml b/lib/public_key/doc/src/public_key_app.xml
index 47dcea3a85..497c4fbd77 100644
--- a/lib/public_key/doc/src/public_key_app.xml
+++ b/lib/public_key/doc/src/public_key_app.xml
@@ -82,7 +82,7 @@
<section>
<title>SEE ALSO</title>
- <p><seealso marker="kernel:application">application(3)</seealso></p>
+ <p><seeerl marker="kernel:application">application(3)</seeerl></p>
</section>
</appref>
diff --git a/lib/public_key/doc/src/public_key_records.xml b/lib/public_key/doc/src/public_key_records.xml
index d26867c12f..3036d45c00 100644
--- a/lib/public_key/doc/src/public_key_records.xml
+++ b/lib/public_key/doc/src/public_key_records.xml
@@ -51,8 +51,8 @@
<p>Common non-standard Erlang
data types used to describe the record fields in the
- following sections and which are not defined in the Public Key <seealso
- marker="public_key">Reference Manual</seealso>
+ following sections and which are not defined in the Public Key <seeerl
+ marker="public_key">Reference Manual</seeerl>
follows here:</p>
<taglist>
@@ -138,7 +138,22 @@
prime, % integer()
exponent, % integer()
coefficient % integer()
- }. </code>
+ }.
+
+#'RSASSA-PSS-params'{hashAlgorithm, % #'HashAlgorithm'{}},
+ maskGenAlgorithm, % #'MaskGenAlgorithm'{}},
+ saltLength, % integer(),
+ trailerField, % integer()
+ }.
+
+#'HashAlgorithm'{algorithm, % oid()
+ parameters % defaults to asn1_NOVALUE
+ }.
+
+#'MaskGenAlgorithm'{algorithm, % oid()
+ parameters, % defaults to asn1_NOVALUE
+ }.
+ </code>
</section>
@@ -424,10 +439,10 @@ are as follows:</p>
}.</code>
<p><c>id_extensions()</c>
- <seealso marker="#StdCertExt">Standard Certificate Extensions</seealso>,
- <seealso marker="#PrivIntExt">Private Internet Extensions</seealso>,
- <seealso marker="#CRLCertExt">CRL Extensions</seealso> and
- <seealso marker="#CRLEntryExt">CRL Entry Extensions</seealso>.
+ <seeguide marker="#StdCertExt">Standard Certificate Extensions</seeguide>,
+ <seeguide marker="#PrivIntExt">Private Internet Extensions</seeguide>,
+ <seeguide marker="#CRLCertExt">CRL Extensions</seeguide> and
+ <seeguide marker="#CRLEntryExt">CRL Entry Extensions</seeguide>.
</p>
</section>
diff --git a/lib/public_key/doc/src/using_public_key.xml b/lib/public_key/doc/src/using_public_key.xml
index de0a6596c3..14e14d24a0 100644
--- a/lib/public_key/doc/src/using_public_key.xml
+++ b/lib/public_key/doc/src/using_public_key.xml
@@ -439,11 +439,11 @@ true = public_key:verify(Digest, none, Signature, PublicKey),</code>
could trust the host names signed in it.
</p>
<p>There is a default hostname matching procedure defined in
- <url href="https://tools.ietf.org/html/rfc6125#section-6">RFC 6125, section 6</url>
+ <url href="https://tools.ietf.org/html/rfc6125#section/6">RFC 6125, section 6</url>
as well as protocol dependent variations defined in
<url href="https://tools.ietf.org/html/rfc6125#appendix-B">RFC 6125 appendix B</url>.
The default procedure is implemented in
- <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2,3</seealso>.
+ <seemfa marker="public_key:public_key#pkix_verify_hostname/2">public_key:pkix_verify_hostname/2,3</seemfa>.
It is possible for a client to hook in modified rules using the options list.
</p>
<p>Some terminology is needed: the certificate presents hostname(s) on which it is valid.
diff --git a/lib/public_key/src/Makefile b/lib/public_key/src/Makefile
index e61390bce3..76bdffe089 100644
--- a/lib/public_key/src/Makefile
+++ b/lib/public_key/src/Makefile
@@ -46,7 +46,8 @@ MODULES = \
pubkey_pbe \
pubkey_cert \
pubkey_cert_records \
- pubkey_crl
+ pubkey_crl\
+ pubkey_ocsp
HRL_FILES = $(INCLUDE)/public_key.hrl
diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl
index a5836f6d07..e84edffd53 100644
--- a/lib/public_key/src/pubkey_cert.erl
+++ b/lib/public_key/src/pubkey_cert.erl
@@ -24,16 +24,30 @@
-include("public_key.hrl").
--export([init_validation_state/3, prepare_for_next_cert/2,
- validate_time/3, validate_signature/6,
- validate_issuer/4, validate_names/6,
+-export([init_validation_state/3,
+ prepare_for_next_cert/2,
+ validate_time/3,
+ validate_signature/6,
+ validate_issuer/4,
+ validate_names/6,
validate_extensions/4,
- normalize_general_name/1, is_self_signed/1,
- is_issuer/2, issuer_id/2, distribution_points/1,
- is_fixed_dh_cert/1, verify_data/1, verify_fun/4,
- select_extension/2, match_name/3,
- extensions_list/1, cert_auth_key_id/1, time_str_2_gregorian_sec/1,
- gen_test_certs/1, root_cert/2]).
+ normalize_general_name/1,
+ is_self_signed/1,
+ is_issuer/2,
+ issuer_id/2,
+ subject_id/1,
+ distribution_points/1,
+ is_fixed_dh_cert/1,
+ verify_data/1,
+ verify_fun/4,
+ select_extension/2,
+ match_name/3,
+ extensions_list/1,
+ cert_auth_key_id/1,
+ time_str_2_gregorian_sec/1,
+ gen_test_certs/1,
+ x509_pkix_sign_types/1,
+ root_cert/2]).
-define(NULL, 0).
@@ -54,11 +68,15 @@
-type test_root_cert() ::
#{cert := binary(), key := public_key:private_key()}.
%%====================================================================
-%% Internal application APIu
+%% Internal application APIs
%%====================================================================
%%--------------------------------------------------------------------
--spec verify_data(DER::binary()) -> {md5 | sha, binary(), binary()}.
+-spec verify_data(DER::binary()) ->
+ {DigestType, PlainText, Signature}
+ when DigestType :: md5 | crypto:sha1() | crypto:sha2(),
+ PlainText :: binary(),
+ Signature :: binary().
%%
%% Description: Extracts data from DerCert needed to call public_key:verify/4.
%%--------------------------------------------------------------------
@@ -291,6 +309,20 @@ issuer_id(Otpcert, self) ->
SerialNr = TBSCert#'OTPTBSCertificate'.serialNumber,
{ok, {SerialNr, normalize_general_name(Issuer)}}.
+
+%%--------------------------------------------------------------------
+-spec subject_id(#'OTPCertificate'{}) ->
+ {integer(), term()}.
+%%
+%% Description: Extracts the subject and serial number from a certificate.
+%%--------------------------------------------------------------------
+subject_id(Otpcert) ->
+ TBSCert = Otpcert#'OTPCertificate'.tbsCertificate,
+ Subject = TBSCert#'OTPTBSCertificate'.subject,
+ SerialNr = TBSCert#'OTPTBSCertificate'.serialNumber,
+ {SerialNr, normalize_general_name(Subject)}.
+
+
distribution_points(Otpcert) ->
TBSCert = Otpcert#'OTPCertificate'.tbsCertificate,
Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions),
@@ -503,6 +535,17 @@ gen_test_certs(
DERCAs = ca_config(RootCert, CAsKeys),
[{cert, DERCert}, {key, DERKey}, {cacerts, DERCAs}].
+
+x509_pkix_sign_types(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS',
+ parameters = #'RSASSA-PSS-params'{hashAlgorithm = #'HashAlgorithm'{algorithm = Alg}}}) ->
+ Hash = public_key:pkix_hash_type(Alg),
+ {Hash, rsa_pss_pss, [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, -1},
+ {rsa_mgf1_md, Hash}]};
+x509_pkix_sign_types(#'SignatureAlgorithm'{algorithm = Alg}) ->
+ {Hash, Sign} = public_key:pkix_sign_types(Alg),
+ {Hash, Sign, []}.
+
%%%
-spec root_cert(string(), [cert_opt()]) -> test_root_cert().
%%
@@ -511,13 +554,16 @@ root_cert(Name, Opts) ->
PrivKey = gen_key(proplists:get_value(key, Opts, default_key_gen())),
TBS = cert_template(),
Issuer = subject("root", Name),
+ SignatureId = sign_algorithm(PrivKey, Opts),
+ SPI = public_key(PrivKey, SignatureId),
+
OTPTBS =
TBS#'OTPTBSCertificate'{
- signature = sign_algorithm(PrivKey, Opts),
+ signature = SignatureId,
issuer = Issuer,
validity = validity(Opts),
subject = Issuer,
- subjectPublicKeyInfo = public_key(PrivKey),
+ subjectPublicKeyInfo = SPI,
extensions = extensions(undefined, ca, Opts)
},
#{cert => public_key:pkix_sign(OTPTBS, PrivKey),
@@ -552,17 +598,21 @@ extensions_list(Extensions) ->
extract_verify_data(OtpCert, DerCert) ->
Signature = OtpCert#'OTPCertificate'.signature,
- SigAlgRec = OtpCert#'OTPCertificate'.signatureAlgorithm,
- SigAlg = SigAlgRec#'SignatureAlgorithm'.algorithm,
+ SigAlg = OtpCert#'OTPCertificate'.signatureAlgorithm,
PlainText = encoded_tbs_cert(DerCert),
- {DigestType,_} = public_key:pkix_sign_types(SigAlg),
+ {DigestType,_,_} = x509_pkix_sign_types(SigAlg),
{DigestType, PlainText, Signature}.
verify_signature(OtpCert, DerCert, Key, KeyParams) ->
{DigestType, PlainText, Signature} = extract_verify_data(OtpCert, DerCert),
case Key of
#'RSAPublicKey'{} ->
- public_key:verify(PlainText, DigestType, Signature, Key);
+ case KeyParams of
+ #'RSASSA-PSS-params'{} ->
+ public_key:verify(PlainText, DigestType, Signature, Key, verify_options(KeyParams));
+ 'NULL' ->
+ public_key:verify(PlainText, DigestType, Signature, Key)
+ end;
_ ->
public_key:verify(PlainText, DigestType, Signature, {Key, KeyParams})
end.
@@ -1119,6 +1169,8 @@ is_key(#'DSAPrivateKey'{}) ->
true;
is_key(#'RSAPrivateKey'{}) ->
true;
+is_key({#'RSAPrivateKey'{}, _}) ->
+ true;
is_key(#'ECPrivateKey'{}) ->
true;
is_key(_) ->
@@ -1166,18 +1218,41 @@ validity(Opts) ->
DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1),
DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7),
{DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}),
- Format =
+
+ GenFormat =
fun({Y,M,D}) ->
lists:flatten(
io_lib:format("~4..0w~2..0w~2..0w130000Z",[Y,M,D]))
end,
- #'Validity'{notBefore={generalTime, Format(DefFrom)},
- notAfter ={generalTime, Format(DefTo)}}.
-
-sign_algorithm(#'RSAPrivateKey'{}, Opts) ->
- Type = rsa_digest_oid(proplists:get_value(digest, Opts, sha1)),
- #'SignatureAlgorithm'{algorithm = Type,
- parameters = 'NULL'};
+
+ UTCFormat =
+ fun({Y,M,D}) ->
+ [_, _, Y3, Y4] = integer_to_list(Y),
+ lists:flatten(
+ io_lib:format("~s~2..0w~2..0w130000Z",[[Y3, Y4],M,D]))
+ end,
+
+ #'Validity'{notBefore = validity_format(DefFrom, GenFormat, UTCFormat),
+ notAfter = validity_format(DefTo, GenFormat, UTCFormat)}.
+
+validity_format({Year, _, _} = Validity, GenFormat, _UTCFormat) when Year >= 2049 ->
+ {generalTime, GenFormat(Validity)};
+validity_format(Validity, _GenFormat, UTCFormat) ->
+ {utcTime, UTCFormat(Validity)}.
+
+
+sign_algorithm(#'RSAPrivateKey'{} = Key , Opts) ->
+ case proplists:get_value(rsa_padding, Opts, rsa_pkcs1_pss_padding) of
+ rsa_pkcs1_pss_padding ->
+ DigestId = rsa_digest_oid(proplists:get_value(digest, Opts, sha1)),
+ rsa_sign_algo(Key, DigestId, 'NULL');
+ rsa_pss_rsae ->
+ DigestId = rsa_digest_oid(proplists:get_value(digest, Opts, sha256)),
+ rsa_sign_algo(Key, DigestId, 'NULL')
+ end;
+sign_algorithm({#'RSAPrivateKey'{} = Key,#'RSASSA-PSS-params'{} = Params}, _Opts) ->
+ rsa_sign_algo(Key, ?'id-RSASSA-PSS', Params);
+
sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) ->
#'SignatureAlgorithm'{algorithm = ?'id-dsa-with-sha1',
parameters = {params,#'Dss-Parms'{p=P, q=Q, g=G}}};
@@ -1185,6 +1260,16 @@ sign_algorithm(#'ECPrivateKey'{parameters = Parms}, Opts) ->
Type = ecdsa_digest_oid(proplists:get_value(digest, Opts, sha1)),
#'SignatureAlgorithm'{algorithm = Type,
parameters = Parms}.
+
+rsa_sign_algo(#'RSAPrivateKey'{}, ?'id-RSASSA-PSS' = Type, #'RSASSA-PSS-params'{} = Params) ->
+ #'SignatureAlgorithm'{algorithm = Type,
+ parameters = Params};
+rsa_sign_algo(#'RSAPrivateKey'{}, Type, Parms) ->
+ #'SignatureAlgorithm'{algorithm = Type,
+ parameters = Parms}.
+
+rsa_digest_oid(Oid) when is_tuple(Oid) ->
+ Oid;
rsa_digest_oid(sha1) ->
?'sha1WithRSAEncryption';
rsa_digest_oid(sha) ->
@@ -1196,8 +1281,10 @@ rsa_digest_oid(sha384) ->
rsa_digest_oid(sha256) ->
?'sha256WithRSAEncryption';
rsa_digest_oid(md5) ->
- ?'md5WithRSAEncryption'.
+ ?'md5WithRSAEncryption'.
+ecdsa_digest_oid(Oid) when is_tuple(Oid) ->
+ Oid;
ecdsa_digest_oid(sha1) ->
?'ecdsa-with-SHA1';
ecdsa_digest_oid(sha) ->
@@ -1229,12 +1316,13 @@ cert_chain(Role, IssuerCert, IssuerKey, [CAOpts | Rest], N, Acc) ->
cert(Role, #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subject = Issuer}},
PrivKey, Key, Contact, Name, Opts, Type) ->
TBS = cert_template(),
+ SignAlgoId = sign_algorithm(PrivKey, Opts),
OTPTBS = TBS#'OTPTBSCertificate'{
- signature = sign_algorithm(PrivKey, Opts),
+ signature = SignAlgoId,
issuer = Issuer,
validity = validity(Opts),
subject = subject(Contact, atom_to_list(Role) ++ Name),
- subjectPublicKeyInfo = public_key(Key),
+ subjectPublicKeyInfo = public_key(Key, SignAlgoId),
extensions = extensions(Role, Type, Opts)
},
public_key:pkix_sign(OTPTBS, PrivKey).
@@ -1251,19 +1339,33 @@ default_key_gen() ->
{namedCurve, Oid}
end.
-public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E}) ->
+public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E},
+ #'SignatureAlgorithm'{algorithm = ?rsaEncryption,
+ parameters = #'RSASSA-PSS-params'{} = Params}) ->
+ Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters = Params},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo,
+ subjectPublicKey = Public};
+public_key({#'RSAPrivateKey'{modulus=N, publicExponent=E}, #'RSASSA-PSS-params'{} = Params},
+ #'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS',
+ parameters = #'RSASSA-PSS-params'{} = Params}) ->
+ Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-RSASSA-PSS', parameters= Params},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo,
+ subjectPublicKey = Public};
+public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E}, _) ->
Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo,
subjectPublicKey = Public};
-public_key(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) ->
+public_key(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}, _) ->
Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa',
parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y};
public_key(#'ECPrivateKey'{version = _Version,
privateKey = _PrivKey,
parameters = Params,
- publicKey = PubKey}) ->
+ publicKey = PubKey}, _) ->
Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params},
#'OTPSubjectPublicKeyInfo'{algorithm = Algo,
subjectPublicKey = #'ECPoint'{point = PubKey}}.
@@ -1306,6 +1408,9 @@ add_default_extensions(Defaults0, Exts) ->
end, Defaults0),
Exts ++ Defaults.
+encode_key({#'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}} = Key) ->
+ {Asn1Type, DER, _} = public_key:pem_entry_encode('PrivateKeyInfo', Key),
+ {Asn1Type, DER};
encode_key(#'RSAPrivateKey'{} = Key) ->
{'RSAPrivateKey', public_key:der_encode('RSAPrivateKey', Key)};
encode_key(#'ECPrivateKey'{} = Key) ->
@@ -1313,3 +1418,11 @@ encode_key(#'ECPrivateKey'{} = Key) ->
encode_key(#'DSAPrivateKey'{} = Key) ->
{'DSAPrivateKey', public_key:der_encode('DSAPrivateKey', Key)}.
+verify_options(#'RSASSA-PSS-params'{saltLength = SaltLen,
+ maskGenAlgorithm =
+ #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = HashOid}}}) ->
+ HashAlgo = public_key:pkix_hash_type(HashOid),
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, SaltLen},
+ {rsa_mgf1_md, HashAlgo}].
diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl
index cdb3723bf3..b556314ad1 100644
--- a/lib/public_key/src/pubkey_cert_records.erl
+++ b/lib/public_key/src/pubkey_cert_records.erl
@@ -110,7 +110,8 @@ supportedPublicKeyAlgorithms(?'rsaEncryption') -> 'RSAPublicKey';
supportedPublicKeyAlgorithms(?'id-dsa') -> 'DSAPublicKey';
supportedPublicKeyAlgorithms(?'dhpublicnumber') -> 'DHPublicKey';
supportedPublicKeyAlgorithms(?'id-keyExchangeAlgorithm') -> 'KEA-PublicKey';
-supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint'.
+supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint';
+supportedPublicKeyAlgorithms(?'id-RSASSA-PSS') -> 'RSAPublicKey'.
supportedCurvesTypes(?'characteristic-two-field') -> characteristic_two_field;
supportedCurvesTypes(?'prime-field') -> prime_field;
diff --git a/lib/public_key/src/pubkey_ocsp.erl b/lib/public_key/src/pubkey_ocsp.erl
new file mode 100644
index 0000000000..249b430660
--- /dev/null
+++ b/lib/public_key/src/pubkey_ocsp.erl
@@ -0,0 +1,345 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(pubkey_ocsp).
+
+-include("public_key.hrl").
+
+-export([otp_cert/1,
+ get_ocsp_responder_id/1,
+ get_nonce_extn/1,
+ decode_ocsp_response/1,
+ verify_ocsp_response/3,
+ get_acceptable_response_types_extn/0,
+ find_single_response/3,
+ ocsp_status/1]).
+
+ocsp_status({good, _}) ->
+ valid;
+ocsp_status({unknown, Reason}) ->
+ {bad_cert, {revocation_status_undetermined, Reason}};
+ocsp_status({revoked, Reason}) ->
+ {bad_cert, {revoked, Reason}}.
+
+%%--------------------------------------------------------------------
+-spec verify_ocsp_response(binary(), list(), undefined | binary()) ->
+ {ok, term()} | {error, term()}.
+%%
+%% Description: Verify the OCSP response to get the certificate status
+%%--------------------------------------------------------------------
+verify_ocsp_response(OCSPResponseDer, ResponderCerts, Nonce) ->
+ do_verify_ocsp_response(
+ decode_ocsp_response(OCSPResponseDer), ResponderCerts, Nonce
+ ).
+
+%%--------------------------------------------------------------------
+-spec do_verify_ocsp_response({ok, #'BasicOCSPResponse'{}} | {error, term()},
+ list(), undefined | binary()) ->
+ {ok, term()} | {error, term()}.
+%%
+%% Description: Verify the OCSP response to get the certificate status
+%%--------------------------------------------------------------------
+do_verify_ocsp_response(
+ {ok, #'BasicOCSPResponse'{
+ tbsResponseData = ResponseData,
+ signatureAlgorithm = SignatureAlgo,
+ signature = Signature,
+ certs = Certs
+ }}, ResponderCerts, Nonce) ->
+
+ #'ResponseData'{
+ responderID = ResponderID
+ } = ResponseData,
+
+ case verify_ocsp_signature(
+ public_key:der_encode('ResponseData', ResponseData),
+ SignatureAlgo#'AlgorithmIdentifier'.algorithm,
+ Signature, Certs ++ ResponderCerts,
+ ResponderID) of
+ ok ->
+ verify_ocsp_nonce(ResponseData, Nonce);
+ {error, Reason} ->
+ {error, Reason}
+ end;
+do_verify_ocsp_response({error, Reason}, _ResponderCerts, _Nonce) ->
+ {error, Reason}.
+
+%%--------------------------------------------------------------------
+-spec verify_ocsp_nonce(#'ResponseData'{}, undefined | binary()) ->
+ {ok, term()} | {error, nonce_mismatch}.
+%%
+%% Description: Check if the nonces matches in OCSP response
+%%--------------------------------------------------------------------
+verify_ocsp_nonce(ResponseData, Nonce) ->
+ #'ResponseData'{
+ responses = Responses,
+ responseExtensions = ResponseExtns
+ } = ResponseData,
+
+ case get_nonce_value(ResponseExtns) of
+ Nonce ->
+ {ok, Responses};
+ _Other ->
+ {error, nonce_mismatch}
+ end.
+
+%%--------------------------------------------------------------------
+-spec get_nonce_value(asn1_NOVALUE | list()) ->
+ undefined | binary().
+%%
+%% Description: Get the nonce value from extensions
+%%--------------------------------------------------------------------
+%% no extensions present in response
+get_nonce_value(asn1_NOVALUE) ->
+ undefined;
+%% extensions exist, but no oscp nonce
+get_nonce_value([]) ->
+ undefined;
+get_nonce_value([#'Extension'{
+ extnID = ?'id-pkix-ocsp-nonce',
+ extnValue = Value} | _Extns]) ->
+ Value;
+get_nonce_value([_Extn | Rest]) ->
+ get_nonce_value(Rest).
+
+%%--------------------------------------------------------------------
+-spec decode_ocsp_response(binary()) ->
+ {ok, #'BasicOCSPResponse'{}} | {error, term()}.
+%%
+%% Description: Decode the OCSP response der
+%%--------------------------------------------------------------------
+decode_ocsp_response(Response) ->
+ Resp = public_key:der_decode('OCSPResponse', Response),
+ case Resp#'OCSPResponse'.responseStatus of
+ successful ->
+ decode_response_bytes(
+ Resp#'OCSPResponse'.responseBytes
+ );
+ Error ->
+ {error, Error}
+ end.
+
+%%--------------------------------------------------------------------
+-spec decode_response_bytes(#'ResponseBytes'{}) ->
+ {ok, #'BasicOCSPResponse'{}} | {error, term()}.
+%%
+%% Description: Get basic ocsp response field
+%%--------------------------------------------------------------------
+decode_response_bytes(#'ResponseBytes'{
+ responseType = ?'id-pkix-ocsp-basic',
+ response = Data}) ->
+ {ok, public_key:der_decode('BasicOCSPResponse', Data)};
+decode_response_bytes(#'ResponseBytes'{responseType = RespType}) ->
+ {error, {ocsp_response_type_not_supported, RespType}}.
+
+%%--------------------------------------------------------------------
+-spec verify_ocsp_signature(binary(), term(), term(), list(), term()) ->
+ ok | {error, term()}.
+%%
+%% Description: Verify the signature of OCSP response
+%%--------------------------------------------------------------------
+verify_ocsp_signature(
+ ResponseDataDer, SignatureAlgo, Signature, Certs, ResponderID) ->
+ case find_responder_cert(ResponderID, Certs) of
+ {ok, Cert} ->
+ do_verify_ocsp_signature(
+ ResponseDataDer, Signature, SignatureAlgo, Cert);
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+%%--------------------------------------------------------------------
+-spec find_responder_cert(term(), list()) ->
+ {ok, #'Certificate'{} | #'OTPCertificate'{}} | {error, term()}.
+%%
+%% Description: Find the OCSP responder's cert in input list
+%%--------------------------------------------------------------------
+find_responder_cert(_ResponderID, []) ->
+ {error, ocsp_responder_cert_not_found};
+find_responder_cert(ResponderID, [Cert | TCerts]) ->
+ case is_responder(ResponderID, Cert) of
+ true ->
+ {ok, Cert};
+ false ->
+ find_responder_cert(ResponderID, TCerts)
+ end.
+
+%%--------------------------------------------------------------------
+-spec do_verify_ocsp_signature(
+ binary(), term(), term(), #'Certificate'{} | #'OTPCertificate'{}) ->
+ ok | {error, term()}.
+%%
+%% Description: Verify the signature of OCSP response
+%%--------------------------------------------------------------------
+do_verify_ocsp_signature(ResponseDataDer, Signature, AlgorithmID, Cert) ->
+ {DigestType, _SignatureType} = public_key:pkix_sign_types(AlgorithmID),
+ case public_key:verify(
+ ResponseDataDer, DigestType, Signature,
+ get_public_key_rec(Cert)) of
+ true ->
+ ok;
+ false ->
+ {error, ocsp_response_bad_signature}
+ end.
+
+%%--------------------------------------------------------------------
+-spec get_public_key_rec(#'Certificate'{} | #'OTPCertificate'{}) ->
+ term().
+%%
+%% Description: Get the subject public key field
+%%--------------------------------------------------------------------
+get_public_key_rec(#'Certificate'{} = Cert) ->
+ get_public_key_rec(otp_cert(Cert));
+get_public_key_rec(#'OTPCertificate'{tbsCertificate = TbsCert}) ->
+ PKInfo = TbsCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ PKInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey.
+
+%%--------------------------------------------------------------------
+-spec is_responder(tuple(), #'Certificate'{} | #'OTPCertificate'{}) ->
+ boolean().
+%%
+%% Description: Check if is OCSP responder's cert
+%%--------------------------------------------------------------------
+is_responder({byName, Name}, Cert) ->
+ public_key:der_encode('Name', Name) == get_subject_name(Cert);
+is_responder({byKey, Key}, Cert) ->
+ Key == crypto:hash(sha, get_public_key(Cert)).
+
+%%--------------------------------------------------------------------
+-spec otp_cert(#'Certificate'{} | #'OTPCertificate'{} | binary()) ->
+ #'OTPCertificate'{}.
+%%
+%% Description: Convert to #'OTPCertificate'{}
+%%--------------------------------------------------------------------
+otp_cert(#'OTPCertificate'{} = Cert) ->
+ Cert;
+otp_cert(#'Certificate'{} = Cert) ->
+ public_key:pkix_decode_cert(
+ public_key:der_encode('Certificate', Cert), otp);
+otp_cert(CertDer) when is_binary(CertDer) ->
+ public_key:pkix_decode_cert(CertDer, otp).
+
+%%--------------------------------------------------------------------
+-spec get_ocsp_responder_id(#'Certificate'{}) -> binary().
+%%
+%% Description: Get the OCSP responder ID der
+%%--------------------------------------------------------------------
+get_ocsp_responder_id(#'Certificate'{tbsCertificate = TbsCert}) ->
+ public_key:der_encode(
+ 'ResponderID', {byName, TbsCert#'TBSCertificate'.subject}).
+
+%%--------------------------------------------------------------------
+-spec get_subject_name(#'Certificate'{} | #'OTPCertificate'{}) -> binary().
+%%
+%% Description: Get the subject der from cert
+%%--------------------------------------------------------------------
+get_subject_name(#'Certificate'{} = Cert) ->
+ get_subject_name(otp_cert(Cert));
+get_subject_name(#'OTPCertificate'{tbsCertificate = TbsCert}) ->
+ public_key:pkix_encode('Name', TbsCert#'OTPTBSCertificate'.subject, otp).
+
+%%--------------------------------------------------------------------
+-spec get_public_key(#'Certificate'{} | #'OTPCertificate'{}) -> binary().
+%%
+%% Description: Get the public key from cert
+%%--------------------------------------------------------------------
+get_public_key(#'Certificate'{} = Cert) ->
+ get_public_key(otp_cert(Cert));
+get_public_key(#'OTPCertificate'{tbsCertificate = TbsCert}) ->
+ PKInfo = TbsCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ enc_pub_key(PKInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey).
+
+enc_pub_key(Key = #'RSAPublicKey'{}) ->
+ public_key:der_encode('RSAPublicKey', Key);
+enc_pub_key({DsaInt, #'Dss-Parms'{}}) when is_integer(DsaInt) ->
+ public_key:der_encode('DSAPublicKey', DsaInt);
+enc_pub_key({#'ECPoint'{point = Key}, _ECParam}) ->
+ Key.
+
+%%--------------------------------------------------------------------
+-spec get_nonce_extn(undefined | binary()) -> undefined | #'Extension'{}.
+%%
+%% Description: Get an OCSP nonce der
+%%--------------------------------------------------------------------
+get_nonce_extn(undefined) ->
+ undefined;
+get_nonce_extn(Nonce) when is_binary(Nonce) ->
+ #'Extension'{
+ extnID = ?'id-pkix-ocsp-nonce',
+ extnValue = Nonce
+ }.
+
+%%--------------------------------------------------------------------
+-spec get_acceptable_response_types_extn() -> #'Extension'{}.
+%%
+%% Description: Get an acceptable response types der
+%%--------------------------------------------------------------------
+get_acceptable_response_types_extn() ->
+ #'Extension'{
+ extnID = ?'id-pkix-ocsp-response',
+ extnValue = public_key:der_encode(
+ 'AcceptableResponses', [?'id-pkix-ocsp-basic'])
+ }.
+
+%%--------------------------------------------------------------------
+-spec get_serial_num(binary | #'Certificate'{} | #'OTPCertificate'{}) ->
+ term().
+%%
+%% Description: Get the serial number of a certificate
+%%--------------------------------------------------------------------
+get_serial_num(Cert) ->
+ #'OTPCertificate'{tbsCertificate = TbsCert} = otp_cert(Cert),
+ TbsCert#'OTPTBSCertificate'.serialNumber.
+
+
+%%--------------------------------------------------------------------
+%% -spec find_single_response(#'OTPCertificate'{}, #'OTPCertificate'{},
+%% [#'SingleResponse'{}]) ->
+%% #'SingleResponse'{} | {error, no_matched_response}.
+%% %%
+%% Description: Find the matched single response.
+%%--------------------------------------------------------------------
+find_single_response(Cert, IssuerCert, SingleResponseList) ->
+ IssuerName = get_subject_name(IssuerCert),
+ IssuerKey = get_public_key(IssuerCert),
+ SerialNum = get_serial_num(Cert),
+ match_single_response(
+ IssuerName, IssuerKey, SerialNum, SingleResponseList).
+
+match_single_response(_IssuerName, _IssuerKey, _SerialNum, []) ->
+ {error, no_matched_response};
+match_single_response(
+ IssuerName, IssuerKey, SerialNum,
+ [#'SingleResponse'{
+ certID = #'CertID'{hashAlgorithm = Algo} = CertID
+ } = Response | Responses]) ->
+ HashType = public_key:pkix_hash_type(
+ Algo#'AlgorithmIdentifier'.algorithm),
+ case (SerialNum == CertID#'CertID'.serialNumber) andalso
+ (crypto:hash(
+ HashType, IssuerName) == CertID#'CertID'.issuerNameHash) andalso
+ (crypto:hash(
+ HashType, IssuerKey) == CertID#'CertID'.issuerKeyHash) of
+ true ->
+ {ok, Response};
+ false ->
+ match_single_response(IssuerName, IssuerKey, SerialNum, Responses)
+ end.
+
diff --git a/lib/public_key/src/pubkey_pbe.erl b/lib/public_key/src/pubkey_pbe.erl
index 972a9f8db6..dc327567e4 100644
--- a/lib/public_key/src/pubkey_pbe.erl
+++ b/lib/public_key/src/pubkey_pbe.erl
@@ -39,23 +39,22 @@
%%--------------------------------------------------------------------
encode(Data, Password, "DES-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_encrypt(des_cbc, Key, IV, pbe_pad(Data, block_size(des_cbc)));
+ crypto:crypto_one_time(des_cbc, Key, IV, pbe_pad(Data, block_size(des_cbc)), true);
encode(Data, Password, "DES-EDE3-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- <<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key,
- crypto:block_encrypt(des3_cbc, [Key1, Key2, Key3], IV, pbe_pad(Data, block_size(des_3ede)));
+ crypto:crypto_one_time(des_ede3_cbc, Key, IV, pbe_pad(Data, block_size(des_ede3_cbc)), true);
encode(Data, Password, "RC2-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_encrypt(rc2_cbc, Key, IV, pbe_pad(Data, block_size(rc2_cbc)));
+ crypto:crypto_one_time(rc2_cbc, Key, IV, pbe_pad(Data, block_size(rc2_cbc)), true);
encode(Data, Password, "AES-128-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_encrypt(aes_128_cbc, Key, IV, pbe_pad(Data, block_size(aes_128_cbc)));
+ crypto:crypto_one_time(aes_128_cbc, Key, IV, pbe_pad(Data, block_size(aes_128_cbc)), true);
encode(Data, Password, "AES-192-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_encrypt(aes_192_cbc, Key, IV, pbe_pad(Data, block_size(aes_192_cbc)));
+ crypto:crypto_one_time(aes_192_cbc, Key, IV, pbe_pad(Data, block_size(aes_192_cbc)), true);
encode(Data, Password, "AES-256-CBC"= Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_encrypt(aes_256_cbc, Key, IV, pbe_pad(Data, block_size(aes_256_cbc))).
+ crypto:crypto_one_time(aes_256_cbc, Key, IV, pbe_pad(Data, block_size(aes_256_cbc)), true).
%%--------------------------------------------------------------------
-spec decode(binary(), string(), string(), term()) -> binary().
@@ -64,23 +63,22 @@ encode(Data, Password, "AES-256-CBC"= Cipher, KeyDevParams) ->
%%--------------------------------------------------------------------
decode(Data, Password,"DES-CBC"= Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_decrypt(des_cbc, Key, IV, Data);
+ crypto:crypto_one_time(des_cbc, Key, IV, Data, false);
decode(Data, Password,"DES-EDE3-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- <<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key,
- crypto:block_decrypt(des3_cbc, [Key1, Key2, Key3], IV, Data);
+ crypto:crypto_one_time(des_ede3_cbc, Key, IV, Data, false);
decode(Data, Password,"RC2-CBC"= Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_decrypt(rc2_cbc, Key, IV, Data);
+ crypto:crypto_one_time(rc2_cbc, Key, IV, Data, false);
decode(Data, Password,"AES-128-CBC"= Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_decrypt(aes_128_cbc, Key, IV, Data);
+ crypto:crypto_one_time(aes_128_cbc, Key, IV, Data, false);
decode(Data, Password,"AES-192-CBC"= Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_decrypt(aes_192_cbc, Key, IV, Data);
+ crypto:crypto_one_time(aes_192_cbc, Key, IV, Data, false);
decode(Data, Password,"AES-256-CBC"= Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
- crypto:block_decrypt(aes_256_cbc, Key, IV, Data).
+ crypto:crypto_one_time(aes_256_cbc, Key, IV, Data, false).
%%--------------------------------------------------------------------
-spec pbdkdf1(iodata(), iodata(), integer(), atom()) -> binary().
@@ -247,17 +245,21 @@ key_derivation_params(#'PBES2-params'{keyDerivationFunc = KeyDerivationFunc,
%% ?'id-hmacWithSHA1, but we need some kind of ASN1-fix for this.
pseudo_random_function(#'PBKDF2-params_prf'{algorithm =
{_,_, _,'id-hmacWithSHA1'}}) ->
- {fun crypto:hmac/4, sha, pseudo_output_length(?'id-hmacWithSHA1')};
+ {fun hmac4/4, sha, pseudo_output_length(?'id-hmacWithSHA1')};
pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA1' = Algo}) ->
- {fun crypto:hmac/4, sha, pseudo_output_length(Algo)};
+ {fun hmac4/4, sha, pseudo_output_length(Algo)};
pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA224'= Algo}) ->
- {fun crypto:hmac/4, sha224, pseudo_output_length(Algo)};
+ {fun hmac4/4, sha224, pseudo_output_length(Algo)};
pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA256' = Algo}) ->
- {fun crypto:hmac/4, sha256, pseudo_output_length(Algo)};
+ {fun hmac4/4, sha256, pseudo_output_length(Algo)};
pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA384' = Algo}) ->
- {fun crypto:hmac/4, sha384, pseudo_output_length(Algo)};
+ {fun hmac4/4, sha384, pseudo_output_length(Algo)};
pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA512' = Algo}) ->
- {fun crypto:hmac/4, sha512, pseudo_output_length(Algo)}.
+ {fun hmac4/4, sha512, pseudo_output_length(Algo)}.
+
+hmac4(SubType, Key, Data, MacLength) ->
+ crypto:macN(hmac, SubType, Key, Data, MacLength).
+
pseudo_output_length(?'id-hmacWithSHA1') ->
20; %%160/8
@@ -295,7 +297,7 @@ derived_key_length(Cipher,_) when (Cipher == "AES-256-CBC");
block_size(Cipher) when Cipher == rc2_cbc;
Cipher == des_cbc;
- Cipher == des_3ede ->
+ Cipher == des_ede3_cbc ->
8;
block_size(Cipher) when Cipher == aes_128_cbc;
Cipher == aes_192_cbc;
diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl
index 2979f4e172..a33db4ecf0 100644
--- a/lib/public_key/src/pubkey_pem.erl
+++ b/lib/public_key/src/pubkey_pem.erl
@@ -57,7 +57,8 @@
%% Description: Decodes a PEM binary.
%%--------------------------------------------------------------------
decode(Bin) ->
- decode_pem_entries(split_bin(Bin), []).
+ decode_pem_entries(
+ binary:split(Bin, [<<"\r\n">>, <<"\r">>, <<"\n">>], [global]), []).
%%--------------------------------------------------------------------
-spec encode([public_key:pem_entry()]) -> iolist().
@@ -112,7 +113,8 @@ decode_pem_entries([<<>>], Entries) ->
lists:reverse(Entries);
decode_pem_entries([<<>> | Lines], Entries) ->
decode_pem_entries(Lines, Entries);
-decode_pem_entries([Start| Lines], Entries) ->
+decode_pem_entries([StartLine | Lines], Entries) ->
+ Start = strip_tail_whitespace(StartLine),
case pem_end(Start) of
undefined ->
decode_pem_entries(Lines, Entries);
@@ -121,6 +123,20 @@ decode_pem_entries([Start| Lines], Entries) ->
decode_pem_entries(RestLines, [decode_pem_entry(Start, Entry) | Entries])
end.
+strip_tail_whitespace(Bin) when is_binary(Bin) ->
+ strip_tail_whitespace(lists:reverse(binary:bin_to_list(Bin)));
+strip_tail_whitespace([Char|Rest])
+ when Char == $ ;
+ Char == $\t;
+ Char == $\v;
+ Char == $\f;
+ Char == $\r;
+ Char == $\n ->
+ strip_tail_whitespace(Rest);
+strip_tail_whitespace(List) ->
+ binary:list_to_bin(
+ lists:reverse(List)).
+
decode_pem_entry(Start, [<<"Proc-Type: 4,ENCRYPTED", _/binary>>, Line | Lines]) ->
Type = asn1_type(Start),
Cs = erlang:iolist_to_binary(Lines),
@@ -151,21 +167,6 @@ encode_encrypted_private_keyinfo(EncData, EncryptParmams) ->
public_key:der_encode('EncryptedPrivateKeyInfo',
#'EncryptedPrivateKeyInfo'{encryptionAlgorithm = AlgorithmInfo,
encryptedData = EncData}).
-split_bin(Bin) ->
- split_bin(0, Bin).
-
-split_bin(N, Bin) ->
- case Bin of
- <<Line:N/binary, "\r\n", Rest/binary>> ->
- [Line | split_bin(0, Rest)];
- <<Line:N/binary, "\n", Rest/binary>> ->
- [Line | split_bin(0, Rest)];
- <<Line:N/binary>> ->
- [Line];
- _ ->
- split_bin(N+1, Bin)
- end.
-
b64encode_and_split(Bin) ->
split_lines(base64:encode(Bin)).
diff --git a/lib/public_key/src/public_key.app.src b/lib/public_key/src/public_key.app.src
index 5833141e87..6be7cd3bc4 100644
--- a/lib/public_key/src/public_key.app.src
+++ b/lib/public_key/src/public_key.app.src
@@ -8,6 +8,7 @@
pubkey_cert,
pubkey_cert_records,
pubkey_crl,
+ pubkey_ocsp,
'OTP-PUB-KEY',
'PKCS-FRAME'
]},
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 996cf9db2c..8c8b5585a0 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -40,13 +40,15 @@
sign/3, sign/4, verify/4, verify/5,
generate_key/1,
compute_key/2, compute_key/3,
- pkix_sign/2, pkix_verify/2,
+ pkix_sign/2, pkix_verify/2,
+ pkix_hash_type/1,
pkix_sign_types/1,
pkix_is_self_signed/1,
pkix_is_fixed_dh_cert/1,
pkix_is_issuer/2,
pkix_issuer_id/2,
- pkix_normalize_name/1,
+ pkix_subject_id/1,
+ pkix_normalize_name/1,
pkix_path_validation/3,
pkix_verify_hostname/2, pkix_verify_hostname/3,
pkix_verify_hostname_match_fun/1,
@@ -61,18 +63,22 @@
pkix_crl_issuer/1,
short_name_hash/1,
pkix_test_data/1,
- pkix_test_root_cert/2
+ pkix_test_root_cert/2,
+ pkix_ocsp_validate/5,
+ ocsp_responder_id/1,
+ ocsp_extensions/1
]).
-export_type([public_key/0, private_key/0, pem_entry/0,
pki_asn1_type/0, asn1_type/0, ssh_file/0, der_encoded/0,
- key_params/0, digest_type/0, issuer_name/0, oid/0]).
-
--type public_key() :: rsa_public_key() | dsa_public_key() | ec_public_key() | ed_public_key() .
--type private_key() :: rsa_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() .
+ key_params/0, digest_type/0, issuer_name/0, cert_id/0, oid/0]).
+-type public_key() :: rsa_public_key() | rsa_pss_public_key() | dsa_public_key() | ec_public_key() | ed_public_key() .
+-type private_key() :: rsa_private_key() | rsa_pss_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() .
-type rsa_public_key() :: #'RSAPublicKey'{}.
--type rsa_private_key() :: #'RSAPrivateKey'{}.
+-type rsa_private_key() :: #'RSAPrivateKey'{}.
+-type rsa_pss_public_key() :: {#'RSAPublicKey'{}, #'RSASSA-PSS-params'{}}.
+-type rsa_pss_private_key() :: { #'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}.
-type dsa_private_key() :: #'DSAPrivateKey'{}.
-type dsa_public_key() :: {integer(), #'Dss-Parms'{}}.
-type ecpk_parameters() :: {ecParameters, #'ECParameters'{}} | {namedCurve, Oid::tuple()}.
@@ -121,8 +127,7 @@
-type oid() :: tuple().
-type chain_type() :: server_chain | client_chain.
--type issuer_id() :: {SerialNr::integer(), issuer_name()} .
-
+-type cert_id() :: {SerialNr::integer(), issuer_name()} .
-type issuer_name() :: {rdnSequence,[#'AttributeTypeAndValue'{}]} .
@@ -293,6 +298,12 @@ der_priv_key_decode({'PrivateKeyInfo', v1,
{'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption', _}, PrivKey, _}) ->
der_decode('RSAPrivateKey', PrivKey);
der_priv_key_decode({'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-RSASSA-PSS',
+ {asn1_OPENTYPE, Parameters}}, PrivKey, _}) ->
+ Key = der_decode('RSAPrivateKey', PrivKey),
+ Params = der_decode('RSASSA-PSS-params', Parameters),
+ {Key, Params};
+der_priv_key_decode({'PrivateKeyInfo', v1,
{'PrivateKeyInfo_privateKeyAlgorithm', ?'id-dsa', {asn1_OPENTYPE, Parameters}}, PrivKey, _}) ->
{params, #'Dss-Parms'{p=P, q=Q, g=G}} = der_decode('DSAParams', Parameters),
X = der_decode('Prime-p', PrivKey),
@@ -307,34 +318,40 @@ der_priv_key_decode(PKCS8Key) ->
%%
%% Description: Encodes a public key entity with asn1 DER encoding.
%%--------------------------------------------------------------------
-
der_encode('PrivateKeyInfo', #'DSAPrivateKey'{p=P, q=Q, g=G, x=X}) ->
der_encode('PrivateKeyInfo',
- {'PrivateKeyInfo', v1,
- {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-dsa',
- {asn1_OPENTYPE, der_encode('Dss-Parms', #'Dss-Parms'{p=P, q=Q, g=G})}},
+ {'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-dsa',
+ {asn1_OPENTYPE, der_encode('Dss-Parms', #'Dss-Parms'{p=P, q=Q, g=G})}},
der_encode('Prime-p', X), asn1_NOVALUE});
der_encode('PrivateKeyInfo', #'RSAPrivateKey'{} = PrivKey) ->
der_encode('PrivateKeyInfo',
- {'PrivateKeyInfo', v1,
- {'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption', {asn1_OPENTYPE, ?DER_NULL}},
- der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE});
+ {'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption',
+ {asn1_OPENTYPE, ?DER_NULL}},
+ der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE});
+der_encode('PrivateKeyInfo', {#'RSAPrivateKey'{} = PrivKey, Parameters}) ->
+ der_encode('PrivateKeyInfo',
+ {'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-RSASSA-PSS',
+ {asn1_OPENTYPE, der_encode('RSASSA-PSS-params', Parameters)}},
+ der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE});
der_encode('PrivateKeyInfo', #'ECPrivateKey'{parameters = Parameters} = PrivKey) ->
der_encode('PrivateKeyInfo',
- {'PrivateKeyInfo', v1,
+ {'PrivateKeyInfo', v1,
{'PrivateKeyInfo_privateKeyAlgorithm', ?'id-ecPublicKey',
- {asn1_OPENTYPE, der_encode('EcpkParameters', Parameters)}},
- der_encode('ECPrivateKey', PrivKey#'ECPrivateKey'{parameters = asn1_NOVALUE}), asn1_NOVALUE});
+ {asn1_OPENTYPE, der_encode('EcpkParameters', Parameters)}},
+ der_encode('ECPrivateKey', PrivKey#'ECPrivateKey'{parameters = asn1_NOVALUE}),
+ asn1_NOVALUE});
der_encode(Asn1Type, Entity) when (Asn1Type == 'PrivateKeyInfo') or
(Asn1Type == 'EncryptedPrivateKeyInfo') ->
try
- {ok, Encoded} = 'PKCS-FRAME':encode(Asn1Type, Entity),
- Encoded
- catch
+ {ok, Encoded} = 'PKCS-FRAME':encode(Asn1Type, Entity),
+ Encoded
+ catch
error:{badmatch, {error, _}} = Error ->
- erlang:error(Error)
- end;
-
+ erlang:error(Error)
+ end;
der_encode(Asn1Type, Entity) when is_atom(Asn1Type) ->
try
{ok, Encoded} = 'OTP-PUB-KEY':encode(Asn1Type, Entity),
@@ -625,6 +642,22 @@ pkix_sign_types(?'ecdsa-with-SHA512') ->
{sha512, ecdsa}.
%%--------------------------------------------------------------------
+-spec pkix_hash_type(HashOid::oid()) -> DigestType:: md5 | crypto:sha1() | crypto:sha2().
+
+pkix_hash_type(?'id-sha1') ->
+ sha;
+pkix_hash_type(?'id-sha512') ->
+ sha512;
+pkix_hash_type(?'id-sha384') ->
+ sha384;
+pkix_hash_type(?'id-sha256') ->
+ sha256;
+pkix_hash_type('id-sha224') ->
+ sha224;
+pkix_hash_type('id-md5') ->
+ md5.
+
+%%--------------------------------------------------------------------
%% Description: Create digital signature.
%%--------------------------------------------------------------------
-spec sign(Msg, DigestType, Key) ->
@@ -768,12 +801,11 @@ pkix_match_dist_point(#'CertificateList'{
%% der encoded 'Certificate'{}
%%--------------------------------------------------------------------
pkix_sign(#'OTPTBSCertificate'{signature =
- #'SignatureAlgorithm'{algorithm = Alg}
+ #'SignatureAlgorithm'{}
= SigAlg} = TBSCert, Key) ->
-
Msg = pkix_encode('OTPTBSCertificate', TBSCert, otp),
- {DigestType, _} = pkix_sign_types(Alg),
- Signature = sign(Msg, DigestType, Key),
+ {DigestType, _, Opts} = pubkey_cert:x509_pkix_sign_types(SigAlg),
+ Signature = sign(Msg, DigestType, format_pkix_sign_key(Key), Opts),
Cert = #'OTPCertificate'{tbsCertificate= TBSCert,
signatureAlgorithm = SigAlg,
signature = Signature
@@ -796,6 +828,11 @@ pkix_verify(DerCert, #'RSAPublicKey'{} = RSAKey)
{DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
verify(PlainText, DigestType, Signature, RSAKey);
+pkix_verify(DerCert, {#'RSAPublicKey'{} = RSAKey, #'RSASSA-PSS-params'{} = Params})
+ when is_binary(DerCert) ->
+ {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
+ verify(PlainText, DigestType, Signature, RSAKey, rsa_opts(Params));
+
pkix_verify(DerCert, Key = {#'ECPoint'{}, _})
when is_binary(DerCert) ->
{DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
@@ -871,7 +908,7 @@ pkix_is_fixed_dh_cert(Cert) when is_binary(Cert) ->
%%--------------------------------------------------------------------
-spec pkix_issuer_id(Cert, IssuedBy) ->
- {ok, issuer_id()} | {error, Reason}
+ {ok, ID::cert_id()} | {error, Reason}
when Cert::der_encoded()| #'OTPCertificate'{},
IssuedBy :: self | other,
Reason :: term() .
@@ -886,6 +923,19 @@ pkix_issuer_id(Cert, Signed) when is_binary(Cert) ->
pkix_issuer_id(OtpCert, Signed).
%%--------------------------------------------------------------------
+-spec pkix_subject_id(Cert) -> ID
+ when Cert::der_encoded()| #'OTPCertificate'{},
+ ID::cert_id() .
+
+%% Description: Returns the subject id.
+%%--------------------------------------------------------------------
+pkix_subject_id(#'OTPCertificate'{} = OtpCert) ->
+ pubkey_cert:subject_id(OtpCert);
+pkix_subject_id(Cert) when is_binary(Cert) ->
+ OtpCert = pkix_decode_cert(Cert, otp),
+ pkix_subject_id(OtpCert).
+
+%%--------------------------------------------------------------------
-spec pkix_crl_issuer(CRL| #'CertificateList'{}) ->
Issuer when CRL :: der_encoded(),
Issuer :: issuer_name() .
@@ -911,7 +961,7 @@ pkix_normalize_name(Issuer) ->
%%--------------------------------------------------------------------
-spec pkix_path_validation(Cert::binary()| #'OTPCertificate'{} | atom(),
- CertChain :: [binary()] ,
+ CertChain :: [binary() | #'OTPCertificate'{}] ,
Options :: [{atom(),term()}]) ->
{ok, {PublicKeyInfo :: term(),
PolicyTree :: term()}} |
@@ -928,8 +978,8 @@ pkix_path_validation(PathErr, [Cert | Chain], Options0) when is_atom(PathErr)->
Options = proplists:delete(verify_fun, Options0),
pkix_path_validation(Otpcert, Chain, [{verify_fun,
{VerifyFun, Userstate}}| Options]);
- {fail, _} ->
- {error, Reason}
+ {fail, UserReason} ->
+ {error, UserReason}
catch
_:_ ->
{error, Reason}
@@ -942,10 +992,19 @@ pkix_path_validation(TrustedCert, CertChain, Options)
pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options)
when is_list(CertChain), is_list(Options) ->
MaxPathDefault = length(CertChain),
- ValidationState = pubkey_cert:init_validation_state(TrustedCert,
- MaxPathDefault,
- Options),
- path_validation(CertChain, ValidationState).
+ {VerifyFun, Userstat0} =
+ proplists:get_value(verify_fun, Options, ?DEFAULT_VERIFYFUN),
+ try pubkey_cert:validate_time(TrustedCert, Userstat0, VerifyFun) of
+ Userstate1 ->
+ ValidationState = pubkey_cert:init_validation_state(TrustedCert,
+ MaxPathDefault,
+ [{verify_fun, {VerifyFun, Userstate1}} |
+ proplists:delete(verify_fun, Options)]),
+ path_validation(CertChain, ValidationState)
+ catch
+ throw:{bad_cert, cert_expired} = Reason ->
+ {error, Reason}
+ end.
%--------------------------------------------------------------------
-spec pkix_crls_validate(OTPcertificate, DPandCRLs, Options) ->
@@ -1227,6 +1286,47 @@ pkix_test_data(#{} = Chain) ->
pkix_test_root_cert(Name, Opts) ->
pubkey_cert:root_cert(Name, Opts).
+
+%%--------------------------------------------------------------------
+-spec pkix_ocsp_validate(Cert, IssuerCert, OcspRespDer,
+ ResponderCerts, NonceExt) -> valid | {bad_cert, Reason}
+ when Cert::der_encoded() | #'OTPCertificate'{},
+ IssuerCert::der_encoded() | #'OTPCertificate'{},
+ OcspRespDer::der_encoded(),
+ ResponderCerts::[der_encoded()],
+ NonceExt::undefined | binary(),
+ Reason::term().
+
+%% Description: Validate OCSP staple response
+%%--------------------------------------------------------------------
+pkix_ocsp_validate(DerCert, IssuerCert, OcspRespDer, ResponderCerts, NonceExt) when is_binary(DerCert) ->
+ pkix_ocsp_validate(pkix_decode_cert(DerCert, otp), IssuerCert, OcspRespDer, ResponderCerts, NonceExt);
+pkix_ocsp_validate(Cert, DerIssuerCert, OcspRespDer, ResponderCerts, NonceExt) when is_binary(DerIssuerCert) ->
+ pkix_ocsp_validate(Cert, pkix_decode_cert(DerIssuerCert, otp), OcspRespDer, ResponderCerts, NonceExt);
+pkix_ocsp_validate(Cert, IssuerCert, OcspRespDer, ResponderCerts, NonceExt) ->
+ case ocsp_responses(OcspRespDer, ResponderCerts, NonceExt) of
+ {ok, Responses} ->
+ ocsp_status(Cert, IssuerCert, Responses);
+ {error, Reason} ->
+ {bad_cert, {revocation_status_undetermined, Reason}}
+ end.
+
+%%--------------------------------------------------------------------
+-spec ocsp_extensions(undefined | binary()) -> list().
+%% Description: Get OCSP stapling extensions for request
+%%--------------------------------------------------------------------
+ocsp_extensions(Nonce) ->
+ [Extn || Extn <- [pubkey_ocsp:get_nonce_extn(Nonce),
+ pubkey_ocsp:get_acceptable_response_types_extn()],
+ erlang:is_record(Extn, 'Extension')].
+
+%%--------------------------------------------------------------------
+-spec ocsp_responder_id(#'Certificate'{}) -> binary().
+%%
+%% Description: Get the OCSP responder ID der
+%%--------------------------------------------------------------------
+ocsp_responder_id(Cert) ->
+ pubkey_ocsp:get_ocsp_responder_id(Cert).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -1257,7 +1357,11 @@ set_padding(Pad, Opts) ->
T =/= rsa_pad]
].
-
+format_pkix_sign_key({#'RSAPrivateKey'{} = Key, _}) ->
+ %% Params are handled in option arg
+ Key;
+format_pkix_sign_key(Key) ->
+ Key.
format_sign_key(Key = #'RSAPrivateKey'{}) ->
{rsa, format_rsa_private_key(Key)};
format_sign_key(#'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) ->
@@ -1289,6 +1393,15 @@ format_verify_key(#'DSAPrivateKey'{y=Y, p=P, q=Q, g=G}) ->
format_verify_key(_) ->
badarg.
+rsa_opts(#'RSASSA-PSS-params'{maskGenAlgorithm =
+ #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = HashAlgoOid}
+ }}) ->
+ HashAlgo = pkix_hash_type(HashAlgoOid),
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, -1},
+ {rsa_mgf1_md, HashAlgo}].
+
do_pem_entry_encode(Asn1Type, Entity, CipherInfo, Password) ->
Der = der_encode(Asn1Type, Entity),
DecryptDer = pubkey_pem:cipher(Der, CipherInfo, Password),
@@ -1340,16 +1453,16 @@ path_validation([Cert | _] = Path,
end.
validate(Cert, #path_validation_state{working_issuer_name = Issuer,
- working_public_key = Key,
- working_public_key_parameters =
- KeyParams,
- permitted_subtrees = Permit,
- excluded_subtrees = Exclude,
- last_cert = Last,
- user_state = UserState0,
- verify_fun = VerifyFun} =
+ working_public_key = Key,
+ working_public_key_parameters =
+ KeyParams,
+ permitted_subtrees = Permit,
+ excluded_subtrees = Exclude,
+ last_cert = Last,
+ user_state = UserState0,
+ verify_fun = VerifyFun} =
ValidationState0) ->
-
+
OtpCert = otp_cert(Cert),
{ValidationState1, UserState1} =
@@ -1711,8 +1824,8 @@ verify_hostname_match_default0(_, _) ->
verify_hostname_match_wildcard(FQDN, Name) ->
- [F1|Fs] = string:tokens(FQDN, "."),
- [N1|Ns] = string:tokens(Name, "."),
+ [F1|Fs] = string:tokens(to_lower_ascii(FQDN), "."),
+ [N1|Ns] = string:tokens(to_lower_ascii(Name), "."),
match_wild(F1,N1) andalso Fs==Ns.
@@ -1776,3 +1889,15 @@ format_details([]) ->
no_relevant_crls;
format_details(Details) ->
Details.
+
+ocsp_status(Cert, IssuerCert, Responses) ->
+ case pubkey_ocsp:find_single_response(Cert, IssuerCert, Responses) of
+ {ok, #'SingleResponse'{certStatus = CertStatus}} ->
+ pubkey_ocsp:ocsp_status(CertStatus);
+ {error, no_matched_response = Reason} ->
+ {bad_cert, {revocation_status_undetermined, Reason}}
+ end.
+
+ocsp_responses(OCSPResponseDer, ResponderCerts, Nonce) ->
+ pubkey_ocsp:verify_ocsp_response(OCSPResponseDer,
+ ResponderCerts, Nonce).
diff --git a/lib/public_key/test/erl_make_certs.erl b/lib/public_key/test/erl_make_certs.erl
index e772ea1734..4c0b60d543 100644
--- a/lib/public_key/test/erl_make_certs.erl
+++ b/lib/public_key/test/erl_make_certs.erl
@@ -23,8 +23,10 @@
-module(erl_make_certs).
-include_lib("public_key/include/public_key.hrl").
--export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]).
--compile(export_all).
+-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3,
+ gen_dsa/2, gen_ec/1,
+ pem_to_der/1, der_to_pem/2
+ ]).
%%--------------------------------------------------------------------
%% @doc Create and return a der encoded certificate
diff --git a/lib/public_key/test/pbe_SUITE.erl b/lib/public_key/test/pbe_SUITE.erl
index fb9a74e375..4745698293 100644
--- a/lib/public_key/test/pbe_SUITE.erl
+++ b/lib/public_key/test/pbe_SUITE.erl
@@ -23,8 +23,27 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ pbdkdf1/0,
+ pbdkdf1/1,
+ pbdkdf2/0,
+ pbdkdf2/1,
+ old_pbe/0,
+ old_pbe/1,
+ pbes1/0,
+ pbes1/1,
+ pbes2/0,
+ pbes2/1
+ ]).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -114,7 +133,7 @@ pbdkdf2(Config) when is_list(Config) ->
<<16#0c, 16#60, 16#c8, 16#0f, 16#96, 16#1f, 16#0e, 16#71,
16#f3, 16#a9, 16#b5, 16#24, 16#af, 16#60, 16#12, 16#06,
- 16#2f, 16#e0, 16#37, 16#a6>> = pubkey_pbe:pbdkdf2("password", "salt", 1, 20, fun crypto:hmac/4, sha, 20),
+ 16#2f, 16#e0, 16#37, 16#a6>> = pubkey_pbe:pbdkdf2("password", "salt", 1, 20, fun hmac4/4, sha, 20),
%% Input:
%% P = "password" (8 octets)
@@ -130,7 +149,7 @@ pbdkdf2(Config) when is_list(Config) ->
<<16#ea, 16#6c, 16#01, 16#4d, 16#c7, 16#2d, 16#6f, 16#8c,
16#cd, 16#1e, 16#d9, 16#2a, 16#ce, 16#1d, 16#41, 16#f0,
16#d8, 16#de, 16#89, 16#57>> =
- pubkey_pbe:pbdkdf2("password", "salt", 2, 20, fun crypto:hmac/4, sha, 20),
+ pubkey_pbe:pbdkdf2("password", "salt", 2, 20, fun hmac4/4, sha, 20),
%% Input:
%% P = "password" (8 octets)
@@ -145,7 +164,7 @@ pbdkdf2(Config) when is_list(Config) ->
<<16#4b, 16#00, 16#79, 16#01, 16#b7, 16#65, 16#48, 16#9a,
16#be, 16#ad, 16#49, 16#d9, 16#26, 16#f7, 16#21, 16#d0,
- 16#65, 16#a4, 16#29, 16#c1>> = pubkey_pbe:pbdkdf2("password", "salt", 4096, 20, fun crypto:hmac/4, sha, 20),
+ 16#65, 16#a4, 16#29, 16#c1>> = pubkey_pbe:pbdkdf2("password", "salt", 4096, 20, fun hmac4/4, sha, 20),
%% Input:
%% P = "password" (8 octets)
@@ -161,7 +180,7 @@ pbdkdf2(Config) when is_list(Config) ->
<<16#ee, 16#fe, 16#3d, 16#61, 16#cd, 16#4d, 16#a4, 16#e4,
16#e9, 16#94, 16#5b, 16#3d, 16#6b, 16#a2, 16#15, 16#8c,
- 16#26, 16#34, 16#e9, 16#84>> = pubkey_pbe:pbdkdf2("password", "salt", 16777216, 20, fun crypto:hmac/4, sha, 20),
+ 16#26, 16#34, 16#e9, 16#84>> = pubkey_pbe:pbdkdf2("password", "salt", 16777216, 20, fun hmac4/4, sha, 20),
%% Input:
%% P = "passwordPASSWORDpassword" (24 octets)
@@ -180,7 +199,7 @@ pbdkdf2(Config) when is_list(Config) ->
16#8b, 16#29, 16#1a, 16#96, 16#4c, 16#f2, 16#f0, 16#70,
16#38>>
= pubkey_pbe:pbdkdf2("passwordPASSWORDpassword",
- "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 25, fun crypto:hmac/4, sha, 20),
+ "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 25, fun hmac4/4, sha, 20),
%% Input:
%% P = "pass\0word" (9 octets)
@@ -195,7 +214,7 @@ pbdkdf2(Config) when is_list(Config) ->
<<16#56, 16#fa, 16#6a, 16#a7, 16#55, 16#48, 16#09, 16#9d,
16#cc, 16#37, 16#d7, 16#f0, 16#34, 16#25, 16#e0, 16#c3>>
= pubkey_pbe:pbdkdf2("pass\0word",
- "sa\0lt", 4096, 16, fun crypto:hmac/4, sha, 20).
+ "sa\0lt", 4096, 16, fun hmac4/4, sha, 20).
pbes1() ->
[{doc,"Tests encode/decode EncryptedPrivateKeyInfo encrypted with different ciphers using PBES1"}].
@@ -250,3 +269,6 @@ decode_encode_key_file(File, Password, Cipher, Config) ->
strip_ending_newlines(Bin) ->
string:strip(binary_to_list(Bin), right, 10).
+
+hmac4(SubType, Key, Data, MacLength) ->
+ crypto:macN(hmac, SubType, Key, Data, MacLength).
diff --git a/lib/public_key/test/pkits_SUITE.erl b/lib/public_key/test/pkits_SUITE.erl
index 487b3dbe3f..3b08dfc727 100644
--- a/lib/public_key/test/pkits_SUITE.erl
+++ b/lib/public_key/test/pkits_SUITE.erl
@@ -26,10 +26,164 @@
-include_lib("public_key/include/public_key.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ %% CT callbaks:
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2,
+
+ %% Test cases:
+ attrib_name_chain/1,
+ attrib_name_chain/0,
+ basic_invalid/1,
+ basic_invalid/0,
+ basic_valid/1,
+ basic_valid/0,
+ capitalization_name_chain/1,
+ capitalization_name_chain/0,
+ crl_signing_invalid/1,
+ crl_signing_invalid/0,
+ crl_signing_valid/1,
+ crl_signing_valid/0,
+ delta_without_crl/1,
+ delta_without_crl/0,
+ fresh_CRL/1,
+ fresh_CRL/0,
+ invalid_CRL/1,
+ invalid_CRL/0,
+ invalid_CRL_issuer/1,
+ invalid_CRL_issuer/0,
+ invalid_CRL_signature/1,
+ invalid_CRL_signature/0,
+ invalid_DN_and_rfc822_name_constraints/1,
+ invalid_DN_and_rfc822_name_constraints/0,
+ invalid_DN_name_constraints/1,
+ invalid_DN_name_constraints/0,
+ invalid_crl_issuer/1,
+ invalid_crl_issuer/0,
+ invalid_delta_crls/1,
+ invalid_delta_crls/0,
+ invalid_distribution_points/1,
+ invalid_distribution_points/0,
+ invalid_dns_name_constraints/1,
+ invalid_dns_name_constraints/0,
+ invalid_dsa_signature/1,
+ invalid_dsa_signature/0,
+ invalid_indirect_crl/1,
+ invalid_indirect_crl/0,
+ invalid_key_usage/1,
+ invalid_key_usage/0,
+ invalid_name_chain/1,
+ invalid_name_chain/0,
+ invalid_only_contains/1,
+ invalid_only_contains/0,
+ invalid_only_some_reasons/1,
+ invalid_only_some_reasons/0,
+ invalid_path_constraints/1,
+ invalid_path_constraints/0,
+ invalid_rfc822_name_constraints/1,
+ invalid_rfc822_name_constraints/0,
+ invalid_rsa_signature/1,
+ invalid_rsa_signature/0,
+ invalid_separate_keys/1,
+ invalid_separate_keys/0,
+ invalid_serial/1,
+ invalid_serial/0,
+ invalid_uri_name_constraints/1,
+ invalid_uri_name_constraints/0,
+ missing_CRL/1,
+ missing_CRL/0,
+ missing_basic_constraints/1,
+ missing_basic_constraints/0,
+ not_after_invalid/1,
+ not_after_invalid/0,
+ not_after_valid/1,
+ not_after_valid/0,
+ not_before_invalid/1,
+ not_before_invalid/0,
+ not_before_valid/1,
+ not_before_valid/0,
+ old_CRL/1,
+ old_CRL/0,
+ revoked_CA/1,
+ revoked_CA/0,
+ revoked_peer/1,
+ revoked_peer/0,
+ string_name_chain/1,
+ string_name_chain/0,
+ uid_name_chain/1,
+ uid_name_chain/0,
+ unknown_CRL_extension/1,
+ unknown_CRL_extension/0,
+ unknown_critical_extension/1,
+ unknown_critical_extension/0,
+ unknown_not_critical_extension/1,
+ unknown_not_critical_extension/0,
+ valid_CRL/1,
+ valid_CRL/0,
+ valid_DN_and_rfc822_name_constraints/1,
+ valid_DN_and_rfc822_name_constraints/0,
+ valid_DN_name_constraints/1,
+ valid_DN_name_constraints/0,
+ valid_basic_constraint/1,
+ valid_basic_constraint/0,
+ valid_crl_issuer/1,
+ valid_crl_issuer/0,
+ valid_delta_crls/1,
+ valid_delta_crls/0,
+ valid_distribution_points/1,
+ valid_distribution_points/0,
+ valid_distribution_points_no_issuing_distribution_point/1,
+ valid_distribution_points_no_issuing_distribution_point/0,
+ valid_dns_name_constraints/1,
+ valid_dns_name_constraints/0,
+ valid_dsa_signature/1,
+ valid_dsa_signature/0,
+ valid_indirect_crl/1,
+ valid_indirect_crl/0,
+ valid_key_usage/1,
+ valid_key_usage/0,
+ valid_only_contains/1,
+ valid_only_contains/0,
+ valid_only_some_reasons/1,
+ valid_only_some_reasons/0,
+ valid_path_constraints/1,
+ valid_path_constraints/0,
+ valid_rfc822_name_constraints/1,
+ valid_rfc822_name_constraints/0,
+ valid_rsa_signature/1,
+ valid_rsa_signature/0,
+ valid_seperate_keys/1,
+ valid_seperate_keys/0,
+ valid_serial/1,
+ valid_serial/0,
+ valid_uri_name_constraints/1,
+ valid_uri_name_constraints/0,
+ whitespace_name_chain/1,
+ whitespace_name_chain/0,
+
+ %% Marked as "Not supported yet":
+ certificate_policies/0,
+ certificate_policies/1,
+ require_explicit_policy/0,
+ require_explicit_policy/1,
+ policy_mappings/0,
+ policy_mappings/1,
+ inhibit_policy_mapping/0,
+ inhibit_policy_mapping/1,
+ inhibit_any_policy/0,
+ inhibit_any_policy/1
+ ]).
-define(error(Format,Args), error(Format,Args,?FILE,?LINE)).
+
+-export([warning/4]).
-define(warning(Format,Args), warning(Format,Args,?FILE,?LINE)).
-define(CERTS, "pkits/certs").
@@ -691,8 +845,7 @@ run({Chap, Test, Result}, TA) ->
{ok, _OK} when Result =/= ok ->
?error(" ~p ~p~n Expected ~p got ~p ~n", [Chap, Test, Result, ok]),
fail
- catch Type:Reason ->
- Stack = erlang:get_stacktrace(),
+ catch Type:Reason:Stack ->
io:format("Crash ~p:~p in ~p~n",[Type,Reason,Stack]),
io:format(" ~p ~p Expected ~p ~n", [Chap, Test, Result]),
exit(crash)
diff --git a/lib/public_key/test/pubkey_ocsp_SUITE.erl b/lib/public_key/test/pubkey_ocsp_SUITE.erl
new file mode 100644
index 0000000000..006741e81a
--- /dev/null
+++ b/lib/public_key/test/pubkey_ocsp_SUITE.erl
@@ -0,0 +1,376 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(pubkey_ocsp_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+all() ->
+ [ocsp_test].
+
+groups() ->
+ [].
+
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+ocsp_test() ->
+ [{doc, "Test functions in pubkey_ocsp"}].
+ocsp_test(Config) when is_list(Config) ->
+ %% Feeding data
+ SingleResponseGood =
+ [#'SingleResponse'{
+ certID = {'CertID',
+ {'AlgorithmIdentifier',{1,3,14,3,2,26},<<5,0>>},
+ <<227,147,252,182,155,101,129,45,194,162,
+ 22,93,127,46,112,193,196,28,241,232>>,
+ <<99,34,37,88,164,188,98,22,125,252,71,72,
+ 246,115,141,222,108,19,122,168>>,7},
+ certStatus = {good,'NULL'},
+ thisUpdate = "20200428083205Z",
+ nextUpdate = asn1_NOVALUE,
+ singleExtensions = asn1_NOVALUE}],
+
+ Nonce = <<226,210,104,247,153,233,71,246>>,
+
+ NonceExtension =
+ #'Extension'{
+ extnID = ?'id-pkix-ocsp-nonce',
+ extnValue = Nonce
+ },
+
+ OCSPResponseDer =
+ <<48,130,7,6,10,1,0,160,130,6,255,48,130,6,251,6,9,43,6,1,5,5,7,48,1,1,4,130,6,
+ 236,48,130,6,232,48,130,1,11,161,129,137,48,129,134,49,17,48,15,6,3,85,4,3,
+ 12,8,98,46,115,101,114,118,101,114,49,19,48,17,6,3,85,4,11,12,10,69,114,108,
+ 97,110,103,32,79,84,80,49,20,48,18,6,3,85,4,10,12,11,69,114,105,99,115,115,
+ 111,110,32,65,66,49,11,48,9,6,3,85,4,6,19,2,83,69,49,18,48,16,6,3,85,4,7,12,
+ 9,83,116,111,99,107,104,111,108,109,49,37,48,35,6,9,42,134,72,134,247,13,1,9,
+ 1,22,22,112,101,116,101,114,64,101,114,105,120,46,101,114,105,99,115,115,111,
+ 110,46,115,101,24,15,50,48,50,48,48,52,50,56,48,56,51,50,48,53,90,48,81,48,
+ 79,48,58,48,9,6,5,43,14,3,2,26,5,0,4,20,227,147,252,182,155,101,129,45,194,
+ 162,22,93,127,46,112,193,196,28,241,232,4,20,99,34,37,88,164,188,98,22,125,
+ 252,71,72,246,115,141,222,108,19,122,168,2,1,7,128,0,24,15,50,48,50,48,48,52,
+ 50,56,48,56,51,50,48,53,90,161,25,48,23,48,21,6,9,43,6,1,5,5,7,48,1,2,4,8,
+ 226,210,104,247,153,233,71,246,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,
+ 130,1,1,0,85,82,43,226,38,172,139,105,77,248,24,250,244,154,2,174,232,141,52,
+ 93,102,37,177,31,59,105,104,242,117,238,102,93,61,56,24,47,69,169,184,234,
+ 109,204,5,64,109,101,23,197,234,6,250,223,95,175,131,138,227,66,123,199,182,
+ 57,102,47,221,72,112,208,1,4,128,209,235,108,64,209,31,128,37,130,176,132,
+ 203,119,24,188,187,254,8,167,54,80,28,208,26,118,236,149,184,182,25,236,252,
+ 158,253,167,143,114,14,184,198,144,51,195,44,16,38,255,112,124,81,201,255,
+ 132,143,98,119,135,23,232,10,184,54,150,227,131,212,81,101,158,152,82,252,
+ 156,28,30,163,203,145,11,179,105,230,187,132,119,186,189,67,198,165,48,106,
+ 114,75,151,128,108,28,44,121,195,162,222,25,45,99,46,84,116,125,51,72,191,
+ 250,186,71,78,21,222,219,232,143,233,226,56,163,23,51,170,69,152,223,0,63,8,
+ 236,219,175,18,165,88,166,125,71,31,53,40,12,133,64,250,30,190,113,10,187,38,
+ 171,17,210,170,126,198,232,195,224,228,1,246,75,140,139,121,229,17,153,115,
+ 199,68,227,171,176,163,117,171,160,130,4,193,48,130,4,189,48,130,4,185,48,
+ 130,3,161,160,3,2,1,2,2,1,9,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,48,129,
+ 131,49,14,48,12,6,3,85,4,3,12,5,111,116,112,67,65,49,19,48,17,6,3,85,4,11,12,
+ 10,69,114,108,97,110,103,32,79,84,80,49,20,48,18,6,3,85,4,10,12,11,69,114,
+ 105,99,115,115,111,110,32,65,66,49,11,48,9,6,3,85,4,6,19,2,83,69,49,18,48,16,
+ 6,3,85,4,7,12,9,83,116,111,99,107,104,111,108,109,49,37,48,35,6,9,42,134,72,
+ 134,247,13,1,9,1,22,22,112,101,116,101,114,64,101,114,105,120,46,101,114,105,
+ 99,115,115,111,110,46,115,101,48,30,23,13,50,48,48,52,50,56,48,56,51,50,48,
+ 53,90,23,13,51,48,48,51,48,55,48,56,51,50,48,53,90,48,129,134,49,17,48,15,6,
+ 3,85,4,3,12,8,98,46,115,101,114,118,101,114,49,19,48,17,6,3,85,4,11,12,10,69,
+ 114,108,97,110,103,32,79,84,80,49,20,48,18,6,3,85,4,10,12,11,69,114,105,99,
+ 115,115,111,110,32,65,66,49,11,48,9,6,3,85,4,6,19,2,83,69,49,18,48,16,6,3,85,
+ 4,7,12,9,83,116,111,99,107,104,111,108,109,49,37,48,35,6,9,42,134,72,134,247,
+ 13,1,9,1,22,22,112,101,116,101,114,64,101,114,105,120,46,101,114,105,99,115,
+ 115,111,110,46,115,101,48,130,1,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,
+ 3,130,1,15,0,48,130,1,10,2,130,1,1,0,188,248,42,161,172,252,200,52,180,217,
+ 145,59,193,72,33,176,213,106,37,81,119,251,205,254,70,196,171,127,79,157,147,
+ 235,14,61,25,162,207,134,25,239,35,62,57,10,214,115,231,71,203,226,198,73,
+ 223,222,199,165,82,67,33,78,176,116,241,192,97,169,143,164,219,152,40,115,
+ 229,242,128,97,98,183,217,199,35,127,146,94,20,115,0,250,200,39,9,255,230,
+ 216,80,140,6,133,251,39,96,240,176,184,34,1,134,247,126,237,255,130,170,98,
+ 242,140,104,105,95,48,75,115,135,229,89,191,180,179,123,198,232,228,220,249,
+ 113,86,186,212,176,194,66,14,164,236,219,138,254,80,57,118,232,163,192,94,78,
+ 224,100,124,206,199,81,105,54,222,26,245,170,147,184,192,237,77,143,154,180,
+ 79,42,107,75,77,81,215,19,75,8,160,106,199,196,66,53,16,233,184,175,85,167,
+ 148,12,232,248,113,61,89,14,156,199,128,83,40,214,228,83,9,36,72,188,25,29,
+ 47,172,78,114,191,120,240,227,234,255,194,61,132,57,1,141,131,227,64,152,209,
+ 205,63,24,172,223,194,254,97,133,255,192,133,148,237,178,115,2,3,1,0,1,163,
+ 130,1,49,48,130,1,45,48,9,6,3,85,29,19,4,2,48,0,48,11,6,3,85,29,15,4,4,3,2,5,
+ 224,48,29,6,3,85,29,14,4,22,4,20,63,72,140,0,84,13,114,48,50,31,9,241,231,
+ 177,20,184,8,114,244,29,48,129,179,6,3,85,29,35,4,129,171,48,129,168,128,20,
+ 99,34,37,88,164,188,98,22,125,252,71,72,246,115,141,222,108,19,122,168,161,
+ 129,140,164,129,137,48,129,134,49,17,48,15,6,3,85,4,3,12,8,101,114,108,97,
+ 110,103,67,65,49,19,48,17,6,3,85,4,11,12,10,69,114,108,97,110,103,32,79,84,
+ 80,49,20,48,18,6,3,85,4,10,12,11,69,114,105,99,115,115,111,110,32,65,66,49,
+ 18,48,16,6,3,85,4,7,12,9,83,116,111,99,107,104,111,108,109,49,11,48,9,6,3,85,
+ 4,6,19,2,83,69,49,37,48,35,6,9,42,134,72,134,247,13,1,9,1,22,22,112,101,116,
+ 101,114,64,101,114,105,120,46,101,114,105,99,115,115,111,110,46,115,101,130,
+ 1,1,48,27,6,3,85,29,17,4,20,48,18,130,16,104,111,115,116,46,101,120,97,109,
+ 112,108,101,46,99,111,109,48,33,6,3,85,29,18,4,26,48,24,129,22,112,101,116,
+ 101,114,64,101,114,105,120,46,101,114,105,99,115,115,111,110,46,115,101,48,
+ 13,6,9,42,134,72,134,247,13,1,1,5,5,0,3,130,1,1,0,86,112,29,225,102,143,193,
+ 55,126,115,187,208,118,153,111,177,160,121,55,33,184,60,27,111,40,7,93,241,9,
+ 226,40,125,181,36,173,116,190,43,187,52,254,50,229,222,56,215,132,67,217,174,
+ 121,24,94,240,163,56,12,36,212,2,40,94,102,126,206,52,40,32,218,59,86,166,
+ 238,137,144,90,57,211,141,81,32,102,215,180,59,133,125,208,199,166,81,35,49,
+ 24,88,100,127,90,145,237,150,249,227,123,120,98,230,12,106,72,201,127,54,94,
+ 164,204,23,158,3,230,232,181,95,251,98,6,28,115,46,153,241,233,254,152,176,
+ 114,12,148,24,234,185,204,177,189,70,14,73,181,232,245,63,226,14,138,249,101,
+ 56,222,188,78,127,191,174,232,182,207,67,162,111,248,192,202,65,96,237,206,
+ 52,220,63,50,108,82,185,169,29,148,30,75,74,16,156,229,166,96,102,214,145,77,
+ 225,218,180,54,109,61,62,119,144,231,72,105,61,201,245,219,192,63,160,242,
+ 247,112,64,199,65,248,252,59,145,150,212,151,166,223,237,121,135,13,122,111,
+ 22,117,115,166,64,143,10,40,13,5,240,22,38,235,32,107,194,41>>,
+
+ MalOCSPResponseDer =
+ <<48,130,7,6,10,1,0,160,130,6,255,48,130,6,251,6,9,43,6,1,5,5,7,48,1,1,4,130,6,
+ 236,48,130,6,232,48,130,1,11,161,129,137,48,129,134,49,17,48,15,6,3,85,4,3,
+ 12,8,98,46,115,101,114,118,101,114,49,19,48,17,6,3,85,4,11,12,10,69,114,108,
+ 97,110,103,32,79,84,80,49,20,48,18,6,3,85,4,10,12,11,69,114,105,99,115,115,
+ 111,110,32,65,66,49,11,48,9,6,3,85,4,6,19,2,83,69,49,18,48,16,6,3,85,4,7,12,
+ 9,83,116,111,99,107,104,111,108,109,49,37,48,35,6,9,42,134,72,134,247,13,1,9,
+ 1,22,22,112,101,116,101,114,64,101,114,105,120,46,101,114,105,99,115,115,111,
+ 110,46,115,101,24,15,50,48,50,48,48,52,50,56,48,56,51,50,48,53,90,48,81,48,
+ 79,48,58,48,9,6,5,43,14,3,2,26,5,0,4,20,227,147,252,182,155,101,129,45,194,
+ 162,22,93,127,46,112,193,196,28,241,232,4,20,99,34,37,88,164,188,98,22,125,
+ 252,71,72,246,115,141,222,108,19,122,168,2,1,7,128,0,24,15,50,48,50,48,48,52,
+ 50,56,48,56,51,50,48,53,90,161,25,48,23,48,21,6,9,43,6,1,5,5,7,48,1,2,4,8,
+ 226,210,104,247,153,233,71,246,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,
+ 130,1,1,0,85,82,43,226,38,172,139,105,77,248,24,250,244,154,2,174,232,141,52,
+ 93,102,37,177,31,59,105,104,242,117,238,102,93,61,56,24,47,69,169,184,234,
+ 109,204,5,64,109,101,23,197,234,6,250,223,95,175,131,138,227,66,123,199,182,
+ 57,102,47,221,72,112,208,1,4,128,209,235,108,64,209,31,128,37,130,176,132,
+ 203,119,24,188,187,254,8,167,54,80,28,208,26,118,236,149,184,182,25,236,252,
+ 158,253,167,143,114,14,184,198,168,56,195,44,16,38,255,112,124,81,201,255,
+ 132,143,98,119,135,23,232,10,184,54,150,227,131,212,81,101,158,152,82,252,
+ 156,28,30,163,203,145,11,179,105,230,187,132,119,186,189,67,198,165,48,106,
+ 114,75,151,128,108,28,44,121,195,162,222,25,45,99,46,84,116,125,51,72,191,
+ 250,186,71,78,21,222,219,232,143,233,226,56,163,23,51,170,69,152,223,0,63,8,
+ 236,219,175,18,165,88,166,125,71,31,53,40,12,133,64,250,30,190,113,10,187,38,
+ 171,17,210,170,126,198,232,195,224,228,1,246,75,140,139,121,229,17,153,115,
+ 199,68,227,171,176,163,117,171,160,130,4,193,48,130,4,189,48,130,4,185,48,
+ 130,3,161,160,3,2,1,2,2,1,9,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,48,129,
+ 131,49,14,48,12,6,3,85,4,3,12,5,111,116,112,67,65,49,19,48,17,6,3,85,4,11,12,
+ 10,69,114,108,97,110,103,32,79,84,80,49,20,48,18,6,3,85,4,10,12,11,69,114,
+ 105,99,115,115,111,110,32,65,66,49,11,48,9,6,3,85,4,6,19,2,83,69,49,18,48,16,
+ 6,3,85,4,7,12,9,83,116,111,99,107,104,111,108,109,49,37,48,35,6,9,42,134,72,
+ 134,247,13,1,9,1,22,22,112,101,116,101,114,64,101,114,105,120,46,101,114,105,
+ 99,115,115,111,110,46,115,101,48,30,23,13,50,48,48,52,50,56,48,56,51,50,48,
+ 53,90,23,13,51,48,48,51,48,55,48,56,51,50,48,53,90,48,129,134,49,17,48,15,6,
+ 3,85,4,3,12,8,98,46,115,101,114,118,101,114,49,19,48,17,6,3,85,4,11,12,10,69,
+ 114,108,97,110,103,32,79,84,80,49,20,48,18,6,3,85,4,10,12,11,69,114,105,99,
+ 115,115,111,110,32,65,66,49,11,48,9,6,3,85,4,6,19,2,83,69,49,18,48,16,6,3,85,
+ 4,7,12,9,83,116,111,99,107,104,111,108,109,49,37,48,35,6,9,42,134,72,134,247,
+ 13,1,9,1,22,22,112,101,116,101,114,64,101,114,105,120,46,101,114,105,99,115,
+ 115,111,110,46,115,101,48,130,1,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,
+ 3,130,1,15,0,48,130,1,10,2,130,1,1,0,188,248,42,161,172,252,200,52,180,217,
+ 145,59,193,72,33,176,213,106,37,81,119,251,205,254,70,196,171,127,79,157,147,
+ 235,14,61,25,162,207,134,25,239,35,62,57,10,214,115,231,71,203,226,198,73,
+ 223,222,199,165,82,67,33,78,176,116,241,192,97,169,143,164,219,152,40,115,
+ 229,242,128,97,98,183,217,199,35,127,146,94,20,115,0,250,200,39,9,255,230,
+ 216,80,140,6,133,251,39,96,240,176,184,34,1,134,247,126,237,255,130,170,98,
+ 242,140,104,105,95,48,75,115,135,229,89,191,180,179,123,198,232,228,220,249,
+ 113,86,186,212,176,194,66,14,164,236,219,138,254,80,57,118,232,163,192,94,78,
+ 224,100,124,206,199,81,105,54,222,26,245,170,147,184,192,237,77,143,154,180,
+ 79,42,107,75,77,81,215,19,75,8,160,106,199,196,66,53,16,233,184,175,85,167,
+ 148,12,232,248,113,61,89,14,156,199,128,83,40,214,228,83,9,36,72,188,25,29,
+ 47,172,78,114,191,120,240,227,234,255,194,61,132,57,1,141,131,227,64,152,209,
+ 205,63,24,172,223,194,254,97,133,255,192,133,148,237,178,115,2,3,1,0,1,163,
+ 130,1,49,48,130,1,45,48,9,6,3,85,29,19,4,2,48,0,48,11,6,3,85,29,15,4,4,3,2,5,
+ 224,48,29,6,3,85,29,14,4,22,4,20,63,72,140,0,84,13,114,48,50,31,9,241,231,
+ 177,20,184,8,114,244,29,48,129,179,6,3,85,29,35,4,129,171,48,129,168,128,20,
+ 99,34,37,88,164,188,98,22,125,252,71,72,246,115,141,222,108,19,122,168,161,
+ 129,140,164,129,137,48,129,134,49,17,48,15,6,3,85,4,3,12,8,101,114,108,97,
+ 110,103,67,65,49,19,48,17,6,3,85,4,11,12,10,69,114,108,97,110,103,32,79,84,
+ 80,49,20,48,18,6,3,85,4,10,12,11,69,114,105,99,115,115,111,110,32,65,66,49,
+ 18,48,16,6,3,85,4,7,12,9,83,116,111,99,107,104,111,108,109,49,11,48,9,6,3,85,
+ 4,6,19,2,83,69,49,37,48,35,6,9,42,134,72,134,247,13,1,9,1,22,22,112,101,116,
+ 101,114,64,101,114,105,120,46,101,114,105,99,115,115,111,110,46,115,101,130,
+ 1,1,48,27,6,3,85,29,17,4,20,48,18,130,16,104,111,115,116,46,101,120,97,109,
+ 112,108,101,46,99,111,109,48,33,6,3,85,29,18,4,26,48,24,129,22,112,101,116,
+ 101,114,64,101,114,105,120,46,101,114,105,99,115,115,111,110,46,115,101,48,
+ 13,6,9,42,134,72,134,247,13,1,1,5,5,0,3,130,1,1,0,86,112,29,225,102,143,193,
+ 55,126,115,187,208,118,153,111,177,160,121,55,33,184,60,27,111,40,7,93,241,9,
+ 226,40,125,181,36,173,116,190,43,187,52,254,50,229,222,56,215,132,67,217,174,
+ 121,24,94,240,163,56,12,36,212,2,40,94,102,126,206,52,40,32,218,59,86,166,
+ 238,137,144,90,57,211,141,81,32,102,215,180,59,133,125,208,199,166,81,35,49,
+ 24,88,100,127,90,145,237,150,249,227,123,120,98,230,12,106,72,201,127,54,94,
+ 164,204,23,158,3,230,232,181,95,251,98,6,28,115,46,153,241,233,254,152,176,
+ 114,12,148,24,234,185,204,177,189,70,14,73,181,232,245,63,226,14,138,249,101,
+ 56,222,188,78,127,191,174,232,182,207,67,162,111,248,192,202,65,96,237,206,
+ 52,220,63,50,108,82,185,169,29,148,30,75,74,16,156,229,166,96,102,214,145,77,
+ 225,218,180,54,109,61,62,119,144,231,72,105,61,201,245,219,192,63,160,242,
+ 247,112,64,199,65,248,252,59,145,150,212,151,166,223,237,121,135,13,122,111,
+ 22,117,115,166,64,143,10,40,13,5,240,22,38,235,32,107,194,41>>,
+
+ ResponderIDer =
+ <<161,129,135,48,129,132,49,17,48,15,6,3,85,4,3,12,8,98,46,
+ 115,101,114,118,101,114,49,18,48,16,6,3,85,4,11,12,10,69,
+ 114,108,97,110,103,79,84,80,49,19,48,17,6,3,85,4,10,12,11,
+ 69,114,105,99,115,115,111,110,65,66,49,11,48,9,6,3,85,4,6,
+ 19,2,83,69,49,18,48,16,6,3,85,4,7,12,9,83,116,111,99,107,
+ 104,111,108,109,49,37,48,35,6,9,42,134,72,134,247,13,1,9,1,
+ 22,22,112,101,116,101,114,64,101,114,105,120,46,101,114,105,
+ 99,115,115,111,110,46,115,101>>,
+
+ Cert =
+ #'Certificate'{
+ tbsCertificate = #'TBSCertificate'{
+ version = v3,
+ serialNumber = 9,
+ signature = {'AlgorithmIdentifier',{1,2,840,113549,1,1,5},<<5,0>>},
+ issuer = {rdnSequence,
+ [[{'AttributeTypeAndValue',{2,5,4,3},<<12,5,111,116,112,67,65>>}],
+ [{'AttributeTypeAndValue',{2,5,4,11},<<"\f\nErlangOTP">>}],
+ [{'AttributeTypeAndValue',{2,5,4,10},<<"\f\vEricssonAB">>}],
+ [{'AttributeTypeAndValue',{2,5,4,6},<<19,2,83,69>>}],
+ [{'AttributeTypeAndValue',{2,5,4,7},<<"\f\tStockholm">>}],
+ [{'AttributeTypeAndValue',
+ {1,2,840,113549,1,9,1},
+ <<22,22,112,101,116,101,114,64,101,114,105,120,46,101,
+ 114,105,99,115,115,111,110,46,115,101>>}]]},
+ validity = {'Validity',{utcTime,"200428083205Z"},{utcTime,"300307083205Z"}},
+ subject = {rdnSequence,
+ [[{'AttributeTypeAndValue',{2,5,4,3},<<"\f\bb.server">>}],
+ [{'AttributeTypeAndValue',{2,5,4,11},<<"\f\nErlangOTP">>}],
+ [{'AttributeTypeAndValue',{2,5,4,10},<<"\f\vEricssonAB">>}],
+ [{'AttributeTypeAndValue',{2,5,4,6},<<19,2,83,69>>}],
+ [{'AttributeTypeAndValue',{2,5,4,7},<<"\f\tStockholm">>}],
+ [{'AttributeTypeAndValue',
+ {1,2,840,113549,1,9,1},
+ <<22,22,112,101,116,101,114,64,101,114,105,120,46,101,
+ 114,105,99,115,115,111,110,46,115,101>>}]]},
+ subjectPublicKeyInfo = {'SubjectPublicKeyInfo',
+ {'AlgorithmIdentifier',{1,2,840,113549,1,1,1},<<5,0>>},
+ <<48,130,1,10,2,130,1,1,0,188,248,42,161,172,252,200,52,180,217,
+ 145,59,193,72,33,176,213,106,37,81,119,251,205,254,70,196,171,
+ 127,79,157,147,235,14,61,25,162,207,134,25,239,35,62,57,10,214,
+ 115,231,71,203,226,198,73,223,222,199,165,82,67,33,78,176,116,
+ 241,192,97,169,143,164,219,152,40,115,229,242,128,97,98,183,217,
+ 199,35,127,146,94,20,115,0,250,200,39,9,255,230,216,80,140,6,
+ 133,251,39,96,240,176,184,34,1,134,247,126,237,255,130,170,98,
+ 242,140,104,105,95,48,75,115,135,229,89,191,180,179,123,198,232,
+ 228,220,249,113,86,186,212,176,194,66,14,164,236,219,138,254,80,
+ 57,118,232,163,192,94,78,224,100,124,206,199,81,105,54,222,26,
+ 245,170,147,184,192,237,77,143,154,180,79,42,107,75,77,81,215,
+ 19,75,8,160,106,199,196,66,53,16,233,184,175,85,167,148,12,232,
+ 248,113,61,89,14,156,199,128,83,40,214,228,83,9,36,72,188,25,29,
+ 47,172,78,114,191,120,240,227,234,255,194,61,132,57,1,141,131,
+ 227,64,152,209,205,63,24,172,223,194,254,97,133,255,192,133,148,
+ 237,178,115,2,3,1,0,1>>},
+ issuerUniqueID = asn1_NOVALUE,
+ subjectUniqueID = asn1_NOVALUE,
+ extensions = [{'Extension',{2,5,29,19},false,<<48,0>>},
+ {'Extension',{2,5,29,15},false,<<3,2,5,224>>},
+ {'Extension',
+ {2,5,29,14},
+ false,
+ <<4,20,63,72,140,0,84,13,114,48,50,31,9,241,231,177,20,184,8,114,
+ 244,29>>},
+ {'Extension',
+ {2,5,29,35},
+ false,
+ <<48,129,168,128,20,99,34,37,88,164,188,98,22,125,252,71,72,246,
+ 115,141,222,108,19,122,168,161,129,140,164,129,137,48,129,134,
+ 49,17,48,15,6,3,85,4,3,12,8,101,114,108,97,110,103,67,65,49,19,
+ 48,17,6,3,85,4,11,12,10,69,114,108,97,110,103,32,79,84,80,49,
+ 20,48,18,6,3,85,4,10,12,11,69,114,105,99,115,115,111,110,32,65,
+ 66,49,18,48,16,6,3,85,4,7,12,9,83,116,111,99,107,104,111,108,
+ 109,49,11,48,9,6,3,85,4,6,19,2,83,69,49,37,48,35,6,9,42,134,72,
+ 134,247,13,1,9,1,22,22,112,101,116,101,114,64,101,114,105,120,
+ 46,101,114,105,99,115,115,111,110,46,115,101,130,1,1>>},
+ {'Extension',
+ {2,5,29,17},
+ false,
+ <<48,18,130,16,104,111,115,116,46,101,120,97,109,112,108,101,46,
+ 99,111,109>>},
+ {'Extension',
+ {2,5,29,18},
+ false,
+ <<48,24,129,22,112,101,116,101,114,64,101,114,105,120,46,101,
+ 114,105,99,115,115,111,110,46,115,101>>}]},
+ signatureAlgorithm = {'AlgorithmIdentifier',{1,2,840,113549,1,1,5},<<5,0>>},
+ signature = <<86,112,29,225,102,143,193,55,126,115,187,208,118,153,111,177,160,121,55,
+ 33,184,60,27,111,40,7,93,241,9,226,40,125,181,36,173,116,190,43,187,52,
+ 254,50,229,222,56,215,132,67,217,174,121,24,94,240,163,56,12,36,212,2,
+ 40,94,102,126,206,52,40,32,218,59,86,166,238,137,144,90,57,211,141,81,
+ 32,102,215,180,59,133,125,208,199,166,81,35,49,24,88,100,127,90,145,237,
+ 150,249,227,123,120,98,230,12,106,72,201,127,54,94,164,204,23,158,3,230,
+ 232,181,95,251,98,6,28,115,46,153,241,233,254,152,176,114,12,148,24,234,
+ 185,204,177,189,70,14,73,181,232,245,63,226,14,138,249,101,56,222,188,
+ 78,127,191,174,232,182,207,67,162,111,248,192,202,65,96,237,206,52,220,
+ 63,50,108,82,185,169,29,148,30,75,74,16,156,229,166,96,102,214,145,77,
+ 225,218,180,54,109,61,62,119,144,231,72,105,61,201,245,219,192,63,160,
+ 242,247,112,64,199,65,248,252,59,145,150,212,151,166,223,237,121,135,13,
+ 122,111,22,117,115,166,64,143,10,40,13,5,240,22,38,235,32,107,194,41>>},
+
+ %% test of the exported functions
+ ct:pal("Check pubkey_ocsp:verify_ocsp_response/3~n"),
+ {ok, SingleResponseGood} =
+ pubkey_ocsp:verify_ocsp_response(OCSPResponseDer, [Cert], Nonce),
+ {error, ocsp_response_bad_signature} =
+ pubkey_ocsp:verify_ocsp_response(MalOCSPResponseDer, [Cert], Nonce),
+ {error, nonce_mismatch} =
+ pubkey_ocsp:verify_ocsp_response(OCSPResponseDer, [Cert], <<1,2,3>>),
+ ct:pal("pubkey_ocsp:verify_ocsp_response/3...ok~n"),
+
+ ct:pal("Check pubkey_ocsp:decode_ocsp_response/1~n"),
+ {ok, #'BasicOCSPResponse'{}} =
+ pubkey_ocsp:decode_ocsp_response(OCSPResponseDer),
+ ct:pal("pubkey_ocsp:decode_ocsp_response/1...ok~n"),
+
+ ct:pal("Check pubkey_ocsp:get_ocsp_responder_id/1~n"),
+ ResponderIDer =
+ pubkey_ocsp:get_ocsp_responder_id(Cert),
+ ct:pal("pubkey_ocsp:get_ocsp_responder_id/1...ok~n"),
+
+ ct:pal("Check pubkey_ocsp:get_nonce_extn/1~n"),
+ undefined =
+ pubkey_ocsp:get_nonce_extn(undefined),
+ NonceExtension =
+ pubkey_ocsp:get_nonce_extn(Nonce),
+ ct:pal("pubkey_ocsp:get_nonce_extn/1...ok~n"). \ No newline at end of file
diff --git a/lib/public_key/test/pubkey_ssh_SUITE.erl b/lib/public_key/test/pubkey_ssh_SUITE.erl
index afa3741346..effaece8f2 100644
--- a/lib/public_key/test/pubkey_ssh_SUITE.erl
+++ b/lib/public_key/test/pubkey_ssh_SUITE.erl
@@ -24,8 +24,39 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2,
+
+ ssh_rsa_public_key/1,
+ ssh_dsa_public_key/1,
+ ssh_ecdsa_public_key/1,
+ ssh_rfc4716_rsa_comment/1,
+ ssh_rfc4716_dsa_comment/1,
+ ssh_rfc4716_rsa_subject/1,
+ ssh_known_hosts/1,
+ ssh1_known_hosts/1,
+ ssh_auth_keys/1,
+ ssh1_auth_keys/1,
+ ssh_openssh_public_key_with_comment/1,
+ ssh_openssh_public_key_long_header/1,
+
+ ssh_hostkey_fingerprint_md5_implicit/1,
+ ssh_hostkey_fingerprint_md5/1,
+ ssh_hostkey_fingerprint_sha/1,
+ ssh_hostkey_fingerprint_sha256/1,
+ ssh_hostkey_fingerprint_sha384/1,
+ ssh_hostkey_fingerprint_sha512/1,
+ ssh_hostkey_fingerprint_list/1
+ ]).
+
-define(TIMEOUT, 120000). % 2 min
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index 97a1f14de9..cf3b5ea0c8 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -24,8 +24,97 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ init_common_per_testcase/1,
+ end_per_testcase/2,
+ app/0,
+ app/1,
+ appup/0,
+ appup/1,
+ dsa_pem/0,
+ dsa_pem/1,
+ dsa_priv_pkcs8/0,
+ dsa_priv_pkcs8/1,
+ rsa_pem/0,
+ rsa_pem/1,
+ rsa_pss_pss_pem/0,
+ rsa_pss_pss_pem/1,
+ rsa_priv_pkcs8/0,
+ rsa_priv_pkcs8/1,
+ ec_pem/0,
+ ec_pem/1,
+ ec_pem2/0,
+ ec_pem2/1,
+ ec_priv_pkcs8/0,
+ ec_priv_pkcs8/1,
+ init_ec_pem_encode_generated/1,
+ ec_pem_encode_generated/0,
+ ec_pem_encode_generated/1,
+ encrypted_pem/0,
+ encrypted_pem/1,
+ dh_pem/0,
+ dh_pem/1,
+ pkcs10_pem/0,
+ pkcs10_pem/1,
+ pkcs7_pem/0,
+ pkcs7_pem/1,
+ cert_pem/0,
+ cert_pem/1,
+ encrypt_decrypt/0,
+ encrypt_decrypt/1,
+ rsa_sign_verify/0,
+ rsa_sign_verify/1,
+ rsa_pss_sign_verify/0,
+ rsa_pss_sign_verify/1,
+ dsa_sign_verify/0,
+ dsa_sign_verify/1,
+ pkix/0,
+ pkix/1,
+ pkix_countryname/0,
+ pkix_countryname/1,
+ pkix_emailaddress/0,
+ pkix_emailaddress/1,
+ pkix_path_validation/0,
+ pkix_path_validation/1,
+ pkix_path_validation_root_expired/0,
+ pkix_path_validation_root_expired/1,
+ pkix_verify_hostname_cn/1,
+ pkix_verify_hostname_subjAltName/1,
+ pkix_verify_hostname_options/1,
+ pkix_verify_hostname_subjAltName_IP/1,
+ pkix_iso_rsa_oid/0,
+ pkix_iso_rsa_oid/1,
+ pkix_iso_dsa_oid/0,
+ pkix_iso_dsa_oid/1,
+ pkix_dsa_sha2_oid/0,
+ pkix_dsa_sha2_oid/1,
+ pkix_crl/0,
+ pkix_crl/1,
+ general_name/0,
+ general_name/1,
+ pkix_hash_type/0,
+ pkix_hash_type/1,
+ pkix_test_data_all_default/0,
+ pkix_test_data_all_default/1,
+ pkix_test_data/0,
+ pkix_test_data/1,
+ short_cert_issuer_hash/0,
+ short_cert_issuer_hash/1,
+ short_crl_issuer_hash/0,
+ short_crl_issuer_hash/1,
+ gen_ec_param_prime_field/0,
+ gen_ec_param_prime_field/1,
+ gen_ec_param_char_2_field/0,
+ gen_ec_param_char_2_field/1
+ ]).
-define(TIMEOUT, 120000). % 2 min
@@ -38,31 +127,40 @@ suite() ->
[].
all() ->
- [app, appup,
+ [app,
+ appup,
{group, pem_decode_encode},
encrypt_decrypt,
{group, sign_verify},
- pkix, pkix_countryname, pkix_emailaddress, pkix_path_validation,
- pkix_iso_rsa_oid, pkix_iso_dsa_oid,
+ pkix,
+ pkix_countryname,
+ pkix_emailaddress,
+ pkix_path_validation,
+ pkix_path_validation_root_expired,
+ pkix_iso_rsa_oid,
+ pkix_iso_dsa_oid,
pkix_dsa_sha2_oid,
- pkix_crl, general_name,
+ pkix_crl,
+ pkix_hash_type,
+ general_name,
pkix_verify_hostname_cn,
pkix_verify_hostname_subjAltName,
pkix_verify_hostname_subjAltName_IP,
pkix_verify_hostname_options,
pkix_test_data_all_default,
pkix_test_data,
- short_cert_issuer_hash, short_crl_issuer_hash
+ short_cert_issuer_hash,
+ short_crl_issuer_hash
].
groups() ->
- [{pem_decode_encode, [], [dsa_pem, rsa_pem, ec_pem, encrypted_pem,
+ [{pem_decode_encode, [], [dsa_pem, rsa_pem, rsa_pss_pss_pem, ec_pem, encrypted_pem,
dh_pem, cert_pem, pkcs7_pem, pkcs10_pem, ec_pem2,
rsa_priv_pkcs8, dsa_priv_pkcs8, ec_priv_pkcs8,
ec_pem_encode_generated,
gen_ec_param_prime_field, gen_ec_param_char_2_field
]},
- {sign_verify, [], [rsa_sign_verify, dsa_sign_verify]}
+ {sign_verify, [], [rsa_sign_verify, rsa_pss_sign_verify, dsa_sign_verify]}
].
%%-------------------------------------------------------------------
init_per_suite(Config) ->
@@ -101,6 +199,18 @@ init_per_testcase(gen_ec_param_prime_field=TC, Config) ->
init_per_testcase(gen_ec_param_char_2_field=TC, Config) ->
init_per_testcase_gen_ec_param(TC, sect571r1, Config);
+init_per_testcase(rsa_pss_sign_verify, Config) ->
+ Supports = crypto:supports(),
+ RSAOpts = proplists:get_value(rsa_opts, Supports),
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
+ andalso lists:member(rsa_mgf1_md, RSAOpts) of
+ true ->
+ Config;
+ false ->
+ {skip, not_supported_by_crypto}
+ end;
init_per_testcase(TestCase, Config) ->
case TestCase of
ec_pem_encode_generated ->
@@ -204,6 +314,19 @@ rsa_pem(Config) when is_list(Config) ->
RSARawPemNoEndNewLines = strip_superfluous_newlines(RSARawPem),
RSARawPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([PubEntry1])).
+rsa_pss_pss_pem() ->
+ [{doc, "RSA PKCS8 RSASSA-PSS private key decode/encode"}].
+rsa_pss_pss_pem(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(data_dir, Config),
+ {ok, RsaPem} = file:read_file(filename:join(Datadir, "rsa_pss_pss_key.pem")),
+ [{'PrivateKeyInfo', DerRSAKey, not_encrypted} = Entry0 ] = public_key:pem_decode(RsaPem),
+ {RSAKey, Parms} = public_key:der_decode('PrivateKeyInfo', DerRSAKey),
+ {RSAKey, Parms} = public_key:pem_entry_decode(Entry0),
+ true = check_entry_type(RSAKey, 'RSAPrivateKey'),
+ PrivEntry0 = public_key:pem_entry_encode('PrivateKeyInfo', {RSAKey, Parms}),
+ RSAPemNoEndNewLines = strip_superfluous_newlines(RsaPem),
+ RSAPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([PrivEntry0])).
+
rsa_priv_pkcs8() ->
[{doc, "RSA PKCS8 private key decode/encode"}].
rsa_priv_pkcs8(Config) when is_list(Config) ->
@@ -410,6 +533,24 @@ rsa_sign_verify(Config) when is_list(Config) ->
true = public_key:verify(Msg, md5, RSASign1, PublicRSA).
%%--------------------------------------------------------------------
+rsa_pss_sign_verify() ->
+ [{doc, "Checks that we can sign and verify rsa pss signatures."}].
+rsa_pss_sign_verify(Config) when is_list(Config) ->
+ CertChainConf = #{server_chain =>
+ #{root => [{digest, sha256}, {hardcode_rsa_key(1), pss_params(sha256)}],
+ intermediates => [[]],
+ peer => [{digest, sha256}, {hardcode_rsa_key(2), pss_params(sha256)}]},
+ client_chain =>
+ #{root => [{digest, sha256}, {hardcode_rsa_key(3), pss_params(sha256)}],
+ intermediates => [[]],
+ peer => [{digest, sha256}, {hardcode_rsa_key(4), pss_params(sha256)}]}},
+ #{client_config := ClientConf} = public_key:pkix_test_data(CertChainConf),
+ Cert = proplists:get_value(cert, ClientConf),
+ {#'RSAPrivateKey'{modulus=Mod, publicExponent=Exp}, Parms} = {hardcode_rsa_key(4), pss_params(sha256)},
+
+ public_key:pkix_verify(Cert, {#'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, Parms}).
+
+%%--------------------------------------------------------------------
dsa_sign_verify() ->
[{doc, "Checks that we can sign and verify dsa signatures."}].
@@ -472,13 +613,19 @@ pkix(Config) when is_list(Config) ->
end,
[TestTransform(Cert) || Cert <- Certs0 ++ Certs1],
- true = public_key:pkix_is_self_signed(element(2,hd(Certs0))),
- false = public_key:pkix_is_self_signed(element(2,hd(Certs1))),
+ Root = element(2, hd(Certs0)),
+ Peer = element(2, hd(Certs1)),
+
+ true = public_key:pkix_is_self_signed(Root),
+ false = public_key:pkix_is_self_signed(Peer),
CaIds = [element(2, public_key:pkix_issuer_id(Cert, self)) ||
{'Certificate', Cert, _} <- Certs0],
- {ok, IssuerId = {_, _IssuerName}} =
- public_key:pkix_issuer_id(element(2,hd(Certs1)), other),
+ {ok, IssuerId} =
+ public_key:pkix_issuer_id(Peer, other),
+
+ {ok, Id} = public_key:pkix_issuer_id(Root, self),
+ Id = public_key:pkix_subject_id(Root),
true = lists:member(IssuerId, CaIds),
@@ -586,8 +733,36 @@ pkix_path_validation(Config) when is_list(Config) ->
{ok, _} =
public_key:pkix_path_validation(unknown_ca, [Cert1], [{verify_fun,
VerifyFunAndState1}]),
- ok.
+ VerifyFunAndState2 =
+ {fun(_, {bad_cert, selfsigned_peer}, _UserState) ->
+ {fail, custom_reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState}
+ end, []},
+
+ {error, custom_reason} =
+ public_key:pkix_path_validation(selfsigned_peer, [Trusted], [{verify_fun,
+ VerifyFunAndState2}]).
+pkix_path_validation_root_expired() ->
+ [{doc, "Test root expiration so that it does not fall between chairs"}].
+pkix_path_validation_root_expired(Config) when is_list(Config) ->
+ {Year, Month, Day} = date(),
+ SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", [{validity, {{Year-2, Month, Day},
+ {Year-1, Month, Day}}}]),
+ #{server_config := Conf} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => [],
+ peer => []},
+ client_chain => #{root => [],
+ intermediates => [],
+ peer => []}}),
+ [ICA, Root] = proplists:get_value(cacerts, Conf),
+ true = public_key:pkix_is_self_signed(Root),
+ Peer = proplists:get_value(cert, Conf),
+ {error, {bad_cert, cert_expired}} = public_key:pkix_path_validation(Root, [ICA, Peer], []).
+
%%--------------------------------------------------------------------
%% To generate the PEM file contents:
%%
@@ -648,24 +823,26 @@ pkix_verify_hostname_subjAltName(Config) ->
%% Check that a dns_id matches a DNS subjAltName:
true = public_key:pkix_verify_hostname(Cert, [{dns_id,"kb.example.org"}]),
+ true = public_key:pkix_verify_hostname(Cert, [{dns_id,"KB.EXAMPLE.ORG"}]),
%% Check that a dns_id does not match a DNS subjAltName wiht wildcard
false = public_key:pkix_verify_hostname(Cert, [{dns_id,"other.example.org"}]),
%% Check that a dns_id does match a DNS subjAltName wiht wildcard with matchfun
- true = public_key:pkix_verify_hostname(Cert, [{dns_id,"other.example.org"}],
- [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}
- ]
- ),
+ MatchFun = {match_fun, public_key:pkix_verify_hostname_match_fun(https)},
+ true = public_key:pkix_verify_hostname(Cert, [{dns_id,"other.example.org"}], [MatchFun]),
+ true = public_key:pkix_verify_hostname(Cert, [{dns_id,"OTHER.EXAMPLE.ORG"}], [MatchFun]),
%% Check that a uri_id does not match a DNS subjAltName wiht wildcard
false = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://other.example.org"}]),
+ false = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://OTHER.EXAMPLE.ORG"}]),
%% Check that a dns_id does match a DNS subjAltName wiht wildcard with matchfun
- true = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://other.example.org"}],
- [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}
- ]
- ).
+ true = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://other.example.org"}], [MatchFun]),
+ true = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://OTHER.EXAMPLE.ORG"}], [MatchFun]),
+ true = public_key:pkix_verify_hostname(Cert, [{uri_id,"https://OTHER.example.org"}], [MatchFun]),
+
+ ok.
%%--------------------------------------------------------------------
%% Uses the pem-file for pkix_verify_hostname_cn
@@ -835,6 +1012,21 @@ general_name(Config) when is_list(Config) ->
[{rfc822Name, DummyRfc822Name}],
authorityCertSerialNumber =
1}).
+
+%%--------------------------------------------------------------------
+
+pkix_hash_type() ->
+ [{doc, "Test API function pkix_hash_type/1"}].
+
+pkix_hash_type(Config) when is_list(Config) ->
+ sha = public_key:pkix_hash_type(?'id-sha1'),
+ sha512 = public_key:pkix_hash_type(?'id-sha512'),
+ sha384 = public_key:pkix_hash_type(?'id-sha384'),
+ sha256 = public_key:pkix_hash_type(?'id-sha256'),
+ sha224 = public_key:pkix_hash_type('id-sha224'),
+ md5 = public_key:pkix_hash_type('id-md5').
+
+
%%--------------------------------------------------------------------
pkix_test_data_all_default() ->
@@ -888,7 +1080,7 @@ pkix_test_data(Config) when is_list(Config) ->
public_key:pkix_test_data(#{server_chain =>
#{root => [],
intermediates => [],
- peer => [{key, hardcode_rsa_key()}]},
+ peer => [{key, hardcode_rsa_key(1)}]},
client_chain =>
#{root => [{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}],
@@ -1076,9 +1268,7 @@ incorrect_countryname_pkix_cert() ->
incorrect_emailaddress_pkix_cert() ->
<<48,130,3,74,48,130,2,50,2,9,0,133,49,203,25,198,156,252,230,48,13,6,9,42,134, 72,134,247,13,1,1,5,5,0,48,103,49,11,48,9,6,3,85,4,6,19,2,65,85,49,19,48,17, 6,3,85,4,8,12,10,83,111,109,101,45,83,116,97,116,101,49,33,48,31,6,3,85,4,10, 12,24,73,110,116,101,114,110,101,116,32,87,105,100,103,105,116,115,32,80,116, 121,32,76,116,100,49,32,48,30,6,9,42,134,72,134,247,13,1,9,1,12,17,105,110, 118,97,108,105,100,64,101,109,97,105,108,46,99,111,109,48,30,23,13,49,51,49, 49,48,55,50,48,53,54,49,56,90,23,13,49,52,49,49,48,55,50,48,53,54,49,56,90, 48,103,49,11,48,9,6,3,85,4,6,19,2,65,85,49,19,48,17,6,3,85,4,8,12,10,83,111, 109,101,45,83,116,97,116,101,49,33,48,31,6,3,85,4,10,12,24,73,110,116,101, 114,110,101,116,32,87,105,100,103,105,116,115,32,80,116,121,32,76,116,100,49, 32,48,30,6,9,42,134,72,134,247,13,1,9,1,12,17,105,110,118,97,108,105,100,64, 101,109,97,105,108,46,99,111,109,48,130,1,34,48,13,6,9,42,134,72,134,247,13, 1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,190,243,49,213,219,60,232,105, 1,127,126,9,130,15,60,190,78,100,148,235,246,223,21,91,238,200,251,84,55,212, 78,32,120,61,85,172,0,144,248,5,165,29,143,79,64,178,51,153,203,76,115,238, 192,49,173,37,121,203,89,62,157,13,181,166,30,112,154,40,202,140,104,211,157, 73,244,9,78,236,70,153,195,158,233,141,42,238,2,143,160,225,249,27,30,140, 151,176,43,211,87,114,164,108,69,47,39,195,123,185,179,219,28,218,122,53,83, 77,48,81,184,14,91,243,12,62,146,86,210,248,228,171,146,225,87,51,146,155, 116,112,238,212,36,111,58,41,67,27,6,61,61,3,84,150,126,214,121,57,38,12,87, 121,67,244,37,45,145,234,131,115,134,58,194,5,36,166,52,59,229,32,47,152,80, 237,190,58,182,248,98,7,165,198,211,5,31,231,152,116,31,108,71,218,64,188, 178,143,27,167,79,15,112,196,103,116,212,65,197,94,37,4,132,103,91,217,73, 223,207,185,7,153,221,240,232,31,44,102,108,82,83,56,242,210,214,74,71,246, 177,217,148,227,220,230,4,176,226,74,194,37,2,3,1,0,1,48,13,6,9,42,134,72, 134,247,13,1,1,5,5,0,3,130,1,1,0,89,247,141,154,173,123,123,203,143,85,28,79, 73,37,164,6,17,89,171,224,149,22,134,17,198,146,158,192,241,41,253,58,230, 133,71,189,43,66,123,88,15,242,119,227,249,99,137,61,200,54,161,0,177,167, 169,114,80,148,90,22,97,78,162,181,75,93,209,116,245,46,81,232,64,157,93,136, 52,57,229,113,197,218,113,93,42,161,213,104,205,137,30,144,183,58,10,98,47, 227,177,96,40,233,98,150,209,217,68,22,221,133,27,161,152,237,46,36,179,59, 172,97,134,194,205,101,137,71,192,57,153,20,114,27,173,233,166,45,56,0,61, 205,45,202,139,7,132,103,248,193,157,184,123,43,62,172,236,110,49,62,209,78, 249,83,219,133,1,213,143,73,174,16,113,143,189,41,84,60,128,222,30,177,104, 134,220,52,239,171,76,59,176,36,113,176,214,118,16,44,235,21,167,199,216,200, 76,219,142,248,13,70,145,205,216,230,226,148,97,223,216,179,68,209,222,63, 140,137,24,164,192,149,194,79,119,247,75,159,49,116,70,241,70,116,11,40,119, 176,157,36,160,102,140,255,34,248,25,231,136,59>>.
-
-
-hardcode_rsa_key() ->
+hardcode_rsa_key(1) ->
#'RSAPrivateKey'{
version = 'two-prime',
modulus = 23995666614853919027835084074500048897452890537492185072956789802729257783422306095699263934587064480357348855732149402060270996295002843755712064937715826848741191927820899197493902093529581182351132392364214171173881547273475904587683433713767834856230531387991145055273426806331200574039205571401702219159773947658558490957010003143162250693492642996408861265758000254664396313741422909188635443907373976005987612936763564996605457102336549804831742940035613780926178523017685712710473543251580072875247250504243621640157403744718833162626193206685233710319205099867303242759099560438381385658382486042995679707669,
@@ -1089,4 +1279,51 @@ hardcode_rsa_key() ->
exponent1 = 119556097830058336212015217380447172615655659108450823901745048534772786676204666783627059584226579481512852103690850928442711896738555003036938088452023283470698275450886490965004917644550167427154181661417665446247398284583687678213495921811770068712485038160606780733330990744565824684470897602653233516609,
exponent2 = 41669135975672507953822256864985956439473391144599032012999352737636422046504414744027363535700448809435637398729893409470532385959317485048904982111185902020526124121798693043976273393287623750816484427009887116945685005129205106462566511260580751570141347387612266663707016855981760014456663376585234613993,
coefficient = 76837684977089699359024365285678488693966186052769523357232308621548155587515525857011429902602352279058920284048929101483304120686557782043616693940283344235057989514310975192908256494992960578961614059245280827077951132083993754797053182279229469590276271658395444955906108899267024101096069475145863928441,
+ otherPrimeInfos = asn1_NOVALUE};
+
+hardcode_rsa_key(2) ->
+ #'RSAPrivateKey'{
+ version = 'two-prime',
+ modulus = 21343679768589700771839799834197557895311746244621307033143551583788179817796325695589283169969489517156931770973490560582341832744966317712674900833543896521418422508485833901274928542544381247956820115082240721897193055368570146764204557110415281995205343662628196075590438954399631753508888358737971039058298703003743872818150364935790613286541190842600031570570099801682794056444451081563070538409720109449780410837763602317050353477918147758267825417201591905091231778937606362076129350476690460157227101296599527319242747999737801698427160817755293383890373574621116766934110792127739174475029121017282777887777,
+ publicExponent = 17,
+ privateExponent = 18832658619343853622211588088997845201745658451136447382185486691577805721584993260814073385267196632785528033211903435807948675951440868570007265441362261636545666919252206383477878125774454042314841278013741813438699754736973658909592256273895837054592950290554290654932740253882028017801960316533503857992358685308186680144968293076156011747178275038098868263178095174694099811498968993700538293188879611375604635940554394589807673542938082281934965292051746326331046224291377703201248790910007232374006151098976879987912446997911775904329728563222485791845480864283470332826504617837402078265424772379987120023773,
+ prime1 = 146807662748886761089048448970170315054939768171908279335181627815919052012991509112344782731265837727551849787333310044397991034789843793140419387740928103541736452627413492093463231242466386868459637115999163097726153692593711599245170083315894262154838974616739452594203727376460632750934355508361223110419,
+ prime2 = 145385325050081892763917667176962991350872697916072592966410309213561884732628046256782356731057378829876640317801978404203665761131810712267778698468684631707642938779964806354584156202882543264893826268426566901882487709510744074274965029453915224310656287149777603803201831202222853023280023478269485417083,
+ exponent1 = 51814469205489445090252393754177758254684624060673510353593515699736136004585238510239335081623236845018299924941168250963996835808180162284853901555621683602965806809675350150634081614988136541809283687999704622726877773856604093851236499993845033701707873394143336209718962603456693912094478414715725803677,
+ exponent2 = 51312467664734785681382706062457526359131540440966797517556579722433606376221663384746714140373192528191755406283051201483646739222992016094510128871300458249756331334105225772206172777487956446433115153562317730076172132768497908567634716277852432109643395464627389577600646306666889302334125933506877206029,
+ coefficient = 30504662229874176232343608562807118278893368758027179776313787938167236952567905398252901545019583024374163153775359371298239336609182249464886717948407152570850677549297935773605431024166978281486607154204888016179709037883348099374995148481968169438302456074511782717758301581202874062062542434218011141540,
+ otherPrimeInfos = asn1_NOVALUE};
+hardcode_rsa_key(3) ->
+ #'RSAPrivateKey'{
+ version = 'two-prime',
+ modulus = 25089040456112869869472694987833070928503703615633809313972554887193090845137746668197820419383804666271752525807484521370419854590682661809972833718476098189250708650325307850184923546875260207894844301992963978994451844985784504212035958130279304082438876764367292331581532569155681984449177635856426023931875082020262146075451989132180409962870105455517050416234175675478291534563995772675388370042873175344937421148321291640477650173765084699931690748536036544188863178325887393475703801759010864779559318631816411493486934507417755306337476945299570726975433250753415110141783026008347194577506976486290259135429,
+ publicExponent = 17,
+ privateExponent = 8854955455098659953931539407470495621824836570223697404931489960185796768872145882893348383311931058684147950284994536954265831032005645344696294253579799360912014817761873358888796545955974191021709753644575521998041827642041589721895044045980930852625485916835514940558187965584358347452650930302268008446431977397918214293502821599497633970075862760001650736520566952260001423171553461362588848929781360590057040212831994258783694027013289053834376791974167294527043946669963760259975273650548116897900664646809242902841107022557239712438496384819445301703021164043324282687280801738470244471443835900160721870265,
+ prime1 = 171641816401041100605063917111691927706183918906535463031548413586331728772311589438043965564336865070070922328258143588739626712299625805650832695450270566547004154065267940032684307994238248203186986569945677705100224518137694769557564475390859269797990555863306972197736879644001860925483629009305104925823,
+ prime2 =146170909759497809922264016492088453282310383272504533061020897155289106805616042710009332510822455269704884883705830985184223718261139908416790475825625309815234508695722132706422885088219618698987115562577878897003573425367881351537506046253616435685549396767356003663417208105346307649599145759863108910523,
+ exponent1 = 60579464612132153154728441333538327425711971378777222246428851853999433684345266860486105493295364142377972586444050678378691780811632637288529186629507258781295583787741625893888579292084087601124818789392592131211843947578009918667375697196773859928702549128225990187436545756706539150170692591519448797349,
+ exponent2 = 137572620950115585809189662580789132500998007785886619351549079675566218169991569609420548245479957900898715184664311515467504676010484619686391036071176762179044243478326713135456833024206699951987873470661533079532774988581535389682358631768109586527575902839864474036157372334443583670210960715165278974609,
+ coefficient = 15068630434698373319269196003209754243798959461311186548759287649485250508074064775263867418602372588394608558985183294561315208336731894947137343239541687540387209051236354318837334154993136528453613256169847839789803932725339395739618592522865156272771578671216082079933457043120923342632744996962853951612,
+ otherPrimeInfos = asn1_NOVALUE};
+hardcode_rsa_key(4) ->
+ #'RSAPrivateKey'{
+ version ='two-prime',
+ modulus = 28617237755030755643854803617273584643843067580642149032833640135949799721163782522787597288521902619948688786051081993247908700824196122780349730169173433743054172191054872553484065655968335396052034378669869864779940355219732200954630251223541048434478476115391643898092650304645086338265930608997389611376417609043761464100338332976874588396803891301015812818307951159858145399281035705713082131199940309445719678087542976246147777388465712394062188801177717719764254900022006288880246925156931391594131839991579403409541227225173269459173129377291869028712271737734702830877034334838181789916127814298794576266389,
+ publicExponent = 17,
+ privateExponent = 26933870828264240605980991639786903194205240075898493207372837775011576208154148256741268036255908348187001210401018346586267012540419880263858569570986761169933338532757527109161473558558433313931326474042230460969355628442100895016122589386862163232450330461545076609969553227901257730132640573174013751883368376011370428995523268034111482031427024082719896108094847702954695363285832195666458915142143884210891427766607838346722974883433132513540317964796373298134261669479023445911856492129270184781873446960437310543998533283339488055776892320162032014809906169940882070478200435536171854883284366514852906334641,
+ prime1 = 177342190816702392178883147766999616783253285436834252111702533617098994535049411784501174309695427674025956656849179054202187436663487378682303508229883753383891163725167367039879190685255046547908384208614573353917213168937832054054779266431207529839577747601879940934691505396807977946728204814969824442867,
+ prime2 = 161367340863680900415977542864139121629424927689088951345472941851682581254789586032968359551717004797621579428672968948552429138154521719743297455351687337112710712475376510559020211584326773715482918387500187602625572442687231345855402020688502483137168684570635690059254866684191216155909970061793538842967,
+ exponent1 = 62591361464718491357252875682470452982324688977706206627659717747211409835899792394529826226951327414362102349476180842659595565881230839534930649963488383547255704844176717778780890830090016428673547367746320007264898765507470136725216211681602657590439205035957626212244060728285168687080542875871702744541,
+ exponent2 = 28476589564178982426348978152495139111074987239250991413906989738532220221433456358759122273832412611344984605059935696803369847909621479954699550944415412431654831613301737157474154985469430655673456186029444871051571607533040825739188591886206320553618003159523945304574388238386685203984112363845918619347,
+ coefficient = 34340318160575773065401929915821192439103777558577109939078671096408836197675640654693301707202885840826672396546056002756167635035389371579540325327619480512374920136684787633921441576901246290213545161954865184290700344352088099063404416346968182170720521708773285279884132629954461545103181082503707725012,
otherPrimeInfos = asn1_NOVALUE}.
+
+pss_params(sha256) ->
+ #'RSASSA-PSS-params'{
+ hashAlgorithm = #'HashAlgorithm'{algorithm = ?'id-sha256'},
+ maskGenAlgorithm = #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = ?'id-sha256'}
+ },
+ saltLength = 32,
+ trailerField = 1}.
+
diff --git a/lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem b/lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem
new file mode 100644
index 0000000000..65032269c1
--- /dev/null
+++ b/lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem
@@ -0,0 +1,29 @@
+-----BEGIN PRIVATE KEY-----
+MIIE7wIBADA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3
+DQEBCDALBglghkgBZQMEAgGiAwIBIASCBKkwggSlAgEAAoIBAQDDlygksUEAajpd
+Vquo9XIAyTd9ZJ+55hNmhBfhn3lHz3ryPD+0XlgCE9qsKwfR7iYaqmnNilQnsxWp
+MGXAgOlC1+w5zh8qHvrI5wX+A6U9N8leIOSgFuFNP0FMMG7I677QzRxGFqKX1o4V
+73JWqnHCfnfHRyZY9xM0tYbJKNbRO7Hy4jKBPl3ptPHUoTltr4WYTOpgstcEamdi
+iif+0U4bQvVltNg9pzFEjkAktTUGn92W5CgLnsbPXxBo6a/kUlHcgmhYbpOXEjCP
+ufZLgsQo8iF2Bq8eWMEsByjr0chQjzrfZAUVtD8Hmh2uMVAPQFAHUkaLj2tHukL+
+s9tAaWKNAgMBAAECggEBAIzgfwWOtmb6HHfGSXY085wlUlZ696EKWsboNdtI5i4W
+/1Mimi/sFC/K5SJFDCjlA4UJYZOuItdFYkCun1t8foaqx3cLQ98u2SuDWwmOzqG9
+YMjvoDy+viDJgtrBt8n4I0R5t/ezrgD3hPe/s/dAZRfVx6g9Ux2ZOLgqV57kT3X7
+6paEz3jrIMvuoXQCsi9Qh+eJQ23/sAcc7OHQ7uD8QJVudEBnSHQ+ttvOPXhr7tba
+8NuNVa6E/KewkKHRAZqBTJolCVyPtWmvfaDwdJtunCvyR1w3Rv1adZLK4YRFz+vc
+sOMK+K1c2aojA+/Fnba19inNq13j6Dwqmq8Ho7MZwHECgYEA6aSx7/93S1VGpxQ9
+KqFE4Fy9ylliC/hanc9qOcfEIo0tDus9lfpuPp+aOXML0msVkIfhCnaru32qtnaI
+AQkIbPhSZFvC/i6BibpArXINbDzTS/46zZHehXskjWFGw+iRm/YI7MBuCmWzSnFO
+YUwSKRIPKZKyXswFzP8RsQO/QbsCgYEA1k5SamQheuKdo/X40ShWTTOoDlpL4Sir
+b2zTnEqlHyMv8c7w880hPf4P+0pqrKyf7jmEykJvp1qSAmyMUCWzrKTr8gQ2sMyb
+zj90cEm++M5YIQh5lPJy4pGqmCliJXqkt+zT1xmnRASwMNQOnU2bBmXkve/ofb4M
+dEwyig/nZFcCgYBLWPilTD6dhce+NBGxwMZkkKQIMKEk+RfIEs7QCXNgLSUdzZFT
+36pT+caTxl1Go5AVxyw04qZpVZKLO1iK9O3Jrp9rjAgrTrYpw23+QWzAvjDqLfeq
+ueMIKvlTus5GeacTo9mm+DvEkJ2sYTQEvrKQmilXn950IdmxDYUYD/xK5wKBgQDQ
+5ON9BUGFUSQsUHVLG7CT7EhiRS41ubjyEfhrHm+53Ei9weQpIcjHbsERR8aXrmTu
+h26i4QOI88XjSv+ymC19mfzLmcPdrnQpJL1RPvFCAZDyEhrBT1sg8rCBRcV/lv68
+scMEpuLecFt2HR5pwt3b7LJ9Wj8bYoctTaDt5va8XQKBgQDCr4hZB5haAcKmNm/g
+PjlaLdrDEIuuBjxMzX1t3PXwsEene1cE731v6fbmrDUa8AuJyMY80xhGrTTDQfS3
+QOu/6wtcUv/JC/06OwEaUlT/kdYek+zYfBm3b1sKP3HVKSxCLTcPcC4aQoAFqbEy
+3kuSVh03vVBdaP//qMPyeue17w==
+-----END PRIVATE KEY-----
diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk
index 0008cf7a16..442b3cc18e 100644
--- a/lib/public_key/vsn.mk
+++ b/lib/public_key/vsn.mk
@@ -1 +1 @@
-PUBLIC_KEY_VSN = 1.7.2
+PUBLIC_KEY_VSN = 1.10
diff --git a/lib/reltool/Makefile b/lib/reltool/Makefile
index 4b6aad07b3..0a39d7daa8 100644
--- a/lib/reltool/Makefile
+++ b/lib/reltool/Makefile
@@ -36,3 +36,6 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=wx sasl
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/reltool/doc/src/Makefile b/lib/reltool/doc/src/Makefile
index dce8059616..76cf8b82dd 100644
--- a/lib/reltool/doc/src/Makefile
+++ b/lib/reltool/doc/src/Makefile
@@ -28,84 +28,9 @@ VSN=$(RELTOOL_VSN)
APPLICATION=reltool
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
include files.mk
-# ----------------------------------------------------
-
-XML_FILES = \
- $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) \
- $(XML_PART_FILES) $(XML_CHAPTER_FILES)
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html:images $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES)
-
-images: $(IMAGE_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean clean_docs:
- for file in $(XML_FILES); do \
- if [ -f $$file\src ]; then \
- rm -f $$file; \
- fi \
- done
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
-
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/reltool/doc/src/files.mk b/lib/reltool/doc/src/files.mk
index efd7d8f09c..46288d0f50 100644
--- a/lib/reltool/doc/src/files.mk
+++ b/lib/reltool/doc/src/files.mk
@@ -37,3 +37,6 @@ BOOK_FILES = book.xml
IMAGE_FILES =
+XML_FILES = \
+ $(BOOK_FILES) $(XML_CHAPTER_FILES) \
+ $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES)
diff --git a/lib/reltool/doc/src/reltool_examples.xml b/lib/reltool/doc/src/reltool_examples.xml
index 3888b643a2..5da17d4323 100644
--- a/lib/reltool/doc/src/reltool_examples.xml
+++ b/lib/reltool/doc/src/reltool_examples.xml
@@ -42,11 +42,11 @@
is or be used via the GUI frontend process. When the GUI is
started, a server process will automatically be started. The GUI
process is started with
- <seealso marker="reltool#start-0"><c>reltool:start/0</c></seealso>,
- <seealso marker="reltool#start-1"><c>reltool:start/1</c></seealso> or
- <seealso marker="reltool#start_link-1"><c>reltool:start_link/1</c></seealso>.
+ <seemfa marker="reltool#start/0"><c>reltool:start/0</c></seemfa>,
+ <seemfa marker="reltool#start/1"><c>reltool:start/1</c></seemfa> or
+ <seemfa marker="reltool#start_link/1"><c>reltool:start_link/1</c></seemfa>.
The pid of its server can be obtained with
- <seealso marker="reltool#start_link-1"><c>reltool:get_server/1</c></seealso>
+ <seemfa marker="reltool#start_link/1"><c>reltool:get_server/1</c></seemfa>
</p>
<pre>
diff --git a/lib/reltool/test/reltool_test_lib.erl b/lib/reltool/test/reltool_test_lib.erl
index 033d952d0a..4860f7ad66 100644
--- a/lib/reltool/test/reltool_test_lib.erl
+++ b/lib/reltool/test/reltool_test_lib.erl
@@ -27,10 +27,17 @@
init_per_suite(Config) when is_list(Config)->
global:register_name(reltool_global_logger, group_leader()),
- incr_timetrap(Config, ?timeout).
+ ErlLibs = os:getenv("ERL_LIBS"),
+ os:unsetenv("ERL_LIBS"),
+ [{erl_libs,ErlLibs}|incr_timetrap(Config, ?timeout)].
end_per_suite(Config) when is_list(Config)->
global:unregister_name(reltool_global_logger),
+ case proplists:get_value(erl_libs, Config) of
+ false -> ok;
+ ErlLibs ->
+ os:putenv("ERL_LIBS", ErlLibs)
+ end,
ok.
incr_timetrap(Config, Times) ->
@@ -238,7 +245,7 @@ wait_for_close() ->
end.
erl_libs() ->
- lists:sort([filename:absname(P) || P<-reltool_utils:erl_libs()]).
+ [filename:absname(P) || P<-reltool_utils:erl_libs()].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A small test server, which can be run standalone in a shell
diff --git a/lib/runtime_tools/Makefile b/lib/runtime_tools/Makefile
index eec1ff379b..4b0f1633ab 100644
--- a/lib/runtime_tools/Makefile
+++ b/lib/runtime_tools/Makefile
@@ -36,4 +36,6 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=mnesia
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/runtime_tools/doc/src/LTTng.xml b/lib/runtime_tools/doc/src/LTTng.xml
index 89cbc805d8..c12de0c6e9 100644
--- a/lib/runtime_tools/doc/src/LTTng.xml
+++ b/lib/runtime_tools/doc/src/LTTng.xml
@@ -77,7 +77,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>procs</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>procs</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">process_spawn: { cpu_id = 3 }, { pid = "&lt;0.131.0&gt;", parent = "&lt;0.130.0&gt;", entry = "erlang:apply/2" }</code>
@@ -90,7 +90,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>procs</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>procs</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">process_link: { cpu_id = 3 }, { from = "&lt;0.130.0&gt;", to = "&lt;0.131.0&gt;", type = "link" }</code>
@@ -103,7 +103,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>procs</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>procs</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">process_exit: { cpu_id = 3 }, { pid = "&lt;0.130.0&gt;", reason = "normal" }</code>
@@ -125,7 +125,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>running</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>running</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">process_scheduled: { cpu_id = 0 }, { pid = "&lt;0.136.0&gt;", entry = "erlang:apply/2", type = "in" }</code>
@@ -139,7 +139,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>ports</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>ports</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">port_open: { cpu_id = 5 }, { pid = "&lt;0.131.0&gt;", driver = "'/bin/sh -s unix:cmd'", port = "#Port&lt;0.1887&gt;" }</code>
@@ -151,7 +151,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>ports</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>ports</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">port_exit: { cpu_id = 5 }, { port = "#Port&lt;0.1887&gt;", reason = "normal" }</code>
@@ -164,7 +164,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>ports</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>ports</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">port_link: { cpu_id = 5 }, { from = "#Port&lt;0.1887&gt;", to = "&lt;0.131.0&gt;", type = "unlink" }</code>
@@ -172,7 +172,7 @@ $ make </code>
<p><em>port_scheduled</em></p>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>running</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>running</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<list type="bulleted">
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
@@ -184,7 +184,7 @@ $ make </code>
<code type="none">port_scheduled: { cpu_id = 5 }, { pid = "#Port&lt;0.1905&gt;", entry = "close", type = "out" }</code>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>running</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>running</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p><em>function_call</em></p>
<list type="bulleted">
@@ -194,7 +194,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>call</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>call</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">function_call: { cpu_id = 5 }, { pid = "&lt;0.145.0&gt;", entry = "dyntrace_lttng_SUITE:'-t_call/1-fun-1-'/0", depth = 0 }</code>
@@ -207,7 +207,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>call</c> or <c>return_to</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>call</c> or <c>return_to</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">function_return: { cpu_id = 5 }, { pid = "&lt;0.145.0&gt;", entry = "dyntrace_lttng_SUITE:waiter/0", depth = 0 }</code>
@@ -220,7 +220,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>call</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>call</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">function_exception: { cpu_id = 5 }, { pid = "&lt;0.144.0&gt;", entry = "t:call_exc/1", class = "error" }</code>
@@ -233,7 +233,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>send</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>send</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">message_send: { cpu_id = 3 }, { from = "#Port&lt;0.1938&gt;", to = "&lt;0.160.0&gt;", message = "{#Port&lt;0.1938&gt;,eof}" }</code>
@@ -245,7 +245,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>'receive'</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>'receive'</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">message_receive: { cpu_id = 7 }, { to = "&lt;0.167.0&gt;", message = "{&lt;0.165.0&gt;,ok}" }</code>
@@ -259,7 +259,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>garbage_collection</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>garbage_collection</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">gc_minor_start: { cpu_id = 0 }, { pid = "&lt;0.172.0&gt;", need = 0, heap = 610, old_heap = 0 }</code>
@@ -273,7 +273,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>garbage_collection</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>garbage_collection</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">gc_minor_end: { cpu_id = 0 }, { pid = "&lt;0.172.0&gt;", reclaimed = 120, heap = 1598, old_heap = 1598 }</code>
@@ -287,7 +287,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>garbage_collection</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>garbage_collection</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">gc_major_start: { cpu_id = 0 }, { pid = "&lt;0.172.0&gt;", need = 8, heap = 2586, old_heap = 1598 }</code>
@@ -301,7 +301,7 @@ $ make </code>
</list>
<p>
Available through
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso> with trace flag <c>garbage_collection</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa> with trace flag <c>garbage_collection</c> and <c>{tracer,dyntrace,[]}</c> as tracer module.
</p>
<p>Example:</p>
<code type="none">gc_major_end: { cpu_id = 0 }, { pid = "&lt;0.172.0&gt;", reclaimed = 240, heap = 4185, old_heap = 0 }</code>
@@ -313,14 +313,6 @@ $ make </code>
<p>All tracepoints are in the domain of <c>org_erlang_otp</c></p>
<p>All Erlang types are the string equivalent in LTTng.</p>
- <p><em>scheduler_poll</em></p>
- <list type="bulleted">
- <item><c>scheduler : integer</c> :: Scheduler ID. Ex. <c>1</c></item>
- <item><c>runnable : integer</c> :: Runnable. Ex. <c>1</c></item>
- </list>
- <p>Example:</p>
- <code type="none">scheduler_poll: { cpu_id = 4 }, { scheduler = 1, runnable = 1 }</code>
-
<p><em>driver_init</em></p>
<list type="bulleted">
<item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
@@ -450,23 +442,6 @@ $ make </code>
<p>Example:</p>
<code type="none">driver_control: { cpu_id = 3 }, { pid = "&lt;0.32767.8191&gt;", port = "#Port&lt;0.0&gt;", driver = "forker", command = 83, bytes = 32 }</code>
- <p><em>aio_pool_get</em></p>
- <list type="bulleted">
- <item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>length : integer</c> :: Async queue length. Ex. <c>0</c></item>
- </list>
- <p>Example:</p>
- <code type="none">aio_pool_get: { cpu_id = 4 }, { port = "#Port&lt;0.3614&gt;", length = 0 }</code>
-
- <p><em>aio_pool_put</em></p>
- <list type="bulleted">
- <item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>length : integer</c> :: Async queue length. Ex. <c>-1</c></item>
- </list>
- <p>Async queue length is not defined for <c>put</c> operations.</p>
- <p>Example:</p>
- <code type="none">aio_pool_put: { cpu_id = 3 }, { port = "#Port&lt;0.3614&gt;", length = -1 }</code>
-
<p><em>carrier_create</em></p>
<list type="bulleted">
<item><c>type : string</c> :: Carrier type. Ex. <c>"ets_alloc"</c></item>
diff --git a/lib/runtime_tools/doc/src/Makefile b/lib/runtime_tools/doc/src/Makefile
index 2399ed51e0..53c3cc9d26 100644
--- a/lib/runtime_tools/doc/src/Makefile
+++ b/lib/runtime_tools/doc/src/Makefile
@@ -33,11 +33,6 @@ VSN=$(RUNTIME_TOOLS_VSN)
APPLICATION=runtime_tools
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -64,83 +59,17 @@ XML_FILES = \
XML_GEN_FILES = $(GENERATED_XML_FILES:%=$(XMLDIR)/%)
-GIF_FILES =
-
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+IMAGE_FILES =
TOP_SPECS_FILE = specs.xml
# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-SPECS_ESRC = ../../src
-
-SPECS_FLAGS = -I../../include -I../../../kernel/src
-
-# ----------------------------------------------------
# Targets
# ----------------------------------------------------
$(XMLDIR)/%.xml: $(ERL_TOP)/HOWTO/%.md $(ERL_TOP)/make/emd2exml
$(ERL_TOP)/make/emd2exml $< $@
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES) $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
-
-release_spec:
+
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/runtime_tools/doc/src/dbg.xml b/lib/runtime_tools/doc/src/dbg.xml
index e15fc3efe6..168e89b7f0 100644
--- a/lib/runtime_tools/doc/src/dbg.xml
+++ b/lib/runtime_tools/doc/src/dbg.xml
@@ -36,8 +36,8 @@
<modulesummary>The Text Based Trace Facility</modulesummary>
<description>
<p>This module implements a text based interface to the
- <seealso marker="erts:erlang#trace-3"><c>trace/3</c></seealso> and the
- <seealso marker="erts:erlang#trace_pattern-2"><c>trace_pattern/2</c></seealso> BIFs. It makes it
+ <seemfa marker="erts:erlang#trace/3"><c>trace/3</c></seemfa> and the
+ <seemfa marker="erts:erlang#trace_pattern/2"><c>trace_pattern/2</c></seemfa> BIFs. It makes it
possible to trace functions, processes, ports and messages.
</p>
<p>
@@ -58,12 +58,12 @@
</pre>
<p>
For more examples of how to use <c>dbg</c> from the Erlang
- shell, see the <seealso marker="#simple_example">simple example</seealso> section.
+ shell, see the <seeerl marker="#simple_example">simple example</seeerl> section.
</p>
<p>The utilities are also suitable to use in system testing on
large systems, where other tools have too much impact on the
system performance. Some primitive support for sequential tracing
- is also included, see the <seealso marker="#advanced">advanced topics</seealso> section.
+ is also included, see the <seeerl marker="#advanced">advanced topics</seeerl> section.
</p>
</description>
<funcs>
@@ -76,10 +76,10 @@
</type>
<desc>
<p>Pseudo function that by means of a <c>parse_transform</c>
- translates the <em>literal</em><c>fun()</c> typed as parameter in
+ translates the <em>literal</em> <c>fun()</c> typed as parameter in
the function call to a match specification as described in
the <c>match_spec</c> manual of ERTS users guide.
- (with literal I mean that the <c>fun()</c> needs to
+ (With literal I mean that the <c>fun()</c> needs to
textually be written as the parameter of the function, it
cannot be held in a variable which in turn is passed to the
function). </p>
@@ -184,8 +184,8 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<tag><c>pid()</c> or <c>port()</c></tag>
<item>The corresponding process or port is traced. The process or port may
be a remote process or port (on another Erlang node). The node must
- be in the list of traced nodes (see <seealso marker="#n-1"><c>n/1</c></seealso>
- and <seealso marker="#tracer-3"><c>tracer/3</c></seealso>).</item>
+ be in the list of traced nodes (see <seemfa marker="#n/1"><c>n/1</c></seemfa>
+ and <seemfa marker="#tracer/3"><c>tracer/3</c></seemfa>).</item>
<tag><c>all</c></tag>
<item>All processes and ports in the system as well as all processes and ports
created hereafter are to be traced.</item>
@@ -208,23 +208,23 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<tag><c>atom()</c></tag>
<item>The process or port with the corresponding registered name is traced. The process or
port may be a remote process (on another Erlang node). The node must be
- added with the <seealso marker="#n-1"><c>n/1</c></seealso> or
- <seealso marker="#tracer-3"><c>tracer/3</c></seealso> function.</item>
+ added with the <seemfa marker="#n/1"><c>n/1</c></seemfa> or
+ <seemfa marker="#tracer/3"><c>tracer/3</c></seemfa> function.</item>
<tag><c>integer()</c></tag>
<item>The process <c><![CDATA[<0.Item.0>]]></c> is traced.</item>
<tag><c>{X, Y, Z}</c></tag>
<item>The process <c><![CDATA[<X.Y.Z>]]></c> is traced. </item>
<tag><c>string()</c></tag>
<item>If the <c>Item</c> is a string <![CDATA["<X.Y.Z>"]]>
- as returned from <seealso marker="erts:erlang#pid_to_list-1"><c>pid_to_list/1</c></seealso>,
+ as returned from <seemfa marker="erts:erlang#pid_to_list/1"><c>pid_to_list/1</c></seemfa>,
the process <c><![CDATA[<X.Y.Z>]]></c> is traced.
</item>
</taglist>
<p>When enabling an <c>Item</c> that represents a group of processes,
the <c>Item</c> is enabled on all nodes added with the
- <seealso marker="#n-1"><c>n/1</c></seealso> or
- <seealso marker="#tracer-3"><c>tracer/3</c></seealso> function.</p>
+ <seemfa marker="#n/1"><c>n/1</c></seemfa> or
+ <seemfa marker="#tracer/3"><c>tracer/3</c></seemfa> function.</p>
<p><c>Flags</c> can be a single atom,
or a list of flags. The available flags are:
@@ -276,7 +276,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<item>
<p>This is the same as <c>sol</c>, but only for
the first call to
- <seealso marker="erts:erlang#link-1"><c>link/1</c></seealso> by the traced process.</p>
+ <seemfa marker="erts:erlang#link/1"><c>link/1</c></seemfa> by the traced process.</p>
</item>
<tag><c>all</c></tag>
<item>
@@ -289,13 +289,13 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</item>
</taglist>
<p>The list can also include any of the flags allowed in
- <seealso marker="erts:erlang#trace-3"><c>erlang:trace/3</c></seealso></p>
+ <seemfa marker="erts:erlang#trace/3"><c>erlang:trace/3</c></seemfa></p>
<p>The function returns either an error tuple or a tuple
<c>{ok, List}</c>. The <c>List</c> consists of
specifications of how many processes and ports that matched (in the
case of a pure pid() exactly 1). The specification of
matched processes is <c>{matched, Node, N}</c>. If the
- remote processor call,<c>rpc</c>, to a remote node fails,
+ remote processor call, <c>rpc</c>, to a remote node fails,
the <c>rpc</c> error message is delivered as a fourth
argument and the number of matched processes are 0. Note
that the result {ok, List} may contain a list where
@@ -369,11 +369,11 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
please turn to the
<em>User's guide</em> part of the online
documentation for the runtime system (<em>erts</em>). The
- chapter <seealso marker="erts:match_spec"><em>Match Specifications in Erlang</em></seealso>
+ chapter <seeguide marker="erts:match_spec"><em>Match Specifications in Erlang</em></seeguide>
explains the general match specification "language".
The most common generic match specifications used can be
found as <c>Built-inAlias</c>', see
- <seealso marker="#ltp-0"><c>ltp/0</c></seealso> below for details.
+ <seemfa marker="#ltp/0"><c>ltp/0</c></seemfa> below for details.
</p>
<p>The Module, Function and/or Arity parts of the tuple may
be specified as the atom <c>'_'</c> which is a "wild-card"
@@ -381,21 +381,21 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
Module is specified as <c>'_'</c>, the Function and Arity
parts have to be specified as '_' too. The same holds for the
Functions relation to the Arity.</p>
- <p>All nodes added with <seealso marker="#n-1"><c>n/1</c></seealso> or
- <seealso marker="#tracer-3"><c>tracer/3</c></seealso> will
+ <p>All nodes added with <seemfa marker="#n/1"><c>n/1</c></seemfa> or
+ <seemfa marker="#tracer/3"><c>tracer/3</c></seemfa> will
be affected by this call, and if Module is not <c>'_'</c>
the module will be loaded on all nodes.</p>
<p>The function returns either an error tuple or a tuple
<c>{ok, List}</c>. The <c>List</c> consists of specifications of how
many functions that matched, in the same way as the processes and ports
- are presented in the return value of <seealso marker="#p-2"><c>p/2</c></seealso>. </p>
+ are presented in the return value of <seemfa marker="#p/2"><c>p/2</c></seemfa>. </p>
<p>There may be a tuple <c>{saved, N}</c> in the return value,
if the MatchSpec is other
than []. The integer <c>N</c> may then be used in
subsequent calls to this function and will stand as an
"alias" for the given expression. There are also a couple of
built-in aliases for common expressions, see
- <seealso marker="#ltp-0"><c>ltp/0</c></seealso> below for details.</p>
+ <seemfa marker="#ltp/0"><c>ltp/0</c></seemfa> below for details.</p>
<p>If an error is returned, it can be due to errors in
compilation of the match specification. Such errors are
presented as a list of tuples <c>{error, string()}</c> where
@@ -434,7 +434,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<name since="">tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
<desc>
- <p>This function works as <seealso marker="#tp-2"><c>tp/2</c></seealso>, but enables
+ <p>This function works as <seemfa marker="#tp/2"><c>tp/2</c></seemfa>, but enables
tracing for local calls (and local functions) as well as for
global calls (and functions).</p>
</desc>
@@ -461,7 +461,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<p>For a description of the <c>match_spec()</c> syntax,
please turn to the <em>User's guide</em> part of the online
documentation for the runtime system (<em>erts</em>). The
- chapter <seealso marker="erts:match_spec"><em>Match Specifications in Erlang</em></seealso>
+ chapter <seeguide marker="erts:match_spec"><em>Match Specifications in Erlang</em></seeguide>
explains the general match specification "language".</p>
<p>For <c>send</c>, the matching is done on the list <c>[Receiver, Msg]</c>.
<c>Receiver</c> is the process or port identity of the receiver and
@@ -474,11 +474,11 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
be the case for remote senders). <c>Msg</c> is the
message term. The pid of the receiving process can be
accessed with the guard function <c>self/0</c>.</p>
- <p>All nodes added with <seealso marker="#n-1"><c>n/1</c></seealso> or
- <seealso marker="#tracer-3"><c>tracer/3</c></seealso> will
+ <p>All nodes added with <seemfa marker="#n/1"><c>n/1</c></seemfa> or
+ <seemfa marker="#tracer/3"><c>tracer/3</c></seemfa> will
be affected by this call.</p>
<p>The return value is the same as for
- <seealso marker="#tp-2"><c>tp/2</c></seealso>. The number of matched
+ <seemfa marker="#tp/2"><c>tp/2</c></seemfa>. The number of matched
events are never larger than 1 as <c>tpe/2</c> does not
accept any form of wildcards for argument <c>Event</c>.</p>
</desc>
@@ -525,10 +525,10 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<p>This function disables call tracing on the specified
functions. The semantics of the parameter is the same
as for the corresponding function specification in
- <seealso marker="#tp-2"><c>tp/2</c></seealso> or <seealso marker="#tpl-2"><c>tpl/2</c></seealso>. Both local and global call trace
+ <seemfa marker="#tp/2"><c>tp/2</c></seemfa> or <seemfa marker="#tpl/2"><c>tpl/2</c></seemfa>. Both local and global call trace
is disabled. </p>
<p>The return value reflects how many functions that matched,
- and is constructed as described in <seealso marker="#tp-2"><c>tp/2</c></seealso>. No tuple
+ and is constructed as described in <seemfa marker="#tp/2"><c>tp/2</c></seemfa>. No tuple
<c>{saved, N}</c> is however ever returned (for obvious reasons).</p>
</desc>
</func>
@@ -564,9 +564,9 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<name since="">ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
- <p>This function works as <seealso marker="#ctp-1"><c>ctp/1</c></seealso>, but only disables
- tracing set up with <seealso marker="#tpl-2"><c>tpl/2</c></seealso>
- (not with <seealso marker="#tp-2"><c>tp/2</c></seealso>).</p>
+ <p>This function works as <seemfa marker="#ctp/1"><c>ctp/1</c></seemfa>, but only disables
+ tracing set up with <seemfa marker="#tpl/2"><c>tpl/2</c></seemfa>
+ (not with <seemfa marker="#tp/2"><c>tp/2</c></seemfa>).</p>
</desc>
</func>
<func>
@@ -601,9 +601,9 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<name since="">ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
- <p>This function works as <seealso marker="#ctp-1"><c>ctp/1</c></seealso>, but only disables
- tracing set up with <seealso marker="#tp-2"><c>tp/2</c></seealso>
- (not with <seealso marker="#tpl-2"><c>tpl/2</c></seealso>).</p>
+ <p>This function works as <seemfa marker="#ctp/1"><c>ctp/1</c></seemfa>, but only disables
+ tracing set up with <seemfa marker="#tp/2"><c>tp/2</c></seemfa>
+ (not with <seemfa marker="#tpl/2"><c>tpl/2</c></seemfa>).</p>
</desc>
</func>
<func>
@@ -619,7 +619,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
trace event (<c>send</c> or <c>'receive'</c>). It will revert back
to the default behavior of tracing all triggered events.</p>
<p>The return value follow the same style as for
- <seealso marker="#ctp-1"><c>ctp/1</c></seealso>.</p>
+ <seemfa marker="#ctp/1"><c>ctp/1</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -628,14 +628,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<desc>
<p>Use this function to recall all match specifications previously
used in the session (i. e. previously saved during calls
- to <seealso marker="#tp-2"><c>tp/2</c></seealso>, and built-in match specifications.
+ to <seemfa marker="#tp/2"><c>tp/2</c></seemfa>, and built-in match specifications.
This is very useful, as a complicated
match_spec can be quite awkward to write. Note that the
- match specifications are lost if <seealso marker="#stop-0"><c>stop/0</c></seealso> is called.</p>
+ match specifications are lost if <seemfa marker="#stop/0"><c>stop/0</c></seemfa> is called.</p>
<p>Match specifications used can be saved in a file (if a
read-write file system is present) for use in later
- debugging sessions, see <seealso marker="#wtp-1"><c>wtp/1</c></seealso>
- and <seealso marker="#rtp-1"><c>rtp/1</c></seealso></p>
+ debugging sessions, see <seemfa marker="#wtp/1"><c>wtp/1</c></seemfa>
+ and <seemfa marker="#rtp/1"><c>rtp/1</c></seemfa></p>
<p>There are three built-in trace patterns:
<c>exception_trace</c>, <c>caller_trace</c>
and <c>caller_exception_trace</c> (or <c>x</c>, <c>c</c> and
@@ -658,10 +658,10 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<fsummary>Delete all saved match specifications.</fsummary>
<desc>
<p>Use this function to "forget" all match specifications
- saved during calls to <seealso marker="#tp-2"><c>tp/2</c></seealso>.
+ saved during calls to <seemfa marker="#tp/2"><c>tp/2</c></seemfa>.
This is useful when one wants to restore other match
- specifications from a file with <seealso marker="#rtp-1"><c>rtp/1</c></seealso>. Use
- <seealso marker="#dtp-1"><c>dtp/1</c></seealso> to delete specific saved match specifications.</p>
+ specifications from a file with <seemfa marker="#rtp/1"><c>rtp/1</c></seemfa>. Use
+ <seemfa marker="#dtp/1"><c>dtp/1</c></seemfa> to delete specific saved match specifications.</p>
</desc>
</func>
<func>
@@ -672,7 +672,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</type>
<desc>
<p>Use this function to "forget" a specific match specification
- saved during calls to <seealso marker="#tp-2"><c>tp/2</c></seealso>.</p>
+ saved during calls to <seemfa marker="#tp/2"><c>tp/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -684,12 +684,12 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</type>
<desc>
<p>This function will save all match specifications saved
- during the session (during calls to <seealso marker="#tp-2"><c>tp/2</c></seealso>)
+ during the session (during calls to <seemfa marker="#tp/2"><c>tp/2</c></seemfa>)
and built-in match specifications in a text
file with the name designated by <c>Name</c>. The format
of the file is textual, why it can be edited with an
ordinary text editor, and then restored with
- <seealso marker="#rtp-1"><c>rtp/1</c></seealso>. </p>
+ <seemfa marker="#rtp/1"><c>rtp/1</c></seemfa>. </p>
<p>Each match spec in the file ends with a full stop
(<c>.</c>) and new (syntactically correct) match
specifications can be added to the file manually.</p>
@@ -707,7 +707,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</type>
<desc>
<p>This function reads match specifications from a file
- (possibly) generated by the <seealso marker="#wtp-1"><c>wtp/1</c></seealso>
+ (possibly) generated by the <seemfa marker="#wtp/1"><c>wtp/1</c></seemfa>
function. It checks
the syntax of all match specifications and verifies that
they are correct. The error handling principle is "all or
@@ -716,7 +716,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
saved match specifications for the running system. </p>
<p>The match specifications in the file are <em>merged</em>
with the current match specifications, so that no duplicates
- are generated. Use <seealso marker="#ltp-0"><c>ltp/0</c></seealso>
+ are generated. Use <seemfa marker="#ltp/0"><c>ltp/0</c></seemfa>
to see what numbers were
assigned to the specifications from the file.</p>
<p>The function will return an error, either due to I/O
@@ -736,9 +736,9 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</type>
<desc>
<p>The <c>dbg</c> server keeps a list of nodes where tracing
- should be performed. Whenever a <seealso marker="#tp-2"><c>tp/2</c></seealso> call or a
- <seealso marker="#p-2"><c>p/2</c></seealso> call is made, it is executed for all nodes in this
- list including the local node (except for <seealso marker="#p-2"><c>p/2</c></seealso> with a
+ should be performed. Whenever a <seemfa marker="#tp/2"><c>tp/2</c></seemfa> call or a
+ <seemfa marker="#p/2"><c>p/2</c></seemfa> call is made, it is executed for all nodes in this
+ list including the local node (except for <seemfa marker="#p/2"><c>p/2</c></seemfa> with a
specific <c>pid()</c> or <c>port()</c> as first argument, in which case the
command is executed only on the node where the designated
process or port resides).
@@ -750,17 +750,17 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
distribution). If no tracer process is running on the local
node, the error reason <c>no_local_tracer</c> is returned. The
tracer process on the local node must be started with the
- <seealso marker="#tracer-2"><c>tracer/0/2</c></seealso> function.
+ <seemfa marker="#tracer/2"><c>tracer/0/2</c></seemfa> function.
</p>
<p>If <c>Nodename</c> is the local node, the error reason
<c>cant_add_local_node</c> is returned.
</p>
- <p>If a trace port (see <seealso marker="#trace_port-2"><c>trace_port/2</c></seealso>) is
+ <p>If a trace port (see <seemfa marker="#trace_port/2"><c>trace_port/2</c></seemfa>) is
running on the local node, remote nodes cannot be traced with
a tracer process. The error reason
<c>cant_trace_remote_pid_to_local_port</c> is returned. A
trace port can however be started on the remote node with the
- <seealso marker="#tracer-3"><c>tracer/3</c></seealso> function.
+ <seemfa marker="#tracer/3"><c>tracer/3</c></seemfa> function.
</p>
<p>The function will also return an error if the node
<c>Nodename</c> is not reachable.</p>
@@ -774,8 +774,8 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</type>
<desc>
<p>Clears a node from the list of traced nodes. Subsequent
- calls to <seealso marker="#tp-2"><c>tp/2</c></seealso> and
- <seealso marker="#p-2"><c>p/2</c></seealso> will not consider that
+ calls to <seemfa marker="#tp/2"><c>tp/2</c></seemfa> and
+ <seemfa marker="#p/2"><c>p/2</c></seemfa> will not consider that
node, but tracing already activated on the node will continue
to be in effect.</p>
<p>Returns <c>ok</c>, cannot fail.</p>
@@ -794,14 +794,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<desc>
<p>This function starts a server on the local node that will
be the recipient of all trace messages. All subsequent calls
- to <seealso marker="#p-2"><c>p/2</c></seealso> will result in messages sent to the newly
+ to <seemfa marker="#p/2"><c>p/2</c></seemfa> will result in messages sent to the newly
started trace server.</p>
<p>A trace server started in this way will simply display the
trace messages in a formatted way in the Erlang shell
- (i. e. use io:format). See <seealso marker="#tracer-2"><c>tracer/2</c></seealso>
+ (i. e. use io:format). See <seemfa marker="#tracer/2"><c>tracer/2</c></seemfa>
for a description of how the trace message handler can be customized.
</p>
- <p>To start a similar tracer on a remote node, use <seealso marker="#n-1"><c>n/1</c></seealso>.</p>
+ <p>To start a similar tracer on a remote node, use <seemfa marker="#n/1"><c>n/1</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -825,9 +825,9 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
by a receiving process (<c>process</c>), by a tracer port
(<c>port</c>) or by a tracer module
(<c>module</c>). For a description about tracer ports see
- <seealso marker="#trace_port-2"><c>trace_port/2</c></seealso>
+ <seemfa marker="#trace_port/2"><c>trace_port/2</c></seemfa>
and for a tracer modules see
- <seealso marker="erts:erl_tracer"><c>erl_tracer</c></seealso>.
+ <seeerl marker="erts:erl_tracer"><c>erl_tracer</c></seeerl>.
</p>
<p>If <c>Type</c> is <c>process</c>, a message handler function can
be specified (<c>HandlerSpec</c>). The handler function, which
@@ -843,10 +843,10 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<p>If <c>Type</c> is <c>port</c>, then the second parameter should
be a <em>fun</em> which takes no arguments and returns a
newly opened trace port when called. Such a <em>fun</em> is
- preferably generated by calling <seealso marker="#trace_port-2"><c>trace_port/2</c></seealso>.
+ preferably generated by calling <seemfa marker="#trace_port/2"><c>trace_port/2</c></seemfa>.
</p>
<p>if <c>Type</c> is <c>module</c>, then the second parameter should
- be either a tuple describing the <seealso marker="erts:erl_tracer"><c>erl_tracer</c></seealso>
+ be either a tuple describing the <seeerl marker="erts:erl_tracer"><c>erl_tracer</c></seeerl>
module to be used for tracing and the state to be used for
that tracer module or a fun returning the same tuple.</p>
<p>If an error is returned, it can either be due to a tracer
@@ -854,7 +854,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
due to the <c>HandlerFun</c> throwing an exception.
</p>
<p>To start a similar tracer on a remote node, use
- <seealso marker="#tracer-3"><c>tracer/3</c></seealso>.
+ <seemfa marker="#tracer/3"><c>tracer/3</c></seemfa>.
</p>
</desc>
</func>
@@ -865,19 +865,19 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<v>Nodename = atom()</v>
</type>
<desc>
- <p>This function is equivalent to <seealso marker="#tracer-2"><c>tracer/2</c></seealso>, but acts on
+ <p>This function is equivalent to <seemfa marker="#tracer/2"><c>tracer/2</c></seemfa>, but acts on
the given node. A tracer is started on the node
(<c>Nodename</c>) and the node is added to the list of traced nodes.
</p>
<note>
- <p>This function is not equivalent to <seealso marker="#n-1"><c>n/1</c></seealso>. While
- <seealso marker="#n-1"><c>n/1</c></seealso> starts a process tracer which redirects all trace
+ <p>This function is not equivalent to <seemfa marker="#n/1"><c>n/1</c></seemfa>. While
+ <seemfa marker="#n/1"><c>n/1</c></seemfa> starts a process tracer which redirects all trace
information to a process tracer on the local node (i.e. the
- trace control node), <seealso marker="#tracer-3"><c>tracer/3</c></seealso> starts a tracer of any
+ trace control node), <seemfa marker="#tracer/3"><c>tracer/3</c></seemfa> starts a tracer of any
type which is independent of the tracer on the trace control
node.</p>
</note>
- <p>For details, see <seealso marker="#tracer-2"><c>tracer/2</c></seealso>.</p>
+ <p>For details, see <seemfa marker="#tracer/2"><c>tracer/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -909,9 +909,9 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<c>file</c> and the <c>ip</c> trace drivers. The file driver
sends all trace messages into one or several binary files,
from where they later can be fetched and processed with the
- <seealso marker="#trace_client-2"><c>trace_client/2</c></seealso> function. The ip driver opens a TCP/IP
+ <seemfa marker="#trace_client/2"><c>trace_client/2</c></seemfa> function. The ip driver opens a TCP/IP
port where it listens for connections. When a client
- (preferably started by calling <seealso marker="#trace_client-2"><c>trace_client/2</c></seealso> on
+ (preferably started by calling <seemfa marker="#trace_client/2"><c>trace_client/2</c></seemfa> on
another Erlang node) connects, all trace messages are sent
over the IP network for further processing by the remote
client. </p>
@@ -950,7 +950,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
as fast as they are produced by the runtime system, a special
message is sent, which indicates how many messages that are
dropped. That message will arrive at the handler function
- specified in <seealso marker="#trace_client-3"><c>trace_client/3</c></seealso>
+ specified in <seemfa marker="#trace_client/3"><c>trace_client/3</c></seemfa>
as the tuple <c>{drop, N}</c> where <c>N</c> is the number of consecutive messages
dropped. In case of heavy tracing, drop's are likely to occur,
and they surely occur if no client is reading the trace
@@ -1029,7 +1029,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<p>This function starts a trace client that reads the output
created by a trace port driver and handles it in mostly the
same way as a tracer process created by the
- <seealso marker="#tracer-0"><c>tracer/0</c></seealso> function.</p>
+ <seemfa marker="#tracer/0"><c>tracer/0</c></seemfa> function.</p>
<p>If <c>Type</c> is <c>file</c>, the client reads all trace
messages stored in the file named <c>Filename</c> or
specified by <c>WrapFilesSpec</c> (must be the same as used
@@ -1040,7 +1040,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<p>If <c>Type</c> is <c>follow_file</c>, the client behaves as
in the <c>file</c> case, but keeps trying to read (and
process) more data
- from the file until stopped by <seealso marker="#stop_trace_client-1"><c>stop_trace_client/1</c></seealso>.
+ from the file until stopped by <seemfa marker="#stop_trace_client/1"><c>stop_trace_client/1</c></seemfa>.
<c>WrapFilesSpec</c> is not allowed as second argument
for this <c>Type</c>.</p>
<p>If <c>Type</c> is <c>ip</c>, the client connects to the
@@ -1096,10 +1096,10 @@ hello</pre>
<v>InitialData = term()</v>
</type>
<desc>
- <p>This function works exactly as <seealso marker="#trace_client-2"><c>trace_client/2</c></seealso>,
+ <p>This function works exactly as <seemfa marker="#trace_client/2"><c>trace_client/2</c></seemfa>,
but allows you to write your own handler function. The handler
function works mostly as the one described in
- <seealso marker="#tracer-2"><c>tracer/2</c></seealso>, but will also have to be prepared to handle
+ <seemfa marker="#tracer/2"><c>tracer/2</c></seemfa>, but will also have to be prepared to handle
trace messages of the form <c>{drop, N}</c>, where <c>N</c> is
the number of dropped messages. This pseudo trace message will
only occur if the ip trace driver is used.</p>
@@ -1118,8 +1118,8 @@ hello</pre>
<desc>
<p>This function shuts down a previously started trace
client. The <c>Pid</c> argument is the process id returned
- from the <seealso marker="#trace_client-2"><c>trace_client/2</c></seealso>
- or <seealso marker="#trace_client-3"><c>trace_client/3</c></seealso> call.</p>
+ from the <seemfa marker="#trace_client/2"><c>trace_client/2</c></seemfa>
+ or <seemfa marker="#trace_client/3"><c>trace_client/3</c></seemfa> call.</p>
</desc>
</func>
<func>
@@ -1272,7 +1272,7 @@ SeqTrace [0]: (&lt;0.30.0>) &lt;0.25.0> ! {dbg,{ok,&lt;0.31.0>}} [Serial: {4,5}]
of causing a deadlock. This will happen if a group leader process generates a trace
message and the tracer process, by calling the trace handler function, sends an IO
request to the same group leader. The problem can only occur if the trace handler
- prints to tty using an <c>io</c> function such as <seealso marker="stdlib:io#format-2"><c>format/2</c></seealso>.
+ prints to tty using an <c>io</c> function such as <seemfa marker="stdlib:io#format/2"><c>format/2</c></seemfa>.
Note that when
<c>dbg:p(all,call)</c> is called, IO processes are also traced.
Here's an example:</p>
diff --git a/lib/runtime_tools/doc/src/dyntrace.xml b/lib/runtime_tools/doc/src/dyntrace.xml
index 4935dfcd71..85e9ed8899 100644
--- a/lib/runtime_tools/doc/src/dyntrace.xml
+++ b/lib/runtime_tools/doc/src/dyntrace.xml
@@ -43,7 +43,7 @@
</list>
<p>Both building with dynamic trace probes and using them is experimental and unsupported by Erlang/OTP. It is included as an option for the developer to trace and debug performance issues in their systems.</p>
<p>The original implementation is mostly done by Scott Lystiger Fritchie as an Open Source Contribution and it should be viewed as such even though the source for dynamic tracing as well as this module is included in the main distribution. However, the ability to use dynamic tracing of the virtual machine is a very valuable contribution which OTP has every intention to maintain as a tool for the developer.</p>
- <p>How to write <c>d</c> programs or <c>systemtap</c> scripts can be learned from books and from a lot of pages on the Internet. This manual page does not include any documentation about using the dynamic trace tools of respective platform. The <c>examples</c> directory of the <c>runtime_tools</c> application however contains comprehensive examples of both <c>d</c> and <c>systemtap</c> programs that will help you get started. Another source of information is the <seealso marker="DTRACE">dtrace</seealso> and <seealso marker="SYSTEMTAP">systemtap</seealso> chapters in the Runtime Tools Users' Guide.</p>
+ <p>How to write <c>d</c> programs or <c>systemtap</c> scripts can be learned from books and from a lot of pages on the Internet. This manual page does not include any documentation about using the dynamic trace tools of respective platform. The <c>examples</c> directory of the <c>runtime_tools</c> application however contains comprehensive examples of both <c>d</c> and <c>systemtap</c> programs that will help you get started. Another source of information is the <seeguide marker="DTRACE">dtrace</seeguide> and <seeguide marker="SYSTEMTAP">systemtap</seeguide> chapters in the Runtime Tools Users' Guide.</p>
</description>
<funcs>
<func>
@@ -51,8 +51,8 @@
<fsummary>Check if dynamic tracing is available</fsummary>
<desc>
<p>This function uses the NIF library to determine if dynamic
- tracing is available. Usually calling <seealso
- marker="erts:erlang#system_info/1">erlang:system_info/1</seealso>
+ tracing is available. Usually calling <seemfa
+ marker="erts:erlang#system_info/1">erlang:system_info/1</seemfa>
is a better indicator of the availability of dynamic
tracing.</p>
<p>The function will throw an exception if the <c>dyntrace</c> NIF library could not be loaded by the on_load function of this module.</p>
@@ -83,21 +83,21 @@
<name since="OTP R15B01">p(integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
- <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
+ <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seemfa marker="#p/2">p/2</seemfa>.</p>
</desc>
</func>
<func>
<name since="OTP R15B01">p(integer() | string(), integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
- <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
+ <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seemfa marker="#p/2">p/2</seemfa>.</p>
</desc>
</func>
<func>
<name since="OTP R15B01">p(integer(), integer() | string(), integer() | string(), integer() | string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
- <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
+ <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seemfa marker="#p/2">p/2</seemfa>.</p>
<p>There can be no more than four parameters of any type (integer() or string()), so the first parameter has to be an integer() and the last a string().</p>
</desc>
</func>
@@ -105,7 +105,7 @@
<name since="OTP R15B01">p(integer(), integer(), integer() | string(), integer() | string(), string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
- <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
+ <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seemfa marker="#p/2">p/2</seemfa>.</p>
<p>There can be no more than four parameters of any type (integer() or string()), so the first two parameters has to be integer()'s and the last two string()'s.</p>
</desc>
</func>
@@ -113,7 +113,7 @@
<name since="OTP R15B01">p(integer(), integer(), integer(), integer() | string(), string(), string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
- <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
+ <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seemfa marker="#p/2">p/2</seemfa>.</p>
<p>There can be no more than four parameters of any type (integer() or string()), so the first three parameters has to be integer()'s and the last three string()'s.</p>
</desc>
</func>
@@ -140,11 +140,11 @@
<p>This function returns the user tag set in the current
process or, if no user tag is present, the last user tag sent
to the process together with a message (in the same way as
- <seealso marker="kernel:seq_trace">sequential trace
- tokens</seealso> are spread to other processes together with
+ <seeerl marker="kernel:seq_trace">sequential trace
+ tokens</seeerl> are spread to other processes together with
messages. For an explanation of how user tags can be spread
- together with messages, see <seealso
- marker="#spread_tag/1">spread_tag/1</seealso>. If no tag is
+ together with messages, see <seemfa
+ marker="#spread_tag/1">spread_tag/1</seemfa>. If no tag is
found or dynamic tracing is not available, it returns
<c>undefined</c></p>
</desc>
@@ -166,13 +166,13 @@
<name since="OTP R15B01">spread_tag(boolean()) -> TagData</name>
<fsummary>Start or stop spreading dynamic trace user tags with the next message.</fsummary>
<type>
- <v>TagData = opaque data that can be used as parameter to <seealso marker="#restore_tag/1">restore_tag/1</seealso></v>
+ <v>TagData = opaque data that can be used as parameter to <seemfa marker="#restore_tag/1">restore_tag/1</seemfa></v>
</type>
<desc>
<p>This function controls if user tags are to be spread to other processes with the next message. Spreading of user tags work like spreading of sequential trace tokens, so that a received user tag will be active in the process until the next message arrives (if that message does not also contain the user tag.</p>
<p>This functionality is used when a client process communicates with a file i/o-server to spread the user tag to the I/O-server and then down to the efile_drv driver. By using <c>spread_tag/1</c> and <c>restore_tag/1</c>, one can enable or disable spreading of user tags to other processes and then restore the previous state of the user tag. The TagData returned from this call contains all previous information so the state (including any previously spread user tags) will be completely restored by a later call to <c>restore_tag/1</c>.</p>
- <p>The <seealso marker="kernel:file">file</seealso> module already spread's tags, so there is noo need to manually call these function to get user tags spread to the efile driver through that module.</p>
- <p>The most use of this function would be if one for example uses the <seealso marker="stdlib:io">io</seealso> module to communicate with an I/O-server for a regular file, like in the following example:</p>
+ <p>The <seeerl marker="kernel:file">file</seeerl> module already spread's tags, so there is noo need to manually call these function to get user tags spread to the efile driver through that module.</p>
+ <p>The most use of this function would be if one for example uses the <seeerl marker="stdlib:io">io</seeerl> module to communicate with an I/O-server for a regular file, like in the following example:</p>
<pre>
f() ->
{ok, F} = file:open("test.tst",[write]),
@@ -188,10 +188,10 @@ f() ->
<name since="OTP R15B01">restore_tag(TagData) -> true</name>
<fsummary>Restore to a previous state of user tag spreading.</fsummary>
<type>
- <v>TagData = opaque data returned by <seealso marker="#spread_tag/1">spread_tag/1</seealso></v>
+ <v>TagData = opaque data returned by <seemfa marker="#spread_tag/1">spread_tag/1</seemfa></v>
</type>
<desc>
- <p>Restores the previous state of user tags and their spreading as it was before a call to <seealso marker="#spread_tag/1">spread_tag/1</seealso>. Note that the restoring is not limited to the same process, one can utilize this to turn off spreding in one process and restore it in a newly created, the one that actually is going to send messages:</p>
+ <p>Restores the previous state of user tags and their spreading as it was before a call to <seemfa marker="#spread_tag/1">spread_tag/1</seemfa>. Note that the restoring is not limited to the same process, one can utilize this to turn off spreding in one process and restore it in a newly created, the one that actually is going to send messages:</p>
<pre>
f() ->
TagData=dyntrace:spread_tag(false),
diff --git a/lib/runtime_tools/doc/src/erts_alloc_config.xml b/lib/runtime_tools/doc/src/erts_alloc_config.xml
index 5bcce1b5e3..f26721b9e9 100644
--- a/lib/runtime_tools/doc/src/erts_alloc_config.xml
+++ b/lib/runtime_tools/doc/src/erts_alloc_config.xml
@@ -37,10 +37,10 @@
tool and might be subject to backward incompatible
changes.</p>
</note>
- <p><seealso marker="erts:erts_alloc">erts_alloc(3)</seealso> is an
+ <p><seecref marker="erts:erts_alloc">erts_alloc(3)</seecref> is an
Erlang Run-Time System internal memory allocator library.
<c>erts_alloc_config</c> is intended to be used to aid creation
- of an <seealso marker="erts:erts_alloc">erts_alloc(3)</seealso>
+ of an <seecref marker="erts:erts_alloc">erts_alloc(3)</seecref>
configuration that is suitable for a limited number of runtime
scenarios. The configuration that <c>erts_alloc_config</c>
produce is intended as a suggestion, and may need to be
@@ -65,25 +65,25 @@
<p>A configuration is created in the following way:</p>
<list type="bulleted">
<item>
- <p>Pass the <seealso marker="erts:erts_alloc#Mea">+Mea config</seealso>
+ <p>Pass the <seecref marker="erts:erts_alloc#Mea">+Mea config</seecref>
command-line flag to the Erlang runtime system you are going
to use for creation of the allocator configuration. It will
disable features that prevent <c>erts_alloc_config</c> from
doing its job. Note, you should <em>not</em> use this flag
when using the created configuration. Also note that it is
important that you use the same
- <seealso marker="erts:erl#+S">amount of schedulers</seealso>
+ <seecom marker="erts:erl#+S">amount of schedulers</seecom>
when creating the configuration as you are going the use on
the system using the configuration.</p>
</item>
<item>
<p>Run your applications with different scenarios (the more
the better) and save information about each scenario by calling
- <seealso marker="#save_scenario/0">save_scenario/0</seealso>.
+ <seemfa marker="#save_scenario/0">save_scenario/0</seemfa>.
It may be hard to know when the applications are at an (for
<c>erts_alloc_config</c>) important runtime scenario. A good
approach may therefore be to call
- <seealso marker="#save_scenario/0">save_scenario/0</seealso>
+ <seemfa marker="#save_scenario/0">save_scenario/0</seemfa>
repeatedly, e.g. once every tenth second. Note that it is
important that your applications reach the runtime scenarios
that are important for <c>erts_alloc_config</c> when you are
@@ -92,13 +92,13 @@
</item>
<item>
<p>When you have covered all scenarios, call
- <seealso marker="#make_config/1">make_config/1</seealso>
+ <seemfa marker="#make_config/1">make_config/1</seemfa>
in order to create a configuration. The configuration is
written to a file that you have chosen. This configuration
file can later be read by an Erlang runtime-system at
startup. Pass the command line argument
- <seealso marker="erts:erl#args_file">-args_file FileName</seealso>
- to the <seealso marker="erts:erl">erl(1)</seealso> command.</p>
+ <seecom marker="erts:erl#args_file">-args_file FileName</seecom>
+ to the <seecom marker="erts:erl">erl(1)</seecom> command.</p>
</item>
<item>
<p>The configuration produced by <c>erts_alloc_config</c> may
@@ -106,10 +106,10 @@
file produced by <c>erts_alloc_config</c>; instead, put your
modifications in another file and load this file after the
file produced by <c>erts_alloc_config</c>. That is, put the
- <seealso marker="erts:erl#args_file">-args_file FileName</seealso>
+ <seecom marker="erts:erl#args_file">-args_file FileName</seecom>
argument that reads your modification file later on the
command-line than the
- <seealso marker="erts:erl#args_file">-args_file FileName</seealso>
+ <seecom marker="erts:erl#args_file">-args_file FileName</seecom>
argument that reads the configuration file produced by
<c>erts_alloc_config</c>. If a memory allocation parameter
appear multiple times, the last version of will be used, i.e.,
@@ -144,13 +144,13 @@
<desc>
<p><c>save_scenario/0</c> saves information about the current
runtime scenario. This information will later be used when
- <seealso marker="#make_config/0">make_config/0</seealso>,
- or <seealso marker="#make_config/1">make_config/1</seealso>
+ <seemfa marker="#make_config/0">make_config/0</seemfa>,
+ or <seemfa marker="#make_config/1">make_config/1</seemfa>
is called.</p>
<p>The first time <c>save_scenario/0</c> is called a server
will be started. This server will save runtime scenarios. All
saved scenarios can be removed by calling
- <seealso marker="#make_config/0">stop/0</seealso>.</p>
+ <seemfa marker="#make_config/0">stop/0</seemfa>.</p>
</desc>
</func>
<func>
@@ -161,7 +161,7 @@
</type>
<desc>
<p>This is the same as calling
- <seealso marker="#make_config/1">make_config(group_leader())</seealso>.</p>
+ <seemfa marker="#make_config/1">make_config(group_leader())</seemfa>.</p>
</desc>
</func>
<func>
@@ -173,7 +173,7 @@
</type>
<desc>
<p><c>make_config/1</c> uses the information previously saved by
- <seealso marker="#save_scenario/0">save_scenario/0</seealso>
+ <seemfa marker="#save_scenario/0">save_scenario/0</seemfa>
in order to produce an <c>erts_alloc</c> configuration. At
least one scenario have had to be saved. All scenarios
previously saved will be used when creating the
@@ -183,9 +183,9 @@
filename. A file named <c>FileNameOrIODev</c> is created and
the configuration will be written to that file. If
<c>FileNameOrIODev</c> is an
- <seealso marker="stdlib:io">io_device()</seealso> (see the
+ <seeerl marker="stdlib:io">io_device()</seeerl> (see the
documentation of the module
- <seealso marker="stdlib:io">io</seealso>), the configuration
+ <seeerl marker="stdlib:io">io</seeerl>), the configuration
will be written to the io device.</p>
</desc>
</func>
@@ -203,9 +203,9 @@
<section>
<title>See Also</title>
- <p><seealso marker="erts:erts_alloc">erts_alloc(3)</seealso>,
- <seealso marker="erts:erl">erl(1)</seealso>,
- <seealso marker="stdlib:io">io(3)</seealso></p>
+ <p><seecref marker="erts:erts_alloc">erts_alloc(3)</seecref>,
+ <seecom marker="erts:erl">erl(1)</seecom>,
+ <seeerl marker="stdlib:io">io(3)</seeerl></p>
</section>
</erlref>
diff --git a/lib/runtime_tools/doc/src/msacc.xml b/lib/runtime_tools/doc/src/msacc.xml
index ae089de8d0..0a716136fd 100644
--- a/lib/runtime_tools/doc/src/msacc.xml
+++ b/lib/runtime_tools/doc/src/msacc.xml
@@ -37,8 +37,8 @@
<p>This module implements some convenience functions for analyzing
microstate accounting data. For details about how to use the basic api and
what the different states represent see
- <seealso marker="erts:erlang#statistics_microstate_accounting"><c>
- erlang:statistics(microstate_accounting)</c></seealso>.</p>
+ <seeerl marker="erts:erlang#statistics_microstate_accounting"><c>
+ erlang:statistics(microstate_accounting)</c></seeerl>.</p>
<marker id="msacc_print_example"></marker>
<p><em>Basic Scenario</em></p>
<pre>1> <input>msacc:start(1000).</input>
@@ -64,10 +64,10 @@ Stats per type:
ok
</pre>
<p>This first command enables microstate accounting for 1000 milliseconds.
- See <seealso marker="#start-0"><c>start/0</c></seealso>,
- <seealso marker="#stop-0"><c>stop/0</c></seealso>,
- <seealso marker="#reset-0"><c>reset/0</c></seealso> and
- <seealso marker="#start-1"><c>start/1</c></seealso> for more details.
+ See <seemfa marker="#start/0"><c>start/0</c></seemfa>,
+ <seemfa marker="#stop/0"><c>stop/0</c></seemfa>,
+ <seemfa marker="#reset/0"><c>reset/0</c></seemfa> and
+ <seemfa marker="#start/1"><c>start/1</c></seemfa> for more details.
The second command prints the statistics gathered during that time.
First three general statistics are printed.</p>
<taglist>
@@ -113,7 +113,7 @@ ok
<desc><p>A map containing information about a specific thread. The
percentages in the map can be either run-time or real-time depending
on if <c>runtime</c> or <c>realtime</c> was requested from
- <seealso marker="#stats-2">stats/2</seealso>. <c>system</c> is the
+ <seemfa marker="#stats/2">stats/2</seemfa>. <c>system</c> is the
percentage of total system time for this specific thread.</p></desc>
</datatype>
<datatype>
@@ -133,14 +133,14 @@ ok
<datatype>
<name name="msacc_state"/>
<desc><p>The different states that a thread can be in. See
- <seealso marker="erts:erlang#statistics_microstate_accounting">
- erlang:statistics(microstate_accounting)</seealso> for details.
+ <seeerl marker="erts:erlang#statistics_microstate_accounting">
+ erlang:statistics(microstate_accounting)</seeerl> for details.
</p></desc>
</datatype>
<datatype>
<name name="msacc_print_options"/>
<desc><p>The different options that can be given to
- <seealso marker="#print-2"><c>print/2</c></seealso>.
+ <seemfa marker="#print/2"><c>print/2</c></seemfa>.
</p></desc>
</datatype>
</datatypes>
@@ -192,9 +192,9 @@ ok
<p>
Prints the current microstate accounting to standard out.
Same as
- <seealso marker="#print-1">
+ <seemfa marker="#print/1">
<c>msacc:print(msacc:stats(),#{}).</c>
- </seealso>
+ </seemfa>
</p>
</desc>
</func>
@@ -204,9 +204,9 @@ ok
<desc>
<p>Print the given microstate statistics values to stdout.
Same as
- <seealso marker="#print-1">
+ <seemfa marker="#print/1">
<c>msacc:print(DataOrStats,#{}).</c>
- </seealso>
+ </seemfa>
</p>
</desc>
</func>
@@ -219,7 +219,7 @@ ok
reference manual for a brief description of what the fields mean.</p>
<p>It is possible to print more specific types of statistics by
first manipulating the <c>DataOrStats</c> using
- <seealso marker="#stats-2"><c>stats/2</c></seealso>.
+ <seemfa marker="#stats/2"><c>stats/2</c></seemfa>.
For instance if you want to print the percentage of run-time for each
thread you can do:</p>
<pre><input>msacc:print(msacc:stats(runtime,msacc:stats())).</input></pre>
@@ -239,7 +239,7 @@ ok
<desc>
<p>Print the given microstate statistics values to the given file
or device. The other arguments behave the same way as for
- <seealso marker="#print-2"><c>print/2</c></seealso>.</p>
+ <seemfa marker="#print/2"><c>print/2</c></seemfa>.</p>
</desc>
</func>
<func>
@@ -248,8 +248,8 @@ ok
<desc>
<p>Returns a runtime system independent version of the microstate
statistics data presented by
- <seealso marker="erts:erlang#statistics_microstate_accounting">
- <c>erlang:statistics(microstate_accounting)</c></seealso>.
+ <seeerl marker="erts:erlang#statistics_microstate_accounting">
+ <c>erlang:statistics(microstate_accounting)</c></seeerl>.
All counters have been normalized to be in microsecond resolution.</p>
</desc>
</func>
@@ -289,16 +289,16 @@ ok
<fsummary></fsummary>
<desc>
<p>Dumps the current microstate statistics counters to a file that can
- be parsed with <seealso marker="kernel:file#consult/1">
- file:consult/1</seealso>.</p>
+ be parsed with <seemfa marker="kernel:file#consult/1">
+ file:consult/1</seemfa>.</p>
</desc>
</func>
<func>
<name name="from_file" arity="1" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
- <p>Read a file dump produced by <seealso marker="#to_file/1">
- to_file(Filename)</seealso>.</p>
+ <p>Read a file dump produced by <seemfa marker="#to_file/1">
+ to_file(Filename)</seemfa>.</p>
</desc>
</func>
</funcs>
diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml
index 210d63687c..6303397f57 100644
--- a/lib/runtime_tools/doc/src/notes.xml
+++ b/lib/runtime_tools/doc/src/notes.xml
@@ -32,6 +32,62 @@
<p>This document describes the changes made to the Runtime_Tools
application.</p>
+<section><title>Runtime_Tools 1.16</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Clarify documentation of module 'scheduler'.</p>
+ <p>
+ Own Id: OTP-17208 Aux Id: GH-4502, PR-4532 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Runtime_Tools 1.15.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed a crash in <c>appmon_info</c> triggered by trying
+ to read port info from a port that was in the process of
+ terminating.</p>
+ <p>
+ <c>appmon_info</c> is used by <c>observer</c> to get
+ information from the observed node.</p>
+ <p>
+ Own Id: OTP-16787 Aux Id: PR-2673 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Runtime_Tools 1.15</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Improved the presentation of allocations and carriers
+ in the <c>instrument</c> module.</p>
+ <p>
+ Own Id: OTP-16327</p>
+ </item>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Runtime_Tools 1.14</title>
<section><title>Improvements and New Features</title>
@@ -327,10 +383,10 @@
added to runtime_tools that can assist in gathering and
interpreting the data from Microstate accounting.</p>
<p>
- For more information see <seealso
+ For more information see <seeerl
marker="erts:erlang#statistics_microstate_accounting">erlang:statistics(microstate_accounting,
- _)</seealso> and the <seealso
- marker="runtime_tools:msacc">msacc</seealso> module in
+ _)</seeerl> and the <seeerl
+ marker="runtime_tools:msacc">msacc</seeerl> module in
runtime_tools.</p>
<p>
Own Id: OTP-12345</p>
@@ -366,9 +422,9 @@
configure option <c>--with-dynamic-trace=lttng</c>.</p>
<p>The dynamic trace module <c>dyntrace</c> is now
capable to be used as a LTTng sink for Erlang tracing.
- For a list of all tracepoints, see <seealso
+ For a list of all tracepoints, see <seeguide
marker="runtime_tools:LTTng">Runtime Tools User's
- Guide</seealso> .</p>
+ Guide</seeguide> .</p>
<p>This feature also introduces an incompatible change in
trace tags. The trace tags <c>gc_start</c> and
<c>gc_end</c> has been split into <c>gc_minor_start</c>,
@@ -547,18 +603,18 @@
</item>
<item>
<p>
- The <seealso
- marker="kernel:app"><c>app</c></seealso>-file key
- <seealso
- marker="kernel:app#runtime_dependencies"><c>runtime_dependencies</c></seealso>
+ The <seefile
+ marker="kernel:app"><c>app</c></seefile>-file key
+ <seefile
+ marker="kernel:app#runtime_dependencies"><c>runtime_dependencies</c></seefile>
has been introduced.</p>
<p>
Runtime dependencies have been added to all app-files in
OTP. Note that these may not be completely correct during
OTP 17, but this is actively being worked on.</p>
<p>
- The function <seealso
- marker="runtime_tools:system_information#sanity_check/0"><c>system_information:sanity_check/0</c></seealso>
+ The function <seemfa
+ marker="runtime_tools:system_information#sanity_check/0"><c>system_information:sanity_check/0</c></seemfa>
will verify all declared runtime dependencies in the
system when called.</p>
<p>
diff --git a/lib/runtime_tools/doc/src/scheduler.xml b/lib/runtime_tools/doc/src/scheduler.xml
index b033430183..d539ccb1c6 100644
--- a/lib/runtime_tools/doc/src/scheduler.xml
+++ b/lib/runtime_tools/doc/src/scheduler.xml
@@ -37,10 +37,10 @@
<description>
<p>This module contains utility functions for easier measurement and
calculation of scheduler utilization, otherwise obtained from calling the
- more primitive <seealso marker="erts:erlang#statistics_scheduler_wall_time">
- <c>statistics(scheduler_wall_time)</c></seealso>.</p>
- <p>The simplest usage is to call <seealso marker="#utilization-1">
- <c>scheduler:utilization(Seconds)</c></seealso>.</p>
+ more primitive <seeerl marker="erts:erlang#statistics_scheduler_wall_time">
+ <c>statistics(scheduler_wall_time)</c></seeerl>.</p>
+ <p>The simplest usage is to call <seemfa marker="#utilization/1">
+ <c>scheduler:utilization(Seconds)</c></seemfa>.</p>
</description>
<datatypes>
@@ -63,14 +63,17 @@
<taglist>
<tag><c>{normal, SchedulerId, Util, Percent}</c></tag>
<item>Scheduler utilization of a normal scheduler with number
- <c>SchedulerId</c>.</item>
+ <c>SchedulerId</c>. Schedulers that are not online will also be
+ included.
+ <seeerl marker="erts:erlang#system_info_schedulers_online">Online
+ schedulers</seeerl> have the lowest <c>SchedulerId</c>.</item>
<tag><c>{cpu, SchedulerId, Util, Percent}</c></tag>
<item>Scheduler utilization of a dirty-cpu scheduler with number
<c>SchedulerId</c>.</item>
<tag><c>{io, SchedulerId, Util, Percent}</c></tag>
<item>Scheduler utilization of a dirty-io scheduler with number
<c>SchedulerId</c>. This tuple will only exist if both samples were
- taken with <seealso marker="#sample_all-0"><c>sample_all/0</c></seealso>.</item>
+ taken with <seemfa marker="#sample_all/0"><c>sample_all/0</c></seemfa>.</item>
<tag><c>{total, Util, Percent}</c></tag>
<item>Total utilization of all normal and dirty-cpu schedulers.</item>
<tag><c>{weighted, Util, Percent}</c></tag>
@@ -117,6 +120,29 @@
<p>Calculate scheduler utilizations for the time interval from when
<c><anno>Sample</anno></c> was taken and "now". The same as calling
<c>scheduler:utilization(Sample, scheduler:sample_all())</c>.</p>
+ <note>
+ <p>
+ Scheduler utilization is measured as an average value over a time
+ interval, calculated as the difference between two samples. To get
+ good useful utilization values at least a couple of seconds should
+ have passed between the two samples. For this reason, you should not
+ do
+ </p>
+<pre>
+scheduler:utilization(scheduler:sample()). % DO NOT DO THIS!
+</pre>
+ <p>
+ The above example takes two samples in rapid succession and calculates
+ the scheduler utilization between them. The resulting values will
+ probably be more misleading than informative.
+ </p>
+ <p>
+ Instead use <seemfa marker="#utilization/1">
+ <c>scheduler:utilization(Seconds)</c></seemfa> or let some time pass
+ between <c>Sample=scheduler:sample()</c> and
+ <c>scheduler:utilization(Sample)</c>.
+ </p>
+ </note>
</desc>
</func>
@@ -126,8 +152,8 @@
<desc>
<p>Calculates scheduler utilizations for the time interval between
the two samples obtained from calling
- <seealso marker="#sample-0"><c>sample/0</c></seealso> or
- <seealso marker="#sample_all-0"><c>sample_all/0</c></seealso>.</p>
+ <seemfa marker="#sample/0"><c>sample/0</c></seemfa> or
+ <seemfa marker="#sample_all/0"><c>sample_all/0</c></seemfa>.</p>
</desc>
</func>
diff --git a/lib/runtime_tools/doc/src/system_information.xml b/lib/runtime_tools/doc/src/system_information.xml
index a356b5c6f8..8cb43ed520 100644
--- a/lib/runtime_tools/doc/src/system_information.xml
+++ b/lib/runtime_tools/doc/src/system_information.xml
@@ -60,8 +60,8 @@
is invalid.</p></item>
<tag><c><anno>MissingRuntimeDependencies</anno></c></tag>
<item><p>An application is missing
- <seealso marker="kernel:app#runtime_dependencies">runtime
- dependencies</seealso>. The second element identifies the
+ <seefile marker="kernel:app#runtime_dependencies">runtime
+ dependencies</seefile>. The second element identifies the
application (with version) that has missing dependencies.
The third element contains the missing dependencies.</p>
<p>Note that this check use application versions that
@@ -70,11 +70,11 @@
installed in the system, but if those are not loaded this
check will fail. The system will of course also fail when
used like this. This may happen when you have multiple
- <seealso marker="doc/system_principles:versions">branched
- versions</seealso> of the same application installed in the
+ <seeguide marker="system/system_principles:versions">branched
+ versions</seeguide> of the same application installed in the
system, but you do not use a
- <seealso marker="doc/system_principles:system_principles#BOOTSCRIPT">boot
- script</seealso> identifing the correct application version.</p>
+ <seeguide marker="system/system_principles:system_principles#BOOTSCRIPT">boot
+ script</seeguide> identifing the correct application version.</p>
</item>
</taglist>
<p>Currently the sanity check is limited to verifying
diff --git a/lib/runtime_tools/examples/function-calls.d b/lib/runtime_tools/examples/function-calls.d
index f8ca388228..a51ff51253 100644
--- a/lib/runtime_tools/examples/function-calls.d
+++ b/lib/runtime_tools/examples/function-calls.d
@@ -19,39 +19,85 @@
* %CopyrightEnd%
*/
+/**
+ * Triggered on local function entry
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the function
+ * @param arg2 depth
+ */
erlang*:::local-function-entry
{
printf("pid %s enter (local) %s depth %d\n",
copyinstr(arg0), copyinstr(arg1), arg2);
}
+/**
+ * Triggered on global function entry
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the function
+ * @param arg2 depth
+ */
erlang*:::global-function-entry
{
printf("pid %s enter (global) %s depth %d\n",
copyinstr(arg0), copyinstr(arg1), arg2);
}
+/**
+ * Triggered upon function return, either global or
+ * local
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the returned from function
+ * @param arg2 depth
+ */
erlang*:::function-return
{
printf("pid %s return %s depth %d\n",
copyinstr(arg0), copyinstr(arg1), arg2);
}
+/**
+ * Triggered on built-in function entry
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the function
+ */
erlang*:::bif-entry
{
printf("pid %s BIF entry mfa %s\n", copyinstr(arg0), copyinstr(arg1));
}
+/**
+ * Triggered on built-in function return
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the returned from function
+ */
erlang*:::bif-return
{
printf("pid %s BIF return mfa %s\n", copyinstr(arg0), copyinstr(arg1));
}
+/**
+ * Triggered on native function entry
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the function
+ */
erlang*:::nif-entry
{
printf("pid %s NIF entry mfa %s\n", copyinstr(arg0), copyinstr(arg1));
}
+/**
+ * Triggered upon native function return
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the returned from function
+ */
erlang*:::nif-return
{
printf("pid %s NIF return mfa %s\n", copyinstr(arg0), copyinstr(arg1));
diff --git a/lib/runtime_tools/examples/function-calls.systemtap b/lib/runtime_tools/examples/function-calls.systemtap
index 6bb173b3ec..8f748ce0d1 100644
--- a/lib/runtime_tools/examples/function-calls.systemtap
+++ b/lib/runtime_tools/examples/function-calls.systemtap
@@ -29,39 +29,85 @@
* to your environment.
*/
+/**
+ * Triggered on local function entry
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the function
+ * @param arg2 depth
+ */
probe process("beam.smp").mark("local-function-entry")
{
printf("pid %s enter (local) %s depth %d\n",
user_string($arg1), user_string($arg2), $arg3);
}
+/**
+ * Triggered on global function entry
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the function
+ * @param arg2 depth
+ */
probe process("beam.smp").mark("global-function-entry")
{
printf("pid %s enter (global) %s depth %d\n",
user_string($arg1), user_string($arg2), $arg3);
}
+/**
+ * Triggered upon function return, either global or
+ * local
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the returned from function
+ * @param arg2 depth
+ */
probe process("beam.smp").mark("function-return")
{
printf("pid %s return %s depth %d\n",
user_string($arg1), user_string($arg2), $arg3);
}
+/**
+ * Triggered on built-in function entry
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the function
+ */
probe process("beam.smp").mark("bif-entry")
{
printf("pid %s BIF entry mfa %s\n", user_string($arg1), user_string($arg2));
}
+/**
+ * Triggered on built-in function return
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the returned from function
+ */
probe process("beam.smp").mark("bif-return")
{
printf("pid %s BIF return mfa %s\n", user_string($arg1), user_string($arg2));
}
+/**
+ * Triggered on native function entry
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the function
+ */
probe process("beam.smp").mark("nif-entry")
{
printf("pid %s NIF entry mfa %s\n", user_string($arg1), user_string($arg2));
}
+/**
+ * Triggered upon native function return
+ *
+ * @param arg0 pid
+ * @param arg1 MFA of the returned from function
+ */
probe process("beam.smp").mark("nif-return")
{
printf("pid %s NIF return mfa %s\n", user_string($arg1), user_string($arg2));
diff --git a/lib/runtime_tools/src/appmon_info.erl b/lib/runtime_tools/src/appmon_info.erl
index d64206decf..a12cea8f15 100644
--- a/lib/runtime_tools/src/appmon_info.erl
+++ b/lib/runtime_tools/src/appmon_info.erl
@@ -713,7 +713,11 @@ format(P) when is_pid(P) ->
_ -> pid_to_list(P)
end;
format(P) when is_port(P) ->
- "port " ++ integer_to_list(element(2, erlang:port_info(P, id)));
+ case erlang:port_info(P, id) of
+ undefined -> "port closed";
+ {_, Pid} ->
+ "port " ++ integer_to_list(Pid)
+ end;
format(X) ->
io:format("What: ~p~n", [X]),
"???".
diff --git a/lib/runtime_tools/src/erts_alloc_config.erl b/lib/runtime_tools/src/erts_alloc_config.erl
index 845efaf9ef..d8133ee14f 100644
--- a/lib/runtime_tools/src/erts_alloc_config.erl
+++ b/lib/runtime_tools/src/erts_alloc_config.erl
@@ -356,14 +356,23 @@ save_scenario(AlcList) ->
process_flag(priority, OP),
Res.
-save_ai2(Alc, AI) ->
- Alc1 = chk_sbct(Alc, AI),
- case ai_value(mbcs, blocks_size, AI) of
- {blocks_size, MinBS, _, MaxBS} ->
- set_alloc_util(chk_mbcs_blocks_size(Alc1, MinBS, MaxBS), true);
- _ ->
- set_alloc_util(Alc, false)
- end.
+save_ai2(#alloc{name=Name}=Alc0, AI) ->
+ Alc1 = chk_sbct(Alc0, AI),
+
+ {Alc, IsAUtil} =
+ case ai_value(mbcs, blocks, AI) of
+ {blocks, Bs} ->
+ case ai_value(Name, size, Bs) of
+ {size, MinBS, _, MaxBS} ->
+ {chk_mbcs_blocks_size(Alc1, MinBS, MaxBS), true};
+ _ ->
+ {Alc1, false}
+ end;
+ _ ->
+ {Alc1, false}
+ end,
+
+ set_alloc_util(Alc, IsAUtil).
save_ai(Alc, [{instance, 0, AI}]) ->
save_ai2(Alc, AI);
diff --git a/lib/runtime_tools/src/observer_backend.erl b/lib/runtime_tools/src/observer_backend.erl
index 3a24986381..e38757b939 100644
--- a/lib/runtime_tools/src/observer_backend.erl
+++ b/lib/runtime_tools/src/observer_backend.erl
@@ -433,7 +433,9 @@ ttb_meta_tracer(MetaFile,PI,Parent,SessionData) ->
ReturnMS = [{'_',[],[{return_trace}]}],
erlang:trace_pattern({erlang,spawn,3},ReturnMS,[meta]),
erlang:trace_pattern({erlang,spawn_link,3},ReturnMS,[meta]),
- erlang:trace_pattern({erlang,spawn_opt,1},ReturnMS,[meta]),
+ erlang:trace_pattern({erlang,spawn_opt,4},ReturnMS,[meta]),
+ erlang:trace_pattern({erts_internal,spawn_init,1},[],[meta]),
+ erlang:trace_pattern({erts_internal,dist_spawn_init,1},[],[meta]),
erlang:trace_pattern({erlang,register,2},[],[meta]),
erlang:trace_pattern({global,register_name,2},[],[meta]),
ok;
@@ -459,7 +461,7 @@ ttb_meta_tracer_loop(MetaFile,PI,Acc,State) ->
{trace_ts,_,call,{global,register_name,[Name,Pid]},_} ->
ok = ttb_store_meta({pid,{Pid,{global,Name}}},MetaFile),
ttb_meta_tracer_loop(MetaFile,PI,Acc,State);
- {trace_ts,CallingPid,call,{erlang,spawn_opt,[{M,F,Args,_}]},_} ->
+ {trace_ts,CallingPid,call,{erlang,spawn_opt,[M,F,Args,_]},_} ->
MFA = {M,F,length(Args)},
NewAcc = dict:update(CallingPid,
fun(Old) -> [MFA|Old] end, [MFA],
@@ -497,6 +499,16 @@ ttb_meta_tracer_loop(MetaFile,PI,Acc,State) ->
Acc),
ttb_meta_tracer_loop(MetaFile,PI,NewAcc,State);
+ {trace_ts,CallingPid,call,{erts_internal,spawn_init,[{M,F,Args}]},_} ->
+ %% Local spawn_request()...
+ ok = ttb_store_meta({pid,{CallingPid,{M,F,length(Args)}}},MetaFile),
+ ttb_meta_tracer_loop(MetaFile,PI,Acc,State);
+
+ {trace_ts,CallingPid,call,{erts_internal, dist_spawn_init, [MFnoA]},_} ->
+ %% Distributed spawn_request()...
+ ok = ttb_store_meta({pid,{CallingPid,MFnoA}},MetaFile),
+ ttb_meta_tracer_loop(MetaFile,PI,Acc,State);
+
{metadata,Data} when is_list(Data) ->
ok = ttb_store_meta(Data,MetaFile),
ttb_meta_tracer_loop(MetaFile,PI,Acc,State);
@@ -530,7 +542,9 @@ ttb_meta_tracer_loop(MetaFile,PI,Acc,State) ->
try_stop_overload_check(State),
erlang:trace_pattern({erlang,spawn,3},false,[meta]),
erlang:trace_pattern({erlang,spawn_link,3},false,[meta]),
- erlang:trace_pattern({erlang,spawn_opt,1},false,[meta]),
+ erlang:trace_pattern({erlang,spawn_opt,4},false,[meta]),
+ erlang:trace_pattern({erts_internal,spawn_init,1},false,[meta]),
+ erlang:trace_pattern({erts_internal,dist_spawn_init,1},false,[meta]),
erlang:trace_pattern({erlang,register,2},false,[meta]),
erlang:trace_pattern({global,register_name,2},false,[meta]);
stop ->
@@ -752,6 +766,7 @@ sys_tables() ->
mnesia_gvar, mnesia_stats,
% mnesia_transient_decision,
pg2_table,
+ pg,
queue,
schema,
shell_records,
@@ -763,7 +778,7 @@ sys_tables() ->
sys_processes() ->
[auth, code_server, global_name_server, inet_db,
- mnesia_recover, net_kernel, timer_server, wxe_master].
+ mnesia_recover, net_kernel, pg, timer_server, wxe_master].
mnesia_tables() ->
[ir_AliasDef, ir_ArrayDef, ir_AttributeDef, ir_ConstantDef,
diff --git a/lib/runtime_tools/src/runtime_tools.app.src b/lib/runtime_tools/src/runtime_tools.app.src
index b026048b94..d7c2975a5b 100644
--- a/lib/runtime_tools/src/runtime_tools.app.src
+++ b/lib/runtime_tools/src/runtime_tools.app.src
@@ -29,5 +29,5 @@
{applications, [kernel, stdlib]},
{env, []},
{mod, {runtime_tools, []}},
- {runtime_dependencies, ["stdlib-3.0","mnesia-4.12","kernel-5.0",
- "erts-8.0"]}]}.
+ {runtime_dependencies, ["stdlib-3.13","mnesia-4.12","kernel-7.0",
+ "erts-11.0"]}]}.
diff --git a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl
index 6ae51d9a26..9ab61b89d2 100644
--- a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl
+++ b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl
@@ -25,7 +25,9 @@
-include_lib("common_test/include/ct.hrl").
%-compile(export_all).
--export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2]).
+-export([all/0, suite/0,
+ init_per_suite/1, end_per_suite/1,
+ init_per_testcase/2, end_per_testcase/2]).
%% Testcases
-export([basic/1]).
@@ -40,6 +42,18 @@ suite() ->
all() ->
[basic].
+init_per_suite(Config) ->
+ case test_server:is_asan() of
+ true ->
+ %% No point testing own allocators under address sanitizer.
+ {skip, "Address sanitizer"};
+ false ->
+ Config
+ end.
+
+end_per_suite(_Config) ->
+ ok.
+
init_per_testcase(Case, Config) when is_list(Config) ->
[{testcase, Case},
{erl_flags_env, save_env()} | Config].
diff --git a/lib/runtime_tools/vsn.mk b/lib/runtime_tools/vsn.mk
index c01dd60009..e62d59acf6 100644
--- a/lib/runtime_tools/vsn.mk
+++ b/lib/runtime_tools/vsn.mk
@@ -1 +1 @@
-RUNTIME_TOOLS_VSN = 1.14
+RUNTIME_TOOLS_VSN = 1.16
diff --git a/lib/sasl/Makefile b/lib/sasl/Makefile
index 065eb45fbb..06af80fd35 100644
--- a/lib/sasl/Makefile
+++ b/lib/sasl/Makefile
@@ -36,3 +36,6 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=tools
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/sasl/doc/specs/.gitignore b/lib/sasl/doc/specs/.gitignore
new file mode 100644
index 0000000000..322eebcb06
--- /dev/null
+++ b/lib/sasl/doc/specs/.gitignore
@@ -0,0 +1 @@
+specs_*.xml
diff --git a/lib/sasl/doc/src/Makefile b/lib/sasl/doc/src/Makefile
index 8e1e8b502c..249b278f6b 100644
--- a/lib/sasl/doc/src/Makefile
+++ b/lib/sasl/doc/src/Makefile
@@ -27,11 +27,6 @@ VSN=$(SASL_VSN)
APPLICATION=sasl
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -51,80 +46,15 @@ XML_CHAPTER_FILES = sasl_intro.xml \
BOOK_FILES = book.xml
-GIF_FILES =
+IMAGE_FILES =
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF4_FILES) \
$(XML_REF6_FILES) $(XML_APPLICATION_FILES)
+TOP_SPECS_FILE = specs.xml
# ----------------------------------------------------
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN4_FILES = $(XML_REF4_FILES:%.xml=$(MAN4DIR)/%.4)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES) $(MAN4_FILES) $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%) # We depend just to copy them to ../html
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(MAN4DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man4"
- $(INSTALL_DATA) $(MAN4_FILES) "$(RELEASE_PATH)/man/man4"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/sasl/doc/src/alarm_handler.xml b/lib/sasl/doc/src/alarm_handler.xml
index 3bccccc9d4..8e39e4e074 100644
--- a/lib/sasl/doc/src/alarm_handler.xml
+++ b/lib/sasl/doc/src/alarm_handler.xml
@@ -38,7 +38,7 @@
<modulesummary>An Alarm Handling Process</modulesummary>
<description>
<p>The alarm handler process is a
- <seealso marker="stdlib:gen_event"><c>gen_event</c></seealso>
+ <seeerl marker="stdlib:gen_event"><c>gen_event</c></seeerl>
event manager process that receives alarms in the system.
This process is not intended to be a complete alarm handler.
It defines a place to which alarms can be sent. One simple event
@@ -71,11 +71,11 @@
</taglist>
<p>The default simple handler is called <c>alarm_handler</c> and
it can be exchanged by calling
- <seealso marker="stdlib:gen_event#swap_handler/3"><c>gen_event:swap_handler/3</c></seealso>
+ <seemfa marker="stdlib:gen_event#swap_handler/3"><c>gen_event:swap_handler/3</c></seemfa>
as <c>gen_event:swap_handler(alarm_handler, {alarm_handler, swap},
{NewHandler, Args})</c>. <c>NewHandler:init({Args, {alarm_handler,
Alarms}})</c> is called. For more details, see
- <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso>
+ <seeerl marker="stdlib:gen_event"><c>gen_event(3)</c></seeerl>
in STDLIB.</p>
</description>
@@ -121,8 +121,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="kernel:error_logger"><c>error_logger(3)</c></seealso>,
- <seealso marker="stdlib:gen_event"><c>gen_event(3)</c></seealso></p>
+ <p><seeerl marker="kernel:error_logger"><c>error_logger(3)</c></seeerl>,
+ <seeerl marker="stdlib:gen_event"><c>gen_event(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/sasl/doc/src/appup.xml b/lib/sasl/doc/src/appup.xml
index 102ea9e5d7..598ce226d5 100644
--- a/lib/sasl/doc/src/appup.xml
+++ b/lib/sasl/doc/src/appup.xml
@@ -34,7 +34,7 @@
<p>The <em>application upgrade file</em> defines how an application
is upgraded or downgraded in a running system.</p>
<p>This file is used by the functions in
- <seealso marker="systools"><c>systools</c></seealso>
+ <seeerl marker="systools"><c>systools</c></seeerl>
when generating a release upgrade file <c>relup</c>.</p>
</description>
@@ -70,8 +70,8 @@
numbers.</p></item>
<tag><c>Instructions</c></tag>
<item><p>A list of <em>release upgrade instructions</em>, see
- <seealso marker="#Release Upgrade Instructions">Release
- Upgrade Instructions</seealso>. It is recommended to use
+ <seefile marker="#Release Upgrade Instructions">Release
+ Upgrade Instructions</seefile>. It is recommended to use
high-level instructions only. These are automatically
translated to low-level instructions by <c>systools</c> when
creating the <c>relup</c> file.</p></item>
@@ -95,14 +95,14 @@
<p>Release upgrade instructions are interpreted by the release
handler when an upgrade or downgrade is made. For more
information about release handling, see
- <seealso marker="doc/design_principles:release_handling">OTP
- Design Principles</seealso> in <em>System Documentation</em>.</p>
+ <seeguide marker="system/design_principles:release_handling">OTP
+ Design Principles</seeguide> in <em>System Documentation</em>.</p>
<p>A process is said to <em>use</em> a module <c>Mod</c> if
<c>Mod</c> is listed in the <c>Modules</c> part of the child
specification used to start the process, see
- <seealso marker="stdlib:supervisor"><c>supervisor(3)</c></seealso>.
+ <seeerl marker="stdlib:supervisor"><c>supervisor(3)</c></seeerl>.
In the case of
- <seealso marker="stdlib:gen_event"><c>gen_event</c></seealso>,
+ <seeerl marker="stdlib:gen_event"><c>gen_event</c></seeerl>,
an event manager process is said to use <c>Mod</c> if <c>Mod</c>
is an installed event handler.</p>
@@ -127,19 +127,19 @@
<p>Synchronized code replacement of processes using module
<c>Mod</c>.</p>
<p>All those processes are suspended using
- <seealso marker="stdlib:sys#suspend/1"><c>sys:suspend</c></seealso>,
+ <seemfa marker="stdlib:sys#suspend/1"><c>sys:suspend</c></seemfa>,
the new module version is loaded, and
then the processes are resumed using
- <seealso marker="stdlib:sys#resume/1"><c>sys:resume</c></seealso>.</p>
+ <seemfa marker="stdlib:sys#resume/1"><c>sys:resume</c></seemfa>.</p>
<taglist>
<tag><c>Change</c></tag>
<item><p>Defaults to <c>soft</c> and defines the type of
code change. If it is set to <c>{advanced,Extra}</c>, implemented
processes using
- <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso>,
- <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso>,
- <seealso marker="stdlib:gen_statem"><c>gen_statem</c></seealso>, or
- <seealso marker="stdlib:gen_event"><c>gen_event</c></seealso>
+ <seeerl marker="stdlib:gen_server"><c>gen_server</c></seeerl>,
+ <seeerl marker="stdlib:gen_fsm"><c>gen_fsm</c></seeerl>,
+ <seeerl marker="stdlib:gen_statem"><c>gen_statem</c></seeerl>, or
+ <seeerl marker="stdlib:gen_event"><c>gen_event</c></seeerl>
transform their internal state by calling the callback function
<c>code_change</c>. Special processes call the callback
function <c>system_code_change/4</c>. In both cases, the term
@@ -151,7 +151,7 @@
before loading the new module version. If the value
is <c>brutal_purge</c>, the processes are killed. If the value is
<c>soft_purge</c>,
- <seealso marker="release_handler#install_release/1"><c>release_handler:install_release/1</c></seealso>
+ <seemfa marker="release_handler#install_release/1"><c>release_handler:install_release/1</c></seemfa>
returns <c>{error,{old_processes,Mod}}</c>.</p></item>
<tag><c>PostPurge</c></tag>
<item><p>Defaults to <c>brutal_purge</c>. It controls
@@ -172,7 +172,7 @@
<tag><c>Timeout</c></tag>
<item><p>Defines the time-out when suspending processes.
If no value or <c>default</c> is specified, the default value for
- <seealso marker="stdlib:sys#suspend/1"><c>sys:suspend</c></seealso>
+ <seemfa marker="stdlib:sys#suspend/1"><c>sys:suspend</c></seemfa>
is used.</p></item>
<tag><c>ModType</c></tag>
<item><p>Defaults to <c>dynamic</c>. It specifies if
@@ -235,7 +235,7 @@
<p><c>Type</c> defaults to <c>permanent</c> and specifies the start type
of the application. If <c>Type = permanent | transient | temporary</c>,
the application is loaded and started in the corresponding way, see
- <seealso marker="kernel:application"><c>application(3)</c></seealso>.
+ <seeerl marker="kernel:application"><c>application(3)</c></seeerl>.
If <c>Type = load</c>, the application is only loaded.
If <c>Type = none</c>, the application is not loaded and not
started, although the code for its modules is loaded.</p>
@@ -308,7 +308,7 @@ point_of_no_return</pre>
spontaneously switches to new code, or as a result of a purge
operation. If no <c>Timeout</c> is specified or <c>default</c> is
specified, the default value for
- <seealso marker="stdlib:sys#suspend/1"><c>sys:suspend</c></seealso>
+ <seemfa marker="stdlib:sys#suspend/1"><c>sys:suspend</c></seemfa>
is used.</p>
<pre>
{resume, [Mod]}
@@ -324,13 +324,13 @@ point_of_no_return</pre>
upgrade or downgrade. This instruction sends a <c>code_change</c>
system message to all processes using a module <c>Mod</c> by
calling function
- <seealso marker="stdlib:sys#change_code/4"><c>sys:change_code</c></seealso>,
+ <seemfa marker="stdlib:sys#change_code/4"><c>sys:change_code</c></seemfa>,
passing term <c>Extra</c> as argument.</p>
<pre>
{stop, [Mod]}
Mod = atom()</pre>
<p>Stops all processes using a module <c>Mod</c> by calling
- <seealso marker="stdlib:supervisor#terminate_child/2"><c>supervisor:terminate_child/2</c></seealso>.
+ <seemfa marker="stdlib:supervisor#terminate_child/2"><c>supervisor:terminate_child/2</c></seemfa>.
This instruction is useful
when the simplest way to change code is to stop and restart the
processes that run the code.</p>
@@ -338,7 +338,7 @@ point_of_no_return</pre>
{start, [Mod]}
Mod = atom()</pre>
<p>Starts all stopped processes using a module <c>Mod</c> by calling
- <seealso marker="stdlib:supervisor#restart_child/2"><c>supervisor:restart_child/2</c></seealso>.</p>
+ <seemfa marker="stdlib:supervisor#restart_child/2"><c>supervisor:restart_child/2</c></seemfa>.</p>
<pre>
{sync_nodes, Id, [Node]}
{sync_nodes, Id, {M, F, A}}
@@ -362,7 +362,7 @@ point_of_no_return</pre>
<p>Evaluates <c>apply(M, F, A)</c>.</p>
<p>If the instruction appears before instruction
<c>point_of_no_return</c>, a failure is caught.
- <seealso marker="release_handler#install_release/1"><c>release_handler:install_release/1</c></seealso>
+ <seemfa marker="release_handler#install_release/1"><c>release_handler:install_release/1</c></seemfa>
then returns <c>{error,{'EXIT',Reason}}</c>, unless <c>{error,Error}</c>
is thrown or returned. Then it returns <c>{error,Error}</c>.</p>
<p>If the instruction appears after instruction
@@ -378,14 +378,14 @@ restart_new_emulator</pre>
SASL are used when the emulator restarts.
Only one <c>restart_new_emulator</c> instruction is allowed
in the <c>relup</c> file, and it must be placed first.
- <seealso marker="systools#make_relup/3"><c>systools:make_relup/3,4</c></seealso>
+ <seemfa marker="systools#make_relup/3"><c>systools:make_relup/3,4</c></seemfa>
ensures this when the <c>relup</c> file is generated. The rest of the
instructions in the <c>relup</c> file is executed after the
restart as a part of the boot script.</p>
<p>An info report is written when the upgrade is completed.
To programmatically determine if the upgrade is complete,
- call <seealso marker="release_handler#which_releases/0">
- <c>release_handler:which_releases/0,1</c></seealso> and check if the
+ call <seemfa marker="release_handler#which_releases/0">
+ <c>release_handler:which_releases/0,1</c></seemfa> and check if the
expected release has status <c>current</c>.</p>
<p>The new release must still be made permanent after the upgrade
is completed, otherwise the old emulator is started if there is
@@ -412,7 +412,7 @@ restart_emulator</pre>
applications, but can be used by any application when a complete
reboot of the system is required.</p>
<p>When generating the <c>relup</c> file,
- <seealso marker="systools#make_relup/3"><c>systools:make_relup/3,4</c></seealso>
+ <seemfa marker="systools#make_relup/3"><c>systools:make_relup/3,4</c></seemfa>
ensures that there is only one <c>restart_emulator</c>
instruction and that it is the last instruction in the
<c>relup</c> file.</p>
@@ -421,10 +421,10 @@ restart_emulator</pre>
<section>
<title>See Also</title>
- <p><seealso marker="release_handler"><c>release_handler(3)</c></seealso>,
- <seealso marker="relup"><c>relup(4)</c></seealso>,
- <seealso marker="stdlib:supervisor"><c>supervisor(3)</c></seealso>,
- <seealso marker="systools"><c>systools(3)</c></seealso></p>
+ <p><seeerl marker="release_handler"><c>release_handler(3)</c></seeerl>,
+ <seefile marker="relup"><c>relup(4)</c></seefile>,
+ <seeerl marker="stdlib:supervisor"><c>supervisor(3)</c></seeerl>,
+ <seeerl marker="systools"><c>systools(3)</c></seeerl></p>
</section>
</fileref>
diff --git a/lib/sasl/doc/src/error_logging.xml b/lib/sasl/doc/src/error_logging.xml
index e6c244c1b9..401250924e 100644
--- a/lib/sasl/doc/src/error_logging.xml
+++ b/lib/sasl/doc/src/error_logging.xml
@@ -35,32 +35,32 @@
<note>
<p>The SASL error logging concept described in this section is
deprecated since Erlang/OTP 21.0, when the
- new <seealso marker="kernel:logger_chapter">logging
- API</seealso> was introduced.</p>
+ new <seeguide marker="kernel:logger_chapter">logging
+ API</seeguide> was introduced.</p>
<p>The new default behaviour is that the SASL application no
longer affects which log events that are logged.
- <seealso marker="#supervisor_report">Supervisor
- reports</seealso> and <seealso marker="#crash_report">crash
- reports</seealso> are logged via the default logger handler
+ <seeguide marker="#supervisor_report">Supervisor
+ reports</seeguide> and <seeguide marker="#crash_report">crash
+ reports</seeguide> are logged via the default logger handler
which is setup by
- Kernel. <seealso marker="#progress_report">Progress
- reports</seealso> are by default not logged, but can be enabled
+ Kernel. <seeguide marker="#progress_report">Progress
+ reports</seeguide> are by default not logged, but can be enabled
by setting the primary log level to <c>info</c>, for example by
using the Kernel configuration
- parameter <seealso marker="kernel:kernel_app#logger_level">
- <c>logger_level</c></seealso>.</p>
+ parameter <seeapp marker="kernel:kernel_app#logger_level">
+ <c>logger_level</c></seeapp>.</p>
<p>The old SASL error logging behaviour can be re-enabled by setting the
Kernel configuration
- parameter <seealso marker="kernel:kernel_app#logger_sasl_compatible">
- <c>logger_sasl_compatible</c></seealso> to <c>true</c>.</p>
+ parameter <seeapp marker="kernel:kernel_app#logger_sasl_compatible">
+ <c>logger_sasl_compatible</c></seeapp> to <c>true</c>.</p>
<p>The mechanism
- for <seealso marker="#multi_file_logging">multi-file error report
- logging</seealso> as described in this section is also kept for
+ for <seeguide marker="#multi_file_logging">multi-file error report
+ logging</seeguide> as described in this section is also kept for
backwards compatibility. However, the new logging API also
- introduces <seealso marker="kernel:logger_disk_log_h">
- <c>logger_disk_log_h(3)</c></seealso>, which is a logger
+ introduces <seeerl marker="kernel:logger_disk_log_h">
+ <c>logger_disk_log_h(3)</c></seeerl>, which is a logger
handler that can print to multiple files
- using <seealso marker="kernel:disk_log"><c>disk_log(3)</c></seealso>.</p>
+ using <seeerl marker="kernel:disk_log"><c>disk_log(3)</c></seeerl>.</p>
</note>
<section>
@@ -73,8 +73,8 @@
</list>
<p>When the SASL application is started, it adds a Logger handler
that formats and writes these reports, as specified in
- the <seealso marker="sasl_app#deprecated_error_logger_config">configuration
- parameters for SASL</seealso>.</p>
+ the <seeapp marker="sasl_app#deprecated_error_logger_config">configuration
+ parameters for SASL</seeapp>.</p>
<section>
<marker id="supervisor_report"/>
@@ -116,15 +116,15 @@
<marker id="crash_report"/>
<title>Crash Report</title>
<p>Processes started with functions
- <seealso marker="stdlib:proc_lib#spawn/1"><c>proc_lib:spawn</c></seealso> or
- <seealso marker="stdlib:proc_lib#spawn_link/1"><c>proc_lib:spawn_link</c></seealso>
+ <seemfa marker="stdlib:proc_lib#spawn/1"><c>proc_lib:spawn</c></seemfa> or
+ <seemfa marker="stdlib:proc_lib#spawn_link/1"><c>proc_lib:spawn_link</c></seemfa>
are wrapped within a <c>catch</c>. A crash report is issued when such
a process terminates with an unexpected reason, which is any reason
other than <c>normal</c>, <c>shutdown</c>, or <c>{shutdown,Term}</c>.
Processes using behaviors
- <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso>,
- <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso> or
- <seealso marker="stdlib:gen_statem"><c>gen_statem</c></seealso>
+ <seeerl marker="stdlib:gen_server"><c>gen_server</c></seeerl>,
+ <seeerl marker="stdlib:gen_fsm"><c>gen_fsm</c></seeerl> or
+ <seeerl marker="stdlib:gen_statem"><c>gen_statem</c></seeerl>
are examples of such processes. A crash report contains the following items:</p>
<taglist>
<tag><c>Crasher</c></tag>
@@ -147,7 +147,7 @@
supervisor. A division by zero is executed and the error is
first reported by the faulty process. A crash report is
generated, as the process was started using function
- <seealso marker="stdlib:proc_lib#spawn/3"><c>proc_lib:spawn/3</c></seealso>.
+ <seemfa marker="stdlib:proc_lib#spawn/3"><c>proc_lib:spawn/3</c></seemfa>.
The supervisor generates a
supervisor report showing the crashed process. A
progress report is generated when the process is finally
@@ -205,8 +205,8 @@
of files exist at the same time. The logging is very fast, as
each error message is written as a binary term.</p>
<p>For more details, see the
- <seealso marker="sasl_app#deprecated_error_logger_config">
- <c>sasl(6)</c></seealso>
+ <seeapp marker="sasl_app#deprecated_error_logger_config">
+ <c>sasl(6)</c></seeapp>
application in the Reference Manual.</p>
</section>
@@ -214,7 +214,7 @@
<title>Report Browser</title>
<p>The report browser is used to browse and format error reports
written by the error logger handler
- <seealso marker="stdlib:log_mf_h"><c>log_mf_h</c></seealso>
+ <seeerl marker="stdlib:log_mf_h"><c>log_mf_h</c></seeerl>
defined in STDLIB.</p>
<p>The <c>log_mf_h</c> handler writes all reports to a
report logging directory, which is specified when
@@ -228,7 +228,7 @@
<section>
<title>Starting Report Browser</title>
<p>Start the <c>rb_server</c> with function
- <seealso marker="rb#start/1"><c>rb:start([Options])</c></seealso>
+ <seemfa marker="rb#start/1"><c>rb:start([Options])</c></seemfa>
as shown in the following example:</p>
<pre>
5> <input>rb:start([{max, 20}]).</input>
@@ -242,14 +242,14 @@
<section>
<title>Online Help</title>
<p>Enter command
- <seealso marker="rb#help/0"><c>rb:help()</c></seealso>
+ <seemfa marker="rb#help/0"><c>rb:help()</c></seemfa>
to access the report browser online help system.</p>
</section>
<section>
<title>List Reports in Server</title>
<p>Use function
- <seealso marker="rb#list/0"><c>rb:list()</c></seealso>
+ <seemfa marker="rb#list/0"><c>rb:list()</c></seemfa>
to list all loaded reports:</p>
<pre>
4> <input>rb:list().</input>
@@ -281,7 +281,7 @@
<section>
<title>Show Reports</title>
<p>Use function
- <seealso marker="rb#show/1"><c>rb:show(Number)</c></seealso>
+ <seemfa marker="rb#show/1"><c>rb:show(Number)</c></seemfa>
to show details of a specific report:</p>
<pre>
7> <input>rb:show(4).</input>
@@ -411,7 +411,7 @@ ok</pre>
<section>
<title>Stop Server</title>
<p>Use function
- <seealso marker="rb#stop/0"><c>rb:stop()</c></seealso>
+ <seemfa marker="rb#stop/0"><c>rb:stop()</c></seemfa>
to stop the <c>rb_server</c>:</p>
<pre>
13> <input>rb:stop().</input>
diff --git a/lib/sasl/doc/src/notes.xml b/lib/sasl/doc/src/notes.xml
index 18dbfb375b..46e6762b90 100644
--- a/lib/sasl/doc/src/notes.xml
+++ b/lib/sasl/doc/src/notes.xml
@@ -31,6 +31,86 @@
</header>
<p>This document describes the changes made to the SASL application.</p>
+<section><title>SASL 4.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix dependent application to be stopped after the primary
+ application when upgrading a release and the primary and
+ dependent application is removed.</p>
+ <p>
+ Example: In a release where app1 depends on app2 and we
+ should remove app1 and app2 using a release upgrade. When
+ the release upgrade is done app1 should be stopped and
+ purged before app2 as otherwise app1 could start crashing
+ when its dependency is removed.</p>
+ <p>
+ This bugfix changes the order of removal to be correct.</p>
+ <p>
+ Own Id: OTP-17113 Aux Id: ERL-1410 PR-2882 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SASL 4.0.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Make <c>release_handler</c> more resilient against
+ exiting processes during upgrade.</p>
+ <p>
+ Own Id: OTP-16744 Aux Id: ERL-1247, PR-2666 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SASL 4.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>
+ <c>systools:make_script/2</c> now accepts the name of the
+ boot file to create, it is not restricted to only
+ <c>RelName.boot</c> or <c>start.boot</c>.</p>
+ <p>
+ <c>systools:make_tar/2</c> now accepts the option
+ <c>extra_files</c> to add any extra non release related
+ files to the tar file.</p>
+ <p>
+ Own Id: OTP-16561 Aux Id: PR-2420 </p>
+ </item>
+ <item>
+ <p>
+ <seemfa
+ marker="systools#make_tar/1"><c>systools:make_tar/1,2</c></seemfa>
+ now filters out any tools from erts if included in the
+ release tar ball. See the documentation for more details.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16603</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SASL 3.4.2</title>
<section><title>Improvements and New Features</title>
@@ -118,10 +198,10 @@
<list>
<item>
<p>A new logging API is added to Erlang/OTP, see the
- <seealso
- marker="kernel:logger"><c>logger(3)</c></seealso> manual
- page, and section <seealso
- marker="kernel:logger_chapter">Logging</seealso> in the
+ <seeerl
+ marker="kernel:logger"><c>logger(3)</c></seeerl> manual
+ page, and section <seeguide
+ marker="kernel:logger_chapter">Logging</seeguide> in the
Kernel User's Guide.</p>
<p>Calls to <c>error_logger</c> are automatically
redirected to the new API, and legacy error logger event
@@ -352,8 +432,8 @@
new purge strategy will as of ERTS version 8.1 be
enabled. This new strategy is not fully backwards
compatible with the strategy used by default. For more
- information see the documentation of <seealso
- marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>.</p>
+ information see the documentation of <seemfa
+ marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seemfa>.</p>
<p>
Own Id: OTP-13808 Aux Id: OTP-13833 </p>
</item>
@@ -381,8 +461,8 @@
the runtime system is built with support for dirty
schedulers.</p>
<p>
- For more information see the documentation of <seealso
- marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>.</p>
+ For more information see the documentation of <seemfa
+ marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seemfa>.</p>
<p>
Own Id: OTP-13833</p>
</item>
diff --git a/lib/sasl/doc/src/rb.xml b/lib/sasl/doc/src/rb.xml
index 349e2d8ba2..f1f3888d53 100644
--- a/lib/sasl/doc/src/rb.xml
+++ b/lib/sasl/doc/src/rb.xml
@@ -37,7 +37,7 @@
<description>
<p>The Report Browser (RB) tool is used to browse and
format error reports written by the error logger handler
- <seealso marker="stdlib:log_mf_h"><c>log_mf_h</c></seealso>
+ <seeerl marker="stdlib:log_mf_h"><c>log_mf_h</c></seeerl>
in STDLIB.</p>
</description>
@@ -61,7 +61,7 @@
<p>When a filter includes the <c>no</c> atom, it excludes the
reports that match that filter.</p>
<p>The reports are matched using the
- <seealso marker="stdlib:proplists"><c>proplists</c></seealso>
+ <seeerl marker="stdlib:proplists"><c>proplists</c></seeerl>
module in STDLIB. The report must be a proplist
to be matched against any of the filters.</p>
<p>If the filter has the form <c>{Key, RegExp, re}</c>, the
@@ -79,9 +79,9 @@
parameter.</p>
<p>For details about parameter <c>RegExp</c>, see <c>rb:grep/1</c>.</p>
<p>For details about data type <c>mp()</c>, see
- <seealso marker="stdlib:re#type-mp"><c>re:mp()</c></seealso>.</p>
+ <seetype marker="stdlib:re#mp"><c>re:mp()</c></seetype>.</p>
<p>For details about data type <c>datetime()</c>, see
- <seealso marker="stdlib:calendar#type-datetime"><c>calendar:datetime()</c></seealso>.</p>
+ <seetype marker="stdlib:calendar#datetime"><c>calendar:datetime()</c></seetype>.</p>
</desc>
</func>
@@ -101,10 +101,10 @@
<item>A compiled regular expression and the options for running it</item>
</list>
<p>For a definition of valid regular expressions and options, see
- the <seealso marker="stdlib:re"><c>re</c></seealso> module in
+ the <seeerl marker="stdlib:re"><c>re</c></seeerl> module in
STDLIB and in particular function <c>re:run/3</c>.</p>
<p>For details about data type <c>mp()</c>, see
- <seealso marker="stdlib:re#type-mp"><c>re:mp()</c></seealso>.</p>
+ <seetype marker="stdlib:re#mp"><c>re:mp()</c></seetype>.</p>
</desc>
</func>
@@ -131,7 +131,7 @@
<p>Lists all reports loaded in
<c>rb_server</c>. Each report is given a unique number that
can be used as a reference to the report in function
- <seealso marker="#show/1"><c>show/1</c></seealso>.</p>
+ <seemfa marker="#show/1"><c>show/1</c></seemfa>.</p>
<p>If no <c>Type</c> is specified, all reports are listed.</p>
</desc>
</func>
@@ -148,8 +148,8 @@
</type>
<desc>
<p>Same as functions
- <seealso marker="#list/0"><c>list/0</c></seealso> or
- <seealso marker="#list/1"><c>list/1</c></seealso>,
+ <seemfa marker="#list/0"><c>list/0</c></seemfa> or
+ <seemfa marker="#list/1"><c>list/1</c></seemfa>,
but the result is printed to a log file, if set; otherwise
to <c>standard_io</c>.</p>
<p>If no <c>Type</c> is specified, all reports are listed.</p>
@@ -166,7 +166,7 @@
<desc>
<p>Rescans the report directory. <c>Options</c> is the same as
for function
- <seealso marker="#start/1"><c>start/1</c></seealso>.</p>
+ <seemfa marker="#start/1"><c>start/1</c></seemfa>.</p>
</desc>
</func>
@@ -227,7 +227,7 @@
the error log files are located. Default is
the directory specified by application environment
variable <c>error_logger_mf_dir</c>,
- see <seealso marker="sasl_app">sasl(6)</seealso>.</p></item>
+ see <seeapp marker="sasl_app">sasl(6)</seeapp>.</p></item>
<tag><c>{type, ReportType}</c></tag>
<item><p>Controls what kind of reports
<c>rb_server</c> is to read at startup. <c>ReportType</c>
diff --git a/lib/sasl/doc/src/rel.xml b/lib/sasl/doc/src/rel.xml
index 9356b2cd47..9aedd1b697 100644
--- a/lib/sasl/doc/src/rel.xml
+++ b/lib/sasl/doc/src/rel.xml
@@ -36,7 +36,7 @@
<p>The <em>release resource file</em> specifies which applications
are included in a release (system) based on Erlang/OTP.</p>
<p>This file is used by the functions in
- <seealso marker="systools"><c>systools</c></seealso>
+ <seeerl marker="systools"><c>systools</c></seeerl>
when generating start scripts (<c>.script</c>, <c>.boot</c>) and
release upgrade files (<c>relup</c>).</p>
</description>
@@ -68,7 +68,7 @@
<item><p>Start type of an application included in the release.</p>
<p>If <c>Type = permanent | transient | temporary</c>, the
application is loaded and started in the corresponding way, see
- <seealso marker="kernel:application"><c>application(3)</c></seealso>.</p>
+ <seeerl marker="kernel:application"><c>application(3)</c></seeerl>.</p>
<p>If <c>Type = load</c>, the application is only loaded.</p>
<p>If <c>Type = none</c>, the application is not loaded and not
started, although the code for its modules is loaded.</p>
@@ -89,9 +89,9 @@
<section>
<title>See Also</title>
- <p><seealso marker="kernel:application"><c>application(3)</c></seealso>,
- <seealso marker="relup"><c>relup(4)</c></seealso>,
- <seealso marker="systools"><c>systools(3)</c></seealso></p>
+ <p><seeerl marker="kernel:application"><c>application(3)</c></seeerl>,
+ <seefile marker="relup"><c>relup(4)</c></seefile>,
+ <seeerl marker="systools"><c>systools(3)</c></seeerl></p>
</section>
</fileref>
diff --git a/lib/sasl/doc/src/release_handler.xml b/lib/sasl/doc/src/release_handler.xml
index f8ee0306d8..c70ec78470 100644
--- a/lib/sasl/doc/src/release_handler.xml
+++ b/lib/sasl/doc/src/release_handler.xml
@@ -35,15 +35,15 @@
application, which is responsible for <em>release handling</em>,
that is, unpacking, installation, and removal of release packages.</p>
<p>An introduction to release handling and an example is provided in
- <seealso marker="doc/design_principles:release_handling">OTP Design
- Principles</seealso> in <em>System Documentation</em>.</p>
+ <seeguide marker="system/design_principles:release_handling">OTP Design
+ Principles</seeguide> in <em>System Documentation</em>.</p>
<p>A <em>release package</em> is a compressed tar file containing
code for a certain version of a release, created by calling
- <seealso marker="systools#make_tar/1"><c>systools:make_tar/1,2</c></seealso>.
+ <seemfa marker="systools#make_tar/1"><c>systools:make_tar/1,2</c></seemfa>.
The release package is to be located in the <c>$ROOT/releases</c>
directory of the previous version of the release, where
<c>$ROOT</c> is the installation root directory,
- <seealso marker="kernel:code#root_dir/0"><c>code:root_dir()</c></seealso>.
+ <seemfa marker="kernel:code#root_dir/0"><c>code:root_dir()</c></seemfa>.
Another <c>releases</c> directory can be specified using the SASL
configuration parameter <c>releases_dir</c> or the OS environment
variable <c>RELDIR</c>. The release handler must have write access
@@ -112,7 +112,7 @@
system configuration file are to be used if the system is
restarted. This is taken care of automatically if Erlang is
started as an embedded system. Read about this in
- <seealso marker="doc/embedded:users_guide">Embedded System</seealso> in
+ <seeguide marker="system/embedded:index">Embedded System</seeguide> in
<em>System Documentation</em>. In this case, the system
configuration file <c>sys.config</c> is mandatory.</p>
<p>The installation of a new release can restart the system. Which
@@ -134,7 +134,7 @@
<p>The release handler at a node running on a diskless machine,
or with a read-only file system, must be configured accordingly
using the following SASL configuration parameters (for
- details, see <seealso marker="sasl_app">sasl(6)</seealso>):</p>
+ details, see <seeapp marker="sasl_app">sasl(6)</seeapp>):</p>
<taglist>
<tag><c>masters</c></tag>
<item>
@@ -191,13 +191,13 @@
the <c>point_of_no_return</c> instruction in the release
upgrade script.</p>
<p>Returns the same as
- <seealso marker="#install_release/1"><c>install_release/1</c></seealso>.
+ <seemfa marker="#install_release/1"><c>install_release/1</c></seemfa>.
<c>Descr</c> defaults to "" if no <c>relup</c> file is found.</p>
<p>If option <c>purge</c> is specified, all old code that can
be soft-purged is purged after all other checks are
successfully completed. This can be useful to
- reduce the time needed by <seealso
- marker="#install_release/1"><c>install_release/1</c></seealso>.</p>
+ reduce the time needed by <seemfa
+ marker="#install_release/1"><c>install_release/1</c></seemfa>.</p>
</desc>
</func>
@@ -246,7 +246,7 @@
<c>start.boot</c>, <c>relup</c>, and <c>sys.config</c>.</p>
<p>The function can be called, for example, when these files
are generated at the target. The function is to be called after
- <seealso marker="#set_unpacked/2"><c>set_unpacked/2</c></seealso>
+ <seemfa marker="#set_unpacked/2"><c>set_unpacked/2</c></seemfa>
has been called.</p>
</desc>
</func>
@@ -303,21 +303,21 @@
<tag><c>error_action</c></tag>
<item><p>Defines if the node is to be
restarted
- (<seealso marker="erts:init#restart/0"><c>init:restart()</c></seealso>)
+ (<seemfa marker="erts:init#restart/0"><c>init:restart()</c></seemfa>)
or rebooted
- (<seealso marker="erts:init#reboot/0"><c>init:reboot()</c></seealso>)
+ (<seemfa marker="erts:init#reboot/0"><c>init:reboot()</c></seemfa>)
if there is an error during
the installation. Default is <c>restart</c>.</p></item>
<tag><c>code_change_timeout</c></tag>
<item><p>Defines the time-out
for all calls to
- <seealso marker="stdlib:sys#change_code/4"><c>sys:change_code</c></seealso>.
+ <seemfa marker="stdlib:sys#change_code/4"><c>sys:change_code</c></seemfa>.
If no value is specified or <c>default</c> is specified, the
default value defined in <c>sys</c> is used.</p></item>
<tag><c>suspend_timeout</c></tag>
<item><p>Defines the time-out for
all calls to
- <seealso marker="stdlib:sys#suspend/1"><c>sys:suspend</c></seealso>.
+ <seemfa marker="stdlib:sys#suspend/1"><c>sys:suspend</c></seemfa>.
If no value is specified, the values defined by the <c>Timeout</c>
parameter of the <c>upgrade</c> or <c>suspend</c> instructions are used.
If <c>default</c> is specified, the default value defined in
@@ -330,8 +330,8 @@
effect for other application directories than the default
<c>$ROOT/lib/App-Vsn</c>, that is, application directories
specified in argument <c>AppDirs</c> in a call to
- <seealso marker="#create_RELEASES/4"><c>create_RELEASES/4</c></seealso> or
- <seealso marker="#set_unpacked/2"><c>set_unpacked/2</c></seealso>.</p>
+ <seemfa marker="#create_RELEASES/4"><c>create_RELEASES/4</c></seemfa> or
+ <seemfa marker="#set_unpacked/2"><c>set_unpacked/2</c></seemfa>.</p>
<p><em>Example:</em></p>
<p>In the current version <c>CurVsn</c> of a release, the
application directory of <c>myapp</c> is
@@ -343,7 +343,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
=> {ok,NewVsn}</code>
<p>If <c>NewVsn</c> is installed with option
<c>{update_paths,true}</c>, then
- <seealso marker="kernel:code#lib_dir/1"><c>code:lib_dir(myapp)</c></seealso>
+ <seemfa marker="kernel:code#lib_dir/1"><c>code:lib_dir(myapp)</c></seemfa>
returns <c>/home/user/myapp-1.0</c>.</p></item>
</taglist>
<note>
@@ -353,14 +353,14 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
before a module can be purged. This check can lead to
garbage collections and copying of data.</p>
<p>To speed up the execution of
- <seealso marker="#install_release/1"><c>install_release</c></seealso>,
- first call <seealso
- marker="#check_install_release/1"><c>check_install_release</c></seealso>,
+ <seemfa marker="#install_release/1"><c>install_release</c></seemfa>,
+ first call <seemfa
+ marker="#check_install_release/1"><c>check_install_release</c></seemfa>,
using option <c>purge</c>. This does the same
check for old code. Then purges all modules that can be
soft-purged. The purged modules do then no longer have any
old code, and
- <seealso marker="#install_release/1"><c>install_release</c></seealso>
+ <seemfa marker="#install_release/1"><c>install_release</c></seemfa>
does not need to do the
checks.</p>
<p>This does not reduce the overall time for the
@@ -376,8 +376,8 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
overcome this problem, the new application code is to be
compiled with the old emulator. For more information about
emulator upgrade from pre OTP R15 versions, see
- <seealso marker="doc/design_principles:appup_cookbook">Design
- Principles</seealso> in <em>System Documentation</em>.</p>
+ <seeguide marker="system/design_principles:appup_cookbook">Design
+ Principles</seeguide> in <em>System Documentation</em>.</p>
</note>
</desc>
</func>
@@ -419,7 +419,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<desc>
<p>Reboots the system by making the old release permanent, and
calls
- <seealso marker="erts:init#reboot/0"><c>init:reboot()</c></seealso>
+ <seemfa marker="erts:init#reboot/0"><c>init:reboot()</c></seemfa>
directly. The release must have status <c>old</c>.</p>
</desc>
</func>
@@ -509,33 +509,34 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
</funcs>
- <section>
- <title>Application Upgrade/Downgrade</title>
- <p>The following functions can be used to test upgrade and downgrade
- of single applications (instead of upgrading/downgrading an entire
- release). A script corresponding to the instructions in the
- <c>relup</c> file is created
- on-the-fly, based on the <c>.appup</c> file for the application,
- and evaluated exactly in the same way as <c>release_handler</c>
- does.</p>
- <warning>
- <p>These functions are primarily intended for simplified testing
- of <c>.appup</c> files. They are not run within the context of
- the <c>release_handler</c> process. They must therefore
- <em>not</em> be used together with calls to
- <seealso marker="#install_release/1"><c>install_release/1,2</c></seealso>,
- as this causes the
- <c>release_handler</c> to end up in an inconsistent state.</p>
- <p>No persistent information is updated, so these functions can
- be used on any Erlang node, embedded or not. Also, using these
- functions does not affect which code is loaded if there is
- a reboot.</p>
- <p>If the upgrade or downgrade fails, the application can end up
- in an inconsistent state.</p>
- </warning>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Application Upgrade/Downgrade</title>
+ <p>The following functions can be used to test upgrade and downgrade
+ of single applications (instead of upgrading/downgrading an entire
+ release). A script corresponding to the instructions in the
+ <c>relup</c> file is created
+ on-the-fly, based on the <c>.appup</c> file for the application,
+ and evaluated exactly in the same way as <c>release_handler</c>
+ does.</p>
+ <warning>
+ <p>These functions are primarily intended for simplified testing
+ of <c>.appup</c> files. They are not run within the context of
+ the <c>release_handler</c> process. They must therefore
+ <em>not</em> be used together with calls to
+ <seemfa marker="#install_release/1"><c>install_release/1,2</c></seemfa>,
+ as this causes the
+ <c>release_handler</c> to end up in an inconsistent state.</p>
+ <p>No persistent information is updated, so these functions can
+ be used on any Erlang node, embedded or not. Also, using these
+ functions does not affect which code is loaded if there is
+ a reboot.</p>
+ <p>If the upgrade or downgrade fails, the application can end up
+ in an inconsistent state.</p>
+ </warning>
+ </fsdescription>
<func>
<name since="">upgrade_app(App, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
<fsummary>Upgrades to a new application version.</fsummary>
@@ -558,11 +559,11 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<p>The function looks in the <c>.appup</c> file and tries to
find an upgrade script from the current version of
the application using
- <seealso marker="#upgrade_script/2"><c>upgrade_script/2</c></seealso>.
+ <seemfa marker="#upgrade_script/2"><c>upgrade_script/2</c></seemfa>.
This script is evaluated using
- <seealso marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seealso>,
+ <seemfa marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seemfa>,
exactly in the same way as
- <seealso marker="#install_release/1"><c>install_release/1,2</c></seealso>
+ <seemfa marker="#install_release/1"><c>install_release/1,2</c></seemfa>
does.</p>
<p>Returns one of the following:</p>
<list type="bulleted">
@@ -576,12 +577,12 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</list>
<p>If the <c>restart_new_emulator</c> instruction is found in
the script,
- <seealso marker="#upgrade_app/2"><c>upgrade_app/2</c></seealso>
+ <seemfa marker="#upgrade_app/2"><c>upgrade_app/2</c></seemfa>
returns <c>{error,restart_new_emulator}</c>. This because
<c>restart_new_emulator</c> requires a new version of the
emulator to be started before the rest of the upgrade
instructions can be executed, and this can only be done by
- <seealso marker="#install_release/1"><c>install_release/1,2</c></seealso>.</p>
+ <seemfa marker="#install_release/1"><c>install_release/1,2</c></seemfa>.</p>
</desc>
</func>
@@ -609,15 +610,15 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
are to be located under <c>Dir/ebin</c>. The <c>.appup</c>
file is to be located in the <c>ebin</c> directory of
the <em>current</em> library directory of the application
- (<seealso marker="kernel:code#lib_dir/1"><c>code:lib_dir(App)</c></seealso>).</p>
+ (<seemfa marker="kernel:code#lib_dir/1"><c>code:lib_dir(App)</c></seemfa>).</p>
<p>The function looks in the <c>.appup</c> file and tries to
find a downgrade script to the previous version of
the application using
- <seealso marker="#downgrade_script/3"><c>downgrade_script/3</c></seealso>.
+ <seemfa marker="#downgrade_script/3"><c>downgrade_script/3</c></seemfa>.
This script is evaluated using
- <seealso marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seealso>,
+ <seemfa marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seemfa>,
exactly in the same way as
- <seealso marker="#install_release/1"><c>install_release/1,2</c></seealso>
+ <seemfa marker="#install_release/1"><c>install_release/1,2</c></seemfa>
does.</p>
<p>Returns one of the following:</p>
<list type="bulleted">
@@ -646,9 +647,9 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
from the current version to a new version located in
<c>Dir</c>.</p>
<p>The upgrade script can then be evaluated using
- <seealso marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seealso>.
+ <seemfa marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seemfa>.
It is recommended to use
- <seealso marker="#upgrade_app/2"><c>upgrade_app/2</c></seealso>
+ <seemfa marker="#upgrade_app/2"><c>upgrade_app/2</c></seemfa>
instead, but this function (<c>upgrade_script</c>) is useful
to inspect the contents of the script.</p>
<p><c>App</c> is the name of the application, which must be
@@ -664,7 +665,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<p>Returns <c>{ok, NewVsn, Script}</c> if successful, where
<c>NewVsn</c> is the new application version.
For details about <c>Script</c>, see
- <seealso marker="appup"><c>appup(4)</c></seealso>.</p>
+ <seefile marker="appup"><c>appup(4)</c></seefile>.</p>
<p>Failure: If a script cannot be found, the function fails
with an appropriate error reason.</p>
</desc>
@@ -683,9 +684,9 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
from the current version to a previous version <c>OldVsn</c>
located in <c>Dir</c>.</p>
<p>The downgrade script can then be evaluated using
- <seealso marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seealso>.
+ <seemfa marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seemfa>.
It is recommended to use
- <seealso marker="#downgrade_app/2"><c>downgrade_app/2,3</c></seealso>
+ <seemfa marker="#downgrade_app/2"><c>downgrade_app/2,3</c></seemfa>
instead, but this function (<c>downgrade_script</c>) is useful
to inspect the contents of the script.</p>
<p><c>App</c> is the name of the application, which must be
@@ -695,7 +696,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<c>Dir/ebin</c>. The <c>.appup</c> file is to be located in
the <c>ebin</c> directory of the <em>current</em> library
directory of the application
- (<seealso marker="kernel:code#lib_dir/1"><c>code:lib_dir(App)</c>)</seealso>.</p>
+ (<seemfa marker="kernel:code#lib_dir/1"><c>code:lib_dir(App)</c>)</seemfa>.</p>
<p>The function looks in the <c>.appup</c> file and tries to
find a downgrade script from the current application version.
High-level instructions are translated to
@@ -703,7 +704,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
the same manner as when generating a <c>relup</c> file.</p>
<p>Returns <c>{ok, Script}</c> if successful.
For details about <c>Script</c>, see
- <seealso marker="appup"><c>appup(4)</c></seealso>.</p>
+ <seefile marker="appup"><c>appup(4)</c></seefile>.</p>
<p>Failure: If a script cannot be found, the function fails
with an appropriate error reason.</p>
</desc>
@@ -716,7 +717,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<v>App = atom()</v>
<v>ToVsn = ToDir = string()</v>
<v>Script</v>
- <d>See <seealso marker="#upgrade_script/2"><c>upgrade_script/2</c></seealso>, <seealso marker="#downgrade_script/3"><c>downgrade_script/3</c></seealso></d>
+ <d>See <seemfa marker="#upgrade_script/2"><c>upgrade_script/2</c></seemfa>, <seemfa marker="#downgrade_script/3"><c>downgrade_script/3</c></seemfa></d>
<v>Unpurged = [Module]</v>
<v>&nbsp;Module = atom()</v>
<v>Reason = term()</v>
@@ -724,10 +725,10 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<desc>
<p>Evaluates an application upgrade or downgrade script
<c>Script</c>, the result from calling
- <seealso marker="#upgrade_script/2"><c>upgrade_script/2</c></seealso> or
- <seealso marker="#downgrade_script/3"><c>downgrade_script/3</c></seealso>,
+ <seemfa marker="#upgrade_script/2"><c>upgrade_script/2</c></seemfa> or
+ <seemfa marker="#downgrade_script/3"><c>downgrade_script/3</c></seemfa>,
exactly in the same way as
- <seealso marker="#install_release/1"><c>install_release/1,2</c></seealso>
+ <seemfa marker="#install_release/1"><c>install_release/1,2</c></seemfa>
does.</p>
<p><c>App</c> is the name of the application, which must be
started. <c>ToVsn</c> is the version to be upgraded/downgraded
@@ -746,12 +747,12 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</list>
<p>If the <c>restart_new_emulator</c> instruction is found in
the script,
- <seealso marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seealso>
+ <seemfa marker="#eval_appup_script/4"><c>eval_appup_script/4</c></seemfa>
returns <c>{error,restart_new_emulator}</c>. This because
<c>restart_new_emulator</c> requires a new version of the
emulator to be started before the rest of the upgrade
instructions can be executed, and this can only be done by
- <seealso marker="#install_release/1"><c>install_release/1,2</c></seealso>.</p>
+ <seemfa marker="#install_release/1"><c>install_release/1,2</c></seemfa>.</p>
</desc>
</func>
</funcs>
@@ -772,7 +773,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
data.</p></item>
<tag><c>{cannot_extract_file, Name, Reason}</c></tag>
<item><p>Problems when extracting from a tar file,
- <seealso marker="stdlib:erl_tar#extract/2"><c>erl_tar:extract/2</c></seealso>
+ <seemfa marker="stdlib:erl_tar#extract/2"><c>erl_tar:extract/2</c></seemfa>
returned <c>{error, {Name, Reason}}</c>.</p></item>
<tag><c>{existing_release, Vsn}</c></tag>
<item><p>Specified release version <c>Vsn</c> is already
@@ -801,7 +802,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<item><p>Some file operation failed for <c>File</c>. <c>Posix</c>
is an atom named from the Posix error codes, such as
<c>enoent</c>, <c>eacces</c>, or <c>eisdir</c>. See
- <seealso marker="kernel:file"><c>file(3)</c></seealso>
+ <seeerl marker="kernel:file"><c>file(3)</c></seeerl>
in Kernel.</p></item>
<tag><c>Posix</c></tag>
<item><p>Some file operation failed, as for the previous item in
@@ -811,12 +812,12 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<section>
<title>See Also</title>
- <p><seealso marker="doc/design_principles:users_guide">OTP Design Principles</seealso>,
- <seealso marker="kernel:config"><c>config(4)</c></seealso>,
- <seealso marker="rel"><c>rel(4)</c></seealso>,
- <seealso marker="relup"><c>relup(4)</c></seealso>,
- <seealso marker="script"><c>script(4)</c></seealso>,
- <seealso marker="stdlib:sys"><c>sys(3)</c></seealso>,
- <seealso marker="systools"><c>systools(3)</c></seealso></p>
+ <p><seeguide marker="system/design_principles:index">OTP Design Principles</seeguide>,
+ <seefile marker="kernel:config"><c>config(4)</c></seefile>,
+ <seefile marker="rel"><c>rel(4)</c></seefile>,
+ <seefile marker="relup"><c>relup(4)</c></seefile>,
+ <seefile marker="script"><c>script(4)</c></seefile>,
+ <seeerl marker="stdlib:sys"><c>sys(3)</c></seeerl>,
+ <seeerl marker="systools"><c>systools(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/sasl/doc/src/relup.xml b/lib/sasl/doc/src/relup.xml
index 4c5535002c..f1c5a9be72 100644
--- a/lib/sasl/doc/src/relup.xml
+++ b/lib/sasl/doc/src/relup.xml
@@ -36,7 +36,7 @@
<p>The <em>release upgrade file</em> describes how a release is
upgraded in a running system.</p>
<p>This file is automatically generated by
- <seealso marker="systools#make_relup/3"><c>systools:make_relup/3,4</c></seealso>,
+ <seemfa marker="systools#make_relup/3"><c>systools:make_relup/3,4</c></seemfa>,
using a release resource file
(<c>.rel</c>), application resource files (<c>.app</c>), and
application upgrade files (<c>.appup</c>) as input.</p>
@@ -61,12 +61,12 @@
<tag><c>Descr = term()</c></tag>
<item><p>A user-defined parameter passed
from the function
- <seealso marker="systools#make_relup/3"><c>systools:make_relup/3,4</c></seealso>.
+ <seemfa marker="systools#make_relup/3"><c>systools:make_relup/3,4</c></seemfa>.
It is used in the return value of
- <seealso marker="release_handler#install_release/1"><c>release_handler:install_release/1,2</c></seealso>.</p></item>
+ <seemfa marker="release_handler#install_release/1"><c>release_handler:install_release/1,2</c></seemfa>.</p></item>
<tag><c>Instructions</c></tag>
<item><p>A list of low-level release upgrade instructions, see
- <seealso marker="appup"><c>appup(4)</c></seealso>.
+ <seefile marker="appup"><c>appup(4)</c></seefile>.
It consists of the release upgrade instructions from
the respective application upgrade files (high-level instructions
are translated to low-level instructions), in the same order
@@ -78,11 +78,11 @@
<section>
<title>See Also</title>
- <p><seealso marker="kernel:app"><c>app(4)</c></seealso>,
- <seealso marker="appup"><c>appup(4)</c></seealso>,
- <seealso marker="rel"><c>rel(4)</c></seealso>,
- <seealso marker="release_handler"><c>release_handler(3)</c></seealso>,
- <seealso marker="systools"><c>systools(3)</c></seealso></p>
+ <p><seefile marker="kernel:app"><c>app(4)</c></seefile>,
+ <seefile marker="appup"><c>appup(4)</c></seefile>,
+ <seefile marker="rel"><c>rel(4)</c></seefile>,
+ <seeerl marker="release_handler"><c>release_handler(3)</c></seeerl>,
+ <seeerl marker="systools"><c>systools(3)</c></seeerl></p>
</section>
</fileref>
diff --git a/lib/sasl/doc/src/sasl_app.xml b/lib/sasl/doc/src/sasl_app.xml
index fc83f63fe6..4209df2d8b 100644
--- a/lib/sasl/doc/src/sasl_app.xml
+++ b/lib/sasl/doc/src/sasl_app.xml
@@ -47,7 +47,7 @@
<title>Configuration</title>
<p>The following configuration parameters are defined for the SASL
application. For more information about configuration parameters, see
- <seealso marker="kernel:app"><c>app(4)</c></seealso> in Kernel.</p>
+ <seefile marker="kernel:app"><c>app(4)</c></seefile> in Kernel.</p>
<p>All configuration parameters are optional.</p>
<taglist>
<tag><c><![CDATA[start_prg = string() ]]></c></tag>
@@ -66,7 +66,7 @@
<item>
<p>This parameter specifies the client directory at the master
nodes. For details, see
- <seealso marker="doc/design_principles:release_handling">Release Handling</seealso>
+ <seeguide marker="system/design_principles:release_handling">Release Handling</seeguide>
in <em>OTP Design Principles</em>. This parameter is
ignored if parameter <c>masters</c> is not set.</p>
</item>
@@ -105,8 +105,8 @@
reports are by default stopped by the primary log level, but can
be enabled by setting this level to <c>info</c>, for example by
using the Kernel configuration
- parameter <seealso marker="kernel:kernel_app#logger_level">
- <c>logger_level</c></seealso>.</p>
+ parameter <seeapp marker="kernel:kernel_app#logger_level">
+ <c>logger_level</c></seeapp>.</p>
<p>If the old error logger event handlers are still desired, they
must be added by
calling <c>error_logger:add_report_handler/1,2</c>.</p>
@@ -116,7 +116,7 @@
<p>Formats and writes <em>supervisor reports</em>, <em>crash
reports</em>, and <em>progress reports</em> to <c>stdio</c>.
This error logger event handler uses
- <seealso marker="kernel:kernel_app#deprecated-configuration-parameters"><c>error_logger_format_depth</c></seealso>
+ <seeapp marker="kernel:kernel_app#deprecated-configuration-parameters"><c>error_logger_format_depth</c></seeapp>
in the Kernel application to limit how much detail is printed
in crash and supervisor reports.</p>
</item>
@@ -125,15 +125,15 @@
<p>Formats and writes <em>supervisor reports</em>, <em>crash
report</em>, and <em>progress report</em> to a single file.
This error logger event handler uses
- <seealso marker="kernel:kernel_app#deprecated-configuration-parameters"><c>error_logger_format_depth</c></seealso>
+ <seeapp marker="kernel:kernel_app#deprecated-configuration-parameters"><c>error_logger_format_depth</c></seeapp>
in the Kernel application to limit the details printed in
crash and supervisor reports.</p>
</item>
</taglist>
<p>A similar behaviour, but still using the new logger API, can be
obtained by setting the Kernel application environment
- variable <seealso marker="kernel:kernel_app#logger_sasl_compatible">
- <c>logger_sasl_compatible</c></seealso> to <c>true</c>. This
+ variable <seeapp marker="kernel:kernel_app#logger_sasl_compatible">
+ <c>logger_sasl_compatible</c></seeapp> to <c>true</c>. This
adds a second instance of the standard Logger handler,
named <c>sasl</c>, which only prints the SASL reports. No SASL
reports are then printed by the Kernel logger handler.</p>
@@ -155,7 +155,7 @@
<tag><c>{file,FileName,Modes}</c></tag>
<item><p>Same as <c>{file,FileName}</c>, except that <c>Modes</c>
allows you to specify the modes used for opening the <c>FileName</c>
- given to the <seealso marker="kernel:file#open/2">file:open/2</seealso>
+ given to the <seemfa marker="kernel:file#open/2">file:open/2</seemfa>
call. By default, the file is opened in <c>write</c> mode
with encoding <c>utf8</c>. Use <c>[append]</c> to have
the <c>FileName</c> open in append mode. A different
@@ -184,8 +184,8 @@
the error logger to disk. Multiple files and log rotation are
used. For efficiency reasons, each event is written as a
binary. For more information about this handler,
- see <seealso marker="stdlib:log_mf_h">the STDLIB Reference
- Manual</seealso>.</p>
+ see <seeerl marker="stdlib:log_mf_h">the STDLIB Reference
+ Manual</seeerl>.</p>
<p>To activate this event handler, three SASL configuration
parameters must be
set:</p>
@@ -209,8 +209,8 @@
not installed.</p>
</item>
</taglist>
- <p>The new <seealso marker="kernel:logger_disk_log_h">
- <c>logger_disk_log_h</c></seealso> might be an alternative
+ <p>The new <seeerl marker="kernel:logger_disk_log_h">
+ <c>logger_disk_log_h</c></seeerl> might be an alternative
to <c>log_mf_h</c> if log rotation is desired. This does,
however, write the log events in clear text and not as binaries.</p>
@@ -218,13 +218,13 @@
<section>
<title>See Also</title>
- <p><seealso marker="alarm_handler"><c>alarm_handler(3)</c></seealso>,
- <seealso marker="kernel:error_logger"><c>error_logger(3)</c></seealso>,
- <seealso marker="kernel:logger"><c>logger(3)</c></seealso>,
- <seealso marker="stdlib:log_mf_h"><c>log_mf_h(3)</c></seealso>,
- <seealso marker="rb"><c>rb(3)</c></seealso>,
- <seealso marker="release_handler"><c>release_handler(3)</c></seealso>,
- <seealso marker="systools"><c>systools(3)</c></seealso></p>
+ <p><seeerl marker="alarm_handler"><c>alarm_handler(3)</c></seeerl>,
+ <seeerl marker="kernel:error_logger"><c>error_logger(3)</c></seeerl>,
+ <seeerl marker="kernel:logger"><c>logger(3)</c></seeerl>,
+ <seeerl marker="stdlib:log_mf_h"><c>log_mf_h(3)</c></seeerl>,
+ <seeerl marker="rb"><c>rb(3)</c></seeerl>,
+ <seeerl marker="release_handler"><c>release_handler(3)</c></seeerl>,
+ <seeerl marker="systools"><c>systools(3)</c></seeerl></p>
</section>
</appref>
diff --git a/lib/sasl/doc/src/sasl_intro.xml b/lib/sasl/doc/src/sasl_intro.xml
index f74a7c1db8..fe74e42347 100644
--- a/lib/sasl/doc/src/sasl_intro.xml
+++ b/lib/sasl/doc/src/sasl_intro.xml
@@ -40,14 +40,14 @@
<item>Report browsing</item>
</list>
<p>Section
- <seealso marker="error_logging">SASL Error Logging</seealso>
+ <seeguide marker="error_logging">SASL Error Logging</seeguide>
describes the error
handler that produces the supervisor, progress, and crash
reports, which can be written to screen or to a specified file.
It also describes the Report Browser (RB).</p>
<p>The sections about release structure and release handling have
been moved to section
- <seealso marker="doc/design_principles:users_guide">OTP Design Principles</seealso>
+ <seeguide marker="system/design_principles:index">OTP Design Principles</seeguide>
in <em>System Documentation</em>.</p>
</section>
diff --git a/lib/sasl/doc/src/script.xml b/lib/sasl/doc/src/script.xml
index b40ff28179..1784fc6c30 100644
--- a/lib/sasl/doc/src/script.xml
+++ b/lib/sasl/doc/src/script.xml
@@ -43,7 +43,7 @@
<p>Command <c>erl -boot Name</c> starts the system with a boot
file called <c>Name.boot</c>, which is generated from the
<c>Name.script</c> file, using
- <seealso marker="systools#script2boot/1"><c>systools:script2boot/1</c></seealso>.</p>
+ <seemfa marker="systools#script2boot/1"><c>systools:script2boot/1</c></seemfa>.</p>
<p>The <c>.script</c> file is generated by <c>systools</c> from a
<c>.rel</c> file and from <c>.app</c> files.</p>
</description>
@@ -75,7 +75,7 @@
<tag><c>{progress, Term}</c></tag>
<item><p>Sets the "progress" of the initialization
program. The
- <seealso marker="erts:init#get_status/0"><c>init:get_status/0</c></seealso>
+ <seemfa marker="erts:init#get_status/0"><c>init:get_status/0</c></seemfa>
function returns the current value of the progress, which is
<c>{InternalStatus,Term}</c>.</p></item>
<tag><c>{path, [Dir]}</c></tag>
@@ -115,8 +115,8 @@
<item><p>Loads the modules <c>[Mod]</c>
from the directories specified in <c>Path</c>. The script
interpreter fetches the appropriate module by calling
- <seealso marker="erts:erl_prim_loader#get_file/1">
- <c>erl_prim_loader:get_file(Mod)</c></seealso>. A fatal error
+ <seemfa marker="erts:erl_prim_loader#get_file/1">
+ <c>erl_prim_loader:get_file(Mod)</c></seemfa>. A fatal error
that terminates the system occurs if the module cannot be
located.</p></item>
<tag><c>{kernel_load_completed}</c></tag>
@@ -146,9 +146,9 @@
<p>In an interactive system, the code loader provides
demand-driven code loading, but in an embedded system
the code loader loads all code immediately. The same
- version of <seealso marker="kernel:code"><c>code</c></seealso>
+ version of <seeerl marker="kernel:code"><c>code</c></seeerl>
is used in both cases. The code server calls
- <seealso marker="erts:init#get_argument/1"><c>init:get_argument(mode)</c></seealso>
+ <seemfa marker="erts:init#get_argument/1"><c>init:get_argument(mode)</c></seemfa>
to determine if it is to run in demand mode or non-demand
driven mode.</p>
</note>
@@ -156,7 +156,7 @@
<section>
<title>See Also</title>
- <p><seealso marker="systools"><c>systools(3)</c></seealso></p>
+ <p><seeerl marker="systools"><c>systools(3)</c></seeerl></p>
</section>
</fileref>
diff --git a/lib/sasl/doc/src/specs.xml b/lib/sasl/doc/src/specs.xml
new file mode 100644
index 0000000000..f64b7df114
--- /dev/null
+++ b/lib/sasl/doc/src/specs.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<specs xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include href="../specs/specs_systools.xml"/>
+</specs>
diff --git a/lib/sasl/doc/src/systools.xml b/lib/sasl/doc/src/systools.xml
index 429fd37517..13878d379a 100644
--- a/lib/sasl/doc/src/systools.xml
+++ b/lib/sasl/doc/src/systools.xml
@@ -59,7 +59,7 @@
<p>Generates a release upgrade file <c>relup</c> containing instructions
for upgrading from or downgrading to one or more previous releases.
The instructions are used by
- <seealso marker="release_handler"><c>release_handler</c></seealso>
+ <seeerl marker="release_handler"><c>release_handler</c></seeerl>
when installing a new version of a release in runtime.</p>
<p>By default, <c>relup</c> file is located in the current working
directory. If option <c>{outdir,Dir}</c> is specified,
@@ -94,13 +94,13 @@
between application versions are fetched from the relevant
application upgrade files <c>App.appup</c>, sorted in
the same order as when generating a boot script, see
- <seealso marker="#make_script/1"><c>make_script/1,2</c></seealso>.
+ <seemfa marker="#make_script/1"><c>make_script/1,2</c></seemfa>.
High-level instructions are translated
into low-level instructions and the result is printed to the
<c>relup</c> file.</p>
<p>The optional <c>Descr</c> parameter is included "as is" in
the <c>relup</c> file, see
- <seealso marker="relup"><c>relup(4)</c></seealso>. Defaults to
+ <seefile marker="relup"><c>relup(4)</c></seefile>. Defaults to
the empty list.</p>
<p>All the files are searched for in the code path. It is
assumed that the <c>.app</c> and <c>.appup</c> files for an
@@ -117,8 +117,8 @@
than OTP R15 to OTP R15 or later, the warning
<c>pre_R15_emulator_upgrade</c> is issued. For more information
about this, see
- <seealso marker="doc/design_principles:appup_cookbook">Design
- Principles</seealso> in <em>System Documentation</em>.</p>
+ <seeguide marker="system/design_principles:appup_cookbook">Design
+ Principles</seeguide> in <em>System Documentation</em>.</p>
<p>By default, errors and warnings are printed to tty and
the function returns <c>ok</c> or <c>error</c>. If option
<c>silent</c> is specified, the function instead either returns
@@ -143,7 +143,7 @@
<v>Name = string()</v>
<v>Opt = src_tests | {path,[Dir]} | local | {variables,[Var]} | exref |
{exref,[App]}] | silent | {outdir,Dir} | no_dot_erlang | no_warn_sasl |
- warnings_as_errors</v>
+ warnings_as_errors | {script_name, Name}</v>
<v>&nbsp;Dir = string()</v>
<v>&nbsp;Var = {VarName,Prefix}</v>
<v>&nbsp;&nbsp;VarName = Prefix = string()</v>
@@ -154,17 +154,19 @@
</type>
<desc>
<p>Generates a boot script <c>Name.script</c> and its binary
- version, the boot file <c>Name.boot</c>. The boot file
+ version, the boot file <c>Name.boot</c>, unless the <c>{script_name, ScriptName}</c>
+ option is given, in which case the names are <c>ScriptName.script</c>
+ and <c>ScriptName.boot</c> The boot file
specifies which code to be loaded and which applications
to be started when the Erlang runtime system is started.
- See <seealso marker="script"><c>script(4)</c></seealso>.</p>
+ See <seefile marker="script"><c>script(4)</c></seefile>.</p>
<p>The release resource file <c>Name.rel</c> is read to determine
which applications are included in the release. Then
the relevant application resource files <c>App.app</c> are read
to determine which modules to be loaded, and if and
how the applications are to be started. (Keys <c>modules</c>
and <c>mod</c>, see
- <seealso marker="kernel:app"><c>app(4)</c></seealso>.</p>
+ <seefile marker="kernel:app"><c>app(4)</c></seefile>.</p>
<p>By default, the boot script and boot file are located in
the same directory as <c>Name.rel</c>. That is, in the current
working directory unless <c>Name</c> contains a path. If
@@ -249,8 +251,8 @@
warnings are issued for calls to undefined functions.</p>
<p>By default, errors and warnings are printed to tty and
the function returns <c>ok</c> or <c>error</c>. If option
- <c>silent</c> is specified, the function instead returns
<c>{ok,Module,Warnings}</c> or <c>{error,Module,Error}</c>.
+ <c>silent</c> is specified, the function instead returns
Warnings and errors can be converted to strings by calling
<c>Module:format_warning(Warnings)</c> or
<c>Module:format_error(Error)</c>.</p>
@@ -263,35 +265,21 @@
</func>
<func>
- <name since="">make_tar(Name) -> Result</name>
- <name since="">make_tar(Name, [Opt]) -> Result</name>
+ <name name="make_tar" arity="1" since=""/>
+ <name name="make_tar" arity="2" since=""/>
<fsummary>Creates a release package.</fsummary>
- <type>
- <v>Name = string()</v>
- <v>Opt = {dirs,[IncDir]} | {path,[Dir]} | {variables,[Var]} | {var_tar,VarTar} | {erts,Dir} | src_tests | exref | {exref,[App]} | silent | {outdir,Dir} | | no_warn_sasl | warnings_as_errors</v>
- <v>&nbsp;Dir = string()</v>
- <v>&nbsp;IncDir = src | include | atom()</v>
- <v>&nbsp;Var = {VarName,PreFix}</v>
- <v>&nbsp;&nbsp;VarName = Prefix = string()</v>
- <v>&nbsp;VarTar = include | ownfile | omit</v>
- <v>&nbsp;Machine = atom()</v>
- <v>&nbsp;App = atom()</v>
- <v>Result = ok | error | {ok,Module,Warnings} | {error,Module,Error}</v>
- <v>&nbsp;Module = atom()</v>
- <v>&nbsp;Warning = Error = term()</v>
- </type>
<desc>
<p>Creates a release package file <c>Name.tar.gz</c>.
This file must be uncompressed and unpacked on the target
system using
- <seealso marker="release_handler"><c>release_handler</c></seealso>
+ <seeerl marker="release_handler"><c>release_handler</c></seeerl>
before the new release can be installed.</p>
<p>The release resource file <c>Name.rel</c> is read to determine
which applications are included in the release. Then
the relevant application resource files <c>App.app</c> are
read to determine the version and modules of each application
(keys <c>vsn</c> and <c>modules</c>, see
- <seealso marker="kernel:app"><c>app(4)</c></seealso>).</p>
+ <seefile marker="kernel:app"><c>app(4)</c></seefile>).</p>
<p>By default, the release package file is located in the same
directory as <c>Name.rel</c>. That is, in the current working
directory unless <c>Name</c> contains a path. If option
@@ -311,6 +299,11 @@
appended to the current path. Wildcard <c>*</c> is
expanded to all matching directories.
Example: <c>"lib/*/ebin"</c>.</p>
+ <p>If the <c>{extra_files, ExtraFiles}</c> option is given then the
+ <c>ExtraFiles</c> are added to the tarball after everything else to be
+ included has been added. The <c>ExtraFiles</c> list is a list of files
+ or directories in the same format as the <c>add_type()</c> tuple for
+ <seemfa marker="stdlib:erl_tar#add/3">erl_tar:add/3,4</seemfa></p>
<p>Option <c>variables</c> can be used to specify an
installation directory other than <c>lib</c> for some of
the applications. If variable <c>{VarName,Prefix}</c> is
@@ -355,16 +348,18 @@ myapp-1/ebin/myapp.app
specified using option <c>path</c>. In the case of <c>sys.config</c>
it is not included if <c>sys.config.src</c> is found.</p>
<p>If the release package is to contain a new Erlang runtime
- system, the <c>bin</c> directory of the specified runtime
- system <c>{erts,Dir}</c> is copied to <c>erts-ErtsVsn/bin</c>.</p>
+ system, the <c>erts-ErtsVsn/bin</c> directory of the specified runtime
+ system <c>{erts,Dir}</c> is copied to <c>erts-ErtsVsn/bin</c>. Some
+ erts executables are not copied by default, if you want to include all
+ executables you can give the <c>erts_all</c> option.</p>
<p>All checks with function
- <seealso marker="#make_script/1"><c>make_script</c></seealso>
+ <seemfa marker="#make_script/1"><c>make_script</c></seemfa>
are performed before the release package is created.
Options <c>src_tests</c> and <c>exref</c> are also
valid here.</p>
<p>The return value and the handling of errors and warnings
are the same as described for
- <seealso marker="#make_script/1"><c>make_script</c></seealso>.</p>
+ <seemfa marker="#make_script/1"><c>make_script</c></seemfa>.</p>
</desc>
</func>
@@ -381,7 +376,7 @@ myapp-1/ebin/myapp.app
to a binary term, which is stored in the <c>File.boot</c>
file.</p>
<p>A boot script generated using
- <seealso marker="#make_script/1"><c>make_script</c></seealso>
+ <seemfa marker="#make_script/1"><c>make_script</c></seemfa>
is already transformed to the binary form.</p>
</desc>
</func>
@@ -389,12 +384,12 @@ myapp-1/ebin/myapp.app
<section>
<title>See Also</title>
- <p><seealso marker="kernel:app"><c>app(4)</c></seealso>,
- <seealso marker="appup"><c>appup(4)</c></seealso>,
- <seealso marker="erts:erl"><c>erl(1)</c></seealso>,
- <seealso marker="rel"><c>rel(4)</c></seealso>,
- <seealso marker="release_handler"><c>release_handler(3)</c></seealso>,
- <seealso marker="relup"><c>relup(4)</c></seealso>,
- <seealso marker="script"><c>script(4)</c></seealso></p>
+ <p><seefile marker="kernel:app"><c>app(4)</c></seefile>,
+ <seefile marker="appup"><c>appup(4)</c></seefile>,
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom>,
+ <seefile marker="rel"><c>rel(4)</c></seefile>,
+ <seeerl marker="release_handler"><c>release_handler(3)</c></seeerl>,
+ <seefile marker="relup"><c>relup(4)</c></seefile>,
+ <seefile marker="script"><c>script(4)</c></seefile></p>
</section>
</erlref>
diff --git a/lib/sasl/src/release_handler_1.erl b/lib/sasl/src/release_handler_1.erl
index de525b292d..ddaa5c7577 100644
--- a/lib/sasl/src/release_handler_1.erl
+++ b/lib/sasl/src/release_handler_1.erl
@@ -659,16 +659,25 @@ get_proc_state(Proc) ->
{status, _, {module, _}, [_, State, _, _, _]} when State == running ;
State == suspended ->
State
- catch exit:{noproc, {sys, get_status, [Proc]}} ->
+ catch exit:{Reason, {sys, get_status, [Proc]}}
+ when Reason =/= timeout andalso
+ not (is_tuple(Reason) andalso
+ element(1,Reason) =:= nodedown) ->
noproc
end.
maybe_get_dynamic_mods(Name, Pid) ->
- case catch gen:call(Pid, self(), get_modules) of
+ try gen:call(Pid, self(), get_modules) of
{ok, Res} ->
- Res;
- Other ->
- error_logger:error_msg("release_handler: ~p~nerror during a"
+ Res
+ catch
+ exit:Reason when Reason =/= timeout andalso
+ not (is_tuple(Reason) andalso
+ element(1,Reason) =:= nodedown) ->
+ [];
+ exit:Other ->
+ error_logger:error_msg("release_handler: {'EXIT',~p}~n"
+ "error during a"
" get_modules call to ~p (~w),"
" there may be an error in it's"
" childspec. Exiting ...~n",
diff --git a/lib/sasl/src/sasl.appup.src b/lib/sasl/src/sasl.appup.src
index b795123645..4fff8f79ab 100644
--- a/lib/sasl/src/sasl.appup.src
+++ b/lib/sasl/src/sasl.appup.src
@@ -21,6 +21,7 @@
%% versions from the following OTP releases:
%% - OTP 21
%% - OTP 22
+%% - OTP 23
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
@@ -34,7 +35,11 @@
{<<"^3\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.4$">>,[restart_new_emulator]},
{<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^4\\.0$">>,[restart_new_emulator]},
+ {<<"^4\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^4\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
[{<<"^3\\.2$">>,[restart_new_emulator]},
{<<"^3\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -42,4 +47,8 @@
{<<"^3\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.4$">>,[restart_new_emulator]},
{<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
+ {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^4\\.0$">>,[restart_new_emulator]},
+ {<<"^4\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^4\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/sasl/src/systools.erl b/lib/sasl/src/systools.erl
index 34eca6679f..a6aa4919ec 100644
--- a/lib/sasl/src/systools.erl
+++ b/lib/sasl/src/systools.erl
@@ -62,8 +62,32 @@ make_script(RelName, Opt) ->
%% release package and erts specifies that the erts-Vsn/bin directory
%% should be included in the release package and there it can be found.
%%-----------------------------------------------------------------
+-spec make_tar(Name) -> Result when
+ Name :: string(),
+ Result :: ok | error | {ok, Module :: module(), Warnings :: term()} |
+ {error, Module :: module(), Error :: term()}.
make_tar(RelName) -> make_tar(RelName, []).
+-spec make_tar(Name, Opts) -> Result when
+ Name :: string(),
+ Opts :: [Opt],
+ Opt :: {dirs,[IncDir]} | {path,[Dir]} |
+ {variables,[Var]} | {var_tar,VarTar} |
+ {erts,Dir} | erts_all | src_tests | exref |
+ {exref,[App]} | silent | {outdir,Dir} |
+ no_warn_sasl | warnings_as_errors |
+ {extra_files, ExtraFiles},
+ Dir :: file:filename_all(),
+ IncDir :: src | include | atom(),
+ Var :: {VarName,PreFix},
+ VarName :: string(),
+ PreFix :: string(),
+ VarTar :: include | ownfile | omit,
+ App :: atom(),
+ Result :: ok | error | {ok, Module :: module(), Warnings :: term()} |
+ {error, Module :: module(), Error :: term()},
+ ExtraFiles :: [{NameInArchive, file:filename_all()}],
+ NameInArchive :: string().
make_tar(RelName, Opt) ->
systools_make:make_tar(RelName, Opt).
diff --git a/lib/sasl/src/systools_lib.erl b/lib/sasl/src/systools_lib.erl
index dd97aeff08..f5489e7900 100644
--- a/lib/sasl/src/systools_lib.erl
+++ b/lib/sasl/src/systools_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -63,8 +63,8 @@ read_term(File) ->
end.
read_term_from_stream(Stream, File) ->
- _ = epp:set_encoding(Stream),
- R = io:request(Stream, {get_until,'',erl_scan,tokens,[1]}),
+ Encoding = epp:set_encoding(Stream),
+ R = io:request(Stream, {get_until,Encoding,'',erl_scan,tokens,[1]}),
case R of
{ok,Toks,_EndLine} ->
case erl_parse:parse_term(Toks) of
diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl
index 90a513a39c..a371239823 100644
--- a/lib/sasl/src/systools_make.erl
+++ b/lib/sasl/src/systools_make.erl
@@ -27,8 +27,7 @@
-export([format_error/1, format_warning/1]).
--export([read_release/2, get_release/2, get_release/3,
- get_release/4, pack_app/1]).
+-export([read_release/2, get_release/2, get_release/3, pack_app/1]).
-export([read_application/4]).
@@ -47,11 +46,9 @@
-compile({inline,[{badarg,2}]}).
-ifdef(USE_ESOCK).
--define(ESOCK_SOCKET_MODS, [socket, socket_registry]).
--define(ESOCK_NET_MODS, [prim_net]).
+-define(ESOCK_MODS, [prim_net,prim_socket,socket_registry]).
-else.
--define(ESOCK_SOCKET_MODS, []).
--define(ESOCK_NET_MODS, []).
+-define(ESOCK_MODS, []).
-endif.
@@ -67,7 +64,6 @@
%% New options: {path,Path} can contain wildcards
%% src_tests
%% {variables,[{Name,AbsString}]}
-%% {machine, jam | beam | vee}
%% exref | {exref, [AppName]}
%% no_warn_sasl
%%-----------------------------------------------------------------
@@ -78,15 +74,16 @@ make_script(RelName) ->
badarg(RelName,[RelName]).
make_script(RelName, Flags) when is_list(RelName), is_list(Flags) ->
+ ScriptName = get_script_name(RelName, Flags),
case get_outdir(Flags) of
"" ->
- make_script(RelName, RelName, Flags);
+ make_script(RelName, ScriptName, Flags);
OutDir ->
%% To maintain backwards compatibility for make_script/3,
%% the boot script file name is constructed here, before
%% checking the validity of OutDir
%% (is done in check_args_script/1)
- Output = filename:join(OutDir, filename:basename(RelName)),
+ Output = filename:join(OutDir, filename:basename(ScriptName)),
make_script(RelName, Output, Flags)
end.
@@ -99,7 +96,7 @@ make_script(RelName, Output, Flags) when is_list(RelName),
Path1 = mk_path(Path0), % expand wildcards etc.
Path = make_set(Path1 ++ code:get_path()),
ModTestP = {member(src_tests, Flags),xref_p(Flags)},
- case get_release(RelName, Path, ModTestP, machine(Flags)) of
+ case get_release(RelName, Path, ModTestP) of
{ok, Release, Appls, Warnings0} ->
Warnings = wsasl(Flags, Warnings0),
case systools_lib:werror(Flags, Warnings) of
@@ -138,10 +135,10 @@ wsasl(Options, Warnings) ->
badarg(BadArg, Args) ->
erlang:error({badarg,BadArg}, Args).
-machine(Flags) ->
- case get_flag(machine,Flags) of
- {machine, Machine} when is_atom(Machine) -> Machine;
- _ -> false
+get_script_name(RelName, Flags) ->
+ case get_flag(script_name,Flags) of
+ {script_name,ScriptName} when is_list(ScriptName) -> ScriptName;
+ _ -> RelName
end.
get_path(Flags) ->
@@ -357,7 +354,6 @@ add_apply_upgrade(Script,Args) ->
%% src_tests
%% exref | {exref, [AppName]}
%% {variables,[{Name,AbsString}]}
-%% {machine, jam | beam | vee}
%% {var_tar, include | ownfile | omit}
%% no_warn_sasl
%% warnings_as_errors
@@ -393,7 +389,7 @@ make_tar(RelName, Flags) when is_list(RelName), is_list(Flags) ->
Path1 = mk_path(Path0),
Path = make_set(Path1 ++ code:get_path()),
ModTestP = {member(src_tests, Flags),xref_p(Flags)},
- case get_release(RelName, Path, ModTestP, machine(Flags)) of
+ case get_release(RelName, Path, ModTestP) of
{ok, Release, Appls, Warnings0} ->
Warnings = wsasl(Flags, Warnings0),
case systools_lib:werror(Flags, Warnings) of
@@ -425,17 +421,13 @@ make_tar(RelName, Flags) ->
%%______________________________________________________________________
%% get_release(File, Path) ->
%% get_release(File, Path, ModTestP) ->
-%% get_release(File, Path, ModTestP, Machine) ->
%% {ok, #release, [{{Name,Vsn},#application}], Warnings} | {error, What}
get_release(File, Path) ->
- get_release(File, Path, {false,false}, false).
+ get_release(File, Path, {false,false}).
get_release(File, Path, ModTestP) ->
- get_release(File, Path, ModTestP, false).
-
-get_release(File, Path, ModTestP, Machine) ->
- case catch get_release1(File, Path, ModTestP, Machine) of
+ case catch get_release1(File, Path, ModTestP) of
{error, Error} ->
{error, ?MODULE, Error};
{'EXIT', Why} ->
@@ -444,12 +436,12 @@ get_release(File, Path, ModTestP, Machine) ->
Answer
end.
-get_release1(File, Path, ModTestP, Machine) ->
+get_release1(File, Path, ModTestP) ->
{ok, Release, Warnings1} = read_release(File, Path),
{ok, Appls0} = collect_applications(Release, Path),
{ok, Appls1} = check_applications(Appls0),
{ok, Appls2} = sort_used_and_incl_appls(Appls1, Release), % OTP-4121, OTP-9984
- {ok, Warnings2} = check_modules(Appls2, Path, ModTestP, Machine),
+ {ok, Warnings2} = check_modules(Appls2, Path, ModTestP),
{ok, Appls} = sort_appls(Appls2),
{ok, Release, Appls, Warnings1 ++ Warnings2}.
@@ -969,13 +961,13 @@ find_pos(N, Name, [_OtherAppl|OrderedAppls]) ->
find_pos(N+1, Name, OrderedAppls).
%%______________________________________________________________________
-%% check_modules(Appls, Path, TestP, Machine) ->
+%% check_modules(Appls, Path, TestP) ->
%% {ok, Warnings} | throw({error, What})
%% where Appls = [{App,Vsn}, #application}]
%% performs logical checking that we can find all the modules
%% etc.
-check_modules(Appls, Path, TestP, Machine) ->
+check_modules(Appls, Path, TestP) ->
%% first check that all the module names are unique
%% Make a list M1 = [{Mod,App,Dir}]
M1 = [{Mod,App,A#application.dir} ||
@@ -983,7 +975,7 @@ check_modules(Appls, Path, TestP, Machine) ->
Mod <- A#application.modules],
case duplicates(M1) of
[] ->
- case check_mods(M1, Appls, Path, TestP, Machine) of
+ case check_mods(M1, Appls, Path, TestP) of
{error, Errors} ->
throw({error, {modules, Errors}});
Return ->
@@ -999,8 +991,8 @@ check_modules(Appls, Path, TestP, Machine) ->
%% Use the module extension of the running machine as extension for
%% the checked modules.
-check_mods(Modules, Appls, Path, {SrcTestP, XrefP}, Machine) ->
- SrcTestRes = check_src(Modules, Appls, Path, SrcTestP, Machine),
+check_mods(Modules, Appls, Path, {SrcTestP, XrefP}) ->
+ SrcTestRes = check_src(Modules, Appls, Path, SrcTestP),
XrefRes = check_xref(Appls, Path, XrefP),
Res = SrcTestRes ++ XrefRes,
case filter(fun({error, _}) -> true;
@@ -1016,8 +1008,8 @@ check_mods(Modules, Appls, Path, {SrcTestP, XrefP}, Machine) ->
{error, Errors}
end.
-check_src(Modules, Appls, Path, true, Machine) ->
- Ext = objfile_extension(Machine),
+check_src(Modules, Appls, Path, true) ->
+ Ext = code:objfile_extension(),
IncPath = create_include_path(Appls, Path),
append(map(fun(ModT) ->
{Mod,App,Dir} = ModT,
@@ -1031,7 +1023,7 @@ check_src(Modules, Appls, Path, true, Machine) ->
end
end,
Modules));
-check_src(_, _, _, _, _) ->
+check_src(_, _, _, _) ->
[].
check_xref(_Appls, _Path, false) ->
@@ -1129,11 +1121,6 @@ exists_xref(Flag) ->
_ -> Flag
end.
-objfile_extension(false) ->
- code:objfile_extension();
-objfile_extension(Machine) ->
- "." ++ atom_to_list(Machine).
-
check_mod(Mod,App,Dir,Ext,IncPath) ->
ObjFile = mod_to_filename(Dir, Mod, Ext),
case file:read_file_info(ObjFile) of
@@ -1571,12 +1558,23 @@ mandatory_modules() ->
%% This is the modules that are preloaded into the Erlang system.
preloaded() ->
- %% Sorted
- [atomics,counters,erl_init,erl_prim_loader,erl_tracer,erlang,
- erts_code_purger,erts_dirty_process_signal_handler,
- erts_internal,erts_literal_area_collector,
- init,persistent_term,prim_buffer,prim_eval,prim_file,
- prim_inet] ++ ?ESOCK_NET_MODS ++ [prim_zip] ++ ?ESOCK_SOCKET_MODS ++ [zlib].
+ lists:sort(
+ ?ESOCK_MODS ++
+ [atomics,counters,erl_init,erl_prim_loader,erl_tracer,erlang,
+ erts_code_purger,erts_dirty_process_signal_handler,
+ erts_internal,erts_literal_area_collector,
+ init,persistent_term,prim_buffer,prim_eval,prim_file,
+ prim_inet,prim_zip,zlib]).
+
+%%______________________________________________________________________
+%% This is the erts binaries that should *not* be part of a systool:make_tar package
+
+erts_binary_filter() ->
+ Cmds = ["typer", "dialyzer", "ct_run", "yielding_c_fun", "erlc"],
+ case os:type() of
+ {unix,_} -> Cmds;
+ {win32,_} -> [ [Cmd, ".exe"] || Cmd <- Cmds]
+ end.
%%______________________________________________________________________
%% Kernel processes; processes that are specially treated by the init
@@ -1669,8 +1667,17 @@ mk_tar(Tar, RelName, Release, Appls, Flags, Path1) ->
add_applications(Appls, Tar, Variables, Flags, false),
add_variable_tars(Variables, Appls, Tar, Flags),
add_system_files(Tar, RelName, Release, Path1),
- add_erts_bin(Tar, Release, Flags).
-
+ add_erts_bin(Tar, Release, Flags),
+ add_additional_files(Tar, Flags).
+
+add_additional_files(Tar, Flags) ->
+ case get_flag(extra_files, Flags) of
+ {extra_files, ToAdd} ->
+ [add_to_tar(Tar, From, To) || {From, To} <- ToAdd];
+ _ ->
+ ok
+ end.
+
add_applications(Appls, Tar, Variables, Flags, Var) ->
Res = foldl(fun({{Name,Vsn},App}, Errs) ->
case catch add_appl(to_list(Name), Vsn, App,
@@ -1767,11 +1774,16 @@ add_system_files(Tar, RelName, Release, Path1) ->
[RelDir, "."|Path1]
end,
- case lookup_file(RelName0 ++ ".boot", Path) of
- false ->
- throw({error, {tar_error,{add, RelName0++".boot",enoent}}});
- Boot ->
- add_to_tar(Tar, Boot, filename:join(RelVsnDir, "start.boot"))
+ case lookup_file("start.boot", Path) of
+ false ->
+ case lookup_file(RelName0 ++ ".boot", Path) of
+ false ->
+ throw({error, {tar_error, {add, boot, RelName, enoent}}});
+ Boot ->
+ add_to_tar(Tar, Boot, filename:join(RelVsnDir, "start.boot"))
+ end;
+ Boot ->
+ add_to_tar(Tar, Boot, filename:join(RelVsnDir, "start.boot"))
end,
case lookup_file("relup", Path) of
@@ -1874,7 +1886,7 @@ add_appl(Name, Vsn, App, Tar, Variables, Flags, Var) ->
Tar,
AppDir,
BinDir,
- objfile_extension(machine(Flags)))
+ code:objfile_extension())
end.
%%______________________________________________________________________
@@ -1953,18 +1965,26 @@ add_priv(ADir, ToDir, Tar) ->
end.
add_erts_bin(Tar, Release, Flags) ->
- case get_flag(erts,Flags) of
- {erts,ErtsDir} ->
- EVsn = Release#release.erts_vsn,
- FromDir = filename:join([to_list(ErtsDir),
- "erts-" ++ EVsn, "bin"]),
- dirp(FromDir),
- ToDir = filename:join("erts-" ++ EVsn, "bin"),
- add_to_tar(Tar, FromDir, ToDir);
+ case {get_flag(erts,Flags),member(erts_all,Flags)} of
+ {{erts,ErtsDir},true} ->
+ add_erts_bin(Tar, Release, ErtsDir, []);
+ {{erts,ErtsDir},false} ->
+ add_erts_bin(Tar, Release, ErtsDir, erts_binary_filter());
_ ->
ok
end.
+add_erts_bin(Tar, Release, ErtsDir, Filters) ->
+ FlattenedFilters = [filename:flatten(Filter) || Filter <- Filters],
+ EVsn = Release#release.erts_vsn,
+ FromDir = filename:join([to_list(ErtsDir),
+ "erts-" ++ EVsn, "bin"]),
+ ToDir = filename:join("erts-" ++ EVsn, "bin"),
+ {ok, Bins} = file:list_dir(FromDir),
+ [add_to_tar(Tar, filename:join(FromDir,Bin), filename:join(ToDir,Bin))
+ || Bin <- Bins, not lists:member(Bin, FlattenedFilters)],
+ ok.
+
%%______________________________________________________________________
%% Tar functions.
@@ -2158,9 +2178,6 @@ cas([{variables, V} | Args], X) when is_list(V) ->
error ->
cas(Args, X++[{variables, V}])
end;
-%%% machine ------------------------------------------------------------
-cas([{machine, M} | Args], X) when is_atom(M) ->
- cas(Args, X);
%%% exref --------------------------------------------------------------
cas([exref | Args], X) ->
cas(Args, X);
@@ -2189,6 +2206,9 @@ cas([no_module_tests | Args], X) ->
cas(Args, X);
cas([no_dot_erlang | Args], X) ->
cas(Args, X);
+%% set the name of the script and boot file to create
+cas([{script_name, Name} | Args], X) when is_list(Name) ->
+ cas(Args, X);
%%% ERROR --------------------------------------------------------------
cas([Y | Args], X) ->
@@ -2224,6 +2244,8 @@ cat([{dirs, D} | Args], X) ->
%%% erts ---------------------------------------------------------------
cat([{erts, E} | Args], X) when is_list(E)->
cat(Args, X);
+cat([erts_all | Args], X) ->
+ cat(Args, X);
%%% src_tests ----------------------------------------------------
cat([src_tests | Args], X) ->
cat(Args, X);
@@ -2240,9 +2262,6 @@ cat([{var_tar, VT} | Args], X) when VT == include;
VT == ownfile;
VT == omit ->
cat(Args, X);
-%%% machine ------------------------------------------------------------
-cat([{machine, M} | Args], X) when is_atom(M) ->
- cat(Args, X);
%%% exref --------------------------------------------------------------
cat([exref | Args], X) ->
cat(Args, X);
@@ -2269,6 +2288,9 @@ cat([no_warn_sasl | Args], X) ->
%%% no_module_tests (kept for backwards compatibility, but ignored) ----
cat([no_module_tests | Args], X) ->
cat(Args, X);
+cat([{extra_files, ExtraFiles} | Args], X) when is_list(ExtraFiles) ->
+ cat(Args, X);
+
%%% ERROR --------------------------------------------------------------
cat([Y | Args], X) ->
cat(Args, X++[Y]).
@@ -2423,6 +2445,9 @@ form_reading(W) ->
form_tar_err({open, File, Error}) ->
io_lib:format("Cannot open tar file ~ts - ~ts~n",
[File, erl_tar:format_error(Error)]);
+form_tar_err({add, boot, RelName, enoent}) ->
+ io_lib:format("Cannot find file start.boot or ~ts to add to tar file - ~ts~n",
+ [RelName, erl_tar:format_error(enoent)]);
form_tar_err({add, File, Error}) ->
io_lib:format("Cannot add file ~ts to tar file - ~ts~n",
[File, erl_tar:format_error(Error)]).
diff --git a/lib/sasl/src/systools_relup.erl b/lib/sasl/src/systools_relup.erl
index 63e18d394b..19d25fef6e 100644
--- a/lib/sasl/src/systools_relup.erl
+++ b/lib/sasl/src/systools_relup.erl
@@ -290,9 +290,9 @@ foreach_baserel_up(TopRel, TopApps, [BaseRelDc|BaseRelDcs], Path, Opts,
%%
{RUs1, Ws1} = collect_appup_scripts(up, TopApps, BaseRel, Ws0++Ws, []),
- {RUs2, Ws2} = create_add_app_scripts(BaseRel, TopRel, RUs1, Ws1),
+ {RUs2, Ws2} = prepend_add_app_scripts(BaseRel, TopRel, RUs1, Ws1),
- {RUs3, Ws3} = create_remove_app_scripts(BaseRel, TopRel, RUs2, Ws2),
+ {RUs3, Ws3} = append_remove_app_scripts(BaseRel, TopRel, RUs2, Ws2),
{RUs4, Ws4} = check_for_emulator_restart(TopRel, BaseRel, RUs3, Ws3, Opts),
@@ -342,9 +342,9 @@ foreach_baserel_dn(TopRel, TopApps, [BaseRelDc|BaseRelDcs], Path, Opts,
%%
{RUs1, Ws1} = collect_appup_scripts(dn, TopApps, BaseRel, Ws0++Ws, []),
- {RUs2, Ws2} = create_add_app_scripts(TopRel, BaseRel, RUs1, Ws1),
+ {RUs2, Ws2} = prepend_add_app_scripts(TopRel, BaseRel, RUs1, Ws1),
- {RUs3, Ws3} = create_remove_app_scripts(TopRel, BaseRel, RUs2, Ws2),
+ {RUs3, Ws3} = append_remove_app_scripts(TopRel, BaseRel, RUs2, Ws2),
{RUs4, Ws4} = check_for_emulator_restart(TopRel, BaseRel, RUs3, Ws3, Opts),
@@ -439,7 +439,7 @@ collect_appup_scripts(_, [], _, Ws, RUs) -> {RUs, Ws}.
%% FromRel = ToRel = #release
%% ToApps = [#application]
%%
-create_add_app_scripts(FromRel, ToRel, RU0s, W0s) ->
+prepend_add_app_scripts(FromRel, ToRel, RU0s, W0s) ->
AddedNs = [{N, T} || {N, _V, T} <- ToRel#release.applications,
not lists:keymember(N, 1, FromRel#release.applications)],
%% io:format("Added apps: ~p~n", [AddedNs]),
@@ -454,12 +454,12 @@ create_add_app_scripts(FromRel, ToRel, RU0s, W0s) ->
%%
%% XXX ToApps not used.
%%
-create_remove_app_scripts(FromRel, ToRel, RU0s, W0s) ->
+append_remove_app_scripts(FromRel, ToRel, RU0s, W0s) ->
RemovedNs = [N || {N, _V, _T} <- FromRel#release.applications,
not lists:keymember(N, 1, ToRel#release.applications)],
%% io:format("Removed apps: ~p~n", [RemovedNs]),
RUs = [[{remove_application, N}] || N <- RemovedNs],
- {RUs ++ RU0s, W0s}.
+ { RU0s ++ RUs, W0s}.
%% get_script_from_appup(Mode, TopApp, BaseVsn, Ws, RUs) -> {NRUs, NWs}
%% Mode = up | dn
diff --git a/lib/sasl/test/installer.erl b/lib/sasl/test/installer.erl
index 61a932809c..2c5c9d725a 100644
--- a/lib/sasl/test/installer.erl
+++ b/lib/sasl/test/installer.erl
@@ -1063,7 +1063,7 @@ permanent_p1h(TestNode) ->
reg_proc(Name) ->
catch unregister(Name),
- Pid = spawn_link(?MODULE, registered_loop, [Name]),
+ Pid = spawn(?MODULE, registered_loop, [Name]),
global:register_name(Name, Pid),
ok.
diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl
index d1bcc40049..5ce4050d30 100644
--- a/lib/sasl/test/release_handler_SUITE.erl
+++ b/lib/sasl/test/release_handler_SUITE.erl
@@ -1847,14 +1847,19 @@ otp_10463_upgrade_script_regexp(cleanup,Config) ->
code:del_path(filename:join([DataDir,regexp_appup,app1,ebin])),
ok.
-no_dot_erlang(_Conf) ->
- case init:get_argument(home) of
- {ok,[[Home]]} when is_list(Home) ->
- no_dot_erlang_1(Home);
- _ -> ok
+no_dot_erlang(Conf) ->
+ case {os:type(),init:get_argument(home)} of
+ {{unix,_},_} ->
+ %% On unix we set HOME to priv_dir so that we
+ %% do not have to change the users ~/.erlang
+ Home = ?config(priv_dir, Conf),
+ no_dot_erlang_1("HOME=\""++ Home ++"\" ",Home);
+ {{win32,_},{ok,[[Home]]}} when is_list(Home) ->
+ no_dot_erlang_1("",Home);
+ _ -> {skip,"Could not find home directory"}
end.
-no_dot_erlang_1(Home) ->
+no_dot_erlang_1(Prefix, Home) ->
DotErlang = filename:join(Home, ".erlang"),
BupErlang = filename:join(Home, ".erlang_testbup"),
try
@@ -1869,7 +1874,7 @@ no_dot_erlang_1(Home) ->
Args = " -noinput -run c pwd -run erlang halt",
ok = file:write_file(DotErlang, <<"io:put_chars(\"DOT_ERLANG_READ\\n\").\n">>),
- CMD1 = Quote ++ Erl ++ Quote ++ Args ,
+ CMD1 = Prefix ++ Quote ++ Erl ++ Quote ++ Args ,
case os:cmd(CMD1) of
"DOT_ERLANG_READ" ++ _ ->
io:format("~p: Success~n", [?LINE]);
@@ -1880,7 +1885,7 @@ no_dot_erlang_1(Home) ->
exit({failed_to_start, test_error})
end,
NO_DOT_ERL = " -boot no_dot_erlang",
- CMD2 = Quote ++ Erl ++ Quote ++ NO_DOT_ERL ++ Args,
+ CMD2 = Prefix ++ Quote ++ Erl ++ Quote ++ NO_DOT_ERL ++ Args,
case lists:prefix(Wd, Other2 = os:cmd(CMD2)) of
true -> io:format("~p: Success~n", [?LINE]);
false ->
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.0/src/m.erl b/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.0/src/m.erl
index 418102bebb..e0ff480703 100644
--- a/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.0/src/m.erl
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.0/src/m.erl
@@ -8,4 +8,5 @@ start(NProcs) ->
receive stop -> Cs end
end) ||
_ <- lists:seq(1,NProcs)],
+ [unlink(Pid) || Pid <- Pids],
{Modules,Pids}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.1/src/m.erl b/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.1/src/m.erl
index 418102bebb..e0ff480703 100644
--- a/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.1/src/m.erl
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-1.1/src/m.erl
@@ -8,4 +8,5 @@ start(NProcs) ->
receive stop -> Cs end
end) ||
_ <- lists:seq(1,NProcs)],
+ [unlink(Pid) || Pid <- Pids],
{Modules,Pids}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-2.0/src/m.erl b/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-2.0/src/m.erl
index 2edc1e6be4..8ec3629e78 100644
--- a/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-2.0/src/m.erl
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/many_mods-2.0/src/m.erl
@@ -8,4 +8,5 @@ start(NProcs) ->
receive stop -> Cs end
end) ||
_ <- lists:seq(1,NProcs)],
+ [unlink(Pid) || Pid <- Pids],
{Modules,Pids}.
diff --git a/lib/sasl/test/sasl_SUITE.erl b/lib/sasl/test/sasl_SUITE.erl
index fc80e37210..beb129c1a6 100644
--- a/lib/sasl/test/sasl_SUITE.erl
+++ b/lib/sasl/test/sasl_SUITE.erl
@@ -32,6 +32,8 @@
log_file/1,
utc_log/1]).
+-compile(r21).
+
all() ->
[log_mf_h_env, log_file, app_test, appup_test, utc_log].
@@ -104,7 +106,7 @@ appup_tests(App,{OkVsns0,NokVsns}) ->
create_test_vsns(App) ->
ThisMajor = erlang:system_info(otp_release),
FirstMajor = previous_major(ThisMajor),
- SecondMajor = previous_major(FirstMajor),
+ SecondMajor = previous_major(previous_major(FirstMajor)),
Ok = app_vsn(App,[ThisMajor,FirstMajor]),
Nok0 = app_vsn(App,[SecondMajor]),
Nok = case Ok of
diff --git a/lib/sasl/test/sasl_report_SUITE.erl b/lib/sasl/test/sasl_report_SUITE.erl
index e639b55cee..bc984754cc 100644
--- a/lib/sasl/test/sasl_report_SUITE.erl
+++ b/lib/sasl/test/sasl_report_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
-export([gen_server_crash/1, gen_server_crash_unicode/1]).
+-export([gen_server_crash_chars_limit/1,
+ gen_server_crash_chars_limit_unicode/1]).
-export([legacy_gen_server_crash/1, legacy_gen_server_crash_unicode/1]).
-export([crash_me/0,start_link/0,init/1,handle_cast/2,terminate/2]).
@@ -32,6 +34,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[gen_server_crash,
gen_server_crash_unicode,
+ gen_server_crash_chars_limit,
+ gen_server_crash_chars_limit_unicode,
legacy_gen_server_crash,
legacy_gen_server_crash_unicode].
@@ -51,18 +55,31 @@ end_per_group(_GroupName, Config) ->
Config.
gen_server_crash(Config) ->
- gen_server_crash(Config, latin1).
+ gen_server_crash(Config, latin1, depth).
gen_server_crash_unicode(Config) ->
- gen_server_crash(Config, unicode).
+ gen_server_crash(Config, unicode, depth).
+
+gen_server_crash_chars_limit(Config) ->
+ gen_server_crash(Config, latin1, chars_limit).
+
+gen_server_crash_chars_limit_unicode(Config) ->
+ gen_server_crash(Config, unicode, chars_limit).
legacy_gen_server_crash(Config) ->
- legacy_gen_server_crash(Config,latin1).
+ legacy_gen_server_crash(Config, latin1).
legacy_gen_server_crash_unicode(Config) ->
- legacy_gen_server_crash(Config,unicode).
+ legacy_gen_server_crash(Config, unicode).
+
+gen_server_crash(Config, Encoding, depth) ->
+ FormatterOpts = #{depth=>30},
+ gen_server_crash(Config, Encoding, FormatterOpts, 70000, 150000);
+gen_server_crash(Config, Encoding, chars_limit) ->
+ FormatterOpts = #{chars_limit=>50000, single_line=>true},
+ gen_server_crash(Config, Encoding, FormatterOpts, 50000, 100000).
-gen_server_crash(Config, Encoding) ->
+gen_server_crash(Config, Encoding, FormatterOpts, Min, Max) ->
TC = list_to_atom(lists:concat([?FUNCTION_NAME,"_",Encoding])),
PrivDir = filename:join(?config(priv_dir,Config),?MODULE),
ConfigFileName = filename:join(PrivDir,TC),
@@ -85,9 +102,9 @@ gen_server_crash(Config, Encoding) ->
" -boot start_sasl -kernel start_timer true "
"-config ",ConfigFileName]}]),
- %% Set depth
- ok = rpc:call(Node,logger,update_formatter_config,[default,depth,30]),
- ok = rpc:call(Node,logger,update_formatter_config,[sasl,depth,30]),
+ %% Set depth or chars_limit.
+ ok = rpc:call(Node,logger,update_formatter_config,[default,FormatterOpts]),
+ ok = rpc:call(Node,logger,update_formatter_config,[sasl,FormatterOpts]),
%% Make sure remote node logs it's own logs, and current node does
%% not log them.
@@ -98,7 +115,7 @@ gen_server_crash(Config, Encoding) ->
(_,_) -> ignore
end,[]}),
ct:log("Local node Logger config:~n~p",
- [rpc:call(Node,logger,get_config,[])]),
+ [logger:get_config()]),
ct:log("Remote node Logger config:~n~p",
[rpc:call(Node,logger,get_config,[])]),
ct:log("Remote node error_logger handlers: ~p",
@@ -112,8 +129,8 @@ gen_server_crash(Config, Encoding) ->
test_server:stop_node(Node),
ok = logger:remove_primary_filter(no_remote),
- check_file(KernelLog, utf8, 70000, 150000),
- check_file(SaslLog, Encoding, 70000, 150000),
+ check_file(KernelLog, utf8, Min, Max),
+ check_file(SaslLog, Encoding, Min, Max),
ok = file:delete(KernelLog),
ok = file:delete(SaslLog),
@@ -169,6 +186,7 @@ check_file(File, Encoding, Min, Max) ->
_ -> io:format("~ts\n", [Bin])
end,
Sz = byte_size(Bin),
+ %% Sz = string:length(string:replace(Bin, " ", "", all)),
io:format("Size: ~p (allowed range is ~p..~p)\n",
[Sz,Min,Max]),
if
diff --git a/lib/sasl/test/systools_SUITE.erl b/lib/sasl/test/systools_SUITE.erl
index 6c913850b9..4b67d406a6 100644
--- a/lib/sasl/test/systools_SUITE.erl
+++ b/lib/sasl/test/systools_SUITE.erl
@@ -57,7 +57,7 @@ all() ->
groups() ->
[{script, [],
- [script_options, normal_script, unicode_script, no_mod_vsn_script,
+ [script_options, normal_script, start_script, unicode_script, no_mod_vsn_script,
wildcard_script, variable_script, abnormal_script,
no_sasl_script, no_dot_erlang_script,
src_tests_script, crazy_script,
@@ -66,13 +66,14 @@ groups() ->
duplicate_modules_script,
otp_3065_circular_dependenies, included_and_used_sort_script]},
{tar, [],
- [tar_options, normal_tar, no_mod_vsn_tar, system_files_tar,
+ [tar_options, relname_tar, normal_tar, no_mod_vsn_tar, system_files_tar,
system_src_file_tar, invalid_system_files_tar, variable_tar,
src_tests_tar, var_tar, exref_tar, link_tar, no_sasl_tar,
- otp_9507_path_ebin]},
+ otp_9507_path_ebin, additional_files_tar, erts_tar]},
{relup, [],
[normal_relup, restart_relup, abnormal_relup, no_sasl_relup,
- no_appup_relup, bad_appup_relup, app_start_type_relup, regexp_relup
+ no_appup_relup, bad_appup_relup, app_start_type_relup, regexp_relup,
+ replace_app_relup
]},
{hybrid, [], [normal_hybrid,hybrid_no_old_sasl,hybrid_no_new_sasl]},
{options, [], [otp_6226_outdir,app_file_defaults]}].
@@ -235,6 +236,29 @@ normal_script(Config) when is_list(Config) ->
code:set_path(PSAVE), % Restore path
ok.
+%% make_script: Check that script can be named start.script
+start_script(Config) when is_list(Config) ->
+ {ok, OldDir} = file:get_cwd(),
+ PSAVE = code:get_path(), % Save path
+
+ {LatestDir, LatestName} = create_script(latest,Config),
+
+ DataDir = filename:absname(?copydir),
+ LibDir = fname([DataDir, d_normal, lib]),
+ P1 = fname([LibDir, 'db-2.1', ebin]),
+ P2 = fname([LibDir, 'fe-3.1', ebin]),
+
+ true = code:add_patha(P1),
+ true = code:add_patha(P2),
+
+ ok = file:set_cwd(LatestDir),
+
+ ok = systools:make_script(filename:basename(LatestName), [{script_name, "start"}]),
+ {ok, _} = read_script_file("start"), % Check readabillity
+
+ ok = file:set_cwd(OldDir),
+ code:set_path(PSAVE), % Restore path
+ ok.
%% make_script: Test make_script with unicode .app file
unicode_script(Config) when is_list(Config) ->
@@ -869,7 +893,7 @@ tar_options(Config) when is_list(Config) ->
ok.
-%% make_tar: Check normal case
+%% make_tar: Check case of start.boot
normal_tar(Config) when is_list(Config) ->
{ok, OldDir} = file:get_cwd(),
@@ -882,7 +906,29 @@ normal_tar(Config) when is_list(Config) ->
ok = file:set_cwd(LatestDir),
- {ok, _, []} = systools:make_script(LatestName, [silent, {path, P}]),
+ {ok, _, []} = systools:make_script(LatestName, [silent, {path, P}, {script_name, "start"}]),
+ ok = systools:make_tar(LatestName, [{path, P}]),
+ ok = check_tar(fname([lib,'db-2.1',ebin,'db.app']), LatestName),
+ {ok, _, []} = systools:make_tar(LatestName, [{path, P}, silent]),
+ ok = check_tar(fname([lib,'fe-3.1',ebin,'fe.app']), LatestName),
+
+ ok = file:set_cwd(OldDir),
+ ok.
+
+%% make_tar: Check case of relname.boot
+relname_tar(Config) when is_list(Config) ->
+ {ok, OldDir} = file:get_cwd(),
+
+ {LatestDir, LatestName} = create_script(latest,Config),
+
+ DataDir = filename:absname(?copydir),
+ LibDir = fname([DataDir, d_normal, lib]),
+ P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ok = file:set_cwd(LatestDir),
+
+ {ok, _, []} = systools:make_script(LatestName, [silent, {path, P}, {script_name, LatestName}]),
ok = systools:make_tar(LatestName, [{path, P}]),
ok = check_tar(fname([lib,'db-2.1',ebin,'db.app']), LatestName),
{ok, _, []} = systools:make_tar(LatestName, [{path, P}, silent]),
@@ -945,6 +991,56 @@ system_files_tar(Config) ->
ok.
+%% make_tar: Check that extra_files are included in the tarball
+additional_files_tar(Config) ->
+ {ok, OldDir} = file:get_cwd(),
+
+ {LatestDir, LatestName} = create_script(latest,Config),
+
+ DataDir = filename:absname(?copydir),
+ LibDir = fname([DataDir, d_normal, lib]),
+ P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ok = file:set_cwd(LatestDir),
+
+ %% Add dummy relup and sys.config
+ ok = file:write_file("sys.config","[].\n"),
+ ok = file:write_file("relup","{\"LATEST\",[],[]}.\n"),
+
+ %% unrelated files that must be included explicitly
+ RandomFile = "somefile",
+ ok = file:write_file(RandomFile,"hello\n"),
+
+ TopLevelDir = "some_dir",
+ TopLevelFile = filename:join(TopLevelDir, "top_level_file"),
+
+ filelib:ensure_dir(TopLevelFile),
+ ok = file:write_file(TopLevelFile, "hello there\n"),
+
+ {ok, _, []} = systools:make_script(LatestName, [silent, {path, P}]),
+ ok = systools:make_tar(LatestName, [{path, P}]),
+ ok = check_tar(fname(["releases","LATEST","sys.config"]), LatestName),
+ ok = check_tar(fname(["releases","LATEST","relup"]), LatestName),
+ %% random file should not be in this tarball
+ {error, _} = check_tar(fname(["releases","LATEST",RandomFile]), LatestName),
+
+ RandomFilePathInTar = filename:join(["releases", "LATEST", RandomFile]),
+
+ {ok, _, []} = systools:make_tar(LatestName,
+ [{path, P}, silent,
+ {extra_files, [{RandomFile, RandomFilePathInTar},
+ {TopLevelDir, TopLevelDir}]}]),
+ ok = check_tar(fname(["releases","LATEST","sys.config"]), LatestName),
+ ok = check_tar(fname(["releases","LATEST","relup"]), LatestName),
+
+ %% random file and dir should be in this tarball
+ ok = check_tar(fname(["releases","LATEST",RandomFile]), LatestName),
+ ok = check_tar(fname([TopLevelFile]), LatestName),
+
+ ok = file:set_cwd(OldDir),
+
+ ok.
system_files_tar(cleanup,Config) ->
Dir = ?privdir,
@@ -952,6 +1048,84 @@ system_files_tar(cleanup,Config) ->
file:delete(filename:join(Dir,"relup")),
ok.
+erts_tar(Config) ->
+
+ {ok, OldDir} = file:get_cwd(),
+
+ {LatestDir, LatestName} = create_script(current_all,Config),
+
+ ERTS_VSN = erlang:system_info(version),
+ ERTS_DIR = fname(["erts-" ++ ERTS_VSN,bin]),
+
+ %% List of all expected executable files in erts/bin
+ %% This list needs to be kept up to date whenever a file is
+ %% added or removed.
+ {Default, Ignored} =
+ case os:type() of
+ {unix,_} ->
+ {["beam.smp","dyn_erl","epmd","erl","erl_call","erl_child_setup",
+ "erlexec","erl.src","escript","heart","inet_gethost","run_erl",
+ "start","start_erl.src","start.src","to_erl"],
+ ["ct_run","dialyzer","erlc","typer","yielding_c_fun"]};
+ {win32, _} ->
+ {["beam.smp.pdb","erl.exe",
+ "erl.pdb","erl_log.exe","erlexec.dll","erlsrv.exe","heart.exe",
+ "start_erl.exe","werl.exe","beam.smp.dll",
+ "epmd.exe","erl.ini","erl_call.exe",
+ "erlexec.pdb","escript.exe","inet_gethost.exe","werl.pdb"],
+ ["dialyzer.exe","erlc.exe","yielding_c_fun.exe","ct_run.exe","typer.exe"]}
+ end,
+
+ ErtsTarContent =
+ fun(TarName) ->
+ lists:sort(
+ [filename:basename(File)
+ || File <- tar_contents(TarName),
+ string:equal(filename:dirname(File),ERTS_DIR),
+ %% Filter out beam.*.smp.*
+ re:run(filename:basename(File), "beam\\.[^\\.]+\\.smp(\\.dll)?") == nomatch,
+ %% Filter out any erl_child_setup.*
+ re:run(filename:basename(File), "erl_child_setup\\..*") == nomatch
+ ])
+ end,
+
+ DataDir = filename:absname(?copydir),
+ LibDir = fname([DataDir, d_normal, lib]),
+ P = [fname([LibDir, 'db-2.1', ebin])],
+
+ ok = file:set_cwd(LatestDir),
+
+ {ok, _, []} = systools:make_script(LatestName, [silent, {path, P}, {script_name, "start"}]),
+ ok = systools:make_tar(LatestName, [{path, P}, {erts, code:root_dir()}]),
+ ErtsContent = ErtsTarContent(LatestName),
+
+ case lists:sort(Default) of
+ ErtsContent ->
+ ok;
+ Expected ->
+ ct:pal("Content: ~p",[ErtsContent]),
+ ct:pal("Expected: ~p",[Expected]),
+ ct:fail("Incorrect erts bin content")
+ end,
+
+ ok = systools:make_tar(LatestName, [{path, P},
+ {erts, code:root_dir()},
+ erts_all]),
+ ErtsAllContent = ErtsTarContent(LatestName),
+
+ case lists:sort(Default ++ Ignored) of
+ ErtsAllContent ->
+ ok;
+ ExpectedIgn ->
+ ct:pal("Content: ~p",[ErtsAllContent]),
+ ct:pal("Expected: ~p",[ExpectedIgn]),
+ ct:fail("Incorrect erts bin content")
+ end,
+
+ ok = file:set_cwd(OldDir),
+ ok.
+
+
%% make_tar: Check that sys.config.src and not sys.config is included
system_src_file_tar(Config) ->
{ok, OldDir} = file:get_cwd(),
@@ -1769,6 +1943,59 @@ regexp_relup(Config) ->
ok.
+%% make_relup: Replace an application dependency with another
+%% The key part here is that the new application should be
+%% started before the old one is stopped.
+replace_app_relup(Config) when is_list(Config) ->
+ {ok, OldDir} = file:get_cwd(),
+
+ {LatestDir,LatestName} = create_script(replace_app0,Config),
+ {_LatestDir1,LatestName1} = create_script(replace_app1,Config),
+
+ DataDir = filename:absname(?copydir),
+ LibDir = [fname([DataDir, d_replace_app, lib])],
+ P = [fname([LibDir, '*', ebin]),
+ fname([DataDir, lib, kernel, ebin]),
+ fname([DataDir, lib, stdlib, ebin]),
+ fname([DataDir, lib, sasl, ebin])],
+
+ ok = file:set_cwd(LatestDir),
+
+ ok = systools:make_relup(LatestName, [LatestName1], [LatestName1],
+ [{path, P}]),
+
+ check_start_stop_order([{start,gh},{stop,fe}], [{start,fe},{stop,gh}]),
+
+ ok = file:set_cwd(OldDir),
+ ok.
+
+
+check_start_stop_order(UpOrder, DownOrder) ->
+
+ {ok, [{_V0, [{_V1, [], Up}],
+ [{_V1, [], Down}]
+ }]} = file:consult(relup),
+
+ GetAppStartStop = fun(Instr) ->
+ [{Action,App} || {apply,{application,Action,[App|_]}} <- Instr,
+ lists:member(Action,[start,stop])]
+ end,
+
+ case GetAppStartStop(Up) of
+ UpOrder -> ok;
+ ActualUpOrder ->
+ ct:fail("Incorrect upgrade order.~nExpected: ~p~nGot:~p",
+ [UpOrder,ActualUpOrder])
+ end,
+
+ case GetAppStartStop(Down) of
+ DownOrder -> ok;
+ ActualDownOrder ->
+ ct:fail("Incorrect down order.~nExpected: ~p~nGot:~p",
+ [DownOrder,ActualDownOrder])
+ end,
+
+ ok.
%% make_hybrid_boot: Normal case.
%% For upgrade of erts - create a boot file which is a hybrid between
@@ -2246,7 +2473,7 @@ delete_tree(Dir) ->
end.
tar_contents(Name) ->
- {ok, Cont} = erl_tar:table(Name ++ ".tar.gz", [compressed]),
+ {ok, Cont} = erl_tar:table(tar_name(Name), [compressed]),
Cont.
tar_name(Name) ->
@@ -2324,7 +2551,13 @@ create_script({unicode,RelVsn},Config) ->
do_create_script(unicode,RelVsn,Config,current,Apps);
create_script(duplicate_modules,Config) ->
Apps = core_apps(current) ++ [{app1,"1.0"},{app2,"1.0"}],
- do_create_script(duplicate_modules,Config,current,Apps).
+ do_create_script(duplicate_modules,Config,current,Apps);
+create_script(replace_app0,Config) ->
+ Apps = core_apps(current) ++ [{db,"1.1"},{gh,"1.0"}],
+ do_create_script(repace_app0,Config,current,Apps);
+create_script(replace_app1,Config) ->
+ Apps = core_apps(current) ++ [{db,"1.0"},{fe,"2.1"}],
+ do_create_script(repace_app1,Config,current,Apps).
do_create_script(Id,Config,ErtsVsn,AppVsns) ->
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/ebin/db.app
new file mode 100644
index 0000000000..d12fcfaf7d
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/ebin/db.app
@@ -0,0 +1,8 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "1.0"},
+ {modules, [db1, db2]},
+ {registered, []},
+ {applications, [fe]},
+ {env, []},
+ {start, {db1, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/src/db1.erl b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/src/db1.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/src/db1.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/src/db2.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.0/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/ebin/db.app
new file mode 100644
index 0000000000..517a0810f9
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/ebin/db.app
@@ -0,0 +1,8 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "1.1"},
+ {modules, [db1, db2]},
+ {registered, []},
+ {applications, [gh]},
+ {env, []},
+ {start, {db1, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/ebin/db.appup b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/ebin/db.appup
new file mode 100644
index 0000000000..12d7ad4c9c
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/ebin/db.appup
@@ -0,0 +1,14 @@
+{
+ "1.1",
+%%% Upgrade from:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/src/db1.erl b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/src/db1.erl
new file mode 100644
index 0000000000..ee7497f5c1
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/src/db1.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.1").
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/src/db2.erl
new file mode 100644
index 0000000000..ee7497f5c1
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/db-1.1/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.1").
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/ebin/fe.app
new file mode 100644
index 0000000000..717d30cf45
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/ebin/fe.app
@@ -0,0 +1,8 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "2.1"},
+ {modules, [fe1, fe2, fe3]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {fe2, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe1.erl b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe1.erl
new file mode 100644
index 0000000000..aa5bfa8098
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe1.erl
@@ -0,0 +1,2 @@
+-module(fe1).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe2.erl b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe2.erl
new file mode 100644
index 0000000000..869f3b93c8
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe2.erl
@@ -0,0 +1,2 @@
+-module(fe2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe3.erl b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe3.erl
new file mode 100644
index 0000000000..6473342f52
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/fe-2.1/src/fe3.erl
@@ -0,0 +1,2 @@
+-module(fe3).
+-vsn("2.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/gh-1.0/ebin/gh.app b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/gh-1.0/ebin/gh.app
new file mode 100644
index 0000000000..2823a7e592
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/gh-1.0/ebin/gh.app
@@ -0,0 +1,8 @@
+{application, gh,
+ [{description, "ERICSSON NR FOR GH"},
+ {vsn, "1.0"},
+ {modules, [gh1]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {gh1, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/gh-1.0/src/gh1.erl b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/gh-1.0/src/gh1.erl
new file mode 100644
index 0000000000..acd0f43d6a
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_replace_app/lib/gh-1.0/src/gh1.erl
@@ -0,0 +1,2 @@
+-module(gh1).
+-vsn("1.0").
diff --git a/lib/sasl/vsn.mk b/lib/sasl/vsn.mk
index fd045e49d5..a61b72d6f6 100644
--- a/lib/sasl/vsn.mk
+++ b/lib/sasl/vsn.mk
@@ -1 +1 @@
-SASL_VSN = 3.4.2
+SASL_VSN = 4.0.2
diff --git a/lib/snmp/Makefile b/lib/snmp/Makefile
index 1a5bed50a5..52debf1670 100644
--- a/lib/snmp/Makefile
+++ b/lib/snmp/Makefile
@@ -2,7 +2,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2019. All Rights Reserved.
+# Copyright Ericsson AB 1996-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -71,24 +71,6 @@ do_configure: configure
configure: configure.in
autoconf
-.PHONY: info gclean
-
-info:
- @echo "OS: $(OS)"
- @echo "DOCB: $(DOCB)"
- @echo ""
- @echo "SNMP_VSN: $(SNMP_VSN)"
- @echo "APP_VSN: $(APP_VSN)"
- @echo ""
- @echo "DIA_PLT: $(DIA_PLT)"
- @echo "DIA_ANALYSIS: $(DIA_ANALYSIS)"
- @echo ""
-
-
-gclean:
- git clean -fXd
-
-
# ----------------------------------------------------
# Application (source) release targets
# ----------------------------------------------------
@@ -130,30 +112,6 @@ tar: $(APP_TAR_FILE)
$(APP_TAR_FILE): $(APP_DIR)
(cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME))
-dclean:
- rm -f $(DIA_PLT)
- rm -f $(DIA_ANALYSIS)
-
-dialyzer_plt: $(DIA_PLT)
-
-$(DIA_PLT): Makefile
- @echo "Building $(APPLICATION) plt file"
- @dialyzer --build_plt \
- --output_plt $@ \
- -r ../$(APPLICATION)/ebin \
- ../../lib/kernel/ebin \
- ../../lib/stdlib/ebin \
- ../../lib/compiler/ebin \
- ../../lib/hipe/ebin \
- ../../lib/runtime_tools/ebin \
- ../../lib/crypto/ebin \
- ../../lib/mnesia/ebin \
- ../../erts/preloaded/ebin \
- --output $(DIA_ANALYSIS) \
- --verbose
-
-dialyzer: $(DIA_PLT)
- @echo "Running dialyzer on $(APPLICATION)"
- @dialyzer --plt $< \
- ../$(APPLICATION)/ebin \
- --verbose
+DIA_PLT_APPS=runtime_tools crypto mnesia
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/snmp/configure.in b/lib/snmp/configure.in
index f9ca8f9ac4..bac042ccca 100644
--- a/lib/snmp/configure.in
+++ b/lib/snmp/configure.in
@@ -4,12 +4,7 @@ define([AC_CACHE_SAVE], )dnl
AC_INIT(vsn.mk)
-if test -z "$ERL_TOP" || test ! -d $ERL_TOP ; then
- AC_CONFIG_AUX_DIRS(autoconf)
-else
- erl_top=${ERL_TOP}
- AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
-fi
+AC_CONFIG_AUX_DIRS(${ERL_TOP}/erts/autoconf)
if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
AC_CANONICAL_HOST
diff --git a/lib/snmp/doc/src/Makefile b/lib/snmp/doc/src/Makefile
index e7ab491eef..cfc41156cf 100644
--- a/lib/snmp/doc/src/Makefile
+++ b/lib/snmp/doc/src/Makefile
@@ -29,11 +29,6 @@ VSN = $(SNMP_VSN)
APPLICATION=snmp
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
@@ -58,89 +53,21 @@ XML_ERRS = $(XML_FILES:%.xml=%.latex.xmls_errs) \
XML_OUTPUT = $(XML_FILES:%.xml=%.latex.xmls_output) \
$(XML_FILES:%.xml=%.html.xmls_output)
-INFO_FILE = ../../info
-
-#HTML_REF1_FILES = $(XML_REF1_FILES:%.xml=$(HTMLDIR)/%.html)
-HTML_REF3_FILES = $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html)
-HTML_REF6_FILES = $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html)
-HTML_CHAP_FILES = $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-EXTRA_FILES = \
- $(DEFAULT_HTML_FILES) \
- $(HTML_REF1_FILES) \
- $(HTML_REF3_FILES) \
- $(HTML_REF6_FILES) \
- $(HTML_CHAP_FILES)
-
-
-MAN7DIR = $(DOCDIR)/man7
-
-MAN1_FILES = $(MAN1DIR)/snmpc.1
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-MAN7_FILES = $(MIB_FILES:$(MIBSDIR)/%.mib=$(MAN7DIR)/%.7)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-GIF_TARGETS = $(GIF_FILES:%=$(HTMLDIR)/%)
-
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
+NO_CHUNKS = snmpa_discovery_handler.xml \
+ snmpa_error_report.xml \
+ snmpa_mib_data.xml \
+ snmpa_mib_storage.xml \
+ snmpa_network_interface.xml \
+ snmpa_network_interface_filter.xml \
+ snmpa_notification_delivery_info_receiver.xml \
+ snmpa_notification_filter.xml \
+ snmpm_network_interface.xml \
+ snmpm_network_interface_filter.xml \
+ snmpm_user.xml
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif # Copy them to ../html
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-ldocs: local_docs
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs: clean_html clean_man clean_pdf
- rm -f errs core *~
-
-man: man1 man3 man6 man7
-
-man1: $(MAN1_FILES)
-
-man3: $(MAN3_FILES)
-
-man6: $(MAN6_FILES)
-
-man7: $(MAN7_FILES)
-
-gifs: $(GIF_TARGETS)
-
-debug opt:
-
-clean_pdf:
- @echo "cleaning pdf:"
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
-
-clean_man:
- @echo "cleaning man:"
- rm -f $(MAN1DIR)/*
- rm -f $(MAN3DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(MAN7DIR)/*
-
-clean_html:
- @echo "cleaning html:"
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
$(MAN7DIR)/%.7: $(MIBSDIR)/%.mib
@echo "processing $*"
@@ -150,40 +77,15 @@ $(MAN7DIR)/%.7: $(MIBSDIR)/%.mib
@echo ".fi" >> $@
@echo "" >> $@
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-
$(MAN1DIR)/snmpc.1: snmpc_cmd.xml
date=`date +"%B %e %Y"`; \
xsltproc --output "$@" --stringparam company "Ericsson AB" --stringparam docgen "$(DOCGEN)" --stringparam gendate "$$date" --stringparam appname "$(APPLICATION)" --stringparam appver "$(VSN)" --xinclude -path $(DOCGEN)/priv/dtd -path $(DOCGEN)/priv/dtd_man_entities $(DOCGEN)/priv/xsl/db_man.xsl $<
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DATA) $(MAN1DIR)/* "$(RELEASE_PATH)/man/man1"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6DIR)/* "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man7"
- $(INSTALL_DATA) $(MAN7DIR)/* "$(RELEASE_PATH)/man/man7"
-
-release_spec:
+# ----------------------------------------------------
+
+include $(ERL_TOP)/make/doc.mk
info: info_xml info_man info_html
- @echo "MAN1DIR: $(MAN1DIR)"
- @echo "MAN3DIR: $(MAN3DIR)"
- @echo "MAN6DIR: $(MAN6DIR)"
- @echo "MAN7DIR: $(MAN7DIR)"
info_man:
@echo "man files:"
diff --git a/lib/snmp/doc/src/files.mk b/lib/snmp/doc/src/files.mk
index f364cb6fa5..ea4143d121 100644
--- a/lib/snmp/doc/src/files.mk
+++ b/lib/snmp/doc/src/files.mk
@@ -113,7 +113,7 @@ XML_FILES = $(BOOK_FILES) \
$(XML_REF6_FILES) \
$(XML_APPLICATION_FILES)
-GIF_FILES = \
+IMAGE_FILES = \
getnext1.gif \
getnext2.gif \
getnext3.gif \
@@ -125,19 +125,7 @@ GIF_FILES = \
snmp-um-1-image-3.gif \
MIB_mechanism.gif
-PS_FILES = getnext1.ps \
- getnext2.ps \
- getnext3.ps \
- getnext4.ps \
- snmp_agent_netif.ps \
- snmp-um-1-image-1.ps \
- snmp-um-1-image-2.ps \
- snmp-um-1-image-3.ps \
- snmp-um-1-image-8.ps \
- MIB_mechanism.ps
-
-
-MIB_FILES = \
+MIB_REF7_FILES = \
$(MIBSDIR)/RFC1213-MIB.mib \
$(MIBSDIR)/STANDARD-MIB.mib \
$(MIBSDIR)/SNMPv2-TM.mib \
diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml
index 6f333bc4bf..f8caf6228b 100644
--- a/lib/snmp/doc/src/notes.xml
+++ b/lib/snmp/doc/src/notes.xml
@@ -34,7 +34,327 @@
</header>
- <section><title>SNMP 5.5</title>
+ <section><title>SNMP 5.8</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add function to get a list of configured agent
+ transports. Also improved agent info with regards to
+ transports.</p>
+ <p>
+ Own Id: OTP-17109 Aux Id: ERIERL-583 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.7.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ [manager] In a function handling snmp errors, an unused
+ result (_Error) could result in matching issues and
+ therefor case clause runtime errors (crash). Note that
+ this would only happen in *very* unusual error cases.</p>
+ <p>
+ Own Id: OTP-17161</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.7.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ [manager] Misspelled priv protocol (atom) made it
+ impossible to update usm user 'priv_key' configuration
+ for usmAesCfb128Protocol via function calls.</p>
+ <p>
+ Own Id: OTP-17110 Aux Id: ERIERL-586 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.7.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed usage of <c>AC_CONFIG_AUX_DIRS()</c> macros in
+ configure script sources.</p>
+ <p>
+ Own Id: OTP-17093 Aux Id: ERL-1447, PR-2948 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ If an attempt was made to send a v1 trap on a IPv6
+ transport this could cause a master agent crash (if the
+ agent was *not* multi-threaded).</p>
+ <p>
+ Own Id: OTP-16920 Aux Id: OTP-16649 </p>
+ </item>
+ <item>
+ <p>
+ The deprecation info for a couple of the deprecated MIB
+ compiler functions where incorrect. Referred to functions
+ in the 'snmpa' module instead of 'snmpc'.</p>
+ <p>
+ Own Id: OTP-17056 Aux Id: OTP-17049 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Make it possible for the agent to configure separate
+ transports (sockets) for request-responder and
+ trap-sender.</p>
+ <p>
+ Own Id: OTP-16649</p>
+ </item>
+ <item>
+ <p>
+ The mib server cache handling has been improved. First,
+ the default gclimit has been changed from 100 to infinity
+ (to ensure the size is as small as possible). Also, the
+ method of removing old elements has been optimized.</p>
+ <p>
+ Own Id: OTP-16989 Aux Id: ERIERL-544 </p>
+ </item>
+ <item>
+ <p>
+ It is now possible to configure the agent in such a way
+ that the order of outgoing notifications are processed in
+ order in the agent. What happens after the notification
+ message has left the agent (been sent) is of course still
+ out of our control.</p>
+ <p>
+ Own Id: OTP-17022 Aux Id: ERIERL-492 </p>
+ </item>
+ <item>
+ <p>
+ Improve handling of the udp_error message. Basically an
+ improved error/warning message.</p>
+ <p>
+ Own Id: OTP-17033</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.6.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ For agent fix PrivParams for SNMPv3 USM with AES privacy,
+ as earlier fixed for the manager in OTP_16541.</p>
+ <p>
+ Own Id: OTP-15130 Aux Id: ERIERL-524, OTP-16541 </p>
+ </item>
+ <item>
+ <p>
+ The SNMP Agent missed to re-activate datagram reception
+ in an odd timeout case and went deaf. This bug has been
+ fixed.</p>
+ <p>
+ Own Id: OTP-15767 Aux Id: ERIERL-523 </p>
+ </item>
+ <item>
+ <p>
+ Use of deprecated functions in example 2 has been removed
+ (no more compiler warnings).</p>
+ <p>
+ Own Id: OTP-16716</p>
+ </item>
+ <item>
+ <p>
+ A file descriptor leak has been plugged. When calling the
+ reconfigure function of a mib, it opened the config
+ file(s) but never closed them on successful read.</p>
+ <p>
+ Own Id: OTP-16760 Aux Id: ERIERL-511 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ For manager, fix PrivParams for SNMPv3 USM with AES
+ privacy; * In `snmp_usm:do_decrypt/3`, pass full
+ UsmSecParams to `snmp_usm:try_decrypt/5` as expected by
+ AES clause. * Change `snmpm_usm:aes_encrypt/3` to use
+ EngineBoots and EngineTime as cached by
+ `snmpm_config:get_usm_eboots/1` and
+ `snmpm_config:get_usm_etime/1` instead of
+ `snmpm_config:get_engine_boots/0` and
+ `snmpm_config:get_engine_time/0`. This ensures correct
+ msgPrivacyParameters are sent when AES is used. * Add
+ test `snmp.snmp_manager_SUITE.usm_priv_aes/1` to avoid
+ regression.</p>
+ <p>
+ Own Id: OTP-16541 Aux Id: #2544 </p>
+ </item>
+ <item>
+ <p>
+ Invalid character in (manager) usm config entry generator
+ function.</p>
+ <p>
+ Own Id: OTP-16552 Aux Id: ERL-1196 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ <item>
+ <p>
+ Finalize deprecation. Already deprecated functions has a
+ "remove version 24" set and "new" functions added to list
+ of deprecated functions.</p>
+ <p>
+ Own Id: OTP-16463</p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>SNMP 5.5.0.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The mib server cache handling has been improved. First,
+ the default gclimit has been changed from 100 to infinity
+ (in order to ensure the size is as small as possible).
+ Also the method of removing old elements has been
+ optimized.</p>
+ <p>
+ Own Id: OTP-16989 Aux Id: ERIERL-544 </p>
+ </item>
+ <item>
+ <p>
+ It is now possible to configure the agent in such a way
+ that the order of outgoing notifications are processed in
+ order in the agent. What happens after the notification
+ message has left the agent (been sent) is of course still
+ out of our control.</p>
+ <p>
+ Own Id: OTP-17022 Aux Id: ERIERL-492 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>SNMP 5.5.0.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ For agent fix PrivParams for SNMPv3 USM with AES privacy,
+ as earlier fixed for the manager in OTP_16541.</p>
+ <p>
+ Own Id: OTP-15130 Aux Id: ERIERL-524, OTP-16541 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>SNMP 5.5.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The SNMP Agent missed to re-activate datagram reception
+ in an odd timeout case and went deaf. This bug has been
+ fixed.</p>
+ <p>
+ Own Id: OTP-15767 Aux Id: ERIERL-523 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section><title>SNMP 5.5.0.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A file descriptor leak has been plugged. When calling the
+ reconfigure function of a mib, it opened the config
+ file(s) but never closed them on successful read.</p>
+ <p>
+ Own Id: OTP-16760 Aux Id: ERIERL-511 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.5</title>
<section><title>Improvements and New Features</title>
<list>
@@ -277,8 +597,8 @@
firstly handles encountered errors and write an
informative message (into the converted stream) and
secondly keeps count of the number of successful or
- failed entry conversions. See <seealso
- marker="snmpa#log_to_txt">log_to_txt</seealso> for more
+ failed entry conversions. See <seeerl
+ marker="snmpa#log_to_txt">log_to_txt</seeerl> for more
info. </p> <p>The reason the ATL contained invalid
entries have also been fixed. The reason was that for
some outgoing messages (not response):</p> <list
@@ -626,8 +946,8 @@
<list type="bulleted">
<item>
<p>[agent]
- see <seealso marker="snmpa#load_mibs">load_mibs</seealso> and
- <seealso marker="snmpa#unload_mibs">unload_mibs</seealso>. </p>
+ see <seeerl marker="snmpa#load_mibs">load_mibs</seeerl> and
+ <seeerl marker="snmpa#unload_mibs">unload_mibs</seeerl>. </p>
<p>Own Id: OTP-11216</p>
</item>
@@ -813,11 +1133,11 @@
<item>
<p>[agent] Enable SNMP to create missing database directories. </p>
<p>Add
- <seealso marker="snmp_config#db_init_error">
- {db_init_error, create_db_and_dir}</seealso> option to SNMP
- <seealso marker="snmp_config#manager_opts_and_types">manager</seealso>
+ <seeguide marker="snmp_config#db_init_error">
+ {db_init_error, create_db_and_dir}</seeguide> option to SNMP
+ <seeguide marker="snmp_config#manager_opts_and_types">manager</seeguide>
and
- <seealso marker="snmp_config#agent_opts_and_types">agent</seealso>.
+ <seeguide marker="snmp_config#agent_opts_and_types">agent</seeguide>.
This allows them to create any missing parent directories for
<c>db_dir</c>, rather than treating any missing directories
as a fatal error.
@@ -829,7 +1149,7 @@
<item>
<p>[manager] Improved handling of unexpected return values from
- <seealso marker="snmpm_user">snmpm_user</seealso>
+ <seeerl marker="snmpm_user">snmpm_user</seeerl>
callback functions. </p>
<p>Violations of the documented API (crashes or invalid return
values) will now result in an error message. </p>
@@ -844,11 +1164,11 @@
there is a chance it may otherwise wrap during conversion). </p>
<p>See
agent
- <seealso marker="snmpa#log_to_txt">log_to_txt</seealso> and
- <seealso marker="snmpa#log_to_io">log_to_io</seealso> and also
+ <seeerl marker="snmpa#log_to_txt">log_to_txt</seeerl> and
+ <seeerl marker="snmpa#log_to_io">log_to_io</seeerl> and also
manager
- <seealso marker="snmpm#log_to_txt">log_to_txt</seealso> and
- <seealso marker="snmpm#log_to_io">log_to_io</seealso>
+ <seeerl marker="snmpm#log_to_txt">log_to_txt</seeerl> and
+ <seeerl marker="snmpm#log_to_io">log_to_io</seeerl>
for details. </p>
<p>Own Id: OTP-11396</p>
<p>Own Id: seq12433</p>
@@ -932,12 +1252,12 @@
<item>
<p>[agent] Improved documentation for the functions for
loading and unloading mibs,
- see <seealso marker="snmpa#load_mibs">load_mibs</seealso> and
- <seealso marker="snmpa#unload_mibs">unload_mibs</seealso> for
+ see <seeerl marker="snmpa#load_mibs">load_mibs</seeerl> and
+ <seeerl marker="snmpa#unload_mibs">unload_mibs</seeerl> for
more info. </p>
<p>Also added new functions for loading and unloading a single mib,
- see <seealso marker="snmpa#load_mib">load_mib</seealso> and
- <seealso marker="snmpa#unload_mib">unload_mib</seealso> for
+ see <seeerl marker="snmpa#load_mib">load_mib</seeerl> and
+ <seeerl marker="snmpa#unload_mib">unload_mib</seeerl> for
more info. </p>
<p>Own Id: OTP-11216</p>
</item>
@@ -954,8 +1274,8 @@
<list type="bulleted">
<item>
<p>[agent]
- see <seealso marker="snmpa#load_mibs">load_mibs</seealso> and
- <seealso marker="snmpa#unload_mibs">unload_mibs</seealso>. </p>
+ see <seeerl marker="snmpa#load_mibs">load_mibs</seeerl> and
+ <seeerl marker="snmpa#unload_mibs">unload_mibs</seeerl>. </p>
<p>Own Id: OTP-11216</p>
</item>
@@ -1017,7 +1337,7 @@
it in the wrong (internal bitlist) format. </p>
<p>The vacmViewTreeFamilyMask is defined as a bit string in the MIB
(OCTET STRING). Internally a bitlist (list of 1's and 0's,
- see <seealso marker="snmp_agent_config_files#vacm">vacm config file</seealso>
+ see <seeguide marker="snmp_agent_config_files#vacm">vacm config file</seeguide>
for more info) is used.
However, the MIB implementation assumed the latter, effectively
rendering all attempts to read/set masks via SNMP unsuccessful. </p>
@@ -1083,25 +1403,25 @@
<item>
<p>[agent] Introduced a documented behaviour for the mib-server
- <seealso marker="snmpa_mib_data">mib-data backend</seealso>.
+ <seeerl marker="snmpa_mib_data">mib-data backend</seeerl>.
At present only the default module (<c>snmpa_mib_data_tttn</c>) is
provided. </p>
<p>A config option for the (agent)
- <seealso marker="snmp_config#agent_mib_server">mib-servers</seealso>
+ <seeguide marker="snmp_config#agent_mib_server">mib-servers</seeguide>
mib-data backend module has been added to the agent config options,
- <seealso marker="snmp_config#agent_ms_data_module">data_module</seealso>. </p>
+ <seeguide marker="snmp_config#agent_ms_data_module">data_module</seeguide>. </p>
<p>Own Id: OTP-11101</p>
</item>
<item>
<p>[agent] Introduced a documented behaviour for the
- <seealso marker="snmpa_mib_storage">mib storage</seealso>.
+ <seeerl marker="snmpa_mib_storage">mib storage</seeerl>.
At present there are three simple modules
(<c>snmpa_mib_storage_ets</c>, <c>snmpa_mib_storage_dets</c> and
<c>snmpa_mib_storage_mnesia</c>) implementing this behaviour,
provided with the app. </p>
<p>A config option for the (agent)
- <seealso marker="snmp_config#agent_mib_storage">mib storage</seealso>
+ <seeguide marker="snmp_config#agent_mib_storage">mib storage</seeguide>
has been added to the agent config options. </p>
<p>Own Id: OTP-11107</p>
</item>
@@ -1265,7 +1585,7 @@
<list type="bulleted">
<item>
<p>[agent] Simultaneous
- <seealso marker="snmpa#backup">snmpa:backup/1,2</seealso>
+ <seeerl marker="snmpa#backup">snmpa:backup/1,2</seeerl>
calls can interfere.
The master agent did not check if a backup was already in
progress when a backup request was accepted. </p>
@@ -1365,7 +1685,7 @@
<item>
<p>[manager]
- <seealso marker="snmpm#log_to_io">snmpm:log_to_io/6</seealso>
+ <seeerl marker="snmpm#log_to_io">snmpm:log_to_io/6</seeerl>
did not use the LogName argument. </p>
<p>Own Id: OTP-10066</p>
</item>
@@ -1411,9 +1731,9 @@
<item>
<p>Added the <c>log_to_io</c> audit-trail-log converter function
to the api modules of both the
- <seealso marker="snmpm#log_to_io">manager</seealso>
+ <seeerl marker="snmpm#log_to_io">manager</seeerl>
and
- <seealso marker="snmpa#log_to_io">agent</seealso>. </p>
+ <seeerl marker="snmpa#log_to_io">agent</seeerl>. </p>
<p>Own Id: OTP-9940</p>
</item>
@@ -1428,7 +1748,7 @@
<item>
<p>[agent] Documenting previously existing but undocumented function,
- <seealso marker="snmp_generic#get_table_info">snmp_generic:get_table_info/2</seealso>. </p>
+ <seeerl marker="snmp_generic#get_table_info">snmp_generic:get_table_info/2</seeerl>. </p>
<p>Own Id: OTP-9942</p>
</item>
@@ -1450,7 +1770,7 @@
<list type="bulleted">
<item>
<p>[agent] Simultaneous
- <seealso marker="snmpa#backup">snmpa:backup/1,2</seealso>
+ <seeerl marker="snmpa#backup">snmpa:backup/1,2</seeerl>
calls can interfere.
The master agent did not check if a backup was already in
progress when a backup request was accepted. </p>
@@ -1490,7 +1810,7 @@
limit the number of varbinds that can be included in
a Get-BULK response message. This is specified by the
new config option,
- <seealso marker="snmp_config#agent_gb_max_vbs">gb_max_vbs</seealso>.
+ <seeerl marker="snmp_config#agent_gb_max_vbs">gb_max_vbs</seeerl>.
</p>
<p>Own Id: OTP-9700</p>
</item>
@@ -1509,7 +1829,7 @@
<list type="bulleted">
<item>
<p>[agent] Simultaneous
- <seealso marker="snmpa#backup">snmpa:backup/1,2</seealso>
+ <seeerl marker="snmpa#backup">snmpa:backup/1,2</seeerl>
calls can interfere.
The master agent did not check if a backup was already in
progress when a backup request was accepted. </p>
@@ -1549,7 +1869,7 @@
limit the number of varbinds that can be included in
a Get-BULK response message. This is specified by the
new config option,
- <seealso marker="snmp_config#agent_gb_max_vbs">gb_max_vbs</seealso>.
+ <seeguide marker="snmp_config#agent_gb_max_vbs">gb_max_vbs</seeguide>.
</p>
<p>Own Id: OTP-9700</p>
</item>
@@ -1569,9 +1889,9 @@
<p>[agent] Mib server cache gclimit update function incorrectly calls
age update function.
The gclimit update function,
- <seealso marker="snmpa#update_mibs_cache_gclimit">update_mibs_cache_gclimit/1</seealso>,
+ <seeerl marker="snmpa#update_mibs_cache_gclimit">update_mibs_cache_gclimit/1</seeerl>,
<em>incorrectly</em> called the age update function,
- <seealso marker="snmpa#update_mibs_cache_age">update_mibs_cache_age/2</seealso>. </p>
+ <seeerl marker="snmpa#update_mibs_cache_age">update_mibs_cache_age/2</seeerl>. </p>
<p>Johan Claesson</p>
<p>Own Id: OTP-9868</p>
</item>
@@ -1722,7 +2042,7 @@
<list type="bulleted">
<item>
<p>[compiler] Improved version info printout from the
- <seealso marker="snmpc(command)#">MIB compiler frontend escript</seealso>. </p>
+ <seecom marker="snmpc(command)#">MIB compiler frontend escript</seecom>. </p>
<p>Own Id: OTP-9618</p>
</item>
@@ -1752,7 +2072,7 @@
<item>
<p>[compiler] Fix the <c>--warnings/--W</c> option parsing in the
- <seealso marker="snmpc(command)#option_warnings">snmpc</seealso>
+ <seecom marker="snmpc(command)#option_warnings">snmpc</seecom>
wrapper (e)script.
The short warning option was incorrectly <c>--w</c>, instead
of as documented <c>--W</c>. This has now been corrected. </p>
@@ -1850,9 +2170,9 @@
warnings-as-errors variable), which caused the
compiler to crash when using the snmpc (e)script. </p>
<p>Also added the option
- <seealso marker="snmpc(command)#option_werror">--Werror</seealso>
+ <seecom marker="snmpc(command)#option_werror">--Werror</seecom>
for the SNMP MIB compiler (escript) frontend (to mimic
- <seealso marker="erts:erlc">erlc</seealso>),
+ <seecom marker="erts:erlc">erlc</seecom>),
which specifies whether warnings should be treated as errors. </p>
<p>Own Id: OTP-9447</p>
</item>
@@ -1908,9 +2228,9 @@
(transportDomainUdpIpv6).
To facilitate this, the transport domain, <c>tdomain</c>,
is now a (new) valid option when
- <seealso marker="snmpm#register_agent">registering</seealso>
+ <seeerl marker="snmpm#register_agent">registering</seeerl>
a new agent (and
- <seealso marker="snmpm#update_agent_info">updating</seealso>
+ <seeerl marker="snmpm#update_agent_info">updating</seeerl>
agent info). </p>
<p>This also mean that the transport behaviour has changed. </p>
<p>Own Id: OTP-9305</p>
@@ -1919,9 +2239,9 @@
<item>
<p>[compiler] Added the option
- <seealso marker="snmpc#compile">warnings_as_errors</seealso>
+ <seeerl marker="snmpc#compile">warnings_as_errors</seeerl>
(for the SNMP MIB compiler (escript) frontend, the option
- <seealso marker="snmpc(command)#option_wae">--wae</seealso> is used)
+ <seecom marker="snmpc(command)#option_wae">--wae</seecom> is used)
which specifies whether warnings should be treated as errors. </p>
<p>Tuncer Ayaz</p>
<p>Own Id: OTP-9437</p>
@@ -2034,9 +2354,9 @@
<item>
<p>[agent] Added support for sending traps to IPv6 targets. </p>
<p>See the
- <seealso marker="snmp_agent_config_files#target_addr">target address config file</seealso>,
- the <seealso marker="snmpa_conf#target_addr_entry">target_addr_entry/11</seealso> function or
- <seealso marker="snmp_target_mib#add_addr">add_addr/11</seealso> for more info. </p>
+ <seeguide marker="snmp_agent_config_files#target_addr">target address config file</seeguide>,
+ the <seeerl marker="snmpa_conf#target_addr_entry">target_addr_entry/11</seeerl> function or
+ <seeerl marker="snmp_target_mib#add_addr">add_addr/11</seeerl> for more info. </p>
<p>Own Id: OTP-9088</p>
<p>Aux Id: Seq 11790</p>
</item>
@@ -2045,8 +2365,8 @@
<item>
<p>[agent] To be able to handle multiple engine-id(s) when
sending trap(s), the function
- <seealso marker="snmp_community_mib#add_community">
- add_community/6</seealso> has been added. </p>
+ <seeerl marker="snmp_community_mib#add_community">
+ add_community/6</seeerl> has been added. </p>
<p>Own Id: OTP-9119</p>
<p>Aux Id: Seq 11792</p>
</item>
@@ -2055,14 +2375,14 @@
<p>[manager] The API for snmp requests has been augmented to
allow the caller to override some configuration. </p>
<p>This has been done by introducing a new set of API functions, see
- <seealso marker="snmpm#sync_get2">sync_get2/3,4</seealso>,
- <seealso marker="snmpm#async_get2">async_get2/3,4</seealso>,
- <seealso marker="snmpm#sync_get_next2">sync_get_next2/3,4</seealso>,
- <seealso marker="snmpm#async_get_next2">async_get_next2/3,4</seealso>,
- <seealso marker="snmpm#sync_get_bulk2">sync_get_bulk2/5,6</seealso>,
- <seealso marker="snmpm#async_get_bulk2">async_get_bulk2/5,6</seealso>,
- <seealso marker="snmpm#sync_set2">sync_set2/3,4</seealso> and
- <seealso marker="snmpm#async_set2">async_set2/3,4</seealso>
+ <seeerl marker="snmpm#sync_get2">sync_get2/3,4</seeerl>,
+ <seeerl marker="snmpm#async_get2">async_get2/3,4</seeerl>,
+ <seeerl marker="snmpm#sync_get_next2">sync_get_next2/3,4</seeerl>,
+ <seeerl marker="snmpm#async_get_next2">async_get_next2/3,4</seeerl>,
+ <seeerl marker="snmpm#sync_get_bulk2">sync_get_bulk2/5,6</seeerl>,
+ <seeerl marker="snmpm#async_get_bulk2">async_get_bulk2/5,6</seeerl>,
+ <seeerl marker="snmpm#sync_set2">sync_set2/3,4</seeerl> and
+ <seeerl marker="snmpm#async_set2">async_set2/3,4</seeerl>
for more info. </p>
<p>Own Id: OTP-9162</p>
</item>
@@ -2083,13 +2403,13 @@
<p>[agent] Pass extra info through the agent to the net-if
process when sending notifications. </p>
<p>See
- <seealso marker="snmpa#send_notification2">
- snmpa:send_notification2/3</seealso> for more info.
+ <seeerl marker="snmpa#send_notification2">
+ snmpa:send_notification2/3</seeerl> for more info.
See also the incomming net-if messages when sending a
- <seealso marker="snmp_agent_netif#im_send_pdu">trap</seealso>
+ <seeguide marker="snmp_agent_netif#im_send_pdu">trap</seeguide>
(send_pdu message) and
- <seealso marker="snmp_agent_netif#im_send_pdu_req">
- notification</seealso> (send_pdu_req message). </p>
+ <seeguide marker="snmp_agent_netif#im_send_pdu_req">
+ notification</seeguide> (send_pdu_req message). </p>
<p>Own Id: OTP-9183</p>
<p>Aux Id: Seq 11817</p>
</item>
diff --git a/lib/snmp/doc/src/notes_history.xml b/lib/snmp/doc/src/notes_history.xml
index f711757548..ed71a86e83 100644
--- a/lib/snmp/doc/src/notes_history.xml
+++ b/lib/snmp/doc/src/notes_history.xml
@@ -76,10 +76,10 @@
<item>
<p>[agent] Added very basic support for multiple SNMPv3
EngineIDs in a single agent. See
- <seealso marker="snmpa#send_notification">send_notification/7</seealso>,
- <seealso marker="snmpa_mpd#process_packet">process_packet/7</seealso>,
- <seealso marker="snmpa_mpd#generate_response_msg">generate_response_msg/6</seealso> or
- <seealso marker="snmpa_mpd#generate_msg">generate_msg/6</seealso>
+ <seeerl marker="snmpa#send_notification">send_notification/7</seeerl>,
+ <seeerl marker="snmpa_mpd#process_packet">process_packet/7</seeerl>,
+ <seeerl marker="snmpa_mpd#generate_response_msg">generate_response_msg/6</seeerl> or
+ <seeerl marker="snmpa_mpd#generate_msg">generate_msg/6</seeerl>
for more info. </p>
<p>Own Id: OTP-8478</p>
@@ -97,7 +97,7 @@
<list type="bulleted">
<item>
<p>The config utility
- (<seealso marker="snmp#config">snmp:config/0</seealso>)
+ (<seeerl marker="snmp#config">snmp:config/0</seeerl>)
generated a default notify.conf
with a bad name for the standard trap entry (was "stadard trap",
but should have been "standard trap"). This has been corrected. </p>
@@ -132,7 +132,7 @@
<p>[compiler] The SMI specifies that a table row OID should be
named: { &lt;tableIdentifier&gt; "1" }. </p>
<p>A new option has been introduced,
- <seealso marker="snmpc#compiler_opts">relaxed_row_name_assign_check</seealso>,
+ <seeerl marker="snmpc#compiler_opts">relaxed_row_name_assign_check</seeerl>,
that allows for a more liberal numbering scheme</p>
<p>Own Id: OTP-8574</p>
</item>
@@ -208,10 +208,10 @@
<p>[agent|manager] Entries in the audit trail log can now be
augmented by a sequence number. </p>
<p>This is enabled by the <c>seqno</c> option, which is part of the
- <seealso marker="snmp_config#audit_trail_log">Audit Trail Log</seealso>
+ <seeerl marker="snmp_config#audit_trail_log">Audit Trail Log</seeerl>
config option. </p>
<p>See the
- <seealso marker="snmp_config#configuration_params">Configuring the application</seealso>
+ <seeerl marker="snmp_config#configuration_params">Configuring the application</seeerl>
chapter of the User's Guide for further info. </p>
<p>Own Id: OTP-8395</p>
@@ -267,10 +267,10 @@
<p>[agent|manager] Entries in the audit trail log can now be
augmented by a sequence number. </p>
<p>This is enabled by the <c>seqno</c> option, which is part of the
- <seealso marker="snmp_config#audit_trail_log">Audit Trail Log</seealso>
+ <seeerl marker="snmp_config#audit_trail_log">Audit Trail Log</seeerl>
config option. </p>
<p>See the
- <seealso marker="snmp_config#configuration_params">Configuring the application</seealso>
+ <seeerl marker="snmp_config#configuration_params">Configuring the application</seeerl>
chapter of the User's Guide for further info. </p>
<p>Own Id: OTP-8395</p>
@@ -289,7 +289,7 @@
<list type="bulleted">
<item>
<p>[manager] Registration of agents using the config file,
- <seealso marker="snmp_manager_config_files#agents">agents.conf</seealso>,
+ <seeguide marker="snmp_manager_config_files#agents">agents.conf</seeguide>,
does not work. This has now been corrected. </p>
<p>Per Hedeland</p>
<p>Own Id: OTP-8442</p>
@@ -297,7 +297,7 @@
<item>
<p>The config utility
- (<seealso marker="snmp#config">snmp:config/0</seealso>)
+ (<seeerl marker="snmp#config">snmp:config/0</seeerl>)
generated a default notify.conf
with a bad name for the standard trap entry (was "stadard trap",
but should have been "standard trap"). This has been corrected. </p>
@@ -357,14 +357,14 @@
was generated "on the fly"). </p>
<p>This has now been changed so that when a message is received
from an unknown agent, then only
- <seealso marker="snmpm_user#handle_agent">handle_agent</seealso>
+ <seeerl marker="snmpm_user#handle_agent">handle_agent</seeerl>
(for the default user) is called, but now this call also has a
<c>Type</c> argument, which is
<c>pdu | trap | report | inform</c>, depending on what kind of
message was actually received, thus making it possible for the
user to properly analyze the data received. </p>
<p>To handle this, the
- <seealso marker="snmpm_user">snmpm_user</seealso> behaviour has
+ <seeerl marker="snmpm_user">snmpm_user</seeerl> behaviour has
been updated. </p>
<p>*** POTENTIAL INCOMPATIBILITY ***</p>
<p>Own Id: OTP-8229</p>
@@ -407,12 +407,12 @@
The component that actually make the filter decisions
is the network interface filter module. This module
must implement the
- <seealso marker="snmpm_network_interface_filter">network interface filter behaviour</seealso>
+ <seeerl marker="snmpm_network_interface_filter">network interface filter behaviour</seeerl>
for message filtering.
See also the Configuring chapter of
the User's Guide to see how to configure this feature. </p>
<p>See the
- <seealso marker="snmp_config#configuration_params">configuration</seealso>
+ <seeerl marker="snmp_config#configuration_params">configuration</seeerl>
chapter for more info about the filter options.</p>
<p>Own Id: OTP-8228</p>
<p>Aux Id: Seq 11411</p>
@@ -473,16 +473,16 @@
<p>A number of new functions and config options for the mib server
cache has been added. </p>
<p>See
- <seealso marker="snmpa#invalidate_mibs_cache">invalidate_mibs_cache/0,1</seealso>,
- <seealso marker="snmpa#enable_mibs_cache">enable_mibs_cache/0,1</seealso>,
- <seealso marker="snmpa#disable_mibs_cache">disable_mibs_cache/0,1</seealso>,
- <seealso marker="snmpa#gc_mibs_cache">gc_mibs_cache/0,1,2,3</seealso>,
- <seealso marker="snmpa#enable_mibs_cache_autogc">enable_mibs_cache_autogc/0,1</seealso>,
- <seealso marker="snmpa#disable_mibs_cache_autogc">disable_mibs_cache_autogc/0,1</seealso>,
- <seealso marker="snmpa#update_mibs_cache_age">update_mibs_cache_age/1,2</seealso> and
- <seealso marker="snmpa#update_mibs_cache_gclimit">update_mibs_cache_gclimit/1,2</seealso> for more info. </p>
+ <seeerl marker="snmpa#invalidate_mibs_cache">invalidate_mibs_cache/0,1</seeerl>,
+ <seeerl marker="snmpa#enable_mibs_cache">enable_mibs_cache/0,1</seeerl>,
+ <seeerl marker="snmpa#disable_mibs_cache">disable_mibs_cache/0,1</seeerl>,
+ <seeerl marker="snmpa#gc_mibs_cache">gc_mibs_cache/0,1,2,3</seeerl>,
+ <seeerl marker="snmpa#enable_mibs_cache_autogc">enable_mibs_cache_autogc/0,1</seeerl>,
+ <seeerl marker="snmpa#disable_mibs_cache_autogc">disable_mibs_cache_autogc/0,1</seeerl>,
+ <seeerl marker="snmpa#update_mibs_cache_age">update_mibs_cache_age/1,2</seeerl> and
+ <seeerl marker="snmpa#update_mibs_cache_gclimit">update_mibs_cache_gclimit/1,2</seeerl> for more info. </p>
<p>See also the
- <seealso marker="snmp_config#configuration_params">configuration</seealso>
+ <seeerl marker="snmp_config#configuration_params">configuration</seeerl>
chapter for more info about the mib server cache options.</p>
<p>Own Id: OTP-8182</p>
<p>Aux Id: Seq 11383</p>
@@ -495,7 +495,7 @@
make it possible to configure the username the agent reacts to.
Default is <c>""</c>. </p>
<p>See the
- <seealso marker="snmp_config#configuration_params">configuration</seealso>
+ <seeerl marker="snmp_config#configuration_params">configuration</seeerl>
chapter for more info about the discovery options.</p>
<p>Own Id: OTP-8120</p>
<p>Aux Id: Seq 11361</p>
@@ -545,7 +545,7 @@
<item>
<p>[agent] Support for the discovery process. </p>
<p>The agent can both initiate discovery itself (see the
- <seealso marker="snmp_agent_funct_descr#discovery">discovery</seealso> chapter
+ <seeguide marker="snmp_agent_funct_descr#discovery">discovery</seeguide> chapter
for more info) and respond to discovery initiated by a manager.</p>
<p>Own Id: OTP-7571</p>
<p>Aux Id: Seq 11053</p>
@@ -583,13 +583,13 @@
<item>
<p>[agent] Originating discovery improvement. </p>
<p>Added the ExtraInfo argument to the
- <seealso marker="snmpa#discovery">discovery</seealso> function.
+ <seeerl marker="snmpa#discovery">discovery</seeerl> function.
This argument will be passed on to the stage1_finish callback
function. Also, the
- <seealso marker="snmpa#discovery">discovery</seealso> function
+ <seeerl marker="snmpa#discovery">discovery</seeerl> function
will now always return <c>{ok, ManagerEngineID}</c> on successful
discovery. </p>
- <p>The <seealso marker="snmpa_discovery_handler">discovery handler</seealso>
+ <p>The <seeerl marker="snmpa_discovery_handler">discovery handler</seeerl>
behaviour updated accordingly. </p>
<p>Own Id: OTP-8098</p>
<p>Aux Id: Seq 11346</p>
@@ -621,7 +621,7 @@
<item>
<p>[agent] Support for the discovery process. </p>
<p>The agent can both initiate discovery itself (see the
- <seealso marker="snmp_agent_funct_descr#discovery">discovery</seealso> chapter
+ <seeguide marker="snmp_agent_funct_descr#discovery">discovery</seeguide> chapter
for more info) and respond to discovery initiated by a manager.</p>
<p>Own Id: OTP-7571</p>
<p>Aux Id: Seq 11053</p>
@@ -650,12 +650,12 @@
<p>This problem has now been fixed, but requires that the MIB
defining this mib-entry is loaded! </p>
<p>The utility function
- <seealso marker="snmpm#oid_to_type">oid_to_type</seealso>
+ <seeerl marker="snmpm#oid_to_type">oid_to_type</seeerl>
has been added, for debug purpose. </p>
<p>The utility function(s)
- <seealso marker="snmp#octet_string_to_bits">octet_string_to_bits</seealso>
+ <seeerl marker="snmp#octet_string_to_bits">octet_string_to_bits</seeerl>
and
- <seealso marker="snmp#bits_to_octet_string">bits_to_octet_string</seealso>
+ <seeerl marker="snmp#bits_to_octet_string">bits_to_octet_string</seeerl>
has also been added. These can be used if the user prefers to
handle the conversion on their own. </p>
<p>Own Id: OTP-8015</p>
@@ -665,12 +665,12 @@
<item>
<p>[agent] Fixed some issues with the discovery handling. </p>
<p>Changed the API of the
- <seealso marker="snmpa#discovery">discovery</seealso>
+ <seeerl marker="snmpa#discovery">discovery</seeerl>
function to solve some
of these problems. </p>
<p>Introduced various options for controlling the discovery
process. See the
- <seealso marker="snmp_config#configuration_params">configuration</seealso>
+ <seeerl marker="snmp_config#configuration_params">configuration</seeerl>
chapter for more info about the discovery options.</p>
<p>Own Id: OTP-8020</p>
<p>Aux Id: Seq 11295</p>
@@ -702,7 +702,7 @@
<item>
<p>[agent] Support for the discovery process. </p>
<p>The agent can both initiate discovery itself (see the
- <seealso marker="snmp_agent_funct_descr#discovery">discovery</seealso> chapter
+ <seeguide marker="snmp_agent_funct_descr#discovery">discovery</seeguide> chapter
for more info) and respond to discovery initiated by a manager.</p>
<p>Own Id: OTP-7571</p>
<p>Aux Id: Seq 11053</p>
@@ -748,7 +748,7 @@
<item>
<p>[agent] As of version 4.13 the possible return values
of the function
- <seealso marker="snmpa_mpd#process_packet">snmpa_mpd:process_packet/4</seealso>
+ <seeerl marker="snmpa_mpd#process_packet">snmpa_mpd:process_packet/4</seeerl>
changed, but this was not documented. </p>
<p>Own Id: OTP-7989</p>
<p>Aux Id: Seq 11275</p>
@@ -779,7 +779,7 @@
<item>
<p>[agent] Support for the discovery process. </p>
<p>The agent can both initiate discovery itself (see the
- <seealso marker="snmp_agent_funct_descr#discovery">discovery</seealso> chapter
+ <seeguide marker="snmp_agent_funct_descr#discovery">discovery</seeguide> chapter
for more info) and respond to discovery initiated by a manager.</p>
<p>Own Id: OTP-7571</p>
<p>Aux Id: Seq 11053</p>
@@ -799,15 +799,15 @@
<item>
<p>[manager] Registration of users had some issues. </p>
<p>Not all of the registration functions where actually exported
- (<seealso marker="snmpm#register_user">register_user/4</seealso>
+ (<seeerl marker="snmpm#register_user">register_user/4</seeerl>
and
- <seealso marker="snmpm#register_user_monitor">register_user_monitor/4</seealso>).
+ <seeerl marker="snmpm#register_user_monitor">register_user_monitor/4</seeerl>).
This has now been fixed. </p>
<p>Also, the registration did not succeed unless
user implemented the *new* behaviour. This has now
also been fixed (registration succeeds if the user
implements either the new (i.e. updated
- <seealso marker="snmpm_user">snmpm_user</seealso>)
+ <seeerl marker="snmpm_user">snmpm_user</seeerl>)
or the old user behaviour (<c>snmpm_user_old</c>)). </p>
<p>Own Id: OTP-7902</p>
<p>Aux Id: Seq 11240</p>
@@ -839,7 +839,7 @@
<item>
<p>[agent] Support for the discovery process. </p>
<p>The agent can both initiate discovery itself (see the
- <seealso marker="snmp_agent_funct_descr#discovery">discovery</seealso> chapter
+ <seeguide marker="snmp_agent_funct_descr#discovery">discovery</seeguide> chapter
for more info) and respond to discovery initiated by a manager.</p>
<p>Own Id: OTP-7571</p>
<p>Aux Id: Seq 11053</p>
@@ -875,23 +875,23 @@
<p>These problems has been solved in the following way: </p>
<p>First, a new set of api functions has been introduced (and documented):
- <seealso marker="snmpm#register_user">register_user/4</seealso>,
- <seealso marker="snmpm#register_user_monitor">register_user_monitor/4</seealso>,
- <seealso marker="snmpm#register_agent">register_agent/3</seealso>,
- <seealso marker="snmpm#unregister_agent">unregister_agent/2</seealso>,
- <seealso marker="snmpm#agent_info">agent_info/2</seealso>,
- <seealso marker="snmpm#update_agent_info">update_agent_info/4</seealso>,
- <seealso marker="snmpm#sync_get">sync_get/3,4,5,6</seealso>,
- <seealso marker="snmpm#async_get">async_get/3,4,5,6</seealso>,
- <seealso marker="snmpm#sync_get_next">sync_get_next/3,4,5,6</seealso>,
- <seealso marker="snmpm#async_get_next">async_get_next/3,4,5,6</seealso>,
- <seealso marker="snmpm#sync_set">sync_set/3,4,5,6</seealso>,
- <seealso marker="snmpm#async_set">async_set/3,4,5,6</seealso>,
- <seealso marker="snmpm#sync_get_bulk">sync_get_bulk/5,6,7,8</seealso> and
- <seealso marker="snmpm#async_get_bulk">async_get_bulk/5,6,7,8</seealso>
+ <seeerl marker="snmpm#register_user">register_user/4</seeerl>,
+ <seeerl marker="snmpm#register_user_monitor">register_user_monitor/4</seeerl>,
+ <seeerl marker="snmpm#register_agent">register_agent/3</seeerl>,
+ <seeerl marker="snmpm#unregister_agent">unregister_agent/2</seeerl>,
+ <seeerl marker="snmpm#agent_info">agent_info/2</seeerl>,
+ <seeerl marker="snmpm#update_agent_info">update_agent_info/4</seeerl>,
+ <seeerl marker="snmpm#sync_get">sync_get/3,4,5,6</seeerl>,
+ <seeerl marker="snmpm#async_get">async_get/3,4,5,6</seeerl>,
+ <seeerl marker="snmpm#sync_get_next">sync_get_next/3,4,5,6</seeerl>,
+ <seeerl marker="snmpm#async_get_next">async_get_next/3,4,5,6</seeerl>,
+ <seeerl marker="snmpm#sync_set">sync_set/3,4,5,6</seeerl>,
+ <seeerl marker="snmpm#async_set">async_set/3,4,5,6</seeerl>,
+ <seeerl marker="snmpm#sync_get_bulk">sync_get_bulk/5,6,7,8</seeerl> and
+ <seeerl marker="snmpm#async_get_bulk">async_get_bulk/5,6,7,8</seeerl>
that all use <c>TargetName</c> (and not, as previously, <c>Addr</c>
and <c>Port</c>) to identify the agent (also the return value of
- <seealso marker="snmpm#which_agents">which_agents</seealso> has
+ <seeerl marker="snmpm#which_agents">which_agents</seeerl> has
been changed). </p>
<p>Second, for backward compatibility, the old functions still
exist, but are no longer documented and are now wrappers for the
@@ -899,14 +899,14 @@
all. The TargetName is however generated from the provided
<c>Addr</c>, <c>Port</c> and <c>Version</c> config options. </p>
<p>Third, the behaviour of the
- <seealso marker="snmpm_user">SNMP manager user</seealso> has
+ <seeerl marker="snmpm_user">SNMP manager user</seeerl> has
been changed to reflect this, i.e.
- <seealso marker="snmpm_user#handle_pdu">handle_pdu/4</seealso>,
- <seealso marker="snmpm_user#handle_trap">handle_trap/3</seealso>,
- <seealso marker="snmpm_user#handle_inform">handle_inform/3</seealso>,
- <seealso marker="snmpm_user#handle_report">handle_report/3</seealso>
+ <seeerl marker="snmpm_user#handle_pdu">handle_pdu/4</seeerl>,
+ <seeerl marker="snmpm_user#handle_trap">handle_trap/3</seeerl>,
+ <seeerl marker="snmpm_user#handle_inform">handle_inform/3</seeerl>,
+ <seeerl marker="snmpm_user#handle_report">handle_report/3</seeerl>
and the return-value of
- <seealso marker="snmpm_user#handle_agent">handle_agent/4</seealso>.
+ <seeerl marker="snmpm_user#handle_agent">handle_agent/4</seeerl>.
The old (non-documented) callback-functions (using Addr and Port)
will still be called if the agent was registered using the old
registration functions. </p>
@@ -1054,7 +1054,7 @@
<p>[agent] A simple lookup cache has been added to improve
the mib server lookup performance. </p>
<p>This can be disabled with the mib_server
- <seealso marker="snmp_config#agent_ms_cache">cache</seealso>
+ <seeerl marker="snmp_config#agent_ms_cache">cache</seeerl>
option. </p>
<p>Own Id: OTP-7346</p>
</item>
@@ -1108,11 +1108,11 @@
<item>
<p>Added utility functions for transforming DateAndTime
as [int()] to strings;
- <seealso marker="snmp#dat2s">date_and_time_to_string/2</seealso>
+ <seeerl marker="snmp#dat2s">date_and_time_to_string/2</seeerl>
and
- <seealso marker="snmp#dat2s2">date_and_time_to_string2/1</seealso>. </p>
+ <seeerl marker="snmp#dat2s2">date_and_time_to_string2/1</seeerl>. </p>
<p>Also added new validation function
- <seealso marker="snmp#vdat">validate_date_and_time/2</seealso>. </p>
+ <seeerl marker="snmp#vdat">validate_date_and_time/2</seeerl>. </p>
<p>Own Id: OTP-7412</p>
<p>Aux Id: Seq 10987</p>
</item>
@@ -1179,7 +1179,7 @@
solution (and probably not backward compatible) is devised, which
retrieves and stores all REFERENCE part(s) of a MIB. </p>
<p>See the
- <seealso marker="snmpc#compiler_opts">compiler options</seealso>
+ <seeerl marker="snmpc#compiler_opts">compiler options</seeerl>
for more info. </p>
<p>Serge Aleynikov</p>
@@ -1189,11 +1189,11 @@
<item>
<p>Added utility functions for transforming DateAndTime
as [int()] to strings;
- <seealso marker="snmp#dat2s">date_and_time_to_string/2</seealso>
+ <seeerl marker="snmp#dat2s">date_and_time_to_string/2</seeerl>
and
- <seealso marker="snmp#dat2s2">date_and_time_to_string2/1</seealso>. </p>
+ <seeerl marker="snmp#dat2s2">date_and_time_to_string2/1</seeerl>. </p>
<p>Also added new validation function
- <seealso marker="snmp#vdat">validate_date_and_time/2</seealso>. </p>
+ <seeerl marker="snmp#vdat">validate_date_and_time/2</seeerl>. </p>
<p>Own Id: OTP-7412</p>
<p>Aux Id: Seq 10987</p>
</item>
@@ -1245,8 +1245,8 @@
<item>
<p>The API for sending inform(s) has been improved. Also
the documentation has been corrected and updated. See
- <seealso marker="snmpa#send_notification">snmpa:send_notification</seealso> and
- <seealso marker="snmpa_notification_delivery_info_receiver">snmpa_notification_delivery_info_receiver</seealso>
+ <seeerl marker="snmpa#send_notification">snmpa:send_notification</seeerl> and
+ <seeerl marker="snmpa_notification_delivery_info_receiver">snmpa_notification_delivery_info_receiver</seeerl>
for more info.</p>
<p>Own Id: OTP-7287</p>
<p>Aux Id: Seq 10926</p>
@@ -1261,8 +1261,8 @@
<item>
<p>[agent] Added utility functions,
- <seealso marker="snmpa#restart_worker">snmpa:restart_worker/0,1</seealso> and
- <seealso marker="snmpa#restart_set_worker">snmpa:restart_set_worker/0,1</seealso>,
+ <seeerl marker="snmpa#restart_worker">snmpa:restart_worker/0,1</seeerl> and
+ <seeerl marker="snmpa#restart_set_worker">snmpa:restart_set_worker/0,1</seeerl>,
for restarting the agent worker processes (in case the agent is
multi-threaded).</p>
<p>Own Id: OTP-7369</p>
@@ -1270,7 +1270,7 @@
<item>
<p>Add utility function to
- <seealso marker="snmp#read_mib">read</seealso>
+ <seeerl marker="snmp#read_mib">read</seeerl>
a compiled mib. </p>
<p>Own Id: OTP-7371</p>
</item>
@@ -1325,7 +1325,7 @@
<list type="bulleted">
<item>
<p>[manager] The configuration option
- <seealso marker="snmp_config#manager_irb">inform_request_behaviour</seealso>
+ <seeerl marker="snmp_config#manager_irb">inform_request_behaviour</seeerl>
was not properly parsed, which caused the manager to revert
to the default value, <c>auto</c>. </p>
<p>Own Id: OTP-7219</p>
@@ -1432,9 +1432,9 @@
<item>
<p>Added buffer sizes (both receive and send) of
the udp socket(s) info when calling the
- <seealso marker="snmpa#info">agent info</seealso>
+ <seeerl marker="snmpa#info">agent info</seeerl>
and
- <seealso marker="snmpm#info">manager info</seealso>
+ <seeerl marker="snmpm#info">manager info</seeerl>
function(s). </p>
<p>Own Id: OTP-6945</p>
</item>
@@ -1480,11 +1480,11 @@
<p>The SNMP application contains some (previously undocumented)
simple utility functions for function tracing (using dbg),
see
- <seealso marker="snmp#enable_trace">enable_trace</seealso>,
- <seealso marker="snmp#disable_trace">disable_trace</seealso>,
- <seealso marker="snmp#set_trace1">set_trace/1</seealso>,
- <seealso marker="snmp#reset_trace">reset_trace</seealso> and
- <seealso marker="snmp#set_trace2">set_trace/2</seealso>
+ <seeerl marker="snmp#enable_trace">enable_trace</seeerl>,
+ <seeerl marker="snmp#disable_trace">disable_trace</seeerl>,
+ <seeerl marker="snmp#set_trace1">set_trace/1</seeerl>,
+ <seeerl marker="snmp#reset_trace">reset_trace</seeerl> and
+ <seeerl marker="snmp#set_trace2">set_trace/2</seeerl>
for more info.
These functions are intended to make it easy to enable tracing on
individual functions.
@@ -1526,9 +1526,9 @@
<item>
<p>Added buffer sizes (both receive and send) of
the udp socket(s) info when calling the
- <seealso marker="snmpa#info">agent info</seealso>
+ <seeerl marker="snmpa#info">agent info</seeerl>
and
- <seealso marker="snmpm#info">manager info</seealso>
+ <seeerl marker="snmpm#info">manager info</seeerl>
function(s). </p>
<p>Own Id: OTP-6945</p>
</item>
@@ -1536,7 +1536,7 @@
<item>
<p>[agent] Added the ability to change the request limit
in run-time, see
- <seealso marker="snmpa#set_request_limit">set_request_limit</seealso>
+ <seeerl marker="snmpa#set_request_limit">set_request_limit</seeerl>
for more info. </p>
<p>Own Id: OTP-6898</p>
</item>
@@ -1544,9 +1544,9 @@
<item>
<p>Added the ability to change the audit trail log type
in run-time (both agent and manager), see function(s)
- <seealso marker="snmpm#set_log_type">[manager] set_log_type</seealso>
+ <seeerl marker="snmpm#set_log_type">[manager] set_log_type</seeerl>
and
- <seealso marker="snmpa#set_log_type">[agent] set_log_type</seealso>
+ <seeerl marker="snmpa#set_log_type">[agent] set_log_type</seeerl>
for more info. </p>
<p>Own Id: OTP-6841</p>
</item>
@@ -1556,9 +1556,9 @@
interface module provided with the application.
The component that actually make the filter decisions is the
network interface filter module. This module must implement the
- <seealso marker="snmpa_network_interface_filter">network interface filter behaviour</seealso>.
+ <seeerl marker="snmpa_network_interface_filter">network interface filter behaviour</seeerl>.
See also the
- <seealso marker="snmp_config#configuration_params">Configuring the application</seealso>
+ <seeerl marker="snmp_config#configuration_params">Configuring the application</seeerl>
chapter of the User's Guide to see how to configure this
feature. </p>
<p>Own Id: OTP-6649</p>
@@ -1825,12 +1825,12 @@
<list type="bulleted">
<item>
<p>Added documentation for
- <seealso marker="snmp#passwd2localized_key">snmp:passwd2localized_key/3</seealso>. </p>
+ <seeerl marker="snmp#passwd2localized_key">snmp:passwd2localized_key/3</seeerl>. </p>
<p>Own Id: OTP-6540</p>
</item>
<item>
<p>The
- <seealso marker="snmp#config">snmp:config/0</seealso>
+ <seeerl marker="snmp#config">snmp:config/0</seeerl>
utility function assumes by default
current working dir as the directory to write config
files. This is a problem since there are two files
@@ -1860,8 +1860,8 @@
<item>
<p>Added utility modules to read, write and update the config
files of the
- <seealso marker="snmpa_conf">agent</seealso> and
- <seealso marker="snmpm_conf">manager</seealso>. </p>
+ <seeerl marker="snmpa_conf">agent</seeerl> and
+ <seeerl marker="snmpm_conf">manager</seeerl>. </p>
<p>Own Id: OTP-6318</p>
</item>
<item>
@@ -2044,7 +2044,7 @@
<item>
<p>Added a config option, <c>sndbuf</c>, for the net_if-module(s).
See
- <seealso marker="snmp_config#configuration_params">Configure the application</seealso>
+ <seeerl marker="snmp_config#configuration_params">Configure the application</seeerl>
for more info (look for manager_net_if_options and
agent_net_if_options). </p>
<p>Own Id: OTP-6137</p>
@@ -2092,7 +2092,7 @@
<list type="bulleted">
<item>
<p>Add another example,
- <seealso marker="snmp_impl_example_manager">ex2</seealso>.
+ <seeguide marker="snmp_impl_example_manager">ex2</seeguide>.
This example is basically a simple manager module. </p>
<p>Own Id: OTP-6042</p>
</item>
@@ -2178,13 +2178,13 @@
will result in a decode failure (a catched function
clause), which in turn will be passed on to the user,
via a call to the
- <seealso marker="snmpm_user#handle_error">handle_error</seealso>
+ <seeerl marker="snmpm_user#handle_error">handle_error</seeerl>
callback function, in the <c>Reason</c> argument.
This has now been changed so that instead, the
<c>Reason</c> argument will get a <c>empty message</c>
value.</p>
<p>See
- <seealso marker="snmpm_user#handle_error">handle_error</seealso>
+ <seeerl marker="snmpm_user#handle_error">handle_error</seeerl>
for more details. </p>
<p>Own Id: OTP-6024</p>
</item>
@@ -2197,7 +2197,7 @@
<item>
<p>[manager] Some error cases are reported to the 'user'
with the ReqId and Reason swapped in the call to the
- <seealso marker="snmpm_user#handle_error">handle_error</seealso>
+ <seeerl marker="snmpm_user#handle_error">handle_error</seeerl>
callback function.</p>
<p>Own Id: OTP-5992</p>
</item>
@@ -2220,13 +2220,13 @@
<list type="bulleted">
<item>
<p>Added new version info print functions,
- <seealso marker="snmp#print_version_info">print_version_info</seealso>.</p>
+ <seeerl marker="snmp#print_version_info">print_version_info</seeerl>.</p>
<p>Own Id: OTP-5968</p>
</item>
<item>
<p>Updated the documentation for the
- <seealso marker="snmpa#log_to_txt">agent</seealso> and
- <seealso marker="snmpm#log_to_txt">manager</seealso>
+ <seeerl marker="snmpa#log_to_txt">agent</seeerl> and
+ <seeerl marker="snmpm#log_to_txt">manager</seeerl>
log_to_txt functions.</p>
<p>Own Id: OTP-5969</p>
</item>
@@ -2261,15 +2261,15 @@
<list type="bulleted">
<item>
<p>Add simple backup mechanism,
- see <seealso marker="snmpa#backup">agent backup</seealso>
- and <seealso marker="snmpm#backup">manager backup</seealso>.</p>
+ see <seeerl marker="snmpa#backup">agent backup</seeerl>
+ and <seeerl marker="snmpm#backup">manager backup</seeerl>.</p>
<p>Own Id: OTP-5870</p>
</item>
<item>
<p>Improve handling of faulty data base files.</p>
<p>Added new agent and manager config option,
<c>db_init_error</c>, see
- <seealso marker="snmp_config">config</seealso> for more
+ <seeerl marker="snmp_config">config</seeerl> for more
info.</p>
<p>Own Id: OTP-5934</p>
<p>Aux Id: Seq 10202</p>
@@ -2278,7 +2278,7 @@
<p>Added possibility to configure restart type for each of
the components (default is permanent for the agent and
transient for the manager).
- See <seealso marker="snmp_config">config</seealso> for more
+ See <seeerl marker="snmp_config">config</seeerl> for more
info.</p>
<p>Own Id: OTP-5935</p>
</item>
@@ -2348,27 +2348,27 @@
It is even possible to start an "empty" snmp
application and start the agent and/or manager
afterwords. See
- <seealso marker="snmp#start_agent">start_agent</seealso> and
- <seealso marker="snmp#start_manager">start_manager</seealso>.</p>
+ <seeerl marker="snmp#start_agent">start_agent</seeerl> and
+ <seeerl marker="snmp#start_manager">start_manager</seeerl>.</p>
<p>Own Id: OTP-5797</p>
<p>Aux Id: Seq 10128</p>
</item>
<item>
<p>[agent] Adding utility function to convert old
application config to current agent config.
- See the <seealso marker="snmpa#convert_config">convert_config</seealso>
+ See the <seeerl marker="snmpa#convert_config">convert_config</seeerl>
for more info.</p>
<p>Own Id: OTP-5787</p>
</item>
<item>
<p>Improved handling of audit trail logs.
- See the <seealso marker="snmp_config">atl_repair</seealso>
+ See the <seeerl marker="snmp_config">atl_repair</seeerl>
config for more info.</p>
<p>Own Id: OTP-5771</p>
</item>
<item>
<p>[manager] Introduced the
- <seealso marker="snmpm#notify_started">notify_started</seealso>
+ <seeerl marker="snmpm#notify_started">notify_started</seeerl>
function used by a <em>client</em> to get a notification
when the manager is started.</p>
<p>Own Id: OTP-5763</p>
@@ -2403,7 +2403,7 @@
<list type="bulleted">
<item>
<p>[manager] Introduced the
- <seealso marker="snmp_config#manager_irb">inform request behaviour</seealso>
+ <seeerl marker="snmp_config#manager_irb">inform request behaviour</seeerl>
configuration option to allow the user to specify
how/when the manager shall acknowledge inform-request's.</p>
<p>Own Id: OTP-5733</p>
@@ -2411,7 +2411,7 @@
<item>
<p>[manager] In order to improve application behaviour,
all callback function (see the
- <seealso marker="snmpm_user">snmpm_user behaviour</seealso>)
+ <seeerl marker="snmpm_user">snmpm_user behaviour</seeerl>)
calls
are now done by spawned processes (and not as previously
by the SNMP manager server process).</p>
@@ -2420,17 +2420,17 @@
<item>
<p>[manager] In order to present the various error
reason's returned by the manager, a
- <seealso marker="snmpm#format_reason">format_reason</seealso>
+ <seeerl marker="snmpm#format_reason">format_reason</seeerl>
function has been added.
The error reasons this function handles are those
returned by the (sync and async) get, get-next,
get-bulk and set-functions
- (see the <seealso marker="snmpm">snmpm</seealso> module)
+ (see the <seeerl marker="snmpm">snmpm</seeerl> module)
as well as the
<c>Reason</c> argument of the
- <seealso marker="snmpm_user#handle_error">handle_error</seealso>
+ <seeerl marker="snmpm_user#handle_error">handle_error</seeerl>
function of the
- <seealso marker="snmpm_user">user callback module</seealso>.</p>
+ <seeerl marker="snmpm_user">user callback module</seeerl>.</p>
<p>Own Id: OTP-5581</p>
<p>Aux Id: Seq 9870</p>
</item>
@@ -2470,10 +2470,10 @@
<list type="bulleted">
<item>
<p>[manager] The
- <seealso marker="snmpm_network_interface">snmpm_network_interface</seealso>
+ <seeerl marker="snmpm_network_interface">snmpm_network_interface</seeerl>
behaviour has been updated (see
- <seealso marker="snmpm_network_interface#inform_response">inform_response</seealso> and
- <seealso marker="snmp_manager_netif">definition of the manager net if</seealso>).</p>
+ <seeerl marker="snmpm_network_interface#inform_response">inform_response</seeerl> and
+ <seeguide marker="snmp_manager_netif">definition of the manager net if</seeguide>).</p>
<p>Own Id: OTP-5733</p>
</item>
</list>
@@ -2495,8 +2495,8 @@
independent of each other. The agent is "permanent"
and the manager "transient". In order to handle
manager crashes a simple
- <seealso marker="snmpm#monitor">monitor</seealso>
- (and <seealso marker="snmpm#demonitor">demonitor</seealso>)
+ <seeerl marker="snmpm#monitor">monitor</seeerl>
+ (and <seeerl marker="snmpm#demonitor">demonitor</seeerl>)
function has been added.</p>
<p>Own Id: OTP-5720</p>
</item>
@@ -2532,10 +2532,10 @@
<list type="bulleted">
<item>
<p>Updated and extended the
- <seealso marker="snmpa#info">agent</seealso>
+ <seeerl marker="snmpa#info">agent</seeerl>
info retrieval function. Also added an info retrieval
function for the
- <seealso marker="snmpm#info">manager</seealso>.</p>
+ <seeerl marker="snmpm#info">manager</seeerl>.</p>
<p>Own Id: OTP-5666</p>
</item>
<item>
@@ -2582,7 +2582,7 @@
<p>[agent] The format of the info returned by the agent
info retrieval function has been changed. The info
can be converted to the old format by calling the
- <seealso marker="snmpa#old_info_format">old_info_format</seealso>).</p>
+ <seeerl marker="snmpa#old_info_format">old_info_format</seeerl>).</p>
<p>function. </p>
<p>Own Id: OTP-5666</p>
</item>
@@ -2664,15 +2664,15 @@
</item>
<item>
<p>[manager] Added a
- <seealso marker="snmpm#unregister_usm_user">unregister_usm_user</seealso> function.</p>
+ <seeerl marker="snmpm#unregister_usm_user">unregister_usm_user</seeerl> function.</p>
<p>Own Id: OTP-5580</p>
<p>Aux Id: Seq 9850</p>
</item>
<item>
<p>[agent] Added new functions to get lists of all tables,
- <seealso marker="snmpa#which_tables">which_tables</seealso>,
+ <seeerl marker="snmpa#which_tables">which_tables</seeerl>,
and variables,
- <seealso marker="snmpa#which_variables">which_variables</seealso>,
+ <seeerl marker="snmpa#which_variables">which_variables</seeerl>,
known to the agent.</p>
<p>Own Id: OTP-5590</p>
</item>
@@ -2712,7 +2712,7 @@
<p>[manager] The <c>snmpm_network_interface</c> behaviour has changed.
One more argument (ExtraInfo) was added to the
function send_pdu (see
- <seealso marker="snmpm_network_interface#send_pdu">send_pdu</seealso>).</p>
+ <seeerl marker="snmpm_network_interface#send_pdu">send_pdu</seeerl>).</p>
<p>Own Id: OTP-5574</p>
<p>Aux Id: Seq 9850</p>
</item>
@@ -2833,9 +2833,9 @@
<item>
<p>[manager] Failed to register usm users. Both using the
usm config file
- (<seealso marker="snmp_manager_config_files#usm_user">usm.conf</seealso>)
+ (<seeguide marker="snmp_manager_config_files#usm_user">usm.conf</seeguide>)
and the API functions
- <seealso marker="snmpm#register_usm_user">register_usm_user</seealso></p>
+ <seeerl marker="snmpm#register_usm_user">register_usm_user</seeerl></p>
<p>Own Id: OTP-5499</p>
<p>Aux Id: Seq 9804</p>
</item>
@@ -2921,7 +2921,7 @@
</item>
<item>
<p>[agent] Documented instrumentation utility functions
- (e.g. <seealso marker="snmpa#current_request_id">current_request_id</seealso>).</p>
+ (e.g. <seeerl marker="snmpa#current_request_id">current_request_id</seeerl>).</p>
<p>Own Id: OTP-5423</p>
</item>
</list>
@@ -2972,17 +2972,17 @@
<item>
<p>[manager] Missing interface functions for loading and
unloading mibs into/from the manager:
- <seealso marker="snmpm#load_mib">load_mib</seealso>,
- <seealso marker="snmpm#unload_mib">unload_mib</seealso>,
- <seealso marker="snmpm#which_mibs">which_mibs</seealso>,
- <seealso marker="snmpm#name_to_oid">name_to_oid</seealso> and
- <seealso marker="snmpm#oid_to_name">oid_to_name</seealso>.</p>
+ <seeerl marker="snmpm#load_mib">load_mib</seeerl>,
+ <seeerl marker="snmpm#unload_mib">unload_mib</seeerl>,
+ <seeerl marker="snmpm#which_mibs">which_mibs</seeerl>,
+ <seeerl marker="snmpm#name_to_oid">name_to_oid</seeerl> and
+ <seeerl marker="snmpm#oid_to_name">oid_to_name</seeerl>.</p>
<p>Own Id: OTP-5441</p>
</item>
<item>
<p>Added utility functions to retrieve some system and application
- info, see <seealso marker="snmp#versions1">versions1</seealso> and
- <seealso marker="snmp#versions2">versions2</seealso>.</p>
+ info, see <seeerl marker="snmp#versions1">versions1</seeerl> and
+ <seeerl marker="snmp#versions2">versions2</seeerl>.</p>
<p>Own Id: OTP-5445</p>
</item>
</list>
@@ -3049,14 +3049,14 @@
<list type="bulleted">
<item>
<p>[manager] Added possibility to monitor a registered user.
- See <seealso marker="snmpm#register_user_monitor">snmpm:register_user_monitor</seealso>.</p>
+ See <seeerl marker="snmpm#register_user_monitor">snmpm:register_user_monitor</seeerl>.</p>
<p>Own Id: OTP-5286</p>
</item>
<item>
<p>[agent] Improved symbolic store. Alias and Oids where stored
with similar key's (separated by types: atom() and
lists() respectively). Also added new function:
- <seealso marker="snmpa#which_aliasnames">snmpa:which_aliasnames</seealso>.</p>
+ <seeerl marker="snmpa#which_aliasnames">snmpa:which_aliasnames</seeerl>.</p>
<p>Own Id: OTP-5298</p>
</item>
<item>
@@ -3080,17 +3080,17 @@
</item>
<item>
<p>[agent] Unclear documentation for function
- <seealso marker="snmpa#send_notification">snmpa:send_notification</seealso>. The Recv argument
+ <seeerl marker="snmpa#send_notification">snmpa:send_notification</seeerl>. The Recv argument
(specifically the {M,F,A} variant).</p>
<p>Own Id: OTP-5281</p>
</item>
<item>
<p>[manager] It was never documented how the default
- <seealso marker="snmpm_user">user behaviour</seealso>
+ <seeerl marker="snmpm_user">user behaviour</seeerl>
could be overridden (default user is the module
<c>snmpm_user_default</c>).
See
- <seealso marker="snmp_config#configuration_params">configuration params</seealso>.</p>
+ <seeerl marker="snmp_config#configuration_params">configuration params</seeerl>.</p>
<p>Own Id: OTP-5299</p>
</item>
<item>
@@ -3103,7 +3103,7 @@
<item>
<p>[agent] The agent config file, target_addr.conf, was
incorrectly described in the
- <seealso marker="snmp_agent_config_files#target_addr">Target Address Definitions</seealso> chapter of the
+ <seeguide marker="snmp_agent_config_files#target_addr">Target Address Definitions</seeguide> chapter of the
User's Guide. The EngineId option was left out.</p>
<p>Own Id: OTP-5307</p>
<p>Aux Id: Seq 9689</p>
@@ -3208,7 +3208,7 @@
snmpm:s), and if the request
was asynchronous, the new callback function,
handle_error
- (see <seealso marker="snmpm_user">snmpm_user</seealso>) is
+ (see <seeerl marker="snmpm_user">snmpm_user</seeerl>) is
called. </p>
<p>Own Id: OTP-5242</p>
</item>
@@ -3236,7 +3236,7 @@
<list type="bulleted">
<item>
<p>[manager] Introduced a new callback function in the behaviour
- <seealso marker="snmpm_user">snmpm_user</seealso>.</p>
+ <seeerl marker="snmpm_user">snmpm_user</seeerl>.</p>
<p>Own Id: OTP-5242</p>
</item>
</list>
@@ -3254,9 +3254,9 @@
<item>
<p>[agent] Added functions to get a list of all mibs loaded into
an agent
- (see <seealso marker="snmpa#which_mibs">snmpa:which_mibs</seealso>)
+ (see <seeerl marker="snmpa#which_mibs">snmpa:which_mibs</seeerl>)
and to get the (full path) file name of a loaded mib (see
- <seealso marker="snmpa#whereis_mib">snmpa:whereis_mib</seealso>).</p>
+ <seeerl marker="snmpa#whereis_mib">snmpa:whereis_mib</seeerl>).</p>
<p>Own Id: OTP-5187</p>
</item>
</list>
@@ -3294,23 +3294,23 @@
<item>
<p>[agent] Add a <c>snmpa:get/3</c> with an extra <c>Context</c>
argument. Also added similar <c>snmpa:get_next/2,3</c> functions.
- See <seealso marker="snmpa#get">snmpa:get</seealso> and
- <seealso marker="snmpa#get_next">snmpa:get_next</seealso>. </p>
+ See <seeerl marker="snmpa#get">snmpa:get</seeerl> and
+ <seeerl marker="snmpa#get_next">snmpa:get_next</seeerl>. </p>
<p>Martin Bj&ouml;rklund</p>
<p>Own Id: OTP-5054</p>
</item>
<item>
<p>[agent] Add <em>notification filters</em>. See
- <seealso marker="snmpa_notification_filter">snmpa_notification_filter</seealso>,
- <seealso marker="snmpa#register_notification_filter">register_notification_filter</seealso>,
- <seealso marker="snmpa#unregister_notification_filter">unregister_notification_filter</seealso> and
- <seealso marker="snmpa#which_notification_filter">which_notification_filter</seealso>.</p>
+ <seeerl marker="snmpa_notification_filter">snmpa_notification_filter</seeerl>,
+ <seeerl marker="snmpa#register_notification_filter">register_notification_filter</seeerl>,
+ <seeerl marker="snmpa#unregister_notification_filter">unregister_notification_filter</seeerl> and
+ <seeerl marker="snmpa#which_notification_filter">which_notification_filter</seeerl>.</p>
<p>Own Id: OTP-5055</p>
</item>
<item>
<p>[agent] Added two mib look-up functions,
- <seealso marker="snmpa#me_of">me_of</seealso> and
- <seealso marker="snmpa#mib_of">mib_of</seealso>.</p>
+ <seeerl marker="snmpa#me_of">me_of</seeerl> and
+ <seeerl marker="snmpa#mib_of">mib_of</seeerl>.</p>
<p>Own Id: OTP-5082</p>
<p>Aux Id: Seq 8848</p>
</item>
@@ -3327,17 +3327,17 @@
<p>[compiler] Added compiler options <c>imports</c> and
<c>module_identity</c> to include the imports list and
module identity (only SMIv2) info in the compiled mib,
- see <seealso marker="snmpc">snmpc</seealso>.</p>
+ see <seeerl marker="snmpc">snmpc</seeerl>.</p>
</item>
<item>
<p>[compiler] Added the MIB compiler option
- <c>+no_defs</c>, see <seealso marker="snmpc">snmpc</seealso>.</p>
+ <c>+no_defs</c>, see <seeerl marker="snmpc">snmpc</seeerl>.</p>
<p>Martin Bj&ouml;rklund</p>
</item>
<item>
<p>[compiler] Added the MIB compiler option
<c>+'{module, atom()}'</c>,
- see <seealso marker="snmpc">snmpc</seealso>. </p>
+ see <seeerl marker="snmpc">snmpc</seeerl>. </p>
<p>Martin Bj&ouml;rklund</p>
</item>
</list>
@@ -3377,7 +3377,7 @@
longer documented and will eventually be
eliminated.
See
- <seealso marker="snmp_config#configuration_params">configuration params</seealso> for more info.</p>
+ <seeerl marker="snmp_config#configuration_params">configuration params</seeerl> for more info.</p>
</item>
<item>
<p>Three new interface modules have been introduced. One
@@ -3404,7 +3404,7 @@
</item>
<item>
<p>[agent] The agent network interface API has changed.
- See <seealso marker="snmp_agent_netif">snmp agent net if</seealso>,</p>
+ See <seeguide marker="snmp_agent_netif">snmp agent net if</seeguide>,</p>
</item>
<item>
<p>[agent] The default agent audit trail log name has changed.</p>
diff --git a/lib/snmp/doc/src/snmp.xml b/lib/snmp/doc/src/snmp.xml
index d20f1a8d06..9219e58ab1 100644
--- a/lib/snmp/doc/src/snmp.xml
+++ b/lib/snmp/doc/src/snmp.xml
@@ -45,7 +45,7 @@
<list type="bulleted">
<item>
<p><c>datetime() = {date(), time()}</c></p>
- <p>See <seealso marker="stdlib:calendar">calendar</seealso>
+ <p>See <seeerl marker="stdlib:calendar">calendar</seeerl>
for more info.</p>
</item>
@@ -86,7 +86,7 @@
</type>
<desc>
<p>Starts the SNMP application.</p>
- <p>See <seealso marker="kernel:application">application</seealso> for more info.</p>
+ <p>See <seeerl marker="kernel:application">application</seeerl> for more info.</p>
<marker id="start_agent"></marker>
</desc>
@@ -186,7 +186,7 @@
validation of the <c>DateAndTime</c> argument. Whenever the data
is found to not follow RFC2579, the fun is called to allow a more
"lax" validation.
- See the <seealso marker="#vdat">validate_date_and_time/2</seealso>
+ See the <seeerl marker="#vdat">validate_date_and_time/2</seeerl>
function for more info on the <c>Validate</c> fun. </p>
<marker id="dat2s2"></marker>
@@ -432,7 +432,7 @@
<desc>
<p>Converts an Audit Trail Log to a readable format and
prints it on stdio. See
- <seealso marker="snmp#log_to_txt">log_to_txt</seealso>
+ <seeerl marker="snmp#log_to_txt">log_to_txt</seeerl>
above for more info.</p>
<marker id="change_log_size"></marker>
diff --git a/lib/snmp/doc/src/snmp_agent_config_files.xml b/lib/snmp/doc/src/snmp_agent_config_files.xml
index e1a4c143cd..8456b84952 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>2016</year>
+ <year>1997</year><year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -40,45 +40,45 @@
SNMP-MPD-MIB, SNMP-VIEW-BASED-ACM-MIB, SNMP-COMMUNITY-MIB,
SNMP-USER-BASED-SM-MIB, SNMP-TARGET-MIB and SNMP-NOTIFICATION-MIB
(refer to the
- <seealso marker="snmp_agent_funct_descr#management">Management of the Agent</seealso>
+ <seeguide marker="snmp_agent_funct_descr#management">Management of the Agent</seeguide>
for a description of the MIBs). </p>
<p>The files are: </p>
<list type="bulleted">
<item>
<p><c>agent.conf</c>: see
- <seealso marker="#agent_information">Agent Information</seealso></p>
+ <seeguide marker="#agent_information">Agent Information</seeguide></p>
</item>
<item>
<p><c>standard.conf</c>: see
- <seealso marker="#system_information">System Information</seealso></p>
+ <seeguide marker="#system_information">System Information</seeguide></p>
</item>
<item>
<p><c>context.conf</c>: see
- <seealso marker="#context">Contexts</seealso></p>
+ <seeguide marker="#context">Contexts</seeguide></p>
</item>
<item>
<p><c>community.conf</c>: see
- <seealso marker="#community">Communities</seealso></p>
+ <seeguide marker="#community">Communities</seeguide></p>
</item>
<item>
<p><c>target_addr.conf</c>: see
- <seealso marker="#target_addr">Target Address Definitions</seealso></p>
+ <seeguide marker="#target_addr">Target Address Definitions</seeguide></p>
</item>
<item>
<p><c>target_params.conf</c>: see
- <seealso marker="#target_params">Target Parameters Definitions</seealso></p>
+ <seeguide marker="#target_params">Target Parameters Definitions</seeguide></p>
</item>
<item>
<p><c>vacm.conf</c>: see
- <seealso marker="#vacm">MIB Views for VACM</seealso></p>
+ <seeguide marker="#vacm">MIB Views for VACM</seeguide></p>
</item>
<item>
<p><c>usm.conf</c>: see
- <seealso marker="#usm">Security data for USM</seealso></p>
+ <seeguide marker="#usm">Security data for USM</seeguide></p>
</item>
<item>
<p><c>notify.conf</c>: see
- <seealso marker="#notify">Notify Definitions</seealso></p>
+ <seeguide marker="#notify">Notify Definitions</seeguide></p>
</item>
</list>
<p>The directory where the configuration files are found is given as
@@ -100,7 +100,7 @@
<p><c>{AgentVariable, Value}.</c></p>
<list type="bulleted">
<item>
- <p><c>AgentVariable</c> is one of the variables is
+ <p><c>AgentVariable</c> is one of the variables in
SNMP-FRAMEWORK-MIB or one of the internal variables
<c>intAgentUDPPort</c>, which defines which UDP port the agent
listens to, or <c>intAgentTransports</c>, which defines the
@@ -119,29 +119,79 @@
{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,
+ <p>These are the supported entries and their value types: </p>
+
+ <pre>
+ {snmpEngine, string()}. % Mandatory
+ {snmpEngineMaxMessageSize, non_neg_integer()}. % Mandatory
+ {intAgentUDPPort, pos_integer()}. % Optional
+ {intAgentTransports, intAgentTransports()}. % Mandatory
+ </pre>
+
+ <p>And here are the transport value types: </p>
+
+ <pre>
+ intAgentTransports() :: [intAgentTransport()].
+ intAgentTransport() :: {TDomain, Addr} |
+ {TDomain, EAddr, Kind} |
+ {TDomain, EAddr, Opts} |
+ {TDomain, EAddr, Kind, Opts}
+ TDomain :: transportDomainUdpIpv4 | transportDomainUdpIpv6.
+ Addr :: {IpAddr, IpPort} | IpAddr.
+ IpAddr :: <seetype marker="kernel:inet#ip_address"><c>inet:ip_address()</c></seetype> | snmpIpAddr().
+ snmpIpAddr() :: [non_neg_integer()].
+ IpPort :: pos_integer().
+ EAddr :: {<seetype marker="kernel:inet#ip_address"><c>inet:ip_address()</c></seetype>, PortInfo}.
+ PortInfo :: pos_integer() | system | range() | ranges().
+ range() :: {Min :: pos_integer(), Max :: pos_integer()}, Min &lt; Max
+ ranges() :: [pos_integer() | range()].
+ Kind :: req_responder | trap_sender.
+ Opts :: list().
+ </pre>
+
+ <p>If a "traditional" transport is specified (without explicit <c>Kind</c>,
+ handling both requests and traps) for a transport domain, its <em>not</em>
+ possible to also specify a transport (for that domain) with a specific
+ <c>Kind</c>. This is for example <em>not</em> allowed:</p>
+
+ <pre>
+ [{transportDomainUdpIpv4, {{141,213,11,24}, 4000}},
+ {transportDomainUdpIpv4, {{141,213,11,24}, 4001}, trap_sender}].
+ </pre>
+
+ <p>Note that only one transport per kind for each transport domain
+ can be configured. </p>
+
+ <p><c>PortInfo</c> <c>system</c> is used to indicate that the 'system'
+ should choose (the way port number '0' (zero) is normally used).
+ Port info '0' (zero) cannot be used for this, since it is (internally) used
+ to represent the 'default' port number. </p>
+
+ <p>In the traditional transport entries, 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>Note that the (new) extended transport entries (including <c>Kind</c>
+ and <c>Opts</c>) <em>must</em> specify port-info as they ignore
+ any value specified by <c>intAgentUDPPort</c>.</p>
+ <p><c>Opts</c> is the same as for the
+ <seeguide marker="snmp_config#agent_ni_opts">net-if</seeguide>
+ process <em>and</em>
+ takes precedence (for that transport) if present.
+ The point is that each transport can have its own socket options. </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>
+ <note><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>But they <em>cannot</em> be combined with intAgentTransports.</p>
+ </note>
+
<marker id="context"></marker>
</section>
@@ -399,7 +449,7 @@
<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>
+ <seetype marker="kernel:inet#ip_address"><c>ip_address()</c></seetype>
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>
diff --git a/lib/snmp/doc/src/snmp_agent_funct_descr.xml b/lib/snmp/doc/src/snmp_agent_funct_descr.xml
index fdcab79646..4fe0ae87ca 100644
--- a/lib/snmp/doc/src/snmp_agent_funct_descr.xml
+++ b/lib/snmp/doc/src/snmp_agent_funct_descr.xml
@@ -205,12 +205,12 @@
<icaption>MIB Compiler Principles</icaption>
</image>
<p>The compiler parses the SMI file and associates each table or
- variable with an instrumentation function (see the figure <seealso marker="#image-1">MIB Compiler Principles</seealso>). The actual
- instrumentation functions are not needed at MIB compile time, only
+ variable with an instrumentation function (see the figure <seeguide marker="#image-1">MIB Compiler Principles</seeguide>).
+ The actual instrumentation functions are not needed at MIB compile time, only
their names.
</p>
<p>The binary output file produced by the compiler is read by the
- agent at MIB load time (see the figure <seealso marker="#image-2">Starting the Agent</seealso>). The instrumentation is ordinary Erlang code which
+ agent at MIB load time (see the figure <seeguide marker="#image-2">Starting the Agent</seeguide>). The instrumentation is ordinary Erlang code which
is loaded explicitly or automatically the first time it is called.</p>
<marker id="image-2"></marker>
<image file="snmp-um-1-image-2.gif">
@@ -303,7 +303,7 @@
<item>to provide interaction with other SNMP agent toolkits.</item>
</list>
<p>Refer to the chapter
- <seealso marker="snmp_advanced_agent">Advanced Agent Topics</seealso>
+ <seeguide marker="snmp_advanced_agent">Advanced Agent Topics</seeguide>
in this User's Guide for more information about these topics.
</p>
<p>The communication protocol between sub-agents is the normal
@@ -374,7 +374,7 @@
than the default context, <c>""</c>. If it is to support more
contexts, these must be explicitly added, by using an appropriate
configuration file
- <seealso marker="snmp_agent_config_files">Agent Configuration Files</seealso>.
+ <seeguide marker="snmp_agent_config_files">Agent Configuration Files</seeguide>.
</p>
</section>
@@ -457,7 +457,7 @@
</code>
<p>The initial values for the managed objects defined in these
tables, are read at start-up from a set of configuration files.
- These are described in <seealso marker="snmp_config">Configuration Files</seealso>.
+ These are described in <seeguide marker="snmp_config">Configuration Files</seeguide>.
</p>
<section>
@@ -478,7 +478,7 @@
The new compiled MIB
must have the same name as the original MIB (i.e. STANDARD-MIB
or SNMPv2-MIB), and be located in the SNMP configuration
- directory (see <seealso marker="snmp_config">Configuration Files</seealso>.)
+ directory (see <seeguide marker="snmp_config">Configuration Files</seeguide>.)
</p>
<p>One of these MIBs is always loaded. If only SNMPv1 is used,
STANDARD-MIB is loaded, otherwise SNMPv2-MIB is loaded.
@@ -891,9 +891,9 @@ snmpa:send_trap(Agent, Notification, Community [, Receiver, Varbinds])
sent. Their purpose is to allow modification, suppression or
other type of actions.</p>
<p>A notification filter is a module implementing the
- <seealso marker="snmpa_notification_filter">snmpa_notification_filter</seealso> behaviour. A filter is added/deleted using the functions:
- <seealso marker="snmpa#register_notification_filter">snmpa:register_notification_filter</seealso> and
- <seealso marker="snmpa#unregister_notification_filter">snmpa:unregister_notification_filter</seealso>.</p>
+ <seeerl marker="snmpa_notification_filter">snmpa_notification_filter</seeerl> behaviour. A filter is added/deleted using the functions:
+ <seeerl marker="snmpa#register_notification_filter">snmpa:register_notification_filter</seeerl> and
+ <seeerl marker="snmpa#unregister_notification_filter">snmpa:unregister_notification_filter</seeerl>.</p>
<p>Unless otherwise specified, the order of the registered filters
will be the order in which they are registered.</p>
</section>
@@ -935,9 +935,9 @@ snmpa:send_trap(Agent, Notification, Community [, Receiver, Varbinds])
<p>The agent responds to discovery autonomously, without interaction
by the user. </p>
<p>Initiating discovery towards a manager is done by calling the
- <seealso marker="snmpa#discovery">discovery</seealso> function.
+ <seeerl marker="snmpa#discovery">discovery</seeerl> function.
The <c>EngineId</c> field of the target (manager) entry in the
- <seealso marker="snmp_agent_config_files#target_addr">target_addr.conf</seealso> file has to have the value <c>discovery</c>.
+ <seeguide marker="snmp_agent_config_files#target_addr">target_addr.conf</seeguide> file has to have the value <c>discovery</c>.
Note that if the manager does not respond, the <c>Timeout</c> and
<c>RetryCount</c>
fields decide how long the function will hang before it returns. </p>
diff --git a/lib/snmp/doc/src/snmp_agent_netif.xml b/lib/snmp/doc/src/snmp_agent_netif.xml
index bee557a212..e3ea60a8d0 100644
--- a/lib/snmp/doc/src/snmp_agent_netif.xml
+++ b/lib/snmp/doc/src/snmp_agent_netif.xml
@@ -62,7 +62,7 @@
<marker id="mandatory_functions"></marker>
<title>Mandatory Functions</title>
<p>A Net if process must implement the SNMP agent
- <seealso marker="snmpa_network_interface">network interface behaviour</seealso>.
+ <seeerl marker="snmpa_network_interface">network interface behaviour</seeerl>.
</p>
</section>
@@ -76,7 +76,7 @@
<c>{Domain, Addr}</c> tuple where <c>Domain</c> is
<c>transportDomainUdpIpv4</c> or <c>transportDomainUdpIpv4</c>,
and <c>Addr</c> is an
- <c>{</c><seealso marker="kernel:inet#type-ip_address"><c>IpAddr</c></seealso><c>,IpPort}</c> tuple.</p>
+ <c>{</c><seetype marker="kernel:inet#ip_address"><c>IpAddr</c></seetype><c>,IpPort}</c> tuple.</p>
<section>
<marker id="outgoing_messages"></marker>
@@ -124,7 +124,7 @@ Pid ! {snmp_response_received, Vsn, Pdu, From}
<item><c>Pid</c> is the Process that waits for the response
for the request. The Pid was specified in the
<c>send_pdu_req</c> message
- <seealso marker="#im_send_pdu_req">(see below)</seealso>.
+ <seeguide marker="#im_send_pdu_req">(see below)</seeguide>.
</item>
<item><c>Vsn</c> is either <c>'version-1'</c>, <c>'version-2'</c>, or
<c>'version-3'</c>.
@@ -239,8 +239,8 @@ Pid ! {snmp_response_received, Vsn, Pdu, From}
<p><c>Extra</c> is any term that the notification sender
wishes to pass to the Net if process when sending a notification
(see
- <seealso marker="snmpa#send_notification2">send notification
- </seealso> for more info). </p>
+ <seeerl marker="snmpa#send_notification2">send notification
+ </seeerl> for more info). </p>
</item>
</list>
</item>
@@ -281,8 +281,8 @@ Pid ! {snmp_response_received, Vsn, Pdu, From}
<p><c>Extra</c> is any term that the notification sender
wishes to pass to the Net if process when sending a notification
(see
- <seealso marker="snmpa#send_notification2">send notification
- </seealso> for more info). </p>
+ <seeerl marker="snmpa#send_notification2">send notification
+ </seeerl> for more info). </p>
</item>
</list>
</item>
@@ -296,12 +296,12 @@ Pid ! {snmp_response_received, Vsn, Pdu, From}
counters in the SNMP group in MIB-II. It can use the functions
in the module <c>snmpa_mpd</c> for this purpose (refer to the
Reference Manual, section <c>snmp</c>,
- module <seealso marker="snmp_pdus">snmpa_mpd</seealso>
+ module <seeerl marker="snmp_pdus">snmpa_mpd</seeerl>
for more details.)
</p>
<p>There are also some useful functions for encoding and
decoding of SNMP messages in the module
- <seealso marker="snmp_pdus">snmp_pdus</seealso>.
+ <seeerl marker="snmp_pdus">snmp_pdus</seeerl>.
</p>
</section>
</section>
diff --git a/lib/snmp/doc/src/snmp_app.xml b/lib/snmp/doc/src/snmp_app.xml
index 978aff59b1..361781cb61 100644
--- a/lib/snmp/doc/src/snmp_app.xml
+++ b/lib/snmp/doc/src/snmp_app.xml
@@ -52,7 +52,7 @@
<!--
-There are achors (marker id) in this file, but do **NOT** link to anything
+There are anchors (marker id) in this file, but do **NOT** link to anything
in this file!! Because of the title is in capital letters, the generated
html file will also be in capital letters.
This should not be a problem since almost everything here is duplicated
@@ -188,22 +188,46 @@ in the snmp_config file!
<tag><marker id="agent_orig_disco_opts"></marker>
<c><![CDATA[agent_originating_discovery_opts() = [agent_originating_discovery_opt()] <optional>]]></c></tag>
<item>
- <p><c>agent_originating_discovery_opt() =
- {enable, boolean()}</c></p>
- <p>These are options effecting discovery <c>originating</c> in this
- agent. </p>
- <p>The default values for the <c>originating</c>
- discovery options are: </p>
- <list type="bulleted">
- <item>enable: <c>true</c></item>
+ <p><c>agent_originating_discovery_opt() = {enable, boolean()}</c></p>
+ <p>These are options effecting discovery <c>originating</c> in this
+ agent. </p>
+ <p>The default values for the <c>originating</c>
+ discovery options are: </p>
+ <list type="bulleted">
+ <item>enable: <c>true</c></item>
</list>
</item>
<tag><marker id="agent_mt"></marker>
- <c><![CDATA[multi_threaded() = bool() <optional>]]></c></tag>
+ <c><![CDATA[multi_threaded() = bool() | extended <optional>]]></c></tag>
<item>
- <p>If <c>true</c>, the agent is multi-threaded, with one
- thread for each get request. </p>
+ <p>If <c>true</c> (or <c>extended</c>), the agent is multi-threaded,
+ with one thread for each get request.</p>
+ <p>The value <c>extended</c> means that a special 'process'
+ is also created intended to handle <em>all</em> notifications. </p>
+ <list>
+ <item>
+ <p><c>true</c> - One worker dedicated to 'set-requests' and one
+ (main) worker for all other requests ('get-request' and
+ notifications).</p>
+ <p>If the 'main' worker is busy, a temporary process is
+ spawned to handle that job ('get-request' or notification). </p>
+ </item>
+ <item>
+ <p><c>extended</c> - One worker dedicated to 'set-requests',
+ one worker dedicated to notifications and one
+ (main) worker for all 'get-requests'. </p>
+ <p>If the 'main' worker is busy, a temporary process is
+ spawned to handle that 'get-request'. </p>
+ </item>
+ </list>
+ <note>
+ <p>Even with multi-threaded set to <c>extended</c>
+ there is still a risk for 'reorder' when sending inform-requsts,
+ which require a response (and may therefor require resending). </p>
+ <p>Also, there is of course no way to guarantee order once the
+ package is on the network. </p>
+ </note>
<p>Default is <c>false</c>.</p>
</item>
@@ -263,7 +287,7 @@ in the snmp_config file!
<item>
<p>Module which handles the network interface part for the
SNMP agent. Must implement the
- <seealso marker="snmpa_network_interface">snmpa_network_interface</seealso> behaviour.</p>
+ <seeerl marker="snmpa_network_interface">snmpa_network_interface</seeerl> behaviour.</p>
<p>Default is <c>snmpa_net_if</c>.</p>
</item>
@@ -271,16 +295,23 @@ in the snmp_config file!
<c><![CDATA[agent_net_if_options() = [agent_net_if_option()] <optional>]]></c></tag>
<item>
<p><c>agent_net_if_option() = {bind_to, bind_to()} |
- {sndbuf, sndbuf()} |
- {recbuf, recbuf()} |
- {no_reuse, no_reuse()} |
- {req_limit, req_limit()} |
- {filter, agent_net_if_filter_options()} |
- {extra_sock_opts, extra_socket_options()}</c></p>
- <p>These options are actually specific to the used module.
- The ones shown here are applicable to the default
- <c>agent_net_if_module()</c>.</p>
- <p>For defaults see the options in <c>agent_net_if_option()</c>.</p>
+ {sndbuf, sndbuf()} |
+ {recbuf, recbuf()} |
+ {no_reuse, no_reuse()} |
+ {req_limit, req_limit()} |
+ {filter, agent_net_if_filter_options()} |
+ {extra_sock_opts, extra_socket_options()}</c></p>
+ <p>These options are actually specific to the used module.
+ The ones shown here are applicable to the default
+ <c>agent_net_if_module()</c>.</p>
+ <note>
+ <p>If the user has configured transports <em>with</em> options
+ then those will take precedence over these options.
+ See
+ <seeguide marker="snmp_agent_config_files#agent_information">agent information</seeguide>
+ for more info. </p>
+ </note>
+ <p>For defaults see the options in <c>agent_net_if_option()</c>.</p>
</item>
<tag><marker id="agent_ni_req_limit"></marker>
@@ -306,7 +337,7 @@ in the snmp_config file!
<item>
<p>Module which handles the network interface filter part for the
SNMP agent. Must implement the
- <seealso marker="snmpa_network_interface_filter">snmpa_network_interface_filter</seealso> behaviour.</p>
+ <seeerl marker="snmpa_network_interface_filter">snmpa_network_interface_filter</seeerl> behaviour.</p>
<p>Default is <c>snmpa_net_if_filter</c>.</p>
</item>
@@ -339,7 +370,7 @@ in the snmp_config file!
<c><![CDATA[mib_storage_module() = snmpa_mib_data_ets | snmpa_mib_data_dets | snmpa_mib_data_mnesia | module()]]></c></tag>
<item>
<p>Defines the mib storage module of the SNMP agent as defined by the
- <seealso marker="snmpa_mib_storage">snmpa_mib_storage</seealso>
+ <seeerl marker="snmpa_mib_storage">snmpa_mib_storage</seeerl>
behaviour. </p>
<p>Several entities (<c>mib-server</c> via the its data module and
the <c>symbolic-store</c>) of the snmp agent uses this for storage
@@ -477,7 +508,7 @@ in the snmp_config file!
<item>
<p>Defines the backend data module of the SNMP agent mib-server as
defined by the
- <seealso marker="snmpa_mib_data">snmpa_mib_data</seealso>
+ <seeerl marker="snmpa_mib_data">snmpa_mib_data</seeerl>
behaviour. </p>
<p>At present only the default module is provided with the agent,
<c>snmpa_mib_data_tttn</c>. </p>
@@ -511,7 +542,7 @@ in the snmp_config file!
<item>
<p>Defines if the mib server shall perform cache gc automatically or
leave it to the user (see
- <seealso marker="snmpa#gc_mibs_cache">gc_mibs_cache/0,1,2,3</seealso>). </p>
+ <seeerl marker="snmpa#gc_mibs_cache">gc_mibs_cache/0,1,2,3</seeerl>). </p>
<p>Default is <c>true</c>.</p>
</item>
@@ -526,21 +557,27 @@ in the snmp_config file!
</item>
<tag><marker id="agent_ms_cache_gclimit"></marker>
- <c><![CDATA[mibs_cache_gclimit() = integer() > 0 | infinity <optional>]]></c></tag>
+ <c><![CDATA[mibs_cache_gclimit() = infinity | integer() > 0 <optional>]]></c></tag>
<item>
<p>When performing a GC, this is the max number of cache entries
that will be deleted from the cache. </p>
- <p>The reason for having this limit is that if the cache is
+
+ <p>The reason why its possible to set a limit, is that if the cache is
large, the GC can potentially take a long time, during which
- the agent is locked. </p>
- <p>Default is <c>100</c>.</p>
+ the agent is "busy".
+ <em>But</em> on a heavily loaded system, we also risk not removing
+ enough elements in the cache, instead causing it to grow over time.
+ This is the reason the default value is <c>infinity</c>, which will
+ ensure that <em>all</em> candidates are removed as soon as possible. </p>
+
+ <p>Default is <c>infinity</c>.</p>
</item>
<tag><marker id="agent_error_report_mod"></marker>
<c><![CDATA[error_report_mod() = atom() <optional>]]></c></tag>
<item>
<p>Defines an error report module, implementing the
- <seealso marker="snmpa_error_report">snmpa_error_report</seealso>
+ <seeerl marker="snmpa_error_report">snmpa_error_report</seeerl>
behaviour. Two modules are provided with the toolkit:
<c>snmpa_error_logger</c> and <c>snmpa_error_io</c>.</p>
<p>Default is <c>snmpa_error_logger</c>.</p>
@@ -613,9 +650,9 @@ in the snmp_config file!
The information will have an <em>best before</em> time,
defined by the <c>Expire</c> time given when calling the
request function (see
- <seealso marker="snmpm#async_get">async_get</seealso>,
- <seealso marker="snmpm#async_get_next">async_get_next</seealso> and
- <seealso marker="snmpm#async_set">async_set</seealso>).</p>
+ <seeerl marker="snmpm#async_get2">async_get</seeerl>,
+ <seeerl marker="snmpm#async_get_next2">async_get_next</seeerl> and
+ <seeerl marker="snmpm#async_set2">async_set</seeerl>).</p>
<p>Time in milli-seconds.</p>
<p>Default is <c>30000</c>.</p>
</item>
@@ -648,7 +685,7 @@ in the snmp_config file!
net-if process.
Note that this will only work if the used net-if process actually supports
the protocol. See
- <seealso marker="snmpm_network_interface">snmpm_network_interface</seealso> behaviour for more info. </p>
+ <seeerl marker="snmpm_network_interface">snmpm_network_interface</seeerl> behaviour for more info. </p>
<taglist>
<tag><marker id="manager_server_nis_none"></marker>
<c><![CDATA[none (default)]]></c></tag>
@@ -720,7 +757,7 @@ in the snmp_config file!
<item>
<p><c>{user, integer()}</c> - The manager will send response
(acknowledgment) to inform-request messages when the
- <seealso marker="snmpm_user#handle_inform">handle_inform</seealso>
+ <seeerl marker="snmpm_user#handle_inform">handle_inform</seeerl>
function completes. The integer is the time, in milli-seconds,
that the manager will consider the stored inform-request info
valid.</p>
@@ -731,9 +768,9 @@ in the snmp_config file!
</item>
</list>
<p>See
- <seealso marker="snmpm_network_interface">snmpm_network_interface</seealso>,
- <seealso marker="snmpm_user">handle_inform</seealso> and
- <seealso marker="snmp_manager_netif">definition of the manager net if</seealso> for more info.</p>
+ <seeerl marker="snmpm_network_interface">snmpm_network_interface</seeerl>,
+ <seeerl marker="snmpm_user">handle_inform</seeerl> and
+ <seeguide marker="snmp_manager_netif">definition of the manager net if</seeguide> for more info.</p>
<p>Default is <c>auto</c>.</p>
</item>
@@ -776,7 +813,7 @@ in the snmp_config file!
<item>
<p>The module which handles the network interface part for the
SNMP manager. It must implement the
- <seealso marker="snmpm_network_interface">snmpm_network_interface</seealso> behaviour.</p>
+ <seeerl marker="snmpm_network_interface">snmpm_network_interface</seeerl> behaviour.</p>
<p>Default is <c>snmpm_net_if</c>.</p>
</item>
@@ -796,7 +833,7 @@ in the snmp_config file!
<item>
<p>Module which handles the network interface filter part for the
SNMP manager. Must implement the
- <seealso marker="snmpm_network_interface_filter">snmpm_network_interface_filter</seealso> behaviour.</p>
+ <seeerl marker="snmpm_network_interface_filter">snmpm_network_interface_filter</seeerl> behaviour.</p>
<p>Default is <c>snmpm_net_if_filter</c>.</p>
</item>
@@ -804,7 +841,7 @@ in the snmp_config file!
<c><![CDATA[def_user_module() = atom() <optional>]]></c></tag>
<item>
<p>The module implementing the default user. See the
- <seealso marker="snmpm_user">snmpm_user</seealso> behaviour.</p>
+ <seeerl marker="snmpm_user">snmpm_user</seeerl> behaviour.</p>
<p>Default is <c>snmpm_user_default</c>.</p>
</item>
@@ -823,7 +860,7 @@ in the snmp_config file!
<tag><marker id="restart_type"></marker>
<c>restart_type() = permanent | transient | temporary</c></tag>
<item>
- <p>See <seealso marker="stdlib:supervisor#child_spec">supervisor</seealso>
+ <p>See <seeerl marker="stdlib:supervisor#child_spec">supervisor</seeerl>
documentation for more info.</p>
<p>Default is <c>permanent</c> for the agent and <c>transient</c>
for the manager.</p>
diff --git a/lib/snmp/doc/src/snmp_audit_trail_log.xml b/lib/snmp/doc/src/snmp_audit_trail_log.xml
index 4fcdceb321..e3b2ca86cf 100644
--- a/lib/snmp/doc/src/snmp_audit_trail_log.xml
+++ b/lib/snmp/doc/src/snmp_audit_trail_log.xml
@@ -57,7 +57,7 @@
mechanism. This means that the log is not human readable, but
needs to be formatted off-line before it can be read. Use the
function
- <seealso marker="snmpa#log_to_txt">snmpa:log_to_txt</seealso>
+ <seeerl marker="snmpa#log_to_txt">snmpa:log_to_txt</seeerl>
for this purpose.
</p>
</section>
@@ -74,7 +74,7 @@
mechanism. This means that the log is not human readable, but
needs to be formatted off-line before it can be read. Use the
function
- <seealso marker="snmpm#log_to_txt">snmpm:log_to_txt</seealso>
+ <seeerl marker="snmpm#log_to_txt">snmpm:log_to_txt</seeerl>
for this purpose.
</p>
</section>
diff --git a/lib/snmp/doc/src/snmp_config.xml b/lib/snmp/doc/src/snmp_config.xml
index 79c6703c94..e3d74fec16 100644
--- a/lib/snmp/doc/src/snmp_config.xml
+++ b/lib/snmp/doc/src/snmp_config.xml
@@ -43,8 +43,8 @@
</list>
<p>Refer also to the chapter(s)
- <seealso marker="snmp_agent_config_files">Definition of Agent Configuration Files</seealso> and
- <seealso marker="snmp_manager_config_files">Definition of Manager Configuration Files</seealso> which contains more detailed information
+ <seeguide marker="snmp_agent_config_files">Definition of Agent Configuration Files</seeguide> and
+ <seeguide marker="snmp_manager_config_files">Definition of Manager Configuration Files</seeguide> which contains more detailed information
about the agent and manager configuration files.</p>
<section>
@@ -56,7 +56,7 @@
<item>
<p>the <em>configuration directory</em> stores all
configuration files used by the agent (refer to the chapter
- <seealso marker="snmp_agent_config_files">Definition of Agent Configuration Files</seealso> for more information). </p>
+ <seeguide marker="snmp_agent_config_files">Definition of Agent Configuration Files</seeguide> for more information). </p>
</item>
<item>the <em>database directory</em> stores the internal
database files.</item>
@@ -67,7 +67,7 @@
<item>
<p>the <em>configuration directory</em> stores all
configuration files used by the manager (refer to the chapter
- <seealso marker="snmp_manager_config_files">Definition of Manager Configuration Files</seealso> for more information). </p>
+ <seeguide marker="snmp_manager_config_files">Definition of Manager Configuration Files</seeguide> for more information). </p>
</item>
<item>
<p>the <em>database directory</em> stores the internal
@@ -184,10 +184,35 @@
</item>
<tag><marker id="agent_mt"></marker>
- <c><![CDATA[multi_threaded() = bool() <optional>]]></c></tag>
+ <c><![CDATA[multi_threaded() = bool() | extended<optional>]]></c></tag>
<item>
- <p>If <c>true</c>, the agent is multi-threaded, with one
- thread for each get request. </p>
+ <p>If <c>true</c> (or <c>extended</c>), the agent is multi-threaded,
+ with one thread for each get request. </p>
+ <p>The value <c>extended</c> means that a special 'process'
+ is also created intended to handle <em>all</em> notifications. </p>
+ <list>
+ <item>
+ <p><c>true</c> - One worker dedicated to 'set-requests' and one
+ (main) worker for all other requests ('get-request' and
+ notifications).</p>
+ <p>If the 'main' worker is busy, a temporary process is
+ spawned to handle that job ('get-request' or notification). </p>
+ </item>
+ <item>
+ <p><c>extended</c> - One worker dedicated to 'set-requests',
+ one worker dedicated to notifications and one
+ (main) worker for all 'get-requests'. </p>
+ <p>If the 'main' worker is busy, a temporary process is
+ spawned to handle that 'get-request'. </p>
+ </item>
+ </list>
+ <note>
+ <p>Even with multi-threaded set to <c>extended</c>
+ there is still a risk for 'reorder' when sending inform-requsts,
+ which require a response (and may therefor require resending). </p>
+ <p>Also, there is of course no way to guarantee order once the
+ package is on the network. </p>
+ </note>
<p>Default is <c>false</c>.</p>
</item>
@@ -248,7 +273,7 @@
<item>
<p>Module which handles the network interface part for the
SNMP agent. Must implement the
- <seealso marker="snmpa_network_interface">snmpa_network_interface</seealso> behaviour.</p>
+ <seeerl marker="snmpa_network_interface">snmpa_network_interface</seeerl> behaviour.</p>
<p>Default is <c>snmpa_net_if</c>.</p>
</item>
@@ -256,15 +281,22 @@
<c><![CDATA[agent_net_if_options() = [agent_net_if_option()] <optional>]]></c></tag>
<item>
<p><c>agent_net_if_option() = {bind_to, bind_to()} |
- {sndbuf, sndbuf()} |
- {recbuf, recbuf()} |
- {no_reuse, no_reuse()} |
- {req_limit, req_limit()} |
- {filter, agent_net_if_filter_options()} |
- {extra_sock_opts, extra_socket_options()}</c></p>
+ {sndbuf, sndbuf()} |
+ {recbuf, recbuf()} |
+ {no_reuse, no_reuse()} |
+ {req_limit, req_limit()} |
+ {filter, agent_net_if_filter_options()} |
+ {extra_sock_opts, extra_socket_options()}</c></p>
<p>These options are actually specific to the used module.
- The ones shown here are applicable to the default
- <c>agent_net_if_module()</c>.</p>
+ The ones shown here are applicable to the default
+ <c>agent_net_if_module()</c>.</p>
+ <note>
+ <p>If the user has configured transports <em>with</em> options
+ then those will take precedence over these options.
+ See
+ <seeguide marker="snmp_agent_config_files#agent_information">agent information</seeguide>
+ for more info. </p>
+ </note>
<p>For defaults see the options in <c>agent_net_if_option()</c>.</p>
</item>
@@ -290,8 +322,8 @@
<item>
<p>Module which handles the network interface filter part for the
SNMP agent. Must implement the
- <seealso marker="snmpa_network_interface_filter">snmpa_network_interface_filter
- </seealso> behaviour.</p>
+ <seeerl marker="snmpa_network_interface_filter">snmpa_network_interface_filter
+ </seeerl> behaviour.</p>
<p>Default is <c>snmpa_net_if_filter</c>.</p>
</item>
@@ -324,7 +356,7 @@
<c><![CDATA[mib_storage_module() = snmpa_mib_data_ets | snmpa_mib_data_dets | snmpa_mib_data_mnesia | module()]]></c></tag>
<item>
<p>Defines the mib storage module of the SNMP agent as defined by the
- <seealso marker="snmpa_mib_storage">snmpa_mib_storage</seealso>
+ <seeerl marker="snmpa_mib_storage">snmpa_mib_storage</seeerl>
behaviour. </p>
<p>Several entities (<c>mib-server</c> via the its data module and
the <c>symbolic-store</c>) of the snmp agent uses this for storage
@@ -495,7 +527,7 @@ in so far as it will be converted to the new format if found.
<item>
<p>Defines the backend data module of the SNMP agent mib-server as
defined by the
- <seealso marker="snmpa_mib_data">snmpa_mib_data</seealso>
+ <seeerl marker="snmpa_mib_data">snmpa_mib_data</seeerl>
behaviour. </p>
<p>At present only the default module is provided with the agent,
<c>snmpa_mib_data_tttn</c>. </p>
@@ -529,7 +561,7 @@ in so far as it will be converted to the new format if found.
<item>
<p>Defines if the mib server shall perform cache gc automatically or
leave it to the user (see
- <seealso marker="snmpa#gc_mibs_cache">gc_mibs_cache/0,1,2,3</seealso>). </p>
+ <seeerl marker="snmpa#gc_mibs_cache">gc_mibs_cache/0,1,2,3</seeerl>). </p>
<p>Default is <c>true</c>.</p>
</item>
@@ -544,21 +576,26 @@ in so far as it will be converted to the new format if found.
</item>
<tag><marker id="agent_ms_cache_gclimit"></marker>
- <c><![CDATA[mibs_cache_gclimit() = integer() > 0 | infinity <optional>]]></c></tag>
+ <c><![CDATA[mibs_cache_gclimit() = infinity | integer() > 0 <optional>]]></c></tag>
<item>
<p>When performing a GC, this is the max number of cache entries
that will be deleted from the cache. </p>
- <p>The reason for having this limit is that if the cache is
+
+ <p>The reason why its possible to set a limit, is that if the cache is
large, the GC can potentially take a long time, during which
- the agent is locked. </p>
- <p>Default is <c>100</c>.</p>
+ the agent is "busy".
+ <em>But</em> on a heavily loaded system, we also risk not removing
+ enough elements in the cache, instead causing it to grow over time.
+ This is the reason the default value is <c>infinity</c>, which will
+ ensure that <em>all</em> candidates are removed as soon as possible. </p>
+ <p>Default is <c>infinity</c>.</p>
</item>
<tag><marker id="agent_error_report_mod"></marker>
<c><![CDATA[error_report_mod() = atom() <optional>]]></c></tag>
<item>
<p>Defines an error report module, implementing the
- <seealso marker="snmpa_error_report">snmpa_error_report</seealso>
+ <seeerl marker="snmpa_error_report">snmpa_error_report</seeerl>
behaviour. Two modules are provided with the toolkit:
<c>snmpa_error_logger</c> and <c>snmpa_error_io</c>.</p>
<p>Default is <c>snmpa_error_logger</c>.</p>
@@ -631,9 +668,9 @@ in so far as it will be converted to the new format if found.
The information will have a <em>best before</em> time,
defined by the <c>Expire</c> time given when calling the
request function (see
- <seealso marker="snmpm#async_get">async_get</seealso>,
- <seealso marker="snmpm#async_get_next">async_get_next</seealso> and
- <seealso marker="snmpm#async_set">async_set</seealso>).</p>
+ <seeerl marker="snmpm#async_get2">async_get</seeerl>,
+ <seeerl marker="snmpm#async_get_next2">async_get_next</seeerl> and
+ <seeerl marker="snmpm#async_set2">async_set</seeerl>).</p>
<p>Time in milli-seconds.</p>
<p>Default is <c>30000</c>.</p>
</item>
@@ -666,7 +703,7 @@ in so far as it will be converted to the new format if found.
net-if process.
Note that this will only work if the used net-if process actually supports
the protocol. See
- <seealso marker="snmpm_network_interface">snmpm_network_interface</seealso> behaviour for more info. </p>
+ <seeerl marker="snmpm_network_interface">snmpm_network_interface</seeerl> behaviour for more info. </p>
<taglist>
<tag><marker id="manager_server_nis_none"></marker>
<c><![CDATA[none (default)]]></c></tag>
@@ -679,12 +716,12 @@ in so far as it will be converted to the new format if found.
<item>
<p>The <c>PingTO</c> time specifies the between a successful ping
(or start) and the time when a
- <seealso marker="snmp_manager_netif#im_ping">ping</seealso>
+ <seeguide marker="snmp_manager_netif#im_ping">ping</seeguide>
message is to be sent to the net-if
process (basically the time between ping:s). </p>
<p>The <c>PongTO</c> time specifies how long time the net-if process
has to respond to a ping message, with a
- <seealso marker="snmp_manager_netif#om_pong">pong</seealso>
+ <seeguide marker="snmp_manager_netif#om_pong">pong</seeguide>
message.
It starts counting when the ping message has been sent.</p>
<p>Both times are in milli seconds.</p>
@@ -742,7 +779,7 @@ in so far as it will be converted to the new format if found.
<item>
<p><c>{user, integer()}</c> - The manager will send response
(acknowledgment) to inform-request messages when the
- <seealso marker="snmpm_user#handle_inform">handle_inform</seealso>
+ <seeerl marker="snmpm_user#handle_inform">handle_inform</seeerl>
function completes. The integer is the time, in milli-seconds,
that the manager will consider the stored inform-request info
valid.</p>
@@ -753,9 +790,9 @@ in so far as it will be converted to the new format if found.
</item>
</list>
<p>See
- <seealso marker="snmpm_network_interface">snmpm_network_interface</seealso>,
- <seealso marker="snmpm_user">handle_inform</seealso> and
- <seealso marker="snmp_manager_netif">definition of the manager net if</seealso> for more info.</p>
+ <seeerl marker="snmpm_network_interface">snmpm_network_interface</seeerl>,
+ <seeerl marker="snmpm_user">handle_inform</seeerl> and
+ <seeguide marker="snmp_manager_netif">definition of the manager net if</seeguide> for more info.</p>
<p>Default is <c>auto</c>.</p>
</item>
@@ -798,7 +835,7 @@ in so far as it will be converted to the new format if found.
<item>
<p>The module which handles the network interface part for the
SNMP manager. It must implement the
- <seealso marker="snmpm_network_interface">snmpm_network_interface</seealso> behaviour. </p>
+ <seeerl marker="snmpm_network_interface">snmpm_network_interface</seeerl> behaviour. </p>
<p>Default is <c>snmpm_net_if</c>. </p>
</item>
@@ -818,7 +855,7 @@ in so far as it will be converted to the new format if found.
<item>
<p>Module which handles the network interface filter part for the
SNMP manager. Must implement the
- <seealso marker="snmpm_network_interface_filter">snmpm_network_interface_filter</seealso> behaviour.</p>
+ <seeerl marker="snmpm_network_interface_filter">snmpm_network_interface_filter</seeerl> behaviour.</p>
<p>Default is <c>snmpm_net_if_filter</c>.</p>
</item>
@@ -826,7 +863,7 @@ in so far as it will be converted to the new format if found.
<c><![CDATA[def_user_module() = atom() <optional>]]></c></tag>
<item>
<p>The module implementing the default user. See the
- <seealso marker="snmpm_user">snmpm_user</seealso> behaviour.</p>
+ <seeerl marker="snmpm_user">snmpm_user</seeerl> behaviour.</p>
<p>Default is <c>snmpm_user_default</c>.</p>
</item>
@@ -845,7 +882,7 @@ in so far as it will be converted to the new format if found.
<tag><marker id="restart_type"></marker>
<c>restart_type() = permanent | transient | temporary</c></tag>
<item>
- <p>See <seealso marker="stdlib:supervisor#child_spec">supervisor</seealso>
+ <p>See <seeerl marker="stdlib:supervisor#child_spec">supervisor</seeerl>
documentation for more info.</p>
<p>Default is <c>permanent</c> for the agent and <c>transient</c>
for the manager.</p>
@@ -1224,7 +1261,7 @@ ok
application). This is done by calling the
<c>snmpa:verbosity/2</c> and <c>snmpm:verbosity/2</c> function(s)
and/or using
- <seealso marker="#configuration_params">configuration parameters</seealso>.
+ <seeguide marker="#configuration_params">configuration parameters</seeguide>.
The verbosity itself has several <em>levels</em>: <c>silence | info | log | debug | trace</c>. For the lowest verbosity <c>silence</c>,
nothing is printed. The higher the verbosity, the more is printed.
Default value is always <c>silence</c>.</p>
@@ -1265,37 +1302,37 @@ ok
<taglist>
<tag><c><![CDATA[snmpa:info/0,1]]></c></tag>
<item>
- <p><seealso marker="snmpa#info">info</seealso> is used to retrieve a list of miscellaneous agent information.</p>
+ <p><seeerl marker="snmpa#info">info</seeerl> is used to retrieve a list of miscellaneous agent information.</p>
</item>
<tag><c><![CDATA[snmpa:which_aliasnames/0]]></c></tag>
<item>
- <p><seealso marker="snmpa#which_aliasnames">which_aliasnames</seealso> is used to retrieve a list of all alias-names known to the agent. </p>
+ <p><seeerl marker="snmpa#which_aliasnames">which_aliasnames</seeerl> is used to retrieve a list of all alias-names known to the agent. </p>
</item>
<tag><c><![CDATA[snmpa:which_tables/0]]></c></tag>
<item>
- <p><seealso marker="snmpa#which_tables">which_tables</seealso> is used to retrieve a list of all (MIB) tables known to the agent. </p>
+ <p><seeerl marker="snmpa#which_tables">which_tables</seeerl> is used to retrieve a list of all (MIB) tables known to the agent. </p>
</item>
<tag><c><![CDATA[snmpa:which_variables/0]]></c></tag>
<item>
- <p><seealso marker="snmpa#which_variables">which_variables</seealso> is used to retrieve a list of all (MIB) variables known to the agent. </p>
+ <p><seeerl marker="snmpa#which_variables">which_variables</seeerl> is used to retrieve a list of all (MIB) variables known to the agent. </p>
</item>
<tag><c><![CDATA[snmpa:which_notifications/0]]></c></tag>
<item>
- <p><seealso marker="snmpa#which_notifications">which_notifications</seealso> is used to retrieve a list of all (MIB) notifications/traps known to the agent. </p>
+ <p><seeerl marker="snmpa#which_notifications">which_notifications</seeerl> is used to retrieve a list of all (MIB) notifications/traps known to the agent. </p>
</item>
<tag><c><![CDATA[snmpa:restart_worker/0,1]]></c></tag>
<item>
- <p><seealso marker="snmpa#restart_worker">restart_worker</seealso> is used to restart the worker process of a multi-threaded agent. </p>
+ <p><seeerl marker="snmpa#restart_worker">restart_worker</seeerl> is used to restart the worker process of a multi-threaded agent. </p>
</item>
<tag><c><![CDATA[snmpa:restart_set_worker/0,1]]></c></tag>
<item>
- <p><seealso marker="snmpa#restart_set_worker">restart_set_worker</seealso> is used to restart the set-worker process of a multi-threaded agent. </p>
+ <p><seeerl marker="snmpa#restart_set_worker">restart_set_worker</seeerl> is used to restart the set-worker process of a multi-threaded agent. </p>
</item>
<tag><c><![CDATA[snmpa_local_db:print/0,1,2]]></c></tag>
@@ -1310,10 +1347,10 @@ ok
This can be done by simply calling: </p>
<p><c><![CDATA[snmpa:print_mib_info()]]></c></p>
<p>See
- <seealso marker="snmpa#print_mib_info">print_mib_info/0</seealso>,
- <seealso marker="snmpa#print_mib_tables">print_mib_tables/0</seealso>
+ <seeerl marker="snmpa#print_mib_info">print_mib_info/0</seeerl>,
+ <seeerl marker="snmpa#print_mib_tables">print_mib_tables/0</seeerl>
or
- <seealso marker="snmpa#print_mib_variables">print_mib_variables/0</seealso>
+ <seeerl marker="snmpa#print_mib_variables">print_mib_variables/0</seeerl>
for more info. </p>
</section>
diff --git a/lib/snmp/doc/src/snmp_def_instr_functions.xml b/lib/snmp/doc/src/snmp_def_instr_functions.xml
index 3445648de2..c07de630d6 100644
--- a/lib/snmp/doc/src/snmp_def_instr_functions.xml
+++ b/lib/snmp/doc/src/snmp_def_instr_functions.xml
@@ -49,7 +49,7 @@
code, it is converted into an SNMPv1 code before it is sent to a
SNMPv1 manager. It is recommended to use the SNMPv2 error codes
for all instrumentation functions, as these provide more
- details. See <seealso marker="snmp_app_a">Appendix A</seealso> for a
+ details. See <seeguide marker="snmp_app_a">Appendix A</seeguide> for a
description of error code conversions.
</p>
@@ -202,7 +202,7 @@
code, it is converted into an SNMPv1 code before it is sent to a
SNMPv1 manager. It is recommended to use the SNMPv2 error codes
for all instrumentation functions, as these provide more
- details. See <seealso marker="snmp_app_a">Appendix A</seealso> for a
+ details. See <seeguide marker="snmp_app_a">Appendix A</seeguide> for a
description of error code conversions.</p>
<section>
diff --git a/lib/snmp/doc/src/snmp_index.xml b/lib/snmp/doc/src/snmp_index.xml
index 4ead0fcb65..9c8e6b8b33 100644
--- a/lib/snmp/doc/src/snmp_index.xml
+++ b/lib/snmp/doc/src/snmp_index.xml
@@ -168,7 +168,7 @@ get_next_pid(Oid, SnmpIndex) ->
<desc>
<p>Deletes a complete index structure (i.e. the ets table
holding the index). The index can no longer be referenced
- after this call. See the <seealso marker="#1">warning note</seealso>
+ after this call. See the <seeerl marker="#1">warning note</seeerl>
above.</p>
</desc>
</func>
diff --git a/lib/snmp/doc/src/snmp_instr_functions.xml b/lib/snmp/doc/src/snmp_instr_functions.xml
index 38512b72d0..e9dbfb1e98 100644
--- a/lib/snmp/doc/src/snmp_instr_functions.xml
+++ b/lib/snmp/doc/src/snmp_instr_functions.xml
@@ -63,7 +63,7 @@
values for the table, and <c>Column</c> is a column number.
</p>
<p>These functions are described in detail in
- <seealso marker="snmp_def_instr_functions">Definition of Instrumentation Functions</seealso>.
+ <seeguide marker="snmp_def_instr_functions">Definition of Instrumentation Functions</seeguide>.
</p>
<section>
diff --git a/lib/snmp/doc/src/snmp_manager_config_files.xml b/lib/snmp/doc/src/snmp_manager_config_files.xml
index c7c423f5eb..1db96afa78 100644
--- a/lib/snmp/doc/src/snmp_manager_config_files.xml
+++ b/lib/snmp/doc/src/snmp_manager_config_files.xml
@@ -47,7 +47,7 @@
</p>
<p>If syntax errors are discovered in these files they are reported with the
function <c>config_err/2</c> of the
- <seealso marker="snmpa_error_report">error report module</seealso>
+ <seeerl marker="snmpa_error_report">error report module</seeerl>
at start-up.
</p>
@@ -81,8 +81,8 @@
<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
+ <seetype marker="kernel:inet#ip_address">
+ <c>ip_address()</c></seetype> 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,
@@ -138,7 +138,7 @@
<p>For each <em>manager user</em>, the manager needs some information.
This information is either added in the <c>users.conf</c> config
file or by calling the
- <seealso marker="snmpm#register_user">register_user</seealso>
+ <seeerl marker="snmpm#register_user">register_user</seeerl>
function in run-time.
</p>
<p>Each row defines a <em>manager user</em> of the manager.
@@ -174,7 +174,7 @@
<p>The information needed to handle agents should be stored in a
file called <c>agents.conf</c>. It is also possible to add agents
in run-time by calling the
- <seealso marker="snmpm#register_agent">register_agent</seealso>.
+ <seeerl marker="snmpm#register_agent">register_agent</seeerl>.
</p>
<p>Each entry is a tuple:
</p>
@@ -200,7 +200,7 @@
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>
+ <seetype marker="kernel:inet#ip_address"><c>ip_address()</c></seetype>
or a traditional SNMP integer list not containing port number,
and <c>IpPort</c> is an integer.</p>
</item>
@@ -242,7 +242,7 @@
file called <c>usm.conf</c>, which must be present if the manager
wishes to use SNMPv3 when communicating with agents. It is also
possible to add usm data in run-time by calling the
- <seealso marker="snmpm#register_usm_user">register_usm_user</seealso>.
+ <seeerl marker="snmpm#register_usm_user">register_usm_user</seeerl>.
</p>
<p>The corresponding table is <c>usmUserTable</c> in the
SNMP-USER-BASED-SM-MIB.
diff --git a/lib/snmp/doc/src/snmp_manager_funct_descr.xml b/lib/snmp/doc/src/snmp_manager_funct_descr.xml
index b2da01ae32..aa1cf8cc77 100644
--- a/lib/snmp/doc/src/snmp_manager_funct_descr.xml
+++ b/lib/snmp/doc/src/snmp_manager_funct_descr.xml
@@ -42,7 +42,7 @@
itself. That is up to the <em>user</em>.
</p>
<p>A <em>user</em> in this context is basically a module implementing
- the <seealso marker="snmpm_user">snmpm_user</seealso> behaviour.
+ the <seeerl marker="snmpm_user">snmpm_user</seeerl> behaviour.
A <em>user</em> can issue snmp requests and receive
notification/traps.</p>
<p>Agents to be accessed by the manager needs to be registered by
@@ -56,24 +56,24 @@
<c>snmpm_user_default</c> module, which simply sends an info message
to the error_logger. It is however highly recommended that this
module be replaced by another that does something useful
- (see <seealso marker="snmp_config#configuration_params">configuration params</seealso> for more info).</p>
+ (see <seeguide marker="snmp_config#configuration_params">configuration params</seeguide> for more info).</p>
<p>When using version 3, then (at least one) <em>usm user</em> has to
be registered.</p>
<p>Requests can be issued in two different ways. Synchronous (see
- <seealso marker="snmpm#sync_set">sync_set</seealso>,
- <seealso marker="snmpm#sync_get">sync_get</seealso>,
- <seealso marker="snmpm#sync_get_next">sync_get_next</seealso> and
- <seealso marker="snmpm#sync_get_bulk">sync_get_bulk</seealso>)
+ <seeerl marker="snmpm#sync_set2">sync_set</seeerl>,
+ <seeerl marker="snmpm#sync_get2">sync_get</seeerl>,
+ <seeerl marker="snmpm#sync_get_next2">sync_get_next</seeerl> and
+ <seeerl marker="snmpm#sync_get_bulk2">sync_get_bulk</seeerl>)
and asynchronous (see
- <seealso marker="snmpm#async_set">async_set</seealso>,
- <seealso marker="snmpm#async_get">async_get</seealso>,
- <seealso marker="snmpm#async_get_next">async_get_next</seealso> and
- <seealso marker="snmpm#async_get_bulk">async_get_bulk</seealso>).
+ <seeerl marker="snmpm#async_set2">async_set</seeerl>,
+ <seeerl marker="snmpm#async_get2">async_get</seeerl>,
+ <seeerl marker="snmpm#async_get_next2">async_get_next</seeerl> and
+ <seeerl marker="snmpm#async_get_bulk2">async_get_bulk</seeerl>).
With synchronous
the snmp reply is returned by the function. With asynchronous,
the reply will instead be delivered through a call to one of the
<c>handle_pdu</c> callback function defined by the
- <seealso marker="snmpm_user#handle_pdu">handle_pdu</seealso>
+ <seeerl marker="snmpm_user#handle_pdu">handle_pdu</seeerl>
behaviour.</p>
</section>
diff --git a/lib/snmp/doc/src/snmp_manager_netif.xml b/lib/snmp/doc/src/snmp_manager_netif.xml
index ba3f843cb1..f6c9e642cf 100644
--- a/lib/snmp/doc/src/snmp_manager_netif.xml
+++ b/lib/snmp/doc/src/snmp_manager_netif.xml
@@ -58,15 +58,15 @@
is created that processes the message/request and then exits. </p>
<p>There is a <c>server</c> config option,
- <seealso marker="snmp_config#manager_server_nis">netif_sup</seealso>
+ <seeguide marker="snmp_config#manager_server_nis">netif_sup</seeguide>
that enables "active" Net If supervision. This is very simple mechanism.
The (supervising) process simply sends a
- <seealso marker="#im_ping">ping</seealso> message and expects a
- <seealso marker="#om_pong">pong</seealso> message response
+ <seeguide marker="#im_ping">ping</seeguide> message and expects a
+ <seeguide marker="#om_pong">pong</seeguide> message response
(withing a specific time).
The interval between each <c>ping/pong</c> exhange is user configurable.
As is the time that is allowed for the
- <seealso marker="#om_pong">pong</seealso>
+ <seeguide marker="#om_pong">pong</seeguide>
message to arrive.
Both the NetIf module(s) provided with the app supports active supervision.
If a NetIf module/process is used which do not implement this, then
@@ -79,7 +79,7 @@
<marker id="mandatory_functions"></marker>
<title>Mandatory Functions</title>
<p>A Net If process must implement the SNMP manager
- <seealso marker="snmpm_network_interface">network interface behaviour</seealso>. </p>
+ <seeerl marker="snmpm_network_interface">network interface behaviour</seeerl>. </p>
</section>
<section>
@@ -90,7 +90,7 @@
<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>{</c><seealso marker="kernel:inet#type-ip_address"><c>IpAddr</c></seealso><c>,IpPort}</c> tuple.</p>
+ <c>{</c><seetype marker="kernel:inet#ip_address"><c>IpAddr</c></seetype><c>,IpPort}</c> tuple.</p>
<section>
<marker id="outgoing_messages"></marker>
@@ -140,9 +140,9 @@ Server ! {snmp_inform, Ref, Pdu, PduMS, Domain, Addr}
<c>ignore</c> is used if the response (acknowledgment) to the
inform-request has already been sent (this means that the server
will not make the call to the
- <seealso marker="snmpm_network_interface#inform_response">inform_response</seealso>
+ <seeerl marker="snmpm_network_interface#inform_response">inform_response</seeerl>
function). See the
- <seealso marker="snmp_config#manager_irb">inform request behaviour</seealso>
+ <seeguide marker="snmp_config#manager_irb">inform request behaviour</seeguide>
configuration option for more info.</p>
</item>
<item>
@@ -165,7 +165,7 @@ Server ! {snmp_report, Data, Domain, Addr}
<p><c>Data</c> is either <c>{ok, Pdu}</c> or
<c>{error, ReqId, ReasonInfo, Pdu}</c>. Which one is used depends
on the return value from the MPD
- <seealso marker="snmpm_mpd#process_msg">process_msg</seealso> function. If the MsgData is <c>ok</c>,
+ <seeerl marker="snmpm_mpd#process_msg">process_msg</seeerl> function. If the MsgData is <c>ok</c>,
the first is used, and if it is <c>{error, ReqId, Reason}</c>
the latter is used.</p>
</item>
@@ -194,7 +194,7 @@ Supervisor ! {pong, self()}
<list type="bulleted">
<item>
<p><c>Supervisor</c> is the process that sent the
- <seealso marker="#im_ping">ping</seealso> message (see below). </p>
+ <seeguide marker="#im_ping">ping</seeguide> message (see below). </p>
</item>
</list>
</section>
@@ -212,7 +212,7 @@ Supervisor ! {pong, self()}
<p>This message is sent to the Net If process by a process that
has been configured to perfor "active supervision" of the Net If
process. The Net If process should respond immediately with
- a <seealso marker="#om_pong">pong</seealso> message. </p>
+ a <seeguide marker="#om_pong">pong</seeguide> message. </p>
<list type="bulleted">
<item>
<p><c>Supervisor</c> is a <c>pid()</c>. </p>
diff --git a/lib/snmp/doc/src/snmp_mib_compiler.xml b/lib/snmp/doc/src/snmp_mib_compiler.xml
index 285d11fce9..4418ccfc98 100644
--- a/lib/snmp/doc/src/snmp_mib_compiler.xml
+++ b/lib/snmp/doc/src/snmp_mib_compiler.xml
@@ -62,7 +62,7 @@
instrumentation functions for the MIB, should have the suffix
<c>.funcs</c>. If the compiler does not find the association file,
it gives a warning message and uses default instrumentation
- functions. (See <seealso marker="snmp_instr_functions#snmp_3">Default Instrumentation</seealso> for more details).
+ functions. (See <seeguide marker="snmp_instr_functions#snmp_3">Default Instrumentation</seeguide> for more details).
</p>
<p>The MIB compiler is started with a call to
<c><![CDATA[snmpc:compile(<mibname>).]]></c> For example:
diff --git a/lib/snmp/doc/src/snmp_pdus.xml b/lib/snmp/doc/src/snmp_pdus.xml
index a70597aad9..120f5192a1 100644
--- a/lib/snmp/doc/src/snmp_pdus.xml
+++ b/lib/snmp/doc/src/snmp_pdus.xml
@@ -125,7 +125,6 @@
<p>Decodes a list of bytes into an SNMP UsmSecurityParameters</p>
</desc>
</func>
-
<func>
<name since="">enc_message(Message) -> [byte()]</name>
<fsummary>Encode an SNMP Message</fsummary>
diff --git a/lib/snmp/doc/src/snmp_target_mib.xml b/lib/snmp/doc/src/snmp_target_mib.xml
index 4ac25e68a8..18382628a7 100644
--- a/lib/snmp/doc/src/snmp_target_mib.xml
+++ b/lib/snmp/doc/src/snmp_target_mib.xml
@@ -49,8 +49,8 @@
<section>
<title>DATA TYPES</title>
- <p>See the <seealso marker="snmpa_conf#types">
- data types in <c>snmpa_conf</c></seealso>.</p>
+ <p>See the <seeerl marker="snmpa_conf#types">
+ data types in <c>snmpa_conf</c></seeerl>.</p>
<marker id="configure"></marker>
</section>
diff --git a/lib/snmp/doc/src/snmp_view_based_acm_mib.xml b/lib/snmp/doc/src/snmp_view_based_acm_mib.xml
index 059b39b8f8..a00c8270f3 100644
--- a/lib/snmp/doc/src/snmp_view_based_acm_mib.xml
+++ b/lib/snmp/doc/src/snmp_view_based_acm_mib.xml
@@ -90,7 +90,7 @@
</p>
<p>If an error is found in the configuration file, it is
reported using the function
- <seealso marker="snmpa_error#config_err">config_err/2</seealso>
+ <seeerl marker="snmpa_error#config_err">config_err/2</seeerl>
of the error report module, and the function fails with the reason
<c>configuration_error</c>.
</p>
diff --git a/lib/snmp/doc/src/snmpa.xml b/lib/snmp/doc/src/snmpa.xml
index f74b85eddf..c3025dc945 100644
--- a/lib/snmp/doc/src/snmpa.xml
+++ b/lib/snmp/doc/src/snmpa.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2004</year><year>2020</year>
+ <year>2004</year><year>2021</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -61,7 +61,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<tag><c><![CDATA[mod = module()]]></c></tag>
<item>
<p>A module implementing the
- <seealso marker="snmpa_notification_delivery_info_receiver">snmpa_notification_delivery_info_receiver</seealso>
+ <seeerl marker="snmpa_notification_delivery_info_receiver">snmpa_notification_delivery_info_receiver</seeerl>
behaviour. The info functions of this module will be called at
various stages of delivery. </p>
</item>
@@ -141,7 +141,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
corresponding instrumentation functions just as if it was a
GET request coming from a manager. </p>
<p>Note that the request specific parameters (such as
- <seealso marker="#current_request_id">current_request_id</seealso>)
+ <seeerl marker="#current_request_id">current_request_id</seeerl>)
are not accessible for the instrumentation functions if this
function is used. </p>
@@ -226,26 +226,6 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<p>Returns a list (a dictionary) containing information about
the agent. Information includes loaded MIBs, registered
sub-agents, some information about the memory allocation. </p>
- <p>As of version 4.4 the format of the info has been changed.
- To convert the info to the old format, call the
- <seealso marker="#old_info_format">old_info_format</seealso>
- function. </p>
-
- <marker id="old_info_format"></marker>
- </desc>
- </func>
-
- <func>
- <name since="">old_info_format(NewInfo) -> OldInfo</name>
- <fsummary>Return information about the agent</fsummary>
- <type>
- <v>OldInfo = NewInfo = [{Key, Value}]</v>
- </type>
- <desc>
- <p>As of version 4.4 the format of the info has been changed.
- This function is used to convert to the old (pre-4.4) info
- format. </p>
-
<marker id="load_mib"></marker>
</desc>
</func>
@@ -523,6 +503,24 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<desc>
<p>Retrieve all tables known to the agent.</p>
+ <marker id="which_transports"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">which_transports() -> Result</name>
+ <fsummary>Get all configured transports</fsummary>
+ <type>
+ <v>Result = [{TDomain, TAddress} | {TDomain, TAddress, Kind}]</v>
+ <v>TDomain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v>
+ <v>TAddress = {IpAddr, IpPort}</v>
+ <v>IpAddr = inet:ip_address()</v>
+ <v>IpPort = pos_integer()</v>
+ <v>Kind = req_responder | trap_sender</v>
+ </type>
+ <desc>
+ <p>Retrieve all configured transports.</p>
+
<marker id="which_variables"></marker>
</desc>
</func>
@@ -592,7 +590,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
during conversion. This could be usefull when converting large
logs (when otherwise the log could wrap during conversion).
Defaults to <c>true</c>. </p>
- <p>See <seealso marker="snmp#log_to_txt">snmp:log_to_txt</seealso>
+ <p>See <seeerl marker="snmp#log_to_txt">snmp:log_to_txt</seeerl>
for more info.</p>
<marker id="log_to_io"></marker>
@@ -633,7 +631,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
during conversion. This could be usefull when converting large
logs (when otherwise the log could wrap during conversion).
Defaults to <c>true</c>. </p>
- <p>See <seealso marker="snmp#log_to_io">snmp:log_to_io</seealso>
+ <p>See <seeerl marker="snmp#log_to_io">snmp:log_to_io</seeerl>
for more info.</p>
<marker id="change_log_size"></marker>
@@ -1051,7 +1049,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<item>
<p><c>notification_delivery_info()</c> - The information is
delivered via a function call according to this data. See the
- <seealso marker="#data_types">DATA TYPES</seealso> section
+ <seeerl marker="#data_types">DATA TYPES</seeerl> section
above for details. </p>
</item>
@@ -1122,7 +1120,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<note>
<p>The <c>extra</c> info is not normally interpreted by the agent,
instead it is passed through to the
- <seealso marker="snmp_agent_netif">net-if</seealso> process. It is
+ <seeguide marker="snmp_agent_netif">net-if</seeguide> process. It is
up to the implementor of that process to make use of this data. </p>
<p>The version of net-if provided by this application makes no use
of this data, with one exception:
@@ -1130,10 +1128,10 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<c>snmpa_default_notification_extra_info</c>
may be used by the agent and is therefor <em>reserved</em>. </p>
<p>See the net-if incomming messages for sending a
- <seealso marker="snmp_agent_netif#im_send_pdu">
- trap</seealso> and
- <seealso marker="snmp_agent_netif#im_send_pdu_req">
- notification</seealso> for more info. </p>
+ <seeguide marker="snmp_agent_netif#im_send_pdu">
+ trap</seeguide> and
+ <seeguide marker="snmp_agent_netif#im_send_pdu_req">
+ notification</seeguide> for more info. </p>
</note>
<marker id="send_notification"></marker>
@@ -1194,7 +1192,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<item>
<p><c>notification_delivery_info()</c> - The information is
delivered via a function call according to this data. See the
- <seealso marker="#data_types">DATA TYPES</seealso> section
+ <seeerl marker="#data_types">DATA TYPES</seeerl> section
above for details. </p>
</item>
@@ -1271,7 +1269,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<p>If <c>Receiver</c> is a <c>notification_delivery_info()</c> record,
then the information about the notification delivery will be delivered
to the <c>receiver</c> via the callback functions defined by the
- <seealso marker="snmpa_notification_delivery_info_receiver">snmpa_notification_delivery_info_receiver</seealso>
+ <seeerl marker="snmpa_notification_delivery_info_receiver">snmpa_notification_delivery_info_receiver</seeerl>
behaviour according to the content of the <c>notification_delivery_info()</c>
record. </p>
@@ -1382,7 +1380,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<p>The <c>DiscoHandler</c> module is used during the discovery
process. See
- <seealso marker="snmpa_discovery_handler">discovery handler</seealso>
+ <seeerl marker="snmpa_discovery_handler">discovery handler</seeerl>
for more info. </p>
<p>The <c>ExtraInfo</c> argument is passed on to the callback functions
@@ -1416,7 +1414,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
see the OTP R9C documentation.</p>
<p>For information about the current agent config
(<c>AgentConfig</c>), see the
- <seealso marker="snmp_config#configuration_params">Configuring the application</seealso>
+ <seeguide marker="snmp_config#configuration_params">Configuring the application</seeguide>
chapter of the SNMP user's guide.</p>
<marker id="restart_worker"></marker>
diff --git a/lib/snmp/doc/src/snmpa_conf.xml b/lib/snmp/doc/src/snmpa_conf.xml
index ae3e6fe1ed..8a0df62939 100644
--- a/lib/snmp/doc/src/snmpa_conf.xml
+++ b/lib/snmp/doc/src/snmpa_conf.xml
@@ -82,8 +82,8 @@ 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>
+ <seetype marker="kernel:inet#ip_address">
+ <c>inet:ip_address()</c></seetype></p>
<marker id="agent_entry"></marker>
</section>
@@ -103,7 +103,7 @@ word() = 0..65535
<p>Create an entry for the agent config file, <c>agent.conf</c>. </p>
<p>The type of <c>Val</c> depends on the value of <c>Tag</c>,
see
- <seealso marker="snmp_agent_config_files#agent_information">Agent Information</seealso>
+ <seeguide marker="snmp_agent_config_files#agent_information">Agent Information</seeguide>
for more info. </p>
<marker id="write_agent_config"></marker>
@@ -126,7 +126,7 @@ word() = 0..65535
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_agent_config_files#agent_information">Agent Information</seealso>
+ <seeguide marker="snmp_agent_config_files#agent_information">Agent Information</seeguide>
for more info. </p>
<marker id="append_agent_config"></marker>
@@ -145,7 +145,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#agent_information">Agent Information</seealso>
+ <seeguide marker="snmp_agent_config_files#agent_information">Agent Information</seeguide>
for more info. </p>
<marker id="read_agent_config"></marker>
@@ -164,7 +164,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#agent_information">Agent Information</seealso>
+ <seeguide marker="snmp_agent_config_files#agent_information">Agent Information</seeguide>
for more info. </p>
<marker id="standard_entry"></marker>
@@ -184,7 +184,7 @@ word() = 0..65535
<c>standard.conf</c>. </p>
<p>The type of <c>Val</c> depends on the value of <c>Tag</c>,
see
- <seealso marker="snmp_agent_config_files#system_information">System Information</seealso>
+ <seeguide marker="snmp_agent_config_files#system_information">System Information</seeguide>
for more info. </p>
<marker id="write_standard_config"></marker>
@@ -208,7 +208,7 @@ word() = 0..65535
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_agent_config_files#system_information">System Information</seealso>
+ <seeguide marker="snmp_agent_config_files#system_information">System Information</seeguide>
for more info. </p>
<marker id="append_standard_config"></marker>
@@ -228,7 +228,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#system_information">System Information</seealso>
+ <seeguide marker="snmp_agent_config_files#system_information">System Information</seeguide>
for more info. </p>
<marker id="read_standard_config"></marker>
@@ -247,7 +247,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#system_information">System Information</seealso>
+ <seeguide marker="snmp_agent_config_files#system_information">System Information</seeguide>
for more info. </p>
<marker id="context_entry"></marker>
@@ -265,7 +265,7 @@ word() = 0..65535
<p>Create an entry for the agent context config file,
<c>context.conf</c>. </p>
<p>See
- <seealso marker="snmp_agent_config_files#context">Contexts</seealso>
+ <seeguide marker="snmp_agent_config_files#context">Contexts</seeguide>
for more info. </p>
<marker id="write_context_config"></marker>
@@ -289,7 +289,7 @@ word() = 0..65535
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_agent_config_files#context">Contexts</seealso>
+ <seeguide marker="snmp_agent_config_files#context">Contexts</seeguide>
for more info. </p>
<marker id="append_context_config"></marker>
@@ -309,7 +309,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#context">Contexts</seealso>
+ <seeguide marker="snmp_agent_config_files#context">Contexts</seeguide>
for more info. </p>
<marker id="read_context_config"></marker>
@@ -328,7 +328,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#context">Contexts</seealso>
+ <seeguide marker="snmp_agent_config_files#context">Contexts</seeguide>
for more info. </p>
<marker id="community_entry"></marker>
@@ -356,7 +356,7 @@ word() = 0..65535
<p><c>community_entry("all-rights")</c> translates to the following
call: <c>community_entry(CommunityIndex, CommunityIndex, CommunityIndex, "", "")</c>. </p>
<p>See
- <seealso marker="snmp_agent_config_files#community">Community</seealso>
+ <seeguide marker="snmp_agent_config_files#community">Community</seeguide>
for more info. </p>
<marker id="write_community_config"></marker>
@@ -380,7 +380,7 @@ word() = 0..65535
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_agent_config_files#community">Community</seealso>
+ <seeguide marker="snmp_agent_config_files#community">Community</seeguide>
for more info. </p>
<marker id="append_community_config"></marker>
@@ -400,7 +400,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#community">Community</seealso>
+ <seeguide marker="snmp_agent_config_files#community">Community</seeguide>
for more info. </p>
<marker id="read_community_config"></marker>
@@ -419,7 +419,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#community">Communities</seealso>
+ <seeguide marker="snmp_agent_config_files#community">Communities</seeguide>
for more info. </p>
<marker id="target_addr_entry"></marker>
@@ -456,7 +456,7 @@ word() = 0..65535
<p><c>target_addr_entry/8</c> translates to the following call:
<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>
+ <seeguide marker="snmp_agent_config_files#target_addr">Target Address Definitions</seeguide>
for more info. </p>
<marker id="write_target_addr_config"></marker>
@@ -480,7 +480,7 @@ word() = 0..65535
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_agent_config_files#target_addr">Target Address Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#target_addr">Target Address Definitions</seeguide>
for more info. </p>
<marker id="append_target_addr_config"></marker>
@@ -500,7 +500,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#target_addr">Target Address Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#target_addr">Target Address Definitions</seeguide>
for more info. </p>
<marker id="read_target_addr_config"></marker>
@@ -519,7 +519,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#target_addr">Target Address Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#target_addr">Target Address Definitions</seeguide>
for more info. </p>
<marker id="target_params_entry"></marker>
@@ -556,7 +556,7 @@ word() = 0..65535
call: <c>target_params_entry(Name, MPModel, SecModel, SecName, SecLevel)</c> where <c>MPModel</c> and
<c>SecModel</c> is mapped from <c>Vsn</c>, see above. </p>
<p>See
- <seealso marker="snmp_agent_config_files#target_params">Target Parameters Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#target_params">Target Parameters Definitions</seeguide>
for more info. </p>
<marker id="write_target_params_config"></marker>
@@ -580,7 +580,7 @@ word() = 0..65535
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_agent_config_files#target_params">Target Parameters Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#target_params">Target Parameters Definitions</seeguide>
for more info. </p>
<marker id="append_target_params_config"></marker>
@@ -600,7 +600,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#target_params">Target Parameters Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#target_params">Target Parameters Definitions</seeguide>
for more info. </p>
<marker id="read_target_params_config"></marker>
@@ -619,7 +619,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#target_params">Target Parameters Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#target_params">Target Parameters Definitions</seeguide>
for more info. </p>
<marker id="vacm_entry"></marker>
@@ -657,7 +657,7 @@ word() = 0..65535
<p><c>vacm_vtf_entry/2</c> translates to the following call:
<c>vacm_vtf_entry(ViewIndex, ViewSubtree, included, null)</c>. </p>
<p>See
- <seealso marker="snmp_agent_config_files#vacm">MIB Views for VACM</seealso>
+ <seeguide marker="snmp_agent_config_files#vacm">MIB Views for VACM</seeguide>
for more info. </p>
<marker id="write_vacm_config"></marker>
@@ -682,7 +682,7 @@ word() = 0..65535
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_agent_config_files#vacm">MIB Views for VACM</seealso>
+ <seeguide marker="snmp_agent_config_files#vacm">MIB Views for VACM</seeguide>
for more info. </p>
<marker id="append_vacm_config"></marker>
@@ -702,7 +702,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#vacm">MIB Views for VACM</seealso>
+ <seeguide marker="snmp_agent_config_files#vacm">MIB Views for VACM</seeguide>
for more info. </p>
<marker id="read_vacm_config"></marker>
@@ -721,7 +721,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#vacm">MIB Views for VACM</seealso>
+ <seeguide marker="snmp_agent_config_files#vacm">MIB Views for VACM</seeguide>
for more info. </p>
<marker id="usm_entry"></marker>
@@ -754,7 +754,7 @@ word() = 0..65535
<p><c>usm_entry/1</c> translates to the following call:
<c>usm_entry("initial", "initial", zeroDotZero, usmNoAuthProtocol, "", "", usmNoPrivProtocol, "", "", "", "", "")</c>. </p>
<p>See
- <seealso marker="snmp_agent_config_files#usm">Security data for USM</seealso>
+ <seeguide marker="snmp_agent_config_files#usm">Security data for USM</seeguide>
for more info. </p>
<marker id="write_usm_config"></marker>
@@ -778,7 +778,7 @@ word() = 0..65535
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_agent_config_files#usm">Security data for USM</seealso>
+ <seeguide marker="snmp_agent_config_files#usm">Security data for USM</seeguide>
for more info. </p>
<marker id="append_usm_config"></marker>
@@ -798,7 +798,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#usm">Security data for USM</seealso>
+ <seeguide marker="snmp_agent_config_files#usm">Security data for USM</seeguide>
for more info. </p>
<marker id="read_usm_config"></marker>
</desc>
@@ -816,7 +816,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#usm">Security data for USM</seealso>
+ <seeguide marker="snmp_agent_config_files#usm">Security data for USM</seeguide>
for more info. </p>
<marker id="notify_entry"></marker>
@@ -837,7 +837,7 @@ word() = 0..65535
<c>notify.conf</c>. </p>
<p><c>Name</c> must be a <em>non-empty</em> string. </p>
<p>See
- <seealso marker="snmp_agent_config_files#notify">Notify Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#notify">Notify Definitions</seeguide>
for more info. </p>
<marker id="write_notify_config"></marker>
@@ -861,7 +861,7 @@ word() = 0..65535
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_agent_config_files#notify">Notify Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#notify">Notify Definitions</seeguide>
for more info. </p>
<marker id="append_notify_config"></marker>
@@ -881,7 +881,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#notify">Notify Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#notify">Notify Definitions</seeguide>
for more info. </p>
<marker id="read_notify_config"></marker>
@@ -900,7 +900,7 @@ word() = 0..65535
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_agent_config_files#notify">Notify Definitions</seealso>
+ <seeguide marker="snmp_agent_config_files#notify">Notify Definitions</seeguide>
for more info. </p>
<marker id="end"></marker>
</desc>
diff --git a/lib/snmp/doc/src/snmpa_discovery_handler.xml b/lib/snmp/doc/src/snmpa_discovery_handler.xml
index 473cbf3851..20adf5d2ab 100644
--- a/lib/snmp/doc/src/snmpa_discovery_handler.xml
+++ b/lib/snmp/doc/src/snmpa_discovery_handler.xml
@@ -40,7 +40,7 @@
must export the following functions: </p>
<list type="bulleted">
<item>
- <p><seealso marker="#stage1_finish">stage1_finish/2</seealso></p>
+ <p><seeerl marker="#stage1_finish">stage1_finish/2</seeerl></p>
</item>
</list>
<p>The semantics of them and their exact signatures are explained
@@ -64,9 +64,9 @@
<p>This function is called at the end of stage 1 of the
discovery process. It should return either the atom
<c>ignore</c> or <c>{ok, usm_entry() | [usm_entry()]}</c>. See
- <seealso marker="snmp_agent_config_files#usm">usm_entry()</seealso>
+ <seeguide marker="snmp_agent_config_files#usm">usm_entry()</seeguide>
and
- <seealso marker="snmpa_conf#usm_entry">usm_entry/13</seealso>
+ <seeerl marker="snmpa_conf#usm_entry">usm_entry/13</seeerl>
for more info. </p>
<p>If the function returns <c>ignore</c>, then it is assumed that
@@ -80,12 +80,12 @@
<p>In either case, the agent will do nothing, but return
the retrieved ManagerEngineID (see
- <seealso marker="snmpa#discovery">discovery</seealso>
+ <seeerl marker="snmpa#discovery">discovery</seeerl>
for more info) and possible continue with stage 2 of
the discovery process. </p>
<p>The <c>ExtraInfo</c> argument is passed on from the
- <seealso marker="snmpa#discovery">discovery</seealso>
+ <seeerl marker="snmpa#discovery">discovery</seeerl>
function. </p>
<p>This function may return an updated <c>NewExtraInfo</c>
diff --git a/lib/snmp/doc/src/snmpa_error.xml b/lib/snmp/doc/src/snmpa_error.xml
index 7141c3752e..cbbbf39cc9 100644
--- a/lib/snmp/doc/src/snmpa_error.xml
+++ b/lib/snmp/doc/src/snmpa_error.xml
@@ -44,13 +44,13 @@
</p>
<p>Two simple implementation(s) is provided with the
toolkit; the modules
- <seealso marker="snmpa_error_logger">snmpa_error_logger</seealso>
+ <seeerl marker="snmpa_error_logger">snmpa_error_logger</seeerl>
which is the default and
- <seealso marker="snmpa_error_io">snmpa_error_io</seealso>.
+ <seeerl marker="snmpa_error_io">snmpa_error_io</seeerl>.
</p>
<p>The error report module is configured using the directive
<c>error_report_mod</c>, see
- <seealso marker="snmp_config#configuration_params">configuration parameters</seealso>.
+ <seeguide marker="snmp_config#configuration_params">configuration parameters</seeguide>.
</p>
<marker id="config_err"></marker>
diff --git a/lib/snmp/doc/src/snmpa_error_io.xml b/lib/snmp/doc/src/snmpa_error_io.xml
index 3b19348f02..608cf46172 100644
--- a/lib/snmp/doc/src/snmpa_error_io.xml
+++ b/lib/snmp/doc/src/snmpa_error_io.xml
@@ -37,7 +37,7 @@
<description>
<p>The module <c>snmpa_error_io</c> implements the
<c>snmp_error_report</c> behaviour
- (see <seealso marker="snmpa_error_report">snmpa_error_report</seealso>)
+ (see <seeerl marker="snmpa_error_report">snmpa_error_report</seeerl>)
containing two callback functions which are called in order to
report SNMP errors.
</p>
@@ -46,8 +46,8 @@
It is provided as an simple example.
</p>
<p>This module needs to be explicitly configured, see
- <seealso marker="snmpa_error#desc">snmpa_error</seealso> and
- <seealso marker="snmp_config#configuration_params">configuration parameters</seealso>.
+ <seeerl marker="snmpa_error#desc">snmpa_error</seeerl> and
+ <seeguide marker="snmp_config#configuration_params">configuration parameters</seeguide>.
</p>
</description>
<funcs>
diff --git a/lib/snmp/doc/src/snmpa_error_logger.xml b/lib/snmp/doc/src/snmpa_error_logger.xml
index 440dcff527..e6554ca49e 100644
--- a/lib/snmp/doc/src/snmpa_error_logger.xml
+++ b/lib/snmp/doc/src/snmpa_error_logger.xml
@@ -37,7 +37,7 @@
<description>
<p>The module <c>snmpa_error_logger</c> implements the
<c>snmpa_error_report</c> behaviour
- (see <seealso marker="snmpa_error_report">snmpa_error_report</seealso>)
+ (see <seeerl marker="snmpa_error_report">snmpa_error_report</seeerl>)
containing two callback functions which are called in order to
report SNMP errors.
</p>
@@ -48,8 +48,8 @@
</p>
<p>This module is the default error report module, but can be
explicitly configured, see
- <seealso marker="snmpa_error#desc">snmpa_error</seealso> and
- <seealso marker="snmp_config#configuration_params">configuration parameters</seealso>.
+ <seeerl marker="snmpa_error#desc">snmpa_error</seeerl> and
+ <seeguide marker="snmp_config#configuration_params">configuration parameters</seeguide>.
</p>
</description>
<funcs>
diff --git a/lib/snmp/doc/src/snmpa_mib_data.xml b/lib/snmp/doc/src/snmpa_mib_data.xml
index 6575fc43a7..1937e0f780 100644
--- a/lib/snmp/doc/src/snmpa_mib_data.xml
+++ b/lib/snmp/doc/src/snmpa_mib_data.xml
@@ -39,52 +39,52 @@
must export the following functions: </p>
<list type="bulleted">
<item>
- <seealso marker="#new">new/1</seealso>
+ <seeerl marker="#new">new/1</seeerl>
</item>
<item>
- <seealso marker="#close">close/1</seealso>
+ <seeerl marker="#close">close/1</seeerl>
</item>
<item>
- <seealso marker="#sync">sync/1</seealso>
+ <seeerl marker="#sync">sync/1</seeerl>
</item>
<item>
- <seealso marker="#load_mib">load_mib/4</seealso>
+ <seeerl marker="#load_mib">load_mib/4</seeerl>
</item>
<item>
- <seealso marker="#unload_mib">unload_mib/4</seealso>
+ <seeerl marker="#unload_mib">unload_mib/4</seeerl>
</item>
<item>
- <seealso marker="#lookup">lookup/2</seealso>
+ <seeerl marker="#lookup">lookup/2</seeerl>
</item>
<item>
- <seealso marker="#next">next/3</seealso>
+ <seeerl marker="#next">next/3</seeerl>
</item>
<item>
- <seealso marker="#register_subagent">register_subagent/3</seealso>
+ <seeerl marker="#register_subagent">register_subagent/3</seeerl>
</item>
<item>
- <seealso marker="#unregister_subagent">unregister_subagent/2</seealso>
+ <seeerl marker="#unregister_subagent">unregister_subagent/2</seeerl>
</item>
<item>
- <seealso marker="#which_mib">which_mib/2</seealso>
+ <seeerl marker="#which_mib">which_mib/2</seeerl>
</item>
<item>
- <seealso marker="#which_mibs">which_mibs/1</seealso>
+ <seeerl marker="#which_mibs">which_mibs/1</seeerl>
</item>
<item>
- <seealso marker="#whereis_mib">whereis_mib/2</seealso>
+ <seeerl marker="#whereis_mib">whereis_mib/2</seeerl>
</item>
<item>
- <seealso marker="#dump">dump/2</seealso>
+ <seeerl marker="#dump">dump/2</seeerl>
</item>
<item>
- <seealso marker="#info">info/1</seealso>
+ <seeerl marker="#info">info/1</seeerl>
</item>
<item>
- <seealso marker="#backup">backup/2</seealso>
+ <seeerl marker="#backup">backup/2</seeerl>
</item>
<item>
- <seealso marker="#code_change">code_change/4</seealso>
+ <seeerl marker="#code_change">code_change/4</seeerl>
</item>
</list>
@@ -98,15 +98,16 @@
</description>
- <section>
- <title>CALLBACK FUNCTIONS</title>
- <p>The following functions must be exported from a
- <c>mib-server</c> data callback module: </p>
-
- <marker id="new"></marker>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>CALLBACK FUNCTIONS</title>
+ <p>The following functions must be exported from a
+ <c>mib-server</c> data callback module: </p>
+
+ <marker id="new"></marker>
+ </fsdescription>
<func>
<name since="OTP R16B01">Module:new(Storage) -> State</name>
<fsummary>Create new (mib-server) data instance</fsummary>
@@ -381,7 +382,7 @@
<desc>
<p>Perform a code-change (upgrade or downgrade). </p>
<p>See
- <seealso marker="stdlib:gen_server">gen_server</seealso>
+ <seeerl marker="stdlib:gen_server">gen_server</seeerl>
for more info regarding the <c>Vsn</c> and <c>Extra</c> arguments. </p>
</desc>
diff --git a/lib/snmp/doc/src/snmpa_mib_storage.xml b/lib/snmp/doc/src/snmpa_mib_storage.xml
index 6db2f178a9..a0883c9981 100644
--- a/lib/snmp/doc/src/snmpa_mib_storage.xml
+++ b/lib/snmp/doc/src/snmpa_mib_storage.xml
@@ -44,40 +44,40 @@
must export the following functions: </p>
<list type="bulleted">
<item>
- <seealso marker="#open">open/5</seealso>
+ <seeerl marker="#open">open/5</seeerl>
</item>
<item>
- <seealso marker="#close">close/1</seealso>
+ <seeerl marker="#close">close/1</seeerl>
</item>
<item>
- <seealso marker="#read">read/2</seealso>
+ <seeerl marker="#read">read/2</seeerl>
</item>
<item>
- <seealso marker="#write">write/2</seealso>
+ <seeerl marker="#write">write/2</seeerl>
</item>
<item>
- <seealso marker="#delete1">delete/1</seealso>
+ <seeerl marker="#delete1">delete/1</seeerl>
</item>
<item>
- <seealso marker="#delete2">delete/2</seealso>
+ <seeerl marker="#delete2">delete/2</seeerl>
</item>
<item>
- <seealso marker="#match_object">match_object/2</seealso>
+ <seeerl marker="#match_object">match_object/2</seeerl>
</item>
<item>
- <seealso marker="#match_delete">match_delete/2</seealso>
+ <seeerl marker="#match_delete">match_delete/2</seeerl>
</item>
<item>
- <seealso marker="#tab2list">tab2list/1</seealso>
+ <seeerl marker="#tab2list">tab2list/1</seeerl>
</item>
<item>
- <seealso marker="#info">info/1</seealso>
+ <seeerl marker="#info">info/1</seeerl>
</item>
<item>
- <seealso marker="#sync">sync/1</seealso>
+ <seeerl marker="#sync">sync/1</seeerl>
</item>
<item>
- <seealso marker="#backup">backup/2</seealso>
+ <seeerl marker="#backup">backup/2</seeerl>
</item>
</list>
@@ -86,15 +86,16 @@
</description>
- <section>
- <title>CALLBACK FUNCTIONS</title>
- <p>The following functions must be exported from a
- <c>mib-server</c> data callback module: </p>
-
- <marker id="open"></marker>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>CALLBACK FUNCTIONS</title>
+ <p>The following functions must be exported from a
+ <c>mib-server</c> data callback module: </p>
+
+ <marker id="open"></marker>
+ </fsdescription>
<func>
<name since="OTP R16B01">Module:open(Name, RecordName, Fields, Type, Options) -> {ok, TabId} | {error, Reason}</name>
<fsummary>Create new (mib-server) data instance</fsummary>
diff --git a/lib/snmp/doc/src/snmpa_mpd.xml b/lib/snmp/doc/src/snmpa_mpd.xml
index 347be9bbbb..e9294430bd 100644
--- a/lib/snmp/doc/src/snmpa_mpd.xml
+++ b/lib/snmp/doc/src/snmpa_mpd.xml
@@ -38,7 +38,7 @@
<p>The module <c>snmpa_mpd</c> implements the version independent
Message Processing and Dispatch functionality in SNMP for the agent.
It is supposed to be used from a Network Interface process
- (<seealso marker="snmp_agent_netif">Definition of Agent Net if</seealso>).
+ (<seeguide marker="snmp_agent_netif">Definition of Agent Net if</seeguide>).
</p>
<marker id="init"></marker>
@@ -46,8 +46,8 @@
<section>
<title>DATA TYPES</title>
- <p>See the <seealso marker="snmpa_conf#types">
- data types in <c>snmpa_conf</c></seealso>.</p>
+ <p>See the <seeerl marker="snmpa_conf#types">
+ data types in <c>snmpa_conf</c></seeerl>.</p>
</section>
<funcs>
@@ -79,7 +79,7 @@
<v>TDomain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v>
<v>TAddr = {IpAddr, IpPort}</v>
<v>LocalEngineID = string()</v>
- <v>IpAddr = <seealso marker="kernel:inet#type-ip_address">inet:ip_address()</seealso></v>
+ <v>IpAddr = <seetype marker="kernel:inet#ip_address">inet:ip_address()</seetype></v>
<v>IpPort = inet:port_number()</v>
<v>State = mpd_state()</v>
<v>NoteStore = pid()</v>
@@ -159,10 +159,10 @@
</p>
<p><c>MsgData</c> is the message specific data used in
the SNMP message. This value is received in a
- <seealso marker="snmp_agent_netif#im_send_pdu"><c>send_pdu</c></seealso>
+ <seeguide marker="snmp_agent_netif#im_send_pdu"><c>send_pdu</c></seeguide>
or
- <seealso marker="snmp_agent_netif#im_send_pdu_req">
- <c>send_pdu_req</c></seealso>
+ <seeguide marker="snmp_agent_netif#im_send_pdu_req">
+ <c>send_pdu_req</c></seeguide>
message from the agent. In SNMPv1 and
SNMPv2c, this message data is the community string. In
SNMPv3, it is the context information.</p>
@@ -170,7 +170,7 @@
<c>To</c> is a list of destination addresses and
their corresponding security parameters. This value is
received in the same message from the agent and then transformed
- trough <seealso marker="#process_taddrs"><c>process_taddrs</c></seealso>
+ trough <seeerl marker="#process_taddrs"><c>process_taddrs</c></seeerl>
before passed to this function.</p>
<note>
@@ -201,9 +201,9 @@
</type>
<desc>
<p>Transforms addresses from internal MIB format to one
- more useful to <seealso marker="snmp_agent_netif">Agent Net if</seealso>.
+ more useful to <seeguide marker="snmp_agent_netif">Agent Net if</seeguide>.
</p>
- <p>See also <seealso marker="#generate_msg"><c>generate_msg</c>.</seealso>
+ <p>See also <seeerl marker="#generate_msg"><c>generate_msg</c>.</seeerl>
</p>
<marker id="discarded_pdu"></marker>
diff --git a/lib/snmp/doc/src/snmpa_network_interface.xml b/lib/snmp/doc/src/snmpa_network_interface.xml
index 88bca8eee1..4029bbb708 100644
--- a/lib/snmp/doc/src/snmpa_network_interface.xml
+++ b/lib/snmp/doc/src/snmpa_network_interface.xml
@@ -40,19 +40,19 @@
must export the following functions: </p>
<list type="bulleted">
<item>
- <p><seealso marker="#start_link">start_link/4</seealso></p>
+ <p><seeerl marker="#start_link">start_link/4</seeerl></p>
</item>
<item>
- <p><seealso marker="#info">info/1</seealso></p>
+ <p><seeerl marker="#info">info/1</seeerl></p>
</item>
<item>
- <p><seealso marker="#get_log_type">get_log_type/1</seealso></p>
+ <p><seeerl marker="#get_log_type">get_log_type/1</seeerl></p>
</item>
<item>
- <p><seealso marker="#set_log_type">set_log_type/2</seealso></p>
+ <p><seeerl marker="#set_log_type">set_log_type/2</seeerl></p>
</item>
<item>
- <p><seealso marker="#verbosity">verbosity/2</seealso></p>
+ <p><seeerl marker="#verbosity">verbosity/2</seeerl></p>
</item>
</list>
<p>The semantics of them and their exact signatures are explained
@@ -60,7 +60,7 @@
<p>But this is not enough. There is also a set of <em>mandatory</em>
messages which the network interface entity must be able to
receive and be able to send. This is described in chapter
- <seealso marker="snmp_agent_netif">snmp_agent_netif</seealso>.
+ <seeguide marker="snmp_agent_netif">snmp_agent_netif</seeguide>.
</p>
<marker id="start_link"></marker>
@@ -104,7 +104,7 @@
memory allocation and various socket information.</p>
<p>The info returned by this function is returned together with other
info collected by the agent when the
- <seealso marker="snmpa#info">info</seealso> function is called
+ <seeerl marker="snmpa#info">info</seeerl> function is called
(tagged with with the key <c>net_if</c>).</p>
<marker id="verbosity"></marker>
@@ -138,7 +138,7 @@
So, it is this process that has to retrieve the actual log-type. </p>
<!--
<p>See
- <seealso marker="snmpa#get_log_type">get_log_type</seealso>
+ <seeerl marker="snmpa#get_log_type">get_log_type</seeerl>
for more info. </p>
-->
@@ -159,7 +159,7 @@
So, it is this process that has to do the actual changing of the
type. </p>
<p>See
- <seealso marker="snmpa#set_log_type">set_log_type</seealso>
+ <seeerl marker="snmpa#set_log_type">set_log_type</seeerl>
for more info. </p>
</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 58d4f59209..f5556ddfd6 100644
--- a/lib/snmp/doc/src/snmpa_network_interface_filter.xml
+++ b/lib/snmp/doc/src/snmpa_network_interface_filter.xml
@@ -40,16 +40,16 @@
must export the following functions: </p>
<list type="bulleted">
<item>
- <p><seealso marker="#accept_recv">accept_recv/2</seealso></p>
+ <p><seeerl marker="#accept_recv">accept_recv/2</seeerl></p>
</item>
<item>
- <p><seealso marker="#accept_send">accept_send/2</seealso></p>
+ <p><seeerl marker="#accept_send">accept_send/2</seeerl></p>
</item>
<item>
- <p><seealso marker="#accept_recv_pdu">accept_recv_pdu/3</seealso></p>
+ <p><seeerl marker="#accept_recv_pdu">accept_recv_pdu/3</seeerl></p>
</item>
<item>
- <p><seealso marker="#accept_send_pdu">accept_send_pdu/2</seealso></p>
+ <p><seeerl marker="#accept_send_pdu">accept_send_pdu/2</seeerl></p>
</item>
</list>
<p>The semantics of them and their exact signatures are explained
@@ -76,9 +76,9 @@
(<c>snmpa_net_if</c>). The default filter accepts all messages.</p>
<p>A network interface filter can e.g. be used during testing or for load
regulation. If the intended use is load regulation, see also
- <seealso marker="snmp_config#agent_ni_req_limit">req_limit</seealso> and
+ <seeguide marker="snmp_config#agent_ni_req_limit">req_limit</seeguide> and
the function
- <seealso marker="snmpa#register_notification_filter">register_notification_filter</seealso>. </p>
+ <seeerl marker="snmpa#register_notification_filter">register_notification_filter</seeerl>. </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
@@ -95,8 +95,8 @@ 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>
+ <p>See also the <seeerl marker="snmpa_conf#types">
+ data types in <c>snmpa_conf</c></seeerl>.</p>
<marker id="accept_recv"></marker>
</section>
<funcs>
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 963142f788..5e3c64e3f0 100644
--- a/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml
+++ b/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml
@@ -46,10 +46,10 @@
must export the following functions: </p>
<list type="bulleted">
<item>
- <p><seealso marker="#delivery_targets/3">delivery_targets/3</seealso></p>
+ <p><seemfa marker="#delivery_targets/3">delivery_targets/3</seemfa></p>
</item>
<item>
- <p><seealso marker="#delivery_info/4">delivery_info/4</seealso></p>
+ <p><seemfa marker="#delivery_info/4">delivery_info/4</seemfa></p>
</item>
</list>
<p>The semantics of them and their exact signatures are explained
@@ -67,8 +67,8 @@
<section>
<title>DATA TYPES</title>
- <p>See the <seealso marker="snmpa_conf#types">
- data types in <c>snmpa_conf</c></seealso>.</p>
+ <p>See the <seeerl marker="snmpa_conf#types">
+ data types in <c>snmpa_conf</c></seeerl>.</p>
<marker id="accept_recv"></marker>
<marker id="delivery_targets"></marker>
diff --git a/lib/snmp/doc/src/snmpa_notification_filter.xml b/lib/snmp/doc/src/snmpa_notification_filter.xml
index 317f291fd7..0eedcb66e6 100644
--- a/lib/snmp/doc/src/snmpa_notification_filter.xml
+++ b/lib/snmp/doc/src/snmpa_notification_filter.xml
@@ -64,7 +64,7 @@
the notification, return <c>{send, NewNotif}</c> or
suppress the notification, return <c>dont_send</c>.</p>
<p><c>Data</c> is supplied at filter registration time,
- see <seealso marker="snmpa#register_notification_filter">register_notification_filter</seealso>.
+ see <seeerl marker="snmpa#register_notification_filter">register_notification_filter</seeerl>.
</p>
</desc>
</func>
diff --git a/lib/snmp/doc/src/snmpa_supervisor.xml b/lib/snmp/doc/src/snmpa_supervisor.xml
index 935af365e4..f11d669996 100644
--- a/lib/snmp/doc/src/snmpa_supervisor.xml
+++ b/lib/snmp/doc/src/snmpa_supervisor.xml
@@ -55,7 +55,7 @@
started by calling <c>start_sub_agent/3</c>.
</p>
<p><c>db_dir</c> is mandatory.</p>
- <p>See <seealso marker="snmp_config#configuration_params">configuration parameters</seealso> for
+ <p>See <seeguide marker="snmp_config#configuration_params">configuration parameters</seeguide> for
a description of the options.</p>
</desc>
</func>
@@ -77,7 +77,7 @@
</p>
<p><c>db_dir</c> is mandatory.</p>
<p><c>dir</c> in config is mandatory.</p>
- <p>See <seealso marker="snmp_config">snmp config</seealso> for
+ <p>See <seeguide marker="snmp_config">snmp config</seeguide> for
a description of the options.</p>
</desc>
</func>
diff --git a/lib/snmp/doc/src/snmpc_cmd.xml b/lib/snmp/doc/src/snmpc_cmd.xml
index e4e710a58c..3c6c96f34b 100644
--- a/lib/snmp/doc/src/snmpc_cmd.xml
+++ b/lib/snmp/doc/src/snmpc_cmd.xml
@@ -45,12 +45,12 @@
<fsummary>Compile MIBs</fsummary>
<desc>
<p><c><![CDATA[snmpc]]></c> compile a SNMP MIB file,
- see <seealso marker="snmpc#compile">compile/1,2</seealso> for
+ see <seeerl marker="snmpc#compile">compile/1,2</seeerl> for
more info. </p>
<p>It can also be used to generate a header file (.hrl)
with definitions of Erlang constants for the objects in
the MIB, see
- <seealso marker="snmpc#mib_to_hrl">mib_to_hrl/1</seealso>. </p>
+ <seeerl marker="snmpc#mib_to_hrl">mib_to_hrl/1</seeerl>. </p>
<marker id="options"></marker>
</desc>
@@ -214,9 +214,9 @@
<section>
<title>SEE ALSO</title>
- <p><seealso marker="erts:erlc">erlc(1)</seealso>,
- <seealso marker="compiler:compile">compile(3)</seealso>,
- <seealso marker="snmp:snmpc">snmpc(3)</seealso></p>
+ <p><seecom marker="erts:erlc">erlc(1)</seecom>,
+ <seeerl marker="compiler:compile">compile(3)</seeerl>,
+ <seeerl marker="snmp:snmpc">snmpc(3)</seeerl></p>
</section>
</comref>
diff --git a/lib/snmp/doc/src/snmpm.xml b/lib/snmp/doc/src/snmpm.xml
index 71308dcf80..721b3d36ec 100644
--- a/lib/snmp/doc/src/snmpm.xml
+++ b/lib/snmp/doc/src/snmpm.xml
@@ -70,8 +70,8 @@ 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>
+ <p>See also the <seeerl marker="snmpa_conf#types">
+ data types in <c>snmpa_conf</c></seeerl>.</p>
<marker id="monitor"></marker>
</section>
@@ -298,7 +298,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
the manager shall handle.
Note that there is an alternate way to do the same thing:
Add the agent to the manager config files (see
- <seealso marker="snmp_manager_config_files#agents">agents.conf</seealso>).</p>
+ <seeguide marker="snmp_manager_config_files#agents">agents.conf</seeguide>).</p>
<p><c>TargetName</c> is a non-empty string,
uniquely identifying the agent. </p>
<p>The type of <c>Val</c> depends on <c>Item</c>: </p>
@@ -371,7 +371,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
<p>Update agent config. The function <c>update_agent_info/3</c>
should be used when several values needs to be updated atomically. </p>
<p>See function
- <seealso marker="#register_agent">register_agent</seealso>
+ <seeerl marker="#register_agent">register_agent</seeerl>
for more info about what kind of items are allowed. </p>
<marker id="which_agents"></marker>
@@ -411,7 +411,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
<p>Explicitly instruct the manager to handle this USM user.
Note that there is an alternate way to do the same thing:
Add the usm user to the manager config files (see
- <seealso marker="snmp_manager_config_files#usm_user">usm.conf</seealso>).</p>
+ <seeguide marker="snmp_manager_config_files#usm_user">usm.conf</seeguide>).</p>
<p>The type of <c>Val</c> depends on <c>Item</c>: </p>
<code type="none"><![CDATA[
sec_name = string()
@@ -552,12 +552,14 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
when the agent was registered. </p>
<p>For <c>SnmpInfo</c>, see the user callback function
- <seealso marker="snmpm_user#handle_report">handle_report</seealso>.</p>
+ <seeerl marker="snmpm_user#handle_report">handle_report</seeerl>.</p>
- <marker id="sync_get"></marker>
+ <!-- <marker id="sync_get"></marker> -->
+ <marker id="async_get2"></marker>
</desc>
</func>
+ <!-- DEPRECATED
<func>
<name since="">sync_get(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<name since="">sync_get(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
@@ -598,11 +600,12 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
<em>Any</em> tuple with <c>snmpm_extra_info_tag</c> as its first
element is reserved for internal use. </p>
<p>For <c>SnmpInfo</c>, see the user callback function
- <seealso marker="snmpm_user#handle_report">handle_report</seealso>.</p>
+ <seeerl marker="snmpm_user#handle_report">handle_report</seeerl>.</p>
<marker id="async_get2"></marker>
</desc>
</func>
+ -->
<func>
<name since="OTP R14B03">async_get2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
@@ -642,10 +645,12 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
for <em>this</em> request, they override any configuration done
when the agent was registered. </p>
- <marker id="async_get"></marker>
+ <!-- <marker id="async_get"></marker> -->
+ <marker id="sync_get_next2"></marker>
</desc>
</func>
+ <!-- DEPRECATED
<func>
<name since="">async_get(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
<name since="">async_get(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
@@ -681,6 +686,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
<marker id="sync_get_next2"></marker>
</desc>
</func>
+ -->
<func>
<name since="OTP R14B03">sync_get_next2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
@@ -730,12 +736,14 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
when the agent was registered. </p>
<p>For <c>SnmpInfo</c>, see the user callback function
- <seealso marker="snmpm_user#handle_report">handle_report</seealso>.</p>
+ <seeerl marker="snmpm_user#handle_report">handle_report</seeerl>.</p>
- <marker id="sync_get_next"></marker>
+ <!-- <marker id="sync_get_next"></marker> -->
+ <marker id="async_get_next2"></marker>
</desc>
</func>
+ <!-- DEPRECATED
<func>
<name since="">sync_get_next(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<name since="">sync_get_next(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
@@ -773,6 +781,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
<marker id="async_get_next2"></marker>
</desc>
</func>
+ -->
<func>
<name since="OTP R14B03">async_get_next2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
@@ -809,10 +818,12 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
for <em>this</em> request, they override any configuration done
when the agent was registered. </p>
- <marker id="async_get_next"></marker>
+ <!-- <marker id="async_get_next"></marker> -->
+ <marker id="sync_set2"></marker>
</desc>
</func>
+ <!-- DEPRECATED
<func>
<name since="">async_get_next(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
<name since="">async_get_next(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
@@ -847,6 +858,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
<marker id="sync_set2"></marker>
</desc>
</func>
+ -->
<func>
<name since="OTP R14B03">sync_set2(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
@@ -899,12 +911,14 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
when the agent was registered. </p>
<p>For <c>SnmpInfo</c>, see the user callback function
- <seealso marker="snmpm_user#handle_report">handle_report</seealso>.</p>
+ <seeerl marker="snmpm_user#handle_report">handle_report</seeerl>.</p>
- <marker id="sync_set"></marker>
+ <!-- <marker id="sync_set"></marker> -->
+ <marker id="async_set2"></marker>
</desc>
</func>
+ <!-- DEPRECATED
<func>
<name since="">sync_set(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
@@ -944,6 +958,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
<marker id="async_set2"></marker>
</desc>
</func>
+ -->
<func>
<name since="OTP R14B03">async_set2(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
@@ -985,10 +1000,12 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
for <em>this</em> request, they override any configuration done
when the agent was registered. </p>
- <marker id="async_set"></marker>
+ <!-- <marker id="async_set"></marker> -->
+ <marker id="sync_get_bulk2"></marker>
</desc>
</func>
+ <!-- DEPRECATED
<func>
<name since="">async_set(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
<name since="">async_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
@@ -1024,6 +1041,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
<marker id="sync_get_bulk2"></marker>
</desc>
</func>
+ -->
<func>
<name since="OTP R14B03">sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
@@ -1075,12 +1093,14 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
when the agent was registered. </p>
<p>For <c>SnmpInfo</c>, see the user callback function
- <seealso marker="snmpm_user#handle_report">handle_report</seealso>.</p>
+ <seeerl marker="snmpm_user#handle_report">handle_report</seeerl>.</p>
- <marker id="sync_get_bulk"></marker>
+ <!-- <marker id="sync_get_bulk"></marker> -->
+ <marker id="async_get_bulk2"></marker>
</desc>
</func>
+ <!-- DEPRECATED
<func>
<name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
@@ -1119,6 +1139,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
<marker id="async_get_bulk2"></marker>
</desc>
</func>
+ -->
<func>
<name since="OTP R14B03">async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
@@ -1157,10 +1178,12 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
for <em>this</em> request, they override any configuration done
when the agent was registered. </p>
- <marker id="async_get_bulk"></marker>
+ <!-- <marker id="async_get_bulk"></marker> -->
+ <marker id="cancel_async_request"></marker>
</desc>
</func>
+ <!-- DEPRECATED
<func>
<name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
<name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
@@ -1197,6 +1220,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
<marker id="cancel_async_request"></marker>
</desc>
</func>
+ -->
<func>
<name since="">cancel_async_request(UserId, ReqId) -> ok | {error, Reason}</name>
@@ -1249,7 +1273,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
during conversion. This could be usefull when converting large
logs (when otherwise the log could wrap during conversion).
Defaults to <c>true</c>. </p>
- <p>See <seealso marker="snmp#log_to_txt">snmp:log_to_txt</seealso>
+ <p>See <seeerl marker="snmp#log_to_txt">snmp:log_to_txt</seeerl>
for more info.</p>
<marker id="log_to_io"></marker>
@@ -1291,7 +1315,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
during conversion. This could be usefull when converting large
logs (when otherwise the log could wrap during conversion).
Defaults to <c>true</c>. </p>
- <p>See <seealso marker="snmp#log_to_io">snmp:log_to_io</seealso>
+ <p>See <seeerl marker="snmp#log_to_io">snmp:log_to_io</seeerl>
for more info.</p>
<marker id="change_log_size"></marker>
@@ -1527,7 +1551,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</item>
<item>
<p>The <c>Reason</c> parameter in the
- <seealso marker="snmpm_user#handle_error">handle_error</seealso> user callback function.</p>
+ <seeerl marker="snmpm_user#handle_error">handle_error</seeerl> user callback function.</p>
</item>
</list>
<p><c>Prefix</c> should either be an indention string
diff --git a/lib/snmp/doc/src/snmpm_conf.xml b/lib/snmp/doc/src/snmpm_conf.xml
index 4d10948ece..e3c9322a32 100644
--- a/lib/snmp/doc/src/snmpm_conf.xml
+++ b/lib/snmp/doc/src/snmpm_conf.xml
@@ -54,7 +54,7 @@
<c>manager.conf</c>. </p>
<p>The type of <c>Val</c> depends on the value of <c>Tag</c>,
see
- <seealso marker="snmp_manager_config_files#manager_information">Manager Information</seealso>
+ <seeguide marker="snmp_manager_config_files#manager_information">Manager Information</seeguide>
for more info. </p>
<marker id="write_manager_config"></marker>
</desc>
@@ -75,7 +75,7 @@
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_manager_config_files#manager_information">Manager Information</seealso>
+ <seeguide marker="snmp_manager_config_files#manager_information">Manager Information</seeguide>
for more info. </p>
<marker id="append_manager_config"></marker>
</desc>
@@ -92,7 +92,7 @@
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_manager_config_files#manager_information">Manager Information</seealso>
+ <seeguide marker="snmp_manager_config_files#manager_information">Manager Information</seeguide>
for more info. </p>
<marker id="read_manager_config"></marker>
</desc>
@@ -109,7 +109,7 @@
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_manager_config_files#manager_information">Manager Information</seealso>
+ <seeguide marker="snmp_manager_config_files#manager_information">Manager Information</seeguide>
for more info. </p>
<marker id="users_entry"></marker>
</desc>
@@ -133,7 +133,7 @@
<p><c>users_entry(UserId, UserMod)</c> translates to the following
call: <c>users_entry(UserId, UserMod, undefined)</c>. </p>
<p>See
- <seealso marker="snmp_manager_config_files#users">Users</seealso>
+ <seeguide marker="snmp_manager_config_files#users">Users</seeguide>
for more info. </p>
<marker id="write_users_config"></marker>
</desc>
@@ -155,7 +155,7 @@
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_manager_config_files#users">Users</seealso>
+ <seeguide marker="snmp_manager_config_files#users">Users</seeguide>
for more info. </p>
<marker id="append_users_config"></marker>
</desc>
@@ -173,7 +173,7 @@
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_manager_config_files#users">Users</seealso>
+ <seeguide marker="snmp_manager_config_files#users">Users</seeguide>
for more info. </p>
<marker id="read_users_config"></marker>
</desc>
@@ -190,7 +190,7 @@
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_manager_config_files#users">Users</seealso>
+ <seeguide marker="snmp_manager_config_files#users">Users</seeguide>
for more info. </p>
<marker id="agents_entry"></marker>
</desc>
@@ -217,7 +217,7 @@
<p>Create an entry for the manager agents config file,
<c>agents.conf</c>. </p>
<p>See
- <seealso marker="snmp_manager_config_files#agents">Agents</seealso>
+ <seeguide marker="snmp_manager_config_files#agents">Agents</seeguide>
for more info. </p>
<marker id="write_agents_config"></marker>
</desc>
@@ -239,7 +239,7 @@
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_manager_config_files#agents">Agents</seealso>
+ <seeguide marker="snmp_manager_config_files#agents">Agents</seeguide>
for more info. </p>
<marker id="append_agents_config"></marker>
</desc>
@@ -257,7 +257,7 @@
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_manager_config_files#agents">Agents</seealso>
+ <seeguide marker="snmp_manager_config_files#agents">Agents</seeguide>
for more info. </p>
<marker id="read_agents_config"></marker>
</desc>
@@ -274,7 +274,7 @@
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_manager_config_files#agents">Agents</seealso>
+ <seeguide marker="snmp_manager_config_files#agents">Agents</seeguide>
for more info. </p>
<marker id="usm_entry"></marker>
</desc>
@@ -297,7 +297,7 @@
<p>Create an entry for the agent community config file,
<c>community.conf</c>. </p>
<p>See
- <seealso marker="snmp_manager_config_files#usm">Security data for USM</seealso>
+ <seeguide marker="snmp_manager_config_files#usm">Security data for USM</seeguide>
for more info. </p>
<marker id="write_usm_config"></marker>
</desc>
@@ -319,7 +319,7 @@
<p><c>Hdr</c> is an optional file header (note that this text is
written to the file as is). </p>
<p>See
- <seealso marker="snmp_manager_config_files#usm">Security data for USM</seealso>
+ <seeguide marker="snmp_manager_config_files#usm">Security data for USM</seeguide>
for more info. </p>
<marker id="append_usm_config"></marker>
</desc>
@@ -337,7 +337,7 @@
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_manager_config_files#usm">Security data for USM</seealso>
+ <seeguide marker="snmp_manager_config_files#usm">Security data for USM</seeguide>
for more info. </p>
<marker id="read_usm_config"></marker>
</desc>
@@ -354,7 +354,7 @@
<p><c>Dir</c> is the path to the directory where to store the
config file. </p>
<p>See
- <seealso marker="snmp_manager_config_files#usm">Security data for USM</seealso>
+ <seeguide marker="snmp_manager_config_files#usm">Security data for USM</seeguide>
for more info. </p>
<marker id="end"></marker>
</desc>
diff --git a/lib/snmp/doc/src/snmpm_mpd.xml b/lib/snmp/doc/src/snmpm_mpd.xml
index e48a20d8cc..2374090c53 100644
--- a/lib/snmp/doc/src/snmpm_mpd.xml
+++ b/lib/snmp/doc/src/snmpm_mpd.xml
@@ -38,7 +38,7 @@
<p>The module <c>snmpm_mpd</c> implements the version independent
Message Processing and Dispatch functionality in SNMP for the manager.
It is supposed to be used from a Network Interface process
- (<seealso marker="snmp_manager_netif">Definition of Manager Net if</seealso>).
+ (<seeguide marker="snmp_manager_netif">Definition of Manager Net if</seeguide>).
</p>
<p>Legacy API function <c>process_msg/7</c> that has got separate
@@ -69,7 +69,7 @@
<type>
<v>Msg = binary()</v>
<v>Domain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v>
- <v>Addr = {<seealso marker="kernel:inet#type-ip_address">inet:ip_address(), inet:port_number()</seealso>} </v>
+ <v>Addr = {<seetype marker="kernel:inet#ip_address">inet:ip_address(), inet:port_number()</seetype>} </v>
<v>State = mpd_state()</v>
<v>NoteStore = pid()</v>
<v>Logger = function()</v>
@@ -133,7 +133,7 @@
</p>
<p><c>MsgData</c> is the message specific data used in the SNMP
message. This value is received from the
- <seealso marker="snmpm_mpd#process_msg">process_msg</seealso>
+ <seeerl marker="snmpm_mpd#process_msg">process_msg</seeerl>
function.
</p>
</desc>
diff --git a/lib/snmp/doc/src/snmpm_network_interface.xml b/lib/snmp/doc/src/snmpm_network_interface.xml
index cd835a1035..d6e58fbdb4 100644
--- a/lib/snmp/doc/src/snmpm_network_interface.xml
+++ b/lib/snmp/doc/src/snmpm_network_interface.xml
@@ -40,31 +40,31 @@
must export the following functions: </p>
<list type="bulleted">
<item>
- <p><seealso marker="#start_link">start_link/2</seealso></p>
+ <p><seeerl marker="#start_link">start_link/2</seeerl></p>
</item>
<item>
- <p><seealso marker="#stop">stop/1</seealso></p>
+ <p><seeerl marker="#stop">stop/1</seeerl></p>
</item>
<item>
- <p><seealso marker="#send_pdu">send_pdu/7</seealso></p>
+ <p><seeerl marker="#send_pdu">send_pdu/7</seeerl></p>
</item>
<item>
- <p><seealso marker="#inform_response">inform_response/4</seealso></p>
+ <p><seeerl marker="#inform_response">inform_response/4</seeerl></p>
</item>
<item>
- <p><seealso marker="#note_store">note_store/2</seealso></p>
+ <p><seeerl marker="#note_store">note_store/2</seeerl></p>
</item>
<item>
- <p><seealso marker="#info">info/1</seealso></p>
+ <p><seeerl marker="#info">info/1</seeerl></p>
</item>
<item>
- <p><seealso marker="#get_log_type">get_log_type/1</seealso></p>
+ <p><seeerl marker="#get_log_type">get_log_type/1</seeerl></p>
</item>
<item>
- <p><seealso marker="#set_log_type">set_log_type/2</seealso></p>
+ <p><seeerl marker="#set_log_type">set_log_type/2</seeerl></p>
</item>
<item>
- <p><seealso marker="#verbosity">verbosity/2</seealso></p>
+ <p><seeerl marker="#verbosity">verbosity/2</seeerl></p>
</item>
</list>
<p>The semantics of them and their exact signatures are explained
@@ -116,7 +116,7 @@
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
<v>MsgData = term()</v>
<v>Domain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v>
- <v>Addr = {<seealso marker="kernel:inet#type-ip_address">inet:ip_address(), inet:port_number()</seealso>} </v>
+ <v>Addr = {<seetype marker="kernel:inet#ip_address">inet:ip_address(), inet:port_number()</seetype>} </v>
<v>ExtraInfo = term()</v>
</type>
<desc>
@@ -125,13 +125,13 @@
<p><c>ExtraInfo</c> is some opaque data that is passed to the
net-if process. It originates from the <c>ExtraInfo</c>
parameter in the calls to the
- <seealso marker="snmpm#sync_get">synchronous get-request</seealso>,
- <seealso marker="snmpm#async_get">asynchronous get-request</seealso>,
- <seealso marker="snmpm#sync_get_next">synchronous get-next-request</seealso>,
- <seealso marker="snmpm#async_get_next">asynchronous get-next-request</seealso>,
- <seealso marker="snmpm#sync_set">synchronous set-request</seealso>
+ <seeerl marker="snmpm#sync_get2">synchronous get-request</seeerl>,
+ <seeerl marker="snmpm#async_get2">asynchronous get-request</seeerl>,
+ <seeerl marker="snmpm#sync_get_next2">synchronous get-next-request</seeerl>,
+ <seeerl marker="snmpm#async_get_next2">asynchronous get-next-request</seeerl>,
+ <seeerl marker="snmpm#sync_set2">synchronous set-request</seeerl>
and
- <seealso marker="snmpm#async_set">asynchronous set-request</seealso>
+ <seeerl marker="snmpm#async_set2">asynchronous set-request</seeerl>
functions.
Whether the net-if process chooses
to use this is implementation dependent. The net-if process
@@ -190,7 +190,7 @@
memory allocation and various socket information.</p>
<p>The info returned by this function is returned together with other
info collected by the manager when the
- <seealso marker="snmpm#info">info</seealso> function is called
+ <seeerl marker="snmpm#info">info</seeerl> function is called
(tagged with the key <c>net_if</c>).</p>
<marker id="verbosity"></marker>
@@ -224,7 +224,7 @@
So, it is this process that has to return the actual log-type. </p>
<!--
<p>See
- <seealso marker="snmpm#get_log_type">get_log_type</seealso>
+ <seeerl marker="snmpm#get_log_type">get_log_type</seeerl>
for more info. </p>
-->
@@ -245,7 +245,7 @@
So, it is this process that has to do the actual changing of the
type. </p>
<p>See
- <seealso marker="snmpm#set_log_type">set_log_type</seealso>
+ <seeerl marker="snmpm#set_log_type">set_log_type</seeerl>
for more info. </p>
</desc>
</func>
diff --git a/lib/snmp/doc/src/snmpm_network_interface_filter.xml b/lib/snmp/doc/src/snmpm_network_interface_filter.xml
index 4ea441bf75..d4b787f919 100644
--- a/lib/snmp/doc/src/snmpm_network_interface_filter.xml
+++ b/lib/snmp/doc/src/snmpm_network_interface_filter.xml
@@ -40,16 +40,16 @@
must export the following functions: </p>
<list type="bulleted">
<item>
- <p><seealso marker="#accept_recv">accept_recv/2</seealso></p>
+ <p><seeerl marker="#accept_recv">accept_recv/2</seeerl></p>
</item>
<item>
- <p><seealso marker="#accept_send">accept_send/2</seealso></p>
+ <p><seeerl marker="#accept_send">accept_send/2</seeerl></p>
</item>
<item>
- <p><seealso marker="#accept_recv_pdu">accept_recv_pdu/3</seealso></p>
+ <p><seeerl marker="#accept_recv_pdu">accept_recv_pdu/3</seeerl></p>
</item>
<item>
- <p><seealso marker="#accept_send_pdu">accept_send_pdu/2</seealso></p>
+ <p><seeerl marker="#accept_send_pdu">accept_send_pdu/2</seeerl></p>
</item>
</list>
<p>The semantics of them and their exact signatures are explained
@@ -93,8 +93,8 @@ 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>
+ <p>See also the <seeerl marker="snmpa_conf#types">
+ data types in <c>snmpa_conf</c></seeerl>.</p>
<marker id="accept_recv"></marker>
</section>
diff --git a/lib/snmp/doc/src/snmpm_user.xml b/lib/snmp/doc/src/snmpm_user.xml
index fe0ac0b0d2..bea3cd88cd 100644
--- a/lib/snmp/doc/src/snmpm_user.xml
+++ b/lib/snmp/doc/src/snmpm_user.xml
@@ -130,7 +130,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
<fsummary>Handle agent</fsummary>
<type>
<v>Domain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v>
- <v>Addr = {<seealso marker="kernel:inet#type-ip_address">inet:ip_address(), inet:port_number()</seealso>} </v>
+ <v>Addr = {<seetype marker="kernel:inet#ip_address">inet:ip_address(), inet:port_number()</seetype>} </v>
<v>Type = pdu | trap | report | inform</v>
<v>SnmpInfo = SnmpPduInfo | SnmpTrapInfo | SnmpReportInfo | SnmpInformInfo</v>
<v>SnmpPduInfo = snmp_gen_info()</v>
@@ -148,7 +148,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
unknown agent.</p>
<p>Note that this will always be the default user that is called.</p>
<p>For more info about the <c>agent_config()</c>, see
- <seealso marker="snmpm#register_agent">register_agent</seealso>.</p>
+ <seeerl marker="snmpm#register_agent">register_agent</seeerl>.</p>
<p>The arguments <c>Type</c> and <c>SnmpInfo</c> relates in the
following way: </p>
@@ -156,22 +156,22 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
<list type="bulleted">
<item>
<p><c>pdu</c> - <c>SnmpPduInfo</c>
- (see <seealso marker="#handle_pdu">handle_pdu</seealso>
+ (see <seeerl marker="#handle_pdu">handle_pdu</seeerl>
for more info).</p>
</item>
<item>
<p><c>trap</c> - <c>SnmpTrapInfo</c>
- (see <seealso marker="#handle_trap">handle_trap</seealso>
+ (see <seeerl marker="#handle_trap">handle_trap</seeerl>
for more info).</p>
</item>
<item>
<p><c>report</c> - <c>SnmpReportInfo</c>
- (see <seealso marker="#handle_report">handle_report</seealso>
+ (see <seeerl marker="#handle_report">handle_report</seeerl>
for more info).</p>
</item>
<item>
<p><c>inform</c> - <c>SnmpInformInfo</c>
- (see <seealso marker="#handle_inform">handle_inform</seealso>
+ (see <seeerl marker="#handle_inform">handle_inform</seeerl>
for more info).</p>
</item>
</list>
@@ -195,9 +195,9 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</type>
<desc>
<p>Handle the reply to an asynchronous request, such as
- <seealso marker="snmpm#async_get">async_get</seealso>,
- <seealso marker="snmpm#async_get_next">async_get_next</seealso> or
- <seealso marker="snmpm#async_set">async_set</seealso>.</p>
+ <seeerl marker="snmpm#async_get2">async_get</seeerl>,
+ <seeerl marker="snmpm#async_get_next2">async_get_next</seeerl> or
+ <seeerl marker="snmpm#async_set2">async_set</seeerl>.</p>
<p>It could also be a late reply to a synchronous request.</p>
<p><c>ReqId</c> is returned by the asynchronous request function.</p>
@@ -219,7 +219,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
<desc>
<p>Handle a trap/notification message from an agent.</p>
<p>For more info about the <c>agent_config()</c>, see
- <seealso marker="snmpm#register_agent">register_agent</seealso></p>
+ <seeerl marker="snmpm#register_agent">register_agent</seeerl></p>
<p>The only user which would return
<c>{register, UserId, TargetName2, agent_info()}</c> is the
<em>default user</em>.</p>
@@ -242,12 +242,12 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
<desc>
<p>Handle a inform message.</p>
<p>For more info about the <c>agent_config()</c>, see
- <seealso marker="snmpm#register_agent">register_agent</seealso></p>
+ <seeerl marker="snmpm#register_agent">register_agent</seeerl></p>
<p>The only user which would return
<c>{register, UserId, TargetName2, AgentConfig}</c> is the
<em>default user</em>.</p>
<p>If the
- <seealso marker="snmp_config#manager_irb">inform request behaviour</seealso>
+ <seeguide marker="snmp_config#manager_irb">inform request behaviour</seeguide>
configuration option is set to <c>user</c> or
<c>{user, integer()}</c>, the response (acknowledgment) to this
inform-request will be sent when this function returns.</p>
@@ -272,7 +272,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
<desc>
<p>Handle a report message.</p>
<p>For more info about the <c>agent_config()</c>, see
- <seealso marker="snmpm#register_agent">register_agent</seealso></p>
+ <seeerl marker="snmpm#register_agent">register_agent</seeerl></p>
<p>The only user which would return
<c>{register, UserId, TargetName2, AgentConfig}</c> is the
<em>default user</em>.</p>
diff --git a/lib/snmp/examples/ex2/snmp_ex2_manager.erl b/lib/snmp/examples/ex2/snmp_ex2_manager.erl
index 939f9f2b4d..03559917cd 100644
--- a/lib/snmp/examples/ex2/snmp_ex2_manager.erl
+++ b/lib/snmp/examples/ex2/snmp_ex2_manager.erl
@@ -190,19 +190,19 @@ handle_call({oid_to_name, Oid}, _From, S) ->
{reply, Reply, S};
handle_call({sync_get, TargetName, Oids}, _From, S) ->
- Reply = (catch snmpm:sync_get(?USER, TargetName, Oids)),
+ Reply = (catch snmpm:sync_get2(?USER, TargetName, Oids)),
{reply, Reply, S};
handle_call({sync_get_next, TargetName, Oids}, _From, S) ->
- Reply = (catch snmpm:sync_get_next(?USER, TargetName, Oids)),
+ Reply = (catch snmpm:sync_get_next2(?USER, TargetName, Oids)),
{reply, Reply, S};
handle_call({sync_get_bulk, TargetName, NR, MR, Oids}, _From, S) ->
- Reply = (catch snmpm:sync_get_bulk(?USER, TargetName, NR, MR, Oids)),
+ Reply = (catch snmpm:sync_get_bulk2(?USER, TargetName, NR, MR, Oids)),
{reply, Reply, S};
handle_call({sync_set, TargetName, VarsAndVals}, _From, S) ->
- Reply = (catch snmpm:sync_set(?USER, TargetName, VarsAndVals)),
+ Reply = (catch snmpm:sync_set2(?USER, TargetName, VarsAndVals)),
{reply, Reply, S};
handle_call(Req, From, State) ->
diff --git a/lib/snmp/src/agent/snmp_framework_mib.erl b/lib/snmp/src/agent/snmp_framework_mib.erl
index 6db6f87a85..5db686d29b 100644
--- a/lib/snmp/src/agent/snmp_framework_mib.erl
+++ b/lib/snmp/src/agent/snmp_framework_mib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -52,6 +52,7 @@
get_engine_boots/0, get_engine_time/0,
set_engine_boots/1, set_engine_time/1,
table_next/2, check_status/3]).
+-export([which_trap_transport/1, which_req_transport/1, which_transport/2]).
-export([add_context/1, delete_context/1]).
-export([check_agent/2, check_context/1, order_agent/2]).
@@ -191,49 +192,198 @@ check_context(Context) ->
%% {Name, Value}.
%%-----------------------------------------------------------------
check_agent(Entry, undefined) ->
- check_agent(Entry, {snmp_target_mib:default_domain(), undefined});
-check_agent({intAgentTransportDomain, Domain}, {_, Port}) ->
- {snmp_conf:check_domain(Domain), {Domain, Port}};
-check_agent({intAgentUDPPort, Port}, {Domain, _}) ->
+ check_agent(Entry, #{domain => snmp_target_mib:default_domain(),
+ port => undefined});
+
+%% <BACKWARD-COMPAT>
+check_agent({intAgentTransportDomain, Domain},
+ #{transports := T} = State) when is_list(T) andalso (T =/= []) ->
+ ?vinfo("check_agent(intAgentTransportDomain) -> entry with"
+ "~n Domain: ~p"
+ "~n when"
+ "~n State: ~p", [Domain, State]),
+ error({transports_already_defined, T});
+check_agent({intAgentTransportDomain, Domain}, State) ->
+ ?vtrace("check_agent(intAgentTransportDomain) -> entry with"
+ "~n Domain: ~p", [Domain]),
+ {snmp_conf:check_domain(Domain), State#{domain => Domain}};
+%% </BACKWARD-COMPAT>
+
+check_agent({intAgentUDPPort, NewPort}, #{port := OldPort})
+ when is_integer(OldPort) ->
+ ?vinfo("check_agent(intAgentUDPPort) -> entry with"
+ "~n New Port: ~p"
+ "~n when"
+ "~n Old Port: ~p", [NewPort, OldPort]),
+ error({port_already_defined, OldPort});
+check_agent({intAgentUDPPort, Port}, State) ->
+ ?vtrace("check_agent(intAgentUDPPort) -> entry with"
+ "~n Port: ~p"
+ "~n when"
+ "~n State: ~p", [Port, State]),
ok = snmp_conf:check_port(Port),
- {ok, {Domain, Port}};
-check_agent({intAgentIpAddress, _}, {_, undefined}) ->
+ {ok, State#{port => Port}};
+
+%% <BACKWARD-COMPAT>
+check_agent({intAgentIpAddress, Ip}, #{port := undefined}) ->
+ ?vinfo("check_agent(intAgentIpAddress) -> "
+ "entry when port not defined with"
+ "~n Ip: ~p", [Ip]),
error({missing_mandatory, intAgentUDPPort});
-check_agent({intAgentIpAddress = Tag, Ip} = Entry, {Domain, Port} = State) ->
- {case snmp_conf:check_ip(Domain, Ip) of
- ok ->
- [Entry,
- {intAgentTransports, [{Domain, {Ip, Port}}]}];
- {ok, FixedIp} ->
- [{Tag, FixedIp},
- {intAgentTransports, [{Domain, {FixedIp, Port}}]}]
- end, State};
-check_agent({intAgentTransports = Tag, Transports}, {_, Port} = State)
+check_agent({intAgentIpAddress = _Tag, Ip} = _Entry,
+ #{transports := T} = _State) when (T =/= []) ->
+ ?vinfo("check_agent(intAgentIpAddress) -> "
+ "entry when transports already defined with"
+ "~n Ip: ~p"
+ "~n when"
+ "~n Transports: ~p", [Ip, T]),
+ error({transports_already_defined, T});
+check_agent({intAgentIpAddress = Tag, Ip} = _Entry,
+ #{domain := Domain, port := Port} = State) ->
+ ?vtrace("check_agent(intAgentIpAddress) -> entry with"
+ "~n Ip: ~p"
+ "~n when"
+ "~n Domain: ~p"
+ "~n Port: ~p", [Ip, Domain, Port]),
+ FixedIp = case snmp_conf:check_ip(Domain, Ip) of
+ ok ->
+ Ip;
+ {ok, FIp} ->
+ FIp
+ end,
+ T = [{Domain, {FixedIp, Port}, all, []}],
+ Rows = [{Tag, FixedIp}, {intAgentTransports, T}],
+ {Rows, State#{transports => T}};
+%% </BACKWARD-COMPAT>
+
+check_agent({intAgentTransports = _Tag, _Transports},
+ #{transports := T} = _State) when (T =/= []) ->
+ ?vinfo("check_agent(intAgentTransports) -> "
+ "entry when transports already defined with"
+ "~n T: ~p", [T]),
+ error({transports_already_defined, T});
+check_agent({intAgentTransports = Tag, Transports}, #{port := Port} = State)
when is_list(Transports) ->
+ ?vtrace("check_agent(intAgentTransports) -> entry when"
+ "~n Port: ~p", [Port]),
+ CheckAddress =
+ fun(D, A, undefined) ->
+ snmp_conf:check_address(D, A);
+ (D, A, P) ->
+ snmp_conf:check_address(D, A, P)
+ end,
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;
+ ?vtrace("check_agent(intAgentTransports) -> check transport: "
+ "~n Domain: ~p"
+ "~n Address: ~p", [Domain, Address]),
+ CheckedAddress =
+ case CheckAddress(Domain, Address, Port) of
+ ok ->
+ Address;
+ {ok, Address2} ->
+ Address2
+ end,
+ ?vtrace("check_agent(intAgentTransports) -> checked address: "
+ "~n ~p", [CheckedAddress]),
+ {Domain, CheckedAddress, all, []};
+
+ {Domain, Address, Kind} when is_atom(Kind) ->
+ ?vtrace("check_agent(intAgentTransports) -> check transport: "
+ "~n Domain: ~p"
+ "~n Address: ~p"
+ "~n Kind: ~p", [Domain, Address, Kind]),
+ ok = snmp_conf:check_transport_kind(Kind),
+ ?vtrace("check_agent(intAgentTransports) -> checked kind"),
+ case snmp_conf:check_transport_address(Domain, Address) of
+ true ->
+ ?vtrace("check_agent(intAgentTransports) -> "
+ "checked transport address"),
+ {Domain, Address, Kind, []};
+ false ->
+ ?vinfo("check_agent(intAgentTransports) -> "
+ "invalid transport address: "
+ "~n ~p", [Address]),
+ error({bad_transport_addr, Address})
+ end;
+
+ {Domain, Address, Opts} when is_list(Opts) ->
+ ?vtrace("check_agent(intAgentTransports) -> check transport: "
+ "~n Domain: ~p"
+ "~n Address: ~p"
+ "~n Opts: ~p", [Domain, Address, Opts]),
+ CheckedOpts = snmp_conf:check_transport_opts(Opts),
+ ?vtrace("check_agent(intAgentTransports) -> checked opts: "
+ "~n ~p", [CheckedOpts]),
+ case snmp_conf:check_transport_address(Domain, Address) of
+ true ->
+ ?vtrace("check_agent(intAgentTransports) -> "
+ "checked transport address"),
+ {Domain, Address, all, CheckedOpts};
+ false ->
+ ?vinfo("check_agent(intAgentTransports) -> "
+ "invalid transport address: "
+ "~n ~p", [Address]),
+ error({bad_transport_addr, Address})
+ end;
+
+ {Domain, Address, Kind, Opts} ->
+ ?vtrace("check_agent(intAgentTransports) -> check transport: "
+ "~n Domain: ~p"
+ "~n Address: ~p"
+ "~n Kind: ~p"
+ "~n Opts: ~p", [Domain, Address, Kind, Opts]),
+ ok = snmp_conf:check_transport_kind(Kind),
+ ?vtrace("check_agent(intAgentTransports) -> checked kind"),
+ CheckedOpts = snmp_conf:check_transport_opts(Opts),
+ ?vtrace("check_agent(intAgentTransports) -> checked opts: "
+ "~n ~p", [CheckedOpts]),
+ case snmp_conf:check_transport_address(Domain, Address) of
+ true ->
+ ?vtrace("check_agent(intAgentTransports) -> "
+ "checked transport address"),
+ {Domain, Address, Kind, CheckedOpts};
+ false ->
+ ?vinfo("check_agent(intAgentTransports) -> "
+ "invalid transport address: "
+ "~n ~p", [Address]),
+ error({bad_transport_addr, Address})
+ end;
+
_ ->
- error({bad_transport, Transport})
+ ?vinfo("check_agent(intAgentTransports) -> invalid transport:"
+ "~n ~p", [Transport]),
+ error({bad_transport, Transport})
end
|| Transport <- Transports],
+ validate_transports(CheckedTransports),
+ ?vtrace("check_agent(intAgentTransports) -> checked transports"),
{{ok, {Tag, CheckedTransports}}, State};
check_agent(Entry, State) ->
+ ?vtrace("check_agent -> entry when"
+ "~n Entry: ~p", [Entry]),
{check_agent(Entry), State}.
+%% Basically this is intended to check that there are no
+%% inconsistencies (between transports). Such as specifying
+%% both an old style transport (Kind = all) and transports
+%% with specified Kind:s (rep_responser or trap_sender).
+validate_transports(Transports) ->
+ validate_transports(Transports, false).
+
+validate_transports([] = _Transports, _All) ->
+ ok;
+validate_transports([{_Domain, _Addr, all, _Opts} | Transports], _All) ->
+ validate_transports(Transports, true);
+validate_transports([{_Domain, _Addr, Kind, _Opts} | _Transports], true)
+ when (Kind =/= all) ->
+ error({bad_transport_kind, Kind});
+validate_transports([{_Domain, _Addr, _Kind, _Opts} | Transports], All) ->
+ validate_transports(Transports, All).
+
+
+
%% This one is kept for backwards compatibility
check_agent({intAgentMaxPacketSize, Value}) ->
snmp_conf:check_packet_size(Value);
@@ -246,6 +396,7 @@ check_agent(X) ->
%% Ordering function to sort intAgentTransportDomain first
%% hence before intAgentIpAddress. Sort other entries on the key.
+%% Note that neither of these are required!
-dialyzer({nowarn_function, order_agent/2}).
order_agent(EntryA, EntryB) ->
snmp_conf:keyorder(
@@ -268,7 +419,7 @@ init_vars(Vars) ->
init_var({Var, Val}) ->
?vtrace("init var: "
- "~n set ~w to ~w",[Var, Val]),
+ "~n set ~w to ~w", [Var, Val]),
snmp_generic:variable_set(db(Var), Val).
init_tabs(Contexts) ->
@@ -432,6 +583,41 @@ intAgentTransports(Op) ->
snmp_generic:variable_func(Op, db(intAgentTransports)).
+which_trap_transport(Domain) when (Domain =:= snmpUDPDomain) ->
+ case which_transport(Domain, all) of
+ {value, _} = VALUE ->
+ VALUE;
+ false ->
+ which_transport(transportDomainUdpIpv4, all)
+ end;
+which_trap_transport(Domain) ->
+ case which_transport(Domain, trap_sender) of
+ {value, _} = VALUE ->
+ VALUE;
+ false ->
+ which_transport(Domain, all)
+ end.
+
+which_req_transport(Domain) ->
+ which_transport(Domain, req_responder).
+
+which_transport(Domain, Kind) ->
+ {value, Transports} = intAgentTransports(get),
+ which_transport(Domain, Kind, Transports).
+
+which_transport(_Domain, _Kind, []) ->
+ false;
+which_transport(Domain, Kind,
+ [{Domain, _Addr, _Kind, _Opts} = Transport|_Transports])
+ when (Kind =:= all) ->
+ {value, Transport};
+which_transport(Domain, Kind,
+ [{Domain, _Addr, Kind, _Opts} = Transport|_Transports]) ->
+ {value, Transport};
+which_transport(Domain, Kind, [_Transport|Transports]) ->
+ which_transport(Domain, Kind, Transports).
+
+
snmpEngineID(print) ->
VarAndValue = [{snmpEngineID, snmpEngineID(get)}],
diff --git a/lib/snmp/src/agent/snmpa.erl b/lib/snmp/src/agent/snmpa.erl
index ff7be54283..729789d487 100644
--- a/lib/snmp/src/agent/snmpa.erl
+++ b/lib/snmp/src/agent/snmpa.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -63,6 +63,8 @@
register_subagent/3, unregister_subagent/2,
+ which_transports/0,
+
send_notification2/3,
send_notification/3, send_notification/4, send_notification/5,
send_notification/6, send_notification/7,
@@ -123,7 +125,7 @@
mib_storage_options/0
]).
--deprecated([{old_info_format, 1, next_major_release}]).
+-deprecated([{old_info_format, 1, "use \"new\" format instead"}]).
-include("snmpa_atl.hrl").
@@ -832,6 +834,18 @@ sys_up_time() ->
%%%-----------------------------------------------------------------
+which_transports() ->
+ {value, Transports} = snmp_framework_mib:intAgentTransports(get),
+ [case Kind of
+ all ->
+ {Domain, Address};
+ _ ->
+ {Domain, Address, Kind}
+ end || {Domain, Address, Kind, _} <- Transports].
+
+
+%%%-----------------------------------------------------------------
+
restart_worker() ->
restart_worker(snmp_master_agent).
diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl
index cc3bef15af..5039b08391 100644
--- a/lib/snmp/src/agent/snmpa_agent.erl
+++ b/lib/snmp/src/agent/snmpa_agent.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@
-export([get_request_limit/1, set_request_limit/2]).
-export([invalidate_ca_cache/0]).
-export([increment_counter/3]).
--export([restart_worker/1, restart_set_worker/1]).
+-export([restart_worker/1, restart_set_worker/1, restart_notif_worker/1]).
%% For backward compatibillity
-export([send_trap/6, send_trap/7]).
@@ -71,7 +71,7 @@
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, tr_var/2, tr_varbind/1,
- handle_pdu/8, worker/2, worker_loop/1,
+ handle_pdu/8, worker/4, worker_loop/2,
do_send_trap/7, do_send_trap/8]).
%% <BACKWARD-COMPAT>
-export([handle_pdu/7,
@@ -177,6 +177,7 @@
worker,
worker_state = ready,
set_worker,
+ notif_worker,
multi_threaded,
ref,
vsns,
@@ -249,6 +250,9 @@ restart_worker(Agent) ->
restart_set_worker(Agent) ->
call(Agent, restart_set_worker).
+restart_notif_worker(Agent) ->
+ call(Agent, restart_notif_worker).
+
get_log_type(Agent) ->
call(Agent, get_log_type).
@@ -391,11 +395,12 @@ do_init(Prio, Parent, Ref, Options) ->
put(net_if, NetIfPid),
put(mibserver, MibPid),
process_flag(trap_exit, true),
- {Worker, SetWorker} = workers_start(MultiT),
+ {Worker, SetWorker, NotifWorker} = workers_start(MultiT),
{ok, #state{type = Type,
parent = Parent,
worker = Worker,
set_worker = SetWorker,
+ notif_worker = NotifWorker,
multi_threaded = MultiT,
ref = Ref,
vsns = Vsns,
@@ -975,9 +980,13 @@ handle_info({'EXIT', Pid, Reason}, #state{worker = Pid} = S) ->
NewWorker = worker_start(),
{noreply, S#state{worker = NewWorker}};
handle_info({'EXIT', Pid, Reason}, #state{set_worker = Pid} = S) ->
- ?vlog("set-worker (~p) exited -> create new ~n ~p", [Pid,Reason]),
+ ?vlog("set-worker (~p) exited -> create new ~n ~p", [Pid, Reason]),
NewWorker = set_worker_start(),
{noreply, S#state{set_worker = NewWorker}};
+handle_info({'EXIT', Pid, Reason}, #state{notif_worker = Pid} = S) ->
+ ?vlog("notif-worker (~p) exited -> create new ~n ~p", [Pid, Reason]),
+ NewWorker = notif_worker_start(),
+ {noreply, S#state{notif_worker = NewWorker}};
handle_info({'EXIT', Pid, Reason}, #state{parent = Pid} = S) ->
?vlog("parent (~p) exited for reason ~n~p", [Pid,Reason]),
{stop, {parent_died, Reason}, S};
@@ -1415,13 +1424,17 @@ handle_cast({verbosity, Verbosity}, S) ->
?vlog("verbosity: ~p -> ~p",[get(verbosity), Verbosity]),
put(verbosity,snmp_verbosity:validate(Verbosity)),
case S#state.worker of
- Pid when is_pid(Pid) -> Pid ! ?mk_verbosity_wreq(Verbosity);
+ Pid1 when is_pid(Pid1) -> Pid1 ! ?mk_verbosity_wreq(Verbosity);
_ -> ok
end,
case S#state.set_worker of
Pid2 when is_pid(Pid2) -> Pid2 ! ?mk_verbosity_wreq(Verbosity);
_ -> ok
end,
+ case S#state.notif_worker of
+ Pid3 when is_pid(Pid3) -> Pid3 ! ?mk_verbosity_wreq(Verbosity);
+ _ -> ok
+ end,
{noreply, S};
handle_cast({sub_agents_verbosity,Verbosity}, S) ->
@@ -1447,14 +1460,16 @@ handle_cast(Msg, S) ->
{noreply, S}.
-terminate(shutdown, #state{worker = Worker,
- set_worker = SetWorker,
- backup = Backup,
+terminate(shutdown, #state{worker = Worker,
+ set_worker = SetWorker,
+ notif_worker = NotifWorker,
+ backup = Backup,
ref = Ref}) ->
%% Ordered shutdown - stop misc-workers, net_if, mib-server and note-store.
backup_server_stop(Backup),
worker_stop(Worker, 100),
worker_stop(SetWorker, 100),
+ worker_stop(NotifWorker, 100),
snmpa_misc_sup:stop_net_if(Ref),
snmpa_misc_sup:stop_mib_server(Ref);
terminate(_Reason, _S) ->
@@ -1600,20 +1615,26 @@ backup_server_stop(_) ->
ok.
+workers_start(extended) ->
+ ?vdebug("start worker, set-worker and notif-worker", []),
+ {worker_start(), set_worker_start(), notif_worker_start()};
workers_start(true) ->
- ?vdebug("start worker and set-worker",[]),
- {worker_start(), set_worker_start()};
+ ?vdebug("start worker and set-worker", []),
+ {worker_start(), set_worker_start(), undefined};
workers_start(_) ->
- {undefined, undefined}.
+ {undefined, undefined, undefined}.
worker_start() ->
- worker_start(get()).
+ worker_start(mw, true, get()).
set_worker_start() ->
- worker_start([{master, self()} | get()]).
+ worker_start(sw, false, [{master, self()} | get()]).
+
+notif_worker_start() ->
+ worker_start(nw, false, [{master, self()} | get()]).
-worker_start(Dict) ->
- proc_lib:spawn_link(?MODULE, worker, [self(), Dict]).
+worker_start(SName, Report, Dict) ->
+ proc_lib:spawn_link(?MODULE, worker, [self(), SName, Report, Dict]).
%% worker_stop(Pid) ->
%% worker_stop(Pid, infinity).
@@ -1783,13 +1804,13 @@ do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
LocalEngineID, ExtraInfo, get(net_if)).
-worker(Master, Dict) ->
+worker(Master, SName, Report, Dict) ->
lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict),
- put(sname, worker_short_name(get(sname))),
+ put(sname, worker_short_name(get(sname), SName)),
?vlog("starting",[]),
- worker_loop(Master).
+ worker_loop(Master, Report).
-worker_loop(Master) ->
+worker_loop(Master, Report) ->
Res =
receive
#wrequest{cmd = handle_pdu,
@@ -1813,7 +1834,7 @@ worker_loop(Master) ->
C:E:S ->
exit({worker_crash, Req, C, E, S})
end,
- Master ! worker_available,
+ worker_maybe_announce_available(Master, Report),
HandlePduRes; % For debugging...
@@ -1840,7 +1861,7 @@ worker_loop(Master) ->
C:E:S ->
exit({worker_crash, Req, C, E, S})
end,
- Master ! worker_available,
+ worker_maybe_announce_available(Master, Report),
SendTrapRes; % For debugging...
@@ -1853,8 +1874,10 @@ worker_loop(Master) ->
#wrequest{cmd = terminate} ->
?vtrace("worker_loop -> received terminate request", []),
exit(normal);
-
-
+
+
+
+
%% *************************************************************
%%
%% Kept for backward compatibillity reasons
@@ -1904,7 +1927,12 @@ worker_loop(Master) ->
end,
?vtrace("worker_loop -> wrap with"
"~n ~p", [Res]),
- ?MODULE:worker_loop(Master).
+ ?MODULE:worker_loop(Master, Report).
+
+worker_maybe_announce_available(Master, true) ->
+ Master ! worker_available;
+worker_maybe_announce_available(_Master, _Report) ->
+ ok.
%%-----------------------------------------------------------------
@@ -2186,6 +2214,13 @@ do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds,
Recv, Vbs, LocalEngineID, ExtraInfo,
get(net_if)),
{ok, S};
+ master_agent when (S#state.multi_threaded =:= extended) ->
+ %% Send to main worker
+ ?vtrace("do_handle_send_trap -> send to notif-worker",[]),
+ S#state.notif_worker ! ?mk_send_trap_wreq(TrapRec, NotifyName,
+ ContextName, Recv, Vbs,
+ LocalEngineID, ExtraInfo),
+ {ok, S};
master_agent when S#state.worker_state =:= busy ->
%% Main worker busy => create new worker
?vtrace("do_handle_send_trap -> main worker busy: "
@@ -2195,7 +2230,7 @@ do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds,
{ok, S};
master_agent ->
%% Send to main worker
- ?vtrace("do_handle_send_trap -> send to main worker",[]),
+ ?vtrace("do_handle_send_trap -> send to main worker", []),
S#state.worker ! ?mk_send_trap_wreq(TrapRec, NotifyName,
ContextName, Recv, Vbs,
LocalEngineID, ExtraInfo),
@@ -3225,8 +3260,10 @@ mapfoldl(_F, _Eas, Accu, []) -> {Accu,[]}.
short_name(none) -> ma;
short_name(_Pid) -> sa.
-worker_short_name(ma) -> maw;
-worker_short_name(_) -> saw.
+worker_short_name(ma, mw) -> mamw;
+worker_short_name(ma, sw) -> masw;
+worker_short_name(ma, nw) -> manw;
+worker_short_name(_, _) -> saw.
trap_sender_short_name(ma) -> mats;
trap_sender_short_name(_) -> sats.
@@ -3318,27 +3355,33 @@ handle_set_request_limit(_, _) ->
{error, not_supported}.
-agent_info(#state{worker = W, set_worker = SW}) ->
- case (catch get_agent_info(W, SW)) of
+agent_info(#state{worker = W, set_worker = SW, notif_worker = NW}) ->
+ case (catch get_agent_info(W, SW, NW)) of
Info when is_list(Info) ->
Info;
E ->
[{error, E}]
end.
-get_agent_info(W, SW) ->
+get_agent_info(W, SW, NW) ->
MASz = proc_mem(self()),
- WSz = proc_mem(W),
- SWSz = proc_mem(SW),
ATSz = tab_mem(snmp_agent_table),
CCSz = tab_mem(snmp_community_cache),
VacmSz = tab_mem(snmpa_vacm),
- [{process_memory, [{master_agent, MASz},
- {worker, WSz},
- {set_worker, SWSz}]},
- {db_memory, [{agent, ATSz},
- {community_cache, CCSz},
- {vacm, VacmSz}]}].
+ [{process_memory,
+ [{master_agent, MASz}] ++
+ process_memory(worker, W) ++
+ process_memory(set_worker, SW) ++
+ process_memory(notif_worker, NW)},
+ {db_memory,
+ [{agent, ATSz},
+ {community_cache, CCSz},
+ {vacm, VacmSz}]}].
+
+process_memory(Tag, P) when is_pid(P) ->
+ [{Tag, proc_mem(P)}];
+process_memory(_, _) ->
+ [].
proc_mem(P) when is_pid(P) ->
case (catch erlang:process_info(P, memory)) of
diff --git a/lib/snmp/src/agent/snmpa_mib.erl b/lib/snmp/src/agent/snmpa_mib.erl
index ab1098514c..8e594213f9 100644
--- a/lib/snmp/src/agent/snmpa_mib.erl
+++ b/lib/snmp/src/agent/snmpa_mib.erl
@@ -40,14 +40,18 @@
which_cache_size/1
]).
-%% <BACKWARD-COMPAT>
--export([load_mibs/2, unload_mibs/2]).
-%% </BACKWARD-COMPAT>
+%% Utility exports
+-export([subscribe_gc_events/1, unsubscribe_gc_events/1]).
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
+%% <BACKWARD-COMPAT>
+-export([load_mibs/2, unload_mibs/2]).
+%% </BACKWARD-COMPAT>
+
+
-include_lib("kernel/include/file.hrl").
-include("snmpa_internal.hrl").
-include("snmp_types.hrl").
@@ -55,14 +59,15 @@
-include("snmp_debug.hrl").
--define(SERVER, ?MODULE).
--define(NO_CACHE, no_mibs_cache).
--define(DEFAULT_CACHE_USAGE, true).
--define(CACHE_GC_TICKTIME, timer:minutes(1)).
--define(DEFAULT_CACHE_AUTOGC, true).
--define(DEFAULT_CACHE_GCLIMIT, 100).
--define(DEFAULT_CACHE_AGE, timer:minutes(10)).
--define(CACHE_GC_TRIGGER, cache_gc_trigger).
+-define(SERVER, ?MODULE).
+-define(NO_CACHE, no_mibs_cache).
+-define(DEFAULT_CACHE_USAGE, true).
+-define(CACHE_GC_TICKTIME, timer:minutes(1)).
+-define(DEFAULT_CACHE_AUTOGC, true).
+-define(DEFAULT_CACHE_GCLIMIT, infinity). % 100).
+-define(DEFAULT_CACHE_GCVERBOSE, false).
+-define(DEFAULT_CACHE_AGE, timer:minutes(10)).
+-define(CACHE_GC_TRIGGER, cache_gc_trigger).
@@ -85,7 +90,8 @@
%%-----------------------------------------------------------------
-record(state,
{data, meo, teo, backup,
- cache, cache_tmr, cache_autogc, cache_gclimit, cache_age,
+ cache, cache_tmr, cache_autogc, cache_gclimit, cache_age,
+ cache_sub, cache_gcverbose = false,
data_mod}).
@@ -153,6 +159,13 @@ update_cache_opts(MibServer, Key, Value) ->
call(MibServer, {update_cache_opts, Key, Value}).
+subscribe_gc_events(MibServer) ->
+ call(MibServer, {subscribe_gc_events, self()}).
+
+unsubscribe_gc_events(MibServer) ->
+ call(MibServer, {unsubscribe_gc_events, self()}).
+
+
%%-----------------------------------------------------------------
%% Func: lookup/2
%% Purpose: Finds the mib entry corresponding to the Oid. If it is a
@@ -277,7 +290,7 @@ do_init(Prio, Mibs, Opts) ->
process_flag(trap_exit, true),
put(sname, ms),
put(verbosity, ?vvalidate(get_verbosity(Opts))),
- ?vlog("starting",[]),
+ ?vlog("starting", []),
%% Extract the cache options
{Cache, CacheOptions} =
@@ -291,9 +304,10 @@ do_init(Prio, Mibs, Opts) ->
Bad ->
throw({error, {bad_option, {cache, Bad}}})
end,
- CacheAutoGC = get_cacheopt_autogc(Cache, CacheOptions),
- CacheGcLimit = get_cacheopt_gclimit(Cache, CacheOptions),
- CacheAge = get_cacheopt_age(Cache, CacheOptions),
+ CacheAutoGC = get_cacheopt_autogc(Cache, CacheOptions),
+ CacheGcLimit = get_cacheopt_gclimit(Cache, CacheOptions),
+ CacheAge = get_cacheopt_age(Cache, CacheOptions),
+ CacheGcVerb = get_cacheopt_gcverbose(Cache, CacheOptions),
%% Maybe start the cache gc timer
CacheGcTimer =
@@ -322,15 +336,16 @@ do_init(Prio, Mibs, Opts) ->
?vdebug("started",[]),
MibDataMod:sync(Data2),
?vdebug("mib data synced",[]),
- {ok, #state{data = Data2,
- teo = TeOverride,
- meo = MeOverride,
- cache = Cache,
- cache_tmr = CacheGcTimer,
- cache_autogc = CacheAutoGC,
- cache_gclimit = CacheGcLimit,
- cache_age = CacheAge,
- data_mod = MibDataMod}};
+ {ok, #state{data = Data2,
+ teo = TeOverride,
+ meo = MeOverride,
+ cache = Cache,
+ cache_tmr = CacheGcTimer,
+ cache_autogc = CacheAutoGC,
+ cache_gclimit = CacheGcLimit,
+ cache_age = CacheAge,
+ cache_gcverbose = CacheGcVerb,
+ data_mod = MibDataMod}};
{'aborted at', Mib, _NewData, Reason} ->
?vinfo("failed loading mib ~p: ~p",[Mib,Reason]),
{error, {Mib, Reason}}
@@ -418,9 +433,43 @@ handle_call({update_cache_opts, Key, Value}, _From, State) ->
{Result, NewState} = handle_update_cache_opts(Key, Value, State),
{reply, Result, NewState};
+
+handle_call({subscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Sub} = State)
+ when (Sub =:= undefined) ->
+ ?vdebug("subscribe_gc_events: ~p => ok", [Pid]),
+ {reply, ok, State#state{cache_sub = Pid}};
+handle_call({subscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Pid} = State) ->
+ ?vinfo("subscribe_gc_events: ~p => error:already-subscribed", [Pid]),
+ {reply, {error, already_subscribed}, State};
+handle_call({subscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Sub} = State)
+ when is_pid(Sub) andalso (Pid =/= Sub) ->
+ ?vinfo("subscribe_gc_events: ~p => error:already-subscribed ~p",
+ [Pid, Sub]),
+ {reply, {error, {already_subscribed, Sub}}, State};
+
+handle_call({unsubscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Pid} = State) ->
+ ?vdebug("unsubscribe_gc_events: ~p => ok", [Pid]),
+ {reply, ok, State#state{cache_sub = undefined}};
+handle_call({unsubscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Sub} = State)
+ when (Sub =:= undefined) ->
+ ?vinfo("unsubscribe_gc_events: ~p => error:not-subscribed", [Pid]),
+ {reply, {error, not_subscribed}, State};
+handle_call({unsubscribe_gc_events, Pid}, _From,
+ #state{cache_sub = Sub} = State)
+ when is_pid(Sub) andalso (Pid =/= Sub) ->
+ ?vinfo("unsubscribe_gc_events: ~p => error:not-subscribed ~p",
+ [Pid, Sub]),
+ {reply, {error, {not_subscribed, Sub}}, State};
+
+
handle_call({lookup, Oid}, _From,
#state{data = Data, cache = Cache, data_mod = Mod} = State) ->
- ?vlog("lookup ~p", [Oid]),
+ ?vlog("lookup ~p", [Oid]),
Key = {lookup, Oid},
{Reply, NewState} =
case maybe_cache_lookup(Cache, Key) of
@@ -431,7 +480,7 @@ handle_call({lookup, Oid}, _From,
ets:insert(Cache, {Key, Rep, timestamp()}),
{Rep, maybe_start_cache_gc_timer(State)};
[{Key, Rep, _}] ->
- ?vdebug("lookup -> found in cache", []),
+ ?vtrace("lookup -> found in cache - update timestamp", []),
ets:update_element(Cache, Key, {3, timestamp()}),
{Rep, State}
end,
@@ -458,7 +507,7 @@ handle_call({next, Oid, MibView}, _From,
ets:insert(Cache, {Key, Rep, timestamp()}),
{Rep, maybe_start_cache_gc_timer(State)};
[{Key, Rep, _}] ->
- ?vdebug("lookup -> found in cache", []),
+ ?vdebug("lookup -> found in cache - update timestamp", []),
ets:update_element(Cache, Key, {3, timestamp()}),
{Rep, State}
end,
@@ -570,7 +619,7 @@ handle_call(info, _From, #state{data = Data,
Reply =
case (catch Mod:info(Data)) of
Info when is_list(Info) ->
- [{cache, size_cache(Cache)} | Info];
+ [{cache, cache_info(Cache)} | Info];
E ->
[{error, E}]
end,
@@ -664,13 +713,21 @@ handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) ->
gen_server:reply(From, Reply),
{noreply, S#state{backup = undefined}};
-handle_info(?CACHE_GC_TRIGGER, #state{cache = Cache,
- cache_age = Age,
- cache_gclimit = GcLimit,
- cache_autogc = true} = S)
+handle_info(?CACHE_GC_TRIGGER, #state{cache = Cache,
+ cache_age = Age,
+ cache_gclimit = GcLimit,
+ cache_autogc = true,
+ cache_gcverbose = GcVerbose} = S)
when (Cache =/= ?NO_CACHE) ->
- ?vlog("cache gc trigger event", []),
- maybe_gc_cache(Cache, Age, GcLimit),
+ gcvprint(GcVerbose, "GC: begin"),
+ case maybe_gc_cache(Cache, Age, GcLimit) of
+ {ok, NumDeleted} = Result when (NumDeleted > 0) ->
+ gcvprint(GcVerbose,
+ "GC: ~w elements deleted from cache", [NumDeleted]),
+ maybe_send_gc_result(S, Result);
+ _ ->
+ ok
+ end,
Tmr = start_cache_gc_timer(),
{noreply, S#state{cache_tmr = Tmr}};
@@ -749,14 +806,15 @@ get_cacheopt_autogc(Cache, CacheOpts) ->
IsValid).
get_cacheopt_gclimit(Cache, CacheOpts) ->
- IsValid = fun(Limit) when ((is_integer(Limit) andalso (Limit > 0)) orelse
- (Limit =:= infinity)) ->
+ IsValid = fun(Limit) when ((is_integer(Limit) andalso
+ (Limit > 0)) orelse
+ (Limit =:= infinity)) ->
true;
(_) ->
false
end,
- get_cacheopt(Cache, gclimit, CacheOpts,
- infinity, ?DEFAULT_CACHE_GCLIMIT,
+ get_cacheopt(Cache, gclimit, CacheOpts,
+ infinity, ?DEFAULT_CACHE_GCLIMIT,
IsValid).
get_cacheopt_age(Cache, CacheOpts) ->
@@ -765,8 +823,18 @@ get_cacheopt_age(Cache, CacheOpts) ->
(_) ->
false
end,
- get_cacheopt(Cache, age, CacheOpts,
- ?DEFAULT_CACHE_AGE, ?DEFAULT_CACHE_AGE,
+ get_cacheopt(Cache, age, CacheOpts,
+ ?DEFAULT_CACHE_AGE, ?DEFAULT_CACHE_AGE,
+ IsValid).
+
+get_cacheopt_gcverbose(Cache, CacheOpts) ->
+ IsValid = fun(Verbosity) when is_boolean(Verbosity) ->
+ true;
+ (_) ->
+ false
+ end,
+ get_cacheopt(Cache, gcverbose, CacheOpts,
+ ?DEFAULT_CACHE_GCVERBOSE, ?DEFAULT_CACHE_GCVERBOSE,
IsValid).
get_cacheopt(?NO_CACHE, _, _, NoCacheVal, _, _) ->
@@ -843,21 +911,28 @@ start_cache_gc_timer() ->
%% ----------------------------------------------------------------
+gcvprint(GcVerbose, F) ->
+ gcvprint(GcVerbose, F, []).
+
+gcvprint(true, F, A) ->
+ ?vinfo(F, A);
+gcvprint(_, _, _) ->
+ ok.
+
maybe_gc_cache(?NO_CACHE, _Age) ->
?vtrace("cache not enabled", []),
ok;
maybe_gc_cache(Cache, Age) ->
- MatchSpec = gc_cache_matchspec(Age),
- Keys = ets:select(Cache, MatchSpec),
- do_gc_cache(Cache, Keys),
- {ok, length(Keys)}.
+ MatchSpec = gc_cache_matchspec_del(Age),
+ NumDeleted = ets:select_delete(Cache, MatchSpec),
+ {ok, NumDeleted}.
maybe_gc_cache(?NO_CACHE, _Age, _GcLimit) ->
ok;
maybe_gc_cache(Cache, Age, infinity = _GcLimit) ->
maybe_gc_cache(Cache, Age);
maybe_gc_cache(Cache, Age, GcLimit) ->
- MatchSpec = gc_cache_matchspec(Age),
+ MatchSpec = gc_cache_matchspec_key(Age),
Keys =
case ets:select(Cache, MatchSpec, GcLimit) of
{Match, _Cont} ->
@@ -868,14 +943,26 @@ maybe_gc_cache(Cache, Age, GcLimit) ->
do_gc_cache(Cache, Keys),
{ok, length(Keys)}.
-gc_cache_matchspec(Age) ->
- Oldest = timestamp() - Age,
+gc_cache_matchspec_del(Age) ->
+ %% The entry is a 3-tuple: {Key, Value, Timestamp}
+ MatchHead = {'_', '_', '$2'},
+ Return = true,
+ gc_cache_matchspec(Age, MatchHead, Return).
+
+gc_cache_matchspec_key(Age) ->
+ %% The entry is a 3-tuple: {Key, Value, Timestamp}
MatchHead = {'$1', '_', '$2'},
+ Return = '$1',
+ gc_cache_matchspec(Age, MatchHead, Return).
+
+gc_cache_matchspec(Age, MatchHead, Return) ->
+ Oldest = timestamp() - Age,
Guard = [{'<', '$2', Oldest}],
- MatchFunc = {MatchHead, Guard, ['$1']},
+ MatchFunc = {MatchHead, Guard, [Return]},
MatchSpec = [MatchFunc],
MatchSpec.
+
do_gc_cache(_, []) ->
ok;
do_gc_cache(Cache, [Key|Keys]) ->
@@ -906,20 +993,37 @@ maybe_cache_lookup(?NO_CACHE, _) ->
maybe_cache_lookup(Cache, Key) ->
ets:lookup(Cache, Key).
-size_cache(?NO_CACHE) ->
+
+cache_info(?NO_CACHE) ->
undefined;
-size_cache(Cache) ->
- case (catch ets:info(Cache, memory)) of
- Sz when is_integer(Sz) ->
- Sz;
- _ ->
- undefined
+cache_info(Cache) ->
+ try
+ begin
+ [
+ {memory, ets:info(Cache, memory)},
+ {size, ets:info(Cache, size)},
+ {stats, ets:info(Cache, stats)}
+ ]
+ end
+ catch
+ _:_:_ ->
+ undefined
end.
+
timestamp() ->
snmp_misc:now(ms).
+maybe_send_gc_result(S, Result) ->
+ maybe_send_gc_event(S, gc_result, Result).
+
+maybe_send_gc_event(#state{cache_sub = Sub}, Ev, Info) when is_pid(Sub) ->
+ Sub ! {self(), Ev, Info};
+maybe_send_gc_event(_, _, _) ->
+ ok.
+
+
%% ----------------------------------------------------------------
get_opt(Key, Options) ->
diff --git a/lib/snmp/src/agent/snmpa_mpd.erl b/lib/snmp/src/agent/snmpa_mpd.erl
index 552b9671af..0d40840d35 100644
--- a/lib/snmp/src/agent/snmpa_mpd.erl
+++ b/lib/snmp/src/agent/snmpa_mpd.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -166,6 +166,19 @@ process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) ->
NoteStore, Packet, From,
LocalEngineID, V3Hdr, Data, Log);
+ #message{version = MsgVersion} ->
+ ?vlog("Invalid Version: "
+ "~n Message Version: ~p"
+ "~nwhen"
+ "~n Versions:"
+ "~n v1: ~w"
+ "~n v2c: ~w"
+ "~n v3: ~w",
+ [MsgVersion,
+ State#state.v1, State#state.v2c, State#state.v3]),
+ inc(snmpInBadVersions),
+ {discarded, snmpInBadVersions};
+
{'EXIT', {bad_version, Vsn}} ->
?vtrace("exit: bad version: ~p",[Vsn]),
inc(snmpInBadVersions),
@@ -177,9 +190,11 @@ process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) ->
{discarded, Reason};
UnknownMessage ->
- ?vtrace("Unknown message: ~n ~p"
- "~nwhen"
- "~n State: ~p", [UnknownMessage, State]),
+ ?vdebug("Unknown message: "
+ "~n ~p"
+ "~nwhen"
+ "~n State: "
+ "~n ~p", [UnknownMessage, State]),
inc(snmpInBadVersions),
{discarded, snmpInBadVersions}
end.
diff --git a/lib/snmp/src/agent/snmpa_net_if.erl b/lib/snmp/src/agent/snmpa_net_if.erl
index c1f7c6b424..b6b115bd75 100644
--- a/lib/snmp/src/agent/snmpa_net_if.erl
+++ b/lib/snmp/src/agent/snmpa_net_if.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -36,27 +36,110 @@
-include("snmp_debug.hrl").
-include("snmp_verbosity.hrl").
+
+%% Regarding Trap/Notification transport(s),
+%% it *should* be possible to specify either:
+%% 1) A fixed set of transport(s) used for sending.
+%% For instance, one for IPv4 and one for IPv6.
+%% 2) A single one-shot ephemeral port.
+%% That is, a port is created, used once, and then closed.
+%% 3) A pool of ephemeral ports, used for "a time"
+%% (a configurable number of sends, a set number of bytes
+%% or time based) thar is cycled through.
+%% Though, we do *not* currently implement ephemeral sockets,
+%% so case 2 and 3 are not possible at the moment.
+
+%% Also, the request-reponder and trap-sender transport(s),
+%% has different needs.
+%% The trap-sender transport will send more data then it will receive.
+%% Therefor, we should be able to specify;
+%% bind_to, no_reuse_address, recbuf and sndbuf individually:
+%% {intAgentTransports,
+%% [{transportDomainUdpIpv4, {{141,213,11,24}, PortInfo},
+%% req_responder, Opts},
+%% {transportDomainUdpIpv4, {{141,213,11,24}, PortInfo},
+%% trap_sender, Opts},
+%% {transportDomainUdpIpv6, {{0,0,0,0,0,0,0,1}, Portinfo},
+%% req_responder, Opts},
+%% {transportDomainUdpIpv6, {{0,0,0,0,0,0,0,1}, Portinfo},
+%% trap_sender, Opts}]}.
+%% Opts is basically "socket options" for this transport (the same
+%% options as for net-if).
+%% Also, always give the option to provide a port range:
+%% Port :: pos_integer() | system | range() || ranges()
+%% system => Let the system choose
+%% (0 is unfortunately already used as 'default',
+%% so we can't use that for 'system').
+%% range() :: {Min :: pos_integer(), Max :: pos_integer()} when Min < Max
+%% ranges() :: [pos_integer() | range()]
+%% Examples:
+%% [{2000, 2004}]
+%% [2000, 2001, 2002, 2003, 2004]
+%% [2000, 2001, {2002, 2004}]
+%% [{5000, 5100}, {6000, 6100}]
+
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% , *but*
+%% may also contain the tuple, {ephemeral, EphmOpts}.
+%% Ephm sockets are created on the fly, used for the specified
+%% time (number of sends, number of bytes sent, used time, ...).
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
-record(state,
{parent,
note_store,
master_agent,
- transports = [],
-%% usock,
-%% usock_opts,
+ transports = [],
mpd_state,
log,
reqs = [],
debug = false,
limit = infinity,
-%% rcnt = [],
filter}).
-%% domain = snmpUDPDomain}).
+
+-type transport_kind() :: req_responder | trap_sender.
+-type port_info() :: non_neg_integer() |
+ system |
+ {pos_integer(), pos_integer()}.
+
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% How would 'ephemeral' effect this?
+%% What kind of usage would we have for emphemeral ports?
+%% once (send and maybe receive reply (inform)) |
+%% {sends, pos_integer()} (number of sends) |
+%% {data, pos_integer()} (number of bytes sent) |
+%% {alive_time, pos_integer()} (once used for the first time, alive time)
+%% You can't combine a ephemeral info with a fixed port (pos_integer())
+%% The port_info() is used when creating a port, even an ephemeral port.
+%% But it must either be 'system' or a range in that (ephemeral) case.
+%% Also, ephemeral transports are only allowed if kind = trap_sender
+%% -type ephemeral() :: none |
+%% once |
+%% {sends, pos_integer()} |
+%% {data, pos_integer()} |
+%% {alive_time, pos_integer()}.
+
+%% Note that since informs require confirmation,
+%% an ephemeral socket cannot be removed immediately
+%% when it has been "used up".
+%% We need to keep it for some time to receive responces
+%% and in case a resend is needed!.
+%% </EPHEMERAL-FOR-FUTUR-USE>
-record(transport,
{socket,
- domain = snmpUDPDomain,
- opts = [],
- req_refs = []}).
+ kind = all :: all | transport_kind(),
+ domain = snmpUDPDomain,
+ address :: inet:ip_address(),
+ port_no :: pos_integer(),
+ port_info :: port_info(),
+ %% <EPHEMERAL-FOR-FUTUR-USE>
+ ephm = none, %% :: ephemeral(),
+ ephm_info = undefined, % Only used if ephm =/= none and once
+ %% </EPHEMERAL-FOR-FUTUR-USE>
+ opts = [],
+ req_refs = [] % Not used for trap/notification transports
+ }).
-ifndef(default_verbosity).
-define(default_verbosity,silence).
@@ -160,11 +243,11 @@ init(Prio, NoteStore, MasterAgent, Parent, Opts) ->
do_init(Prio, NoteStore, MasterAgent, Parent, Opts) ->
process_flag(trap_exit, true),
- process_flag(priority, Prio),
+ process_flag(priority, Prio),
%% -- Verbosity --
- put(sname,nif),
- put(verbosity,get_verbosity(Opts)),
+ put(sname, nif),
+ put(verbosity, get_verbosity(Opts)),
?vlog("starting",[]),
%% -- Versions --
@@ -182,22 +265,49 @@ do_init(Prio, NoteStore, MasterAgent, Parent, Opts) ->
Log = create_log(),
?vdebug("Log: ~w",[Log]),
- DomainAddresses = get_transports(),
- ?vdebug("DomainAddresses: ~w",[DomainAddresses]),
+ RawTransports = get_transports(),
+ ?vdebug("Raw Transports: "
+ "~n ~p", [RawTransports]),
try
[begin
- SocketOpts = socket_opts(Domain, Address, Opts),
- Socket = socket_open(Domain, SocketOpts),
+ %% Any socket option not explicitly configured for the transport
+ %% will be taken from the "global" socket options (which serve as
+ %% default values).
+ %% Also, note that Ephm are not actually used at this time.
+ {Ephm, IpAddr, PortInfo, SocketOpts} = socket_opts(Domain, Address,
+ RawSocketOpts, Opts),
+ ?vtrace("socket opts processed:"
+ "~n Ephm: ~p"
+ "~n Port Info: ~p"
+ "~n Socket Opts: ~p", [Ephm, PortInfo, SocketOpts]),
+ {Socket, IpPort} = socket_open(Domain, PortInfo,
+ SocketOpts),
+ ?vtrace("socket opened:"
+ "~n Socket: ~p"
+ "~n Port No: ~p", [Socket, IpPort]),
+ %% Should we really do this here?
+ %% If Kind =:= trap_sender, we only need to receive after
+ %% we have sent an inform!
active_once(Socket),
#transport{
- socket = Socket,
- domain = Domain,
- opts = SocketOpts}
- end || {Domain, Address} <- DomainAddresses]
+ socket = Socket,
+ kind = Kind,
+ domain = Domain,
+ %% We may not have explicitly specified the port ('system'
+ %% or a range), so it could have been "generated".
+ %% Also, shall we push this into the transport (handled by the
+ %% FRAMEWORK MIB)? Would not work for ephemeral sockets.
+ address = IpAddr,
+ port_no = IpPort,
+ port_info = PortInfo,
+ ephm = Ephm,
+ opts = SocketOpts}
+ %% We need to fix this also
+ end || {Domain, Address, Kind, RawSocketOpts} <- RawTransports]
of
[] ->
- ?vinfo("No transports configured: ~p", [DomainAddresses]),
- {error, {no_transports,DomainAddresses}};
+ ?vinfo("No transports configured: ~p", [RawTransports]),
+ {error, {no_transports, RawTransports}};
Transports ->
MpdState = snmpa_mpd:init(Vsns),
init_counters(),
@@ -291,45 +401,158 @@ format_address(Address) ->
iolist_to_binary(snmp_conf:mk_addr_string(Address)).
-socket_open(snmpUDPDomain = Domain, [IpPort | Opts]) ->
+socket_open(snmpUDPDomain = Domain, IpPort, Opts) ->
+ ?vdebug("socket_open(~p) -> entry with"
+ "~n Port: ~p"
+ "~n Opts: ~p", [Domain, IpPort, Opts]),
case init:get_argument(snmp_fd) of
{ok, [[FdStr]]} ->
- Fd = list_to_integer(FdStr),
- ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p",
- [Domain, IpPort, Opts, Fd]),
- gen_udp_open(0, [{fd, Fd} | Opts]);
+ FD = list_to_integer(FdStr),
+ ?vdebug("socket_open(~p) -> open with fd - "
+ "(old) snmp_fd command line argument provided: "
+ "~n FD: ~p"
+ "~n Port: ~p"
+ "~n Opts: ~p", [Domain, FD, IpPort, Opts]),
+ gen_udp_open(0, [{fd, FD} | Opts]);
error ->
case init:get_argument(snmpa_fd) of
{ok, [[FdStr]]} ->
- Fd = list_to_integer(FdStr),
- ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p",
- [Domain, IpPort, Opts, Fd]),
- gen_udp_open(0, [{fd, Fd} | Opts]);
+ FD = list_to_integer(FdStr),
+ ?vdebug("socket_open(~p) -> open with fd - "
+ "snmpa_fd command line argument provided: "
+ "~n FD: ~p"
+ "~n Port: ~p"
+ "~n Opts: ~p", [Domain, FD, IpPort, Opts]),
+ gen_udp_open(0, [{fd, FD} | Opts]);
error ->
- ?vdebug("socket_open(~p, [~p | ~p])",
- [Domain, IpPort, Opts]),
+ ?vdebug("socket_open(~p) -> plain open"
+ "~n Port: ~p"
+ "~n Opts: ~p", [Domain, IpPort, Opts]),
gen_udp_open(IpPort, Opts)
end
end;
-socket_open(Domain, [IpPort | Opts])
- when Domain =:= transportDomainUdpIpv4;
- Domain =:= transportDomainUdpIpv6 ->
- ?vdebug("socket_open(~p, [~p | ~p])", [Domain, IpPort, Opts]),
- gen_udp_open(IpPort, Opts);
-socket_open(Domain, Opts) ->
+socket_open(Domain, PortInfo, Opts)
+ when (Domain =:= transportDomainUdpIpv4) orelse
+ (Domain =:= transportDomainUdpIpv6) ->
+ ?vdebug("socket_open(~p) -> entry with"
+ "~n PortInfo: ~p"
+ "~n Opts: ~p", [Domain, PortInfo, Opts]),
+ gen_udp_open(PortInfo, Opts);
+socket_open(Domain, PortInfo, Opts) ->
+ ?vinfo("socket_open(~p) -> entry when invalid with"
+ "~n PortInfo: ~p"
+ "~n Opts: ~p", [Domain, PortInfo, Opts]),
throw({socket_open, Domain, Opts}).
-gen_udp_open(IpPort, Opts) ->
+
+%% Make the system choose!
+gen_udp_open(system, Opts) ->
+ ?vtrace("gen_udp_open(system) -> entry"),
+ case gen_udp:open(0, Opts) of
+ {ok, Socket} ->
+ case inet:port(Socket) of
+ {ok, PortNo} ->
+ ?vtrace("gen_udp_open(system) -> created: "
+ "~n ~p (~w)", [Socket, PortNo]),
+ {Socket, PortNo};
+ {error, PReason} ->
+ (catch gen_udp:close(Socket)),
+ throw({udp_open, {port, PReason}})
+ end;
+ {error, OReason} ->
+ throw({udp_open, {open, OReason}})
+ end;
+%% This is for "future compat" since we cannot actually config '0'...
+gen_udp_open(IpPort, Opts) when (IpPort =:= 0) ->
+ ?vtrace("gen_udp_open(0) -> entry"),
+ case gen_udp:open(IpPort, Opts) of
+ {ok, Socket} ->
+ case inet:port(Socket) of
+ {ok, PortNo} ->
+ ?vtrace("gen_udp_open(0) -> created: "
+ "~n ~p (~w)", [Socket, PortNo]),
+ {Socket, PortNo};
+ {error, PReason} ->
+ (catch gen_udp:close(Socket)),
+ throw({udp_open, {port, PReason}})
+ end;
+ {error, Reason} ->
+ throw({udp_open, {open, IpPort, Reason}})
+ end;
+gen_udp_open(IpPort, Opts) when is_integer(IpPort) ->
+ ?vtrace("gen_udp_open(~w) -> entry", [IpPort]),
case gen_udp:open(IpPort, Opts) of
{ok, Socket} ->
- Socket;
+ ?vtrace("gen_udp_open(~w) -> created: "
+ "~n ~p", [Socket]),
+ {Socket, IpPort};
{error, Reason} ->
- throw({udp_open, IpPort, Reason})
+ throw({udp_open, {open, IpPort, Reason}})
+ end;
+%% A range is "pointless" if we allow reuseaddr...
+%% ...but we leave that to the user...
+gen_udp_open({Min, Max}, Opts) ->
+ ?vtrace("gen_udp_open(~w,~w) -> entry", [Min, Max]),
+ gen_udp_range_open(Min, Max, Opts);
+%% A range is "pointless" if we allow reuseaddr...
+%% ...but we leave that to the user...
+gen_udp_open(Ranges, Opts) when is_list(Ranges) ->
+ gen_udp_ranges_open(Ranges, Opts).
+
+gen_udp_range_open(Min, Max, _Opts) when (Min > Max) ->
+ ?vinfo("gen_udp_range_open -> entry when no available ports"),
+ throw({udp_open, no_available_ports});
+gen_udp_range_open(Min, Max, Opts) ->
+ ?vtrace("gen_udp_range_open -> entry with"
+ "~n Min: ~w"
+ "~n Max: ~w", [Min, Max]),
+ try gen_udp:open(Min, Opts) of
+ {ok, Socket} ->
+ ?vtrace("gen_udp_range_open(~w,~w) -> created: "
+ "~n ~p", [Min, Max, Socket]),
+ {Socket, Min};
+ {error, eaddrinuse} ->
+ ?vdebug("gen_udp_range_open(~w,~w) -> eaddrinuse"),
+ gen_udp_range_open(Min+1, Max, Opts);
+ {error, Reason} ->
+ ?vdebug("gen_udp_range_open(~w,~w) -> ~w", [Reason]),
+ throw({udp_open, {open, Reason}})
+ catch
+ C:E:S ->
+ ?vinfo("gen_udp_range_open(~w,~w) -> failed open socket: "
+ "~n C: ~p"
+ "~n E: ~p"
+ "~n S: ~p", [Min, Max, C, E, S]),
+ erlang:raise(C, E, S)
end.
+gen_udp_ranges_open([], _Opts) ->
+ ?vinfo("gen_udp_ranges_open -> entry when no available ports"),
+ throw({udp_open, no_available_ports});
+gen_udp_ranges_open([PortNo|Ranges], Opts) when is_integer(PortNo) andalso
+ (PortNo > 0) ->
+ ?vtrace("gen_udp_ranges_open(~w) -> entry", [PortNo]),
+ try gen_udp_open(PortNo, Opts) of
+ {_Sock, PortNo} = SUCCESS when is_integer(PortNo) ->
+ SUCCESS
+ catch
+ throw:{udp_open, _} ->
+ gen_udp_ranges_open(Ranges, Opts)
+ end;
+gen_udp_ranges_open([{Min, Max}|Ranges], Opts) ->
+ ?vtrace("gen_udp_ranges_open(~w,~w) -> entry", [Min, Max]),
+ try gen_udp_range_open(Min, Max, Opts) of
+ {_Sock, PortNo} = SUCCESS when is_integer(PortNo) ->
+ SUCCESS
+ catch
+ throw:{udp_open, _} ->
+ gen_udp_ranges_open(Ranges, Opts)
+ end.
-loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
+loop(#state{transports = Transports,
+ limit = Limit,
+ parent = Parent} = S) ->
?vdebug("loop(~p)", [S]),
receive
{udp, Socket, IpAddr, IpPort, Packet} = Msg when is_port(Socket) ->
@@ -349,6 +572,15 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
loop(S)
end;
+ {udp_error, Socket, Error} when is_port(Socket) ->
+ ?vinfo("got udp-error on ~p: ~w", [Socket, Error]),
+ case lists:keyfind(Socket, #transport.socket, Transports) of
+ #transport{socket = Socket} = Transport ->
+ loop(handle_udp_error(S, Transport, Error));
+ false ->
+ loop(handle_udp_error_unknown(S, Socket, Error))
+ end;
+
{info, ReplyRef, Pid} ->
Info = get_info(S),
Pid ! {ReplyRef, Info, self()},
@@ -363,12 +595,11 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
case
case
(Limit =/= infinity) andalso
- select_transport_from_req_ref(ReqRef, Transports)
+ select_transport(ReqRef, Transports)
of
false ->
- select_transport_from_domain(
- address_to_domain(To),
- Transports);
+ select_transport(address_to_domain(To), Type,
+ Transports);
T ->
T
end
@@ -463,7 +694,7 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
loop(update_req_counter_outgoing(S, false, ReqRef));
true ->
case
- select_transport_from_req_ref(ReqRef, Transports)
+ select_transport(ReqRef, Transports)
of
false ->
error_msg(
@@ -519,27 +750,31 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
"~n ~p", [Parent, Reason]),
exit(Reason);
+ %% We should not do this.
+ %% Future versions of sockets will/may not be linkable (port)
{'EXIT', Socket, Reason} when is_port(Socket) ->
case lists:keyfind(Socket, #transport.socket, Transports) of
#transport{
- socket = Socket,
- domain = Domain,
- opts = SocketOpts,
- req_refs = ReqRefs} = Transport ->
- try socket_open(Domain, SocketOpts) of
- NewSocket ->
+ socket = Socket,
+ domain = Domain,
+ port_info = PortInfo,
+ opts = SocketOpts,
+ req_refs = ReqRefs} = Transport ->
+ try socket_open(Domain, PortInfo, SocketOpts) of
+ {NewSocket, PortNo} ->
error_msg(
"Socket ~p exited for reason"
"~n ~p"
- "~n Re-opened (~p)",
- [Socket, Reason, NewSocket]),
+ "~n Re-opened (~p, ~w)",
+ [Socket, Reason, NewSocket, PortNo]),
(length(ReqRefs) < Limit) andalso
active_once(NewSocket),
S#state{
transports =
lists:keyreplace(
Socket, #transport.socket, Transports,
- Transport#transport{socket = NewSocket})}
+ Transport#transport{socket = NewSocket,
+ port_no = PortNo})}
catch
ReopenReason ->
error_msg(
@@ -572,6 +807,47 @@ loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) ->
end.
+handle_udp_error(S, #transport{socket = Socket,
+ kind = Kind}, Error) ->
+ try inet:sockname(Socket) of
+ {ok, {IP, Port}} ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p (~p, ~p)"
+ "~n Kind: ~p"
+ "~n Error: ~p", [Socket, IP, Port, Kind, Error]);
+ {error, _} ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Kind: ~p"
+ "~n Error: ~p", [Socket, Kind, Error])
+ catch
+ _:_:_ ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Kind: ~p"
+ "~n Error: ~p", [Socket, Kind, Error])
+ end,
+ S.
+
+handle_udp_error_unknown(S, Socket, Error) ->
+ try inet:sockname(Socket) of
+ {ok, {IP, Port}} ->
+ warning_msg("UDP Error for unknown transport: "
+ "~n Socket: ~p (~p, ~p)"
+ "~n Error: ~p", [Socket, IP, Port, Error]);
+ {error, _} ->
+ warning_msg("UDP Error for unknown transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ catch
+ _:_:_ ->
+ warning_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ end,
+ S.
+
+
update_req_counter_incoming(
#state{limit = infinity} = S,
#transport{socket = Socket},
@@ -615,7 +891,7 @@ update_req_counter_outgoing(
ReqRef) ->
LengthReqRefs = length(ReqRefs),
?vtrace("update_req_counter_outgoing() -> entry with~n"
- " Limit: ~w~n"
+ " Limit: ~w~n"
" ReqRef: ~w~n"
" length(ReqRefs): ~w", [Limit, ReqRef, LengthReqRefs]),
NewReqRefs = lists:delete(ReqRef, ReqRefs),
@@ -654,7 +930,9 @@ maybe_handle_recv(
end
of
false ->
- %% Drop the received packet
+ %% Drop the received packet
+ %% What if this is an expected (inform) reply
+ %% on an ephemeral socket?
inc(netIfMsgInDrops),
active_once(Socket),
S;
@@ -729,9 +1007,9 @@ handle_discovery_response(
_From,
#pdu{request_id = ReqId} = Pdu,
ManagerEngineId) ->
+ active_once(Socket),
case lists:keyfind(ReqId, 1, S#state.reqs) of
{ReqId, Pid} ->
- active_once(Socket),
Pid ! {snmp_discovery_response_received, Pdu, ManagerEngineId},
%% XXX Strange... Reqs from this Pid should be reaped
%% at process exit by clear_reqs/2 so the following
@@ -748,7 +1026,6 @@ handle_discovery_response(
case (length(DiscoReqs) =:= 2) of
true ->
[{_, Pid}, _] = DiscoReqs,
- active_once(Socket),
Pid ! {snmp_discovery_response_received, Pdu,
ManagerEngineId},
NReqs = S#state.reqs -- DiscoReqs,
@@ -910,13 +1187,13 @@ handle_reply_pdu(
-maybe_handle_send_pdu(
- #state{filter = FilterMod, transports = Transports} = S,
- Vsn, Pdu, MsgData, TDomAddrSecs, From) ->
-
- ?vtrace("maybe_handle_send_pdu -> entry with~n"
- " FilterMod: ~p~n"
- " TDomAddrSecs: ~p", [FilterMod, TDomAddrSecs]),
+maybe_handle_send_pdu(#state{filter = FilterMod,
+ transports = Transports} = S,
+ Vsn, Pdu, MsgData, TDomAddrSecs, From) ->
+
+ ?vtrace("maybe_handle_send_pdu -> entry with"
+ "~n FilterMod: ~p"
+ "~n TDomAddrSecs: ~p", [FilterMod, TDomAddrSecs]),
DomAddrSecs = snmpa_mpd:process_taddrs(TDomAddrSecs),
AddressesToFilter =
@@ -1023,7 +1300,7 @@ handle_send_discovery(
case (catch snmpa_mpd:generate_discovery_msg(NS, Pdu, MsgData, To)) of
{ok, {Domain, Address, Packet}} ->
- case select_transport_from_domain(Domain, Transports) of
+ case select_transport(Domain, Type, Transports) of
false ->
error_msg(
"Can not find transport to: ~s",
@@ -1067,16 +1344,21 @@ do_handle_send_pdu(S, Type, Pdu, Addresses) ->
[Sz, Reason, Pdu])
end.
-do_handle_send_pdu1(S, Type, Addresses) ->
- lists:foreach(
- fun ({Domain, Address, Pkg}) when is_binary(Pkg) ->
- do_handle_send_pdu2(S, Type, Domain, Address,
- Pkg, Pkg, "");
- ({Domain, Address, {Pkg, LogPkg}}) when is_binary(Pkg) ->
- do_handle_send_pdu2(S, Type, Domain, Address,
- Pkg, LogPkg, " encrypted")
- end,
- Addresses).
+%% Because of the ephemeral sockets used by some transports,
+%% the list of transports may be update for each send...
+do_handle_send_pdu1(S, _Type, []) ->
+ S;
+do_handle_send_pdu1(S, Type, [{Domain, Address, Pkg}|Addresses])
+ when is_binary(Pkg) ->
+ NewS = do_handle_send_pdu2(S, Type, Domain, Address,
+ Pkg, Pkg, ""),
+ do_handle_send_pdu1(NewS, Type, Addresses);
+do_handle_send_pdu1(S, Type, [{Domain, Address, {Pkg, LogPkg}}|Addresses])
+ when is_binary(Pkg) ->
+ NewS = do_handle_send_pdu2(S, Type, Domain, Address,
+ Pkg, LogPkg, " encrypted"),
+ do_handle_send_pdu1(NewS, Type, Addresses).
+
do_handle_send_pdu2(#state{transports = Transports} = S,
Type, Domain, Address, Pkg, LogPkg, EncrStr) ->
@@ -1084,17 +1366,207 @@ do_handle_send_pdu2(#state{transports = Transports} = S,
"~n size: ~p"
"~n to: ~p", [Domain, EncrStr, sz(Pkg), Address]),
To = {Domain, Address},
- case select_transport_from_domain(Domain, Transports) of
+ case select_transport(Domain, Type, Transports) of
false ->
- error_msg("Can not find transport: "
+ error_msg("Transport not found: "
"~n size: ~p"
"~n to: ~s",
- [sz(Pkg), format_address(To)]);
- Transport ->
- maybe_udp_send_w_log(S, Transport, To, Pkg, LogPkg, Type)
+ [sz(Pkg), format_address(To)]),
+ S;
+ #transport{ephm = none} = Transport ->
+ ?vtrace("do_handle_send_pdu2 -> transport(ephm = none) selected: "
+ "~n ~p", [Transport]),
+ maybe_udp_send_w_log(S, Transport, To, Pkg, LogPkg, Type),
+ S%;
+
+ %% <EPHEMERAL-FOR-FUTUR-USE>
+ %% #transport{} = Transport ->
+ %% ?vtrace("do_handle_send_pdu2 -> transport selected: "
+ %% "~n ~p", [Transport]),
+ %% case maybe_udp_send_w_log(S, Transport, To, Pkg, LogPkg, Type) of
+ %% {ok, Sz} -> % we actually sent something
+ %% maybe_update_ephm_transport(S, Transport, Type, Sz);
+ %% _ -> % Non-fatal error -> Nothing sent
+ %% S
+ %% end
+ %% </EPHEMERAL-FOR-FUTUR-USE>
end.
+%% <EPHEMERAL-FOR-FUTUR-USE>
+
+%% For inform:
+%% This will have a reply, so we cannot close it directly!
+%% Also, we will resend (a couple of times), if we don't
+%% get a reply in time.
+%% So, how do we handle this transport in this case?
+%% Shall we create a list of used sockets? Tagged with a key
+%% so we can reuse the correct socket for a resend?
+
+%%
+%% Or shall we used the same socket for all the sends for this
+%% trap/inform? That is, we send the trap/inform to a number of
+%% targets (the Addresses list), and *that* is considered *one*
+%% use?
+%% That would mean that we would potentially need to wait for
+%% replies from a large number of targets?
+%% But it may be better in the long term, because we will not
+%% use of so many sockets.
+%%
+
+%% maybe_update_ephm_transport(S, #transport{ephm = once} = _Transport,
+%% 'inform-request' = _Type, _Sz) ->
+%% S; % Figure out the above first!
+
+%% %% Before we close the current socket, create the new.
+%% %% This is done in case we fail to create a new socket
+%% %% (if we first close the current and then fail to create
+%% %% the new, we are stuck).
+%% %% If we fail to create the new socket, we keep the current.
+%% %% Better then nothing!
+%% maybe_update_ephm_transport(S, #transport{socket = OldSocket,
+%% ephm = once,
+%% port_info = PortInfo,
+%% opts = Opts} = Transport,
+%% _Type, _Sz) ->
+%% try
+%% begin
+%% {Socket, PortNo} = gen_udp_open(PortInfo, Opts),
+%% (catch gen_udp:close(OldSocket)),
+%% T2 = Transport#transport{socket = Socket,
+%% port_no = PortNo},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2),
+%% S#state{transports = TS2}
+%% end
+%% catch
+%% _:_:_ ->
+%% %% We need to identify which transport!
+%% error_msg("Failed creating new ephemeral socket for transport"),
+%% S
+%% end;
+
+%% %% Note that we do not currently handle inform:s, as that adds a whole
+%% %% set of issues. See above for more info.
+%% maybe_update_ephm_transport(S, #transport{socket = Socket,
+%% ephm = {sends, MaxSends},
+%% ephm_info = NumSends,
+%% port_info = _PortInfo,
+%% opts = _Opts} = Transport,
+%% _Type, _Sz) when (MaxSends > NumSends) ->
+%% T2 = Transport#transport{ephm_info = NumSends + 1},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(Socket, #transport.socket, TS, T2),
+%% S#state{transports = TS2};
+%% maybe_update_ephm_transport(S, #transport{socket = OldSocket,
+%% ephm = {sends, _MaxSends},
+%% ephm_info = _NumSends,
+%% port_info = PortInfo,
+%% opts = Opts} = Transport,
+%% _Type, _Sz) ->
+%% try
+%% begin
+%% {Socket, PortNo} = gen_udp_open(PortInfo, Opts),
+%% (catch gen_udp:close(OldSocket)),
+%% T2 = Transport#transport{socket = Socket,
+%% ephm_info = 0,
+%% port_no = PortNo},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2),
+%% S#state{transports = TS2}
+%% end
+%% catch
+%% _:_:_ ->
+%% %% We need to identify which transport!
+%% error_msg("Failed creating new ephemeral socket for transport"),
+%% S
+%% end;
+
+%% %% Note that we do not currently handle inform:s, as that adds a whole
+%% %% set of issues. See above for more info.
+%% maybe_update_ephm_transport(S, #transport{socket = Socket,
+%% ephm = {data, MaxData},
+%% ephm_info = AccSent,
+%% port_info = _PortInfo,
+%% opts = _Opts} = Transport,
+%% _Type, Sz) when (MaxData > (AccSent + Sz)) ->
+%% T2 = Transport#transport{ephm_info = AccSent + Sz},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(Socket, #transport.socket, TS, T2),
+%% S#state{transports = TS2};
+%% maybe_update_ephm_transport(S, #transport{socket = OldSocket,
+%% ephm = {data, _MaxData},
+%% ephm_info = _AccSent,
+%% port_info = PortInfo,
+%% opts = Opts} = Transport,
+%% _Type, _Sz) ->
+%% try
+%% begin
+%% {Socket, PortNo} = gen_udp_open(PortInfo, Opts),
+%% (catch gen_udp:close(OldSocket)),
+%% T2 = Transport#transport{socket = Socket,
+%% ephm_info = 0,
+%% port_no = PortNo},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2),
+%% S#state{transports = TS2}
+%% end
+%% catch
+%% _:_:_ ->
+%% %% We need to identify which transport!
+%% error_msg("Failed creating new ephemeral socket for transport"),
+%% S
+%% end;
+
+%% %% Note that we do not currently handle inform:s, as that adds a whole
+%% %% set of issues. See above for more info.
+%% maybe_update_ephm_transport(S, #transport{socket = Socket,
+%% ephm = {alive_time, AliveTime},
+%% ephm_info = undefined} = Transport,
+%% _Type, _Sz) ->
+%% AliveEnd = erlang:monotonic_time(micro_seconds) + AliveTime,
+%% T2 = Transport#transport{ephm_info = AliveEnd},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(Socket, #transport.socket, TS, T2),
+%% S#state{transports = TS2};
+%% maybe_update_ephm_transport(S, #transport{socket = OldSocket,
+%% ephm = {alive_time, _AliveTime},
+%% ephm_info = AliveEnd,
+%% port_info = PortInfo,
+%% opts = Opts} = Transport,
+%% _Type, _Sz) ->
+%% TS = erlang:monotonic_time(micro_seconds),
+%% if
+%% (TS > AliveEnd) ->
+%% try
+%% begin
+%% {Socket, PortNo} = gen_udp_open(PortInfo, Opts),
+%% (catch gen_udp:close(OldSocket)),
+%% T2 = Transport#transport{socket = Socket,
+%% %% This will be set when the transport
+%% %% is first used
+%% ephm_info = undefined,
+%% port_no = PortNo},
+%% TS = S#state.transports,
+%% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2),
+%% S#state{transports = TS2}
+%% end
+%% catch
+%% _:_:_ ->
+%% %% We need to identify which transport!
+%% error_msg("Failed creating new ephemeral socket for transport"),
+%% S
+%% end;
+%% true ->
+%% S
+%% end;
+
+%% maybe_update_ephm_transport(S, _Transport, _Type, _Sz) ->
+%% S.
+
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
+
%% This function is used when logging has already been done!
maybe_udp_send_wo_log(
#state{filter = FilterMod, transports = Transports},
@@ -1157,6 +1629,7 @@ maybe_udp_send_w_log(
udp_send(Socket, To, Pkg)
end.
+
udp_send(Socket, To, B) ->
{IpAddr, IpPort} =
case To of
@@ -1172,9 +1645,11 @@ udp_send(Socket, To, B) ->
{error, ErrorReason} ->
error_msg("[error] cannot send message "
"(destination: ~p:~p, size: ~p, reason: ~p)",
- [IpAddr, IpPort, sz(B), ErrorReason]);
+ [IpAddr, IpPort, sz(B), ErrorReason]),
+ ok;
ok ->
- ok
+ %% For future use! Ephemeral ports!
+ {ok, size(B)}
catch
error:ExitReason:StackTrace ->
error_msg("[exit] cannot send message "
@@ -1230,31 +1705,90 @@ active_once(Sock) ->
inet:setopts(Sock, [{active, once}]).
-select_transport_from_req_ref(_, []) ->
+select_transport(_, []) ->
false;
-select_transport_from_req_ref(
- ReqRef,
- [#transport{req_refs = ReqRefs} = Transport | Transports]) ->
+select_transport(ReqRef,
+ [#transport{req_refs = ReqRefs} = Transport | Transports]) ->
case lists:member(ReqRef, ReqRefs) of
true ->
Transport;
false ->
- select_transport_from_req_ref(ReqRef, Transports)
+ select_transport(ReqRef, Transports)
end.
-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
+select_transport(snmpUDPDomain = Domain, Type, Transports) ->
+ ?vtrace("select_transport -> entry with"
+ "~n Domain: ~p"
+ "~n Type: ~p"
+ "~n Transports: ~p",
+ [Domain, Type, Transports]),
+ case select_transport2(Domain, Type, Transports) of
+ #transport{} = Transport ->
+ ?vtrace("select_transport -> selected: "
+ "~n ~p",
+ [Transport]),
+ Transport;
+ false ->
+ select_transport2(transportDomainUdpIpv4, Type, Transports)
+ end;
+select_transport(Domain, Type, Transports)
+ when is_atom(Domain) ->
+ ?vtrace("select_transport -> entry with"
+ "~n Domain: ~p"
+ "~n Type: ~p"
+ "~n Transports: ~p",
+ [Domain, Type, Transports]),
+ case select_transport2(Domain, Type, Transports) of
+ #transport{} = Transport ->
+ ?vtrace("select_transport -> selected: "
+ "~n ~p",
+ [Transport]),
+ Transport;
+ false when Domain =:= transportDomainUdpIpv4 ->
+ ?vdebug("select_transport -> (~p) not found: "
+ "~n try ~p", [Domain, snmpUDPDomain]),
+ select_transport2(snmpUDPDomain, Type, Transports);
+ false ->
+ false
end.
+
+%% Two kinds of (pdu) type that we (the agent) will ever attempt to send:
+%% req-responder: 'get-response'
+%% trap-sender: 'inform-request' |
+%% 'snmpv2-trap' |
+%% report |
+%% trap (which is a 'fake' type (#trappdu{}))
+select_transport2(_Domain, _Type,
+ []) ->
+ false;
+select_transport2(snmpUDPDomain = Domain, _Type,
+ [#transport{domain = Domain} = Transport|_Transports]) ->
+ Transport;
+select_transport2(snmpUDPDomain = Domain, Type,
+ [_|Transports]) ->
+ select_transport2(Domain, Type, Transports);
+select_transport2(Domain, Type,
+ [#transport{domain = Domain,
+ kind = Kind} = Transport|_Transports])
+ when ((Type =:= 'inform-request') orelse
+ (Type =:= 'snmpv2-trap') orelse
+ (Type =:= report) orelse
+ (Type =:= trap) orelse
+ (Type =:= trappdu)) andalso
+ ((Kind =:= all) orelse (Kind =:= trap_sender)) ->
+ Transport;
+select_transport2(Domain, Type,
+ [#transport{domain = Domain,
+ kind = Kind} = Transport|_Transports])
+ when (Type =:= 'get-response') andalso
+ ((Kind =:= all) orelse (Kind =:= req_responder)) ->
+ Transport;
+select_transport2(Domain, Type, [_|Transports]) ->
+ select_transport2(Domain, Type, Transports).
+
+
+
address_to_domain({Domain, _Addr}) when is_atom(Domain) ->
Domain;
address_to_domain({_Ip, Port}) when is_integer(Port) ->
@@ -1447,47 +1981,62 @@ get_counters([Counter|Counters], Acc) ->
%% ----------------------------------------------------------------
-socket_opts(Domain, {IpAddr, IpPort}, Opts) ->
- [IpPort, % Picked off at socket open, separate argument
- binary
- | case snmp_conf:tdomain_to_family(Domain) of
- inet6 = Family ->
- [Family, {ipv6_v6only, true}];
- Family ->
- [Family]
- end ++
- case get_bind_to_ip_address(Opts) of
- true ->
- [{ip, IpAddr}];
- _ ->
- []
- end ++
- case get_no_reuse_address(Opts) of
- false ->
- [{reuseaddr, true}];
- _ ->
- []
- end ++
- case get_recbuf(Opts) of
- use_default ->
- [];
- Sz ->
- [{recbuf, Sz}]
- end ++
- case get_sndbuf(Opts) of
- use_default ->
- [];
- Sz ->
+%% This extracts the socket options from what is specified for
+%% the transport, or if not, from the "global" socket options.
+socket_opts(Domain, {IpAddr, PortInfo}, SocketOpts, DefaultOpts) ->
+ ?vtrace("socket_opts -> entry with"
+ "~n Domain: ~p"
+ "~n IpAddr: ~p"
+ "~n PortInfo: ~p"
+ "~n SpocketOpts: ~p"
+ "~n DefaultOpts: ~p",
+ [Domain, IpAddr, PortInfo, SocketOpts, DefaultOpts]),
+ Opts =
+ [binary |
+ case snmp_conf:tdomain_to_family(Domain) of
+ inet6 = Family ->
+ [Family, {ipv6_v6only, true}];
+ Family ->
+ [Family]
+ end ++
+ case get_bind_to_ip_address(SocketOpts, DefaultOpts) of
+ true ->
+ [{ip, IpAddr}];
+ _ ->
+ []
+ end ++
+ case get_no_reuse_address(SocketOpts, DefaultOpts) of
+ false ->
+ [{reuseaddr, true}];
+ _ ->
+ []
+ end ++
+ case get_recbuf(SocketOpts, DefaultOpts) of
+ use_default ->
+ [];
+ Sz ->
+ [{recbuf, Sz}]
+ end ++
+ case get_sndbuf(SocketOpts, DefaultOpts) of
+ use_default ->
+ [];
+ Sz ->
[{sndbuf, Sz}]
- end] ++
- case get_extra_sock_opts(Opts) of
+ end
+ ] ++
+ case get_extra_sock_opts(SocketOpts, DefaultOpts) of
ESO when is_list(ESO) ->
ESO;
BadESO ->
error_msg("Invalid 'extra socket options' (=> ignored):"
"~n ~p", [BadESO]),
[]
- end.
+ end,
+ %% <EPHEMERAL-FOR-FUTUR-USE>
+ %% Ephm = get_ephemeral(SocketOpts),
+ %% {Ephm, PortInfo, Opts}.
+ %% </EPHEMERAL-FOR-FUTUR-USE>
+ {none, IpAddr, PortInfo, Opts}.
%% ----------------------------------------------------------------
@@ -1529,27 +2078,47 @@ get_filter_opts(O) ->
get_filter_module(O) ->
snmp_misc:get_option(module, O, ?DEFAULT_FILTER_MODULE).
-get_recbuf(Opts) ->
- snmp_misc:get_option(recbuf, Opts, use_default).
+get_recbuf(Opts, DefaultOpts) ->
+ get_socket_opt(recbuf, Opts, DefaultOpts, use_default).
-get_sndbuf(Opts) ->
- snmp_misc:get_option(sndbuf, Opts, use_default).
+get_sndbuf(Opts, DefaultOpts) ->
+ get_socket_opt(sndbuf, Opts, DefaultOpts, use_default).
-get_no_reuse_address(Opts) ->
- snmp_misc:get_option(no_reuse, Opts, false).
+get_bind_to_ip_address(Opts, DefaultOpts) ->
+ get_socket_opt(bind_to, Opts, DefaultOpts, false).
-get_bind_to_ip_address(Opts) ->
- snmp_misc:get_option(bind_to, Opts, false).
+get_no_reuse_address(Opts, DefaultOpts) ->
+ get_socket_opt(no_reuse, Opts, DefaultOpts, false).
-get_extra_sock_opts(Opts) ->
- snmp_misc:get_option(extra_sock_opts, Opts, []).
+get_extra_sock_opts(Opts, DefaultOpts) ->
+ get_socket_opt(extra_sock_opts, Opts, DefaultOpts, []).
+
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% This is not realy a socket option, but rather socket 'meta'
+%% information. Its still put together with the actual socket
+%% options.
+%% get_ephemeral(SocketOpts) ->
+%% snmp_misc:get_option(ephemeral, SocketOpts, none).
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
+
+get_socket_opt(Opt, Opts, DefaultOpts, DefaultVal) ->
+ snmp_misc:get_option(Opt, Opts,
+ snmp_misc:get_option(Opt, DefaultOpts, DefaultVal)).
+
%% ----------------------------------------------------------------
-error_msg(F,A) ->
+%% error_msg(F) ->
+%% error_msg(F, []).
+
+error_msg(F, A) ->
?snmpa_error("NET-IF server: " ++ F, A).
+warning_msg(F, A) ->
+ ?snmpa_warning("NET-IF server: " ++ F, A).
+
info_msg(F,A) ->
?snmpa_info("NET-IF server: " ++ F, A).
@@ -1582,9 +2151,22 @@ get_info(#state{transports = Transports, reqs = Reqs}) ->
Counters = get_counters(),
[{reqs, Reqs},
{counters, Counters},
- {process_memory, ProcSize}
- | [{port_info, get_port_info(Socket)}
- || #transport{socket = Socket} <- Transports]].
+ {process_memory, ProcSize},
+ {transport_info, [#{tdomain => Domain,
+ taddress => {Address, PortNo},
+ transport_kind => Kind,
+ port_info => PortInfo,
+ opts => Opts,
+ socket_info => get_port_info(Socket),
+ num_reqs => length(Refs)} ||
+ #transport{socket = Socket,
+ domain = Domain,
+ address = Address,
+ port_no = PortNo,
+ port_info = PortInfo,
+ opts = Opts,
+ kind = Kind,
+ req_refs = Refs} <- Transports]}].
proc_mem(P) when is_pid(P) ->
case (catch erlang:process_info(P, memory)) of
@@ -1648,4 +2230,4 @@ get_port_info(Id) ->
BufSz.
-%% ----------------------------------------------------------------
+%% ---------------------------------------------------------------
diff --git a/lib/snmp/src/agent/snmpa_trap.erl b/lib/snmp/src/agent/snmpa_trap.erl
index 121a8c979c..9ee854b67d 100644
--- a/lib/snmp/src/agent/snmpa_trap.erl
+++ b/lib/snmp/src/agent/snmpa_trap.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -920,34 +920,53 @@ send_v1_trap(
do_send_v1_trap(Enter, Spec, V1Res, NVbs, ExtraInfo, NetIf, SysUpTime).
do_send_v1_trap(Enter, Spec, V1Res, NVbs, ExtraInfo, NetIf, SysUpTime) ->
+ ?vtrace("do_send_v1_trap -> try get transports"),
{value, Transports} = snmp_framework_mib:intAgentTransports(get),
- {_Domain, {AgentIp, _AgentPort}} =
- case lists:keyfind(snmpUDPDomain, 1, Transports) of
- false ->
- case lists:keyfind(transportDomainUdpIpv4, 1, Transports) of
- false ->
- ?vtrace(
- "snmpa_trap: cannot send v1 trap "
- "without IPv4 domain: ~p",
- [Transports]),
- user_err(
- "snmpa_trap: cannot send v1 trap "
- "without IPv4 domain: ~p",
- [Transports]);
- DomainAddr ->
- DomainAddr
- end;
- DomainAddr ->
- DomainAddr
- end,
- TrapPdu = make_v1_trap_pdu(Enter, Spec, NVbs, SysUpTime, AgentIp),
- AddrCommunities = mk_addr_communities(V1Res),
- lists:foreach(
- fun ({Community, Addrs}) ->
- ?vtrace("send v1 trap to ~p",[Addrs]),
- NetIf ! {send_pdu, 'version-1', TrapPdu,
- {community, Community}, Addrs, ExtraInfo}
- end, AddrCommunities).
+ ?vtrace("do_send_v1_trap -> transports: "
+ "~n ~p", [Transports]),
+ try
+ begin
+ {_Domain, {AgentIp, _AgentPort}, _Kind, _Opts} =
+ case lists:keyfind(snmpUDPDomain, 1, Transports) of
+ false ->
+ case lists:keyfind(transportDomainUdpIpv4, 1, Transports) of
+ false ->
+ ?vlog(
+ "do_send_v1_trap -> cannot send v1 trap "
+ "without IPv4 domain: ~p",
+ [Transports]),
+ user_err(
+ "snmpa_trap: cannot send v1 trap "
+ "without IPv4 domain: "
+ "~n ~p", [Transports]),
+ throw({error,
+ "Cannot send v1 trap without IPv4 domain"});
+ DomainAddr ->
+ ?vtrace("do_send_v1_trap -> found ~w transport:"
+ "~n ~p",
+ [transportDomainUdpIpv4, DomainAddr]),
+ DomainAddr
+ end;
+ DomainAddr ->
+ ?vtrace("do_send_v1_trap -> found ~w transport:"
+ "~n ~p",
+ [snmpUDPDomain, DomainAddr]),
+ DomainAddr
+ end,
+ TrapPdu = make_v1_trap_pdu(Enter, Spec, NVbs, SysUpTime, AgentIp),
+ AddrCommunities = mk_addr_communities(V1Res),
+ lists:foreach(
+ fun ({Community, Addrs}) ->
+ ?vtrace("do_send_v1_trap -> send v1 trap to ~p",[Addrs]),
+ NetIf ! {send_pdu, 'version-1', TrapPdu,
+ {community, Community}, Addrs, ExtraInfo}
+ end, AddrCommunities)
+ end
+ catch
+ throw:{error, _} = ERROR:_ ->
+ ERROR
+ end.
+
send_v2_trap(_TrapRec, [], _Vbs, _Recv, _ExtraInfo, _NetIf, _SysUpTime) ->
ok;
diff --git a/lib/snmp/src/agent/snmpa_usm.erl b/lib/snmp/src/agent/snmpa_usm.erl
index 1debceae98..6c660e1e8b 100644
--- a/lib/snmp/src/agent/snmpa_usm.erl
+++ b/lib/snmp/src/agent/snmpa_usm.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
%%
%% %CopyrightEnd%
%%
+%% USM: RFC 3414
%% AES: RFC 3826
%%
@@ -475,19 +476,10 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel,
_ -> % 3.1.1a
SecData
end,
- %% 3.1.4
- ?vtrace("generate_outgoing_msg -> [3.1.4]"
- "~n UserName: ~p"
- "~n AuthProtocol: ~p"
- "~n PrivProtocol: ~p",
- [UserName, AuthProtocol, PrivProtocol]),
- ScopedPduBytes = Message#message.data,
- {ScopedPduData, MsgPrivParams} =
- encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel),
+ %% 3.1.6
SnmpEngineID = LocalEngineID,
?vtrace("generate_outgoing_msg -> SnmpEngineID: ~p [3.1.6]",
[SnmpEngineID]),
- %% 3.1.6
{MsgAuthEngineBoots, MsgAuthEngineTime} =
case snmp_misc:is_auth(SecLevel) of
false when SecData =:= [] -> % not a response
@@ -501,8 +493,18 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel,
{get_local_engine_boots(SnmpEngineID),
get_local_engine_time(SnmpEngineID)}
end,
- %% 3.1.5 - 3.1.7
- ?vtrace("generate_outgoing_msg -> [3.1.5 - 3.1.7]",[]),
+ %% 3.1.4
+ ?vtrace("generate_outgoing_msg -> [3.1.4]"
+ "~n UserName: ~p"
+ "~n AuthProtocol: ~p"
+ "~n PrivProtocol: ~p",
+ [UserName, AuthProtocol, PrivProtocol]),
+ ScopedPduBytes = Message#message.data,
+ {ScopedPduData, MsgPrivParams} =
+ encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel,
+ MsgAuthEngineBoots, MsgAuthEngineTime),
+ %% 3.1.5, 3.1.7
+ ?vtrace("generate_outgoing_msg -> [3.1.5, 3.1.7]",[]),
UsmSecParams =
#usmSecurityParameters{msgAuthoritativeEngineID = SecEngineID,
msgAuthoritativeEngineBoots = MsgAuthEngineBoots,
@@ -561,12 +563,17 @@ generate_discovery_msg(Message,
end
end,
ScopedPduBytes = Message#message.data,
+ MsgAuthEngineBoots = 0,
+ MsgAuthEngineTime = 0,
{ScopedPduData, MsgPrivParams} =
- encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel),
+ encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel,
+ MsgAuthEngineBoots, MsgAuthEngineTime),
UsmSecParams =
#usmSecurityParameters{msgAuthoritativeEngineID = SecEngineID,
- msgAuthoritativeEngineBoots = 0, % Boots,
- msgAuthoritativeEngineTime = 0, % Time,
+ msgAuthoritativeEngineBoots = % Boots
+ MsgAuthEngineBoots,
+ msgAuthoritativeEngineTime = % Time
+ MsgAuthEngineTime,
msgUserName = UserName,
msgPrivacyParameters = MsgPrivParams},
Message2 = Message#message{data = ScopedPduData},
@@ -575,14 +582,15 @@ generate_discovery_msg(Message,
%% Ret: {ScopedPDU, MsgPrivParams} - both are already encoded as OCTET STRINGs
-encrypt(Data, PrivProtocol, PrivKey, SecLevel) ->
+encrypt(Data, PrivProtocol, PrivKey, SecLevel, EngineBoots, EngineTime) ->
case snmp_misc:is_priv(SecLevel) of
false -> % 3.1.4b
?vtrace("encrypt -> 3.1.4b",[]),
{Data, []};
true -> % 3.1.4a
?vtrace("encrypt -> 3.1.4a",[]),
- case (catch try_encrypt(PrivProtocol, PrivKey, Data)) of
+ case (catch try_encrypt(PrivProtocol, PrivKey, Data,
+ EngineBoots, EngineTime)) of
{ok, ScopedPduData, MsgPrivParams} ->
?vtrace("encrypt -> encrypted - now encode tag",[]),
{snmp_pdus:enc_oct_str_tag(ScopedPduData), MsgPrivParams};
@@ -597,12 +605,13 @@ encrypt(Data, PrivProtocol, PrivKey, SecLevel) ->
end
end.
-try_encrypt(?usmNoPrivProtocol, _PrivKey, _Data) -> % 3.1.2
+try_encrypt(
+ ?usmNoPrivProtocol, _PrivKey, _Data, _EngineBoots, _EngineTime) -> % 3.1.2
error(unsupportedSecurityLevel);
-try_encrypt(?usmDESPrivProtocol, PrivKey, Data) ->
+try_encrypt(?usmDESPrivProtocol, PrivKey, Data, _EngineBoots, _EngineTime) ->
des_encrypt(PrivKey, Data);
-try_encrypt(?usmAesCfb128Protocol, PrivKey, Data) ->
- aes_encrypt(PrivKey, Data).
+try_encrypt(?usmAesCfb128Protocol, PrivKey, Data, EngineBoots, EngineTime) ->
+ aes_encrypt(PrivKey, Data, EngineBoots, EngineTime).
authenticate_outgoing(Message, UsmSecParams,
@@ -658,11 +667,9 @@ get_des_salt() ->
EngineBoots = snmp_framework_mib:get_engine_boots(),
[?i32(EngineBoots), ?i32(SaltInt)].
-aes_encrypt(PrivKey, Data) ->
- EngineBoots = snmp_framework_mib:get_engine_boots(),
- EngineTime = snmp_framework_mib:get_engine_time(),
- snmp_usm:aes_encrypt(PrivKey, Data, fun get_aes_salt/0,
- EngineBoots, EngineTime).
+aes_encrypt(PrivKey, Data, EngineBoots, EngineTime) ->
+ snmp_usm:aes_encrypt(PrivKey, Data, fun get_aes_salt/0,
+ EngineBoots, EngineTime).
aes_decrypt(PrivKey, UsmSecParams, EncData) ->
#usmSecurityParameters{msgPrivacyParameters = PrivParams,
diff --git a/lib/snmp/src/app/snmp.erl b/lib/snmp/src/app/snmp.erl
index 1e6a93deff..e634890147 100644
--- a/lib/snmp/src/app/snmp.erl
+++ b/lib/snmp/src/app/snmp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -64,7 +64,7 @@
-export([c/1, c/2, is_consistent/1, mib_to_hrl/1,
compile/3]).
-%% Agent exports (Dont use these, they will be removed eventually)
+%% Agent exports (Dont use these, they will be removed in OTP 24)
-export([current_request_id/0, current_community/0, current_address/0,
current_context/0, current_net_if_data/0,
@@ -129,53 +129,56 @@
%% This is for XREF
--deprecated([{c, 1, eventually},
- {c, 2, eventually},
- {compile, 3, eventually},
- {is_consistent, 1, eventually},
- {mib_to_hrl, 1, eventually},
-
- {change_log_size, 1, eventually},
- {log_to_txt, 2, eventually},
- {log_to_txt, 3, eventually},
- {log_to_txt, 4, eventually},
-
- {current_request_id, 0, eventually},
- {current_community, 0, eventually},
- {current_address, 0, eventually},
- {current_context, 0, eventually},
- {current_net_if_data, 0, eventually},
-
- {get_symbolic_store_db, 0, eventually},
- {name_to_oid, 1, eventually},
- {name_to_oid, 2, eventually},
- {oid_to_name, 1, eventually},
- {oid_to_name, 2, eventually},
- {int_to_enum, 2, eventually},
- {int_to_enum, 3, eventually},
- {enum_to_int, 2, eventually},
- {enum_to_int, 3, eventually},
-
- {get, 2, eventually},
- {info, 1, eventually},
- {load_mibs, 2, eventually},
- {unload_mibs, 2, eventually},
- {dump_mibs, 0, eventually},
- {dump_mibs, 1, eventually},
-
- {register_subagent, 3, eventually},
- {unregister_subagent, 2, eventually},
-
- {send_notification, 3, eventually},
- {send_notification, 4, eventually},
- {send_notification, 5, eventually},
- {send_notification, 6, eventually},
- {send_trap, 3, eventually},
- {send_trap, 4, eventually},
-
- {add_agent_caps, 2, eventually},
- {del_agent_caps, 1, eventually},
- {get_agent_caps, 0, eventually}]).
+-deprecated(
+ [
+ {c, 1, "use snmpc:compile/1 instead."},
+ {c, 2, "use snmpc:compile/2 instead."},
+ {compile, 3, "use snmpc:compile/3 instead."},
+ {is_consistent, 1, "use snmpc:is_consistent/1 instead."},
+ {mib_to_hrl, 1, "use snmpc:mib_to_hrl/1 instead."},
+
+ {change_log_size, 1, "use snmpa:change_log_size/1 instead."},
+ {log_to_txt, 2, "use snmpa:log_to_txt/2 instead."},
+ {log_to_txt, 3, "use snmpa:log_to_txt/3 instead."},
+ {log_to_txt, 4, "use snmpa:log_to_txt/4 instead."},
+
+ {current_request_id, 0, "use snmpa:current_request_id/0 instead."},
+ {current_community, 0, "use snmpa:current_community/0 instead."},
+ {current_address, 0, "use snmpa:current_address/0 instead."},
+ {current_context, 0, "use snmpa:current_context/0 instead."},
+ {current_net_if_data, 0, "use snmpa:current_net_if_data/0 instead."},
+
+ {get_symbolic_store_db, 0, "use snmpa:get_symbolic_store_db/0 instead."},
+ {name_to_oid, 1, "use snmpa:name_to_oid/1 instead."},
+ {name_to_oid, 2, "use snmpa:name_to_oid/2 instead."},
+ {oid_to_name, 1, "use snmpa:oid_to_name/1 instead."},
+ {oid_to_name, 2, "use snmpa:oid_to_name/2 instead."},
+ {int_to_enum, 2, "use snmpa:int_to_enum/2 instead."},
+ {int_to_enum, 3, "use snmpa:int_to_enum/3 instead."},
+ {enum_to_int, 2, "use snmpa:enum_to_int/2 instead."},
+ {enum_to_int, 3, "use snmpa:enum_to_int/3 instead."},
+
+ {get, 2, "use snmpa:get/2 instead."},
+ {info, 1, "use snmpa:info/1 instead."},
+ {load_mibs, 2, "use snmpa:load_mibs/2 instead."},
+ {unload_mibs, 2, "use snmpa:unload_mibs/2 instead."},
+ {dump_mibs, 0, "use snmpa:dump_mibs/0 instead."},
+ {dump_mibs, 1, "use snmpa:dump_mibs/1 instead."},
+
+ {register_subagent, 3, "use snmpa:register_subagent/3 instead."},
+ {unregister_subagent, 2, "use snmpa:unregister_subagent/2 instead."},
+
+ {send_notification, 3, "use snmpa:send_notification/3 instead."},
+ {send_notification, 4, "use snmpa:send_notification/4 instead."},
+ {send_notification, 5, "use snmpa:send_notification/5 instead."},
+ {send_notification, 6, "use snmpa:send_notification/6 instead."},
+ {send_trap, 3, "use snmpa:send_trap/3 instead."},
+ {send_trap, 4, "use snmpa:send_trap/4 instead."},
+
+ {add_agent_caps, 2, "use snmpa:add_agent_caps/2 instead."},
+ {del_agent_caps, 1, "use snmpa:del_agent_caps/1 instead."},
+ {get_agent_caps, 0, "use snmpa:get_agent_caps/0 instead."}
+ ]).
-define(APPLICATION, snmp).
diff --git a/lib/snmp/src/compile/snmpc_misc.erl b/lib/snmp/src/compile/snmpc_misc.erl
index 312074f2e7..d0fee418e1 100644
--- a/lib/snmp/src/compile/snmpc_misc.erl
+++ b/lib/snmp/src/compile/snmpc_misc.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -156,7 +156,8 @@ loop(Fd, Res, Func, StartLine, File) ->
%% io:read modified to give us line numbers.
%%-----------------------------------------------------------------
read(Io, Prompt, StartLine) ->
- case io:request(Io, {get_until, Prompt, erl_scan, tokens, [StartLine]}) of
+ Enc = latin1,
+ case io:request(Io, {get_until, Enc, Prompt, erl_scan, tokens, [StartLine]}) of
{ok, Toks, EndLine} ->
case erl_parse:parse_term(Toks) of
{ok, Term} ->
diff --git a/lib/snmp/src/manager/snmpm.erl b/lib/snmp/src/manager/snmpm.erl
index c0859f6fd8..ffa6b0012c 100644
--- a/lib/snmp/src/manager/snmpm.erl
+++ b/lib/snmp/src/manager/snmpm.erl
@@ -119,6 +119,49 @@
]).
+-deprecated(
+ [
+ {sync_get, 3, "use snmpm:sync_get2/3 instead."},
+ {sync_get, 4, "use snmpm:sync_get2/4 instead."},
+ {sync_get, 5, "use snmpm:sync_get2/4 instead."},
+ {sync_get, 6, "use snmpm:sync_get2/4 instead."},
+
+ {async_get, 3, "use snmpm:async_get2/3 instead."},
+ {async_get, 4, "use snmpm:async_get2/4 instead."},
+ {async_get, 5, "use snmpm:async_get2/4 instead."},
+ {async_get, 6, "use snmpm:async_get2/4 instead."},
+
+ {sync_get_next, 3, "use snmpm:sync_get_next2/3 instead."},
+ {sync_get_next, 4, "use snmpm:sync_get_next2/4 instead."},
+ {sync_get_next, 5, "use snmpm:sync_get_next2/4 instead."},
+ {sync_get_next, 6, "use snmpm:sync_get_next2/4 instead."},
+
+ {async_get_next, 3, "use snmpm:async_get_next2/3 instead."},
+ {async_get_next, 4, "use snmpm:async_get_next2/4 instead."},
+ {async_get_next, 5, "use snmpm:async_get_next2/4 instead."},
+ {async_get_next, 6, "use snmpm:async_get_next2/4 instead."},
+
+ {sync_set, 3, "use snmpm:sync_set2/3 instead."},
+ {sync_set, 4, "use snmpm:sync_set2/4 instead."},
+ {sync_set, 5, "use snmpm:sync_set2/4 instead."},
+ {sync_set, 6, "use snmpm:sync_set2/4 instead."},
+
+ {async_set, 3, "use snmpm:async_set2/3 instead."},
+ {async_set, 4, "use snmpm:async_set2/4 instead."},
+ {async_set, 5, "use snmpm:async_set2/4 instead."},
+ {async_set, 6, "use snmpm:async_set2/4 instead."},
+
+ {sync_get_bulk, 5, "use snmpm:sync_get_bulk2/5 instead."},
+ {sync_get_bulk, 6, "use snmpm:sync_get_bulk2/6 instead."},
+ {sync_get_bulk, 7, "use snmpm:sync_get_bulk2/6 instead."},
+ {sync_get_bulk, 8, "use snmpm:sync_get_bulk2/6 instead."},
+
+ {async_get_bulk, 5, "use snmpm:async_get_bulk2/5 instead."},
+ {async_get_bulk, 6, "use snmpm:async_get_bulk2/6 instead."},
+ {async_get_bulk, 7, "use snmpm:async_get_bulk2/6 instead."},
+ {async_get_bulk, 8, "use snmpm:async_get_bulk2/6 instead."}
+ ]).
+
-include_lib("snmp/src/misc/snmp_debug.hrl").
-include_lib("snmp/include/snmp_types.hrl").
-include("snmpm_atl.hrl").
@@ -514,7 +557,7 @@ sync_get2(UserId, TargetName, Oids) ->
sync_get2(UserId, TargetName, Oids, SendOpts)
when is_list(Oids) andalso is_list(SendOpts) ->
- snmpm_server:sync_get2(UserId, TargetName, Oids, SendOpts).
+ snmpm_server:sync_get(UserId, TargetName, Oids, SendOpts).
%% <BACKWARD-COMPAT>
sync_get(UserId, TargetName, Oids) ->
@@ -551,7 +594,7 @@ async_get2(UserId, TargetName, Oids) ->
async_get2(UserId, TargetName, Oids, SendOpts)
when is_list(Oids) andalso is_list(SendOpts) ->
- snmpm_server:async_get2(UserId, TargetName, Oids, SendOpts).
+ snmpm_server:async_get(UserId, TargetName, Oids, SendOpts).
%% <BACKWARD-COMPAT>
async_get(UserId, TargetName, Oids) ->
@@ -583,7 +626,7 @@ sync_get_next2(UserId, TargetName, Oids) ->
sync_get_next2(UserId, TargetName, Oids, SendOpts)
when is_list(Oids) andalso is_list(SendOpts) ->
- snmpm_server:sync_get_next2(UserId, TargetName, Oids, SendOpts).
+ snmpm_server:sync_get_next(UserId, TargetName, Oids, SendOpts).
%% <BACKWARD-COMPAT>
sync_get_next(UserId, TargetName, Oids) ->
@@ -616,7 +659,7 @@ async_get_next2(UserId, TargetName, Oids) ->
async_get_next2(UserId, TargetName, Oids, SendOpts)
when is_list(Oids) andalso is_list(SendOpts) ->
- snmpm_server:async_get_next2(UserId, TargetName, Oids, SendOpts).
+ snmpm_server:async_get_next(UserId, TargetName, Oids, SendOpts).
%% <BACKWARD-COMPAT>
async_get_next(UserId, TargetName, Oids) ->
@@ -649,7 +692,7 @@ sync_set2(UserId, TargetName, VarsAndVals) ->
sync_set2(UserId, TargetName, VarsAndVals, SendOpts)
when is_list(VarsAndVals) andalso is_list(SendOpts) ->
- snmpm_server:sync_set2(UserId, TargetName, VarsAndVals, SendOpts).
+ snmpm_server:sync_set(UserId, TargetName, VarsAndVals, SendOpts).
%% <BACKWARD-COMPAT>
sync_set(UserId, TargetName, VarsAndVals) ->
@@ -682,7 +725,7 @@ async_set2(UserId, TargetName, VarsAndVals) ->
async_set2(UserId, TargetName, VarsAndVals, SendOpts)
when is_list(VarsAndVals) andalso is_list(SendOpts) ->
- snmpm_server:async_set2(UserId, TargetName, VarsAndVals, SendOpts).
+ snmpm_server:async_set(UserId, TargetName, VarsAndVals, SendOpts).
%% <BACKWARD-COMPAT>
async_set(UserId, TargetName, VarsAndVals) ->
@@ -718,16 +761,8 @@ sync_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts)
is_integer(MaxRep) andalso
is_list(Oids) andalso
is_list(SendOpts) ->
- %% p("sync_get_bulk -> entry with"
- %% "~n UserId: ~p"
- %% "~n TargetName: ~p"
- %% "~n NonRep: ~p"
- %% "~n MaxRep: ~p"
- %% "~n Oids: ~p"
- %% "~n SendOpts: ~p",
- %% [UserId, TargetName, NonRep, MaxRep, Oids, SendOpts]),
- snmpm_server:sync_get_bulk2(UserId, TargetName,
- NonRep, MaxRep, Oids, SendOpts).
+ snmpm_server:sync_get_bulk(UserId, TargetName,
+ NonRep, MaxRep, Oids, SendOpts).
%% <BACKWARD-COMPAT>
sync_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) ->
@@ -745,13 +780,6 @@ sync_get_bulk(UserId, TargetName, NonRep, MaxRep, Context, Oids)
is_integer(MaxRep) andalso
is_list(Context) andalso
is_list(Oids) ->
- %% p("sync_get_bulk -> entry with"
- %% "~n UserId: ~p"
- %% "~n TargetName: ~p"
- %% "~n NonRep: ~p"
- %% "~n MaxRep: ~p"
- %% "~n Context: ~p"
- %% "~n Oids: ~p", [UserId, TargetName, NonRep, MaxRep, Context, Oids]),
SendOpts = [{context, Context}],
sync_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts).
@@ -777,8 +805,8 @@ async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts)
is_integer(MaxRep) andalso
is_list(Oids) andalso
is_list(SendOpts) ->
- snmpm_server:async_get_bulk2(UserId, TargetName,
- NonRep, MaxRep, Oids, SendOpts).
+ snmpm_server:async_get_bulk(UserId, TargetName,
+ NonRep, MaxRep, Oids, SendOpts).
%% <BACKWARD-COMPAT>
async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) ->
diff --git a/lib/snmp/src/manager/snmpm_conf.erl b/lib/snmp/src/manager/snmpm_conf.erl
index d097b40438..c74b69d023 100644
--- a/lib/snmp/src/manager/snmpm_conf.erl
+++ b/lib/snmp/src/manager/snmpm_conf.erl
@@ -337,7 +337,7 @@ do_write_usm_conf(
{EngineID, UserName, SecName,
AuthP, AuthKey, PrivP, PrivKey}) ->
io:format(
- Fd, "{\"~s\", \"~s\", \"~s\", í~w, ~w, ~w, ~w}.~n",
+ Fd, "{\"~s\", \"~s\", \"~s\", ~w, ~w, ~w, ~w}.~n",
[EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey]);
do_write_usm_conf(_Fd, Crap) ->
error({bad_usm_conf, Crap}).
diff --git a/lib/snmp/src/manager/snmpm_config.erl b/lib/snmp/src/manager/snmpm_config.erl
index f69bd16577..356ba44b08 100644
--- a/lib/snmp/src/manager/snmpm_config.erl
+++ b/lib/snmp/src/manager/snmpm_config.erl
@@ -363,7 +363,7 @@ register_agent(UserId, TargetName, Config0)
%% Check:
%% 1) That the mandatory configs are present
%% 2) That no illegal config, e.g. user_id (used internally),
- %% is not present
+ %% are present
%% 3) Check that there are no invalid or erroneous configs
%% 4) Check that the manager is capable of using the selected version
try
@@ -1444,10 +1444,10 @@ verify_server_cbproxy(CBP) ->
verify_server_nis(none) ->
ok;
-verify_server_nis({PingTo, PongTo} = V) when is_integer(PingTo) andalso
- (PingTo > 0) andalso
- is_integer(PongTo) andalso
- (PongTo > 0) ->
+verify_server_nis({PingTo, PongTo} = _V) when is_integer(PingTo) andalso
+ (PingTo > 0) andalso
+ is_integer(PongTo) andalso
+ (PongTo > 0) ->
ok;
verify_server_nis(NIS) ->
error({invalid_server_netif_sup, NIS}).
@@ -3118,7 +3118,7 @@ do_update_usm_user_info(Key,
{error, {unsupported_crypto, des_cbc}}
end;
do_update_usm_user_info(Key,
- #usm_user{priv = usmAesCfb128Protocoll} = User,
+ #usm_user{priv = usmAesCfb128Protocol} = User,
priv_key, Val)
when length(Val) =:= 16 ->
case is_crypto_supported(aes_cfb128) of
diff --git a/lib/snmp/src/manager/snmpm_net_if.erl b/lib/snmp/src/manager/snmpm_net_if.erl
index 5c18058a7a..1eb5ae216a 100644
--- a/lib/snmp/src/manager/snmpm_net_if.erl
+++ b/lib/snmp/src/manager/snmpm_net_if.erl
@@ -607,6 +607,19 @@ handle_info(
{noreply, State}
end;
+handle_info(
+ {udp_error, Socket, Error},
+ #state{transports = Transports} = State) ->
+ ?vinfo("got udp-error on ~p: ~w", [Socket, Error]),
+ case lists:keyfind(Socket, #transport.socket, Transports) of
+ #transport{socket = Socket} = Transport ->
+ handle_udp_error(Transport, Error),
+ {noreply, State};
+ false ->
+ handle_udp_error_unknown(Socket, Error),
+ {noreply, State}
+ end;
+
handle_info(inform_response_gc, State) ->
?vlog("received inform_response_gc message", []),
State2 = handle_inform_response_gc(State),
@@ -639,6 +652,41 @@ handle_info(Info, State) ->
handle_info_unknown(Info, State).
+handle_udp_error(#transport{socket = Socket}, Error) ->
+ try inet:sockname(Socket) of
+ {ok, {IP, Port}} ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p (~p, ~p)"
+ "~n Error: ~p", [Socket, IP, Port, Error]);
+ {error, _} ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ catch
+ _:_:_ ->
+ error_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ end.
+
+handle_udp_error_unknown(Socket, Error) ->
+ try inet:sockname(Socket) of
+ {ok, {IP, Port}} ->
+ warning_msg("UDP Error for unknown transport: "
+ "~n Socket: ~p (~p, ~p)"
+ "~n Error: ~p", [Socket, IP, Port, Error]);
+ {error, _} ->
+ warning_msg("UDP Error for unknown transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ catch
+ _:_:_ ->
+ warning_msg("UDP Error for transport: "
+ "~n Socket: ~p"
+ "~n Error: ~p", [Socket, Error])
+ end.
+
+
handle_info_unknown(Info, State) ->
warning_msg("received unknown info: ~n~p", [Info]),
{noreply, State}.
diff --git a/lib/snmp/src/manager/snmpm_server.erl b/lib/snmp/src/manager/snmpm_server.erl
index 1a8d09ab9b..ca18637b8d 100644
--- a/lib/snmp/src/manager/snmpm_server.erl
+++ b/lib/snmp/src/manager/snmpm_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -33,14 +33,14 @@
register_user/4, register_user_monitor/4, unregister_user/1,
- sync_get2/4,
- async_get2/4,
- sync_get_next2/4,
- async_get_next2/4,
- sync_get_bulk2/6,
- async_get_bulk2/6,
- sync_set2/4,
- async_set2/4,
+ sync_get/4,
+ async_get/4,
+ sync_get_next/4,
+ async_get_next/4,
+ sync_get_bulk/6,
+ async_get_bulk/6,
+ sync_set/4,
+ async_set/4,
cancel_async_request/2,
%% discovery/2, discovery/3, discovery/4, discovery/5, discovery/6,
@@ -56,20 +56,6 @@
]).
-%% <BACKWARD-COMPAT>
--export([sync_get/4, sync_get/5, sync_get/6,
- async_get/4, async_get/5, async_get/6,
- sync_get_next/4, sync_get_next/5, sync_get_next/6,
- async_get_next/4, async_get_next/5, async_get_next/6,
- sync_get_bulk/6, sync_get_bulk/7, sync_get_bulk/8,
- async_get_bulk/6, async_get_bulk/7, async_get_bulk/8,
- sync_set/4, sync_set/5, sync_set/6,
- async_set/4, async_set/5, async_set/6
- ]).
-%% </BACKWARD-COMPAT>
-
-
-
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
@@ -281,233 +267,62 @@ unregister_user(UserId) ->
%% The reason why we have a sync_get2 is to simplify backward
%% compatibillity.
-%% sync_get2(UserId, TargetName, Oids) ->
-%% sync_get2(UserId, TargetName, Oids, []).
-sync_get2(UserId, TargetName, Oids, Opts) ->
+sync_get(UserId, TargetName, Oids, Opts) ->
call({sync_get, self(), UserId, TargetName, Oids, Opts}).
-%% <BACKWARD-COMPAT>
-sync_get(UserId, TargetName, CtxName, Oids) ->
- sync_get(UserId, TargetName, CtxName, Oids,
- ?DEFAULT_SYNC_GET_TIMEOUT).
-
-sync_get(UserId, TargetName, CtxName, Oids, Timeout) ->
- sync_get(UserId, TargetName, CtxName, Oids, Timeout, ?DEFAULT_EXTRA_INFO).
-
-sync_get(UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo)
- when is_list(TargetName) andalso
- is_list(CtxName) andalso
- is_list(Oids) andalso
- is_integer(Timeout) ->
- Opts = [{context, CtxName}, {timeout, Timeout}, {extra, ExtraInfo}],
- sync_get2(UserId, TargetName, Oids, Opts).
-%% </BACKWARD-COMPAT>
-
-
%% -- [async] get --
-%% async_get2(UserId, TargetName, Oids) ->
-%% async_get2(UserId, TargetName, Oids, []).
-async_get2(UserId, TargetName, Oids, SendOpts) ->
+async_get(UserId, TargetName, Oids, SendOpts) ->
call({async_get, self(), UserId, TargetName, Oids, SendOpts}).
-%% <BACKWARD-COMPAT>
-async_get(UserId, TargetName, CtxName, Oids) ->
- async_get(UserId, TargetName, CtxName, Oids,
- ?DEFAULT_ASYNC_GET_TIMEOUT, ?DEFAULT_EXTRA_INFO).
-
-async_get(UserId, TargetName, CtxName, Oids, Expire) ->
- async_get(UserId, TargetName, CtxName, Oids, Expire, ?DEFAULT_EXTRA_INFO).
-
-async_get(UserId, TargetName, CtxName, Oids, Expire, ExtraInfo)
- when (is_list(TargetName) andalso
- is_list(CtxName) andalso
- is_list(Oids) andalso
- is_integer(Expire) andalso (Expire >= 0)) ->
- SendOpts = [{context, CtxName}, {expire, Expire}, {extra, ExtraInfo}],
- async_get2(UserId, TargetName, Oids, SendOpts).
-%% </BACKWARD-COMPAT>
-
-
%% -- [sync] get-next --
-%% sync_get_next2(UserId, TargetName, Oids) ->
-%% sync_get_next2(UserId, TargetName, Oids, []).
-sync_get_next2(UserId, TargetName, Oids, SendOpts) ->
+sync_get_next(UserId, TargetName, Oids, SendOpts) ->
call({sync_get_next, self(), UserId, TargetName, Oids, SendOpts}).
-%% <BACKWARD-COMPAT>
-sync_get_next(UserId, TargetName, CtxName, Oids) ->
- sync_get_next(UserId, TargetName, CtxName, Oids,
- ?DEFAULT_SYNC_GET_TIMEOUT, ?DEFAULT_EXTRA_INFO).
-
-sync_get_next(UserId, TargetName, CtxName, Oids, Timeout) ->
- sync_get_next(UserId, TargetName, CtxName, Oids, Timeout,
- ?DEFAULT_EXTRA_INFO).
-
-sync_get_next(UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo)
- when is_list(TargetName) andalso
- is_list(CtxName) andalso
- is_list(Oids) andalso
- is_integer(Timeout) ->
- SendOpts = [{context, CtxName}, {timeout, Timeout}, {extra, ExtraInfo}],
- sync_get_next2(UserId, TargetName, Oids, SendOpts).
-%% <BACKWARD-COMPAT>
-
-
%% -- [async] get-next --
-%% async_get_next2(UserId, TargetName, Oids) ->
-%% async_get_next2(UserId, TargetName, Oids, []).
-async_get_next2(UserId, TargetName, Oids, SendOpts) ->
+async_get_next(UserId, TargetName, Oids, SendOpts) ->
call({async_get_next, self(), UserId, TargetName, Oids, SendOpts}).
-async_get_next(UserId, TargetName, CtxName, Oids) ->
- async_get_next(UserId, TargetName, CtxName, Oids,
- ?DEFAULT_ASYNC_GET_NEXT_TIMEOUT, ?DEFAULT_EXTRA_INFO).
-
-async_get_next(UserId, TargetName, CtxName, Oids, Expire) ->
- async_get_next(UserId, TargetName, CtxName, Oids, Expire,
- ?DEFAULT_EXTRA_INFO).
-
-async_get_next(UserId, TargetName, CtxName, Oids, Expire, ExtraInfo)
- when (is_list(TargetName) andalso
- is_list(CtxName) andalso
- is_list(Oids) andalso
- is_integer(Expire) andalso (Expire >= 0)) ->
- SendOpts = [{context, CtxName}, {expire, Expire}, {extra, ExtraInfo}],
- async_get_next2(UserId, TargetName, Oids, SendOpts).
-
-
%% -- [sync] get-bulk --
-%% sync_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids) ->
-%% sync_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, []).
-sync_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) ->
- %% p("sync_get_bulk2 -> entry with"
- %% "~n UserId: ~p"
- %% "~n TargetName: ~p"
- %% "~n NonRep: ~p"
- %% "~n MaxRep: ~p"
- %% "~n Oids: ~p"
- %% "~n SendOpts: ~p",
- %% [UserId, TargetName, NonRep, MaxRep, Oids, SendOpts]),
+sync_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) ->
call({sync_get_bulk, self(), UserId, TargetName,
NonRep, MaxRep, Oids, SendOpts}).
-sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids) ->
- sync_get_bulk(UserId, TargetName,
- NonRep, MaxRep, CtxName, Oids,
- ?DEFAULT_SYNC_GET_TIMEOUT, ?DEFAULT_EXTRA_INFO).
-
-sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Timeout) ->
- sync_get_bulk(UserId, TargetName,
- NonRep, MaxRep, CtxName, Oids,
- Timeout, ?DEFAULT_EXTRA_INFO).
-
-sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Timeout,
- ExtraInfo)
- when is_list(TargetName) andalso
- is_integer(NonRep) andalso
- is_integer(MaxRep) andalso
- is_list(CtxName) andalso
- is_list(Oids) andalso
- is_integer(Timeout) ->
- SendOpts = [{context, CtxName}, {timeout, Timeout}, {extra, ExtraInfo}],
- sync_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts).
%% -- [async] get-bulk --
-%% async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids) ->
-%% async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, []).
-async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) ->
+async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) ->
call({async_get_bulk, self(), UserId, TargetName, NonRep, MaxRep,
Oids, SendOpts}).
-async_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids) ->
- async_get_bulk(UserId, TargetName,
- NonRep, MaxRep, CtxName, Oids,
- ?DEFAULT_ASYNC_GET_BULK_TIMEOUT, ?DEFAULT_EXTRA_INFO).
-
-async_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Expire) ->
- async_get_bulk(UserId, TargetName,
- NonRep, MaxRep, CtxName, Oids,
- Expire, ?DEFAULT_EXTRA_INFO).
-
-async_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Expire,
- ExtraInfo)
- when is_list(TargetName) andalso
- is_integer(NonRep) andalso
- is_integer(MaxRep) andalso
- is_list(CtxName) andalso
- is_list(Oids) andalso
- is_integer(Expire) ->
- SendOpts = [{context, CtxName}, {expire, Expire}, {extra, ExtraInfo}],
- async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts).
-
-
%% -- [sync] set --
%% VarsAndValues is: {PlainOid, o|s|i, Value} (unknown mibs) | {Oid, Value}
-%% sync_set2(UserId, TargetName, VarsAndVals) ->
-%% sync_set2(UserId, TargetName, VarsAndVals, []).
-sync_set2(UserId, TargetName, VarsAndVals, SendOpts) ->
+sync_set(UserId, TargetName, VarsAndVals, SendOpts) ->
call({sync_set, self(), UserId, TargetName, VarsAndVals, SendOpts}).
-sync_set(UserId, TargetName, CtxName, VarsAndVals) ->
- sync_set(UserId, TargetName, CtxName, VarsAndVals,
- ?DEFAULT_SYNC_SET_TIMEOUT, ?DEFAULT_EXTRA_INFO).
-
-sync_set(UserId, TargetName, CtxName, VarsAndVals, Timeout) ->
- sync_set(UserId, TargetName, CtxName, VarsAndVals,
- Timeout, ?DEFAULT_EXTRA_INFO).
-
-sync_set(UserId, TargetName, CtxName, VarsAndVals, Timeout, ExtraInfo)
- when is_list(TargetName) andalso
- is_list(CtxName) andalso
- is_list(VarsAndVals) andalso
- is_integer(Timeout) ->
- SendOpts = [{context, CtxName}, {timeout, Timeout}, {extra, ExtraInfo}],
- sync_set2(UserId, TargetName, VarsAndVals, SendOpts).
-
-
%% -- [async] set --
-%% async_set2(UserId, TargetName, VarsAndVals) ->
-%% async_set2(UserId, TargetName, VarsAndVals, []).
-async_set2(UserId, TargetName, VarsAndVals, SendOpts) ->
+async_set(UserId, TargetName, VarsAndVals, SendOpts) ->
call({async_set, self(), UserId, TargetName, VarsAndVals, SendOpts}).
-async_set(UserId, TargetName, CtxName, VarsAndVals) ->
- async_set(UserId, TargetName, CtxName, VarsAndVals,
- ?DEFAULT_ASYNC_SET_TIMEOUT, ?DEFAULT_EXTRA_INFO).
-
-async_set(UserId, TargetName, CtxName, VarsAndVals, Expire) ->
- async_set(UserId, TargetName, CtxName, VarsAndVals,
- Expire, ?DEFAULT_EXTRA_INFO).
-
-async_set(UserId, TargetName, CtxName, VarsAndVals, Expire, ExtraInfo)
- when (is_list(TargetName) andalso
- is_list(CtxName) andalso
- is_list(VarsAndVals) andalso
- is_integer(Expire) andalso (Expire >= 0)) ->
- SendOpts = [{context, CtxName}, {expire, Expire}, {extra, ExtraInfo}],
- async_set2(UserId, TargetName, VarsAndVals, SendOpts).
-
cancel_async_request(UserId, ReqId) ->
call({cancel_async_request, UserId, ReqId}).
@@ -1797,16 +1612,17 @@ handle_snmp_error(#pdu{request_id = ReqId} = Pdu, Reason, State) ->
handle_snmp_error(CrapError, Reason, _State) ->
error_msg("received crap (snmp) error =>"
- "~n~p~n~p", [CrapError, Reason]),
+ "~n ~p"
+ "~n ~p", [CrapError, Reason]),
ok.
handle_snmp_error(Domain, Addr, ReqId, Reason, State) ->
- ?vtrace("handle_snmp_error -> entry with~n"
- " Domain: ~p~n"
- " Addr: ~p~n"
- " ReqId: ~p~n"
- " Reason: ~p", [Domain, Addr, ReqId, Reason]),
+ ?vtrace("handle_snmp_error -> entry with"
+ "~n Domain: ~p"
+ "~n Addr: ~p"
+ "~n ReqId: ~p"
+ "~n Reason: ~p", [Domain, Addr, ReqId, Reason]),
case snmpm_config:get_agent_user_id(Domain, Addr) of
{ok, UserId} ->
@@ -1814,24 +1630,24 @@ handle_snmp_error(Domain, Addr, ReqId, Reason, State) ->
{ok, UserMod, UserData} ->
handle_error(UserId, UserMod, Reason, ReqId,
UserData, State);
- _Error ->
+ _Error1 ->
case snmpm_config:user_info() of
{ok, DefUserId, DefMod, DefData} ->
handle_error(DefUserId, DefMod, Reason,
ReqId, DefData, State);
- _Error ->
+ _Error2 ->
error_msg("failed retreiving the default user "
"info handling snmp error "
"<~p,~p>: ~n~w~n~w",
[Domain, Addr, ReqId, Reason])
end
end;
- _Error ->
+ _Error3 ->
case snmpm_config:user_info() of
{ok, DefUserId, DefMod, DefData} ->
handle_error(DefUserId, DefMod, Reason,
ReqId, DefData, State);
- _Error ->
+ _Error4 ->
error_msg("failed retreiving the default user "
"info handling snmp error "
"<~p,~p>: ~n~w~n~w",
@@ -1864,10 +1680,10 @@ do_handle_error(Mod, ReqId, Reason, Data) ->
handle_snmp_pdu(#pdu{type = 'get-response', request_id = ReqId} = Pdu,
Domain, Addr, State) ->
- ?vtrace("handle_snmp_pdu(get-response) -> entry with~n"
- " Domain: ~p~n"
- " Addr: ~p~n"
- " Pdu: ~p", [Domain, Addr, Pdu]),
+ ?vtrace("handle_snmp_pdu(get-response) -> entry with"
+ "~n Domain: ~p"
+ "~n Addr: ~p"
+ "~n Pdu: ~p", [Domain, Addr, Pdu]),
case ets:lookup(snmpm_request_table, ReqId) of
@@ -2028,6 +1844,7 @@ handle_snmp_pdu(#pdu{type = 'get-response', request_id = ReqId} = Pdu,
end
end;
+
handle_snmp_pdu(CrapPdu, Domain, Addr, _State) ->
error_msg("received crap (snmp) Pdu from ~w:~w =>"
"~p", [Domain, Addr, CrapPdu]),
@@ -2278,7 +2095,7 @@ handle_snmp_trap(CrapTrap, Domain, Addr, _State) ->
do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
case snmpm_config:get_agent_user_info(Domain, Addr) of
{ok, UserId, Target, RegType} ->
- ?vtrace("handle_snmp_trap -> found user: ~p", [UserId]),
+ ?vdebug("do_handle_snmp_trap -> found user: ~p", [UserId]),
case snmpm_config:user_info(UserId) of
{ok, Mod, Data} ->
handle_trap(
@@ -2290,7 +2107,7 @@ do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
%% User no longer exists, unregister agent
?vlog("[trap] failed retreiving user info for "
"user ~p: "
- "~n ~p", [UserId, Error1]),
+ "~n ~p", [UserId, Error1]),
case snmpm_config:unregister_agent(UserId, Target) of
ok ->
%% Try use the default user
@@ -2316,8 +2133,8 @@ do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
"failed unregister agent ~p <~p,~p> "
"belonging to non-existing "
"user ~p, handling trap: "
- "~n Error: ~w"
- "~n Trap info: ~w",
+ "~n Error: ~p"
+ "~n Trap info: ~p",
[Target, Domain, Addr, UserId,
Error3, SnmpTrapInfo])
end
@@ -2326,7 +2143,13 @@ do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
Error4 ->
%% Unknown agent, pass it on to the default user
?vlog("[trap] failed retreiving user id for agent <~p,~p>: "
- "~n ~p", [Domain, Addr, Error4]),
+ "~n Error: ~p"
+ "~n when"
+ "~n Users: ~p"
+ "~n Agents: ~p",
+ [Domain, Addr, Error4,
+ snmpm_config:which_users(),
+ snmpm_config:which_agents()]),
case snmpm_config:user_info() of
{ok, DefUserId, DefMod, DefData} ->
handle_agent(
@@ -2337,8 +2160,9 @@ do_handle_snmp_trap(SnmpTrapInfo, Domain, Addr, State) ->
Error5 ->
error_msg(
"failed retreiving "
- "the default user info handling trap from "
- "<~p,~p>: ~n~w~n~w",
+ "the default user info, handling trap from <~p,~p>:"
+ "~n Error: ~p"
+ "~n Trap Info: ~p",
[Domain, Addr, Error5, SnmpTrapInfo])
end
end,
@@ -3698,6 +3522,7 @@ nis_stop(_) ->
ok.
+-dialyzer({nowarn_function, nis_info/1}).
nis_info(NIS) when is_pid(NIS) ->
NIS ! {?MODULE, self(), info},
receive
diff --git a/lib/snmp/src/manager/snmpm_usm.erl b/lib/snmp/src/manager/snmpm_usm.erl
index 7dd79e703a..9e625d0315 100644
--- a/lib/snmp/src/manager/snmpm_usm.erl
+++ b/lib/snmp/src/manager/snmpm_usm.erl
@@ -268,15 +268,16 @@ decrypt(Data, UsmUser, UsmSecParams, SecLevel) ->
do_decrypt(Data, #usm_user{sec_name = SecName,
priv = PrivP,
priv_key = PrivKey},
- #usmSecurityParameters{msgPrivacyParameters = PrivParms}) ->
+ UsmSecParams) ->
EncryptedPDU = snmp_pdus:dec_scoped_pdu_data(Data),
- try_decrypt(PrivP, PrivKey, PrivParms, EncryptedPDU, SecName).
+ try_decrypt(PrivP, PrivKey, UsmSecParams, EncryptedPDU, SecName).
try_decrypt(usmNoPrivProtocol, _, _, _, SecName) -> % 3.2.5
error(usmStatsUnsupportedSecLevels,
?usmStatsUnsupportedSecLevels_instance, SecName);
try_decrypt(usmDESPrivProtocol,
- PrivKey, MsgPrivParams, EncryptedPDU, SecName) ->
+ PrivKey, UsmSecParams, EncryptedPDU, SecName) ->
+ #usmSecurityParameters{msgPrivacyParameters = MsgPrivParams} = UsmSecParams,
case (catch des_decrypt(PrivKey, MsgPrivParams, EncryptedPDU)) of
{ok, DecryptedData} ->
DecryptedData;
@@ -328,11 +329,6 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) ->
_ -> % 3.1.1a
SecData
end,
- %% 3.1.4
- ?vtrace("generate_outgoing_msg -> (3.1.4)",[]),
- ScopedPduBytes = Message#message.data,
- {ScopedPduData, MsgPrivParams} =
- encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel),
SnmpEngineID = get_engine_id(),
?vtrace("SnmpEngineID: ~p (3.1.6)",[SnmpEngineID]),
%% 3.1.6
@@ -345,6 +341,11 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) ->
_ ->
{get_engine_boots(), get_engine_time()}
end,
+ %% 3.1.4
+ ?vtrace("generate_outgoing_msg -> (3.1.4)",[]),
+ ScopedPduBytes = Message#message.data,
+ {ScopedPduData, MsgPrivParams} = encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel, MsgAuthEngineBoots,
+ MsgAuthEngineTime),
%% 3.1.5 - 3.1.7
?vtrace("generate_outgoing_msg -> (3.1.5 - 3.1.7)",[]),
UsmSecParams =
@@ -361,12 +362,12 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) ->
%% Ret: {ScopedPDU, MsgPrivParams} - both are already encoded as OCTET STRINGs
-encrypt(Data, PrivProtocol, PrivKey, SecLevel) ->
+encrypt(Data, PrivProtocol, PrivKey, SecLevel, EngineBoots, EngineTime) ->
case snmp_misc:is_priv(SecLevel) of
false -> % 3.1.4b
{Data, []};
true -> % 3.1.4a
- case (catch try_encrypt(PrivProtocol, PrivKey, Data)) of
+ case (catch try_encrypt(PrivProtocol, PrivKey, Data, EngineBoots, EngineTime)) of
{ok, ScopedPduData, MsgPrivParams} ->
{snmp_pdus:enc_oct_str_tag(ScopedPduData), MsgPrivParams};
{error, Reason} ->
@@ -376,12 +377,12 @@ encrypt(Data, PrivProtocol, PrivKey, SecLevel) ->
end
end.
-try_encrypt(usmNoPrivProtocol, _PrivKey, _Data) -> % 3.1.2
+try_encrypt(usmNoPrivProtocol, _PrivKey, _Data, _EngineBoots, _EngineTime) -> % 3.1.2
error(unsupportedSecurityLevel);
-try_encrypt(usmDESPrivProtocol, PrivKey, Data) ->
+try_encrypt(usmDESPrivProtocol, PrivKey, Data, _EngineBoots, _EngineTime) ->
des_encrypt(PrivKey, Data);
-try_encrypt(usmAesCfb128Protocol, PrivKey, Data) ->
- aes_encrypt(PrivKey, Data).
+try_encrypt(usmAesCfb128Protocol, PrivKey, Data, EngineBoots, EngineTime) ->
+ aes_encrypt(PrivKey, Data, EngineBoots, EngineTime).
authenticate_outgoing(Message, UsmSecParams,
AuthKey, AuthProtocol, SecLevel) ->
@@ -419,11 +420,8 @@ get_des_salt() ->
EngineBoots = get_engine_boots(),
[?i32(EngineBoots), ?i32(SaltInt)].
-aes_encrypt(PrivKey, Data) ->
- EngineBoots = get_engine_boots(),
- EngineTime = get_engine_time(),
- snmp_usm:aes_encrypt(PrivKey, Data, fun get_aes_salt/0,
- EngineBoots, EngineTime).
+aes_encrypt(PrivKey, Data, EngineBoots, EngineTime) ->
+ snmp_usm:aes_encrypt(PrivKey, Data, fun get_aes_salt/0, EngineBoots, EngineTime).
aes_decrypt(PrivKey, UsmSecParams, EncData) ->
#usmSecurityParameters{msgPrivacyParameters = MsgPrivParams,
diff --git a/lib/snmp/src/misc/snmp_conf.erl b/lib/snmp/src/misc/snmp_conf.erl
index 20b7af0373..39c18777c7 100644
--- a/lib/snmp/src/misc/snmp_conf.erl
+++ b/lib/snmp/src/misc/snmp_conf.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -51,6 +51,10 @@
check_ip/1, check_ip/2,
check_port/1,
%% ip_port_to_domaddr/2,
+ check_transport_address/2,
+ check_transport_kind/1,
+ check_transport_opts/1,
+ check_transport_port_ranges/1,
check_address/2, check_address/3,
check_taddress/2,
mk_taddress/1, mk_taddress/2,
@@ -198,8 +202,10 @@ read(File, Verify) ->
read(File, Order, Check) when is_function(Order), is_function(Check) ->
?vdebug("read -> entry with~n"
" File: ~p", [File]),
- Fd = open_file(File),
- read_fd(File, Order, Check, Fd, 1, []).
+ Fd = open_file(File),
+ Lines = read_fd(File, Order, Check, Fd, 1, []),
+ file:close(Fd),
+ Lines.
read_fd(File, Order, Check, Fd, StartLine, Res) ->
case do_read(Fd, "", StartLine) of
@@ -265,7 +271,8 @@ open_file(File) ->
end.
do_read(Io, Prompt, StartLine) ->
- case io:request(Io, {get_until,Prompt,erl_scan,tokens,[StartLine]}) of
+ Enc = latin1,
+ case io:request(Io, {get_until,Enc,Prompt,erl_scan,tokens,[StartLine]}) of
{ok, Toks, EndLine} ->
case erl_parse:parse_term(Toks) of
{ok, Term} ->
@@ -802,6 +809,91 @@ mk_addr_string_ntoa(Domain, IP, Port) ->
io_lib:format(
"~s:~w", [mk_addr_string_ntoa(Domain, IP), Port])).
+
+%% ---------
+
+%% This is internal - can *not* be specified by the user.
+%% check_transport_kind(all) ->
+%% ok;
+check_transport_kind(req_responder) ->
+ ok;
+check_transport_kind(trap_sender) ->
+ ok;
+check_transport_kind(BadKind) ->
+ error({bad_transport_kind, BadKind}).
+
+
+
+%% ---------
+
+%% The options we accept are the same as for net-if!
+%% {bind_to, bind_to()} |
+%% {sndbuf, sndbuf()} |
+%% {recbuf, recbuf()} |
+%% {no_reuse, no_reuse()} |
+%% {extra_sock_opts, list()}
+%% bind_to() :: boolean()
+%% sndbuf() :: pos_integer()
+%% rcvbuf() :: pos_integer()
+%% no_reuse() :: boolean()
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% {ephemeral, ephemeral()} |
+%% ephemeral() :: none |
+%% once |
+%% {data, pos_integer()} |
+%% {sends, pos_integer()} |
+%% {alive_time, pos_integer()}
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
+check_transport_opts(Opts) when is_list(Opts) ->
+ check_transport_opts(Opts, [], []);
+check_transport_opts(BadOpts) ->
+ error({bad_transport_opts, BadOpts}).
+
+check_transport_opts([], Extra, Acc) ->
+ lists:reverse(Acc) ++ Extra;
+check_transport_opts([{bind_to, BindTo} = Opt|Opts], Extra, Acc)
+ when is_boolean(BindTo) ->
+ check_transport_opts(Opts, Extra, [Opt|Acc]);
+check_transport_opts([{sndbuf, BufSz} = Opt|Opts], Extra, Acc)
+ when is_integer(BufSz) andalso (BufSz > 0) ->
+ check_transport_opts(Opts, Extra, [Opt | Acc]);
+check_transport_opts([{recbuf, BufSz} = Opt|Opts], Extra, Acc)
+ when is_integer(BufSz) andalso (BufSz > 0) ->
+ check_transport_opts(Opts, Extra, [Opt | Acc]);
+check_transport_opts([{no_reuse, NoReuse} = Opt|Opts], Extra, Acc)
+ when is_boolean(NoReuse) ->
+ check_transport_opts(Opts, Extra, [Opt|Acc]);
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% check_transport_opts([{ephemeral, Ephm} = Opt|Opts], Extra, Acc) ->
+%% check_transport_opt_ophm(Ephm),
+%% check_transport_opts(Opts, Extra, [Opt|Acc]);
+%% </EPHEMERAL-FOR-FUTUR-USE>
+check_transport_opts([{extra_sock_opts, Extra1} = Opt|Opts], Extra2, Acc)
+ when is_list(Extra1) andalso (Extra2 =:= []) ->
+ check_transport_opts(Opts, Extra1, [Opt|Acc]);
+check_transport_opts([H|_], _Extra, _Acc) ->
+ error({bad_transport_opts, H}).
+
+%% <EPHEMERAL-FOR-FUTUR-USE>
+%% check_transport_opt_ophm(none) ->
+%% ok;
+%% check_transport_opt_ophm(once) ->
+%% ok;
+%% check_transport_opt_ophm({data, DataSz})
+%% when is_integer(DataSz) andalso (DataSz > 0) ->
+%% ok;
+%% check_transport_opt_ophm({sends, Sends})
+%% when is_integer(Sends) andalso (Sends > 0) ->
+%% ok;
+%% check_transport_opt_ophm({alive_time, T})
+%% when is_integer(T) andalso (T > 0) ->
+%% ok;
+%% check_transport_opt_ophm(BadEphm) ->
+%% error({bad_transport_opts, {ephemeral, BadEphm}}).
+%% </EPHEMERAL-FOR-FUTUR-USE>
+
+
%% ---------
check_ip(X) ->
@@ -826,26 +918,69 @@ check_port(Port) when ?is_word(Port) ->
check_port(Port) ->
error({bad_port, Port}).
-%% ip_port_to_domaddr(IP, Port) when ?is_word(Port) ->
-%% %% XXX There is only code for IP domains here
-%% case check_address_ip(transportDomainUdpIpv4, IP) of
-%% false ->
-%% case check_address_ip(transportDomainUdpIpv6, IP) of
-%% false ->
-%% error({bad_address, {transportDomainUdpIpv4, {IP, Port}}});
-%% true ->
-%% {transportDomainUdpIpv6, {IP, Port}};
-%% FixedIP ->
-%% {transportDomainUdpIpv6, {FixedIP, Port}}
-%% end;
-%% true ->
-%% {transportDomainUdpIpv4, {IP, Port}};
-%% FixedIP ->
-%% {transportDomainUdpIpv4, {FixedIP, Port}}
-%% end;
-%% ip_port_to_domaddr(IP, Port) ->
-%% error({bad_address, {transportDomainUdpIpv4, {IP, Port}}}).
+check_transport_address(transportDomainUdpIpv4 = _Domain,
+ {{A0, A1, A2, A3}, PortInfo})
+ when ?is_ipv4_addr(A0, A1, A2, A3) ->
+ case PortInfo of
+ system ->
+ %% The actual port number will be choosen
+ %% by the system (create with port = 0)
+ %% when the socket is created.
+ true;
+ Port when ?is_word(Port) andalso (Port >= 0) ->
+ %% Note that the value 0, zero, has the normal meaning of
+ %% letting the system choose (that is the same effect as
+ %% using 'system').
+ true;
+ {Min, Max} when ?is_word(Min) andalso (Min > 0) andalso
+ ?is_word(Max) andalso (Max > Min) ->
+ true;
+ Ranges when is_list(Ranges) ->
+ check_transport_port_ranges(Ranges);
+ _ ->
+ false
+ end;
+check_transport_address(transportDomainUdpIpv6 = _Domain,
+ {{A0, A1, A2, A3, A4, A5, A6, A7}, PortInfo})
+ when ?is_ipv6_addr(A0, A1, A2, A3, A4, A5, A6, A7) ->
+ case PortInfo of
+ system ->
+ %% The actual port number will be choosen
+ %% by the system (create with port = 0)
+ %% when the socket is created.
+ true;
+ Port when ?is_word(Port) andalso (Port >= 0) ->
+ %% Note that the value 0, zero, has the normal meaning of
+ %% letting the system choose (that is the same effect as
+ %% using 'system').
+ true;
+ {Min, Max} when ?is_word(Min) andalso (Min > 0) andalso
+ ?is_word(Max) andalso (Max > Min) ->
+ true;
+ Ranges when is_list(Ranges) ->
+ check_transport_port_ranges(Ranges);
+ _ ->
+ false
+ end;
+check_transport_address(BadDomain, _) ->
+ error({bad_domain, BadDomain}).
+
+
+check_transport_port_ranges([]) ->
+ true;
+check_transport_port_ranges([PortNo|Ranges])
+ when ?is_word(PortNo) andalso (PortNo > 0) ->
+ check_transport_port_ranges(Ranges);
+check_transport_port_ranges([{Min, Max}|Ranges])
+ when ?is_word(Min) andalso (Min > 0) andalso
+ ?is_word(Max) andalso (Max > Min) ->
+ check_transport_port_ranges(Ranges);
+check_transport_port_ranges(_) ->
+ false.
+
+
+
%% Check a configuration term field from a file to see if it
%% can be fixed to be fed to mk_taddress/2.
diff --git a/lib/snmp/src/misc/snmp_config.erl b/lib/snmp/src/misc/snmp_config.erl
index 3104f2a096..43596f1e74 100644
--- a/lib/snmp/src/misc/snmp_config.erl
+++ b/lib/snmp/src/misc/snmp_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -47,7 +47,8 @@
write_agent_snmp_usm_conf/5,
write_agent_snmp_vacm_conf/3,
- write_manager_snmp_files/8,
+ write_manager_snmp_files/4, write_manager_snmp_files/5,
+ write_manager_snmp_files/7, write_manager_snmp_files/8,
write_manager_snmp_conf/4, write_manager_snmp_conf/5,
write_manager_snmp_users_conf/2,
write_manager_snmp_agents_conf/2,
@@ -500,6 +501,48 @@ config_agent_sys() ->
{Vsns, ConfigDir, SysConfig}.
+config_agent_transports(ID) ->
+ config_agent_transports(ID, []).
+
+config_agent_transports(ID, []) ->
+ i(ID ++ ". Configure atleast one transport: "),
+ T = config_agent_transport(ID),
+ config_agent_transports(ID, [T]);
+config_agent_transports(ID, Acc) ->
+ case ask(ID ++ ". Configure another transport (yes/no)?",
+ "yes", fun verify_yes_or_no/1) of
+ yes ->
+ T = config_agent_transport(ID),
+ config_agent_transports(ID, [T|Acc]);
+ no ->
+ lists:reverse(Acc)
+ end.
+
+config_agent_transport(ID) ->
+ TDomain = ask(ID ++ "a. Transport Domain "
+ "(UDP IPv4 (u4) or UDP IPv6 (u6))",
+ "u4", fun verify_transport_domain/1),
+ Host = host(TDomain),
+ Address = ask(ID ++ "b. Address of transport",
+ Host, fun(A) -> verify_transport_address(TDomain, A) end),
+ PortInfo = ask(ID ++ "c. Port number info (how we shall choose a port)"
+ "~n Note that we do not allow all variants here! "
+ "~n Edit manually for more variants (range/ranges)."
+ "~n default(d)/system(s)/pos-integer",
+ "4000", fun verify_port_number_info/1),
+ TAddress = {Address, PortInfo},
+ Kind = ask(ID ++ "d. Kind of transport "
+ "(all(a)/request-responder(rr)/trap-sender(ts))",
+ "a", fun verify_transport_kind/1),
+ i("*** We do not ask about the transport options here ***~n"
+ "*** the user must manually edit the config files! ***"),
+ case Kind of
+ all ->
+ {TDomain, TAddress, []};
+ _ when (Kind =:= req_responder) orelse (Kind =:= trap_sender) ->
+ {TDomain, TAddress, Kind, []}
+ end.
+
config_agent_snmp(Dir, Vsns) ->
i("~nAgent snmp config: "
"~n------------------"),
@@ -511,21 +554,25 @@ config_agent_snmp(Dir, Vsns) ->
EngineName, fun verify_engine_id/1),
MMS = ask("3. Max message size?", "484",
fun verify_max_message_size/1),
- AgentUDP = ask("4. The UDP port the agent listens to. "
- "(standard 161)",
- "4000", fun verify_port_number/1),
- Host = host(),
- AgentIP = ask("5. IP address for the agent (only used as id ~n"
- " when sending traps)", Host, fun verify_address/1),
- %% We intentionally skip TDomain...
- %% If the user wish to use IPv6, the user must create an dummy entry here
- %% and then manually edit these entries later.
+ ATransports = config_agent_transports("4"),
+ %% AgentUDP = ask("4. The UDP port the agent listens to. "
+ %% "(standard 161)",
+ %% "4000", fun verify_port_number/1),
+ %% AgentIP = ask("5. IP address for the agent (only used as id ~n"
+ %% " when sending traps)", Host, fun verify_address/1),
+
+ ManagerTDomain = ask("5. Manager Transport Domain"
+ "(UDP IPv4 (u4) or UDP IPv6 (u6))",
+ "u4", fun verify_transport_domain/1),
+ Host = host(ManagerTDomain),
ManagerIP = ask("6. IP address for the manager (only this manager ~n"
" will have access to the agent, traps are sent ~n"
- " to this one)", Host, fun verify_address/1),
- TrapUdp = ask("7. To what UDP port at the manager should traps ~n"
+ " to this one)", Host,
+ fun(A) -> verify_transport_address(ManagerTDomain, A) end),
+ ManagerUdp = ask("7. To what UDP port at the manager should traps ~n"
" be sent (standard 162)?", "5000",
fun verify_port_number/1),
+
SecType = ask("8. Do you want a none- minimum- or semi-secure"
" configuration? ~n"
" Note that if you chose v1 or v2, you won't get any"
@@ -571,8 +618,13 @@ config_agent_snmp(Dir, Vsns) ->
end,
NT
end,
+
+
+
case (catch write_agent_snmp_files(
- Dir, Vsns, ManagerIP, TrapUdp, AgentIP, AgentUDP, SysName,
+ Dir, Vsns,
+ ManagerTDomain, ManagerIP, ManagerUdp,
+ ATransports, SysName,
NotifType, SecType, Passwd, EngineID, MMS)) of
ok ->
i("~n- - - - - - - - - - - - -"),
@@ -679,7 +731,7 @@ config_manager_sys() ->
"(true/false)?",
"false", fun verify_bool/1),
NetIfNoReuse = ask("17. Shall the manager IP address and port "
- "be not reusable (true/false)?",
+ "be *not* reusable (true/false)?",
"false", fun verify_bool/1),
NetIfRecbuf =
case ask("18. Receive buffer size of the manager (in bytes) "
@@ -1113,8 +1165,14 @@ verify_address(A, transportDomainUdpIpv6 = _Domain) ->
do_verify_address(A, inet6).
do_verify_address(A, Family) ->
+ do_verify_address(A, Family, list).
+
+do_verify_address(A, Family, Form)
+ when (Form =:= list) orelse (Form =:= tuple) ->
case (catch snmp_misc:ip(A, Family)) of
- {ok, IP} ->
+ {ok, IP} when (Form =:= tuple) ->
+ {ok, IP};
+ {ok, IP} when (Form =:= list) ->
{ok, tuple_to_list(IP)};
{error, _} ->
{error, "invalid address: " ++ A};
@@ -1348,6 +1406,55 @@ verify_irb_user(TO) ->
end.
+verify_transport_domain("u4") ->
+ {ok, transportDomainUdpIpv4};
+verify_transport_domain("udp4") ->
+ {ok, transportDomainUdpIpv4};
+verify_transport_domain("transportDomainUdpIpv4") ->
+ {ok, transportDomainUdpIpv4};
+verify_transport_domain("u6") ->
+ {ok, transportDomainUdpIpv6};
+verify_transport_domain("udp6") ->
+ {ok, transportDomainUdpIpv6};
+verify_transport_domain("transportDomainUdpIpv6") ->
+ {ok, transportDomainUdpIpv6};
+verify_transport_domain(TS) ->
+ {error, "invalid transport domain: " ++ TS}.
+
+
+verify_transport_address(transportDomainUdpIpv4 = _Domain, A) ->
+ do_verify_address(A, inet, tuple);
+verify_transport_address(transportDomainUdpIpv6 = _Domain, A) ->
+ do_verify_address(A, inet6, tuple).
+
+
+verify_transport_kind("a") ->
+ {ok, all};
+verify_transport_kind("rr") ->
+ {ok, req_responder};
+verify_transport_kind("ts") ->
+ {ok, trap_sender};
+verify_transport_kind(K) ->
+ {error, "invalid transport kind: " ++ K}.
+
+
+verify_port_number_info("d") ->
+ {ok, 0};
+verify_port_number_info("default") ->
+ {ok, 0};
+verify_port_number_info("s") ->
+ {ok, system};
+verify_port_number_info("system") ->
+ {ok, system};
+verify_port_number_info(P) ->
+ case (catch list_to_integer(P)) of
+ N when is_integer(N) andalso (N > 0) ->
+ {ok, N};
+ _ ->
+ {error, "invalid port number: " ++ P}
+ end.
+
+
verify_term_disco_behaviour("discovery") ->
{ok, discovery};
verify_term_disco_behaviour("plain") ->
@@ -1540,17 +1647,29 @@ ask(Q, Default, Verify) when is_list(Q) andalso is_function(Verify) ->
host() ->
+ do_host(inet).
+
+host(transportDomainUdpIpv4) ->
+ do_host(inet);
+host(transportDomainUdpIpv6) ->
+ do_host(inet6).
+
+do_host(Fam) ->
case (catch inet:gethostname()) of
{ok, Name} ->
- case (catch inet:getaddr(Name, inet)) of
+ case (catch inet:getaddr(Name, Fam)) of
{ok, Addr} when is_tuple(Addr) ->
lists:flatten(
io_lib:format("~w.~w.~w.~w", tuple_to_list(Addr)));
- _ ->
- "127.0.0.1"
+ _ when (Fam =:= inet) ->
+ "127.0.0.1";
+ _ when (Fam =:= inet6) ->
+ "::1"
end;
- _ ->
- "127.0.0.1"
+ _ when (Fam =:= inet) ->
+ "127.0.0.1";
+ _ when (Fam =:= inet6) ->
+ "::1"
end.
guess_agent_name() ->
@@ -1616,10 +1735,88 @@ write_agent_snmp_files(
%% ----- Agent config files generator functions -----
%%
+%% This function has no documentation, so for "possible" users
+%% (other then our test suite), we have this spec...
+
+-type agent_pre_transport() :: #{addr := tuple(),
+ kind := req_responder | trap_sender,
+ opts := list()} |
+ #{addr := tuple(),
+ kind := req_responder | trap_sender} |
+ #{addr := tuple()}.
+
+-spec write_agent_snmp_files(Dir, Vsns,
+ TransportDomain, ManagerAddr, AgentPreTransports,
+ SysName,
+ NotifyType, SecType, Passwd, EngineID, MMS) ->
+ ok when
+ Dir :: file:filename(),
+ Vsns :: [snmp:version()],
+ TransportDomain :: snmp:tdomain(),
+ ManagerAddr :: {inet:ip_address(), inet:port_number()},
+ AgentPreTransports :: [agent_pre_transport()],
+ SysName :: string(),
+ NotifyType :: trap | inform,
+ SecType :: none | minimum | {semi, des | aes},
+ Passwd :: list(),
+ EngineID :: snmp:engine_id(),
+ MMS :: snmp:mms();
+
+ (Dir, Vsns,
+ TransportDomain, ManagerAddr, AgentAddr,
+ SysName,
+ NotifyType, SecType, Passwd, EngineID, MMS) ->
+ ok when
+ Dir :: file:filename(),
+ Vsns :: [snmp:version()],
+ TransportDomain :: snmp:tdomain(),
+ ManagerAddr :: {inet:ip_address(), inet:port_number()},
+ AgentAddr :: {inet:ip_address(), inet:port_number()},
+ SysName :: string(),
+ NotifyType :: trap | inform,
+ SecType :: none | minimum | {semi, des | aes},
+ Passwd :: list(),
+ EngineID :: snmp:engine_id(),
+ MMS :: snmp:mms().
+
+write_agent_snmp_files(
+ Dir, Vsns, TransportDomain, ManagerAddr, AgentPreTransports, SysName,
+ NotifType, SecType, Passwd, EngineID, MMS) when is_list(AgentPreTransports) ->
+ F = fun(#{addr := Addr, kind := Kind, opts := Opts})
+ when is_tuple(Addr) andalso
+ is_atom(Kind) andalso
+ is_list(Opts) ->
+ {TransportDomain, Addr, Kind, Opts};
+ (#{addr := Addr, kind := Kind})
+ when is_tuple(Addr) andalso
+ is_atom(Kind) ->
+ {TransportDomain, Addr, Kind, []};
+ (#{addr := Addr})
+ when is_tuple(Addr) ->
+ {TransportDomain, Addr}
+ end,
+ AgentTransports = lists:map(F, AgentPreTransports),
+ write_agent_snmp_conf(Dir, AgentTransports, EngineID, MMS),
+ write_agent_snmp_context_conf(Dir),
+ write_agent_snmp_community_conf(Dir),
+ write_agent_snmp_standard_conf(Dir, SysName),
+ write_agent_snmp_target_addr_conf(Dir, TransportDomain, ManagerAddr, Vsns),
+ write_agent_snmp_target_params_conf(Dir, Vsns),
+ write_agent_snmp_notify_conf(Dir, NotifType),
+ write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
+ write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
+ ok;
write_agent_snmp_files(
Dir, Vsns, Domain, ManagerAddr, AgentAddr, SysName,
- NotifType, SecType, Passwd, EngineID, MMS) ->
- write_agent_snmp_conf(Dir, Domain, AgentAddr, EngineID, MMS),
+ NotifType, SecType, Passwd, EngineID, MMS)
+ when is_list(Dir) andalso
+ is_list(Vsns) andalso
+ is_atom(Domain) andalso
+ is_tuple(ManagerAddr) andalso
+ is_tuple(ManagerAddr) andalso
+ is_list(SysName) andalso
+ is_atom(NotifType) ->
+ write_agent_snmp_conf(Dir, [{Domain, AgentAddr}], EngineID, MMS),
write_agent_snmp_context_conf(Dir),
write_agent_snmp_community_conf(Dir),
write_agent_snmp_standard_conf(Dir, SysName),
@@ -1632,7 +1829,15 @@ write_agent_snmp_files(
write_agent_snmp_files(
Dir, Vsns, ManagerIP, TrapUDP, AgentIP, AgentUDP, SysName,
- NotifType, SecType, Passwd, EngineID, MMS) ->
+ NotifType, SecType, Passwd, EngineID, MMS)
+ when is_list(Dir) andalso
+ is_list(Vsns) andalso
+ is_list(ManagerIP) andalso
+ is_integer(TrapUDP) andalso
+ is_list(AgentIP) andalso
+ is_integer(AgentUDP) andalso
+ is_list(SysName) andalso
+ is_atom(NotifType) ->
Domain = snmp_target_mib:default_domain(),
ManagerAddr = {ManagerIP, TrapUDP},
write_agent_snmp_conf(Dir, AgentIP, AgentUDP, EngineID, MMS),
@@ -1644,6 +1849,30 @@ write_agent_snmp_files(
write_agent_snmp_notify_conf(Dir, NotifType),
write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
+ ok;
+write_agent_snmp_files(
+ Dir, Vsns,
+ ManagerTDomain, ManagerIP, ManagerTrapUDP,
+ AgentTransports, SysName,
+ NotifType, SecType, Passwd, EngineID, MMS)
+ when is_list(Dir) andalso
+ is_list(Vsns) andalso
+ is_atom(ManagerTDomain) andalso
+ is_tuple(ManagerIP) andalso
+ is_integer(ManagerTrapUDP) andalso
+ is_list(AgentTransports) andalso
+ is_list(SysName) andalso
+ is_atom(NotifType) ->
+ ManagerAddr = {ManagerIP, ManagerTrapUDP},
+ write_agent_snmp_conf(Dir, AgentTransports, EngineID, MMS),
+ write_agent_snmp_context_conf(Dir),
+ write_agent_snmp_community_conf(Dir),
+ write_agent_snmp_standard_conf(Dir, SysName),
+ write_agent_snmp_target_addr_conf(Dir, ManagerTDomain, ManagerAddr, Vsns),
+ write_agent_snmp_target_params_conf(Dir, Vsns),
+ write_agent_snmp_notify_conf(Dir, NotifType),
+ write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
+ write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
ok.
@@ -2105,6 +2334,22 @@ update_agent_vacm_config(Dir, Conf) ->
%% ----- Manager config files generator functions -----
%%
+write_manager_snmp_files(Dir, Transports, MMS, EngineID) ->
+ write_manager_snmp_files(Dir, Transports, MMS, EngineID,
+ [], [], []).
+
+write_manager_snmp_files(Dir, IP, Port, MMS, EngineID) ->
+ write_manager_snmp_files(Dir, IP, Port, MMS, EngineID,
+ [], [], []).
+
+write_manager_snmp_files(Dir, Transports, MMS, EngineID,
+ Users, Agents, Usms) ->
+ write_manager_snmp_conf(Dir, Transports, MMS, EngineID),
+ write_manager_snmp_users_conf(Dir, Users),
+ write_manager_snmp_agents_conf(Dir, Agents),
+ write_manager_snmp_usm_conf(Dir, Usms),
+ ok.
+
write_manager_snmp_files(Dir, IP, Port, MMS, EngineID,
Users, Agents, Usms) ->
write_manager_snmp_conf(Dir, IP, Port, MMS, EngineID),
@@ -2130,7 +2375,7 @@ write_manager_snmp_conf(Dir, Transports, MMS, EngineID) ->
"%%\n\n",
Hdr = header() ++ Comment,
Conf =
- [{transports, Transports},
+ [{transports, Transports},
{engine_id, EngineID},
{max_message_size, MMS}],
write_manager_config(Dir, Hdr, Conf).
@@ -2737,7 +2982,8 @@ read_lines(Fd, Acc, StartLine) ->
end.
read_and_parse_term(Fd, StartLine) ->
- case io:request(Fd, {get_until, "", erl_scan, tokens, [StartLine]}) of
+ Enc = latin1,
+ case io:request(Fd, {get_until, Enc, "", erl_scan, tokens, [StartLine]}) of
{ok, Tokens, EndLine} ->
case erl_parse:parse_term(Tokens) of
{ok, Term} ->
diff --git a/lib/snmp/src/misc/snmp_usm.erl b/lib/snmp/src/misc/snmp_usm.erl
index bae6bdec72..e46993aad4 100644
--- a/lib/snmp/src/misc/snmp_usm.erl
+++ b/lib/snmp/src/misc/snmp_usm.erl
@@ -45,7 +45,12 @@
-define(i32(Int), (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255).
--define(BLOCK_CIPHER_AES, aes_cfb128).
+-define(BLOCK_CIPHER_AES(Key), case bit_size(iolist_to_binary(Key)) of
+ 128 -> aes_128_cfb128;
+ 192 -> aes_192_cfb128;
+ 256 -> aes_256_cfb128
+ end).
+
-define(BLOCK_CIPHER_DES, des_cbc).
@@ -157,7 +162,7 @@ md5_auth_out(AuthKey, Message, UsmSecParams) ->
Packet = snmp_pdus:enc_message_only(Message2),
%% 6.3.1.2-4 is done by the crypto function
%% 6.3.1.4
- MAC = binary_to_list(crypto:hmac(md5, AuthKey, Packet, 12)),
+ MAC = binary_to_list(crypto:macN(hmac, md5, AuthKey, Packet, 12)),
%% ?vtrace("md5_auth_out -> crypto (md5) encoded"
%% "~n MAC: ~w", [MAC]),
%% 6.3.1.5
@@ -171,7 +176,7 @@ md5_auth_in(AuthKey, AuthParams, Packet) when length(AuthParams) == 12 ->
%% 6.3.2.3
Packet2 = patch_packet(binary_to_list(Packet)),
%% 6.3.2.5
- MAC = binary_to_list(crypto:hmac(md5, AuthKey, Packet2, 12)),
+ MAC = binary_to_list(crypto:macN(hmac, md5, AuthKey, Packet2, 12)),
%% 6.3.2.6
%% ?vtrace("md5_auth_in -> crypto (md5) encoded"
%% "~n MAC: ~w", [MAC]),
@@ -190,7 +195,7 @@ sha_auth_out(AuthKey, Message, UsmSecParams) ->
Packet = snmp_pdus:enc_message_only(Message2),
%% 7.3.1.2-4 is done by the crypto function
%% 7.3.1.4
- MAC = binary_to_list(crypto:hmac(sha, AuthKey, Packet, 12)),
+ MAC = binary_to_list(crypto:macN(hmac, sha, AuthKey, Packet, 12)),
%% 7.3.1.5
set_msg_auth_params(Message, UsmSecParams, MAC).
@@ -198,7 +203,7 @@ sha_auth_in(AuthKey, AuthParams, Packet) when length(AuthParams) =:= 12 ->
%% 7.3.2.3
Packet2 = patch_packet(binary_to_list(Packet)),
%% 7.3.2.5
- MAC = binary_to_list(crypto:hmac(sha, AuthKey, Packet2, 12)),
+ MAC = binary_to_list(crypto:macN(hmac, sha, AuthKey, Packet2, 12)),
%% 7.3.2.6
MAC == AuthParams;
sha_auth_in(_AuthKey, _AuthParams, _Packet) ->
@@ -216,8 +221,8 @@ des_encrypt(PrivKey, Data, SaltFun) ->
IV = list_to_binary(snmp_misc:str_xor(PreIV, Salt)),
TailLen = (8 - (length(Data) rem 8)) rem 8,
Tail = mk_tail(TailLen),
- EncData = crypto:block_encrypt(?BLOCK_CIPHER_DES,
- DesKey, IV, [Data,Tail]),
+ EncData = crypto:crypto_one_time(?BLOCK_CIPHER_DES,
+ DesKey, IV, [Data,Tail], true),
{ok, binary_to_list(EncData), Salt}.
des_decrypt(PrivKey, MsgPrivParams, EncData)
@@ -231,8 +236,8 @@ des_decrypt(PrivKey, MsgPrivParams, EncData)
Salt = MsgPrivParams,
IV = list_to_binary(snmp_misc:str_xor(PreIV, Salt)),
%% Whatabout errors here??? E.g. not a mulitple of 8!
- Data = binary_to_list(crypto:block_decrypt(?BLOCK_CIPHER_DES,
- DesKey, IV, EncData)),
+ Data = binary_to_list(crypto:crypto_one_time(?BLOCK_CIPHER_DES,
+ DesKey, IV, EncData, false)),
Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data),
{ok, Data2};
des_decrypt(PrivKey, BadMsgPrivParams, EncData) ->
@@ -248,8 +253,8 @@ aes_encrypt(PrivKey, Data, SaltFun, EngineBoots, EngineTime) ->
AesKey = PrivKey,
Salt = SaltFun(),
IV = list_to_binary([?i32(EngineBoots), ?i32(EngineTime) | Salt]),
- EncData = crypto:block_encrypt(?BLOCK_CIPHER_AES,
- AesKey, IV, Data),
+ EncData = crypto:crypto_one_time(?BLOCK_CIPHER_AES(AesKey),
+ AesKey, IV, Data, true),
{ok, binary_to_list(EncData), Salt}.
aes_decrypt(PrivKey, MsgPrivParams, EncData, EngineBoots, EngineTime)
@@ -258,8 +263,8 @@ aes_decrypt(PrivKey, MsgPrivParams, EncData, EngineBoots, EngineTime)
Salt = MsgPrivParams,
IV = list_to_binary([?i32(EngineBoots), ?i32(EngineTime) | Salt]),
%% Whatabout errors here??? E.g. not a mulitple of 8!
- Data = binary_to_list(crypto:block_decrypt(?BLOCK_CIPHER_AES,
- AesKey, IV, EncData)),
+ Data = binary_to_list(crypto:crypto_one_time(?BLOCK_CIPHER_AES(AesKey),
+ AesKey, IV, EncData, false)),
Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data),
{ok, Data2}.
diff --git a/lib/snmp/src/misc/snmp_verbosity.erl b/lib/snmp/src/misc/snmp_verbosity.erl
index 9bc3e2762f..0572ebea03 100644
--- a/lib/snmp/src/misc/snmp_verbosity.erl
+++ b/lib/snmp/src/misc/snmp_verbosity.erl
@@ -100,7 +100,9 @@ image_of_verbosity(_) -> "".
%% ShortName
image_of_sname(ma) -> "MASTER-AGENT";
-image_of_sname(maw) -> io_lib:format("MASTER-AGENT-worker(~p)",[self()]);
+image_of_sname(mamw) -> io_lib:format("MASTER-AGENT-main_worker(~p)",[self()]);
+image_of_sname(masw) -> io_lib:format("MASTER-AGENT-set_worker(~p)",[self()]);
+image_of_sname(manw) -> io_lib:format("MASTER-AGENT-notif_worker(~p)",[self()]);
image_of_sname(madis) -> io_lib:format("MASTER-AGENT-discovery_inform_sender(~p)",
[self()]);
image_of_sname(mais) -> io_lib:format("MASTER-AGENT-inform_sender(~p)",
diff --git a/lib/snmp/test/modules.mk b/lib/snmp/test/modules.mk
index ba0aea21dd..66cfa07f3b 100644
--- a/lib/snmp/test/modules.mk
+++ b/lib/snmp/test/modules.mk
@@ -38,6 +38,7 @@ TEST_UTIL_MODULES = \
snmp_test_lib \
snmp_agent_test_lib \
snmp_agent_test_get \
+ snmp_otp16649_user \
snmp_manager_user \
snmp_manager_user_old \
snmp_manager_user_test_lib \
diff --git a/lib/snmp/test/snmp_agent_SUITE.erl b/lib/snmp/test/snmp_agent_SUITE.erl
index 2a6cfbedbe..83bc05ff05 100644
--- a/lib/snmp/test/snmp_agent_SUITE.erl
+++ b/lib/snmp/test/snmp_agent_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -204,7 +204,7 @@
v3_sha_auth/1,
v3_des_priv/1,
- %% all_tcs - test_multi_threaded
+ %% all_tcs - test_multi_threaded, test_multi_threaded_ext
multi_threaded/1,
mt_trap/1,
@@ -303,7 +303,14 @@
%% tickets2
otp8395/1,
- otp9884/1
+ otp9884/1,
+ otp16649_1/1,
+ otp16649_2/1,
+ otp16649_3/1,
+ otp16649_4/1,
+ otp16649_5/1,
+ otp16649_6/1,
+ otp16649_7/1
]).
%% Internal exports
@@ -319,7 +326,7 @@
db_notify_client_test/0,
notify/2,
multi_threaded_test/0,
- mt_trap_test/1,
+ mt_trap_test/2,
types_v2_test/0,
implied_test/1,
sparse_table_test/0,
@@ -420,6 +427,9 @@
mnesia_start/0,
mnesia_stop/0,
start_standalone_agent/1,
+ stop_standalone_agent/1,
+ start_standalone_manager/1,
+ stop_standalone_manager/1,
do_info/1
]).
@@ -441,6 +451,9 @@
-define(sa, [1,3,6,1,4,1,193,2]).
-define(system, [1,3,6,1,2,1,1]).
-define(snmp, [1,3,6,1,2,1,11]).
+-define(sysDescr_instance, [1,3,6,1,2,1,1,1,0]).
+-define(sysObjectID_instance, [1,3,6,1,2,1,1,2,0]).
+-define(sysUpTime_instance, [1,3,6,1,2,1,1,3,0]).
-define(snmpTraps, [1,3,6,1,6,3,1,1,5]).
-define(ericsson, [1,3,6,1,4,1,193]).
-define(testTrap, [1,3,6,1,2,1,15,0]).
@@ -456,6 +469,11 @@
-define(TRAP_UDP, 5000).
+-define(MGR_PORT, 5000).
+-define(MGR_MMS, 1024).
+-define(MGR_ENGINE_ID, "mgrEngine").
+
+
-define(tooBigStr, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").
-define(str(X), snmp_pdus:bits_to_str(X)).
@@ -535,6 +553,7 @@ groups() ->
{test_v3, [], v3_cases()},
{test_v3_ipv6, [], v3_cases_ipv6()},
{test_multi_threaded, [], mt_cases()},
+ {test_multi_threaded_ext, [], mt_cases()},
{multiple_reqs, [], mul_cases()},
{multiple_reqs_2, [], mul_cases_2()},
{multiple_reqs_3, [], mul_cases_3()},
@@ -552,7 +571,10 @@ groups() ->
{tickets2, [], tickets2_cases()},
{otp4394, [], [otp_4394]},
{otp7157, [], [otp_7157]},
- {otp16092, [], otp16092_cases()}
+ {otp16092, [], otp16092_cases()},
+ {otp16649, [], otp16649_cases()},
+ {otp16649_ipv4, [], otp16649_gen_cases()},
+ {otp16649_ipv6, [], otp16649_gen_cases()}
].
@@ -568,6 +590,7 @@ all_cases() ->
{group, test_v1_v2_ipv6},
{group, test_v3_ipv6},
{group, test_multi_threaded},
+ {group, test_multi_threaded_ext},
{group, mib_storage},
{group, tickets1}
].
@@ -654,7 +677,9 @@ init_per_group(multiple_reqs_2 = GroupName, Config) ->
init_per_group(multiple_reqs_3 = GroupName, Config) ->
init_mul(snmp_test_lib:init_group_top_dir(GroupName, Config));
init_per_group(test_multi_threaded = GroupName, Config) ->
- init_mt(snmp_test_lib:init_group_top_dir(GroupName, Config));
+ init_mt(snmp_test_lib:init_group_top_dir(GroupName, Config), true);
+init_per_group(test_multi_threaded_ext = GroupName, Config) ->
+ init_mt(snmp_test_lib:init_group_top_dir(GroupName, Config), extended);
init_per_group(test_v3 = GroupName, Config) ->
case snmp_test_lib:crypto_start() of
ok ->
@@ -714,6 +739,45 @@ init_per_group(mib_storage_dets = GroupName, Config) ->
init_mib_storage_dets(snmp_test_lib:init_group_top_dir(GroupName, Config));
init_per_group(mib_storage_ets = GroupName, Config) ->
init_mib_storage_ets(snmp_test_lib:init_group_top_dir(GroupName, Config));
+init_per_group(otp16649_ipv4 = GroupName, Config) ->
+ Config2 = [{ip, ?LOCALHOST(inet)},
+ {ipfamily, inet},
+ {tdomain, transportDomainUdpIpv4} |
+ lists:keydelete(ip, 1, Config)],
+ snmp_test_lib:init_group_top_dir(GroupName, Config2);
+init_per_group(otp16649_ipv6 = GroupName, Config) ->
+ init_per_group_ipv6(GroupName,
+ [{tdomain, transportDomainUdpIpv6} | Config],
+ fun(C) -> C end);
+ %% SupportsIPv6 =
+ %% case ?HAS_SUPPORT_IPV6() of
+ %% true ->
+ %% case os:type() of
+ %% {unix, netbsd} ->
+ %% {false, "Host *may* not *properly* support IPV6"};
+ %% {unix, darwin} ->
+ %% case os:version() of
+ %% V > {9, 8, 0} ->
+ %% true;
+ %% _ ->
+ %% {false, "Host *may* not *properly* support IPV6"};
+ %% end;
+ %% _ ->
+ %% true
+ %% end;
+ %% false ->
+ %% {false, "Host does not support IPv6"}
+ %% end,
+ %% case SupportsIPv6 of
+ %% true ->
+ %% Config2 = [{ip, ?LOCALHOST(inet6)},
+ %% {ipfamily, inet6},
+ %% {tdomain, transportDomainUdpIpv6} |
+ %% lists:keydelete(ip, 1, Config)],
+ %% snmp_test_lib:init_group_top_dir(GroupName, Config2);
+ %% {false, SkipReason} ->
+ %% {skip, SkipReason}
+ %% end;
init_per_group(GroupName, Config) ->
snmp_test_lib:init_group_top_dir(GroupName, Config).
@@ -724,6 +788,9 @@ init_per_group_ipv6(GroupName, Config, Init) ->
%% are actually causing the failures...
OSSkipable = [{unix,
[
+ %% We only have the one NetBSD machine,
+ %% and UDP on IPv6 is very glitchy, so ...
+ {netbsd, fun(_) -> true end},
{darwin, fun(V) when (V > {9, 8, 0}) ->
%% This version is OK: No Skip
false;
@@ -764,49 +831,51 @@ end_per_group(v2_inform, Config) ->
end_per_group(v3_inform, Config) ->
finish_v3_inform(Config);
end_per_group(multiple_reqs, Config) ->
- finish_mul(Config);
+ finish_mul(Config);
end_per_group(multiple_reqs_2, Config) ->
finish_mul(Config);
end_per_group(multiple_reqs_3, Config) ->
finish_mul(Config);
end_per_group(test_multi_threaded, Config) ->
- finish_mt(Config);
+ finish_mt(Config);
+end_per_group(test_multi_threaded_ext, Config) ->
+ finish_mt(Config);
end_per_group(test_v3_ipv6, Config) ->
- finish_v3(Config);
+ finish_v3(Config);
end_per_group(test_v1_v2_ipv6, Config) ->
- finish_v1_v2(Config);
+ finish_v1_v2(Config);
end_per_group(test_v2_ipv6, Config) ->
- finish_v2(Config);
+ finish_v2(Config);
end_per_group(test_v1_ipv6, Config) ->
- finish_v1(Config);
+ finish_v1(Config);
end_per_group(test_v3, Config) ->
- finish_v3(Config);
+ finish_v3(Config);
end_per_group(test_v1_v2, Config) ->
- finish_v1_v2(Config);
+ finish_v1_v2(Config);
end_per_group(test_v2, Config) ->
- finish_v2(Config);
+ finish_v2(Config);
end_per_group(test_v1, Config) ->
- finish_v1(Config);
+ finish_v1(Config);
end_per_group(misc, Config) ->
- finish_misc(Config);
+ finish_misc(Config);
end_per_group(mib_storage_varm_mnesia, Config) ->
- finish_varm_mib_storage_mnesia(Config);
+ finish_varm_mib_storage_mnesia(Config);
end_per_group(mib_storage_varm_dets, Config) ->
- finish_varm_mib_storage_dets(Config);
+ finish_varm_mib_storage_dets(Config);
end_per_group(mib_storage_size_check_mnesia, Config) ->
- finish_size_check_msm(Config);
+ finish_size_check_msm(Config);
end_per_group(mib_storage_size_check_dets, Config) ->
- finish_size_check_msd(Config);
+ finish_size_check_msd(Config);
end_per_group(mib_storage_size_check_ets, Config) ->
- finish_size_check_mse(Config);
+ finish_size_check_mse(Config);
end_per_group(mib_storage_mnesia, Config) ->
- finish_mib_storage_mnesia(Config);
+ finish_mib_storage_mnesia(Config);
end_per_group(mib_storage_dets, Config) ->
- finish_mib_storage_dets(Config);
+ finish_mib_storage_dets(Config);
end_per_group(mib_storage_ets, Config) ->
- finish_mib_storage_ets(Config);
+ finish_mib_storage_ets(Config);
end_per_group(_GroupName, Config) ->
- Config.
+ Config.
@@ -855,6 +924,41 @@ init_per_testcase1(otp9884 = Case, Config) when is_list(Config) ->
"~n Case: ~p"
"~n Config: ~p", [Case, Config]),
otp9884({init, init_per_testcase2(Case, Config)});
+init_per_testcase1(otp16649_1 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_1_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_2 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_2_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_3 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_3_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_4 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_4_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_5 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_5_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_6 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_6_init(init_per_testcase2(Case, Config));
+init_per_testcase1(otp16649_7 = Case, Config) when is_list(Config) ->
+ ?DBG("init_per_testcase1 -> entry with"
+ "~n Case: ~p"
+ "~n Config: ~p", [Case, Config]),
+ otp16649_7_init(init_per_testcase2(Case, Config));
init_per_testcase1(otp_7157 = _Case, Config) when is_list(Config) ->
?DBG("init_per_testcase1 -> entry with"
"~n Case: ~p"
@@ -941,25 +1045,26 @@ end_per_testcase(Case, Config) when is_list(Config) ->
%% already failed, we will want to get as much of the logs
%% as possible. So, set no timeout (infinity) and let the
%% test framework take care of things...
- DisplayLogTimeout =
- case ?config(tc_status, Config) of
- ok ->
- ?SECS(30);
- _ ->
- infinity
+ %% But also, *don't* bother with this unless the test case
+ %% has failed!
+ case ?config(tc_status, Config) of
+ ok ->
+ ok;
+ _ ->
+ To = ?SECS(30),
+ Flag = process_flag(trap_exit, true),
+ Pid = spawn_link(fun() -> display_log(Config), exit(normal) end),
+ receive
+ {'EXIT', Pid, _} ->
+ process_flag(trap_exit, Flag),
+ ok
+ after To ->
+ ?WPRINT("Display Log process fail to complete in time"
+ "(~w msec): kill it", [To]),
+ process_flag(trap_exit, Flag),
+ exit(Pid, kill)
+ end
end,
- Flag = process_flag(trap_exit, true),
- Pid = spawn_link(fun() -> display_log(Config), exit(normal) end),
- receive
- {'EXIT', Pid, _} ->
- process_flag(trap_exit, Flag),
- ok
- after DisplayLogTimeout ->
- ?WPRINT("Display Log process fail to complete in time (~w msec): "
- "kill it", [DisplayLogTimeout]),
- process_flag(trap_exit, Flag),
- exit(Pid, kill)
- end,
Result = end_per_testcase1(Case, Config),
@@ -972,6 +1077,20 @@ end_per_testcase1(otp8395, Config) when is_list(Config) ->
otp8395({fin, Config});
end_per_testcase1(otp9884, Config) when is_list(Config) ->
otp9884({fin, Config});
+end_per_testcase1(otp16649_1, Config) when is_list(Config) ->
+ otp16649_1_fin(Config);
+end_per_testcase1(otp16649_2, Config) when is_list(Config) ->
+ otp16649_2_fin(Config);
+end_per_testcase1(otp16649_3, Config) when is_list(Config) ->
+ otp16649_3_fin(Config);
+end_per_testcase1(otp16649_4, Config) when is_list(Config) ->
+ otp16649_4_fin(Config);
+end_per_testcase1(otp16649_5, Config) when is_list(Config) ->
+ otp16649_5_fin(Config);
+end_per_testcase1(otp16649_6, Config) when is_list(Config) ->
+ otp16649_6_fin(Config);
+end_per_testcase1(otp16649_7, Config) when is_list(Config) ->
+ otp16649_7_fin(Config);
end_per_testcase1(_Case, Config) when is_list(Config) ->
?DBG("end_per_testcase1 -> entry with"
"~n Case: ~p"
@@ -1040,8 +1159,8 @@ start_v3_agent(Config, Opts) ->
start_bilingual_agent(Config) ->
?ALIB:start_bilingual_agent(Config).
-start_multi_threaded_agent(Config) when is_list(Config) ->
- ?ALIB:start_mt_agent(Config).
+start_multi_threaded_agent(Config, MT) when is_list(Config) ->
+ [{multi_threaded, MT} | ?ALIB:start_mt_agent(Config, MT)].
stop_agent(Config) ->
?ALIB:stop_agent(Config).
@@ -1440,6 +1559,7 @@ msd_varm_mib_start(X) ->
msm_varm_mib_start(X) ->
%% <CONDITIONAL-SKIP>
+ %% This is a bit radioactive but...
Skippable = [win32],
Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
?NON_PC_TC_MAYBE_SKIP(X, Condition),
@@ -2117,7 +2237,7 @@ mt_cases() ->
mt_trap
].
-init_mt(Config) when is_list(Config) ->
+init_mt(Config, MT) when is_list(Config) ->
SaNode = ?config(snmp_sa, Config),
create_tables(SaNode),
AgentConfDir = ?config(agent_conf_dir, Config),
@@ -2125,7 +2245,7 @@ init_mt(Config) when is_list(Config) ->
Ip = ?config(ip, Config),
?line ok = config([v2], MgrDir, AgentConfDir,
tuple_to_list(Ip), tuple_to_list(Ip)),
- [{vsn, v2} | start_multi_threaded_agent(Config)].
+ [{vsn, v2} | start_multi_threaded_agent(Config, MT)].
finish_mt(Config) when is_list(Config) ->
delete_tables(),
@@ -2290,10 +2410,11 @@ mt_trap(Config) when is_list(Config) ->
?P(mt_trap),
init_case(Config),
MA = whereis(snmp_master_agent),
+ MT = ?config(multi_threaded, Config),
?line load_master("Test1"),
?line load_master("TestTrapv2"),
- try_test(mt_trap_test, [MA]),
+ try_test(mt_trap_test, [MA, MT]),
?line unload_master("TestTrapv2"),
?line unload_master("Test1"),
ok.
@@ -3959,37 +4080,51 @@ multi_threaded_test() ->
?line ?expect1([{[sysLocation,0], "kalle"}]).
%% Req. Test1, TestTrapv2
-mt_trap_test(MA) ->
- ?NPRINT("Testing trap-sending with multi threaded agent..."),
- ?DBG("mt_trap_test(01) -> issue testTrapv22 (standard trap)", []),
+mt_trap_test(MA, MT) ->
+ ?NPRINT("Testing trap-sending with multi threaded (~w) agent...", [MT]),
+ ?IPRINT("mt_trap_test(01) -> issue testTrapv22 (standard trap)", []),
snmpa:send_trap(MA, testTrapv22, "standard trap"),
- ?DBG("mt_trap_test(02) -> await v2trap", []),
+ ?IPRINT("mt_trap_test(02) -> await v2trap", []),
?line ?expect2(v2trap, [{[sysUpTime, 0], any},
{[snmpTrapOID, 0], ?system ++ [0,1]}]),
- ?DBG("mt_trap_test(03) -> issue mtTrap (standard trap)", []),
+ %% multi-threaded = true
+ %% This will *lock* the 'main thread' of a multi-threaded agent,
+ %% the worker state will be 'busy'. Therefor when a new request
+ %% arrives a new *temporary* worker will be spawned.
+ ?IPRINT("mt_trap_test(03) -> issue mtTrap (standard trap)", []),
snmpa:send_trap(MA, mtTrap, "standard trap"),
Pid = get_multi_pid(),
- ?DBG("mt_trap_test(04) -> multi pid: ~p. Now request sysUpTime...", [Pid]),
+ ?IPRINT("mt_trap_test(04) -> multi pid: ~p. Now request sysUpTime...", [Pid]),
g([[sysUpTime,0]]),
- ?DBG("mt_trap_test(06) -> await sysUpTime", []),
+ ?IPRINT("mt_trap_test(06) -> await sysUpTime", []),
?line ?expect1([{[sysUpTime,0], any}]),
- ?DBG("mt_trap_test(07) -> issue testTrapv22 (standard trap)", []),
- snmpa:send_trap(MA, testTrapv22, "standard trap"),
- ?DBG("mt_trap_test(08) -> await v2trap", []),
- ?line ?expect2(v2trap,
- [{[sysUpTime, 0], any},
- {[snmpTrapOID, 0], ?system ++ [0,1]}]),
- ?DBG("mt_trap_test(09) -> send continue to multi-pid", []),
+ %% This will *only* work if multi-threaded is 'true', not 'extended'
+ %% since in the latter case all notifications are serialized through
+ %% a dedicated worker, which is now locked (see above).
+
+ if
+ (MT =:= true) ->
+ ?IPRINT("mt_trap_test(07) -> issue testTrapv22 (standard trap)", []),
+ snmpa:send_trap(MA, testTrapv22, "standard trap"),
+ ?IPRINT("mt_trap_test(08) -> await v2trap", []),
+ ?line ?expect2(v2trap,
+ [{[sysUpTime, 0], any},
+ {[snmpTrapOID, 0], ?system ++ [0,1]}]);
+ true ->
+ ok
+ end,
+
+ ?IPRINT("mt_trap_test(09) -> send continue to multi-pid", []),
Pid ! continue,
- ?DBG("mt_trap_test(10) -> await v2trap", []),
+ ?IPRINT("mt_trap_test(10) -> await v2trap", []),
?line ?expect2(v2trap, [{[sysUpTime, 0], any},
{[snmpTrapOID, 0], ?testTrap ++ [2]},
{[multiStr,0], "ok"}]),
- ?DBG("mt_trap_test(11) -> done", []),
+ ?IPRINT("mt_trap_test(11) -> done", []),
ok.
@@ -4394,7 +4529,7 @@ sa_mib() ->
ok.
ma_trap1(MA) ->
- ok = snmpa:send_trap(MA, testTrap2, "standard trap"),
+ ok = snmpa:send_trap(MA, testTrap2, "standard trap"),
?line ?expect5(trap, [system], 6, 1, [{[system, [4,0]],
"{mbj,eklas}@erlang.ericsson.se"}]),
ok = snmpa:send_trap(MA, testTrap1, "standard trap"),
@@ -4562,8 +4697,11 @@ ma_v2_inform1(MA) ->
after
20000 ->
?EPRINT("ma_v2_inform1 -> "
- "timeout awaiting snmp_notification [~p]",
- [Tag03]),
+ "timeout awaiting snmp_notification [~p]: "
+ "~n Message Queue: "
+ "~n ~p",
+ [Tag03,
+ process_info(self(), messages)]),
{error, snmp_notification_timeout}
end
end,
@@ -4601,8 +4739,11 @@ ma_v2_inform1(MA) ->
after
240000 ->
?EPRINT("ma_v2_inform1 -> "
- "timeout awaiting snmp_notification [~p]",
- [Tag07]),
+ "timeout awaiting snmp_notification [~p]: "
+ "~n Message Queue: "
+ "~n ~p",
+ [Tag07,
+ process_info(self(), messages)]),
{error, snmp_notification_timeout}
end
end,
@@ -4652,28 +4793,35 @@ ma_v2_inform2(MA) ->
%% Await callback(s)
CmdAwaitDeliveryCallback =
fun(Kind, Ref, Tag) ->
- ?IPRINT("CmdAwaitDeliveryCallback -> entry with"
+ ?IPRINT("ma_v2_inform2:"
+ "CmdAwaitDeliveryCallback -> entry with"
"~n Kind: ~p"
"~n Ref: ~p"
"~n Tag: ~p", [Kind, Ref, Tag]),
receive
{Kind, Ref, ok} ->
- ?IPRINT("CmdAwaitDeliveryCallback(~p,~p) -> "
+ ?IPRINT("ma_v2_inform2:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"received expected result: ok"
"~n", [Tag, Ref]),
ok;
{Kind, Ref, Error} ->
- ?IPRINT("CmdAwaitDeliveryCallback(~p,~p) -> "
+ ?IPRINT("ma_v2_inform2:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"received unexpected result: "
"~n Error: ~p"
"~n", [Tag, Ref, Error]),
{error, {unexpected_response, Error}}
after
240000 ->
- ?EPRINT("ma_v2_inform2 -> "
+ ?EPRINT("ma_v2_inform2:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"timeout awaiting got_response for "
- "snmp_notification [~p]",
- [Tag]),
+ "snmp_notification [~p]: "
+ "~n Message Queue: "
+ "~n ~p",
+ [Tag, Ref, Tag,
+ process_info(self(), messages)]),
{error, snmp_notification_timeout}
end
end,
@@ -4724,7 +4872,8 @@ ma_v2_inform3(MA) ->
CmdExpectInform =
fun(_No, Response) ->
- ?DBG("CmdExpectInform -> ~p: ~n~p", [_No, Response]),
+ ?DBG("ma_v2_inform3:CmdExpectInform -> ~p: "
+ "~n ~p", [_No, Response]),
?expect2({inform, Response},
[{[sysUpTime, 0], any},
{[snmpTrapOID, 0], ?system ++ [0,1]}])
@@ -4734,7 +4883,7 @@ ma_v2_inform3(MA) ->
fun(ok) ->
ok;
({ok, _Val}) ->
- ?DBG("CmdExp -> Val: ~p", [_Val]),
+ ?DBG("ma_v2_inform3:CmdExp -> Val: ~p", [_Val]),
ok;
({error, Id, Extra}) ->
{error, {unexpected, Id, Extra}};
@@ -4745,28 +4894,35 @@ ma_v2_inform3(MA) ->
%% Await callback(s)
CmdAwaitDeliveryCallback =
fun(Kind, Ref, Tag) ->
- ?IPRINT("CmdAwaitDeliveryCallback -> entry with"
+ ?IPRINT("ma_v2_inform3:"
+ "CmdAwaitDeliveryCallback -> entry with"
"~n Kind: ~p"
"~n Ref: ~p"
"~n Tag: ~p", [Kind, Ref, Tag]),
receive
{Kind, Ref, ok} ->
- ?IPRINT("CmdAwaitDeliveryCallback(~p,~p) -> "
+ ?IPRINT("ma_v2_inform3:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"received expected result: ok"
"~n", [Tag, Ref]),
ok;
{Kind, Ref, Error} ->
- ?IPRINT("CmdAwaitDeliveryCallback(~p,~p) -> "
+ ?IPRINT("ma_v2_inform3:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"received unexpected result: "
"~n Error: ~p"
"~n", [Tag, Ref, Error]),
{error, {unexpected_response, Error}}
after
240000 ->
- ?EPRINT("ma_v2_inform3 -> "
+ ?EPRINT("ma_v2_inform3:"
+ "CmdAwaitDeliveryCallback(~p,~p) -> "
"timeout awaiting got_response for "
- "snmp_notification [~p]",
- [Tag]),
+ "snmp_notification [~p]: "
+ "~n Message Queue: "
+ "~n ~p",
+ [Kind, Ref, Tag,
+ process_info(self(), messages)]),
{error, snmp_notification_timeout}
end
end,
@@ -4849,16 +5005,74 @@ command_handler([]) ->
ok;
command_handler([{_No, _Desc, Cmd}|Rest]) ->
?IPRINT("command_handler -> command ~w: ~n ~s", [_No, _Desc]),
- case (catch Cmd()) of
- ok ->
- ?IPRINT("command_handler -> ~w: ok", [_No]),
- command_handler(Rest);
- {error, Reason} ->
- ?EPRINT("command_handler -> ~w error: ~n~p", [_No, Reason]),
- ?line ?FAIL(Reason);
- Error ->
- ?EPRINT("command_handler -> ~w unexpected: ~n~p", [_No, Error]),
- ?line ?FAIL({unexpected_command_result, Error})
+ %% case (catch Cmd()) of
+ %% ok ->
+ %% ?IPRINT("command_handler -> ~w: ok", [_No]),
+ %% command_handler(Rest);
+ %% {error, Reason} ->
+ %% ?EPRINT("command_handler -> ~w error: ~n~p", [_No, Reason]),
+ %% ?line ?FAIL(Reason);
+ %% Error ->
+ %% ?EPRINT("command_handler -> ~w unexpected: ~n~p", [_No, Error]),
+ %% ?line ?FAIL({unexpected_command_result, Error})
+ %% end.
+ try Cmd() of
+ ok ->
+ ?IPRINT("command_handler -> ~w: ok", [_No]),
+ command_handler(Rest);
+ {error, Reason} ->
+ ?IPRINT("command_handler -> command ~w error", [_No]),
+ SysEvs = snmp_test_global_sys_monitor:events(),
+ if
+ (SysEvs =:= []) ->
+ ?EPRINT("command_handler -> ~w error: ~n~p", [_No, Reason]),
+ ?line ?FAIL(Reason);
+ true ->
+ ?WPRINT("command_handler -> "
+ "failed when we got system events: "
+ "~n Reason: ~p"
+ "~n Sys Events: ~p"
+ "~n", [Reason, SysEvs]),
+ ?SKIP([{reason, Reason}, {system_events, SysEvs}])
+ end;
+ Error ->
+ ?IPRINT("command_handler -> command ~w unexpected", [_No]),
+ SysEvs = snmp_test_global_sys_monitor:events(),
+ if
+ (SysEvs =:= []) ->
+ ?EPRINT("command_handler -> "
+ "~w unexpected: ~n~p", [_No, Error]),
+ ?line ?FAIL({unexpected_command_result, Error});
+ true ->
+ ?WPRINT("command_handler -> "
+ "unexpected when we got system events: "
+ "~n Unexpected: ~p"
+ "~n Sys Events: ~p"
+ "~n", [Error, SysEvs]),
+ ?SKIP([{unexpected, Error}, {system_events, SysEvs}])
+ end
+ catch
+ C:E:S ->
+ ?IPRINT("command_handler -> command ~w catched", [_No]),
+ SysEvs = snmp_test_global_sys_monitor:events(),
+ if
+ (SysEvs =:= []) ->
+ ?EPRINT("command_handler -> ~w catched: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [_No, C, E, S]),
+ ?line ?FAIL({catched_command_result, {C, E, S}});
+ true ->
+ ?WPRINT("command_handler -> "
+ "catched when we got system events: "
+ "~n Catched: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p"
+ "~n Sys Events: ~p"
+ "~n", [C, E, S, SysEvs]),
+ ?SKIP([{catched, {C, E, S}}, {system_events, SysEvs}])
+ end
end.
@@ -5048,25 +5262,32 @@ snmp_standard_mib(Config) when is_list(Config) ->
?DBG("snmp_standard_mib -> std_mib_a", []),
InBadVsns = try_test(std_mib_a),
put(vsn, v2),
+
?DBG("snmp_standard_mib -> std_mib_read", []),
try_test(std_mib_read),
put(vsn, v1),
?DBG("snmp_standard_mib -> std_mib_b (~w)", [InBadVsns]),
Bad = try_test(std_mib_b, [InBadVsns]),
+
?DBG("snmp_standard_mib -> std_mib_read (community: 'bad community')", []),
try_test(std_mib_read, [], [{community, "bad community"}]),
+
?DBG("snmp_standard_mib -> std_mib_write (community: 'public')", []),
try_test(std_mib_write, [], [{community, "public"}]),
+
?DBG("snmp_standard_mib -> std_mib_asn_err", []),
try_test(std_mib_asn_err),
+
?DBG("snmp_standard_mib -> std_mib_c (~w)", [Bad]),
try_test(std_mib_c, [Bad]),
+
?DBG("snmp_standard_mib -> std_mib_a", []),
try_test(standard_mib_a),
?DBG("snmp_standard_mib -> std_mib_finish", []),
try_test(std_mib_finish),
+
?DBG("snmp_standard_mib -> std_mib_test_finish", []),
try_test(standard_mib_test_finish, [], [{community, "bad community"}]).
@@ -5158,7 +5379,7 @@ snmpv2_mib_2(Config) when is_list(Config) ->
?DBG("snmpv2_mib_2 -> write with public community",[]),
try_test(std_mib_write, [], [{community, "public"}]),
- ?DBG("snmpv2_mib_2 -> asn err",[]),
+ ?DBG("snmpv2_mib_2 -> asn err", []),
try_test(std_mib_asn_err),
?DBG("snmpv2_mib_2 -> check counters",[]),
@@ -5171,7 +5392,7 @@ snmpv2_mib_2(Config) when is_list(Config) ->
try_test(std_mib_finish),
?DBG("snmpv2_mib_2 -> force auth failure, and await trap, "
- "then disable auth traps",[]),
+ "then disable auth traps", []),
try_test(snmpv2_mib_test_finish, [], [{community, "bad community"}]),
?IPRINT("snmpv2_mib_2 -> done"),
@@ -5325,6 +5546,16 @@ snmp_framework_mib_3(Config) when is_list(Config) ->
%% Therefor we must take that into account when we check if the
%% Engine Time diff (between the two checks) is acceptably.
snmp_framework_mib_test() ->
+
+ ?IPRINT("transports: "
+ "~n ~p"
+ "~ninfo: "
+ "~n ~p",
+ [
+ rpc:call(get(master_node), snmpa, which_transports, []),
+ rpc:call(get(master_node), snmpa, info, [])
+ ]),
+
Sleep = 5,
?line ["agentEngine"] = get_req(1, [[snmpEngineID,0]]),
T1 = snmp_misc:now(ms),
@@ -6019,81 +6250,129 @@ loop_mib_3(Config) when is_list(Config) ->
%% Req. As many mibs all possible
loop_mib_1_test() ->
- ?DBG("loop_mib_1_test -> entry",[]),
+ ?IPRINT("loop_mib_1_test -> entry"),
N = loop_it_1([1,1], 0),
- io:format(user, "found ~w varibles\n", [N]),
+ ?IPRINT("found ~w varibles", [N]),
?line N = if N < 100 -> 100;
true -> N
end.
loop_it_1(Oid, N) ->
- ?DBG("loop_it_1_test -> entry with~n"
- "\tOid: ~p~n"
- "\tN: ~p",[Oid,N]),
+ ?IPRINT("loop_it_1_test -> entry with"
+ "~n Oid: ~p"
+ "~n N: ~p", [Oid, N]),
case get_next_req([Oid]) of
#pdu{type = 'get-response',
error_status = noError,
error_index = 0,
varbinds = [#varbind{oid = NOid,
value = _Value}]} when NOid > Oid ->
- ?DBG("loop_it_1_test -> "
- "~n NOid: ~p"
- "~n Value: ~p", [NOid, _Value]),
+ ?IPRINT("loop_it_1_test -> "
+ "expected intermediate (get-next) result: "
+ "~n NOid: ~p"
+ "~n Value: ~p", [NOid, _Value]),
?line [_Value2] = get_req(1, [NOid]), % must not be same
- ?DBG("loop_it_1_test -> "
- "~n Value2: ~p", [_Value2]),
+ ?IPRINT("loop_it_1_test -> expected intermediate (get) result: "
+ "~n Value2: ~p", [_Value2]),
loop_it_1(NOid, N+1);
#pdu{type = 'get-response',
error_status = noError,
error_index = 0,
varbinds = Vbs} ->
- exit({unexpected_vbs, ?LINE, Vbs});
+ ?EPRINT("loop_it_1_test -> unexpected (get-response) vbs: "
+ "~n Vbs: ~p", [Vbs]),
+ ?line ?FAIL({unexpected_vbs,
+ [{get_next_oid, Oid},
+ {counter, N},
+ {varbinds, Vbs}]});
#pdu{type = 'get-response',
error_status = noSuchName,
error_index = 1,
varbinds = [_]} ->
- ?DBG("loop_it_1_test -> done: ~p",[N]),
+ ?IPRINT("loop_it_1_test -> done: ~p", [N]),
N;
#pdu{type = 'get-response',
error_status = Err,
error_index = Idx,
varbinds = Vbs} ->
- exit({unexpected_pdu, ?LINE, Err, Idx, Vbs});
+ ?EPRINT("loop_it_1_test -> unexpected (get-response) pdu: "
+ "~n Err: ~p"
+ "~n Idx: ~p"
+ "~n Vbs: ~p", [Err, Idx, Vbs]),
+ ?line ?FAIL({unexpected_pdu,
+ [{get_next_oid, Oid},
+ {counter, N},
+ {error_status, Err},
+ {error_index, Idx},
+ {varbinds, Vbs}]});
#pdu{type = Type,
error_status = Err,
error_index = Idx,
varbinds = Vbs} ->
- exit({unexpected_pdu, ?LINE, Type, Err, Idx, Vbs});
+ ?EPRINT("loop_it_1_test -> unexpected pdu: "
+ "~n Type: ~p"
+ "~n Err: ~p"
+ "~n Idx: ~p"
+ "~n Vbs: ~p", [Type, Err, Idx, Vbs]),
+ ?line ?FAIL({unexpected_pdu,
+ [{get_next_oid, Oid},
+ {counter, N},
+ {type, Type},
+ {error_status, Err},
+ {error_index, Idx},
+ {varbinds, Vbs}]});
{error, Reason} ->
- exit({error, Reason, ?LINE})
+ %% Regardless of the error here (its usually timeout),
+ %% if we have had system events we skip since the results
+ %% in those cases are simply not reliable.
+ %% There is just no point in trying to analyze the reason.
+ ?IPRINT("loop_it_1_test -> receive error: "
+ "~n ~p", [Reason]),
+ SysEvs = snmp_test_global_sys_monitor:events(),
+ if
+ (SysEvs =:= []) ->
+ ?EPRINT("loop_it_1_test -> error: "
+ "~n ~p", [Reason]),
+ ?line ?FAIL([{get_next_oid, Oid},
+ {counter, N},
+ {reason, Reason}]);
+
+ true ->
+ ?WPRINT("loop_it_1_test -> "
+ "error when we got system events: "
+ "~n Reason: ~p"
+ "~n Sys Events: ~p"
+ "~n", [Reason, SysEvs]),
+ ?SKIP([{reason, Reason}, {system_events, SysEvs}])
+ end
end.
%% Req. As many mibs all possible
loop_mib_2_test() ->
- ?DBG("loop_mib_2_test -> entry",[]),
+ ?IPRINT("loop_mib_2_test -> entry"),
N = loop_it_2([1,1], 0),
- io:format(user, "found ~w varibles\n", [N]),
+ ?IPRINT("found ~w varibles", [N]),
?line N = if N < 100 -> 100;
true -> N
end.
loop_it_2(Oid, N) ->
- ?DBG("loop_it_2 -> entry with"
- "~n Oid: ~p"
- "~n N: ~p",[Oid, N]),
+ ?IPRINT("loop_it_2 -> entry with"
+ "~n Oid: ~p"
+ "~n N: ~p", [Oid, N]),
case get_next_req([Oid]) of
#pdu{type = 'get-response',
error_status = noError,
error_index = 0,
varbinds = [#varbind{oid = _NOid, value = endOfMibView}]} ->
- ?DBG("loop_it_2 -> "
- "~n NOid: ~p", [_NOid]),
+ ?IPRINT("loop_it_2 -> done: "
+ "~n NOid: ~p", [_NOid]),
N;
#pdu{type = 'get-response',
@@ -6101,52 +6380,82 @@ loop_it_2(Oid, N) ->
error_index = 0,
varbinds = [#varbind{oid = NOid,
value = _Value}]} when NOid > Oid ->
- ?DBG("loop_it_2 -> "
- "~n NOid: ~p"
- "~n Value: ~p", [NOid, _Value]),
+ ?IPRINT("loop_it_2 -> "
+ "expected intermediate (get-next) result: "
+ "~n NOid: ~p"
+ "~n Value: ~p", [NOid, _Value]),
?line [_Value2] = get_req(1, [NOid]), % must not be same
- ?DBG("loop_it_2 -> "
- "~n Value2: ~p", [_Value2]),
+ ?IPRINT("loop_it_2 -> expected intermediate (get) result: "
+ "~n Value2: ~p", [_Value2]),
loop_it_2(NOid, N+1);
#pdu{type = 'get-response',
error_status = noError,
error_index = 0,
varbinds = Vbs} ->
- exit({unexpected_pdu, ?LINE,
- [{varbinds, Vbs},
- {get_next_oid, Oid},
- {counter, N}]});
+ ?EPRINT("loop_it_2 -> unexpected (get-response) vbs: "
+ "~n Vbs: ~p", [Vbs]),
+ ?line ?FAIL({unexpected_vbs,
+ [{get_next_oid, Oid},
+ {counter, N},
+ {varbinds, Vbs}]});
#pdu{type = 'get-response',
error_status = ES,
error_index = EI,
varbinds = Vbs} ->
- exit({unexpected_pdu, ?LINE,
- [{error_status, ES},
- {error_index, EI},
- {varbinds, Vbs},
- {get_next_oid, Oid},
- {counter, N}]});
+ ?EPRINT("loop_it_2 -> unexpected (get-response) pdu: "
+ "~n ES: ~p"
+ "~n EI: ~p"
+ "~n Vbs: ~p", [ES, EI, Vbs]),
+ ?line ?FAIL({unexpected_pdu,
+ [{get_next_oid, Oid},
+ {counter, N},
+ {error_status, ES},
+ {error_index, EI},
+ {varbinds, Vbs}]});
#pdu{type = Type,
error_status = ES,
error_index = EI,
varbinds = Vbs} ->
- exit({unexpected_pdu, ?LINE,
- [{type, Type},
- {error_status, ES},
- {error_index, EI},
- {varbinds, Vbs},
- {get_next_oid, Oid},
- {counter, N}]});
+ ?EPRINT("loop_it_2 -> unexpected pdu: "
+ "~n Type: ~p"
+ "~n ES: ~p"
+ "~n EI: ~p"
+ "~n Vbs: ~p", [Type, ES, EI, Vbs]),
+ ?line ?FAIL({unexpected_pdu,
+ [{get_next_oid, Oid},
+ {counter, N},
+ {type, Type},
+ {error_status, ES},
+ {error_index, EI},
+ {varbinds, Vbs}]});
{error, Reason} ->
- exit({unexpected_result, ?LINE,
- [{reason, Reason},
- {get_next_oid, Oid},
- {counter, N}]})
-
+ %% Regardless of the error here (its usually timeout),
+ %% if we have had system events we skip since the results
+ %% in those cases are simply not reliable.
+ %% There is just no point in trying to analyze the reason.
+ ?IPRINT("loop_it_2 -> receive error: "
+ "~n ~p", [Reason]),
+ SysEvs = snmp_test_global_sys_monitor:events(),
+ if
+ (SysEvs =:= []) ->
+ ?EPRINT("loop_it_2 -> error: "
+ "~n ~p", [Reason]),
+ ?line ?FAIL([{get_next_oid, Oid},
+ {counter, N},
+ {reason, Reason}]);
+
+ true ->
+ ?WPRINT("loop_it_2 -> "
+ "error when we got system events: "
+ "~n Reason: ~p"
+ "~n Sys Events: ~p"
+ "~n", [Reason, SysEvs]),
+ ?SKIP([{reason, Reason}, {system_events, SysEvs}])
+ end
end.
loop_mib_3_test() ->
@@ -6297,27 +6606,34 @@ otp_1131_3(X) ->
%% Montavista Linux looks like a Debian distro (/etc/issue)
LinuxVersionVerify =
fun() ->
- case os:cmd("uname -m") of
+ case string:to_lower(os:cmd("uname -m")) of
"ppc" ++ _ ->
case file:read_file_info("/etc/issue") of
{ok, _} ->
- case os:cmd("grep -i montavista /etc/issue") of
- Info when (is_list(Info) andalso
- (length(Info) > 0)) ->
+ case string:to_lower(
+ os:cmd("grep -i montavista /etc/issue")) of
+ "montavista" ++ _ ->
case os:version() of
{2, 6, 10} ->
+ ?IPRINT("(PPC Linux) kernel version check: "
+ "{2, 6, 10} => SKIP"),
true;
- _ ->
+ V ->
+ ?IPRINT("(PPC Linux) kernel version check: "
+ "~p != {2, 6, 10} => *NO* SKIP", [V]),
false
end;
_ -> % Maybe plain Debian or Ubuntu
+ ?IPRINT("(PPC Linux) Not MontaVista => *NO* SKIP"),
false
end;
_ ->
%% Not a Debian based distro
+ ?IPRINT("(PPC Linux) Unknown distro => *NO* SKIP"),
false
end;
_ ->
+ ?IPRINT("(Linux) Not PPC => *NO* SKIP"),
false
end
end,
@@ -7013,12 +7329,12 @@ otp16092_try_start_and_stop_agent(Node, Opts, Expected) ->
?IPRINT("try start snmp (agent) supervisor (on ~p) - expect ~p",
[Node, Expected]),
case start_standalone_agent(Node, Opts) of
- Pid when is_pid(Pid) andalso (Expected =:= success) ->
+ {ok, Pid} when is_pid(Pid) andalso (Expected =:= success) ->
?IPRINT("Expected success starting snmp (agent) supervisor"),
?SLEEP(1000),
stop_standalone_agent(Pid),
ok;
- Pid when is_pid(Pid) andalso (Expected =:= failure) ->
+ {ok, Pid} when is_pid(Pid) andalso (Expected =:= failure) ->
?EPRINT("Unexpected success starting snmp (agent) supervisor: (~p)",
[Pid]),
?SLEEP(1000),
@@ -7075,7 +7391,6 @@ otp16092_try_start_and_stop_agent(Node, Opts, Expected) ->
end,
ok.
-
%%-----------------------------------------------------------------
@@ -7086,7 +7401,25 @@ otp16092_try_start_and_stop_agent(Node, Opts, Expected) ->
tickets2_cases() ->
[
otp8395,
- otp9884
+ otp9884,
+ {group, otp16649}
+ ].
+
+otp16649_cases() ->
+ [
+ {group, otp16649_ipv4},
+ {group, otp16649_ipv6}
+ ].
+
+otp16649_gen_cases() ->
+ [
+ otp16649_1,
+ otp16649_2,
+ otp16649_3,
+ otp16649_4,
+ otp16649_5,
+ otp16649_6,
+ otp16649_7
].
@@ -7094,9 +7427,9 @@ otp8395({init, Config}) when is_list(Config) ->
?DBG("otp8395(init) -> entry with"
"~n Config: ~p", [Config]),
- %% --
+ %% --
%% Start nodes
- %%
+ %%
{ok, AgentNode} = start_node(agent),
{ok, ManagerNode} = start_node(manager),
@@ -7352,6 +7685,525 @@ otp9884_await_backup_completion(First, Second) ->
%%-----------------------------------------------------------------
+otp16649_1_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {4001, trap_sender}],
+ otp16649_init(1, AgentPreTransports, Config).
+
+otp16649_1_fin(Config) ->
+ otp16649_fin(1, Config).
+
+otp16649_1(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_1(Config) when is_list(Config) ->
+ otp16649(1, Config).
+
+
+otp16649_2_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {system, trap_sender}],
+ otp16649_init(2, AgentPreTransports, Config).
+
+otp16649_2_fin(Config) ->
+ otp16649_fin(2, Config).
+
+otp16649_2(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_2(Config) when is_list(Config) ->
+ otp16649(2, Config).
+
+
+otp16649_3_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {0, trap_sender}],
+ otp16649_init(3, AgentPreTransports, Config).
+
+otp16649_3_fin(Config) ->
+ otp16649_fin(3, Config).
+
+otp16649_3(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_3(Config) when is_list(Config) ->
+ otp16649(3, Config).
+
+
+otp16649_4_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {{4000,4010}, trap_sender, [{no_reuse, true}]}],
+ otp16649_init(4, AgentPreTransports, Config).
+
+otp16649_4_fin(Config) ->
+ otp16649_fin(4, Config).
+
+otp16649_4(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_4(Config) when is_list(Config) ->
+ otp16649(4, Config).
+
+
+otp16649_5_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {[{4000,4010}], trap_sender, [{no_reuse, true}]}],
+ otp16649_init(5, AgentPreTransports, Config).
+
+otp16649_5_fin(Config) ->
+ otp16649_fin(5, Config).
+
+otp16649_5(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_5(Config) when is_list(Config) ->
+ otp16649(5, Config).
+
+
+otp16649_6_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {[4000,4001,4002], trap_sender, [{no_reuse, true}]}],
+ otp16649_init(6, AgentPreTransports, Config).
+
+otp16649_6_fin(Config) ->
+ otp16649_fin(5, Config).
+
+otp16649_6(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_6(Config) when is_list(Config) ->
+ otp16649(6, Config).
+
+
+otp16649_7_init(Config) ->
+ AgentPreTransports = [{4000, req_responder},
+ {[4000,{5100,5110}], trap_sender,
+ [{no_reuse, true}]}],
+ otp16649_init(7, AgentPreTransports, Config).
+
+otp16649_7_fin(Config) ->
+ otp16649_fin(7, Config).
+
+otp16649_7(doc) ->
+ "OTP-16649 - Multiple transports.";
+otp16649_7(Config) when is_list(Config) ->
+ otp16649(7, Config).
+
+
+otp16649(N, Config) ->
+ ?IPRINT("otp16649 -> entry with"
+ "~n N: ~w"
+ "~n Config: ~p", [N, Config]),
+
+ AgentNode = ?config(agent_node, Config),
+ ManagerNode = ?config(manager_node, Config),
+
+ ?line AInfo = rpc:call(AgentNode, snmpa, info, []),
+
+ ?IPRINT("Agent Info: "
+ "~n ~p", [AInfo]),
+
+ {value, {_, AgentRawTransports}} =
+ lists:keysearch(agent_raw_transports, 1, Config),
+ {value, {_, NetIF}} =
+ lists:keysearch(net_if, 1, AInfo),
+ {value, {_, TIs}} =
+ lists:keysearch(transport_info, 1, NetIF),
+
+ if (length(AgentRawTransports) =:= length(TIs)) ->
+ ok;
+ true ->
+ ?IPRINT("Invalid transports: "
+ "~n Number of raw transports: ~w"
+ "~n Number of transports: ~w",
+ [length(AgentRawTransports), length(TIs)]),
+ exit({invalid_num_transports,
+ length(AgentRawTransports), length(TIs)})
+ end,
+
+ ?IPRINT("validate transports"),
+ otp16649_validate_transports(AgentRawTransports, TIs),
+
+ ?IPRINT("which req-responder port-no"),
+ AgentReqPortNo = otp16649_which_req_port_no(TIs),
+
+ ?IPRINT("which trap-sender port-no"),
+ AgentTrapPortNo = otp16649_which_trap_port_no(TIs),
+
+ ?IPRINT("(mgr) register user"),
+ ?line ok = otp16649_mgr_reg_user(ManagerNode),
+
+ ?IPRINT("(mgr) register agent"),
+ TargetBase = "otp16649-agent-",
+ ReqTarget = TargetBase ++ "req",
+ TrapTarget = TargetBase ++ "trap",
+
+ ?line ok = otp16649_mgr_reg_agent(ManagerNode,
+ ?config(ipfamily, Config),
+ ?config(tdomain, Config),
+ ReqTarget, AgentReqPortNo),
+ ?line ok = otp16649_mgr_reg_agent(ManagerNode,
+ ?config(ipfamily, Config),
+ ?config(tdomain, Config),
+ TrapTarget, AgentTrapPortNo),
+
+ ?IPRINT("(mgr) simple (sync) get request"),
+ Oids = [?sysObjectID_instance, ?sysDescr_instance, ?sysUpTime_instance],
+ ?line ok = case otp16649_mgr_get_req(ManagerNode, Oids) of
+ {ok, {noError, 0, ReplyOids}, _} ->
+ ?IPRINT("(mgr) simple (sync) successful reply: "
+ "~n ~p", [ReplyOids]),
+ ok;
+ {ok, InvalidReply, _} ->
+ ?IPRINT("(mgr) simple (sync) invalid reply: "
+ "~n ~p", [InvalidReply]),
+ ok;
+ {error, Reason} ->
+ ?IPRINT("(mgr) simple (sync) error: "
+ "~n ~p", [Reason]),
+ error
+ end,
+
+ ?IPRINT("load TestTrap..."),
+ MibDir = ?config(mib_dir, Config),
+ ?line ok = otp16649_agent_load_mib(AgentNode, MibDir, "TestTrap"),
+
+ ?IPRINT("(agent) send trap (testTrap2)"),
+ ?line ok = otp16649_agent_send_trap(AgentNode, testTrap2),
+
+ TDomain = ?config(tdomain, Config),
+
+ receive
+ {handle_trap, From_v1, TrapTarget,
+ {?system, 6, 1, _, Vbs_v1}}
+ when is_pid(From_v1) andalso
+ is_list(Vbs_v1) andalso
+ (TDomain =:= transportDomainUdpIpv4) ->
+ ?IPRINT("received expected (v1) handle trap callback message: "
+ "~n ~p", [Vbs_v1]),
+ ok
+
+ after 5000 ->
+ case TDomain of
+ transportDomainUdpIpv4 ->
+ ?IPRINT("TIMEOUT"),
+ ?line exit(timeout);
+ transportDomainUdpIpv6 ->
+ ?IPRINT("expected timeout - "
+ "v1 trap's can only be sent on IPv4 domains"),
+ ok
+ end
+ end,
+
+
+ ?IPRINT("load TestTrapv2..."),
+ ?line ok = otp16649_agent_load_mib(AgentNode, MibDir, "TestTrapv2"),
+
+ ?IPRINT("(agent) send trap (testTrapv22)"),
+ ?line ok = otp16649_agent_send_trap(AgentNode, testTrapv22),
+
+ receive
+ {handle_trap, From_v2, TrapTarget,
+ {noError, 0, Vbs_v2}} when is_pid(From_v2) andalso
+ is_list(Vbs_v2) ->
+ ?IPRINT("received expected (v2) handle trap callback message: "
+ "~n ~p", [Vbs_v2]),
+ ok
+
+ after 5000 ->
+ ?IPRINT("TIMEOUT"),
+ ?line exit(timeout)
+ end,
+
+ ?IPRINT("done"),
+ ok.
+
+
+otp16649_init(N, AgentPreTransports, Config) ->
+ ?IPRINT("otp16649_init -> entry with"
+ "~n N: ~w"
+ "~n AgentPreTransports: ~w"
+ "~n Config: ~p", [N, AgentPreTransports, Config]),
+
+ %% --
+ %% Start nodes
+ %%
+
+ ?IPRINT("start (agent and mansger) nodes"),
+
+ {ok, AgentNode} = start_node(otp16649_mk_name(N, agent)),
+ {ok, ManagerNode} = start_node(otp16649_mk_name(N, manager)),
+
+ %% --
+ %% Misc
+ %%
+
+ AgentHost = ?HOSTNAME(AgentNode),
+ ManagerHost = ?HOSTNAME(ManagerNode),
+
+ ?IPRINT("otp16649_init -> "
+ "~n AgentHost: ~p"
+ "~n ManagerHost: ~p",
+ [AgentHost, ManagerHost]),
+
+ Host = snmp_test_lib:hostname(),
+ Ip = ?config(ip, Config),
+ %% We should really "extract" the address from the hostnames,
+ %% but because on some OSes (Ubuntu) adds 12.7.0.1.1 to its hosts file,
+ %% this does not work. We want a "proper" address.
+ %% Also, since both nodes (agent and manager) are both started locally,
+ %% we can use 'Ip' for both!
+ %% {ok, AgentIP} = snmp_misc:ip(AgentHost),
+ %% {ok, ManagerIP0} = snmp_misc:ip(ManagerHost),
+ AgentIP = Ip,
+ ManagerIP0 = Ip,
+ ManagerIP = tuple_to_list(ManagerIP0),
+ ?IPRINT("otp16649_init -> "
+ "~n Host: ~p"
+ "~n Ip: ~p"
+ "~n AgentIP: ~p"
+ "~n ManagerIP0: ~p"
+ "~n ManagerIP: ~p",
+ [Host, Ip, AgentIP, ManagerIP0, ManagerIP]),
+
+
+ %% --
+ %% Write agent config
+ %%
+
+ AgentConfDir = ?config(agent_conf_dir, Config),
+ Vsns = [v1,v2],
+ TransportDomain = ?config(tdomain, Config),
+ F = fun({PortInfo, Kind}) ->
+ #{addr => {AgentIP, PortInfo}, kind => Kind};
+ ({PortInfo, Kind, Opts}) ->
+ #{addr => {AgentIP, PortInfo}, kind => Kind, opts => Opts}
+ end,
+ AgentPreTransports2 = [F(T) || T <- AgentPreTransports],
+
+ ?IPRINT("write agent config files"),
+ ?line ok = snmp_config:write_agent_snmp_files(
+ AgentConfDir, Vsns,
+ TransportDomain, {ManagerIP, ?MGR_PORT}, AgentPreTransports2,
+ "test"),
+
+ ?IPRINT("start agent"),
+ Config2 = start_agent([{host, Host},
+ {agent_node, AgentNode},
+ {agent_host, AgentHost},
+ {agent_ip, AgentIP},
+ {manager_node, ManagerNode},
+ {manager_host, ManagerHost},
+ {manager_ip, ManagerIP}|Config]),
+
+
+
+ %% --
+ %% Write manager config
+ %%
+
+ ?IPRINT("create manager dirs"),
+ MgrTopDir = ?config(manager_top_dir, Config),
+ MgrDbDir = filename:join(MgrTopDir, "db/"),
+ MgrConfDir = filename:join(MgrTopDir, "conf/"),
+ ?line ok = file:make_dir(MgrConfDir),
+ MgrDbDir = filename:join(MgrTopDir, "db/"),
+ ?line ok = file:make_dir(MgrDbDir),
+ MgrLogDir = filename:join(MgrTopDir, "log/"),
+ ?line ok = file:make_dir(MgrLogDir),
+
+ ?IPRINT("write manager config files"),
+ MgrTransports = [{TransportDomain, {ManagerIP0, ?MGR_PORT}}],
+ ?line ok = snmp_config:write_manager_snmp_files(
+ MgrConfDir,
+ MgrTransports,
+ ?MGR_MMS, ?MGR_ENGINE_ID),
+
+ Config3 = [{manager_db_dir, MgrDbDir},
+ {manager_conf_dir, MgrConfDir},
+ {manager_log_dir, MgrLogDir} | Config2],
+
+ ?IPRINT("start manager"),
+ ?line ok = start_manager(Config3),
+
+ ?DBG("otp16649_init -> done when"
+ "~n Config2: ~p", [Config3]),
+ [{agent_raw_transports, AgentPreTransports} | Config3].
+
+otp16649_mk_name(N, Post) when is_integer(N) andalso is_atom(Post) ->
+ list_to_atom(?F("otp16649_~w_~w", [N, Post])).
+
+
+otp16649_fin(N, Config) when is_integer(N) ->
+ ?IPRINT("otp16649_fin -> entry with"
+ "~n N: ~p"
+ "~n Config: ~p", [N, Config]),
+
+ ManagerNode = ?config(manager_node, Config),
+ AgentNode = ?config(agent_node, Config),
+
+ %% -
+ %% Stop agent (this is the nice way to do it,
+ %% so logs and files can be closed in the proper way).
+ %%
+
+ ?line AgentTopSup = ?config(agent_sup, Config),
+ stop_standalone_agent(AgentTopSup),
+
+
+ %% -
+ %% Stop manager
+ %%
+
+ stop_standalone_manager(ManagerNode),
+
+
+ %%
+ %% Stop the manager node
+ %%
+
+ ?DBG("otp16649_fin -> stop manager node", []),
+ stop_node(ManagerNode),
+
+
+ %%
+ %% Stop the agent node
+ %%
+
+ ?DBG("otp16649_fin -> stop agent node", []),
+ stop_node(AgentNode),
+
+ ?DBG("otp16649_fin -> done", []),
+ Config1 = lists:keydelete(manager_node, 1, Config),
+ lists:keydelete(agent_node, 1, Config1).
+
+
+otp16649_validate_transports([], []) ->
+ ok;
+otp16649_validate_transports([AgentRawTransport|AgentRawTransports],
+ [TI|TIs]) ->
+ ?IPRINT("validate transport:"
+ "~n AgentRawTransport: ~p"
+ "~n TI: ~p", [AgentRawTransport, TI]),
+ otp16649_validate_transport(AgentRawTransport, TI),
+ otp16649_validate_transports(AgentRawTransports, TIs).
+
+otp16649_validate_transport({PortInfo, Kind}, #{taddress := {_, PortNo},
+ transport_kind := Kind}) ->
+ ?IPRINT("validate ~w transport:"
+ "~n PortNo: ~w"
+ "~n PortInfo: ~p", [Kind, PortNo, PortInfo]),
+ otp16649_validate_port(PortInfo, PortNo);
+otp16649_validate_transport({_, ConfKind}, #{taddress := {_, PortNo},
+ transport_kind := ActualKind}) ->
+ exit({invalid_transport_kind, {PortNo, ConfKind, ActualKind}});
+otp16649_validate_transport({PortInfo, Kind, _}, #{taddress := {_, PortNo},
+ transport_kind := Kind}) ->
+ ?IPRINT("validate ~w transport:"
+ "~n PortNo: ~w"
+ "~n PortInfo: ~p", [Kind, PortNo, PortInfo]),
+ otp16649_validate_port(PortInfo, PortNo);
+otp16649_validate_transport({_, ConfKind, _}, #{taddress := {_, PortNo},
+ transport_kind := ActualKind}) ->
+ exit({invalid_transport_kind, {PortNo, ConfKind, ActualKind}}).
+
+otp16649_validate_port(PortNo, PortNo) when is_integer(PortNo) ->
+ ok;
+otp16649_validate_port(0, PortNo) when is_integer(PortNo) ->
+ ok;
+otp16649_validate_port(system, PortNo) when is_integer(PortNo) ->
+ ok;
+otp16649_validate_port(Range, PortNo) when is_tuple(Range) ->
+ case otp16649_validate_port_range(Range, PortNo) of
+ ok ->
+ ok;
+ error ->
+ exit({invalid_transport_port_no, {Range, PortNo}})
+ end;
+otp16649_validate_port(Ranges, PortNo) when is_list(Ranges) ->
+ case otp16649_validate_port_ranges(Ranges, PortNo) of
+ ok ->
+ ok;
+ error ->
+ exit({invalid_transport_port_no, {Ranges, PortNo}})
+ end.
+
+
+otp16649_validate_port_range({Min, Max}, PortNo)
+ when is_integer(Min) andalso
+ is_integer(Max) andalso
+ is_integer(PortNo) andalso
+ (Min =< PortNo) andalso
+ (PortNo =< Max) ->
+ ok;
+otp16649_validate_port_range(_Range, _PortNo) ->
+ error.
+
+otp16649_validate_port_ranges([], _PortNo) ->
+ error;
+otp16649_validate_port_ranges([PortNo|_], PortNo) when is_integer(PortNo) ->
+ ok;
+otp16649_validate_port_ranges([Range|Ranges], PortNo) when is_tuple(Range) ->
+ case otp16649_validate_port_range(Range, PortNo) of
+ ok ->
+ ok;
+ error ->
+ otp16649_validate_port_ranges(Ranges, PortNo)
+ end;
+otp16649_validate_port_ranges([_|Ranges], PortNo) ->
+ otp16649_validate_port_ranges(Ranges, PortNo).
+
+
+otp16649_which_req_port_no(TIs) ->
+ ?IPRINT("otp16649_which_req_port_no -> entry with"
+ "~n TIs: ~p", [TIs]),
+ otp16649_which_port_no(TIs, req_responder).
+
+otp16649_which_trap_port_no(TIs) ->
+ ?IPRINT("otp16649_which_trap_port_no -> entry with"
+ "~n TIs: ~p", [TIs]),
+ otp16649_which_port_no(TIs, trap_sender).
+
+otp16649_which_port_no([], Kind) ->
+ exit({no_transport_port_no, Kind});
+otp16649_which_port_no([#{taddress := {_, PortNo},
+ transport_kind := Kind}|_], Kind) ->
+ PortNo;
+otp16649_which_port_no([_|TIs], Kind) ->
+ otp16649_which_port_no(TIs, Kind).
+
+
+otp16649_mgr_reg_user(Node) ->
+ rpc:call(Node, snmpm, register_user,
+ [otp16649, snmp_otp16649_user, self()]).
+
+otp16649_mgr_reg_agent(Node, IPFam, TDomain, Target, PortNo) ->
+ ?IPRINT("otp16649_mgr_reg_agent -> entry with"
+ "~n Node: ~p"
+ "~n IPFam: ~p"
+ "~n TDomain: ~p"
+ "~n Target: ~p"
+ "~n PortNo: ~p",
+ [Node, IPFam, TDomain, Target, PortNo]),
+ Localhost = ?LOCALHOST(IPFam),
+ Config = [{address, Localhost},
+ {port, PortNo},
+ {version, v1},
+ {tdomain, TDomain},
+ {engine_id, "agentEngine"}],
+ rpc:call(Node, snmpm, register_agent,
+ [otp16649, Target, Config]).
+
+otp16649_mgr_get_req(Node, Oids) ->
+ TargetName = "otp16649-agent-req",
+ rpc:call(Node, snmpm, sync_get2, [otp16649, TargetName, Oids]).
+
+otp16649_agent_load_mib(Node, MibDir, Mib) ->
+ rpc:call(Node, snmpa, unload_mib, [snmp_master_agent, Mib]), % For safety
+ MibPath = join(MibDir, Mib),
+ rpc:call(Node, snmpa, load_mib, [snmp_master_agent, MibPath]).
+
+otp16649_agent_send_trap(Node, Trap) ->
+ rpc:call(Node, snmpa, send_trap,
+ [snmp_master_agent, Trap, "standard trap"]).
+
+
+%%-----------------------------------------------------------------
+
agent_log_validation(Node) ->
rpc:call(Node, ?MODULE, agent_log_validation, []).
@@ -7402,7 +8254,7 @@ start_agent(Config, Opts) ->
process_flag(trap_exit, true),
- AgentTopSup = start_standalone_agent(AgentNode, AgentConfig),
+ ?line {ok, AgentTopSup} = start_standalone_agent(AgentNode, AgentConfig),
[{agent_sup, AgentTopSup} | Config].
@@ -7454,9 +8306,9 @@ start_standalone_agent(Config) ->
case snmpa_supervisor:start_link(normal, Config) of
{ok, AgentTopSup} ->
unlink(AgentTopSup),
- AgentTopSup;
+ {ok, AgentTopSup};
{error, {already_started, AgentTopSup}} ->
- AgentTopSup;
+ {ok, AgentTopSup};
{error, _} = ERROR ->
ERROR
end.
@@ -7480,6 +8332,31 @@ stop_standalone_agent(Pid) ->
nkill(Pid, kill).
+start_manager(Config) ->
+ Node = ?config(manager_node, Config),
+ ConfDir = ?config(manager_conf_dir, Config),
+ DbDir = ?config(manager_db_dir, Config),
+
+ Opts = [{server, [{verbosity, trace}]},
+ {net_if, [{verbosity, trace}]},
+ {note_store, [{verbosity, trace}]},
+ {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}],
+ ?line ok = start_standalone_manager(Node, Opts).
+
+
+start_standalone_manager(Node, Config) ->
+ rpc:call(Node, ?MODULE, start_standalone_manager, [Config]).
+
+start_standalone_manager(Config) ->
+ snmpm:start(Config).
+
+
+stop_standalone_manager(Node) when (Node =/= node()) ->
+ rpc:call(Node, snmpm, stop, []);
+stop_standalone_manager(_) ->
+ snmpm:stop().
+
+
nkill(Pid, Reason) ->
nkill(Pid, Reason, 10).
@@ -7517,7 +8394,7 @@ do_info(MaNode) ->
Keys = [vsns,
stats_counters,
{agent, [process_memory, db_memory]},
- {net_if, [process_memory, port_info, reqs]},
+ {net_if, [process_memory, transport_info, reqs]},
{note_store, [process_memory, db_memory]},
{symbolic_store, [process_memory, db_memory]},
{local_db, [process_memory, db_memory]},
@@ -7585,7 +8462,7 @@ try_test(Func, A, Opts) ->
?ALIB:try_test(?MODULE, Func, A, Opts).
-%% Test manager wrapperfunctions:
+%% Test manager wrapper functions:
g(Oids) -> snmp_test_mgr:g(Oids).
%%gn() -> snmp_test_mgr:gn().
gn(OidsOrN) -> snmp_test_mgr:gn(OidsOrN).
@@ -7952,5 +8829,3 @@ rcall(Node, Mod, Func, Args) ->
Else ->
Else
end.
-
-
diff --git a/lib/snmp/test/snmp_agent_mibs_SUITE.erl b/lib/snmp/test/snmp_agent_mibs_SUITE.erl
index 150e015554..ce6ec80322 100644
--- a/lib/snmp/test/snmp_agent_mibs_SUITE.erl
+++ b/lib/snmp/test/snmp_agent_mibs_SUITE.erl
@@ -172,29 +172,28 @@ init_per_testcase2(size_check_ets2_bad_file1, Config) when is_list(Config) ->
%% Create a bad file
ok = file:write_file(join(DbDir, "snmpa_symbolic_store.db"),
"calvin and hoppes play chess"),
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(1 + (Factor div 2))),
Config;
init_per_testcase2(size_check_ets3_bad_file1, Config) when is_list(Config) ->
DbDir = ?config(db_dir, Config),
%% Create a bad file
ok = file:write_file(join(DbDir, "snmpa_symbolic_store.db"),
"calvin and hoppes play chess"),
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(1 + (Factor div 2))),
Config;
init_per_testcase2(size_check_mnesia, Config) when is_list(Config) ->
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(1 + (Factor div 2))),
Config;
init_per_testcase2(cache_test, Config) when is_list(Config) ->
- Min = timer:minutes(5),
- Timeout =
- case lists:keysearch(tc_timeout, 1, Config) of
- {value, {tc_timeout, TcTimeout}} when TcTimeout < Min ->
- Min;
- {value, {tc_timeout, TcTimeout}} ->
- TcTimeout;
- _ ->
- Min
- end,
- Dog = test_server:timetrap(Timeout),
- [{watchdog, Dog} | Config];
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(10 + (Factor div 2))),
+ Config;
init_per_testcase2(_Case, Config) when is_list(Config) ->
+ Factor = ?config(snmp_factor, Config),
+ ct:timetrap(?MINS(1 + (Factor div 3))),
Config.
@@ -207,8 +206,6 @@ end_per_testcase1(size_check_mnesia, Config) when is_list(Config) ->
mnesia_stop(),
Config;
end_per_testcase1(cache_test, Config) when is_list(Config) ->
- Dog = ?config(watchdog, Config),
- test_server:timetrap_cancel(Dog),
Config;
end_per_testcase1(_Case, Config) when is_list(Config) ->
Config.
@@ -220,6 +217,10 @@ end_per_testcase1(_Case, Config) when is_list(Config) ->
start_and_stop(suite) -> [];
start_and_stop(Config) when is_list(Config) ->
+ tc_try(start_and_start,
+ fun() -> do_start_and_stop(Config) end).
+
+do_start_and_stop(_Config) ->
Prio = normal,
Verbosity = trace,
@@ -238,6 +239,10 @@ start_and_stop(Config) when is_list(Config) ->
load_unload(suite) -> [];
load_unload(Config) when is_list(Config) ->
+ tc_try(load_unload,
+ fun() -> do_load_unload(Config) end).
+
+do_load_unload(Config) ->
?DBG("load_unload -> start", []),
Prio = normal,
@@ -365,49 +370,8 @@ do_size_check(Name, Config) ->
do_size_check(Name, Init, Config).
do_size_check(Name, Init, Config) ->
- Pre = fun() ->
- {ok, Node} = ?ALIB:start_node(unique(Name)),
- ok = run_on(Node, Init),
- Node
- end,
- Case = fun(Node) ->
- monitor_node(Node, true),
- Pid = spawn_link(Node, fun() -> do_size_check(Config) end),
- receive
- {nodedown, Node} = N ->
- exit(N);
- {'EXIT', Pid, normal} ->
- monitor_node(Node, false),
- ok;
- {'EXIT', Pid, ok} ->
- monitor_node(Node, false),
- ok;
- {'EXIT', Pid, Reason} ->
- monitor_node(Node, false),
- exit(Reason)
- end
- end,
- Post = fun({Node, _}) ->
- ?STOP_NODE(Node)
- end,
- ?TC_TRY(Name, Pre, Case, Post).
+ tc_try(Name, Init, fun() -> do_size_check(Config) end).
-run_on(Node, F) when is_atom(Node) andalso is_function(F, 0) ->
- monitor_node(Node, true),
- Pid = spawn_link(Node, F),
- receive
- {nodedown, Node} = N ->
- exit(N);
- {'EXIT', Pid, normal} ->
- monitor_node(Node, false),
- ok;
- {'EXIT', Pid, Reason} ->
- monitor_node(Node, false),
- Reason
- end.
-
-unique(PreName) ->
- list_to_atom(?F("~w_~w", [PreName, erlang:system_time(millisecond)])).
do_size_check(Config) ->
?IPRINT("do_size_check -> start with"
@@ -465,6 +429,10 @@ do_size_check(Config) ->
me_lookup(suite) -> [];
me_lookup(Config) when is_list(Config) ->
+ tc_try(me_lookup,
+ fun() -> do_me_lookup(Config) end).
+
+do_me_lookup(Config) ->
Prio = normal,
Verbosity = trace,
MibDir = ?config(data_dir, Config),
@@ -518,6 +486,10 @@ me_lookup(Config) when is_list(Config) ->
which_mib(suite) -> [];
which_mib(Config) when is_list(Config) ->
+ tc_try(which_mib,
+ fun() -> do_which_mib(Config) end).
+
+do_which_mib(Config) ->
Prio = normal,
Verbosity = trace,
MibDir = ?config(data_dir, Config),
@@ -574,9 +546,14 @@ which_mib(Config) when is_list(Config) ->
cache_test(suite) -> [];
cache_test(Config) when is_list(Config) ->
- ?DBG("cache_test -> start", []),
+ tc_try(cache_test,
+ fun() -> do_cache_test(Config) end).
+
+do_cache_test(Config) ->
+ ?IPRINT("cache_test -> start"),
Prio = normal,
- Verbosity = trace,
+ %% Verbosity = trace,
+ Verbosity = info,
MibStorage = [{module, snmpa_mib_storage_ets}],
MibDir = ?config(data_dir, Config),
StdMibDir = filename:join(code:priv_dir(snmp), "mibs") ++ "/",
@@ -587,94 +564,306 @@ cache_test(Config) when is_list(Config) ->
"SNMP-MPD-MIB",
"SNMP-NOTIFICATION-MIB",
"SNMP-TARGET-MIB",
- %% "SNMP-USER-BASED-SM-MIB",
+ "SNMP-USER-BASED-SM-MIB",
"SNMP-VIEW-BASED-ACM-MIB",
"SNMPv2-MIB",
"SNMPv2-TC",
"SNMPv2-TM"],
- ?DBG("cache_test -> start symbolic store", []),
- ?line sym_start(Prio, MibStorage, Verbosity),
+ ?IPRINT("cache_test -> start symbolic store"),
+ ?line sym_start(Prio, MibStorage, silence), % Verbosity),
- ?DBG("cache_test -> start mib server", []),
- GcLimit = 2,
- Age = timer:seconds(10),
- CacheOpts = [{autogc, false}, {age, Age}, {gclimit, GcLimit}],
+ ?IPRINT("cache_test -> start mib server"),
+ GcLimit = 3,
+ Age = timer:seconds(10),
+ CacheOpts = [{autogc, false},
+ {age, Age},
+ {gclimit, GcLimit},
+ {gcverbose, true}],
?line MibsPid = mibs_start(Prio, MibStorage, [], Verbosity, CacheOpts),
- ?DBG("cache_test -> load mibs", []),
+ ?NPRINT("Info before load mibs: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> load mibs"),
?line load_mibs(MibsPid, MibDir, Mibs),
- ?DBG("cache_test -> load std mibs", []),
+
+ ?NPRINT("Info before load std mibs: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> load std mibs"),
?line load_mibs(MibsPid, StdMibDir, StdMibs),
- ?DBG("cache_test -> do a simple walk to populate the cache", []),
- ?line ok = walk(MibsPid),
-
- {ok, Sz1} = snmpa_mib:which_cache_size(MibsPid),
- ?DBG("cache_test -> Size1: ~p", [Sz1]),
+ ?NPRINT("Info (after mibs load but) before populate: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> populate the cache"),
+ ?line ok = populate(MibsPid),
- ?DBG("cache_test -> sleep 5 secs", []),
+ ?NPRINT("Info after populate: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ Sz1 = cache_sz_verify(1, MibsPid, any),
+
+ ?IPRINT("cache_test -> sleep 5 secs"),
?SLEEP(timer:seconds(5)),
- ?DBG("cache_test -> perform gc, expect nothing", []),
- {ok, 0} = snmpa_mib:gc_cache(MibsPid),
+ _ = cache_gc_verify(1, MibsPid),
+
+ ?NPRINT("Info after 5 sec sleep: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
- ?DBG("cache_test -> sleep 10 secs", []),
+ ?IPRINT("cache_test -> sleep 10 secs"),
?SLEEP(timer:seconds(10)),
- ?DBG("cache_test -> perform gc, expect GcLimit", []),
- GcLimit1 = GcLimit + 1,
- {ok, GcLimit1} = snmpa_mib:gc_cache(MibsPid, Age, GcLimit1),
+ ?NPRINT("Info after 10 sec sleep: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ GcLimit1 = cache_gc_verify(2, MibsPid, Age, GcLimit + 1),
+
+ Sz2 = cache_sz_verify(2, MibsPid, Sz1 - GcLimit1),
- Sz2 = Sz1 - GcLimit1,
- {ok, Sz2} = snmpa_mib:which_cache_size(MibsPid),
- ?DBG("cache_test -> Size2: ~p", [Sz2]),
- ?DBG("cache_test -> enable cache autogc", []),
+ ?IPRINT("cache_test -> subscribe to GC events"),
+ ?line ok = snmpa_mib:subscribe_gc_events(MibsPid),
+
+ ?IPRINT("cache_test -> enable cache autogc"),
?line ok = snmpa_mib:enable_cache_autogc(MibsPid),
- ?DBG("cache_test -> wait 65 seconds to allow gc to happen", []),
+ ?IPRINT("cache_test -> wait 65 seconds to allow gc to happen"),
?SLEEP(timer:seconds(65)),
- Sz3 = Sz2 - GcLimit,
- {ok, Sz3} = snmpa_mib:which_cache_size(MibsPid),
- ?DBG("cache_test -> Size3: ~p", [Sz3]),
- ?DBG("cache_test -> "
- "wait 2 minutes to allow gc to happen, expect empty cache", []),
+ ?NPRINT("Info after 65 sec sleep: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> [1] flush expected GC events"),
+ {NumEvents1, TotGC1} = cache_flush_gc_events(MibsPid),
+ ?IPRINT("cache_test -> GC events: "
+ "~n Number of Events: ~p"
+ "~n Total elements GCed: ~p", [NumEvents1, TotGC1]),
+
+ _ = cache_sz_verify(3, MibsPid, Sz2 - GcLimit),
+
+ ?IPRINT("cache_test -> "
+ "wait 2 minutes to allow gc to happen, expect empty cache"),
?SLEEP(timer:minutes(2)),
- {ok, 0} = snmpa_mib:which_cache_size(MibsPid),
- ?DBG("cache_test -> stop mib server", []),
+ ?NPRINT("Info after 2 min sleep: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ _ = cache_sz_verify(4, MibsPid, 0),
+
+
+ ?IPRINT("cache_test -> change gclimit to infinity"),
+ snmpa_mib:update_cache_gclimit(MibsPid, infinity),
+
+ ?IPRINT("cache_test -> change age to ~w mins", [3]),
+ snmpa_mib:update_cache_age(MibsPid, ?MINS(3)),
+
+ ?IPRINT("cache_test -> [2] flush expected GC events"),
+ {NumEvents2, TotGC2} = cache_flush_gc_events(MibsPid),
+ ?IPRINT("cache_test -> GC events: "
+ "~n Number of Events: ~p"
+ "~n Total elements GCed: ~p", [NumEvents2, TotGC2]),
+
+ ?IPRINT("cache_test -> populate the cache again"),
+ populate(MibsPid),
+
+ ?IPRINT("cache_test -> validate cache size"),
+ {ok, Sz4} = snmpa_mib:which_cache_size(MibsPid),
+ if (Sz4 > 0) ->
+ ?IPRINT("cache_test -> expected cache size: ~w > 0", [Sz4]);
+ true ->
+ ?EPRINT("cache_test -> cache *not* populated"),
+ ?FAIL(cache_not_populated)
+ end,
+
+ ?NPRINT("Info after poulated: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> wait 2 mins - before tuching some entries"),
+ ?SLEEP(?MINS(2)),
+
+ %% There should not be anything GC:ed
+
+ receive
+ {MibsPid, gc_result, {ok, NGC1}} ->
+ ?EPRINT("cache_test -> unexpected GC of ~w elements", [NGC1]),
+ exit({unexpected_gc_result, NGC1})
+ after 0 ->
+ ok
+ end,
+
+ ?IPRINT("cache_test -> touch some elements again (update the cache)"),
+ populate_lookup(MibsPid),
+
+ ?IPRINT("cache_test -> await partial GC"),
+ NumGC2 =
+ receive
+ {MibsPid, gc_result, {ok, NGC2}}
+ when (NGC2 > 0) andalso (Sz4 > NGC2) ->
+ ?NPRINT("cache_test -> "
+ "received partial GC result of ~w elements", [NGC2]),
+ NGC2
+ end,
+
+ ?NPRINT("Info after partial GC: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+
+ ?IPRINT("cache_test -> await final GC"),
+ receive
+ {MibsPid, gc_result, {ok, NGC3}}
+ when (NGC3 > 0) andalso ((Sz4 - NumGC2) =:= NGC3) ->
+ ?NPRINT("cache_test -> "
+ "received final GC result of ~w elements", [NGC3]),
+ NGC3;
+ Any ->
+ ?EPRINT("cache_test -> unexpected message: "
+ "~n ~p", [Any]),
+ ?FAIL({unexpected, Any})
+ end,
+
+ ?NPRINT("Info after final GC: "
+ "~n ~p", [snmpa_mib:info(MibsPid)]),
+
+ ?IPRINT("cache_test -> validate cache size (expect empty)"),
+ {ok, Sz5} = snmpa_mib:which_cache_size(MibsPid),
+ if (Sz5 =:= 0) ->
+ ?IPRINT("cache_test -> expected cache size: 0");
+ true ->
+ ?EPRINT("cache_test -> cache *not* empty (~w)", [Sz5]),
+ ?FAIL({cache_populated, Sz5})
+ end,
+
+
+ ?IPRINT("cache_test -> stop mib server"),
?line mibs_stop(MibsPid),
- ?DBG("cache_test -> stop symbolic store", []),
+ ?IPRINT("cache_test -> stop symbolic store"),
?line sym_stop(),
+
+ ?IPRINT("cache_test -> end"),
+ ok.
+
+populate(MibsPid) ->
+ %% Make some lookups
+ populate_lookup(MibsPid),
+ %% Make some walk's
+ populate_walk(MibsPid).
+
+populate_lookup(MibsPid) ->
+ {variable, _} = snmpa_mib:lookup(MibsPid, ?snmpTrapCommunity_instance),
+ {variable, _} = snmpa_mib:lookup(MibsPid, ?vacmViewSpinLock_instance),
+ {variable, _} = snmpa_mib:lookup(MibsPid, ?usmStatsNotInTimeWindows_instance),
+ {variable, _} = snmpa_mib:lookup(MibsPid, ?tDescr_instance),
ok.
-walk(MibsPid) ->
+populate_walk(MibsPid) ->
MibView = snmpa_acm:get_root_mib_view(),
- do_walk(MibsPid, ?snmpTrapCommunity_instance, MibView),
- do_walk(MibsPid, ?vacmViewSpinLock_instance, MibView),
- do_walk(MibsPid, ?usmStatsNotInTimeWindows_instance, MibView),
- do_walk(MibsPid, ?tDescr_instance, MibView).
-
+ walk(MibsPid, ?snmpTrapCommunity_instance, MibView),
+ walk(MibsPid, ?vacmViewSpinLock_instance, MibView),
+ walk(MibsPid, ?usmStatsNotInTimeWindows_instance, MibView),
+ walk(MibsPid, ?tDescr_instance, MibView),
+ ok.
+
+walk(MibsPid, Oid, MibView) ->
+ ?IPRINT("walk -> entry with"
+ "~n Oid: ~p", [Oid]),
+ do_walk(MibsPid, Oid, MibView).
do_walk(MibsPid, Oid, MibView) ->
- io:format("do_walk -> entry with"
- "~n Oid: ~p"
- "~n", [Oid]),
+ ?IPRINT("do_walk -> entry with"
+ "~n Oid: ~p"
+ "~n", [Oid]),
case snmpa_mib:next(MibsPid, Oid, MibView) of
{table, _, _, #me{oid = Oid}} ->
+ ?IPRINT("do_walk -> done for table (~p)", [Oid]),
ok;
{table, _, _, #me{oid = Next}} ->
+ ?IPRINT("do_walk -> table next ~p", [Next]),
do_walk(MibsPid, Next, MibView);
{variable, #me{oid = Oid}, _} ->
+ ?IPRINT("do_walk -> done for variable (~p)", [Oid]),
ok;
{variable, #me{oid = Next}, _} ->
+ ?IPRINT("do_walk -> variable next ~p", [Next]),
do_walk(MibsPid, Next, MibView)
end.
+cache_gc_verify(ID, MibsPid) ->
+ GC = fun() -> snmpa_mib:gc_cache(MibsPid) end,
+ cache_gc_verify(ID, GC, 0).
+
+cache_gc_verify(ID, MibsPid, Age, ExpectedGcLimit) ->
+ GC = fun() -> snmpa_mib:gc_cache(MibsPid, Age, ExpectedGcLimit) end,
+ cache_gc_verify(ID, GC, ExpectedGcLimit).
+
+cache_gc_verify(ID, GC, ExpectedGc) ->
+ ?IPRINT("cache_gc_verify -> [~w] perform gc, expect ~w", [ID, ExpectedGc]),
+ case GC() of
+ {ok, ExpectedGc} ->
+ ?IPRINT("cache_gc_verify -> [~w] gc => ok", [ID]),
+ ExpectedGc;
+ {ok, OtherGc} ->
+ ?IPRINT("cache_gc_verify -> [~w] invalid GC limit: "
+ "~n Expected: ~p"
+ "~n Got: ~p"
+ "~n ~p",
+ [ID, 0, OtherGc]),
+ exit({ID, invalid_gc_limit, {ExpectedGc, OtherGc}});
+ Unexpected ->
+ ?IPRINT("cache_gc_verify -> [~w] unexpected: "
+ "~n ~p",
+ [ID, Unexpected]),
+ exit({ID, unexpected, Unexpected})
+ end.
+
+
+cache_sz_verify(ID, MibsPid, ExpectedSz) ->
+ ?IPRINT("cache_sz_verify -> [~w] expect size ~w", [ID, ExpectedSz]),
+ case snmpa_mib:which_cache_size(MibsPid) of
+ {ok, ExpectedSz} ->
+ ?IPRINT("cache_sz_verify -> [~w] sz => ok", [ID]),
+ ExpectedSz;
+ {ok, UnexpectedSz} when (ExpectedSz =:= any) ->
+ ?IPRINT("cache_sz_verify -> [~w] sz => ok (~w)", [ID, UnexpectedSz]),
+ UnexpectedSz;
+ {ok, UnexpectedSz} ->
+ ?IPRINT("cache_sz_verify -> [~w] invalid size: "
+ "~n Expected: ~p"
+ "~n Got: ~p",
+ [ID, ExpectedSz, UnexpectedSz]),
+ exit({ID, invalid_size, {ExpectedSz, UnexpectedSz}});
+ Unexpected ->
+ ?IPRINT("cache_sz_verify -> [~w] unexpected: "
+ "~n ~p",
+ [ID, Unexpected]),
+ exit({ID, unexpected, Unexpected})
+ end.
+
+
+cache_flush_gc_events(MibServer) ->
+ cache_flush_gc_events(MibServer, 0, 0).
+
+cache_flush_gc_events(MibServer, NumEvents, TotGC) ->
+ receive
+ {MibServer, gc_result, {ok, NumGC}} ->
+ ?IPRINT("cache_flush_gc_events -> GC event ~w (~w)",
+ [NumGC, NumEvents]),
+ cache_flush_gc_events(MibServer, NumEvents+1, TotGC+NumGC)
+ after 0 ->
+ if
+ (NumEvents =:= 0) andalso (TotGC =:= 0) ->
+ ?IPRINT("cache_flush_gc_events -> no GC events"),
+ exit(no_gc_events);
+ true ->
+ {NumEvents, TotGC}
+ end
+ end.
+
+
%%======================================================================
%% Internal functions
%%======================================================================
@@ -897,6 +1086,65 @@ mib_storage() ->
[{module, snmpa_mib_storage_ets}].
+%% --
+
+tc_try(Name, TC) ->
+ tc_try(Name, fun() -> ok end, TC).
+
+tc_try(Name, Init, TC)
+ when is_atom(Name) andalso is_function(Init, 0) andalso is_function(TC, 0) ->
+ Pre = fun() ->
+ {ok, Node} = ?ALIB:start_node(unique(Name)),
+ ok = run_on(Node, Init),
+ Node
+ end,
+ Case = fun(Node) ->
+ monitor_node(Node, true),
+ Pid = spawn_link(Node, TC),
+ receive
+ {nodedown, Node} = N ->
+ exit(N);
+ {'EXIT', Pid, normal} ->
+ monitor_node(Node, false),
+ ok;
+ {'EXIT', Pid, ok} ->
+ monitor_node(Node, false),
+ ok;
+ {'EXIT', Pid, Reason} ->
+ monitor_node(Node, false),
+ exit(Reason)
+ end
+ end,
+ Post = fun(Node) ->
+ monitor_node(Node, true),
+ ?NPRINT("try stop node ~p", [Node]),
+ ?STOP_NODE(Node),
+ receive
+ {nodedown, Node} ->
+ ?NPRINT("node ~p stopped", [Node]),
+ ok
+ end
+ end,
+ ?TC_TRY(Name, Pre, Case, Post).
+
+run_on(Node, F) when is_atom(Node) andalso is_function(F, 0) ->
+ monitor_node(Node, true),
+ Pid = spawn_link(Node, F),
+ receive
+ {nodedown, Node} = N ->
+ exit(N);
+ {'EXIT', Pid, normal} ->
+ monitor_node(Node, false),
+ ok;
+ {'EXIT', Pid, Reason} ->
+ monitor_node(Node, false),
+ Reason
+ end.
+
+unique(PreName) ->
+ list_to_atom(?F("~w_~w", [PreName, erlang:system_time(millisecond)])).
+
+
%% --
display_memory_usage(MibsPid) ->
diff --git a/lib/snmp/test/snmp_agent_test_lib.erl b/lib/snmp/test/snmp_agent_test_lib.erl
index 6a4c582f36..96cc6add81 100644
--- a/lib/snmp/test/snmp_agent_test_lib.erl
+++ b/lib/snmp/test/snmp_agent_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@
start_v2_agent/1, start_v2_agent/2,
start_v3_agent/1, start_v3_agent/2,
start_bilingual_agent/1, start_bilingual_agent/2,
- start_mt_agent/1, start_mt_agent/2,
+ start_mt_agent/1, start_mt_agent/2, start_mt_agent/3,
stop_agent/1,
%% start_sup/0, stop_sup/2,
@@ -328,7 +328,7 @@ await_tc_runner_started(Runner, OldFlag) ->
{'EXIT', Runner, Reason} ->
?EPRINT("TC runner start failed: "
"~n ~p~n", [Reason]),
- exit({tx_runner_start_failed, Reason});
+ exit({tc_runner_start_failed, Reason});
{tc_runner_started, Runner} ->
?IPRINT("TC runner start acknowledged~n"),
ok
@@ -345,7 +345,13 @@ await_tc_runner_started(Runner, OldFlag) ->
await_tc_runner_done(Runner, OldFlag) ->
receive
- {'EXIT', Runner, Reason} ->
+ {'EXIT', Runner, {udp_error, _} = Reason} ->
+ ?EPRINT("TC runner failed with an udp error: "
+ "~n Reason: ~p"
+ "~n", [Reason]),
+ skip([{reason, Reason}]);
+
+ {'EXIT', Runner, Reason} ->
%% This is not a normal (tc) failure (that is the clause below).
%% Instead the tc runner process crashed, for some reason. So
%% check if have got any system events, and if so, skip.
@@ -353,8 +359,9 @@ await_tc_runner_done(Runner, OldFlag) ->
if
(SysEvs =:= []) ->
?EPRINT("TC runner failed: "
- "~n ~p~n", [Reason]),
- exit({tx_runner_failed, Reason});
+ "~n ~p"
+ "~n", [Reason]),
+ exit({tc_runner_failed, Reason});
true ->
?WPRINT("TC runner failed when we got system events: "
"~n Reason: ~p"
@@ -380,15 +387,29 @@ await_tc_runner_done(Runner, OldFlag) ->
unlink_and_flush_exit(Runner),
put(test_server_loc, Loc),
exit(Rn);
- {tc_runner_done, Runner, Ret, _Zed} ->
- ?DBG("call -> done:"
- "~n Ret: ~p"
- "~n Zed: ~p", [Ret, _Zed]),
+ {tc_runner_done, Runner, Ret, _Loc} ->
+ ?IPRINT("call -> done:"
+ "~n Ret: ~p"
+ "~n Loc: ~p", [Ret, _Loc]),
trap_exit(OldFlag),
unlink_and_flush_exit(Runner),
case Ret of
{error, Reason} ->
- exit(Reason);
+ %% Any failures while we have system events are skipped
+ SysEvs = snmp_test_global_sys_monitor:events(),
+ if
+ (SysEvs =:= []) ->
+ ?EPRINT("TC failure: "
+ "~n ~p"
+ "~n", [Reason]),
+ exit(Reason);
+ true ->
+ ?WPRINT("TC failure when we got system events: "
+ "~n Reason: ~p"
+ "~n Sys Events: ~p"
+ "~n", [Reason, SysEvs]),
+ skip([{reason, Reason}, {system_events, SysEvs}])
+ end;
{skip, Reason} ->
skip(Reason);
OK ->
@@ -467,52 +488,46 @@ tc_run(Mod, Func, Args, Opts) ->
"~n Community: ~p"
"~n StdM: ~p"
"~n", [M,Vsn,Dir,User,SecLevel,EngineID,CtxEngineID,Community,StdM]),
- case snmp_test_mgr:start([%% {agent, snmp_test_lib:hostname()},
- {packet_server_debug, true},
- {debug, false},
- {agent, get(master_host)},
- {ipfamily, get(ipfamily)},
- {agent_udp, 4000},
- {trap_udp, 5000},
- {recbuf, 65535},
- quiet,
- Vsn,
- {community, Community},
- {user, User},
- {sec_level, SecLevel},
- {engine_id, EngineID},
- {context_engine_id, CtxEngineID},
- {dir, Dir},
- {mibs, mibs(StdM, M)}]) of
+ case snmp_test_mgr:start_link([%% {agent, snmp_test_lib:hostname()},
+ {packet_server_debug, true},
+ {debug, false},
+ {agent, get(master_host)},
+ {ipfamily, get(ipfamily)},
+ {agent_udp, 4000},
+ %% <SEP-TRANSPORTS>
+ %% First port is used to request replies
+ %% Second port is used for traps sent
+ %% by the agent.
+ %% {agent_udp, {4000, 4001}},
+ %% </SEP-TRANSPORTS>
+ {trap_udp, 5000},
+ {recbuf, 65535},
+ quiet,
+ Vsn,
+ {community, Community},
+ {user, User},
+ {sec_level, SecLevel},
+ {engine_id, EngineID},
+ {context_engine_id, CtxEngineID},
+ {dir, Dir},
+ {mibs, mibs(StdM, M)}]) of
{ok, _Pid} ->
- case (catch apply(Mod, Func, Args)) of
- {'EXIT', {skip, Reason}} ->
- ?WPRINT("apply skip detected: "
- "~n ~p", [Reason]),
- (catch snmp_test_mgr:stop()),
- ?SKIP(Reason);
- {'EXIT', Reason} ->
- %% We have hosts (mostly *very* slooow VMs) that
- %% can timeout anything. Since we are basically
- %% testing communication, we therefor must check
- %% for system events at every failure. Grrr!
- SysEvs = snmp_test_global_sys_monitor:events(),
- (catch snmp_test_mgr:stop()),
- if
- (SysEvs =:= []) ->
- ?EPRINT("TC runner failed: "
- "~n ~p~n", [Reason]),
- ?FAIL({apply_failed, {Mod, Func, Args}, Reason});
- true ->
- ?WPRINT("apply exit catched when we got system events: "
- "~n Reason: ~p"
- "~n Sys Events: ~p"
- "~n", [Reason, SysEvs]),
- ?SKIP([{reason, Reason}, {system_events, SysEvs}])
- end;
- Res ->
+ try apply(Mod, Func, Args) of
+ Res ->
(catch snmp_test_mgr:stop()),
Res
+ catch
+ C:{skip, Reason} ->
+ ?WPRINT("apply (~w-) skip detected: "
+ "~n ~p", [C, Reason]),
+ (catch snmp_test_mgr:stop()),
+ ?SKIP(Reason);
+
+ throw:{error, Reason} ->
+ tc_run_skip_sheck(Mod, Func, Args, Reason, throw);
+
+ exit:Reason ->
+ tc_run_skip_sheck(Mod, Func, Args, Reason, exit)
end;
{error, Reason} ->
@@ -528,6 +543,28 @@ tc_run(Mod, Func, Args, Opts) ->
?line ?FAIL({mgr_start_failure, Err})
end.
+%% We have hosts (mostly *very* slooow VMs) that
+%% can timeout anything. Since we are basically
+%% testing communication, we therefor must check
+%% for system events at every failure. Grrr!
+tc_run_skip_sheck(Mod, Func, Args, Reason, Cat) ->
+ SysEvs = snmp_test_global_sys_monitor:events(),
+ (catch snmp_test_mgr:stop()),
+ if
+ (SysEvs =:= []) ->
+ ?EPRINT("TC runner (~w-) failed: "
+ "~n ~p~n", [Cat, Reason]),
+ ?FAIL({apply_failed, {Mod, Func, Args}, Reason});
+ true ->
+ ?WPRINT("apply (~w) catched "
+ "when we got system events: "
+ "~n Reason: ~p"
+ "~n Sys Events: ~p"
+ "~n", [Cat, Reason, SysEvs]),
+ ?SKIP([{category, Cat},
+ {reason, Reason}, {system_events, SysEvs}])
+ end.
+
%% ---------------------------------------------------------------
%% --- ---
@@ -560,12 +597,18 @@ start_bilingual_agent(Config, Opts)
when is_list(Config) andalso is_list(Opts) ->
start_agent(Config, [v1,v2], Opts).
-start_mt_agent(Config) when is_list(Config) ->
- start_agent(Config, [v2], [{multi_threaded, true}]).
-
-start_mt_agent(Config, Opts) when is_list(Config) andalso is_list(Opts) ->
- start_agent(Config, [v2], [{multi_threaded, true}|Opts]).
+start_mt_agent(Config) ->
+ start_mt_agent(Config, true, []).
+start_mt_agent(Config, MT) ->
+ start_mt_agent(Config, MT, []).
+
+start_mt_agent(Config, MT, Opts)
+ when is_list(Config) andalso
+ ((MT =:= true) orelse (MT =:= extended)) andalso
+ is_list(Opts) ->
+ start_agent(Config, [v2], [{multi_threaded, MT} | Opts]).
+
start_agent(Config, Vsns) ->
start_agent(Config, Vsns, []).
start_agent(Config, Vsns, Opts) ->
@@ -1577,14 +1620,28 @@ config(Vsns, MgrDir, AgentConfDir, MIp, AIp, IpFamily) ->
?line {Domain, ManagerAddr} =
case IpFamily of
inet6 ->
- Ipv6Domain = transportDomainUdpIpv6,
- AgentIpv6Addr = {AIp, 4000},
- ManagerIpv6Addr = {MIp, ?TRAP_UDP},
+ TransportDomain6 = transportDomainUdpIpv6,
+ AgentAddr6 = {AIp, 4000},
+ ManagerAddr6 = {MIp, ?TRAP_UDP},
+ ?line ok =
+ snmp_config:write_agent_snmp_files(
+ AgentConfDir, Vsns,
+ TransportDomain6, ManagerAddr6, AgentAddr6, "test"),
+ {TransportDomain6, ManagerAddr6};
+ inet ->
+ TransportDomain4 = transportDomainUdpIpv4,
+ AIp2 = maybe_fix_addr(AIp),
+ ManagerAddr4 = {MIp, ?TRAP_UDP},
+ %% AgentPreTransport =
+ %% [#{addr => {AIp2, 4000}, kind => req_responder},
+ %% #{addr => {AIp2, 4001}, kind => trap_sender}],
+ AgentPreTransport = [#{addr => {AIp2, 4000}}],
?line ok =
snmp_config:write_agent_snmp_files(
AgentConfDir, Vsns,
- Ipv6Domain, ManagerIpv6Addr, AgentIpv6Addr, "test"),
- {Ipv6Domain, ManagerIpv6Addr};
+ TransportDomain4, ManagerAddr4, AgentPreTransport,
+ "test"),
+ {TransportDomain4, ManagerAddr4};
_ ->
?line ok =
snmp_config:write_agent_snmp_files(
@@ -1607,6 +1664,12 @@ config(Vsns, MgrDir, AgentConfDir, MIp, AIp, IpFamily) ->
?line write_notify_conf(AgentConfDir),
ok.
+maybe_fix_addr(Addr) when is_list(Addr) ->
+ list_to_tuple(Addr);
+maybe_fix_addr(Addr) when is_tuple(Addr) ->
+ Addr.
+
+
delete_files(Config) ->
AgentDir = ?config(agent_dir, Config),
delete_files(AgentDir, [db, conf]).
diff --git a/lib/snmp/test/snmp_conf_SUITE.erl b/lib/snmp/test/snmp_conf_SUITE.erl
index 7d60485060..698cf2d6f3 100644
--- a/lib/snmp/test/snmp_conf_SUITE.erl
+++ b/lib/snmp/test/snmp_conf_SUITE.erl
@@ -28,6 +28,7 @@
%% Include files
%%----------------------------------------------------------------------
+-include_lib("kernel/include/file.hrl").
-include_lib("common_test/include/ct.hrl").
-include("snmp_test_lib.hrl").
@@ -60,7 +61,9 @@
check_timer/1,
read/1,
- read_files/1
+ read_files/1,
+
+ fd_leak_check/1
]).
@@ -85,7 +88,8 @@ all() ->
check_sec_model2,
check_sec_level,
check_timer,
- read, read_files
+ read, read_files,
+ fd_leak_check
].
groups() ->
@@ -109,10 +113,15 @@ init_per_suite(Config0) when is_list(Config0) ->
%% We need a monitor on this node also
snmp_test_sys_monitor:start(),
+ PrivDir = ?config(priv_dir, Config1),
+ PrivSubdir = filename:join(PrivDir, "snmp_conf_test"),
+ ok = filelib:ensure_dir(filename:join(PrivSubdir, "dummy")),
+ Config2 = [{priv_subdir, PrivSubdir} | Config1],
+
?IPRINT("init_per_suite -> end when"
- "~n Config: ~p", [Config1]),
+ "~n Config: ~p", [Config2]),
- Config1
+ Config2
end.
end_per_suite(Config0) when is_list(Config0) ->
@@ -144,6 +153,71 @@ end_per_group(_GroupName, Config) ->
%% -----
%%
+init_per_testcase(fd_leak_check = _Case, Config) when is_list(Config) ->
+ ?IPRINT("init_per_testcase -> entry with"
+ "~n Config: ~p", [Config]),
+
+ %% There are other ways to test this:
+ %% lsof (linux and maybe FreeBSD): lsof -p <pid>
+ %% os:cmd("lsof -p " ++ os:getpid() ++ " | grep -v COMMAND | wc -l").
+ %% fstat (FreeBSD, OpenBSD and maybe NetBSD): fstat -p <pid>
+ %% os:cmd("fstat -p " ++ os:getpid() ++ " | grep -v USER | wc -l").
+ %% But this (list:dir) is good enough...
+ case os:type() of
+ {unix, linux} ->
+ ?IPRINT("init_per_testcase -> linux: check proc fs"),
+ case file:read_file_info("/proc/" ++ os:getpid() ++ "/fd") of
+ {ok, #file_info{type = directory,
+ access = Access}}
+ when (Access =:= read) orelse
+ (Access =:= read_write) ->
+ ?IPRINT("init_per_testcase -> linux: usingh proc fs"),
+ [{num_open_fd, fun num_open_fd_using_list_dir/0} |
+ Config];
+ _ ->
+ {skip, "Not proc fd"}
+ end;
+ {unix, solaris} ->
+ %% Something strange happens when we use pfiles from within erlang,
+ %% so skip the test for now
+
+ %% For some reason even though 'which' exists (atleast in
+ %% a tcsh shell), it hangs when called via os:cmd/1.
+ %% And type produces results that is not so easy to
+ %% "analyze". So, we 'try it' and know that it starts
+ %% by writing the pid and program on the first line...
+
+ %% ?IPRINT("init_per_testcase -> solaris: check pfiles"),
+ %% PID = os:getpid(),
+ %% case string:find(os:cmd("pfiles -n " ++ PID), PID) of
+ %% nomatch ->
+ %% {skip, "pfiles not found"};
+ %% _ ->
+ %% ?IPRINT("init_per_testcase -> solaris: pfiles found"),
+ %% [{num_open_fd, fun num_open_fd_using_pfiles/0} |
+ %% Config]
+ %% end;
+ {skip, "pfiles not found"};
+
+ {unix, BSD} when (BSD =:= freebsd) orelse
+ (BSD =:= openbsd) orelse
+ (BSD =:= netbsd) ->
+ ?IPRINT("init_per_testcase -> ~w: check fstat", [BSD]),
+ case os:cmd("which fstat") of
+ [] ->
+ {skip, "fstat not found"};
+ _ ->
+ ?IPRINT("init_per_testcase -> ~w: fstat found", [BSD]),
+ [{num_open_fd, fun num_open_fd_using_fstat/0} |
+ Config]
+ end;
+ {win32, _} ->
+ ?IPRINT("init_per_testcase -> windows: no check"),
+ {skip, "Not implemented"};
+ _ ->
+ ?IPRINT("init_per_testcase -> no check"),
+ {skip, "Not implemented"}
+ end;
init_per_testcase(_Case, Config) when is_list(Config) ->
Config.
@@ -716,6 +790,103 @@ read_files(Config) when is_list(Config) ->
%%======================================================================
+
+%% Since we need something to read, we also write.
+%% And we can just as well check then (after write) too...
+fd_leak_check(suite) -> [];
+fd_leak_check(Config) when is_list(Config) ->
+ ?TC_TRY(fd_leak_check,
+ fun() -> ok end,
+ fun(_) -> do_fd_leak_check(Config) end,
+ fun(_) -> ok end).
+
+do_fd_leak_check(Config) ->
+ Dir = ?config(priv_subdir, Config),
+ NumOpenFD = ?config(num_open_fd, Config),
+
+ %% Create "some" config
+ ?IPRINT("do_fd_leak_check -> create some config"),
+ Entries = [
+ snmpa_conf:agent_entry(intAgentIpAddress, {0,0,0,0}),
+ snmpa_conf:agent_entry(intAgentUDPPort, 161),
+ snmpa_conf:agent_entry(snmpEngineMaxMessageSize, 484),
+ snmpa_conf:agent_entry(snmpEngineID, "fooBarEI")
+ ],
+
+ ?IPRINT("do_fd_leak_check -> get number of FD (before test)"),
+ NumFD01 = NumOpenFD(),
+ ?IPRINT("do_fd_leak_check -> before config write: ~w", [NumFD01]),
+
+ ?IPRINT("do_fd_leak_check -> write config to file"),
+ ok = snmpa_conf:write_agent_config(Dir, Entries),
+
+ NumFD02 = NumOpenFD(),
+ ?IPRINT("do_fd_leak_check -> (after write) before config read: ~w", [NumFD02]),
+
+ {ok, _} = snmpa_conf:read_agent_config(Dir),
+
+
+ NumFD03 = NumOpenFD(),
+ ?IPRINT("do_fd_leak_check -> after config read: ~w", [NumFD03]),
+ if
+ (NumFD01 =:= NumFD02) andalso (NumFD02 =:= NumFD03) ->
+ ?IPRINT("do_fd_leak_check -> fd leak check ok"),
+ ok;
+ true ->
+ ?EPRINT("do_fd_leak_check -> fd leak check failed: "
+ "~n Before write: ~w"
+ "~n Before read: ~w"
+ "~n After read: ~w", [NumFD01, NumFD02, NumFD03]),
+ ?FAIL({num_open_fd, NumFD01, NumFD02, NumFD03})
+ end.
+
+
+
+%% There are other ways to test this:
+%% lsof (linux and maybe FreeBSD): lsof -p <pid>
+%% os:cmd("lsof -p " ++ os:getpid() ++ " | grep -v COMMAND | wc -l").
+%% fstat (FreeBSD and maybe OpenBSD): fstat -p <pid>
+%% os:cmd("fstat -p " ++ os:getpid() ++ " | grep -v USER | wc -l").
+%% But this is good enough...
+num_open_fd_using_list_dir() ->
+ case file:list_dir("/proc/" ++ os:getpid() ++ "/fd") of
+ {ok, FDs} ->
+ length(FDs);
+ {error, Reason} ->
+ ?EPRINT("Failed listing proc fs (for fd): "
+ "~n Reason: ~p", [Reason]),
+ ?SKIP({failed_listing_fd, Reason})
+ end.
+
+
+%% num_open_fd_using_pfiles() ->
+%% NumString = os:cmd("pfiles -n " ++ os:getpid() ++
+%% " | grep -v " ++ os:getpid() ++
+%% " | grep -v \"Current rlimit\" | wc -l"),
+%% try list_to_integer(string:trim(NumString))
+%% catch
+%% C:E ->
+%% ?EPRINT("Failed pfiles: "
+%% "~n Error Class: ~p"
+%% "~n Error: ~p", [C, E]),
+%% ?SKIP({failed_pfiles, C, E})
+%% end.
+
+
+num_open_fd_using_fstat() ->
+ NumString = os:cmd("fstat -p " ++ os:getpid() ++
+ " | grep -v MOUNT | wc -l"),
+ try list_to_integer(string:trim(NumString))
+ catch
+ C:E ->
+ ?EPRINT("Failed fstat: "
+ "~n Error Class: ~p"
+ "~n Error: ~p", [C, E]),
+ ?SKIP({failed_fstat, C, E})
+ end.
+
+
+%%======================================================================
%% Internal functions
%%======================================================================
diff --git a/lib/snmp/test/snmp_manager_SUITE.erl b/lib/snmp/test/snmp_manager_SUITE.erl
index 9d8e2efacf..6cc84d1e35 100644
--- a/lib/snmp/test/snmp_manager_SUITE.erl
+++ b/lib/snmp/test/snmp_manager_SUITE.erl
@@ -66,35 +66,26 @@
register_agent3/1,
info/1,
+ usm_priv_aes/1,
- simple_sync_get2/1,
simple_sync_get3/1,
- simple_async_get2/1,
simple_async_get3/1,
- simple_sync_get_next2/1,
simple_sync_get_next3/1,
- simple_async_get_next2/1,
simple_async_get_next3_cbp_def/1,
simple_async_get_next3_cbp_temp/1,
simple_async_get_next3_cbp_perm/1,
- simple_sync_set2/1,
simple_sync_set3/1,
- simple_async_set2/1,
simple_async_set3_cbp_def/1,
simple_async_set3_cbp_temp/1,
simple_async_set3_cbp_perm/1,
- simple_sync_get_bulk2/1,
simple_sync_get_bulk3/1,
- simple_async_get_bulk2/1,
simple_async_get_bulk3_cbp_def/1,
simple_async_get_bulk3_cbp_temp/1,
simple_async_get_bulk3_cbp_perm/1,
- misc_async2/1,
-
discovery/1,
trap1/1,
@@ -184,7 +175,8 @@ groups() ->
},
{misc_tests, [],
[
- info
+ info,
+ usm_priv_aes
]
},
{user_tests, [],
@@ -204,8 +196,7 @@ groups() ->
{group, get_tests},
{group, get_next_tests},
{group, set_tests},
- {group, bulk_tests},
- {group, misc_request_tests}
+ {group, bulk_tests}
]
},
{request_tests_mt, [],
@@ -213,23 +204,18 @@ groups() ->
{group, get_tests},
{group, get_next_tests},
{group, set_tests},
- {group, bulk_tests},
- {group, misc_request_tests}
+ {group, bulk_tests}
]
},
{get_tests, [],
[
- simple_sync_get2,
simple_sync_get3,
- simple_async_get2,
simple_async_get3
]
},
{get_next_tests, [],
[
- simple_sync_get_next2,
simple_sync_get_next3,
- simple_async_get_next2,
simple_async_get_next3_cbp_def,
simple_async_get_next3_cbp_temp,
simple_async_get_next3_cbp_perm
@@ -237,9 +223,7 @@ groups() ->
},
{set_tests, [],
[
- simple_sync_set2,
simple_sync_set3,
- simple_async_set2,
simple_async_set3_cbp_def,
simple_async_set3_cbp_temp,
simple_async_set3_cbp_perm
@@ -247,19 +231,12 @@ groups() ->
},
{bulk_tests, [],
[
- simple_sync_get_bulk2,
simple_sync_get_bulk3,
- simple_async_get_bulk2,
simple_async_get_bulk3_cbp_def,
simple_async_get_bulk3_cbp_temp,
simple_async_get_bulk3_cbp_perm
]
},
- {misc_request_tests, [],
- [
- misc_async2
- ]
- },
{event_tests, [],
[
trap1,
@@ -313,16 +290,11 @@ ipv6_tests() ->
[
register_agent_old,
simple_sync_get_next3,
- simple_async_get2,
simple_sync_get3,
- simple_async_get_next2,
simple_sync_set3,
- simple_async_set2,
- simple_sync_get_bulk2,
simple_async_get_bulk3_cbp_def,
simple_async_get_bulk3_cbp_temp,
simple_async_get_bulk3_cbp_perm,
- misc_async2,
inform1,
inform_swarm_cbp_def,
inform_swarm_cbp_temp,
@@ -527,18 +499,16 @@ init_per_testcase2(Case, Config) ->
Family = proplists:get_value(ipfamily, Config, inet),
+ Factor = ?config(snmp_factor, Config),
TO = case Case of
inform3 ->
- ?MINS(2);
+ ?MINS(2 + (Factor div 2));
InformSwarm when (InformSwarm =:= inform_swarm_cbp_def) orelse
(InformSwarm =:= inform_swarm_cbp_temp) orelse
(InformSwarm =:= inform_swarm_cbp_perm) ->
- case ?config(snmp_factor, Config) of
- N when is_integer(N) -> ?MINS(2*N);
- _ -> ?MINS(2)
- end;
+ ?MINS(1 + Factor);
_ ->
- ?MINS(1)
+ ?MINS(1 + (Factor div 2))
end,
?IPRINT("Set test case timetrap: ~p", [TO]),
ct:timetrap(TO),
@@ -562,15 +532,6 @@ init_per_testcase2(Case, Config) ->
init_per_testcase3(Case, Config) ->
ApiCases02 =
[
- simple_sync_get2,
- simple_async_get2,
- simple_sync_get_next2,
- simple_async_get_next2,
- simple_sync_set2,
- simple_async_set2,
- simple_sync_get_bulk2,
- simple_async_get_bulk2,
- misc_async2,
otp8395_1
],
ApiCases03 =
@@ -700,15 +661,6 @@ end_per_testcase(Case, Config) when is_list(Config) ->
end_per_testcase2(Case, Config) ->
ApiCases02 =
[
- simple_sync_get2,
- simple_async_get2,
- simple_sync_get_next2,
- simple_async_get_next2,
- simple_sync_set2,
- simple_async_set2,
- simple_sync_get_bulk2,
- simple_async_get_bulk2,
- misc_async2,
otp8395_1
],
ApiCases03 =
@@ -1157,20 +1109,15 @@ notify_started02(Config) when is_list(Config) ->
notify_started02_cond(Config) ->
LinuxVersionVerify =
fun() ->
- case os:cmd("uname -m") of
- "i686" ++ _ ->
- case os:version() of
- {2, 6, Rev} when Rev >= 16 ->
- false;
- {2, Min, _} when Min > 6 ->
- false;
- {Maj, _, _} when Maj > 2 ->
- false;
- _ ->
- true
- end;
- _ ->
- false
+ case os:version() of
+ V when V > {2, 6, 16} ->
+ ?IPRINT("(Linux) kernel version check: "
+ "~p > {2, 6, 16} => *NO* SKIP", [V]),
+ false;
+ V ->
+ ?IPRINT("(Linux) kernel version check: "
+ "~p =< {2, 6, 16} => *SKIP*", [V]),
+ true
end
end,
Skippable = [{unix, [{linux, LinuxVersionVerify}]}],
@@ -1455,6 +1402,165 @@ verify_info([{Key, SubKeys}|Keys], Info) ->
%%======================================================================
+%% USM privacy fails with AES in OTP 22.2.3. Test to prevent
+%% regression in future releases.
+%%
+usm_priv_aes(suite) -> [];
+usm_priv_aes(Config) when is_list(Config) ->
+ Pre = fun() ->
+ ConfDir = ?config(manager_conf_dir, Config),
+ DbDir = ?config(manager_db_dir, Config),
+
+ write_manager_conf(ConfDir),
+
+ Opts = [{server, [{verbosity, trace}]},
+ {net_if, [{verbosity, trace}]},
+ {note_store, [{verbosity, trace}]},
+ {config, [{verbosity, trace},
+ {dir, ConfDir},
+ {db_dir, DbDir}]}],
+
+ io:format("[~s] try starting manager", [?FTS()]),
+ ok = snmpm:start(Opts),
+ ?SLEEP(1000), % Give it time to settle
+ ok
+ end,
+ Case = fun(_) -> do_usm_priv_aes(Config) end,
+ Post = fun(_) ->
+ io:format("[~s] try stop manager", [?FTS()]),
+ ok = snmpm:stop(),
+ ?SLEEP(1000), % Give it time to settle
+ ok
+ end,
+ ?TC_TRY(usm_priv_aes, Pre, Case, Post).
+
+do_usm_priv_aes(Config) ->
+ io:format("[~s] starting with Config: "
+ "~n ~p", [?FTS(), Config]),
+
+ io:format("[~s] generate AES-encrypted message", [?FTS()]),
+
+ EngineID = [128,0,0,0,6],
+ SecName = "v3_user",
+ AuthPass = "authpass",
+ AuthKey =
+ snmp:passwd2localized_key(sha, AuthPass, EngineID),
+ PrivPass = "privpass",
+ PrivKey =
+ snmp:passwd2localized_key(md5, PrivPass, EngineID),
+
+ Credentials =
+ [ {auth, usmHMACSHAAuthProtocol},
+ {auth_key, AuthKey},
+ {priv, usmAesCfb128Protocol},
+ {priv_key, PrivKey}
+ ],
+
+ AgentConfig =
+ [ {engine_id, EngineID},
+ {address, {192,0,2,1}},
+ {version, v3},
+ {sec_model, usm},
+ {sec_level, authPriv},
+ {sec_name, SecName}
+ ],
+
+ snmpm:register_user(SecName, snmpm_user_default, nil),
+ snmpm:register_usm_user(EngineID, SecName, Credentials),
+ snmpm:register_agent(SecName, "v3_agent", AgentConfig),
+
+ PduType = 'get-request',
+ ScopedPDU =
+ { scopedPdu,
+ "", % CtxEngineID
+ "", % Context
+ { pdu,
+ PduType,
+ 0, % RequestID
+ noError, % ErrorStatus
+ 0, % ErrorIndex
+ [ {varbind, [1,3,6,1,2,1,1,5,0], 'OCTET STRING', [], 0}
+ ]
+ }
+ },
+
+ MsgSecurityParameters =
+ { usmSecurityParameters,
+ _MsgAuthoritativeEngineID = EngineID,
+ _MsgAuthoritativeEngineBoots = 1,
+ _MsgAuthoritativeEngineTime = 0,
+ _MsgUserName = SecName,
+ _MsgAuthenticationParameters = AuthKey,
+ _MsgPrivacyParameters = PrivKey
+ },
+
+ {ok, MsgMaxSize} =
+ snmpm_config:get_engine_max_message_size(),
+
+ Message =
+ { message,
+ _Version = 'version-3',
+ { v3_hdr,
+ _MsgID = 1,
+ MsgMaxSize,
+ _MsgFlags = snmp_misc:mk_msg_flags(PduType, 2),
+ _MsgSecurityModel = 3, % SEC_USM
+ MsgSecurityParameters,
+ 0
+ },
+ Data = snmp_pdus:enc_scoped_pdu(ScopedPDU)
+ },
+
+ {_, CredVals} = lists:unzip(Credentials),
+
+ SecLevel = 2,
+
+ Msg =
+ snmpm_usm:generate_outgoing_msg(
+ Message,
+ EngineID,
+ SecName,
+ list_to_tuple([SecName|CredVals]),
+ SecLevel
+ ),
+
+ io:format("[~s] got AES-encrypted message, now decrypt: "
+ "~n ~p", [?FTS(), Msg]),
+
+ {message, _Version, Hdr, NextData} =
+ snmp_pdus:dec_message_only(Msg),
+
+ { v3_hdr,
+ _MsgID,
+ _MsgMaxSize,
+ _MsgFlags,
+ _SecModel,
+ SecParams,
+ _Hdr_size
+ } = Hdr,
+
+ { ok,
+ { _MsgAuthEngineID,
+ _SecName,
+ ScopedPDUBytes,
+ _CachedSecData
+ }
+ } =
+ snmpm_usm:process_incoming_msg(
+ Msg,
+ NextData,
+ SecParams,
+ SecLevel
+ ),
+
+ Data = ScopedPDUBytes,
+
+ io:format("[~s] Message decrypted", [?FTS()]),
+ ok.
+
+
+%%======================================================================
+
register_user1(suite) -> [];
register_user1(Config) when is_list(Config) ->
Pre = fun() ->
@@ -1981,25 +2087,40 @@ do_register_agent3([ManagerNode], Config) ->
%%======================================================================
-simple_sync_get2(doc) ->
- ["Simple sync get-request - Version 2 API (TargetName)"];
-simple_sync_get2(suite) -> [];
-simple_sync_get2(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get2,
- fun() -> do_simple_sync_get2(Config) end).
+simple_sync_get3(doc) ->
+ ["Simple sync get-request - Version 3 API (TargetName and send-opts)"];
+simple_sync_get3(suite) -> [];
+simple_sync_get3(Config) when is_list(Config) ->
+ ?TC_TRY(simple_sync_get3,
+ fun() -> do_simple_sync_get3(Config) end).
-do_simple_sync_get2(Config) ->
+do_simple_sync_get3(Config) ->
?IPRINT("starting with Config: "
"~n ~p", [Config]),
+ Self = self(),
+ Msg = simple_sync_get3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
Get = fun(Node, TargetName, Oids) ->
- mgr_user_sync_get(Node, TargetName, Oids)
- end,
- PostVerify = fun() -> ok end,
- Res = do_simple_sync_get2(Config, Get, PostVerify),
+ mgr_user_sync_get2(Node, TargetName, Oids, SendOpts)
+ end,
+ PostVerify =
+ fun() ->
+ receive
+ Msg ->
+ ok
+ end
+ end,
+ Res = do_simple_sync_get3(Config, Get, PostVerify),
display_log(Config),
Res.
-do_simple_sync_get2(Config, Get, PostVerify) ->
+
+do_simple_sync_get3(Config, Get, PostVerify) ->
?IPRINT("starting with Config: "
"~n ~p", [Config]),
@@ -2008,15 +2129,15 @@ do_simple_sync_get2(Config, Get, PostVerify) ->
?IPRINT("issue get-request without loading the mib"),
Oids1 = [?sysObjectID_instance, ?sysDescr_instance, ?sysUpTime_instance],
- ?line ok = do_simple_sync_get2(Node, TargetName, Oids1, Get, PostVerify),
+ ?line ok = do_simple_sync_get3(Node, TargetName, Oids1, Get, PostVerify),
?IPRINT("issue get-request after first loading the mibs"),
?line ok = mgr_user_load_mib(Node, std_mib()),
Oids2 = [[sysObjectID, 0], [sysDescr, 0], [sysUpTime, 0]],
- ?line ok = do_simple_sync_get2(Node, TargetName, Oids2, Get, PostVerify),
+ ?line ok = do_simple_sync_get3(Node, TargetName, Oids2, Get, PostVerify),
ok.
-do_simple_sync_get2(Node, TargetName, Oids, Get, PostVerify)
+do_simple_sync_get3(Node, TargetName, Oids, Get, PostVerify)
when is_function(Get, 3) andalso is_function(PostVerify, 0) ->
?line {ok, Reply, _Rem} = Get(Node, TargetName, Oids),
@@ -2052,43 +2173,6 @@ do_simple_sync_get2(Node, TargetName, Oids, Get, PostVerify)
%%======================================================================
-simple_sync_get3(doc) ->
- ["Simple sync get-request - Version 3 API (TargetName and send-opts)"];
-simple_sync_get3(suite) -> [];
-simple_sync_get3(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get3,
- fun() -> do_simple_sync_get3(Config) end).
-
-do_simple_sync_get3(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
- Self = self(),
- Msg = simple_sync_get3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
- Get = fun(Node, TargetName, Oids) ->
- mgr_user_sync_get2(Node, TargetName, Oids, SendOpts)
- end,
- PostVerify =
- fun() ->
- receive
- Msg ->
- ok
- end
- end,
- Res = do_simple_sync_get2(Config, Get, PostVerify),
- display_log(Config),
- Res.
-
-
-
-
-%%======================================================================
-
sag_verify({noError, 0, _Vbs}, any) ->
?IPRINT("verified [any]"),
ok;
@@ -2122,35 +2206,46 @@ sag_verify_vbs([Vb|_], [E|_]) ->
%%======================================================================
-simple_async_get2(doc) ->
- ["Simple (async) get-request - Version 2 API (TargetName)"];
-simple_async_get2(suite) -> [];
-simple_async_get2(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get2,
- fun() -> do_simple_async_get2(Config) end).
+simple_async_get3(doc) ->
+ ["Simple (async) get-request - Version 3 API (TargetName and send-opts)"];
+simple_async_get3(suite) -> [];
+simple_async_get3(Config) when is_list(Config) ->
+ ?TC_TRY(simple_async_get3,
+ fun() -> do_simple_async_get3(Config) end).
-do_simple_async_get2(Config) ->
+do_simple_async_get3(Config) ->
?IPRINT("starting with Config: "
"~n ~p", [Config]),
MgrNode = ?config(manager_node, Config),
AgentNode = ?config(agent_node, Config),
TargetName = ?config(manager_agent_target_name, Config),
- Get = fun(Oids) -> async_g_exec2(MgrNode, TargetName, Oids) end,
- PostVerify = fun(Res) -> Res end,
- do_simple_async_sync_get2(Config, MgrNode, AgentNode, Get, PostVerify),
+ Self = self(),
+ Msg = simple_async_get3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+ Get = fun(Oids) -> async_g_exec3(MgrNode, TargetName, Oids, SendOpts) end,
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Error) -> Error
+ end,
+ Res = do_simple_async_sync_get3(Config, MgrNode, AgentNode,
+ Get, PostVerify),
display_log(Config),
- ok.
+ Res.
-do_simple_async_sync_get2(Config, MgrNode, AgentNode, Get, PostVerify) ->
+do_simple_async_sync_get3(Config, MgrNode, AgentNode, Get, PostVerify) ->
?line ok = mgr_user_load_mib(MgrNode, std_mib()),
Test2Mib = test2_mib(Config),
?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
?line ok = agent_load_mib(AgentNode, Test2Mib),
- do_simple_async_sync_get2(fun() -> mgr_info(MgrNode) end,
+ do_simple_async_sync_get3(fun() -> mgr_info(MgrNode) end,
fun() -> agent_info(AgentNode) end,
Get, PostVerify).
-do_simple_async_sync_get2(MgrInfo, AgentInfo, Get, PostVerify)
+do_simple_async_sync_get3(MgrInfo, AgentInfo, Get, PostVerify)
when is_function(MgrInfo, 0) andalso
is_function(AgentInfo, 0) andalso
is_function(Get, 1) andalso
@@ -2204,41 +2299,6 @@ do_simple_async_sync_get2(MgrInfo, AgentInfo, Get, PostVerify)
ok.
-async_g_exec2(Node, TargetName, Oids) ->
- mgr_user_async_get(Node, TargetName, Oids).
-
-
-%%======================================================================
-
-simple_async_get3(doc) ->
- ["Simple (async) get-request - Version 3 API (TargetName and send-opts)"];
-simple_async_get3(suite) -> [];
-simple_async_get3(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get3,
- fun() -> do_simple_async_get3(Config) end).
-
-do_simple_async_get3(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
- Self = self(),
- Msg = simple_async_get3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
- Get = fun(Oids) -> async_g_exec3(MgrNode, TargetName, Oids, SendOpts) end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Error) -> Error
- end,
- Res = do_simple_async_sync_get2(Config, MgrNode, AgentNode, Get, PostVerify),
- display_log(Config),
- Res.
-
async_g_exec3(Node, TargetName, Oids, SendOpts) ->
mgr_user_async_get2(Node, TargetName, Oids, SendOpts).
@@ -2279,27 +2339,35 @@ check_ssgn_vbs([Vb|_], [E|_]) ->
%%======================================================================
-simple_sync_get_next2(doc) ->
- ["Simple (sync) get_next-request - Version 2 API (TargetName)"];
-simple_sync_get_next2(suite) -> [];
-simple_sync_get_next2(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get_next2,
- fun() -> do_simple_sync_get_next2(Config) end).
-
-do_simple_sync_get_next2(Config) ->
+simple_sync_get_next3(doc) ->
+ ["Simple (sync) get_next-request - "
+ "Version 3 API (TargetName with send-opts)"];
+simple_sync_get_next3(suite) -> [];
+simple_sync_get_next3(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ put(tname, ssgn3),
?IPRINT("starting with Config: "
"~n ~p", [Config]),
-
+ Self = self(),
+ Msg = simple_sync_get_next3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
GetNext = fun(Node, TargetName, Oids) ->
- mgr_user_sync_get_next(Node, TargetName, Oids)
+ mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
end,
- PostVerify = fun(Res) -> Res end,
- Res = do_simple_sync_get_next2(Config, GetNext, PostVerify),
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Error) -> Error
+ end,
+ do_simple_sync_get_next3(Config, GetNext, PostVerify),
display_log(Config),
- Res.
+ ok.
-do_simple_sync_get_next2(Config, GetNext, PostVerify)
+do_simple_sync_get_next3(Config, GetNext, PostVerify)
when is_function(GetNext, 3) andalso is_function(PostVerify, 1) ->
MgrNode = ?config(manager_node, Config),
@@ -2389,7 +2457,6 @@ do_simple_sync_get_next2(Config, GetNext, PostVerify)
GetNext, PostVerify),
ok.
-
do_simple_get_next(N, Node, TargetName, Oids, Verify, GetNext, PostVerify) ->
?IPRINT("issue get-next command ~w", [N]),
case GetNext(Node, TargetName, Oids) of
@@ -2404,46 +2471,36 @@ do_simple_get_next(N, Node, TargetName, Oids, Verify, GetNext, PostVerify) ->
end.
+
%%======================================================================
-simple_sync_get_next3(doc) ->
- ["Simple (sync) get_next-request - "
+simple_async_get_next3_cbp_def(doc) ->
+ ["Simple (async) get_next-request - "
"Version 3 API (TargetName with send-opts)"];
-simple_sync_get_next3(suite) -> [];
-simple_sync_get_next3(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
- put(tname, ssgn3),
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
- Self = self(),
- Msg = simple_sync_get_next3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
- GetNext = fun(Node, TargetName, Oids) ->
- mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
- end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Error) -> Error
- end,
- do_simple_sync_get_next2(Config, GetNext, PostVerify),
- display_log(Config),
- ok.
+simple_async_get_next3_cbp_def(suite) -> [];
+simple_async_get_next3_cbp_def(Config) when is_list(Config) ->
+ simple_async_get_next3(ssgn2_cbp_def, Config).
+simple_async_get_next3_cbp_temp(doc) ->
+ ["Simple (async) get_next-request - "
+ "Version 3 API (TargetName with send-opts)"];
+simple_async_get_next3_cbp_temp(suite) -> [];
+simple_async_get_next3_cbp_temp(Config) when is_list(Config) ->
+ simple_async_get_next3(ssgn2_cbp_temp, Config).
-%%======================================================================
+simple_async_get_next3_cbp_perm(doc) ->
+ ["Simple (async) get_next-request - "
+ "Version 3 API (TargetName with send-opts)"];
+simple_async_get_next3_cbp_perm(suite) -> [];
+simple_async_get_next3_cbp_perm(Config) when is_list(Config) ->
+ simple_async_get_next3(ssgn2_cbp_perm, Config).
-simple_async_get_next2(doc) ->
- ["Simple (async) get_next-request - Version 2 API (TargetName)"];
-simple_async_get_next2(suite) -> [];
-simple_async_get_next2(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get_next2,
- fun() -> do_simple_async_get_next2(Config) end).
+simple_async_get_next3(Case, Config) when is_list(Config) ->
+ ?TC_TRY(Case,
+ fun() -> do_simple_async_get_next3(Config) end).
-do_simple_async_get_next2(Config) ->
+do_simple_async_get_next3(Config) ->
+ %% process_flag(trap_exit, true),
?IPRINT("starting with Config: "
"~n ~p", [Config]),
@@ -2455,15 +2512,28 @@ do_simple_async_get_next2(Config) ->
Test2Mib = test2_mib(Config),
?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
?line ok = agent_load_mib(AgentNode, Test2Mib),
+
+ Self = self(),
+ Msg = simple_async_get_next3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+
GetNext = fun(Oids) ->
- async_gn_exec2(MgrNode, TargetName, Oids)
+ async_gn_exec3(MgrNode, TargetName, Oids, SendOpts)
end,
- PostVerify = fun(Res) -> Res end,
- Res = do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify),
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Error) -> Error
+ end,
+
+ Res = do_simple_async_get_next3(MgrNode, AgentNode, GetNext, PostVerify),
display_log(Config),
Res.
-do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify)
+do_simple_async_get_next3(MgrNode, AgentNode, GetNext, PostVerify)
when is_function(GetNext, 1) andalso is_function(PostVerify, 1) ->
?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2),
?line {ok, [TGenErr1|_]} = mgr_user_name_to_oid(MgrNode, tGenErr1),
@@ -2547,71 +2617,6 @@ do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify)
ok.
-async_gn_exec2(Node, TargetName, Oids) ->
- mgr_user_async_get_next(Node, TargetName, Oids).
-
-
-%%======================================================================
-
-simple_async_get_next3_cbp_def(doc) ->
- ["Simple (async) get_next-request - "
- "Version 3 API (TargetName with send-opts)"];
-simple_async_get_next3_cbp_def(suite) -> [];
-simple_async_get_next3_cbp_def(Config) when is_list(Config) ->
- simple_async_get_next3(ssgn2_cbp_def, Config).
-
-simple_async_get_next3_cbp_temp(doc) ->
- ["Simple (async) get_next-request - "
- "Version 3 API (TargetName with send-opts)"];
-simple_async_get_next3_cbp_temp(suite) -> [];
-simple_async_get_next3_cbp_temp(Config) when is_list(Config) ->
- simple_async_get_next3(ssgn2_cbp_temp, Config).
-
-simple_async_get_next3_cbp_perm(doc) ->
- ["Simple (async) get_next-request - "
- "Version 3 API (TargetName with send-opts)"];
-simple_async_get_next3_cbp_perm(suite) -> [];
-simple_async_get_next3_cbp_perm(Config) when is_list(Config) ->
- simple_async_get_next3(ssgn2_cbp_perm, Config).
-
-simple_async_get_next3(Case, Config) when is_list(Config) ->
- ?TC_TRY(Case,
- fun() -> do_simple_async_get_next3(Config) end).
-
-do_simple_async_get_next3(Config) ->
- %% process_flag(trap_exit, true),
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Self = self(),
- Msg = simple_async_get_next3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
-
- GetNext = fun(Oids) ->
- async_gn_exec3(MgrNode, TargetName, Oids, SendOpts)
- end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Error) -> Error
- end,
-
- Res = do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify),
- display_log(Config),
- Res.
-
async_gn_exec3(Node, TargetName, Oids, SendOpts) ->
mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts).
@@ -2631,27 +2636,36 @@ value_of_vavs([{_Oid, Val}|VAVs], Acc) ->
%%======================================================================
-simple_sync_set2(doc) ->
- ["Simple (sync) set-request - Version 2 API (TargetName)"];
-simple_sync_set2(suite) -> [];
-simple_sync_set2(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_set2,
- fun() -> do_simple_sync_set2(Config) end).
+simple_sync_set3(doc) ->
+ ["Simple (sync) set-request - Version 3 API (TargetName with send-opts)"];
+simple_sync_set3(suite) -> [];
+simple_sync_set3(Config) when is_list(Config) ->
+ ?TC_TRY(simple_sync_set3,
+ fun() -> do_simple_sync_set3(Config) end).
-do_simple_sync_set2(Config) ->
+do_simple_sync_set3(Config) ->
?IPRINT("starting with Config: "
"~n ~p", [Config]),
+ Self = self(),
+ Msg = simple_sync_set3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+
Set = fun(Node, TargetName, VAVs) ->
- mgr_user_sync_set(Node, TargetName, VAVs)
+ mgr_user_sync_set2(Node, TargetName, VAVs, SendOpts)
end,
- PostVerify = fun() -> ok end,
+ PostVerify = fun() -> receive Msg -> ok end end,
- Res = do_simple_sync_set2(Config, Set, PostVerify),
+ Res = do_simple_sync_set3(Config, Set, PostVerify),
display_log(Config),
Res.
-do_simple_sync_set2(Config, Set, PostVerify)
+do_simple_sync_set3(Config, Set, PostVerify)
when is_function(Set, 3) andalso is_function(PostVerify, 0) ->
Node = ?config(manager_node, Config),
@@ -2664,7 +2678,7 @@ do_simple_sync_set2(Config, Set, PostVerify)
{?sysName_instance, s, Val11},
{?sysLocation_instance, s, Val12}
],
- ?line ok = do_simple_set2(Node, TargetName, VAVs1, Set, PostVerify),
+ ?line ok = do_simple_set3(Node, TargetName, VAVs1, Set, PostVerify),
?IPRINT("issue set-request after first loading the mibs"),
?line ok = mgr_user_load_mib(Node, std_mib()),
@@ -2674,10 +2688,10 @@ do_simple_sync_set2(Config, Set, PostVerify)
{[sysName, 0], Val21},
{[sysLocation, 0], Val22}
],
- ?line ok = do_simple_set2(Node, TargetName, VAVs2, Set, PostVerify),
+ ?line ok = do_simple_set3(Node, TargetName, VAVs2, Set, PostVerify),
ok.
-do_simple_set2(Node, TargetName, VAVs, Set, PostVerify) ->
+do_simple_set3(Node, TargetName, VAVs, Set, PostVerify) ->
[SysName, SysLoc] = value_of_vavs(VAVs),
?line {ok, Reply, _Rem} = Set(Node, TargetName, VAVs),
@@ -2705,38 +2719,6 @@ do_simple_set2(Node, TargetName, VAVs, Set, PostVerify) ->
%%======================================================================
-simple_sync_set3(doc) ->
- ["Simple (sync) set-request - Version 3 API (TargetName with send-opts)"];
-simple_sync_set3(suite) -> [];
-simple_sync_set3(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_set3,
- fun() -> do_simple_sync_set3(Config) end).
-
-do_simple_sync_set3(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p", [Config]),
-
- Self = self(),
- Msg = simple_sync_set3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
-
- Set = fun(Node, TargetName, VAVs) ->
- mgr_user_sync_set2(Node, TargetName, VAVs, SendOpts)
- end,
- PostVerify = fun() -> receive Msg -> ok end end,
-
- Res = do_simple_sync_set2(Config, Set, PostVerify),
- display_log(Config),
- Res.
-
-
-%%======================================================================
-
sas_verify({noError, 0, _Vbs}, any) ->
?IPRINT("verified [any]"),
ok;
@@ -2769,17 +2751,31 @@ sas_verify_vbs([Vb|_], [E|_]) ->
%%======================================================================
-simple_async_set2(doc) ->
- ["Simple (async) set-request - Version 2 API (TargetName)"];
-simple_async_set2(suite) -> [];
-simple_async_set2(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_set2,
- fun() -> do_simple_async_set2(Config) end).
+simple_async_set3_cbp_def(doc) ->
+ ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
+simple_async_set3_cbp_def(suite) -> [];
+simple_async_set3_cbp_def(Config) when is_list(Config) ->
+ simple_async_set3(sas3_cbp_def, Config).
-do_simple_async_set2(Config) ->
+simple_async_set3_cbp_temp(doc) ->
+ ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
+simple_async_set3_cbp_temp(suite) -> [];
+simple_async_set3_cbp_temp(Config) when is_list(Config) ->
+ simple_async_set3(sas3_cbp_temp, Config).
+
+simple_async_set3_cbp_perm(doc) ->
+ ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
+simple_async_set3_cbp_perm(suite) -> [];
+simple_async_set3_cbp_perm(Config) when is_list(Config) ->
+ simple_async_set3(sas3_cbp_perm, Config).
+
+simple_async_set3(Case, Config) ->
+ ?TC_TRY(Case,
+ fun() -> do_simple_async_set3(Config) end).
+
+do_simple_async_set3(Config) ->
?IPRINT("starting with Config: "
- "~n ~p"
- "~n", [Config]),
+ "~n ~p~n", [Config]),
MgrNode = ?config(manager_node, Config),
AgentNode = ?config(agent_node, Config),
@@ -2790,17 +2786,28 @@ do_simple_async_set2(Config) ->
?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
?line ok = agent_load_mib(AgentNode, Test2Mib),
+ Self = self(),
+ Msg = simple_async_set3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+
Set =
fun(Oids) ->
- async_s_exec2(MgrNode, TargetName, Oids)
+ async_s_exec3(MgrNode, TargetName, Oids, SendOpts)
end,
- PostVerify = fun(Res) -> Res end,
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Res) -> Res
+ end,
- Res = do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify),
+ Res = do_simple_async_set3(MgrNode, AgentNode, Set, PostVerify),
display_log(Config),
Res.
-do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify) ->
+do_simple_async_set3(MgrNode, AgentNode, Set, PostVerify) ->
Requests =
[
{1,
@@ -2844,68 +2851,6 @@ do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify) ->
ok.
-async_s_exec2(Node, TargetName, VAVs) ->
- mgr_user_async_set(Node, TargetName, VAVs).
-
-
-%%======================================================================
-
-simple_async_set3_cbp_def(doc) ->
- ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
-simple_async_set3_cbp_def(suite) -> [];
-simple_async_set3_cbp_def(Config) when is_list(Config) ->
- simple_async_set3(sas3_cbp_def, Config).
-
-simple_async_set3_cbp_temp(doc) ->
- ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
-simple_async_set3_cbp_temp(suite) -> [];
-simple_async_set3_cbp_temp(Config) when is_list(Config) ->
- simple_async_set3(sas3_cbp_temp, Config).
-
-simple_async_set3_cbp_perm(doc) ->
- ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"];
-simple_async_set3_cbp_perm(suite) -> [];
-simple_async_set3_cbp_perm(Config) when is_list(Config) ->
- simple_async_set3(sas3_cbp_perm, Config).
-
-simple_async_set3(Case, Config) ->
- ?TC_TRY(Case,
- fun() -> do_simple_async_set3(Config) end).
-
-do_simple_async_set3(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Self = self(),
- Msg = simple_async_set3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
-
- Set =
- fun(Oids) ->
- async_s_exec3(MgrNode, TargetName, Oids, SendOpts)
- end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Res) -> Res
- end,
-
- Res = do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify),
- display_log(Config),
- Res.
-
async_s_exec3(Node, TargetName, VAVs, SendOpts) ->
mgr_user_async_set2(Node, TargetName, VAVs, SendOpts).
@@ -2949,14 +2894,15 @@ check_ssgb_vbs([R|_], [E|_]) ->
%%======================================================================
-simple_sync_get_bulk2(doc) ->
- ["Simple (sync) get_bulk-request - Version 2 API (TargetName)"];
-simple_sync_get_bulk2(suite) -> [];
-simple_sync_get_bulk2(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get_bulk2,
- fun() -> do_simple_sync_get_bulk2(Config) end).
+simple_sync_get_bulk3(doc) ->
+ ["Simple (sync) get_bulk-request - "
+ "Version 3 API (TargetName with send-opts)"];
+simple_sync_get_bulk3(suite) -> [];
+simple_sync_get_bulk3(Config) when is_list(Config) ->
+ ?TC_TRY(simple_sync_get_bulk3,
+ fun() -> do_simple_sync_get_bulk3(Config) end).
-do_simple_sync_get_bulk2(Config) ->
+do_simple_sync_get_bulk3(Config) ->
?IPRINT("starting with Config: "
"~n ~p~n", [Config]),
@@ -2964,32 +2910,43 @@ do_simple_sync_get_bulk2(Config) ->
AgentNode = ?config(agent_node, Config),
TargetName = ?config(manager_agent_target_name, Config),
+ Self = self(),
+ Msg = simple_async_set3,
+ Fun = fun() -> Self ! Msg end,
+ Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
+ SendOpts =
+ [
+ {extra, Extra}
+ ],
+
GetBulk =
fun(NonRep, MaxRep, Oids) ->
- mgr_user_sync_get_bulk(MgrNode, TargetName,
- NonRep, MaxRep, Oids)
+ mgr_user_sync_get_bulk2(MgrNode, TargetName,
+ NonRep, MaxRep, Oids, SendOpts)
end,
- PostVerify = fun(Res) -> Res end,
+ PostVerify = fun(ok) -> receive Msg -> ok end;
+ (Res) -> Res
+ end,
- Res = do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify),
+ Res = do_simple_sync_get_bulk3(Config, MgrNode, AgentNode, GetBulk, PostVerify),
display_log(Config),
Res.
-do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
+do_simple_sync_get_bulk3(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
%% -- 1 --
- ?line ok = do_simple_get_bulk2(1,
+ ?line ok = do_simple_get_bulk3(1,
1, 1, [],
fun verify_ssgb_reply1/1,
GetBulk, PostVerify),
%% -- 2 --
- ?line ok = do_simple_get_bulk2(2,
+ ?line ok = do_simple_get_bulk3(2,
-1, 1, [],
fun verify_ssgb_reply1/1,
GetBulk, PostVerify),
%% -- 3 --
- ?line ok = do_simple_get_bulk2(3,
+ ?line ok = do_simple_get_bulk3(3,
-1, -1, [],
fun verify_ssgb_reply1/1,
GetBulk, PostVerify),
@@ -2999,12 +2956,12 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
VF04 = fun(X) ->
verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
end,
- ?line ok = do_simple_get_bulk2(4,
+ ?line ok = do_simple_get_bulk3(4,
2, 0, [[sysDescr],[1,3,7,1]], VF04,
GetBulk, PostVerify),
%% -- 5 --
- ?line ok = do_simple_get_bulk2(5,
+ ?line ok = do_simple_get_bulk3(5,
1, 2, [[sysDescr],[1,3,7,1]], VF04,
GetBulk, PostVerify),
@@ -3014,7 +2971,7 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
[?sysDescr_instance, endOfMibView,
?sysObjectID_instance, endOfMibView])
end,
- ?line ok = do_simple_get_bulk2(6,
+ ?line ok = do_simple_get_bulk3(6,
0, 2, [[sysDescr],[1,3,7,1]], VF06,
GetBulk, PostVerify),
@@ -3025,7 +2982,7 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
?sysDescr_instance, endOfMibView,
?sysObjectID_instance, endOfMibView])
end,
- ?line ok = do_simple_get_bulk2(7,
+ ?line ok = do_simple_get_bulk3(7,
2, 2,
[[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]],
VF07,
@@ -3041,14 +2998,14 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
[?sysDescr_instance,
?sysDescr_instance])
end,
- ?line ok = do_simple_get_bulk2(8,
+ ?line ok = do_simple_get_bulk3(8,
1, 2,
[[sysDescr],[sysDescr],[tTooBig]],
VF08,
GetBulk, PostVerify),
%% -- 9 --
- ?line ok = do_simple_get_bulk2(9,
+ ?line ok = do_simple_get_bulk3(9,
1, 12,
[[tDescr2], [sysDescr]],
fun verify_ssgb_reply1/1,
@@ -3062,7 +3019,7 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
{?tGenErr1, 'NULL'},
{?sysDescr, 'NULL'}])
end,
- ?line ok = do_simple_get_bulk2(10,
+ ?line ok = do_simple_get_bulk3(10,
2, 2,
[[sysDescr],
[sysObjectID],
@@ -3079,14 +3036,14 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) ->
[{fl([TCnt2,2]), 100},
{fl([TCnt2,2]), endOfMibView}])
end,
- ?line ok = do_simple_get_bulk2(11,
+ ?line ok = do_simple_get_bulk3(11,
0, 2,
[[TCnt2, 1]], VF11,
GetBulk, PostVerify),
ok.
-do_simple_get_bulk2(N,
+do_simple_get_bulk3(N,
NonRep, MaxRep, Oids,
Verify, GetBulk, PostVerify)
when is_function(Verify, 1) andalso
@@ -3107,15 +3064,19 @@ do_simple_get_bulk2(N,
%%======================================================================
-simple_sync_get_bulk3(doc) ->
- ["Simple (sync) get_bulk-request - "
+simple_async_get_bulk3_cbp_def(doc) ->
+ ["Simple (async) get_bulk-request - "
"Version 3 API (TargetName with send-opts)"];
-simple_sync_get_bulk3(suite) -> [];
-simple_sync_get_bulk3(Config) when is_list(Config) ->
- ?TC_TRY(simple_sync_get_bulk3,
- fun() -> do_simple_sync_get_bulk3(Config) end).
+simple_async_get_bulk3_cbp_def(suite) -> [];
+simple_async_get_bulk3_cbp_def(Config) when is_list(Config) ->
+ simple_async_get_bulk3(sagb3_cbp_def, Config).
-do_simple_sync_get_bulk3(Config) ->
+simple_async_get_bulk3(Case, Config) ->
+ ?TC_TRY(Case,
+ fun() -> do_simple_async_get_bulk3(Config) end).
+
+do_simple_async_get_bulk3(Config) ->
+ process_flag(trap_exit, true),
?IPRINT("starting with Config: "
"~n ~p~n", [Config]),
@@ -3123,8 +3084,13 @@ do_simple_sync_get_bulk3(Config) ->
AgentNode = ?config(agent_node, Config),
TargetName = ?config(manager_agent_target_name, Config),
+ ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
+ Test2Mib = test2_mib(Config),
+ ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
+ ?line ok = agent_load_mib(AgentNode, Test2Mib),
+
Self = self(),
- Msg = simple_async_set3,
+ Msg = simple_async_get_bulk3,
Fun = fun() -> Self ! Msg end,
Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
SendOpts =
@@ -3133,52 +3099,18 @@ do_simple_sync_get_bulk3(Config) ->
],
GetBulk =
- fun(NonRep, MaxRep, Oids) ->
- mgr_user_sync_get_bulk2(MgrNode, TargetName,
- NonRep, MaxRep, Oids, SendOpts)
+ fun(Data) ->
+ async_gb_exec3(MgrNode, TargetName, Data, SendOpts)
end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
+ PostVerify = fun(ok) -> receive Msg -> ok end;
(Res) -> Res
end,
- Res = do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify),
+ Res = do_simple_async_get_bulk3(MgrNode, AgentNode, GetBulk, PostVerify),
display_log(Config),
Res.
-
-%%======================================================================
-
-simple_async_get_bulk2(doc) ->
- ["Simple (async) get_bulk-request - Version 2 API (TargetName)"];
-simple_async_get_bulk2(suite) -> [];
-simple_async_get_bulk2(Config) when is_list(Config) ->
- ?TC_TRY(simple_async_get_bulk2,
- fun() -> do_simple_async_get_bulk2(Config) end).
-
-do_simple_async_get_bulk2(Config) ->
- ?IPRINT("starting with Config: "
- "~p ~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- GetBulk =
- fun(Data) ->
- async_gb_exec2(MgrNode, TargetName, Data)
- end,
- PostVerify = fun(Res) -> Res end,
-
- Res = do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify),
- display_log(Config),
- Res.
-
-do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify) ->
+do_simple_async_get_bulk3(MgrNode, AgentNode, GetBulk, PostVerify) ->
%% We re-use the verification functions from the ssgb test-case
VF04 = fun(X) ->
PostVerify(
@@ -3304,58 +3236,6 @@ do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify) ->
ok.
-async_gb_exec2(Node, TargetName, {NR, MR, Oids}) ->
- mgr_user_async_get_bulk(Node, TargetName, NR, MR, Oids).
-
-
-%%======================================================================
-
-simple_async_get_bulk3_cbp_def(doc) ->
- ["Simple (async) get_bulk-request - "
- "Version 3 API (TargetName with send-opts)"];
-simple_async_get_bulk3_cbp_def(suite) -> [];
-simple_async_get_bulk3_cbp_def(Config) when is_list(Config) ->
- simple_async_get_bulk3(sagb3_cbp_def, Config).
-
-simple_async_get_bulk3(Case, Config) ->
- ?TC_TRY(Case,
- fun() -> do_simple_async_get_bulk3(Config) end).
-
-do_simple_async_get_bulk3(Config) ->
- process_flag(trap_exit, true),
- ?IPRINT("starting with Config: "
- "~n ~p~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- Self = self(),
- Msg = simple_async_get_bulk3,
- Fun = fun() -> Self ! Msg end,
- Extra = {?SNMPM_EXTRA_INFO_TAG, Fun},
- SendOpts =
- [
- {extra, Extra}
- ],
-
- GetBulk =
- fun(Data) ->
- async_gb_exec3(MgrNode, TargetName, Data, SendOpts)
- end,
- PostVerify = fun(ok) -> receive Msg -> ok end;
- (Res) -> Res
- end,
-
- Res = do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify),
- display_log(Config),
- Res.
-
async_gb_exec3(Node, TargetName, {NR, MR, Oids}, SendOpts) ->
mgr_user_async_get_bulk2(Node, TargetName, NR, MR, Oids, SendOpts).
@@ -3382,203 +3262,6 @@ simple_async_get_bulk3_cbp_perm(Config) when is_list(Config) ->
%%======================================================================
-misc_async2(doc) ->
- ["Misc (async) request(s) - Version 2 API (TargetName)"];
-misc_async2(suite) -> [];
-misc_async2(Config) when is_list(Config) ->
- ?TC_TRY(misc_async2,
- fun() -> do_misc_async2(Config) end).
-
-do_misc_async2(Config) ->
- ?IPRINT("starting with Config: "
- "~n ~p"
- "~n", [Config]),
-
- MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
- TargetName = ?config(manager_agent_target_name, Config),
-
- ?line ok = mgr_user_load_mib(MgrNode, std_mib()),
- Test2Mib = test2_mib(Config),
- ?line ok = mgr_user_load_mib(MgrNode, Test2Mib),
- ?line ok = agent_load_mib(AgentNode, Test2Mib),
-
- ExecG = fun(Data) ->
- async_g_exec2(MgrNode, TargetName, Data)
- end,
-
- ExecGN = fun(Data) ->
- async_gn_exec2(MgrNode, TargetName, Data)
- end,
-
- ExecS = fun(Data) ->
- async_s_exec2(MgrNode, TargetName, Data)
- end,
-
- ExecGB = fun(Data) ->
- async_gb_exec2(MgrNode, TargetName, Data)
- end,
-
- ?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2),
- ?line {ok, [TGenErr1|_]} = mgr_user_name_to_oid(MgrNode, tGenErr1),
- ?line {ok, [TGenErr2|_]} = mgr_user_name_to_oid(MgrNode, tGenErr2),
- ?line {ok, [TGenErr3|_]} = mgr_user_name_to_oid(MgrNode, tGenErr3),
- ?line {ok, [TTooBig|_]} = mgr_user_name_to_oid(MgrNode, tTooBig),
-
- Requests =
- [
- { 1,
- [?sysObjectID_instance],
- ExecG,
- fun(X) ->
- sag_verify(X, [?sysObjectID_instance])
- end
- },
- { 2,
- {1, 1, []},
- ExecGB,
- fun verify_ssgb_reply1/1},
- { 3,
- {-1, 1, []},
- ExecGB,
- fun verify_ssgb_reply1/1},
- { 4,
- [{?sysLocation_instance, s, "Stockholm"},
- {?sysName_instance, s, "Arne Anka"}],
- ExecS,
- fun(X) ->
- sas_verify(X, [?sysLocation_instance, ?sysName_instance])
- end},
- { 5,
- [[sysDescr], [1,3,7,1]],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply1(X, [?sysDescr_instance, endOfMibView])
- end},
- { 6,
- [[sysObjectID, 0], [sysDescr, 0], [sysUpTime, 0]],
- ExecG,
- fun(X) ->
- sag_verify(X, [?sysObjectID_instance,
- ?sysDescr_instance,
- ?sysUpTime_instance])
- end},
- { 7,
- [TGenErr2],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr2]})
- end},
- { 8,
- {2, 0, [[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
- end},
- { 9,
- {1, 2, [[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView])
- end},
- {10,
- [TGenErr1],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 1, [TGenErr1]})
- end},
- {11,
- {0, 2, [[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end},
- {12,
- [{[sysName, 0], "Gothenburg"},
- {[sysLocation, 0], "Sune Anka"}],
- ExecS,
- fun(X) ->
- sas_verify(X, [?sysName_instance, ?sysLocation_instance])
- end},
- {13,
- {2, 2, [[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance, endOfMibView,
- ?sysDescr_instance, endOfMibView,
- ?sysObjectID_instance, endOfMibView])
- end},
- {14,
- {1, 2, [[sysDescr],[sysDescr],[tTooBig]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [?sysDescr_instance,
- ?sysDescr_instance])
- end},
- {15,
- {1, 12, [[tDescr2], [sysDescr]]},
- ExecGB,
- fun verify_ssgb_reply1/1},
- {16,
- {2, 2, [[sysDescr],[sysObjectID], [tGenErr1],[sysDescr]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply3(X,
- [{?sysDescr, 'NULL'},
- {?sysObjectID, 'NULL'},
- {?tGenErr1, 'NULL'},
- {?sysDescr, 'NULL'}])
- end},
- {17,
- [[sysDescr], TGenErr3],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {genErr, 2, [TGenErr3]})
- end},
- {18,
- {0, 2, [[TCnt2, 1]]},
- ExecGB,
- fun(X) ->
- verify_ssgb_reply2(X,
- [{fl([TCnt2,2]), 100},
- {fl([TCnt2,2]), endOfMibView}])
- end},
- {19,
- [TTooBig],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {tooBig, 0, []})
- end},
- {20,
- [TTooBig],
- ExecGN,
- fun(X) ->
- verify_ssgn_reply2(X, {tooBig, 0, []})
- end}
- ],
-
- ?IPRINT("manager info when starting test: "
- "~n ~p", [mgr_info(MgrNode)]),
- ?IPRINT("agent info when starting test: "
- "~n ~p", [agent_info(AgentNode)]),
-
- ?line ok = async_exec(Requests, []),
-
- ?IPRINT("manager info when ending test: "
- "~n ~p", [mgr_info(MgrNode)]),
- ?IPRINT("agent info when ending test: "
- "~n ~p", [agent_info(AgentNode)]),
-
- display_log(Config),
- ok.
-
-
-%%======================================================================
-
discovery(suite) -> [];
discovery(Config) when is_list(Config) ->
?SKIP(not_yet_implemented).
@@ -4583,27 +4266,30 @@ do_inform4(Config) ->
inform_swarm_cbp_def(suite) -> [];
inform_swarm_cbp_def(Config) when is_list(Config) ->
- inform_swarm(is_cbp_def, Config).
+ inform_swarm(is_cbp_def, 1500, Config).
inform_swarm_cbp_temp(suite) -> [];
inform_swarm_cbp_temp(Config) when is_list(Config) ->
- inform_swarm(is_cbp_temp, Config).
+ inform_swarm(is_cbp_temp, 1500, Config).
inform_swarm_cbp_perm(suite) -> [];
inform_swarm_cbp_perm(Config) when is_list(Config) ->
- inform_swarm(is_cbp_perm, Config).
+ inform_swarm(is_cbp_perm, 1800, Config).
-inform_swarm(Case, Config) ->
+inform_swarm(Case, NumI, Config) ->
?TC_TRY(Case,
- fun() -> do_inform_swarm(Config) end).
+ fun() -> do_inform_swarm(NumI, Config) end).
-do_inform_swarm(Config) ->
+do_inform_swarm(NumI, Config) ->
%% process_flag(trap_exit, true),
- ?IPRINT("starting with Config: "
- "~p ~n", [Config]),
+ ?IPRINT("starting with"
+ "~n NumI: ~p"
+ "~n Config: ~p"
+ "~n", [NumI, Config]),
MgrNode = ?config(manager_node, Config),
- AgentNode = ?config(agent_node, Config),
+ AgentNode = ?config(agent_node, Config),
+ Factor = ?config(snmp_factor, Config),
?line ok = mgr_user_load_mib(MgrNode, snmpv2_mib()),
Test2Mib = test2_mib(Config),
@@ -4615,7 +4301,7 @@ do_inform_swarm(Config) ->
?line ok = agent_load_mib(AgentNode, Test2Mib),
?line ok = agent_load_mib(AgentNode, TestTrapMib),
?line ok = agent_load_mib(AgentNode, TestTrapv2Mib),
- NumInforms = 2000,
+ NumInforms = NumI div Factor,
Collector = self(),
@@ -4635,8 +4321,8 @@ do_inform_swarm(Config) ->
{{inform2_tag1, N}, Collector},
"standard inform",
[]),
- %% Sleep some [(N div 10)*100 ms]
- %% every tenth notification
+ %% Sleep 1000 ms every 100th notif
+ %% Sleep 100 ms every 10th notif
if
N rem 100 == 0 ->
Sleep = 1000,
@@ -4675,11 +4361,11 @@ do_inform_swarm(Config) ->
Commands =
[
- {1, "Manager and agent info at start of test", Cmd1},
- {2, "Send notifcation(s) from agent", Cmd2},
- {3, "Await send-ack(s)/inform(s)/response(s)", Cmd3},
- {4, "Sleep some time (1 sec)", Cmd4},
- {5, "Manager and agent info after test completion", Cmd1}
+ {1, "Manager and agent info at start of test", Cmd1},
+ {2, ?F("Send ~p notifcation(s) from agent", [NumInforms]), Cmd2},
+ {3, "Await send-ack(s)/inform(s)/response(s)", Cmd3},
+ {4, "Sleep some time (1 sec)", Cmd4},
+ {5, "Manager and agent info after test completion", Cmd1}
],
Res = command_handler(Commands),
@@ -4724,7 +4410,7 @@ inform_swarm_collector(N, SentAckCnt, RecvCnt, RespCnt, Timeout) ->
%% The manager has received the actual inform
{async_event, From, {inform, Pid, Inform}} ->
- ?IPRINT("received inform"),
+ ?IPRINT("received inform (~p of ~p)", [RecvCnt+1, N]),
case Inform of
{noError, 0, VBs} when is_list(VBs) ->
Pid ! {handle_inform_response, From},
@@ -4840,7 +4526,7 @@ otp8395_1(Config) when is_list(Config) ->
fun() -> do_otp8395_1(Config) end).
do_otp8395_1(Config) ->
- do_simple_sync_get2(Config).
+ do_simple_sync_get3(Config).
%%======================================================================
@@ -5546,16 +5232,16 @@ mgr_user_load_mib(Node, Mib) ->
%% mgr_user_sync_get(Node, Oids) ->
%% mgr_user_sync_get(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
-mgr_user_sync_get(Node, TargetName, Oids) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, sync_get, [TargetName, Oids]).
+%% mgr_user_sync_get(Node, TargetName, Oids) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, sync_get, [TargetName, Oids]).
mgr_user_sync_get2(Node, TargetName, Oids, SendOpts) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_get2, [TargetName, Oids, SendOpts]).
%% mgr_user_async_get(Node, Oids) ->
%% mgr_user_async_get(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
-mgr_user_async_get(Node, TargetName, Oids) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, async_get, [TargetName, Oids]).
+%% mgr_user_async_get(Node, TargetName, Oids) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, async_get, [TargetName, Oids]).
mgr_user_async_get2(Node, TargetName, Oids, SendOpts)
when is_list(TargetName) ->
@@ -5563,8 +5249,8 @@ mgr_user_async_get2(Node, TargetName, Oids, SendOpts)
%% mgr_user_sync_get_next(Node, Oids) ->
%% mgr_user_sync_get_next(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
-mgr_user_sync_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, sync_get_next, [TargetName, Oids]).
+%% mgr_user_sync_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, sync_get_next, [TargetName, Oids]).
mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
when is_list(TargetName) ->
@@ -5572,8 +5258,8 @@ mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts)
%% mgr_user_async_get_next(Node, Oids) ->
%% mgr_user_async_get_next(Node, ?LOCALHOST(), ?AGENT_PORT, Oids).
-mgr_user_async_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, async_get_next, [TargetName, Oids]).
+%% mgr_user_async_get_next(Node, TargetName, Oids) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, async_get_next, [TargetName, Oids]).
mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts)
when is_list(TargetName) ->
@@ -5581,16 +5267,16 @@ mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts)
%% mgr_user_sync_set(Node, VAV) ->
%% mgr_user_sync_set(Node, ?LOCALHOST(), ?AGENT_PORT, VAV).
-mgr_user_sync_set(Node, TargetName, VAV) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, sync_set, [TargetName, VAV]).
+%% mgr_user_sync_set(Node, TargetName, VAV) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, sync_set, [TargetName, VAV]).
mgr_user_sync_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, sync_set2, [TargetName, VAV, SendOpts]).
%% mgr_user_async_set(Node, VAV) ->
%% mgr_user_async_set(Node, ?LOCALHOST(), ?AGENT_PORT, VAV).
-mgr_user_async_set(Node, TargetName, VAV) when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, async_set, [TargetName, VAV]).
+%% mgr_user_async_set(Node, TargetName, VAV) when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, async_set, [TargetName, VAV]).
mgr_user_async_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) ->
rcall(Node, snmp_manager_user, async_set2, [TargetName, VAV, SendOpts]).
@@ -5598,10 +5284,10 @@ mgr_user_async_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) ->
%% mgr_user_sync_get_bulk(Node, NonRep, MaxRep, Oids) ->
%% mgr_user_sync_get_bulk(Node, ?LOCALHOST(), ?AGENT_PORT,
%% NonRep, MaxRep, Oids).
-mgr_user_sync_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
- when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, sync_get_bulk,
- [TargetName, NonRep, MaxRep, Oids]).
+%% mgr_user_sync_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
+%% when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, sync_get_bulk,
+%% [TargetName, NonRep, MaxRep, Oids]).
mgr_user_sync_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts)
when is_list(TargetName) ->
@@ -5611,10 +5297,10 @@ mgr_user_sync_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts)
%% mgr_user_async_get_bulk(Node, NonRep, MaxRep, Oids) ->
%% mgr_user_async_get_bulk(Node, ?LOCALHOST(), ?AGENT_PORT,
%% NonRep, MaxRep, Oids).
-mgr_user_async_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
- when is_list(TargetName) ->
- rcall(Node, snmp_manager_user, async_get_bulk,
- [TargetName, NonRep, MaxRep, Oids]).
+%% mgr_user_async_get_bulk(Node, TargetName, NonRep, MaxRep, Oids)
+%% when is_list(TargetName) ->
+%% rcall(Node, snmp_manager_user, async_get_bulk,
+%% [TargetName, NonRep, MaxRep, Oids]).
mgr_user_async_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts)
when is_list(TargetName) ->
diff --git a/lib/snmp/test/snmp_manager_config_SUITE.erl b/lib/snmp/test/snmp_manager_config_SUITE.erl
index 9a7b485a60..f7f7fd6928 100644
--- a/lib/snmp/test/snmp_manager_config_SUITE.erl
+++ b/lib/snmp/test/snmp_manager_config_SUITE.erl
@@ -2249,6 +2249,7 @@ register_usm_user_using_function(Conf) when is_list(Conf) ->
{auth, usmHMACMD5AuthProtocol},
{auth_key, [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6]},
{priv, usmNoPrivProtocol}],
+
?line ok = snmpm_config:register_usm_user(EngineID, UserName1, UsmConfig1),
?IPRINT("try register user 1 again (error)"),
?line {error, {already_registered, EngineID, UserName1}} =
@@ -2270,7 +2271,17 @@ register_usm_user_using_function(Conf) when is_list(Conf) ->
{auth_key, [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6]},
{priv, usmNoPrivProtocol}],
?line ok = snmpm_config:register_usm_user(EngineID, UserName3, UsmConfig3),
-
+
+ ?IPRINT("register user 4 (ok)"),
+ UserName4 = "samu4",
+ SecName4 = "samu_auth4",
+ UsmConfig4 = [{sec_name, SecName4},
+ {auth, usmHMACMD5AuthProtocol},
+ {auth_key, [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6]},
+ {priv, usmAesCfb128Protocol},
+ {priv_key, [190,54,66,227,33,171,152,0,133,223,204,155,109,111,77,44]}],
+ ?line ok = snmpm_config:register_usm_user(EngineID, UserName4, UsmConfig4),
+
?IPRINT("lookup 1 (ok)"),
?line {ok, #usm_user{name = UserName1} = User1} =
snmpm_config:get_usm_user_from_sec_name(EngineID, SecName1),
@@ -2286,9 +2297,14 @@ register_usm_user_using_function(Conf) when is_list(Conf) ->
snmpm_config:get_usm_user_from_sec_name(EngineID, SecName3),
?IPRINT("User: ~p", [User3]),
- ?IPRINT("lookup 4 (error)"),
+ ?IPRINT("lookup 4 (ok)"),
+ ?line {ok, #usm_user{name = UserName4} = User4} =
+ snmpm_config:get_usm_user_from_sec_name(EngineID, SecName4),
+ ?IPRINT("User: ~p", [User4]),
+
+ ?IPRINT("lookup 5 (error)"),
?line {error, not_found} =
- snmpm_config:get_usm_user_from_sec_name(EngineID, SecName3 ++ "_1"),
+ snmpm_config:get_usm_user_from_sec_name(EngineID, SecName4 ++ "_1"),
%% --
?IPRINT("stop config process"),
@@ -2341,6 +2357,7 @@ update_usm_user_info(Conf) when is_list(Conf) ->
?IPRINT("start"),
process_flag(trap_exit, true),
+ ?IPRINT("Start crypto and ensure support"),
case ?CRYPTO_START() of
ok ->
case ?CRYPTO_SUPPORT() of
@@ -2353,9 +2370,62 @@ update_usm_user_info(Conf) when is_list(Conf) ->
?SKIP({failed_starting_crypto, Reason})
end,
- _ConfDir = ?config(manager_conf_dir, Conf),
- _DbDir = ?config(manager_db_dir, Conf),
- ?SKIP(not_yet_implemented).
+ ConfDir = ?config(manager_conf_dir, Conf),
+ DbDir = ?config(manager_db_dir, Conf),
+
+ ?IPRINT("write manager config"),
+ write_manager_conf(ConfDir),
+
+ Opts = [{versions, [v3]},
+ {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}],
+
+ ?IPRINT("Start config server"),
+ ?line {ok, _Pid} = snmpm_config:start_link(Opts),
+
+ ?IPRINT("Register usm user"),
+ EngineID = "engine",
+ UsmUser = "UsmUser",
+ SecName = UsmUser,
+ AuthProto = usmHMACMD5AuthProtocol,
+ AuthKey = [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6],
+ PrivProto1 = usmNoPrivProtocol,
+ UsmConfig = [{sec_name, SecName},
+ {auth, AuthProto},
+ {auth_key, AuthKey},
+ {priv, PrivProto1}],
+ ok = snmpm_config:register_usm_user(EngineID, UsmUser, UsmConfig),
+
+ ?IPRINT("verify user user config"),
+ ?line {ok, AuthProto} = snmpm_config:usm_user_info(EngineID, UsmUser, auth),
+ ?line {ok, AuthKey} = snmpm_config:usm_user_info(EngineID, UsmUser, auth_key),
+ ?line {ok, PrivProto1} = snmpm_config:usm_user_info(EngineID, UsmUser, priv),
+
+ ?IPRINT("usm user update 1"),
+ PrivProto2 = usmAesCfb128Protocol,
+ PrivKey2 = [190,54,66,227,33,171,152,0,133,223,204,155,109,111,77,44],
+ ok = snmpm_config:update_usm_user_info(EngineID, UsmUser, priv, PrivProto2),
+ ok = snmpm_config:update_usm_user_info(EngineID, UsmUser, priv_key, PrivKey2),
+
+ ?IPRINT("verify updated user user config after update 1"),
+ ?line {ok, AuthProto} = snmpm_config:usm_user_info(EngineID, UsmUser, auth),
+ ?line {ok, AuthKey} = snmpm_config:usm_user_info(EngineID, UsmUser, auth_key),
+ ?line {ok, PrivProto2} = snmpm_config:usm_user_info(EngineID, UsmUser, priv),
+ ?line {ok, PrivKey2} = snmpm_config:usm_user_info(EngineID, UsmUser, priv_key),
+
+ ?IPRINT("usm user update 2"),
+ PrivProto3 = PrivProto1,
+ ok = snmpm_config:update_usm_user_info(EngineID, UsmUser, priv, PrivProto3),
+
+ ?IPRINT("verify updated user user config after update 2"),
+ ?line {ok, AuthProto} = snmpm_config:usm_user_info(EngineID, UsmUser, auth),
+ ?line {ok, AuthKey} = snmpm_config:usm_user_info(EngineID, UsmUser, auth_key),
+ ?line {ok, PrivProto3} = snmpm_config:usm_user_info(EngineID, UsmUser, priv),
+
+ ?IPRINT("Stop config server"),
+ ?line ok = snmpm_config:stop(),
+
+ ?IPRINT("done"),
+ ok.
%%
diff --git a/lib/snmp/test/snmp_manager_user.erl b/lib/snmp/test/snmp_manager_user.erl
index 409f87cf40..60a6844875 100644
--- a/lib/snmp/test/snmp_manager_user.erl
+++ b/lib/snmp/test/snmp_manager_user.erl
@@ -51,14 +51,14 @@
update_agent_info/3,
which_all_agents/0, which_own_agents/0,
load_mib/1, unload_mib/1,
- sync_get/1, sync_get/2, sync_get2/3,
- async_get/1, async_get/2, async_get2/3,
- sync_get_next/1, sync_get_next/2, sync_get_next2/3,
- async_get_next/1, async_get_next/2, async_get_next2/3,
- sync_set/1, sync_set/2, sync_set2/3,
- async_set/1, async_set/2, async_set2/3,
- sync_get_bulk/3, sync_get_bulk/4, sync_get_bulk2/5,
- async_get_bulk/3, async_get_bulk/4, async_get_bulk2/5,
+ sync_get2/3,
+ async_get2/3,
+ sync_get_next2/3,
+ async_get_next2/3,
+ sync_set2/3,
+ async_set2/3,
+ sync_get_bulk2/5,
+ async_get_bulk2/5,
name_to_oid/1, oid_to_name/1,
purify_oid/1
]).
@@ -159,90 +159,42 @@ unload_mib(Mib) ->
%% --
-sync_get(Oids) ->
- call({sync_get, Oids}).
-
-sync_get(TargetName, Oids) ->
- call({sync_get, TargetName, Oids}).
-
sync_get2(TargetName, Oids, SendOpts) ->
call({sync_get2, TargetName, Oids, SendOpts}).
%% --
-async_get(Oids) ->
- call({async_get, Oids}).
-
-async_get(TargetName, Oids) ->
- call({async_get, TargetName, Oids}).
-
async_get2(TargetName, Oids, SendOpts) ->
call({async_get2, TargetName, Oids, SendOpts}).
%% --
-sync_get_next(Oids) ->
- call({sync_get_next, Oids}).
-
-sync_get_next(TargetName, Oids) ->
- call({sync_get_next, TargetName, Oids}).
-
sync_get_next2(TargetName, Oids, SendOpts) ->
call({sync_get_next2, TargetName, Oids, SendOpts}).
%% --
-async_get_next(Oids) ->
- call({async_get_next, Oids}).
-
-async_get_next(TargetName, Oids) ->
- call({async_get_next, TargetName, Oids}).
-
async_get_next2(TargetName, Oids, SendOpts) ->
call({async_get_next2, TargetName, Oids, SendOpts}).
%% --
-sync_set(VAV) ->
- call({sync_set, VAV}).
-
-sync_set(TargetName, VAV) ->
- call({sync_set, TargetName, VAV}).
-
sync_set2(TargetName, VAV, SendOpts) ->
call({sync_set2, TargetName, VAV, SendOpts}).
%% --
-async_set(VAV) ->
- call({async_set, VAV}).
-
-async_set(TargetName, VAV) ->
- call({async_set, TargetName, VAV}).
-
async_set2(TargetName, VAV, SendOpts) ->
call({async_set2, TargetName, VAV, SendOpts}).
%% --
-sync_get_bulk(NonRep, MaxRep, Oids) ->
- call({sync_get_bulk, NonRep, MaxRep, Oids}).
-
-sync_get_bulk(TargetName, NonRep, MaxRep, Oids) ->
- call({sync_get_bulk, TargetName, NonRep, MaxRep, Oids}).
-
sync_get_bulk2(TargetName, NonRep, MaxRep, Oids, SendOpts) ->
call({sync_get_bulk2, TargetName, NonRep, MaxRep, Oids, SendOpts}).
%% --
-async_get_bulk(NonRep, MaxRep, Oids) ->
- call({async_get_bulk, NonRep, MaxRep, Oids}).
-
-async_get_bulk(TargetName, NonRep, MaxRep, Oids) ->
- call({async_get_bulk, TargetName, NonRep, MaxRep, Oids}).
-
async_get_bulk2(TargetName, NonRep, MaxRep, Oids, SendOpts) ->
call({async_get_bulk2, TargetName, NonRep, MaxRep, Oids, SendOpts}).
@@ -377,23 +329,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{sync_get, Oids}, From, Ref} ->
- d("loop -> received sync_get request "
- "(for every agent of this user)"),
- Res = [snmpm:sync_get(Id, TargetName, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{sync_get, TargetName, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received sync_get request with"
- "~n TargetName: ~p"
- "~n Oids: ~p", [TargetName, Oids]),
- Res = snmpm:sync_get(Id, TargetName, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (async) get-request --
@@ -409,22 +344,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{async_get, Oids}, From, Ref} ->
- d("loop -> received async_get request"),
- Res = [snmpm:async_get(Id, TargetName, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{async_get, TargetName, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received async_get request with"
- "~n TargetName: ~p"
- "~n Oids: ~p", [TargetName, Oids]),
- Res = snmpm:async_get(Id, TargetName, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (sync) get_next-request --
@@ -440,22 +359,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{sync_get_next, Oids}, From, Ref} ->
- d("loop -> received sync_get_next request"),
- Res = [snmpm:sync_get_next(Id, TargetName, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{sync_get_next, TargetName, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received sync_get_next request with"
- "~n TargetName: ~p"
- "~n Oids: ~p", [TargetName, Oids]),
- Res = snmpm:sync_get_next(Id, TargetName, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (async) get_next-request --
@@ -471,22 +374,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{async_get_next, Oids}, From, Ref} ->
- d("loop -> received async_get_next request"),
- Res = [snmpm:async_get_next(Id, TargetName, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{async_get_next, TargetName, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received async_get_next request with"
- "~n TargetName: ~p"
- "~n Oids: ~p", [TargetName, Oids]),
- Res = snmpm:async_get_next(Id, TargetName, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (sync) set-request --
@@ -502,19 +389,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- {{sync_set, VAV}, From, Ref} ->
- d("loop -> received sync_set request"),
- Res = [snmpm:sync_set(Id, TargetName, VAV) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{sync_set, TargetName, VAV}, From, Ref} when is_list(TargetName) ->
- d("loop -> received sync_set request"),
- Res = snmpm:sync_set(Id, TargetName, VAV),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (async) set-request --
@@ -530,19 +404,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- {{async_set, VAV}, From, Ref} ->
- d("loop -> received async_set request"),
- Res = [snmpm:async_set(Id, TargetName, VAV) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{async_set, TargetName, VAV}, From, Ref} when is_list(TargetName) ->
- d("loop -> received async_set request"),
- Res = snmpm:async_set(Id, TargetName, VAV),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (sync) get-bulk-request --
@@ -562,27 +423,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{sync_get_bulk, NonRep, MaxRep, Oids}, From, Ref} ->
- d("loop -> received sync_get_bulk request with"
- "~n NonRep: ~w"
- "~n MaxRep: ~w"
- "~n Oids: ~p", [NonRep, MaxRep, Oids]),
- Res = [snmpm:sync_get_bulk(Id, TargetName, NonRep, MaxRep, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{sync_get_bulk, TargetName, NonRep, MaxRep, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received sync_get_bulk request with"
- "~n TargetName: ~p"
- "~n NonRep: ~w"
- "~n MaxRep: ~w"
- "~n Oids: ~p", [TargetName, NonRep, MaxRep, Oids]),
- Res = snmpm:sync_get_bulk(Id, TargetName, NonRep, MaxRep, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- (async) get-bulk-request --
@@ -602,27 +442,6 @@ loop(#state{parent = Parent, id = Id} = S) ->
reply(From, Res, Ref),
loop(S);
- %% No agent specified, so send it to all of them
- {{async_get_bulk, NonRep, MaxRep, Oids}, From, Ref} ->
- d("loop -> received async_get_bulk request with"
- "~n NonRep: ~w"
- "~n MaxRep: ~w"
- "~n Oids: ~p", [NonRep, MaxRep, Oids]),
- Res = [snmpm:async_get_bulk(Id, TargetName, NonRep, MaxRep, Oids) ||
- TargetName <- snmpm:which_agents(Id)],
- reply(From, Res, Ref),
- loop(S);
-
- {{async_get_bulk, TargetName, NonRep, MaxRep, Oids}, From, Ref} when is_list(TargetName) ->
- d("loop -> received async_get_bulk request with"
- "~n TargetName: ~p"
- "~n NonRep: ~w"
- "~n MaxRep: ~w"
- "~n Oids: ~p", [TargetName, NonRep, MaxRep, Oids]),
- Res = snmpm:async_get_bulk(Id, TargetName, NonRep, MaxRep, Oids),
- reply(From, Res, Ref),
- loop(S);
-
%%
%% -- logical name translation --
diff --git a/lib/snmp/test/snmp_manager_user_SUITE.erl b/lib/snmp/test/snmp_manager_user_SUITE.erl
index eca0d8a4f9..bad746b29a 100644
--- a/lib/snmp/test/snmp_manager_user_SUITE.erl
+++ b/lib/snmp/test/snmp_manager_user_SUITE.erl
@@ -888,26 +888,17 @@ register_monitor_and_crash3(Conf) when is_list(Conf) ->
put(tname,rlac3),
%% <CONDITIONAL-SKIP>
- %% The point of this is to catch machines running
- %% SLES9 (2.6.5)
LinuxVersionVerify =
fun() ->
- case os:cmd("uname -m") of
- "i686" ++ _ ->
-%% io:format("found an i686 machine, "
-%% "now check version~n", []),
- case os:version() of
- {2, 6, Rev} when Rev >= 16 ->
- true;
- {2, Min, _} when Min > 6 ->
- true;
- {Maj, _, _} when Maj > 2 ->
- true;
- _ ->
- false
- end;
- _ ->
- true
+ case os:version() of
+ V when V > {2, 6, 16} ->
+ ?IPRINT("(Linux) kernel version check: "
+ "~p > {2, 6, 16} => *NO* SKIP", [V]),
+ false;
+ V ->
+ ?IPRINT("(Linux) kernel version check: "
+ "~p =< {2, 6, 16} => *SKIP*", [V]),
+ true
end
end,
Skippable = [{unix, [{linux, LinuxVersionVerify}]}],
@@ -922,10 +913,10 @@ register_monitor_and_crash3(Conf) when is_list(Conf) ->
write_manager_conf(ConfDir),
- Opts = [{server, [{verbosity, trace}]},
- {net_if, [{verbosity, trace}]},
+ Opts = [{server, [{verbosity, trace}]},
+ {net_if, [{verbosity, trace}]},
{note_store, [{verbosity, trace}]},
- {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}],
+ {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}],
?IPRINT("try starting manager"),
?line ok = snmpm:start_link(Opts),
diff --git a/lib/snmp/test/snmp_otp16649_user.erl b/lib/snmp/test/snmp_otp16649_user.erl
new file mode 100644
index 0000000000..260099a999
--- /dev/null
+++ b/lib/snmp/test/snmp_otp16649_user.erl
@@ -0,0 +1,93 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Utility functions for the (snmp manager) user test(s).
+%%----------------------------------------------------------------------
+
+-module(snmp_otp16649_user).
+
+-behaviour(snmpm_user).
+
+
+%%----------------------------------------------------------------------
+%% Include files
+%%----------------------------------------------------------------------
+-include_lib("common_test/include/ct.hrl").
+-include("snmp_test_lib.hrl").
+
+
+%%----------------------------------------------------------------------
+%% External exports
+%%----------------------------------------------------------------------
+-export([
+ ]).
+
+
+%%----------------------------------------------------------------------
+%% Internal exports
+%%----------------------------------------------------------------------
+
+-export([
+ handle_error/3,
+ handle_agent/5,
+ handle_pdu/4,
+ handle_trap/3,
+ handle_inform/3,
+ handle_report/3
+ ]).
+
+
+%%----------------------------------------------------------------------
+%% User callback functions:
+%%----------------------------------------------------------------------
+
+handle_error(ReqId, Reason, UserPid) ->
+ UserPid ! {handle_error, self(), ReqId, Reason},
+ ignore.
+
+
+handle_agent(Addr, Port, SnmpInfo, UserPid, UserData) ->
+ UserPid ! {handle_agent, self(), Addr, Port, SnmpInfo, UserData},
+ ignore.
+
+
+handle_pdu(TargetName, ReqId, SnmpResponse, UserPid) ->
+ UserPid ! {handle_pdu, self(), TargetName, ReqId, SnmpResponse},
+ ignore.
+
+handle_trap(TargetName, SnmpTrap, UserPid) ->
+ UserPid ! {handle_trap, self(), TargetName, SnmpTrap},
+ ignore.
+
+handle_inform(TargetName, SnmpInform, UserPid) ->
+ UserPid ! {handle_inform, self(), TargetName, SnmpInform},
+ receive
+ {handle_inform_no_response, TargetName} ->
+ no_reply;
+ {handle_inform_response, TargetName} ->
+ ignore
+ end.
+
+handle_report(TargetName, SnmpReport, UserPid) ->
+ UserPid ! {handle_report, self(), TargetName, SnmpReport},
+ ignore.
+
+
diff --git a/lib/snmp/test/snmp_test_global_sys_monitor.erl b/lib/snmp/test/snmp_test_global_sys_monitor.erl
index 54cc7d588e..c3f2e24096 100644
--- a/lib/snmp/test/snmp_test_global_sys_monitor.erl
+++ b/lib/snmp/test/snmp_test_global_sys_monitor.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,8 @@
-include("snmp_test_lib.hrl").
--define(NAME, ?MODULE).
+-define(NAME, ?MODULE).
+-define(TIMEOUT, timer:seconds(6)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -43,10 +44,10 @@ stop() ->
%% This does not reset the global counter but the "collector"
%% See events for more info.
reset_events() ->
- call(reset_events).
+ call(reset_events, ?TIMEOUT).
events() ->
- call(events).
+ call(events, ?TIMEOUT).
log(Event) ->
cast({node(), Event}).
@@ -198,23 +199,66 @@ cast(Msg) ->
{error, {catched, C, E}}
end.
-call(Req) ->
- call(Req, infinity).
-
-call(Req, Timeout) ->
- Ref = make_ref(),
- try global:send(?NAME, {?MODULE, Ref, self(), Req}) of
- Pid when is_pid(Pid) ->
- receive
- {?MODULE, Ref, Rep} ->
- Rep
- after Timeout ->
- {error, timeout}
- end
- catch
- C:E:_ ->
- {error, {catched, C, E}}
+%% call(Req) ->
+%% call(Req, infinity).
+
+%% call(Req, Timeout) ->
+%% Ref = make_ref(),
+%% try global:send(?NAME, {?MODULE, Ref, self(), Req}) of
+%% Pid when is_pid(Pid) ->
+%% receive
+%% {?MODULE, Ref, Rep} ->
+%% Rep
+%% after Timeout ->
+%% {error, timeout}
+%% end
+%% catch
+%% C:E:_ ->
+%% {error, {catched, C, E}}
+%% end.
+
+call(Req, Timeout) when (Timeout =:= infinity) ->
+ call(Req, Timeout, Timeout);
+call(Req, Timeout) when is_integer(Timeout) andalso (Timeout > 2000) ->
+ call(Req, Timeout, Timeout - 1000);
+call(Req, Timeout) when is_integer(Timeout) andalso (Timeout > 1000) ->
+ call(Req, Timeout, Timeout - 500);
+call(Req, Timeout) when is_integer(Timeout) ->
+ call(Req, Timeout, Timeout div 2).
+
+%% This peace of wierdness is because on some machines this call has
+%% hung (in a call during end_per_testcase, which had a 1 min timeout,
+%% or if that was the total time for the test case).
+%% But because it hung there, we don't really know what where it git stuck.
+%% So, by making the call in a tmp process, that we supervise, we can
+%% keep control. Also, we change the default timeout from infinity to an
+%% actual time (16 seconds).
+call(Req, Timeout1, Timeout2) ->
+ F = fun() ->
+ Ref = make_ref(),
+ try global:send(?NAME, {?MODULE, Ref, self(), Req}) of
+ NamePid when is_pid(NamePid) ->
+ receive
+ {?MODULE, Ref, Rep} ->
+ Rep
+ after Timeout2 ->
+ {error, timeout}
+ end
+ catch
+ C:E:_ ->
+ {error, {catched, C, E}}
+ end
+ end,
+ {Pid, Mon} = spawn_monitor(F),
+ receive
+ {'DOWN', Mon, process, Pid, Result} ->
+ Result
+ after Timeout1 ->
+ PInfo = process_info(Pid),
+ exit(Pid, kill),
+ {error, {timeout, PInfo}}
end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl
index 3e445174ba..1828e62ffb 100644
--- a/lib/snmp/test/snmp_test_lib.erl
+++ b/lib/snmp/test/snmp_test_lib.erl
@@ -25,6 +25,7 @@
-export([tc_try/2, tc_try/3,
tc_try/4, tc_try/5]).
+-export([proxy_call/3]).
-export([hostname/0, hostname/1, localhost/0, localhost/1, os_type/0, sz/1,
display_suite_info/1]).
-export([non_pc_tc_maybe_skip/4,
@@ -38,7 +39,7 @@
replace_config/3, set_config/3, get_config/2, get_config/3]).
-export([fail/3, skip/3]).
-export([hours/1, minutes/1, seconds/1, sleep/1]).
--export([flush_mqueue/0, trap_exit/0, trap_exit/1]).
+-export([flush_mqueue/0, mqueue/0, mqueue/1, trap_exit/0, trap_exit/1]).
-export([ping/1, local_nodes/0, nodes_on/1]).
-export([start_node/2, stop_node/1]).
-export([is_app_running/1,
@@ -257,6 +258,19 @@ tc_which_name() ->
%% Misc functions
%%
+proxy_call(F, Timeout, Default)
+ when is_function(F, 0) andalso is_integer(Timeout) andalso (Timeout > 0) ->
+ {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end),
+ receive
+ {'DOWN', M, process, P, Reply} ->
+ Reply
+ after Timeout ->
+ erlang:demonitor(M, [flush]),
+ exit(P, kill),
+ Default
+ end.
+
+
hostname() ->
hostname(node()).
@@ -268,20 +282,6 @@ hostname(Node) ->
[]
end.
-%% localhost() ->
-%% {ok, Ip} = snmp_misc:ip(net_adm:localhost()),
-%% Ip.
-%% localhost(Family) ->
-%% {ok, Ip} = snmp_misc:ip(net_adm:localhost(), Family),
-%% Ip.
-
-%% localhost() ->
-%% {ok, Ip} = snmp_misc:ip(net_adm:localhost()),
-%% Ip.
-%% localhost(Family) ->
-%% {ok, Ip} = snmp_misc:ip(net_adm:localhost(), Family),
-%% Ip.
-
localhost() ->
localhost(inet).
@@ -467,6 +467,10 @@ os_base_skip(Skippable, OsFam, OsName) ->
case lists:keysearch(OsFam, 1, Skippable) of
{value, {OsFam, OsName}} ->
true;
+ {value, {OsFam, Check}} when is_function(Check, 0) ->
+ Check();
+ {value, {OsFam, Check}} when is_function(Check, 1) ->
+ Check(os:version());
{value, {OsFam, OsNames}} when is_list(OsNames) ->
%% OsNames is a list of:
%% [atom()|{atom(), function/0 | function/1}]
@@ -503,7 +507,7 @@ has_support_ipv6() ->
%% so for windows we need to use the old style...
old_has_support_ipv6();
_ ->
- socket:supports(ipv6) andalso has_valid_ipv6_address()
+ socket:is_supported(ipv6) andalso has_valid_ipv6_address()
end.
has_valid_ipv6_address() ->
@@ -619,11 +623,31 @@ old_is_ipv6_host(Hostname) ->
init_per_suite(Config) ->
+ ct:timetrap(minutes(2)),
+
+ try analyze_and_print_host_info() of
+ {Factor, HostInfo} when is_integer(Factor) ->
+ try maybe_skip(HostInfo) of
+ true ->
+ {skip, "Unstable host and/or os (or combo thererof)"};
+ false ->
+ snmp_test_global_sys_monitor:start(),
+ [{snmp_factor, Factor} | Config]
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end
+ catch
+ throw:{skip, _} = SKIP ->
+ SKIP
+ end.
+
+maybe_skip(_HostInfo) ->
+
%% We have some crap machines that causes random test case failures
%% for no obvious reason. So, attempt to identify those without actually
%% checking for the host name...
- %% We have two "machines" we are checking for. Both are old installations
- %% running on really slow VMs (the host machines are old and tired).
+
LinuxVersionVerify =
fun(V) when (V > {3,6,11}) ->
false; % OK - No skip
@@ -634,8 +658,30 @@ init_per_suite(Config) ->
_ ->
false
end;
+ (V) when (V =:= {3,4,20}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Wind River Linux 5.0.1.0" ++ _ -> % *Old* Wind River => skip
+ true;
+ _ ->
+ false
+ end;
+ (V) when (V =:= {2,6,32}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "Debian GNU/Linux 6.0 " ++ _ -> % Stone age Debian => Skip
+ true;
+ _ ->
+ false
+ end;
(V) when (V > {2,6,24}) ->
false; % OK - No skip
+ (V) when (V =:= {2,6,10}) ->
+ case string:trim(os:cmd("cat /etc/issue")) of
+ "MontaVista" ++ _ -> % Stone age MontaVista => Skip
+ %% The real problem is that the machine is *very* slow
+ true;
+ _ ->
+ false
+ end;
(_) ->
%% We are specifically checking for
%% a *really* old gento...
@@ -646,15 +692,33 @@ init_per_suite(Config) ->
true
end
end,
- COND = [{unix, [{linux, LinuxVersionVerify}]}],
- case os_based_skip(COND) of
- true ->
- {skip, "Unstable host and/or os (or combo thererof)"};
- false ->
- Factor = analyze_and_print_host_info(),
- snmp_test_global_sys_monitor:start(),
- [{snmp_factor, Factor} | Config]
- end.
+ DarwinVersionVerify =
+ fun(V) when (V > {9, 8, 0}) ->
+ %% This version is OK: No Skip
+ false;
+ (_V) ->
+ %% This version is *not* ok: Skip
+ true
+ end,
+ SkipWindowsOnVirtual =
+ %% fun() ->
+ %% SysMan = win_sys_info_lookup(system_manufacturer, HostInfo),
+ %% case string:to_lower(SysMan) of
+ %% "vmware" ++ _ ->
+ %% true;
+ %% _ ->
+ %% false
+ %% end
+ %% end,
+ fun() ->
+ %% The host has been replaced and the VM has been reinstalled
+ %% so for now we give it a chance...
+ false
+ end,
+ COND = [{unix, [{linux, LinuxVersionVerify},
+ {darwin, DarwinVersionVerify}]},
+ {win32, SkipWindowsOnVirtual}],
+ os_based_skip(COND).
end_per_suite(Config) when is_list(Config) ->
@@ -795,7 +859,7 @@ skip(Reason, Module, Line) ->
%% when analyzing the test suite (results).
%% It also returns a "factor" that can be used when deciding
%% the load for some test cases. Such as run time or number of
-%% iteraions. This only works for some OSes.
+%% iterations. This only works for some OSes.
%%
%% We make some calculations on Linux, OpenBSD and FreeBSD.
%% On SunOS we always set the factor to 2 (just to be on the safe side)
@@ -817,122 +881,329 @@ analyze_and_print_host_info() ->
analyze_and_print_openbsd_host_info(Version);
{unix, freebsd} ->
analyze_and_print_freebsd_host_info(Version);
+ {unix, netbsd} ->
+ analyze_and_print_netbsd_host_info(Version);
+ {unix, darwin} ->
+ analyze_and_print_darwin_host_info(Version);
{unix, sunos} ->
analyze_and_print_solaris_host_info(Version);
{win32, nt} ->
analyze_and_print_win_host_info(Version);
_ ->
io:format("OS Family: ~p"
- "~n OS Type: ~p"
- "~n Version: ~p"
- "~n Num Schedulers: ~s"
+ "~n OS Type: ~p"
+ "~n Version: ~p"
+ "~n Num Online Schedulers: ~s"
"~n", [OsFam, OsName, Version, str_num_schedulers()]),
- try erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- N when (N =< 6) ->
- 2;
- _ ->
- 1
- catch
- _:_:_ ->
- 10
- end
+ {num_schedulers_to_factor(), []}
end.
-
-analyze_and_print_linux_host_info(Version) ->
+
+linux_which_distro(Version) ->
case file:read_file_info("/etc/issue") of
{ok, _} ->
- io:format("Linux: ~s"
- "~n ~s"
- "~n",
- [Version, string:trim(os:cmd("cat /etc/issue"))]);
+ case [string:trim(S) ||
+ S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of
+ [DistroStr|_] ->
+ io:format("Linux: ~s"
+ "~n ~s"
+ "~n",
+ [Version, DistroStr]),
+ case DistroStr of
+ "Wind River Linux" ++ _ ->
+ wind_river;
+ "MontaVista" ++ _ ->
+ montavista;
+ "Yellow Dog" ++ _ ->
+ yellow_dog;
+ _ ->
+ other
+ end;
+ X ->
+ io:format("Linux: ~s"
+ "~n ~p"
+ "~n",
+ [Version, X]),
+ other
+ end;
_ ->
io:format("Linux: ~s"
- "~n", [Version])
- end,
+ "~n", [Version]),
+ other
+ end.
+
+analyze_and_print_linux_host_info(Version) ->
+ Distro =
+ case file:read_file_info("/etc/issue") of
+ {ok, _} ->
+ linux_which_distro(Version);
+ _ ->
+ io:format("Linux: ~s"
+ "~n", [Version]),
+ other
+ end,
Factor =
- case (catch linux_which_cpuinfo()) of
+ case (catch linux_which_cpuinfo(Distro)) of
{ok, {CPU, BogoMIPS}} ->
io:format("CPU: "
- "~n Model: ~s"
- "~n BogoMIPS: ~s"
- "~n", [CPU, BogoMIPS]),
- %% We first assume its a float, and if not try integer
- try list_to_float(string:trim(BogoMIPS)) of
- F when F > 1000 ->
+ "~n Model: ~s"
+ "~n BogoMIPS: ~w"
+ "~n Num Online Schedulers: ~s"
+ "~n", [CPU, BogoMIPS, str_num_schedulers()]),
+ if
+ (BogoMIPS > 20000) ->
1;
- F when F > 1000 ->
+ (BogoMIPS > 10000) ->
2;
- _ ->
- 3
- catch
- _:_:_ ->
- %%
- try list_to_integer(string:trim(BogoMIPS)) of
- I when I > 1000 ->
- 1;
- I when I > 1000 ->
- 2;
- _ ->
- 3
- catch
- _:_:_ ->
- 1
- end
+ (BogoMIPS > 5000) ->
+ 3;
+ (BogoMIPS > 2000) ->
+ 5;
+ (BogoMIPS > 1000) ->
+ 8;
+ true ->
+ 10
+ end;
+ {ok, CPU} ->
+ io:format("CPU: "
+ "~n Model: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n", [CPU, str_num_schedulers()]),
+ NumChed = erlang:system_info(schedulers),
+ if
+ (NumChed > 2) ->
+ 2;
+ true ->
+ 5
end;
_ ->
- 1
+ 5
end,
%% Check if we need to adjust the factor because of the memory
try linux_which_meminfo() of
AddFactor ->
- io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
- Factor + AddFactor
+ io:format("TS Scale Factor: ~w (~w + ~w)~n",
+ [timetrap_scale_factor(), Factor, AddFactor]),
+ {Factor + AddFactor, []}
catch
_:_:_ ->
- io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
- Factor
+ io:format("TS Scale Factor: ~w (~w)~n",
+ [timetrap_scale_factor(), Factor]),
+ {Factor, []}
+ end.
+
+
+
+linux_cpuinfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/cpuinfo").
+
+linux_cpuinfo_cpu() ->
+ case linux_cpuinfo_lookup("cpu") of
+ [Model] ->
+ Model;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_motherboard() ->
+ case linux_cpuinfo_lookup("motherboard") of
+ [MB] ->
+ MB;
+ _ ->
+ "-"
end.
-linux_which_cpuinfo() ->
- %% Check for x86 (Intel or AMD)
+linux_cpuinfo_bogomips() ->
+ case linux_cpuinfo_lookup("bogomips") of
+ BMips when is_list(BMips) ->
+ try lists:sum([bogomips_to_int(BM) || BM <- BMips])
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_BogoMIPS() ->
+ case linux_cpuinfo_lookup("BogoMIPS") of
+ BMips when is_list(BMips) ->
+ try lists:sum([bogomips_to_int(BM) || BM <- BMips])
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_total_bogomips() ->
+ case linux_cpuinfo_lookup("total bogomips") of
+ [TBM] ->
+ try bogomips_to_int(TBM)
+ catch
+ _:_:_ ->
+ "-"
+ end;
+ _ ->
+ "-"
+ end.
+
+bogomips_to_int(BM) ->
+ try list_to_float(BM) of
+ F ->
+ floor(F)
+ catch
+ _:_:_ ->
+ try list_to_integer(BM) of
+ I ->
+ I
+ catch
+ _:_:_ ->
+ throw(noinfo)
+ end
+ end.
+
+linux_cpuinfo_model() ->
+ case linux_cpuinfo_lookup("model") of
+ [M|_] ->
+ M;
+ _X ->
+ "-"
+ end.
+
+linux_cpuinfo_platform() ->
+ case linux_cpuinfo_lookup("platform") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_model_name() ->
+ case linux_cpuinfo_lookup("model name") of
+ [M|_] ->
+ M;
+ _ ->
+ "-"
+ end.
+
+linux_cpuinfo_processor() ->
+ case linux_cpuinfo_lookup("Processor") of
+ [P] ->
+ P;
+ _ ->
+ "-"
+ end.
+
+
+linux_which_cpuinfo(montavista) ->
CPU =
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"model name\" /proc/cpuinfo"), [$:,$\n])] of
- ["model name", ModelName | _] ->
- ModelName;
- _ ->
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+linux_which_cpuinfo(yellow_dog) ->
+ CPU =
+ case linux_cpuinfo_cpu() of
+ "-" ->
+ throw(noinfo);
+ Model ->
+ case linux_cpuinfo_motherboard() of
+ "-" ->
+ Model;
+ MB ->
+ Model ++ " (" ++ MB ++ ")"
+ end
+ end,
+ {ok, CPU};
+
+linux_which_cpuinfo(wind_river) ->
+ Model =
+ case linux_cpuinfo_model() of
+ "-" ->
+ %% Try 'model name'
+ case linux_cpuinfo_model_name() of
+ "-" ->
+ throw(noinfo);
+ MN ->
+ MN
+ end;
+ M ->
+ M
+ end,
+ CPU =
+ case linux_cpuinfo_platform() of
+ "-" ->
+ Model;
+ Platform ->
+ Model ++ " (" ++ Platform ++ ")"
+ end,
+ case linux_cpuinfo_total_bogomips() of
+ "-" ->
+ case linux_cpuinfo_BogoMIPS() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+ BMips ->
+ {ok, {CPU, BMips}}
+ end;
+
+%% Check for x86 (Intel or AMD)
+linux_which_cpuinfo(other) ->
+ CPU =
+ case linux_cpuinfo_model_name() of
+ "-" ->
%% ARM (at least some distros...)
- try [string:trim(S) || S <- string:tokens(os:cmd("grep \"Processor\" /proc/cpuinfo"), [$:,$\n])] of
- ["Processor", Proc | _] ->
- Proc;
- _ ->
+ case linux_cpuinfo_processor() of
+ "-" ->
%% Ok, we give up
- throw(noinfo)
- catch
- _:_:_ ->
- throw(noinfo)
- end
- catch
- _:_:_ ->
- throw(noinfo)
+ throw(noinfo);
+ Proc ->
+ Proc
+ end;
+ ModelName ->
+ ModelName
end,
- try [string:trim(S) || S <- string:tokens(os:cmd("grep -i \"bogomips\" /proc/cpuinfo"), [$:,$\n])] of
- [_, BMips | _] ->
- {ok, {CPU, BMips}};
- _ ->
- {ok, CPU}
- catch
- _:_:_ ->
- {ok, CPU}
+ case linux_cpuinfo_bogomips() of
+ "-" ->
+ {ok, CPU};
+ BMips ->
+ {ok, {CPU, BMips}}
end.
+linux_meminfo_lookup(Key) when is_list(Key) ->
+ linux_info_lookup(Key, "/proc/meminfo").
+
+linux_meminfo_memtotal() ->
+ case linux_meminfo_lookup("MemTotal") of
+ [X] ->
+ X;
+ _ ->
+ "-"
+ end.
+
%% We *add* the value this return to the Factor.
linux_which_meminfo() ->
- try [string:trim(S) || S <- string:tokens(os:cmd("grep MemTotal /proc/meminfo"), [$:])] of
- [_, MemTotal] ->
+ case linux_meminfo_memtotal() of
+ "-" ->
+ 0;
+ MemTotal ->
io:format("Memory:"
"~n ~s"
"~n", [MemTotal]),
@@ -956,18 +1227,13 @@ linux_which_meminfo() ->
(MemSz3 >= 4194304) ->
1;
(MemSz3 >= 2097152) ->
- 2;
+ 3;
true ->
- 3
+ 5
end;
_X ->
0
- end;
- _ ->
- 0
- catch
- _:_:_ ->
- 0
+ end
end.
@@ -1054,12 +1320,12 @@ analyze_and_print_openbsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
- 1
+ {2, []}
end.
@@ -1095,10 +1361,10 @@ analyze_and_print_freebsd_host_info(Version) ->
NCPU = analyze_freebsd_ncpu(Extract),
Memory = analyze_freebsd_memory(Extract),
io:format("CPU:"
- "~n Model: ~s"
- "~n Speed: ~w"
- "~n N: ~w"
- "~n Num Schedulers: ~s"
+ "~n Model: ~s"
+ "~n Speed: ~w"
+ "~n N: ~w"
+ "~n Num Online Schedulers: ~s"
"~nMemory:"
"~n ~w KB"
"~n",
@@ -1142,22 +1408,23 @@ analyze_and_print_freebsd_host_info(Version) ->
true ->
3
end,
- CPUFactor + MemAddFactor
+ {CPUFactor + MemAddFactor, []}
end
catch
_:_:_ ->
io:format("CPU:"
- "~n Num Schedulers: ~s"
+ "~n Num Online Schedulers: ~s"
"~n", [str_num_schedulers()]),
io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
- case erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- _ ->
- 2
- end
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
end.
@@ -1198,6 +1465,426 @@ analyze_freebsd_item(Extract, Key, Process, Default) ->
end.
+analyze_and_print_netbsd_host_info(Version) ->
+ io:format("NetBSD:"
+ "~n Version: ~p"
+ "~n", [Version]),
+ %% This test require that the program 'sysctl' is in the path.
+ %% First test with 'which sysctl', if that does not work
+ %% try with 'which /sbin/sysctl'. If that does not work either,
+ %% we skip the test...
+ try
+ begin
+ SysCtl =
+ case string:trim(os:cmd("which sysctl")) of
+ [] ->
+ case string:trim(os:cmd("which /sbin/sysctl")) of
+ [] ->
+ throw(sysctl);
+ SC2 ->
+ SC2
+ end;
+ SC1 ->
+ SC1
+ end,
+ Extract =
+ fun(Key) ->
+ [string:trim(S) ||
+ S <-
+ string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
+ [$=])]
+ end,
+ CPU = analyze_netbsd_cpu(Extract),
+ Machine = analyze_netbsd_machine(Extract),
+ Arch = analyze_netbsd_machine_arch(Extract),
+ CPUSpeed = analyze_netbsd_cpu_speed(Extract),
+ NCPU = analyze_netbsd_ncpu(Extract),
+ Memory = analyze_netbsd_memory(Extract),
+ io:format("CPU:"
+ "~n Model: ~s (~s, ~s)"
+ "~n Speed: ~w MHz"
+ "~n N: ~w"
+ "~n Num Schedulers: ~w"
+ "~nMemory:"
+ "~n ~w KB"
+ "~n",
+ [CPU, Machine, Arch, CPUSpeed, NCPU,
+ erlang:system_info(schedulers), Memory]),
+ CPUFactor =
+ if
+ (CPUSpeed =:= -1) ->
+ 1;
+ (CPUSpeed >= 2000) ->
+ if
+ (NCPU >= 4) ->
+ 1;
+ (NCPU >= 2) ->
+ 2;
+ true ->
+ 3
+ end;
+ true ->
+ if
+ (NCPU =:= -1) ->
+ 1;
+ (NCPU >= 4) ->
+ 2;
+ (NCPU >= 2) ->
+ 3;
+ true ->
+ 4
+ end
+ end,
+ MemAddFactor =
+ if
+ (Memory =:= -1) ->
+ 0;
+ (Memory >= 8388608) ->
+ 0;
+ (Memory >= 4194304) ->
+ 1;
+ (Memory >= 2097152) ->
+ 2;
+ true ->
+ 3
+ end,
+ {CPUFactor + MemAddFactor, []}
+ end
+ catch
+ _:_:_ ->
+ io:format("CPU:"
+ "~n Num Schedulers: ~w"
+ "~n", [erlang:system_info(schedulers)]),
+ Factor = case erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ _ ->
+ 2
+ end,
+ {Factor, []}
+ end.
+
+analyze_netbsd_cpu(Extract) ->
+ analyze_netbsd_item(Extract, "hw.model", fun(X) -> X end, "-").
+
+analyze_netbsd_machine(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine", fun(X) -> X end, "-").
+
+analyze_netbsd_machine_arch(Extract) ->
+ analyze_netbsd_item(Extract, "hw.machine_arch", fun(X) -> X end, "-").
+
+analyze_netbsd_cpu_speed(Extract) ->
+ analyze_netbsd_item(Extract, "machdep.dmi.processor-frequency",
+ fun(X) -> case string:tokens(X, [$\ ]) of
+ [MHz, "MHz"] ->
+ list_to_integer(MHz);
+ _ ->
+ -1
+ end
+ end, "-").
+
+analyze_netbsd_ncpu(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.ncpu",
+ fun(X) -> list_to_integer(X) end,
+ -1).
+
+analyze_netbsd_memory(Extract) ->
+ analyze_netbsd_item(Extract,
+ "hw.physmem64",
+ fun(X) -> list_to_integer(X) div 1024 end,
+ -1).
+
+analyze_netbsd_item(Extract, Key, Process, Default) ->
+ analyze_freebsd_item(Extract, Key, Process, Default).
+
+
+
+%% Model Identifier: Macmini7,1
+%% Processor Name: Intel Core i5
+%% Processor Speed: 2,6 GHz
+%% Number of Processors: 1
+%% Total Number of Cores: 2
+%% L2 Cache (per Core): 256 KB
+%% L3 Cache: 3 MB
+%% Hyper-Threading Technology: Enabled
+%% Memory: 16 GB
+
+analyze_and_print_darwin_host_info(Version) ->
+ %% This stuff is for macOS.
+ %% If we ever tested on a pure darwin machine,
+ %% we need to find some other way to find some info...
+ %% Also, I suppose its possible that we for some other
+ %% reason *fail* to get the info...
+ case analyze_darwin_software_info() of
+ [] ->
+ io:format("Darwin:"
+ "~n Version: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n", [Version, str_num_schedulers()]),
+ {num_schedulers_to_factor(), []};
+ SwInfo when is_list(SwInfo) ->
+ SystemVersion = analyze_darwin_sw_system_version(SwInfo),
+ KernelVersion = analyze_darwin_sw_kernel_version(SwInfo),
+ HwInfo = analyze_darwin_hardware_info(),
+ ModelName = analyze_darwin_hw_model_name(HwInfo),
+ ModelId = analyze_darwin_hw_model_identifier(HwInfo),
+ ProcName = analyze_darwin_hw_processor_name(HwInfo),
+ ProcSpeed = analyze_darwin_hw_processor_speed(HwInfo),
+ NumProc = analyze_darwin_hw_number_of_processors(HwInfo),
+ NumCores = analyze_darwin_hw_total_number_of_cores(HwInfo),
+ Memory = analyze_darwin_hw_memory(HwInfo),
+ io:format("Darwin:"
+ "~n System Version: ~s"
+ "~n Kernel Version: ~s"
+ "~n Model: ~s (~s)"
+ "~n Processor: ~s (~s, ~s, ~s)"
+ "~n Memory: ~s"
+ "~n Num Online Schedulers: ~s"
+ "~n", [SystemVersion, KernelVersion,
+ ModelName, ModelId,
+ ProcName, ProcSpeed, NumProc, NumCores,
+ Memory,
+ str_num_schedulers()]),
+ CPUFactor = analyze_darwin_cpu_to_factor(ProcName,
+ ProcSpeed,
+ NumProc,
+ NumCores),
+ MemFactor = analyze_darwin_memory_to_factor(Memory),
+ if (MemFactor =:= 1) ->
+ {CPUFactor, []};
+ true ->
+ {CPUFactor + MemFactor, []}
+ end
+ end.
+
+analyze_darwin_sw_system_version(SwInfo) ->
+ proplists:get_value("system version", SwInfo, "-").
+
+analyze_darwin_sw_kernel_version(SwInfo) ->
+ proplists:get_value("kernel version", SwInfo, "-").
+
+analyze_darwin_software_info() ->
+ analyze_darwin_system_profiler("SPSoftwareDataType").
+
+analyze_darwin_hw_model_name(HwInfo) ->
+ proplists:get_value("model name", HwInfo, "-").
+
+analyze_darwin_hw_model_identifier(HwInfo) ->
+ proplists:get_value("model identifier", HwInfo, "-").
+
+analyze_darwin_hw_processor_name(HwInfo) ->
+ proplists:get_value("processor name", HwInfo, "-").
+
+analyze_darwin_hw_processor_speed(HwInfo) ->
+ proplists:get_value("processor speed", HwInfo, "-").
+
+analyze_darwin_hw_number_of_processors(HwInfo) ->
+ proplists:get_value("number of processors", HwInfo, "-").
+
+analyze_darwin_hw_total_number_of_cores(HwInfo) ->
+ proplists:get_value("total number of cores", HwInfo, "-").
+
+analyze_darwin_hw_memory(HwInfo) ->
+ proplists:get_value("memory", HwInfo, "-").
+
+analyze_darwin_hardware_info() ->
+ analyze_darwin_system_profiler("SPHardwareDataType").
+
+%% This basically has the structure: "Key: Value"
+%% But could also be (for example):
+%% "Something:" (which we ignore)
+%% "Key: Value1:Value2"
+analyze_darwin_system_profiler(DataType) ->
+ %% First, make sure the program actually exist:
+ case os:cmd("which system_profiler") of
+ [] ->
+ [];
+ _ ->
+ D0 = os:cmd("system_profiler " ++ DataType),
+ D1 = string:tokens(D0, [$\n]),
+ D2 = [string:trim(S1) || S1 <- D1],
+ D3 = [string:tokens(S2, [$:]) || S2 <- D2],
+ analyze_darwin_system_profiler2(D3)
+ end.
+
+analyze_darwin_system_profiler2(L) ->
+ analyze_darwin_system_profiler2(L, []).
+
+analyze_darwin_system_profiler2([], Acc) ->
+ [{string:to_lower(K), V} || {K, V} <- lists:reverse(Acc)];
+analyze_darwin_system_profiler2([[_]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, Acc);
+analyze_darwin_system_profiler2([[H1,H2]|T], Acc) ->
+ analyze_darwin_system_profiler2(T, [{H1, string:trim(H2)}|Acc]);
+analyze_darwin_system_profiler2([[H|TH0]|T], Acc) ->
+ %% Some value parts has ':' in them, so put them together
+ TH1 = colonize(TH0),
+ analyze_darwin_system_profiler2(T, [{H, string:trim(TH1)}|Acc]).
+
+%% This is only called if the length is at least 2
+colonize([L1, L2]) ->
+ L1 ++ ":" ++ L2;
+colonize([H|T]) ->
+ H ++ ":" ++ colonize(T).
+
+
+%% The memory looks like this "<size> <unit>". Example: "2 GB"
+analyze_darwin_memory_to_factor(Mem) ->
+ case [string:to_lower(S) || S <- string:tokens(Mem, [$\ ])] of
+ [_SzStr, "tb"] ->
+ 1;
+ [SzStr, "gb"] ->
+ try list_to_integer(SzStr) of
+ Sz when Sz < 2 ->
+ 20;
+ Sz when Sz < 4 ->
+ 10;
+ Sz when Sz < 8 ->
+ 5;
+ Sz when Sz < 16 ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 20
+ end;
+ [_SzStr, "mb"] ->
+ 20;
+ _ ->
+ 20
+ end.
+
+
+%% The speed is a string: "<speed> <unit>"
+%% the speed may be a float, which we transforms into an integer of MHz.
+%% To calculate a factor based on processor speed, number of procs
+%% and number of cores is ... not an exact ... science ...
+analyze_darwin_cpu_to_factor(_ProcName,
+ ProcSpeedStr, NumProcStr, NumCoresStr) ->
+ Speed =
+ case [string:to_lower(S) || S <- string:tokens(ProcSpeedStr, [$\ ])] of
+ [SpeedStr, "mhz"] ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ SpeedI
+ catch
+ _:_:_ ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(SpeedF)
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ [SpeedStr, "ghz"] ->
+ try list_to_float(SpeedStr) of
+ SpeedF ->
+ trunc(1000*SpeedF)
+ catch
+ _:_:_ ->
+ try list_to_integer(SpeedStr) of
+ SpeedI ->
+ 1000*SpeedI
+ catch
+ _:_:_ ->
+ -1
+ end
+ end;
+ _ ->
+ -1
+ end,
+ NumProc = try list_to_integer(NumProcStr) of
+ NumProcI ->
+ NumProcI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ NumCores = try list_to_integer(NumCoresStr) of
+ NumCoresI ->
+ NumCoresI
+ catch
+ _:_:_ ->
+ 1
+ end,
+ if
+ (Speed > 3000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 5;
+ (NumCores < 4) ->
+ 3;
+ (NumCores < 6) ->
+ 2;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ (Speed > 2000) ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 8;
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 6) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 5;
+ (NumCores < 8) ->
+ 2;
+ true ->
+ 1
+ end
+ end;
+ true ->
+ if
+ (NumProc =:= 1) ->
+ if
+ (NumCores < 2) ->
+ 10;
+ (NumCores < 4) ->
+ 7;
+ (NumCores < 6) ->
+ 5;
+ (NumCores < 8) ->
+ 3;
+ true ->
+ 1
+ end;
+ true ->
+ if
+ (NumCores < 4) ->
+ 8;
+ (NumCores < 8) ->
+ 4;
+ true ->
+ 1
+ end
+ end
+ end.
+
+
analyze_and_print_solaris_host_info(Version) ->
Release =
case file:read_file_info("/etc/release") of
@@ -1275,13 +1962,13 @@ analyze_and_print_solaris_host_info(Version) ->
"-"
end,
io:format("Solaris: ~s"
- "~n Release: ~s"
- "~n Banner Name: ~s"
- "~n Instruction Set: ~s"
- "~n CPUs: ~s (~s)"
- "~n System Config: ~s"
- "~n Memory Size: ~s"
- "~n Num Schedulers: ~s"
+ "~n Release: ~s"
+ "~n Banner Name: ~s"
+ "~n Instruction Set: ~s"
+ "~n CPUs: ~s (~s)"
+ "~n System Config: ~s"
+ "~n Memory Size: ~s"
+ "~n Num Online Schedulers: ~s"
"~n~n", [Version, Release, BannerName, InstructionSet,
NumPhysCPU, NumVCPU,
SysConf, MemSz,
@@ -1323,19 +2010,19 @@ analyze_and_print_solaris_host_info(Version) ->
_:_:_ ->
10
end,
- try erlang:system_info(schedulers) of
- 1 ->
- 10;
- 2 ->
- 5;
- N when (N =< 6) ->
- 2;
- _ ->
- 1
- catch
- _:_:_ ->
- 10
- end + MemFactor.
+ {try erlang:system_info(schedulers) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end + MemFactor, []}.
@@ -1353,7 +2040,7 @@ analyze_and_print_win_host_info(Version) ->
"~n System Model: ~s"
"~n Number of Processor(s): ~s"
"~n Total Physical Memory: ~s"
- "~n Num Schedulers: ~s"
+ "~n Num Online Schedulers: ~s"
"~n~n", [OsName, OsVersion, Version,
SysMan, SysMod, NumProcs, TotPhysMem,
str_num_schedulers()]),
@@ -1409,7 +2096,7 @@ analyze_and_print_win_host_info(Version) ->
_ ->
2
end,
- CPUFactor + MemFactor.
+ {CPUFactor + MemFactor, SysInfo}.
win_sys_info_lookup(Key, SysInfo) ->
win_sys_info_lookup(Key, SysInfo, "-").
@@ -1424,14 +2111,24 @@ win_sys_info_lookup(Key, SysInfo, Def) ->
%% This function only extracts the prop we actually care about!
which_win_system_info() ->
- SysInfo = os:cmd("systeminfo"),
- try process_win_system_info(string:tokens(SysInfo, [$\r, $\n]), [])
- catch
- _:_:_ ->
- io:format("Failed process System info: "
- "~s~n", [SysInfo]),
- []
- end.
+ F = fun() ->
+ try
+ begin
+ SysInfo = os:cmd("systeminfo"),
+ process_win_system_info(
+ string:tokens(SysInfo, [$\r, $\n]), [])
+ end
+ catch
+ C:E:S ->
+ io:format("Failed get or process System info: "
+ " Error Class: ~p"
+ " Error: ~p"
+ " Stack: ~p"
+ "~n", [C, E, S]),
+ []
+ end
+ end,
+ proxy_call(F, minutes(1), []).
process_win_system_info([], Acc) ->
Acc;
@@ -1469,13 +2166,59 @@ process_win_system_info([H|T], Acc) ->
str_num_schedulers() ->
- try erlang:system_info(schedulers) of
+ try erlang:system_info(schedulers_online) of
N -> f("~w", [N])
catch
_:_:_ -> "-"
end.
-
+num_schedulers_to_factor() ->
+ try erlang:system_info(schedulers_online) of
+ 1 ->
+ 10;
+ 2 ->
+ 5;
+ N when (N =< 6) ->
+ 2;
+ _ ->
+ 1
+ catch
+ _:_:_ ->
+ 10
+ end.
+
+
+linux_info_lookup(Key, File) ->
+ %% try
+ %% begin
+ %% GREP = os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File),
+ %% io:format("linux_info_lookup() -> GREP: ~p~n", [GREP]),
+ %% TOKENS = string:tokens(GREP, [$:,$\n]),
+ %% io:format("linux_info_lookup() -> TOKENS: ~p~n", [TOKENS]),
+ %% INFO = [string:trim(S) || S <- TOKENS],
+ %% io:format("linux_info_lookup() -> INFO: ~p~n", [INFO]),
+ %% linux_info_lookup_collect(Key, INFO, [])
+ %% end
+ %% catch
+ %% _:_:_ ->
+ %% "-"
+ %% end.
+ try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of
+ Info ->
+ linux_info_lookup_collect(Key, Info, [])
+ catch
+ _:_:_ ->
+ "-"
+ end.
+
+linux_info_lookup_collect(_Key, [], Values) ->
+ lists:reverse(Values);
+linux_info_lookup_collect(Key, [Key, Value|Rest], Values) ->
+ linux_info_lookup_collect(Key, Rest, [Value|Values]);
+linux_info_lookup_collect(_, _, Values) ->
+ lists:reverse(Values).
+
+
%% ----------------------------------------------------------------
%% Time related function
@@ -1501,7 +2244,18 @@ sleep(MSecs) ->
%% ----------------------------------------------------------------
%% Process utility function
-%%
+%%
+
+mqueue() ->
+ mqueue(self()).
+mqueue(Pid) when is_pid(Pid) ->
+ Key = messages,
+ case process_info(Pid, Key) of
+ {Key, Msgs} ->
+ Msgs;
+ _ ->
+ []
+ end.
flush_mqueue() ->
io:format("~p~n", [lists:reverse(flush_mqueue([]))]).
@@ -1516,10 +2270,10 @@ flush_mqueue(MQ) ->
trap_exit() ->
- {trap_exit,Flag} = process_info(self(),trap_exit),Flag.
+ {trap_exit, Flag} = process_info(self(),trap_exit), Flag.
trap_exit(Flag) ->
- process_flag(trap_exit,Flag).
+ process_flag(trap_exit, Flag).
diff --git a/lib/snmp/test/snmp_test_lib.hrl b/lib/snmp/test/snmp_test_lib.hrl
index f4863c9a1e..78d1453c12 100644
--- a/lib/snmp/test/snmp_test_lib.hrl
+++ b/lib/snmp/test/snmp_test_lib.hrl
@@ -52,10 +52,13 @@
-define(OS_BASED_SKIP(Skippable), ?LIB:os_based_skip(Skippable)).
-define(NON_PC_TC_MAYBE_SKIP(Config, Condition),
?LIB:non_pc_tc_maybe_skip(Config, Condition, ?MODULE, ?LINE)).
+
-define(SKIP(Reason), ?LIB:skip(Reason, ?MODULE, ?LINE)).
-define(FAIL(Reason), ?LIB:fail(Reason, ?MODULE, ?LINE)).
-define(HAS_SUPPORT_IPV6(), ?LIB:has_support_ipv6()).
+-define(PCALL(F, T, D), ?LIB:proxy_call(F, T, D)).
+
%% - Time macros -
@@ -82,6 +85,8 @@
%% - Process utility macros -
-define(FLUSH(), ?LIB:flush_mqueue()).
+-define(MQUEUE(), ?LIB:mqueue()).
+-define(MQUEUE(P), ?LIB:mqueue(P)).
-define(ETRAP_GET(), ?LIB:trap_exit()).
-define(ETRAP_SET(O), ?LIB:trap_exit(O)).
-define(PINFO(__P__), try process_info(__P__)
diff --git a/lib/snmp/test/snmp_test_manager.erl b/lib/snmp/test/snmp_test_manager.erl
index 6ab5ce164c..fcbc0dff6b 100644
--- a/lib/snmp/test/snmp_test_manager.erl
+++ b/lib/snmp/test/snmp_test_manager.erl
@@ -193,22 +193,22 @@ handle_call(stop, _From, S) ->
handle_call({sync_get, Oids}, _From,
#state{agent_target_name = TargetName} = S) ->
- Reply = (catch snmpm:sync_get(?USER, TargetName, Oids)),
+ Reply = (catch snmpm:sync_get2(?USER, TargetName, Oids)),
{reply, Reply, S};
handle_call({sync_get_next, Oids}, _From,
#state{agent_target_name = TargetName} = S) ->
- Reply = (catch snmpm:sync_get_next(?USER, TargetName, Oids)),
+ Reply = (catch snmpm:sync_get_next2(?USER, TargetName, Oids)),
{reply, Reply, S};
handle_call({sync_get_bulk, NR, MR, Oids}, _From,
#state{agent_target_name = TargetName} = S) ->
- Reply = (catch snmpm:sync_get_bulk(?USER, TargetName, NR, MR, Oids)),
+ Reply = (catch snmpm:sync_get_bulk2(?USER, TargetName, NR, MR, Oids)),
{reply, Reply, S};
handle_call({sync_set, VarsAndVals}, _From,
#state{agent_target_name = TargetName} = S) ->
- Reply = (catch snmpm:sync_set(?USER, TargetName, VarsAndVals)),
+ Reply = (catch snmpm:sync_set2(?USER, TargetName, VarsAndVals)),
{reply, Reply, S};
handle_call(Req, From, State) ->
diff --git a/lib/snmp/test/snmp_test_mgr.erl b/lib/snmp/test/snmp_test_mgr.erl
index 0a80860ed1..fe2852c573 100644
--- a/lib/snmp/test/snmp_test_mgr.erl
+++ b/lib/snmp/test/snmp_test_mgr.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -168,6 +168,7 @@ get_timeout() ->
get_timeout(_) -> 10000. % Trying to improve test results % 3500.
+
%%----------------------------------------------------------------------
%% Receives a trap from the agent.
%% Returns: TrapPdu|{error, Reason}
@@ -208,7 +209,8 @@ init({Options, CallerPid}) ->
RecBufSz = get_value(recbuf, Options, 1024),
?IPRINT("init -> RecBufSz: ~p", [RecBufSz]),
Mibs = get_value(mibs, Options, []),
- ?IPRINT("init -> Mibs: ~p", [Mibs]),
+ ?IPRINT("init -> Mibs: "
+ "~n ~tp", [Mibs]),
Udp = get_value(agent_udp, Options, 4000),
?IPRINT("init -> Udp: ~p", [Udp]),
User = get_value(user, Options, "initial"),
@@ -311,6 +313,9 @@ is_options_ok([{ipfamily,IpFamily}|Opts])
is_options_ok(Opts);
is_options_ok([{agent_udp,Int}|Opts]) when is_integer(Int) ->
is_options_ok(Opts);
+is_options_ok([{agent_udp, {IntR, IntT}}|Opts]) when is_integer(IntR) andalso
+ is_integer(IntT) ->
+ is_options_ok(Opts);
is_options_ok([{trap_udp,Int}|Opts]) when is_integer(Int) ->
is_options_ok(Opts);
is_options_ok([{community,List}|Opts]) when is_list(List) ->
@@ -428,8 +433,8 @@ handle_cast(iter_get_next, State)
{noreply, execute_request(get_next, Oids, State)};
handle_cast(iter_get_next, State) ->
- ?PACK_SERV:error("[Iterated get-next] No Response PDU to "
- "start iterating from.", []),
+ ?EPRINT("[Iterated get-next] No Response PDU to "
+ "start iterating from.", []),
{noreply, State};
handle_cast({iter_get_next, N}, State) ->
@@ -441,8 +446,8 @@ handle_cast({iter_get_next, N}, State) ->
State#state.packet_server),
{noreply, State#state{last_received_pdu = PDU}};
true ->
- ?PACK_SERV:error("[Iterated get-next] No Response PDU to "
- "start iterating from.", []),
+ ?EPRINT("[Iterated get-next] No Response PDU to "
+ "start iterating from.", []),
{noreply, State}
end;
@@ -492,15 +497,23 @@ handle_info({snmp_msg, Msg, Ip, Udp}, State) ->
end,
{noreply, State#state{last_received_pdu = PDU}};
+handle_info({'EXIT', Pid, Reason}, #state{packet_server = Pid} = State) ->
+ error_logger:error_msg("Received unexpected exit signal from Packet Server ~p: "
+ "~n ~p", [Pid, Reason]),
+ {stop, State#state{packet_server = undefined}};
+
handle_info(Info, State) ->
d("handle_info -> unknown info: "
"~n ~p", [Info]),
{noreply, State}.
-terminate(Reason, State) ->
+terminate(Reason, #state{packet_server = Pid} = _State) when is_pid(Pid) ->
+ d("terminate -> with Reason: ~n\t~p", [Reason]),
+ ?PACK_SERV:stop(Pid);
+terminate(Reason, _State) ->
d("terminate -> with Reason: ~n\t~p",[Reason]),
- ?PACK_SERV:stop(State#state.packet_server).
+ ok.
%%----------------------------------------------------------------------
@@ -528,7 +541,7 @@ report_error(#state{quiet = true, parent = Pid}, Format, Args) ->
Reason = lists:flatten(io_lib:format(Format, Args)),
Pid ! {oid_error, Reason};
report_error(_, Format, Args) ->
- ?PACK_SERV:error(Format, Args).
+ ?EPRINT(Format, Args).
get_oid_from_varbind(#varbind{oid = Oid}) -> Oid.
@@ -690,13 +703,13 @@ echo_pdu(PDU, MiniMIB) ->
%% Test Sequence
%%----------------------------------------------------------------------
echo_errors({error, Id, {ExpectedFormat, ExpectedData}, {Format, Data}})->
- ?IPRINT("*** Unexpected Behaviour *** Id: ~w.~n"
- " Expected: " ++ ExpectedFormat ++ "~n"
- " Got: " ++ Format ++ "~n",
+ ?EPRINT("*** Unexpected Behaviour *** Id: ~w"
+ "~n Expected: " ++ ExpectedFormat ++
+ "~n Got: " ++ Format ++ "~n",
[Id] ++ ExpectedData ++ Data),
{error, Id, {ExpectedFormat, ExpectedData}, {Format, Data}};
echo_errors(ok) -> ok;
-echo_errors({ok, Val}) -> {ok, Val}.
+echo_errors({ok, _} = OK) -> OK.
get_response_impl(Id, ExpVars) ->
?IPRINT("await response ~w with"
@@ -729,9 +742,12 @@ get_response_impl(Id, ExpVars) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w",
[Type2, Err2, Index2]}};
- {error, Reason} ->
+ {error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive pdu error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
@@ -747,8 +763,11 @@ expect_impl(Id, any) ->
?IPRINT("received expected pdu (~w)", [Id]),
ok;
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -759,8 +778,11 @@ expect_impl(Id, return) ->
?IPRINT("received expected pdu (~w)", [Id]),
{ok, PDU};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -771,8 +793,11 @@ expect_impl(Id, trap) ->
?IPRINT("received expected trap (~w)", [Id]),
ok;
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -809,8 +834,11 @@ expect_impl(Id, Err) when is_atom(Err) ->
{"ErrorStatus: ~w", [Err2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -843,8 +871,11 @@ expect_impl(Id, ExpectedVarbinds) when is_list(ExpectedVarbinds) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
@@ -877,8 +908,11 @@ expect_impl(Id, v2trap, ExpectedVarbinds) when is_list(ExpectedVarbinds) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -911,8 +945,11 @@ expect_impl(Id, report, ExpectedVarbinds) when is_list(ExpectedVarbinds) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -972,8 +1009,11 @@ expect_impl(Id, {inform, Reply}, ExpectedVarbinds)
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
@@ -1031,8 +1071,11 @@ expect_impl(Id, Err, Index, any = _ExpectedVarbinds) ->
{"Type: ~w, ErrStat: ~w, Idx: ~w", [Type2, Err2, Index2]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected (receive) response: "
- "~n ~p", [Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end;
@@ -1110,8 +1153,11 @@ expect_impl(Id, Err, Index, ExpectedVarbinds) ->
[Type2,Err2,Index2,VBs]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive pdu error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
@@ -1160,8 +1206,11 @@ expect_impl(Id, trap, Enterp, Generic, Specific, ExpectedVarbinds) ->
[Ent2, G2, Spec2, VBs]}};
{error, Reason} ->
+ %% We did not get the message we wanted,
+ %% but what did we get?
?EPRINT("unexpected receive trap pdu error: ~w"
- "~n ~p", [Id, Reason]),
+ "~n Reason: ~p"
+ "~n Msg Que: ~p", [Id, Reason, ?MQUEUE()]),
format_reason(Id, Reason)
end.
diff --git a/lib/snmp/test/snmp_test_mgr_misc.erl b/lib/snmp/test/snmp_test_mgr_misc.erl
index 738f45a1b0..2a20ebee42 100644
--- a/lib/snmp/test/snmp_test_mgr_misc.erl
+++ b/lib/snmp/test/snmp_test_mgr_misc.erl
@@ -28,7 +28,6 @@
stop/1,
send_discovery_pdu/2,
send_pdu/2, send_msg/4, send_bytes/2,
- error/2,
get_pdu/1, set_pdu/2, format_hdr/1]).
%% internal exports
@@ -62,9 +61,29 @@ start_link_packet(
start_link_packet(
InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
Dbg, IpFamily) when is_integer(UdpPort) ->
+ do_start_link_packet(InHandler,
+ AgentIp, UdpPort, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
+ Dbg, IpFamily);
+start_link_packet(InHandler,
+ AgentIp, {AReqPort, ATrapPort} = UdpPorts, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
+ Dbg, IpFamily) when is_integer(AReqPort) andalso
+ is_integer(ATrapPort) ->
+ do_start_link_packet(InHandler,
+ AgentIp, UdpPorts, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
+ Dbg, IpFamily).
+
+do_start_link_packet(InHandler,
+ AgentIp, UdpPorts, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
+ Dbg, IpFamily) ->
Args =
[self(),
- InHandler, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
+ InHandler,
+ AgentIp, UdpPorts, TrapUdp,
+ VsnHdr, Version, Dir, BufSz,
Dbg, IpFamily],
proc_lib:start_link(?MODULE, init_packet, Args).
@@ -100,7 +119,7 @@ send_bytes(Bytes, PacketPid) ->
%%--------------------------------------------------
init_packet(
Parent,
- SnmpMgr, AgentIp, UdpPort, TrapUdp, VsnHdr, Version, Dir, BufSz,
+ SnmpMgr, AgentIp, UdpPorts, TrapUdp, VsnHdr, Version, Dir, BufSz,
DbgOptions, IpFamily) ->
%% This causes "verbosity printouts" to print (from the
%% specified level) in the modules we "borrow" from the
@@ -117,7 +136,7 @@ init_packet(
init_usm(Version, Dir),
proc_lib:init_ack(Parent, self()),
?IPRINT("started"),
- packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, []).
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []).
init_debug(Dbg) when is_atom(Dbg) ->
put(debug,Dbg),
@@ -139,7 +158,7 @@ init_debug(DbgOptions) when is_list(DbgOptions) ->
ok.
-packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
+packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, MsgData) ->
receive
{send_discovery_pdu, From, Pdu} ->
d("packet_loop -> received send_discovery_pdu with"
@@ -151,9 +170,10 @@ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
{M, B} when is_list(B) ->
put(discovery, {M, From}),
display_outgoing_message(M),
- udp_send(UdpId, AgentIp, UdpPort, B)
+ Port = select_request_port(UdpPorts),
+ udp_send(UdpId, AgentIp, Port, B)
end,
- packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, []);
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []);
{send_pdu, Pdu} ->
d("packet_loop -> received send_pdu with"
@@ -161,10 +181,11 @@ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
case mk_msg(Version, Pdu, VsnHdr, MsgData) of
error ->
ok;
- B when is_list(B) ->
- udp_send(UdpId, AgentIp, UdpPort, B)
+ B when is_list(B) ->
+ Port = select_request_port(UdpPorts),
+ udp_send(UdpId, AgentIp, Port, B)
end,
- packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,[]);
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []);
{send_msg, Msg, Ip, Udp} ->
d("packet_loop -> received send_msg with"
@@ -173,40 +194,70 @@ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPort, VsnHdr, Version, MsgData) ->
"~n Udp: ~p", [Msg,Ip,Udp]),
case catch snmp_pdus:enc_message(Msg) of
{'EXIT', Reason} ->
- error("Encoding error:"
- "~n Msg: ~w"
- "~n Reason: ~w",[Msg, Reason]);
+ ?EPRINT("Encoding error:"
+ "~n Msg: ~w"
+ "~n Reason: ~w",[Msg, Reason]);
B when is_list(B) ->
udp_send(UdpId, Ip, Udp, B)
end,
- packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,[]);
- {udp, UdpId, Ip, UdpPort, Bytes} ->
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []);
+
+ {udp, UdpId, Ip, Port, Bytes} ->
d("packet_loop -> received udp with"
"~n UdpId: ~p"
"~n Ip: ~p"
- "~n UdpPort: ~p"
- "~n sz(Bytes): ~p", [UdpId, Ip, UdpPort, sz(Bytes)]),
+ "~n Port: ~p (~p)"
+ "~n sz(Bytes): ~p", [UdpId, Ip, Port, UdpPorts, sz(Bytes)]),
+ case UdpPorts of
+ Port ->
+ ok;
+ {Port, _} -> % Should be a (request) response
+ ok;
+ {_, Port} -> % Should be a trap
+ ok;
+ _ ->
+ d("packet_loop -> received packet from unknown port"
+ "~n ~p", [Port]),
+ exit({snmp_packet_from_unknown_port, Port, UdpPorts})
+ end,
MsgData3 = handle_udp_packet(Version, erase(discovery),
- UdpId, Ip, UdpPort, Bytes,
+ UdpId, Ip, Port, Bytes,
SnmpMgr, AgentIp),
- packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version,
MsgData3);
+
+ {udp_error, UdpId, Reason} ->
+ gen_udp:close(UdpId),
+ exit({udp_error, Reason});
+
{send_bytes, B} ->
d("packet_loop -> received send_bytes with"
- "~n sz(B): ~p", [sz(B)]),
- udp_send(UdpId, AgentIp, UdpPort, B),
- packet_loop(SnmpMgr,UdpId,AgentIp,UdpPort,VsnHdr,Version,[]);
+ "~n sz(B): ~p", [sz(B)]),
+ Port = select_request_port(UdpPorts),
+ udp_send(UdpId, AgentIp, Port, B),
+ packet_loop(SnmpMgr, UdpId, AgentIp, UdpPorts, VsnHdr, Version, []);
+
{stop, Pid} ->
d("packet_loop -> received stop from ~p", [Pid]),
gen_udp:close(UdpId),
Pid ! {self(), stopped},
exit(normal);
+
Other ->
d("packet_loop -> received unknown"
"~n ~p", [Other]),
exit({snmp_packet_got_other, Other})
end.
+select_request_port({Port, _}) when is_integer(Port) ->
+ Port;
+select_request_port(Port) when is_integer(Port) ->
+ Port.
+
+%% select_trap_port({_, Port}) when is_integer(Port) ->
+%% Port;
+%% select_trap_port(Port) when is_integer(Port) ->
+%% Port.
handle_udp_packet(_V, undefined,
UdpId, Ip, UdpPort,
@@ -224,9 +275,13 @@ handle_udp_packet(_V, undefined,
catch
Class:Error:_ ->
- error("Decoding error (~w). Bytes: ~w ~n Error: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Class, Bytes, Error, UdpPort, Ip]),
+ ?EPRINT("Decoding error: "
+ "~n Class: ~w"
+ "~n Error: ~p"
+ "~n Bytes: ~p"
+ "~n Port: ~w"
+ "~n Ip: ~p)",
+ [Class, Error, Bytes, UdpPort, Ip]),
[]
end;
handle_udp_packet(V, {DiscoReqMsg, From}, _UdpId, _Ip, _UdpPort,
@@ -267,26 +322,30 @@ handle_v3_message(Mgr, UdpId, Ip, UdpPort, AgentIp,
catch
throw:{error, Reason, B}:_ ->
udp_send(UdpId, AgentIp, UdpPort, B),
- error("Decoding (v3) error. Auto-sending Report.\n"
- "~n Reason: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Reason, UdpPort, Ip]),
+ ?EPRINT("Decoding (v3) error - Auto-sending Report:"
+ "~n Reason: ~p"
+ "~n Port: ~p"
+ "~n Ip: ~p",
+ [Reason, UdpPort, Ip]),
[];
throw:{error, Reason}:_ ->
- error("Decoding (v3) error. "
- "~n Bytes: ~w"
- "~n Reason: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Bytes, Reason, UdpPort, Ip]),
+ ?EPRINT("Decoding (v3) error:"
+ "~n Reason: ~p"
+ "~n Bytes: ~p"
+ "~n Port: ~p"
+ "~n Ip: ~p",
+ [Reason, Bytes, UdpPort, Ip]),
[];
Class:Error:_ ->
- error("Decoding (v3) error (~w). "
- "~n Bytes: ~w"
- "~n Error: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Class, Bytes, Error, UdpPort, Ip]),
+ ?EPRINT("Decoding (v3) error:"
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Bytes: ~p"
+ "~n Port: ~p"
+ "~n Ip: ~p",
+ [Class, Error, Bytes, UdpPort, Ip]),
[]
end.
@@ -321,11 +380,13 @@ handle_v1_or_v2_message(Mgr, _UdpId, Ip, UdpPort, _AgentIp,
catch
Class:Error:_ ->
- error("Decoding (v1 or v2) error (~w): "
- "~n Bytes: ~w"
- "~n Error: ~w "
- "(UDPport: ~w, Ip: ~w)",
- [Class, Bytes, Error, UdpPort, Ip])
+ ?EPRINT("Decoding (v1 or v2) error: "
+ "~n Class: ~p"
+ "~n Error: ~p "
+ "~n Bytes: ~p"
+ "~n Port: ~p"
+ "~n Ip: ~p",
+ [Class, Error, Bytes, UdpPort, Ip])
end.
@@ -458,12 +519,6 @@ generate_v3_report_msg(_MsgID, _MsgSecurityModel, Data, ErrorInfo) ->
undefined).
-error(Format, Data) ->
- io:format("*** Error ***~n"),
- ok = io:format(Format, Data),
- io:format("~n").
-
-
mk_discovery_msg('version-3', Pdu, _VsnHdr, UserName) ->
ScopedPDU = #scopedPdu{contextEngineID = "",
contextName = "",
@@ -488,9 +543,9 @@ mk_discovery_msg('version-3', Pdu, _VsnHdr, UserName) ->
Msg = #message{version = 'version-3', vsn_hdr = Hdr, data = Bytes},
case (catch snmp_pdus:enc_message_only(Msg)) of
{'EXIT', Reason} ->
- error("Discovery encoding error: "
- "~n Pdu: ~w"
- "~n Reason: ~w",[Pdu, Reason]),
+ ?EPRINT("Discovery encoding error: "
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Pdu, Reason]),
error;
L when is_list(L) ->
{Msg#message{data = ScopedPDU}, L}
@@ -499,9 +554,9 @@ 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("Discovery encoding error:"
- "~n Pdu: ~w"
- "~n Reason: ~w",[Pdu, Reason]),
+ ?EPRINT("Discovery encoding error:"
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Pdu, Reason]),
error;
L when is_list(L) ->
{Msg, L}
@@ -547,14 +602,14 @@ mk_msg('version-3', Pdu, {Context, User, EngineID, CtxEngineId, SecLevel},
case catch snmpa_usm:generate_outgoing_msg(Message, SecEngineID,
SecName, SecData, SecLevel) of
{'EXIT', Reason} ->
- error("version-3 message encoding exit"
- "~n Pdu: ~w"
- "~n Reason: ~w",[Pdu, Reason]),
+ ?EPRINT("version-3 message encoding exit"
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Pdu, Reason]),
error;
{error, Reason} ->
- error("version-3 message encoding error"
- "~n Pdu: ~w"
- "~n Reason: ~w",[Pdu, Reason]),
+ ?EPRINT("version-3 message encoding error"
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Pdu, Reason]),
error;
Packet ->
Packet
@@ -563,9 +618,9 @@ 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("~w encoding error"
- "~n Pdu: ~w"
- "~n Reason: ~w",[Version, Pdu, Reason]),
+ ?EPRINT("~w encoding error"
+ "~n Pdu: ~p"
+ "~n Reason: ~p", [Version, Pdu, Reason]),
error;
B when is_list(B) ->
B
@@ -586,12 +641,14 @@ vsn('version-2') -> v2c.
udp_send(UdpId, AgentIp, UdpPort, B) ->
?vlog("attempt send message (~w bytes) to ~p", [sz(B), {AgentIp, UdpPort}]),
case (catch gen_udp:send(UdpId, AgentIp, UdpPort, B)) of
- {error,ErrorReason} ->
- error("failed (error) sending message to ~p:~p: "
- "~n ~p",[AgentIp, UdpPort, ErrorReason]);
- {'EXIT',ExitReason} ->
- error("failed (exit) sending message to ~p:~p:"
- "~n ~p",[AgentIp, UdpPort, ExitReason]);
+ {error, ErrorReason} ->
+ ?EPRINT("failed (error) sending message to ~p:~p: "
+ "~n ~p",[AgentIp, UdpPort, ErrorReason]),
+ error;
+ {'EXIT', ExitReason} ->
+ ?EPRINT("failed (exit) sending message to ~p:~p:"
+ "~n ~p",[AgentIp, UdpPort, ExitReason]),
+ error;
_ ->
ok
end.
diff --git a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl
index 76a1967513..aacdcff504 100644
--- a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl
+++ b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl
@@ -200,10 +200,19 @@ end_per_suite(Config) ->
init_per_group(ipv4, Config) ->
init_per_group_ip([inet], Config);
init_per_group(ipv6, Config) ->
- init_per_group_ipv6([inet6], Config);
+ case os:type() of
+ {unix, netbsd} ->
+ {skip, "Host *may* not *properly* support IPV6"};
+ _ ->
+ init_per_group_ipv6([inet6], Config)
+ end;
init_per_group(ipv4_ipv6, Config) ->
- init_per_group_ipv6([inet, inet6], Config);
-
+ case os:type() of
+ {unix, netbsd} ->
+ {skip, "Host *may* not *properly* support IPV6"};
+ _ ->
+ init_per_group_ipv6([inet, inet6], Config)
+ end;
init_per_group(snmpget = Exec, Config) ->
%% From Ubuntu package snmp
init_per_group_agent(Exec, Config);
@@ -344,11 +353,39 @@ start_agent(Config) ->
ok = application:set_env(snmp, agent, agent_app_env(Config)),
ok = application:start(snmp).
+%% stop_agent(_Config) ->
+%% case application:stop(snmp) of
+%% ok ->
+%% ok;
+%% E1 ->
+%% ct:pal("application:stop(snmp) -> ~p", [E1])
+%% end,
+%% case application:unload(snmp) of
+%% ok ->
+%% ok;
+%% E2 ->
+%% ct:pal("application:unload(snmp) -> ~p", [E2])
+%% end.
+
start_manager(Config) ->
ok = application:load(snmp),
ok = application:set_env(snmp, manager, manager_app_env(Config)),
ok = application:start(snmp).
+%% stop_manager(_Config) ->
+%% case application:stop(snmp) of
+%% ok ->
+%% ok;
+%% E1 ->
+%% ct:pal("application:stop(snmp) -> ~p", [E1])
+%% end,
+%% case application:unload(snmp) of
+%% ok ->
+%% ok;
+%% E2 ->
+%% ct:pal("application:unload(snmp) -> ~p", [E2])
+%% end.
+
%%--------------------------------------------------------------------
@@ -398,9 +435,9 @@ erlang_manager_netsnmp_get(Config) when is_list(Config) ->
{version, v2}, {sec_model, v2c}, {sec_level, noAuthNoPriv}])
|| {Domain, Addr} <- Transports],
Results =
- [snmp_manager_user:sync_get(
+ [snmp_manager_user:sync_get2(
TargetName++domain_suffix(Domain),
- [?sysDescr_instance])
+ [?sysDescr_instance], [])
|| {Domain, _} <- Transports],
ct:pal("sync_get -> ~p", [Results]),
snmp_manager_user:stop(),
diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk
index 84299ec250..102de54127 100644
--- a/lib/snmp/vsn.mk
+++ b/lib/snmp/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = snmp
-SNMP_VSN = 5.5
+SNMP_VSN = 5.8
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)"
diff --git a/lib/ssh/Makefile b/lib/ssh/Makefile
index dedc7ac3a6..da0e3e6cd1 100644
--- a/lib/ssh/Makefile
+++ b/lib/ssh/Makefile
@@ -17,6 +17,7 @@
#
# %CopyrightEnd%
#
+#
include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/make/$(TARGET)/otp.mk
@@ -37,4 +38,6 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=crypto runtime_tools public_key
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/ssh/doc/src/Makefile b/lib/ssh/doc/src/Makefile
index 4e32dd9976..3bad66c948 100644
--- a/lib/ssh/doc/src/Makefile
+++ b/lib/ssh/doc/src/Makefile
@@ -30,11 +30,6 @@ VSN=$(SSH_VSN)
APPLICATION=ssh
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -46,10 +41,11 @@ XML_REF3_FILES = \
ssh_server_channel.xml \
ssh_server_key_api.xml \
ssh_file.xml \
+ ssh_agent.xml \
ssh_sftp.xml \
ssh_sftpd.xml \
-XML_REF6_FILES = ssh_app.xml
+XML_REF6_FILES = SSH_app.xml
XML_PART_FILES = usersguide.xml
@@ -58,7 +54,9 @@ XML_CHAPTER_FILES = \
introduction.xml \
using_ssh.xml \
terminology.xml \
- configure_algos.xml
+ configurations.xml \
+ configure_algos.xml \
+ hardening.xml
BOOK_FILES = book.xml
@@ -67,87 +65,9 @@ XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) $(XML_REF6
IMAGE_FILES = SSH_protocols.png
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-EXTRA_FILES = \
- $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
-
TOP_SPECS_FILE = specs.xml
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-#SPECS_FLAGS = -I../../include -I../../../kernel/include
-SPECS_FLAGS = -I../../../public_key/include -I../../../public_key/src -I../../..
+NO_CHUNKS = ssh_client_key_api.xml ssh_server_key_api.xml ssh_server_channel.xml ssh_file.xml
# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.png: %.png
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-images: $(IMAGE_FILES:%=$(HTMLDIR)/%)
-
-pdf: $(TOP_PDF_FILE)
-
-html: images $(HTML_REF_MAN_FILE)
-
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECS_FILES)
- rm -f errs core *~
-
-man: $(MAN3_FILES) $(MAN6_FILES)
-
-
-debug opt:
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
-
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/ssh/doc/src/ssh_app.xml b/lib/ssh/doc/src/SSH_app.xml
index 129f5a96b8..f471971ab5 100644
--- a/lib/ssh/doc/src/ssh_app.xml
+++ b/lib/ssh/doc/src/SSH_app.xml
@@ -42,67 +42,87 @@
<section>
<title>DEPENDENCIES</title>
<p>The <c>ssh</c> application uses the applications
- <seealso marker="public_key:public_key">public_key</seealso> and
- <seealso marker="crypto:crypto">crypto</seealso>
+ <seeerl marker="public_key:public_key">public_key</seeerl> and
+ <seeerl marker="crypto:crypto">crypto</seeerl>
to handle public keys and encryption. Hence, these
- applications must be loaded for the <c>ssh</c> application to work. In
- an embedded environment this means that they must be started with
- <seealso marker="kernel:application#start/1">application:start/1,2</seealso> before the
- <c>ssh</c> application is started.
+ applications must be loaded for the <c>ssh</c> application to work. The call
+ <seemfa marker="ssh#start/0">ssh:start/0</seemfa> will do the necessary
+ calls to
+ <seemfa marker="kernel:application#start/1">application:start/1,2</seemfa>
+ before it starts the <c>ssh</c> itself.
</p>
</section>
<section>
<title>CONFIGURATION</title>
- <p>The <c>ssh</c> application does not have an application-
- specific configuration file, as described in <seealso marker="kernel:application">application(3)</seealso>.
- However, by default it use the following configuration files
- from OpenSSH:</p>
+ <p>The SSH application uses Configuration Parameters.
+ Where to set them are described in
+ <seefile marker="kernel:config">config User's Guide</seefile> with
+ SSH details in
+ <seeguide marker="ssh:configurations">Configuration in SSH</seeguide>.
+ </p>
+ <p>Some special configuration files from OpenSSH are also used:</p>
<list type="bulleted">
<item><c>known_hosts</c></item>
<item><c>authorized_keys</c></item>
<item><c>authorized_keys2</c></item>
- <item><c>id_dsa</c></item>
- <item><c>id_rsa</c></item>
+ <item><c>id_dsa</c> <i>(supported but disabled by default)</i></item>
+ <item><c>id_rsa</c> <i>(SHA1 sign/verify are supported but disabled by default from OTP-24)</i></item>
<item><c>id_ecdsa</c></item>
- <item><c>ssh_host_dsa_key</c></item>
- <item><c>ssh_host_rsa_key</c></item>
+ <item><c>id_ed25519</c></item>
+ <item><c>id_ed448</c></item>
+ <item><c>ssh_host_dsa_key</c> <i>(supported but disabled by default)</i></item>
+ <item><c>ssh_host_rsa_key</c> <i>(SHA1 sign/verify are supported but disabled by default from OTP-24)</i></item>
<item><c>ssh_host_ecdsa_key</c></item>
+ <item><c>ssh_host_ed25519_key</c></item>
+ <item><c>ssh_host_ed448_key</c></item>
</list>
- <p>By default, <c>ssh</c> looks for <c>id_dsa</c>, <c>id_rsa</c>,
- <c>id_ecdsa_key</c>,
- <c>known_hosts</c>, and <c>authorized_keys</c> in ~/.ssh,
- and for the host key files in <c>/etc/ssh</c>. These locations can be changed
+ <p>By default, <c>ssh</c> looks for <c>id_*</c>,
+ <c>known_hosts</c>, and <c>authorized_keys</c> in <c>~/.ssh</c>,
+ and for the ssh_host_*_key files in <c>/etc/ssh</c>. These locations can be changed
by the options
- <seealso marker="ssh_file#type-user_dir_common_option"><c>user_dir</c></seealso> and
- <seealso marker="ssh_file#type-system_dir_daemon_option"><c>system_dir</c></seealso>.
+ <seetype marker="ssh_file#user_dir_common_option"><c>user_dir</c></seetype> and
+ <seetype marker="ssh_file#system_dir_daemon_option"><c>system_dir</c></seetype>.
+ More about where to set them is described in
+ <seeguide marker="ssh:configurations">Configuration in SSH</seeguide>.
</p>
<p>Public key handling can also be customized through a callback module that
implements the behaviors
- <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso> and
- <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
+ <seeerl marker="ssh_client_key_api">ssh_client_key_api</seeerl> and
+ <seeerl marker="ssh_server_key_api">ssh_server_key_api</seeerl>.
</p>
<p>See also the default callback module documentation in
- <seealso marker="ssh_file">ssh_file</seealso>.
+ <seeerl marker="ssh_file">ssh_file</seeerl>.
+ </p>
+ <p>Disabled public key algorithms can be enabled with the
+ <seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+ or
+ <seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ options.
+ See <seeguide marker="configure_algos#example-9">Example 9</seeguide> in
+ <seeguide marker="configure_algos">Configuring algorithms in SSH</seeguide>
+ for a description.
</p>
</section>
<section>
<title>Public Keys</title>
- <p><c>id_dsa</c>, <c>id_rsa</c> and <c>id_ecdsa</c> are the users private key files.
+ <p><c>id_*</c> are the users private key files.
Notice that the public key is part of the private key so the <c>ssh</c>
- application does not use the <c>id_&lt;*>.pub</c> files. These are
+ application does not use the <c>id_*.pub</c> files. These are
for the user's convenience when it is needed to convey the user's
public key.
</p>
+ <p>See <seeerl marker="ssh_file#FILE-id_STAR">ssh_file</seeerl> for details.</p>
</section>
<section>
<title>Known Hosts</title>
<p>The <c>known_hosts</c> file contains a list of approved servers and
their public keys. Once a server is listed, it can be verified
without user interaction.
- </p>
+ </p>
+ <p>See <seeerl marker="ssh_file#FILE-known_hosts">ssh_file</seeerl> for details.</p>
</section>
<section>
<title>Authorized Keys</title>
@@ -111,17 +131,20 @@
log in without entering their password, which is supported by the
Erlang <c>ssh</c> daemon.
</p>
+ <p>See <seeerl marker="ssh_file#FILE-authorized_keys">ssh_file</seeerl> for details.</p>
</section>
<section>
<title>Host Keys</title>
- <p>RSA, DSA and ECDSA host keys are supported and are
- expected to be found in files named <c>ssh_host_rsa_key</c>,
- <c>ssh_host_dsa_key</c> and <c>ssh_host_ecdsa_key</c>.
- </p>
+ <p>RSA, DSA (if enabled), ECDSA, ED25519 and ED448 host keys are supported and are
+ expected to be found in files named <c>ssh_host_rsa_key</c>,
+ <c>ssh_host_dsa_key</c>, <c>ssh_host_ecdsa_key</c>,
+ <c>ssh_host_ed25519_key</c> and <c>ssh_host_ed448_key</c>.
+ </p>
+ <p>See <seeerl marker="ssh_file#FILE-ssh_host_STAR_key">ssh_file</seeerl> for details.</p>
</section>
<section>
<title>ERROR LOGGER AND EVENT HANDLERS</title>
- <p>The <c>ssh</c> application uses the default <seealso marker="kernel:error_logger">OTP error logger</seealso> to log unexpected errors or print information about special events.</p>
+ <p>The <c>ssh</c> application uses the default <seeerl marker="kernel:error_logger">OTP error logger</seeerl> to log unexpected errors or print information about special events.</p>
</section>
<section>
@@ -133,14 +156,14 @@
<title>Algorithms</title>
<p>The actual set of algorithms may vary depending on which OpenSSL crypto library that is installed on the machine.
For the list on a particular installation, use the command
- <seealso marker="ssh:ssh#default_algorithms/0">ssh:default_algorithms/0</seealso>.
+ <seemfa marker="ssh:ssh#default_algorithms/0">ssh:default_algorithms/0</seemfa>.
The user may override the default algorithm configuration both on the server side and the client side.
See the options
- <seealso marker="ssh:ssh#type-preferred_algorithms_common_option">preferred_algorithms</seealso>
+ <seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
and
- <seealso marker="ssh:ssh#type-modify_algorithms_common_option">modify_algorithms</seealso>
- in the <seealso marker="ssh:ssh#daemon/1">ssh:daemon/1,2,3</seealso> and
- <seealso marker="ssh:ssh#connect/3">ssh:connect/3,4</seealso> functions.
+ <seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ in the <seemfa marker="ssh:ssh#daemon/1">ssh:daemon/1,2,3</seemfa> and
+ <seemfa marker="ssh:ssh#connect/3">ssh:connect/3,4</seemfa> functions.
</p>
<p>Supported algorithms are (in the default order):</p>
@@ -159,14 +182,18 @@
<item>curve25519-sha256</item>
<item>curve25519-sha256@libssh.org</item>
<item>curve448-sha512</item>
- <item>diffie-hellman-group14-sha1</item>
- <item>diffie-hellman-group-exchange-sha1</item>
- <item>(diffie-hellman-group1-sha1, retired: It can be enabled with the
- <seealso marker="ssh:ssh#type-preferred_algorithms_common_option">preferred_algorithms</seealso>
- or
- <seealso marker="ssh:ssh#type-modify_algorithms_common_option">modify_algorithms</seealso>
- options. Use for example the Option value <c>{modify_algorithms, [{append, [{kex,['diffie-hellman-group1-sha1']}]}]}</c>)</item>
</list>
+ <p>The following unsecure <c>SHA1</c> algorithms are now disabled by default:</p>
+ <list>
+ <item>(diffie-hellman-group14-sha1)</item>
+ <item>(diffie-hellman-group-exchange-sha1)</item>
+ <item>(diffie-hellman-group1-sha1)</item>
+ </list>
+ <p>They can be enabled with the
+ <seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+ or
+ <seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ options. Use for example the Option value <c>{modify_algorithms, [{append, [{kex,['diffie-hellman-group1-sha1']}]}]}</c>)</p>
</item>
<tag>Public key algorithms</tag>
@@ -177,25 +204,45 @@
<item>ecdsa-sha2-nistp256</item>
<item>ssh-ed25519</item>
<item>ssh-ed448</item>
- <item>ssh-rsa</item>
<item>rsa-sha2-256</item>
<item>rsa-sha2-512</item>
- <item>ssh-dss</item>
+ <item>ssh-rsa <i>(SHA1 sign/verify are supported but disabled by default from OTP-24)</i></item>
+ </list>
+ <p>The following unsecure <c>SHA1</c> algorithm is supported but disabled by default:</p>
+ <list>
+ <item>(ssh-dss)</item>
</list>
+ <p>See
+ Disabled public key algorithms can be enabled with the
+ <seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+ or
+ <seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ options.
+ See <seeguide marker="configure_algos#example-9">Example 9</seeguide> in
+ <seeguide marker="configure_algos">Configuring algorithms in SSH</seeguide>
+ for a description.
+ </p>
</item>
<tag>MAC algorithms</tag>
<item>
<list type="bulleted">
+ <item>hmac-sha2-256-etm@openssh.com</item>
+ <item>hmac-sha2-512-etm@openssh.com</item>
+ <item>hmac-sha1-etm@openssh.com</item>
<item>hmac-sha2-256</item>
<item>hmac-sha2-512</item>
<item>hmac-sha1</item>
- <item>(hmac-sha1-96 It can be enabled with the
- <seealso marker="ssh:ssh#type-preferred_algorithms_common_option">preferred_algorithms</seealso>
- or
- <seealso marker="ssh:ssh#type-modify_algorithms_common_option">modify_algorithms</seealso>
- options. Use for example the Option value <c>{modify_algorithms, [{append, [{mac,['hmac-sha1-96']}]}]}</c>)</item>
</list>
+ <p>The following unsecure <c>SHA1</c> algorithm is disabled by default:</p>
+ <list>
+ <item>(hmac-sha1-96)</item>
+ </list>
+ <p>It can be enabled with the
+ <seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+ or
+ <seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ options. Use for example the Option value <c>{modify_algorithms, [{append, [{mac,['hmac-sha1-96']}]}]}</c>)</p>
</item>
<tag>Encryption algorithms (ciphers)</tag>
@@ -214,10 +261,10 @@
<item>(AEAD_AES_128_GCM, not enabled per default)</item>
<item>(AEAD_AES_256_GCM, not enabled per default)</item>
</list>
- <p>See the text at the description of <seealso marker="#rfc5647_note">the rfc 5647 further down</seealso>
+ <p>See the text at the description of <seeapp marker="#rfc5647_note">the rfc 5647 further down</seeapp>
for more information regarding AEAD_AES_*_GCM.
</p>
- <p>Following the internet de-facto standard, the cipher and mac algorithm AEAD_AES_128_GCM is selected when the
+ <p>Following the internet de-facto standard, the cipher and mac algorithm AEAD_AES_128_GCM is selected when the
cipher aes128-gcm@openssh.com is negotiated. The cipher and mac algorithm AEAD_AES_256_GCM is selected when the
cipher aes256-gcm@openssh.com is negotiated.
</p>
@@ -236,7 +283,7 @@
<section>
<title>Unicode support</title>
<p>Unicode filenames are supported if the emulator and the underlaying OS support it. See section DESCRIPTION in the
- <seealso marker="kernel:file">file</seealso> manual page in Kernel for information about this subject.
+ <seeerl marker="kernel:file">file</seeerl> manual page in Kernel for information about this subject.
</p>
<p>The shell and the cli both support unicode.
</p>
@@ -267,13 +314,25 @@
<item><url href="https://tools.ietf.org/html/rfc4253">RFC 4253</url>, The Secure Shell (SSH) Transport Layer Protocol.
<p>Except</p>
<list type="bulleted">
- <item>8.1. diffie-hellman-group1-sha1. Disabled by default, can be enabled with the
- <seealso marker="ssh:ssh#type-preferred_algorithms_common_option">preferred_algorithms</seealso>
- or
- <seealso marker="ssh:ssh#type-modify_algorithms_common_option">modify_algorithms</seealso>
- options.</item>
+ <item>8.1. diffie-hellman-group1-sha1</item>
+ <item>6.6. Public Key Algorithms
+ <list type="bulleted">
+ <item>ssh-dss</item>
+ </list>
+ </item>
</list>
- <p/>
+ <p>They are disabled by default as they now are regarded insecure, but they can be enabled with the
+ <seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+ or
+ <seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ options.
+ See <seeguide marker="configure_algos#example-8">Example 8</seeguide> (diffie-hellman-group1-sha1)
+ and
+ <seeguide marker="configure_algos#example-9">Example 9</seeguide> (ssh-dss)
+ in
+ <seeguide marker="configure_algos">Configuring algorithms in SSH</seeguide>
+ for descriptions.
+ </p>
</item>
<item><url href="https://tools.ietf.org/html/rfc4254">RFC 4254</url>, The Secure Shell (SSH) Connection Protocol.
@@ -296,9 +355,18 @@
<p/>
</item>
- <item><url href="https://tools.ietf.org/html/rfc4419">RFC 4419</url>, Diffie-Hellman Group Exchange for
- the Secure Shell (SSH) Transport Layer Protocol.
- <p/>
+ <item><url href="https://tools.ietf.org/html/rfc4419">RFC 4419</url>,
+ Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer Protocol.
+ <p>Except</p>
+ <list type="bulleted">
+ <item>4.1. diffie-hellman-group-exchange-sha1</item>
+ </list>
+ <p>It is disabled by defaultas as it now is regarded insecure, but it can be enabled with the
+ <seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+ or
+ <seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ options.
+ </p>
</item>
<item><url href="https://tools.ietf.org/html/rfc4716">RFC 4716</url>, The Secure Shell (SSH) Public Key File Format.
@@ -311,9 +379,9 @@
This is resolved by OpenSSH in the ciphers aes128-gcm@openssh.com and aes256-gcm@openssh.com which are implemented.
If the explicit ciphers and macs AEAD_AES_128_GCM or AEAD_AES_256_GCM are needed,
they could be enabled with the options
- <seealso marker="ssh:ssh#type-preferred_algorithms_common_option">preferred_algorithms</seealso>
+ <seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
or
- <seealso marker="ssh:ssh#type-modify_algorithms_common_option">modify_algorithms</seealso>.
+ <seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>.
</p>
<warning>
<p>
@@ -354,28 +422,25 @@
<item><url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-kex-sha2">Draft-ietf-curdle-ssh-kex-sha2 (work in progress)</url>, Key Exchange (KEX) Method Updates and Recommendations for Secure Shell (SSH).
<p>Deviations:</p>
<list type="bulleted">
- <item>The <c>diffie-hellman-group1-sha1</c> is not enabled by default, but is still supported and can be enabled
- with the options
- <seealso marker="ssh:ssh#type-preferred_algorithms_common_option">preferred_algorithms</seealso>
- or
- <seealso marker="ssh:ssh#type-modify_algorithms_common_option">modify_algorithms</seealso>.
- </item>
- <item>The questionable sha1-based algorithms <c>diffie-hellman-group-exchange-sha1</c> and
- <c>diffie-hellman-group14-sha1</c> are still enabled by default for compatibility with ancient clients and servers.
- They can be disabled with the options
- <seealso marker="ssh:ssh#type-preferred_algorithms_common_option">preferred_algorithms</seealso>
- or
- <seealso marker="ssh:ssh#type-modify_algorithms_common_option">modify_algorithms</seealso>.
- They will be disabled by default when the draft is turned into an RFC.</item>
+ <item><c>diffie-hellman-group1-sha1</c></item>
+ <item><c>diffie-hellman-group-exchange-sha1</c></item>
+ <item><c>diffie-hellman-group14-sha1</c></item>
</list>
- <p/>
+ <p>are not enabled by default as they now are regarded insecure,
+ but are still supported and can be enabled with the options
+ <seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+ or
+ <seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>.
+ </p>
</item>
-
+
<item><url href="https://tools.ietf.org/html/rfc8332">RFC 8332</url>, Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol.
+ <p/>
</item>
<item><marker id="supported-ext-info"/>
- <url href="https://tools.ietf.org/html/rfc8308">RFC 8308</url>, Extension Negotiation in the Secure Shell (SSH) Protocol.
+ <url href="https://tools.ietf.org/html/rfc8308">RFC 8308</url>,
+ Extension Negotiation in the Secure Shell (SSH) Protocol.
<p>Implemented are:</p>
<list type="bulleted">
<item>The Extension Negotiation Mechanism</item>
@@ -386,10 +451,12 @@
<item>
<url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves">Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 (work in progress)</url>
+ <p/>
</item>
<item>
- <url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-ed448">Ed25519 and Ed448 public key algorithms for the Secure Shell (SSH) protocol (work in progress)</url>
+ <url href="https://tools.ietf.org/html/rfc8709">RFC 8709</url>
+ Ed25519 and Ed448 public key algorithms for the Secure Shell (SSH) protocol
</item>
</list>
@@ -398,7 +465,7 @@
<section>
<title>SEE ALSO</title>
- <p><seealso marker="kernel:application">application(3)</seealso></p>
+ <p><seeerl marker="kernel:application">application(3)</seeerl></p>
</section>
</appref>
diff --git a/lib/ssh/doc/src/configurations.xml b/lib/ssh/doc/src/configurations.xml
new file mode 100644
index 0000000000..cd55e87027
--- /dev/null
+++ b/lib/ssh/doc/src/configurations.xml
@@ -0,0 +1,341 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2020</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>Configuration in SSH</title>
+ <prepared></prepared>
+ <docno></docno>
+ <approved></approved>
+ <date></date>
+ <rev></rev>
+ <file>configurations.xml</file>
+ </header>
+
+ <section>
+ <marker id="introduction"/>
+ <title>Introduction</title>
+ <p>The OTP SSH app can be configurated by a large amount of <i>Options</i>. This chapter will not go into
+ details of what each of the options does. It will however describe and define different ways by which they
+ could be entered.
+ </p>
+ <p>Options for hardening are described in the <seeguide marker="hardening">Hardening SSH</seeguide> chapter.
+ How the options for algorithm configuration interact are described in the
+ <seeguide marker="configure_algos">Configuring algorithms in SSH</seeguide> chapter.
+ </p>
+ </section>
+
+ <section>
+ <title>Options configuration</title>
+ <p>There are from OTP-23.0 two main ways to set an option:
+ </p>
+ <list>
+ <item>Like before, in the <c>Options</c> parameter in the Erlang code
+ in a call to for example
+ <seemfa marker="ssh#daemon/3">ssh:daemon/3</seemfa> or
+ <seemfa marker="ssh#connect/3">ssh:connect/3</seemfa> or
+ any of their variants. Example:
+ <code>ssh:connect(22, [{user,"foo"}])</code>
+ </item>
+
+ <item>In <seefile marker="kernel:config">OTP Configuration Parameters</seefile>:
+ <p/>
+ <list>
+ <item>In the erl command line:
+ <pre>erl -ssh user \"foo\"</pre>
+ </item>
+ <item>In the <c>ssh.app</c> file, in the <c>env</c> part
+ <code>
+{application, ssh,
+ [{description, "SSH-2 for Erlang/OTP"},
+ {vsn, "4.9"},
+ {modules, [ssh,
+ ...
+ ssh_xfer]},
+ {registered, []},
+ {applications, [kernel, stdlib, crypto, public_key]},
+ {env, [{user, "bar"]}, % &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt; HERE
+ {mod, {ssh_app, []}},
+ ...
+</code>
+ </item>
+ <item>In a .config file:
+ <pre>erl -config ex1</pre>
+ where <c>ex1.config</c> contains:
+ <code>[
+{ssh, [{user, "foo"}]}
+].
+</code>
+ </item>
+ </list>
+ <p>If the option is intended only for a server or for a client, it may be set in this way:
+ </p>
+ <code>[
+{ssh, [{server_options,[{user, "foo"}]},
+ {client_options,[{user, "bar"}]}
+].
+ </code>
+ <p>A server (daemon) will use the user name <c>foo</c>, and a client will use the name <c>bar</c>.</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Precedens</title>
+ <p>If an option is set in more than one way, what happens?</p>
+ <p>There is an ordering, which is:
+ </p>
+ <list>
+ <item>Level 0: Hard-coded default values in the source code</item>
+ <item>Level 1: <seefile marker="kernel:config">OTP Configuration Parameters</seefile></item>
+ <item>Level 2: Options in the <seefile marker="kernel:config">OTP Configuration Parameters</seefile>
+ <c>server_options</c> or <c>client_options</c></item>
+ <item>Level 3: Options in argument list to a function</item>
+ </list>
+ <p>If the same option is set at two different levels, the one at the highest level is used.
+ </p>
+ <p>The only exception is the
+ <seetype marker="ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ common option. They are all applied in ascending level order on the set of algorithms. So a
+ <c>modify_algorithms</c> on level zero is applied before one of level one and so on.
+ </p>
+ <p>If there is an
+ <seetype marker="ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+ option on some level the whole set is replaced by that in that option and <em>all modify_algorithms
+ are applied</em> in level ordering.
+ </p>
+ <p>The reason for applying all
+ <seetype marker="ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ in level order, is to enable the user to add an algorithm that has been removed from the
+ default set without code changes, only by adding an option in a config file.
+ This can be used to interoperate with legacy systems that still uses algorithms no longer
+ considered secure enough to be supported by default.
+ </p>
+
+ <section>
+ <title>Algorithm configuration</title>
+ <p>There is a <seeguide marker="configure_algos#introduction">separate chapter</seeguide> about how
+ <seetype marker="ssh#preferred_algorithms_common_option">preferred_algorithms</seetype> and
+ <seetype marker="ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ co-operate. How the different configuration levels affect them, is described here in this section.
+ </p>
+ <section>
+ <title>The ssh:start/0 function</title>
+ <p>If the application SSH is <em>not</em> <seemfa marker="ssh#start/0">started</seemfa>, the command
+ <seemfa marker="ssh#default_algorithms/0">ssh:default_algorithms/0</seemfa>
+ delivers the list of default (hardcoded) algorithms with respect to the support in the current cryptolib.
+ </p>
+ <p>If the application SSH <em>is</em> <seemfa marker="ssh#start/0">started</seemfa>, the command
+ <seemfa marker="ssh#default_algorithms/0">ssh:default_algorithms/0</seemfa>
+ delvers the list of algorithms after application of level 0 and level 1 configurations.
+ </p>
+ <p>Here is an example. The config-file has the following contents:</p>
+ <code>
+$ cat ex2.config
+[
+ {ssh, [{preferred_algorithms, [{cipher, ['aes192-ctr']},
+ {public_key, ['ssh-rsa']},
+ {kex, ['ecdh-sha2-nistp384']},
+ {mac, ['hmac-sha1']}]}]}
+].
+ </code>
+ <p>Erlang is started with <c>ex2.config</c> as configuration and we check the default set of
+ algorithms before starting ssh:</p>
+ <code>
+$ erl -config ex2
+Erlang/OTP 23 [RELEASE CANDIDATE 1] [erts-10.6.4] [source-96a0823109] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
+
+Eshell V10.6.4 (abort with ^G)
+1> ssh:default_algorithms().
+[{kex,['ecdh-sha2-nistp384','ecdh-sha2-nistp521',
+ 'ecdh-sha2-nistp256','diffie-hellman-group-exchange-sha256',
+ 'diffie-hellman-group16-sha512',
+ 'diffie-hellman-group18-sha512',
+ 'diffie-hellman-group14-sha256','curve25519-sha256',
+ 'curve25519-sha256@libssh.org','curve448-sha512',
+ 'diffie-hellman-group14-sha1',
+ 'diffie-hellman-group-exchange-sha1']},
+ {public_key,['ecdsa-sha2-nistp384','ecdsa-sha2-nistp521',
+ 'ecdsa-sha2-nistp256','ssh-ed25519','ssh-ed448','ssh-rsa',
+ 'rsa-sha2-256','rsa-sha2-512','ssh-dss']},
+ {cipher,[{client2server,['chacha20-poly1305@openssh.com',
+ 'aes256-gcm@openssh.com','aes256-ctr','aes192-ctr',
+ 'aes128-gcm@openssh.com','aes128-ctr','aes256-cbc',
+ 'aes192-cbc','aes128-cbc','3des-cbc']},
+ {server2client,['chacha20-poly1305@openssh.com',
+ 'aes256-gcm@openssh.com','aes256-ctr','aes192-ctr',
+ 'aes128-gcm@openssh.com','aes128-ctr','aes256-cbc',
+ 'aes192-cbc','aes128-cbc','3des-cbc']}]},
+ {mac,[{client2server,['hmac-sha2-256','hmac-sha2-512',
+ 'hmac-sha1']},
+ {server2client,['hmac-sha2-256','hmac-sha2-512',
+ 'hmac-sha1']}]},
+ {compression,[{client2server,[none,'zlib@openssh.com',zlib]},
+ {server2client,[none,'zlib@openssh.com',zlib]}]}]
+ </code>
+ <p>Note that the algorithms in the file <c>ex2.config</c> is not yet applied. They will be
+ when we start ssh:
+ </p>
+ <code>
+2> ssh:start().
+ok
+3> ssh:default_algorithms().
+[{kex,['ecdh-sha2-nistp384']},
+ {public_key,['ssh-rsa']},
+ {cipher,[{client2server,['aes192-ctr']},
+ {server2client,['aes192-ctr']}]},
+ {mac,[{client2server,['hmac-sha1']},
+ {server2client,['hmac-sha1']}]},
+ {compression,[{client2server,[none,'zlib@openssh.com',zlib]},
+ {server2client,[none,'zlib@openssh.com',zlib]}]}]
+4>
+
+ </code>
+ <p>We see that the algorithm set is changed to the one in the <c>ex2.config</c>. Since
+ <c>compression</c> is not specified in the file, the hard-coded default is still used
+ for that entry.
+ </p>
+ </section>
+
+ <section>
+ <title>Establishing a connection (ssh:connect et al) or starting a daemon (ssh:daemon)</title>
+ <p>Both when the client establishes a connection with ssh:connect or other functions, or a
+ daemon is started with ssh:daemon, the option lists in the function calls are also used.
+ </p>
+ <p>If a client is started (ssh:connect et al), the environment variable <c>client_options</c> is used.
+ Similarly for a daemon the <c>server_options</c> variable is handled.
+ </p>
+ <p>If any
+ <seetype marker="ssh#preferred_algorithms_common_option">preferred_algorithms</seetype> is
+ present, the one with the highest level is used, that is, the <c>Option</c> list parameter has the highest
+ priority. Then the
+ <seetype marker="ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ on all levels in order starting with level 0 are applied.
+
+ </p>
+ <p>We continue the example above by connecting to a server and modifying the <c>kex</c>
+ algorithm set. We remove the only one (<c>'ecdh-sha2-nistp384'</c>) and add
+ <c>'curve25519-sha256@libssh.org'</c>
+ by appending it to the now empty list:</p>
+ <code>
+4> {ok,C} = ssh:connect(loopback, 22,
+ [{modify_algorithms,
+ [{rm,
+ [ {kex,['ecdh-sha2-nistp384']} ]
+ },
+ {append,
+ [ {kex,['curve25519-sha256@libssh.org']} ]
+ }
+ ]
+ }
+ ]).
+{ok,&gt;0.118.0>}
+ </code>
+ <p>We check which algoritms are negotiated by the client and the server, and note that
+ the (only) <c>kex</c> algorithm <c>'curve25519-sha256@libssh.org'</c> was selected:
+ </p>
+ <code>
+5> ssh:connection_info(C, algorithms).
+{algorithms,[{kex,'curve25519-sha256@libssh.org'},
+ {hkey,'ssh-rsa'},
+ {send_mac,'hmac-sha1'},
+ {recv_mac,'hmac-sha1'},
+ {encrypt,'aes192-ctr'},
+ {decrypt,'aes192-ctr'},
+ {compress,none},
+ {decompress,none},
+ {send_ext_info,false},
+ {recv_ext_info,true}]}
+ </code>
+ </section>
+
+ <section>
+ <title>Example of modify_algorithms handling</title>
+ <p>We will now check if the
+ <seetype marker="ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ on a lower level is applied to a
+ <seetype marker="ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+ on a higher level. We will do that by enabling the <c>ssh-dss</c>
+ algorithm that is supported, but not in the default set.
+ </p>
+ <p>The config file <c>ex3.config</c> has the contents:
+ </p>
+ <code>
+[
+ {ssh, [{modify_algorithms,
+ [ {prepend, [{public_key, ['ssh-dss']}]} ]
+ }]}
+].
+ </code>
+ <p>A newly started erlang shell shows that no <c>'ssh-dss'</c> is present in the
+ <c>public_key</c> entry:</p>
+ <code>
+1> proplists:get_value(public_key, ssh:default_algorithms()).
+['ecdsa-sha2-nistp384','ecdsa-sha2-nistp521',
+ 'ecdsa-sha2-nistp256','ssh-ed25519','ssh-ed448',
+ 'rsa-sha2-256','rsa-sha2-512','ssh-rsa']
+2>
+ </code>
+ <p>A call to <c>ssh:connect/3</c> removes all algorithms but one of each type:
+ </p>
+ <code>
+2> ssh:start().
+ok
+3> {ok,C} = ssh:connect(loopback, 22,
+ [{preferred_algorithms,
+ [{public_key, ['ecdsa-sha2-nistp256']},
+ {kex, ['ecdh-sha2-nistp256']},
+ {cipher, ['chacha20-poly1305@openssh.com']},
+ {mac, ['hmac-sha2-256']},
+ {compression, [none]}
+ ]}
+ ]).
+{ok,&lt;0.101.0>}
+4> ssh:connection_info(C,algorithms).
+{algorithms,[{kex,'ecdh-sha2-nistp256'},
+ {hkey,'ssh-dss'},
+ {send_mac,'chacha20-poly1305@openssh.com'},
+ {recv_mac,'chacha20-poly1305@openssh.com'},
+ {encrypt,'chacha20-poly1305@openssh.com'},
+ {decrypt,'chacha20-poly1305@openssh.com'},
+ {compress,none},
+ {decompress,none},
+ {send_ext_info,false},
+ {recv_ext_info,true}]}
+5>
+ </code>
+ <p>But <c>'ssh-dss'</c> is selected although the call inserted <em>only</em>
+ <c>'ecdsa-sha2-nistp256'</c> as acceptable.
+ </p>
+ <p>This example showed that we could augment the set of algorithms with a
+ config-file without the need to change the actual call.
+ </p>
+ <p>For demonstration purposes we used <c>prepend</c> instead of <c>append</c>.
+ This forces the negotiation to select <c>ssh-dss</c> since the the full list
+ of public key algorithms was
+ <c>['ssh-dss','ecdsa-sha2-nistp256']</c>.
+ Normally it is safer to append a non-default algorithm.
+ </p>
+ </section>
+ </section>
+ </section>
+</chapter>
diff --git a/lib/ssh/doc/src/configure_algos.xml b/lib/ssh/doc/src/configure_algos.xml
index fa45b1cb4c..1b26cd2f25 100644
--- a/lib/ssh/doc/src/configure_algos.xml
+++ b/lib/ssh/doc/src/configure_algos.xml
@@ -41,6 +41,11 @@
<p>The first subsection will give a short background of the SSH protocol while later sections describes
the implementation and provides some examples</p>
+ <p>How the different levels of configuration "interfer" with this, see the section
+ <seeguide marker="configurations#algorithm-configuration">Algorithm Configuration</seeguide> in the chapter
+ <seeguide marker="configurations">Configuration in SSH</seeguide>.
+ </p>
+
<section>
<title>Basics of the ssh protocol's algorithms handling</title>
@@ -107,7 +112,7 @@
<title>The SSH app's mechanism</title>
<p>The set of algorithms that the SSH app uses by default depends on the algoritms supported by the:</p>
<list>
- <item><p><seealso marker="crypto:crypto">crypto</seealso> app,</p>
+ <item><p><seeerl marker="crypto:crypto">crypto</seeerl> app,</p>
</item>
<item><p>The cryptolib OTP is linked with, usally the one the OS uses, probably OpenSSL,</p>
</item>
@@ -116,7 +121,7 @@
</list>
<p>Due to this, it impossible to list in documentation what algorithms that are available in a certain installation.</p>
<p>There is an important command to list the actual algorithms and their ordering:
- <seealso marker="ssh#default_algorithms-0">ssh:default_algorithms/0</seealso>.</p>
+ <seemfa marker="ssh#default_algorithms/0">ssh:default_algorithms/0</seemfa>.</p>
<marker id="example_default_algorithms"/>
<code type="erl">
0> ssh:default_algorithms().
@@ -145,19 +150,21 @@
</code>
<p>To change the algorithm list, there are two options which can be used in
- <seealso marker="ssh#connect-3">ssh:connect/2,3,4</seealso>
+ <seemfa marker="ssh#connect/3">ssh:connect/2,3,4</seemfa>
and
- <seealso marker="ssh#daemon-2">ssh:daemon/2,3</seealso>. The options could of course
+ <seemfa marker="ssh#daemon/2">ssh:daemon/2,3</seemfa>. The options could of course
be used in all other functions that initiates connections.</p>
- <p>The options are <c>preferred_algorithms</c> and <c>modify_algorithms</c>. The first one
- replaces the default set, while the latter modifies the default set.</p>
+ <p>The options are
+ <seetype marker="ssh#preferred_algorithms_common_option">preferred_algorithms</seetype> and
+ <seetype marker="ssh#modify_algorithms_common_option">modify_algorithms</seetype>.
+ The first one replaces the default set, while the latter modifies the default set.</p>
</section>
</section>
<section>
<title>Replacing the default set: preferred_algorithms</title>
- <p>See the <seealso marker="ssh#type-preferred_algorithms_common_option">Reference Manual</seealso> for details</p>
+ <p>See the <seetype marker="ssh#preferred_algorithms_common_option">Reference Manual</seetype> for details</p>
<p>Here follows a series of examples ranging from simple to more complex.</p>
@@ -302,7 +309,7 @@
First one has to list them with <c>ssh:default_algorithms()</c> and then do changes in the lists.</p>
<p>To facilitate addition or removal of algorithms the option <c>modify_algorithms</c> is available.
- See the <seealso marker="ssh#type-modify_algorithms_common_option">Reference Manual</seealso> for details.</p>
+ See the <seetype marker="ssh#modify_algorithms_common_option">Reference Manual</seetype> for details.</p>
<p>The option takes a list with instructions to append, prepend or remove algorithms:</p>
<code type="erl">
@@ -315,7 +322,7 @@
<section>
<title>Example 5</title>
<p>As an example let's add the Diffie-Hellman Group1 first in the kex list. It is supported according to
- <seealso marker="SSH_app#supported_algos">Supported algoritms</seealso>.</p>
+ <seeapp marker="SSH_app#supported_algos">Supported algoritms</seeapp>.</p>
<code type="erl">
5> ssh:chk_algos_opts(
[{modify_algorithms,
@@ -422,7 +429,49 @@
but it is possible if an unforeseen need should arise.</p>
</section>
-
+ <section>
+ <title>Example 8</title>
+ <p>In this example, we need to use a diffie-hellman-group1-sha1 key exchange algorithm
+ although it is unsage and disabled by default.
+ </p>
+ <p>We use the
+ <seetype marker="ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ option, because we want to keep all other algorithm definitions.
+ </p>
+ <p>We add the option:
+ </p>
+ <code type="erl">
+ {modify_algorithms, [{append, [{kex,['diffie-hellman-group1-sha1']}]}]}
+ </code>
+ <p>either to the Options list in a function call, in the <c>ssh.app</c> file or in a <c>.config</c> file for
+ the <c>erl</c> command.
+ See the chapter
+ <seeguide marker="ssh:configurations">Configuration in SSH</seeguide>
+ in the SSH User's Guide.
+ </p>
+ </section>
+
+ <section>
+ <title>Example 9</title>
+ <p>In this example, we need to use a DSA key for sign and verify.
+ It might be either as a user's key, a host's key or both.
+ </p>
+ <p>To do that, we enable the 'ssh-dss' algorithm that is disabled by default by security reasons. We use the
+ <seetype marker="ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+ option, because we want to keep all other algorithm definitions.
+ </p>
+ <p>We add the option:
+ </p>
+ <code type="erl">
+ {modify_algorithms, [{append, [{public_key,['ssh-dss']}]}]}
+ </code>
+ <p>either to the Options list in a function call, in the <c>ssh.app</c> file or in a <c>.config</c> file for
+ the <c>erl</c> command.
+ See the chapter
+ <seeguide marker="ssh:configurations">Configuration in SSH</seeguide>
+ in the SSH User's Guide.
+ </p>
+ </section>
</section>
diff --git a/lib/ssh/doc/src/hardening.xml b/lib/ssh/doc/src/hardening.xml
new file mode 100644
index 0000000000..c1d3f7669c
--- /dev/null
+++ b/lib/ssh/doc/src/hardening.xml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2017</year>
+ <year>2020</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>Hardening</title>
+ <prepared></prepared>
+ <docno></docno>
+ <approved></approved>
+ <date></date>
+ <rev></rev>
+ <file>hardening.xml</file>
+ </header>
+
+ <section>
+ <marker id="introduction"/>
+ <title>Introduction</title>
+ <p>The Erlang/OTP SSH application is intended to be used in other applications as a library.
+ </p>
+ <p>Different applications using this library may have very different requirements.
+ One application could be running on a high performance server, while another is running on
+ a small device with very limited cpu capacity. For example, the first one may accept many users
+ simultaneously logged in, while the second one wants to limit them to only one.
+ </p>
+ <p>That simple example shows that it is impossible to deliver the SSH application with default
+ values on hardening options as well on other options that suites every need.
+ </p>
+ <p>The purpose of this guide is to discuss the different hardening options available, as a
+ guide to the reader. Configuration in general is described in the
+ <seeguide marker="configurations">Configuration in SSH</seeguide> chapter.
+ </p>
+ </section>
+
+ <section>
+ <title>Resilience to DoS attacks</title>
+ <p>The following applies to daemons (servers).</p>
+ <p>DoS (Denial of Service) attacks are hard to fight at the node level. Here are firewalls and
+ other means needed, but that is out of scope for this guide.
+ However, some measures could be taken in the configuration of the SSH server to increase the resilence.
+ The options to use
+ are:</p>
+ <taglist>
+ <tag><seetype marker="ssh#hello_timeout_daemon_option">hello_timeout</seetype></tag>
+ <item>
+ If the client fails to send the first ssh message after a tcp connection setup
+ within this time (in milliseconds), the connection is closed.
+ The default value is 30 seconds. This is actualy a generous time, so it can lowered
+ to make the daemon less prone to DoS attacks.
+ </item>
+ <tag><seetype marker="ssh#negotiation_timeout_daemon_option">negotiation_timeout</seetype></tag>
+ <item>
+ Maximum time in milliseconds for the authentication negotiation.
+ If the client fails to log in within this time, the connection is closed.
+ The default value is 2 minutes. It is quite a long time, but can lowered if the client is
+ supposed to be fast like if it is a program logging in.
+ </item>
+ <tag><seeerl marker="ssh#hardening_daemon_options--max_sessions">max_sessions</seeerl></tag>
+ <item>
+ The maximum number of simultaneous sessions that are accepted at any time for this daemon.
+ This includes sessions that are being authorized. The default is that an unlimited number of
+ simultaneous sessions are allowed. It is a good candidate to set if the capacity of the server
+ is low or a capacity margin is needed.
+ </item>
+ <tag><seeerl marker="ssh#hardening_daemon_options--max_channels">max_channels</seeerl></tag>
+ <item>
+ The maximum number of channels that are accepted for each connection. The default is unlimited.
+ </item>
+ <tag><seeerl marker="ssh#hardening_daemon_options--parallel_login">parallel_login</seeerl></tag>
+ <item>
+ If set to false (the default value), only one login is handled at a time.
+ If set to true, the number of simultaneous login attempts are limited by the value of
+ <seeerl marker="ssh#hardening_daemon_options--max_sessions">max_sessions</seeerl> option.
+ </item>
+ <tag><seetype marker="ssh#max_idle_time_common_option">idle_time<!--sic!--></seetype></tag>
+ <item>
+ Sets a time-out on a connection when no channels are open. Defaults to infinity.
+ </item>
+ </taglist>
+ </section>
+
+
+ <section>
+ <title>Verifying the remote daemon (server) in an SSH client</title>
+ <p>Every SSH server presents a public key - the <i>host key</i> - to the client while keeping the corresponding
+ private key in relatively safe privacy.
+ </p>
+ <p>The client checks that the host that presented the public key also possesses the private key of the key-pair.
+ That check is part of the SSH protocol.
+ </p>
+ <p>But how can the client know that the host <i>really</i> is the one that it tried to connect to and not an
+ evil one impersonating the expected one using its own valid key-pair? There are two alternatives available with the
+ default key handling plugin <seeerl marker="ssh_file"><c>ssh_file</c></seeerl>.
+ The alternatives are:
+ </p>
+ <taglist>
+ <tag>Pre-store the host key</tag>
+ <item>
+ <list>
+ <item>
+ For the default handler ssh_file, store the valid host keys in the file
+ <seeerl marker="ssh_file#FILE-known_hosts"><c>known_hosts</c></seeerl> and set the option
+ <seeerl marker="ssh#hardening_client_options--silently_accept_hosts">silently_accept_hosts</seeerl>
+ to <c>false</c>.
+ </item>
+ <item>or, write a specialized key handler using the <seeerl marker="ssh_client_key_api">SSH client key API</seeerl>
+ that accesses the pre-shared key in some other way.
+ </item>
+ </list>
+ </item>
+
+ <tag>Pre-store the "fingerprint" (checksum) of the host key</tag>
+ <item>
+ <list>
+ <item>
+ <seeerl marker="ssh#hardening_client_options--silently_accept_hosts">silently_accept_hosts</seeerl>
+ <list>
+ <item><seetype marker="ssh#accept_callback"><c>accept_callback()</c></seetype></item>
+ <item><seetype marker="ssh#accept_hosts"><c>{HashAlgoSpec, accept_callback()}</c></seetype></item>
+ </list>
+ </item>
+ </list>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>Verifying the remote client in a daemon (server)</title>
+ <taglist>
+ <tag>Password checking</tag>
+ <item>
+ <p>The default password checking is with the list in the
+ <seeerl marker="ssh#option-user_passwords">user_passwords</seeerl> option in the SSH daemon.
+ It could be replaced with a <seeerl marker="ssh#option-pwdfun">pwdfun</seeerl> plugin. The
+ arity four variant (<seetype marker="ssh#pwdfun_4"><c>pwdfun_4()</c></seetype>)
+ can also be used for introducing delays after failed password checking attempts. Here is a simple
+ example of such a pwdfun:
+ </p>
+ <code>
+fun(User, Password, _PeerAddress, State) ->
+ case lists:member({User,Password}, my_user_pwds()) of
+ true ->
+ {true, undefined}; % Reset delay time
+ false when State == undefined ->
+ timer:sleep(1000),
+ {false, 2000}; % Next delay is 2000 ms
+ false when is_integer(State) ->
+ timer:sleep(State),
+ {false, 2*State} % Double the delay for each failure
+ end
+end.
+</code>
+ <p>If a public key is used for logging in, there is normally no checking of the user name. It
+ could be enabled by setting the option
+ <seeerl marker="ssh#option-pk_check_user"><c>pk_check_user</c></seeerl>
+ to <c>true</c>.
+ In that case the pwdfun will get the atom <c>pubkey</c> in the password argument.
+ </p>
+ </item>
+
+ </taglist>
+ </section>
+
+ <section>
+ <title>Hardening in the cryptographic area</title>
+ <section>
+ <title>Algorithm selection</title>
+ <p>One of the cornerstones of security in SSH is cryptography. The development in crypto analysis is fast,
+ and yesterday's secure algorithms are unsafe today. Therefore some algorithms are no longer enabled by default
+ and that group grows with time.
+ See the
+ <seeapp marker="SSH_app#supported-specifications-and-standards">SSH (App)</seeapp>
+ for a list of supported and of disabled algorithms.
+ In the User's Guide the chapter
+ <seeguide marker="configure_algos">Configuring algorithms in SSH</seeguide>
+ describes the options for enabling or disabling algorithms -
+ <seetype marker="ssh#preferred_algorithms_common_option">preferred_algorithms</seetype> and
+ <seetype marker="ssh#modify_algorithms_common_option">modify_algorithms</seetype>.
+ </p>
+ </section>
+
+ <section>
+ <title>Re-keying</title>
+ <p>In the setup of the SSH connection a secret cipher key is generated by co-operation of the
+ client and the server. Keeping this key secret is crucial for keeping the communication secret.
+ As time passes and encrypted messages are exchanged, the probability that a listener could
+ guess that key increases.
+ </p>
+ <p>The SSH protocol therefore has a special operation defined - <i>key re-negotiation</i> or
+ <i>re-keying</i>.
+ Any side (client or server) could initiate the re-keying and the result is a new cipher key.
+ The result is that the eves-dropper has to restart its evil and dirty craftmanship.
+ </p>
+ <p>See the option <seetype marker="ssh#rekey_limit_common_option">rekey_limit</seetype> for a
+ description.
+ </p>
+ </section>
+
+ </section>
+
+ <section>
+ <title>Hardening of the SSH protocol - both daemons and clients</title>
+ <section>
+ <title>Disabling shell and exec in a daemon</title>
+ <p>A daemon has two services for evaluating tasks on behalf of a remote client. The <i>exec</i>
+ server-side service takes a string provided by the client, evaluates it and returns the result.
+ The <i>shell</i> function enables the client to open a shell in the shell host.
+ </p>
+ <p>Those service could - and should - be disabled when they are not needed. The options
+ <seetype marker="ssh#exec_daemon_option">exec</seetype> and
+ <seetype marker="ssh#shell_daemon_option">shell</seetype> are enabled per default but could be
+ set to <c>disabled</c> if not needed. The same options could also install handlers for the string(s)
+ passed from the client to the server.
+ </p>
+ </section>
+
+ <section>
+ <title>The id string</title>
+ <p>One way to reduce the risk of intrusion is to not convey which software and which version
+ the intruder is connected to. This limits the risk of an intruder exploiting known faults or
+ peculiarities learned by reading the public code.
+ </p>
+ <p>Each SSH client or daemon presents themselves to each other with brand and version. This may
+ look like</p>
+ <pre>SSH-2.0-Erlang/4.10</pre>
+ <p>or</p>
+ <pre>SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3</pre>
+ <p>This brand and version may be changed with the option
+ <seetype marker="ssh#id_string_common_option">id_string</seetype>.
+ We start a daemon with that option:
+ </p>
+ <code>
+ ssh:daemon(1234, [{id_string,"hi there"}, ... ]).
+ </code>
+ <p>and the deamon will present itself as:</p>
+ <pre>SSH-2.0-hi there</pre>
+ <p>It is possible to replace the string with one randomly generated for each connection attempt.
+ See the reference manual for <seetype marker="ssh#id_string_common_option">id_string</seetype>.
+ </p>
+ </section>
+
+ </section>
+
+ <section>
+ <title>Client connection options</title>
+ <p>A client could limit the time for the initial tcp connection establishment with the option
+ <seetype marker="ssh#connect_timeout_client_option">connect_timeout</seetype>.
+ The time is in milliseconds, and the initial value is infinity.
+ </p>
+ <p>The negotiation (session setup time) time can be limited with the <i>parameter</i>
+ <c>NegotiationTimeout</c> in a call establishing an ssh session, for example
+ <seemfa marker="ssh:ssh#connect/3">ssh:connect/3</seemfa>.
+ </p>
+ </section>
+
+</chapter>
diff --git a/lib/ssh/doc/src/introduction.xml b/lib/ssh/doc/src/introduction.xml
index 8444daf0cc..850e606b2b 100644
--- a/lib/ssh/doc/src/introduction.xml
+++ b/lib/ssh/doc/src/introduction.xml
@@ -48,8 +48,8 @@
<list type="bulleted">
<item>API functions to write customized SSH clients and servers applications</item>
<item>The Erlang shell available over SSH</item>
- <item>An SFTP client (<seealso marker="ssh_sftp">ssh_sftp</seealso>)
- and server (<seealso marker="ssh_sftp">ssh_sftpd</seealso>)</item>
+ <item>An SFTP client (<seeerl marker="ssh_sftp">ssh_sftp</seeerl>)
+ and server (<seeerl marker="ssh_sftp">ssh_sftpd</seeerl>)</item>
</list>
</section>
@@ -77,7 +77,7 @@
authentication, and integrity protection. A minimum of
Message Authentication Code (MAC) and encryption
algorithms are supported. For details, see the
- <seealso marker="ssh">ssh(3)</seealso> manual page in <c>ssh</c>.</p>
+ <seeerl marker="ssh">ssh(3)</seeerl> manual page in <c>ssh</c>.</p>
</section>
<section>
@@ -106,17 +106,17 @@
</list>
<p>Several configuration options for
authentication handling are available in
- <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso>
- and <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso>.</p>
+ <seemfa marker="ssh#connect/3">ssh:connect/[3,4]</seemfa>
+ and <seemfa marker="ssh#daemon/2">ssh:daemon/[2,3]</seemfa>.</p>
<p>
The public key handling can be customized by implementing
the following behaviours from <c>ssh</c>:</p>
<list type="bulleted">
<item>Module
- <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso>.
+ <seeerl marker="ssh_client_key_api">ssh_client_key_api</seeerl>.
</item>
<item>Module
- <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
+ <seeerl marker="ssh_server_key_api">ssh_server_key_api</seeerl>.
</item>
</list>
</section>
@@ -128,8 +128,8 @@
services over the transport pipe, for example, channel multiplexing,
flow control, remote program execution, signal propagation, and
connection forwarding. Functions for handling the SSH
- Connection Protocol can be found in the module <seealso
- marker="ssh_connection">ssh_connection</seealso> in <c>ssh</c>.
+ Connection Protocol can be found in the module <seeerl
+ marker="ssh_connection">ssh_connection</seeerl> in <c>ssh</c>.
</p>
</section>
@@ -145,7 +145,7 @@
data that can be sent to the channel peer without adjusting the
window. Typically, an SSH client opens a channel, sends data (commands),
receives data (control information), and then closes the channel.
- The <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behaviour
+ The <seeerl marker="ssh_client_channel">ssh_client_channel</seeerl> behaviour
handles generic parts of SSH channel management. This makes it easy
to write your own SSH client/server processes that use flow-control
and thus opens for more focus on the application logic.
@@ -155,8 +155,8 @@
<list type="bulleted">
<item><em>Subsystem</em> - Named services that can be run as
- part of an SSH server, such as SFTP <seealso
- marker="ssh_sftpd">(ssh_sftpd)</seealso>, that is built into the
+ part of an SSH server, such as SFTP <seeerl
+ marker="ssh_sftpd">(ssh_sftpd)</seeerl>, that is built into the
SSH daemon (server) by default, but it can be disabled. The Erlang <c>ssh</c>
daemon can be configured to run any Erlang-
implemented SSH subsystem.
@@ -168,7 +168,7 @@
but that is much more work.
</item>
<item><em>Exec</em> - One-time remote execution of commands. See function
- <seealso marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso>
+ <seemfa marker="ssh_connection#exec/4">ssh_connection:exec/4</seemfa>
for more information.</item>
</list>
</section>
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index fe523d3a45..7e7ed4ae37 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -30,6 +30,545 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.11.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The idle_time timer was not cancelled when a channel was
+ opened within the timeout time on an empty connection
+ that have had channels previously.</p>
+ <p>
+ Own Id: OTP-17279</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.11</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The long name field in SSH_FXP_NAME responses to display
+ file information in sftp version 3 now contains the
+ expanded format defined in the sftp draft. It is similar
+ to what is returned by "ls -l" on Unix systems.</p>
+ <p>
+ Own Id: OTP-17197 Aux Id: PR- 3049 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Don't timeout slow connection setups and tear-downs. A
+ rare crash risk for the controller is also removed.</p>
+ <p>
+ Own Id: OTP-17173 Aux Id: ERIERL-581 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The SSH daemon erroneously replaced LF with CRLF also
+ when there was no pty requested from the server.</p>
+ <p>
+ Own Id: OTP-17108 Aux Id: ERL-1442 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed problems in the ssh cli/shell handling. Most
+ important are:</p>
+ <p>
+ 1) the ssh:shell function did sometimes cause the input
+ to be echoed twice,</p>
+ <p>
+ 2) the ssh:shell function didn't transfer the LANG and
+ LC_ALL shell variables to the connected server which
+ sometimes made Unicode handling erroneous,</p>
+ <p>
+ 3) Unicode was not always transferred correctly to and
+ from the peer.</p>
+ <p>
+ Own Id: OTP-16799</p>
+ </item>
+ <item>
+ <p>
+ The SSH protocol message SSH_MSG_DISCONNECT was sometimes
+ sent instead of SSH_MSG_CHANNEL_FAILURE</p>
+ <p>
+ Own Id: OTP-16900</p>
+ </item>
+ <item>
+ <p>
+ The ssh_cli module now always sends the exit-status to
+ connected clients so they can use that to check for
+ successful command execution.</p>
+ <p>
+ Own Id: OTP-16908 Aux Id: PR-2753 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ A new option <seeerl
+ marker="ssh:ssh#option-pk_check_user"><c>pk_check_user</c></seeerl>
+ enables checking of the client's user name in the server
+ when doing public key authentication.</p>
+ <p>
+ Own Id: OTP-16889</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ An ssh-client can take an accepted socket from a
+ listening socket and do an ssh:connect/2 on it.</p>
+ <p>
+ Multiple clients on sockets accepted from the same
+ listening socket had stopped working. This is corrected
+ now.</p>
+ <p>
+ Own Id: OTP-17021 Aux Id: ERIERL-567 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The inet option raw was not passed on from the ssh option
+ list to inet.</p>
+ <p>
+ Own Id: OTP-17016 Aux Id: ERIERL-562 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A supervisor sub-tree could be left if the connection
+ handler process is brutally killed. This will make the
+ max_sessions checking option to count the existing
+ sessions erroneously and could finally block further
+ sessions.</p>
+ <p>
+ Own Id: OTP-17006 Aux Id: ERIERL-556 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix decoder bug.</p>
+ <p>
+ Own Id: OTP-16904</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed a bug when a message to ssh-agent was divided into
+ separate packets.</p>
+ <p>
+ Own Id: OTP-16761 Aux Id: PR-2679 </p>
+ </item>
+ <item>
+ <p>
+ Fix a bug that could crash the cli server if a too large
+ cli-window was requested from the client.</p>
+ <p>
+ Own Id: OTP-16791 Aux Id: ERIERL-520 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Increased test coverage.</p>
+ <p>
+ Own Id: OTP-14106</p>
+ </item>
+ <item>
+ <p>
+ A chapter about <seeguide
+ marker="ssh:hardening">hardening the OTP SSH</seeguide>
+ is added to the User's Guide.</p>
+ <p>
+ Own Id: OTP-16411</p>
+ </item>
+ <item>
+ <p>
+ The internal Diffie-Hellman high level API for key
+ generation was slow in old and by OpenSSL now unsupported
+ cryptolib versions (1.0.1 and earlier).</p>
+ <p>
+ If such a cryptolib is used anyhow, the low-level API is
+ used internally in the crypto application.</p>
+ <p>
+ Own Id: OTP-16774</p>
+ </item>
+ <item>
+ <p>
+ A new timeout is defined for daemons: <seetype
+ marker="ssh:ssh#hello_timeout_daemon_option">hello_timeout</seetype>.</p>
+ <p>
+ The timeout is supposed to be used as a simple <seeguide
+ marker="ssh:hardening#resilience-to-dos-attacks">DoS
+ attack protection</seeguide>. It closes an incoming
+ TCP-connection if no valid first SSH message is received
+ from the client within the timeout limit after the TCP
+ initial connection setup.</p>
+ <p>
+ The initial value is 30s by compatibility reasons, but
+ could be lowered if needed, for example in the code or in
+ a <seeguide marker="ssh:configurations">config
+ file</seeguide>.</p>
+ <p>
+ Own Id: OTP-16803</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.10</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix error in ssh_sftpd typespec.</p>
+ <p>
+ Own Id: OTP-16363</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The plug-in file ssh_file.erl, that is responsible for
+ default file handling, is re-factored, optimized and
+ re-written.</p>
+ <p>
+ Own Id: OTP-11688 Aux Id: OTP-12699 </p>
+ </item>
+ <item>
+ <p>
+ OpenSSH 6.5 introduced a new file representation of keys
+ called <url
+ href="https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=1.1">openssh-key-v1</url>.</p>
+ <p>
+ OTP/SSH had an experimental implementation of this
+ format. That implementation is now improved and supported
+ with the exception of handling encrypted keys.</p>
+ <p>
+ Own Id: OTP-15434</p>
+ </item>
+ <item>
+ <p>
+ TCP/IP port forwarding, a.k.a tunneling a.k.a
+ tcp-forward/direct-tcp is implemented. In the OpenSSH
+ client, this corresponds to the options -L and -R.</p>
+ <p>
+ The client or server listens to a specified socket, and
+ when something connects to it with TCP/IP, that
+ connection is forwarded in an encrypted tunnel to the
+ peer. The peer then connects to a predefined IP/port pair
+ and then acts as a proxy.</p>
+ <p>
+ See the manual, <seemfa
+ marker="ssh:ssh#tcpip_tunnel_to_server/6"><c>ssh:tcpip_tunnel_to_server/6</c></seemfa>
+ and <seemfa
+ marker="ssh:ssh#tcpip_tunnel_from_server/6"><c>ssh:tcpip_tunnel_from_server/6</c></seemfa>.</p>
+ <p>
+ The functionality is disabled per default but can be
+ enabled when starting a daemon.</p>
+ <p>
+ Own Id: OTP-15998 Aux Id: PR-2376, PR-2368 </p>
+ </item>
+ <item>
+ <p>
+ The client-side of the supervisor tree (under sshc_sup)
+ was previously not complete; the channel handling
+ processes were handled with links but had no supervisors.</p>
+ <p>
+ This is now corrected with a client-side supervisor tree
+ under <c>sshc_sup</c>, similar to the server-side
+ supervisor tree under <c>sshd_sup</c>.</p>
+ <p>
+ Own Id: OTP-16026 Aux Id: PR-2368, (OTP-15998) </p>
+ </item>
+ <item>
+ <p>
+ The extension <url
+ href="https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD">posix-rename@openssh.com</url>
+ is added to the <seemfa
+ marker="ssh:ssh_sftp#rename/3">ssh/sftp rename</seemfa>
+ operation.</p>
+ <p>
+ Own Id: OTP-16289 Aux Id: PR-2448 </p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ <item>
+ <p>
+ The default known_hosts file handling is improved to
+ include ports.</p>
+ <p>
+ The handling of the contents in that file is updated to
+ support the <url
+ href="https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT">full
+ syntax</url>, with exception of 1) the wildcard '?', 2)
+ wildcards in canonical names and 3) the option
+ '@cert-authority'</p>
+ <p>
+ Own Id: OTP-16506</p>
+ </item>
+ <item>
+ <p>
+ The MAC (Message Authorization Code) algorithms</p>
+ <list> <item>hmac-sha1-etm@openssh.com</item>
+ <item>hmac-sha2-256-etm@openssh.com</item>
+ <item>hmac-sha2-512-etm@openssh.com</item> </list> <p>are
+ implemented.</p>
+ <p>
+ Own Id: OTP-16508</p>
+ </item>
+ <item>
+ <p>
+ The key-exchange algorithms
+ <c>'diffie-hellman-group14-sha1'</c> and
+ <c>'diffie-hellman-group-exchange-sha1'</c> are disabled
+ per default. The reason is that SHA1 now is considered
+ insecure.</p>
+ <p>
+ They can be enabled if needed, see <seeapp
+ marker="ssh:SSH_app#algorithms">SSH (App)</seeapp>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16509</p>
+ </item>
+ <item>
+ <p>
+ The public key algorithm <c>'ssh-dss'</c> is disabled per
+ default. The reason is that it is now considered as
+ insecure.</p>
+ <p>
+ It can be enabled if needed, see <seeapp
+ marker="ssh:SSH_app#algorithms">SSH (App)</seeapp>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16510</p>
+ </item>
+ <item>
+ <p>
+ The public key <c>'ssh-rsa'</c> is now considered as
+ insecure because of its usage of SHA1.</p>
+ <p>
+ It is therefore deprecated and will no longer be enabled
+ per default in OTP-24.0.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16511</p>
+ </item>
+ <item>
+ <p>
+ An option <seetype
+ marker="ssh:ssh_file#optimize_key_lookup">optimize
+ (optimize_key_lookup)</seetype> is introduced for the
+ file interface ssh_file.erl</p>
+ <p>
+ The option enables the user to select between the default
+ handling which is fast but memory consuming vs memory
+ efficient but not as fast. The effect might be observable
+ only for large files.</p>
+ <p>
+ See the manual for <seemfa
+ marker="ssh:ssh_file#is_host_key/5">ssh_file:is_host_key/5</seemfa>
+ and <seemfa
+ marker="ssh:ssh_file#is_auth_key/3">ssh_file:is_auth_key/3</seemfa>.</p>
+ <p>
+ Own Id: OTP-16512</p>
+ </item>
+ <item>
+ <p>
+ The ssh agent is now implemented in the ssh_agent key
+ callback module. </p>
+ <p>
+ Enable with the the option <c> {key_cb, {ssh_agent,
+ []}}</c> in for example ssh:connect/3.</p>
+ <p>
+ See the <seeerl marker="ssh:ssh_agent">ssh_agent
+ manual</seeerl> for details.</p>
+ <p>
+ Own Id: OTP-16513</p>
+ </item>
+ <item>
+ <p>
+ Algorithm configuration could now be done in a .config
+ file.</p>
+ <p>
+ This is useful for example to enable an algorithm that is
+ disabled by default. It could now be enabled in an
+ .config-file without changing the code,</p>
+ <p>
+ See the SSH User's Guide chapter <seeguide
+ marker="ssh:configurations">"Configuration in
+ SSH"</seeguide>.</p>
+ <p>
+ Own Id: OTP-16540</p>
+ </item>
+ <item>
+ <p>
+ Documented which gen_tcp socket options can't be used in
+ calls to ssh:connect and ssh:daemon.</p>
+ <p>
+ Own Id: OTP-16589</p>
+ </item>
+ <item>
+ <p>
+ Added <seetype
+ marker="ssh:ssh#kb_int_fun_4">kb_int_fun_4()</seetype> to
+ the <seetype
+ marker="ssh:ssh#authentication_daemon_options">authentication_daemon_options()</seetype>
+ to enable generating dynamic keyboard-interactive prompts
+ from the user's state returned from the authentication
+ fun <seetype
+ marker="ssh:ssh#pwdfun_4">pwdfun_4()</seetype>.</p>
+ <p>
+ Own Id: OTP-16622 Aux Id: PR-2604 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.9.1.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix decoder bug.</p>
+ <p>
+ Own Id: OTP-16904</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.9.1.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix a bug that could crash the cli server if a too large
+ cli-window was requested from the client.</p>
+ <p>
+ Own Id: OTP-16791 Aux Id: ERIERL-520 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ A new timeout is defined for daemons:
+ <c>hello_timeout</c>.</p>
+ <p>
+ It closes an incoming TCP-connection if no valid 1st
+ message is received from the client within the timeout
+ limit.</p>
+ <p>
+ Own Id: OTP-16803</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.9.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -71,10 +610,10 @@
</item>
<item>
<p>
- The new functions <seealso
- marker="ssh:ssh#set_sock_opts-2">ssh:set_sock_opts/2</seealso>
- and <seealso
- marker="ssh:ssh#get_sock_opts-2">ssh:get_sock_opts/2</seealso>
+ The new functions <seemfa
+ marker="ssh:ssh#set_sock_opts/2">ssh:set_sock_opts/2</seemfa>
+ and <seemfa
+ marker="ssh:ssh#get_sock_opts/2">ssh:get_sock_opts/2</seemfa>
sets and reads option values for the underlying TCP
stream.</p>
<p>
@@ -191,9 +730,9 @@
</item>
<item>
<p>
- The documentation of <seealso
+ The documentation of <seeguide
marker="ssh:using_ssh#one-time-execution">One-Time
- Execution</seealso> in the User's Guide is updated with
+ Execution</seeguide> in the User's Guide is updated with
more examples.</p>
<p>
Own Id: OTP-16108 Aux Id: OTP-15417 </p>
@@ -319,6 +858,37 @@
</section>
+<section><title>Ssh 4.7.6.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix decoder bug.</p>
+ <p>
+ Own Id: OTP-16904</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.7.6.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Potential hazard between re-keying decision and socket
+ close.</p>
+ <p>
+ Own Id: OTP-16462 Aux Id: ERIERL-464 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.7.6.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssh/doc/src/ref_man.xml b/lib/ssh/doc/src/ref_man.xml
index 60572b985b..1e3410c894 100644
--- a/lib/ssh/doc/src/ref_man.xml
+++ b/lib/ssh/doc/src/ref_man.xml
@@ -33,7 +33,7 @@
Secure Shell Protocol (SSH) as defined by RFC 4250 - 4254.</p>
</description>
- <xi:include href="ssh_app.xml"/>
+ <xi:include href="SSH_app.xml"/>
<xi:include href="ssh.xml"/>
<xi:include href="ssh_client_channel.xml"/>
<xi:include href="ssh_server_channel.xml"/>
@@ -41,7 +41,7 @@
<xi:include href="ssh_client_key_api.xml"/>
<xi:include href="ssh_server_key_api.xml"/>
<xi:include href="ssh_file.xml"/>
+ <xi:include href="ssh_agent.xml"/>
<xi:include href="ssh_sftp.xml"/>
<xi:include href="ssh_sftpd.xml"/>
</application>
-
diff --git a/lib/ssh/doc/src/specs.xml b/lib/ssh/doc/src/specs.xml
index a6517f3660..a4e7301a95 100644
--- a/lib/ssh/doc/src/specs.xml
+++ b/lib/ssh/doc/src/specs.xml
@@ -7,8 +7,7 @@
<xi:include href="../specs/specs_ssh_server_channel.xml"/>
<xi:include href="../specs/specs_ssh_server_key_api.xml"/>
<xi:include href="../specs/specs_ssh_file.xml"/>
+ <xi:include href="../specs/specs_ssh_agent.xml"/>
<xi:include href="../specs/specs_ssh_sftp.xml"/>
<xi:include href="../specs/specs_ssh_sftpd.xml"/>
</specs>
-
-
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 221ca0b6fd..b88bdc1667 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -34,17 +34,17 @@
<p>This is the interface module for the <c>SSH</c> application.
The Secure Shell (SSH) Protocol is a protocol for secure remote login
and other secure network services over an insecure network.
- See <seealso marker="ssh:SSH_app#supported">ssh(6)</seealso> for details of supported RFCs, versions,
+ See <seeapp marker="ssh:SSH_app#supported">ssh(6)</seeapp> for details of supported RFCs, versions,
algorithms and unicode handling.
</p>
<p>With the SSH application it is possible to start <i>clients</i> and to start <i>daemons</i> (servers).
</p>
<p>Clients are started with
- <seealso marker="#connect/2">connect/2</seealso>,
- <seealso marker="#connect/3">connect/3</seealso> or
- <seealso marker="#connect/4">connect/4</seealso>. They open an encrypted connection on top of TCP/IP.
+ <seemfa marker="#connect/2">connect/2</seemfa>,
+ <seemfa marker="#connect/3">connect/3</seemfa> or
+ <seemfa marker="#connect/4">connect/4</seemfa>. They open an encrypted connection on top of TCP/IP.
In that encrypted connection one or more channels could be opened with
- <seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seealso>.
+ <seemfa marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seemfa>.
</p>
<p>Each channel is an isolated "pipe" between a client-side process and a server-side process. Those process
pairs could handle for example file transfers (sftp) or remote command execution (shell, exec and/or cli).
@@ -52,37 +52,37 @@
the user is not necessarily a human but probably a system interfacing the SSH app.
</p>
<p>A server-side subssystem (channel) server is requested by the client with
- <seealso marker="ssh_connection#subsystem/4">ssh_connection:subsystem/4</seealso>.
+ <seemfa marker="ssh_connection#subsystem/4">ssh_connection:subsystem/4</seemfa>.
</p>
<p>A server (daemon) is started with
- <seealso marker="#daemon/2">daemon/1</seealso>,
- <seealso marker="#daemon/2">daemon/2</seealso> or
- <seealso marker="#daemon/2">daemon/3</seealso>.
+ <seemfa marker="#daemon/2">daemon/1</seemfa>,
+ <seemfa marker="#daemon/2">daemon/2</seemfa> or
+ <seemfa marker="#daemon/2">daemon/3</seemfa>.
Possible channel handlers (subsystems) are declared with the
- <seealso marker="#type-subsystem_daemon_option">subsystem</seealso> option when the daemon is started.
+ <seetype marker="#subsystem_daemon_option">subsystem</seetype> option when the daemon is started.
</p>
<p>To just run a shell on a remote machine, there are functions that bundles the needed
three steps needed into one:
- <seealso marker="#shell/1">shell/1,2,3</seealso>.
+ <seemfa marker="#shell/1">shell/1,2,3</seemfa>.
Similarily, to just open an sftp (file transfer) connection to a remote machine, the simplest way is to use
- <seealso marker="ssh_sftp#start_channel/1">ssh_sftp:start_channel/1,2,3</seealso>.
+ <seemfa marker="ssh_sftp#start_channel/1">ssh_sftp:start_channel/1,2,3</seemfa>.
</p>
<p>To write your own client channel handler, use the behaviour
- <seealso marker="ssh_client_channel">ssh_client_channel</seealso>. For server channel handlers use
- <seealso marker="ssh_server_channel">ssh_server_channel</seealso> behaviour (replaces ssh_daemon_channel).
+ <seeerl marker="ssh_client_channel">ssh_client_channel</seeerl>. For server channel handlers use
+ <seeerl marker="ssh_server_channel">ssh_server_channel</seeerl> behaviour (replaces ssh_daemon_channel).
</p>
<p>Both clients and daemons accepts options that controls the exact behaviour. Some options are common to both.
The three sets are called
- <seealso marker="#type-client_options">Client Options</seealso>,
- <seealso marker="#type-daemon_options">Daemon Options</seealso> and
- <seealso marker="#type-common_options">Common Options</seealso>.
+ <seetype marker="#client_options">Client Options</seetype>,
+ <seetype marker="#daemon_options">Daemon Options</seetype> and
+ <seetype marker="#common_options">Common Options</seetype>.
</p>
<p>The descriptions of the options uses the
- <seealso marker="doc/reference_manual:typespec">Erlang Type Language</seealso> with explaining text.
+ <seeguide marker="system/reference_manual:typespec">Erlang Type Language</seeguide> with explaining text.
</p>
<note>
- <p>The <seealso marker="users_guide">User's Guide</seealso> has examples and a
- <seealso marker="using_ssh">Getting Started</seealso>
+ <p>The <seeguide marker="index">User's Guide</seeguide> has examples and a
+ <seeguide marker="using_ssh">Getting Started</seeguide>
section.
</p>
</note>
@@ -91,23 +91,23 @@
<section>
<title>Keys and files</title>
<p>A number of objects must be present for the SSH application to work.
- Thoose objects are per default stored in files.
+ Those objects are per default stored in files.
The default names, paths and file formats are the same as for
<url href="http://www.openssh.com">OpenSSH</url>. Keys could be generated with the <c>ssh-keygen</c>
program from OpenSSH. See the
- <seealso marker="using_ssh#running-an-erlang-ssh-daemon">User's Guide</seealso>.
+ <seeguide marker="using_ssh#running-an-erlang-ssh-daemon">User's Guide</seeguide>.
</p>
<p>The paths could easily be changed by options:
- <seealso marker="ssh_file#type-user_dir_common_option"><c>user_dir</c></seealso> and
- <seealso marker="ssh_file#type-system_dir_daemon_option"><c>system_dir</c></seealso>.
+ <seetype marker="ssh_file#user_dir_common_option"><c>user_dir</c></seetype> and
+ <seetype marker="ssh_file#system_dir_daemon_option"><c>system_dir</c></seetype>.
</p>
<p>A completly different storage could be interfaced by writing call-back modules
using the behaviours
- <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso> and/or
- <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
+ <seeerl marker="ssh_client_key_api">ssh_client_key_api</seeerl> and/or
+ <seeerl marker="ssh_server_key_api">ssh_server_key_api</seeerl>.
A callback module is installed with the option
- <seealso marker="#type-key_cb_common_option"><c>key_cb</c></seealso>
+ <seetype marker="#key_cb_common_option"><c>key_cb</c></seetype>
to the client and/or the daemon.
</p>
@@ -123,12 +123,12 @@
<item><c>ssh_host_ecdsa_key</c> and <c>ssh_host_ecdsa_key.pub</c></item>
</list>
<p>The host keys directory could be changed with the option
- <seealso marker="ssh_file#type-system_dir_daemon_option"><c>system_dir</c></seealso>.</p>
+ <seetype marker="ssh_file#system_dir_daemon_option"><c>system_dir</c></seetype>.</p>
</item>
<item>Optional: one or more <i>User's public key</i> in case of <c>publickey</c> authorization.
Default is to store them concatenated in the file <c>.ssh/authorized_keys</c> in the user's home directory.
<p>The user keys directory could be changed with the option
- <seealso marker="ssh_file#type-user_dir_common_option"><c>user_dir</c></seealso>.</p>
+ <seetype marker="ssh_file#user_dir_common_option"><c>user_dir</c></seetype>.</p>
</item>
</list>
</section>
@@ -138,14 +138,14 @@
<p>The keys and some other data are by default stored in files in the directory <c>.ssh</c>
in the user's home directory.</p>
<p>The directory could be changed with the option
- <seealso marker="ssh_file#type-user_dir_common_option"><c>user_dir</c></seealso>.
+ <seetype marker="ssh_file#user_dir_common_option"><c>user_dir</c></seetype>.
</p>
<list>
<item>Optional: a list of <i>Host public key(s)</i> for previously connected hosts. This list
is handled by the SSH application without any need of user assistance. The default
is to store them in the file <c>known_hosts</c>.
<p>The
- <seealso marker="#type-host_accepting_client_options">host_accepting_client_options()</seealso>
+ <seetype marker="#host_accepting_client_options">host_accepting_client_options()</seetype>
are associated with this list of keys.
</p>
</item>
@@ -176,9 +176,20 @@
<name name="client_options"/>
<name name="client_option"/>
<desc>
- <p>Options for <seealso marker="#connect/3">clients</seealso>.
+ <p>Options for <seemfa marker="#connect/3">clients</seemfa>.
The individual options are further explained below or by following the hyperlinks.
</p>
+ <p>Note that not every
+ <seetype marker="kernel:gen_tcp#connect_option">gen_tcp:connect_option()</seetype>
+ is accepted. See
+ <seemfa marker="ssh#set_sock_opts/2">set_sock_opts/2</seemfa>
+ for a list of prohibited options.
+ </p>
+ <p>Also note that setting a
+ <seetype marker="kernel:gen_tcp#connect_option">gen_tcp:connect_option()</seetype>
+ could change the socket in a way that impacts the ssh client's behaviour
+ negatively. You use it on your own risk.
+ </p>
</desc>
</datatype>
@@ -190,18 +201,22 @@
<name name="fingerprint"/>
<desc>
<taglist>
- <tag><c>silently_accept_hosts</c></tag>
+ <tag>
+ <marker id="hardening_client_options--silently_accept_hosts"/>
+ <c>silently_accept_hosts</c>
+ </tag>
<item>
<p>This option guides the <c>connect</c> function on how to act when the connected server presents a Host
Key that the client has not seen before. The default is to ask the user with a question on stdio of whether to
accept or reject the new Host Key.
- See the option <seealso marker="ssh_file#type-user_dir_common_option"><c>user_dir</c></seealso>
+ See the option <seetype marker="ssh_file#user_dir_common_option"><c>user_dir</c></seetype>
for specifying the path to the file <c>known_hosts</c> where previously accepted Host Keys are recorded.
See also the option
- <seealso marker="#type-key_cb_common_option">key_cb</seealso>
+ <seetype marker="#key_cb_common_option">key_cb</seetype>
for the general way to handle keys.
</p>
- <p>The option can be given in three different forms as seen above:</p>
+ <p>The option can be given in three different forms as seen <seetype marker="ssh#accept_hosts">above</seetype>:
+ </p>
<list>
<item>The value is a <c>boolean()</c>.
The value <c>true</c> will make the client accept any unknown Host Key without any user interaction.
@@ -214,7 +229,7 @@
<list type="bulleted">
<item><c>PeerName</c> - a string with the name or address of the remote host.</item>
<item><c>FingerPrint</c> - the fingerprint of the Host Key as
- <seealso marker="public_key:public_key#ssh_hostkey_fingerprint-1">public_key:ssh_hostkey_fingerprint/1</seealso>
+ <seemfa marker="public_key:public_key#ssh_hostkey_fingerprint/1">public_key:ssh_hostkey_fingerprint/1</seemfa>
calculates it.
</item>
</list>
@@ -224,7 +239,7 @@
shall be used to calculate the fingerprint used in the call of the <c>accept_callback()</c>.
The <c>HashALgoSpec</c>
is either an atom or a list of atoms as the first argument in
- <seealso marker="public_key:public_key#ssh_hostkey_fingerprint-2">public_key:ssh_hostkey_fingerprint/2</seealso>.
+ <seemfa marker="public_key:public_key#ssh_hostkey_fingerprint/2">public_key:ssh_hostkey_fingerprint/2</seemfa>.
If it is a list of hash algorithm names, the <c>FingerPrint</c> argument in the
<c>accept_callback()</c> will be
a list of fingerprints in the same order as the corresponding name in the <c>HashAlgoSpec</c> list.
@@ -249,9 +264,9 @@
<item>
<p>If <c>true</c>, the client saves an accepted host key to avoid the
accept question the next time the same host is connected. If the option
- <seealso marker="#type-key_cb_common_option"><c>key_cb</c></seealso>
+ <seetype marker="#key_cb_common_option"><c>key_cb</c></seetype>
is not present, the key is saved in the file "known_hosts". See option
- <seealso marker="ssh_file#type-user_dir_common_option"><c>user_dir</c></seealso> for
+ <seetype marker="ssh_file#user_dir_common_option"><c>user_dir</c></seetype> for
the location of that file.
</p>
<p>If <c>false</c>, the key is not saved and the key will still be unknown
@@ -307,10 +322,10 @@
<name name="connect_timeout_client_option"/>
<desc>
<p>Sets a timeout on the transport layer connect time.
- For <seealso marker="kernel:gen_tcp"><c>gen_tcp</c></seealso> the time is in milli-seconds and the default
+ For <seeerl marker="kernel:gen_tcp"><c>gen_tcp</c></seeerl> the time is in milli-seconds and the default
value is <c>infinity</c>.
</p>
- <p>See the parameter <c>Timeout</c> in <seealso marker="#connect/4">connect/4</seealso> for
+ <p>See the parameter <c>Timeout</c> in <seemfa marker="#connect/4">connect/4</seemfa> for
a timeout of the negotiation phase.
</p>
</desc>
@@ -322,7 +337,7 @@
<p>Make the client tell the server that the client accepts extension negotiation, that is,
include <c>ext-info-c</c> in the kexinit message sent. See
<url href="https://tools.ietf.org/html/rfc8308">RFC 8308</url>
- for details and <seealso marker="SSH_app#supported-ext-info">ssh(6)</seealso>
+ for details and <seeapp marker="SSH_app#supported-ext-info">ssh(6)</seeapp>
for a list of currently implemented extensions.
</p>
<p>
@@ -338,9 +353,20 @@
<name name="daemon_options"/>
<name name="daemon_option"/>
<desc>
- <p>Options for <seealso marker="#daemon/1">daemons</seealso>.
+ <p>Options for <seemfa marker="#daemon/1">daemons</seemfa>.
The individual options are further explained below or by following the hyperlinks.
</p>
+ <p>Note that not every
+ <seetype marker="kernel:gen_tcp#listen_option">gen_tcp:listen_option()</seetype>
+ is accepted. See
+ <seemfa marker="ssh#set_sock_opts/2">set_sock_opts/2</seemfa>
+ for a list of prohibited options.
+ </p>
+ <p>Also note that setting a
+ <seetype marker="kernel:gen_tcp#listen_option">gen_tcp:listen_option()</seetype>
+ could change the socket in a way that impacts the ssh deamon's behaviour
+ negatively. You use it on your own risk.
+ </p>
</desc>
</datatype>
@@ -352,12 +378,12 @@
<desc>
<p>Defines a subsystem in the daemon.</p>
<p>The <c>subsystem_name</c> is the name that a client requests to start with for example
- <seealso marker="ssh_connection#subsystem/4">ssh_connection:subsystem/4</seealso>.
+ <seemfa marker="ssh_connection#subsystem/4">ssh_connection:subsystem/4</seemfa>.
</p>
<p>The <c>channel_callback</c> is the module that implements the
- <seealso marker="ssh_server_channel">ssh_server_channel</seealso> (replaces ssh_daemon_channel)
+ <seeerl marker="ssh_server_channel">ssh_server_channel</seeerl> (replaces ssh_daemon_channel)
behaviour in the daemon. See the section
- <seealso marker="using_ssh#usersguide_creating_a_subsystem">Creating a Subsystem</seealso>
+ <seeguide marker="using_ssh#usersguide_creating_a_subsystem">Creating a Subsystem</seeguide>
in the User's Guide for more information and an example.
</p>
<p>If the subsystems option is not present, the value of <c>ssh_sftpd:subsystem_spec([])</c> is used.
@@ -376,7 +402,7 @@
<p>Defines the read-eval-print loop used in a daemon when a shell is requested by the client.
The default is to use the Erlang shell: <c><![CDATA[{shell, start, []}]]></c>
</p>
- <p>See the option <seealso marker="#type-exec_daemon_option"><c>exec-option</c></seealso>
+ <p>See the option <seetype marker="#exec_daemon_option"><c>exec-option</c></seetype>
for a description of how the daemon executes shell-requests and exec-requests depending on
the shell- and exec-options.</p>
</desc>
@@ -404,7 +430,7 @@
is formatted to a string if it is a non-string type. No trailing newline is added in the ok-case.
</p>
<p>See the User's Guide section on
- <seealso marker="using_ssh#one-time-execution">One-Time Execution</seealso> for examples.
+ <seeguide marker="using_ssh#one-time-execution">One-Time Execution</seeguide> for examples.
</p>
<p>Error texts are returned on channel-type 1 which usually is piped to <c>stderr</c> on e.g Linux systems.
Texts from a successful execution are returned on channel-type 0 and
@@ -418,68 +444,68 @@
will be sent as data-events to the client. An OS shell client like the command 'ssh' will usally use
stdin and stdout for the user interface.
</p>
- <p>The option cooperates with the daemon-option <seealso marker="#type-shell_daemon_option"><c>shell</c></seealso>
+ <p>The option cooperates with the daemon-option <seetype marker="#shell_daemon_option"><c>shell</c></seetype>
in the following way:</p>
<taglist>
- <tag>1. If neither the <seealso marker="#type-exec_daemon_option"><c>exec-option</c></seealso> nor the
- <seealso marker="#type-shell_daemon_option"><c>shell-option</c></seealso> is present:</tag>
+ <tag>1. If neither the <seetype marker="#exec_daemon_option"><c>exec-option</c></seetype> nor the
+ <seetype marker="#shell_daemon_option"><c>shell-option</c></seetype> is present:</tag>
<item>
<p>The default Erlang evaluator is used both for exec and shell requests.
The result is returned to the client.</p>
</item>
- <tag>2. If the <seealso marker="#type-exec_daemon_option"><c>exec_spec</c></seealso>'s value is <c>disabled</c>
- (the <seealso marker="#type-shell_daemon_option"><c>shell-option</c></seealso> may or may not be present):</tag>
+ <tag>2. If the <seetype marker="#exec_daemon_option"><c>exec_spec</c></seetype>'s value is <c>disabled</c>
+ (the <seetype marker="#shell_daemon_option"><c>shell-option</c></seetype> may or may not be present):</tag>
<item>
<p>No exec-requests are executed but shell-requests are not affected, they follow the
- <seealso marker="#type-shell_daemon_option"><c>shell_spec</c></seealso>'s value.</p>
+ <seetype marker="#shell_daemon_option"><c>shell_spec</c></seetype>'s value.</p>
</item>
- <tag>3. If the <seealso marker="#type-exec_daemon_option"><c>exec-option</c></seealso>
- is present and the <seealso marker="#type-exec_daemon_option"><c>exec_spec</c></seealso> value =/= <c>disabled</c>
- (the <seealso marker="#type-shell_daemon_option"><c>shell-option</c></seealso> may or may not be present):</tag>
+ <tag>3. If the <seetype marker="#exec_daemon_option"><c>exec-option</c></seetype>
+ is present and the <seetype marker="#exec_daemon_option"><c>exec_spec</c></seetype> value =/= <c>disabled</c>
+ (the <seetype marker="#shell_daemon_option"><c>shell-option</c></seetype> may or may not be present):</tag>
<item>
- <p>The <seealso marker="#type-exec_daemon_option"><c>exec_spec</c></seealso>
+ <p>The <seetype marker="#exec_daemon_option"><c>exec_spec</c></seetype>
<c>fun()</c> is called with the same number of parameters as the arity of the fun,
and the result is returned to the client.
Shell-requests are not affected, they follow the
- <seealso marker="#type-shell_daemon_option"><c>shell_spec</c></seealso>'s value.
+ <seetype marker="#shell_daemon_option"><c>shell_spec</c></seetype>'s value.
</p>
</item>
- <tag>4. If the <seealso marker="#type-exec_daemon_option"><c>exec-option</c></seealso> is absent, and the
- <seealso marker="#type-shell_daemon_option"><c>shell-option</c></seealso>
+ <tag>4. If the <seetype marker="#exec_daemon_option"><c>exec-option</c></seetype> is absent, and the
+ <seetype marker="#shell_daemon_option"><c>shell-option</c></seetype>
is present with the default Erlang shell as the
- <seealso marker="#type-shell_daemon_option"><c>shell_spec</c></seealso>'s
+ <seetype marker="#shell_daemon_option"><c>shell_spec</c></seetype>'s
value:</tag>
<item>
<p>The default Erlang evaluator is used both for exec and shell requests.
The result is returned to the client.</p>
</item>
- <tag>5. If the <seealso marker="#type-exec_daemon_option"><c>exec-option</c></seealso> is absent, and the
- <seealso marker="#type-shell_daemon_option"><c>shell-option</c></seealso>
+ <tag>5. If the <seetype marker="#exec_daemon_option"><c>exec-option</c></seetype> is absent, and the
+ <seetype marker="#shell_daemon_option"><c>shell-option</c></seetype>
is present with a value that is neither the default Erlang shell nor the value <c>disabled</c>:</tag>
<item>
<p>The exec-request is not evaluated and an error message is returned to the client. Shell-requests
are executed according to the value of the
- <seealso marker="#type-shell_daemon_option"><c>shell_spec</c></seealso>.</p>
+ <seetype marker="#shell_daemon_option"><c>shell_spec</c></seetype>.</p>
</item>
- <tag>6. If the <seealso marker="#type-exec_daemon_option"><c>exec-option</c></seealso> is absent, and the
- <seealso marker="#type-shell_daemon_option"><c>shell_spec</c></seealso>'s value is <c>disabled</c>:</tag>
+ <tag>6. If the <seetype marker="#exec_daemon_option"><c>exec-option</c></seetype> is absent, and the
+ <seetype marker="#shell_daemon_option"><c>shell_spec</c></seetype>'s value is <c>disabled</c>:</tag>
<item>
<p>Exec requests are executed by the default shell, but shell-requests are not executed.</p>
</item>
</taglist>
<p>If a custom CLI is installed (see the option
- <seealso marker="#type-ssh_cli_daemon_option"><c>ssh_cli</c></seealso>)
+ <seetype marker="#ssh_cli_daemon_option"><c>ssh_cli</c></seetype>)
the rules above are replaced by thoose implied by the custom CLI.
</p>
<note>
- <p>The <seealso marker="#type-exec_daemon_option"><c>exec-option</c></seealso>
+ <p>The <seetype marker="#exec_daemon_option"><c>exec-option</c></seetype>
has existed for a long time but has not previously been documented. The old
definition and behaviour are retained but obey the rules 1-6 above if conflicting.
The old and undocumented style should not be used in new programs.</p>
@@ -501,10 +527,10 @@
<p>Provides your own CLI implementation in a daemon.</p>
<p>It is a channel callback module that implements a shell
and command execution. The shell's read-eval-print loop can be customized, using the
- option <seealso marker="#type-shell_daemon_option"><c>shell</c></seealso>. This means less work than implementing
+ option <seetype marker="#shell_daemon_option"><c>shell</c></seetype>. This means less work than implementing
an own CLI channel. If <c>ssh_cli</c> is set to <c>no_cli</c>, the CLI channels
- like <seealso marker="#type-shell_daemon_option"><c>shell</c></seealso>
- and <seealso marker="#type-exec_daemon_option"><c>exec</c></seealso>
+ like <seetype marker="#shell_daemon_option"><c>shell</c></seetype>
+ and <seetype marker="#exec_daemon_option"><c>exec</c></seetype>
are disabled and only subsystem channels are allowed.</p>
</desc>
</datatype>
@@ -514,6 +540,7 @@
<name name="prompt_texts"/>
<name name="kb_int_tuple"/>
<name name="kb_int_fun_3"/>
+ <name name="kb_int_fun_4"/>
<name name="pwdfun_2"/>
<name name="pwdfun_4"/>
<desc>
@@ -522,7 +549,7 @@
<item>
<p>Sets the text strings that the daemon sends to the client for presentation to the user when
using <c>keyboard-interactive</c> authentication.</p>
- <p>If the fun/3 is used, it is called when the actual authentication occurs and may therefore
+ <p>If the fun/3 or fun/4 is used, it is called when the actual authentication occurs and may therefore
return dynamic data like time, remote ip etc.</p>
<p>The parameter <c>Echo</c> guides the client about need to hide the password.</p>
<p>The default value is:
@@ -538,6 +565,26 @@
</p>
</item>
+ <tag><marker id="option-pk_check_user"/><c>pk_check_user</c></tag>
+ <item>
+ <p>Enables checking of the
+ <seetype marker="#authentication_client_options">client's user name</seetype>
+ in the server when doing public key authentication. It is disabled by default.
+ </p>
+ <p>The term "user" is used differently in OpenSSH and SSH in Erlang/OTP:
+ see more in the <seeguide marker="terminology#the-term--user-">User's Guide</seeguide>.
+ </p>
+ <p>If the option is enabled, and no
+ <seeerl marker="#option-pwdfun"><c>pwdfun</c></seeerl>
+ is present, the user name must present in the
+ <seeerl marker="#option-user_passwords">user_passwords</seeerl>
+ for the check to succeed but the value of the password is not checked.
+ </p>
+ <p>In case of a <seeerl marker="#option-pwdfun"><c>pwdfun</c></seeerl>
+ checking the user, the atom <c>pubkey</c> is put in the password argument.
+ </p>
+ </item>
+
<tag><marker id="option-password"/><c>password</c></tag>
<item>
<p>Provides a global password that authenticates any user.</p>
@@ -548,19 +595,18 @@
</item>
<tag><marker id="option-pwdfun"/><c>pwdfun</c> with
- <seealso marker="#type-pwdfun_4"><c>pwdfun_4()</c></seealso>
+ <seetype marker="#pwdfun_4"><c>pwdfun_4()</c></seetype>
</tag>
<item>
<p>Provides a function for password validation. This could used for calling an external system or handeling
passwords stored as hash values.
</p>
<p>This fun can also be used to make delays in authentication tries for example by calling
- <seealso marker="stdlib:timer#sleep/1">timer:sleep/1</seealso>.</p>
+ <seemfa marker="stdlib:timer#sleep/1">timer:sleep/1</seemfa>.</p>
<p>To facilitate for instance counting of failed tries,
the <c>State</c> variable could be used. This state is per connection only. The first time the pwdfun
is called for a connection, the <c>State</c> variable has the value <c>undefined</c>.
</p>
-
<p>The fun should return:
</p>
<list type="bulleted">
@@ -571,13 +617,16 @@
<item><c>{true, NewState:any()}</c> if the user and password is valid</item>
<item><c>{false, NewState:any()}</c> if the user or password is invalid</item>
</list>
-
<p>A third usage is to block login attempts from a missbehaving peer. The <c>State</c> described above
can be used for this. The return value <c>disconnect</c> is useful for this.</p>
+ <p>In case of the <seeerl marker="#option-pk_check_user"><c>pk_check_user</c></seeerl> is set,
+ the atom <c>pubkey</c> is put in the password argument when validating a public key login. The
+ pwdfun is then responsible to check that the user name is valid.
+ </p>
</item>
<tag><c>pwdfun</c> with
- <seealso marker="#type-pwdfun_2"><c>pwdfun_2()</c></seealso>
+ <seetype marker="#pwdfun_2"><c>pwdfun_2()</c></seetype>
</tag>
<item>
<p>Provides a function for password validation. This function is called with user and password
@@ -586,6 +635,10 @@
<item><c>true</c> if the user and password is valid</item>
<item><c>false</c> if the user or password is invalid</item>
</list>
+ <p>In case of the <seeerl marker="#option-pk_check_user"><c>pk_check_user</c></seeerl> is set,
+ the atom <c>pubkey</c> is put in the password argument when validating a public key login. The
+ pwdfun is then responsible to check that the user name is valid.
+ </p>
<p>This variant is kept for compatibility.</p>
</item>
</taglist>
@@ -617,12 +670,12 @@
</item>
<tag><c>{ssh_moduli_file,filename()}</c></tag>
<item>The file must be in
- <seealso marker="public_key:public_key#dh_gex_group/4">ssh-keygen moduli file format</seealso>.
+ <seemfa marker="public_key:public_key#dh_gex_group/4">ssh-keygen moduli file format</seemfa>.
The file is read when the daemon starts.
</item>
</taglist>
<p>The default list is fetched from the
- <seealso marker="public_key:public_key#dh_gex_group/4">public_key</seealso> application.
+ <seemfa marker="public_key:public_key#dh_gex_group/4">public_key</seemfa> application.
</p>
</item>
@@ -646,6 +699,16 @@
</datatype>
<datatype>
+ <name name="hello_timeout_daemon_option"/>
+ <desc>
+ <p>Maximum time in milliseconds for the first part of the ssh session setup, the hello message exchange.
+ Defaults to 30000 ms (30 seconds). If the client fails to send the first message within this time,
+ the connection is closed.
+ </p>
+ </desc>
+ </datatype>
+
+ <datatype>
<name name="negotiation_timeout_daemon_option"/>
<desc>
<p>Maximum time in milliseconds for the authentication negotiation.
@@ -659,7 +722,10 @@
<name name="hardening_daemon_options"/>
<desc>
<taglist>
- <tag><c>max_sessions</c></tag>
+ <tag>
+ <marker id="hardening_daemon_options--max_sessions"/>
+ <c>max_sessions</c>
+ </tag>
<item>
<p>The maximum number of simultaneous sessions that are accepted at any time
for this daemon. This includes sessions that are being authorized.
@@ -678,8 +744,11 @@
<p>By default, this option is not set. This means that the number is not limited.
</p>
</item>
-
- <tag><c>max_channels</c></tag>
+
+ <tag>
+ <marker id="hardening_daemon_options--max_channels"/>
+ <c>max_channels</c>
+ </tag>
<item>
<p>The maximum number of channels with active remote subsystem that are accepted for
each connection to this daemon</p>
@@ -687,7 +756,10 @@
</p>
</item>
- <tag><c>parallel_login</c></tag>
+ <tag>
+ <marker id="hardening_daemon_options--parallel_login"/>
+ <c>parallel_login</c>
+ </tag>
<item>
<p>If set to false (the default value), only one login is handled at a time.
If set to true, an unlimited number of login attempts are allowed simultaneously.
@@ -704,7 +776,10 @@
</warning>
</item>
- <tag><c>minimal_remote_max_packet_size</c></tag>
+ <tag>
+ <marker id="hardening_daemon_options--minimal_remote_max_packet_size"/>
+ <c>minimal_remote_max_packet_size</c>
+ </tag>
<item>
<p>The least maximum packet size that the daemon will accept in channel open requests from the client.
The default value is 0.
@@ -738,7 +813,7 @@
<p>Make the server (daemon) tell the client that the server accepts extension negotiation, that is,
include <c>ext-info-s</c> in the kexinit message sent. See
<url href="https://tools.ietf.org/html/rfc8308">RFC 8308</url>
- for details and <seealso marker="SSH_app#supported-ext-info">ssh(6)</seealso>
+ for details and <seeapp marker="SSH_app#supported-ext-info">ssh(6)</seeapp>
for a list of currently implemented extensions.
</p>
<p>Default value is <c>true</c> which is compatible with other implementations not supporting ext-info.
@@ -746,6 +821,25 @@
</desc>
</datatype>
+ <datatype>
+ <name name="tcpip_tunnel_in_daemon_option"/>
+ <desc>
+ <p>Enables (<c>true</c>) or disables (<c>false</c>) the possibility to tunnel a TCP/IP connection in to a
+ <seemfa marker="ssh:ssh#daemon/2">server</seemfa>.
+ Disabled per default.
+ </p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="tcpip_tunnel_out_daemon_option"/>
+ <desc>
+ <p>Enables (<c>true</c>) or disables (<c>false</c>) the possibility to tunnel a TCP/IP connection out of a
+ <seemfa marker="ssh:ssh#daemon/2">server</seemfa>.
+ Disabled per default.
+ </p>
+ </desc>
+ </datatype>
<!--................................................................-->
<datatype_title>Options common to clients and daemons</datatype_title>
@@ -808,8 +902,8 @@
<name name="key_cb_common_option"/>
<desc>
<p>Module implementing the behaviour
- <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso> and/or
- <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
+ <seeerl marker="ssh_client_key_api">ssh_client_key_api</seeerl> and/or
+ <seeerl marker="ssh_server_key_api">ssh_server_key_api</seeerl>.
Can be used to
customize the handling of public keys. If callback options are provided
along with the module name, they are made available to the callback
@@ -818,19 +912,19 @@
<p>The <c>Opts</c> defaults to <c>[]</c> when only the <c>Module</c> is specified.
</p>
<p>The default value of this option is <c>{ssh_file, []}</c>. See also the manpage of
- <seealso marker="ssh:ssh_file">ssh_file</seealso>.
+ <seeerl marker="ssh:ssh_file">ssh_file</seeerl>.
</p>
<p>A call to the call-back function <c>F</c> will be</p>
<code>
Module:F(..., [{key_cb_private,Opts}|UserOptions])
</code>
<p>where <c>...</c> are arguments to <c>F</c> as in
- <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso> and/or
- <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
+ <seeerl marker="ssh_client_key_api">ssh_client_key_api</seeerl> and/or
+ <seeerl marker="ssh_server_key_api">ssh_server_key_api</seeerl>.
The <c>UserOptions</c> are the options given to
- <seealso marker="ssh:ssh#connect-3">ssh:connect</seealso>,
- <seealso marker="ssh:ssh#shell-1">ssh:shell</seealso> or
- <seealso marker="ssh:ssh#daemon-2">ssh:daemon</seealso>.
+ <seemfa marker="ssh:ssh#connect/3">ssh:connect</seemfa>,
+ <seemfa marker="ssh:ssh#shell/1">ssh:shell</seemfa> or
+ <seemfa marker="ssh:ssh#daemon/2">ssh:daemon</seemfa>.
</p>
</desc>
@@ -841,12 +935,12 @@
<desc>
<p>List of user (client) public key algorithms to try to use.</p>
<p>The default value is the <c>public_key</c> entry in the list returned by
- <seealso marker="#default_algorithms/0">ssh:default_algorithms/0</seealso>.
+ <seemfa marker="#default_algorithms/0">ssh:default_algorithms/0</seemfa>.
</p>
<p>If there is no public key of a specified type available, the corresponding entry is ignored.
Note that the available set is dependent on the underlying cryptolib and current user's public keys.
</p>
- <p>See also the option <seealso marker="ssh_file#type-user_dir_common_option"><c>user_dir</c></seealso>
+ <p>See also the option <seetype marker="ssh_file#user_dir_common_option"><c>user_dir</c></seetype>
for specifying the path to the user's keys.
</p>
</desc>
@@ -855,7 +949,7 @@
<datatype>
<name name="disconnectfun_common_option"/>
<desc>
- <p>Provides a fun to implement your own logging when the peer disconnects.</p>
+ <p>Provides a fun to implement your own logging or other handling at disconnects.</p>
</desc>
</datatype>
@@ -874,7 +968,7 @@
<p>Provide a fun to implement your own logging of the SSH message SSH_MSG_DEBUG.
The last three parameters are from the message, see
<url href="https://tools.ietf.org/html/rfc4253#section-11.3">RFC 4253, section 11.3</url>.
- The <seealso marker="#type-connection_ref"><c>connection_ref()</c></seealso> is the reference
+ The <seetype marker="#connection_ref"><c>connection_ref()</c></seetype> is the reference
to the connection on which the message arrived.
The return value from the fun is not checked.
</p>
@@ -912,7 +1006,7 @@
<desc>
<p>List of algorithms to use in the algorithm negotiation. The default <c>algs_list()</c> can
- be obtained from <seealso marker="#default_algorithms/0">default_algorithms/0</seealso>.
+ be obtained from <seemfa marker="#default_algorithms/0">default_algorithms/0</seemfa>.
</p>
<p>If an alg_entry() is missing in the algs_list(), the default value is used for that entry.</p>
<p>Here is an example of this option:</p>
@@ -930,7 +1024,7 @@
for cipher but specifies the same algorithms for mac and compression in both directions.
The kex (key exchange) is implicit but public_key is set explicitly.</p>
- <p>For background and more examples see the <seealso marker="configure_algos#introduction">User's Guide</seealso>.</p>
+ <p>For background and more examples see the <seeguide marker="configure_algos#introduction">User's Guide</seeguide>.</p>
<p>If an algorithm name occurs more than once in a list, the behaviour is undefined. The tags in the property lists
are also assumed to occur at most one time.
@@ -955,7 +1049,7 @@
<item>
<p>Input is the <c>modify_algs_list()</c> and a set of algorithms <c>A</c>
obtained from the <c>preferred_algorithms</c> option if existing, or else from the
- <seealso marker="ssh#default_algorithms-0">ssh:default_algorithms/0</seealso>.
+ <seemfa marker="ssh#default_algorithms/0">ssh:default_algorithms/0</seemfa>.
</p>
</item>
<item>
@@ -998,7 +1092,7 @@
<item><p>The compression algorithm none (= no compression) is removed so compression is enforced</p>
</item>
</list>
- <p>For background and more examples see the <seealso marker="configure_algos#introduction">User's Guide</seealso>.</p>
+ <p>For background and more examples see the <seeguide marker="configure_algos#introduction">User's Guide</seeguide>.</p>
</desc>
</datatype>
@@ -1058,8 +1152,8 @@
<datatype>
<name name="open_socket"/>
<desc>
- <p>The socket is supposed to be result of a <seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect</seealso>
- or a <seealso marker="kernel:gen_tcp#accept-1">gen_tcp:accept</seealso>. The socket must be in passive
+ <p>The socket is supposed to be result of a <seemfa marker="kernel:gen_tcp#connect/3">gen_tcp:connect</seemfa>
+ or a <seemfa marker="kernel:gen_tcp#accept/1">gen_tcp:accept</seemfa>. The socket must be in passive
mode (that is, opened with the option <c>{active,false})</c>.
</p>
</desc>
@@ -1069,7 +1163,7 @@
<name name="daemon_ref"/>
<desc>
<p>Opaque data type representing a daemon.</p>
- <p>Returned by the functions <seealso marker="ssh#daemon-1"><c>daemon/1,2,3</c></seealso>.</p>
+ <p>Returned by the functions <seemfa marker="ssh#daemon/1"><c>daemon/1,2,3</c></seemfa>.</p>
</desc>
</datatype>
@@ -1078,8 +1172,8 @@
<desc>
<p>Opaque data type representing a connection between a client and a server (daemon).</p>
<p>Returned by the functions
- <seealso marker="ssh#connect-3"><c>connect/2,3,4</c></seealso> and
- <seealso marker="ssh_sftp#start_channel-2"><c>ssh_sftp:start_channel/2,3</c></seealso>.
+ <seemfa marker="ssh#connect/3"><c>connect/2,3,4</c></seemfa> and
+ <seemfa marker="ssh_sftp#start_channel/2"><c>ssh_sftp:start_channel/2,3</c></seemfa>.
</p>
</desc>
</datatype>
@@ -1089,7 +1183,7 @@
<desc>
<p>Opaque data type representing a channel inside a connection.</p>
<p>Returned by the functions
- <seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seealso>.
+ <seemfa marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seemfa>.
</p>
</desc>
</datatype>
@@ -1103,8 +1197,8 @@
<name name="conn_info_channels"/>
<desc>
<p>Return values from the
- <seealso marker="#connection_info/1">connection_info/1</seealso> and
- <seealso marker="#connection_info/2">connection_info/2</seealso> functions.
+ <seemfa marker="#connection_info/1">connection_info/1</seemfa> and
+ <seemfa marker="#connection_info/2">connection_info/2</seemfa> functions.
</p>
<p>In the <c>option</c> info tuple are only the options included that differs from the default values.
</p>
@@ -1115,8 +1209,8 @@
<name name="daemon_info_tuple"/>
<desc>
<p>Return values from the
- <seealso marker="#daemon_info/1">daemon_info/1</seealso> and
- <seealso marker="#daemon_info/2">daemon_info/2</seealso> functions.
+ <seemfa marker="#daemon_info/1">daemon_info/1</seemfa> and
+ <seemfa marker="#daemon_info/2">daemon_info/2</seemfa> functions.
</p>
<p>In the <c>option</c> info tuple are only the options included that differs from the default values.
</p>
@@ -1124,13 +1218,10 @@
</datatype>
<datatype>
- <name>opaque_client_options</name>
- <name>opaque_daemon_options</name>
- <name>opaque_common_options</name>
+ <name>opaque_client_options()</name>
+ <name>opaque_daemon_options()</name>
+ <name>opaque_common_options()</name>
<desc>
- <marker id="type-opaque_client_options"/>
- <marker id="type-opaque_daemon_options"/>
- <marker id="type-opaque_common_options"/>
<p>Opaque types that define experimental options that are not to be used in products.</p>
</desc>
</datatype>
@@ -1159,12 +1250,12 @@
<name since="">connect(TcpSocket, Options, NegotiationTimeout) -> Result</name>
<fsummary>Connects to an SSH server.</fsummary>
<type>
- <v>Host = <seealso marker="#type-host">host()</seealso></v>
- <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v>
- <v>Options = <seealso marker="#type-client_options">client_options()</seealso></v>
- <v>TcpSocket = <seealso marker="#type-open_socket">open_socket()</seealso></v>
+ <v>Host = <seetype marker="#host">host()</seetype></v>
+ <v>Port = <seetype marker="kernel:inet#port_number">inet:port_number()</seetype></v>
+ <v>Options = <seetype marker="#client_options">client_options()</seetype></v>
+ <v>TcpSocket = <seetype marker="#open_socket">open_socket()</seetype></v>
<v>NegotiationTimeout = timeout()</v>
- <v>Result = {ok, <seealso marker="#type-connection_ref">connection_ref()</seealso>} | {error, term()}</v>
+ <v>Result = {ok, <seetype marker="#connection_ref">connection_ref()</seetype>} | {error, term()}</v>
</type>
<desc>
<p>Connects to an SSH server at the <c>Host</c> on <c>Port</c>.
@@ -1173,12 +1264,12 @@
The SSH initiation and negotiation will be initiated on that one with the SSH that should be at the
other end.
</p>
- <p>No channel is started. This is done by calling <seealso marker="ssh_connection#session_channel/2">
- ssh_connection:session_channel/[2, 4]</seealso>.
+ <p>No channel is started. This is done by calling <seemfa marker="ssh_connection#session_channel/2">
+ ssh_connection:session_channel/[2, 4]</seemfa>.
</p>
<p>The <c>NegotiationTimeout</c> is in milli-seconds. The default value is <c>infinity</c>.
For connection timeout, use the option
- <seealso marker="#type-connect_timeout_client_option"><c>connect_timeout</c></seealso>.
+ <seetype marker="#connect_timeout_client_option"><c>connect_timeout</c></seetype>.
</p>
</desc>
</func>
@@ -1203,10 +1294,19 @@
<desc>
<p>Sets tcp socket options on the tcp-socket below an ssh connection.</p>
<p>This function calls the
- <seealso marker="kernel:inet#setopts/2">inet:setopts/2</seealso>, read that documentation and
- for <seealso marker="kernel:gen_tcp#type-option">gen_tcp:option()</seealso>.
- All gen_tcp socket options except <c>active</c>, <c>deliver</c>, <c>mode</c> and <c>packet</c>
- are allowed. The excluded options are reserved by the SSH application.
+ <seemfa marker="kernel:inet#setopts/2">inet:setopts/2</seemfa>, read that documentation and
+ for <seetype marker="kernel:gen_tcp#option">gen_tcp:option()</seetype>.
+ </p>
+ <p>
+ All gen_tcp socket options except
+ </p>
+ <list>
+ <item><c>active</c></item>
+ <item><c>deliver</c></item>
+ <item><c>mode</c> and</item>
+ <item><c>packet</c></item>
+ </list>
+ <p>are allowed. The excluded options are reserved by the SSH application.
</p>
<warning>
<p>This is an extremly dangerous function. You use it on your own risk.</p>
@@ -1226,7 +1326,7 @@
<desc>
<p>Get tcp socket option values of the tcp-socket below an ssh connection.</p>
<p>This function calls the
- <seealso marker="kernel:inet#getopts/2">inet:getopts/2</seealso>, read that documentation.
+ <seemfa marker="kernel:inet#getopts/2">inet:getopts/2</seemfa>, read that documentation.
</p>
</desc>
</func>
@@ -1239,24 +1339,24 @@
<fsummary>Starts a server listening for SSH connections.</fsummary>
<type>
<v>Port = integer()</v>
- <v>TcpSocket = <seealso marker="#type-open_socket">open_socket()</seealso></v>
- <v>Options = <seealso marker="#type-daemon_options">daemon_options()</seealso></v>
- <v>HostAddress = <seealso marker="#type-host">host()</seealso> | any</v>
- <v>Result = {ok, <seealso marker="#type-daemon_ref">daemon_ref()</seealso>} | {error, atom()}</v>
+ <v>TcpSocket = <seetype marker="#open_socket">open_socket()</seetype></v>
+ <v>Options = <seetype marker="#daemon_options">daemon_options()</seetype></v>
+ <v>HostAddress = <seetype marker="#host">host()</seetype> | any</v>
+ <v>Result = {ok, <seetype marker="#daemon_ref">daemon_ref()</seetype>} | {error, atom()}</v>
</type>
<desc>
<p>Starts a server listening for SSH connections on the given port. If the <c>Port</c> is 0,
- a random free port is selected. See <seealso marker="#daemon_info/1">daemon_info/1</seealso>
+ a random free port is selected. See <seemfa marker="#daemon_info/1">daemon_info/1</seemfa>
about how to find the selected port number.
</p>
<p>As an alternative, an already open TCP socket could be passed to the function in <c>TcpSocket</c>.
The SSH initiation and negotiation will be initiated on that one when an SSH starts at the other end
of the TCP socket.
</p>
- <p>For a description of the options, see <seealso marker="#type-daemon_options">Daemon Options</seealso>.
+ <p>For a description of the options, see <seetype marker="#daemon_options">Daemon Options</seetype>.
</p>
<p>Please note that by historical reasons both the <c>HostAddress</c> argument and the
- <seealso marker="kernel:gen_tcp#type-connect_option">gen_tcp connect_option() <c>{ip,Address}</c></seealso>
+ <seetype marker="kernel:gen_tcp#connect_option">gen_tcp connect_option() <c>{ip,Address}</c></seetype>
set the listening address. This is a source of possible inconsistent settings.
</p>
<p>The rules for handling the two address passing options are:</p>
@@ -1301,7 +1401,7 @@
<desc>
<p>Returns a key-value list, where the keys are the different types of algorithms and the values are the
algorithms themselves.</p>
- <p>See the <seealso marker="configure_algos#example_default_algorithms">User's Guide</seealso> for
+ <p>See the <seeguide marker="configure_algos#example_default_algorithms">User's Guide</seeguide> for
an example.</p>
</desc>
</func>
@@ -1313,10 +1413,10 @@
<name since="">shell(Host, Port, Options) -> Result </name>
<fsummary>Starts an interactive shell on a remote SSH server.</fsummary>
<type>
- <v>Host = <seealso marker="#type-host">host()</seealso></v>
- <v>TcpSocket = <seealso marker="#type-open_socket">open_socket()</seealso></v>
- <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v>
- <v>Options = <seealso marker="#type-client_options">client_options()</seealso></v>
+ <v>Host = <seetype marker="#host">host()</seetype></v>
+ <v>TcpSocket = <seetype marker="#open_socket">open_socket()</seetype></v>
+ <v>Port = <seetype marker="kernel:inet#port_number">inet:port_number()</seetype></v>
+ <v>Options = <seetype marker="#client_options">client_options()</seetype></v>
<v>Result = ok | {error, Reason::term()}</v>
</type>
<desc>
@@ -1327,7 +1427,7 @@
The SSH initiation and negotiation will be initiated on that one and finaly a shell will be started
on the host at the other end of the TCP socket.
</p>
- <p>For a description of the options, see <seealso marker="#type-client_options">Client Options</seealso>.</p>
+ <p>For a description of the options, see <seetype marker="#client_options">Client Options</seetype>.</p>
<p>The function waits for user input, and does not return until the remote shell is ended (that is,
exit from the shell).
</p>
@@ -1341,7 +1441,7 @@
<desc>
<p>Utility function that starts the applications <c>crypto</c>, <c>public_key</c>,
and <c>ssh</c>. Default type is <c>temporary</c>.
- For more information, see the <seealso marker="kernel:application">application(3)</seealso>
+ For more information, see the <seeerl marker="kernel:application">application(3)</seeerl>
manual page in Kernel.</p>
</desc>
</func>
@@ -1351,7 +1451,7 @@
<fsummary>Stops the <c>ssh</c> application.</fsummary>
<desc>
<p>Stops the <c>ssh</c> application.
- For more information, see the <seealso marker="kernel:application">application(3)</seealso>
+ For more information, see the <seeerl marker="kernel:application">application(3)</seeerl>
manual page in Kernel.</p>
</desc>
</func>
@@ -1376,6 +1476,49 @@
</desc>
</func>
+ <func>
+ <name name="tcpip_tunnel_from_server" arity="5" since=""/>
+ <name name="tcpip_tunnel_from_server" arity="6" since=""/>
+ <fsummary>TCP/IP tunneling from a server to a client ("tcpip-forward")</fsummary>
+ <desc>
+ <p>Asks the remote server of <c>ConnectionRef</c> to listen to <c>ListenHost:ListenPort</c>.
+ When someone connects that address, the connection is forwarded in an encrypted channel from
+ the server to the client. The client (that is, at the node that calls this function) then
+ connects to <c>ConnectToHost:ConnectToPort</c>.
+ </p>
+ <p>The returned <c>TrueListenPort</c> is the port that is listened to. It is the same as
+ <c>ListenPort</c>, except when <c>ListenPort = 0</c>. In that case a free port is selected
+ by the underlying OS.
+ </p>
+ <p>Note that in case of an Erlang/OTP SSH server (daemon) as peer, that server must have been
+ started with the option
+ <seetype marker="#tcpip_tunnel_out_daemon_option">tcpip_tunnel_out</seetype>
+ to allow the connection.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="tcpip_tunnel_to_server" arity="5" since=""/>
+ <name name="tcpip_tunnel_to_server" arity="6" since=""/>
+ <fsummary>TCP/IP tunneling from a client to a server ("direct-tcpip")</fsummary>
+ <desc>
+ <p>Tells the local client to listen to <c>ListenHost:ListenPort</c>. When someone
+ connects to that address, the connection is forwarded in an encrypted channel to the peer server
+ of <c>ConnectionRef</c>. That server then connects to <c>ConnectToHost:ConnectToPort</c>.
+ </p>
+ <p>The returned <c>TrueListenPort</c> is the port that is listened to. It is the same as
+ <c>ListenPort</c>, except when <c>ListenPort = 0</c>. In that case a free port is selected
+ by the underlying OS.
+ </p>
+ <p>Note that in case of an Erlang/OTP SSH server (daemon) as peer, that server must have been
+ started with the option
+ <seetype marker="#tcpip_tunnel_in_daemon_option">tcpip_tunnel_in</seetype>
+ to allow the connection.
+ </p>
+ </desc>
+ </func>
+
</funcs>
</erlref>
diff --git a/lib/ssh/doc/src/ssh_agent.xml b/lib/ssh/doc/src/ssh_agent.xml
new file mode 100644
index 0000000000..f2d920705e
--- /dev/null
+++ b/lib/ssh/doc/src/ssh_agent.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2018</year><year>2018</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>ssh_agent</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module since="OTP 23.0">ssh_agent</module>
+ <modulesummary>Callback module for using an SSH agent instead of the default ssh_file callback.</modulesummary>
+ <description>
+ <p>
+ This module defines a callback handler for the communication with an
+ <url href="https://tools.ietf.org/html/draft-miller-ssh-agent-02">SSH Agent</url>
+ and can be used to replace the <seeerl marker="ssh:ssh_file">default callback</seeerl>.
+ This allows to issue signing requests to an agent that stores SSH private keys to perform
+ authentication.
+ </p>
+ <p>
+ Ssh_agent implements the <seeerl marker="ssh:ssh_client_key_api">ssh_client_key_api</seeerl>, to
+ allow it to be used by setting the option
+ <seetype marker="ssh:ssh#key_cb_common_option"><c>key_cb</c></seetype>
+ when starting a client (with for example
+ <seemfa marker="ssh:ssh#connect/3">ssh:connect</seemfa>,
+ <seemfa marker="ssh:ssh#shell/1">ssh:shell</seemfa>
+ ).
+ </p>
+ <code type="erl">
+ {key_cb, {ssh_agent, []}}
+ </code>
+ <p>
+ The agent communication is established through a UNIX domain socket. By default, the socket path
+ will be fetched from the <c>SSH_AUTH_SOCK</c> enviroment variable, which is the default socket path in the agent
+ implementation of
+ <url href="http://www.openssh.com">OpenSSH</url>.
+ </p>
+ <p>
+ <marker id="SOCKET_PATH"/>
+ In order to set a different socket path the <c>socket_path</c> option can be set.
+ </p>
+ <code type="erl">
+ {key_cb, {ssh_agent, [{socket_path, SocketPath}]}}
+ </code>
+
+ <note>
+ <p>The functions are <i>Callbacks</i> for the SSH app. They are not intended to be called from the user's code!
+ </p>
+ </note>
+ </description>
+
+ <datatypes>
+ <datatype_title>Options for the ssh_agent callback module</datatype_title>
+ <datatype>
+ <name name="socket_path_option"/>
+ <desc>
+ <p>Sets the <seeerl marker="#SOCKET_PATH">socket path</seeerl> for the communication with the agent.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="timeout_option"/>
+ <desc>
+ <p>
+ Sets the time-out in milliseconds when communicating with the agent via the socket.
+ The default value is <c>1000</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="call_ssh_file_option"/>
+ <desc>
+ <p>
+ The module which the <c>add_host_key</c> and <c>is_host_key</c> callbacks are delegated to. Defaults to the
+ <seeerl marker="ssh:ssh_file">ssh_file</seeerl> module.
+ </p>
+ </desc>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+ <func>
+ <name since="" name="add_host_key" arity="3"/>
+ <name since="" name="add_host_key" arity="4"/>
+ <fsummary></fsummary>
+ <desc>
+ <p>
+ This callback is delegated to the <seemfa marker="ssh:ssh_file#add_host_key/4">ssh_file</seemfa> module.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="" name="is_host_key" arity="4"/>
+ <name since="" name="is_host_key" arity="5"/>
+ <fsummary></fsummary>
+ <desc>
+ <p>
+ This callback is delegated to the <seemfa marker="ssh:ssh_file#is_host_key/5">ssh_file</seemfa> module.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="" name="user_key" arity="2"/>
+ <fsummary></fsummary>
+ <desc>
+ <p><strong>Types and description</strong></p>
+ <p>See the api description in
+ <seemfa marker="ssh:ssh_client_key_api#Module:user_key/2">ssh_client_key_api, Module:user_key/2</seemfa>.
+ </p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
diff --git a/lib/ssh/doc/src/ssh_client_channel.xml b/lib/ssh/doc/src/ssh_client_channel.xml
index e5f64ea603..e1be5996ce 100644
--- a/lib/ssh/doc/src/ssh_client_channel.xml
+++ b/lib/ssh/doc/src/ssh_client_channel.xml
@@ -53,7 +53,7 @@
</p>
<note><p>When implementing a <c>ssh</c> subsystem for daemons, use
- <seealso marker="ssh_server_channel">-behaviour(ssh_server_channel)</seealso> (Replaces ssh_daemon_channel)
+ <seeerl marker="ssh_server_channel">-behaviour(ssh_server_channel)</seeerl> (Replaces ssh_daemon_channel)
instead.
</p>
</note>
@@ -73,7 +73,7 @@
<fsummary>Makes a synchronous call to a channel.</fsummary>
<type>
<v>ChannelRef = pid() </v>
- <d>As returned by <seealso marker = "#start_link-4">start_link/4</seealso></d>
+ <d>As returned by <seemfa marker="#start_link/4">start_link/4</seemfa></d>
<v>Msg = term()</v>
<v>Timeout = timeout()</v>
<v>Reply = term()</v>
@@ -83,8 +83,7 @@
<desc>
<p>Makes a synchronous call to the channel process by sending
a message and waiting until a reply arrives, or a time-out
- occurs. The channel calls <seealso marker =
- "#Module:handle_call-3">Module:handle_call/3</seealso>
+ occurs. The channel calls <seemfa marker="#Module:handle_call/3">Module:handle_call/3</seemfa>
to handle the message. If the channel process does not exist,
<c>{error, closed}</c> is returned.
</p>
@@ -97,14 +96,14 @@
ChannelRef and returns ok.</fsummary>
<type>
<v>ChannelRef = pid()</v>
- <d>As returned by <seealso marker = "#start_link-4">start_link/4</seealso></d>
+ <d>As returned by <seemfa marker="#start_link/4">start_link/4</seemfa></d>
<v>Msg = term()</v>
</type>
<desc>
<p>Sends an asynchronous message to the channel process and
returns ok immediately, ignoring if the destination node or
channel process does not exist. The channel calls
- <seealso marker = "#Module:handle_cast-2">Module:handle_cast/2</seealso>
+ <seemfa marker="#Module:handle_cast/2">Module:handle_cast/2</seemfa>
to handle the message.
</p>
</desc>
@@ -115,17 +114,17 @@
<fsummary>Makes an existing process an ssh_client_channel (replaces ssh_channel) process.</fsummary>
<type>
<v>State = term()</v>
- <d>as returned by <seealso marker = "#init-1">init/1</seealso></d>
+ <d>as returned by <seemfa marker="#init/1">init/1</seemfa></d>
</type>
<desc>
<p>Makes an existing process an <c>ssh_client_channel</c> (replaces ssh_channel)
process. Does not return, instead the calling process
enters the <c>ssh_client_channel</c> (replaces ssh_channel) process receive loop and become an
<c>ssh_client_channel</c> process. The process must have been started using
- one of the start functions in <c>proc_lib</c>, see the <seealso
- marker="stdlib:proc_lib">proc_lib(3)</seealso> manual page in STDLIB.
+ one of the start functions in <c>proc_lib</c>, see the <seeerl
+ marker="stdlib:proc_lib">proc_lib(3)</seeerl> manual page in STDLIB.
The user is responsible for any initialization of the process
- and must call <seealso marker = "#init-1">init/1</seealso>.
+ and must call <seemfa marker="#init/1">init/1</seemfa>.
</p>
</desc>
</func>
@@ -150,14 +149,14 @@
<tag><c>{init_args(), list()}</c></tag>
<item><p>The list of arguments to the <c>init</c> function of the callback module.</p></item>
- <tag><c>{cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag>
+ <tag><c>{cm, </c><seetype marker="ssh:ssh#connection_ref">ssh:connection_ref()</seetype><c>}</c></tag>
<item><p>Reference to the <c>ssh</c> connection as returned by
- <seealso marker="ssh#connect-3">ssh:connect/3</seealso>.
+ <seemfa marker="ssh#connect/3">ssh:connect/3</seemfa>.
</p></item>
- <tag><c>{channel_id, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag>
+ <tag><c>{channel_id, </c><seetype marker="ssh:ssh#channel_id">ssh:channel_id()</seetype><c>}</c></tag>
<item><p>Id of the <c>ssh</c> channel as returned by
- <seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seealso>.
+ <seemfa marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seemfa>.
</p></item>
</taglist>
@@ -183,12 +182,12 @@
<p>This function can be used by a channel to send a
reply to a client that called <c>call/[2,3]</c> when the reply
cannot be defined in the return value of
- <seealso marker ="#Module:handle_call-3">Module:handle_call/3</seealso>.</p>
+ <seemfa marker="#Module:handle_call/3">Module:handle_call/3</seemfa>.</p>
<p><c>Client</c> must be the <c>From</c> argument provided to
the callback function <c>handle_call/3</c>.
<c>Reply</c> is an arbitrary term,
which is given back to the client as the return value of
- <seealso marker="#call-2">call/[2,3].</seealso></p>
+ <seemfa marker="#call/2">call/[2,3].</seemfa></p>
</desc>
</func>
@@ -198,13 +197,13 @@
{ok, ChannelRef} | {error, Reason}</name>
<fsummary>Starts a process that handles an SSH channel.</fsummary>
<type>
- <v>SshConnection = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v>
- <d>As returned by <seealso marker="ssh#connect-3">ssh:connect/3</seealso></d>
+ <v>SshConnection = <seetype marker="ssh:ssh#connection_ref">ssh:connection_ref()</seetype></v>
+ <d>As returned by <seemfa marker="ssh#connect/3">ssh:connect/3</seemfa></d>
- <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v>
+ <v>ChannelId = <seetype marker="ssh#channel_id">ssh:channel_id()</seetype></v>
<d>As returned by
- <seealso marker ="ssh_connection#session_channel/2">
- ssh_connection:session_channel/[2,4]</seealso>.</d>
+ <seemfa marker="ssh_connection#session_channel/2">
+ ssh_connection:session_channel/[2,4]</seemfa>.</d>
<v>ChannelCb = atom()</v>
<d>Name of the module implementing the service-specific parts
@@ -225,24 +224,23 @@
</func>
</funcs>
-
- <section>
- <title>Callback Functions</title>
- <p>
- The following functions are to be exported from a
- <c>ssh_client_channel</c> callback module.
- </p>
- <marker id="cb_timeouts"></marker>
- <section>
- <title>Callback timeouts</title>
- <p>The timeout values that can be returned by the callback functions
- have the same semantics as in a <seealso marker="stdlib:gen_server">gen_server</seealso>.
- If the time-out occurs, <seealso marker="#Module:handle_msg-2">handle_msg/2</seealso>
- is called as <c>handle_msg(timeout, State)</c>.</p>
- </section>
- </section>
<funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>
+ The following functions are to be exported from a
+ <c>ssh_client_channel</c> callback module.
+ </p>
+ <marker id="cb_timeouts"></marker>
+ <section>
+ <title>Callback timeouts</title>
+ <p>The timeout values that can be returned by the callback functions
+ have the same semantics as in a <seeerl marker="stdlib:gen_server">gen_server</seeerl>.
+ If the time-out occurs, <seemfa marker="#Module:handle_msg/2">handle_msg/2</seemfa>
+ is called as <c>handle_msg(timeout, State)</c>.</p>
+ </section>
+ </fsdescription>
<func>
<name since="OTP 21.0">Module:code_change(OldVsn, State, Extra) -> {ok,
NewState}</name>
@@ -271,7 +269,7 @@
<c>Change={advanced,Extra}</c>, is given in the <c>appup</c>
file. For more information, refer to Section 9.11.6
Release Handling Instructions in the
- <seealso marker="doc/design_principles:release_handling#instr">System Documentation</seealso>.
+ <seeguide marker="system/design_principles:release_handling#instr">System Documentation</seeguide>.
</p>
<note><p>Soft upgrade according to the OTP release concept
@@ -302,7 +300,7 @@
state if the initializations succeed.
</p>
<p>For more detailed information on time-outs, see Section
- <seealso marker="#cb_timeouts">Callback timeouts</seealso>. </p>
+ <seeerl marker="#cb_timeouts">Callback timeouts</seeerl>. </p>
</desc>
</func>
@@ -314,22 +312,22 @@
<v>Msg = term()</v>
<v>From = opaque()</v>
<d>Is to be used as argument to
- <seealso marker="#reply-2">reply/2</seealso></d>
+ <seemfa marker="#reply/2">reply/2</seemfa></d>
<v>State = term()</v>
<v>Result = {reply, Reply, NewState} | {reply, Reply, NewState, timeout()}
| {noreply, NewState} | {noreply , NewState, timeout()}
| {stop, Reason, Reply, NewState} | {stop, Reason, NewState} </v>
<v>Reply = term()</v>
- <d>Will be the return value of <seealso marker="#call-2">call/[2,3]</seealso></d>
+ <d>Will be the return value of <seemfa marker="#call/2">call/[2,3]</seemfa></d>
<v>NewState = term()</v>
<v>Reason = term()</v>
</type>
<desc>
<p>Handles messages sent by calling
- <seealso marker="#call-2">call/[2,3]</seealso>
+ <seemfa marker="#call/2">call/[2,3]</seemfa>
</p>
<p>For more detailed information on time-outs,, see Section
- <seealso marker="#cb_timeouts">Callback timeouts</seealso>.</p>
+ <seeerl marker="#cb_timeouts">Callback timeouts</seeerl>.</p>
</desc>
</func>
@@ -350,7 +348,7 @@
<c>cast/2</c>.
</p>
<p>For more detailed information on time-outs, see Section
- <seealso marker="#cb_timeouts">Callback timeouts</seealso>.</p>
+ <seeerl marker="#cb_timeouts">Callback timeouts</seeerl>.</p>
</desc>
</func>
@@ -362,7 +360,7 @@
call, or cast messages sent to the channel.</fsummary>
<type>
<v>Msg = timeout | term()</v>
- <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v>
+ <v>ChannelId = <seetype marker="ssh#channel_id">ssh:channel_id()</seetype></v>
<v>State = term() </v>
</type>
<desc>
@@ -374,10 +372,10 @@
function and all channels are to handle the following message.</p>
<taglist>
- <tag><c>{ssh_channel_up, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag>
+ <tag><c>{ssh_channel_up, </c><seetype marker="ssh:ssh#channel_id">ssh:channel_id()</seetype><c>, </c><seetype marker="ssh:ssh#connection_ref">ssh:connection_ref()</seetype><c>}</c></tag>
<item><p>This is the first message that the channel receives.
- It is sent just before the <seealso
- marker="#init-1">init/1</seealso> function
+ It is sent just before the <seemfa
+ marker="#init/1">init/1</seemfa> function
returns successfully. This is especially useful if the
server wants to send a message to the client without first
receiving a message from it. If the message is not
@@ -393,21 +391,21 @@
ChannelId, State}</name>
<fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary>
<type>
- <v>Msg = <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso></v>
- <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v>
+ <v>Msg = <seetype marker="ssh_connection#event">ssh_connection:event()</seetype></v>
+ <v>ChannelId = <seetype marker="ssh#channel_id">ssh:channel_id()</seetype></v>
<v>State = term()</v>
</type>
<desc>
<p>Handles SSH Connection Protocol messages that may need
service-specific attention. For details,
- see <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso>.
+ see <seetype marker="ssh_connection#event">ssh_connection:event()</seetype>.
</p>
<p>The following message is taken care of by the
<c>ssh_client_channel</c> behavior.</p>
<taglist>
- <tag><c>{closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag>
+ <tag><c>{closed, </c><seetype marker="ssh:ssh#channel_id">ssh:channel_id()</seetype><c>}</c></tag>
<item><p>The channel behavior sends a close message to the
other side, if such a message has not already been sent.
Then it terminates the channel with reason <c>normal</c>.</p></item>
@@ -425,9 +423,9 @@
</type>
<desc>
<p>This function is called by a channel process when it is
- about to terminate. Before this function is called, <seealso
- marker="ssh_connection#close-2"> ssh_connection:close/2
- </seealso> is called, if it has not been called earlier.
+ about to terminate. Before this function is called, <seemfa
+ marker="ssh_connection#close/2"> ssh_connection:close/2
+ </seemfa> is called, if it has not been called earlier.
This function does any necessary cleaning
up. When it returns, the channel process terminates with
reason <c>Reason</c>. The return value is ignored.
diff --git a/lib/ssh/doc/src/ssh_client_key_api.xml b/lib/ssh/doc/src/ssh_client_key_api.xml
index 9f2f3013e5..7d0121a7d0 100644
--- a/lib/ssh/doc/src/ssh_client_key_api.xml
+++ b/lib/ssh/doc/src/ssh_client_key_api.xml
@@ -38,46 +38,17 @@
the callbacks defined in this behavior, the public key handling of an SSH client can
be customized. By default the <c>ssh</c> application implements this behavior
with help of the standard OpenSSH files,
- see the <seealso marker="SSH_app"> ssh(6)</seealso> application manual.</p>
+ see the <seeapp marker="SSH_app"> ssh(6)</seeapp> application manual.</p>
</description>
- <!-- section>
- <title>DATA TYPES</title>
-
- <p>Type definitions that are used more than once in this module,
- or abstractions to indicate the intended use of the data
- type, or both. For more details on public key data types,
- refer to Section 2 Public Key Records in the
- <seealso marker="public_key:public_key_records"> public_key user's guide:</seealso>
- </p>
- <taglist>
- <tag><c>boolean() =</c></tag>
- <item><p><c>true | false</c></p></item>
- <tag><c>string() =</c></tag>
- <item><p><c>[byte()]</c></p></item>
- <tag><c>public_key() =</c></tag>
- <item><p><c>#'RSAPublicKey'{}
- | {integer(),#'Dss-Parms'{}}
- | {#'ECPoint'{},{namedCurve,Curve::string()}}</c></p></item>
- <tag><c>private_key() =</c></tag>
- <item><p><c>#'RSAPrivateKey'{}
- | #'DSAPrivateKey'{}
- | #'ECPrivateKey'{}</c></p></item>
- <tag><c>public_key_algorithm() =</c></tag>
- <item><p><c>'ssh-rsa' | 'ssh-dss'
- | 'rsa-sha2-256' | 'rsa-sha2-384' | 'rsa-sha2-512'
- | 'ecdsa-sha2-nistp256' | 'ecdsa-sha2-nistp384' | 'ecdsa-sha2-nistp521' </c></p></item>
- </taglist>
- </section -->
-
<datatypes>
<datatype>
<name name="client_key_cb_options"/>
<desc>
- <p>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso>.
+ <p>Options provided to <seemfa marker="ssh#connect/3">ssh:connect/[3,4]</seemfa>.
</p>
<p>The option list given in the
- <seealso marker="ssh#type-key_cb_common_option"><c>key_cb</c></seealso>
+ <seetype marker="ssh#key_cb_common_option"><c>key_cb</c></seetype>
option is available with the key <c>key_cb_private</c>.
</p>
</desc>
@@ -92,56 +63,122 @@
<v>HostNames = string()</v>
<d>Description of the host that owns the <c>PublicHostKey</c>.</d>
- <v>PublicHostKey = <seealso marker="public_key:public_key#type-public_key">public_key:public_key()</seealso></v>
+ <v>PublicHostKey = <seetype marker="public_key:public_key#public_key">public_key:public_key()</seetype></v>
<d>Of ECDSA keys, only the Normally an RSA, DSA or ECDSA public key, but handling of other public keys can be added.</d>
- <v>ConnectOptions = <seealso marker="#type-client_key_cb_options">client_key_cb_options()</seealso></v>
+ <v>ConnectOptions = <seetype marker="#client_key_cb_options">client_key_cb_options()</seetype></v>
</type>
<desc>
+ <p>This function is retired in favour for <c>Module:add_host_key/4</c> which is the prefered API function.
+ The calling SSH application will still try the <c>add_host_key/3</c> if the call to <c>add_host_key/4</c> failed.
+ </p>
<p>Adds a host key to the set of trusted host keys.</p>
</desc>
</func>
<func>
+ <name since="OTP R16B">Module:add_host_key(Host, Port, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
+ <fsummary>Adds a host key to the set of trusted host keys.</fsummary>
+ <type>
+ <v>Host = <seetype marker="kernel:inet#ip_address">inet:ip_address()</seetype>
+ | <seetype marker="kernel:inet#hostname">inet:hostname()</seetype>
+ | [ <seetype marker="kernel:inet#ip_address">inet:ip_address()</seetype>
+ | <seetype marker="kernel:inet#hostname">inet:hostname()</seetype> ]</v>
+ <d>The host that owns the <c>PublicHostKey</c>. One or more IP addresses or hostnames.</d>
+
+ <v>Port = <seetype marker="kernel:inet#port_number">inet:port_number()</seetype></v>
+ <d>The Port number of the Host.</d>
+
+ <v>PublicHostKey = <seetype marker="public_key:public_key#public_key">public_key:public_key()</seetype></v>
+ <d>Of ECDSA keys, only the Normally an RSA, DSA or ECDSA public key, but handling of other public keys can be added.</d>
+
+ <v>ConnectOptions = <seetype marker="#client_key_cb_options">client_key_cb_options()</seetype></v>
+ </type>
+ <desc>
+ <p>Adds a host key to the set of trusted host keys.</p>
+ <p>This function is prefered to the old <c>Module:add_host_key/3</c> since it also uses
+ the peer host port number and may return an error message.</p>
+ <p>The OTP/SSH application first calls this function in the callback module, and then
+ the old <c>Module:add_host_key/3</c> for compatibilty.</p>
+ </desc>
+ </func>
+
+ <func>
<name since="OTP R16B">Module:is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
<fsummary>Checks if a host key is trusted.</fsummary>
<type>
- <v>Key = <seealso marker="public_key:public_key#type-public_key">public_key:public_key()</seealso></v>
+ <v>Key = <seetype marker="public_key:public_key#public_key">public_key:public_key()</seetype></v>
<d>Normally an RSA, DSA or ECDSA public key, but handling of other public keys can be added.</d>
<v>Host = string()</v>
<d>Description of the host.</d>
- <v>Algorithm = <seealso marker="ssh#type-pubkey_alg">ssh:pubkey_alg()</seealso></v>
+ <v>Algorithm = <seetype marker="ssh#pubkey_alg">ssh:pubkey_alg()</seetype></v>
<d>Host key algorithm.</d>
- <v>ConnectOptions = <seealso marker="#type-client_key_cb_options">client_key_cb_options()</seealso></v>
+ <v>ConnectOptions = <seetype marker="#client_key_cb_options">client_key_cb_options()</seetype></v>
<v>Result = boolean()</v>
</type>
<desc>
+ <p>This function is retired in favour for <c>Module:is_host_key/5</c> which is the prefered API function.
+ The calling SSH application will still try the <c>is_host_key/4</c> if the call to <c>is_host_key/5</c> failed.
+ </p>
<p>Checks if a host key is trusted.</p>
</desc>
</func>
<func>
- <name since="OTP R16B">Module:user_key(Algorithm, ConnectOptions) ->
- {ok, PrivateKey} | {error, Reason}</name>
- <fsummary>Fetches the users <em>public key</em> matching the <c>Algorithm</c>.</fsummary>
+ <name since="OTP 23.0">Module:is_host_key(Key, Host, Port, Algorithm, ConnectOptions) -> Result</name>
+ <fsummary>Checks if a host key is trusted.</fsummary>
<type>
- <v>Algorithm = <seealso marker="ssh#type-pubkey_alg">ssh:pubkey_alg()</seealso></v>
+ <v>Key = <seetype marker="public_key:public_key#public_key">public_key:public_key()</seetype></v>
+ <d>Normally an RSA, DSA or ECDSA public key, but handling of other public keys can be added.</d>
+
+ <v>Host = <seetype marker="kernel:inet#ip_address">inet:ip_address()</seetype>
+ | <seetype marker="kernel:inet#hostname">inet:hostname()</seetype>
+ | [ <seetype marker="kernel:inet#ip_address">inet:ip_address()</seetype>
+ | <seetype marker="kernel:inet#hostname">inet:hostname()</seetype> ]</v>
+ <d>Description of the host with one or more IP addresses or hostnames.</d>
+
+ <v>Port = <seetype marker="kernel:inet#port_number">inet:port_number()</seetype></v>
+ <d>The Port number of the host.</d>
+
+ <v>Algorithm = <seetype marker="ssh#pubkey_alg">ssh:pubkey_alg()</seetype></v>
<d>Host key algorithm.</d>
- <v>ConnectOptions = <seealso marker="#type-client_key_cb_options">client_key_cb_options()</seealso></v>
+ <v>ConnectOptions = <seetype marker="#client_key_cb_options">client_key_cb_options()</seetype></v>
+
+ <v>Result = boolean() | {error, Error::term()}</v>
+ <d>The exact error message depends on the actual callback module. The Error message makes
+ the connection to fail, and is returned from e.g ssh:connect/3.</d>
+ </type>
+ <desc>
+ <p>Checks if a host key is trusted.</p>
+ <p>This function is prefered to the old <c>Module:is_host_key/4</c> since it also uses
+ the peer host port number and may return an error message.</p>
+ <p>The OTP/SSH application first calls this function in the callback module, and then
+ the old <c>Module:is_host_key/4</c> for compatibilty.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R16B">Module:user_key(Algorithm, ConnectOptions) -> Result</name>
+ <fsummary>Fetches the users <em>public key</em> matching the <c>Algorithm</c>.</fsummary>
+ <type>
+ <v>Algorithm = <seetype marker="ssh#pubkey_alg">ssh:pubkey_alg()</seetype></v>
+ <d>Host key algorithm.</d>
- <v>PrivateKey = <seealso marker="public_key:public_key#type-private_key">public_key:private_key()</seealso></v>
- <d>Private key of the user matching the <c>Algorithm</c>.</d>
+ <v>ConnectOptions = <seetype marker="#client_key_cb_options">client_key_cb_options()</seetype></v>
- <v>Reason = term()</v>
+ <v>Result = {ok, <seetype marker="public_key:public_key#private_key">public_key:private_key()</seetype>} |
+ {ok, {ssh2_pubkey, PubKeyBlob :: binary()}} |
+ {error, term()}</v>
</type>
<desc>
- <p>Fetches the users <em>public key</em> matching the <c>Algorithm</c>.</p>
+ <p>Fetches the users <em>public key</em> matching the <c>Algorithm</c>. Some key callback modules
+ may return <c>{ssh2_pubkey, PubKeyBlob :: binary()}</c>.</p>
<note><p>The private key contains the public key.</p></note>
</desc>
diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml
index 762cca1ded..cd4b9c0f85 100644
--- a/lib/ssh/doc/src/ssh_connection.xml
+++ b/lib/ssh/doc/src/ssh_connection.xml
@@ -42,14 +42,14 @@
SSH connection. The API functions in this module send SSH Connection Protocol events,
which are received as messages by the remote channel handling the remote channel.
The Erlang format of thoose messages is
- (see also <seealso marker="#type-event">below</seealso>):
+ (see also <seetype marker="#event">below</seetype>):
</p>
- <p><c>{ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, </c><seealso marker="#type-channel_msg"><c>channel_msg()</c></seealso><c>}</c>
+ <p><c>{ssh_cm, </c><seetype marker="ssh:ssh#connection_ref">ssh:connection_ref()</seetype><c>, </c><seetype marker="#channel_msg"><c>channel_msg()</c></seetype><c>}</c>
</p>
<p>
- If the <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior is used to
+ If the <seeerl marker="ssh_client_channel">ssh_client_channel</seeerl> behavior is used to
implement the channel process, these messages are handled by
- <seealso marker="ssh_client_channel#Module:handle_ssh_msg-2">handle_ssh_msg/2</seealso>.</p>
+ <seemfa marker="ssh_client_channel#Module:handle_ssh_msg/2">handle_ssh_msg/2</seemfa>.</p>
</description>
<datatypes>
@@ -57,7 +57,7 @@
<name name="ssh_data_type_code"/>
<desc>
<p>The valid values are <c>0</c> ("normal") and <c>1</c> ("stderr"), see
- <url href="https://tools.ietf.org/html/rfc4254#page-8">RFC 4254, Section 5.2</url>.</p>
+ <url href="https://tools.ietf.org/html/rfc4254#page/8">RFC 4254, Section 5.2</url>.</p>
</desc>
</datatype>
@@ -67,9 +67,9 @@
<desc>
<p>The result of a call.</p>
<p>If the request reached the peer, was handled and the response
- reached the requesting node the <seealso marker="#type-req_status">req_status()</seealso>
+ reached the requesting node the <seetype marker="#req_status">req_status()</seetype>
is the status reported from the peer.</p>
- <p>If not, the <seealso marker="#type-reason">reason()</seealso> indicates what went wrong:</p>
+ <p>If not, the <seetype marker="#reason">reason()</seetype> indicates what went wrong:</p>
<taglist>
<tag><c>closed</c></tag>
<item>indicates that the channel or connection was closed when trying to send the request
@@ -99,7 +99,7 @@
<p>As mentioned in the introduction, the
<url href="https://tools.ietf.org/html/rfc4254">SSH Connection Protocol</url>
events are handled as messages. When writing a channel handling process without using
- the support by the <seealso marker="ssh_client_channel">ssh_client_channel</seealso>
+ the support by the <seeerl marker="ssh_client_channel">ssh_client_channel</seeerl>
behavior the process must handle thoose messages.
</p>
</desc>
@@ -109,8 +109,8 @@
<name name="want_reply"/>
<desc>
<p>Messages that include a <c>WantReply</c> expect the channel handling
- process to call <seealso marker="ssh_connection#reply_request-4">
- ssh_connection:reply_request/4</seealso>
+ process to call <seemfa marker="ssh_connection#reply_request/4">
+ ssh_connection:reply_request/4</seemfa>
with the boolean value of <c>WantReply</c> as the second argument.</p>
</desc>
</datatype>
@@ -121,7 +121,7 @@
<name name="data_ch_msg"/>
<desc>
<p>Data has arrived on the channel. This event is sent as a result of calling
- <seealso marker="ssh_connection#send-3"> ssh_connection:send/[3,4,5]</seealso>.
+ <seemfa marker="ssh_connection#send/3"> ssh_connection:send/[3,4,5]</seemfa>.
</p>
</desc>
</datatype>
@@ -132,7 +132,7 @@
<name name="eof_ch_msg"/>
<desc>
<p>Indicates that the other side sends no more data. This event is sent as a result of calling
- <seealso marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>.
+ <seemfa marker="ssh_connection#send_eof/2"> ssh_connection:send_eof/2</seemfa>.
</p>
</desc>
</datatype>
@@ -140,9 +140,9 @@
<name name="closed_ch_msg"/>
<desc>
<p>This event is sent as a result of calling
- <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso>.
+ <seemfa marker="ssh_connection#close/2">ssh_connection:close/2</seemfa>.
Both the handling of this event and sending it are taken care of by the
- <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior.</p>
+ <seeerl marker="ssh_client_channel">ssh_client_channel</seeerl> behavior.</p>
</desc>
</datatype>
@@ -160,11 +160,11 @@
drawable area of the window. <c>Opcode</c> in the
<c>TerminalModes</c> list is the mnemonic name, represented
as a lowercase Erlang atom, defined in
- <url href="https://tools.ietf.org/html/rfc4254#section-8">RFC 4254</url>, Section 8.
+ <url href="https://tools.ietf.org/html/rfc4254#section/8">RFC 4254</url>, Section 8.
It can also be an <c>Opcode</c> if the mnemonic name is not listed in the
RFC. Example: <c>OP code: 53, mnemonic name ECHO erlang atom:
- echo</c>. This event is sent as a result of calling <seealso
- marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p>
+ echo</c>. This event is sent as a result of calling <seemfa
+ marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seemfa>.</p>
</desc>
</datatype>
@@ -174,8 +174,8 @@
<name name="env_ch_msg"/>
<desc>
<p>Environment variables can be passed to the shell/command
- to be started later. This event is sent as a result of calling <seealso
- marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>.
+ to be started later. This event is sent as a result of calling <seemfa
+ marker="ssh_connection#setenv/5"> ssh_connection:setenv/5</seemfa>.
</p>
</desc>
</datatype>
@@ -187,7 +187,7 @@
<desc>
<p>This message requests that the user default shell
is started at the other end. This event is sent as a result of calling
- <seealso marker="ssh_connection#shell-2"> ssh_connection:shell/2</seealso>.
+ <seemfa marker="ssh_connection#shell/2"> ssh_connection:shell/2</seemfa>.
</p>
</desc>
</datatype>
@@ -195,8 +195,8 @@
<name name="exec_ch_msg"/>
<desc>
<p>This message requests that the server starts
- execution of the given command. This event is sent as a result of calling <seealso
- marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>.
+ execution of the given command. This event is sent as a result of calling <seemfa
+ marker="ssh_connection#exec/4">ssh_connection:exec/4 </seemfa>.
</p>
</desc>
</datatype>
@@ -234,8 +234,8 @@
following message can be sent to return the exit status of the
command. A zero <c>exit_status</c> usually means that the command
terminated successfully. This event is sent as a result of calling
- <seealso marker="ssh_connection#exit_status-3">
- ssh_connection:exit_status/3</seealso>.</p>
+ <seemfa marker="ssh_connection#exit_status/3">
+ ssh_connection:exit_status/3</seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -260,11 +260,11 @@
<p>Adjusts the SSH flow control window. This is to be done by both the
client- and server-side channel processes.</p>
- <note><p>Channels implemented with the <seealso marker="ssh_client_channel"> ssh_client_channel</seealso>
+ <note><p>Channels implemented with the <seeerl marker="ssh_client_channel"> ssh_client_channel</seeerl>
behavior do not normally need to call this function as flow control
is handled by the behavior. The behavior adjusts the window every time
- the callback <seealso marker="ssh_client_channel#Module:handle_ssh_msg-2">
- handle_ssh_msg/2</seealso> returns after processing channel data.</p></note>
+ the callback <seemfa marker="ssh_client_channel#Module:handle_ssh_msg/2">
+ handle_ssh_msg/2</seemfa> returns after processing channel data.</p></note>
</desc>
</func>
@@ -276,8 +276,8 @@
sending a close event.
</p>
<note><p>This function is called by the <c>ssh_client_channel</c>
- behavior when the channel is terminated, see <seealso
- marker="ssh_client_channel"> ssh_client_channel(3)</seealso>. Thus, channels implemented
+ behavior when the channel is terminated, see <seeerl
+ marker="ssh_client_channel"> ssh_client_channel(3)</seeerl>. Thus, channels implemented
with the behavior are not to call this function explicitly.</p></note>
</desc>
</func>
@@ -292,27 +292,27 @@
request is a one-time execution that closes the channel when it is done.</p>
<taglist>
- <tag>N x <seealso marker="#type-data_ch_msg">data message(s)</seealso></tag>
+ <tag>N x <seetype marker="#data_ch_msg">data message(s)</seetype></tag>
<item><p>The result of executing the command can be only one line
or thousands of lines depending on the command.</p></item>
- <tag>0 or 1 x <seealso marker="#type-eof_ch_msg">eof message</seealso></tag>
+ <tag>0 or 1 x <seetype marker="#eof_ch_msg">eof message</seetype></tag>
<item><p>Indicates that no more data is to be sent.</p></item>
- <tag>0 or 1 x <seealso marker="#type-exit_signal_ch_msg">exit signal message</seealso></tag>
+ <tag>0 or 1 x <seetype marker="#exit_signal_ch_msg">exit signal message</seetype></tag>
<item><p>Not all systems send signals. For details on valid string
values, see RFC 4254, Section 6.10</p></item>
- <tag>0 or 1 x <seealso marker="#type-exit_status_ch_msg">exit status message</seealso></tag>
+ <tag>0 or 1 x <seetype marker="#exit_status_ch_msg">exit status message</seetype></tag>
<item><p>It is recommended by the SSH Connection Protocol to send this
message, but that is not always the case.</p></item>
- <tag>1 x <seealso marker="#type-closed_ch_msg">closed status message</seealso></tag>
+ <tag>1 x <seetype marker="#closed_ch_msg">closed status message</seetype></tag>
<item><p>Indicates that the <c>ssh_client_channel</c> started for the
execution of the command has now been shut down.</p></item>
</taglist>
<p>See the User's Guide section on
- <seealso marker="using_ssh#one-time-execution">One-Time Execution</seealso> for examples.
+ <seeguide marker="using_ssh#one-time-execution">One-Time Execution</seeguide> for examples.
</p>
</desc>
</func>
@@ -381,17 +381,17 @@
ok | Error</name>
<fsummary>Sends channel data.</fsummary>
<type>
- <v>ConnectionRef = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v>
- <v>ChannelId = <seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso></v>
+ <v>ConnectionRef = <seetype marker="ssh:ssh#connection_ref">ssh:connection_ref()</seetype></v>
+ <v>ChannelId = <seetype marker="ssh:ssh#channel_id">ssh:channel_id()</seetype></v>
<v>Data = iodata()</v>
- <v>Type = <seealso marker="#type-ssh_data_type_code">ssh_data_type_code()</seealso></v>
+ <v>Type = <seetype marker="#ssh_data_type_code">ssh_data_type_code()</seetype></v>
<v>Timeout = timeout()</v>
- <v>Error = {error, <seealso marker="#type-reason">reason()</seealso>}</v>
+ <v>Error = {error, <seetype marker="#reason">reason()</seetype>}</v>
</type>
<desc>
<p>Is to be called by client- and server-channel processes to send data to each other.
</p>
- <p>The function <seealso marker="ssh:ssh_connection#subsystem/4">subsystem/4</seealso> and subsequent
+ <p>The function <seemfa marker="ssh:ssh_connection#subsystem/4">subsystem/4</seemfa> and subsequent
calls of <c>send/3,4,5</c> must be executed in the same process.
</p>
</desc>
@@ -448,7 +448,7 @@
subsystem on the server.
</p>
<p>The function <c>subsystem/4</c> and subsequent calls of
- <seealso marker="ssh:ssh_connection#send/3">send/3,4,5</seealso> must be executed in the same process.
+ <seemfa marker="ssh:ssh_connection#send/3">send/3,4,5</seemfa> must be executed in the same process.
</p>
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_file.xml b/lib/ssh/doc/src/ssh_file.xml
index f1fef09083..dadadcc249 100644
--- a/lib/ssh/doc/src/ssh_file.xml
+++ b/lib/ssh/doc/src/ssh_file.xml
@@ -41,16 +41,16 @@
<url href="http://www.openssh.com">OpenSSH</url>.
</p>
- <p>Ssh_file implements the <seealso marker="ssh:ssh_server_key_api">ssh_server_key_api</seealso> and
- the <seealso marker="ssh:ssh_client_key_api">ssh_client_key_api</seealso>.
+ <p>Ssh_file implements the <seeerl marker="ssh:ssh_server_key_api">ssh_server_key_api</seeerl> and
+ the <seeerl marker="ssh:ssh_client_key_api">ssh_client_key_api</seeerl>.
This enables the user to make an own interface using for example a database handler.
</p>
<p>Such another callback module could be used by setting the option
- <seealso marker="ssh:ssh#type-key_cb_common_option"><c>key_cb</c></seealso>
+ <seetype marker="ssh:ssh#key_cb_common_option"><c>key_cb</c></seetype>
when starting a client or a server (with for example
- <seealso marker="ssh:ssh#connect-3">ssh:connect</seealso>,
- <seealso marker="ssh:ssh#daemon-2">ssh:daemon</seealso> of
- <seealso marker="ssh:ssh#shell-1">ssh:shell</seealso>
+ <seemfa marker="ssh:ssh#connect/3">ssh:connect</seemfa>,
+ <seemfa marker="ssh:ssh#daemon/2">ssh:daemon</seemfa> of
+ <seemfa marker="ssh:ssh#shell/1">ssh:shell</seemfa>
).
</p>
@@ -64,19 +64,19 @@
<title>Files, directories and who uses them</title>
<section>
<title>Daemons</title>
- <p>Daemons uses all files stored in the <seealso marker="#SYSDIR">SYSDIR</seealso> directory.
+ <p>Daemons uses all files stored in the <seeerl marker="#SYSDIR">SYSDIR</seeerl> directory.
</p>
<p>Optionaly, in case of <c>publickey</c> authorization, one or more of the remote user's public keys
- in the <seealso marker="#USERDIR">USERDIR</seealso> directory are used.
+ in the <seeerl marker="#USERDIR">USERDIR</seeerl> directory are used.
See the files
- <seealso marker="#USERDIR-authorized_keys"><c>USERDIR/authorized_keys</c></seealso> and
- <seealso marker="#USERDIR-authorized_keys2"><c>USERDIR/authorized_keys2</c></seealso>.
+ <seeerl marker="#FILE-authorized_keys"><c>USERDIR/authorized_keys</c></seeerl> and
+ <seeerl marker="#FILE-authorized_keys2"><c>USERDIR/authorized_keys2</c></seeerl>.
</p>
</section>
<section>
<title>Clients</title>
- <p>Clients uses all files stored in the <seealso marker="#USERDIR">USERDIR</seealso> directory.
+ <p>Clients uses all files stored in the <seeerl marker="#USERDIR">USERDIR</seeerl> directory.
</p>
</section>
@@ -90,39 +90,79 @@
<tag><marker id="SYSDIR"/>SYSDIR</tag>
<item><p>This is the directory holding the server's files:</p>
<list>
- <item><marker id="SYSDIR-ssh_host_dsa_key"/><c>ssh_host_dsa_key</c> - private dss host key (optional)</item>
- <item><marker id="SYSDIR-ssh_host_rsa_key"/><c>ssh_host_rsa_key</c> - private rsa host key (optional)</item>
- <item><marker id="SYSDIR-ssh_host_ecdsa_key"/><c>ssh_host_ecdsa_key</c> - private ecdsa host key (optional)</item>
- <item><marker id="SYSDIR-ssh_host_ed25519_key"/><c>ssh_host_ed25519_key</c> - private eddsa host key for curve 25519 (optional)</item>
- <item><marker id="SYSDIR-ssh_host_ed448_key"/><c>ssh_host_ed448_key</c> - private eddsa host key for curve 448 (optional)</item>
+ <item><marker id="FILE-ssh_host_STAR_key"/>
+ <marker id="FILE-ssh_host_dsa_key"/><c>ssh_host_dsa_key</c> - private dss host key (optional)</item>
+ <item><marker id="FILE-ssh_host_rsa_key"/><c>ssh_host_rsa_key</c> - private rsa host key (optional)</item>
+ <item><marker id="FILE-ssh_host_ecdsa_key"/><c>ssh_host_ecdsa_key</c> - private ecdsa host key (optional)</item>
+ <item><marker id="FILE-ssh_host_ed25519_key"/><c>ssh_host_ed25519_key</c> - private eddsa host key for curve 25519 (optional)</item>
+ <item><marker id="FILE-ssh_host_ed448_key"/><c>ssh_host_ed448_key</c> - private eddsa host key for curve 448 (optional)</item>
</list>
+ <p>The key files could be generated with OpenSSH's ssh-keygen command.</p>
<p>At least one host key must be defined. The default value of SYSDIR is <marker id="#/etc/ssh"/><c>/etc/ssh</c>.
</p>
<p>For security reasons, this directory is normally accessible only to the root user.
</p>
- <p>To change the SYSDIR, see the <seealso marker="#type-system_dir_daemon_option">system_dir</seealso> option.
+ <p>To change the SYSDIR, see the <seetype marker="#system_dir_daemon_option">system_dir</seetype> option.
</p>
</item>
<tag><marker id="USERDIR"/>USERDIR</tag>
<item><p>This is the directory holding the files:</p>
<list>
- <item><marker id="USERDIR-authorized_keys"/><c>authorized_keys</c>
+ <item><marker id="FILE-authorized_keys"/><c>authorized_keys</c>
and, as second alternative
- <marker id="USERDIR-authorized_keys2"/><c>authorized_keys2</c> -
+ <marker id="FILE-authorized_keys2"/><c>authorized_keys2</c> -
the user's public keys are stored concatenated in one of those files.
+ <p>It is composed of lines as for
+ <url href="https://man.openbsd.org/sshd#AUTHORIZED_KEYS_FILE_FORMAT">OpenSSH</url>:</p>
+ <pre>(options)? keytype base64-encoded-key comment</pre>
+ <p>where</p>
+ <code>
+options :: option(,option)*
+option :: % All options are skipped
+keytype :: 'ssh-dsa'
+ | 'ssh-rsa'
+ | 'ssh-ecdsa-nistp256'
+ | 'ssh-ecdsa-nistp384'
+ | 'ssh-ecdsa-nistp521'
+ | 'ssh-ed25519'
+ | 'ssh-ed448'
+base64-encoded-key :: % The user's public key
+comment :: % Comments are skipped
+ </code>
</item>
- <item><marker id="USERDIR-known_hosts"/><c>known_hosts</c> - host keys from hosts visited
- concatenated. The file is created and used by the client.</item>
- <item><marker id="USERDIR-id_dsa"/><c>id_dsa</c> - private dss user key (optional)</item>
- <item><marker id="USERDIR-id_rsa"/><c>id_rsa</c> - private rsa user key (optional)</item>
- <item><marker id="USERDIR-id_ecdsa"/><c>id_ecdsa</c> - private ecdsa user key (optional)</item>
- <item><marker id="USERDIR-id_ed25519"/><c>id_ed25519</c> - private eddsa user key for curve 25519 (optional)</item>
- <item><marker id="USERDIR-id_ed448"/><c>id_ed448</c> - private eddsa user key for curve 448 (optional)</item>
+ <item><marker id="FILE-known_hosts"/><c>known_hosts</c> - host keys from hosts visited
+ concatenated. The file is created and used by the client.
+ <p>It is composed of lines as for
+ <url href="https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT">OpenSSH</url>:</p>
+ <pre>(option)? pattern(,pattern)* keytype key (comment)?</pre>
+ <p>where</p>
+ <code>
+option :: '@revoked'
+pattern :: host | '[' host ']:' port
+host :: ip-address | hostname | '*'
+port :: portnumber | '*'
+keytype :: 'ssh-dsa'
+ | 'ssh-rsa'
+ | 'ssh-ecdsa-nistp256'
+ | 'ssh-ecdsa-nistp384'
+ | 'ssh-ecdsa-nistp521'
+ | 'ssh-ed25519'
+ | 'ssh-ed448'
+key :: % encoded key from eg ssh_host_*.pub
+</code>
+ </item>
+ <item><marker id="FILE-id_STAR"/>
+ <marker id="FILE-id_dsa"/><c>id_dsa</c> - private dss user key (optional)</item>
+ <item><marker id="FILE-id_rsa"/><c>id_rsa</c> - private rsa user key (optional)</item>
+ <item><marker id="FILE-id_ecdsa"/><c>id_ecdsa</c> - private ecdsa user key (optional)</item>
+ <item><marker id="FILE-id_ed25519"/><c>id_ed25519</c> - private eddsa user key for curve 25519 (optional)</item>
+ <item><marker id="FILE-id_ed448"/><c>id_ed448</c> - private eddsa user key for curve 448 (optional)</item>
</list>
- <p>The default value of USERDIR is <c>/home/</c><seealso marker="#LOCALUSER"><c>LOCALUSER</c></seealso><c>/.ssh</c>.
+ <p>The key files could be generated with OpenSSH's ssh-keygen command.</p>
+ <p>The default value of USERDIR is <c>/home/</c><seeerl marker="#LOCALUSER"><c>LOCALUSER</c></seeerl><c>/.ssh</c>.
</p>
- <p>To change the USERDIR, see the <seealso marker="#type-user_dir_common_option">user_dir</seealso> option
+ <p>To change the USERDIR, see the <seetype marker="#user_dir_common_option">user_dir</seetype> option
</p>
</item>
</taglist>
@@ -134,7 +174,7 @@
<datatype>
<name name="user_dir_common_option"/>
<desc>
- <p>Sets the <seealso marker="#USERDIR">user directory</seealso>.</p>
+ <p>Sets the <seeerl marker="#USERDIR">user directory</seeerl>.</p>
</desc>
</datatype>
@@ -142,7 +182,7 @@
<name name="user_dir_fun_common_option"/>
<name name="user2dir"/>
<desc>
- <p>Sets the <seealso marker="#USERDIR">user directory</seealso> dynamically
+ <p>Sets the <seeerl marker="#USERDIR">user directory</seeerl> dynamically
by evaluating the <c>user2dir</c> function.
</p>
</desc>
@@ -151,7 +191,7 @@
<datatype>
<name name="system_dir_daemon_option"/>
<desc>
- <p>Sets the <seealso marker="#SYSDIR">system directory</seealso>.</p>
+ <p>Sets the <seeerl marker="#SYSDIR">system directory</seeerl>.</p>
</desc>
</datatype>
@@ -165,117 +205,145 @@
</desc>
</datatype>
+ <datatype>
+ <name name="optimize_key_lookup"/>
+ <desc>
+ <p>Make the handling of large files fast by setting <c>time</c>, but this will use more memory.
+ The <c>space</c> variant shrinks the memory requirements, but with a higher time consumption.
+ </p>
+ <p>To set it, set the
+ option <c>{key_cb, {ssh_file, [{optimize,TimeOrSpace}]}</c> in the call of
+ <seemfa marker="ssh#connect/3">"ssh:connect/3</seemfa>,
+ <seemfa marker="ssh#daemon/2">ssh:daemon/2</seemfa>
+ or similar function call that initiates an ssh connection.
+ </p>
+ </desc>
+ </datatype>
+
</datatypes>
<funcs>
<func>
- <name since="OTP 21.2">host_key(Algorithm, DaemonOptions) -> {ok, Key} | {error, Reason}</name>
+ <name since="OTP 21.2" name="host_key" arity="2"/>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
<p>See the api description in
- <seealso marker="ssh:ssh_server_key_api#Module:host_key-2">ssh_server_key_api, Module:host_key/2</seealso>.
+ <seemfa marker="ssh:ssh_server_key_api#Module:host_key/2">ssh_server_key_api, Module:host_key/2</seemfa>.
</p>
<p><strong>Options</strong></p>
<list>
- <item><seealso marker="#type-system_dir_daemon_option">system_dir</seealso></item>
+ <item><seetype marker="#system_dir_daemon_option">system_dir</seetype></item>
<!-- item>dsa_pass_phrase</item -->
<!-- item>rsa_pass_phrase</item -->
<!-- item>ecdsa_pass_phrase</item -->
</list>
<p><strong>Files</strong></p>
<list>
- <item><seealso marker="#SYSDIR-ssh_host_rsa_key"><c>SYSDIR/ssh_host_rsa_key</c></seealso></item>
- <item><seealso marker="#SYSDIR-ssh_host_dsa_key"><c>SYSDIR/ssh_host_dsa_key</c></seealso></item>
- <item><seealso marker="#SYSDIR-ssh_host_ecdsa_key"><c>SYSDIR/ssh_host_ecdsa_key</c></seealso></item>
- <item><seealso marker="#SYSDIR-ssh_host_ed25519_key"><c>SYSDIR/ssh_host_ed25519_key</c></seealso></item>
- <item><seealso marker="#SYSDIR-ssh_host_ed448_key"><c>SYSDIR/ssh_host_ed448_key</c>c></seealso></item>
+ <item><seeerl marker="#FILE-ssh_host_rsa_key"><c>SYSDIR/ssh_host_rsa_key</c></seeerl></item>
+ <item><seeerl marker="#FILE-ssh_host_dsa_key"><c>SYSDIR/ssh_host_dsa_key</c></seeerl></item>
+ <item><seeerl marker="#FILE-ssh_host_ecdsa_key"><c>SYSDIR/ssh_host_ecdsa_key</c></seeerl></item>
+ <item><seeerl marker="#FILE-ssh_host_ed25519_key"><c>SYSDIR/ssh_host_ed25519_key</c></seeerl></item>
+ <item><seeerl marker="#FILE-ssh_host_ed448_key"><c>SYSDIR/ssh_host_ed448_key</c>c></seeerl></item>
</list>
</desc>
</func>
<func>
- <name since="OTP 21.2">is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
+ <name since="OTP 21.2" name="is_auth_key" arity="3"/>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
<p>See the api description in
- <seealso marker="ssh:ssh_server_key_api#Module:is_auth_key-3">ssh_server_key_api: Module:is_auth_key/3</seealso>.
+ <seemfa marker="ssh:ssh_server_key_api#Module:is_auth_key/3">ssh_server_key_api: Module:is_auth_key/3</seemfa>.
</p>
<p><strong>Options</strong></p>
<list>
- <item><seealso marker="#type-user_dir_fun_common_option">user_dir_fun</seealso></item>
- <item><seealso marker="#type-user_dir_common_option">user_dir</seealso></item>
+ <item><seetype marker="#user_dir_fun_common_option">user_dir_fun</seetype></item>
+ <item><seetype marker="#user_dir_common_option">user_dir</seetype></item>
</list>
<p><strong>Files</strong></p>
<list>
- <item><seealso marker="#USERDIR-authorized_keys"><c>USERDIR/authorized_keys</c></seealso></item>
- <item><seealso marker="#USERDIR-authorized_keys2"><c>USERDIR/authorized_keys2</c></seealso></item>
+ <item><seeerl marker="#FILE-authorized_keys"><c>USERDIR/authorized_keys</c></seeerl></item>
+ <item><seeerl marker="#FILE-authorized_keys2"><c>USERDIR/authorized_keys2</c></seeerl></item>
</list>
+ <p>This functions discards all options in the begining of the lines of thoose files when reading them.
+ </p>
</desc>
</func>
<func>
- <name since="OTP 21.2">add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
+ <name since="OTP 23.0" name="add_host_key" arity="4"/>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
<p>See the api description in
- <seealso marker="ssh:ssh_client_key_api#Module:add_host_key-3">ssh_client_key_api, Module:add_host_key/3</seealso>.
+ <seemfa marker="ssh:ssh_client_key_api#Module:add_host_key/4">ssh_client_key_api, Module:add_host_key/4</seemfa>.
+ </p>
+ <marker id="add_host_key-3"/>
+ <p>Note that the alternative, the old
+ <seemfa marker="ssh:ssh_client_key_api#Module:add_host_key/3">Module:add_host_key/3</seemfa>
+ is no longer supported by <c>ssh_file</c>.
</p>
<p><strong>Option</strong></p>
<list>
- <item><seealso marker="#type-user_dir_common_option">user_dir</seealso></item>
+ <item><seetype marker="#user_dir_common_option">user_dir</seetype></item>
</list>
<p><strong>File</strong></p>
<list>
- <item><seealso marker="#USERDIR-known_hosts"><c>USERDIR/known_hosts</c></seealso></item>
+ <item><seeerl marker="#FILE-known_hosts"><c>USERDIR/known_hosts</c></seeerl></item>
</list>
</desc>
</func>
<func>
- <name since="OTP 21.2">is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
+ <name since="OTP 23.0" name="is_host_key" arity="5"/>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
<p>See the api description in
- <seealso marker="ssh:ssh_client_key_api#Module:is_host_key-4">ssh_client_key_api, Module:is_host_key/4</seealso>.
+ <seemfa marker="ssh:ssh_client_key_api#Module:is_host_key/5">ssh_client_key_api, Module:is_host_key/5</seemfa>.
+ </p>
+ <marker id="is_host_key-4"/>
+ <p>Note that the alternative, the old
+ <seemfa marker="ssh:ssh_client_key_api#Module:is_host_key/4">Module:is_host_key/4</seemfa>
+ is no longer supported by <c>ssh_file</c>.
</p>
<p><strong>Option</strong></p>
<list>
- <item><seealso marker="#type-user_dir_common_option">user_dir</seealso></item>
+ <item><seetype marker="#user_dir_common_option">user_dir</seetype></item>
</list>
<p><strong>File</strong></p>
<list>
- <item><seealso marker="#USERDIR-known_hosts"><c>USERDIR/known_hosts</c></seealso></item>
+ <item><seeerl marker="#FILE-known_hosts"><c>USERDIR/known_hosts</c></seeerl></item>
</list>
</desc>
</func>
<func>
- <name since="OTP 21.2">user_key(Algorithm, ConnectOptions) -> {ok, PrivateKey} | {error, Reason}</name>
+ <name since="OTP 21.2" name="user_key" arity="2"/>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
<p>See the api description in
- <seealso marker="ssh:ssh_client_key_api#Module:user_key-2">ssh_client_key_api, Module:user_key/2</seealso>.
+ <seemfa marker="ssh:ssh_client_key_api#Module:user_key/2">ssh_client_key_api, Module:user_key/2</seemfa>.
</p>
<p><strong>Options</strong></p>
<list>
- <item><seealso marker="#type-user_dir_common_option">user_dir</seealso></item>
- <item><seealso marker="#type-pubkey_passphrase_client_options">dsa_pass_phrase</seealso></item>
- <item><seealso marker="#type-pubkey_passphrase_client_options">rsa_pass_phrase</seealso></item>
- <item><seealso marker="#type-pubkey_passphrase_client_options">ecdsa_pass_phrase</seealso></item>
+ <item><seetype marker="#user_dir_common_option">user_dir</seetype></item>
+ <item><seetype marker="#pubkey_passphrase_client_options">dsa_pass_phrase</seetype></item>
+ <item><seetype marker="#pubkey_passphrase_client_options">rsa_pass_phrase</seetype></item>
+ <item><seetype marker="#pubkey_passphrase_client_options">ecdsa_pass_phrase</seetype></item>
</list>
<p>Note that EdDSA passhrases (Curves 25519 and 448) are not implemented.</p>
- <p><strong>Files</strong></p>
+ <p><strong>Files</strong>
+ </p>
<list>
- <item><seealso marker="#USERDIR-id_dsa"><c>USERDIR/id_dsa</c></seealso></item>
- <item><seealso marker="#USERDIR-id_rsa"><c>USERDIR/id_rsa</c></seealso></item>
- <item><seealso marker="#USERDIR-id_ecdsa"><c>USERDIR/id_ecdsa</c></seealso></item>
- <item><seealso marker="#USERDIR-id_ed25519"><c>USERDIR/id_ed25519</c></seealso></item>
- <item><seealso marker="#USERDIR-id_ed448"><c>USERDIR/id_ed448</c></seealso></item>
+ <item><seeerl marker="#FILE-id_dsa"><c>USERDIR/id_dsa</c></seeerl></item>
+ <item><seeerl marker="#FILE-id_rsa"><c>USERDIR/id_rsa</c></seeerl></item>
+ <item><seeerl marker="#FILE-id_ecdsa"><c>USERDIR/id_ecdsa</c></seeerl></item>
+ <item><seeerl marker="#FILE-id_ed25519"><c>USERDIR/id_ed25519</c></seeerl></item>
+ <item><seeerl marker="#FILE-id_ed448"><c>USERDIR/id_ed448</c></seeerl></item>
</list>
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_protocol.xml b/lib/ssh/doc/src/ssh_protocol.xml
index 0d99a96997..5f0e0923d1 100644
--- a/lib/ssh/doc/src/ssh_protocol.xml
+++ b/lib/ssh/doc/src/ssh_protocol.xml
@@ -37,7 +37,7 @@
authentication and integrity protection. Currently, only a
minimum of MAC- (message authentication code, a short piece of
information used to authenticate a message) and encryption
- algorithms are supported see <seealso marker="ssh">ssh(3)</seealso>
+ algorithms are supported see <seeerl marker="ssh">ssh(3)</seeerl>
</p>
</section>
@@ -57,13 +57,13 @@
this case the the plain text password will be encrypted before sent
over the network. There are several configuration options for
authentication handling available in
- <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso>
- and <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso>
+ <seemfa marker="ssh#connect/3">ssh:connect/[3,4]</seemfa>
+ and <seemfa marker="ssh#daemon/2">ssh:daemon/[2,3]</seemfa>
It is also possible to customize the public key handling
- by implementing the behaviours <seealso
- marker="ssh_client_key_api">ssh_client_key_api</seealso> and
- <seealso
- marker="ssh_server_key_api">ssh_server_key_api</seealso>
+ by implementing the behaviours <seeerl
+ marker="ssh_client_key_api">ssh_client_key_api</seeerl> and
+ <seeerl
+ marker="ssh_server_key_api">ssh_server_key_api</seeerl>
</p>
</section>
@@ -74,8 +74,8 @@
services over the transport pipe, such as channel multiplexing,
flow control, remote program execution, signal propagation,
connection forwarding, etc. Functions for handling the SSH
- Connection Protocol can be found in the module <seealso
- marker="ssh_connection">ssh_connection</seealso>.
+ Connection Protocol can be found in the module <seeerl
+ marker="ssh_connection">ssh_connection</seeerl>.
</p>
</section>
@@ -88,8 +88,8 @@
SSH client will open a channel, send data/commands, receive
data/"control information" and when it is done close the
channel. The
- <seealso marker="ssh_client_channel">ssh_client_channel</seealso> /
- <seealso marker="ssh_server_channel">ssh_server_channel</seealso> (Replaces ssh_daemon_channel)
+ <seeerl marker="ssh_client_channel">ssh_client_channel</seeerl> /
+ <seeerl marker="ssh_server_channel">ssh_server_channel</seeerl> (Replaces ssh_daemon_channel)
behaviours makes it easy to
write your own SSH client/server processes that use flow
control. It handles generic parts of SSH channel management and
@@ -100,8 +100,8 @@
<list type="bulleted">
<item><em>Subsystem</em> - named services that can be run as
- part of an SSH server such as SFTP <seealso
- marker="ssh_sftpd">ssh_sftpd</seealso>, that is built in to the
+ part of an SSH server such as SFTP <seeerl
+ marker="ssh_sftpd">ssh_sftpd</seeerl>, that is built in to the
SSH daemon (server) by default but may be disabled. The Erlang SSH
daemon may be configured to run any Erlang
implemented SSH subsystem.
@@ -112,8 +112,8 @@
read-eval-print loop. It is also possible, but much more work,
to provide your own CLI (Command Line Interface) implementation.
</item>
- <item><em>Exec</em> - one-time remote execution of commands. See <seealso
- marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso></item>
+ <item><em>Exec</em> - one-time remote execution of commands. See <seemfa
+ marker="ssh_connection#exec/4">ssh_connection:exec/4</seemfa></item>
</list>
</section>
diff --git a/lib/ssh/doc/src/ssh_server_channel.xml b/lib/ssh/doc/src/ssh_server_channel.xml
index 037c4acda3..a0c97e140c 100644
--- a/lib/ssh/doc/src/ssh_server_channel.xml
+++ b/lib/ssh/doc/src/ssh_server_channel.xml
@@ -54,21 +54,22 @@
</p>
<note><p>When implementing a client subsystem handler, use
- <seealso marker="ssh_client_channel">-behaviour(ssh_client_channel)</seealso> instead.
+ <seeerl marker="ssh_client_channel">-behaviour(ssh_client_channel)</seeerl> instead.
</p>
</note>
</description>
- <section>
- <title>Callback Functions</title>
- <p>
- The following functions are to be exported from a
- <c>ssh_server_channel</c> callback module.
- </p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>
+ The following functions are to be exported from a
+ <c>ssh_server_channel</c> callback module.
+ </p>
+ </fsdescription>
<func>
<name since="OTP 21.0">Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
{stop, Reason}</name>
@@ -85,8 +86,8 @@
state if the initializations succeed.
</p>
<p>The time-out values that can be returned
- have the same semantics as in a <seealso marker="stdlib:gen_server">gen_server</seealso>.
- If the time-out occurs, <seealso marker="#Module:handle_msg-2">handle_msg/2</seealso>
+ have the same semantics as in a <seeerl marker="stdlib:gen_server">gen_server</seeerl>.
+ If the time-out occurs, <seemfa marker="#Module:handle_msg/2">handle_msg/2</seemfa>
is called as <c>handle_msg(timeout, State)</c>.
</p>
</desc>
@@ -100,7 +101,7 @@
call, or cast messages sent to the channel.</fsummary>
<type>
<v>Msg = timeout | term()</v>
- <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v>
+ <v>ChannelId = <seetype marker="ssh#channel_id">ssh:channel_id()</seetype></v>
<v>State = term() </v>
</type>
<desc>
@@ -112,7 +113,7 @@
function and all channels are to handle the following message.</p>
<taglist>
- <tag><c>{ssh_channel_up, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag>
+ <tag><c>{ssh_channel_up, </c><seetype marker="ssh:ssh#channel_id">ssh:channel_id()</seetype><c>, </c><seetype marker="ssh:ssh#connection_ref">ssh:connection_ref()</seetype><c>}</c></tag>
<item><p>This is the first message that the channel receives.
This is especially useful if the
server wants to send a message to the client without first
@@ -129,21 +130,21 @@
ChannelId, State}</name>
<fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary>
<type>
- <v>Msg = <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso></v>
- <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v>
+ <v>Msg = <seetype marker="ssh_connection#event">ssh_connection:event()</seetype></v>
+ <v>ChannelId = <seetype marker="ssh#channel_id">ssh:channel_id()</seetype></v>
<v>State = term()</v>
</type>
<desc>
<p>Handles SSH Connection Protocol messages that may need
service-specific attention. For details,
- see <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso>.
+ see <seetype marker="ssh_connection#event">ssh_connection:event()</seetype>.
</p>
<p>The following message is taken care of by the
<c>ssh_server_channel</c> behavior.</p>
<taglist>
- <tag><c>{closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag>
+ <tag><c>{closed, </c><seetype marker="ssh:ssh#channel_id">ssh:channel_id()</seetype><c>}</c></tag>
<item><p>The channel behavior sends a close message to the
other side, if such a message has not already been sent.
Then it terminates the channel with reason <c>normal</c>.</p></item>
@@ -161,9 +162,9 @@
</type>
<desc>
<p>This function is called by a channel process when it is
- about to terminate. Before this function is called, <seealso
- marker="ssh_connection#close-2"> ssh_connection:close/2
- </seealso> is called, if it has not been called earlier.
+ about to terminate. Before this function is called, <seemfa
+ marker="ssh_connection#close/2"> ssh_connection:close/2
+ </seemfa> is called, if it has not been called earlier.
This function does any necessary cleaning
up. When it returns, the channel process terminates with
reason <c>Reason</c>. The return value is ignored.
diff --git a/lib/ssh/doc/src/ssh_server_key_api.xml b/lib/ssh/doc/src/ssh_server_key_api.xml
index 013a788a4a..21cf00f1cb 100644
--- a/lib/ssh/doc/src/ssh_server_key_api.xml
+++ b/lib/ssh/doc/src/ssh_server_key_api.xml
@@ -38,7 +38,7 @@
the callbacks defined in this behavior, the public key handling of an SSH server can
be customized. By default the SSH application implements this behavior
with help of the standard OpenSSH files,
- see the <seealso marker="SSH_app"> ssh(6)</seealso> application manual.</p>
+ see the <seeapp marker="SSH_app"> ssh(6)</seeapp> application manual.</p>
</description>
<!-- section>
@@ -48,7 +48,7 @@
or abstractions to indicate the intended use of the data
type, or both. For more details on public key data types,
refer to Section 2 Public Key Records in the
- <seealso marker="public_key:public_key_records"> public_key user's guide</seealso>.
+ <seeguide marker="public_key:public_key_records"> public_key user's guide</seeguide>.
</p>
<taglist>
@@ -75,10 +75,10 @@
<datatype>
<name name="daemon_key_cb_options"/>
<desc>
- <p>Options provided to <seealso marker="ssh#daemon-2">ssh:daemon/2,3</seealso>.
+ <p>Options provided to <seemfa marker="ssh#daemon/2">ssh:daemon/2,3</seemfa>.
</p>
<p>The option list given in the
- <seealso marker="ssh#type-key_cb_common_option"><c>key_cb</c></seealso>
+ <seetype marker="ssh#key_cb_common_option"><c>key_cb</c></seetype>
option is available with the key <c>key_cb_private</c>.
</p>
</desc>
@@ -88,16 +88,16 @@
<funcs>
<func>
<name since="OTP R16B">Module:host_key(Algorithm, DaemonOptions) ->
- {ok, Key} | {error, Reason}</name>
+ {ok, PrivateKey} | {error, Reason}</name>
<fsummary>Fetches the host’s private key.</fsummary>
<type>
- <v>Algorithm = <seealso marker="ssh#type-pubkey_alg">ssh:pubkey_alg()</seealso></v>
+ <v>Algorithm = <seetype marker="ssh#pubkey_alg">ssh:pubkey_alg()</seetype></v>
<d>Host key algorithm.</d>
- <v>DaemonOptions = <seealso marker="#type-daemon_key_cb_options">daemon_key_cb_options()</seealso></v>
+ <v>DaemonOptions = <seetype marker="#daemon_key_cb_options">daemon_key_cb_options()</seetype></v>
- <v>PrivateKey = <seealso marker="public_key:public_key#type-private_key">public_key:private_key()</seealso>
- | <seealso marker="crypto:crypto#type-engine_key_ref">crypto:engine_key_ref()</seealso>
+ <v>PrivateKey = <seetype marker="public_key:public_key#private_key">public_key:private_key()</seetype>
+ | <seetype marker="crypto:crypto#engine_key_ref">crypto:engine_key_ref()</seetype>
</v>
<d>Private key of the host matching the <c>Algorithm</c>.
@@ -114,13 +114,13 @@
<name since="OTP R16B">Module:is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
<fsummary>Checks if the user key is authorized.</fsummary>
<type>
- <v>PublicUserKey = <seealso marker="public_key:public_key#type-public_key">public_key:public_key()</seealso></v>
+ <v>PublicUserKey = <seetype marker="public_key:public_key#public_key">public_key:public_key()</seetype></v>
<d>Normally an RSA, DSA or ECDSA public key, but handling of other public keys can be added</d>
<v>User = string()</v>
<d>User owning the public key.</d>
- <v>DaemonOptions = <seealso marker="#type-daemon_key_cb_options">daemon_key_cb_options()</seealso></v>
+ <v>DaemonOptions = <seetype marker="#daemon_key_cb_options">daemon_key_cb_options()</seetype></v>
<v>Result = boolean()</v>
</type>
diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml
index 5c8eba6203..6e30c1e20d 100644
--- a/lib/ssh/doc/src/ssh_sftp.xml
+++ b/lib/ssh/doc/src/ssh_sftp.xml
@@ -70,14 +70,14 @@
<name name="decrypt_spec"/>
<desc>
<p>Specifies the encryption or decryption applied to tar files when using
- <seealso marker="#open_tar/3">open_tar/3</seealso> or
- <seealso marker="#open_tar/4">open_tar/4</seealso>.
+ <seemfa marker="#open_tar/3">open_tar/3</seemfa> or
+ <seemfa marker="#open_tar/4">open_tar/4</seemfa>.
</p>
<p>The encryption or decryption is applied to the generated stream of
bytes prior to sending the resulting stream to the SFTP server.
</p>
<p>For code examples see Section
- <seealso marker="using_ssh#example-with-encryption">Example with encryption</seealso>
+ <seeguide marker="using_ssh#example-with-encryption">Example with encryption</seeguide>
in the ssh Users Guide.
</p>
</desc>
@@ -89,11 +89,11 @@
<name name="crypto_state"/>
<desc>
<p>The <c>init_fun()</c> in the
- <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso>
+ <seetype marker="#tar_crypto_spec">tar_crypto_spec</seetype>
is applied once prior to any other <c>crypto</c>
operation. The intention is that this function initiates the encryption or
decryption for example by calling
- <seealso marker="crypto:crypto#crypto_init/4">crypto:crypto_init/4</seealso>
+ <seemfa marker="crypto:crypto#crypto_init/4">crypto:crypto_init/4</seemfa>
or similar. The <c>crypto_state()</c> is the state such a function may return.
</p>
<p>If the selected cipher needs to have the input data partioned into
@@ -112,9 +112,9 @@
<name name="crypto_result"/>
<desc>
<p>The initial <c>crypto_state()</c> returned from the
- <seealso marker="#type-init_fun">init_fun()</seealso>
+ <seetype marker="#init_fun">init_fun()</seetype>
is folded into repeated applications of the <c>crypto_fun()</c> in the
- <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso>.
+ <seetype marker="#tar_crypto_spec">tar_crypto_spec</seetype>.
The binary returned from that fun is sent to the remote SFTP server and
the new <c>crypto_state()</c> is used in the next call of the
<c>crypto_fun()</c>.
@@ -130,7 +130,7 @@
<desc>
<p>If doing encryption,
the <c>final_fun()</c> in the
- <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso>
+ <seetype marker="#tar_crypto_spec">tar_crypto_spec</seetype>
is applied to the last piece of data.
The <c>final_fun()</c> is responsible for padding (if needed) and
encryption of that last piece.
@@ -144,8 +144,8 @@
<name name="apread" arity="4" since=""/>
<fsummary>Reads asynchronously from an open file.</fsummary>
<desc><p>The <c><![CDATA[apread/4]]></c> function reads from a specified position,
- combining the <seealso marker="#position-3"><c>position/3</c></seealso> and
- <seealso marker="#aread-3"><c>aread/3</c></seealso> functions.</p>
+ combining the <seemfa marker="#position/3"><c>position/3</c></seemfa> and
+ <seemfa marker="#aread/3"><c>aread/3</c></seemfa> functions.</p>
</desc>
</func>
@@ -153,8 +153,8 @@
<name name="apwrite" arity="4" since=""/>
<fsummary>Writes asynchronously to an open file.</fsummary>
<desc><p>The <c><![CDATA[apwrite/4]]></c> function writes to a specified position,
- combining the <seealso marker="#position-3"><c>position/3</c></seealso> and
- <seealso marker="#awrite-3"><c>awrite/3</c></seealso> functions.</p>
+ combining the <seemfa marker="#position/3"><c>position/3</c></seemfa> and
+ <seemfa marker="#awrite/3"><c>awrite/3</c></seemfa> functions.</p>
</desc>
</func>
@@ -276,18 +276,18 @@
<p>Opens a handle to a tar file on the server, associated with <c>ChannelPid</c>.
The handle can be used for remote tar creation and extraction. The actual writing
and reading is performed by calls to
- <seealso marker="stdlib:erl_tar#add-3">erl_tar:add/3,4</seealso> and
- <seealso marker="stdlib:erl_tar#extract-2">erl_tar:extract/2</seealso>.
+ <seemfa marker="stdlib:erl_tar#add/3">erl_tar:add/3,4</seemfa> and
+ <seemfa marker="stdlib:erl_tar#extract/2">erl_tar:extract/2</seemfa>.
Note: The
- <seealso marker="stdlib:erl_tar#init-3">erl_tar:init/3</seealso> function should not
+ <seemfa marker="stdlib:erl_tar#init/3">erl_tar:init/3</seemfa> function should not
be called, that one is called by this open_tar function.
</p>
<p>For code examples see Section
- <seealso marker="using_ssh#sftp-client-with-tar-compression">SFTP Client with TAR Compression</seealso>
+ <seeguide marker="using_ssh#sftp-client-with-tar-compression">SFTP Client with TAR Compression</seeguide>
in the ssh Users Guide.
</p>
<p>The <c>crypto</c> mode option is explained in the data types section above, see
- <seealso marker="#Crypto operations for open_tar">Crypto operations for open_tar</seealso>.
+ <seeerl marker="#Crypto operations for open_tar">Crypto operations for open_tar</seeerl>.
Encryption is assumed if the <c>Mode</c> contains <c>write</c>, and
decryption if the <c>Mode</c> contains <c>read</c>.
</p>
@@ -335,8 +335,8 @@
<name name="pread" arity="5" since=""/>
<fsummary>Reads from an open file.</fsummary>
<desc><p>The <c><![CDATA[pread/3,4]]></c> function reads from a specified position,
- combining the <seealso marker="#position-3"><c>position/3</c></seealso> and
- <seealso marker="#read-3"><c>read/3,4</c></seealso> functions.</p>
+ combining the <seemfa marker="#position/3"><c>position/3</c></seemfa> and
+ <seemfa marker="#read/3"><c>read/3,4</c></seemfa> functions.</p>
</desc>
</func>
@@ -345,8 +345,8 @@
<name name="pwrite" arity="5" since=""/>
<fsummary>Writes to an open file.</fsummary>
<desc><p>The <c><![CDATA[pwrite/3,4]]></c> function writes to a specified position,
- combining the <seealso marker="#position-3"><c>position/3</c></seealso> and
- <seealso marker="#write-3"><c>write/3,4</c></seealso> functions.</p>
+ combining the <seemfa marker="#position/3"><c>position/3</c></seemfa> and
+ <seemfa marker="#write/3"><c>write/3,4</c></seemfa> functions.</p>
</desc>
</func>
@@ -381,12 +381,12 @@
<desc>
<p>Returns a <c><![CDATA[file_info]]></c> record from the file system object specified by
<c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>. See
- <seealso marker="kernel:file#read_file_info-2">file:read_file_info/2</seealso>
+ <seemfa marker="kernel:file#read_file_info/2">file:read_file_info/2</seemfa>
for information about the record.
</p>
<p>
Depending on the underlying OS:es links might be followed and info on the final file, directory
- etc is returned. See <seealso marker="#read_link_info-2">read_link_info/2</seealso>
+ etc is returned. See <seemfa marker="#read_link_info/2">read_link_info/2</seemfa>
on how to get information on links instead.
</p>
</desc>
@@ -410,7 +410,7 @@
<p>Returns a <c><![CDATA[file_info]]></c> record from the symbolic
link specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>.
See
- <seealso marker="kernel:file#read_link_info-2">file:read_link_info/2</seealso>
+ <seemfa marker="kernel:file#read_link_info/2">file:read_link_info/2</seemfa>
for information about the record.
</p>
</desc>
@@ -441,15 +441,15 @@
<fsummary>Starts an SFTP client.</fsummary>
<type>
- <v>Host = <seealso marker="ssh:ssh#type-host">ssh:host()</seealso></v>
- <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v>
- <v>TcpSocket = <seealso marker="ssh:ssh#type-open_socket">ssh:open_socket()</seealso></v>
- <v>Options = [ <seealso marker="#type-sftp_option">sftp_option()</seealso>
- | <seealso marker="ssh:ssh#type-client_option">ssh:client_option()</seealso> ]</v>
- <v>SftpOptions = [ <seealso marker="#type-sftp_option">sftp_option()</seealso> ]</v>
+ <v>Host = <seetype marker="ssh:ssh#host">ssh:host()</seetype></v>
+ <v>Port = <seetype marker="kernel:inet#port_number">inet:port_number()</seetype></v>
+ <v>TcpSocket = <seetype marker="ssh:ssh#open_socket">ssh:open_socket()</seetype></v>
+ <v>Options = [ <seetype marker="#sftp_option">sftp_option()</seetype>
+ | <seetype marker="ssh:ssh#client_option">ssh:client_option()</seetype> ]</v>
+ <v>SftpOptions = [ <seetype marker="#sftp_option">sftp_option()</seetype> ]</v>
<v>ChannelPid = pid()</v>
- <v>ConnectionRef = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v>
- <v>Error = {error, <seealso marker="#type-reason">reason()</seealso>}</v>
+ <v>ConnectionRef = <seetype marker="ssh:ssh#connection_ref">ssh:connection_ref()</seetype></v>
+ <v>Error = {error, <seetype marker="#reason">reason()</seetype>}</v>
</type>
<desc>
<p>If no connection reference is provided, a connection is set
@@ -484,7 +484,7 @@
</item>
</taglist>
<p>All other options are directly passed to
- <seealso marker="ssh">ssh:connect/3</seealso> or ignored if a
+ <seeerl marker="ssh">ssh:connect/3</seeerl> or ignored if a
connection is already provided.</p>
</desc>
</func>
@@ -494,7 +494,7 @@
<fsummary>Stops the SFTP client channel.</fsummary>
<desc>
<p>Stops an SFTP channel. Does not close the SSH connection.
- Use <seealso marker="ssh#close-1">ssh:close/1</seealso> to close it.</p>
+ Use <seemfa marker="ssh#close/1">ssh:close/1</seemfa> to close it.</p>
</desc>
</func>
@@ -527,7 +527,7 @@
<desc>
<p>Writes file information from a <c><![CDATA[file_info]]></c> record to the
file specified by <c><![CDATA[Name]]></c>. See
- <seealso marker="kernel:file#write_file_info-2">file:write_file_info/[2,3]</seealso>
+ <seemfa marker="kernel:file#write_file_info/2">file:write_file_info/[2,3]</seemfa>
for information about the record.
</p>
</desc>
diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml
index 3c24100bce..49a23f4333 100644
--- a/lib/ssh/doc/src/ssh_sftpd.xml
+++ b/lib/ssh/doc/src/ssh_sftpd.xml
@@ -43,7 +43,7 @@
<p>Is to be used together with <c>ssh:daemon/[1,2,3]</c></p>
<p>The <c>Name</c> is <c>"sftp"</c> and
<c>CbMod</c> is the name of the Erlang module implementing the subsystem using the
- <seealso marker="ssh_server_channel">ssh_server_channel</seealso> (replaces ssh_daemon_channel) behaviour.
+ <seeerl marker="ssh_server_channel">ssh_server_channel</seeerl> (replaces ssh_daemon_channel) behaviour.
</p>
<p>Options:</p>
<taglist>
@@ -55,7 +55,7 @@
<item>
<p>Determines which module to call for accessing
the file server. The default value is <c>ssh_sftpd_file</c>, which uses the
- <seealso marker="kernel:file">file</seealso> and <seealso marker="stdlib:filelib">filelib</seealso>
+ <seeerl marker="kernel:file">file</seeerl> and <seeerl marker="stdlib:filelib">filelib</seeerl>
APIs to access the standard OTP file server. This option can be used to plug in
other file servers.</p>
</item>
diff --git a/lib/ssh/doc/src/terminology.xml b/lib/ssh/doc/src/terminology.xml
index db1e08970d..067eaa88d1 100644
--- a/lib/ssh/doc/src/terminology.xml
+++ b/lib/ssh/doc/src/terminology.xml
@@ -119,15 +119,15 @@
</p>
<list type="ordered">
<item>If a
- <seealso marker="ssh:ssh#option-pwdfun"><c>pwdfun</c></seealso>
+ <seeerl marker="ssh:ssh#option-pwdfun"><c>pwdfun</c></seeerl>
is defined, that one is called and the returned boolean is the authentication result.
</item>
<item>Else, if the
- <seealso marker="ssh:ssh#option-user_passwords"><c>user_passwords</c></seealso>
+ <seeerl marker="ssh:ssh#option-user_passwords"><c>user_passwords</c></seeerl>
option is defined and the username and the password matches, the authentication is a success.
</item>
<item>Else, if the option
- <seealso marker="ssh:ssh#option-password"><c>password</c></seealso>
+ <seeerl marker="ssh:ssh#option-password"><c>password</c></seeerl>
is defined and matches the password the authentication is a success.
Note that the use of this option is not recommended in non-test code.
</item>
@@ -140,18 +140,18 @@
</p>
<list type="ordered">
<item>A callback module is selected using the options
- <seealso marker="ssh:ssh#type-key_cb_common_option"><c>key_cb</c></seealso>.
+ <seetype marker="ssh:ssh#key_cb_common_option"><c>key_cb</c></seetype>.
</item>
<item>The callback module is used to check that the provided public key is one of the user's pre-stored.
In case of the default callback module, the files <c>authorized_keys</c> and <c>authorized_keys2</c>
are searched in a directory found in the following order:
<list>
<item>If the option
- <seealso marker="ssh:ssh_file#type-user_dir_fun_common_option"><c>user_dir_fun</c></seealso>
+ <seetype marker="ssh:ssh_file#user_dir_fun_common_option"><c>user_dir_fun</c></seetype>
is defined, that fun is called and the returned directory is used,
</item>
<item>Else, If the option
- <seealso marker="ssh:ssh_file#type-user_dir_common_option"><c>user_dir</c></seealso>
+ <seetype marker="ssh:ssh_file#user_dir_common_option"><c>user_dir</c></seetype>
is defined, that directory is used,
</item>
<item>Else the subdirectory <c>.ssh</c> in the home directory of the user executing
diff --git a/lib/ssh/doc/src/usersguide.xml b/lib/ssh/doc/src/usersguide.xml
index 8a4df208d8..aabc12a750 100644
--- a/lib/ssh/doc/src/usersguide.xml
+++ b/lib/ssh/doc/src/usersguide.xml
@@ -37,5 +37,7 @@
<xi:include href="introduction.xml"/>
<xi:include href="using_ssh.xml"/>
<xi:include href="terminology.xml"/>
+ <xi:include href="configurations.xml"/>
<xi:include href="configure_algos.xml"/>
+ <xi:include href="hardening.xml"/>
</part>
diff --git a/lib/ssh/doc/src/using_ssh.xml b/lib/ssh/doc/src/using_ssh.xml
index 17468917fb..a127b66607 100644
--- a/lib/ssh/doc/src/using_ssh.xml
+++ b/lib/ssh/doc/src/using_ssh.xml
@@ -35,7 +35,7 @@
<section>
<title>General Information</title>
<p>The following examples use the utility function
- <seealso marker="ssh#start-0"> ssh:start/0</seealso> to start
+ <seemfa marker="ssh#start/0"> ssh:start/0</seemfa> to start
all needed applications (<c>crypto</c>, <c>public_key</c>, and <c>ssh</c>).
All examples are run in an Erlang shell, or in a bash shell, using <em>openssh</em>
to illustrate how the <c>ssh</c> application can be used. The
@@ -74,15 +74,15 @@ logout
<title>Running an Erlang ssh Daemon</title>
<p>The
- <seealso marker="ssh_file#type-system_dir_daemon_option"><c>system_dir</c></seealso>
+ <seetype marker="ssh_file#system_dir_daemon_option"><c>system_dir</c></seetype>
option must be a directory containing a host key file and it defaults to <c>/etc/ssh</c>.
- For details, see Section Configuration Files in <seealso marker="SSH_app">ssh(6)</seealso>.
+ For details, see Section Configuration Files in <seeapp marker="SSH_app">ssh(6)</seeapp>.
</p>
<note><p>Normally, the <c>/etc/ssh</c> directory is only readable by root.</p>
</note>
- <p>The option <seealso marker="ssh_file#type-user_dir_common_option"><c>user_dir</c></seealso>
+ <p>The option <seetype marker="ssh_file#user_dir_common_option"><c>user_dir</c></seetype>
defaults to directory <c>users ~/.ssh</c>.</p>
<p><em>Step 1.</em> To run the example without root privileges,
@@ -172,8 +172,8 @@ ok
{channels,[]}
7></pre>
- <p>See <seealso marker="ssh_connection#description">ssh_connection</seealso> and
- <seealso marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso>
+ <p>See <seeerl marker="ssh_connection#description">ssh_connection</seeerl> and
+ <seemfa marker="ssh_connection#exec/4">ssh_connection:exec/4</seemfa>
for finding documentation of the channel messages.</p>
<p>To collect the channel messages in a program, use <c>receive...end</c> instead of <c>flush/1</c>:</p>
@@ -198,9 +198,9 @@ ok
{ok,1}
8></pre>
- <p>To close the connection, call the function <seealso marker="ssh#close-1"><c>ssh:close(ConnectionRef)</c></seealso>.
+ <p>To close the connection, call the function <seemfa marker="ssh#close/1"><c>ssh:close(ConnectionRef)</c></seemfa>.
As an alternative, set the option
- <seealso marker="ssh#type-max_idle_time_common_option"><c>{idle_time, 1}</c></seealso>
+ <seetype marker="ssh#max_idle_time_common_option"><c>{idle_time, 1}</c></seetype>
when opening the connection. This will cause the connection to be closed automaticaly when there are
no channels open for the specified time period, in this case 1 ms.
</p>
@@ -210,14 +210,14 @@ ok
<title>OS standard client and Erlang daemon (server)</title>
<p>An Erlang SSH daemon could be called for one-time execution of a "command". The "command" must be
as if entered into the erlang shell, that is a sequence of Erlang
- <seealso marker="doc/reference_manual:expressions">expressions</seealso> ended by a period (.).
+ <seeguide marker="system/reference_manual:expressions">expressions</seeguide> ended by a period (.).
Variables bound in that sequence will keep their bindings throughout the expression sequence.
The bindings are disposed when the result is returned.</p>
<p>Here is an example of a suitable expression sequence:</p>
<pre>
A=1, B=2, 3 == (A + B).</pre>
<p>It evaluates to <c>true</c> if submitted to the Erlang daemon started in
- <seealso marker="#start-daemon-step3">Step 3</seealso> above:</p>
+ <seeguide marker="#start-daemon-step3">Step 3</seeguide> above:</p>
<pre>
$bash> <input>ssh tarlop -p 8989 "A=1, B=2, 3 == (A + B)."</input>
true
@@ -260,7 +260,7 @@ ok
$bash></pre>
<p>And similar for reading from stdin. As an example we use
- <seealso marker="stdlib:io#read-1">io:read/1</seealso> which
+ <seemfa marker="stdlib:io#read/1">io:read/1</seemfa> which
displays the argument as a prompt on stdout, reads a term from stdin and returns it
in an ok-tuple:</p>
<pre>
@@ -307,20 +307,20 @@ ok
<section>
<title>Configuring the server's (daemon's) command execution</title>
- <p>Every time a daemon <seealso marker="#running-an-erlang-ssh-daemon">is started</seealso>, it enables
+ <p>Every time a daemon <seeguide marker="#running-an-erlang-ssh-daemon">is started</seeguide>, it enables
one-time execution of commands as described in the
- <seealso marker="#simple-client-example">previous section</seealso> unless explicitly disabled.
+ <seeguide marker="#simple-client-example">previous section</seeguide> unless explicitly disabled.
</p>
<p>There is often a need to configure some other exec evaluator to tailor the input language or
restrict the possible functions to call. There are two ways of doing this which will be shown with examples
- below. See <seealso marker="ssh#daemon-2">ssh:daemon/2,3</seealso> and
- <seealso marker="ssh#type-exec_daemon_option">exec_daemon_option()</seealso>) for details.</p>
+ below. See <seemfa marker="ssh#daemon/2">ssh:daemon/2,3</seemfa> and
+ <seetype marker="ssh#exec_daemon_option">exec_daemon_option()</seetype>) for details.</p>
<p>Examples of the two ways to configure the exec evaluator:</p>
<list type="ordered">
<item>Disable one-time execution.<br/>
To modify the daemon start example above to reject one-time execution requests,
- we change <seealso marker="#start-daemon-step3">Step 3</seealso> by adding the option
+ we change <seeguide marker="#start-daemon-step3">Step 3</seeguide> by adding the option
<c>{exec, disabled}</c>
to:
<pre>
@@ -397,7 +397,7 @@ $bash> </pre>
{ok,&lt;0.92.0>}
3> <input>{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).</input>
{ok,0}
-4> <input>success = ssh_connection:exec(ConnectionRef, ChannelId, "1+ 2."</input>
+4> <input>success = ssh_connection:exec(ConnectionRef, ChannelId, "1+ 2.").</input>
success
5> <input>flush().</input>
Shell got {ssh_cm,&lt;0.106.0>,{data,0,1,&lt;&lt;"**Error** {bad_input,\"1+ 2.\"}">>}}
@@ -408,7 +408,7 @@ ok
6></pre>
<p>The <c>fun()</c> in the exec option could take up to three arguments
(<c>Cmd</c>, <c>User</c> and <c>ClientAddress</c>). See
- the <seealso marker="ssh#type-exec_daemon_option">exec_daemon_option()</seealso> for the details.</p>
+ the <seetype marker="ssh#exec_daemon_option">exec_daemon_option()</seetype> for the details.</p>
</item>
</list>
@@ -486,7 +486,7 @@ ok = erl_tar:close(HandleRead),
<section>
<title>Example with encryption</title>
- <p>The previous <seealso marker="using_ssh#basic-example">Basic example</seealso>
+ <p>The previous <seeguide marker="using_ssh#basic-example">Basic example</seeguide>
can be extended with encryption and decryption as follows:</p>
<code type="erl">
%% First three parameters depending on which crypto type we select:
@@ -597,8 +597,8 @@ terminate(_Reason, _State) ->
</code>
<p>The subsystem can be run on the host <em>tarlop</em> with the generated keys,
- as described in Section <seealso marker="#Running an Erlang ssh Daemon">
- Running an Erlang ssh Daemon</seealso>:</p>
+ as described in Section <seeguide marker="#Running an Erlang ssh Daemon">
+ Running an Erlang ssh Daemon</seeguide>:</p>
<pre>
1> <input>ssh:start().</input>
@@ -624,7 +624,7 @@ ok
{ssh_msg, &lt;0.57.0>, {closed, 0}}
7> <input>{error, closed} = ssh_connection:send(ConnectionRef, ChannelId, "10", infinity).</input> </pre>
-<p>See also <seealso marker="ssh_client_channel">ssh_client_channel(3)</seealso> (replaces ssh_channel(3)).</p>
+<p>See also <seeerl marker="ssh_client_channel">ssh_client_channel(3)</seeerl> (replaces ssh_channel(3)).</p>
</section>
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
index 2b96020678..ab6137e518 100644
--- a/lib/ssh/src/Makefile
+++ b/lib/ssh/src/Makefile
@@ -58,20 +58,22 @@ MODULES= \
ssh \
ssh_acceptor \
ssh_acceptor_sup \
+ ssh_agent \
ssh_app \
ssh_auth\
ssh_bits \
+ ssh_channel_sup \
ssh_cli \
ssh_connection \
ssh_connection_handler \
ssh_connection_sup \
+ ssh_controller \
ssh_file \
ssh_info \
ssh_io \
ssh_message \
ssh_no_io \
ssh_options \
- ssh_server_channel_sup \
ssh_sftp \
ssh_sftpd \
ssh_sftpd_file\
@@ -79,6 +81,10 @@ MODULES= \
ssh_subsystem_sup \
ssh_sup \
ssh_system_sup \
+ ssh_tcpip_forward_srv \
+ ssh_tcpip_forward_client \
+ ssh_tcpip_forward_acceptor_sup \
+ ssh_tcpip_forward_acceptor \
ssh_transport \
ssh_xfer \
sshc_sup \
@@ -107,7 +113,7 @@ APP_TARGET= $(EBIN)/$(APP_FILE)
APPUP_SRC= $(APPUP_FILE).src
APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
-INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_xfer.hrl
+INTERNAL_HRL_FILES = ssh_agent.hrl ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_xfer.hrl
# ----------------------------------------------------
# FLAGS
@@ -180,9 +186,10 @@ $(EBIN)/ssh_connection_handler.$(EMULATOR): ssh_connection_handler.erl ssh.hrl \
$(EBIN)/ssh_shell.$(EMULATOR): ssh_shell.erl ssh_connect.hrl
$(EBIN)/ssh_system_sup.$(EMULATOR): ssh_system_sup.erl ssh.hrl
$(EBIN)/ssh_subsystem_sup.$(EMULATOR): ssh_subsystem_sup.erl
-$(EBIN)/ssh_server_channel_sup.$(EMULATOR): ssh_server_channel_sup.erl
+$(EBIN)/ssh_channel_sup.$(EMULATOR): ssh_channel_sup.erl ssh.hrl
$(EBIN)/ssh_acceptor_sup.$(EMULATOR): ssh_acceptor_sup.erl ssh.hrl
$(EBIN)/ssh_acceptor.$(EMULATOR): ssh_acceptor.erl ssh.hrl
+$(EBIN)/ssh_agent.$(EMULATOR): ssh_agent.erl ssh.hrl ssh_agent.hrl
$(EBIN)/ssh_app.$(EMULATOR): ssh_app.erl
$(EBIN)/ssh_auth.$(EMULATOR): ssh_auth.erl \
../../public_key/include/public_key.hrl \
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
index 21e3604400..6974290ac7 100644
--- a/lib/ssh/src/ssh.app.src
+++ b/lib/ssh/src/ssh.app.src
@@ -8,9 +8,11 @@
ssh_acceptor,
ssh_acceptor_sup,
ssh_options,
+ ssh_agent,
ssh_auth,
ssh_message,
ssh_bits,
+ ssh_channel_sup,
ssh_cli,
ssh_client_channel,
ssh_client_key_api,
@@ -18,6 +20,7 @@
ssh_connection,
ssh_connection_handler,
ssh_connection_sup,
+ ssh_controller,
ssh_daemon_channel,
ssh_dbg,
ssh_shell,
@@ -28,7 +31,6 @@
ssh_info,
ssh_no_io,
ssh_server_channel,
- ssh_server_channel_sup,
ssh_server_key_api,
ssh_sftp,
ssh_sftpd,
@@ -36,6 +38,10 @@
ssh_sftpd_file_api,
ssh_subsystem_sup,
ssh_sup,
+ ssh_tcpip_forward_client,
+ ssh_tcpip_forward_srv,
+ ssh_tcpip_forward_acceptor_sup,
+ ssh_tcpip_forward_acceptor,
ssh_system_sup,
ssh_transport,
ssh_xfer]},
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 1f06c33b90..d4b492d61c 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -40,7 +40,9 @@
chk_algos_opts/1,
stop_listener/1, stop_listener/2, stop_listener/3,
stop_daemon/1, stop_daemon/2, stop_daemon/3,
- shell/1, shell/2, shell/3
+ shell/1, shell/2, shell/3,
+ tcpip_tunnel_from_server/5, tcpip_tunnel_from_server/6,
+ tcpip_tunnel_to_server/5, tcpip_tunnel_to_server/6
]).
%%% "Deprecated" types export:
@@ -93,6 +95,10 @@ start() ->
start(Type) ->
case application:ensure_all_started(ssh, Type) of
{ok, _} ->
+ %% Clear cached default_algorithms (if exists) ...
+ ssh_transport:clear_default_algorithms_env(),
+ %% ... and rebuld them taking configure options in account
+ ssh_transport:default_algorithms(),
ok;
Other ->
Other
@@ -129,15 +135,15 @@ connect(Socket, UserOptions, NegotiationTimeout) when is_port(Socket),
{error, Error} ->
{error, Error};
Options ->
- case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
- ok ->
- {ok, {Host,_Port}} = inet:peername(Socket),
- Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options),
- ssh_connection_handler:start_connection(client, Socket, Opts, NegotiationTimeout);
- {error,SockError} ->
- {error,SockError}
- end
- end;
+ case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
+ ok ->
+ connect_socket(Socket,
+ ?PUT_INTERNAL_OPT({connected_socket,Socket}, Options),
+ NegotiationTimeout);
+ {error,SockError} ->
+ {error,SockError}
+ end
+ end;
connect(Host, Port, Options) when is_integer(Port),
Port>0,
@@ -151,9 +157,9 @@ connect(Host, Port, Options) when is_integer(Port),
Options :: client_options(),
NegotiationTimeout :: timeout().
-connect(Host0, Port, UserOptions, Timeout) when is_integer(Port),
- Port>0,
- is_list(UserOptions) ->
+connect(Host0, Port, UserOptions, NegotiationTimeout) when is_integer(Port),
+ Port>0,
+ is_list(UserOptions) ->
case ssh_options:handle_options(client, UserOptions) of
{error, _Reason} = Error ->
Error;
@@ -164,8 +170,9 @@ connect(Host0, Port, UserOptions, Timeout) when is_integer(Port),
Host = mangle_connect_address(Host0, SocketOpts),
try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of
{ok, Socket} ->
- Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options),
- ssh_connection_handler:start_connection(client, Socket, Opts, Timeout);
+ connect_socket(Socket,
+ ?PUT_INTERNAL_OPT({host,Host}, Options),
+ NegotiationTimeout);
{error, Reason} ->
{error, Reason}
catch
@@ -176,6 +183,22 @@ connect(Host0, Port, UserOptions, Timeout) when is_integer(Port),
end
end.
+
+connect_socket(Socket, Options0, NegotiationTimeout) ->
+ {ok, {Host,Port}} = inet:sockname(Socket),
+ Profile = ?GET_OPT(profile, Options0),
+
+ {ok, {SystemSup, SubSysSup}} = sshc_sup:start_system_subsystem(Host, Port, Profile, Options0),
+
+ ConnectionSup = ssh_system_sup:connection_supervisor(SystemSup),
+ Opts = ?PUT_INTERNAL_OPT([{user_pid,self()},
+ {supervisors, [{system_sup, SystemSup},
+ {subsystem_sup, SubSysSup},
+ {connection_sup, ConnectionSup}]}
+ ], Options0),
+ ssh_connection_handler:start_connection(client, Socket, Opts, NegotiationTimeout).
+
+
%%--------------------------------------------------------------------
-spec close(ConnectionRef) -> ok | {error,term()} when
ConnectionRef :: connection_ref() .
@@ -452,7 +475,7 @@ stop_listener(Address, Port, Profile) ->
-spec stop_daemon(DaemonRef::daemon_ref()) -> ok.
stop_daemon(SysSup) ->
- ssh_system_sup:stop_system(SysSup).
+ ssh_system_sup:stop_system(server, SysSup).
-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok.
@@ -465,11 +488,11 @@ stop_daemon(Address, Port) ->
stop_daemon(any, Port, Profile) ->
map_ip(fun(IP) ->
- ssh_system_sup:stop_system(IP, Port, Profile)
+ ssh_system_sup:stop_system(server, IP, Port, Profile)
end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]);
stop_daemon(Address, Port, Profile) ->
map_ip(fun(IP) ->
- ssh_system_sup:stop_system(IP, Port, Profile)
+ ssh_system_sup:stop_system(server, IP, Port, Profile)
end, {address,Address}).
%%--------------------------------------------------------------------
@@ -486,7 +509,11 @@ shell(Socket) when is_port(Socket) ->
shell(ConnectionRef) when is_pid(ConnectionRef) ->
case ssh_connection:session_channel(ConnectionRef, infinity) of
{ok,ChannelId} ->
- success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []),
+ success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId,
+ [{pty_opts, [{echo,0}]}
+ ]),
+ success = ssh_connection:send_environment_vars(ConnectionRef, ChannelId,
+ ["LANG", "LC_ALL"]),
Args = [{channel_cb, ssh_shell},
{init_args,[ConnectionRef, ChannelId]},
{cm, ConnectionRef}, {channel_id, ChannelId}],
@@ -581,6 +608,113 @@ get_sock_opts(ConnectionRef, SocketGetOptions) ->
ssh_connection_handler:get_sock_opts(ConnectionRef, SocketGetOptions).
%%--------------------------------------------------------------------
+%% Ask local client to listen to ListenHost:ListenPort. When someone
+%% connects that address, connect to ConnectToHost:ConnectToPort from
+%% the server.
+%%--------------------------------------------------------------------
+-spec tcpip_tunnel_to_server(ConnectionRef,
+ ListenHost, ListenPort,
+ ConnectToHost, ConnectToPort
+ ) ->
+ {ok,TrueListenPort} | {error, term()} when
+ ConnectionRef :: connection_ref(),
+ ListenHost :: host(),
+ ListenPort :: inet:port_number(),
+ ConnectToHost :: host(),
+ ConnectToPort :: inet:port_number(),
+ TrueListenPort :: inet:port_number().
+
+tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort) ->
+ tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort, infinity).
+
+
+-spec tcpip_tunnel_to_server(ConnectionRef,
+ ListenHost, ListenPort,
+ ConnectToHost, ConnectToPort,
+ Timeout) ->
+ {ok,TrueListenPort} | {error, term()} when
+ ConnectionRef :: connection_ref(),
+ ListenHost :: host(),
+ ListenPort :: inet:port_number(),
+ ConnectToHost :: host(),
+ ConnectToPort :: inet:port_number(),
+ Timeout :: timeout(),
+ TrueListenPort :: inet:port_number().
+
+tcpip_tunnel_to_server(ConnectionHandler, ListenHost, ListenPort, ConnectToHost0, ConnectToPort, Timeout) ->
+ SockOpts = [],
+ try
+ list_to_binary(
+ case mangle_connect_address(ConnectToHost0,SockOpts) of
+ IP when is_tuple(IP) -> inet_parse:ntoa(IP);
+ _ when is_list(ConnectToHost0) -> ConnectToHost0
+ end)
+ of
+ ConnectToHost ->
+ ssh_connection_handler:handle_direct_tcpip(ConnectionHandler,
+ mangle_tunnel_address(ListenHost), ListenPort,
+ ConnectToHost, ConnectToPort,
+ Timeout)
+ catch
+ _:_ ->
+ {error, bad_connect_to_address}
+ end.
+
+%%--------------------------------------------------------------------
+%% Ask remote server to listen to ListenHost:ListenPort. When someone
+%% connects that address, connect to ConnectToHost:ConnectToPort from
+%% the client.
+%%--------------------------------------------------------------------
+-spec tcpip_tunnel_from_server(ConnectionRef,
+ ListenHost, ListenPort,
+ ConnectToHost, ConnectToPort
+ ) ->
+ {ok,TrueListenPort} | {error, term()} when
+ ConnectionRef :: connection_ref(),
+ ListenHost :: host(),
+ ListenPort :: inet:port_number(),
+ ConnectToHost :: host(),
+ ConnectToPort :: inet:port_number(),
+ TrueListenPort :: inet:port_number().
+
+tcpip_tunnel_from_server(ConnectionRef, ListenHost, ListenPort, ConnectToHost, ConnectToPort) ->
+ tcpip_tunnel_from_server(ConnectionRef, ListenHost, ListenPort, ConnectToHost, ConnectToPort, infinity).
+
+-spec tcpip_tunnel_from_server(ConnectionRef,
+ ListenHost, ListenPort,
+ ConnectToHost, ConnectToPort,
+ Timeout) ->
+ {ok,TrueListenPort} | {error, term()} when
+ ConnectionRef :: connection_ref(),
+ ListenHost :: host(),
+ ListenPort :: inet:port_number(),
+ ConnectToHost :: host(),
+ ConnectToPort :: inet:port_number(),
+ Timeout :: timeout(),
+ TrueListenPort :: inet:port_number().
+
+tcpip_tunnel_from_server(ConnectionRef, ListenHost0, ListenPort, ConnectToHost0, ConnectToPort, Timeout) ->
+ SockOpts = [],
+ ListenHost = mangle_tunnel_address(ListenHost0),
+ ConnectToHost = mangle_connect_address(ConnectToHost0, SockOpts),
+ case ssh_connection_handler:global_request(ConnectionRef, "tcpip-forward", true,
+ {ListenHost,ListenPort,ConnectToHost,ConnectToPort},
+ Timeout) of
+ {success,<<>>} ->
+ {ok, ListenPort};
+ {success,<<TruePort:32/unsigned-integer>>} when ListenPort==0 ->
+ {ok, TruePort};
+ {success,_} = Res ->
+ {error, {bad_result,Res}};
+ {failure,<<>>} ->
+ {error,not_accepted};
+ {failure,Error} ->
+ {error,Error};
+ Other ->
+ Other
+ end.
+
+%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
%% The handle_daemon_args/2 function basically only sets the ip-option in Opts
@@ -697,3 +831,16 @@ mangle_connect_address1(A, _) ->
{ok, {0,0,0,0,0,0,0,0}} -> loopback(true);
_ -> A
end.
+
+%%%----------------------------------------------------------------
+mangle_tunnel_address(any) -> <<"">>;
+mangle_tunnel_address(loopback) -> <<"localhost">>;
+mangle_tunnel_address({0,0,0,0}) -> <<"">>;
+mangle_tunnel_address({0,0,0,0,0,0,0,0}) -> <<"">>;
+mangle_tunnel_address(IP) when is_tuple(IP) -> list_to_binary(inet_parse:ntoa(IP));
+mangle_tunnel_address(A) when is_atom(A) -> mangle_tunnel_address(atom_to_list(A));
+mangle_tunnel_address(X) when is_list(X) -> case catch inet:parse_address(X) of
+ {ok, {0,0,0,0}} -> <<"">>;
+ {ok, {0,0,0,0,0,0,0,0}} -> <<"">>;
+ _ -> list_to_binary(X)
+ end.
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index b5b08962ed..33f6833830 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -51,6 +51,7 @@
-define(STRING(X), ?UINT32((size(X))), (X)/binary).
-define(DEC_BIN(X,Len), ?UINT32(Len), X:Len/binary ).
+-define(DEC_INT(I,Len), ?UINT32(Len), I:Len/big-signed-integer-unit:8 ).
-define(DEC_MPINT(I,Len), ?UINT32(Len), I:Len/big-signed-integer-unit:8 ).
%% building macros
@@ -176,9 +177,12 @@
-type mac_alg() :: 'AEAD_AES_128_GCM' |
'AEAD_AES_256_GCM' |
'hmac-sha1' |
+ 'hmac-sha1-etm@openssh.com' |
'hmac-sha1-96' |
'hmac-sha2-256' |
- 'hmac-sha2-512'
+ 'hmac-sha2-512' |
+ 'hmac-sha2-256-etm@openssh.com' |
+ 'hmac-sha2-512-etm@openssh.com'
.
-type compression_alg() :: 'none' |
@@ -287,7 +291,9 @@
-type fp_digest_alg() :: 'md5' | crypto:sha1() | crypto:sha2() .
--type accept_callback() :: fun((PeerName::string(), fingerprint() ) -> boolean()) .
+-type accept_callback() :: fun((PeerName::string(), fingerprint() ) -> boolean()) % Old style
+ | fun((PeerName::string(), Port::inet:port_number(), fingerprint() ) -> boolean()) % New style
+ .
-type fingerprint() :: string() | [string()].
-type authentication_client_options() ::
@@ -308,9 +314,12 @@
| shell_daemon_option()
| exec_daemon_option()
| ssh_cli_daemon_option()
+ | tcpip_tunnel_out_daemon_option()
+ | tcpip_tunnel_in_daemon_option()
| authentication_daemon_options()
| diffie_hellman_group_exchange_daemon_option()
| negotiation_timeout_daemon_option()
+ | hello_timeout_daemon_option()
| hardening_daemon_options()
| callbacks_daemon_options()
| send_ext_info_daemon_option()
@@ -338,26 +347,32 @@
-type ssh_cli_daemon_option() :: {ssh_cli, mod_args() | no_cli }.
+-type tcpip_tunnel_out_daemon_option() :: {tcpip_tunnel_out, boolean()} .
+-type tcpip_tunnel_in_daemon_option() :: {tcpip_tunnel_in, boolean()} .
+
-type send_ext_info_daemon_option() :: {send_ext_info, boolean()} .
-type authentication_daemon_options() ::
ssh_file:system_dir_daemon_option()
| {auth_method_kb_interactive_data, prompt_texts() }
| {user_passwords, [{UserName::string(),Pwd::string()}]}
+ | {pk_check_user, boolean()}
| {password, string()}
| {pwdfun, pwdfun_2() | pwdfun_4()} .
-type prompt_texts() ::
kb_int_tuple()
| kb_int_fun_3()
+ | kb_int_fun_4()
.
-type kb_int_fun_3() :: fun((Peer::ip_port(), User::string(), Service::string()) -> kb_int_tuple()).
+-type kb_int_fun_4() :: fun((Peer::ip_port(), User::string(), Service::string(), State::any()) -> kb_int_tuple()).
-type kb_int_tuple() :: {Name::string(), Instruction::string(), Prompt::string(), Echo::boolean()}.
--type pwdfun_2() :: fun((User::string(), Password::string()) -> boolean()) .
+-type pwdfun_2() :: fun((User::string(), Password::string()|pubkey) -> boolean()) .
-type pwdfun_4() :: fun((User::string(),
- Password::string(),
+ Password::string()|pubkey,
PeerAddress::ip_port(),
State::any()) ->
boolean() | disconnect | {boolean(),NewState::any()}
@@ -372,6 +387,7 @@
-type ssh_moduli_file() :: {ssh_moduli_file,string()}.
-type negotiation_timeout_daemon_option() :: {negotiation_timeout, timeout()} .
+-type hello_timeout_daemon_option() :: {hello_timeout, timeout()} .
-type hardening_daemon_options() ::
{max_sessions, pos_integer()}
@@ -488,7 +504,8 @@
recv_ext_info
}).
--record(ssh_pty, {term = "", % e.g. "xterm"
+-record(ssh_pty, {c_version = "", % client version string, e.g "SSH-2.0-Erlang/4.10.5"
+ term = "", % e.g. "xterm"
width = 80,
height = 25,
pixel_width = 1024,
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index 15e59dd1fe..7874f93fa2 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -46,12 +46,56 @@ start_link(Port, Address, Options, AcceptTimeout) ->
proc_lib:start_link(?MODULE, acceptor_init, Args).
%%%----------------------------------------------------------------
-number_of_connections(SystemSup) ->
- length([X ||
- {R,X,supervisor,[ssh_subsystem_sup]} <- supervisor:which_children(SystemSup),
- is_pid(X),
- is_reference(R)
- ]).
+number_of_connections(SysSup) ->
+ length([S || S <- supervisor:which_children(SysSup),
+ has_worker(SysSup,S)]).
+
+
+has_worker(SysSup, {R,SubSysSup,supervisor,[ssh_subsystem_sup]}) when is_reference(R),
+ is_pid(SubSysSup) ->
+ try
+ {{server, ssh_connection_sup, _, _}, Pid, supervisor, [ssh_connection_sup]} =
+ lists:keyfind([ssh_connection_sup], 4, supervisor:which_children(SubSysSup)),
+ {Pid, supervisor:which_children(Pid)}
+ of
+ {ConnSup,[]} ->
+ %% Strange. Since the connection supervisor exists, there should have been
+ %% a connection here.
+ %% It might be that the connection_handler worker has "just died", maybe
+ %% due to a exit(_,kill). It might also be so that the worker is starting.
+ %% Spawn a killer that redo the test and kills it if the problem persists.
+ %% TODO: Fix this better in the supervisor tree....
+ spawn(fun() ->
+ timer:sleep(10),
+ try supervisor:which_children(ConnSup)
+ of
+ [] ->
+ %% we are on the server-side:
+ ssh_system_sup:stop_subsystem(SysSup, SubSysSup);
+ [_] ->
+ %% is ok now
+ ok;
+ _ ->
+ %% What??
+ error
+ catch _:_ ->
+ %% What??
+ error
+ end
+ end),
+ false;
+ {_ConnSup,[_]}->
+ true;
+ _ ->
+ %% What??
+ false
+ catch _:_ ->
+ %% What??
+ false
+ end;
+
+has_worker(_,_) ->
+ false.
%%%----------------------------------------------------------------
listen(Port, Options) ->
@@ -217,6 +261,11 @@ ssh_dbg_format(connections, {call, {?MODULE,acceptor_init,
[_Parent, Port, Address, _Opts, _AcceptTimeout]}}) ->
[io_lib:format("Starting LISTENER on ~s:~p\n", [ntoa(Address),Port])
];
+ssh_dbg_format(connections, {return_from, {?MODULE,acceptor_init,5}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(connections, {call, {?MODULE,handle_connection,[_,_,_,_,_]}}) ->
+ skip;
ssh_dbg_format(connections, {return_from, {?MODULE,handle_connection,5}, {error,Error}}) ->
["Starting connection to server failed:\n",
io_lib:format("Error = ~p", [Error])
diff --git a/lib/ssh/src/ssh_agent.erl b/lib/ssh/src/ssh_agent.erl
new file mode 100644
index 0000000000..1c89de2bd1
--- /dev/null
+++ b/lib/ssh/src/ssh_agent.erl
@@ -0,0 +1,210 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% Reference: https://tools.ietf.org/html/draft-miller-ssh-agent-02
+
+-module(ssh_agent).
+
+-behaviour(ssh_client_key_api).
+
+-include("ssh.hrl").
+-include("ssh_agent.hrl").
+
+-export([send/2]).
+-export([add_host_key/3, add_host_key/4, is_host_key/4, is_host_key/5, user_key/2, sign/3]).
+
+-type socket_path_option() :: {socket_path, string()}.
+-type timeout_option() :: {timeout, integer()}.
+-type call_ssh_file_option() :: {call_ssh_file, atom()}.
+
+%% ssh_client_key_api implementation
+
+%% Old (compatibility) version
+-spec add_host_key(string(),
+ public_key:public_key(),
+ Options
+ ) ->
+ ok | {error, Error :: term()} when
+ Options :: ssh_client_key_api:client_key_cb_options(call_ssh_file_option()).
+
+add_host_key(Host, PublicKey, Options) ->
+ KeyCbOpts = proplists:get_value(key_cb_private, Options, []),
+ SshFileCb = proplists:get_value(call_ssh_file, KeyCbOpts, ssh_file),
+ SshFileCb:add_host_key(Host, PublicKey, Options).
+
+
+-spec is_host_key(Key :: public_key:public_key(),
+ Host :: string(),
+ Algorithm :: ssh:pubkey_alg(),
+ Options
+ ) ->
+ boolean() when
+ Options :: ssh_client_key_api:client_key_cb_options(call_ssh_file_option()) .
+
+is_host_key(Key, PeerName, Algorithm, Opts) ->
+ KeyCbOpts = proplists:get_value(key_cb_private, Opts, []),
+ SshFileCb = proplists:get_value(call_ssh_file, KeyCbOpts, ssh_file),
+ SshFileCb:is_host_key(Key, PeerName, Algorithm, Opts).
+
+%% New version
+-spec add_host_key(Host,
+ inet:port_number(),
+ public_key:public_key(),
+ Options
+ ) -> Result when
+ Host :: inet:ip_address() | inet:hostname() | [inet:ip_address() | inet:hostname()],
+ Options :: ssh_client_key_api:client_key_cb_options(call_ssh_file_option()),
+ Result :: ok | {error, Error :: term()}.
+
+add_host_key(Host, Port, PublicKey, Options) ->
+ KeyCbOpts = proplists:get_value(key_cb_private, Options, []),
+ SshFileCb = proplists:get_value(call_ssh_file, KeyCbOpts, ssh_file),
+ SshFileCb:add_host_key(Host, Port, PublicKey, Options).
+
+
+-spec is_host_key(public_key:public_key(),
+ Host,
+ inet:port_number(),
+ ssh:pubkey_alg(),
+ Options
+ ) ->
+ boolean() when
+ Host :: inet:ip_address() | inet:hostname() | [inet:ip_address() | inet:hostname()],
+ Options :: ssh_client_key_api:client_key_cb_options(call_ssh_file_option()).
+
+is_host_key(Key, PeerName, Port, Algorithm, Opts) ->
+ KeyCbOpts = proplists:get_value(key_cb_private, Opts, []),
+ SshFileCb = proplists:get_value(call_ssh_file, KeyCbOpts, ssh_file),
+ SshFileCb:is_host_key(Key, PeerName, Port, Algorithm, Opts).
+
+
+-spec user_key(Algorithm :: ssh:pubkey_alg(),
+ Options) -> Result when
+ Result :: {ok, public_key:private_key()} |
+ {ok, {ssh2_pubkey, PubKeyBlob :: binary()}} |
+ {error, string()},
+ Options :: ssh_client_key_api:client_key_cb_options(socket_path_option()
+ | timeout_option()).
+
+user_key(Algorithm, Opts) ->
+ KeyCbOpts = proplists:get_value(key_cb_private, Opts, []),
+
+ Request = #ssh_agent_identities_request{},
+ Response = ssh_agent:send(Request, KeyCbOpts),
+
+ #ssh_agent_identities_response{keys = Keys} = Response,
+
+ AlgorithmStr = atom_to_list(Algorithm),
+ MatchingKeys = lists:filter(fun(Key) -> has_key_type(Key, AlgorithmStr) end, Keys),
+
+ % The "client_key_api" behaviour only allows returning a single user key,
+ % so we simply select the first one returned from the SSH agent here. This
+ % means that if a user adds multiple keys for the same algorithm, only the
+ % first one added will be used.
+ case MatchingKeys of
+ [#ssh_agent_key{blob = PubKeyBlob} | _OtherKeys] ->
+ {ok, {ssh2_pubkey, PubKeyBlob}};
+ _ ->
+ {error, enoent}
+ end.
+
+-spec sign(binary(),
+ binary(),
+ Options
+ ) ->
+ Blob :: binary() when
+ Options :: ssh_client_key_api:client_key_cb_options(socket_path_option()
+ | timeout_option()).
+
+sign(PubKeyBlob, SigData, Opts) ->
+ KeyCbOpts = proplists:get_value(key_cb_private, Opts, []),
+ % OpenSSH does not seem to care when these flags are set for
+ % signature algorithms other than RSA, so we always send them.
+ SignFlags = ?SSH_AGENT_RSA_SHA2_256 bor ?SSH_AGENT_RSA_SHA2_512,
+ SignRequest = #ssh_agent_sign_request{key_blob = PubKeyBlob, data = SigData, flags = SignFlags},
+ SignResponse = ssh_agent:send(SignRequest, KeyCbOpts),
+ #ssh_agent_sign_response{signature = #ssh_agent_signature{blob = Blob}} = SignResponse,
+ Blob.
+
+%% Utility functions
+
+has_key_type(#ssh_agent_key{blob = KeyBlob}, Type) ->
+ <<?DEC_BIN(KeyType, _KeyTypeLen), _KeyBlobRest/binary>> = KeyBlob,
+ binary_to_list(KeyType) == Type.
+
+%% Agent communication
+
+send(Request, Opts) ->
+ SocketPath = proplists:get_value(socket_path, Opts, os:getenv("SSH_AUTH_SOCK")),
+ Timeout = proplists:get_value(timeout, Opts, 1000),
+
+ ConnectOpts = [binary, {packet, 0}, {active, false}],
+ {ok, Socket} = gen_tcp:connect({local, SocketPath}, 0, ConnectOpts, Timeout),
+
+ BinRequest = pack(encode(Request)),
+ ok = gen_tcp:send(Socket, BinRequest),
+
+ {ok, <<Len:32/unsigned-big-integer>>} = gen_tcp:recv(Socket, 4, Timeout),
+ {ok, BinResponse} = gen_tcp:recv(Socket, Len, Timeout),
+
+ ok = gen_tcp:close(Socket),
+
+ Response = decode(BinResponse),
+
+ Response.
+
+%% Message packing
+
+pack(Data) ->
+ <<(size(Data)):32/unsigned-big-integer, Data/binary>>.
+
+%% SSH Agent message encoding
+
+encode(#ssh_agent_identities_request{}) ->
+ <<?Ebyte(?SSH_AGENTC_REQUEST_IDENTITIES)>>;
+
+encode(#ssh_agent_sign_request{key_blob = KeyBlob, data = Data, flags = Flags}) ->
+ <<?Ebyte(?SSH_AGENTC_SIGN_REQUEST), ?Estring(KeyBlob), ?Estring(Data), ?Euint32(Flags)>>.
+
+%% SSH Agent message decoding
+
+decode_keys(<<>>, Acc, 0) ->
+ lists:reverse(Acc);
+
+decode_keys(<<?DEC_BIN(KeyBlob, _KeyBlobLen), ?DEC_BIN(Comment, _CommentLen), Rest/binary>>, Acc, N) ->
+ Key = #ssh_agent_key{blob = KeyBlob, comment = Comment},
+ decode_keys(Rest, [Key | Acc], N - 1).
+
+decode_signature(<<?DEC_BIN(Format, _FormatLen), Blob/binary>>) ->
+ % Decode signature according to https://tools.ietf.org/html/rfc4253#section-6.6
+ <<?DEC_BIN(SignatureBlob, _SignatureBlobLen)>> = Blob,
+ #ssh_agent_signature{format = Format, blob = SignatureBlob}.
+
+decode(<<?BYTE(?SSH_AGENT_SUCCESS)>>) ->
+ #ssh_agent_success{};
+
+decode(<<?BYTE(?SSH_AGENT_FAILURE)>>) ->
+ #ssh_agent_failure{};
+
+decode(<<?BYTE(?SSH_AGENT_IDENTITIES_ANSWER), ?UINT32(NumKeys), KeyData/binary>>) ->
+ #ssh_agent_identities_response{keys = decode_keys(KeyData, [], NumKeys)};
+
+decode(<<?BYTE(?SSH_AGENT_SIGN_RESPONSE), ?DEC_BIN(Signature, _SignatureLen)>>) ->
+ #ssh_agent_sign_response{signature = decode_signature(Signature)}.
diff --git a/lib/ssh/src/ssh_agent.hrl b/lib/ssh/src/ssh_agent.hrl
new file mode 100644
index 0000000000..687a982e59
--- /dev/null
+++ b/lib/ssh/src/ssh_agent.hrl
@@ -0,0 +1,106 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% Reference: https://tools.ietf.org/html/draft-miller-ssh-agent-02
+
+%% SSH Agent message numbers
+%%
+%% Reference: https://tools.ietf.org/html/draft-miller-ssh-agent-02#section-5.1
+
+%% The following numbers are used for requests from the client to the agent.
+
+-define(SSH_AGENTC_REQUEST_IDENTITIES, 11).
+-define(SSH_AGENTC_SIGN_REQUEST, 13).
+-define(SSH_AGENTC_ADD_IDENTITY, 17).
+-define(SSH_AGENTC_REMOVE_IDENTITY, 18).
+-define(SSH_AGENTC_REMOVE_ALL_IDENTITIES, 19).
+-define(SSH_AGENTC_ADD_ID_CONSTRAINED, 25).
+-define(SSH_AGENTC_ADD_SMARTCARD_KEY, 20).
+-define(SSH_AGENTC_REMOVE_SMARTCARD_KEY, 21).
+-define(SSH_AGENTC_LOCK, 22).
+-define(SSH_AGENTC_UNLOCK, 23).
+-define(SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED, 26).
+-define(SSH_AGENTC_EXTENSION, 27).
+
+%% The following numbers are used for replies from the agent to the client.
+
+-define(SSH_AGENT_FAILURE, 5).
+-define(SSH_AGENT_SUCCESS, 6).
+-define(SSH_AGENT_EXTENSION_FAILURE, 28).
+-define(SSH_AGENT_IDENTITIES_ANSWER, 12).
+-define(SSH_AGENT_SIGN_RESPONSE, 14).
+
+%% SSH Agent signature flags
+%%
+%% Reference: https://tools.ietf.org/html/draft-miller-ssh-agent-02#section-5.3
+
+-define(SSH_AGENT_RSA_SHA2_256, 2).
+-define(SSH_AGENT_RSA_SHA2_512, 4).
+
+%% SSH Agent messages
+%%
+%% Reference: https://tools.ietf.org/html/draft-miller-ssh-agent-02#section-4
+
+%% 4.1 Generic server responses
+
+-record(ssh_agent_success,
+ {
+ }).
+
+-record(ssh_agent_failure,
+ {
+ }).
+
+%% 4.4 Requesting a list of keys
+
+-record(ssh_agent_identities_request,
+ {
+ }).
+
+-record(ssh_agent_key,
+ {
+ blob, % string
+ comment % string
+ }).
+
+-record(ssh_agent_identities_response,
+ {
+ keys % list of ssh_agent_key records
+ }).
+
+%% 4.5 Private key operations
+
+-record(ssh_agent_sign_request,
+ {
+ key_blob, % string
+ data, % string
+ flags % integer
+ }).
+
+-record(ssh_agent_signature,
+ {
+ format, % string
+ blob % binary
+ }).
+
+-record(ssh_agent_sign_response,
+ {
+ signature % ssh_agent_signature
+ }).
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index 8003e805d5..5bc2859b4e 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -26,15 +26,19 @@
-include("ssh.hrl").
-include("ssh_auth.hrl").
+-include("ssh_agent.hrl").
-include("ssh_transport.hrl").
-export([get_public_key/2,
publickey_msg/1, password_msg/1, keyboard_interactive_msg/1,
service_request_msg/1, init_userauth_request_msg/1,
- userauth_request_msg/1, handle_userauth_request/3,
+ userauth_request_msg/1, handle_userauth_request/3, ssh_msg_userauth_result/1,
handle_userauth_info_request/2, handle_userauth_info_response/2
]).
+-behaviour(ssh_dbg).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/3]).
+
%%--------------------------------------------------------------------
%%% Internal application API
%%--------------------------------------------------------------------
@@ -109,14 +113,13 @@ password_msg([#ssh{opts = Opts,
not_ok ->
{not_ok, Ssh};
_ ->
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "password",
- data =
- <<?BOOLEAN(?FALSE),
- ?STRING(unicode:characters_to_binary(Password))>>},
- Ssh)
+ {#ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "password",
+ data =
+ <<?BOOLEAN(?FALSE),
+ ?STRING(unicode:characters_to_binary(Password))>>},
+ Ssh}
end.
%% See RFC 4256 for info on keyboard-interactive
@@ -127,66 +130,77 @@ keyboard_interactive_msg([#ssh{user = User,
not_ok ->
{not_ok,Ssh}; % No need to use a failed pwd once more
_ ->
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "keyboard-interactive",
- data = << ?STRING(<<"">>),
- ?STRING(<<>>) >> },
- Ssh)
+ {#ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "keyboard-interactive",
+ data = << ?STRING(<<"">>),
+ ?STRING(<<>>) >> },
+ Ssh}
end.
get_public_key(SigAlg, #ssh{opts = Opts}) ->
KeyAlg = key_alg(SigAlg),
case ssh_transport:call_KeyCb(user_key, [KeyAlg], Opts) of
+ {ok, {ssh2_pubkey, PubKeyBlob}} ->
+ {ok, {ssh2_pubkey, PubKeyBlob}};
+
{ok, PrivKey} ->
try
%% Check the key - the KeyCb may be a buggy plugin
- true = ssh_transport:valid_key_sha_alg(PrivKey, KeyAlg),
+ true = ssh_transport:valid_key_sha_alg(private, PrivKey, KeyAlg),
Key = ssh_transport:extract_public_key(PrivKey),
- public_key:ssh_encode(Key, ssh2_pubkey)
+ ssh_message:ssh2_pubkey_encode(Key)
of
- PubKeyBlob -> {ok,{PrivKey,PubKeyBlob}}
+ PubKeyBlob -> {ok, {PrivKey, PubKeyBlob}}
catch
_:_ ->
not_ok
end;
- _Error ->
- not_ok
+ _Error ->
+ not_ok
end.
publickey_msg([SigAlg, #ssh{user = User,
- session_id = SessionId,
- service = Service} = Ssh]) ->
+ session_id = SessionId,
+ service = Service,
+ opts = Opts} = Ssh]) ->
case get_public_key(SigAlg, Ssh) of
- {ok, {PrivKey,PubKeyBlob}} ->
+ {ok, {_, PubKeyBlob} = Key} ->
SigAlgStr = atom_to_list(SigAlg),
- SigData = build_sig_data(SessionId, User, Service,
- PubKeyBlob, SigAlgStr),
- Hash = ssh_transport:sha(SigAlg),
- Sig = ssh_transport:sign(SigData, Hash, PrivKey),
+ SigData = build_sig_data(SessionId, User, Service, PubKeyBlob, SigAlgStr),
+
+ Sig = case Key of
+ {ssh2_pubkey, PubKeyBlob} ->
+ ssh_transport:call_KeyCb(sign, [PubKeyBlob, SigData], Opts);
+
+ {PrivKey, PubKeyBlob} ->
+ Hash = ssh_transport:sha(SigAlg),
+ ssh_transport:sign(SigData, Hash, PrivKey)
+ end,
+
SigBlob = list_to_binary([?string(SigAlgStr),
?binary(Sig)]),
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "publickey",
- data = [?TRUE,
- ?string(SigAlgStr),
- ?binary(PubKeyBlob),
- ?binary(SigBlob)]},
- Ssh);
- _ ->
- {not_ok, Ssh}
+
+ {#ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "publickey",
+ data = [?TRUE,
+ ?string(SigAlgStr),
+ ?binary(PubKeyBlob),
+ ?binary(SigBlob)]},
+ Ssh};
+
+ _ ->
+ {not_ok, Ssh}
end.
%%%----------------------------------------------------------------
service_request_msg(Ssh) ->
- ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"},
- Ssh#ssh{service = "ssh-userauth"}).
+ {#ssh_msg_service_request{name = "ssh-userauth"},
+ Ssh#ssh{service = "ssh-userauth"}}.
%%%----------------------------------------------------------------
init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
@@ -196,41 +210,40 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
?DISCONNECT(?SSH_DISCONNECT_ILLEGAL_USER_NAME,
"Could not determine the users name");
User ->
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = "ssh-connection",
- method = "none",
- data = <<>>},
- Ssh#ssh{user = User,
- userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys),
- userauth_methods = none,
- service = "ssh-connection"}
- )
+ {#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none",
+ data = <<>>},
+ Ssh#ssh{user = User,
+ userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys),
+ userauth_methods = none,
+ service = "ssh-connection"}
+ }
end.
%%%----------------------------------------------------------------
%%% called by server
handle_userauth_request(#ssh_msg_service_request{name = Name = "ssh-userauth"},
_, Ssh) ->
- {ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name},
- Ssh#ssh{service = "ssh-connection"})};
+ {ok, {#ssh_msg_service_accept{name = Name},
+ Ssh#ssh{service = "ssh-connection"}}};
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "password",
data = <<?FALSE, ?UINT32(Sz), BinPwd:Sz/binary>>}, _,
- #ssh{opts = Opts,
- userauth_supported_methods = Methods} = Ssh) ->
+ #ssh{userauth_supported_methods = Methods} = Ssh) ->
Password = unicode:characters_to_list(BinPwd),
- case check_password(User, Password, Opts, Ssh) of
+ case check_password(User, Password, Ssh) of
{true,Ssh1} ->
{authorized, User,
- ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)};
+ {#ssh_msg_userauth_success{}, Ssh1}
+ };
{false,Ssh1} ->
{not_authorized, {User, {error,"Bad user or password"}},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh1)}
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh1}
+ }
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -250,18 +263,18 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
%% or the old password was bad.
{not_authorized, {User, {error,"Password change not supported"}},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh)};
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ 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},
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_failure{authentications = Methods,
- partial_success = false}, Ssh)};
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ };
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
@@ -273,20 +286,26 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
>>
},
_SessionId,
- #ssh{opts = Opts,
- userauth_supported_methods = Methods} = Ssh) ->
-
- case pre_verify_sig(User, KeyBlob, Opts) of
+ #ssh{userauth_supported_methods = Methods} = Ssh0) ->
+ Ssh =
+ case check_user(User, Ssh0) of
+ {true,Ssh01} -> Ssh01#ssh{user=User};
+ {false,Ssh01} -> Ssh01#ssh{user=false}
+ end,
+
+ case
+ pre_verify_sig(User, KeyBlob, Ssh)
+ of
true ->
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg),
- key_blob = KeyBlob}, Ssh)};
+ {#ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg),
+ key_blob = KeyBlob}, Ssh}
+ };
false ->
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh)}
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ }
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -298,19 +317,24 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
SigWLen/binary>>
},
SessionId,
- #ssh{userauth_supported_methods = Methods} = Ssh) ->
+ #ssh{user = PreVerifyUser,
+ userauth_supported_methods = Methods} = Ssh0) ->
- case verify_sig(SessionId, User, "ssh-connection",
- BAlg, KeyBlob, SigWLen, Ssh) of
+ {UserOk,Ssh} = check_user(User, Ssh0),
+ case
+ ((PreVerifyUser == User) orelse (PreVerifyUser == undefined)) andalso
+ UserOk andalso
+ verify_sig(SessionId, User, "ssh-connection", BAlg, KeyBlob, SigWLen, Ssh)
+ of
true ->
{authorized, User,
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_success{}, Ssh)};
+ {#ssh_msg_userauth_success{}, Ssh}
+ };
false ->
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh)}
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ }
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -323,9 +347,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
case KbTriesLeft of
N when N<1 ->
{not_authorized, {User, {authmethod, "keyboard-interactive"}},
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_failure{authentications = Methods,
- partial_success = false}, Ssh)};
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ };
_ ->
%% RFC4256
@@ -350,6 +374,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
Default;
{_,_,_,_}=V ->
V;
+ F when is_function(F, 4) ->
+ {_,PeerName} = Ssh#ssh.peer,
+ F(PeerName, User, "ssh-connection", Ssh#ssh.pwdfun_user_state);
F when is_function(F) ->
{_,PeerName} = Ssh#ssh.peer,
F(PeerName, User, "ssh-connection")
@@ -367,8 +394,8 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
>>
},
{not_authorized, {User, undefined},
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User
- })}
+ {Msg, Ssh#ssh{user = User}}
+ }
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -376,9 +403,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
method = Other}, _,
#ssh{userauth_supported_methods = Methods} = Ssh) ->
{not_authorized, {User, {authmethod, Other}},
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_failure{authentications = Methods,
- partial_success = false}, Ssh)}.
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ }.
%%%----------------------------------------------------------------
@@ -394,9 +421,9 @@ handle_userauth_info_request(#ssh_msg_userauth_info_request{name = Name,
not_ok;
Responses ->
{ok,
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_info_response{num_responses = NumPrompts,
- data = Responses}, Ssh)}
+ {#ssh_msg_userauth_info_response{num_responses = NumPrompts,
+ data = Responses},
+ Ssh}}
end.
%%%----------------------------------------------------------------
@@ -412,34 +439,32 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
orelse
proplists:get_value(one_empty, ?GET_OPT(tstflg,Opts), false),
- case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of
+ case check_password(User, unicode:characters_to_list(Password), Ssh) of
{true,Ssh1} when SendOneEmpty==true ->
- Msg = #ssh_msg_userauth_info_request{name = "",
- instruction = "",
- language_tag = "",
- num_prompts = 0,
- data = <<?BOOLEAN(?FALSE)>>
- },
{authorized_but_one_more, User,
- ssh_transport:ssh_packet(Msg, Ssh1)};
+ {#ssh_msg_userauth_info_request{name = "",
+ instruction = "",
+ language_tag = "",
+ num_prompts = 0,
+ data = <<?BOOLEAN(?FALSE)>>
+ },
+ Ssh1}};
{true,Ssh1} ->
{authorized, User,
- ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)};
+ {#ssh_msg_userauth_success{}, Ssh1}};
{false,Ssh1} ->
{not_authorized, {User, {error,"Bad user or password"}},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false},
- Ssh1#ssh{kb_tries_left = max(KbTriesLeft-1, 0)}
- )}
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false},
+ Ssh1#ssh{kb_tries_left = max(KbTriesLeft-1, 0)}}}
end;
handle_userauth_info_response({extra,#ssh_msg_userauth_info_response{}},
#ssh{user = User} = Ssh) ->
{authorized, User,
- ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)};
+ {#ssh_msg_userauth_success{}, Ssh}};
handle_userauth_info_response(#ssh_msg_userauth_info_response{},
_Auth) ->
@@ -458,8 +483,23 @@ method_preference(SigKeyAlgs) ->
],
PubKeyDefs ++ NonPKmethods.
-check_password(User, Password, Opts, Ssh) ->
+check_user(User, Ssh) ->
+ case ?GET_OPT(pk_check_user, Ssh#ssh.opts) of
+ true ->
+ check_password(User, pubkey, Ssh);
+ _ ->
+ {true, Ssh} % i.e, skip the test
+ end.
+
+check_password(User, Password, #ssh{opts=Opts} = Ssh) ->
case ?GET_OPT(pwdfun, Opts) of
+ undefined when Password==pubkey ->
+ %% Just check the User name
+ case lists:keysearch(User, 1, ?GET_OPT(user_passwords,Opts)) of
+ {value, {User, _}} -> {true, Ssh};
+ false -> {false, Ssh}
+ end;
+
undefined ->
Static = get_password_option(Opts, User),
{crypto:equal_const_time(Password,Static), Ssh};
@@ -493,19 +533,19 @@ get_password_option(Opts, User) ->
false -> ?GET_OPT(password, Opts)
end.
-pre_verify_sig(User, KeyBlob, Opts) ->
+pre_verify_sig(User, KeyBlob, #ssh{opts=Opts}) ->
try
- Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception
+ Key = ssh_message:ssh2_pubkey_decode(KeyBlob), % or exception
ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts)
catch
_:_ ->
false
end.
-verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, #ssh{opts = Opts} = Ssh) ->
+verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, #ssh{opts=Opts} = Ssh) ->
try
Alg = binary_to_list(AlgBin),
- Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception
+ Key = ssh_message:ssh2_pubkey_decode(KeyBlob), % or exception
true = ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts),
PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg),
<<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,
@@ -605,3 +645,203 @@ write_if_nonempty(_, "") -> ok;
write_if_nonempty(_, <<>>) -> ok;
write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]).
+%%%----------------------------------------------------------------
+%%% Called just for the tracer ssh_dbg
+ssh_msg_userauth_result(_R) -> ok.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+
+ssh_dbg_trace_points() -> [authentication].
+
+ssh_dbg_flags(authentication) -> [c].
+
+ssh_dbg_on(authentication) -> dbg:tp(?MODULE, handle_userauth_request, 3, x),
+ dbg:tp(?MODULE, init_userauth_request_msg, 1, x),
+ dbg:tp(?MODULE, ssh_msg_userauth_result, 1, x),
+ dbg:tp(?MODULE, userauth_request_msg, 1, x).
+
+ssh_dbg_off(authentication) -> dbg:ctpg(?MODULE, handle_userauth_request, 3),
+ dbg:ctpg(?MODULE, init_userauth_request_msg, 1),
+ dbg:ctpg(?MODULE, ssh_msg_userauth_result, 1),
+ dbg:ctpg(?MODULE, userauth_request_msg, 1).
+
+
+
+%%% Server ----------------
+ssh_dbg_format(authentication, {call, {?MODULE,handle_userauth_request, [Req,_SessionID,Ssh]}},
+ Stack) ->
+ {skip, [{Req,Ssh}|Stack]};
+
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {ok,{#ssh_msg_service_accept{name=Name},_Ssh}}},
+ [{#ssh_msg_service_request{name=Name},_} | Stack]) ->
+ {skip, Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {authorized,User,_Repl}},
+ [{#ssh_msg_userauth_request{}=Req,Ssh}|Stack]) ->
+ {["AUTH srvr: Peer client authorized\n",
+ io_lib:format("user = ~p~n", [User]),
+ fmt_req(Req, Ssh)],
+ Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {not_authorized,{User,_X},_Repl}},
+ [{#ssh_msg_userauth_request{method="none"},Ssh}|Stack]) ->
+ Methods = Ssh#ssh.userauth_supported_methods,
+ {["AUTH srvr: Peer queries auth methods\n",
+ io_lib:format("user = ~p~nsupported methods = ~p ?", [User,Methods])
+ ],
+ Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {not_authorized,{User,_X}, Repl}
+ },
+ [{#ssh_msg_userauth_request{method = "publickey",
+ data = <<?BYTE(?FALSE), _/binary>>
+ }=Req,Ssh}|Stack]) ->
+ {case Repl of
+ {#ssh_msg_userauth_pk_ok{}, _} ->
+ ["AUTH srvr: Answer - pub key supported\n"];
+ {#ssh_msg_userauth_failure{}, _} ->
+ ["AUTH srvr: Answer - pub key not supported\n"];
+ {Other, _} ->
+ ["AUTH srvr: Answer - strange answer\n",
+ io_lib:format("strange answer = ~p~n",[Other])
+ ]
+ end
+ ++ [io_lib:format("user = ~p~n", [User]),
+ fmt_req(Req, Ssh)],
+ Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {not_authorized,{User,_X},
+ {#ssh_msg_userauth_info_request{},_Ssh}}},
+ [{#ssh_msg_userauth_request{method="keyboard-interactive"
+ } = Req,Ssh}|Stack]) ->
+ {["AUTH srvr: Ask peer client for password\n",
+ io_lib:format("user = ~p~n", [User]),
+ fmt_req(Req, Ssh)],
+ Stack};
+
+
+ssh_dbg_format(authentication, {call, {?MODULE,ssh_msg_userauth_result,[success]}},
+ Stack) ->
+ {["AUTH client: Success"],Stack};
+ssh_dbg_format(authentication, {return_from, {?MODULE,ssh_msg_userauth_result,1}, _Result},
+ Stack) ->
+ {skip, Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3},
+ {not_authorized,{User,_X},_Repl}},
+ [{#ssh_msg_userauth_request{}=Req,Ssh}|Stack]) ->
+ {["AUTH srvr: Peer client authorization failed\n",
+ io_lib:format("user = ~p~n", [User]),
+ fmt_req(Req, Ssh)],
+ Stack};
+
+%%% Client ----------------
+ssh_dbg_format(authentication, {call, {?MODULE,init_userauth_request_msg, [#ssh{opts = Opts}]}},
+ Stack) ->
+ {["AUTH client: Service ssh-userauth accepted\n",
+ case ?GET_OPT(user, Opts) of
+ undefined ->
+ io_lib:format("user = undefined *** ERROR ***", []);
+ User ->
+ io_lib:format("user = ~p", [User])
+ end
+ ],
+ Stack};
+ssh_dbg_format(authentication, {return_from, {?MODULE,init_userauth_request_msg,1},
+ {Repl = #ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none"},
+ _Ssh}},
+ Stack) ->
+ {["AUTH client: Query for accepted methods\n",
+ io_lib:format("user = ~p", [User])],
+ [Repl|Stack]};
+
+ssh_dbg_format(authentication, {call, {?MODULE,userauth_request_msg,
+ [#ssh{userauth_methods = Methods}]}},
+ [ #ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none"} | Stack]) ->
+ {["AUTH client: Server supports\n",
+ io_lib:format("user = ~p~nmethods = ~p", [User,Methods])],
+ Stack};
+
+ssh_dbg_format(authentication, {call, {?MODULE,userauth_request_msg,[_Ssh]}},
+ Stack) ->
+ {skip,Stack};
+
+ssh_dbg_format(authentication, {return_from, {?MODULE,userauth_request_msg,1},
+ {send_disconnect, _Code, _Ssh}},
+ Stack) ->
+ {skip,Stack};
+ssh_dbg_format(authentication, {return_from, {?MODULE,userauth_request_msg,1},
+ {Method,{_Msg,_Ssh}}},
+ Stack) ->
+ {["AUTH client: Try auth with\n",
+ io_lib:format("method = ~p", [Method])],
+ Stack};
+
+
+
+ssh_dbg_format(authentication, Unhandled, Stack) ->
+ case Unhandled of
+ {call, {?MODULE,_F,_Args}} -> ok;
+ {return_from, {?MODULE,_F,_A}, _Resp} -> ok
+ end,
+ {["UNHANDLED AUTH FORMAT\n",
+ io_lib:format("Unhandled = ~p~nStack = ~p", [Unhandled,Stack])],
+ Stack}.
+
+
+%%% Dbg helpers ----------------
+
+
+fmt_req(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = Method,
+ data = Data},
+ #ssh{kb_tries_left = KbTriesLeft,
+ userauth_supported_methods = Methods}) ->
+ [io_lib:format("req user = ~p~n"
+ "req method = ~p~n"
+ "supported methods = ~p",
+ [User,Method,Methods]),
+ case Method of
+ "none" -> "";
+ "password" -> fmt_bool(Data);
+ "keyboard-interactive" -> fmt_kb_tries_left(KbTriesLeft);
+ "publickey" -> [case Data of
+ <<?BYTE(_), ?UINT32(ALen), Alg:ALen/binary, _/binary>> ->
+ io_lib:format("~nkey-type = ~p", [Alg]);
+ _ ->
+ ""
+ end];
+ _ -> ""
+ end].
+
+
+fmt_kb_tries_left(N) when is_integer(N)->
+ io_lib:format("~ntries left = ~p", [N-1]).
+
+
+fmt_bool(<<?BYTE(Bool),_/binary>>) ->
+ io_lib:format("~nBool = ~s",
+ [case Bool of
+ ?TRUE -> "true";
+ ?FALSE -> "false";
+ _ -> io_lib:format("? (~p)",[Bool])
+ end]);
+fmt_bool(<<>>) ->
+ "".
+
+
+
diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl
new file mode 100644
index 0000000000..4b36c8a5a0
--- /dev/null
+++ b/lib/ssh/src/ssh_channel_sup.erl
@@ -0,0 +1,90 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Ssh channel supervisor.
+%%----------------------------------------------------------------------
+-module(ssh_channel_sup).
+
+-behaviour(supervisor).
+-include("ssh.hrl").
+
+-export([start_link/1, start_child/8]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% Internal API
+%%%=========================================================================
+start_link(Args) ->
+ supervisor:start_link(?MODULE, [Args]).
+
+
+start_child(client, ChannelSup, ConnRef, Callback, Id, Args, Exec, _Opts) when is_pid(ConnRef) ->
+ start_the_child(ssh_client_channel, ChannelSup, ConnRef, Callback, Id, Args, Exec);
+start_child(server, ChannelSup, ConnRef, Callback, Id, Args, Exec, Opts) when is_pid(ConnRef) ->
+ case max_num_channels_not_exceeded(ChannelSup, Opts) of
+ true ->
+ start_the_child(ssh_server_channel, ChannelSup, ConnRef, Callback, Id, Args, Exec);
+ false ->
+ {error, max_num_channels_exceeded}
+ end.
+
+
+start_the_child(ChanMod, ChannelSup, ConnRef, Callback, Id, Args, Exec) ->
+ ChildSpec =
+ #{id => make_ref(),
+ start => {ChanMod, start_link, [ConnRef, Id, Callback, Args, Exec]},
+ restart => temporary,
+ type => worker,
+ modules => [ChanMod]
+ },
+ case supervisor:start_child(ChannelSup, ChildSpec) of
+ {ok, Pid} ->
+ {ok, Pid};
+ {ok, Pid, _Info} ->
+ {ok,Pid};
+ {error, {Error,_Info}} ->
+ {error, Error};
+ {error, Error} ->
+ {error, Error}
+ end.
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(_Args) ->
+ RestartStrategy = one_for_one,
+ MaxR = 10,
+ MaxT = 3600,
+ Children = [],
+ {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
+max_num_channels_not_exceeded(ChannelSup, Opts) ->
+ MaxNumChannels = ?GET_OPT(max_channels, Opts),
+ NumChannels = length([x || {_,_,worker,[ssh_server_channel]} <-
+ supervisor:which_children(ChannelSup)]),
+ %% Note that NumChannels is BEFORE starting a new one
+ NumChannels < MaxNumChannels.
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index d6c8cf84ed..13a44beea3 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -41,6 +41,12 @@
cm,
channel,
pty,
+ encoding,
+ deduced_encoding, % OpenSSH sometimes lies about its encodeing. This variable
+ % is for the process of guessing the peer encoding, taylord
+ % after the behaviour of openssh. If it says latin1 it is so.
+ % It there arrives characters encoded in latin1 it is so. Otherwise
+ % assume utf8 until otherwise is proved.
group,
buf,
shell,
@@ -70,8 +76,9 @@ init([Shell]) ->
%%--------------------------------------------------------------------
handle_ssh_msg({ssh_cm, _ConnectionHandler,
{data, _ChannelId, _Type, Data}},
- #state{group = Group} = State) ->
- List = binary_to_list(Data),
+ #state{group = Group} = State0) ->
+ {Enc, State} = guess_encoding(Data, State0),
+ List = unicode:characters_to_list(Data, Enc),
to_group(List, Group),
{ok, State};
@@ -93,10 +100,69 @@ handle_ssh_msg({ssh_cm, ConnectionHandler,
{ok, State};
handle_ssh_msg({ssh_cm, ConnectionHandler,
- {env, ChannelId, WantReply, _Var, _Value}}, State) ->
+ {env, ChannelId, WantReply, Var, Value}}, State = #state{encoding=Enc0}) ->
+ %% It is not as simple as it sounds to set environment variables
+ %% in the server.
+ %% The (OS) env vars should be per per Channel; otherwise anyone
+ %% could affect anyone other's variables.
+ %% Therefore this clause always return a failure.
ssh_connection:reply_request(ConnectionHandler,
WantReply, failure, ChannelId),
- {ok, State};
+
+ %% https://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
+ %% LANG
+ %% This variable determines the locale category for native language,
+ %% local customs and coded character set in the absence of the LC_ALL
+ %% and other LC_* (LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY,
+ %% LC_NUMERIC, LC_TIME) environment variables. This can be used by
+ %% applications to determine the language to use for error messages
+ %% and instructions, collating sequences, date formats, and so forth.
+ %% LC_ALL
+ %% This variable determines the values for all locale categories. The
+ %% value of the LC_ALL environment variable has precedence over any of
+ %% the other environment variables starting with LC_ (LC_COLLATE,
+ %% LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME) and the
+ %% LANG environment variable.
+ %% ...
+ %%
+ %% The values of locale categories are determined by a precedence order;
+ %% the first condition met below determines the value:
+ %%
+ %% 1. If the LC_ALL environment variable is defined and is not null,
+ %% the value of LC_ALL is used.
+ %%
+ %% 2. If the LC_* environment variable ( LC_COLLATE, LC_CTYPE, LC_MESSAGES,
+ %% LC_MONETARY, LC_NUMERIC, LC_TIME) is defined and is not null, the
+ %% value of the environment variable is used to initialise the category
+ %% that corresponds to the environment variable.
+ %%
+ %% 3. If the LANG environment variable is defined and is not null, the value
+ %% of the LANG environment variable is used.
+ %%
+ %% 4. If the LANG environment variable is not set or is set to the empty string,
+ %% the implementation-dependent default locale is used.
+
+ Enc =
+ %% Rule 1 and 3 above says that LC_ALL has precedence over LANG. Since they
+ %% arrives in different messages and in an undefined order, it is resolved
+ %% like this:
+ case Var of
+ <<"LANG">> when Enc0==undefined ->
+ %% No previous LC_ALL
+ case claim_encoding(Value) of
+ {ok,Enc1} -> Enc1;
+ _ -> Enc0
+ end;
+ <<"LC_ALL">> ->
+ %% Maybe or maybe not a LANG has been handled, LC_ALL doesn't care
+ case claim_encoding(Value) of
+ {ok,Enc1} -> Enc1;
+ _ -> Enc0
+ end;
+ _ ->
+ Enc0
+ end,
+ {ok, State#state{encoding=Enc}};
handle_ssh_msg({ssh_cm, ConnectionHandler,
{window_change, ChannelId, Width, Height, PixWidth, PixHeight}},
@@ -114,15 +180,21 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, {shell, ChannelId, WantReply}}, #sta
ssh_connection:exit_status(ConnectionHandler, ChannelId, ?EXEC_ERROR_STATUS),
ssh_connection:send_eof(ConnectionHandler, ChannelId),
{stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionHandler}};
-handle_ssh_msg({ssh_cm, ConnectionHandler, {shell, ChannelId, WantReply}}, State) ->
+handle_ssh_msg({ssh_cm, ConnectionHandler, {shell, ChannelId, WantReply}}, State0) ->
+ State = case State0#state.encoding of
+ undefined -> State0#state{encoding = utf8};
+ _-> State0
+ end,
NewState = start_shell(ConnectionHandler, State),
ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),
{ok, NewState#state{channel = ChannelId,
cm = ConnectionHandler}};
-handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}}, S0) ->
+handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd0}}, S0) ->
+ {Enc,S1} = guess_encoding(Cmd0, S0),
+ Cmd = unicode:characters_to_list(Cmd0, Enc),
case
- case S0#state.exec of
+ case S1#state.exec of
disabled ->
{"Prohibited.", ?EXEC_ERROR_STATUS, 1};
@@ -130,7 +202,7 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}},
%% Exec called and a Fun or MFA is defined to use. The F returns the
%% value to return.
%% The standard I/O is directed from/to the channel ChannelId.
- exec_direct(ConnectionHandler, ChannelId, Cmd, F, WantReply, S0);
+ exec_direct(ConnectionHandler, ChannelId, Cmd, F, WantReply, S1);
undefined when S0#state.shell == ?DEFAULT_SHELL ;
S0#state.shell == disabled ->
@@ -138,7 +210,7 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}},
%% To be exact, eval the term as an Erlang term (but not using the
%% ?DEFAULT_SHELL directly). This disables banner, prompts and such.
%% The standard I/O is directed from/to the channel ChannelId.
- exec_in_erlang_default_shell(ConnectionHandler, ChannelId, Cmd, WantReply, S0);
+ exec_in_erlang_default_shell(ConnectionHandler, ChannelId, Cmd, WantReply, S1);
undefined ->
%% Exec called, but the a shell other than the default shell is defined.
@@ -150,17 +222,18 @@ handle_ssh_msg({ssh_cm, ConnectionHandler, {exec, ChannelId, WantReply, Cmd}},
%% Exec called and a Fun or MFA is defined to use. The F communicates via
%% standard io:write/read.
%% Kept for compatibility.
- S1 = start_exec_shell(ConnectionHandler, Cmd, S0),
+ S2 = start_exec_shell(ConnectionHandler, Cmd, S1),
ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),
- {ok, S1}
+ {ok, S2}
end
of
{Reply, Status, Type} ->
- write_chars(ConnectionHandler, ChannelId, Type, Reply),
+ write_chars(ConnectionHandler, ChannelId, Type,
+ unicode:characters_to_binary(Reply, utf8, out_enc(S1))),
ssh_connection:reply_request(ConnectionHandler, WantReply, success, ChannelId),
ssh_connection:exit_status(ConnectionHandler, ChannelId, Status),
ssh_connection:send_eof(ConnectionHandler, ChannelId),
- {stop, ChannelId, S0#state{channel = ChannelId, cm = ConnectionHandler}};
+ {stop, ChannelId, S1#state{channel = ChannelId, cm = ConnectionHandler}};
{ok, S} ->
{ok, S#state{channel = ChannelId,
@@ -225,16 +298,26 @@ handle_msg({Group, tty_geometry}, #state{group = Group,
{ok,State};
handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
- cm = ConnectionHandler,
- channel = ChannelId} = State) ->
- {Chars, NewBuf} = io_request(Req, Buf, Pty, Group),
+ cm = ConnectionHandler,
+ channel = ChannelId} = State) ->
+ {Chars0, NewBuf} = io_request(Req, Buf, Pty, Group),
+ Chars = unicode:characters_to_binary(Chars0, utf8, out_enc(State)),
write_chars(ConnectionHandler, ChannelId, Chars),
{ok, State#state{buf = NewBuf}};
-handle_msg({'EXIT', Group, _Reason}, #state{group = Group,
+handle_msg({'EXIT', Group, Reason}, #state{group = Group,
cm = ConnectionHandler,
channel = ChannelId} = State) ->
ssh_connection:send_eof(ConnectionHandler, ChannelId),
+ ExitStatus = case Reason of
+ normal ->
+ 0;
+ {exit_status, V} when is_integer(V) ->
+ V;
+ _ ->
+ ?EXEC_ERROR_STATUS
+ end,
+ ssh_connection:exit_status(ConnectionHandler, ChannelId, ExitStatus),
{stop, ChannelId, State};
handle_msg(_, State) ->
@@ -251,6 +334,61 @@ terminate(_Reason, _State) ->
%%% Internal functions
%%--------------------------------------------------------------------
+claim_encoding(<<"/", _/binary>>) ->
+ %% If the locale value begins with a slash, it is interpreted
+ %% as the pathname of a file that was created in the output format
+ %% used by the localedef utility; see OUTPUT FILES under localedef.
+ %% Referencing such a pathname will result in that locale being used
+ %% for the indicated category.
+ undefined;
+
+claim_encoding(EnvValue) ->
+ %% If the locale value has the form:
+ %% language[_territory][.codeset]
+ %% it refers to an implementation-provided locale, where settings of
+ %% language, territory and codeset are implementation-dependent.
+ try string:tokens(binary_to_list(EnvValue), ".")
+ of
+ [_,"UTF-8"] -> {ok,utf8};
+ [_,"ISO-8859-1"] -> {ok,latin1}; % There are -1 ... -16 called latin1..latin16
+ _ -> undefined
+ catch
+ _:_ -> undefined
+ end.
+
+
+guess_encoding(Data0, #state{encoding = PeerEnc0,
+ deduced_encoding = TestEnc0} = State) ->
+ Enc =
+ case {PeerEnc0,TestEnc0} of
+ {latin1,_} -> latin1;
+ {_,latin1} -> latin1;
+ _ -> case unicode:characters_to_binary(Data0, utf8, utf8) of
+ Data0 -> utf8;
+ _ -> latin1
+ end
+ end,
+ case TestEnc0 of
+ Enc ->
+ {Enc, State};
+ latin1 ->
+ {Enc, State};
+ utf8 when Enc==latin1 ->
+ {Enc, State#state{deduced_encoding=latin1}};
+ undefined ->
+ {Enc, State#state{deduced_encoding=Enc}}
+ end.
+
+
+out_enc(#state{encoding = PeerEnc,
+ deduced_encoding = DeducedEnc}) ->
+ case DeducedEnc of
+ undefined -> PeerEnc;
+ _ -> DeducedEnc
+ end.
+
+%%--------------------------------------------------------------------
+
to_group([], _Group) ->
ok;
to_group([$\^C | Tail], Group) ->
@@ -342,23 +480,29 @@ get_tty_command(left, N, _TerminalType) ->
%% convert input characters to buffer and to writeout
%% Note that the buf is reversed but the buftail is not
%% (this is handy; the head is always next to the cursor)
-conv_buf([], AccBuf, AccBufTail, AccWrite, Col) ->
+conv_buf([], AccBuf, AccBufTail, AccWrite, Col, _Tty) ->
{AccBuf, AccBufTail, lists:reverse(AccWrite), Col};
-conv_buf([13, 10 | Rest], _AccBuf, AccBufTail, AccWrite, _Col) ->
- conv_buf(Rest, [], tl2(AccBufTail), [10, 13 | AccWrite], 0);
-conv_buf([13 | Rest], _AccBuf, AccBufTail, AccWrite, _Col) ->
- conv_buf(Rest, [], tl1(AccBufTail), [13 | AccWrite], 0);
-conv_buf([10 | Rest], _AccBuf, AccBufTail, AccWrite, _Col) ->
- conv_buf(Rest, [], tl1(AccBufTail), [10, 13 | AccWrite], 0);
-conv_buf([C | Rest], AccBuf, AccBufTail, AccWrite, Col) ->
- conv_buf(Rest, [C | AccBuf], tl1(AccBufTail), [C | AccWrite], Col + 1).
+conv_buf([13, 10 | Rest], _AccBuf, AccBufTail, AccWrite, _Col, Tty) ->
+ conv_buf(Rest, [], tl2(AccBufTail), [10, 13 | AccWrite], 0, Tty);
+conv_buf([13 | Rest], _AccBuf, AccBufTail, AccWrite, _Col, Tty) ->
+ conv_buf(Rest, [], tl1(AccBufTail), [13 | AccWrite], 0, Tty);
+conv_buf([10 | Rest], _AccBuf, AccBufTail, AccWrite0, _Col, Tty) ->
+ AccWrite =
+ case pty_opt(onlcr,Tty) of
+ 0 -> [10 | AccWrite0];
+ 1 -> [10,13 | AccWrite0];
+ undefined -> [10 | AccWrite0]
+ end,
+ conv_buf(Rest, [], tl1(AccBufTail), AccWrite, 0, Tty);
+conv_buf([C | Rest], AccBuf, AccBufTail, AccWrite, Col, Tty) ->
+ conv_buf(Rest, [C | AccBuf], tl1(AccBufTail), [C | AccWrite], Col + 1, Tty).
%%% put characters at current position (possibly overwriting
%%% characters after current position in buffer)
-put_chars(Chars, {Buf, BufTail, Col}, _Tty) ->
+put_chars(Chars, {Buf, BufTail, Col}, Tty) ->
{NewBuf, NewBufTail, WriteBuf, NewCol} =
- conv_buf(Chars, Buf, BufTail, [], Col),
+ conv_buf(Chars, Buf, BufTail, [], Col, Tty),
{WriteBuf, {NewBuf, NewBufTail, NewCol}}.
%%% insert character at current position
@@ -366,7 +510,7 @@ insert_chars([], {Buf, BufTail, Col}, _Tty) ->
{[], {Buf, BufTail, Col}};
insert_chars(Chars, {Buf, BufTail, Col}, Tty) ->
{NewBuf, _NewBufTail, WriteBuf, NewCol} =
- conv_buf(Chars, Buf, [], [], Col),
+ conv_buf(Chars, Buf, [], [], Col, Tty),
M = move_cursor(special_at_width(NewCol+length(BufTail), Tty), NewCol, Tty),
{[WriteBuf, BufTail | M], {NewBuf, BufTail, NewCol}}.
@@ -390,14 +534,29 @@ delete_chars(N, {Buf, BufTail, Col}, Tty) -> % N < 0
%%% if current window is wider than previous)
window_change(Tty, OldTty, Buf)
when OldTty#ssh_pty.width == Tty#ssh_pty.width ->
+ %% No line width change
{[], Buf};
window_change(Tty, OldTty, {Buf, BufTail, Col}) ->
- M1 = move_cursor(Col, 0, OldTty),
- N = erlang:max(Tty#ssh_pty.width - OldTty#ssh_pty.width, 0) * 2,
- S = lists:reverse(Buf, [BufTail | lists:duplicate(N, $ )]),
- M2 = move_cursor(length(Buf) + length(BufTail) + N, Col, Tty),
- {[M1, S | M2], {Buf, BufTail, Col}}.
-
+ case OldTty#ssh_pty.width - Tty#ssh_pty.width of
+ 0 ->
+ %% No line width change
+ {[], {Buf,BufTail,Col}};
+
+ DeltaW0 when DeltaW0 < 0,
+ BufTail == [] ->
+ % Line width is decreased, cursor is at end of input
+ {[], {Buf,BufTail,Col}};
+
+ DeltaW0 when DeltaW0 < 0,
+ BufTail =/= [] ->
+ % Line width is decreased, cursor is not at end of input
+ {[], {Buf,BufTail,Col}};
+
+ DeltaW0 when DeltaW0 > 0 ->
+ % Line width is increased
+ {[], {Buf,BufTail,Col}}
+ end.
+
%% move around in buffer, respecting pad characters
step_over(0, Buf, [?PAD | BufTail], Col) ->
{[?PAD | Buf], BufTail, Col+1};
@@ -605,11 +764,10 @@ exec_in_self_group(ConnectionHandler, ChannelId, WantReply, State, Fun) ->
end
of
{ok,Str} ->
- write_chars(ConnectionHandler, ChannelId, t2str(Str)),
- ssh_connection:exit_status(ConnectionHandler, ChannelId, 0);
+ write_chars(ConnectionHandler, ChannelId, t2str(Str));
{error, Str} ->
write_chars(ConnectionHandler, ChannelId, 1, "**Error** "++t2str(Str)),
- ssh_connection:exit_status(ConnectionHandler, ChannelId, ?EXEC_ERROR_STATUS)
+ exit({exit_status, ?EXEC_ERROR_STATUS})
end
end)
end,
@@ -624,14 +782,11 @@ t2str(T) -> try io_lib:format("~s",[T])
%%--------------------------------------------------------------------
% Pty can be undefined if the client never sets any pty options before
% starting the shell.
-get_echo(undefined) ->
- true;
-get_echo(#ssh_pty{modes = Modes}) ->
- case proplists:get_value(echo, Modes, 1) of
- 0 ->
- false;
- _ ->
- true
+get_echo(Tty) ->
+ case pty_opt(echo,Tty) of
+ 0 -> false;
+ 1 -> true;
+ undefined -> true
end.
% Group is undefined if the pty options are sent between open and
@@ -647,22 +802,125 @@ not_zero(0, B) ->
not_zero(A, _) ->
A.
+%%%----------------------------------------------------------------
+pty_opt(Name, Tty) ->
+ try
+ proplists:get_value(Name, Tty#ssh_pty.modes, undefined)
+ catch
+ _:_ -> undefined
+ end.
+
%%%################################################################
%%%#
%%%# Tracing
%%%#
-ssh_dbg_trace_points() -> [terminate].
+ssh_dbg_trace_points() -> [terminate, cli, cli_details].
+ssh_dbg_flags(cli) -> [c];
ssh_dbg_flags(terminate) -> [c].
+ssh_dbg_on(cli) -> dbg:tp(?MODULE,handle_ssh_msg,2,x),
+ dbg:tp(?MODULE,write_chars,4,x);
+ssh_dbg_on(cli_details) -> dbg:tp(?MODULE,handle_msg,2,x);
ssh_dbg_on(terminate) -> dbg:tp(?MODULE, terminate, 2, x).
+
+ssh_dbg_off(cli) -> dbg:ctpg(?MODULE,handle_ssh_msg,2),
+ dbg:ctpg(?MODULE,write_chars,4);
+ssh_dbg_off(cli_details) -> dbg:ctpg(?MODULE,handle_msg,2);
ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
+
+ssh_dbg_format(cli, {call,{?MODULE,handle_ssh_msg,
+ [{ssh_cm, _ConnectionHandler, Request},
+ S = #state{channel=Ch}]}}) when is_tuple(Request) ->
+ [io_lib:format("CLI conn ~p chan ~p, req ~p",
+ [self(),Ch,element(1,Request)]),
+ case Request of
+ {window_change, ChannelId, Width, Height, PixWidth, PixHeight} ->
+ fmt_kv([{channel_id,ChannelId},
+ {width,Width}, {height,Height},
+ {pix_width,PixWidth}, {pixel_hight,PixHeight}]);
+
+ {env, ChannelId, WantReply, Var, Value} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply}, {Var,Value}]);
+
+ {exec, ChannelId, WantReply, Cmd} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply}, {command,Cmd}]);
+
+ {pty, ChannelId, WantReply,
+ {TermName, Width, Height, PixWidth, PixHeight, Modes}} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply},
+ {term,TermName},
+ {width,Width}, {height,Height}, {pix_width,PixWidth}, {pixel_hight,PixHeight},
+ {pty_opts, Modes}]);
+
+ {data, ChannelId, Type, Data} ->
+ fmt_kv([{channel_id,ChannelId},
+ {type, type(Type)},
+ {data, us, ssh_dbg:shrink_bin(Data)},
+ {hex, h, Data}
+ ]);
+
+ {shell, ChannelId, WantReply} ->
+ fmt_kv([{channel_id,ChannelId},
+ {want_reply,WantReply},
+ {encoding, S#state.encoding},
+ {pty, S#state.pty}
+ ]);
+
+ _ ->
+ io_lib:format("~nunder construction:~nRequest = ~p",[Request])
+ end];
+ssh_dbg_format(cli, {call,{?MODULE,handle_ssh_msg,_}}) -> skip;
+ssh_dbg_format(cli, {return_from,{?MODULE,handle_ssh_msg,2},_Result}) -> skip;
+
+ssh_dbg_format(cli, {call,{?MODULE,write_chars, [C, Ch, Type, Chars]}}) ->
+ [io_lib:format("CLI conn ~p chan ~p reply", [C,Ch]),
+ fmt_kv([{channel_id,Ch},
+ {type, type(Type)},
+ {data, us, ssh_dbg:shrink_bin(Chars)},
+ {hex, h, Chars}
+ ])];
+ssh_dbg_format(cli, {return_from,{?MODULE,write_chars,4},_Result}) -> skip;
+
+
+ssh_dbg_format(cli_details, {call,{?MODULE,handle_msg,
+ [{Group,Arg}, #state{channel=Ch}]}}) ->
+ [io_lib:format("CLI detail conn ~p chan ~p group ~p",
+ ['?', Ch, Group]),
+ case Arg of
+ {put_chars_sync,Class,Cs,Reply} ->
+ fmt_kv([{op, put_chars_sync},
+ {class, Class},
+ {data, us, ssh_dbg:shrink_bin(Cs)},
+ {hex, h, Cs},
+ {reply, Reply}]);
+ _ ->
+ io_lib:format("~nunder construction:~nRequest = ~p",[Arg])
+ end];
+ssh_dbg_format(cli_details, {call,{?MODULE,handle_msg,_}}) -> skip;
+ssh_dbg_format(cli_details, {return_from,{?MODULE,handle_msg,2},_Result}) -> skip;
+
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Cli Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
- ].
+ ];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip.
+
?wr_record(state).
+
+fmt_kv(KVs) -> lists:map(fun fmt_kv1/1, KVs).
+
+fmt_kv1({K,V}) -> io_lib:format("~n~p: ~p",[K,V]);
+fmt_kv1({K,s,V}) -> io_lib:format("~n~p: ~s",[K,V]);
+fmt_kv1({K,us,V}) -> io_lib:format("~n~p: ~ts",[K,V]);
+fmt_kv1({K,h,V}) -> io_lib:format("~n~p: ~s",[K, [$\n|ssh_dbg:hex_dump(V)]]).
+
+type(0) -> "0 (normal data)";
+type(1) -> "1 (extended data, i.e. errors)";
+type(T) -> T.
+
diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl
index 6de897e1b2..bb16d878fa 100644
--- a/lib/ssh/src/ssh_client_channel.erl
+++ b/lib/ssh/src/ssh_client_channel.erl
@@ -419,12 +419,16 @@ ssh_dbg_format(channels, {return_from, {?MODULE,init,1}, {stop,Reason}}) ->
["Server Channel Start FAILED!\n",
io_lib:format("Reason = ~p", [Reason])
];
+
ssh_dbg_format(channels, F) ->
ssh_dbg_format(terminate, F);
+
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Server Channel Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip;
ssh_dbg_format(channel_events, {call, {?MODULE,handle_call, [Call,From,State]}}) ->
[hdr("is called", State),
@@ -432,23 +436,25 @@ ssh_dbg_format(channel_events, {call, {?MODULE,handle_call, [Call,From,State]}})
];
ssh_dbg_format(channel_events, {return_from, {?MODULE,handle_call,3}, Ret}) ->
["Server Channel call returned:\n",
- io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)])
+ io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret,#state{})])
];
+
ssh_dbg_format(channel_events, {call, {?MODULE,handle_cast, [Cast,State]}}) ->
[hdr("got cast", State),
io_lib:format("Cast: ~p~n", [Cast])
];
ssh_dbg_format(channel_events, {return_from, {?MODULE,handle_cast,2}, Ret}) ->
["Server Channel cast returned:\n",
- io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)])
+ io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret,#state{})])
];
+
ssh_dbg_format(channel_events, {call, {?MODULE,handle_info, [Info,State]}}) ->
[hdr("got info", State),
io_lib:format("Info: ~p~n", [Info])
];
ssh_dbg_format(channel_events, {return_from, {?MODULE,handle_info,2}, Ret}) ->
["Server Channel info returned:\n",
- io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)])
+ io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret,#state{})])
].
hdr(Title, S) ->
diff --git a/lib/ssh/src/ssh_client_key_api.erl b/lib/ssh/src/ssh_client_key_api.erl
index 384740b786..c1b43e04eb 100644
--- a/lib/ssh/src/ssh_client_key_api.erl
+++ b/lib/ssh/src/ssh_client_key_api.erl
@@ -23,25 +23,97 @@
-include_lib("public_key/include/public_key.hrl").
-include("ssh.hrl").
--export_type([client_key_cb_options/0]).
+-export_type([client_key_cb_options/1]).
--type client_key_cb_options() :: [{key_cb_private,term()} | ssh:client_option()].
+%%%****************************************************************
+%%% The option key_cb_private is to pass options needed by other
+%%% callback modules than the default ssh_file.erl
+%%%
+%%% If ssh:connect(x, n, [ {key_cb_private, {hi,{there}}} ]
+%%% is called, the term() will be {hi,{there}}
+
+-type client_key_cb_options(T) :: [{key_cb_private,[T]} | ssh:client_option()].
+
+
+%%%****************************************************************
+%%% Checks if the public key Key is a host key for (any of) the
+%%% host(s) in the argument Host with the port Port.
+%%%
+%%% Due to compatibility reasons, the OTP/SSH application first
+%%% checks is_host_key/4 and then the old is_host_key/3
-callback is_host_key(Key :: public_key:public_key(),
- Host :: string(),
+ Host :: inet:ip_address() | inet:hostname()
+ | [inet:ip_address() | inet:hostname()],
+ Port :: inet:port_number(),
Algorithm :: ssh:pubkey_alg(),
- Options :: client_key_cb_options()
+ Options :: client_key_cb_options(any())
+ ) ->
+ boolean() | {error, term()} .
+
+%%% is_host_key/4 is an old variant which is kept for compatibility.
+%%% Use is_host_key/5 in new programs.
+
+-callback is_host_key(Key :: public_key:public_key(),
+ Host :: string(),
+ Algorithm :: ssh:pubkey_alg(),
+ Options :: client_key_cb_options(any())
) ->
boolean().
+-optional_callbacks(
+ [is_host_key/4, is_host_key/5 % One in the pair must be defined
+ ]).
+
+
+%%%****************************************************************
+%%% Fetch the user's private key that is of type Algorithm.
+
-callback user_key(Algorithm :: ssh:pubkey_alg(),
- Options :: client_key_cb_options()
+ Options :: client_key_cb_options(any())
) ->
- {ok, PrivateKey :: public_key:private_key()} | {error, string()}.
+ {ok, public_key:private_key()} |
+ {ok, {ssh2_pubkey, PubKeyBlob :: binary()}} |
+ {error, string()}.
+%%%****************************************************************
+%%% Remembers that the the public key Key is a key for the host(s)
+%%% in the argument Host with the port Port.
+%%%
+%%% Due to compatibility reasons, the OTP/SSH application first
+%%% trys add_host_key/4 and then the old add_host_key/3
+
+-callback add_host_key(Host :: inet:ip_address() | inet:hostname()
+ | [inet:ip_address() | inet:hostname()],
+ Port :: inet:port_number(),
+ PublicKey :: public_key:public_key(),
+ Options :: client_key_cb_options(any())
+ ) ->
+ ok | {error, term()}.
+
+%%% is_host_key/3 is an old variant which is kept for compatibility.
+%%% Use is_host_key/4 in new constructions.
+
-callback add_host_key(Host :: string(),
PublicKey :: public_key:public_key(),
- Options :: client_key_cb_options()
+ Options :: client_key_cb_options(any())
) ->
- ok | {error, Error::term()}.
+ ok | {error, term()}.
+
+-optional_callbacks(
+ [add_host_key/3, add_host_key/4 % One in the pair be defined
+ ]).
+
+
+%%%****************************************************************
+%%% Sign the SigData with the *private* key corresponding to PubKeyBlob
+%%%
+
+-callback sign(PubKeyBlob :: binary(),
+ SigData :: binary(),
+ Options :: client_key_cb_options(any())) ->
+ Blob :: binary().
+
+-optional_callbacks([sign/3]).
+
diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl
index 00d1320a78..e00e78d6b7 100644
--- a/lib/ssh/src/ssh_connect.hrl
+++ b/lib/ssh/src/ssh_connect.hrl
@@ -206,6 +206,7 @@
-define(IXANY,39). %% Any char will restart after stop.
-define(IXOFF,40). %% Enable input flow control.
-define(IMAXBEL,41). %% Ring bell on input queue full.
+-define(IUTF8,42). %% Terminal input and output is assumed to be encoded in UTF-8.
-define(ISIG,50). %% Enable signals INTR, QUIT, [D]SUSP.
-define(ICANON,51). %% Canonicalize input lines.
-define(XCASE,52). %% Enable input and output of uppercase characters by
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index 099613d936..a966f7bbf1 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -42,9 +42,11 @@
%% Internal SSH application API
-export([channel_data/5,
- handle_msg/3,
+ handle_msg/4,
handle_stop/1,
+ open_channel/4,
+
channel_adjust_window_msg/2,
channel_close_msg/1,
channel_open_failure_msg/4,
@@ -57,12 +59,18 @@
channel_request_msg/4,
channel_success_msg/1,
+ request_global_msg/3,
request_failure_msg/0,
request_success_msg/1,
+ send_environment_vars/3,
+
encode_ip/1
]).
+%% For testing only
+-export([encode_pty_opts/1, decode_pty_opts/1]).
+
-type connection_ref() :: ssh:connection_ref().
-type channel_id() :: ssh:channel_id().
@@ -202,10 +210,22 @@ session_channel(ConnectionHandler, Timeout) ->
Result :: {ok, ssh:channel_id()} | {error, reason()} .
session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) ->
- case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>,
- InitialWindowSize,
- MaxPacketSize, Timeout) of
- {open, Channel} ->
+ open_channel(ConnectionHandler, "session", <<>>,
+ InitialWindowSize,
+ MaxPacketSize,
+ Timeout).
+
+%%--------------------------------------------------------------------
+%% Description: Opens a channel for the given type.
+%% --------------------------------------------------------------------
+open_channel(ConnectionHandler, Type, ChanData, Timeout) ->
+ open_channel(ConnectionHandler, Type, ChanData, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
+
+open_channel(ConnectionHandler, Type, ChanData, InitialWindowSize, MaxPacketSize, Timeout) ->
+ case ssh_connection_handler:open_channel(ConnectionHandler, Type, ChanData,
+ InitialWindowSize, MaxPacketSize,
+ Timeout) of
+ {open, Channel} ->
{ok, Channel};
Error ->
Error
@@ -304,7 +324,7 @@ adjust_window(ConnectionHandler, Channel, Bytes) ->
ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes).
%%--------------------------------------------------------------------
--spec setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> result() when
+-spec setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> success when
ConnectionRef :: ssh:connection_ref(),
ChannelId :: ssh:channel_id(),
Var :: string(),
@@ -314,12 +334,18 @@ adjust_window(ConnectionHandler, Channel, Bytes) ->
%%
%% Description: Environment variables may be passed to the shell/command to be
%% started later.
-%%--------------------------------------------------------------------
setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) ->
- ssh_connection_handler:request(ConnectionHandler, ChannelId,
- "env", true, [?string(Var), ?string(Value)], TimeOut).
-
-
+ setenv(ConnectionHandler, ChannelId, true, Var, Value, TimeOut).
+
+setenv(ConnectionHandler, ChannelId, WantReply, Var, Value, TimeOut) ->
+ case ssh_connection_handler:request(ConnectionHandler, ChannelId,
+ "env", WantReply,
+ [?string(Var), ?string(Value)], TimeOut) of
+ ok when WantReply == false ->
+ success;
+ Reply ->
+ Reply
+ end.
%%--------------------------------------------------------------------
-spec close(ConnectionRef, ChannelId) -> ok when
ConnectionRef :: ssh:connection_ref(),
@@ -376,6 +402,7 @@ ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) ->
proplists:get_value(pixel_height, TermData, PixHeight),
proplists:get_value(pty_opts, TermData, []), TimeOut
).
+
%%--------------------------------------------------------------------
%% Not yet officialy supported! The following functions are part of the
%% initial contributed ssh application. They are untested. Do we want them?
@@ -445,7 +472,7 @@ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId,
sender_channel = RemoteId,
initial_window_size = WindowSz,
maximum_packet_size = PacketSz},
- #connection{channel_cache = Cache} = Connection0, _) ->
+ #connection{channel_cache = Cache} = Connection0, _, _SSH) ->
#channel{remote_id = undefined} = Channel =
ssh_client_channel:cache_lookup(Cache, ChannelId),
@@ -463,22 +490,22 @@ handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId,
reason = Reason,
description = Descr,
lang = Lang},
- #connection{channel_cache = Cache} = Connection0, _) ->
+ #connection{channel_cache = Cache} = Connection0, _, _SSH) ->
Channel = ssh_client_channel:cache_lookup(Cache, ChannelId),
ssh_client_channel:cache_delete(Cache, ChannelId),
reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang});
-handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, Connection, _) ->
+handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId}, Connection, _, _SSH) ->
reply_msg(ChannelId, Connection, success);
-handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, Connection, _) ->
+handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId}, Connection, _, _SSH) ->
reply_msg(ChannelId, Connection, failure);
-handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, Connection, _) ->
+handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId}, Connection, _, _SSH) ->
reply_msg(ChannelId, Connection, {eof, ChannelId});
handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId},
- #connection{channel_cache = Cache} = Connection0, _) ->
+ #connection{channel_cache = Cache} = Connection0, _, _SSH) ->
case ssh_client_channel:cache_lookup(Cache, ChannelId) of
#channel{sent_close = Closed, remote_id = RemoteId,
@@ -511,18 +538,18 @@ handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId},
handle_msg(#ssh_msg_channel_data{recipient_channel = ChannelId,
data = Data},
- Connection, _) ->
+ Connection, _, _SSH) ->
channel_data_reply_msg(ChannelId, Connection, 0, Data);
handle_msg(#ssh_msg_channel_extended_data{recipient_channel = ChannelId,
data_type_code = DataType,
data = Data},
- Connection, _) ->
+ Connection, _, _SSH) ->
channel_data_reply_msg(ChannelId, Connection, DataType, Data);
handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId,
bytes_to_add = Add},
- #connection{channel_cache = Cache} = Connection, _) ->
+ #connection{channel_cache = Cache} = Connection, _, _SSH) ->
#channel{send_window_size = Size, remote_id = RemoteId} =
Channel0 = ssh_client_channel:cache_lookup(Cache, ChannelId),
@@ -541,7 +568,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type,
initial_window_size = WindowSz,
maximum_packet_size = PacketSz},
#connection{options = SSHopts} = Connection0,
- server) ->
+ server, _SSH) ->
MinAcceptedPackSz =
?GET_OPT(minimal_remote_max_packet_size, SSHopts),
@@ -566,10 +593,128 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type,
{[{connection_reply, FailMsg}], Connection0}
end;
+handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip",
+ sender_channel = RemoteId,
+ initial_window_size = WindowSize,
+ maximum_packet_size = PacketSize,
+ data = <<?DEC_BIN(ConnectedHost,_L1), ?UINT32(ConnectedPort),
+ ?DEC_BIN(_OriginHost,_L2), ?UINT32(_OriginPort)
+ >>
+ },
+ #connection{channel_cache = Cache,
+ channel_id_seed = ChId,
+ options = Options,
+ sub_system_supervisor = SubSysSup
+ } = C,
+ client, _SSH) ->
+ {ReplyMsg, NextChId} =
+ case ssh_connection_handler:retrieve(C, {tcpip_forward,ConnectedHost,ConnectedPort}) of
+ {ok, {ConnectToHost,ConnectToPort}} ->
+ case gen_tcp:connect(ConnectToHost, ConnectToPort, [{active,false}, binary]) of
+ {ok,Sock} ->
+ {ok,Pid} = ssh_subsystem_sup:start_channel(client, SubSysSup, self(),
+ ssh_tcpip_forward_client, ChId,
+ [Sock], undefined, Options),
+ ssh_client_channel:cache_update(Cache,
+ #channel{type = "forwarded-tcpip",
+ sys = "none",
+ local_id = ChId,
+ remote_id = RemoteId,
+ user = Pid,
+ recv_window_size = ?DEFAULT_WINDOW_SIZE,
+ recv_packet_size = ?DEFAULT_PACKET_SIZE,
+ send_window_size = WindowSize,
+ send_packet_size = PacketSize,
+ send_buf = queue:new()
+ }),
+ gen_tcp:controlling_process(Sock, Pid),
+ inet:setopts(Sock, [{active,once}]),
+ {channel_open_confirmation_msg(RemoteId, ChId,
+ ?DEFAULT_WINDOW_SIZE,
+ ?DEFAULT_PACKET_SIZE),
+ ChId + 1};
+
+ {error,Error} ->
+ {channel_open_failure_msg(RemoteId,
+ ?SSH_OPEN_CONNECT_FAILED,
+ io_lib:format("Forwarded connection refused: ~p",[Error]),
+ "en"),
+ ChId}
+ end;
+
+ undefined ->
+ {channel_open_failure_msg(RemoteId,
+ ?SSH_OPEN_CONNECT_FAILED,
+ io_lib:format("No forwarding ordered",[]),
+ "en"),
+ ChId}
+ end,
+ {[{connection_reply, ReplyMsg}], C#connection{channel_id_seed = NextChId}};
+
+handle_msg(#ssh_msg_channel_open{channel_type = "direct-tcpip",
+ sender_channel = RemoteId,
+ initial_window_size = WindowSize,
+ maximum_packet_size = PacketSize,
+ data = <<?DEC_BIN(HostToConnect,_L1), ?UINT32(PortToConnect),
+ ?DEC_BIN(_OriginatorIPaddress,_L2), ?UINT32(_OrignatorPort)
+ >>
+ },
+ #connection{channel_cache = Cache,
+ channel_id_seed = ChId,
+ options = Options,
+ sub_system_supervisor = SubSysSup
+ } = C,
+ server, _SSH) ->
+ {ReplyMsg, NextChId} =
+ case ?GET_OPT(tcpip_tunnel_in, Options) of
+ %% May add more to the option, like allowed ip/port pairs to connect to
+ false ->
+ {channel_open_failure_msg(RemoteId,
+ ?SSH_OPEN_CONNECT_FAILED,
+ "Forwarding disabled", "en"),
+ ChId};
+
+ true ->
+ case gen_tcp:connect(binary_to_list(HostToConnect), PortToConnect,
+ [{active,false}, binary]) of
+ {ok,Sock} ->
+ {ok,Pid} = ssh_subsystem_sup:start_channel(server, SubSysSup, self(),
+ ssh_tcpip_forward_srv, ChId,
+ [Sock], undefined, Options),
+ ssh_client_channel:cache_update(Cache,
+ #channel{type = "direct-tcpip",
+ sys = "none",
+ local_id = ChId,
+ remote_id = RemoteId,
+ user = Pid,
+ recv_window_size = ?DEFAULT_WINDOW_SIZE,
+ recv_packet_size = ?DEFAULT_PACKET_SIZE,
+ send_window_size = WindowSize,
+ send_packet_size = PacketSize,
+ send_buf = queue:new()
+ }),
+ gen_tcp:controlling_process(Sock, Pid),
+ inet:setopts(Sock, [{active,once}]),
+
+ {channel_open_confirmation_msg(RemoteId, ChId,
+ ?DEFAULT_WINDOW_SIZE,
+ ?DEFAULT_PACKET_SIZE),
+ ChId + 1};
+
+ {error,Error} ->
+ {channel_open_failure_msg(RemoteId,
+ ?SSH_OPEN_CONNECT_FAILED,
+ io_lib:format("Forwarded connection refused: ~p",[Error]),
+ "en"),
+ ChId}
+ end
+ end,
+ {[{connection_reply, ReplyMsg}], C#connection{channel_id_seed = NextChId}};
+
handle_msg(#ssh_msg_channel_open{channel_type = "session",
sender_channel = RemoteId},
Connection,
- client) ->
+ client, _SSH) ->
%% Client implementations SHOULD reject any session channel open
%% requests to make it more difficult for a corrupt server to attack the
%% client. See See RFC 4254 6.1.
@@ -578,7 +723,7 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session",
"Connection refused", "en"),
{[{connection_reply, FailMsg}], Connection};
-handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _) ->
+handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _, _SSH) ->
FailMsg = channel_open_failure_msg(RemoteId,
?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
"Not allowed", "en"),
@@ -587,7 +732,7 @@ handle_msg(#ssh_msg_channel_open{sender_channel = RemoteId}, Connection, _) ->
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "exit-status",
data = Data},
- Connection, _) ->
+ Connection, _, _SSH) ->
<<?UINT32(Status)>> = Data,
reply_msg(ChannelId, Connection, {exit_status, ChannelId, Status});
@@ -595,7 +740,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "exit-signal",
want_reply = false,
data = Data},
- #connection{channel_cache = Cache} = Connection0, _) ->
+ #connection{channel_cache = Cache} = Connection0, _, _SSH) ->
<<?DEC_BIN(SigName, _SigLen),
?BOOLEAN(_Core),
?DEC_BIN(Err, _ErrLen),
@@ -614,7 +759,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "xon-xoff",
want_reply = false,
data = Data},
- Connection, _) ->
+ Connection, _, _SSH) ->
<<?BOOLEAN(CDo)>> = Data,
reply_msg(ChannelId, Connection, {xon_xoff, ChannelId, CDo=/= 0});
@@ -622,7 +767,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "window-change",
want_reply = false,
data = Data},
- Connection0, _) ->
+ Connection0, _, _SSH) ->
<<?UINT32(Width),?UINT32(Height),
?UINT32(PixWidth), ?UINT32(PixHeight)>> = Data,
reply_msg(ChannelId, Connection0, {window_change, ChannelId,
@@ -632,7 +777,7 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "signal",
data = Data},
- Connection0, _) ->
+ Connection0, _, _SSH) ->
<<?DEC_BIN(SigName, _SigLen)>> = Data,
reply_msg(ChannelId, Connection0, {signal, ChannelId,
binary_to_list(SigName)});
@@ -641,29 +786,24 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "subsystem",
want_reply = WantReply,
data = Data},
- #connection{channel_cache = Cache} = Connection, server) ->
+ #connection{channel_cache = Cache} = Connection, server, _SSH) ->
<<?DEC_BIN(SsName,_SsLen)>> = Data,
#channel{remote_id=RemoteId} = Channel =
ssh_client_channel:cache_lookup(Cache, ChannelId),
Reply =
- try
- start_subsystem(SsName, Connection, Channel,
- {subsystem, ChannelId, WantReply, binary_to_list(SsName)})
- of
+ case start_subsystem(SsName, Connection, Channel,
+ {subsystem, ChannelId, WantReply, binary_to_list(SsName)}) of
{ok, Pid} ->
erlang:monitor(process, Pid),
ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}),
channel_success_msg(RemoteId);
{error,_Error} ->
channel_failure_msg(RemoteId)
- catch
- _:_ ->
- channel_failure_msg(RemoteId)
end,
{[{connection_reply,Reply}], Connection};
handle_msg(#ssh_msg_channel_request{request_type = "subsystem"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore subsystem requests. See RFC 4254 6.5.
{[], Connection};
@@ -671,31 +811,48 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "pty-req",
want_reply = WantReply,
data = Data},
- Connection, server) ->
+ Connection, server, SSH) ->
<<?DEC_BIN(BTermName,_TermLen),
?UINT32(Width),?UINT32(Height),
?UINT32(PixWidth), ?UINT32(PixHeight),
Modes/binary>> = Data,
TermName = binary_to_list(BTermName),
+ PtyOpts0 = decode_pty_opts(Modes),
+ PtyOpts = case SSH#ssh.c_version of
+ "SSH-2.0-PuTTY"++_ ->
+ %% If - peer client is PuTTY
+ %% - it asked for pty
+ %% - did not tell if LF->CRLF expansion is wanted
+ %% then
+ %% - do LF->CRLF expansion
+ case proplists:get_value(onlcr, PtyOpts0, undefined) of
+ undefined ->
+ [{onlcr,1} | PtyOpts0];
+ _ ->
+ PtyOpts0
+ end;
+ _ ->
+ PtyOpts0
+ end,
PtyRequest = {TermName, Width, Height,
- PixWidth, PixHeight, decode_pty_opts(Modes)},
+ PixWidth, PixHeight, PtyOpts},
handle_cli_msg(Connection, ChannelId,
{pty, ChannelId, WantReply, PtyRequest});
handle_msg(#ssh_msg_channel_request{request_type = "pty-req"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore pty requests. See RFC 4254 6.2.
{[], Connection};
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "shell",
want_reply = WantReply},
- Connection, server) ->
+ Connection, server, _SSH) ->
handle_cli_msg(Connection, ChannelId,
{shell, ChannelId, WantReply});
handle_msg(#ssh_msg_channel_request{request_type = "shell"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore shell requests. See RFC 4254 6.5.
{[], Connection};
@@ -703,13 +860,13 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "exec",
want_reply = WantReply,
data = Data},
- Connection, server) ->
+ Connection, server, _SSH) ->
<<?DEC_BIN(Command, _Len)>> = Data,
handle_cli_msg(Connection, ChannelId,
{exec, ChannelId, WantReply, binary_to_list(Command)});
handle_msg(#ssh_msg_channel_request{request_type = "exec"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore exec requests. See RFC 4254 6.5.
{[], Connection};
@@ -717,35 +874,79 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "env",
want_reply = WantReply,
data = Data},
- Connection, server) ->
+ Connection, server, _SSH) ->
<<?DEC_BIN(Var,_VarLen), ?DEC_BIN(Value,_ValLen)>> = Data,
handle_cli_msg(Connection, ChannelId,
{env, ChannelId, WantReply, Var, Value});
handle_msg(#ssh_msg_channel_request{request_type = "env"},
- Connection, client) ->
+ Connection, client, _SSH) ->
%% The client SHOULD ignore env requests.
{[], Connection};
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
- request_type = _Other,
- want_reply = WantReply},
- #connection{channel_cache = Cache} = Connection, _) ->
- if WantReply == true ->
- case ssh_client_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = RemoteId} ->
- FailMsg = channel_failure_msg(RemoteId),
- {[{connection_reply, FailMsg}], Connection};
- undefined -> %% Chanel has been closed
- {[], Connection}
- end;
- true ->
- {[], Connection}
+ want_reply = WantReply},
+ #connection{channel_cache = Cache} = Connection, _, _SSH) ->
+ %% Not a valid request_type. All valid types are handling the
+ %% parameter checking in their own clauses above.
+ %%
+ %% The special ReqType faulty_msg signals that something went
+ %% wrong found during decoding.
+ %%
+ %% RFC4254 5.4 says:
+ %% "If 'want reply' is FALSE, no response will be sent to the request.
+ %% Otherwise, the recipient responds with either
+ %% SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, or request-specific
+ %% continuation messages. If the request is not recognized or is not
+ %% supported for the channel, SSH_MSG_CHANNEL_FAILURE is returned."
+ %%
+ case ssh_client_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = RemoteId} when WantReply==true ->
+ FailMsg = channel_failure_msg(RemoteId),
+ {[{connection_reply, FailMsg}], Connection};
+ _ -> %% Channel has been closed or no reply is wanted
+ {[], Connection}
+ end;
+
+handle_msg(#ssh_msg_global_request{name = <<"tcpip-forward">>,
+ want_reply = WantReply,
+ data = <<?DEC_BIN(ListenAddrStr,_Len),?UINT32(ListenPort)>>},
+ #connection{options = Opts} = Connection, server, _SSH) ->
+ case ?GET_OPT(tcpip_tunnel_out, Opts) of
+ false ->
+ %% This daemon instance has not enabled tcpip_forwarding
+ {[{connection_reply, request_failure_msg()}], Connection};
+
+ true ->
+ Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
+ SubSysSup = proplists:get_value(subsystem_sup, Sups),
+ FwdSup = ssh_subsystem_sup:tcpip_fwd_supervisor(SubSysSup),
+ ConnPid = self(),
+ case ssh_tcpip_forward_acceptor:supervised_start(FwdSup,
+ {ListenAddrStr, ListenPort},
+ undefined,
+ "forwarded-tcpip", ssh_tcpip_forward_srv,
+ ConnPid) of
+ {ok,ListenPort} when WantReply==true ->
+ {[{connection_reply, request_success_msg(<<>>)}], Connection};
+
+ {ok,LPort} when WantReply==true ->
+ {[{connection_reply, request_success_msg(<<?UINT32(LPort)>>)}], Connection};
+
+ {error,_} when WantReply==true ->
+ {[{connection_reply, request_failure_msg()}], Connection};
+
+ _ when WantReply==true ->
+ {[{connection_reply, request_failure_msg()}], Connection};
+
+ _ ->
+ {[], Connection}
+ end
end;
handle_msg(#ssh_msg_global_request{name = _Type,
want_reply = WantReply,
- data = _Data}, Connection, _) ->
+ data = _Data}, Connection, _Role, _SSH) ->
if WantReply == true ->
FailMsg = request_failure_msg(),
{[{connection_reply, FailMsg}], Connection};
@@ -754,18 +955,29 @@ handle_msg(#ssh_msg_global_request{name = _Type,
end;
handle_msg(#ssh_msg_request_failure{},
- #connection{requests = [{_, From} | Rest]} = Connection, _) ->
+ #connection{requests = [{_, From} | Rest]} = Connection, _, _SSH) ->
+ {[{channel_request_reply, From, {failure, <<>>}}],
+ Connection#connection{requests = Rest}};
+
+handle_msg(#ssh_msg_request_failure{},
+ #connection{requests = [{_, From,_} | Rest]} = Connection, _, _SSH) ->
{[{channel_request_reply, From, {failure, <<>>}}],
Connection#connection{requests = Rest}};
handle_msg(#ssh_msg_request_success{data = Data},
- #connection{requests = [{_, From} | Rest]} = Connection, _) ->
+ #connection{requests = [{_, From} | Rest]} = Connection, _, _SSH) ->
+ {[{channel_request_reply, From, {success, Data}}],
+ Connection#connection{requests = Rest}};
+
+handle_msg(#ssh_msg_request_success{data = Data},
+ #connection{requests = [{_, From, Fun} | Rest]} = Connection0, _, _SSH) ->
+ Connection = Fun({success,Data}, Connection0),
{[{channel_request_reply, From, {success, Data}}],
Connection#connection{requests = Rest}};
handle_msg(#ssh_msg_disconnect{code = Code,
description = Description},
- Connection, _) ->
+ Connection, _, _SSH) ->
{disconnect, {Code, Description}, handle_stop(Connection)}.
@@ -847,8 +1059,13 @@ channel_success_msg(ChannelId) ->
%%%----------------------------------------------------------------
%%% request_*_msg(...)
-%%% Returns a #ssh_msg_....{} for request responses.
+%%% Returns a #ssh_msg_....{}
%%%
+request_global_msg(Name, WantReply, Data) ->
+ #ssh_msg_global_request{name = Name,
+ want_reply = WantReply,
+ data = Data}.
+
request_failure_msg() ->
#ssh_msg_request_failure{}.
@@ -919,7 +1136,7 @@ start_cli(#connection{options = Options,
no_cli ->
{error, cli_disabled};
{CbModule, Args} ->
- start_channel(CbModule, ChannelId, Args, SubSysSup, Exec, Options)
+ ssh_subsystem_sup:start_channel(server, SubSysSup, self(), CbModule, ChannelId, Args, Exec, Options)
end.
@@ -929,37 +1146,15 @@ start_subsystem(BinName, #connection{options = Options,
Name = binary_to_list(BinName),
case check_subsystem(Name, Options) of
{Callback, Opts} when is_atom(Callback), Callback =/= none ->
- start_channel(Callback, ChannelId, Opts, SubSysSup, Options);
- {Other, _} when Other =/= none ->
+ ssh_subsystem_sup:start_channel(server, SubSysSup, self(), Callback, ChannelId, Opts, undefined, Options);
+ {none, _} ->
+ {error, bad_subsystem};
+ {_, _} ->
{error, legacy_option_not_supported}
end.
%%% Helpers for starting cli/subsystems
-start_channel(Cb, Id, Args, SubSysSup, Opts) ->
- start_channel(Cb, Id, Args, SubSysSup, undefined, Opts).
-
-start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) ->
- ChannelSup = ssh_subsystem_sup:channel_supervisor(SubSysSup),
- case max_num_channels_not_exceeded(ChannelSup, Opts) of
- true ->
- case ssh_server_channel_sup:start_child(ChannelSup, Cb, Id, Args, Exec) of
- {error,{Error,_Info}} ->
- throw(Error);
- Others ->
- Others
- end;
- false ->
- throw(max_num_channels_exceeded)
- end.
-
-max_num_channels_not_exceeded(ChannelSup, Opts) ->
- MaxNumChannels = ?GET_OPT(max_channels, Opts),
- NumChannels = length([x || {_,_,worker,[ssh_server_channel]} <-
- supervisor:which_children(ChannelSup)]),
- %% Note that NumChannels is BEFORE starting a new one
- NumChannels < MaxNumChannels.
-
check_subsystem("sftp"= SsName, Options) ->
case ?GET_OPT(subsystems, Options) of
no_subsys -> % FIXME: Can 'no_subsys' ever be matched?
@@ -1153,6 +1348,8 @@ encode_pty_opts2([{ixoff,Value} | Opts]) ->
[?IXOFF, ?uint32(Value) | encode_pty_opts2(Opts)];
encode_pty_opts2([{imaxbel,Value} | Opts]) ->
[?IMAXBEL, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{iutf8,Value} | Opts]) ->
+ [?IUTF8, ?uint32(Value) | encode_pty_opts2(Opts)];
encode_pty_opts2([{isig,Value} | Opts]) ->
[?ISIG, ?uint32(Value) | encode_pty_opts2(Opts)];
encode_pty_opts2([{icanon,Value} | Opts]) ->
@@ -1247,6 +1444,7 @@ decode_pty_opts2(<<Code, ?UINT32(Value), Tail/binary>>) ->
?IXANY -> ixany;
?IXOFF -> ixoff;
?IMAXBEL -> imaxbel;
+ ?IUTF8 -> iutf8; % RFC 8160
?ISIG -> isig;
?ICANON -> icanon;
?XCASE -> xcase;
@@ -1298,13 +1496,13 @@ handle_cli_msg(C0, ChId, Reply0) ->
Ch0 = ssh_client_channel:cache_lookup(Cache, ChId),
case Ch0#channel.user of
undefined ->
- case (catch start_cli(C0, ChId)) of
+ case start_cli(C0, ChId) of
{ok, Pid} ->
erlang:monitor(process, Pid),
Ch = Ch0#channel{user = Pid},
ssh_client_channel:cache_update(Cache, Ch),
reply_msg(Ch, C0, Reply0);
- _Other ->
+ {error, _Error} ->
Reply = {connection_reply, channel_failure_msg(Ch0#channel.remote_id)},
{[Reply], C0}
end;
@@ -1315,6 +1513,10 @@ handle_cli_msg(C0, ChId, Reply0) ->
%%%----------------------------------------------------------------
%%%
+%%% TCP/IP forwarding
+
+%%%----------------------------------------------------------------
+%%%
%%% Request response handling on return to the calling ssh_connection_handler
%%% state machine.
%%%
@@ -1364,3 +1566,15 @@ request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid},
{[{channel_data, ChannelPid, Reply}], Connection}
end.
+%%%----------------------------------------------------------------
+send_environment_vars(ConnectionHandler, Channel, VarNames) ->
+ lists:foldl(
+ fun(Var, success) ->
+ case os:getenv(Var) of
+ false ->
+ success;
+ Value ->
+ setenv(ConnectionHandler, Channel, false,
+ Var, Value, infinity)
+ end
+ end, success, VarNames).
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 9a9b0aee79..805ec042a6 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -48,17 +48,23 @@
-export([start_connection/4,
available_hkey_algorithms/2,
open_channel/6,
+ start_channel/5,
+ handle_direct_tcpip/6,
request/6, request/7,
reply_request/3,
+ global_request/5,
send/5,
send_eof/2,
+ store/3,
+ retrieve/2,
info/1, info/2,
connection_info/2,
channel_info/3,
adjust_window/3, close/2,
disconnect/4,
get_print_info/1,
- set_sock_opts/2, get_sock_opts/2
+ set_sock_opts/2, get_sock_opts/2,
+ prohibited_sock_option/1
]).
-type connection_ref() :: ssh:connection_ref().
@@ -128,35 +134,29 @@ stop(ConnectionHandler)->
timeout()
) -> {ok, connection_ref()} | {error, term()}.
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-start_connection(client = Role, Socket, Options, Timeout) ->
+start_connection(Role, Socket, Options, Timeout) ->
try
- {ok, Pid} = sshc_sup:start_child([Role, Socket, Options]),
- ok = socket_control(Socket, Pid, Options),
- handshake(Pid, erlang:monitor(process,Pid), Timeout)
- catch
- exit:{noproc, _} ->
- {error, ssh_not_started};
- _:Error ->
- {error, Error}
- end;
-
-start_connection(server = Role, Socket, Options, Timeout) ->
- try
- case ?GET_OPT(parallel_login, Options) of
- true ->
- 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 ->
- ChildPid = start_the_connection_child(self(), Role, Socket, Options),
- handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout)
- end
+ case Role of
+ client ->
+ ChildPid = start_the_connection_child(self(), Role, Socket, Options),
+ handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout);
+ server ->
+ case ?GET_OPT(parallel_login, Options) of
+ true ->
+ 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 ->
+ ChildPid = start_the_connection_child(self(), Role, Socket, Options),
+ handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout)
+ end
+ end
catch
exit:{noproc, _} ->
{error, ssh_not_started};
@@ -178,6 +178,8 @@ disconnect(Code, DetailedText, Module, Line) ->
[{next_event, internal, {send_disconnect, Code, DetailedText, Module, Line}}]}).
%%--------------------------------------------------------------------
+%%% Open a channel in the connection to the peer, that is, do the ssh
+%%% signalling with the peer.
-spec open_channel(connection_ref(),
string(),
iodata(),
@@ -197,6 +199,22 @@ open_channel(ConnectionHandler,
Timeout}).
%%--------------------------------------------------------------------
+%%% Start a channel handling process in the superviser tree
+-spec start_channel(connection_ref(), atom(), channel_id(), list(), term()) ->
+ {ok, pid()} | {error, term()}.
+
+%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+start_channel(ConnectionHandler, CallbackModule, ChannelId, Args, Exec) ->
+ {ok, {SubSysSup,Role,Opts}} = call(ConnectionHandler, get_misc),
+ ssh_subsystem_sup:start_channel(Role, SubSysSup,
+ ConnectionHandler, CallbackModule, ChannelId,
+ Args, Exec, Opts).
+
+%%--------------------------------------------------------------------
+handle_direct_tcpip(ConnectionHandler, ListenHost, ListenPort, ConnectToHost, ConnectToPort, Timeout) ->
+ call(ConnectionHandler, {handle_direct_tcpip, ListenHost, ListenPort, ConnectToHost, ConnectToPort, Timeout}).
+
+%%--------------------------------------------------------------------
-spec request(connection_ref(),
pid(),
channel_id(),
@@ -235,6 +253,12 @@ reply_request(ConnectionHandler, Status, ChannelId) ->
cast(ConnectionHandler, {reply_request, Status, ChannelId}).
%%--------------------------------------------------------------------
+global_request(ConnectionHandler, Type, true, Data, Timeout) ->
+ call(ConnectionHandler, {global_request, Type, Data, Timeout});
+global_request(ConnectionHandler, Type, false, Data, _) ->
+ cast(ConnectionHandler, {global_request, Type, Data}).
+
+%%--------------------------------------------------------------------
-spec send(connection_ref(),
channel_id(),
non_neg_integer(),
@@ -324,14 +348,29 @@ close(ConnectionHandler, ChannelId) ->
%%--------------------------------------------------------------------
+store(ConnectionHandler, Key, Value) ->
+ cast(ConnectionHandler, {store,Key,Value}).
+
+retrieve(#connection{options=Opts}, Key) ->
+ try ?GET_INTERNAL_OPT(Key, Opts) of
+ Value ->
+ {ok,Value}
+ catch
+ error:{badkey,Key} ->
+ undefined
+ end;
+retrieve(ConnectionHandler, Key) ->
+ call(ConnectionHandler, {retrieve,Key}).
+
+%%--------------------------------------------------------------------
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
set_sock_opts(ConnectionRef, SocketOptions) ->
try lists:foldr(fun({Name,_Val}, Acc) ->
- case lists:member(Name, [active, deliver, mode, packet]) of
+ case prohibited_sock_option(Name) of
true -> [Name|Acc];
false -> Acc
end
- end, [], SocketOptions)
+ end, [], SocketOptions)
of
[] ->
call(ConnectionRef, {set_sock_opts,SocketOptions});
@@ -342,6 +381,12 @@ set_sock_opts(ConnectionRef, SocketOptions) ->
{error, badarg}
end.
+prohibited_sock_option(active) -> true;
+prohibited_sock_option(deliver) -> true;
+prohibited_sock_option(mode) -> true;
+prohibited_sock_option(packet) -> true;
+prohibited_sock_option(_) -> false.
+
%%--------------------------------------------------------------------
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
get_sock_opts(ConnectionRef, SocketGetOptions) ->
@@ -369,7 +414,8 @@ alg(ConnectionHandler) ->
| undefined,
auth_user :: string()
| undefined,
- connection_state :: #connection{},
+ connection_state :: #connection{}
+ | undefined,
latest_channel_id = 0 :: non_neg_integer()
| undefined,
transport_protocol :: atom()
@@ -408,8 +454,21 @@ alg(ConnectionHandler) ->
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
init_connection_handler(Role, Socket, Opts) ->
case init([Role, Socket, Opts]) of
- {ok, StartState, D} ->
+ {ok, StartState, D} when Role == server ->
+ process_flag(trap_exit, true),
+ gen_statem:enter_loop(?MODULE,
+ [], %%[{debug,[trace,log,statistics,debug]} ], %% []
+ StartState,
+ D);
+
+ {ok, StartState, D0=#data{connection_state=C}} when Role == client ->
process_flag(trap_exit, true),
+ Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
+ D = D0#data{connection_state =
+ C#connection{system_supervisor = proplists:get_value(system_sup, Sups),
+ sub_system_supervisor = proplists:get_value(subsystem_sup, Sups),
+ connection_supervisor = proplists:get_value(connection_sup, Sups)
+ }},
gen_statem:enter_loop(?MODULE,
[], %%[{debug,[trace,log,statistics,debug]} ], %% []
StartState,
@@ -497,7 +556,7 @@ init_ssh_record(Role, Socket, PeerAddr, Opts) ->
end,
case Role of
client ->
- PeerName = case ?GET_INTERNAL_OPT(host, Opts) of
+ PeerName = case ?GET_INTERNAL_OPT(host, Opts, element(1,PeerAddr)) of
PeerIP when is_tuple(PeerIP) ->
inet_parse:ntoa(PeerIP);
PeerName0 when is_atom(PeerName0) ->
@@ -610,8 +669,8 @@ handle_event(_, _Event, {init_error,Error}=StateName, D) ->
%%% ######## {hello, client|server} ####
%% The very first event that is sent when the we are set as controlling process of Socket
-handle_event(_, socket_control, {hello,_}=StateName, D) ->
- VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)),
+handle_event(_, socket_control, {hello,_}=StateName, #data{ssh_params = Ssh0} = D) ->
+ VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh0)),
send_bytes(VsnMsg, D),
case inet:getopts(Socket=D#data.socket, [recbuf]) of
{ok, [{recbuf,Size}]} ->
@@ -622,7 +681,8 @@ handle_event(_, socket_control, {hello,_}=StateName, D) ->
% be max ?MAX_PROTO_VERSION bytes:
{recbuf, ?MAX_PROTO_VERSION},
{nodelay,true}]),
- {keep_state, D#data{inet_initial_recbuf_size=Size}};
+ Time = ?GET_OPT(hello_timeout, Ssh0#ssh.opts, infinity),
+ {keep_state, D#data{inet_initial_recbuf_size=Size}, [{state_timeout,Time,no_hello_received}] };
Other ->
?call_disconnectfun_and_log_cond("Option return",
@@ -671,6 +731,10 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D0) ->
{stop, Shutdown, D}
end;
+handle_event(_, no_hello_received, {hello,_Role}=StateName, D0) ->
+ {Shutdown, D} =
+ ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, "No HELLO recieved", StateName, D0),
+ {stop, Shutdown, D};
%%% ######## {kexinit, client|server, init|renegotiate} ####
@@ -767,13 +831,13 @@ handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,cl
%%% ######## {new_keys, client|server} ####
%% First key exchange round:
-handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D) ->
- {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D0) ->
+ {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D0#data.ssh_params),
%% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1),
- %% send_bytes(ExtInfo, D),
+ %% send_bytes(ExtInfo, D0),
{MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1),
- send_bytes(MsgReq, D),
- {next_state, {ext_info,client,init}, D#data{ssh_params=Ssh}};
+ D = send_msg(MsgReq, D0#data{ssh_params = Ssh}),
+ {next_state, {ext_info,client,init}, D};
handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, D) ->
{ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
@@ -819,8 +883,8 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s
"ssh-userauth" ->
Ssh0 = #ssh{session_id=SessionId} = D0#data.ssh_params,
{ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
- send_bytes(Reply, D0),
- {next_state, {userauth,server}, D0#data{ssh_params = Ssh}};
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth,server}, D};
_ ->
{Shutdown, D} =
@@ -831,10 +895,12 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s
end;
handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client},
- #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) ->
+ #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = D0) ->
{Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0),
- send_bytes(Msg, State),
- {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}};
+ D = send_msg(Msg, D0#data{ssh_params = Ssh,
+ auth_user = Ssh#ssh.user
+ }),
+ {next_state, {userauth,client}, D};
%%% ######## {userauth, client|server} ####
@@ -850,8 +916,8 @@ handle_event(_,
%% Probably the very first userauth_request but we deny unauthorized login
{not_authorized, _, {Reply,Ssh}} =
ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0),
- send_bytes(Reply, D0),
- {keep_state, D0#data{ssh_params = Ssh}};
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {keep_state, D};
{"ssh-connection", "ssh-connection", Method} ->
%% Userauth request with a method like "password" or so
@@ -859,21 +925,24 @@ handle_event(_,
true ->
%% Yepp! we support this method
case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of
- {authorized, User, {Reply, Ssh}} ->
- send_bytes(Reply, D0),
- D0#data.starter ! ssh_connected,
- connected_fun(User, Method, D0),
+ {authorized, User, {Reply, Ssh1}} ->
+ D = #data{ssh_params=Ssh} =
+ send_msg(Reply, D0#data{ssh_params = Ssh1}),
+ D#data.starter ! ssh_connected,
+ connected_fun(User, Method, D),
{next_state, {connected,server},
- D0#data{auth_user = User,
- ssh_params = Ssh#ssh{authenticated = true}}};
+ D#data{auth_user=User,
+ %% Note: authenticated=true MUST NOT be sent
+ %% before send_msg!
+ ssh_params = Ssh#ssh{authenticated = true}}};
{not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" ->
retry_fun(User, Reason, D0),
- send_bytes(Reply, D0),
- {next_state, {userauth_keyboard_interactive,server}, D0#data{ssh_params = Ssh}};
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive,server}, D};
{not_authorized, {User, Reason}, {Reply, Ssh}} ->
retry_fun(User, Reason, D0),
- send_bytes(Reply, D0),
- {keep_state, D0#data{ssh_params = Ssh}}
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {keep_state, D}
end;
false ->
%% No we do not support this method (=/= none)
@@ -901,6 +970,7 @@ handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) ->
{keep_state, D};
handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_params = Ssh}) ->
+ ssh_auth:ssh_msg_userauth_result(success),
D#data.starter ! ssh_connected,
{next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}};
@@ -932,11 +1002,11 @@ handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName=
StateName, D0#data{ssh_params = Ssh}),
{stop, Shutdown, D};
{"keyboard-interactive", {Msg, Ssh}} ->
- send_bytes(Msg, D0),
- {next_state, {userauth_keyboard_interactive,client}, D0#data{ssh_params = Ssh}};
+ D = send_msg(Msg, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive,client}, D};
{_Method, {Msg, Ssh}} ->
- send_bytes(Msg, D0),
- {keep_state, D0#data{ssh_params = Ssh}}
+ D = send_msg(Msg, D0#data{ssh_params = Ssh}),
+ {keep_state, D}
end;
%%---- banner to client
@@ -951,39 +1021,46 @@ handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) -
%%% ######## {userauth_keyboard_interactive, client|server}
handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client},
- #data{ssh_params = Ssh0} = D) ->
+ #data{ssh_params = Ssh0} = D0) ->
case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of
{ok, {Reply, Ssh}} ->
- send_bytes(Reply, D),
- {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}};
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive_info_response,client}, D};
not_ok ->
- {next_state, {userauth,client}, D, [postpone]}
+ {next_state, {userauth,client}, D0, [postpone]}
end;
-handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) ->
- case ssh_auth:handle_userauth_info_response(Msg, D#data.ssh_params) of
- {authorized, User, {Reply, Ssh}} ->
- send_bytes(Reply, D),
+handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D0) ->
+ case ssh_auth:handle_userauth_info_response(Msg, D0#data.ssh_params) of
+ {authorized, User, {Reply, Ssh1}} ->
+ D = #data{ssh_params=Ssh} =
+ send_msg(Reply, D0#data{ssh_params = Ssh1}),
D#data.starter ! ssh_connected,
connected_fun(User, "keyboard-interactive", D),
{next_state, {connected,server}, D#data{auth_user = User,
+ %% Note: authenticated=true MUST NOT be sent
+ %% before send_msg!
ssh_params = Ssh#ssh{authenticated = true}}};
{not_authorized, {User, Reason}, {Reply, Ssh}} ->
- retry_fun(User, Reason, D),
- send_bytes(Reply, D),
- {next_state, {userauth,server}, D#data{ssh_params = Ssh}};
+ retry_fun(User, Reason, D0),
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth,server}, D};
{authorized_but_one_more, _User, {Reply, Ssh}} ->
- send_bytes(Reply, D),
- {next_state, {userauth_keyboard_interactive_extra,server}, D#data{ssh_params = Ssh}}
+ D = send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive_extra,server}, D}
end;
-handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D) ->
- {authorized, User, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response({extra,Msg}, D#data.ssh_params),
- send_bytes(Reply, D),
+handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D0) ->
+ {authorized, User, {Reply, Ssh1}} =
+ ssh_auth:handle_userauth_info_response({extra,Msg}, D0#data.ssh_params),
+ D = #data{ssh_params=Ssh} =
+ send_msg(Reply, D0#data{ssh_params = Ssh1}),
D#data.starter ! ssh_connected,
connected_fun(User, "keyboard-interactive", D),
{next_state, {connected,server}, D#data{auth_user = User,
+ %% Note: authenticated=true MUST NOT be sent
+ %% before send_msg!
ssh_params = Ssh#ssh{authenticated = true}}};
handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client},
@@ -1032,7 +1109,7 @@ handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) ->
handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) ->
{disconnect, _, RepliesCon} =
- ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName)),
+ ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName), D0#data.ssh_params),
{Actions,D} = send_replies(RepliesCon, D0),
disconnect_fun("Received disconnect: "++Desc, D),
{stop_and_reply, {shutdown,Desc}, Actions, D};
@@ -1052,7 +1129,7 @@ handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User,
event_queue = Qev0} = D0) ->
Role = role(StateName),
Rengotation = renegotiation(StateName),
- try ssh_connection:handle_msg(Msg, Connection0, Role) of
+ try ssh_connection:handle_msg(Msg, Connection0, Role, D0#data.ssh_params) of
{disconnect, Reason0, RepliesConn} ->
{Repls, D} = send_replies(RepliesConn, D0),
case {Reason0,Role} of
@@ -1093,17 +1170,17 @@ handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User,
end;
-handle_event(enter, _OldState, {connected,_}=State, D) ->
+handle_event(enter, OldState, {connected,_}=NewState, D) ->
%% Entering the state where re-negotiation is possible
- init_renegotiate_timers(State, D);
+ init_renegotiate_timers(OldState, NewState, D);
-handle_event(enter, _OldState, {ext_info,_,renegotiate}=State, D) ->
+handle_event(enter, OldState, {ext_info,_,renegotiate}=NewState, D) ->
%% Could be hanging in exit_info state if nothing else arrives
- init_renegotiate_timers(State, D);
+ init_renegotiate_timers(OldState, NewState, D);
-handle_event(enter, {connected,_}, State, D) ->
+handle_event(enter, {connected,_}=OldState, NewState, D) ->
%% Exiting the state where re-negotiation is possible
- pause_renegotiate_timers(State, D);
+ pause_renegotiate_timers(OldState, NewState, D);
handle_event(cast, force_renegotiate, StateName, D) ->
handle_event({timeout,renegotiate}, undefined, StateName, D);
@@ -1162,12 +1239,20 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTE
keep_state_and_data
end;
-handle_event(cast, {reply_request,success,ChannelId}, StateName, D) when ?CONNECTED(StateName) ->
+handle_event(cast, {reply_request,Resp,ChannelId}, StateName, D) when ?CONNECTED(StateName) ->
case ssh_client_channel:cache_lookup(cache(D), ChannelId) of
- #channel{remote_id = RemoteId} ->
- Msg = ssh_connection:channel_success_msg(RemoteId),
- update_inet_buffers(D#data.socket),
- {keep_state, send_msg(Msg,D)};
+ #channel{remote_id = RemoteId} when Resp== success ; Resp==failure ->
+ Msg =
+ case Resp of
+ success -> ssh_connection:channel_success_msg(RemoteId);
+ failure -> ssh_connection:channel_failure_msg(RemoteId)
+ end,
+ update_inet_buffers(D#data.socket),
+ {keep_state, send_msg(Msg,D)};
+
+ #channel{} ->
+ Details = io_lib:format("Unhandled reply in state ~p:~n~p", [StateName,Resp]),
+ ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D);
undefined ->
keep_state_and_data
@@ -1183,6 +1268,10 @@ handle_event(cast, {unknown,Data}, StateName, D) when ?CONNECTED(StateName) ->
Msg = #ssh_msg_unimplemented{sequence = Data},
{keep_state, send_msg(Msg,D)};
+handle_event(cast, {global_request, Type, Data}, StateName, D) when ?CONNECTED(StateName) ->
+ {keep_state, send_msg(ssh_connection:request_global_msg(Type,false,Data), D)};
+
+
%%% Previously handle_sync_event began here
handle_event({call,From}, get_print_info, StateName, D) ->
Reply =
@@ -1274,6 +1363,34 @@ handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName,
{keep_state, D, cond_set_idle_timer(D)}
end;
+handle_event({call,From}, {global_request, "tcpip-forward" = Type,
+ {ListenHost,ListenPort,ConnectToHost,ConnectToPort},
+ Timeout}, StateName, D0) when ?CONNECTED(StateName) ->
+ Id = make_ref(),
+ Data = <<?STRING(ListenHost), ?Euint32(ListenPort)>>,
+ Fun = fun({success, <<Port:32/unsigned-integer>>}, C) ->
+ Key = {tcpip_forward,ListenHost,Port},
+ Value = {ConnectToHost,ConnectToPort},
+ C#connection{options = ?PUT_INTERNAL_OPT({Key,Value}, C#connection.options)};
+ ({success, <<>>}, C) ->
+ Key = {tcpip_forward,ListenHost,ListenPort},
+ Value = {ConnectToHost,ConnectToPort},
+ C#connection{options = ?PUT_INTERNAL_OPT({Key,Value}, C#connection.options)};
+ (_, C) ->
+ C
+ end,
+ D = send_msg(ssh_connection:request_global_msg(Type, true, Data),
+ add_request(Fun, Id, From, D0)),
+ start_channel_request_timer(Id, From, Timeout),
+ {keep_state, D, cond_set_idle_timer(D)};
+
+handle_event({call,From}, {global_request, Type, Data, Timeout}, StateName, D0) when ?CONNECTED(StateName) ->
+ Id = make_ref(),
+ D = send_msg(ssh_connection:request_global_msg(Type, true, Data),
+ add_request(true, Id, From, D0)),
+ start_channel_request_timer(Id, From, Timeout),
+ {keep_state, D, cond_set_idle_timer(D)};
+
handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0)
when ?CONNECTED(StateName) ->
{Repls,D} = send_replies(ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From),
@@ -1291,6 +1408,13 @@ handle_event({call,From}, {eof, ChannelId}, StateName, D0)
{keep_state, D0, [{reply,From,{error,closed}}]}
end;
+handle_event({call,From}, get_misc, StateName,
+ #data{connection_state = #connection{options = Opts}} = D) when ?CONNECTED(StateName) ->
+ Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
+ SubSysSup = proplists:get_value(subsystem_sup, Sups),
+ Reply = {ok, {SubSysSup, role(StateName), Opts}},
+ {keep_state, D, [{reply,From,Reply}]};
+
handle_event({call,From},
{open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout},
StateName,
@@ -1347,6 +1471,17 @@ handle_event({call,From}, {close, ChannelId}, StateName, D0)
{keep_state_and_data, [{reply,From,ok}]}
end;
+handle_event(cast, {store,Key,Value}, _StateName, #data{connection_state=C0} = D) ->
+ C = C0#connection{options = ?PUT_INTERNAL_OPT({Key,Value}, C0#connection.options)},
+ {keep_state, D#data{connection_state = C}};
+
+handle_event({call,From}, {retrieve,Key}, _StateName, #data{connection_state=C}) ->
+ case retrieve(C, Key) of
+ {ok,Value} ->
+ {keep_state_and_data, [{reply,From,{ok,Value}}]};
+ _ ->
+ {keep_state_and_data, [{reply,From,undefined}]}
+ end;
%%===== Reception of encrypted bytes, decryption and framing
handle_event(info, {Proto, Sock, Info}, {hello,_}, #data{socket = Sock,
@@ -1492,8 +1627,13 @@ handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D) ->
end, [], Cache),
{keep_state, D, cond_set_idle_timer(D)};
-handle_event({timeout,idle_time}, _Data, _StateName, _D) ->
- {stop, {shutdown, "Timeout"}};
+handle_event({timeout,idle_time}, _Data, _StateName, D) ->
+ case ssh_client_channel:cache_info(num_entries, cache(D)) of
+ 0 ->
+ {stop, {shutdown, "Timeout"}};
+ _ ->
+ keep_state_and_data
+ end;
%%% So that terminate will be run when supervisor is shutdown
handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) ->
@@ -1515,6 +1655,32 @@ handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) ->
handle_event(info, check_cache, _, D) ->
{keep_state, D, cond_set_idle_timer(D)};
+handle_event(info, {fwd_connect_received, Sock, ChId, ChanCB}, StateName, #data{connection_state = Connection}) ->
+ #connection{options = Options,
+ channel_cache = Cache,
+ sub_system_supervisor = SubSysSup} = Connection,
+ Channel = ssh_client_channel:cache_lookup(Cache, ChId),
+ {ok,Pid} = ssh_subsystem_sup:start_channel(role(StateName), SubSysSup, self(), ChanCB, ChId, [Sock], undefined, Options),
+ ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}),
+ gen_tcp:controlling_process(Sock, Pid),
+ inet:setopts(Sock, [{active,once}]),
+ keep_state_and_data;
+
+handle_event({call,From},
+ {handle_direct_tcpip, ListenHost, ListenPort, ConnectToHost, ConnectToPort, _Timeout},
+ _StateName,
+ #data{connection_state = #connection{sub_system_supervisor=SubSysSup}}) ->
+ case ssh_tcpip_forward_acceptor:supervised_start(ssh_subsystem_sup:tcpip_fwd_supervisor(SubSysSup),
+ {ListenHost, ListenPort},
+ {ConnectToHost, ConnectToPort},
+ "direct-tcpip", ssh_tcpip_forward_client,
+ self()) of
+ {ok,LPort} ->
+ {keep_state_and_data, [{reply,From,{ok,LPort}}]};
+ {error,Error} ->
+ {keep_state_and_data, [{reply,From,{error,Error}}]}
+ end;
+
handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) ->
case unexpected_fun(UnexpectedMessage, D) of
report ->
@@ -1618,8 +1784,8 @@ terminate(shutdown, _StateName, D0) ->
D0),
close_transport(D);
-terminate(kill, _StateName, D) ->
- %% Got a kill signal
+terminate(killed, _StateName, D) ->
+ %% Got a killed signal
stop_subsystem(D),
close_transport(D);
@@ -1705,21 +1871,50 @@ start_the_connection_child(UserPid, Role, Socket, Options0) ->
Sups = ?GET_INTERNAL_OPT(supervisors, Options0),
ConnectionSup = proplists:get_value(connection_sup, Sups),
Options = ?PUT_INTERNAL_OPT({user_pid,UserPid}, Options0),
- {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Options]),
- ok = socket_control(Socket, Pid, Options),
+ InitArgs = [Role, Socket, Options],
+ {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, InitArgs),
+ ok = socket_control(Socket, Pid, Options), % transfer the Socket ownership in a controlled way.
Pid.
%%--------------------------------------------------------------------
%% Stopping
-stop_subsystem(#data{connection_state =
+stop_subsystem(#data{ssh_params =
+ #ssh{role = Role},
+ connection_state =
#connection{system_supervisor = SysSup,
- sub_system_supervisor = SubSysSup}}) when is_pid(SubSysSup) ->
- ssh_system_sup:stop_subsystem(SysSup, SubSysSup);
+ sub_system_supervisor = SubSysSup,
+ options = Opts}
+ }) when is_pid(SysSup) andalso is_pid(SubSysSup) ->
+ C = self(),
+ spawn(fun() ->
+ wait_until_dead(C, 10000),
+ case {Role, ?GET_INTERNAL_OPT(connected_socket,Opts,non_socket_started)} of
+ {server, non_socket_started} ->
+ ssh_system_sup:stop_subsystem(SysSup, SubSysSup);
+ {client, non_socket_started} ->
+ ssh_system_sup:stop_system(Role, SysSup);
+ {server, _Socket} ->
+ ssh_system_sup:stop_system(Role, SysSup);
+ {client, _Socket} ->
+ ssh_system_sup:stop_subsystem(SysSup, SubSysSup),
+ wait_until_dead(SubSysSup, 1000),
+ sshc_sup:stop_system(SysSup)
+ end
+ end);
stop_subsystem(_) ->
ok.
+wait_until_dead(Pid, Timeout) ->
+ Mref = erlang:monitor(process, Pid),
+ receive
+ {'DOWN', Mref, process, Pid, _Info} -> ok
+ after
+ Timeout -> ok
+ end.
+
+
close_transport(#data{transport_cb = Transport,
socket = Socket}) ->
try
@@ -1807,6 +2002,8 @@ call(FsmPid, Event, Timeout) ->
exit:{normal, _R} ->
{error, closed};
exit:{{shutdown, _R},_} ->
+ {error, closed};
+ exit:{shutdown, _R} ->
{error, closed}
end.
@@ -1941,6 +2138,11 @@ add_request(true, ChannelId, From, #data{connection_state =
#connection{requests = Requests0} =
Connection} = State) ->
Requests = [{ChannelId, From} | Requests0],
+ State#data{connection_state = Connection#connection{requests = Requests}};
+add_request(Fun, ChannelId, From, #data{connection_state =
+ #connection{requests = Requests0} =
+ Connection} = State) when is_function(Fun) ->
+ Requests = [{ChannelId, From, Fun} | Requests0],
State#data{connection_state = Connection#connection{requests = Requests}}.
new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} =
@@ -1959,15 +2161,15 @@ start_rekeying(Role, D0) ->
{next_state, {kexinit,Role,renegotiate}, D}.
-init_renegotiate_timers(State, D) ->
+init_renegotiate_timers(_OldState, NewState, D) ->
{RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
- {next_state, State, D, [{{timeout,renegotiate}, RekeyTimeout, none},
- {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none} ]}.
+ {next_state, NewState, D, [{{timeout,renegotiate}, RekeyTimeout, none},
+ {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none} ]}.
-pause_renegotiate_timers(State, D) ->
- {next_state, State, D, [{{timeout,renegotiate}, infinity, none},
- {{timeout,check_data_size}, infinity, none} ]}.
+pause_renegotiate_timers(_OldState, NewState, D) ->
+ {next_state, NewState, D, [{{timeout,renegotiate}, infinity, none},
+ {{timeout,check_data_size}, infinity, none} ]}.
check_data_rekeying(Role, D) ->
case inet:getstat(D#data.socket, [send_oct]) of
@@ -2343,20 +2545,22 @@ ssh_dbg_flags(disconnect) -> [c].
ssh_dbg_on(connections) -> dbg:tp(?MODULE, init_connection_handler, 3, x),
ssh_dbg_on(terminate);
ssh_dbg_on(connection_events) -> dbg:tp(?MODULE, handle_event, 4, x);
-ssh_dbg_on(renegotiation) -> dbg:tpl(?MODULE, init_renegotiate_timers, 2, x),
- dbg:tpl(?MODULE, pause_renegotiate_timers, 2, x),
+ssh_dbg_on(renegotiation) -> dbg:tpl(?MODULE, init_renegotiate_timers, 3, x),
+ dbg:tpl(?MODULE, pause_renegotiate_timers, 3, x),
dbg:tpl(?MODULE, check_data_rekeying_dbg, 2, x),
- dbg:tpl(?MODULE, start_rekeying, 2, x);
+ dbg:tpl(?MODULE, start_rekeying, 2, x),
+ dbg:tp(?MODULE, renegotiate, 1, x);
ssh_dbg_on(terminate) -> dbg:tp(?MODULE, terminate, 3, x);
ssh_dbg_on(disconnect) -> dbg:tpl(?MODULE, send_disconnect, 7, x).
ssh_dbg_off(disconnect) -> dbg:ctpl(?MODULE, send_disconnect, 7);
ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 3);
-ssh_dbg_off(renegotiation) -> dbg:ctpl(?MODULE, init_renegotiate_timers, 2),
- dbg:ctpl(?MODULE, pause_renegotiate_timers, 2),
+ssh_dbg_off(renegotiation) -> dbg:ctpl(?MODULE, init_renegotiate_timers, 3),
+ dbg:ctpl(?MODULE, pause_renegotiate_timers, 3),
dbg:ctpl(?MODULE, check_data_rekeying_dbg, 2),
- dbg:ctpl(?MODULE, start_rekeying, 2);
+ dbg:ctpl(?MODULE, start_rekeying, 2),
+ dbg:ctpg(?MODULE, renegotiate, 1);
ssh_dbg_off(connection_events) -> dbg:ctpg(?MODULE, handle_event, 4);
ssh_dbg_off(connections) -> dbg:ctpg(?MODULE, init_connection_handler, 3),
ssh_dbg_off(terminate).
@@ -2392,25 +2596,49 @@ ssh_dbg_format(connection_events, {call, {?MODULE,handle_event, [EventType, Even
];
ssh_dbg_format(connection_events, {return_from, {?MODULE,handle_event,4}, Ret}) ->
["Connection event result\n",
- io_lib:format("~p~n", [event_handler_result(Ret)])
+ io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret, #data{})])
];
-ssh_dbg_format(renegotiation, {call, {?MODULE,init_renegotiate_timers,[_State,D]}}) ->
- ["Renegotiation init\n",
- io_lib:format("rekey_limit: ~p ({ms,bytes})~ncheck_data_size: ~p (ms)~n",
- [?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
+ssh_dbg_format(renegotiation, {call, {?MODULE,init_renegotiate_timers,[OldState,NewState,D]}}) ->
+ ["Renegotiation: start timer (init_renegotiate_timers)\n",
+ io_lib:format("State: ~p --> ~p~n"
+ "rekey_limit: ~p ({ms,bytes})~n"
+ "check_data_size: ~p (ms)~n",
+ [OldState, NewState,
+ ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
?REKEY_DATA_TIMOUT])
];
-ssh_dbg_format(renegotiation, {call, {?MODULE,pause_renegotiate_timers,[_State,_D]}}) ->
- ["Renegotiation pause\n"];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,init_renegotiate_timers,3}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(renegotiation, {call, {?MODULE,renegotiate,[ConnectionHandler]}}) ->
+ ["Renegotiation: renegotiation forced\n",
+ io_lib:format("~p:renegotiate(~p) called~n",
+ [?MODULE,ConnectionHandler])
+ ];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,renegotiate,1}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(renegotiation, {call, {?MODULE,pause_renegotiate_timers,[OldState,NewState,_D]}}) ->
+ ["Renegotiation: pause timers\n",
+ io_lib:format("State: ~p --> ~p~n",
+ [OldState, NewState])
+ ];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,pause_renegotiate_timers,3}, _Ret}) ->
+ skip;
+
ssh_dbg_format(renegotiation, {call, {?MODULE,start_rekeying,[_Role,_D]}}) ->
- ["Renegotiation start rekeying\n"];
+ ["Renegotiation: start rekeying\n"];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,start_rekeying,2}, _Ret}) ->
+ skip;
+
ssh_dbg_format(renegotiation, {call, {?MODULE,check_data_rekeying_dbg,[SentSinceRekey, MaxSent]}}) ->
- ["Renegotiation check data sent\n",
+ ["Renegotiation: check size of data sent\n",
io_lib:format("TotalSentSinceRekey: ~p~nMaxBeforeRekey: ~p~nStartRekey: ~p~n",
[SentSinceRekey, MaxSent, SentSinceRekey >= MaxSent])
];
-
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,check_data_rekeying_dbg,2}, _Ret}) ->
+ skip;
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, StateName, D]}}) ->
@@ -2445,6 +2673,8 @@ ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, StateName, D]}}) -
[Reason, StateName, ExtraInfo, state_data2proplist(D)])
]
end;
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,terminate,3}, _Ret}) ->
+ skip;
ssh_dbg_format(disconnect, {call,{?MODULE,send_disconnect,
[Code, Reason, DetailedText, Module, Line, StateName, _D]}}) ->
@@ -2454,41 +2684,7 @@ ssh_dbg_format(disconnect, {call,{?MODULE,send_disconnect,
" DetailedText =~n"
" ~p",
[Module, Line, StateName, Code, Reason, lists:flatten(DetailedText)])
- ].
-
-
-event_handler_result({next_state, NextState, _NewData}) ->
- {next_state, NextState, "#data{}"};
-event_handler_result({next_state, NextState, _NewData, Actions}) ->
- {next_state, NextState, "#data{}", Actions};
-event_handler_result(R) ->
- state_callback_result(R).
+ ];
+ssh_dbg_format(renegotiation, {return_from, {?MODULE,send_disconnect,7}, _Ret}) ->
+ skip.
-state_callback_result({keep_state, _NewData}) ->
- {keep_state, "#data{}"};
-state_callback_result({keep_state, _NewData, Actions}) ->
- {keep_state, "#data{}", Actions};
-state_callback_result(keep_state_and_data) ->
- keep_state_and_data;
-state_callback_result({keep_state_and_data, Actions}) ->
- {keep_state_and_data, Actions};
-state_callback_result({repeat_state, _NewData}) ->
- {repeat_state, "#data{}"};
-state_callback_result({repeat_state, _NewData, Actions}) ->
- {repeat_state, "#data{}", Actions};
-state_callback_result(repeat_state_and_data) ->
- repeat_state_and_data;
-state_callback_result({repeat_state_and_data, Actions}) ->
- {repeat_state_and_data, Actions};
-state_callback_result(stop) ->
- stop;
-state_callback_result({stop, Reason}) ->
- {stop, Reason};
-state_callback_result({stop, Reason, _NewData}) ->
- {stop, Reason, "#data{}"};
-state_callback_result({stop_and_reply, Reason, Replies}) ->
- {stop_and_reply, Reason, Replies};
-state_callback_result({stop_and_reply, Reason, Replies, _NewData}) ->
- {stop_and_reply, Reason, Replies, "#data{}"};
-state_callback_result(R) ->
- R.
diff --git a/lib/ssh/src/ssh_controller.erl b/lib/ssh/src/ssh_controller.erl
new file mode 100644
index 0000000000..d8fbc8d0f2
--- /dev/null
+++ b/lib/ssh/src/ssh_controller.erl
@@ -0,0 +1,125 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles an ssh connection, e.i. both the
+%% setup SSH Transport Layer Protocol (RFC 4253), Authentication
+%% Protocol (RFC 4252) and SSH connection Protocol (RFC 4255)
+%% Details of the different protocols are
+%% implemented in ssh_transport.erl, ssh_auth.erl and ssh_connection.erl
+%% ----------------------------------------------------------------------
+
+-module(ssh_controller).
+
+-behaviour(gen_server).
+
+
+%%====================================================================
+%%% Exports
+%%====================================================================
+
+%%% API
+-export([start_system_subsystem/7,
+ stop_system/2
+ ]).
+
+%%% Start and stop
+-export([start_link/2
+ ]).
+
+-export([init/1,
+ handle_call/3,
+ handle_cast/2
+ ]).
+
+%%====================================================================
+%% Start / stop
+%%====================================================================
+
+start_link(Role, RegName) ->
+ gen_server:start_link({local,RegName}, ?MODULE, [Role], []).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+-define(TIMEOUT, 30000).
+
+start_system_subsystem(Controller, Sup, Host, Port, Profile, Options, ChildSpec) ->
+ gen_server:call(Controller, {start_system_subsystem, Sup, Host, Port, Profile, Options, ChildSpec}, ?TIMEOUT).
+
+stop_system(Controller, SysSup) ->
+ gen_server:call(Controller, {stop_system,SysSup}, ?TIMEOUT).
+
+%%====================================================================
+%% Internal process state
+%%====================================================================
+-record(data, {
+ role
+ }).
+
+%%====================================================================
+%% Intitialisation
+%%====================================================================
+init([Role]=_Args) ->
+ {ok, #data{role=Role}}.
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+handle_call({start_system_subsystem, Sup, Address, Port, Profile, Options, ChildSpec}, _From, D) ->
+ try
+ {ok,SystemSup0} =
+ case ssh_system_sup:system_supervisor(Address, Port, Profile) of
+ undefined ->
+ supervisor:start_child(Sup, ChildSpec);
+ Pid ->
+ {ok,Pid}
+ end,
+ {SystemSup0, ssh_system_sup:start_subsystem(SystemSup0, D#data.role, Address, Port, Profile, Options)}
+ of
+ {SystemSup, {ok,SubSysSup}} ->
+ {reply, {ok,{SystemSup,SubSysSup}}, D}
+ catch
+ C:E:S ->
+ {reply, {error,{failed,C,E,S}}, D}
+ end;
+
+
+handle_call({stop_system,SysSup}, _From, D) ->
+ try
+ case supervisor:which_children(SysSup) of
+ [] ->
+ ssh_system_sup:stop_system(D#data.role, SysSup);
+ _X ->
+ ok
+ end
+ catch
+ _:_ ->
+ %% Already stopped (?)
+ skip
+ end,
+ {reply, ok, D}.
+
+
+
+
+handle_cast(_Request, D) ->
+ {noreply, D}.
diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl
index 1809b14099..54a88a479f 100644
--- a/lib/ssh/src/ssh_dbg.erl
+++ b/lib/ssh/src/ssh_dbg.erl
@@ -54,6 +54,8 @@
start_tracer/0, start_tracer/1,
on/1, on/0,
off/1, off/0,
+ is_on/0,
+ is_off/0,
go_on/0,
%% Circular buffer
cbuf_start/0, cbuf_start/1,
@@ -65,11 +67,14 @@
]).
-export([shrink_bin/1,
- reduce_state/1,
+ reduce_state/2, reduce_state/3,
wr_record/3]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
+%% Internal apply_after:
+-export([ets_delete/2]).
+
-include("ssh.hrl").
-include("ssh_transport.hrl").
-include("ssh_connect.hrl").
@@ -82,12 +87,16 @@
-type trace_point() :: atom().
-type trace_points() :: [trace_point()].
+-type stack() :: list(term()).
-callback ssh_dbg_trace_points() -> trace_points().
-callback ssh_dbg_flags(trace_point()) -> [atom()].
-callback ssh_dbg_on(trace_point() | trace_points()) -> term().
-callback ssh_dbg_off(trace_point() | trace_points()) -> term().
--callback ssh_dbg_format(trace_point(), term()) -> iolist().
+-callback ssh_dbg_format(trace_point(), term()) -> iolist() | skip.
+-callback ssh_dbg_format(trace_point(), term(), stack()) -> {iolist() | skip, stack()}.
+
+-optional_callbacks([ssh_dbg_format/2, ssh_dbg_format/3]). % At least one of them are to be used
%%%================================================================
@@ -134,10 +143,13 @@ start_tracer(WriteFun, InitAcc) when is_function(WriteFun, 3) ->
%%%----------------------------------------------------------------
on() -> on(?ALL_DBG_TYPES).
on(Type) -> switch(on, Type).
-
+is_on() -> gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT).
+
off() -> off(?ALL_DBG_TYPES). % A bit overkill...
off(Type) -> switch(off, Type).
+is_off() -> ?ALL_DBG_TYPES -- is_on().
+
go_on() ->
IsOn = gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT),
@@ -155,17 +167,22 @@ shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T)));
shrink_bin(X) -> X.
%%%----------------------------------------------------------------
-%% Replace last element (the state) with "#<state-name>{}"
-reduce_state(T) ->
- try
- erlang:setelement(size(T),
- T,
- lists:concat(['#',element(1,element(size(T),T)),'{}'])
- )
- catch
- _:_ ->
- T
- end.
+%% Replace any occurence of {Name,...}, with "#Name{}"
+reduce_state(T, RecordExample) ->
+ Name = element(1, RecordExample),
+ Arity = size(RecordExample),
+ reduce_state(T, Name, Arity).
+
+%% Replace any occurence of {Name,...}, with "#Name{}"
+reduce_state(T, Name, Arity) when element(1,T) == Name,
+ size(T) == Arity ->
+ lists:concat(['#',Name,'{}']);
+reduce_state(L, Name, Arity) when is_list(L) ->
+ [reduce_state(E,Name,Arity) || E <- L];
+reduce_state(T, Name, Arity) when is_tuple(T) ->
+ list_to_tuple( reduce_state(tuple_to_list(T),Name,Arity) );
+reduce_state(X, _, _) ->
+ X.
%%%================================================================
-record(data, {
@@ -174,8 +191,42 @@ reduce_state(T) ->
%%%----------------------------------------------------------------
init(_) ->
+ new_table(),
{ok, #data{}}.
+
+new_table() ->
+ try
+ ets:new(?MODULE, [public, named_table]),
+ ok
+ catch
+ exit:badarg ->
+ ok
+ end.
+
+
+get_proc_stack(Pid) when is_pid(Pid) ->
+ try ets:lookup_element(?MODULE, Pid, 2)
+ catch
+ error:badarg ->
+ %% Non-existing item
+ new_proc(Pid),
+ ets:insert(?MODULE, {Pid,[]}),
+ []
+ end.
+
+
+put_proc_stack(Pid, Data) when is_pid(Pid),
+ is_list(Data) ->
+ ets:insert(?MODULE, {Pid,Data}).
+
+
+new_proc(Pid) when is_pid(Pid) ->
+ gen_server:cast(?SERVER, {new_proc,Pid}).
+
+ets_delete(Tab, Key) ->
+ catch ets:delete(Tab, Key).
+
%%%----------------------------------------------------------------
handle_call({switch,on,Types}, _From, D) ->
NowOn = lists:usort(Types ++ D#data.types_on),
@@ -196,10 +247,20 @@ handle_call(C, _From, D) ->
{reply, {error,{unknown_call,C}}, D}.
+handle_cast({new_proc,Pid}, D) ->
+ monitor(process, Pid),
+ {noreply, D};
+
handle_cast(C, D) ->
io:format('*** Unknown cast: ~p~n',[C]),
{noreply, D}.
+
+handle_info({'DOWN', _MonitorRef, process, Pid, _Info}, D) ->
+ %% Universal real-time synchronization (there might be dbg msgs in the queue to the tracer):
+ timer:apply_after(20000, ?MODULE, ets_delete, [?MODULE, Pid]),
+ {noreply, D};
+
handle_info(C, D) ->
io:format('*** Unknown info: ~p~n',[C]),
{noreply, D}.
@@ -285,34 +346,22 @@ switch(X, Types) when is_list(Types) ->
%%% {send,Msg,To}
%%% {'receive',Msg}
-trace_pid({trace,Pid,_}) -> Pid;
-trace_pid({trace,Pid,_,_}) -> Pid;
-trace_pid({trace,Pid,_,_,_}) -> Pid;
-trace_pid({trace,Pid,_,_,_,_}) -> Pid;
-trace_pid({trace,Pid,_,_,_,_,_}) -> Pid;
-trace_pid({trace_ts,Pid,_,_TS}) -> Pid;
-trace_pid({trace_ts,Pid,_,_,_TS}) -> Pid;
-trace_pid({trace_ts,Pid,_,_,_,_TS}) -> Pid;
-trace_pid({trace_ts,Pid,_,_,_,_,_TS}) -> Pid;
-trace_pid({trace_ts,Pid,_,_,_,_,_,_TS}) -> Pid.
-
-trace_ts({trace_ts,_Pid,_,TS}) -> ts(TS);
-trace_ts({trace_ts,_Pid,_,_,TS}) -> ts(TS);
-trace_ts({trace_ts,_Pid,_,_,_,TS}) -> ts(TS);
-trace_ts({trace_ts,_Pid,_,_,_,_,TS}) -> ts(TS);
-trace_ts({trace_ts,_Pid,_,_,_,_,_,TS}) -> ts(TS);
-trace_ts(_) -> "-".
-
-trace_info({trace,_Pid,A}) -> A;
-trace_info({trace,_Pid,A,B}) -> {A,B};
-trace_info({trace,_Pid,A,B,C}) -> {A,B,C};
-trace_info({trace,_Pid,A,B,C,D}) -> {A,B,C,D};
-trace_info({trace,_Pid,A,B,C,D,E}) -> {A,B,C,D,E};
-trace_info({trace_ts,_Pid,A,_TS}) -> A;
-trace_info({trace_ts,_Pid,A,B,_TS}) -> {A,B};
-trace_info({trace_ts,_Pid,A,B,C,_TS}) -> {A,B,C};
-trace_info({trace_ts,_Pid,A,B,C,D,_TS}) -> {A,B,C,D};
-trace_info({trace_ts,_Pid,A,B,C,D,E,_TS}) -> {A,B,C,D,E}.
+%% Pick 2nd element, the Pid
+trace_pid(T) when element(1,T)==trace
+ ; element(1,T)==trace_ts ->
+ element(2,T).
+
+%% Pick last element, the Time Stamp, and format it
+trace_ts(T) when element(1,T)==trace_ts ->
+ ts( element(size(T), T) ).
+
+%% Make a tuple of all elements but the 1st, 2nd and last
+trace_info(T) ->
+ case tuple_to_list(T) of
+ [trace,_Pid | Info] -> list_to_tuple(Info);
+ [trace_ts,_Pid | InfoTS] -> list_to_tuple(
+ lists:droplast(InfoTS))
+ end.
try_all_types_in_all_modules(TypesOn, Arg, WriteFun, Acc0) ->
@@ -320,20 +369,60 @@ try_all_types_in_all_modules(TypesOn, Arg, WriteFun, Acc0) ->
TS = trace_ts(Arg),
PID = trace_pid(Arg),
INFO = trace_info(Arg),
- lists:foldl(
- fun(Type, Acc1) ->
- lists:foldl(
- fun(SshMod,Acc) ->
- try WriteFun("~n~s ~p ~s~n",
- [lists:flatten(TS),
- PID,
- lists:flatten(SshMod:ssh_dbg_format(Type, INFO))],
- Acc)
- catch
- _:_ -> Acc
- end
- end, Acc1, SshModules)
- end, Acc0, TypesOn).
+ Acc =
+ lists:foldl(
+ fun(Type, Acc1) ->
+ lists:foldl(
+ fun(SshMod,Acc) ->
+ try
+ %% First, call without stack
+ SshMod:ssh_dbg_format(Type, INFO)
+ of
+ skip ->
+ %% Don't try to print this later
+ written;
+ Txt when is_list(Txt) ->
+ write_txt(WriteFun, TS, PID, Txt)
+ catch
+ error:E when E==undef ; E==function_clause ; element(1,E)==case_clause ->
+ try
+ %% then, call with stack
+ STACK = get_proc_stack(PID),
+ SshMod:ssh_dbg_format(Type, INFO, STACK)
+ of
+ {skip, NewStack} ->
+ %% Don't try to print this later
+ put_proc_stack(PID, NewStack),
+ written;
+ {Txt, NewStack} when is_list(Txt) ->
+ put_proc_stack(PID, NewStack),
+ write_txt(WriteFun, TS, PID, Txt)
+ catch
+ _:_ ->
+ %% and finally, signal for special formatting
+ %% if noone else formats it
+ Acc
+ end
+ end
+ end, Acc1, SshModules)
+ end, Acc0, TypesOn),
+ case Acc of
+ Acc0 ->
+ %% INFO :: any()
+ WriteFun("~n~s ~p DEBUG~n~p~n", [lists:flatten(TS),PID,INFO], Acc0);
+ written ->
+ Acc0
+ end.
+
+
+
+write_txt(WriteFun, TS, PID, Txt) when is_list(Txt) ->
+ WriteFun("~n~s ~p ~ts~n",
+ [lists:flatten(TS),
+ PID,
+ lists:flatten(Txt)],
+ written % this is returned
+ ).
%%%----------------------------------------------------------------
wr_record(T, Fs, BL) when is_tuple(T) ->
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index 510269bbb1..8f12b617bb 100644
--- a/lib/ssh/src/ssh_file.erl
+++ b/lib/ssh/src/ssh_file.erl
@@ -24,361 +24,663 @@
-module(ssh_file).
--behaviour(ssh_server_key_api).
--behaviour(ssh_client_key_api).
-
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/file.hrl").
-include("ssh.hrl").
--export([host_key/2,
- user_key/2,
- is_host_key/4,
- add_host_key/3,
- is_auth_key/3]).
-
-
--export_type([system_dir_daemon_option/0,
- user_dir_common_option/0,
- user_dir_fun_common_option/0,
- pubkey_passphrase_client_options/0
- ]).
+%% experimental:
+-export([decode_ssh_file/4]).
+%%%--------------------- server exports ---------------------------
+-behaviour(ssh_server_key_api).
+-export([host_key/2, is_auth_key/3]).
+-export_type([system_dir_daemon_option/0]).
-type system_dir_daemon_option() :: {system_dir, string()}.
--type user_dir_common_option() :: {user_dir, string()}.
--type user_dir_fun_common_option() :: {user_dir_fun, user2dir()}.
--type user2dir() :: fun((RemoteUserName::string()) -> UserDir :: string()) .
+%%%--------------------- client exports ---------------------------
+-behaviour(ssh_client_key_api).
+-export([is_host_key/5, user_key/2, add_host_key/4]).
+-export_type([pubkey_passphrase_client_options/0]).
-type pubkey_passphrase_client_options() :: {dsa_pass_phrase, string()}
| {rsa_pass_phrase, string()}
%% Not yet implemented: | {ed25519_pass_phrase, string()}
%% Not yet implemented: | {ed448_pass_phrase, string()}
| {ecdsa_pass_phrase, string()} .
+%%%--------------------- common exports ---------------------------
+-export_type([user_dir_common_option/0,
+ user_dir_fun_common_option/0
+ ]).
--define(PERM_700, 8#700).
--define(PERM_644, 8#644).
+-type user_dir_common_option() :: {user_dir, string()}.
+-type user_dir_fun_common_option() :: {user_dir_fun, user2dir()}.
+-type user2dir() :: fun((RemoteUserName::string()) -> UserDir :: string()) .
+-type optimize_key_lookup() :: {optimize, time|space} .
+%%%================================================================
+%%%
%%% API
+%%%
+
+%%%---------------- SERVER API ------------------------------------
+-spec host_key(Algorithm, Options) -> Result when
+ Algorithm :: ssh:pubkey_alg(),
+ Result :: {ok, public_key:private_key()} | {error, term()},
+ Options :: ssh_server_key_api:daemon_key_cb_options(none()).
-%% Used by server
host_key(Algorithm, Opts) ->
- File = file_name(system, file_base_name(Algorithm), Opts),
- %% We do not expect host keys to have pass phrases
- %% so probably we could hardcod Password = ignore, but
- %% we keep it as an undocumented option for now.
- Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore),
- case decode(File, Password) of
- {ok,Key} ->
- check_key_type(Key, Algorithm);
- {error,DecodeError} ->
- {error,DecodeError}
- end.
+ read_ssh_key_file(system, private, Algorithm, Opts).
+
+%%%................................................................
+-spec is_auth_key(Key, User, Options) -> boolean() when
+ Key :: public_key:public_key(),
+ User :: string(),
+ Options :: ssh_server_key_api:daemon_key_cb_options(optimize_key_lookup()).
+
+is_auth_key(Key0, User, Opts) ->
+ Dir = ssh_dir({remoteuser,User}, Opts),
+ ok = assure_file_mode(Dir, user_read),
+ KeyType = normalize_alg(
+ erlang:atom_to_binary(ssh_transport:public_algo(Key0), latin1)),
+ Key = encode_key(Key0),
+ lookup_auth_keys(KeyType, Key, filename:join(Dir,"authorized_keys"), Opts)
+ orelse
+ lookup_auth_keys(KeyType, Key, filename:join(Dir,"authorized_keys2"), Opts).
+
+%%%---------------- CLIENT API ------------------------------------
+-spec user_key(Algorithm, Options) -> Result when
+ Algorithm :: ssh:pubkey_alg(),
+ Result :: {ok, public_key:private_key()} |
+ {error, string()},
+ Options :: ssh_client_key_api:client_key_cb_options(none()).
-is_auth_key(Key, User,Opts) ->
- case lookup_user_key(Key, User, Opts) of
- {ok, Key} ->
- true;
- _ ->
- false
+user_key(Algorithm, Opts) ->
+ read_ssh_key_file(user, private, Algorithm, Opts).
+
+%%%................................................................
+%%% New style (with port number)
+-spec is_host_key(Key, Host, Port, Algorithm, Options) -> Result when
+ Key :: public_key:public_key(),
+ Host :: inet:ip_address() | inet:hostname() | [inet:ip_address() | inet:hostname()],
+ Port :: inet:port_number(),
+ Algorithm :: ssh:pubkey_alg(),
+ Options :: ssh_client_key_api:client_key_cb_options(optimize_key_lookup()),
+ Result :: boolean() | {error, term()} .
+
+is_host_key(Key0, Hosts0, Port, Algorithm, Opts) ->
+ Dir = ssh_dir(user, Opts),
+ File = filename:join(Dir, "known_hosts"),
+ Hosts = [list_to_binary(H) || H <- normalize_hosts_list(Hosts0, Port)],
+ KeyType = normalize_alg(erlang:atom_to_binary(Algorithm, latin1)),
+ Key = encode_key(Key0),
+ ok = assure_file_mode(File, user_read),
+ lookup_host_keys(Hosts, KeyType, Key, File, Opts).
+
+%%%----------------------------------------------------------------
+-spec add_host_key(Host, Port, Key, Options) -> Result when
+ Host :: inet:ip_address() | inet:hostname()
+ | [inet:ip_address() | inet:hostname()],
+ Port :: inet:port_number(),
+ Key :: public_key:public_key(),
+ Options :: ssh_client_key_api:client_key_cb_options(none()),
+ Result :: ok | {error, term()}.
+
+add_host_key(Hosts0, Port, Key, Opts) ->
+ File = file_name(user, "known_hosts", Opts),
+ assure_file_mode(File, user_write),
+ case file:open(File, [write,append]) of
+ {ok, Fd} ->
+ KeyType = erlang:atom_to_binary(ssh_transport:public_algo(Key), latin1),
+ EncKey = ssh_message:ssh2_pubkey_encode(Key),
+ Hosts1 = normalize_hosts_list(Hosts0, Port),
+ SshBin =
+ iolist_to_binary(["\n", % Just in case the last line is not terminated by \n
+ lists:join(",", Hosts1), " ",
+ KeyType," ",base64:encode(iolist_to_binary(EncKey)),
+ "\n"]),
+ Res = file:write(Fd, SshBin),
+ file:close(Fd),
+ Res;
+ {error,Error} ->
+ {error,{add_host_key,Error}}
end.
-
-%% Used by client
-is_host_key(Key, PeerName, Algorithm, Opts) ->
- case lookup_host_key(Key, PeerName, Algorithm, Opts) of
- {ok, Key} ->
- true;
- _ ->
- false
+%%%================================================================
+%%%
+%%% Local functions
+%%%
+
+%%%---------------- SERVER FUNCTIONS ------------------------------
+
+lookup_auth_keys(KeyType, Key, File, Opts) ->
+ case get_kb_option(optimize, Opts, time) of
+ time ->
+ case file:read_file(File) of
+ {ok,Bin} ->
+ Lines = binary:split(Bin, <<"\n">>, [global,trim_all]),
+ find_key(KeyType, Key, Lines);
+ _ ->
+ false
+ end;
+ space ->
+ case file:open(File, [read, binary]) of
+ {ok, Fd} ->
+ Result =
+ read_test_loop(Fd,
+ fun(Line) ->
+ find_key(KeyType, Key, [Line])
+ end),
+ file:close(Fd),
+ Result;
+ {error,_Error} ->
+ false
+ end;
+ Other ->
+ {error,{is_auth_key,{opt,Other}}}
end.
-user_key(Algorithm, Opts) ->
- File = file_name(user, identity_key_filename(Algorithm), Opts),
- Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore),
- case decode(File, Password) of
- {ok, Key} ->
- check_key_type(Key, Algorithm);
- Error ->
- Error
- end.
+
+find_key(KeyType, Key, [<<"#",_/binary>> | Lines]) ->
+ find_key(KeyType, Key, Lines);
+find_key(KeyType, Key, [Line | Lines]) ->
+ try
+ [E1,E2|Es] = binary:split(Line, <<" ">>, [global,trim_all]),
+ [normalize_alg(E1), normalize_alg(E2) | Es] % KeyType is in first or second element
+ of
+ [_Options, KeyType, Key | _Comment] ->
+ true;
+ [KeyType, Key | _Comment] ->
+ true;
+ _ ->
+ find_key(KeyType, Key, Lines)
+ catch
+ _:_ ->
+ find_key(KeyType, Key, Lines)
+ end;
+find_key(_, _, _) ->
+ false.
-%% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-check_key_type(Key, Algorithm) ->
- case ssh_transport:valid_key_sha_alg(Key,Algorithm) of
- true -> {ok,Key};
- false -> {error,bad_keytype_in_file}
- end.
+%%%---------------- CLIENT FUNCTIONS ------------------------------
+
+normalize_alg(<<"rsa-sha2-",_/binary>>) -> <<"ssh-rsa">>;
+normalize_alg(X) -> X.
+
+%%%--------------------------------
+normalize_hosts_list(Hosts, Port) when is_list(hd(Hosts)) ->
+ lists:reverse(
+ lists:foldl(fun(H0, Acc) ->
+ H1s = add_ip(replace_localhost(H0)),
+ Hs = case Port of
+ 22 -> H1s;
+ _ -> [lists:concat(["[",Hx,"]:",Port]) || Hx <- H1s]
+ end,
+ lists:foldl(
+ fun(Hy, Acc2) ->
+ case lists:member(Hy, Acc2) of
+ true ->
+ Acc2;
+ false ->
+ [Hy|Acc2]
+ end
+ end, Acc, Hs)
+ end, [], Hosts));
+normalize_hosts_list(Hosts, Port) ->
+ normalize_hosts_list([Hosts], Port).
+
+replace_localhost(any) ->
+ replace_localhost("localhost");
+replace_localhost(loopback) ->
+ replace_localhost("localhost");
+replace_localhost("localhost") ->
+ {ok, Hostname} = inet:gethostname(),
+ Hostname;
+replace_localhost(H) when is_atom(H) ->
+ replace_localhost(atom_to_list(H));
+replace_localhost(Host) ->
+ Host.
-file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key";
-file_base_name('rsa-sha2-256' ) -> "ssh_host_rsa_key";
-file_base_name('rsa-sha2-384' ) -> "ssh_host_rsa_key";
-file_base_name('rsa-sha2-512' ) -> "ssh_host_rsa_key";
-file_base_name('ssh-dss' ) -> "ssh_host_dsa_key";
-file_base_name('ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key";
-file_base_name('ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key";
-file_base_name('ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key";
-file_base_name('ssh-ed25519' ) -> "ssh_host_ed25519_key";
-file_base_name('ssh-ed448' ) -> "ssh_host_ed448_key";
-file_base_name(_ ) -> "ssh_host_key".
-
-decode(File, Password) ->
- try {ok, decode_ssh_file(read_ssh_file(File), Password)}
- catch
- throw:Reason ->
- {error, Reason};
- error:Reason ->
- {error, Reason}
- end.
-
-read_ssh_file(File) ->
- {ok, Bin} = file:read_file(File),
- Bin.
-
-%% Public key
-decode_ssh_file(SshBin, public_key) ->
- public_key:ssh_decode(SshBin, public_key);
-
-%% Private Key
-decode_ssh_file(Pem, Password) ->
- case public_key:pem_decode(Pem) of
- [{_, _, not_encrypted} = Entry] ->
- public_key:pem_entry_decode(Entry);
- [Entry] when Password =/= ignore ->
- public_key:pem_entry_decode(Entry, Password);
- _ ->
- throw("No pass phrase provided for private key file")
+add_ip(IP) when is_tuple(IP) ->
+ [ssh_connection:encode_ip(IP)];
+add_ip(Host) ->
+ case inet:getaddr(Host, inet) of
+ {ok, Addr} ->
+ case ssh_connection:encode_ip(Addr) of
+ false -> [Host];
+ Host -> [Host];
+ IPString -> [Host,IPString]
+ end;
+ _ -> [Host]
end.
-
-%% lookup_host_key
-%% return {ok, Key(s)} or {error, not_found}
-%%
+%%%--------------------------------
+encode_key(Key) ->
+ base64:encode(
+ iolist_to_binary(
+ ssh_message:ssh2_pubkey_encode(Key))).
-lookup_host_key(KeyToMatch, Host, Alg, Opts) ->
- Host1 = replace_localhost(Host),
- do_lookup_host_key(KeyToMatch, Host1, Alg, Opts).
-
-
-add_host_key(Host, Key, Opts) ->
- Host1 = add_ip(replace_localhost(Host)),
- KnownHosts = file_name(user, "known_hosts", Opts),
- case file:open(KnownHosts, [write,append]) of
- {ok, Fd} ->
- ok = file:change_mode(KnownHosts, ?PERM_644),
- Res = add_key_fd(Fd, Host1, Key),
- file:close(Fd),
- Res;
- Error ->
- Error
+%%%--------------------------------
+read_test_loop(Fd, Test) ->
+ case io:get_line(Fd, '') of
+ eof ->
+ file:close(Fd),
+ false;
+ {error,Error} ->
+ %% Rare... For example NFS errors
+ {error,Error};
+ Line0 ->
+ case binary:split(Line0, <<"\n">>, [global,trim_all]) of % remove trailing \n
+ [Line] ->
+ case Test(Line) of
+ false ->
+ read_test_loop(Fd, Test);
+ Other ->
+ Other
+ end;
+ _ ->
+ read_test_loop(Fd, Test)
+ end
end.
-
-lookup_user_key(Key, User, Opts) ->
- SshDir = ssh_dir({remoteuser,User}, Opts),
- case lookup_user_key_f(Key, User, SshDir, "authorized_keys", Opts) of
- {ok, Key} ->
- {ok, Key};
- _ ->
- lookup_user_key_f(Key, User, SshDir, "authorized_keys2", Opts)
+
+%%%--------------------------------
+
+lookup_host_keys(Hosts, KeyType, Key, File, Opts) ->
+ case get_kb_option(optimize, Opts, time) of
+ time ->
+ case file:read_file(File) of
+ {ok,Bin} ->
+ Lines = binary:split(Bin, <<"\n">>, [global,trim_all]),
+ case find_host_key(Hosts, KeyType, Key, Lines) of
+ {true,RestLines} ->
+ case revoked_key(Hosts, KeyType, Key, RestLines) of
+ true ->
+ {error,revoked_key};
+ false ->
+ true
+ end;
+ false ->
+ false
+ end;
+ {error,enoent} ->
+ false;
+ {error,Error} ->
+ {error,{is_host_key,Error}}
+ end;
+ space ->
+ case file:open(File, [read, binary]) of
+ {ok, Fd} ->
+ Result =
+ case read_test_loop(Fd,
+ fun(Line) ->
+ find_host_key(Hosts, KeyType, Key, [Line])
+ end)
+ of
+ {true,_} ->
+ %% The key is found, now check the rest of the file to see if it is
+ %% revoked
+ case read_test_loop(Fd,
+ fun(Line) ->
+ revoked_key(Hosts, KeyType, Key, [Line])
+ end)
+ of
+ true ->
+ {error,revoked_key};
+ false ->
+ true
+ end;
+ {error,Error} ->
+ {error,{is_host_key,Error}};
+ Other ->
+ Other
+ end,
+ file:close(Fd),
+ Result;
+ {error,Error} ->
+ {error,Error}
+ end;
+ Other ->
+ {error,{is_host_key,{opt,Other}}}
end.
-%%
-%% Utils
-%%
+find_host_key(Hosts, KeyType, EncKey, [<<"#",_/binary>>|PatternLines]) ->
+ %% skip comments
+ find_host_key(Hosts, KeyType, EncKey, PatternLines);
+find_host_key(Hosts, KeyType, EncKey, [Line|PatternLines]) ->
+ %% split the line into the separate parts:
+ %% option? pattern(,pattern)* keytype key comment?
+ SplitLine = binary:split(Line, <<" ">>, [global,trim_all]),
+ case known_key_in_line(Hosts, KeyType, EncKey, SplitLine) of
+ true ->
+ {true, PatternLines};
+ false ->
+ find_host_key(Hosts, KeyType, EncKey, PatternLines)
+ end;
+find_host_key(_, _, _, []) ->
+ false.
-%% server use this to find individual keys for
-%% an individual user when user tries to login
-%% with publickey
-ssh_dir({remoteuser, User}, Opts) ->
- case proplists:get_value(user_dir_fun, Opts) of
- undefined ->
- case proplists:get_value(user_dir, Opts, false) of
- false ->
- default_user_dir();
- Dir ->
- Dir
- end;
- FUN ->
- FUN(User)
+
+revoked_key(Hosts, KeyType, EncKey, [<<"@revoked ",RestLine/binary>> | Lines]) ->
+ case binary:split(RestLine, <<" ">>, [global,trim_all]) of
+ [Patterns, KeyType, EncKey|_Comment] ->
+ %% Very likeley to be a revoked key,
+ %% but does any of the hosts match the pattern?
+ case host_match(Hosts, Patterns) of
+ true ->
+ true;
+ false ->
+ revoked_key(Hosts, KeyType, EncKey, Lines)
+ end;
+ _ ->
+ revoked_key(Hosts, KeyType, EncKey, Lines)
end;
+revoked_key(Hosts, KeyType, EncKey, [_ | Lines]) ->
+ %% Not a revokation line, check the rest
+ revoked_key(Hosts, KeyType, EncKey, Lines);
+revoked_key(_, _, _, _) ->
+ false.
-%% client use this to find client ssh keys
-ssh_dir(user, Opts) ->
- case proplists:get_value(user_dir, Opts, false) of
- false -> default_user_dir();
- D -> D
+
+known_key_in_line(Hosts, KeyType, EncKey, FullLine=[Option | Rest]) ->
+ case line_match(Hosts, KeyType, EncKey, Rest) of
+ true ->
+ case Option of
+ <<"@revoked">> ->
+ {error, revoked_key};
+ _ ->
+ %% No other options than @revoked handled (but the key matched)
+ false
+ end;
+ false ->
+ line_match(Hosts, KeyType, EncKey, FullLine)
end;
+known_key_in_line(_, _, _, _) ->
+ false.
-%% server use this to find server host keys
-ssh_dir(system, Opts) ->
- proplists:get_value(system_dir, Opts, "/etc/ssh").
+line_match(Hosts, KeyType, EncKey, [Patterns, KeyType0, EncKey0|_Comment]) ->
+ KeyType==normalize_alg(KeyType0)
+ andalso EncKey==EncKey0
+ andalso host_match(Hosts, Patterns);
+line_match(_, _, _, _) ->
+ false.
-file_name(Type, Name, Opts) ->
- FN = filename:join(ssh_dir(Type, Opts), Name),
- FN.
+host_match(Hosts, Patterns) ->
+ PatternList = binary:split(Patterns, <<",">>, [global]),
+ host_matchL(Hosts, PatternList).
+
+host_matchL([H|Hosts], Patterns) ->
+ case one_host_match(H, Patterns) of
+ true ->
+ true;
+ false ->
+ host_matchL(Hosts, Patterns)
+ end;
+host_matchL(_, _) ->
+ false.
-%% in: "host" out: "host,1.2.3.4.
-add_ip(IP) when is_tuple(IP) ->
- ssh_connection:encode_ip(IP);
-add_ip(Host) ->
- case inet:getaddr(Host, inet) of
- {ok, Addr} ->
- case ssh_connection:encode_ip(Addr) of
- false -> Host;
- IPString -> Host ++ "," ++ IPString
- end;
- _ -> Host
- end.
-replace_localhost("localhost") ->
- {ok, Hostname} = inet:gethostname(),
- Hostname;
-replace_localhost(Host) ->
- Host.
+one_host_match(H, [Pat|Patterns]) ->
+ case pos_match(H, Pat) of
+ true ->
+ %% Not true if there is any "!" pattern that matches
+ not lists:any(fun(P) -> neg_match(H,P) end,
+ Patterns);
+ false ->
+ one_host_match(H, Patterns)
+ end;
+one_host_match(_, _) ->
+ false.
-do_lookup_host_key(KeyToMatch, Host, Alg, Opts) ->
- case file:open(file_name(user, "known_hosts", Opts), [read, binary]) of
- {ok, Fd} ->
- Res = lookup_host_key_fd(Fd, KeyToMatch, Host, Alg),
- file:close(Fd),
- Res;
- {error, enoent} ->
- {error, not_found};
- Error ->
- Error
+
+neg_match(H, <<"!",P/binary>>) ->
+ pos_match(H, P);
+neg_match(_, _) ->
+ false.
+
+
+pos_match(_, <<"*">> ) -> true;
+pos_match(_, <<"*:*">> ) -> true;
+pos_match(_, <<"[*]:*">>) -> true;
+pos_match(H, <<"!",P/binary>>) -> not pos_match(H, P);
+pos_match(H, H) -> true;
+pos_match(H, P) ->
+ case
+ {binary:split(H,<<":">>),
+ binary:split(P, <<":">>)}
+ of
+ {[Hh,_], [Ph,<<"*">>]} ->
+ %% [host]:port [host]:*
+ Ph == Hh;
+
+ {[Hh], [Ph,<<"*">>]} ->
+ %% host [host]:*
+ Sz = size(Hh),
+ Ph == <<"[", Hh:Sz/binary, "]">>;
+
+ {[Hh], [Ph,<<"22">>]} ->
+ %% host [host]:22
+ Sz = size(Hh),
+ Ph == <<"[", Hh:Sz/binary, "]">>;
+
+ _ ->
+ false
end.
-identity_key_filename('ssh-dss' ) -> "id_dsa";
-identity_key_filename('ssh-rsa' ) -> "id_rsa";
-identity_key_filename('rsa-sha2-256' ) -> "id_rsa";
-identity_key_filename('rsa-sha2-384' ) -> "id_rsa";
-identity_key_filename('rsa-sha2-512' ) -> "id_rsa";
-identity_key_filename('ssh-ed25519' ) -> "id_ed25519";
-identity_key_filename('ssh-ed448' ) -> "id_ed448";
-identity_key_filename('ecdsa-sha2-nistp256') -> "id_ecdsa";
-identity_key_filename('ecdsa-sha2-nistp384') -> "id_ecdsa";
-identity_key_filename('ecdsa-sha2-nistp521') -> "id_ecdsa".
-
-identity_pass_phrase("ssh-dss" ) -> dsa_pass_phrase;
-identity_pass_phrase("ssh-rsa" ) -> rsa_pass_phrase;
-identity_pass_phrase("rsa-sha2-256" ) -> rsa_pass_phrase;
-identity_pass_phrase("rsa-sha2-384" ) -> rsa_pass_phrase;
-identity_pass_phrase("rsa-sha2-512" ) -> rsa_pass_phrase;
-%% Not yet implemented: identity_pass_phrase("ssh-ed25519" ) -> ed25519_pass_phrase;
-%% Not yet implemented: identity_pass_phrase("ssh-ed448" ) -> ed448_pass_phrase;
-identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase;
-identity_pass_phrase(P) when is_atom(P) ->
- identity_pass_phrase(atom_to_list(P));
-identity_pass_phrase(_) -> undefined.
-
-lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) ->
- case io:get_line(Fd, '') of
- eof ->
- {error, not_found};
- {error,Error} ->
- %% Rare... For example NFS errors
- {error,Error};
- Line ->
- case ssh_decode_line(Line, known_hosts) of
- [{Key, Attributes}] ->
- handle_host(Fd, KeyToMatch, Host, proplists:get_value(hostnames, Attributes), Key, KeyType);
- [] ->
- lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType)
- end
+%%%---------------- COMMON FUNCTIONS ------------------------------
+
+assure_file_mode(File, user_write) -> assure_file_mode(File, 8#200);
+assure_file_mode(File, user_read) -> assure_file_mode(File, 8#400);
+assure_file_mode(File, Mode) ->
+ case file:read_file_info(File) of
+ {ok,#file_info{mode=FileMode}} ->
+ case (FileMode band Mode) of % is the wanted Mode set?
+ Mode ->
+ %% yes
+ ok;
+ _ ->
+ %% no
+ file:change_mode(File, FileMode bor Mode) % set missing bit(s)
+ end;
+ {error,enoent} ->
+ %% Not yet created
+ ok;
+ {error,Error} ->
+ {error,Error}
end.
-ssh_decode_line(Line, Type) ->
- try
- public_key:ssh_decode(Line, Type)
- catch _:_ ->
- []
+
+get_kb_option(Key, Opts, Default) ->
+ try
+ proplists:get_value(Key,
+ proplists:get_value(key_cb_private, Opts, []),
+ Default)
+ catch
+ _:_ ->
+ Default
end.
-handle_host(Fd, KeyToMatch, Host, HostList, Key, KeyType) ->
- Host1 = host_name(Host),
- case lists:member(Host1, HostList) andalso key_match(Key, KeyType) of
- true when KeyToMatch == Key ->
- {ok,Key};
- _ ->
- lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType)
+
+read_ssh_key_file(Role, PrivPub, Algorithm, Opts) ->
+ File = file_name(Role, file_base_name(Role,Algorithm), Opts),
+ Password = %% Pwd for Host Keys is an undocumented option and should not be used
+ proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore),
+
+ ok = assure_file_mode(File, user_read),
+ case file:read_file(File) of
+ {ok, Pem} ->
+ try
+ decode_ssh_file(PrivPub, Algorithm, Pem, Password)
+ of
+ {ok, [Key|_Keys]} ->
+ {ok,Key};
+ {error, Reason} ->
+ {error, Reason}
+ catch
+ throw:Reason ->
+ {error, Reason};
+ error:Reason ->
+ {error, Reason}
+ end;
+
+ {error, Reason} ->
+ {error, Reason}
end.
-host_name(Atom) when is_atom(Atom) ->
- atom_to_list(Atom);
-host_name(List) ->
- List.
-
-key_match(#'RSAPublicKey'{}, 'ssh-rsa') ->
- true;
-key_match({_, #'Dss-Parms'{}}, 'ssh-dss') ->
- true;
-key_match({#'ECPoint'{},{namedCurve,Curve}}, Alg) ->
- case atom_to_list(Alg) of
- "ecdsa-sha2-"++IdS ->
- Curve == public_key:ssh_curvename2oid(list_to_binary(IdS));
- _ ->
- false
- end;
-key_match({ed_pub,ed25519,_}, 'ssh-ed25519') ->
- true;
-key_match({ed_pub,ed448,_}, 'ssh-ed448') ->
- true;
-key_match(_, _) ->
- false.
-add_key_fd(Fd, Host,Key) ->
- SshBin = public_key:ssh_encode([{Key, [{hostnames, [Host]}]}], known_hosts),
- file:write(Fd, SshBin).
-
-lookup_user_key_f(_, _User, [], _F, _Opts) ->
- {error, nouserdir};
-lookup_user_key_f(_, _User, nouserdir, _F, _Opts) ->
- {error, nouserdir};
-lookup_user_key_f(Key, _User, Dir, F, _Opts) ->
- FileName = filename:join(Dir, F),
- case file:open(FileName, [read, binary]) of
- {ok, Fd} ->
- Res = lookup_user_key_fd(Fd, Key),
- file:close(Fd),
- Res;
- {error, Reason} ->
- {error, {{openerr, Reason}, {file, FileName}}}
+-spec decode_ssh_file(PrivPub, Algorithm, Pem, Password) -> Result when
+ PrivPub :: private | public,
+ Algorithm :: ssh:pubkey_alg(),
+ Pem :: binary(),
+ Password :: string(),
+ Result :: {ok, Keys} | {error, any()},
+ Keys :: [Key],
+ Key :: public_key:private_key() | public_key:public_key() .
+
+decode_ssh_file(PrivPub, Algorithm, Pem, Password) ->
+ try decode_pem_keys(Pem, Password)
+ of
+ {ok, Keys0} ->
+ case [Key || Key <- Keys0,
+ ssh_transport:valid_key_sha_alg(PrivPub, Key, Algorithm)] of
+ [] ->
+ {error,no_key_found};
+ Keys ->
+ {ok,Keys}
+ end;
+
+ {error,Error} ->
+ {error,Error}
+
+ catch
+ _:_ ->
+ {error, key_decode_failed}
end.
-lookup_user_key_fd(Fd, Key) ->
- case io:get_line(Fd, '') of
- eof ->
- {error, not_found};
- {error,Error} ->
- %% Rare... For example NFS errors
- {error,Error};
- Line ->
- case ssh_decode_line(Line, auth_keys) of
- [{AuthKey, _}] ->
- case is_auth_key(Key, AuthKey) of
- true ->
- {ok, Key};
- false ->
- lookup_user_key_fd(Fd, Key)
- end;
- [] ->
- lookup_user_key_fd(Fd, Key)
- end
+decode_pem_keys(Pem, Password) ->
+ %% Private Key
+ try get_key_part(Pem) of
+ {'openssh-key-v1', Bin, _KeyValues} ->
+ %% Holds both public and private keys
+ KeyPairs = new_openssh_decode(Bin, Password),
+ Keys = [Key || {Pub,Priv} <- KeyPairs,
+ Key <- [Pub,Priv]],
+ {ok,Keys};
+
+ {rfc4716, Bin, _KeyValues} ->
+ %% rfc4716 only defines public keys
+ Key = ssh_message:ssh2_pubkey_decode(Bin),
+ {ok,[Key]};
+
+ {Type, Bin, KeyValues} ->
+ case get_encrypt_hdrs(KeyValues) of
+ not_encrypted ->
+ Key = public_key:pem_entry_decode({Type,Bin,not_encrypted}),
+ {ok, [Key]};
+
+ [Cipher,Salt] when is_binary(Cipher),
+ is_binary(Salt),
+ Password =/= ignore ->
+ CryptInfo =
+ {binary_to_list(Cipher), unhex(binary_to_list(Salt))},
+ Key = public_key:pem_entry_decode({Type,Bin,CryptInfo}, Password),
+ {ok, [Key]};
+
+ _X ->
+ {error, no_pass_phrase}
+ end
+ catch
+ _:_ -> error(bad_or_unsupported_key_format)
end.
-is_auth_key(Key, Key) ->
- true;
-is_auth_key(_,_) ->
- false.
+get_encrypt_hdrs(KVs) ->
+ lists:foldl(fun({<<"Proc-Type">>, <<"4,ENCRYPTED", _/binary>>}, _Acc) ->
+ {proc_type, <<"4,ENCRYPTED">>};
+ ({<<"DEK-Info">>, DEKinfo}, {proc_type,_}) ->
+ binary:split(DEKinfo, <<",">>);
+ (_, Acc) ->
+ Acc
+ end, not_encrypted, KVs).
+
+unhex(S) ->
+ %% I would like to do erlang:list_to_integer(S,16), but that does not fit
+ %% the public_key:pem_entry_decode API
+ list_to_binary(
+ lists:foldr(fun(D2, {D1,Acc}) ->
+ [erlang:list_to_integer([D2,D1], 16) | Acc]; % sic!
+ (D1, Acc) when is_list(Acc) ->
+ {D1,Acc}
+ end, [], S)).
+
+file_base_name(user, 'ecdsa-sha2-nistp256') -> "id_ecdsa";
+file_base_name(user, 'ecdsa-sha2-nistp384') -> "id_ecdsa";
+file_base_name(user, 'ecdsa-sha2-nistp521') -> "id_ecdsa";
+file_base_name(user, 'rsa-sha2-256' ) -> "id_rsa";
+file_base_name(user, 'rsa-sha2-384' ) -> "id_rsa";
+file_base_name(user, 'rsa-sha2-512' ) -> "id_rsa";
+file_base_name(user, 'ssh-dss' ) -> "id_dsa";
+file_base_name(user, 'ssh-ed25519' ) -> "id_ed25519";
+file_base_name(user, 'ssh-ed448' ) -> "id_ed448";
+file_base_name(user, 'ssh-rsa' ) -> "id_rsa";
+file_base_name(system, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key";
+file_base_name(system, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key";
+file_base_name(system, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key";
+file_base_name(system, 'rsa-sha2-256' ) -> "ssh_host_rsa_key";
+file_base_name(system, 'rsa-sha2-384' ) -> "ssh_host_rsa_key";
+file_base_name(system, 'rsa-sha2-512' ) -> "ssh_host_rsa_key";
+file_base_name(system, 'ssh-dss' ) -> "ssh_host_dsa_key";
+file_base_name(system, 'ssh-ed25519' ) -> "ssh_host_ed25519_key";
+file_base_name(system, 'ssh-ed448' ) -> "ssh_host_ed448_key";
+file_base_name(system, 'ssh-rsa' ) -> "ssh_host_rsa_key";
+file_base_name(system, _ ) -> "ssh_host_key".
+
+
+identity_pass_phrase('ssh-dss' ) -> dsa_pass_phrase;
+identity_pass_phrase('ssh-rsa' ) -> rsa_pass_phrase;
+identity_pass_phrase('rsa-sha2-256' ) -> rsa_pass_phrase;
+identity_pass_phrase('rsa-sha2-384' ) -> rsa_pass_phrase;
+identity_pass_phrase('rsa-sha2-512' ) -> rsa_pass_phrase;
+identity_pass_phrase('ecdsa-sha2-nistp256') -> ecdsa_pass_phrase;
+identity_pass_phrase('ecdsa-sha2-nistp384') -> ecdsa_pass_phrase;
+identity_pass_phrase('ecdsa-sha2-nistp521') -> ecdsa_pass_phrase;
+%% Not yet implemented: identity_pass_phrase('ssh-ed25519' ) -> ed25519_pass_phrase;
+%% Not yet implemented: identity_pass_phrase('ssh-ed448' ) -> ed448_pass_phrase;
+identity_pass_phrase(_) -> undefined.
+
+%%%----------------------------------------------------------------
+file_name(Type, Name, Opts) ->
+ filename:join(ssh_dir(Type, Opts), Name).
+
+%%%--------------------------------
+ssh_dir({remoteuser, User}, Opts) ->
+ %% server use this to find individual keys for an individual
+ %% user when user tries to login with publickey
+ case proplists:get_value(user_dir_fun, Opts) of
+ undefined ->
+ %% Try the local user instead
+ ssh_dir(user, Opts);
+ FUN ->
+ FUN(User)
+ end;
+
+ssh_dir(user, Opts) ->
+ %% client use this to find client ssh keys
+ case proplists:get_value(user_dir, Opts, false) of
+ false -> default_user_dir();
+ D -> D
+ end;
+
+ssh_dir(system, Opts) ->
+ %% server use this to find server host keys
+ proplists:get_value(system_dir, Opts, "/etc/ssh").
+
+%%%--------------------------------
default_user_dir() ->
try
default_user_dir(os:getenv("HOME"))
@@ -392,12 +694,133 @@ default_user_dir({ok,[[Home|_]]}) ->
default_user_dir(Home) when is_list(Home) ->
UserDir = filename:join(Home, ".ssh"),
ok = filelib:ensure_dir(filename:join(UserDir, "dummy")),
- {ok,Info} = file:read_file_info(UserDir),
- #file_info{mode=Mode} = Info,
- case (Mode band 8#777) of
- ?PERM_700 ->
- ok;
- _Other ->
- ok = file:change_mode(UserDir, ?PERM_700)
- end,
UserDir.
+
+%%%################################################################
+get_key_part(RawBin) when is_binary(RawBin) ->
+ case binary:split(
+ binary:replace(RawBin, <<"\\\n">>, <<"">>, [global]),
+ <<"\n">>, [global,trim_all])
+ of
+ [<<"---- BEGIN SSH2 PUBLIC KEY ----">> | Lines0] ->
+ %% RFC 4716 format
+ {KeyValues,Lines} = get_hdr_lines(Lines0, []),
+ ExpectedEndLine = <<"---- END SSH2 PUBLIC KEY ----">>,
+ {rfc4716, get_body(Lines,ExpectedEndLine), KeyValues};
+
+ [<<"-----BEGIN ", Rest/binary>> | Lines0] ->
+ %% PEM format
+ ExpectedEndLine = <<"-----END ",Rest/binary>>,
+ [MiddlePart, <<>>] = binary:split(Rest, <<" KEY-----">>),
+ {KeyValues,Lines} = get_hdr_lines(Lines0, []),
+ {asn1_type(MiddlePart), get_body(Lines,ExpectedEndLine), KeyValues}
+ end.
+
+
+get_hdr_lines(Lines, Acc) ->
+ Line1 = hd(Lines),
+ case binary:split(Line1, <<":">>) of
+ [Line1] ->
+ {lists:reverse(Acc), Lines};
+ [Key,Value] ->
+ get_hdr_lines(tl(Lines), [{trim(Key),trim(Value)}|Acc])
+ end.
+
+
+get_body(Lines, ExpectedEndLine) ->
+ {KeyPart, [ExpectedEndLine]} = lists:split(length(Lines)-1, Lines),
+ base64:mime_decode(iolist_to_binary(KeyPart)).
+
+trim(<<" ",B/binary>>) -> trim(B);
+trim(B) -> B.
+
+asn1_type(<<"RSA PRIVATE">>) -> 'RSAPrivateKey';
+asn1_type(<<"RSA PUBLIC">>) -> 'RSAPublicKey';
+asn1_type(<<"DSA PRIVATE">>) -> 'DSAPrivateKey';
+asn1_type(<<"EC PRIVATE">>) -> 'ECPrivateKey';
+asn1_type(<<"OPENSSH PRIVATE">>) -> 'openssh-key-v1';
+asn1_type(_) -> undefined.
+
+%%%================================================================
+%%% From https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
+%%%
+
+-define(NON_CRYPT_BLOCKSIZE, 8).
+
+new_openssh_decode(<<"openssh-key-v1",0,
+ ?DEC_BIN(CipherName, _L1),
+ ?DEC_BIN(KdfName, _L2),
+ ?DEC_BIN(KdfOptions, _L3),
+ ?UINT32(N), % number of keys
+ Rest/binary
+ >>, Pwd) ->
+ new_openssh_decode(Rest, N, Pwd, CipherName, KdfName, KdfOptions, N, []).
+
+
+new_openssh_decode(<<?DEC_BIN(BinKey,_L1), Rest/binary>>, I, Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAcc) when I>0 ->
+ PublicKey = ssh_message:ssh2_pubkey_decode(BinKey),
+ new_openssh_decode(Rest, I-1, Pwd, CipherName, KdfName, KdfOptions, N, [PublicKey|PubKeyAcc]);
+
+new_openssh_decode(<<?DEC_BIN(Encrypted,_L)>>,
+ 0, Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAccRev) ->
+ PubKeys = lists:reverse(PubKeyAccRev),
+ try
+ Plain = decrypt_new_openssh(Encrypted, KdfName, KdfOptions, CipherName, Pwd),
+ new_openssh_decode_priv_keys(Plain, N, N, [], [])
+ of
+ {PrivKeys, _Comments} ->
+ lists:map(fun({ {ed_pub,A,Pub}, {ed_pri,A,Pub,Pri0} }) ->
+ Pri = binary:part(Pri0, {0,size(Pri0)-size(Pub)}),
+ {{ed_pub,A,Pub}, {ed_pri,A,Pub,Pri}};
+ (Pair) ->
+ Pair
+ end, lists:zip(PubKeys, PrivKeys))
+ catch
+ error:{decryption, DecryptError} ->
+ error({decryption, DecryptError})
+ end.
+
+
+new_openssh_decode_priv_keys(Bin, I, N, KeyAcc, CmntAcc) when I>0 ->
+ {PrivKey, <<?DEC_BIN(Comment,_Lc),Rest/binary>>} = ssh_message:ssh2_privkey_decode2(Bin),
+ new_openssh_decode_priv_keys(Rest, I-1, N, [PrivKey|KeyAcc], [Comment|CmntAcc]);
+new_openssh_decode_priv_keys(_Padding, 0, _N, PrivKeyAccRev, CommentAccRev) ->
+ {lists:reverse(PrivKeyAccRev),
+ lists:reverse(CommentAccRev)}.
+
+
+decrypt_new_openssh(Encrypted, <<"none">>, <<>>, _CipherName, _Pwd) ->
+ check_valid_decryption(Encrypted, ?NON_CRYPT_BLOCKSIZE);
+decrypt_new_openssh(Encrypted, <<>>, <<>>, _CipherName, _Pwd) ->
+ check_valid_decryption(Encrypted, ?NON_CRYPT_BLOCKSIZE);
+decrypt_new_openssh(_Encrypted, <<"bcrypt">>, <<?DEC_BIN(_Salt,_L),?UINT32(_Rounds)>>, _CipherName, _Pwd) ->
+ error({decryption, {not_supported,bcrypt}});
+decrypt_new_openssh(_Encrypted, KdfName, _KdfOpts, _CipherName, _Pwd) ->
+ error({decryption, {not_supported,KdfName}}).
+
+
+check_valid_decryption(<<?UINT32(Checkint1),?UINT32(Checkint2),Plain/binary>>, BlockSize) when Checkint2==Checkint1 ->
+ case check_padding(Plain, BlockSize) of
+ true ->
+ Plain;
+ false ->
+ error({decryption,bad_padding})
+ end;
+check_valid_decryption(_, _) ->
+ error({decryption,bad_result}).
+
+
+check_padding(Bin, BlockSize) ->
+ N = binary:last(Bin),
+ if
+ N < BlockSize ->
+ %% Check that Bin is <<...,1,2,...,N>>
+ Padding = binary:part(Bin, {byte_size(Bin),-N}),
+ ExpectedPadding = list_to_binary(lists:seq(1,N)), % <<1,2,...,N>>
+ Padding == ExpectedPadding;
+ true ->
+ true
+ end.
+
+%%%================================================================
+%%%
diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl
index 79cd95e422..583274d44c 100644
--- a/lib/ssh/src/ssh_info.erl
+++ b/lib/ssh/src/ssh_info.erl
@@ -34,8 +34,10 @@
-include("ssh_connect.hrl").
print() ->
- io:format("~s", [string()]).
+ print(fun io:format/2).
+print(F) when is_function(F,2) ->
+ F("~s", [string()]);
print(File) when is_list(File) ->
{ok,D} = file:open(File, [write]),
print(D),
@@ -51,10 +53,10 @@ string() ->
print_general(),
io_lib:nl(),
underline("Client part", $=),
- print_clients(),
+ lists:map(fun print_system/1, children(sshc_sup)),
io_lib:nl(),
underline("Server part", $=),
- print_servers(),
+ lists:map(fun print_system/1, children(sshd_sup)),
io_lib:nl(),
underline("Supervisors", $=),
walk_sups(ssh_sup),
@@ -66,6 +68,8 @@ string() ->
%%%================================================================
+-define(inc(N), (N+4)).
+
-define(INDENT, " ").
print_general() ->
@@ -74,105 +78,87 @@ print_general() ->
io_lib:format('This printout is generated ~s. ~n',[datetime()])
].
-print_clients() ->
- try
- lists:map(fun print_client/1,
- supervisor:which_children(sshc_sup))
- catch
- C:E ->
- io_lib:format('***print_clients 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_lib:format(?INDENT"Local: ~s Remote: ~s ConnectionRef = ~p~n",
- [fmt_host_port(Local), fmt_host_port(Remote), Pid]),
- case channels(Pid) of
- {ok,Channels=[_|_]} ->
- [print_ch(ChPid) || #channel{user=ChPid} <- Channels];
- _ ->
- io_lib:format(?INDENT?INDENT?INDENT"No channels~n",[])
- end];
+print_system({{server,ssh_system_sup,Addr,Port,Profile}, Pid, supervisor, [ssh_system_sup]}) ->
+ [io_lib:format(?INDENT"Local: ~s (~p children) Profile ~p~n",
+ [fmt_host_port({Addr,Port}),
+ ssh_acceptor:number_of_connections(Pid),
+ Profile
+ ]),
+ lists:map(fun print_subsystem/1, children(Pid))
+ ];
+print_system({{client,ssh_system_sup,Addr,Port,Profile}, Pid, supervisor, [ssh_system_sup]}) ->
+ [io_lib:format(?INDENT"Local: ~s Profile ~p~n",
+ [fmt_host_port({Addr,Port}),
+ Profile
+ ]),
+ lists:map(fun print_subsystem/1, children(Pid))
+ ];
+print_system({_, _Pid, worker, [ssh_controller]}) ->
+ ""; % io_lib:format(?INDENT"Controller~n",[]);
+print_system(_X) ->
+ io_lib:format(?INDENT"nyi system ~p~n",[_X]).
+
+
+
+
+print_subsystem({{ssh_acceptor_sup,_Addr,_Port,_Profile}, _Pid, supervisor, [ssh_acceptor_sup]}) ->
+ io_lib:format(?INDENT?INDENT"Acceptor~n",[]);
+print_subsystem({Ref,Pid,supervisor,[ssh_subsystem_sup]}) when is_reference(Ref),
+ is_pid(Pid) ->
+ Cs = children(Pid),
+ [
+ lists:map(
+ fun(Sup) ->
+ [print_conn(P) || {undefined,P,worker,[ssh_connection_handler]} <- children(Sup)]
+ end,
+ [P || {_Ref,P,supervisor,[ssh_connection_sup]} <- Cs]),
+
+ lists:map(
+ fun(Sup) ->
+ [print_ch(M,P) || {_Ref,P,worker,[M]} <- children(Sup),
+ lists:member(M, [ssh_channel,
+ ssh_channel_sup,
+ ssh_client_channel,
+ ssh_daemon_channel,
+ ssh_server_channel
+ ])]
+ end,
+ [P || {_Ref,P,supervisor,[ssh_channel_sup]} <- Cs]),
+
+ lists:map(
+ fun(Sup) ->
+ [io_lib:format(?INDENT?INDENT?INDENT"TCP/IP fwd acceptor: ~p~n", [Pa])
+ || {undefined,Pa,worker,[ssh_tcpip_forward_acceptor]} <- children(Sup)]
+ end,
+ [P || {_Ref,P,supervisor,[ssh_tcpip_forward_acceptor_sup]} <- Cs])
+ ];
-print_client(Other) ->
- io_lib:format(" [[Other 1: ~p]]~n",[Other]).
+print_subsystem(_X) ->
+ io_lib:format(?INDENT?INDENT"nyi subsystem ~p~n",[_X]).
-%%%================================================================
-print_servers() ->
+print_conn(Pid) ->
try
- lists:map(fun print_server/1,
- supervisor:which_children(sshd_sup))
+ {{_Local,Remote},StrM} = ssh_connection_handler:get_print_info(Pid),
+ io_lib:format(?INDENT?INDENT"Remote: ~s ConnectionRef = ~p ~s~n",[fmt_host_port(Remote),Pid,StrM])
catch
- C:E ->
- io_lib:format('***print_servers FAILED: ~p:~p~n',[C,E])
- end.
-
+ C:E ->
+ io_lib:format('****print_conn FAILED for ConnPid ~p: ~p:~p~n',[Pid, C, E])
+ end.
+
-print_server({{server,ssh_system_sup,LocalHost,LocalPort,Profile},Pid,supervisor,[ssh_system_sup]}) when is_pid(Pid) ->
- Children = supervisor:which_children(Pid),
- [io_lib:format(?INDENT"Listen: ~s (~p children) Profile ~p",[fmt_host_port({LocalHost,LocalPort}),
- ssh_acceptor:number_of_connections(Pid),
- Profile]),
- case [AccPid
- || {{ssh_acceptor_sup,_LocalHost,_LocalPort,_Profile}, AccPid, supervisor, [ssh_acceptor_sup]}
- <- Children] of
- AcceptorPids = [_|_] ->
- [io_lib:format(" [Acceptor Pid", []),
- [io_lib:format(" ~p",[AccPid]) || AccPid <- AcceptorPids],
- io_lib:format("]~n", [])
- ];
- [] ->
- io_lib:nl()
- end,
- lists:map(fun print_system_sup/1,
- supervisor:which_children(Pid))
- ].
-
-
-print_system_sup({Ref,Pid,supervisor,[ssh_subsystem_sup]}) when is_reference(Ref),
- is_pid(Pid) ->
- lists:map(fun print_channels/1,
- supervisor:which_children(Pid));
-
-print_system_sup({{ssh_acceptor_sup,_LocalHost,_LocalPort,_Profile}, Pid, supervisor, [ssh_acceptor_sup]}) when is_pid(Pid) ->
- [].
-
-
-
-print_channels({{server,ssh_server_channel_sup,_,_},Pid,supervisor,[ssh_server_channel_sup]}) when is_pid(Pid) ->
- Children = supervisor:which_children(Pid),
- ChannelPids = [P || {R,P,worker,[ssh_server_channel]} <- Children,
- is_pid(P),
- is_reference(R)],
- case ChannelPids of
- [] -> io_lib:format(?INDENT?INDENT"No channels~n",[]);
- [Ch1Pid|_] ->
- {{ConnManager,_}, _Str} = ssh_server_channel:get_print_info(Ch1Pid),
- {{_,Remote},_} = ssh_connection_handler:get_print_info(ConnManager),
- [io_lib:format(?INDENT?INDENT"Remote: ~s ConnectionRef = ~p~n",[fmt_host_port(Remote),ConnManager]),
- lists:map(fun print_ch/1, ChannelPids)
- ]
- end;
-print_channels({{server,ssh_connection_sup,_,_},Pid,supervisor,[ssh_connection_sup]}) when is_pid(Pid) ->
- []. % The supervisor of the connections socket owning process
-
-print_ch(Pid) ->
+print_ch(CBmod, Pid) ->
try
- {{ConnManager,ChannelID}, Str} = ssh_server_channel:get_print_info(Pid),
- {_LocalRemote,StrM} = ssh_connection_handler:get_print_info(ConnManager),
- io_lib:format(?INDENT?INDENT?INDENT"ch ~p ~p: ~s ~s~n",[ChannelID, Pid, StrM, Str])
+ {{_ConnManager,ChannelID}, Str} = ssh_server_channel:get_print_info(Pid),
+ io_lib:format(?INDENT?INDENT?INDENT"ch ~p ~p ~p: ~s~n",[ChannelID, Pid, CBmod, Str])
catch
C:E ->
io_lib:format('****print_ch FAILED for ChanPid ~p: ~p:~p~n',[Pid, C, E])
end.
-
%%%================================================================
--define(inc(N), (N+4)).
-
walk_sups(StartPid) ->
- io_lib:format("Start at ~p, ~s.~n",[StartPid,dead_or_alive(StartPid)]),
walk_sups(children(StartPid), _Indent=?inc(0)).
walk_sups([H={_,Pid,_,_}|T], Indent) ->
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index adbad7d422..fa9176b61f 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -31,6 +31,9 @@
-include("ssh_transport.hrl").
-export([encode/1, decode/1, decode_keyboard_interactive_prompts/2]).
+-export([ssh2_pubkey_decode/1,
+ ssh2_pubkey_encode/1,
+ ssh2_privkey_decode2/1]).
-behaviour(ssh_dbg).
-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2]).
@@ -46,6 +49,11 @@ ucl(B) ->
-define(unicode_list(B), ucl(B)).
+%%%================================================================
+%%%
+%%% Encode/decode messages
+%%%
+
encode(#ssh_msg_global_request{
name = Name,
want_reply = Bool,
@@ -242,7 +250,7 @@ encode(#ssh_msg_kexdh_reply{
f = F,
h_sig = Signature
}) ->
- EncKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncKey = ssh2_pubkey_encode(Key),
EncSign = encode_signature(Key, SigAlg, Signature),
<<?Ebyte(?SSH_MSG_KEXDH_REPLY), ?Ebinary(EncKey), ?Empint(F), ?Ebinary(EncSign)>>;
@@ -268,7 +276,7 @@ encode(#ssh_msg_kex_dh_gex_reply{
f = F,
h_sig = Signature
}) ->
- EncKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncKey = ssh2_pubkey_encode(Key),
EncSign = encode_signature(Key, SigAlg, Signature),
<<?Ebyte(?SSH_MSG_KEX_DH_GEX_REPLY), ?Ebinary(EncKey), ?Empint(F), ?Ebinary(EncSign)>>;
@@ -276,7 +284,7 @@ encode(#ssh_msg_kex_ecdh_init{q_c = Q_c}) ->
<<?Ebyte(?SSH_MSG_KEX_ECDH_INIT), ?Ebinary(Q_c)>>;
encode(#ssh_msg_kex_ecdh_reply{public_host_key = {Key,SigAlg}, q_s = Q_s, h_sig = Sign}) ->
- EncKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncKey = ssh2_pubkey_encode(Key),
EncSign = encode_signature(Key, SigAlg, Sign),
<<?Ebyte(?SSH_MSG_KEX_ECDH_REPLY), ?Ebinary(EncKey), ?Ebinary(Q_s), ?Ebinary(EncSign)>>;
@@ -358,13 +366,25 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_CLOSE), ?UINT32(Recipient)>>) ->
recipient_channel = Recipient
};
decode(<<?BYTE(?SSH_MSG_CHANNEL_REQUEST), ?UINT32(Recipient),
- ?DEC_BIN(RequestType,__0), ?BYTE(Bool), Data/binary>>) ->
- #ssh_msg_channel_request{
- recipient_channel = Recipient,
- request_type = ?unicode_list(RequestType),
- want_reply = erl_boolean(Bool),
- data = Data
- };
+ ?DEC_BIN(RequestType,__0), ?BYTE(Bool), Data/binary>>=Bytes) ->
+ try
+ #ssh_msg_channel_request{
+ recipient_channel = Recipient,
+ request_type = ?unicode_list(RequestType),
+ want_reply = erl_boolean(Bool),
+ data = Data
+ }
+ catch _:_ ->
+ %% Faulty, RFC4254 says:
+ %% "If the request is not recognized or is not
+ %% supported for the channel, SSH_MSG_CHANNEL_FAILURE is returned."
+ %% So we provoke such a message to be sent
+ #ssh_msg_channel_request{
+ recipient_channel = Recipient,
+ request_type = faulty_msg,
+ data = Bytes
+ }
+ end;
decode(<<?BYTE(?SSH_MSG_CHANNEL_SUCCESS), ?UINT32(Recipient)>>) ->
#ssh_msg_channel_success{
recipient_channel = Recipient
@@ -453,7 +473,7 @@ decode(<<"dh",?BYTE(?SSH_MSG_KEXDH_INIT), ?DEC_MPINT(E,__0)>>) ->
decode(<<"dh", ?BYTE(?SSH_MSG_KEXDH_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), ?DEC_BIN(Hashsign,__2)>>) ->
#ssh_msg_kexdh_reply{
- public_host_key = public_key:ssh_decode(Key, ssh2_pubkey),
+ public_host_key = ssh2_pubkey_decode(Key),
f = F,
h_sig = decode_signature(Hashsign)
};
@@ -483,7 +503,7 @@ decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_INIT), ?DEC_MPINT(E,__0)>>) ->
decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REPLY), ?DEC_BIN(Key,__0), ?DEC_MPINT(F,__1), ?DEC_BIN(Hashsign,__2)>>) ->
#ssh_msg_kex_dh_gex_reply{
- public_host_key = public_key:ssh_decode(Key, ssh2_pubkey),
+ public_host_key = ssh2_pubkey_decode(Key),
f = F,
h_sig = decode_signature(Hashsign)
};
@@ -496,7 +516,7 @@ decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_INIT), ?DEC_BIN(Q_c,__0)>>) ->
decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_REPLY),
?DEC_BIN(Key,__1), ?DEC_BIN(Q_s,__2), ?DEC_BIN(Sig,__3)>>) ->
#ssh_msg_kex_ecdh_reply{
- public_host_key = public_key:ssh_decode(Key, ssh2_pubkey),
+ public_host_key = ssh2_pubkey_decode(Key),
q_s = Q_s,
h_sig = decode_signature(Sig)
};
@@ -540,6 +560,132 @@ decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?DEC_BIN(Msg,__0), ?DEC_BIN(Lang,__
message = Msg,
language = Lang}.
+
+%%%================================================================
+%%%
+%%% Encode/decode ssh public/private keys
+%%%
+
+%%%-------- public key --------
+ssh2_pubkey_encode(#'RSAPublicKey'{modulus = N, publicExponent = E}) ->
+ <<?STRING(<<"ssh-rsa">>), ?Empint(E), ?Empint(N)>>;
+ssh2_pubkey_encode({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) ->
+ <<?STRING(<<"ssh-dss">>), ?Empint(P), ?Empint(Q), ?Empint(G), ?Empint(Y)>>;
+ssh2_pubkey_encode({#'ECPoint'{point = Q}, {namedCurve,OID}}) ->
+ Curve = public_key:oid2ssh_curvename(OID),
+ KeyType = <<"ecdsa-sha2-", Curve/binary>>,
+ <<?STRING(KeyType), ?STRING(Curve), ?Estring(Q)>>;
+ssh2_pubkey_encode({ed_pub, ed25519, Key}) ->
+ <<?STRING(<<"ssh-ed25519">>), ?Estring(Key)>>;
+ssh2_pubkey_encode({ed_pub, ed448, Key}) ->
+ <<?STRING(<<"ssh-ed448">>), ?Estring(Key)>>.
+
+%%%--------
+ssh2_pubkey_decode(KeyBlob) ->
+ {Key,_RestBlob} = ssh2_pubkey_decode2(KeyBlob),
+ Key.
+
+ssh2_pubkey_decode2(<<?UINT32(7), "ssh-rsa",
+ ?DEC_INT(E, _EL),
+ ?DEC_INT(N, _NL),
+ Rest/binary>>) ->
+ {#'RSAPublicKey'{modulus = N,
+ publicExponent = E
+ }, Rest};
+ssh2_pubkey_decode2(<<?UINT32(7), "ssh-dss",
+ ?DEC_INT(P, _PL),
+ ?DEC_INT(Q, _QL),
+ ?DEC_INT(G, _GL),
+ ?DEC_INT(Y, _YL),
+ Rest/binary>>) ->
+ {{Y, #'Dss-Parms'{p = P,
+ q = Q,
+ g = G}
+ }, Rest};
+ssh2_pubkey_decode2(<<?UINT32(TL), "ecdsa-sha2-",KeyRest/binary>>) ->
+ Sz = TL-11,
+ <<_Curve:Sz/binary,
+ ?DEC_BIN(SshName, _IL),
+ ?DEC_BIN(Q, _QL),
+ Rest/binary>> = KeyRest,
+ OID = public_key:ssh_curvename2oid(SshName),
+ {{#'ECPoint'{point = Q}, {namedCurve,OID}
+ }, Rest};
+ssh2_pubkey_decode2(<<?UINT32(11), "ssh-ed25519",
+ ?DEC_BIN(Key, _L),
+ Rest/binary>>) ->
+ {{ed_pub, ed25519, Key},
+ Rest};
+ssh2_pubkey_decode2(<<?UINT32(9), "ssh-ed448",
+ ?DEC_BIN(Key, _L),
+ Rest/binary>>) ->
+ {{ed_pub, ed448, Key},
+ Rest}.
+
+%%%-------- private key --------
+
+%% dialyser... ssh2_privkey_decode(KeyBlob) ->
+%% dialyser... {Key,_RestBlob} = ssh2_privkey_decode2(KeyBlob),
+%% dialyser... Key.
+
+%% See sshkey_private_serialize_opt in sshkey.c
+ssh2_privkey_decode2(<<?UINT32(7), "ssh-rsa",
+ ?DEC_INT(N, _NL), % Yes, N and E is reversed relative pubkey format
+ ?DEC_INT(E, _EL), % --"--
+ ?DEC_INT(D, _DL),
+ ?DEC_INT(IQMP, _IQMPL),
+ ?DEC_INT(P, _PL),
+ ?DEC_INT(Q, _QL),
+ Rest/binary>>) ->
+ {#'RSAPrivateKey'{version = 'two-prime', % Found this in public_key:generate_key/1 ..
+ modulus = N,
+ publicExponent = E,
+ privateExponent = D,
+ prime1 = P,
+ prime2 = Q,
+ %exponent1, % D_mod_P_1
+ %exponent2, % D_mod_Q_1
+ coefficient = IQMP
+ }, Rest};
+ssh2_privkey_decode2(<<?UINT32(7), "ssh-dss",
+ ?DEC_INT(P, _PL),
+ ?DEC_INT(Q, _QL),
+ ?DEC_INT(G, _GL),
+ ?DEC_INT(Y, _YL), % Publ key
+ ?DEC_INT(X, _XL), % Priv key
+ Rest/binary>>) ->
+ {#'DSAPrivateKey'{version = 0,
+ p = P,
+ q = Q,
+ g = G,
+ y = Y,
+ x = X
+ }, Rest};
+ssh2_privkey_decode2(<<?UINT32(TL), "ecdsa-sha2-",KeyRest/binary>>) ->
+ Sz = TL-11,
+ <<_Curve:Sz/binary,
+ ?DEC_BIN(CurveName, _SNN),
+ ?DEC_BIN(Q, _QL),
+ ?DEC_BIN(Priv, _PrivL),
+ Rest/binary>> = KeyRest,
+ OID = public_key:ssh_curvename2oid(CurveName),
+ {#'ECPrivateKey'{version = 1,
+ parameters = {namedCurve,OID},
+ privateKey = Priv,
+ publicKey = Q
+ }, Rest};
+ssh2_privkey_decode2(<<?UINT32(11), "ssh-ed25519",
+ ?DEC_BIN(Pub,_Lpub),
+ ?DEC_BIN(Priv,_Lpriv),
+ Rest/binary>>) ->
+ {{ed_pri, ed25519, Pub, Priv}, Rest};
+ssh2_privkey_decode2(<<?UINT32(9), "ssh-ed448",
+ ?DEC_BIN(Pub,_Lpub),
+ ?DEC_BIN(Priv,_Lpriv),
+ Rest/binary>>) ->
+ {{ed_pri, ed448, Pub, Priv}, Rest}.
+
+
%%%================================================================
%%%
%%% Helper functions
@@ -550,12 +696,18 @@ bin_foldr(Fun, Acc, Bin) ->
bin_foldl(_, Acc, <<>>) -> Acc;
bin_foldl(Fun, Acc0, Bin0) ->
- {Bin,Acc} = Fun(Bin0,Acc0),
- bin_foldl(Fun, Acc, Bin).
+ case Fun(Bin0,Acc0) of
+ {Bin0,Acc0} ->
+ Acc0;
+ {Bin,Acc} ->
+ bin_foldl(Fun, Acc, Bin)
+ end.
%%%----------------------------------------------------------------
decode_keyboard_interactive_prompts(<<>>, Acc) ->
lists:reverse(Acc);
+decode_keyboard_interactive_prompts(<<0>>, Acc) ->
+ lists:reverse(Acc);
decode_keyboard_interactive_prompts(<<?DEC_BIN(Prompt,__0), ?BYTE(Bool), Bin/binary>>,
Acc) ->
decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]).
@@ -594,8 +746,8 @@ encode_signature(#'RSAPublicKey'{}, SigAlg, Signature) ->
encode_signature({_, #'Dss-Parms'{}}, _SigAlg, Signature) ->
<<?Ebinary(<<"ssh-dss">>), ?Ebinary(Signature)>>;
encode_signature({#'ECPoint'{}, {namedCurve,OID}}, _SigAlg, Signature) ->
- CurveName = public_key:oid2ssh_curvename(OID),
- <<?Ebinary(<<"ecdsa-sha2-",CurveName/binary>>), ?Ebinary(Signature)>>;
+ Curve = public_key:oid2ssh_curvename(OID),
+ <<?Ebinary(<<"ecdsa-sha2-",Curve/binary>>), ?Ebinary(Signature)>>;
encode_signature({ed_pub, ed25519,_}, _SigAlg, Signature) ->
<<?Ebinary(<<"ssh-ed25519">>), ?Ebinary(Signature)>>;
encode_signature({ed_pub, ed448,_}, _SigAlg, Signature) ->
@@ -628,15 +780,54 @@ ssh_dbg_format(ssh_messages, {call,{?MODULE,encode,[Msg]}}) ->
["Going to send ",Name,":\n",
wr_record(ssh_dbg:shrink_bin(Msg))
];
+ssh_dbg_format(ssh_messages, {return_from, {?MODULE,encode,1}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(ssh_messages, {call, {?MODULE,decode,[_]}}) ->
+ skip;
ssh_dbg_format(ssh_messages, {return_from,{?MODULE,decode,1},Msg}) ->
Name = string:to_upper(atom_to_list(element(1,Msg))),
["Received ",Name,":\n",
- wr_record(ssh_dbg:shrink_bin(Msg))
+ wr_record(ssh_dbg:shrink_bin(Msg)),
+ case Msg of
+ #ssh_msg_userauth_request{service = "ssh-connection",
+ method = "publickey",
+ data = <<_,?DEC_BIN(Alg,__0),_/binary>>} ->
+ io_lib:format(" data decoded: ~s ... ~n", [Alg]);
+
+ #ssh_msg_channel_request{request_type = "env",
+ data = <<?DEC_BIN(Var,__0),?DEC_BIN(Val,__1)>>} ->
+ io_lib:format(" data decoded: ~s = ~s~n", [Var, Val]);
+
+ #ssh_msg_channel_request{request_type = "exec",
+ data = <<?DEC_BIN(Cmnd,__0)>>} ->
+ io_lib:format(" data decoded: ~s~n", [Cmnd]);
+
+ #ssh_msg_channel_request{request_type = "pty-req",
+ data = <<?DEC_BIN(BTermName,_TermLen),
+ ?UINT32(Width),?UINT32(Height),
+ ?UINT32(PixWidth), ?UINT32(PixHeight),
+ Modes/binary>>} ->
+ io_lib:format(" data decoded: terminal = ~s~n"
+ " width x height = ~p x ~p~n"
+ " pix-width x pix-height = ~p x ~p~n"
+ " pty-opts = ~p~n",
+ [BTermName, Width,Height, PixWidth, PixHeight,
+ ssh_connection:decode_pty_opts(Modes)]);
+ _ ->
+ ""
+ end
];
+
ssh_dbg_format(raw_messages, {call,{?MODULE,decode,[BytesPT]}}) ->
["Received plain text bytes (shown after decryption):\n",
io_lib:format("~p",[BytesPT])
];
+ssh_dbg_format(raw_messages, {return_from, {?MODULE,decode,1}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(raw_messages, {call, {?MODULE,encode,[_]}}) ->
+ skip;
ssh_dbg_format(raw_messages, {return_from,{?MODULE,encode,1},BytesPT}) ->
["Going to send plain text bytes (shown before encryption):\n",
io_lib:format("~p",[BytesPT])
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
index 33355dd504..b5b0a9d2d8 100644
--- a/lib/ssh/src/ssh_options.erl
+++ b/lib/ssh/src/ssh_options.erl
@@ -31,7 +31,10 @@
delete_key/5,
handle_options/2,
keep_user_options/2,
- keep_set_options/2
+ keep_set_options/2,
+
+ initial_default_algorithms/2,
+ check_preferred_algorithms/1
]).
-export_type([private_options/0
@@ -158,58 +161,125 @@ delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opt
handle_options(Role, PropList0) ->
handle_options(Role, PropList0, #{socket_options => [],
internal_options => #{},
- user_options => []
+ key_cb_options => []
}).
-handle_options(Role, PropList0, Opts0) when is_map(Opts0),
- is_list(PropList0) ->
- PropList1 = proplists:unfold(PropList0),
+handle_options(Role, OptsList0, Opts0) when is_map(Opts0),
+ is_list(OptsList0) ->
+ OptsList1 = proplists:unfold(
+ lists:foldr(fun(T,Acc) when is_tuple(T),
+ size(T) =/= 2-> [{special_trpt_args,T} | Acc];
+ (X,Acc) -> [X|Acc]
+ end,
+ [], OptsList0)),
try
OptionDefinitions = default(Role),
- InitialMap =
+ RoleCnfs = application:get_env(ssh, cnf_key(Role), []),
+ {InitialMap,OptsList2} =
maps:fold(
- fun(K, #{default:=V}, M) -> M#{K=>V};
- (_,_,M) -> M
+ fun(K, #{default:=Vd}, {M,PL}) ->
+ %% Now set as the default value:
+ %% 1: from erl command list: erl -ssh opt val
+ %% 2: from config file: {options, [..., {opt,val}, ....]}
+ %% 3: from the hard-coded option values in default/1
+ %% The value in the option list will be handled later in save/3 later
+ case config_val(K, RoleCnfs, OptsList1) of
+ {ok,V1} ->
+ %% A value set in config or options. Replace the current.
+ {M#{K => V1,
+ key_cb_options => [{K,V1} | maps:get(key_cb_options,M)]},
+ [{K,V1} | PL]
+ };
+
+ {append,V1} ->
+ %% A value set in config or options, but should be
+ %% appended to the existing value
+ NewVal = maps:get(K,M,[]) ++ V1,
+ {M#{K => NewVal,
+ key_cb_options => [{K,NewVal} |
+ lists:keydelete(K,1,maps:get(key_cb_options,M))]},
+ [{K,NewVal} | lists:keydelete(K,1,PL)]
+ };
+
+ undefined ->
+ %% Use the default value
+ {M#{K => Vd}, PL}
+ end
+ %% ;
+ %% (_,_,Acc) ->
+ %% Acc
end,
- Opts0#{user_options =>
- maps:get(user_options,Opts0) ++ PropList1
- },
+ {Opts0#{key_cb_options => maps:get(key_cb_options,Opts0)},
+ [{K,V} || {K,V} <- OptsList1,
+ not maps:is_key(K,Opts0) % Keep socket opts
+ ]
+ },
OptionDefinitions),
+
+
%% Enter the user's values into the map; unknown keys are
%% treated as socket options
final_preferred_algorithms(
lists:foldl(fun(KV, Vals) ->
save(KV, OptionDefinitions, Vals)
- end, InitialMap, PropList1))
+ end, InitialMap, OptsList2))
catch
- error:{eoptions, KV, undefined} ->
- {error, {eoptions,KV}};
+ error:{EO, KV, Reason} when EO == eoptions ; EO == eerl_env ->
+ if
+ Reason == undefined ->
+ {error, {EO,KV}};
+ is_list(Reason) ->
+ {error, {EO,{KV,lists:flatten(Reason)}}};
+ true ->
+ {error, {EO,{KV,Reason}}}
+ end
+ end.
+
+cnf_key(server) -> server_options;
+cnf_key(client) -> client_options.
- error:{eoptions, KV, Txt} when is_list(Txt) ->
- {error, {eoptions,{KV,lists:flatten(Txt)}}};
- error:{eoptions, KV, Extra} ->
- {error, {eoptions,{KV,Extra}}}
+config_val(modify_algorithms=Key, RoleCnfs, Opts) ->
+ V = case application:get_env(ssh, Key) of
+ {ok,V0} -> V0;
+ _ -> []
+ end
+ ++ proplists:get_value(Key, RoleCnfs, [])
+ ++ proplists:get_value(Key, Opts, []),
+ case V of
+ [] -> undefined;
+ _ -> {append,V}
+ end;
+
+config_val(Key, RoleCnfs, Opts) ->
+ case lists:keysearch(Key, 1, Opts) of
+ {value, {_,V}} ->
+ {ok,V};
+ false ->
+ case lists:keysearch(Key, 1, RoleCnfs) of
+ {value, {_,V}} ->
+ {ok,V};
+ false ->
+ application:get_env(ssh, Key) % returns {ok,V} | undefined
+ end
end.
check_fun(Key, Defs) ->
- #{chk := Fun} = maps:get(Key, Defs),
- Fun.
+ case ssh_connection_handler:prohibited_sock_option(Key) of
+ false ->
+ #{chk := Fun} = maps:get(Key, Defs),
+ Fun;
+ true ->
+ fun(_,_) -> forbidden end
+ end.
%%%================================================================
%%%
%%% Check and save one option
%%%
-
-%%% First some prohibited inet options:
-save({K,V}, _, _) when K == reuseaddr ;
- K == active
- ->
- forbidden_option(K, V);
-
-%%% then compatibility conversions:
+%%% First compatibility conversions:
save({allow_user_interaction,V}, Opts, Vals) ->
save({user_interaction,V}, Opts, Vals);
@@ -221,6 +291,10 @@ save(Inet, Defs, OptMap) when Inet==inet ; Inet==inet6 ->
save({Inet,true}, Defs, OptMap) when Inet==inet ; Inet==inet6 -> save({inet,Inet}, Defs, OptMap);
save({Inet,false}, _Defs, OptMap) when Inet==inet ; Inet==inet6 -> OptMap;
+%% There are inet-options that are not a tuple sized 2. They where marked earlier
+save({special_trpt_args,T}, _Defs, OptMap) when is_map(OptMap) ->
+ OptMap#{socket_options := [T | maps:get(socket_options,OptMap)]};
+
%% and finaly the 'real stuff':
save({Key,Value}, Defs, OptMap) when is_map(OptMap) ->
try (check_fun(Key,Defs))(Value)
@@ -230,7 +304,12 @@ save({Key,Value}, Defs, OptMap) when is_map(OptMap) ->
{true, ModifiedValue} ->
OptMap#{Key := ModifiedValue};
false ->
- error({eoptions, {Key,Value}, "Bad value"})
+ error({eoptions, {Key,Value}, "Bad value"});
+ forbidden ->
+ error({eoptions, {Key,Value},
+ io_lib:format("The option '~s' is used internally. The "
+ "user is not allowed to specify this option.",
+ [Key])})
catch
%% An unknown Key (= not in the definition map) is
%% regarded as an inet option:
@@ -307,7 +386,8 @@ default(server) ->
#{default => ?DEFAULT_SHELL,
chk => fun({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A);
(disabled) -> true;
- (V) -> check_function1(V) orelse check_function2(V)
+ (V) -> check_function1(V) orelse
+ check_function2(V)
end,
class => user_option
},
@@ -331,6 +411,18 @@ default(server) ->
class => user_option
},
+ tcpip_tunnel_out =>
+ #{default => false,
+ chk => fun(V) -> erlang:is_boolean(V) end,
+ class => user_option
+ },
+
+ tcpip_tunnel_in =>
+ #{default => false,
+ chk => fun(V) -> erlang:is_boolean(V) end,
+ class => user_option
+ },
+
system_dir =>
#{default => "/etc/ssh",
chk => fun(V) -> check_string(V) andalso check_dir(V) end,
@@ -345,7 +437,8 @@ default(server) ->
check_string(S3) andalso
is_boolean(B);
(F) ->
- check_function3(F)
+ check_function3(F) orelse
+ check_function4(F)
end,
class => user_option
},
@@ -362,15 +455,21 @@ default(server) ->
class => user_option
},
+ pk_check_user =>
+ #{default => false,
+ chk => fun(V) -> erlang:is_boolean(V) end,
+ class => user_option
+ },
+
password =>
#{default => undefined,
- chk => fun check_string/1,
+ chk => fun(V) -> check_string(V) end,
class => user_option
},
dh_gex_groups =>
#{default => undefined,
- chk => fun check_dh_gex_groups/1,
+ chk => fun(V) -> check_dh_gex_groups(V) end,
class => user_option
},
@@ -394,31 +493,37 @@ default(server) ->
negotiation_timeout =>
#{default => 2*60*1000,
+ chk => fun(V) -> check_timeout(V) end,
+ class => user_option
+ },
+
+ hello_timeout =>
+ #{default => 30*1000,
chk => fun check_timeout/1,
class => user_option
},
max_sessions =>
#{default => infinity,
- chk => fun check_pos_integer/1,
+ chk => fun(V) -> check_pos_integer(V) end,
class => user_option
},
max_channels =>
#{default => infinity,
- chk => fun check_pos_integer/1,
+ chk => fun(V) -> check_pos_integer(V) end,
class => user_option
},
parallel_login =>
#{default => false,
- chk => fun erlang:is_boolean/1,
+ chk => fun(V) -> erlang:is_boolean(V) end,
class => user_option
},
minimal_remote_max_packet_size =>
#{default => 0,
- chk => fun check_pos_integer/1,
+ chk => fun(V) -> check_pos_integer(V) end,
class => user_option
},
@@ -432,7 +537,7 @@ default(server) ->
connectfun =>
#{default => fun(_,_,_) -> void end,
- chk => fun check_function3/1,
+ chk => fun(V) -> check_function3(V) end,
class => user_option
},
@@ -451,49 +556,49 @@ default(client) ->
#{
dsa_pass_phrase =>
#{default => undefined,
- chk => fun check_string/1,
+ chk => fun(V) -> check_string(V) end,
class => user_option
},
rsa_pass_phrase =>
#{default => undefined,
- chk => fun check_string/1,
+ chk => fun(V) -> check_string(V) end,
class => user_option
},
ecdsa_pass_phrase =>
#{default => undefined,
- chk => fun check_string/1,
+ chk => fun(V) -> check_string(V) end,
class => user_option
},
%%% Not yet implemented ed25519_pass_phrase =>
%%% Not yet implemented #{default => undefined,
-%%% Not yet implemented chk => fun check_string/1,
+%%% Not yet implemented chk => fun(V) -> check_string(V) end,
%%% Not yet implemented class => user_option
%%% Not yet implemented },
%%% Not yet implemented
%%% Not yet implemented ed448_pass_phrase =>
%%% Not yet implemented #{default => undefined,
-%%% Not yet implemented chk => fun check_string/1,
+%%% Not yet implemented chk => fun(V) -> check_string(V) end,
%%% Not yet implemented class => user_option
%%% Not yet implemented },
%%% Not yet implemented
silently_accept_hosts =>
#{default => false,
- chk => fun check_silently_accept_hosts/1,
+ chk => fun(V) -> check_silently_accept_hosts(V) end,
class => user_option
},
user_interaction =>
#{default => true,
- chk => fun erlang:is_boolean/1,
+ chk => fun(V) -> erlang:is_boolean(V) end,
class => user_option
},
save_accepted_host =>
#{default => true,
- chk => fun erlang:is_boolean/1,
+ chk => fun(V) -> erlang:is_boolean(V) end,
class => user_option
},
@@ -509,7 +614,7 @@ default(client) ->
connect_timeout =>
#{default => infinity,
- chk => fun check_timeout/1,
+ chk => fun(V) -> check_timeout(V) end,
class => user_option
},
@@ -530,26 +635,26 @@ default(client) ->
User
end
end,
- chk => fun check_string/1,
+ chk => fun(V) -> check_string(V) end,
class => user_option
},
password =>
#{default => undefined,
- chk => fun check_string/1,
+ chk => fun(V) -> check_string(V) end,
class => user_option
},
quiet_mode =>
#{default => false,
- chk => fun erlang:is_boolean/1,
+ chk => fun(V) -> erlang:is_boolean(V) end,
class => user_option
},
%%%%% Undocumented
keyboard_interact_fun =>
#{default => undefined,
- chk => fun check_function3/1,
+ chk => fun(V) -> check_function3(V) end,
class => undoc_user_option
}
};
@@ -562,15 +667,18 @@ default(common) ->
class => user_option
},
+ %% NOTE: This option's default value must be undefined.
+ %% In the final stage that "merges" the modify_algorithms and preferred_algorithms,
+ %% this option's default values is set.
pref_public_key_algs =>
- #{default => ssh_transport:default_algorithms(public_key),
- chk => fun check_pref_public_key_algs/1,
+ #{default => undefined,
+ chk => fun(V) -> check_pref_public_key_algs(V) end,
class => user_option
},
preferred_algorithms =>
#{default => ssh:default_algorithms(),
- chk => fun check_preferred_algorithms/1,
+ chk => fun(V) -> check_preferred_algorithms(V) end,
class => user_option
},
@@ -579,12 +687,16 @@ default(common) ->
%% The preferred_algorithms is the one to use in the rest of the ssh application!
modify_algorithms =>
#{default => undefined, % signals error if unsupported algo in preferred_algorithms :(
- chk => fun check_modify_algorithms/1,
+ chk => fun(V) -> check_modify_algorithms(V) end,
class => user_option
},
id_string =>
- #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0
+ #{default => try {ok, [_|_] = VSN} = application:get_key(ssh, vsn),
+ "Erlang/" ++ VSN
+ catch
+ _:_ -> ""
+ end,
chk => fun(random) ->
{true, {random,2,5}}; % 2 - 5 random characters
({random,I1,I2}) ->
@@ -609,31 +721,31 @@ default(common) ->
profile =>
#{default => ?DEFAULT_PROFILE,
- chk => fun erlang:is_atom/1,
+ chk => fun(V) -> erlang:is_atom(V) end,
class => user_option
},
idle_time =>
#{default => infinity,
- chk => fun check_timeout/1,
+ chk => fun(V) -> check_timeout(V) end,
class => user_option
},
disconnectfun =>
#{default => fun(_) -> void end,
- chk => fun check_function1/1,
+ chk => fun(V) -> check_function1(V) end,
class => user_option
},
unexpectedfun =>
#{default => fun(_,_) -> report end,
- chk => fun check_function2/1,
+ chk => fun(V) -> check_function2(V) end,
class => user_option
},
ssh_msg_debug_fun =>
#{default => fun(_,_,_,_) -> void end,
- chk => fun check_function4/1,
+ chk => fun(V) -> check_function4(V) end,
class => user_option
},
@@ -704,19 +816,19 @@ default(common) ->
tstflg =>
#{default => [],
- chk => fun erlang:is_list/1,
+ chk => fun(V) -> erlang:is_list(V) end,
class => undoc_user_option
},
user_dir_fun =>
#{default => undefined,
- chk => fun check_function1/1,
+ chk => fun(V) -> check_function1(V) end,
class => undoc_user_option
},
max_random_length_padding =>
#{default => ?MAX_RND_PADDING_LEN,
- chk => fun check_non_neg_integer/1,
+ chk => fun(V) -> check_non_neg_integer(V) end,
class => undoc_user_option
}
}.
@@ -902,6 +1014,11 @@ valid_hash(L, Ss) when is_list(L) -> lists:all(fun(S) -> valid_hash(S,Ss) end, L
valid_hash(X, _) -> error_in_check(X, "Expect atom or list in fingerprint spec").
%%%----------------------------------------------------------------
+initial_default_algorithms(DefList, ModList) ->
+ {true, L0} = check_modify_algorithms(ModList),
+ rm_non_supported(false, eval_ops(DefList,L0)).
+
+%%%----------------------------------------------------------------
check_modify_algorithms(M) when is_list(M) ->
[error_in_check(Op_KVs, "Bad modify_algorithms")
|| Op_KVs <- M,
@@ -1021,19 +1138,25 @@ check_input_ok(Algs) ->
orelse (size(KVs) =/= 2)].
%%%----------------------------------------------------------------
-final_preferred_algorithms(Options) ->
+final_preferred_algorithms(Options0) ->
Result =
- case ?GET_OPT(modify_algorithms, Options) of
+ case ?GET_OPT(modify_algorithms, Options0) of
undefined ->
rm_non_supported(true,
- ?GET_OPT(preferred_algorithms, Options));
+ ?GET_OPT(preferred_algorithms, Options0));
ModAlgs ->
rm_non_supported(false,
- eval_ops(?GET_OPT(preferred_algorithms, Options),
+ eval_ops(?GET_OPT(preferred_algorithms, Options0),
ModAlgs))
end,
error_if_empty(Result), % Throws errors if any value list is empty
- ?PUT_OPT({preferred_algorithms,Result}, Options).
+ Options1 = ?PUT_OPT({preferred_algorithms,Result}, Options0),
+ case ?GET_OPT(pref_public_key_algs, Options1) of
+ undefined ->
+ ?PUT_OPT({pref_public_key_algs, proplists:get_value(public_key,Result)}, Options1);
+ _ ->
+ Options1
+ end.
eval_ops(PrefAlgs, ModAlgs) ->
lists:foldl(fun eval_op/2, PrefAlgs, ModAlgs).
@@ -1087,10 +1210,3 @@ error_if_empty([]) ->
ok.
%%%----------------------------------------------------------------
-forbidden_option(K,V) ->
- Txt = io_lib:format("The option '~s' is used internally. The "
- "user is not allowed to specify this option.",
- [K]),
- error({eoptions, {K,V}, Txt}).
-
-%%%----------------------------------------------------------------
diff --git a/lib/ssh/src/ssh_server_key_api.erl b/lib/ssh/src/ssh_server_key_api.erl
index ef1fe7d69c..ec6121d228 100644
--- a/lib/ssh/src/ssh_server_key_api.erl
+++ b/lib/ssh/src/ssh_server_key_api.erl
@@ -23,18 +23,31 @@
-include_lib("public_key/include/public_key.hrl").
-include("ssh.hrl").
--export_type([daemon_key_cb_options/0]).
+-export_type([daemon_key_cb_options/1]).
--type daemon_key_cb_options() :: [{key_cb_private,term()} | ssh:daemon_option()].
+%%%****************************************************************
+%%% The option key_cb_private is to pass options needed by other
+%%% callback modules than the default ssh_file.erl
+%%%
+%%% If ssh:deamon(n, [ {key_cb_private, {hi,{there}}} ]
+%%% is called, the term() will be {hi,{there}}
+-type daemon_key_cb_options(T) :: [{key_cb_private,[T]} | ssh:daemon_option()].
+
+
+%%%****************************************************************
+%%% Fetch the host's private key that is of type Algorithm.
-callback host_key(Algorithm :: ssh:pubkey_alg(),
- DaemonOptions :: daemon_key_cb_options()
+ DaemonOptions :: daemon_key_cb_options(any())
) ->
{ok, PrivateKey :: public_key:private_key()} | {error, term()}.
+%%%****************************************************************
+%%% Check that PublicKey is known to be a public key for the User
+
-callback is_auth_key(PublicKey :: public_key:public_key(),
User :: string(),
- DaemonOptions :: daemon_key_cb_options()
+ DaemonOptions :: daemon_key_cb_options(any())
) ->
boolean().
diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl
index fc0f8488f9..69fff09979 100644
--- a/lib/ssh/src/ssh_sftp.erl
+++ b/lib/ssh/src/ssh_sftp.erl
@@ -165,8 +165,8 @@ start_channel(Cm, UserOptions) when is_pid(Cm) ->
PacketSize = proplists:get_value(packet_size, ChanOpts, ?XFER_PACKET_SIZE),
case ssh_connection:session_channel(Cm, WindowSize, PacketSize, Timeout) of
{ok, ChannelId} ->
- case ssh_client_channel:start(Cm, ChannelId,
- ?MODULE, [Cm, ChannelId, SftpOpts]) of
+ case ssh_connection_handler:start_channel(Cm, ?MODULE, ChannelId,
+ [Cm,ChannelId,SftpOpts], undefined) of
{ok, Pid} ->
case wait_for_version_negotiation(Pid, Timeout) of
ok ->
@@ -175,9 +175,7 @@ start_channel(Cm, UserOptions) when is_pid(Cm) ->
TimeOut
end;
{error, Reason} ->
- {error, format_channel_start_error(Reason)};
- ignore ->
- {error, ignore}
+ {error, format_channel_start_error(Reason)}
end;
Error ->
Error
@@ -1845,7 +1843,9 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Sftp Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
- ].
+ ];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip.
?wr_record(state).
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index 23a6580cf0..1c53690ed0 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -32,6 +32,7 @@
-include("ssh_xfer.hrl").
-include("ssh_connect.hrl"). %% For ?DEFAULT_PACKET_SIZE and ?DEFAULT_WINDOW_SIZE
+
%%--------------------------------------------------------------------
%% External exports
-export([subsystem_spec/1]).
@@ -62,14 +63,15 @@
%%====================================================================
-spec subsystem_spec(Options) -> Spec when
Options :: [ {cwd, string()} |
- {file_handler, CallbackModule::string()} |
+ {file_handler, CbMod | {CbMod, FileState}} |
{max_files, integer()} |
{root, string()} |
{sftpd_vsn, integer()}
],
Spec :: {Name, {CbMod,Options}},
Name :: string(),
- CbMod :: atom() .
+ CbMod :: atom(),
+ FileState :: term().
subsystem_spec(Options) ->
@@ -452,19 +454,19 @@ get_handle(Handles, BinHandle) ->
%%% read_dir/5: read directory, send names, and return new state
read_dir(State0 = #state{file_handler = FileMod, max_files = MaxLength, file_state = FS0},
- XF, ReqId, Handle, RelPath, {cache, Files}) ->
+ XF = #ssh_xfer{cm = _CM, channel = _Channel, vsn = Vsn}, ReqId, Handle, RelPath, {cache, Files}) ->
AbsPath = relate_file_name(RelPath, State0),
if
length(Files) > MaxLength ->
{ToSend, NewCache} = lists:split(MaxLength, Files),
- {NamesAndAttrs, FS1} = get_attrs(AbsPath, ToSend, FileMod, FS0),
+ {NamesAndAttrs, FS1} = get_attrs(AbsPath, ToSend, FileMod, FS0, Vsn),
ssh_xfer:xf_send_names(XF, ReqId, NamesAndAttrs),
Handles = lists:keyreplace(Handle, 1,
State0#state.handles,
{Handle, directory, {RelPath,{cache, NewCache}}}),
State0#state{handles = Handles, file_state = FS1};
true ->
- {NamesAndAttrs, FS1} = get_attrs(AbsPath, Files, FileMod, FS0),
+ {NamesAndAttrs, FS1} = get_attrs(AbsPath, Files, FileMod, FS0, Vsn),
ssh_xfer:xf_send_names(XF, ReqId, NamesAndAttrs),
Handles = lists:keyreplace(Handle, 1,
State0#state.handles,
@@ -472,12 +474,12 @@ read_dir(State0 = #state{file_handler = FileMod, max_files = MaxLength, file_sta
State0#state{handles = Handles, file_state = FS1}
end;
read_dir(State0 = #state{file_handler = FileMod, max_files = MaxLength, file_state = FS0},
- XF, ReqId, Handle, RelPath, _Status) ->
+ XF = #ssh_xfer{cm = _CM, channel = _Channel, vsn = Vsn}, ReqId, Handle, RelPath, _Status) ->
AbsPath = relate_file_name(RelPath, State0),
{Res, FS1} = FileMod:list_dir(AbsPath, FS0),
case Res of
{ok, Files} when MaxLength == 0 orelse MaxLength > length(Files) ->
- {NamesAndAttrs, FS2} = get_attrs(AbsPath, Files, FileMod, FS1),
+ {NamesAndAttrs, FS2} = get_attrs(AbsPath, Files, FileMod, FS1, Vsn),
ssh_xfer:xf_send_names(XF, ReqId, NamesAndAttrs),
Handles = lists:keyreplace(Handle, 1,
State0#state.handles,
@@ -485,7 +487,7 @@ read_dir(State0 = #state{file_handler = FileMod, max_files = MaxLength, file_sta
State0#state{handles = Handles, file_state = FS2};
{ok, Files} ->
{ToSend, Cache} = lists:split(MaxLength, Files),
- {NamesAndAttrs, FS2} = get_attrs(AbsPath, ToSend, FileMod, FS1),
+ {NamesAndAttrs, FS2} = get_attrs(AbsPath, ToSend, FileMod, FS1, Vsn),
ssh_xfer:xf_send_names(XF, ReqId, NamesAndAttrs),
Handles = lists:keyreplace(Handle, 1,
State0#state.handles,
@@ -496,21 +498,74 @@ read_dir(State0 = #state{file_handler = FileMod, max_files = MaxLength, file_sta
send_status({error, Error}, ReqId, State1)
end.
+type_to_string(regular) -> "-";
+type_to_string(directory) -> "d";
+type_to_string(symlink) -> "s";
+type_to_string(device) -> "?";
+type_to_string(undefined) -> "?";
+type_to_string(other) -> "?".
+
+%% Converts a numeric mode to its human-readable representation
+mode_to_string(Mode) ->
+ mode_to_string(Mode, "xwrxwrxwr", []).
+mode_to_string(Mode, [C|T], Acc) when Mode band 1 =:= 1 ->
+ mode_to_string(Mode bsr 1, T, [C|Acc]);
+mode_to_string(Mode, [_|T], Acc) ->
+ mode_to_string(Mode bsr 1, T, [$-|Acc]);
+mode_to_string(_, [], Acc) ->
+ Acc.
+
+%% Converts a POSIX time to a readable string
+time_to_string({{Y, Mon, Day}, {H, Min, _}}) ->
+ io_lib:format("~s ~2w ~s:~s ~w", [month(Mon), Day, two_d(H), two_d(Min), Y]).
+
+two_d(N) ->
+ tl(integer_to_list(N + 100)).
+
+month(1) -> "Jan";
+month(2) -> "Feb";
+month(3) -> "Mar";
+month(4) -> "Apr";
+month(5) -> "May";
+month(6) -> "Jun";
+month(7) -> "Jul";
+month(8) -> "Aug";
+month(9) -> "Sep";
+month(10) -> "Oct";
+month(11) -> "Nov";
+month(12) -> "Dec".
+
+longame({Name, Type, Size, Mtime, Mode, Uid, Gid}) ->
+ io_lib:format("~s~s ~4w/~-4w ~7w ~s ~s\n",
+ [type_to_string(Type), mode_to_string(Mode),
+ Uid, Gid, Size, time_to_string(Mtime), Name]).
+
+%%% get_long_name: get file longname (version 3)
+%%% format output : -rwxr-xr-x 1 uid/gid 348911 Mar 25 14:29 t-filexfer
+get_long_name(FileName, I) when is_record(I, file_info) ->
+ longame({FileName, I#file_info.type, I#file_info.size, I#file_info.mtime,
+ I#file_info.mode, I#file_info.uid, I#file_info.gid}).
%%% get_attrs: get stat of each file and return
-get_attrs(RelPath, Files, FileMod, FS) ->
- get_attrs(RelPath, Files, FileMod, FS, []).
+get_attrs(RelPath, Files, FileMod, FS, Vsn) ->
+ get_attrs(RelPath, Files, FileMod, FS, Vsn, []).
-get_attrs(_RelPath, [], _FileMod, FS, Acc) ->
+get_attrs(_RelPath, [], _FileMod, FS, _Vsn, Acc) ->
{lists:reverse(Acc), FS};
-get_attrs(RelPath, [F | Rest], FileMod, FS0, Acc) ->
+get_attrs(RelPath, [F | Rest], FileMod, FS0, Vsn, Acc) ->
Path = filename:absname(F, RelPath),
case FileMod:read_link_info(Path, FS0) of
{{ok, Info}, FS1} ->
+ Name = if Vsn =< 3 ->
+ LongName = get_long_name(F, Info),
+ {F, LongName};
+ true ->
+ F
+ end,
Attrs = ssh_sftp:info_to_attr(Info),
- get_attrs(RelPath, Rest, FileMod, FS1, [{F, Attrs} | Acc]);
+ get_attrs(RelPath, Rest, FileMod, FS1, Vsn, [{Name, Attrs} | Acc]);
{{error, enoent}, FS1} ->
- get_attrs(RelPath, Rest, FileMod, FS1, Acc);
+ get_attrs(RelPath, Rest, FileMod, FS1, Vsn, Acc);
{Error, FS1} ->
{Error, FS1}
end.
@@ -963,6 +1018,8 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["SftpD Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
- ].
+ ];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip.
?wr_record(state).
diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl
index b5862b2395..bd11afa080 100644
--- a/lib/ssh/src/ssh_shell.erl
+++ b/lib/ssh/src/ssh_shell.erl
@@ -92,7 +92,7 @@ handle_ssh_msg({ssh_cm, _, {data, _ChannelId, 0, Data}}, State) ->
%% TODO: When unicode support is ready
%% should we call this function or perhaps a new
%% function.
- io:put_chars(Data),
+ io:format("~ts", [Data]),
{ok, State};
handle_ssh_msg({ssh_cm, _,
@@ -101,7 +101,7 @@ handle_ssh_msg({ssh_cm, _,
%% TODO: When unicode support is ready
%% should we call this function or perhaps a new
%% function.
- io:put_chars(Data),
+ io:format("~ts", [Data]),
{ok, State};
handle_ssh_msg({ssh_cm, _, {eof, _ChannelId}}, State) ->
@@ -144,9 +144,18 @@ handle_msg({input, IoPid, eof}, #state{io = IoPid, channel = ChannelId,
ssh_connection:send_eof(ConnectionManager, ChannelId),
{ok, State};
-handle_msg({input, IoPid, Line}, #state{io = IoPid,
+handle_msg({input, IoPid, Line0}, #state{io = IoPid,
channel = ChannelId,
cm = ConnectionManager} = State) ->
+ %% Not nice, but it make it work somehow
+ Line = case encoding(Line0) of
+ utf8 ->
+ Line0;
+ unicode ->
+ unicode:characters_to_binary(Line0);
+ latin1 ->
+ unicode:characters_to_binary(Line0,latin1,utf8)
+ end,
ssh_connection:send(ConnectionManager, ChannelId, Line),
{ok, State}.
@@ -161,9 +170,19 @@ terminate(_Reason, #state{io = IoPid}) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+encoding(Bin) ->
+ case unicode:characters_to_binary(Bin,utf8,utf8) of
+ Bin ->
+ utf8;
+ Bin2 when is_binary(Bin2) ->
+ unicode;
+ _ ->
+ latin1
+ end.
+%%--------------------------------------------------------------------
input_loop(Fd, Pid) ->
- case io:get_line(Fd, '>') of
+ case io:get_line(Fd, '') of
eof ->
Pid ! {input, self(), eof},
ok;
@@ -189,17 +208,70 @@ get_ancestors() ->
%%%# Tracing
%%%#
-ssh_dbg_trace_points() -> [terminate].
+ssh_dbg_trace_points() -> [terminate, shell].
+ssh_dbg_flags(shell) -> [c];
ssh_dbg_flags(terminate) -> [c].
+ssh_dbg_on(shell) -> dbg:tp(?MODULE,handle_ssh_msg,2,x);
ssh_dbg_on(terminate) -> dbg:tp(?MODULE, terminate, 2, x).
+ssh_dbg_off(shell) -> dbg:ctpg(?MODULE,handle_ssh_msg,2);
ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2).
+
+ssh_dbg_format(shell, {call,{?MODULE,handle_ssh_msg,
+ [{ssh_cm, _ConnectionHandler, Request},
+ #state{channel=Ch}]}}) when is_tuple(Request) ->
+ [io_lib:format("SHELL conn ~p chan ~p, req ~p",
+ [self(),Ch,element(1,Request)]),
+ case Request of
+ {window_change, ChannelId, Width, Height, PixWidth, PixHeight} ->
+ fmt_kv([{channel_id,ChannelId},
+ {width,Width}, {height,Height},
+ {pix_width,PixWidth}, {pixel_hight,PixHeight}]);
+
+ {env, ChannelId, WantReply, Var, Value} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply}, {Var,Value}]);
+
+ {exec, ChannelId, WantReply, Cmd} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply}, {command,Cmd}]);
+
+ {pty, ChannelId, WantReply,
+ {TermName, Width, Height, PixWidth, PixHeight, Modes}} ->
+ fmt_kv([{channel_id,ChannelId}, {want_reply,WantReply},
+ {term,TermName},
+ {width,Width}, {height,Height}, {pix_width,PixWidth}, {pixel_hight,PixHeight},
+ {pty_opts, Modes}]);
+
+ {data, ChannelId, Type, Data} ->
+ fmt_kv([{channel_id,ChannelId},
+ {type, case Type of
+ 0 -> "0 (normal data)";
+ 1 -> "1 (extended data, i.e. errors)";
+ _ -> Type
+ end},
+ {data, ssh_dbg:shrink_bin(Data)},
+ {hex, h, Data}
+ ]);
+ _ ->
+ io_lib:format("~nunder construction:~nRequest = ~p",[Request])
+ end];
+ssh_dbg_format(shell, {call,{?MODULE,handle_ssh_msg,_}}) -> skip;
+ssh_dbg_format(shell, {return_from,{?MODULE,handle_ssh_msg,2},_Result}) -> skip;
+
ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
["Shell Terminating:\n",
io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
- ].
+ ];
+ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) ->
+ skip.
?wr_record(state).
+
+fmt_kv(KVs) -> lists:map(fun fmt_kv1/1, KVs).
+
+fmt_kv1({K,V}) -> io_lib:format("~n~p: ~p",[K,V]);
+fmt_kv1({K,s,V}) -> io_lib:format("~n~p: ~s",[K,V]);
+fmt_kv1({K,h,V}) -> io_lib:format("~n~p: ~s",[K, [$\n|ssh_dbg:hex_dump(V)]]).
+
diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl
index 5fc8f7e764..140b219b32 100644
--- a/lib/ssh/src/ssh_subsystem_sup.erl
+++ b/lib/ssh/src/ssh_subsystem_sup.erl
@@ -30,7 +30,9 @@
-export([start_link/5,
connection_supervisor/1,
- channel_supervisor/1
+ channel_supervisor/1,
+ tcpip_fwd_supervisor/1,
+ start_channel/8
]).
%% Supervisor callback
@@ -46,9 +48,17 @@ connection_supervisor(SupPid) ->
Children = supervisor:which_children(SupPid),
ssh_connection_sup(Children).
-channel_supervisor(SupPid) ->
+channel_supervisor(SupPid) when is_pid(SupPid) ->
Children = supervisor:which_children(SupPid),
- ssh_server_channel_sup(Children).
+ ssh_channel_sup(Children).
+
+tcpip_fwd_supervisor(SupPid) when is_pid(SupPid) ->
+ Children = supervisor:which_children(SupPid),
+ tcpip_fwd_sup(Children).
+
+start_channel(Role, SupPid, ConnRef, Callback, Id, Args, Exec, Opts) ->
+ ChannelSup = channel_supervisor(SupPid),
+ ssh_channel_sup:start_child(Role, ChannelSup, ConnRef, Callback, Id, Args, Exec, Opts).
%%%=========================================================================
%%% Supervisor callback
@@ -64,11 +74,10 @@ init([Role, Address, Port, Profile, Options]) ->
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-child_specs(client, _Address, _Port, _Profile, _Options) ->
- [];
-child_specs(server, Address, Port, Profile, Options) ->
- [ssh_channel_child_spec(server, Address, Port, Profile, Options),
- ssh_connection_child_spec(server, Address, Port, Profile, Options)].
+child_specs(Role, Address, Port, Profile, Options) ->
+ [ssh_channel_child_spec(Role, Address, Port, Profile, Options),
+ ssh_connection_child_spec(Role, Address, Port, Profile, Options),
+ ssh_tcpip_forward_acceptor_child_spec()].
ssh_connection_child_spec(Role, Address, Port, _Profile, Options) ->
#{id => id(Role, ssh_connection_sup, Address, Port),
@@ -78,12 +87,20 @@ ssh_connection_child_spec(Role, Address, Port, _Profile, Options) ->
}.
ssh_channel_child_spec(Role, Address, Port, _Profile, Options) ->
- #{id => id(Role, ssh_server_channel_sup, Address, Port),
- start => {ssh_server_channel_sup, start_link, [Options]},
+ #{id => id(Role, ssh_channel_sup, Address, Port),
+ start => {ssh_channel_sup, start_link, [Options]},
+ restart => temporary,
+ type => supervisor
+ }.
+
+ssh_tcpip_forward_acceptor_child_spec() ->
+ #{id => make_ref(),
+ start => {ssh_tcpip_forward_acceptor_sup, start_link, []},
restart => temporary,
type => supervisor
}.
+
id(Role, Sup, Address, Port) ->
{Role, Sup, Address, Port}.
@@ -92,10 +109,13 @@ ssh_connection_sup([{_, Child, _, [ssh_connection_sup]} | _]) ->
ssh_connection_sup([_ | Rest]) ->
ssh_connection_sup(Rest).
-ssh_server_channel_sup([{_, Child, _, [ssh_server_channel_sup]} | _]) ->
+ssh_channel_sup([{_, Child, _, [ssh_channel_sup]} | _]) ->
Child;
-ssh_server_channel_sup([_ | Rest]) ->
- ssh_server_channel_sup(Rest).
-
+ssh_channel_sup([_ | Rest]) ->
+ ssh_channel_sup(Rest).
+tcpip_fwd_sup([{_, Child, _, [ssh_tcpip_forward_acceptor_sup]} | _]) ->
+ Child;
+tcpip_fwd_sup([_ | Rest]) ->
+ tcpip_fwd_sup(Rest).
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index 75023a3d77..d80fe3e296 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -31,9 +31,9 @@
-include("ssh.hrl").
--export([start_link/4, stop_listener/1,
- stop_listener/3, stop_system/1,
- stop_system/3, system_supervisor/3,
+-export([start_link/5, stop_listener/1,
+ stop_listener/3, stop_system/2,
+ stop_system/4, system_supervisor/3,
subsystem_supervisor/1, channel_supervisor/1,
connection_supervisor/1,
acceptor_supervisor/1, start_subsystem/6,
@@ -50,14 +50,14 @@
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Address, Port, Profile, Options) ->
+start_link(Role, Address, Port, Profile, Options) ->
Name = make_name(Address, Port, Profile),
- supervisor:start_link({local, Name}, ?MODULE, [Address, Port, Profile, Options]).
+ supervisor:start_link({local, Name}, ?MODULE, [Role, Address, Port, Profile, Options]).
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-init([Address, Port, Profile, Options]) ->
+init([server, Address, Port, Profile, Options]) ->
SupFlags = #{strategy => one_for_one,
intensity => 0,
period => 3600
@@ -73,6 +73,14 @@ init([Address, Port, Profile, Options]) ->
_ ->
[]
end,
+ {ok, {SupFlags,ChildSpecs}};
+
+init([client, _Address, _Port, _Profile, _Options]) ->
+ SupFlags = #{strategy => one_for_one,
+ intensity => 0,
+ period => 3600
+ },
+ ChildSpecs = [],
{ok, {SupFlags,ChildSpecs}}.
%%%=========================================================================
@@ -92,11 +100,10 @@ stop_listener(Address, Port, Profile) ->
system_supervisor(Address, Port, Profile)).
-stop_system(SysSup) ->
- catch sshd_sup:stop_child(SysSup),
- ok.
+stop_system(server, SysSup) -> catch sshd_sup:stop_child(SysSup), ok;
+stop_system(client, SysSup) -> catch sshc_sup:stop_child(SysSup), ok.
-stop_system(Address, Port, Profile) ->
+stop_system(server, Address, Port, Profile) ->
catch sshd_sup:stop_child(Address, Port, Profile),
ok.
diff --git a/lib/ssh/src/ssh_tcpip_forward_acceptor.erl b/lib/ssh/src/ssh_tcpip_forward_acceptor.erl
new file mode 100644
index 0000000000..6f2fda2bf9
--- /dev/null
+++ b/lib/ssh/src/ssh_tcpip_forward_acceptor.erl
@@ -0,0 +1,116 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The supervisor for tcpip-forwarding acceptor
+%%----------------------------------------------------------------------
+
+-module(ssh_tcpip_forward_acceptor).
+
+-export([supervised_start/6,
+ start_link/6]).
+
+-include("ssh.hrl").
+
+%%%----------------------------------------------------------------
+supervised_start(FwdSup, {ListenAddrStr, ListenPort}, ConnectToAddr, ChanType, ChanCB, ConnPid) ->
+ case get_fwd_listen_opts(ListenAddrStr) of
+ {ok,Opts} ->
+ %% start listening on Addr:BoundPort
+ case gen_tcp:listen(ListenPort, [binary,
+ {reuseaddr,true},
+ {active,false} | Opts]) of
+ {ok,LSock} ->
+ {ok,{_, TrueListenPort}} = inet:sockname(LSock),
+ ssh_tcpip_forward_acceptor_sup:start_child(FwdSup,
+ LSock,
+ {ListenAddrStr,TrueListenPort},
+ ConnectToAddr,
+ ChanType,
+ ChanCB,
+ ConnPid),
+ {ok, TrueListenPort};
+
+ {error,Error} ->
+ {error,Error}
+ end;
+
+ {error,Error} ->
+ {error,Error}
+ end.
+
+
+%%%----------------------------------------------------------------
+start_link(LSock, {ListenAddrStr,ListenPort}, ConnectToAddr, ChanType, ChanCB, ConnPid) ->
+ Pid = proc_lib:spawn_link(
+ fun() ->
+ acceptor_loop(LSock, ListenAddrStr, ListenPort, ConnectToAddr, ChanType, ChanCB, ConnPid)
+ end),
+ {ok, Pid}.
+
+%%%================================================================
+%%%
+%%% Internal
+%%%
+acceptor_loop(LSock, ListenAddrStr, ListenPort, ConnectToAddr, ChanType, ChanCB, ConnPid) ->
+ case gen_tcp:accept(LSock) of
+ {ok, Sock} ->
+ {ok, {RemHost,RemPort}} = inet:peername(Sock),
+ RemHostBin = list_to_binary(encode_ip(RemHost)),
+ Data =
+ case ConnectToAddr of
+ undefined ->
+ <<?STRING(ListenAddrStr), ?UINT32(ListenPort),
+ ?STRING(RemHostBin), ?UINT32(RemPort)>>;
+ {ConnectToHost, ConnectToPort} ->
+ <<?STRING(ConnectToHost), ?UINT32(ConnectToPort),
+ ?STRING(RemHostBin), ?UINT32(RemPort)>>
+ end,
+ case ssh_connection:open_channel(ConnPid, ChanType, Data, infinity) of
+ {ok,ChId} ->
+ gen_tcp:controlling_process(Sock, ConnPid),
+ ConnPid ! {fwd_connect_received, Sock, ChId, ChanCB};
+ _ ->
+ gen_tcp:close(Sock)
+ end,
+ acceptor_loop(LSock, ListenAddrStr, ListenPort, ConnectToAddr, ChanType, ChanCB, ConnPid);
+
+ {error,closed} ->
+ ok
+ end.
+
+%%%----------------------------------------------------------------
+get_fwd_listen_opts(<<"">> ) -> {ok, []};
+get_fwd_listen_opts(<<"0.0.0.0">> ) -> {ok, [inet]};
+get_fwd_listen_opts(<<"::">> ) -> {ok, [inet6]};
+get_fwd_listen_opts(<<"localhost">>) -> {ok, [{ip,loopback}]};
+get_fwd_listen_opts(AddrStr) ->
+ case inet:getaddr(binary_to_list(AddrStr), inet) of
+ {ok, Addr} -> {ok, [{ip,Addr}]};
+ {error,Error} -> {error,Error}
+ end.
+
+%%%----------------------------------------------------------------
+encode_ip(Addr) when is_tuple(Addr) ->
+ case catch inet_parse:ntoa(Addr) of
+ {'EXIT',_} -> false;
+ A -> A
+ end.
diff --git a/lib/ssh/src/ssh_server_channel_sup.erl b/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl
index ff74061bb3..522ce650ff 100644
--- a/lib/ssh/src/ssh_server_channel_sup.erl
+++ b/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl
@@ -20,42 +20,42 @@
%%
%%----------------------------------------------------------------------
-%% Purpose: Ssh channel supervisor.
+%% Purpose: The supervisor for tcpip-forwarding acceptor
%%----------------------------------------------------------------------
--module(ssh_server_channel_sup).
+-module(ssh_tcpip_forward_acceptor_sup).
-behaviour(supervisor).
--export([start_link/1, start_child/5]).
+-include("ssh.hrl").
+
+-export([start_link/0, start_child/7]).
%% Supervisor callback
-export([init/1]).
%%%=========================================================================
-%%% Internal API
+%%% API
%%%=========================================================================
-start_link(Args) ->
- supervisor:start_link(?MODULE, [Args]).
-
-start_child(Sup, Callback, Id, Args, Exec) ->
- ChildSpec =
- #{id => make_ref(),
- start => {ssh_server_channel, start_link, [self(), Id, Callback, Args, Exec]},
- restart => temporary,
- type => worker,
- modules => [ssh_server_channel]
- },
- supervisor:start_child(Sup, ChildSpec).
+start_link() ->
+ supervisor:start_link(?MODULE, []).
+
+start_child(Sup, LSock, ListenAddr, ConnectToAddr, ChanType, ChanCB, ConnPid) ->
+ Args = [LSock, ListenAddr, ConnectToAddr, ChanType, ChanCB, ConnPid],
+ supervisor:start_child(Sup, Args).
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-init(_Args) ->
- RestartStrategy = one_for_one,
- MaxR = 10,
- MaxT = 3600,
- Children = [],
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+init([]) ->
+ SupFlags = #{strategy => simple_one_for_one,
+ intensity => 10,
+ period => 3600
+ },
+ ChildSpecs = [#{id => undefined, % As simple_one_for_one is used.
+ start => {ssh_tcpip_forward_acceptor, start_link, []}
+ }
+ ],
+ {ok, {SupFlags,ChildSpecs}}.
%%%=========================================================================
%%% Internal functions
diff --git a/lib/ssh/src/ssh_tcpip_forward_client.erl b/lib/ssh/src/ssh_tcpip_forward_client.erl
new file mode 100644
index 0000000000..490d283461
--- /dev/null
+++ b/lib/ssh/src/ssh_tcpip_forward_client.erl
@@ -0,0 +1,84 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(ssh_tcpip_forward_client).
+
+-behaviour(ssh_client_channel).
+
+-record(state, {
+ id, cm,
+ fwd_socket
+ }).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_msg/2, handle_ssh_msg/2, terminate/2, code_change/3]).
+
+init([FwdSocket]) ->
+ {ok, #state{fwd_socket=FwdSocket}}.
+
+
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) ->
+ {ok, State#state{id = ChannelId,
+ cm = ConnectionManager}};
+
+handle_msg({tcp,Sock,Data}, #state{fwd_socket = Sock,
+ cm = CM,
+ id = ChId} = State) ->
+ ssh_connection:send(CM, ChId, Data),
+ inet:setopts(Sock, [{active,once}]),
+ {ok, State};
+
+handle_msg({tcp_closed,Sock}, #state{fwd_socket = Sock,
+ cm = CM,
+ id = ChId} = State) ->
+ ssh_connection:send_eof(CM, ChId),
+ {stop, ChId, State#state{fwd_socket=undefined}}.
+
+
+
+handle_ssh_msg({ssh_cm, _CM, {data, _ChannelId, _Type, Data}}, #state{fwd_socket=Sock} = State) ->
+ gen_tcp:send(Sock, Data),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _CM, {eof, ChId}}, State) ->
+ {stop, ChId, State};
+
+handle_ssh_msg({ssh_cm, _CM, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _CM, {exit_signal, ChId, _, _Error, _}}, State) ->
+ {stop, ChId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChId, _Status}}, State) ->
+ {stop, ChId, State}.
+
+
+terminate(_Reason, #state{fwd_socket=Sock}) ->
+ gen_tcp:close(Sock),
+ ok.
+
+
+handle_call(Req, _, S) -> {reply, {unknown,Req}, S}.
+
+handle_cast(_, S) -> {noreply, S}.
+
+code_change(_, S, _) -> {ok, S}.
+
+
diff --git a/lib/ssh/src/ssh_tcpip_forward_srv.erl b/lib/ssh/src/ssh_tcpip_forward_srv.erl
new file mode 100644
index 0000000000..a29f70cf48
--- /dev/null
+++ b/lib/ssh/src/ssh_tcpip_forward_srv.erl
@@ -0,0 +1,75 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(ssh_tcpip_forward_srv).
+
+-behaviour(ssh_server_channel).
+
+-record(state, {
+ id, cm,
+ fwd_socket
+ }).
+
+-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+
+init([FwdSocket]) ->
+ {ok, #state{fwd_socket=FwdSocket}}.
+
+
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) ->
+ {ok, State#state{id = ChannelId,
+ cm = ConnectionManager}};
+
+handle_msg({tcp,Sock,Data}, #state{fwd_socket = Sock,
+ cm = CM,
+ id = ChId} = State) ->
+ ssh_connection:send(CM, ChId, Data),
+ inet:setopts(Sock, [{active,once}]),
+ {ok, State};
+
+handle_msg({tcp_closed,Sock}, #state{fwd_socket = Sock,
+ cm = CM,
+ id = ChId} = State) ->
+ ssh_connection:send_eof(CM, ChId),
+ {stop, ChId, State#state{fwd_socket=undefined}}.
+
+
+
+handle_ssh_msg({ssh_cm, _CM, {data, _ChannelId, _Type, Data}}, #state{fwd_socket=Sock} = State) ->
+ gen_tcp:send(Sock, Data),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _CM, {eof, ChId}}, State) ->
+ {stop, ChId, State};
+
+handle_ssh_msg({ssh_cm, _CM, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _CM, {exit_signal, ChId, _, _Error, _}}, State) ->
+ {stop, ChId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChId, _Status}}, State) ->
+ {stop, ChId, State}.
+
+
+terminate(_Reason, #state{fwd_socket=Sock}) ->
+ gen_tcp:close(Sock),
+ ok.
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index f5e4c96c17..813d5ad350 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -34,6 +34,7 @@
-export([next_seqnum/1,
supported_algorithms/0, supported_algorithms/1,
default_algorithms/0, default_algorithms/1,
+ clear_default_algorithms_env/0,
algo_classes/0, algo_class/1,
algo_two_spec_classes/0, algo_two_spec_class/1,
handle_packet_part/5,
@@ -50,17 +51,17 @@
parallell_gen_key/1,
extract_public_key/1,
ssh_packet/2, pack/2,
- valid_key_sha_alg/2,
+ valid_key_sha_alg/3,
sha/1, sign/3, verify/5,
get_host_key/2,
- call_KeyCb/3]).
+ call_KeyCb/3,
+ public_algo/1]).
-behaviour(ssh_dbg).
-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2]).
%%% For test suites
-export([pack/3, adjust_algs_for_peer_version/2]).
--export([decompress/2, decrypt_blocks/3, is_valid_mac/3 ]). % FIXME: remove
%%%----------------------------------------------------------------------------
%%%
@@ -76,7 +77,46 @@
%%% and test them without letting the default users know about them.
%%%
-default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()].
+-define(DEFAULT_ALGS, '$def-algs$').
+
+clear_default_algorithms_env() ->
+ application:unset_env(ssh, ?DEFAULT_ALGS).
+
+-spec default_algorithms() -> algs_list()
+ | no_return() % error(Reason)
+ .
+default_algorithms() ->
+ case application:get_env(ssh, ?DEFAULT_ALGS) of
+ undefined ->
+ %% Not cached, have to build the default, connection independent
+ %% set of algorithms:
+ Opts = get_alg_conf(),
+ Algs1 =
+ case proplists:get_value(preferred_algorithms, Opts) of
+ undefined ->
+ [{K,default_algorithms1(K)} || K <- algo_classes()];
+ Algs0 ->
+ {true,Algs01} = ssh_options:check_preferred_algorithms(Algs0),
+ Algs01
+ end,
+ Algs =
+ case proplists:get_value(modify_algorithms, Opts) of
+ undefined ->
+ Algs1;
+ Modifications ->
+ ssh_options:initial_default_algorithms(Algs1, Modifications)
+ end,
+ application:set_env(ssh, ?DEFAULT_ALGS, Algs),
+ Algs;
+
+ {ok,Algs} ->
+ Algs
+ end.
+
+get_alg_conf() ->
+ [{T,L} || T <- [preferred_algorithms, modify_algorithms],
+ L <- [application:get_env(ssh, T, [])],
+ L =/= []].
algo_classes() -> [kex, public_key, cipher, mac, compression].
@@ -95,25 +135,42 @@ algo_two_spec_class(mac) -> true;
algo_two_spec_class(compression) -> true;
algo_two_spec_class(_) -> false.
+
+default_algorithms(Tag) ->
+ case application:get_env(ssh, ?DEFAULT_ALGS) of
+ undefined ->
+ default_algorithms1(Tag);
+ {ok,Algs} ->
+ proplists:get_value(Tag, Algs, [])
+ end.
-default_algorithms(kex) ->
+default_algorithms1(kex) ->
supported_algorithms(kex, [
%% Gone in OpenSSH 7.3.p1:
- 'diffie-hellman-group1-sha1'
+ 'diffie-hellman-group1-sha1',
+ %% Gone in OpenSSH 8.2
+ 'diffie-hellman-group14-sha1',
+ 'diffie-hellman-group-exchange-sha1'
]);
-default_algorithms(cipher) ->
+default_algorithms1(cipher) ->
supported_algorithms(cipher, same(['AEAD_AES_128_GCM',
'AEAD_AES_256_GCM'
]));
-default_algorithms(mac) ->
+default_algorithms1(mac) ->
supported_algorithms(mac, same(['AEAD_AES_128_GCM',
'AEAD_AES_256_GCM',
'hmac-sha1-96'
]));
-default_algorithms(Alg) ->
+default_algorithms1(public_key) ->
+ supported_algorithms(public_key, [
+ %% Gone in OpenSSH 7.3.p1:
+ 'ssh-dss'
+ ]);
+
+default_algorithms1(Alg) ->
supported_algorithms(Alg, []).
@@ -146,9 +203,9 @@ supported_algorithms(public_key) ->
{'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {curves,secp256r1}]},
{'ssh-ed25519', [{public_keys,eddsa}, {curves,ed25519} ]},
{'ssh-ed448', [{public_keys,eddsa}, {curves,ed448} ]},
- {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]},
{'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]},
{'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]},
+ {'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]},
{'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1
]);
@@ -173,8 +230,11 @@ supported_algorithms(cipher) ->
supported_algorithms(mac) ->
same(
select_crypto_supported(
- [{'hmac-sha2-256', [{macs,hmac}, {hashs,sha256}]},
+ [{'hmac-sha2-256-etm@openssh.com', [{macs,hmac}, {hashs,sha256}]},
+ {'hmac-sha2-512-etm@openssh.com', [{macs,hmac}, {hashs,sha256}]},
+ {'hmac-sha2-256', [{macs,hmac}, {hashs,sha256}]},
{'hmac-sha2-512', [{macs,hmac}, {hashs,sha512}]},
+ {'hmac-sha1-etm@openssh.com', [{macs,hmac}, {hashs,sha256}]},
{'hmac-sha1', [{macs,hmac}, {hashs,sha}]},
{'hmac-sha1-96', [{macs,hmac}, {hashs,sha}]},
{'AEAD_AES_128_GCM', [{ciphers,aes_128_gcm}]},
@@ -195,27 +255,19 @@ versions(server, Options) ->
Vsn = ?GET_INTERNAL_OPT(vsn, Options, ?DEFAULT_SERVER_VERSION),
{Vsn, format_version(Vsn, software_version(Options))}.
+format_version({Major,Minor}, "") ->
+ lists:concat(["SSH-",Major,".",Minor]);
+format_version({Major,Minor}, SoftwareVersion) ->
+ lists:concat(["SSH-",Major,".",Minor,"-",SoftwareVersion]).
+
software_version(Options) ->
case ?GET_OPT(id_string, Options) of
- undefined ->
- "Erlang"++ssh_vsn();
{random,Nlo,Nup} ->
random_id(Nlo,Nup);
ID ->
ID
end.
-ssh_vsn() ->
- try {ok,L} = application:get_all_key(ssh),
- proplists:get_value(vsn, L, "")
- of
- "" -> "";
- VSN when is_list(VSN) -> "/" ++ VSN;
- _ -> ""
- catch
- _:_ -> ""
- end.
-
random_id(Nlo, Nup) ->
[$a + rand:uniform($z-$a+1) - 1 || _<- lists:duplicate(Nlo + rand:uniform(Nup-Nlo+1) - 1, x)].
@@ -225,21 +277,12 @@ hello_version_msg(Data) ->
next_seqnum(SeqNum) ->
(SeqNum + 1) band 16#ffffffff.
-decrypt_blocks(Bin, Length, Ssh0) ->
- <<EncBlocks:Length/binary, EncData/binary>> = Bin,
- {Ssh, DecData} = decrypt(Ssh0, EncBlocks),
- {Ssh, DecData, EncData}.
-
is_valid_mac(_, _ , #ssh{recv_mac_size = 0}) ->
true;
is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm,
recv_mac_key = Key, recv_sequence = SeqNum}) ->
crypto:equal_const_time(Mac, mac(Algorithm, Key, SeqNum, Data)).
-format_version({Major,Minor}, SoftwareVersion) ->
- "SSH-" ++ integer_to_list(Major) ++ "." ++
- integer_to_list(Minor) ++ "-" ++ SoftwareVersion.
-
handle_hello_version(Version) ->
try
StrVersion = trim_tail(Version),
@@ -786,7 +829,7 @@ get_host_key(SignAlg, Opts) ->
case call_KeyCb(host_key, [SignAlg], Opts) of
{ok, PrivHostKey} ->
%% Check the key - the KeyCb may be a buggy plugin
- case valid_key_sha_alg(PrivHostKey, SignAlg) of
+ case valid_key_sha_alg(private, PrivHostKey, SignAlg) of
true -> PrivHostKey;
false -> exit({error, bad_hostkey})
end;
@@ -796,7 +839,7 @@ get_host_key(SignAlg, Opts) ->
call_KeyCb(F, Args, Opts) ->
{KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts),
- UserOpts = ?GET_OPT(user_options, Opts),
+ UserOpts = ?GET_OPT(key_cb_options, Opts),
apply(KeyCb, F, Args ++ [[{key_cb_private,KeyCbOpts}|UserOpts]]).
extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) ->
@@ -804,7 +847,7 @@ extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) ->
extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) ->
{Y, #'Dss-Parms'{p=P, q=Q, g=G}};
extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},
- publicKey = Q}) ->
+ publicKey = Q}) when is_tuple(OID) ->
{#'ECPoint'{point=Q}, {namedCurve,OID}};
extract_public_key({ed_pri, Alg, Pub, _Priv}) ->
{ed_pub, Alg, Pub};
@@ -833,12 +876,15 @@ verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, {AlgStr,Signature})
%%% -> boolean() | {error,_}
-accepted_host(Ssh, PeerName, Public, Opts) ->
+accepted_host(Ssh, PeerName, Port, Public, Opts) ->
+ PortStr = case Port of
+ 22 -> "";
+ _ -> lists:concat([":",Port])
+ end,
case ?GET_OPT(silently_accept_hosts, Opts) of
-
%% Original option values; User question and no host key fingerprints known.
%% Keep the original question unchanged:
- false -> yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept");
+ false -> yes == yes_no(Ssh, "New host " ++ PeerName ++ PortStr ++ " accept");
true -> true;
%% Variant: User question but with host key fingerprint in the question:
@@ -846,10 +892,10 @@ accepted_host(Ssh, PeerName, Public, Opts) ->
HostKeyAlg = (Ssh#ssh.algorithms)#alg.hkey,
Prompt = io_lib:format("The authenticity of the host can't be established.~n"
"~s host key fingerprint is ~s.~n"
- "New host ~p accept",
+ "New host ~p~p accept",
[fmt_hostkey(HostKeyAlg),
public_key:ssh_hostkey_fingerprint(Alg,Public),
- PeerName]),
+ PeerName, PortStr]),
yes == yes_no(Ssh, Prompt);
%% Call-back alternatives: A user provided fun is called for the decision:
@@ -859,10 +905,22 @@ accepted_host(Ssh, PeerName, Public, Opts) ->
_ -> {error, fingerprint_check_failed}
end;
+ F when is_function(F,3) ->
+ case catch F(PeerName, Port, public_key:ssh_hostkey_fingerprint(Public)) of
+ true -> true;
+ _ -> {error, fingerprint_check_failed}
+ end;
+
{DigestAlg,F} when is_function(F,2) ->
case catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public)) of
true -> true;
_ -> {error, {fingerprint_check_failed,DigestAlg}}
+ end;
+
+ {DigestAlg,F} when is_function(F,3) ->
+ case catch F(PeerName, Port, public_key:ssh_hostkey_fingerprint(DigestAlg,Public)) of
+ true -> true;
+ _ -> {error, {fingerprint_check_failed,DigestAlg}}
end
end.
@@ -881,25 +939,46 @@ fmt_hostkey("ecdsa"++_) -> "ECDSA";
fmt_hostkey(X) -> X.
-known_host_key(#ssh{opts = Opts, peer = {PeerName,_}} = Ssh,
+known_host_key(#ssh{opts = Opts, peer = {PeerName,{IP,Port}}} = Ssh,
Public, Alg) ->
- case call_KeyCb(is_host_key, [Public, PeerName, Alg], Opts) of
+ IsHostKey =
+ try
+ %% New style (with Port)
+ call_KeyCb(is_host_key, [Public, [PeerName,IP], Port, Alg], Opts)
+ catch
+ error:undef ->
+ %% old style (without Port)
+ call_KeyCb(is_host_key, [Public, PeerName, Alg], Opts)
+ end,
+
+ case IsHostKey of
true ->
ok;
false ->
+ %% Not in "known_hosts" and, if is_host_key/4, not revoked
DoAdd = ?GET_OPT(save_accepted_host, Opts),
- case accepted_host(Ssh, PeerName, Public, Opts) of
+ case accepted_host(Ssh, PeerName, Port, Public, Opts) of
true when DoAdd == true ->
- call_KeyCb(add_host_key, [PeerName, Public], Opts);
+ try
+ %% New style (with Port)
+ call_KeyCb(add_host_key, [[PeerName,IP], Port, Public], Opts)
+ catch
+ error:undef ->
+ %% old style (without Port)
+ call_KeyCb(add_host_key, [PeerName, Public], Opts)
+ end;
true when DoAdd == false ->
ok;
false ->
{error, rejected_by_user};
{error,E} ->
{error,E}
- end
+ end;
+ {error, Error} ->
+ %% Only returned by is_host_key/4
+ {error, Error}
end.
-
+
%% Each of the algorithm strings MUST be a comma-separated list of
%% algorithm names (see ''Algorithm Naming'' in [SSH-ARCH]). Each
%% supported (allowed) algorithm MUST be listed in order of preference.
@@ -1146,50 +1225,54 @@ pack(Data, Ssh=#ssh{}) ->
pack(PlainText,
#ssh{send_sequence = SeqNum,
send_mac = MacAlg,
- send_mac_key = MacKey,
encrypt = CryptoAlg} = Ssh0, PacketLenDeviationForTests) when is_binary(PlainText) ->
-
{Ssh1, CompressedPlainText} = compress(Ssh0, PlainText),
- {FinalPacket, Ssh3} =
- case pkt_type(CryptoAlg) of
- common ->
- PaddingLen = padding_length(4+1+size(CompressedPlainText), Ssh0),
- Padding = ssh_bits:random(PaddingLen),
- PlainPacketLen = 1 + PaddingLen + size(CompressedPlainText) + PacketLenDeviationForTests,
- PlainPacketData = <<?UINT32(PlainPacketLen),?BYTE(PaddingLen), CompressedPlainText/binary, Padding/binary>>,
- {Ssh2, EcryptedPacket0} = encrypt(Ssh1, PlainPacketData),
- MAC0 = mac(MacAlg, MacKey, SeqNum, PlainPacketData),
- {<<EcryptedPacket0/binary,MAC0/binary>>, Ssh2};
- aead ->
- PaddingLen = padding_length(1+size(CompressedPlainText), Ssh0),
- Padding = ssh_bits:random(PaddingLen),
- PlainPacketLen = 1 + PaddingLen + size(CompressedPlainText) + PacketLenDeviationForTests,
- PlainPacketData = <<?BYTE(PaddingLen), CompressedPlainText/binary, Padding/binary>>,
- {Ssh2, {EcryptedPacket0,MAC0}} = encrypt(Ssh1, <<?UINT32(PlainPacketLen),PlainPacketData/binary>>),
- {<<EcryptedPacket0/binary,MAC0/binary>>, Ssh2}
- end,
- Ssh = Ssh3#ssh{send_sequence = (SeqNum+1) band 16#ffffffff},
+ {FinalPacket, Ssh2} = pack(pkt_type(CryptoAlg), mac_type(MacAlg),
+ CompressedPlainText, PacketLenDeviationForTests,
+ Ssh1),
+ Ssh = Ssh2#ssh{send_sequence = (SeqNum+1) band 16#ffffffff},
{FinalPacket, Ssh}.
-padding_length(Size, #ssh{encrypt_block_size = BlockSize,
- random_length_padding = RandomLengthPadding}) ->
- PL = (BlockSize - (Size rem BlockSize)) rem BlockSize,
- MinPaddingLen = if PL < 4 -> PL + BlockSize;
- true -> PL
- end,
- PadBlockSize = max(BlockSize,4),
- MaxExtraBlocks = (max(RandomLengthPadding,MinPaddingLen) - MinPaddingLen) div PadBlockSize,
- ExtraPaddingLen = try (rand:uniform(MaxExtraBlocks+1) - 1) * PadBlockSize
- catch _:_ -> 0
- end,
- MinPaddingLen + ExtraPaddingLen.
-
-
-
-handle_packet_part(<<>>, Encrypted0, AEAD0, undefined, #ssh{decrypt = CryptoAlg} = Ssh0) ->
+pack(common, rfc4253, PlainText, DeltaLenTst,
+ #ssh{send_sequence = SeqNum,
+ send_mac = MacAlg,
+ send_mac_key = MacKey} = Ssh0) ->
+ PadLen = padding_length(4+1+size(PlainText), Ssh0),
+ Pad = ssh_bits:random(PadLen),
+ TextLen = 1 + size(PlainText) + PadLen + DeltaLenTst,
+ PlainPkt = <<?UINT32(TextLen),?BYTE(PadLen), PlainText/binary, Pad/binary>>,
+ {Ssh1, CipherPkt} = encrypt(Ssh0, PlainPkt),
+ MAC0 = mac(MacAlg, MacKey, SeqNum, PlainPkt),
+ {<<CipherPkt/binary,MAC0/binary>>, Ssh1};
+
+
+pack(common, enc_then_mac, PlainText, DeltaLenTst,
+ #ssh{send_sequence = SeqNum,
+ send_mac = MacAlg,
+ send_mac_key = MacKey} = Ssh0) ->
+ PadLen = padding_length(1+size(PlainText), Ssh0),
+ Pad = ssh_bits:random(PadLen),
+ PlainLen = 1 + size(PlainText) + PadLen + DeltaLenTst,
+ PlainPkt = <<?BYTE(PadLen), PlainText/binary, Pad/binary>>,
+ {Ssh1, CipherPkt} = encrypt(Ssh0, PlainPkt),
+ EncPacketPkt = <<?UINT32(PlainLen), CipherPkt/binary>>,
+ MAC0 = mac(MacAlg, MacKey, SeqNum, EncPacketPkt),
+ {<<?UINT32(PlainLen), CipherPkt/binary, MAC0/binary>>, Ssh1};
+
+pack(aead, _, PlainText, DeltaLenTst, Ssh0) ->
+ PadLen = padding_length(1+size(PlainText), Ssh0),
+ Pad = ssh_bits:random(PadLen),
+ PlainLen = 1 + size(PlainText) + PadLen + DeltaLenTst,
+ PlainPkt = <<?BYTE(PadLen), PlainText/binary, Pad/binary>>,
+ {Ssh1, {CipherPkt,MAC0}} = encrypt(Ssh0, <<?UINT32(PlainLen),PlainPkt/binary>>),
+ {<<CipherPkt/binary,MAC0/binary>>, Ssh1}.
+
+%%%================================================================
+handle_packet_part(<<>>, Encrypted0, AEAD0, undefined, #ssh{decrypt = CryptoAlg,
+ recv_mac = MacAlg} = Ssh0) ->
%% New ssh packet
- case get_length(pkt_type(CryptoAlg), Encrypted0, Ssh0) of
+ case get_length(pkt_type(CryptoAlg), mac_type(MacAlg), Encrypted0, Ssh0) of
get_more ->
%% too short to get the length
{get_more, <<>>, Encrypted0, AEAD0, undefined, Ssh0};
@@ -1211,36 +1294,60 @@ handle_packet_part(DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0)
%% need more bytes to finalize the packet
{get_more, DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0};
-handle_packet_part(DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded,
- #ssh{recv_mac_size = MacSize,
- decrypt = CryptoAlg} = Ssh0) ->
+handle_packet_part(DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, #ssh{decrypt = CryptoAlg,
+ recv_mac = MacAlg} = Ssh0) ->
%% enough bytes to decode the packet.
- DecryptLen = TotalNeeded - size(DecryptedPfx) - MacSize,
- <<EncryptedSfx:DecryptLen/binary, Mac:MacSize/binary, NextPacketBytes/binary>> = EncryptedBuffer,
- case pkt_type(CryptoAlg) of
- common ->
- {Ssh1, DecryptedSfx} = decrypt(Ssh0, EncryptedSfx),
- DecryptedPacket = <<DecryptedPfx/binary, DecryptedSfx/binary>>,
- case is_valid_mac(Mac, DecryptedPacket, Ssh1) of
- false ->
- {bad_mac, Ssh1};
- true ->
- {Ssh, DecompressedPayload} = decompress(Ssh1, payload(DecryptedPacket)),
- {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh}
- end;
- aead ->
- case decrypt(Ssh0, {AEAD,EncryptedSfx,Mac}) of
- {Ssh1, error} ->
- {bad_mac, Ssh1};
- {Ssh1, DecryptedSfx} ->
- DecryptedPacket = <<DecryptedPfx/binary, DecryptedSfx/binary>>,
- {Ssh, DecompressedPayload} = decompress(Ssh1, payload(DecryptedPacket)),
- {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh}
- end
+ case unpack(pkt_type(CryptoAlg), mac_type(MacAlg),
+ DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0) of
+ {ok, Payload, NextPacketBytes, Ssh1} ->
+ {Ssh, DecompressedPayload} = decompress(Ssh1, Payload),
+ {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh};
+ Other ->
+ Other
end.
-
-
-get_length(common, EncryptedBuffer, #ssh{decrypt_block_size = BlockSize} = Ssh0) ->
+
+%%%----------------
+unpack(common, rfc4253, DecryptedPfx, EncryptedBuffer, _AEAD, TotalNeeded,
+ #ssh{recv_mac_size = MacSize} = Ssh0) ->
+ MoreNeeded = TotalNeeded - size(DecryptedPfx) - MacSize,
+ <<EncryptedSfx:MoreNeeded/binary, Mac:MacSize/binary, NextPacketBytes/binary>> = EncryptedBuffer,
+ {Ssh1, DecryptedSfx} = decrypt(Ssh0, EncryptedSfx),
+ PlainPkt = <<DecryptedPfx/binary, DecryptedSfx/binary>>,
+ case is_valid_mac(Mac, PlainPkt, Ssh1) of
+ true ->
+ {ok, payload(PlainPkt), NextPacketBytes, Ssh1};
+ false ->
+ {bad_mac, Ssh1}
+ end;
+
+unpack(common, enc_then_mac, <<?UINT32(PlainLen)>>, EncryptedBuffer, _AEAD, _TotalNeeded,
+ #ssh{recv_mac_size = MacSize} = Ssh0) ->
+ <<Payload:PlainLen/binary, MAC0:MacSize/binary, NextPacketBytes/binary>> = EncryptedBuffer,
+ case is_valid_mac(MAC0, <<?UINT32(PlainLen),Payload/binary>>, Ssh0) of
+ true ->
+ {Ssh1, <<?BYTE(PaddingLen), PlainRest/binary>>} = decrypt(Ssh0, Payload),
+ CompressedPlainTextLen = size(PlainRest) - PaddingLen,
+ <<CompressedPlainText:CompressedPlainTextLen/binary, _Padding/binary>> = PlainRest,
+ {ok, CompressedPlainText, NextPacketBytes, Ssh1};
+ false ->
+ {bad_mac, Ssh0}
+ end;
+
+unpack(aead, _, DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded,
+ #ssh{recv_mac_size = MacSize} = Ssh0) ->
+ %% enough bytes to decode the packet.
+ MoreNeeded = TotalNeeded - size(DecryptedPfx) - MacSize,
+ <<EncryptedSfx:MoreNeeded/binary, Mac:MacSize/binary, NextPacketBytes/binary>> = EncryptedBuffer,
+ case decrypt(Ssh0, {AEAD,EncryptedSfx,Mac}) of
+ {Ssh1, error} ->
+ {bad_mac, Ssh1};
+ {Ssh1, DecryptedSfx} ->
+ DecryptedPacket = <<DecryptedPfx/binary, DecryptedSfx/binary>>,
+ {ok, payload(DecryptedPacket), NextPacketBytes, Ssh1}
+ end.
+
+%%%----------------------------------------------------------------
+get_length(common, rfc4253, EncryptedBuffer, #ssh{decrypt_block_size = BlockSize} = Ssh0) ->
case size(EncryptedBuffer) >= erlang:max(8, BlockSize) of
true ->
<<EncBlock:BlockSize/binary, EncryptedRest/binary>> = EncryptedBuffer,
@@ -1251,7 +1358,16 @@ get_length(common, EncryptedBuffer, #ssh{decrypt_block_size = BlockSize} = Ssh0)
get_more
end;
-get_length(aead, EncryptedBuffer, Ssh) ->
+get_length(common, enc_then_mac, EncryptedBuffer, Ssh) ->
+ case EncryptedBuffer of
+ <<Decrypted:4/binary, EncryptedRest/binary>> ->
+ <<?UINT32(PacketLen)>> = Decrypted,
+ {ok, PacketLen, Decrypted, EncryptedRest, <<>>, Ssh};
+ _ ->
+ get_more
+ end;
+
+get_length(aead, _, EncryptedBuffer, Ssh) ->
case {size(EncryptedBuffer) >= 4, Ssh#ssh.decrypt} of
{true, 'chacha20-poly1305@openssh.com'} ->
<<EncryptedLen:4/binary, EncryptedRest/binary>> = EncryptedBuffer,
@@ -1266,11 +1382,26 @@ get_length(aead, EncryptedBuffer, Ssh) ->
end.
+padding_length(Size, #ssh{encrypt_block_size = BlockSize,
+ random_length_padding = RandomLengthPad}) ->
+ PL = (BlockSize - (Size rem BlockSize)) rem BlockSize,
+ MinPadLen = if PL < 4 -> PL + BlockSize;
+ true -> PL
+ end,
+ PadBlockSize = max(BlockSize,4),
+ MaxExtraBlocks = (max(RandomLengthPad,MinPadLen) - MinPadLen) div PadBlockSize,
+ ExtraPadLen = try (rand:uniform(MaxExtraBlocks+1) - 1) * PadBlockSize
+ catch _:_ -> 0
+ end,
+ MinPadLen + ExtraPadLen.
+
+
payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) ->
PayloadLen = PacketLen - PaddingLen - 1,
<<Payload:PayloadLen/binary, _/binary>> = PayloadAndPadding,
Payload.
+%%%----------------------------------------------------------------
sign(SigData, HashAlg, #{algorithm:=dss} = Key) ->
mk_dss_sig(crypto:sign(dss, HashAlg, SigData, Key));
sign(SigData, HashAlg, #{algorithm:=SigAlg} = Key) ->
@@ -1289,7 +1420,7 @@ mk_dss_sig(DerSignature) ->
#'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature),
<<R:160/big-unsigned-integer, S:160/big-unsigned-integer>>.
-
+%%%----------------------------------------------------------------
verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key, _) ->
case Sig of
<<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> ->
@@ -1406,6 +1537,11 @@ cipher(_) ->
pkt_type(SshCipher) -> (cipher(SshCipher))#cipher.pkt_type.
+mac_type('hmac-sha2-256-etm@openssh.com') -> enc_then_mac;
+mac_type('hmac-sha2-512-etm@openssh.com') -> enc_then_mac;
+mac_type('hmac-sha1-etm@openssh.com') -> enc_then_mac;
+mac_type(_) -> rfc4253.
+
decrypt_magic(server) -> {"A", "C"};
decrypt_magic(client) -> {"B", "D"}.
@@ -1480,7 +1616,7 @@ encrypt(#ssh{encrypt = 'chacha20-poly1305@openssh.com',
%% MAC tag
PolyKey = crypto:crypto_one_time(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>, <<0:32/unit:8>>, true),
EncBytes = <<EncLen/binary,EncPayloadData/binary>>,
- Ctag = crypto:poly1305(PolyKey, EncBytes),
+ Ctag = crypto:mac(poly1305, PolyKey, EncBytes),
%% Result
{Ssh, {EncBytes,Ctag}};
@@ -1561,7 +1697,7 @@ decrypt(#ssh{decrypt = 'chacha20-poly1305@openssh.com',
%% The length is already decrypted and used to divide the input
%% Check the mac (important that it is timing-safe):
PolyKey = crypto:crypto_one_time(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>, <<0:32/unit:8>>, false),
- case crypto:equal_const_time(Ctag, crypto:poly1305(PolyKey, <<AAD/binary,Ctext/binary>>)) of
+ case crypto:equal_const_time(Ctag, crypto:mac(poly1305, PolyKey, <<AAD/binary,Ctext/binary>>)) of
true ->
%% MAC is ok, decode
IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>,
@@ -1722,17 +1858,23 @@ recv_mac_final(SSH) ->
mac(none, _ , _, _) ->
<<>>;
mac('hmac-sha1', Key, SeqNum, Data) ->
- crypto:hmac(sha, Key, [<<?UINT32(SeqNum)>>, Data]);
+ crypto:mac(hmac, sha, Key, [<<?UINT32(SeqNum)>>, Data]);
mac('hmac-sha1-96', Key, SeqNum, Data) ->
- crypto:hmac(sha, Key, [<<?UINT32(SeqNum)>>, Data], mac_digest_size('hmac-sha1-96'));
+ crypto:macN(hmac, sha, Key, [<<?UINT32(SeqNum)>>, Data], mac_digest_size('hmac-sha1-96'));
mac('hmac-md5', Key, SeqNum, Data) ->
- crypto:hmac(md5, Key, [<<?UINT32(SeqNum)>>, Data]);
+ crypto:mac(hmac, md5, Key, [<<?UINT32(SeqNum)>>, Data]);
mac('hmac-md5-96', Key, SeqNum, Data) ->
- crypto:hmac(md5, Key, [<<?UINT32(SeqNum)>>, Data], mac_digest_size('hmac-md5-96'));
+ crypto:macN(hmac, md5, Key, [<<?UINT32(SeqNum)>>, Data], mac_digest_size('hmac-md5-96'));
mac('hmac-sha2-256', Key, SeqNum, Data) ->
- crypto:hmac(sha256, Key, [<<?UINT32(SeqNum)>>, Data]);
+ crypto:mac(hmac, sha256, Key, [<<?UINT32(SeqNum)>>, Data]);
mac('hmac-sha2-512', Key, SeqNum, Data) ->
- crypto:hmac(sha512, Key, [<<?UINT32(SeqNum)>>, Data]).
+ crypto:mac(hmac, sha512, Key, [<<?UINT32(SeqNum)>>, Data]);
+mac('hmac-sha1-etm@openssh.com', Key, SeqNum, Data) ->
+ mac('hmac-sha1', Key, SeqNum, Data);
+mac('hmac-sha2-256-etm@openssh.com', Key, SeqNum, Data) ->
+ mac('hmac-sha2-256', Key, SeqNum, Data);
+mac('hmac-sha2-512-etm@openssh.com', Key, SeqNum, Data) ->
+ mac('hmac-sha2-512', Key, SeqNum, Data).
%%%----------------------------------------------------------------
@@ -1760,7 +1902,7 @@ kex_hash(SSH, Key, HashAlg, Args) ->
kex_plaintext(SSH, Key, Args) ->
- EncodedKey = public_key:ssh_encode(Key, ssh2_pubkey),
+ EncodedKey = ssh_message:ssh2_pubkey_encode(Key),
<<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit),
?Ebinary(EncodedKey),
@@ -1787,33 +1929,36 @@ kex_alg_dependent({Min, NBits, Max, Prime, Gen, E, F, K}) ->
%%%----------------------------------------------------------------
-valid_key_sha_alg(#{engine:=_, key_id:=_}, _Alg) -> true; % Engine key
+valid_key_sha_alg(_, #{engine:=_, key_id:=_}, _Alg) -> true; % Engine key
-valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-512') -> true;
-valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-384') -> true;
-valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-256') -> true;
-valid_key_sha_alg(#'RSAPublicKey'{}, 'ssh-rsa' ) -> true;
+valid_key_sha_alg(public, #'RSAPublicKey'{}, 'rsa-sha2-512') -> true;
+valid_key_sha_alg(public, #'RSAPublicKey'{}, 'rsa-sha2-384') -> true;
+valid_key_sha_alg(public, #'RSAPublicKey'{}, 'rsa-sha2-256') -> true;
+valid_key_sha_alg(public, #'RSAPublicKey'{}, 'ssh-rsa' ) -> true;
-valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-512') -> true;
-valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-384') -> true;
-valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-256') -> true;
-valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa' ) -> true;
+valid_key_sha_alg(private, #'RSAPrivateKey'{}, 'rsa-sha2-512') -> true;
+valid_key_sha_alg(private, #'RSAPrivateKey'{}, 'rsa-sha2-384') -> true;
+valid_key_sha_alg(private, #'RSAPrivateKey'{}, 'rsa-sha2-256') -> true;
+valid_key_sha_alg(private, #'RSAPrivateKey'{}, 'ssh-rsa' ) -> true;
-valid_key_sha_alg({_, #'Dss-Parms'{}}, 'ssh-dss') -> true;
-valid_key_sha_alg(#'DSAPrivateKey'{}, 'ssh-dss') -> true;
+valid_key_sha_alg(public, {_, #'Dss-Parms'{}}, 'ssh-dss') -> true;
+valid_key_sha_alg(private, #'DSAPrivateKey'{}, 'ssh-dss') -> true;
-valid_key_sha_alg({ed_pub, ed25519,_}, 'ssh-ed25519') -> true;
-valid_key_sha_alg({ed_pri, ed25519,_,_},'ssh-ed25519') -> true;
-valid_key_sha_alg({ed_pub, ed448,_}, 'ssh-ed448') -> true;
-valid_key_sha_alg({ed_pri, ed448,_,_}, 'ssh-ed448') -> true;
+valid_key_sha_alg(public, {ed_pub, ed25519,_}, 'ssh-ed25519') -> true;
+valid_key_sha_alg(private, {ed_pri, ed25519,_,_},'ssh-ed25519') -> true;
+valid_key_sha_alg(public, {ed_pub, ed448,_}, 'ssh-ed448') -> true;
+valid_key_sha_alg(private, {ed_pri, ed448,_,_}, 'ssh-ed448') -> true;
-valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> valid_key_sha_alg_ec(OID, Alg);
-valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> valid_key_sha_alg_ec(OID, Alg);
-valid_key_sha_alg(_, _) -> false.
+valid_key_sha_alg(public, {#'ECPoint'{},{namedCurve,OID}}, Alg) when is_tuple(OID) ->
+ valid_key_sha_alg_ec(OID, Alg);
+valid_key_sha_alg(private, #'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) when is_tuple(OID) ->
+ valid_key_sha_alg_ec(OID, Alg);
+valid_key_sha_alg(_, _, _) -> false.
-valid_key_sha_alg_ec(OID, Alg) ->
- Curve = public_key:oid2ssh_curvename(OID),
- try Alg == list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(Curve))
+valid_key_sha_alg_ec(OID, Alg) ->
+ try
+ Curve = public_key:oid2ssh_curvename(OID),
+ Alg == list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(Curve))
catch
_:_ -> false
end.
@@ -1825,9 +1970,9 @@ public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-cu
public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss';
public_algo({ed_pub, ed25519,_}) -> 'ssh-ed25519';
public_algo({ed_pub, ed448,_}) -> 'ssh-ed448';
-public_algo({#'ECPoint'{},{namedCurve,OID}}) ->
- Curve = public_key:oid2ssh_curvename(OID),
- try list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(Curve))
+public_algo({#'ECPoint'{},{namedCurve,OID}}) when is_tuple(OID) ->
+ SshName = public_key:oid2ssh_curvename(OID),
+ try list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(SshName))
catch
_:_ -> undefined
end.
@@ -1868,22 +2013,28 @@ sha(Str) when is_list(Str), length(Str)<50 -> sha(list_to_existing_atom(Str)).
mac_key_bytes('hmac-sha1') -> 20;
+mac_key_bytes('hmac-sha1-etm@openssh.com') -> 20;
mac_key_bytes('hmac-sha1-96') -> 20;
mac_key_bytes('hmac-md5') -> 16;
mac_key_bytes('hmac-md5-96') -> 16;
mac_key_bytes('hmac-sha2-256')-> 32;
+mac_key_bytes('hmac-sha2-256-etm@openssh.com')-> 32;
mac_key_bytes('hmac-sha2-512')-> 64;
+mac_key_bytes('hmac-sha2-512-etm@openssh.com')-> 64;
mac_key_bytes('AEAD_AES_128_GCM') -> 0;
mac_key_bytes('AEAD_AES_256_GCM') -> 0;
mac_key_bytes('chacha20-poly1305@openssh.com') -> 0;
mac_key_bytes(none) -> 0.
mac_digest_size('hmac-sha1') -> 20;
+mac_digest_size('hmac-sha1-etm@openssh.com') -> 20;
mac_digest_size('hmac-sha1-96') -> 12;
mac_digest_size('hmac-md5') -> 20;
mac_digest_size('hmac-md5-96') -> 12;
mac_digest_size('hmac-sha2-256') -> 32;
+mac_digest_size('hmac-sha2-256-etm@openssh.com') -> 32;
mac_digest_size('hmac-sha2-512') -> 64;
+mac_digest_size('hmac-sha2-512-etm@openssh.com') -> 64;
mac_digest_size('AEAD_AES_128_GCM') -> 16;
mac_digest_size('AEAD_AES_256_GCM') -> 16;
mac_digest_size('chacha20-poly1305@openssh.com') -> 16;
@@ -2008,18 +2159,27 @@ ssh_dbg_off(ssh_messages) -> ssh_dbg_off(hello).
+ssh_dbg_format(hello, {call,{?MODULE,hello_version_msg,[_]}}) ->
+ skip;
ssh_dbg_format(hello, {return_from,{?MODULE,hello_version_msg,1},Hello}) ->
["Going to send hello message:\n",
Hello
];
+
ssh_dbg_format(hello, {call,{?MODULE,handle_hello_version,[Hello]}}) ->
["Received hello message:\n",
Hello
];
+ssh_dbg_format(hello, {return_from,{?MODULE,handle_hello_version,1},_Ret}) ->
+ skip;
+
+ssh_dbg_format(alg, {call,{?MODULE,select_algorithm,[_,_,_,_]}}) ->
+ skip;
ssh_dbg_format(alg, {return_from,{?MODULE,select_algorithm,4},{ok,Alg}}) ->
["Negotiated algorithms:\n",
wr_record(Alg)
];
+
ssh_dbg_format(raw_messages, X) -> ssh_dbg_format(hello, X);
ssh_dbg_format(ssh_messages, X) -> ssh_dbg_format(hello, X).
diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl
index 4fc7e98c0c..d4fa0ccd9f 100644
--- a/lib/ssh/src/ssh_xfer.erl
+++ b/lib/ssh/src/ssh_xfer.erl
@@ -117,13 +117,20 @@ rename(XF, ReqID, OldPath, NewPath, Flags) ->
true ->
(<<>>)
end,
- xf_request(XF, ?SSH_FXP_RENAME,
- [?uint32(ReqID),
- ?string_utf8(OldPath),
- ?string_utf8(NewPath),
- FlagBits]).
-
-
+ Ext = XF#ssh_xfer.ext,
+ ExtRename = "posix-rename@openssh.com",
+ case lists:member({ExtRename, "1"}, Ext) of
+ true ->
+ extended(XF, ReqID, ExtRename,
+ [?string_utf8(OldPath),
+ ?string_utf8(NewPath)]);
+ false ->
+ xf_request(XF, ?SSH_FXP_RENAME,
+ [?uint32(ReqID),
+ ?string_utf8(OldPath),
+ ?string_utf8(NewPath),
+ FlagBits])
+ end.
%% Create directory
mkdir(XF, ReqID, Path, Attrs) ->
@@ -222,7 +229,7 @@ extended(XF, ReqID, Request, Data) ->
xf_request(XF, ?SSH_FXP_EXTENDED,
[?uint32(ReqID),
?string(Request),
- ?binary(Data)]).
+ Data]).
%% Send xfer request to connection manager
@@ -802,6 +809,15 @@ decode_names(Vsn, I, <<?UINT32(Len), FileName:Len/binary,
encode_names(Vsn, NamesAndAttrs) ->
lists:mapfoldl(fun(N, L) -> encode_name(Vsn, N, L) end, 0, NamesAndAttrs).
+encode_name(Vsn, {{NameUC,LongNameUC},Attr}, Len) when Vsn =< 3 ->
+ Name = binary_to_list(unicode:characters_to_binary(NameUC)),
+ NLen = length(Name),
+ LongName = binary_to_list(unicode:characters_to_binary(LongNameUC)),
+ LNLen = length(LongName),
+ EncAttr = encode_ATTR(Vsn, Attr),
+ ALen = size(EncAttr),
+ NewLen = Len + NLen + LNLen + 4 + 4 + ALen,
+ {[<<?UINT32(NLen)>>, Name, <<?UINT32(LNLen)>>, LongName, EncAttr], NewLen};
encode_name(Vsn, {NameUC,Attr}, Len) when Vsn =< 3 ->
Name = binary_to_list(unicode:characters_to_binary(NameUC)),
NLen = length(Name),
diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl
index 869de244ac..e582733de8 100644
--- a/lib/ssh/src/sshc_sup.erl
+++ b/lib/ssh/src/sshc_sup.erl
@@ -27,7 +27,12 @@
-behaviour(supervisor).
--export([start_link/0, start_child/1]).
+-export([start_link/0,
+ start_child/4,
+ start_system_subsystem/4,
+ stop_child/1,
+ stop_system/1
+ ]).
%% Supervisor callback
-export([init/1]).
@@ -38,22 +43,64 @@
%%% API
%%%=========================================================================
start_link() ->
- supervisor:start_link({local,?SSHC_SUP}, ?MODULE, []).
+ supervisor:start_link({local,?MODULE}, ?MODULE, []).
+
+start_child(Address, Port, Profile, Options) ->
+ case ssh_system_sup:system_supervisor(Address, Port, Profile) of
+ undefined ->
+ %% Here we a new connction on a new Host/Port/Profile
+ Spec = child_spec(Address, Port, Profile, Options),
+ supervisor:start_child(?MODULE, Spec);
+ Pid ->
+ {ok,Pid}
+ end.
+
+
+start_system_subsystem(Host, Port, Profile, Options) ->
+ ssh_controller:start_system_subsystem(client_controller, ?MODULE, Host, Port, Profile, Options,
+ child_spec(Host, Port, Profile, Options)
+ ).
+
+stop_child(ChildId) when is_tuple(ChildId) ->
+ supervisor:terminate_child(?SSHC_SUP, ChildId);
+stop_child(ChildPid) when is_pid(ChildPid)->
+ stop_child(system_name(ChildPid)).
+
+stop_system(SysSup) ->
+ ssh_controller:stop_system(client_controller, SysSup).
-start_child(Args) ->
- supervisor:start_child(?MODULE, Args).
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
init(_) ->
- SupFlags = #{strategy => simple_one_for_one,
+ SupFlags = #{strategy => one_for_one,
intensity => 0,
period => 3600
},
- ChildSpecs = [#{id => undefined, % As simple_one_for_one is used.
- start => {ssh_connection_handler, start_link, []},
- restart => temporary % because there is no way to restart a crashed connection
- }
- ],
+ ChildSpecs = [#{id => client_controller,
+ start => {ssh_controller, start_link, [client, client_controller]},
+ restart => permanent,
+ type => worker
+ }],
{ok, {SupFlags,ChildSpecs}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
+child_spec(Address, Port, Profile, Options) ->
+ #{id => id(Address, Port, Profile),
+ start => {ssh_system_sup, start_link, [client, Address, Port, Profile, Options]},
+ restart => temporary,
+ type => supervisor
+ }.
+
+id(Address, Port, Profile) ->
+ {client, ssh_system_sup, Address, Port, Profile}.
+
+system_name(SysSup) ->
+ case lists:keyfind(SysSup, 2, supervisor:which_children(?SSHC_SUP)) of
+ {Name, SysSup, _, _} -> Name;
+ false -> undefind
+ end.
+
diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl
index b5361abba5..b716b66ec7 100644
--- a/lib/ssh/src/sshd_sup.erl
+++ b/lib/ssh/src/sshd_sup.erl
@@ -89,7 +89,7 @@ init(_) ->
%%%=========================================================================
child_spec(Address, Port, Profile, Options) ->
#{id => id(Address, Port, Profile),
- start => {ssh_system_sup, start_link, [Address, Port, Profile, Options]},
+ start => {ssh_system_sup, start_link, [server, Address, Port, Profile, Options]},
restart => temporary,
type => supervisor
}.
diff --git a/lib/ssh/test/.gitignore b/lib/ssh/test/.gitignore
index c9d5f086b3..f0adbaf33f 100644
--- a/lib/ssh/test/.gitignore
+++ b/lib/ssh/test/.gitignore
@@ -1,4 +1,6 @@
+*COVER.html
+ssh_sftp_SUITE_data/test_data*
property_test/ssh_eqc_client_server_dirs/system
property_test/ssh_eqc_client_server_dirs/user
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
index e221e94075..04627052e5 100644
--- a/lib/ssh/test/Makefile
+++ b/lib/ssh/test/Makefile
@@ -22,12 +22,6 @@ include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-include ../vsn.mk
-VSN=$(GS_VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
@@ -39,10 +33,14 @@ MODULES= \
ssh_chan_behaviours_SUITE \
ssh_compat_SUITE \
ssh_connection_SUITE \
+ ssh_agent_mock_server \
+ ssh_agent_SUITE \
ssh_dbg_SUITE \
ssh_engine_SUITE \
ssh_protocol_SUITE \
ssh_property_test_SUITE \
+ ssh_pubkey_SUITE \
+ ssh_renegotiate_SUITE \
ssh_sftp_SUITE \
ssh_sftpd_SUITE \
ssh_sftpd_erlclient_SUITE \
@@ -63,30 +61,29 @@ MODULES= \
ssh_relay \
ssh_eqc_event_handler
-HRL_FILES_NEEDED_IN_TEST= \
+ERL_FILES= $(MODULES:%=%.erl)
+
+HRL_FILES= \
$(ERL_TOP)/lib/ssh/test/ssh_test_lib.hrl \
$(ERL_TOP)/lib/ssh/src/ssh.hrl \
+ $(ERL_TOP)/lib/ssh/src/ssh_agent.hrl \
+ $(ERL_TOP)/lib/ssh/src/ssh_auth.hrl \
+ $(ERL_TOP)/lib/ssh/src/ssh_connect.hrl \
+ $(ERL_TOP)/lib/ssh/src/ssh_transport.hrl \
$(ERL_TOP)/lib/ssh/src/ssh_xfer.hrl
-ERL_FILES= $(MODULES:%=%.erl)
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-DATA_DIRS = $(MODULES:%=%_data)
+SPEC_FILES = \
+ ssh.spec \
+ ssh_bench.spec
-INCLUDES = -I$(ERL_TOP)/lib/ssh/src
+COVER_FILE = ssh.cover
EMAKEFILE=Emakefile
MAKE_EMAKE = $(wildcard $(ERL_TOP)/make/make_emakefile)
-ifeq ($(MAKE_EMAKE),)
-BUILDTARGET = $(TARGET_FILES)
-RELTEST_FILES = $(INETS_SPECS) $(SOURCE)
-else
-BUILDTARGET = emakebuild
-RELTEST_FILES = $(EMAKEFILE) $(INETS_SPECS) $(SOURCE)
-endif
-
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
@@ -94,19 +91,19 @@ RELSYSDIR = $(RELEASE_PATH)/ssh_test
# ----------------------------------------------------
# FLAGS
-# The path to the test_server ebin dir is needed when
-# running the target "targets".
# ----------------------------------------------------
-ERL_COMPILE_FLAGS += $(INCLUDES)
+
+INCLUDES = -I$(ERL_TOP)/lib/ssh/src
+
+ERL_COMPILE_FLAGS += $(INCLUDES) -pa ../ebin
EBIN = .
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-tests debug opt: $(BUILDTARGET)
-targets: $(TARGET_FILES)
+tests debug opt: emakebuild $(TARGET_FILES)
.PHONY: emakebuild
@@ -120,11 +117,8 @@ clean:
rm -f $(EMAKEFILE)
rm -f $(TARGET_FILES)
rm -f core
-docs:
-info:
- @echo "TARGET_FILES = $(TARGET_FILES)"
- @echo "DATA_DIRS = $(DATA_DIRS)"
+docs:
# ----------------------------------------------------
# Release Target
@@ -135,9 +129,11 @@ release_spec: opt
release_tests_spec: opt
$(INSTALL_DIR) "$(RELSYSDIR)"
- $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)"
- $(INSTALL_DATA) ssh.spec ssh_bench.spec ssh.cover "$(RELSYSDIR)"
- $(INSTALL_DATA) $(HRL_FILES_NEEDED_IN_TEST) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(EMAKEFILE) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(SPEC_FILES) $(COVER_FILE) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(HRL_FILES) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(TARGET_FILES) "$(RELSYSDIR)"
chmod -R u+w "$(RELSYSDIR)"
@tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -)
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 e0b739ab53..4bfc23d5ff 100644
--- a/lib/ssh/test/property_test/ssh_eqc_client_server.erl
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server.erl
@@ -287,7 +287,7 @@ client_loop() ->
client_loop()
end.
-do(Pid, Fun) -> do(Pid, Fun, 30?sec).
+do(Pid, Fun) -> do(Pid, Fun, 60?sec).
do(Pid, Fun, Timeout) when is_function(Fun,0) ->
Pid ! {please_do,Fun,Ref=make_ref(),self()},
@@ -418,7 +418,7 @@ ssh_send(C=#chan{conn_ref=ConnectionRef, ref=ChannelRef, client_pid=Pid}, Type,
ok ->
receive
{ssh_cm,ConnectionRef,{data,ChannelRef,Type,Answer}} -> Answer
- after 15?sec ->
+ after 30?sec ->
%% receive
%% Other -> {error,{unexpected,Other}}
%% after 0 ->
@@ -480,12 +480,9 @@ setup_rsa(Dir) ->
erase_dir(user_dir(Dir)),
file:make_dir(system_dir(Dir)),
file:make_dir(user_dir(Dir)),
-
- file:copy(data_dir(Dir,"id_rsa"), user_dir(Dir,"id_rsa")),
- file:copy(data_dir(Dir,"ssh_host_rsa_key"), system_dir(Dir,"ssh_host_rsa_key")),
- file:copy(data_dir(Dir,"ssh_host_rsa_key"), system_dir(Dir,"ssh_host_rsa_key.pub")),
- ssh_test_lib:setup_rsa_known_host(data_dir(Dir), user_dir(Dir)),
- ssh_test_lib:setup_rsa_auth_keys(data_dir(Dir), user_dir(Dir)).
+ ct:log("Dir = ~p~ndata_dir = ~p~nsystem_dir = ~p~nuser = ~p~n",
+ [Dir,data_dir(Dir),system_dir(Dir),user_dir(Dir)]),
+ ssh_test_lib:setup_all_user_host_keys( data_dir(Dir), user_dir(Dir), system_dir(Dir)).
data_dir(Dir, File) -> filename:join(Dir, File).
system_dir(Dir, File) -> filename:join([Dir, "system", File]).
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa
index d306f8b26e..24628e071b 100644
--- a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256
new file mode 100644
index 0000000000..4b1eb12eaa
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49
+AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br
+N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub
new file mode 100644
index 0000000000..a0147e60fa
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384
new file mode 100644
index 0000000000..4e8aa40959
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S
+PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd
+ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF
+8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub
new file mode 100644
index 0000000000..41e722e545
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521
new file mode 100644
index 0000000000..7196f46e97
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q
+9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5
+01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7
+XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf
+pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub
new file mode 100644
index 0000000000..8f059120bc
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519
new file mode 100644
index 0000000000..401a3e4a9a
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us
+aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw
+AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L
+ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub
new file mode 100644
index 0000000000..a5c03b19c1
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448
new file mode 100644
index 0000000000..8ecfd710dc
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh
+1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf
+fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32
+BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ
+m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub
new file mode 100644
index 0000000000..cec0765a5d
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa
index 9d7e0dd5fb..2202c2ead8 100644
--- a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
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 d1cfb190dc..843a2b5a3c 100644
--- a/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl
+++ b/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl
@@ -186,7 +186,7 @@ gen_pubkey_string(Type) ->
ecdsa -> {#'ECPoint'{point=[1,2,3,4,5]},
{namedCurve,{1,2,840,10045,3,1,7}}} % 'secp256r1' nistp256
end,
- gen_string(public_key:ssh_encode(PubKey, ssh2_pubkey)).
+ gen_string(ssh_message:ssh2_pubkey_encode(PubKey)).
gen_signature_string(Type) ->
diff --git a/lib/ssh/test/ssh.cover b/lib/ssh/test/ssh.cover
index 69d2a1c4f8..aa0da36c75 100644
--- a/lib/ssh/test/ssh.cover
+++ b/lib/ssh/test/ssh.cover
@@ -1,3 +1,21 @@
{incl_app,ssh,details}.
-{excl_mods, ssh, [ssh_dbg, ssh_info, ssh_server_key_api, ssh_sftpd_file_api]}. \ No newline at end of file
+{excl_mods, ssh,
+ [
+ %% App
+ ssh_app,
+
+ %% %% Supervisors
+ %% ssh_acceptor_sup, ssh_channel_sup, ssh_connection_sup,
+ %% sshc_sup, sshd_sup, ssh_subsystem_sup, ssh_sup,
+ %% ssh_system_sup, ssh_tcpip_forward_acceptor_sup,
+
+ %% Test and/or info modules:
+ ssh_dbg, ssh_info,
+
+ %% API modules (behaviours):
+ ssh_server_key_api, ssh_client_key_api,
+ ssh_sftpd_file_api,
+ ssh_channel, ssh_client_channel, ssh_daemon_channel, ssh_server_channel
+ ]}.
+
diff --git a/lib/ssh/test/ssh_agent_SUITE.erl b/lib/ssh/test/ssh_agent_SUITE.erl
new file mode 100644
index 0000000000..e22c13ee8d
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE.erl
@@ -0,0 +1,180 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssh_agent_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include("ssh.hrl").
+-include("ssh_agent.hrl").
+-include("ssh_test_lib.hrl").
+
+-export([
+ suite/0,
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ connect_with_ssh_agent/1,
+ request_identities/1,
+ sign_request/1
+ ]).
+
+
+%% Test configuration
+
+suite() ->
+ [{timetrap, {seconds, 30}}].
+
+all() ->
+ [request_identities, sign_request, connect_with_ssh_agent].
+
+init_per_suite(Config) ->
+ ?CHECK_CRYPTO(
+ begin
+ ok = ssh:start(),
+ chk_unix_domain_socket(Config)
+ end
+ ).
+
+end_per_suite(_Config) ->
+ ok = ssh:stop().
+
+init_per_testcase(connect_with_ssh_agent, Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+ AgentUserDir = filename:join(UserDir,"agent"), % just to separate them in the tests
+ % so we know that the right dir is used
+ file:make_dir(AgentUserDir),
+ %% Arrange the host keys in <priv_dir>/system
+ ct:log("Host keys setup for: ~p",
+ [ssh_test_lib:setup_all_host_keys(Config)]),
+ %% Copy the user's files used by the daemon
+ {ok,_} = file:copy(filename:join(DataDir,"authorized_keys"),
+ filename:join(UserDir,"authorized_keys")),
+ %% And copy the user's files used by the agent (and not by the user)
+ {ok,_} = file:copy(filename:join(DataDir,"id_rsa"),
+ filename:join(AgentUserDir,"id_rsa")),
+ Config;
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%% Test cases
+
+%%% Request a list of identities
+request_identities(_Config) ->
+ Request = #ssh_agent_identities_request{},
+
+ SocketPath =
+ ssh_agent_mock_server:respond(
+ <<?UINT32(41), % message length
+ ?BYTE(12), % message type (1 byte)
+ ?UINT32(2), % number of keys (4 bytes)
+ ?STRING(<<"key-1">>), % key 1 blob (4 + 5 bytes)
+ ?STRING(<<"lorem">>), % key 1 comment (4 + 5 bytes)
+ ?STRING(<<"key-2">>), % key 2 blob (4 + 5 bytes)
+ ?STRING(<<"ipsum">>) % key 2 comment (4 + 5 bytes)
+ >>
+ ),
+
+ Opts = [{socket_path, SocketPath}],
+ Response = ssh_agent:send(Request, Opts),
+
+ #ssh_agent_identities_response{keys = Keys} = Response,
+
+ [{ssh_agent_key,<<"key-1">>,<<"lorem">>},
+ {ssh_agent_key,<<"key-2">>,<<"ipsum">>}] = Keys,
+
+ ok.
+
+%%% Request a signature on a binary blob
+sign_request(_Config) ->
+ PubKeyBlob = <<"key">>,
+ SigData = <<"data">>,
+
+ SignFlags = ?SSH_AGENT_RSA_SHA2_256 bor ?SSH_AGENT_RSA_SHA2_512,
+ SignRequest = #ssh_agent_sign_request{key_blob = PubKeyBlob, data = SigData, flags = SignFlags},
+
+ SocketPath =
+ ssh_agent_mock_server:respond(
+ <<?UINT32(29), % message length
+ ?BYTE(14), % message type (1 byte)
+ ?STRING(
+ <<?STRING(<<"ssh-rsa">>), % signature format (4 + 7 bytes)
+ ?STRING(<<"signature">>) % signature blob (4 + 9 bytes)
+ >>
+ ) % signature total (4 + 24 bytes)
+ >>
+ ),
+
+ Opts = [{socket_path, SocketPath}],
+ SignResponse = ssh_agent:send(SignRequest, Opts),
+
+ #ssh_agent_sign_response{signature = #ssh_agent_signature{format = Format, blob = Sig}} = SignResponse,
+ Format = <<"ssh-rsa">>,
+ Sig = <<"signature">>,
+
+ ok.
+
+%%% Connect with RSA key from SSH agent
+connect_with_ssh_agent(Config) ->
+ UserDir = PrivDir = proplists:get_value(priv_dir, Config),
+ AgentUserDir = filename:join(UserDir,"agent"),
+ SystemDir = filename:join(PrivDir, "system"),
+ {ok, SocketPath} = ssh_agent_mock_server:start_link('rsa-sha2-256', AgentUserDir),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir}]),
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{user_dir, UserDir},
+ {silently_accept_hosts, true},
+ {user_interaction, false},
+ {auth_methods, "publickey"},
+ {key_cb, {ssh_agent, [{socket_path, SocketPath}]}}
+ ]),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid),
+ ssh_agent_mock_server:stop(SocketPath).
+
+
+%%%================================================================
+chk_unix_domain_socket(Config0) ->
+ case ssh_agent_mock_server:check_mktemp(Config0) of
+ {skip, Msg} ->
+ {skip, Msg};
+
+ Config ->
+ SocketPath = string:chomp(os:cmd("mktemp -u")),
+ case gen_tcp:listen(0, [local, {ip, {local,SocketPath}}]) of
+ {error,eafnosupport} ->
+ file:delete(SocketPath),
+ {skip, "Unix Domain Sockets are not supported"};
+ {ok, Socket} ->
+ gen_tcp:close(Socket),
+ file:delete(SocketPath),
+ Config
+ end
+ end.
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/authorized_keys b/lib/ssh/test/ssh_agent_SUITE_data/authorized_keys
new file mode 100644
index 0000000000..418e1fa64a
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/authorized_keys
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2d3XMIA8GTEQcUFCOm31M5jt6lGjN61ZYGnXBVBjEcyJynB7Y3C437cDpjmvbSWF1oSVVDTwMERwnXzixLG//7w8K7i6aJLKpHKtS91qnrQidmrUWDnQ4kx8AZxaN46nhSsf+cZ0nKp03ZjjR5WxeDimiDLsSUbdDmFE6ZsL2+k5OStvcqu/skUVfPe+FGTGJgIw3DyErxM7J72jUkLJXMiZkYbB1QD05k3g2LOiPqJ73QoJVGgj7YagTSA3Lgy3s/6U7IMHMV4lsaXShv1Lk/eCfIJVSaVXQRjV9KKM3wgg6PmWqwGkAO36w3eJiW1kmYKfnAM/+I5Gfo/TiNZTX uabhnil@elxadlj3q32 \ No newline at end of file
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/id_rsa b/lib/ssh/test/ssh_agent_SUITE_data/id_rsa
new file mode 100644
index 0000000000..fa5190c0f2
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAtnd1zCAPBkxEHFBQjpt9TOY7epRozetWWBp1wVQYxHMicpwe
+2NwuN+3A6Y5r20lhdaElVQ08DBEcJ184sSxv/+8PCu4umiSyqRyrUvdap60InZq1
+Fg50OJMfAGcWjeOp4UrH/nGdJyqdN2Y40eVsXg4pogy7ElG3Q5hROmbC9vpOTkrb
+3Krv7JFFXz3vhRkxiYCMNw8hK8TOye9o1JCyVzImZGGwdUA9OZN4Nizoj6ie90KC
+VRoI+2GoE0gNy4Mt7P+lOyDBzFeJbGl0ob9S5P3gnyCVUmlV0EY1fSijN8IIOj5l
+qsBpADt+sN3iYltZJmCn5wDP/iORn6P04jWU1wIDAQABAoIBAHlncHw5lGWXVvYT
+xhWshSkmQsrjdfwUqmWCbXkNkFEdXf0tvSSDE0lpKqL7fO3xnCPc7W7ymFJbDAVy
+SNExhO+fyr12DpHG+wykI6XXKH1KFuJuLjCXu2JtGQJ2lL4hjUV2MS0twOdvZh2X
+KRUW9gx6ld7ZY5rjvfD+poUaHHygnN6f0+PiyBpUZaL+ZTj/6CpHiCxdZtOCf0o6
+bU7TaPNcZ3vf3Qhk4jk140vEDLJQnPF8stBqPWa9HfmM7CNjWBdmhQXHtHw8CtF6
+aba9BRC/FMYx43itE9hkg6p3JrSqAN/gZ6RCXLog6mQYttJV+y8oLTXvblT2Y3c9
+YjnigGkCgYEA2Y4+sjsGw3l5HnQ9CNuUQvJZgloVd/NTw8/UXXkV1QudI6tzmMJn
+XAxCCtt2DVlPBqFJ74uwdWpY0nwFJslEp4sV6VPv2TpBmAOPDB6QtQ8SXhFWQ7vj
+BVh5kwl3LUDVI+NIACoaMyZuS6N97Fp4a/mKtgj+ucOkfr15+IF7L/UCgYEA1rXd
+ATBFQlLoPuv3VglU1LLgaLs/3qzoBH9DzwPPyEFdWYQUCuMHsL9eEfDZMsG/GeZb
+Fch/CR0R2Qt+ZlcxcRichgMw3ydzIBqCvLe444lBzxdLWFqS5gCnSh5iage2QRs0
++6QD9O16HJER6HmBwR6DtwpP3N4dHxLXJRjDrhsCgYEAl3/M/UTJkvpWc/SyRCbU
++kHWP0YIST2ziVqDIoydvXyW8y4EE87dN2Z53yGw9d7Jf252FFCMk1d5fypKVBY4
+rwvWOGPxVK6S2w8vYFswnkVenw8nqYd/sktIbjJbQbIyOwmdLDAlipUqnZW+rQbb
+cSWXiOh+qlIpjPDZrUpNxLkCgYAMPjiI7dC1NHcLx3bGECgnLMABGNROhTuBriQW
+tNfvSlLhXNeru0BgArmBemNYMpYMCwecmV8tDNxMrQwbF9O46DdcqOfrgZtd9EUK
+L8u6JcR7448nTZrcxKLFZjAkbaYl1kBSLQsQt03kPR1xTSRp96x5Dnx5Uq0EbZWZ
+Bu15iwKBgAlSiCUqCNir3fdd0wE2+MIczam3YshQmnS3/XEk+7Bmfmb7Rxdwk94l
+P/SaQYZ3buCKoBTz5OveBl4aEdiyvEqBkeJoUbzwFILyo0RNncqULcrYAPIJtbKK
+H0o0naCZUgUJGsX0/DdJsEE27KMljc1A1Fpd3qQ2qqVLFfDTrsuB
+-----END RSA PRIVATE KEY----- \ No newline at end of file
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..15d2205302
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEArrzkBUAMYvAso4hR79vmNbxNbLYt7QocukbiCWOq29HQvqbS
+zj/OSE1Qg6C/aTbghvdpvxbaFt3aqdWroF0PhVVoEsJY76bI7RsobIe9zI/Z3dkQ
+RmW3IyjHvpVBwy84fKZ05A8Bd10kca1GfrQXi7LkFZ5FRjxarFzMojGVesWcZLag
+ThPG0QAjSw2sG+oql4YoIeagSdf3tzOOF+04vcpgqCRugsscw17lI1Rwq0nM3thU
+BgSWDRbjzmkWo9i5Wpc9ZKS1z4ANET+I2hGW7PEA6XAXJKC6nIWdVIie0GN02C/m
+i45NyTPn52I/TJKFAtIoZ8fbrHEepX7V/Dt7DQIDAQABAoIBAA1yJY2t3wYh+x1e
+WQe/ARjzc3XBEwmhdJJ07+HPFI+ztn9lMOWEDWiM4nwue2wqN97K3Q1CQefujGvz
+MDC32IDnEIoZAGT4jY+JPnQTgexiyV4D3Pe9zfjbo3sr2xKc6JjW6jm+WduIhExn
+C/yl+QXb7ycmtafw7v1CatC0Rg9bUtE8nMHKYVPazn30wlHdPl6TyTtEXoZCKMg2
+OTxrva80x5JboUqLZXX41VqVmqqoakEO3NOGlhrIzIOB866py8d+f6wilN/rUcGe
+MJwB8aTrYPxLkCYl9PGeMDMLARvhjMm53f6UQFDpL2rY0XpnaqrZqS9KrbJoTQDW
+lMj3OiECgYEA4FL3fba4FYGzf7T/m2xRSC5qrpr0gNn1mZJ5oUWGiHBwjAHC7EVW
+apcjskac7WrznIBJiV2ozzxQOgIymuYO9LX95G6b3nrkWhVXqUiyCkpdMSE/YVfA
+iMc3Z0QNz+I/cbEPUKZ7osKPZm4BRpUNvJwj4Vvt5jXwTZlPmVmpNHUCgYEAx2lx
+q6HO+Grba0Azg7wobnnZ8uZZvdZ+QpxgGhH1Rx9862KM+uVYrJ7xEUlNTYfBtdpq
+JXQnGlpzjGPeziZjJxv7AgWbJA80aXtTH/oE9E0KMmGRzE2bQNR8kUZHU5S2e0u+
+x1DUyvKqyKSRByxUp8wj5QZGPOH4MPvCHVZF+TkCgYAg52qQERYtaWn36Ie5t4iw
+qsZROD93CwGAdkDLDBSwvLV1g+igmYcUeXjt9HeeR5rWMOcYdBmH1FP8PkhH+kjl
+UjCcqjDI0IPgRtMl7JjY85F53GOclq+SII6a4huYi5o8xfj2HoVyGVHJd4dOYBy0
+tr54lvBtXSoTZ9KKLuGn5QKBgBUy4XGkfvMrsO3C4ncTrpyn+YJ3+HxU7BE6vICo
+/hE0iLwhOumFLhsTvn7e8wfV8cLaWERpB6smiHgZOdtie1HyCIobfHWl5CV+hcS1
+eIdcFURr2OsGKQYIUMHE3dpFyexrjfl0X1q/12YDEKPZk5pO+lXjh937C75xVR53
+SHMJAoGAS57QNKLFaWZunoMNuvvNAj7Z0q1JrFuLEwfnkG28g5+ov3wIBinPtlrr
+3HaK6sny0hHLPoRP+fa6BVRaQhDzeeKDu6PqNEkNnocWPi79lxfk531EJQHOgQgX
+yt7Ruq0TlBYs5wGrmtYXLKAGvcfyx9EoFs2Km1iNKqu6b/dbQXc=
+-----END RSA PRIVATE KEY----- \ No newline at end of file
diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub
@@ -0,0 +1,5 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8
+semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW
+RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_agent_mock_server.erl b/lib/ssh/test/ssh_agent_mock_server.erl
new file mode 100644
index 0000000000..09a632b056
--- /dev/null
+++ b/lib/ssh/test/ssh_agent_mock_server.erl
@@ -0,0 +1,158 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Description: ssh-agent mock
+-module(ssh_agent_mock_server).
+
+-behaviour(gen_server).
+
+-include("ssh.hrl").
+-include("ssh_agent.hrl").
+
+-export([respond/1, check_mktemp/1]).
+-export([start_link/2, stop/1]).
+-export([init/1, handle_info/2, handle_cast/2, handle_call/3, terminate/2]).
+
+-record(state, {socket, priv_key, pub_key, socket_path, sig_alg}).
+
+start_link(SigAlg, PrivKeyDir) ->
+ {ok, PrivKey} =ssh_file:user_key(SigAlg, [{user_dir, PrivKeyDir}]),
+
+ %% We cannot use priv_dir because unix socket paths are limited to 108 bytes.
+ SocketPath = string:chomp(os:cmd("mktemp -u")),
+ PubKey = extract_pubkey(PrivKey),
+
+ InitialState = #state{socket_path=SocketPath, priv_key=PrivKey, pub_key=PubKey, sig_alg=SigAlg},
+ {ok, _} = gen_server:start_link(?MODULE, InitialState, []),
+ {ok, SocketPath}.
+
+stop(SocketPath) ->
+ ConnectOpts = [binary, {packet, 0}, {active, false}],
+ {ok, Socket} = gen_tcp:connect({local, SocketPath}, 0, ConnectOpts, 1000),
+ ok = gen_tcp:send(Socket, <<0>>),
+ ok = gen_tcp:close(Socket).
+
+init(InitialState = #state{socket_path=SocketPath}) ->
+ Address = {local, SocketPath},
+ ConnectOpts = [local, binary, {ip, Address}, {packet, 0}],
+
+ {ok, Socket} = gen_tcp:listen(0, ConnectOpts),
+ gen_server:cast(self(), accept),
+ {ok, InitialState#state{socket=Socket}}.
+
+handle_cast(accept, State = #state{socket=Socket}) ->
+ {ok, _} = gen_tcp:accept(Socket),
+ {noreply, State};
+
+handle_cast(_, State) ->
+ {noreply, State}.
+
+handle_call(_E, _From, State) -> {noreply, State}.
+
+handle_info({tcp, Socket, <<Len:32/unsigned-big-integer, Data:Len/binary>>}, State) ->
+ Response = handle_request(Data, State),
+ ok = gen_tcp:send(Socket, Response),
+ {noreply, State};
+
+handle_info({tcp, _, <<0>>}, State) ->
+ {stop, normal, State};
+
+handle_info({tcp_closed, _Socket}, State) ->
+ gen_server:cast(self(), accept),
+ {noreply, State}.
+
+handle_request(<<11>>, #state{pub_key=PubKey}) ->
+ %% REQUEST_IDENTITIES
+ PubKeyLen = byte_size(PubKey),
+ <<?UINT32(18 + PubKeyLen), % message length
+ ?BYTE(12), % message type (1 byte)
+ ?UINT32(1), % number of keys (4 bytes)
+ ?STRING(PubKey), % PubKey (4 + PubKeyLen bytes)
+ ?STRING(<<"lorem">>) % key 1 comment (4 + 5 bytes)
+ >>;
+
+handle_request(<<13, Rest/binary>>, #state{priv_key=PrivKey, pub_key=PubKey, sig_alg=SigAlg}) ->
+ Flags = ?SSH_AGENT_RSA_SHA2_256 bor ?SSH_AGENT_RSA_SHA2_512,
+ <<?DEC_BIN(PubKey, _KeyBlobLen), ?DEC_BIN(Data, _DataLen), ?Euint32(Flags)>> = Rest,
+
+ Hash = ssh_transport:sha(SigAlg),
+ Sig = ssh_transport:sign(Data, Hash, PrivKey),
+ SigFormat = sig_format(SigAlg),
+ Msg =
+ <<?BYTE(14), % message type (1 byte)
+ ?STRING( % nested string (4 bytes)
+ <<?STRING(SigFormat), % signature format (4 + ? bytes)
+ ?STRING(Sig) % signature blob (4 + SigLen bytes)
+ >>
+ )
+ >>,
+ MsgLen = size(Msg),
+ <<?UINT32(MsgLen), % message length
+ Msg/binary
+ >>.
+
+terminate(_Reason, #state{socket_path=SocketPath, socket=Socket}) ->
+ ok = gen_tcp:close(Socket),
+ ok = file:delete(SocketPath).
+
+respond(BinResponse) ->
+ %% We cannot use priv_dir because unix socket paths are limited to 108 bytes.
+ SocketPath = string:chomp(os:cmd("mktemp -u")),
+
+ Parent = self(),
+
+ spawn(fun() ->
+ Address = {local, SocketPath},
+ ConnectOpts = [local, binary, {ip, Address}, {packet, 0}, {active, false}],
+
+ {ok, ListenSocket} = gen_tcp:listen(0, ConnectOpts),
+ Parent ! listening,
+
+ {ok, Socket} = gen_tcp:accept(ListenSocket),
+ {ok, _BinRequest} = gen_tcp:recv(Socket, 0),
+
+ ok = gen_tcp:send(Socket, BinResponse),
+ ok = gen_tcp:close(Socket),
+ ok = gen_tcp:close(ListenSocket),
+ ok = file:delete(SocketPath)
+ end),
+
+ receive
+ listening -> SocketPath
+ end.
+
+check_mktemp(Config) ->
+ case os:find_executable("mktemp") of
+ false ->
+ {skip, "Can't find mktemp in your $PATH"};
+ _ ->
+ Config
+ end.
+
+extract_pubkey(PrivKey) ->
+ PubKey = ssh_transport:extract_public_key(PrivKey),
+ ssh_message:ssh2_pubkey_encode(PubKey).
+
+sig_format('ssh-rsa') -> <<"ssh-rsa">>;
+sig_format('rsa-sha2-256') -> <<"ssh-rsa">>;
+sig_format('rsa-sha2-384') -> <<"ssh-rsa">>;
+sig_format('rsa-sha2-512') -> <<"ssh-rsa">>.
diff --git a/lib/ssh/test/ssh_algorithms_SUITE.erl b/lib/ssh/test/ssh_algorithms_SUITE.erl
index 6dcf52ce13..2ee8fd24bd 100644
--- a/lib/ssh/test/ssh_algorithms_SUITE.erl
+++ b/lib/ssh/test/ssh_algorithms_SUITE.erl
@@ -23,11 +23,33 @@
-module(ssh_algorithms_SUITE).
-include_lib("common_test/include/ct.hrl").
--include_lib("ssh/src/ssh_transport.hrl").
+-include("ssh_transport.hrl").
-include("ssh_test_lib.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ interpolate/1,
+ simple_connect/1,
+ simple_exec/1,
+ simple_exec_groups/0,
+ simple_exec_groups/1,
+ simple_exec_groups_no_match_too_large/1,
+ simple_exec_groups_no_match_too_small/1,
+ simple_sftp/1,
+ sshc_simple_exec_os_cmd/1,
+ sshd_simple_exec/1
+ ]).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -35,7 +57,7 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{seconds,60}}].
+ {timetrap,{seconds,120}}].
all() ->
%% [{group,kex},{group,cipher}... etc
@@ -48,9 +70,9 @@ groups() ->
SshdAlgos = extract_algos(ssh_test_lib:default_algorithms(sshd)),
DoubleAlgos =
- [{Tag, double(Algs)} || {Tag,Algs} <- ErlAlgos,
- length(Algs) > 1,
- lists:member(Tag, two_way_tags())],
+ [{Tag, double(Tag,Algs)} || {Tag,Algs} <- ErlAlgos,
+ length(Algs) > 1,
+ lists:member(Tag, two_way_tags())],
TagGroupSet =
[{Tag, [], group_members_for_tag(Tag,Algs,DoubleAlgos)}
|| {Tag,Algs} <- ErlAlgos,
@@ -60,14 +82,14 @@ groups() ->
TypeSSH = ssh_test_lib:ssh_type(),
AlgoTcSet =
- [{Alg, [parallel], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos,TypeSSH)}
+ [{Alg, [], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos,TypeSSH)}
|| {Tag,Algs} <- ErlAlgos ++ DoubleAlgos,
Alg <- Algs],
TagGroupSet ++ AlgoTcSet.
tags() -> [kex,cipher,mac,compression,public_key].
-two_way_tags() -> [cipher,mac,compression].
+two_way_tags() -> [cipher,mac,compression, public_key].
%%--------------------------------------------------------------------
init_per_suite(Config) ->
@@ -91,7 +113,10 @@ init_per_suite(Config) ->
ssh_test_lib:installed_ssh_version("TIMEOUT"),
ssh:default_algorithms(),
crypto:info_lib(),
- ssh_test_lib:default_algorithms(sshc),
+ ssh_test_lib:default_algorithms(sshc,
+ %% Use a fake system_dir to enable the test
+ %% daemon to start:
+ [{system_dir,proplists:get_value(data_dir,Config)}]),
ssh_test_lib:default_algorithms(sshd),
{?DEFAULT_DH_GROUP_MIN,?DEFAULT_DH_GROUP_NBITS,?DEFAULT_DH_GROUP_MAX},
public_key:dh_gex_group_sizes(),
@@ -126,32 +151,43 @@ init_per_group(Group, Config) ->
init_per_group(public_key=Tag, Alg, Config) ->
- ct:log("Init tests for public_key ~p",[Alg]),
- PrefAlgs = {preferred_algorithms,[{Tag,[Alg]}]},
+ PA =
+ case split(Tag, Alg) of
+ [_] ->
+ [Alg];
+ [A1,A2] ->
+ [A1,A2]
+ end,
+ OtherAlgs = [{T,L} || {T,L} <- ssh_transport:supported_algorithms(), T=/=Tag],
+ ct:log("Init tests for public_key ~p~nOtherAlgs=~p",[PA,OtherAlgs]),
+ PrefAlgs = {preferred_algorithms,[{Tag,PA}|OtherAlgs]},
%% Daemon started later in init_per_testcase
try
- setup_pubkey(Alg,
+ setup_pubkey(PA,
[{pref_algs,PrefAlgs},
- {tag_alg,{Tag,Alg}}
+ {tag_alg,{Tag,PA}}
| Config])
catch
- _:_ -> {skip, io_lib:format("Unsupported: ~p",[Alg])}
+ _C:_E:_S ->
+ ct:log("Exception ~p:~p~n~p",[_C,_E,_S]),
+ {skip, io_lib:format("Unsupported: ~p",[Alg])}
end;
init_per_group(Tag, Alg, Config) ->
PA =
- case split(Alg) of
+ case split(Tag, Alg) of
[_] ->
[Alg];
[A1,A2] ->
[{client2server,[A1]},
{server2client,[A2]}]
end,
- ct:log("Init tests for tag=~p alg=~p",[Tag,PA]),
- PrefAlgs = {preferred_algorithms,[{Tag,PA}]},
+ OtherAlgs = [{T,L} || {T,L} <- ssh_transport:supported_algorithms(), T=/=Tag],
+ ct:log("Init tests for tag=~p alg=~p~nOtherAlgs=~p",[Tag,PA,OtherAlgs]),
+ PrefAlgs = {preferred_algorithms,[{Tag,PA}|OtherAlgs]},
start_std_daemon([PrefAlgs],
[{pref_algs,PrefAlgs},
- {tag_alg,{Tag,Alg}}
+ {tag_alg,{Tag,[Alg]}}
| Config]).
@@ -171,6 +207,7 @@ init_per_testcase(TC, Config) ->
init_per_testcase(TC, {public_key,Alg}, Config) ->
+ ct:log("init_per_testcase TC=~p, Alg=~p",[TC,Alg]),
ExtraOpts = case TC of
simple_connect ->
[{user_dir, proplists:get_value(priv_dir,Config)}];
@@ -178,21 +215,26 @@ init_per_testcase(TC, {public_key,Alg}, Config) ->
[]
end,
Opts = pubkey_opts(Config) ++ ExtraOpts,
- case {ssh_file:user_key(Alg,Opts), ssh_file:host_key(Alg,Opts)} of
+ {UserAlg,SrvrAlg} =
+ case Alg of
+ [A1,A2] -> {A1,A2};
+ [A0] -> {A0,A0}
+ end,
+ case {ssh_file:user_key(UserAlg,Opts), ssh_file:host_key(SrvrAlg,Opts)} of
{{ok,_}, {ok,_}} ->
start_pubkey_daemon([proplists:get_value(pref_algs,Config)
| ExtraOpts],
[{extra_daemon,true}|Config]);
{{ok,_}, {error,Err}} ->
- ct:log("Alg = ~p~nOpts = ~p",[Alg,Opts]),
+ ct:log("SrvrAlg = ~p~nOpts = ~p",[SrvrAlg,Opts]),
{skip, io_lib:format("No host key: ~p",[Err])};
{{error,Err}, {ok,_}} ->
- ct:log("Alg = ~p~nOpts = ~p",[Alg,Opts]),
+ ct:log("UserAlg = ~p~nOpts = ~p",[UserAlg,Opts]),
{skip, io_lib:format("No user key: ~p",[Err])};
_ ->
- ct:log("Alg = ~p~nOpts = ~p",[Alg,Opts]),
+ ct:log("UserAlg = ~p SrvrAlg = ~p~nOpts = ~p",[UserAlg,SrvrAlg,Opts]),
{skip, "Neither host nor user key"}
end;
@@ -242,14 +284,23 @@ simple_exec(Config) ->
%%--------------------------------------------------------------------
%% A simple exec call
simple_connect(Config) ->
+ ct:log("PrivDir ~p:~n~p~n~nPrivDir/system: ~p",[proplists:get_value(priv_dir,Config),
+ file:list_dir(proplists:get_value(priv_dir,Config)),
+ catch file:list_dir(
+ filename:join(proplists:get_value(priv_dir,Config),
+ system))]),
{Host,Port} = proplists:get_value(srvr_addr, Config),
{preferred_algorithms,AlgEntries} = proplists:get_value(pref_algs, Config),
Opts =
case proplists:get_value(tag_alg, Config) of
- {public_key,Alg} -> [{pref_public_key_algs,[Alg]}];
+ {public_key,Alg} -> [{pref_public_key_algs,Alg},
+ {preferred_algorithms,AlgEntries}];
_ -> [{modify_algorithms,[{append,AlgEntries}]}]
end,
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, Opts),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port,
+ [{silently_accept_hosts, true},
+ {user_interaction, false} |
+ Opts]),
ct:log("~p:~p connected! ~p",[?MODULE,?LINE,ConnectionRef]),
ssh:close(ConnectionRef).
@@ -274,7 +325,7 @@ try_exec_simple_group(Group, Config) ->
%% Testing all default groups
simple_exec_groups() ->
- [{timetrap,{seconds,180}}].
+ [{timetrap,{seconds,240}}].
simple_exec_groups(Config) ->
Sizes = interpolate( public_key:dh_gex_group_sizes() ),
@@ -311,7 +362,10 @@ sshc_simple_exec_os_cmd(Config) ->
Result = ssh_test_lib:open_sshc(Host, Port,
[" -C"
" -o UserKnownHostsFile=",KnownHosts,
+ " -o CheckHostIP=no"
" -o StrictHostKeyChecking=no"
+ " -q"
+ " -x"
],
" 1+1."),
Parent ! {result, self(), Result, "2"}
@@ -336,7 +390,7 @@ sshc_simple_exec_os_cmd(Config) ->
sshd_simple_exec(Config) ->
ClientPubKeyOpts =
case proplists:get_value(tag_alg,Config) of
- {public_key,Alg} -> [{pref_public_key_algs,[Alg]}];
+ {public_key,Alg} -> [{pref_public_key_algs,Alg}];
_ -> []
end,
ConnectionRef = ssh_test_lib:connect(22, [{silently_accept_hosts, true},
@@ -389,13 +443,20 @@ sshd_simple_exec(Config) ->
group_members_for_tag(Tag, Algos, DoubleAlgos) ->
[{group,Alg} || Alg <- Algos++proplists:get_value(Tag,DoubleAlgos,[])].
-double(Algs) -> [concat(A1,A2) || A1 <- Algs,
- A2 <- Algs,
- A1 =/= A2].
+double(Tag, Algs) -> [concat(Tag,A1,A2) || A1 <- Algs,
+ A2 <- Algs,
+ A1 =/= A2].
-concat(A1, A2) -> list_to_atom(lists:concat([A1," + ",A2])).
+concat(Tag, A1, A2) ->
+ list_to_atom(lists:concat(["D: ",Tag," ",A1," + ",A2])).
-split(Alg) -> ssh_test_lib:to_atoms(string:tokens(atom_to_list(Alg), " + ")).
+split(TagA, Alg) ->
+ Tag = atom_to_list(TagA),
+ ssh_test_lib:to_atoms(
+ case string:tokens(atom_to_list(Alg), " ") of
+ ["D:",Tag,A1,"+",A2] ->[A1,A2];
+ Other -> Other
+ end).
specific_test_cases(Tag, Alg, SshcAlgos, SshdAlgos, TypeSSH) ->
case Tag of
@@ -429,7 +490,7 @@ supports(Tag, Alg, Algos) ->
lists:all(fun(A) ->
lists:member(A, proplists:get_value(Tag, Algos,[]))
end,
- split(Alg)).
+ split(Tag, Alg)).
extract_algos(Spec) ->
@@ -469,21 +530,33 @@ pubkey_opts(Config) ->
{system_dir, SystemDir}].
-setup_pubkey(Alg, Config) ->
+setup_pubkey([AlgClient, AlgServer], Config) ->
DataDir = proplists:get_value(data_dir, Config),
UserDir = proplists:get_value(priv_dir, Config),
+ ssh_test_lib:del_dir_contents(UserDir),
+ ok = ssh_test_lib:setup_user_key(AlgClient, DataDir, UserDir),
+ _SysDir = ssh_test_lib:setup_host_key_create_dir(AlgServer, DataDir, UserDir),
+try ct:log("~p:~p AlgClient=~p, AlgServer=~p~nPrivDir ~p:~n~p~n~nSYsDir=~p~nPrivDir/system: ~p",
+ [?MODULE,?LINE,
+ AlgClient, AlgServer,
+ proplists:get_value(priv_dir,Config),
+ file:list_dir(proplists:get_value(priv_dir,Config)),
+ _SysDir,
+ catch file:list_dir(
+ filename:join(proplists:get_value(priv_dir,Config),
+ system))
+ ])
+catch _C:_E:_S ->
+ ct:log("~p:~p ~p:~p~n~p",[?MODULE,?LINE,_C,_E,_S])
+end,
+ Config;
+
+setup_pubkey([Alg], Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
ct:log("Setup keys for ~p",[Alg]),
- case Alg of
- 'ssh-dss' -> ssh_test_lib:setup_dsa(DataDir, UserDir);
- 'ssh-rsa' -> ssh_test_lib:setup_rsa(DataDir, UserDir);
- 'rsa-sha2-256' -> ssh_test_lib:setup_rsa(DataDir, UserDir);
- 'rsa-sha2-512' -> ssh_test_lib:setup_rsa(DataDir, UserDir);
- 'ecdsa-sha2-nistp256' -> ssh_test_lib:setup_ecdsa("256", DataDir, UserDir);
- 'ecdsa-sha2-nistp384' -> ssh_test_lib:setup_ecdsa("384", DataDir, UserDir);
- 'ecdsa-sha2-nistp521' -> ssh_test_lib:setup_ecdsa("521", DataDir, UserDir);
- 'ssh-ed25519' -> ssh_test_lib:setup_eddsa(ed25519, DataDir, UserDir);
- 'ssh-ed448' -> ssh_test_lib:setup_eddsa(ed448, DataDir, UserDir)
- end,
+ ssh_test_lib:setup_user_key(Alg, DataDir, PrivDir),
+ ssh_test_lib:setup_host_key_create_dir(Alg, DataDir, PrivDir),
Config.
@@ -492,5 +565,6 @@ simple_exec_group(I, Config) when is_integer(I) ->
simple_exec_group({Min,I,Max}, Config) ->
{Host,Port} = proplists:get_value(srvr_addr, Config),
ssh_test_lib:std_simple_exec(Host, Port, Config,
- [{dh_gex_limits,{Min,I,Max}}]).
+ [proplists:get_value(pref_algs,Config),
+ {dh_gex_limits,{Min,I,Max}}]).
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa
index d306f8b26e..24628e071b 100644
--- a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa
index 9d7e0dd5fb..2202c2ead8 100644
--- a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_all.cover b/lib/ssh/test/ssh_all.cover
new file mode 100644
index 0000000000..6b2d375589
--- /dev/null
+++ b/lib/ssh/test/ssh_all.cover
@@ -0,0 +1 @@
+{incl_app,ssh,details}.
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index a62bdb343d..7c0d5fa9e5 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -27,8 +27,73 @@
-include_lib("kernel/include/file.hrl").
-include("ssh_test_lib.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ always_ok/1,
+ app_test/1,
+ appup_test/1,
+ basic_test/1,
+ check_error/1,
+ cli/1,
+ cli_exit_normal/1,
+ cli_exit_status/1,
+ close/1,
+ daemon_already_started/1,
+ daemon_error_closes_port/1,
+ daemon_opt_fd/1,
+ double_close/1,
+ exec/1,
+ exec_compressed/1,
+ exec_with_io_in/1,
+ exec_with_io_out/1,
+ host_equal/2,
+ idle_time_client/1,
+ idle_time_server/1,
+ inet6_option/0,
+ inet6_option/1,
+ inet_option/1,
+ internal_error/1,
+ ips/1,
+ key_callback/1,
+ key_callback_options/1,
+ known_hosts/1,
+ login_bad_pwd_no_retry1/1,
+ login_bad_pwd_no_retry2/1,
+ login_bad_pwd_no_retry3/1,
+ login_bad_pwd_no_retry4/1,
+ login_bad_pwd_no_retry5/1,
+ misc_ssh_options/1,
+ multi_daemon_opt_fd/1,
+ openssh_zlib_basic_test/1,
+ packet_size/1,
+ pass_phrase/1,
+ peername_sockname/1,
+ send/1,
+ setopts_getopts/1,
+ shell/1,
+ shell_exit_status/1,
+ shell_no_unicode/1,
+ shell_socket/1,
+ shell_ssh_conn/1,
+ shell_unicode_string/1,
+ ssh_file_is_auth_key/1,
+ ssh_file_is_host_key/0,
+ ssh_file_is_host_key/1,
+ ssh_file_is_host_key_misc/1,
+ ssh_info_print/1
+ ]).
+
-define(NEWLINE, <<"\r\n">>).
@@ -40,110 +105,68 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{seconds,40}}].
+ {timetrap,{seconds,90}}].
all() ->
- [{group, all_tests},
- daemon_already_started
+ [{group, all_tests}
].
%%%-define(PARALLEL, ).
-define(PARALLEL, parallel).
groups() ->
- [{all_tests, [?PARALLEL], [{group, ssh_renegotiate_SUITE},
- {group, ssh_basic_SUITE}
- ]},
- {ssh_basic_SUITE, [], [app_test,
- appup_test,
- {group, dsa_key},
- {group, rsa_key},
- {group, ecdsa_sha2_nistp256_key},
- {group, ecdsa_sha2_nistp384_key},
- {group, ecdsa_sha2_nistp521_key},
- {group, ed25519_key},
- {group, ed448_key},
- {group, dsa_pass_key},
- {group, rsa_pass_key},
- {group, ecdsa_sha2_nistp256_pass_key},
- {group, ecdsa_sha2_nistp384_pass_key},
- {group, ecdsa_sha2_nistp521_pass_key},
- {group, host_user_key_differs},
- {group, key_cb},
- {group, internal_error},
- {group, rsa_host_key_is_actualy_ecdsa},
- daemon_already_started,
- double_close,
- daemon_opt_fd,
- multi_daemon_opt_fd,
- packet_size,
- ssh_info_print,
- {group, login_bad_pwd_no_retry},
- shell_exit_status,
- setopts_getopts
- ]},
-
- {ssh_renegotiate_SUITE, [?PARALLEL], [rekey0,
- rekey1,
- rekey2,
- rekey3,
- rekey4,
- rekey_limit_client,
- rekey_limit_daemon,
- rekey_time_limit_client,
- rekey_time_limit_daemon,
- norekey_limit_client,
- norekey_limit_daemon,
- renegotiate1,
- renegotiate2]},
-
- {dsa_key, [], [{group, basic}]},
- {rsa_key, [], [{group, basic}]},
- {ecdsa_sha2_nistp256_key, [], [{group, basic}]},
- {ecdsa_sha2_nistp384_key, [], [{group, basic}]},
- {ecdsa_sha2_nistp521_key, [], [{group, basic}]},
- {ed25519_key, [], [{group, basic}]},
- {ed448_key, [], [{group, basic}]},
- {rsa_host_key_is_actualy_ecdsa, [], [fail_daemon_start]},
- {host_user_key_differs, [?PARALLEL], [exec_key_differs1,
- exec_key_differs2,
- exec_key_differs3,
- exec_key_differs_fail]},
- {dsa_pass_key, [], [pass_phrase]},
- {rsa_pass_key, [], [pass_phrase]},
- {ecdsa_sha2_nistp256_pass_key, [], [pass_phrase]},
- {ecdsa_sha2_nistp384_pass_key, [], [pass_phrase]},
- {ecdsa_sha2_nistp521_pass_key, [], [pass_phrase]},
+ [{all_tests, [?PARALLEL], [{group, sequential},
+ {group, p_basic},
+ {group, internal_error},
+ {group, login_bad_pwd_no_retry},
+ {group, key_cb}
+ ]},
+
+ {sequential, [], [app_test,
+ appup_test,
+ daemon_already_started,
+ daemon_error_closes_port, % Should be re-written..
+ double_close,
+ daemon_opt_fd,
+ multi_daemon_opt_fd,
+ packet_size,
+ ssh_info_print,
+ shell_exit_status,
+ setopts_getopts,
+ known_hosts,
+ ssh_file_is_host_key,
+ ssh_file_is_host_key_misc,
+ ssh_file_is_auth_key
+ ]},
+
{key_cb, [?PARALLEL], [key_callback, key_callback_options]},
- {internal_error, [], [internal_error]},
+
+ {internal_error, [?PARALLEL], [internal_error]},
+
{login_bad_pwd_no_retry, [?PARALLEL], [login_bad_pwd_no_retry1,
- login_bad_pwd_no_retry2,
- login_bad_pwd_no_retry3,
- login_bad_pwd_no_retry4,
- login_bad_pwd_no_retry5
- ]},
+ login_bad_pwd_no_retry2,
+ login_bad_pwd_no_retry3,
+ login_bad_pwd_no_retry4,
+ login_bad_pwd_no_retry5
+ ]},
- {basic, [], [{group,p_basic},
- shell, shell_no_unicode, shell_unicode_string,
- close,
- known_hosts
- ]},
{p_basic, [?PARALLEL], [send, peername_sockname,
- exec, exec_compressed,
- exec_with_io_out, exec_with_io_in,
- cli,
- idle_time_client, idle_time_server, openssh_zlib_basic_test,
- misc_ssh_options, inet_option, inet6_option]}
+ exec, exec_compressed,
+ exec_with_io_out, exec_with_io_in,
+ cli, cli_exit_normal, cli_exit_status,
+ idle_time_client, idle_time_server, openssh_zlib_basic_test,
+ misc_ssh_options, inet_option, inet6_option,
+ shell, shell_socket, shell_ssh_conn, shell_no_unicode, shell_unicode_string,
+ close
+ ]}
].
-
-
-
-
%%--------------------------------------------------------------------
init_per_suite(Config) ->
?CHECK_CRYPTO(begin
ssh:start(),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
Config
end).
@@ -151,279 +174,22 @@ end_per_suite(_Config) ->
ssh:stop().
%%--------------------------------------------------------------------
-init_per_group(ssh_renegotiate_SUITE, Config) ->
- [{preferred_algorithms, ssh:default_algorithms()} | Config];
-init_per_group(dsa_key, Config) ->
- case lists:member('ssh-dss',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(rsa_key, Config) ->
- case lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(rsa_host_key_is_actualy_ecdsa, Config) ->
- case
- lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key)) and
- lists:member('ecdsa-sha2-nistp256',
- ssh_transport:default_algorithms(public_key))
- of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir),
- %% The following sets up bad rsa keys:
- begin
- UserDir = PrivDir,
- System = filename:join(UserDir, "system"),
- file:copy(filename:join(DataDir, "id_rsa"), filename:join(UserDir, "id_rsa")),
- file:rename(filename:join(System, "ssh_host_ecdsa_key"), filename:join(System, "ssh_host_rsa_key")),
- file:rename(filename:join(System, "ssh_host_ecdsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")),
- ssh_test_lib:setup_rsa_known_host(DataDir, UserDir),
- ssh_test_lib:setup_rsa_auth_keys(DataDir, UserDir)
- end,
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp256_key, Config) ->
- case lists:member('ecdsa-sha2-nistp256',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp384_key, Config) ->
- case lists:member('ecdsa-sha2-nistp384',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_ecdsa("384", DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp521_key, Config) ->
- case lists:member('ecdsa-sha2-nistp521',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_ecdsa("521", DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ed25519_key, Config) ->
- case lists:member('ssh-ed25519',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_eddsa(ed25519, DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ed448_key, Config) ->
- case lists:member('ssh-ed448',
- ssh_transport:default_algorithms(public_key)) of
- true ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_eddsa(ed448, DataDir, PrivDir),
- Config;
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(rsa_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_rsa_pass_phrase(DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {rsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(dsa_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ssh-dss',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_dsa_pass_phrase(DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {dsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp256_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ecdsa-sha2-nistp256',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_ecdsa_pass_phrase("256", DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {ecdsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp384_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ecdsa-sha2-nistp384',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_ecdsa_pass_phrase("384", DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {ecdsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(ecdsa_sha2_nistp521_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- case lists:member('ecdsa-sha2-nistp521',
- ssh_transport:default_algorithms(public_key))
- andalso
- ssh_test_lib:setup_ecdsa_pass_phrase("521", DataDir, PrivDir, "Password")
- of
- true ->
- [{pass_phrase, {ecdsa_pass_phrase, "Password"}}| Config];
- false ->
- {skip, unsupported_pub_key}
- end;
-init_per_group(host_user_key_differs, Config) ->
- Data = proplists:get_value(data_dir, Config),
- Sys = filename:join(proplists:get_value(priv_dir, Config), system_rsa),
- SysUsr = filename:join(Sys, user),
- Usr = filename:join(proplists:get_value(priv_dir, Config), user_ecdsa_256),
- file:make_dir(Sys),
- file:make_dir(SysUsr),
- file:make_dir(Usr),
- file:copy(filename:join(Data, "ssh_host_rsa_key"), filename:join(Sys, "ssh_host_rsa_key")),
- file:copy(filename:join(Data, "ssh_host_rsa_key.pub"), filename:join(Sys, "ssh_host_rsa_key.pub")),
- file:copy(filename:join(Data, "id_ecdsa256"), filename:join(Usr, "id_ecdsa")),
- file:copy(filename:join(Data, "id_ecdsa256.pub"), filename:join(Usr, "id_ecdsa.pub")),
- ssh_test_lib:setup_ecdsa_auth_keys("256", Data, SysUsr),
- ssh_test_lib:setup_rsa_known_host(Sys, Usr),
- Config;
init_per_group(key_cb, Config) ->
case lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key)) of
+ ssh_transport:supported_algorithms(public_key)) of
true ->
DataDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
+ ssh_test_lib:setup_user_key('ssh-rsa', DataDir, PrivDir),
+ ssh_test_lib:setup_host_key_create_dir('ssh-rsa', DataDir, PrivDir),
Config;
false ->
{skip, unsupported_pub_key}
end;
-init_per_group(internal_error, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- %% In the test case the key will be deleted after the daemon start:
- %% ... file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")),
- Config;
-init_per_group(dir_options, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- %% Make unreadable dir:
- Dir_unreadable = filename:join(PrivDir, "unread"),
- ok = file:make_dir(Dir_unreadable),
- {ok,F1} = file:read_file_info(Dir_unreadable),
- ok = file:write_file_info(Dir_unreadable,
- F1#file_info{mode = F1#file_info.mode band (bnot 8#00444)}),
- %% Make readable file:
- File_readable = filename:join(PrivDir, "file"),
- ok = file:write_file(File_readable, <<>>),
-
- %% Check:
- case {file:read_file_info(Dir_unreadable),
- file:read_file_info(File_readable)} of
- {{ok, Id=#file_info{type=directory, access=Md}},
- {ok, If=#file_info{type=regular, access=Mf}}} ->
- AccessOK =
- case {Md, Mf} of
- {read, _} -> false;
- {read_write, _} -> false;
- {_, read} -> true;
- {_, read_write} -> true;
- _ -> false
- end,
-
- case AccessOK of
- true ->
- %% Save:
- [{unreadable_dir, Dir_unreadable},
- {readable_file, File_readable}
- | Config];
- false ->
- ct:log("File#file_info : ~p~n"
- "Dir#file_info : ~p",[If,Id]),
- {skip, "File or dir mode settings failed"}
- end;
-
- NotDirFile ->
- ct:log("{Dir,File} -> ~p",[NotDirFile]),
- {skip, "File/Dir creation failed"}
- end;
init_per_group(_, Config) ->
Config.
-end_per_group(dsa_key, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(PrivDir),
- Config;
-end_per_group(rsa_key, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(PrivDir),
- Config;
-end_per_group(dsa_pass_key, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(PrivDir),
- Config;
-end_per_group(rsa_pass_key, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(PrivDir),
- Config;
-end_per_group(key_cb, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(PrivDir),
- Config;
-end_per_group(internal_error, Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(PrivDir),
- Config;
-
end_per_group(_, Config) ->
Config.
%%--------------------------------------------------------------------
@@ -456,11 +222,6 @@ init_per_testcase(inet6_option, Config) ->
init_per_testcase(_TestCase, Config) ->
Config.
-end_per_testcase(TestCase, Config) when TestCase == server_password_option;
- TestCase == server_userpassword_option ->
- UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
- ssh_test_lib:del_dirs(UserDir),
- end_per_testcase(Config);
end_per_testcase(TC, Config) when TC==shell_no_unicode ;
TC==shell_unicode_string ->
case proplists:get_value(sftpd, Config) of
@@ -521,6 +282,7 @@ inet_option(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
%%% Test configuring IPv6
+inet6_option() -> [{timetrap,{seconds,30}}].
inet6_option(Config) when is_list(Config) ->
SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
UserDir = proplists:get_value(priv_dir, Config),
@@ -597,13 +359,16 @@ exec_with_io_out(Config) when is_list(Config) ->
"io:write(hej).", infinity),
case ssh_test_lib:receive_exec_result(
[{ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"hej">>}},
- {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"ok">>}}]) of
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"ok">>}},
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}},
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}},
+ {ssh_cm, ConnectionRef, {closed, ChannelId0}}
+ ]) of
expected ->
ok;
Other0 ->
ct:fail(Other0)
end,
- ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0),
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid).
@@ -665,43 +430,40 @@ exec_compressed(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-%%% Idle timeout test, client
-idle_time_client(Config) ->
- SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
- UserDir = proplists:get_value(priv_dir, Config),
+%%% Idle timeout test
+idle_time_client(Config) -> idle_time_common([], [{idle_time, 2000}], Config).
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {user_dir, UserDir},
- {failfun, fun ssh_test_lib:failfun/2}]),
- ConnectionRef =
- ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
- {user_dir, UserDir},
- {user_interaction, false},
- {idle_time, 2000}]),
- {ok, Id} = ssh_connection:session_channel(ConnectionRef, 1000),
- ssh_connection:close(ConnectionRef, Id),
- receive
- after 10000 ->
- {error, closed} = ssh_connection:session_channel(ConnectionRef, 1000)
- end,
- ssh:stop_daemon(Pid).
+idle_time_server(Config) -> idle_time_common([{idle_time, 2000}], [], Config).
-%%--------------------------------------------------------------------
-%%% Idle timeout test, server
-idle_time_server(Config) ->
+
+idle_time_common(DaemonExtraOpts, ClientExtraOpts, Config) ->
SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
UserDir = proplists:get_value(priv_dir, Config),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
- {idle_time, 2000},
- {failfun, fun ssh_test_lib:failfun/2}]),
+ {failfun, fun ssh_test_lib:failfun/2}
+ | DaemonExtraOpts
+ ]),
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
- {user_interaction, false}]),
- {ok, Id} = ssh_connection:session_channel(ConnectionRef, 1000),
- ssh_connection:close(ConnectionRef, Id),
+ {user_interaction, false}
+ | ClientExtraOpts
+ ]),
+ {ok, Id1} = ssh_sftp:start_channel(ConnectionRef),
+ {ok, Id2} = ssh_sftp:start_channel(ConnectionRef),
+ ssh_sftp:stop_channel(Id2),
+ timer:sleep(2500),
+ {ok, Id3} = ssh_sftp:start_channel(ConnectionRef),
+ ssh_sftp:stop_channel(Id1),
+ ssh_sftp:stop_channel(Id3),
+ timer:sleep(1000),
+ {ok, Id4} = ssh_sftp:start_channel(ConnectionRef),
+ timer:sleep(2500),
+ {ok, Id5} = ssh_sftp:start_channel(ConnectionRef),
+ ssh_sftp:stop_channel(Id4),
+ ssh_sftp:stop_channel(Id5),
receive
after 10000 ->
{error, closed} = ssh_connection:session_channel(ConnectionRef, 1000)
@@ -722,7 +484,8 @@ shell(Config) when is_list(Config) ->
IO = ssh_test_lib:start_io_server(),
Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}]),
receive
- {'EXIT', _, _} ->
+ {'EXIT', _, _} = Exit ->
+ ct:log("~p:~p ~p", [?MODULE,?LINE,Exit]),
ct:fail(no_ssh_connection);
ErlShellStart ->
ct:log("Erlang shell start: ~p~n", [ErlShellStart]),
@@ -732,80 +495,76 @@ shell(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-%%% Test that we could user different types of host pubkey and user pubkey
-exec_key_differs1(Config) -> exec_key_differs(Config, ['ecdsa-sha2-nistp256']).
-
-exec_key_differs2(Config) -> exec_key_differs(Config, ['ssh-dss','ecdsa-sha2-nistp256']).
-
-exec_key_differs3(Config) -> exec_key_differs(Config, ['ecdsa-sha2-nistp384','ecdsa-sha2-nistp256']).
+%%% Test that ssh:shell/2 works when attaching to a open TCP-connection
+shell_socket(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+ UserDir = proplists:get_value(priv_dir, Config),
+ {_Pid, Host0, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ Host = ssh_test_lib:mangle_connect_address(Host0),
+ ct:sleep(500),
+ %% First test with active mode:
+ {ok,ActiveSock} = gen_tcp:connect(Host,
+ Port,
+ [{active,true}]),
+ {error,not_passive_mode} = ssh:shell(ActiveSock),
+ ct:log("~p:~p active tcp socket failed ok", [?MODULE,?LINE]),
+ gen_tcp:close(ActiveSock),
+
+ %% Secondly, test with an UDP socket:
+ {ok,BadSock} = gen_udp:open(0),
+ {error,not_tcp_socket} = ssh:shell(BadSock),
+ ct:log("~p:~p udp socket failed ok", [?MODULE,?LINE]),
+ gen_udp:close(BadSock),
+
+ %% And finaly test with passive mode (which should work):
+ IO = ssh_test_lib:start_io_server(),
+ {ok,Sock} = gen_tcp:connect(Host, Port, [{active,false}]),
+ Shell = ssh_test_lib:start_shell(Sock, IO, [{user_dir,UserDir}]),
+ gen_tcp:controlling_process(Sock, Shell),
+ Shell ! start,
-exec_key_differs(Config, UserPKAlgs) ->
- case lists:usort(['ssh-rsa'|UserPKAlgs])
- -- ssh_transport:supported_algorithms(public_key)
- of
- [] ->
- process_flag(trap_exit, true),
- SystemDir = filename:join(proplists:get_value(priv_dir, Config), system_rsa),
- SystemUserDir = filename:join(SystemDir, user),
- UserDir = filename:join(proplists:get_value(priv_dir, Config), user_ecdsa_256),
-
- {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {user_dir, SystemUserDir},
- {preferred_algorithms,
- [{public_key,['ssh-rsa'|UserPKAlgs]}]}]),
- ct:sleep(500),
-
- IO = ssh_test_lib:start_io_server(),
- Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir},
- {preferred_algorithms,[{public_key,['ssh-rsa']}]},
- {pref_public_key_algs,UserPKAlgs}
- ]),
-
-
- receive
- {'EXIT', _, _} ->
- ct:fail(no_ssh_connection);
- ErlShellStart ->
- ct:log("Erlang shell start: ~p~n", [ErlShellStart]),
- do_shell(IO, Shell)
- after
- 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end;
-
- UnsupportedPubKeys ->
- {skip, io_lib:format("~p unsupported",[UnsupportedPubKeys])}
+ receive
+ {'EXIT', _, _} = Exit ->
+ ct:log("~p:~p ~p", [?MODULE,?LINE,Exit]),
+ ct:fail(no_ssh_connection);
+ ErlShellStart ->
+ ct:log("Erlang shell start: ~p~n", [ErlShellStart]),
+ do_shell(IO, Shell)
+ after
+ 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
-
+
%%--------------------------------------------------------------------
-exec_key_differs_fail(Config) when is_list(Config) ->
+%%% Test that ssh:shell/2 works when attaching to a open SSH-connection
+shell_ssh_conn(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- SystemDir = filename:join(proplists:get_value(priv_dir, Config), system_rsa),
- SystemUserDir = filename:join(SystemDir, user),
- UserDir = filename:join(proplists:get_value(priv_dir, Config), user_ecdsa_256),
-
- {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {user_dir, SystemUserDir},
- {preferred_algorithms,
- [{public_key,['ssh-rsa']}]}]),
+ SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+ UserDir = proplists:get_value(priv_dir, Config),
+
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
ct:sleep(500),
IO = ssh_test_lib:start_io_server(),
- ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir},
- {recv_ext_info, false},
- {preferred_algorithms,[{public_key,['ssh-rsa']}]},
- {pref_public_key_algs,['ssh-dss']}]),
+ {ok,C} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user_interaction, false}]),
+ Shell = ssh_test_lib:start_shell(C, IO, undefined),
receive
- {'EXIT', _, _} ->
- ok;
+ {'EXIT', _, _} = Exit ->
+ ct:log("~p:~p ~p", [?MODULE,?LINE,Exit]),
+ ct:fail(no_ssh_connection);
ErlShellStart ->
ct:log("Erlang shell start: ~p~n", [ErlShellStart]),
- ct:fail(connection_not_rejected)
- after
+ do_shell(IO, Shell)
+ after
30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
-
+
%%--------------------------------------------------------------------
cli(Config) when is_list(Config) ->
process_flag(trap_exit, true),
@@ -847,6 +606,57 @@ cli(Config) when is_list(Config) ->
30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
+%%-----------------------------------------------------------------------------
+%%% Test that SSH client receives exit-status 0 on successful command execution
+cli_exit_normal(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+ UserDir = proplists:get_value(priv_dir, Config),
+
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
+ {password, "morot"},
+ {ssh_cli, {ssh_cli, [fun (_) -> spawn(fun () -> ok end) end]}},
+ {subsystems, []},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ct:sleep(500),
+
+ 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),
+ ssh_connection:shell(ConnectionRef, ChannelId),
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId, _ExpectedExitStatus = 0).
+
+%%---------------------------------------------------------
+%%% Test that SSH client receives user provided exit-status
+cli_exit_status(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+ UserDir = proplists:get_value(priv_dir, Config),
+ NonZeroExitStatus = 7,
+
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
+ {password, "morot"},
+ {ssh_cli, {ssh_cli, [fun (_) ->
+ spawn(fun () -> exit({exit_status, NonZeroExitStatus}) end)
+ end]}},
+ {subsystems, []},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ct:sleep(500),
+
+ 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),
+ ssh_connection:shell(ConnectionRef, ChannelId),
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId, NonZeroExitStatus).
+
%%--------------------------------------------------------------------
%%% Test that get correct error message if you try to start a daemon
%%% on an adress that already runs a daemon see also seq10667
@@ -865,6 +675,11 @@ daemon_already_started(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
%%% Test that a failed daemon start does not leave the port open
+
+%%%%%%%%%%%%%%%%%%%%%% REWRITE! %%%%%%%%%%%%%%%%%%%%
+%%% 1) check that {error,_} is not {error,eaddrinuse}
+%%% 2) instead of ssh_test_lib:daemon second time, use gen_tcp:listen
+
daemon_error_closes_port(Config) ->
GoodSystemDir = proplists:get_value(data_dir, Config),
Port = inet_port(),
@@ -880,6 +695,11 @@ daemon_error_closes_port(Config) ->
ssh:stop_daemon(Pid)
end.
+inet_port() ->
+ {ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]),
+ {ok, Port} = inet:port(Socket),
+ gen_tcp:close(Socket),
+ Port.
%%--------------------------------------------------------------------
%%% check that known_hosts is updated correctly
@@ -900,13 +720,174 @@ known_hosts(Config) when is_list(Config) ->
{ok, _Channel} = ssh_connection:session_channel(ConnectionRef, infinity),
ok = ssh:close(ConnectionRef),
{ok, Binary} = file:read_file(KnownHosts),
+ ct:log("known_hosts:~n~p",[Binary]),
Lines = string:tokens(binary_to_list(Binary), "\n"),
[Line] = Lines,
[HostAndIp, Alg, _KeyData] = string:tokens(Line, " "),
- [StoredHost, _Ip] = string:tokens(HostAndIp, ","),
- true = ssh_test_lib:match_ip(StoredHost, Host),
+
+ {StoredHost,StoredPort} =
+ case HostAndIp of
+ "["++X -> [Hpart,":"++Pstr] = string:tokens(X, "]"),
+ {Hpart,list_to_integer(Pstr)};
+ _ -> {HostAndIp,Port}
+ end,
+
+ true = ssh_test_lib:match_ip(StoredHost, Host) andalso (Port==StoredPort),
"ssh-" ++ _ = Alg,
+ NLines = length(binary:split(Binary, <<"\n">>, [global,trim_all])),
+ ct:log("NLines = ~p~n~p", [NLines,Binary]),
+ if
+ NLines>1 -> ct:fail("wrong num lines", []);
+ NLines<1 -> ct:fail("wrong num lines", []);
+ true -> ok
+ end,
+
+ _ConnectionRef2 =
+ ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
+ {user_interaction, false},
+ silently_accept_hosts]),
+ {ok, Binary2} = file:read_file(KnownHosts),
+ case Binary of
+ Binary2 -> ok;
+ _ -> ct:log("2nd differ~n~p", [Binary2]),
+ ct:fail("wrong num lines", [])
+ end,
+
+ Binary3 = <<"localhost,",Binary/binary>>,
+ ok = file:write_file(KnownHosts, Binary3),
+ _ConnectionRef3 =
+ ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
+ {user_interaction, false},
+ silently_accept_hosts]),
+ ct:log("New known_hosts:~n~p",[Binary3]),
+ {ok, Binary4} = file:read_file(KnownHosts),
+ case Binary3 of
+ Binary4 -> ok;
+ _ -> ct:log("2nd differ~n~p", [Binary4]),
+ ct:fail("wrong num lines", [])
+ end,
+
+
ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+ssh_file_is_host_key() -> [{timetrap,{seconds,240}}]. % Some machines are S L O W !
+ssh_file_is_host_key(Config) ->
+ Dir = ssh_test_lib:create_random_dir(Config),
+ ct:log("Dir = ~p", [Dir]),
+ KnownHosts = filename:join(Dir, "known_hosts"),
+
+ Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
+ 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
+ Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
+ 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
+ 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
+ 190,175,232,37,97,128>>},
+ Key3 = {'RSAPublicKey',26565213557098441060571713941539431805641814292761836797158846333985276408616038302348064841541244792430014595960643885863857366044141899534486816837416587694213836843799730043696945690516841209754307951050689906601353687467659852190777927968674989320642319504162787468947018505175948989102544757855693228490011564030927714896252701919941617689227585365348356580525802093985552564228730275431222515673065363441446158870936027338182083252824862151536327733046243804704721201548991176621134884093279416695997338124856506800535228380202243308550318880784741179703553922258881924287662178348044420509921666661119986374777,
+ 65537},
+
+ FileContents = <<"h11,h12,[h13]:*,h14 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9\n",
+ "h21,[h22]:2345,h23 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
+ "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n",
+ " \n",
+ "\n",
+ "h31 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSb+D77XKvkMDWGu05CD6gWlEXJ+exSvxmegU1pvicPds090qTK3HwSzV7Hg1YVEV6bUiO74Om9Da4EMQponiSeLfVlIkBY5Ko4am4HMNOPTi5Ac4zR1B36nPvyTJluHKOZiCE0ZkSjKYvLEua0Y4Gqd+4RS93Q6r31OO8ukEVM+gG7z0tvhVLkAo8G5QnGRPW0z11tkfEeyjJzhk8H+4lmNjJRK4m6z71P0ACAEBJCpYKpKY3+AjksWuEZnWLgfuk9aPI4q8tI/TO3lF1BmyTPj7/QTFMiWgL7lNM94oaRHTjZ1CdB0UAW1+TMABu155z5KxVUIzrMoVKGBmJPhh5"
+ >>,
+ ok = file:write_file(KnownHosts, FileContents),
+
+ true = ssh_file:is_host_key(Key1, "h11", 22, 'ssh-ed25519', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key1, "h12", 22, 'ssh-ed25519', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key1, "h13", 1234, 'ssh-ed25519', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key1, "h13", 22, 'ssh-ed25519', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key1, "h14", 22, 'ssh-ed25519', [{user_dir,Dir}]),
+
+ true = ssh_file:is_host_key(Key1, ["h11","noh1"], 22, 'ssh-ed25519', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key1, ["noh1","h11"], 22, 'ssh-ed25519', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key1, ["noh1","h12","noh2"], 22, 'ssh-ed25519', [{user_dir,Dir}]),
+
+ true = ssh_file:is_host_key(Key2, "h21", 22, 'ssh-ed448', [{user_dir,Dir}]),
+ false= ssh_file:is_host_key(Key2, "h22", 22, 'ssh-ed448', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key2, "h22", 2345, 'ssh-ed448', [{user_dir,Dir}]),
+ false= ssh_file:is_host_key(Key2, "h22", 1234, 'ssh-ed448', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key2, "h23", 22, 'ssh-ed448', [{user_dir,Dir}]),
+
+ false = ssh_file:is_host_key(Key2, "h11", 22, 'ssh-ed448', [{user_dir,Dir}]),
+ false = ssh_file:is_host_key(Key1, "h21", 22, 'ssh-ed25519', [{user_dir,Dir}]),
+
+ true = ssh_file:is_host_key(Key3, "h31", 22, 'ssh-rsa', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key3, "h31", 22, 'rsa-sha2-256',[{user_dir,Dir}]),
+
+ ok.
+
+%%--------------------------------------------------------------------
+ssh_file_is_host_key_misc(Config) ->
+ Dir = ssh_test_lib:create_random_dir(Config),
+ ct:log("Dir = ~p", [Dir]),
+ KnownHosts = filename:join(Dir, "known_hosts"),
+
+ Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
+ 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
+ Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
+ 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
+ 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
+ 190,175,232,37,97,128>>},
+
+ FileContents = <<"h11,h12,!h12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9\n",
+ %% Key revoked later in file:
+ "h22 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
+ "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n",
+ "@revoked h22 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
+ "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n",
+ "h21 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
+ "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n"
+ >>,
+ ok = file:write_file(KnownHosts, FileContents),
+
+ true = ssh_file:is_host_key(Key1, "h11", 22, 'ssh-ed25519', [{user_dir,Dir}]),
+ true = ssh_file:is_host_key(Key2, "h21", 22, 'ssh-ed448', [{user_dir,Dir}]),
+
+ true = ssh_file:is_host_key(Key2, "h21", 22, 'ssh-ed448', [{user_dir,Dir},
+ {key_cb_private,[{optimize,space}]}]),
+ %% Check revoked key:
+ {error,revoked_key} =
+ ssh_file:is_host_key(Key2, "h22", 22, 'ssh-ed448', [{user_dir,Dir}]),
+ {error,revoked_key} =
+ ssh_file:is_host_key(Key2, "h22", 22, 'ssh-ed448', [{user_dir,Dir},
+ {key_cb_private,[{optimize,space}]}]),
+ %% Check key with "!" in pattern:
+ false= ssh_file:is_host_key(Key1, "h12", 22, 'ssh-ed25519', [{user_dir,Dir}]),
+
+ ok.
+
+%%--------------------------------------------------------------------
+ssh_file_is_auth_key(Config) ->
+ Dir = ssh_test_lib:create_random_dir(Config),
+ ct:log("Dir = ~p", [Dir]),
+ AuthKeys = filename:join(Dir, "authorized_keys"),
+
+ Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
+ 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
+ Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
+ 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
+ 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
+ 190,175,232,37,97,128>>},
+
+ FileContents = <<" \n",
+ "# A test file\n",
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 foo@example.com\n",
+ "no-X11-forwarding,pty ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
+ "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= bar@example.com\n"
+ >>,
+ ok = file:write_file(AuthKeys, FileContents),
+
+ true = ssh_file:is_auth_key(Key1, "donald_duck", [{user_dir,Dir}]),
+ true = ssh_file:is_auth_key(Key2, "mickey_mouse", [{user_dir,Dir}]),
+
+ true = ssh_file:is_auth_key(Key1, "donald_duck", [{user_dir,Dir},{key_cb_private,[{optimize,space}]}]),
+ true = ssh_file:is_auth_key(Key2, "mickey_mouse", [{user_dir,Dir},{key_cb_private,[{optimize,space}]}]),
+
+ ok.
+
%%--------------------------------------------------------------------
%%% Test that we can use keyes protected by pass phrases
@@ -985,13 +966,16 @@ internal_error(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
UserDir = proplists:get_value(priv_dir, Config),
SystemDir = filename:join(PrivDir, system),
-
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}]),
%% Now provoke an error in the following connect:
+ file:delete(filename:join(PrivDir, "system/ssh_host_rsa_key")),
file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")),
+ file:delete(filename:join(PrivDir, "system/ssh_host_ecdsa_key")),
+ file:delete(filename:join(PrivDir, "system/ssh_host_ed25519_key")),
+ file:delete(filename:join(PrivDir, "system/ssh_host_ed448_key")),
{error, Error} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
@@ -1006,12 +990,13 @@ send(Config) when is_list(Config) ->
process_flag(trap_exit, true),
SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
UserDir = proplists:get_value(priv_dir, Config),
-
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}]),
ConnectionRef =
- ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ ssh_test_lib:connect(Host, Port, [{preferred_algorithms, ssh_transport:supported_algorithms()},
+ {silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false}]),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
@@ -1021,17 +1006,6 @@ send(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
-%%%
-fail_daemon_start(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
- SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
- UserDir = proplists:get_value(priv_dir, Config),
-
- {error,_} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {user_dir, UserDir},
- {failfun, fun ssh_test_lib:failfun/2}]).
-
-%%--------------------------------------------------------------------
%%% Test ssh:connection_info([peername, sockname])
peername_sockname(Config) when is_list(Config) ->
process_flag(trap_exit, true),
@@ -1421,11 +1395,10 @@ shell_exit_status(Config) when is_list(Config) ->
SystemDir = proplists:get_value(data_dir, Config),
UserDir = proplists:get_value(priv_dir, Config),
- ShellFun = fun (_User) -> spawn(fun() -> ok end) end,
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{user_passwords, [{"vego", "morot"}]},
- {shell, ShellFun},
+ {shell, {?MODULE,always_ok,[]}},
{failfun, fun ssh_test_lib:failfun/2}]),
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
@@ -1439,14 +1412,15 @@ shell_exit_status(Config) when is_list(Config) ->
ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
ssh:stop_daemon(Pid).
-
+always_ok(_) -> ok.
+
%%----------------------------------------------------------------------------
setopts_getopts(Config) ->
process_flag(trap_exit, true),
SystemDir = proplists:get_value(data_dir, Config),
UserDir = proplists:get_value(priv_dir, Config),
- ShellFun = fun (_User) -> spawn(fun() -> ok end) end,
+ ShellFun = fun (_User, _Peer) -> spawn(fun() -> ok end) end,
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{user_passwords, [{"vego", "morot"}]},
@@ -1454,6 +1428,7 @@ setopts_getopts(Config) ->
{failfun, fun ssh_test_lib:failfun/2}]),
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {quiet_mode, true}, % Just to use quiet_mode once
{user_dir, UserDir},
{user, "vego"},
{password, "morot"},
@@ -1476,315 +1451,6 @@ setopts_getopts(Config) ->
ssh:stop_daemon(Pid).
-%%----------------------------------------------------------------------------
-%%% Idle timeout test
-rekey0() -> [{timetrap,{seconds,90}}].
-rekey1() -> [{timetrap,{seconds,90}}].
-rekey2() -> [{timetrap,{seconds,90}}].
-rekey3() -> [{timetrap,{seconds,90}}].
-rekey4() -> [{timetrap,{seconds,90}}].
-
-rekey0(Config) -> rekey_chk(Config, 0, 0).
-rekey1(Config) -> rekey_chk(Config, infinity, 0).
-rekey2(Config) -> rekey_chk(Config, {infinity,infinity}, 0).
-rekey3(Config) -> rekey_chk(Config, 0, infinity).
-rekey4(Config) -> rekey_chk(Config, 0, {infinity,infinity}).
-
-rekey_chk(Config, RLdaemon, RLclient) ->
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, [{rekey_limit, RLdaemon}]),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, RLclient}]),
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- %% Make both sides send something:
- {ok, _SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- %% Check rekeying
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex1==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
-
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-%%% Test rekeying by data volume
-
-rekey_limit_client() -> [{timetrap,{seconds,400}}].
-rekey_limit_client(Config) ->
- Limit = 6000,
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "rekey.data"),
- Data = lists:duplicate(Limit+10,1),
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
- {max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- %% Check that it doesn't rekey without data transfer
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that datatransfer triggers rekeying
- ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
-
- %% Check that datatransfer continues to trigger rekeying
- ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
-
- %% Check that it doesn't rekey without data transfer
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it doesn't rekey on a small datatransfer
- ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it doesn't rekey without data transfer
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-
-
-rekey_limit_daemon() -> [{timetrap,{seconds,400}}].
-rekey_limit_daemon(Config) ->
- Limit = 6000,
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile1 = filename:join(UserDir, "rekey1.data"),
- DataFile2 = filename:join(UserDir, "rekey2.data"),
- file:write_file(DataFile1, lists:duplicate(Limit+10,1)),
- file:write_file(DataFile2, "hi\n"),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
- {max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- %% Check that it doesn't rekey without data transfer
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
- timer:sleep(?REKEY_DATA_TMO),
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- %% Check that datatransfer triggers rekeying
- {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
-
- %% Check that datatransfer continues to trigger rekeying
- {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
- timer:sleep(?REKEY_DATA_TMO),
- ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
-
- %% Check that it doesn't rekey without data transfer
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it doesn't rekey on a small datatransfer
- {ok,_} = ssh_sftp:read_file(SftpPid, DataFile2),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it doesn't rekey without data transfer
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-
-%%--------------------------------------------------------------------
-%% Check that datatransfer in the other direction does not trigger re-keying
-norekey_limit_client() -> [{timetrap,{seconds,400}}].
-norekey_limit_client(Config) ->
- Limit = 6000,
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "rekey3.data"),
- file:write_file(DataFile, lists:duplicate(Limit+10,1)),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
- {max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- {ok,_} = ssh_sftp:read_file(SftpPid, DataFile),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%% Check that datatransfer in the other direction does not trigger re-keying
-norekey_limit_daemon() -> [{timetrap,{seconds,400}}].
-norekey_limit_daemon(Config) ->
- Limit = 6000,
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "rekey4.data"),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
- {max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ok = ssh_sftp:write_file(SftpPid, DataFile, lists:duplicate(Limit+10,1)),
- timer:sleep(?REKEY_DATA_TMO),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-%%% Test rekeying by time
-
-rekey_time_limit_client() -> [{timetrap,{seconds,400}}].
-rekey_time_limit_client(Config) ->
- Minutes = ?REKEY_DATA_TMO div 60000,
- GB = 1024*1000*1000,
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, {Minutes, GB}},
- {max_random_length_padding,0}]),
- rekey_time_limit(Pid, ConnectionRef).
-
-rekey_time_limit_daemon() -> [{timetrap,{seconds,400}}].
-rekey_time_limit_daemon(Config) ->
- Minutes = ?REKEY_DATA_TMO div 60000,
- GB = 1024*1000*1000,
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, {Minutes, GB}},
- {max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
- rekey_time_limit(Pid, ConnectionRef).
-
-
-rekey_time_limit(Pid, ConnectionRef) ->
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- timer:sleep(5000),
- true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
-
- %% Check that it rekeys when the max time + 30s has passed
- timer:sleep(?REKEY_DATA_TMO + 30*1000),
- ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
-
- %% Check that it does not rekey when nothing is transferred
- timer:sleep(?REKEY_DATA_TMO + 30*1000),
- ?wait_match(false, Kex2==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
-
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-
-%%% Test rekeying with simultaneous send request
-
-renegotiate1(Config) ->
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "renegotiate1.data"),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
-
- ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
-
- ssh_relay:hold(RelayPid, rx, 20, 1000),
- ssh_connection_handler:renegotiate(ConnectionRef),
- spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
-
- timer:sleep(2000),
-
- Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- false = (Kex2 == Kex1),
-
- ssh_relay:stop(RelayPid),
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
-%%--------------------------------------------------------------------
-
-%%% Test rekeying with inflight messages from peer
-
-renegotiate2(Config) ->
- UserDir = proplists:get_value(priv_dir, Config),
- DataFile = filename:join(UserDir, "renegotiate2.data"),
-
- Algs = proplists:get_value(preferred_algorithms, Config),
- {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
- {preferred_algorithms,Algs}]),
-
- {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
-
- ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
- {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
-
- Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
-
- ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
-
- ssh_relay:hold(RelayPid, rx, 20, infinity),
- spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
- %% need a small pause here to ensure ssh_sftp:write is executed
- ct:sleep(10),
- ssh_connection_handler:renegotiate(ConnectionRef),
- ssh_relay:release(RelayPid, rx),
-
- timer:sleep(2000),
-
- Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
-
- false = (Kex2 == Kex1),
-
- ssh_relay:stop(RelayPid),
- ssh_sftp:stop_channel(SftpPid),
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
-
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
@@ -1805,71 +1471,19 @@ basic_test(Config) ->
ok = ssh:close(CM),
ssh:stop_daemon(Pid).
-do_shell(IO, Shell) ->
- receive
- ErlPrompt0 ->
- ct:log("Erlang prompt: ~p~n", [ErlPrompt0])
- end,
- IO ! {input, self(), "1+1.\r\n"},
- receive
- Echo0 ->
- ct:log("Echo: ~p ~n", [Echo0])
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- receive
- ?NEWLINE ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- receive
- Result0 = <<"2">> ->
- ct:log("Result: ~p~n", [Result0])
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- receive
- ?NEWLINE ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- receive
- ErlPrompt1 ->
- ct:log("Erlang prompt: ~p~n", [ErlPrompt1])
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- exit(Shell, kill).
- %%Does not seem to work in the testserver!
- %% IO ! {input, self(), "q().\r\n"},
- %% receive
- %% ?NEWLINE ->
- %% ok
- %% end,
- %% receive
- %% Echo1 ->
- %% ct:log("Echo: ~p ~n", [Echo1])
- %% end,
- %% receive
- %% ?NEWLINE ->
- %% ok
- %% end,
- %% receive
- %% Result1 ->
- %% ct:log("Result: ~p~n", [Result1])
- %% end,
- %% receive
- %% {'EXIT', Shell, killed} ->
- %% ok
- %% end.
-
+do_shell(IO, _Shell) ->
+ new_do_shell(IO, [new_prompt,
+ {type,"1+1."},
+ {expect,"2"},
+ new_prompt,
+ {type,"exit()."}
+ ]).
%%--------------------------------------------------------------------
wait_for_erlang_first_line(Config) ->
receive
- {'EXIT', _, _} ->
+ {'EXIT', _, _} = Exit ->
+ ct:log("~p:~p ~p", [?MODULE,?LINE,Exit]),
{fail,no_ssh_connection};
<<"Eshell ",_/binary>> = _ErlShellStart ->
ct:log("Erlang shell start: ~p~n", [_ErlShellStart]),
@@ -1959,14 +1573,8 @@ new_do_shell_prompt(IO, N, type, Str, More) ->
ct:log("Matched prompt ~p to trigger sending of next line to server",[N]),
IO ! {input, self(), Str++"\r\n"},
ct:log("Promt '~p> ', Sent ~ts",[N,Str++"\r\n"]),
- new_do_shell(IO, N, [{expect_echo,Str}|More]); % expect echo of the sent line
+ new_do_shell(IO, N, More);
new_do_shell_prompt(IO, N, Op, Str, More) ->
ct:log("Matched prompt ~p",[N]),
new_do_shell(IO, N, [{Op,Str}|More]).
-%%--------------------------------------------------------------------
-inet_port() ->
- {ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]),
- {ok, Port} = inet:port(Socket),
- gen_tcp:close(Socket),
- Port.
diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_dsa b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa
index d306f8b26e..24628e071b 100644
--- a/lib/ssh/test/ssh_basic_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_rsa b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa
index 9d7e0dd5fb..2202c2ead8 100644
--- a/lib/ssh/test/ssh_basic_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_bench_SUITE.erl b/lib/ssh/test/ssh_bench_SUITE.erl
index ae797a23d2..98c1936c43 100644
--- a/lib/ssh/test/ssh_bench_SUITE.erl
+++ b/lib/ssh/test/ssh_bench_SUITE.erl
@@ -18,15 +18,29 @@
%% %CopyrightEnd%
%%
-module(ssh_bench_SUITE).
--compile(export_all).
+
+-export([
+ suite/0,
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ connect/1,
+ transfer_text/1,
+ send_wait_acc/3
+ ]).
-include_lib("common_test/include/ct_event.hrl").
-include_lib("common_test/include/ct.hrl").
--include_lib("ssh/src/ssh.hrl").
--include_lib("ssh/src/ssh_transport.hrl").
--include_lib("ssh/src/ssh_connect.hrl").
--include_lib("ssh/src/ssh_auth.hrl").
+-include("ssh.hrl").
+-include("ssh_transport.hrl").
+-include("ssh_connect.hrl").
+-include("ssh_auth.hrl").
%%%================================================================
%%%
@@ -233,7 +247,7 @@ send_wait_acc(C, Ch, Data) ->
%%%----------------------------------------------------------------
preferred_algorithms(KexAlg) ->
[{kex, [KexAlg]},
- {public_key, ['ssh-rsa']},
+ {public_key, ['rsa-sha2-256']},
{cipher, ['aes128-ctr']},
{mac, ['hmac-sha1']},
{compression, [none]}
@@ -252,13 +266,13 @@ median(Data) when is_list(Data) ->
1 ->
lists:nth(N div 2 + 1, SortedData)
end,
- ct:pal("median(~p) = ~p",[SortedData,Median]),
+ ct:log("median(~p) = ~p",[SortedData,Median]),
Median.
%%%----------------------------------------------------------------
report(LabelList, Value) ->
Label = report_chars(lists:concat(LabelList)),
- ct:pal("ct_event:notify ~p: ~p", [Label, Value]),
+ ct:log("ct_event:notify ~p: ~p", [Label, Value]),
ct_event:notify(
#event{name = benchmark_data,
data = [{suite, ?MODULE},
diff --git a/lib/ssh/test/ssh_chan_behaviours_SUITE.erl b/lib/ssh/test/ssh_chan_behaviours_SUITE.erl
index 29cc26d780..8d6ad13ef5 100644
--- a/lib/ssh/test/ssh_chan_behaviours_SUITE.erl
+++ b/lib/ssh/test/ssh_chan_behaviours_SUITE.erl
@@ -23,11 +23,24 @@
-module(ssh_chan_behaviours_SUITE).
-include_lib("common_test/include/ct.hrl").
--include_lib("ssh/src/ssh.hrl").
+-include("ssh.hrl").
-include("ssh_test_lib.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ defined_subsystem/1,
+ noexist_subsystem/1,
+ subsystem_client/1,
+ undefined_subsystem/1
+ ]).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -72,7 +85,7 @@ end_per_testcase(_TC, Config) ->
ct:log("Stop daemon: ~p ms",[(100*(Time div 1000)) / 100]),
case flush() of
[] -> ok;
- Msgs -> ct:pal("Unhandled messages:~n~p", [Msgs])
+ Msgs -> ct:log("Unhandled messages:~n~p", [Msgs])
end.
diff --git a/lib/ssh/test/ssh_compat_SUITE.erl b/lib/ssh/test/ssh_compat_SUITE.erl
index 1b62bcb6c6..6df42660ed 100644
--- a/lib/ssh/test/ssh_compat_SUITE.erl
+++ b/lib/ssh/test/ssh_compat_SUITE.erl
@@ -23,13 +23,29 @@
-module(ssh_compat_SUITE).
-include_lib("common_test/include/ct.hrl").
--include_lib("ssh/src/ssh_transport.hrl"). % #ssh_msg_kexinit{}
+-include("ssh_transport.hrl"). % #ssh_msg_kexinit{}
-include_lib("kernel/include/inet.hrl"). % #hostent{}
-include_lib("kernel/include/file.hrl"). % #file_info{}
-include("ssh_test_lib.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2
+ ]).
+
+-export([
+ all_algorithms_sftp_exec_reneg_otp_is_client/1,
+ check_docker_present/1,
+ login_otp_is_client/1,
+ login_otp_is_server/1,
+ renegotiation_otp_is_server/1,
+ send_recv_big_with_renegotiate_otp_is_client/1
+ ]).
-define(USER,"sshtester").
-define(PASSWD, "foobar").
@@ -41,7 +57,7 @@
%%--------------------------------------------------------------------
suite() ->
- [{timetrap,{seconds,60}}].
+ [{timetrap,{seconds,90}}].
all() ->
%% [check_docker_present] ++
@@ -53,7 +69,7 @@ groups() ->
send_recv_big_with_renegotiate_otp_is_client
]},
{otp_server, [], [login_otp_is_server,
- all_algorithms_sftp_exec_reneg_otp_is_server
+ renegotiation_otp_is_server
]} |
[{G, [], [{group,otp_client}, {group,otp_server}]} || G <- ssh_image_versions()]
].
@@ -196,17 +212,19 @@ login_otp_is_client(Config) ->
[' ']
end
],
-
+
chk_all_algos(?FUNCTION_NAME, CommonAuths, Config,
fun(AuthMethod,Alg) ->
{Opts,Dir} =
case AuthMethod of
publickey ->
- {[], setup_remote_auth_keys_and_local_priv(Alg, Config)};
+ {[{pref_public_key_algs, [Alg]}],
+ setup_remote_auth_keys_and_local_priv(Alg, Config)};
_ ->
{[{password,?PASSWD}], new_dir(Config)}
end,
ssh:connect(IP, Port, [{auth_methods, atom_to_list(AuthMethod)},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
{user,?USER},
{user_dir, Dir},
{silently_accept_hosts,true},
@@ -234,7 +252,9 @@ login_otp_is_server(Config) ->
{Opts,UsrDir} =
case AuthMethod of
publickey ->
- {[{user_passwords, [{?USER,?BAD_PASSWD}]}],
+ {[{user_passwords, [{?USER,?BAD_PASSWD}]},
+ {pref_public_key_algs, [Alg]}
+ ],
setup_remote_priv_and_local_auth_keys(Alg, Config)
};
_ ->
@@ -245,6 +265,7 @@ login_otp_is_server(Config) ->
{Server, Host, HostPort} =
ssh_test_lib:daemon(0,
[{auth_methods, atom_to_list(AuthMethod)},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
{system_dir, SysDir},
{user_dir, UsrDir},
{failfun, fun ssh_test_lib:failfun/2}
@@ -264,13 +285,16 @@ all_algorithms_sftp_exec_reneg_otp_is_client(Config) ->
{IP,Port} = ip_port(Config),
chk_all_algos(?FUNCTION_NAME, CommonAlgs, Config,
fun(Tag, Alg) ->
+ PrefAlgs =
+ [{T,L} || {T,L} <- ssh_transport:supported_algorithms(),
+ T =/= Tag],
ConnRes =
ssh:connect(IP, Port,
[{user,?USER},
{password,?PASSWD},
{auth_methods, "password"},
{user_dir, new_dir(Config)},
- {preferred_algorithms, [{Tag,[Alg]}]},
+ {preferred_algorithms, [{Tag,[Alg]} | PrefAlgs]},
{silently_accept_hosts,true},
{user_interaction,false}
]) ,
@@ -284,43 +308,57 @@ all_algorithms_sftp_exec_reneg_otp_is_client(Config) ->
end).
%%--------------------------------------------------------------------
-all_algorithms_sftp_exec_reneg_otp_is_server(Config) ->
- CommonAlgs = proplists:get_value(common_remote_client_algs, Config),
- UserDir = setup_remote_priv_and_local_auth_keys('ssh-rsa', Config),
- chk_all_algos(?FUNCTION_NAME, CommonAlgs, Config,
- fun(Tag,Alg) ->
- HostKeyAlg = case Tag of
- public_key -> Alg;
- _ -> 'ssh-rsa'
- end,
- SftpRootDir = new_dir(Config),
- %% ct:log("Rootdir = ~p",[SftpRootDir]),
- {Server, Host, HostPort} =
- ssh_test_lib:daemon(0,
- [{preferred_algorithms, [{Tag,[Alg]}]},
- {system_dir, setup_local_hostdir(HostKeyAlg, Config)},
- {user_dir, UserDir},
- {user_passwords, [{?USER,?PASSWD}]},
- {failfun, fun ssh_test_lib:failfun/2},
- {subsystems,
- [ssh_sftpd:subsystem_spec([{cwd,SftpRootDir},
- {root,SftpRootDir}]),
- {"echo_10",{ssh_echo_server,[10,[{dbg,true}]]}}
- ]}
- ]),
- R = do([fun() ->
- exec_from_docker(Config, Host, HostPort,
- "hi_there.\r\n",
- [<<"hi_there">>],
- "")
- end,
- fun() ->
- sftp_tests_erl_server(Config, Host, HostPort, SftpRootDir, UserDir)
- end
- ]),
- ssh:stop_daemon(Server),
- R
- end).
+renegotiation_otp_is_server(Config) ->
+ PublicKeyAlgs = [A || {public_key,A} <- proplists:get_value(common_remote_client_algs, Config, [])],
+ UserDir = setup_remote_priv_and_local_auth_keys(hd(PublicKeyAlgs), Config),
+ SftpRootDir = new_dir(Config),
+ ct:log("Rootdir = ~p",[SftpRootDir]),
+ Parent = self(),
+ Ref = make_ref(),
+ {Server, Host, Port} =
+ ssh_test_lib:daemon(0,
+ [{system_dir, setup_local_hostdir(Config)},
+ {user_dir, UserDir},
+ {user_passwords, [{?USER,?PASSWD}]},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {connectfun,
+ fun(_,_,_) ->
+ HostConnRef = self(),
+ reneg_tester(Parent, Ref, HostConnRef),
+ ct:log("Connected ~p",[HostConnRef]),
+ timer:sleep(1100), % Just a bit more than 1 s
+ ok
+ end},
+ {subsystems,
+ [ssh_sftpd:subsystem_spec([{cwd,SftpRootDir},
+ {root,SftpRootDir}])
+ ]}
+ ]),
+ case sftp_tests_erl_server(Config, Host, Port, SftpRootDir, UserDir, Ref) of
+ ok ->
+ ssh:stop_daemon(Server);
+ {error,Error} ->
+ ssh:stop_daemon(Server),
+ ct:log("Error: ~p", [Error]),
+ {fail, Error}
+ end.
+
+
+reneg_tester(Parent, Ref, HostConnRef) ->
+ spawn(fun() ->
+ reneg_tester_loop(Parent, Ref, HostConnRef, renegotiate_test(init,HostConnRef))
+ end).
+
+reneg_tester_loop(Parent, Ref, HostConnRef, Kex1) ->
+ case ssh_test_lib:get_kex_init(HostConnRef) of
+ Kex1 ->
+ timer:sleep(500),
+ reneg_tester_loop(Parent, Ref, HostConnRef, Kex1);
+ _OtherKex ->
+ ct:log("Kex is changed.", []),
+ Parent ! {kex_changed, Ref}
+ end.
+
%%--------------------------------------------------------------------
send_recv_big_with_renegotiate_otp_is_client(Config) ->
@@ -330,6 +368,7 @@ send_recv_big_with_renegotiate_otp_is_client(Config) ->
{password,?PASSWD},
{user_dir, setup_remote_auth_keys_and_local_priv('ssh-rsa', Config)},
{silently_accept_hosts,true},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
{user_interaction,false}
]),
@@ -438,6 +477,7 @@ exec_from_docker(Config, HostIP, HostPort, Command, Expects, ExtraSshArg) when i
[{user,?USER},
{password,?PASSWD},
{user_dir, new_dir(Config)},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
{silently_accept_hosts,true},
{user_interaction,false}
]),
@@ -521,21 +561,22 @@ result_of_exec(C, Ch, ExitStatus, Acc) ->
chk_all_algos(FunctionName, CommonAlgs, Config, DoTestFun) when is_function(DoTestFun,2) ->
ct:comment("~p algorithms",[length(CommonAlgs)]),
%% Check each algorithm
- Failed =
+ Nmax = length(CommonAlgs),
+ {_N,Failed} =
lists:foldl(
- fun({Tag,Alg}, FailedAlgos) ->
- %% ct:log("Try ~p",[Alg]),
+ fun({Tag,Alg}, {N,FailedAlgos}) ->
+ ct:log("Try ~p ~p/~p",[{Tag,Alg},N,Nmax]),
case DoTestFun(Tag,Alg) of
{ok,C} ->
ssh:close(C),
- FailedAlgos;
+ {N+1,FailedAlgos};
ok ->
- FailedAlgos;
+ {N+1,FailedAlgos};
Other ->
ct:log("FAILED! ~p ~p: ~p",[Tag,Alg,Other]),
- [{Alg,Other}|FailedAlgos]
+ {N+1, [{Alg,Other}|FailedAlgos]}
end
- end, [], CommonAlgs),
+ end, {1,[]}, CommonAlgs),
ct:pal("~s", [format_result_table_use_all_algos(FunctionName, Config, CommonAlgs, Failed)]),
case Failed of
[] ->
@@ -546,39 +587,27 @@ chk_all_algos(FunctionName, CommonAlgs, Config, DoTestFun) when is_function(DoTe
-%%%----------------------------------------------------------------
-%%%
-%%% Call all Funs as Fun() which returns 'ok', {ok,C} or Other.
-%%% do/1 returns 'ok' or the first encountered value that is not
-%%% successful.
-%%%
-
-do(Funs) ->
- do(Funs, 1).
-
-do([Fun|Funs], N) ->
- case Fun() of
- ok ->
- %% ct:log("Fun ~p ok",[N]),
- do(Funs, N-1);
- {ok,C} ->
- %% ct:log("Fun ~p {ok,C}",[N]),
- ssh:close(C),
- do(Funs, N-1);
- Other ->
- ct:log("Fun ~p FAILED:~n~p",[N, Other]),
- Other
- end;
-
-do([], _) ->
- %% ct:log("All Funs ok",[]),
- ok.
-
%%--------------------------------------------------------------------
%%
%% Functions to set up local and remote host's and user's keys and directories
%%
+setup_local_hostdir(Config) ->
+ KeyAlgs = lists:usort(
+ [A || From <- [common_remote_client_algs,
+ common_remote_server_algs],
+ {public_key,A} <- proplists:get_value(From, Config, [])]),
+ setup_local_hostdirs(KeyAlgs, new_dir(Config), Config).
+
+
+setup_local_hostdirs(KeyAlgs, HostDir, Config) ->
+ lists:foreach(
+ fun(KeyAlg) ->
+ setup_local_hostdir(KeyAlg, HostDir, Config)
+ end, KeyAlgs),
+ HostDir.
+
+
setup_local_hostdir(KeyAlg, Config) ->
setup_local_hostdir(KeyAlg, new_dir(Config), Config).
setup_local_hostdir(KeyAlg, HostDir, Config) ->
@@ -594,13 +623,6 @@ setup_remote_auth_keys_and_local_priv(KeyAlg, Config) ->
{IP,Port} = ip_port(Config),
setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, new_dir(Config), Config).
-setup_remote_auth_keys_and_local_priv(KeyAlg, UserDir, Config) ->
- {IP,Port} = ip_port(Config),
- setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, UserDir, Config).
-
-setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, Config) ->
- setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, new_dir(Config), Config).
-
setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, UserDir, Config) ->
{ok, {Priv,Publ}} = user_priv_pub_keys(Config, KeyAlg),
%% Local private and public keys
@@ -612,6 +634,7 @@ setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, UserDir, Config) ->
{password, ?PASSWD },
{auth_methods, "password"},
{silently_accept_hosts,true},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
{user_interaction,false}
]),
_ = ssh_sftp:make_dir(Ch, ".ssh"),
@@ -627,13 +650,6 @@ setup_remote_priv_and_local_auth_keys(KeyAlg, Config) ->
{IP,Port} = ip_port(Config),
setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, new_dir(Config), Config).
-setup_remote_priv_and_local_auth_keys(KeyAlg, UserDir, Config) ->
- {IP,Port} = ip_port(Config),
- setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, UserDir, Config).
-
-setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, Config) ->
- setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, new_dir(Config), Config).
-
setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, UserDir, Config) ->
{ok, {Priv,Publ}} = user_priv_pub_keys(Config, KeyAlg),
%% Local auth_methods with public key
@@ -643,6 +659,7 @@ setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, UserDir, Config) ->
{ok,Ch,Cc} = ssh_sftp:start_channel(IP, Port, [{user, ?USER },
{password, ?PASSWD },
{auth_methods, "password"},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
{silently_accept_hosts,true},
{user_interaction,false}
]),
@@ -789,11 +806,6 @@ ip_port(Config) ->
{_Ver,{IP,Port},_} = proplists:get_value(id,Config),
{IP,Port}.
-port_mapped_to(Id) ->
- Cmnd = lists:concat(["docker ps --format \"{{.Ports}}\" --filter id=",Id]),
- [_, PortStr | _] = string:tokens(os:cmd(Cmnd), ":->/"),
- list_to_integer(PortStr).
-
ip(Id) ->
Cmnd = lists:concat(["docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ",
Id]),
@@ -833,29 +845,6 @@ new_dir(Config) ->
new_dir(Config)
end.
-clear_dir(Dir) ->
- delete_all_contents(Dir),
- {ok,[]} = file:list_dir(Dir),
- Dir.
-
-delete_all_contents(Dir) ->
- {ok,Fs} = file:list_dir(Dir),
- lists:map(fun(F0) ->
- F = filename:join(Dir, F0),
- case filelib:is_file(F) of
- true ->
- file:delete(F);
- false ->
- case filelib:is_dir(F) of
- true ->
- delete_all_contents(F),
- file:del_dir(F);
- false ->
- ct:log("Neither file nor dir: ~p",[F])
- end
- end
- end, Fs).
-
%%--------------------------------------------------------------------
%%
%% Find the intersection of algoritms for otp ssh and the docker ssh.
@@ -920,14 +909,25 @@ find_common_algs(Remote, Local) ->
use_algorithms(RemoteHelloBin) ->
MyAlgos = ssh:chk_algos_opts(
[{modify_algorithms,
- [{append,
- [{kex,['diffie-hellman-group1-sha1']}
- ]}
+ [{append, alg_diff()}
]}
]),
ssh_transport:adjust_algs_for_peer_version(binary_to_list(RemoteHelloBin)++"\r\n",
MyAlgos).
+
+alg_diff() ->
+ alg_diff(ssh:default_algorithms(), ssh_transport:supported_algorithms()).
+
+alg_diff(L1, L2) when is_atom(hd(L1)) ; is_atom(hd(L2)) ->
+ (L2--L1)--['AEAD_AES_256_GCM','AEAD_AES_128_GCM'];
+alg_diff(L1, L2) ->
+ [{T, Diff} || {{T,EL1},{T,EL2}} <- lists:zip(L1,L2),
+ Diff <- [alg_diff(EL1,EL2)],
+ Diff =/= []
+ ].
+
+
kexint_msg2default_algorithms(#ssh_msg_kexinit{kex_algorithms = Kex,
server_host_key_algorithms = PubKey,
encryption_algorithms_client_to_server = CipherC2S,
@@ -1095,14 +1095,18 @@ receive_kexinit(S, Ack) ->
%%% Test of sftp from the OpenSSH client side
%%%
-sftp_tests_erl_server(Config, ServerIP, ServerPort, ServerRootDir, UserDir) ->
+sftp_tests_erl_server(Config, ServerIP, ServerPort, ServerRootDir, UserDir, Ref) ->
try
Cmnds = prepare_local_directory(ServerRootDir),
- call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir),
- check_local_directory(ServerRootDir)
+ case call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir, Ref) of
+ ok ->
+ check_local_directory(ServerRootDir);
+ {error,Error} ->
+ {error,Error}
+ end
catch
- Class:Error:ST ->
- {error, {Class,Error,ST}}
+ Class:Excep:ST ->
+ {error, {Class,Excep,ST}}
end.
@@ -1175,12 +1179,14 @@ do_check_local_directory(ServerRootDir) ->
end.
-call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir) ->
+call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir, Ref) ->
{DockerIP,DockerPort} = ip_port(Config),
+ ct:log("Going to connect ~p:~p", [DockerIP, DockerPort]),
{ok,C} = ssh:connect(DockerIP, DockerPort,
[{user,?USER},
{password,?PASSWD},
{user_dir, UserDir},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
{silently_accept_hosts,true},
{user_interaction,false}
]),
@@ -1193,7 +1199,7 @@ call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir) ->
PostExpectCmnds= [],
ExpectCmnds =
PreExpectCmnds ++
- ["expect \"sftp>\" {send \""++Cmnd++"\n\"}\n" || Cmnd <- Cmnds] ++
+ ["expect \"sftp>\" {sleep 1; send \""++Cmnd++"\n\"}\n" || Cmnd <- Cmnds] ++
PostExpectCmnds,
%% Make an commands file in the docker
@@ -1203,14 +1209,30 @@ call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir) ->
%% Call expect in the docker
{ok, Ch1} = ssh_connection:session_channel(C, infinity),
- Kex1 = renegotiate_test(init, C),
+ ct:log("Going to execute 'expect commands' in docker", []),
success = ssh_connection:exec(C, Ch1, "expect commands", infinity),
+ ct:log("'expect commands' in docker executed!", []),
+ case recv_log_msgs(C, Ch1) of
+ ok ->
+ receive
+ {kex_changed, Ref} ->
+ %% success
+ ct:log("Kex changed",[]),
+ ssh:close(C),
+ ok
+ after
+ 30000 ->
+ %% failure
+ ct:log("Kex NOT changed",[]),
+ ssh:close(C),
+ {error,"Kex NOT changed"}
+ end;
- renegotiate_test(Kex1, C),
- recv_log_msgs(C, Ch1),
+ {error,Error} ->
+ ssh:close(C),
+ {error,Error}
+ end.
- %% Done.
- ssh:close(C).
recv_log_msgs(C, Ch) ->
receive
@@ -1223,6 +1245,12 @@ recv_log_msgs(C, Ch) ->
{ssh_cm,C,_Msg} ->
%% ct:log("Got ~p",[_Msg]),
recv_log_msgs(C, Ch)
+ after
+ 30000 ->
+ %% failure
+ ct:log("Exec Channel ~p in ~p NOT closed properly", [Ch,C]),
+ ssh:close(C),
+ {error,"Exec channel NOT closed"}
end.
%%%----------------------------------------------------------------
@@ -1287,7 +1315,7 @@ one_test_erl_client(exec, Id, C) ->
{eof,<<"Hi there\n">>} ->
ok;
Other ->
- ct:pal("exec Got other ~p", [Other]),
+ ct:log("exec Got other ~p", [Other]),
{error, {exec,Id,bad_msg,Other,undefined}}
end;
@@ -1297,7 +1325,7 @@ one_test_erl_client(no_subsyst, Id, C) ->
failure ->
ok;
Other ->
- ct:pal("no_subsyst Got other ~p", [Other]),
+ ct:log("no_subsyst Got other ~p", [Other]),
{error, {no_subsyst,Id,bad_ret,Other,undefined}}
end;
@@ -1325,7 +1353,7 @@ one_test_erl_client(setenv, Id, C) ->
{eof,Env} ->
ok;
Other ->
- ct:pal("setenv Got other ~p", [Other]),
+ ct:log("setenv Got other ~p", [Other]),
{error, {setenv,Id,bad_msg,Other,undefined}}
end;
diff --git a/lib/ssh/test/ssh_compat_SUITE_data/build_scripts/create_all b/lib/ssh/test/ssh_compat_SUITE_data/build_scripts/create_all
index c2e77fcc79..729fa5e140 100755
--- a/lib/ssh/test/ssh_compat_SUITE_data/build_scripts/create_all
+++ b/lib/ssh/test/ssh_compat_SUITE_data/build_scripts/create_all
@@ -24,6 +24,8 @@ SSH_SSL_VERSIONS=(\
openssh 7.9p1 openssl 1.0.2p \
\
openssh 7.9p1 libressl 2.6.4 \
+ \
+ openssh 8.2p1 openssl 1.0.2p \
)
if [ "x$1" == "x-b" ]
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index d7d8bb0352..49168d38bb 100644
--- a/lib/ssh/test/ssh_connection_SUITE.erl
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -22,10 +22,74 @@
-module(ssh_connection_SUITE).
-include_lib("common_test/include/ct.hrl").
--include_lib("ssh/src/ssh_connect.hrl").
+-include("ssh_connect.hrl").
-include("ssh_test_lib.hrl").
--compile(export_all).
+
+
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ big_cat/1,
+ connect_sock_not_passive/1,
+ connect_sock_not_tcp/1,
+ daemon_sock_not_passive/1,
+ daemon_sock_not_tcp/1,
+ do_interrupted_send/3,
+ do_simple_exec/1,
+ encode_decode_pty_opts/1,
+ exec_disabled/1,
+ exec_erlang_term/1,
+ exec_erlang_term_non_default_shell/1,
+ exec_shell_disabled/1,
+ gracefull_invalid_long_start/1,
+ gracefull_invalid_long_start_no_nl/1,
+ gracefull_invalid_start/1,
+ gracefull_invalid_version/1,
+ interrupted_send/1,
+ max_channels_option/1,
+ ptty_alloc/1,
+ ptty_alloc_default/1,
+ ptty_alloc_pixel/1,
+ read_write_loop/1,
+ read_write_loop1/2,
+ send_after_exit/1,
+ simple_eval/1,
+ simple_exec/1,
+ simple_exec_sock/1,
+ simple_exec_two_socks/1,
+ small_cat/1,
+ small_interrupted_send/1,
+ start_exec_direct_fun1_read_write/1,
+ start_exec_direct_fun1_read_write_advanced/1,
+ start_shell/1,
+ start_shell_pty/1,
+ start_shell_exec/1,
+ start_shell_exec_direct_fun/1,
+ start_shell_exec_direct_fun1_error/1,
+ start_shell_exec_direct_fun1_error_type/1,
+ start_shell_exec_direct_fun2/1,
+ start_shell_exec_direct_fun3/1,
+ start_shell_exec_fun/1,
+ start_shell_exec_fun2/1,
+ start_shell_exec_fun3/1,
+ start_shell_sock_daemon_exec/1,
+ start_shell_sock_daemon_exec_multi/1,
+ start_shell_sock_exec_fun/1,
+ start_subsystem_on_closed_channel/1,
+ stop_listener/1,
+ ssh_exec_echo/2 % called as an MFA
+ ]).
-define(SSH_DEFAULT_PORT, 22).
-define(EXEC_TIMEOUT, 10000).
@@ -50,6 +114,7 @@ all() ->
exec_disabled,
exec_shell_disabled,
start_shell,
+ start_shell_pty,
start_shell_exec,
start_shell_exec_fun,
start_shell_exec_fun2,
@@ -63,6 +128,8 @@ all() ->
start_exec_direct_fun1_read_write_advanced,
start_shell_sock_exec_fun,
start_shell_sock_daemon_exec,
+ start_shell_sock_daemon_exec_multi,
+ encode_decode_pty_opts,
connect_sock_not_tcp,
daemon_sock_not_tcp,
gracefull_invalid_version,
@@ -79,6 +146,7 @@ groups() ->
payload() ->
[simple_exec,
simple_exec_sock,
+ simple_exec_two_socks,
small_cat,
big_cat,
send_after_exit].
@@ -120,66 +188,50 @@ end_per_group(_, Config) ->
Config.
%%--------------------------------------------------------------------
-init_per_testcase(TestCase, Config) ->
+init_per_testcase(_TestCase, Config) ->
%% To make sure we start clean as it is not certain that
%% end_per_testcase will be run!
- end_per_testcase(Config),
+ end_per_testcase(any, Config),
ssh:start(),
Config.
-end_per_testcase(_Config) ->
+end_per_testcase(_TestCase, _Config) ->
ssh:stop().
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-simple_exec() ->
- [{doc, "Simple openssh connectivity test for ssh_connection:exec"}].
-
simple_exec(Config) when is_list(Config) ->
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false}]),
do_simple_exec(ConnectionRef).
-
+%%--------------------------------------------------------------------
simple_exec_sock(_Config) ->
{ok, Sock} = ssh_test_lib:gen_tcp_connect("localhost", ?SSH_DEFAULT_PORT, [{active,false}]),
{ok, ConnectionRef} = ssh:connect(Sock, [{silently_accept_hosts, true},
{user_interaction, false}]),
do_simple_exec(ConnectionRef).
-
-
-
-do_simple_exec(ConnectionRef) ->
- {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
- success = ssh_connection:exec(ConnectionRef, ChannelId0,
- "echo testing", infinity),
- %% receive response to input
- receive
- {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}} ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- %% receive close messages
- receive
- {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
+%%--------------------------------------------------------------------
+simple_exec_two_socks(_Config) ->
+ Parent = self(),
+ F = fun() ->
+ spawn_link(
+ fun() ->
+ {ok, Sock} = ssh_test_lib:gen_tcp_connect("localhost", ?SSH_DEFAULT_PORT, [{active,false}]),
+ {ok, ConnectionRef} = ssh:connect(Sock, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ Parent ! {self(),do_simple_exec(ConnectionRef)}
+ end)
+ end,
+ Pid1 = F(),
+ Pid2 = F(),
receive
- {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ {Pid1,ok} -> ok
end,
receive
- {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ {Pid2,ok} -> ok
end.
%%--------------------------------------------------------------------
@@ -207,9 +259,6 @@ daemon_sock_not_passive(_Config) ->
gen_tcp:close(Sock).
%%--------------------------------------------------------------------
-small_cat() ->
- [{doc, "Use 'cat' to echo small data block back to us."}].
-
small_cat(Config) when is_list(Config) ->
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false}]),
@@ -249,9 +298,6 @@ small_cat(Config) when is_list(Config) ->
10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
%%--------------------------------------------------------------------
-big_cat() ->
- [{doc,"Use 'cat' to echo large data block back to us."}].
-
big_cat(Config) when is_list(Config) ->
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false}]),
@@ -302,9 +348,6 @@ big_cat(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-send_after_exit() ->
- [{doc, "Send channel data after the channel has been closed."}].
-
send_after_exit(Config) when is_list(Config) ->
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false}]),
@@ -343,9 +386,33 @@ send_after_exit(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-ptty_alloc_default() ->
- [{doc, "Test sending PTTY alloc message with only defaults."}].
+encode_decode_pty_opts(_Config) ->
+ Tags =
+ [vintr, vquit, verase, vkill, veof, veol, veol2, vstart, vstop, vsusp, vdsusp,
+ vreprint, vwerase, vlnext, vflush, vswtch, vstatus, vdiscard, ignpar, parmrk,
+ inpck, istrip, inlcr, igncr, icrnl, iuclc, ixon, ixany, ixoff, imaxbel, isig,
+ icanon, xcase, echo, echoe, echok, echonl, noflsh, tostop, iexten, echoctl,
+ echoke, pendin, opost, olcuc, onlcr, ocrnl, onocr, onlret, cs7, cs8, parenb,
+ parodd, tty_op_ispeed, tty_op_ospeed],
+ Opts =
+ lists:zip(Tags,
+ lists:seq(1, length(Tags))),
+
+ case ssh_connection:encode_pty_opts(Opts) of
+ Bin when is_binary(Bin) ->
+ case ssh_connection:decode_pty_opts(Bin) of
+ Opts ->
+ ok;
+ Other ->
+ ct:log("Expected ~p~nGot ~p~nBin = ~p",[Opts,Other,Bin]),
+ ct:fail("Not the same",[])
+ end;
+ Other ->
+ ct:log("encode -> ~p",[Other]),
+ ct:fail("Encode failed",[])
+ end.
+%%--------------------------------------------------------------------
ptty_alloc_default(Config) when is_list(Config) ->
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false}]),
@@ -358,9 +425,6 @@ ptty_alloc_default(Config) when is_list(Config) ->
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}]),
@@ -375,9 +439,6 @@ ptty_alloc(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
-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}]),
@@ -505,9 +566,6 @@ do_interrupted_send(Config, SendSize, EchoSize) ->
end.
%%--------------------------------------------------------------------
-start_shell() ->
- [{doc, "Start a shell"}].
-
start_shell(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
@@ -523,16 +581,34 @@ start_shell(Config) when is_list(Config) ->
{password, "morot"},
{user_interaction, true},
{user_dir, UserDir}]),
- test_shell_is_enabled(ConnectionRef, <<"Enter command\r\n">>),
+ test_shell_is_enabled(ConnectionRef, <<"Enter command">>), % No pty alloc by erl client
test_exec_is_disabled(ConnectionRef),
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid).
+%%-------------------------------------------------------------------
+start_shell_pty(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = proplists:get_value(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} ]),
-%%--------------------------------------------------------------------
-start_shell_exec() ->
- [{doc, "start shell to exec command"}].
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, true},
+ {user_dir, UserDir}]),
+ test_shell_is_enabled(ConnectionRef, <<"Enter command\r\n">>, [{pty_opts,[{onlcr,1}]}]), % alloc pty
+ test_exec_is_disabled(ConnectionRef),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
start_shell_exec(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
@@ -640,42 +716,54 @@ exec_shell_disabled(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
start_shell_exec_fun(Config) ->
- do_start_shell_exec_fun(fun ssh_exec_echo/1,
- "testing", <<"echo testing\r\n">>, 0,
+ do_start_shell_exec_fun(fun(Cmd) ->
+ spawn(fun() ->
+ io:format("echo ~s\n", [Cmd])
+ end)
+ end,
+ "testing", <<"echo testing\n">>, 0,
Config).
start_shell_exec_fun2(Config) ->
- do_start_shell_exec_fun(fun ssh_exec_echo/2,
- "testing", <<"echo foo testing\r\n">>, 0,
+ do_start_shell_exec_fun(fun(Cmd, User) ->
+ spawn(fun() ->
+ io:format("echo ~s ~s\n",[User,Cmd])
+ end)
+ end,
+ "testing", <<"echo foo testing\n">>, 0,
Config).
start_shell_exec_fun3(Config) ->
- do_start_shell_exec_fun(fun ssh_exec_echo/3,
- "testing", <<"echo foo testing\r\n">>, 0,
+ do_start_shell_exec_fun(fun(Cmd, User, _PeerAddr) ->
+ spawn(fun() ->
+ io:format("echo ~s ~s\n",[User,Cmd])
+ end)
+ end,
+ "testing", <<"echo foo testing\n">>, 0,
Config).
start_shell_exec_direct_fun(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo/1},
+ do_start_shell_exec_fun({direct, fun(Cmd) -> {ok, io_lib:format("echo ~s~n",[Cmd])} end},
"testing", <<"echo testing\n">>, 0,
Config).
start_shell_exec_direct_fun2(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo/2},
+ do_start_shell_exec_fun({direct, fun(Cmd,User) -> {ok, io_lib:format("echo ~s ~s",[User,Cmd])} end},
"testing", <<"echo foo testing">>, 0,
Config).
start_shell_exec_direct_fun3(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo/3},
+ do_start_shell_exec_fun({direct, fun(Cmd,User,_PeerAddr) -> {ok, io_lib:format("echo ~s ~s",[User,Cmd])} end},
"testing", <<"echo foo testing">>, 0,
Config).
start_shell_exec_direct_fun1_error(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo_error_return/1},
+ do_start_shell_exec_fun({direct, fun(_Cmd) -> {error, {bad}} end},
"testing", <<"**Error** {bad}">>, 1,
Config).
start_shell_exec_direct_fun1_error_type(Config) ->
- do_start_shell_exec_fun({direct, fun ssh_exec_direct_echo_error_return_type/1},
+ do_start_shell_exec_fun({direct, fun(_Cmd) -> very_bad end},
"testing", <<"**Error** Bad exec fun in server. Invalid return value: very_bad">>, 1,
Config).
@@ -698,11 +786,11 @@ start_exec_direct_fun1_read_write(Config) ->
{ok, Ch} = ssh_connection:session_channel(C, infinity),
success = ssh_connection:exec(C, Ch, "> ", infinity),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"1> ">>}}),
ok = ssh_connection:send(C, Ch, "hej.\n", 5000),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"2> ">>}}),
ok = ssh_connection:send(C, Ch, "quit.\n", 5000),
@@ -746,18 +834,18 @@ start_exec_direct_fun1_read_write_advanced(Config) ->
{ok, Ch} = ssh_connection:session_channel(C, infinity),
success = ssh_connection:exec(C, Ch, "> ", infinity),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"1> ">>}}),
ok = ssh_connection:send(C, Ch, "hej.\n", 5000),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"2> ">>}}),
ok = ssh_connection:send(C, Ch, "'Hi ", 5000),
ok = ssh_connection:send(C, Ch, "there", 5000),
ok = ssh_connection:send(C, Ch, "'", 5000),
ok = ssh_connection:send(C, Ch, ".\n", 5000),
- ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,'Hi there'}\r\n">>}}),
+ ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,'Hi there'}\n">>}}),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"3> ">>}}),
ok = ssh_connection:send(C, Ch, "bad_input.\n", 5000),
ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,1,<<"**Error** {bad_input,3}">>}}),
@@ -828,7 +916,8 @@ do_start_shell_exec_fun(Fun, Command, Expect, ExpectType, Config) ->
after 5000 ->
receive
Other ->
- ct:pal("Received other:~n~p",[Other]),
+ ct:log("Received other:~n~p~nExpected: ~p~n",
+ [Other, {ssh_cm, ConnectionRef, {data, '_ChannelId', ExpectType, Expect}} ]),
ct:fail("Unexpected response")
after 0 ->
ct:fail("Exec Timeout")
@@ -839,9 +928,6 @@ do_start_shell_exec_fun(Fun, Command, Expect, ExpectType, Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
-start_shell_sock_exec_fun() ->
- [{doc, "start shell on tcp-socket to exec command"}].
-
start_shell_sock_exec_fun(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
@@ -866,7 +952,7 @@ start_shell_sock_exec_fun(Config) when is_list(Config) ->
"testing", infinity),
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -882,9 +968,11 @@ start_shell_sock_daemon_exec(Config) ->
file:make_dir(UserDir),
SysDir = proplists:get_value(data_dir, Config),
+ %% Listening tcp socket at the client side
{ok,Sl} = gen_tcp:listen(0, [{active,false}]),
{ok,{_IP,Port}} = inet:sockname(Sl), % _IP is likely to be {0,0,0,0}. Win don't like...
-
+
+ %% A server tcp-contects to the listening socket and starts an ssh daemon
spawn_link(fun() ->
{ok,Ss} = ssh_test_lib:gen_tcp_connect("localhost", Port, [{active,false}]),
{ok, _Pid} = ssh:daemon(Ss, [{system_dir, SysDir},
@@ -892,27 +980,100 @@ start_shell_sock_daemon_exec(Config) ->
{password, "morot"},
{exec, fun ssh_exec_echo/1}])
end),
+
+ %% The client accepts the tcp connection from the server and ssh-connects to it
{ok,Sc} = gen_tcp:accept(Sl),
{ok,ConnectionRef} = ssh:connect(Sc, [{silently_accept_hosts, true},
{user, "foo"},
{password, "morot"},
{user_interaction, true},
{user_dir, UserDir}]),
-
+
+ %% And runs some commands
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:exec(ConnectionRef, ChannelId0,
"testing", infinity),
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
end,
ssh:close(ConnectionRef).
-
+
+%%--------------------------------------------------------------------
+start_shell_sock_daemon_exec_multi(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = proplists:get_value(data_dir, Config),
+
+ NumConcurent = 5,
+
+ %% Listening tcp socket at the client side
+ {ok,Sl} = gen_tcp:listen(0, [{active,false}]),
+ {ok,{_IP,Port}} = inet:sockname(Sl), % _IP is likely to be {0,0,0,0}. Win don't like...
+
+ DaemonOpts = [{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {exec, fun ssh_exec_echo/1}],
+
+ %% Servers tcp-contects to the listening socket and starts an ssh daemon
+ Pids =
+ [spawn_link(fun() ->
+ {ok,Ss} = ssh_test_lib:gen_tcp_connect("localhost", Port, [{active,false}]),
+ {ok, _Pid} = ssh:daemon(Ss, DaemonOpts)
+ end)
+ || _ <- lists:seq(1,NumConcurent)],
+ ct:log("~p:~p: ~p daemons spawned!", [?MODULE,?LINE,length(Pids)]),
+
+ %% The client accepts the tcp connections from the servers and ssh-connects to it
+ ConnectionRefs =
+ [begin
+ {ok,Sc} = gen_tcp:accept(Sl),
+ {ok,ConnectionRef} = ssh:connect(Sc, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, true},
+ {user_dir, UserDir}]),
+ ConnectionRef
+ end || _Pid <- Pids],
+ ct:log("~p:~p: ~p connections accepted!", [?MODULE,?LINE,length(ConnectionRefs)]),
+
+ %% And runs some exec commands
+ Parent = self(),
+ ClientPids =
+ lists:map(
+ fun(ConnectionRef) ->
+ spawn_link(
+ fun() ->
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0, "testing", infinity),
+ ct:log("~p:~p: exec on connection ~p", [?MODULE,?LINE,ConnectionRef]),
+ receive
+ {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
+ Parent ! {answer_received,self()},
+ ct:log("~p:~p: recevied result on connection ~p", [?MODULE,?LINE,ConnectionRef])
+ after 5000 ->
+ ct:fail("Exec Timeout")
+ end
+ end)
+ end, ConnectionRefs),
+ ct:log("~p:~p: ~p clients spawned!", [?MODULE,?LINE,length(ClientPids)]),
+
+ lists:foreach(fun(P) ->
+ receive
+ {answer_received,P} -> ok
+ end
+ end, ClientPids),
+ ct:log("~p:~p: All answers received!", [?MODULE,?LINE]),
+
+ lists:foreach(fun ssh:close/1, ConnectionRefs).
+
%%--------------------------------------------------------------------
gracefull_invalid_version(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
@@ -1004,9 +1165,6 @@ gracefull_invalid_long_start_no_nl(Config) when is_list(Config) ->
10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
-stop_listener() ->
- [{doc, "start ssh daemon, setup connections, stop listener, restart listner"}].
-
stop_listener(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
@@ -1036,7 +1194,7 @@ stop_listener(Config) when is_list(Config) ->
success = ssh_connection:exec(ConnectionRef0, ChannelId0,
"testing", infinity),
receive
- {ssh_cm, ConnectionRef0, {data, ChannelId0, 0, <<"echo testing\r\n">>}} ->
+ {ssh_cm, ConnectionRef0, {data, ChannelId0, 0, <<"echo testing\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -1104,9 +1262,6 @@ start_subsystem_on_closed_channel(Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
-max_channels_option() ->
- [{doc, "Test max_channels option"}].
-
max_channels_option(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
@@ -1191,6 +1346,41 @@ max_channels_option(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
+
+do_simple_exec(ConnectionRef) ->
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,
+ "echo testing", infinity),
+ %% receive response to input
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}} ->
+ ok
+ after
+ 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ after
+ 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ after
+ 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ after
+ 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end.
+
+
+%%--------------------------------------------------------------------
flush_msgs() ->
receive
_ -> flush_msgs()
@@ -1198,6 +1388,7 @@ flush_msgs() ->
500 -> ok
end.
+%%--------------------------------------------------------------------
test_shell_is_disabled(ConnectionRef) ->
test_shell_is_disabled(ConnectionRef, <<"Prohibited.">>, <<"Eshell V">>).
@@ -1214,13 +1405,15 @@ test_shell_is_disabled(ConnectionRef, Expect, NotExpect) ->
ct:fail("Could start disabled shell!");
R ->
- ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,R]),
+ ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
+ [?MODULE,?LINE,R, {ssh_cm, ConnectionRef, {data, ChannelId, '0|1', Expect}} ]),
ct:fail("Strange shell response")
after 5000 ->
ct:fail("Shell Timeout")
end.
+%%--------------------------------------------------------------------
test_exec_is_disabled(ConnectionRef) ->
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:exec(ConnectionRef, ChannelId, "1+2.", infinity),
@@ -1228,25 +1421,38 @@ test_exec_is_disabled(ConnectionRef) ->
{ssh_cm, ConnectionRef, {data,ChannelId,1,<<"Prohibited.">>}} ->
flush_msgs();
R ->
- ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,R]),
+ ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
+ [?MODULE,?LINE,R, {ssh_cm, ConnectionRef, {data,ChannelId,1,<<"Prohibited.">>}} ]),
ct:fail("Could exec erlang term although non-erlang shell")
after 5000 ->
ct:fail("Exec Timeout")
end.
+%%--------------------------------------------------------------------
test_shell_is_enabled(ConnectionRef) ->
test_shell_is_enabled(ConnectionRef, <<"Eshell V">>).
test_shell_is_enabled(ConnectionRef, Expect) ->
+ test_shell_is_enabled(ConnectionRef, Expect, []).
+
+test_shell_is_enabled(ConnectionRef, Expect, PtyOpts) ->
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ case PtyOpts of
+ [] ->
+ no_alloc;
+ _ ->
+ success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, PtyOpts)
+ end,
ok = ssh_connection:shell(ConnectionRef,ChannelId),
+
ExpSz = size(Expect),
receive
{ssh_cm,ConnectionRef, {data, ChannelId, 0, <<Expect:ExpSz/binary, _/binary>>}} ->
flush_msgs();
R ->
- ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,R]),
+ ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
+ [?MODULE,?LINE,R, {ssh_cm, ConnectionRef, {data, ChannelId, 0, Expect}} ]),
ct:fail("Strange shell response")
after 5000 ->
@@ -1265,42 +1471,12 @@ test_exec_is_enabled(ConnectionRef, Exec, Expect) ->
{ssh_cm, ConnectionRef, {data, ChannelId, 0, <<Expect:ExpSz/binary, _/binary>>}} = R ->
ct:log("~p:~p Got expected ~p",[?MODULE,?LINE,R]);
Other ->
- ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,Other])
+ ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
+ [?MODULE,?LINE, Other, {ssh_cm, ConnectionRef, {data, ChannelId, 0, Expect}} ])
after 5000 ->
{fail,"Exec Timeout"}
end.
-
-%%%----------------------------------------------------------------
-get_channel_close_sequence(ConnectionRef, ChannelId) ->
- Set = [eof, exit_status, closed],
- get_channel_close_sequence(ConnectionRef, ChannelId, Set).
-
-get_channel_close_sequence(_ConnectionRef, _ChannelId, []) ->
- ok;
-get_channel_close_sequence(ConnectionRef, ChannelId, Set) ->
- receive
- {ssh_cm, ConnectionRef, Event} when element(2,Event) == ChannelId ->
- try lists:member(element(1,Event), Set)
- of
- true ->
- ct:log("get_channel_close_sequence: ~p received", [Event]),
- get_channel_close_sequence(ConnectionRef, Event, Set--[element(1,Event)]);
- false ->
- ct:log("~p:~p Got unexpected ~p~nExpecting ~p",[?MODULE,?LINE,Event,Set]),
- ct:fail("Strange response 1")
- catch
- _ :_ ->
- ct:log("~p:~p Got unexpected ~p~nExpecting ~p",[?MODULE,?LINE,Event,Set]),
- ct:fail("Strange response 2")
- end;
- Msg ->
- ct:log("~p:~p Got unexpected ~p~nExpecting event from the set ~p",[?MODULE,?LINE,Msg,Set]),
- ct:fail("Strange response 3")
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end.
-
%%%----------------------------------------------------------------
big_cat_rx(ConnectionRef, ChannelId) ->
big_cat_rx(ConnectionRef, ChannelId, []).
@@ -1377,12 +1553,3 @@ ssh_exec_echo(Cmd, User) ->
spawn(fun() ->
io:format("echo ~s ~s\n",[User,Cmd])
end).
-ssh_exec_echo(Cmd, User, _PeerAddr) ->
- ssh_exec_echo(Cmd,User).
-
-ssh_exec_direct_echo(Cmd) -> {ok, io_lib:format("echo ~s~n",[Cmd])}.
-ssh_exec_direct_echo(Cmd, User) -> {ok, io_lib:format("echo ~s ~s",[User,Cmd])}.
-ssh_exec_direct_echo(Cmd, User, _PeerAddr) -> ssh_exec_direct_echo(Cmd,User).
-
-ssh_exec_direct_echo_error_return(_Cmd) -> {error, {bad}}.
-ssh_exec_direct_echo_error_return_type(_Cmd) -> very_bad.
diff --git a/lib/ssh/test/ssh_dbg_SUITE.erl b/lib/ssh/test/ssh_dbg_SUITE.erl
index ab7918fa90..fec92c6087 100644
--- a/lib/ssh/test/ssh_dbg_SUITE.erl
+++ b/lib/ssh/test/ssh_dbg_SUITE.erl
@@ -23,11 +23,32 @@
-module(ssh_dbg_SUITE).
-include_lib("common_test/include/ct.hrl").
--include_lib("ssh/src/ssh.hrl").
+-include("ssh.hrl").
-include("ssh_test_lib.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ all_dbg/1,
+ cb_basic/1,
+ cb_macros_print/1,
+ cb_print/1,
+ dbg_alg_terminate/1,
+ dbg_authentication/1,
+ dbg_basic/1,
+ dbg_channels/1,
+ dbg_connections/1,
+ dbg_ssh_messages/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -47,7 +68,9 @@ groups() ->
dbg_alg_terminate,
dbg_ssh_messages,
dbg_connections,
- dbg_channels]},
+ dbg_channels,
+ dbg_authentication,
+ all_dbg]},
{circ_buf, [], [cb_basic,
cb_print,
cb_macros_print
@@ -58,7 +81,7 @@ groups() ->
init_per_suite(Config) ->
?CHECK_CRYPTO(begin
ssh:start(),
- Config
+ setup_dirs(Config)
end).
end_per_suite(_Config) ->
@@ -82,6 +105,8 @@ end_per_testcase(_TC, Config) ->
ok
after 5000 ->
+ ct:log("~p:~p Messages:~n~p",
+ [?MODULE,?LINE, process_info(self(),messages)]),
ssh_dbg:stop(),
ssh:stop_daemon(Pid),
ct:fail("No '~s' debug message",[ExpectPfx])
@@ -191,6 +216,84 @@ dbg_connections(Config) ->
stop_and_fail_if_unhandled_dbg_msgs(Ref, [C,D], Pid).
%%--------------------------------------------------------------------
+dbg_authentication(Config) ->
+ Ref = ssh_dbg_start(),
+ {ok,[authentication]} = ssh_dbg:on([authentication]),
+
+ Parent = self(),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, system_dir(Config)},
+ {user_dir, user_dir(Config)},
+ {user_passwords, [{?USR,?PWD}]},
+ {connectfun, fun(_,_,_) ->
+ Parent ! {daemon_c,Ref,self()}
+ end},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+
+ %% ---- Check password ----
+ Cpwd = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, user_dir(Config)},
+ {user,?USR},
+ {password,?PWD},
+ {auth_methods,"password"},
+ {user_interaction, false}]),
+ Cpwd_d = daemon_connection_ref(Ref, Cpwd),
+
+ ?DBG_RECEIVE("AUTH client: Service ssh-userauth accepted", Ref, Cpwd, Pid),
+ ?DBG_RECEIVE("AUTH client: Query for accepted methods", Ref, Cpwd, Pid),
+ ?DBG_RECEIVE("AUTH srvr: Peer queries auth methods", Ref, Cpwd_d, Pid),
+ ?DBG_RECEIVE("AUTH client: Server supports", Ref, Cpwd, Pid),
+ ?DBG_RECEIVE("AUTH client: Try auth with", Ref, Cpwd, Pid),
+ ?DBG_RECEIVE("AUTH srvr: Peer client authorized", Ref, Cpwd_d, Pid),
+ ?DBG_RECEIVE("AUTH client: Success", Ref, Cpwd, Pid),
+ ssh:close(Cpwd),
+ fail_if_unhandled_dbg_msgs(Ref, [Cpwd,Cpwd_d]),
+
+ %% ---- Check keyboard-interactive ----
+ Ckbi = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, user_dir(Config)},
+ {user,?USR},
+ {password,?PWD},
+ {auth_methods,"keyboard-interactive"},
+ {user_interaction, false}]),
+ Ckbi_d = daemon_connection_ref(Ref, Ckbi),
+
+ ?DBG_RECEIVE("AUTH client: Service ssh-userauth accepted", Ref, Ckbi, Pid),
+ ?DBG_RECEIVE("AUTH client: Query for accepted methods", Ref, Ckbi, Pid),
+ ?DBG_RECEIVE("AUTH srvr: Peer queries auth methods", Ref, Ckbi_d, Pid),
+ ?DBG_RECEIVE("AUTH client: Server supports", Ref, Ckbi, Pid),
+ ?DBG_RECEIVE("AUTH client: Try auth with", Ref, Ckbi, Pid),
+ ?DBG_RECEIVE("AUTH srvr: Ask peer client for password", Ref, Ckbi_d, Pid),
+ ?DBG_RECEIVE("AUTH client: Success", Ref, Ckbi, Pid),
+ ssh:close(Ckbi),
+ fail_if_unhandled_dbg_msgs(Ref, [Ckbi,Ckbi_d]),
+
+ %% ---- Check publickey ----
+ Cpkey = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, user_dir(Config)},
+ {auth_methods,"publickey"},
+ {user_interaction, false}]),
+ Cpkey_d = daemon_connection_ref(Ref, Cpkey),
+
+ ?DBG_RECEIVE("AUTH client: Service ssh-userauth accepted", Ref, Cpkey, Pid),
+ ?DBG_RECEIVE("AUTH client: Query for accepted methods", Ref, Cpkey, Pid),
+ ?DBG_RECEIVE("AUTH srvr: Peer queries auth methods", Ref, Cpkey_d, Pid),
+ ?DBG_RECEIVE("AUTH client: Server supports", Ref, Cpkey, Pid),
+ ?DBG_RECEIVE("AUTH client: Try auth with", Ref, Cpkey, Pid),
+ ?DBG_RECEIVE("AUTH srvr: Peer client authorized", Ref, Cpkey_d, Pid),
+ ?DBG_RECEIVE("AUTH client: Success", Ref, Cpkey, Pid),
+ ssh:close(Cpkey),
+ stop_and_fail_if_unhandled_dbg_msgs(Ref, [Cpkey,Cpkey_d], Pid).
+
+
+daemon_connection_ref(Ref,C) ->
+ D =
+ receive
+ {daemon_c,Ref,D0} -> D0
+ end,
+ ct:log("~p:~p~nC = ~p, D=~p",[?MODULE,?LINE, C, D]),
+ D.
+
+%%--------------------------------------------------------------------
dbg_ssh_messages(Config) ->
SystemDir = proplists:get_value(data_dir, Config),
UserDir = proplists:get_value(priv_dir, Config),
@@ -351,6 +454,37 @@ dbg_channels(Config) ->
stop_and_fail_if_unhandled_dbg_msgs(Ref, [C,D], Pid).
%%--------------------------------------------------------------------
+all_dbg(Config) ->
+ SystemDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+
+ Dir0 = filename:join(proplists:get_value(priv_dir,Config), ssh_test_lib:random_chars(10)),
+ file:make_dir(Dir0),
+ Dir = w2l(Config, Dir0),
+ ct:log("~p:~p created the directory~nsDir0 = ~p~nDir = ~p", [?MODULE,?LINE,Dir0,Dir]),
+
+ AllTags = ssh_dbg:start(),
+ {ok,AllTags} = ssh_dbg:on(AllTags),
+
+ {_, Host, Port} =
+ ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords, [{?USR,?PWD}]}
+ ]),
+
+ {ok, ChPid, _C} =
+ ssh_sftp:start_channel(Host, Port,
+ [{user_dir, UserDir},
+ {user,?USR},
+ {password,?PWD},
+ {user_interaction, false},
+ {silently_accept_hosts, true}
+ ]),
+
+ {ok, _Files} = ssh_sftp:list_dir(ChPid, Dir).
+
+
+%%--------------------------------------------------------------------
cb_basic(_Config) ->
%% Check that the circular buffer is disabled at start:
[] = ssh_dbg:cbuf_list(),
@@ -412,6 +546,16 @@ ssh_dbg_start(Ref) ->
Ref.
%%--------------------------------------------------------------------
+setup_dirs(Config) ->
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
+ Config.
+
+system_dir(Config) -> filename:join(proplists:get_value(priv_dir, Config), system).
+
+user_dir(Config) -> proplists:get_value(priv_dir, Config).
+
+%%--------------------------------------------------------------------
queued_msgs(Ref, Conns) ->
queued_msgs(Ref, Conns, []).
@@ -429,11 +573,10 @@ queued_msgs(Ref, Conns, Acc) ->
end.
%%--------------------------------------------------------------------
-stop_and_fail_if_unhandled_dbg_msgs(Ref, Conns, DaemonPid) ->
- stop_and_fail_if_unhandled_dbg_msgs(queued_msgs(Ref,Conns), Ref, Conns, DaemonPid).
+fail_if_unhandled_dbg_msgs(Ref, Conns) ->
+ fail_if_unhandled_dbg_msgs(queued_msgs(Ref,Conns), Ref, Conns).
-stop_and_fail_if_unhandled_dbg_msgs(Msgs, _Ref, _Conns, DaemonPid) ->
- ssh:stop_daemon(DaemonPid),
+fail_if_unhandled_dbg_msgs(Msgs, _Ref, _Conns) ->
case Msgs of
[] ->
ok;
@@ -443,15 +586,29 @@ stop_and_fail_if_unhandled_dbg_msgs(Msgs, _Ref, _Conns, DaemonPid) ->
end.
%%--------------------------------------------------------------------
+stop_and_fail_if_unhandled_dbg_msgs(Ref, Conns, DaemonPid) ->
+ stop_and_fail_if_unhandled_dbg_msgs(queued_msgs(Ref,Conns), Ref, Conns, DaemonPid).
+
+stop_and_fail_if_unhandled_dbg_msgs(Msgs, Ref, Conns, DaemonPid) ->
+ ssh:stop_daemon(DaemonPid),
+ fail_if_unhandled_dbg_msgs(Msgs, Ref, Conns).
+
+
+%%--------------------------------------------------------------------
dbg_SKIP(Ref, Prefixes) ->
dbg_SKIP(Ref, Prefixes, []).
dbg_SKIP(Ref, Prefixes, UnexpectedAcc) ->
receive
+ {Ref, [_, _C, Msg]} when is_tuple(Msg) ->
+ %% filter non ssh_dbg messages, for example from dbg:tp(..) etc
+ dbg_SKIP(Ref, Prefixes, UnexpectedAcc);
{Ref, [_, _C, Msg]=M} ->
case lists:any(
fun(Pfx) ->
- lists:prefix(Pfx, Msg)
+ try lists:prefix(Pfx, Msg)
+ catch _:_ -> false
+ end
end, Prefixes) of
true ->
ct:log("Skip:~n~p", [M]),
@@ -463,3 +620,11 @@ dbg_SKIP(Ref, Prefixes, UnexpectedAcc) ->
lists:reverse(UnexpectedAcc)
end.
+%%%----------------------------------------------------------------
+%% w2l(P) ->
+%% ssh_test_lib:winpath_to_linuxpath(P).
+
+w2l(Config, P) ->
+ W2L = proplists:get_value(w2l, Config, fun(X) -> X end),
+ W2L(P).
+
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_dsa b/lib/ssh/test/ssh_dbg_SUITE_data/id_dsa
new file mode 100644
index 0000000000..24628e071b
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
+-----END DSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_dbg_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa256
new file mode 100644
index 0000000000..4b1eb12eaa
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49
+AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br
+N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa256.pub
new file mode 100644
index 0000000000..a0147e60fa
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa384
new file mode 100644
index 0000000000..4e8aa40959
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S
+PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd
+ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF
+8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa384.pub
new file mode 100644
index 0000000000..41e722e545
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa521
new file mode 100644
index 0000000000..7196f46e97
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q
+9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5
+01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7
+XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf
+pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa521.pub
new file mode 100644
index 0000000000..8f059120bc
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_dbg_SUITE_data/id_ed25519
new file mode 100644
index 0000000000..401a3e4a9a
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us
+aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw
+AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L
+ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_dbg_SUITE_data/id_ed25519.pub
new file mode 100644
index 0000000000..a5c03b19c1
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ed448 b/lib/ssh/test/ssh_dbg_SUITE_data/id_ed448
new file mode 100644
index 0000000000..8ecfd710dc
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ed448
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh
+1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf
+fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32
+BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ
+m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_dbg_SUITE_data/id_ed448.pub
new file mode 100644
index 0000000000..cec0765a5d
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_rsa b/lib/ssh/test/ssh_dbg_SUITE_data/id_rsa
new file mode 100644
index 0000000000..2202c2ead8
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_dbg_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_dbg_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_dbg_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_echo_server.erl b/lib/ssh/test/ssh_echo_server.erl
index e039439f87..0e2519fc84 100644
--- a/lib/ssh/test/ssh_echo_server.erl
+++ b/lib/ssh/test/ssh_echo_server.erl
@@ -57,6 +57,7 @@ handle_ssh_msg({ssh_cm, CM, {data, ChannelId, 0, Data}}, #state{n = N} = State)
case M > 0 of
true ->
?DBG(State, "ssh_cm data Cid=~p size(Data)=~p M=~p",[ChannelId,size(Data),M]),
+ ssh_connection:adjust_window(CM, ChannelId, size(Data)),
ssh_connection:send(CM, ChannelId, Data),
{ok, State#state{n = M}};
false ->
diff --git a/lib/ssh/test/ssh_engine_SUITE.erl b/lib/ssh/test/ssh_engine_SUITE.erl
index 3adb23acdb..2494d0e83e 100644
--- a/lib/ssh/test/ssh_engine_SUITE.erl
+++ b/lib/ssh/test/ssh_engine_SUITE.erl
@@ -26,7 +26,20 @@
-include("ssh_test_lib.hrl").
%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2
+ ]).
+
+-export([
+ simple_connect/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -81,7 +94,7 @@ end_per_suite(Config) ->
%%--------------------------------------------------------------------
init_per_group(dsa_key, Config) ->
case lists:member('ssh-dss',
- ssh_transport:default_algorithms(public_key)) of
+ ssh_transport:supported_algorithms(public_key)) of
true ->
start_daemon(Config, 'ssh-dss', "dsa_private_key.pem");
false ->
@@ -89,7 +102,7 @@ init_per_group(dsa_key, Config) ->
end;
init_per_group(rsa_key, Config) ->
case lists:member('ssh-rsa',
- ssh_transport:default_algorithms(public_key)) of
+ ssh_transport:supported_algorithms(public_key)) of
true ->
start_daemon(Config, 'ssh-rsa', "rsa_private_key.pem");
false ->
@@ -102,9 +115,11 @@ start_daemon(Config, KeyType, KeyId) ->
KeyCBOpts = [{engine, proplists:get_value(engine,Config)},
{KeyType, FullKeyId}
],
- Opts = [{key_cb, {ssh_key_cb_engine_keys, KeyCBOpts}}],
+ Opts = [{key_cb, {ssh_key_cb_engine_keys, KeyCBOpts}},
+ {modify_algorithms, [{append, [{public_key,[KeyType]}]}]}
+ ],
{Pid, Host, Port} = ssh_test_lib:std_daemon(Config, Opts),
- [{host_port,{Host,Port}}, {daemon_pid,Pid}| Config].
+ [{host_port,{Host,Port}}, {daemon_pid,Pid}, {key_type,KeyType}| Config].
end_per_group(_, Config) ->
@@ -118,7 +133,11 @@ end_per_group(_, Config) ->
%% A simple exec call
simple_connect(Config) ->
{Host,Port} = proplists:get_value(host_port, Config),
- CRef = ssh_test_lib:std_connect(Config, Host, Port, []),
+ KeyType = proplists:get_value(key_type, Config),
+ Opts = [
+ {modify_algorithms, [{append, [{public_key,[KeyType]}]}]}
+ ],
+ CRef = ssh_test_lib:std_connect(Config, Host, Port, Opts),
ssh:close(CRef).
%%--------------------------------------------------------------------
@@ -146,8 +165,3 @@ load_engine() ->
{error, Error}
end.
-start_std_daemon(Opts, Config) ->
- ct:log("starting std_daemon",[]),
- {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, Opts),
- ct:log("started ~p:~p ~p",[Host,Port,Opts]),
- [{srvr_pid,Pid},{srvr_addr,{Host,Port}} | Config].
diff --git a/lib/ssh/test/ssh_eqc_event_handler.erl b/lib/ssh/test/ssh_eqc_event_handler.erl
index 233965012a..c1a3d2d6b6 100644
--- a/lib/ssh/test/ssh_eqc_event_handler.erl
+++ b/lib/ssh/test/ssh_eqc_event_handler.erl
@@ -1,6 +1,16 @@
-module(ssh_eqc_event_handler).
--compile(export_all).
+-export([
+ add_report_handler/0,
+ get_reports/1,
+ code_change/3,
+ handle_call/2,
+ handle_event/2,
+ handle_info/2,
+ init/1,
+ terminate/2
+ ]).
+
-behaviour(gen_event).
diff --git a/lib/ssh/test/ssh_key_cb.erl b/lib/ssh/test/ssh_key_cb.erl
index 5564b9d873..9762e5ffbe 100644
--- a/lib/ssh/test/ssh_key_cb.erl
+++ b/lib/ssh/test/ssh_key_cb.erl
@@ -25,7 +25,13 @@
-module(ssh_key_cb).
-behaviour(ssh_client_key_api).
--compile(export_all).
+
+-export([
+ add_host_key/3,
+ is_host_key/4,
+ user_key/2
+ ]).
+
add_host_key(_, _, _) ->
ok.
diff --git a/lib/ssh/test/ssh_key_cb_engine_keys.erl b/lib/ssh/test/ssh_key_cb_engine_keys.erl
index fc9cbfd49b..ff78b98894 100644
--- a/lib/ssh/test/ssh_key_cb_engine_keys.erl
+++ b/lib/ssh/test/ssh_key_cb_engine_keys.erl
@@ -25,7 +25,12 @@
-module(ssh_key_cb_engine_keys).
-behaviour(ssh_server_key_api).
--compile(export_all).
+
+-export([
+ host_key/2,
+ is_auth_key/3
+ ]).
+
host_key(SshAlg, Options) ->
KBopts = proplists:get_value(key_cb_private, Options, []),
diff --git a/lib/ssh/test/ssh_key_cb_options.erl b/lib/ssh/test/ssh_key_cb_options.erl
index c104a2f129..96b0089e80 100644
--- a/lib/ssh/test/ssh_key_cb_options.erl
+++ b/lib/ssh/test/ssh_key_cb_options.erl
@@ -25,7 +25,12 @@
-module(ssh_key_cb_options).
-behaviour(ssh_client_key_api).
--compile(export_all).
+-export([
+ add_host_key/3,
+ is_host_key/4,
+ user_key/2
+ ]).
+
add_host_key(_, _, _) ->
ok.
diff --git a/lib/ssh/test/ssh_limited.cover b/lib/ssh/test/ssh_limited.cover
new file mode 100644
index 0000000000..2b0fcf5bf6
--- /dev/null
+++ b/lib/ssh/test/ssh_limited.cover
@@ -0,0 +1,22 @@
+%% -*- erlang -*-
+
+{incl_app,ssh,details}.
+
+{excl_mods, ssh,
+ [
+ %% App
+ ssh_app,
+
+ %% Supervisors
+ ssh_acceptor_sup, ssh_channel_sup, ssh_connection_sup,
+ sshc_sup, sshd_sup, ssh_subsystem_sup, ssh_sup,
+ ssh_system_sup, ssh_tcpip_forward_acceptor_sup,
+
+ %% Test and/or info modules:
+ ssh_dbg, ssh_info,
+
+ %% API modules (behaviours):
+ ssh_server_key_api, ssh_client_key_api,
+ ssh_sftpd_file_api,
+ ssh_channel, ssh_client_channel, ssh_daemon_channel, ssh_server_channel
+ ]}.
diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl
index 7d306e5c74..09b1c7ccbb 100644
--- a/lib/ssh/test/ssh_options_SUITE.erl
+++ b/lib/ssh/test/ssh_options_SUITE.erl
@@ -30,7 +30,11 @@
-include("ssh_test_lib.hrl").
%%% Test cases
--export([connectfun_disconnectfun_client/1,
+-export([
+ auth_method_kb_interactive_data_tuple/1,
+ auth_method_kb_interactive_data_fun3/1,
+ auth_method_kb_interactive_data_fun4/1,
+ connectfun_disconnectfun_client/1,
disconnectfun_option_client/1,
disconnectfun_option_server/1,
id_string_no_opt_client/1,
@@ -45,11 +49,14 @@
max_sessions_sftp_start_channel_sequential/1,
max_sessions_ssh_connect_parallel/1,
max_sessions_ssh_connect_sequential/1,
+ max_sessions_drops_tcp_connects/1,
+ max_sessions_drops_tcp_connects/0,
server_password_option/1,
server_userpassword_option/1,
server_pwdfun_option/1,
server_pwdfun_4_option/1,
server_keyboard_interactive/1,
+ server_keyboard_interactive_extra_msg/1,
ssh_connect_arg4_timeout/1,
ssh_connect_negtimeout_parallel/1,
ssh_connect_negtimeout_sequential/1,
@@ -62,7 +69,8 @@
system_dir_option/1,
unexpectedfun_option_client/1,
unexpectedfun_option_server/1,
- user_dir_option/1,
+ user_dir_option/1,
+ user_dir_fun_option/1,
connectfun_disconnectfun_server/1,
hostkey_fingerprint_check/1,
hostkey_fingerprint_check_md5/1,
@@ -71,7 +79,10 @@
hostkey_fingerprint_check_sha384/1,
hostkey_fingerprint_check_sha512/1,
hostkey_fingerprint_check_list/1,
- save_accepted_host_option/1
+ save_accepted_host_option/1,
+ raw_option/1,
+ config_file/1,
+ config_file_modify_algorithms_order/1
]).
%%% Common test callbacks
@@ -81,7 +92,6 @@
init_per_testcase/2, end_per_testcase/2
]).
-
-define(NEWLINE, <<"\r\n">>).
%%--------------------------------------------------------------------
@@ -90,7 +100,7 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{seconds,30}}].
+ {timetrap,{seconds,60}}].
all() ->
[connectfun_disconnectfun_server,
@@ -100,6 +110,10 @@ all() ->
server_pwdfun_option,
server_pwdfun_4_option,
server_keyboard_interactive,
+ server_keyboard_interactive_extra_msg,
+ auth_method_kb_interactive_data_tuple,
+ auth_method_kb_interactive_data_fun3,
+ auth_method_kb_interactive_data_fun4,
{group, dir_options},
ssh_connect_timeout,
ssh_connect_arg4_timeout,
@@ -126,6 +140,9 @@ all() ->
id_string_own_string_server_trail_space,
id_string_random_server,
save_accepted_host_option,
+ raw_option,
+ config_file,
+ config_file_modify_algorithms_order,
{group, hardening_tests}
].
@@ -137,9 +154,11 @@ groups() ->
max_sessions_ssh_connect_parallel,
max_sessions_ssh_connect_sequential,
max_sessions_sftp_start_channel_parallel,
- max_sessions_sftp_start_channel_sequential
+ max_sessions_sftp_start_channel_sequential,
+ max_sessions_drops_tcp_connects
]},
{dir_options, [], [user_dir_option,
+ user_dir_fun_option,
system_dir_option]}
].
@@ -153,10 +172,8 @@ end_per_suite(_Config) ->
%%--------------------------------------------------------------------
init_per_group(hardening_tests, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
Config;
init_per_group(dir_options, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
@@ -383,7 +400,7 @@ server_pwdfun_4_option(Config) ->
%%--------------------------------------------------------------------
server_keyboard_interactive(Config) ->
UserDir = proplists:get_value(user_dir, Config),
- SysDir = proplists:get_value(data_dir, Config),
+ SysDir = proplists:get_value(data_dir, Config),
%% Test that the state works
Parent = self(),
PWDFUN = fun("foo",P="bar",_,S) -> Parent!{P,S},true;
@@ -438,7 +455,116 @@ server_keyboard_interactive(Config) ->
end, [{"incorrect",undefined},
{"Bad again",1},
{"bar",2}]).
-
+
+%%--------------------------------------------------------------------
+server_keyboard_interactive_extra_msg(Config) ->
+ UserDir = proplists:get_value(user_dir, Config),
+ SysDir = proplists:get_value(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {auth_methods,"keyboard-interactive"},
+ {tstflg, [{one_empty,true}]},
+ {user_passwords, [{"foo","bar"}]}
+ ]),
+
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "bar"},
+ {user_dir, UserDir}]),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+auth_method_kb_interactive_data_tuple(Config) ->
+ T = {"abc1", "def1", "ghi1: ", true},
+ amkid(Config, T, T).
+
+auth_method_kb_interactive_data_fun3(Config) ->
+ T = {"abc2", "def2", "ghi2: ", true},
+ amkid(Config, T,
+ fun(_Peer, _User, _Service) -> T end
+ ).
+
+auth_method_kb_interactive_data_fun4(Config) ->
+ T = {"abc3", "def3", "ghi3: ", true},
+ amkid(Config, T,
+ fun(_Peer, _User, _Service, _State) -> T end
+ ).
+
+amkid(Config, {ExpectName,ExpectInstr,ExpectPrompts,ExpectEcho}, OptVal) ->
+ UserDir = proplists:get_value(user_dir, Config),
+ SysDir = proplists:get_value(data_dir, Config),
+ %% Test that the state works
+ Parent = self(),
+ PWDFUN = fun("foo",P="bar",_,S) -> Parent!{P,S},true;
+ (_,P,_,S=undefined) -> Parent!{P,S},{false,1};
+ (_,P,_,S) -> Parent!{P,S}, {false,S+1}
+ end,
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {auth_methods,"keyboard-interactive"},
+ {pwdfun,PWDFUN},
+ {auth_method_kb_interactive_data,OptVal}
+ ]),
+
+ KIFFUN = fun(Name, Instr, PromptInfos) ->
+ K={k,self()},
+ Answer =
+ case get(K) of
+ undefined ->
+ put(K,1),
+ ["incorrect"];
+ 2 ->
+ put(K,3),
+ ["bar"];
+ S->
+ put(K,S+1),
+ ["Bad again"]
+ end,
+ ct:log("keyboard_interact_fun:~n"
+ " Name = ~p~n"
+ " Instruction = ~p~n"
+ " Prompts = ~p~n"
+ "~nAnswer:~n ~p~n",
+ [Name, Instr, PromptInfos, Answer]),
+ case {binary_to_list(Name),
+ binary_to_list(Instr),
+ [{binary_to_list(PI),Echo} || {PI,Echo} <- PromptInfos]
+ } of
+ {ExpectName, ExpectInstr, [{ExpectPrompts,ExpectEcho}]} ->
+ ct:log("Match!", []),
+ Answer;
+ _ ->
+ ct:log("Not match!~n"
+ " ExpectName = ~p~n"
+ " ExpectInstruction = ~p~n"
+ " ExpectPrompts = ~p~n",
+ [ExpectName, ExpectInstr, [{ExpectPrompts,ExpectEcho}]]),
+ ct:fail("no_match")
+ end
+ end,
+ ssh_dbg:start(), ssh_dbg:on(authentication), %% Test dbg code
+ ConnectionRef2 =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {keyboard_interact_fun, KIFFUN},
+ {user_dir, UserDir}]),
+ ssh_dbg:stop(),
+ ssh:close(ConnectionRef2),
+ ssh:stop_daemon(Pid),
+
+ lists:foreach(fun(Expect) ->
+ receive
+ Expect -> ok;
+ Other -> ct:fail("Expect: ~p~nReceived ~p",[Expect,Other])
+ after
+ 2000 -> ct:fail("Timeout expecting ~p",[Expect])
+ end
+ end, [{"incorrect",undefined},
+ {"Bad again",1},
+ {"bar",2}]).
+
%%--------------------------------------------------------------------
system_dir_option(Config) ->
DirUnread = proplists:get_value(unreadable_dir,Config),
@@ -460,7 +586,7 @@ system_dir_option(Config) ->
ct:fail("Didn't detect that option is a plain file", [])
end.
-
+%%--------------------------------------------------------------------
user_dir_option(Config) ->
DirUnread = proplists:get_value(unreadable_dir,Config),
FileRead = proplists:get_value(readable_file,Config),
@@ -482,6 +608,44 @@ user_dir_option(Config) ->
end.
%%--------------------------------------------------------------------
+user_dir_fun_option(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ SysDir = filename:join(PrivDir,"system"),
+ ssh_test_lib:setup_all_host_keys(DataDir, SysDir),
+ UserDir = filename:join(PrivDir,"user"),
+ ssh_test_lib:setup_all_user_keys(DataDir, UserDir),
+
+ Parent = self(),
+ Ref = make_ref(),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir_fun, fun(User) ->
+ ct:log("user_dir_fun called ~p",[User]),
+ Parent ! {user,Ref,User},
+ UserDir
+ end},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ _ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {user_dir, UserDir},
+ {auth_methods,"publickey"},
+ {user_interaction, false}]),
+ receive
+ {user,Ref,"foo"} ->
+ ssh:stop_daemon(Pid),
+ ok;
+ {user,Ref,What} ->
+ ssh:stop_daemon(Pid),
+ ct:log("Got ~p",[What]),
+ {fail, bad_userid}
+ after 2000 ->
+ ssh:stop_daemon(Pid),
+ {fail,timeout_in_receive}
+ end.
+
+
+%%--------------------------------------------------------------------
%%% validate client that uses the 'ssh_msg_debug_fun' option
ssh_msg_debug_fun_option_client(Config) ->
UserDir = proplists:get_value(user_dir, Config),
@@ -933,7 +1097,7 @@ ssh_connect_arg4_timeout(_Config) ->
Msp = ms_passed(T0),
exit(Server,hasta_la_vista___baby),
Low = 0.9*Timeout,
- High = 2.5*Timeout,
+ High = 4.0*Timeout,
ct:log("Timeout limits: ~.4f - ~.4f ms, timeout "
"was ~.4f ms, expected ~p ms",[Low,High,Msp,Timeout]),
if
@@ -1033,7 +1197,7 @@ id_string_random_client(Config) ->
receive
{id,Server,Id="SSH-2.0-Erlang"++_} ->
ct:fail("Unexpected id: ~s.",[Id]);
- {id,Server,Rnd="SSH-2.0-"++_} ->
+ {id,Server,Rnd="SSH-2.0-"++ID} when 4=<length(ID),length(ID)=<7 -> %% Add 2 for CRLF
ct:log("Got correct ~s",[Rnd]);
{id,Server,Id} ->
ct:fail("Unexpected id: ~s.",[Id])
@@ -1062,14 +1226,21 @@ id_string_own_string_server_trail_space(Config) ->
%%--------------------------------------------------------------------
id_string_random_server(Config) ->
- {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, [{id_string,random}]),
+ %% Check undocumented format of id_string. First a bad variant:
+ {error,{eoptions,_}} = ssh:daemon(0, [{id_string,{random,8,6}}]),
+ %% And then a correct one:
+ {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, [{id_string,{random,6,8}}]),
{ok,S1}=ssh_test_lib:gen_tcp_connect(Host,Port,[{active,false},{packet,line}]),
{ok,"SSH-2.0-"++Rnd} = gen_tcp:recv(S1, 0, 2000),
case Rnd of
"Erlang"++_ -> ct:log("Id=~p",[Rnd]),
{fail,got_default_id};
"Olle\r\n" -> {fail,got_previous_tests_value};
- _ -> ct:log("Got ~s.",[Rnd])
+ _ when 8=<length(Rnd),length(Rnd)=<10 -> %% Add 2 for CRLF
+ ct:log("Got correct ~s",[Rnd]);
+ _ ->
+ ct:log("Got wrong sized ~s.",[Rnd]),
+ {fail,got_wrong_size}
end.
%%--------------------------------------------------------------------
@@ -1161,19 +1332,6 @@ one_shell_op(IO, TimeOut) ->
IO ! {input, self(), "2*3*7.\r\n"},
receive
- Echo0 -> ct:log("Echo: ~p ~n", [Echo0])
- after TimeOut -> ct:fail("Timeout waiting for echo")
- end,
-
- receive
- ?NEWLINE -> ct:log("NEWLINE received", [])
- after TimeOut ->
- receive Any1 -> ct:log("Bad NEWLINE: ~p",[Any1])
- after 0 -> ct:fail("Timeout waiting for NEWLINE")
- end
- end,
-
- receive
Result0 -> ct:log("Result: ~p~n", [Result0])
after TimeOut -> ct:fail("Timeout waiting for result")
end.
@@ -1238,7 +1396,7 @@ max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) ->
ct:log("Connections up: ~p",[Connections]),
[_|_] = Connections,
- %% Now try one more than alowed:
+ %% N w try one more than alowed:
ct:pal("Info Report expected here (if not disabled) ...",[]),
try Connect(Host,Port)
of
@@ -1287,6 +1445,89 @@ try_to_connect(Connect, Host, Port, Pid, Tref, N) ->
end.
%%--------------------------------------------------------------------
+max_sessions_drops_tcp_connects() ->
+ [{timetrap,{minutes,20}}].
+
+max_sessions_drops_tcp_connects(Config) ->
+ MaxSessions = 20,
+ UseSessions = 2, % Must be =< MaxSessions
+ FloodSessions = 1000,
+ ParallelLogin = true,
+ NegTimeOut = 8*1000,
+ HelloTimeOut = 1*1000,
+
+ %% Start a test daemon
+ SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+ UserDir = proplists:get_value(priv_dir, Config),
+ {Pid, Host0, Port} =
+ ssh_test_lib:daemon([
+ {system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords, [{"carni", "meat"}]},
+ {parallel_login, ParallelLogin},
+ {hello_timeout, HelloTimeOut},
+ {negotiation_timeout, NegTimeOut},
+ {max_sessions, MaxSessions}
+ ]),
+ Host = ssh_test_lib:mangle_connect_address(Host0),
+ ct:log("~p:~p ~p Listen ~p:~p for max ~p sessions. Mangled Host = ~p",
+ [?MODULE,?LINE,Pid,Host0,Port,MaxSessions,Host]),
+
+ %% Log in UseSessions connections
+ SSHconnect = fun(N) ->
+ R = ssh:connect(Host, Port,
+ [{silently_accept_hosts, true},
+ {user_dir, proplists:get_value(priv_dir,Config)},
+ {user_interaction, false},
+ {user, "carni"},
+ {password, "meat"}
+ ]),
+ ct:log("~p:~p ~p: ssh:connect -> ~p", [?MODULE,?LINE,N,R]),
+ R
+ end,
+
+ L1 = oks([SSHconnect(N) || N <- lists:seq(1,UseSessions)]),
+ case length(L1) of
+ UseSessions ->
+ %% As expected
+ %% Try gen_tcp:connect
+ [ct:log("~p:~p ~p: gen_tcp:connect -> ~p",
+ [?MODULE,?LINE, N, gen_tcp:connect(Host, Port, [])])
+ || N <- lists:seq(UseSessions+1, MaxSessions)
+ ],
+
+ ct:log("~p:~p Now try ~p gen_tcp:connect to be rejected", [?MODULE,?LINE,FloodSessions]),
+ [ct:log("~p:~p ~p: gen_tcp:connect -> ~p",
+ [?MODULE,?LINE, N, gen_tcp:connect(Host, Port, [])])
+ || N <- lists:seq(MaxSessions+1, MaxSessions+1+FloodSessions)
+ ],
+
+ ct:log("~p:~p try ~p ssh:connect", [?MODULE,?LINE, MaxSessions - UseSessions]),
+ try_ssh_connect(MaxSessions - UseSessions, NegTimeOut, SSHconnect);
+
+ Len1 ->
+ {fail, Len1}
+ end.
+
+try_ssh_connect(N, NegTimeOut, F) when N>0 ->
+ case F(N) of
+ {ok,_} ->
+ try_ssh_connect(N-1, NegTimeOut, F);
+ {error,_} when N==1 ->
+ try_ssh_connect(N, NegTimeOut, F);
+ {error,_} ->
+ timer:sleep(NegTimeOut),
+ try_ssh_connect(N, NegTimeOut, F)
+ end;
+try_ssh_connect(_N, _NegTimeOut, _F) ->
+ done.
+
+
+oks(L) -> lists:filter(fun({ok,_}) -> true;
+ (_) -> false
+ end, L).
+
+%%--------------------------------------------------------------------
save_accepted_host_option(Config) ->
UserDir = proplists:get_value(user_dir, Config),
KnownHosts = filename:join(UserDir, "known_hosts"),
@@ -1314,8 +1555,213 @@ save_accepted_host_option(Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
+raw_option(_Config) ->
+ Opts = [{raw,1,2,3,4}],
+ #{socket_options := Opts} = ssh_options:handle_options(client, Opts),
+ #{socket_options := Opts} = ssh_options:handle_options(server, Opts).
+
+%%--------------------------------------------------------------------
+config_file(Config) ->
+ %% First find common algs:
+ ServerAlgs = ssh_test_lib:default_algorithms(sshd),
+ OurAlgs = ssh_transport:supported_algorithms(), % Incl disabled but supported
+ CommonAlgs = ssh_test_lib:intersection(ServerAlgs, OurAlgs),
+ ct:log("ServerAlgs =~n~p~n~nOurAlgs =~n~p~n~nCommonAlgs =~n~p",[ServerAlgs,OurAlgs,CommonAlgs]),
+ Nkex = length(proplists:get_value(kex, CommonAlgs, [])),
+
+ case {ServerAlgs, ssh_test_lib:some_empty(CommonAlgs)} of
+ {[],_} ->
+ {skip, "No server algorithms found"};
+ {_,true} ->
+ {fail, "Missing common algorithms"};
+ _ when Nkex<3 ->
+ {skip, "Not enough number of common kex"};
+ _ ->
+ %% Then find three common kex and one common cipher:
+ [K1a,K1b,K2a|_] = proplists:get_value(kex, CommonAlgs),
+ [{_,[Ch1|_]}|_] = proplists:get_value(cipher, CommonAlgs),
+
+ %% Make config file:
+ Contents =
+ [{ssh, [{preferred_algorithms,
+ [{cipher, [Ch1]},
+ {kex, [K1a]}
+ ]},
+ {client_options,
+ [{modify_algorithms,
+ [{rm, [{kex, [K1a]}]},
+ {append, [{kex, [K1b]}]}
+ ]}
+ ]}
+ ]}
+ ],
+ %% write the file:
+ PrivDir = proplists:get_value(priv_dir, Config),
+ ConfFile = filename:join(PrivDir,"c2.config"),
+ {ok,D} = file:open(ConfFile, [write]),
+ io:format(D, "~p.~n", [Contents]),
+ file:close(D),
+ {ok,Cnfs} = file:read_file(ConfFile),
+ ct:log("c2.config:~n~s", [Cnfs]),
+
+ %% Start the slave node with the configuration just made:
+ {ok,Node} = start_node(random_node_name(?MODULE), ConfFile),
+
+ R0 = rpc:call(Node, ssh, default_algorithms, []),
+ ct:log("R0 = ~p",[R0]),
+ R0 = ssh:default_algorithms(),
+
+ %% Start ssh on the slave. This should apply the ConfFile:
+ rpc:call(Node, ssh, start, []),
+
+ R1 = rpc:call(Node, ssh, default_algorithms, []),
+ ct:log("R1 = ~p",[R1]),
+ [{kex,[K1a]},
+ {public_key,_},
+ {cipher,[{_,[Ch1]},
+ {_,[Ch1]}]} | _] = R1,
+
+ %% First connection. The client_options should be applied:
+ {ok,C1} = rpc:call(Node, ssh, connect, [loopback, 22, [{silently_accept_hosts, true},
+ {user_interaction, false}
+ ]]),
+ ct:log("C1 = ~n~p", [C1]),
+ {algorithms,As1} = rpc:call(Node, ssh, connection_info, [C1, algorithms]),
+ K1b = proplists:get_value(kex, As1),
+ Ch1 = proplists:get_value(encrypt, As1),
+ Ch1 = proplists:get_value(decrypt, As1),
+ {options,Os1} = rpc:call(Node, ssh, connection_info, [C1, options]),
+ ct:log("C1 algorithms:~n~p~n~noptions:~n~p", [As1,Os1]),
+
+ %% Second connection, the Options take precedence:
+ C2_Opts = [{modify_algorithms,[{rm,[{kex,[K1b]}]}, % N.B.
+ {append, [{kex,[K2a]}]}]},
+ {silently_accept_hosts, true},
+ {user_interaction, false}
+ ],
+ {ok,C2} = rpc:call(Node, ssh, connect, [loopback, 22, C2_Opts]),
+ {algorithms,As2} = rpc:call(Node, ssh, connection_info, [C2, algorithms]),
+ K2a = proplists:get_value(kex, As2),
+ Ch1 = proplists:get_value(encrypt, As2),
+ Ch1 = proplists:get_value(decrypt, As2),
+ {options,Os2} = rpc:call(Node, ssh, connection_info, [C2, options]),
+ ct:log("C2 opts:~n~p~n~nalgorithms:~n~p~n~noptions:~n~p", [C2_Opts,As2,Os2]),
+
+ stop_node_nice(Node)
+ end.
+
+%%%----------------------------------------------------------------
+config_file_modify_algorithms_order(Config) ->
+ %% First find common algs:
+ ServerAlgs = ssh_test_lib:default_algorithms(sshd),
+ OurAlgs = ssh_transport:supported_algorithms(), % Incl disabled but supported
+ CommonAlgs = ssh_test_lib:intersection(ServerAlgs, OurAlgs),
+ ct:log("ServerAlgs =~n~p~n~nOurAlgs =~n~p~n~nCommonAlgs =~n~p",[ServerAlgs,OurAlgs,CommonAlgs]),
+ Nkex = length(proplists:get_value(kex, CommonAlgs, [])),
+ case {ServerAlgs, ssh_test_lib:some_empty(CommonAlgs)} of
+ {[],_} ->
+ {skip, "No server algorithms found"};
+ {_,true} ->
+ {fail, "Missing common algorithms"};
+ _ when Nkex<3 ->
+ {skip, "Not enough number of common kex"};
+ _ ->
+ %% Then find three common kex and one common cipher:
+ [K1,K2,K3|_] = proplists:get_value(kex, CommonAlgs),
+ [{_,[Ch1|_]}|_] = proplists:get_value(cipher, CommonAlgs),
+
+ %% Make config file:
+ Contents =
+ [{ssh, [{preferred_algorithms,
+ [{cipher, [Ch1]},
+ {kex, [K1]}
+ ]},
+ {server_options,
+ [{modify_algorithms,
+ [{rm, [{kex, [K1]}]},
+ {append, [{kex, [K2]}]}
+ ]}
+ ]},
+ {client_options,
+ [{modify_algorithms,
+ [{rm, [{kex, [K1]}]},
+ {append, [{kex, [K3]}]}
+ ]}
+ ]}
+ ]}
+ ],
+ %% write the file:
+ PrivDir = proplists:get_value(priv_dir, Config),
+ ConfFile = filename:join(PrivDir,"c3.config"),
+ {ok,D} = file:open(ConfFile, [write]),
+ io:format(D, "~p.~n", [Contents]),
+ file:close(D),
+ {ok,Cnfs} = file:read_file(ConfFile),
+ ct:log("c3.config:~n~s", [Cnfs]),
+
+ %% Start the slave node with the configuration just made:
+ {ok,Node} = start_node(random_node_name(?MODULE), ConfFile),
+
+ R0 = rpc:call(Node, ssh, default_algorithms, []),
+ ct:log("R0 = ~p",[R0]),
+ R0 = ssh:default_algorithms(),
+
+ %% Start ssh on the slave. This should apply the ConfFile:
+ ok = rpc:call(Node, ssh, start, []),
+ R1 = rpc:call(Node, ssh, default_algorithms, []),
+ ct:log("R1 = ~p",[R1]),
+ [{kex,[K1]} | _] = R1,
+
+ %% Start a daemon
+ {Server, Host, Port} = rpc:call(Node, ssh_test_lib, std_daemon, [Config, []]),
+ {ok,ServerInfo} = rpc:call(Node, ssh, daemon_info, [Server]),
+ ct:log("ServerInfo =~n~p", [ServerInfo]),
+
+ %% Test that the server_options env key works:
+ [K2] = proplists:get_value(kex,
+ proplists:get_value(preferred_algorithms,
+ proplists:get_value(options, ServerInfo))),
+
+ {badrpc, {'EXIT', {{badmatch,ExpectedError}, _}}} =
+ %% No common kex algorithms expected.
+ rpc:call(Node, ssh_test_lib, std_connect, [Config, Host, Port, []]),
+ {error,"Key exchange failed"} = ExpectedError,
+
+ C = rpc:call(Node, ssh_test_lib, std_connect,
+ [Config, Host, Port,
+ [{modify_algorithms,[{append,[{kex,[K2]}]}]}]]),
+ ConnInfo = rpc:call(Node, ssh, connection_info, [C]),
+ ct:log("ConnInfo =~n~p", [ConnInfo]),
+ Algs = proplists:get_value(algorithms, ConnInfo),
+ ct:log("Algs =~n~p", [Algs]),
+ ConnOptions = proplists:get_value(options, ConnInfo),
+ ConnPrefAlgs = proplists:get_value(preferred_algorithms, ConnOptions),
+
+ %% And now, are all levels appied in right order:
+ [K3,K2] = proplists:get_value(kex, ConnPrefAlgs),
+
+ stop_node_nice(Node)
+ end.
+
+
+%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
+
+start_node(Name, ConfigFile) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ test_server:start_node(Name, slave, [{args,
+ " -pa " ++ Pa ++
+ " -config " ++ ConfigFile}]).
+
+stop_node_nice(Node) when is_atom(Node) ->
+ test_server:stop_node(Node).
+
+random_node_name(BaseName) ->
+ L = integer_to_list(erlang:unique_integer([positive])),
+ lists:concat([BaseName,"___",L]).
+
+%%%----
expected_ssh_vsn(Str) ->
try
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_dsa b/lib/ssh/test/ssh_options_SUITE_data/id_dsa
index d306f8b26e..24628e071b 100644
--- a/lib/ssh/test/ssh_options_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256
new file mode 100644
index 0000000000..4b1eb12eaa
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49
+AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br
+N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub
new file mode 100644
index 0000000000..a0147e60fa
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384
new file mode 100644
index 0000000000..4e8aa40959
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S
+PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd
+ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF
+8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub
new file mode 100644
index 0000000000..41e722e545
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521
new file mode 100644
index 0000000000..7196f46e97
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q
+9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5
+01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7
+XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf
+pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub
new file mode 100644
index 0000000000..8f059120bc
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519
new file mode 100644
index 0000000000..401a3e4a9a
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us
+aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw
+AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L
+ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub
new file mode 100644
index 0000000000..a5c03b19c1
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed448 b/lib/ssh/test/ssh_options_SUITE_data/id_ed448
new file mode 100644
index 0000000000..8ecfd710dc
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed448
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh
+1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf
+fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32
+BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ
+m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub
new file mode 100644
index 0000000000..cec0765a5d
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_rsa b/lib/ssh/test/ssh_options_SUITE_data/id_rsa
index 9d7e0dd5fb..2202c2ead8 100644
--- a/lib/ssh/test/ssh_options_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_property_test_SUITE.erl b/lib/ssh/test/ssh_property_test_SUITE.erl
index e927aef5a9..a091b15228 100644
--- a/lib/ssh/test/ssh_property_test_SUITE.erl
+++ b/lib/ssh/test/ssh_property_test_SUITE.erl
@@ -22,18 +22,27 @@
%%% Run like this:
%%% ct:run_test([{suite,"ssh_property_test_SUITE"}, {logdir,"/ldisk/OTP/LOG"}]).
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%% %%%
-%%% WARNING %%%
-%%% %%%
-%%% This is experimental code which may be changed or removed %%%
-%%% anytime without any warning. %%%
-%%% %%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-module(ssh_property_test_SUITE).
--compile(export_all).
+-export([
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ client_sends_info_timing/1,
+ client_server_parallel/1,
+ client_server_parallel_multi/1,
+ client_server_sequential/1,
+ decode/1,
+ decode_encode/1
+ ]).
-include_lib("common_test/include/ct.hrl").
-include("ssh_test_lib.hrl").
diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
index dc94ad4ba2..ab854e4282 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -23,13 +23,60 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet.hrl").
--include_lib("ssh/src/ssh.hrl"). % ?UINT32, ?BYTE, #ssh{} ...
--include_lib("ssh/src/ssh_transport.hrl").
--include_lib("ssh/src/ssh_auth.hrl").
+-include("ssh.hrl"). % ?UINT32, ?BYTE, #ssh{} ...
+-include("ssh_transport.hrl").
+-include("ssh_auth.hrl").
-include("ssh_test_lib.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ bad_long_service_name/1,
+ bad_packet_length/2,
+ bad_service_name/1,
+ bad_service_name/2,
+ bad_service_name_length/2,
+ bad_service_name_then_correct/1,
+ bad_very_long_service_name/1,
+ client_handles_keyboard_interactive_0_pwds/1,
+ client_info_line/1,
+ do_gex_client_init/3,
+ do_gex_client_init_old/3,
+ empty_service_name/1,
+ ext_info_c/1,
+ ext_info_s/1,
+ gex_client_init_option_groups/1,
+ gex_client_init_option_groups_file/1,
+ gex_client_init_option_groups_moduli_file/1,
+ gex_client_old_request_exact/1,
+ gex_client_old_request_noexact/1,
+ gex_server_gex_limit/1,
+ lib_match/1,
+ lib_no_match/1,
+ lib_works_as_client/1,
+ lib_works_as_server/1,
+ modify_append/1,
+ modify_combo/1,
+ modify_prepend/1,
+ modify_rm/1,
+ no_common_alg_client_disconnects/1,
+ no_common_alg_server_disconnects/1,
+ no_ext_info_s1/1,
+ no_ext_info_s2/1,
+ packet_length_too_large/1,
+ packet_length_too_short/1,
+ preferred_algorithms/1,
+ service_name_length_too_large/1,
+ service_name_length_too_short/1
+ ]).
-define(NEWLINE, <<"\r\n">>).
-define(REKEY_DATA_TMO, 65000).
@@ -419,7 +466,7 @@ do_gex_client_init(Config, {Min,N,Max}, {G,P}) ->
[{silently_accept_hosts, true},
{user_dir, user_dir(Config)},
{user_interaction, false},
- {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha1']},
+ {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha256']},
{cipher,?DEFAULT_CIPHERS}
]}
]},
@@ -454,7 +501,7 @@ do_gex_client_init_old(Config, N, {G,P}) ->
[{silently_accept_hosts, true},
{user_dir, user_dir(Config)},
{user_interaction, false},
- {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha1']},
+ {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha256']},
{cipher,?DEFAULT_CIPHERS}
]}
]},
@@ -907,10 +954,8 @@ stop_apps(_Config) ->
setup_dirs(Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
Config.
system_dir(Config) -> filename:join(proplists:get_value(priv_dir, Config), system).
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa
index d306f8b26e..24628e071b 100644
--- a/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256
new file mode 100644
index 0000000000..4b1eb12eaa
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49
+AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br
+N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub
new file mode 100644
index 0000000000..a0147e60fa
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384
new file mode 100644
index 0000000000..4e8aa40959
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S
+PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd
+ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF
+8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub
new file mode 100644
index 0000000000..41e722e545
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521
new file mode 100644
index 0000000000..7196f46e97
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q
+9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5
+01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7
+XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf
+pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub
new file mode 100644
index 0000000000..8f059120bc
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519
new file mode 100644
index 0000000000..401a3e4a9a
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us
+aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw
+AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L
+ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub
new file mode 100644
index 0000000000..a5c03b19c1
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448
new file mode 100644
index 0000000000..8ecfd710dc
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh
+1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf
+fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32
+BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ
+m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub
new file mode 100644
index 0000000000..cec0765a5d
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa
index 9d7e0dd5fb..2202c2ead8 100644
--- a/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_pubkey_SUITE.erl b/lib/ssh/test/ssh_pubkey_SUITE.erl
new file mode 100644
index 0000000000..481666d25e
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE.erl
@@ -0,0 +1,423 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(ssh_pubkey_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ connect_dsa_to_dsa/1,
+ connect_dsa_to_ecdsa/1,
+ connect_dsa_to_ed25519/1,
+ connect_dsa_to_ed448/1,
+ connect_dsa_to_rsa/1,
+ connect_ecdsa_to_dsa/1,
+ connect_ecdsa_to_ecdsa/1,
+ connect_ecdsa_to_ed25519/1,
+ connect_ecdsa_to_ed448/1,
+ connect_ecdsa_to_rsa/1,
+ connect_ed25519_to_dsa/1,
+ connect_ed25519_to_ecdsa/1,
+ connect_ed25519_to_ed25519/1,
+ connect_ed25519_to_ed448/1,
+ connect_ed25519_to_rsa/1,
+ connect_ed448_to_dsa/1,
+ connect_ed448_to_ecdsa/1,
+ connect_ed448_to_ed25519/1,
+ connect_ed448_to_ed448/1,
+ connect_ed448_to_rsa/1,
+ connect_rsa_to_dsa/1,
+ connect_rsa_to_ecdsa/1,
+ connect_rsa_to_ed25519/1,
+ connect_rsa_to_ed448/1,
+ connect_rsa_to_rsa/1
+ ]).
+
+-include_lib("common_test/include/ct.hrl").
+-include("ssh_test_lib.hrl").
+
+%%%----------------------------------------------------------------
+%%% Common Test interface functions -------------------------------
+%%%----------------------------------------------------------------
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{seconds,40}}].
+
+all() ->
+ [{group, old_format},
+ {group, new_format},
+ {group, option_space}
+ ].
+
+
+-define(tests_old, [connect_rsa_to_rsa,
+ connect_rsa_to_dsa,
+ connect_rsa_to_ecdsa,
+ connect_dsa_to_rsa,
+ connect_dsa_to_dsa,
+ connect_dsa_to_ecdsa,
+ connect_ecdsa_to_rsa,
+ connect_ecdsa_to_dsa,
+ connect_ecdsa_to_ecdsa
+ ]).
+
+-define(tests_new, [
+ connect_dsa_to_ed25519,
+ connect_dsa_to_ed448,
+ connect_ecdsa_to_ed25519,
+ connect_ecdsa_to_ed448,
+ connect_ed25519_to_dsa,
+ connect_ed25519_to_ecdsa,
+ connect_ed25519_to_ed448,
+ connect_ed25519_to_ed25519,
+ connect_ed25519_to_rsa,
+ connect_ed448_to_dsa,
+ connect_ed448_to_ecdsa,
+ connect_ed448_to_ed25519,
+ connect_ed448_to_ed448,
+ connect_ed448_to_rsa,
+ connect_rsa_to_ed25519,
+ connect_rsa_to_ed448
+ | ?tests_old % but taken from the new format directory
+ ]).
+
+groups() ->
+ [{new_format, [], ?tests_new},
+ {old_format, [], ?tests_old++[{group,passphrase}]},
+ {passphrase, [], ?tests_old},
+ {option_space,[], [{group,new_format}]}
+ ].
+
+%%%----------------------------------------------------------------
+init_per_suite(Config) ->
+ ?CHECK_CRYPTO(
+ begin
+ ssh:start(),
+ [{client_opts,[]},
+ {daemon_opts,[]}
+ | Config]
+ end).
+
+end_per_suite(_onfig) ->
+ ssh:stop().
+
+%%%----------------------------------------------------------------
+init_per_group(new_format, Config) ->
+ Dir = filename:join(proplists:get_value(data_dir,Config), "new_format"),
+ [{fmt,new_format},
+ {key_src_dir,Dir} | Config];
+
+init_per_group(old_format, Config) ->
+ Dir = filename:join(proplists:get_value(data_dir,Config), "old_format"),
+ [{fmt,old_format},
+ {key_src_dir,Dir} | Config];
+
+init_per_group(option_space, Config) ->
+ extend_optsL([client_opts,daemon_opts],
+ [{key_cb, {ssh_file, [{optimize, space}]}}],
+ Config);
+
+init_per_group(passphrase, Config0) ->
+ case supported(hashs, md5) of
+ true ->
+ Dir = filename:join(proplists:get_value(data_dir,Config0), "old_format_passphrase"),
+ PassPhrases = [{K,"somepwd"} || K <- [dsa_pass_phrase,
+ rsa_pass_phrase,
+ ecdsa_pass_phrase]],
+ Config1 = extend_optsL(client_opts, PassPhrases, Config0),
+ replace_opt(key_src_dir, Dir, Config1);
+ false ->
+ {skip, "Unsupported hash"}
+ end;
+
+init_per_group(_, Config) ->
+ Config.
+
+
+extend_optsL(OptNames, Values, Config) when is_list(OptNames) ->
+ lists:foldl(fun(N, Cnf) ->
+ extend_optsL(N, Values, Cnf)
+ end, Config, OptNames);
+extend_optsL(OptName, Values, Config) when is_atom(OptName) ->
+ Opts = proplists:get_value(OptName, Config),
+ replace_opt(OptName, Values ++ Opts, Config).
+
+replace_opt(OptName, Value, Config) ->
+ lists:keyreplace(OptName, 1, Config, {OptName,Value}).
+
+
+
+end_per_group(_, Config) ->
+ Config.
+
+%%%----------------------------------------------------------------
+init_per_testcase(connect_rsa_to_rsa, Config0) ->
+ setup_user_system_dir(rsa, rsa, Config0);
+init_per_testcase(connect_rsa_to_dsa, Config0) ->
+ setup_user_system_dir(rsa, dsa, Config0);
+init_per_testcase(connect_rsa_to_ecdsa, Config0) ->
+ setup_user_system_dir(rsa, ecdsa, Config0);
+init_per_testcase(connect_rsa_to_ed25519, Config0) ->
+ setup_user_system_dir(rsa, ed25519, Config0);
+init_per_testcase(connect_rsa_to_ed448, Config0) ->
+ setup_user_system_dir(rsa, ed448, Config0);
+init_per_testcase(connect_dsa_to_rsa, Config0) ->
+ setup_user_system_dir(dsa, rsa, Config0);
+init_per_testcase(connect_dsa_to_dsa, Config0) ->
+ setup_user_system_dir(dsa, dsa, Config0);
+init_per_testcase(connect_dsa_to_ecdsa, Config0) ->
+ setup_user_system_dir(dsa, ecdsa, Config0);
+init_per_testcase(connect_dsa_to_ed25519, Config0) ->
+ setup_user_system_dir(dsa, ed25519, Config0);
+init_per_testcase(connect_dsa_to_ed448, Config0) ->
+ setup_user_system_dir(dsa, ed448, Config0);
+init_per_testcase(connect_ecdsa_to_rsa, Config0) ->
+ setup_user_system_dir(ecdsa, rsa, Config0);
+init_per_testcase(connect_ecdsa_to_dsa, Config0) ->
+ setup_user_system_dir(ecdsa, dsa, Config0);
+init_per_testcase(connect_ecdsa_to_ecdsa, Config0) ->
+ setup_user_system_dir(ecdsa, ecdsa, Config0);
+init_per_testcase(connect_ecdsa_to_ed25519, Config0) ->
+ setup_user_system_dir(ecdsa, ed25519, Config0);
+init_per_testcase(connect_ecdsa_to_ed448, Config0) ->
+ setup_user_system_dir(ecdsa, ed448, Config0);
+init_per_testcase(connect_ed25519_to_rsa, Config0) ->
+ setup_user_system_dir(ed25519, rsa, Config0);
+init_per_testcase(connect_ed25519_to_dsa, Config0) ->
+ setup_user_system_dir(ed25519, dsa, Config0);
+init_per_testcase(connect_ed25519_to_ecdsa, Config0) ->
+ setup_user_system_dir(ed25519, ecdsa, Config0);
+init_per_testcase(connect_ed25519_to_ed25519, Config0) ->
+ setup_user_system_dir(ed25519, ed25519, Config0);
+init_per_testcase(connect_ed25519_to_ed448, Config0) ->
+ setup_user_system_dir(ed25519, ed448, Config0);
+init_per_testcase(connect_ed448_to_rsa, Config0) ->
+ setup_user_system_dir(ed448, rsa, Config0);
+init_per_testcase(connect_ed448_to_dsa, Config0) ->
+ setup_user_system_dir(ed448, dsa, Config0);
+init_per_testcase(connect_ed448_to_ecdsa, Config0) ->
+ setup_user_system_dir(ed448, ecdsa, Config0);
+init_per_testcase(connect_ed448_to_ed25519, Config0) ->
+ setup_user_system_dir(ed448, ed25519, Config0);
+init_per_testcase(connect_ed448_to_ed448, Config0) ->
+ setup_user_system_dir(ed448, ed448, Config0);
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(_, Config) ->
+ Config.
+
+%%%----------------------------------------------------------------
+%%% Test Cases ----------------------------------------------------
+%%%----------------------------------------------------------------
+connect_rsa_to_rsa(Config) ->
+ try_connect(Config).
+
+connect_rsa_to_dsa(Config) ->
+ try_connect(Config).
+
+connect_rsa_to_ecdsa(Config) ->
+ try_connect(Config).
+
+connect_rsa_to_ed25519(Config) ->
+ try_connect(Config).
+
+connect_rsa_to_ed448(Config) ->
+ try_connect(Config).
+
+connect_dsa_to_rsa(Config) ->
+ try_connect(Config).
+
+connect_dsa_to_dsa(Config) ->
+ try_connect(Config).
+
+connect_dsa_to_ecdsa(Config) ->
+ try_connect(Config).
+
+connect_dsa_to_ed25519(Config) ->
+ try_connect(Config).
+
+connect_dsa_to_ed448(Config) ->
+ try_connect(Config).
+
+connect_ecdsa_to_rsa(Config) ->
+ try_connect(Config).
+
+connect_ecdsa_to_dsa(Config) ->
+ try_connect(Config).
+
+connect_ecdsa_to_ecdsa(Config) ->
+ try_connect(Config).
+
+connect_ecdsa_to_ed25519(Config) ->
+ try_connect(Config).
+
+connect_ecdsa_to_ed448(Config) ->
+ try_connect(Config).
+
+connect_ed25519_to_rsa(Config) ->
+ try_connect(Config).
+
+connect_ed25519_to_dsa(Config) ->
+ try_connect(Config).
+
+connect_ed25519_to_ecdsa(Config) ->
+ try_connect(Config).
+
+connect_ed25519_to_ed25519(Config) ->
+ try_connect(Config).
+
+connect_ed25519_to_ed448(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_rsa(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_dsa(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_ecdsa(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_ed25519(Config) ->
+ try_connect(Config).
+
+connect_ed448_to_ed448(Config) ->
+ try_connect(Config).
+
+
+%%%----------------------------------------------------------------
+try_connect({skip,Reson}) ->
+ {skip,Reson};
+try_connect(Config) ->
+ SystemDir = proplists:get_value(system_dir, Config),
+ UserDir = proplists:get_value(user_dir, Config),
+ ClientOpts = proplists:get_value(client_opts, Config, []),
+ DaemonOpts = proplists:get_value(daemon_opts, Config, []),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir}
+ | DaemonOpts]),
+
+ C = ssh_test_lib:connect(Host, Port, [{user_dir, UserDir},
+ {silently_accept_hosts, true},
+ {user_interaction, false}
+ | ClientOpts]),
+ ssh:close(C),
+ ssh:stop_daemon(Pid).
+
+%%%----------------------------------------------------------------
+%%% Local ---------------------------------------------------------
+%%%----------------------------------------------------------------
+setup_user_system_dir(ClientAlg, ServerAlg, Config) ->
+ case supported(public_keys, ClientAlg) andalso supported(public_keys, ServerAlg) of
+ true ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ KeySrcDir = proplists:get_value(key_src_dir, Config),
+ Fmt = proplists:get_value(fmt, Config),
+
+ System = lists:concat(["system_", ClientAlg, "_", ServerAlg, "_", Fmt]),
+ SystemDir = filename:join(PrivDir, System),
+ file:make_dir(SystemDir),
+
+ User = lists:concat(["user_", ClientAlg, "_", ServerAlg, "_", Fmt]),
+ UserDir = filename:join(PrivDir, User),
+ file:make_dir(UserDir),
+
+ HostSrcFile = filename:join(KeySrcDir, file(host,ServerAlg)),
+ HostDstFile = filename:join(SystemDir, file(host,ServerAlg)),
+
+ UserSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)),
+ UserDstFile = filename:join(UserDir, file(user,ClientAlg)),
+
+ UserPubSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)++".pub"),
+ AuthorizedKeys = filename:join(UserDir, "authorized_keys"),
+
+ try
+ {ok,_} = file:copy(UserSrcFile, UserDstFile),
+ {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys),
+ {ok,_} = file:copy(HostSrcFile, HostDstFile)
+ of
+ _ ->
+ ModAlgs = [{modify_algorithms,
+ [{append,[{public_key,
+ lists:usort([alg(ClientAlg),
+ alg(ServerAlg)])}]}]}
+ ],
+ [{system_dir,SystemDir},
+ {user_dir,UserDir}
+ | extend_optsL([daemon_opts,client_opts], ModAlgs, Config)]
+ catch
+ error:{badmatch,{error,enoent}}:S ->
+ ct:log("~p:~p Stack:~n~p", [?MODULE,?LINE,S]),
+ {skip, no_key_file_found}
+ end;
+
+ false ->
+ {skip, unsupported_algorithm}
+ end.
+
+%%%----------------------------------------------------------------
+file(host, dsa) -> "ssh_host_dsa_key";
+file(host, ecdsa) -> "ssh_host_ecdsa_key";
+file(host, ed25519) -> "ssh_host_ed25519_key";
+file(host, ed448) -> "ssh_host_ed448_key";
+file(host, rsa) -> "ssh_host_rsa_key";
+file(user, dsa) -> "id_dsa";
+file(user, ecdsa) -> "id_ecdsa";
+file(user, ed25519) -> "id_ed25519";
+file(user, ed448) -> "id_ed448";
+file(user, rsa) -> "id_rsa".
+
+alg(dsa) -> 'ssh-dss';
+alg(ecdsa) -> 'ecdsa-sha2-nistp256';
+alg(ed25519) -> 'ssh-ed25519';
+alg(ed448) -> 'ssh-ed448';
+alg(rsa) -> 'ssh-rsa'.
+
+
+supported(public_keys, rsa) -> supported(public_key, 'ssh-rsa') orelse
+ supported(public_key, 'rsa-sha2-256') orelse
+ supported(public_key, 'rsa-sha2-521');
+supported(public_keys, dsa) -> supported(public_key, 'ssh-dss');
+supported(public_keys, ecdsa) -> supported(public_key, 'ecdsa-sha2-nistp256') orelse
+ supported(public_key, 'ecdsa-sha2-nistp384') orelse
+ supported(public_key, 'ecdsa-sha2-nistp521');
+supported(public_keys, ed448) -> supported(public_key, 'ssh-ed448');
+supported(public_keys, ed25519) -> supported(public_key, 'ssh-ed25519');
+supported(Type, Alg) ->
+ case proplists:get_value(Type,ssh_transport:supported_algorithms()) of
+ undefined ->
+ lists:member(Alg, crypto:supports(Type));
+ L ->
+ lists:member(Alg, L)
+ end.
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa
new file mode 100644
index 0000000000..5167322957
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa
@@ -0,0 +1,21 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsgAAAAdzc2gtZH
+NzAAAAgQDsCchYjb27VZTDJuPwnYQQYZBCnQlVROAfDHa1dmAIQswOm/xFu5RyVpYCQPOY
+Y2OBdEibM7/FzaAJUs9gRLfbcCHA9jYy8zjag5KijtPJe9BxoDPAM8bSEFfLDklebS8NDZ
+tZvHsSz4UwagBQKNvoTPjljsf7fgjaQ9735S2jmwAAABUAiLuwAwdJr1qlGSmJSFqeM0Ao
+18UAAACAMA+NIBNjhzYr4nIhWv1x0TYZ8OldFIEh5iUDRf53ZxcdCloxtfNpZjYJbwEwLK
+UUW6xcPEp7/nCVOd50Yk4HgaDV5YvHwhS7g2yFXoK2gHKSa42BfWR4c8fPoMfapWSJ0aQU
+xGqebjTDeavwJq5umZCbk9/MfIcSctT9Pn88BncAAACBANMOLq9WhZs3LtechqTpFmXgzQ
+zjtoqFYbOd1ERDMXBffyS12aAlrJ1uUTKA1P/XVrIUMNuuXapWY6QwmqSOFVD58QQx9Z4l
+itZUu4H+lOSVPHpJrMq45hqmr4Mu35ENtxzNkfQI544/QLlfqkXJ3SME2tUUXAHvTHEnUY
+UvvteXAAAB8BiO51oYjudaAAAAB3NzaC1kc3MAAACBAOwJyFiNvbtVlMMm4/CdhBBhkEKd
+CVVE4B8MdrV2YAhCzA6b/EW7lHJWlgJA85hjY4F0SJszv8XNoAlSz2BEt9twIcD2NjLzON
+qDkqKO08l70HGgM8AzxtIQV8sOSV5tLw0Nm1m8exLPhTBqAFAo2+hM+OWOx/t+CNpD3vfl
+LaObAAAAFQCIu7ADB0mvWqUZKYlIWp4zQCjXxQAAAIAwD40gE2OHNiviciFa/XHRNhnw6V
+0UgSHmJQNF/ndnFx0KWjG182lmNglvATAspRRbrFw8Snv+cJU53nRiTgeBoNXli8fCFLuD
+bIVegraAcpJrjYF9ZHhzx8+gx9qlZInRpBTEap5uNMN5q/Amrm6ZkJuT38x8hxJy1P0+fz
+wGdwAAAIEA0w4ur1aFmzcu15yGpOkWZeDNDOO2ioVhs53UREMxcF9/JLXZoCWsnW5RMoDU
+/9dWshQw265dqlZjpDCapI4VUPnxBDH1niWK1lS7gf6U5JU8ekmsyrjmGqavgy7fkQ23HM
+2R9Ajnjj9AuV+qRcndIwTa1RRcAe9McSdRhS++15cAAAAUfRVtNnUPI+lhQgeu4d1i+H8x
+Y9UAAAATdWFiaG5pbEBlbHhhZGxqM3EzMgECAwQFBgc=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa.pub
new file mode 100644
index 0000000000..2f155b6ac9
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAOwJyFiNvbtVlMMm4/CdhBBhkEKdCVVE4B8MdrV2YAhCzA6b/EW7lHJWlgJA85hjY4F0SJszv8XNoAlSz2BEt9twIcD2NjLzONqDkqKO08l70HGgM8AzxtIQV8sOSV5tLw0Nm1m8exLPhTBqAFAo2+hM+OWOx/t+CNpD3vflLaObAAAAFQCIu7ADB0mvWqUZKYlIWp4zQCjXxQAAAIAwD40gE2OHNiviciFa/XHRNhnw6V0UgSHmJQNF/ndnFx0KWjG182lmNglvATAspRRbrFw8Snv+cJU53nRiTgeBoNXli8fCFLuDbIVegraAcpJrjYF9ZHhzx8+gx9qlZInRpBTEap5uNMN5q/Amrm6ZkJuT38x8hxJy1P0+fzwGdwAAAIEA0w4ur1aFmzcu15yGpOkWZeDNDOO2ioVhs53UREMxcF9/JLXZoCWsnW5RMoDU/9dWshQw265dqlZjpDCapI4VUPnxBDH1niWK1lS7gf6U5JU8ekmsyrjmGqavgy7fkQ23HM2R9Ajnjj9AuV+qRcndIwTa1RRcAe9McSdRhS++15c= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa
new file mode 100644
index 0000000000..16f8d330e9
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa
@@ -0,0 +1,9 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
+1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQ6WBbp8A+ypG22sM4pqd+973IoBF9a
+TcWU237H8NQRf4BbAKlA+NFfi5XHYrRtpW7XCTkdTQn1jdayyq8RtYaGAAAAsHHJLVBxyS
+1QAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDpYFunwD7Kkbbaw
+zimp373vcigEX1pNxZTbfsfw1BF/gFsAqUD40V+LlcditG2lbtcJOR1NCfWN1rLKrxG1ho
+YAAAAhAIDK4KZm7jhx4e0gRH/DlLg8WYu+BUDNjmMAgcMLsDv9AAAAE3VhYmhuaWxAZWx4
+YWRsajNxMzIBAgME
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa.pub
new file mode 100644
index 0000000000..162ebd3fb0
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ecdsa.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDpYFunwD7Kkbbawzimp373vcigEX1pNxZTbfsfw1BF/gFsAqUD40V+LlcditG2lbtcJOR1NCfWN1rLKrxG1hoY= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519 b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519
new file mode 100644
index 0000000000..e2b83c0800
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBo2p2XftaIjxYLqNQeIcDrdejVxhTX+db3O5UFV5XdLwAAAJhZQvGOWULx
+jgAAAAtzc2gtZWQyNTUxOQAAACBo2p2XftaIjxYLqNQeIcDrdejVxhTX+db3O5UFV5XdLw
+AAAECiqNZLTp3o73H+B1VseAJKhEiGXf4otOH461y+sAwBCmjanZd+1oiPFguo1B4hwOt1
+6NXGFNf51vc7lQVXld0vAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519.pub
new file mode 100644
index 0000000000..4d2f9f3677
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGjanZd+1oiPFguo1B4hwOt16NXGFNf51vc7lQVXld0v uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448 b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448
new file mode 100644
index 0000000000..fc8e79e8f0
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448
@@ -0,0 +1,15 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+A: Key
+SomeKey: Very long \
+line \
+spanning more lines \
+
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa
new file mode 100644
index 0000000000..243c17cfd2
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAQEA6helL/aMwpp5cHOCZ5bB8tkpgiXykiV0U61t6K4h7fdud8zOyI9e
+N/H9l787QkzOoLBJxzFvTOYcqsC1CMYKNiCvPEtFDZyfD6tZv03nEOc3VZhEO9+pKmnW3y
+/7xjRekEW3bqyEk3GkMhnDFfm3JILtZmdRrooXCkVGZaVxELm8N+cEtAE8TCbOIOo1mjsv
+x+R8bGktx057EZ8Ieg8tFWh/atFemHj75ZILjY795ji5YQHBHfDUBAwNNY5lCsue4COT6Z
+Udv03JgwnCVTu+XsXqH6yemjefU7nvWktudnaKGbg5OS/H2pDrm6ivjRtGdz0gkMiD9miw
+Q2n03cFThQAAA8h66iCmeuogpgAAAAdzc2gtcnNhAAABAQDqF6Uv9ozCmnlwc4JnlsHy2S
+mCJfKSJXRTrW3oriHt9253zM7Ij1438f2XvztCTM6gsEnHMW9M5hyqwLUIxgo2IK88S0UN
+nJ8Pq1m/TecQ5zdVmEQ736kqadbfL/vGNF6QRbdurISTcaQyGcMV+bckgu1mZ1GuihcKRU
+ZlpXEQubw35wS0ATxMJs4g6jWaOy/H5HxsaS3HTnsRnwh6Dy0VaH9q0V6YePvlkguNjv3m
+OLlhAcEd8NQEDA01jmUKy57gI5PplR2/TcmDCcJVO75exeofrJ6aN59Tue9aS252dooZuD
+k5L8fakOubqK+NG0Z3PSCQyIP2aLBDafTdwVOFAAAAAwEAAQAAAQA0vFbuUzCqtnodJyh9
+hazztJBxTXM0EVP/ddaI0JG8Nj2gp3b+H64uFEn44Y/MA9mYwZ4dTbmxLTXQEdG2xEaQox
+RXFO3dfycmNIfnXPltCWmh0sesZVqKv4U0im7B3BJhlhMYz6yeOr+uubcFQFhN1WD97NCt
+7VX7blfJlle+WGsH6IB728MSlo+pU7desTPaWIamsDTftUkzrIVvgbppmk79XX/zhObIi/
+8xR3cCxygq3LM31LwTOcEnRvMufJOv3lR0ybUE5INtJVYYpqZ2hFaNON+hOTxaAFZ/pJJ1
+zm5XTb9HWSaouLlalzkQONCTucp7qU7PnBnIB/nQ5A0BAAAAgDO683uK2WICRSr2J7E1Q3
+jQ+GqU0c4SslORQ4mwjMbJVMyQwd4U00ctex4XIoQdB6dn17jmvEJa7UiH0+TMm9cJefoL
+V8G4O4XThEDIBtF7Kp1oKV9roxVdJ/iS7psMop3g9X+/9L4nOUMxlI21j7YKf3C5KJwhD3
+RAqyE/YClnAAAAgQD6BacnxjxmcFJdCevQcfIhSCwzXp7fjYWK46U9fBF5k+H+GUicfaL6
+LGp0DDBhhpQ/lAzNO5IMn6pPAfl0ZSOsGie9OszSpMujxG1fSTDeUqyf0hL+t7yVrCRTEc
+cyl5t0JBSUyoHy0w1B3pXF2vjRtrmqFvTmTrWYJAHxbPePsQAAAIEA77B9KyiCwX8AKubN
+AA+nRRdmJfju7yM0xiHO6RfOgSnp9EAMKyoUdVFY1yddxgTQvpGSSigBogJHLB2ptGtmTE
+6DtIEtP0SDQGso/Q51AhTtk4ScYlPvZJiXVfRmyibVdxmQqa8+aJcqS0j1cReSmAu5zRQm
+zW0/752JsHk3qhUAAAATdWFiaG5pbEBlbHhhZGxqM3EzMg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa.pub
new file mode 100644
index 0000000000..3042323a71
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDqF6Uv9ozCmnlwc4JnlsHy2SmCJfKSJXRTrW3oriHt9253zM7Ij1438f2XvztCTM6gsEnHMW9M5hyqwLUIxgo2IK88S0UNnJ8Pq1m/TecQ5zdVmEQ736kqadbfL/vGNF6QRbdurISTcaQyGcMV+bckgu1mZ1GuihcKRUZlpXEQubw35wS0ATxMJs4g6jWaOy/H5HxsaS3HTnsRnwh6Dy0VaH9q0V6YePvlkguNjv3mOLlhAcEd8NQEDA01jmUKy57gI5PplR2/TcmDCcJVO75exeofrJ6aN59Tue9aS252dooZuDk5L8fakOubqK+NG0Z3PSCQyIP2aLBDafTdwVOF uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key
new file mode 100644
index 0000000000..071d761e46
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key
@@ -0,0 +1,21 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH
+NzAAAAgQDQp0Dp9ne0IXxq93hFwqQi+iOTNqw0GeJlCQvzSYsnLgPzWCj/4o1R6czLdx4/
+6nNWPJVmbManC0gcBwdcJbVXW7nTbW3xnD2LmzyB9dwjka8CgfC/A87MkslY8YCt0ZHz8f
+/9L9eRqAv5udHlxgtaX1u81VDVPxAk4bOAAyz4AwAAABUA0pu1vOhk0mBbY9cz+uN+ELA2
+20MAAACAW1PJ1Rau3yNLwzldcoejYt4gOKmAoAUWL9F3fp+IKGhYf6Z6GX5OI/98BE2wWu
+3/Kk12K7ZtVyTj2B1JheNjzivsZryNhZwJlCCbFQalkU9C2tApALix2j/PYQy++Hefk8yK
+4qFwSG9DTC/TvMKPjaZA5w3TjIkWuC0tGoqvkkwAAACAIRynnqPLhvlyD0wg5F4v7iG+fr
+d1JfusrjLobdgBz+d60wyu+0IUuaJdhB1z0TjPdxmkwwrbOzmkAhcRbHduV8HTAY/l0Trp
+X8E4b7gzpJwcy/2T1lPx5pIwlcd3TwqwMBQPNOXV8FR2fZYhIARSZy0ccvePc+/XKFmKT6
+jjBCcAAAHwPSMhRT0jIUUAAAAHc3NoLWRzcwAAAIEA0KdA6fZ3tCF8avd4RcKkIvojkzas
+NBniZQkL80mLJy4D81go/+KNUenMy3ceP+pzVjyVZmzGpwtIHAcHXCW1V1u5021t8Zw9i5
+s8gfXcI5GvAoHwvwPOzJLJWPGArdGR8/H//S/XkagL+bnR5cYLWl9bvNVQ1T8QJOGzgAMs
++AMAAAAVANKbtbzoZNJgW2PXM/rjfhCwNttDAAAAgFtTydUWrt8jS8M5XXKHo2LeIDipgK
+AFFi/Rd36fiChoWH+mehl+TiP/fARNsFrt/ypNdiu2bVck49gdSYXjY84r7Ga8jYWcCZQg
+mxUGpZFPQtrQKQC4sdo/z2EMvvh3n5PMiuKhcEhvQ0wv07zCj42mQOcN04yJFrgtLRqKr5
+JMAAAAgCEcp56jy4b5cg9MIOReL+4hvn63dSX7rK4y6G3YAc/netMMrvtCFLmiXYQdc9E4
+z3cZpMMK2zs5pAIXEWx3blfB0wGP5dE66V/BOG+4M6ScHMv9k9ZT8eaSMJXHd08KsDAUDz
+Tl1fBUdn2WISAEUmctHHL3j3Pv1yhZik+o4wQnAAAAFQCWaj2vQN6mnxfVgP8YzlPakzQI
+WAAAABN1YWJobmlsQGVseGFkbGozcTMyAQIDBAUGBw==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..97193b1c58
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_dsa_key.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBANCnQOn2d7QhfGr3eEXCpCL6I5M2rDQZ4mUJC/NJiycuA/NYKP/ijVHpzMt3Hj/qc1Y8lWZsxqcLSBwHB1wltVdbudNtbfGcPYubPIH13CORrwKB8L8DzsySyVjxgK3RkfPx//0v15GoC/m50eXGC1pfW7zVUNU/ECThs4ADLPgDAAAAFQDSm7W86GTSYFtj1zP6434QsDbbQwAAAIBbU8nVFq7fI0vDOV1yh6Ni3iA4qYCgBRYv0Xd+n4goaFh/pnoZfk4j/3wETbBa7f8qTXYrtm1XJOPYHUmF42POK+xmvI2FnAmUIJsVBqWRT0La0CkAuLHaP89hDL74d5+TzIrioXBIb0NML9O8wo+NpkDnDdOMiRa4LS0aiq+STAAAAIAhHKeeo8uG+XIPTCDkXi/uIb5+t3Ul+6yuMuht2AHP53rTDK77QhS5ol2EHXPROM93GaTDCts7OaQCFxFsd25XwdMBj+XROulfwThvuDOknBzL/ZPWU/HmkjCVx3dPCrAwFA805dXwVHZ9liEgBFJnLRxy949z79coWYpPqOMEJw== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key
new file mode 100644
index 0000000000..3d01041bed
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key
@@ -0,0 +1,9 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
+1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQT21rZj/MTJN9fK+jDZ6l0bYF1EndNL
+N+3a4cAYl8jRhRk18s8QbCN3GP+CsFWtor5fRhragFo2X7yPVAVU75FoAAAAsEntb7pJ7W
++6AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPbWtmP8xMk318r6
+MNnqXRtgXUSd00s37drhwBiXyNGFGTXyzxBsI3cY/4KwVa2ivl9GGtqAWjZfvI9UBVTvkW
+gAAAAhAJVvBMRkEfuS8/YZ9PayecITNQ5CfZ5I49z3Ay17cxbbAAAAE3VhYmhuaWxAZWx4
+YWRsajNxMzIBAgME
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key.pub
new file mode 100644
index 0000000000..b125971661
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ecdsa_key.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPbWtmP8xMk318r6MNnqXRtgXUSd00s37drhwBiXyNGFGTXyzxBsI3cY/4KwVa2ivl9GGtqAWjZfvI9UBVTvkWg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key
new file mode 100644
index 0000000000..0ebf1dc8b7
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBKsbrzown8TYrOXlZYV/D+LICEyw23bwjdXRGfm4FEAwAAAJheMIX9XjCF
+/QAAAAtzc2gtZWQyNTUxOQAAACBKsbrzown8TYrOXlZYV/D+LICEyw23bwjdXRGfm4FEAw
+AAAEBxNZQd4RXl1DDYt+bm7WeSXlVmncu/bQ/ubdj56T2xV0qxuvOjCfxNis5eVlhX8P4s
+gITLDbdvCN1dEZ+bgUQDAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..0108498194
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEqxuvOjCfxNis5eVlhX8P4sgITLDbdvCN1dEZ+bgUQD uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key
new file mode 100644
index 0000000000..fc8e79e8f0
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key
@@ -0,0 +1,15 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+A: Key
+SomeKey: Very long \
+line \
+spanning more lines \
+
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key
new file mode 100644
index 0000000000..6d51bf7672
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key
@@ -0,0 +1,27 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAQEAt+8ElruXAw+OXtyGBUMxAkSfsw/A7BWqIzbtRn6hD3YlJPFYBo4O
+0wNeW4g9z+k5u42Q4Sv3gZAPuVAVi/E4MEd3eg8ZRkkC674a5lUvEzIrDYPbkhzR5bLidG
+0TK4wdqL/HRGE9gpTIaOcIDkK2RI2PeRhjvjscUHTsa/Kx6dwBiKOK5vjIXA2MlsTgVY7N
+MSpjR/sH/TG6UZXxhlgmBGsrpVsVM/3kQomhVVoMn1yRG6Wz2Ed+b6ENYKNmuomeIoCZoU
++uE0RMqiE5qD32YVExt1B89CVAR3GbQKKTop+Pb1xkNMnYvCpDo8+68dZ7U7zJ1SZr00yd
+G0M4pFntDwAAA9At3ILHLdyCxwAAAAdzc2gtcnNhAAABAQC37wSWu5cDD45e3IYFQzECRJ
++zD8DsFaojNu1GfqEPdiUk8VgGjg7TA15biD3P6Tm7jZDhK/eBkA+5UBWL8TgwR3d6DxlG
+SQLrvhrmVS8TMisNg9uSHNHlsuJ0bRMrjB2ov8dEYT2ClMho5wgOQrZEjY95GGO+OxxQdO
+xr8rHp3AGIo4rm+MhcDYyWxOBVjs0xKmNH+wf9MbpRlfGGWCYEayulWxUz/eRCiaFVWgyf
+XJEbpbPYR35voQ1go2a6iZ4igJmhT64TREyqITmoPfZhUTG3UHz0JUBHcZtAopOin49vXG
+Q0ydi8KkOjz7rx1ntTvMnVJmvTTJ0bQzikWe0PAAAAAwEAAQAAAQBjIt2rTIJxMOJAeMV3
+cqaonUoiHdySon6oKkOrGjc++SO+DKKwLcMJsqgZ143RUNhAIWY0Jxlo6LfA3swuOB5bzz
+kzPY4W1uVPIJCpEsKjqwePakFfOE9daZQqwltxvjyCJpOFZI/doMl/2P37ibNpsY7h6uZf
+ssZpCwwehpmj/HknoAlrGTrG2SlzLTMvk6vwYgNoeHKCQfM2wfr9fFlbwNFJE7L44o0PfK
+8UX9u8mC7PR1Q1u2OopOsUXWu6f1Vc/nU5La6Z6W1voHxMfUMLhq7c/Jih7SfPVX2z6U22
+VKGXinhh1q5a71nv44BZPxTGw0TC/FrDntTWyDu3jkuhAAAAgQCNZx4JVV+yoFnbypRHh+
+n0hhYvPbtHEzmhK9WEyjQCWIhf7zMXau+00bhDaS+6nqiZOfXPecC9UbjH06KT8da6yqYK
+3SwsXA+RALbKe0uqWO1KKufge+FxZWX3j07D/8+pL1fE0996f9yjR06kM8b0afxQBMDxnG
+HzVnPJEmUpjAAAAIEA50BnLTKqHsQCiBHRYuzRAXomeW7EWO+lQSB7WUcx91QtuzspoXMK
+NyiaabzsPfTqmqRH9fcL1ORSTy4Jk+0tZjK20P+gGtGthHCxQjQnOhgyjvSzp0P7F55Cme
+LSr1lYtFtjrh4nLval05ddQ6cYmebzRzy0NveKUbeIgkR5zJEAAACBAMuePyCBm2RB6Hlw
+cYqUTJ+zduNFhu83YJ2vQg0ojJ5If9jHNrTHyiRG/k81AJS0fZs9aF6//fvq+85Bf3bGd6
+4PSWbSwjlxLNvbcGqfQ8gOk75lni2hr5e5SQBimGavJM7bjqr3+nqBvAiPrDROWQpALdbh
+hVWNOMdo8JBxcW+fAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAgMEBQYH
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..0b12e79f63
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_rsa_key.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC37wSWu5cDD45e3IYFQzECRJ+zD8DsFaojNu1GfqEPdiUk8VgGjg7TA15biD3P6Tm7jZDhK/eBkA+5UBWL8TgwR3d6DxlGSQLrvhrmVS8TMisNg9uSHNHlsuJ0bRMrjB2ov8dEYT2ClMho5wgOQrZEjY95GGO+OxxQdOxr8rHp3AGIo4rm+MhcDYyWxOBVjs0xKmNH+wf9MbpRlfGGWCYEayulWxUz/eRCiaFVWgyfXJEbpbPYR35voQ1go2a6iZ4igJmhT64TREyqITmoPfZhUTG3UHz0JUBHcZtAopOin49vXGQ0ydi8KkOjz7rx1ntTvMnVJmvTTJ0bQzikWe0P uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa
new file mode 100644
index 0000000000..f76cc234b0
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvQIBAAKBgQD5eG+xea43Nas9xKOEPqkMaBmTZuKo+nz9S2mo3Qkuj5OV6Mqc
+Jd6g9DUylc5XMtyYvq0AImgirc3ZTCOLyH3wXunNDkafUxwxrSO3w68mgdGbjJxZ
+euS/dRx6pYmeb/ykWCQFd6D/t1OGK0QJT7gn9ke6pXAL1ARVafP2Yri8rQIVAORv
+Zk6erYLd3aAvPNzVSLH6P6CRAoGBAImjtInOXhMXa+7ABuwrvdN78bJX5pSdlgUW
+W1Nx1obUb65njR78CqB+fynwxevHDBPNEVE0T5xfAg3zWBTGH/mwQ/ivA3t350hT
+usbRBbPs9kzzhfWdq7GKVKL76UmefYU1cwM58VdOFcuMbfUEujwIgw+KbJzI7c6y
+DH8ll+s7AoGBAK8z5gZp4k43uEFRsLwhZj4edgb3vmZvOvEzo3awP+Pr2Et4ReL1
+48YYfb422FafzePzCkvkRNMdZF2iiPQXK1r4JIVCfi2Zyci8fMzHmBR3bmvz5cjP
+9tVtKGR9z48dYe5R74Td+Vp//h6lCHyi06kSg8yoambE8sdAnp5DdgN6AhUAg8aR
+exXtcrukzyrWLA/4jZSnrFY=
+-----END DSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa.pub
new file mode 100644
index 0000000000..20c42561b0
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAPl4b7F5rjc1qz3Eo4Q+qQxoGZNm4qj6fP1LaajdCS6Pk5Xoypwl3qD0NTKVzlcy3Ji+rQAiaCKtzdlMI4vIffBe6c0ORp9THDGtI7fDryaB0ZuMnFl65L91HHqliZ5v/KRYJAV3oP+3U4YrRAlPuCf2R7qlcAvUBFVp8/ZiuLytAAAAFQDkb2ZOnq2C3d2gLzzc1Uix+j+gkQAAAIEAiaO0ic5eExdr7sAG7Cu903vxslfmlJ2WBRZbU3HWhtRvrmeNHvwKoH5/KfDF68cME80RUTRPnF8CDfNYFMYf+bBD+K8De3fnSFO6xtEFs+z2TPOF9Z2rsYpUovvpSZ59hTVzAznxV04Vy4xt9QS6PAiDD4psnMjtzrIMfyWX6zsAAACBAK8z5gZp4k43uEFRsLwhZj4edgb3vmZvOvEzo3awP+Pr2Et4ReL148YYfb422FafzePzCkvkRNMdZF2iiPQXK1r4JIVCfi2Zyci8fMzHmBR3bmvz5cjP9tVtKGR9z48dYe5R74Td+Vp//h6lCHyi06kSg8yoambE8sdAnp5DdgN6 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa
new file mode 100644
index 0000000000..e0ed11843a
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEINRRCrEPoBUJJueQ6IDc7UxpvYi+gzBJkn3pv+yp08tvoAoGCCqGSM49
+AwEHoUQDQgAEKAnppzgUZKpYf6qLbLL1LmTLWPLI7NDjY/+oWE1NqrUrMOM4NXKG
+NCTNhLKWtICjfb8h0E3zbCh5PNubVw14WQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa.pub
new file mode 100644
index 0000000000..2793c68b70
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_ecdsa.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCgJ6ac4FGSqWH+qi2yy9S5ky1jyyOzQ42P/qFhNTaq1KzDjODVyhjQkzYSylrSAo32/IdBN82woeTzbm1cNeFk= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa
new file mode 100644
index 0000000000..30871b6a67
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAtnd1zCAPBkxEHFBQjpt9TOY7epRozetWWBp1wVQYxHMicpwe
+2NwuN+3A6Y5r20lhdaElVQ08DBEcJ184sSxv/+8PCu4umiSyqRyrUvdap60InZq1
+Fg50OJMfAGcWjeOp4UrH/nGdJyqdN2Y40eVsXg4pogy7ElG3Q5hROmbC9vpOTkrb
+3Krv7JFFXz3vhRkxiYCMNw8hK8TOye9o1JCyVzImZGGwdUA9OZN4Nizoj6ie90KC
+VRoI+2GoE0gNy4Mt7P+lOyDBzFeJbGl0ob9S5P3gnyCVUmlV0EY1fSijN8IIOj5l
+qsBpADt+sN3iYltZJmCn5wDP/iORn6P04jWU1wIDAQABAoIBAHlncHw5lGWXVvYT
+xhWshSkmQsrjdfwUqmWCbXkNkFEdXf0tvSSDE0lpKqL7fO3xnCPc7W7ymFJbDAVy
+SNExhO+fyr12DpHG+wykI6XXKH1KFuJuLjCXu2JtGQJ2lL4hjUV2MS0twOdvZh2X
+KRUW9gx6ld7ZY5rjvfD+poUaHHygnN6f0+PiyBpUZaL+ZTj/6CpHiCxdZtOCf0o6
+bU7TaPNcZ3vf3Qhk4jk140vEDLJQnPF8stBqPWa9HfmM7CNjWBdmhQXHtHw8CtF6
+aba9BRC/FMYx43itE9hkg6p3JrSqAN/gZ6RCXLog6mQYttJV+y8oLTXvblT2Y3c9
+YjnigGkCgYEA2Y4+sjsGw3l5HnQ9CNuUQvJZgloVd/NTw8/UXXkV1QudI6tzmMJn
+XAxCCtt2DVlPBqFJ74uwdWpY0nwFJslEp4sV6VPv2TpBmAOPDB6QtQ8SXhFWQ7vj
+BVh5kwl3LUDVI+NIACoaMyZuS6N97Fp4a/mKtgj+ucOkfr15+IF7L/UCgYEA1rXd
+ATBFQlLoPuv3VglU1LLgaLs/3qzoBH9DzwPPyEFdWYQUCuMHsL9eEfDZMsG/GeZb
+Fch/CR0R2Qt+ZlcxcRichgMw3ydzIBqCvLe444lBzxdLWFqS5gCnSh5iage2QRs0
++6QD9O16HJER6HmBwR6DtwpP3N4dHxLXJRjDrhsCgYEAl3/M/UTJkvpWc/SyRCbU
++kHWP0YIST2ziVqDIoydvXyW8y4EE87dN2Z53yGw9d7Jf252FFCMk1d5fypKVBY4
+rwvWOGPxVK6S2w8vYFswnkVenw8nqYd/sktIbjJbQbIyOwmdLDAlipUqnZW+rQbb
+cSWXiOh+qlIpjPDZrUpNxLkCgYAMPjiI7dC1NHcLx3bGECgnLMABGNROhTuBriQW
+tNfvSlLhXNeru0BgArmBemNYMpYMCwecmV8tDNxMrQwbF9O46DdcqOfrgZtd9EUK
+L8u6JcR7448nTZrcxKLFZjAkbaYl1kBSLQsQt03kPR1xTSRp96x5Dnx5Uq0EbZWZ
+Bu15iwKBgAlSiCUqCNir3fdd0wE2+MIczam3YshQmnS3/XEk+7Bmfmb7Rxdwk94l
+P/SaQYZ3buCKoBTz5OveBl4aEdiyvEqBkeJoUbzwFILyo0RNncqULcrYAPIJtbKK
+H0o0naCZUgUJGsX0/DdJsEE27KMljc1A1Fpd3qQ2qqVLFfDTrsuB
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa.pub
new file mode 100644
index 0000000000..f7b1180aad
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2d3XMIA8GTEQcUFCOm31M5jt6lGjN61ZYGnXBVBjEcyJynB7Y3C437cDpjmvbSWF1oSVVDTwMERwnXzixLG//7w8K7i6aJLKpHKtS91qnrQidmrUWDnQ4kx8AZxaN46nhSsf+cZ0nKp03ZjjR5WxeDimiDLsSUbdDmFE6ZsL2+k5OStvcqu/skUVfPe+FGTGJgIw3DyErxM7J72jUkLJXMiZkYbB1QD05k3g2LOiPqJ73QoJVGgj7YagTSA3Lgy3s/6U7IMHMV4lsaXShv1Lk/eCfIJVSaVXQRjV9KKM3wgg6PmWqwGkAO36w3eJiW1kmYKfnAM/+I5Gfo/TiNZTX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key
new file mode 100644
index 0000000000..99da9723ef
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQDLRBMHSgExTaMVjXrZwxdgmkpZgjQXGy/IUqBNw9MTq1AmndGH
+6Pkj3LzPiNsZArTwg3k0strcx+VtbQcJ1TrH7nOABdO5vTGAd9arPIkhts0LgwHA
+HcAHf+9iNr26uG8jtIc7+o7IpJyTzy2buVmfosYhKcwVbPHIhQp8KjAWfQIVAPd+
+7YzKkxRyBPZZ4K+G3QI3YJiPAoGBAKGRI74EwgLwRWMeVOJun7oWh0uVQFdi51sY
+3e9JC4/0hx0D0JHO2+b3opGPw/wrVXmGWZdmgpslsPZbta/oVR0OgAK4V2tTgn8p
+ijOIcEJmvpogtaJj40laTV+mpwRfQZWsfJPYTibZqtPVvvtu+6eVB1dTlNYngOAN
+mDHh6wpzAoGATv2nRnv0l878aIchG99kKFq5hX1rfLOYqeDq5uKXHu+Zf9IDPEQB
+Dmo73RA4pXWlD8Kf9mV3p5CSxg3tdSTelCt7h0cVoq3LmSU5fjA8eVNI7KsFi9QB
+dBnxEdMIBW2S8x4FfuWzgBUrQBzLqLR0CEEPmr1N9Dw63OiTgh9drJcCFHmpYuRa
+y/ynGrBb/mzt9QAGhNls
+-----END DSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..485997cfb9
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_dsa_key.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMtEEwdKATFNoxWNetnDF2CaSlmCNBcbL8hSoE3D0xOrUCad0Yfo+SPcvM+I2xkCtPCDeTSy2tzH5W1tBwnVOsfuc4AF07m9MYB31qs8iSG2zQuDAcAdwAd/72I2vbq4byO0hzv6jsiknJPPLZu5WZ+ixiEpzBVs8ciFCnwqMBZ9AAAAFQD3fu2MypMUcgT2WeCvht0CN2CYjwAAAIEAoZEjvgTCAvBFYx5U4m6fuhaHS5VAV2LnWxjd70kLj/SHHQPQkc7b5veikY/D/CtVeYZZl2aCmyWw9lu1r+hVHQ6AArhXa1OCfymKM4hwQma+miC1omPjSVpNX6anBF9Blax8k9hOJtmq09W++277p5UHV1OU1ieA4A2YMeHrCnMAAACATv2nRnv0l878aIchG99kKFq5hX1rfLOYqeDq5uKXHu+Zf9IDPEQBDmo73RA4pXWlD8Kf9mV3p5CSxg3tdSTelCt7h0cVoq3LmSU5fjA8eVNI7KsFi9QBdBnxEdMIBW2S8x4FfuWzgBUrQBzLqLR0CEEPmr1N9Dw63OiTgh9drJc= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key
new file mode 100644
index 0000000000..75ba71da56
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJeZs+jukpumHYLii4OXg5k9dN7u1aNLZovxbqFoEfgToAoGCCqGSM49
+AwEHoUQDQgAEBg0zAjfzxl0ccv2wnJHZvLXETa6bopctXD1V/FWcPoBL5dh42mOj
+I6ZgtrUnnjbhdxJyeG3BjntqhP5rLMMpeA==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key.pub
new file mode 100644
index 0000000000..26e7285240
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ecdsa_key.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAYNMwI388ZdHHL9sJyR2by1xE2um6KXLVw9VfxVnD6AS+XYeNpjoyOmYLa1J5424XcScnhtwY57aoT+ayzDKXg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key
new file mode 100644
index 0000000000..6044fc7725
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEArrzkBUAMYvAso4hR79vmNbxNbLYt7QocukbiCWOq29HQvqbS
+zj/OSE1Qg6C/aTbghvdpvxbaFt3aqdWroF0PhVVoEsJY76bI7RsobIe9zI/Z3dkQ
+RmW3IyjHvpVBwy84fKZ05A8Bd10kca1GfrQXi7LkFZ5FRjxarFzMojGVesWcZLag
+ThPG0QAjSw2sG+oql4YoIeagSdf3tzOOF+04vcpgqCRugsscw17lI1Rwq0nM3thU
+BgSWDRbjzmkWo9i5Wpc9ZKS1z4ANET+I2hGW7PEA6XAXJKC6nIWdVIie0GN02C/m
+i45NyTPn52I/TJKFAtIoZ8fbrHEepX7V/Dt7DQIDAQABAoIBAA1yJY2t3wYh+x1e
+WQe/ARjzc3XBEwmhdJJ07+HPFI+ztn9lMOWEDWiM4nwue2wqN97K3Q1CQefujGvz
+MDC32IDnEIoZAGT4jY+JPnQTgexiyV4D3Pe9zfjbo3sr2xKc6JjW6jm+WduIhExn
+C/yl+QXb7ycmtafw7v1CatC0Rg9bUtE8nMHKYVPazn30wlHdPl6TyTtEXoZCKMg2
+OTxrva80x5JboUqLZXX41VqVmqqoakEO3NOGlhrIzIOB866py8d+f6wilN/rUcGe
+MJwB8aTrYPxLkCYl9PGeMDMLARvhjMm53f6UQFDpL2rY0XpnaqrZqS9KrbJoTQDW
+lMj3OiECgYEA4FL3fba4FYGzf7T/m2xRSC5qrpr0gNn1mZJ5oUWGiHBwjAHC7EVW
+apcjskac7WrznIBJiV2ozzxQOgIymuYO9LX95G6b3nrkWhVXqUiyCkpdMSE/YVfA
+iMc3Z0QNz+I/cbEPUKZ7osKPZm4BRpUNvJwj4Vvt5jXwTZlPmVmpNHUCgYEAx2lx
+q6HO+Grba0Azg7wobnnZ8uZZvdZ+QpxgGhH1Rx9862KM+uVYrJ7xEUlNTYfBtdpq
+JXQnGlpzjGPeziZjJxv7AgWbJA80aXtTH/oE9E0KMmGRzE2bQNR8kUZHU5S2e0u+
+x1DUyvKqyKSRByxUp8wj5QZGPOH4MPvCHVZF+TkCgYAg52qQERYtaWn36Ie5t4iw
+qsZROD93CwGAdkDLDBSwvLV1g+igmYcUeXjt9HeeR5rWMOcYdBmH1FP8PkhH+kjl
+UjCcqjDI0IPgRtMl7JjY85F53GOclq+SII6a4huYi5o8xfj2HoVyGVHJd4dOYBy0
+tr54lvBtXSoTZ9KKLuGn5QKBgBUy4XGkfvMrsO3C4ncTrpyn+YJ3+HxU7BE6vICo
+/hE0iLwhOumFLhsTvn7e8wfV8cLaWERpB6smiHgZOdtie1HyCIobfHWl5CV+hcS1
+eIdcFURr2OsGKQYIUMHE3dpFyexrjfl0X1q/12YDEKPZk5pO+lXjh937C75xVR53
+SHMJAoGAS57QNKLFaWZunoMNuvvNAj7Z0q1JrFuLEwfnkG28g5+ov3wIBinPtlrr
+3HaK6sny0hHLPoRP+fa6BVRaQhDzeeKDu6PqNEkNnocWPi79lxfk531EJQHOgQgX
+yt7Ruq0TlBYs5wGrmtYXLKAGvcfyx9EoFs2Km1iNKqu6b/dbQXc=
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..8e62458395
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_rsa_key.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuvOQFQAxi8CyjiFHv2+Y1vE1sti3tChy6RuIJY6rb0dC+ptLOP85ITVCDoL9pNuCG92m/FtoW3dqp1augXQ+FVWgSwljvpsjtGyhsh73Mj9nd2RBGZbcjKMe+lUHDLzh8pnTkDwF3XSRxrUZ+tBeLsuQVnkVGPFqsXMyiMZV6xZxktqBOE8bRACNLDawb6iqXhigh5qBJ1/e3M44X7Ti9ymCoJG6CyxzDXuUjVHCrScze2FQGBJYNFuPOaRaj2Llalz1kpLXPgA0RP4jaEZbs8QDpcBckoLqchZ1UiJ7QY3TYL+aLjk3JM+fnYj9MkoUC0ihnx9uscR6lftX8O3sN uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa
new file mode 100644
index 0000000000..5420f40352
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa
@@ -0,0 +1,15 @@
+-----BEGIN DSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,C5C1380D397337A7425EF5FE7B2A01B5
+
+/xZ9L6iKTvN6AbZu3Uper6DzMxLdhGMix9UB9pq3JXJAElZRfP5LkAYBX+3mYYXu
+VGYsMsHrPLtItgDQQ7DNhbo+IOoC3Id7gIssxitXaNbuDs91ahMANzYPeypV71+U
+fgNnznaTIL2yl6pr342IFksCw1lt9L6Z3nnslUO/4mwbESL7nIe2WDpxdAxIYQSw
+QetL8FoP0ohyMvNdrmooJ48oJylR3Uctir1Szb6Rg65LEuvasY0SbJhDaS+weN9c
++dRgPTrZmIhohEy5FT52JqsSaONxM0NVV8wmkw0uX642cYpPU1WkVDK5RwXExiR0
+5hORAu1lqdCGG8M1KNutn/8ASN+ger79Jccp9Ity9TP3I2RbSFJCR8byysIJoFdF
+Pb6ju+SRmtMBmrIMaAQdO10zAeBdVq78ALiK78Jw8D2ciA1zRnzvzBCoQhKw4dxU
+XmD4V2qBVUuYFKms6Z/LRnjvShooQgmCm4CtX7RehQFTlbrJJcrfEaJdrb84d9QK
+uY/lY/47tSsyC8YIK9ta9uyLgvxOmPvfKSA5lJpTPk0oEeRJEmtrMzDhfB4tXR7p
++d1F5kWJlTVdsYOpd1li28LpAu/YJrop85QP75TV/F8=
+-----END DSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa.pub
new file mode 100644
index 0000000000..2ce1d597f4
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBALpx1AMh2SgvU5KTZLYlMMADqt46X/7KrXNiM/JbCG2vsUg+Pf1k24J5DThLH7Ex0o5ghmd4xrCi9hMrkcNe+RI1KU2B68mIrddGYmL9nLCwWf4loKmzVnTY+RCjBT7YRKOT4H9B1gF33kpRellmd+x7zVFSM1DbEdKjuPA9wseFAAAAFQD1R2qkuiiy67GoE6nErIaFH7cOIwAAAIEAqcswmM+/NrpXfaEF+mCUN1ylnO/YF2arqJp+CvAzSB9VhGnk7+hAX6vbQ+iQg6pDL8OOtky5fYqwlGdLJ4rTMiZuOOYx8grxgUxjMCmLCq3exH5VpXQnbcffZ+Zrqv6HtpqenLZgJxxcNG27oH/n4CLSoQOw0+AeJ8/NzIU8A68AAACBAJnnmAXlF9QW408bw9t0hNZDtacPDdiRENJaSVsb37+gTQvJPMfr6YpvDh2PO0ODg9Omwqddm/YpLAQAJ2tWzRMuxoI18ikcFWPveetaDZS/1eEPrTY3SMitmevrdCXGSpdEuvidbXNdW874MtmP5E4hW4vVe7wJkCEZ0hCL8AcN uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa
new file mode 100644
index 0000000000..3108bf688f
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa
@@ -0,0 +1,8 @@
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,E2E108EACB898D6E55241106B2CDEC45
+
+hAmOR9aVIF2RNJet/vFVGgsx9Jh1/r02FJEPMWcdfSMgxJMfjKfCpKDqY72+Idzs
++WVk/Nr6s/TzNZwOH4WJV2ObZ1EG9yClHPPnAYsgoog88JhW5RRwEaa5X7oHFmVc
+zK1cixK5qc39SUiAGGn0PWcnx6qVVWd1KIWbqFzV5Hs=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa.pub
new file mode 100644
index 0000000000..8854f3be20
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_ecdsa.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGcNr/NDr9ueJUzkYyy/MdhK5CFFJKcTIYMod81dlhvhtynWgE7B/eW5hdKwuUm07FLu6lOw14s39VCQ/WVPox4= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa
new file mode 100644
index 0000000000..a74e47862c
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,B965F2149914DA8A54DD98275B94EC1C
+
+QRDCx1Taj7lBQRhLiPs8WLLsSSd8AKK/mzRUOwuf3E0VdrP20LLtcjn5xm9+zj91
+BQgPZshoB4eP2j2exJfTOOIkBdzjF3gYz/QyrSJ8fjdIA/MDzHcrv+HDpMes6gCP
+UTb4daKtBmae/fnRoUJtOJeyfQsJT4HqSRuGitvp+/47TdiD8N4GeBO/buMGEch0
+zw89/9w5bGrn7cPYo6mB5WWjxBcGM0ertCJ4x+uU+QRaohXnatgVpaydCYIzbhdS
+/thmlNEFizDtfSAoZQlGv30DXQWsvoYdDVZLtRPvYnNifTrcPBkfy/Etl9cgwmFT
++3aF8pQKppdGSeR5vaT5ardjyWEhoM1hVlucWzBjbhlWLeozjzMyT74lxw3hSiJW
+KJsoFHrFsfMB1G4cfI/gANbaFOc834w5GHxrz6PCHnWwDLulnhMTPDv1Pu6tg+8Z
++Dxu8pVIehnNUifj9OfPfY+ZNIbdroRavYr/YXB+16lyP1u4r1/rbIbBWTorebLw
+8Z6Yc1q4svnhiytWoyfzF3UQotCucZ13tHN3OaQ/u0SQnckYU1SdlGJqSvLW8N7S
+Yx5jW5mFbuOAQbvzs2CgeO/ooXq0KNVTYOkr3neN6PMk4WYcEsagmaqhMfIFVCBJ
+Fc4GFXbkSO4KKqRysGnnEt4sI9TJX9uIBVTtg38Q4rGisGMejD4f7eTjwqSMy4Xy
+ETF2+JQW5rJTnMstAqA1RvGxqpsZXNxx4+pLSeHTe0xIC+LuB92THXiD1a5xcQa3
+RS2s8CT4trOqKLLr22eRkV2XfEG+v1LEcp2uVyjBVM8CPKRP6w5KNnut19CA7AnQ
+WVVeNRGdVEtvId/kq2J91ZGTUWkmjgapTY+xhFNX67Sx0kwuosb2N1SvR6FidWIH
+d7xCHf/WO2aKuQgLv5fVvPqN4vPM/VMd8Vsx4y5IxUne0dkSYtk5iAeHwS+b3sti
+sMQAEauF8clt+jUXSGX4vSLIAc8JYSLxDRr2yy6q8dzdQ77pqqcFgg7Fa6tLKNQ6
+Op1eknVzPRDUDyaDr0mlE9pnSde1g++mEwEca4VORLljEQpf6vYDD/bDnyEfTXoA
+d1zt06X8OHAubtG8GcLilw9rMglWLthX+ymHqC1UWMOzgBpua6mffDb2I4qYWR29
+NPdEF1u0PKlzT7fqBE4tpiL4OY5lT/vcNDmf6cq59KKgSVYTzADTH/nQ6eLTkTgt
+pU4L60NtCIsn5r2xqwMI8R2f9gjqIwjn45BkqqJBq2vdTlyW6m0vdUdYnGHRkA0c
+fU1XOYUHUPi7Wa9MfcwyKYe55zMo7zspa0kIVOgkwfMVSnFr1eDM4jTNfaWFjIf0
+xEqnF6sOGO+KefL7nscJHfX7UipASXO3cWl26YvT5sJPCbwOZSNS3S+6+YCfimLn
+kNxWOUBDuHMWOsUDc26lh7YtBaImsLrM8eqdz8vRP2V9WH5RoHu0WT43HMZkWYZd
+FQeE5fNUPTAJb/PLOyh9LZQ4BibDE4ZbikEJfj1ut9+4C5aWt2NKmwSnDnJTCu3J
+qUeSlk0zIZXLbRsnK6TBQLVXc4cnZrVeb/Dfb2fropioh0f/WFusO5TKuP88XcSG
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa.pub
new file mode 100644
index 0000000000..469a6ceb7c
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRAvJCrHYBhf5riHOyqBFfBCBZpKXf2zIc3iwbwY2xl/CHkEwZujBHe2KX1O1h8m7TpUNKMcdGNinsloZsDVKW2VEN7yT6oe3M2fyHLXDfd1KTyINF7LXqgtVq9+A8iLYdOBat7JBkY6c18pMvVU6yCWyr3m8+lnGskCW7swthANbwuW42PvyFdZf9/CkzNIVY6SsAF8wdXdf830wDIimZpN+ER8sLDGcu7dIaoypvs2KlCqwS6DE9kN7X2zXrxZA4thFsoJafgPDCaMOLRkCD2L8NBlRjXBE/U/sOb46oOfZTUy2wjo7pPwp9zekEO8l7OPZ9VgZ5bxXU+WTWox+z uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key
new file mode 100644
index 0000000000..99da9723ef
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQDLRBMHSgExTaMVjXrZwxdgmkpZgjQXGy/IUqBNw9MTq1AmndGH
+6Pkj3LzPiNsZArTwg3k0strcx+VtbQcJ1TrH7nOABdO5vTGAd9arPIkhts0LgwHA
+HcAHf+9iNr26uG8jtIc7+o7IpJyTzy2buVmfosYhKcwVbPHIhQp8KjAWfQIVAPd+
+7YzKkxRyBPZZ4K+G3QI3YJiPAoGBAKGRI74EwgLwRWMeVOJun7oWh0uVQFdi51sY
+3e9JC4/0hx0D0JHO2+b3opGPw/wrVXmGWZdmgpslsPZbta/oVR0OgAK4V2tTgn8p
+ijOIcEJmvpogtaJj40laTV+mpwRfQZWsfJPYTibZqtPVvvtu+6eVB1dTlNYngOAN
+mDHh6wpzAoGATv2nRnv0l878aIchG99kKFq5hX1rfLOYqeDq5uKXHu+Zf9IDPEQB
+Dmo73RA4pXWlD8Kf9mV3p5CSxg3tdSTelCt7h0cVoq3LmSU5fjA8eVNI7KsFi9QB
+dBnxEdMIBW2S8x4FfuWzgBUrQBzLqLR0CEEPmr1N9Dw63OiTgh9drJcCFHmpYuRa
+y/ynGrBb/mzt9QAGhNls
+-----END DSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..485997cfb9
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_dsa_key.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMtEEwdKATFNoxWNetnDF2CaSlmCNBcbL8hSoE3D0xOrUCad0Yfo+SPcvM+I2xkCtPCDeTSy2tzH5W1tBwnVOsfuc4AF07m9MYB31qs8iSG2zQuDAcAdwAd/72I2vbq4byO0hzv6jsiknJPPLZu5WZ+ixiEpzBVs8ciFCnwqMBZ9AAAAFQD3fu2MypMUcgT2WeCvht0CN2CYjwAAAIEAoZEjvgTCAvBFYx5U4m6fuhaHS5VAV2LnWxjd70kLj/SHHQPQkc7b5veikY/D/CtVeYZZl2aCmyWw9lu1r+hVHQ6AArhXa1OCfymKM4hwQma+miC1omPjSVpNX6anBF9Blax8k9hOJtmq09W++277p5UHV1OU1ieA4A2YMeHrCnMAAACATv2nRnv0l878aIchG99kKFq5hX1rfLOYqeDq5uKXHu+Zf9IDPEQBDmo73RA4pXWlD8Kf9mV3p5CSxg3tdSTelCt7h0cVoq3LmSU5fjA8eVNI7KsFi9QBdBnxEdMIBW2S8x4FfuWzgBUrQBzLqLR0CEEPmr1N9Dw63OiTgh9drJc= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key
new file mode 100644
index 0000000000..75ba71da56
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJeZs+jukpumHYLii4OXg5k9dN7u1aNLZovxbqFoEfgToAoGCCqGSM49
+AwEHoUQDQgAEBg0zAjfzxl0ccv2wnJHZvLXETa6bopctXD1V/FWcPoBL5dh42mOj
+I6ZgtrUnnjbhdxJyeG3BjntqhP5rLMMpeA==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key.pub
new file mode 100644
index 0000000000..26e7285240
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ecdsa_key.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAYNMwI388ZdHHL9sJyR2by1xE2um6KXLVw9VfxVnD6AS+XYeNpjoyOmYLa1J5424XcScnhtwY57aoT+ayzDKXg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key
new file mode 100644
index 0000000000..6044fc7725
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEArrzkBUAMYvAso4hR79vmNbxNbLYt7QocukbiCWOq29HQvqbS
+zj/OSE1Qg6C/aTbghvdpvxbaFt3aqdWroF0PhVVoEsJY76bI7RsobIe9zI/Z3dkQ
+RmW3IyjHvpVBwy84fKZ05A8Bd10kca1GfrQXi7LkFZ5FRjxarFzMojGVesWcZLag
+ThPG0QAjSw2sG+oql4YoIeagSdf3tzOOF+04vcpgqCRugsscw17lI1Rwq0nM3thU
+BgSWDRbjzmkWo9i5Wpc9ZKS1z4ANET+I2hGW7PEA6XAXJKC6nIWdVIie0GN02C/m
+i45NyTPn52I/TJKFAtIoZ8fbrHEepX7V/Dt7DQIDAQABAoIBAA1yJY2t3wYh+x1e
+WQe/ARjzc3XBEwmhdJJ07+HPFI+ztn9lMOWEDWiM4nwue2wqN97K3Q1CQefujGvz
+MDC32IDnEIoZAGT4jY+JPnQTgexiyV4D3Pe9zfjbo3sr2xKc6JjW6jm+WduIhExn
+C/yl+QXb7ycmtafw7v1CatC0Rg9bUtE8nMHKYVPazn30wlHdPl6TyTtEXoZCKMg2
+OTxrva80x5JboUqLZXX41VqVmqqoakEO3NOGlhrIzIOB866py8d+f6wilN/rUcGe
+MJwB8aTrYPxLkCYl9PGeMDMLARvhjMm53f6UQFDpL2rY0XpnaqrZqS9KrbJoTQDW
+lMj3OiECgYEA4FL3fba4FYGzf7T/m2xRSC5qrpr0gNn1mZJ5oUWGiHBwjAHC7EVW
+apcjskac7WrznIBJiV2ozzxQOgIymuYO9LX95G6b3nrkWhVXqUiyCkpdMSE/YVfA
+iMc3Z0QNz+I/cbEPUKZ7osKPZm4BRpUNvJwj4Vvt5jXwTZlPmVmpNHUCgYEAx2lx
+q6HO+Grba0Azg7wobnnZ8uZZvdZ+QpxgGhH1Rx9862KM+uVYrJ7xEUlNTYfBtdpq
+JXQnGlpzjGPeziZjJxv7AgWbJA80aXtTH/oE9E0KMmGRzE2bQNR8kUZHU5S2e0u+
+x1DUyvKqyKSRByxUp8wj5QZGPOH4MPvCHVZF+TkCgYAg52qQERYtaWn36Ie5t4iw
+qsZROD93CwGAdkDLDBSwvLV1g+igmYcUeXjt9HeeR5rWMOcYdBmH1FP8PkhH+kjl
+UjCcqjDI0IPgRtMl7JjY85F53GOclq+SII6a4huYi5o8xfj2HoVyGVHJd4dOYBy0
+tr54lvBtXSoTZ9KKLuGn5QKBgBUy4XGkfvMrsO3C4ncTrpyn+YJ3+HxU7BE6vICo
+/hE0iLwhOumFLhsTvn7e8wfV8cLaWERpB6smiHgZOdtie1HyCIobfHWl5CV+hcS1
+eIdcFURr2OsGKQYIUMHE3dpFyexrjfl0X1q/12YDEKPZk5pO+lXjh937C75xVR53
+SHMJAoGAS57QNKLFaWZunoMNuvvNAj7Z0q1JrFuLEwfnkG28g5+ov3wIBinPtlrr
+3HaK6sny0hHLPoRP+fa6BVRaQhDzeeKDu6PqNEkNnocWPi79lxfk531EJQHOgQgX
+yt7Ruq0TlBYs5wGrmtYXLKAGvcfyx9EoFs2Km1iNKqu6b/dbQXc=
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..8e62458395
--- /dev/null
+++ b/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_rsa_key.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuvOQFQAxi8CyjiFHv2+Y1vE1sti3tChy6RuIJY6rb0dC+ptLOP85ITVCDoL9pNuCG92m/FtoW3dqp1augXQ+FVWgSwljvpsjtGyhsh73Mj9nd2RBGZbcjKMe+lUHDLzh8pnTkDwF3XSRxrUZ+tBeLsuQVnkVGPFqsXMyiMZV6xZxktqBOE8bRACNLDawb6iqXhigh5qBJ1/e3M44X7Ti9ymCoJG6CyxzDXuUjVHCrScze2FQGBJYNFuPOaRaj2Llalz1kpLXPgA0RP4jaEZbs8QDpcBckoLqchZ1UiJ7QY3TYL+aLjk3JM+fnYj9MkoUC0ihnx9uscR6lftX8O3sN uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE.erl b/lib/ssh/test/ssh_renegotiate_SUITE.erl
new file mode 100644
index 0000000000..2e39f4aceb
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE.erl
@@ -0,0 +1,430 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssh_renegotiate_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("kernel/include/inet.hrl").
+-include_lib("kernel/include/file.hrl").
+-include("ssh_test_lib.hrl").
+
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2
+ ]).
+
+-export([
+ norekey_limit_client/0,
+ norekey_limit_client/1,
+ norekey_limit_daemon/0,
+ norekey_limit_daemon/1,
+ rekey0/0,
+ rekey0/1,
+ rekey1/0,
+ rekey1/1,
+ rekey2/0,
+ rekey2/1,
+ rekey3/0,
+ rekey3/1,
+ rekey4/0,
+ rekey4/1,
+ rekey_limit_client/0,
+ rekey_limit_client/1,
+ rekey_limit_daemon/0,
+ rekey_limit_daemon/1,
+ rekey_time_limit_client/0,
+ rekey_time_limit_client/1,
+ rekey_time_limit_daemon/0,
+ rekey_time_limit_daemon/1,
+ renegotiate1/1,
+ renegotiate2/1
+ ]).
+
+-define(NEWLINE, <<"\r\n">>).
+
+-define(REKEY_DATA_TMO, 1 * 60000). % Should be multiples of 60000
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{seconds,90}}].
+
+all() ->
+ [{group, renegotiate}
+ ].
+
+groups() ->
+ [{renegotiate, [parallel], [rekey0,
+ rekey1,
+ rekey2,
+ rekey3,
+ rekey4,
+ rekey_limit_client,
+ rekey_limit_daemon,
+ rekey_time_limit_client,
+ rekey_time_limit_daemon,
+ norekey_limit_client,
+ norekey_limit_daemon,
+ renegotiate1,
+ renegotiate2]}
+ ].
+
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ ?CHECK_CRYPTO(begin
+ ssh:start(),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
+ [{preferred_algorithms,ssh_transport:supported_algorithms()}
+ | Config]
+ end).
+
+end_per_suite(_Config) ->
+ ssh:stop().
+
+
+init_per_group(_, Config) -> Config.
+
+end_per_group(_, Config) -> Config.
+%%----------------------------------------------------------------------------
+%%% Idle timeout test
+rekey0() -> [{timetrap,{seconds,120}}].
+rekey1() -> [{timetrap,{seconds,120}}].
+rekey2() -> [{timetrap,{seconds,120}}].
+rekey3() -> [{timetrap,{seconds,120}}].
+rekey4() -> [{timetrap,{seconds,120}}].
+
+rekey0(Config) -> ssh_dbg:start(), ssh_dbg:on(renegotiation),
+ R = rekey_chk(Config, 0, 0),
+ ssh_dbg:stop(),
+ R.
+rekey1(Config) -> rekey_chk(Config, infinity, 0).
+rekey2(Config) -> rekey_chk(Config, {infinity,infinity}, 0).
+rekey3(Config) -> rekey_chk(Config, 0, infinity).
+rekey4(Config) -> rekey_chk(Config, 0, {infinity,infinity}).
+
+rekey_chk(Config, RLdaemon, RLclient) ->
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, [{rekey_limit, RLdaemon}]),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, RLclient}]),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ %% Make both sides send something:
+ {ok, _SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ %% Check rekeying
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex1==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
+
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+%%% Test rekeying by data volume
+
+rekey_limit_client() -> [{timetrap,{seconds,500}}].
+rekey_limit_client(Config) ->
+ Limit = 6000,
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "rekey.data"),
+ Data = lists:duplicate(Limit+10,1),
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
+ {max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ %% Check that it doesn't rekey without data transfer
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that datatransfer triggers rekeying
+ ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
+
+ %% Check that datatransfer continues to trigger rekeying
+ ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
+
+ %% Check that it doesn't rekey without data transfer
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it doesn't rekey on a small datatransfer
+ ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it doesn't rekey without data transfer
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+
+
+rekey_limit_daemon() -> [{timetrap,{seconds,500}}].
+rekey_limit_daemon(Config) ->
+ Limit = 6000,
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile1 = filename:join(UserDir, "rekey1.data"),
+ DataFile2 = filename:join(UserDir, "rekey2.data"),
+ file:write_file(DataFile1, lists:duplicate(Limit+10,1)),
+ file:write_file(DataFile2, "hi\n"),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
+ {max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ %% Check that it doesn't rekey without data transfer
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+ timer:sleep(?REKEY_DATA_TMO),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ %% Check that datatransfer triggers rekeying
+ {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
+
+ %% Check that datatransfer continues to trigger rekeying
+ {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1),
+ timer:sleep(?REKEY_DATA_TMO),
+ ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10),
+
+ %% Check that it doesn't rekey without data transfer
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it doesn't rekey on a small datatransfer
+ {ok,_} = ssh_sftp:read_file(SftpPid, DataFile2),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it doesn't rekey without data transfer
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+
+%%--------------------------------------------------------------------
+%% Check that datatransfer in the other direction does not trigger re-keying
+norekey_limit_client() -> [{timetrap,{seconds,500}}].
+norekey_limit_client(Config) ->
+ Limit = 6000,
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "rekey3.data"),
+ file:write_file(DataFile, lists:duplicate(Limit+10,1)),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit},
+ {max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ {ok,_} = ssh_sftp:read_file(SftpPid, DataFile),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%% Check that datatransfer in the other direction does not trigger re-keying
+norekey_limit_daemon() -> [{timetrap,{seconds,500}}].
+norekey_limit_daemon(Config) ->
+ Limit = 6000,
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "rekey4.data"),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit},
+ {max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ok = ssh_sftp:write_file(SftpPid, DataFile, lists:duplicate(Limit+10,1)),
+ timer:sleep(?REKEY_DATA_TMO),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+%%% Test rekeying by time
+
+rekey_time_limit_client() -> [{timetrap,{seconds,500}}].
+rekey_time_limit_client(Config) ->
+ Minutes = ?REKEY_DATA_TMO div 60000,
+ GB = 1024*1000*1000,
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, {Minutes, GB}},
+ {max_random_length_padding,0}]),
+ rekey_time_limit(Pid, ConnectionRef).
+
+rekey_time_limit_daemon() -> [{timetrap,{seconds,500}}].
+rekey_time_limit_daemon(Config) ->
+ Minutes = ?REKEY_DATA_TMO div 60000,
+ GB = 1024*1000*1000,
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, {Minutes, GB}},
+ {max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]),
+ rekey_time_limit(Pid, ConnectionRef).
+
+
+rekey_time_limit(Pid, ConnectionRef) ->
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ timer:sleep(5000),
+ true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)),
+
+ %% Check that it rekeys when the max time + 30s has passed
+ timer:sleep(?REKEY_DATA_TMO + 30*1000),
+ ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10),
+
+ %% Check that it does not rekey when nothing is transferred
+ timer:sleep(?REKEY_DATA_TMO + 30*1000),
+ ?wait_match(false, Kex2==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10),
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+
+%%% Test rekeying with simultaneous send request
+
+renegotiate1(Config) ->
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "renegotiate1.data"),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
+
+ ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
+
+ ssh_relay:hold(RelayPid, rx, 20, 1000),
+ ssh_connection_handler:renegotiate(ConnectionRef),
+ spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
+
+ timer:sleep(2000),
+
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ false = (Kex2 == Kex1),
+
+ ssh_relay:stop(RelayPid),
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+
+%%% Test rekeying with inflight messages from peer
+
+renegotiate2(Config) ->
+ UserDir = proplists:get_value(priv_dir, Config),
+ DataFile = filename:join(UserDir, "renegotiate2.data"),
+
+ Algs = proplists:get_value(preferred_algorithms, Config),
+ {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0},
+ {preferred_algorithms,Algs}]),
+
+ {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
+
+ ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
+
+ ssh_relay:hold(RelayPid, rx, 20, infinity),
+ spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
+ %% need a small pause here to ensure ssh_sftp:write is executed
+ ct:sleep(10),
+ ssh_connection_handler:renegotiate(ConnectionRef),
+ ssh_relay:release(RelayPid, rx),
+
+ timer:sleep(2000),
+
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
+
+ false = (Kex2 == Kex1),
+
+ ssh_relay:stop(RelayPid),
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa
new file mode 100644
index 0000000000..24628e071b
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3
+uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D
+txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd
+CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs
+picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr
+c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI
+hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4
+cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C
+ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC
+sQCY/I35vb6GUJn9tEdP
+-----END DSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..018ef6f537
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256
new file mode 100644
index 0000000000..4b1eb12eaa
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49
+AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br
+N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub
new file mode 100644
index 0000000000..a0147e60fa
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384
new file mode 100644
index 0000000000..4e8aa40959
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S
+PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd
+ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF
+8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub
new file mode 100644
index 0000000000..41e722e545
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521
new file mode 100644
index 0000000000..7196f46e97
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q
+9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5
+01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7
+XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf
+pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub
new file mode 100644
index 0000000000..8f059120bc
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519
new file mode 100644
index 0000000000..401a3e4a9a
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us
+aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw
+AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L
+ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub
new file mode 100644
index 0000000000..a5c03b19c1
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448
new file mode 100644
index 0000000000..8ecfd710dc
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh
+1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf
+fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32
+BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ
+m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub
new file mode 100644
index 0000000000..cec0765a5d
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa
new file mode 100644
index 0000000000..2202c2ead8
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w
+W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7
+LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ
+cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA
+ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b
+IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR
+OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn
+3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w
+W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5
+SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1
+A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI
+wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx
+5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx
+f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln
+4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8
+gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di
+d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp
+jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY
+SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg
+xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi
+b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF
+xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6
+Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur
+DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE
+oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg==
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..b4084d320a
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..fb1a862ded
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw
+mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3
+CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7
+Hneb/99fIYopdMH5NMnk60zGO1uZ2vc=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..428d5fb7d7
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..3e51ec2ecd
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx
+U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq
+Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/
+vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5
+ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..017a29f4da
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key
new file mode 100644
index 0000000000..31a7e4e8c3
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key
@@ -0,0 +1,10 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz
+c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2
+m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA
+ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl
+D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS
+jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f
+ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ=
+-----END OPENSSH PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub
new file mode 100644
index 0000000000..8c390dcb58
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..79968bdd7d
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub
@@ -0,0 +1,5 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8
+semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW
+RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl
index 9b36bbfe8b..14d1a0173d 100644
--- a/lib/ssh/test/ssh_sftp_SUITE.erl
+++ b/lib/ssh/test/ssh_sftp_SUITE.erl
@@ -21,8 +21,62 @@
%%
-module(ssh_sftp_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ ascii_filename_ascii_contents_to_tar/1,
+ ascii_filename_unicode_contents_to_tar/1,
+ async_read/1,
+ async_write/1,
+ big_file_to_tar/1,
+ binaries_to_tar/1,
+ block_size_16_crypto_tar/1,
+ block_size_1_crypto_tar/1,
+ create_empty_tar/1,
+ directory_to_tar/1,
+ file_owner_access/1,
+ files_chunked_to_tar/1,
+ files_to_tar/1,
+ links/1,
+ mk_rm_dir/1,
+ null_crypto_tar/1,
+ open_close_dir/1,
+ open_close_file/1,
+ pos_read/1,
+ pos_write/1,
+ position/1,
+ read_crypto_tar/1,
+ read_dir/1,
+ read_file/1,
+ read_null_crypto_tar/1,
+ read_tar/1,
+ remove_file/1,
+ rename_file/1,
+ retrieve_attributes/1,
+ set_attributes/1,
+ sftp_nonexistent_subsystem/1,
+ sftp_read_big_file/1,
+ simple_crypto_tar_big/1,
+ simple_crypto_tar_small/1,
+ start_channel_sock/1,
+ stuff/1,
+ unicode_filename_ascii_contents_to_tar/1,
+ unstuff/1,
+ version_option/1,
+ write_big_file/1,
+ write_file/1,
+ write_file_iolist/1
+ ]).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
@@ -86,7 +140,7 @@ groups() ->
directory_to_tar, binaries_to_tar, null_crypto_tar,
simple_crypto_tar_small, simple_crypto_tar_big,
read_tar, read_null_crypto_tar, read_crypto_tar,
- aes_cbc256_crypto_tar, aes_ctr_stream_crypto_tar
+ block_size_1_crypto_tar, block_size_16_crypto_tar
]},
{write_read_tests, [], [open_close_file, open_close_dir, read_file, read_dir,
@@ -330,8 +384,6 @@ end_per_testcase(Config) ->
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-open_close_file() ->
- [{doc, "Test API functions open/3 and close/2"}].
open_close_file(Config) when is_list(Config) ->
FileName = proplists:get_value(filename, Config),
SftpFileName = w2l(Config, FileName),
@@ -350,8 +402,6 @@ open_close_file(Server, File, Mode) ->
ok = ssh_sftp:close(Server, Handle).
%%--------------------------------------------------------------------
-open_close_dir() ->
- [{doc, "Test API functions opendir/2 and close/2"}].
open_close_dir(Config) when is_list(Config) ->
PrivDir = proplists:get_value(sftp_priv_dir, Config),
SftpPrivDir = w2l(Config, PrivDir),
@@ -364,8 +414,6 @@ open_close_dir(Config) when is_list(Config) ->
{error, _} = ssh_sftp:opendir(Sftp, SftpFileName).
%%--------------------------------------------------------------------
-read_file() ->
- [{doc, "Test API funtion read_file/2"}].
read_file(Config) when is_list(Config) ->
FileName = proplists:get_value(filename, Config),
SftpFileName = w2l(Config, FileName),
@@ -375,8 +423,6 @@ read_file(Config) when is_list(Config) ->
{ok, Data} = file:read_file(FileName).
%%--------------------------------------------------------------------
-read_dir() ->
- [{doc,"Test API function list_dir/2"}].
read_dir(Config) when is_list(Config) ->
PrivDir = proplists:get_value(sftp_priv_dir, Config),
SftpPrivDir = w2l(Config, PrivDir),
@@ -386,8 +432,6 @@ read_dir(Config) when is_list(Config) ->
ct:log("sftp list dir: ~p~n", [Files]).
%%--------------------------------------------------------------------
-write_file() ->
- [{doc, "Test API function write_file/2"}].
write_file(Config) when is_list(Config) ->
FileName = proplists:get_value(filename, Config),
SftpFileName = w2l(Config, FileName),
@@ -398,8 +442,6 @@ write_file(Config) when is_list(Config) ->
{ok, Expected} = file:read_file(FileName).
%%--------------------------------------------------------------------
-write_file_iolist() ->
- [{doc, "Test API function write_file/2 with iolists"}].
write_file_iolist(Config) when is_list(Config) ->
FileName = proplists:get_value(filename, Config),
SftpFileName = w2l(Config, FileName),
@@ -421,8 +463,6 @@ write_file_iolist(Config) when is_list(Config) ->
]).
%%--------------------------------------------------------------------
-write_big_file() ->
- [{doc, "Test API function write_file/2 with big data"}].
write_big_file(Config) when is_list(Config) ->
FileName = proplists:get_value(filename, Config),
SftpFileName = w2l(Config, FileName),
@@ -435,8 +475,6 @@ write_big_file(Config) when is_list(Config) ->
{ok, Expected} = file:read_file(FileName).
%%--------------------------------------------------------------------
-sftp_read_big_file() ->
- [{doc, "Test API function read_file/2 with big data"}].
sftp_read_big_file(Config) when is_list(Config) ->
FileName = proplists:get_value(filename, Config),
SftpFileName = w2l(Config, FileName),
@@ -449,8 +487,6 @@ sftp_read_big_file(Config) when is_list(Config) ->
{ok, Expected} = ssh_sftp:read_file(Sftp, SftpFileName).
%%--------------------------------------------------------------------
-remove_file() ->
- [{doc,"Test API function delete/2"}].
remove_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(sftp_priv_dir, Config),
SftpPrivDir = w2l(Config, PrivDir),
@@ -465,8 +501,6 @@ remove_file(Config) when is_list(Config) ->
false = lists:member(filename:basename(FileName), NewFiles),
{error, no_such_file} = ssh_sftp:delete(Sftp, SftpFileName).
%%--------------------------------------------------------------------
-rename_file() ->
- [{doc, "Test API function rename_file/2"}].
rename_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(sftp_priv_dir, Config),
SftpPrivDir = w2l(Config, PrivDir),
@@ -488,8 +522,6 @@ rename_file(Config) when is_list(Config) ->
true = lists:member(filename:basename(NewFileName), NewFiles).
%%--------------------------------------------------------------------
-mk_rm_dir() ->
- [{doc,"Test API functions make_dir/2, del_dir/2"}].
mk_rm_dir(Config) when is_list(Config) ->
PrivDir = proplists:get_value(sftp_priv_dir, Config),
SftpPrivDir = w2l(Config, PrivDir),
@@ -505,8 +537,6 @@ mk_rm_dir(Config) when is_list(Config) ->
{error, _} = ssh_sftp:del_dir(Sftp, SftpPrivDir).
%%--------------------------------------------------------------------
-links() ->
- [{doc,"Tests API function make_symlink/3"}].
links(Config) when is_list(Config) ->
case os:type() of
{win32, _} ->
@@ -523,8 +553,6 @@ links(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-retrieve_attributes() ->
- [{doc, "Test API function read_file_info/3"}].
retrieve_attributes(Config) when is_list(Config) ->
FileName = proplists:get_value(filename, Config),
SftpFileName = w2l(Config, FileName),
@@ -537,8 +565,6 @@ retrieve_attributes(Config) when is_list(Config) ->
ct:log("SFTP: ~p FILE: ~p~n", [FileInfo, NewFileInfo]).
%%--------------------------------------------------------------------
-set_attributes() ->
- [{doc,"Test API function write_file_info/3"}].
set_attributes(Config) when is_list(Config) ->
FileName = proplists:get_value(testfile, Config),
SftpFileName = w2l(Config, FileName),
@@ -552,8 +578,6 @@ set_attributes(Config) when is_list(Config) ->
ok = file:write_file(FileName, "hello again").
%%--------------------------------------------------------------------
-file_owner_access() ->
- [{doc,"Test file user access validity"}].
file_owner_access(Config) when is_list(Config) ->
case os:type() of
{win32, _} ->
@@ -583,8 +607,6 @@ file_owner_access(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-async_read() ->
- [{doc,"Test API aread/3"}].
async_read(Config) when is_list(Config) ->
{Sftp, _} = proplists:get_value(sftp, Config),
@@ -603,8 +625,6 @@ async_read(Config) when is_list(Config) ->
30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
%%--------------------------------------------------------------------
-async_write() ->
- [{doc,"Test API awrite/3"}].
async_write(Config) when is_list(Config) ->
{Sftp, _} = proplists:get_value(sftp, Config),
FileName = proplists:get_value(testfile, Config),
@@ -622,9 +642,6 @@ async_write(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-
-position() ->
- [{doc, "Test API functions position/3"}].
position(Config) when is_list(Config) ->
FileName = proplists:get_value(testfile, Config),
SftpFileName = w2l(Config, FileName),
@@ -653,8 +670,6 @@ position(Config) when is_list(Config) ->
{ok, "2"} = ssh_sftp:read(Sftp, Handle, 1).
%%--------------------------------------------------------------------
-pos_read() ->
- [{doc,"Test API functions pread/3 and apread/3"}].
pos_read(Config) when is_list(Config) ->
FileName = proplists:get_value(testfile, Config),
SftpFileName = w2l(Config, FileName),
@@ -682,8 +697,6 @@ pos_read(Config) when is_list(Config) ->
{ok,Expect1} = ssh_sftp:pread(Sftp, Handle, {bof,0}, Len1).
%%--------------------------------------------------------------------
-pos_write() ->
- [{doc,"Test API functions pwrite/4 and apwrite/4"}].
pos_write(Config) when is_list(Config) ->
FileName = proplists:get_value(testfile, Config),
SftpFileName = w2l(Config, FileName),
@@ -767,8 +780,6 @@ start_channel_sock(Config) ->
ok.
%%--------------------------------------------------------------------
-sftp_nonexistent_subsystem() ->
- [{doc, "Try to execute sftp subsystem on a server that does not support it"}].
sftp_nonexistent_subsystem(Config) when is_list(Config) ->
{_,Host, Port} = proplists:get_value(sftpd, Config),
User = proplists:get_value(user, Config),
@@ -781,8 +792,6 @@ 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).
@@ -1017,54 +1026,10 @@ read_crypto_tar(Config) ->
chk_tar(NameBins, Config, [{crypto,Cr}]).
%%--------------------------------------------------------------------
-aes_cbc256_crypto_tar(Config) ->
- ChPid2 = proplists:get_value(channel_pid2, Config),
- NameBins = lists:sort(
- [{"b1",<<"A binary">>},
- {"b2",list_to_binary(lists:duplicate(750000,"a"))},
- {"d1",fn("d1",Config)} % Dir
- ]),
- Key = <<"This is a 256 bit key. Boring...">>,
- Ivec0 = crypto:strong_rand_bytes(16),
- DataSize = 1024, % data_size rem 16 = 0 for aes_cbc
-
- Cinitw = fun() -> {ok, Ivec0, DataSize} end,
- Cinitr = fun() -> {ok, Ivec0, DataSize} end,
-
- Cenc = fun(PlainBin,Ivec) ->
- CipherBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, PlainBin),
- {ok, CipherBin, crypto:next_iv(aes_cbc,CipherBin), DataSize}
- end,
- Cdec = fun(CipherBin,Ivec) ->
- PlainBin = crypto:block_decrypt(aes_cbc256, Key, Ivec, CipherBin),
- {ok, PlainBin, crypto:next_iv(aes_cbc,CipherBin), DataSize}
- end,
-
- Cendw = fun(PlainBin, _) when PlainBin == <<>> -> {ok, <<>>};
- (PlainBin, Ivec) ->
- CipherBin = crypto:block_encrypt(aes_cbc256, Key, Ivec,
- pad(16,PlainBin)), %% Last chunk
- {ok, CipherBin}
- end,
-
- Cw = {Cinitw,Cenc,Cendw},
- TarFileName = proplists:get_value(tar_filename, Config),
- SftpTarFileName = w2l(Config, TarFileName),
-
- {ok,HandleWrite} = ssh_sftp:open_tar(ChPid2, SftpTarFileName, [write,{crypto,Cw}]),
- [ok = erl_tar:add(HandleWrite, Bin, Name, [verbose]) || {Name,Bin} <- NameBins],
- ok = erl_tar:close(HandleWrite),
-
- Cr = {Cinitr,Cdec},
- chk_tar(NameBins, Config, [{crypto,Cr}]).
-
+block_size_16_crypto_tar(Config) -> cipher_crypto_tar(aes_256_cbc, Config).
+block_size_1_crypto_tar(Config) -> cipher_crypto_tar(aes_256_ctr, Config).
-pad(BlockSize, Bin) ->
- PadSize = (BlockSize - (size(Bin) rem BlockSize)) rem BlockSize,
- list_to_binary( lists:duplicate(PadSize,0) ).
-
-%%--------------------------------------------------------------------
-aes_ctr_stream_crypto_tar(Config) ->
+cipher_crypto_tar(Cipher, Config) ->
ChPid2 = proplists:get_value(channel_pid2, Config),
NameBins = lists:sort(
[{"b1",<<"A binary">>},
@@ -1074,22 +1039,25 @@ aes_ctr_stream_crypto_tar(Config) ->
Key = <<"This is a 256 bit key. Boring...">>,
Ivec0 = crypto:strong_rand_bytes(16),
- Cinitw = Cinitr = fun() -> {ok, crypto:stream_init(aes_ctr,Key,Ivec0)} end,
+ Cinitw = fun() -> {ok, crypto:crypto_init(Cipher,Key,Ivec0,[{encrypt,true},
+ {padding,zero}])} end,
+ Cinitr = fun() -> {ok, crypto:crypto_init(Cipher,Key,Ivec0,false)} end,
Cenc = fun(PlainBin,State) ->
- {NewState,CipherBin} = crypto:stream_encrypt(State, PlainBin),
- {ok, CipherBin, NewState}
+ CipherBin = crypto:crypto_update(State, PlainBin),
+ {ok, CipherBin, State}
end,
Cdec = fun(CipherBin,State) ->
- {NewState,PlainBin} = crypto:stream_decrypt(State, CipherBin),
- {ok, PlainBin, NewState}
+ PlainBin = crypto:crypto_update(State, CipherBin),
+ {ok, PlainBin, State}
end,
- Cendw = fun(PlainBin, _) when PlainBin == <<>> -> {ok, <<>>};
- (PlainBin, Ivec) ->
- CipherBin = crypto:block_encrypt(aes_cbc256, Key, Ivec,
- pad(16,PlainBin)), %% Last chunk
- {ok, CipherBin}
+ Cendw = fun(PlainBin, State) ->
+ CipherBin1 = crypto:crypto_update(State, PlainBin),
+ Sz1 = size(CipherBin1),
+ CipherBin2 = crypto:crypto_final(State),
+ Sz2 = size(CipherBin2),
+ {ok, <<CipherBin1:Sz1/binary, CipherBin2:Sz2/binary>>}
end,
Cw = {Cinitw,Cenc,Cendw},
@@ -1106,40 +1074,9 @@ aes_ctr_stream_crypto_tar(Config) ->
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
-old_prepare(Config0) ->
- PrivDir = proplists:get_value(priv_dir, Config0),
- Dir = filename:join(PrivDir, ssh_test_lib:random_chars(10)),
- file:make_dir(Dir),
- ct:log("~p:~p created the directory~nsftp_priv_dir = ~p", [?MODULE,?LINE,Dir]),
- Keys = [filename,
- testfile,
- linktest,
- tar_filename],
- Config1 = foldl_keydelete(Keys, Config0),
- Config2 = lists:foldl(fun({Key,Name}, ConfAcc) ->
- [{Key, filename:join(Dir,Name)} | ConfAcc]
- end,
- Config1,
- lists:zip(Keys, [proplists:get_value(K,Config0) || K<-Keys])),
-
- catch ct:log("~p:~p Prepared filenames (Key -> Value):~n~ts",
- [?MODULE,?LINE,
- [io_lib:format("~p -> ~ts~n", [K,V]) || {K,V} <- Config2,
- lists:member(K, Keys)]]),
-
- DataDir = proplists:get_value(data_dir, Config2),
- FilenameSrc = filename:join(DataDir, "sftp.txt"),
- FilenameDst = proplists:get_value(filename, Config2),
- {ok,_} = file:copy(FilenameSrc, FilenameDst),
- [{sftp_priv_dir,Dir} | Config2].
-
-
have_unicode_support() -> (file:native_name_encoding() == utf8) andalso ("四" == [22235]).
-make_data_sub_dir(Config, SubDir) ->
- make_data_sub_dir(Config, SubDir, SubDir).
-
make_data_sub_dir(Config, SubDirSrc, SubDirDst) ->
SrcDir = filename:join(proplists:get_value(data_dir, Config),
SubDirSrc
diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl
index 852801d013..70a0049730 100644
--- a/lib/ssh/test/ssh_sftpd_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_SUITE.erl
@@ -21,8 +21,42 @@
%%
-module(ssh_sftpd_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ access_outside_root/1,
+ links/1,
+ mk_rm_dir/1,
+ open_close_dir/1,
+ open_close_file/1,
+ open_file_dir_v5/1,
+ open_file_dir_v6/1,
+ read_dir/1,
+ read_file/1,
+ real_path/1,
+ relative_path/1,
+ relpath/1,
+ remove_file/1,
+ rename_file/1,
+ retrieve_attributes/1,
+ root_with_cwd/1,
+ set_attributes/1,
+ sshd_read_file/1,
+ ver3_open_flags/1,
+ ver3_rename/1,
+ ver6_basic/1,
+ write_file/1
+ ]).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
@@ -80,9 +114,9 @@ groups() ->
init_per_suite(Config) ->
?CHECK_CRYPTO(
begin
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
+ ssh:start(),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
%% to make sure we don't use public-key-auth
%% this should be tested by other test suites
UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
@@ -91,8 +125,6 @@ init_per_suite(Config) ->
end).
end_per_suite(Config) ->
- SysDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(SysDir),
UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
file:del_dir(UserDir),
ssh:stop().
@@ -196,8 +228,6 @@ end_per_testcase(_TestCase, Config) ->
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-open_close_file() ->
- [{doc, "Test SSH_FXP_OPEN and SSH_FXP_CLOSE commands"}].
open_close_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
@@ -226,8 +256,6 @@ open_close_file(Config) when is_list(Config) ->
?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
?SSH_FXF_OPEN_EXISTING).
-ver3_open_flags() ->
- [{doc, "Test open flags"}].
ver3_open_flags(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "not_exist.txt"),
@@ -260,8 +288,6 @@ ver3_open_flags(Config) when is_list(Config) ->
Cm, Channel).
%%--------------------------------------------------------------------
-open_close_dir() ->
- [{doc,"Test SSH_FXP_OPENDIR and SSH_FXP_CLOSE commands"}].
open_close_dir(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
{Cm, Channel} = proplists:get_value(sftp, Config),
@@ -287,8 +313,6 @@ open_close_dir(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-read_file() ->
- [{doc, "Test SSH_FXP_READ command"}].
read_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
@@ -310,8 +334,6 @@ read_file(Config) when is_list(Config) ->
{ok, Data} = file:read_file(FileName).
%%--------------------------------------------------------------------
-read_dir() ->
- [{doc,"Test SSH_FXP_READDIR command"}].
read_dir(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
{Cm, Channel} = proplists:get_value(sftp, Config),
@@ -321,8 +343,6 @@ read_dir(Config) when is_list(Config) ->
ok = read_dir(Handle, Cm, Channel, ReqId).
%%--------------------------------------------------------------------
-write_file() ->
- [{doc, "Test SSH_FXP_WRITE command"}].
write_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
@@ -345,8 +365,6 @@ write_file(Config) when is_list(Config) ->
{ok, Data} = file:read_file(FileName).
%%--------------------------------------------------------------------
-remove_file() ->
- [{doc, "Test SSH_FXP_REMOVE command"}].
remove_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
@@ -366,8 +384,6 @@ remove_file(Config) when is_list(Config) ->
remove(PrivDir, Cm, Channel, NewReqId).
%%--------------------------------------------------------------------
-rename_file() ->
- [{doc, "Test SSH_FXP_RENAME command"}].
rename_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
@@ -403,8 +419,6 @@ rename_file(Config) when is_list(Config) ->
?SSH_FXP_RENAME_ATOMIC).
%%--------------------------------------------------------------------
-mk_rm_dir() ->
- [{doc, "Test SSH_FXP_MKDIR and SSH_FXP_RMDIR command"}].
mk_rm_dir(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
{Cm, Channel} = proplists:get_value(sftp, Config),
@@ -426,8 +440,6 @@ mk_rm_dir(Config) when is_list(Config) ->
_/binary>>, _} = rmdir(DirName, Cm, Channel, NewReqId2).
%%--------------------------------------------------------------------
-real_path() ->
- [{doc, "Test SSH_FXP_REALPATH command"}].
real_path(Config) when is_list(Config) ->
case os:type() of
{win32, _} ->
@@ -481,8 +493,6 @@ links(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-retrieve_attributes() ->
- [{"Test SSH_FXP_STAT, SSH_FXP_LSTAT AND SSH_FXP_FSTAT commands"}].
retrieve_attributes(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
@@ -546,8 +556,6 @@ retrieve_attributes(Config) when is_list(Config) ->
end, AttrValues).
%%--------------------------------------------------------------------
-set_attributes() ->
- [{doc, "Test SSH_FXP_SETSTAT AND SSH_FXP_FSETSTAT commands"}].
set_attributes(Config) when is_list(Config) ->
case os:type() of
{win32, _} ->
@@ -604,8 +612,6 @@ set_attributes(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-ver3_rename() ->
- [{doc, "Test that ver3 rename message is handled OTP 6352"}].
ver3_rename(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
@@ -618,8 +624,6 @@ ver3_rename(Config) when is_list(Config) ->
rename(FileName, NewFileName, Cm, Channel, ReqId, 3, 0).
%%--------------------------------------------------------------------
-relpath() ->
- [{doc, "Check that realpath works ok seq10670"}].
relpath(Config) when is_list(Config) ->
ReqId = 0,
{Cm, Channel} = proplists:get_value(sftp, Config),
@@ -641,8 +645,6 @@ relpath(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-sshd_read_file() ->
- [{doc,"Test SSH_FXP_READ command, using sshd-server"}].
sshd_read_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
@@ -663,8 +665,6 @@ sshd_read_file(Config) when is_list(Config) ->
{ok, Data} = file:read_file(FileName).
%%--------------------------------------------------------------------
-ver6_basic() ->
- [{doc, "Test SFTP Version 6"}].
ver6_basic(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
%FileName = filename:join(PrivDir, "test.txt"),
@@ -677,8 +677,6 @@ ver6_basic(Config) when is_list(Config) ->
?SSH_FXF_OPEN_EXISTING).
%%--------------------------------------------------------------------
-access_outside_root() ->
- [{doc, "Try access files outside the tree below RootDir"}].
access_outside_root(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
BaseDir = filename:join(PrivDir, access_outside_root),
@@ -723,8 +721,6 @@ try_access(Path, Cm, Channel, ReqId) ->
end.
%%--------------------------------------------------------------------
-root_with_cwd() ->
- [{doc, "Check if files are found, if the CWD and Root are specified"}].
root_with_cwd(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
RootDir = filename:join(PrivDir, root_with_cwd),
@@ -753,8 +749,6 @@ root_with_cwd(Config) when is_list(Config) ->
?SSH_FXF_OPEN_EXISTING).
%%--------------------------------------------------------------------
-relative_path() ->
- [{doc, "Test paths relative to CWD when opening a file handle."}].
relative_path(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = "test_relative_path.txt",
@@ -769,8 +763,6 @@ relative_path(Config) when is_list(Config) ->
?SSH_FXF_OPEN_EXISTING).
%%--------------------------------------------------------------------
-open_file_dir_v5() ->
- [{doc, "Test if open_file fails when opening existing directory."}].
open_file_dir_v5(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = "open_file_dir_v5",
@@ -786,8 +778,6 @@ open_file_dir_v5(Config) when is_list(Config) ->
?SSH_FXF_OPEN_EXISTING).
%%--------------------------------------------------------------------
-open_file_dir_v6() ->
- [{doc, "Test if open_file fails when opening existing directory."}].
open_file_dir_v6(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = "open_file_dir_v6",
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
index 417b5c4f16..accdd33557 100644
--- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
@@ -21,8 +21,26 @@
%%
-module(ssh_sftpd_erlclient_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ close_file/1,
+ file_cb/1,
+ list_dir_limited/1,
+ quit/1,
+ root_dir/1,
+ ver6_basic/1
+ ]).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
@@ -58,24 +76,18 @@ init_per_suite(Config) ->
begin
catch ssh:stop(),
DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
FileAlt = filename:join(DataDir, "ssh_sftpd_file_alt.erl"),
c:c(FileAlt),
FileName = filename:join(DataDir, "test.txt"),
{ok, FileInfo} = file:read_file_info(FileName),
ok = file:write_file_info(FileName,
FileInfo#file_info{mode = 8#400}),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
+ ct:log("Pub keys setup for: ~p",
+ [ssh_test_lib:setup_all_user_host_keys(Config)]),
Config
end).
-end_per_suite(Config) ->
- UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
- file:del_dir(UserDir),
- SysDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(SysDir),
- ssh_test_lib:clean_dsa(SysDir),
+end_per_suite(_Config) ->
ok.
%%--------------------------------------------------------------------
@@ -86,52 +98,45 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
%%--------------------------------------------------------------------
-
init_per_testcase(TestCase, Config) ->
ssh:start(),
- PrivDir = proplists:get_value(priv_dir, Config),
- SystemDir = filename:join(PrivDir, system),
+ UserDir = PrivDir = proplists:get_value(priv_dir, Config),
+ SysDir = filename:join(PrivDir,"system"),
Options =
case atom_to_list(TestCase) of
"file_cb" ++ _ ->
- Spec =
- ssh_sftpd:subsystem_spec([{file_handler,
- ssh_sftpd_file_alt}]),
- [{system_dir, SystemDir},
- {user_dir, PrivDir},
- {subsystems, [Spec]}];
+ Spec = ssh_sftpd:subsystem_spec([{file_handler,
+ ssh_sftpd_file_alt}]),
+ [{subsystems, [Spec]}];
"root_dir" ->
- Privdir = proplists:get_value(priv_dir, Config),
- Root = filename:join(Privdir, root),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Root = filename:join(PrivDir, root),
file:make_dir(Root),
Spec = ssh_sftpd:subsystem_spec([{root,Root}]),
- [{system_dir, SystemDir},
- {user_dir, PrivDir},
- {subsystems, [Spec]}];
+ [{subsystems, [Spec]}];
"list_dir_limited" ->
- Spec =
- ssh_sftpd:subsystem_spec([{max_files,1}]),
- [{system_dir, SystemDir},
- {user_dir, PrivDir},
- {subsystems, [Spec]}];
+ Spec = ssh_sftpd:subsystem_spec([{max_files,1}]),
+ [{subsystems, [Spec]}];
"ver6_basic" ->
- Spec =
- ssh_sftpd:subsystem_spec([{sftpd_vsn, 6}]),
- [{system_dir, SystemDir},
- {user_dir, PrivDir},
- {subsystems, [Spec]}];
+ Spec = ssh_sftpd:subsystem_spec([{sftpd_vsn, 6}]),
+ [{subsystems, [Spec]}];
_ ->
- [{user_dir, PrivDir},
- {system_dir, SystemDir}]
+ []
end,
- {Sftpd, Host, Port} = ssh_test_lib:daemon(Options),
+ {Sftpd, Host, Port} = ssh_test_lib:daemon([{preferred_algorithms,
+ ssh_transport:supported_algorithms()},
+ {system_dir, SysDir},
+ {user_dir, UserDir},
+ {user_passwords, [{?USER,?PASSWD}]}
+ | Options]),
{ok, ChannelPid, Connection} =
ssh_sftp:start_channel(Host, Port,
[{silently_accept_hosts, true},
- {user_dir, PrivDir},
+ {preferred_algorithms, ssh_transport:supported_algorithms()},
+ {user_dir, UserDir},
{timeout, 30000}]),
TmpConfig = lists:keydelete(sftp, 1, Config),
NewConfig = lists:keydelete(sftpd, 1, TmpConfig),
@@ -147,10 +152,6 @@ end_per_testcase(_TestCase, Config) ->
%%--------------------------------------------------------------------
%% Test cases starts here. -------------------------------------------
%%--------------------------------------------------------------------
-close_file() ->
- [{doc, "Test that sftpd closes its fildescriptors after compleating the "
- "transfer OTP-6350"}].
-
close_file(Config) when is_list(Config) ->
DataDir = proplists:get_value(data_dir, Config),
FileName = filename:join(DataDir, "test.txt"),
@@ -166,12 +167,6 @@ close_file(Config) when is_list(Config) ->
NumOfPorts = length(erlang:ports()).
%%--------------------------------------------------------------------
-
-quit() ->
- [{doc, " When the sftp client ends the session the "
- "server will now behave correctly and not leave the "
- "client hanging. OTP-6349"}].
-
quit(Config) when is_list(Config) ->
DataDir = proplists:get_value(data_dir, Config),
FileName = filename:join(DataDir, "test.txt"),
@@ -189,6 +184,8 @@ quit(Config) when is_list(Config) ->
timer:sleep(5000),
{ok, NewSftp, _Conn} = ssh_sftp:start_channel(Host, Port,
[{silently_accept_hosts, true},
+ {preferred_algorithms,
+ ssh_transport:supported_algorithms()},
{user_dir, UserDir},
{user, ?USER}, {password, ?PASSWD}]),
@@ -197,11 +194,6 @@ quit(Config) when is_list(Config) ->
ok = ssh_sftp:stop_channel(NewSftp).
%%--------------------------------------------------------------------
-
-file_cb() ->
- [{"Test that it is possible to change the callback module for"
- " the sftpds filehandling. OTP-6356"}].
-
file_cb(Config) when is_list(Config) ->
DataDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
@@ -264,8 +256,6 @@ list_dir_limited(Config) when is_list(Config) ->
ct:log("Listing: ~p~n", [Listing]).
%%--------------------------------------------------------------------
-ver6_basic() ->
- [{doc, "Test some version 6 features"}].
ver6_basic(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
NewDir = filename:join(PrivDir, "testdir2"),
@@ -273,10 +263,10 @@ ver6_basic(Config) when is_list(Config) ->
ok = ssh_sftp:make_dir(Sftp, NewDir),
%%Test file_is_a_directory
{error, file_is_a_directory} = ssh_sftp:delete(Sftp, NewDir).
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
-
alt_file_handler_check(Msg) ->
receive
Msg ->
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/authorized_keys b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/authorized_keys
new file mode 100644
index 0000000000..de8031ff09
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/authorized_keys
@@ -0,0 +1,4 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAKNvLwC0zNq31/V9bdnW3CcpuAzIIhHdq1YIWR86rS3kLVwSiHCi4qzi88+tZ6XUDpdB9IY8xilFZuk8NdCaEJ9jaCIpQbTJfSJoACxhRqZKCFamJXlIxrgI9MADuc3pO3wjBmQlj6Izwpm5m3Gwp4FG6kOJycEtcdCEbFnV13ovAAAAFQD4DVuhRHaz9OmJaGpwrsZlbtTcQQAAAIAhQbL6jEaJUClTwmAgjmHTYF5pADgrwz8dznqbDcveK8rsh1bJXyP2lV7uodiOQPHnyvuxf4WcHN8VihcKYcQIPJi4PXLzfNbibN0+QtLiriHQp8UztSd13LJPasSS3vOlx9VhdIvXhLZXV8tV5LeeQ7DejzQYeL5pbvJZCNwrtwAAAIBkxv0UvuBnhKLDW0HH09dfkocPqUGy7PzOgDumTI48OPML/kdG5gqmkJrsANIEowVzl5gb0VLQFcOhJsSUBbu6GYkCPOQmqxp4qWFfsoSdDxJWGJxkIt30RLxPvoub3yyGTDw3lkpkmctaybVu0H93iPBol0rjNMSKv66e3mhnYA== uabhnil@elxadlj3q32
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAFiU3ECc+pvCdcwrhQoCxrs1Wn1pcf5b938qY0X6FbCXoJ5xjcj5DFrUPRs7nT87ZIM4Osv3Fsum/JbLPtD500= uabhnil@elxadlj3q32
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF6DjNiKQXUx9KMIQvpZhkULBDX5TGPdbUM0Xz1GvyQN uabhnil@elxadlj3q32
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9rjL5w+XPI74lO9KHrDg0DAaGkPaPfPkBODlhIft8+8k3mxFhvjrSCfKqQy8W2U9/3H6Ib3203d2utonw70xxR/wWfQeB7X9FDROfvXkf5hWSyv1NENTF88ccBSFjmNmgIlt9jl3sAN+s4pF9V2kpy0j95y2rGm6AiichRVSxUhLixuQUmgpsPgPKz6HjR8Qf8Fxa5hPChhGa6UZonbacFYcixTHyQi9hj/kSgydalx2PcRENgn58mr8y5MC49L44HPGtjiMWwWpC6VqtdnYgY7r1LzrR+H691Ut1pAv4+vnNUhK0ROGftWUaQ1DG8cNYrhG270HaM/Zbe960XVhH uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_dsa b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_dsa
index d306f8b26e..403904bb16 100644
--- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_dsa
@@ -1,13 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
-MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
-APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
-/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
-kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
-JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
-OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
-+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
-uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
-Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
-ZU8w8Q+H7z0j+a+70x2iAw==
+MIIBuwIBAAKBgQCjby8AtMzat9f1fW3Z1twnKbgMyCIR3atWCFkfOq0t5C1cEohw
+ouKs4vPPrWel1A6XQfSGPMYpRWbpPDXQmhCfY2giKUG0yX0iaAAsYUamSghWpiV5
+SMa4CPTAA7nN6Tt8IwZkJY+iM8KZuZtxsKeBRupDicnBLXHQhGxZ1dd6LwIVAPgN
+W6FEdrP06YloanCuxmVu1NxBAoGAIUGy+oxGiVApU8JgII5h02BeaQA4K8M/Hc56
+mw3L3ivK7IdWyV8j9pVe7qHYjkDx58r7sX+FnBzfFYoXCmHECDyYuD1y83zW4mzd
+PkLS4q4h0KfFM7UnddyyT2rEkt7zpcfVYXSL14S2V1fLVeS3nkOw3o80GHi+aW7y
+WQjcK7cCgYBkxv0UvuBnhKLDW0HH09dfkocPqUGy7PzOgDumTI48OPML/kdG5gqm
+kJrsANIEowVzl5gb0VLQFcOhJsSUBbu6GYkCPOQmqxp4qWFfsoSdDxJWGJxkIt30
+RLxPvoub3yyGTDw3lkpkmctaybVu0H93iPBol0rjNMSKv66e3mhnYAIVANrZ4g1R
+9YivPLIpevmFKRNA38+Y
-----END DSA PRIVATE KEY-----
-
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_dsa.pub
new file mode 100644
index 0000000000..ca26857c74
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAKNvLwC0zNq31/V9bdnW3CcpuAzIIhHdq1YIWR86rS3kLVwSiHCi4qzi88+tZ6XUDpdB9IY8xilFZuk8NdCaEJ9jaCIpQbTJfSJoACxhRqZKCFamJXlIxrgI9MADuc3pO3wjBmQlj6Izwpm5m3Gwp4FG6kOJycEtcdCEbFnV13ovAAAAFQD4DVuhRHaz9OmJaGpwrsZlbtTcQQAAAIAhQbL6jEaJUClTwmAgjmHTYF5pADgrwz8dznqbDcveK8rsh1bJXyP2lV7uodiOQPHnyvuxf4WcHN8VihcKYcQIPJi4PXLzfNbibN0+QtLiriHQp8UztSd13LJPasSS3vOlx9VhdIvXhLZXV8tV5LeeQ7DejzQYeL5pbvJZCNwrtwAAAIBkxv0UvuBnhKLDW0HH09dfkocPqUGy7PzOgDumTI48OPML/kdG5gqmkJrsANIEowVzl5gb0VLQFcOhJsSUBbu6GYkCPOQmqxp4qWFfsoSdDxJWGJxkIt30RLxPvoub3yyGTDw3lkpkmctaybVu0H93iPBol0rjNMSKv66e3mhnYA== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ecdsa256
new file mode 100644
index 0000000000..5200736c60
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ecdsa256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIBg4VtS15XVbsVK7NB1umrMpE6XfBDF3rC7LON6pyT9VoAoGCCqGSM49
+AwEHoUQDQgAEAWJTcQJz6m8J1zCuFCgLGuzVafWlx/lv3fypjRfoVsJegnnGNyPk
+MWtQ9GzudPztkgzg6y/cWy6b8lss+0PnTQ==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ecdsa256.pub
new file mode 100644
index 0000000000..fbe93b37e2
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ecdsa256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAFiU3ECc+pvCdcwrhQoCxrs1Wn1pcf5b938qY0X6FbCXoJ5xjcj5DFrUPRs7nT87ZIM4Osv3Fsum/JbLPtD500= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ed25519
new file mode 100644
index 0000000000..1e22094f4b
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBeg4zYikF1MfSjCEL6WYZFCwQ1+Uxj3W1DNF89Rr8kDQAAAJiM9onXjPaJ
+1wAAAAtzc2gtZWQyNTUxOQAAACBeg4zYikF1MfSjCEL6WYZFCwQ1+Uxj3W1DNF89Rr8kDQ
+AAAEAcEFZEHgihrAnH95hQzmkvaXLyRlTUznD6ybUzPFLyT16DjNiKQXUx9KMIQvpZhkUL
+BDX5TGPdbUM0Xz1GvyQNAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ed25519.pub
new file mode 100644
index 0000000000..d7376ecfef
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF6DjNiKQXUx9KMIQvpZhkULBDX5TGPdbUM0Xz1GvyQN uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa
index 9d7e0dd5fb..41d1ca459f 100644
--- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa
@@ -1,15 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
-DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
-zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
-AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
-TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
-CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
-SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
-z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
-WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
-sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
-xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
-dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
-ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+MIIEpAIBAAKCAQEAva4y+cPlzyO+JTvSh6w4NAwGhpD2j3z5ATg5YSH7fPvJN5sR
+Yb460gnyqkMvFtlPf9x+iG99tN3drraJ8O9McUf8Fn0Hge1/RQ0Tn715H+YVksr9
+TRDUxfPHHAUhY5jZoCJbfY5d7ADfrOKRfVdpKctI/ectqxpugIonIUVUsVIS4sbk
+FJoKbD4Dys+h40fEH/BcWuYTwoYRmulGaJ22nBWHIsUx8kIvYY/5EoMnWpcdj3ER
+DYJ+fJq/MuTAuPS+OBzxrY4jFsFqQularXZ2IGO69S860fh+vdVLdaQL+Pr5zVIS
+tEThn7VlGkNQxvHDWK4Rtu9B2jP2W3vetF1YRwIDAQABAoIBAGbE3zGcBgM45mje
+BK8ljD4G1fJK0IvltjQG9m++LbgF43uRNNW0b8IlSakeRYEntH8eRCA/5+f63YUv
+uYUk4lHuIpM8oriuqOoMr7k4u0uhKcci3CLJH78w2YxA61o/EopEkzVv0UtxHb9Z
+8mHq3juavK10d7Xg2EYklBberYh0c2VjMSPXTo2ZLF5SUSKkFhF0xGQx4QtPW1qw
+1yd7G5AeAehCnkmkQXPLHjhrkbGm5od9u4t8nrbfJ+aKj29NydcYxNeTk5dc2zmT
+iEC/Now0jr69518rWOOw7zn+zQ/q+3rJvEGWe58TJen4GvQCsDCUIibm2W3AduSt
+2MoEuGECgYEA9B2ACbdwexLmEd4Co1LWhPsKAPZvT5Ux2b8lA5fpGsTg/GeLMvDw
+3N1IWfHTLWnypIswCKyzBBJYdDX/JGA1tXbPT6VhLdNJsrPwC4D7xQUlieewyf3j
+SEKXdvmTOYWrCnAL4K/YZkmxl+bfC51E7KRthwVm4FgBMLx5bIeirbMCgYEAxupC
+DCzTX3/Q7/8YCUkHfZGLCm+FhdHK3qVgLYey4qUZ8CtKGTewWhnVHjGaTaZK11SR
+TCPYM02FspBUrsLVoqkAmwWMXK+yEVC/yr65OAvZfrDyPb9i0SZMfOF58gm0f9yW
+dcOTLKc0TQi5kWlAmyhh+Y5QSZjaD1MDu69qKR0CgYEApZ019mGbUAQcTlbeV0ul
++gYOXWTTU5yWe3GwfOWc4epb8SGWUHMiFdWSdmMXsCHG2lcLuXbg5IxFsmHHvTgA
+eCIot5Itgddskm59FnDlXl8Ffq/PQtqaTbXP6ImdllLXHXeytzbVi1/HupSJEeoa
+jqlnHUlO6DkMZbIYdsb2WP0CgYBtpFpbk6m/DhkA8L8y/wod8MePLQGQTZ9hMg7d
+idrUiuTNOU9SrxuJzotmZTkEbBD8uCSOr4NQY/65+kxzN/nB+x3WVSyVOlMQsE3H
+V3GIrx9ncelUl9XEKzQl8bLh8u+HOvPEXdUV2+Q8Qf7nY/i0jYHTqNogdxxbLgr0
+vS/rEQKBgQC7B88diAlcV2RMMl5BxreZe5OUNKJD+Xld1Jh25/N1QKon0YgfG+Zy
+i7qQi2Pwf4OuWan9OvgTXYWDt7odHsr7BjcFSf9nnC8KlAA3/J2ZYjrq3TNqvFvh
+DeYoQR3T1mci2vT2tntP0o1ZayXwYody5o9czzTMy0ndrwNo6zjhcA==
-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..1787eb5f56
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9rjL5w+XPI74lO9KHrDg0DAaGkPaPfPkBODlhIft8+8k3mxFhvjrSCfKqQy8W2U9/3H6Ib3203d2utonw70xxR/wWfQeB7X9FDROfvXkf5hWSyv1NENTF88ccBSFjmNmgIlt9jl3sAN+s4pF9V2kpy0j95y2rGm6AiichRVSxUhLixuQUmgpsPgPKz6HjR8Qf8Fxa5hPChhGa6UZonbacFYcixTHyQi9hj/kSgydalx2PcRENgn58mr8y5MC49L44HPGtjiMWwWpC6VqtdnYgY7r1LzrR+H691Ut1pAv4+vnNUhK0ROGftWUaQ1DG8cNYrhG270HaM/Zbe960XVhH uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ecdsa_key256
new file mode 100644
index 0000000000..353747bd24
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ecdsa_key256
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEILRQBlgIsj56NH+mbDlE2EnzwL3mPTV5Wu3qA/mzdHhtoAoGCCqGSM49
+AwEHoUQDQgAExl9ki6ar3omRG0wicxa0+hQcy6P7JYjGQH2g/PlWWUml78mCNDch
+7xDBsyPY/qfFOuo8mO8NGJcNd5vBb1fSXA==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ecdsa_key256.pub
new file mode 100644
index 0000000000..4a2c52c745
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ecdsa_key256.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMZfZIumq96JkRtMInMWtPoUHMuj+yWIxkB9oPz5VllJpe/JgjQ3Ie8QwbMj2P6nxTrqPJjvDRiXDXebwW9X0lw= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..c1eacf1bd7
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAlEkkLcygZhM8rFG7BB8DDqGXQIAB6j29QrtZJR4nvqwAAAJh9+iWjffol
+owAAAAtzc2gtZWQyNTUxOQAAACAlEkkLcygZhM8rFG7BB8DDqGXQIAB6j29QrtZJR4nvqw
+AAAEBhCKcv/8rlrKPgn48QJhHNsNUoNtCtSBida+k9V8CZ+iUSSQtzKBmEzysUbsEHwMOo
+ZdAgAHqPb1Cu1klHie+rAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..b6f5a17d78
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICUSSQtzKBmEzysUbsEHwMOoZdAgAHqPb1Cu1klHie+r uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_sup_SUITE.erl b/lib/ssh/test/ssh_sup_SUITE.erl
index 8a102651d8..8ad405b1d7 100644
--- a/lib/ssh/test/ssh_sup_SUITE.erl
+++ b/lib/ssh/test/ssh_sup_SUITE.erl
@@ -21,11 +21,29 @@
-module(ssh_sup_SUITE).
-include_lib("common_test/include/ct.hrl").
--include_lib("ssh/src/ssh.hrl").
+-include("ssh.hrl").
-include("ssh_test_lib.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ default_tree/1,
+ killed_acceptor_restarts/1,
+ shell_channel_tree/1,
+ sshc_subtree/1,
+ sshd_subtree/1,
+ sshd_subtree_profile/1
+ ]).
-define(USER, "Alladin").
-define(PASSWD, "Sesame").
@@ -89,9 +107,6 @@ end_per_testcase(_, _Config) ->
%%-------------------------------------------------------------------------
%% Test cases
%%-------------------------------------------------------------------------
-default_tree() ->
- [{doc, "Makes sure the correct processes are started and linked,"
- "in the default case."}].
default_tree(Config) when is_list(Config) ->
TopSupChildren = supervisor:which_children(ssh_sup),
2 = length(TopSupChildren),
@@ -99,41 +114,46 @@ default_tree(Config) when is_list(Config) ->
lists:keysearch(sshc_sup, 1, TopSupChildren),
{value, {sshd_sup, _,supervisor,[sshd_sup]}} =
lists:keysearch(sshd_sup, 1, TopSupChildren),
- ?wait_match([], supervisor:which_children(sshc_sup)),
+ ?wait_match([{client_controller,_,worker,_}], supervisor:which_children(sshc_sup)),
?wait_match([], supervisor:which_children(sshd_sup)).
%%-------------------------------------------------------------------------
-sshc_subtree() ->
- [{doc, "Make sure the sshc subtree is correct"}].
sshc_subtree(Config) when is_list(Config) ->
{_Pid, Host, Port} = proplists:get_value(server, Config),
UserDir = proplists:get_value(userdir, Config),
- ?wait_match([], supervisor:which_children(sshc_sup)),
+ ?wait_match([{client_controller,_,worker,_}], supervisor:which_children(sshc_sup)),
{ok, Pid1} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
{user_interaction, false},
{user, ?USER}, {password, ?PASSWD},{user_dir, UserDir}]),
- ?wait_match([{_, _,worker,[ssh_connection_handler]}],
- supervisor:which_children(sshc_sup)),
+ ?wait_match([{{client,ssh_system_sup, LocalIP, LocalPort, ?DEFAULT_PROFILE},
+ SysSup, supervisor,[ssh_system_sup]},
+ {client_controller,_,worker,_}
+ ],
+ supervisor:which_children(sshc_sup),
+ [SysSup, LocalIP, LocalPort]),
+ check_sshc_system_tree(SysSup, Pid1, LocalIP, LocalPort, Config),
{ok, Pid2} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
{user_interaction, false},
{user, ?USER}, {password, ?PASSWD}, {user_dir, UserDir}]),
- ?wait_match([{_,_,worker,[ssh_connection_handler]},
- {_,_,worker,[ssh_connection_handler]}],
+ ?wait_match([{_, _,supervisor,[ssh_system_sup]},
+ {_, _,supervisor,[ssh_system_sup]},
+ {client_controller,_,worker,_}
+ ],
supervisor:which_children(sshc_sup)),
ssh:close(Pid1),
- ?wait_match([{_,_,worker,[ssh_connection_handler]}],
+ ?wait_match([{_, _,supervisor,[ssh_system_sup]},
+ {client_controller,_,worker,_}
+ ],
supervisor:which_children(sshc_sup)),
ssh:close(Pid2),
- ?wait_match([], supervisor:which_children(sshc_sup)).
+ ?wait_match([{client_controller,_,worker,_}], supervisor:which_children(sshc_sup)).
%%-------------------------------------------------------------------------
-sshd_subtree() ->
- [{doc, "Make sure the sshd subtree is correct"}].
sshd_subtree(Config) when is_list(Config) ->
SystemDir = proplists:get_value(data_dir, Config),
{Daemon, HostIP, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
@@ -154,8 +174,6 @@ sshd_subtree(Config) when is_list(Config) ->
?wait_match([], supervisor:which_children(sshd_sup)).
%%-------------------------------------------------------------------------
-sshd_subtree_profile() ->
- [{doc, "Make sure the sshd subtree using profile option is correct"}].
sshd_subtree_profile(Config) when is_list(Config) ->
Profile = proplists:get_value(profile, Config),
SystemDir = proplists:get_value(data_dir, Config),
@@ -212,6 +230,7 @@ killed_acceptor_restarts(Config) ->
ct:log("~s",[lists:flatten(ssh_info:string())]),
%% Make acceptor restart:
+ ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [AccPid]),
exit(AccPid, kill),
?wait_match(undefined, process_info(AccPid)),
@@ -221,6 +240,8 @@ killed_acceptor_restarts(Config) ->
AccPid1,
500, 30),
+ ct:pal("... now there should not be any SUPERVISOR REPORT.~n", []),
+
true = (AccPid1 =/= AccPid2),
%% Connect second client and check it is alive:
@@ -284,6 +305,7 @@ shell_channel_tree(Config) ->
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
ok = ssh_connection:shell(ConnectionRef,ChannelId0),
+ success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId0, [{pty_opts,[{onlcr,1}]}]),
?wait_match([{_, GroupPid,worker,[ssh_server_channel]}],
supervisor:which_children(ChannelSup),
@@ -331,12 +353,14 @@ chk_empty_con_daemon(Daemon) ->
{{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}],
supervisor:which_children(Daemon),
[SubSysSup,AccSup]),
- ?wait_match([{{server,ssh_connection_sup, _,_},
+ ?wait_match([{_,_, supervisor,
+ [ssh_tcpip_forward_acceptor_sup]},
+ {{server,ssh_connection_sup, _,_},
ConnectionSup, supervisor,
[ssh_connection_sup]},
- {{server,ssh_server_channel_sup,_ ,_},
+ {{server,ssh_channel_sup,_ ,_},
ChannelSup,supervisor,
- [ssh_server_channel_sup]}],
+ [ssh_channel_sup]}],
supervisor:which_children(SubSysSup),
[ConnectionSup,ChannelSup]),
?wait_match([{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}],
@@ -362,12 +386,15 @@ check_sshd_system_tree(Daemon, Host, Port, Config) ->
supervisor:which_children(Daemon),
[SubSysSup,AccSup]),
- ?wait_match([{{server,ssh_connection_sup, _,_},
+ ?wait_match([{_,
+ _AcceptorSup, supervisor,
+ [ssh_tcpip_forward_acceptor_sup]},
+ {{server,ssh_connection_sup, _,_},
ConnectionSup, supervisor,
[ssh_connection_sup]},
- {{server,ssh_server_channel_sup,_ ,_},
+ {{server,ssh_channel_sup,_ ,_},
ChannelSup,supervisor,
- [ssh_server_channel_sup]}],
+ [ssh_channel_sup]}],
supervisor:which_children(SubSysSup),
[ConnectionSup,ChannelSup]),
@@ -379,12 +406,58 @@ check_sshd_system_tree(Daemon, Host, Port, Config) ->
?wait_match([], supervisor:which_children(ChannelSup)),
- ssh_sftp:start_channel(Client),
+ {ok,PidC} = ssh_sftp:start_channel(Client),
+
+ ?wait_match([{_, PidS,worker,[ssh_server_channel]}],
+ supervisor:which_children(ChannelSup),
+ [PidS]),
+ true = (PidS =/= PidC),
- ?wait_match([{_, _,worker,[ssh_server_channel]}],
- supervisor:which_children(ChannelSup)),
ssh:close(Client).
+
+check_sshc_system_tree(SysSup, Connection, LocalIP, LocalPort, _Config) ->
+ ?wait_match([{_,SubSysSup,supervisor,[ssh_subsystem_sup]}],
+ supervisor:which_children(SysSup),
+ [SubSysSup]),
+ ?wait_match([{_,FwdAccSup, supervisor,
+ [ssh_tcpip_forward_acceptor_sup]},
+ {{client,ssh_connection_sup, LocalIP, LocalPort},
+ ConnectionSup, supervisor,
+ [ssh_connection_sup]},
+ {{client,ssh_channel_sup, LocalIP, LocalPort},
+ ChannelSup,supervisor,
+ [ssh_channel_sup]}],
+ supervisor:which_children(SubSysSup),
+ [ConnectionSup,ChannelSup,FwdAccSup]),
+ ?wait_match([{_, Connection, worker,[ssh_connection_handler]}],
+ supervisor:which_children(ConnectionSup)),
+ ?wait_match([], supervisor:which_children(ChannelSup)),
+ ?wait_match([], supervisor:which_children(FwdAccSup)),
+
+ {ok,ChPid1} = ssh_sftp:start_channel(Connection),
+ ?wait_match([{_,ChPid1,worker,[ssh_client_channel]}],
+ supervisor:which_children(ChannelSup)),
+
+ {ok,ChPid2} = ssh_sftp:start_channel(Connection),
+ ?wait_match([{_,ChPidA,worker,[ssh_client_channel]},
+ {_,ChPidB,worker,[ssh_client_channel]}],
+ supervisor:which_children(ChannelSup),
+ [ChPidA, ChPidB]),
+ true = (lists:sort([ChPidA, ChPidB]) == lists:sort([ChPid1, ChPid2])),
+
+ ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [ChPid1]),
+ exit(ChPid1, kill),
+ ?wait_match([{_,ChPid2,worker,[ssh_client_channel]}],
+ supervisor:which_children(ChannelSup)),
+
+ ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [ChPid2]),
+ exit(ChPid2, kill),
+ ?wait_match([], supervisor:which_children(ChannelSup)),
+ ct:pal("... now there should not be any SUPERVISOR REPORT.~n", []).
+
+
+
acceptor_pid(DaemonPid) ->
Parent = self(),
Pid = spawn(fun() ->
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index bf533fc553..ae8f7abb70 100644
--- a/lib/ssh/test/ssh_test_lib.erl
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -22,12 +22,109 @@
%%----------------------------------------------------------------------
-module(ssh_test_lib).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+connect/2,
+connect/3,
+daemon/1,
+daemon/2,
+daemon/3,
+daemon_port/1,
+daemon_port/2,
+gen_tcp_connect/3,
+open_sshc/3,
+open_sshc/4,
+open_sshc_cmd/3,
+open_sshc_cmd/4,
+std_daemon/2,
+std_daemon1/2,
+std_connect/4,
+std_simple_sftp/3,
+std_simple_sftp/4,
+std_simple_exec/3,
+std_simple_exec/4,
+start_shell/2,
+start_shell/3,
+start_io_server/0,
+init_io_server/1,
+loop_io_server/2,
+io_request/5,
+io_reply/3,
+reply/2,
+rcv_expected/3,
+rcv_lingering/1,
+receive_exec_result/1,
+receive_exec_result_or_fail/1,
+receive_exec_end/2,
+receive_exec_end/3,
+receive_exec_result/3,
+failfun/2,
+hostname/0,
+del_dirs/1,
+del_dir_contents/1,
+do_del_files/2,
+openssh_sanity_check/1,
+default_algorithms/1,
+default_algorithms/3,
+default_algorithms/2,
+run_fake_ssh/1,
+extract_algos/1,
+get_atoms/1,
+intersection/2,
+intersect/2,
+intersect_bi_dir/1,
+some_empty/1,
+sort_spec/1,
+sshc/1,
+ssh_type/0,
+ssh_type1/0,
+installed_ssh_version/1,
+algo_intersection/2,
+to_atoms/1,
+ssh_supports/2,
+has_inet6_address/0,
+open_port/1,
+open_port/2,
+sleep_millisec/1,
+sleep_microsec/1,
+busy_wait/2,
+get_kex_init/1,
+get_kex_init/3,
+expected_state/1,
+random_chars/1,
+create_random_dir/1,
+match_ip/2,
+match_ip0/2,
+match_ip1/2,
+mangle_connect_address/1,
+mangle_connect_address/2,
+loopback/1,
+mangle_connect_address1/2,
+ntoa/1,
+try_enable_fips_mode/0,
+is_cryptolib_fips_capable/0,
+report/2,
+lc_name_in/1,
+ptty_supported/0,
+has_WSL/0,
+winpath_to_linuxpath/1,
+copy_recursive/2,
+mk_dir_path/1,
+setup_all_user_host_keys/1,
+setup_all_user_host_keys/2,
+setup_all_user_host_keys/3,
+setup_all_host_keys/1,
+setup_all_host_keys/2,
+setup_all_user_keys/2,
+setup_user_key/3,
+setup_host_key_create_dir/3,
+setup_host_key/3,
+setup_known_host/3,
+get_addr_str/0,
+file_base_name/2
+ ]).
--include_lib("public_key/include/public_key.hrl").
-include_lib("common_test/include/ct.hrl").
--include_lib("ssh/src/ssh_transport.hrl").
+-include("ssh_transport.hrl").
-include_lib("kernel/include/file.hrl").
-include("ssh_test_lib.hrl").
@@ -184,11 +281,44 @@ start_shell(Port, IOServer) ->
start_shell(Port, IOServer, ExtraOptions) ->
spawn_link(
fun() ->
- Host = hostname(),
+ ct:log("~p:~p:~p ssh_test_lib:start_shell(~p, ~p, ~p)",
+ [?MODULE,?LINE,self(), Port, IOServer, ExtraOptions]),
Options = [{user_interaction, false},
{silently_accept_hosts,true} | ExtraOptions],
- group_leader(IOServer, self()),
- ssh:shell(Host, Port, Options)
+ try
+ group_leader(IOServer, self()),
+ case Port of
+ 22 ->
+ Host = hostname(),
+ ct:log("Port==22 Call ssh:shell(~p, ~p)",
+ [Host, Options]),
+ ssh:shell(Host, Options);
+ _ when is_integer(Port) ->
+ Host = hostname(),
+ ct:log("is_integer(Port) Call ssh:shell(~p, ~p, ~p)",
+ [Host, Port, Options]),
+ ssh:shell(Host, Port, Options);
+ Socket when is_port(Socket) ->
+ receive
+ start -> ok
+ end,
+ ct:log("is_port(Socket) Call ssh:shell(~p, ~p)",
+ [Socket, Options]),
+ ssh:shell(Socket, Options);
+ ConnRef when is_pid(ConnRef) ->
+ ct:log("is_pid(ConnRef) Call ssh:shell(~p)",
+ [ConnRef]),
+ ssh:shell(ConnRef) % Options were given in ssh:connect
+ end
+ of
+ R ->
+ ct:log("~p:~p ssh_test_lib:start_shell(~p, ~p, ~p) -> ~p",
+ [?MODULE,?LINE,Port, IOServer, ExtraOptions, R])
+ catch
+ C:E:S ->
+ ct:log("Exception ~p:~p~n~p", [C,E,S]),
+ ct:fail("Exception",[])
+ end
end).
@@ -209,6 +339,7 @@ loop_io_server(TestCase, Buff0) ->
%%ct:log("io_server ~p:~p ~p got ~p",[?MODULE,?LINE,self(),_REQ]),
{ok, Reply, Buff} = io_request(Request, TestCase, From,
ReplyAs, Buff0),
+ %%ct:log("io_server ~p:~p ~p going to reply ~p",[?MODULE,?LINE,self(),Reply]),
io_reply(From, ReplyAs, Reply),
loop_io_server(TestCase, Buff);
{'EXIT',_, _} = _Exit ->
@@ -218,6 +349,12 @@ loop_io_server(TestCase, Buff0) ->
30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
+io_request(getopts,_TestCase, _, _, Buff) ->
+ {ok, [], Buff};
+io_request({get_geometry,columns},_TestCase, _, _, Buff) ->
+ {ok, 80, Buff};
+io_request({get_geometry,rows},_TestCase, _, _, Buff) ->
+ {ok, 24, Buff};
io_request({put_chars, Chars}, TestCase, _, _, Buff) ->
reply(TestCase, Chars),
{ok, ok, Buff};
@@ -225,7 +362,7 @@ io_request({put_chars, unicode, Chars}, TestCase, _, _, Buff) when is_binary(Cha
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)),
+ reply(TestCase, unicode:characters_to_binary(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)),
@@ -241,6 +378,7 @@ io_request({get_line, _Enc, _Prompt} = Request, _, From, ReplyAs, [] = Buff) ->
io_request({get_line, _Enc,_}, _, _, _, [Line | Buff]) ->
{ok, Line, Buff}.
+
io_reply(_, _, []) ->
ok;
io_reply(From, ReplyAs, Reply) ->
@@ -294,25 +432,37 @@ rcv_lingering(Timeout) ->
receive_exec_result([]) ->
expected;
receive_exec_result(Msgs) when is_list(Msgs) ->
- ct:log("Expect data! ~p", [Msgs]),
+ ct:log("~p:~p Expect data! ~p", [?MODULE,?FUNCTION_NAME,Msgs]),
receive
Msg ->
- case lists:member(Msg, Msgs) of
+ case lists:member(Msg, Msgs)
+ orelse lists:member({optional,Msg}, Msgs)
+ of
true ->
- ct:log("Collected data ~p", [Msg]),
- receive_exec_result(Msgs--[Msg]);
+ ct:log("~p:~p Collected data ~p", [?MODULE,?FUNCTION_NAME,Msg]),
+ receive_exec_result(Msgs--[Msg,{optional,Msg}]);
false ->
case Msg of
{ssh_cm,_,{data,_,1, Data}} ->
- ct:log("StdErr: ~p~n", [Data]),
+ ct:log("~p:~p unexpected StdErr: ~p~n~p~n", [?MODULE,?FUNCTION_NAME,Data,Msg]),
receive_exec_result(Msgs);
Other ->
- ct:log("Other ~p", [Other]),
+ ct:log("~p:~p unexpected Other ~p", [?MODULE,?FUNCTION_NAME,Other]),
{unexpected_msg, Other}
end
end
after
- 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ 30000 ->
+ case lists:all(fun(M) ->
+ is_tuple(M) andalso (element(1,M) == optional)
+ end, Msgs)
+ of
+ false ->
+ ct:fail("timeout ~p:~p",[?MODULE,?FUNCTION_NAME]);
+ true ->
+ ct:log("~p:~p Only optional messages expected!~n ~p", [?MODULE,?FUNCTION_NAME,Msgs]),
+ expected
+ end
end;
receive_exec_result(Msg) ->
receive_exec_result([Msg]).
@@ -325,26 +475,14 @@ receive_exec_result_or_fail(Msg) ->
end.
receive_exec_end(ConnectionRef, ChannelId) ->
- Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
- ExitStatus = {ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}},
- Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}},
- case receive_exec_result(ExitStatus) of
- {unexpected_msg, Eof} -> %% Open ssh seems to not allways send these messages
- %% in the same order!
- ct:log("2: Collected data ~p", [Eof]),
- case receive_exec_result(ExitStatus) of
- expected ->
- expected = receive_exec_result(Closed);
- {unexpected_msg, Closed} ->
- ct:log("3: Collected data ~p", [Closed])
- end;
- expected ->
- ct:log("4: Collected data ~p", [ExitStatus]),
- expected = receive_exec_result(Eof),
- expected = receive_exec_result(Closed);
- Other ->
- ct:fail({unexpected_msg, Other})
- end.
+ receive_exec_end(ConnectionRef, ChannelId, 0).
+
+receive_exec_end(ConnectionRef, ChannelId, ExitStatus) ->
+ receive_exec_result(
+ [{ssh_cm, ConnectionRef, {eof, ChannelId}},
+ {optional, {ssh_cm, ConnectionRef, {exit_status, ChannelId, ExitStatus}}},
+ {ssh_cm, ConnectionRef, {closed, ChannelId}}
+ ]).
receive_exec_result(Data, ConnectionRef, ChannelId) ->
Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
@@ -354,21 +492,6 @@ receive_exec_result(Data, ConnectionRef, ChannelId) ->
expected = receive_exec_result(Closed).
-setup_ssh_auth_keys(RSAFile, DSAFile, Dir) ->
- Entries = ssh_file_entry(RSAFile) ++ ssh_file_entry(DSAFile),
- AuthKeys = public_key:ssh_encode(Entries , auth_keys),
- AuthKeysFile = filename:join(Dir, "authorized_keys"),
- file:write_file(AuthKeysFile, AuthKeys).
-
-ssh_file_entry(PubFile) ->
- case file:read_file(PubFile) of
- {ok, Ssh} ->
- [{Key, _}] = public_key:ssh_decode(Ssh, public_key),
- [{Key, [{comment, "Test"}]}];
- _ ->
- []
- end.
-
failfun(_User, {authmethod,none}) ->
ok;
failfun(User, Reason) ->
@@ -378,233 +501,31 @@ hostname() ->
{ok,Host} = inet:gethostname(),
Host.
-known_hosts(BR) ->
- KnownHosts = ssh_file:file_name(user, "known_hosts", []),
- B = KnownHosts ++ "xxx",
- case BR of
- backup ->
- file:rename(KnownHosts, B);
- restore ->
- file:delete(KnownHosts),
- file:rename(B, KnownHosts)
- end.
+del_dirs(Dir) ->
+ del_dir_contents(Dir),
+ file:del_dir(Dir),
+ ok.
-setup_dsa(DataDir, UserDir) ->
- file:copy(filename:join(DataDir, "id_dsa"), filename:join(UserDir, "id_dsa")),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_dsa_key"), filename:join(System, "ssh_host_dsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_dsa_key.pub"), filename:join(System, "ssh_host_dsa_key.pub")),
-ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]),
- setup_dsa_known_host(DataDir, UserDir),
- setup_dsa_auth_keys(DataDir, UserDir).
-
-setup_rsa(DataDir, UserDir) ->
- file:copy(filename:join(DataDir, "id_rsa"), filename:join(UserDir, "id_rsa")),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_rsa_key"), filename:join(System, "ssh_host_rsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_rsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")),
-ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]),
- setup_rsa_known_host(DataDir, UserDir),
- setup_rsa_auth_keys(DataDir, UserDir).
-
-setup_ecdsa(Size, DataDir, UserDir) ->
- file:copy(filename:join(DataDir, "id_ecdsa"++Size), filename:join(UserDir, "id_ecdsa")),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size), filename:join(System, "ssh_host_ecdsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size++".pub"), filename:join(System, "ssh_host_ecdsa_key.pub")),
-ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]),
- setup_ecdsa_known_host(Size, System, UserDir),
- setup_ecdsa_auth_keys(Size, DataDir, UserDir).
-
-setup_eddsa(Alg, DataDir, UserDir) ->
- {IdPriv, _IdPub, HostPriv, HostPub} =
- case Alg of
- ed25519 -> {"id_ed25519", "id_ed25519.pub", "ssh_host_ed25519_key", "ssh_host_ed25519_key.pub"};
- ed448 -> {"id_ed448", "id_ed448.pub", "ssh_host_ed448_key", "ssh_host_ed448_key.pub"}
- end,
- file:copy(filename:join(DataDir, IdPriv), filename:join(UserDir, IdPriv)),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, HostPriv), filename:join(System, HostPriv)),
- file:copy(filename:join(DataDir, HostPub), filename:join(System, HostPub)),
-ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]),
- setup_eddsa_known_host(HostPub, DataDir, UserDir),
- setup_eddsa_auth_keys(IdPriv, DataDir, UserDir).
-
-clean_dsa(UserDir) ->
- del_dirs(filename:join(UserDir, "system")),
- file:delete(filename:join(UserDir,"id_dsa")),
- file:delete(filename:join(UserDir,"known_hosts")),
- file:delete(filename:join(UserDir,"authorized_keys")).
-
-clean_rsa(UserDir) ->
- del_dirs(filename:join(UserDir, "system")),
- file:delete(filename:join(UserDir,"id_rsa")),
- file:delete(filename:join(UserDir,"known_hosts")),
- file:delete(filename:join(UserDir,"authorized_keys")).
-
-setup_dsa_pass_phrase(DataDir, UserDir, Phrase) ->
- try
- {ok, KeyBin} = file:read_file(filename:join(DataDir, "id_dsa")),
- setup_pass_phrase(KeyBin, filename:join(UserDir, "id_dsa"), Phrase),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_dsa_key"), filename:join(System, "ssh_host_dsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_dsa_key.pub"), filename:join(System, "ssh_host_dsa_key.pub")),
- setup_dsa_known_host(DataDir, UserDir),
- setup_dsa_auth_keys(DataDir, UserDir)
- of
- _ -> true
- catch
- _:_ -> false
- end.
-setup_rsa_pass_phrase(DataDir, UserDir, Phrase) ->
- try
- {ok, KeyBin} = file:read_file(filename:join(DataDir, "id_rsa")),
- setup_pass_phrase(KeyBin, filename:join(UserDir, "id_rsa"), Phrase),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_rsa_key"), filename:join(System, "ssh_host_rsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_rsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")),
- setup_rsa_known_host(DataDir, UserDir),
- setup_rsa_auth_keys(DataDir, UserDir)
- of
- _ -> true
- catch
- _:_ -> false
+del_dir_contents(Dir) ->
+ case file:list_dir(Dir) of
+ {ok, Files} ->
+ do_del_files(Dir, Files);
+ _ ->
+ ok
end.
-setup_ecdsa_pass_phrase(Size, DataDir, UserDir, Phrase) ->
- try
- {ok, KeyBin} =
- case file:read_file(F=filename:join(DataDir, "id_ecdsa"++Size)) of
- {error,E} ->
- ct:log("Failed (~p) to read ~p~nFiles: ~p", [E,F,file:list_dir(DataDir)]),
- file:read_file(filename:join(DataDir, "id_ecdsa"));
- Other ->
- Other
- end,
- setup_pass_phrase(KeyBin, filename:join(UserDir, "id_ecdsa"), Phrase),
- System = filename:join(UserDir, "system"),
- file:make_dir(System),
- file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size), filename:join(System, "ssh_host_ecdsa_key")),
- file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size++".pub"), filename:join(System, "ssh_host_ecdsa_key.pub")),
- setup_ecdsa_known_host(Size, System, UserDir),
- setup_ecdsa_auth_keys(Size, DataDir, UserDir)
- of
- _ -> true
- catch
- _:_ -> false
- end.
+do_del_files(Dir, Files) ->
+ lists:foreach(fun(File) ->
+ FullPath = filename:join(Dir,File),
+ case filelib:is_dir(FullPath) of
+ true ->
+ del_dirs(FullPath);
+ false ->
+ file:delete(FullPath)
+ end
+ end, Files).
-setup_pass_phrase(KeyBin, OutFile, Phrase) ->
- [{KeyType, _,_} = Entry0] = public_key:pem_decode(KeyBin),
- Key = public_key:pem_entry_decode(Entry0),
- Salt = crypto:strong_rand_bytes(8),
- Entry = public_key:pem_entry_encode(KeyType, Key,
- {{"DES-CBC", Salt}, Phrase}),
- Pem = public_key:pem_encode([Entry]),
- file:write_file(OutFile, Pem).
-
-setup_dsa_known_host(SystemDir, UserDir) ->
- {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_dsa_key.pub")),
- [{Key, _}] = public_key:ssh_decode(SshBin, public_key),
- setup_known_hosts(Key, UserDir).
-
-setup_rsa_known_host(SystemDir, UserDir) ->
- {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_rsa_key.pub")),
- [{Key, _}] = public_key:ssh_decode(SshBin, public_key),
- setup_known_hosts(Key, UserDir).
-
-setup_ecdsa_known_host(_Size, SystemDir, UserDir) ->
- {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_ecdsa_key.pub")),
- [{Key, _}] = public_key:ssh_decode(SshBin, public_key),
- setup_known_hosts(Key, UserDir).
-
-setup_eddsa_known_host(HostPub, SystemDir, UserDir) ->
- {ok, SshBin} = file:read_file(filename:join(SystemDir, HostPub)),
- [{Key, _}] = public_key:ssh_decode(SshBin, public_key),
- setup_known_hosts(Key, UserDir).
-
-setup_known_hosts(Key, UserDir) ->
- {ok, Hostname} = inet:gethostname(),
- {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet),
- IP = lists:concat([A, ".", B, ".", C, ".", D]),
- HostNames = [{hostnames,[Hostname, IP]}],
- KnownHosts = [{Key, HostNames}],
- KnownHostsEnc = public_key:ssh_encode(KnownHosts, known_hosts),
- KHFile = filename:join(UserDir, "known_hosts"),
- file:write_file(KHFile, KnownHostsEnc).
-
-setup_dsa_auth_keys(Dir, UserDir) ->
- {ok, Pem} = file:read_file(filename:join(Dir, "id_dsa")),
- DSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))),
- PKey = DSA#'DSAPrivateKey'.y,
- P = DSA#'DSAPrivateKey'.p,
- Q = DSA#'DSAPrivateKey'.q,
- G = DSA#'DSAPrivateKey'.g,
- Dss = #'Dss-Parms'{p=P, q=Q, g=G},
- setup_auth_keys([{{PKey, Dss}, [{comment, "Test"}]}], UserDir).
-
-setup_rsa_auth_keys(Dir, UserDir) ->
- {ok, Pem} = file:read_file(filename:join(Dir, "id_rsa")),
- RSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))),
- #'RSAPrivateKey'{publicExponent = E, modulus = N} = RSA,
- PKey = #'RSAPublicKey'{publicExponent = E, modulus = N},
- setup_auth_keys([{ PKey, [{comment, "Test"}]}], UserDir).
-
-setup_ecdsa_auth_keys(Size, Dir, UserDir) ->
- {ok, Pem} =
- case file:read_file(F=filename:join(Dir, "id_ecdsa"++Size)) of
- {error,E} ->
- ct:log("Failed (~p) to read ~p~nFiles: ~p", [E,F,file:list_dir(Dir)]),
- file:read_file(filename:join(Dir, "id_ecdsa"));
- Other ->
- Other
- end,
- ECDSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))),
- #'ECPrivateKey'{publicKey = Q,
- parameters = Param = {namedCurve,_Id0}} = ECDSA,
- PKey = #'ECPoint'{point = Q},
- setup_auth_keys([{ {PKey,Param}, [{comment, "Test"}]}], UserDir).
-
-setup_eddsa_auth_keys(IdPriv, Dir, UserDir) ->
- {ok, Pem} = file:read_file(filename:join(Dir, IdPriv)),
- {ed_pri, Alg, Pub, _} = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))),
- setup_auth_keys([{{ed_pub,Alg,Pub}, [{comment, "Test"}]}], UserDir).
-
-setup_auth_keys(Keys, Dir) ->
- AuthKeys = public_key:ssh_encode(Keys, auth_keys),
- AuthKeysFile = filename:join(Dir, "authorized_keys"),
- ok = file:write_file(AuthKeysFile, AuthKeys),
- AuthKeys.
-
-write_auth_keys(Keys, Dir) ->
- AuthKeysFile = filename:join(Dir, "authorized_keys"),
- file:write_file(AuthKeysFile, Keys).
-
-del_dirs(Dir) ->
- case file:list_dir(Dir) of
- {ok, []} ->
- file:del_dir(Dir);
- {ok, Files} ->
- lists:foreach(fun(File) ->
- FullPath = filename:join(Dir,File),
- case filelib:is_dir(FullPath) of
- true ->
- del_dirs(FullPath),
- file:del_dir(FullPath);
- false ->
- file:delete(FullPath)
- end
- end, Files);
- _ ->
- ok
- end.
openssh_sanity_check(Config) ->
ssh:start(),
@@ -622,34 +543,6 @@ openssh_sanity_check(Config) ->
{skip, Str}
end.
-openssh_supports(ClientOrServer, Tag, Alg) when ClientOrServer == sshc ;
- ClientOrServer == sshd ->
- SSH_algos = ssh_test_lib:default_algorithms(ClientOrServer),
- L = proplists:get_value(Tag, SSH_algos, []),
- lists:member(Alg, L) orelse
- lists:member(Alg, proplists:get_value(client2server, L, [])) orelse
- lists:member(Alg, proplists:get_value(server2client, L, [])).
-
-%%--------------------------------------------------------------------
-%% Check if we have a "newer" ssh client that supports these test cases
-
-ssh_client_supports_Q() ->
- 0 == check_ssh_client_support2(
- ?MODULE:open_port({spawn, "ssh -Q cipher"})
- ).
-
-check_ssh_client_support2(P) ->
- receive
- {P, {data, _A}} ->
- check_ssh_client_support2(P);
- {P, {exit_status, E}} ->
- ct:log("~p:~p exit_status:~n~p",[?MODULE,?LINE,E]),
- E
- after 5000 ->
- ct:log("Openssh command timed out ~n"),
- -1
- end.
-
%%%--------------------------------------------------------------------
%%% Probe a server or a client about algorithm support
@@ -776,6 +669,15 @@ intersect_bi_dir([H={_,[A|_]}|T]) when is_atom(A) ->
intersect_bi_dir([]) ->
[].
+some_empty([]) ->
+ false;
+some_empty([{_,[]}|_]) ->
+ true;
+some_empty([{_,L}|T]) when is_atom(hd(L)) ->
+ some_empty(T);
+some_empty([{_,L}|T]) when is_tuple(hd(L)) ->
+ some_empty(L) orelse some_empty(T).
+
sort_spec(L = [{_,_}|_] ) -> [{Tag,sort_spec(Es)} || {Tag,Es} <- L];
sort_spec(L) -> lists:usort(L).
@@ -967,12 +869,14 @@ get_kex_init(Conn, Ref, TRef) ->
end;
false ->
- ct:log("~p:~p Not in 'connected' state: ~p",[?MODULE,?LINE,State]),
receive
{reneg_timeout,Ref} ->
+ ct:log("~p:~p Not in 'connected' state: ~p but reneg_timeout received. Fail.",
+ [?MODULE,?LINE,State]),
ct:log("S = ~p", [S]),
ct:fail(reneg_timeout)
after 0 ->
+ ct:log("~p:~p Not in 'connected' state: ~p, Will try again after 100ms",[?MODULE,?LINE,State]),
timer:sleep(100), % If renegotiation is complete we do not
% want to exit on the reneg_timeout
get_kex_init(Conn, Ref, TRef)
@@ -1177,3 +1081,150 @@ mk_dir_path(DirPath) ->
%%ct:log("~p:~p return Other ~p ~ts", [?MODULE,?LINE,Other,DirPath]),
Other
end.
+
+%%%----------------------------------------------------------------
+%%% New
+
+setup_all_user_host_keys(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ setup_all_user_host_keys(DataDir, PrivDir).
+
+setup_all_user_host_keys(DataDir, PrivDir) ->
+ setup_all_user_host_keys(DataDir, PrivDir, filename:join(PrivDir,"system")).
+
+setup_all_user_host_keys(DataDir, UserDir, SysDir) ->
+ lists:foldl(fun(Alg, OkAlgs) ->
+ try
+ ok = ssh_test_lib:setup_user_key(Alg, DataDir, UserDir),
+ ok = ssh_test_lib:setup_host_key(Alg, DataDir, SysDir)
+ of
+ ok -> [Alg|OkAlgs]
+ catch
+ error:{badmatch, {error,enoent}} ->
+ OkAlgs;
+ C:E:S ->
+ ct:log("Exception in ~p:~p for alg ~p: ~p:~p~n~p",
+ [?MODULE,?FUNCTION_NAME,Alg,C,E,S]),
+ OkAlgs
+ end
+ end, [], ssh_transport:supported_algorithms(public_key)).
+
+
+setup_all_host_keys(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ setup_all_host_keys(DataDir, filename:join(PrivDir,"system")).
+
+setup_all_host_keys(DataDir, SysDir) ->
+ lists:foldl(fun(Alg, OkAlgs) ->
+ try
+ ok = ssh_test_lib:setup_host_key(Alg, DataDir, SysDir)
+ of
+ ok -> [Alg|OkAlgs]
+ catch
+ error:{badmatch, {error,enoent}} ->
+ OkAlgs;
+ C:E:S ->
+ ct:log("Exception in ~p:~p for alg ~p: ~p:~p~n~p",
+ [?MODULE,?FUNCTION_NAME,Alg,C,E,S]),
+ OkAlgs
+ end
+ end, [], ssh_transport:supported_algorithms(public_key)).
+
+
+setup_all_user_keys(DataDir, UserDir) ->
+ lists:foldl(fun(Alg, OkAlgs) ->
+ try
+ ok = ssh_test_lib:setup_user_key(Alg, DataDir, UserDir)
+ of
+ ok -> [Alg|OkAlgs]
+ catch
+ error:{badmatch, {error,enoent}} ->
+ OkAlgs;
+ C:E:S ->
+ ct:log("Exception in ~p:~p for alg ~p: ~p:~p~n~p",
+ [?MODULE,?FUNCTION_NAME,Alg,C,E,S]),
+ OkAlgs
+ end
+ end, [], ssh_transport:supported_algorithms(public_key)).
+
+
+setup_user_key(SshAlg, DataDir, UserDir) ->
+ file:make_dir(UserDir),
+ %% Copy private user key to user's dir
+ {ok,_} = file:copy(filename:join(DataDir, file_base_name(user_src,SshAlg)),
+ filename:join(UserDir, file_base_name(user,SshAlg))),
+ %% Setup authorized_keys in user's dir
+ {ok,Pub} = file:read_file(filename:join(DataDir, file_base_name(user_src,SshAlg)++".pub")),
+ ok = file:write_file(filename:join(UserDir, "authorized_keys"),
+ io_lib:format("~n~s~n",[Pub]),
+ [append]),
+ ?ct_log_show_file( filename:join(DataDir, file_base_name(user_src,SshAlg)++".pub") ),
+ ?ct_log_show_file( filename:join(UserDir, "authorized_keys") ),
+ ok.
+
+setup_host_key_create_dir(SshAlg, DataDir, BaseDir) ->
+ SysDir = filename:join(BaseDir,"system"),
+ ct:log("~p:~p SshAlg=~p~nDataDir = ~p~nBaseDir = ~p~nSysDir = ~p",[?MODULE,?LINE,SshAlg, DataDir, BaseDir,SysDir]),
+ file:make_dir(SysDir),
+ setup_host_key(SshAlg, DataDir, SysDir),
+ SysDir.
+
+setup_host_key(SshAlg, DataDir, SysDir) ->
+ mk_dir_path(SysDir),
+ %% Copy private host key to system's dir
+ {ok,_} = file:copy(filename:join(DataDir, file_base_name(system_src,SshAlg)),
+ filename:join(SysDir, file_base_name(system,SshAlg))),
+ ?ct_log_show_file( filename:join(SysDir, file_base_name(system,SshAlg)) ),
+ ok.
+
+setup_known_host(SshAlg, DataDir, UserDir) ->
+ {ok,Pub} = file:read_file(filename:join(DataDir, file_base_name(system_src,SshAlg)++".pub")),
+ S = lists:join(" ", lists:reverse(tl(lists:reverse(string:tokens(binary_to_list(Pub), " "))))),
+ ok = file:write_file(filename:join(UserDir, "known_hosts"),
+ io_lib:format("~p~n",[S])),
+ ?ct_log_show_file( filename:join(UserDir, "known_hosts") ),
+ ok.
+
+
+get_addr_str() ->
+ {ok, Hostname} = inet:gethostname(),
+ {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet),
+ IP = lists:concat([A, ".", B, ".", C, ".", D]),
+ lists:concat([Hostname,",",IP]).
+
+
+file_base_name(user, 'ecdsa-sha2-nistp256') -> "id_ecdsa";
+file_base_name(user, 'ecdsa-sha2-nistp384') -> "id_ecdsa";
+file_base_name(user, 'ecdsa-sha2-nistp521') -> "id_ecdsa";
+file_base_name(user, 'rsa-sha2-256' ) -> "id_rsa";
+file_base_name(user, 'rsa-sha2-384' ) -> "id_rsa";
+file_base_name(user, 'rsa-sha2-512' ) -> "id_rsa";
+file_base_name(user, 'ssh-dss' ) -> "id_dsa";
+file_base_name(user, 'ssh-ed25519' ) -> "id_ed25519";
+file_base_name(user, 'ssh-ed448' ) -> "id_ed448";
+file_base_name(user, 'ssh-rsa' ) -> "id_rsa";
+
+file_base_name(user_src, 'ecdsa-sha2-nistp256') -> "id_ecdsa256";
+file_base_name(user_src, 'ecdsa-sha2-nistp384') -> "id_ecdsa384";
+file_base_name(user_src, 'ecdsa-sha2-nistp521') -> "id_ecdsa521";
+file_base_name(user_src, Alg) -> file_base_name(user, Alg);
+
+file_base_name(system, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key";
+file_base_name(system, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key";
+file_base_name(system, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key";
+file_base_name(system, 'rsa-sha2-256' ) -> "ssh_host_rsa_key";
+file_base_name(system, 'rsa-sha2-384' ) -> "ssh_host_rsa_key";
+file_base_name(system, 'rsa-sha2-512' ) -> "ssh_host_rsa_key";
+file_base_name(system, 'ssh-dss' ) -> "ssh_host_dsa_key";
+file_base_name(system, 'ssh-ed25519' ) -> "ssh_host_ed25519_key";
+file_base_name(system, 'ssh-ed448' ) -> "ssh_host_ed448_key";
+file_base_name(system, 'ssh-rsa' ) -> "ssh_host_rsa_key";
+
+file_base_name(system_src, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key256";
+file_base_name(system_src, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key384";
+file_base_name(system_src, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key521";
+file_base_name(system_src, Alg) -> file_base_name(system, Alg).
+
+%%%----------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_test_lib.hrl b/lib/ssh/test/ssh_test_lib.hrl
index b9af2ecb5d..0e36793955 100644
--- a/lib/ssh/test/ssh_test_lib.hrl
+++ b/lib/ssh/test/ssh_test_lib.hrl
@@ -29,7 +29,7 @@
case FunctionCall of
Pattern when Guard -> Bind;
_ when N>0 ->
- ct:pal("Must sleep ~p ms at ~p:~p",[Timeout,?MODULE,?LINE]),
+ ct:log("Must sleep ~p ms at ~p:~p",[Timeout,?MODULE,?LINE]),
timer:sleep(Timeout),
F1(N-1, F1);
Other ->
@@ -49,3 +49,13 @@
-define(wait_match(Pattern, FunctionCall), ?wait_match(Pattern, FunctionCall, ok) ).
+%%-------------------------------------------------------------------------
+%% Write file into log
+%%-------------------------------------------------------------------------
+
+-define(ct_log_show_file(File),
+ (fun(File__) ->
+ {ok,Contents__} = file:read_file(File__),
+ ct:log("~p:~p Show file~n~s =~n~s~n",
+ [?MODULE,?LINE,File__, Contents__])
+ end)(File)).
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
index 2a3da845a0..96072cb1b6 100644
--- a/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -24,8 +24,31 @@
-include_lib("common_test/include/ct.hrl").
-include("ssh_test_lib.hrl").
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ erlang_server_openssh_client_renegotiate/1,
+ erlang_shell_client_openssh_server/1,
+ exec_direct_with_io_in_sshc/1,
+ exec_with_io_in_sshc/1,
+ tunnel_in_erlclient_erlserver/1,
+ tunnel_in_erlclient_openssh_server/1,
+ tunnel_in_non_erlclient_erlserver/1,
+ tunnel_out_erlclient_erlserver/1,
+ tunnel_out_erlclient_openssh_server/1,
+ tunnel_out_non_erlclient_erlserver/1
+
+ ]).
-define(SSH_DEFAULT_PORT, 22).
-define(REKEY_DATA_TMO, 65000).
@@ -48,12 +71,20 @@ all() ->
end.
groups() ->
- [{erlang_client, [], [erlang_shell_client_openssh_server
+ [{erlang_client, [], [tunnel_in_erlclient_erlserver,
+ tunnel_out_erlclient_erlserver,
+ {group, tunnel_distro_server},
+ erlang_shell_client_openssh_server
]},
- {erlang_server, [], [erlang_server_openssh_client_renegotiate,
+ {tunnel_distro_server, [], [tunnel_in_erlclient_openssh_server,
+ tunnel_out_erlclient_openssh_server]},
+ {erlang_server, [], [{group, tunnel_distro_client},
+ erlang_server_openssh_client_renegotiate,
exec_with_io_in_sshc,
exec_direct_with_io_in_sshc
- ]}
+ ]},
+ {tunnel_distro_client, [], [tunnel_in_non_erlclient_erlserver,
+ tunnel_out_non_erlclient_erlserver]}
].
init_per_suite(Config) ->
@@ -73,11 +104,15 @@ end_per_suite(_Config) ->
ok.
init_per_group(erlang_server, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- UserDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa_known_host(DataDir, UserDir),
- ssh_test_lib:setup_rsa_known_host(DataDir, UserDir),
Config;
+init_per_group(G, Config) when G==tunnel_distro_server ;
+ G==tunnel_distro_client ->
+ case no_forwarding() of
+ true ->
+ {skip, "port forwarding disabled in external ssh"};
+ false ->
+ Config
+ end;
init_per_group(erlang_client, Config) ->
CommonAlgs = ssh_test_lib:algo_intersection(
ssh:default_algorithms(),
@@ -86,11 +121,6 @@ init_per_group(erlang_client, Config) ->
init_per_group(_, Config) ->
Config.
-end_per_group(erlang_server, Config) ->
- UserDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(UserDir),
- ssh_test_lib:clean_rsa(UserDir),
- Config;
end_per_group(_, Config) ->
Config.
@@ -111,10 +141,6 @@ end_per_testcase(_TestCase, _Config) ->
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-
-erlang_shell_client_openssh_server() ->
- [{doc, "Test that ssh:shell/2 works"}].
-
erlang_shell_client_openssh_server(Config) when is_list(Config) ->
process_flag(trap_exit, true),
IO = ssh_test_lib:start_io_server(),
@@ -150,11 +176,18 @@ exec_with_io_in_sshc(Config) when is_list(Config) ->
{failfun, fun ssh_test_lib:failfun/2}]),
ct:sleep(500),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
ExecStr = "\"io:read('% ').\"",
Cmd = "echo howdy. | " ++ ssh_test_lib:open_sshc_cmd(Host, Port,
- "-x", % Disable X forwarding
+ [" -o UserKnownHostsFile=", KnownHosts,
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x" % Disable X forwarding
+ ],
ExecStr),
- ct:pal("Cmd = ~p~n",[Cmd]),
+ ct:log("Cmd = ~p~n",[Cmd]),
case os:cmd(Cmd) of
"% {ok,howdy}" -> ok;
"{ok,howdy}% " -> ok; % Could happen if the client sends the piped
@@ -177,10 +210,17 @@ exec_direct_with_io_in_sshc(Config) when is_list(Config) ->
]),
ct:sleep(500),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
Cmd = "echo ciao. | " ++ ssh_test_lib:open_sshc_cmd(Host, Port,
- "-x", % Disable X forwarding
+ [" -o UserKnownHostsFile=", KnownHosts,
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x" % Disable X forwarding
+ ],
"'? '"),
- ct:pal("Cmd = ~p~n",[Cmd]),
+ ct:log("Cmd = ~p~n",[Cmd]),
case os:cmd(Cmd) of
"? {ciao,\"oaic\"}" -> ok;
"'? '{ciao,\"oaic\"}" -> ok; % WSL
@@ -196,7 +236,6 @@ erlang_server_openssh_client_renegotiate(Config) ->
_PubKeyAlg = ssh_rsa,
SystemDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{failfun, fun ssh_test_lib:failfun/2}]),
@@ -207,9 +246,13 @@ erlang_server_openssh_client_renegotiate(Config) ->
Data = lists:duplicate(trunc(1.1*RenegLimitK*1024), $a),
ok = file:write_file(DataFile, Data),
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
Cmd = ssh_test_lib:open_sshc_cmd(Host, Port,
[" -o UserKnownHostsFile=", KnownHosts,
- " -o StrictHostKeyChecking=no",
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x",
" -o RekeyLimit=",integer_to_list(RenegLimitK),"K"]),
@@ -247,8 +290,174 @@ erlang_server_openssh_client_renegotiate(Config) ->
end.
%%--------------------------------------------------------------------
+tunnel_out_non_erlclient_erlserver(Config) ->
+ SystemDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_out, true},
+ {system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ {ToSock, _ToHost, ToPort} = tunneling_listner(),
+
+ ListenHost = {127,0,0,1},
+ ListenPort = 2345,
+
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
+ Cmd = ssh_test_lib:open_sshc_cmd(Host, Port,
+ [" -o UserKnownHostsFile=", KnownHosts,
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x",
+ " -R ",integer_to_list(ListenPort),":127.0.0.1:",integer_to_list(ToPort)]),
+ spawn(fun() ->
+ ct:log(["ssh command:\r\n ",Cmd],[]),
+ R = os:cmd(Cmd),
+ ct:log(["ssh returned:\r\n",R],[])
+ end),
+
+ ct:sleep(1000),
+ test_tunneling(ToSock, ListenHost, ListenPort).
+
+%%--------------------------------------------------------------------
+tunnel_in_non_erlclient_erlserver(Config) ->
+ SystemDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, true},
+ {system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ {ToSock, _ToHost, ToPort} = tunneling_listner(),
+
+ ListenHost = {127,0,0,1},
+ ListenPort = 2345,
+
+ KnownHosts = filename:join(UserDir, "known_hosts"),
+ Cmd =
+ ssh_test_lib:open_sshc_cmd(Host, Port,
+ [" -o UserKnownHostsFile=", KnownHosts,
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -q"
+ " -x",
+ " -L ",integer_to_list(ListenPort),":127.0.0.1:",integer_to_list(ToPort)]),
+ spawn(fun() ->
+ ct:log(["ssh command:\r\n ",Cmd],[]),
+ R = os:cmd(Cmd),
+ ct:log(["ssh returned:\r\n",R],[])
+ end),
+ ct:sleep(1000),
+ test_tunneling(ToSock, ListenHost, ListenPort).
+
+%%--------------------------------------------------------------------
+tunnel_in_erlclient_erlserver(Config) ->
+ SystemDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, true},
+ {system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords, [{"foo", "bar"}]},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user,"foo"},{password,"bar"},
+ {user_interaction, false}]),
+ {ToSock, ToHost, ToPort} = tunneling_listner(),
+
+ ListenHost = {127,0,0,1},
+ {ok,ListenPort} = ssh:tcpip_tunnel_to_server(C, ListenHost,0, ToHost,ToPort, 2000),
+
+ test_tunneling(ToSock, ListenHost, ListenPort).
+
+%%--------------------------------------------------------------------
+tunnel_in_erlclient_openssh_server(_Config) ->
+ C = ssh_test_lib:connect(loopback, 22, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ {ToSock, ToHost, ToPort} = tunneling_listner(),
+
+ ListenHost = {127,0,0,1},
+ {ok,ListenPort} = ssh:tcpip_tunnel_to_server(C, ListenHost,0, ToHost,ToPort, 5000),
+
+ test_tunneling(ToSock, ListenHost, ListenPort).
+
+%%--------------------------------------------------------------------
+tunnel_out_erlclient_erlserver(Config) ->
+ SystemDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+ {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_out, true},
+ {system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords, [{"foo", "bar"}]},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user,"foo"},{password,"bar"},
+ {user_interaction, false}]),
+ {ToSock, ToHost, ToPort} = tunneling_listner(),
+
+ ListenHost = {127,0,0,1},
+ {ok,ListenPort} = ssh:tcpip_tunnel_from_server(C, ListenHost,0, ToHost,ToPort, 5000),
+
+ test_tunneling(ToSock, ListenHost, ListenPort).
+
+%%--------------------------------------------------------------------
+tunnel_out_erlclient_openssh_server(_Config) ->
+ C = ssh_test_lib:connect(loopback, 22, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ {ToSock, ToHost, ToPort} = tunneling_listner(),
+
+ ListenHost = {127,0,0,1},
+ {ok,ListenPort} = ssh:tcpip_tunnel_from_server(C, ListenHost,0, ToHost,ToPort, 5000),
+
+ test_tunneling(ToSock, ListenHost, ListenPort).
+
+%%--------------------------------------------------------------------
%%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
+tunneling_listner() ->
+ {ok,LSock} = gen_tcp:listen(0, [{active,false}]),
+ {ok, {LHost,LPort}} = inet:sockname(LSock),
+ {LSock, LHost, LPort}.
+
+test_tunneling(ListenSocket, Host, Port) ->
+ {ok,Client1} = gen_tcp:connect(Host, Port, [{active,false}]),
+ {ok,Server1} = gen_tcp:accept(ListenSocket),
+ {ok,Client2} = gen_tcp:connect(Host, Port, [{active,false}]),
+ {ok,Server2} = gen_tcp:accept(ListenSocket),
+ send_rcv("Hi!", Client1, Server1),
+ send_rcv("Happy to see you!", Server1, Client1),
+ send_rcv("Hi, you to!", Client2, Server2),
+ send_rcv("Happy to see you also!", Server2, Client2),
+ close_and_check(Client1, Server1),
+ send_rcv("Still there?", Client2, Server2),
+ send_rcv("Yes!", Server2, Client2),
+ close_and_check(Server2, Client2).
+
+
+close_and_check(OneSide, OtherSide) ->
+ ok = gen_tcp:close(OneSide),
+ ok = chk_closed(OtherSide).
+
+
+chk_closed(Sock) ->
+ chk_closed(Sock, 0).
+chk_closed(Sock, Timeout) ->
+ case gen_tcp:recv(Sock, 0, Timeout) of
+ {error,closed} ->
+ ok;
+ {error,timeout} ->
+ chk_closed(Sock, 2*max(Timeout,250));
+ Other ->
+ Other
+ end.
+
+send_rcv(Txt, From, To) ->
+ ct:log("Send ~p from ~p to ~p", [Txt, From, To]),
+ ok = gen_tcp:send(From, Txt),
+ ct:log("Recv ~p on ~p", [Txt, To]),
+ {ok,Txt} = gen_tcp:recv(To, 0, 5000),
+ ok.
+
+%%--------------------------------------------------------------------
receive_data(Data, Conn) ->
receive
Info when is_binary(Info) ->
@@ -325,18 +534,40 @@ extra_logout() ->
ok
end.
-%%--------------------------------------------------------------------
-%% Check if we have a "newer" ssh client that supports these test cases
-check_ssh_client_support(Config) ->
- case ssh_test_lib:ssh_client_supports_Q() of
- true ->
- ssh:start(),
- Config;
- _ ->
- {skip, "test case not supported by ssh client"}
- end.
-
-comment(AtomList) ->
- ct:comment(
- string:join(lists:map(fun erlang:atom_to_list/1, AtomList),
- ", ")).
+%%%----------------------------------------------------------------
+no_forwarding() ->
+ %%% Check if the ssh of the OS has tunneling enabled
+ Cmnd = "ssh -R 0:localhost:4567 localhost exit",
+ FailRegExp =
+ "Port forwarding is disabled"
+ "|remote port forwarding failed"
+ "|Bad.*specification"
+ "|Bad forwarding port",
+ {Result,TheText} =
+ try
+ Parent = self(),
+ Pid = spawn(fun() ->
+ Parent ! {self(), os:cmd(Cmnd)}
+ end),
+ receive
+ {Pid, Txt} ->
+ case re:run(Txt, FailRegExp) of
+ {match,_} -> {true,Txt};
+ _ -> {false,Txt}
+ end
+ after 10000 ->
+ ct:log("*** TIMEOUT ***",[]),
+ {true,""}
+ end
+ catch C:E:S ->
+ ct:log("Exception in no_forwarding():~n~p:~p~n~p~n", [C,E,S]),
+ {true, ""}
+ end,
+ ct:log("---- os:cmd(~p) returned:~n~s~n"
+ "~n"
+ "---- Checking with regexp~n"
+ "~p~n"
+ "~n"
+ "---- The function no_forwarding() returns ~p",
+ [Cmnd,TheText, FailRegExp, Result]),
+ Result.
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key
new file mode 100644
index 0000000000..2979ea88ed
--- /dev/null
+++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49
+AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s
+VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A==
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub
new file mode 100644
index 0000000000..85dc419345
--- /dev/null
+++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key
new file mode 100644
index 0000000000..13a8fcf491
--- /dev/null
+++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD
+uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ
+AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W
+TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000..156ef4045c
--- /dev/null
+++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_trpt_test_lib.erl b/lib/ssh/test/ssh_trpt_test_lib.erl
index 8b40c2c047..eea392bd35 100644
--- a/lib/ssh/test/ssh_trpt_test_lib.erl
+++ b/lib/ssh/test/ssh_trpt_test_lib.erl
@@ -20,8 +20,6 @@
-module(ssh_trpt_test_lib).
-%%-compile(export_all).
-
-export([exec/1, exec/2,
instantiate/2,
format_msg/1,
@@ -30,9 +28,9 @@
).
-include_lib("common_test/include/ct.hrl").
--include_lib("ssh/src/ssh.hrl"). % ?UINT32, ?BYTE, #ssh{} ...
--include_lib("ssh/src/ssh_transport.hrl").
--include_lib("ssh/src/ssh_auth.hrl").
+-include("ssh.hrl"). % ?UINT32, ?BYTE, #ssh{} ...
+-include("ssh_transport.hrl").
+-include("ssh_auth.hrl").
%%%----------------------------------------------------------------
-record(s, {
@@ -100,7 +98,7 @@ exec(Op, S0=#s{}) ->
report_trace(exit, Exit, S1),
exit({Exit,Op});
Cls:Err ->
- ct:pal("Class=~p, Error=~p", [Cls,Err]),
+ ct:log("Class=~p, Error=~p", [Cls,Err]),
error({"fooooooO",Op})
end;
exec(Op, {ok,S=#s{}}) -> exec(Op, S);
diff --git a/lib/ssh/test/ssh_upgrade_SUITE.erl b/lib/ssh/test/ssh_upgrade_SUITE.erl
index 4417962d26..7c7c93e04b 100644
--- a/lib/ssh/test/ssh_upgrade_SUITE.erl
+++ b/lib/ssh/test/ssh_upgrade_SUITE.erl
@@ -19,8 +19,22 @@
%%
-module(ssh_upgrade_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-export([
+ suite/0,
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+-export([
+ major_upgrade/1,
+ minor_upgrade/1,
+ upgrade_downgraded/2,
+ upgrade_init/2,
+ upgrade_upgraded/2
+ ]).
-include_lib("common_test/include/ct.hrl").
-include("ssh_test_lib.hrl").
@@ -61,9 +75,7 @@ init_per_suite(Config0) ->
end_per_suite(Config) ->
ct_release_test:cleanup(Config),
- ssh:stop(),
- UserDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_rsa(UserDir).
+ ssh:stop().
init_per_testcase(_TestCase, Config) ->
Config.
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index 1733709f11..2a4487a813 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,4 +1,4 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 4.9.1
+SSH_VSN = 4.11.1
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/lib/ssl/Makefile b/lib/ssl/Makefile
index bd43794a36..526560288f 100644
--- a/lib/ssl/Makefile
+++ b/lib/ssl/Makefile
@@ -38,4 +38,6 @@ SPECIAL_TARGETS =
#
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=crypto runtime_tools inets public_key
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/ssl/doc/src/Makefile b/lib/ssl/doc/src/Makefile
index 464093f5e4..428783f8e1 100644
--- a/lib/ssl/doc/src/Makefile
+++ b/lib/ssl/doc/src/Makefile
@@ -30,11 +30,6 @@ VSN=$(SSL_VSN)
APPLICATION=ssl
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = refman.xml
@@ -55,86 +50,9 @@ BOOK_FILES = book.xml
XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) $(XML_REF6_FILES) \
$(XML_PART_FILES) $(XML_CHAPTER_FILES)
-GIF_FILES =
-
-PS_FILES =
-
-XML_FLAGS += -defs cite cite.defs -booksty otpA4
-
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-EXTRA_FILES = \
- $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
-
TOP_SPECS_FILE = specs.xml
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-SPECS_FLAGS = -I../../../public_key/include -I../../../public_key/src -I../../..
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: html pdf man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECS_FILES)
- rm -f errs core *~
-
-man: $(MAN3_FILES) $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
+NO_CHUNKS = ssl_crl_cache_api.xml ssl_session_cache_api.xml
# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/ssl/doc/src/cite.defs b/lib/ssl/doc/src/cite.defs
deleted file mode 100644
index 45c9054e32..0000000000
--- a/lib/ssl/doc/src/cite.defs
+++ /dev/null
@@ -1,112 +0,0 @@
-[
-{"4711","CCITT","CCITT, Red Book Vol.-FASCILE VIII.7, Recomm. X400-X.430, Geneva, 1985","jocke"},
-{"X.680","ITU-T X.680","ITU-T Recommendation X.680 (1994) | ISO/IEC 8824-1: 1995, Abstract Syntax Notation One (ASN.1): Specification of Basic Notation"},
-{"X.682","ITU-T X.682","ITU-T Recommendation X.682 (1994) | ISO/IEC 8824-3: 1995, Abstract Syntax Notation One (ASN.1): Constraint Specification"},
-{"X.690","ITU-T X.690","ITU-T Recommendation X.690 (1994) | ISO/IEC 8825-1: 1995, ASN.1 Encoding Rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER)"},
-{"X.691","ITU-T X.691","ITU-T Recommendation X.691 (04/95) | ISO/IEC 8825-2: 1995, ASN.1 Encoding Rules: Specification of Packed Encoding Rules (PER)"},
-{"X.681","ITU-T X.681","ITU-T Recommendation X.681 (1994) | ISO/IEC 8824-2: 1995, Abstract Syntax Notation One (ASN.1): Information Object Specification"},
-{"X.683","ITU-T X.683","ITU-T Recommendation X.683 (1994) | ISO/IEC 8824-4: 1995, Abstract Syntax Notation One (ASN.1): Parameterization of ASN.1 Specifications"},
-{"X.509","ITU-T X.509","ITU-T Recommendation X.509 (1997) | "
- "ISO/IEC 9594-8:1997 : "
- "http://www.itu.int/ITU-T/asn1/database/itu-t/x/x509/1997/index.html",
- peter},
-{
- "DUBUISSON",
- "ASN.1 Communication between Heterogeneous Systems",
- "Oliver Dubuisson: ASN.1 Communication between Heterogeneous Systems, "
- "June 2000 ISBN 0-126333361-0", "nibe"
- },
- {
- "erlbook2",
- "Concurrent Programming in ERLANG",
- "J. Armstrong, R. Virding, C. Wikstrom, M. Williams: "
- "Concurrent Programming in ERLANG, Prentice Hall, 1996, ISBN 0-13-508301-X",
- "kent"
- },
- {"practsgml",
- "Practical SGML",
- "Eric van Herwijnen: Practical SGML, Kluwer Academic Publishers, 1990.",
- "peter"
- },
- {"ssl",
- "SSL",
- "The SSL Protocol Version 3.0, Internet draft November 18, 1996."
- "peter"
- },
- {"rescorla",
- "SSL and TLS",
- "Eric Rescorla: SSL and TLS - Designing and Building Secure Systems, "
- "Addison-Wesley, 2001, ISBN 0-201-61598-3.",
- "peter"
- },
- {"rfc3279",
- "RFC 3279",
- "Algorithms and Identifiers for the Internet X.509 Public Key "
- "Infrastructure Certificate and Certificate Revocation List (CRL) Profile.",
- "peter"
- },
- {"rfc3280",
- "RFC 3280",
- "Internet X.509 Public Key Infrastructure Certificate and "
- "Certificate Revocation List (CRL) Profile.",
- "peter"
- },
- {"rfc3281",
- "RFC 3281",
- "An Internet Attribute Certificate Profile for Authorization.",
- "peter"
- },
- {"pkcs1",
- "PKCS #1",
- "[RFC 2437] RSA Cryptography Specifications, version 2.0 .",
- "peter"
- },
- {"pkcs3",
- "PKCS #3",
- "Diffie-Hellman Key-Agreement Standard, version 1.4, November 1993.",
- "peter"
- },
- {"pkcs5",
- "PKCS #5",
- "[RFC 2898] Password-Based Cryptography Specification, version 2.0 .",
- "peter"
- },
- {"pkcs6",
- "PKCS #6",
- "Extended-Certificate Syntax Standard, version 1.5, November 1993.",
- "peter"
- },
- {"pkcs7",
- "PKCS #7",
- "Cryptographic Message Syntax Standard, version 1.5, November 1993.",
- "peter"
- },
- {"pkcs8",
- "PKCS #8",
- "Private-Key Information Syntax Standard, version 1.2, November 1993.",
- "peter"
- },
- {"pkcs9",
- "PKCS #9",
- "[RFC 2985] Selected Object Classes and Attribute Types, "
- "version 2.0 .",
- "peter"
- },
- {"pkcs10",
- "PKCS #10",
- "[RFC 2986] Certification Request Syntax Specification, version 1.7 .",
- "peter"
- },
- {"pkcs12",
- "PKCS #12",
- "Personal Information Exchange Syntax Standard, version 1.0 DRAFT, "
- "30 April 1997.",
- "peter"
- },
- {"schneier",
- "Applied Cryptography",
- "Bruce Schneier: Applied Cryptography: Protocols, Algorithms, and Source Code in C, "
- "Second Edition, John Wiley & Sons, 1995, ISBN 0471117099",
- "peter"
- }
-].
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 4b0252cfcf..19ad5bdbac 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,644 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<section><title>SSL 10.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix CRL handling that previously could fail to find the
+ issuer cert under some circumstances.</p>
+ <p>
+ Own Id: OTP-17261 Aux Id: GH-4589 </p>
+ </item>
+ <item>
+ <p>
+ TLS-1.3 client could, under some circumstances, select an
+ incorrect algorithm to sign the certificate verification
+ message causing a TLS Decrypt Alert being issued by the
+ server.</p>
+ <p>
+ Own Id: OTP-17281 Aux Id: GH-4620 </p>
+ </item>
+ <item>
+ <p>
+ Correct handling of default values for emulated socket
+ options and retain the order of the ssl options list to
+ ensure backwards compatible behavior if options should be
+ set more than once.</p>
+ <p>
+ Own Id: OTP-17282</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Enhance pre TLS-1.3 session handling so the client and
+ server side handling is completely separated and client
+ disregards oldest session when reaching max limit of the
+ session table.</p>
+ <p>
+ Own Id: OTP-16876</p>
+ </item>
+ <item>
+ <p>
+ This change implements the early data feature for TLS 1.3
+ clients.</p>
+ <p>
+ TLS 1.3 allows clients to send data in the first flight
+ using a Pre-Shared Key to authenticate the server and to
+ encrypt the early data.</p>
+ <p>
+ Own Id: OTP-16985</p>
+ </item>
+ <item>
+ <p>
+ This change implements the early data feature for TLS 1.3
+ servers.</p>
+ <p>
+ Own Id: OTP-17042</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.2.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Enhance logging option log_level to support none and all,
+ also restore backwards compatibility for log_alert
+ option.</p>
+ <p>
+ Own Id: OTP-17228 Aux Id: ERIERL-614 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.2.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid race when the first two upgrade server handshakes
+ (that is servers that use a gen_tcp socket as input to
+ ssl:handshake/2,3) start close to each other. Could lead
+ to that one of the handshakes would fail.</p>
+ <p>
+ Own Id: OTP-17190 Aux Id: ERIERL-606 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid that upgrade (from TCP to TLS) servers starts
+ multiple session cache handlers for the same server. This
+ applies to Erlang distribution over TLS servers.</p>
+ <p>
+ Own Id: OTP-17139 Aux Id: ERL-1458, OTP-16239 </p>
+ </item>
+ <item>
+ <p>
+ Legacy cipher suites defined before TLS-1.2 (but still
+ supported) should be possible to use in TLS-1.2. They
+ where accidentally excluded for available cipher suites
+ for TLS-1.2 in OTP-23.2.2.</p>
+ <p>
+ Own Id: OTP-17174 Aux Id: ERIERL-597 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Enable Erlang distribution over TLS to run TLS-1.3,
+ although TLS-1.2 will still be default.</p>
+ <p>
+ Own Id: OTP-16239 Aux Id: ERL-1458, OTP-17139 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix CVE-2020-35733 this only affects ssl-10.2 (OTP-23.2).
+ This vulnerability could enable a man in the middle
+ attack using a fake chain to a known trusted ROOT. Also
+ limits alternative chain handling, for handling of
+ possibly extraneous certs, to improve memory management.</p>
+ <p>
+ Own Id: OTP-17098</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add support for AES CCM based cipher suites defined in
+ RFC 7251</p>
+ <p>
+ Also Correct cipher suite name conversion to OpenSSL
+ names. A few names where corrected earlier in OTP-16267
+ For backwards compatible reasons we support usage of
+ openSSL names for cipher suites. Mostly anonymous suites
+ names where incorrect, but also some legacy suites.</p>
+ <p>
+ Own Id: OTP-17100</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ SSL's Erlang Distribution Protocol modules inet_tls_dist
+ and inet6_tls_dist lacked a callback function, so the
+ start flag "-dist_listen false" did not work, which has
+ now been fixed.</p>
+ <p>
+ Own Id: OTP-15126 Aux Id: ERL-1375 </p>
+ </item>
+ <item>
+ <p>
+ Correct OpenSSL names for newer cipher suites using DHE
+ in their name that accidentally got the wrong value when
+ fixing other older names using EDH instead.</p>
+ <p>
+ Own Id: OTP-16267 Aux Id: ERIERL-571, ERIERL-477 </p>
+ </item>
+ <item>
+ <p>
+ This change improves the handling of DTLS listening
+ dockets, making it possible to open multiple listeners on
+ the same port with different IP addresses.</p>
+ <p>
+ Own Id: OTP-16849 Aux Id: ERL-1339 </p>
+ </item>
+ <item>
+ <p>
+ Fix a bug that causes cross-build failure.</p>
+ <p>
+ This change excludes the ssl.d dependency file from the
+ source tarballs.</p>
+ <p>
+ Own Id: OTP-16921</p>
+ </item>
+ <item>
+ <p>
+ This change fixes ssl:peername/1 when called on a DTLS
+ client socket.</p>
+ <p>
+ Own Id: OTP-16923 Aux Id: ERL-1341, PR-2786 </p>
+ </item>
+ <item>
+ <p>
+ Retain emulation of active once on a closed socket to
+ behave as before 23.1</p>
+ <p>
+ Own Id: OTP-17018 Aux Id: ERL-1409 </p>
+ </item>
+ <item>
+ <p>
+ Corrected server session cache entry deletion pre
+ TLS-1.3. May increase session reuse.</p>
+ <p>
+ Own Id: OTP-17019 Aux Id: ERL-1412 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Handle extraneous certs in certificate chains as well as
+ chains that are incomplete but can be reconstructed or
+ unordered chains. The cert and certfile options will now
+ accept a list of certificates so that the user may
+ specify the chain explicitly.</p>
+ <p>
+ Also, the default value of the depth option has been
+ increased to allow longer chains by default.</p>
+ <p>
+ Own Id: OTP-16277</p>
+ </item>
+ <item>
+ <p>
+ This change implements optional NSS-style keylog in
+ ssl:connection_information/2 for debugging purposes.</p>
+ <p>
+ The keylog contains various TLS secrets that can be
+ loaded in Wireshark to decrypt TLS packets.</p>
+ <p>
+ Own Id: OTP-16445 Aux Id: PR-2823 </p>
+ </item>
+ <item>
+ <p>
+ Use new gen_statem feature of changing callback mode to
+ improve code maintainability.</p>
+ <p>
+ Own Id: OTP-16529</p>
+ </item>
+ <item>
+ <p>
+ The handling of Service Name Indication has been aligned
+ with RFC8446.</p>
+ <p>
+ Own Id: OTP-16762</p>
+ </item>
+ <item>
+ <p>
+ Add explicit session reuse option to TLS clients for pre
+ TLS-1.3 sessions. Also, add documentation to Users Guide
+ for such sessions.</p>
+ <p>
+ Own Id: OTP-16893</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ If a passive socket is created, ssl:recv/2,3 is never
+ called and then the peer closes the socket the
+ controlling process will no longer receive an active
+ close message.</p>
+ <p>
+ Own Id: OTP-16697 Aux Id: ERIERL-496 </p>
+ </item>
+ <item>
+ <p>
+ Data deliver with ssl:recv/2,3 could fail for when using
+ packet mode. This has been fixed by correcting the flow
+ control handling of passive sockets when packet mode is
+ used.</p>
+ <p>
+ Own Id: OTP-16764</p>
+ </item>
+ <item>
+ <p>
+ This change fixes a potential man-in-the-middle
+ vulnerability when the ssl client is configured to
+ automatically handle session tickets ({session_tickets,
+ auto}).</p>
+ <p>
+ Own Id: OTP-16765</p>
+ </item>
+ <item>
+ <p>
+ Fix the internal handling of options 'verify' and
+ 'verify_fun'.</p>
+ <p>
+ This change fixes a vulnerability when setting the ssl
+ option 'verify' to verify_peer in a continued handshake
+ won't take any effect resulting in the acceptance of
+ expired peer certificates.</p>
+ <p>
+ Own Id: OTP-16767 Aux Id: ERIERL-512 </p>
+ </item>
+ <item>
+ <p>
+ This change fixes the handling of stateless session
+ tickets when anti-replay is enabled.</p>
+ <p>
+ Own Id: OTP-16776 Aux Id: ERL-1316 </p>
+ </item>
+ <item>
+ <p>
+ Fix a crash due to the faulty handling of stateful
+ session tickets received by servers expecting stateless
+ session tickets.</p>
+ <p>
+ This change also improves the handling of faulty/invalid
+ tickets.</p>
+ <p>
+ Own Id: OTP-16777 Aux Id: ERL-1317 </p>
+ </item>
+ <item>
+ <p>
+ Correct flow ctrl checks from OTP-16764 to work as
+ intended. Probably will not have a noticeable affect but
+ will make connections more well behaved under some
+ circumstances.</p>
+ <p>
+ Own Id: OTP-16837 Aux Id: ERL-1319, OTP-16764 </p>
+ </item>
+ <item>
+ <p>
+ Distribution over TLS could exhibit livelock-like
+ behaviour when there is a constant stream of distribution
+ messages. Distribution data is now chunked every 16 Mb to
+ avoid that.</p>
+ <p>
+ Own Id: OTP-16851 Aux Id: PR-2703 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Implement the cookie extension for TLS 1.3.</p>
+ <p>
+ Own Id: OTP-15855</p>
+ </item>
+ <item>
+ <p>
+ Experimental OCSP client support.</p>
+ <p>
+ Own Id: OTP-16448</p>
+ </item>
+ <item>
+ <p>
+ TLS 1.0 -TLS-1.2 sessions tables now have a absolute max
+ value instead of using a shrinking mechanism when
+ reaching the limit. To avoid out of memory problems under
+ heavy load situations. Note that this change infers that
+ implementations of ssl_session_cache_api needs to
+ implement the size function (introduce in OTP 19) for
+ session reuse to be optimally utilized.</p>
+ <p>
+ Own Id: OTP-16802 Aux Id: ERIERL-516 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 10.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix a bug that causes cross-build failure.</p>
+ <p>
+ This change excludes the ssl.d dependency file from the
+ source tar balls.</p>
+ <p>
+ Own Id: OTP-16562 Aux Id: ERL-1168 </p>
+ </item>
+ <item>
+ <p>
+ Correct translation of OpenSSL legacy names for two
+ legacy cipher suites</p>
+ <p>
+ Own Id: OTP-16573 Aux Id: ERIERL-477 </p>
+ </item>
+ <item>
+ <p>
+ Correct documentation for PSK identity and SRP username.</p>
+ <p>
+ Own Id: OTP-16585</p>
+ </item>
+ <item>
+ <p>
+ Make sure client hostname check is run when client uses
+ its own verify_fun</p>
+ <p>
+ Own Id: OTP-16626 Aux Id: ERL-1232 </p>
+ </item>
+ <item>
+ <p>
+ Improved signature selection mechanism in TLS 1.3 for
+ increased interoperability.</p>
+ <p>
+ Own Id: OTP-16638 Aux Id: ERL-1206 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Drop support for SSL-3.0. Support for this legacy TLS
+ version has not been enabled by default since OTP 19. Now
+ all code to support it has been removed, that is SSL-3.0
+ protocol version can not be used and is considered
+ invalid.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-14790</p>
+ </item>
+ <item>
+ <p>
+ Added support for RSA-PSS signature schemes</p>
+ <p>
+ Own Id: OTP-15247</p>
+ </item>
+ <item>
+ <p>
+ Improve interoperability by implementing the middlebox
+ compatiblity mode.</p>
+ <p>
+ The middlebox compatibility mode makes the TLS 1.3
+ handshake look more like a TLS 1.2 handshake and
+ increases the chance of successfully establishing TLS 1.3
+ connections through legacy middleboxes.</p>
+ <p>
+ Own Id: OTP-15589</p>
+ </item>
+ <item>
+ <p>
+ Utilize new properties of <seemfa
+ marker="erts:erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data()</c></seemfa>
+ for performance improvement of Erlang distribution over
+ TLS.</p>
+ <p>
+ Own Id: OTP-16127 Aux Id: OTP-15618 </p>
+ </item>
+ <item>
+ <p>
+ Calls of deprecated functions in the <seeguide
+ marker="crypto:new_api#the-old-api">Old Crypto
+ API</seeguide> are replaced by calls of their <seeguide
+ marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p>
+ <p>
+ Own Id: OTP-16346</p>
+ </item>
+ <item>
+ <p>
+ Implement cipher suite TLS_AES_128_CCM_8_SHA256.</p>
+ <p>
+ Own Id: OTP-16391</p>
+ </item>
+ <item>
+ <p>
+ This change adds TLS-1.3 to the list of default supported
+ versions. That is, TLS-1.3 and TLS-1.2 are configured
+ when ssl option 'versions' is not explicitly set.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16400</p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p>
+ Extended ssl:versions so that it lists supported,
+ available and implemented TLS/DTLS versions.</p>
+ <p>
+ Own Id: OTP-16519</p>
+ </item>
+ <item>
+ <p>
+ Added new option exclusive for ssl:cipher_suites/2,3</p>
+ <p>
+ Own Id: OTP-16532</p>
+ </item>
+ <item>
+ <p>
+ Avoid DoS attack against stateful session_tickets by
+ making session ticket ids unpredictable.</p>
+ <p>
+ Own Id: OTP-16533</p>
+ </item>
+ <item>
+ <p>
+ Add support for the max_fragment_length extension (RFC
+ 6066).</p>
+ <p>
+ Own Id: OTP-16547 Aux Id: PR-2547 </p>
+ </item>
+ <item>
+ <p>
+ Add srp_username in ssl:connection_info, update the
+ document with types of this function.</p>
+ <p>
+ Own Id: OTP-16584</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.6.2.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct flow ctrl checks from OTP-16764 to work as
+ intended. Probably will not have a noticeable affect but
+ will make connections more well behaved under some
+ circumstances.</p>
+ <p>
+ Own Id: OTP-16837 Aux Id: ERL-1319, OTP-16764 </p>
+ </item>
+ <item>
+ <p>
+ Fix a bug that causes cross-build failure.</p>
+ <p>
+ This change excludes the ssl.d dependency file from the
+ source tar balls.</p>
+ <p>
+ Own Id: OTP-16921</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.6.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Data deliver with ssl:recv/2,3 could fail for when using
+ packet mode. This has been fixed by correcting the flow
+ control handling of passive sockets when packet mode is
+ used.</p>
+ <p>
+ Own Id: OTP-16764</p>
+ </item>
+ <item>
+ <p>
+ Fix the internal handling of options 'verify' and
+ 'verify_fun'.</p>
+ <p>
+ This change fixes a vulnerability when setting the ssl
+ option 'verify' to verify_peer in a continued handshake
+ won't take any effect resulting in the acceptance of
+ expired peer certificates.</p>
+ <p>
+ Own Id: OTP-16767 Aux Id: ERIERL-512 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.6.2.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ If a passive socket is created, ssl:recv/2,3 is never
+ called and then the peer closes the socket the
+ controlling process will no longer receive an active
+ close message.</p>
+ <p>
+ Own Id: OTP-16697 Aux Id: ERIERL-496 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.6.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -573,6 +1211,40 @@
</section>
+<section><title>SSL 9.2.3.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Data deliver with ssl:recv/2,3 could fail for when using
+ packet mode. This has been fixed by correcting the flow
+ control handling of passive sockets when packet mode is
+ used.</p>
+ <p>
+ Own Id: OTP-16764</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.2.3.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix timing bug that could cause ssl sockets to become
+ unresponsive after an ssl:recv/3 call timed out</p>
+ <p>
+ Own Id: OTP-16619 Aux Id: ERL-1213 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.2.3.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -3341,7 +4013,7 @@
<item>
<p>
Add support for PSK (Pre Shared Key) and SRP (Secure
- Remote Password) chipher suits, thanks to Andreas
+ Remote Password) cipher suites, thanks to Andreas
Schultz.</p>
<p>
Own Id: OTP-10450 Aux Id: kunagi-269 [180] </p>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 172e3051b9..c3dd3f4df0 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -32,9 +32,9 @@
<modulesummary>Interface Functions for Secure Socket Layer</modulesummary>
<description>
<p>
- This module contains interface functions for the SSL/TLS/DTLS protocol.
+ This module contains interface functions for the TLS/DTLS protocol.
For detailed information about the supported standards see
- <seealso marker="ssl_app">ssl(6)</seealso>.
+ <seeapp marker="ssl_app">ssl(6)</seeapp>.
</p>
</description>
@@ -45,7 +45,7 @@
-->
<datatypes>
- <datatype_title>Types used in SSL/TLS/DTLS</datatype_title>
+ <datatype_title>Types used in TLS/DTLS</datatype_title>
<datatype>
@@ -78,11 +78,11 @@
<p>The default socket options are
<c>[{mode,list},{packet, 0},{header, 0},{active, true}]</c>.</p>
<p>For valid options, see the
- <seealso marker="kernel:inet">inet(3)</seealso>,
- <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso> and
- <seealso marker="kernel:gen_tcp">gen_udp(3)</seealso>
+ <seeerl marker="kernel:inet">inet(3)</seeerl>,
+ <seeerl marker="kernel:gen_tcp">gen_tcp(3)</seeerl> and
+ <seeerl marker="kernel:gen_tcp">gen_udp(3)</seeerl>
manual pages in Kernel. Note that stream oriented options such as packet
- are only relevant for SSL/TLS and not DTLS</p>
+ are only relevant for TLS and not DTLS</p>
</desc>
</datatype>
@@ -296,23 +296,37 @@
<p>Defaults to <c>full</c>. If hello is specified the handshake will
pause after the hello message and give the user a possibility make decisions
based on hello extensions before continuing or aborting the handshake by calling
- <seealso marker="#handshake_continue-3"> handshake_continue/3</seealso> or
- <seealso marker="#handshake_cancel-1"> handshake_cancel/1</seealso></p>
+ <seemfa marker="#handshake_continue/3"> handshake_continue/3</seemfa> or
+ <seemfa marker="#handshake_cancel/1"> handshake_cancel/1</seemfa></p>
</desc>
</datatype>
<datatype>
<name name="cert"/>
<desc>
- <p>The DER-encoded users certificate. If this option
- is supplied, it overrides option <c>certfile</c>.</p>
+ <p>The DER-encoded users certificate. Note that the cert option may also
+ be a list of DER-encoded certificates where the first one is the users
+ certificate and the rest of the certificates constitutes the
+ certificate chain. For maximum interoperability the
+ certificates in the chain should be in the correct order, the
+ chain will be sent as is to the peer. If chain certificates
+ are not provided, certificates from <seetype
+ marker="#client_cacerts">client_cacerts()</seetype>, <seetype
+ marker="#server_cacerts">server_cacerts()</seetype>, or
+ <seetype marker="#client_cafile">client_cafile()</seetype>,
+ <seetype marker="#server_cafile">server_cafile()</seetype> are
+ used to construct the chain. If this option is supplied, it
+ overrides option <c>certfile</c>.</p>
</desc>
</datatype>
<datatype>
<name name="cert_pem"/>
<desc>
- <p>Path to a file containing the user certificate on PEM format.</p>
+ <p>Path to a file containing the user certificate on PEM format or possible several
+ certificates where the first one is the users certificate and the rest of the certificates
+ constitutes the certificate chain. For more details see <seetype marker="#cert">cert()</seetype>,
+ </p>
</desc>
</datatype>
@@ -321,8 +335,8 @@
<desc>
<p>The DER-encoded user's private key or a map refering to a crypto
engine and its key reference that optionally can be password protected,
- seealso <seealso marker="crypto:crypto#engine_load-4"> crypto:engine_load/4
- </seealso> and <seealso marker="crypto:engine_load"> Crypto's Users Guide</seealso>. If this option
+ seealso <seemfa marker="crypto:crypto#engine_load/4"> crypto:engine_load/4
+ </seemfa> and <seeguide marker="crypto:engine_load"> Crypto's Users Guide</seeguide>. If this option
is supplied, it overrides option <c>keyfile</c>.</p>
</desc>
</datatype>
@@ -350,22 +364,22 @@
<desc>
<p>A list of cipher suites that should be supported</p>
- <p>The function <seealso
- marker="#cipher_suites-2"> ssl:cipher_suites/2 </seealso>
+ <p>The function <seemfa
+ marker="#cipher_suites/2"> ssl:cipher_suites/2 </seemfa>
can be used to find all cipher suites that
are supported by default and all cipher suites that may be configured.</p>
<p>If you compose your own <c>cipher_suites()</c> make
sure they are filtered for cryptolib support
- <seealso
- marker="#filter_cipher_suites-2"> ssl:filter_cipher_suites/2 </seealso>
- Additionaly the functions <seealso
- marker="#append_cipher_suites-2"> ssl:append_cipher_suites/2 </seealso>
- , <seealso
- marker="#prepend_cipher_suites-2"> ssl:prepend_cipher_suites/2</seealso>,
- <seealso marker="#suite_to_str/1">ssl:suite_to_str/1</seealso>,
- <seealso marker="#str_to_suite/1">ssl:str_to_suite/1</seealso>,
- and <seealso marker="#suite_to_openssl_str/1">ssl:suite_to_openssl_str/1</seealso>
+ <seemfa
+ marker="#filter_cipher_suites/2"> ssl:filter_cipher_suites/2 </seemfa>
+ Additionaly the functions <seemfa
+ marker="#append_cipher_suites/2"> ssl:append_cipher_suites/2 </seemfa>
+ , <seemfa
+ marker="#prepend_cipher_suites/2"> ssl:prepend_cipher_suites/2</seemfa>,
+ <seemfa marker="#suite_to_str/1">ssl:suite_to_str/1</seemfa>,
+ <seemfa marker="#str_to_suite/1">ssl:str_to_suite/1</seemfa>,
+ and <seemfa marker="#suite_to_openssl_str/1">ssl:suite_to_openssl_str/1</seemfa>
also exist to help creating customized cipher suite lists.</p>
<note><p>Note that TLS-1.3 and TLS-1.2 cipher suites are not overlapping
@@ -437,7 +451,7 @@
in a valid certification path. So, if depth is 0 the PEER must
be signed by the trusted ROOT-CA directly; if 1 the path can
be PEER, CA, ROOT-CA; if 2 the path can be PEER, CA, CA,
- ROOT-CA, and so on. The default value is 1.</p>
+ ROOT-CA, and so on. The default value is 10.</p>
</desc>
</datatype>
@@ -449,8 +463,8 @@
<code>
fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() |
{revoked, atom()}} |
- {extension, #'Extension'{}}, InitialUserState :: term()) ->
- {valid, UserState :: term()} | {valid_peer, UserState :: term()} |
+ {extension, #'Extension'{}} | valid | valid_peer, InitialUserState :: term()) ->
+ {valid, UserState :: term()} |
{fail, Reason :: term()} | {unknown, UserState :: term()}.
</code>
@@ -462,8 +476,8 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() |
application. It differentiates between the peer certificate
and the CA certificates by using <c>valid_peer</c> or
<c>valid</c> as second argument to the verification fun. See
- the <seealso marker="public_key:public_key_records">public_key
- User's Guide</seealso> for definition of
+ the <seeguide marker="public_key:public_key_records">public_key
+ User's Guide</seeguide> for definition of
<c>#'OTPCertificate'{}</c> and <c>#'Extension'{}</c>.</p>
<list type="bulleted">
@@ -532,8 +546,8 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() |
<item><p>The chain consisted only of one self-signed certificate.</p></item>
<tag><c>PKIX X-509-path validation error</c></tag>
- <item><p>For possible reasons, see <seealso
-marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso>
+ <item><p>For possible reasons, see <seemfa
+marker="public_key:public_key#pkix_path_validation/3">public_key:pkix_path_validation/3</seemfa>
</p></item>
</taglist>
</desc>
@@ -543,11 +557,11 @@ marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_valid
<name name="crl_check"/>
<desc>
<p>Perform CRL (Certificate Revocation List) verification
- <seealso marker="public_key:public_key#pkix_crls_validate-3">
- (public_key:pkix_crls_validate/3)</seealso> on all the
- certificates during the path validation <seealso
- marker="public_key:public_key#pkix_path_validation-3">(public_key:pkix_path_validation/3)
- </seealso>
+ <seemfa marker="public_key:public_key#pkix_crls_validate/3">
+ (public_key:pkix_crls_validate/3)</seemfa> on all the
+ certificates during the path validation <seemfa
+ marker="public_key:public_key#pkix_path_validation/3">(public_key:pkix_path_validation/3)
+ </seemfa>
of the certificate chain. Defaults to <c>false</c>.</p>
<taglist>
@@ -563,7 +577,7 @@ marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_valid
construct the certificate chain validating the CRLs.</p>
<p>The CRLs will be fetched from a local or external cache. See
- <seealso marker="ssl:ssl_crl_cache_api">ssl_crl_cache_api(3)</seealso>.</p>
+ <seeerl marker="ssl:ssl_crl_cache_api">ssl_crl_cache_api(3)</seeerl>.</p>
</desc>
</datatype>
@@ -571,7 +585,7 @@ marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_valid
<name name="crl_cache_opts"/>
<desc>
<p>Specify how to perform lookup and caching of certificate revocation lists.
- <c>Module</c> defaults to <seealso marker="ssl:ssl_crl_cache">ssl_crl_cache</seealso>
+ <c>Module</c> defaults to <seeerl marker="ssl:ssl_crl_cache">ssl_crl_cache</seeerl>
with <c> DbHandle </c> being <c>internal</c> and an
empty argument list.</p>
@@ -581,16 +595,16 @@ marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_valid
<tag><c>ssl_crl_cache</c></tag>
<item>
<p>This module maintains a cache of CRLs. CRLs can be
- added to the cache using the function <seealso
- marker="ssl:ssl_crl_cache#insert-1">ssl_crl_cache:insert/1</seealso>,
+ added to the cache using the function <seemfa
+ marker="ssl:ssl_crl_cache#insert/1">ssl_crl_cache:insert/1</seemfa>,
and optionally automatically fetched through HTTP if the
following argument is specified:</p>
<taglist>
<tag><c>{http, timeout()}</c></tag>
<item><p>
- Enables fetching of CRLs specified as http URIs in<seealso
- marker="public_key:public_key_records">X509 certificate extensions</seealso>.
+ Enables fetching of CRLs specified as http URIs in<seeguide
+ marker="public_key:public_key_records">X509 certificate extensions</seeguide>.
Requires the OTP inets application.</p>
</item>
</taglist>
@@ -635,8 +649,8 @@ fun(Chain::[public_key:der_encoded()]) ->
{trusted_ca, DerCert::public_key:der_encoded()} | unknown_ca}
</code>
<p>Claim an intermediate CA in the chain as trusted. TLS then
- performs <seealso
- marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso>
+ performs <seemfa
+ marker="public_key:public_key#pkix_path_validation/3">public_key:pkix_path_validation/3</seemfa>
with the selected CA as trusted anchor and the rest of the chain.</p>
</desc>
</datatype>
@@ -647,8 +661,8 @@ fun(Chain::[public_key:der_encoded()]) ->
<desc><p>TLS protocol versions supported by started clients and servers.
This option overrides the application environment option
<c>protocol_version</c> and <c>dtls_protocol_version</c>. If the environment option is not set, it defaults
- to all versions, except SSL-3.0, supported by the SSL application.
- See also <seealso marker="ssl:ssl_app">ssl(6).</seealso></p>
+ to all versions, supported by the SSL application.
+ See also <seeapp marker="ssl:ssl_app">ssl(6).</seeapp></p>
</desc>
</datatype>
@@ -658,9 +672,9 @@ fun(Chain::[public_key:der_encoded()]) ->
<desc><p>The lookup fun is to defined as follows:</p>
<code>
-fun(psk, PSKIdentity ::string(), UserState :: term()) ->
+fun(psk, PSKIdentity :: binary(), UserState :: term()) ->
{ok, SharedSecret :: binary()} | error;
-fun(srp, Username :: string(), UserState :: term()) ->
+fun(srp, Username :: binary(), UserState :: term()) ->
{ok, {SRPParams :: srp_param_type(), Salt :: binary(),
DerivedKey :: binary()}} | error.
</code>
@@ -674,7 +688,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<p>For Secure Remote Password (SRP), the fun is only used by the server to
obtain parameters that it uses to generate its session keys.
<c>DerivedKey</c> is to be derived according to
- <url href="http://tools.ietf.org/html/rfc2945#section-3"> RFC 2945</url> and
+ <url href="http://tools.ietf.org/html/rfc2945#section/3"> RFC 2945</url> and
<url href="http://tools.ietf.org/html/rfc5054#section-2.4"> RFC 5054</url>:
<c>crypto:sha([Salt, crypto:sha([Username, &lt;&lt;$:&gt;&gt;, Password])])</c>
</p>
@@ -691,7 +705,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<datatype>
<name name="log_alert"/>
<desc><p>If set to <c>false</c>, TLS/DTLS Alert reports are not displayed.
- Deprecated in OTP 22, use {log_level, <seealso marker="#type-logging_level">logging_level()</seealso>} instead.</p>
+ Deprecated in OTP 22, use {log_level, <seetype marker="#logging_level">logging_level()</seetype>} instead.</p>
</desc>
</datatype>
@@ -700,7 +714,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc><p>Specifies the log level for a TLS/DTLS
connection. Alerts are logged on <c>notice</c> level, which is
the default level. The level <c>debug</c> triggers verbose
- logging of TLS/DTLS protocol messages. See also <seealso marker="ssl_app">ssl(6)</seealso>
+ logging of TLS/DTLS protocol messages. See also <seeapp marker="ssl_app">ssl(6)</seeapp>
</p>
</desc>
</datatype>
@@ -736,7 +750,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<datatype>
<name name="beast_mitigation"/>
- <desc><p>Affects SSL-3.0 and TLS-1.0 connections only. Used to change the BEAST
+ <desc><p>Affects TLS-1.0 connections only. Used to change the BEAST
mitigation strategy to interoperate with legacy software.
Defaults to <c>one_n_minus_one</c>.</p>
@@ -746,7 +760,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<p><c>disabled</c> - Disable BEAST mitigation.</p>
- <warning><p>Using <c>{beast_mitigation, disabled}</c> makes SSL-3.0 or TLS-1.0
+ <warning><p>Using <c>{beast_mitigation, disabled}</c> makes TLS-1.0
vulnerable to the BEAST attack.</p></warning>
</desc>
</datatype>
@@ -777,6 +791,27 @@ fun(srp, Username :: string(), UserState :: term()) ->
</desc>
</datatype>
+ <datatype>
+ <name name="middlebox_comp_mode"/>
+ <desc><p>Configures the middlebox compatibility mode on a TLS 1.3 connection.</p>
+ <p>A significant number of middleboxes misbehave when a TLS 1.3 connection is
+ negotiated. Implementations can increase the chance of making connections
+ through those middleboxes by making the TLS 1.3 handshake more like a
+ TLS 1.2 handshake.</p>
+ <p>The middlebox compatibility mode is enabled (<c>true</c>) by default.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="keep_secrets"/>
+ <desc><p>Configures a TLS 1.3 connection for keylogging</p>
+ <p>In order to retrieve keylog information on a TLS 1.3 connection, it must be configured
+ in advance to keep the client_random and various handshake secrets.</p>
+ <p>The keep_secrets functionality is disabled (<c>false</c>) by default.</p>
+ <p>Added in OTP 23.2</p>
+ </desc>
+ </datatype>
+
<datatype_title>TLS/DTLS OPTION DESCRIPTIONS - CLIENT</datatype_title>
<datatype>
@@ -786,15 +821,19 @@ fun(srp, Username :: string(), UserState :: term()) ->
<datatype>
<name name="client_verify_type"/>
<desc><p>In mode <c>verify_none</c> the default behavior is to allow
- all x509-path validation errors. See also option <seealso marker="#type-custom_verify">verify_fun</seealso>.</p>
+ all x509-path validation errors. See also option <seetype marker="#custom_verify">verify_fun</seetype>.</p>
</desc>
</datatype>
<datatype>
<name name="client_reuse_session"/>
<desc>
- <p>Reuses a specific session earlier saved with the option
- <c>{reuse_sessions, save} since OTP-21.3 </c>
+ <p>Reuses a specific session. The session should be refered by its session id if it is
+ earlier saved with the option <c>{reuse_sessions, save}</c> since OTP-21.3 or
+ explicitly specified by its session id and associated data since OTP-22.3.
+ See also
+ <seeguide marker="ssl:using_ssl#session-reuse-pre-tls-1.3">
+ SSL's Users Guide, Session Reuse pre TLS 1.3</seeguide>
</p>
</desc>
</datatype>
@@ -804,8 +843,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc>
<p>When <c>save</c> is specified a new connection will be negotiated
and saved for later reuse. The session ID can be fetched with
- <seealso marker="#connection_information-2">connection_information/2</seealso>
- and used with the client option <seealso marker="#type-client_reuse_session">reuse_session</seealso>
+ <seemfa marker="#connection_information/2">connection_information/2</seemfa>
+ and used with the client option <seetype marker="#client_reuse_session">reuse_session</seetype>
The boolean value true specifies that if possible, automatized session reuse will
be performed. If a new session is created, and is unique in regard
to previous stored sessions, it will be saved for possible later reuse. Since OTP-21.3</p>
@@ -868,6 +907,15 @@ fun(srp, Username :: string(), UserState :: term()) ->
</datatype>
<datatype>
+ <name name="max_fragment_length"/>
+ <desc>
+ <p>Specifies the maximum fragment length the client
+ is prepared to accept from the server.
+ See <url href="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</url></p>
+ </desc>
+ </datatype>
+
+ <datatype>
<name name="client_psk_identity"/>
<desc>
<p>Specifies the identity the client presents to the server.
@@ -887,15 +935,15 @@ fun(srp, Username :: string(), UserState :: term()) ->
<name name="sni"/>
<desc>
<p>Specify the hostname to be used in TLS Server Name Indication extension.
- If not specified it will default to the <c>Host</c> argument of <seealso marker="#connect-3">connect/[3,4]</seealso>
+ If not specified it will default to the <c>Host</c> argument of <seemfa marker="#connect/3">connect/[3,4]</seemfa>
unless it is of type inet:ipaddress().</p>
<p>
The <c>HostName</c> will also be used in the hostname verification of the peer certificate using
- <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>.
+ <seemfa marker="public_key:public_key#pkix_verify_hostname/2">public_key:pkix_verify_hostname/2</seemfa>.
</p>
<p> The special value <c>disable</c> prevents the Server Name Indication extension from being sent and
disables the hostname verification check
- <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso> </p>
+ <seemfa marker="public_key:public_key#pkix_verify_hostname/2">public_key:pkix_verify_hostname/2</seemfa> </p>
</desc>
</datatype>
@@ -904,7 +952,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc>
<p> Customizes the hostname verification of the peer certificate, as different protocols that use
TLS such as HTTP or LDAP may want to do it differently, for possible options see
- <seealso marker="public_key:public_key#pkix_verify_hostname-3">public_key:pkix_verify_hostname/3</seealso> </p>
+ <seemfa marker="public_key:public_key#pkix_verify_hostname/3">public_key:pkix_verify_hostname/3</seemfa> </p>
</desc>
</datatype>
@@ -916,10 +964,9 @@ fun(srp, Username :: string(), UserState :: term()) ->
<warning><p>Note this option is not needed in normal TLS usage and should not be used
to implement new clients. But legacy clients that retries connections in the following manner</p>
- <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv2', 'tlsv1.1', 'tlsv1', 'sslv3']}])</c></p>
- <p><c> ssl:connect(Host, Port, [...{versions, [tlsv1.1', 'tlsv1', 'sslv3']}, {fallback, true}])</c></p>
- <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv1', 'sslv3']}, {fallback, true}]) </c></p>
- <p><c> ssl:connect(Host, Port, [...{versions, ['sslv3']}, {fallback, true}]) </c></p>
+ <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv2', 'tlsv1.1', 'tlsv1']}])</c></p>
+ <p><c> ssl:connect(Host, Port, [...{versions, [tlsv1.1', 'tlsv1']}, {fallback, true}])</c></p>
+ <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv1']}, {fallback, true}]) </c></p>
<p>may use it to avoid undesired TLS version downgrade. Note that TLS_FALLBACK_SCSV must also
be supported by the server for the prevention to work.
@@ -976,8 +1023,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
<p>If it is set to <c>auto</c>, the client automatically handles received tickets and tries
to use them when making new TLS connections (session resumption with pre-shared keys).</p>
<note><p>This option is supported by TLS 1.3 and above. See also
- <seealso marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">
- SSL's Users Guide, Session Tickets and Session Resumption in TLS 1.3</seealso>
+ <seeguide marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">
+ SSL's Users Guide, Session Tickets and Session Resumption in TLS 1.3</seeguide>
</p></note>
</desc>
</datatype>
@@ -990,14 +1037,51 @@ fun(srp, Username :: string(), UserState :: term()) ->
<note><p>Session tickets are only sent to user if option <em>session_tickets</em> is set
to <c>manual</c></p>
<p>This option is supported by TLS 1.3 and above. See also
- <seealso marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">
- SSL's Users Guide, Session Tickets and Session Resumption in TLS 1.3</seealso>
+ <seeguide marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">
+ SSL's Users Guide, Session Tickets and Session Resumption in TLS 1.3</seeguide>
</p></note>
</desc>
</datatype>
- <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - SERVER </datatype_title>
+ <datatype>
+ <name name="client_early_data"/>
+ <desc>
+ <p>Configures the early data to be sent by the client.</p>
+ <p>In order to be able to verify
+ that the server has the intention to process the early data, the following 3-tuple is
+ sent to the user process:</p>
+ <p><c>{ssl, SslSocket, {early_data, Result}}</c></p>
+ <p>where <c>Result</c> is either <c>accepted</c> or <c>rejected</c>.</p>
+ <warning>
+ <p>It is the responsibility of the user to handle a rejected Early Data and
+ to resend when it is appropriate.</p></warning>
+ </desc>
+ </datatype>
+ <!-- <datatype> -->
+ <!-- <name name="ocsp_stapling"/> -->
+ <!-- <desc><p>If true, OCSP stapling will be enabled, an extension of type "status_request" will be -->
+ <!-- included in the client hello to indicate the desire to receive certificate status information. -->
+ <!-- If false (the default), OCSP stapling will be disabled.</p> -->
+ <!-- </desc> -->
+ <!-- </datatype> -->
+
+ <!-- <datatype> -->
+ <!-- <name name="ocsp_responder_certs"/> -->
+ <!-- <desc><p>This option is a list of DER encoded certificates of OCSP responders which the client -->
+ <!-- trusts. This option is an empty list by default which means that the responders are implicitly -->
+ <!-- known to the server.</p> -->
+ <!-- </desc> -->
+ <!-- </datatype> -->
+
+ <!-- <datatype> -->
+ <!-- <name name="ocsp_nonce"/> -->
+ <!-- <desc><p>If true (the default), the nonce will be included as one of the request extensions in the -->
+ <!-- request. If false, nonce will not present in the request extensions.</p> -->
+ <!-- </desc> -->
+ <!-- </datatype> -->
+
+ <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - SERVER</datatype_title>
<datatype>
<name name="server_option"/>
@@ -1068,7 +1152,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc><p>The boolean value true specifies that the server will
agree to reuse sessions. Setting it to false will result in an empty
session table, that is no sessions will be reused.
- See also option <seealso marker="#type-server_reuse_session">reuse_session</seealso>
+ See also option <seetype marker="#server_reuse_session">reuse_session</seetype>
</p>
</desc>
</datatype>
@@ -1145,12 +1229,12 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc>
<p>If the server receives a SNI (Server Name Indication)
from the client, the given function will be called to
- retrieve <seealso marker="#type-server_option">[server_option()] </seealso> for the indicated server.
+ retrieve <seetype marker="#server_option">[server_option()] </seetype> for the indicated server.
These options will be merged into predefined
- <seealso marker="#type-server_option">[server_option()] </seealso> list.
+ <seetype marker="#server_option">[server_option()] </seetype> list.
The function should be defined as:
- fun(ServerName :: string()) -> <seealso marker="#type-server_option">[server_option()] </seealso>
+ fun(ServerName :: string()) -> <seetype marker="#server_option">[server_option()] </seetype>
and can be specified as a fun or as named <c>fun module:function/1</c>
The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p>
@@ -1195,9 +1279,9 @@ fun(srp, Username :: string(), UserState :: term()) ->
ones accepted by the server in a signature algorithm
negotiation, introduced in TLS-1.2. The algorithms will also
be offered to the client if a client certificate is
- requested. For more details see the <seealso
- marker="#type-client_signature_algs">corresponding client
- option</seealso>.
+ requested. For more details see the <seetype
+ marker="#client_signature_algs">corresponding client
+ option</seetype>.
</p>
</desc>
</datatype>
@@ -1215,8 +1299,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
material and state data.
</p>
<note><p>This option is supported by TLS 1.3 and above. See also
- <seealso marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">
- SSL's Users Guide, Session Tickets and Session Resumption in TLS 1.3</seealso>
+ <seeguide marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">
+ SSL's Users Guide, Session Tickets and Session Resumption in TLS 1.3</seeguide>
</p></note>
</desc>
</datatype>
@@ -1244,12 +1328,70 @@ fun(srp, Username :: string(), UserState :: term()) ->
<note><p>This option is supported by TLS 1.3 and above and only with stateless session tickets.
Ticket lifetime, the number of tickets sent by the server and the maximum number of tickets
stored by the server in stateful mode are configured by
- <seealso marker="ssl:ssl_app#configuration">application variables</seealso>. See also
- <seealso marker="ssl:using_ssl#anti-replay-protection-in-tls-1.3">
- SSL's Users Guide, Anti-Replay Protection in TLS 1.3</seealso>
+ <seeapp marker="ssl:ssl_app#configuration">application variables</seeapp>. See also
+ <seeguide marker="ssl:using_ssl#anti-replay-protection-in-tls-1.3">
+ SSL's Users Guide, Anti-Replay Protection in TLS 1.3</seeguide>
</p></note>
</desc>
</datatype>
+
+ <datatype>
+ <name name="cookie"/>
+ <desc><p>If <c>true</c> (default), the server sends a cookie extension in its
+ HelloRetryRequest messages.</p>
+ <note><p>The cookie extension has two main purposes. It allows the server to
+ force the client to demonstrate reachability at their apparent network
+ address (thus providing a measure of DoS protection). This is primarily
+ useful for non-connection-oriented transports. It also allows to offload the
+ server's state to the client.
+ The cookie extension is enabled by default as it is a mandatory extension
+ in RFC8446.</p></note>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="server_early_data"/>
+ <desc>
+ <p>Configures if the server accepts (<c>enabled</c>) or rejects (<c>rejects</c>) early
+ data sent by a client. The default value is <c>disabled</c>.
+ </p>
+ <warning><p>This option is a placeholder, early data is not yet implemented on the server side.
+ </p></warning>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="connection_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="common_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="curve_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="ssl_options_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="security_info"/>
+ </datatype>
+
+ <datatype>
+ <name name="connection_info_items"/>
+ </datatype>
+
+ <datatype>
+ <name name="connection_info_item"/>
+ </datatype>
+
+ <datatype>
+ <name name="tls_options_name"/>
+ </datatype>
+
</datatypes>
<!--
@@ -1266,7 +1408,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc><p>Make <c>Deferred</c> suites become the least preferred
suites, that is put them at the end of the cipher suite list
<c>Suites</c> after removing them from <c>Suites</c> if
- present. <c>Deferred</c> may be a list of cipher suits or a
+ present. <c>Deferred</c> may be a list of cipher suites or a
list of filters in which case the filters are use on <c>Suites</c> to
extract the Deferred cipher list.</p>
</desc>
@@ -1277,27 +1419,37 @@ fun(srp, Username :: string(), UserState :: term()) ->
<name name="cipher_suites" arity="1" since="OTP R14B"/>
<fsummary>Returns a list of supported cipher suites.</fsummary>
<desc>
- <p>Deprecated in OTP 21, use <seealso marker="#cipher_suites-2">cipher_suites/2</seealso> instead.</p>
+ <p>Deprecated in OTP 21, use <seemfa marker="#cipher_suites/2">cipher_suites/2</seemfa> instead.</p>
</desc>
</func>
<func>
<name name="cipher_suites" arity="2" since="OTP 20.3"/>
- <fsummary>Returns a list of all default or
- all supported cipher suites.</fsummary>
- <desc><p>Returns all default or all supported (except anonymous),
- or all anonymous cipher suites for a
- TLS version</p>
-
- <note><p>The cipher suites returned by this function are the
- cipher suites that the OTP ssl application can support provided
- that they are supported by the cryptolib linked with the OTP
- crypto application. Use <seealso
- marker="#filter_cipher_suites-2"> ssl:filter_cipher_suites(Suites,
- []).</seealso> to filter the list for the current
- cryptolib. Note that cipher suites may be filtered out because
- they are too old or too new depending on the
- cryptolib</p></note>
+ <fsummary>Returns a list of cipher suites.</fsummary>
+ <desc><p>Lists all possible cipher suites corresponding to
+ <c>Description</c> that are available. The
+ <c>exclusive</c> option will exclusively list cipher suites
+ introduced in <c>Version</c> whereas the other options
+ are inclusive from the lowest possible version to
+ <c>Version</c>. The <c>all</c> options includes all suites
+ except the anonymous.
+ </p>
+
+ <note><p>TLS-1.3 has no overlapping cipher suites with previous
+ TLS versions, that is the result of
+ <c>cipher_suites(all, 'tlsv1.3').</c> contains a separate set of
+ suites that can be used with TLS-1.3 an other set that can be used
+ if a lower version is negotiated. No anonymous suites are
+ supported by TLS-1.3.</p>
+
+ <p>Also note that the cipher suites returned
+ by this function are the cipher suites that the OTP ssl
+ application can support provided that they are supported by the
+ cryptolib linked with the OTP crypto application. Use <seemfa
+ marker="#filter_cipher_suites/2"> ssl:filter_cipher_suites(Suites,
+ []).</seemfa> to filter the list for the current cryptolib. Note
+ that cipher suites may be filtered out because they are too old or
+ too new depending on the cryptolib</p></note>
</desc>
</func>
@@ -1305,8 +1457,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name name="cipher_suites" arity="3" since="OTP 22.0"/>
<fsummary>Returns a list of RFC or OpenSSL names</fsummary>
- <desc><p>Same as <seealso marker="#cipher_suites-2">cipher_suites/2</seealso>
- but lists RFC or OpenSSL string names instead of <seealso marker="#type-erl_cipher_suite">erl_cipher_suite()</seealso>
+ <desc><p>Same as <seemfa marker="#cipher_suites/2">cipher_suites/2</seemfa>
+ but lists RFC or OpenSSL string names instead of <seetype marker="#erl_cipher_suite">erl_cipher_suite()</seetype>
</p>
</desc>
</func>
@@ -1336,33 +1488,33 @@ fun(srp, Username :: string(), UserState :: term()) ->
<name name="connect" arity="2" since="OTP R14B"/>
<name name="connect" arity="3" clause_i="1" since=""/>
<fsummary>Upgrades a <c>gen_tcp</c>, or
- equivalent, connected socket to an TLS socket.</fsummary>
+ equivalent, connected socket to a TLS socket.</fsummary>
<desc><p>Upgrades a <c>gen_tcp</c>, or equivalent,
- connected socket to an TLS socket, that is, performs the
+ connected socket to a TLS socket, that is, performs the
client-side TLS handshake.</p>
<note><p>If the option <c>verify</c> is set to
<c>verify_peer</c> the option <c>server_name_indication</c>
shall also be specified, if it is not no Server Name
- Indication extension will be sent, and <seealso
- marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>
+ Indication extension will be sent, and <seemfa
+ marker="public_key:public_key#pkix_verify_hostname/2">public_key:pkix_verify_hostname/2</seemfa>
will be called with the IP-address of the connection as
- <c>ReferenceID</c>, which is proably not what you want.</p>
+ <c>ReferenceID</c>, which is probably not what you want.</p>
</note>
<p> If the option <c>{handshake, hello}</c> is used the
handshake is paused after receiving the server hello message
and the success response is <c>{ok, SslSocket, Ext}</c>
instead of <c>{ok, SslSocket}</c>. Thereafter the handshake
- is continued or canceled by calling <seealso
- marker="#handshake_continue-3">
- <c>handshake_continue/3</c></seealso> or <seealso
- marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>.
+ is continued or canceled by calling <seemfa
+ marker="#handshake_continue/3">
+ <c>handshake_continue/3</c></seemfa> or <seemfa
+ marker="#handshake_cancel/1"><c>handshake_cancel/1</c></seemfa>.
</p>
<p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value,
the process owning the sslsocket will receive messages of type
- <seealso marker="#type-active_msgs"> active_msgs() </seealso>
+ <seetype marker="#active_msgs"> active_msgs() </seetype>
</p>
</desc>
</func>
@@ -1370,20 +1522,20 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name since="" name="connect" arity="3" clause_i="2"/>
<name since="" name="connect" arity="4"/>
- <fsummary>Opens an TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</fsummary>
- <desc><p>Opens an TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</p>
+ <fsummary>Opens a TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</fsummary>
+ <desc><p>Opens a TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</p>
<p> When the option <c>verify</c> is set to <c>verify_peer</c> the check
- <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>
+ <seemfa marker="public_key:public_key#pkix_verify_hostname/2">public_key:pkix_verify_hostname/2</seemfa>
will be performed in addition to the usual x509-path validation checks. If the check fails the error {bad_cert, hostname_check_failed} will
- be propagated to the path validation fun <seealso marker="#type-custom_verify">verify_fun</seealso>, where it is possible to do customized
- checks by using the full possibilities of the <seealso marker="public_key:public_key#pkix_verify_hostname-3">public_key:pkix_verify_hostname/3</seealso> API.
+ be propagated to the path validation fun <seetype marker="#custom_verify">verify_fun</seetype>, where it is possible to do customized
+ checks by using the full possibilities of the <seemfa marker="public_key:public_key#pkix_verify_hostname/3">public_key:pkix_verify_hostname/3</seemfa> API.
When the option <c>server_name_indication</c> is provided, its value (the DNS name) will be used as <c>ReferenceID</c>
- to <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>.
+ to <seemfa marker="public_key:public_key#pkix_verify_hostname/2">public_key:pkix_verify_hostname/2</seemfa>.
When no <c>server_name_indication</c> option is given, the <c>Host</c> argument will be used as
Server Name Indication extension. The <c>Host</c> argument will also be used for the
- <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso> check and if the <c>Host</c>
+ <seemfa marker="public_key:public_key#pkix_verify_hostname/2">public_key:pkix_verify_hostname/2</seemfa> check and if the <c>Host</c>
argument is an <c>inet:ip_address()</c> the <c>ReferenceID</c> used for the check will be <c>{ip, Host}</c> otherwise
<c>dns_id</c> will be assumed with a fallback to <c>ip</c> if that fails. </p>
<note><p>According to good practices certificates should not use IP-addresses as "server names". It would
@@ -1394,29 +1546,29 @@ fun(srp, Username :: string(), UserState :: term()) ->
handshake is paused after receiving the server hello message
and the success response is <c>{ok, SslSocket, Ext}</c>
instead of <c>{ok, SslSocket}</c>. Thereafter the handshake is continued or
- canceled by calling <seealso marker="#handshake_continue-3">
- <c>handshake_continue/3</c></seealso> or <seealso
- marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>.
+ canceled by calling <seemfa marker="#handshake_continue/3">
+ <c>handshake_continue/3</c></seemfa> or <seemfa
+ marker="#handshake_cancel/1"><c>handshake_cancel/1</c></seemfa>.
</p>
<p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value,
the process owning the sslsocket will receive messages of type
- <seealso marker="#type-active_msgs"> active_msgs() </seealso>
+ <seetype marker="#active_msgs"> active_msgs() </seetype>
</p>
</desc>
</func>
<func>
<name since="" name="close" arity="1" />
- <fsummary>Closes an TLS/DTLS connection.</fsummary>
- <desc><p>Closes an TLS/DTLS connection.</p>
+ <fsummary>Closes a TLS/DTLS connection.</fsummary>
+ <desc><p>Closes a TLS/DTLS connection.</p>
</desc>
</func>
<func>
<name since="OTP 18.1" name="close" arity="2"/>
- <fsummary>Closes an TLS connection.</fsummary>
- <desc><p>Closes or downgrades an TLS connection. In the latter case the transport
+ <fsummary>Closes a TLS connection.</fsummary>
+ <desc><p>Closes or downgrades a TLS connection. In the latter case the transport
connection will be handed over to the <c>NewController</c> process after receiving
the TLS close alert from the peer. The returned transport socket will have
the following options set: <c>[{active, false}, {packet, 0}, {mode, binary}]</c></p>
@@ -1440,9 +1592,9 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc><p>Returns the most relevant information about the connection, ssl options that
are undefined will be filtered out. Note that values that affect the security of the
connection will only be returned if explicitly requested by connection_information/2.</p>
- <note><p>The legacy <c>Item = cipher_suite</c> is still supported
- and returns the cipher suite on its (undocumented) legacy format.
- It should be replaced by <c>selected_cipher_suite</c>.</p></note>
+ <note><p>The legacy <c>Item = cipher_suite</c> was removed in OTP-23.
+ Previously it returned the cipher suite on its (undocumented)
+ legacy format. It is replaced by <c>selected_cipher_suite</c>.</p></note>
</desc>
</func>
@@ -1452,7 +1604,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</fsummary>
<desc><p>Returns the requested information items about the connection,
if they are defined.</p>
- <p>Note that client_random, server_random and master_secret are values
+ <p>Note that client_random, server_random, master_secret and keylog are values
that affect the security of connection. Meaningful atoms, not specified
above, are the ssl option names.</p>
@@ -1468,7 +1620,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
returns false for any part of the cipher suite. If no filter function is supplied for some
part the default behaviour regards it as if there was a filter function that returned true.
- For examples see <seealso marker="ssl:using_ssl#customizing-cipher-suits"> Customizing cipher suits </seealso>
+ For examples see <seeguide marker="ssl:using_ssl#customizing-cipher-suites"> Customizing cipher suites </seeguide>
Additionaly this function also filters the cipher suites to
exclude cipher suites not supported by the cryptolib used by the
@@ -1509,14 +1661,14 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name since="OTP 21.0" name="handshake" arity="1" />
<name since="OTP 21.0" name="handshake" arity="2" clause_i="1" />
- <fsummary>Performs server-side SSL/TLS handshake.</fsummary>
+ <fsummary>Performs server-side TLS handshake.</fsummary>
<desc>
- <p>Performs the SSL/TLS/DTLS server-side handshake.</p>
+ <p>Performs the TLS/DTLS server-side handshake.</p>
<p>Returns a new TLS/DTLS socket if the handshake is successful.</p>
<p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value,
the process owning the sslsocket will receive messages of type
- <seealso marker="#type-active_msgs"> active_msgs() </seealso>
+ <seetype marker="#active_msgs"> active_msgs() </seetype>
</p>
</desc>
</func>
@@ -1524,36 +1676,36 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name since="OTP 21.0" name="handshake" arity="2" clause_i="2" />
<name since="OTP 21.0" name="handshake" arity="3" />
- <fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary>
+ <fsummary>Performs server-side TLS/DTLS handshake.</fsummary>
<desc>
<p>If <c>Socket</c> is a ordinary <c>socket()</c>: upgrades a <c>gen_tcp</c>,
or equivalent, socket to an SSL socket, that is, performs
- the SSL/TLS server-side handshake and returns a TLS socket.</p>
+ the TLS server-side handshake and returns a TLS socket.</p>
<warning><p>The <c>Socket</c> shall be in passive mode ({active,
false}) before calling this function or else the behavior of this function
is undefined.
</p></warning>
-
+
<p>If <c>Socket</c> is an
- <seealso marker="#type-sslsocket"> sslsocket() </seealso>: provides extra SSL/TLS/DTLS
+ <seetype marker="#sslsocket"> sslsocket() </seetype>: provides extra TLS/DTLS
options to those specified in
- <seealso marker="#listen-2">listen/2 </seealso> and then performs
- the SSL/TLS/DTLS handshake. Returns a new TLS/DTLS socket if the handshake is successful.</p>
-
+ <seemfa marker="#listen/2">listen/2</seemfa> and then performs
+ the TLS/DTLS handshake. Returns a new TLS/DTLS socket if the handshake is successful.</p>
+
<p>
If option <c>{handshake, hello}</c> is specified the handshake is
paused after receiving the client hello message and the
success response is <c>{ok, SslSocket, Ext}</c> instead of <c>{ok,
SslSocket}</c>. Thereafter the handshake is continued or
- canceled by calling <seealso marker="#handshake_continue-3">
- <c>handshake_continue/3</c></seealso> or <seealso
- marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>.
+ canceled by calling <seemfa marker="#handshake_continue/3">
+ <c>handshake_continue/3</c></seemfa> or <seemfa
+ marker="#handshake_cancel/1"><c>handshake_cancel/1</c></seemfa>.
</p>
<p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value,
the process owning the sslsocket will receive messages of type
- <seealso marker="#type-active_msgs"> active_msgs() </seealso>
+ <seetype marker="#active_msgs"> active_msgs() </seetype>
</p>
</desc>
@@ -1570,9 +1722,9 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name since="OTP 21.0" name="handshake_continue" arity="2" />
<name since="OTP 21.0" name="handshake_continue" arity="3" />
- <fsummary>Continue the SSL/TLS handshake.</fsummary>
+ <fsummary>Continue the TLS handshake.</fsummary>
<desc>
- <p>Continue the SSL/TLS handshake possiby with new, additional or changed options.</p>
+ <p>Continue the TLS handshake possiby with new, additional or changed options.</p>
</desc>
</func>
@@ -1600,7 +1752,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc>
<p>The peer certificate is returned as a DER-encoded binary.
The certificate can be decoded with
- <seealso marker="public_key:public_key#pkix_decode_cert-2">public_key:pkix_decode_cert/2</seealso>
+ <seemfa marker="public_key:public_key#pkix_decode_cert/2">public_key:pkix_decode_cert/2</seemfa>
</p>
</desc>
</func>
@@ -1619,7 +1771,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc><p>Make <c>Preferred</c> suites become the most preferred
suites that is put them at the head of the cipher suite list
<c>Suites</c> after removing them from <c>Suites</c> if
- present. <c>Preferred</c> may be a list of cipher suits or a
+ present. <c>Preferred</c> may be a list of cipher suites or a
list of filters in which case the filters are use on <c>Suites</c> to
extract the preferred cipher list. </p>
</desc>
@@ -1633,8 +1785,6 @@ fun(srp, Username :: string(), UserState :: term()) ->
extra key material. It either takes user-generated values for
<c>Secret</c> and <c>Seed</c> or atoms directing it to use a specific
value from the session security parameters.</p>
- <p>Can only be used with TLS/DTLS connections; <c>{error, undefined}</c>
- is returned for SSLv3 connections.</p>
</desc>
</func>
@@ -1644,7 +1794,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<fsummary>Receives data on a socket.</fsummary>
<type_desc variable="HttpPacket">See the description of
<c>HttpPacket</c> in
- <seealso marker="erts:erlang#decode_packet/3"><c>erlang:decode_packet/3</c></seealso>
+ <seemfa marker="erts:erlang#decode_packet/3"><c>erlang:decode_packet/3</c></seemfa>
in ERTS.
</type_desc>
<desc>
@@ -1679,7 +1829,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc><p>There are cryptographic limits on the amount of plaintext which can be safely
encrypted under a given set of keys. If the amount of data surpasses those limits, a key
update is triggered and a new set of keys are installed.
- See also the option <seealso marker="ssl:ssl#type-key_update_at">key_update_at</seealso>.</p>
+ See also the option <seetype marker="ssl:ssl#key_update_at">key_update_at</seetype>.</p>
<p>This function can be used to explicitly start a key update on a TLS 1.3 connection.
There are two types of the key update: if <em>Type</em> is set to <em>write</em>, only the writing
key is updated; if <em>Type</em> is set to <em>read_write</em>, both the reading and writing keys
@@ -1722,18 +1872,18 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name since="" name="ssl_accept" arity="1" />
<name since="" name="ssl_accept" arity="2" />
- <fsummary>Performs server-side SSL/TLS handshake.</fsummary>
+ <fsummary>Performs server-side TLS handshake.</fsummary>
<desc>
- <p>Deprecated in OTP 21, use <seealso marker="#handshake-1">handshake/[1,2]</seealso> instead.</p>
+ <p>Deprecated in OTP 21, use <seemfa marker="#handshake/1">handshake/[1,2]</seemfa> instead.</p>
<note><p>handshake/[1,2] always returns a new socket.</p></note>
</desc>
</func>
<func>
<name since="OTP R14B" name="ssl_accept" arity="3" />
- <fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary>
+ <fsummary>Performs server-side TLS/DTLS handshake.</fsummary>
<desc>
- <p>Deprecated in OTP 21, use <seealso marker="#handshake-3">handshake/[2,3]</seealso> instead.</p>
+ <p>Deprecated in OTP 21, use <seemfa marker="#handshake/3">handshake/[2,3]</seemfa> instead.</p>
<note><p>handshake/[2,3] always returns a new socket.</p></note>
</desc>
</func>
@@ -1749,7 +1899,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name since="OTP R14B" name="start" arity="0" />
- <name since="OTP R14B">start(Type) -> ok | {error, Reason}</name>
+ <name since="OTP R14B" name="start" arity="1" />
<fsummary>Starts the SSL application.</fsummary>
<desc>
<p>Starts the SSL application. Default type
@@ -1770,7 +1920,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<fsummary>Converts an RFC or OpenSSL name string to an erlang cipher suite format</fsummary>
<desc>
<p>Converts an RFC or OpenSSL name string to an
- <seealso marker="#type-erl_cipher_suite">erl_cipher_suite()</seealso>
+ <seetype marker="#erl_cipher_suite">erl_cipher_suite()</seetype>
Returns an error if the cipher suite is not supported or the name is not a valid cipher suite name.</p>
</desc>
</func>
@@ -1780,7 +1930,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<fsummary>Converts erlang cipher suite format to an OpenSSL name string.
</fsummary>
<desc>
- <p>Converts <seealso marker="#type-erl_cipher_suite">erl_cipher_suite()</seealso>
+ <p>Converts <seetype marker="#erl_cipher_suite">erl_cipher_suite()</seetype>
to OpenSSL name string. </p>
<p>PRE TLS-1.3 these names differ for RFC names</p>
@@ -1792,7 +1942,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<name since="OTP 21.0" name="suite_to_str" arity="1" clause_i="1" />
<fsummary>Converts an erlang cipher suite to an RFC name string.</fsummary>
<desc>
- <p>Converts <seealso marker="#type-erl_cipher_suite">erl_cipher_suite()</seealso>
+ <p>Converts <seetype marker="#erl_cipher_suite">erl_cipher_suite()</seetype>
to RFC name string.</p>
</desc>
</func>
@@ -1805,11 +1955,11 @@ fun(srp, Username :: string(), UserState :: term()) ->
<desc>
<p>Accepts an incoming connection request on a listen socket.
<c>ListenSocket</c> must be a socket returned from
- <seealso marker="#listen-2"> listen/2</seealso>.
+ <seemfa marker="#listen/2"> listen/2</seemfa>.
The socket returned is to be passed to
- <seealso marker="#handshake-2"> handshake/[2,3]</seealso>
+ <seemfa marker="#handshake/2"> handshake/[2,3]</seemfa>
to complete handshaking, that is,
- establishing the SSL/TLS/DTLS connection.</p>
+ establishing the TLS/DTLS connection.</p>
<warning>
<p>Most API functions require that the TLS/DTLS
connection is established to work as expected.
@@ -1817,7 +1967,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</warning>
<p>The accepted socket inherits the options set for
<c>ListenSocket</c> in
- <seealso marker="#listen-2"> listen/2</seealso>.</p>
+ <seemfa marker="#listen/2"> listen/2</seemfa>.</p>
<p>The default
value for <c>Timeout</c> is <c>infinity</c>. If
<c>Timeout</c> is specified and no connection is accepted
@@ -1828,44 +1978,56 @@ fun(srp, Username :: string(), UserState :: term()) ->
<func>
<name since="OTP R14B" name="versions" arity="0" />
- <fsummary>Returns version information relevant for the
- SSL application.</fsummary>
+ <fsummary>Lists information, mainly concerning TLS/DTLS versions,
+ in runtime for debugging and testing purposes.</fsummary>
<desc>
- <p>Returns version information relevant for the SSL
- application.</p>
+ <p>Lists information, mainly concerning TLS/DTLS versions,
+ in runtime for debugging and testing purposes.
+ </p>
<taglist>
<tag><c>app_vsn</c></tag>
<item>The application version of the SSL application.</item>
<tag><c>supported</c></tag>
- <item>SSL/TLS versions supported by default.
+ <item>TLS versions supported with current application environment
+ and crypto library configuration.
Overridden by a version option on
- <seealso marker="#connect-2"> connect/[2,3,4]</seealso>,
- <seealso marker="#listen-2"> listen/2</seealso>, and <seealso
- marker="#ssl_accept-2">ssl_accept/[1,2,3]</seealso>.
- For the negotiated SSL/TLS version, see <seealso
- marker="#connection_information-1">connection_information/1
- </seealso>.</item>
+ <seemfa marker="#connect/2"> connect/[2,3,4]</seemfa>,
+ <seemfa marker="#listen/2"> listen/2</seemfa>, and <seemfa
+ marker="#ssl_accept/2">ssl_accept/[1,2,3]</seemfa>.
+ For the negotiated TLS version, see <seemfa
+ marker="#connection_information/1">connection_information/1
+ </seemfa>.</item>
<tag><c>supported_dtls</c></tag>
- <item>DTLS versions supported by default.
+ <item>DTLS versions supported with current application environment
+ and crypto library configuration.
Overridden by a version option on
- <seealso marker="#connect-2"> connect/[2,3,4]</seealso>,
- <seealso marker="#listen-2"> listen/2</seealso>, and <seealso
- marker="#ssl_accept-2">ssl_accept/[1,2,3]</seealso>.
- For the negotiated DTLS version, see <seealso
- marker="#connection_information-1">connection_information/1
- </seealso>.</item>
+ <seemfa marker="#connect/2"> connect/[2,3,4]</seemfa>,
+ <seemfa marker="#listen/2"> listen/2</seemfa>, and <seemfa
+ marker="#ssl_accept/2">ssl_accept/[1,2,3]</seemfa>.
+ For the negotiated DTLS version, see <seemfa
+ marker="#connection_information/1">connection_information/1
+ </seemfa>.</item>
<tag><c>available</c></tag>
- <item>All SSL/TLS versions supported by the SSL application.
- TLS 1.2 requires sufficient support from the Crypto
- application.</item>
+ <item>All TLS versions supported with the
+ linked crypto library.
+ </item>
<tag><c>available_dtls</c></tag>
- <item>All DTLS versions supported by the SSL application.
- DTLS 1.2 requires sufficient support from the Crypto
- application.</item>
+ <item>All DTLS versions supported with the
+ linked crypto library.</item>
+
+ <tag><c>implemented</c></tag>
+ <item>All TLS versions supported by the SSL
+ application if linked with a crypto library with the
+ necessary support.</item>
+
+ <tag><c>implemented_dtls</c></tag>
+ <item>All DTLS versions supported by the SSL
+ application if linked with a crypto library with the
+ necessary support.</item>
</taglist>
</desc>
@@ -1875,9 +2037,9 @@ fun(srp, Username :: string(), UserState :: term()) ->
<section>
<title>SEE ALSO</title>
- <p><seealso marker="kernel:inet">inet(3)</seealso> and
- <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>
- <seealso marker="kernel:gen_udp">gen_udp(3)</seealso>
+ <p><seeerl marker="kernel:inet">inet(3)</seeerl> and
+ <seeerl marker="kernel:gen_tcp">gen_tcp(3)</seeerl>
+ <seeerl marker="kernel:gen_udp">gen_udp(3)</seeerl>
</p>
</section>
diff --git a/lib/ssl/doc/src/ssl_app.xml b/lib/ssl/doc/src/ssl_app.xml
index 5c259cda01..0f09d873f9 100644
--- a/lib/ssl/doc/src/ssl_app.xml
+++ b/lib/ssl/doc/src/ssl_app.xml
@@ -37,7 +37,7 @@
<p>
The ssl application is an implementation of the SSL, TLS and DTLS protocols in Erlang.
</p>
- <p>For current statement of standards compliance see the <seealso marker="standards_compliance">User's Guide</seealso>.</p>
+ <p>For current statement of standards compliance see the <seeguide marker="standards_compliance">User's Guide</seeguide>.</p>
</description>
<section>
@@ -56,7 +56,7 @@
The application environment configuration parameters in this section
are defined for the SSL application. For more information
about configuration parameters, see the
- <seealso marker="kernel:application">application(3)</seealso>
+ <seeerl marker="kernel:application">application(3)</seeerl>
manual page in Kernel.</p>
<p>The environment parameters can be set on the command line,
@@ -65,14 +65,16 @@
<p><c>erl -ssl protocol_version "['tlsv1.2', 'tlsv1.1']"</c></p>
<taglist>
- <tag><c>protocol_version = </c><seealso marker="ssl#type-protocol">ssl:ssl_tls_protocol()</seealso><c><![CDATA[<optional>]]></c></tag>
+ <tag><c>protocol_version = </c><seetype marker="ssl#tls_version">ssl:tls_version()</seetype> |
+ [<seetype marker="ssl#tls_version">ssl:tls_version()</seetype>] <c><![CDATA[<optional>]]></c></tag>
<item><p>Protocol supported by started clients and
servers. If this option is not set, it defaults to all
TLS protocols currently supported, more might be configurable, by the SSL application.
This option can be overridden by the version option
to <c>ssl:connect/[2,3]</c> and <c>ssl:listen/2</c>.</p></item>
- <tag><c>dtls_protocol_version = </c><seealso marker="ssl#type-protocol">ssl:dtls_protocol()</seealso><c><![CDATA[<optional>]]></c></tag>
+ <tag><c>dtls_protocol_version = </c><seetype marker="ssl#dtls_version">ssl:dtls_version()</seetype> |
+ [<seetype marker="ssl#dtls_version">ssl:dtls_version()</seetype>] <c><![CDATA[<optional>]]></c></tag>
<item><p>Protocol supported by started clients and
servers. If this option is not set, it defaults to all
DTLS protocols currently supported, more might be configurable, by the SSL application.
@@ -86,15 +88,37 @@
</p></item>
<tag><c><![CDATA[session_cb = atom() <optional>]]></c></tag>
- <item><p>Name of the session cache callback module that implements
+ <item><p> Deprecated Since OTP-23.3 replaced by <c>client_session_cb</c>
+ and <c>server_session_cb</c>
+ </p></item>
+
+ <tag><c><![CDATA[client_session_cb = atom() <optional>]]></c></tag>
+ <item><p> Since OTP-23.3 Name client of the session cache callback module that implements
+ the <c>ssl_session_cache_api</c> behavior. Defaults to
+ <c>ssl_client_session_cache_db</c>.</p></item>
+
+
+ <tag><c><![CDATA[server_session_cb = atom() <optional>]]></c></tag>
+ <item><p>Since OTP-23.3 Name of the server session cache callback module that implements
the <c>ssl_session_cache_api</c> behavior. Defaults to
- <c>ssl_session_cache</c>.</p></item>
+ <c>ssl_server_session_cache_db</c>.</p></item>
<tag><c><![CDATA[session_cb_init_args = proplist:proplist() <optional>]]></c></tag>
+ <item><p>Deprecated Since OTP-23.3 replaced by <c>client_session_cb_init_args</c>
+ and <c>server_session_cb_init_args</c></p></item>
+
+ <tag><c><![CDATA[client_session_cb_init_args = proplist:proplist() <optional>]]></c></tag>
+
<item><p>List of extra user-defined arguments to the <c>init</c> function
in the session cache callback module. Defaults to <c>[]</c>.</p></item>
+ <tag><c><![CDATA[server_session_cb_init_args = proplist:proplist() <optional>]]></c></tag>
+
+ <item><p>List of extra user-defined arguments to the <c>init</c> function
+ in the session cache callback module. Defaults to <c>[]</c>.</p></item>
+
+
<tag><c><![CDATA[session_cache_client_max = integer() <optional>]]></c><br/></tag>
<item><p>Limits the growth of the clients session cache, that is
how many sessions towards servers that are cached to be used by
@@ -115,8 +139,8 @@
<p>
Number of milliseconds between PEM cache validations. Defaults to 2 minutes.
</p>
- <seealso
- marker="ssl#clear_pem_cache-0">ssl:clear_pem_cache/0</seealso>
+ <seemfa
+ marker="ssl#clear_pem_cache/0">ssl:clear_pem_cache/0</seemfa>
</item>
@@ -177,6 +201,16 @@
</p>
</item>
+ <tag><c><![CDATA[server_session_ticket_max_early_data = integer() <optional>]]></c></tag>
+ <item>
+ <p>
+ Sets the maximum size of the early data that the server accepts and also configures
+ its NewSessionTicket messages to include this same size limit in their
+ early_data_indication extension.
+ Defaults to 16384. Size limit is enforced by both client and server.
+ </p>
+ </item>
+
<tag><c><![CDATA[client_session_ticket_lifetime = integer() <optional>]]></c></tag>
<item>
<p>
@@ -198,8 +232,8 @@
<section>
<title>ERROR LOGGER AND EVENT HANDLERS</title>
- <p>The SSL application uses <seealso
- marker="kernel:logger">OTP logger</seealso>.
+ <p>The SSL application uses <seeerl
+ marker="kernel:logger">OTP logger</seeerl>.
TLS/DTLS alerts are logged on notice level. Unexpected
errors are logged on error level. These log entries
will by default end up in the default Erlang log.
@@ -212,7 +246,7 @@
<section>
<title>SEE ALSO</title>
- <p><seealso marker="kernel:application">application(3)</seealso></p>
+ <p><seeerl marker="kernel:application">application(3)</seeerl></p>
</section>
</appref>
diff --git a/lib/ssl/doc/src/ssl_crl_cache.xml b/lib/ssl/doc/src/ssl_crl_cache.xml
index a33aec62a7..347edb1f14 100644
--- a/lib/ssl/doc/src/ssl_crl_cache.xml
+++ b/lib/ssl/doc/src/ssl_crl_cache.xml
@@ -29,8 +29,8 @@
<description>
<p>
Implements an internal CRL (Certificate Revocation List) cache.
- In addition to implementing the <seealso
- marker="ssl_crl_cache_api"> ssl_crl_cache_api</seealso> behaviour
+ In addition to implementing the <seeerl
+ marker="ssl_crl_cache_api"> ssl_crl_cache_api</seeerl> behaviour
the following functions are available.
</p>
</description>
@@ -53,7 +53,7 @@
<name since="OTP 18.0">delete(Entries) -> ok | {error, Reason} </name>
<fsummary> </fsummary>
<type>
- <v> Entries = <seealso marker="#type-crl_src">crl_src()</seealso>]}</v>
+ <v> Entries = <seetype marker="#crl_src">crl_src()</seetype>]}</v>
<v> Reason = crl_reason()</v>
</type>
<desc>
@@ -65,8 +65,8 @@
<name since="OTP 18.0">insert(URI, CRLSrc) -> ok | {error, Reason}</name>
<fsummary> </fsummary>
<type>
- <v> CRLSrc = <seealso marker="#type-crl_src">crl_src()</seealso>]}</v>
- <v> URI = <seealso marker="#type-uri">uri()</seealso> </v>
+ <v> CRLSrc = <seetype marker="#crl_src">crl_src()</seetype>]}</v>
+ <v> URI = <seetype marker="#uri">uri()</seetype> </v>
<v> Reason = term()</v>
</type>
<desc>
diff --git a/lib/ssl/doc/src/ssl_crl_cache_api.xml b/lib/ssl/doc/src/ssl_crl_cache_api.xml
index 2662c25de1..69f1b3f4ae 100644
--- a/lib/ssl/doc/src/ssl_crl_cache_api.xml
+++ b/lib/ssl/doc/src/ssl_crl_cache_api.xml
@@ -25,10 +25,10 @@
</header>
<module since="OTP 18.0">ssl_crl_cache_api</module>
- <modulesummary>API for a SSL/TLS CRL (Certificate Revocation List) cache.</modulesummary>
+ <modulesummary>API for a TLS CRL (Certificate Revocation List) cache.</modulesummary>
<description>
<p>
- When SSL/TLS performs certificate path validation according to
+ When TLS performs certificate path validation according to
<url href="http://www.ietf.org/rfc/rfc5280.txt">RFC 5280 </url>
it should also perform CRL validation checks. To enable the CRL
checks the application needs access to CRLs. A database of CRLs
@@ -59,16 +59,16 @@
<datatype>
<name name="dist_point"/>
<desc>
- <p>For description see <seealso
- marker="public_key:public_key_records"> X509 certificates records</seealso></p>
+ <p>For description see <seeguide
+ marker="public_key:public_key_records"> X509 certificates records</seeguide></p>
</desc>
</datatype>
<datatype>
<name name="logger_info"/>
<desc>
- <p>Information for ssl applications use of <seealso
- marker="kernel:logger"> Logger(3)</seealso></p>
+ <p>Information for ssl applications use of <seeerl
+ marker="kernel:logger"> Logger(3)</seeerl></p>
</desc>
</datatype>
@@ -77,21 +77,21 @@
<funcs>
<func>
- <name since="@maint@">fresh_crl(DistributionPoint, CRL) -> FreshCRL </name>
- <name since="OTP 18.0">fresh_crl(DistributionPoint, CRL) -> FreshCRL | {LoggerInfo, FreshCRL}</name>
+ <name since="OTP 22.2">Module:fresh_crl(DistributionPoint, CRL) -> FreshCRL </name>
+ <name since="OTP 18.0">Module:fresh_crl(DistributionPoint, CRL) -> FreshCRL | {LoggerInfo, FreshCRL}</name>
<fsummary> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to
public_key:pkix_crls_validate/3 </fsummary>
<type>
- <v> DistributionPoint = <seealso marker="#type-dist_point"> dist_point() </seealso> </v>
- <v> CRL = [<seealso
- marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v>
- <v> FreshCRL = [<seealso
- marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v>
- <v> LoggerInfo = {logger, <seealso marker="#type-logger_info"> logger_info() </seealso>}} </v>
+ <v> DistributionPoint = <seetype marker="#dist_point"> dist_point() </seetype> </v>
+ <v> CRL = [<seetype
+ marker="public_key:public_key#der_encoded">public_key:der_encoded()</seetype>] </v>
+ <v> FreshCRL = [<seetype
+ marker="public_key:public_key#der_encoded">public_key:der_encoded()</seetype>] </v>
+ <v> LoggerInfo = {logger, <seetype marker="#logger_info"> logger_info() </seetype>}} </v>
</type>
<desc>
<p> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to
- <seealso marker="public_key:public_key#pkix_crls_validate-3">public_key:pkix_crls_validate/3 </seealso> </p>
+ <seemfa marker="public_key:public_key#pkix_crls_validate/3">public_key:pkix_crls_validate/3 </seemfa> </p>
<p>It is possible to return logger info that will be used by the TLS connection
to produce log events.
@@ -100,19 +100,19 @@
</func>
<func>
- <name since="@maint@">lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs |
+ <name since="OTP 22.2">Module:lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs |
{LoggerInfo, CRLs} </name>
- <name since="OTP 19.0">lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name>
- <name since="OTP 18.0">lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name>
+ <name since="OTP 19.0">Module:lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name>
+ <name since="OTP 18.0">Module:lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name>
<fsummary> </fsummary>
<type>
- <v> DistributionPoint = <seealso marker="#type-dist_point"> dist_point() </seealso> </v>
- <v> Issuer = <seealso
- marker="public_key:public_key#type-issuer_name">public_key:issuer_name()</seealso> </v>
- <v> DbHandle = <seealso marker="#type-crl_cache_ref"> crl_cache_ref() </seealso></v>
- <v> CRLs = [<seealso
- marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>]</v>
- <v> LoggerInfo = {logger, <seealso marker="#type-logger_info"> logger_info() </seealso>}} </v>
+ <v> DistributionPoint = <seetype marker="#dist_point"> dist_point() </seetype> </v>
+ <v> Issuer = <seetype
+ marker="public_key:public_key#issuer_name">public_key:issuer_name()</seetype> </v>
+ <v> DbHandle = <seetype marker="#crl_cache_ref"> crl_cache_ref() </seetype></v>
+ <v> CRLs = [<seetype
+ marker="public_key:public_key#der_encoded">public_key:der_encoded()</seetype>]</v>
+ <v> LoggerInfo = {logger, <seetype marker="#logger_info"> logger_info() </seetype>}} </v>
</type>
<desc> <p>Lookup the CRLs belonging to the distribution point <c> Distributionpoint</c>.
This function may choose to only look in the cache or to follow distribution point
@@ -137,19 +137,19 @@
</func>
<func>
- <name since="@maint@">select(Issuer, DbHandle) -> CRLs | {LoggerInfo, CRLs} </name>
- <name since="OTP 18.0">select(Issuer, DbHandle) -> CRLs </name>
+ <name since="OTP 22.2">Module:select(Issuer, DbHandle) -> CRLs | {LoggerInfo, CRLs} </name>
+ <name since="OTP 18.0">Module:select(Issuer, DbHandle) -> CRLs </name>
<fsummary>Select the CRLs in the cache that are issued by <c>Issuer</c></fsummary>
<type>
- <v> Issuer = <seealso
- marker="public_key:public_key#type-issuer_name">public_key:issuer_name()</seealso> | list() </v>
- <v> DbHandle = <seealso marker="#type-crl_cache_ref"> cache_ref() </seealso></v>
- <v> LoggerInfo = {logger, <seealso marker="#type-logger_info"> logger_info() </seealso>} </v>
+ <v> Issuer = <seetype
+ marker="public_key:public_key#issuer_name">public_key:issuer_name()</seetype> | list() </v>
+ <v> DbHandle = <seetype marker="#crl_cache_ref"> cache_ref() </seetype></v>
+ <v> LoggerInfo = {logger, <seetype marker="#logger_info"> logger_info() </seetype>} </v>
</type>
<desc>
<p>Select the CRLs in the cache that are issued by <c>Issuer</c> unless
- the value is a list of so called general names, see <seealso
- marker="public_key:public_key_records"> X509 certificates records</seealso>,
+ the value is a list of so called general names, see <seeguide
+ marker="public_key:public_key_records"> X509 certificates records</seeguide>,
originating form <c>#'DistributionPoint'.cRLissuer</c> and
representing different mechanism to obtain the CRLs. The cache
callback needs to use the appropriate entry to retrive the CRLs or
diff --git a/lib/ssl/doc/src/ssl_distribution.xml b/lib/ssl/doc/src/ssl_distribution.xml
index 6993e824cc..43839c5dc3 100644
--- a/lib/ssl/doc/src/ssl_distribution.xml
+++ b/lib/ssl/doc/src/ssl_distribution.xml
@@ -154,7 +154,7 @@ Eshell V5.0 (abort with ^G)
<section>
<title>Specifying Distribution Module for net_kernel</title>
- <p>The distribution module for SSL/TLS is named <c>inet_tls_dist</c>
+ <p>The distribution module for TLS is named <c>inet_tls_dist</c>
and is specified on the command line with option <c>-proto_dist</c>.
The argument to <c>-proto_dist</c> is to be the module
name without suffix <c>_dist</c>. So, this distribution
@@ -179,16 +179,16 @@ Eshell V5.0 (abort with ^G)
</section>
<section>
- <title>Specifying SSL/TLS Options</title>
+ <title>Specifying TLS Options</title>
<p>
- The SSL/TLS distribution options can be written into a file
+ The TLS distribution options can be written into a file
that is consulted when the node is started. This file name
is then specified with the command line argument
<c>-ssl_dist_optfile</c>.
</p>
<p>
- Any available SSL/TLS option can be specified in an options file,
+ Any available TLS option can be specified in an options file,
but note that options that take a <c>fun()</c> has to use
the syntax <c>fun Mod:Func/Arity</c> since a function
body cannot be compiled when consulting a file.
@@ -202,7 +202,7 @@ Eshell V5.0 (abort with ^G)
interfere severely, so beware!
</p>
<p>
- For SSL/TLS to work, at least a public key and a certificate
+ For TLS to work, at least a public key and a certificate
must be specified for the server side.
In the following example, the PEM file
<c>"/home/me/ssl/erlserver.pem"</c> contains both
@@ -263,7 +263,7 @@ $ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls
</section>
<section>
- <title>Specifying SSL/TLS Options (Legacy)</title>
+ <title>Specifying TLS Options (Legacy)</title>
<p>
As in the previous section the PEM file
@@ -272,9 +272,9 @@ $ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls
</p>
<p>On the <c>erl</c> command line you can specify options that the
- SSL/TLS distribution adds when creating a socket.</p>
+ TLS distribution adds when creating a socket.</p>
- <p>The simplest SSL/TLS options in the following list can be specified
+ <p>The simplest TLS options in the following list can be specified
by adding the
prefix <c>server_</c> or <c>client_</c> to the option name:</p>
<list type="bulleted">
@@ -294,7 +294,7 @@ $ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls
</list>
<p>Note that <c>verify_fun</c> needs to be written in a different
- form than the corresponding SSL/TLS option, since funs are not
+ form than the corresponding TLS option, since funs are not
accepted on the command line.</p>
<p>The server can also take the options <c>dhfile</c> and
@@ -307,7 +307,7 @@ $ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls
<p>Raw socket options, such as <c>packet</c> and <c>size</c> must not
be specified on the command line.</p>
- <p>The command-line argument for specifying the SSL/TLS options is named
+ <p>The command-line argument for specifying the TLS options is named
<c>-ssl_dist_opt</c> and is to be followed by pairs of
SSL options and their values. Argument <c>-ssl_dist_opt</c> can
be repeated any number of times.</p>
@@ -331,10 +331,10 @@ Eshell V5.0 (abort with ^G)
</section>
<section>
- <title>Setting up Environment to Always Use SSL/TLS (Legacy)</title>
+ <title>Setting up Environment to Always Use TLS (Legacy)</title>
<p>A convenient way to specify arguments to Erlang is to use environment
variable <c>ERL_FLAGS</c>. All the flags needed to
- use the SSL/TLS distribution can be specified in that variable and are
+ use the TLS distribution can be specified in that variable and are
then interpreted as command-line arguments for all
subsequent invocations of Erlang.</p>
@@ -365,8 +365,8 @@ Eshell V5.0 (abort with ^G)
</section>
<section>
- <title>Using SSL/TLS distribution over IPv6</title>
- <p>It is possible to use SSL/TLS distribution over IPv6 instead of
+ <title>Using TLS distribution over IPv6</title>
+ <p>It is possible to use TLS distribution over IPv6 instead of
IPv4. To do this, pass the option <c>-proto_dist inet6_tls</c>
instead of <c>-proto_dist inet_tls</c> when starting Erlang,
either on the command line or in the <c>ERL_FLAGS</c> environment
@@ -380,6 +380,6 @@ $ erl -boot /home/me/ssl/start_ssl -proto_dist inet6_tls
</code>
<p>A node started in this way will only be able to communicate with
- other nodes using SSL/TLS distribution over IPv6.</p>
+ other nodes using TLS distribution over IPv6.</p>
</section>
</chapter>
diff --git a/lib/ssl/doc/src/ssl_introduction.xml b/lib/ssl/doc/src/ssl_introduction.xml
index adcfb091b7..d37cd9bbf9 100644
--- a/lib/ssl/doc/src/ssl_introduction.xml
+++ b/lib/ssl/doc/src/ssl_introduction.xml
@@ -49,7 +49,7 @@
<title>Prerequisites</title>
<p>It is assumed that the reader is familiar with the Erlang
programming language, the concepts of OTP, and has a basic
- understanding of SSL/TLS/DTLS.</p>
+ understanding of TLS/DTLS.</p>
</section>
</chapter>
diff --git a/lib/ssl/doc/src/ssl_protocol.xml b/lib/ssl/doc/src/ssl_protocol.xml
index 3ab836443f..aae08a2c9e 100644
--- a/lib/ssl/doc/src/ssl_protocol.xml
+++ b/lib/ssl/doc/src/ssl_protocol.xml
@@ -33,12 +33,12 @@
<file>ssl_protocol.xml</file>
</header>
- <p>The Erlang SSL application implements the SSL/TLS/DTLS protocol
+ <p>The Erlang SSL application implements the TLS/DTLS protocol
for the currently supported versions, see the
- <seealso marker="ssl">ssl(3)</seealso> manual page.
+ <seeerl marker="ssl">ssl(3)</seeerl> manual page.
</p>
- <p>By default SSL/TLS is run over the TCP/IP protocol even
+ <p>By default TLS is run over the TCP/IP protocol even
though you can plug in any other reliable transport protocol
with the same Application Programming Interface (API) as the
<c>gen_tcp</c> module in Kernel. DTLS is by default run over UDP/IP,
@@ -46,7 +46,7 @@
transports, such as SCTP, may be supported in future releases.</p>
<p>If a client and a server wants to use an upgrade mechanism, such as
- defined by RFC 2817, to upgrade a regular TCP/IP connection to an TLS
+ defined by RFC 2817, to upgrade a regular TCP/IP connection to a TLS
connection, this is supported by the Erlang SSL application API. This can be
useful for, for example, supporting HTTP and HTTPS on the same port and
implementing virtual hosting. Note this is a TLS feature only.
@@ -137,28 +137,42 @@
</section>
<section>
- <title>TLS Sessions</title>
+ <title>TLS Sessions - PRE TLS-1.3</title>
- <p>From the TLS RFC: "A TLS session is an association between a
- client and a server. Sessions are created by the handshake
- protocol. Sessions define a set of cryptographic security
- parameters, which can be shared among multiple
- connections. Sessions are used to avoid the expensive negotiation
- of new security parameters for each connection."</p>
+ <p>From the TLS RFC: "A TLS session is an association between a
+ client and a server. Sessions are created by the handshake
+ protocol. Sessions define a set of cryptographic security
+ parameters, which can be shared among multiple
+ connections. Sessions are used to avoid the expensive negotiation
+ of new security parameters for each connection."</p>
- <p>Session data is by default kept by the SSL application in a
- memory storage, hence session data is lost at application
- restart or takeover. Users can define their own callback module
- to handle session data storage if persistent data storage is
- required. Session data is also invalidated after 24 hours
- from it was saved, for security reasons. The amount of time the
- session data is to be saved can be configured.</p>
+ <p>Session data is by default kept by the SSL application in a
+ memory storage, hence session data is lost at application
+ restart or takeover. Users can define their own callback module
+ to handle session data storage if persistent data storage is
+ required. Session data is also invalidated when session
+ database exceeds its limit or 24 hours after being saved
+ (RFC max lifetime recommendation). The amount of time the
+ session data is to be saved can be configured.</p>
- <p>By default the TLS/DTLS clients try to reuse an available session and
- by default the TLS/DTLS servers agree to reuse sessions when clients
- ask for it.</p>
+ <p>By default the TLS/DTLS clients try to reuse an available
+ session and by default the TLS/DTLS servers agree to reuse
+ sessions when clients ask for it. See also
+ <seeguide marker="ssl:using_ssl#session-reuse-pre-tls-1.3"> Session Reuse Pre TLS-1.3 </seeguide>
+ </p>
+ </section>
+ <section>
+ <title>TLS-1.3 session tickets</title>
+
+ <p>In TLS 1.3 the session reuse is replaced by a new session
+ tickets mechanism based on the pre shared key concept. This
+ mechanism also obsoletes the session tickets from RFC5077, not
+ implemented by this application. See also
+ <seeguide marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">Session Tickets and Session Resumption in TLS-1.3 </seeguide>
+ </p>
</section>
+
</chapter>
diff --git a/lib/ssl/doc/src/ssl_session_cache_api.xml b/lib/ssl/doc/src/ssl_session_cache_api.xml
index 8e90ddc6bb..3067d89b13 100644
--- a/lib/ssl/doc/src/ssl_session_cache_api.xml
+++ b/lib/ssl/doc/src/ssl_session_cache_api.xml
@@ -33,7 +33,7 @@
<description>
<p>
- Defines the API for the TLS session cache so
+ Defines the API for the TLS session cache (pre TLS-1.3) so
that the data storage scheme can be replaced by
defining a new callback module implementing this API.
</p>
@@ -77,11 +77,11 @@
<funcs>
<func>
- <name since="OTP R14B">delete(Cache, Key) -> _</name>
+ <name since="OTP R14B">Module:delete(Cache, Key) -> _</name>
<fsummary>Deletes a cache entry.</fsummary>
<type>
- <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
- <v>Key = <seealso marker="#type-session_cache_key">session_cache_key() </seealso> </v>
+ <v>Cache = <seetype marker="#session_cache_ref"> session_cache_ref() </seetype></v>
+ <v>Key = <seetype marker="#session_cache_key">session_cache_key() </seetype> </v>
</type>
<desc>
<p>Deletes a cache entry. Is only called from the cache
@@ -91,12 +91,12 @@
</func>
<func>
- <name since="OTP R14B">foldl(Fun, Acc0, Cache) -> Acc</name>
+ <name since="OTP R14B">Module:foldl(Fun, Acc0, Cache) -> Acc</name>
<fsummary></fsummary>
<type>
<v>Fun = fun()</v>
<v>Acc0 = Acc = term()</v>
- <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
+ <v>Cache = <seetype marker="#session_cache_ref"> session_cache_ref() </seetype></v>
</type>
<desc>
<p>Calls <c>Fun(Elem, AccIn)</c> on successive elements of the
@@ -105,22 +105,27 @@
The function returns the final value of the accumulator.
<c>Acc0</c> is returned if the cache is empty.
</p>
+
+ <note><p>Since OTP-23.3 this functions is only used on the client side
+ and does not need to implemented for a server cache.
+ </p></note>
+
</desc>
</func>
<func>
- <name since="OTP 18.0">init(Args) -> Cache </name>
+ <name since="OTP 18.0">Module:init(Args) -> Cache </name>
<fsummary>Returns cache reference.</fsummary>
<type>
- <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
- <v>Args = <seealso marker="stdlib:proplists#type-proplist">proplists:proplist()</seealso></v>
+ <v>Cache = <seetype marker="#session_cache_ref"> session_cache_ref() </seetype></v>
+ <v>Args = <seetype marker="stdlib:proplists#proplist">proplists:proplist()</seetype></v>
</type>
<desc>
<p>Includes property <c>{role, client | server}</c>.
Currently this is the only predefined property,
there can also be user-defined properties. See also
application environment variable
- <seealso marker="ssl_app">session_cb_init_args</seealso>.
+ <seeapp marker="ssl_app">session_cb_init_args</seeapp>.
</p>
<p>Performs possible initializations of the cache and returns
a reference to it that is used as parameter to the other
@@ -135,12 +140,12 @@
</func>
<func>
- <name since="OTP R14B">lookup(Cache, Key) -> Entry</name>
+ <name since="OTP R14B">Module:lookup(Cache, Key) -> Entry</name>
<fsummary>Looks up a cache entry.</fsummary>
<type>
- <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
- <v>Key = <seealso marker="#type-session_cache_key">session_cache_key()</seealso> </v>
- <v>Session = <seealso marker="#type-session">session()</seealso> | undefined</v>
+ <v>Cache = <seetype marker="#session_cache_ref"> session_cache_ref() </seetype></v>
+ <v>Key = <seetype marker="#session_cache_key">session_cache_key()</seetype> </v>
+ <v>Session = <seetype marker="#session">session()</seetype> | undefined</v>
</type>
<desc>
<p>Looks up a cache entry. Is to be callable from any
@@ -150,25 +155,28 @@
</func>
<func>
- <name since="OTP R14B">select_session(Cache, PartialKey) -> [Session]</name>
+ <name since="OTP R14B">Module:select_session(Cache, PartialKey) -> [Session]</name>
<fsummary>Selects sessions that can be reused.</fsummary>
<type>
- <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
- <v>PartialKey = <seealso marker="#type-partial_key"> partial_key() </seealso></v>
- <v>Session = <seealso marker="#type-session">session()</seealso></v>
+ <v>Cache = <seetype marker="#session_cache_ref"> session_cache_ref() </seetype></v>
+ <v>PartialKey = <seetype marker="#partial_key"> partial_key() </seetype></v>
+ <v>Session = <seetype marker="#session">session()</seetype></v>
</type>
<desc>
- <p>Selects sessions that can be reused. Is to be callable
- from any process.
- </p>
+ <p>Selects sessions that can be reused, that is sessions that
+ include <c>PartialKey</c> in its key. Is to be callable from
+ any process.</p>
+ <note><p>Since OTP-23.3 This functions is only used on the client side
+ and does not need to implemented for a server cache.
+ </p></note>
</desc>
</func>
<func>
- <name since="OTP 19.3">size(Cache) -> integer()</name>
+ <name since="OTP 19.3">Module:size(Cache) -> integer()</name>
<fsummary>Returns the number of sessions in the cache.</fsummary>
<type>
- <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
+ <v>Cache = <seetype marker="#session_cache_ref"> session_cache_ref() </seetype></v>
</type>
<desc>
<p>Returns the number of sessions in the cache. If size
@@ -180,11 +188,11 @@
</func>
<func>
- <name since="OTP R14B">terminate(Cache) -> _</name>
+ <name since="OTP R14B">Module:terminate(Cache) -> _</name>
<fsummary>Called by the process that handles the cache when it
is about to terminate.</fsummary>
<type>
- <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
+ <v>Cache = <seetype marker="#session_cache_ref"> session_cache_ref() </seetype></v>
<d>As returned by init/0</d>
</type>
<desc>
@@ -195,12 +203,12 @@
</func>
<func>
- <name since="OTP R14B">update(Cache, Key, Session) -> _</name>
+ <name since="OTP R14B">Module:update(Cache, Key, Session) -> _</name>
<fsummary>Caches a new session or updates an already cached one.</fsummary>
<type>
- <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
- <v>Key = <seealso marker="#type-session_cache_key">session_cache_key()</seealso> </v>
- <v>Session = <seealso marker="#type-session">session()</seealso></v>
+ <v>Cache = <seetype marker="#session_cache_ref"> session_cache_ref() </seetype></v>
+ <v>Key = <seetype marker="#session_cache_key">session_cache_key()</seetype> </v>
+ <v>Session = <seetype marker="#session">session()</seetype></v>
</type>
<desc>
<p>Caches a new session or updates an already cached one. Is
diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml
index a54b9884c5..b2e7d17c45 100644
--- a/lib/ssl/doc/src/standards_compliance.xml
+++ b/lib/ssl/doc/src/standards_compliance.xml
@@ -91,6 +91,7 @@
<section>
<title>SSL 3.0</title>
+ <p>For security reasons SSL-3.0 is no longer supported at all. (OTP 23) </p>
<p>For security reasons SSL-3.0 is no longer supported by default, but can be configured. (OTP 19)</p>
</section>
@@ -131,22 +132,19 @@
<list type="bulleted">
<item>Key Exchange: ECDHE</item>
<item>Groups: all standard groups supported for the Diffie-Hellman key exchange</item>
- <item>Ciphers: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384,
- TLS_CHACHA20_POLY1305_SHA256 and TLS_AES_128_CCM_SHA256</item>
- <item>Signature Algorithms: rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512,
- ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256,
- rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pkcs1_sha1 and ecdsa_sha1</item>
- <item>Certificates: RSA (it MUST use the rsaEncryption OID) and ECDSA keys</item>
+ <item>Ciphers: all cipher suites are supported</item>
+ <item>Signature Algorithms: All algorithms form RFC 8446</item>
+ <item>Certificates: RSA and ECDSA keys</item>
</list>
<p>Other notable features:</p>
<list type="bulleted">
<item>PSK and session resumption is supported (stateful and stateless tickets)</item>
<item>Anti-replay protection using Bloom-filters with stateless tickets</item>
- <item>Early data and 0-RTT not supported</item>
- <item>Key and Initialization Vector Update not supported</item>
+ <item>Early data and 0-RTT is supported</item>
+ <item>Key and Initialization Vector Update is supported</item>
</list>
<p>For more detailed information see the
- <seealso marker="#soc_table">Standards Compliance</seealso> below.</p>
+ <seeguide marker="#soc_table">Standards Compliance</seeguide> below.</p>
<p> The following table describes the current state of standards compliance for TLS 1.3.</p>
<p>(<em>C</em> = Compliant, <em>NC</em> = Non-Compliant, <em>PC</em> = Partially-Compliant,
@@ -180,7 +178,7 @@
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">RSASSA-PSS signature schemes</cell>
<cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -197,7 +195,7 @@
<row>
<cell align="left" valign="middle">
- <url href="https://tools.ietf.org/html/rfc8446#section-2">
+ <url href="https://tools.ietf.org/html/rfc8446#section/2">
2. Protocol Overview
</url>
</cell>
@@ -253,8 +251,8 @@
</url>
</cell>
<cell align="left" valign="middle"></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>PC</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
@@ -299,14 +297,14 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">server_name (RFC6066)</cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22.2</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">max_fragment_length (RFC6066)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.0</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -389,14 +387,14 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">early_data (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">cookie (RFC8446) </cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.1</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -438,14 +436,14 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">server_name (RFC6066)</cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22.2</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">max_fragment_length (RFC6066)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.0</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -528,14 +526,14 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">early_data (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">cookie (RFC8446) </cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.1</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -641,7 +639,7 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Server</em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>22</em></cell>
</row>
<row>
@@ -653,8 +651,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">cookie (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.1</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -687,14 +685,14 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Client</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.1</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"><em></em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.1</em></cell>
</row>
<row>
@@ -704,8 +702,8 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Client</em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22.1</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -776,20 +774,20 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha256</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha384</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha512</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -879,20 +877,20 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha256</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha384</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">rsa_pss_pss_sha512</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1137,14 +1135,14 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Client</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"><em></em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
@@ -1228,14 +1226,14 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">server_name (RFC6066)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">max_fragment_length (RFC6066)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.0</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1276,8 +1274,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">early_data (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1295,14 +1293,14 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">server_name (RFC6066)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">max_fragment_length (RFC6066)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.0</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1343,8 +1341,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">early_data (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1466,6 +1464,18 @@
</row>
<row>
<cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle">Arbitrary certificate chain orderings</cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>22.2</em></cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle">Extraneous certificates in chain</cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">status_request (RFC6066)</cell>
<cell align="left" valign="middle"><em>NC</em></cell>
<cell align="left" valign="middle"></cell>
@@ -1553,8 +1563,8 @@
extensions are used to guide certificate selection. As servers
MAY require the presence of the "server_name" extension, clients
SHOULD send this extension, when applicable.</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>PC</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
@@ -1657,14 +1667,14 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Client</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"><em></em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
@@ -1674,27 +1684,27 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Client</em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22.2</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">early_data (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22.2</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">early_data (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
@@ -1908,7 +1918,7 @@
<row>
<cell align="left" valign="middle">
- <url href="https://tools.ietf.org/html/rfc8446#section-8">
+ <url href="https://tools.ietf.org/html/rfc8446#section/8">
8. 0-RTT and Anti-Replay
</url>
</cell>
@@ -2030,8 +2040,8 @@
</url>
</cell>
<cell align="left" valign="middle"><em></em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -2042,8 +2052,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">Cookie</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.1</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -2072,8 +2082,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">Server Name Indication</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
@@ -2141,14 +2151,14 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>TLS 1.3 ServerHello</em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">MUST support the use of the "server_name" extension</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23.2</em></cell>
</row>
<row>
@@ -2207,8 +2217,8 @@
</url>
</cell>
<cell align="left" valign="middle"><em></em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -2237,8 +2247,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">TLS_AES_128_CCM_8_SHA256</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
@@ -2336,8 +2346,8 @@
</url>
</cell>
<cell align="left" valign="middle"><em></em></cell>
- <cell align="left" valign="middle"><em>PC</em></cell>
- <cell align="left" valign="middle"><em>22</em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>23</em></cell>
</row>
<row>
diff --git a/lib/ssl/doc/src/usersguide.xml b/lib/ssl/doc/src/usersguide.xml
index 2941d3afa2..14ed266269 100644
--- a/lib/ssl/doc/src/usersguide.xml
+++ b/lib/ssl/doc/src/usersguide.xml
@@ -30,8 +30,8 @@
<file>usersguide.sgml</file>
</header>
<description>
- <p>The Secure Socket Layer (SSL) application provides secure communication over
- sockets.
+ <p>The SSL application implements Transport Layer Security (TLS), formerly known as the Secure Socket Layer (SSL), that is
+ it provides secure communication over sockets.
</p>
</description>
<xi:include href="ssl_introduction.xml"/>
diff --git a/lib/ssl/doc/src/using_ssl.xml b/lib/ssl/doc/src/using_ssl.xml
index 8c1e15a616..7f45b72db9 100644
--- a/lib/ssl/doc/src/using_ssl.xml
+++ b/lib/ssl/doc/src/using_ssl.xml
@@ -33,10 +33,10 @@
<file>using_ssl.xml</file>
</header>
<p>To see relevant version information for ssl, call
- <seealso marker="ssl:ssl#versions-0"><c>ssl:versions/0</c></seealso>
+ <seemfa marker="ssl:ssl#versions/0"><c>ssl:versions/0</c></seemfa>
.</p>
- <p>To see all supported cipher suites, call <seealso marker="ssl:ssl#cipher_suites-1"><c>ssl:cipher_suites(all)</c> </seealso>.
+ <p>To see all supported cipher suites, call <seemfa marker="ssl:ssl#cipher_suites/1"><c>ssl:cipher_suites(all)</c> </seemfa>.
The available cipher suites for a connection depend on your certificate.
Specific cipher suites that you want your connection to use can also be
specified. Default is to use the strongest available.</p>
@@ -51,7 +51,7 @@
<section>
<title>Minimal Example</title>
- <note><p> The minimal setup is not the most secure setup of SSL/TLS/DTLS.</p>
+ <note><p> The minimal setup is not the most secure setup of TLS/DTLS.</p>
</note>
<p>To set up client/server connections:</p>
@@ -60,7 +60,7 @@
<code type="erl">1 server> ssl:start().
ok</code>
- <p><em>Step 2:</em> Create an TLS listen socket: (To run DTLS add the option {protocol, dtls})</p>
+ <p><em>Step 2:</em> Create a TLS listen socket: (To run DTLS add the option {protocol, dtls})</p>
<code type="erl">2 server> {ok, ListenSocket} =
ssl:listen(9999, [{certfile, "cert.pem"}, {keyfile, "key.pem"},{reuseaddr, true}]).
{ok,{sslsocket, [...]}}</code>
@@ -78,7 +78,7 @@ ok</code>
<p><em>Step 5:</em> Do the TLS handshake:</p>
<code type="erl">4 server> {ok, Socket} = ssl:handshake(TLSTransportSocket).
-ok</code>
+{ok,{sslsocket, [...]}}</code>
<p><em>Step 6:</em> Send a message over TLS:</p>
<code type="erl">5 server> ssl:send(Socket, "foo").
@@ -94,12 +94,12 @@ ok</code>
<section>
<title>Upgrade Example - TLS only </title>
- <note><p>To upgrade a TCP/IP connection to an SSL connection, the
+ <note><p>To upgrade a TCP/IP connection to a TLS connection, the
client and server must agree to do so. The agreement
can be accomplished by using a protocol, for example, the one used by HTTP
specified in RFC 2817.</p></note>
- <p>To upgrade to an SSL connection:</p>
+ <p>To upgrade to a TLS connection:</p>
<p><em>Step 1:</em> Start the server side:</p>
<code type="erl">1 server> ssl:start().
@@ -120,8 +120,8 @@ ok</code>
<code type="erl">2 client> {ok, Socket} = gen_tcp:connect("localhost", 9999, [], infinity).</code>
<p><em>Step 5:</em> Ensure <c>active</c> is set to <c>false</c> before trying
- to upgrade a connection to an SSL connection, otherwise
- SSL handshake messages can be delivered to the wrong process:</p>
+ to upgrade a connection to a TLS connection, otherwise
+ TLS handshake messages can be delivered to the wrong process:</p>
<code type="erl">4 server> inet:setopts(Socket, [{active, false}]).
ok</code>
@@ -130,7 +130,7 @@ ok</code>
{certfile, "cert.pem"}, {keyfile, "key.pem"}]).
{ok,{sslsocket,[...]}}</code>
- <p><em>Step 7:</em> Upgrade to an TLS connection. The client and server
+ <p><em>Step 7:</em> Upgrade to a TLS connection. The client and server
must agree upon the upgrade. The server must call
<c>ssl:handshake/2</c> before the client calls <c>ssl:connect/3.</c></p>
<code type="erl">3 client>{ok, TLSSocket} = ssl:connect(Socket, [{cacertfile, "cacerts.pem"},
@@ -154,9 +154,9 @@ ok</code>
</section>
<section>
- <title>Customizing cipher suits</title>
+ <title>Customizing cipher suites</title>
- <p>Fetch default cipher suite list for an TLS/DTLS version. Change default
+ <p>Fetch default cipher suite list for a TLS/DTLS version. Change default
to all to get all possible cipher suites.</p>
<code type="erl">1> Default = ssl:cipher_suites(default, 'tlsv1.2').
[#{cipher => aes_256_gcm,key_exchange => ecdhe_ecdsa,
@@ -229,10 +229,186 @@ ssl:connect("localhost", 9999,
{key, PrivKey}], infinity).
</code>
- <p>See also <seealso marker="crypto:engine_load#engine_load"> crypto documentation</seealso> </p>
+ <p>See also <seeguide marker="crypto:engine_load#engine_load"> crypto documentation</seeguide> </p>
</section>
+
+ <section>
+ <title>Session Reuse pre TLS 1.3</title>
+ <p>Clients can request to reuse a session established
+ by a previous full handshake between that client and server by
+ sending the id of the session in the initial handshake
+ message. The server may or may not agree to reuse it. If agreed
+ the server will send back the id and if not it will send a new
+ id. The ssl application has several options for handling session
+ reuse.</p>
+
+ <p>On the client side the ssl application will save session data
+ to try to automate session reuse on behalf of the client processes
+ on the Erlang node. Note that only verified sessions will be
+ saved for security reasons, that is session resumption relies on
+ the certificate validation to have been run in the original
+ handshake. To minimize memory consumption only unique sessions
+ will be saved unless the special <c>save</c> value is specified
+ for the following option <c> {reuse_sessions, boolean() |
+ save}</c> in which case a full handhake will be performed and that
+ specific session will have been saved before the handshake
+ returns. The session id and even an opaque binary containing the
+ session data can be retrieved using
+ <c>ssl:connection_information/1</c> function. A saved session
+ (guaranteed by the save option) can be explicitly reused using
+ <c>{reuse_session, SessionId}</c>. Also it is possible for the
+ client to reuse a session that is not saved by the ssl application
+ using <c>{reuse_session, {SessionId, SessionData}}</c>.</p>
+
+ <note><p>When using explicit session reuse, it is up to the client
+ to make sure that the session being reused is for the correct
+ server and has been verified.</p></note>
+
+ <p>Here follows a client side example,
+ divide into several steps for readability.
+ </p>
+
+ <p>Step 1 - Automated Session Reuse</p>
+
+ <code type="erl">
+1> ssl:start().
+ok
+
+2&gt; {ok, C1} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.7&gt;,tls_connection,undefined}, ...}}
+
+3&gt; ssl:connection_information(C1, [session_id]).
+{ok,[{session_id,&lt;&lt;95,32,43,22,35,63,249,22,26,36,106,
+ 152,49,52,124,56,130,192,137,161,
+ 146,145,164,232,...&gt;&gt;}]}
+
+%% Reuse session if possible, note that if C2 is really fast the session
+%% data might not be available for reuse.
+4&gt; {ok, C2} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_sessions, true}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.8&gt;,tls_connection,undefined}, ...]}}
+
+%% C2 got same session ID as client one, session was automatically reused.
+5&gt; ssl:connection_information(C2, [session_id]).
+{ok,[{session_id,&lt;&lt;95,32,43,22,35,63,249,22,26,36,106,
+ 152,49,52,124,56,130,192,137,161,
+ 146,145,164,232,...&gt;&gt;}]}
+
+</code>
+
+<p>Step 2- Using <c>save</c> Option </p>
+
+<code type="erl">
+%% We want save this particular session for reuse although it has the same basis as C1
+6&gt; {ok, C3} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_sessions, save}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.9&gt;,tls_connection,undefined}, ...]}}
+
+%% A full handshake is performed and we get a new session ID
+7&gt; {ok, [{session_id, ID}]} = ssl:connection_information(C3, [session_id]).
+{ok,[{session_id,&lt;&lt;91,84,27,151,183,39,84,90,143,141,
+ 121,190,66,192,10,1,27,192,33,95,78,
+ 8,34,180,...&gt;&gt;}]}
+
+%% Use automatic session reuse
+8&gt; {ok, C4} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_sessions, true}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.10&gt;,tls_connection,
+ undefined}, ...]}}
+
+%% The "saved" one happened to be selected, but this is not a guarantee
+9&gt; ssl:connection_information(C4, [session_id]).
+{ok,[{session_id,&lt;&lt;91,84,27,151,183,39,84,90,143,141,
+ 121,190,66,192,10,1,27,192,33,95,78,
+ 8,34,180,...&gt;&gt;}]}
+
+%% Make sure to reuse the "saved" session
+10&gt; {ok, C5} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_session, ID}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.11&gt;,tls_connection,
+ undefined}, ...]}}
+
+11&gt; ssl:connection_information(C5, [session_id]).
+{ok,[{session_id,&lt;&lt;91,84,27,151,183,39,84,90,143,141,
+ 121,190,66,192,10,1,27,192,33,95,78,
+ 8,34,180,...&gt;&gt;}]}
+</code>
+
+<p>Step 3 - Explicit Session Reuse </p>
+
+<code type="erl">
+%% Preform a full handshake and the session will not be saved for reuse
+12&gt; {ok, C9} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_sessions, false},
+ {server_name_indication, disable}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.14&gt;,tls_connection, ...}}
+
+%% Fetch session ID and data for C9 connection
+12&gt; {ok, [{session_id, ID1}, {session_data, SessData}]} =
+ ssl:connection_information(C9, [session_id, session_data]).
+{ok,[{session_id,&lt;&lt;9,233,4,54,170,88,170,180,17,96,202,
+ 85,85,99,119,47,9,68,195,50,120,52,
+ 130,239,...&gt;&gt;},
+ {session_data,&lt;&lt;131,104,13,100,0,7,115,101,115,115,105,
+ 111,110,109,0,0,0,32,9,233,4,54,170,...&gt;&gt;}]}
+
+%% Explicitly reuse the session from C9
+13&gt; {ok, C10} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_session, {ID1, SessData}}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.15&gt;,tls_connection,
+ undefined}, ...}}
+
+14&gt; ssl:connection_information(C10, [session_id]).
+{ok,[{session_id,&lt;&lt;9,233,4,54,170,88,170,180,17,96,202,
+ 85,85,99,119,47,9,68,195,50,120,52,
+ 130,239,...&gt;&gt;}]}
+
+</code>
+
+<p>Step 4 - Not Possible to Reuse Explicit Session by ID Only</p>
+
+<code type="erl">
+%% Try to reuse the session from C9 using only the id
+15&gt; {ok, E} = ssl:connect("localhost", 9999, [{verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ {cacertfile, "cacerts.pem"},
+ {reuse_session, ID1}]).
+{ok,{sslsocket,{gen_tcp,#Port&lt;0.18&gt;,tls_connection,
+ undefined}, ...}}
+
+%% This will fail (as it is not saved for reuse)
+%% and a full handshake will be performed, we get a new id.
+16&gt; ssl:connection_information(E, [session_id]).
+{ok,[{session_id,&lt;&lt;87,46,43,126,175,68,160,153,37,29,
+ 196,240,65,160,254,88,65,224,18,63,
+ 18,17,174,39,...&gt;&gt;}]}
+</code>
+
+ <p>On the server side the the <c>{reuse_sessions, boolean()}</c> option
+ determines if the server will save session data and allow session
+ reuse or not. This can be further customized by the option
+ <c>{reuse_session, fun()}</c> that may introduce a local policy for
+ session reuse.
+ </p>
+
+ </section>
+
<section>
<title>Session Tickets and Session Resumption in TLS 1.3</title>
@@ -249,12 +425,12 @@ ssl:connect("localhost", 9999,
are opaque for the clients. Generally, stateful tickets are smaller and the server can guarantee
that tickets are only used once. Stateless tickets contain additional data, require less storage
on the server side, but they offer different guarantees against anti-replay. See also
- <seealso marker="ssl:using_ssl#anti-replay-protection-in-tls-1.3">
- Anti-Replay Protection in TLS 1.3</seealso>
+ <seeguide marker="ssl:using_ssl#anti-replay-protection-in-tls-1.3">
+ Anti-Replay Protection in TLS 1.3</seeguide>
</p>
<p>Session tickets are sent by servers on newly estalished TLS connections.
The number of tickets sent and their lifetime are configurable by application variables. See also
- <seealso marker="ssl:ssl_app#configuration"> SSL's configuration</seealso>.</p>
+ <seeapp marker="ssl:ssl_app#configuration"> SSL's configuration</seeapp>.</p>
<p>Session tickets are protected by application traffic keys, and in stateless
tickets, the opaque data structure itself is self-encrypted.</p>
@@ -383,6 +559,120 @@ ssl:connect("localhost", 9999,
</section>
<section>
+ <title>Early Data in TLS 1.3</title>
+ <p>TLS 1.3 allows clients to send data on the first flight if the endpoints have
+ a shared crypographic secret (pre-shared key). This means that clients can send
+ early data if they have a valid session ticket received in a previous
+ successful handshake. For more information about session resumption see
+ <seeguide marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">
+ Session Tickets and Session Resumption in TLS 1.3</seeguide>.
+ </p>
+ <p>The security properties of Early Data are weaker than other kinds of TLS data.
+ This data is not forward secret, and it is vulnerable to replay attacks. For available
+ mitigation strategies see
+ <seeguide marker="ssl:using_ssl#anti-replay-protection-in-tls-1.3">
+ Anti-Replay Protection in TLS 1.3</seeguide>.</p>
+ <p>In normal operation, clients will not know which, if any, of the available mitigation
+ strategies servers actually implement, and hence must only send early data which
+ they deem safe to be replayed. For example, idempotent HTTP operations, such as HEAD and
+ GET, can usually be regarded as safe but even they can be exploited by a large number of
+ replays causing resource limit exhaustion and other similar problems.</p>
+ <p>An example of sending early data with automatic and manual session ticket handling:</p>
+ <warning>
+ <p>The Early Data feature is experimental in this version of OTP.
+ </p>
+ </warning>
+
+ <p><em>Server (with NSS key logging)</em></p>
+ <code type="none">
+ early_data_server() ->
+ application:load(ssl),
+ {ok, _} = application:ensure_all_started(ssl),
+ Port = 11029,
+ LOpts = [{certfile, ?SERVER_CERT},
+ {keyfile, ?SERVER_KEY},
+ {reuseaddr, true},
+ {versions, ['tlsv1.2','tlsv1.3']},
+ {session_tickets, stateless},
+ {early_data, enabled},
+ {keep_secrets, true} %% Enable NSS key log (debug option)
+ ],
+ {ok, LSock} = ssl:listen(Port, LOpts),
+ %% Accept first connection
+ {ok, CSock0} = ssl:transport_accept(LSock),
+ {ok, _} = ssl:handshake(CSock0),
+ %% Accept second connection
+ {ok, CSock1} = ssl:transport_accept(LSock),
+ {ok, Sock} = ssl:handshake(CSock1),
+ Sock.
+ </code>
+ <p><em>Exporting the secrets (optional)</em></p>
+ <code type="none">
+ {ok, [{keylog, KeylogItems}]} = ssl:connection_information(Sock, [keylog]).
+ file:write_file("key.log", [[KeylogItem,$\n] || KeylogItem &lt;- KeylogItems]).
+ </code>
+ <p><em>Client (automatic ticket handling):</em></p>
+ <code type="erl">
+ early_data_auto() -&gt;
+ %% First handshake 1-RTT - get session tickets
+ application:load(ssl),
+ {ok, _} = application:ensure_all_started(ssl),
+ Port = 11029,
+ Data = &lt;&lt;"HEAD / HTTP/1.1\r\nHost: \r\nConnection: close\r\n"&gt;&gt;,
+ COpts0 = [{cacertfile, ?CA_CERT},
+ {versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, auto}],
+ {ok, Sock0} = ssl:connect("localhost", Port, COpts0),
+
+ %% Wait for session tickets
+ timer:sleep(500),
+ %% Close socket if server cannot handle multiple connections e.g. openssl s_server
+ ssl:close(Sock0),
+
+ %% Second handshake 0-RTT
+ COpts1 = [{cacertfile, ?CA_CERT},
+ {versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, auto},
+ {early_data, Data}],
+ {ok, Sock} = ssl:connect("localhost", Port, COpts1),
+ Sock.
+ </code>
+ <p><em>Client (manual ticket handling):</em></p>
+ <code type="erl">
+ early_data_manual() -&gt;
+ %% First handshake 1-RTT - get session tickets
+ application:load(ssl),
+ {ok, _} = application:ensure_all_started(ssl),
+ Port = 11029,
+ Data = &lt;&lt;"HEAD / HTTP/1.1\r\nHost: \r\nConnection: close\r\n"&gt;&gt;,
+ COpts0 = [{cacertfile, ?CA_CERT},
+ {versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, manual}],
+ {ok, Sock0} = ssl:connect("localhost", Port, COpts0),
+
+ %% Wait for session tickets
+ Ticket =
+ receive
+ {ssl, session_ticket, Ticket0} ->
+ Ticket0
+ end,
+
+ %% Close socket if server cannot handle multiple connections
+ %% e.g. openssl s_server
+ ssl:close(Sock0),
+
+ %% Second handshake 0-RTT
+ COpts1 = [{cacertfile, ?CA_CERT},
+ {versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, manual},
+ {use_ticket, [Ticket]},
+ {early_data, Data}],
+ {ok, Sock} = ssl:connect("localhost", Port, COpts1),
+ Sock.
+ </code>
+ </section>
+
+ <section>
<title>Anti-Replay Protection in TLS 1.3</title>
<p>The TLS 1.3 protocol does not provide inherent protection for replay of 0-RTT data but
@@ -414,7 +704,7 @@ ssl:connect("localhost", 9999,
Bloom filters are fast, memory-efficient, probabilistic data structures that can tell
if an element may be in a set or if it is definitely not in the set.</p>
- <p>If the option <seealso marker="ssl:ssl#type-anti_replay">anti_replay</seealso>
+ <p>If the option <seetype marker="ssl:ssl#anti_replay">anti_replay</seetype>
is defined in the server, a pair of Bloom filters (<em>current</em> and
<em>old</em>) are used to record incoming ClientHello messages (it is the unique
binder value that is actually stored).
@@ -424,12 +714,12 @@ ssl:connect("localhost", 9999,
is set as <em>current</em>.
</p>
- <p>The Anti-Replay protection feature in statless servers executes in the following steps
+ <p>The Anti-Replay protection feature in stateless servers executes in the following steps
when a new ClientHello is received:</p>
<list type="bulleted">
<item><p>Reported ticket age (obfuscated ticket age) shall be
less than ticket lifetime.</p></item>
- <item><p>Actual ticket age shall be less than the ticket lifetime (statless session
+ <item><p>Actual ticket age shall be less than the ticket lifetime (stateless session
tickets contain the servers timestamp when the ticket was issued).</p></item>
<item><p>Ticket shall be used within specified time window (freshness checks).</p></item>
<item><p>If all above checks passed both <em>current</em> and <em>old</em> Bloom filters
diff --git a/lib/ssl/prebuild.skip b/lib/ssl/prebuild.skip
new file mode 100644
index 0000000000..c416485981
--- /dev/null
+++ b/lib/ssl/prebuild.skip
@@ -0,0 +1 @@
+src/deps/ssl.d \ No newline at end of file
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 03c39b4722..5edd6cb4b9 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -47,9 +47,12 @@ MODULES= \
dtls_connection \
dtls_connection_sup \
dtls_handshake \
+ dtls_gen_connection \
dtls_listener_sup \
dtls_packet_demux \
dtls_record \
+ dtls_server_sup\
+ dtls_server_session_cache_sup\
dtls_sup \
dtls_socket \
dtls_v1 \
@@ -62,8 +65,8 @@ MODULES= \
ssl_certificate \
ssl_cipher \
ssl_cipher_format \
+ ssl_client_session_cache_db \
ssl_config \
- ssl_connection \
ssl_connection_sup \
ssl_crl \
ssl_crl_cache \
@@ -72,6 +75,7 @@ MODULES= \
ssl_dist_admin_sup \
ssl_dist_connection_sup \
ssl_dist_sup \
+ ssl_gen_statem \
ssl_handshake \
ssl_listen_tracker_sup \
ssl_logger \
@@ -79,20 +83,26 @@ MODULES= \
ssl_pem_cache \
ssl_pkix_db \
ssl_record \
+ ssl_server_session_cache \
+ ssl_server_session_cache_db \
+ ssl_server_session_cache_sup \
+ ssl_upgrade_server_session_cache_sup \
ssl_session \
- ssl_session_cache \
ssl_srp_primes \
ssl_sup \
- ssl_v3 \
tls_bloom_filter \
+ tls_dtls_connection \
tls_connection \
tls_connection_sup \
tls_connection_1_3 \
+ tls_gen_connection \
tls_handshake \
tls_handshake_1_3 \
tls_record \
tls_record_1_3 \
tls_client_ticket_store \
+ tls_dist_sup \
+ tls_dist_server_sup \
tls_sender \
tls_server_session_ticket\
tls_server_session_ticket_sup\
@@ -142,15 +152,21 @@ DEPDIR=$(ERL_TOP)/lib/ssl/src/deps
DEP_FILE=$(DEPDIR)/ssl.d
$(shell mkdir -p $(dir $(DEP_FILE)) >/dev/null)
+ifeq ($(TARGET), win32)
+ # Native path without C: ignore driveletter case
+ ERL_TOP_NATIVE = $(shell w32_path.sh -m $(ERL_TOP) | sed "s@[a-zA-Z]:@:@")
+else
+ ERL_TOP_NATIVE = $(ERL_TOP)
+endif
+
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
-EXTRA_ERLC_FLAGS = +warn_unused_vars
+EXTRA_ERLC_FLAGS = +warn_unused_vars -Werror
ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
-pz $(EBIN) \
-pz $(ERL_TOP)/lib/public_key/ebin \
- $(EXTRA_ERLC_FLAGS)
-
+ $(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\"
# ----------------------------------------------------
# Targets
@@ -159,8 +175,9 @@ ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
$(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES)
$(DEP_FILE): $(ERL_FILES)
+ @echo SED $(TARGET) $(ERL_TOP_NATIVE)
$(gen_verbose)erlc -M $(ERL_FILES) \
- | sed "s@$(ERL_TOP)@../../..@g" \
+ | sed "s@[a-zA-Z]\?$(ERL_TOP_NATIVE)@../../..@g" \
| sed "s/\.$(EMULATOR)/\.$$\(EMULATOR\)/" \
| sed 's@^dtls_@$$(EBIN)/dtls_@' \
| sed 's@^inet_@$$(EBIN)/inet_@' \
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 975dc5fc4e..78348826e4 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -17,12 +17,105 @@
%%
%% %CopyrightEnd%
%%
+
-module(dtls_connection).
+%%----------------------------------------------------------------------
+%% Purpose: DTLS-1-DTLS-1.2 FSM (* = optional)
+%%----------------------------------------------------------------------
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% For UDP transport the following flights are used as retransmission units
+%% in case of package loss. Flight timers are handled in state entry functions.
+%%
+%% Client Server
+%% ------ ------
+%%
+%% ClientHello --------> Flight 1
+%%
+%% <------- HelloVerifyRequest Flight 2
+%%
+%% ClientHello --------> Flight 3
+%%
+%% ServerHello \
+%% Certificate* \
+%% ServerKeyExchange* Flight 4
+%% CertificateRequest* /
+%% <-------- ServerHelloDone /
+%%
+%% Certificate* \
+%% ClientKeyExchange \
+%% CertificateVerify* Flight 5
+%% [ChangeCipherSpec] /
+%% Finished --------> /
+%%
+%% [ChangeCipherSpec] \ Flight 6
+%% <-------- Finished /
+%%
+%% Message Flights for Full Handshake
+%%
+%%
+%% Client Server
+%% ------ ------
+%%
+%% ClientHello --------> Abbrev Flight 1
+%%
+%% ServerHello \ part 1
+%% [ChangeCipherSpec] Abbrev Flight 2
+%% <-------- Finished / part 2
+%%
+%% [ChangeCipherSpec] \ Abbrev Flight 3
+%% Finished --------> /
+%%
+%%
+%% Message Flights for Abbbriviated Handshake
+%%----------------------------------------------------------------------
+%% Start FSM ---> CONFIG_ERROR
+%% Send error to user
+%% | and shutdown
+%% |
+%% V
+%% INITIAL_HELLO
+%%
+%% | Send/ Recv Flight 1
+%% |
+%% |
+%% USER_HELLO |
+%% <- Possibly let user provide V
+%% options after looking at hello ex -> HELLO
+%% | Send Recv Flight 2 to Flight 4 or
+%% | Abbrev Flight 1 to Abbrev Flight 2 part 1
+%% |
+%% New session | Resumed session
+%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED
+%%
+%% <- Possibly Receive -- | |
+%% OCSP Stapel ------> | Send/ Recv Flight 5 |
+%% | |
+%% V | Send / Recv Abbrev Flight part 2
+%% | to Abbrev Flight 3
+%% CIPHER |
+%% | |
+%% | Send/ Recv Flight 6 |
+%% | |
+%% V V
+%% ----------------------------------------------------
+%% |
+%% |
+%% V
+%% CONNECTION
+%% |
+%% | Renegotiaton
+%% V
+%% GO BACK TO HELLO
+%%----------------------------------------------------------------------
+
%% Internal application API
-behaviour(gen_statem).
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
-include("dtls_connection.hrl").
-include("dtls_handshake.hrl").
-include("ssl_alert.hrl").
@@ -31,34 +124,31 @@
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
--include_lib("kernel/include/logger.hrl").
%% Internal application API
%% Setup
--export([start_fsm/8, start_link/7, init/1, pids/1]).
-
-%% State transition handling
--export([next_event/3, next_event/4, handle_protocol_record/3]).
-
-%% Handshake handling
--export([renegotiate/2, send_handshake/2,
- queue_handshake/2, queue_change_cipher/2,
- reinit/1, reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/2]).
+-export([init/1]).
-%% Alert and close handling
--export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]).
-
-%% Data handling
--export([socket/4, setopts/3, getopts/3]).
+-export([renegotiate/2]).
%% gen_statem state functions
--export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+-export([initial_hello/3,
+ config_error/3,
+ downgrade/3,
+ hello/3,
+ user_hello/3,
+ wait_ocsp_stapling/3,
+ certify/3,
+ cipher/3,
+ abbreviated/3,
connection/3]).
+
%% gen_statem callbacks
--export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
+-export([callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
%%====================================================================
%% Internal application API
@@ -66,373 +156,47 @@
%%====================================================================
%% Setup
%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts,
- User, {CbModule, _, _, _, _} = CbInfo,
- Timeout) ->
- try
- {ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker),
- ssl_connection:handshake(SslSocket, Timeout)
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
- {ok, pid()} | ignore | {error, reason()}.
-%%
-%% Description: Creates a gen_statem process which calls Module:init/1 to
-%% initialize.
-%%--------------------------------------------------------------------
-start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}.
-
init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
process_flag(trap_exit, true),
- State0 = #state{protocol_specific = Map} = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
+ State0 = #state{protocol_specific = Map} =
+ initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
try
- State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
- gen_statem:enter_loop(?MODULE, [], init, State)
+ State = ssl_gen_statem:ssl_config(State0#state.ssl_options,
+ Role, State0),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch
throw:Error ->
- EState = State0#state{protocol_specific = Map#{error => Error}},
- gen_statem:enter_loop(?MODULE, [], error, EState)
- end.
-
-pids(_) ->
- [self()].
-
-%%====================================================================
-%% State transition handling
-%%====================================================================
-next_record(#state{handshake_env =
- #handshake_env{unprocessed_handshake_events = N} = HsEnv}
- = State) when N > 0 ->
- {no_record, State#state{handshake_env =
- HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
-next_record(#state{protocol_buffers =
- #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]}
- = Buffers,
- connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) ->
- CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read),
- case dtls_record:replay_detect(CT, CurrentRead) of
- false ->
- decode_cipher_text(State#state{connection_states = ConnectionStates}) ;
- true ->
- %% Ignore replayed record
- next_record(State#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_cipher_texts = Rest},
- connection_states = ConnectionStates})
- end;
-next_record(#state{protocol_buffers =
- #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]}
- = Buffers,
- connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State)
- when Epoch > CurrentEpoch ->
- %% TODO Buffer later Epoch message, drop it for now
- next_record(State#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_cipher_texts = Rest},
- connection_states = ConnectionStates});
-next_record(#state{protocol_buffers =
- #protocol_buffers{dtls_cipher_texts = [ _ | Rest]}
- = Buffers,
- connection_states = ConnectionStates} = State) ->
- %% Drop old epoch message
- next_record(State#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_cipher_texts = Rest},
- connection_states = ConnectionStates});
-next_record(#state{static_env = #static_env{role = server,
- socket = {Listener, {Client, _}}}} = State) ->
- dtls_packet_demux:active_once(Listener, Client, self()),
- {no_record, State};
-next_record(#state{protocol_specific = #{active_n_toggle := true,
- active_n := N} = ProtocolSpec,
- static_env = #static_env{role = client,
- socket = {_Server, Socket} = DTLSSocket,
- close_tag = CloseTag,
- transport_cb = Transport}} = State) ->
- case dtls_socket:setopts(Transport, Socket, [{active,N}]) of
- ok ->
- {no_record, State#state{protocol_specific =
- ProtocolSpec#{active_n_toggle => false}}};
- _ ->
- self() ! {CloseTag, DTLSSocket},
- {no_record, State}
- end;
-next_record(State) ->
- {no_record, State}.
-
-next_event(StateName, Record, State) ->
- next_event(StateName, Record, State, []).
-
-next_event(StateName, no_record,
- #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
- case next_record(State0) of
- {no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
- {#ssl_tls{epoch = CurrentEpoch,
- type = ?HANDSHAKE,
- version = Version} = Record, State1} ->
- State = dtls_version(StateName, Version, State1),
- {next_state, StateName, State,
- [{next_event, internal, {protocol_record, Record}} | Actions]};
- {#ssl_tls{epoch = CurrentEpoch} = Record, State} ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
- {#ssl_tls{epoch = Epoch,
- type = ?HANDSHAKE,
- version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
- {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
- next_event(StateName, no_record, State, Actions ++ MoreActions);
- %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
- {#ssl_tls{epoch = Epoch,
- type = ?CHANGE_CIPHER_SPEC,
- version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
- {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
- next_event(StateName, no_record, State, Actions ++ MoreActions);
- {#ssl_tls{epoch = _Epoch,
- version = _Version}, State} ->
- %% TODO maybe buffer later epoch
- next_event(StateName, no_record, State, Actions);
- {#alert{} = Alert, State} ->
- Version = State#state.connection_env#connection_env.negotiated_version,
- handle_own_alert(Alert, Version, StateName, State)
- end;
-next_event(connection = StateName, Record,
- #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
- case Record of
- #ssl_tls{epoch = CurrentEpoch,
- type = ?HANDSHAKE,
- version = Version} = Record ->
- State = dtls_version(StateName, Version, State0),
- {next_state, StateName, State,
- [{next_event, internal, {protocol_record, Record}} | Actions]};
- #ssl_tls{epoch = CurrentEpoch} ->
- {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]};
- #ssl_tls{epoch = Epoch,
- type = ?HANDSHAKE,
- version = _Version} when Epoch == CurrentEpoch-1 ->
- {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
- next_event(StateName, no_record, State, Actions ++ MoreActions);
- %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
- #ssl_tls{epoch = Epoch,
- type = ?CHANGE_CIPHER_SPEC,
- version = _Version} when Epoch == CurrentEpoch-1 ->
- {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
- next_event(StateName, no_record, State, Actions ++ MoreActions);
- _ ->
- next_event(StateName, no_record, State0, Actions)
- end;
-next_event(StateName, Record,
- #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
- case Record of
- #ssl_tls{epoch = CurrentEpoch,
- version = Version} = Record ->
- State = dtls_version(StateName, Version, State0),
- {next_state, StateName, State,
- [{next_event, internal, {protocol_record, Record}} | Actions]};
- #ssl_tls{epoch = _Epoch,
- version = _Version} = _Record ->
- %% TODO maybe buffer later epoch
- next_event(StateName, no_record, State0, Actions);
- #alert{} = Alert ->
- Version = State0#state.connection_env#connection_env.negotiated_version,
- handle_own_alert(Alert, Version, StateName, State0)
+ EState = State0#state{protocol_specific =
+ Map#{error => Error}},
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
-
-%%% DTLS record protocol level application data messages
-
-handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) ->
- case ssl_connection:read_application_data(Data, State0) of
- {stop, _, _} = Stop->
- Stop;
- {Record, State1} ->
- {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1),
- ssl_connection:hibernate_after(StateName, State, Actions)
- end;
-%%% DTLS record protocol level handshake messages
-handle_protocol_record(#ssl_tls{type = ?HANDSHAKE,
- fragment = Data},
- StateName,
- #state{protocol_buffers = Buffers0,
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = Options} = State) ->
- try
- case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0, Options) of
- {[], Buffers} ->
- next_event(StateName, no_record, State#state{protocol_buffers = Buffers});
- {Packets, Buffers} ->
- HsEnv = State#state.handshake_env,
- Events = dtls_handshake_events(Packets),
- {next_state, StateName,
- State#state{protocol_buffers = Buffers,
- handshake_env =
- HsEnv#handshake_env{unprocessed_handshake_events
- = unprocessed_events(Events)}}, Events}
- end
- catch throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State)
- end;
-%%% DTLS record protocol level change cipher messages
-handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
-%%% DTLS record protocol level Alert messages
-handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- case decode_alerts(EncAlerts) of
- Alerts = [_|_] ->
- handle_alerts(Alerts, {next_state, StateName, State});
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State)
- end;
-%% Ignore unknown TLS record level protocol messages
-handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, State, []}.
-
%%====================================================================
-%% Handshake handling
+%% Handshake
%%====================================================================
-
renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
- State = reinit_handshake_data(State0),
+ State = dtls_gen_connection:reinit_handshake_data(State0),
{next_state, connection, State,
[{next_event, internal, #hello_request{}} | Actions]};
renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) ->
HelloRequest = ssl_handshake:hello_request(),
State1 = prepare_flight(State0),
- {State, MoreActions} = send_handshake(HelloRequest, State1),
- next_event(hello, no_record, State, Actions ++ MoreActions).
-
-send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) ->
- #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
- send_handshake_flight(queue_handshake(Handshake, State), Epoch).
-
-queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := HsBuffer0,
- change_cipher_spec := undefined,
- next_sequence := Seq} = Flight0,
- ssl_options = #{log_level := LogLevel}} = State) ->
- Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
- Hist = update_handshake_history(Handshake0, Handshake, Hist0),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0),
-
- State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0],
- next_sequence => Seq +1},
- handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
-
-queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0,
- next_sequence := Seq} = Flight0,
- ssl_options = #{log_level := LogLevel}} = State) ->
- Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
- Hist = update_handshake_history(Handshake0, Handshake, Hist0),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0),
-
- State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0],
- next_sequence => Seq +1},
- handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}.
-
-queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight,
- connection_states = ConnectionStates0} = State) ->
- ConnectionStates =
- dtls_record:next_epoch(ConnectionStates0, write),
- State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher},
- connection_states = ConnectionStates}.
-
-reinit(State) ->
- %% To be API compatible with TLS NOOP here
- reinit_handshake_data(State).
-reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag},
- protocol_buffers = Buffers,
- protocol_specific = PS,
- handshake_env = HsEnv} = State) ->
- State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
- public_key_info = undefined,
- premaster_secret = undefined},
- protocol_specific = PS#{flight_state => initial_flight_state(DataTag)},
- flight_buffer = new_flight(),
- protocol_buffers =
- Buffers#protocol_buffers{
- dtls_handshake_next_seq = 0,
- dtls_handshake_next_fragments = [],
- dtls_handshake_later_fragments = []
- }}.
-
-select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
- SNI;
-select_sni_extension(_) ->
- undefined.
-
-empty_connection_state(ConnectionEnd, BeastMitigation) ->
- Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation),
- dtls_record:empty_connection_state(Empty).
-
-%%====================================================================
-%% Alert and close handling
-%%====================================================================
-encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
- dtls_record:encode_alert_record(Alert, Version, ConnectionStates).
-
-send_alert(Alert, #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
-
- connection_env = #connection_env{negotiated_version = Version},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0) ->
- {BinMsg, ConnectionStates} =
- encode_alert(Alert, Version, ConnectionStates0),
- send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
- State0#state{connection_states = ConnectionStates}.
-
-send_alert_in_connection(Alert, State) ->
- _ = send_alert(Alert, State),
- ok.
-
-close(downgrade, _,_,_,_) ->
- ok;
-%% Other
-close(_, Socket, Transport, _,_) ->
- dtls_socket:close(Transport,Socket).
-
-protocol_name() ->
- "DTLS".
-
-%%====================================================================
-%% Data handling
-%%====================================================================
-
-send(Transport, {Listener, Socket}, Data) when is_pid(Listener) -> % Server socket
- dtls_socket:send(Transport, Socket, Data);
-send(Transport, Socket, Data) -> % Client socket
- dtls_socket:send(Transport, Socket, Data).
-
-socket(Pid, Transport, Socket, _Tracker) ->
- dtls_socket:socket(Pid, Transport, Socket, ?MODULE).
-
-setopts(Transport, Socket, Other) ->
- dtls_socket:setopts(Transport, Socket, Other).
-
-getopts(Transport, Socket, Tag) ->
- dtls_socket:getopts(Transport, Socket, Tag).
+ {State, MoreActions} = dtls_gen_connection:send_handshake(HelloRequest, State1),
+ dtls_gen_connection:next_event(hello, no_record, State, Actions ++ MoreActions).
%%--------------------------------------------------------------------
%% State functions
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
-init(enter, _, State) ->
+initial_hello(enter, _, State) ->
{keep_state, State};
-init({call, From}, {start, Timeout},
+initial_hello({call, From}, {start, Timeout},
#state{static_env = #static_env{host = Host,
port = Port,
role = client,
@@ -441,48 +205,49 @@ init({call, From}, {start, Timeout},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
connection_env = CEnv,
ssl_options = #{versions := Versions} = SslOpts,
- session = #session{own_certificate = Cert} = NewSession,
+ session = #session{own_certificates = OwnCerts} = NewSession,
connection_states = ConnectionStates0
} = State0) ->
Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Session#session.session_id, Renegotiation, Cert),
+ Session#session.session_id, Renegotiation, OwnCerts),
+ MaxFragEnum = maps:get(max_frag_enum, Hello#client_hello.extensions, undefined),
+ ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, Versions),
- State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
- {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}),
+ State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates1}),
+ {State2, Actions} =
+ dtls_gen_connection:send_handshake(Hello,
+ State1#state{connection_env =
+ CEnv#connection_env{negotiated_version = HelloVersion}}),
State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion
session = Session,
start_or_recv_from = From},
- next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]);
-init({call, _} = Type, Event, #state{static_env = #static_env{role = server},
- protocol_specific = PS} = State) ->
- Result = gen_handshake(?FUNCTION_NAME, Type, Event,
- State#state{protocol_specific = PS#{current_cookie_secret => dtls_v1:cookie_secret(),
+ dtls_gen_connection:next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]);
+initial_hello({call, _} = Type, Event, #state{static_env = #static_env{role = server},
+ protocol_specific = PS} = State) ->
+ Result = ssl_gen_statem:?FUNCTION_NAME(Type, Event,
+ State#state{protocol_specific =
+ PS#{current_cookie_secret => dtls_v1:cookie_secret(),
previous_cookie_secret => <<>>,
ignored_alerts => 0,
max_ignored_alerts => 10}}),
erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
Result;
-init(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
-error(enter, _, State) ->
+config_error(enter, _, State) ->
{keep_state, State};
-error({call, From}, {start, _Timeout},
- #state{protocol_specific = #{error := Error}} = State) ->
- {stop_and_reply, {shutdown, normal},
- [{reply, From, {error, Error}}], State};
-error({call, _} = Call, Msg, State) ->
- gen_handshake(?FUNCTION_NAME, Call, Msg, State);
-error(_, _, _) ->
- {keep_state_and_data, [postpone]}.
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec hello(gen_statem:event_type(),
@@ -503,90 +268,114 @@ hello(internal, #client_hello{cookie = <<>>,
handshake_env = HsEnv,
connection_env = CEnv,
protocol_specific = #{current_cookie_secret := Secret}} = State0) ->
- {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
- Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
- %% FROM RFC 6347 regarding HelloVerifyRequest message:
- %% The server_version field has the same syntax as in TLS. However, in
- %% order to avoid the requirement to do version negotiation in the
- %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS
- %% version 1.0 regardless of the version of TLS that is expected to be
- %% negotiated.
- VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
- State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
- {State, Actions} = send_handshake(VerifyRequest, State1),
- next_event(?FUNCTION_NAME, no_record,
- State#state{handshake_env = HsEnv#handshake_env{
- tls_handshake_history =
- ssl_handshake:init_handshake_history()}},
- Actions);
-hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client,
- host = Host,
- port = Port},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
- connection_env = CEnv,
- ssl_options = SslOpts,
- session = #session{own_certificate = Cert, session_id = Id},
- connection_states = ConnectionStates0
- } = State0) ->
-
+ case tls_dtls_connection:handle_sni_extension(State0, Hello) of
+ #state{} = State1 ->
+ {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
+ Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
+ %% FROM RFC 6347 regarding HelloVerifyRequest message:
+ %% The server_version field has the same syntax as in TLS. However, in
+ %% order to avoid the requirement to do version negotiation in the
+ %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS
+ %% version 1.0 regardless of the version of TLS that is expected to be
+ %% negotiated.
+ VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
+ State2 = prepare_flight(State1#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
+ {State, Actions} = dtls_gen_connection:send_handshake(VerifyRequest, State2),
+ dtls_gen_connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{
+ tls_handshake_history =
+ ssl_handshake:init_handshake_history()}},
+ Actions);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version,?FUNCTION_NAME, State0)
+ end;
+hello(internal, #hello_verify_request{cookie = Cookie},
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState0} = HsEnv,
+ connection_env = CEnv,
+ ssl_options = #{ocsp_stapling := OcspStaplingOpt,
+ ocsp_nonce := OcspNonceOpt} = SslOpts,
+ session = #session{own_certificates = OwnCerts,
+ session_id = Id},
+ connection_states = ConnectionStates0
+ } = State0) ->
+ OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
- SslOpts, Id, Renegotiation, Cert),
+ SslOpts, Id, Renegotiation, OwnCerts, OcspNonce),
Version = Hello#client_hello.client_version,
State1 = prepare_flight(State0#state{handshake_env =
- HsEnv#handshake_env{tls_handshake_history
- = ssl_handshake:init_handshake_history()}}),
+ HsEnv#handshake_env{tls_handshake_history
+ = ssl_handshake:init_handshake_history(),
+ ocsp_stapling_state =
+ OcspState0#{ocsp_nonce => OcspNonce}}}),
- {State2, Actions} = send_handshake(Hello, State1),
+ {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1),
+
State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version} % RequestedVersion
},
- next_event(?FUNCTION_NAME, no_record, State, Actions);
-hello(internal, #client_hello{extensions = Extensions} = Hello,
+ dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, Actions);
+hello(internal, #client_hello{extensions = Extensions, client_version = ClientVersion} = Hello,
#state{ssl_options = #{handshake := hello},
handshake_env = HsEnv,
- start_or_recv_from = From} = State) ->
- {next_state, user_hello, State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{hello = Hello}},
- [{reply, From, {ok, Extensions}}]};
+ start_or_recv_from = From} = State0) ->
+ case tls_dtls_connection:handle_sni_extension(State0, Hello) of
+ #state{} = State ->
+ {next_state, user_hello, State#state{start_or_recv_from = undefined,
+ handshake_env = HsEnv#handshake_env{hello = Hello}},
+ [{reply, From, {ok, Extensions}}]};
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ClientVersion, ?FUNCTION_NAME, State0)
+ end;
hello(internal, #server_hello{extensions = Extensions} = Hello,
- #state{ssl_options = #{handshake := hello},
+ #state{ssl_options = #{
+ handshake := hello},
handshake_env = HsEnv,
start_or_recv_from = From} = State) ->
{next_state, user_hello, State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{hello = Hello}},
- [{reply, From, {ok, Extensions}}]};
-
+ handshake_env = HsEnv#handshake_env{
+ hello = Hello}},
+ [{reply, From, {ok, Extensions}}]};
hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server,
transport_cb = Transport,
socket = Socket},
protocol_specific = #{current_cookie_secret := Secret,
previous_cookie_secret := PSecret}
- } = State0) ->
+ } = State) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
case dtls_handshake:cookie(Secret, IP, Port, Hello) of
Cookie ->
- handle_client_hello(Hello, State0);
+ handle_client_hello(Hello, State);
_ ->
case dtls_handshake:cookie(PSecret, IP, Port, Hello) of
Cookie ->
- handle_client_hello(Hello, State0);
+ handle_client_hello(Hello, State);
_ ->
%% Handle bad cookie as new cookie request RFC 6347 4.1.2
- hello(internal, Hello#client_hello{cookie = <<>>}, State0)
+ hello(internal, Hello#client_hello{cookie = <<>>}, State)
end
end;
hello(internal, #server_hello{} = Hello,
#state{
static_env = #static_env{role = client},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+ handshake_env = #handshake_env{
+ renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState0} = HsEnv,
connection_env = #connection_env{negotiated_version = ReqVersion},
connection_states = ConnectionStates0,
+ session = #session{session_id = OldId},
ssl_options = SslOptions} = State) ->
- case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State);
- {Version, NewId, ConnectionStates, ProtoExt, Protocol} ->
- ssl_connection:handle_session(Hello,
- Version, NewId, ConnectionStates, ProtoExt, Protocol, State)
+ case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State);
+ {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} ->
+ tls_dtls_connection:handle_session(Hello,
+ Version, NewId, ConnectionStates, ProtoExt, Protocol,
+ State#state{handshake_env =
+ HsEnv#handshake_env{
+ ocsp_stapling_state = maps:merge(OcspState0,OcspState)}})
end;
hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, State) ->
%% Initial hello should not be in handshake history
@@ -595,8 +384,9 @@ hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) ->
%% hello_verify should not be in handshake history
{next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]};
hello(internal, #change_cipher_spec{type = <<1>>}, State0) ->
- {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
- {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),
+ {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
+ {next_state, ?FUNCTION_NAME, State, Actions} =
+ dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions};
hello(info, Event, State) ->
@@ -635,6 +425,21 @@ abbreviated(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
abbreviated(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_ocsp_stapling(enter, _Event, State0) ->
+ {State, Actions} = handle_flight_timer(State0),
+ {keep_state, State, Actions};
+wait_ocsp_stapling(info, Event, State) ->
+ gen_info(Event, ?FUNCTION_NAME, State);
+wait_ocsp_stapling(state_timeout, Event, State) ->
+ handle_state_timeout(Event, ?FUNCTION_NAME, State);
+wait_ocsp_stapling(Type, Event, State) ->
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
+
%%--------------------------------------------------------------------
-spec certify(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
@@ -645,10 +450,11 @@ certify(enter, _, State0) ->
certify(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
certify(internal = Type, #server_hello_done{} = Event, State) ->
- ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event, prepare_flight(State));
certify(internal, #change_cipher_spec{type = <<1>>}, State0) ->
- {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
- {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),
+ {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
+ {next_state, ?FUNCTION_NAME, State, Actions} =
+ dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions};
certify(state_timeout, Event, State) ->
@@ -669,17 +475,16 @@ cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event,
#state{connection_states = ConnectionStates0} = State) ->
ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read),
ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read),
- ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates});
cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates,
protocol_specific = PS} = State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event,
- prepare_flight(State#state{connection_states = ConnectionStates,
- protocol_specific = PS#{flight_state => connection}}),
- ?MODULE);
+ gen_handshake(?FUNCTION_NAME, Type, Event,
+ prepare_flight(State#state{connection_states = ConnectionStates,
+ protocol_specific = PS#{flight_state => connection}}));
cipher(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -698,7 +503,7 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho
},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
connection_env = CEnv,
- session = #session{own_certificate = Cert} = Session0,
+ session = #session{own_certificates = OwnCerts} = Session0,
ssl_options = #{versions := Versions} = SslOpts,
connection_states = ConnectionStates0,
protocol_specific = PS
@@ -706,16 +511,20 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho
Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Session#session.session_id, Renegotiation, Cert),
+ Session#session.session_id, Renegotiation, OwnCerts),
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, Versions),
State1 = prepare_flight(State0),
- {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}),
- State = State2#state{protocol_specific = PS#{flight_state => initial_flight_state(DataTag)},
+ {State2, Actions} =
+ dtls_gen_connection:send_handshake(Hello,
+ State1#state{connection_env =
+ CEnv#connection_env{negotiated_version = HelloVersion}}),
+ State = State2#state{protocol_specific = PS#{flight_state => dtls_gen_connection:initial_flight_state(DataTag)},
session = Session},
- next_event(hello, no_record, State, Actions);
-connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) ->
+ dtls_gen_connection:next_event(hello, no_record, State, Actions);
+connection(internal, #client_hello{} = Hello,
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
@@ -725,20 +534,21 @@ connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{ro
{next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer},
allow_renegotiate = false}},
[{next_event, internal, Hello}]};
-connection(internal, #client_hello{}, #state{static_env = #static_env{role = server},
+connection(internal, #client_hello{}, #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
handshake_env = #handshake_env{allow_renegotiate = false}} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
- State1 = send_alert(Alert, State0),
- {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE),
- next_event(?FUNCTION_NAME, Record, State);
+ State1 = dtls_gen_connection:send_alert(Alert, State0),
+ {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection),
+ dtls_gen_connection:next_event(?FUNCTION_NAME, Record, State);
connection({call, From}, {application_data, Data}, State) ->
try
send_application_data(Data, From, ?FUNCTION_NAME, State)
catch throw:Error ->
- ssl_connection:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}])
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}])
end;
connection(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ tls_dtls_connection:?FUNCTION_NAME(Type, Event, State).
%%TODO does this make sense for DTLS ?
%%--------------------------------------------------------------------
@@ -748,7 +558,7 @@ connection(Type, Event, State) ->
downgrade(enter, _, State) ->
{keep_state, State};
downgrade(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ tls_dtls_connection:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
%% gen_statem callbacks
@@ -757,40 +567,29 @@ callback_mode() ->
[state_functions, state_enter].
terminate(Reason, StateName, State) ->
- ssl_connection:terminate(Reason, StateName, State).
+ ssl_gen_statem:terminate(Reason, StateName, State).
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
format_status(Type, Data) ->
- ssl_connection:format_status(Type, Data).
+ ssl_gen_statem:format_status(Type, Data).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
initial_state(Role, Host, Port, Socket,
- {#{client_renegotiation := ClientRenegotiation} = SSLOptions, SocketOptions, _}, User,
+ {#{client_renegotiation := ClientRenegotiation} = SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
#{beast_mitigation := BeastMitigation} = SSLOptions,
ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation),
-
- SessionCacheCb = case application:get_env(ssl, session_cb) of
- {ok, Cb} when is_atom(Cb) ->
- Cb;
- _ ->
- ssl_session_cache
- end,
- InternalActiveN = case application:get_env(ssl, internal_active_n) of
- {ok, N} when is_integer(N) ->
- N;
- _ ->
- ?INTERNAL_ACTIVE_N
- end,
+ #{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role),
+ InternalActiveN = ssl_config:get_internal_active_n(),
Monitor = erlang:monitor(process, User),
InitStatEnv = #static_env{
role = Role,
transport_cb = CbModule,
- protocol_cb = ?MODULE,
+ protocol_cb = dtls_gen_connection,
data_tag = DataTag,
close_tag = CloseTag,
error_tag = ErrorTag,
@@ -798,7 +597,8 @@ initial_state(Role, Host, Port, Socket,
host = Host,
port = Port,
socket = Socket,
- session_cache_cb = SessionCacheCb
+ session_cache_cb = SessionCacheCb,
+ trackers = Trackers
},
#state{static_env = InitStatEnv,
@@ -812,253 +612,100 @@ initial_state(Role, Host, Port, Socket,
%% We do not want to save the password in the state so that
%% could be written in the clear into error logs.
ssl_options = SSLOptions#{password => undefined},
- session = #session{is_resumable = new},
+ session = #session{is_resumable = false},
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
user_data_buffer = {[],0,[]},
start_or_recv_from = undefined,
- flight_buffer = new_flight(),
+ flight_buffer = dtls_gen_connection:new_flight(),
protocol_specific = #{active_n => InternalActiveN,
active_n_toggle => true,
- flight_state => initial_flight_state(DataTag)}
+ flight_state => dtls_gen_connection:initial_flight_state(DataTag)}
}.
-initial_flight_state(udp)->
- {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT};
-initial_flight_state(_) ->
- reliable.
-
-next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
- dtls_record_buffer = Buf0,
- dtls_cipher_texts = CT0} = Buffers,
- connection_env = #connection_env{negotiated_version = Version},
- static_env = #static_env{data_tag = DataTag},
- ssl_options = SslOpts} = State0) ->
- case dtls_record:get_dtls_records(Data,
- {DataTag, StateName, Version,
- [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]},
- Buf0, SslOpts) of
- {Records, Buf1} ->
- CT1 = CT0 ++ Records,
- next_record(State0#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_record_buffer = Buf1,
- dtls_cipher_texts = CT1}});
- #alert{} = Alert ->
- Alert
- end.
-
-
-dtls_handshake_events(Packets) ->
- lists:map(fun(Packet) ->
- {next_event, internal, {handshake, Packet}}
- end, Packets).
-
-decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers,
- connection_states = ConnStates0} = State) ->
- case dtls_record:decode_cipher_text(CT, ConnStates0) of
- {Plain, ConnStates} ->
- {Plain, State#state{protocol_buffers =
- Buffers#protocol_buffers{dtls_cipher_texts = Rest},
- connection_states = ConnStates}};
- #alert{} = Alert ->
- {Alert, State}
- end.
-
-dtls_version(hello, Version, #state{static_env = #static_env{role = server},
- connection_env = CEnv} = State) ->
- State#state{connection_env = CEnv#connection_env{negotiated_version = Version}}; %%Inital version
-dtls_version(_,_, State) ->
- State.
-
-handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
- #state{connection_states = ConnectionStates0,
- static_env = #static_env{port = Port,
- session_cache = Cache,
- session_cache_cb = CacheCb},
- handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
- renegotiation = {Renegotiation, _},
- negotiated_protocol = CurrentProtocol} = HsEnv,
- connection_env = CEnv,
- session = #session{own_certificate = Cert} = Session0,
- ssl_options = SslOpts} = State0) ->
-
- case dtls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
- ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of
- #alert{} = Alert ->
- handle_own_alert(Alert, ClientVersion, hello, State0);
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
- State = prepare_flight(State0#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{negotiated_version = Version},
- handshake_env = HsEnv#handshake_env{
- hashsign_algorithm = HashSign,
+handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) ->
+ case tls_dtls_connection:handle_sni_extension(State0, Hello) of
+ #state{connection_states = ConnectionStates0,
+ static_env = #static_env{trackers = Trackers},
+ handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
+ renegotiation = {Renegotiation, _},
+ negotiated_protocol = CurrentProtocol} = HsEnv,
+ connection_env = CEnv,
+ session = #session{own_certificates = OwnCerts} = Session0,
+ ssl_options = SslOpts} = State1 ->
+ SessionTracker = proplists:get_value(session_id_tracker, Trackers),
+ case dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0,
+ ConnectionStates0, OwnCerts, KeyExAlg}, Renegotiation) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, State1);
+ {Version, {Type, Session},
+ ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
+ Protocol = case Protocol0 of
+ undefined -> CurrentProtocol;
+ _ -> Protocol0
+ end,
+
+ State = prepare_flight(State0#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = Version},
+ handshake_env = HsEnv#handshake_env{
+ hashsign_algorithm = HashSign,
client_hello_version = ClientVersion,
- negotiated_protocol = Protocol},
- session = Session}),
-
- ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt},
- State, ?MODULE)
- end.
+ negotiated_protocol = Protocol},
+ session = Session}),
+ {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]}
+ end;
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, State0)
+ end.
-%% raw data from socket, unpack records
-handle_info({Protocol, _, _, _, Data}, StateName,
- #state{static_env = #static_env{role = Role,
- data_tag = Protocol}} = State0) ->
- case next_dtls_record(Data, StateName, State0) of
- {Record, State} ->
- next_event(StateName, Record, State);
- #alert{} = Alert ->
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
- {stop, {shutdown, own_alert}, State0}
- end;
-
-handle_info({PassiveTag, Socket}, StateName,
- #state{static_env = #static_env{socket = {_, Socket},
- passive_tag = PassiveTag},
- protocol_specific = PS} = State) ->
- next_event(StateName, no_record,
- State#state{protocol_specific = PS#{active_n_toggle => true}});
-
-handle_info({CloseTag, Socket}, StateName,
- #state{static_env = #static_env{
- role = Role,
- socket = Socket,
- close_tag = CloseTag},
- connection_env = #connection_env{negotiated_version = Version},
- socket_options = #socket_options{active = Active},
- protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs},
- protocol_specific = PS} = State) ->
- %% Note that as of DTLS 1.2 (TLS 1.1),
- %% failure to properly close a connection no longer requires that a
- %% session not be resumed. This is a change from DTLS 1.0 to conform
- %% with widespread implementation practice.
- case (Active == false) andalso (CTs =/= []) of
- false ->
- case Version of
- {254, N} when N =< 253 ->
- ok;
- _ ->
- %% As invalidate_sessions here causes performance issues,
- %% we will conform to the widespread implementation
- %% practice and go aginst the spec
- %%invalidate_session(Role, Host, Port, Session)
- ok
- end,
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop, {shutdown, transport_closed}, State};
- true ->
- %% Fixes non-delivery of final DTLS record in {active, once}.
- %% Basically allows the application the opportunity to set {active, once} again
- %% and then receive the final message.
- next_event(StateName, no_record, State#state{
- protocol_specific = PS#{active_n_toggle => true}})
- end;
-
-handle_info(new_cookie_secret, StateName,
- #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) ->
- erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
- {next_state, StateName, State#state{protocol_specific =
- CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(),
- previous_cookie_secret => Secret}}};
-handle_info(Msg, StateName, State) ->
- ssl_connection:StateName(info, Msg, State, ?MODULE).
-
handle_state_timeout(flight_retransmission_timeout, StateName,
#state{protocol_specific =
#{flight_state := {retransmit, _NextTimeout}}} = State0) ->
- {State1, Actions0} = send_handshake_flight(State0,
- retransmit_epoch(StateName, State0)),
- {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0),
+ {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0,
+ retransmit_epoch(StateName, State0)),
+ {next_state, StateName, State, Actions} =
+ dtls_gen_connection:next_event(StateName, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions}.
-handle_alerts([], Result) ->
- Result;
-handle_alerts(_, {stop, _, _} = Stop) ->
- Stop;
-handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
-handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)).
-
-handle_own_alert(Alert, Version, StateName,
- #state{static_env = #static_env{data_tag = udp,
- role = Role},
- ssl_options = #{log_level := LogLevel}} = State0) ->
- case ignore_alert(Alert, State0) of
- {true, State} ->
- log_ignore_alert(LogLevel, StateName, Alert, Role),
- {next_state, StateName, State};
- {false, State} ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State)
- end;
-handle_own_alert(Alert, Version, StateName, State) ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State).
-
-encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) ->
- Fragments = lists:map(fun(Handshake) ->
- dtls_handshake:fragment_handshake(Handshake, MaxFragmentSize)
- end, Flight),
- dtls_record:encode_handshake(Fragments, Version, Epoch, ConnectionStates).
-encode_change_cipher(#change_cipher_spec{}, Version, Epoch, ConnectionStates) ->
- dtls_record:encode_change_cipher_spec(Version, Epoch, ConnectionStates).
-
-decode_alerts(Bin) ->
- ssl_alert:decode(Bin).
gen_handshake(StateName, Type, Event,
#state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try ssl_connection:StateName(Type, Event, State, ?MODULE) of
+ try tls_dtls_connection:StateName(Type, Event, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
+ try dtls_gen_connection:handle_info(Event, StateName, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
malformed_data),
Version, StateName, State)
end;
gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
+ try dtls_gen_connection:handle_info(Event, StateName, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
-unprocessed_events(Events) ->
- %% The first handshake event will be processed immediately
- %% as it is entered first in the event queue and
- %% when it is processed there will be length(Events)-1
- %% handshake events left to process before we should
- %% process more TLS-records received on the socket.
- erlang:length(Events)-1.
-update_handshake_history(#hello_verify_request{}, _, Hist) ->
- Hist;
-update_handshake_history(_, Handshake, Hist) ->
- ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake)).
prepare_flight(#state{flight_buffer = Flight,
connection_states = ConnectionStates0,
protocol_buffers =
@@ -1069,11 +716,6 @@ prepare_flight(#state{flight_buffer = Flight,
protocol_buffers = Buffers#protocol_buffers{
dtls_handshake_next_fragments = [],
dtls_handshake_later_fragments = []}}.
-new_flight() ->
- #{next_sequence => 0,
- handshakes => [],
- change_cipher_spec => undefined,
- handshakes_after_change_cipher_spec => []}.
next_flight(Flight) ->
Flight#{handshakes => [],
@@ -1099,122 +741,11 @@ new_timeout(N) when N =< 30000 ->
new_timeout(_) ->
60000.
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := Flight,
- change_cipher_spec := undefined},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0,
- Epoch) ->
- %% TODO remove hardcoded Max size
- {Encoded, ConnectionStates} =
- encode_handshake_flight(lists:reverse(Flight), Version, 1400, Epoch, ConnectionStates0),
- send(Transport, Socket, Encoded),
- ssl_logger:debug(LogLevel, outbound, 'record', Encoded),
- {State0#state{connection_states = ConnectionStates}, []};
-
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := [_|_] = Flight0,
- change_cipher_spec := ChangeCipher,
- handshakes_after_change_cipher_spec := []},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0,
- Epoch) ->
- {HsBefore, ConnectionStates1} =
- encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch, ConnectionStates0),
- {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1),
-
- send(Transport, Socket, [HsBefore, EncChangeCipher]),
- ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
- ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
- {State0#state{connection_states = ConnectionStates}, []};
-
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := [_|_] = Flight0,
- change_cipher_spec := ChangeCipher,
- handshakes_after_change_cipher_spec := Flight1},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0,
- Epoch) ->
- {HsBefore, ConnectionStates1} =
- encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch-1, ConnectionStates0),
- {EncChangeCipher, ConnectionStates2} =
- encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1),
- {HsAfter, ConnectionStates} =
- encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates2),
- send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]),
- ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
- ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
- ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
- {State0#state{connection_states = ConnectionStates}, []};
-
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = #{handshakes := [],
- change_cipher_spec := ChangeCipher,
- handshakes_after_change_cipher_spec := Flight1},
- connection_states = ConnectionStates0,
- ssl_options = #{log_level := LogLevel}} = State0,
- Epoch) ->
- {EncChangeCipher, ConnectionStates1} =
- encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0),
- {HsAfter, ConnectionStates} =
- encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates1),
- send(Transport, Socket, [EncChangeCipher, HsAfter]),
- ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
- ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
- {State0#state{connection_states = ConnectionStates}, []}.
-
retransmit_epoch(_StateName, #state{connection_states = ConnectionStates}) ->
#{epoch := Epoch} =
ssl_record:current_connection_state(ConnectionStates, write),
Epoch.
-ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N,
- max_ignored_alerts := N}} = State) ->
- {false, State};
-ignore_alert(#alert{level = ?FATAL} = Alert,
- #state{protocol_specific = #{ignored_alerts := N} = PS} = State) ->
- case is_ignore_alert(Alert) of
- true ->
- {true, State#state{protocol_specific = PS#{ignored_alerts => N+1}}};
- false ->
- {false, State}
- end;
-ignore_alert(_, State) ->
- {false, State}.
-
-%% RFC 6347 4.1.2.7. Handling Invalid Records
-%% recommends to silently ignore invalid DTLS records when
-%% upd is the transport. Note we do not support compression so no need
-%% include ?DECOMPRESSION_FAILURE
-is_ignore_alert(#alert{description = ?BAD_RECORD_MAC}) ->
- true;
-is_ignore_alert(#alert{description = ?RECORD_OVERFLOW}) ->
- true;
-is_ignore_alert(#alert{description = ?DECODE_ERROR}) ->
- true;
-is_ignore_alert(#alert{description = ?DECRYPT_ERROR}) ->
- true;
-is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) ->
- true;
-is_ignore_alert(_) ->
- false.
-
-log_ignore_alert(Level, StateName, #alert{where = Location} = Alert, Role) ->
- ssl_logger:log(info,
- Level, #{alert => Alert,
- alerter => ignored,
- statename => StateName,
- role => Role,
- protocol => protocol_name()}, Location).
-
send_application_data(Data, From, _StateName,
#state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
@@ -1232,12 +763,12 @@ send_application_data(Data, From, _StateName,
{Msgs, ConnectionStates} =
dtls_record:encode_data(Data, Version, ConnectionStates0),
State = State0#state{connection_states = ConnectionStates},
- case send(Transport, Socket, Msgs) of
+ case dtls_gen_connection:send(Transport, Socket, Msgs) of
ok ->
ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
- ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]);
+ ssl_gen_statem:hibernate_after(connection, State, [{reply, From, ok}]);
Result ->
- ssl_connection:hibernate_after(connection, State, [{reply, From, Result}])
+ ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Result}])
end
end.
diff --git a/lib/ssl/src/dtls_connection_sup.erl b/lib/ssl/src/dtls_connection_sup.erl
index 7d7be5743d..4c5c0a490f 100644
--- a/lib/ssl/src/dtls_connection_sup.erl
+++ b/lib/ssl/src/dtls_connection_sup.erl
@@ -57,10 +57,10 @@ init(_O) ->
MaxT = 3600,
Name = undefined, % As simple_one_for_one is used.
- StartFunc = {dtls_connection, start_link, []},
+ StartFunc = {ssl_gen_statem, start_link, []},
Restart = temporary, % E.g. should not be restarted
Shutdown = 4000,
- Modules = [dtls_connection, ssl_connection],
+ Modules = [ssl_gen_statem, dtls_connection],
Type = worker,
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl
new file mode 100644
index 0000000000..2032d77074
--- /dev/null
+++ b/lib/ssl/src/dtls_gen_connection.erl
@@ -0,0 +1,685 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose:
+%%----------------------------------------------------------------------
+-module(dtls_gen_connection).
+
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+-include("dtls_connection.hrl").
+-include("dtls_handshake.hrl").
+-include("ssl_alert.hrl").
+-include("dtls_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_api.hrl").
+-include("ssl_internal.hrl").
+
+%% Setup
+-export([start_fsm/8,
+ pids/1]).
+
+%% Handshake handling
+-export([send_handshake/2,
+ send_handshake_flight/2,
+ queue_handshake/2,
+ queue_change_cipher/2,
+ reinit/1,
+ reinit_handshake_data/1,
+ select_sni_extension/1,
+ empty_connection_state/2]).
+
+%% State transition handling
+-export([next_event/3,
+ next_event/4,
+ handle_protocol_record/3,
+ new_flight/0,
+ initial_flight_state/1
+ ]).
+
+%% Data handling
+-export([send/3,
+ socket/4,
+ setopts/3,
+ getopts/3,
+ handle_info/3]).
+
+%% Alert and close handling
+-export([send_alert/2,
+ send_alert_in_connection/2,
+ close/5,
+ protocol_name/0]).
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%====================================================================
+%% Setup
+%%====================================================================
+start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts,
+ User, {CbModule, _, _, _, _} = CbInfo,
+ Timeout) ->
+ try
+ {ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket,
+ Opts, User, CbInfo]),
+ {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker),
+ ssl_gen_statem:handshake(SslSocket, Timeout)
+ catch
+ error:{badmatch, {error, _} = Error} ->
+ Error
+ end.
+
+pids(_) ->
+ [self()].
+
+%%====================================================================
+%% State transition handling
+%%====================================================================
+next_record(#state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
+next_record(#state{protocol_buffers =
+ #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]}
+ = Buffers,
+ connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) ->
+ CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read),
+ case dtls_record:replay_detect(CT, CurrentRead) of
+ false ->
+ decode_cipher_text(State#state{connection_states = ConnectionStates}) ;
+ true ->
+ %% Ignore replayed record
+ next_record(State#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_cipher_texts = Rest},
+ connection_states = ConnectionStates})
+ end;
+next_record(#state{protocol_buffers =
+ #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]}
+ = Buffers,
+ connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State)
+ when Epoch > CurrentEpoch ->
+ %% TODO Buffer later Epoch message, drop it for now
+ next_record(State#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_cipher_texts = Rest},
+ connection_states = ConnectionStates});
+next_record(#state{protocol_buffers =
+ #protocol_buffers{dtls_cipher_texts = [ _ | Rest]}
+ = Buffers,
+ connection_states = ConnectionStates} = State) ->
+ %% Drop old epoch message
+ next_record(State#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_cipher_texts = Rest},
+ connection_states = ConnectionStates});
+next_record(#state{static_env = #static_env{role = server,
+ socket = {Listener, {Client, _}}}} = State) ->
+ dtls_packet_demux:active_once(Listener, Client, self()),
+ {no_record, State};
+next_record(#state{protocol_specific = #{active_n_toggle := true,
+ active_n := N} = ProtocolSpec,
+ static_env = #static_env{role = client,
+ socket = {_Server, Socket} = DTLSSocket,
+ close_tag = CloseTag,
+ transport_cb = Transport}} = State) ->
+ case dtls_socket:setopts(Transport, Socket, [{active,N}]) of
+ ok ->
+ {no_record, State#state{protocol_specific =
+ ProtocolSpec#{active_n_toggle => false}}};
+ _ ->
+ self() ! {CloseTag, DTLSSocket},
+ {no_record, State}
+ end;
+next_record(State) ->
+ {no_record, State}.
+
+next_event(StateName, Record, State) ->
+ next_event(StateName, Record, State, []).
+
+next_event(StateName, no_record,
+ #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
+ case next_record(State0) of
+ {no_record, State} ->
+ ssl_gen_statem:hibernate_after(StateName, State, Actions);
+ {#ssl_tls{epoch = CurrentEpoch,
+ type = ?HANDSHAKE,
+ version = Version} = Record, State1} ->
+ State = dtls_version(StateName, Version, State1),
+ {next_state, StateName, State,
+ [{next_event, internal, {protocol_record, Record}} | Actions]};
+ {#ssl_tls{epoch = CurrentEpoch} = Record, State} ->
+ {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
+ {#ssl_tls{epoch = Epoch,
+ type = ?HANDSHAKE,
+ version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
+ {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
+ %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
+ {#ssl_tls{epoch = Epoch,
+ type = ?CHANGE_CIPHER_SPEC,
+ version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
+ {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
+ {#ssl_tls{epoch = _Epoch,
+ version = _Version}, State} ->
+ %% TODO maybe buffer later epoch
+ next_event(StateName, no_record, State, Actions);
+ {#alert{} = Alert, State} ->
+ Version = State#state.connection_env#connection_env.negotiated_version,
+ handle_own_alert(Alert, Version, StateName, State)
+ end;
+next_event(connection = StateName, Record,
+ #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
+ case Record of
+ #ssl_tls{epoch = CurrentEpoch,
+ type = ?HANDSHAKE,
+ version = Version} = Record ->
+ State = dtls_version(StateName, Version, State0),
+ {next_state, StateName, State,
+ [{next_event, internal, {protocol_record, Record}} | Actions]};
+ #ssl_tls{epoch = CurrentEpoch} ->
+ {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]};
+ #ssl_tls{epoch = Epoch,
+ type = ?HANDSHAKE,
+ version = _Version} when Epoch == CurrentEpoch-1 ->
+ {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
+ %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
+ #ssl_tls{epoch = Epoch,
+ type = ?CHANGE_CIPHER_SPEC,
+ version = _Version} when Epoch == CurrentEpoch-1 ->
+ {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
+ _ ->
+ next_event(StateName, no_record, State0, Actions)
+ end;
+next_event(StateName, Record,
+ #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
+ case Record of
+ #ssl_tls{epoch = CurrentEpoch,
+ version = Version} = Record ->
+ State = dtls_version(StateName, Version, State0),
+ {next_state, StateName, State,
+ [{next_event, internal, {protocol_record, Record}} | Actions]};
+ #ssl_tls{epoch = _Epoch,
+ version = _Version} = _Record ->
+ %% TODO maybe buffer later epoch
+ next_event(StateName, no_record, State0, Actions);
+ #alert{} = Alert ->
+ Version = State0#state.connection_env#connection_env.negotiated_version,
+ handle_own_alert(Alert, Version, StateName, State0)
+ end.
+
+initial_flight_state(udp)->
+ {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT};
+initial_flight_state(_) ->
+ reliable.
+
+new_flight() ->
+ #{next_sequence => 0,
+ handshakes => [],
+ change_cipher_spec => undefined,
+ handshakes_after_change_cipher_spec => []}.
+
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := Flight,
+ change_cipher_spec := undefined},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0,
+ Epoch) ->
+ PMTUEstimate = 1400, %% TODO make configurable
+ #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
+ MaxSize = min(MaxFragmentLength, PMTUEstimate),
+ {Encoded, ConnectionStates} =
+ encode_handshake_flight(lists:reverse(Flight), Version, MaxSize, Epoch, ConnectionStates0),
+ send(Transport, Socket, Encoded),
+ ssl_logger:debug(LogLevel, outbound, 'record', Encoded),
+ {State0#state{connection_states = ConnectionStates}, []};
+
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := [_|_] = Flight0,
+ change_cipher_spec := ChangeCipher,
+ handshakes_after_change_cipher_spec := []},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0,
+ Epoch) ->
+ PMTUEstimate = 1400, %% TODO make configurable
+ #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
+ MaxSize = min(MaxFragmentLength, PMTUEstimate),
+ {HsBefore, ConnectionStates1} =
+ encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch, ConnectionStates0),
+ {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1),
+
+ send(Transport, Socket, [HsBefore, EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
+ {State0#state{connection_states = ConnectionStates}, []};
+
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := [_|_] = Flight0,
+ change_cipher_spec := ChangeCipher,
+ handshakes_after_change_cipher_spec := Flight1},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0,
+ Epoch) ->
+ PMTUEstimate = 1400, %% TODO make configurable
+ #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
+ MaxSize = min(MaxFragmentLength, PMTUEstimate),
+ {HsBefore, ConnectionStates1} =
+ encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch-1, ConnectionStates0),
+ {EncChangeCipher, ConnectionStates2} =
+ encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1),
+ {HsAfter, ConnectionStates} =
+ encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates2),
+ send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
+ {State0#state{connection_states = ConnectionStates}, []};
+
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := [],
+ change_cipher_spec := ChangeCipher,
+ handshakes_after_change_cipher_spec := Flight1},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0,
+ Epoch) ->
+ PMTUEstimate = 1400, %% TODO make configurable
+ #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0,
+ MaxSize = min(MaxFragmentLength, PMTUEstimate),
+ {EncChangeCipher, ConnectionStates1} =
+ encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0),
+ {HsAfter, ConnectionStates} =
+ encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates1),
+ send(Transport, Socket, [EncChangeCipher, HsAfter]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
+ {State0#state{connection_states = ConnectionStates}, []}.
+
+%%% DTLS record protocol level application data messages
+
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) ->
+ case ssl_gen_statem:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, State1} ->
+ {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1),
+ ssl_gen_statem:hibernate_after(StateName, State, Actions)
+ end;
+%%% DTLS record protocol level handshake messages
+handle_protocol_record(#ssl_tls{type = ?HANDSHAKE,
+ fragment = Data},
+ StateName,
+ #state{protocol_buffers = Buffers0,
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = Options} = State) ->
+ try
+ case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0, Options) of
+ {[], Buffers} ->
+ next_event(StateName, no_record, State#state{protocol_buffers = Buffers});
+ {Packets, Buffers} ->
+ HsEnv = State#state.handshake_env,
+ Events = dtls_handshake_events(Packets),
+ {next_state, StateName,
+ State#state{protocol_buffers = Buffers,
+ handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
+ end
+ catch throw:#alert{} = Alert ->
+ handle_own_alert(Alert, Version, StateName, State)
+ end;
+%%% DTLS record protocol level change cipher messages
+handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
+ {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
+%%% DTLS record protocol level Alert messages
+handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ case decode_alerts(EncAlerts) of
+ Alerts = [_|_] ->
+ handle_alerts(Alerts, {next_state, StateName, State});
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, StateName, State)
+ end;
+%% Ignore unknown TLS record level protocol messages
+handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
+ {next_state, StateName, State, []}.
+
+%%====================================================================
+%% Handshake handling
+%%====================================================================
+send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) ->
+ #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
+ send_handshake_flight(queue_handshake(Handshake, State), Epoch).
+
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := HsBuffer0,
+ change_cipher_spec := undefined,
+ next_sequence := Seq} = Flight0,
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
+ Hist = update_handshake_history(Handshake0, Handshake, Hist0),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0),
+
+ State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0],
+ next_sequence => Seq +1},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
+
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0,
+ next_sequence := Seq} = Flight0,
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
+ Hist = update_handshake_history(Handshake0, Handshake, Hist0),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0),
+
+ State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0],
+ next_sequence => Seq +1},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}.
+
+queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight,
+ connection_states = ConnectionStates0} = State) ->
+ ConnectionStates =
+ dtls_record:next_epoch(ConnectionStates0, write),
+ State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher},
+ connection_states = ConnectionStates}.
+
+reinit(State) ->
+ %% To be API compatible with TLS NOOP here
+ reinit_handshake_data(State).
+reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag},
+ protocol_buffers = Buffers,
+ protocol_specific = PS,
+ handshake_env = HsEnv} = State) ->
+ State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
+ public_key_info = undefined,
+ premaster_secret = undefined},
+ protocol_specific = PS#{flight_state => initial_flight_state(DataTag)},
+ flight_buffer = new_flight(),
+ protocol_buffers =
+ Buffers#protocol_buffers{
+ dtls_handshake_next_seq = 0,
+ dtls_handshake_next_fragments = [],
+ dtls_handshake_later_fragments = []
+ }}.
+
+select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
+ SNI;
+select_sni_extension(_) ->
+ undefined.
+
+empty_connection_state(ConnectionEnd, BeastMitigation) ->
+ Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation),
+ dtls_record:empty_connection_state(Empty).
+%%====================================================================
+%% Alert and close handling
+%%====================================================================
+encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
+ dtls_record:encode_alert_record(Alert, Version, ConnectionStates).
+
+send_alert(Alert, #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0,
+ ssl_options = #{log_level := LogLevel}} = State0) ->
+ {BinMsg, ConnectionStates} =
+ encode_alert(Alert, Version, ConnectionStates0),
+ send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
+ State0#state{connection_states = ConnectionStates}.
+
+send_alert_in_connection(Alert, State) ->
+ _ = send_alert(Alert, State),
+ ok.
+
+close(downgrade, _,_,_,_) ->
+ ok;
+%% Other
+close(_, Socket, Transport, _,_) ->
+ dtls_socket:close(Transport,Socket).
+
+protocol_name() ->
+ "DTLS".
+
+%%====================================================================
+%% Data handling
+%%====================================================================
+send(Transport, {Listener, Socket}, Data) when is_pid(Listener) ->
+ %% Server socket
+ dtls_socket:send(Transport, Socket, Data);
+send(Transport, Socket, Data) -> % Client socket
+ dtls_socket:send(Transport, Socket, Data).
+
+socket(Pid, Transport, Socket, _Tracker) ->
+ dtls_socket:socket(Pid, Transport, Socket, ?MODULE).
+
+setopts(Transport, Socket, Other) ->
+ dtls_socket:setopts(Transport, Socket, Other).
+
+getopts(Transport, Socket, Tag) ->
+ dtls_socket:getopts(Transport, Socket, Tag).
+
+%% raw data from socket, unpack records
+handle_info({Protocol, _, _, _, Data}, StateName,
+ #state{static_env = #static_env{role = Role,
+ data_tag = Protocol}} = State0) ->
+ case next_dtls_record(Data, StateName, State0) of
+ {Record, State} ->
+ next_event(StateName, Record, State);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
+ {stop, {shutdown, own_alert}, State0}
+ end;
+
+handle_info({PassiveTag, Socket}, StateName,
+ #state{static_env = #static_env{socket = {_, Socket},
+ passive_tag = PassiveTag},
+ protocol_specific = PS} = State) ->
+ next_event(StateName, no_record,
+ State#state{protocol_specific = PS#{active_n_toggle => true}});
+
+handle_info({CloseTag, Socket}, StateName,
+ #state{static_env = #static_env{
+ role = Role,
+ socket = Socket,
+ close_tag = CloseTag},
+ connection_env = #connection_env{negotiated_version = Version},
+ socket_options = #socket_options{active = Active},
+ protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs},
+ protocol_specific = PS} = State) ->
+ %% Note that as of DTLS 1.2 (TLS 1.1),
+ %% failure to properly close a connection no longer requires that a
+ %% session not be resumed. This is a change from DTLS 1.0 to conform
+ %% with widespread implementation practice.
+ case (Active == false) andalso (CTs =/= []) of
+ false ->
+ case Version of
+ {254, N} when N =< 253 ->
+ ok;
+ _ ->
+ %% As invalidate_sessions here causes performance issues,
+ %% we will conform to the widespread implementation
+ %% practice and go aginst the spec
+ %%invalidate_session(Role, Host, Port, Session)
+ ok
+ end,
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop, {shutdown, transport_closed}, State};
+ true ->
+ %% Fixes non-delivery of final DTLS record in {active, once}.
+ %% Basically allows the application the opportunity to set {active, once} again
+ %% and then receive the final message.
+ next_event(StateName, no_record, State#state{
+ protocol_specific = PS#{active_n_toggle => true}})
+ end;
+
+handle_info(new_cookie_secret, StateName,
+ #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) ->
+ erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
+ {next_state, StateName, State#state{protocol_specific =
+ CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(),
+ previous_cookie_secret => Secret}}};
+handle_info(Msg, StateName, State) ->
+ ssl_gen_statem:handle_info(Msg, StateName, State).
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+dtls_handshake_events(Packets) ->
+ lists:map(fun(Packet) ->
+ {next_event, internal, {handshake, Packet}}
+ end, Packets).
+
+unprocessed_events(Events) ->
+ %% The first handshake event will be processed immediately
+ %% as it is entered first in the event queue and
+ %% when it is processed there will be length(Events)-1
+ %% handshake events left to process before we should
+ %% process more TLS-records received on the socket.
+ erlang:length(Events)-1.
+
+encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) ->
+ Fragments = lists:map(fun(Handshake) ->
+ dtls_handshake:fragment_handshake(Handshake, MaxFragmentSize)
+ end, Flight),
+ dtls_record:encode_handshake(Fragments, Version, Epoch, ConnectionStates).
+
+encode_change_cipher(#change_cipher_spec{}, Version, Epoch, ConnectionStates) ->
+ dtls_record:encode_change_cipher_spec(Version, Epoch, ConnectionStates).
+
+update_handshake_history(#hello_verify_request{}, _, Hist) ->
+ Hist;
+update_handshake_history(_, Handshake, Hist) ->
+ ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake)).
+
+next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
+ dtls_record_buffer = Buf0,
+ dtls_cipher_texts = CT0} = Buffers,
+ connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{data_tag = DataTag},
+ ssl_options = SslOpts} = State0) ->
+ case dtls_record:get_dtls_records(Data,
+ {DataTag, StateName, Version,
+ [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]},
+ Buf0, SslOpts) of
+ {Records, Buf1} ->
+ CT1 = CT0 ++ Records,
+ next_record(State0#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_record_buffer = Buf1,
+ dtls_cipher_texts = CT1}});
+ #alert{} = Alert ->
+ Alert
+ end.
+
+
+
+decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers,
+ connection_states = ConnStates0} = State) ->
+ case dtls_record:decode_cipher_text(CT, ConnStates0) of
+ {Plain, ConnStates} ->
+ {Plain, State#state{protocol_buffers =
+ Buffers#protocol_buffers{dtls_cipher_texts = Rest},
+ connection_states = ConnStates}};
+ #alert{} = Alert ->
+ {Alert, State}
+ end.
+
+decode_alerts(Bin) ->
+ ssl_alert:decode(Bin).
+
+handle_alerts([], Result) ->
+ Result;
+handle_alerts(_, {stop, _, _} = Stop) ->
+ Stop;
+handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State));
+handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)).
+
+handle_own_alert(Alert, Version, StateName,
+ #state{static_env = #static_env{data_tag = udp,
+ role = Role},
+ ssl_options = #{log_level := LogLevel}} = State0) ->
+ case ignore_alert(Alert, State0) of
+ {true, State} ->
+ log_ignore_alert(LogLevel, StateName, Alert, Role),
+ {next_state, StateName, State};
+ {false, State} ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State)
+ end;
+handle_own_alert(Alert, Version, StateName, State) ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State).
+ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N,
+ max_ignored_alerts := N}} = State) ->
+ {false, State};
+ignore_alert(#alert{level = ?FATAL} = Alert,
+ #state{protocol_specific = #{ignored_alerts := N} = PS} = State) ->
+ case is_ignore_alert(Alert) of
+ true ->
+ {true, State#state{protocol_specific = PS#{ignored_alerts => N+1}}};
+ false ->
+ {false, State}
+ end;
+ignore_alert(_, State) ->
+ {false, State}.
+
+%% RFC 6347 4.1.2.7. Handling Invalid Records
+%% recommends to silently ignore invalid DTLS records when
+%% upd is the transport. Note we do not support compression so no need
+%% include ?DECOMPRESSION_FAILURE
+is_ignore_alert(#alert{description = ?BAD_RECORD_MAC}) ->
+ true;
+is_ignore_alert(#alert{description = ?RECORD_OVERFLOW}) ->
+ true;
+is_ignore_alert(#alert{description = ?DECODE_ERROR}) ->
+ true;
+is_ignore_alert(#alert{description = ?DECRYPT_ERROR}) ->
+ true;
+is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) ->
+ true;
+is_ignore_alert(_) ->
+ false.
+
+log_ignore_alert(Level, StateName, #alert{where = Location} = Alert, Role) ->
+ ssl_logger:log(info,
+ Level, #{alert => Alert,
+ alerter => ignored,
+ statename => StateName,
+ role => Role,
+ protocol => protocol_name()}, Location).
+
+dtls_version(hello, Version, #state{static_env = #static_env{role = server},
+ connection_env = CEnv} = State) ->
+ State#state{connection_env = CEnv#connection_env{negotiated_version = Version}}; %%Inital version
+dtls_version(_,_, State) ->
+ State.
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 892a76bd04..af053ef48c 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -30,16 +30,16 @@
-include("ssl_alert.hrl").
%% Handshake handling
--export([client_hello/7, client_hello/8, cookie/4, hello/4,
+-export([client_hello/7, client_hello/9, cookie/4, hello/5, hello/4,
hello_verify_request/2]).
-
+
%% Handshake encoding
-export([fragment_handshake/2, encode_handshake/3]).
%% Handshake decodeing
-export([get_dtls_handshake/4]).
--type dtls_handshake() :: #client_hello{} | #hello_verify_request{} |
+-type dtls_handshake() :: #client_hello{} | #hello_verify_request{} |
ssl_handshake:ssl_handshake().
%%====================================================================
@@ -47,20 +47,20 @@
%%====================================================================
%%--------------------------------------------------------------------
-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
- ssl_options(), binary(), boolean(), der_cert()) ->
+ ssl_options(), binary(), boolean(), [der_cert()]) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
%%--------------------------------------------------------------------
client_hello(Host, Port, ConnectionStates, SslOpts,
- Id, Renegotiation, OwnCert) ->
+ Id, Renegotiation, OwnCerts) ->
%% First client hello (two sent in DTLS ) uses empty Cookie
client_hello(Host, Port, <<>>, ConnectionStates, SslOpts,
- Id, Renegotiation, OwnCert).
+ Id, Renegotiation, OwnCerts, undefined).
%%--------------------------------------------------------------------
-spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(),
- ssl_options(), binary(),boolean(), der_cert()) ->
+ ssl_options(), binary(),boolean(), [der_cert()], binary() | undefined) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
@@ -69,7 +69,7 @@ client_hello(_Host, _Port, Cookie, ConnectionStates,
#{versions := Versions,
ciphers := UserSuites,
fallback := Fallback} = SslOpts,
- Id, Renegotiation, _OwnCert) ->
+ Id, Renegotiation, _OwnCert, OcspNonce) ->
Version = dtls_record:highest_protocol_version(Versions),
Pending = ssl_record:pending_connection_state(ConnectionStates, read),
SecParams = maps:get(security_parameters, Pending),
@@ -79,7 +79,7 @@ client_hello(_Host, _Port, Cookie, ConnectionStates,
Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites,
SslOpts, ConnectionStates,
Renegotiation, undefined,
- undefined),
+ undefined, OcspNonce),
#client_hello{session_id = Id,
client_version = Version,
@@ -97,15 +97,16 @@ hello(#server_hello{server_version = Version, random = Random,
compression_method = Compression,
session_id = SessionId, extensions = HelloExt},
#{versions := SupportedVersions} = SslOpt,
- ConnectionStates0, Renegotiation) ->
+ ConnectionStates0, Renegotiation, OldId) ->
+ IsNew = ssl_session:is_new(OldId, SessionId),
case dtls_record:is_acceptable_version(Version, SupportedVersions) of
true ->
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt,
- ConnectionStates0, Renegotiation);
+ ConnectionStates0, Renegotiation, IsNew);
false ->
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
- end;
+ end.
hello(#client_hello{client_version = ClientVersion} = Hello,
#{versions := Versions} = SslOpts,
Info, Renegotiation) ->
@@ -120,7 +121,7 @@ cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor},
CookieData = [address_to_bin(Address, Port),
<<?BYTE(Major), ?BYTE(Minor)>>,
Random, SessionId, CipherSuites, CompressionMethods],
- crypto:hmac(sha, Key, CookieData).
+ crypto:mac(hmac, sha, Key, CookieData).
%%--------------------------------------------------------------------
-spec hello_verify_request(binary(), ssl_record:ssl_version()) -> #hello_verify_request{}.
%%
@@ -173,27 +174,28 @@ handle_client_hello(Version,
signature_algs := SupportedHashSigns,
eccs := SupportedECCs,
honor_ecc_order := ECCOrder} = SslOpts,
- {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _},
+ {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _},
Renegotiation) ->
+ OwnCert = ssl_handshake:select_own_cert(OwnCerts),
case dtls_record:is_acceptable_version(Version, Versions) of
true ->
Curves = maps:get(elliptic_curves, HelloExt, undefined),
ClientHashSigns = maps:get(signature_algs, HelloExt, undefined),
TLSVersion = dtls_v1:corresponding_tls_version(Version),
AvailableHashSigns = ssl_handshake:available_signature_algs(
- ClientHashSigns, SupportedHashSigns, Cert,TLSVersion),
+ ClientHashSigns, SupportedHashSigns, OwnCert,TLSVersion),
ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
{Type, #session{cipher_suite = CipherSuite} = Session1}
= ssl_handshake:select_session(SugesstedId, CipherSuites,
AvailableHashSigns, Compressions,
- Port, Session0#session{ecc = ECCCurve}, TLSVersion,
- SslOpts, Cache, CacheCb, Cert),
+ SessIdTracker, Session0#session{ecc = ECCCurve}, TLSVersion,
+ SslOpts, OwnCert),
case CipherSuite of
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
_ ->
#{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, Cert, KeyExAlg,
+ case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, OwnCert, KeyExAlg,
SupportedHashSigns, TLSVersion) of
#alert{} = Alert ->
Alert;
@@ -212,7 +214,8 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
try ssl_handshake:handle_client_hello_extensions(dtls_record, Random, CipherSuites,
HelloExt, dtls_v1:corresponding_tls_version(Version),
SslOpts, Session0,
- ConnectionStates0, Renegotiation) of
+ ConnectionStates0, Renegotiation,
+ Session0#session.is_resumable) of
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}
catch throw:Alert ->
@@ -220,13 +223,13 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
end.
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
- Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
+ Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) ->
try ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
Compression, HelloExt,
dtls_v1:corresponding_tls_version(Version),
- SslOpt, ConnectionStates0, Renegotiation) of
- {ConnectionStates, ProtoExt, Protocol} ->
- {Version, SessionId, ConnectionStates, ProtoExt, Protocol}
+ SslOpt, ConnectionStates0, Renegotiation, IsNew) of
+ {ConnectionStates, ProtoExt, Protocol, OcspState} ->
+ {Version, SessionId, ConnectionStates, ProtoExt, Protocol, OcspState}
catch throw:Alert ->
Alert
end.
diff --git a/lib/ssl/src/dtls_listener_sup.erl b/lib/ssl/src/dtls_listener_sup.erl
index ab1c5eee20..4f46407290 100644
--- a/lib/ssl/src/dtls_listener_sup.erl
+++ b/lib/ssl/src/dtls_listener_sup.erl
@@ -30,8 +30,8 @@
%% API
-export([start_link/0]).
-export([start_child/1,
- lookup_listner/1,
- register_listner/2]).
+ lookup_listener/2,
+ register_listener/3]).
%% Supervisor callback
-export([init/1]).
@@ -44,32 +44,34 @@ start_link() ->
start_child(Args) ->
supervisor:start_child(?MODULE, Args).
-
-lookup_listner(0) ->
- undefined;
-lookup_listner(Port) ->
- try ets:lookup(dtls_listener_sup, Port) of
- [{Port, {Owner, Handler}}] ->
+
+lookup_listener(IP, Port) ->
+ try ets:lookup(dtls_listener_sup, {IP, Port}) of
+ [] ->
+ undefined;
+ [{{IP, Port}, {Owner, Handler}}] ->
case erlang:is_process_alive(Handler) of
true ->
- case (Owner =/= undefined) andalso erlang:is_process_alive(Owner) of
+ case (Owner =/= undefined) andalso
+ erlang:is_process_alive(Owner) of
true ->
+ %% Trying to bind port that is already bound
{error, already_listening};
false ->
+ %% Re-open same listen socket when the handler
+ %% is dead.
{ok, Handler}
end;
false ->
- ets:delete(dtls_listener_sup, Port),
+ ets:delete(dtls_listener_sup, {IP, Port}),
undefined
- end;
- [] ->
- undefined
+ end
catch _:_ ->
undefined
end.
-register_listner(OwnerAndListner, Port) ->
- ets:insert(dtls_listener_sup, {Port, OwnerAndListner}).
+register_listener(OwnerAndListner, IP, Port) ->
+ ets:insert(dtls_listener_sup, {{IP, Port}, OwnerAndListner}).
%%%=========================================================================
%%% Supervisor callback
diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index 892f29449d..f9660ea1b6 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -59,7 +59,8 @@
dtls_processes = kv_new(),
accepters = queue:new(),
first,
- close
+ close,
+ session_id_tracker
}).
%%%===================================================================
@@ -101,27 +102,19 @@ getstat(PacketSocket, Opts) ->
%%% gen_server callbacks
%%%===================================================================
-init([Port, {TransportModule, _,_,_,_} = TransportInfo, EmOpts, InetOptions, DTLSOptions]) ->
- try
- {ok, Socket} = TransportModule:open(Port, InetOptions),
- InternalActiveN = case application:get_env(ssl, internal_active_n) of
- {ok, N} when is_integer(N) ->
- N;
- _ ->
- ?INTERNAL_ACTIVE_N
- end,
-
- {ok, #state{active_n = InternalActiveN,
- port = Port,
- first = true,
- transport = TransportInfo,
- dtls_options = DTLSOptions,
- emulated_options = EmOpts,
- listener = Socket,
- close = false}}
- catch _:_ ->
- {stop, {shutdown, {error, closed}}}
- end.
+init([Port0, TransportInfo, EmOpts, DTLSOptions, Socket]) ->
+ InternalActiveN = get_internal_active_n(),
+ {ok, SessionIdHandle} = session_id_tracker(Socket, DTLSOptions),
+ {ok, #state{active_n = InternalActiveN,
+ port = Port0,
+ first = true,
+ transport = TransportInfo,
+ dtls_options = DTLSOptions,
+ emulated_options = EmOpts,
+ listener = Socket,
+ close = false,
+ session_id_tracker = SessionIdHandle}}.
+
handle_call({accept, _}, _, #state{close = true} = State) ->
{reply, {error, closed}, State};
@@ -279,9 +272,10 @@ setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes,
dtls_options = DTLSOpts,
port = Port,
listener = Socket,
+ session_id_tracker = Tracker,
emulated_options = EmOpts} = State) ->
ConnArgs = [server, "localhost", Port, {self(), {Client, Socket}},
- {DTLSOpts, EmOpts, dtls_listener}, User, dtls_socket:default_cb_info()],
+ {DTLSOpts, EmOpts, [{session_id_tracker, Tracker}]}, User, dtls_socket:default_cb_info()],
case dtls_connection_sup:start_child(ConnArgs) of
{ok, Pid} ->
erlang:monitor(process, Pid),
@@ -358,3 +352,17 @@ emulated_opts_list( Opts, [mode | Rest], Acc) ->
emulated_opts_list(Opts, [active | Rest], Acc) ->
emulated_opts_list(Opts, Rest, [{active, Opts#socket_options.active} | Acc]).
+%% Regardless of the option reuse_sessions we need the session_id_tracker
+%% to generate session ids, but no sessions will be stored unless
+%% reuse_sessions = true.
+session_id_tracker(Listner,_) ->
+ dtls_server_session_cache_sup:start_child(Listner).
+
+get_internal_active_n() ->
+ case application:get_env(ssl, internal_active_n) of
+ {ok, N} when is_integer(N) ->
+ N;
+ _ ->
+ ?INTERNAL_ACTIVE_N
+ end.
+
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index ee0ce2d22a..16542a8eb3 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -215,8 +215,26 @@ encode_change_cipher_spec(Version, Epoch, ConnectionStates) ->
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
encode_data(Data, Version, ConnectionStates) ->
- #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
- encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates).
+ #{epoch := Epoch, max_fragment_length := MaxFragmentLength}
+ = ssl_record:current_connection_state(ConnectionStates, write),
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
+ case iolist_size(Data) of
+ N when N > MaxLength ->
+ Frags = tls_record:split_iovec(erlang:iolist_to_iovec(Data), MaxLength),
+ {RevCipherText, ConnectionStates1} =
+ lists:foldl(fun(Frag, {Acc, CS0}) ->
+ {CipherText, CS1} =
+ encode_plain_text(?APPLICATION_DATA, Version, Epoch, Frag, CS0),
+ {[CipherText|Acc], CS1}
+ end, {[], ConnectionStates}, Frags),
+ {lists:reverse(RevCipherText), ConnectionStates1};
+ _ ->
+ encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates)
+ end.
encode_plain_text(Type, Version, Epoch, Data, ConnectionStates) ->
Write0 = get_connection_state_by_epoch(Epoch, ConnectionStates, write),
@@ -393,7 +411,8 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
mac_secret => undefined,
secure_renegotiation => undefined,
client_verify_data => undefined,
- server_verify_data => undefined
+ server_verify_data => undefined,
+ max_fragment_length => undefined
}.
get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
diff --git a/lib/ssl/src/dtls_server_session_cache_sup.erl b/lib/ssl/src/dtls_server_session_cache_sup.erl
new file mode 100644
index 0000000000..65fbb34918
--- /dev/null
+++ b/lib/ssl/src/dtls_server_session_cache_sup.erl
@@ -0,0 +1,63 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Supervisor for a listen options tracker
+%%----------------------------------------------------------------------
+-module(dtls_server_session_cache_sup).
+
+-behaviour(supervisor).
+
+-include("ssl_internal.hrl").
+
+%% API
+-export([start_link/0]).
+-export([start_child/1]).
+
+%% Supervisor callback
+-export([init/1]).
+
+-define(DEFAULT_MAX_SESSION_CACHE, 1000).
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+start_child(Listener) ->
+ supervisor:start_child(?MODULE, [Listener | [ssl_config:pre_1_3_session_opts(server)]]).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(_O) ->
+ RestartStrategy = simple_one_for_one,
+ MaxR = 0,
+ MaxT = 3600,
+
+ Name = undefined, % As simple_one_for_one is used.
+ StartFunc = {ssl_server_session_cache, start_link, []},
+ Restart = temporary, % E.g. should not be restarted
+ Shutdown = 4000,
+ Modules = [ssl_server_session_cache],
+ Type = worker,
+
+ ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
+ {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
diff --git a/lib/ssl/src/dtls_server_sup.erl b/lib/ssl/src/dtls_server_sup.erl
new file mode 100644
index 0000000000..7ec6db3984
--- /dev/null
+++ b/lib/ssl/src/dtls_server_sup.erl
@@ -0,0 +1,75 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(dtls_server_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+
+init([]) ->
+ DTLSListeners = dtls_listeners_spec(),
+ %% Add SessionTracker if we add DTLS-1.3
+ Pre_1_3SessionTracker = ssl_server_session_child_spec(),
+
+ {ok, {{one_for_all, 10, 3600}, [DTLSListeners,
+ Pre_1_3SessionTracker
+ ]}}.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+dtls_listeners_spec() ->
+ Name = dtls_listener,
+ StartFunc = {dtls_listener_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+ssl_server_session_child_spec() ->
+ Name = dtls_server_session_cache_sup,
+ StartFunc = {dtls_server_session_cache_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [dtls_server_session_cache_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl
index 87a4f7ce09..f1569f5069 100644
--- a/lib/ssl/src/dtls_socket.erl
+++ b/lib/ssl/src/dtls_socket.erl
@@ -46,32 +46,26 @@
send(Transport, {{IP,Port},Socket}, Data) ->
Transport:send(Socket, IP, Port, Data).
-listen(Port, #config{transport_info = TransportInfo,
- ssl = SslOpts,
- emulated = EmOpts0,
- inet_user = Options} = Config) ->
-
- Result = case dtls_listener_sup:lookup_listner(Port) of
- undefined ->
- Result0 = {ok, Listner0} = dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts0, #socket_options{}),
- Options ++ internal_inet_values(), SslOpts]),
- dtls_listener_sup:register_listner({self(), Listner0}, Port),
- Result0;
- {ok, Listner0} = Result0 ->
- dtls_packet_demux:new_owner(Listner0),
- dtls_packet_demux:set_all_opts(Listner0, {Options, emulated_socket_options(EmOpts0, #socket_options{}), SslOpts}),
- dtls_listener_sup:register_listner({self(), Listner0}, Port),
- Result0;
- Result0 ->
- Result0
- end,
- case Result of
- {ok, Listner} ->
- Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Listner, Port}}}},
- check_active_n(EmOpts0, Socket),
- {ok, Socket};
- Err ->
- Err
+listen(Port, #config{inet_ssl = SockOpts,
+ ssl = SslOpts,
+ emulated = EmOpts,
+ inet_user = Options} = Config) ->
+ IP = proplists:get_value(ip, SockOpts, {0,0,0,0}),
+ case dtls_listener_sup:lookup_listener(IP, Port) of
+ undefined ->
+ start_new_listener(IP, Port, Config);
+ {ok, Listener} ->
+ dtls_packet_demux:new_owner(Listener),
+ dtls_packet_demux:set_all_opts(
+ Listener, {Options,
+ emulated_socket_options(EmOpts,
+ #socket_options{}),
+ SslOpts}),
+ dtls_listener_sup:register_listener({self(), Listener},
+ IP, Port),
+ {ok, create_dtls_socket(Config, Listener, Port)};
+ Error ->
+ Error
end.
accept(dtls, #config{transport_info = {Transport,_,_,_,_},
@@ -91,7 +85,7 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo
inet_ssl = SocketOpts}, Timeout) ->
case Transport:open(0, SocketOpts ++ internal_inet_values()) of
{ok, Socket} ->
- ssl_connection:connect(ConnectionCb, Address, Port, {{Address, Port},Socket},
+ ssl_gen_statem:connect(ConnectionCb, Address, Port, {{Address, Port},Socket},
{SslOpts,
emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
@@ -99,8 +93,11 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo
Error
end.
-close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, Port}}}}) ->
- dtls_listener_sup:register_listner({undefined, Pid}, Port),
+close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, Port0},
+ inet_ssl = SockOpts}}}) ->
+ IP = proplists:get_value(ip, SockOpts, {0,0,0,0}),
+ Port = get_real_port(Pid, Port0),
+ dtls_listener_sup:register_listener({undefined, Pid}, IP, Port),
dtls_packet_demux:close(Pid).
close(_, dtls) ->
@@ -110,10 +107,11 @@ close(gen_udp, {_Client, _Socket}) ->
close(Transport, {_Client, Socket}) ->
Transport:close(Socket).
-socket(Pids, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) ->
+socket(Pids, gen_udp = Transport,
+ PeerAndSock = {{_Host, _Port}, _Socket}, ConnectionCb) ->
#sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
- fd = {Transport, Socket, ConnectionCb}};
+ fd = {Transport, PeerAndSock, ConnectionCb}};
socket(Pids, Transport, Socket, ConnectionCb) ->
#sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
@@ -178,14 +176,19 @@ getstat(gen_udp, Pid, Options) when is_pid(Pid) ->
dtls_packet_demux:getstat(Pid, Options);
getstat(gen_udp, {_,{_, Socket}}, Options) ->
inet:getstat(Socket, Options);
+getstat(gen_udp, {_, Socket}, Options) ->
+ inet:getstat(Socket, Options);
getstat(gen_udp, Socket, Options) ->
inet:getstat(Socket, Options);
getstat(Transport, Socket, Options) ->
Transport:getstat(Socket, Options).
+
peername(_, undefined) ->
{error, enotconn};
peername(gen_udp, {_, {Client, _Socket}}) ->
{ok, Client};
+peername(gen_udp, {Client, _Socket}) ->
+ {ok, Client};
peername(Transport, Socket) ->
Transport:peername(Socket).
sockname(gen_udp, {_, {_,Socket}}) ->
@@ -269,3 +272,59 @@ validate_inet_option(active, Value)
throw({error, {options, {active,Value}}});
validate_inet_option(_, _) ->
ok.
+
+get_real_port(Listener, Port0) when is_pid(Listener) andalso
+ is_integer(Port0) ->
+ case Port0 of
+ 0 ->
+ {ok, {_, NewPort}} = dtls_packet_demux:sockname(Listener),
+ NewPort;
+ _ ->
+ Port0
+ end.
+
+start_new_listener(IP, Port0,
+ #config{transport_info = {TransportModule, _,_,_,_},
+ inet_user = Options} = Config) ->
+ InetOptions = Options ++ internal_inet_values(),
+ case TransportModule:open(Port0, InetOptions) of
+ {ok, Socket} ->
+ Port = case Port0 of
+ 0 ->
+ {ok, P} = inet:port(Socket),
+ P;
+ _ ->
+ Port0
+ end,
+ start_dtls_packet_demux(Config, IP, Port, Socket);
+ {error, eaddrinuse} ->
+ {error, already_listening};
+ Error ->
+ Error
+ end.
+
+start_dtls_packet_demux(#config{
+ transport_info =
+ {TransportModule, _,_,_,_} = TransportInfo,
+ emulated = EmOpts0,
+ ssl = SslOpts} = Config, IP, Port, Socket) ->
+ EmOpts = emulated_socket_options(EmOpts0, #socket_options{}),
+ case dtls_listener_sup:start_child([Port, TransportInfo, EmOpts,
+ SslOpts, Socket]) of
+ {ok, Multiplexer} ->
+ ok = TransportModule:controlling_process(Socket, Multiplexer),
+ dtls_listener_sup:register_listener({self(), Multiplexer},
+ IP, Port),
+ DTLSSocket = create_dtls_socket(Config, Multiplexer, Port),
+ {ok, DTLSSocket};
+ Error ->
+ Error
+ end.
+
+create_dtls_socket(#config{emulated = EmOpts} = Config,
+ Listener, Port) ->
+ Socket = #sslsocket{
+ pid = {dtls, Config#config{dtls_handler = {Listener, Port}}}},
+ check_active_n(EmOpts, Socket),
+ Socket.
+
diff --git a/lib/ssl/src/dtls_sup.erl b/lib/ssl/src/dtls_sup.erl
index 2e72c10ba0..acc4415a9f 100644
--- a/lib/ssl/src/dtls_sup.erl
+++ b/lib/ssl/src/dtls_sup.erl
@@ -44,17 +44,25 @@ start_link() ->
%%%=========================================================================
init([]) ->
- DTLSConnetionManager = dtls_connection_manager_child_spec(),
- DTLSListeners = dtls_listeners_spec(),
+ DTLSConnectionManager = dtls_connection_manager_child_spec(),
+ DTLSServers = dtls_server_spec(),
- {ok, {{one_for_one, 10, 3600}, [DTLSConnetionManager,
- DTLSListeners
+ {ok, {{one_for_one, 10, 3600}, [DTLSConnectionManager,
+ DTLSServers
]}}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+dtls_server_spec() ->
+ Name = dtls_servers,
+ StartFunc = {dtls_server_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [dtls_server_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
dtls_connection_manager_child_spec() ->
Name = dtls_connection,
@@ -66,11 +74,3 @@ dtls_connection_manager_child_spec() ->
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-dtls_listeners_spec() ->
- Name = dtls_listener,
- StartFunc = {dtls_listener_sup, start_link, []},
- Restart = permanent,
- Shutdown = 4000,
- Modules = [],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/inet6_tls_dist.erl b/lib/ssl/src/inet6_tls_dist.erl
index 96ce4d493a..5ca0cd6904 100644
--- a/lib/ssl/src/inet6_tls_dist.erl
+++ b/lib/ssl/src/inet6_tls_dist.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,8 +22,8 @@
-module(inet6_tls_dist).
-export([childspecs/0]).
--export([listen/1, accept/1, accept_connection/5,
- setup/5, close/1, select/1]).
+-export([listen/2, accept/1, accept_connection/5,
+ setup/5, close/1, select/1, address/0]).
childspecs() ->
inet_tls_dist:childspecs().
@@ -31,8 +31,11 @@ childspecs() ->
select(Node) ->
inet_tls_dist:gen_select(inet6_tcp, Node).
-listen(Name) ->
- inet_tls_dist:gen_listen(inet6_tcp, Name).
+address() ->
+ inet_tls_dist:gen_address(inet6_tcp).
+
+listen(Name, Host) ->
+ inet_tls_dist:gen_listen(inet6_tcp, Name, Host).
accept(Listen) ->
inet_tls_dist:gen_accept(inet6_tcp, Listen).
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index 8d9b92361b..eaa481f119 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,12 +22,12 @@
-module(inet_tls_dist).
-export([childspecs/0]).
--export([listen/1, accept/1, accept_connection/5,
- setup/5, close/1, select/1, is_node_name/1]).
+-export([listen/2, accept/1, accept_connection/5,
+ setup/5, close/1, select/1, address/0, is_node_name/1]).
%% Generalized dist API
--export([gen_listen/2, gen_accept/2, gen_accept_connection/6,
- gen_setup/6, gen_close/2, gen_select/2]).
+-export([gen_listen/3, gen_accept/2, gen_accept_connection/6,
+ gen_setup/6, gen_close/2, gen_select/2, gen_address/1]).
-export([nodelay/0]).
@@ -63,6 +63,14 @@ gen_select(Driver, Node) ->
false
end.
+%% ------------------------------------------------------------
+%% Get the address family that this distribution uses
+%% ------------------------------------------------------------
+address() ->
+ gen_address(inet_tcp).
+gen_address(Driver) ->
+ inet_tcp_dist:gen_address(Driver).
+
%% -------------------------------------------------------------------------
is_node_name(Node) ->
@@ -193,11 +201,11 @@ split_stat([], R, W, P) ->
%% -------------------------------------------------------------------------
-listen(Name) ->
- gen_listen(inet_tcp, Name).
+listen(Name, Host) ->
+ gen_listen(inet_tcp, Name, Host).
-gen_listen(Driver, Name) ->
- case inet_tcp_dist:gen_listen(Driver, Name) of
+gen_listen(Driver, Name, Host) ->
+ case inet_tcp_dist:gen_listen(Driver, Name, Host) of
{ok, {Socket, Address, Creation}} ->
inet:setopts(Socket, [{packet, 4}, {nodelay, true}]),
{ok, {Socket, Address#net_address{protocol=tls}, Creation}};
@@ -758,8 +766,8 @@ nodelay() ->
get_ssl_options(Type) ->
try ets:lookup(ssl_dist_opts, Type) of
- [{Type, Opts}] ->
- [{erl_dist, true} | Opts];
+ [{Type, Opts0}] ->
+ [{erl_dist, true} | dist_defaults(Opts0)];
_ ->
get_ssl_dist_arguments(Type)
catch
@@ -770,11 +778,18 @@ get_ssl_options(Type) ->
get_ssl_dist_arguments(Type) ->
case init:get_argument(ssl_dist_opt) of
{ok, Args} ->
- [{erl_dist, true} | ssl_options(Type, lists:append(Args))];
+ [{erl_dist, true} | dist_defaults(ssl_options(Type, lists:append(Args)))];
_ ->
[{erl_dist, true}]
end.
+dist_defaults(Opts) ->
+ case proplists:get_value(versions, Opts, undefined) of
+ undefined ->
+ [{versions, ['tlsv1.2']} | Opts];
+ _ ->
+ Opts
+ end.
ssl_options(_Type, []) ->
[];
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index 90c96b2be1..78bbcc8c04 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -11,8 +11,8 @@
tls_record_1_3,
tls_socket,
tls_v1,
- ssl_v3,
tls_connection_sup,
+ tls_gen_connection,
tls_sender,
tls_server_sup,
tls_server_session_ticket_sup,
@@ -26,15 +26,19 @@
dtls_socket,
dtls_v1,
dtls_connection_sup,
+ dtls_gen_connection,
dtls_packet_demux,
dtls_listener_sup,
dtls_sup,
+ dtls_server_sup,
+ dtls_server_session_cache_sup,
%% API
ssl, %% Main API
ssl_session_cache_api,
%% Both TLS/SSL and DTLS
+ tls_dtls_connection,
ssl_config,
- ssl_connection,
+ ssl_gen_statem,
ssl_handshake,
ssl_record,
ssl_cipher,
@@ -50,9 +54,15 @@
ssl_dist_sup,
ssl_dist_connection_sup,
ssl_dist_admin_sup,
+ tls_dist_sup,
+ tls_dist_server_sup,
%% SSL/TLS session and cert handling
ssl_session,
- ssl_session_cache,
+ ssl_client_session_cache_db,
+ ssl_server_session_cache,
+ ssl_server_session_cache_db,
+ ssl_server_session_cache_sup,
+ ssl_upgrade_server_session_cache_sup,
ssl_manager,
ssl_pem_cache,
ssl_pkix_db,
@@ -74,5 +84,5 @@
{applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}},
- {runtime_dependencies, ["stdlib-3.5","public_key-1.7.2","kernel-6.0",
+ {runtime_dependencies, ["stdlib-3.12","public_key-1.8","kernel-6.0",
"erts-10.0","crypto-4.2", "inets-5.10.7"]}]}.
diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src
index ae4d60b6ed..b0faa738d3 100644
--- a/lib/ssl/src/ssl.appup.src
+++ b/lib/ssl/src/ssl.appup.src
@@ -1,6 +1,7 @@
%% -*- erlang -*-
{"%VSN%",
[
+ {<<"10\\..*">>, [{restart_application, ssl}]},
{<<"9\\..*">>, [{restart_application, ssl}]},
{<<"8\\..*">>, [{restart_application, ssl}]},
{<<"7\\..*">>, [{restart_application, ssl}]},
@@ -10,6 +11,7 @@
{<<"3\\..*">>, [{restart_application, ssl}]}
],
[
+ {<<"10\\..*">>, [{restart_application, ssl}]},
{<<"9\\..*">>, [{restart_application, ssl}]},
{<<"8\\..*">>, [{restart_application, ssl}]},
{<<"7\\..*">>, [{restart_application, ssl}]},
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index ca6b65e8db..f4f8f7cc9d 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -20,7 +20,8 @@
%%
-%%% Purpose : Main API module for SSL see also tls.erl and dtls.erl
+%%% Purpose : Main API module for the SSL application that implements TLS and DTLS
+%%% SSL is a legacy name.
-module(ssl).
@@ -28,12 +29,16 @@
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
--include("ssl_internal.hrl").
-include("ssl_record.hrl").
-include("ssl_cipher.hrl").
-include("ssl_handshake.hrl").
-include("ssl_srp.hrl").
+%% Needed to make documentation rendering happy
+-ifndef(VSN).
+-define(VSN,"unknown").
+-endif.
+
%% Application handling
-export([start/0,
start/1,
@@ -100,9 +105,15 @@
suite_to_openssl_str/1,
str_to_suite/1]).
--deprecated({ssl_accept, 1, eventually}).
--deprecated({ssl_accept, 2, eventually}).
--deprecated({ssl_accept, 3, eventually}).
+-deprecated({ssl_accept, '_', "use ssl_handshake/1,2,3 instead"}).
+
+-deprecated({cipher_suites, 0, "use cipher_suites/2,3 instead"}).
+-deprecated({cipher_suites, 1, "use cipher_suites/2,3 instead"}).
+
+-removed([{negotiated_next_protocol,1,
+ "use ssl:negotiated_protocol/1 instead"}]).
+-removed([{connection_info,1,
+ "use ssl:connection_information/[1,2] instead"}]).
-export_type([socket/0,
sslsocket/0,
@@ -153,7 +164,7 @@
-type protocol_version() :: tls_version() | dtls_version(). % exported
-type tls_version() :: 'tlsv1.2' | 'tlsv1.3' | tls_legacy_version().
-type dtls_version() :: 'dtlsv1.2' | dtls_legacy_version().
--type tls_legacy_version() :: tlsv1 | 'tlsv1.1' | sslv3.
+-type tls_legacy_version() :: tlsv1 | 'tlsv1.1' .
-type dtls_legacy_version() :: 'dtlsv1'.
-type verify_type() :: verify_none | verify_peer.
-type cipher() :: aes_128_cbc |
@@ -290,7 +301,7 @@
%% -------------------------------------------------------------------------------------------------------
-type common_option() :: {protocol, protocol()} |
{handshake, handshake_completion()} |
- {cert, cert()} |
+ {cert, cert() | [cert()]} |
{certfile, cert_pem()} |
{key, key()} |
{keyfile, key_pem()} |
@@ -300,6 +311,7 @@
{signature_algs_cert, signature_schemes()} |
{supported_groups, supported_groups()} |
{secure_renegotiate, secure_renegotiation()} |
+ {keep_secrets, keep_secrets()} |
{depth, allowed_cert_chain_length()} |
{verify_fun, custom_verify()} |
{crl_check, crl_check()} |
@@ -315,7 +327,8 @@
{beast_mitigation, beast_mitigation()} |
{ssl_imp, ssl_imp()} |
{session_tickets, session_tickets()} |
- {key_update_at, key_update_at()}.
+ {key_update_at, key_update_at()} |
+ {middlebox_comp_mode, middlebox_comp_mode()}.
-type protocol() :: tls | dtls.
-type handshake_completion() :: hello | full.
@@ -335,12 +348,15 @@
-type cipher_filters() :: list({key_exchange | cipher | mac | prf,
algo_filter()}). % exported
-type algo_filter() :: fun((kex_algo()|cipher()|hash()|aead|default_prf) -> true | false).
+-type keep_secrets() :: boolean().
-type secure_renegotiation() :: boolean().
-type allowed_cert_chain_length() :: integer().
-type custom_verify() :: {Verifyfun :: fun(), InitialUserState :: any()}.
-type crl_check() :: boolean() | peer | best_effort.
--type crl_cache_opts() :: [any()].
+-type crl_cache_opts() :: {Module :: atom(),
+ {DbHandle :: internal | term(),
+ Args :: list()}}.
-type handshake_size() :: integer().
-type hibernate_after() :: timeout().
-type root_fun() :: fun().
@@ -354,7 +370,7 @@
-type srp_identity() :: {Username :: string(), Password :: string()}.
-type psk_identity() :: string().
-type log_alert() :: boolean().
--type logging_level() :: logger:level().
+-type logging_level() :: logger:level() | none | all.
-type client_session_tickets() :: disabled | manual | auto.
-type server_session_tickets() :: disabled | stateful | stateless.
-type session_tickets() :: client_session_tickets() | server_session_tickets().
@@ -367,6 +383,9 @@
bloom_filter_hash_functions(), %% k - number of hash functions
bloom_filter_bits()}. %% m - number of bits in bit vector
-type use_ticket() :: [binary()].
+-type middlebox_comp_mode() :: boolean().
+-type client_early_data() :: binary().
+-type server_early_data() :: disabled | enabled.
%% -------------------------------------------------------------------------------------------------------
@@ -380,14 +399,19 @@
{psk_identity, client_psk_identity()} |
{srp_identity, client_srp_identity()} |
{server_name_indication, sni()} |
+ {max_fragment_length, max_fragment_length()} |
{customize_hostname_check, customize_hostname_check()} |
{signature_algs, client_signature_algs()} |
{fallback, fallback()} |
{session_tickets, client_session_tickets()} |
- {use_ticket, use_ticket()}.
+ {use_ticket, use_ticket()} |
+ {early_data, client_early_data()}.
+ %% {ocsp_stapling, ocsp_stapling()} |
+ %% {ocsp_responder_certs, ocsp_responder_certs()} |
+ %% {ocsp_nonce, ocsp_nonce()}.
-type client_verify_type() :: verify_type().
--type client_reuse_session() :: session_id().
+-type client_reuse_session() :: session_id() | {session_id(), SessionData::binary()}.
-type client_reuse_sessions() :: boolean() | save.
-type client_cacerts() :: [public_key:der_encoded()].
-type client_cafile() :: file:filename().
@@ -402,9 +426,13 @@
-type client_srp_identity() :: srp_identity().
-type customize_hostname_check() :: list().
-type sni() :: HostName :: hostname() | disable.
+-type max_fragment_length() :: undefined | 512 | 1024 | 2048 | 4096.
-type client_signature_algs() :: signature_algs().
-type fallback() :: boolean().
-type ssl_imp() :: new | old.
+%% -type ocsp_stapling() :: boolean().
+%% -type ocsp_responder_certs() :: [public_key:der_encoded()].
+%% -type ocsp_nonce() :: boolean().
%% -------------------------------------------------------------------------------------------------------
@@ -427,7 +455,9 @@
{client_renegotiation, client_renegotiation()}|
{signature_algs, server_signature_algs()} |
{session_tickets, server_session_tickets()} |
- {anti_replay, anti_replay()}.
+ {anti_replay, anti_replay()} |
+ {cookie, cookie()} |
+ {early_data, server_early_data()}.
-type server_cacerts() :: [public_key:der_encoded()].
-type server_cafile() :: file:filename().
@@ -446,6 +476,7 @@
-type honor_cipher_order() :: boolean().
-type honor_ecc_order() :: boolean().
-type client_renegotiation() :: boolean().
+-type cookie() :: boolean().
%% -------------------------------------------------------------------------------------------------------
-type prf_random() :: client_random | server_random. % exported
-type protocol_extensions() :: #{renegotiation_info => binary(),
@@ -453,10 +484,38 @@
alpn => app_level_protocol(),
srp => binary(),
next_protocol => app_level_protocol(),
+ max_frag_enum => 1..4,
ec_point_formats => [0..2],
elliptic_curves => [public_key:oid()],
sni => hostname()}. % exported
%% -------------------------------------------------------------------------------------------------------
+-type connection_info() :: [common_info() | curve_info() | ssl_options_info() | security_info()].
+-type common_info() :: {protocol, protocol_version()} |
+ {session_id, session_id()} |
+ {session_resumption, boolean()} |
+ {selected_cipher_suite, erl_cipher_suite()} |
+ {sni_hostname, term()} |
+ {srp_username, term()}.
+-type curve_info() :: {ecc, {named_curve, term()}}.
+-type ssl_options_info() :: tls_option().
+-type security_info() :: {client_random, binary()} |
+ {server_random, binary()} |
+ {master_secret, binary()}.
+-type connection_info_items() :: [connection_info_item()].
+-type connection_info_item() :: protocol |
+ session_id |
+ session_resumption |
+ selected_cipher_suite |
+ sni_hostname |
+ srp_username |
+ ecc |
+ client_random |
+ server_random |
+ master_secret |
+ keylog |
+ tls_options_name().
+-type tls_options_name() :: atom().
+%% -------------------------------------------------------------------------------------------------------
%%%--------------------------------------------------------------------
%%% API
@@ -499,7 +558,7 @@ stop() ->
TCPSocket :: socket(),
TLSOptions :: [tls_client_option()].
-connect(Socket, SslOptions) when is_port(Socket) ->
+connect(Socket, SslOptions) ->
connect(Socket, SslOptions, infinity).
-spec connect(TCPSocket, TLSOptions, Timeout) ->
@@ -516,24 +575,21 @@ connect(Socket, SslOptions) when is_port(Socket) ->
Port :: inet:port_number(),
TLSOptions :: [tls_client_option()].
-connect(Socket, SslOptions0, Timeout) when is_port(Socket),
+connect(Socket, SslOptions0, Timeout) when is_list(SslOptions0) andalso
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+
CbInfo = handle_option_cb_info(SslOptions0, tls),
-
Transport = element(1, CbInfo),
- EmulatedOptions = tls_socket:emulated_options(),
- {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
- try handle_options(SslOptions0 ++ SocketValues, client) of
- {ok, Config} ->
- tls_socket:upgrade(Socket, Config, Timeout)
+ try handle_options(Transport, Socket, SslOptions0, client, undefined) of
+ {ok, Config} ->
+ tls_socket:upgrade(Socket, Config, Timeout)
catch
- _:{error, Reason} ->
+ _:{error, Reason} ->
{error, Reason}
- end;
+ end;
connect(Host, Port, Options) ->
connect(Host, Port, Options, infinity).
-
-spec connect(Host, Port, TLSOptions, Timeout) ->
{ok, sslsocket()} |
{ok, sslsocket(),Ext :: protocol_extensions()} |
@@ -548,9 +604,9 @@ connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout
try
{ok, Config} = handle_options(Options, client, Host),
case Config#config.connection_cb of
- tls_connection ->
+ tls_gen_connection ->
tls_socket:connect(Host,Port,Config,Timeout);
- dtls_connection ->
+ dtls_gen_connection ->
dtls_socket:connect(Host,Port,Config,Timeout)
end
catch
@@ -599,9 +655,9 @@ transport_accept(#sslsocket{pid = {ListenSocket,
#config{connection_cb = ConnectionCb} = Config}}, Timeout)
when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
case ConnectionCb of
- tls_connection ->
+ tls_gen_connection ->
tls_socket:accept(ListenSocket, Config, Timeout);
- dtls_connection ->
+ dtls_gen_connection ->
dtls_socket:accept(ListenSocket, Config, Timeout)
end.
@@ -680,7 +736,7 @@ handshake(ListenSocket) ->
handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
(Timeout == infinity) ->
- ssl_connection:handshake(Socket, Timeout);
+ ssl_gen_statem:handshake(Socket, Timeout);
%% If Socket is a ordinary socket(): upgrades a gen_tcp, or equivalent, socket to
%% an SSL socket, that is, performs the SSL/TLS server-side handshake and returns
@@ -688,9 +744,8 @@ handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Tim
%%
%% If Socket is an sslsocket(): provides extra SSL/TLS/DTLS options to those
%% specified in ssl:listen/2 and then performs the SSL/TLS/DTLS handshake.
-handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
+handshake(ListenSocket, SslOptions) ->
handshake(ListenSocket, SslOptions, infinity).
-
-spec handshake(Socket, Options, Timeout) ->
{ok, SslSocket} |
{ok, SslSocket, Ext} |
@@ -710,7 +765,7 @@ handshake(#sslsocket{fd = {_, _, _, Trackers}} = Socket, SslOpts, Timeout) when
try
Tracker = proplists:get_value(option_tracker, Trackers),
{ok, EmOpts, _} = tls_socket:get_all_opts(Tracker),
- ssl_connection:handshake(Socket, {SslOpts,
+ ssl_gen_statem:handshake(Socket, {SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
catch
Error = {error, _Reason} -> Error
@@ -719,31 +774,28 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout)
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
try
{ok, EmOpts, _} = dtls_packet_demux:get_all_opts(Pid),
- ssl_connection:handshake(Socket, {SslOpts,
+ ssl_gen_statem:handshake(Socket, {SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
catch
Error = {error, _Reason} -> Error
end;
-handshake(Socket, SslOptions, Timeout) when is_port(Socket),
- (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+handshake(Socket, SslOptions, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
CbInfo = handle_option_cb_info(SslOptions, tls),
-
Transport = element(1, CbInfo),
- EmulatedOptions = tls_socket:emulated_options(),
- {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
ConnetionCb = connection_cb(SslOptions),
- try handle_options(SslOptions ++ SocketValues, server) of
- {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
- ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
- {ok, Port} = tls_socket:port(Transport, Socket),
- ssl_connection:handshake(ConnetionCb, Port, Socket,
+ try handle_options(Transport, Socket, SslOptions, server, undefined) of
+ {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
+ ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
+ {ok, Port} = tls_socket:port(Transport, Socket),
+ {ok, SessionIdHandle} = tls_socket:session_id_tracker(ssl_unknown_listener, SslOpts),
+ ssl_gen_statem:handshake(ConnetionCb, Port, Socket,
{SslOpts,
- tls_socket:emulated_socket_options(EmOpts, #socket_options{}), undefined},
+ tls_socket:emulated_socket_options(EmOpts, #socket_options{}),
+ [{session_id_tracker, SessionIdHandle}]},
self(), CbInfo, Timeout)
catch
- Error = {error, _Reason} -> Error
- end.
-
+ Error = {error, _Reason} -> Error
+ end.
%%--------------------------------------------------------------------
-spec handshake_continue(HsSocket, Options) ->
@@ -771,14 +823,14 @@ handshake_continue(Socket, SSLOptions) ->
%% Description: Continues the handshke possible with newly supplied options.
%%--------------------------------------------------------------------
handshake_continue(Socket, SSLOptions, Timeout) ->
- ssl_connection:handshake_continue(Socket, SSLOptions, Timeout).
+ ssl_gen_statem:handshake_continue(Socket, SSLOptions, Timeout).
%%--------------------------------------------------------------------
-spec handshake_cancel(#sslsocket{}) -> any().
%%
%% Description: Cancels the handshakes sending a close alert.
%%--------------------------------------------------------------------
handshake_cancel(Socket) ->
- ssl_connection:handshake_cancel(Socket).
+ ssl_gen_statem:handshake_cancel(Socket).
%%--------------------------------------------------------------------
-spec close(SslSocket) -> ok | {error, Reason} when
@@ -788,7 +840,7 @@ handshake_cancel(Socket) ->
%% Description: Close an ssl connection
%%--------------------------------------------------------------------
close(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT});
+ ssl_gen_statem:close(Pid, {close, ?DEFAULT_TIMEOUT});
close(#sslsocket{pid = {dtls, #config{dtls_handler = {_, _}}}} = DTLSListen) ->
dtls_socket:close(DTLSListen);
close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}) ->
@@ -806,7 +858,7 @@ close(#sslsocket{pid = [TLSPid|_]},
{Pid, Timeout} = DownGrade) when is_pid(TLSPid),
is_pid(Pid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- case ssl_connection:close(TLSPid, {close, DownGrade}) of
+ case ssl_gen_statem:close(TLSPid, {close, DownGrade}) of
ok -> %% In normal close {error, closed} is regarded as ok, as it is not interesting which side
%% that got to do the actual close. But in the downgrade case only {ok, Port} is a sucess.
{error, closed};
@@ -815,7 +867,7 @@ close(#sslsocket{pid = [TLSPid|_]},
end;
close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- ssl_connection:close(TLSPid, {close, Timeout});
+ ssl_gen_statem:close(TLSPid, {close, Timeout});
close(#sslsocket{pid = {dtls = ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
dtls_socket:close(Transport, ListenSocket);
close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
@@ -829,7 +881,7 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}
%% Description: Sends data over the ssl connection
%%--------------------------------------------------------------------
send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) ->
- ssl_connection:send(Pid, Data);
+ ssl_gen_statem:send(Pid, Data);
send(#sslsocket{pid = [_, Pid]}, Data) when is_pid(Pid) ->
tls_sender:send_data(Pid, erlang:iolist_to_iovec(Data));
send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) ->
@@ -862,7 +914,7 @@ recv(Socket, Length) ->
recv(#sslsocket{pid = [Pid|_]}, Length, Timeout) when is_pid(Pid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
- ssl_connection:recv(Pid, Length, Timeout);
+ ssl_gen_statem:recv(Pid, Length, Timeout);
recv(#sslsocket{pid = {dtls,_}}, _, _) ->
{error,enotconn};
recv(#sslsocket{pid = {Listen,
@@ -880,7 +932,7 @@ recv(#sslsocket{pid = {Listen,
%% or once.
%%--------------------------------------------------------------------
controlling_process(#sslsocket{pid = [Pid|_]}, NewOwner) when is_pid(Pid), is_pid(NewOwner) ->
- ssl_connection:new_user(Pid, NewOwner);
+ ssl_gen_statem:new_user(Pid, NewOwner);
controlling_process(#sslsocket{pid = {dtls, _}},
NewOwner) when is_pid(NewOwner) ->
ok; %% Meaningless but let it be allowed to conform with TLS
@@ -895,14 +947,12 @@ controlling_process(#sslsocket{pid = {Listen,
%%--------------------------------------------------------------------
-spec connection_information(SslSocket) -> {ok, Result} | {error, reason()} when
SslSocket :: sslsocket(),
- Result :: [{OptionName, OptionValue}],
- OptionName :: atom(),
- OptionValue :: any().
+ Result :: connection_info().
%%
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
connection_information(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- case ssl_connection:connection_information(Pid, false) of
+ case ssl_gen_statem:connection_information(Pid, false) of
{ok, Info} ->
{ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]};
Error ->
@@ -916,15 +966,13 @@ connection_information(#sslsocket{pid = {dtls,_}}) ->
%%--------------------------------------------------------------------
-spec connection_information(SslSocket, Items) -> {ok, Result} | {error, reason()} when
SslSocket :: sslsocket(),
- Items :: [OptionName],
- Result :: [{OptionName, OptionValue}],
- OptionName :: atom(),
- OptionValue :: any().
+ Items :: connection_info_items(),
+ Result :: connection_info().
%%
%% Description: Return SSL information for the connection
%%--------------------------------------------------------------------
connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) ->
- case ssl_connection:connection_information(Pid, include_security_info(Items)) of
+ case ssl_gen_statem:connection_information(Pid, include_security_info(Items)) of
{ok, Info} ->
{ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items),
Value =/= undefined]};
@@ -960,7 +1008,7 @@ peername(#sslsocket{pid = {dtls,_}}) ->
%% Description: Returns the peercert.
%%--------------------------------------------------------------------
peercert(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- case ssl_connection:peer_certificate(Pid) of
+ case ssl_gen_statem:peer_certificate(Pid) of
{ok, undefined} ->
{error, no_peercert};
Result ->
@@ -981,7 +1029,7 @@ peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
%% protocol has been negotiated will return {error, protocol_not_negotiated}
%%--------------------------------------------------------------------
negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
- ssl_connection:negotiated_protocol(Pid).
+ ssl_gen_statem:negotiated_protocol(Pid).
%%--------------------------------------------------------------------
-spec cipher_suites() -> [old_cipher_suite()] | [string()].
@@ -1005,47 +1053,46 @@ cipher_suites(all) ->
[ssl_cipher_format:suite_legacy(Suite) || Suite <- available_suites(all)].
%%--------------------------------------------------------------------
--spec cipher_suites(Supported, Version) -> ciphers() when
- Supported :: default | all | anonymous,
+-spec cipher_suites(Description, Version) -> ciphers() when
+ Description :: default | all | exclusive | anonymous,
Version :: protocol_version().
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
%%--------------------------------------------------------------------
-cipher_suites(Base, Version) when Version == 'tlsv1.3';
+cipher_suites(Description, Version) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'tlsv1.1';
- Version == tlsv1;
- Version == sslv3 ->
- cipher_suites(Base, tls_record:protocol_version(Version));
-cipher_suites(Base, Version) when Version == 'dtlsv1.2';
+ Version == tlsv1 ->
+ cipher_suites(Description, tls_record:protocol_version(Version));
+cipher_suites(Description, Version) when Version == 'dtlsv1.2';
Version == 'dtlsv1'->
- cipher_suites(Base, dtls_record:protocol_version(Version));
-cipher_suites(Base, Version) ->
- [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Base, Version)].
+ cipher_suites(Description, dtls_record:protocol_version(Version));
+cipher_suites(Description, Version) ->
+ [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Description, Version)].
%%--------------------------------------------------------------------
--spec cipher_suites(Supported, Version, rfc | openssl) -> [string()] when
- Supported :: default | all | anonymous,
+-spec cipher_suites(Description, Version, rfc | openssl) -> [string()] when
+ Description :: default | all | exclusive | anonymous,
Version :: protocol_version().
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
%%--------------------------------------------------------------------
-cipher_suites(Base, Version, StringType) when Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == tlsv1;
- Version == sslv3 ->
- cipher_suites(Base, tls_record:protocol_version(Version), StringType);
-cipher_suites(Base, Version, StringType) when Version == 'dtlsv1.2';
+cipher_suites(Description, Version, StringType) when Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == tlsv1 ->
+ cipher_suites(Description, tls_record:protocol_version(Version), StringType);
+cipher_suites(Description, Version, StringType) when Version == 'dtlsv1.2';
Version == 'dtlsv1'->
- cipher_suites(Base, dtls_record:protocol_version(Version), StringType);
-cipher_suites(Base, Version, rfc) ->
- [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite))
- || Suite <- supported_suites(Base, Version)];
-cipher_suites(Base, Version, openssl) ->
- [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite))
- || Suite <- supported_suites(Base, Version)].
+ cipher_suites(Description, dtls_record:protocol_version(Version), StringType);
+cipher_suites(Description, Version, rfc) ->
+ [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite))
+ || Suite <- supported_suites(Description, Version)];
+cipher_suites(Description, Version, openssl) ->
+ [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite))
+ || Suite <- supported_suites(Description, Version)].
%%--------------------------------------------------------------------
-spec filter_cipher_suites(Suites, Filters) -> Ciphers when
@@ -1076,7 +1123,7 @@ filter_cipher_suites(Suites, Filters0) ->
%% Description: Make <Preferred> suites become the most prefered
%% suites that is put them at the head of the cipher suite list
%% and remove them from <Suites> if present. <Preferred> may be a
-%% list of cipher suits or a list of filters in which case the
+%% list of cipher suites or a list of filters in which case the
%% filters are use on Suites to extract the the preferred
%% cipher list.
%% --------------------------------------------------------------------
@@ -1121,8 +1168,6 @@ eccs() ->
%% Description: returns the curves supported for a given version of
%% ssl/tls.
%%--------------------------------------------------------------------
-eccs(sslv3) ->
- [];
eccs('dtlsv1') ->
eccs('tlsv1.1');
eccs('dtlsv1.2') ->
@@ -1161,7 +1206,7 @@ groups(default) ->
%% Description: Gets options
%%--------------------------------------------------------------------
getopts(#sslsocket{pid = [Pid|_]}, OptionTags) when is_pid(Pid), is_list(OptionTags) ->
- ssl_connection:get_opts(Pid, OptionTags);
+ ssl_gen_statem:get_opts(Pid, OptionTags);
getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) ->
try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of
{ok, _} = Result ->
@@ -1199,11 +1244,11 @@ setopts(#sslsocket{pid = [Pid, Sender]}, Options0) when is_pid(Pid), is_list(Opt
Options ->
case proplists:get_value(packet, Options, undefined) of
undefined ->
- ssl_connection:set_opts(Pid, Options);
+ ssl_gen_statem:set_opts(Pid, Options);
PacketOpt ->
case tls_sender:setopts(Sender, [{packet, PacketOpt}]) of
ok ->
- ssl_connection:set_opts(Pid, Options);
+ ssl_gen_statem:set_opts(Pid, Options);
Error ->
Error
end
@@ -1216,7 +1261,7 @@ setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0)
try proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Options0) of
Options ->
- ssl_connection:set_opts(Pid, Options)
+ ssl_gen_statem:set_opts(Pid, Options)
catch
_:_ ->
{error, {options, {not_a_proplist, Options0}}}
@@ -1292,7 +1337,7 @@ shutdown(#sslsocket{pid = {Listen, #config{transport_info = Info}}},
shutdown(#sslsocket{pid = {dtls,_}},_) ->
{error, enotconn};
shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) ->
- ssl_connection:shutdown(Pid, How).
+ ssl_gen_statem:shutdown(Pid, How).
%%--------------------------------------------------------------------
-spec sockname(SslSocket) ->
@@ -1315,23 +1360,37 @@ sockname(#sslsocket{pid = [Pid| _], fd = {Transport, Socket,_,_}}) when is_pid(P
%%---------------------------------------------------------------
-spec versions() -> [VersionInfo] when
VersionInfo :: {ssl_app, string()} |
- {supported | available, [tls_version()]} |
- {supported_dtls | available_dtls, [dtls_version()]}.
+ {supported | available | implemented, [tls_version()]} |
+ {supported_dtls | available_dtls | implemented_dtls, [dtls_version()]}.
%%
%% Description: Returns a list of relevant versions.
%%--------------------------------------------------------------------
versions() ->
- TLSVsns = tls_record:supported_protocol_versions(),
- DTLSVsns = dtls_record:supported_protocol_versions(),
- SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- TLSVsns],
- SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- DTLSVsns],
- AvailableTLSVsns = ?ALL_AVAILABLE_VERSIONS,
- AvailableDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS,
- [{ssl_app, "9.2"}, {supported, SupportedTLSVsns},
+ ConfTLSVsns = tls_record:supported_protocol_versions(),
+ ConfDTLSVsns = dtls_record:supported_protocol_versions(),
+ ImplementedTLSVsns = ?ALL_AVAILABLE_VERSIONS,
+ ImplementedDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS,
+
+ TLSCryptoSupported = fun(Vsn) ->
+ tls_record:sufficient_crypto_support(Vsn)
+ end,
+ DTLSCryptoSupported = fun(Vsn) ->
+ tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(Vsn))
+ end,
+ SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- ConfTLSVsns, TLSCryptoSupported(Vsn)],
+ SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- ConfDTLSVsns, DTLSCryptoSupported(Vsn)],
+
+ AvailableTLSVsns = [Vsn || Vsn <- ImplementedTLSVsns, TLSCryptoSupported(tls_record:protocol_version(Vsn))],
+ AvailableDTLSVsns = [Vsn || Vsn <- ImplementedDTLSVsns, DTLSCryptoSupported(dtls_record:protocol_version(Vsn))],
+
+ [{ssl_app, ?VSN},
+ {supported, SupportedTLSVsns},
{supported_dtls, SupportedDTLSVsns},
{available, AvailableTLSVsns},
- {available_dtls, AvailableDTLSVsns}].
-
+ {available_dtls, AvailableDTLSVsns},
+ {implemented, ImplementedTLSVsns},
+ {implemented_dtls, ImplementedDTLSVsns}
+ ].
%%---------------------------------------------------------------
-spec renegotiate(SslSocket) -> ok | {error, reason()} when
@@ -1343,12 +1402,12 @@ renegotiate(#sslsocket{pid = [Pid, Sender |_]}) when is_pid(Pid),
is_pid(Sender) ->
case tls_sender:renegotiate(Sender) of
{ok, Write} ->
- tls_connection:renegotiation(Pid, Write);
+ tls_dtls_connection:renegotiation(Pid, Write);
Error ->
Error
end;
renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) ->
- ssl_connection:renegotiation(Pid);
+ tls_dtls_connection:renegotiation(Pid);
renegotiate(#sslsocket{pid = {dtls,_}}) ->
{error, enotconn};
renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) ->
@@ -1372,7 +1431,7 @@ update_keys(#sslsocket{pid = [Pid, Sender |_]}, Type0) when is_pid(Pid) andalso
read_write ->
update_requested
end,
- tls_connection:send_key_update(Sender, Type);
+ tls_connection_1_3:send_key_update(Sender, Type);
update_keys(_, Type) ->
{error, {illegal_parameter, Type}}.
@@ -1389,7 +1448,7 @@ update_keys(_, Type) ->
%%--------------------------------------------------------------------
prf(#sslsocket{pid = [Pid|_]},
Secret, Label, Seed, WantedLength) when is_pid(Pid) ->
- ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength);
+ tls_dtls_connection:prf(Pid, Secret, Label, Seed, WantedLength);
prf(#sslsocket{pid = {dtls,_}}, _,_,_,_) ->
{error, enotconn};
prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) ->
@@ -1499,6 +1558,8 @@ available_suites(all) ->
Version = tls_record:highest_protocol_version([]),
ssl_cipher:filter_suites(ssl_cipher:all_suites(Version)).
+supported_suites(exclusive, {3,Minor}) ->
+ tls_v1:exclusive_suites(Minor);
supported_suites(default, Version) ->
ssl_cipher:suites(Version);
supported_suites(all, Version) ->
@@ -1506,49 +1567,41 @@ supported_suites(all, Version) ->
supported_suites(anonymous, Version) ->
ssl_cipher:anonymous_suites(Version).
-do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_connection) ->
+do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_gen_connection) ->
tls_socket:listen(Transport, Port, Config);
-do_listen(Port, Config, dtls_connection) ->
+do_listen(Port, Config, dtls_gen_connection) ->
dtls_socket:listen(Port, Config).
-
-
-spec handle_options([any()], client | server) -> {ok, #config{}};
([any()], ssl_options()) -> ssl_options().
handle_options(Opts, Role) ->
- handle_options(Opts, Role, undefined).
+ handle_options(undefined, undefined, Opts, Role, undefined).
+handle_options(Opts, Role, InheritedSslOpts) ->
+ handle_options(undefined, undefined, Opts, Role, InheritedSslOpts).
%% Handle ssl options at handshake, handshake_continue
-handle_options(Opts0, Role, InheritedSslOpts) when is_map(InheritedSslOpts) ->
+handle_options(_, _, Opts0, Role, InheritedSslOpts) when is_map(InheritedSslOpts) ->
{SslOpts, _} = expand_options(Opts0, ?RULES),
process_options(SslOpts, InheritedSslOpts, #{role => Role,
rules => ?RULES});
%% Handle all options in listen, connect and handshake
-handle_options(Opts0, Role, Host) ->
- {SslOpts0, SockOpts} = expand_options(Opts0, ?RULES),
-
+handle_options(Transport, Socket, Opts0, Role, Host) ->
+ {SslOpts0, SockOpts0} = expand_options(Opts0, ?RULES),
+
%% Ensure all options are evaluated at startup
SslOpts1 = add_missing_options(SslOpts0, ?RULES),
- SslOpts = #{protocol := Protocol,
- versions := Versions}
+ SslOpts = #{protocol := Protocol}
= process_options(SslOpts1,
#{},
#{role => Role,
host => Host,
rules => ?RULES}),
-
- case Versions of
- [{3, 0}] ->
- reject_alpn_next_prot_options(SslOpts0);
- _ ->
- ok
- end,
-
+
%% Handle special options
- {Sock, Emulated} = emulated_options(Protocol, SockOpts),
+ {Sock, Emulated} = emulated_options(Transport, Socket, Protocol, SockOpts0),
ConnetionCb = connection_cb(Protocol),
CbInfo = handle_option_cb_info(Opts0, Protocol),
@@ -1598,7 +1651,9 @@ handle_option(anti_replay = Option, unbound, OptionsMap, #{rules := Rules}) ->
Value = validate_option(Option, default_value(Option, Rules)),
OptionsMap#{Option => Value};
handle_option(anti_replay = Option, Value0,
- #{session_tickets := SessionTickets} = OptionsMap, #{rules := Rules}) ->
+ #{session_tickets := SessionTickets,
+ versions := Versions} = OptionsMap, #{rules := Rules}) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
assert_option_dependency(Option, session_tickets, [SessionTickets], [stateless]),
case SessionTickets of
stateless ->
@@ -1607,6 +1662,13 @@ handle_option(anti_replay = Option, Value0,
_ ->
OptionsMap#{Option => default_value(Option, Rules)}
end;
+handle_option(beast_mitigation = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(beast_mitigation = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts,
verify := Verify,
verify_fun := VerifyFun} = OptionsMap, _Env)
@@ -1625,19 +1687,48 @@ handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts,
handle_option(cacertfile = Option, Value0, OptionsMap, _Env) ->
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
-handle_option(ciphers = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := Rules}) ->
- Value = handle_cipher_option(default_value(Option, Rules), HighestVersion),
+handle_option(ciphers = Option, unbound, #{versions := Versions} = OptionsMap, #{rules := Rules}) ->
+ Value = handle_cipher_option(default_value(Option, Rules), Versions),
OptionsMap#{Option => Value};
-handle_option(ciphers = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
- Value = handle_cipher_option(Value0, HighestVersion),
+handle_option(ciphers = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
+ Value = handle_cipher_option(Value0, Versions),
OptionsMap#{Option => Value};
handle_option(client_renegotiation = Option, unbound, OptionsMap, #{role := Role}) ->
Value = default_option_role(server, true, Role),
OptionsMap#{Option => Value};
-handle_option(client_renegotiation = Option, Value0, OptionsMap, #{role := Role}) ->
+handle_option(client_renegotiation = Option, Value0,
+ #{versions := Versions} = OptionsMap, #{role := Role}) ->
assert_role(server_only, Role, Option, Value0),
+ assert_option_dependency(Option, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
+handle_option(early_data = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(early_data = Option, Value0, #{session_tickets := SessionTickets,
+ versions := Versions} = OptionsMap,
+ #{role := server = Role}) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
+ assert_option_dependency(Option, session_tickets, [SessionTickets],
+ [stateful, stateless]),
+ Value = validate_option(Option, Value0, Role),
+ OptionsMap#{Option => Value};
+handle_option(early_data = Option, Value0, #{session_tickets := SessionTickets,
+ use_ticket := UseTicket,
+ versions := Versions} = OptionsMap,
+ #{role := client = Role}) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
+ assert_option_dependency(Option, session_tickets, [SessionTickets],
+ [manual, auto]),
+ case UseTicket of
+ undefined when SessionTickets =/= auto ->
+ throw({error, {options, dependency, {Option, use_ticket}}});
+ _ ->
+ ok
+ end,
+ Value = validate_option(Option, Value0, Role),
+ OptionsMap#{Option => Value};
handle_option(eccs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) ->
Value = handle_eccs_option(eccs(), HighestVersion),
OptionsMap#{Option => Value};
@@ -1651,6 +1742,14 @@ handle_option(fallback = Option, Value0, OptionsMap, #{role := Role}) ->
assert_role(client_only, Role, Option, Value0),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
+handle_option(cookie = Option, unbound, OptionsMap, #{role := Role}) ->
+ Value = default_option_role(server, true, Role),
+ OptionsMap#{Option => Value};
+handle_option(cookie = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
+ assert_role(server_only, Role, Option, Value0),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(honor_cipher_order = Option, unbound, OptionsMap, #{role := Role}) ->
Value = default_option_role(server, false, Role),
OptionsMap#{Option => Value};
@@ -1675,13 +1774,50 @@ handle_option(key_update_at = Option, Value0, #{versions := Versions} = OptionsM
assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
+handle_option(next_protocols_advertised = Option, unbound, OptionsMap,
+ #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(next_protocols_advertised = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(next_protocols_advertised, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(next_protocol_selector = Option, unbound, OptionsMap, #{rules := Rules}) ->
Value = default_value(Option, Rules),
OptionsMap#{Option => Value};
-handle_option(next_protocol_selector = Option, Value0, OptionsMap, _Env) ->
+handle_option(next_protocol_selector = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(client_preferred_next_protocols, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
Value = make_next_protocol_selector(
validate_option(client_preferred_next_protocols, Value0)),
OptionsMap#{Option => Value};
+handle_option(padding_check = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(padding_check = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(psk_identity = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(psk_identity = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(secure_renegotiate = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(secure_renegotiate= Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(secure_renegotiate, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) ->
Value =
case Role of
@@ -1691,14 +1827,20 @@ handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) ->
fun(_, _, _, _) -> true end
end,
OptionsMap#{Option => Value};
-handle_option(reuse_session = Option, Value0, OptionsMap, _Env) ->
+handle_option(reuse_session = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(reuse_session, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
%% TODO: validate based on role
handle_option(reuse_sessions = Option, unbound, OptionsMap, #{rules := Rules}) ->
Value = validate_option(Option, default_value(Option, Rules)),
OptionsMap#{Option => Value};
-handle_option(reuse_sessions = Option, Value0, OptionsMap, _Env) ->
+handle_option(reuse_sessions = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(reuse_sessions, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
handle_option(server_name_indication = Option, unbound, OptionsMap, #{host := Host,
@@ -1708,13 +1850,13 @@ handle_option(server_name_indication = Option, unbound, OptionsMap, #{host := Ho
handle_option(server_name_indication = Option, Value0, OptionsMap, _Env) ->
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
-handle_option(session_tickets = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
+handle_option(session_tickets = Option, unbound, OptionsMap, #{role := Role,
+ rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules), Role),
OptionsMap#{Option => Value};
handle_option(session_tickets = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) ->
assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- assert_role_value(Role, Option, Value0, [disabled, stateful, stateless], [disabled, manual, auto]),
- Value = validate_option(Option, Value0),
+ Value = validate_option(Option, Value0, Role),
OptionsMap#{Option => Value};
handle_option(signature_algs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{role := Role}) ->
Value =
@@ -1752,25 +1894,48 @@ handle_option(sni_fun = Option, Value0, OptionsMap, _Env) ->
throw({error, {conflict_options, [sni_fun, sni_hosts]}})
end,
OptionsMap#{Option => Value};
+handle_option(srp_identity = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(srp_identity = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(srp_identity, versions, Versions,
+ ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(supported_groups = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) ->
Value = handle_supported_groups_option(groups(default), HighestVersion),
OptionsMap#{Option => Value};
-handle_option(supported_groups = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
+handle_option(supported_groups = Option, Value0,
+ #{versions := [HighestVersion|_] = Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
Value = handle_supported_groups_option(Value0, HighestVersion),
OptionsMap#{Option => Value};
+handle_option(use_ticket = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(use_ticket = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(user_lookup_fun = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(user_lookup_fun = Option, Value0,
+ #{versions := Versions} = OptionsMap, _Env) ->
+ assert_option_dependency(Option, versions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
handle_option(verify = Option, unbound, OptionsMap, #{rules := Rules}) ->
handle_verify_option(default_value(Option, Rules), OptionsMap);
handle_option(verify = _Option, Value, OptionsMap, _Env) ->
handle_verify_option(Value, OptionsMap);
-
handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, #{rules := Rules})
- when Verify =:= verify_none orelse
- Verify =:= 0 ->
+ when Verify =:= verify_none ->
OptionsMap#{Option => default_value(Option, Rules)};
handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, _Env)
- when Verify =:= verify_peer orelse
- Verify =:= 1 orelse
- Verify =:= 2 ->
+ when Verify =:= verify_peer ->
OptionsMap#{Option => undefined};
handle_option(verify_fun = Option, Value0, OptionsMap, _Env) ->
Value = validate_option(Option, Value0),
@@ -1815,13 +1980,11 @@ maybe_map_key_internal(client_preferred_next_protocols) ->
maybe_map_key_internal(K) ->
K.
-
maybe_map_key_external(next_protocol_selector) ->
client_preferred_next_protocols;
maybe_map_key_external(K) ->
K.
-
check_dependencies(K, OptionsMap, Env) ->
Rules = maps:get(rules, Env),
Deps = get_dependencies(K, Rules),
@@ -1854,10 +2017,10 @@ dependecies_already_defined(L, OptionsMap) ->
expand_options(Opts0, Rules) ->
Opts1 = proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Opts0),
- assert_proplist(Opts1),
+ Opts2 = handle_option_format(Opts1, []),
%% Remove depricated ssl_imp option
- Opts = proplists:delete(ssl_imp, Opts1),
+ Opts = proplists:delete(ssl_imp, Opts2),
AllOpts = maps:keys(Rules),
SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) end,
Opts,
@@ -1866,8 +2029,9 @@ expand_options(Opts0, Rules) ->
cb_info,
client_preferred_next_protocols, %% next_protocol_selector
log_alert]), %% obsoleted by log_level
-
- SslOpts = {Opts -- SockOpts, [], length(Opts -- SockOpts)},
+
+ SslOpts0 = Opts -- SockOpts,
+ SslOpts = {SslOpts0, [], length(SslOpts0)},
{SslOpts, SockOpts}.
@@ -1886,7 +2050,6 @@ add_missing_options({L0, S, _C}, Rules) ->
L = lists:foldl(Fun, L0, AllOpts),
{L, S, length(L)}.
-
default_value(Key, Rules) ->
{Default, _} = maps:get(Key, Rules, {undefined, []}),
Default.
@@ -1903,204 +2066,236 @@ assert_role(server_only, _, _, undefined) ->
assert_role(Type, _, Key, _) ->
throw({error, {option, Type, Key}}).
-
-assert_role_value(client, Option, Value, _, ClientValues) ->
- case lists:member(Value, ClientValues) of
- true ->
- ok;
- false ->
- %% throw({error, {option, client, Option, Value, ClientValues}})
- throw({error, {options, role, {Option, {Value, {client, ClientValues}}}}})
- end;
-assert_role_value(server, Option, Value, ServerValues, _) ->
- case lists:member(Value, ServerValues) of
- true ->
- ok;
- false ->
- %% throw({error, {option, server, Option, Value, ServerValues}})
- throw({error, {options, role, {Option, {Value, {server, ServerValues}}}}})
- end.
-
-
assert_option_dependency(Option, OptionDep, Values0, AllowedValues) ->
- %% special handling for version
- Values =
- case OptionDep of
- versions ->
- lists:map(fun tls_record:protocol_version/1, Values0);
- _ ->
- Values0
- end,
- Set1 = sets:from_list(Values),
- Set2 = sets:from_list(AllowedValues),
- case sets:size(sets:intersection(Set1, Set2)) > 0 of
+ case is_dtls_configured(Values0) of
true ->
+ %% TODO: Check option dependency for DTLS
ok;
false ->
- %% Message = build_error_message(Option, OptionDep, AllowedValues),
- %% throw({error, {options, Message}})
- throw({error, {options, dependency, {Option, {OptionDep, AllowedValues}}}})
+ %% special handling for version
+ Values =
+ case OptionDep of
+ versions ->
+ lists:map(fun tls_record:protocol_version/1, Values0);
+ _ ->
+ Values0
+ end,
+ Set1 = sets:from_list(Values),
+ Set2 = sets:from_list(AllowedValues),
+ case sets:size(sets:intersection(Set1, Set2)) > 0 of
+ true ->
+ ok;
+ false ->
+ throw({error, {options, dependency,
+ {Option, {OptionDep, AllowedValues}}}})
+ end
end.
+is_dtls_configured(Versions) ->
+ Fun = fun (Version) when Version =:= {254, 253} orelse
+ Version =:= {254, 255} ->
+ true;
+ (_) ->
+ false
+ end,
+ lists:any(Fun, Versions).
-validate_option(versions, Versions) ->
- validate_versions(Versions, Versions);
-validate_option(verify, Value)
- when Value == verify_none; Value == verify_peer ->
+validate_option(Option, Value) ->
+ validate_option(Option, Value, undefined).
+%%
+validate_option(Opt, Value, _)
+ when Opt =:= alpn_advertised_protocols orelse
+ Opt =:= alpn_preferred_protocols,
+ is_list(Value) ->
+ validate_binary_list(Opt, Value),
Value;
-validate_option(verify_fun, undefined) ->
+validate_option(Opt, Value, _)
+ when Opt =:= alpn_advertised_protocols orelse
+ Opt =:= alpn_preferred_protocols,
+ Value =:= undefined ->
undefined;
-%% Backwards compatibility
-validate_option(verify_fun, Fun) when is_function(Fun) ->
- {fun(_,{bad_cert, _} = Reason, OldFun) ->
- case OldFun([Reason]) of
- true ->
- {valid, OldFun};
- false ->
- {fail, Reason}
- end;
- (_,{extension, _}, UserState) ->
- {unknown, UserState};
- (_, valid, UserState) ->
- {valid, UserState};
- (_, valid_peer, UserState) ->
- {valid, UserState}
- end, Fun};
-validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) ->
- Value;
-validate_option(partial_chain, Value) when is_function(Value) ->
+validate_option(anti_replay, '10k', _) ->
+ %% n = 10000
+ %% p = 0.030003564 (1 in 33)
+ %% m = 72985 (8.91KiB)
+ %% k = 5
+ {10, 5, 72985};
+validate_option(anti_replay, '100k', _) ->
+ %% n = 100000
+ %% p = 0.03000428 (1 in 33)
+ %% m = 729845 (89.09KiB)
+ %% k = 5
+ {10, 5, 729845};
+validate_option(anti_replay, Value, _)
+ when (is_tuple(Value) andalso
+ tuple_size(Value) =:= 3) ->
Value;
-validate_option(fail_if_no_peer_cert, Value) when is_boolean(Value) ->
+validate_option(beast_mitigation, Value, _)
+ when Value == one_n_minus_one orelse
+ Value == zero_n orelse
+ Value == disabled ->
+ Value;
+%% certfile must be present in some cases otherwhise it can be set
+%% to the empty string.
+validate_option(cacertfile, undefined, _) ->
+ <<>>;
+validate_option(cacertfile, Value, _)
+ when is_binary(Value) ->
+ Value;
+validate_option(cacertfile, Value, _)
+ when is_list(Value), Value =/= ""->
+ binary_filename(Value);
+validate_option(cacerts, Value, _)
+ when Value == undefined;
+ is_list(Value) ->
Value;
-validate_option(verify_client_once, Value) when is_boolean(Value) ->
+validate_option(cb_info, {V1, V2, V3, V4} = Value, _)
+ when is_atom(V1),
+ is_atom(V2),
+ is_atom(V3),
+ is_atom(V4) ->
Value;
-validate_option(depth, Value) when is_integer(Value),
- Value >= 0, Value =< 255->
+validate_option(cb_info, {V1, V2, V3, V4, V5} = Value, _)
+ when is_atom(V1),
+ is_atom(V2),
+ is_atom(V3),
+ is_atom(V4),
+ is_atom(V5) ->
Value;
-validate_option(cert, Value) when Value == undefined;
- is_binary(Value) ->
+validate_option(cert, Value, _) when Value == undefined;
+ is_list(Value)->
Value;
-validate_option(certfile, undefined = Value) ->
+validate_option(cert, Value, _) when Value == undefined;
+ is_binary(Value)->
+ [Value];
+validate_option(certfile, undefined = Value, _) ->
Value;
-validate_option(certfile, Value) when is_binary(Value) ->
+validate_option(certfile, Value, _)
+ when is_binary(Value) ->
Value;
-validate_option(certfile, Value) when is_list(Value) ->
+validate_option(certfile, Value, _)
+ when is_list(Value) ->
binary_filename(Value);
-
-validate_option(key, undefined) ->
+validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols}, _)
+ when is_list(PreferredProtocols) ->
+ validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
+ validate_npn_ordering(Precedence),
+ {Precedence, PreferredProtocols, ?NO_PROTOCOL};
+validate_option(client_preferred_next_protocols,
+ {Precedence, PreferredProtocols, Default} = Value, _)
+ when is_list(PreferredProtocols), is_binary(Default),
+ byte_size(Default) > 0, byte_size(Default) < 256 ->
+ validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
+ validate_npn_ordering(Precedence),
+ Value;
+validate_option(client_preferred_next_protocols, undefined, _) ->
undefined;
-validate_option(key, {KeyType, Value}) when is_binary(Value),
- KeyType == rsa; %% Backwards compatibility
- KeyType == dsa; %% Backwards compatibility
- KeyType == 'RSAPrivateKey';
- KeyType == 'DSAPrivateKey';
- KeyType == 'ECPrivateKey';
- KeyType == 'PrivateKeyInfo' ->
- {KeyType, Value};
-validate_option(key, #{algorithm := _} = Value) ->
+validate_option(client_renegotiation, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(keyfile, undefined) ->
- <<>>;
-validate_option(keyfile, Value) when is_binary(Value) ->
+validate_option(cookie, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(keyfile, Value) when is_list(Value), Value =/= "" ->
- binary_filename(Value);
-validate_option(key_update_at, Value) when is_integer(Value) andalso
- Value > 0 ->
+validate_option(crl_cache, {Cb, {_Handle, Options}} = Value, _)
+ when is_atom(Cb) and is_list(Options) ->
Value;
-validate_option(password, Value) when is_list(Value) ->
+validate_option(crl_check, Value, _)
+ when is_boolean(Value) ->
Value;
-
-validate_option(cacerts, Value) when Value == undefined;
- is_list(Value) ->
+validate_option(crl_check, Value, _)
+ when (Value == best_effort) or
+ (Value == peer) ->
Value;
-%% certfile must be present in some cases otherwhise it can be set
-%% to the empty string.
-validate_option(cacertfile, undefined) ->
- <<>>;
-validate_option(cacertfile, Value) when is_binary(Value) ->
+validate_option(customize_hostname_check, Value, _)
+ when is_list(Value) ->
Value;
-validate_option(cacertfile, Value) when is_list(Value), Value =/= ""->
- binary_filename(Value);
-validate_option(dh, Value) when Value == undefined;
- is_binary(Value) ->
+validate_option(depth, Value, _)
+ when is_integer(Value),
+ Value >= 0, Value =< 255->
Value;
-validate_option(dhfile, undefined = Value) ->
+validate_option(dh, Value, _)
+ when Value == undefined;
+ is_binary(Value) ->
Value;
-validate_option(dhfile, Value) when is_binary(Value) ->
+validate_option(dhfile, undefined = Value, _) ->
Value;
-validate_option(dhfile, Value) when is_list(Value), Value =/= "" ->
+validate_option(dhfile, Value, _)
+ when is_binary(Value) ->
+ Value;
+validate_option(dhfile, Value, _)
+ when is_list(Value), Value =/= "" ->
binary_filename(Value);
-validate_option(psk_identity, undefined) ->
- undefined;
-validate_option(psk_identity, Identity)
- when is_list(Identity), Identity =/= "", length(Identity) =< 65535 ->
- binary_filename(Identity);
-validate_option(user_lookup_fun, undefined) ->
- undefined;
-validate_option(user_lookup_fun, {Fun, _} = Value) when is_function(Fun, 3) ->
- Value;
-validate_option(srp_identity, undefined) ->
- undefined;
-validate_option(srp_identity, {Username, Password})
- when is_list(Username), is_list(Password), Username =/= "", length(Username) =< 255 ->
- {unicode:characters_to_binary(Username),
- unicode:characters_to_binary(Password)};
-
-validate_option(reuse_session, undefined) ->
- undefined;
-validate_option(reuse_session, Value) when is_function(Value) ->
+validate_option(early_data, Value, server)
+ when Value =:= disabled orelse
+ Value =:= enabled ->
Value;
-validate_option(reuse_session, Value) when is_binary(Value) ->
+validate_option(early_data = Option, Value, server) ->
+ throw({error,
+ {options, role, {Option, {Value, {server, [disabled, enabled]}}}}});
+validate_option(early_data, Value, client)
+ when is_binary(Value) ->
Value;
-validate_option(reuse_sessions, Value) when is_boolean(Value) ->
+validate_option(early_data = Option, Value, client) ->
+ throw({error,
+ {options, type, {Option, {Value, not_binary}}}});
+validate_option(erl_dist, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(reuse_sessions, save = Value) ->
+validate_option(fail_if_no_peer_cert, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(secure_renegotiate, Value) when is_boolean(Value) ->
+validate_option(fallback, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(client_renegotiation, Value) when is_boolean(Value) ->
+validate_option(handshake, hello = Value, _) ->
Value;
-validate_option(renegotiate_at, Value) when is_integer(Value) ->
- erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT);
-
-validate_option(hibernate_after, undefined) -> %% Backwards compatibility
+validate_option(handshake, full = Value, _) ->
+ Value;
+validate_option(hibernate_after, undefined, _) -> %% Backwards compatibility
infinity;
-validate_option(hibernate_after, infinity) ->
+validate_option(hibernate_after, infinity, _) ->
infinity;
-validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 ->
+validate_option(hibernate_after, Value, _)
+ when is_integer(Value), Value >= 0 ->
Value;
-
-validate_option(erl_dist,Value) when is_boolean(Value) ->
+validate_option(honor_cipher_order, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(Opt, Value) when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols,
- is_list(Value) ->
- validate_binary_list(Opt, Value),
+validate_option(honor_ecc_order, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(Opt, Value)
- when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols,
- Value =:= undefined ->
- undefined;
-validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols})
- when is_list(PreferredProtocols) ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
- {Precedence, PreferredProtocols, ?NO_PROTOCOL};
-validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols, Default} = Value)
- when is_list(PreferredProtocols), is_binary(Default),
- byte_size(Default) > 0, byte_size(Default) < 256 ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
+validate_option(keep_secrets, Value, _) when is_boolean(Value) ->
Value;
-validate_option(client_preferred_next_protocols, undefined) ->
+validate_option(key, undefined, _) ->
undefined;
-validate_option(log_alert, true) ->
- notice;
-validate_option(log_alert, false) ->
- warning;
-validate_option(log_level, Value) when
+validate_option(key, {KeyType, Value}, _)
+ when is_binary(Value),
+ KeyType == rsa; %% Backwards compatibility
+ KeyType == dsa; %% Backwards compatibility
+ KeyType == 'RSAPrivateKey';
+ KeyType == 'DSAPrivateKey';
+ KeyType == 'ECPrivateKey';
+ KeyType == 'PrivateKeyInfo' ->
+ {KeyType, Value};
+validate_option(key, #{algorithm := _} = Value, _) ->
+ Value;
+validate_option(keyfile, undefined, _) ->
+ <<>>;
+validate_option(keyfile, Value, _)
+ when is_binary(Value) ->
+ Value;
+validate_option(keyfile, Value, _)
+ when is_list(Value), Value =/= "" ->
+ binary_filename(Value);
+validate_option(key_update_at, Value, _)
+ when is_integer(Value) andalso
+ Value > 0 ->
+ Value;
+validate_option(log_level, Value, _) when
is_atom(Value) andalso
- (Value =:= emergency orelse
+ (Value =:= none orelse
+ Value =:= all orelse
+ Value =:= emergency orelse
Value =:= alert orelse
Value =:= critical orelse
Value =:= error orelse
@@ -2109,110 +2304,178 @@ validate_option(log_level, Value) when
Value =:= info orelse
Value =:= debug) ->
Value;
-validate_option(next_protocols_advertised, Value) when is_list(Value) ->
- validate_binary_list(next_protocols_advertised, Value),
- Value;
-validate_option(next_protocols_advertised, undefined) ->
+%% RFC 6066, Section 4
+validate_option(max_fragment_length, I, _)
+ when I == ?MAX_FRAGMENT_LENGTH_BYTES_1;
+ I == ?MAX_FRAGMENT_LENGTH_BYTES_2;
+ I == ?MAX_FRAGMENT_LENGTH_BYTES_3;
+ I == ?MAX_FRAGMENT_LENGTH_BYTES_4 ->
+ I;
+validate_option(max_fragment_length, undefined, _) ->
undefined;
-validate_option(server_name_indication, Value) when is_list(Value) ->
- %% RFC 6066, Section 3: Currently, the only server names supported are
- %% DNS hostnames
- %% case inet_parse:domain(Value) of
- %% false ->
- %% throw({error, {options, {{Opt, Value}}}});
- %% true ->
- %% Value
- %% end;
- %%
- %% But the definition seems very diffuse, so let all strings through
- %% and leave it up to public_key to decide...
+validate_option(max_handshake_size, Value, _)
+ when is_integer(Value) andalso
+ Value =< ?MAX_UNIT24 ->
Value;
-validate_option(server_name_indication, undefined) ->
- undefined;
-validate_option(server_name_indication, disable) ->
- disable;
-
-validate_option(sni_hosts, []) ->
- [];
-validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail]) when is_list(Hostname) ->
- RecursiveSNIOptions = proplists:get_value(sni_hosts, SSLOptions, undefined),
- case RecursiveSNIOptions of
- undefined ->
- [{Hostname, validate_options(SSLOptions)} | validate_option(sni_hosts, Tail)];
- _ ->
- throw({error, {options, {sni_hosts, RecursiveSNIOptions}}})
- end;
-validate_option(sni_fun, undefined) ->
+validate_option(middlebox_comp_mode, Value, _)
+ when is_boolean(Value) ->
+ Value;
+validate_option(next_protocols_advertised, Value, _) when is_list(Value) ->
+ validate_binary_list(next_protocols_advertised, Value),
+ Value;
+validate_option(next_protocols_advertised, undefined, _) ->
undefined;
-validate_option(sni_fun, Fun) when is_function(Fun) ->
- Fun;
-validate_option(honor_cipher_order, Value) when is_boolean(Value) ->
+validate_option(ocsp_nonce, Value, _)
+ when Value =:= true orelse
+ Value =:= false ->
Value;
-validate_option(honor_ecc_order, Value) when is_boolean(Value) ->
+%% The OCSP responders' certificates can be given as a suggestion and
+%% will be used to verify the OCSP response.
+validate_option(ocsp_responder_certs, Value, _)
+ when is_list(Value) ->
+ [public_key:pkix_decode_cert(CertDer, plain) || CertDer <- Value,
+ is_binary(CertDer)];
+validate_option(ocsp_stapling, Value, _)
+ when Value =:= true orelse
+ Value =:= false ->
Value;
-validate_option(padding_check, Value) when is_boolean(Value) ->
+validate_option(padding_check, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(fallback, Value) when is_boolean(Value) ->
+validate_option(partial_chain, Value, _)
+ when is_function(Value) ->
Value;
-validate_option(crl_check, Value) when is_boolean(Value) ->
+validate_option(password, Value, _)
+ when is_list(Value) ->
Value;
-validate_option(crl_check, Value) when (Value == best_effort) or (Value == peer) ->
+validate_option(protocol, Value = tls, _) ->
Value;
-validate_option(crl_cache, {Cb, {_Handle, Options}} = Value) when is_atom(Cb) and is_list(Options) ->
+validate_option(protocol, Value = dtls, _) ->
Value;
-validate_option(beast_mitigation, Value) when Value == one_n_minus_one orelse
- Value == zero_n orelse
- Value == disabled ->
- Value;
-validate_option(max_handshake_size, Value) when is_integer(Value) andalso Value =< ?MAX_UNIT24 ->
+validate_option(psk_identity, undefined, _) ->
+ undefined;
+validate_option(psk_identity, Identity, _)
+ when is_list(Identity), Identity =/= "", length(Identity) =< 65535 ->
+ binary_filename(Identity);
+validate_option(renegotiate_at, Value, _) when is_integer(Value) ->
+ erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT);
+validate_option(reuse_session, undefined, _) ->
+ undefined;
+validate_option(reuse_session, Value, _)
+ when is_function(Value) ->
Value;
-validate_option(protocol, Value = tls) ->
+validate_option(reuse_session, Value, _)
+ when is_binary(Value) ->
Value;
-validate_option(protocol, Value = dtls) ->
+validate_option(reuse_session, {Id, Data} = Value, _)
+ when is_binary(Id) andalso
+ is_binary(Data) ->
Value;
-validate_option(handshake, hello = Value) ->
+validate_option(reuse_sessions, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(handshake, full = Value) ->
+validate_option(reuse_sessions, save = Value, _) ->
Value;
-validate_option(customize_hostname_check, Value) when is_list(Value) ->
+validate_option(secure_renegotiate, Value, _)
+ when is_boolean(Value) ->
Value;
-validate_option(cb_info, {V1, V2, V3, V4} = Value) when is_atom(V1),
- is_atom(V2),
- is_atom(V3),
- is_atom(V4)
- ->
+validate_option(server_name_indication, Value, _)
+ when is_list(Value) ->
+ %% RFC 6066, Section 3: Currently, the only server names supported are
+ %% DNS hostnames
+ %% case inet_parse:domain(Value) of
+ %% false ->
+ %% throw({error, {options, {{Opt, Value}}}});
+ %% true ->
+ %% Value
+ %% end;
+ %%
+ %% But the definition seems very diffuse, so let all strings through
+ %% and leave it up to public_key to decide...
Value;
-validate_option(cb_info, {V1, V2, V3, V4, V5} = Value) when is_atom(V1),
- is_atom(V2),
- is_atom(V3),
- is_atom(V4),
- is_atom(V5)
- ->
+validate_option(server_name_indication, undefined, _) ->
+ undefined;
+validate_option(server_name_indication, disable, _) ->
+ disable;
+validate_option(session_tickets, Value, server)
+ when Value =:= disabled orelse
+ Value =:= stateful orelse
+ Value =:= stateless ->
Value;
-validate_option(use_ticket, Value) when is_list(Value) ->
+validate_option(session_tickets, Value, server) ->
+ throw({error,
+ {options, role,
+ {session_tickets,
+ {Value, {server, [disabled, stateful, stateless]}}}}});
+validate_option(session_tickets, Value, client)
+ when Value =:= disabled orelse
+ Value =:= manual orelse
+ Value =:= auto ->
Value;
-validate_option(session_tickets, Value) when Value =:= disabled orelse
- Value =:= manual orelse
- Value =:= auto orelse
- Value =:= stateless orelse
- Value =:= stateful ->
+validate_option(session_tickets, Value, client) ->
+ throw({error,
+ {options, role,
+ {session_tickets,
+ {Value, {client, [disabled, manual, auto]}}}}});
+validate_option(sni_fun, undefined, _) ->
+ undefined;
+validate_option(sni_fun, Fun, _)
+ when is_function(Fun) ->
+ Fun;
+validate_option(sni_hosts, [], _) ->
+ [];
+validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail], _)
+ when is_list(Hostname) ->
+ RecursiveSNIOptions = proplists:get_value(sni_hosts, SSLOptions, undefined),
+ case RecursiveSNIOptions of
+ undefined ->
+ [{Hostname, validate_options(SSLOptions)} |
+ validate_option(sni_hosts, Tail)];
+ _ ->
+ throw({error, {options, {sni_hosts, RecursiveSNIOptions}}})
+ end;
+validate_option(srp_identity, undefined, _) ->
+ undefined;
+validate_option(srp_identity, {Username, Password}, _)
+ when is_list(Username),
+ is_list(Password), Username =/= "",
+ length(Username) =< 255 ->
+ {unicode:characters_to_binary(Username),
+ unicode:characters_to_binary(Password)};
+validate_option(user_lookup_fun, undefined, _) ->
+ undefined;
+validate_option(user_lookup_fun, {Fun, _} = Value, _)
+ when is_function(Fun, 3) ->
+ Value;
+validate_option(use_ticket, Value, _)
+ when is_list(Value) ->
Value;
-validate_option(anti_replay, '10k') ->
- %% n = 10000
- %% p = 0.030003564 (1 in 33)
- %% m = 72985 (8.91KiB)
- %% k = 5
- {10, 5, 72985};
-validate_option(anti_replay, '100k') ->
- %% n = 100000
- %% p = 0.03000428 (1 in 33)
- %% m = 729845 (89.09KiB)
- %% k = 5
- {10, 5, 729845};
-validate_option(anti_replay, Value) when (is_tuple(Value) andalso
- tuple_size(Value) =:= 3) ->
+validate_option(verify, Value, _)
+ when Value == verify_none; Value == verify_peer ->
Value;
-validate_option(Opt, undefined = Value) ->
+validate_option(verify_fun, undefined, _) ->
+ undefined;
+%% Backwards compatibility
+validate_option(verify_fun, Fun, _) when is_function(Fun) ->
+ {fun(_,{bad_cert, _} = Reason, OldFun) ->
+ case OldFun([Reason]) of
+ true ->
+ {valid, OldFun};
+ false ->
+ {fail, Reason}
+ end;
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end, Fun};
+validate_option(verify_fun, {Fun, _} = Value, _) when is_function(Fun) ->
+ Value;
+validate_option(versions, Versions, _) ->
+ validate_versions(Versions, Versions);
+validate_option(Opt, undefined = Value, _) ->
AllOpts = maps:keys(?RULES),
case lists:member(Opt, AllOpts) of
true ->
@@ -2220,7 +2483,7 @@ validate_option(Opt, undefined = Value) ->
false ->
throw({error, {options, {Opt, Value}}})
end;
-validate_option(Opt, Value) ->
+validate_option(Opt, Value, _) ->
throw({error, {options, {Opt, Value}}}).
handle_cb_info({V1, V2, V3, V4}) ->
@@ -2290,25 +2553,34 @@ validate_versions([], Versions) ->
validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'tlsv1.1';
- Version == tlsv1;
- Version == sslv3 ->
- tls_validate_versions(Rest, Versions);
+ Version == tlsv1 ->
+ case tls_record:sufficient_crypto_support(Version) of
+ true ->
+ tls_validate_versions(Rest, Versions);
+ false ->
+ throw({error, {options, {insufficient_crypto_support, {Version, {versions, Versions}}}}})
+ end;
validate_versions([Version | Rest], Versions) when Version == 'dtlsv1';
Version == 'dtlsv1.2'->
- dtls_validate_versions(Rest, Versions);
-validate_versions([Ver| _], Versions) ->
- throw({error, {options, {Ver, {versions, Versions}}}}).
+ DTLSVer = dtls_record:protocol_version(Version),
+ case tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(DTLSVer)) of
+ true ->
+ dtls_validate_versions(Rest, Versions);
+ false ->
+ throw({error, {options, {insufficient_crypto_support, {Version, {versions, Versions}}}}})
+ end;
+validate_versions([Version| _], Versions) ->
+ throw({error, {options, {Version, {versions, Versions}}}}).
tls_validate_versions([], Versions) ->
tls_validate_version_gap(Versions);
tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'tlsv1.1';
- Version == tlsv1;
- Version == sslv3 ->
+ Version == tlsv1 ->
tls_validate_versions(Rest, Versions);
-tls_validate_versions([Ver| _], Versions) ->
- throw({error, {options, {Ver, {versions, Versions}}}}).
+tls_validate_versions([Version| _], Versions) ->
+ throw({error, {options, {Version, {versions, Versions}}}}).
%% Do not allow configuration of TLS 1.3 with a gap where TLS 1.2 is not supported
%% as that configuration can trigger the built in version downgrade protection
@@ -2325,7 +2597,6 @@ tls_validate_version_gap(Versions) ->
_ ->
Versions
end.
-
dtls_validate_versions([], Versions) ->
Versions;
dtls_validate_versions([Version | Rest], Versions) when Version == 'dtlsv1';
@@ -2345,16 +2616,21 @@ ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) ->
%% some trusted certs.
ca_cert_default(verify_peer, undefined, _) ->
"".
-emulated_options(Protocol, Opts) ->
+emulated_options(undefined, undefined, Protocol, Opts) ->
case Protocol of
tls ->
tls_socket:emulated_options(Opts);
dtls ->
dtls_socket:emulated_options(Opts)
- end.
+ end;
+emulated_options(Transport, Socket, Protocol, Opts) ->
+ EmulatedOptions = tls_socket:emulated_options(),
+ {ok, Original} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
+ {Inet, Emulated0} = emulated_options(undefined, undefined, Protocol, Opts),
+ {Inet, lists:ukeymerge(1, Emulated0, Original)}.
-handle_cipher_option(Value, Version) when is_list(Value) ->
- try binary_cipher_suites(Version, Value) of
+handle_cipher_option(Value, Versions) when is_list(Value) ->
+ try binary_cipher_suites(Versions, Value) of
Suites ->
Suites
catch
@@ -2364,37 +2640,44 @@ handle_cipher_option(Value, Version) when is_list(Value) ->
throw({error, {options, {ciphers, Value}}})
end.
-binary_cipher_suites(Version, []) ->
+binary_cipher_suites([{3,4} = Version], []) ->
+ %% Defaults to all supported suites that does
+ %% not require explicit configuration TLS-1.3
+ %% only mode.
+ default_binary_suites(exclusive, Version);
+binary_cipher_suites([Version| _], []) ->
%% Defaults to all supported suites that does
%% not require explicit configuration
- default_binary_suites(Version);
-binary_cipher_suites(Version, [Map|_] = Ciphers0) when is_map(Map) ->
+ default_binary_suites(default, Version);
+binary_cipher_suites(Versions, [Map|_] = Ciphers0) when is_map(Map) ->
Ciphers = [ssl_cipher_format:suite_map_to_bin(C) || C <- Ciphers0],
- binary_cipher_suites(Version, Ciphers);
-binary_cipher_suites(Version, [Tuple|_] = Ciphers0) when is_tuple(Tuple) ->
+ binary_cipher_suites(Versions, Ciphers);
+binary_cipher_suites(Versions, [Tuple|_] = Ciphers0) when is_tuple(Tuple) ->
Ciphers = [ssl_cipher_format:suite_map_to_bin(tuple_to_map(C)) || C <- Ciphers0],
- binary_cipher_suites(Version, Ciphers);
-binary_cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
+ binary_cipher_suites(Versions, Ciphers);
+binary_cipher_suites([Version |_] = Versions, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
All = ssl_cipher:all_suites(Version) ++
ssl_cipher:anonymous_suites(Version),
case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of
[] ->
%% Defaults to all supported suites that does
%% not require explicit configuration
- default_binary_suites(Version);
+ binary_cipher_suites(Versions, []);
Ciphers ->
Ciphers
end;
-binary_cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) ->
+binary_cipher_suites(Versions, [Head | _] = Ciphers0) when is_list(Head) ->
%% Format: ["RC4-SHA","RC4-MD5"]
Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- Ciphers0],
- binary_cipher_suites(Version, Ciphers);
-binary_cipher_suites(Version, Ciphers0) ->
+ binary_cipher_suites(Versions, Ciphers);
+binary_cipher_suites(Versions, Ciphers0) ->
%% Format: "RC4-SHA:RC4-MD5"
Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- string:lexemes(Ciphers0, ":")],
- binary_cipher_suites(Version, Ciphers).
+ binary_cipher_suites(Versions, Ciphers).
-default_binary_suites(Version) ->
+default_binary_suites(exclusive, {_, Minor}) ->
+ ssl_cipher:filter_suites(tls_v1:exclusive_suites(Minor));
+default_binary_suites(default, Version) ->
ssl_cipher:filter_suites(ssl_cipher:suites(Version)).
tuple_to_map({Kex, Cipher, Mac}) ->
@@ -2497,9 +2780,9 @@ make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) ->
end.
connection_cb(tls) ->
- tls_connection;
+ tls_gen_connection;
connection_cb(dtls) ->
- dtls_connection;
+ dtls_gen_connection;
connection_cb(Opts) ->
connection_cb(proplists:get_value(protocol, Opts, tls)).
@@ -2514,34 +2797,53 @@ binary_filename(FileName) ->
Enc = file:native_name_encoding(),
unicode:characters_to_binary(FileName, unicode, Enc).
-assert_proplist([]) ->
- true;
-assert_proplist([{Key,_} | Rest]) when is_atom(Key) ->
- assert_proplist(Rest);
+%% Assert that basic options are on the format {Key, Value}
+%% with a few exceptions and phase out log_alert
+handle_option_format([], Acc) ->
+ lists:reverse(Acc);
+handle_option_format([{log_alert, Bool} | Rest], Acc) when is_boolean(Bool) ->
+ case proplists:get_value(log_level, Acc ++ Rest, undefined) of
+ undefined ->
+ handle_option_format(Rest, [{log_level,
+ map_log_level(Bool)} | Acc]);
+ _ ->
+ handle_option_format(Rest, Acc)
+ end;
+handle_option_format([{Key,_} = Opt | Rest], Acc) when is_atom(Key) ->
+ handle_option_format(Rest, [Opt | Acc]);
%% Handle exceptions
-assert_proplist([{raw,_,_,_} | Rest]) ->
- assert_proplist(Rest);
-assert_proplist([inet | Rest]) ->
- assert_proplist(Rest);
-assert_proplist([inet6 | Rest]) ->
- assert_proplist(Rest);
-assert_proplist([Value | _]) ->
+handle_option_format([{raw,_,_,_} = Opt | Rest], Acc) ->
+ handle_option_format(Rest, [Opt | Acc]);
+handle_option_format([inet = Opt | Rest], Acc) ->
+ handle_option_format(Rest, [Opt | Acc]);
+handle_option_format([inet6 = Opt | Rest], Acc) ->
+ handle_option_format(Rest, [Opt | Acc]);
+handle_option_format([Value | _], _) ->
throw({option_not_a_key_value_tuple, Value}).
-
-handle_verify_option(verify_none, #{fail_if_no_peer_cert := _FailIfNoPeerCert} = OptionsMap) ->
- OptionsMap#{verify => verify_none,
- fail_if_no_peer_cert => false};
-handle_verify_option(verify_peer, #{fail_if_no_peer_cert := FailIfNoPeerCert} = OptionsMap) ->
+map_log_level(true) ->
+ notice;
+map_log_level(false) ->
+ none.
+
+handle_verify_option(verify_none, #{fail_if_no_peer_cert := false} = OptionsMap) ->
+ OptionsMap#{verify => verify_none};
+handle_verify_option(verify_none, #{fail_if_no_peer_cert := true}) ->
+ throw({error, {options, incompatible,
+ {verify, verify_none},
+ {fail_if_no_peer_cert, true}}});
+%% The option 'verify' is simulated by the configured 'verify_fun' that is mostly
+%% hidden from the end user. When 'verify' is set to verify_none, the option
+%% 'verify_fun' is also set to a default verify-none-verify_fun when processing
+%% the configuration. If 'verify' is later changed from verify_none to verify_peer,
+%% the 'verify_fun' must also be changed to undefined. When 'verify_fun' is set to
+%% undefined, public_key's default verify_fun will be used that performs a full
+%% verification.
+handle_verify_option(verify_peer, #{verify := verify_none} = OptionsMap) ->
OptionsMap#{verify => verify_peer,
- fail_if_no_peer_cert => FailIfNoPeerCert};
-%% Handle 0, 1, 2 for backwards compatibility
-handle_verify_option(0, OptionsMap) ->
- handle_verify_option(verify_none, OptionsMap);
-handle_verify_option(1, OptionsMap) ->
- handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => false});
-handle_verify_option(2, OptionsMap) ->
- handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => true});
+ verify_fun => undefined};
+handle_verify_option(verify_peer, OptionsMap) ->
+ OptionsMap#{verify => verify_peer};
handle_verify_option(Value, _) ->
throw({error, {options, {verify, Value}}}).
@@ -2567,7 +2869,7 @@ default_cb_info(dtls) ->
include_security_info([]) ->
false;
include_security_info([Item | Items]) ->
- case lists:member(Item, [client_random, server_random, master_secret]) of
+ case lists:member(Item, [client_random, server_random, master_secret, keylog]) of
true ->
true;
false ->
@@ -2580,25 +2882,6 @@ server_name_indication_default(Host) when is_list(Host) ->
server_name_indication_default(_) ->
undefined.
-
-reject_alpn_next_prot_options({Opts,_,_}) ->
- AlpnNextOpts = [alpn_advertised_protocols,
- alpn_preferred_protocols,
- next_protocols_advertised,
- next_protocol_selector,
- client_preferred_next_protocols],
- reject_alpn_next_prot_options(AlpnNextOpts, Opts).
-
-reject_alpn_next_prot_options([], _) ->
- ok;
-reject_alpn_next_prot_options([Opt| AlpnNextOpts], Opts) ->
- case lists:keyfind(Opt, 1, Opts) of
- {Opt, Value} ->
- throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}});
- false ->
- reject_alpn_next_prot_options(AlpnNextOpts, Opts)
- end.
-
add_filter(undefined, Filters) ->
Filters;
add_filter(Filter, Filters) ->
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 41bb7efcf6..aa21c8213e 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -119,7 +119,7 @@ own_alert_txt(#alert{level = Level, description = Description, where = #{line :=
alert_format(Alert) ->
Txt = alert_txt(Alert),
- {" ~s\n ", [Txt]}.
+ {" ~s\n", [Txt]}.
alert_txt(#alert{level = Level, description = Description, role = Role}) ->
"received " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 6d718dfef9..424bf05791 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -31,7 +31,7 @@
-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([trusted_cert_and_path/4,
+-export([trusted_cert_and_paths/4,
certificate_chain/3,
certificate_chain/4,
file_to_certificats/2,
@@ -50,46 +50,47 @@
%%====================================================================
%%--------------------------------------------------------------------
--spec trusted_cert_and_path([der_cert()], db_handle(), certdb_ref(), fun()) ->
- {der_cert() | unknown_ca, [der_cert()]}.
+-spec trusted_cert_and_paths([der_cert()], db_handle(), certdb_ref(), fun()) ->
+ [{der_cert() | unknown_ca | invalid_issuer | selfsigned_peer, [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}
+%% Description: Construct input to public_key:pkix_path_validation/3,
+%% If the ROOT cert is not found {bad_cert, unknown_ca} will be returned
+%% instead of the ROOT cert to be handled as a path validation error
+%% by the verify_fun.
+%% Returns {RootCert | RootCertRelatedError, Path}
+%% Note: Path = lists:reverse(Chain) -- Root, that is on the peer cert
+%% always comes first in the chain but last in the path.
%%--------------------------------------------------------------------
-trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) ->
- Path = [BinCert | _] = lists:reverse(CertChain),
- OtpCert = public_key:pkix_decode_cert(BinCert, otp),
- SignedAndIssuerID =
- case public_key:pkix_is_self_signed(OtpCert) of
- true ->
- {ok, IssuerId} = public_key:pkix_issuer_id(OtpCert, self),
- {self, IssuerId};
- false ->
- other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef)
- end,
-
- case SignedAndIssuerID of
- {error, issuer_not_found} ->
- %% The root CA was not sent and cannot be found.
- 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, Trusted} ->
- %% Trusted must be selfsigned or it is an incomplete chain
- handle_path(Trusted, Path, PartialChainHandler);
- _ ->
- %% Root CA could not be verified, but partial
- %% chain handler may trusted a cert that we got
- handle_incomplete_chain(Path, PartialChainHandler)
- end
- end.
+trusted_cert_and_paths([Peer] = Chain, CertDbHandle, CertDbRef, PartialChainHandler) ->
+ OtpCert = public_key:pkix_decode_cert(Peer, otp),
+ case public_key:pkix_is_self_signed(OtpCert) of
+ true ->
+ [{selfsigned_peer, [Peer]}];
+ false ->
+ [handle_incomplete_chain(Chain, PartialChainHandler, {unknown_ca, [Peer]},
+ CertDbHandle, CertDbRef)]
+ end;
+trusted_cert_and_paths(Chain, CertDbHandle, CertDbRef, PartialChainHandler) ->
+ %% Construct possible certificate paths from the chain certificates.
+ %% If the chain contains extraneous certificates there could be
+ %% more than one possible path such chains might be used to phase out
+ %% an old certificate.
+ Paths = paths(Chain, CertDbHandle),
+ lists:map(fun(Path) ->
+ case handle_partial_chain(Path, PartialChainHandler, CertDbHandle, CertDbRef) of
+ {unknown_ca, _} = Result ->
+ handle_incomplete_chain(Chain,
+ PartialChainHandler,
+ Result,
+ CertDbHandle, CertDbRef);
+ Result ->
+ Result
+ end
+ end, Paths).
%%--------------------------------------------------------------------
--spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref()) ->
- {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}.
+-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref() | {extracted, list()}) ->
+ {error, no_cert} | {ok, der_cert() | undefined, [der_cert()]}.
%%
%% Description: Return the certificate chain to send to peer.
%%--------------------------------------------------------------------
@@ -104,7 +105,7 @@ certificate_chain(OwnCert, CertDbHandle, CertsDbRef) ->
%%--------------------------------------------------------------------
-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref() | {extracted, list()}, [der_cert()]) ->
- {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}.
+ {error, no_cert} | {ok, der_cert() | undefined, [der_cert()]}.
%%
%% Description: Create certificate chain with certs from
%%--------------------------------------------------------------------
@@ -133,7 +134,7 @@ file_to_crls(File, DbHandle) ->
[Bin || {'CertificateList', Bin, not_encrypted} <- List].
%%--------------------------------------------------------------------
--spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid,
+-spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid | valid_peer,
term()) -> {valid, term()} |
{fail, tuple()} |
{unknown, term()}.
@@ -141,7 +142,7 @@ file_to_crls(File, DbHandle) ->
%% Description: Validates ssl/tls specific extensions
%%--------------------------------------------------------------------
validate(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage',
- extnValue = KeyUse}}, UserState = {Role, _,_, _, _, _}) ->
+ extnValue = KeyUse}}, UserState = #{role := Role}) ->
case is_valid_extkey_usage(KeyUse, Role) of
true ->
{valid, UserState};
@@ -152,12 +153,28 @@ validate(_, {extension, _}, UserState) ->
{unknown, UserState};
validate(_, {bad_cert, _} = Reason, _) ->
{fail, Reason};
-validate(_, valid, UserState) ->
- {valid, UserState};
-validate(Cert, valid_peer, UserState = {client, _,_, {Hostname, Customize}, _, _}) when Hostname =/= disable ->
- verify_hostname(Hostname, Customize, Cert, UserState);
-validate(_, valid_peer, UserState) ->
- {valid, UserState}.
+validate(Cert, valid, UserState) ->
+ case verify_sign(Cert, UserState) of
+ true ->
+ case maps:get(cert_ext, UserState, undefined) of
+ undefined ->
+ {valid, UserState};
+ _ ->
+ verify_cert_extensions(Cert, UserState)
+ end;
+ false ->
+ {fail, {bad_cert, invalid_signature}}
+ end;
+validate(Cert, valid_peer, UserState = #{role := client, server_name := Hostname,
+ customize_hostname_check := Customize}) when Hostname =/= disable ->
+ case verify_hostname(Hostname, Customize, Cert, UserState) of
+ {valid, UserState} ->
+ validate(Cert, valid, UserState);
+ Error ->
+ Error
+ end;
+validate(Cert, valid_peer, UserState) ->
+ validate(Cert, valid, UserState).
%%--------------------------------------------------------------------
-spec is_valid_key_usage(list(), term()) -> boolean().
@@ -321,6 +338,10 @@ public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorith
public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'rsaEncryption'},
subjectPublicKey = Key}) ->
Key;
+public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-RSASSA-PSS',
+ parameters = Params},
+ subjectPublicKey = Key}) ->
+ {Key, Params};
public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-dsa',
parameters = {params, Params}},
subjectPublicKey = Key}) ->
@@ -339,31 +360,6 @@ other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) ->
end
end.
-handle_path({BinCert, OTPCert}, Path, PartialChainHandler) ->
- case public_key:pkix_is_self_signed(OTPCert) of
- true ->
- {BinCert, lists:delete(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, []}.
-
verify_hostname({fallback, Hostname}, Customize, Cert, UserState) when is_list(Hostname) ->
case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}], Customize) of
true ->
@@ -392,3 +388,211 @@ verify_hostname(Hostname, Customize, Cert, UserState) ->
false ->
{fail, {bad_cert, hostname_check_failed}}
end.
+
+verify_cert_extensions(Cert, #{cert_ext := CertExts} = UserState) ->
+ Id = public_key:pkix_subject_id(Cert),
+ Extensions = maps:get(Id, CertExts, []),
+ verify_cert_extensions(Cert, UserState, Extensions, #{}).
+
+verify_cert_extensions(Cert, UserState, [], _) ->
+ {valid, UserState#{issuer => Cert}};
+verify_cert_extensions(Cert, #{ocsp_responder_certs := ResponderCerts,
+ ocsp_state := OscpState,
+ issuer := Issuer} = UserState, [#certificate_status{response = OcspResponsDer} | Exts], Context) ->
+ #{ocsp_nonce := Nonce} = OscpState,
+ case public_key:pkix_ocsp_validate(Cert, Issuer, OcspResponsDer, ResponderCerts, Nonce) of
+ valid ->
+ verify_cert_extensions(Cert, UserState, Exts, Context);
+ {bad_cert, _} = Status ->
+ {fail, Status}
+ end;
+verify_cert_extensions(Cert, UserState, [_|Exts], Context) ->
+ %% Skip unknow extensions!
+ verify_cert_extensions(Cert, UserState, Exts, Context).
+
+verify_sign(_, #{version := {_, Minor}}) when Minor < 3 ->
+ %% This verification is not applicable pre TLS-1.2
+ true;
+verify_sign(Cert, #{signature_algs := SignAlgs,
+ signature_algs_cert := undefined}) ->
+ is_supported_signature_algorithm(Cert, SignAlgs);
+verify_sign(Cert, #{signature_algs_cert := SignAlgs}) ->
+ is_supported_signature_algorithm(Cert, SignAlgs).
+
+is_supported_signature_algorithm(#'OTPCertificate'{signatureAlgorithm =
+ #'SignatureAlgorithm'{algorithm = ?'id-dsa-with-sha1'}}, [{_,_}|_] = SignAlgs) ->
+ lists:member({sha, dsa}, SignAlgs);
+is_supported_signature_algorithm(#'OTPCertificate'{signatureAlgorithm = SignAlg}, [{_,_}|_] = SignAlgs) ->
+ Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
+ {Hash, Sign, _ } = ssl_cipher:scheme_to_components(Scheme),
+ lists:member({pre_1_3_hash(Hash), pre_1_3_sign(Sign)}, SignAlgs);
+is_supported_signature_algorithm(#'OTPCertificate'{signatureAlgorithm = SignAlg}, SignAlgs) ->
+ Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
+ lists:member(Scheme, SignAlgs).
+
+pre_1_3_sign(rsa_pkcs1) ->
+ rsa;
+pre_1_3_sign(Other) ->
+ Other.
+pre_1_3_hash(sha1) ->
+ sha;
+pre_1_3_hash(Hash) ->
+ Hash.
+
+paths(Chain, CertDbHandle) ->
+ paths(Chain, Chain, CertDbHandle, []).
+
+paths([Root], _, _, Path) ->
+ [[Root | Path]];
+paths([Cert1, Cert2 | Rest], Chain, CertDbHandle, Path) ->
+ case public_key:pkix_is_issuer(Cert1, Cert2) of
+ true ->
+ %% Chain orded so far
+ paths([Cert2 | Rest], Chain, CertDbHandle, [Cert1 | Path]);
+ false ->
+ %% Chain is unorded and/or contains extraneous certificates
+ unorded_or_extraneous(Chain, CertDbHandle)
+ end.
+
+unorded_or_extraneous([Peer | UnorderedChain], CertDbHandle) ->
+ ChainCandidates = extraneous_chains(UnorderedChain),
+ lists:map(fun(Candidate) ->
+ path_candidate(Peer, Candidate, CertDbHandle)
+ end,
+ ChainCandidates).
+
+path_candidate(Peer, ChainCandidateCAs, CertDbHandle) ->
+ {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, ChainCandidateCAs}),
+ %% certificate_chain/4 will make sure the chain is ordered
+ case certificate_chain(Peer, CertDbHandle, ExtractedCerts, []) of
+ {ok, undefined, Chain} ->
+ lists:reverse(Chain);
+ {ok, Root, Chain} ->
+ [Root | lists:reverse(Chain)]
+ end.
+
+handle_partial_chain([IssuerCert| Rest] = Path, PartialChainHandler, CertDbHandle, CertDbRef) ->
+ case public_key:pkix_is_self_signed(IssuerCert) of
+ true -> %% IssuerCert = ROOT (That is ROOT was included in chain)
+ {ok, {SerialNr, IssuerId}} = public_key:pkix_issuer_id(IssuerCert, self),
+ case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, IssuerId) of
+ {ok, {IssuerCert, _}} -> %% Match sent ROOT to trusted ROOT
+ maybe_shorten_path(Path, PartialChainHandler, {IssuerCert, Rest});
+ {ok, _} -> %% Did not match trusted ROOT
+ maybe_shorten_path(Path, PartialChainHandler, {invalid_issuer, Path});
+ _ ->
+ maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path})
+ end;
+ false ->
+ OTPCert = public_key:pkix_decode_cert(IssuerCert, otp),
+ case other_issuer(OTPCert, IssuerCert, CertDbHandle, CertDbRef) of
+ {other, {SerialNr, IssuerId}} ->
+ case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, IssuerId) of
+ {ok, {NewIssuerCert, _}} ->
+ case public_key:pkix_is_self_signed(NewIssuerCert) of
+ true -> %% NewIssuerCert is a trusted ROOT cert
+ maybe_shorten_path([NewIssuerCert | Path], PartialChainHandler, {NewIssuerCert, Path});
+ false ->
+ maybe_shorten_path([NewIssuerCert | Path], PartialChainHandler,
+ {unknown_ca, [NewIssuerCert | Path]})
+ end;
+ _ ->
+ maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path})
+ end;
+ {error, issuer_not_found} ->
+ maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path})
+ end
+ end.
+
+maybe_shorten_path(Path, PartialChainHandler, Default) ->
+ %% This function might shorthen the
+ %% certificate path to be validated with
+ %% public_key:pkix_path_validation by letting
+ %% the user put its trust in an intermidate cert
+ %% from the certifcate chain sent by the peer.
+ try PartialChainHandler(Path) of
+ {trusted_ca, Root} ->
+ new_trusteded_path(Root, Path, Default);
+ unknown_ca ->
+ Default
+ catch _:_ ->
+ Default
+ end.
+
+new_trusteded_path(DerCert, [DerCert | Chain], _) ->
+ {DerCert, Chain};
+new_trusteded_path(DerCert, [_ | Rest], Default) ->
+ new_trusteded_path(DerCert, Rest, Default);
+new_trusteded_path(_, [], Default) ->
+ %% User did not pick a cert present
+ %% in the cert chain so ignore
+ Default.
+
+handle_incomplete_chain([PeerCert| _] = Chain0, PartialChainHandler, Default, CertDbHandle, CertDbRef) ->
+ %% We received an incomplete chain, that is not all certs expected to be present are present.
+ %% See if we have the certificates to rebuild it.
+ case certificate_chain(PeerCert, CertDbHandle, CertDbRef) of
+ {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found
+ handle_partial_chain(lists:reverse(Chain), PartialChainHandler, CertDbHandle, CertDbRef);
+ _ ->
+ Default
+ end.
+
+extraneous_chains(Certs) ->
+ %% If some certs claim to be the same cert that is have the same
+ %% subject field we should create a list of possible chain certs
+ %% for each such cert. Only one chain, if any, should be
+ %% verifiable using available ROOT certs.
+ Subjects = [{subject(Cert), Cert} || Cert <- Certs],
+ Duplicates = find_duplicates(Subjects),
+ %% Number of certs with duplicates (same subject) has been limited
+ %% to 4 and the maximum number of combinations is limited to 16.
+ build_candidates(Duplicates, 4, 16).
+
+build_candidates(Map, Duplicates, Combinations) ->
+ Subjects = maps:keys(Map),
+ build_candidates(Subjects, Map, Duplicates, 1, Combinations, []).
+%%
+build_candidates([], _, _, _, _, Acc) ->
+ Acc;
+build_candidates([H|T], Map, Duplicates, Combinations, Max, Acc0) ->
+ case maps:get(H, Map) of
+ {Certs, Counter} when Counter > 1 andalso
+ Duplicates > 0 andalso
+ Counter * Combinations =< Max ->
+ case Acc0 of
+ [] ->
+ Acc = [[Cert] || Cert <- Certs],
+ build_candidates(T, Map, Duplicates - 1, Combinations * Counter, Max, Acc);
+ _Else ->
+ Acc = [[Cert|L] || Cert <- Certs, L <- Acc0],
+ build_candidates(T, Map, Duplicates - 1, Combinations * Counter, Max, Acc)
+ end;
+ {[Cert|_Throw], _Counter} ->
+ case Acc0 of
+ [] ->
+ Acc = [[Cert]],
+ build_candidates(T, Map, Duplicates, Combinations, Max, Acc);
+ _Else ->
+ Acc = [[Cert|L] || L <- Acc0],
+ build_candidates(T, Map, Duplicates, Combinations, Max, Acc)
+ end
+ end.
+
+find_duplicates(Chain) ->
+ find_duplicates(Chain, #{}).
+%%
+find_duplicates([], Acc) ->
+ Acc;
+find_duplicates([{Subject, Cert}|T], Acc) ->
+ case maps:get(Subject, Acc, none) of
+ none ->
+ find_duplicates(T, Acc#{Subject => {[Cert], 1}});
+ {Certs, Counter} ->
+ find_duplicates(T, Acc#{Subject => {[Cert|Certs], Counter + 1}})
+ end.
+
+subject(Cert) ->
+ {_Serial,Subject} = public_key:pkix_subject_id(Cert),
+ Subject.
+
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index c97884ec08..85042e8612 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -34,25 +34,53 @@
-include("tls_handshake_1_3.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([security_parameters/2, security_parameters/3, security_parameters_1_3/2,
- cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/6, aead_decrypt/6,
- suites/1, all_suites/1, crypto_support_filters/0,
- chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1,
- srp_suites/1, srp_suites_anon/1,
- rc4_suites/1, des_suites/1, rsa_suites/1,
- filter/3, filter_suites/1, filter_suites/2,
- hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1,
- random_bytes/1, calc_mac_hash/4, calc_mac_hash/6,
- is_stream_ciphersuite/1, signature_scheme/1,
- scheme_to_components/1, hash_size/1, effective_key_bits/1,
- key_material/1, signature_algorithm_to_scheme/1]).
+-export([security_parameters/2,
+ security_parameters/3,
+ security_parameters_1_3/2,
+ cipher_init/3,
+ nonce_seed/2,
+ decipher/6,
+ cipher/5,
+ aead_encrypt/6,
+ aead_decrypt/6,
+ suites/1,
+ all_suites/1,
+ crypto_support_filters/0,
+ anonymous_suites/1,
+ psk_suites/1,
+ psk_suites_anon/1,
+ srp_suites/1,
+ srp_suites_anon/1,
+ rc4_suites/1,
+ des_suites/1,
+ rsa_suites/1,
+ filter/3,
+ filter_suites/1,
+ filter_suites/2,
+ hash_algorithm/1,
+ sign_algorithm/1,
+ is_acceptable_hash/2,
+ is_fallback/1,
+ random_bytes/1,
+ calc_mac_hash/4,
+ calc_mac_hash/6,
+ is_stream_ciphersuite/1,
+ signature_scheme/1,
+ scheme_to_components/1,
+ hash_size/1,
+ effective_key_bits/1,
+ key_material/1,
+ signature_algorithm_to_scheme/1,
+ bulk_cipher_algorithm/1]).
%% RFC 8446 TLS 1.3
-export([generate_client_shares/1,
generate_server_share/1,
add_zero_padding/2,
encrypt_ticket/3,
- decrypt_ticket/3]).
+ decrypt_ticket/3,
+ encrypt_data/4,
+ decrypt_data/4]).
-compile(inline).
@@ -108,7 +136,7 @@ security_parameters_1_3(SecParams, CipherSuite) ->
%% Description: Initializes the #cipher_state according to BCA
%%-------------------------------------------------------------------
cipher_init(?RC4, IV, Key) ->
- State = crypto:stream_init(rc4, Key),
+ State = {stream_init,rc4,Key,IV},
#cipher_state{iv = IV, key = Key, state = State};
cipher_init(Type, IV, Key) when Type == ?AES_GCM;
Type == ?AES_CCM ->
@@ -130,45 +158,60 @@ nonce_seed(Seed, CipherState) ->
-spec cipher(cipher_enum(), #cipher_state{}, binary(), iodata(), ssl_record:ssl_version()) ->
{binary(), #cipher_state{}}.
%%
-%% Description: Encrypts the data and the MAC using chipher described
+%% Description: Encrypts the data and the MAC using cipher described
%% by cipher_enum() and updating the cipher state
%% Used for "MAC then Cipher" suites where first an HMAC of the
-%% data is calculated and the data plus the HMAC is ecncrypted.
+%% data is calculated and the data plus the HMAC is encrypted.
%%-------------------------------------------------------------------
cipher(?NULL, CipherState, <<>>, Fragment, _Version) ->
{iolist_to_binary(Fragment), CipherState};
-cipher(?RC4, CipherState = #cipher_state{state = State0}, Mac, Fragment, _Version) ->
+cipher(CipherEnum, CipherState = #cipher_state{state = {stream_init,rc4,Key,_IV}}, Mac, Fragment, Version) ->
+ State = crypto:crypto_init(rc4, Key, true),
+ cipher(CipherEnum, CipherState#cipher_state{state = State}, Mac, Fragment, Version);
+cipher(?RC4, CipherState = #cipher_state{state = State}, Mac, Fragment, _Version) ->
GenStreamCipherList = [Fragment, Mac],
- {State1, T} = crypto:stream_encrypt(State0, GenStreamCipherList),
- {iolist_to_binary(T), CipherState#cipher_state{state = State1}};
+ T = crypto:crypto_update(State, GenStreamCipherList),
+ {iolist_to_binary(T), CipherState};
cipher(?DES, CipherState, Mac, Fragment, Version) ->
block_cipher(fun(Key, IV, T) ->
- crypto:block_encrypt(des_cbc, Key, IV, T)
+ crypto:crypto_one_time(des_cbc, Key, IV, T, true)
end, block_size(des_cbc), CipherState, Mac, Fragment, Version);
cipher(?'3DES', CipherState, Mac, Fragment, Version) ->
- block_cipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) ->
- crypto:block_encrypt(des3_cbc, [K1, K2, K3], IV, T)
- end, block_size(des_cbc), CipherState, Mac, Fragment, Version);
+ block_cipher(fun(Key, IV, T) ->
+ crypto:crypto_one_time(des_ede3_cbc, Key, IV, T, true)
+ end, block_size(des_ede3_cbc), CipherState, Mac, Fragment, Version);
cipher(?AES_CBC, CipherState, Mac, Fragment, Version) ->
block_cipher(fun(Key, IV, T) when byte_size(Key) =:= 16 ->
- crypto:block_encrypt(aes_cbc128, Key, IV, T);
+ crypto:crypto_one_time(aes_128_cbc, Key, IV, T, true);
(Key, IV, T) when byte_size(Key) =:= 32 ->
- crypto:block_encrypt(aes_cbc256, Key, IV, T)
+ crypto:crypto_one_time(aes_256_cbc, Key, IV, T, true)
end, block_size(aes_128_cbc), CipherState, Mac, Fragment, Version).
aead_encrypt(Type, Key, Nonce, Fragment, AdditionalData, TagLen) ->
- crypto:block_encrypt(aead_type(Type), Key, Nonce, {AdditionalData, Fragment, TagLen}).
+ crypto:crypto_one_time_aead(aead_type(Type,size(Key)), Key, Nonce, Fragment, AdditionalData, TagLen, true).
aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AdditionalData) ->
- crypto:block_decrypt(aead_type(Type), Key, Nonce, {AdditionalData, CipherText, CipherTag}).
-
-aead_type(?AES_GCM) ->
- aes_gcm;
-aead_type(?AES_CCM) ->
- aes_ccm;
-aead_type(?AES_CCM_8) ->
- aes_ccm;
-aead_type(?CHACHA20_POLY1305) ->
+ crypto:crypto_one_time_aead(aead_type(Type,size(Key)), Key, Nonce, CipherText, AdditionalData, CipherTag, false).
+
+aead_type(?AES_GCM, 16) ->
+ aes_128_gcm;
+aead_type(?AES_GCM, 24) ->
+ aes_192_gcm;
+aead_type(?AES_GCM, 32) ->
+ aes_256_gcm;
+aead_type(?AES_CCM, 16) ->
+ aes_128_ccm;
+aead_type(?AES_CCM, 24) ->
+ aes_192_ccm;
+aead_type(?AES_CCM, 32) ->
+ aes_256_ccm;
+aead_type(?AES_CCM_8, 16) ->
+ aes_128_ccm;
+aead_type(?AES_CCM_8, 24) ->
+ aes_192_ccm;
+aead_type(?AES_CCM_8, 32) ->
+ aes_256_ccm;
+aead_type(?CHACHA20_POLY1305, _) ->
chacha20_poly1305.
build_cipher_block(BlockSz, Mac, Fragment) ->
@@ -211,12 +254,16 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV, state = IV_Cache0} = CS
%%-------------------------------------------------------------------
decipher(?NULL, _HashSz, CipherState, Fragment, _, _) ->
{Fragment, <<>>, CipherState};
-decipher(?RC4, HashSz, CipherState = #cipher_state{state = State0}, Fragment, _, _) ->
- try crypto:stream_decrypt(State0, Fragment) of
- {State, Text} ->
+decipher(CipherEnum, HashSz, CipherState = #cipher_state{state = {stream_init,rc4,Key,_IV}},
+ Fragment, Version, PaddingCheck) ->
+ State = crypto:crypto_init(rc4, Key, false),
+ decipher(CipherEnum, HashSz, CipherState#cipher_state{state = State}, Fragment, Version, PaddingCheck);
+decipher(?RC4, HashSz, CipherState = #cipher_state{state = State}, Fragment, _, _) ->
+ try crypto:crypto_update(State, Fragment) of
+ Text ->
GSC = generic_stream_cipher_from_bin(Text, HashSz),
#generic_stream_cipher{content = Content, mac = Mac} = GSC,
- {Content, Mac, CipherState#cipher_state{state = State}}
+ {Content, Mac, CipherState}
catch
_:_ ->
%% This is a DECRYPTION_FAILED but
@@ -229,17 +276,17 @@ decipher(?RC4, HashSz, CipherState = #cipher_state{state = State0}, Fragment, _,
decipher(?DES, HashSz, CipherState, Fragment, Version, PaddingCheck) ->
block_decipher(fun(Key, IV, T) ->
- crypto:block_decrypt(des_cbc, Key, IV, T)
+ crypto:crypto_one_time(des_cbc, Key, IV, T, false)
end, CipherState, HashSz, Fragment, Version, PaddingCheck);
decipher(?'3DES', HashSz, CipherState, Fragment, Version, PaddingCheck) ->
- block_decipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) ->
- crypto:block_decrypt(des3_cbc, [K1, K2, K3], IV, T)
+ block_decipher(fun(Key, IV, T) ->
+ crypto:crypto_one_time(des_ede3_cbc, Key, IV, T, false)
end, CipherState, HashSz, Fragment, Version, PaddingCheck);
decipher(?AES_CBC, HashSz, CipherState, Fragment, Version, PaddingCheck) ->
block_decipher(fun(Key, IV, T) when byte_size(Key) =:= 16 ->
- crypto:block_decrypt(aes_cbc128, Key, IV, T);
+ crypto:crypto_one_time(aes_128_cbc, Key, IV, T, false);
(Key, IV, T) when byte_size(Key) =:= 32 ->
- crypto:block_decrypt(aes_cbc256, Key, IV, T)
+ crypto:crypto_one_time(aes_256_cbc, Key, IV, T, false)
end, CipherState, HashSz, Fragment, Version, PaddingCheck).
block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
@@ -277,8 +324,6 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
%%
%% Description: Returns a list of supported cipher suites.
%%--------------------------------------------------------------------
-suites({3, 0}) ->
- ssl_v3:suites();
suites({3, Minor}) ->
tls_v1:suites(Minor);
suites({_, Minor}) ->
@@ -286,29 +331,14 @@ suites({_, Minor}) ->
all_suites({3, _} = Version) ->
suites(Version)
- ++ chacha_suites(Version)
++ psk_suites(Version)
++ srp_suites(Version)
- ++ rc4_suites(Version)
+ ++ rsa_suites(Version)
++ des_suites(Version)
- ++ rsa_suites(Version);
+ ++ rc4_suites(Version);
all_suites(Version) ->
dtls_v1:all_suites(Version).
-%%--------------------------------------------------------------------
--spec chacha_suites(ssl_record:ssl_version() | integer()) ->
- [ssl_cipher_format:cipher_suite()].
-%%
-%% Description: Returns list of the chacha cipher suites, only supported
-%% if explicitly set by user for now due to interop problems, proably need
-%% to be fixed in crypto.
-%%--------------------------------------------------------------------
-chacha_suites({3, _}) ->
- [?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
- ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
- ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256];
-chacha_suites(_) ->
- [].
%%--------------------------------------------------------------------
-spec anonymous_suites(ssl_record:ssl_version() | integer()) ->
@@ -426,8 +456,6 @@ psk_suites_anon(0) ->
%% Description: Returns a list of the SRP cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-srp_suites({3,0}) ->
- [];
srp_suites(_) ->
[?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
@@ -442,8 +470,6 @@ srp_suites(_) ->
%% Description: Returns a list of the SRP anonymous cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-srp_suites_anon({3,0}) ->
- [];
srp_suites_anon(_) ->
[?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,
?TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
@@ -505,13 +531,15 @@ rsa_suites(0) ->
?TLS_RSA_WITH_AES_128_CBC_SHA,
?TLS_RSA_WITH_3DES_EDE_CBC_SHA
];
-rsa_suites(N) when N =< 4 ->
+rsa_suites(N) when N >= 3 ->
[
?TLS_RSA_WITH_AES_256_GCM_SHA384,
?TLS_RSA_WITH_AES_256_CBC_SHA256,
?TLS_RSA_WITH_AES_128_GCM_SHA256,
?TLS_RSA_WITH_AES_128_CBC_SHA256
- ].
+ ];
+rsa_suites(_) ->
+ [].
%%--------------------------------------------------------------------
-spec filter(undefined | binary(), [ssl_cipher_format:cipher_suite()],
@@ -645,24 +673,12 @@ is_acceptable_cipher(null, _Algos) ->
true;
is_acceptable_cipher(rc4_128, Algos) ->
proplists:get_bool(rc4, Algos);
-is_acceptable_cipher(des_cbc, Algos) ->
- proplists:get_bool(des_cbc, Algos);
is_acceptable_cipher('3des_ede_cbc', Algos) ->
- proplists:get_bool(des_ede3, Algos);
-is_acceptable_cipher(aes_128_cbc, Algos) ->
- proplists:get_bool(aes_cbc128, Algos);
-is_acceptable_cipher(aes_256_cbc, Algos) ->
- proplists:get_bool(aes_cbc256, Algos);
-is_acceptable_cipher(Cipher, Algos)
- when Cipher == aes_128_gcm;
- Cipher == aes_256_gcm ->
- proplists:get_bool(aes_gcm, Algos);
-is_acceptable_cipher(Cipher, Algos)
- when Cipher == aes_128_ccm;
- Cipher == aes_256_ccm;
- Cipher == aes_128_ccm_8;
- Cipher == aes_256_ccm_8 ->
- proplists:get_bool(aes_ccm, Algos);
+ proplists:get_bool(des_ede3_cbc, Algos);
+is_acceptable_cipher(aes_128_ccm_8, Algos) ->
+ proplists:get_bool(aes_128_ccm, Algos);
+is_acceptable_cipher(aes_256_ccm_8, Algos) ->
+ proplists:get_bool(aes_256_ccm, Algos);
is_acceptable_cipher(Cipher, Algos) ->
proplists:get_bool(Cipher, Algos).
@@ -734,8 +750,6 @@ hash_size(sha512) ->
mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type,
_Length, _Fragment) ->
<<>>;
-mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
- ssl_v3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment);
mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment)
when N =:= 1; N =:= 2; N =:= 3; N =:= 4 ->
tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
@@ -863,6 +877,7 @@ iv_size(Cipher) ->
block_size(Cipher).
block_size(Cipher) when Cipher == des_cbc;
+ Cipher == des_ede3_cbc;
Cipher == '3des_ede_cbc' ->
8;
block_size(Cipher) when Cipher == aes_128_cbc;
@@ -982,8 +997,20 @@ scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined};
%% Handling legacy signature algorithms
scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}.
-
-%% TODO: Add support for ed25519, ed448, rsa_pss*
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS',
+ parameters = #'RSASSA-PSS-params'{
+ maskGenAlgorithm =
+ #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = HashAlgo}}}) ->
+ #'HashAlgorithm'{algorithm = HashOid} = HashAlgo,
+ case public_key:pkix_hash_type(HashOid) of
+ sha256 ->
+ rsa_pss_pss_sha256;
+ sha384 ->
+ rsa_pss_pss_sha384;
+ sha512 ->
+ rsa_pss_pss_sha512
+ end;
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha256WithRSAEncryption}) ->
rsa_pkcs1_sha256;
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha384WithRSAEncryption}) ->
@@ -1001,8 +1028,18 @@ signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'sha-1WithRSAEn
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) ->
rsa_pkcs1_sha1;
signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA1'}) ->
- ecdsa_sha1.
-
+ ecdsa_sha1;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-Ed25519'}) ->
+ eddsa_ed25519;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-Ed448'}) ->
+ eddsa_ed448;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'rsaEncryption',
+ parameters = ?NULL}) ->
+ rsa_pkcs1_sha1;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'rsaEncryption'}) ->
+ rsa_pss_rsae;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS'}) ->
+ rsa_pss_pss.
%% RFC 5246: 6.2.3.2. CBC Block Cipher
%%
@@ -1426,7 +1463,7 @@ decrypt_ticket(CipherFragment, Shard, IV) ->
encrypt_ticket_data(Plaintext, Shard, IV) ->
- AAD = additional_data(erlang:iolist_size(Plaintext) + 16), %% TagLen = 16
+ AAD = additional_data(<<"ticket">>, erlang:iolist_size(Plaintext) + 16), %% TagLen = 16
{OTP, Key} = make_otp_key(Shard),
{Content, CipherTag} = crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, Plaintext, AAD, 16, true),
<<Content/binary,CipherTag/binary,OTP/binary>>.
@@ -1434,16 +1471,34 @@ encrypt_ticket_data(Plaintext, Shard, IV) ->
decrypt_ticket_data(CipherFragment, Shard, IV) ->
Size = byte_size(Shard),
- AAD = additional_data(erlang:iolist_size(CipherFragment) - Size),
+ AAD = additional_data(<<"ticket">>, erlang:iolist_size(CipherFragment) - Size),
+ Len = byte_size(CipherFragment) - Size - 16,
+ case CipherFragment of
+ <<Encrypted:Len/binary,CipherTag:16/binary,OTP:Size/binary>> ->
+ Key = crypto:exor(OTP, Shard),
+ crypto:crypto_one_time_aead(aes_256_gcm, Key, IV,
+ Encrypted, AAD, CipherTag,
+ false);
+ _ ->
+ error
+ end.
+
+encrypt_data(ADTag, Plaintext, Shard, IV) ->
+ AAD = additional_data(ADTag, erlang:iolist_size(Plaintext) + 16), %% TagLen = 16
+ {OTP, Key} = make_otp_key(Shard),
+ {Content, CipherTag} = crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, Plaintext, AAD, 16, true),
+ <<Content/binary,CipherTag/binary,OTP/binary>>.
+
+decrypt_data(ADTag, CipherFragment, Shard, IV) ->
+ Size = byte_size(Shard),
+ AAD = additional_data(ADTag, erlang:iolist_size(CipherFragment) - Size),
Len = byte_size(CipherFragment) - Size - 16,
<<Encrypted:Len/binary,CipherTag:16/binary,OTP:Size/binary>> = CipherFragment,
Key = crypto:exor(OTP, Shard),
crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, Encrypted, AAD, CipherTag, false).
-
-additional_data(Length) ->
- <<"ticket",?UINT16(Length)>>.
-
+additional_data(Tag, Length) ->
+ <<Tag/binary,?UINT16(Length)>>.
make_otp_key(Shard) ->
Size = byte_size(Shard),
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 0a7c4560fb..9f2141b6f8 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -260,6 +260,18 @@
%% TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = { 0xC0, 0x0A }
-define(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#0A)>>).
+%% TLS_ECDHE_ECDSA_WITH_AES_128_CCM = {0xC0,0xAC}
+-define(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, <<?BYTE(16#C0), ?BYTE(16#AC)>>).
+
+%% TLS_ECDHE_ECDSA_WITH_AES_256_CCM = {0xC0,0xAD}
+-define(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, <<?BYTE(16#C0), ?BYTE(16#AD)>>).
+
+%% TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = {0xC0,0xAE}
+-define(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, <<?BYTE(16#C0), ?BYTE(16#AE)>>).
+
+%% TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = {0xC0,0xAF}
+-define(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, <<?BYTE(16#C0), ?BYTE(16#AF)>>).
+
%% ECDH_RSA
%% TLS_ECDH_RSA_WITH_NULL_SHA = { 0xC0, 0x0B }
diff --git a/lib/ssl/src/ssl_cipher_format.erl b/lib/ssl/src/ssl_cipher_format.erl
index f14b55bcf6..589b0facf8 100644
--- a/lib/ssl/src/ssl_cipher_format.erl
+++ b/lib/ssl/src/ssl_cipher_format.erl
@@ -77,13 +77,13 @@ suite_map_to_str(#{key_exchange := Kex,
cipher := Cipher,
mac := aead,
prf := PRF}) ->
- "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++
+ "TLS_" ++ kex_str(Kex) ++
"_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++
- "_" ++ string:to_upper(atom_to_list(PRF));
+ prf_str("_", PRF);
suite_map_to_str(#{key_exchange := Kex,
cipher := Cipher,
mac := Mac}) ->
- "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++
+ "TLS_" ++ kex_str(Kex) ++
"_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++
"_" ++ string:to_upper(atom_to_list(Mac)).
@@ -97,12 +97,6 @@ suite_str_to_map(SuiteStr)->
case string:split(Str0, "_WITH_") of
[Rest] ->
tls_1_3_suite_str_to_map(Rest);
- [Prefix, Kex | Rest] when Prefix == "SPR";
- Prefix == "PSK";
- Prefix == "DHE";
- Prefix == "ECDHE"
- ->
- pre_tls_1_3_suite_str_to_map(Prefix ++ "_" ++ Kex, Rest);
[Kex| Rest] ->
pre_tls_1_3_suite_str_to_map(Kex, Rest)
end.
@@ -116,26 +110,36 @@ suite_map_to_openssl_str(#{key_exchange := null} = Suite) ->
suite_map_to_str(Suite);
suite_map_to_openssl_str(#{key_exchange := rsa = Kex,
cipher := Cipher,
- mac := Mac}) when Cipher == "des_cbc";
- Cipher == "3des_ede_cbc" ->
+ mac := aead,
+ prf := PRF}) when PRF =/= default_prf ->
+ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
+ "-" ++ string:to_upper(atom_to_list(PRF));
+suite_map_to_openssl_str(#{key_exchange := Kex,
+ cipher := Cipher,
+ mac := Mac}) when (Kex == rsa) orelse
+ (Kex == srp_anon)
+ andalso
+ (Cipher == "des_cbc") orelse
+ (Cipher == "3des_ede_cbc") ->
openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
"-" ++ string:to_upper(atom_to_list(Mac));
suite_map_to_openssl_str(#{key_exchange := Kex,
cipher := chacha20_poly1305 = Cipher,
- mac := aead}) ->
- openssl_suite_start(string:to_upper(atom_to_list(Kex)))
- ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher)));
+ mac := aead,
+ prf := sha256}) ->
+ openssl_suite_start(kex_str(Kex), Cipher)
+ ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher)));
suite_map_to_openssl_str(#{key_exchange := Kex,
cipher := Cipher,
mac := aead,
prf := PRF}) ->
- openssl_suite_start(string:to_upper(atom_to_list(Kex)))
+ openssl_suite_start(kex_str(Kex), Cipher)
++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
- "-" ++ string:to_upper(atom_to_list(PRF));
+ prf_str("-", PRF);
suite_map_to_openssl_str(#{key_exchange := Kex,
- cipher := Cipher,
- mac := Mac}) ->
- openssl_suite_start(string:to_upper(atom_to_list(Kex)))
+ cipher := Cipher,
+ mac := Mac}) ->
+ openssl_suite_start(kex_str(Kex), Cipher)
++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
"-" ++ string:to_upper(atom_to_list(Mac)).
@@ -148,14 +152,20 @@ suite_openssl_str_to_map("DES-CBC3-SHA") ->
suite_str_to_map("TLS_RSA_WITH_3DES_EDE_CBC_SHA");
suite_openssl_str_to_map("SRP-DSS-DES-CBC3-SHA") ->
suite_str_to_map("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA");
-suite_openssl_str_to_map("DHE-RSA-" ++ Rest) ->
+suite_openssl_str_to_map("ADH" ++ Rest) ->
+ suite_openssl_str_to_map("DH-anon", Rest);
+suite_openssl_str_to_map("AECDH" ++ Rest) ->
+ suite_openssl_str_to_map("ECDH-anon", Rest);
+suite_openssl_str_to_map("EDH-RSA" ++ Rest) ->
suite_openssl_str_to_map("DHE-RSA", Rest);
-suite_openssl_str_to_map("DHE-DSS-" ++ Rest) ->
+suite_openssl_str_to_map("EDH-DSS-" ++ Rest) ->
suite_openssl_str_to_map("DHE-DSS", Rest);
-suite_openssl_str_to_map("EDH-RSA-" ++ Rest) ->
+suite_openssl_str_to_map("DHE-RSA-" ++ Rest) ->
suite_openssl_str_to_map("DHE-RSA", Rest);
-suite_openssl_str_to_map("EDH-DSS-" ++ Rest) ->
+suite_openssl_str_to_map("DHE-DSS-" ++ Rest) ->
suite_openssl_str_to_map("DHE-DSS", Rest);
+suite_openssl_str_to_map("DHE-PSK-" ++ Rest) ->
+ suite_openssl_str_to_map("DHE-PSK", Rest);
suite_openssl_str_to_map("DES" ++ _ = Rest) ->
suite_openssl_str_to_map("RSA", Rest);
suite_openssl_str_to_map("AES" ++ _ = Rest) ->
@@ -174,8 +184,6 @@ suite_openssl_str_to_map("RSA-PSK-" ++ Rest) ->
suite_openssl_str_to_map("RSA-PSK", Rest);
suite_openssl_str_to_map("RSA-" ++ Rest) ->
suite_openssl_str_to_map("RSA", Rest);
-suite_openssl_str_to_map("DHE-PSK-" ++ Rest) ->
- suite_openssl_str_to_map("DHE-PSK", Rest);
suite_openssl_str_to_map("ECDHE-PSK-" ++ Rest) ->
suite_openssl_str_to_map("ECDHE-PSK", Rest);
suite_openssl_str_to_map("PSK-" ++ Rest) ->
@@ -348,12 +356,12 @@ suite_bin_to_map(?TLS_DH_anon_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => dh_anon,
cipher => aes_128_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_DH_anon_WITH_AES_256_CBC_SHA256) ->
#{key_exchange => dh_anon,
cipher => aes_256_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
%%% PSK Cipher Suites RFC 4279
suite_bin_to_map(?TLS_PSK_WITH_RC4_128_SHA) ->
#{key_exchange => psk,
@@ -466,7 +474,7 @@ suite_bin_to_map(?TLS_PSK_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => psk,
cipher => aes_128_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => psk,
cipher => aes_256_cbc,
@@ -476,7 +484,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => dhe_psk,
cipher => aes_128_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => dhe_psk,
cipher => aes_256_cbc,
@@ -506,7 +514,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA256) ->
#{key_exchange => dhe_psk,
cipher => null,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA384) ->
#{key_exchange => dhe_psk,
cipher => null,
@@ -516,7 +524,7 @@ suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA256) ->
#{key_exchange => rsa_psk,
cipher => null,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA384) ->
#{key_exchange => rsa_psk,
cipher => null,
@@ -547,7 +555,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_cbc,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => ecdhe_psk,
cipher => aes_256_cbc,
@@ -557,7 +565,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => null,
mac => sha256,
- prf => default_prf};
+ prf => sha256};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) ->
#{key_exchange => ecdhe_psk,
cipher => null, mac => sha384,
@@ -566,22 +574,22 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) ->
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_gcm,
- mac => null,
+ mac => aead,
prf => sha256};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384) ->
#{key_exchange => ecdhe_psk,
cipher => aes_256_gcm,
- mac => null,
+ mac => aead,
prf => sha384};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_ccm,
- mac => null,
+ mac => aead,
prf => sha256};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_ccm_8,
- mac => null,
+ mac => aead,
prf => sha256};
%%% SRP Cipher Suites RFC 5054
suite_bin_to_map(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) ->
@@ -680,6 +688,26 @@ suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) ->
cipher => aes_256_cbc,
mac => sha,
prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CCM) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_ccm,
+ mac => aead,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CCM) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_ccm,
+ mac => aead,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_ccm_8,
+ mac => aead,
+ prf => default_prf};
+suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8) ->
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_ccm_8,
+ mac => aead,
+ prf => default_prf};
suite_bin_to_map(?TLS_ECDH_RSA_WITH_NULL_SHA) ->
#{key_exchange => ecdh_rsa,
cipher => null,
@@ -840,7 +868,7 @@ suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) ->
suite_bin_to_map(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) ->
#{key_exchange => dh_dss,
cipher => aes_128_gcm,
- mac => null,
+ mac => aead,
prf => sha256};
suite_bin_to_map(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) ->
#{key_exchange => dh_dss,
@@ -902,42 +930,42 @@ suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM) ->
#{key_exchange => psk,
cipher => aes_128_ccm,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM) ->
#{key_exchange => psk,
cipher => aes_256_ccm,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CCM) ->
#{key_exchange => dhe_psk,
cipher => aes_128_ccm,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CCM) ->
#{key_exchange => dhe_psk,
cipher => aes_256_ccm,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM_8) ->
#{key_exchange => psk,
cipher => aes_128_ccm_8,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM_8) ->
#{key_exchange => psk,
cipher => aes_256_ccm_8,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_DHE_WITH_AES_128_CCM_8) ->
#{key_exchange => dhe_psk,
cipher => aes_128_ccm_8,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(?TLS_PSK_DHE_WITH_AES_256_CCM_8) ->
#{key_exchange => dhe_psk,
cipher => aes_256_ccm_8,
mac => aead,
- prf => sha256};
+ prf => default_prf};
suite_bin_to_map(#{key_exchange := psk_dhe,
cipher := aes_256_ccm_8,
mac := aead,
@@ -980,12 +1008,12 @@ suite_bin_to_map(?TLS_AES_128_CCM_SHA256) ->
#{key_exchange => any,
cipher => aes_128_ccm,
mac => aead,
+ prf => sha256};
+suite_bin_to_map(?TLS_AES_128_CCM_8_SHA256) ->
+ #{key_exchange => any,
+ cipher => aes_128_ccm_8,
+ mac => aead,
prf => sha256}.
-%% suite_bin_to_map(?TLS_AES_128_CCM_8_SHA256) ->
-%% #{key_exchange => any,
-%% cipher => aes_128_ccm_8,
-%% mac => aead,
-%% prf => sha256}.
%%--------------------------------------------------------------------
-spec suite_legacy(cipher_suite() | internal_erl_cipher_suite()) -> old_erl_cipher_suite().
@@ -1297,22 +1325,22 @@ suite_map_to_bin(#{key_exchange := ecdhe_psk,
%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05
suite_map_to_bin(#{key_exchange := ecdhe_psk,
cipher := aes_128_gcm,
- mac := null,
+ mac := aead,
prf := sha256}) ->
?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256;
suite_map_to_bin(#{key_exchange := ecdhe_psk,
cipher := aes_256_gcm,
- mac := null,
+ mac := aead,
prf := sha384}) ->
?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384;
suite_map_to_bin(#{key_exchange := ecdhe_psk,
cipher := aes_128_ccm_8,
- mac := null,
+ mac := aead,
prf := sha256}) ->
?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256;
suite_map_to_bin(#{key_exchange := ecdhe_psk,
cipher := aes_128_ccm,
- mac := null,
+ mac := aead,
prf := sha256}) ->
?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256;
%%% SRP Cipher Suites RFC 5054
@@ -1393,6 +1421,22 @@ suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
cipher := aes_256_cbc,
mac := sha}) ->
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_ccm,
+ mac := aead}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_256_ccm,
+ mac := aead}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_ccm_8,
+ mac := aead}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8;
+suite_map_to_bin(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_256_ccm_8,
+ mac := aead}) ->
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8;
suite_map_to_bin(#{key_exchange := ecdh_rsa,
cipher := null,
mac := sha}) ->
@@ -1616,22 +1660,22 @@ suite_map_to_bin(#{key_exchange := dhe_rsa,
suite_map_to_bin(#{key_exchange := psk,
cipher := aes_128_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_WITH_AES_128_CCM;
suite_map_to_bin(#{key_exchange := psk,
cipher := aes_256_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_WITH_AES_256_CCM;
suite_map_to_bin(#{key_exchange := dhe_psk,
cipher := aes_128_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_PSK_WITH_AES_128_CCM;
suite_map_to_bin(#{key_exchange := dhe_psk,
cipher := aes_256_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_PSK_WITH_AES_256_CCM;
suite_map_to_bin(#{key_exchange := rsa,
cipher := aes_128_ccm,
@@ -1641,7 +1685,7 @@ suite_map_to_bin(#{key_exchange := rsa,
suite_map_to_bin(#{key_exchange := rsa,
cipher := aes_256_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_RSA_WITH_AES_256_CCM;
suite_map_to_bin(#{key_exchange := dhe_rsa,
cipher := aes_128_ccm,
@@ -1651,48 +1695,48 @@ suite_map_to_bin(#{key_exchange := dhe_rsa,
suite_map_to_bin(#{key_exchange := dhe_rsa,
cipher := aes_256_ccm,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_RSA_WITH_AES_256_CCM;
suite_map_to_bin(#{key_exchange := psk,
cipher := aes_128_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_WITH_AES_128_CCM_8;
suite_map_to_bin(#{key_exchange := psk,
cipher := aes_256_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_WITH_AES_256_CCM_8;
suite_map_to_bin(#{key_exchange := dhe_psk,
cipher := aes_128_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_DHE_WITH_AES_128_CCM_8;
suite_map_to_bin(#{key_exchange := dhe_psk,
cipher := aes_256_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_PSK_DHE_WITH_AES_256_CCM_8;
suite_map_to_bin(#{key_exchange := rsa,
cipher := aes_128_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_RSA_WITH_AES_128_CCM_8;
suite_map_to_bin(#{key_exchange := rsa,
cipher := aes_256_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_RSA_WITH_AES_256_CCM_8;
suite_map_to_bin(#{key_exchange := dhe_rsa,
cipher := aes_128_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_RSA_WITH_AES_128_CCM_8;
suite_map_to_bin(#{key_exchange := dhe_rsa,
cipher := aes_256_ccm_8,
mac := aead,
- prf := sha256}) ->
+ prf := default_prf}) ->
?TLS_DHE_RSA_WITH_AES_256_CCM_8;
%% TLS 1.3 Cipher Suites RFC8446
@@ -1715,12 +1759,12 @@ suite_map_to_bin(#{key_exchange := any,
cipher := aes_128_ccm,
mac := aead,
prf := sha256}) ->
- ?TLS_AES_128_CCM_SHA256.
-%% suite_map_to_bin(#{key_exchange := any,
-%% cipher := aes_128_ccm_8,
-%% mac := aead,
-%% prf := sha256}) ->
-%% ?TLS_AES_128_CCM_8_SHA256.
+ ?TLS_AES_128_CCM_SHA256;
+suite_map_to_bin(#{key_exchange := any,
+ cipher := aes_128_ccm_8,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_AES_128_CCM_8_SHA256.
tls_1_3_suite_str_to_map(CipherStr) ->
@@ -1740,21 +1784,42 @@ pre_tls_1_3_suite_str_to_map(KexStr, Rest) ->
cipher => Cipher,
prf => Prf
}.
-
-cipher_str_to_algs(_, CipherStr, "CCM"= End) -> %% PRE TLS 1.3
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-cipher_str_to_algs(_, CipherStr, "8" = End) -> %% PRE TLS 1.3
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-cipher_str_to_algs(_, CipherStr, "CHACHA20_POLY1305" = End) -> %% PRE TLS 1.3
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-cipher_str_to_algs(_, CipherStr0, "") -> %% TLS 1.3
+
+kex_str(srp_dss) ->
+ "SRP_SHA_DSS";
+kex_str(srp_rsa) ->
+ "SRP_SHA_RSA";
+kex_str(srp_anon) ->
+ "SRP_SHA";
+kex_str(dh_anon) ->
+ "DH_anon";
+kex_str(ecdh_anon) ->
+ "ECDH_anon";
+kex_str(Kex) ->
+ string:to_upper(atom_to_list(Kex)).
+
+prf_str(_, default_prf) ->
+ "";
+prf_str(Prefix, PRF) ->
+ Prefix ++ string:to_upper(atom_to_list(PRF)).
+
+cipher_str_to_algs(any, CipherStr0, "") -> %% TLS 1.3
[CipherStr, AlgStr] = string:split(CipherStr0, "_", trailing),
Hash = algo_str_to_atom(AlgStr),
Cipher = algo_str_to_atom(CipherStr),
{Cipher, aead, Hash};
+cipher_str_to_algs(_Kex, CipherStr, "CCM"= End) -> %% PRE TLS 1.3
+ Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
+ {Cipher, aead, default_prf};
+cipher_str_to_algs(_Kex, CipherStr, "GCM"= End) -> %% PRE TLS 1.3
+ Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
+ {Cipher, aead, default_prf};
+cipher_str_to_algs(_Kex, CipherStr, "8" = End) -> %% PRE TLS 1.3
+ Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
+ {Cipher, aead, default_prf};
+cipher_str_to_algs(_Kex, "CHACHA20_POLY1305" = CipherStr, "") -> %% PRE TLS 1.3
+ Cipher = algo_str_to_atom(CipherStr),
+ {Cipher, aead, sha256};
cipher_str_to_algs(Kex, CipherStr, HashStr) -> %% PRE TLS 1.3
Hash = algo_str_to_atom(HashStr),
Cipher = algo_str_to_atom(CipherStr),
@@ -1796,88 +1861,143 @@ openssl_is_aead_cipher("CHACHA20-POLY1305") ->
openssl_is_aead_cipher(CipherStr) ->
case string:split(CipherStr, "-", trailing) of
[_, Rest] ->
- (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "8");
+ (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "CCM8");
[_] ->
false
end.
algo_str_to_atom("SRP_SHA_DSS") ->
srp_dss;
+algo_str_to_atom("SRP_SHA_RSA") ->
+ srp_rsa;
+algo_str_to_atom("SRP_SHA") ->
+ srp_anon;
+algo_str_to_atom("SRP") ->
+ srp_anon;
algo_str_to_atom(AlgoStr) ->
erlang:list_to_existing_atom(string:to_lower(AlgoStr)).
+openssl_cipher_name(Kex, "3DES_EDE_CBC" ++ _) when Kex == ecdhe_psk;
+ Kex == srp_anon;
+ Kex == psk;
+ Kex == dhe_psk ->
+ "3DES-EDE-CBC";
openssl_cipher_name(_, "3DES_EDE_CBC" ++ _) ->
"DES-CBC3";
openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == rsa;
Kex == dhe_rsa;
+ Kex == dhe_dss;
+ Kex == ecdh_rsa;
Kex == ecdhe_rsa;
- Kex == ecdhe_ecdsa ->
+ Kex == ecdh_ecdsa;
+ Kex == ecdhe_ecdsa;
+ Kex == ecdh_anon;
+ Kex == dh_anon ->
openssl_name_concat(CipherStr);
openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == rsa;
Kex == dhe_rsa;
+ Kex == dhe_dss;
+ Kex == ecdh_rsa;
Kex == ecdhe_rsa;
- Kex == ecdhe_ecdsa ->
+ Kex == ecdh_ecdsa;
+ Kex == ecdhe_ecdsa;
+ Kex == ecdh_anon;
+ Kex == dh_anon ->
openssl_name_concat(CipherStr);
-openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == srp;
+openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == srp_anon;
Kex == srp_rsa ->
lists:append(string:replace(CipherStr, "_", "-", all));
-openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == srp;
+openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == srp_anon;
Kex == srp_rsa ->
lists:append(string:replace(CipherStr, "_", "-", all));
openssl_cipher_name(_, "AES_128_CBC" ++ _ = CipherStr) ->
openssl_name_concat(CipherStr) ++ "-CBC";
openssl_cipher_name(_, "AES_256_CBC" ++ _ = CipherStr) ->
openssl_name_concat(CipherStr) ++ "-CBC";
+openssl_cipher_name(_, "AES_128_GCM_8") ->
+ openssl_name_concat("AES_128_GCM") ++ "-GCM8";
+openssl_cipher_name(_, "AES_256_GCM_8") ->
+ openssl_name_concat("AES_256_GCM") ++ "-GCM8";
+openssl_cipher_name(_, "AES_128_CCM_8") ->
+ openssl_name_concat("AES_128_CCM") ++ "-CCM8";
+openssl_cipher_name(_, "AES_256_CCM_8") ->
+ openssl_name_concat("AES_256_CCM") ++ "-CCM8";
openssl_cipher_name(_, "AES_128_GCM" ++ _ = CipherStr) ->
openssl_name_concat(CipherStr) ++ "-GCM";
openssl_cipher_name(_, "AES_256_GCM" ++ _ = CipherStr) ->
openssl_name_concat(CipherStr) ++ "-GCM";
+openssl_cipher_name(_, "AES_128_CCM" ++ _ = CipherStr) ->
+ openssl_name_concat(CipherStr) ++ "-CCM";
+openssl_cipher_name(_, "AES_256_CCM" ++ _ = CipherStr) ->
+ openssl_name_concat(CipherStr) ++ "-CCM";
openssl_cipher_name(_, "RC4" ++ _) ->
"RC4";
openssl_cipher_name(_, CipherStr) ->
lists:append(string:replace(CipherStr, "_", "-", all)).
-
-openssl_suite_start(Kex) ->
- case openssl_kex_name(Kex) of
+openssl_suite_start(Kex, Cipher) ->
+ case openssl_kex_name(Kex, Cipher) of
"" ->
"";
Name ->
Name ++ "-"
end.
-openssl_kex_name("RSA") ->
+openssl_kex_name("RSA", _) ->
"";
-openssl_kex_name(Kex) ->
+openssl_kex_name("DH_anon", _) ->
+ "ADH";
+openssl_kex_name("ECDH_anon", _) ->
+ "AECDH";
+openssl_kex_name("SRP_SHA", _) ->
+ "SRP";
+openssl_kex_name("SRP_SHA_RSA", _) ->
+ "SRP-RSA";
+openssl_kex_name("SRP_SHA_DSS", _) ->
+ "SRP-DSS";
+openssl_kex_name("DHE_RSA", Cipher) when Cipher == des_cbc;
+ Cipher == '3des_ede_cbc' ->
+ "EDH-RSA";
+openssl_kex_name(Kex, _) ->
lists:append(string:replace(Kex, "_", "-", all)).
-
kex_name_from_openssl(Kex) ->
- lists:append(string:replace(Kex, "-", "_", all)).
+ case lists:append(string:replace(Kex, "-", "_", all)) of
+ "EDH-RSA" ->
+ "DHE_RSA";
+ "SRP" ->
+ "SRP_SHA";
+ Str ->
+ Str
+ end.
cipher_name_from_openssl("AES128") ->
"AES_128_CBC";
cipher_name_from_openssl("AES256") ->
"AES_256_CBC";
-cipher_name_from_openssl("AES128-CBC") ->
- "AES_128_CBC";
-cipher_name_from_openssl("AES256-CBC") ->
- "AES_256_CBC";
-cipher_name_from_openssl("AES-128-CBC") ->
- "AES_128_CBC";
-cipher_name_from_openssl("AES-256-CBC") ->
- "AES_256_CBC";
-cipher_name_from_openssl("AES128-GCM") ->
- "AES_128_GCM";
-cipher_name_from_openssl("AES256-GCM") ->
- "AES_256_GCM";
+cipher_name_from_openssl("AES128-CCM8") ->
+ "AES_128_CCM_8";
+cipher_name_from_openssl("AES256-CCM8") ->
+ "AES_256_CCM_8";
+cipher_name_from_openssl("AES128-" ++ Suffix) ->
+ "AES_128_" ++ lists:append(string:replace(Suffix, "-", "_", all));
+cipher_name_from_openssl("AES256-" ++ Suffix) ->
+ "AES_256_" ++ lists:append(string:replace(Suffix, "-", "_", all));
+cipher_name_from_openssl("AES128_" ++ Suffix) ->
+ "AES_128_" ++ Suffix;
+cipher_name_from_openssl("AES256_" ++ Suffix) ->
+ "AES_256_" ++ Suffix;
cipher_name_from_openssl("DES-CBC") ->
"DES_CBC";
cipher_name_from_openssl("DES-CBC3") ->
"3DES_EDE_CBC";
+cipher_name_from_openssl("3DES-EDE-CBC") ->
+ "3DES_EDE_CBC";
cipher_name_from_openssl("RC4") ->
"RC4_128";
+cipher_name_from_openssl("CHACHA20-POLY1305") ->
+ "CHACHA20_POLY1305";
cipher_name_from_openssl(Str) ->
- Str.
+ lists:append(string:replace(Str, "-", "_", all)).
openssl_name_concat(Str0) ->
[Str, _] = string:split(Str0, "_", trailing),
@@ -1887,8 +2007,8 @@ openssl_name_concat(Str0) ->
suite_openssl_str_to_map(Kex0, Rest) ->
Kex = algo_str_to_atom(kex_name_from_openssl(Kex0)),
- [CipherStr, AlgStr] = string:split(Rest, "-", trailing),
- {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(Kex, CipherStr, AlgStr),
+ [Part1, Part2] = string:split(Rest, "-", trailing),
+ {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(Kex, Part1, Part2),
#{key_exchange => Kex,
mac => Mac,
cipher => Cipher,
@@ -1896,19 +2016,25 @@ suite_openssl_str_to_map(Kex0, Rest) ->
}.
%% Does only need own implementation PRE TLS 1.3
-openssl_cipher_str_to_algs(_, CipherStr, "CCM"= End) ->
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-openssl_cipher_str_to_algs(_, CipherStr, "8" = End) ->
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
+openssl_cipher_str_to_algs(_, Part1, "CCM" = End) ->
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(Part1 ++ "_" ++ End)),
+ {Cipher, aead, default_prf};
+openssl_cipher_str_to_algs(_, Part1, "GCM" = End) ->
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(Part1 ++ "_" ++ End)),
+ {Cipher, aead, default_prf};
+openssl_cipher_str_to_algs(_, Part2, "CCM8") ->
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(Part2 ++ "-CCM-8")),
+ {Cipher, aead, default_prf};
+openssl_cipher_str_to_algs(_, Part2, "GCM8") ->
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(Part2 ++ "-GCM-8")),
+ {Cipher, aead, default_prf};
+openssl_cipher_str_to_algs(_, "CHACHA20", "POLY1305") ->
+ Cipher = chacha20_poly1305,
{Cipher, aead, sha256};
-openssl_cipher_str_to_algs(_, CipherStr, "POLY1305" = End) ->
- Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
- {Cipher, aead, sha256};
-openssl_cipher_str_to_algs(Kex, CipherStr, HashStr) ->
- Hash = algo_str_to_atom(HashStr),
- Cipher = algo_str_to_atom(cipher_name_from_openssl(CipherStr)),
- case openssl_is_aead_cipher(CipherStr) of
+openssl_cipher_str_to_algs(Kex, Part1, Part2) ->
+ Hash = algo_str_to_atom(Part2),
+ Cipher = algo_str_to_atom(cipher_name_from_openssl(string:strip(Part1, left, $-))),
+ case openssl_is_aead_cipher(Part1) of
true ->
{Cipher, aead, Hash};
false ->
diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_client_session_cache_db.erl
index c79ad1523b..d344294231 100644
--- a/lib/ssl/src/ssl_session_cache.erl
+++ b/lib/ssl/src/ssl_client_session_cache_db.erl
@@ -19,38 +19,47 @@
%%
%%
--module(ssl_session_cache).
+-module(ssl_client_session_cache_db).
-behaviour(ssl_session_cache_api).
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
--export([init/1, terminate/1, lookup/2, update/3, delete/2, foldl/3,
- select_session/2, size/1]).
+-export([init/1,
+ terminate/1,
+ lookup/2,
+ update/3,
+ delete/2,
+ foldl/3,
+ select_session/2,
+ size/1]).
%%--------------------------------------------------------------------
-%% Description: Return table reference. Called by ssl_manager process.
+%% Description: Return table reference. Called by ssl_manager process.
%%--------------------------------------------------------------------
init(Options) ->
ets:new(cache_name(proplists:get_value(role, Options)), [ordered_set, protected]).
%%--------------------------------------------------------------------
-%% Description: Handles cache table at termination of ssl manager.
+%% Description: Handles cache table at termination of ssl manager.
%%--------------------------------------------------------------------
terminate(Cache) ->
ets:delete(Cache).
%%--------------------------------------------------------------------
-%% Description: Looks up a cach entry. Should be callable from any
+%% Description: Looks up a cache entry. Should be callable from any
%% process.
%%--------------------------------------------------------------------
lookup(Cache, Key) ->
- case ets:lookup(Cache, Key) of
+ try ets:lookup(Cache, Key) of
[{Key, Session}] ->
Session;
[] ->
undefined
+ catch
+ _:_ ->
+ undefined
end.
%%--------------------------------------------------------------------
@@ -61,7 +70,7 @@ update(Cache, Key, Session) ->
ets:insert(Cache, {Key, Session}).
%%--------------------------------------------------------------------
-%% Description: Delets a cache entry.
+%% Description: Deletes a cache entry.
%% Will only be called from the ssl_manager process.
%%--------------------------------------------------------------------
delete(Cache, Key) ->
@@ -75,15 +84,27 @@ delete(Cache, Key) ->
%% is empty.Should be callable from any process
%%--------------------------------------------------------------------
foldl(Fun, Acc0, Cache) ->
- ets:foldl(Fun, Acc0, Cache).
-
+ try ets:foldl(Fun, Acc0, Cache) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ Acc0
+ end.
+
%%--------------------------------------------------------------------
%% Description: Selects a session that could be reused. Should be callable
%% from any process.
%%--------------------------------------------------------------------
-select_session(Cache, PartialKey) ->
- ets:select(Cache,
- [{{{PartialKey,'_'}, '$1'},[],['$1']}]).
+select_session(Cache, PartialKey) ->
+ try ets:select(Cache,
+ [{{{PartialKey,'_'}, '$1'},[],['$1']}]) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ []
+ end.
%%--------------------------------------------------------------------
%% Description: Returns the cache size
diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
index 10f95d5b3c..2832d76d42 100644
--- a/lib/ssl/src/ssl_config.erl
+++ b/lib/ssl/src/ssl_config.erl
@@ -26,8 +26,19 @@
-include("ssl_connection.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([init/2]).
+-define(DEFAULT_MAX_SESSION_CACHE, 1000).
+-export([init/2,
+ pre_1_3_session_opts/1,
+ get_max_early_data_size/0,
+ get_ticket_lifetime/0,
+ get_ticket_store_size/0,
+ get_internal_active_n/0
+ ]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
init(#{erl_dist := ErlDist,
key := Key,
keyfile := KeyFile,
@@ -44,6 +55,50 @@ init(#{erl_dist := ErlDist,
DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role),
{ok, Config#{private_key => PrivateKey, dh_params => DHParams}}.
+pre_1_3_session_opts(Role) ->
+ {Cb, InitArgs} = session_cb_opts(Role),
+ CbOpts = #{session_cb => Cb,
+ session_cb_init_args => InitArgs},
+ LifeTime = session_lifetime(Role),
+ Max = max_session_cache_size(Role),
+ CbOpts#{lifetime => LifeTime, max => Max}.
+
+get_ticket_lifetime() ->
+ case application:get_env(ssl, server_session_ticket_lifetime) of
+ {ok, Seconds} when is_integer(Seconds) andalso
+ Seconds =< 604800 -> %% MUST be less than 7 days
+ Seconds;
+ _ ->
+ 7200 %% Default 2 hours
+ end.
+
+get_ticket_store_size() ->
+ case application:get_env(ssl, server_session_ticket_store_size) of
+ {ok, Size} when is_integer(Size) ->
+ Size;
+ _ ->
+ 1000
+ end.
+
+get_max_early_data_size() ->
+ case application:get_env(ssl, server_session_ticket_max_early_data) of
+ {ok, Size} when is_integer(Size) ->
+ Size;
+ _ ->
+ ?DEFAULT_MAX_EARLY_DATA_SIZE
+ end.
+
+get_internal_active_n() ->
+ case application:get_env(ssl, internal_active_n) of
+ {ok, N} when is_integer(N) ->
+ N;
+ _ ->
+ ?INTERNAL_ACTIVE_N
+ end.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
init_manager_name(false) ->
put(ssl_manager, ssl_manager:name(normal)),
put(ssl_pem_cache, ssl_pem_cache:name(normal));
@@ -54,7 +109,7 @@ init_manager_name(true) ->
init_certificates(#{cacerts := CaCerts,
cacertfile := CACertFile,
certfile := CertFile,
- cert := Cert,
+ cert := OwnCerts,
crl_cache := CRLCache
}, Role) ->
{ok, Config} =
@@ -70,31 +125,31 @@ init_certificates(#{cacerts := CaCerts,
_:Reason ->
file_error(CACertFile, {cacertfile, Reason})
end,
- init_certificates(Cert, Config, CertFile, Role).
+ init_certificates(OwnCerts, Config, CertFile, Role).
init_certificates(undefined, Config, <<>>, _) ->
- {ok, Config#{own_certificate => undefined}};
+ {ok, Config#{own_certificates => undefined}};
init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, client) ->
try
- %% Ignoring potential proxy-certificates see:
- %% http://dev.globus.org/wiki/Security/ProxyFileFormat
- [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, Config#{own_certificate => OwnCert}}
+ %% OwnCert | [OwnCert | Chain]
+ OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
+ {ok, Config#{own_certificates => OwnCerts}}
catch _Error:_Reason ->
- {ok, Config#{own_certificate => undefined}}
+ {ok, Config#{own_certificates => undefined}}
end;
init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server) ->
try
- [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, Config#{own_certificate => OwnCert}}
+ %% OwnCert | [OwnCert | Chain]
+ OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
+ {ok, Config#{own_certificates => OwnCerts}}
catch
_:Reason ->
file_error(CertFile, {certfile, Reason})
end;
-init_certificates(Cert, Config, _, _) ->
- {ok, Config#{own_certificate => Cert}}.
+init_certificates(OwnCerts, Config, _, _) ->
+ {ok, Config#{own_certificates => OwnCerts}}.
init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa;
Alg == rsa;
Alg == dss ->
@@ -176,3 +231,67 @@ init_diffie_hellman(DbHandle,_, DHParamFile, server) ->
_:Reason ->
file_error(DHParamFile, {dhfile, Reason})
end.
+
+
+session_cb_init_args(client) ->
+ case application:get_env(ssl, client_session_cb_init_args) of
+ undefined ->
+ case application:get_env(ssl, session_cb_init_args) of
+ {ok, Args} when is_list(Args) ->
+ Args;
+ _ ->
+ []
+ end;
+ {ok, Args} ->
+ Args
+ end;
+session_cb_init_args(server) ->
+ case application:get_env(ssl, server_session_cb_init_args) of
+ undefined ->
+ case application:get_env(ssl, session_cb_init_args) of
+ {ok, Args} when is_list(Args) ->
+ Args;
+ _ ->
+ []
+ end;
+ {ok, Args} ->
+ Args
+ end.
+
+session_lifetime(_Role) ->
+ case application:get_env(ssl, session_lifetime) of
+ {ok, Time} when is_integer(Time) ->
+ Time;
+ _ ->
+ ?'24H_in_sec'
+ end.
+
+max_session_cache_size(client) ->
+ case application:get_env(ssl, session_cache_client_max) of
+ {ok, Size} when is_integer(Size) ->
+ Size;
+ _ ->
+ ?DEFAULT_MAX_SESSION_CACHE
+ end;
+max_session_cache_size(server) ->
+ case application:get_env(ssl, session_cache_server_max) of
+ {ok, Size} when is_integer(Size) ->
+ Size;
+ _ ->
+ ?DEFAULT_MAX_SESSION_CACHE
+ end.
+
+session_cb_opts(client = Role)->
+ case application:get_env(ssl, session_cb, ssl_client_session_cache_db) of
+ ssl_client_session_cache_db = ClientCb ->
+ {ClientCb, []};
+ ClientCb ->
+ {ClientCb, session_cb_init_args(Role)}
+ end;
+session_cb_opts(server = Role) ->
+ case application:get_env(ssl, session_cb, ssl_server_session_cache_db) of
+ ssl_server_session_cache_db = ServerCb ->
+ {ServerCb, []};
+ ServerCb ->
+ {ServerCb, session_cb_init_args(Role)}
+ end.
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
deleted file mode 100644
index 8e2e794280..0000000000
--- a/lib/ssl/src/ssl_connection.erl
+++ /dev/null
@@ -1,3117 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2013-2020. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%%----------------------------------------------------------------------
-%% Purpose: Common handling of a TLS/SSL/DTLS connection, see also
-%% tls_connection.erl and dtls_connection.erl
-%%----------------------------------------------------------------------
-
--module(ssl_connection).
-
--include("ssl_api.hrl").
--include("ssl_connection.hrl").
--include("ssl_handshake.hrl").
--include("ssl_alert.hrl").
--include("ssl_record.hrl").
--include("ssl_cipher.hrl").
--include("ssl_internal.hrl").
--include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
--include_lib("kernel/include/logger.hrl").
-
-%% Setup
-
--export([connect/8, handshake/7, handshake/2, handshake/3, handle_common_event/5,
- handshake_continue/3, handshake_cancel/1,
- socket_control/4, socket_control/5]).
-
-%% User Events
--export([send/2, recv/3, close/2, shutdown/2,
- new_user/2, get_opts/2, set_opts/2,
- peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5,
- connection_information/2
- ]).
-
-%% Alert and close handling
--export([handle_own_alert/4, handle_alert/3,
- handle_normal_shutdown/3,
- handle_trusted_certs_db/1,
- maybe_invalidate_session/6]).
-
-%% Data handling
--export([read_application_data/2, internal_renegotiation/2]).
-
-%% Help functions for tls|dtls_connection.erl
--export([handle_session/7, ssl_config/3,
- prepare_connection/2, hibernate_after/3]).
-
-%% General gen_statem state functions with extra callback argument
-%% to determine if it is an SSL/TLS or DTLS gen_statem machine
--export([init/4, error/4, hello/4, user_hello/4, abbreviated/4, certify/4, cipher/4,
- connection/4, downgrade/4]).
-
-%% gen_statem callbacks
--export([terminate/3, format_status/2]).
-
-%% Erlang Distribution export
--export([dist_handshake_complete/2]).
-
-%%====================================================================
-%% Setup
-%%====================================================================
-%%--------------------------------------------------------------------
--spec connect(tls_connection | dtls_connection,
- ssl:host(), inet:port_number(),
- port() | {tuple(), port()}, %% TLS | DTLS
- {ssl_options(), #socket_options{},
- %% Tracker only needed on server side
- undefined},
- pid(), tuple(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Connect to an ssl server.
-%%--------------------------------------------------------------------
-connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
- try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
- Timeout)
- catch
- exit:{noproc, _} ->
- {error, ssl_not_started}
- end.
-%%--------------------------------------------------------------------
--spec handshake(tls_connection | dtls_connection,
- inet:port_number(), port(),
- {ssl_options(), #socket_options{}, undefined | pid()},
- pid(), tuple(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Performs accept on an ssl listen socket. e.i. performs
-%% ssl handshake.
-%%--------------------------------------------------------------------
-handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
- try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
- CbInfo, Timeout)
- catch
- exit:{noproc, _} ->
- {error, ssl_not_started}
- end.
-
-%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
- {ok, #sslsocket{}, map()}| {error, reason()}.
-%%
-%% Description: Starts ssl handshake.
-%%--------------------------------------------------------------------
-handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) ->
- case call(Pid, {start, Timeout}) of
- connected ->
- {ok, Socket};
- {ok, Ext} ->
- {ok, Socket, no_records(Ext)};
- Error ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) ->
- {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}.
-%%
-%% Description: Starts ssl handshake with some new options
-%%--------------------------------------------------------------------
-handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
- case call(Pid, {start, SslOptions, Timeout}) of
- connected ->
- {ok, Socket};
- Error ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()],
- timeout()) -> {ok, #sslsocket{}}| {error, reason()}.
-%%
-%% Description: Continues handshake with new options
-%%--------------------------------------------------------------------
-handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
- case call(Pid, {handshake_continue, SslOptions, Timeout}) of
- connected ->
- {ok, Socket};
- Error ->
- Error
- end.
-%%--------------------------------------------------------------------
--spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}.
-%%
-%% Description: Cancels connection
-%%--------------------------------------------------------------------
-handshake_cancel(#sslsocket{pid = [Pid|_]}) ->
- case call(Pid, cancel) of
- closed ->
- ok;
- Error ->
- Error
- end.
-%--------------------------------------------------------------------
--spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%
-%% Description: Set the ssl process to own the accept socket
-%%--------------------------------------------------------------------
-socket_control(Connection, Socket, Pid, Transport) ->
- socket_control(Connection, Socket, Pid, Transport, undefined).
-
-%--------------------------------------------------------------------
--spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), [pid()] | atom()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-%%--------------------------------------------------------------------
-socket_control(Connection, Socket, Pids, Transport, udp_listener) ->
- %% dtls listener process must have the socket control
- {ok, Connection:socket(Pids, Transport, Socket, undefined)};
-
-socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
- case Transport:controlling_process(Socket, Pid) of
- ok ->
- {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
- {error, Reason} ->
- {error, Reason}
- end;
-socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, Trackers) ->
- case Transport:controlling_process(Socket, Pid) of
- ok ->
- {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
- {error, Reason} ->
- {error, Reason}
- end.
-
-
-%%====================================================================
-%% User events
-%%====================================================================
-
-%%--------------------------------------------------------------------
--spec send(pid(), iodata()) -> ok | {error, reason()}.
-%%
-%% Description: Sends data over the ssl connection
-%%--------------------------------------------------------------------
-send(Pid, Data) ->
- call(Pid, {application_data,
- %% iolist_to_iovec should really
- %% be called iodata_to_iovec()
- erlang:iolist_to_iovec(Data)}).
-
-%%--------------------------------------------------------------------
--spec recv(pid(), integer(), timeout()) ->
- {ok, binary() | list()} | {error, reason()}.
-%%
-%% Description: Receives data when active = false
-%%--------------------------------------------------------------------
-recv(Pid, Length, Timeout) ->
- call(Pid, {recv, Length, Timeout}).
-
-%%--------------------------------------------------------------------
--spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}.
-%%
-%% Description: Get the SNI hostname
-%%--------------------------------------------------------------------
-connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) ->
- call(Pid, {connection_information, IncludeSecrityInfo}).
-
-%%--------------------------------------------------------------------
--spec close(pid(), {close, Timeout::integer() |
- {NewController::pid(), Timeout::integer()}}) ->
- ok | {ok, port()} | {error, reason()}.
-%%
-%% Description: Close an ssl connection
-%%--------------------------------------------------------------------
-close(ConnectionPid, How) ->
- case call(ConnectionPid, How) of
- {error, closed} ->
- ok;
- Other ->
- Other
- end.
-%%--------------------------------------------------------------------
--spec shutdown(pid(), atom()) -> ok | {error, reason()}.
-%%
-%% Description: Same as gen_tcp:shutdown/2
-%%--------------------------------------------------------------------
-shutdown(ConnectionPid, How) ->
- call(ConnectionPid, {shutdown, How}).
-
-%%--------------------------------------------------------------------
--spec new_user(pid(), pid()) -> ok | {error, reason()}.
-%%
-%% Description: Changes process that receives the messages when active = true
-%% or once.
-%%--------------------------------------------------------------------
-new_user(ConnectionPid, User) ->
- call(ConnectionPid, {new_user, User}).
-
-%%--------------------------------------------------------------------
--spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}.
-%%
-%% Description: Returns the negotiated protocol
-%%--------------------------------------------------------------------
-negotiated_protocol(ConnectionPid) ->
- call(ConnectionPid, negotiated_protocol).
-
-%%--------------------------------------------------------------------
--spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
-%%
-%% Description: Same as inet:getopts/2
-%%--------------------------------------------------------------------
-get_opts(ConnectionPid, OptTags) ->
- call(ConnectionPid, {get_opts, OptTags}).
-%%--------------------------------------------------------------------
--spec set_opts(pid(), list()) -> ok | {error, reason()}.
-%%
-%% Description: Same as inet:setopts/2
-%%--------------------------------------------------------------------
-set_opts(ConnectionPid, Options) ->
- call(ConnectionPid, {set_opts, Options}).
-
-%%--------------------------------------------------------------------
--spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}.
-%%
-%% Description: Returns the peer cert
-%%--------------------------------------------------------------------
-peer_certificate(ConnectionPid) ->
- call(ConnectionPid, peer_certificate).
-
-%%--------------------------------------------------------------------
--spec renegotiation(pid()) -> ok | {error, reason()}.
-%%
-%% Description: Starts a renegotiation of the ssl session.
-%%--------------------------------------------------------------------
-renegotiation(ConnectionPid) ->
- call(ConnectionPid, renegotiate).
-
-%%--------------------------------------------------------------------
--spec internal_renegotiation(pid(), ssl_record:connection_states()) ->
- ok.
-%%
-%% Description: Starts a renegotiation of the ssl session.
-%%--------------------------------------------------------------------
-internal_renegotiation(ConnectionPid, #{current_write := WriteState}) ->
- gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}).
-
-dist_handshake_complete(ConnectionPid, DHandle) ->
- gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}).
-
-%%--------------------------------------------------------------------
--spec prf(pid(), binary() | 'master_secret', binary(),
- [binary() | ssl:prf_random()], non_neg_integer()) ->
- {ok, binary()} | {error, reason()} | {'EXIT', term()}.
-%%
-%% Description: use a ssl sessions TLS PRF to generate key material
-%%--------------------------------------------------------------------
-prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
- call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
-
-
-%%====================================================================
-%% Alert and close handling
-%%====================================================================
-handle_own_alert(Alert0, _, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- ssl_options = #{log_level := LogLevel}} = State) ->
- try %% Try to tell the other side
- send_alert(Alert0, StateName, State)
- catch _:_ -> %% Can crash if we are in a uninitialized state
- ignore
- end,
- try %% Try to tell the local user
- Alert = Alert0#alert{role = Role},
- log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert),
- handle_normal_shutdown(Alert,StateName, State)
- catch _:_ ->
- ok
- end,
- {stop, {shutdown, own_alert}, State}.
-
-handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
- socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- trackers = Trackers},
- handshake_env = #handshake_env{renegotiation = {false, first}},
- start_or_recv_from = StartFrom} = State) ->
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection);
-
-handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
- socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- trackers = Trackers},
- connection_env = #connection_env{user_application = {_Mon, Pid}},
- socket_options = Opts,
- start_or_recv_from = RecvFrom} = State) ->
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
-
-handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
- #state{static_env = #static_env{role = Role,
- socket = Socket,
- host = Host,
- port = Port,
- trackers = Trackers,
- transport_cb = Transport,
- protocol_cb = Connection},
- connection_env = #connection_env{user_application = {_Mon, Pid}},
- ssl_options = #{log_level := LogLevel},
- start_or_recv_from = From,
- session = Session,
- socket_options = Opts} = State) ->
- invalidate_session(Role, Host, Port, Session),
- Alert = Alert0#alert{role = opposite_role(Role)},
- log_alert(LogLevel, Role, Connection:protocol_name(),
- StateName, Alert),
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
- {stop, {shutdown, normal}, State};
-
-handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
- downgrade= StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, Alert}]};
-handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0,
- StateName, #state{static_env = #static_env{role = Role}} = State) ->
- Alert = Alert0#alert{role = opposite_role(Role)},
- handle_normal_shutdown(Alert, StateName, State),
- {stop,{shutdown, peer_close}, State};
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = {true, internal}},
- ssl_options = #{log_level := LogLevel}} = State) ->
- Alert = Alert0#alert{role = opposite_role(Role)},
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName, Alert),
- handle_normal_shutdown(Alert, StateName, State),
- {stop,{shutdown, peer_close}, State};
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
- ssl_options = #{log_level := LogLevel}
- } = State0) ->
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
- gen_statem:reply(From, {error, renegotiation_rejected}),
- State = Connection:reinit_handshake_data(State0),
- Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}});
-
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
- ssl_options = #{log_level := LogLevel}
- } = State0) ->
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
- gen_statem:reply(From, {error, renegotiation_rejected}),
- %% Go back to connection!
- State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
- Connection:next_event(connection, no_record, State);
-
-%% Gracefully log and ignore all other warning alerts
-handle_alert(#alert{level = ?WARNING} = Alert, StateName,
- #state{static_env = #static_env{role = Role,
- protocol_cb = Connection},
- ssl_options = #{log_level := LogLevel}} = State) ->
- log_alert(LogLevel, Role,
- Connection:protocol_name(), StateName,
- Alert#alert{role = opposite_role(Role)}),
- Connection:next_event(StateName, no_record, State).
-
-maybe_invalidate_session(undefined,_, _, _, _, _) ->
- ok;
-maybe_invalidate_session({3, 4},_, _, _, _, _) ->
- ok;
-maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 ->
- maybe_invalidate_session(Type, Role, Host, Port, Session).
-
-%%====================================================================
-%% Data handling
-%%====================================================================
-passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
- %% Assert! Erl distribution uses active sockets
- connection_env = #connection_env{erl_dist_handle = undefined}}
- = State0, StateName, Connection, StartTimerAction) ->
- case BufferSize of
- 0 ->
- Connection:next_event(StateName, no_record, State0, StartTimerAction);
- _ ->
- case read_application_data(State0, Front, BufferSize, Rear) of
- {stop, _, _} = ShutdownError ->
- ShutdownError;
- {Record, State} ->
- case State#state.start_or_recv_from of
- undefined ->
- %% Cancel recv timeout as data has been delivered
- Connection:next_event(StateName, Record, State,
- [{{timeout, recv}, infinity, timeout}]);
- _ ->
- Connection:next_event(StateName, Record, State, StartTimerAction)
- end
- end
- end.
-
-read_application_data(
- Data,
- #state{
- user_data_buffer = {Front0,BufferSize0,Rear0},
- connection_env = #connection_env{erl_dist_handle = DHandle}} = State) ->
- %%
- Front = Front0,
- BufferSize = BufferSize0 + byte_size(Data),
- Rear = [Data|Rear0],
- case DHandle of
- undefined ->
- read_application_data(State, Front, BufferSize, Rear);
- _ ->
- try read_application_dist_data(DHandle, Front, BufferSize, Rear) of
- Buffer ->
- {no_record, State#state{user_data_buffer = Buffer}}
- catch error:_ ->
- {stop,disconnect,
- State#state{user_data_buffer = {Front,BufferSize,Rear}}}
- end
- end.
-
-
-read_application_data(#state{
- socket_options = SocketOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) ->
- read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead).
-
-%% Pick binary from queue front, if empty wait for more data
-read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
- read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin);
-read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) ->
- 0 = BufferSize, % Assert
- {no_record, State#state{socket_options = SocketOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {Front,BufferSize,Rear}}};
-read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
- [Bin|Front] = lists:reverse(Rear),
- read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin).
-
-read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) ->
- %% Done with this binary - get next
- read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead);
-read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) ->
- %% Decode one packet from a binary
- case get_data(SocketOpts0, BytesToRead, Bin0) of
- {ok, Data, Bin} -> % Send data
- BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)),
- read_application_data_deliver(
- State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data);
- {more, undefined} ->
- %% We need more data, do not know how much
- if
- byte_size(Bin0) < BufferSize0 ->
- %% We have more data in the buffer besides the first binary - concatenate all and retry
- Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
- read_application_data_bin(
- State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin);
- true ->
- %% All data is in the first binary, no use to retry - wait for more
- {no_record, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}
- end;
- {more, Size} when Size =< BufferSize0 ->
- %% We have a packet in the buffer - collect it in a binary and decode
- {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]),
- Bin = iolist_to_binary(Data),
- read_application_data_bin(
- State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin);
- {more, _Size} ->
- %% We do not have a packet in the buffer - wait for more
- {no_record, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
- passive ->
- {no_record, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
- {error,_Reason} ->
- %% Invalid packet in packet mode
- #state{
- static_env =
- #static_env{
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- trackers = Trackers},
- connection_env =
- #connection_env{user_application = {_Mon, Pid}}} = State,
- Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
- deliver_packet_error(
- Connection:pids(State), Transport, Socket, SocketOpts0,
- Buffer, Pid, RecvFrom, Trackers, Connection),
- {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- user_data_buffer = {[Buffer],BufferSize0,[]}}}
- end.
-
-read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) ->
- #state{
- static_env =
- #static_env{
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- trackers = Trackers},
- connection_env =
- #connection_env{user_application = {_Mon, Pid}}} = State,
- SocketOpts =
- deliver_app_data(
- Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection),
- if
- SocketOpts#socket_options.active =:= false ->
- %% Passive mode, wait for active once or recv
- {no_record,
- State#state{
- user_data_buffer = {Front,BufferSize,Rear},
- start_or_recv_from = undefined,
- bytes_to_read = undefined,
- socket_options = SocketOpts
- }};
- true -> %% Try to deliver more data
- read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined)
- end.
-
-
-read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) ->
- read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin);
-read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) ->
- BufferSize = 0,
- {Front,BufferSize,Rear};
-read_application_dist_data(DHandle, [], BufferSize, Rear) ->
- [Bin|Front] = lists:reverse(Rear),
- read_application_dist_data(DHandle, Front, BufferSize, [], Bin).
-%%
-read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
- case Bin0 of
- %%
- %% START Optimization
- %% It is cheaper to match out several packets in one match operation than to loop for each
- <<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary,
- SizeC:32, DataC:SizeC/binary,
- SizeD:32, DataD:SizeD/binary, Rest/binary>>
- when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD ->
- %% We have 4 complete packets in the first binary
- erlang:dist_ctrl_put_data(DHandle, DataA),
- erlang:dist_ctrl_put_data(DHandle, DataB),
- erlang:dist_ctrl_put_data(DHandle, DataC),
- erlang:dist_ctrl_put_data(DHandle, DataD),
- read_application_dist_data(
- DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest);
- <<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary,
- SizeC:32, DataC:SizeC/binary, Rest/binary>>
- when 0 < SizeA, 0 < SizeB, 0 < SizeC ->
- %% We have 3 complete packets in the first binary
- erlang:dist_ctrl_put_data(DHandle, DataA),
- erlang:dist_ctrl_put_data(DHandle, DataB),
- erlang:dist_ctrl_put_data(DHandle, DataC),
- read_application_dist_data(
- DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest);
- <<SizeA:32, DataA:SizeA/binary,
- SizeB:32, DataB:SizeB/binary, Rest/binary>>
- when 0 < SizeA, 0 < SizeB ->
- %% We have 2 complete packets in the first binary
- erlang:dist_ctrl_put_data(DHandle, DataA),
- erlang:dist_ctrl_put_data(DHandle, DataB),
- read_application_dist_data(
- DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest);
- %% END Optimization
- %%
- %% Basic one packet code path
- <<Size:32, Data:Size/binary, Rest/binary>> ->
- %% We have a complete packet in the first binary
- 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
- read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest);
- <<Size:32, FirstData/binary>> when 4+Size =< BufferSize ->
- %% We have a complete packet in the buffer
- %% - fetch the missing content from the buffer front
- {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]),
- 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
- read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear);
- <<Bin/binary>> ->
- %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we
- %% match out the whole binary which will trick the optimization into keeping the match context
- %% for the first binary contains complete packet code above
- case Bin of
- <<_Size:32, _InsufficientData/binary>> ->
- %% We have a length field in the first binary but there is not enough data
- %% in the buffer to form a complete packet - await more data
- {[Bin|Front0],BufferSize,Rear0};
- <<IncompleteLengthField/binary>> when 4 < BufferSize ->
- %% We do not have a length field in the first binary but the buffer
- %% contains enough data to maybe form a packet
- %% - fetch a tiny binary from the buffer front to complete the length field
- {LengthField,Front,Rear} =
- case IncompleteLengthField of
- <<>> ->
- iovec_from_front(4, Front0, Rear0, []);
- _ ->
- iovec_from_front(
- 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField])
- end,
- LengthBin = iolist_to_binary(LengthField),
- read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin);
- <<IncompleteLengthField/binary>> ->
- %% We do not have enough data in the buffer to even form a length field - await more data
- case IncompleteLengthField of
- <<>> ->
- {Front0,BufferSize,Rear0};
- _ ->
- {[IncompleteLengthField|Front0],BufferSize,Rear0}
- end
- end
- end.
-
-iovec_from_front(0, Front, Rear, Acc) ->
- {lists:reverse(Acc),Front,Rear};
-iovec_from_front(Size, [], Rear, Acc) ->
- case Rear of
- %% Avoid lists:reverse/1 for simple cases.
- %% Case clause for [] to avoid infinite loop.
- [_] ->
- iovec_from_front(Size, Rear, [], Acc);
- [Bin2,Bin1] ->
- iovec_from_front(Size, [Bin1,Bin2], [], Acc);
- [Bin3,Bin2,Bin1] ->
- iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc);
- [_,_,_|_] = Rear ->
- iovec_from_front(Size, lists:reverse(Rear), [], Acc)
- end;
-iovec_from_front(Size, [Bin|Front], Rear, []) ->
- case Bin of
- <<Last:Size/binary>> -> % Just enough
- {[Last],Front,Rear};
- <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
- {[Last],[Rest|Front],Rear};
- <<>> -> % Not enough, skip empty binaries
- iovec_from_front(Size, Front, Rear, []);
- <<_/binary>> -> % Not enough
- BinSize = byte_size(Bin),
- iovec_from_front(Size - BinSize, Front, Rear, [Bin])
- end;
-iovec_from_front(Size, [Bin|Front], Rear, Acc) ->
- case Bin of
- <<Last:Size/binary>> -> % Just enough
- {lists:reverse(Acc, [Last]),Front,Rear};
- <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
- {lists:reverse(Acc, [Last]),[Rest|Front],Rear};
- <<>> -> % Not enough, skip empty binaries
- iovec_from_front(Size, Front, Rear, Acc);
- <<_/binary>> -> % Not enough
- BinSize = byte_size(Bin),
- iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc])
- end.
-
-
-%%====================================================================
-%% Help functions for tls|dtls_connection.erl
-%%====================================================================
-%%--------------------------------------------------------------------
--spec handle_session(#server_hello{}, ssl_record:ssl_version(),
- binary(), ssl_record:connection_states(), _,_, #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-handle_session(#server_hello{cipher_suite = CipherSuite,
- compression_method = Compression},
- Version, NewId, ConnectionStates, ProtoExt, Protocol0,
- #state{session = #session{session_id = OldId},
- handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv,
- connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) ->
- #{key_exchange := KeyAlgorithm} =
- ssl_cipher_format:suite_bin_to_map(CipherSuite),
-
- PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
-
- {ExpectNPN, Protocol} = case Protocol0 of
- undefined ->
-
- {false, CurrentProtocol};
- _ ->
- {ProtoExt =:= npn, Protocol0}
- end,
-
- State = State0#state{connection_states = ConnectionStates,
- handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm,
- premaster_secret = PremasterSecret,
- expecting_next_protocol_negotiation = ExpectNPN,
- negotiated_protocol = Protocol},
- connection_env = CEnv#connection_env{negotiated_version = Version}},
-
- case ssl_session:is_new(OldId, NewId) of
- true ->
- handle_new_session(NewId, CipherSuite, Compression,
- State#state{connection_states = ConnectionStates});
- false ->
- handle_resumed_session(NewId,
- State#state{connection_states = ConnectionStates})
- end.
-
-%%--------------------------------------------------------------------
--spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
-%%--------------------------------------------------------------------
-ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
- handshake_env = HsEnv,
- connection_env = CEnv} = State0) ->
- {ok, #{cert_db_ref := Ref,
- cert_db_handle := CertDbHandle,
- fileref_db_handle := FileRefHandle,
- session_cache := CacheHandle,
- crl_db_info := CRLDbHandle,
- private_key := Key,
- dh_params := DHParams,
- own_certificate := OwnCert}} =
- ssl_config:init(Opts, Role),
- TimeStamp = erlang:monotonic_time(),
- Session = State0#state.session,
-
- State0#state{session = Session#session{own_certificate = OwnCert,
- time_stamp = TimeStamp},
- static_env = InitStatEnv0#static_env{
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle
- },
- handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams},
- connection_env = CEnv#connection_env{private_key = Key},
- ssl_options = Opts}.
-
-%%====================================================================
-%% gen_statem general state functions with connection cb argument
-%%====================================================================
-%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
- {start, timeout()} | {start, {list(), list()}, timeout()}| term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-
-init({call, From}, {start, Timeout}, State0, Connection) ->
- Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From},
- [{{timeout, handshake}, Timeout, close}]);
-init({call, From}, {start, {Opts, EmOpts}, Timeout},
- #state{static_env = #static_env{role = Role},
- ssl_options = OrigSSLOptions,
- socket_options = SockOpts} = State0, Connection) ->
- try
- SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
- State = ssl_config(SslOpts, Role, State0),
- init({call, From}, {start, Timeout},
- State#state{ssl_options = SslOpts,
- socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
- catch throw:Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0}
- end;
-init({call, From}, {new_user, _} = Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-init({call, From}, _Msg, _State, _Connection) ->
- {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]};
-init(_Type, _Event, _State, _Connection) ->
- {keep_state_and_data, [postpone]}.
-
-%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
- {start, timeout()} | term(), #state{},
- tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-error({call, From}, {close, _}, State, _Connection) ->
- {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State};
-error({call, From}, _Msg, State, _Connection) ->
- {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}.
-
-%%--------------------------------------------------------------------
--spec hello(gen_statem:event_type(),
- #hello_request{} | #server_hello{} | term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-hello({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) ->
- do_server_hello(Type, ServerHelloExt, State, Connection);
-hello(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-hello(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) ->
- gen_statem:reply(From, ok),
- handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
- Version, ?FUNCTION_NAME, State);
-user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
- #state{static_env = #static_env{role = Role},
- handshake_env = #handshake_env{hello = Hello},
- ssl_options = Options0} = State0, _Connection) ->
- Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
- State = ssl_config(Options, Role, State0),
- {next_state, hello, State#state{start_or_recv_from = From},
- [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
-user_hello(_, _, _, _) ->
- {keep_state_and_data, [postpone]}.
-
-%%--------------------------------------------------------------------
--spec abbreviated(gen_statem:event_type(),
- #hello_request{} | #finished{} | term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-abbreviated({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{tls_handshake_history = Hist,
- expecting_finished = true} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{master_secret = MasterSecret},
- connection_states = ConnectionStates0} =
- State0, Connection) ->
- case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client,
- get_current_prf(ConnectionStates0, write),
- MasterSecret, Hist) of
- verified ->
- ConnectionStates =
- ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
- {Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates,
- handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection),
- Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
- end;
-abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{tls_handshake_history = Hist0},
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{master_secret = MasterSecret},
- connection_states = ConnectionStates0} = State0, Connection) ->
- case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
- get_pending_prf(ConnectionStates0, write),
- MasterSecret, Hist0) of
- verified ->
- ConnectionStates1 =
- ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
- {#state{handshake_env = HsEnv} = State1, Actions} =
- finalize_handshake(State0#state{connection_states = ConnectionStates1},
- ?FUNCTION_NAME, Connection),
- {Record, State} = prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection),
- Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
- end;
-%% only allowed to send next_protocol message after change cipher spec
-%% & before finished message and it is not allowed during renegotiation
-abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State,
- Connection) ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
- expecting_next_protocol_negotiation = false}});
-abbreviated(internal,
- #change_cipher_spec{type = <<1>>},
- #state{connection_states = ConnectionStates0,
- handshake_env = HsEnv} = State, Connection) ->
- ConnectionStates1 =
- ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
- ConnectionStates1,
- handshake_env = HsEnv#handshake_env{expecting_finished = true}});
-abbreviated(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-abbreviated(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
--spec certify(gen_statem:event_type(),
- #hello_request{} | #certificate{} | #server_key_exchange{} |
- #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-certify({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-certify(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-certify(internal, #certificate{asn1_certificates = []},
- #state{static_env = #static_env{role = server},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{verify := verify_peer,
- fail_if_no_peer_cert := true}} =
- State, _) ->
- Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided),
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
-certify(internal, #certificate{asn1_certificates = []},
- #state{static_env = #static_env{role = server},
- ssl_options = #{verify := verify_peer,
- fail_if_no_peer_cert := false}} =
- State0, Connection) ->
- Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
-certify(internal, #certificate{},
- #state{static_env = #static_env{role = server},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{verify := verify_none}} =
- State, _) ->
- Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
-certify(internal, #certificate{} = Cert,
- #state{static_env = #static_env{
- role = Role,
- host = Host,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbInfo},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = Opts} = State, Connection) ->
- case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
- Opts, CRLDbInfo, Role, Host) of
- {PeerCert, PublicKeyInfo} ->
- handle_peer_cert(Role, PeerCert, PublicKeyInfo,
- State#state{client_certificate_requested = false}, Connection);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
- end;
-certify(internal, #server_key_exchange{exchange_keys = Keys},
- #state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- public_key_info = PubKeyInfo} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = Session,
- connection_states = ConnectionStates} = State, Connection)
- when KexAlg == dhe_dss;
- KexAlg == dhe_rsa;
- KexAlg == ecdhe_rsa;
- KexAlg == ecdhe_ecdsa;
- KexAlg == dh_anon;
- KexAlg == ecdh_anon;
- KexAlg == psk;
- KexAlg == dhe_psk;
- KexAlg == ecdhe_psk;
- KexAlg == rsa_psk;
- KexAlg == srp_dss;
- KexAlg == srp_rsa;
- KexAlg == srp_anon ->
-
- Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)),
-
- %% Use negotiated value if TLS-1.2 otherwhise return default
- HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)),
-
- case is_anonymous(KexAlg) of
- true ->
- calculate_secret(Params#server_key_params.params,
- State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection);
- false ->
- case ssl_handshake:verify_server_key(Params, HashSign,
- ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of
- true ->
- calculate_secret(Params#server_key_params.params,
- State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign},
- session = session_handle_params(Params#server_key_params.params, Session)},
- Connection);
- false ->
- handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR),
- Version, ?FUNCTION_NAME, State)
- end
- end;
-certify(internal, #certificate_request{},
- #state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg},
- connection_env = #connection_env{negotiated_version = Version}} = State, _)
- when KexAlg == dh_anon;
- KexAlg == ecdh_anon;
- KexAlg == psk;
- KexAlg == dhe_psk;
- KexAlg == ecdhe_psk;
- KexAlg == rsa_psk;
- KexAlg == srp_dss;
- KexAlg == srp_rsa;
- KexAlg == srp_anon ->
- handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
- Version, ?FUNCTION_NAME, State);
-certify(internal, #certificate_request{},
- #state{static_env = #static_env{role = client},
- session = #session{own_certificate = undefined}} = State, Connection) ->
- %% The client does not have a certificate and will send an empty reply, the server may fail
- %% or accept the connection by its own preference. No signature algorihms needed as there is
- %% no certificate to verify.
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true});
-certify(internal, #certificate_request{} = CertRequest,
- #state{static_env = #static_env{role = client},
- handshake_env = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{own_certificate = Cert},
- ssl_options = #{signature_algs := SupportedHashSigns}} = State, Connection) ->
- case ssl_handshake:select_hashsign(CertRequest, Cert,
- SupportedHashSigns, ssl:tls_version(Version)) of
- #alert {} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
- NegotiatedHashSign ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{client_certificate_requested = true,
- handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}})
- end;
-%% PSK and RSA_PSK might bypass the Server-Key-Exchange
-certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
- session = #session{master_secret = undefined},
- connection_env = #connection_env{negotiated_version = Version},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- premaster_secret = undefined,
- server_psk_identity = PSKIdentity} = HsEnv,
- ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection)
- when KexAlg == psk ->
- case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
- PremasterSecret ->
- State = master_secret(PremasterSecret,
- State0#state{handshake_env =
- HsEnv#handshake_env{premaster_secret = PremasterSecret}}),
- client_certify_and_key_exchange(State, Connection)
- end;
-certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
- connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version,
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- premaster_secret = undefined,
- server_psk_identity = PSKIdentity} = HsEnv,
- session = #session{master_secret = undefined},
- ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection)
- when KexAlg == rsa_psk ->
- Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
- RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>,
- case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup,
- RSAPremasterSecret) of
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
- PremasterSecret ->
- State = master_secret(PremasterSecret,
- State0#state{handshake_env =
- HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}),
- client_certify_and_key_exchange(State, Connection)
- end;
-%% Master secret was determined with help of server-key exchange msg
-certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
- connection_env = #connection_env{negotiated_version = Version},
- handshake_env = #handshake_env{premaster_secret = undefined},
- session = #session{master_secret = MasterSecret} = Session,
- connection_states = ConnectionStates0} = State0, Connection) ->
- case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
- ConnectionStates0, client) of
- {MasterSecret, ConnectionStates} ->
- State = State0#state{connection_states = ConnectionStates},
- client_certify_and_key_exchange(State, Connection);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
- end;
-%% Master secret is calculated from premaster_secret
-certify(internal, #server_hello_done{},
- #state{static_env = #static_env{role = client},
- connection_env = #connection_env{negotiated_version = Version},
- handshake_env = #handshake_env{premaster_secret = PremasterSecret},
- session = Session0,
- connection_states = ConnectionStates0} = State0, Connection) ->
- case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
- ConnectionStates0, client) of
- {MasterSecret, ConnectionStates} ->
- Session = Session0#session{master_secret = MasterSecret},
- State = State0#state{connection_states = ConnectionStates,
- session = Session},
- client_certify_and_key_exchange(State, Connection);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
- end;
-certify(internal = Type, #client_key_exchange{} = Msg,
- #state{static_env = #static_env{role = server},
- client_certificate_requested = true,
- ssl_options = #{fail_if_no_peer_cert := true}} = State,
- Connection) ->
- %% We expect a certificate here
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection);
-certify(internal, #client_key_exchange{exchange_keys = Keys},
- State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg},
- connection_env = #connection_env{negotiated_version = Version}}, Connection) ->
- try
- certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)),
- State, Connection)
- catch
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
- end;
-certify(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
--spec cipher(gen_statem:event_type(),
- #hello_request{} | #certificate_verify{} | #finished{} | term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-cipher({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-cipher(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-cipher(internal, #certificate_verify{signature = Signature,
- hashsign_algorithm = CertHashSign},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{tls_handshake_history = Hist,
- kex_algorithm = KexAlg,
- public_key_info = PubKeyInfo} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{master_secret = MasterSecret}
- } = State, Connection) ->
-
- TLSVersion = ssl:tls_version(Version),
- %% Use negotiated value if TLS-1.2 otherwhise return default
- HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion),
- case ssl_handshake:certificate_verify(Signature, PubKeyInfo,
- TLSVersion, HashSign, MasterSecret, Hist) of
- valid ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}});
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
- end;
-%% client must send a next protocol message if we are expecting it
-cipher(internal, #finished{},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{expecting_next_protocol_negotiation = true,
- negotiated_protocol = undefined},
- connection_env = #connection_env{negotiated_version = Version}} = State0,
- _Connection) ->
- handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
-cipher(internal, #finished{verify_data = Data} = Finished,
- #state{static_env = #static_env{role = Role,
- host = Host,
- port = Port},
- handshake_env = #handshake_env{tls_handshake_history = Hist,
- expecting_finished = true} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- session = #session{master_secret = MasterSecret}
- = Session0,
- ssl_options = SslOpts,
- connection_states = ConnectionStates0} = State, Connection) ->
- case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished,
- opposite_role(Role),
- get_current_prf(ConnectionStates0, read),
- MasterSecret, Hist) of
- verified ->
- Session = handle_session(Role, SslOpts, Host, Port, Session0),
- cipher_role(Role, Data, Session,
- State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
- end;
-%% only allowed to send next_protocol message after change cipher spec
-%% & before finished message and it is not allowed during renegotiation
-cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{expecting_finished = true,
- expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) ->
- Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
- expecting_next_protocol_negotiation = false}});
-cipher(internal, #change_cipher_spec{type = <<1>>}, #state{handshake_env = HsEnv, connection_states = ConnectionStates0} =
- State, Connection) ->
- ConnectionStates =
- ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true},
- connection_states = ConnectionStates});
-cipher(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
--spec connection(gen_statem:event_type(), term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-connection({call, RecvFrom}, {recv, N, Timeout},
- #state{static_env = #static_env{protocol_cb = Connection},
- socket_options =
- #socket_options{active = false}} = State0, Connection) ->
- passive_receive(State0#state{bytes_to_read = N,
- start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection,
- [{{timeout, recv}, Timeout, timeout}]);
-
-connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection},
- handshake_env = HsEnv} = State,
- Connection) ->
- Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
-connection({call, From}, peer_certificate,
- #state{session = #session{peer_certificate = Cert}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]);
-connection({call, From}, {connection_information, true}, State, _) ->
- Info = connection_info(State) ++ security_info(State),
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
-connection({call, From}, {connection_information, false}, State, _) ->
- Info = connection_info(State),
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = undefined,
- negotiated_protocol = undefined}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = undefined,
- negotiated_protocol = SelectedProtocol}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State,
- [{reply, From, {ok, SelectedProtocol}}]);
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
- negotiated_protocol = undefined}} = State, _) ->
- hibernate_after(?FUNCTION_NAME, State,
- [{reply, From, {ok, SelectedProtocol}}]);
-connection({call, From}, Msg, State, Connection) ->
- handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection},
- handshake_env = HsEnv,
- connection_states = ConnectionStates}
- = State, Connection) ->
- Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
- connection_states = ConnectionStates#{current_write => WriteState}}, []);
-connection(cast, {dist_handshake_complete, DHandle},
- #state{ssl_options = #{erl_dist := true},
- connection_env = CEnv,
- socket_options = SockOpts} = State0, Connection) ->
- process_flag(priority, normal),
- State1 =
- State0#state{
- socket_options = SockOpts#socket_options{active = true},
- connection_env = CEnv#connection_env{erl_dist_handle = DHandle},
- bytes_to_read = undefined},
- {Record, State} = read_application_data(<<>>, State1),
- Connection:next_event(connection, Record, State);
-connection(info, Msg, State, _) ->
- handle_info(Msg, ?FUNCTION_NAME, State);
-connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom} = State, Connection) ->
- passive_receive(State, ?FUNCTION_NAME, Connection, []);
-connection(Type, Msg, State, Connection) ->
- handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
--spec downgrade(gen_statem:event_type(), term(),
- #state{}, tls_connection | dtls_connection) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-downgrade(Type, Event, State, Connection) ->
- handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection).
-
-%%--------------------------------------------------------------------
-%% Event handling functions called by state functions to handle
-%% common or unexpected events for the state.
-%%--------------------------------------------------------------------
-handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName,
- #state{static_env = #static_env{role = client},
- handshake_env = HsEnv} = State, _) ->
- %% Should not be included in handshake history
- {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}},
- [{next_event, internal, Handshake}]};
-handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName,
- #state{static_env = #static_env{role = client}}, _)
- when StateName =/= connection ->
- keep_state_and_data;
-handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
- #state{handshake_env = #handshake_env{tls_handshake_history = Hist0},
- connection_env = #connection_env{negotiated_version = Version}} = State0,
- Connection) ->
-
- PossibleSNI = Connection:select_sni_extension(Handshake),
- %% This function handles client SNI hello extension when Handshake is
- %% a client_hello, which needs to be determined by the connection callback.
- %% In other cases this is a noop
- case handle_sni_extension(PossibleSNI, State0) of
- #state{handshake_env = HsEnv} = State ->
- Hist = ssl_handshake:update_handshake_history(Hist0, Raw),
- {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}},
- [{next_event, internal, Handshake}]};
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State0)
- end;
-handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) ->
- Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State);
-handle_common_event(timeout, hibernate, _, _, _) ->
- {keep_state_and_data, [hibernate]};
-handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) ->
- handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version,
- StateName, State);
-handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State, _) ->
- {stop_and_reply,
- {shutdown, user_timeout},
- {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}};
-handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State, _) ->
- {next_state, StateName, State#state{start_or_recv_from = undefined,
- bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]};
-handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}, _) when
- StateName =/= connection ->
- {keep_state_and_data, [postpone]};
-handle_common_event(Type, Msg, StateName, #state{connection_env =
- #connection_env{negotiated_version = Version}} = State,
- _) ->
- Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type,Msg}}),
- handle_own_alert(Alert, Version, StateName, State).
-
-handle_call({application_data, _Data}, _, _, _, _) ->
- %% In renegotiation priorities handshake, send data when handshake is finished
- {keep_state_and_data, [postpone]};
-handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State, _Connection) ->
- %% Run terminate before returning so that the reuseaddr
- %% inet-option works properly
- Result = terminate(Close, StateName, State),
- {stop_and_reply,
- {shutdown, normal},
- {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}};
-handle_call({shutdown, read_write = How}, From, StateName,
- #state{static_env = #static_env{transport_cb = Transport,
- socket = Socket},
- connection_env = CEnv} = State, _) ->
- try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- StateName, State) of
- _ ->
- case Transport:shutdown(Socket, How) of
- ok ->
- {next_state, StateName, State#state{connection_env =
- CEnv#connection_env{terminated = true}},
- [{reply, From, ok}]};
- Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, Error},
- State#state{connection_env = CEnv#connection_env{terminated = true}}}
- end
- catch
- throw:Return ->
- Return
- end;
-handle_call({shutdown, How0}, From, StateName,
- #state{static_env = #static_env{transport_cb = Transport,
- socket = Socket}} = State, _) ->
- case Transport:shutdown(Socket, How0) of
- ok ->
- {next_state, StateName, State, [{reply, From, ok}]};
- Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State}
- end;
-handle_call({recv, _N, _Timeout}, From, _,
- #state{socket_options =
- #socket_options{active = Active}}, _) when Active =/= false ->
- {keep_state_and_data, [{reply, From, {error, einval}}]};
-handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) ->
- %% Doing renegotiate wait with handling request until renegotiate is
- %% finished.
- {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom},
- [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]};
-handle_call({new_user, User}, From, StateName,
- State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}, _) ->
- NewMon = erlang:monitor(process, User),
- erlang:demonitor(OldMon, [flush]),
- {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}},
- [{reply, From, ok}]};
-handle_call({get_opts, OptTags}, From, _,
- #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- socket_options = SockOpts}, Connection) ->
- OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []),
- {keep_state_and_data, [{reply, From, OptsReply}]};
-handle_call({set_opts, Opts0}, From, StateName,
- #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport,
- trackers = Trackers},
- connection_env =
- #connection_env{user_application = {_Mon, Pid}},
- socket_options = Opts1
- } = State0, Connection) ->
- {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
- case {proplists:lookup(active, Opts0), Opts} of
- {{_, N}, #socket_options{active=false}} when is_integer(N) ->
- send_user(
- Pid,
- format_passive(
- Connection:pids(State0), Transport, Socket, Trackers, Connection));
- _ ->
- ok
- end,
- State = State0#state{socket_options = Opts},
- handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
-
-handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection ->
- {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
-
-handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
- #state{connection_states = ConnectionStates,
- connection_env = #connection_env{negotiated_version = Version}}, _) ->
- #{security_parameters := SecParams} =
- ssl_record:current_connection_state(ConnectionStates, read),
- #security_parameters{master_secret = MasterSecret,
- client_random = ClientRandom,
- server_random = ServerRandom,
- prf_algorithm = PRFAlgorithm} = SecParams,
- Reply = try
- SecretToUse = case Secret of
- _ when is_binary(Secret) -> Secret;
- master_secret -> MasterSecret
- end,
- SeedToUse = lists:reverse(
- lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc];
- (client_random, Acc) -> [ClientRandom|Acc];
- (server_random, Acc) -> [ServerRandom|Acc]
- end, [], Seed)),
- ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
- catch
- exit:_ -> {error, badarg};
- error:Reason -> {error, Reason}
- end,
- {keep_state_and_data, [{reply, From, Reply}]};
-handle_call(_,_,_,_,_) ->
- {keep_state_and_data, [postpone]}.
-
-handle_info({ErrorTag, Socket, econnaborted}, StateName,
- #state{static_env = #static_env{role = Role,
- host = Host,
- port = Port,
- socket = Socket,
- transport_cb = Transport,
- error_tag = ErrorTag,
- trackers = Trackers,
- protocol_cb = Connection},
- handshake_env = #handshake_env{renegotiation = Type},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session,
- start_or_recv_from = StartFrom
- } = State) when StateName =/= connection ->
-
- maybe_invalidate_session(Version, Type, Role, Host, Port, Session),
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers,Socket,
- StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection),
- {stop, {shutdown, normal}, State};
-
-handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{
- role = Role,
- socket = Socket,
- error_tag = ErrorTag},
- ssl_options = #{log_level := Level}} = State) ->
- ssl_logger:log(info, Level, #{description => "Socket error",
- reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION),
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}),
- handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop, {shutdown,normal}, State};
-
-handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
- #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}},
- ssl_options = #{erl_dist := true}}) ->
- {stop, {shutdown, Reason}};
-handle_info({'DOWN', MonitorRef, _, _, _}, _,
- #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) ->
- {stop, {shutdown, normal}};
-handle_info({'EXIT', Pid, _Reason}, StateName,
- #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) ->
- %% It seems the user application has linked to us
- %% - ignore that and let the monitor handle this
- {next_state, StateName, State};
-%%% So that terminate will be run when supervisor issues shutdown
-handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
- {stop, shutdown, State};
-handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
- %% Handle as transport close"
- {stop,{shutdown, transport_closed}, State};
-handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
- {stop,{shutdown, Reason}, State};
-
-handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) ->
- {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}};
-
-handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag},
- ssl_options = #{log_level := Level}} = State) ->
- ssl_logger:log(notice, Level, #{description => "Unexpected INFO message",
- reason => [{message, Msg}, {socket, Socket},
- {error_tag, ErrorTag}]}, ?LOCATION),
- {next_state, StateName, State}.
-
-%%====================================================================
-%% general gen_statem callbacks
-%%====================================================================
-terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) ->
- %% Happens when user closes the connection using ssl:close/1
- %% we want to guarantee that Transport:close has been called
- %% when ssl:close/1 returns unless it is a downgrade where
- %% we want to guarantee that close alert is received before
- %% returning. In both cases terminate has been run manually
- %% before run by gen_statem which will end up here
- ok;
-terminate({shutdown, transport_closed} = Reason,
- _StateName, #state{static_env = #static_env{protocol_cb = Connection,
- socket = Socket,
- transport_cb = Transport}} = State) ->
- handle_trusted_certs_db(State),
- Connection:close(Reason, Socket, Transport, undefined, undefined);
-terminate({shutdown, own_alert}, _StateName, #state{
- static_env = #static_env{protocol_cb = Connection,
- socket = Socket,
- transport_cb = Transport}} = State) ->
- handle_trusted_certs_db(State),
- case application:get_env(ssl, alert_timeout) of
- {ok, Timeout} when is_integer(Timeout) ->
- Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined);
- _ ->
- Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
- end;
-terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection,
- transport_cb = Transport,
- socket = Socket}
- } = State) ->
- handle_trusted_certs_db(State),
- Connection:close(Reason, Socket, Transport, undefined, undefined);
-terminate(Reason, connection, #state{static_env = #static_env{
- protocol_cb = Connection,
- transport_cb = Transport,
- socket = Socket},
- connection_states = ConnectionStates,
- ssl_options = #{padding_check := Check}
- } = State) ->
- handle_trusted_certs_db(State),
- Alert = terminate_alert(Reason),
- %% Send the termination ALERT if possible
- catch (ok = Connection:send_alert_in_connection(Alert, State)),
- Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
-terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport,
- protocol_cb = Connection,
- socket = Socket}
- } = State) ->
- handle_trusted_certs_db(State),
- Connection:close(Reason, Socket, Transport, undefined, undefined).
-
-format_status(normal, [_, StateName, State]) ->
- [{data, [{"State", {StateName, State}}]}];
-format_status(terminate, [_, StateName, State]) ->
- SslOptions = (State#state.ssl_options),
- NewOptions = SslOptions#{password => ?SECRET_PRINTOUT,
- cert => ?SECRET_PRINTOUT,
- cacerts => ?SECRET_PRINTOUT,
- key => ?SECRET_PRINTOUT,
- dh => ?SECRET_PRINTOUT,
- psk_identity => ?SECRET_PRINTOUT,
- srp_identity => ?SECRET_PRINTOUT},
- [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
- protocol_buffers = ?SECRET_PRINTOUT,
- user_data_buffer = ?SECRET_PRINTOUT,
- handshake_env = ?SECRET_PRINTOUT,
- connection_env = ?SECRET_PRINTOUT,
- session = ?SECRET_PRINTOUT,
- ssl_options = NewOptions,
- flight_buffer = ?SECRET_PRINTOUT}
- }}]}].
-
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
- Connection:send_alert_in_connection(Alert, State);
-send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
- Connection:send_alert(Alert, State).
-
-connection_info(#state{static_env = #static_env{protocol_cb = Connection},
- handshake_env = #handshake_env{sni_hostname = SNIHostname,
- resumption = Resumption},
- session = #session{session_id = SessionId,
- cipher_suite = CipherSuite, ecc = ECCCurve},
- connection_env = #connection_env{negotiated_version = {_,_} = Version},
- ssl_options = Opts}) ->
- RecordCB = record_cb(Connection),
- CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- IsNamedCurveSuite = lists:member(KexAlg,
- [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]),
- CurveInfo = case ECCCurve of
- {namedCurve, Curve} when IsNamedCurveSuite ->
- [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}];
- _ ->
- []
- end,
- [{protocol, RecordCB:protocol_version(Version)},
- {session_id, SessionId},
- {session_resumption, Resumption},
- {cipher_suite, ssl_cipher_format:suite_legacy(CipherSuiteDef)},
- {selected_cipher_suite, CipherSuiteDef},
- {sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts).
-
-security_info(#state{connection_states = ConnectionStates}) ->
- #{security_parameters :=
- #security_parameters{client_random = ClientRand,
- server_random = ServerRand,
- master_secret = MasterSecret}} =
- ssl_record:current_connection_state(ConnectionStates, read),
- [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}].
-
-do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} =
- ServerHelloExt,
- #state{connection_env = #connection_env{negotiated_version = Version},
- handshake_env = HsEnv,
- session = #session{session_id = SessId},
- connection_states = ConnectionStates0,
- ssl_options = #{versions := [HighestVersion|_]}}
- = State0, Connection) when is_atom(Type) ->
- %% TLS 1.3 - Section 4.1.3
- %% Override server random values for TLS 1.3 downgrade protection mechanism.
- ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion),
- State1 = State0#state{connection_states = ConnectionStates1},
- ServerHello =
- ssl_handshake:server_hello(SessId, ssl:tls_version(Version),
- ConnectionStates1, ServerHelloExt),
- State = server_hello(ServerHello,
- State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
- NextProtocols =/= undefined}}, Connection),
- case Type of
- new ->
- new_server_hello(ServerHello, State, Connection);
- resumed ->
- resumed_server_hello(State, Connection)
- end.
-
-update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} =
- ReadState0,
- pending_write := #{security_parameters := WriteSecParams0} =
- WriteState0} = ConnectionStates,
- Version, HighestVersion) ->
- ReadRandom = override_server_random(
- ReadSecParams0#security_parameters.server_random,
- Version,
- HighestVersion),
- WriteRandom = override_server_random(
- WriteSecParams0#security_parameters.server_random,
- Version,
- HighestVersion),
- ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom},
- WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom},
- ReadState = ReadState0#{security_parameters => ReadSecParams},
- WriteState = WriteState0#{security_parameters => WriteSecParams},
-
- ConnectionStates#{pending_read => ReadState, pending_write => WriteState}.
-
-%% TLS 1.3 - Section 4.1.3
-%%
-%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes
-%% of their Random value to the bytes:
-%%
-%% 44 4F 57 4E 47 52 44 01
-%%
-%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2
-%% servers SHOULD set the last eight bytes of their Random value to the
-%% bytes:
-%%
-%% 44 4F 57 4E 47 52 44 00
-override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
- when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above
- if M =:= 3 andalso N =:= 3 -> %% Negotating TLS 1.2
- Down = ?RANDOM_OVERRIDE_TLS12,
- <<Random0/binary,Down/binary>>;
- M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
- Down = ?RANDOM_OVERRIDE_TLS11,
- <<Random0/binary,Down/binary>>;
- true ->
- Random
- end;
-override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
- when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2
- if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
- Down = ?RANDOM_OVERRIDE_TLS11,
- <<Random0/binary,Down/binary>>;
- true ->
- Random
- end;
-override_server_random(Random, _, _) ->
- Random.
-
-new_server_hello(#server_hello{cipher_suite = CipherSuite,
- compression_method = Compression,
- session_id = SessionId},
- #state{session = Session0,
- connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
- try server_certify_and_key_exchange(State0, Connection) of
- #state{} = State1 ->
- {State, Actions} = server_hello_done(State1, Connection),
- Session =
- Session0#session{session_id = SessionId,
- cipher_suite = CipherSuite,
- compression_method = Compression},
- Connection:next_event(certify, no_record, State#state{session = Session}, Actions)
- catch
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
- end.
-
-resumed_server_hello(#state{session = Session,
- connection_states = ConnectionStates0,
- connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
-
- case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
- ConnectionStates0, server) of
- {_, ConnectionStates1} ->
- State1 = State0#state{connection_states = ConnectionStates1,
- session = Session},
- {State, Actions} =
- finalize_handshake(State1, abbreviated, Connection),
- Connection:next_event(abbreviated, no_record, State, Actions);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
- end.
-
-server_hello(ServerHello, State0, Connection) ->
- CipherSuite = ServerHello#server_hello.cipher_suite,
- #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}.
-
-server_hello_done(State, Connection) ->
- HelloDone = ssl_handshake:server_hello_done(),
- Connection:send_handshake(HelloDone, State).
-
-handle_peer_cert(Role, PeerCert, PublicKeyInfo,
- #state{handshake_env = HsEnv,
- session = #session{cipher_suite = CipherSuite} = Session} = State0,
- Connection) ->
- State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo},
- session =
- Session#session{peer_certificate = PeerCert}},
- #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
- Connection:next_event(certify, no_record, State).
-
-handle_peer_cert_key(client, _,
- {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
- PublicKeyParams},
- KeyAlg, #state{handshake_env = HsEnv,
- session = Session} = State) when KeyAlg == ecdh_rsa;
- KeyAlg == ecdh_ecdsa ->
- ECDHKey = public_key:generate_key(PublicKeyParams),
- PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey),
- master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey},
- session = Session#session{ecc = PublicKeyParams}});
-handle_peer_cert_key(_, _, _, _, State) ->
- State.
-
-certify_client(#state{static_env = #static_env{role = client,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef},
- client_certificate_requested = true,
- session = #session{own_certificate = OwnCert}}
- = State, Connection) ->
- Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
- Connection:queue_handshake(Certificate, State);
-certify_client(#state{client_certificate_requested = false} = State, _) ->
- State.
-
-verify_client_cert(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{tls_handshake_history = Hist,
- cert_hashsign_algorithm = HashSign},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- client_certificate_requested = true,
- session = #session{master_secret = MasterSecret,
- own_certificate = OwnCert}} = State, Connection) ->
-
- case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
- ssl:tls_version(Version), HashSign, PrivateKey, Hist) of
- #certificate_verify{} = Verified ->
- Connection:queue_handshake(Verified, State);
- ignore ->
- State;
- #alert{} = Alert ->
- throw(Alert)
- end;
-verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
- State.
-
-client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} =
- State0, Connection) ->
- try do_client_certify_and_key_exchange(State0, Connection) of
- State1 = #state{} ->
- {State2, Actions} = finalize_handshake(State1, certify, Connection),
- State = State2#state{
- %% Reinitialize
- client_certificate_requested = false},
- Connection:next_event(cipher, no_record, State, Actions)
- catch
- throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end.
-
-do_client_certify_and_key_exchange(State0, Connection) ->
- State1 = certify_client(State0, Connection),
- State2 = key_exchange(State1, Connection),
- verify_client_cert(State2, Connection).
-
-server_certify_and_key_exchange(State0, Connection) ->
- State1 = certify_server(State0, Connection),
- State2 = key_exchange(State1, Connection),
- request_client_cert(State2, Connection).
-
-certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
- #state{connection_env = #connection_env{private_key = Key},
- handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}}
- = State, Connection) ->
- FakeSecret = make_premaster_secret(Version, rsa),
- %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
- %% and fail handshake later.RFC 5246 section 7.4.7.1.
- PremasterSecret =
- try ssl_handshake:premaster_secret(EncPMS, Key) of
- Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES ->
- case Secret of
- <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct
- <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>;
- <<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch
- <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>
- end;
- _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES
- FakeSecret
- catch
- #alert{description = ?DECRYPT_ERROR} ->
- FakeSecret
- end,
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
-certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
- #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
- kex_keys = {_, ServerDhPrivateKey}}
- } = State,
- Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params),
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
-
-certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
- #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey),
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
-certify_client_key_exchange(#client_psk_identity{} = ClientKey,
- #state{ssl_options =
- #{user_lookup_fun := PSKLookup}} = State0,
- Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
-certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
- #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
- kex_keys = {_, ServerDhPrivateKey}},
- ssl_options =
- #{user_lookup_fun := PSKLookup}} = State0,
- Connection) ->
- PremasterSecret =
- ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
-certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey,
- #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey},
- ssl_options =
- #{user_lookup_fun := PSKLookup}} = State,
- Connection) ->
- PremasterSecret =
- ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
-certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey,
- #state{connection_env = #connection_env{private_key = Key},
- ssl_options =
- #{user_lookup_fun := PSKLookup}} = State0,
- Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
-certify_client_key_exchange(#client_srp_public{} = ClientKey,
- #state{handshake_env = #handshake_env{srp_params = Params,
- kex_keys = Key}
- } = State0, Connection) ->
- PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params),
- calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher).
-
-certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} =
- State, _) when KexAlg == dh_anon;
- KexAlg == ecdh_anon;
- KexAlg == psk;
- KexAlg == dhe_psk;
- KexAlg == ecdhe_psk;
- KexAlg == srp_anon ->
- State;
-certify_server(#state{static_env = #static_env{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef},
- session = #session{own_certificate = OwnCert}} = State, Connection) ->
- case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
- Cert = #certificate{} ->
- Connection:queue_handshake(Cert, State);
- Alert = #alert{} ->
- throw(Alert)
- end.
-
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) ->
- State;
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- diffie_hellman_params = #'DHParameter'{} = Params,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- connection_states = ConnectionStates0} = State0, Connection)
- when KexAlg == dhe_dss;
- KexAlg == dhe_rsa;
- KexAlg == dh_anon ->
- DHKeys = public_key:generate_key(Params),
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv,
- connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key},
- session = Session} = State, _)
- when KexAlg == ecdh_ecdsa;
- KexAlg == ecdh_rsa ->
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key},
- session = Session#session{ecc = ECCurve}};
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- session = #session{ecc = ECCCurve},
- connection_states = ConnectionStates0} = State0, Connection)
- when KexAlg == ecdhe_ecdsa;
- KexAlg == ecdhe_rsa;
- KexAlg == ecdh_anon ->
-
- ECDHKeys = public_key:generate_key(ECCCurve),
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {ecdh, ECDHKeys,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = psk},
- ssl_options = #{psk_identity := undefined}} = State, _) ->
- State;
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{psk_identity := PskIdentityHint},
- handshake_env = #handshake_env{kex_algorithm = psk,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- connection_states = ConnectionStates0} = State0, Connection) ->
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {psk, PskIdentityHint,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{psk_identity := PskIdentityHint},
- handshake_env = #handshake_env{kex_algorithm = dhe_psk,
- diffie_hellman_params = #'DHParameter'{} = Params,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- connection_states = ConnectionStates0
- } = State0, Connection) ->
- DHKeys = public_key:generate_key(Params),
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {dhe_psk,
- PskIdentityHint, DHKeys, Params,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{psk_identity := PskIdentityHint},
- handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- session = #session{ecc = ECCCurve},
- connection_states = ConnectionStates0
- } = State0, Connection) ->
- ECDHKeys = public_key:generate_key(ECCCurve),
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {ecdhe_psk,
- PskIdentityHint, ECDHKeys,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
-key_exchange(#state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{kex_algorithm = rsa_psk},
- ssl_options = #{psk_identity := undefined}} = State, _) ->
- State;
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{psk_identity := PskIdentityHint},
- handshake_env = #handshake_env{kex_algorithm = rsa_psk,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- connection_states = ConnectionStates0
- } = State0, Connection) ->
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {psk, PskIdentityHint,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = server},
- ssl_options = #{user_lookup_fun := LookupFun},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- hashsign_algorithm = HashSignAlgo},
- connection_env = #connection_env{negotiated_version = Version,
- private_key = PrivateKey},
- session = #session{srp_username = Username},
- connection_states = ConnectionStates0
- } = State0, Connection)
- when KexAlg == srp_dss;
- KexAlg == srp_rsa;
- KexAlg == srp_anon ->
- SrpParams = handle_srp_identity(Username, LookupFun),
- Keys = case generate_srp_server_keys(SrpParams, 0) of
- Alert = #alert{} ->
- throw(Alert);
- Keys0 = {_,_} ->
- Keys0
- end,
- #{security_parameters := SecParams} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{client_random = ClientRandom,
- server_random = ServerRandom} = SecParams,
- Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
- {srp, Keys, SrpParams,
- HashSignAlgo, ClientRandom,
- ServerRandom,
- PrivateKey}),
- #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
- State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams,
- kex_keys = Keys}};
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = rsa,
- public_key_info = PublicKeyInfo,
- premaster_secret = PremasterSecret},
- connection_env = #connection_env{negotiated_version = Version}
- } = State0, Connection) ->
- Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- kex_keys = {DhPubKey, _}},
- connection_env = #connection_env{negotiated_version = Version}
- } = State0, Connection)
- when KexAlg == dhe_dss;
- KexAlg == dhe_rsa;
- KexAlg == dh_anon ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}),
- Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session
- } = State0, Connection)
- when KexAlg == ecdhe_ecdsa;
- KexAlg == ecdhe_rsa;
- KexAlg == ecdh_ecdsa;
- KexAlg == ecdh_rsa;
- KexAlg == ecdh_anon ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}),
- Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}});
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = psk},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {psk, PSKIdentity}),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = dhe_psk,
- kex_keys = {DhPubKey, _}},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {dhe_psk,
- PSKIdentity, DhPubKey}),
- Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
- kex_keys = ECDHKeys},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {ecdhe_psk,
- PSKIdentity, ECDHKeys}),
- Connection:queue_handshake(Msg, State0);
-
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = rsa_psk,
- public_key_info = PublicKeyInfo,
- premaster_secret = PremasterSecret},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{psk_identity := PSKIdentity}}
- = State0, Connection) ->
- Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity,
- PremasterSecret, PublicKeyInfo),
- Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = client},
- handshake_env = #handshake_env{kex_algorithm = KexAlg,
- kex_keys = {ClientPubKey, _}},
- connection_env = #connection_env{negotiated_version = Version}}
- = State0, Connection)
- when KexAlg == srp_dss;
- KexAlg == srp_rsa;
- KexAlg == srp_anon ->
- Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}),
- Connection:queue_handshake(Msg, State0).
-
-rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
- when Algorithm == ?rsaEncryption;
- Algorithm == ?md2WithRSAEncryption;
- Algorithm == ?md5WithRSAEncryption;
- Algorithm == ?sha1WithRSAEncryption;
- Algorithm == ?sha224WithRSAEncryption;
- Algorithm == ?sha256WithRSAEncryption;
- Algorithm == ?sha384WithRSAEncryption;
- Algorithm == ?sha512WithRSAEncryption
- ->
- ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {premaster_secret, PremasterSecret,
- PublicKeyInfo});
-rsa_key_exchange(_, _, _) ->
- throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
-
-rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret,
- PublicKeyInfo = {Algorithm, _, _})
- when Algorithm == ?rsaEncryption;
- Algorithm == ?md2WithRSAEncryption;
- Algorithm == ?md5WithRSAEncryption;
- Algorithm == ?sha1WithRSAEncryption;
- Algorithm == ?sha224WithRSAEncryption;
- Algorithm == ?sha256WithRSAEncryption;
- Algorithm == ?sha384WithRSAEncryption;
- Algorithm == ?sha512WithRSAEncryption
- ->
- ssl_handshake:key_exchange(client, ssl:tls_version(Version),
- {psk_premaster_secret, PskIdentity, PremasterSecret,
- PublicKeyInfo});
-rsa_psk_key_exchange(_, _, _, _) ->
- throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
-
-request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _)
- when Alg == dh_anon;
- Alg == ecdh_anon;
- Alg == psk;
- Alg == dhe_psk;
- Alg == ecdhe_psk;
- Alg == rsa_psk;
- Alg == srp_dss;
- Alg == srp_rsa;
- Alg == srp_anon ->
- State;
-
-request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{verify := verify_peer,
- signature_algs := SupportedHashSigns},
- connection_states = ConnectionStates0} = State0, Connection) ->
- #{security_parameters :=
- #security_parameters{cipher_suite = CipherSuite}} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- TLSVersion = ssl:tls_version(Version),
- HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns,
- TLSVersion),
- Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef,
- HashSigns, TLSVersion),
- State = Connection:queue_handshake(Msg, State0),
- State#state{client_certificate_requested = true};
-
-request_client_cert(#state{ssl_options = #{verify := verify_none}} =
- State, _) ->
- State.
-
-calculate_master_secret(PremasterSecret,
- #state{connection_env = #connection_env{negotiated_version = Version},
- connection_states = ConnectionStates0,
- session = Session0} = State0, Connection,
- _Current, Next) ->
- case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
- ConnectionStates0, server) of
- {MasterSecret, ConnectionStates} ->
- Session = Session0#session{master_secret = MasterSecret},
- State = State0#state{connection_states = ConnectionStates,
- session = Session},
- Connection:next_event(Next, no_record, State);
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, certify, State0)
- end.
-
-finalize_handshake(State0, StateName, Connection) ->
- #state{connection_states = ConnectionStates0} =
- State1 = cipher_protocol(State0, Connection),
-
- ConnectionStates =
- ssl_record:activate_pending_connection_state(ConnectionStates0,
- write, Connection),
-
- State2 = State1#state{connection_states = ConnectionStates},
- State = next_protocol(State2, Connection),
- finished(State, StateName, Connection).
-
-next_protocol(#state{static_env = #static_env{role = server}} = State, _) ->
- State;
-next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) ->
- State;
-next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) ->
- State;
-next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) ->
- NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
- Connection:queue_handshake(NextProtocolMessage, State0).
-
-cipher_protocol(State, Connection) ->
- Connection:queue_change_cipher(#change_cipher_spec{}, State).
-
-finished(#state{static_env = #static_env{role = Role},
- handshake_env = #handshake_env{tls_handshake_history = Hist},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session,
- connection_states = ConnectionStates0} = State0,
- StateName, Connection) ->
- MasterSecret = Session#session.master_secret,
- Finished = ssl_handshake:finished(ssl:tls_version(Version), Role,
- get_current_prf(ConnectionStates0, write),
- MasterSecret, Hist),
- ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
- Connection:send_handshake(Finished, State0#state{connection_states =
- ConnectionStates}).
-
-save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) ->
- ssl_record:set_client_verify_data(current_write, Data, ConnectionStates);
-save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) ->
- ssl_record:set_server_verify_data(current_both, Data, ConnectionStates);
-save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
- ssl_record:set_client_verify_data(current_both, Data, ConnectionStates);
-save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
- ssl_record:set_server_verify_data(current_write, Data, ConnectionStates).
-
-calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base,
- dh_y = ServerPublicDhKey} = Params,
- #state{handshake_env = HsEnv} = State, Connection) ->
- Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]),
- PremasterSecret =
- ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params),
- calculate_master_secret(PremasterSecret,
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
- Connection, certify, certify);
-
-calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
- #state{handshake_env = HsEnv,
- session = Session} = State, Connection) ->
- ECDHKeys = public_key:generate_key(ECCurve),
- PremasterSecret =
- ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys),
- calculate_master_secret(PremasterSecret,
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
- session = Session#session{ecc = ECCurve}},
- Connection, certify, certify);
-
-calculate_secret(#server_psk_params{
- hint = IdentityHint},
- #state{handshake_env = HsEnv} = State, Connection) ->
- %% store for later use
- Connection:next_event(certify, no_record,
- State#state{handshake_env =
- HsEnv#handshake_env{server_psk_identity = IdentityHint}});
-
-calculate_secret(#server_dhe_psk_params{
- dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
- #state{handshake_env = HsEnv,
- ssl_options = #{user_lookup_fun := PSKLookup}} =
- State, Connection) ->
- Keys = {_, PrivateDhKey} =
- crypto:generate_key(dh, [Prime, Base]),
- PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
- Connection, certify, certify);
-
-calculate_secret(#server_ecdhe_psk_params{
- dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey,
- #state{ssl_options = #{user_lookup_fun := PSKLookup}} =
- #state{handshake_env = HsEnv,
- session = Session} = State, Connection) ->
- ECDHKeys = public_key:generate_key(ECCurve),
-
- PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup),
- calculate_master_secret(PremasterSecret,
- State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
- session = Session#session{ecc = ECCurve}},
- Connection, certify, certify);
-
-calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey,
- #state{handshake_env = HsEnv,
- ssl_options = #{srp_identity := SRPId}} = State,
- Connection) ->
- Keys = generate_srp_client_keys(Generator, Prime, 0),
- PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId),
- calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection,
- certify, certify).
-
-master_secret(#alert{} = Alert, _) ->
- Alert;
-master_secret(PremasterSecret, #state{static_env = #static_env{role = Role},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session,
- connection_states = ConnectionStates0} = State) ->
- case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
- ConnectionStates0, Role) of
- {MasterSecret, ConnectionStates} ->
- State#state{
- session =
- Session#session{master_secret = MasterSecret},
- connection_states = ConnectionStates};
- #alert{} = Alert ->
- Alert
- end.
-
-generate_srp_server_keys(_SrpParams, 10) ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
-generate_srp_server_keys(SrpParams =
- #srp_user{generator = Generator, prime = Prime,
- verifier = Verifier}, N) ->
- try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of
- Keys ->
- Keys
- catch
- error:_ ->
- generate_srp_server_keys(SrpParams, N+1)
- end.
-
-generate_srp_client_keys(_Generator, _Prime, 10) ->
- ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
-generate_srp_client_keys(Generator, Prime, N) ->
-
- try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of
- Keys ->
- Keys
- catch
- error:_ ->
- generate_srp_client_keys(Generator, Prime, N+1)
- end.
-
-handle_srp_identity(Username, {Fun, UserState}) ->
- case Fun(srp, Username, UserState) of
- {ok, {SRPParams, Salt, DerivedKey}}
- when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) ->
- {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams),
- Verifier = crypto:mod_pow(Generator, DerivedKey, Prime),
- #srp_user{generator = Generator, prime = Prime,
- salt = Salt, verifier = Verifier};
- #alert{} = Alert ->
- throw(Alert);
- _ ->
- throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
- end.
-
-
-cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State0,
- Connection) ->
- ConnectionStates = ssl_record:set_server_verify_data(current_both, Data,
- ConnectionStates0),
- {Record, State} = prepare_connection(State0#state{session = Session,
- connection_states = ConnectionStates},
- Connection),
- Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
-cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0,
- Connection) ->
- ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data,
- ConnectionStates0),
- {State1, Actions} =
- finalize_handshake(State0#state{connection_states = ConnectionStates1,
- session = Session}, cipher, Connection),
- {Record, State} = prepare_connection(State1, Connection),
- Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]).
-
-is_anonymous(KexAlg) when KexAlg == dh_anon;
- KexAlg == ecdh_anon;
- KexAlg == psk;
- KexAlg == dhe_psk;
- KexAlg == ecdhe_psk;
- KexAlg == rsa_psk;
- KexAlg == srp_anon ->
- true;
-is_anonymous(_) ->
- false.
-
-get_current_prf(CStates, Direction) ->
- #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction),
- SecParams#security_parameters.prf_algorithm.
-get_pending_prf(CStates, Direction) ->
- #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction),
- SecParams#security_parameters.prf_algorithm.
-
-opposite_role(client) ->
- server;
-opposite_role(server) ->
- client.
-
-record_cb(tls_connection) ->
- tls_record;
-record_cb(dtls_connection) ->
- dtls_record.
-
-call(FsmPid, Event) ->
- try gen_statem:call(FsmPid, Event)
- catch
- exit:{noproc, _} ->
- {error, closed};
- exit:{normal, _} ->
- {error, closed};
- exit:{{shutdown, _},_} ->
- {error, closed}
- end.
-
-get_socket_opts(_, _,_,[], _, Acc) ->
- {ok, Acc};
-get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
- [{mode, SockOpts#socket_options.mode} | Acc]);
-get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) ->
- case SockOpts#socket_options.packet of
- {Type, headers} ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
- Type ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
- end;
-get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
- [{header, SockOpts#socket_options.header} | Acc]);
-get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
- [{active, SockOpts#socket_options.active} | Acc]);
-get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) ->
- case Connection:getopts(Transport, Socket, [Tag]) of
- {ok, [Opt]} ->
- get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]);
- {error, Reason} ->
- {error, {options, {socket_options, Tag, Reason}}}
- end;
-get_socket_opts(_,_, _,Opts, _,_) ->
- {error, {options, {socket_options, Opts, function_clause}}}.
-
-set_socket_opts(_,_,_, [], SockOpts, []) ->
- {ok, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) ->
- %% Set non emulated options
- try ConnectionCb:setopts(Transport, Socket, Other) of
- ok ->
- {ok, SockOpts};
- {error, InetError} ->
- {{error, {options, {socket_options, Other, InetError}}}, SockOpts}
- catch
- _:Error ->
- %% So that inet behavior does not crash our process
- {{error, {options, {socket_options, Other, Error}}}, SockOpts}
- end;
-
-set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other)
- when Mode == list; Mode == binary ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{mode = Mode}, Other);
-set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) ->
- {{error, {options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other)
- when Packet == raw;
- Packet == 0;
- Packet == 1;
- Packet == 2;
- Packet == 4;
- Packet == asn1;
- Packet == cdr;
- Packet == sunrm;
- Packet == fcgi;
- Packet == tpkt;
- Packet == line;
- Packet == http;
- Packet == httph;
- Packet == http_bin;
- Packet == httph_bin ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{packet = Packet}, Other);
-set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) ->
- {{error, {options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other)
- when is_integer(Header) ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{header = Header}, Other);
-set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) ->
- {{error,{options, {socket_options, Opt}}}, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other)
- when Active == once;
- Active == true;
- Active == false ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{active = Active}, Other);
-set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts],
- SockOpts=#socket_options{active = Active0}, Other)
- when Active1 >= -32768, Active1 =< 32767 ->
- Active = if
- is_integer(Active0), Active0 + Active1 < -32768 ->
- error;
- is_integer(Active0), Active0 + Active1 =< 0 ->
- false;
- is_integer(Active0), Active0 + Active1 > 32767 ->
- error;
- Active1 =< 0 ->
- false;
- is_integer(Active0) ->
- Active0 + Active1;
- true ->
- Active1
- end,
- case Active of
- error ->
- {{error, {options, {socket_options, Opt}} }, SockOpts};
- _ ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts,
- SockOpts#socket_options{active = Active}, Other)
- end;
-set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) ->
- {{error, {options, {socket_options, Opt}} }, SockOpts};
-set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) ->
- set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]).
-
-
-
-hibernate_after(connection = StateName,
- #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
- Actions) ->
- {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
-hibernate_after(StateName, State, Actions) ->
- {next_state, StateName, State, Actions}.
-
-
-terminate_alert(normal) ->
- ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
-terminate_alert({Reason, _}) when Reason == close;
- Reason == shutdown ->
- ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
-terminate_alert(_) ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR).
-
-handle_trusted_certs_db(#state{ssl_options =
- #{cacertfile := <<>>, cacerts := []}}) ->
- %% No trusted certs specified
- ok;
-handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
- cert_db = CertDb},
- ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined ->
- %% Certs provided as DER directly can not be shared
- %% with other connections and it is safe to delete them when the connection ends.
- ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
-handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) ->
- %% Something went wrong early (typically cacertfile does not
- %% exist) so there is nothing to handle
- ok;
-handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
- file_ref_db = RefDb},
- ssl_options = #{cacertfile := File}}) ->
- case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
- 0 ->
- ssl_manager:clean_cert_db(Ref, File);
- _ ->
- ok
- end.
-
-prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
- start_or_recv_from = RecvFrom} = State0, Connection)
- when Renegotiate =/= {false, first},
- RecvFrom =/= undefined ->
- State = Connection:reinit(State0),
- {no_record, ack_connection(State)};
-prepare_connection(State0, Connection) ->
- State = Connection:reinit(State0),
- {no_record, ack_connection(State)}.
-
-ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer;
- Initiater == internal ->
- State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
-ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) ->
- gen_statem:reply(From, ok),
- State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
-ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv,
- start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined ->
- gen_statem:reply(StartFrom, connected),
- State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
- start_or_recv_from = undefined};
-ack_connection(State) ->
- State.
-
-session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
- Session#session{ecc = ECCurve};
-session_handle_params(_, Session) ->
- Session.
-
-handle_session(Role = server, #{reuse_sessions := true} = SslOpts,
- Host, Port, Session0) ->
- register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, true);
-handle_session(Role = client, #{verify := verify_peer,
- reuse_sessions := Reuse} = SslOpts,
- Host, Port, Session0) when Reuse =/= false ->
- register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, reg_type(Reuse));
-handle_session(server, _, Host, Port, Session) ->
- %% Remove "session of type new" entry from session DB
- ssl_manager:invalidate_session(Host, Port, Session),
- Session;
-handle_session(client, _,_,_, Session) ->
- %% In client case there is no entry yet, so nothing to remove
- Session.
-
-reg_type(save) ->
- true;
-reg_type(true) ->
- unique.
-
-register_session(client, Host, Port, #session{is_resumable = new} = Session0, Save) ->
- Session = Session0#session{is_resumable = true},
- ssl_manager:register_session(Host, Port, Session, Save),
- Session;
-register_session(server, _, Port, #session{is_resumable = new} = Session0, _) ->
- Session = Session0#session{is_resumable = true},
- ssl_manager:register_session(Port, Session),
- Session;
-register_session(_, _, _, Session, _) ->
- Session. %% Already registered
-
-host_id(client, _Host, #{server_name_indication := Hostname}) when is_list(Hostname) ->
- Hostname;
-host_id(_, Host, _) ->
- Host.
-
-handle_new_session(NewId, CipherSuite, Compression,
- #state{static_env = #static_env{protocol_cb = Connection},
- session = Session0
- } = State0) ->
- Session = Session0#session{session_id = NewId,
- cipher_suite = CipherSuite,
- compression_method = Compression},
- Connection:next_event(certify, no_record, State0#state{session = Session}).
-
-handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
- port = Port,
- protocol_cb = Connection,
- session_cache = Cache,
- session_cache_cb = CacheCb},
- connection_env = #connection_env{negotiated_version = Version},
- connection_states = ConnectionStates0} = State) ->
- Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
- case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
- ConnectionStates0, client) of
- {_, ConnectionStates} ->
- Connection:next_event(abbreviated, no_record, State#state{
- connection_states = ConnectionStates,
- session = Session});
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State)
- end.
-
-make_premaster_secret({MajVer, MinVer}, rsa) ->
- Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
- <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
-make_premaster_secret(_, _) ->
- undefined.
-
-negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) ->
- %% Not negotiated choose default
- case is_anonymous(KexAlg) of
- true ->
- {null, anon};
- false ->
- {PubAlg, _, _} = PubKeyInfo,
- ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version)
- end;
-negotiated_hashsign(HashSign = {_, _}, _, _, _) ->
- HashSign.
-
-ssl_options_list(SslOptions) ->
- L = maps:to_list(SslOptions),
- ssl_options_list(L, []).
-
-ssl_options_list([], Acc) ->
- lists:reverse(Acc);
-%% Skip internal options, only return user options
-ssl_options_list([{protocol, _}| T], Acc) ->
- ssl_options_list(T, Acc);
-ssl_options_list([{erl_dist, _}|T], Acc) ->
- ssl_options_list(T, Acc);
-ssl_options_list([{renegotiate_at, _}|T], Acc) ->
- ssl_options_list(T, Acc);
-ssl_options_list([{ciphers = Key, Value}|T], Acc) ->
- ssl_options_list(T,
- [{Key, lists:map(
- fun(Suite) ->
- ssl_cipher_format:suite_bin_to_map(Suite)
- end, Value)}
- | Acc]);
-ssl_options_list([{Key, Value}|T], Acc) ->
- ssl_options_list(T, [{Key, Value} | Acc]).
-
-handle_active_option(false, connection = StateName, To, Reply, State) ->
- hibernate_after(StateName, State, [{reply, To, Reply}]);
-
-handle_active_option(_, connection = StateName, To, _Reply, #state{static_env = #static_env{role = Role},
- connection_env = #connection_env{terminated = true},
- user_data_buffer = {_,0,_}} = State) ->
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_deliverd),
- handle_normal_shutdown(Alert#alert{role = Role}, StateName,
- State#state{start_or_recv_from = To}),
- {stop,{shutdown, peer_close}, State};
-handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
- user_data_buffer = {_,0,_}} = State0) ->
- case Connection:next_event(StateName0, no_record, State0) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, [{reply, To, Reply}]);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
- {stop, _, _} = Stop ->
- Stop
- end;
-handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) ->
- %% Active once already set
- {next_state, StateName, State, [{reply, To, Reply}]};
-
-%% user_data_buffer nonempty
-handle_active_option(_, StateName0, To, Reply,
- #state{static_env = #static_env{protocol_cb = Connection}} = State0) ->
- case read_application_data(<<>>, State0) of
- {stop, _, _} = Stop ->
- Stop;
- {Record, State1} ->
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case Connection:next_event(StateName0, Record, State1) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, [{reply, To, Reply}]);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
- {stop, _, _} = Stop ->
- Stop
- end
- end.
-
-
-%% Picks ClientData
-get_data(#socket_options{active=false}, undefined, _Bin) ->
- %% Recv timed out save buffer data until next recv
- passive;
-get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin)
- when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
- case Bin of
- <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 ->
- %% Active true or once, or passive mode recv(0)
- {ok, Bin, <<>>};
- <<Data:BytesToRead/binary, Rest/binary>> ->
- %% Passive Mode, recv(Bytes)
- {ok, Data, Rest};
- <<_/binary>> ->
- %% Passive Mode not enough data
- {more, BytesToRead}
- end;
-get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) ->
- PacketOpts = [{packet_size, Size}],
- decode_packet(Type, Bin, PacketOpts).
-
-decode_packet({http, headers}, Buffer, PacketOpts) ->
- decode_packet(httph, Buffer, PacketOpts);
-decode_packet({http_bin, headers}, Buffer, PacketOpts) ->
- decode_packet(httph_bin, Buffer, PacketOpts);
-decode_packet(Type, Buffer, PacketOpts) ->
- erlang:decode_packet(Type, Buffer, PacketOpts).
-
-%% Just like with gen_tcp sockets, an ssl socket that has been configured with
-%% {packet, http} (or {packet, http_bin}) will automatically switch to expect
-%% HTTP headers after it sees a HTTP Request or HTTP Response line. We
-%% represent the current state as follows:
-%% #socket_options.packet =:= http: Expect a HTTP Request/Response line
-%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers
-%% Note that if the user has explicitly configured the socket to expect
-%% HTTP headers using the {packet, httph} option, we don't do any automatic
-%% switching of states.
-deliver_app_data(
- CPids, Transport, Socket,
- #socket_options{active=Active, packet=Type} = SOpts,
- Data, Pid, From, Trackers, Connection) ->
- %%
- send_or_reply(
- Active, Pid, From,
- format_reply(
- CPids, Transport, Socket, SOpts, Data, Trackers, Connection)),
- SO =
- case Data of
- {P, _, _, _}
- when ((P =:= http_request) or (P =:= http_response)),
- ((Type =:= http) or (Type =:= http_bin)) ->
- SOpts#socket_options{packet={Type, headers}};
- http_eoh when tuple_size(Type) =:= 2 ->
- %% End of headers - expect another Request/Response line
- {Type1, headers} = Type,
- SOpts#socket_options{packet=Type1};
- _ ->
- SOpts
- end,
- case Active of
- once ->
- SO#socket_options{active=false};
- 1 ->
- send_user(
- Pid,
- format_passive(
- CPids, Transport, Socket, Trackers, Connection)),
- SO#socket_options{active=false};
- N when is_integer(N) ->
- SO#socket_options{active=N - 1};
- _ ->
- SO
- end.
-
-format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet,
- header = Header}, Data, _, _) ->
- {ok, do_format_reply(Mode, Packet, Header, Data)};
-format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
- header = Header}, Data, Trackers, Connection) ->
- {ssl, Connection:socket(CPids, Transport, Socket, Trackers),
- do_format_reply(Mode, Packet, Header, Data)}.
-
-deliver_packet_error(CPids, Transport, Socket,
- SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) ->
- send_or_reply(Active, Pid, From, format_packet_error(CPids,
- Transport, Socket, SO, Data, Trackers, Connection)).
-
-format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) ->
- {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
-format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode},
- Data, Trackers, Connection) ->
- {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers),
- {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
-
-do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
- header(N, Data);
-do_format_reply(binary, _, _, Data) ->
- Data;
-do_format_reply(list, Packet, _, Data)
- when Packet == http; Packet == {http, headers};
- Packet == http_bin; Packet == {http_bin, headers};
- Packet == httph; Packet == httph_bin ->
- Data;
-do_format_reply(list, _,_, Data) ->
- binary_to_list(Data).
-
-format_passive(CPids, Transport, Socket, Trackers, Connection) ->
- {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}.
-
-header(0, <<>>) ->
- <<>>;
-header(_, <<>>) ->
- [];
-header(0, Binary) ->
- Binary;
-header(N, Binary) ->
- <<?BYTE(ByteN), NewBinary/binary>> = Binary,
- [ByteN | header(N-1, NewBinary)].
-
-send_or_reply(false, _Pid, From, Data) when From =/= undefined ->
- gen_statem:reply(From, Data);
-%% Can happen when handling own alert or tcp error/close and there is
-%% no outstanding gen_fsm sync events
-send_or_reply(false, no_pid, _, _) ->
- ok;
-send_or_reply(_, Pid, _From, Data) ->
- send_user(Pid, Data).
-
-send_user(Pid, Msg) ->
- Pid ! Msg,
- ok.
-
-alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
-alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection).
-
-alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
-
-alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined ->
- %% If there is an outstanding ssl_accept | recv
- %% From will be defined and send_or_reply will
- %% send the appropriate error message.
- ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName),
- send_or_reply(Active, Pid, From, {error, ReasonCode});
-alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) ->
- case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of
- closed ->
- send_or_reply(Active, Pid, From,
- {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)});
- ReasonCode ->
- send_or_reply(Active, Pid, From,
- {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode})
- end.
-
-log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
- ssl_logger:log(notice, Level, #{protocol => ProtocolName,
- role => Role,
- statename => StateName,
- alert => Alert,
- alerter => own}, Alert#alert.where);
-log_alert(Level, Role, ProtocolName, StateName, Alert) ->
- ssl_logger:log(notice, Level, #{protocol => ProtocolName,
- role => Role,
- statename => StateName,
- alert => Alert,
- alerter => peer}, Alert#alert.where).
-
-maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
- invalidate_session(Role, Host, Port, Session);
-maybe_invalidate_session(_, _, _, _, _) ->
- ok.
-
-invalidate_session(client, Host, Port, Session) ->
- ssl_manager:invalidate_session(Host, Port, Session);
-invalidate_session(server, _, Port, Session) ->
- ssl_manager:invalidate_session(Port, Session).
-
-handle_sni_extension(undefined, State) ->
- State;
-handle_sni_extension(#sni{hostname = Hostname}, State) ->
- case is_sni_value(Hostname) of
- true ->
- handle_sni_extension(Hostname, State);
- false ->
- ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME, {sni_included_trailing_dot, Hostname})
- end;
-handle_sni_extension(Hostname, #state{static_env = #static_env{role = Role} = InitStatEnv0,
- handshake_env = HsEnv,
- connection_env = CEnv} = State0) ->
- NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
- case NewOptions of
- undefined ->
- State0;
- _ ->
- {ok, #{cert_db_ref := Ref,
- cert_db_handle := CertDbHandle,
- fileref_db_handle := FileRefHandle,
- session_cache := CacheHandle,
- crl_db_info := CRLDbHandle,
- private_key := Key,
- dh_params := DHParams,
- own_certificate := OwnCert}} =
- ssl_config:init(NewOptions, Role),
- State0#state{
- session = State0#state.session#session{own_certificate = OwnCert},
- static_env = InitStatEnv0#static_env{
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle
- },
- connection_env = CEnv#connection_env{private_key = Key},
- ssl_options = NewOptions,
- handshake_env = HsEnv#handshake_env{sni_hostname = Hostname,
- diffie_hellman_params = DHParams}
- }
- end.
-
-update_ssl_options_from_sni(#{sni_fun := SNIFun,
- sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) ->
- SSLOption =
- case SNIFun of
- undefined ->
- proplists:get_value(SNIHostname,
- SNIHosts);
- SNIFun ->
- SNIFun(SNIHostname)
- end,
- case SSLOption of
- undefined ->
- undefined;
- _ ->
- ssl:handle_options(SSLOption, server, OrigSSLOptions)
- end.
-
-new_emulated([], EmOpts) ->
- EmOpts;
-new_emulated(NewEmOpts, _) ->
- NewEmOpts.
-
-no_records(Extensions) ->
- maps:map(fun(_, Value) ->
- ssl_handshake:extension_value(Value)
- end, Extensions).
-
-is_sni_value(Hostname) ->
- case hd(lists:reverse(Hostname)) of
- $. ->
- false;
- _ ->
- true
- end.
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index 284ded64d8..4f9584bb9f 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -36,7 +36,7 @@
-record(static_env, {
role :: client | server,
transport_cb :: atom(), % callback module
- protocol_cb :: tls_connection | dtls_connection,
+ protocol_cb :: tls_gen_connection | dtls_gen_connection,
data_tag :: atom(), % ex tcp.
close_tag :: atom(), % ex tcp_closed
error_tag :: atom(), % ex tcp_error
@@ -62,10 +62,14 @@
expecting_finished = false ::boolean(),
renegotiation :: undefined | {boolean(), From::term() | internal | peer},
resumption = false :: boolean(), %% TLS 1.3
+ change_cipher_spec_sent = false :: boolean(), %% TLS 1.3
+ sni_guided_cert_selection = false :: boolean(), %% TLS 1.3
+ early_data_accepted = false :: boolean(), %% TLS 1.3
allow_renegotiate = true ::boolean(),
%% Ext handling
hello, %%:: #client_hello{} | #server_hello{}
sni_hostname = undefined,
+ max_frag_enum :: undefined | {max_frag_enum, integer()},
expecting_next_protocol_negotiation = false ::boolean(),
next_protocol = undefined :: undefined | binary(),
alpn = undefined, %% Used in TLS 1.3
@@ -80,7 +84,10 @@
public_key_info :: ssl_handshake:public_key_info() | 'undefined',
premaster_secret :: binary() | secret_printout() | 'undefined',
server_psk_identity :: binary() | 'undefined', % server psk identity hint
- ticket_seed
+ cookie_iv_shard :: {binary(), binary()} %% IV, Shard
+ | 'undefined',
+ ocsp_stapling_state = #{ocsp_stapling => false,
+ ocsp_expect => no_staple}
}).
-record(connection_env, {
diff --git a/lib/ssl/src/ssl_crl.erl b/lib/ssl/src/ssl_crl.erl
index 888a75bfd6..12d261bcdc 100644
--- a/lib/ssl/src/ssl_crl.erl
+++ b/lib/ssl/src/ssl_crl.erl
@@ -27,35 +27,40 @@
-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([trusted_cert_and_path/3]).
+-export([trusted_cert_and_path/4]).
-trusted_cert_and_path(CRL, {SerialNumber, Issuer},{_, {Db, DbRef}} = DbHandle) ->
+trusted_cert_and_path(CRL, {SerialNumber, Issuer}, CertPath, {Db, DbRef}) ->
+ %% CRL issuer cert ID is known
case ssl_pkix_db:lookup_trusted_cert(Db, DbRef, SerialNumber, Issuer) of
undefined ->
- trusted_cert_and_path(CRL, issuer_not_found, DbHandle);
+ %% But not found in our database
+ search_certpath(CRL, CertPath, Db, DbRef);
{ok, {_, OtpCert}} ->
{ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef),
{ok, Root, lists:reverse(Chain)}
end;
-trusted_cert_and_path(CRL, issuer_not_found, {CertPath, {Db, DbRef}}) ->
- case find_issuer(CRL, {certpath,
- [{Der, public_key:pkix_decode_cert(Der,otp)} || Der <- CertPath]}) of
- {ok, OtpCert} ->
- {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef),
- {ok, Root, lists:reverse(Chain)};
- {error, issuer_not_found} ->
- trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef})
- end;
-trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef} = DbInfo) ->
- case find_issuer(CRL, DbInfo) of
- {ok, OtpCert} ->
- {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef),
- {ok, Root, lists:reverse(Chain)};
- {error, issuer_not_found} ->
- {error, unknown_ca}
- end.
+trusted_cert_and_path(CRL, issuer_not_found, CertPath, {Db, DbRef}) ->
+ case search_certpath(CRL, CertPath, Db, DbRef) of
+ {error, unknown_ca} ->
+ Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)),
+ IsIssuerFun =
+ fun({_Key, {_Der,ErlCertCandidate}}, Acc) ->
+ verify_crl_issuer(CRL, ErlCertCandidate, Issuer, Acc);
+ (_, Acc) ->
+ Acc
+ end,
+ case search_db(IsIssuerFun, Db, DbRef) of
+ {ok, OtpCert} ->
+ {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef),
+ {ok, Root, lists:reverse(Chain)};
+ {error, issuer_not_found} ->
+ {error, unknown_ca}
+ end;
+ Result ->
+ Result
+ end.
-find_issuer(CRL, {certpath = Db, DbRef}) ->
+search_certpath(CRL, CertPath, Db, DbRef) ->
Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)),
IsIssuerFun =
fun({_Der,ErlCertCandidate}, Acc) ->
@@ -63,15 +68,18 @@ find_issuer(CRL, {certpath = Db, DbRef}) ->
(_, Acc) ->
Acc
end,
- find_issuer(IsIssuerFun, Db, DbRef);
-find_issuer(CRL, {Db, DbRef}) ->
- Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)),
- IsIssuerFun =
- fun({_Key, {_Der,ErlCertCandidate}}, Acc) ->
- verify_crl_issuer(CRL, ErlCertCandidate, Issuer, Acc);
- (_, Acc) ->
- Acc
- end,
+ case find_issuer(IsIssuerFun, certpath,
+ [{Der, public_key:pkix_decode_cert(Der,otp)} || Der <- CertPath]) of
+ {ok, OtpCert} ->
+ {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef),
+ {ok, Root, lists:reverse(Chain)};
+ {error, issuer_not_found} ->
+ {error, unknown_ca}
+ end.
+
+search_db(IsIssuerFun, _, {extracted, ExtractedCerts})->
+ find_issuer(IsIssuerFun, extracted, ExtractedCerts);
+search_db(IsIssuerFun, Db, DbRef) ->
find_issuer(IsIssuerFun, Db, DbRef).
find_issuer(IsIssuerFun, certpath, Certs) ->
diff --git a/lib/ssl/src/ssl_dist_connection_sup.erl b/lib/ssl/src/ssl_dist_connection_sup.erl
index e5842c866e..441a7577be 100644
--- a/lib/ssl/src/ssl_dist_connection_sup.erl
+++ b/lib/ssl/src/ssl_dist_connection_sup.erl
@@ -42,38 +42,20 @@ start_link() ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-
init([]) ->
-
- TLSConnetionManager = tls_connection_manager_child_spec(),
- %% Handles emulated options so that they inherited by the accept
- %% socket, even when setopts is performed on the listen socket
- ListenOptionsTracker = listen_options_tracker_child_spec(),
-
- {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager,
- ListenOptionsTracker
- ]}}.
+ TLSSup = tls_sup_child_spec(),
+ {ok, {{one_for_one, 10, 3600}, [TLSSup]}}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-tls_connection_manager_child_spec() ->
- Name = dist_tls_connection,
- StartFunc = {tls_connection_sup, start_link_dist, []},
- Restart = permanent,
- Shutdown = 4000,
- Modules = [tls_connection_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-listen_options_tracker_child_spec() ->
- Name = dist_tls_socket,
- StartFunc = {ssl_listen_tracker_sup, start_link_dist, []},
+tls_sup_child_spec() ->
+ Name = dist_tls_sup,
+ StartFunc = {tls_dist_sup, start_link, []},
Restart = permanent,
Shutdown = 4000,
- Modules = [tls_socket],
+ Modules = [tls_dist_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl
index bea67935d8..ae0887c3d9 100644
--- a/lib/ssl/src/ssl_dist_sup.erl
+++ b/lib/ssl/src/ssl_dist_sup.erl
@@ -70,16 +70,16 @@ ssl_admin_child_spec() ->
StartFunc = {ssl_dist_admin_sup, start_link , []},
Restart = permanent,
Shutdown = 4000,
- Modules = [ssl_admin_sup],
+ Modules = [ssl_dist_admin_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
ssl_connection_sup() ->
- Name = ssl_dist_connection_sup,
- StartFunc = {ssl_dist_connection_sup, start_link, []},
+ Name = tls_dist_sup,
+ StartFunc = {tls_dist_sup, start_link, []},
Restart = permanent,
Shutdown = 4000,
- Modules = [ssl_connection_sup],
+ Modules = [tls_dist_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl
new file mode 100644
index 0000000000..e6268b4876
--- /dev/null
+++ b/lib/ssl/src/ssl_gen_statem.erl
@@ -0,0 +1,2055 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Provid help function to handle generic parts of TLS
+%% connection fsms
+%%----------------------------------------------------------------------
+
+-module(ssl_gen_statem).
+
+-include_lib("kernel/include/logger.hrl").
+
+-include("ssl_api.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_connection.hrl").
+-include("ssl_alert.hrl").
+-include("tls_handshake.hrl").
+
+%% Initial Erlang process setup
+-export([start_link/7,
+ start_link/8,
+ init/1]).
+
+%% TLS connection setup
+-export([ssl_config/3,
+ connect/8,
+ handshake/7,
+ handshake/2,
+ handshake/3,
+ handshake_continue/3,
+ handshake_cancel/1,
+ handle_sni_extension/2,
+ socket_control/4,
+ socket_control/5,
+ prepare_connection/2]).
+
+%% User Events
+-export([send/2,
+ recv/3,
+ close/2,
+ shutdown/2,
+ new_user/2,
+ get_opts/2,
+ set_opts/2,
+ peer_certificate/1,
+ negotiated_protocol/1,
+ connection_information/2
+ ]).
+
+%% Erlang Distribution export
+-export([dist_handshake_complete/2]).
+
+%% Generic fsm states
+-export([initial_hello/3,
+ config_error/3,
+ connection/3]).
+
+-export([call/2,
+ handle_common_event/4,
+ handle_call/4,
+ handle_info/3
+ ]).
+
+-export([hibernate_after/3]).
+
+%% Data handling
+-export([read_application_data/2]).
+
+%% Alert and close handling
+-export([send_alert/3,
+ handle_own_alert/4,
+ handle_alert/3,
+ handle_normal_shutdown/3,
+ handle_trusted_certs_db/1,
+ maybe_invalidate_session/6,
+ maybe_invalidate_session/5,
+ terminate/3]).
+
+%% Log handling
+-export([format_status/2]).
+
+%%--------------------------------------------------------------------
+%%% Initial Erlang process setup
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+-spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+ {ok, pid()} | ignore | {error, reason()}.
+%%
+%% Description: Creates a process which calls Module:init/1 to
+%% choose appropriat gen_statem and initialize.
+%%--------------------------------------------------------------------
+start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) ->
+ {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}.
+
+%%--------------------------------------------------------------------
+-spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+ {ok, pid()} | ignore | {error, reason()}.
+%%
+%% Description: Creates a gen_statem process which calls Module:init/1 to
+%% initialize.
+%%--------------------------------------------------------------------
+start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
+ {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}.
+
+
+%%--------------------------------------------------------------------
+-spec init(list()) -> no_return().
+%% Description: Initialization
+%%--------------------------------------------------------------------
+init([_Role, Sender, _Host, _Port, _Socket, {#{erl_dist := ErlDist} = TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
+ process_flag(trap_exit, true),
+ link(Sender),
+ case ErlDist of
+ true ->
+ process_flag(priority, max);
+ _ ->
+ ok
+ end,
+ ConnectionFsm = tls_connection_fsm(TLSOpts),
+ ConnectionFsm:init(InitArgs);
+init([_Role, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
+ process_flag(trap_exit, true),
+ ConnectionFsm = dtls_connection_fsm(TLSOpts),
+ ConnectionFsm:init(InitArgs).
+
+%%====================================================================
+%% TLS connection setup
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
+%%--------------------------------------------------------------------
+ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
+ handshake_env = HsEnv,
+ connection_env = CEnv} = State0) ->
+ {ok, #{cert_db_ref := Ref,
+ cert_db_handle := CertDbHandle,
+ fileref_db_handle := FileRefHandle,
+ session_cache := CacheHandle,
+ crl_db_info := CRLDbHandle,
+ private_key := Key,
+ dh_params := DHParams,
+ own_certificates := OwnCerts}} =
+ ssl_config:init(Opts, Role),
+ TimeStamp = erlang:monotonic_time(),
+ Session = State0#state.session,
+
+ State0#state{session = Session#session{own_certificates = OwnCerts,
+ time_stamp = TimeStamp},
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
+ handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams},
+ connection_env = CEnv#connection_env{private_key = Key},
+ ssl_options = Opts}.
+
+%%--------------------------------------------------------------------
+-spec connect(tls_gen_connection | dtls_gen_connection,
+ ssl:host(), inet:port_number(),
+ port() | {tuple(), port()}, %% TLS | DTLS
+ {ssl_options(), #socket_options{},
+ %% Tracker only needed on server side
+ undefined},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Connect to an ssl server.
+%%--------------------------------------------------------------------
+connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
+ Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+%%--------------------------------------------------------------------
+-spec handshake(tls_gen_connection | dtls_gen_connection,
+ inet:port_number(), port(),
+ {ssl_options(), #socket_options{}, list()},
+ pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Performs accept on an ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
+handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) ->
+ try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User,
+ CbInfo, Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {ok, #sslsocket{}, map()}| {error, reason()}.
+%%
+%% Description: Starts ssl handshake.
+%%--------------------------------------------------------------------
+handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) ->
+ case call(Pid, {start, Timeout}) of
+ connected ->
+ {ok, Socket};
+ {ok, Ext} ->
+ {ok, Socket, no_records(Ext)};
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) ->
+ {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}.
+%%
+%% Description: Starts ssl handshake with some new options
+%%--------------------------------------------------------------------
+handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
+ case call(Pid, {start, SslOptions, Timeout}) of
+ connected ->
+ {ok, Socket};
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+-spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()],
+ timeout()) -> {ok, #sslsocket{}}| {error, reason()}.
+%%
+%% Description: Continues handshake with new options
+%%--------------------------------------------------------------------
+handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
+ case call(Pid, {handshake_continue, SslOptions, Timeout}) of
+ connected ->
+ {ok, Socket};
+ Error ->
+ Error
+ end.
+%%--------------------------------------------------------------------
+-spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}.
+%%
+%% Description: Cancels connection
+%%--------------------------------------------------------------------
+handshake_cancel(#sslsocket{pid = [Pid|_]}) ->
+ case call(Pid, cancel) of
+ closed ->
+ ok;
+ Error ->
+ Error
+ end.
+%--------------------------------------------------------------------
+-spec socket_control(tls_gen_connection | dtls_gen_connection, port(), [pid()], atom()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Set the ssl process to own the accept socket
+%%--------------------------------------------------------------------
+socket_control(Connection, Socket, Pid, Transport) ->
+ socket_control(Connection, Socket, Pid, Transport, undefined).
+
+%--------------------------------------------------------------------
+-spec socket_control(tls_gen_connection | dtls_gen_connection, port(), [pid()], atom(), [pid()] | atom()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%--------------------------------------------------------------------
+socket_control(dtls_gen_connection = Connection, Socket, Pids, Transport, udp_listener) ->
+ %% dtls listener process must have the socket control
+ {ok, Connection:socket(Pids, Transport, Socket, undefined)};
+
+socket_control(tls_gen_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
+ case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+socket_control(dtls_gen_connection = Connection, {PeerAddrPort, Socket}, [Pid|_] = Pids, Transport, Trackers) ->
+ case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, Connection:socket(Pids, Transport, {PeerAddrPort, Socket}, Trackers)};
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
+ start_or_recv_from = RecvFrom} = State0, Connection)
+ when Renegotiate =/= {false, first},
+ RecvFrom =/= undefined ->
+ State = Connection:reinit(State0),
+ {no_record, ack_connection(State)};
+prepare_connection(State0, Connection) ->
+ State = Connection:reinit(State0),
+ {no_record, ack_connection(State)}.
+
+%%====================================================================
+%% User events
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec send(pid(), iodata()) -> ok | {error, reason()}.
+%%
+%% Description: Sends data over the ssl connection
+%%--------------------------------------------------------------------
+send(Pid, Data) ->
+ call(Pid, {application_data,
+ %% iolist_to_iovec should really
+ %% be called iodata_to_iovec()
+ erlang:iolist_to_iovec(Data)}).
+
+%%--------------------------------------------------------------------
+-spec recv(pid(), integer(), timeout()) ->
+ {ok, binary() | list()} | {error, reason()}.
+%%
+%% Description: Receives data when active = false
+%%--------------------------------------------------------------------
+recv(Pid, Length, Timeout) ->
+ call(Pid, {recv, Length, Timeout}).
+
+%%--------------------------------------------------------------------
+-spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Get connection information
+%%--------------------------------------------------------------------
+connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) ->
+ case call(Pid, {connection_information, IncludeSecrityInfo}) of
+ {ok, Info} when IncludeSecrityInfo == true ->
+ {ok, maybe_add_keylog(Info)};
+ Other ->
+ Other
+ end.
+
+%%--------------------------------------------------------------------
+-spec close(pid(), {close, Timeout::integer() |
+ {NewController::pid(), Timeout::integer()}}) ->
+ ok | {ok, port()} | {error, reason()}.
+%%
+%% Description: Close an ssl connection
+%%--------------------------------------------------------------------
+close(ConnectionPid, How) ->
+ case call(ConnectionPid, How) of
+ {error, closed} ->
+ ok;
+ Other ->
+ Other
+ end.
+%%--------------------------------------------------------------------
+-spec shutdown(pid(), atom()) -> ok | {error, reason()}.
+%%
+%% Description: Same as gen_tcp:shutdown/2
+%%--------------------------------------------------------------------
+shutdown(ConnectionPid, How) ->
+ call(ConnectionPid, {shutdown, How}).
+
+%%--------------------------------------------------------------------
+-spec new_user(pid(), pid()) -> ok | {error, reason()}.
+%%
+%% Description: Changes process that receives the messages when active = true
+%% or once.
+%%--------------------------------------------------------------------
+new_user(ConnectionPid, User) ->
+ call(ConnectionPid, {new_user, User}).
+
+%%--------------------------------------------------------------------
+-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
+%%
+%% Description: Same as inet:getopts/2
+%%--------------------------------------------------------------------
+get_opts(ConnectionPid, OptTags) ->
+ call(ConnectionPid, {get_opts, OptTags}).
+%%--------------------------------------------------------------------
+-spec set_opts(pid(), list()) -> ok | {error, reason()}.
+%%
+%% Description: Same as inet:setopts/2
+%%--------------------------------------------------------------------
+set_opts(ConnectionPid, Options) ->
+ call(ConnectionPid, {set_opts, Options}).
+
+%%--------------------------------------------------------------------
+-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}.
+%%
+%% Description: Returns the peer cert
+%%--------------------------------------------------------------------
+peer_certificate(ConnectionPid) ->
+ call(ConnectionPid, peer_certificate).
+
+%%--------------------------------------------------------------------
+-spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}.
+%%
+%% Description: Returns the negotiated protocol
+%%--------------------------------------------------------------------
+negotiated_protocol(ConnectionPid) ->
+ call(ConnectionPid, negotiated_protocol).
+
+dist_handshake_complete(ConnectionPid, DHandle) ->
+ gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}).
+
+handle_sni_extension(undefined, State) ->
+ {ok, State};
+handle_sni_extension(#sni{hostname = Hostname}, State0) ->
+ case check_hostname(State0, Hostname) of
+ valid ->
+ State1 = handle_sni_hostname(Hostname, State0),
+ State = set_sni_guided_cert_selection(State1, true),
+ {ok, State};
+ unrecognized_name ->
+ {ok, handle_sni_hostname(Hostname, State0)};
+ #alert{} = Alert ->
+ {error, Alert}
+ end.
+
+%%====================================================================
+%% Generic states
+%%====================================================================
+%%--------------------------------------------------------------------
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | {start, {list(), list()}, timeout()}| term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+initial_hello({call, From}, {start, Timeout},
+ #state{static_env = #static_env{role = client = Role,
+ host = Host,
+ port = Port,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState0},
+ connection_env = CEnv,
+ ssl_options = #{%% Use highest version in initial ClientHello.
+ %% Versions is a descending list of supported versions.
+ versions := [HelloVersion|_] = Versions,
+ session_tickets := SessionTickets,
+ ocsp_stapling := OcspStaplingOpt,
+ ocsp_nonce := OcspNonceOpt,
+ early_data := EarlyData} = SslOpts,
+ session = Session,
+ connection_states = ConnectionStates0
+ } = State0) ->
+
+ KeyShare = maybe_generate_client_shares(SslOpts),
+ %% Update UseTicket in case of automatic session resumption. The automatic ticket handling
+ %% also takes it into account if the ticket is suitable for sending early data not exceeding
+ %% the max_early_data_size or if it can only be used for session resumption.
+ {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
+ TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
+ OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
+ Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
+ Session#session.session_id,
+ Renegotiation,
+ Session#session.own_certificates,
+ KeyShare,
+ TicketData,
+ OcspNonce),
+
+ %% Early Data Indication
+ Hello1 = tls_handshake_1_3:maybe_add_early_data_indication(Hello0,
+ EarlyData,
+ HelloVersion),
+
+ %% Update pre_shared_key extension with binders (TLS 1.3)
+ Hello2 = tls_handshake_1_3:maybe_add_binders(Hello1, TicketData, HelloVersion),
+
+ MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined),
+ ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+ State2 = State1#state{connection_states = ConnectionStates1,
+ connection_env = CEnv#connection_env{negotiated_version = HelloVersion}},
+
+ State3 = Connection:queue_handshake(Hello2, State2),
+
+ %% RequestedVersion is used as the legacy record protocol version and shall be
+ %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the
+ %% lowest supported protocol version.
+ %%
+ %% negotiated_version is also used by the TLS 1.3 state machine and is set after
+ %% ServerHello is processed.
+ RequestedVersion = tls_record:hello_version(Versions),
+
+ {Ref,Maybe} = tls_handshake_1_3:maybe(),
+ try
+ %% Send Early Data
+ State4 = Maybe(tls_handshake_1_3:maybe_send_early_data(State3)),
+
+ {#state{handshake_env = HsEnv1} = State5, _} =
+ Connection:send_handshake_flight(State4),
+
+ State = State5#state{
+ connection_env = CEnv#connection_env{
+ negotiated_version = RequestedVersion},
+ session = Session,
+ handshake_env = HsEnv1#handshake_env{
+ ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}},
+ start_or_recv_from = From,
+ key_share = KeyShare},
+ NextState = next_statem_state(Versions, Role),
+ Connection:next_event(NextState, no_record, State,
+ [{{timeout, handshake}, Timeout, close}])
+ catch
+ {Ref, #alert{} = Alert} ->
+ handle_own_alert(Alert, RequestedVersion, init,
+ State0#state{start_or_recv_from = From})
+ end;
+initial_hello({call, From}, {start, Timeout}, #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{versions := Versions}} = State0) ->
+
+ NextState = next_statem_state(Versions, Role),
+ Connection:next_event(NextState, no_record, State0#state{start_or_recv_from = From},
+ [{{timeout, handshake}, Timeout, close}]);
+
+initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout},
+ #state{static_env = #static_env{role = Role},
+ ssl_options = OrigSSLOptions,
+ socket_options = SockOpts} = State0) ->
+ try
+ SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
+ State = ssl_config(SslOpts, Role, State0),
+ initial_hello({call, From}, {start, Timeout},
+ State#state{ssl_options = SslOpts,
+ socket_options = new_emulated(EmOpts, SockOpts)})
+ catch throw:Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0}
+ end;
+initial_hello({call, From}, {new_user, _} = Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+initial_hello({call, From}, _Msg, _State) ->
+ {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]};
+initial_hello(_Type, _Event, _State) ->
+ {keep_state_and_data, [postpone]}.
+
+%%--------------------------------------------------------------------
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+config_error({call, From}, {start, _Timeout},
+ #state{protocol_specific = #{error := Error}} = State) ->
+ {stop_and_reply, {shutdown, normal},
+ [{reply, From, {error, Error}}], State};
+config_error({call, From}, {close, _}, State) ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State};
+config_error({call, From}, _Msg, State) ->
+ {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]};
+config_error(_Type, _Event, _State) ->
+ {keep_state_and_data, [postpone]}.
+
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection({call, RecvFrom}, {recv, N, Timeout},
+ #state{static_env = #static_env{protocol_cb = Connection},
+ socket_options =
+ #socket_options{active = false}} = State0) ->
+ passive_receive(State0#state{bytes_to_read = N,
+ start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection,
+ [{{timeout, recv}, Timeout, timeout}]);
+connection({call, From}, peer_certificate,
+ #state{session = #session{peer_certificate = Cert}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]);
+connection({call, From}, {connection_information, true}, State) ->
+ Info = connection_info(State) ++ security_info(State),
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
+connection({call, From}, {connection_information, false}, State) ->
+ Info = connection_info(State),
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = undefined}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = SelectedProtocol}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} = State) ->
+ hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection({call, From},
+ {close, {Pid, _Timeout}},
+ #state{connection_env = #connection_env{terminated = closed} = CEnv,
+ protocol_specific = PS} = State) ->
+ {next_state, downgrade, State#state{connection_env =
+ CEnv#connection_env{terminated = true,
+ downgrade = {Pid, From}},
+ protocol_specific = PS#{active_n_toggle => true,
+ active_n => 1}
+ },
+ [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
+connection({call, From},
+ {close,{Pid, Timeout}},
+ #state{connection_states = ConnectionStates,
+ static_env = #static_env{protocol_cb = Connection},
+ protocol_specific = #{sender := Sender} = PS,
+ connection_env = CEnv
+ } = State0) ->
+ case tls_sender:downgrade(Sender, Timeout) of
+ {ok, Write} ->
+ %% User downgrades connection
+ %% When downgrading an TLS connection to a transport connection
+ %% we must recive the close alert from the peer before releasing the
+ %% transport socket.
+ State = Connection:send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ State0#state{connection_states =
+ ConnectionStates#{current_write => Write}}),
+ {next_state, downgrade, State#state{connection_env =
+ CEnv#connection_env{downgrade = {Pid, From},
+ terminated = true},
+ protocol_specific = PS#{active_n_toggle => true,
+ active_n => 1}
+ },
+ [{timeout, Timeout, downgrade}]};
+ {error, timeout} ->
+ {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
+ end;
+connection({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+connection(cast, {dist_handshake_complete, DHandle},
+ #state{ssl_options = #{erl_dist := true},
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = CEnv,
+ socket_options = SockOpts} = State0) ->
+ process_flag(priority, normal),
+ State1 =
+ State0#state{
+ socket_options = SockOpts#socket_options{active = true},
+ connection_env = CEnv#connection_env{erl_dist_handle = DHandle},
+ bytes_to_read = undefined},
+ {Record, State} = read_application_data(<<>>, State1),
+ Connection:next_event(connection, Record, State);
+connection(info, Msg, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:handle_info(Msg, ?FUNCTION_NAME, State);
+connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom,
+ static_env = #static_env{protocol_cb = Connection}} = State) ->
+ passive_receive(State, ?FUNCTION_NAME, Connection, []);
+connection(Type, Msg, State) ->
+ handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%====================================================================
+%% Event/Msg handling
+%%====================================================================
+handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
+ #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = _Version}} = State0) ->
+ Hist = ssl_handshake:update_handshake_history(Hist0, Raw),
+ {next_state, StateName,
+ State0#state{handshake_env =
+ HsEnv#handshake_env{tls_handshake_history = Hist}},
+ [{next_event, internal, Handshake}]};
+handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName,
+ #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State);
+handle_common_event(timeout, hibernate, _, _) ->
+ {keep_state_and_data, [hibernate]};
+handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, StateName, State);
+handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State) ->
+ {stop_and_reply,
+ {shutdown, user_timeout},
+ {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}};
+handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State) ->
+ {next_state, StateName, State#state{start_or_recv_from = undefined,
+ bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]};
+handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}) when
+ StateName =/= connection ->
+ {keep_state_and_data, [postpone]};
+handle_common_event(Type, Msg, StateName, #state{connection_env =
+ #connection_env{negotiated_version = Version}} = State) ->
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}),
+ handle_own_alert(Alert, Version, StateName, State).
+
+handle_call({application_data, _Data}, _, _, _) ->
+ %% In renegotiation priorities handshake, send data when handshake is finished
+ {keep_state_and_data, [postpone]};
+handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State) ->
+ %% Run terminate before returning so that the reuseaddr
+ %% inet-option works properly
+ Result = terminate(Close, StateName, State),
+ {stop_and_reply,
+ {shutdown, normal},
+ {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}};
+handle_call({shutdown, read_write = How}, From, StateName,
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket},
+ connection_env = CEnv} = State) ->
+ try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ StateName, State) of
+ _ ->
+ case Transport:shutdown(Socket, How) of
+ ok ->
+ {next_state, StateName, State#state{connection_env =
+ CEnv#connection_env{terminated = true}},
+ [{reply, From, ok}]};
+ Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error},
+ State#state{connection_env = CEnv#connection_env{terminated = true}}}
+ end
+ catch
+ throw:Return ->
+ Return
+ end;
+handle_call({shutdown, How0}, From, StateName,
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket}} = State) ->
+ case Transport:shutdown(Socket, How0) of
+ ok ->
+ {next_state, StateName, State, [{reply, From, ok}]};
+ Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State}
+ end;
+handle_call({recv, _N, _Timeout}, From, _,
+ #state{socket_options =
+ #socket_options{active = Active}}) when Active =/= false ->
+ {keep_state_and_data, [{reply, From, {error, einval}}]};
+handle_call({recv, N, Timeout}, RecvFrom, StateName, State) ->
+ %% Doing renegotiate wait with handling request until renegotiate is
+ %% finished.
+ {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom},
+ [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]};
+handle_call({new_user, User}, From, StateName,
+ State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}) ->
+ NewMon = erlang:monitor(process, User),
+ erlang:demonitor(OldMon, [flush]),
+ {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}},
+ [{reply, From, ok}]};
+handle_call({get_opts, OptTags}, From, _,
+ #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport},
+ socket_options = SockOpts}) ->
+ OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []),
+ {keep_state_and_data, [{reply, From, OptsReply}]};
+handle_call({set_opts, Opts0}, From, StateName,
+ #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport,
+ trackers = Trackers},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}},
+ socket_options = Opts1
+ } = State0) ->
+ {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
+ case {proplists:lookup(active, Opts0), Opts} of
+ {{_, N}, #socket_options{active=false}} when is_integer(N) ->
+ send_user(
+ Pid,
+ format_passive(
+ Connection:pids(State0), Transport, Socket, Trackers, Connection));
+ _ ->
+ ok
+ end,
+ State = State0#state{socket_options = Opts},
+ handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
+
+handle_call(renegotiate, From, StateName, _) when StateName =/= connection ->
+ {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
+
+handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
+ #state{connection_states = ConnectionStates,
+ connection_env = #connection_env{negotiated_version = Version}}) ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{master_secret = MasterSecret,
+ client_random = ClientRandom,
+ server_random = ServerRandom,
+ prf_algorithm = PRFAlgorithm} = SecParams,
+ Reply = try
+ SecretToUse = case Secret of
+ _ when is_binary(Secret) -> Secret;
+ master_secret -> MasterSecret
+ end,
+ SeedToUse = lists:reverse(
+ lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc];
+ (client_random, Acc) -> [ClientRandom|Acc];
+ (server_random, Acc) -> [ServerRandom|Acc]
+ end, [], Seed)),
+ ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
+ catch
+ exit:_ -> {error, badarg};
+ error:Reason -> {error, Reason}
+ end,
+ {keep_state_and_data, [{reply, From, Reply}]};
+handle_call(_,_,_,_) ->
+ {keep_state_and_data, [postpone]}.
+handle_info({ErrorTag, Socket, econnaborted}, StateName,
+ #state{static_env = #static_env{role = Role,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ transport_cb = Transport,
+ error_tag = ErrorTag,
+ trackers = Trackers,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = Type},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session,
+ start_or_recv_from = StartFrom
+ } = State) when StateName =/= connection ->
+
+ maybe_invalidate_session(Version, Type, Role, Host, Port, Session),
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers,Socket,
+ StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection),
+ {stop, {shutdown, normal}, State};
+
+handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{
+ role = Role,
+ socket = Socket,
+ error_tag = ErrorTag},
+ ssl_options = #{log_level := Level}} = State) ->
+ ssl_logger:log(info, Level, #{description => "Socket error",
+ reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION),
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}),
+ handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop, {shutdown,normal}, State};
+
+handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
+ #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}},
+ ssl_options = #{erl_dist := true}}) ->
+ {stop, {shutdown, Reason}};
+handle_info({'DOWN', MonitorRef, _, _, _}, _,
+ #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) ->
+ {stop, {shutdown, normal}};
+handle_info({'EXIT', Pid, _Reason}, StateName,
+ #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) ->
+ %% It seems the user application has linked to us
+ %% - ignore that and let the monitor handle this
+ {next_state, StateName, State};
+%%% So that terminate will be run when supervisor issues shutdown
+handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
+ {stop, shutdown, State};
+handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
+ %% Handle as transport close"
+ {stop,{shutdown, transport_closed}, State};
+handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
+ {stop,{shutdown, Reason}, State};
+handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) -> %% PRE TLS-1.3
+ {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}};
+handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag},
+ ssl_options = #{log_level := Level}} = State) ->
+ ssl_logger:log(notice, Level, #{description => "Unexpected INFO message",
+ reason => [{message, Msg}, {socket, Socket},
+ {error_tag, ErrorTag}]}, ?LOCATION),
+ {next_state, StateName, State}.
+
+%%====================================================================
+%% Application Data
+%%====================================================================
+read_application_data(Data,
+ #state{user_data_buffer =
+ {Front0,BufferSize0,Rear0},
+ connection_env =
+ #connection_env{erl_dist_handle = DHandle}}
+ = State) ->
+ Front = Front0,
+ BufferSize = BufferSize0 + byte_size(Data),
+ Rear = [Data|Rear0],
+ case DHandle of
+ undefined ->
+ read_application_data(State, Front, BufferSize, Rear);
+ _ ->
+ try read_application_dist_data(DHandle, Front, BufferSize, Rear) of
+ Buffer ->
+ {no_record, State#state{user_data_buffer = Buffer}}
+ catch error:_ ->
+ {stop,disconnect,
+ State#state{user_data_buffer = {Front,BufferSize,Rear}}}
+ end
+ end.
+passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
+ %% Assert! Erl distribution uses active sockets
+ connection_env = #connection_env{erl_dist_handle = undefined}}
+ = State0, StateName, Connection, StartTimerAction) ->
+ case BufferSize of
+ 0 ->
+ Connection:next_event(StateName, no_record, State0, StartTimerAction);
+ _ ->
+ case read_application_data(State0, Front, BufferSize, Rear) of
+ {stop, _, _} = ShutdownError ->
+ ShutdownError;
+ {Record, State} ->
+ case State#state.start_or_recv_from of
+ undefined ->
+ %% Cancel recv timeout as data has been delivered
+ Connection:next_event(StateName, Record, State,
+ [{{timeout, recv}, infinity, timeout}]);
+ _ ->
+ Connection:next_event(StateName, Record, State, StartTimerAction)
+ end
+ end
+ end.
+
+%%====================================================================
+%% Hibernation
+%%====================================================================
+
+hibernate_after(connection = StateName,
+ #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
+ Actions) ->
+ {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
+hibernate_after(StateName, State, Actions) ->
+ {next_state, StateName, State, Actions}.
+
+%%====================================================================
+%% Alert and close handling
+%%====================================================================
+send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:send_alert_in_connection(Alert, State);
+send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ Connection:send_alert(Alert, State).
+
+handle_own_alert(Alert0, _, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ try %% Try to tell the other side
+ send_alert(Alert0, StateName, State)
+ catch _:_ -> %% Can crash if we are in a uninitialized state
+ ignore
+ end,
+ try %% Try to tell the local user
+ Alert = Alert0#alert{role = Role},
+ log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert),
+ handle_normal_shutdown(Alert,StateName, State)
+ catch _:_ ->
+ ok
+ end,
+ {stop, {shutdown, own_alert}, State}.
+
+handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ trackers = Trackers},
+ handshake_env = #handshake_env{renegotiation = {false, first}},
+ start_or_recv_from = StartFrom} = State) ->
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection);
+
+handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ trackers = Trackers},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ socket_options = Opts,
+ start_or_recv_from = RecvFrom} = State) ->
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
+
+handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ host = Host,
+ port = Port,
+ trackers = Trackers,
+ transport_cb = Transport,
+ protocol_cb = Connection},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ ssl_options = #{log_level := LogLevel},
+ start_or_recv_from = From,
+ session = Session,
+ socket_options = Opts} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ log_alert(LogLevel, Role, Connection:protocol_name(),
+ StateName, Alert),
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
+ {stop, {shutdown, normal}, State};
+
+handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ downgrade= StateName, State) ->
+ {next_state, StateName, State, [{next_event, internal, Alert}]};
+handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0,
+ StateName, #state{static_env = #static_env{role = Role}} = State) ->
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ handle_normal_shutdown(Alert, StateName, State),
+ {stop,{shutdown, peer_close}, State};
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, internal}},
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName, Alert),
+ handle_normal_shutdown(Alert, StateName, State),
+ {stop,{shutdown, peer_close}, State};
+
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = #{log_level := LogLevel}
+ } = State0) ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
+ gen_statem:reply(From, {error, renegotiation_rejected}),
+ State = Connection:reinit_handshake_data(State0),
+ Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}});
+
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = #{log_level := LogLevel}
+ } = State0) ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
+ gen_statem:reply(From, {error, renegotiation_rejected}),
+ %% Go back to connection!
+ State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
+ Connection:next_event(connection, no_record, State);
+
+%% Gracefully log and ignore all other warning alerts
+handle_alert(#alert{level = ?WARNING} = Alert, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{log_level := LogLevel}} = State) ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName,
+ Alert#alert{role = opposite_role(Role)}),
+ Connection:next_event(StateName, no_record, State).
+handle_trusted_certs_db(#state{ssl_options =
+ #{cacertfile := <<>>, cacerts := []}}) ->
+ %% No trusted certs specified
+ ok;
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ cert_db = CertDb},
+ ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined ->
+ %% Certs provided as DER directly can not be shared
+ %% with other connections and it is safe to delete them when the connection ends.
+ ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
+handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) ->
+ %% Something went wrong early (typically cacertfile does not
+ %% exist) so there is nothing to handle
+ ok;
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ file_ref_db = RefDb},
+ ssl_options = #{cacertfile := File}}) ->
+ case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
+ 0 ->
+ ssl_manager:clean_cert_db(Ref, File);
+ _ ->
+ ok
+ end.
+
+maybe_invalidate_session({3, 4},_, _, _, _, _) ->
+ ok;
+maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 ->
+ maybe_invalidate_session(Type, Role, Host, Port, Session).
+
+maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
+ invalidate_session(Role, Host, Port, Session);
+maybe_invalidate_session(_, _, _, _, _) ->
+ ok.
+
+terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) ->
+ %% Happens when user closes the connection using ssl:close/1
+ %% we want to guarantee that Transport:close has been called
+ %% when ssl:close/1 returns unless it is a downgrade where
+ %% we want to guarantee that close alert is received before
+ %% returning. In both cases terminate has been run manually
+ %% before run by gen_statem which will end up here
+ ok;
+terminate({shutdown, transport_closed} = Reason,
+ _StateName, #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport}} = State) ->
+ handle_trusted_certs_db(State),
+ Connection:close(Reason, Socket, Transport, undefined, undefined);
+terminate({shutdown, own_alert}, _StateName, #state{
+ static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport}} = State) ->
+ handle_trusted_certs_db(State),
+ case application:get_env(ssl, alert_timeout) of
+ {ok, Timeout} when is_integer(Timeout) ->
+ Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined);
+ _ ->
+ Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
+ end;
+terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket}
+ } = State) ->
+ handle_trusted_certs_db(State),
+ Connection:close(Reason, Socket, Transport, undefined, undefined);
+terminate(Reason, connection, #state{static_env = #static_env{
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket},
+ connection_states = ConnectionStates,
+ ssl_options = #{padding_check := Check}
+ } = State) ->
+ handle_trusted_certs_db(State),
+ Alert = terminate_alert(Reason),
+ %% Send the termination ALERT if possible
+ catch (ok = Connection:send_alert_in_connection(Alert, State)),
+ Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
+terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport,
+ protocol_cb = Connection,
+ socket = Socket}
+ } = State) ->
+ handle_trusted_certs_db(State),
+ Connection:close(Reason, Socket, Transport, undefined, undefined).
+
+%%====================================================================
+%% Log handling
+%%====================================================================
+format_status(normal, [_, StateName, State]) ->
+ [{data, [{"State", {StateName, State}}]}];
+format_status(terminate, [_, StateName, State]) ->
+ SslOptions = (State#state.ssl_options),
+ NewOptions = SslOptions#{password => ?SECRET_PRINTOUT,
+ cert => ?SECRET_PRINTOUT,
+ cacerts => ?SECRET_PRINTOUT,
+ key => ?SECRET_PRINTOUT,
+ dh => ?SECRET_PRINTOUT,
+ psk_identity => ?SECRET_PRINTOUT,
+ srp_identity => ?SECRET_PRINTOUT},
+ [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
+ protocol_buffers = ?SECRET_PRINTOUT,
+ user_data_buffer = ?SECRET_PRINTOUT,
+ handshake_env = ?SECRET_PRINTOUT,
+ connection_env = ?SECRET_PRINTOUT,
+ session = ?SECRET_PRINTOUT,
+ ssl_options = NewOptions,
+ flight_buffer = ?SECRET_PRINTOUT}
+ }}]}].
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+tls_connection_fsm(#{versions := [{3,4}]}) ->
+ tls_connection_1_3;
+tls_connection_fsm(_) ->
+ tls_connection.
+
+dtls_connection_fsm(_) ->
+ dtls_connection.
+
+next_statem_state([Version], client) ->
+ case ssl:tls_version(Version) of
+ {3,4} ->
+ wait_sh;
+ _ ->
+ hello
+ end;
+next_statem_state([Version], server) ->
+ case ssl:tls_version(Version) of
+ {3,4} ->
+ start;
+ _ ->
+ hello
+ end;
+next_statem_state(_, _) ->
+ hello.
+
+call(FsmPid, Event) ->
+ try gen_statem:call(FsmPid, Event)
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{normal, _} ->
+ {error, closed};
+ exit:{{shutdown, _},_} ->
+ {error, closed}
+ end.
+
+check_hostname(#state{ssl_options = SslOptions}, Hostname) ->
+ case is_sni_value(Hostname) of
+ true ->
+ case is_hostname_recognized(SslOptions, Hostname) of
+ true ->
+ valid;
+ false ->
+ %% We should send an alert but for interoperability reasons we
+ %% allow the connection to be established.
+ %% ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME)
+ unrecognized_name
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME,
+ {sni_included_trailing_dot, Hostname})
+ end.
+
+is_sni_value(Hostname) ->
+ case hd(lists:reverse(Hostname)) of
+ $. ->
+ false;
+ _ ->
+ true
+ end.
+
+is_hostname_recognized(#{sni_fun := undefined,
+ sni_hosts := SNIHosts}, Hostname) ->
+ proplists:is_defined(Hostname, SNIHosts);
+is_hostname_recognized(_, _) ->
+ true.
+
+handle_sni_hostname(Hostname,
+ #state{static_env = #static_env{role = Role} = InitStatEnv0,
+ handshake_env = HsEnv,
+ connection_env = CEnv} = State0) ->
+ NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
+ case NewOptions of
+ undefined ->
+ State0;
+ _ ->
+ {ok, #{cert_db_ref := Ref,
+ cert_db_handle := CertDbHandle,
+ fileref_db_handle := FileRefHandle,
+ session_cache := CacheHandle,
+ crl_db_info := CRLDbHandle,
+ private_key := Key,
+ dh_params := DHParams,
+ own_certificates := OwnCerts}} =
+ ssl_config:init(NewOptions, Role),
+ State0#state{
+ session = State0#state.session#session{own_certificates = OwnCerts},
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
+ connection_env = CEnv#connection_env{private_key = Key},
+ ssl_options = NewOptions,
+ handshake_env = HsEnv#handshake_env{sni_hostname = Hostname,
+ diffie_hellman_params = DHParams}
+ }
+ end.
+
+update_ssl_options_from_sni(#{sni_fun := SNIFun,
+ sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) ->
+ SSLOption =
+ case SNIFun of
+ undefined ->
+ proplists:get_value(SNIHostname,
+ SNIHosts);
+ SNIFun ->
+ SNIFun(SNIHostname)
+ end,
+ case SSLOption of
+ undefined ->
+ undefined;
+ _ ->
+ ssl:handle_options(SSLOption, server, OrigSSLOptions)
+ end.
+
+set_sni_guided_cert_selection(#state{handshake_env = HsEnv0} = State, Bool) ->
+ HsEnv = HsEnv0#handshake_env{sni_guided_cert_selection = Bool},
+ State#state{handshake_env = HsEnv}.
+
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer;
+ Initiater == internal ->
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) ->
+ gen_statem:reply(From, ok),
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv,
+ start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined ->
+ gen_statem:reply(StartFrom, connected),
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
+ start_or_recv_from = undefined};
+ack_connection(State) ->
+ State.
+
+no_records(Extensions) ->
+ maps:map(fun(_, Value) ->
+ ssl_handshake:extension_value(Value)
+ end, Extensions).
+
+handle_active_option(false, connection = StateName, To, Reply, State) ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+
+handle_active_option(_, connection = StateName, To, Reply, #state{static_env = #static_env{role = Role},
+ connection_env = #connection_env{terminated = true},
+ user_data_buffer = {_,0,_}} = State) ->
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_delivered),
+ handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop_and_reply,{shutdown, peer_close}, [{reply, To, Reply}]};
+handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
+ user_data_buffer = {_,0,_}} = State0) ->
+ case Connection:next_event(StateName0, no_record, State0) of
+ {next_state, StateName, State} ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+ {next_state, StateName, State, Actions} ->
+ hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
+ {stop, _, _} = Stop ->
+ Stop
+ end;
+handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) ->
+ %% Active once already set
+ {next_state, StateName, State, [{reply, To, Reply}]};
+
+%% user_data_buffer nonempty
+handle_active_option(_, StateName0, To, Reply,
+ #state{static_env = #static_env{protocol_cb = Connection}} = State0) ->
+ case read_application_data(<<>>, State0) of
+ {stop, _, _} = Stop ->
+ Stop;
+ {Record, State1} ->
+ %% Note: Renogotiation may cause StateName0 =/= StateName
+ case Connection:next_event(StateName0, Record, State1) of
+ {next_state, StateName, State} ->
+ hibernate_after(StateName, State, [{reply, To, Reply}]);
+ {next_state, StateName, State, Actions} ->
+ hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
+ {stop, _, _} = Stop ->
+ Stop
+ end
+ end.
+
+read_application_data(#state{
+ socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) ->
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead).
+
+%% Pick binary from queue front, if empty wait for more data
+read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin);
+read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ 0 = BufferSize, % Assert
+ {no_record, State#state{socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {Front,BufferSize,Rear}}};
+read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ [Bin|Front] = lists:reverse(Rear),
+ read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin).
+
+read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) ->
+ %% Done with this binary - get next
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead);
+read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) ->
+ %% Decode one packet from a binary
+ case get_data(SocketOpts0, BytesToRead, Bin0) of
+ {ok, Data, Bin} -> % Send data
+ BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)),
+ read_application_data_deliver(
+ State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data);
+ {more, undefined} ->
+ %% We need more data, do not know how much
+ if
+ byte_size(Bin0) < BufferSize0 ->
+ %% We have more data in the buffer besides the first binary - concatenate all and retry
+ Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
+ read_application_data_bin(
+ State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin);
+ true ->
+ %% All data is in the first binary, no use to retry - wait for more
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}
+ end;
+ {more, Size} when Size =< BufferSize0 ->
+ %% We have a packet in the buffer - collect it in a binary and decode
+ {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]),
+ Bin = iolist_to_binary(Data),
+ read_application_data_bin(
+ State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin);
+ {more, _Size} ->
+ %% We do not have a packet in the buffer - wait for more
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
+ passive ->
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
+ {error,_Reason} ->
+ %% Invalid packet in packet mode
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ trackers = Trackers},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}}} = State,
+ Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
+ deliver_packet_error(
+ Connection:pids(State), Transport, Socket, SocketOpts0,
+ Buffer, Pid, RecvFrom, Trackers, Connection),
+ {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Buffer],BufferSize0,[]}}}
+ end.
+
+read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) ->
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ trackers = Trackers},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}}} = State,
+ SocketOpts =
+ deliver_app_data(
+ Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection),
+ if
+ SocketOpts#socket_options.active =:= false ->
+ %% Passive mode, wait for active once or recv
+ {no_record,
+ State#state{
+ user_data_buffer = {Front,BufferSize,Rear},
+ start_or_recv_from = undefined,
+ bytes_to_read = undefined,
+ socket_options = SocketOpts
+ }};
+ true -> %% Try to deliver more data
+ %% Process early data if it is accepted.
+ case (State#state.handshake_env)#handshake_env.early_data_accepted of
+ false ->
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined);
+ true ->
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, undefined)
+ end
+ end.
+
+
+read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) ->
+ read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin);
+read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) ->
+ BufferSize = 0,
+ {Front,BufferSize,Rear};
+read_application_dist_data(DHandle, [], BufferSize, Rear) ->
+ [Bin|Front] = lists:reverse(Rear),
+ read_application_dist_data(DHandle, Front, BufferSize, [], Bin).
+%%
+read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
+ case Bin0 of
+ %%
+ %% START Optimization
+ %% It is cheaper to match out several packets in one match operation than to loop for each
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary,
+ SizeC:32, DataC:SizeC/binary,
+ SizeD:32, DataD:SizeD/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD ->
+ %% We have 4 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ erlang:dist_ctrl_put_data(DHandle, DataC),
+ erlang:dist_ctrl_put_data(DHandle, DataD),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest);
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary,
+ SizeC:32, DataC:SizeC/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB, 0 < SizeC ->
+ %% We have 3 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ erlang:dist_ctrl_put_data(DHandle, DataC),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest);
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary, Rest/binary>>
+ when 0 < SizeA, 0 < SizeB ->
+ %% We have 2 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest);
+ %% END Optimization
+ %%
+ %% Basic one packet code path
+ <<Size:32, Data:Size/binary, Rest/binary>> ->
+ %% We have a complete packet in the first binary
+ 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest);
+ <<Size:32, FirstData/binary>> when 4+Size =< BufferSize ->
+ %% We have a complete packet in the buffer
+ %% - fetch the missing content from the buffer front
+ {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]),
+ 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear);
+ <<Bin/binary>> ->
+ %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we
+ %% match out the whole binary which will trick the optimization into keeping the match context
+ %% for the first binary contains complete packet code above
+ case Bin of
+ <<_Size:32, _InsufficientData/binary>> ->
+ %% We have a length field in the first binary but there is not enough data
+ %% in the buffer to form a complete packet - await more data
+ {[Bin|Front0],BufferSize,Rear0};
+ <<IncompleteLengthField/binary>> when 4 < BufferSize ->
+ %% We do not have a length field in the first binary but the buffer
+ %% contains enough data to maybe form a packet
+ %% - fetch a tiny binary from the buffer front to complete the length field
+ {LengthField,Front,Rear} =
+ case IncompleteLengthField of
+ <<>> ->
+ iovec_from_front(4, Front0, Rear0, []);
+ _ ->
+ iovec_from_front(
+ 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField])
+ end,
+ LengthBin = iolist_to_binary(LengthField),
+ read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin);
+ <<IncompleteLengthField/binary>> ->
+ %% We do not have enough data in the buffer to even form a length field - await more data
+ case IncompleteLengthField of
+ <<>> ->
+ {Front0,BufferSize,Rear0};
+ _ ->
+ {[IncompleteLengthField|Front0],BufferSize,Rear0}
+ end
+ end
+ end.
+
+iovec_from_front(0, Front, Rear, Acc) ->
+ {lists:reverse(Acc),Front,Rear};
+iovec_from_front(Size, [], Rear, Acc) ->
+ case Rear of
+ %% Avoid lists:reverse/1 for simple cases.
+ %% Case clause for [] to avoid infinite loop.
+ [_] ->
+ iovec_from_front(Size, Rear, [], Acc);
+ [Bin2,Bin1] ->
+ iovec_from_front(Size, [Bin1,Bin2], [], Acc);
+ [Bin3,Bin2,Bin1] ->
+ iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc);
+ [_,_,_|_] = Rear ->
+ iovec_from_front(Size, lists:reverse(Rear), [], Acc)
+ end;
+iovec_from_front(Size, [Bin|Front], Rear, []) ->
+ case Bin of
+ <<Last:Size/binary>> -> % Just enough
+ {[Last],Front,Rear};
+ <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
+ {[Last],[Rest|Front],Rear};
+ <<>> -> % Not enough, skip empty binaries
+ iovec_from_front(Size, Front, Rear, []);
+ <<_/binary>> -> % Not enough
+ BinSize = byte_size(Bin),
+ iovec_from_front(Size - BinSize, Front, Rear, [Bin])
+ end;
+iovec_from_front(Size, [Bin|Front], Rear, Acc) ->
+ case Bin of
+ <<Last:Size/binary>> -> % Just enough
+ {lists:reverse(Acc, [Last]),Front,Rear};
+ <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
+ {lists:reverse(Acc, [Last]),[Rest|Front],Rear};
+ <<>> -> % Not enough, skip empty binaries
+ iovec_from_front(Size, Front, Rear, Acc);
+ <<_/binary>> -> % Not enough
+ BinSize = byte_size(Bin),
+ iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc])
+ end.
+%% Picks ClientData
+get_data(#socket_options{active=false}, undefined, _Bin) ->
+ %% Recv timed out save buffer data until next recv
+ passive;
+get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin)
+ when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
+ case Bin of
+ <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 ->
+ %% Active true or once, or passive mode recv(0)
+ {ok, Bin, <<>>};
+ <<Data:BytesToRead/binary, Rest/binary>> ->
+ %% Passive Mode, recv(Bytes)
+ {ok, Data, Rest};
+ <<_/binary>> ->
+ %% Passive Mode not enough data
+ {more, BytesToRead}
+ end;
+get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) ->
+ PacketOpts = [{packet_size, Size}],
+ decode_packet(Type, Bin, PacketOpts).
+
+decode_packet({http, headers}, Buffer, PacketOpts) ->
+ decode_packet(httph, Buffer, PacketOpts);
+decode_packet({http_bin, headers}, Buffer, PacketOpts) ->
+ decode_packet(httph_bin, Buffer, PacketOpts);
+decode_packet(Type, Buffer, PacketOpts) ->
+ erlang:decode_packet(Type, Buffer, PacketOpts).
+
+%% Just like with gen_tcp sockets, an ssl socket that has been configured with
+%% {packet, http} (or {packet, http_bin}) will automatically switch to expect
+%% HTTP headers after it sees a HTTP Request or HTTP Response line. We
+%% represent the current state as follows:
+%% #socket_options.packet =:= http: Expect a HTTP Request/Response line
+%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers
+%% Note that if the user has explicitly configured the socket to expect
+%% HTTP headers using the {packet, httph} option, we don't do any automatic
+%% switching of states.
+deliver_app_data(CPids, Transport, Socket,
+ #socket_options{active=Active, packet=Type} = SOpts,
+ Data, Pid, From, Trackers, Connection) ->
+ send_or_reply(Active, Pid, From,
+ format_reply(CPids, Transport, Socket,
+ SOpts, Data, Trackers, Connection)),
+ SO =
+ case Data of
+ {P, _, _, _}
+ when ((P =:= http_request) or (P =:= http_response)),
+ ((Type =:= http) or (Type =:= http_bin)) ->
+ SOpts#socket_options{packet={Type, headers}};
+ http_eoh when tuple_size(Type) =:= 2 ->
+ %% End of headers - expect another Request/Response line
+ {Type1, headers} = Type,
+ SOpts#socket_options{packet=Type1};
+ _ ->
+ SOpts
+ end,
+ case Active of
+ once ->
+ SO#socket_options{active=false};
+ 1 ->
+ send_user(Pid,
+ format_passive(CPids, Transport,
+ Socket, Trackers, Connection)),
+ SO#socket_options{active=false};
+ N when is_integer(N) ->
+ SO#socket_options{active=N - 1};
+ _ ->
+ SO
+ end.
+
+format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet,
+ header = Header}, Data, _, _) ->
+ {ok, do_format_reply(Mode, Packet, Header, Data)};
+format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
+ header = Header}, Data, Trackers, Connection) ->
+ {ssl, Connection:socket(CPids, Transport, Socket, Trackers),
+ do_format_reply(Mode, Packet, Header, Data)}.
+
+deliver_packet_error(CPids, Transport, Socket,
+ SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) ->
+ send_or_reply(Active, Pid, From, format_packet_error(CPids,
+ Transport, Socket, SO, Data, Trackers, Connection)).
+
+format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) ->
+ {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
+format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode},
+ Data, Trackers, Connection) ->
+ {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers),
+ {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
+
+do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
+ header(N, Data);
+do_format_reply(binary, _, _, Data) ->
+ Data;
+do_format_reply(list, Packet, _, Data)
+ when Packet == http; Packet == {http, headers};
+ Packet == http_bin; Packet == {http_bin, headers};
+ Packet == httph; Packet == httph_bin ->
+ Data;
+do_format_reply(list, _,_, Data) ->
+ binary_to_list(Data).
+
+format_passive(CPids, Transport, Socket, Trackers, Connection) ->
+ {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}.
+
+header(0, <<>>) ->
+ <<>>;
+header(_, <<>>) ->
+ [];
+header(0, Binary) ->
+ Binary;
+header(N, Binary) ->
+ <<?BYTE(ByteN), NewBinary/binary>> = Binary,
+ [ByteN | header(N-1, NewBinary)].
+
+send_or_reply(false, _Pid, From, Data) when From =/= undefined ->
+ gen_statem:reply(From, Data);
+send_or_reply(false, Pid, undefined, _) when is_pid(Pid) ->
+ ok;
+send_or_reply(_, no_pid, _, _) ->
+ ok;
+send_or_reply(_, Pid, _, Data) ->
+ send_user(Pid, Data).
+
+send_user(Pid, Msg) ->
+ Pid ! Msg,
+ ok.
+
+alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
+alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection).
+
+alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
+
+alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined ->
+ %% If there is an outstanding ssl_accept | recv
+ %% From will be defined and send_or_reply will
+ %% send the appropriate error message.
+ ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName),
+ send_or_reply(Active, Pid, From, {error, ReasonCode});
+alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) ->
+ case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of
+ closed ->
+ send_or_reply(Active, Pid, From,
+ {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)});
+ ReasonCode ->
+ send_or_reply(Active, Pid, From,
+ {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode})
+ end.
+
+log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
+ ssl_logger:log(notice, Level, #{protocol => ProtocolName,
+ role => Role,
+ statename => StateName,
+ alert => Alert,
+ alerter => own}, Alert#alert.where);
+log_alert(Level, Role, ProtocolName, StateName, Alert) ->
+ ssl_logger:log(notice, Level, #{protocol => ProtocolName,
+ role => Role,
+ statename => StateName,
+ alert => Alert,
+ alerter => peer}, Alert#alert.where).
+terminate_alert(normal) ->
+ ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
+terminate_alert({Reason, _}) when Reason == close;
+ Reason == shutdown ->
+ ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
+terminate_alert(_) ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR).
+
+invalidate_session(client, Host, Port, Session) ->
+ ssl_manager:invalidate_session(Host, Port, Session);
+invalidate_session(server, _, _, _) ->
+ ok.
+
+opposite_role(client) ->
+ server;
+opposite_role(server) ->
+ client.
+
+connection_info(#state{handshake_env = #handshake_env{sni_hostname = SNIHostname,
+ resumption = Resumption},
+ session = #session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ srp_username = SrpUsername,
+ ecc = ECCCurve} = Session,
+ connection_states = #{current_write := CurrentWrite},
+ connection_env = #connection_env{negotiated_version = {_,_} = Version},
+ ssl_options = #{protocol := Protocol} = Opts}) ->
+ RecordCB = record_cb(Protocol),
+ CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ IsNamedCurveSuite = lists:member(KexAlg,
+ [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]),
+ CurveInfo = case ECCCurve of
+ {namedCurve, Curve} when IsNamedCurveSuite ->
+ [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}];
+ _ ->
+ []
+ end,
+ MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of
+ MaxFragmentLength when is_integer(MaxFragmentLength) ->
+ [{max_fragment_length, MaxFragmentLength}];
+ _ ->
+ []
+ end,
+ [{protocol, RecordCB:protocol_version(Version)},
+ {session_id, SessionId},
+ {session_data, term_to_binary(Session)},
+ {session_resumption, Resumption},
+ {selected_cipher_suite, CipherSuiteDef},
+ {sni_hostname, SNIHostname},
+ {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts).
+
+security_info(#state{connection_states = ConnectionStates,
+ static_env = #static_env{role = Role},
+ ssl_options = #{keep_secrets := KeepSecrets}}) ->
+ ReadState = ssl_record:current_connection_state(ConnectionStates, read),
+ #{security_parameters :=
+ #security_parameters{client_random = ClientRand,
+ server_random = ServerRand,
+ master_secret = MasterSecret,
+ application_traffic_secret = AppTrafSecretRead,
+ client_early_data_secret = ServerEarlyData
+ }} = ReadState,
+ BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}],
+ if KeepSecrets =/= true ->
+ BaseSecurityInfo;
+ true ->
+ #{security_parameters :=
+ #security_parameters{application_traffic_secret = AppTrafSecretWrite,
+ client_early_data_secret = ClientEarlyData
+ }} =
+ ssl_record:current_connection_state(ConnectionStates, write),
+ if Role == server ->
+ if ServerEarlyData =/= undefined ->
+ [{server_traffic_secret_0, AppTrafSecretWrite},
+ {client_traffic_secret_0, AppTrafSecretRead},
+ {client_early_data_secret, ServerEarlyData}];
+ true ->
+ [{server_traffic_secret_0, AppTrafSecretWrite},
+ {client_traffic_secret_0, AppTrafSecretRead}]
+ end;
+ true ->
+ if ClientEarlyData =/= undefined ->
+ [{client_traffic_secret_0, AppTrafSecretWrite},
+ {server_traffic_secret_0, AppTrafSecretRead},
+ {client_early_data_secret, ClientEarlyData}];
+ true ->
+ [{client_traffic_secret_0, AppTrafSecretWrite},
+ {server_traffic_secret_0, AppTrafSecretRead}]
+ end
+ end ++
+ case ReadState of
+ #{client_handshake_traffic_secret := ClientHSTrafficSecret,
+ server_handshake_traffic_secret := ServerHSTrafficSecret} ->
+ [{client_handshake_traffic_secret, ClientHSTrafficSecret},
+ {server_handshake_traffic_secret, ServerHSTrafficSecret}];
+ _ ->
+ []
+ end ++ BaseSecurityInfo
+ end.
+
+record_cb(tls) ->
+ tls_record;
+record_cb(dtls) ->
+ dtls_record.
+
+get_socket_opts(_, _,_,[], _, Acc) ->
+ {ok, Acc};
+get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
+ [{mode, SockOpts#socket_options.mode} | Acc]);
+get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) ->
+ case SockOpts#socket_options.packet of
+ {Type, headers} ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]);
+ Type ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc])
+ end;
+get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
+ [{header, SockOpts#socket_options.header} | Acc]);
+get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts,
+ [{active, SockOpts#socket_options.active} | Acc]);
+get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) ->
+ case Connection:getopts(Transport, Socket, [Tag]) of
+ {ok, [Opt]} ->
+ get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]);
+ {error, Reason} ->
+ {error, {options, {socket_options, Tag, Reason}}}
+ end;
+get_socket_opts(_,_, _,Opts, _,_) ->
+ {error, {options, {socket_options, Opts, function_clause}}}.
+
+set_socket_opts(_,_,_, [], SockOpts, []) ->
+ {ok, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) ->
+ %% Set non emulated options
+ try ConnectionCb:setopts(Transport, Socket, Other) of
+ ok ->
+ {ok, SockOpts};
+ {error, InetError} ->
+ {{error, {options, {socket_options, Other, InetError}}}, SockOpts}
+ catch
+ _:Error ->
+ %% So that inet behavior does not crash our process
+ {{error, {options, {socket_options, Other, Error}}}, SockOpts}
+ end;
+
+set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other)
+ when Mode == list; Mode == binary ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{mode = Mode}, Other);
+set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other)
+ when Packet == raw;
+ Packet == 0;
+ Packet == 1;
+ Packet == 2;
+ Packet == 4;
+ Packet == asn1;
+ Packet == cdr;
+ Packet == sunrm;
+ Packet == fcgi;
+ Packet == tpkt;
+ Packet == line;
+ Packet == http;
+ Packet == httph;
+ Packet == http_bin;
+ Packet == httph_bin ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{packet = Packet}, Other);
+set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other)
+ when is_integer(Header) ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{header = Header}, Other);
+set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) ->
+ {{error,{options, {socket_options, Opt}}}, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other)
+ when Active == once;
+ Active == true;
+ Active == false ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other);
+set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts],
+ SockOpts=#socket_options{active = Active0}, Other)
+ when Active1 >= -32768, Active1 =< 32767 ->
+ Active = if
+ is_integer(Active0), Active0 + Active1 < -32768 ->
+ error;
+ is_integer(Active0), Active0 + Active1 =< 0 ->
+ false;
+ is_integer(Active0), Active0 + Active1 > 32767 ->
+ error;
+ Active1 =< 0 ->
+ false;
+ is_integer(Active0) ->
+ Active0 + Active1;
+ true ->
+ Active1
+ end,
+ case Active of
+ error ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+ _ ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other)
+ end;
+set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]).
+ssl_options_list(SslOptions) ->
+ L = maps:to_list(SslOptions),
+ ssl_options_list(L, []).
+
+new_emulated([], EmOpts) ->
+ EmOpts;
+new_emulated(NewEmOpts, _) ->
+ NewEmOpts.
+
+ssl_options_list([], Acc) ->
+ lists:reverse(Acc);
+%% Skip internal options, only return user options
+ssl_options_list([{protocol, _}| T], Acc) ->
+ ssl_options_list(T, Acc);
+ssl_options_list([{erl_dist, _}|T], Acc) ->
+ ssl_options_list(T, Acc);
+ssl_options_list([{renegotiate_at, _}|T], Acc) ->
+ ssl_options_list(T, Acc);
+ssl_options_list([{max_fragment_length, _}|T], Acc) ->
+ %% skip max_fragment_length from options since it is taken above from connection_states
+ ssl_options_list(T, Acc);
+ssl_options_list([{ciphers = Key, Value}|T], Acc) ->
+ ssl_options_list(T,
+ [{Key, lists:map(
+ fun(Suite) ->
+ ssl_cipher_format:suite_bin_to_map(Suite)
+ end, Value)}
+ | Acc]);
+ssl_options_list([{Key, Value}|T], Acc) ->
+ ssl_options_list(T, [{Key, Value} | Acc]).
+
+%% Maybe add NSS keylog info according to
+%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
+maybe_add_keylog(Info) ->
+ maybe_add_keylog(lists:keyfind(protocol, 1, Info), Info).
+
+maybe_add_keylog({_, 'tlsv1.2'}, Info) ->
+ try
+ {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
+ {master_secret, MasterSecretBin} = lists:keyfind(master_secret, 1, Info),
+ ClientRandom = binary:decode_unsigned(ClientRandomBin),
+ MasterSecret = binary:decode_unsigned(MasterSecretBin),
+ Keylog = [io_lib:format("CLIENT_RANDOM ~64.16.0B ~96.16.0B", [ClientRandom, MasterSecret])],
+ Info ++ [{keylog,Keylog}]
+ catch
+ _Cxx:_Exx ->
+ Info
+ end;
+maybe_add_keylog({_, 'tlsv1.3'}, Info) ->
+ try
+ {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info),
+ {client_traffic_secret_0, ClientTrafficSecret0Bin} = lists:keyfind(client_traffic_secret_0, 1, Info),
+ {server_traffic_secret_0, ServerTrafficSecret0Bin} = lists:keyfind(server_traffic_secret_0, 1, Info),
+ {client_handshake_traffic_secret, ClientHSecretBin} = lists:keyfind(client_handshake_traffic_secret, 1, Info),
+ {server_handshake_traffic_secret, ServerHSecretBin} = lists:keyfind(server_handshake_traffic_secret, 1, Info),
+ {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info),
+ ClientRandom = binary:decode_unsigned(ClientRandomBin),
+ ClientTrafficSecret0 = keylog_secret(ClientTrafficSecret0Bin, Prf),
+ ServerTrafficSecret0 = keylog_secret(ServerTrafficSecret0Bin, Prf),
+ ClientHSecret = keylog_secret(ClientHSecretBin, Prf),
+ ServerHSecret = keylog_secret(ServerHSecretBin, Prf),
+ Keylog0 = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret,
+ io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret,
+ io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0,
+ io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0],
+ Keylog = case lists:keyfind(client_early_data_secret, 1, Info) of
+ {client_early_data_secret, EarlySecret} ->
+ ClientEarlySecret = keylog_secret(EarlySecret, Prf),
+ [io_lib:format("CLIENT_EARLY_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientEarlySecret
+ | Keylog0];
+ _ ->
+ Keylog0
+ end,
+ Info ++ [{keylog,Keylog}]
+ catch
+ _Cxx:_Exx ->
+ Info
+ end;
+maybe_add_keylog(_, Info) ->
+ Info.
+
+keylog_secret(SecretBin, sha256) ->
+ io_lib:format("~64.16.0B", [binary:decode_unsigned(SecretBin)]);
+keylog_secret(SecretBin, sha384) ->
+ io_lib:format("~96.16.0B", [binary:decode_unsigned(SecretBin)]);
+keylog_secret(SecretBin, sha512) ->
+ io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]).
+
+maybe_generate_client_shares(#{versions := [Version|_],
+ supported_groups :=
+ #supported_groups{
+ supported_groups = [Group|_]}})
+ when Version =:= {3,4} ->
+ %% Generate only key_share entry for the most preferred group
+ ssl_cipher:generate_client_shares([Group]);
+maybe_generate_client_shares(_) ->
+ undefined.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 7cc4a0a7c3..de5490d232 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -43,15 +43,15 @@
-type ssl_handshake() :: #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} |
#client_key_exchange{} | #finished{} | #certificate_verify{} |
- #hello_request{} | #next_protocol{}.
+ #hello_request{} | #next_protocol{} | #end_of_early_data{}.
%% Create handshake messages
-export([hello_request/0, server_hello/4, server_hello_done/0,
certificate/4, client_certificate_verify/6, certificate_request/5, key_exchange/3,
- finished/5, next_protocol/1]).
+ finished/5, next_protocol/1, digitally_signed/5]).
%% Handle handshake messages
--export([certify/7, certificate_verify/6, verify_signature/5,
+-export([certify/9, certificate_verify/6, verify_signature/5,
master_secret/4, server_key_exchange_hash/2, verify_connection/6,
init_handshake_history/0, update_handshake_history/2, verify_server_key/5,
select_version/3, select_supported_version/2, extension_value/1
@@ -68,22 +68,23 @@
%% Cipher suites handling
-export([available_suites/2, available_signature_algs/2, available_signature_algs/4,
- cipher_suites/3, prf/6, select_session/11, supported_ecc/1,
+ cipher_suites/3, prf/6, select_session/9, supported_ecc/1,
premaster_secret/2, premaster_secret/3, premaster_secret/4]).
%% Extensions handling
--export([client_hello_extensions/7,
- handle_client_hello_extensions/9, %% Returns server hello extensions
- handle_server_hello_extensions/9, select_curve/2, select_curve/3,
+-export([client_hello_extensions/8,
+ handle_client_hello_extensions/10, %% Returns server hello extensions
+ handle_server_hello_extensions/10, select_curve/2, select_curve/3,
select_hashsign/4, select_hashsign/5,
select_hashsign_algs/3, empty_extensions/2, add_server_share/3,
- add_alpn/2, add_selected_version/1, decode_alpn/1
+ add_alpn/2, add_selected_version/1, decode_alpn/1, max_frag_enum/1
]).
-export([get_cert_params/1,
+ select_own_cert/1,
server_name/3,
- validation_fun_and_state/10,
- handle_path_validation_error/7]).
+ validation_fun_and_state/4,
+ path_validation_alert/1]).
%%====================================================================
%% Create handshake messages
@@ -125,30 +126,33 @@ server_hello_done() ->
#server_hello_done{}.
%%--------------------------------------------------------------------
--spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}.
+-spec certificate([der_cert()] | undefined, db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}.
%%
%% Description: Creates a certificate message.
%%--------------------------------------------------------------------
-certificate(OwnCert, CertDbHandle, CertDbRef, client) ->
+certificate(undefined, _, _, client) ->
+ %% If no suitable certificate is available, the client
+ %% SHOULD send a certificate message containing no
+ %% certificates. (chapter 7.4.6. RFC 4346)
+ #certificate{asn1_certificates = []};
+certificate([OwnCert], CertDbHandle, CertDbRef, client) ->
Chain =
case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
{ok, _, CertChain} ->
CertChain;
{error, _} ->
- %% If no suitable certificate is available, the client
- %% SHOULD send a certificate message containing no
- %% certificates. (chapter 7.4.6. RFC 4346)
- []
- end,
+ certificate(undefined, CertDbHandle, CertDbRef, client)
+ end,
#certificate{asn1_certificates = Chain};
-
-certificate(OwnCert, CertDbHandle, CertDbRef, server) ->
+certificate([OwnCert], CertDbHandle, CertDbRef, server) ->
case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
{ok, _, Chain} ->
#certificate{asn1_certificates = Chain};
{error, Error} ->
?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
- end.
+ end;
+certificate([_, _ |_] = Chain, _, _, _) ->
+ #certificate{asn1_certificates = Chain}.
%%--------------------------------------------------------------------
-spec client_certificate_verify(undefined | der_cert(), binary(),
@@ -162,7 +166,7 @@ client_certificate_verify(undefined, _, _, _, _, _) ->
ignore;
client_certificate_verify(_, _, _, _, undefined, _) ->
ignore;
-client_certificate_verify(OwnCert, MasterSecret, Version,
+client_certificate_verify([OwnCert|_], MasterSecret, Version,
{HashAlgo, SignAlgo},
PrivateKey, {Handshake, _}) ->
case public_key:pkix_is_fixed_dh_cert(OwnCert) of
@@ -171,7 +175,7 @@ client_certificate_verify(OwnCert, MasterSecret, Version,
false ->
Hashes =
calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake),
- Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey),
+ Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo),
#certificate_verify{signature = Signed, hashsign_algorithm = {HashAlgo, SignAlgo}}
end.
@@ -337,37 +341,28 @@ next_protocol(SelectedProtocol) ->
%%====================================================================
%%--------------------------------------------------------------------
-spec certify(#certificate{}, db_handle(), certdb_ref(), ssl_options(), term(),
- client | server, inet:hostname() | inet:ip_address()) -> {der_cert(), public_key_info()} | #alert{}.
+ client | server, inet:hostname() | inet:ip_address(),
+ ssl_record:ssl_version(), map()) -> {der_cert(), public_key_info()} | #alert{}.
%%
%% Description: Handles a certificate handshake message
%%--------------------------------------------------------------------
certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
#{server_name_indication := ServerNameIndication,
- partial_chain := PartialChain,
- verify_fun := VerifyFun,
- customize_hostname_check := CustomizeHostnameCheck,
- crl_check := CrlCheck,
- log_level := Level,
- depth := Depth} = Opts, CRLDbHandle, Role, Host) ->
-
+ partial_chain := PartialChain} = SSlOptions,
+ CRLDbHandle, Role, Host, Version, CertExt) ->
ServerName = server_name(ServerNameIndication, Host, Role),
- [PeerCert | ChainCerts ] = ASN1Certs,
+ [PeerCert | _ChainCerts ] = ASN1Certs,
try
- {TrustedCert, CertPath} =
- ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef,
- PartialChain),
- ValidationFunAndState = validation_fun_and_state(VerifyFun, Role,
- CertDbHandle, CertDbRef, ServerName,
- CustomizeHostnameCheck,
- CrlCheck, CRLDbHandle, CertPath, Level),
- Options = [{max_path_length, Depth},
- {verify_fun, ValidationFunAndState}],
- case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
- {ok, {PublicKeyInfo,_}} ->
- {PeerCert, PublicKeyInfo};
+ PathsAndAnchors =
+ ssl_certificate:trusted_cert_and_paths(ASN1Certs, CertDbHandle, CertDbRef,
+ PartialChain),
+
+ case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SSlOptions, CertExt) of
+ {ok, {PublicKeyInfo, _}} ->
+ {PeerCert, PublicKeyInfo};
{error, Reason} ->
- handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options,
- CertDbHandle, CertDbRef)
+ path_validation_alert(Reason)
end
catch
error:{_,{error, {asn1, Asn1Reason}}} ->
@@ -400,27 +395,34 @@ certificate_verify(Signature, PublicKeyInfo, Version,
%%
%% Description: Checks that a public_key signature is valid.
%%--------------------------------------------------------------------
-verify_signature(_Version, _Hash, {_HashAlgo, anon}, _Signature, _) ->
- true;
-verify_signature({3, Minor}, Hash, {HashAlgo, rsa_pss_rsae}, Signature, {?rsaEncryption, PubKey, _PubKeyParams})
+verify_signature({3, 4}, Hash, {HashAlgo, SignAlgo}, Signature,
+ {_, PubKey, PubKeyParams}) when SignAlgo == rsa_pss_rsae;
+ SignAlgo == rsa_pss_pss ->
+ Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
+ public_key:verify(Hash, HashAlgo, Signature, PubKey, Options);
+verify_signature({3, 3}, Hash, {HashAlgo, SignAlgo}, Signature,
+ {_, PubKey, PubKeyParams}) when SignAlgo == rsa_pss_rsae;
+ SignAlgo == rsa_pss_pss ->
+ Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
+ public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, Options);
+verify_signature({3, Minor}, Hash, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams})
when Minor >= 3 ->
- public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey,
- [{rsa_padding, rsa_pkcs1_pss_padding},
- {rsa_pss_saltlen, -1},
- {rsa_mgf1_md, HashAlgo}]);
-verify_signature({3, Minor}, Hash, {HashAlgo, rsa}, Signature, {?rsaEncryption, PubKey, _PubKeyParams})
- when Minor >= 3 ->
- public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey);
-verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) ->
+ Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
+ public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, Options);
+verify_signature({3, Minor}, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) when Minor =< 2 ->
case public_key:decrypt_public(Signature, PubKey,
[{rsa_pad, rsa_pkcs1_padding}]) of
Hash -> true;
- _ -> false
+ _ -> false
end;
-verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) ->
- public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams});
+verify_signature({3, 4}, Hash, {HashAlgo, _SignAlgo}, Signature, {?'id-ecPublicKey', PubKey, PubKeyParams}) ->
+ public_key:verify(Hash, HashAlgo, Signature, {PubKey, PubKeyParams});
verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature,
{?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
+ public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams});
+verify_signature({3, Minor}, _Hash, {_HashAlgo, anon}, _Signature, _) when Minor =< 3 ->
+ true;
+verify_signature({3, Minor}, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) when Minor =< 3->
public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}).
%%--------------------------------------------------------------------
@@ -683,6 +685,11 @@ encode_extensions([#signature_algorithms_cert{
Len = ListLen + 2,
encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT),
?UINT16(Len), ?UINT16(ListLen), SignSchemeList/binary, Acc/binary>>);
+encode_extensions([#sni{hostname = ""} | Rest], Acc) ->
+ HostnameBin = <<>>,
+ encode_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(0),
+ HostnameBin/binary,
+ Acc/binary>>);
encode_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
HostLen = length(Hostname),
HostnameBin = list_to_binary(Hostname),
@@ -695,6 +702,10 @@ encode_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
?BYTE(?SNI_NAMETYPE_HOST_NAME),
?UINT16(HostLen), HostnameBin/binary,
Acc/binary>>);
+encode_extensions([#max_frag_enum{enum = MaxFragEnum} | Rest], Acc) ->
+ ExtLength = 1,
+ encode_extensions(Rest, <<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(ExtLength), ?BYTE(MaxFragEnum),
+ Acc/binary>>);
encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) ->
Versions = encode_versions(Versions0),
VerLen = byte_size(Versions),
@@ -728,10 +739,19 @@ encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) ->
ExtLen = KEModesLen + 1,
encode_extensions(Rest, <<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT),
?UINT16(ExtLen), ?BYTE(KEModesLen), KEModes/binary, Acc/binary>>);
+encode_extensions([
+ #certificate_status_request{
+ status_type = StatusRequest,
+ request = Request} | Rest], Acc) ->
+ CertStatusReq = encode_cert_status_req(StatusRequest, Request),
+ Len = byte_size(CertStatusReq),
+ encode_extensions(
+ Rest, <<?UINT16(?STATUS_REQUEST), ?UINT16(Len),
+ CertStatusReq/binary, Acc/binary>>);
encode_extensions([#pre_shared_key_client_hello{
offered_psks = #offered_psks{
identities = Identities0,
- binders = Binders0} = _PSKs} | Rest], Acc) ->
+ binders = Binders0}} | Rest], Acc) ->
Identities = encode_psk_identities(Identities0),
Binders = encode_psk_binders(Binders0),
Len = byte_size(Identities) + byte_size(Binders),
@@ -743,8 +763,51 @@ encode_extensions([#pre_shared_key_client_hello{
?UINT16(Len), Identities/binary, Binders/binary>>);
encode_extensions([#pre_shared_key_server_hello{selected_identity = Identity} | Rest], Acc) ->
encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT),
- ?UINT16(2), ?UINT16(Identity), Acc/binary>>).
+ ?UINT16(2), ?UINT16(Identity), Acc/binary>>);
+encode_extensions([#cookie{cookie = Cookie} | Rest], Acc) ->
+ CookieLen = byte_size(Cookie),
+ Len = CookieLen + 2,
+ encode_extensions(Rest, <<?UINT16(?COOKIE_EXT), ?UINT16(Len), ?UINT16(CookieLen),
+ Cookie/binary, Acc/binary>>);
+encode_extensions([#early_data_indication{} | Rest], Acc) ->
+ encode_extensions(Rest, <<?UINT16(?EARLY_DATA_EXT),
+ ?UINT16(0), Acc/binary>>);
+encode_extensions([#early_data_indication_nst{indication = MaxSize} | Rest], Acc) ->
+ encode_extensions(Rest, <<?UINT16(?EARLY_DATA_EXT),
+ ?UINT16(4), ?UINT32(MaxSize), Acc/binary>>).
+
+encode_cert_status_req(
+ StatusType,
+ #ocsp_status_request{
+ responder_id_list = ResponderIDList,
+ request_extensions = ReqExtns}) ->
+ ResponderIDListBin = encode_responderID_list(ResponderIDList),
+ ReqExtnsBin = encode_request_extensions(ReqExtns),
+ <<?BYTE(StatusType), ResponderIDListBin/binary, ReqExtnsBin/binary>>.
+
+encode_responderID_list([]) ->
+ <<?UINT16(0)>>;
+encode_responderID_list(List) ->
+ do_encode_responderID_list(List, <<>>).
+%% ResponderID is DER-encoded ASN.1 type defined in RFC6960.
+do_encode_responderID_list([], Acc) ->
+ Len = byte_size(Acc),
+ <<?UINT16(Len), Acc/binary>>;
+do_encode_responderID_list([Responder | Rest], Acc)
+ when is_binary(Responder) ->
+ Len = byte_size(Responder),
+ do_encode_responderID_list(
+ Rest, <<Acc/binary, ?UINT16(Len), Responder/binary>>).
+
+%% Extensions are DER-encoded ASN.1 type defined in RFC6960 following
+%% extension model employed in X.509 version 3 certificates(RFC5280).
+encode_request_extensions([]) ->
+ <<?UINT16(0)>>;
+encode_request_extensions(Extns) when is_list(Extns) ->
+ ExtnBin = public_key:der_encode('Extensions', Extns),
+ Len = byte_size(ExtnBin),
+ <<?UINT16(Len), ExtnBin/binary>>.
encode_client_protocol_negotiation(undefined, _) ->
undefined;
@@ -757,7 +820,8 @@ encode_protocols_advertised_on_server(undefined) ->
undefined;
encode_protocols_advertised_on_server(Protocols) ->
- #next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
+ #next_protocol_negotiation{
+ extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
%%====================================================================
%% Decode handshake
@@ -796,6 +860,14 @@ decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32
extensions = HelloExtensions};
decode_handshake(_Version, ?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>) ->
#certificate{asn1_certificates = certs_to_list(ASN1Certs)};
+%% RFC 6066, servers return a certificate response along with their certificate
+%% by sending a "CertificateStatus" message immediately after the "Certificate"
+%% message and before any "ServerKeyExchange" or "CertificateRequest" messages.
+decode_handshake(_Version, ?CERTIFICATE_STATUS, <<?BYTE(?CERTIFICATE_STATUS_TYPE_OCSP),
+ ?UINT24(Len), ASN1OcspResponse:Len/binary>>) ->
+ #certificate_status{
+ status_type = ?CERTIFICATE_STATUS_TYPE_OCSP,
+ response = ASN1OcspResponse};
decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) ->
#server_key_exchange{exchange_keys = Keys};
decode_handshake({Major, Minor}, ?CERTIFICATE_REQUEST,
@@ -954,18 +1026,14 @@ cipher_suites(Suites, true) ->
%%
%% Description: use the TLS PRF to generate key material
%%--------------------------------------------------------------------
-prf({3,0}, _, _, _, _, _) ->
- {error, undefined};
prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) ->
{ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}.
-select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve0} =
+select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, #session{ecc = ECCCurve0} =
Session, Version,
- #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = SslOpts,
- Cache, CacheCb, Cert) ->
- {SessionId, Resumed} = ssl_session:server_select_session(Version, Port, SuggestedSessionId,
- SslOpts, Cert,
- Cache, CacheCb),
+ #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = SslOpts,Cert) ->
+ {SessionId, Resumed} = ssl_session:server_select_session(Version, SessIdTracker, SuggestedSessionId,
+ SslOpts, Cert),
case Resumed of
undefined ->
Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0),
@@ -1081,16 +1149,18 @@ premaster_secret(EncSecret, #{algorithm := rsa} = Engine) ->
%% Extensions handling
%%====================================================================
client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare,
- TicketData) ->
+ TicketData, OcspNonce) ->
HelloExtensions0 = add_tls12_extensions(Version, SslOpts, ConnectionStates, Renegotiation),
HelloExtensions1 = add_common_extensions(Version, HelloExtensions0, CipherSuites, SslOpts),
- maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare, TicketData).
+ HelloExtensions2 = maybe_add_certificate_status_request(Version, SslOpts, OcspNonce, HelloExtensions1),
+ maybe_add_tls13_extensions(Version, HelloExtensions2, SslOpts, KeyShare, TicketData).
add_tls12_extensions(_Version,
#{alpn_advertised_protocols := AlpnAdvertisedProtocols,
next_protocol_selector := NextProtocolSelector,
- server_name_indication := ServerNameIndication} = SslOpts,
+ server_name_indication := ServerNameIndication,
+ max_fragment_length := MaxFragmentLength} = SslOpts,
ConnectionStates,
Renegotiation) ->
SRP = srp_user(SslOpts),
@@ -1101,7 +1171,8 @@ add_tls12_extensions(_Version,
next_protocol_negotiation =>
encode_client_protocol_negotiation(NextProtocolSelector,
Renegotiation),
- sni => sni(ServerNameIndication)
+ sni => sni(ServerNameIndication),
+ max_frag_enum => max_frag_enum(MaxFragmentLength)
}.
@@ -1153,6 +1224,31 @@ maybe_add_tls13_extensions({3,4},
maybe_add_tls13_extensions(_, HelloExtensions, _, _, _) ->
HelloExtensions.
+maybe_add_certificate_status_request(
+ _Version, #{ocsp_stapling := false}, _OcspNonce, HelloExtensions) ->
+ HelloExtensions;
+maybe_add_certificate_status_request(
+ _Version, #{ocsp_stapling := true,
+ ocsp_responder_certs := OcspResponderCerts},
+ OcspNonce, HelloExtensions) ->
+ OcspResponderList = get_ocsp_responder_list(OcspResponderCerts),
+ OcspRequestExtns = public_key:ocsp_extensions(OcspNonce),
+ Req = #ocsp_status_request{responder_id_list = OcspResponderList,
+ request_extensions = OcspRequestExtns},
+ CertStatusReqExtn = #certificate_status_request{
+ status_type = ?CERTIFICATE_STATUS_TYPE_OCSP,
+ request = Req
+ },
+ HelloExtensions#{status_request => CertStatusReqExtn}.
+
+get_ocsp_responder_list(ResponderCerts) ->
+ get_ocsp_responder_list(ResponderCerts, []).
+
+get_ocsp_responder_list([], Acc) ->
+ Acc;
+get_ocsp_responder_list([ResponderCert | T], Acc) ->
+ get_ocsp_responder_list(
+ T, [public_key:ocsp_responder_id(ResponderCert) | Acc]).
%% TODO: Add support for PSK key establishment
@@ -1219,7 +1315,9 @@ get_identities_binders(TicketData) ->
%%
get_identities_binders([], {Identities, Binders}, _) ->
{lists:reverse(Identities), lists:reverse(Binders)};
-get_identities_binders([{Key, _, Identity, _, _, HKDF}|T], {I0, B0}, N) ->
+get_identities_binders([#ticket_data{key = Key,
+ identity = Identity,
+ cipher_suite = {_, HKDF}}|T], {I0, B0}, N) ->
%% Use dummy binder for proper calculation of packet size when creating
%% the real binder value.
Binder = dummy_binder(HKDF),
@@ -1286,18 +1384,27 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
alpn_preferred_protocols := ALPNPreferredProtocols} = Opts,
#session{cipher_suite = NegotiatedCipherSuite,
compression_method = Compression} = Session0,
- ConnectionStates0, Renegotiation) ->
+ ConnectionStates0, Renegotiation, IsResumed) ->
Session = handle_srp_extension(maps:get(srp, Exts, undefined), Session0),
+ MaxFragEnum = handle_mfl_extension(maps:get(max_frag_enum, Exts, undefined)),
+ ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined),
Random, NegotiatedCipherSuite,
ClientCipherSuites, Compression,
- ConnectionStates0, Renegotiation, SecureRenegotation),
+ ConnectionStates1, Renegotiation, SecureRenegotation),
Empty = empty_extensions(Version, server_hello),
+ %% RFC 6066 - server doesn't include max_fragment_length for resumed sessions
+ ServerMaxFragEnum = if IsResumed ->
+ undefined;
+ true ->
+ MaxFragEnum
+ end,
ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server,
ConnectionStates, Renegotiation),
ec_point_formats => server_ecc_extension(Version,
- maps:get(ec_point_formats, Exts, undefined))
+ maps:get(ec_point_formats, Exts, undefined)),
+ max_frag_enum => ServerMaxFragEnum
},
%% If we receive an ALPN extension and have ALPN configured for this connection,
@@ -1319,34 +1426,55 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
Exts, Version,
#{secure_renegotiate := SecureRenegotation,
- next_protocol_selector := NextProtoSelector},
- ConnectionStates0, Renegotiation) ->
+ next_protocol_selector := NextProtoSelector,
+ ocsp_stapling := Stapling},
+ ConnectionStates0, Renegotiation, IsNew) ->
ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version,
maps:get(renegotiation_info, Exts, undefined), Random,
CipherSuite, undefined,
Compression, ConnectionStates0,
Renegotiation, SecureRenegotation),
- %% If we receive an ALPN extension then this is the protocol selected,
- %% otherwise handle the NPN extension.
- ALPN = maps:get(alpn, Exts, undefined),
- case decode_alpn(ALPN) of
- %% ServerHello contains exactly one protocol: the one selected.
- %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2).
- [Protocol] when not Renegotiation ->
- {ConnectionStates, alpn, Protocol};
- [_] when Renegotiation ->
- {ConnectionStates, alpn, undefined};
- undefined ->
- NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
- Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation),
- {ConnectionStates, npn, Protocol};
- {error, Reason} ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
- [] ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_protocols_in_server_hello);
- [_|_] ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello)
+ %% RFC 6066: handle received/expected maximum fragment length
+ if IsNew ->
+ ServerMaxFragEnum = maps:get(max_frag_enum, Exts, undefined),
+ #{current_write := #{max_fragment_length := ConnMaxFragLen}} = ConnectionStates,
+ ClientMaxFragEnum = max_frag_enum(ConnMaxFragLen),
+
+ if ServerMaxFragEnum == ClientMaxFragEnum ->
+ ok;
+ true ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end;
+ true ->
+ ok
+ end,
+
+ case handle_ocsp_extension(Stapling, Exts) of
+ #alert{} = Alert ->
+ Alert;
+ OcspState ->
+ %% If we receive an ALPN extension then this is the protocol selected,
+ %% otherwise handle the NPN extension.
+ ALPN = maps:get(alpn, Exts, undefined),
+ case decode_alpn(ALPN) of
+ %% ServerHello contains exactly one protocol: the one selected.
+ %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2).
+ [Protocol] when not Renegotiation ->
+ {ConnectionStates, alpn, Protocol, OcspState};
+ [_] when Renegotiation ->
+ {ConnectionStates, alpn, undefined, OcspState};
+ undefined ->
+ NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
+ Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation),
+ {ConnectionStates, npn, Protocol, OcspState};
+ {error, Reason} ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
+ [] ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_protocols_in_server_hello);
+ [_|_] ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello)
+ end
end.
select_curve(Client, Server) ->
@@ -1393,7 +1521,7 @@ select_hashsign({#hash_sign_algos{hash_sign_algos = ClientHashSigns},
Cert, KeyExAlgo, SupportedHashSigns, {Major, Minor})
when Major >= 3 andalso Minor >= 3 ->
ClientSignatureSchemes = get_signature_scheme(ClientSignatureSchemes0),
- {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert),
+ {SignAlgo0, Param, PublicKeyAlgo0, _} = get_cert_params(Cert),
SignAlgo = sign_algo(SignAlgo0),
PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
@@ -1447,7 +1575,7 @@ select_hashsign(#certificate_request{
Cert,
SupportedHashSigns,
{Major, Minor}) when Major >= 3 andalso Minor >= 3->
- {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert),
+ {SignAlgo0, Param, PublicKeyAlgo0, _} = get_cert_params(Cert),
SignAlgo = sign_algo(SignAlgo0),
PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
@@ -1470,7 +1598,7 @@ select_hashsign(#certificate_request{
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
end;
select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Version) ->
- {_, _, PublicKeyAlgo0} = get_cert_params(Cert),
+ {_, _, PublicKeyAlgo0, _} = get_cert_params(Cert),
PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
%% Check cert even for TLS 1.0/1.1
@@ -1486,14 +1614,28 @@ select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Versio
%% - signature algorithm
%% - parameters of the signature algorithm
%% - public key algorithm (key type)
+%% - RSA key size in bytes
get_cert_params(Cert) ->
#'OTPCertificate'{tbsCertificate = TBSCert,
signatureAlgorithm =
{_,SignAlgo, Param}} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _}} =
+ #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _},
+ subjectPublicKey = PublicKey} =
TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
- {SignAlgo, Param, PublicKeyAlgo}.
+ RSAKeySize =
+ case PublicKey of
+ #'RSAPublicKey'{modulus = Modulus} ->
+ %% Get RSA key size in bytes
+ byte_size(binary:encode_unsigned(Modulus));
+ _ ->
+ undefined
+ end,
+ {SignAlgo, Param, PublicKeyAlgo, RSAKeySize}.
+select_own_cert([OwnCert| _]) ->
+ OwnCert;
+select_own_cert(undefined) ->
+ undefined.
get_signature_scheme(undefined) ->
undefined;
@@ -1555,6 +1697,8 @@ extension_value(#hash_sign_algos{hash_sign_algos = Algos}) ->
Algos;
extension_value(#alpn{extension_data = Data}) ->
Data;
+extension_value(#max_frag_enum{enum = Enum}) ->
+ Enum;
extension_value(#next_protocol_negotiation{extension_data = Data}) ->
Data;
extension_value(#srp{username = Name}) ->
@@ -1578,9 +1722,29 @@ extension_value(#pre_shared_key_client_hello{offered_psks = PSKs}) ->
extension_value(#pre_shared_key_server_hello{selected_identity = SelectedIdentity}) ->
SelectedIdentity;
extension_value(#psk_key_exchange_modes{ke_modes = Modes}) ->
- Modes.
-
-
+ Modes;
+extension_value(#cookie{cookie = Cookie}) ->
+ Cookie.
+
+handle_ocsp_extension(true = Stapling, Extensions) ->
+ case maps:get(status_request, Extensions, false) of
+ undefined -> %% status_request in server hello is empty
+ #{ocsp_stapling => Stapling,
+ ocsp_expect => staple};
+ false -> %% status_request is missing (not negotiated)
+ #{ocsp_stapling => Stapling,
+ ocsp_expect => no_staple};
+ _Else ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, status_request_not_empty)
+ end;
+handle_ocsp_extension(false = Stapling, Extensions) ->
+ case maps:get(status_request, Extensions, false) of
+ false -> %% status_request is missing (not negotiated)
+ #{ocsp_stapling => Stapling,
+ ocsp_expect => no_staple};
+ _Else -> %% unsolicited status_request
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_status_request)
+ end.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -1638,9 +1802,7 @@ certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) ->
[], CertDbData).
%%-------------Handle handshake messages --------------------------------
-validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef,
- ServerNameIndication, CustomizeHostCheck, CRLCheck,
- CRLDbHandle, CertPath, LogLevel) ->
+validation_fun_and_state({Fun, UserState0}, VerifyState, CertPath, LogLevel) ->
{fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) ->
case ssl_certificate:validate(OtpCert,
Extension,
@@ -1649,30 +1811,25 @@ validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef,
{valid, {NewSslState, UserState}};
{fail, Reason} ->
apply_user_fun(Fun, OtpCert, Reason, UserState,
- SslState, CertPath, LogLevel);
+ SslState, CertPath, LogLevel);
{unknown, _} ->
apply_user_fun(Fun, OtpCert,
- Extension, UserState, SslState, CertPath, LogLevel)
+ Extension, UserState, SslState, CertPath, LogLevel)
end;
(OtpCert, VerifyResult, {SslState, UserState}) ->
apply_user_fun(Fun, OtpCert, VerifyResult, UserState,
SslState, CertPath, LogLevel)
- end, {{Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}, UserState0}};
-validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
- ServerNameIndication, CustomizeHostCheck, CRLCheck,
- CRLDbHandle, CertPath, LogLevel) ->
+ end, {VerifyState, UserState0}};
+validation_fun_and_state(undefined, VerifyState, CertPath, LogLevel) ->
{fun(OtpCert, {extension, _} = Extension, SslState) ->
ssl_certificate:validate(OtpCert,
Extension,
SslState);
(OtpCert, VerifyResult, SslState) when (VerifyResult == valid) or
(VerifyResult == valid_peer) ->
- case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef,
- CRLDbHandle, VerifyResult, CertPath, LogLevel) of
- valid ->
- ssl_certificate:validate(OtpCert,
- VerifyResult,
- SslState);
+ case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of
+ valid ->
+ ssl_certificate:validate(OtpCert, VerifyResult, SslState);
Reason ->
{fail, Reason}
end;
@@ -1680,15 +1837,14 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
ssl_certificate:validate(OtpCert,
VerifyResult,
SslState)
- end, {Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}}.
+ end, VerifyState}.
-apply_user_fun(Fun, OtpCert, VerifyResult, UserState0,
- {_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath, LogLevel) when
- (VerifyResult == valid) or (VerifyResult == valid_peer) ->
+apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0, SslState, CertPath, LogLevel) when
+ (VerifyResult0 == valid) or (VerifyResult0 == valid_peer) ->
+ VerifyResult = maybe_check_hostname(OtpCert, VerifyResult0, SslState),
case Fun(OtpCert, VerifyResult, UserState0) of
{Valid, UserState} when (Valid == valid) or (Valid == valid_peer) ->
- case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef,
- CRLDbHandle, VerifyResult, CertPath, LogLevel) of
+ case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of
valid ->
{Valid, {SslState, UserState}};
Result ->
@@ -1707,58 +1863,16 @@ apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath,
{unknown, {SslState, UserState}}
end.
-handle_path_validation_error({bad_cert, unknown_ca} = Reason, PeerCert, Chain,
- Opts, Options, CertDbHandle, CertsDbRef) ->
- handle_incomplete_chain(PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef, Reason);
-handle_path_validation_error({bad_cert, invalid_issuer} = Reason, PeerCert, Chain0,
- Opts, Options, CertDbHandle, CertsDbRef) ->
- handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason);
-handle_path_validation_error(Reason, _, _, _, _,_, _) ->
- path_validation_alert(Reason).
-
-handle_incomplete_chain(PeerCert, Chain0,
- #{partial_chain := PartialChain} = Opts, Options, CertDbHandle, CertsDbRef, Reason) ->
- case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of
- {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found
- case ssl_certificate:trusted_cert_and_path(Chain,
- CertDbHandle, CertsDbRef,
- PartialChain) of
- {unknown_ca, []} ->
- path_validation_alert(Reason);
- {Trusted, Path} ->
- case public_key:pkix_path_validation(Trusted, Path, Options) of
- {ok, {PublicKeyInfo,_}} ->
- {PeerCert, PublicKeyInfo};
- {error, PathError} ->
- handle_unordered_chain(PeerCert, Chain0, Opts, Options,
- CertDbHandle, CertsDbRef, PathError)
- end
- end;
- _ ->
- handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason)
- end.
+maybe_check_hostname(OtpCert, valid_peer, SslState) ->
+ case ssl_certificate:validate(OtpCert, valid_peer, SslState) of
+ {valid, _} ->
+ valid_peer;
+ {fail, Reason} ->
+ Reason
+ end;
+maybe_check_hostname(_, valid, _) ->
+ valid.
-handle_unordered_chain(PeerCert, Chain0,
- #{partial_chain := PartialChain}, Options, CertDbHandle, CertsDbRef, Reason) ->
- {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, Chain0}),
- case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, ExtractedCerts, Chain0) of
- {ok, _, Chain} when Chain =/= Chain0 -> %% Chain appaears to be unordered
- case ssl_certificate:trusted_cert_and_path(Chain,
- CertDbHandle, CertsDbRef,
- PartialChain) of
- {unknown_ca, []} ->
- path_validation_alert(Reason);
- {Trusted, Path} ->
- case public_key:pkix_path_validation(Trusted, Path, Options) of
- {ok, {PublicKeyInfo,_}} ->
- {PeerCert, PublicKeyInfo};
- {error, PathError} ->
- path_validation_alert(PathError)
- end
- end;
- _ ->
- path_validation_alert(Reason)
- end.
path_validation_alert({bad_cert, cert_expired}) ->
?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED);
@@ -1782,46 +1896,97 @@ path_validation_alert({bad_cert, unknown_ca}) ->
path_validation_alert(Reason) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason).
-digitally_signed(Version, Hashes, HashAlgo, PrivateKey) ->
- try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey) of
+digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo) ->
+ try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo) of
Signature ->
Signature
catch
error:badkey->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey)))
end.
-do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine)
- when Minor >= 3 ->
- crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine));
-do_digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 ->
- public_key:sign({digest, Hash}, HashAlgo, Key);
-do_digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) ->
- public_key:encrypt_private(Hash, Key,
- [{rsa_pad, rsa_pkcs1_padding}]);
-do_digitally_signed({3, _}, Hash, _,
- #{algorithm := rsa} = Engine) ->
+
+do_digitally_signed({3, Minor}, Hash, _,
+ #{algorithm := rsa} = Engine, rsa) when Minor =< 2->
crypto:private_encrypt(rsa, Hash, maps:remove(algorithm, Engine),
rsa_pkcs1_padding);
-do_digitally_signed({3, _}, Hash, HashAlgo, #{algorithm := Alg} = Engine) ->
- crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine));
-do_digitally_signed(_Version, Hash, HashAlgo, Key) ->
+do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo)
+ when Minor > 3 ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ crypto:sign(Alg, HashAlgo, Hash, maps:remove(algorithm, Engine), Options);
+do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo)
+ when Minor > 3 ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ crypto:sign(Alg, HashAlgo, Hash, maps:remove(algorithm, Engine), Options);
+do_digitally_signed({3, 3}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine), Options);
+do_digitally_signed({3, 4}, Hash, HashAlgo, {#'RSAPrivateKey'{} = Key,
+ #'RSASSA-PSS-params'{}}, SignAlgo) ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ public_key:sign(Hash, HashAlgo, Key, Options);
+do_digitally_signed({3, 4}, Hash, HashAlgo, Key, SignAlgo) ->
+ Options = signature_options(SignAlgo, HashAlgo),
+ public_key:sign(Hash, HashAlgo, Key, Options);
+do_digitally_signed({3, Minor}, Hash, HashAlgo, Key, SignAlgo) when Minor >= 3 ->
+ Options = signature_options(HashAlgo, SignAlgo),
+ public_key:sign({digest,Hash}, HashAlgo, Key, Options);
+do_digitally_signed({3, Minor}, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key, rsa) when Minor =< 2 ->
+ public_key:encrypt_private(Hash, Key,
+ [{rsa_pad, rsa_pkcs1_padding}]);
+do_digitally_signed(_Version, Hash, HashAlgo, Key, _SignAlgo) ->
public_key:sign({digest, Hash}, HashAlgo, Key).
+
+signature_options(SignAlgo, HashAlgo) when SignAlgo =:= rsa_pss_rsae orelse
+ SignAlgo =:= rsa_pss_pss ->
+ pss_options(HashAlgo);
+signature_options(_, _) ->
+ [].
+
+verify_options(SignAlgo, HashAlgo, _KeyParams)
+ when SignAlgo =:= rsa_pss_rsae orelse
+ SignAlgo =:= rsa_pss_pss ->
+ pss_options(HashAlgo);
+verify_options(_, _, _) ->
+ [].
+
+pss_options(HashAlgo) ->
+ %% of the digest algorithm: rsa_pss_saltlen = -1
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, -1},
+ {rsa_mgf1_md, HashAlgo}].
bad_key(#'DSAPrivateKey'{}) ->
unacceptable_dsa_key;
bad_key(#'RSAPrivateKey'{}) ->
unacceptable_rsa_key;
bad_key(#'ECPrivateKey'{}) ->
+ unacceptable_ecdsa_key;
+bad_key(#{algorithm := rsa}) ->
+ unacceptable_rsa_key;
+bad_key(#{algorithm := ecdsa}) ->
unacceptable_ecdsa_key.
-crl_check(_, false, _,_,_, _, _, _) ->
+cert_status_check(_, #{ocsp_state := #{ocsp_stapling := true,
+ ocsp_expect := stapled}}, _VerifyResult, _, _) ->
+ valid; %% OCSP staple will now be checked by ssl_certifcate:verify_cert_extensions/2 in ssl_certifcate:validate
+cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := false}} = SslState, VerifyResult, CertPath, LogLevel) ->
+ maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel);
+cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := best_effort, %%TODO should we support
+ ocsp_expect := undetermined}} = SslState,
+ VerifyResult, CertPath, LogLevel) ->
+ maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel).
+
+maybe_check_crl(_, #{crl_check := false}, _, _, _) ->
valid;
-crl_check(_, peer, _, _,_, valid, _, _) -> %% Do not check CAs with this option.
+maybe_check_crl(_, #{crl_check := peer}, valid, _, _) -> %% Do not check CAs with this option.
valid;
-crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, CertPath, LogLevel) ->
+maybe_check_crl(OtpCert, #{crl_check := Check,
+ certdb := CertDbHandle,
+ certdb_ref := CertDbRef,
+ crl_db := {Callback, CRLDbHandle}}, _, CertPath, LogLevel) ->
Options = [{issuer_fun, {fun(_DP, CRL, Issuer, DBInfo) ->
- ssl_crl:trusted_cert_and_path(CRL, Issuer, {CertPath,
- DBInfo})
+ ssl_crl:trusted_cert_and_path(CRL, Issuer, CertPath,
+ DBInfo)
end, {CertDbHandle, CertDbRef}}},
{update_crl, fun(DP, CRL) ->
case Callback:fresh_crl(DP, CRL) of
@@ -1925,13 +2090,8 @@ encrypted_premaster_secret(Secret, RSAPublicKey) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed))
end.
-calc_certificate_verify({3, 0}, HashAlgo, MasterSecret, Handshake) ->
- ssl_v3:certificate_verify(HashAlgo, MasterSecret, lists:reverse(Handshake));
calc_certificate_verify({3, N}, HashAlgo, _MasterSecret, Handshake) ->
tls_v1:certificate_verify(HashAlgo, N, lists:reverse(Handshake)).
-
-calc_finished({3, 0}, Role, _PrfAlgo, MasterSecret, Handshake) ->
- ssl_v3:finished(Role, MasterSecret, lists:reverse(Handshake));
calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) ->
tls_v1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)).
@@ -1961,20 +2121,10 @@ master_secret(Version, MasterSecret,
{MasterSecret,
ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState,
ServerCipherState, Role)}.
-
-setup_keys({3,0}, _PrfAlgo, MasterSecret,
- ServerRandom, ClientRandom, HashSize, KML, EKML, IVS) ->
- ssl_v3:setup_keys(MasterSecret, ServerRandom,
- ClientRandom, HashSize, KML, EKML, IVS);
-
setup_keys({3,N}, PrfAlgo, MasterSecret,
ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) ->
tls_v1:setup_keys(N, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KML, IVS).
-
-calc_master_secret({3,0}, _PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) ->
- ssl_v3:master_secret(PremasterSecret, ClientRandom, ServerRandom);
-
calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) ->
tls_v1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom).
@@ -2173,7 +2323,7 @@ enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo},
server_key_exchange_hash(HashAlgo, <<ClientRandom/binary,
ServerRandom/binary,
EncParams/binary>>),
- Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey),
+ Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey, SignAlgo),
#server_key_params{params = Params,
params_bin = EncParams,
hashsign = {HashAlgo, SignAlgo},
@@ -2587,6 +2737,10 @@ decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
decode_extensions(Rest, Version, MessageType,
Acc#{sni => dec_sni(NameList)});
+decode_extensions(<<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(1), ?BYTE(MaxFragEnum), Rest/binary>>,
+ Version, MessageType, Acc) ->
+ %% RFC 6066 Section 4
+ decode_extensions(Rest, Version, MessageType, Acc#{max_frag_enum => #max_frag_enum{enum = MaxFragEnum}});
decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 ->
<<?BYTE(_),Versions/binary>> = ExtData,
@@ -2661,6 +2815,50 @@ decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len),
#pre_shared_key_server_hello{
selected_identity = Identity}});
+decode_extensions(<<?UINT16(?COOKIE_EXT), ?UINT16(Len), ?UINT16(CookieLen),
+ Cookie:CookieLen/binary, Rest/binary>>,
+ Version, MessageType, Acc)
+ when Len == CookieLen + 2 ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{cookie => #cookie{cookie = Cookie}});
+
+%% RFC6066, if a server returns a "CertificateStatus" message, then
+%% the server MUST have included an extension of type "status_request"
+%% with empty "extension_data" in the extended server hello.
+decode_extensions(<<?UINT16(?STATUS_REQUEST), ?UINT16(Len),
+ _ExtensionData:Len/binary, Rest/binary>>, Version,
+ MessageType = server_hello, Acc)
+ when Len =:= 0 ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{status_request => undefined});
+%% RFC8446 4.4.2.1, In TLS1.3, the body of the "status_request" extension
+%% from the server MUST be a CertificateStatus structure as defined in
+%% RFC6066.
+decode_extensions(<<?UINT16(?STATUS_REQUEST), ?UINT16(Len),
+ CertStatus:Len/binary, Rest/binary>>, Version,
+ MessageType, Acc) ->
+ case CertStatus of
+ <<?BYTE(?CERTIFICATE_STATUS_TYPE_OCSP),
+ ?UINT24(OCSPLen),
+ ASN1OCSPResponse:OCSPLen/binary>> ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{status_request => #certificate_status{response = ASN1OCSPResponse}});
+ _Other ->
+ decode_extensions(Rest, Version, MessageType, Acc)
+ end;
+
+decode_extensions(<<?UINT16(?EARLY_DATA_EXT), ?UINT16(0), Rest/binary>>,
+ Version, MessageType, Acc) ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{early_data => #early_data_indication{}});
+
+decode_extensions(<<?UINT16(?EARLY_DATA_EXT), ?UINT16(4), ?UINT32(MaxSize),
+ Rest/binary>>,
+ Version, MessageType, Acc) ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{early_data =>
+ #early_data_indication_nst{indication = MaxSize}});
+
%% Ignore data following the ClientHello (i.e.,
%% extensions) if not understood.
decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
@@ -2775,6 +2973,7 @@ certs_from_list(ACList) ->
CertLen = byte_size(Cert),
<<?UINT24(CertLen), Cert/binary>>
end || Cert <- ACList]).
+
from_3bytes(Bin3) ->
from_3bytes(Bin3, []).
@@ -2948,6 +3147,13 @@ handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) ->
false -> handle_alpn_extension(Tail, ClientProtocols)
end.
+handle_mfl_extension(#max_frag_enum{enum = Enum}=MaxFragEnum) when Enum >= 1, Enum =< 4 ->
+ MaxFragEnum;
+handle_mfl_extension(#max_frag_enum{}) ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER));
+handle_mfl_extension(_) ->
+ undefined.
+
handle_next_protocol(undefined,
_NextProtocolSelector, _Renegotiating) ->
undefined;
@@ -3166,6 +3372,18 @@ sni(disable) ->
sni(Hostname) ->
#sni{hostname = Hostname}.
+%% convert max_fragment_length (in bytes) to the RFC 6066 ENUM
+max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_1) ->
+ #max_frag_enum{enum = 1};
+max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_2) ->
+ #max_frag_enum{enum = 2};
+max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_3) ->
+ #max_frag_enum{enum = 3};
+max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_4) ->
+ #max_frag_enum{enum = 4};
+max_frag_enum(undefined) ->
+ undefined.
+
renegotiation_info(_, client, _, false) ->
#renegotiation_info{renegotiated_connection = undefined};
renegotiation_info(_RecordCB, server, ConnectionStates, false) ->
@@ -3239,9 +3457,6 @@ handle_renegotiation_info(_, _RecordCB, server, #renegotiation_info{renegotiated
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation))
end
end;
-handle_renegotiation_info({3,0}, _RecordCB, client, undefined, ConnectionStates, true, _SecureRenegotation, _) ->
- {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
-
handle_renegotiation_info(_, RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) ->
handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation);
@@ -3286,7 +3501,7 @@ empty_extensions() ->
empty_extensions({3,4}, client_hello) ->
#{
sni => undefined,
- %% max_fragment_length => undefined,
+ %% max_frag_enum => undefined,
%% status_request => undefined,
elliptic_curves => undefined,
signature_algs => undefined,
@@ -3301,7 +3516,7 @@ empty_extensions({3,4}, client_hello) ->
pre_shared_key => undefined,
psk_key_exchange_modes => undefined,
%% early_data => undefined,
- %% cookie => undefined,
+ cookie => undefined,
client_hello_versions => undefined,
%% cert_authorities => undefined,
%% post_handshake_auth => undefined,
@@ -3326,10 +3541,9 @@ empty_extensions({3,4}, server_hello) ->
empty_extensions({3,4}, hello_retry_request) ->
#{server_hello_selected_version => undefined,
key_share => undefined,
- pre_shared_key => undefined
+ pre_shared_key => undefined, %% TODO remove!
+ cookie => undefined
};
-empty_extensions({3,0}, _) ->
- empty_extensions();
empty_extensions(_, server_hello) ->
#{renegotiation_info => undefined,
alpn => undefined,
@@ -3338,3 +3552,51 @@ empty_extensions(_, server_hello) ->
handle_log(Level, {LogLevel, ReportMap, Meta}) ->
ssl_logger:log(Level, LogLevel, ReportMap, Meta).
+
+
+path_validate([{TrustedCert, Path}], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) ->
+ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef,
+ CRLDbHandle, Version, SslOptions, CertExt);
+path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) ->
+ case path_validation(TrustedCert, Path, ServerName,
+ Role, CertDbHandle, CRLDbHandle, CertDbRef,
+ Version, SslOptions, CertExt) of
+ {ok, _} = Result ->
+ Result;
+ {error, _} ->
+ path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt)
+ end.
+
+path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version,
+ #{verify_fun := VerifyFun,
+ customize_hostname_check := CustomizeHostnameCheck,
+ crl_check := CrlCheck,
+ log_level := Level,
+ signature_algs := SignAlgos,
+ depth := Depth},
+ #{cert_ext := CertExt,
+ ocsp_responder_certs := OcspResponderCerts,
+ ocsp_state := OcspState}) ->
+ ValidationFunAndState =
+ validation_fun_and_state(VerifyFun, #{role => Role,
+ certdb => CertDbHandle,
+ certdb_ref => CertDbRef,
+ server_name => ServerName,
+ customize_hostname_check =>
+ CustomizeHostnameCheck,
+ signature_algs => SignAlgos,
+ signature_algs_cert => undefined,
+ version => Version,
+ crl_check => CrlCheck,
+ crl_db => CRLDbHandle,
+ cert_ext => CertExt,
+ issuer => TrustedCert,
+ ocsp_responder_certs => OcspResponderCerts,
+ ocsp_state => OcspState},
+ Path, Level),
+ Options = [{max_path_length, Depth},
+ {verify_fun, ValidationFunAndState}],
+ public_key:pkix_path_validation(TrustedCert, Path, Options).
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index a772567846..3f06eaa095 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -38,19 +38,20 @@
-define(ECDSA, 3).
-record(session, {
- session_id,
- peer_certificate,
- own_certificate,
- compression_method,
- cipher_suite,
- master_secret,
- srp_username,
- is_resumable,
- time_stamp,
- ecc, %% TLS 1.3 Group
- sign_alg, %% TLS 1.3 Signature Algorithm
- dh_public_value %% TLS 1.3 DH Public Value from peer
- }).
+ session_id,
+ internal_id,
+ peer_certificate,
+ own_certificates,
+ compression_method,
+ cipher_suite,
+ master_secret,
+ srp_username,
+ is_resumable,
+ time_stamp,
+ ecc, %% TLS 1.3 Group
+ sign_alg, %% TLS 1.3 Signature Algorithm
+ dh_public_value %% TLS 1.3 DH Public Value from peer
+ }).
-define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3
-define(NUM_OF_PREMASTERSECRET_BYTES, 48).
@@ -376,7 +377,7 @@
-define(NAMED_CURVE, 3).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% RFC 6066 Server name indication
+%% RFC 6066 TLS Extensions: Extension Definitions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% section 3
@@ -389,12 +390,42 @@
hostname = undefined
}).
+%% enum{ 2^9(1), 2^10(2), 2^11(3), 2^12(4), (255) } MaxFragmentLength;
+-define(MAX_FRAGMENT_LENGTH_EXT, 1).
+-define(MAX_FRAGMENT_LENGTH_BYTES_1, 512).
+-define(MAX_FRAGMENT_LENGTH_BYTES_2, 1024).
+-define(MAX_FRAGMENT_LENGTH_BYTES_3, 2048).
+-define(MAX_FRAGMENT_LENGTH_BYTES_4, 4096).
+
+-record(max_frag_enum, {
+ enum = undefined %% contains the enum value 1..4
+ }).
+
+%% Section 8, Certificate Status Request
+-define(STATUS_REQUEST, 5).
+-define(CERTIFICATE_STATUS_TYPE_OCSP, 1).
+-define(CERTIFICATE_STATUS, 22).
+
+%% status request record defined in RFC 6066, section 8
+-record(certificate_status_request, {
+ status_type,
+ request
+}).
+
+-record(ocsp_status_request, {
+ responder_id_list = [],
+ request_extensions = []
+}).
+
+-record(certificate_status, {
+ status_type,
+ response
+}).
+
%% Other possible values from RFC 6066, not supported
--define(MAX_FRAGMENT_LENGTH, 1).
-define(CLIENT_CERTIFICATE_URL, 2).
-define(TRUSTED_CA_KEYS, 3).
-define(TRUNCATED_HMAC, 4).
--define(STATUS_REQUEST, 5).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% RFC 7250 Using Raw Public Keys in Transport Layer Security (TLS)
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index b9411e6ebf..b080016458 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -25,7 +25,6 @@
-include_lib("public_key/include/public_key.hrl").
--define(VSN, "8.2.6").
-define(SECRET_PRINTOUT, "***").
-type reason() :: any().
@@ -75,10 +74,10 @@
%% Keep as interop with legacy software but do not support as default
%% tlsv1.0 and tlsv1.1 is now also considered legacy
%% tlsv1.3 is under development (experimental).
--define(ALL_AVAILABLE_VERSIONS, ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', tlsv1, sslv3]).
+-define(ALL_AVAILABLE_VERSIONS, ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', tlsv1]).
-define(ALL_AVAILABLE_DATAGRAM_VERSIONS, ['dtlsv1.2', dtlsv1]).
%% Defines the default versions when not specified by an ssl option.
--define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2']).
+-define(ALL_SUPPORTED_VERSIONS, ['tlsv1.3', 'tlsv1.2']).
-define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1']).
%% Versions allowed in TLSCiphertext.version (TLS 1.2 and prior) and
@@ -86,7 +85,7 @@
%% TLS 1.3 sets TLSCiphertext.legacy_record_version to 0x0303 for all records
%% generated other than an than an initial ClientHello, where it MAY also be 0x0301.
%% Thus, the allowed range is limited to 0x0300 - 0x0303.
--define(ALL_TLS_RECORD_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]).
+-define(ALL_TLS_RECORD_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1]).
-define(ALL_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2']).
-define(MIN_DATAGRAM_SUPPORTED_VERSIONS, [dtlsv1]).
@@ -116,6 +115,8 @@
%% 2^24.5 * 2^14 = 2^38.5
-define(KEY_USAGE_LIMIT_AES_GCM, 388736063997).
+-define(DEFAULT_MAX_EARLY_DATA_SIZE, 16384).
+
%% This map stores all supported options with default values and
%% list of dependencies:
%% #{<option> => {<default_value>, [<option>]},
@@ -124,6 +125,7 @@
#{
alpn_advertised_protocols => {undefined, [versions]},
alpn_preferred_protocols => {undefined, [versions]},
+ anti_replay => {undefined, [versions, session_tickets]},
beast_mitigation => {one_n_minus_one, [versions]},
cacertfile => {undefined, [versions,
verify_fun,
@@ -133,12 +135,16 @@
certfile => {<<>>, [versions]},
ciphers => {[], [versions]},
client_renegotiation => {undefined, [versions]},
+ cookie => {true, [versions]},
crl_cache => {{ssl_crl_cache, {internal, []}}, [versions]},
crl_check => {false, [versions]},
customize_hostname_check => {[], [versions]},
- depth => {1, [versions]},
+ depth => {10, [versions]},
dh => {undefined, [versions]},
dhfile => {undefined, [versions]},
+ early_data => {undefined, [versions,
+ session_tickets,
+ use_ticket]},
eccs => {undefined, [versions]},
erl_dist => {false, [versions]},
fail_if_no_peer_cert => {false, [versions]},
@@ -153,8 +159,18 @@
key_update_at => {?KEY_USAGE_LIMIT_AES_GCM, [versions]},
log_level => {notice, [versions]},
max_handshake_size => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]},
+ middlebox_comp_mode => {true, [versions]},
+ max_fragment_length => {undefined, [versions]},
next_protocol_selector => {undefined, [versions]},
next_protocols_advertised => {undefined, [versions]},
+ %% If enable OCSP stapling
+ ocsp_stapling => {false, [versions]},
+ %% Optional arg, if give suggestion of OCSP responders
+ ocsp_responder_certs => {[], [versions,
+ ocsp_stapling]},
+ %% Optional arg, if add nonce extension in request
+ ocsp_nonce => {true, [versions,
+ ocsp_stapling]},
padding_check => {true, [versions]},
partial_chain => {fun(_) -> unknown_ca end, [versions]},
password => {"", [versions]},
@@ -163,8 +179,8 @@
renegotiate_at => {?DEFAULT_RENEGOTIATE_AT, [versions]},
reuse_session => {undefined, [versions]},
reuse_sessions => {true, [versions]},
- anti_replay => {undefined, [versions, session_tickets]},
secure_renegotiate => {true, [versions]},
+ keep_secrets => {false, [versions]},
server_name_indication => {undefined, [versions]},
session_tickets => {disabled, [versions]},
signature_algs => {undefined, [versions]},
@@ -176,23 +192,20 @@
supported_groups => {undefined, [versions]},
use_ticket => {undefined, [versions]},
user_lookup_fun => {undefined, [versions]},
- validate_extensions_fun => {undefined, [versions]},
verify => {verify_none, [versions,
fail_if_no_peer_cert,
- partial_chain,
- verify_client_once]},
- verify_client_once => {false, [versions]},
+ partial_chain]},
verify_fun =>
{
- {fun(_,{bad_cert, _}, UserState) ->
+ {fun(_, {bad_cert, _}, UserState) ->
{valid, UserState};
- (_,{extension, #'Extension'{critical = true}}, UserState) ->
+ (_, {extension, #'Extension'{critical = true}}, UserState) ->
%% This extension is marked as critical, so
%% certificate verification should fail if we don't
%% understand the extension. However, this is
%% `verify_none', so let's accept it anyway.
{valid, UserState};
- (_,{extension, _}, UserState) ->
+ (_, {extension, _}, UserState) ->
{unknown, UserState};
(_, valid, UserState) ->
{valid, UserState};
@@ -228,6 +241,17 @@
{stop, any(), any()}.
-type ssl_options() :: map().
+%% Internal ticket data record holding pre-processed ticket data.
+-record(ticket_data,
+ {key, %% key in client ticket store
+ pos, %% ticket position in binders list
+ identity, %% opaque ticket binary
+ psk, %% pre-shared key
+ nonce, %% ticket nonce
+ cipher_suite, %% cipher suite - hash, bulk cipher algorithm
+ max_size %% max early data size allowed by this ticket
+ }).
+
-endif. % -ifdef(ssl_internal).
diff --git a/lib/ssl/src/ssl_listen_tracker_sup.erl b/lib/ssl/src/ssl_listen_tracker_sup.erl
index f7e97bcb76..6afd1c0009 100644
--- a/lib/ssl/src/ssl_listen_tracker_sup.erl
+++ b/lib/ssl/src/ssl_listen_tracker_sup.erl
@@ -69,4 +69,4 @@ init(_O) ->
tracker_name(normal) ->
?MODULE;
tracker_name(dist) ->
- list_to_atom(atom_to_list(?MODULE) ++ "dist").
+ list_to_atom(atom_to_list(?MODULE) ++ "_dist").
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
index dd1f4cb07a..027dbe8732 100644
--- a/lib/ssl/src/ssl_logger.erl
+++ b/lib/ssl/src/ssl_logger.erl
@@ -180,6 +180,11 @@ parse_handshake(Direction, #certificate{} = Certificate) ->
[header_prefix(Direction)]),
Message = io_lib:format("~p", [?rec_info(certificate, Certificate)]),
{Header, Message};
+parse_handshake(Direction, #certificate_status{} = CertificateStatus) ->
+ Header = io_lib:format("~s Handshake, CertificateStatus",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_status, CertificateStatus)]),
+ {Header, Message};
parse_handshake(Direction, #server_key_exchange{} = ServerKeyExchange) ->
Header = io_lib:format("~s Handshake, ServerKeyExchange",
[header_prefix(Direction)]),
@@ -249,9 +254,13 @@ parse_handshake(Direction, #key_update{} = KeyUpdate) ->
Header = io_lib:format("~s Post-Handshake, KeyUpdate",
[header_prefix(Direction)]),
Message = io_lib:format("~p", [?rec_info(key_update, KeyUpdate)]),
+ {Header, Message};
+parse_handshake(Direction, #end_of_early_data{} = EndOfEarlyData) ->
+ Header = io_lib:format("~s Handshake, EndOfEarlyData",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(end_of_early_data, EndOfEarlyData)]),
{Header, Message}.
-
parse_cipher_suites([_|_] = Ciphers) ->
[format_cipher(C) || C <- Ciphers].
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index 664d967cb2..9d0dbc2d9b 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -29,7 +29,7 @@
-export([start_link/1, start_link_dist/1,
connection_init/3, cache_pem_file/2,
lookup_trusted_cert/4,
- new_session_id/1, clean_cert_db/2,
+ clean_cert_db/2,
register_session/2, register_session/4, invalidate_session/2,
insert_crls/2, insert_crls/3, delete_crls/1, delete_crls/2,
invalidate_session/3, name/1]).
@@ -48,15 +48,14 @@
-record(state, {
session_cache_client :: db_handle(),
- session_cache_server :: db_handle(),
- session_cache_cb :: atom(),
+ session_cache_client_cb :: atom(),
session_lifetime :: integer(),
certificate_db :: db_handle(),
session_validation_timer :: reference(),
session_cache_client_max :: integer(),
- session_cache_server_max :: integer(),
- session_server_invalidator :: undefined | pid(),
- session_client_invalidator :: undefined | pid()
+ session_client_invalidator :: undefined | pid(),
+ options :: list(),
+ client_session_order :: gb_trees:tree()
}).
-define(GEN_UNIQUE_ID_MAX_TRIES, 10).
@@ -149,14 +148,6 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) ->
ssl_pkix_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer).
%%--------------------------------------------------------------------
--spec new_session_id(integer()) -> ssl:session_id().
-%%
-%% Description: Creates a session id for the server.
-%%--------------------------------------------------------------------
-new_session_id(Port) ->
- call({new_session_id, Port}).
-
-%%--------------------------------------------------------------------
-spec clean_cert_db(reference(), binary()) -> ok.
%%
%% Description: Send clean request of cert db to ssl_manager process should
@@ -229,30 +220,31 @@ init([ManagerName, PemCacheName, Opts]) ->
put(ssl_manager, ManagerName),
put(ssl_pem_cache, PemCacheName),
process_flag(trap_exit, true),
- CacheCb = proplists:get_value(session_cb, Opts, ssl_session_cache),
+
+ #{session_cb := DefaultCacheCb,
+ session_cb_init_args := DefaultCacheCbInitArgs,
+ lifetime := DefaultSessLifeTime,
+ max := ClientSessMax
+ } = ssl_config:pre_1_3_session_opts(client),
+ CacheCb = proplists:get_value(session_cb, Opts, DefaultCacheCb),
SessionLifeTime =
- proplists:get_value(session_lifetime, Opts, ?'24H_in_sec'),
- CertDb = ssl_pkix_db:create(PemCacheName),
+ proplists:get_value(session_lifetime, Opts, DefaultSessLifeTime),
ClientSessionCache =
CacheCb:init([{role, client} |
- proplists:get_value(session_cb_init_args, Opts, [])]),
- ServerSessionCache =
- CacheCb:init([{role, server} |
- proplists:get_value(session_cb_init_args, Opts, [])]),
+ proplists:get_value(session_cb_init_args, Opts, DefaultCacheCbInitArgs)]),
+
+ CertDb = ssl_pkix_db:create(PemCacheName),
Timer = erlang:send_after(SessionLifeTime * 1000 + 5000,
self(), validate_sessions),
{ok, #state{certificate_db = CertDb,
session_cache_client = ClientSessionCache,
- session_cache_server = ServerSessionCache,
- session_cache_cb = CacheCb,
+ session_cache_client_cb = CacheCb,
session_lifetime = SessionLifeTime,
session_validation_timer = Timer,
- session_cache_client_max =
- max_session_cache_size(session_cache_client_max),
- session_cache_server_max =
- max_session_cache_size(session_cache_server_max),
+ session_cache_client_max = ClientSessMax,
session_client_invalidator = undefined,
- session_server_invalidator = undefined
+ options = Opts,
+ client_session_order = gb_trees:empty()
}}.
%%--------------------------------------------------------------------
@@ -299,12 +291,6 @@ handle_call({{delete_crls, CRLsOrPath}, _}, _From,
#state{certificate_db = Db} = State) ->
ssl_pkix_db:remove_crls(Db, CRLsOrPath),
{reply, ok, State};
-
-handle_call({{new_session_id, Port}, _},
- _, #state{session_cache_cb = CacheCb,
- session_cache_server = Cache} = State) ->
- Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb),
- {reply, Id, State};
handle_call({{register_session, Host, Port, Session},_}, _, State0) ->
State = client_register_session(Host, Port, Session, State0),
{reply, ok, State}.
@@ -324,23 +310,11 @@ handle_cast({register_session, Host, Port, Session, unique}, State0) ->
handle_cast({register_session, Host, Port, Session, true}, State0) ->
State = client_register_session(Host, Port, Session, State0),
{noreply, State};
-
-handle_cast({register_session, Port, Session}, State0) ->
- State = server_register_session(Port, Session, State0),
- {noreply, State};
-
handle_cast({invalidate_session, Host, Port,
#session{session_id = ID} = Session},
#state{session_cache_client = Cache,
- session_cache_cb = CacheCb} = State) ->
+ session_cache_client_cb = CacheCb} = State) ->
invalidate_session(Cache, CacheCb, {{Host, Port}, ID}, Session, State);
-
-handle_cast({invalidate_session, Port, #session{session_id = ID} = Session},
- #state{session_cache_server = Cache,
- session_cache_cb = CacheCb} = State) ->
- invalidate_session(Cache, CacheCb, {Port, ID}, Session, State);
-
-
handle_cast({insert_crls, Path, CRLs},
#state{certificate_db = Db} = State) ->
ssl_pkix_db:add_crls(Db, Path, CRLs),
@@ -359,20 +333,16 @@ handle_cast({delete_crls, CRLsOrPath},
%%
%% Description: Handling all non call/cast messages
%%-------------------------------------------------------------------
-handle_info(validate_sessions, #state{session_cache_cb = CacheCb,
+handle_info(validate_sessions, #state{session_cache_client_cb = CacheCb,
session_cache_client = ClientCache,
- session_cache_server = ServerCache,
session_lifetime = LifeTime,
- session_client_invalidator = Client,
- session_server_invalidator = Server
+ session_client_invalidator = Client
} = State) ->
Timer = erlang:send_after(?SESSION_VALIDATION_INTERVAL,
self(), validate_sessions),
CPid = start_session_validator(ClientCache, CacheCb, LifeTime, Client),
- SPid = start_session_validator(ServerCache, CacheCb, LifeTime, Server),
{noreply, State#state{session_validation_timer = Timer,
- session_client_invalidator = CPid,
- session_server_invalidator = SPid}};
+ session_client_invalidator = CPid}};
handle_info({clean_cert_db, Ref, File},
#state{certificate_db = [CertDb, {RefDb, FileMapDb} | _]} = State) ->
@@ -384,12 +354,8 @@ handle_info({clean_cert_db, Ref, File},
clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File)
end,
{noreply, State};
-
handle_info({'EXIT', Pid, _}, #state{session_client_invalidator = Pid} = State) ->
{noreply, State#state{session_client_invalidator = undefined}};
-handle_info({'EXIT', Pid, _}, #state{session_server_invalidator = Pid} = State) ->
- {noreply, State#state{session_server_invalidator = undefined}};
-
handle_info(_Info, State) ->
{noreply, State}.
@@ -403,13 +369,11 @@ handle_info(_Info, State) ->
%%--------------------------------------------------------------------
terminate(_Reason, #state{certificate_db = Db,
session_cache_client = ClientSessionCache,
- session_cache_server = ServerSessionCache,
- session_cache_cb = CacheCb,
+ session_cache_client_cb = CacheCb,
session_validation_timer = Timer}) ->
erlang:cancel_timer(Timer),
ssl_pkix_db:remove(Db),
catch CacheCb:terminate(ClientSessionCache),
- catch CacheCb:terminate(ServerSessionCache),
ok.
%%--------------------------------------------------------------------
@@ -463,46 +427,15 @@ session_validation({{Port, _}, Session}, LifeTime) ->
validate_session(Port, Session, LifeTime),
LifeTime.
-max_session_cache_size(CacheType) ->
- case application:get_env(ssl, CacheType) of
- {ok, Size} when is_integer(Size) ->
- Size;
- _ ->
- ?DEFAULT_MAX_SESSION_CACHE
- end.
-
-invalidate_session(Cache, CacheCb, Key, _Session, State) ->
+invalidate_session(Cache, CacheCb, Key, _Session,
+ #state{client_session_order = Order} = State) ->
case CacheCb:lookup(Cache, Key) of
undefined -> %% Session is already invalidated
{noreply, State};
- #session{} ->
+ #session{internal_id = InternalId} ->
CacheCb:delete(Cache, Key),
- {noreply, State}
- end.
-
-%% If we cannot generate a not allready in use session ID in
-%% ?GEN_UNIQUE_ID_MAX_TRIES we make the new session uncacheable The
-%% value of ?GEN_UNIQUE_ID_MAX_TRIES is stolen from open SSL which
-%% states : "If we cannot find a session id in
-%% ?GEN_UNIQUE_ID_MAX_TRIES either the RAND code is broken or someone
-%% is trying to open roughly very close to 2^128 (or 2^256) SSL
-%% sessions to our server"
-new_id(_, 0, _, _) ->
- <<>>;
-new_id(Port, Tries, Cache, CacheCb) ->
- Id = ssl_cipher:random_bytes(?NUM_OF_SESSION_ID_BYTES),
- case CacheCb:lookup(Cache, {Port, Id}) of
- undefined ->
- Now = erlang:monotonic_time(),
- %% New sessions cannot be set to resumable
- %% until handshake is compleate and the
- %% other session values are set.
- CacheCb:update(Cache, {Port, Id}, #session{session_id = Id,
- is_resumable = new,
- time_stamp = Now}),
- Id;
- _ ->
- new_id(Port, Tries - 1, Cache, CacheCb)
+ {noreply, State#state{session_cache_client = Cache,
+ client_session_order = gb_trees:delete(InternalId, Order)}}
end.
clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) ->
@@ -515,55 +448,62 @@ clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) ->
ok
end.
-client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache,
- session_cache_cb = CacheCb,
+client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache0,
+ session_cache_client_cb = CacheCb,
session_cache_client_max = Max,
- session_client_invalidator = Pid0} = State) ->
+ options = Options,
+ client_session_order = Order0} = State) ->
TimeStamp = erlang:monotonic_time(),
NewSession = Session#session{time_stamp = TimeStamp},
- case CacheCb:select_session(Cache, {Host, Port}) of
+ case CacheCb:select_session(Cache0, {Host, Port}) of
no_session ->
- Pid = do_register_session({{Host, Port},
- NewSession#session.session_id},
- NewSession, Max, Pid0, Cache, CacheCb),
- State#state{session_client_invalidator = Pid};
+ {Cache, Order} = do_register_session({{Host, Port},
+ NewSession#session.session_id},
+ NewSession, Max, Cache0, CacheCb, Options, Order0),
+ State#state{session_cache_client = Cache, client_session_order = Order};
Sessions ->
register_unique_session(Sessions, NewSession, {Host, Port}, State)
end.
-client_register_session(Host, Port, Session, #state{session_cache_client = Cache,
- session_cache_cb = CacheCb,
+client_register_session(Host, Port, Session, #state{session_cache_client = Cache0,
+ session_cache_client_cb = CacheCb,
session_cache_client_max = Max,
- session_client_invalidator = Pid0} = State) ->
+ options = Options,
+ client_session_order = Order0} = State) ->
TimeStamp = erlang:monotonic_time(),
NewSession = Session#session{time_stamp = TimeStamp},
- Pid = do_register_session({{Host, Port},
- NewSession#session.session_id},
- NewSession, Max, Pid0, Cache, CacheCb),
- State#state{session_client_invalidator = Pid}.
-
-server_register_session(Port, Session, #state{session_cache_server_max = Max,
- session_cache_server = Cache,
- session_cache_cb = CacheCb,
- session_server_invalidator = Pid0} = State) ->
- TimeStamp = erlang:monotonic_time(),
- NewSession = Session#session{time_stamp = TimeStamp},
- Pid = do_register_session({Port, NewSession#session.session_id},
- NewSession, Max, Pid0, Cache, CacheCb),
- State#state{session_server_invalidator = Pid}.
-
-do_register_session(Key, Session, Max, Pid, Cache, CacheCb) ->
- try CacheCb:size(Cache) of
- Size when Size >= Max ->
- invalidate_session_cache(Pid, CacheCb, Cache);
- _ ->
- CacheCb:update(Cache, Key, Session),
- Pid
+ SessionId = NewSession#session.session_id,
+ {Cache, Order} = do_register_session({{Host, Port}, SessionId},
+ NewSession, Max, Cache0, CacheCb, Options, Order0),
+ State#state{session_cache_client = Cache,
+ client_session_order = Order}.
+
+do_register_session(Key, #session{time_stamp = TimeStamp} = Session0,
+ Max, Cache, CacheCb, Options, Order0) ->
+ try
+ case CacheCb:size(Cache) of
+ Max ->
+ InternalId = {TimeStamp, erlang:unique_integer([monotonic])},
+ Session = Session0#session{internal_id = InternalId},
+ {_, OldKey, Order1} = gb_trees:take_smallest(Order0),
+ Order = gb_trees:insert(InternalId, Key, Order1),
+ CacheCb:delete(Cache, OldKey),
+ CacheCb:update(Cache, Key, Session),
+ {Cache, Order};
+ _ ->
+ InternalId = {TimeStamp, erlang:unique_integer([monotonic])},
+ Session = Session0#session{internal_id = InternalId},
+ Order = gb_trees:insert(InternalId, Key, Order0),
+ CacheCb:update(Cache, Key, Session),
+ {Cache, Order}
+ end
catch
- error:undef ->
- CacheCb:update(Cache, Key, Session),
- Pid
+ _:_ ->
+ %% Backwards compatibility if size functions is not implemented by callback
+ Args = proplists:get_value(session_cb_init_args, Options, []),
+ CacheCb:terminate(Cache),
+ {CacheCb:init(Args), gb_trees:empty()}
end.
@@ -571,31 +511,33 @@ do_register_session(Key, Session, Max, Pid, Cache, CacheCb) ->
%% for itself creating big delays at connection time.
register_unique_session(Sessions, Session, PartialKey,
#state{session_cache_client_max = Max,
- session_cache_client = Cache,
- session_cache_cb = CacheCb,
- session_client_invalidator = Pid0} = State) ->
+ session_cache_client = Cache0,
+ session_cache_client_cb = CacheCb,
+ options = Options,
+ client_session_order = Order0} = State) ->
case exists_equivalent(Session , Sessions) of
true ->
State;
false ->
- Pid = do_register_session({PartialKey,
- Session#session.session_id},
- Session, Max, Pid0, Cache, CacheCb),
- State#state{session_client_invalidator = Pid}
+ {Cache, Order} = do_register_session({PartialKey,
+ Session#session.session_id},
+ Session, Max, Cache0, CacheCb, Options, Order0),
+ State#state{session_cache_client = Cache,
+ client_session_order = Order}
end.
exists_equivalent(_, []) ->
false;
exists_equivalent(#session{
peer_certificate = PeerCert,
- own_certificate = OwnCert,
+ own_certificates = [OwnCert | _],
compression_method = Compress,
cipher_suite = CipherSuite,
srp_username = SRP,
ecc = ECC} ,
[#session{
peer_certificate = PeerCert,
- own_certificate = OwnCert,
+ own_certificates = [OwnCert | _],
compression_method = Compress,
cipher_suite = CipherSuite,
srp_username = SRP,
@@ -614,21 +556,14 @@ add_trusted_certs(Pid, Trustedcerts, Db) ->
session_cache(client, #state{session_cache_client = Cache}) ->
Cache;
-session_cache(server, #state{session_cache_server = Cache}) ->
- Cache.
+session_cache(server, _) ->
+ no_longer_defined.
crl_db_info([_,_,_,Local], {internal, Info}) ->
{Local, Info};
crl_db_info(_, UserCRLDb) ->
UserCRLDb.
-%% Only start a session invalidator if there is not
-%% one already active
-invalidate_session_cache(undefined, CacheCb, Cache) ->
- start_session_validator(Cache, CacheCb, {invalidate_before, erlang:monotonic_time()}, undefined);
-invalidate_session_cache(Pid, _CacheCb, _Cache) ->
- Pid.
-
load_mitigation() ->
MSec = rand:uniform(?LOAD_MITIGATION),
receive
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index 867d2cfc5a..040c4f1ebc 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -40,8 +40,13 @@
set_renegotiation_flag/2,
set_client_verify_data/3,
set_server_verify_data/3,
- empty_connection_state/2, initial_connection_state/2, record_protocol_role/1,
- step_encryption_state/1]).
+ set_max_fragment_length/2,
+ empty_connection_state/2,
+ empty_connection_state/3,
+ record_protocol_role/1,
+ step_encryption_state/1,
+ step_encryption_state_read/1,
+ step_encryption_state_write/1]).
%% Compression
-export([compress/3, uncompress/3, compressions/0]).
@@ -87,7 +92,8 @@ pending_connection_state(ConnectionStates, write) ->
maps:get(pending_write, ConnectionStates).
%%--------------------------------------------------------------------
--spec activate_pending_connection_state(connection_states(), read | write, tls_connection | dtls_connection) ->
+-spec activate_pending_connection_state(connection_states(), read | write,
+ tls_gen_connection | dtls_gen_connection) ->
connection_states().
%%
%% Description: Creates a new instance of the connection_states record
@@ -136,6 +142,17 @@ step_encryption_state(#state{connection_states =
ConnStates#{current_read => NewRead,
current_write => NewWrite}}.
+step_encryption_state_read(#state{connection_states =
+ #{pending_read := PendingRead} = ConnStates} = State) ->
+ NewRead = PendingRead#{sequence_number => 0},
+ State#state{connection_states =
+ ConnStates#{current_read => NewRead}}.
+
+step_encryption_state_write(#state{connection_states =
+ #{pending_write := PendingWrite} = ConnStates} = State) ->
+ NewWrite = PendingWrite#{sequence_number => 0},
+ State#state{connection_states =
+ ConnStates#{current_write => NewWrite}}.
%%--------------------------------------------------------------------
-spec set_security_params(#security_parameters{}, #security_parameters{},
@@ -203,6 +220,33 @@ set_renegotiation_flag(Flag, #{current_read := CurrentRead0,
pending_write => PendingWrite}.
%%--------------------------------------------------------------------
+-spec set_max_fragment_length(term(), connection_states()) -> connection_states().
+%%
+%% Description: Set maximum fragment length in all connection states
+%%--------------------------------------------------------------------
+set_max_fragment_length(#max_frag_enum{enum = MaxFragEnum},
+ #{current_read := CurrentRead0,
+ current_write := CurrentWrite0,
+ pending_read := PendingRead0,
+ pending_write := PendingWrite0}
+ = ConnectionStates) ->
+ MaxFragmentLength = if MaxFragEnum == 1 -> ?MAX_FRAGMENT_LENGTH_BYTES_1;
+ MaxFragEnum == 2 -> ?MAX_FRAGMENT_LENGTH_BYTES_2;
+ MaxFragEnum == 3 -> ?MAX_FRAGMENT_LENGTH_BYTES_3;
+ MaxFragEnum == 4 -> ?MAX_FRAGMENT_LENGTH_BYTES_4
+ end,
+ CurrentRead = CurrentRead0#{max_fragment_length => MaxFragmentLength},
+ CurrentWrite = CurrentWrite0#{max_fragment_length => MaxFragmentLength},
+ PendingRead = PendingRead0#{max_fragment_length => MaxFragmentLength},
+ PendingWrite = PendingWrite0#{max_fragment_length => MaxFragmentLength},
+ ConnectionStates#{current_read => CurrentRead,
+ current_write => CurrentWrite,
+ pending_read => PendingRead,
+ pending_write => PendingWrite};
+set_max_fragment_length(_,ConnectionStates) ->
+ ConnectionStates.
+
+%%--------------------------------------------------------------------
-spec set_client_verify_data(current_read | current_write | current_both,
binary(), connection_states())->
connection_states().
@@ -416,6 +460,10 @@ nonce_seed(_,_, CipherState) ->
%%--------------------------------------------------------------------
empty_connection_state(ConnectionEnd, BeastMitigation) ->
+ MaxEarlyDataSize = ssl_config:get_max_early_data_size(),
+ empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize).
+%%
+empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
SecParams = empty_security_params(ConnectionEnd),
#{security_parameters => SecParams,
beast_mitigation => BeastMitigation,
@@ -424,7 +472,11 @@ empty_connection_state(ConnectionEnd, BeastMitigation) ->
mac_secret => undefined,
secure_renegotiation => undefined,
client_verify_data => undefined,
- server_verify_data => undefined
+ server_verify_data => undefined,
+ max_early_data_size => MaxEarlyDataSize,
+ max_fragment_length => undefined,
+ trial_decryption => false,
+ early_data_limit => false
}.
empty_security_params(ConnectionEnd = ?CLIENT) ->
@@ -451,19 +503,6 @@ record_protocol_role(client) ->
record_protocol_role(server) ->
?SERVER.
-initial_connection_state(ConnectionEnd, BeastMitigation) ->
- #{security_parameters =>
- initial_security_params(ConnectionEnd),
- sequence_number => 0,
- beast_mitigation => BeastMitigation,
- compression_state => undefined,
- cipher_state => undefined,
- mac_secret => undefined,
- secure_renegotiation => undefined,
- client_verify_data => undefined,
- server_verify_data => undefined
- }.
-
initial_security_params(ConnectionEnd) ->
SecParams = #security_parameters{connection_end = ConnectionEnd,
compression_algorithm = ?NULL},
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index c0555046c3..d142ecf6da 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -68,6 +68,7 @@
master_secret, % opaque 48
resumption_master_secret,
application_traffic_secret,
+ client_early_data_secret,
client_random, % opaque 32
server_random, % opaque 32
exportable % boolean
@@ -154,6 +155,8 @@
-define(MAX_COMPRESSED_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+1024)).
-define(MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+2048)).
-define(TLS13_MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+256)).
+-define(MAX_PADDING_LENGTH,256).
+-define(MAX_MAC_LENGTH,32).
%% -record(protocol_version, {
%% major, % unit 8
diff --git a/lib/ssl/src/ssl_server_session_cache.erl b/lib/ssl/src/ssl_server_session_cache.erl
new file mode 100644
index 0000000000..b444cf8865
--- /dev/null
+++ b/lib/ssl/src/ssl_server_session_cache.erl
@@ -0,0 +1,259 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Handle server side pre TLS-1.3 reuse session storage.
+%% This implements the RFC session reuse and not the session ticket extension
+%% that inspired the RFC session tickets of TLS-1.3.
+%%----------------------------------------------------------------------
+
+-module(ssl_server_session_cache).
+-behaviour(gen_server).
+
+-include_lib("kernel/include/logger.hrl").
+-include("ssl_handshake.hrl").
+-include("ssl_internal.hrl").
+
+%% API
+-export([start_link/2,
+ new_session_id/1,
+ register_session/2,
+ reuse_session/2
+ ]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2
+ %%code_change/3,
+ %%format_status/2
+ ]).
+
+-record(state, {store_cb,
+ lifetime,
+ db,
+ max,
+ session_order,
+ id_generator,
+ listner
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+-spec start_link(pid(), map()) -> {ok, Pid :: pid()} |
+ {error, Error :: {already_started, pid()}} |
+ {error, Error :: term()} |
+ ignore.
+start_link(ssl_unknown_listener = Listner, Map) ->
+ gen_server:start_link({local, Listner}, ?MODULE, [Listner, Map], []);
+start_link(Listner, Map) ->
+ gen_server:start_link(?MODULE, [Listner, Map], []).
+
+%%--------------------------------------------------------------------
+-spec new_session_id(Pid::pid()) -> ssl:session_id().
+%%
+%% Description: Creates a session id for the server.
+%%--------------------------------------------------------------------
+new_session_id(Pid) ->
+ case call(Pid, new_session_id) of
+ {no_server, _} ->
+ crypto:strong_rand_bytes(32);
+ Result ->
+ Result
+ end.
+
+%%--------------------------------------------------------------------
+-spec reuse_session(pid(), ssl:session_id()) -> #session{} | not_reusable.
+%%
+%% Description: Returns session to reuse
+%%--------------------------------------------------------------------
+reuse_session(Pid, SessionId) ->
+ case call(Pid, {reuse_session, SessionId}) of
+ {no_server, _} ->
+ not_reusable;
+ Result ->
+ Result
+ end.
+%%--------------------------------------------------------------------
+-spec register_session(pid(), term()) -> ok.
+%%
+%% Description: Makes a session available for reuse
+%%--------------------------------------------------------------------
+register_session(Pid, Session) ->
+ gen_server:cast(Pid, {register_session, Session}).
+
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+-spec init(Args :: term()) -> {ok, State :: term()}.
+init([Listner, #{lifetime := Lifetime,
+ session_cb := Cb,
+ session_cb_init_args := InitArgs,
+ max := Max
+ }]) ->
+ process_flag(trap_exit, true),
+ Monitor = monitor_listener(Listner),
+ DbRef = init(Cb, [{role, server} | InitArgs]),
+ State = #state{store_cb = Cb,
+ lifetime = Lifetime,
+ db = DbRef,
+ max = Max,
+ session_order = gb_trees:empty(),
+ id_generator = crypto:strong_rand_bytes(16),
+ listner = Monitor
+ },
+ {ok, State}.
+
+-spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) ->
+ {reply, Reply :: term(), NewState :: term()} .
+handle_call(new_session_id, _From, #state{id_generator = IdGen} = State) ->
+ SessionId = session_id(IdGen),
+ {reply, SessionId, State};
+handle_call({reuse_session, SessionId}, _From, #state{store_cb = Cb,
+ db = Store0,
+ lifetime = Lifetime,
+ session_order = Order0} = State0) ->
+ case lookup(Cb, Store0, SessionId) of
+ undefined ->
+ {reply, not_reusable, State0};
+ #session{internal_id = InId} = Session ->
+ case ssl_session:valid_session(Session, Lifetime) of
+ true ->
+ {reply, Session, State0};
+ false ->
+ Order = invalidate_session(Cb, Store0, Order0, SessionId, InId),
+ {reply, not_reusable, State0#state{session_order = Order}}
+ end
+ end.
+
+-spec handle_cast(Request :: term(), State :: term()) ->
+ {noreply, NewState :: term()}.
+handle_cast({register_session, #session{session_id = SessionId, time_stamp = TimeStamp} = Session0},
+ #state{store_cb = Cb,
+ db = Store0,
+ max = Max,
+ lifetime = Lifetime,
+ session_order = Order0}
+ = State0) ->
+ InternalId = {TimeStamp, erlang:unique_integer([monotonic])},
+ Session = Session0#session{internal_id = InternalId},
+ State = case size(Cb, Store0) of
+ Max ->
+ %% Throw away oldest session table may not grow larger than max
+ {_, OldSessId, Order1} = gb_trees:take_smallest(Order0),
+ Store1 = delete(Cb, Store0, OldSessId),
+ %% Insert new session
+ Order = gb_trees:insert(InternalId, SessionId, Order1),
+ Store = update(Cb, Store1, SessionId, Session),
+ Store#state{db = Store, session_order = Order};
+ Size when Size > 0 ->
+ {_, OldSessId, Order1} = gb_trees:take_smallest(Order0),
+ OldestSession = lookup(Cb, Store0, OldSessId),
+ case ssl_session:valid_session(OldestSession, Lifetime) of
+ true ->
+ Store = update(Cb, Store0, SessionId, Session#session{time_stamp = TimeStamp}),
+ State0#state{db = Store,
+ session_order = gb_trees:insert(InternalId, SessionId, Order0)};
+ false ->
+ %% Throw away oldest session as it is not valid anymore
+ Store1 = delete(Cb, Store0, OldSessId),
+ Store = update(Cb, Store1, SessionId, Session#session{time_stamp = TimeStamp}),
+ State0#state{db = Store,
+ session_order = gb_trees:insert(InternalId, SessionId, Order1)}
+ end;
+ 0 ->
+ Store = update(Cb, Store0, SessionId, Session#session{time_stamp = TimeStamp}),
+ State0#state{db = Store,
+ session_order = gb_trees:insert(InternalId, SessionId, Order0)}
+ end,
+ {noreply, State}.
+
+-spec handle_info(Info :: timeout() | term(), State :: term()) ->
+ {noreply, NewState :: term()}.
+handle_info({'DOWN', Monitor, _, _, _}, #state{listner = Monitor} = State) ->
+ {stop, normal, State};
+handle_info(_, State) ->
+ {noreply, State}.
+
+terminate(_, _) ->
+ ok.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+call(Pid, Msg) ->
+ try gen_server:call(Pid, Msg, infinity)
+ catch
+ exit:Reason ->
+ {no_server, Reason}
+ end.
+
+session_id(Key) ->
+ Unique1 = erlang:unique_integer(),
+ Unique2 = erlang:unique_integer(),
+ %% Obfuscate to avoid DoS attack possiblities
+ %% This id should be unpredictable an 32 bytes
+ %% and unique but have no other cryptographic requirements.
+ Bin1 = crypto:crypto_one_time(aes_128_ecb, Key, <<Unique1:128>>, true),
+ Bin2 = crypto:crypto_one_time(aes_128_ecb, Key, <<Unique2:128>>, true),
+ <<Bin1/binary, Bin2/binary>>.
+
+invalidate_session(Cb, Store, Order, SessionId, InternalId) ->
+ Cb:delete(Store, SessionId),
+ gb_trees:delete(InternalId, Order).
+
+init(Cb, Options) ->
+ Cb:init(Options).
+
+lookup(Cb, Cache, Key) ->
+ Cb:lookup(Cache, Key).
+
+update(ssl_server_session_cache_db = Cb, Cache, Key, Session) ->
+ Cb:update(Cache, Key, Session);
+update(Cb, Cache, Key, Session) ->
+ Cb:update(Cache, Key, Session),
+ Cache.
+
+delete(ssl_server_session_cache_db = Cb, Cache, Key) ->
+ Cb:delete(Cache, Key);
+delete(Cb, Cache, Key) ->
+ Cb:delete(Cache, Key),
+ Cache.
+
+size(Cb,Cache) ->
+ try Cb:size(Cache) of
+ Size ->
+ Size
+ catch
+ error:undef ->
+ Cb:foldl(fun(_, Acc) -> Acc + 1 end, 0, Cache)
+ end.
+
+monitor_listener(ssl_unknown_listener) ->
+ %% Backwards compatible Erlang node
+ %% global process.
+ undefined;
+monitor_listener(Listen) when is_port(Listen) ->
+ erlang:monitor(port, Listen).
diff --git a/lib/ssl/src/ssl_server_session_cache_db.erl b/lib/ssl/src/ssl_server_session_cache_db.erl
new file mode 100644
index 0000000000..b55d2e306f
--- /dev/null
+++ b/lib/ssl/src/ssl_server_session_cache_db.erl
@@ -0,0 +1,80 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Handle server side TLS-1.2 session storage
+%%----------------------------------------------------------------------
+
+-module(ssl_server_session_cache_db).
+
+-behaviour(ssl_session_cache_api).
+
+%% API
+-export([init/1,
+ terminate/1,
+ lookup/2,
+ update/3,
+ delete/2,
+ size/1]).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% Description: Return table reference.
+%%--------------------------------------------------------------------
+init(_Options) ->
+ gb_trees:empty().
+
+%%--------------------------------------------------------------------
+%% Description: Looks up a cach entry. Should be callable from any
+%% process.
+%%--------------------------------------------------------------------
+lookup(Cache, Key) ->
+ case gb_trees:lookup(Key, Cache) of
+ {value, Session} ->
+ Session;
+ none ->
+ undefined
+ end.
+
+%%--------------------------------------------------------------------
+%% Description: Caches a new session or updates a already cached one.
+%% Will only be called from the ssl_server_cache process.
+%%--------------------------------------------------------------------
+update(Cache, Key, Session) ->
+ gb_trees:insert(Key, Session, Cache).
+
+%%--------------------------------------------------------------------
+%% Description: Delets a cache entry.
+%% Will only be called from the ssl_server_cache process.
+%%--------------------------------------------------------------------
+delete(Cache, Key) ->
+ gb_trees:delete(Key, Cache).
+
+%%--------------------------------------------------------------
+%% Description: Returns the cache size
+%%--------------------------------------------------------------------
+size(Cache) ->
+ gb_trees:size(Cache).
+
+terminate(_) ->
+ ok.
diff --git a/lib/ssl/src/ssl_server_session_cache_sup.erl b/lib/ssl/src/ssl_server_session_cache_sup.erl
new file mode 100644
index 0000000000..88f068a319
--- /dev/null
+++ b/lib/ssl/src/ssl_server_session_cache_sup.erl
@@ -0,0 +1,65 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Supervisor for a listen options tracker
+%%----------------------------------------------------------------------
+-module(ssl_server_session_cache_sup).
+
+-behaviour(supervisor).
+
+-include("ssl_internal.hrl").
+
+%% API
+-export([start_link/0]).
+-export([start_child/1]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+start_child(Listner) ->
+ supervisor:start_child(?MODULE, [Listner | [ssl_config:pre_1_3_session_opts(server)]]).
+
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(_O) ->
+ RestartStrategy = simple_one_for_one,
+ MaxR = 3,
+ MaxT = 3600,
+
+ Name = undefined, % As simple_one_for_one is used.
+ StartFunc = {ssl_server_session_cache, start_link, []},
+ Restart = transient, % Should be restarted only on abnormal termination
+ Shutdown = 4000,
+ Modules = [ssl_server_session_cache],
+ Type = worker,
+
+ ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
+ {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
+
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index 3628a35aa2..ccc5c9ded7 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -30,11 +30,22 @@
-include("ssl_api.hrl").
%% Internal application API
--export([is_new/2, client_select_session/4, server_select_session/7, valid_session/2]).
+-export([is_new/2, client_select_session/4, server_select_session/5, valid_session/2, legacy_session_id/0]).
-type seconds() :: integer().
%%--------------------------------------------------------------------
+-spec legacy_session_id() -> ssl:session_id().
+%%
+%% Description: TLS-1.3 deprecates the session id but has a dummy
+%% value for it for protocol backwards-compatibility reasons.
+%% If now lower versions are configured this function can be called
+%% for a dummy value.
+%%--------------------------------------------------------------------
+legacy_session_id() ->
+ crypto:strong_rand_bytes(32).
+
+%%--------------------------------------------------------------------
-spec is_new(ssl:session_id(), ssl:session_id()) -> boolean().
%%
%% Description: Checks if the session id decided by the server is a
@@ -63,34 +74,28 @@ client_select_session({_, _, #{versions := Versions,
case Version of
{3, N} when N >= 4 ->
- NewSession#session{session_id = crypto:strong_rand_bytes(32)};
+ NewSession#session{session_id = legacy_session_id()};
_ ->
do_client_select_session(ClientInfo, Cache, CacheCb, NewSession)
end.
%%--------------------------------------------------------------------
--spec server_select_session(ssl_record:ssl_version(), inet:port_number(), binary(), map(),
- binary(),db_handle(), atom()) -> {binary(), #session{} | undefined}.
+-spec server_select_session(ssl_record:ssl_version(), pid(), binary(), map(),
+ binary()) -> {binary(), #session{} | undefined}.
%%
%% Description: Should be called by the server side to get an id
%% for the client hello message.
%%--------------------------------------------------------------------
-server_select_session({_, Minor}, Port, <<>>, _SslOpts, _Cert, _, _) when Minor >= 4 ->
- {ssl_manager:new_session_id(Port), undefined};
-server_select_session(_, Port, <<>>, _SslOpts, _Cert, _, _) ->
- {ssl_manager:new_session_id(Port), undefined};
-server_select_session(_, Port, SuggestedId, Options, Cert, Cache, CacheCb) ->
- LifeTime = case application:get_env(ssl, session_lifetime) of
- {ok, Time} when is_integer(Time) -> Time;
- _ -> ?'24H_in_sec'
- end,
- case is_resumable(SuggestedId, Port, Options,
- Cache, CacheCb, LifeTime, Cert)
+server_select_session(_, SessIdTracker, <<>>, _SslOpts, _Cert) ->
+ {ssl_server_session_cache:new_session_id(SessIdTracker), undefined};
+server_select_session(_, SessIdTracker, SuggestedId, Options, Cert) ->
+ case is_resumable(SuggestedId, SessIdTracker, Options, Cert)
of
{true, Resumed} ->
{SuggestedId, Resumed};
{false, undefined} ->
- {ssl_manager:new_session_id(Port), undefined}
+ Id = ssl_server_session_cache:new_session_id(SessIdTracker),
+ {Id, undefined}
end.
-spec valid_session(#session{}, seconds() | {invalidate_before, integer()}) -> boolean().
@@ -106,7 +111,15 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-
+do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, _, _, NewSession) when is_binary(SessionId) andalso
+ is_binary(SessionData) ->
+ try binary_to_term(SessionData, [safe]) of
+ Session ->
+ Session
+ catch
+ _:_ ->
+ NewSession#session{session_id = <<>>}
+ end;
do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, NewSession) when is_binary(SessionId)->
case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of
undefined ->
@@ -115,8 +128,8 @@ do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, Cac
Session
end;
do_client_select_session(ClientInfo,
- Cache, CacheCb, #session{own_certificate = OwnCert} = NewSession) ->
- case select_session(ClientInfo, Cache, CacheCb, OwnCert) of
+ Cache, CacheCb, #session{own_certificates = OwnCerts} = NewSession) ->
+ case select_session(ClientInfo, Cache, CacheCb, OwnCerts) of
no_session ->
NewSession#session{session_id = <<>>};
Session ->
@@ -126,37 +139,35 @@ do_client_select_session(ClientInfo,
select_session({_, _, #{reuse_sessions := Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true ->
%% If reuse_sessions == false | save a new session should be created
no_session;
-select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) ->
+select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCerts) ->
Sessions = CacheCb:select_session(Cache, {HostIP, Port}),
- select_session(Sessions, SslOpts, OwnCert).
+ select_session(Sessions, SslOpts, OwnCerts).
select_session([], _, _) ->
no_session;
-select_session(Sessions, #{ciphers := Ciphers}, OwnCert) ->
+select_session(Sessions, #{ciphers := Ciphers}, OwnCerts) ->
IsNotResumable =
fun(Session) ->
not (resumable(Session#session.is_resumable) andalso
lists:member(Session#session.cipher_suite, Ciphers)
- andalso (OwnCert == Session#session.own_certificate))
+ andalso (OwnCerts == Session#session.own_certificates))
end,
case lists:dropwhile(IsNotResumable, Sessions) of
[] -> no_session;
[Session | _] -> Session
end.
-is_resumable(_, _, #{reuse_sessions := false}, _, _, _, _) ->
+is_resumable(_, _, #{reuse_sessions := false}, _) ->
{false, undefined};
-is_resumable(SuggestedSessionId, Port, #{reuse_session := ReuseFun} = Options, Cache,
- CacheCb, SecondLifeTime, OwnCert) ->
- case CacheCb:lookup(Cache, {Port, SuggestedSessionId}) of
+is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = Options, OwnCert) ->
+ case ssl_server_session_cache:reuse_session(SessIdTracker, SuggestedSessionId) of
#session{cipher_suite = CipherSuite,
- own_certificate = SessionOwnCert,
+ own_certificates = [SessionOwnCert | _],
compression_method = Compression,
is_resumable = IsResumable,
peer_certificate = PeerCert} = Session ->
case resumable(IsResumable)
andalso (OwnCert == SessionOwnCert)
- andalso valid_session(Session, SecondLifeTime)
andalso reusable_options(Options, Session)
andalso ReuseFun(SuggestedSessionId, PeerCert,
Compression, CipherSuite)
@@ -164,7 +175,7 @@ is_resumable(SuggestedSessionId, Port, #{reuse_session := ReuseFun} = Options, C
true -> {true, Session};
false -> {false, undefined}
end;
- undefined ->
+ not_reusable ->
{false, undefined}
end.
diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl
index d438a9dafd..59fbd9b3c3 100644
--- a/lib/ssl/src/ssl_session_cache_api.erl
+++ b/lib/ssl/src/ssl_session_cache_api.erl
@@ -40,3 +40,5 @@
-callback foldl(fun(), term(), session_cache_ref()) -> term().
-callback select_session(session_cache_ref(), {ssl:host(), inet:port_number()} | inet:port_number()) -> [#session{}].
-callback size(session_cache_ref()) -> integer().
+
+-optional_callbacks([select_session/2, foldl/3]).
diff --git a/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl b/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl
new file mode 100644
index 0000000000..69169cca0d
--- /dev/null
+++ b/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl
@@ -0,0 +1,90 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Supervisor for a listen options tracker
+%%----------------------------------------------------------------------
+-module(ssl_upgrade_server_session_cache_sup).
+
+-behaviour(supervisor).
+
+-include("ssl_internal.hrl").
+
+%% API
+-export([start_link/0,
+ start_link_dist/0]).
+-export([start_child/1]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link() ->
+ supervisor:start_link({local, sup_name(normal)}, ?MODULE, []).
+
+start_link_dist() ->
+ supervisor:start_link({local, sup_name(dist)}, ?MODULE, []).
+
+start_child(Type) ->
+ SupName = sup_name(Type),
+ Children = supervisor:count_children(SupName),
+ Workers = proplists:get_value(workers, Children),
+ case Workers of
+ 0 ->
+ %% In case two upgrade servers are started very close to each other
+ %% only one will be able to grab the local name and we will use
+ %% that process for handling pre TLS-1.3 sessions for
+ %% servers with to us unknown listeners.
+ case supervisor:start_child(SupName, [ssl_unknown_listener, ssl_config:pre_1_3_session_opts(server)]) of
+ {error, {already_started, Child}} ->
+ {ok, Child};
+ {ok, _} = Return ->
+ Return
+ end;
+ 1 ->
+ [{_,Child,_, _}] = supervisor:which_children(SupName),
+ {ok, Child}
+ end.
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(_O) ->
+ RestartStrategy = simple_one_for_one,
+ MaxR = 3,
+ MaxT = 3600,
+
+ Name = undefined, % As simple_one_for_one is used.
+ StartFunc = {ssl_server_session_cache, start_link, []},
+ Restart = transient, % Should be restarted only on abnormal termination
+ Shutdown = 4000,
+ Modules = [ssl_server_session_cache],
+ Type = worker,
+
+ ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
+ {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
+
+sup_name(normal) ->
+ ?MODULE;
+sup_name(dist) ->
+ list_to_atom(atom_to_list(?MODULE) ++ "_dist").
diff --git a/lib/ssl/src/ssl_v3.erl b/lib/ssl/src/ssl_v3.erl
deleted file mode 100644
index 4eab60b440..0000000000
--- a/lib/ssl/src/ssl_v3.erl
+++ /dev/null
@@ -1,200 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2018. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%%----------------------------------------------------------------------
-%% Purpose: Handles sslv3 encryption.
-%%----------------------------------------------------------------------
-
--module(ssl_v3).
-
--include("ssl_cipher.hrl").
--include("ssl_internal.hrl").
--include("ssl_record.hrl"). % MD5 and SHA
-
--export([master_secret/3, finished/3, certificate_verify/3,
- mac_hash/6, setup_keys/7,
- suites/0]).
--compile(inline).
-
-%%====================================================================
-%% Internal application API
-%%====================================================================
-
--spec master_secret(binary(), binary(), binary()) -> binary().
-
-master_secret(PremasterSecret, ClientRandom, ServerRandom) ->
- %% draft-ietf-tls-ssl-version3-00 - 6.2.2
- %% key_block =
- %% MD5(master_secret + SHA(`A' + master_secret +
- %% ServerHello.random +
- %% ClientHello.random)) +
- %% MD5(master_secret + SHA(`BB' + master_secret +
- %% ServerHello.random +
- %% ClientHello.random)) +
- %% MD5(master_secret + SHA(`CCC' + master_secret +
- %% ServerHello.random +
- %% ClientHello.random)) + [...];
- Block = generate_keyblock(PremasterSecret, ClientRandom, ServerRandom, 48),
- Block.
-
--spec finished(client | server, binary(), [binary()]) -> binary().
-
-finished(Role, MasterSecret, Handshake) ->
- %% draft-ietf-tls-ssl-version3-00 - 5.6.9 Finished
- %% struct {
- %% opaque md5_hash[16];
- %% opaque sha_hash[20];
- %% } Finished;
- %%
- %% md5_hash MD5(master_secret + pad2 +
- %% MD5(handshake_messages + Sender +
- %% master_secret + pad1));
- %% sha_hash SHA(master_secret + pad2 +
- %% SHA(handshake_messages + Sender +
- %% master_secret + pad1));
- Sender = get_sender(Role),
- MD5 = handshake_hash(?MD5, MasterSecret, Sender, Handshake),
- SHA = handshake_hash(?SHA, MasterSecret, Sender, Handshake),
- <<MD5/binary, SHA/binary>>.
-
--spec certificate_verify(md5sha | sha, binary(), [binary()]) -> binary().
-
-certificate_verify(md5sha, MasterSecret, Handshake) ->
- %% md5_hash
- %% MD5(master_secret + pad_2 +
- %% MD5(handshake_messages + master_secret + pad_1));
- %% sha_hash
- %% SHA(master_secret + pad_2 +
- %% SHA(handshake_messages + master_secret + pad_1));
-
- MD5 = handshake_hash(?MD5, MasterSecret, undefined, Handshake),
- SHA = handshake_hash(?SHA, MasterSecret, undefined, Handshake),
- <<MD5/binary, SHA/binary>>;
-
-certificate_verify(sha, MasterSecret, Handshake) ->
- %% sha_hash
- %% SHA(master_secret + pad_2 +
- %% SHA(handshake_messages + master_secret + pad_1));
-
- handshake_hash(?SHA, MasterSecret, undefined, Handshake).
-
--spec mac_hash(integer(), binary(), integer(), integer(), integer(), binary()) -> binary().
-
-mac_hash(Method, Mac_write_secret, Seq_num, Type, Length, Fragment) ->
- %% draft-ietf-tls-ssl-version3-00 - 5.2.3.1
- %% hash(MAC_write_secret + pad_2 +
- %% hash(MAC_write_secret + pad_1 + seq_num +
- %% SSLCompressed.type + SSLCompressed.length +
- %% SSLCompressed.fragment));
- Mac = mac_hash(Method, Mac_write_secret,
- [<<?UINT64(Seq_num), ?BYTE(Type),
- ?UINT16(Length)>>, Fragment]),
- Mac.
-
--spec setup_keys(binary(), binary(), binary(),
- integer(), integer(), term(), integer()) ->
- {binary(), binary(), binary(),
- binary(), binary(), binary()}.
-
-setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) ->
- KeyBlock = generate_keyblock(MasterSecret, ServerRandom, ClientRandom,
- 2*(HS+KML+IVS)),
- %% draft-ietf-tls-ssl-version3-00 - 6.2.2
- %% The key_block is partitioned as follows.
- %% client_write_MAC_secret[CipherSpec.hash_size]
- %% server_write_MAC_secret[CipherSpec.hash_size]
- %% client_write_key[CipherSpec.key_material]
- %% server_write_key[CipherSpec.key_material]
- %% client_write_IV[CipherSpec.IV_size] /* non-export ciphers */
- %% server_write_IV[CipherSpec.IV_size] /* non-export ciphers */
- <<ClientWriteMacSecret:HS/binary, ServerWriteMacSecret:HS/binary,
- ClientWriteKey:KML/binary, ServerWriteKey:KML/binary,
- ClientIV:IVS/binary, ServerIV:IVS/binary>> = KeyBlock,
- {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
- ServerWriteKey, ClientIV, ServerIV}.
-
--spec suites() -> [ssl_cipher_format:cipher_suite()].
-
-suites() ->
- [
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
- ?TLS_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_RSA_WITH_AES_128_CBC_SHA
- ].
-
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-
-hash(?MD5, Data) ->
- crypto:hash(md5, Data);
-hash(?SHA, Data) ->
- crypto:hash(sha, Data).
-
-%%pad_1(?NULL) ->
-%% "";
-pad_1(?MD5) ->
- <<"666666666666666666666666666666666666666666666666">>;
-pad_1(?SHA) ->
- <<"6666666666666666666666666666666666666666">>.
-%%pad_2(?NULL) ->
-%% "";
-pad_2(?MD5) ->
- <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"
- "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>;
-pad_2(?SHA) ->
- <<"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"
- "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\">>.
-
-mac_hash(?NULL, _Secret, _Data) ->
- <<>>;
-mac_hash(Method, Secret, Data) ->
- InnerHash = hash(Method, [Secret, pad_1(Method), Data]),
- hash(Method, [Secret, pad_2(Method), InnerHash]).
-
-handshake_hash(Method, MasterSecret, undefined, Handshake) ->
- InnerHash = hash(Method, [Handshake, MasterSecret, pad_1(Method)]),
- hash(Method, [MasterSecret, pad_2(Method), InnerHash]);
-handshake_hash(Method, MasterSecret, Sender, Handshake) ->
- InnerHash = hash(Method, [Handshake, Sender, MasterSecret, pad_1(Method)]),
- hash(Method, [MasterSecret, pad_2(Method), InnerHash]).
-
-get_sender(client) -> "CLNT";
-get_sender(server) -> "SRVR".
-
-generate_keyblock(MasterSecret, ServerRandom, ClientRandom, WantedLength) ->
- gen(MasterSecret, [MasterSecret, ServerRandom, ClientRandom],
- WantedLength, 0, $A, 1, []).
-
-gen(_Secret, _All, Wanted, Len, _C, _N, Acc) when Wanted =< Len ->
- <<Block:Wanted/binary, _/binary>> = list_to_binary(lists:reverse(Acc)),
- Block;
-gen(Secret, All, Wanted, Len, C, N, Acc) ->
- Prefix = lists:duplicate(N, C),
- SHA = crypto:hash(sha, [Prefix, All]),
- MD5 = crypto:hash(md5, [Secret, SHA]),
- gen(Secret, All, Wanted, Len + 16, C+1, N+1, [MD5 | Acc]).
diff --git a/lib/ssl/src/tls_client_ticket_store.erl b/lib/ssl/src/tls_client_ticket_store.erl
index 70725ffc56..eb10adc9f1 100644
--- a/lib/ssl/src/tls_client_ticket_store.erl
+++ b/lib/ssl/src/tls_client_ticket_store.erl
@@ -25,10 +25,11 @@
-module(tls_client_ticket_store).
-behaviour(gen_server).
+-include("ssl_internal.hrl").
-include("tls_handshake_1_3.hrl").
%% API
--export([find_ticket/2,
+-export([find_ticket/5,
get_tickets/2,
lock_tickets/2,
remove_tickets/1,
@@ -51,7 +52,7 @@
-record(data, {
pos = undefined,
- hkdf,
+ cipher_suite,
sni,
psk,
timestamp,
@@ -69,9 +70,8 @@
start_link(Max, Lifetime) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Max, Lifetime], []).
-find_ticket(Pid, HashAlgos) ->
- %% TODO use also SNI when selecting tickets
- gen_server:call(?MODULE, {find_ticket, Pid, HashAlgos}, infinity).
+find_ticket(Pid, Ciphers, HashAlgos, SNI, EarlyDataSize) ->
+ gen_server:call(?MODULE, {find_ticket, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize}, infinity).
get_tickets(Pid, Keys) ->
gen_server:call(?MODULE, {get_tickets, Pid, Keys}, infinity).
@@ -86,8 +86,8 @@ remove_tickets([]) ->
remove_tickets(Keys) ->
gen_server:cast(?MODULE, {remove_tickets, Keys}).
-store_ticket(Ticket, HKDF, SNI, PSK) ->
- gen_server:call(?MODULE, {store_ticket, Ticket, HKDF, SNI, PSK}, infinity).
+store_ticket(Ticket, CipherSuite, SNI, PSK) ->
+ gen_server:call(?MODULE, {store_ticket, Ticket, CipherSuite, SNI, PSK}, infinity).
unlock_tickets(Pid, Keys) ->
gen_server:call(?MODULE, {unlock, Pid, Keys}, infinity).
@@ -108,8 +108,8 @@ init(Args) ->
-spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) ->
{reply, Reply :: term(), NewState :: term()} .
-handle_call({find_ticket, Pid, HashAlgos}, _From, State) ->
- Key = find_ticket(State, Pid, HashAlgos),
+handle_call({find_ticket, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize}, _From, State) ->
+ Key = do_find_ticket(State, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize),
{reply, Key, State};
handle_call({get_tickets, Pid, Keys}, _From, State) ->
Data = get_tickets(State, Pid, Keys),
@@ -117,8 +117,8 @@ handle_call({get_tickets, Pid, Keys}, _From, State) ->
handle_call({lock, Pid, Keys}, _From, State0) ->
State = lock_tickets(State0, Pid, Keys),
{reply, ok, State};
-handle_call({store_ticket, Ticket, HKDF, SNI, PSK}, _From, State0) ->
- State = store_ticket(State0, Ticket, HKDF, SNI, PSK),
+handle_call({store_ticket, Ticket, CipherSuite, SNI, PSK}, _From, State0) ->
+ State = store_ticket(State0, Ticket, CipherSuite, SNI, PSK),
{reply, ok, State};
handle_call({unlock, Pid, Keys}, _From, State0) ->
State = unlock_tickets(State0, Pid, Keys),
@@ -171,37 +171,84 @@ inital_state([Max, Lifetime]) ->
max = Max
}.
-
-find_ticket(_, _, []) ->
- undefined;
-find_ticket(#state{db = Db,
- lifetime = Lifetime} = State, Pid, [Hash|T]) ->
- case iterate_tickets(gb_trees:iterator(Db), Pid, Hash, Lifetime) of
- none ->
- find_ticket(State, Pid, T);
+do_find_ticket(Iter, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize) ->
+ do_find_ticket(Iter, Pid, Ciphers, HashAlgos, SNI, EarlyDataSize, []).
+%%
+do_find_ticket(_, _, _, [], _, _, []) ->
+ {undefined, undefined};
+do_find_ticket(_, _, _, [], _, _, Acc) ->
+ {undefined, last_elem(Acc)};
+do_find_ticket(#state{db = Db,
+ lifetime = Lifetime} = State, Pid, Ciphers, [Hash|T], SNI, EarlyDataSize, Acc) ->
+ case iterate_tickets(gb_trees:iterator(Db), Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize) of
+ {undefined, undefined} ->
+ do_find_ticket(State, Pid, Ciphers, T, SNI, EarlyDataSize, Acc);
+ {undefined, Key} ->
+ do_find_ticket(State, Pid, Ciphers, T, SNI, EarlyDataSize, [Key|Acc]);
Key ->
Key
end.
-
-iterate_tickets(Iter0, Pid, Hash, Lifetime) ->
+iterate_tickets(Iter0, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize) ->
+ iterate_tickets(Iter0, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, []).
+%%
+iterate_tickets(Iter0, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc) ->
case gb_trees:next(Iter0) of
- {Key, #data{hkdf = Hash,
+ {Key, #data{cipher_suite = {Cipher, Hash},
+ sni = TicketSNI,
+ ticket = #new_session_ticket{
+ extensions = Extensions},
timestamp = Timestamp,
lock = Lock}, Iter} when Lock =:= undefined orelse
Lock =:= Pid ->
+ MaxEarlyData = tls_handshake_1_3:get_max_early_data(Extensions),
Age = erlang:system_time(seconds) - Timestamp,
if Age < Lifetime ->
- Key;
+ case verify_ticket_sni(SNI, TicketSNI) of
+ match ->
+ case lists:member(Cipher, Ciphers) of
+ true ->
+ Front = last_elem(Acc),
+ %% 'Key' can be used with early_data as both
+ %% block cipher and hash algorithm matches.
+ %% 'Front' can only be used for session
+ %% resumption.
+ case EarlyDataSize =:= undefined orelse
+ EarlyDataSize =< MaxEarlyData of
+ true ->
+ {Key, Front};
+ false ->
+ %% 'Key' cannot be used for early_data as the data
+ %% to be sent exceeds the max limit for this ticket.
+ iterate_tickets(Iter, Pid, Ciphers, Hash, SNI,
+ Lifetime, EarlyDataSize,[Key|Acc])
+ end;
+ false ->
+ iterate_tickets(Iter, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, [Key|Acc])
+ end;
+ nomatch ->
+ iterate_tickets(Iter, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc)
+ end;
true ->
- iterate_tickets(Iter, Pid, Hash, Lifetime)
+ iterate_tickets(Iter, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc)
end;
{_, _, Iter} ->
- iterate_tickets(Iter, Pid, Hash, Lifetime);
+ iterate_tickets(Iter, Pid, Ciphers, Hash, SNI, Lifetime, EarlyDataSize, Acc);
none ->
- none
+ {undefined, last_elem(Acc)}
end.
+last_elem([_|_] = L) ->
+ lists:last(L);
+last_elem([]) ->
+ undefined.
+
+verify_ticket_sni(undefined, _) ->
+ match;
+verify_ticket_sni(SNI, SNI) ->
+ match;
+verify_ticket_sni(_, _) ->
+ nomatch.
%% Get tickets that are not locked by another process
get_tickets(State, Pid, Keys) ->
@@ -214,7 +261,7 @@ get_tickets(_, _, [], Acc) ->
get_tickets(#state{db = Db} = State, Pid, [Key|T], Acc) ->
try gb_trees:get(Key, Db) of
#data{pos = Pos,
- hkdf = HKDF,
+ cipher_suite = CipherSuite,
psk = PSK,
timestamp = Timestamp,
ticket = NewSessionTicket,
@@ -225,14 +272,23 @@ get_tickets(#state{db = Db} = State, Pid, [Key|T], Acc) ->
ticket_age_add = AgeAdd,
ticket_nonce = Nonce,
ticket = Ticket,
- extensions = _Extensions
+ extensions = Extensions
} = NewSessionTicket,
TicketAge = erlang:system_time(seconds) - Timestamp,
ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd),
Identity = #psk_identity{
identity = Ticket,
obfuscated_ticket_age = ObfuscatedTicketAge},
- get_tickets(State, Pid, T, [{Key, Pos, Identity, PSK, Nonce, HKDF}|Acc])
+ MaxEarlyData = tls_handshake_1_3:get_max_early_data(Extensions),
+ TicketData = #ticket_data{
+ key = Key,
+ pos = Pos,
+ identity = Identity,
+ psk = PSK,
+ nonce = Nonce,
+ cipher_suite = CipherSuite,
+ max_size = MaxEarlyData},
+ get_tickets(State, Pid, T, [TicketData|Acc])
catch
_:_ ->
get_tickets(State, Pid, T, Acc)
@@ -286,7 +342,7 @@ collect_invalid_tickets(Iter0, Lifetime, Acc) ->
end.
-store_ticket(#state{db = Db0, max = Max} = State, Ticket, HKDF, SNI, PSK) ->
+store_ticket(#state{db = Db0, max = Max} = State, Ticket, CipherSuite, SNI, PSK) ->
Timestamp = erlang:system_time(seconds),
Size = gb_trees:size(Db0),
Db1 = if Size =:= Max ->
@@ -296,7 +352,7 @@ store_ticket(#state{db = Db0, max = Max} = State, Ticket, HKDF, SNI, PSK) ->
end,
Key = {erlang:monotonic_time(), erlang:unique_integer([monotonic])},
Db = gb_trees:insert(Key,
- #data{hkdf = HKDF,
+ #data{cipher_suite = CipherSuite,
sni = SNI,
psk = PSK,
timestamp = Timestamp,
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 4eb03a26b1..8f25e5a3cd 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -19,376 +19,144 @@
%%
%%
%%----------------------------------------------------------------------
-%% Purpose: Handles an ssl connection, e.i. both the setup
-%% e.i. SSL-Handshake, SSL-Alert and SSL-Cipher protocols and delivering
-%% data to the application. All data on the connectinon is received and
-%% sent according to the SSL-record protocol.
+%% Purpose: TLS-1.0-TLS-1.2 FSM (* = optional)
+%% %%----------------------------------------------------------------------
+%% TLS Handshake protocol full Handshake
+%% Client Server
+%%
+%% ClientHello --------> Flight 1
+%% ServerHello \
+%% Certificate* \
+%% ServerKeyExchange* Flight 2
+%% CertificateRequest* /
+%% <-------- ServerHelloDone /
+%% Certificate* \
+%% ClientKeyExchange \
+%% CertificateVerify* Flight 3 part 1
+%% [ChangeCipherSpec] /
+%% Finished --------> / Flight 3 part 2
+%% [ChangeCipherSpec]
+%% <-------- Finished Flight 4
+%% Application Data <-------> Application Data
+%%
+%%
+%% TLS Handshake protocol abbreviated Handshake
+%% Client Server
+%%
+%% ClientHello --------> Abbrev Flight 1
+%% ServerHello Abbrev Flight 2 part 1
+%% [ChangeCipherSpec]
+%% <-------- Finished Abbrev Flight 2 part 2
+%% [ChangeCipherSpec]
+%% Finished --------> Abbrev Flight 3
+%% Application Data <-------> Application Data
+%%
+%%
+%%
+%% Start FSM ---> CONFIG_ERROR
+%% Send error to user
+%% | and shutdown
+%% |
+%% V
+%% INITIAL_HELLO
+%%
+%% | Send/Recv Flight 1
+%% |
+%% |
+%% USER_HELLO |
+%% <- Possibly let user provide V
+%% options after looking at hello ex -> HELLO
+%% | Send/Recv Flight 2 or Abbrev Flight 1 - Abbrev Flight 2 part 1
+%% |
+%% New session | Resumed session
+%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED
+%%
+%% <- Possibly Receive -- | |
+%% OCSP Stapel ------> | Flight 3 part 1 |
+%% | |
+%% V | Abbrev Flight 2 part 2 to Abbrev Flight 3
+%% CIPHER |
+%% | |
+%% | Fligth 3 part 2 to Flight 4 |
+%% | |
+%% V V
+%% ----------------------------------------------------
+%% |
+%% |
+%% V
+%% CONNECTION
+%% |
+%% | Renegotiaton
+%% V
+%% GO BACK TO HELLO
%%----------------------------------------------------------------------
-module(tls_connection).
-behaviour(gen_statem).
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
-include("tls_connection.hrl").
-include("tls_handshake.hrl").
--include("tls_handshake_1_3.hrl").
-include("ssl_alert.hrl").
-include("tls_record.hrl").
-include("ssl_cipher.hrl").
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
--include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
--include_lib("kernel/include/logger.hrl").
%% Internal application API
%% Setup
--export([start_fsm/8, start_link/8, init/1, pids/1]).
-
-%% State transition handling
--export([next_event/3, next_event/4,
- handle_protocol_record/3]).
+-export([init/1]).
-%% Handshake handling
--export([renegotiation/2, renegotiate/2, send_handshake/2,
- send_handshake_flight/1,
- queue_handshake/2, queue_change_cipher/2,
- reinit/1, reinit_handshake_data/1, select_sni_extension/1,
- empty_connection_state/2]).
-
-%% Alert and close handling
--export([send_alert/2, send_alert_in_connection/2,
- send_sync_alert/2,
- close/5, protocol_name/0]).
-
-%% Data handling
--export([socket/4, setopts/3, getopts/3]).
+-export([renegotiate/2]).
%% gen_statem state functions
--export([init/3, error/3, downgrade/3, %% Initiation and take down states
- hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
+-export([initial_hello/3,
+ config_error/3,
+ downgrade/3,
+ hello/3,
+ user_hello/3,
+ wait_ocsp_stapling/3,
+ certify/3,
+ cipher/3,
+ abbreviated/3,
connection/3]).
-%% TLS 1.3 state functions (server)
--export([start/3, %% common state with client
- negotiated/3,
- recvd_ch/3,
- wait_cert/3, %% common state with client
- wait_cv/3, %% common state with client
- wait_eoed/3,
- wait_finished/3, %% common state with client
- wait_flight2/3,
- connected/3 %% common state with client
- ]).
-%% TLS 1.3 state functions (client)
--export([wait_cert_cr/3,
- wait_ee/3,
- wait_sh/3
- ]).
+
%% gen_statem callbacks
--export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
+-export([callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
--export([encode_handshake/4, send_key_update/2, update_cipher_key/2]).
-
--define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]).
-
%%====================================================================
%% Internal application API
%%====================================================================
-%%====================================================================
-%% Setup
-%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts,
- User, {CbModule, _,_, _, _} = CbInfo,
- Timeout) ->
- try
- {ok, Sender} = tls_sender:start(),
- {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
- ssl_connection:handshake(SslSocket, Timeout)
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
- end;
-
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts,
- User, {CbModule, _,_, _, _} = CbInfo,
- Timeout) ->
- try
- {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]),
- {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket,
- Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
- ssl_connection:handshake(SslSocket, Timeout)
- catch
- error:{badmatch, {error, _} = Error} ->
- Error
- end.
-
-%%--------------------------------------------------------------------
--spec start_link(atom(), pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
- {ok, pid()} | ignore | {error, reason()}.
-%%
-%% Description: Creates a gen_statem process which calls Module:init/1 to
-%% initialize.
-%%--------------------------------------------------------------------
-start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}.
-
-init([Role, Sender, Host, Port, Socket, {#{erl_dist := ErlDist}, _, _} = Options, User, CbInfo]) ->
- process_flag(trap_exit, true),
- link(Sender),
- case ErlDist of
- true ->
- process_flag(priority, max);
- _ ->
- ok
- end,
+init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
State0 = #state{protocol_specific = Map} = initial_state(Role, Sender,
Host, Port, Socket, Options, User, CbInfo),
try
- State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0),
- initialize_tls_sender(State),
- gen_statem:enter_loop(?MODULE, [], init, State)
+ State1 = #state{static_env = #static_env{session_cache = Cache,
+ session_cache_cb = CacheCb
+ },
+ ssl_options = SslOptions,
+ session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
+ State = case Role of
+ client ->
+ Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0),
+ State1#state{session = Session};
+ server ->
+ State1
+ end,
+ tls_gen_connection:initialize_tls_sender(State),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch throw:Error ->
EState = State0#state{protocol_specific = Map#{error => Error}},
- gen_statem:enter_loop(?MODULE, [], error, EState)
- end.
-
-pids(#state{protocol_specific = #{sender := Sender}}) ->
- [self(), Sender].
-
-%%====================================================================
-%% State transition handling
-%%====================================================================
-next_record(_, #state{handshake_env =
- #handshake_env{unprocessed_handshake_events = N} = HsEnv}
- = State) when N > 0 ->
- {no_record, State#state{handshake_env =
- HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
-next_record(_, #state{protocol_buffers =
- #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts},
- connection_states = ConnectionStates,
- ssl_options = #{padding_check := Check}} = State) ->
- next_record(State, CipherTexts, ConnectionStates, Check);
-next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
- protocol_specific = #{active_n_toggle := true}
- } = State) ->
- %% If ssl application user is not reading data wait to activate socket
- flow_ctrl(State);
-
-next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
- protocol_specific = #{active_n_toggle := true}
- } = State) ->
- activate_socket(State);
-next_record(_, State) ->
- {no_record, State}.
-
-
-flow_ctrl(#state{user_data_buffer = {_,Size,_},
- socket_options = #socket_options{active = false},
- bytes_to_read = undefined} = State) when Size =/= 0 ->
- {no_record, State};
-flow_ctrl(#state{user_data_buffer = {_,Size,_},
- socket_options = #socket_options{active = false},
- bytes_to_read = 0} = State) when Size =/= 0 ->
- {no_record, State};
-flow_ctrl(#state{user_data_buffer = {_,Size,_},
- socket_options = #socket_options{active = false},
- bytes_to_read = BytesToRead} = State) when (Size >= BytesToRead) andalso
- (BytesToRead > 0) ->
- {no_record, State};
-flow_ctrl(State) ->
- activate_socket(State).
-
-
-activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
- static_env = #static_env{socket = Socket,
- close_tag = CloseTag,
- transport_cb = Transport}
- } = State) ->
- case tls_socket:setopts(Transport, Socket, [{active, N}]) of
- ok ->
- {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};
- _ ->
- self() ! {CloseTag, Socket},
- {no_record, State}
- end.
-
-%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one
-%%
-next_record(State, CipherTexts, ConnectionStates, Check) ->
- next_record(State, CipherTexts, ConnectionStates, Check, []).
-%%
-next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State,
- [CT|CipherTexts], ConnectionStates0, Check, Acc) ->
- case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
- {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
- case CipherTexts of
- [] ->
- %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
- %% from the accumulated fragments
- next_record_done(State, [], ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA,
- fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
- [_|_] ->
- next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
- end;
- {Record, ConnectionStates} when Acc =:= [] ->
- %% Singelton non-?APPLICATION_DATA record - deliver
- next_record_done(State, CipherTexts, ConnectionStates, Record);
- {_Record, _ConnectionStates_to_forget} ->
- %% Not ?APPLICATION_DATA but we have accumulated fragments
- %% -> build an ?APPLICATION_DATA record with concatenated fragments
- %% and forget about decrypting this record - we'll decrypt it again next time
- %% Will not work for stream ciphers
- next_record_done(State, [CT|CipherTexts], ConnectionStates0,
- #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))});
- #alert{} = Alert ->
- Alert
- end;
-next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
- [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) ->
- case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
- {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
- case CipherTexts of
- [] ->
- %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
- %% from the accumulated fragments
- next_record_done(State, [], ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA,
- fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
- [_|_] ->
- next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
- end;
- #alert{} = Alert ->
- Alert
- end;
-next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) ->
- next_record_done(State, CipherTexts, ConnectionStates,
- #ssl_tls{type = ?APPLICATION_DATA,
- fragment = iolist_to_binary(lists:reverse(Acc))});
-next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
- [CT|CipherTexts], ConnectionStates0, Check, []) ->
- case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
- {Record, ConnectionStates} ->
- %% Singelton non-?APPLICATION_DATA record - deliver
- next_record_done(State, CipherTexts, ConnectionStates, Record);
- #alert{} = Alert ->
- Alert
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
-next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) ->
- {Record,
- State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
- connection_states = ConnectionStates}}.
-
-next_event(StateName, Record, State) ->
- next_event(StateName, Record, State, []).
-%%
-next_event(StateName, no_record, #state{static_env = #static_env{role = Role}} = State0, Actions) ->
- case next_record(StateName, State0) of
- {no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
- {Record, State} ->
- next_event(StateName, Record, State, Actions);
- #alert{} = Alert ->
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
- {stop, {shutdown, own_alert}, State0}
- end;
-next_event(StateName, #ssl_tls{} = Record, State, Actions) ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
-next_event(StateName, #alert{} = Alert, State, Actions) ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}.
-
-%%% TLS record protocol level application data messages
-handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName,
- #state{start_or_recv_from = From,
- socket_options = #socket_options{active = false}} = State0) when From =/= undefined ->
- case ssl_connection:read_application_data(Data, State0) of
- {stop, _, _} = Stop->
- Stop;
- {Record, #state{start_or_recv_from = Caller} = State} ->
- TimerAction = case Caller of
- undefined -> %% Passive recv complete cancel timer
- [{{timeout, recv}, infinity, timeout}];
- _ ->
- []
- end,
- next_event(StateName, Record, State, TimerAction)
- end;
-handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) ->
- case ssl_connection:read_application_data(Data, State0) of
- {stop, _, _} = Stop->
- Stop;
- {Record, State} ->
- next_event(StateName, Record, State)
- end;
-%%% TLS record protocol level handshake messages
-handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
- StateName, #state{protocol_buffers =
- #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
- connection_env = #connection_env{negotiated_version = Version},
- static_env = #static_env{role = Role},
- ssl_options = Options} = State0) ->
- try
- %% Calculate the effective version that should be used when decoding an incoming handshake
- %% message.
- EffectiveVersion = effective_version(Version, Options, Role),
- {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options),
- State =
- State0#state{protocol_buffers =
- Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
- case Packets of
- [] ->
- assert_buffer_sanity(Buf, Options),
- next_event(StateName, no_record, State);
- _ ->
- Events = tls_handshake_events(Packets),
- case StateName of
- connection ->
- ssl_connection:hibernate_after(StateName, State, Events);
- _ ->
- HsEnv = State#state.handshake_env,
- {next_state, StateName,
- State#state{handshake_env =
- HsEnv#handshake_env{unprocessed_handshake_events
- = unprocessed_events(Events)}}, Events}
- end
- end
- catch throw:#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
- end;
-%%% TLS record protocol level change cipher messages
-handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
-%%% TLS record protocol level Alert messages
-handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try decode_alerts(EncAlerts) of
- Alerts = [_|_] ->
- handle_alerts(Alerts, {next_state, StateName, State});
- [] ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert),
- Version, StateName, State);
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State)
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error),
- Version, StateName, State)
-
- end;
-%% Ignore unknown TLS record level protocol messages
-handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, State, []}.
-%%====================================================================
-%% Handshake handling
-%%====================================================================
-renegotiation(Pid, WriteState) ->
- gen_statem:call(Pid, {user_renegotiate, WriteState}).
-
renegotiate(#state{static_env = #static_env{role = client},
handshake_env = HsEnv} = State, Actions) ->
%% Handle same way as if server requested
@@ -411,236 +179,26 @@ renegotiate(#state{static_env = #static_env{role = server,
State = State0#state{connection_states =
ConnectionStates,
handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
- next_event(hello, no_record, State, Actions).
-
-send_handshake(Handshake, State) ->
- send_handshake_flight(queue_handshake(Handshake, State)).
-
-queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
- connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = Flight0,
- ssl_options = #{log_level := LogLevel},
- connection_states = ConnectionStates0} = State0) ->
- {BinHandshake, ConnectionStates, Hist} =
- encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake),
- ssl_logger:debug(LogLevel, outbound, 'record', BinHandshake),
-
- State0#state{connection_states = ConnectionStates,
- handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
- flight_buffer = Flight0 ++ [BinHandshake]}.
-
-
-send_handshake_flight(#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- flight_buffer = Flight} = State0) ->
- tls_socket:send(Transport, Socket, Flight),
- {State0#state{flight_buffer = []}, []}.
-
-
-queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version},
- flight_buffer = Flight0,
- ssl_options = #{log_level := LogLevel},
- connection_states = ConnectionStates0} = State0) ->
- {BinChangeCipher, ConnectionStates} =
- encode_change_cipher(Msg, Version, ConnectionStates0),
- ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher),
- State0#state{connection_states = ConnectionStates,
- flight_buffer = Flight0 ++ [BinChangeCipher]}.
-
-reinit(#state{protocol_specific = #{sender := Sender},
- connection_env = #connection_env{negotiated_version = Version},
- connection_states = #{current_write := Write}} = State) ->
- tls_sender:update_connection_state(Sender, Write, Version),
- reinit_handshake_data(State).
-
-reinit_handshake_data(#state{handshake_env = HsEnv} =State) ->
- %% premaster_secret, public_key_info and tls_handshake_info
- %% are only needed during the handshake phase.
- %% To reduce memory foot print of a connection reinitialize them.
- State#state{
- handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
- public_key_info = undefined,
- premaster_secret = undefined}
- }.
-
-select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
- SNI;
-select_sni_extension(_) ->
- undefined.
-
-empty_connection_state(ConnectionEnd, BeastMitigation) ->
- ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation).
-
-%%====================================================================
-%% Alert and close handling
-%%====================================================================
-
-%%--------------------------------------------------------------------
--spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
- {iolist(), ssl_record:connection_states()}.
-%%
-%% Description: Encodes an alert
-%%--------------------------------------------------------------------
-encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
- tls_record:encode_alert_record(Alert, Version, ConnectionStates).
-
-send_alert(Alert, #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
- connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{log_level := LogLevel},
- connection_states = ConnectionStates0} = StateData0) ->
- {BinMsg, ConnectionStates} =
- encode_alert(Alert, Version, ConnectionStates0),
- tls_socket:send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
- StateData0#state{connection_states = ConnectionStates}.
-
-%% If an ALERT sent in the connection state, should cause the TLS
-%% connection to end, we need to synchronize with the tls_sender
-%% process so that the ALERT if possible (that is the tls_sender process is
-%% not blocked) is sent before the connection process terminates and
-%% thereby closes the transport socket.
-send_alert_in_connection(#alert{level = ?FATAL} = Alert, State) ->
- send_sync_alert(Alert, State);
-send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) ->
- send_sync_alert(Alert, State);
-send_alert_in_connection(Alert,
- #state{protocol_specific = #{sender := Sender}}) ->
- tls_sender:send_alert(Sender, Alert).
-send_sync_alert(
- Alert, #state{protocol_specific = #{sender := Sender}} = State) ->
- try tls_sender:send_and_ack_alert(Sender, Alert)
- catch
- _:_ ->
- throw({stop, {shutdown, own_alert}, State})
- end.
-
-%% User closes or recursive call!
-close({close, Timeout}, Socket, Transport = gen_tcp, _,_) ->
- tls_socket:setopts(Transport, Socket, [{active, false}]),
- Transport:shutdown(Socket, write),
- _ = Transport:recv(Socket, 0, Timeout),
- ok;
-%% Peer closed socket
-close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) ->
- close({close, 0}, Socket, Transport, ConnectionStates, Check);
-%% We generate fatal alert
-close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) ->
- %% Standard trick to try to make sure all
- %% data sent to the tcp port is really delivered to the
- %% peer application before tcp port is closed so that the peer will
- %% get the correct TLS alert message and not only a transport close.
- %% Will return when other side has closed or after timout millisec
- %% e.g. we do not want to hang if something goes wrong
- %% with the network but we want to maximise the odds that
- %% peer application gets all data sent on the tcp connection.
- close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
-close(downgrade, _,_,_,_) ->
- ok;
-%% Other
-close(_, Socket, Transport, _,_) ->
- tls_socket:close(Transport, Socket).
-protocol_name() ->
- "TLS".
-
-%%====================================================================
-%% Data handling
-%%====================================================================
-
-socket(Pids, Transport, Socket, Trackers) ->
- tls_socket:socket(Pids, Transport, Socket, ?MODULE, Trackers).
-
-setopts(Transport, Socket, Other) ->
- tls_socket:setopts(Transport, Socket, Other).
-
-getopts(Transport, Socket, Tag) ->
- tls_socket:getopts(Transport, Socket, Tag).
+ tls_gen_connection:next_event(hello, no_record, State, Actions).
%%--------------------------------------------------------------------
%% State functions
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec init(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
%%--------------------------------------------------------------------
-
-init({call, From}, {start, Timeout},
- #state{static_env = #static_env{role = client,
- host = Host,
- port = Port,
- transport_cb = Transport,
- socket = Socket,
- session_cache = Cache,
- session_cache_cb = CacheCb},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
- connection_env = CEnv,
- ssl_options = #{log_level := LogLevel,
- %% Use highest version in initial ClientHello.
- %% Versions is a descending list of supported versions.
- versions := [HelloVersion|_] = Versions,
- session_tickets := SessionTickets} = SslOpts,
- session = NewSession,
- connection_states = ConnectionStates0
- } = State0) ->
- KeyShare = maybe_generate_client_shares(SslOpts),
- Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession),
- %% Update UseTicket in case of automatic session resumption
- {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
- TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
- Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Session#session.session_id,
- Renegotiation,
- Session#session.own_certificate,
- KeyShare,
- TicketData),
-
- Handshake0 = ssl_handshake:init_handshake_history(),
-
- %% Update pre_shared_key extension with binders (TLS 1.3)
- Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion),
-
- {BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Hello1, HelloVersion, ConnectionStates0, Handshake0),
-
- tls_socket:send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1),
- ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
-
- %% RequestedVersion is used as the legacy record protocol version and shall be
- %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the
- %% lowest supported protocol version.
- %%
- %% negotiated_version is also used by the TLS 1.3 state machine and is set after
- %% ServerHello is processed.
- RequestedVersion = tls_record:hello_version(Versions),
- State = State1#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{
- negotiated_version = RequestedVersion},
- session = Session,
- handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake},
- start_or_recv_from = From,
- key_share = KeyShare},
- next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close}]);
-
-init(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
--spec error(gen_statem:event_type(),
+-spec config_error(gen_statem:event_type(),
{start, timeout()} | term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-error({call, From}, {start, _Timeout},
- #state{protocol_specific = #{error := Error}} = State) ->
- {stop_and_reply, {shutdown, normal},
- [{reply, From, {error, Error}}], State};
-
-error({call, _} = Call, Msg, State) ->
- gen_handshake(?FUNCTION_NAME, Call, Msg, State);
-error(_, _, _) ->
- {keep_state_and_data, [postpone]}.
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec hello(gen_statem:event_type(),
@@ -656,90 +214,67 @@ hello(internal, #client_hello{extensions = Extensions} = Hello,
handshake_env = HsEnv#handshake_env{hello = Hello}},
[{reply, From, {ok, Extensions}}]};
hello(internal, #server_hello{extensions = Extensions} = Hello,
- #state{ssl_options = #{handshake := hello},
+ #state{ssl_options = #{
+ handshake := hello},
handshake_env = HsEnv,
- start_or_recv_from = From} = State) ->
- {next_state, user_hello, State#state{start_or_recv_from = undefined,
- handshake_env = HsEnv#handshake_env{hello = Hello}},
- [{reply, From, {ok, Extensions}}]};
-
-hello(internal, #client_hello{client_version = ClientVersion} = Hello,
- #state{connection_states = ConnectionStates0,
- static_env = #static_env{
- port = Port,
- session_cache = Cache,
- session_cache_cb = CacheCb},
- handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
- renegotiation = {Renegotiation, _},
- negotiated_protocol = CurrentProtocol} = HsEnv,
- connection_env = CEnv,
- session = #session{own_certificate = Cert} = Session0,
- ssl_options = SslOpts} = State) ->
-
- case choose_tls_version(SslOpts, Hello) of
- 'tls_v1.3' ->
+ start_or_recv_from = From} = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined,
+ handshake_env = HsEnv#handshake_env{
+ hello = Hello}}, [{reply, From, {ok, Extensions}}]};
+hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{ssl_options = SslOpts0,
+ connection_env = CEnv} = State0) ->
+ case choose_tls_fsm(SslOpts0, Hello) of
+ tls_1_3_fsm ->
%% Continue in TLS 1.3 'start' state
- {next_state, start, State, [{next_event, internal, Hello}]};
- 'tls_v1.2' ->
- case tls_handshake:hello(Hello,
- SslOpts,
- {Port, Session0, Cache, CacheCb,
- ConnectionStates0, Cert, KeyExAlg},
- Renegotiation) of
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, ClientVersion, hello,
- State#state{connection_env = CEnv#connection_env{negotiated_version
- = ClientVersion}});
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
- gen_handshake(?FUNCTION_NAME,
- internal,
- {common_client_hello, Type, ServerHelloExt},
- State#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{negotiated_version = Version},
- handshake_env = HsEnv#handshake_env{
- hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
- negotiated_protocol = Protocol},
- session = Session
- })
+ {next_state, start, State0, [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]};
+ tls_1_0_to_1_2_fsm ->
+ case handle_client_hello(Hello, State0) of
+ {ServerHelloExt, Type, State} ->
+ {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]};
+ Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello,
+ State0#state{connection_env = CEnv#connection_env{negotiated_version
+ = ClientVersion}})
end
-
end;
-hello(internal, #server_hello{} = Hello,
+hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv,
static_env = #static_env{role = client},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
- ssl_options = SslOptions} = State) ->
- case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
- #alert{} = Alert -> %%TODO
- ssl_connection:handle_own_alert(Alert, ReqVersion, hello,
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState0,
+ renegotiation = {Renegotiation, _}} = HsEnv,
+ session = #session{session_id = OldId},
+ ssl_options = SslOptions} = State) ->
+ case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ReqVersion, hello,
State#state{connection_env =
- CEnv#connection_env{negotiated_version = ReqVersion}});
+ CEnv#connection_env{negotiated_version = ReqVersion}
+ });
%% Legacy TLS 1.2 and older
- {Version, NewId, ConnectionStates, ProtoExt, Protocol} ->
- ssl_connection:handle_session(Hello,
- Version, NewId, ConnectionStates, ProtoExt, Protocol, State);
+ {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} ->
+ tls_dtls_connection:handle_session(Hello,
+ Version, NewId, ConnectionStates, ProtoExt, Protocol,
+ State#state{
+ handshake_env = HsEnv#handshake_env{
+ ocsp_stapling_state = maps:merge(OcspState0,OcspState)}});
%% TLS 1.3
- {next_state, wait_sh, SelectedVersion} ->
+ {next_state, wait_sh, SelectedVersion, OcspState} ->
%% Continue in TLS 1.3 'wait_sh' state
{next_state, wait_sh,
- State#state{
- connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}},
- [{next_event, internal, Hello}]}
+ State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state = maps:merge(OcspState0,OcspState)},
+ connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}},
+ [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]}
end;
hello(info, Event, State) ->
- handle_info(Event, ?FUNCTION_NAME, State);
+ tls_gen_connection:handle_info(Event, ?FUNCTION_NAME, State);
hello(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
user_hello(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec abbreviated(gen_statem:event_type(), term(), #state{}) ->
@@ -748,7 +283,16 @@ user_hello(Type, Event, State) ->
abbreviated(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
abbreviated(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_ocsp_stapling(info, Event, State) ->
+ gen_info(Event, ?FUNCTION_NAME, State);
+wait_ocsp_stapling(Type, Event, State) ->
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec certify(gen_statem:event_type(), term(), #state{}) ->
@@ -757,7 +301,7 @@ abbreviated(Type, Event, State) ->
certify(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
certify(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
@@ -766,7 +310,7 @@ certify(Type, Event, State) ->
cipher(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -779,50 +323,16 @@ connection({call, From}, {user_renegotiate, WriteState},
#state{connection_states = ConnectionStates} = State) ->
{next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}},
[{next_event,{call, From}, renegotiate}]};
-connection({call, From},
- {close, {Pid, _Timeout}},
- #state{connection_env = #connection_env{terminated = closed} = CEnv,
- protocol_specific = PS} = State) ->
- {next_state, downgrade, State#state{connection_env =
- CEnv#connection_env{terminated = true,
- downgrade = {Pid, From}},
- protocol_specific = PS#{active_n_toggle => true,
- active_n => 1}
- },
- [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
-connection({call, From},
- {close,{Pid, Timeout}},
- #state{connection_states = ConnectionStates,
- protocol_specific = #{sender := Sender} = PS,
- connection_env = CEnv
- } = State0) ->
- case tls_sender:downgrade(Sender, Timeout) of
- {ok, Write} ->
- %% User downgrades connection
- %% When downgrading an TLS connection to a transport connection
- %% we must recive the close alert from the peer before releasing the
- %% transport socket.
- State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- State0#state{connection_states =
- ConnectionStates#{current_write => Write}}),
- {next_state, downgrade, State#state{connection_env =
- CEnv#connection_env{downgrade = {Pid, From},
- terminated = true},
- protocol_specific = PS#{active_n_toggle => true,
- active_n => 1}
- },
- [{timeout, Timeout, downgrade}]};
- {error, timeout} ->
- {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
- end;
connection(internal, #hello_request{},
#state{static_env = #static_env{role = client,
host = Host,
port = Port,
session_cache = Cache,
session_cache_cb = CacheCb},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, peer}},
- session = #session{own_certificate = Cert} = Session0,
+ handshake_env = #handshake_env{
+ renegotiation = {Renegotiation, peer},
+ ocsp_stapling_state = OcspState},
+ session = #session{own_certificates = OwnCerts} = Session0,
ssl_options = SslOpts,
protocol_specific = #{sender := Pid},
connection_states = ConnectionStates} = State0) ->
@@ -831,11 +341,13 @@ connection(internal, #hello_request{},
Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0),
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
Session#session.session_id,
- Renegotiation, Cert, undefined,
- undefined),
- {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write},
- session = Session}),
- next_event(hello, no_record, State, Actions)
+ Renegotiation, OwnCerts, undefined,
+ undefined, maps:get(ocsp_nonce, OcspState, undefined)),
+ {State, Actions} = tls_gen_connection:send_handshake(Hello,
+ State0#state{connection_states =
+ ConnectionStates#{current_write => Write},
+ session = Session}),
+ tls_gen_connection:next_event(hello, no_record, State, Actions)
catch
_:_ ->
{stop, {shutdown, sender_blocked}, State0}
@@ -844,16 +356,18 @@ connection(internal, #hello_request{},
#state{static_env = #static_env{role = client,
host = Host,
port = Port},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
- session = #session{own_certificate = Cert},
+ handshake_env = #handshake_env{
+ renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState},
+ session = #session{own_certificates = OwnCerts},
ssl_options = SslOpts,
connection_states = ConnectionStates} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- <<>>, Renegotiation, Cert, undefined,
- undefined),
+ <<>>, Renegotiation, OwnCerts, undefined,
+ undefined, maps:get(ocsp_nonce, OcspState, undefined)),
- {State, Actions} = send_handshake(Hello, State0),
- next_event(hello, no_record, State, Actions);
+ {State, Actions} = tls_gen_connection:send_handshake(Hello, State0),
+ tls_gen_connection:next_event(hello, no_record, State, Actions);
connection(internal, #client_hello{} = Hello,
#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{allow_renegotiate = true}= HsEnv,
@@ -867,36 +381,21 @@ connection(internal, #client_hello{} = Hello,
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
{ok, Write} = tls_sender:renegotiate(Sender),
- next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write},
- handshake_env = HsEnv#handshake_env{renegotiation = {true, peer},
- allow_renegotiate = false}
- },
- [{next_event, internal, Hello}]);
+ tls_gen_connection:next_event(hello, no_record,
+ State#state{connection_states = CS#{current_write => Write},
+ handshake_env = HsEnv#handshake_env{renegotiation = {true, peer},
+ allow_renegotiate = false}
+ },
+ [{next_event, internal, Hello}]);
connection(internal, #client_hello{},
#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{allow_renegotiate = false}} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
- send_alert_in_connection(Alert, State0),
- State = reinit_handshake_data(State0),
- next_event(?FUNCTION_NAME, no_record, State);
-
-connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
- %% TLS 1.3
- handle_new_session_ticket(NewSessionTicket, State),
- next_event(?FUNCTION_NAME, no_record, State);
-
-connection(internal, #key_update{} = KeyUpdate, State0) ->
- %% TLS 1.3
- case handle_key_update(KeyUpdate, State0) of
- {ok, State} ->
- next_event(?FUNCTION_NAME, no_record, State);
- {error, State, Alert} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, connection, State),
- next_event(?FUNCTION_NAME, no_record, State)
- end;
-
+ tls_gen_connection:send_alert_in_connection(Alert, State0),
+ State = tls_gen_connection:reinit_handshake_data(State0),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
connection(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ tls_dtls_connection:?FUNCTION_NAME(Type, Event, State).
%%--------------------------------------------------------------------
-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
@@ -918,120 +417,9 @@ downgrade(info, {CloseTag, Socket},
State) ->
{stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State};
downgrade(info, Info, State) ->
- handle_info(Info, ?FUNCTION_NAME, State);
+ tls_gen_connection:handle_info(Info, ?FUNCTION_NAME, State);
downgrade(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
-
-%%--------------------------------------------------------------------
-%% TLS 1.3 state functions
-%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
--spec start(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-start(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-start(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec negotiated(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-negotiated(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-negotiated(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec recvd_ch(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-recvd_ch(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-recvd_ch(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_cert(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_cert(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_cert(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_cv(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_cv(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_cv(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_eoed(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_eoed(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_eoed(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_finished(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_finished(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_finished(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_flight2(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_flight2(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_flight2(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec connected(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-connected(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-connected(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_cert_cr(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_cert_cr(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_cert_cr(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_ee(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_ee(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_ee(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec wait_sh(gen_statem:event_type(), term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-wait_sh(info, Event, State) ->
- gen_info_1_3(Event, ?FUNCTION_NAME, State);
-wait_sh(Type, Event, State) ->
- gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+ tls_dtls_connection:?FUNCTION_NAME(Type, Event, State).
%--------------------------------------------------------------------
%% gen_statem callbacks
@@ -1043,14 +431,14 @@ terminate({shutdown, {sender_died, Reason}}, _StateName,
#state{static_env = #static_env{socket = Socket,
transport_cb = Transport}}
= State) ->
- ssl_connection:handle_trusted_certs_db(State),
- close(Reason, Socket, Transport, undefined, undefined);
+ ssl_gen_statem:handle_trusted_certs_db(State),
+ tls_gen_connection:close(Reason, Socket, Transport, undefined, undefined);
terminate(Reason, StateName, State) ->
- catch ssl_connection:terminate(Reason, StateName, State),
+ catch ssl_gen_statem:terminate(Reason, StateName, State),
ensure_sender_terminate(Reason, State).
format_status(Type, Data) ->
- ssl_connection:format_status(Type, Data).
+ ssl_gen_statem:format_status(Type, Data).
code_change(_OldVsn, StateName, State, _) ->
{ok, StateName, State}.
@@ -1064,23 +452,18 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
erl_dist := IsErlDist,
client_renegotiation := ClientRenegotiation} = SSLOptions,
ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation),
- SessionCacheCb = case application:get_env(ssl, session_cb) of
- {ok, Cb} when is_atom(Cb) ->
- Cb;
- _ ->
- ssl_session_cache
- end,
- InternalActiveN = case application:get_env(ssl, internal_active_n) of
- {ok, N} when is_integer(N) andalso (not IsErlDist) ->
- N;
- _ ->
- ?INTERNAL_ACTIVE_N
+ #{session_cb := SessionCacheCb} = ssl_config:pre_1_3_session_opts(Role),
+ InternalActiveN = case IsErlDist of
+ true ->
+ ?INTERNAL_ACTIVE_N;
+ false ->
+ ssl_config:get_internal_active_n()
end,
UserMonitor = erlang:monitor(process, User),
InitStatEnv = #static_env{
role = Role,
transport_cb = CbModule,
- protocol_cb = ?MODULE,
+ protocol_cb = tls_gen_connection,
data_tag = DataTag,
close_tag = CloseTag,
error_tag = ErrorTag,
@@ -1101,7 +484,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
connection_env = #connection_env{user_application = {UserMonitor, User}},
socket_options = SocketOptions,
ssl_options = SSLOptions,
- session = #session{is_resumable = new},
+ session = #session{is_resumable = false},
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
user_data_buffer = {[],0,[]},
@@ -1113,287 +496,75 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
}
}.
-initialize_tls_sender(#state{static_env = #static_env{
- role = Role,
- transport_cb = Transport,
- socket = Socket,
- trackers = Trackers
- },
- connection_env = #connection_env{negotiated_version = Version},
- socket_options = SockOpts,
- ssl_options = #{renegotiate_at := RenegotiateAt,
- key_update_at := KeyUpdateAt,
- log_level := LogLevel},
- connection_states = #{current_write := ConnectionWriteState},
- protocol_specific = #{sender := Sender}}) ->
- Init = #{current_write => ConnectionWriteState,
- role => Role,
- socket => Socket,
- socket_options => SockOpts,
- trackers => Trackers,
- transport_cb => Transport,
- negotiated_version => Version,
- renegotiate_at => RenegotiateAt,
- key_update_at => KeyUpdateAt,
- log_level => LogLevel},
- tls_sender:initialize(Sender, Init).
-
-next_tls_record(Data, StateName,
- #state{protocol_buffers =
- #protocol_buffers{tls_record_buffer = Buf0,
- tls_cipher_texts = CT0} = Buffers,
- ssl_options = SslOpts} = State0) ->
- Versions =
- %% TLSPlaintext.legacy_record_version is ignored in TLS 1.3 and thus all
- %% record version are accepted when receiving initial ClientHello and
- %% ServerHello. This can happen in state 'hello' in case of all TLS
- %% versions and also in state 'start' when TLS 1.3 is negotiated.
- %% After the version is negotiated all subsequent TLS records shall have
- %% the proper legacy_record_version (= negotiated_version).
- %% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this
- %% point it is the same as the negotiated protocol version.
- %% TODO: Refactor state machine and introduce a record_protocol_version beside
- %% the negotiated_version.
- case StateName of
- State when State =:= hello orelse
- State =:= start ->
- [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS];
- _ ->
- State0#state.connection_env#connection_env.negotiated_version
- end,
- case tls_record:get_tls_records(Data, Versions, Buf0, SslOpts) of
- {Records, Buf1} ->
- CT1 = CT0 ++ Records,
- next_record(StateName, State0#state{protocol_buffers =
- Buffers#protocol_buffers{tls_record_buffer = Buf1,
- tls_cipher_texts = CT1}});
- #alert{} = Alert ->
- handle_record_alert(Alert, State0)
- end.
-
-
-handle_record_alert(Alert, _) ->
- Alert.
-
-tls_handshake_events(Packets) ->
- lists:map(fun(Packet) ->
- {next_event, internal, {handshake, Packet}}
- end, Packets).
-
-%% raw data from socket, upack records
-handle_info({Protocol, _, Data}, StateName,
- #state{static_env = #static_env{data_tag = Protocol},
- connection_env = #connection_env{negotiated_version = Version}} = State0) ->
- case next_tls_record(Data, StateName, State0) of
- {Record, State} ->
- next_event(StateName, Record, State);
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
- end;
-handle_info({PassiveTag, Socket}, StateName,
- #state{static_env = #static_env{socket = Socket,
- passive_tag = PassiveTag},
- start_or_recv_from = From,
- protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
- protocol_specific = PS
- } = State0) ->
- case (From =/= undefined) andalso (CTs == []) of
- true ->
- {Record, State} = activate_socket(State0#state{protocol_specific = PS#{active_n_toggle => true}}),
- next_event(StateName, Record, State);
- false ->
- next_event(StateName, no_record,
- State0#state{protocol_specific = PS#{active_n_toggle => true}})
- end;
-handle_info({CloseTag, Socket}, StateName,
- #state{static_env = #static_env{
- role = Role,
- host = Host,
- port = Port,
- socket = Socket,
- close_tag = CloseTag},
- handshake_env = #handshake_env{renegotiation = Type},
- connection_env = #connection_env{negotiated_version = Version},
- session = Session} = State) when StateName =/= connection ->
- ssl_connection:maybe_invalidate_session(Version, Type, Role, Host, Port, Session),
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop, {shutdown, transport_closed}, State};
-handle_info({CloseTag, Socket}, StateName,
- #state{static_env = #static_env{
- role = Role,
- socket = Socket,
- close_tag = CloseTag},
- socket_options = #socket_options{active = Active},
- protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
- user_data_buffer = {_,BufferSize,_},
- protocol_specific = PS} = State) ->
-
- %% Note that as of TLS 1.1,
- %% failure to properly close a connection no longer requires that a
- %% session not be resumed. This is a change from TLS 1.0 to conform
- %% with widespread implementation practice.
-
- case (Active == false) andalso ((CTs =/= []) or (BufferSize =/= 0)) of
- false ->
- %% As invalidate_sessions here causes performance issues,
- %% we will conform to the widespread implementation
- %% practice and go aginst the spec
- %% case Version of
- %% {3, N} when N >= 1 ->
- %% ok;
- %% _ ->
- %% invalidate_session(Role, Host, Port, Session)
- %% ok
- %% end,
- Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
- ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
- {stop, {shutdown, transport_closed}, State};
- true ->
- %% Fixes non-delivery of final TLS record in {active, once}.
- %% Basically allows the application the opportunity to set {active, once} again
- %% and then receive the final message. Set internal active_n to zero
- %% to ensure socket close message is sent if there is not enough data to deliver.
- next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}})
- end;
-handle_info({'EXIT', Sender, Reason}, _,
- #state{protocol_specific = #{sender := Sender}} = State) ->
- {stop, {shutdown, {sender_died, Reason}}, State};
-handle_info(Msg, StateName, State) ->
- ssl_connection:StateName(info, Msg, State, ?MODULE).
-
-handle_alerts([], Result) ->
- Result;
-handle_alerts(_, {stop, _, _} = Stop) ->
- Stop;
-handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts],
- {next_state, connection = StateName, #state{connection_env = CEnv,
- socket_options = #socket_options{active = false},
- user_data_buffer = {_,BufferSize,_},
- protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}} =
- State}) when (BufferSize =/= 0) orelse
- (CTs =/= []) ->
- {next_state, StateName, State#state{connection_env = CEnv#connection_env{terminated = true}}};
-handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
-handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
- handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)).
-
-encode_handshake(Handshake, Version, ConnectionStates0, Hist0) ->
- Frag = tls_handshake:encode_handshake(Handshake, Version),
- Hist = ssl_handshake:update_handshake_history(Hist0, Frag),
- {Encoded, ConnectionStates} =
- tls_record:encode_handshake(Frag, Version, ConnectionStates0),
- {Encoded, ConnectionStates, Hist}.
-
-encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
- tls_record:encode_change_cipher_spec(Version, ConnectionStates).
-
-decode_alerts(Bin) ->
- ssl_alert:decode(Bin).
-
-gen_handshake(StateName, Type, Event,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try ssl_connection:StateName(Type, Event, State, ?MODULE) of
- Result ->
- Result
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data),
- Version, StateName, State)
- end.
-
-
-gen_handshake_1_3(StateName, Type, Event,
- #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try tls_connection_1_3:StateName(Type, Event, State, ?MODULE) of
- Result ->
- Result
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data),
- Version, StateName, State)
+handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) ->
+ case tls_dtls_connection:handle_sni_extension(State0, Hello) of
+ #state{connection_states = ConnectionStates0,
+ static_env = #static_env{trackers = Trackers},
+ handshake_env = #handshake_env{
+ kex_algorithm = KeyExAlg,
+ renegotiation = {Renegotiation, _},
+ negotiated_protocol = CurrentProtocol,
+ sni_guided_cert_selection = SNICertSelection} = HsEnv,
+ connection_env = CEnv,
+ session = #session{own_certificates = OwnCerts} = Session0,
+ ssl_options = SslOpts} = State ->
+ SessionTracker = proplists:get_value(session_id_tracker, Trackers),
+ case tls_handshake:hello(Hello,
+ SslOpts,
+ {SessionTracker, Session0,
+ ConnectionStates0, OwnCerts, KeyExAlg},
+ Renegotiation) of
+ #alert{} = Alert ->
+ Alert;
+ {Version, {Type, Session},
+ ConnectionStates, Protocol0, ServerHelloExt0, HashSign} ->
+ Protocol = case Protocol0 of
+ undefined -> CurrentProtocol;
+ _ -> Protocol0
+ end,
+ ServerHelloExt =
+ case SNICertSelection of
+ true ->
+ ServerHelloExt0#{sni => #sni{hostname = ""}};
+ false ->
+ ServerHelloExt0
+ end,
+ {ServerHelloExt, Type, State#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = Version},
+ handshake_env = HsEnv#handshake_env{
+ hashsign_algorithm = HashSign,
+ client_hello_version = ClientVersion,
+ negotiated_protocol = Protocol},
+ session = Session
+ }}
+ end;
+ #alert{} = Alert ->
+ Alert
end.
gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
+ try tls_gen_connection:handle_info(Event, StateName, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
malformed_data),
Version, StateName, State)
end;
gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
+ try tls_gen_connection:handle_info(Event, StateName, State) of
Result ->
Result
catch
_:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data),
Version, StateName, State)
end.
-
-gen_info_1_3(Event, connected = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
- Result ->
- Result
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
- malformed_data),
- Version, StateName, State)
- end;
-
-gen_info_1_3(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
- try handle_info(Event, StateName, State) of
- Result ->
- Result
- catch
- _:_ ->
- ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data),
- Version, StateName, State)
- end.
-
-unprocessed_events(Events) ->
- %% The first handshake event will be processed immediately
- %% as it is entered first in the event queue and
- %% when it is processed there will be length(Events)-1
- %% handshake events left to process before we should
- %% process more TLS-records received on the socket.
- erlang:length(Events)-1.
-
-
-assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>,
- #{max_handshake_size := Max}) when
- Length =< Max ->
- case size(Rest) of
- N when N < Length ->
- true;
- N when N > Length ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- too_big_handshake_data));
- _ ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data))
- end;
-assert_buffer_sanity(Bin, _) ->
- case size(Bin) of
- N when N < 3 ->
- true;
- _ ->
- throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- malformed_handshake_data))
- end.
-
ensure_sender_terminate(downgrade, _) ->
ok; %% Do not terminate sender during downgrade phase
ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) ->
@@ -1407,125 +578,17 @@ ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) ->
end,
spawn(Kill).
-maybe_generate_client_shares(#{versions := [Version|_],
- supported_groups :=
- #supported_groups{
- supported_groups = [Group|_]}})
- when Version =:= {3,4} ->
- %% Generate only key_share entry for the most preferred group
- ssl_cipher:generate_client_shares([Group]);
-maybe_generate_client_shares(_) ->
- undefined.
-
-choose_tls_version(#{versions := Versions},
- #client_hello{
- extensions = #{client_hello_versions :=
- #client_hello_versions{versions = ClientVersions}
- }
- }) ->
+choose_tls_fsm(#{versions := Versions},
+ #client_hello{
+ extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }
+ }) ->
case ssl_handshake:select_supported_version(ClientVersions, Versions) of
{3,4} ->
- 'tls_v1.3';
+ tls_1_3_fsm;
_Else ->
- 'tls_v1.2'
+ tls_1_0_to_1_2_fsm
end;
-choose_tls_version(_, _) ->
- 'tls_v1.2'.
-
-
-%% Special version handling for TLS 1.3 clients:
-%% In the shared state 'init' negotiated_version is set to requested version and
-%% that is expected by the legacy part of the state machine. However, in order to
-%% be able to process new TLS 1.3 extensions, the effective version shall be set
-%% {3,4}.
-%% When highest supported version is {3,4} the negotiated version is set to {3,3}.
-effective_version({3,3} , #{versions := [Version|_]}, client) when Version >= {3,4} ->
- Version;
-%% Use highest supported version during startup (TLS server, all versions).
-effective_version(undefined, #{versions := [Version|_]}, _) ->
- Version;
-%% Use negotiated version in all other cases.
-effective_version(Version, _, _) ->
- Version.
-
-
-handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) ->
- ok;
-handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
- #state{connection_states = ConnectionStates,
- ssl_options = #{session_tickets := SessionTickets,
- server_name_indication := SNI},
- connection_env = #connection_env{user_application = {_, User}}})
- when SessionTickets =:= manual ->
- #{security_parameters := SecParams} =
- ssl_record:current_connection_state(ConnectionStates, read),
- HKDF = SecParams#security_parameters.prf_algorithm,
- RMS = SecParams#security_parameters.resumption_master_secret,
- PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
- send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK);
-handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
- #state{connection_states = ConnectionStates,
- ssl_options = #{session_tickets := SessionTickets,
- server_name_indication := SNI}})
- when SessionTickets =:= auto ->
- #{security_parameters := SecParams} =
- ssl_record:current_connection_state(ConnectionStates, read),
- HKDF = SecParams#security_parameters.prf_algorithm,
- RMS = SecParams#security_parameters.resumption_master_secret,
- PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
- tls_client_ticket_store:store_ticket(NewSessionTicket, HKDF, SNI, PSK).
-
-
-handle_key_update(#key_update{request_update = update_not_requested}, State0) ->
- %% Update read key in connection
- {ok, update_cipher_key(current_read, State0)};
-handle_key_update(#key_update{request_update = update_requested},
- #state{protocol_specific = #{sender := Sender}} = State0) ->
- %% Update read key in connection
- State1 = update_cipher_key(current_read, State0),
- %% Send key_update and update sender's write key
- case send_key_update(Sender, update_not_requested) of
- ok ->
- {ok, State1};
- {error, Reason} ->
- {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)}
- end.
-
-
-update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) ->
- CS = update_cipher_key(ConnStateName, CS0),
- State0#state{connection_states = CS};
-update_cipher_key(ConnStateName, CS0) ->
- #{security_parameters := SecParams0,
- cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0),
- HKDF = SecParams0#security_parameters.prf_algorithm,
- CipherSuite = SecParams0#security_parameters.cipher_suite,
- ApplicationTrafficSecret0 = SecParams0#security_parameters.application_traffic_secret,
- ApplicationTrafficSecret = tls_v1:update_traffic_secret(HKDF, ApplicationTrafficSecret0),
-
- %% Calculate traffic keys
- #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, Cipher, ApplicationTrafficSecret),
-
- SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret},
- CipherState = CipherState0#cipher_state{key = Key, iv = IV},
- ConnState = ConnState0#{security_parameters => SecParams,
- cipher_state => CipherState,
- sequence_number => 0},
- CS0#{ConnStateName => ConnState}.
-
-
-send_key_update(Sender, Type) ->
- KeyUpdate = tls_handshake_1_3:key_update(Type),
- tls_sender:send_post_handshake(Sender, KeyUpdate).
-
-
-%% Send ticket data to user as opaque binary
-send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK) ->
- Timestamp = erlang:system_time(seconds),
- TicketData = #{hkdf => HKDF,
- sni => SNI,
- psk => PSK,
- timestamp => Timestamp,
- ticket => NewSessionTicket},
- User ! {ssl, session_ticket, {SNI, erlang:term_to_binary(TicketData)}}.
+choose_tls_fsm(_, _) ->
+ tls_1_0_to_1_2_fsm.
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index b440d65706..6c81933c23 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -20,9 +20,16 @@
%%
%%----------------------------------------------------------------------
-%% Purpose: TODO
+%% Purpose: TLS-1.3 FSM
%%----------------------------------------------------------------------
-
+%% INITIAL_HELLO
+%% Client send
+%% first ClientHello
+%% | ---> CONFIG_ERROR
+%% | Send error to user
+%% | and shutdown
+%% |
+%% V
%% RFC 8446
%% A.1. Client
%%
@@ -104,27 +111,163 @@
-include("ssl_alert.hrl").
-include("ssl_connection.hrl").
+-include("tls_connection.hrl").
-include("tls_handshake.hrl").
-include("tls_handshake_1_3.hrl").
-%% gen_statem helper functions
--export([start/4,
- negotiated/4,
- wait_cert/4,
- wait_cv/4,
- wait_finished/4,
- wait_sh/4,
- wait_ee/4,
- wait_cert_cr/4
+-behaviour(gen_statem).
+
+%% gen_statem callbacks
+-export([init/1, callback_mode/0, terminate/3, code_change/4, format_status/2]).
+
+%% gen_statem state functions
+-export([initial_hello/3,
+ config_error/3,
+ user_hello/3,
+ start/3,
+ negotiated/3,
+ wait_cert/3,
+ wait_cv/3,
+ wait_finished/3,
+ wait_sh/3,
+ wait_ee/3,
+ wait_cert_cr/3,
+ wait_eoed/3,
+ connection/3,
+ downgrade/3
]).
+%% Internal API
+-export([setopts/3,
+ getopts/3,
+ send_key_update/2,
+ update_cipher_key/2]).
+
+%%====================================================================
+%% Internal API
+%%====================================================================
+
+setopts(Transport, Socket, Other) ->
+ tls_socket:setopts(Transport, Socket, Other).
+
+getopts(Transport, Socket, Tag) ->
+ tls_socket:getopts(Transport, Socket, Tag).
+
+send_key_update(Sender, Type) ->
+ KeyUpdate = tls_handshake_1_3:key_update(Type),
+ tls_sender:send_post_handshake(Sender, KeyUpdate).
+
+update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) ->
+ CS = update_cipher_key(ConnStateName, CS0),
+ State0#state{connection_states = CS};
+update_cipher_key(ConnStateName, CS0) ->
+ #{security_parameters := SecParams0,
+ cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0),
+ HKDF = SecParams0#security_parameters.prf_algorithm,
+ CipherSuite = SecParams0#security_parameters.cipher_suite,
+ ApplicationTrafficSecret0 = SecParams0#security_parameters.application_traffic_secret,
+ ApplicationTrafficSecret = tls_v1:update_traffic_secret(HKDF, ApplicationTrafficSecret0),
+
+ %% Calculate traffic keys
+ KeyLength = tls_v1:key_length(CipherSuite),
+ {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, KeyLength, ApplicationTrafficSecret),
+
+ SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret},
+ CipherState = CipherState0#cipher_state{key = Key, iv = IV},
+ ConnState = ConnState0#{security_parameters => SecParams,
+ cipher_state => CipherState,
+ sequence_number => 0},
+ CS0#{ConnStateName => ConnState}.
+
+%--------------------------------------------------------------------
+%% gen_statem callbacks
+%%--------------------------------------------------------------------
+callback_mode() ->
+ state_functions.
-start(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-start(internal, #client_hello{} = Hello, State0, _Module) ->
+init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
+ State0 = #state{protocol_specific = Map} = initial_state(Role, Sender,
+ Host, Port, Socket, Options, User, CbInfo),
+ try
+ State = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
+ tls_gen_connection:initialize_tls_sender(State),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
+ catch throw:Error ->
+ EState = State0#state{protocol_specific = Map#{error => Error}},
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
+ end.
+
+terminate({shutdown, {sender_died, Reason}}, _StateName,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport}}
+ = State) ->
+ ssl_gen_statem:handle_trusted_certs_db(State),
+ tls_gen_connection:close(Reason, Socket, Transport, undefined, undefined);
+terminate(Reason, StateName, State) ->
+ ssl_gen_statem:terminate(Reason, StateName, State).
+
+format_status(Type, Data) ->
+ ssl_gen_statem:format_status(Type, Data).
+
+code_change(_OldVsn, StateName, State, _) ->
+ {ok, StateName, State}.
+
+%--------------------------------------------------------------------
+%% state callbacks
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+
+user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}}
+ = State) ->
+ gen_statem:reply(From, ok),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
+ Version, ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{hello = Hello},
+ ssl_options = Options0} = State0) ->
+ Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
+ State = ssl_gen_statem:ssl_config(Options, Role, State0),
+ Next = case Role of
+ client ->
+ wait_sh;
+ server ->
+ start
+ end,
+ {next_state, Next, State#state{start_or_recv_from = From},
+ [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
+user_hello(_, _, _) ->
+ {keep_state_and_data, [postpone]}.
+
+start(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+start(internal, #client_hello{extensions = Extensions} = Hello,
+ #state{ssl_options = #{handshake := hello},
+ start_or_recv_from = From,
+ handshake_env = HsEnv} = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined,
+ handshake_env = HsEnv#handshake_env{
+ hello = Hello}}, [{reply, From, {ok, Extensions}}]};
+start(internal, #client_hello{} = Hello, State0) ->
case tls_handshake_1_3:do_start(Hello, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0);
{State, start} ->
{next_state, start, State, []};
{State, negotiated} ->
@@ -132,114 +275,299 @@ start(internal, #client_hello{} = Hello, State0, _Module) ->
{State, negotiated, PSK} -> %% Session Resumption with PSK
{next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]}
end;
-start(internal, #server_hello{} = ServerHello, State0, _Module) ->
+start(internal, #server_hello{extensions = Extensions} = ServerHello,
+ #state{ssl_options = #{handshake := hello},
+ handshake_env = HsEnv,
+ start_or_recv_from = From}
+ = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined,
+ handshake_env = HsEnv#handshake_env{
+ hello = ServerHello}}, [{reply, From, {ok, Extensions}}]};
+start(internal, #server_hello{} = ServerHello, State0) ->
case tls_handshake_1_3:do_start(ServerHello, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0);
{State, NextState} ->
{next_state, NextState, State, []}
end;
-start(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
+start(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+start(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-negotiated(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-negotiated(internal, Message, State0, _Module) ->
+negotiated(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+negotiated(internal, Message, State0) ->
case tls_handshake_1_3:do_negotiated(Message, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, negotiated, State0);
{State, NextState} ->
{next_state, NextState, State, []}
- end.
-
+ end;
+negotiated(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State).
-wait_cert(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cert(internal, #change_cipher_spec{}, State0) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0);
wait_cert(internal,
- #certificate_1_3{} = Certificate, State0, _Module) ->
+ #certificate_1_3{} = Certificate, State0) ->
case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
{#alert{} = Alert, State} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert, State);
{State, NextState} ->
- tls_connection:next_event(NextState, no_record, State)
+ tls_gen_connection:next_event(NextState, no_record, State)
end;
-wait_cert(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_cert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_cv(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cv(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
wait_cv(internal,
- #certificate_verify_1_3{} = CertificateVerify, State0, _Module) ->
+ #certificate_verify_1_3{} = CertificateVerify, State0) ->
case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of
{#alert{} = Alert, State} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cv, State);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cv, State);
{State, NextState} ->
- tls_connection:next_event(NextState, no_record, State)
+ tls_gen_connection:next_event(NextState, no_record, State)
end;
-wait_cv(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-
+wait_cv(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cv(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_finished(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_finished(internal, #change_cipher_spec{}, State0) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State0);
wait_finished(internal,
- #finished{} = Finished, State0, Module) ->
+ #finished{} = Finished, State0) ->
case tls_handshake_1_3:do_wait_finished(Finished, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, finished, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, finished, State0);
State1 ->
- {Record, State} = ssl_connection:prepare_connection(State1, Module),
- tls_connection:next_event(connection, Record, State,
+ {Record, State} = ssl_gen_statem:prepare_connection(State1, tls_gen_connection),
+ tls_gen_connection:next_event(connection, Record, State,
[{{timeout, handshake}, cancel}])
end;
-wait_finished(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_finished(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_finished(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_sh(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-wait_sh(internal, #server_hello{} = Hello, State0, _Module) ->
+wait_sh(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_sh(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #{handshake := hello},
+ start_or_recv_from = From,
+ handshake_env = HsEnv} = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined,
+ handshake_env = HsEnv#handshake_env{
+ hello = Hello}}, [{reply, From, {ok, Extensions}}]};
+wait_sh(internal, #server_hello{} = Hello, State0) ->
case tls_handshake_1_3:do_wait_sh(Hello, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_sh, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_sh, State0);
{State1, start, ServerHello} ->
%% hello_retry_request: go to start
{next_state, start, State1, [{next_event, internal, ServerHello}]};
{State1, wait_ee} ->
- tls_connection:next_event(wait_ee, no_record, State1)
+ tls_gen_connection:next_event(wait_ee, no_record, State1)
end;
-wait_sh(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_sh(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_sh(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_ee(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-wait_ee(internal, #encrypted_extensions{} = EE, State0, _Module) ->
+wait_ee(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_ee(internal, #encrypted_extensions{} = EE, State0) ->
case tls_handshake_1_3:do_wait_ee(EE, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_ee, State0);
{State1, NextState} ->
- tls_connection:next_event(NextState, no_record, State1)
+ tls_gen_connection:next_event(NextState, no_record, State1)
end;
-wait_ee(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_ee(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_ee(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-wait_cert_cr(internal, #change_cipher_spec{}, State, _Module) ->
- tls_connection:next_event(?FUNCTION_NAME, no_record, State);
-wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0, _Module) ->
+wait_cert_cr(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) ->
case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of
{#alert{} = Alert, State} ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State);
{State1, NextState} ->
- tls_connection:next_event(NextState, no_record, State1)
+ tls_gen_connection:next_event(NextState, no_record, State1)
end;
-wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0, _Module) ->
+wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0) ->
case tls_handshake_1_3:do_wait_cert_cr(CertificateRequest, State0) of
#alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0);
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0);
{State1, NextState} ->
- tls_connection:next_event(NextState, no_record, State1)
+ tls_gen_connection:next_event(NextState, no_record, State1)
end;
-wait_cert_cr(Type, Msg, State, Connection) ->
- ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+wait_cert_cr(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cert_cr(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+wait_eoed(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+wait_eoed(internal, #end_of_early_data{} = EOED, State0) ->
+ case tls_handshake_1_3:do_wait_eoed(EOED, State0) of
+ {#alert{} = Alert, State} ->
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_eoed, State);
+ {State1, NextState} ->
+ tls_gen_connection:next_event(NextState, no_record, State1)
+ end;
+wait_eoed(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_eoed(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
+ handle_new_session_ticket(NewSessionTicket, State),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+
+connection(internal, #key_update{} = KeyUpdate, State0) ->
+ case handle_key_update(KeyUpdate, State0) of
+ {ok, State} ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+ {error, State, Alert} ->
+ ssl_gen_statem:handle_own_alert(Alert, {3,4}, connection, State),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State)
+ end;
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+downgrade(Type, Event, State) ->
+ tls_connection:?FUNCTION_NAME(Type, Event, State).
+
+%--------------------------------------------------------------------
+%% internal functions
+%%--------------------------------------------------------------------
+initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User,
+ {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
+ #{erl_dist := IsErlDist,
+ client_renegotiation := ClientRenegotiation} = SSLOptions,
+ MaxEarlyDataSize = init_max_early_data_size(Role),
+ ConnectionStates = tls_record:init_connection_states(Role, disabled, MaxEarlyDataSize),
+ InternalActiveN = case application:get_env(ssl, internal_active_n) of
+ {ok, N} when is_integer(N) andalso (not IsErlDist) ->
+ N;
+ _ ->
+ ?INTERNAL_ACTIVE_N
+ end,
+ UserMonitor = erlang:monitor(process, User),
+ InitStatEnv = #static_env{
+ role = Role,
+ transport_cb = CbModule,
+ protocol_cb = tls_gen_connection,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ passive_tag = PassiveTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ trackers = Trackers
+ },
+ #state{
+ static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first},
+ allow_renegotiate = ClientRenegotiation
+ },
+ connection_env = #connection_env{user_application = {UserMonitor, User}},
+ socket_options = SocketOptions,
+ ssl_options = SSLOptions,
+ session = #session{is_resumable = false,
+ session_id = ssl_session:legacy_session_id()},
+ connection_states = ConnectionStates,
+ protocol_buffers = #protocol_buffers{},
+ user_data_buffer = {[],0,[]},
+ start_or_recv_from = undefined,
+ flight_buffer = [],
+ protocol_specific = #{sender => Sender,
+ active_n => InternalActiveN,
+ active_n_toggle => true
+ }
+ }.
+
+handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) ->
+ ok;
+handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{session_tickets := SessionTickets,
+ server_name_indication := SNI},
+ connection_env = #connection_env{user_application = {_, User}}})
+ when SessionTickets =:= manual ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ CipherSuite = SecParams#security_parameters.cipher_suite,
+ #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ send_ticket_data(User, NewSessionTicket, {Cipher, HKDF}, SNI, PSK);
+handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{session_tickets := SessionTickets,
+ server_name_indication := SNI}})
+ when SessionTickets =:= auto ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ CipherSuite = SecParams#security_parameters.cipher_suite,
+ #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ tls_client_ticket_store:store_ticket(NewSessionTicket, {Cipher, HKDF}, SNI, PSK).
+
+send_ticket_data(User, NewSessionTicket, CipherSuite, SNI, PSK) ->
+ Timestamp = erlang:system_time(seconds),
+ TicketData = #{cipher_suite => CipherSuite,
+ sni => SNI,
+ psk => PSK,
+ timestamp => Timestamp,
+ ticket => NewSessionTicket},
+ User ! {ssl, session_ticket, TicketData}.
+
+handle_key_update(#key_update{request_update = update_not_requested}, State0) ->
+ %% Update read key in connection
+ {ok, update_cipher_key(current_read, State0)};
+handle_key_update(#key_update{request_update = update_requested},
+ #state{protocol_specific = #{sender := Sender}} = State0) ->
+ %% Update read key in connection
+ State1 = update_cipher_key(current_read, State0),
+ %% Send key_update and update sender's write key
+ case send_key_update(Sender, update_not_requested) of
+ ok ->
+ {ok, State1};
+ {error, Reason} ->
+ {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)}
+ end.
+
+init_max_early_data_size(client) ->
+ %% Disable trial decryption on the client side
+ %% Servers do trial decryption of max_early_data bytes of plain text.
+ %% Setting it to 0 means that a decryption error will result in an Alert.
+ 0;
+init_max_early_data_size(server) ->
+ ssl_config:get_max_early_data_size().
+
diff --git a/lib/ssl/src/tls_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl
index d5b228dc94..b7f80ad524 100644
--- a/lib/ssl/src/tls_connection_sup.erl
+++ b/lib/ssl/src/tls_connection_sup.erl
@@ -40,13 +40,13 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
start_link_dist() ->
- supervisor:start_link({local, ssl_connection_sup_dist}, ?MODULE, []).
+ supervisor:start_link({local, tls_dist_connection_sup}, ?MODULE, []).
start_child(Args) ->
supervisor:start_child(?MODULE, Args).
start_child_dist(Args) ->
- supervisor:start_child(ssl_connection_sup_dist, Args).
+ supervisor:start_child(tls_dist_connection_sup, Args).
%%%=========================================================================
%%% Supervisor callback
@@ -57,10 +57,10 @@ init(_O) ->
MaxT = 3600,
Name = undefined, % As simple_one_for_one is used.
- StartFunc = {tls_connection, start_link, []},
+ StartFunc = {ssl_gen_statem, start_link, []},
Restart = temporary, % E.g. should not be restarted
Shutdown = 4000,
- Modules = [tls_connection, ssl_connection],
+ Modules = [ssl_gen_statem, tls_connection, tls_connection_1_3],
Type = worker,
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
diff --git a/lib/ssl/src/tls_dist_server_sup.erl b/lib/ssl/src/tls_dist_server_sup.erl
new file mode 100644
index 0000000000..96603a7495
--- /dev/null
+++ b/lib/ssl/src/tls_dist_server_sup.erl
@@ -0,0 +1,89 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2021. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(tls_dist_server_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+
+init([]) ->
+ ListenTracker = listen_options_tracker_child_spec(),
+ SessionTracker = tls_server_session_child_spec(),
+ Pre_1_3SessionTracker = ssl_server_session_child_spec(),
+
+ {ok, {{one_for_all, 10, 3600}, [ListenTracker,
+ SessionTracker,
+ Pre_1_3SessionTracker
+ ]}}.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+%% Handles emulated options so that they inherited by the accept
+%% socket, even when setopts is performed on the listen socket
+listen_options_tracker_child_spec() ->
+ Name = dist_tls_socket,
+ StartFunc = {ssl_listen_tracker_sup, start_link_dist, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_listen_tracker_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+tls_server_session_child_spec() ->
+ Name = dist_tls_server_session_ticket,
+ StartFunc = {tls_server_session_ticket_sup, start_link_dist, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_server_session_ticket_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+ssl_server_session_child_spec() ->
+ Name = dist_ssl_server_session_cache_sup,
+ StartFunc = {ssl_upgrade_server_session_cache_sup, start_link_dist, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_server_session_cache_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
diff --git a/lib/ssl/src/tls_dist_sup.erl b/lib/ssl/src/tls_dist_sup.erl
new file mode 100644
index 0000000000..54e0a6a514
--- /dev/null
+++ b/lib/ssl/src/tls_dist_sup.erl
@@ -0,0 +1,75 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2021. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(tls_dist_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+
+init([]) ->
+
+ TLSConnetionSup = tls_connection_child_spec(),
+ ServerInstanceSup = server_instance_child_spec(),
+
+ {ok, {{one_for_one, 10, 3600}, [TLSConnetionSup,
+ ServerInstanceSup
+ ]}}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+tls_connection_child_spec() ->
+ Name = dist_tls_connection,
+ StartFunc = {tls_connection_sup, start_link_dist, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_connection_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+server_instance_child_spec() ->
+ Name = dist_tls_server_sup,
+ StartFunc = {tls_dist_server_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_dist_server_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl
new file mode 100644
index 0000000000..c27feadfcf
--- /dev/null
+++ b/lib/ssl/src/tls_dtls_connection.erl
@@ -0,0 +1,1687 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Common handling of a TLS/SSL/DTLS connection, see also
+%% tls_connection.erl and dtls_connection.erl
+%%----------------------------------------------------------------------
+
+-module(tls_dtls_connection).
+
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+-include("ssl_api.hrl").
+-include("ssl_connection.hrl").
+-include("ssl_handshake.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_srp.hrl").
+
+%% TLS-1.0 to TLS-1.2 Specific User Events
+-export([renegotiation/1, renegotiation/2, prf/5]).
+
+%% Data handling. Note renegotiation is replaced by sesion key update mechanism in TLS-1.3
+-export([internal_renegotiation/2]).
+
+%% Help functions for tls|dtls_connection.erl
+-export([handle_session/7,
+ handle_sni_extension/2]).
+
+%% General state handlingfor TLS-1.0 to TLS-1.2 and gen_handshake that wrapps
+%% handling of common state handling for handshake messages for error handling
+-export([hello/3,
+ user_hello/3,
+ abbreviated/3,
+ certify/3,
+ wait_ocsp_stapling/3,
+ cipher/3,
+ connection/3,
+ downgrade/3,
+ gen_handshake/4]).
+
+%%--------------------------------------------------------------------
+-spec internal_renegotiation(pid(), ssl_record:connection_states()) ->
+ ok.
+%%
+%% Description: Starts a renegotiation of the ssl session.
+%%--------------------------------------------------------------------
+internal_renegotiation(ConnectionPid, #{current_write := WriteState}) ->
+ gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}).
+
+%%====================================================================
+%% User events
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec renegotiation(pid()) -> ok | {error, reason()}.
+%%
+%% Description: Starts a renegotiation of the ssl session.
+%%--------------------------------------------------------------------
+renegotiation(ConnectionPid) ->
+ ssl_gen_statem:call(ConnectionPid, renegotiate).
+
+renegotiation(Pid, WriteState) ->
+ ssl_gen_statem:call(Pid, {user_renegotiate, WriteState}).
+
+%%--------------------------------------------------------------------
+-spec prf(pid(), binary() | 'master_secret', binary(),
+ [binary() | ssl:prf_random()], non_neg_integer()) ->
+ {ok, binary()} | {error, reason()} | {'EXIT', term()}.
+%%
+%% Description: use a ssl sessions TLS PRF to generate key material
+%%--------------------------------------------------------------------
+prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
+ ssl_gen_statem:call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}).
+
+%%====================================================================
+%% Help functions for tls|dtls_connection.erl
+%%====================================================================
+%%--------------------------------------------------------------------
+-spec handle_session(#server_hello{}, ssl_record:ssl_version(),
+ binary(), ssl_record:connection_states(), _,_, #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+handle_session(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression},
+ Version, NewId, ConnectionStates, ProtoExt, Protocol0,
+ #state{session = #session{session_id = OldId},
+ handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv,
+ connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) ->
+ #{key_exchange := KeyAlgorithm} =
+ ssl_cipher_format:suite_bin_to_map(CipherSuite),
+
+ PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
+
+ {ExpectNPN, Protocol} = case Protocol0 of
+ undefined ->
+
+ {false, CurrentProtocol};
+ _ ->
+ {ProtoExt =:= npn, Protocol0}
+ end,
+
+ State = State0#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm,
+ premaster_secret = PremasterSecret,
+ expecting_next_protocol_negotiation = ExpectNPN,
+ negotiated_protocol = Protocol},
+ connection_env = CEnv#connection_env{negotiated_version = Version}},
+
+ case ssl_session:is_new(OldId, NewId) of
+ true ->
+ handle_new_session(NewId, CipherSuite, Compression,
+ State#state{connection_states = ConnectionStates});
+ false ->
+ handle_resumed_session(NewId,
+ State#state{connection_states = ConnectionStates})
+ end.
+
+
+%%====================================================================
+%% gen_statem general state functions with connection cb argument
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec hello(gen_statem:event_type(),
+ #hello_request{} | #server_hello{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+hello({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+hello(internal, {common_client_hello, Type, ServerHelloExt}, State) ->
+ do_server_hello(Type, ServerHelloExt, State);
+hello(info, Msg, State) ->
+ handle_info(Msg, ?FUNCTION_NAME, State);
+hello(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+hello(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec user_hello(gen_statem:event_type(),
+ #hello_request{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ gen_statem:reply(From, ok),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
+ Version, ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{hello = Hello},
+ ssl_options = Options0} = State0) ->
+ Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
+ State = ssl_gen_statem:ssl_config(Options, Role, State0),
+ {next_state, hello, State#state{start_or_recv_from = From},
+ [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
+user_hello(_, _, _) ->
+ {keep_state_and_data, [postpone]}.
+
+%%--------------------------------------------------------------------
+-spec abbreviated(gen_statem:event_type(),
+ #hello_request{} | #finished{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+abbreviated({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+abbreviated(internal, #finished{verify_data = Data} = Finished,
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ expecting_finished = true} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret},
+ connection_states = ConnectionStates0} =
+ State0) ->
+ case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client,
+ get_current_prf(ConnectionStates0, write),
+ MasterSecret, Hist) of
+ verified ->
+ ConnectionStates =
+ ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
+ {Record, State} =
+ ssl_gen_statem:prepare_connection(State0#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{expecting_finished = false}},
+ Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ end;
+abbreviated(internal, #finished{verify_data = Data} = Finished,
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{tls_handshake_history = Hist0},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret},
+ connection_states = ConnectionStates0} = State0) ->
+ case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
+ get_pending_prf(ConnectionStates0, write),
+ MasterSecret, Hist0) of
+ verified ->
+ ConnectionStates1 =
+ ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
+ {#state{handshake_env = HsEnv} = State1, Actions} =
+ finalize_handshake(State0#state{connection_states = ConnectionStates1},
+ ?FUNCTION_NAME, Connection),
+ {Record, State} =
+ ssl_gen_statem:prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}},
+ Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ end;
+%% only allowed to send next_protocol message after change cipher spec
+%% & before finished message and it is not allowed during renegotiation
+abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State) ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
+ expecting_next_protocol_negotiation = false}});
+abbreviated(internal,
+ #change_cipher_spec{type = <<1>>},
+ #state{static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0,
+ handshake_env = HsEnv} = State) ->
+ ConnectionStates1 =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
+ ConnectionStates1,
+ handshake_env = HsEnv#handshake_env{expecting_finished = true}});
+abbreviated(info, Msg, State) ->
+ handle_info(Msg, ?FUNCTION_NAME, State);
+abbreviated(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+abbreviated(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_ocsp_stapling(gen_statem:event_type(),
+ #certificate{} | #certificate_status{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_ocsp_stapling(internal, #certificate{}, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
+ %% Postpone message, should be handled in certify after receiving staple message
+ Connection:next_event(?FUNCTION_NAME, no_record, State, [{postpone, true}]);
+%% Receive OCSP staple message
+wait_ocsp_stapling(internal, #certificate_status{} = CertStatus,
+ #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState} = HsEnv} = State) ->
+ Connection:next_event(certify, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state =
+ OcspState#{ocsp_expect => stapled,
+ ocsp_response => CertStatus}}});
+%% Server did not send OCSP staple message
+wait_ocsp_stapling(internal, Msg, #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState} = HsEnv} = State)
+ when is_record(Msg, server_key_exchange) orelse
+ is_record(Msg, hello_request) orelse
+ is_record(Msg, certificate_request) orelse
+ is_record(Msg, server_hello_done) orelse
+ is_record(Msg, client_key_exchange) ->
+ Connection:next_event(certify, no_record,
+ State#state{handshake_env =
+ HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}},
+ [{postpone, true}]);
+wait_ocsp_stapling(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+wait_ocsp_stapling(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec certify(gen_statem:event_type(),
+ #hello_request{} | #certificate{} | #server_key_exchange{} |
+ #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+certify({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+certify(info, Msg, State) ->
+ handle_info(Msg, ?FUNCTION_NAME, State);
+certify(internal, #certificate{asn1_certificates = []},
+ #state{static_env = #static_env{role = server},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{verify := verify_peer,
+ fail_if_no_peer_cert := true}} =
+ State) ->
+ Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided),
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+certify(internal, #certificate{asn1_certificates = []},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ ssl_options = #{verify := verify_peer,
+ fail_if_no_peer_cert := false}} =
+ State0) ->
+ Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
+certify(internal, #certificate{},
+ #state{static_env = #static_env{role = server},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{verify := verify_none}} =
+ State) ->
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+certify(internal, #certificate{},
+ #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = #{ocsp_expect := staple}}} = State) ->
+ Connection:next_event(wait_ocsp_stapling, no_record, State, [{postpone, true}]);
+certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert,
+ #state{static_env = #static_env{
+ role = Role,
+ host = Host,
+ protocol_cb = Connection,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbInfo},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = #{ocsp_expect := Status} = OcspState},
+ connection_env = #connection_env{
+ negotiated_version = Version},
+ ssl_options = Opts} = State) when Status =/= staple ->
+ OcspInfo = ocsp_info(OcspState, Opts, Peer),
+ case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
+ Opts, CRLDbInfo, Role, Host,
+ ensure_tls(Version), OcspInfo) of
+ {PeerCert, PublicKeyInfo} ->
+ handle_peer_cert(Role, PeerCert, PublicKeyInfo,
+ State#state{client_certificate_requested = false}, Connection, []);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ end;
+certify(internal, #server_key_exchange{exchange_keys = Keys},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ public_key_info = PubKeyInfo} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session,
+ connection_states = ConnectionStates} = State)
+ when KexAlg == dhe_dss;
+ KexAlg == dhe_rsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdhe_ecdsa;
+ KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == rsa_psk;
+ KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
+
+ Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)),
+
+ %% Use negotiated value if TLS-1.2 otherwhise return default
+ HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)),
+
+ case is_anonymous(KexAlg) of
+ true ->
+ calculate_secret(Params#server_key_params.params,
+ State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection);
+ false ->
+ case ssl_handshake:verify_server_key(Params, HashSign,
+ ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of
+ true ->
+ calculate_secret(Params#server_key_params.params,
+ State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign},
+ session = session_handle_params(Params#server_key_params.params, Session)},
+ Connection);
+ false ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR),
+ Version, ?FUNCTION_NAME, State)
+ end
+ end;
+certify(internal, #certificate_request{},
+ #state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg},
+ connection_env = #connection_env{negotiated_version = Version}} = State)
+ when KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == rsa_psk;
+ KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
+ Version, ?FUNCTION_NAME, State);
+certify(internal, #certificate_request{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ session = #session{own_certificates = undefined}} = State) ->
+ %% The client does not have a certificate and will send an empty reply, the server may fail
+ %% or accept the connection by its own preference. No signature algorihms needed as there is
+ %% no certificate to verify.
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true});
+certify(internal, #certificate_request{} = CertRequest,
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ handshake_env = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{own_certificates = [Cert|_]},
+ ssl_options = #{signature_algs := SupportedHashSigns}} = State) ->
+ case ssl_handshake:select_hashsign(CertRequest, Cert,
+ SupportedHashSigns, ssl:tls_version(Version)) of
+ #alert {} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+ NegotiatedHashSign ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{client_certificate_requested = true,
+ handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}})
+ end;
+%% PSK and RSA_PSK might bypass the Server-Key-Exchange
+certify(internal, #server_hello_done{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ session = #session{master_secret = undefined},
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ premaster_secret = undefined,
+ server_psk_identity = PSKIdentity} = HsEnv,
+ ssl_options = #{user_lookup_fun := PSKLookup}} = State0)
+ when KexAlg == psk ->
+ case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
+ PremasterSecret ->
+ State = master_secret(PremasterSecret,
+ State0#state{handshake_env =
+ HsEnv#handshake_env{premaster_secret = PremasterSecret}}),
+ client_certify_and_key_exchange(State, Connection)
+ end;
+certify(internal, #server_hello_done{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version,
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ premaster_secret = undefined,
+ server_psk_identity = PSKIdentity} = HsEnv,
+ session = #session{master_secret = undefined},
+ ssl_options = #{user_lookup_fun := PSKLookup}} = State0)
+ when KexAlg == rsa_psk ->
+ Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>,
+ case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup,
+ RSAPremasterSecret) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
+ PremasterSecret ->
+ State = master_secret(PremasterSecret,
+ State0#state{handshake_env =
+ HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}),
+ client_certify_and_key_exchange(State, Connection)
+ end;
+%% Master secret was determined with help of server-key exchange msg
+certify(internal, #server_hello_done{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = #handshake_env{premaster_secret = undefined},
+ session = #session{master_secret = MasterSecret} = Session,
+ connection_states = ConnectionStates0} = State0) ->
+ case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
+ ConnectionStates0, client) of
+ {MasterSecret, ConnectionStates} ->
+ State = State0#state{connection_states = ConnectionStates},
+ client_certify_and_key_exchange(State, Connection);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ end;
+%% Master secret is calculated from premaster_secret
+certify(internal, #server_hello_done{},
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = #handshake_env{premaster_secret = PremasterSecret},
+ session = Session0,
+ connection_states = ConnectionStates0} = State0) ->
+ case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
+ ConnectionStates0, client) of
+ {MasterSecret, ConnectionStates} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State = State0#state{connection_states = ConnectionStates,
+ session = Session},
+ client_certify_and_key_exchange(State, Connection);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ end;
+certify(internal = Type, #client_key_exchange{} = Msg,
+ #state{static_env = #static_env{role = server},
+ client_certificate_requested = true,
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{fail_if_no_peer_cert := true}} = State) ->
+ %% We expect a certificate here
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}),
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+certify(internal, #client_key_exchange{exchange_keys = Keys},
+ State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg},
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version}}) ->
+ try
+ certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)),
+ State, Connection)
+ catch
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ end;
+certify(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+certify(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec cipher(gen_statem:event_type(),
+ #hello_request{} | #certificate_verify{} | #finished{} | term(),
+ #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+cipher({call, From}, Msg, State) ->
+ handle_call(Msg, From, ?FUNCTION_NAME, State);
+cipher(info, Msg, State) ->
+ handle_info(Msg, ?FUNCTION_NAME, State);
+cipher(internal, #certificate_verify{signature = Signature,
+ hashsign_algorithm = CertHashSign},
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ kex_algorithm = KexAlg,
+ public_key_info = PubKeyInfo} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret}
+ } = State) ->
+
+ TLSVersion = ssl:tls_version(Version),
+ %% Use negotiated value if TLS-1.2 otherwhise return default
+ HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion),
+ case ssl_handshake:certificate_verify(Signature, PubKeyInfo,
+ TLSVersion, HashSign, MasterSecret, Hist) of
+ valid ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}});
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ end;
+%% client must send a next protocol message if we are expecting it
+cipher(internal, #finished{},
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{expecting_next_protocol_negotiation = true,
+ negotiated_protocol = undefined},
+ connection_env = #connection_env{negotiated_version = Version}} = State0) ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
+cipher(internal, #finished{verify_data = Data} = Finished,
+ #state{static_env = #static_env{role = Role,
+ host = Host,
+ port = Port,
+ trackers = Trackers},
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ expecting_finished = true} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret}
+ = Session0,
+ ssl_options = SslOpts,
+ connection_states = ConnectionStates0} = State) ->
+ case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished,
+ opposite_role(Role),
+ get_current_prf(ConnectionStates0, read),
+ MasterSecret, Hist) of
+ verified ->
+ Session = handle_session(Role, SslOpts, Host, Port, Trackers, Session0),
+ cipher_role(Role, Data, Session,
+ State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}});
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
+ end;
+%% only allowed to send next_protocol message after change cipher spec
+%% & before finished message and it is not allowed during renegotiation
+cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
+ #state{static_env = #static_env{role = server, protocol_cb = Connection},
+ handshake_env = #handshake_env{expecting_finished = true,
+ expecting_next_protocol_negotiation = true} = HsEnv} = State) ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
+ expecting_next_protocol_negotiation = false}});
+cipher(internal, #change_cipher_spec{type = <<1>>},
+ #state{handshake_env = HsEnv,
+ static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0} = State) ->
+ ConnectionStates =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true},
+ connection_states = ConnectionStates});
+cipher(internal, #hello_request{}, _) ->
+ keep_state_and_data;
+cipher(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = tls_gen_connection},
+ handshake_env = HsEnv} = State) ->
+ tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
+connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = dtls_gen_connection},
+ handshake_env = HsEnv} = State) ->
+ dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined,
+ negotiated_protocol = SelectedProtocol}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = tls_gen_connection},
+ handshake_env = HsEnv,
+ connection_states = ConnectionStates}
+ = State) ->
+ tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
+ connection_states = ConnectionStates#{current_write => WriteState}}, []);
+connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = dtls_gen_connection},
+ handshake_env = HsEnv,
+ connection_states = ConnectionStates}
+ = State) ->
+ dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
+ connection_states = ConnectionStates#{current_write => WriteState}}, []);
+
+connection(internal, {handshake, {#hello_request{} = Handshake, _}},
+ #state{handshake_env = HsEnv} = State) ->
+ %% Should not be included in handshake history
+ {next_state, ?FUNCTION_NAME, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}},
+ [{next_event, internal, Handshake}]};
+connection(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+downgrade(Type, Event, State) ->
+ ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State).
+
+gen_handshake(StateName, Type, Event,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ try tls_dtls_connection:StateName(Type, Event, State) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data),
+ Version, StateName, State)
+ end.
+
+%%--------------------------------------------------------------------
+%% Event handling functions called by state functions to handle
+%% common or unexpected events for the state.
+%%--------------------------------------------------------------------
+handle_call(renegotiate, From, StateName, _) when StateName =/= connection ->
+ {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
+
+handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
+ #state{connection_states = ConnectionStates,
+ connection_env = #connection_env{negotiated_version = Version}}) ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{master_secret = MasterSecret,
+ client_random = ClientRandom,
+ server_random = ServerRandom,
+ prf_algorithm = PRFAlgorithm} = SecParams,
+ Reply = try
+ SecretToUse = case Secret of
+ _ when is_binary(Secret) -> Secret;
+ master_secret -> MasterSecret
+ end,
+ SeedToUse = lists:reverse(
+ lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc];
+ (client_random, Acc) -> [ClientRandom|Acc];
+ (server_random, Acc) -> [ServerRandom|Acc]
+ end, [], Seed)),
+ ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength)
+ catch
+ exit:_ -> {error, badarg};
+ error:Reason -> {error, Reason}
+ end,
+ {keep_state_and_data, [{reply, From, Reply}]};
+handle_call(Msg, From, StateName, State) ->
+ ssl_gen_statem:handle_call(Msg, From, StateName, State).
+
+handle_info(Msg, StateName, State) ->
+ ssl_gen_statem:handle_info(Msg, StateName, State).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} =
+ ServerHelloExt,
+ #state{connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{protocol_cb = Connection},
+ handshake_env = HsEnv,
+ session = #session{session_id = SessId},
+ connection_states = ConnectionStates0,
+ ssl_options = #{versions := [HighestVersion|_]}}
+ = State0) when is_atom(Type) ->
+ %% TLS 1.3 - Section 4.1.3
+ %% Override server random values for TLS 1.3 downgrade protection mechanism.
+ ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion),
+ State1 = State0#state{connection_states = ConnectionStates1},
+ ServerHello =
+ ssl_handshake:server_hello(SessId, ssl:tls_version(Version),
+ ConnectionStates1, ServerHelloExt),
+ State = server_hello(ServerHello,
+ State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
+ NextProtocols =/= undefined}}, Connection),
+ case Type of
+ new ->
+ new_server_hello(ServerHello, State, Connection);
+ resumed ->
+ resumed_server_hello(State, Connection)
+ end.
+
+update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} =
+ ReadState0,
+ pending_write := #{security_parameters := WriteSecParams0} =
+ WriteState0} = ConnectionStates,
+ Version, HighestVersion) ->
+ ReadRandom = override_server_random(
+ ReadSecParams0#security_parameters.server_random,
+ Version,
+ HighestVersion),
+ WriteRandom = override_server_random(
+ WriteSecParams0#security_parameters.server_random,
+ Version,
+ HighestVersion),
+ ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom},
+ WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom},
+ ReadState = ReadState0#{security_parameters => ReadSecParams},
+ WriteState = WriteState0#{security_parameters => WriteSecParams},
+
+ ConnectionStates#{pending_read => ReadState, pending_write => WriteState}.
+
+%% TLS 1.3 - Section 4.1.3
+%%
+%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes
+%% of their Random value to the bytes:
+%%
+%% 44 4F 57 4E 47 52 44 01
+%%
+%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2
+%% servers SHOULD set the last eight bytes of their Random value to the
+%% bytes:
+%%
+%% 44 4F 57 4E 47 52 44 00
+override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
+ when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above
+ if M =:= 3 andalso N =:= 3 -> %% Negotating TLS 1.2
+ Down = ?RANDOM_OVERRIDE_TLS12,
+ <<Random0/binary,Down/binary>>;
+ M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
+ Down = ?RANDOM_OVERRIDE_TLS11,
+ <<Random0/binary,Down/binary>>;
+ true ->
+ Random
+ end;
+override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
+ when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2
+ if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
+ Down = ?RANDOM_OVERRIDE_TLS11,
+ <<Random0/binary,Down/binary>>;
+ true ->
+ Random
+ end;
+override_server_random(Random, _, _) ->
+ Random.
+
+new_server_hello(#server_hello{cipher_suite = CipherSuite,
+ compression_method = Compression,
+ session_id = SessionId},
+ #state{session = Session0,
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
+ try server_certify_and_key_exchange(State0, Connection) of
+ #state{} = State1 ->
+ {State, Actions} = server_hello_done(State1, Connection),
+ Session =
+ Session0#session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ Connection:next_event(certify, no_record, State#state{session = Session}, Actions)
+ catch
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0)
+ end.
+
+resumed_server_hello(#state{session = Session,
+ connection_states = ConnectionStates0,
+ static_env = #static_env{protocol_cb = Connection},
+ connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
+
+ case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
+ ConnectionStates0, server) of
+ {_, ConnectionStates1} ->
+ State1 = State0#state{connection_states = ConnectionStates1,
+ session = Session},
+ {State, Actions} =
+ finalize_handshake(State1, abbreviated, Connection),
+ Connection:next_event(abbreviated, no_record, State, Actions);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0)
+ end.
+
+server_hello(ServerHello, State0, Connection) ->
+ CipherSuite = ServerHello#server_hello.cipher_suite,
+ #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}.
+
+server_hello_done(State, Connection) ->
+ HelloDone = ssl_handshake:server_hello_done(),
+ Connection:send_handshake(HelloDone, State).
+
+handle_peer_cert(Role, PeerCert, PublicKeyInfo,
+ #state{handshake_env = HsEnv,
+ static_env = #static_env{protocol_cb = Connection},
+ session = #session{cipher_suite = CipherSuite} = Session} = State0,
+ Connection, Actions) ->
+ State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo},
+ session =
+ Session#session{peer_certificate = PeerCert}},
+ #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
+ Connection:next_event(certify, no_record, State, Actions).
+
+handle_peer_cert_key(client, _,
+ {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
+ PublicKeyParams},
+ KeyAlg, #state{handshake_env = HsEnv,
+ session = Session} = State) when KeyAlg == ecdh_rsa;
+ KeyAlg == ecdh_ecdsa ->
+ ECDHKey = public_key:generate_key(PublicKeyParams),
+ PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey),
+ master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey},
+ session = Session#session{ecc = PublicKeyParams}});
+handle_peer_cert_key(_, _, _, _, State) ->
+ State.
+
+certify_client(#state{static_env = #static_env{role = client,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ client_certificate_requested = true,
+ session = #session{own_certificates = OwnCerts}}
+ = State, Connection) ->
+ Certificate = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, client),
+ Connection:queue_handshake(Certificate, State);
+certify_client(#state{client_certificate_requested = false} = State, _) ->
+ State.
+
+verify_client_cert(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ cert_hashsign_algorithm = HashSign},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ client_certificate_requested = true,
+ session = #session{master_secret = MasterSecret,
+ own_certificates = OwnCerts}} = State, Connection) ->
+
+ case ssl_handshake:client_certificate_verify(OwnCerts, MasterSecret,
+ ssl:tls_version(Version), HashSign, PrivateKey, Hist) of
+ #certificate_verify{} = Verified ->
+ Connection:queue_handshake(Verified, State);
+ ignore ->
+ State;
+ #alert{} = Alert ->
+ throw(Alert)
+ end;
+verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
+ State.
+
+client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} =
+ State0, Connection) ->
+ try do_client_certify_and_key_exchange(State0, Connection) of
+ State1 = #state{} ->
+ {State2, Actions} = finalize_handshake(State1, certify, Connection),
+ State = State2#state{
+ %% Reinitialize
+ client_certificate_requested = false},
+ Connection:next_event(cipher, no_record, State, Actions)
+ catch
+ throw:#alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0)
+ end.
+
+do_client_certify_and_key_exchange(State0, Connection) ->
+ State1 = certify_client(State0, Connection),
+ State2 = key_exchange(State1, Connection),
+ verify_client_cert(State2, Connection).
+
+server_certify_and_key_exchange(State0, Connection) ->
+ State1 = certify_server(State0, Connection),
+ State2 = key_exchange(State1, Connection),
+ request_client_cert(State2, Connection).
+
+certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
+ #state{connection_env = #connection_env{private_key = Key},
+ handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}}
+ = State, Connection) ->
+ FakeSecret = make_premaster_secret(Version, rsa),
+ %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
+ %% and fail handshake later.RFC 5246 section 7.4.7.1.
+ PremasterSecret =
+ try ssl_handshake:premaster_secret(EncPMS, Key) of
+ Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES ->
+ case Secret of
+ <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct
+ <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>;
+ <<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch
+ <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>
+ end;
+ _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES
+ FakeSecret
+ catch
+ #alert{description = ?DECRYPT_ERROR} ->
+ FakeSecret
+ end,
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
+ #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
+ kex_keys = {_, ServerDhPrivateKey}}
+ } = State,
+ Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params),
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+
+certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
+ #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey),
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+certify_client_key_exchange(#client_psk_identity{} = ClientKey,
+ #state{ssl_options =
+ #{user_lookup_fun := PSKLookup}} = State0,
+ Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
+ #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
+ kex_keys = {_, ServerDhPrivateKey}},
+ ssl_options =
+ #{user_lookup_fun := PSKLookup}} = State0,
+ Connection) ->
+ PremasterSecret =
+ ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey,
+ #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey},
+ ssl_options =
+ #{user_lookup_fun := PSKLookup}} = State,
+ Connection) ->
+ PremasterSecret =
+ ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup),
+ calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
+certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey,
+ #state{connection_env = #connection_env{private_key = Key},
+ ssl_options =
+ #{user_lookup_fun := PSKLookup}} = State0,
+ Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
+certify_client_key_exchange(#client_srp_public{} = ClientKey,
+ #state{handshake_env = #handshake_env{srp_params = Params,
+ kex_keys = Key}
+ } = State0, Connection) ->
+ PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params),
+ calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher).
+
+certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} =
+ State, _) when KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == srp_anon ->
+ State;
+certify_server(#state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ session = #session{own_certificates = OwnCerts}} = State, Connection) ->
+ case ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, server) of
+ Cert = #certificate{} ->
+ Connection:queue_handshake(Cert, State);
+ Alert = #alert{} ->
+ throw(Alert)
+ end.
+
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) ->
+ State;
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ diffie_hellman_params = #'DHParameter'{} = Params,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0} = State0, Connection)
+ when KexAlg == dhe_dss;
+ KexAlg == dhe_rsa;
+ KexAlg == dh_anon ->
+ DHKeys = public_key:generate_key(Params),
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv,
+ connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key},
+ session = Session} = State, _)
+ when KexAlg == ecdh_ecdsa;
+ KexAlg == ecdh_rsa ->
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key},
+ session = Session#session{ecc = ECCurve}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ session = #session{ecc = ECCCurve},
+ connection_states = ConnectionStates0} = State0, Connection)
+ when KexAlg == ecdhe_ecdsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdh_anon ->
+
+ ECDHKeys = public_key:generate_key(ECCCurve),
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {ecdh, ECDHKeys,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = psk},
+ ssl_options = #{psk_identity := undefined}} = State, _) ->
+ State;
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{psk_identity := PskIdentityHint},
+ handshake_env = #handshake_env{kex_algorithm = psk,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0} = State0, Connection) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {psk, PskIdentityHint,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{psk_identity := PskIdentityHint},
+ handshake_env = #handshake_env{kex_algorithm = dhe_psk,
+ diffie_hellman_params = #'DHParameter'{} = Params,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0
+ } = State0, Connection) ->
+ DHKeys = public_key:generate_key(Params),
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {dhe_psk,
+ PskIdentityHint, DHKeys, Params,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{psk_identity := PskIdentityHint},
+ handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ session = #session{ecc = ECCCurve},
+ connection_states = ConnectionStates0
+ } = State0, Connection) ->
+ ECDHKeys = public_key:generate_key(ECCCurve),
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {ecdhe_psk,
+ PskIdentityHint, ECDHKeys,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = rsa_psk},
+ ssl_options = #{psk_identity := undefined}} = State, _) ->
+ State;
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{psk_identity := PskIdentityHint},
+ handshake_env = #handshake_env{kex_algorithm = rsa_psk,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0
+ } = State0, Connection) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {psk, PskIdentityHint,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = server},
+ ssl_options = #{user_lookup_fun := LookupFun},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ session = #session{srp_username = Username},
+ connection_states = ConnectionStates0
+ } = State0, Connection)
+ when KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
+ SrpParams = handle_srp_identity(Username, LookupFun),
+ Keys = case generate_srp_server_keys(SrpParams, 0) of
+ Alert = #alert{} ->
+ throw(Alert);
+ Keys0 = {_,_} ->
+ Keys0
+ end,
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{client_random = ClientRandom,
+ server_random = ServerRandom} = SecParams,
+ Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version),
+ {srp, Keys, SrpParams,
+ HashSignAlgo, ClientRandom,
+ ServerRandom,
+ PrivateKey}),
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams,
+ kex_keys = Keys}};
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = rsa,
+ public_key_info = PublicKeyInfo,
+ premaster_secret = PremasterSecret},
+ connection_env = #connection_env{negotiated_version = Version}
+ } = State0, Connection) ->
+ Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ kex_keys = {DhPubKey, _}},
+ connection_env = #connection_env{negotiated_version = Version}
+ } = State0, Connection)
+ when KexAlg == dhe_dss;
+ KexAlg == dhe_rsa;
+ KexAlg == dh_anon ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}),
+ Connection:queue_handshake(Msg, State0);
+
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session
+ } = State0, Connection)
+ when KexAlg == ecdhe_ecdsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdh_ecdsa;
+ KexAlg == ecdh_rsa;
+ KexAlg == ecdh_anon ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}),
+ Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}});
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = psk},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {psk, PSKIdentity}),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = dhe_psk,
+ kex_keys = {DhPubKey, _}},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {dhe_psk,
+ PSKIdentity, DhPubKey}),
+ Connection:queue_handshake(Msg, State0);
+
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
+ kex_keys = ECDHKeys},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {ecdhe_psk,
+ PSKIdentity, ECDHKeys}),
+ Connection:queue_handshake(Msg, State0);
+
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = rsa_psk,
+ public_key_info = PublicKeyInfo,
+ premaster_secret = PremasterSecret},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{psk_identity := PSKIdentity}}
+ = State0, Connection) ->
+ Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity,
+ PremasterSecret, PublicKeyInfo),
+ Connection:queue_handshake(Msg, State0);
+key_exchange(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ kex_keys = {ClientPubKey, _}},
+ connection_env = #connection_env{negotiated_version = Version}}
+ = State0, Connection)
+ when KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
+ Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}),
+ Connection:queue_handshake(Msg, State0).
+
+rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
+ when Algorithm == ?rsaEncryption;
+ Algorithm == ?md2WithRSAEncryption;
+ Algorithm == ?md5WithRSAEncryption;
+ Algorithm == ?sha1WithRSAEncryption;
+ Algorithm == ?sha224WithRSAEncryption;
+ Algorithm == ?sha256WithRSAEncryption;
+ Algorithm == ?sha384WithRSAEncryption;
+ Algorithm == ?sha512WithRSAEncryption
+ ->
+ ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {premaster_secret, PremasterSecret,
+ PublicKeyInfo});
+rsa_key_exchange(_, _, _) ->
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
+
+rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret,
+ PublicKeyInfo = {Algorithm, _, _})
+ when Algorithm == ?rsaEncryption;
+ Algorithm == ?md2WithRSAEncryption;
+ Algorithm == ?md5WithRSAEncryption;
+ Algorithm == ?sha1WithRSAEncryption;
+ Algorithm == ?sha224WithRSAEncryption;
+ Algorithm == ?sha256WithRSAEncryption;
+ Algorithm == ?sha384WithRSAEncryption;
+ Algorithm == ?sha512WithRSAEncryption
+ ->
+ ssl_handshake:key_exchange(client, ssl:tls_version(Version),
+ {psk_premaster_secret, PskIdentity, PremasterSecret,
+ PublicKeyInfo});
+rsa_psk_key_exchange(_, _, _, _) ->
+ throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
+
+request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _)
+ when Alg == dh_anon;
+ Alg == ecdh_anon;
+ Alg == psk;
+ Alg == dhe_psk;
+ Alg == ecdhe_psk;
+ Alg == rsa_psk;
+ Alg == srp_dss;
+ Alg == srp_rsa;
+ Alg == srp_anon ->
+ State;
+
+request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{verify := verify_peer,
+ signature_algs := SupportedHashSigns},
+ connection_states = ConnectionStates0} = State0, Connection) ->
+ #{security_parameters :=
+ #security_parameters{cipher_suite = CipherSuite}} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ TLSVersion = ssl:tls_version(Version),
+ HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns,
+ TLSVersion),
+ Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef,
+ HashSigns, TLSVersion),
+ State = Connection:queue_handshake(Msg, State0),
+ State#state{client_certificate_requested = true};
+
+request_client_cert(#state{ssl_options = #{verify := verify_none}} =
+ State, _) ->
+ State.
+
+calculate_master_secret(PremasterSecret,
+ #state{connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0,
+ session = Session0} = State0, Connection,
+ _Current, Next) ->
+ case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
+ ConnectionStates0, server) of
+ {MasterSecret, ConnectionStates} ->
+ Session = Session0#session{master_secret = MasterSecret},
+ State = State0#state{connection_states = ConnectionStates,
+ session = Session},
+ Connection:next_event(Next, no_record, State);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0)
+ end.
+
+finalize_handshake(State0, StateName, Connection) ->
+ #state{connection_states = ConnectionStates0} =
+ State1 = cipher_protocol(State0, Connection),
+
+ ConnectionStates =
+ ssl_record:activate_pending_connection_state(ConnectionStates0,
+ write, Connection),
+
+ State2 = State1#state{connection_states = ConnectionStates},
+ State = next_protocol(State2, Connection),
+ finished(State, StateName, Connection).
+
+next_protocol(#state{static_env = #static_env{role = server}} = State, _) ->
+ State;
+next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) ->
+ State;
+next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) ->
+ State;
+next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) ->
+ NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
+ Connection:queue_handshake(NextProtocolMessage, State0).
+
+cipher_protocol(State, Connection) ->
+ Connection:queue_change_cipher(#change_cipher_spec{}, State).
+
+finished(#state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session,
+ connection_states = ConnectionStates0} = State0,
+ StateName, Connection) ->
+ MasterSecret = Session#session.master_secret,
+ Finished = ssl_handshake:finished(ssl:tls_version(Version), Role,
+ get_current_prf(ConnectionStates0, write),
+ MasterSecret, Hist),
+ ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
+ Connection:send_handshake(Finished, State0#state{connection_states =
+ ConnectionStates}).
+
+save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) ->
+ ssl_record:set_client_verify_data(current_write, Data, ConnectionStates);
+save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) ->
+ ssl_record:set_server_verify_data(current_both, Data, ConnectionStates);
+save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
+ ssl_record:set_client_verify_data(current_both, Data, ConnectionStates);
+save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
+ ssl_record:set_server_verify_data(current_write, Data, ConnectionStates).
+
+calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base,
+ dh_y = ServerPublicDhKey} = Params,
+ #state{handshake_env = HsEnv} = State, Connection) ->
+ Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]),
+ PremasterSecret =
+ ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params),
+ calculate_master_secret(PremasterSecret,
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
+ Connection, certify, certify);
+
+calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
+ #state{handshake_env = HsEnv,
+ session = Session} = State, Connection) ->
+ ECDHKeys = public_key:generate_key(ECCurve),
+ PremasterSecret =
+ ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys),
+ calculate_master_secret(PremasterSecret,
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
+ session = Session#session{ecc = ECCurve}},
+ Connection, certify, certify);
+
+calculate_secret(#server_psk_params{
+ hint = IdentityHint},
+ #state{handshake_env = HsEnv} = State, Connection) ->
+ %% store for later use
+ Connection:next_event(certify, no_record,
+ State#state{handshake_env =
+ HsEnv#handshake_env{server_psk_identity = IdentityHint}});
+
+calculate_secret(#server_dhe_psk_params{
+ dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
+ #state{handshake_env = HsEnv,
+ ssl_options = #{user_lookup_fun := PSKLookup}} =
+ State, Connection) ->
+ Keys = {_, PrivateDhKey} =
+ crypto:generate_key(dh, [Prime, Base]),
+ PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup),
+ calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
+ Connection, certify, certify);
+
+calculate_secret(#server_ecdhe_psk_params{
+ dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey,
+ #state{ssl_options = #{user_lookup_fun := PSKLookup}} =
+ #state{handshake_env = HsEnv,
+ session = Session} = State, Connection) ->
+ ECDHKeys = public_key:generate_key(ECCurve),
+
+ PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup),
+ calculate_master_secret(PremasterSecret,
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
+ session = Session#session{ecc = ECCurve}},
+ Connection, certify, certify);
+
+calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey,
+ #state{handshake_env = HsEnv,
+ ssl_options = #{srp_identity := SRPId}} = State,
+ Connection) ->
+ Keys = generate_srp_client_keys(Generator, Prime, 0),
+ PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId),
+ calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection,
+ certify, certify).
+
+master_secret(#alert{} = Alert, _) ->
+ Alert;
+master_secret(PremasterSecret, #state{static_env = #static_env{role = Role},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session,
+ connection_states = ConnectionStates0} = State) ->
+ case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
+ ConnectionStates0, Role) of
+ {MasterSecret, ConnectionStates} ->
+ State#state{
+ session =
+ Session#session{master_secret = MasterSecret},
+ connection_states = ConnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end.
+
+generate_srp_server_keys(_SrpParams, 10) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+generate_srp_server_keys(SrpParams =
+ #srp_user{generator = Generator, prime = Prime,
+ verifier = Verifier}, N) ->
+ try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of
+ Keys ->
+ Keys
+ catch
+ error:_ ->
+ generate_srp_server_keys(SrpParams, N+1)
+ end.
+
+generate_srp_client_keys(_Generator, _Prime, 10) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+generate_srp_client_keys(Generator, Prime, N) ->
+
+ try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of
+ Keys ->
+ Keys
+ catch
+ error:_ ->
+ generate_srp_client_keys(Generator, Prime, N+1)
+ end.
+
+handle_srp_identity(Username, {Fun, UserState}) ->
+ case Fun(srp, Username, UserState) of
+ {ok, {SRPParams, Salt, DerivedKey}}
+ when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) ->
+ {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams),
+ Verifier = crypto:mod_pow(Generator, DerivedKey, Prime),
+ #srp_user{generator = Generator, prime = Prime,
+ salt = Salt, verifier = Verifier};
+ #alert{} = Alert ->
+ throw(Alert);
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
+ end.
+
+
+cipher_role(client, Data, Session, #state{static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0} = State0) ->
+ ConnectionStates = ssl_record:set_server_verify_data(current_both, Data,
+ ConnectionStates0),
+ {Record, State} = ssl_gen_statem:prepare_connection(State0#state{session = Session,
+ connection_states = ConnectionStates},
+ Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
+cipher_role(server, Data, Session, #state{static_env = #static_env{protocol_cb = Connection},
+ connection_states = ConnectionStates0} = State0) ->
+ ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data,
+ ConnectionStates0),
+ {State1, Actions} =
+ finalize_handshake(State0#state{connection_states = ConnectionStates1,
+ session = Session}, cipher, Connection),
+ {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]).
+
+is_anonymous(KexAlg) when KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == rsa_psk;
+ KexAlg == srp_anon ->
+ true;
+is_anonymous(_) ->
+ false.
+
+get_current_prf(CStates, Direction) ->
+ #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction),
+ SecParams#security_parameters.prf_algorithm.
+get_pending_prf(CStates, Direction) ->
+ #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction),
+ SecParams#security_parameters.prf_algorithm.
+
+opposite_role(client) ->
+ server;
+opposite_role(server) ->
+ client.
+
+
+
+session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
+ Session#session{ecc = ECCurve};
+session_handle_params(_, Session) ->
+ Session.
+
+handle_session(server, #{reuse_sessions := true},
+ _Host, _Port, Trackers, #session{is_resumable = false} = Session) ->
+ Tracker = proplists:get_value(session_id_tracker, Trackers),
+ server_register_session(Tracker, Session#session{is_resumable = true});
+handle_session(Role = client, #{verify := verify_peer,
+ reuse_sessions := Reuse} = SslOpts,
+ Host, Port, _, #session{is_resumable = false} = Session) when Reuse =/= false ->
+ client_register_session(host_id(Role, Host, SslOpts), Port, Session#session{is_resumable = true},
+ reg_type(Reuse));
+handle_session(_,_,_,_,_, Session) ->
+ Session.
+
+reg_type(save) ->
+ true;
+reg_type(true) ->
+ unique.
+
+client_register_session(Host, Port, Session, Save) ->
+ ssl_manager:register_session(Host, Port, Session, Save),
+ Session.
+server_register_session(Tracker, Session) ->
+ ssl_server_session_cache:register_session(Tracker, Session),
+ Session.
+
+host_id(client, _Host, #{server_name_indication := Hostname}) when is_list(Hostname) ->
+ Hostname;
+host_id(_, Host, _) ->
+ Host.
+
+handle_new_session(NewId, CipherSuite, Compression,
+ #state{static_env = #static_env{protocol_cb = Connection},
+ session = Session0
+ } = State0) ->
+ Session = Session0#session{session_id = NewId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ Connection:next_event(certify, no_record, State0#state{session = Session}).
+
+handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
+ port = Port,
+ protocol_cb = Connection,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0,
+ ssl_options = Opts} = State) ->
+
+ Session = case maps:get(reuse_session, Opts, undefined) of
+ {SessId,SessionData} when is_binary(SessId), is_binary(SessionData) ->
+ binary_to_term(SessionData, [safe]);
+ _Else ->
+ CacheCb:lookup(Cache, {{Host, Port}, SessId})
+ end,
+
+ case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
+ ConnectionStates0, client) of
+ {_, ConnectionStates} ->
+ Connection:next_event(abbreviated, no_record, State#state{
+ connection_states = ConnectionStates,
+ session = Session});
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, hello, State)
+ end.
+
+make_premaster_secret({MajVer, MinVer}, rsa) ->
+ Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
+make_premaster_secret(_, _) ->
+ undefined.
+
+negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) ->
+ %% Not negotiated choose default
+ case is_anonymous(KexAlg) of
+ true ->
+ {null, anon};
+ false ->
+ {PubAlg, _, _} = PubKeyInfo,
+ ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version)
+ end;
+negotiated_hashsign(HashSign = {_, _}, _, _, _) ->
+ HashSign.
+
+%% Handle SNI extension in pre-TLS 1.3 and DTLS
+handle_sni_extension(#state{static_env =
+ #static_env{protocol_cb = Connection}} = State0,
+ Hello) ->
+ PossibleSNI = Connection:select_sni_extension(Hello),
+ case ssl_gen_statem:handle_sni_extension(PossibleSNI, State0) of
+ {ok, State} ->
+ State;
+ {error, Alert} ->
+ Alert
+ end.
+
+ensure_tls({254, _} = Version) ->
+ dtls_v1:corresponding_tls_version(Version);
+ensure_tls(Version) ->
+ Version.
+
+ocsp_info(#{ocsp_expect := stapled,
+ ocsp_response := CertStatus} = OcspState,
+ #{ocsp_responder_certs := OcspResponderCerts}, PeerCert) ->
+ #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]},
+ ocsp_responder_certs => OcspResponderCerts,
+ ocsp_state => OcspState
+ };
+ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) ->
+ #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []},
+ ocsp_responder_certs => [],
+ ocsp_state => OcspState
+ }.
diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl
new file mode 100644
index 0000000000..5da87e79d6
--- /dev/null
+++ b/lib/ssl/src/tls_gen_connection.erl
@@ -0,0 +1,778 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose:
+%%----------------------------------------------------------------------
+
+-module(tls_gen_connection).
+
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+-include("tls_connection.hrl").
+-include("tls_handshake.hrl").
+-include("tls_record.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_api.hrl").
+-include("ssl_internal.hrl").
+
+%% Setup
+-export([start_fsm/8,
+ pids/1,
+ initialize_tls_sender/1]).
+
+%% Handshake handling
+-export([renegotiation/2,
+ renegotiate/2,
+ send_handshake/2,
+ send_handshake_flight/1,
+ queue_handshake/2,
+ queue_change_cipher/2,
+ reinit/1,
+ reinit_handshake_data/1,
+ select_sni_extension/1,
+ empty_connection_state/2,
+ encode_handshake/4]).
+
+%% State transition handling
+-export([next_event/3,
+ next_event/4,
+ handle_protocol_record/3]).
+
+%% Data handling
+-export([socket/4,
+ setopts/3,
+ getopts/3,
+ handle_info/3]).
+
+%% Alert and close handling
+-export([send_alert/2,
+ send_alert_in_connection/2,
+ send_sync_alert/2,
+ close/5,
+ protocol_name/0]).
+
+-define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%====================================================================
+%% Setup
+%%====================================================================
+start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts,
+ User, {CbModule, _,_, _, _} = CbInfo,
+ Timeout) ->
+ try
+ {ok, Sender} = tls_sender:start(),
+ {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket,
+ Opts, User, CbInfo]),
+ {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
+ ssl_gen_statem:handshake(SslSocket, Timeout)
+ catch
+ error:{badmatch, {error, _} = Error} ->
+ Error
+ end;
+
+start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts,
+ User, {CbModule, _,_, _, _} = CbInfo,
+ Timeout) ->
+ try
+ {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]),
+ {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket,
+ Opts, User, CbInfo]),
+ {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
+ ssl_gen_statem:handshake(SslSocket, Timeout)
+ catch
+ error:{badmatch, {error, _} = Error} ->
+ Error
+ end.
+
+pids(#state{protocol_specific = #{sender := Sender}}) ->
+ [self(), Sender].
+
+initialize_tls_sender(#state{static_env = #static_env{
+ role = Role,
+ transport_cb = Transport,
+ socket = Socket,
+ trackers = Trackers
+ },
+ connection_env = #connection_env{negotiated_version = Version},
+ socket_options = SockOpts,
+ ssl_options = #{renegotiate_at := RenegotiateAt,
+ key_update_at := KeyUpdateAt,
+ log_level := LogLevel},
+ connection_states = #{current_write := ConnectionWriteState},
+ protocol_specific = #{sender := Sender}}) ->
+ Init = #{current_write => ConnectionWriteState,
+ role => Role,
+ socket => Socket,
+ socket_options => SockOpts,
+ trackers => Trackers,
+ transport_cb => Transport,
+ negotiated_version => Version,
+ renegotiate_at => RenegotiateAt,
+ key_update_at => KeyUpdateAt,
+ log_level => LogLevel},
+ tls_sender:initialize(Sender, Init).
+
+%%====================================================================
+%% Handshake handling
+%%====================================================================
+renegotiation(Pid, WriteState) ->
+ gen_statem:call(Pid, {user_renegotiate, WriteState}).
+
+renegotiate(#state{static_env = #static_env{role = client},
+ handshake_env = HsEnv} = State, Actions) ->
+ %% Handle same way as if server requested
+ %% the renegotiation
+ Hs0 = ssl_handshake:init_handshake_history(),
+ {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
+ [{next_event, internal, #hello_request{}} | Actions]};
+renegotiate(#state{static_env = #static_env{role = server,
+ socket = Socket,
+ transport_cb = Transport},
+ handshake_env = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0} = State0, Actions) ->
+ HelloRequest = ssl_handshake:hello_request(),
+ Frag = tls_handshake:encode_handshake(HelloRequest, Version),
+ Hs0 = ssl_handshake:init_handshake_history(),
+ {BinMsg, ConnectionStates} =
+ tls_record:encode_handshake(Frag, Version, ConnectionStates0),
+ tls_socket:send(Transport, Socket, BinMsg),
+ State = State0#state{connection_states =
+ ConnectionStates,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
+ next_event(hello, no_record, State, Actions).
+
+send_handshake(Handshake, State) ->
+ send_handshake_flight(queue_handshake(Handshake, State)).
+
+queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = Flight0,
+ ssl_options = #{log_level := LogLevel},
+ connection_states = ConnectionStates0} = State0) ->
+ {BinHandshake, ConnectionStates, Hist} =
+ encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinHandshake),
+
+ State0#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
+ flight_buffer = Flight0 ++ [BinHandshake]}.
+
+-spec send_handshake_flight(StateIn) -> {StateOut, FlightBuffer} when
+ StateIn :: #state{},
+ StateOut :: #state{},
+ FlightBuffer :: list().
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ flight_buffer = Flight} = State0) ->
+ tls_socket:send(Transport, Socket, Flight),
+ {State0#state{flight_buffer = []}, []}.
+
+
+queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = Flight0,
+ ssl_options = #{log_level := LogLevel},
+ connection_states = ConnectionStates0} = State0) ->
+ {BinChangeCipher, ConnectionStates} =
+ encode_change_cipher(Msg, Version, ConnectionStates0),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher),
+ State0#state{connection_states = ConnectionStates,
+ flight_buffer = Flight0 ++ [BinChangeCipher]}.
+
+reinit(#state{protocol_specific = #{sender := Sender},
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = #{current_write := Write}} = State) ->
+ tls_sender:update_connection_state(Sender, Write, Version),
+ reinit_handshake_data(State).
+
+reinit_handshake_data(#state{handshake_env = HsEnv} =State) ->
+ %% premaster_secret, public_key_info and tls_handshake_info
+ %% are only needed during the handshake phase.
+ %% To reduce memory foot print of a connection reinitialize them.
+ State#state{
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
+ public_key_info = undefined,
+ premaster_secret = undefined}
+ }.
+
+select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
+ SNI;
+select_sni_extension(_) ->
+ undefined.
+
+empty_connection_state(ConnectionEnd, BeastMitigation) ->
+ ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation).
+
+%%====================================================================
+%% Data handling
+%%====================================================================
+
+socket(Pids, Transport, Socket, Trackers) ->
+ tls_socket:socket(Pids, Transport, Socket, tls_connection, Trackers).
+
+setopts(Transport, Socket, Other) ->
+ tls_socket:setopts(Transport, Socket, Other).
+
+getopts(Transport, Socket, Tag) ->
+ tls_socket:getopts(Transport, Socket, Tag).
+
+%% raw data from socket, upack records
+handle_info({Protocol, _, Data}, StateName,
+ #state{static_env = #static_env{data_tag = Protocol},
+ connection_env = #connection_env{negotiated_version = Version}} = State0) ->
+ case next_tls_record(Data, StateName, State0) of
+ {Record, State} ->
+ next_event(StateName, Record, State);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0)
+ end;
+handle_info({PassiveTag, Socket}, StateName,
+ #state{static_env = #static_env{socket = Socket,
+ passive_tag = PassiveTag},
+ start_or_recv_from = From,
+ protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
+ protocol_specific = PS
+ } = State0) ->
+ case (From =/= undefined) andalso (CTs == []) of
+ true ->
+ {Record, State} = activate_socket(State0#state{protocol_specific = PS#{active_n_toggle => true}}),
+ next_event(StateName, Record, State);
+ false ->
+ next_event(StateName, no_record,
+ State0#state{protocol_specific = PS#{active_n_toggle => true}})
+ end;
+handle_info({CloseTag, Socket}, StateName,
+ #state{static_env = #static_env{
+ role = Role,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ close_tag = CloseTag},
+ handshake_env = #handshake_env{renegotiation = Type},
+ session = Session} = State) when StateName =/= connection ->
+ ssl_gen_statem:maybe_invalidate_session(Type, Role, Host, Port, Session),
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop, {shutdown, transport_closed}, State};
+handle_info({CloseTag, Socket}, StateName,
+ #state{static_env = #static_env{
+ role = Role,
+ socket = Socket,
+ close_tag = CloseTag},
+ start_or_recv_from = From,
+ socket_options = #socket_options{active = Active},
+ protocol_specific = PS} = State) ->
+
+ %% Note that as of TLS 1.1,
+ %% failure to properly close a connection no longer requires that a
+ %% session not be resumed. This is a change from TLS 1.0 to conform
+ %% with widespread implementation practice.
+
+ case (Active == false) andalso (From == undefined) of
+ false ->
+ %% As invalidate_sessions here causes performance issues,
+ %% we will conform to the widespread implementation
+ %% practice and go aginst the spec
+ %% case Version of
+ %% {3, N} when N >= 1 ->
+ %% ok;
+ %% _ ->
+ %% invalidate_session(Role, Host, Port, Session)
+ %% ok
+ %% end,
+ Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed),
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State),
+ {stop, {shutdown, transport_closed}, State};
+ true ->
+ %% Wait for next socket operation (most probably
+ %% ssl:setopts(S, [{active, true | once | N}]) or
+ %% ssl:recv(S, N, Timeout) before closing. Possible
+ %% buffered data will be deliverd by the code handling
+ %% these options before closing. In the case of the
+ %% peer resetting the connection hard, that is
+ %% we do not receive any close ALERT, and an active once (or possible N)
+ %% strategy is used by the client we want to later trigger a new
+ %% "transport closed" message. This is achieved by setting the internal
+ %% active_n_toggle here which will cause
+ %% this to happen when tls_connection:activate_socket/1
+ %% is called after all data has been deliver.
+ {next_state, StateName, State#state{protocol_specific = PS#{active_n_toggle => true}}, []}
+ end;
+handle_info({'EXIT', Sender, Reason}, _,
+ #state{protocol_specific = #{sender := Sender}} = State) ->
+ {stop, {shutdown, {sender_died, Reason}}, State};
+handle_info(Msg, StateName, State) ->
+ ssl_gen_statem:handle_info(Msg, StateName, State).
+
+%%====================================================================
+%% State transition handling
+%%====================================================================
+next_event(StateName, Record, State) ->
+ next_event(StateName, Record, State, []).
+
+next_event(StateName, no_record, #state{static_env = #static_env{role = Role}} = State0, Actions) ->
+ case next_record(StateName, State0) of
+ {no_record, State} ->
+ ssl_gen_statem:hibernate_after(StateName, State, Actions);
+ {Record, State} ->
+ next_event(StateName, Record, State, Actions);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0),
+ {stop, {shutdown, own_alert}, State0}
+ end;
+next_event(StateName, #ssl_tls{} = Record, State, Actions) ->
+ {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
+next_event(StateName, #alert{} = Alert, State, Actions) ->
+ {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}.
+
+%%% TLS record protocol level application data messages
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName,
+ #state{start_or_recv_from = From,
+ socket_options = #socket_options{active = false}} = State0) when From =/= undefined ->
+ case ssl_gen_statem:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, #state{start_or_recv_from = Caller} = State} ->
+ TimerAction = case Caller of
+ undefined -> %% Passive recv complete cancel timer
+ [{{timeout, recv}, infinity, timeout}];
+ _ ->
+ []
+ end,
+ next_event(StateName, Record, State, TimerAction)
+ end;
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) ->
+ case ssl_gen_statem:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, State} ->
+ next_event(StateName, Record, State)
+ end;
+%%% TLS record protocol level handshake messages
+handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
+ StateName, #state{protocol_buffers =
+ #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
+ connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{role = Role},
+ ssl_options = Options} = State0) ->
+ try
+ %% Calculate the effective version that should be used when decoding an incoming handshake
+ %% message.
+ EffectiveVersion = effective_version(Version, Options, Role),
+ {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options),
+ State =
+ State0#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
+ case Packets of
+ [] ->
+ assert_buffer_sanity(Buf, Options),
+ next_event(StateName, no_record, State);
+ _ ->
+ Events = tls_handshake_events(Packets),
+ case StateName of
+ connection ->
+ ssl_gen_statem:hibernate_after(StateName, State, Events);
+ _ ->
+ HsEnv = State#state.handshake_env,
+ {next_state, StateName,
+ State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
+ end
+ end
+ catch throw:#alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0)
+ end;
+%%% TLS record protocol level change cipher messages
+handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
+ {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
+%%% TLS record protocol level Alert messages
+handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ try decode_alerts(EncAlerts) of
+ Alerts = [_|_] ->
+ handle_alerts(Alerts, {next_state, StateName, State});
+ [] ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert),
+ Version, StateName, State);
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State)
+ catch
+ _:_ ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error),
+ Version, StateName, State)
+
+ end;
+%% Ignore unknown TLS record level protocol messages
+handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
+ {next_state, StateName, State, []}.
+
+%%====================================================================
+%% Alert and close handling
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
+ {iolist(), ssl_record:connection_states()}.
+%%
+%% Description: Encodes an alert
+%%--------------------------------------------------------------------
+encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
+ tls_record:encode_alert_record(Alert, Version, ConnectionStates).
+
+send_alert(Alert, #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = #{log_level := LogLevel},
+ connection_states = ConnectionStates0} = StateData0) ->
+ {BinMsg, ConnectionStates} =
+ encode_alert(Alert, Version, ConnectionStates0),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
+ StateData0#state{connection_states = ConnectionStates}.
+
+%% If an ALERT sent in the connection state, should cause the TLS
+%% connection to end, we need to synchronize with the tls_sender
+%% process so that the ALERT if possible (that is the tls_sender process is
+%% not blocked) is sent before the connection process terminates and
+%% thereby closes the transport socket.
+send_alert_in_connection(#alert{level = ?FATAL} = Alert, State) ->
+ send_sync_alert(Alert, State);
+send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) ->
+ send_sync_alert(Alert, State);
+send_alert_in_connection(Alert,
+ #state{protocol_specific = #{sender := Sender}}) ->
+ tls_sender:send_alert(Sender, Alert).
+send_sync_alert(
+ Alert, #state{protocol_specific = #{sender := Sender}} = State) ->
+ try tls_sender:send_and_ack_alert(Sender, Alert)
+ catch
+ _:_ ->
+ throw({stop, {shutdown, own_alert}, State})
+ end.
+
+%% User closes or recursive call!
+close({close, Timeout}, Socket, Transport = gen_tcp, _,_) ->
+ tls_socket:setopts(Transport, Socket, [{active, false}]),
+ Transport:shutdown(Socket, write),
+ _ = Transport:recv(Socket, 0, Timeout),
+ ok;
+%% Peer closed socket
+close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) ->
+ close({close, 0}, Socket, Transport, ConnectionStates, Check);
+%% We generate fatal alert
+close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) ->
+ %% Standard trick to try to make sure all
+ %% data sent to the tcp port is really delivered to the
+ %% peer application before tcp port is closed so that the peer will
+ %% get the correct TLS alert message and not only a transport close.
+ %% Will return when other side has closed or after timout millisec
+ %% e.g. we do not want to hang if something goes wrong
+ %% with the network but we want to maximise the odds that
+ %% peer application gets all data sent on the tcp connection.
+ close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
+close(downgrade, _,_,_,_) ->
+ ok;
+%% Other
+close(_, Socket, Transport, _,_) ->
+ tls_socket:close(Transport, Socket).
+protocol_name() ->
+ "TLS".
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+tls_handshake_events(Packets) ->
+ lists:map(fun(Packet) ->
+ {next_event, internal, {handshake, Packet}}
+ end, Packets).
+
+unprocessed_events(Events) ->
+ %% The first handshake event will be processed immediately
+ %% as it is entered first in the event queue and
+ %% when it is processed there will be length(Events)-1
+ %% handshake events left to process before we should
+ %% process more TLS-records received on the socket.
+ erlang:length(Events)-1.
+
+encode_handshake(Handshake, Version, ConnectionStates0, Hist0) ->
+ Frag = tls_handshake:encode_handshake(Handshake, Version),
+ Hist = ssl_handshake:update_handshake_history(Hist0, Frag),
+ {Encoded, ConnectionStates} =
+ tls_record:encode_handshake(Frag, Version, ConnectionStates0),
+ {Encoded, ConnectionStates, Hist}.
+
+encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
+ tls_record:encode_change_cipher_spec(Version, ConnectionStates).
+
+next_tls_record(Data, StateName,
+ #state{protocol_buffers =
+ #protocol_buffers{tls_record_buffer = Buf0,
+ tls_cipher_texts = CT0} = Buffers,
+ ssl_options = SslOpts} = State0) ->
+ Versions =
+ %% TLSPlaintext.legacy_record_version is ignored in TLS 1.3 and thus all
+ %% record version are accepted when receiving initial ClientHello and
+ %% ServerHello. This can happen in state 'hello' in case of all TLS
+ %% versions and also in state 'start' when TLS 1.3 is negotiated.
+ %% After the version is negotiated all subsequent TLS records shall have
+ %% the proper legacy_record_version (= negotiated_version).
+ %% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this
+ %% point it is the same as the negotiated protocol version.
+ %% TODO: Refactor state machine and introduce a record_protocol_version beside
+ %% the negotiated_version.
+ case StateName of
+ State when State =:= hello orelse
+ State =:= start ->
+ [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS];
+ _ ->
+ State0#state.connection_env#connection_env.negotiated_version
+ end,
+ #{current_write := #{max_fragment_length := MaxFragLen}} = State0#state.connection_states,
+ case tls_record:get_tls_records(Data, Versions, Buf0, MaxFragLen, SslOpts) of
+ {Records, Buf1} ->
+ CT1 = CT0 ++ Records,
+ next_record(StateName, State0#state{protocol_buffers =
+ Buffers#protocol_buffers{tls_record_buffer = Buf1,
+ tls_cipher_texts = CT1}});
+ #alert{} = Alert ->
+ handle_record_alert(Alert, State0)
+ end.
+
+next_record(_, #state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
+next_record(_, #state{protocol_buffers =
+ #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts},
+ connection_states = ConnectionStates,
+ ssl_options = #{padding_check := Check}} = State) ->
+ next_record(State, CipherTexts, ConnectionStates, Check);
+next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
+ protocol_specific = #{active_n_toggle := true}
+ } = State) ->
+ %% If ssl application user is not reading data wait to activate socket
+ flow_ctrl(State);
+
+next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
+ protocol_specific = #{active_n_toggle := true}
+ } = State) ->
+ activate_socket(State);
+next_record(_, State) ->
+ {no_record, State}.
+
+%%% bytes_to_read equals the integer Length arg of ssl:recv
+%%% the actual value is only relevant for packet = raw | 0
+%%% bytes_to_read = undefined means no recv call is ongoing
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = undefined} = State) when Size =/= 0 ->
+ %% Passive mode wait for new recv request or socket activation
+ %% that is preserv some tcp back pressure by waiting to activate
+ %% socket
+ {no_record, State};
+%%%%%%%%%% A packet mode is set and socket is passive %%%%%%%%%%
+flow_ctrl(#state{socket_options = #socket_options{active = false,
+ packet = Packet}} = State)
+ when ((Packet =/= 0) andalso (Packet =/= raw)) ->
+ %% We need more data to complete the packet.
+ activate_socket(State);
+%%%%%%%%% No packet mode set and socket is passive %%%%%%%%%%%%
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = 0} = State) when Size == 0 ->
+ %% Passive mode no available bytes, get some
+ activate_socket(State);
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = 0} = State) when Size =/= 0 ->
+ %% There is data in the buffer to deliver
+ {no_record, State};
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = BytesToRead} = State) when (BytesToRead > 0) ->
+ case (Size >= BytesToRead) of
+ true -> %% There is enough data bufferd
+ {no_record, State};
+ false -> %% We need more data to complete the delivery of <BytesToRead> size
+ activate_socket(State)
+ end;
+%%%%%%%%%%% Active mode or more data needed %%%%%%%%%%
+flow_ctrl(State) ->
+ activate_socket(State).
+
+
+activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
+ static_env = #static_env{socket = Socket,
+ close_tag = CloseTag,
+ transport_cb = Transport}
+ } = State) ->
+ case tls_socket:setopts(Transport, Socket, [{active, N}]) of
+ ok ->
+ {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};
+ _ ->
+ self() ! {CloseTag, Socket},
+ {no_record, State}
+ end.
+
+%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one
+%%
+next_record(State, CipherTexts, ConnectionStates, Check) ->
+ next_record(State, CipherTexts, ConnectionStates, Check, []).
+%%
+next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State,
+ [CT|CipherTexts], ConnectionStates0, Check, Acc) ->
+ case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
+ {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
+ case CipherTexts of
+ [] ->
+ %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
+ %% from the accumulated fragments
+ next_record_done(State, [], ConnectionStates,
+ #ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
+ [_|_] ->
+ next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
+ end;
+ {trial_decryption_failed, ConnectionStates} ->
+ case CipherTexts of
+ [] ->
+ %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
+ %% from the accumulated fragments
+ next_record_done(State, [], ConnectionStates,
+ #ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc))});
+ [_|_] ->
+ next_record(State, CipherTexts, ConnectionStates, Check, Acc)
+ end;
+ {Record, ConnectionStates} when Acc =:= [] ->
+ %% Singelton non-?APPLICATION_DATA record - deliver
+ next_record_done(State, CipherTexts, ConnectionStates, Record);
+ {_Record, _ConnectionStates_to_forget} ->
+ %% Not ?APPLICATION_DATA but we have accumulated fragments
+ %% -> build an ?APPLICATION_DATA record with concatenated fragments
+ %% and forget about decrypting this record - we'll decrypt it again next time
+ %% Will not work for stream ciphers
+ next_record_done(State, [CT|CipherTexts], ConnectionStates0,
+ #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))});
+ #alert{} = Alert ->
+ Alert
+ end;
+next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
+ [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) ->
+ case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
+ {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
+ case CipherTexts of
+ [] ->
+ %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
+ %% from the accumulated fragments
+ next_record_done(State, [], ConnectionStates,
+ #ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
+ [_|_] ->
+ next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
+ end;
+ #alert{} = Alert ->
+ Alert
+ end;
+next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) ->
+ next_record_done(State, CipherTexts, ConnectionStates,
+ #ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc))});
+next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
+ [CT|CipherTexts], ConnectionStates0, Check, []) ->
+ case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
+ {Record, ConnectionStates} ->
+ %% Singelton non-?APPLICATION_DATA record - deliver
+ next_record_done(State, CipherTexts, ConnectionStates, Record);
+ #alert{} = Alert ->
+ Alert
+ end.
+
+next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) ->
+ {Record,
+ State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
+ connection_states = ConnectionStates}}.
+
+%% Special version handling for TLS 1.3 clients:
+%% In the shared state 'init' negotiated_version is set to requested version and
+%% that is expected by the legacy part of the state machine. However, in order to
+%% be able to process new TLS 1.3 extensions, the effective version shall be set
+%% {3,4}.
+%% When highest supported version is {3,4} the negotiated version is set to {3,3}.
+effective_version({3,3} , #{versions := [Version|_]}, client) when Version >= {3,4} ->
+ Version;
+%% Use highest supported version during startup (TLS server, all versions).
+effective_version(undefined, #{versions := [Version|_]}, _) ->
+ Version;
+%% Use negotiated version in all other cases.
+effective_version(Version, _, _) ->
+ Version.
+
+assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>,
+ #{max_handshake_size := Max}) when
+ Length =< Max ->
+ case size(Rest) of
+ N when N < Length ->
+ true;
+ N when N > Length ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ too_big_handshake_data));
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data))
+ end;
+assert_buffer_sanity(Bin, _) ->
+ case size(Bin) of
+ N when N < 3 ->
+ true;
+ _ ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data))
+ end.
+
+decode_alerts(Bin) ->
+ ssl_alert:decode(Bin).
+
+handle_alerts([], Result) ->
+ Result;
+handle_alerts(_, {stop, _, _} = Stop) ->
+ Stop;
+handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts],
+ {next_state, connection = StateName, #state{connection_env = CEnv,
+ socket_options = #socket_options{active = false},
+ start_or_recv_from = From} = State}) when From == undefined ->
+ {next_state, StateName, State#state{connection_env = CEnv#connection_env{terminated = true}}};
+handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State));
+handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
+ handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)).
+
+handle_record_alert(Alert, _) ->
+ Alert.
+
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index f279e041be..d7c899c7cf 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -36,7 +36,7 @@
-include_lib("kernel/include/logger.hrl").
%% Handshake handling
--export([client_hello/9, hello/4]).
+-export([client_hello/10, hello/5, hello/4]).
%% Handshake encoding
-export([encode_handshake/2]).
@@ -44,6 +44,9 @@
%% Handshake decodeing
-export([get_tls_handshake/4, decode_handshake/3]).
+%% Handshake helper
+-export([ocsp_nonce/2]).
+
-type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake().
%%====================================================================
@@ -52,7 +55,8 @@
%%--------------------------------------------------------------------
-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
ssl_options(), binary(), boolean(), der_cert(),
- #key_share_client_hello{} | undefined, tuple() | undefined) ->
+ #key_share_client_hello{} | undefined, tuple() | undefined,
+ binary() | undefined) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
@@ -62,7 +66,7 @@ client_hello(_Host, _Port, ConnectionStates,
ciphers := UserSuites,
fallback := Fallback
} = SslOpts,
- Id, Renegotiation, _OwnCert, KeyShare, TicketData) ->
+ Id, Renegotiation, _OwnCert, KeyShare, TicketData, OcspNonce) ->
Version = tls_record:highest_protocol_version(Versions),
%% In TLS 1.3, the client indicates its version preferences in the
@@ -82,9 +86,10 @@ client_hello(_Host, _Port, ConnectionStates,
Extensions = ssl_handshake:client_hello_extensions(Version,
AvailableCipherSuites,
SslOpts, ConnectionStates,
- Renegotiation,
- KeyShare,
- TicketData),
+ Renegotiation,
+ KeyShare,
+ TicketData,
+ OcspNonce),
CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback),
#client_hello{session_id = Id,
client_version = LegacyVersion,
@@ -95,17 +100,14 @@ client_hello(_Host, _Port, ConnectionStates,
}.
%%--------------------------------------------------------------------
--spec hello(#server_hello{} | #client_hello{}, ssl_options(),
+-spec hello(#server_hello{}, ssl_options(),
ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(),
atom(), ssl_record:connection_states(),
binary() | undefined, ssl:kex_algo()},
- boolean()) ->
- {tls_record:tls_version(), ssl:session_id(),
- ssl_record:connection_states(), alpn | npn, binary() | undefined}|
- {tls_record:tls_version(), {resumed | new, #session{}},
- ssl_record:connection_states(), binary() | undefined,
- HelloExt::map(), {ssl:hash(), ssl:sign_algo()} |
- undefined} | {atom(), atom()} | {atom(), atom(), tuple()} | #alert{}.
+ boolean(), #session{}) ->
+ {tls_record:tls_version(), ssl:session_id(),
+ ssl_record:connection_states(), alpn | npn, binary() | undefined, map()}|
+ {atom(), atom(), tls_record:tls_version(), map()} | #alert{}.
%%
%% Description: Handles a received hello message
%%--------------------------------------------------------------------
@@ -117,7 +119,7 @@ client_hello(_Host, _Port, ConnectionStates,
%% values.
hello(#server_hello{server_version = {Major, Minor},
random = <<_:24/binary,Down:8/binary>>},
- #{versions := [{M,N}|_]}, _, _)
+ #{versions := [{M,N}|_]}, _, _, _)
when (M > 3 orelse M =:= 3 andalso N >= 4) andalso %% TLS 1.3 client
(Major =:= 3 andalso Minor =:= 3 andalso %% Negotiating TLS 1.2
Down =:= ?RANDOM_OVERRIDE_TLS12) orelse
@@ -131,7 +133,7 @@ hello(#server_hello{server_version = {Major, Minor},
%% equal to the second value if the ServerHello indicates TLS 1.1 or below.
hello(#server_hello{server_version = {Major, Minor},
random = <<_:24/binary,Down:8/binary>>},
- #{versions := [{M,N}|_]}, _, _)
+ #{versions := [{M,N}|_]}, _, _, _)
when (M =:= 3 andalso N =:= 3) andalso %% TLS 1.2 client
(Major =:= 3 andalso Minor < 3 andalso %% Negotiating TLS 1.1 or prior
Down =:= ?RANDOM_OVERRIDE_TLS11) ->
@@ -156,8 +158,9 @@ hello(#server_hello{server_version = LegacyVersion,
extensions = #{server_hello_selected_version :=
#server_hello_selected_version{selected_version = Version} = HelloExt}
},
- #{versions := SupportedVersions} = SslOpt,
- ConnectionStates0, Renegotiation) ->
+ #{versions := SupportedVersions,
+ ocsp_stapling := Stapling} = SslOpt,
+ ConnectionStates0, Renegotiation, OldId) ->
%% In TLS 1.3, the TLS server indicates its version using the "supported_versions" extension
%% (Section 4.2.1), and the legacy_version field MUST be set to 0x0303, which is the version
%% number for TLS 1.2.
@@ -171,13 +174,15 @@ hello(#server_hello{server_version = LegacyVersion,
true ->
case Version of
{3,3} ->
+ IsNew = ssl_session:is_new(OldId, SessionId),
%% TLS 1.2 ServerHello with "supported_versions" (special case)
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt,
- ConnectionStates0, Renegotiation);
+ ConnectionStates0, Renegotiation, IsNew);
SelectedVersion ->
%% TLS 1.3
- {next_state, wait_sh, SelectedVersion}
+ {next_state, wait_sh, SelectedVersion, #{ocsp_stapling => Stapling,
+ ocsp_expect => ocsp_expect(Stapling)}}
end;
false ->
?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
@@ -191,17 +196,29 @@ hello(#server_hello{server_version = Version,
session_id = SessionId,
extensions = HelloExt},
#{versions := SupportedVersions} = SslOpt,
- ConnectionStates0, Renegotiation) ->
+ ConnectionStates0, Renegotiation, OldId) ->
+ IsNew = ssl_session:is_new(OldId, SessionId),
case tls_record:is_acceptable_version(Version, SupportedVersions) of
true ->
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt,
- ConnectionStates0, Renegotiation);
+ ConnectionStates0, Renegotiation, IsNew);
false ->
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
- end;
+ end.
+%%--------------------------------------------------------------------
+-spec hello(#client_hello{}, ssl_options(),
+ {pid(), #session{}, ssl_record:connection_states(),
+ binary() | undefined, ssl:kex_algo()},
+ boolean()) ->
+ {tls_record:tls_version(), ssl:session_id(),
+ ssl_record:connection_states(), alpn | npn, binary() | undefined}|
+ {tls_record:tls_version(), {resumed | new, #session{}},
+ ssl_record:connection_states(), binary() | undefined,
+ HelloExt::map(), {ssl:hash(), ssl:sign_algo()} |
+ undefined} | {atom(), atom()} | {atom(), atom(), tuple()} | #alert{}.
%% TLS 1.2 Server
%% - If "supported_versions" is present (ClientHello):
%% - Select version from "supported_versions" (ignore ClientHello.legacy_version)
@@ -282,6 +299,20 @@ get_tls_handshake(Version, Data, Buffer, Options) ->
get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), Options, []).
%%--------------------------------------------------------------------
+%%% Handshake helper
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+-spec ocsp_nonce(boolean(), boolean()) -> binary() | undefined.
+%%
+%% Description: Get an OCSP nonce
+%%--------------------------------------------------------------------
+ocsp_nonce(true, true) ->
+ public_key:der_encode('Nonce', crypto:strong_rand_bytes(8));
+ocsp_nonce(_OcspNonceOpt, _OcspStaplingOpt) ->
+ undefined.
+
+%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
handle_client_hello(Version,
@@ -294,28 +325,29 @@ handle_client_hello(Version,
signature_algs := SupportedHashSigns,
eccs := SupportedECCs,
honor_ecc_order := ECCOrder} = SslOpts,
- {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _},
+ {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _},
Renegotiation) ->
+ OwnCert = ssl_handshake:select_own_cert(OwnCerts),
case tls_record:is_acceptable_version(Version, Versions) of
true ->
Curves = maps:get(elliptic_curves, HelloExt, undefined),
ClientHashSigns = maps:get(signature_algs, HelloExt, undefined),
ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined),
AvailableHashSigns = ssl_handshake:available_signature_algs(
- ClientHashSigns, SupportedHashSigns, Cert, Version),
+ ClientHashSigns, SupportedHashSigns, OwnCert, Version),
ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
{Type, #session{cipher_suite = CipherSuite} = Session1}
= ssl_handshake:select_session(SugesstedId, CipherSuites,
AvailableHashSigns, Compressions,
- Port, Session0#session{ecc = ECCCurve},
- Version, SslOpts, Cache, CacheCb, Cert),
+ SessIdTracker, Session0#session{ecc = ECCCurve},
+ Version, SslOpts, OwnCert),
case CipherSuite of
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers);
_ ->
#{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
case ssl_handshake:select_hashsign({ClientHashSigns, ClientSignatureSchemes},
- Cert, KeyExAlg,
+ OwnCert, KeyExAlg,
SupportedHashSigns,
Version) of
#alert{} = Alert ->
@@ -338,7 +370,8 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites,
HelloExt, Version, SslOpts,
Session0, ConnectionStates0,
- Renegotiation) of
+ Renegotiation,
+ Session0#session.is_resumable) of
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol,
ServerHelloExt, HashSign}
@@ -348,13 +381,13 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
- Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
+ Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) ->
try ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite,
Compression, HelloExt, Version,
SslOpt, ConnectionStates0,
- Renegotiation) of
- {ConnectionStates, ProtoExt, Protocol} ->
- {Version, SessionId, ConnectionStates, ProtoExt, Protocol}
+ Renegotiation, IsNew) of
+ {ConnectionStates, ProtoExt, Protocol, OcspState} ->
+ {Version, SessionId, ConnectionStates, ProtoExt, Protocol, OcspState}
catch throw:Alert ->
Alert
end.
@@ -442,3 +475,9 @@ decode_handshake({3, 4}, Tag, Msg) ->
tls_handshake_1_3:decode_handshake(Tag, Msg);
decode_handshake(Version, Tag, Msg) ->
ssl_handshake:decode_handshake(Version, Tag, Msg).
+
+
+ocsp_expect(true) ->
+ staple;
+ocsp_expect(_) ->
+ no_staple.
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index c0c47c26c4..cd9beecb3f 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -31,6 +31,7 @@
-include("ssl_connection.hrl").
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
+-include("tls_record_1_3.hrl").
-include_lib("public_key/include/public_key.hrl").
%% Encode
@@ -50,12 +51,19 @@
do_wait_sh/2,
do_wait_ee/2,
do_wait_cert_cr/2,
+ do_wait_eoed/2,
+ early_data_size/1,
get_ticket_data/3,
maybe_add_binders/3,
maybe_add_binders/4,
- maybe_automatic_session_resumption/1]).
+ maybe_add_early_data_indication/3,
+ maybe_automatic_session_resumption/1,
+ maybe_send_early_data/1,
+ update_current_read/3]).
--export([is_valid_binder/4]).
+-export([get_max_early_data/1,
+ is_valid_binder/4,
+ maybe/0]).
%% crypto:hash(sha256, "HelloRetryRequest").
-define(HELLO_RETRY_REQUEST_RANDOM, <<207,33,173,116,229,154,97,17,
@@ -115,15 +123,82 @@ server_hello_random(server_hello, #security_parameters{server_random = Random})
server_hello_random(hello_retry_request, _) ->
?HELLO_RETRY_REQUEST_RANDOM.
+maybe_add_cookie_extension(#state{ssl_options = #{cookie := false}} = State,
+ ServerHello) ->
+ {State, ServerHello};
+maybe_add_cookie_extension(#state{connection_states = ConnectionStates,
+ ssl_options = #{cookie := true},
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history =
+ {[CH1|_], _}} = HsEnv0} = State,
+ #server_hello{extensions = Extensions0} = ServerHello) ->
+ HKDFAlgo = get_hkdf_algorithm(ConnectionStates),
+ MessageHash0 = message_hash(CH1, HKDFAlgo),
+ MessageHash = iolist_to_binary(MessageHash0),
+
+ %% Encrypt MessageHash
+ IV = crypto:strong_rand_bytes(16),
+ Shard = crypto:strong_rand_bytes(32),
+ Cookie = ssl_cipher:encrypt_data(<<"cookie">>, MessageHash, Shard, IV),
+
+ HsEnv = HsEnv0#handshake_env{cookie_iv_shard = {IV, Shard}},
+ Extensions = Extensions0#{cookie => #cookie{cookie = Cookie}},
+ {State#state{handshake_env = HsEnv},
+ ServerHello#server_hello{extensions = Extensions}};
+maybe_add_cookie_extension(undefined, ClientHello) ->
+ ClientHello;
+maybe_add_cookie_extension(Cookie,
+ #client_hello{extensions = Extensions0} = ClientHello) ->
+ Extensions = Extensions0#{cookie => #cookie{cookie = Cookie}},
+ ClientHello#client_hello{extensions = Extensions}.
+
+validate_cookie(_Cookie, #state{ssl_options = #{cookie := false}}) ->
+ ok;
+validate_cookie(undefined, #state{ssl_options = #{cookie := true}}) ->
+ ok;
+validate_cookie(Cookie0, #state{ssl_options = #{cookie := true},
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history =
+ {[_CH2,_HRR,MessageHash|_], _},
+ cookie_iv_shard = {IV, Shard}}}) ->
+ Cookie = ssl_cipher:decrypt_data(<<"cookie">>, Cookie0, Shard, IV),
+ case Cookie =:= iolist_to_binary(MessageHash) of
+ true ->
+ ok;
+ false ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
+ end.
-encrypted_extensions(#state{handshake_env = #handshake_env{alpn = undefined}}) ->
- #encrypted_extensions{
- extensions = #{}
- };
-encrypted_extensions(#state{handshake_env = #handshake_env{alpn = ALPNProtocol}}) ->
- Extensions = ssl_handshake:add_alpn(#{}, ALPNProtocol),
+encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
+ E0 = #{},
+ E1 = case HandshakeEnv#handshake_env.alpn of
+ undefined ->
+ E0;
+ ALPNProtocol ->
+ ssl_handshake:add_alpn(#{}, ALPNProtocol)
+ end,
+ E2 = case HandshakeEnv#handshake_env.max_frag_enum of
+ undefined ->
+ E1;
+ MaxFragEnum ->
+ E1#{max_frag_enum => MaxFragEnum}
+ end,
+ E3 = case HandshakeEnv#handshake_env.sni_guided_cert_selection of
+ false ->
+ E2;
+ true ->
+ E2#{sni => #sni{hostname = ""}}
+ end,
+ E = case HandshakeEnv#handshake_env.early_data_accepted of
+ false ->
+ E3;
+ true ->
+ E3#{early_data => #early_data_indication{}}
+ end,
#encrypted_extensions{
- extensions = Extensions
+ extensions = E
}.
@@ -179,7 +254,11 @@ filter_tls13_algs(Algo) ->
%% opaque certificate_request_context<0..2^8-1>;
%% CertificateEntry certificate_list<0..2^24-1>;
%% } Certificate;
-certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) ->
+certificate(undefined, _, _, _, client) ->
+ {ok, #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []}};
+certificate([OwnCert], CertDbHandle, CertDbRef, _CRContext, Role) ->
case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
{ok, _, Chain} ->
CertList = chain_to_cert_list(Chain),
@@ -201,8 +280,12 @@ certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) ->
{ok, #certificate_1_3{
certificate_request_context = <<>>,
certificate_list = []}}
- end.
-
+ end;
+certificate([_,_| _] = Chain, _,_,_,_) ->
+ CertList = chain_to_cert_list(Chain),
+ {ok, #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = CertList}}.
certificate_verify(PrivateKey, SignatureScheme,
#state{connection_states = ConnectionStates,
@@ -213,7 +296,7 @@ certificate_verify(PrivateKey, SignatureScheme,
ssl_record:pending_connection_state(ConnectionStates, write),
#security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
- {HashAlgo, _, _} =
+ {HashAlgo, SignAlgo, _} =
ssl_cipher:scheme_to_components(SignatureScheme),
Context = lists:reverse(Messages),
@@ -224,7 +307,7 @@ certificate_verify(PrivateKey, SignatureScheme,
%% Digital signatures use the hash function defined by the selected signature
%% scheme.
- case sign(THash, ContextString, HashAlgo, PrivateKey) of
+ case sign(THash, ContextString, HashAlgo, PrivateKey, SignAlgo) of
{ok, Signature} ->
{ok, #certificate_verify_1_3{
algorithm = SignatureScheme,
@@ -486,24 +569,9 @@ certificate_entry(DER) ->
%% 79
%% 00
%% 0101010101010101010101010101010101010101010101010101010101010101
-sign(THash, Context, HashAlgo, #'ECPrivateKey'{} = PrivateKey) ->
- Content = build_content(Context, THash),
- try public_key:sign(Content, HashAlgo, PrivateKey) of
- Signature ->
- {ok, Signature}
- catch
- error:badarg ->
- {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
- end;
-sign(THash, Context, HashAlgo, PrivateKey) ->
+sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) ->
Content = build_content(Context, THash),
-
- %% The length of the Salt MUST be equal to the length of the output
- %% of the digest algorithm: rsa_pss_saltlen = -1
- try public_key:sign(Content, HashAlgo, PrivateKey,
- [{rsa_padding, rsa_pkcs1_pss_padding},
- {rsa_pss_saltlen, -1},
- {rsa_mgf1_md, HashAlgo}]) of
+ try ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo) of
Signature ->
{ok, Signature}
catch
@@ -511,25 +579,9 @@ sign(THash, Context, HashAlgo, PrivateKey) ->
{error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
end.
-
-verify(THash, Context, HashAlgo, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
+verify(THash, Context, HashAlgo, SignAlgo, Signature, PublicKeyInfo) ->
Content = build_content(Context, THash),
- try public_key:verify(Content, HashAlgo, Signature, {PublicKey, PublicKeyParams}) of
- Result ->
- {ok, Result}
- catch
- error:badarg ->
- {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
- end;
-verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyParams}) ->
- Content = build_content(Context, THash),
-
- %% The length of the Salt MUST be equal to the length of the output
- %% of the digest algorithm: rsa_pss_saltlen = -1
- try public_key:verify(Content, HashAlgo, Signature, PublicKey,
- [{rsa_padding, rsa_pkcs1_pss_padding},
- {rsa_pss_saltlen, -1},
- {rsa_mgf1_md, HashAlgo}]) of
+ try ssl_handshake:verify_signature({3, 4}, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of
Result ->
{ok, Result}
catch
@@ -537,7 +589,6 @@ verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyP
{error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)}
end.
-
build_content(Context, THash) ->
Prefix = binary:copy(<<32>>, 64),
<<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>.
@@ -551,34 +602,44 @@ build_content(Context, THash) ->
%% TLS Server
do_start(#client_hello{cipher_suites = ClientCiphers,
session_id = SessionId,
- extensions = Extensions} = _Hello,
- #state{connection_states = _ConnectionStates0,
- ssl_options = #{ciphers := ServerCiphers,
+ extensions = Extensions} = Hello,
+ #state{ssl_options = #{ciphers := ServerCiphers,
signature_algs := ServerSignAlgs,
supported_groups := ServerGroups0,
alpn_preferred_protocols := ALPNPreferredProtocols,
- honor_cipher_order := HonorCipherOrder},
- session = #session{own_certificate = Cert}} = State0) ->
+ keep_secrets := KeepSecrets,
+ honor_cipher_order := HonorCipherOrder,
+ early_data := EarlyDataEnabled}} = State0) ->
+ SNI = maps:get(sni, Extensions, undefined),
ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined),
- ClientGroups = get_supported_groups(ClientGroups0),
- ServerGroups = get_supported_groups(ServerGroups0),
-
- ClientShares0 = maps:get(key_share, Extensions, undefined),
- ClientShares = get_key_shares(ClientShares0),
-
- OfferedPSKs = get_offered_psks(Extensions),
-
- ClientALPN0 = maps:get(alpn, Extensions, undefined),
- ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
+ EarlyDataIndication = maps:get(early_data, Extensions, undefined),
+ {Ref,Maybe} = maybe(),
+ try
+ ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
+ ServerGroups = Maybe(get_supported_groups(ServerGroups0)),
+
+ ClientShares0 = maps:get(key_share, Extensions, undefined),
+ ClientShares = get_key_shares(ClientShares0),
+
+ OfferedPSKs = get_offered_psks(Extensions),
+
+ ClientALPN0 = maps:get(alpn, Extensions, undefined),
+ ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
+
+ ClientSignAlgs = get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ClientSignAlgsCert = get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+
+ CookieExt = maps:get(cookie, Extensions, undefined),
+ Cookie = get_cookie(CookieExt),
- ClientSignAlgs = get_signature_scheme_list(
- maps:get(signature_algs, Extensions, undefined)),
- ClientSignAlgsCert = get_signature_scheme_list(
- maps:get(signature_algs_cert, Extensions, undefined)),
+ #state{connection_states = ConnectionStates0,
+ session = #session{own_certificates = [Cert | _]}} = State1 =
+ Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)),
- {Ref,Maybe} = maybe(),
+ Maybe(validate_cookie(Cookie, State1)),
- try
%% Handle ALPN extension if ALPN is configured
ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)),
@@ -587,17 +648,15 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% and a signature algorithm/certificate pair to authenticate itself to
%% the client.
Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)),
-
Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)),
Maybe(validate_client_key_share(ClientGroups, ClientShares)),
-
- {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
+ {PublicKeyAlgo, SignAlgo, SignHash, RSAKeySize} = get_certificate_params(Cert),
%% Check if client supports signature algorithm of server certificate
Maybe(check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert)),
%% Select signature algorithm (used in CertificateVerify message).
- SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)),
+ SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs)),
%% Select client public key. If no public key found in ClientShares or
%% ClientShares is empty, trigger HelloRetryRequest as we were able
@@ -608,7 +667,23 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% Generate server_share
KeyShare = ssl_cipher:generate_server_share(Group),
- State1 = update_start_state(State0,
+ State2 = case maps:get(max_frag_enum, Extensions, undefined) of
+ MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) ->
+ ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+ HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum},
+ State1#state{handshake_env = HsEnv1,
+ connection_states = ConnectionStates1};
+ _ ->
+ State1
+ end,
+
+ State3 = if KeepSecrets =:= true ->
+ set_client_random(State2, Hello#client_hello.random);
+ true ->
+ State2
+ end,
+
+ State4 = update_start_state(State3,
#{cipher => Cipher,
key_share => KeyShare,
session_id => SessionId,
@@ -623,13 +698,15 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% message if it is able to find an acceptable set of parameters but the
%% ClientHello does not contain sufficient information to proceed with
%% the handshake.
- case Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)) of
+ case Maybe(send_hello_retry_request(State4, ClientPubKey, KeyShare, SessionId)) of
{_, start} = NextStateTuple ->
NextStateTuple;
- {_, negotiated} = NextStateTuple ->
+ {State5, negotiated} ->
+ %% Determine if early data is accepted
+ State = handle_early_data(State5, EarlyDataEnabled, EarlyDataIndication),
%% Exclude any incompatible PSKs.
- PSK = Maybe(handle_pre_shared_key(State1, OfferedPSKs, Cipher)),
- Maybe(session_resumption(NextStateTuple, PSK))
+ PSK = Maybe(handle_pre_shared_key(State, OfferedPSKs, Cipher)),
+ Maybe(session_resumption({State, negotiated}, PSK))
end
catch
{Ref, #alert{} = Alert} ->
@@ -642,22 +719,26 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
#state{static_env = #static_env{role = client,
host = Host,
port = Port,
+ protocol_cb = Connection,
transport_cb = Transport,
socket = Socket},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState},
connection_env = #connection_env{negotiated_version = NegotiatedVersion},
ssl_options = #{ciphers := ClientCiphers,
supported_groups := ClientGroups0,
use_ticket := UseTicket,
session_tickets := SessionTickets,
log_level := LogLevel} = SslOpts,
- session = #session{own_certificate = Cert} = Session0,
+ session = #session{own_certificates = OwnCerts} = Session0,
connection_states = ConnectionStates0
} = State0) ->
- ClientGroups = get_supported_groups(ClientGroups0),
-
{Ref,Maybe} = maybe(),
try
+ ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
+ CookieExt = maps:get(cookie, Extensions, undefined),
+ Cookie = get_cookie(CookieExt),
+
ServerKeyShare = maps:get(key_share, Extensions, undefined),
SelectedGroup = get_selected_group(ServerKeyShare),
@@ -679,9 +760,12 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
%% of the triggering HelloRetryRequest.
ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]),
TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
+ OcspNonce = maps:get(ocsp_nonce, OcspState, undefined),
Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- SessionId, Renegotiation, Cert, ClientKeyShare,
- TicketData),
+ SessionId, Renegotiation, OwnCerts, ClientKeyShare,
+ TicketData, OcspNonce),
+ %% Echo cookie received in HelloRetryrequest
+ Hello1 = maybe_add_cookie_extension(Cookie, Hello0),
%% Update state
State1 = update_start_state(State0,
@@ -695,15 +779,20 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
#state{handshake_env = #handshake_env{tls_handshake_history = HHistory0}} = State2,
%% Update pre_shared_key extension with binders (TLS 1.3)
- Hello = tls_handshake_1_3:maybe_add_binders(Hello0, HHistory0, TicketData, NegotiatedVersion),
+ Hello = tls_handshake_1_3:maybe_add_binders(Hello1, HHistory0, TicketData, NegotiatedVersion),
+
+ {BinMsg0, ConnectionStates, HHistory} =
+ Connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0),
+
+ %% D.4. Middlebox Compatibility Mode
+ {#state{handshake_env = HsEnv} = State3, BinMsg} =
+ maybe_prepend_change_cipher_spec(State2, BinMsg0),
- {BinMsg, ConnectionStates, HHistory} =
- tls_connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0),
tls_socket:send(Transport, Socket, BinMsg),
ssl_logger:debug(LogLevel, outbound, 'handshake', Hello),
ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
- State = State2#state{
+ State = State3#state{
connection_states = ConnectionStates,
session = Session0#session{session_id = Hello#client_hello.session_id},
handshake_env = HsEnv#handshake_env{tls_handshake_history = HHistory},
@@ -716,9 +805,12 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
Alert
end.
-
do_negotiated({start_handshake, PSK0},
#state{connection_states = ConnectionStates0,
+ handshake_env =
+ #handshake_env{
+ early_data_accepted = EarlyDataAccepted},
+ static_env = #static_env{protocol_cb = Connection},
session = #session{session_id = SessionId,
ecc = SelectedGroup,
dh_public_value = ClientPublicKey},
@@ -730,47 +822,61 @@ do_negotiated({start_handshake, PSK0},
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{prf_algorithm = HKDF} = SecParamsR,
-
{Ref,Maybe} = maybe(),
try
%% Create server_hello
ServerHello = server_hello(server_hello, SessionId, KeyShare, PSK0, ConnectionStates0),
-
- {State1, _} = tls_connection:send_handshake(ServerHello, State0),
+ State1 = Connection:queue_handshake(ServerHello, State0),
+ %% D.4. Middlebox Compatibility Mode
+ State2 = maybe_queue_change_cipher_spec(State1, last),
PSK = get_pre_shared_key(PSK0, HKDF),
- State2 =
+ State3 =
calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup,
- PSK, State1),
-
- State3 = ssl_record:step_encryption_state(State2),
+ PSK, State2),
+
+ %% Step only write state if early_data is accepted
+ State4 =
+ case EarlyDataAccepted of
+ true ->
+ ssl_record:step_encryption_state_write(State3);
+ false ->
+ %% Read state is overwritten when hanshake secrets are set.
+ %% Trial_decryption and early_data_limit must be set here!
+ update_current_read(
+ ssl_record:step_encryption_state(State3),
+ true, %% trial_decryption
+ false %% early data limit
+ )
+
+ end,
%% Create EncryptedExtensions
- EncryptedExtensions = encrypted_extensions(State2),
+ EncryptedExtensions = encrypted_extensions(State4),
%% Encode EncryptedExtensions
- State4 = tls_connection:queue_handshake(EncryptedExtensions, State3),
+ State5 = Connection:queue_handshake(EncryptedExtensions, State4),
%% Create and send CertificateRequest ({verify, verify_peer})
- {State5, NextState} = maybe_send_certificate_request(State4, SslOpts, PSK0),
+ {State6, NextState} = maybe_send_certificate_request(State5, SslOpts, PSK0),
%% Create and send Certificate (if PSK is undefined)
- State6 = Maybe(maybe_send_certificate(State5, PSK0)),
+ State7 = Maybe(maybe_send_certificate(State6, PSK0)),
%% Create and send CertificateVerify (if PSK is undefined)
- State7 = Maybe(maybe_send_certificate_verify(State6, PSK0)),
+ State8 = Maybe(maybe_send_certificate_verify(State7, PSK0)),
%% Create Finished
- Finished = finished(State7),
+ Finished = finished(State8),
%% Encode Finished
- State8 = tls_connection:queue_handshake(Finished, State7),
+ State9 = Connection:queue_handshake(Finished, State8),
%% Send first flight
- {State9, _} = tls_connection:send_handshake_flight(State8),
+ {State, _} = Connection:send_handshake_flight(State9),
- {State9, NextState}
+ {State, NextState}
catch
{Ref, #alert{} = Alert} ->
@@ -790,10 +896,15 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
end.
-do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) ->
+do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, #state{static_env = #static_env{role = Role}} = State0) ->
{Ref,Maybe} = maybe(),
try
- State1 = Maybe(verify_signature_algorithm(State0, CertificateVerify)),
+ State1 = case Role of
+ server ->
+ Maybe(verify_signature_algorithm(State0, CertificateVerify));
+ client ->
+ State0
+ end,
Maybe(verify_certificate_verify(State1, CertificateVerify))
catch
{Ref, {#alert{} = Alert, State}} ->
@@ -824,31 +935,29 @@ do_wait_finished(#finished{verify_data = VerifyData},
end;
%% TLS Client
do_wait_finished(#finished{verify_data = VerifyData},
- #state{static_env = #static_env{role = client}} = State0) ->
-
+ #state{static_env = #static_env{role = client,
+ protocol_cb = Connection}} = State0) ->
+
{Ref,Maybe} = maybe(),
try
Maybe(validate_finished(State0, VerifyData)),
-
+ %% D.4. Middlebox Compatibility Mode
+ State1 = maybe_queue_change_cipher_spec(State0, first),
+ %% Signal change of cipher
+ State2 = maybe_send_end_of_early_data(State1),
%% Maybe send Certificate + CertificateVerify
- State1 = Maybe(maybe_queue_cert_cert_cv(State0)),
-
- Finished = finished(State1),
-
+ State3 = Maybe(maybe_queue_cert_cert_cv(State2)),
+ Finished = finished(State3),
%% Encode Finished
- State2 = tls_connection:queue_handshake(Finished, State1),
-
+ State4 = Connection:queue_handshake(Finished, State3),
%% Send first flight
- {State3, _} = tls_connection:send_handshake_flight(State2),
-
- State4 = calculate_traffic_secrets(State3),
- State5 = maybe_calculate_resumption_master_secret(State4),
- State6 = forget_master_secret(State5),
-
+ {State5, _} = Connection:send_handshake_flight(State4),
+ State6 = calculate_traffic_secrets(State5),
+ State7 = maybe_calculate_resumption_master_secret(State6),
+ State8 = forget_master_secret(State7),
%% Configure traffic keys
- ssl_record:step_encryption_state(State6)
-
+ ssl_record:step_encryption_state(State8)
catch
{Ref, #alert{} = Alert} ->
Alert
@@ -863,14 +972,15 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
supported_groups := ClientGroups0,
session_tickets := SessionTickets,
use_ticket := UseTicket}} = State0) ->
- ClientGroups = get_supported_groups(ClientGroups0),
- ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
- ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
- SelectedIdentity = get_selected_identity(ServerPreSharedKey),
- ClientKeyShare = get_key_shares(ClientKeyShare0),
-
+
{Ref,Maybe} = maybe(),
try
+ ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
+ ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
+ ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
+ SelectedIdentity = get_selected_identity(ServerPreSharedKey),
+ ClientKeyShare = get_key_shares(ClientKeyShare0),
+
%% Go to state 'start' if server replies with 'HelloRetryRequest'.
Maybe(maybe_hello_retry_request(ServerHello, State0)),
@@ -902,8 +1012,8 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
PSK = Maybe(get_pre_shared_key(SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity)),
State3 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup,
PSK, State2),
- State4 = ssl_record:step_encryption_state(State3),
-
+ %% State4 = ssl_record:step_encryption_state(State3),
+ State4 = ssl_record:step_encryption_state_read(State3),
{State4, wait_ee}
catch
@@ -918,21 +1028,30 @@ do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) ->
ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
ALPNProtocol = get_alpn(ALPNProtocol0),
+ EarlyDataIndication = maps:get(early_data, Extensions, undefined),
{Ref, Maybe} = maybe(),
try
+ %% RFC 6066: handle received/expected maximum fragment length
+ Maybe(maybe_max_fragment_length(Extensions, State0)),
+
+ %% Check if early_data is accepted/rejected
+ State1 = maybe_check_early_data_indication(EarlyDataIndication, State0),
+
%% Go to state 'wait_finished' if using PSK.
- Maybe(maybe_resumption(State0)),
+ Maybe(maybe_resumption(State1)),
%% Update state
- #state{handshake_env = HsEnv} = State0,
- State1 = State0#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}},
+ #state{handshake_env = HsEnv} = State1,
+ State2 = State1#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}},
- {State1, wait_cert_cr}
+ {State2, wait_cert_cr}
catch
{Ref, {State, StateName}} ->
- {State, StateName}
+ {State, StateName};
+ {Ref, #alert{} = Alert} ->
+ Alert
end.
@@ -956,6 +1075,25 @@ do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) ->
end.
+do_wait_eoed(#end_of_early_data{}, State0) ->
+ {Ref,_Maybe} = maybe(),
+ try
+ %% Step read state to enable reading handshake messages from the client.
+ %% Write state is already stepped in state 'negotiated'.
+ State1 = ssl_record:step_encryption_state_read(State0),
+
+ %% Early data has been received, no more early data is expected.
+ HsEnv = (State1#state.handshake_env)#handshake_env{early_data_accepted = false},
+ State2 = State1#state{handshake_env = HsEnv},
+ {State2, wait_finished}
+ catch
+ {Ref, #alert{} = Alert} ->
+ {Alert, State0};
+ {Ref, {#alert{} = Alert, State}} ->
+ {Alert, State}
+ end.
+
+
%% For reasons of backward compatibility with middleboxes (see
%% Appendix D.4), the HelloRetryRequest message uses the same structure
%% as the ServerHello, but with Random set to the special value of the
@@ -972,6 +1110,16 @@ maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} =
maybe_hello_retry_request(_, _) ->
ok.
+maybe_max_fragment_length(Extensions, State) ->
+ ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined),
+ ClientMaxFragEnum = ssl_handshake:max_frag_enum(
+ maps:get(max_fragment_length, State#state.ssl_options, undefined)),
+ if ServerMaxFragEnum == ClientMaxFragEnum ->
+ ok;
+ true ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
+ end.
+
maybe_resumption(#state{handshake_env = #handshake_env{resumption = true}} = State) ->
{error, {State, wait_finished}};
@@ -985,17 +1133,68 @@ handle_resumption(#state{handshake_env = HSEnv0} = State, _) ->
HSEnv = HSEnv0#handshake_env{resumption = true},
State#state{handshake_env = HSEnv}.
+%% @doc Enqueues a change_cipher_spec record as the first/last message of
+%% the current flight buffer
+%% @end
+maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, first) ->
+ {State, FlightBuffer} = maybe_prepend_change_cipher_spec(State0, FlightBuffer0),
+ State#state{flight_buffer = FlightBuffer};
+maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, last) ->
+ {State, FlightBuffer} = maybe_append_change_cipher_spec(State0, FlightBuffer0),
+ State#state{flight_buffer = FlightBuffer}.
+
+%% @doc Prepends a change_cipher_spec record to the input binary
+%%
+%% It can only prepend the change_cipher_spec record only once in
+%% order to accurately emulate a legacy TLS 1.2 connection.
+%%
+%% D.4. Middlebox Compatibility Mode
+%% If not offering early data, the client sends a dummy
+%% change_cipher_spec record (see the third paragraph of Section 5)
+%% immediately before its second flight. This may either be before
+%% its second ClientHello or before its encrypted handshake flight.
+%% If offering early data, the record is placed immediately after the
+%% first ClientHello.
+%% @end
+maybe_prepend_change_cipher_spec(#state{
+ ssl_options =
+ #{middlebox_comp_mode := true},
+ handshake_env =
+ #handshake_env{
+ change_cipher_spec_sent = false} = HSEnv} = State, Bin) ->
+ CCSBin = create_change_cipher_spec(State),
+ {State#state{handshake_env =
+ HSEnv#handshake_env{change_cipher_spec_sent = true}},
+ [CCSBin|Bin]};
+maybe_prepend_change_cipher_spec(State, Bin) ->
+ {State, Bin}.
+
+%% @doc Appends a change_cipher_spec record to the input binary
+%% @end
+maybe_append_change_cipher_spec(#state{
+ ssl_options =
+ #{middlebox_comp_mode := true},
+ handshake_env =
+ #handshake_env{
+ change_cipher_spec_sent = false} = HSEnv} = State, Bin) ->
+ CCSBin = create_change_cipher_spec(State),
+ {State#state{handshake_env =
+ HSEnv#handshake_env{change_cipher_spec_sent = true}},
+ Bin ++ [CCSBin]};
+maybe_append_change_cipher_spec(State, Bin) ->
+ {State, Bin}.
maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) ->
{ok, State};
maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
session = #session{session_id = _SessionId,
- own_certificate = OwnCert},
+ own_certificates = OwnCerts},
ssl_options = #{} = _SslOpts,
key_share = _KeyShare,
handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
static_env = #static_env{
role = client,
+ protocol_cb = Connection,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
socket = _Socket,
@@ -1004,11 +1203,10 @@ maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
{Ref,Maybe} = maybe(),
try
%% Create Certificate
- Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, client)),
+ Certificate = Maybe(certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, client)),
%% Encode Certificate
- State1 = tls_connection:queue_handshake(Certificate, State0),
-
+ State1 = Connection:queue_handshake(Certificate, State0),
%% Maybe create and queue CertificateVerify
State = Maybe(maybe_queue_cert_verify(Certificate, State1)),
{ok, State}
@@ -1026,12 +1224,13 @@ maybe_queue_cert_verify(_Certificate,
#state{connection_states = _ConnectionStates0,
session = #session{sign_alg = SignatureScheme},
connection_env = #connection_env{private_key = CertPrivateKey},
- static_env = #static_env{role = client}
+ static_env = #static_env{role = client,
+ protocol_cb = Connection}
} = State) ->
{Ref,Maybe} = maybe(),
try
CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)),
- {ok, tls_connection:queue_handshake(CertificateVerify, State)}
+ {ok, Connection:queue_handshake(CertificateVerify, State)}
catch
{Ref, #alert{} = Alert} ->
{error, Alert}
@@ -1064,15 +1263,21 @@ compare_verify_data(_, _) ->
{error, ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)}.
-send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0,
+send_hello_retry_request(#state{connection_states = ConnectionStates0,
+ static_env = #static_env{protocol_cb = Connection}} = State0,
no_suitable_key, KeyShare, SessionId) ->
- ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0),
- {State1, _} = tls_connection:send_handshake(ServerHello, State0),
+ ServerHello0 = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0),
+ {State1, ServerHello} = maybe_add_cookie_extension(State0, ServerHello0),
+
+ State2 = Connection:queue_handshake(ServerHello, State1),
+ %% D.4. Middlebox Compatibility Mode
+ State3 = maybe_queue_change_cipher_spec(State2, last),
+ {State4, _} = Connection:send_handshake_flight(State3),
%% Update handshake history
- State2 = replace_ch1_with_message_hash(State1),
+ State5 = replace_ch1_with_message_hash(State4),
- {ok, {State2, start}};
+ {ok, {State5, start}};
send_hello_retry_request(State0, _, _, _) ->
%% Suitable key found.
{ok, {State0, negotiated}}.
@@ -1082,33 +1287,55 @@ session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State
session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, negotiated}, undefined)
when Tickets =/= disabled ->
{ok, {State, negotiated}};
-session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State0, negotiated}, PSK)
+session_resumption({#state{ssl_options = #{session_tickets := Tickets},
+ handshake_env = #handshake_env{
+ early_data_accepted = false}} = State0, negotiated}, PSK)
when Tickets =/= disabled ->
State = handle_resumption(State0, ok),
- {ok, {State, negotiated, PSK}}.
-
-
+ {ok, {State, negotiated, PSK}};
+session_resumption({#state{ssl_options = #{session_tickets := Tickets},
+ handshake_env = #handshake_env{
+ early_data_accepted = true}} = State0, negotiated}, PSK0)
+ when Tickets =/= disabled ->
+ State1 = handle_resumption(State0, ok),
+ %% TODO Refactor PSK-tuple {Index, PSK}, index might not be needed.
+ {_ , PSK} = PSK0,
+ State2 = calculate_client_early_traffic_secret(State1, PSK),
+ %% Set 0-RTT traffic keys for reading early_data
+ State3 = ssl_record:step_encryption_state_read(State2),
+ State = update_current_read(State3, true, true),
+ {ok, {State, negotiated, PSK0}}.
+
+%% Session resumption with early_data
+maybe_send_certificate_request(#state{
+ handshake_env =
+ #handshake_env{
+ early_data_accepted = true}} = State,
+ _, PSK) when PSK =/= undefined ->
+ %% Go wait for End of Early Data
+ {State, wait_eoed};
%% Do not send CR during session resumption
maybe_send_certificate_request(State, _, PSK) when PSK =/= undefined ->
{State, wait_finished};
maybe_send_certificate_request(State, #{verify := verify_none}, _) ->
{State, wait_finished};
-maybe_send_certificate_request(State, #{verify := verify_peer,
- signature_algs := SignAlgs,
- signature_algs_cert := SignAlgsCert}, _) ->
+maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Connection}} = State,
+ #{verify := verify_peer,
+ signature_algs := SignAlgs,
+ signature_algs_cert := SignAlgsCert}, _) ->
CertificateRequest = certificate_request(SignAlgs, SignAlgsCert),
- {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}.
-
+ {Connection:queue_handshake(CertificateRequest, State), wait_cert}.
maybe_send_certificate(State, PSK) when PSK =/= undefined ->
{ok, State};
-maybe_send_certificate(#state{session = #session{own_certificate = OwnCert},
+maybe_send_certificate(#state{session = #session{own_certificates = OwnCerts},
static_env = #static_env{
+ protocol_cb = Connection,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef}} = State, _) ->
- case certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server) of
+ case certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, server) of
{ok, Certificate} ->
- {ok, tls_connection:queue_handshake(Certificate, State)};
+ {ok, Connection:queue_handshake(Certificate, State)};
Error ->
Error
end.
@@ -1117,11 +1344,12 @@ maybe_send_certificate(#state{session = #session{own_certificate = OwnCert},
maybe_send_certificate_verify(State, PSK) when PSK =/= undefined ->
{ok, State};
maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme},
+ static_env = #static_env{protocol_cb = Connection},
connection_env = #connection_env{
private_key = CertPrivateKey}} = State, _) ->
case certificate_verify(CertPrivateKey, SignatureScheme, State, server) of
{ok, CertificateVerify} ->
- {ok, tls_connection:queue_handshake(CertificateVerify, State)};
+ {ok, Connection:queue_handshake(CertificateVerify, State)};
Error ->
Error
end.
@@ -1143,41 +1371,71 @@ maybe_send_session_ticket(#state{ssl_options = #{session_tickets := disabled}} =
maybe_send_session_ticket(State, 0) ->
State;
maybe_send_session_ticket(#state{connection_states = ConnectionStates,
- static_env = #static_env{trackers = Trackers}} = State0, N) ->
+ static_env = #static_env{trackers = Trackers,
+ protocol_cb = Connection}
+
+ } = State0, N) ->
Tracker = proplists:get_value(session_tickets_tracker, Trackers),
#{security_parameters := SecParamsR} =
ssl_record:current_connection_state(ConnectionStates, read),
#security_parameters{prf_algorithm = HKDF,
resumption_master_secret = RMS} = SecParamsR,
Ticket = tls_server_session_ticket:new(Tracker, HKDF, RMS),
- {State, _} = tls_connection:send_handshake(Ticket, State0),
+ {State, _} = Connection:send_handshake(Ticket, State0),
maybe_send_session_ticket(State, N - 1).
+create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) ->
+ %% Dummy connection_states with NULL cipher
+ ConnectionStates =
+ #{current_write =>
+ #{compression_state => undefined,
+ cipher_state => undefined,
+ sequence_number => 1,
+ security_parameters =>
+ #security_parameters{
+ bulk_cipher_algorithm = 0,
+ compression_algorithm = ?NULL,
+ mac_algorithm = ?NULL
+ },
+ mac_secret => undefined}},
+ {BinChangeCipher, _} =
+ tls_record:encode_change_cipher_spec(?LEGACY_VERSION, ConnectionStates),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher),
+ [BinChangeCipher].
+
process_certificate_request(#certificate_request_1_3{},
- #state{session = #session{own_certificate = undefined}} = State) ->
+ #state{session = #session{own_certificates = undefined}} = State) ->
{ok, {State#state{client_certificate_requested = true}, wait_cert}};
process_certificate_request(#certificate_request_1_3{
- extensions = Extensions},
- #state{session = #session{own_certificate = Cert} = Session} = State) ->
+ extensions = Extensions},
+ #state{ssl_options = #{signature_algs := ClientSignAlgs},
+ session = #session{own_certificates = [Cert|_]} = Session} =
+ State) ->
ServerSignAlgs = get_signature_scheme_list(
maps:get(signature_algs, Extensions, undefined)),
ServerSignAlgsCert = get_signature_scheme_list(
maps:get(signature_algs_cert, Extensions, undefined)),
- {_PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
-
- %% Check if server supports signature algorithm of client certificate
- case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of
- ok ->
- {ok, {State#state{client_certificate_requested = true}, wait_cert}};
- {error, _} ->
- %% Certificate not supported: send empty certificate in state 'wait_finished'
+ {PublicKeyAlgo, SignAlgo, SignHash, MaybeRSAKeySize} = get_certificate_params(Cert),
+ {Ref, Maybe} = maybe(),
+ try
+ SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, MaybeRSAKeySize, ServerSignAlgs, ClientSignAlgs)),
+ %% Check if server supports signature algorithm of client certificate
+ case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of
+ ok ->
{ok, {State#state{client_certificate_requested = true,
- session = Session#session{own_certificate = undefined}}, wait_cert}}
+ session = Session#session{sign_alg = SelectedSignAlg}}, wait_cert}};
+ {error, _} ->
+ %% Certificate not supported: send empty certificate in state 'wait_finished'
+ {ok, {State#state{client_certificate_requested = true,
+ session = Session#session{own_certificates = undefined}}, wait_cert}}
+ end
+ catch
+ {Ref, #alert{} = Alert} ->
+ Alert
end.
-
process_certificate(#certificate_1_3{
certificate_request_context = <<>>,
certificate_list = []},
@@ -1189,7 +1447,6 @@ process_certificate(#certificate_1_3{
certificate_list = []},
#state{ssl_options =
#{fail_if_no_peer_cert := true}} = State0) ->
-
%% At this point the client believes that the connection is up and starts using
%% its traffic secrets. In order to be able send an proper Alert to the client
%% the server should also change its connection state and use the traffic
@@ -1197,56 +1454,30 @@ process_certificate(#certificate_1_3{
State1 = calculate_traffic_secrets(State0),
State = ssl_record:step_encryption_state(State1),
{error, {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}};
-process_certificate(#certificate_1_3{certificate_list = Certs0},
- #state{ssl_options =
- #{signature_algs := SignAlgs,
- signature_algs_cert := SignAlgsCert} = SslOptions,
- static_env =
- #static_env{
- role = Role,
- host = Host,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbHandle}} = State0) ->
- %% TODO: handle extensions!
- %% Remove extensions from list of certificates!
- Certs = convert_certificate_chain(Certs0),
- case is_supported_signature_algorithm(Certs, SignAlgs, SignAlgsCert) of
- true ->
- case validate_certificate_chain(Certs, CertDbHandle, CertDbRef,
- SslOptions, CRLDbHandle, Role, Host) of
- {ok, {PeerCert, PublicKeyInfo}} ->
- State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
- {ok, {State, wait_cv}};
- {error, Reason} ->
- State = update_encryption_state(Role, State0),
- {error, {Reason, State}};
- {ok, #alert{} = Alert} ->
- State = update_encryption_state(Role, State0),
- {error, {Alert, State}}
- end;
- false ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {error, {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- "Client certificate uses unsupported signature algorithm"), State}}
+process_certificate(#certificate_1_3{certificate_list = CertEntries},
+ #state{ssl_options = SslOptions,
+ static_env =
+ #static_env{
+ role = Role,
+ host = Host,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbHandle},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState}} = State0) ->
+ case validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
+ SslOptions, CRLDbHandle, Role, Host, OcspState) of
+ {ok, {PeerCert, PublicKeyInfo}} ->
+ State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
+ {ok, {State, wait_cv}};
+ {error, Reason} ->
+ State = update_encryption_state(Role, State0),
+ {error, {Reason, State}};
+ {ok, #alert{} = Alert} ->
+ State = update_encryption_state(Role, State0),
+ {error, {Alert, State}}
end.
-
-%% TODO: check whole chain!
-is_supported_signature_algorithm(Certs, SignAlgs, undefined) ->
- is_supported_signature_algorithm(Certs, SignAlgs);
-is_supported_signature_algorithm(Certs, _, SignAlgsCert) ->
- is_supported_signature_algorithm(Certs, SignAlgsCert).
-%%
-is_supported_signature_algorithm([BinCert|_], SignAlgs0) ->
- #'OTPCertificate'{signatureAlgorithm = SignAlg} =
- public_key:pkix_decode_cert(BinCert, otp),
- SignAlgs = filter_tls13_algs(SignAlgs0),
- Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
- lists:member(Scheme, SignAlgs).
-
-
%% Sets correct encryption state when sending Alerts in shared states that use different secrets.
%% - If client: use handshake secrets.
%% - If server: use traffic secrets as by this time the client's state machine
@@ -1258,46 +1489,34 @@ update_encryption_state(client, State) ->
State.
-validate_certificate_chain(Certs, CertDbHandle, CertDbRef,
+validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
#{server_name_indication := ServerNameIndication,
partial_chain := PartialChain,
- verify_fun := VerifyFun,
- customize_hostname_check := CustomizeHostnameCheck,
- crl_check := CrlCheck,
- log_level := LogLevel,
- depth := Depth} = SslOptions,
- CRLDbHandle, Role, Host) ->
+ ocsp_responder_certs := OcspResponderCerts
+ } = SslOptions, CRLDbHandle, Role, Host, OcspState0) ->
+ {Certs, CertExt, OcspState} = split_cert_entries(CertEntries, OcspState0),
ServerName = ssl_handshake:server_name(ServerNameIndication, Host, Role),
- [PeerCert | ChainCerts ] = Certs,
- try
- {TrustedCert, CertPath} =
- ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef,
+ [PeerCert | _ChainCerts ] = Certs,
+ try
+ PathsAndAnchors =
+ ssl_certificate:trusted_cert_and_paths(Certs, CertDbHandle, CertDbRef,
PartialChain),
- ValidationFunAndState =
- ssl_handshake:validation_fun_and_state(VerifyFun, Role,
- CertDbHandle, CertDbRef, ServerName,
- CustomizeHostnameCheck,
- CrlCheck, CRLDbHandle, CertPath, LogLevel),
- Options = [{max_path_length, Depth},
- {verify_fun, ValidationFunAndState}],
- %% TODO: Validate if Certificate is using a supported signature algorithm
- %% (signature_algs_cert)!
- case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
- {ok, {PublicKeyInfo,_}} ->
- {ok, {PeerCert, PublicKeyInfo}};
- {error, Reason} ->
- {ok, ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts,
- SslOptions, Options,
- CertDbHandle, CertDbRef)}
- end
- catch
- error:{badmatch,{error, {asn1, Asn1Reason}}} ->
- %% ASN-1 decode of certificate somehow failed
- {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})};
- error:OtherReason ->
- {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})}
- end.
-
+ case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ {3, 4}, SslOptions, #{cert_ext => CertExt,
+ ocsp_state => OcspState,
+ ocsp_responder_certs => OcspResponderCerts}) of
+ {ok, {PublicKeyInfo,_}} ->
+ {ok, {PeerCert, PublicKeyInfo}};
+ {error, Reason} ->
+ {ok, ssl_handshake:path_validation_alert(Reason)}
+ end
+ catch
+ error:{badmatch,{error, {asn1, Asn1Reason}}} ->
+ %% ASN-1 decode of certificate somehow failed
+ {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})};
+ error:OtherReason ->
+ {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})}
+ end.
store_peer_cert(#state{session = Session,
handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) ->
@@ -1305,13 +1524,21 @@ store_peer_cert(#state{session = Session,
handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}}.
-convert_certificate_chain(Certs) ->
- Fun = fun(#certificate_entry{data = Data}) ->
- {true, Data};
- (_) ->
- false
- end,
- lists:filtermap(Fun, Certs).
+split_cert_entries(CertEntries, OcspState) ->
+ split_cert_entries(CertEntries, OcspState, [], #{}).
+split_cert_entries([], OcspState, Chain, Ext) ->
+ {lists:reverse(Chain), Ext, OcspState};
+split_cert_entries([#certificate_entry{data = DerCert,
+ extensions = Extensions0} | CertEntries], OcspState0, Chain, Ext) ->
+ Id = public_key:pkix_subject_id(DerCert),
+ Extensions = [ExtValue || {_, ExtValue} <- maps:to_list(Extensions0)],
+ OcspState = case maps:get(status_request, Extensions0, undefined) of
+ undefined ->
+ OcspState0;
+ _ ->
+ OcspState0#{ocsp_expect => stapled}
+ end,
+ split_cert_entries(CertEntries, OcspState, [DerCert | Chain], Ext#{Id => Extensions}).
%% 4.4.1. The Transcript Hash
@@ -1343,6 +1570,11 @@ replace_ch1_with_message_hash(#state{connection_states = ConnectionStates,
tls_handshake_history =
{[HRR,MessageHash|HHistory], LM}}}.
+get_hkdf_algorithm(ConnectionStates) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+ HKDFAlgo.
message_hash(ClientHello1, HKDFAlgo) ->
[?MESSAGE_HASH,
@@ -1366,26 +1598,88 @@ calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, PSK,
%% Calculate [sender]_handshake_traffic_secret
{Messages, _} = HHistory,
-
ClientHSTrafficSecret =
tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
ServerHSTrafficSecret =
tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
%% Calculate traffic keys
- #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientHSTrafficSecret),
- {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret),
+ KeyLength = tls_v1:key_length(CipherSuite),
+ {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientHSTrafficSecret),
+ {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ServerHSTrafficSecret),
%% Calculate Finished Keys
ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo),
WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo),
- update_pending_connection_states(State0, HandshakeSecret, undefined,
+ State1 = maybe_store_handshake_traffic_secret(State0, ClientHSTrafficSecret, ServerHSTrafficSecret),
+
+ update_pending_connection_states(State1, HandshakeSecret, undefined,
undefined, undefined,
ReadKey, ReadIV, ReadFinishedKey,
WriteKey, WriteIV, WriteFinishedKey).
+%% Server
+calculate_client_early_traffic_secret(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = {Hist, _}}} = State, PSK) ->
+
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{cipher_suite = CipherSuite} = SecParamsR,
+ #{cipher := Cipher,
+ prf := HKDF} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ calculate_client_early_traffic_secret(Hist, PSK, Cipher, HKDF, State).
+
+%% Client
+calculate_client_early_traffic_secret(
+ ClientHello, PSK, Cipher, HKDFAlgo,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{keep_secrets := KeepSecrets},
+ static_env = #static_env{role = Role}} = State0) ->
+ EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
+ ClientEarlyTrafficSecret =
+ tls_v1:client_early_traffic_secret(HKDFAlgo, EarlySecret, ClientHello),
+ %% Calculate traffic key
+ KeyLength = ssl_cipher:key_material(Cipher),
+ {Key, IV} =
+ tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientEarlyTrafficSecret),
+ %% Update pending connection states
+ case Role of
+ client ->
+ PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates, write),
+ PendingWrite1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret,
+ PendingWrite0),
+ PendingWrite = update_connection_state(PendingWrite1, undefined, undefined,
+ undefined,
+ Key, IV, undefined),
+ State0#state{connection_states = ConnectionStates#{pending_write => PendingWrite}};
+ server ->
+ PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read),
+ PendingRead1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret,
+ PendingRead0),
+ PendingRead2 = update_connection_state(PendingRead1, undefined, undefined,
+ undefined,
+ Key, IV, undefined),
+ %% Signal start of early data. This is to prevent handshake messages to be
+ %% counted in max_early_data_size.
+ PendingRead = PendingRead2#{count_early_data => true},
+ State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}}
+ end.
+
+update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataLimit) ->
+ Read0 = ssl_record:current_connection_state(CS, read),
+ Read = Read0#{trial_decryption => TrialDecryption,
+ early_data_limit => EarlyDataLimit},
+ State#state{connection_states = CS#{current_read => Read}}.
+
+maybe_store_early_data_secret(true, EarlySecret, State) ->
+ #{security_parameters := SecParams0} = State,
+ SecParams = SecParams0#security_parameters{client_early_data_secret = EarlySecret},
+ State#{security_parameters := SecParams};
+maybe_store_early_data_secret(false, _, State) ->
+ State.
%% Server
get_pre_shared_key(undefined, HKDFAlgo) ->
@@ -1410,7 +1704,7 @@ get_pre_shared_key(manual = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentit
{ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
illegal_parameter ->
{error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
- {_, PSK} ->
+ {_, PSK, _, _, _} ->
{ok, PSK}
end;
get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) ->
@@ -1422,19 +1716,35 @@ get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity)
illegal_parameter ->
tls_client_ticket_store:unlock_tickets(self(), UseTicket),
{error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
- {Key, PSK} ->
+ {Key, PSK, _, _, _} ->
tls_client_ticket_store:remove_tickets([Key]), %% Remove single-use ticket
tls_client_ticket_store:unlock_tickets(self(), UseTicket -- [Key]),
{ok, PSK}
end.
-
+%%
+%% Early Data
+get_pre_shared_key_early_data(SessionTickets, UseTicket) ->
+ TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
+ case choose_psk(TicketData, 0) of
+ undefined -> %% Should not happen
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
+ illegal_parameter ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
+ {_Key, PSK, Cipher, HKDF, MaxSize} ->
+ {ok, {PSK, Cipher, HKDF, MaxSize}}
+ end.
choose_psk(undefined, _) ->
undefined;
choose_psk([], _) ->
illegal_parameter;
-choose_psk([{Key, SelectedIdentity, _, PSK, _, _}|_], SelectedIdentity) ->
- {Key, PSK};
+choose_psk([#ticket_data{
+ key = Key,
+ pos = SelectedIdentity,
+ psk = PSK,
+ cipher_suite = {Cipher, HKDF},
+ max_size = MaxSize}|_], SelectedIdentity) ->
+ {Key, PSK, Cipher, HKDF, MaxSize};
choose_psk([_|T], SelectedIdentity) ->
choose_psk(T, SelectedIdentity).
@@ -1464,9 +1774,9 @@ calculate_traffic_secrets(#state{
tls_v1:server_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)),
%% Calculate traffic keys
- #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientAppTrafficSecret0),
- {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerAppTrafficSecret0),
+ KeyLength = tls_v1:key_length(CipherSuite),
+ {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ClientAppTrafficSecret0),
+ {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, ServerAppTrafficSecret0),
update_pending_connection_states(State0, MasterSecret, undefined,
ClientAppTrafficSecret0, ServerAppTrafficSecret0,
@@ -1538,6 +1848,36 @@ overwrite_master_secret(ConnectionState = #{security_parameters := SecurityParam
ConnectionState#{security_parameters => SecurityParameters}.
+set_client_random(#state{connection_states =
+ #{pending_read := PendingRead,
+ pending_write := PendingWrite,
+ current_read := CurrentRead,
+ current_write := CurrentWrite} = CS} = State, ClientRandom) ->
+ State#state{connection_states = CS#{pending_read => overwrite_client_random(PendingRead, ClientRandom),
+ pending_write => overwrite_client_random(PendingWrite, ClientRandom),
+ current_read => overwrite_client_random(CurrentRead, ClientRandom),
+ current_write => overwrite_client_random(CurrentWrite, ClientRandom)}}.
+
+
+overwrite_client_random(ConnectionState = #{security_parameters := SecurityParameters0}, ClientRandom) ->
+ SecurityParameters = SecurityParameters0#security_parameters{client_random = ClientRandom},
+ ConnectionState#{security_parameters => SecurityParameters}.
+
+
+maybe_store_handshake_traffic_secret(#state{connection_states =
+ #{pending_read := PendingRead} = CS,
+ ssl_options = #{keep_secrets := true}} = State,
+ ClientHSTrafficSecret, ServerHSTrafficSecret) ->
+ PendingRead1 = store_handshake_traffic_secret(PendingRead, ClientHSTrafficSecret, ServerHSTrafficSecret),
+ State#state{connection_states = CS#{pending_read => PendingRead1}};
+maybe_store_handshake_traffic_secret(State, _, _) ->
+ State.
+
+store_handshake_traffic_secret(ConnectionState, ClientHSTrafficSecret, ServerHSTrafficSecret) ->
+ ConnectionState#{client_handshake_traffic_secret => ClientHSTrafficSecret,
+ server_handshake_traffic_secret => ServerHSTrafficSecret}.
+
+
update_pending_connection_states(#state{
static_env = #static_env{role = server},
connection_states =
@@ -1582,8 +1922,9 @@ update_connection_state(ConnectionState = #{security_parameters := SecurityParam
master_secret = HandshakeSecret,
resumption_master_secret = ResumptionMasterSecret,
application_traffic_secret = ApplicationTrafficSecret},
+ BulkCipherAlgo = SecurityParameters#security_parameters.bulk_cipher_algorithm,
ConnectionState#{security_parameters => SecurityParameters,
- cipher_state => cipher_init(Key, IV, FinishedKey)}.
+ cipher_state => cipher_init(BulkCipherAlgo, Key, IV, FinishedKey)}.
update_start_state(State, Map) ->
@@ -1639,7 +1980,12 @@ update_resumption_master_secret(#state{connection_states = ConnectionStates0} =
State#state{connection_states = ConnectionStates}.
-cipher_init(Key, IV, FinishedKey) ->
+cipher_init(?AES_CCM_8, Key, IV, FinishedKey) ->
+ #cipher_state{key = Key,
+ iv = IV,
+ finished_key = FinishedKey,
+ tag_len = 8};
+cipher_init(_BulkCipherAlgo, Key, IV, FinishedKey) ->
#cipher_state{key = Key,
iv = IV,
finished_key = FinishedKey,
@@ -1745,13 +2091,12 @@ maybe_update_selected_sign_alg(State, _, _) ->
State.
-verify_certificate_verify(#state{
- static_env = #static_env{role = Role},
- connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- public_key_info = PublicKeyInfo,
- tls_handshake_history = HHistory}} = State0,
+verify_certificate_verify(#state{static_env = #static_env{role = Role},
+ connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ public_key_info = PublicKeyInfo,
+ tls_handshake_history = HHistory}} = State0,
#certificate_verify_1_3{
algorithm = SignatureScheme,
signature = Signature}) ->
@@ -1759,7 +2104,7 @@ verify_certificate_verify(#state{
ssl_record:pending_connection_state(ConnectionStates, write),
#security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
- {HashAlgo, _, _} =
+ {HashAlgo, SignAlg, _} =
ssl_cipher:scheme_to_components(SignatureScheme),
Messages = get_handshake_context_cv(HHistory),
@@ -1773,7 +2118,7 @@ verify_certificate_verify(#state{
%% Digital signatures use the hash function defined by the selected signature
%% scheme.
- case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of
+ case verify(THash, ContextString, HashAlgo, SignAlg, Signature, PublicKeyInfo) of
{ok, true} ->
{ok, {State0, wait_finished}};
{ok, false} ->
@@ -1935,7 +2280,7 @@ select_cipher_suite(_, [], _) ->
select_cipher_suite(true, ClientCiphers, ServerCiphers) ->
select_cipher_suite(false, ServerCiphers, ClientCiphers);
select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) ->
- case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso
+ case lists:member(Cipher, tls_v1:exclusive_suites(4)) andalso
lists:member(Cipher, ServerCiphers) of
true ->
{ok, Cipher};
@@ -1977,12 +2322,12 @@ check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) ->
%% DSA keys are not supported by TLS 1.3
-select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) ->
+select_sign_algo(dsa, _RSAKeySize, _PeerSignAlgs, _OwnSignAlgs) ->
{error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)};
-select_sign_algo(_, [], _) ->
+select_sign_algo(_, _RSAKeySize, [], _) ->
{error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)};
-select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
- {_, S, _} = ssl_cipher:scheme_to_components(C),
+select_sign_algo(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs) ->
+ {_, S, _} = ssl_cipher:scheme_to_components(PeerSignAlg),
%% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed
%% TLS handshake messages: filter sha-1 and rsa_pkcs1.
%%
@@ -1991,16 +2336,48 @@ select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
%% RSASSA-PSS PSS algorithms: If the public key is carried in an X.509 certificate,
%% it MUST use the RSASSA-PSS OID.
case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae)
- orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_pss)
+ orelse (PublicKeyAlgo =:= rsa_pss_pss andalso S =:= rsa_pss_pss)
orelse (PublicKeyAlgo =:= ecdsa andalso S =:= ecdsa))
andalso
- lists:member(C, ServerSignAlgs) of
+ lists:member(PeerSignAlg, OwnSignAlgs) of
true ->
- {ok, C};
+ validate_key_compatibility(PublicKeyAlgo, RSAKeySize,
+ [PeerSignAlg|PeerSignAlgs], OwnSignAlgs);
false ->
- select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)
+ select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs)
end.
+validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [PeerSignAlg|PeerSignAlgs], OwnSignAlgs)
+ when PublicKeyAlgo =:= rsa orelse
+ PublicKeyAlgo =:= rsa_pss_pss ->
+ case is_rsa_key_compatible(RSAKeySize, PeerSignAlg) of
+ true ->
+ {ok, PeerSignAlg};
+ false ->
+ select_sign_algo(PublicKeyAlgo, RSAKeySize, PeerSignAlgs, OwnSignAlgs)
+ end;
+validate_key_compatibility(_, _, [PeerSignAlg|_], _) ->
+ {ok, PeerSignAlg}.
+
+is_rsa_key_compatible(KeySize, SigAlg) ->
+ {Hash, _, _} = ssl_cipher:scheme_to_components(SigAlg),
+ HashSize = ssl_cipher:hash_size(Hash),
+
+ %% OpenSSL crypto lib defines a limit on the size of the random salt
+ %% in PSS signatures based on the size of signing RSA key.
+ %% If the limit is unchecked, it causes handshake failures when the
+ %% configured certificates contain short (e.g. 1024-bit) RSA keys.
+ %% For more information see the OpenSSL crypto library
+ %% (rsa_pss:c{77,86}).
+ %% TODO: Move this check into crypto. Investigate if this is a bug in
+ %% OpenSSL crypto lib.
+ if (KeySize < (HashSize + 2)) ->
+ false;
+ (HashSize > (KeySize - HashSize - 2)) ->
+ false;
+ true ->
+ true
+ end.
do_check_cert_sign_algo(_, _, []) ->
{error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)};
@@ -2015,10 +2392,8 @@ do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) ->
%% id-RSASSA-PSS (rsa_pss) indicates that the key may only be used for PSS signatures.
-%% TODO: Uncomment when rsa_pss signatures are supported in certificates
-%% compare_sign_algos(rsa_pss, Hash, Algo, Hash)
-%% when Algo =:= rsa_pss_pss ->
-%% true;
+compare_sign_algos(rsa_pss_pss, Hash, rsa_pss_pss, Hash) ->
+ true;
%% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or
%% signature schemes.
compare_sign_algos(rsa, Hash, Algo, Hash)
@@ -2030,23 +2405,28 @@ compare_sign_algos(Algo, Hash, Algo, Hash) ->
compare_sign_algos(_, _, _, _) ->
false.
-
get_certificate_params(Cert) ->
- {SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert),
- {SignHash0, SignAlgo} = public_key:pkix_sign_types(SignAlgo0),
- %% Convert hash to new format
- SignHash = case SignHash0 of
- sha ->
- sha1;
- H -> H
- end,
- PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
- {PublicKeyAlgo, SignAlgo, SignHash}.
-
-
+ {SignAlgo0, Param, SubjectPublicKeyAlgo0, RSAKeySize} =
+ ssl_handshake:get_cert_params(Cert),
+ {SignHash, SignAlgo} = oids_to_atoms(SignAlgo0, Param),
+ SubjectPublicKeyAlgo = public_key_algo(SubjectPublicKeyAlgo0),
+ {SubjectPublicKeyAlgo, SignAlgo, SignHash, RSAKeySize}.
+
+oids_to_atoms(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm =
+ #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = HashOid}}}) ->
+ Hash = public_key:pkix_hash_type(HashOid),
+ {Hash, rsa_pss_pss};
+oids_to_atoms(SignAlgo, _) ->
+ case public_key:pkix_sign_types(SignAlgo) of
+ {sha, Sign} ->
+ {sha1, Sign};
+ {_,_} = Algs ->
+ Algs
+ end.
%% Note: copied from ssl_handshake
public_key_algo(?'id-RSASSA-PSS') ->
- rsa_pss;
+ rsa_pss_pss;
public_key_algo(?rsaEncryption) ->
rsa;
public_key_algo(?'id-ecPublicKey') ->
@@ -2065,14 +2445,21 @@ get_signature_scheme_list(#signature_algorithms{
lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end,
ClientSignatureSchemes).
+get_supported_groups(undefined = Groups) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})};
get_supported_groups(#supported_groups{supported_groups = Groups}) ->
- Groups.
+ {ok, Groups}.
get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
ClientShares;
get_key_shares(#key_share_server_hello{server_share = ServerShare}) ->
ServerShare.
+get_cookie(undefined) ->
+ undefined;
+get_cookie(#cookie{cookie = Cookie}) ->
+ Cookie.
+
get_selected_identity(undefined) ->
undefined;
get_selected_identity(#pre_shared_key_server_hello{selected_identity = SelectedIdentity}) ->
@@ -2174,18 +2561,18 @@ maybe_add_binders(Hello0, {[HRR,MessageHash|_], _}, TicketData, Version) when Ve
maybe_add_binders(Hello, _, _, Version) when Version =< {3,3} ->
Hello.
-
create_binders(Context, TicketData) ->
create_binders(Context, TicketData, []).
%%
create_binders(_, [], Acc) ->
lists:reverse(Acc);
-create_binders(Context, [{_, _, _, PSK, _, HKDF}|T], Acc) ->
+create_binders(Context, [#ticket_data{
+ psk = PSK,
+ cipher_suite = {_, HKDF}}|T], Acc) ->
FinishedKey = calculate_finished_key(PSK, HKDF),
Binder = calculate_binder(FinishedKey, HKDF, Context),
create_binders(Context, T, [Binder|Acc]).
-
%% Removes the binders list from the ClientHello.
%% opaque PskBinderEntry<32..255>;
%%
@@ -2219,6 +2606,18 @@ truncate_client_hello(HelloBin0) ->
{Truncated, _} = split_binary(HelloBin0, size(HelloBin0) - BindersSize - 2),
Truncated.
+maybe_add_early_data_indication(#client_hello{
+ extensions = Extensions0} = ClientHello,
+ EarlyData,
+ Version)
+ when Version =:= {3,4} andalso
+ is_binary(EarlyData) andalso
+ size(EarlyData) > 0 ->
+ Extensions = Extensions0#{early_data =>
+ #early_data_indication{}},
+ ClientHello#client_hello{extensions = Extensions};
+maybe_add_early_data_indication(ClientHello, _, _) ->
+ ClientHello.
%% The PskBinderEntry is computed in the same way as the Finished
%% message (Section 4.4.4) but with the BaseKey being the binder_key
@@ -2253,13 +2652,21 @@ update_binders(#client_hello{extensions =
maybe_automatic_session_resumption(#state{
ssl_options = #{versions := [Version|_],
ciphers := UserSuites,
- session_tickets := SessionTickets} = SslOpts0
+ early_data := EarlyData,
+ session_tickets := SessionTickets,
+ server_name_indication := SNI} = SslOpts0
} = State0)
when Version >= {3,4} andalso
SessionTickets =:= auto ->
AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
HashAlgos = cipher_hash_algos(AvailableCipherSuites),
- UseTicket = tls_client_ticket_store:find_ticket(self(), HashAlgos),
+ Ciphers = ciphers_for_early_data(AvailableCipherSuites),
+ %% Find a pair of tickets KeyPair = {Ticket0, Ticket2} where Ticket0 satisfies
+ %% requirements for early_data and session resumption while Ticket2 can only
+ %% be used for session resumption.
+ EarlyDataSize = early_data_size(EarlyData),
+ KeyPair = tls_client_ticket_store:find_ticket(self(), Ciphers, HashAlgos, SNI, EarlyDataSize),
+ UseTicket = choose_ticket(KeyPair, EarlyData),
tls_client_ticket_store:lock_tickets(self(), [UseTicket]),
State = State0#state{ssl_options = SslOpts0#{use_ticket => [UseTicket]}},
{[UseTicket], State};
@@ -2268,6 +2675,144 @@ maybe_automatic_session_resumption(#state{
} = State) ->
{UseTicket, State}.
+early_data_size(undefined) ->
+ undefined;
+early_data_size(EarlyData) when is_binary(EarlyData) ->
+ byte_size(EarlyData).
+
+%% Choose a ticket based on the intention of the user. The first argument is
+%% a 2-tuple of ticket keys where the first element refers to a ticket that
+%% fulfills all criteria for sending early_data (hash, cipher, early data size).
+%% Second argument refers to a ticket that can only be used for session
+%% resumption.
+choose_ticket({Key, _}, _) when Key =/= undefined ->
+ Key;
+choose_ticket({_, Key}, EarlyData) when EarlyData =:= undefined ->
+ Key;
+choose_ticket(_, _) ->
+ %% No tickets found that fulfills the original intention of the user
+ %% (sending early_data). It is possible to do session resumption but
+ %% in that case the configured early data would have to be removed
+ %% and that would contradict the will of the user. Returning undefined
+ %% here prevents session resumption instead.
+ undefined.
+
+maybe_send_early_data(#state{
+ handshake_env = #handshake_env{tls_handshake_history = {Hist, _}},
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [Version|_],
+ use_ticket := UseTicket,
+ session_tickets := SessionTickets,
+ early_data := EarlyData} = _SslOpts0
+ } = State0) when Version =:= {3,4} andalso
+ UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined ->
+ %% D.4. Middlebox Compatibility Mode
+ State1 = maybe_queue_change_cipher_spec(State0, last),
+ %% Early traffic secret
+ EarlyDataSize = early_data_size(EarlyData),
+ case get_pre_shared_key_early_data(SessionTickets, UseTicket) of
+ {ok, {PSK, Cipher, HKDF, MaxSize}} when EarlyDataSize =< MaxSize ->
+ State2 = calculate_client_early_traffic_secret(Hist, PSK, Cipher, HKDF, State1),
+ %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData
+ State3 = ssl_record:step_encryption_state_write(State2),
+ {ok, encode_early_data(Cipher, State3)};
+ {ok, {_, _, _, _MaxSize}} ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, too_much_early_data)};
+ {error, Alert} ->
+ {error, Alert}
+ end;
+maybe_send_early_data(State) ->
+ {ok, State}.
+
+encode_early_data(Cipher,
+ #state{
+ flight_buffer = Flight0,
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [Version|_],
+ early_data := EarlyData} = _SslOpts0
+ } = State0) ->
+ #state{connection_states =
+ #{current_write :=
+ #{security_parameters := SecurityParameters0} = Write0} = ConnectionStates0} = State0,
+ BulkCipherAlgo = ssl_cipher:bulk_cipher_algorithm(Cipher),
+ SecurityParameters = SecurityParameters0#security_parameters{
+ cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BulkCipherAlgo},
+ Write = Write0#{security_parameters => SecurityParameters},
+ ConnectionStates1 = ConnectionStates0#{current_write => Write},
+ {BinEarlyData, ConnectionStates} = tls_record:encode_data([EarlyData], Version, ConnectionStates1),
+ State0#state{connection_states = ConnectionStates,
+ flight_buffer = Flight0 ++ [BinEarlyData]}.
+
+maybe_send_end_of_early_data(
+ #state{
+ handshake_env = #handshake_env{early_data_accepted = true},
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [Version|_],
+ use_ticket := UseTicket,
+ early_data := EarlyData},
+ static_env = #static_env{protocol_cb = Connection}
+ } = State0) when Version =:= {3,4} andalso
+ UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined ->
+ %% EndOfEarlydata is encrypted with the 0-RTT traffic keys
+ State1 = Connection:queue_handshake(#end_of_early_data{}, State0),
+ %% Use handshake keys after EndOfEarlyData is sent
+ ssl_record:step_encryption_state_write(State1);
+maybe_send_end_of_early_data(State) ->
+ State.
+
+maybe_check_early_data_indication(EarlyDataIndication,
+ #state{
+ handshake_env = HsEnv,
+ ssl_options = #{versions := [Version|_],
+ use_ticket := UseTicket,
+ early_data := EarlyData}
+ } = State) when Version =:= {3,4} andalso
+ UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined andalso
+ EarlyDataIndication =/= undefined ->
+ signal_user_early_data(State, accepted),
+ State#state{handshake_env = HsEnv#handshake_env{early_data_accepted = true}};
+maybe_check_early_data_indication(EarlyDataIndication,
+ #state{
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [Version|_],
+ use_ticket := UseTicket,
+ early_data := EarlyData} = _SslOpts0
+ } = State) when Version =:= {3,4} andalso
+ UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined andalso
+ EarlyDataIndication =:= undefined ->
+ signal_user_early_data(State, rejected),
+ %% Use handshake keys if early_data is rejected.
+ ssl_record:step_encryption_state_write(State);
+maybe_check_early_data_indication(_, State) ->
+ %% Use handshake keys if there is no early_data.
+ ssl_record:step_encryption_state_write(State).
+
+signal_user_early_data(#state{
+ connection_env =
+ #connection_env{
+ user_application = {_, User}},
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ trackers = Trackers}} = State,
+ Result) ->
+ CPids = Connection:pids(State),
+ SslSocket = Connection:socket(CPids, Transport, Socket, Trackers),
+ User ! {ssl, SslSocket, {early_data, Result}}.
+
+handle_early_data(State, enabled, #early_data_indication{}) ->
+ %% Accept early data
+ HsEnv = (State#state.handshake_env)#handshake_env{early_data_accepted = true},
+ State#state{handshake_env = HsEnv};
+handle_early_data(State, _, _) ->
+ State.
cipher_hash_algos(Ciphers) ->
Fun = fun(Cipher) ->
@@ -2276,6 +2821,14 @@ cipher_hash_algos(Ciphers) ->
end,
lists:map(Fun, Ciphers).
+ciphers_for_early_data(CipherSuites0) ->
+ %% Use only supported TLS 1.3 cipher suites
+ Supported = lists:filter(fun(CipherSuite) ->
+ lists:member(CipherSuite, tls_v1:exclusive_suites(4)) end,
+ CipherSuites0),
+ %% Return supported block cipher algorithms
+ lists:map(fun(#{cipher := Cipher}) -> Cipher end,
+ lists:map(fun ssl_cipher_format:suite_bin_to_map/1, Supported)).
get_ticket_data(_, undefined, _) ->
undefined;
@@ -2293,25 +2846,51 @@ process_user_tickets(UseTicket) ->
process_user_tickets([], Acc, _) ->
lists:reverse(Acc);
process_user_tickets([H|T], Acc, N) ->
- #{hkdf := HKDF,
- sni := _SNI,
- psk := PSK,
- timestamp := Timestamp,
- ticket := NewSessionTicket} = erlang:binary_to_term(H),
+ case process_ticket(H, N) of
+ error ->
+ process_user_tickets(T, Acc, N + 1);
+ TicketData ->
+ process_user_tickets(T, [TicketData|Acc], N + 1)
+ end.
+
+%% Used when session_tickets = manual
+process_ticket(#{cipher_suite := CipherSuite,
+ sni := _SNI, %% TODO user's responsibility to handle SNI?
+ psk := PSK,
+ timestamp := Timestamp,
+ ticket := NewSessionTicket}, N) ->
#new_session_ticket{
ticket_lifetime = _LifeTime,
ticket_age_add = AgeAdd,
ticket_nonce = Nonce,
ticket = Ticket,
- extensions = _Extensions
+ extensions = Extensions
} = NewSessionTicket,
TicketAge = erlang:system_time(seconds) - Timestamp,
ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd),
Identity = #psk_identity{
identity = Ticket,
obfuscated_ticket_age = ObfuscatedTicketAge},
- process_user_tickets(T, [{undefined, N, Identity, PSK, Nonce, HKDF}|Acc], N + 1).
-
+ MaxEarlyData = get_max_early_data(Extensions),
+ #ticket_data{
+ key = undefined,
+ pos = N,
+ identity = Identity,
+ psk = PSK,
+ nonce = Nonce,
+ cipher_suite = CipherSuite,
+ max_size = MaxEarlyData};
+process_ticket(_, _) ->
+ error.
+
+get_max_early_data(Extensions) ->
+ EarlyDataIndication = maps:get(early_data, Extensions, undefined),
+ case EarlyDataIndication of
+ undefined ->
+ undefined;
+ #early_data_indication_nst{indication = MaxSize} ->
+ MaxSize
+ end.
%% The "obfuscated_ticket_age"
%% field of each PskIdentity contains an obfuscated version of the
@@ -2320,3 +2899,53 @@ process_user_tickets([H|T], Acc, N) ->
%% (see Section 4.6.1), modulo 2^32.
obfuscate_ticket_age(TicketAge, AgeAdd) ->
(TicketAge + AgeAdd) rem round(math:pow(2,32)).
+
+path_validate([{TrustedCert, Path}], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) ->
+ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef,
+ CRLDbHandle, Version, SslOptions, CertExt);
+path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) ->
+ case path_validation(TrustedCert, Path, ServerName,
+ Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt) of
+ {ok, _} = Result ->
+ Result;
+ {error, _} ->
+ path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle,
+ Version, SslOptions, CertExt)
+ end.
+
+path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version,
+ #{verify_fun := VerifyFun,
+ customize_hostname_check := CustomizeHostnameCheck,
+ crl_check := CrlCheck,
+ log_level := LogLevel,
+ signature_algs := SignAlgos,
+ signature_algs_cert := SignAlgosCert,
+ depth := Depth},
+ #{cert_ext := CertExt,
+ ocsp_responder_certs := OcspResponderCerts,
+ ocsp_state := OcspState}) ->
+ ValidationFunAndState =
+ ssl_handshake:validation_fun_and_state(VerifyFun, #{role => Role,
+ certdb => CertDbHandle,
+ certdb_ref => CertDbRef,
+ server_name => ServerName,
+ customize_hostname_check =>
+ CustomizeHostnameCheck,
+ crl_check => CrlCheck,
+ crl_db => CRLDbHandle,
+ signature_algs => filter_tls13_algs(SignAlgos),
+ signature_algs_cert =>
+ filter_tls13_algs(SignAlgosCert),
+ version => Version,
+ issuer => TrustedCert,
+ cert_ext => CertExt,
+ ocsp_responder_certs => OcspResponderCerts,
+ ocsp_state => OcspState
+ },
+ Path, LogLevel),
+ Options = [{max_path_length, Depth},
+ {verify_fun, ValidationFunAndState}],
+ public_key:pkix_path_validation(TrustedCert, Path, Options).
diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl
index 9f41c84708..d506821f6c 100644
--- a/lib/ssl/src/tls_handshake_1_3.hrl
+++ b/lib/ssl/src/tls_handshake_1_3.hrl
@@ -87,9 +87,11 @@
%% RFC 8446 4.2.10. Early Data Indication
-record(empty, {
}).
--record(early_data_indication, {
- indication % uint32 max_early_data_size (new_session_ticket) |
- %% #empty{} (client_hello, encrypted_extensions)
+
+%% #empty{} (client_hello, encrypted_extensions)
+-record(early_data_indication, {}).
+-record(early_data_indication_nst, {
+ indication % uint32 max_early_data_size (new_session_ticket)
}).
%% RFC 8446 4.2.11. Pre-Shared Key Extension
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index dc70cb2757..9ec5490aa6 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -33,12 +33,14 @@
-include_lib("kernel/include/logger.hrl").
%% Handling of incoming data
--export([get_tls_records/4, init_connection_states/2]).
+-export([get_tls_records/5,
+ init_connection_states/2,
+ init_connection_states/3]).
%% Encoding TLS records
-export([encode_handshake/3, encode_alert_record/3,
encode_change_cipher_spec/2, encode_data/3]).
--export([encode_plain_text/4, split_iovec/1]).
+-export([encode_plain_text/4, split_iovec/2]).
%% Decoding
-export([decode_cipher_text/4]).
@@ -49,13 +51,14 @@
%% Protocol version handling
-export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2,
highest_protocol_version/1, highest_protocol_version/2,
- is_higher/2, supported_protocol_versions/0,
+ is_higher/2, supported_protocol_versions/0, sufficient_crypto_support/1,
is_acceptable_version/1, is_acceptable_version/2, hello_version/1]).
-export_type([tls_version/0, tls_atom_version/0]).
-type tls_version() :: ssl_record:ssl_version().
--type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'.
+-type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2' | 'tlsv1.3'.
+-type tls_max_frag_len() :: undefined | 512 | 1024 | 2048 | 4096.
-compile(inline).
@@ -63,16 +66,29 @@
%% Handling of incoming data
%%====================================================================
%%--------------------------------------------------------------------
--spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled) ->
- ssl_record:connection_states().
+-spec init_connection_states(Role, BeastMitigation) ->
+ ssl_record:connection_states() when
+ Role :: client | server,
+ BeastMitigation :: one_n_minus_one | zero_n | disabled.
+
%%
%% Description: Creates a connection_states record with appropriate
%% values for the initial SSL connection setup.
%%--------------------------------------------------------------------
init_connection_states(Role, BeastMitigation) ->
+ MaxEarlyDataSize = ssl_config:get_max_early_data_size(),
+ init_connection_states(Role, BeastMitigation, MaxEarlyDataSize).
+%%
+-spec init_connection_states(Role, BeastMitigation, MaxEarlyDataSize) ->
+ ssl_record:connection_states() when
+ Role :: client | server,
+ BeastMitigation :: one_n_minus_one | zero_n | disabled,
+ MaxEarlyDataSize :: non_neg_integer().
+
+init_connection_states(Role, BeastMitigation, MaxEarlyDataSize) ->
ConnectionEnd = ssl_record:record_protocol_role(Role),
- Current = initial_connection_state(ConnectionEnd, BeastMitigation),
- Pending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation),
+ Current = initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize),
+ Pending = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize),
#{current_read => Current,
pending_read => Pending,
current_write => Current,
@@ -83,6 +99,7 @@ init_connection_states(Role, BeastMitigation) ->
binary(),
[tls_version()] | tls_version(),
Buffer0 :: binary() | {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}},
+ tls_max_frag_len(),
ssl_options()) ->
{Records :: [#ssl_tls{}],
Buffer :: {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}} |
@@ -92,10 +109,10 @@ init_connection_states(Role, BeastMitigation) ->
%% Description: Given old buffer and new data from TCP, packs up a records
%% data
%%--------------------------------------------------------------------
-get_tls_records(Data, Versions, Buffer, SslOpts) when is_binary(Buffer) ->
- parse_tls_records(Versions, {[Data],byte_size(Data),[]}, SslOpts, undefined);
-get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, SslOpts) ->
- parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, SslOpts, Hdr).
+get_tls_records(Data, Versions, Buffer, MaxFragLen, SslOpts) when is_binary(Buffer) ->
+ parse_tls_records(Versions, {[Data],byte_size(Data),[]}, MaxFragLen, SslOpts, undefined);
+get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, MaxFragLen, SslOpts) ->
+ parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, MaxFragLen, SslOpts, Hdr).
%%====================================================================
%% Encoding
@@ -112,12 +129,18 @@ encode_handshake(Frag, {3, 4}, ConnectionStates) ->
encode_handshake(Frag, Version,
#{current_write :=
#{beast_mitigation := BeastMitigation,
+ max_fragment_length := MaxFragmentLength,
security_parameters :=
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
case iolist_size(Frag) of
- N when N > ?MAX_PLAIN_TEXT_LENGTH ->
- Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation),
+ N when N > MaxLength ->
+ Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation, MaxLength),
encode_fragments(?HANDSHAKE, Version, Data, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates)
@@ -155,10 +178,16 @@ encode_data(Data, {3, 4}, ConnectionStates) ->
tls_record_1_3:encode_data(Data, ConnectionStates);
encode_data(Data, Version,
#{current_write := #{beast_mitigation := BeastMitigation,
+ max_fragment_length := MaxFragmentLength,
security_parameters :=
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
- Fragments = split_iovec(Data, Version, BCA, BeastMitigation),
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
+ Fragments = split_iovec(Data, Version, BCA, BeastMitigation, MaxLength),
encode_fragments(?APPLICATION_DATA, Version, Fragments, ConnectionStates).
%%====================================================================
@@ -167,7 +196,8 @@ encode_data(Data, Version,
%%--------------------------------------------------------------------
-spec decode_cipher_text(tls_version(), #ssl_tls{}, ssl_record:connection_states(), boolean()) ->
- {#ssl_tls{}, ssl_record:connection_states()}| #alert{}.
+ {#ssl_tls{} | trial_decryption_failed,
+ ssl_record:connection_states()}| #alert{}.
%%
%% Description: Decode cipher text
%%--------------------------------------------------------------------
@@ -353,27 +383,74 @@ supported_protocol_versions() ->
end.
supported_protocol_versions([]) ->
- Vsns = case sufficient_tlsv1_2_crypto_support() of
- true ->
- ?ALL_SUPPORTED_VERSIONS;
- false ->
- ?MIN_SUPPORTED_VERSIONS
- end,
+ Vsns = sufficient_support(?ALL_SUPPORTED_VERSIONS),
application:set_env(ssl, protocol_version, Vsns),
Vsns;
supported_protocol_versions([_|_] = Vsns) ->
- case sufficient_tlsv1_2_crypto_support() of
- true ->
- Vsns;
- false ->
- case Vsns -- ['tlsv1.2'] of
- [] ->
- ?MIN_SUPPORTED_VERSIONS;
- NewVsns ->
- NewVsns
- end
- end.
+ sufficient_support(Vsns).
+
+sufficient_crypto_support(Version) ->
+ sufficient_crypto_support(crypto:supports(), Version).
+
+sufficient_crypto_support(CryptoSupport, {_,_} = Version) ->
+ sufficient_crypto_support(CryptoSupport, protocol_version(Version));
+sufficient_crypto_support(CryptoSupport, Version) when Version == 'tlsv1';
+ Version == 'tlsv1.1' ->
+ Hashes = proplists:get_value(hashs, CryptoSupport),
+ PKeys = proplists:get_value(public_keys, CryptoSupport),
+ proplists:get_bool(sha, Hashes)
+ andalso
+ proplists:get_bool(md5, Hashes)
+ andalso
+ proplists:get_bool(aes_cbc, proplists:get_value(ciphers, CryptoSupport))
+ andalso
+ (proplists:get_bool(ecdsa, PKeys) orelse proplists:get_bool(rsa, PKeys) orelse proplists:get_bool(dss, PKeys))
+ andalso
+ (proplists:get_bool(ecdh, PKeys) orelse proplists:get_bool(dh, PKeys));
+
+sufficient_crypto_support(CryptoSupport, 'tlsv1.2') ->
+ PKeys = proplists:get_value(public_keys, CryptoSupport),
+ (proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)))
+ andalso
+ (proplists:get_bool(aes_cbc, proplists:get_value(ciphers, CryptoSupport)))
+ andalso
+ (proplists:get_bool(ecdsa, PKeys) orelse proplists:get_bool(rsa, PKeys) orelse proplists:get_bool(dss, PKeys))
+ andalso
+ (proplists:get_bool(ecdh, PKeys) orelse proplists:get_bool(dh, PKeys));
+
+%% A TLS-compliant application MUST implement the TLS_AES_128_GCM_SHA256
+%% [GCM] cipher suite and SHOULD implement the TLS_AES_256_GCM_SHA384
+%% [GCM] and TLS_CHACHA20_POLY1305_SHA256 [RFC8439] cipher suites (see
+%% Appendix B.4).
+%%
+%% A TLS-compliant application MUST support digital signatures with
+%% rsa_pkcs1_sha256 (for certificates), rsa_pss_rsae_sha256 (for
+%% CertificateVerify and certificates), and ecdsa_secp256r1_sha256. A
+%% TLS-compliant application MUST support key exchange with secp256r1
+%% (NIST P-256) and SHOULD support key exchange with X25519 [RFC7748].
+sufficient_crypto_support(CryptoSupport, 'tlsv1.3') ->
+ Fun = fun({Group, Algorithm}) ->
+ is_algorithm_supported(CryptoSupport, Group, Algorithm)
+ end,
+ L = [{ciphers, aes_gcm}, %% TLS_AES_*_GCM_*
+ {ciphers, chacha20_poly1305}, %% TLS_CHACHA20_POLY1305_SHA256
+ {hashs, sha256}, %% TLS_AES_128_GCM_SHA256
+ {hashs, sha384}, %% TLS_AES_256_GCM_SHA384
+ {rsa_opts, rsa_pkcs1_padding}, %% rsa_pkcs1_sha256
+ {rsa_opts, rsa_pkcs1_pss_padding}, %% rsa_pss_rsae_*
+ {rsa_opts, rsa_pss_saltlen}, %% rsa_pss_rsae_*
+ {public_keys, ecdh},
+ {public_keys, dh},
+ {public_keys, rsa},
+ {public_keys, ecdsa},
+ %% {public_keys, eddsa}, %% TODO
+ {curves, secp256r1}, %% key exchange with secp256r1
+ {curves, x25519}], %% key exchange with X25519
+ lists:all(Fun, L).
+
+is_algorithm_supported(CryptoSupport, Group, Algorithm) ->
+ proplists:get_bool(Algorithm, proplists:get_value(Group, CryptoSupport)).
-spec is_acceptable_version(tls_version()) -> boolean().
is_acceptable_version({N,_})
@@ -395,16 +472,16 @@ hello_version([Highest|_]) when Highest >= {3,3} ->
hello_version(Versions) ->
lowest_protocol_version(Versions).
-split_iovec([]) ->
+split_iovec([], _) ->
[];
-split_iovec(Data) ->
- {Part,Rest} = split_iovec(Data, ?MAX_PLAIN_TEXT_LENGTH, []),
- [Part|split_iovec(Rest)].
+split_iovec(Data, MaximumFragmentLength) ->
+ {Part,Rest} = split_iovec(Data, MaximumFragmentLength, []),
+ [Part|split_iovec(Rest, MaximumFragmentLength)].
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-initial_connection_state(ConnectionEnd, BeastMitigation) ->
+initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
#{security_parameters =>
ssl_record:initial_security_params(ConnectionEnd),
sequence_number => 0,
@@ -414,7 +491,11 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
mac_secret => undefined,
secure_renegotiation => undefined,
client_verify_data => undefined,
- server_verify_data => undefined
+ server_verify_data => undefined,
+ max_early_data_size => MaxEarlyDataSize,
+ max_fragment_length => undefined,
+ trial_decryption => false,
+ early_data_limit => false
}.
%% Used by logging to recreate the received bytes
@@ -423,88 +504,92 @@ build_tls_record(#ssl_tls{type = Type, version = {MajVer, MinVer}, fragment = Fr
<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),?UINT16(Length), Fragment/binary>>.
-parse_tls_records(Versions, Q, SslOpts, undefined) ->
- decode_tls_records(Versions, Q, SslOpts, [], undefined, undefined, undefined);
-parse_tls_records(Versions, Q, SslOpts, #ssl_tls{type = Type, version = Version, fragment = Length}) ->
- decode_tls_records(Versions, Q, SslOpts, [], Type, Version, Length).
+parse_tls_records(Versions, Q, MaxFragLen, SslOpts, undefined) ->
+ decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [], undefined, undefined, undefined);
+parse_tls_records(Versions, Q, MaxFragLen, SslOpts, #ssl_tls{type = Type, version = Version, fragment = Length}) ->
+ decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [], Type, Version, Length).
%% Generic code path
-decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, undefined, _Version, _Length) ->
+decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, undefined, _Version, _Length) ->
if
5 =< Size ->
{<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(5, Q0),
- validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
3 =< Size ->
{<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(3, Q0),
- validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
1 =< Size ->
{<<?BYTE(Type)>>, Q} = binary_from_front(1, Q0),
- validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, undefined, undefined);
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, undefined, undefined);
true ->
- validate_tls_records_type(Versions, Q0, SslOpts, Acc, undefined, undefined, undefined)
+ validate_tls_records_type(Versions, Q0, MaxFragLen, SslOpts, Acc, undefined, undefined, undefined)
end;
-decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, undefined, _Length) ->
+decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, undefined, _Length) ->
if
4 =< Size ->
{<<?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(4, Q0),
- validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
2 =< Size ->
{<<?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(2, Q0),
- validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
true ->
- validate_tls_record_version(Versions, Q0, SslOpts, Acc, Type, undefined, undefined)
+ validate_tls_record_version(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, undefined, undefined)
end;
-decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, Version, undefined) ->
+decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, Version, undefined) ->
if
2 =< Size ->
{<<?UINT16(Length)>>, Q} = binary_from_front(2, Q0),
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
true ->
- validate_tls_record_length(Versions, Q0, SslOpts, Acc, Type, Version, undefined)
+ validate_tls_record_length(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, Version, undefined)
end;
-decode_tls_records(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length).
+decode_tls_records(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length).
-validate_tls_records_type(_Versions, Q, _SslOpts, Acc, undefined, _Version, _Length) ->
+validate_tls_records_type(_Versions, Q, _MaxFragLen, _SslOpts, Acc, undefined, _Version, _Length) ->
{lists:reverse(Acc),
{undefined, Q}};
-validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
+validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
if
?KNOWN_RECORD_TYPE(Type) ->
- validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
true ->
%% Not ?KNOWN_RECORD_TYPE(Type)
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, {unsupported_record_type, Type})
end.
-validate_tls_record_version(_Versions, Q, _SslOpts, Acc, Type, undefined, _Length) ->
+validate_tls_record_version(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, undefined, _Length) ->
{lists:reverse(Acc),
{#ssl_tls{type = Type, version = undefined, fragment = undefined}, Q}};
-validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
+validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
case Versions of
_ when is_list(Versions) ->
case is_acceptable_version(Version, Versions) of
true ->
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version})
end;
{3, 4} when Version =:= {3, 3} ->
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
Version ->
%% Exact version match
- validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
_ ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version})
end.
-validate_tls_record_length(_Versions, Q, _SslOpts, Acc, Type, Version, undefined) ->
+validate_tls_record_length(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, Version, undefined) ->
{lists:reverse(Acc),
{#ssl_tls{type = Type, version = Version, fragment = undefined}, Q}};
-validate_tls_record_length(Versions, {_,Size0,_} = Q0,
+validate_tls_record_length(Versions, {_,Size0,_} = Q0, MaxFragLen,
#{log_level := LogLevel} = SslOpts,
Acc, Type, Version, Length) ->
- Max = max_len(Versions),
+ Max = if is_integer(MaxFragLen) ->
+ MaxFragLen + ?MAX_PADDING_LENGTH + ?MAX_MAC_LENGTH;
+ true ->
+ max_len(Versions)
+ end,
if
Length =< Max ->
if
@@ -513,7 +598,7 @@ validate_tls_record_length(Versions, {_,Size0,_} = Q0,
{Fragment, Q} = binary_from_front(Length, Q0),
Record = #ssl_tls{type = Type, version = Version, fragment = Fragment},
ssl_logger:debug(LogLevel, inbound, 'record', Record),
- decode_tls_records(Versions, Q, SslOpts, [Record|Acc], undefined, undefined, undefined);
+ decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [Record|Acc], undefined, undefined, undefined);
true ->
{lists:reverse(Acc),
{#ssl_tls{type = Type, version = Version, fragment = Length}, Q0}}
@@ -620,22 +705,22 @@ encode_fragments(_Type, _Version, _Data, CS, _CompS, _CipherS, _Seq, _CipherFrag
exit({cs, CS}).
%%--------------------------------------------------------------------
-%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are
+%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 ciphers are
%% not vulnerable to this attack.
-split_iovec(Data, Version, BCA, one_n_minus_one)
+split_iovec(Data, Version, BCA, one_n_minus_one, MaxLength)
when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
{3, 0} == Version) ->
{Part, RestData} = split_iovec(Data, 1, []),
- [Part|split_iovec(RestData)];
+ [Part|split_iovec(RestData, MaxLength)];
%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
%% splitting.
-split_iovec(Data, Version, BCA, zero_n)
+split_iovec(Data, Version, BCA, zero_n, MaxLength)
when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
{3, 0} == Version) ->
{Part, RestData} = split_iovec(Data, 0, []),
- [Part|split_iovec(RestData)];
-split_iovec(Data, _Version, _BCA, _BeatMitigation) ->
- split_iovec(Data).
+ [Part|split_iovec(RestData, MaxLength)];
+split_iovec(Data, _Version, _BCA, _BeatMitigation, MaxLength) ->
+ split_iovec(Data, MaxLength).
split_iovec([Bin|Data] = Bin_Data, SplitSize, Acc) ->
BinSize = byte_size(Bin),
@@ -668,11 +753,12 @@ highest_protocol_version() ->
lowest_protocol_version() ->
lowest_protocol_version(supported_protocol_versions()).
-sufficient_tlsv1_2_crypto_support() ->
- CryptoSupport = crypto:supports(),
- proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)).
-
max_len([{3,4}|_])->
?TLS13_MAX_CIPHER_TEXT_LENGTH;
max_len(_) ->
?MAX_CIPHER_TEXT_LENGTH.
+
+sufficient_support(Versions) ->
+ CryptoSupport = crypto:supports(),
+ [Ver || Ver <- Versions, sufficient_crypto_support(CryptoSupport, Ver)].
+
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index 89f2a484ff..3e42e3bf97 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -43,11 +43,17 @@
%
%% Description: Encodes a handshake message to send on the tls-1.3-socket.
%%--------------------------------------------------------------------
-encode_handshake(Frag, ConnectionStates) ->
+encode_handshake(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} =
+ ConnectionStates) ->
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ %% TODO: Consider padding here
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
case iolist_size(Frag) of
- N when N > ?MAX_PLAIN_TEXT_LENGTH ->
- %% TODO: Consider padding here
- Data = tls_record:split_iovec(Frag),
+ N when N > MaxLength ->
+ Data = tls_record:split_iovec(erlang:iolist_to_iovec(Frag), MaxLength),
encode_iolist(?HANDSHAKE, Data, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Frag, ConnectionStates)
@@ -69,8 +75,14 @@ encode_alert_record(#alert{level = Level, description = Description},
%%
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_data(Frag, ConnectionStates) ->
- Data = tls_record:split_iovec(Frag),
+encode_data(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} =
+ ConnectionStates) ->
+ MaxLength = if is_integer(MaxFragmentLength) ->
+ MaxFragmentLength;
+ true ->
+ ?MAX_PLAIN_TEXT_LENGTH
+ end,
+ Data = tls_record:split_iovec(Frag, MaxLength),
encode_iolist(?APPLICATION_DATA, Data, ConnectionStates).
encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) ->
@@ -95,7 +107,8 @@ encode_iolist(Type, Data, ConnectionStates0) ->
%%--------------------------------------------------------------------
-spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states()) ->
- {#ssl_tls{}, ssl_record:connection_states()}| #alert{}.
+ {#ssl_tls{} | trial_decryption_failed,
+ ssl_record:connection_states()}| #alert{}.
%%
%% Description: Decode cipher text, use legacy type ssl_tls instead of tls_cipher_text
%% in decoding context so that we can reuse the code from erlier versions.
@@ -112,12 +125,25 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
#security_parameters{
cipher_type = ?AEAD,
bulk_cipher_algorithm =
- BulkCipherAlgo}
+ BulkCipherAlgo},
+ max_early_data_size := MaxEarlyDataSize0,
+ trial_decryption := TrialDecryption,
+ early_data_limit := EarlyDataLimit
} = ReadState0} = ConnectionStates0) ->
case decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) of
+ #alert{} when TrialDecryption =:= true andalso
+ MaxEarlyDataSize0 > 0 -> %% Trial decryption
+ trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0,
+ BulkCipherAlgo, CipherFragment);
#alert{} = Alert ->
Alert;
- PlainFragment ->
+ PlainFragment0 when EarlyDataLimit =:= true andalso
+ MaxEarlyDataSize0 > 0 ->
+ PlainFragment = remove_padding(PlainFragment0),
+ process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq,
+ BulkCipherAlgo, CipherFragment, PlainFragment);
+ PlainFragment0 ->
+ PlainFragment = remove_padding(PlainFragment0),
ConnectionStates =
ConnectionStates0#{current_read =>
ReadState0#{sequence_number => Seq + 1}},
@@ -177,9 +203,55 @@ decode_cipher_text(#ssl_tls{type = Type}, _) ->
%% Version mismatch is already asserted
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_type_mismatch, Type}).
+
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+trial_decrypt(ConnectionStates0, ReadState0, MaxEarlyDataSize0,
+ BulkCipherAlgo, CipherFragment) ->
+ MaxEarlyDataSize = update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment),
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
+ ReadState0#{max_early_data_size => MaxEarlyDataSize}},
+ if MaxEarlyDataSize < 0 ->
+ %% More early data is trial decrypted as the configured limit
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed);
+ true ->
+ {trial_decryption_failed, ConnectionStates}
+ end.
+
+process_early_data(ConnectionStates0, ReadState0, _MaxEarlyDataSize0, Seq,
+ _BulkCipherAlgo, _CipherFragment, PlainFragment)
+ when PlainFragment =:= <<5,0,0,0,22>> ->
+ %% struct {
+ %% opaque content[TLSPlaintext.length]; <<5,0,0,0>> - 5 = EndOfEarlyData
+ %% 0 = (uint24) size
+ %% ContentType type; <<22>> - Handshake
+ %% uint8 zeros[length_of_padding]; <<>> - no padding
+ %% } TLSInnerPlaintext;
+ %% EndOfEarlyData should not be counted into early data
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
+ ReadState0#{sequence_number => Seq + 1}},
+ {decode_inner_plaintext(PlainFragment), ConnectionStates};
+process_early_data(ConnectionStates0, ReadState0, MaxEarlyDataSize0, Seq,
+ BulkCipherAlgo, CipherFragment, PlainFragment) ->
+ %% First packet is deciphered anyway so we must check if more early data is received
+ %% than the configured limit (max_early_data_size).
+ MaxEarlyDataSize =
+ update_max_early_date_size(MaxEarlyDataSize0, BulkCipherAlgo, CipherFragment),
+ if MaxEarlyDataSize < 0 ->
+ %% Too much early data received, send alert unexpected_message
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, too_much_early_data);
+ true ->
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
+ ReadState0#{sequence_number => Seq + 1,
+ max_early_data_size => MaxEarlyDataSize}},
+ {decode_inner_plaintext(PlainFragment), ConnectionStates}
+ end.
+
inner_plaintext(Type, Data, Length) ->
#inner_plaintext{
content = Data,
@@ -287,8 +359,6 @@ aead_ciphertext_split(CipherTextFragment, TagLen)
decode_inner_plaintext(PlainText) ->
case binary:last(PlainText) of
- 0 ->
- decode_inner_plaintext(init_binary(PlainText));
Type when Type =:= ?APPLICATION_DATA orelse
Type =:= ?HANDSHAKE orelse
Type =:= ?ALERT ->
@@ -303,3 +373,30 @@ init_binary(B) ->
{Init, _} =
split_binary(B, byte_size(B) - 1),
Init.
+
+remove_padding(InnerPlainText) ->
+ case binary:last(InnerPlainText) of
+ 0 ->
+ remove_padding(init_binary(InnerPlainText));
+ _ ->
+ InnerPlainText
+ end.
+
+update_max_early_date_size(MaxEarlyDataSize, BulkCipherAlgo, CipherFragment) ->
+ %% CipherFragment is the binary encoded form of a TLSInnerPlaintext:
+ %%
+ %% struct {
+ %% opaque content[TLSPlaintext.length];
+ %% ContentType type;
+ %% uint8 zeros[length_of_padding];
+ %% } TLSInnerPlaintext;
+ %%
+ TypeLen = 1,
+ PaddingLen = 0, %% TODO Update formula when padding is implemented!
+ MaxEarlyDataSize - (byte_size(CipherFragment) - TypeLen - PaddingLen -
+ bca_tag_len(BulkCipherAlgo)).
+
+bca_tag_len(?AES_CCM_8) ->
+ 8;
+bca_tag_len(_) ->
+ 16.
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index 024c783b27..3d2cafa24c 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -272,12 +272,13 @@ connection({call, From}, dist_get_tls_socket,
socket = Socket,
connection_pid = Pid,
trackers = Trackers}} = StateData) ->
- TLSSocket = tls_connection:socket([Pid, self()], Transport, Socket, Trackers),
+ TLSSocket = tls_gen_connection:socket([Pid, self()], Transport, Socket, Trackers),
{next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]};
connection({call, From}, {dist_handshake_complete, _Node, DHandle},
#data{static = #static{connection_pid = Pid} = Static} = StateData) ->
+ false = erlang:dist_ctrl_set_opt(DHandle, get_size, true),
ok = erlang:dist_ctrl_input_handler(DHandle, Pid),
- ok = ssl_connection:dist_handshake_complete(Pid, DHandle),
+ ok = ssl_gen_statem:dist_handshake_complete(Pid, DHandle),
%% From now on we execute on normal priority
process_flag(priority, normal),
{keep_state, StateData#data{static = Static#static{dist_handle = DHandle}},
@@ -287,7 +288,7 @@ connection({call, From}, {dist_handshake_complete, _Node, DHandle},
[];
Data ->
[{next_event, internal,
- {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}]
+ {application_packets,{self(),undefined},Data}}]
end]};
connection(internal, {application_packets, From, Data}, StateData) ->
send_application_data(Data, From, ?FUNCTION_NAME, StateData);
@@ -310,7 +311,7 @@ connection(info, dist_data, #data{static = #static{dist_handle = DHandle}}) ->
[];
Data ->
[{next_event, internal,
- {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}]
+ {application_packets,{self(),undefined},Data}}]
end};
connection(info, tick, StateData) ->
consume_ticks(),
@@ -453,7 +454,7 @@ send_application_data(Data, From, StateName,
{next_event, internal, {key_update, From}},
{next_event, internal, {application_packets, From, Data}}]};
renegotiate ->
- ssl_connection:internal_renegotiation(Pid, ConnectionStates0),
+ tls_dtls_connection:internal_renegotiation(Pid, ConnectionStates0),
{next_state, handshake, StateData0,
[{next_event, internal, {application_packets, From, Data}}]};
chunk_and_key_update ->
@@ -512,7 +513,7 @@ send_post_handshake_data(Handshake, From, StateName,
maybe_update_cipher_key(#data{connection_states = ConnectionStates0,
static = Static0} = StateData, #key_update{}) ->
- ConnectionStates = tls_connection:update_cipher_key(current_write, ConnectionStates0),
+ ConnectionStates = tls_connection_1_3:update_cipher_key(current_write, ConnectionStates0),
Static = Static0#static{bytes_sent = 0},
StateData#data{connection_states = ConnectionStates,
static = Static};
@@ -542,6 +543,8 @@ key_update_at(Version, #{security_parameters :=
?CHACHA20_POLY1305 ->
seq_num_wrap;
?AES_CCM ->
+ KeyUpdateAt;
+ ?AES_CCM_8 ->
KeyUpdateAt
end;
key_update_at(_, _, KeyUpdateAt) ->
@@ -616,7 +619,15 @@ call(FsmPid, Event) ->
%%-------------- Erlang distribution helpers ------------------------------
+%% To avoid livelock, dist_data/2 will check for more bytes coming from
+%% distribution channel, if amount of already collected bytes greater
+%% or equal than the limit defined below.
+-define(TLS_BUNDLE_SOFT_LIMIT, 16 * 1024 * 1024).
+
dist_data(DHandle) ->
+ dist_data(DHandle, 0).
+
+dist_data(DHandle, CurBytes) ->
case erlang:dist_ctrl_get_data(DHandle) of
none ->
erlang:dist_ctrl_get_data_notification(DHandle),
@@ -625,15 +636,17 @@ dist_data(DHandle) ->
%% since the emulator will always deliver a Data
%% smaller than 4 GB, and the distribution will
%% therefore always have to use {packet,4}
- Data when is_binary(Data) ->
- Len = byte_size(Data),
- [[<<Len:32>>,Data]|dist_data(DHandle)];
- [BA,BB] = Data ->
- Len = byte_size(BA) + byte_size(BB),
- [[<<Len:32>>|Data]|dist_data(DHandle)];
- Data when is_list(Data) ->
- Len = iolist_size(Data),
- [[<<Len:32>>|Data]|dist_data(DHandle)]
+ {Len, Data} when Len + CurBytes >= ?TLS_BUNDLE_SOFT_LIMIT ->
+ %% Data is of type iovec(); lets keep it
+ %% as an iovec()...
+ erlang:dist_ctrl_get_data_notification(DHandle),
+ [<<Len:32>> | Data];
+ {Len, Data} ->
+ Packet = [<<Len:32>> | Data],
+ case dist_data(DHandle, CurBytes + Len) of
+ [] -> Packet;
+ More -> Packet ++ More
+ end
end.
diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl
index a804f81eaa..b9bafa6e36 100644
--- a/lib/ssl/src/tls_server_session_ticket.erl
+++ b/lib/ssl/src/tls_server_session_ticket.erl
@@ -31,7 +31,7 @@
-include("ssl_cipher.hrl").
%% API
--export([start_link/4,
+-export([start_link/5,
new/3,
use/4
]).
@@ -46,18 +46,19 @@
stateless,
stateful,
nonce,
- lifetime
+ lifetime,
+ max_early_data_size
}).
%%%===================================================================
%%% API
%%%===================================================================
--spec start_link(atom(), integer(), integer(), tuple()) -> {ok, Pid :: pid()} |
+-spec start_link(atom(), integer(), integer(), integer(), tuple()) -> {ok, Pid :: pid()} |
{error, Error :: {already_started, pid()}} |
{error, Error :: term()} |
ignore.
-start_link(Mode, Lifetime, TicketStoreSize, AntiReplay) ->
- gen_server:start_link(?MODULE, [Mode, Lifetime, TicketStoreSize, AntiReplay], []).
+start_link(Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay) ->
+ gen_server:start_link(?MODULE, [Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay], []).
new(Pid, Prf, MasterSecret) ->
gen_server:call(Pid, {new_session_ticket, Prf, MasterSecret}, infinity).
@@ -81,18 +82,19 @@ init(Args) ->
handle_call({new_session_ticket, Prf, MasterSecret}, _From,
#state{nonce = Nonce,
lifetime = LifeTime,
- stateful = #{}} = State0) ->
- Id = stateful_psk_id(),
+ max_early_data_size = MaxEarlyDataSize,
+ stateful = #{id_generator := IdGen}} = State0) ->
+ Id = stateful_psk_ticket_id(IdGen),
PSK = tls_v1:pre_shared_key(MasterSecret, ticket_nonce(Nonce), Prf),
- SessionTicket = new_session_ticket(Id, Nonce, LifeTime),
+ SessionTicket = new_session_ticket(Id, Nonce, LifeTime, MaxEarlyDataSize),
State = stateful_ticket_store(Id, SessionTicket, Prf, PSK, State0),
{reply, SessionTicket, State};
handle_call({new_session_ticket, Prf, MasterSecret}, _From,
#state{nonce = Nonce,
stateless = #{}} = State) ->
BaseSessionTicket = new_session_ticket_base(State),
- SessionTicket = generate_statless_ticket(BaseSessionTicket, Prf,
- MasterSecret, State),
+ SessionTicket = generate_stateless_ticket(BaseSessionTicket, Prf,
+ MasterSecret, State),
{reply, SessionTicket, State#state{nonce = Nonce+1}};
handle_call({use_ticket, Identifiers, Prf, HandshakeHist}, _From,
#state{stateful = #{}} = State0) ->
@@ -142,31 +144,35 @@ format_status(_Opt, Status) ->
%%% Internal functions
%%%===================================================================
-inital_state([stateless, Lifetime, _, undefined]) ->
+inital_state([stateless, Lifetime, _, MaxEarlyDataSize, undefined]) ->
#state{nonce = 0,
stateless = #{seed => {crypto:strong_rand_bytes(16),
crypto:strong_rand_bytes(32)},
window => undefined},
- lifetime = Lifetime
+ lifetime = Lifetime,
+ max_early_data_size = MaxEarlyDataSize
};
-inital_state([stateless, Lifetime, _, {Window, K, M}]) ->
+inital_state([stateless, Lifetime, _, MaxEarlyDataSize, {Window, K, M}]) ->
erlang:send_after(Window * 1000, self(), rotate_bloom_filters),
#state{nonce = 0,
stateless = #{bloom_filter => tls_bloom_filter:new(K, M),
seed => {crypto:strong_rand_bytes(16),
crypto:strong_rand_bytes(32)},
window => Window},
- lifetime = Lifetime
+ lifetime = Lifetime,
+ max_early_data_size = MaxEarlyDataSize
};
-inital_state([stateful, Lifetime, TicketStoreSize|_]) ->
+inital_state([stateful, Lifetime, TicketStoreSize, MaxEarlyDataSize|_]) ->
%% statfeful servers replay
%% protection is that it saves
%% all valid tickets
#state{lifetime = Lifetime,
+ max_early_data_size = MaxEarlyDataSize,
nonce = 0,
stateful = #{db => stateful_store(),
max => TicketStoreSize,
- ref_index => #{}
+ ref_index => #{},
+ id_generator => crypto:strong_rand_bytes(16)
}
}.
@@ -186,17 +192,21 @@ ticket_nonce(I) ->
<<?UINT64(I)>>.
new_session_ticket_base(#state{nonce = Nonce,
- lifetime = Lifetime}) ->
- new_session_ticket(undefined, Nonce, Lifetime).
+ lifetime = Lifetime,
+ max_early_data_size = MaxEarlyDataSize}) ->
+ new_session_ticket(undefined, Nonce, Lifetime, MaxEarlyDataSize).
-new_session_ticket(Id, Nonce, Lifetime) ->
+new_session_ticket(Id, Nonce, Lifetime, MaxEarlyDataSize) ->
TicketAgeAdd = ticket_age_add(),
+ Extensions = #{early_data =>
+ #early_data_indication_nst{
+ indication = MaxEarlyDataSize}},
#new_session_ticket{
ticket = Id,
ticket_lifetime = Lifetime,
ticket_age_add = TicketAgeAdd,
ticket_nonce = ticket_nonce(Nonce),
- extensions = #{}
+ extensions = Extensions
}.
@@ -295,15 +305,20 @@ stateful_living_ticket({TimeStamp,_},
Lived < LifeTime.
-stateful_psk_id() ->
- term_to_binary(make_ref()).
+stateful_psk_ticket_id(Key) ->
+ Unique = erlang:unique_integer(),
+ %% Obfuscate to avoid DoS attack possiblities
+ %% that could invalidate tickets and render them
+ %% unusable. This id should be unpredictable
+ %% and unique but have no other cryptographic requirements.
+ crypto:crypto_one_time(aes_128_ecb, Key, <<Unique:128>>, true).
%%%===================================================================
%%% Stateless ticket
%%%===================================================================
-generate_statless_ticket(#new_session_ticket{ticket_nonce = Nonce,
- ticket_age_add = TicketAgeAdd,
- ticket_lifetime = Lifetime}
+generate_stateless_ticket(#new_session_ticket{ticket_nonce = Nonce,
+ ticket_age_add = TicketAgeAdd,
+ ticket_lifetime = Lifetime}
= Ticket, Prf, MasterSecret,
#state{stateless = #{seed := {IV, Shard}}}) ->
PSK = tls_v1:pre_shared_key(MasterSecret, Nonce, Prf),
@@ -316,7 +331,7 @@ generate_statless_ticket(#new_session_ticket{ticket_nonce = Nonce,
timestamp = Timestamp
}, Shard, IV),
Ticket#new_session_ticket{ticket = Encrypted}.
-
+
stateless_use(#offered_psks{
identities = Identities,
binders = Binders
@@ -333,7 +348,7 @@ stateless_use([#psk_identity{identity = Encrypted,
case ssl_cipher:decrypt_ticket(Encrypted, Shard, IV) of
#stateless_ticket{hash = Prf,
pre_shared_key = PSK} = Ticket ->
- case statless_usable_ticket(Ticket, ObfAge, Binder,
+ case stateless_usable_ticket(Ticket, ObfAge, Binder,
HandshakeHist, Window) of
true ->
stateless_anti_replay(Index, PSK, Binder, State);
@@ -347,11 +362,11 @@ stateless_use([#psk_identity{identity = Encrypted,
stateless_use(Ids, Binders, Prf, HandshakeHist, Index+1, State)
end.
-statless_usable_ticket(#stateless_ticket{hash = Prf,
- ticket_age_add = TicketAgeAdd,
- lifetime = Lifetime,
- timestamp = Timestamp,
- pre_shared_key = PSK}, ObfAge,
+stateless_usable_ticket(#stateless_ticket{hash = Prf,
+ ticket_age_add = TicketAgeAdd,
+ lifetime = Lifetime,
+ timestamp = Timestamp,
+ pre_shared_key = PSK}, ObfAge,
Binder, HandshakeHist, Window) ->
case stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime,
Timestamp, Window) of
@@ -372,7 +387,7 @@ stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime, Timestamp, Window) ->
in_window(_, undefined) ->
true;
-in_window(Age, {Window, _, _}) ->
+in_window(Age, Window) when is_integer(Window) ->
Age =< Window.
stateless_anti_replay(Index, PSK, Binder,
diff --git a/lib/ssl/src/tls_server_session_ticket_sup.erl b/lib/ssl/src/tls_server_session_ticket_sup.erl
index 7ee4bb7b2c..bdde94ecea 100644
--- a/lib/ssl/src/tls_server_session_ticket_sup.erl
+++ b/lib/ssl/src/tls_server_session_ticket_sup.erl
@@ -27,26 +27,34 @@
-behaviour(supervisor).
%% API
--export([start_link/0, start_link_dist/0]).
--export([start_child/1, start_child_dist/1]).
+-export([start_link/0,
+ start_link_dist/0]).
+-export([start_child/1,
+ start_child_dist/1]).
%% Supervisor callback
--export([init/1]).
+-export([init/1,
+ sup_name/1]).
%%%=========================================================================
%%% API
%%%=========================================================================
start_link() ->
- supervisor:start_link({local, tracker_name(normal)}, ?MODULE, []).
+ supervisor:start_link({local, sup_name(normal)}, ?MODULE, []).
start_link_dist() ->
- supervisor:start_link({local, tracker_name(dist)}, ?MODULE, []).
+ supervisor:start_link({local, sup_name(dist)}, ?MODULE, []).
start_child(Args) ->
- supervisor:start_child(tracker_name(normal), Args).
+ supervisor:start_child(sup_name(normal), Args).
start_child_dist(Args) ->
- supervisor:start_child(tracker_name(dist), Args).
+ supervisor:start_child(sup_name(dist), Args).
+
+sup_name(normal) ->
+ ?MODULE;
+sup_name(dist) ->
+ list_to_atom(atom_to_list(?MODULE) ++ "_dist").
%%%=========================================================================
%%% Supervisor callback
@@ -66,7 +74,3 @@ init(_O) ->
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
{ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
-tracker_name(normal) ->
- ?MODULE;
-tracker_name(dist) ->
- list_to_atom(atom_to_list(?MODULE) ++ "dist").
diff --git a/lib/ssl/src/tls_server_sup.erl b/lib/ssl/src/tls_server_sup.erl
index 7834589206..b2f011f221 100644
--- a/lib/ssl/src/tls_server_sup.erl
+++ b/lib/ssl/src/tls_server_sup.erl
@@ -45,10 +45,14 @@ start_link() ->
init([]) ->
ListenTracker = listen_options_tracker_child_spec(),
- SessionTracker = tls_server_session_child_spec(),
-
+ SessionTracker = tls_server_session_child_spec(),
+ Pre_1_3SessionTracker = ssl_server_session_child_spec(),
+ Pre_1_3UpgradeSessionTracker = ssl_upgrade_server_session_child_spec(),
+
{ok, {{one_for_all, 10, 3600}, [ListenTracker,
- SessionTracker
+ SessionTracker,
+ Pre_1_3SessionTracker,
+ Pre_1_3UpgradeSessionTracker
]}}.
@@ -75,3 +79,21 @@ tls_server_session_child_spec() ->
Modules = [tls_server_session_ticket_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+ssl_server_session_child_spec() ->
+ Name = ssl_server_session_cache_sup,
+ StartFunc = {ssl_server_session_cache_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_server_session_cache_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+ssl_upgrade_server_session_child_spec() ->
+ Name = ssl_upgrade_server_session_cache_sup,
+ StartFunc = {ssl_upgrade_server_session_cache_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_upgrade_server_session_cache_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index 89826e8250..91fdad4e44 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -49,6 +49,7 @@
start_link/3,
terminate/2,
inherit_tracker/3,
+ session_id_tracker/2,
emulated_socket_options/2,
get_emulated_opts/1,
set_emulated_opts/2,
@@ -62,7 +63,7 @@
-record(state, {
emulated_opts,
- port,
+ listen_monitor,
ssl_opts
}).
@@ -78,10 +79,16 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _, _},
case Transport:listen(Port, Options ++ internal_inet_values()) of
{ok, ListenSocket} ->
{ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts),
- LifeTime = get_ticket_lifetime(),
- TicketStoreSize = get_ticket_store_size(),
- {ok, SessionHandler} = session_tickets_tracker(LifeTime, TicketStoreSize, SslOpts),
- Trackers = [{option_tracker, Tracker}, {session_tickets_tracker, SessionHandler}],
+ LifeTime = ssl_config:get_ticket_lifetime(),
+ TicketStoreSize = ssl_config:get_ticket_store_size(),
+ MaxEarlyDataSize = ssl_config:get_max_early_data_size(),
+ %% TLS-1.3 session handling
+ {ok, SessionHandler} =
+ session_tickets_tracker(LifeTime, TicketStoreSize, MaxEarlyDataSize, SslOpts),
+ %% PRE TLS-1.3 session handling
+ {ok, SessionIdHandle} = session_id_tracker(ListenSocket, SslOpts),
+ Trackers = [{option_tracker, Tracker}, {session_tickets_tracker, SessionHandler},
+ {session_id_tracker, SessionIdHandle}],
Socket = #sslsocket{pid = {ListenSocket, Config#config{trackers = Trackers}}},
check_active_n(EmOpts, Socket),
{ok, Socket};
@@ -103,7 +110,7 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_,_} = CbInfo,
{SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo],
case tls_connection_sup:start_child(ConnArgs) of
{ok, Pid} ->
- ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers);
+ ssl_gen_statem:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers);
{error, Reason} ->
{error, Reason}
end;
@@ -117,7 +124,7 @@ upgrade(Socket, #config{transport_info = {Transport,_,_,_,_}= CbInfo,
ok = setopts(Transport, Socket, tls_socket:internal_inet_values()),
case peername(Transport, Socket) of
{ok, {Address, Port}} ->
- ssl_connection:connect(ConnectionCb, Address, Port, Socket,
+ ssl_gen_statem:connect(ConnectionCb, Address, Port, Socket,
{SslOptions,
emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
@@ -132,7 +139,7 @@ connect(Address, Port,
{Transport, _, _, _, _} = CbInfo,
try Transport:connect(Address, Port, SocketOpts, Timeout) of
{ok, Socket} ->
- ssl_connection:connect(ConnetionCb, Address, Port, Socket,
+ ssl_gen_statem:connect(ConnetionCb, Address, Port, Socket,
{SslOpts,
emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
@@ -256,18 +263,40 @@ inherit_tracker(ListenSocket, EmOpts, #{erl_dist := false} = SslOpts) ->
inherit_tracker(ListenSocket, EmOpts, #{erl_dist := true} = SslOpts) ->
ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts, SslOpts]).
-session_tickets_tracker(_, _, #{erl_dist := false,
- session_tickets := disabled}) ->
+session_tickets_tracker(_, _, _, #{erl_dist := false,
+ session_tickets := disabled}) ->
{ok, disabled};
-session_tickets_tracker(Lifetime, TicketStoreSize, #{erl_dist := false,
- session_tickets := Mode,
- anti_replay := AntiReplay}) ->
- tls_server_session_ticket_sup:start_child([Mode, Lifetime, TicketStoreSize, AntiReplay]);
-session_tickets_tracker(Lifetime, TicketStoreSize, #{erl_dist := true,
- session_tickets := Mode}) ->
- tls_server_session_ticket_sup:start_child_dist([Mode, Lifetime, TicketStoreSize]).
-
-
+session_tickets_tracker(Lifetime, TicketStoreSize, MaxEarlyDataSize,
+ #{erl_dist := false,
+ session_tickets := Mode,
+ anti_replay := AntiReplay}) ->
+ tls_server_session_ticket_sup:start_child([Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay]);
+session_tickets_tracker(Lifetime, TicketStoreSize, MaxEarlyDataSize,
+ #{erl_dist := true,
+ session_tickets := Mode,
+ anti_replay := AntiReplay}) ->
+ SupName = tls_server_session_ticket_sup:sup_name(dist),
+ Children = supervisor:count_children(SupName),
+ Workers = proplists:get_value(workers, Children),
+ case Workers of
+ 0 ->
+ tls_server_session_ticket_sup:start_child([Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay]);
+ 1 ->
+ [{_,Child,_, _}] = supervisor:which_children(SupName),
+ {ok, Child}
+ end.
+session_id_tracker(_, #{versions := [{3,4}]}) ->
+ {ok, not_relevant};
+%% Regardless of the option reuse_sessions we need the session_id_tracker
+%% to generate session ids, but no sessions will be stored unless
+%% reuse_sessions = true.
+session_id_tracker(ssl_unknown_listener, #{erl_dist := false}) ->
+ ssl_upgrade_server_session_cache_sup:start_child(normal);
+session_id_tracker(ListenSocket, #{erl_dist := false}) ->
+ ssl_server_session_cache_sup:start_child(ListenSocket);
+session_id_tracker(_, #{erl_dist := true}) ->
+ ssl_upgrade_server_session_cache_sup:start_child(dist).
+
get_emulated_opts(TrackerPid) ->
call(TrackerPid, get_emulated_opts).
set_emulated_opts(TrackerPid, InetValues) ->
@@ -289,10 +318,12 @@ start_link(Port, SockOpts, SslOpts) ->
%%
%% Description: Initiates the server
%%--------------------------------------------------------------------
-init([Port, Opts, SslOpts]) ->
+init([Listen, Opts, SslOpts]) ->
process_flag(trap_exit, true),
- true = link(Port),
- {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), port = Port, ssl_opts = SslOpts}}.
+ Monitor = monitor_listen(Listen),
+ {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []),
+ listen_monitor = Monitor,
+ ssl_opts = SslOpts}}.
%%--------------------------------------------------------------------
-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}.
@@ -337,7 +368,7 @@ handle_cast(_, State)->
%%
%% Description: Handling all non call/cast messages
%%-------------------------------------------------------------------
-handle_info({'EXIT', Port, _}, #state{port = Port} = State) ->
+handle_info({'DOWN', Monitor, _, _, _}, #state{listen_monitor = Monitor} = State) ->
{stop, normal, State}.
@@ -366,6 +397,9 @@ code_change(_OldVsn, State, _Extra) ->
call(Pid, Msg) ->
gen_server:call(Pid, Msg, infinity).
+monitor_listen(Listen) when is_port(Listen) ->
+ erlang:monitor(port, Listen).
+
split_options(Opts) ->
split_options(Opts, emulated_options(), [], []).
split_options([], _, SocketOpts, EmuOpts) ->
@@ -472,19 +506,3 @@ validate_inet_option(active, Value)
validate_inet_option(_, _) ->
ok.
-get_ticket_lifetime() ->
- case application:get_env(ssl, server_session_ticket_lifetime) of
- {ok, Seconds} when is_integer(Seconds) andalso
- Seconds =< 604800 -> %% MUST be less than 7 days
- Seconds;
- _ ->
- 7200 %% Default 2 hours
- end.
-
-get_ticket_store_size() ->
- case application:get_env(ssl, server_session_ticket_store_size) of
- {ok, Size} when is_integer(Size) ->
- Size;
- _ ->
- 1000
- end.
diff --git a/lib/ssl/src/tls_sup.erl b/lib/ssl/src/tls_sup.erl
index 25c1db0272..a425ae31e2 100644
--- a/lib/ssl/src/tls_sup.erl
+++ b/lib/ssl/src/tls_sup.erl
@@ -45,10 +45,10 @@ start_link() ->
init([]) ->
- TLSConnetionManager = tls_connection_manager_child_spec(),
+ TLSConnetionSup = tls_connection_child_spec(),
ServerInstanceSup = server_instance_child_spec(),
- {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager,
+ {ok, {{one_for_one, 10, 3600}, [TLSConnetionSup,
ServerInstanceSup
]}}.
@@ -57,7 +57,7 @@ init([]) ->
%%% Internal functions
%%--------------------------------------------------------------------
-tls_connection_manager_child_spec() ->
+tls_connection_child_spec() ->
Name = tls_connection,
StartFunc = {tls_connection_sup, start_link, []},
Restart = permanent,
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index bab5ad9592..59c425ecbe 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -29,22 +29,53 @@
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
--export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, hmac_hash/3,
- setup_keys/8, suites/1, prf/5,
- ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1,
- default_signature_algs/1, signature_algs/2,
- default_signature_schemes/1, signature_schemes/2,
- groups/1, groups/2, group_to_enum/1, enum_to_group/1, default_groups/1]).
-
--export([derive_secret/4, hkdf_expand_label/5, hkdf_extract/3, hkdf_expand/4,
- key_schedule/3, key_schedule/4, create_info/3,
- external_binder_key/2, resumption_binder_key/2,
- client_early_traffic_secret/3, early_exporter_master_secret/3,
- client_handshake_traffic_secret/3, server_handshake_traffic_secret/3,
- client_application_traffic_secret_0/3, server_application_traffic_secret_0/3,
- exporter_master_secret/3, resumption_master_secret/3,
- update_traffic_secret/2, calculate_traffic_keys/3,
- transcript_hash/2, finished_key/2, finished_verify_data/3, pre_shared_key/3]).
+-export([master_secret/4,
+ finished/5,
+ certificate_verify/3,
+ mac_hash/7,
+ hmac_hash/3,
+ setup_keys/8,
+ suites/1,
+ exclusive_suites/1,
+ prf/5,
+ ecc_curves/1,
+ ecc_curves/2,
+ oid_to_enum/1,
+ enum_to_oid/1,
+ default_signature_algs/1,
+ signature_algs/2,
+ default_signature_schemes/1,
+ signature_schemes/2,
+ groups/1,
+ groups/2,
+ group_to_enum/1,
+ enum_to_group/1,
+ default_groups/1]).
+
+-export([derive_secret/4,
+ hkdf_expand_label/5,
+ hkdf_extract/3,
+ hkdf_expand/4,
+ key_length/1,
+ key_schedule/3,
+ key_schedule/4,
+ create_info/3,
+ external_binder_key/2,
+ resumption_binder_key/2,
+ client_early_traffic_secret/3,
+ early_exporter_master_secret/3,
+ client_handshake_traffic_secret/3,
+ server_handshake_traffic_secret/3,
+ client_application_traffic_secret_0/3,
+ server_application_traffic_secret_0/3,
+ exporter_master_secret/3,
+ resumption_master_secret/3,
+ update_traffic_secret/2,
+ calculate_traffic_keys/3,
+ transcript_hash/2,
+ finished_key/2,
+ finished_verify_data/3,
+ pre_shared_key/3]).
-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 |
sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 |
@@ -425,13 +456,20 @@ update_traffic_secret(Algo, Secret) ->
%%
%% [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
%% [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)
--spec calculate_traffic_keys(atom(), atom(), binary()) -> {binary(), binary()}.
-calculate_traffic_keys(HKDFAlgo, Cipher, Secret) ->
- Key = hkdf_expand_label(Secret, <<"key">>, <<>>, ssl_cipher:key_material(Cipher), HKDFAlgo),
+-spec calculate_traffic_keys(atom(), integer(), binary()) -> {binary(), binary()}.
+calculate_traffic_keys(HKDFAlgo, KeyLength, Secret) ->
+ Key = hkdf_expand_label(Secret, <<"key">>, <<>>, KeyLength, HKDFAlgo),
%% TODO: remove hard coded IV size
IV = hkdf_expand_label(Secret, <<"iv">>, <<>>, 12, HKDFAlgo),
{Key, IV}.
+-spec key_length(CipherSuite) -> KeyLength when
+ CipherSuite :: binary(),
+ KeyLength :: 0 | 8 | 16 | 24 | 32.
+key_length(CipherSuite) ->
+ #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ ssl_cipher:key_material(Cipher).
+
%% TLS v1.3 ---------------------------------------------------
%% TLS 1.0 -1.2 ---------------------------------------------------
@@ -453,50 +491,68 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
%% TODO 1.3 same as above?
--spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()].
+-spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()].
suites(Minor) when Minor == 1; Minor == 2 ->
- [
- ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
-
- ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
- ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
- ];
+ exclusive_suites(2);
suites(3) ->
+ exclusive_suites(3) ++ suites(2);
+
+suites(4) ->
+ exclusive_suites(4) ++ suites(3).
+
+exclusive_suites(4) ->
+ [?TLS_AES_256_GCM_SHA384,
+ ?TLS_AES_128_GCM_SHA256,
+ ?TLS_CHACHA20_POLY1305_SHA256,
+ ?TLS_AES_128_CCM_SHA256,
+ ?TLS_AES_128_CCM_8_SHA256
+ ];
+exclusive_suites(3) ->
[?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM,
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8,
+
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+
+ ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+ ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
+
?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
+
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
- ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
- ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
+ ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
+ ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
- ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
+
?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
+ ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
+ ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,
+
+ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
+
?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
+
+ ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
@@ -505,27 +561,24 @@ suites(3) ->
%% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
%% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
%% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256
- ] ++ suites(2);
-
-suites(4) ->
- [?TLS_AES_256_GCM_SHA384,
- ?TLS_AES_128_GCM_SHA256,
- ?TLS_CHACHA20_POLY1305_SHA256,
- ?TLS_AES_128_CCM_SHA256
- %% Not supported
- %% ?TLS_AES_128_CCM_8_SHA256
- ] ++ suites(3);
-
-suites('TLS_v1.3') ->
- [?TLS_AES_256_GCM_SHA384,
- ?TLS_AES_128_GCM_SHA256,
- ?TLS_CHACHA20_POLY1305_SHA256,
- ?TLS_AES_128_CCM_SHA256
- %% Not supported
- %% ?TLS_AES_128_CCM_8_SHA256
+ ];
+exclusive_suites(Minor) when Minor == 1; Minor == 2 ->
+ [
+ ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
+ ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+ ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
+
+ ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
+ ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+ ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
].
-
signature_algs({3, 4}, HashSigns) ->
signature_algs({3, 3}, HashSigns);
signature_algs({3, 3}, HashSigns) ->
@@ -684,7 +737,7 @@ hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, M, N, Prev, Acc) ->
hmac_hash(?NULL, _, _) ->
<<>>;
hmac_hash(Alg, Key, Value) ->
- crypto:hmac(mac_algo(Alg), Key, Value).
+ crypto:mac(hmac, mac_algo(Alg), Key, Value).
mac_algo(Alg) when is_atom(Alg) ->
Alg;
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index f4324f2f13..4e81a761f6 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -54,7 +54,9 @@ MODULES = \
ssl_npn_SUITE \
openssl_npn_SUITE\
openssl_sni_SUITE\
- ssl_renegotiate_SUITE\
+ ssl_mfl_SUITE\
+ openssl_mfl_SUITE\
+ ssl_renegotiate_SUITE\
openssl_renegotiate_SUITE\
openssl_reject_SUITE\
ssl_cert_tests\
@@ -74,11 +76,12 @@ MODULES = \
ssl_pem_cache_SUITE \
ssl_session_SUITE \
ssl_session_cache_SUITE \
+ ssl_session_cache_api_SUITE\ \
ssl_session_ticket_SUITE \
openssl_session_ticket_SUITE \
openssl_session_SUITE \
ssl_ECC_SUITE \
- ssl_ECC_openssl_SUITE \
+ openssl_ECC_SUITE \
ssl_ECC\
ssl_upgrade_SUITE\
ssl_sni_SUITE \
@@ -90,7 +93,8 @@ MODULES = \
ssl_socket_SUITE\
make_certs \
x509_test \
- inet_crypto_dist
+ inet_crypto_dist \
+ openssl_ocsp_SUITE
ERL_FILES = $(MODULES:%=%.erl)
diff --git a/lib/ssl/test/dtls_api_SUITE.erl b/lib/ssl/test/dtls_api_SUITE.erl
index dfcee916af..eb7a16e0f1 100644
--- a/lib/ssl/test/dtls_api_SUITE.erl
+++ b/lib/ssl/test/dtls_api_SUITE.erl
@@ -21,9 +21,40 @@
%%
-module(dtls_api_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+%% Testcases
+-export([dtls_listen_owner_dies/0,
+ dtls_listen_owner_dies/1,
+ dtls_listen_close/0,
+ dtls_listen_close/1,
+ dtls_listen_reopen/0,
+ dtls_listen_reopen/1,
+ dtls_listen_two_sockets_1/0,
+ dtls_listen_two_sockets_1/1,
+ dtls_listen_two_sockets_2/0,
+ dtls_listen_two_sockets_2/1,
+ dtls_listen_two_sockets_3/0,
+ dtls_listen_two_sockets_3/1,
+ dtls_listen_two_sockets_4/0,
+ dtls_listen_two_sockets_4/1,
+ dtls_listen_two_sockets_5/0,
+ dtls_listen_two_sockets_5/1,
+ dtls_listen_two_sockets_6/0,
+ dtls_listen_two_sockets_6/1
+ ]).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
all() ->
[
{group, 'dtlsv1.2'},
@@ -40,7 +71,13 @@ api_tests() ->
[
dtls_listen_owner_dies,
dtls_listen_close,
- dtls_listen_reopen
+ dtls_listen_reopen,
+ dtls_listen_two_sockets_1,
+ dtls_listen_two_sockets_2,
+ dtls_listen_two_sockets_3,
+ dtls_listen_two_sockets_4,
+ dtls_listen_two_sockets_5,
+ dtls_listen_two_sockets_6
].
init_per_suite(Config0) ->
@@ -60,27 +97,25 @@ end_per_suite(_Config) ->
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
+init_per_testcase(Testcase, Config)
+ when Testcase =:= dtls_listen_two_sockets_1 orelse
+ Testcase =:= dtls_listen_two_sockets_5 orelse
+ Testcase =:= dtls_listen_two_sockets_6 ->
+ case ssl:listen(0, [{protocol, dtls}, {ip, {127,0,0,2}}]) of
+ {ok, S} ->
+ test_listen_on_all_interfaces(S, Config),
+ ssl:close(S),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap({seconds, 10}),
+ maybe_skip_tc_on_windows(Testcase, Config);
+ {error, _} ->
+ {skip, "127.0.0.x address not available"}
+ end;
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 10}),
@@ -185,7 +220,107 @@ dtls_listen_reopen(Config) when is_list(Config) ->
{ssl, Client2, "from server 2"} ->
ssl:close(Client2)
end.
+
+dtls_listen_two_sockets_1() ->
+ [{doc, "Test with two DTLS dockets: 127.0.0.2:Port, 127.0.0.3:Port"}].
+dtls_listen_two_sockets_1(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}, {ip, {127,0,0,2}}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {ok, S2} = ssl:listen(Port, [{protocol, dtls}, {ip, {127,0,0,3}}]),
+ ssl:close(S1),
+ ssl:close(S2),
+ ok.
+
+dtls_listen_two_sockets_2() ->
+ [{doc, "Test with two DTLS dockets: <all_interfaces>:Port, <all_interfaces>:Port"}].
+dtls_listen_two_sockets_2(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S1),
+ ok.
+
+dtls_listen_two_sockets_3() ->
+ [{doc, "Test with two DTLS dockets: <all_interfaces>:Port, <all_interfaces>:Port"}].
+dtls_listen_two_sockets_3(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S1),
+ {ok, S2} = ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S2),
+ ok.
+
+dtls_listen_two_sockets_4() ->
+ [{doc, "Test with two DTLS dockets: process1 - <all_interfaces>:Port, process2 - <all_interfaces>:Port"}].
+dtls_listen_two_sockets_4(_Config) when is_list(_Config) ->
+ Test = self(),
+ Pid = spawn(fun() ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}]),
+ {ok, {_, Port0}} = ssl:sockname(S1),
+ Test ! {self(), Port0}
+ end),
+ Port =
+ receive
+ {Pid, Port1} ->
+ Port1
+ end,
+ {ok, S2} =
+ ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S2),
+ ok.
+
+dtls_listen_two_sockets_5() ->
+ [{doc, "Test with two DTLS dockets: <all_interfaces>:Port, 127.0.0.3:Port"}].
+dtls_listen_two_sockets_5(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}, {ip, {127,0,0,3}}]),
+ ssl:close(S1),
+ {ok, S2} =
+ ssl:listen(Port, [{protocol, dtls}, {ip, {127,0,0,3}}]),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}]),
+ ssl:close(S2),
+ ok.
+
+dtls_listen_two_sockets_6() ->
+ [{doc, "Test with two DTLS dockets: 127.0.0.3:Port, 0.0.0.0:Port"}].
+dtls_listen_two_sockets_6(_Config) when is_list(_Config) ->
+ {ok, S1} = ssl:listen(0, [{protocol, dtls}, {ip, {127,0,0,3}}]),
+ {ok, {_, Port}} = ssl:sockname(S1),
+ {error, already_listening} =
+ ssl:listen(Port, [{protocol, dtls}, {ip, {0,0,0,0}}]),
+ ssl:close(S1),
+ ok.
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
+%% Helper function for init_per_testcase.
+test_listen_on_all_interfaces(S0, Config) ->
+ {ok, {_, Port}} = ssl:sockname(S0),
+ case ssl:listen(Port, [{protocol, dtls}, {ip, {0,0,0,0}}]) of
+ {ok, S1} ->
+ ssl:close(S0),
+ ssl:close(S1),
+ {skip, "Testcase is not supported on this OS."};
+ {error, _} ->
+ Config
+ end.
+
+maybe_skip_tc_on_windows(Testcase, Config)
+ when Testcase =:= dtls_listen_two_sockets_5 orelse
+ Testcase =:= dtls_listen_two_sockets_6 ->
+ case os:type() of
+ {win32, _} ->
+ {skip, "Testcase not supported in Windows"};
+ _ ->
+ Config
+ end;
+maybe_skip_tc_on_windows(_, Config) ->
+ Config.
diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl
index 63c19d9438..b8a2443ba2 100644
--- a/lib/ssl/test/inet_crypto_dist.erl
+++ b/lib/ssl/test/inet_crypto_dist.erl
@@ -60,7 +60,9 @@
iv = 12,
key = 16,
tag_len = 16,
- rekey_interval = 262144
+ rekey_count = 262144,
+ rekey_time = 7200000, % 2 hours
+ rekey_msg
}).
params(Socket) ->
@@ -78,10 +80,10 @@ params(Socket) ->
%%% params = brainpoolP384t1,
params = brainpoolP256t1,
public,
- private}).
-
--define(KEY_PAIR_LIFE_TIME, 3600000). % 1 hour
--define(KEY_PAIR_LIFE_COUNT, 256). % Number of connection setups
+ private,
+ life_time = 3600000, % 1 hour
+ life_count = 256 % Number of connection setups
+ }).
%% -------------------------------------------------------------------------
@@ -102,12 +104,18 @@ start_key_pair_server() ->
key_pair_server() ->
key_pair_server(undefined, undefined, undefined).
%%
-key_pair_server(KeyPair) ->
- key_pair_server(
- KeyPair,
- erlang:start_timer(?KEY_PAIR_LIFE_TIME, self(), discard),
- ?KEY_PAIR_LIFE_COUNT).
-%%
+key_pair_server(
+ #key_pair{life_time = LifeTime, life_count = LifeCount} = KeyPair) ->
+ %% Presuming: 1 < LifeCount
+ Timer =
+ case LifeCount of
+ 1 ->
+ undefined;
+ _ ->
+ erlang:start_timer(LifeTime, self(), discard)
+ end,
+ key_pair_server(KeyPair, Timer, LifeCount - 1).
+%%
key_pair_server(_KeyPair, Timer, 0) ->
cancel_timer(Timer),
key_pair_server();
@@ -138,9 +146,20 @@ generate_key_pair() ->
crypto:generate_key(Type, Params),
#key_pair{public = Public, private = Private}.
+
cancel_timer(undefined) ->
ok;
cancel_timer(Timer) ->
+ erlang_cancel_timer(Timer).
+
+start_rekey_timer(Time) ->
+ Timer = erlang:start_timer(Time, self(), rekey_time),
+ {timeout, Timer, rekey_time}.
+
+cancel_rekey_timer({timeout, Timer, rekey_time}) ->
+ erlang_cancel_timer(Timer).
+
+erlang_cancel_timer(Timer) ->
case erlang:cancel_timer(Timer) of
false ->
receive
@@ -309,7 +328,8 @@ listen(Name) ->
gen_listen(Name, ?DRIVER).
gen_listen(Name, Driver) ->
- case inet_tcp_dist:gen_listen(Driver, Name) of
+ {ok, Host} = inet:gethostname(),
+ case inet_tcp_dist:gen_listen(Driver, Name, Host) of
{ok, {Socket, Address, Creation}} ->
inet:setopts(Socket, socket_options()),
{ok,
@@ -862,11 +882,12 @@ reply({Ref, Pid}, Msg) ->
%%
%% The start message contains the two encrypted random numbers
%% this time encrypted with the session keys for verification
-%% by the other side, plus the rekey interval. The rekey interval
+%% by the other side, plus the rekey count. The rekey count
%% is just there to get an early check for if the other side's
-%% maximum rekey interal is acceptable, it is just an embryo
+%% maximum rekey count is acceptable, it is just an embryo
%% of some better check. Any side may rekey earlier but if the
-%% rekey interval is exceeded the connection fails.
+%% rekey count is exceeded the connection fails. Rekey is also
+%% triggered by a timer.
%%
%% Subsequent encrypted messages has the sequence number and the length
%% of the message as AAD data, and an incrementing IV. These messages
@@ -950,7 +971,7 @@ init_msg(
key = KeyLen,
iv = IVLen,
tag_len = TagLen,
- rekey_interval = RekeyInterval} = Params,
+ rekey_count = RekeyCount} = Params,
Secret, KeyPair, R2A, R3A, Msg) ->
%%
RLen = KeyLen + IVLen,
@@ -974,7 +995,7 @@ init_msg(
rekey_key = PubKeyB,
key = Key2A, iv = IV2A},
%%
- StartCleartext = [R2B, R3B, <<RekeyInterval:32>>],
+ StartCleartext = [R2B, R3B, <<RekeyCount:32>>],
StartMsgLen = TagLen + iolist_size(StartCleartext),
StartAAD = <<StartMsgLen:32>>,
{StartCiphertext, StartTag} =
@@ -1001,7 +1022,7 @@ start_msg(
key = Key2B,
iv = IV2B,
tag_len = TagLen,
- rekey_interval = RekeyIntervalA} = RecvParams, R2A, R3A, Msg) ->
+ rekey_count = RekeyCountA} = RecvParams, R2A, R3A, Msg) ->
%%
case Msg of
<<Tag:TagLen/binary, Ciphertext/binary>> ->
@@ -1014,10 +1035,10 @@ start_msg(
crypto:block_decrypt(
AeadCipher, Key2B, IV2B, {AAD, Ciphertext, Tag})
of
- <<R2A:RLen/binary, R3A:RLen/binary, RekeyIntervalB:32>>
- when RekeyIntervalA =< (RekeyIntervalB bsl 2),
- RekeyIntervalB =< (RekeyIntervalA bsl 2) ->
- RecvParams#params{rekey_interval = RekeyIntervalB}
+ <<R2A:RLen/binary, R3A:RLen/binary, RekeyCountB:32>>
+ when RekeyCountA =< (RekeyCountB bsl 2),
+ RekeyCountB =< (RekeyCountA bsl 2) ->
+ RecvParams#params{rekey_count = RekeyCountB}
end
end.
@@ -1071,7 +1092,10 @@ handshake(
process_flag(priority, normal),
erlang:dist_ctrl_get_data_notification(DistHandle),
output_handler(
- SendParams#params{dist_handle = DistHandle}, SendSeq);
+ SendParams#params{
+ dist_handle = DistHandle,
+ rekey_msg = start_rekey_timer(SendParams#params.rekey_time)},
+ SendSeq);
%%
{?MODULE, From, {send, Data}} ->
case
@@ -1137,9 +1161,12 @@ output_handler(Params, Seq) ->
output_handler_data(Params, Seq);
dist_tick ->
output_handler_tick(Params, Seq);
- Other ->
+ _ when Msg =:= Params#params.rekey_msg ->
+ Params_1 = output_handler_rekey(Params, Seq),
+ output_handler(Params_1, 0);
+ _ ->
%% Ignore
- _ = trace(Other),
+ _ = trace(Msg),
output_handler(Params, Seq)
end
end.
@@ -1152,9 +1179,12 @@ output_handler_data(Params, Seq) ->
output_handler_data(Params, Seq);
dist_tick ->
output_handler_data(Params, Seq);
- Other ->
+ _ when Msg =:= Params#params.rekey_msg ->
+ Params_1 = output_handler_rekey(Params, Seq),
+ output_handler_data(Params_1, 0);
+ _ ->
%% Ignore
- _ = trace(Other),
+ _ = trace(Msg),
output_handler_data(Params, Seq)
end
after 0 ->
@@ -1173,9 +1203,12 @@ output_handler_tick(Params, Seq) ->
output_handler_data(Params, Seq);
dist_tick ->
output_handler_tick(Params, Seq);
- Other ->
+ _ when Msg =:= Params#params.rekey_msg ->
+ Params_1 = output_handler_rekey(Params, Seq),
+ output_handler(Params_1, 0);
+ _ ->
%% Ignore
- _ = trace(Other),
+ _ = trace(Msg),
output_handler_tick(Params, Seq)
end
after 0 ->
@@ -1192,22 +1225,31 @@ output_handler_tick(Params, Seq) ->
end
end.
+output_handler_rekey(Params, Seq) ->
+ case encrypt_and_send_rekey_chunk(Params, Seq) of
+ #params{} = Params_1 ->
+ Params_1;
+ SendError ->
+ _ = trace(SendError),
+ death_row()
+ end.
+
output_handler_send(Params, Seq, {_, Size, _} = Q) ->
if
?CHUNK_SIZE < Size ->
- output_handler_send(Params, Seq, Q, ?CHUNK_SIZE);
+ output_handler_deq_send(Params, Seq, Q, ?CHUNK_SIZE);
true ->
case get_data(Params#params.dist_handle, Q) of
{_, 0, _} ->
{Params, Seq};
{_, Size, _} = Q_1 -> % Got no more
- output_handler_send(Params, Seq, Q_1, Size);
+ output_handler_deq_send(Params, Seq, Q_1, Size);
Q_1 ->
output_handler_send(Params, Seq, Q_1)
end
end.
-output_handler_send(Params, Seq, Q, Size) ->
+output_handler_deq_send(Params, Seq, Q, Size) ->
{Cleartext, Q_1} = deq_iovec(Size, Q),
case
encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK, Cleartext])
@@ -1358,13 +1400,30 @@ deliver_data(DistHandle, Front, Size, Rear, Bin) ->
encrypt_and_send_chunk(
#params{
+ socket = Socket, rekey_count = Seq, rekey_msg = RekeyMsg} = Params,
+ Seq, Cleartext) ->
+ %%
+ cancel_rekey_timer(RekeyMsg),
+ case encrypt_and_send_rekey_chunk(Params, Seq) of
+ #params{} = Params_1 ->
+ Result =
+ gen_tcp:send(Socket, encrypt_chunk(Params, 0, Cleartext)),
+ {Params_1, 1, Result};
+ SendError ->
+ {Params, Seq + 1, SendError}
+ end;
+encrypt_and_send_chunk(#params{socket = Socket} = Params, Seq, Cleartext) ->
+ Result = gen_tcp:send(Socket, encrypt_chunk(Params, Seq, Cleartext)),
+ {Params, Seq + 1, Result}.
+
+encrypt_and_send_rekey_chunk(
+ #params{
socket = Socket,
- rekey_interval = Seq,
rekey_key = PubKeyB,
key = Key,
iv = {IVSalt, IVNo},
hmac_algorithm = HmacAlgo} = Params,
- Seq, Cleartext) ->
+ Seq) ->
%%
KeyLen = byte_size(Key),
IVSaltLen = byte_size(IVSalt),
@@ -1380,17 +1439,13 @@ encrypt_and_send_chunk(
hmac_key_iv(
HmacAlgo, SharedSecret, [Key, IVSalt, IV],
KeyLen, IVSaltLen + 6),
- Params_1 = Params#params{key = Key_1, iv = {IVSalt_1, IVNo_1}},
- Result =
- gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)),
- {Params_1, 1, Result};
+ Params#params{
+ key = Key_1, iv = {IVSalt_1, IVNo_1},
+ rekey_msg = start_rekey_timer(Params#params.rekey_time)};
SendError ->
- {Params, Seq + 1, SendError}
- end;
-encrypt_and_send_chunk(#params{socket = Socket} = Params, Seq, Cleartext) ->
- Result = gen_tcp:send(Socket, encrypt_chunk(Params, Seq, Cleartext)),
- {Params, Seq + 1, Result}.
-
+ SendError
+ end.
+
encrypt_chunk(
#params{
aead_cipher = AeadCipher,
@@ -1431,7 +1486,7 @@ decrypt_chunk(
block_decrypt(
#params{
rekey_key = #key_pair{public = PubKeyA} = KeyPair,
- rekey_interval = RekeyInterval} = Params,
+ rekey_count = RekeyCount} = Params,
Seq, AeadCipher, Key, IV, Data) ->
%%
case crypto:block_decrypt(AeadCipher, Key, IV, Data) of
@@ -1453,7 +1508,7 @@ block_decrypt(
end;
Chunk when is_binary(Chunk) ->
case Seq of
- RekeyInterval ->
+ RekeyCount ->
%% This was one chunk too many without rekeying
error;
_ ->
diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl
index ad0038e4bf..8577397ac0 100644
--- a/lib/ssl/test/make_certs.erl
+++ b/lib/ssl/test/make_certs.erl
@@ -380,7 +380,7 @@ req_cnf(Root, C) ->
"default_bits = ", integer_to_list(C#config.default_bits), "\n"
"RANDFILE = $ROOTDIR/RAND\n"
"encrypt_key = no\n"
- "default_md = sha1\n"
+ "default_md = sha256\n"
"#string_mask = pkix\n"
"x509_extensions = ca_ext\n"
"prompt = no\n"
@@ -430,7 +430,7 @@ ca_cnf(
["crl_extensions = crl_ext\n" || C#config.v2_crls],
"unique_subject = no\n"
"default_days = 3600\n"
- "default_md = sha1\n"
+ "default_md = sha256\n"
"preserve = no\n"
"policy = policy_match\n"
"\n"
@@ -513,7 +513,7 @@ ca_cnf(
["crl_extensions = crl_ext\n" || C#config.v2_crls],
"unique_subject = no\n"
"default_days = 3600\n"
- "default_md = sha1\n"
+ "default_md = sha256\n"
"preserve = no\n"
"policy = policy_match\n"
"\n"
@@ -595,7 +595,7 @@ ca_cnf(
["crl_extensions = crl_ext\n" || C#config.v2_crls],
"unique_subject = no\n"
"default_days = 3600\n"
- "default_md = sha1\n"
+ "default_md = sha256\n"
"preserve = no\n"
"policy = policy_match\n"
"\n"
diff --git a/lib/ssl/test/ssl_ECC_openssl_SUITE.erl b/lib/ssl/test/openssl_ECC_SUITE.erl
index e541134a0d..ec84deda85 100644
--- a/lib/ssl/test/ssl_ECC_openssl_SUITE.erl
+++ b/lib/ssl/test/openssl_ECC_SUITE.erl
@@ -20,14 +20,25 @@
%%
--module(ssl_ECC_openssl_SUITE).
-
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-module(openssl_ECC_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([mix_sign/1]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -50,11 +61,11 @@ groups() ->
[{'tlsv1.2', [], [mix_sign]}]
end.
-%%--------------------------------------------------------------------
init_per_suite(Config0) ->
end_per_suite(Config0),
try crypto:start() of
ok ->
+ ssl_test_lib:clean_start(),
case ssl_test_lib:sufficient_crypto_support(cipher_ec) of
true ->
Config0;
@@ -70,32 +81,12 @@ end_per_suite(_Config) ->
application:stop(crypto),
ssl_test_lib:kill_openssl().
-%%--------------------------------------------------------------------
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- [{tls_version, GroupName},
- {server_type, erlang},
- {client_type, openssl} | ssl_test_lib:init_tls_version(GroupName, Config)];
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
-end_per_group(GroupName, Config0) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- Config = ssl_test_lib:clean_tls_version(Config0),
- proplists:delete(tls_version, Config);
- false ->
- Config0
- end.
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
-%%--------------------------------------------------------------------
init_per_testcase(skip, Config) ->
Config;
init_per_testcase(TestCase, Config) ->
@@ -114,10 +105,6 @@ end_per_testcase(_TestCase, Config) ->
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-
-skip(Config) when is_list(Config) ->
- {skip, openssl_does_not_support_ECC}.
-
mix_sign(Config) ->
{COpts0, SOpts0} = ssl_test_lib:make_mix_cert(Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
diff --git a/lib/ssl/test/openssl_alpn_SUITE.erl b/lib/ssl/test/openssl_alpn_SUITE.erl
index 269127a863..3a7a31d38c 100644
--- a/lib/ssl/test/openssl_alpn_SUITE.erl
+++ b/lib/ssl/test/openssl_alpn_SUITE.erl
@@ -21,11 +21,33 @@
-module(openssl_alpn_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([erlang_client_alpn_openssl_server_alpn/1,
+ erlang_server_alpn_openssl_client_alpn/1,
+ erlang_client_alpn_openssl_server/1,
+ erlang_client_openssl_server_alpn/1,
+ erlang_server_alpn_openssl_client/1,
+ erlang_server_openssl_client_alpn/1,
+ erlang_client_alpn_npn_openssl_server_alpn_npn/1,
+ erlang_server_alpn_npn_openssl_client_alpn_npn/1,
+ erlang_client_alpn_openssl_server_alpn_renegotiate/1,
+ erlang_server_alpn_openssl_client_alpn_renegotiate/1
+ ]).
+
+
-define(OPENSSL_QUIT, "Q\n").
-define(OPENSSL_RENEGOTIATE, "R\n").
-define(SLEEP, 1000).
@@ -35,7 +57,6 @@
%%--------------------------------------------------------------------
all() ->
- %% Note: ALPN not supported in sslv3
case ssl_test_lib:openssl_sane_dtls_alpn() of
true ->
[
@@ -44,7 +65,8 @@ all() ->
{group, 'tlsv1.1'},
{group, 'tlsv1'},
{group, 'dtlsv1.2'},
- {group, 'dtlsv1'}];
+ {group, 'dtlsv1'}
+ ];
false ->
[
{group, 'tlsv1.3'},
@@ -79,7 +101,8 @@ alpn_tests() ->
erlang_client_alpn_openssl_server,
erlang_client_openssl_server_alpn,
erlang_server_alpn_openssl_client,
- erlang_server_openssl_client_alpn].
+ erlang_server_openssl_client_alpn
+ ].
alpn_npn_coexist() ->
[
@@ -119,45 +142,31 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(TestCase, Config) ->
ct:timetrap({seconds, 30}),
special_init(TestCase, Config).
-special_init(erlang_client_alpn_openssl_server_alpn_renegotiate, Config) ->
- {ok, Version} = application:get_env(ssl, protocol_version),
- ssl_test_lib:check_sane_openssl_renegotaite(Config, Version);
-special_init(erlang_server_alpn_openssl_client_alpn_renegotiate, Config) ->
- {ok, Version} = application:get_env(ssl, protocol_version),
- case ssl_test_lib:check_sane_openssl_renegotaite(Config, Version) of
+special_init(TestCase, Config) when TestCase == erlang_client_alpn_openssl_server_alpn_renegotiate;
+ TestCase == erlang_server_alpn_openssl_client_alpn_renegotiate ->
+ [Version | _] = ssl_test_lib:default_tls_version(Config),
+ case ssl_test_lib:check_sane_openssl_renegotiate(Config, Version) of
+ {skip, _} = Skip ->
+ Skip;
Config ->
- ssl_test_lib:openssl_allows_client_renegotaite(Config);
- Skip ->
- Skip
+ ssl_test_lib:openssl_allows_server_renegotiate(Config)
+ end;
+special_init(TestCase, Config) when TestCase == erlang_client_alpn_npn_openssl_server_alpn_npn;
+ TestCase == erlang_server_alpn_npn_openssl_client_alpn_npn ->
+ case ssl_test_lib:check_openssl_npn_support(Config) of
+ {skip, _} = Skip ->
+ Skip;
+ Config ->
+ Config
end;
special_init(_, Config) ->
Config.
@@ -170,254 +179,306 @@ end_per_testcase(_, Config) ->
%%--------------------------------------------------------------------
erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Client, Data)
- end).
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ AlpnProtocol = <<"spdy/2">>,
+
+ {Server, OpenSSLPort} =
+ ssl_test_lib:start_server(openssl, [{alpn,"http/1.1,spdy/2"}, return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {Client, CSocket} = ssl_test_lib:start_client(erlang, [{port, Port},
+ return_socket],
+ [{client_opts,
+ [{alpn_advertised_protocols,
+ [AlpnProtocol]} | ClientOpts]}
+ | Config]),
+
+ case ssl:negotiated_protocol(CSocket) of
+ {ok, AlpnProtocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, AlpnProtocol}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Client, OpenSSLPort),
+ ssl:close(CSocket).
%%--------------------------------------------------------------------
erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Client, Data)
- end).
+ ClientOpts = proplists:get_value(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ Protocol = <<"spdy/2">>,
+ Server = ssl_test_lib:start_server(erlang, [{from, self()}],
+ [{server_opts, [{alpn_preferred_protocols,
+ [<<"spdy/2">>]} |ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+ {_Client, OpenSSLPort} =
+ ssl_test_lib:start_client(openssl, [{port, Port},{alpn, "spdy/2"},
+ {options, ClientOpts}, return_port], Config),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+ case ssl:negotiated_protocol(SSocket) of
+ {ok, Protocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, Protocol}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:close(SSocket).
%%--------------------------------------------------------------------------
erlang_client_alpn_openssl_server(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config,
- [{alpn_advertised_protocols, [<<"spdy/2">>]}],
- [],
- Data, fun(Client, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Client, Data)
- end).
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ Protocol = <<"spdy/2">>,
+
+ {Server, OpenSSLPort} = ssl_test_lib:start_server(openssl, [return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {Client, CSocket} =
+ ssl_test_lib:start_client(erlang, [{port, Port},
+ return_socket],
+ [{client_opts, [{alpn_advertised_protocols,
+ [Protocol]} | ClientOpts]} | Config]),
+
+
+ case ssl:negotiated_protocol(CSocket) of
+ {error, protocol_not_negotiated} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, undefined}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Client, OpenSSLPort).
%%--------------------------------------------------------------------------
erlang_client_openssl_server_alpn(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config,
- [],
- ["-alpn", "spdy/2"],
- Data, fun(Client, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Client, Data)
- end).
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+
+ {Server, OpenSSLPort} = ssl_test_lib:start_server(openssl, [{alpn,"spdy/2"}, return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {Client, CSocket} = ssl_test_lib:start_client(erlang, [{port, Port},
+ return_socket],
+ [{client_opts, ClientOpts} | Config]),
+
+ case ssl:negotiated_protocol(CSocket) of
+ {error, protocol_not_negotiated} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, undefined}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Client, OpenSSLPort).
%%--------------------------------------------------------------------------
-
erlang_server_alpn_openssl_client(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config,
- [{alpn_preferred_protocols, [<<"spdy/2">>]}],
- [],
- Data, fun(Server, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
-
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ Server = ssl_test_lib:start_server(erlang, [{from, self()}],
+ [{server_opts, [{alpn_preferred_protocols,
+ [<<"spdy/2">>]} | ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+ {_Client, OpenSSLPort} =
+ ssl_test_lib:start_client(openssl, [{port, Port},
+ {options, ClientOpts}, return_port], Config),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+ case ssl:negotiated_protocol(SSocket) of
+ {error, protocol_not_negotiated} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, undefined}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:close(SSocket).
%%--------------------------------------------------------------------------
erlang_server_openssl_client_alpn(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config,
- [],
- ["-alpn", "spdy/2"],
- Data, fun(Server, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ Server = ssl_test_lib:start_server(erlang, [{from, self()}],
+ [{server_opts, [ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+ {_Client, OpenSSLPort} =
+ ssl_test_lib:start_client(openssl, [{port, Port}, {alpn, "spdy/2"},
+ {options, ClientOpts}, return_port], Config),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+ case ssl:negotiated_protocol(SSocket) of
+ {error, protocol_not_negotiated} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, undefined}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:close(SSocket).
%%--------------------------------------------------------------------
erlang_client_alpn_openssl_server_alpn_renegotiate(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) ->
- true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE),
- ct:sleep(?SLEEP),
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Client, Data)
- end).
-
+
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ AlpnProtocol = <<"spdy/2">>,
+
+ {Server, OpenSSLPort} =
+ ssl_test_lib:start_server(openssl, [{alpn,"http/1.1,spdy/2"}, return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {Client, CSocket} =
+ ssl_test_lib:start_client(erlang, [{port, Port},
+ return_socket],
+ [{client_opts,
+ [{alpn_advertised_protocols,
+ [AlpnProtocol]} | ClientOpts]} | Config]),
+
+ case ssl:negotiated_protocol(CSocket) of
+ {ok, AlpnProtocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, AlpnProtocol}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Client, OpenSSLPort),
+ ssl_test_lib:send(Server, ?OPENSSL_RENEGOTIATE),
+ ct:sleep(1000),
+ %%% Should still be the same as initially negotiated
+ case ssl:negotiated_protocol(CSocket) of
+ {ok, AlpnProtocol} ->
+ ok;
+ Other ->
+ ct:fail({error, {{expected, AlpnProtocol}, {got, Other}}})
+ end,
+ ssl_test_lib:sanity_check(Client, OpenSSLPort),
+ ssl:close(CSocket).
%%--------------------------------------------------------------------
erlang_server_alpn_openssl_client_alpn_renegotiate(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Server, OpensslPort) ->
- true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE),
- ct:sleep(?SLEEP),
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ AlpnProtocol = <<"spdy/2">>,
+ Server = ssl_test_lib:start_server(erlang, [{from, self()}],
+ [{server_opts, [{alpn_preferred_protocols,
+ [AlpnProtocol]} | ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+ {_Client, OpenSSLPort} =
+ ssl_test_lib:start_client(openssl, [{port, Port}, {alpn, "spdy/2"},
+ {options, ClientOpts}, return_port], Config),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+ case ssl:negotiated_protocol(SSocket) of
+ {ok, AlpnProtocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, AlpnProtocol}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:renegotiate(SSocket),
+ case ssl:negotiated_protocol(SSocket) of
+ {ok, AlpnProtocol} ->
+ ok;
+ Other ->
+ ct:fail({error, {{expected, AlpnProtocol}, {got, Other}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:close(SSocket).
%%--------------------------------------------------------------------
erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Client, Data)
- end).
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ AlpnProtocol = <<"spdy/2">>,
+
+ {Server, OpenSSLPort} =
+ ssl_test_lib:start_server(openssl, [{alpn,"http/1.1,spdy/2"},
+ {np, "spdy/3"}, return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {Client, CSocket} =
+ ssl_test_lib:start_client(erlang, [{port, Port},
+ return_socket],
+ [{client_opts,
+ [{alpn_advertised_protocols, [AlpnProtocol]},
+ {next_protocols_advertised,
+ [<<"spdy/3">>, <<"http/1.1">>]}]} | ClientOpts] ++ Config),
+ case ssl:negotiated_protocol(CSocket) of
+ {ok, AlpnProtocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, AlpnProtocol}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Client, OpenSSLPort).
%%--------------------------------------------------------------------
erlang_server_alpn_npn_openssl_client_alpn_npn(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, fun(Server, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
-
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ AlpnProtocol = <<"spdy/2">>,
+ Server = ssl_test_lib:start_server(erlang,
+ [{from, self()}],
+ [{server_opts, [{alpn_preferred_protocols,
+ [<<"spdy/2">>]},
+ {next_protocols_advertised,
+ [<<"spdy/3">>, <<"http/1.1">>]}
+ | ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+ {_Client, OpenSSLPort} =
+ ssl_test_lib:start_client(openssl, [{port, Port}, {alpn, "http/1.1,spdy/2"},
+ {np,"spdy/3"}, {options, ClientOpts},
+ return_port], Config),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+ case ssl:negotiated_protocol(SSocket) of
+ {ok, AlpnProtocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, AlpnProtocol}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:close(SSocket).
%%--------------------------------------------------------------------
%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
check_openssl_alpn_support(Config) ->
- HelpText = os:cmd("openssl s_client --help"),
+ HelpText = ssl_test_lib:portable_cmd("openssl", ["s_client --help"]),
case string:str(HelpText, "alpn") of
0 ->
{skip, "Openssl not compiled with alpn support"};
_ ->
Config
end.
-
-start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) ->
- process_flag(trap_exit, true),
- ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
- ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0],
-
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
-
- Data = "From openssl to erlang",
-
- Port = ssl_test_lib:inet_port(node()),
- CaCertFile = proplists:get_value(cacertfile, ServerOpts),
- CertFile = proplists:get_value(certfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept",
- integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-CAfile", CaCertFile,
- "-cert", CertFile, "-key", KeyFile],
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
-
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {ssl_test_lib,
- erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}},
- {options, [{reuse_sessions, false} | ClientOpts]}]),
-
- Callback(Client, OpensslPort),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
-
- ssl_test_lib:close(Client),
- process_flag(trap_exit, false).
-
-start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, Callback) ->
- process_flag(trap_exit, true),
- ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]} | ServerOpts0],
-
- {_, ServerNode, _} = ssl_test_lib:run_where(Config),
-
-
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}},
- {options, ServerOpts}]),
- Port = ssl_test_lib:inet_port(Server),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = ["s_client", "-alpn", "http/1.0,spdy/2", "-msg", "-port",
- integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-host", "localhost"],
-
- OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- Callback(Server, OpenSslPort),
-
- ssl_test_lib:close(Server),
-
- ssl_test_lib:close_port(OpenSslPort),
- process_flag(trap_exit, false).
-
-start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Callback) ->
- process_flag(trap_exit, true),
- ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]},
- {client_preferred_next_protocols, {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts0],
-
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
-
- Data = "From openssl to erlang",
-
- Port = ssl_test_lib:inet_port(node()),
- CertFile = proplists:get_value(certfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-nextprotoneg",
- "spdy/3", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-cert", CertFile, "-key", KeyFile],
-
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
-
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {ssl_test_lib,
- erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}},
- {options, [{reuse_sessions, false} | ClientOpts]}]),
-
- Callback(Client, OpensslPort),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
-
- ssl_test_lib:close(Client),
- process_flag(trap_exit, false).
-
-start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Callback) ->
- process_flag(trap_exit, true),
- ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]},
- {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>]} | ServerOpts0],
-
- {_, ServerNode, _} = ssl_test_lib:run_where(Config),
-
-
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}},
- {options, ServerOpts}]),
- Port = ssl_test_lib:inet_port(Server),
- Version = ssl_test_lib:protocol_version(Config),
- Exe = "openssl",
- Args = ["s_client", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", "spdy/3",
- "-msg", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-host", "localhost"],
- OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- Callback(Server, OpenSslPort),
-
- ssl_test_lib:close(Server),
- ssl_test_lib:close_port(OpenSslPort),
- process_flag(trap_exit, false).
-
diff --git a/lib/ssl/test/openssl_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
index 5c10defd2f..fb1f28aa4a 100644
--- a/lib/ssl/test/openssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
@@ -22,27 +22,93 @@
-module(openssl_cipher_suite_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
--define(DEFAULT_TIMEOUT, {seconds, 30}).
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([dhe_psk_des_cbc/1,
+ dhe_rsa_3des_ede_cbc/1,
+ dhe_rsa_aes_128_cbc/1,
+ dhe_rsa_aes_128_gcm/1,
+ dhe_rsa_aes_256_cbc/1,
+ dhe_rsa_aes_256_gcm/1,
+ dhe_rsa_chacha20_poly1305/1,
+ ecdhe_rsa_3des_ede_cbc/1,
+ ecdhe_rsa_rc4_128/1,
+ ecdhe_rsa_aes_128_cbc/1,
+ ecdhe_rsa_aes_128_gcm/1,
+ ecdhe_rsa_aes_256_cbc/1,
+ ecdhe_rsa_aes_256_gcm/1,
+ ecdhe_rsa_chacha20_poly1305/1,
+ ecdhe_ecdsa_rc4_128/1,
+ ecdhe_ecdsa_3des_ede_cbc/1,
+ ecdhe_ecdsa_aes_128_cbc/1,
+ ecdhe_ecdsa_aes_128_gcm/1,
+ ecdhe_ecdsa_aes_256_cbc/1,
+ ecdhe_ecdsa_aes_256_gcm/1,
+ ecdhe_ecdsa_chacha20_poly1305/1,
+ rsa_des_cbc/1,
+ rsa_3des_ede_cbc/1,
+ rsa_aes_128_cbc/1,
+ rsa_aes_256_cbc/1,
+ rsa_aes_128_gcm/1,
+ rsa_aes_256_gcm/1,
+ rsa_rc4_128/1,
+ dhe_dss_des_cbc/1,
+ dhe_dss_3des_ede_cbc/1,
+ dhe_dss_aes_128_cbc/1,
+ dhe_dss_aes_256_cbc/1,
+ dhe_dss_aes_128_gcm/1,
+ dhe_dss_aes_256_gcm/1,
+ dh_anon_rc4_128/1,
+ dh_anon_3des_ede_cbc/1,
+ dh_anon_aes_128_cbc/1,
+ dh_anon_aes_128_gcm/1,
+ dh_anon_aes_256_cbc/1,
+ dh_anon_aes_256_gcm/1,
+ ecdh_anon_3des_ede_cbc/1,
+ ecdh_anon_aes_128_cbc/1,
+ ecdh_anon_aes_256_cbc/1,
+ aes_256_gcm_sha384/1,
+ aes_128_gcm_sha256/1,
+ chacha20_poly1305_sha256/1,
+ aes_128_ccm_sha256/1,
+ aes_128_ccm_8_sha256/1,
+ ecdhe_ecdsa_with_aes_128_ccm/1,
+ ecdhe_ecdsa_with_aes_256_ccm/1,
+ ecdhe_ecdsa_with_aes_128_ccm_8/1,
+ ecdhe_ecdsa_with_aes_256_ccm_8/1
+ ]).
+
+-define(DEFAULT_TIMEOUT, {seconds, 10}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
all() ->
- [
- {group, openssl_server},
- {group, openssl_client}
- ].
+ case ssl_test_lib:working_openssl_client() of
+ true ->
+ [{group, openssl_server},
+ {group, openssl_client}];
+ false ->
+ [{group, openssl_server}]
+ end.
all_protocol_groups() ->
- [{group, 'tlsv1.2'},
+ [
+ {group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
].
@@ -53,33 +119,42 @@ groups() ->
[
{openssl_server, all_protocol_groups()},
{openssl_client, all_protocol_groups()},
+ {'tlsv1.3', [], tls_1_3_kex()},
{'tlsv1.2', [], kex()},
{'tlsv1.1', [], kex()},
{'tlsv1', [], kex()},
- {'sslv3', [], kex()},
{'dtlsv1.2', [], dtls_kex()},
{'dtlsv1', [], dtls_kex()},
{dhe_rsa, [],[dhe_rsa_3des_ede_cbc,
dhe_rsa_aes_128_cbc,
+ dhe_rsa_aes_128_gcm,
dhe_rsa_aes_256_cbc,
+ dhe_rsa_aes_256_gcm,
dhe_rsa_chacha20_poly1305
]},
{ecdhe_rsa, [], [ecdhe_rsa_3des_ede_cbc,
+ ecdhe_rsa_rc4_128,
ecdhe_rsa_aes_128_cbc,
ecdhe_rsa_aes_128_gcm,
ecdhe_rsa_aes_256_cbc,
ecdhe_rsa_aes_256_gcm,
ecdhe_rsa_chacha20_poly1305
]},
+ {ecdhe_1_3_rsa_cert, [], tls_1_3_cipher_suites()},
{ecdhe_ecdsa, [],[ecdhe_ecdsa_rc4_128,
ecdhe_ecdsa_3des_ede_cbc,
ecdhe_ecdsa_aes_128_cbc,
ecdhe_ecdsa_aes_128_gcm,
ecdhe_ecdsa_aes_256_cbc,
ecdhe_ecdsa_aes_256_gcm,
- ecdhe_ecdsa_chacha20_poly1305
+ ecdhe_ecdsa_chacha20_poly1305,
+ ecdhe_ecdsa_with_aes_128_ccm,
+ ecdhe_ecdsa_with_aes_256_ccm,
+ ecdhe_ecdsa_with_aes_128_ccm_8,
+ ecdhe_ecdsa_with_aes_256_ccm_8
]},
- {rsa, [], [rsa_3des_ede_cbc,
+ {rsa, [], [rsa_des_cbc,
+ rsa_3des_ede_cbc,
rsa_aes_128_cbc,
rsa_aes_256_cbc,
rsa_rc4_128
@@ -137,6 +212,16 @@ groups() ->
%% ecdhe_psk_aes_256_cbc
%% ]}
].
+tls_1_3_kex() ->
+ [{group, ecdhe_1_3_rsa_cert}].
+
+tls_1_3_cipher_suites() ->
+ [aes_256_gcm_sha384,
+ aes_128_gcm_sha256,
+ chacha20_poly1305_sha256,
+ aes_128_ccm_sha256,
+ aes_128_ccm_8_sha256
+ ].
kex() ->
rsa() ++ ecdsa() ++ dss() ++ anonymous().
@@ -144,9 +229,6 @@ kex() ->
dtls_kex() -> %% Should be all kex in the future
dtls_rsa() ++ dss() ++ anonymous().
-ssl3_kex() ->
- ssl3_rsa() ++ ssl3_dss() ++ ssl3_anonymous().
-
rsa() ->
[{group, dhe_rsa},
{group, ecdhe_rsa},
@@ -160,11 +242,6 @@ dtls_rsa() ->
%%,{group, rsa_psk}
].
-ssl3_rsa() ->
- [{group, dhe_rsa},
- {group, rsa}
- ].
-
ecdsa() ->
[{group, ecdhe_ecdsa}].
@@ -173,10 +250,6 @@ dss() ->
%%{group, srp_dss}
].
-ssl3_dss() ->
- [{group, dhe_dss}
- ].
-
anonymous() ->
[{group, dh_anon},
{group, ecdh_anon}
@@ -186,9 +259,6 @@ anonymous() ->
%%{group, srp_anon}
].
-ssl3_anonymous() ->
- [{group, dh_anon}].
-
init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
@@ -206,23 +276,13 @@ end_per_suite(_Config) ->
%%--------------------------------------------------------------------
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config),
- do_init_per_group(GroupName, Config);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, {openssl_does_not_support, GroupName}}
- end;
- false ->
- do_init_per_group(GroupName, Config)
- end.
+ case ssl_test_lib:is_protocol_version(GroupName) of
+ true ->
+ ssl_test_lib:init_per_group_openssl(GroupName, Config);
+ false ->
+ do_init_per_group(GroupName, Config)
+ end.
+
do_init_per_group(openssl_client, Config0) ->
Config = proplists:delete(server_type, proplists:delete(client_type, Config0)),
[{client_type, openssl}, {server_type, erlang} | Config];
@@ -231,7 +291,8 @@ do_init_per_group(openssl_server, Config0) ->
[{client_type, erlang}, {server_type, openssl} | Config];
do_init_per_group(GroupName, Config) when GroupName == ecdh_anon;
GroupName == ecdhe_rsa;
- GroupName == ecdhe_psk ->
+ GroupName == ecdhe_psk;
+ GroupName == ecdhe_1_3_rsa_cert->
case proplists:get_bool(ecdh, proplists:get_value(public_keys, crypto:supports())) of
true ->
init_certs(GroupName, Config);
@@ -248,7 +309,8 @@ do_init_per_group(ecdhe_ecdsa = GroupName, Config) ->
end;
do_init_per_group(dhe_dss = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
- case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of
+ case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg)
+ andalso (ssl_test_lib:openssl_dsa_suites() =/= []) of
true ->
init_certs(GroupName, Config);
false ->
@@ -256,14 +318,15 @@ do_init_per_group(dhe_dss = GroupName, Config) ->
end;
do_init_per_group(srp_dss = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
- case lists:member(dss, PKAlg) andalso lists:member(srp, PKAlg) of
+ case lists:member(dss, PKAlg) andalso lists:member(srp, PKAlg)
+ andalso (ssl_test_lib:openssl_dsa_suites() =/= []) of
true ->
init_certs(GroupName, Config);
false ->
{skip, "Missing DSS_SRP crypto support"}
end;
do_init_per_group(GroupName, Config) when GroupName == srp_anon;
- GroupName == srp_rsa ->
+ GroupName == srp_rsa ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
case lists:member(srp, PKAlg) of
true ->
@@ -279,21 +342,33 @@ do_init_per_group(dhe_psk = GroupName, Config) ->
false ->
{skip, "Missing SRP crypto support"}
end;
-do_init_per_group(GroupName, Config0) ->
- case ssl_test_lib:is_tls_version(GroupName) of
+do_init_per_group(dhe_rsa = GroupName, Config) ->
+ PKAlg = proplists:get_value(public_keys, crypto:supports()),
+ case lists:member(dh, PKAlg) andalso lists:member(rsa, PKAlg) of
+ true ->
+ init_certs(GroupName, Config);
+ false ->
+ {skip, "Missing SRP crypto support"}
+ end;
+do_init_per_group(rsa = GroupName, Config) ->
+ PKAlg = proplists:get_value(public_keys, crypto:supports()),
+ case lists:member(rsa, PKAlg) andalso ssl_test_lib:openssl_support_rsa_kex() of
true ->
- ssl_test_lib:init_tls_version(GroupName, end_per_group(GroupName, Config0));
+ init_certs(GroupName, Config);
false ->
- init_certs(GroupName, Config0)
+ {skip, "Missing RSA key exchange support"}
+ end;
+do_init_per_group(dh_anon = GroupName, Config) ->
+ PKAlg = proplists:get_value(public_keys, crypto:supports()),
+ case lists:member(dh, PKAlg) of
+ true ->
+ init_certs(GroupName, Config);
+ false ->
+ {skip, "Missing SRP crypto support"}
end.
-
+
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(TestCase, Config) when TestCase == psk_3des_ede_cbc;
TestCase == srp_anon_3des_ede_cbc;
@@ -313,7 +388,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_3des_ede_cbc;
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(des_ede3, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap({seconds, ?DEFAULT_TIMEOUT}),
Config;
_ ->
{skip, "Missing 3DES crypto support"}
@@ -335,7 +410,6 @@ init_per_testcase(TestCase, Config) when TestCase == psk_rc4_128;
{skip, "Missing RC4 crypto support"}
end;
init_per_testcase(TestCase, Config) when TestCase == psk_aes_128_ccm_8;
- TestCase == rsa_psk_aes_128_ccm_8;
TestCase == psk_aes_128_ccm_8;
TestCase == dhe_psk_aes_128_ccm_8;
TestCase == ecdhe_psk_aes_128_ccm_8 ->
@@ -348,7 +422,6 @@ init_per_testcase(TestCase, Config) when TestCase == psk_aes_128_ccm_8;
{skip, "Missing AES_128_CCM crypto support"}
end;
init_per_testcase(TestCase, Config) when TestCase == psk_aes_256_ccm_8;
- TestCase == rsa_psk_aes_256_ccm_8;
TestCase == psk_aes_256_ccm_8;
TestCase == dhe_psk_aes_256_ccm_8;
TestCase == ecdhe_psk_aes_256_ccm_8 ->
@@ -360,6 +433,86 @@ init_per_testcase(TestCase, Config) when TestCase == psk_aes_256_ccm_8;
_ ->
{skip, "Missing AES_256_CCM crypto support"}
end;
+init_per_testcase(aes_256_gcm_sha384, Config) ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ SupHashs = proplists:get_value(hashs, crypto:supports()),
+ case (lists:member(aes_256_gcm, SupCiphers)) andalso
+ (lists:member(sha384, SupHashs)) of
+ true ->
+ ct:timetrap(?DEFAULT_TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_256_GCM crypto support"}
+ end;
+init_per_testcase(aes_128_gcm_sha256, Config) ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ SupHashs = proplists:get_value(hashs, crypto:supports()),
+ case lists:member(aes_128_gcm, SupCiphers) andalso
+ (lists:member(sha256, SupHashs)) of
+ true ->
+ ct:timetrap(?DEFAULT_TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_128_GCM crypto support"}
+ end;
+
+init_per_testcase(chacha20_poly1305_sha256, Config) ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ SupHashs = proplists:get_value(hashs, crypto:supports()),
+ case (lists:member(chacha20_poly1305, SupCiphers)) andalso
+ (lists:member(sha256, SupHashs)) of
+ true ->
+ ct:timetrap(?DEFAULT_TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing CHACHA20_POLY1305 crypto support"}
+ end;
+init_per_testcase(aes_128_ccm_sha256, Config) ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ SupHashs = proplists:get_value(hashs, crypto:supports()),
+ case (lists:member(aes_128_ccm, SupCiphers)) andalso
+ (lists:member(sha256, SupHashs)) of
+ true ->
+ ct:timetrap(?DEFAULT_TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_128_CCM crypto support"}
+ end;
+
+init_per_testcase(aes_128_ccm_8_sha256, Config) ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ SupHashs = proplists:get_value(hashs, crypto:supports()),
+ case (lists:member(aes_128_ccm, SupCiphers)) andalso
+ (lists:member(sha256, SupHashs)) of
+ true ->
+ ct:timetrap(?DEFAULT_TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_128_CCM_8 crypto support"}
+ end;
+
+init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_128_ccm;
+ TestCase == ecdhe_ecdsa_with_aes_128_ccm_8->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ case lists:member(aes_128_ccm, SupCiphers) of
+ true ->
+ ct:timetrap(?DEFAULT_TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_128_CCM crypto support"}
+ end;
+
+init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_256_ccm;
+ TestCase == ecdhe_ecdsa_with_aes_256_ccm_8 ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ case lists:member(aes_256_ccm, SupCiphers) of
+ true ->
+ ct:timetrap(?DEFAULT_TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_256_CCM crypto support"}
+ end;
+
init_per_testcase(TestCase, Config) ->
Cipher = ssl_test_lib:test_cipher(TestCase, Config),
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
@@ -432,6 +585,15 @@ init_certs(GroupName, Config) when GroupName == dhe_rsa;
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
+init_certs(ecdhe_1_3_rsa_cert, Config) ->
+ {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
+ {client_chain, ssl_test_lib:default_cert_chain_conf()}],
+ Config, ""),
+ [{tls_config, #{server_config => ServerOpts,
+ client_config => ClientOpts}} |
+ proplists:delete(tls_config, Config)];
+
+
init_certs(GroupName, Config) when GroupName == dhe_ecdsa;
GroupName == ecdhe_ecdsa ->
{ClientOpts, ServerOpts} = ssl_test_lib:make_ecc_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
@@ -460,6 +622,20 @@ init_certs(_GroupName, Config) ->
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
+aes_256_gcm_sha384(Config) when is_list(Config)->
+ run_ciphers_test(ecdhe_rsa, 'aes_256_gcm', Config).
+
+aes_128_gcm_sha256(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_rsa, 'aes_128_gcm', Config).
+
+chacha20_poly1305_sha256(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_rsa, 'chacha20_poly1305', Config).
+
+aes_128_ccm_sha256(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_rsa, 'aes_128_ccm', Config).
+
+aes_128_ccm_8_sha256(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_rsa, 'aes_128_ccm_8', Config).
%%--------------------------------------------------------------------
%% SRP --------------------------------------------------------
@@ -491,20 +667,8 @@ rsa_psk_3des_ede_cbc(Config) when is_list(Config) ->
rsa_psk_aes_128_cbc(Config) when is_list(Config) ->
run_ciphers_test(rsa_psk, 'aes_128_cbc', Config).
-rsa_psk_aes_128_ccm(Config) when is_list(Config) ->
- run_ciphers_test(rsa_psk, 'aes_128_ccm', Config).
-
-rsa_psk_aes_128_ccm_8(Config) when is_list(Config) ->
- run_ciphers_test(rsa_psk, 'aes_128_ccm_8', Config).
-
rsa_psk_aes_256_cbc(Config) when is_list(Config) ->
run_ciphers_test(rsa_psk, 'aes_256_cbc', Config).
-
-rsa_psk_aes_256_ccm(Config) when is_list(Config) ->
- run_ciphers_test(rsa_psk, 'aes_256_ccm', Config).
-
-rsa_psk_aes_256_ccm_8(Config) when is_list(Config) ->
- run_ciphers_test(rsa_psk, 'aes_256_ccm_8', Config).
rsa_psk_rc4_128(Config) when is_list(Config) ->
run_ciphers_test(rsa_psk, 'rc4_128', Config).
@@ -599,6 +763,18 @@ ecdhe_ecdsa_aes_256_gcm(Config) when is_list(Config) ->
ecdhe_ecdsa_chacha20_poly1305(Config) when is_list(Config) ->
run_ciphers_test(ecdhe_ecdsa, 'chacha20_poly1305', Config).
+
+ecdhe_ecdsa_with_aes_128_ccm(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm', Config).
+
+ecdhe_ecdsa_with_aes_256_ccm(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm', Config).
+
+ecdhe_ecdsa_with_aes_128_ccm_8(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm_8', Config).
+
+ecdhe_ecdsa_with_aes_256_ccm_8(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm_8', Config).
%%--------------------------------------------------------------------
%% DHE_DSS --------------------------------------------------------
%%--------------------------------------------------------------------
@@ -768,7 +944,7 @@ run_ciphers_test(Kex, Cipher, Config) ->
{skip, {not_sup, Kex, Cipher, Version}}
end.
-cipher_suite_test(CipherSuite, _Version, Config) ->
+cipher_suite_test(CipherSuite, Version, Config) ->
#{server_config := SOpts,
client_config := COpts} = proplists:get_value(tls_config, Config),
ServerOpts = ssl_test_lib:ssl_options(SOpts, Config),
@@ -776,21 +952,28 @@ cipher_suite_test(CipherSuite, _Version, Config) ->
ct:log("Testing CipherSuite ~p~n", [CipherSuite]),
ct:log("Server Opts ~p~n", [ServerOpts]),
ct:log("Client Opts ~p~n", [ClientOpts]),
- ssl_test_lib:basic_test([{ciphers, [CipherSuite]} | COpts], SOpts, Config).
-
+ case proplists:get_value(server_type, Config) of
+ erlang ->
+ ssl_test_lib:basic_test([{ciphers, ssl:cipher_suites(all, Version)} | COpts],
+ [{ciphers, [CipherSuite]} | SOpts], Config);
+ _ ->
+ ssl_test_lib:basic_test([{versions, [Version]}, {ciphers, [CipherSuite]} | COpts],
+ [{ciphers, ssl_test_lib:openssl_ciphers()} | SOpts], Config)
+ end.
test_ciphers(Kex, Cipher, Version) ->
- Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, Version) ++ ssl:cipher_suites(anonymous, Version),
- [{key_exchange,
- fun(Kex0) when Kex0 == Kex -> true;
- (_) -> false
- end},
- {cipher,
- fun(Cipher0) when Cipher0 == Cipher -> true;
- (_) -> false
- end}]),
+ Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version),
+ [{key_exchange,
+ fun(Kex0) when (Kex0 == Kex) andalso (Version =/= 'tlsv1.3') -> true;
+ (Kex0) when (Kex0 == any) andalso (Version == 'tlsv1.3') -> true;
+ (_) -> false
+ end},
+ {cipher,
+ fun(Cipher0) when Cipher0 == Cipher -> true;
+ (_) -> false
+ end}]),
ct:log("Version ~p Testing ~p~n", [Version, Ciphers]),
- OpenSSLCiphers = openssl_ciphers(),
+ OpenSSLCiphers = ssl_test_lib:openssl_ciphers(),
ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]),
lists:filter(fun(C) ->
ct:log("Cipher ~p~n", [C]),
@@ -798,6 +981,5 @@ test_ciphers(Kex, Cipher, Version) ->
end, Ciphers).
-openssl_ciphers() ->
- Str = os:cmd("openssl ciphers"),
- string:split(string:strip(Str, right, $\n), ":", all).
+openssl_suitestr_to_map(OpenSSLSuiteStrs) ->
+ [ssl_cipher_format:suite_openssl_str_to_map(SuiteStr) || SuiteStr <- OpenSSLSuiteStrs].
diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl
index dce40b5638..0248956056 100644
--- a/lib/ssl/test/openssl_client_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_client_cert_SUITE.erl
@@ -21,11 +21,55 @@
%%
-module(openssl_client_cert_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([no_auth/0,
+ no_auth/1,
+ auth/0,
+ auth/1,
+ client_auth_empty_cert_accepted/0,
+ client_auth_empty_cert_accepted/1,
+ client_auth_empty_cert_rejected/0,
+ client_auth_empty_cert_rejected/1,
+ client_auth_partial_chain/0,
+ client_auth_partial_chain/1,
+ client_auth_allow_partial_chain/0,
+ client_auth_allow_partial_chain/1,
+ client_auth_do_not_allow_partial_chain/0,
+ client_auth_do_not_allow_partial_chain/1,
+ client_auth_partial_chain_fun_fail/0,
+ client_auth_partial_chain_fun_fail/1,
+ missing_root_cert_no_auth/0,
+ missing_root_cert_no_auth/1,
+ unsupported_sign_algo_client_auth/0,
+ unsupported_sign_algo_client_auth/1,
+ unsupported_sign_algo_cert_client_auth/0,
+ unsupported_sign_algo_cert_client_auth/1,
+ hello_retry_request/0,
+ hello_retry_request/1,
+ custom_groups/0,
+ custom_groups/1,
+ hello_retry_client_auth/0,
+ hello_retry_client_auth/1,
+ hello_retry_client_auth_empty_cert_accepted/0,
+ hello_retry_client_auth_empty_cert_accepted/1,
+ hello_retry_client_auth_empty_cert_rejected/0,
+ hello_retry_client_auth_empty_cert_rejected/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -41,7 +85,6 @@ groups() ->
{'tlsv1.2', [], pre_tls_1_3_protocol_groups()},
{'tlsv1.1', [], pre_tls_1_3_protocol_groups()},
{'tlsv1', [], pre_tls_1_3_protocol_groups()},
- {'sslv3', [], ssl_protocol_groups()},
{'dtlsv1.2', [], pre_tls_1_3_protocol_groups()},
{'dtlsv1', [], pre_tls_1_3_protocol_groups()},
{rsa, [], all_version_tests()},
@@ -49,18 +92,27 @@ groups() ->
{dsa, [], all_version_tests()},
{rsa_1_3, [], all_version_tests() ++ tls_1_3_tests() ++ [unsupported_sign_algo_client_auth,
unsupported_sign_algo_cert_client_auth]},
+ {rsa_pss_rsae, [], all_version_tests() ++ tls_1_3_tests()},
+ {rsa_pss_pss, [], all_version_tests() ++ tls_1_3_tests()},
{ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()}
].
protocol_groups() ->
- [{group, 'tlsv1.3'},
- {group, 'tlsv1.2'},
- {group, 'tlsv1.1'},
- {group, 'tlsv1'},
- {group, 'sslv3'},
- {group, 'dtlsv1.2'},
- {group, 'dtlsv1'}
- ].
+ case ssl_test_lib:openssl_sane_dtls() of
+ true ->
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'},
+ {group, 'dtlsv1.2'},
+ {group, 'dtlsv1'}];
+ false ->
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'}
+ ]
+ end.
ssl_protocol_groups() ->
[{group, rsa},
@@ -73,6 +125,8 @@ pre_tls_1_3_protocol_groups() ->
tls_1_3_protocol_groups() ->
[{group, rsa_1_3},
+ {group, rsa_pss_rsae},
+ {group, rsa_pss_pss},
{group, ecdsa_1_3}].
tls_1_3_tests() ->
@@ -102,10 +156,15 @@ init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
ok ->
- ssl_test_lib:clean_start(),
- Config
+ case ssl_test_lib:working_openssl_client() of
+ true ->
+ ssl_test_lib:clean_start(),
+ Config;
+ false ->
+ {skip, "Broken OpenSSL s_client"}
+ end
catch _:_ ->
- {skip, "Crypto did not start"}
+ {skip, "Crypto did not start"}
end.
end_per_suite(_Config) ->
@@ -113,16 +172,16 @@ end_per_suite(_Config) ->
application:unload(ssl),
application:stop(crypto).
-init_per_group(openssl_client, Config0) ->
- Config = proplists:delete(server_type, proplists:delete(client_type, Config0)),
+init_per_group(openssl_client, Config) ->
[{client_type, openssl}, {server_type, erlang} | Config];
+
init_per_group(Group, Config0) when Group == rsa;
Group == rsa_1_3 ->
Config = ssl_test_lib:make_rsa_cert(Config0),
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
%% Make sure _rsa* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
true;
(ecdhe_rsa) ->
@@ -141,17 +200,41 @@ init_per_group(Group, Config0) when Group == rsa;
[] ->
{skip, {no_sup, Group, Version}}
end;
+init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
+ Alg == rsa_pss_pss ->
+ Supports = crypto:supports(),
+ RSAOpts = proplists:get_value(rsa_opts, Supports),
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
+ andalso lists:member(rsa_mgf1_md, RSAOpts)
+ andalso ssl_test_lib:is_sane_oppenssl_pss(Alg)
+ of
+ true ->
+ #{client_config := COpts,
+ server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(Alg, [], Config, ""),
+ [{cert_key_alg, Alg} |
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
+ lists:delete(client_cert_opts, Config))])];
+ false ->
+ {skip, "Missing crypto or OpenSSL support"}
+ end;
init_per_group(Group, Config0) when Group == ecdsa;
Group == ecdsa_1_3 ->
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
- lists:member(dh, PKAlg)) of
+ lists:member(dh, PKAlg))
+ andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
+ of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
COpts = proplists:get_value(client_ecdsa_opts, Config),
SOpts = proplists:get_value(server_ecdsa_opts, Config),
%% Make sure ecdh* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
true;
(ecdhe_ecdsa) ->
@@ -176,13 +259,14 @@ init_per_group(Group, Config0) when Group == ecdsa;
end;
init_per_group(Group, Config0) when Group == dsa ->
PKAlg = crypto:supports(public_keys),
- case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of
+ case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg)
+ andalso (ssl_test_lib:openssl_dsa_suites() =/= []) of
true ->
Config = ssl_test_lib:make_dsa_cert(Config0),
COpts = proplists:get_value(client_dsa_opts, Config),
SOpts = proplists:get_value(server_dsa_opts, Config),
%% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
true;
(dhe_dss) ->
@@ -205,31 +289,16 @@ init_per_group(Group, Config0) when Group == dsa ->
{skip, "Missing DSS crypto support"}
end;
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- [{version, GroupName}
- | ssl_test_lib:init_tls_version(GroupName, Config)];
- false ->
- {skip, "Missing openssl support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
+
init_per_testcase(TestCase, Config) when
TestCase == client_auth_empty_cert_accepted;
TestCase == client_auth_empty_cert_rejected ->
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
+
case Version of
sslv3 ->
%% Openssl client sends "No Certificate Reserved" warning ALERT
diff --git a/lib/ssl/test/openssl_key_update_SUITE.erl b/lib/ssl/test/openssl_key_update_SUITE.erl
index 4963f0bb30..55e302dd69 100644
--- a/lib/ssl/test/openssl_key_update_SUITE.erl
+++ b/lib/ssl/test/openssl_key_update_SUITE.erl
@@ -96,15 +96,16 @@ openssl_client_explicit_key_update(Config) ->
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client(openssl, [{port, Port}], Config),
- ssl_test_lib:send_recv_result_active(Client, Server, Data),
-
+ ssl_test_lib:send(Client, Data),
+ Data = ssl_test_lib:check_active_receive(Server, Data),
%% TODO s_client can hang after sending special commands e.g "k", "K"
%% ssl_test_lib:update_keys(Client, write),
%% ssl_test_lib:update_keys(Client, read_write),
ssl_test_lib:update_keys(Server, write),
ssl_test_lib:update_keys(Server, read_write),
- ssl_test_lib:send_recv_result_active(Client, Server, Data),
+ ssl_test_lib:send(Client, Data),
+ Data = ssl_test_lib:check_active_receive(Server, Data),
ssl_test_lib:close(Client),
ssl_test_lib:close(Server).
@@ -121,14 +122,16 @@ openssl_server_explicit_key_update(Config) ->
Client = ssl_test_lib:start_client(erlang, [{port, Port},
{log_level, debug},
{versions, ['tlsv1.2','tlsv1.3']}],Config),
- ssl_test_lib:send_recv_result_active(Server, Client, Data),
-
+ ssl_test_lib:send(Server, Data),
+ Data = ssl_test_lib:check_active_receive(Client, Data),
+
ssl_test_lib:update_keys(Client, write),
ssl_test_lib:update_keys(Client, read_write),
ssl_test_lib:update_keys(Server, write),
ssl_test_lib:update_keys(Server, read_write),
- ssl_test_lib:send_recv_result_active(Client, Server, Data),
+ ssl_test_lib:send(Server, Data),
+ Data = ssl_test_lib:check_active_receive(Client, Data),
ssl_test_lib:close(Client),
ssl_test_lib:close(Server).
diff --git a/lib/ssl/test/openssl_mfl_SUITE.erl b/lib/ssl/test/openssl_mfl_SUITE.erl
new file mode 100644
index 0000000000..c26934082a
--- /dev/null
+++ b/lib/ssl/test/openssl_mfl_SUITE.erl
@@ -0,0 +1,263 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(openssl_mfl_SUITE).
+-include_lib("common_test/include/ct.hrl").
+
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([openssl_client/1,
+ openssl_server/1,
+ reuse_session_erlang_server/1,
+ reuse_session_erlang_client/1]).
+
+-define(SLEEP, 500).
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+all() ->
+ case ssl_test_lib:openssl_dtls_maxfraglen_support() of
+ true ->
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'},
+ {group, 'dtlsv1.2'},
+ {group, 'dtlsv1'}];
+ false ->
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'}]
+ end.
+
+groups() ->
+ [{'tlsv1.3', [], common_tests()},
+ {'tlsv1.2', [], common_tests() ++ pre_tls_1_3()},
+ {'tlsv1.1', [], common_tests() ++ pre_tls_1_3()},
+ {'tlsv1', [], common_tests() ++ pre_tls_1_3()},
+ {'dtlsv1.2', [], common_tests() ++ pre_tls_1_3()},
+ {'dtlsv1', [], common_tests() ++ pre_tls_1_3()}
+ ].
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ case ssl_test_lib:openssl_maxfraglen_support() of
+ true ->
+ ssl_test_lib:clean_start(),
+ ssl:clear_pem_cache(),
+ Config = ssl_test_lib:make_rsa_cert(Config0),
+ ssl_test_lib:cert_options(Config);
+ false ->
+ {skip, "max_fragment_length not supported by OpenSSL"}
+ end
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ ssl:stop(),
+ application:stop(crypto).
+
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
+
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
+
+init_per_testcase(_TestCase, Config) ->
+ ct:timetrap({seconds, 10}),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+common_tests() ->
+ [openssl_client, openssl_server].
+
+pre_tls_1_3() ->
+ [reuse_session_erlang_server, reuse_session_erlang_client].
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+openssl_server(Config) when is_list(Config) ->
+ openssl_server(512, Config),
+ openssl_server(2048, Config).
+
+%--------------------------------------------------------------------------------
+%% check max_fragment_length interworking with openssl client
+openssl_client(Config) when is_list(Config) ->
+ openssl_client(1024, Config),
+ openssl_client(4096, Config).
+
+%--------------------------------------------------------------------------------
+reuse_session_erlang_server(Config) when is_list(Config) ->
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_opts, Config),
+
+ {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+
+ MFL = 512,
+ Data = "reuse_session_erlang_server " ++ lists:duplicate(MFL, $r),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, active_recv, [length(Data)]}},
+ {reconnect_times, 5},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
+ {reconnect, true},
+ {maxfrag, MFL},
+ {options, ClientOpts},
+ return_port], Config),
+ max_frag_len_test(Server, OpenSSLPort, MFL, Data),
+ ssl_test_lib:close(Server).
+
+%%--------------------------------------------------------------------
+
+reuse_session_erlang_client(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ MFL = 512,
+ Data = "reuse_session_erlang_client " ++ lists:duplicate(MFL, $r),
+ ClientOpts = [{max_fragment_length, MFL} | ClientOpts0],
+
+ {Server, OpenSSLPort} = ssl_test_lib:start_server(openssl, [{maxfrag, MFL}, return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+
+ Client0 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()},
+ {options, [{reuse_sessions, save}, {verify, verify_peer}| ClientOpts]}]),
+
+ SID = receive
+ {Client0, Id0} ->
+ Id0
+ end,
+
+ %% quit s_server's current session so we can interact with the next client
+ true = port_command(OpenSSLPort, "q\n"),
+ ssl_test_lib:close(Client0),
+
+ Client1 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_session, SID} | ClientOpts]}]),
+ receive
+ {Client1, SID} ->
+ ok
+ after ?SLEEP ->
+ ct:fail(session_not_reused)
+ end,
+
+ ErlRecvFun = fun() ->
+ Data = ssl_test_lib:check_active_receive(Client1, Data)
+ end,
+ max_frag_len_test(Client1, OpenSSLPort, MFL, Data, ErlRecvFun),
+ ssl_test_lib:close(Client1).
+
+
+openssl_client(MFL, Config) ->
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_opts, Config),
+ {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+
+ Data = "mfl_openssl_server " ++ lists:duplicate(MFL, $s),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, active_recv, [length(Data)]}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
+ {maxfrag, MFL},
+ {options, ClientOpts},
+ return_port], Config),
+
+ max_frag_len_test(Server, OpenSSLPort, MFL, Data).
+
+%% -------------------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------------------
+
+openssl_server(MFL, Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+ Data = "mfl_openssl_server " ++ lists:duplicate(MFL, $s),
+
+ {Server, OpenSSLPort} = ssl_test_lib:start_server(openssl, [{maxfrag, MFL},
+ return_port],
+ [{server_opts, ServerOpts} | Config]),
+ 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,
+ active_recv, [length(Data)]}},
+ {options, [{max_fragment_length, MFL} | ClientOpts]}]),
+
+ max_frag_len_test(Client, OpenSSLPort, MFL, Data).
+
+%% ------------------------------------------------------------
+max_frag_len_test(ErlProc, OpenSSL, MFL, Data) ->
+ ErlRecvFun = fun() ->
+ receive
+ {ErlProc, Data} ->
+ ok
+ end
+ end,
+ max_frag_len_test(ErlProc, OpenSSL, MFL, Data, ErlRecvFun).
+
+max_frag_len_test(ErlProc, OpenSSL, MFL, Data, ErlRecvFun) ->
+ true = port_command(OpenSSL, Data),
+ ErlRecvFun(),
+
+ ErlProc ! get_socket,
+ ErlSocket = receive
+ {ErlProc, {socket, ErlSocket0}} ->
+ ErlSocket0
+ end,
+ ssl_test_lib:assert_mfl(ErlSocket, MFL).
diff --git a/lib/ssl/test/openssl_npn_SUITE.erl b/lib/ssl/test/openssl_npn_SUITE.erl
index 7322e228bd..31fdc6ce51 100644
--- a/lib/ssl/test/openssl_npn_SUITE.erl
+++ b/lib/ssl/test/openssl_npn_SUITE.erl
@@ -21,11 +21,32 @@
-module(openssl_npn_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([erlang_client_openssl_server_npn/0,
+ erlang_client_openssl_server_npn/1,
+ erlang_server_openssl_client_npn/1,
+ erlang_server_openssl_client_npn_only_client/1,
+ erlang_server_openssl_client_npn_only_server/1,
+ erlang_client_openssl_server_npn_only_client/1,
+ erlang_client_openssl_server_npn_only_server/1,
+ erlang_server_openssl_client_npn_renegotiate/1,
+ erlang_client_openssl_server_npn_renegotiate/0,
+ erlang_client_openssl_server_npn_renegotiate/1
+ ]).
+
-define(OPENSSL_QUIT, "Q\n").
-define(OPENSSL_RENEGOTIATE, "R\n").
-define(SLEEP, 1000).
@@ -65,7 +86,7 @@ init_per_suite(Config0) ->
false ->
{skip, "Openssl not found"};
_ ->
- case check_openssl_npn_support(Config0) of
+ case ssl_test_lib:check_openssl_npn_support(Config0) of
{skip, _} = Skip ->
Skip;
_ ->
@@ -88,43 +109,23 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(TestCase, Config) ->
- ct:timetrap({seconds, 10}),
+ ct:timetrap({seconds, 30}),
special_init(TestCase, Config).
special_init(erlang_client_openssl_server_npn_renegotiate, Config) ->
{ok, Version} = application:get_env(ssl, protocol_version),
- ssl_test_lib:check_sane_openssl_renegotaite(Config, Version);
+ ssl_test_lib:check_sane_openssl_renegotiate(Config, Version);
special_init(erlang_server_openssl_client_npn_renegotiate, Config) ->
{ok, Version} = application:get_env(ssl, protocol_version),
- case ssl_test_lib:check_sane_openssl_renegotaite(Config, Version) of
+ case ssl_test_lib:check_sane_openssl_renegotiate(Config, Version) of
Config ->
- ssl_test_lib:openssl_allows_client_renegotaite(Config);
+ ssl_test_lib:openssl_allows_client_renegotiate(Config);
Skip ->
Skip
end;
@@ -142,174 +143,246 @@ erlang_client_openssl_server_npn() ->
[{doc,"Test erlang client with openssl server doing npn negotiation"}].
erlang_client_openssl_server_npn(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data,
- fun(Client, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Client, Data)
- end).
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ NpnProtocol = <<"spdy/2">>,
+
+ {Server, OpenSSLPort} =
+ ssl_test_lib:start_server(openssl, [{np,"http/1.1,spdy/2"},return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {Client, CSocket} =
+ ssl_test_lib:start_client(erlang, [{port, Port},
+ return_socket],
+ [{client_opts,
+ [{client_preferred_next_protocols,
+ {client, [NpnProtocol], <<"http/1.1">>}} | ClientOpts]}
+ | Config]),
+
+ case ssl:negotiated_protocol(CSocket) of
+ {ok, NpnProtocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, NpnProtocol}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Client, OpenSSLPort),
+ ssl:close(CSocket).
%%--------------------------------------------------------------------
erlang_client_openssl_server_npn_renegotiate() ->
[{doc,"Test erlang client with openssl server doing npn negotiation and renegotiate"}].
erlang_client_openssl_server_npn_renegotiate(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data,
- fun(Client, OpensslPort) ->
- true = port_command(OpensslPort,
- ?OPENSSL_RENEGOTIATE),
- ct:sleep(?SLEEP),
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Client, Data)
- end).
+
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ NpnProtocol = <<"spdy/2">>,
+
+ Server = ssl_test_lib:start_server(openssl, [{np,"http/1.1,spdy/2"}],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {_, CSocket} =
+ ssl_test_lib:start_client(erlang, [{port, Port},
+ return_socket],
+ [{client_opts,
+ [{client_preferred_next_protocols,
+ {client, [NpnProtocol], <<"http/1.1">>}} | ClientOpts]} | Config]),
+
+ case ssl:negotiated_protocol(CSocket) of
+ {ok, NpnProtocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, NpnProtocol}, {got, Result}}})
+ end,
+ ssl_test_lib:send(Server, ?OPENSSL_RENEGOTIATE),
+ ct:sleep(1000),
+ %%% Should still be the same as initially negotiated
+ case ssl:negotiated_protocol(CSocket) of
+ {ok, NpnProtocol} ->
+ ok;
+ Other ->
+ ct:fail({error, {{expected, NpnProtocol}, {got, Other}}})
+ end.
+
%%--------------------------------------------------------------------------
erlang_server_openssl_client_npn() ->
[{doc,"Test erlang server with openssl client and npn negotiation"}].
erlang_server_openssl_client_npn(Config) when is_list(Config) ->
-
- Data = "From openssl to erlang",
- start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data,
- fun(Server, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
-
+ ClientOpts = proplists:get_value(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ Protocol = <<"spdy/2">>,
+ Server = ssl_test_lib:start_server(erlang, [{from, self()}],
+ [{server_opts, [{next_protocols_advertised,
+ [<<"spdy/2">>]} |ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+ {_Client, OpenSSLPort} =
+ ssl_test_lib:start_client(openssl, [{port, Port},
+ {np, "spdy/2"},
+ {options, ClientOpts},
+ return_port], Config),
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+ case ssl:negotiated_protocol(SSocket) of
+ {ok, Protocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, Protocol}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:close(SSocket).
+
+
%%--------------------------------------------------------------------------
erlang_server_openssl_client_npn_renegotiate() ->
[{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}].
erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data,
- fun(Server, OpensslPort) ->
- true = port_command(OpensslPort,
- ?OPENSSL_RENEGOTIATE),
- ct:sleep(?SLEEP),
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
-%%--------------------------------------------------------------------------
-erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, [],
- ["-nextprotoneg", "spdy/2"], Data,
- fun(Server, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
-
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ NpnProtocol = <<"spdy/2">>,
+ Server =
+ ssl_test_lib:start_server(erlang, [{from, self()}],
+ [{server_opts, [{next_protocols_advertised,
+ [NpnProtocol]} | ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+ {_Client, OpenSSLPort} =
+ ssl_test_lib:start_client(openssl, [{port, Port}, {np, "spdy/2"},
+ {options, ClientOpts}, return_port], Config),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+ case ssl:negotiated_protocol(SSocket) of
+ {ok, NpnProtocol} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, NpnProtocol}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:renegotiate(SSocket),
+ case ssl:negotiated_protocol(SSocket) of
+ {ok, NpnProtocol} ->
+ ok;
+ Other ->
+ ct:fail({error, {{expected, NpnProtocol}, {got, Other}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:close(SSocket).
%%--------------------------------------------------------------------------
-
erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config,
- [{client_preferred_next_protocols,
- {client, [<<"spdy/2">>], <<"http/1.1">>}}], [],
- Data,
- fun(Server, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+
+ {Server, OpenSSLPort} =
+ ssl_test_lib:start_server(openssl, [{np,"spdy/2"}, return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {Client, CSocket} =
+ ssl_test_lib:start_client(erlang, [{port, Port},
+ return_socket],
+ [{client_opts, ClientOpts} | Config]),
+
+ case ssl:negotiated_protocol(CSocket) of
+ {error, protocol_not_negotiated} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, undefined}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Client, OpenSSLPort),
+ ssl:close(CSocket).
%%--------------------------------------------------------------------------
+erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) ->
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+
+ {Server, OpenSSLPort} =
+ ssl_test_lib:start_server(openssl, [{np,"spdy/2"}, return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ {Client, CSocket} =
+ ssl_test_lib:start_client(erlang, [{port, Port},
+ return_socket],
+ [{client_opts, ClientOpts} | Config]),
+
+ case ssl:negotiated_protocol(CSocket) of
+ {error, protocol_not_negotiated} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, undefined}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Client, OpenSSLPort),
+ ssl:close(CSocket).
+
+%%--------------------------------------------------------------------------
erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config,
- [{next_protocols_advertised, [<<"spdy/2">>]}], [],
- Data,
- fun(Server, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ Server =
+ ssl_test_lib:start_server(erlang, [{from, self()}],
+ [{server_opts, [{client_preferred_next_protocols,
+ {client, [<<"spdy/2">>], <<"http/1.1">>}
+ } | ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+ {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
+ {options, ClientOpts},
+ return_port], Config),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+ case ssl:negotiated_protocol(SSocket) of
+ {error, protocol_not_negotiated} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, undefined}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:close(SSocket).
+%%--------------------------------------------------------------------------
erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) ->
- Data = "From openssl to erlang",
- ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, [], ["-nextprotoneg", "spdy/2"],
- Data,
- fun(Server, OpensslPort) ->
- true = port_command(OpensslPort, Data),
- ssl_test_lib:check_result(Server, Data)
- end).
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ Server = ssl_test_lib:start_server(erlang, [{from, self()}],
+ [{server_opts, [ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
+ {_Client, OpenSSLPort}
+ = ssl_test_lib:start_client(openssl, [{port, Port},
+ {np, "spdy/2"},
+ {options, ClientOpts},
+ return_port], Config),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+ case ssl:negotiated_protocol(SSocket) of
+ {error, protocol_not_negotiated} ->
+ ok;
+ Result ->
+ ct:fail({error, {{expected, undefined}, {got, Result}}})
+ end,
+ ssl_test_lib:sanity_check(Server, OpenSSLPort),
+ ssl:close(SSocket).
%%--------------------------------------------------------------------
%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
-
-start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) ->
- process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
- ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0],
-
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
-
- Data = "From openssl to erlang",
-
- Port = ssl_test_lib:inet_port(node()),
- CaCertFile = proplists:get_value(cacertfile, ServerOpts),
- CertFile = proplists:get_value(certfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port),
- ssl_test_lib:version_flag(Version),
- "-CAfile", CaCertFile,
- "-cert", CertFile, "-key", KeyFile],
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
-
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {ssl_test_lib,
- erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}},
- {options, ClientOpts}]),
-
- Callback(Client, OpensslPort),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
-
- ssl_test_lib:close(Client),
- process_flag(trap_exit, false).
-
-start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) ->
- process_flag(trap_exit, true),
- ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]} | ServerOpts0],
-
- {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}},
- {options, ServerOpts}]),
- Port = ssl_test_lib:inet_port(Server),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-msg", "-connect",
- ssl_test_lib:hostname_format(Hostname) ++ ":"
- ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)],
-
- OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- Callback(Server, OpenSslPort),
-
- ssl_test_lib:close(Server),
-
- ssl_test_lib:close_port(OpenSslPort),
- process_flag(trap_exit, false).
-
-check_openssl_npn_support(Config) ->
- HelpText = os:cmd("openssl s_client --help"),
- case string:str(HelpText, "nextprotoneg") of
- 0 ->
- {skip, "Openssl not compiled with nextprotoneg support"};
- _ ->
- Config
- end.
diff --git a/lib/ssl/test/openssl_ocsp_SUITE.erl b/lib/ssl/test/openssl_ocsp_SUITE.erl
new file mode 100644
index 0000000000..d328a3cfbd
--- /dev/null
+++ b/lib/ssl/test/openssl_ocsp_SUITE.erl
@@ -0,0 +1,321 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(openssl_ocsp_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([ocsp_stapling_basic/0,ocsp_stapling_basic/1,
+ ocsp_stapling_with_nonce/0, ocsp_stapling_with_nonce/1,
+ ocsp_stapling_with_responder_cert/0,ocsp_stapling_with_responder_cert/1,
+ ocsp_stapling_revoked/0, ocsp_stapling_revoked/1
+ ]).
+
+%% spawn export
+-export([ocsp_responder_init/3]).
+
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+all() ->
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'dtlsv1.2'}].
+
+groups() ->
+ [{'tlsv1.3', [], ocsp_tests()},
+ {'tlsv1.2', [], ocsp_tests()},
+ {'dtlsv1.2', [], ocsp_tests()}].
+
+ocsp_tests() ->
+ [ocsp_stapling_basic,
+ ocsp_stapling_with_nonce,
+ ocsp_stapling_with_responder_cert,
+ ocsp_stapling_revoked
+ ].
+
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case ssl_test_lib:openssl_ocsp_support() of
+ true ->
+ do_init_per_suite(Config);
+ false ->
+ {skip, "OCSP not well supported in openSSL"}
+ end.
+
+do_init_per_suite(Config) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+
+ %% Prepare certs
+ {ok, _} = make_certs:all(DataDir, PrivDir),
+
+ ResponderPort = get_free_port(),
+ Pid = start_ocsp_responder(ResponderPort, PrivDir),
+
+ NewConfig =
+ lists:merge(
+ [{responder_port, ResponderPort},
+ {responder_pid, Pid}
+ ], Config),
+
+ ssl_test_lib:cert_options(NewConfig)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+
+end_per_suite(Config) ->
+ ResponderPid = proplists:get_value(responder_pid, Config),
+ ssl_test_lib:close(ResponderPid),
+ ok = ssl:stop(),
+ application:stop(crypto).
+
+%%--------------------------------------------------------------------
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
+
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
+
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap({seconds, 10}),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+ocsp_stapling_basic() ->
+ [{doc, "Verify OCSP stapling works without nonce "
+ "and responder certs."}].
+ocsp_stapling_basic(Config)
+ when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
+
+ Data = "ping", %% 4 bytes
+ GroupName = proplists:get_value(group, Config),
+ ServerOpts = [{log_level, debug},
+ {group, GroupName}],
+ Server = ssl_test_lib:start_server(openssl_ocsp,
+ [{options, ServerOpts}], Config),
+ Port = ssl_test_lib:inet_port(Server),
+
+ ClientOpts = [{log_level, debug},
+ {verify, verify_peer},
+ {cacertfile, CACertsFile},
+ {server_name_indication, disable},
+ {ocsp_stapling, true},
+ {ocsp_nonce, false}] ++ dtls_client_opt(GroupName),
+ Client = ssl_test_lib:start_client(erlang,
+ [{port, Port},
+ {options, ClientOpts}], Config),
+ ssl_test_lib:send(Server, Data),
+ Data = ssl_test_lib:check_active_receive(Client, Data),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+%%--------------------------------------------------------------------
+ocsp_stapling_with_nonce() ->
+ [{doc, "Verify OCSP stapling works with nonce."}].
+ocsp_stapling_with_nonce(Config)
+ when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
+
+ Data = "ping", %% 4 bytes
+ GroupName = proplists:get_value(group, Config),
+ ServerOpts = [{log_level, debug},
+ {group, GroupName}],
+ Server = ssl_test_lib:start_server(openssl_ocsp,
+ [{options, ServerOpts}], Config),
+ Port = ssl_test_lib:inet_port(Server),
+
+ ClientOpts = [{log_level, debug},
+ {verify, verify_peer},
+ {cacertfile, CACertsFile},
+ {server_name_indication, disable},
+ {ocsp_stapling, true},
+ {ocsp_nonce, true}] ++ dtls_client_opt(GroupName),
+ Client = ssl_test_lib:start_client(erlang,
+ [{port, Port},
+ {options, ClientOpts}], Config),
+ ssl_test_lib:send(Server, Data),
+ Data = ssl_test_lib:check_active_receive(Client, Data),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+ocsp_stapling_with_responder_cert() ->
+ [{doc, "Verify OCSP stapling works with nonce "
+ "and responder certs."}].
+ocsp_stapling_with_responder_cert(Config)
+ when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
+
+ Data = "ping", %% 4 bytes
+ GroupName = proplists:get_value(group, Config),
+ ServerOpts = [{log_level, debug},
+ {group, GroupName}],
+ Server = ssl_test_lib:start_server(openssl_ocsp,
+ [{options, ServerOpts}], Config),
+ Port = ssl_test_lib:inet_port(Server),
+
+ PrivDir = proplists:get_value(priv_dir, Config),
+ {ok, ResponderCert} =
+ file:read_file(filename:join(PrivDir, "b.server/cert.pem")),
+ [{'Certificate', Der, _IsEncrypted}] =
+ public_key:pem_decode(ResponderCert),
+
+ ClientOpts = [{log_level, debug},
+ {verify, verify_peer},
+ {cacertfile, CACertsFile},
+ {server_name_indication, disable},
+ {ocsp_stapling, true},
+ {ocsp_nonce, true},
+ {ocsp_responder_certs, [Der]}] ++ dtls_client_opt(GroupName),
+ Client = ssl_test_lib:start_client(erlang,
+ [{port, Port},
+ {options, ClientOpts}], Config),
+ ssl_test_lib:send(Server, Data),
+ Data = ssl_test_lib:check_active_receive(Client, Data),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+%%--------------------------------------------------------------------
+ocsp_stapling_revoked() ->
+ [{doc, "Verify OCSP stapling works with revoked certificate."}].
+ocsp_stapling_revoked(Config)
+ when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ CACertsFile = filename:join(PrivDir, "revoked/cacerts.pem"),
+
+ GroupName = proplists:get_value(group, Config),
+ ServerOpts = [{log_level, debug},
+ {group, GroupName}],
+ {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server(openssl_ocsp_revoked,
+ [{options, ServerOpts}], Config),
+ Port = ssl_test_lib:inet_port(Server),
+
+ ClientOpts = [{log_level, debug},
+ {verify, verify_peer},
+ {server_name_indication, disable},
+ {cacertfile, CACertsFile},
+ {ocsp_stapling, true},
+ {ocsp_nonce, true}
+ ] ++ dtls_client_opt(GroupName),
+
+ Client = ssl_test_lib:start_client_error([{node, ClientNode},{port, Port},
+ {host, Hostname}, {from, self()},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_client_alert(Client, certificate_revoked).
+
+%%--------------------------------------------------------------------
+%% Intrernal functions -----------------------------------------------
+%%--------------------------------------------------------------------
+start_ocsp_responder(ResponderPort, PrivDir) ->
+ Starter = self(),
+ Pid = erlang:spawn_link(
+ ?MODULE, ocsp_responder_init, [ResponderPort, PrivDir, Starter]),
+ receive
+ {started, Pid} ->
+ Pid;
+ {'EXIT', Pid, Reason} ->
+ throw({unable_to_start_ocsp_service, Reason})
+ end.
+
+ocsp_responder_init(ResponderPort, PrivDir, Starter) ->
+ Index = filename:join(PrivDir, "otpCA/index.txt"),
+ CACerts = filename:join(PrivDir, "b.server/cacerts.pem"),
+ Cert = filename:join(PrivDir, "b.server/cert.pem"),
+ Key = filename:join(PrivDir, "b.server/key.pem"),
+
+ Args = ["ocsp", "-index", Index, "-CA", CACerts, "-rsigner", Cert,
+ "-rkey", Key, "-port", erlang:integer_to_list(ResponderPort)],
+ process_flag(trap_exit, true),
+ Port = ssl_test_lib:portable_open_port("openssl", Args),
+ ocsp_responder_loop(Port, {new, Starter}).
+
+ocsp_responder_loop(Port, {Status, Starter} = State) ->
+ receive
+ stop_ocsp_responder ->
+ ct:log("Shut down OCSP responder!~n"),
+ ok = ssl_test_lib:close_port(Port);
+ {_Port, closed} ->
+ ct:log("Port Closed~n"),
+ ok;
+ {'EXIT', _Port, Reason} ->
+ ct:log("Port Closed ~p~n",[Reason]),
+ ok;
+ {Port, {data, _Msg}} when Status == new ->
+ Starter ! {started, self()},
+ ocsp_responder_loop(Port, {started, undefined});
+ {Port, {data, Msg}} ->
+ ct:pal("Responder Msg ~p~n",[Msg]),
+ ocsp_responder_loop(Port, State)
+ after 1000 ->
+ case Status of
+ new ->
+ exit(no_ocsp_server);
+ _ ->
+ ocsp_responder_loop(Port, State)
+ end
+ end.
+
+stop_ocsp_responder(Pid) ->
+ Pid ! stop_ocsp_responder.
+
+get_free_port() ->
+ {ok, Listen} = gen_tcp:listen(0, [{reuseaddr, true}]),
+ {ok, Port} = inet:port(Listen),
+ ok = gen_tcp:close(Listen),
+ Port.
+
+dtls_client_opt('dtlsv1.2') ->
+ [{protocol, dtls}];
+dtls_client_opt(_Other) ->
+ []. \ No newline at end of file
diff --git a/lib/ssl/test/openssl_reject_SUITE.erl b/lib/ssl/test/openssl_reject_SUITE.erl
index deefd11823..e231a5ab99 100644
--- a/lib/ssl/test/openssl_reject_SUITE.erl
+++ b/lib/ssl/test/openssl_reject_SUITE.erl
@@ -21,10 +21,31 @@
-module(openssl_reject_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([erlang_client_bad_openssl_server/0,
+ erlang_client_bad_openssl_server/1,
+ ssl2_erlang_server_openssl_client/0,
+ ssl2_erlang_server_openssl_client/1,
+ ssl3_erlang_server_openssl_client/0,
+ ssl3_erlang_server_openssl_client/1
+ ]).
+
+%% Apply export
+-export([server_sent_garbage/1
+ ]).
+
-define(SLEEP, 1000).
-define(OPENSSL_GARBAGE, "P\n").
@@ -36,20 +57,20 @@
all() ->
[{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
- {group, 'tlsv1'},
- {group, 'sslv3'}].
+ {group, 'tlsv1'}
+ ].
groups() ->
[{'tlsv1.2', [], all_versions_tests()},
{'tlsv1.1', [], all_versions_tests()},
- {'tlsv1', [], all_versions_tests()},
- {'sslv3', [], all_versions_tests()}
+ {'tlsv1', [], all_versions_tests()}
].
-
+
all_versions_tests() ->
- [
+ [
erlang_client_bad_openssl_server,
- ssl2_erlang_server_openssl_client
+ ssl2_erlang_server_openssl_client,
+ ssl3_erlang_server_openssl_client
].
init_per_suite(Config0) ->
@@ -74,30 +95,10 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(TestCase, Config) ->
ct:timetrap({seconds, 10}),
@@ -110,6 +111,14 @@ special_init(ssl2_erlang_server_openssl_client, Config) ->
false ->
{skip, "sslv2 not supported by openssl"}
end;
+special_init(ssl3_erlang_server_openssl_client, Config) ->
+ case ssl_test_lib:supports_ssl_tls_version(sslv3) of
+ true ->
+ Config;
+ false ->
+ {skip, "sslv3 not supported by openssl"}
+ end;
+
special_init(_, Config) ->
Config.
@@ -193,12 +202,37 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) ->
ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]),
ssl_test_lib:consume_port_exit(OpenSslPort),
- ssl_test_lib:check_server_alert(Server, unexpected_message),
+ ssl_test_lib:check_server_alert(Server, bad_record_mac),
process_flag(trap_exit, false).
+%%--------------------------------------------------------------------
+ssl3_erlang_server_openssl_client() ->
+ [{doc,"Test that ssl v3 clients are rejected"}].
+
+ssl3_erlang_server_openssl_client(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Exe = "openssl",
+ Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname) ++ ":" ++ integer_to_list(Port),
+ "-ssl3", "-msg"],
+
+ OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
+
+ ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]),
+ ssl_test_lib:consume_port_exit(OpenSslPort),
+ ssl_test_lib:check_server_alert(Server, bad_record_mac),
+ process_flag(trap_exit, false).
%%--------------------------------------------------------------------
-%% Internal functions ------------------------------------------------
+%% Callback functions ------------------------------------------------
%%--------------------------------------------------------------------
server_sent_garbage(Socket) ->
diff --git a/lib/ssl/test/openssl_renegotiate_SUITE.erl b/lib/ssl/test/openssl_renegotiate_SUITE.erl
index f40d0b1575..e43e691c0b 100644
--- a/lib/ssl/test/openssl_renegotiate_SUITE.erl
+++ b/lib/ssl/test/openssl_renegotiate_SUITE.erl
@@ -21,11 +21,34 @@
-module(openssl_renegotiate_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([erlang_client_openssl_server_renegotiate/0,
+ erlang_client_openssl_server_renegotiate/1,
+ erlang_client_openssl_server_renegotiate_after_client_data/0,
+ erlang_client_openssl_server_renegotiate_after_client_data/1,
+ erlang_client_openssl_server_nowrap_seqnum/0,
+ erlang_client_openssl_server_nowrap_seqnum/1,
+ erlang_server_openssl_client_nowrap_seqnum/0,
+ erlang_server_openssl_client_nowrap_seqnum/1
+ ]).
+
+%% Apply export
+-export([delayed_send/2,
+ send_wait_send/2
+ ]).
+
-define(SLEEP, 1000).
-define(OPENSSL_RENEGOTIATE, "R\n").
-define(DEFAULT_TIMEOUT, {seconds, 30}).
@@ -40,14 +63,13 @@ all() ->
[{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}];
false ->
[{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
- {group, 'tlsv1'},
- {group, 'sslv3'}]
+ {group, 'tlsv1'}
+ ]
end.
groups() ->
@@ -56,15 +78,13 @@ groups() ->
[{'tlsv1.2', [], all_versions_tests()},
{'tlsv1.1', [], all_versions_tests()},
{'tlsv1', [], all_versions_tests()},
- {'sslv3', [], all_versions_tests()},
{'dtlsv1.2', [], all_versions_tests()},
{'dtlsv1', [], all_versions_tests()}
];
false ->
[{'tlsv1.2', [], all_versions_tests()},
{'tlsv1.1', [], all_versions_tests()},
- {'tlsv1', [], all_versions_tests()},
- {'sslv3', [], all_versions_tests()}
+ {'tlsv1', [], all_versions_tests()}
]
end.
@@ -99,36 +119,24 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
+ case ssl_test_lib:check_sane_openssl_version(GroupName) of
true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:check_sane_openssl_renegotaite(ssl_test_lib:init_tls_version(GroupName,
- Config),
- GroupName);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- Config
+ case ssl_test_lib:check_sane_openssl_renegotiate(Config, GroupName) of
+ {skip,_} = Skip ->
+ Skip;
+ _ ->
+ ssl_test_lib:init_per_group_openssl(GroupName, Config)
+ end;
+ false ->
+ {skip, {atom_to_list(GroupName) ++ " not supported by OpenSSL"}}
end.
-
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
+
init_per_testcase(erlang_client_openssl_server_nowrap_seqnum, Config) ->
ct:timetrap(?DEFAULT_TIMEOUT),
- ssl_test_lib:openssl_allows_client_renegotaite(Config);
-init_per_testcase(TestCase, Config) ->
+ ssl_test_lib:openssl_allows_client_renegotiate(Config);
+init_per_testcase(_TestCase, Config) ->
ct:timetrap(?DEFAULT_TIMEOUT),
Config.
@@ -142,7 +150,6 @@ end_per_testcase(_, Config) ->
erlang_client_openssl_server_renegotiate() ->
[{doc,"Test erlang client when openssl server issuses a renegotiate"}].
erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
@@ -151,21 +158,9 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
ErlData = "From erlang to openssl",
OpenSslData = "From openssl to erlang",
- Port = ssl_test_lib:inet_port(node()),
- CertFile = proplists:get_value(certfile, ServerOpts),
- CaCertFile = proplists:get_value(cacertfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = ["s_server", "-accept", integer_to_list(Port),
- ssl_test_lib:version_flag(Version),
- "-CAfile", CaCertFile,
- "-cert", CertFile, "-key", KeyFile, "-msg"],
-
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+ {Server, OpenSSLPort} = ssl_test_lib:start_server(openssl, [return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -174,22 +169,17 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
delayed_send, [[ErlData, OpenSslData]]}},
{options, [{reuse_sessions, false} | ClientOpts]}]),
- true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE),
+ true = port_command(OpenSSLPort, ?OPENSSL_RENEGOTIATE),
ct:sleep(?SLEEP),
- true = port_command(OpensslPort, OpenSslData),
+ true = port_command(OpenSSLPort, OpenSslData),
ssl_test_lib:check_result(Client, OpenSslData),
+ ssl_test_lib:close(Client).
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
- ssl_test_lib:close(Client),
- process_flag(trap_exit, false),
- ok.
%%--------------------------------------------------------------------
erlang_client_openssl_server_renegotiate_after_client_data() ->
[{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}].
erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
@@ -198,21 +188,9 @@ erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(
ErlData = "From erlang to openssl",
OpenSslData = "From openssl to erlang",
- Port = ssl_test_lib:inet_port(node()),
- CaCertFile = proplists:get_value(cacertfile, ServerOpts),
- CertFile = proplists:get_value(certfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = ["s_server", "-accept", integer_to_list(Port),
- ssl_test_lib:version_flag(Version),
- "-CAfile", CaCertFile,
- "-cert", CertFile, "-key", KeyFile, "-msg"],
-
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+ {Server, OpenSSLPort} = ssl_test_lib:start_server(openssl, [return_port],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -221,17 +199,12 @@ erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(
send_wait_send, [[ErlData, OpenSslData]]}},
{options, [{reuse_sessions, false} |ClientOpts]}]),
- true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE),
+ true = port_command(OpenSSLPort, ?OPENSSL_RENEGOTIATE),
ct:sleep(?SLEEP),
- true = port_command(OpensslPort, OpenSslData),
+ true = port_command(OpenSSLPort, OpenSslData),
ssl_test_lib:check_result(Client, OpenSslData),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
- ssl_test_lib:close(Client),
- process_flag(trap_exit, false),
- ok.
+ ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
erlang_client_openssl_server_nowrap_seqnum() ->
@@ -240,7 +213,6 @@ erlang_client_openssl_server_nowrap_seqnum() ->
"in the testcase we use the test option renegotiate_at"
" to lower treashold substantially."}].
erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
@@ -249,21 +221,9 @@ erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) ->
ErlData = "From erlang to openssl\n",
N = 10,
- Port = ssl_test_lib:inet_port(node()),
- CaCertFile = proplists:get_value(cacertfile, ServerOpts),
- CertFile = proplists:get_value(certfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
- Version = ssl_test_lib:protocol_version(Config),
- Exe = "openssl",
- Args = ["s_server", "-accept", integer_to_list(Port),
- ssl_test_lib:version_flag(Version),
- "-CAfile", CaCertFile,
- "-cert", CertFile, "-key", KeyFile, "-msg"],
-
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
-
+ Server = ssl_test_lib:start_server(openssl, [],
+ [{server_opts, ServerOpts} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -273,11 +233,8 @@ erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) ->
{renegotiate_at, N} | ClientOpts]}]),
ssl_test_lib:check_result(Client, ok),
+ ssl_test_lib:close(Client).
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
- ssl_test_lib:close(Client),
- process_flag(trap_exit, false).
%%--------------------------------------------------------------------
erlang_server_openssl_client_nowrap_seqnum() ->
[{doc, "Test that erlang server will renegotiate session when",
@@ -287,8 +244,9 @@ erlang_server_openssl_client_nowrap_seqnum() ->
erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ {_, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
Data = "From openssl to erlang",
@@ -300,26 +258,17 @@ erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) ->
trigger_renegotiate, [[Data, N+2]]}},
{options, [{renegotiate_at, N}, {reuse_sessions, false} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- Version = ssl_test_lib:protocol_version(Config),
- Exe = "openssl",
- Args = ["s_client","-connect", ssl_test_lib:hostname_format(Hostname) ++ ":" ++ integer_to_list(Port),
- ssl_test_lib:version_flag(Version),
- "-msg"],
- OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- true = port_command(OpenSslPort, Data),
+ {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
+ {options, ClientOpts},
+ return_port], Config),
+ true = port_command(OpenSSLPort, Data),
ssl_test_lib:check_result(Server, ok),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close(Server),
- ssl_test_lib:close_port(OpenSslPort),
- process_flag(trap_exit, false).
-
+ ssl_test_lib:close(Server).
%%--------------------------------------------------------------------
-%% Internal functions ------------------------------------------------
+%% Callbacks
%%--------------------------------------------------------------------
delayed_send(Socket, [ErlData, OpenSslData]) ->
ct:sleep(?SLEEP),
diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl
index 8e97f70064..e71bfc8e5c 100644
--- a/lib/ssl/test/openssl_server_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_server_cert_SUITE.erl
@@ -21,11 +21,56 @@
%%
-module(openssl_server_cert_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+
+%% Test cases
+-export([no_auth/0,
+ no_auth/1,
+ auth/0,
+ auth/1,
+ client_auth_empty_cert_accepted/0,
+ client_auth_empty_cert_accepted/1,
+ client_auth_empty_cert_rejected/0,
+ client_auth_empty_cert_rejected/1,
+ client_auth_partial_chain/0,
+ client_auth_partial_chain/1,
+ client_auth_allow_partial_chain/0,
+ client_auth_allow_partial_chain/1,
+ client_auth_do_not_allow_partial_chain/0,
+ client_auth_do_not_allow_partial_chain/1,
+ client_auth_partial_chain_fun_fail/0,
+ client_auth_partial_chain_fun_fail/1,
+ missing_root_cert_no_auth/0,
+ missing_root_cert_no_auth/1,
+ unsupported_sign_algo_client_auth/0,
+ unsupported_sign_algo_client_auth/1,
+ unsupported_sign_algo_cert_client_auth/0,
+ unsupported_sign_algo_cert_client_auth/1,
+ hello_retry_request/0,
+ hello_retry_request/1,
+ custom_groups/0,
+ custom_groups/1,
+ hello_retry_client_auth/0,
+ hello_retry_client_auth/1,
+ hello_retry_client_auth_empty_cert_accepted/0,
+ hello_retry_client_auth_empty_cert_accepted/1,
+ hello_retry_client_auth_empty_cert_rejected/0,
+ hello_retry_client_auth_empty_cert_rejected/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -40,7 +85,6 @@ groups() ->
{'tlsv1.2', [], pre_tls_1_3_protocol_groups()},
{'tlsv1.1', [], pre_tls_1_3_protocol_groups()},
{'tlsv1', [], pre_tls_1_3_protocol_groups()},
- {'sslv3', [], ssl_protocol_groups()},
{'dtlsv1.2', [], pre_tls_1_3_protocol_groups()},
{'dtlsv1', [], pre_tls_1_3_protocol_groups()},
{rsa, [], all_version_tests()},
@@ -50,18 +94,27 @@ groups() ->
%% TODO: Create proper conf of openssl server
%%++ [unsupported_sign_algo_client_auth,
%% unsupported_sign_algo_cert_client_auth]},
+ {rsa_pss_rsae, [], all_version_tests() ++ tls_1_3_tests()},
+ {rsa_pss_pss, [], all_version_tests() ++ tls_1_3_tests()},
{ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()}
].
protocol_groups() ->
- [{group, 'tlsv1.3'},
- {group, 'tlsv1.2'},
- {group, 'tlsv1.1'},
- {group, 'tlsv1'},
- {group, 'sslv3'},
- {group, 'dtlsv1.2'},
- {group, 'dtlsv1'}
- ].
+ case ssl_test_lib:openssl_sane_dtls() of
+ true ->
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'},
+ {group, 'dtlsv1.2'},
+ {group, 'dtlsv1'}];
+ false ->
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'}
+ ]
+ end.
ssl_protocol_groups() ->
[{group, rsa},
@@ -74,6 +127,8 @@ pre_tls_1_3_protocol_groups() ->
tls_1_3_protocol_groups() ->
[{group, rsa_1_3},
+ {group, rsa_pss_rsae},
+ {group, rsa_pss_pss},
{group, ecdsa_1_3}].
tls_1_3_tests() ->
@@ -116,7 +171,7 @@ init_per_group(rsa = Group, Config0) ->
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
%% Make sure _rsa* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
true;
(ecdhe_rsa) ->
@@ -140,7 +195,7 @@ init_per_group(rsa_1_3 = Group, Config0) ->
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
%% Make sure _rsa* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(undefined, Version),
case Ciphers of
[_|_] ->
@@ -153,16 +208,40 @@ init_per_group(rsa_1_3 = Group, Config0) ->
[] ->
{skip, {no_sup, Group, Version}}
end;
+init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
+ Alg == rsa_pss_pss ->
+ Supports = crypto:supports(),
+ RSAOpts = proplists:get_value(rsa_opts, Supports),
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
+ andalso lists:member(rsa_mgf1_md, RSAOpts)
+ andalso ssl_test_lib:is_sane_oppenssl_pss(Alg)
+ of
+ true ->
+ #{client_config := COpts,
+ server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(Alg, [], Config, ""),
+ [{cert_key_alg, Alg} |
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
+ lists:delete(client_cert_opts, Config))])];
+ false ->
+ {skip, "Missing crypto or OpenSSL support"}
+ end;
init_per_group(ecdsa = Group, Config0) ->
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
- lists:member(dh, PKAlg)) of
+ lists:member(dh, PKAlg))
+ andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
+ of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
COpts = proplists:get_value(client_ecdsa_opts, Config),
SOpts = proplists:get_value(server_ecdsa_opts, Config),
%% Make sure ecdh* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
true;
(ecdhe_ecdsa) ->
@@ -188,13 +267,15 @@ init_per_group(ecdsa = Group, Config0) ->
init_per_group(ecdsa_1_3 = Group, Config0) ->
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
- lists:member(dh, PKAlg)) of
+ lists:member(dh, PKAlg))
+ andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
+ of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
COpts = proplists:get_value(client_ecdsa_opts, Config),
SOpts = proplists:get_value(server_ecdsa_opts, Config),
%% Make sure ecdh* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(undefined, Version),
case Ciphers of
[_|_] ->
@@ -213,13 +294,14 @@ init_per_group(ecdsa_1_3 = Group, Config0) ->
end;
init_per_group(Group, Config0) when Group == dsa ->
PKAlg = crypto:supports(public_keys),
- case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of
+ case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) andalso
+ (ssl_test_lib:openssl_dsa_suites() =/= []) of
true ->
Config = ssl_test_lib:make_dsa_cert(Config0),
COpts = proplists:get_value(client_dsa_opts, Config),
SOpts = proplists:get_value(server_dsa_opts, Config),
%% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server
- Version = proplists:get_value(version,Config),
+ Version = ssl_test_lib:protocol_version(Config),
Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
true;
(dhe_dss) ->
@@ -242,27 +324,10 @@ init_per_group(Group, Config0) when Group == dsa ->
{skip, "Missing DSS crypto support"}
end;
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- [{version, GroupName}
- | ssl_test_lib:init_tls_version(GroupName, Config)];
- false ->
- {skip, "Missing openssl support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
diff --git a/lib/ssl/test/openssl_session_SUITE.erl b/lib/ssl/test/openssl_session_SUITE.erl
index ceeb496ba4..08369733dc 100644
--- a/lib/ssl/test/openssl_session_SUITE.erl
+++ b/lib/ssl/test/openssl_session_SUITE.erl
@@ -21,13 +21,27 @@
-module(openssl_session_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([reuse_session_erlang_server/0,
+ reuse_session_erlang_server/1,
+ reuse_session_erlang_client/0,
+ reuse_session_erlang_client/1
+ ]).
-define(SLEEP, 1000).
-define(EXPIRE, 10).
+-define(TIMEOUT, {seconds, 120}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -39,14 +53,13 @@ all() ->
[{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}];
false ->
[{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
- {group, 'tlsv1'},
- {group, 'sslv3'}]
+ {group, 'tlsv1'}
+ ]
end.
groups() ->
@@ -55,15 +68,13 @@ groups() ->
[{'tlsv1.2', [], tests()},
{'tlsv1.1', [], tests()},
{'tlsv1', [], tests()},
- {'sslv3', [], tests()},
{'dtlsv1.2', [], tests()},
{'dtlsv1', [], tests()}
];
false ->
[{'tlsv1.2', [], tests()},
{'tlsv1.1', [], tests()},
- {'tlsv1', [], tests()},
- {'sslv3', [], tests()}
+ {'tlsv1', [], tests()}
]
end.
@@ -84,7 +95,11 @@ init_per_suite(Config0) ->
try crypto:start() of
ok ->
ssl_test_lib:clean_start(),
- ssl_test_lib:make_rsa_cert(Config0)
+ {ClientOpts, ServerOpts} =
+ ssl_test_lib:make_rsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
+ {client_chain, ssl_test_lib:default_cert_chain_conf()}],
+ Config0, "openssl_session_SUITE"),
+ [{client_opts, ClientOpts}, {server_opts, ServerOpts} | Config0]
catch _:_ ->
{skip, "Crypto did not start"}
end
@@ -96,31 +111,10 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(reuse_session_erlang_client, Config) ->
ct:timetrap(?EXPIRE * 1000 * 5),
@@ -130,22 +124,27 @@ init_per_testcase(reuse_session_erlang_client, Config) ->
ssl:start(),
Config;
init_per_testcase(reuse_session_erlang_server, Config) ->
- Version = ssl_test_lib:protocol_version(Config),
- case ssl_test_lib:is_dtls_version(Version) of
+ case ssl_test_lib:working_openssl_client() of
true ->
- case ssl_test_lib:openssl_sane_dtls_session_reuse() of
+ Version = ssl_test_lib:protocol_version(Config),
+ case ssl_test_lib:is_dtls_version(Version) of
true ->
- ct:timetrap({seconds, 10}),
- Config;
+ case ssl_test_lib:openssl_sane_dtls_session_reuse() of
+ true ->
+ ct:timetrap(?TIMEOUT),
+ Config;
+ false ->
+ {skip, "Broken OpenSSL DTLS session reuse"}
+ end;
false ->
- {skip, "Broken OpenSSL DTLS session reuse"}
+ ct:timetrap(?TIMEOUT),
+ Config
end;
- false ->
- ct:timetrap({seconds, 10}),
- Config
+ false ->
+ {skip, "Broken OpenSSL s_client"}
end;
-init_per_testcase(TestCase, Config) ->
- ct:timetrap({seconds, 10}),
+init_per_testcase(_TestCase, Config) ->
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(reuse_session_erlang_client, Config) ->
@@ -163,66 +162,58 @@ reuse_session_erlang_server() ->
[{doc, "Test erlang server with openssl client that reconnects with the"
"same session id, to test reusing of sessions."}].
reuse_session_erlang_server(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ClientOpts = proplists:get_value(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ Version = ssl_test_lib:protocol_version(Config),
- {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+ Ciphers = common_ciphers(Version),
+
Data = "From openssl to erlang",
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, active_recv, [length(Data)]}},
{reconnect_times, 5},
- {options, ServerOpts}]),
+ {options, [{ciphers, Ciphers},
+ {versions, [Version]}| ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
- ++ ":" ++ integer_to_list(Port),
- ssl_test_lib:version_flag(Version),
- "-reconnect"],
-
- OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
+
- true = port_command(OpenSslPort, Data),
+ {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
+ {reconnect, true},
+ {options, [{ciphers, Ciphers} | ClientOpts]},
+ return_port], Config),
+ true = port_command(OpenSSLPort, Data),
ssl_test_lib:check_result(Server, Data),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close(Server),
- ssl_test_lib:close_port(OpenSslPort).
+ ssl_test_lib:close(Server).
%%--------------------------------------------------------------------
reuse_session_erlang_client() ->
[{doc, "Test erlang ssl client that wants to reuse sessions"}].
reuse_session_erlang_client(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
-
- Version = ssl_test_lib:protocol_version(Config),
- Port = ssl_test_lib:inet_port(node()),
- CertFile = proplists:get_value(certfile, ServerOpts),
- CACertFile = proplists:get_value(cacertfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
-
- Exe = "openssl",
- Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-cert", CertFile,"-key", KeyFile, "-CAfile", CACertFile],
-
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = proplists:get_value(server_opts, Config),
+ Version = ssl_test_lib:protocol_version(Config),
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+ Ciphers = common_ciphers(Version),
+ Server = ssl_test_lib:start_server(openssl, [],
+ [{server_opts, [{ciphers, Ciphers} | ServerOpts]} | Config]),
+ Port = ssl_test_lib:inet_port(Server),
Client0 =
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, session_id, []}},
- {from, self()}, {options, [{reuse_sessions, save}, {verify, verify_peer}| ClientOpts]}]),
+ {from, self()},
+ {options, [{reuse_sessions, save},
+ {verify, verify_peer},
+ {ciphers, Ciphers},
+ {versions, [Version]} | ClientOpts]}]),
SID = receive
{Client0, Id0} ->
@@ -235,7 +226,9 @@ reuse_session_erlang_client(Config) when is_list(Config) ->
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, session_id, []}},
- {from, self()}, {options, [{reuse_session, SID} | ClientOpts]}]),
+ {from, self()}, {options, [ {ciphers, Ciphers},
+ {versions, [Version]},
+ {reuse_session, SID} | ClientOpts]}]),
receive
{Client1, SID} ->
ok
@@ -252,7 +245,8 @@ reuse_session_erlang_client(Config) when is_list(Config) ->
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, session_id, []}},
- {from, self()}, {options, ClientOpts}]),
+ {from, self()}, {options, [{ciphers, Ciphers},
+ {versions, [Version]} | ClientOpts]}]),
receive
{Client2, ID} ->
case ID of
@@ -262,12 +256,16 @@ reuse_session_erlang_client(Config) when is_list(Config) ->
ok
end
end,
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
ssl_test_lib:close(Client2).
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
+
+common_ciphers(Version) ->
+ OpenSSLCiphers = ssl_test_lib:openssl_ciphers(),
+ ErlOpenSSLCiphers = [ssl:str_to_suite(C) || C <- OpenSSLCiphers],
+ ErlCiphers = ssl:cipher_suites(all, Version),
+ [Suite || Suite <- ErlOpenSSLCiphers, lists:member(Suite, ErlCiphers)].
+
diff --git a/lib/ssl/test/openssl_session_ticket_SUITE.erl b/lib/ssl/test/openssl_session_ticket_SUITE.erl
index 775048e355..effcedc7ee 100644
--- a/lib/ssl/test/openssl_session_ticket_SUITE.erl
+++ b/lib/ssl/test/openssl_session_ticket_SUITE.erl
@@ -40,7 +40,21 @@
openssl_client_basic/0,
openssl_client_basic/1,
openssl_client_hrr/0,
- openssl_client_hrr/1]).
+ openssl_client_hrr/1,
+ openssl_server_early_data_basic/0,
+ openssl_server_early_data_basic/1,
+ openssl_server_early_data_big/0,
+ openssl_server_early_data_big/1,
+ openssl_server_early_data_manual/0,
+ openssl_server_early_data_manual/1,
+ openssl_server_early_data_manual_big/0,
+ openssl_server_early_data_manual_big/1,
+ openssl_server_early_data_manual_2_tickets/0,
+ openssl_server_early_data_manual_2_tickets/1,
+ openssl_server_early_data_manual_2_chacha_tickets/0,
+ openssl_server_early_data_manual_2_chacha_tickets/1,
+ openssl_client_early_data_basic/0,
+ openssl_client_early_data_basic/1]).
-include("tls_handshake.hrl").
@@ -62,14 +76,21 @@ groups() ->
{group, openssl_server}]},
{openssl_server, [], [openssl_server_basic,
openssl_server_hrr,
- openssl_server_hrr_multiple_tickets
+ openssl_server_hrr_multiple_tickets,
+ openssl_server_early_data_basic,
+ openssl_server_early_data_big,
+ openssl_server_early_data_manual,
+ openssl_server_early_data_manual_big,
+ openssl_server_early_data_manual_2_tickets,
+ openssl_server_early_data_manual_2_chacha_tickets
]},
{stateful, [], session_tests()},
{stateless, [], session_tests()}].
session_tests() ->
[openssl_client_basic,
- openssl_client_hrr].
+ openssl_client_hrr,
+ openssl_client_early_data_basic].
init_per_suite(Config0) ->
catch crypto:stop(),
@@ -112,28 +133,18 @@ end_per_testcase(_TestCase, Config) ->
openssl_server_basic() ->
[{doc,"Test session resumption with session tickets (erlang client - openssl server)"}].
openssl_server_basic(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
- Version = 'tlsv1.3',
- Port = ssl_test_lib:inet_port(node()),
- CertFile = proplists:get_value(certfile, ServerOpts),
- CACertFile = proplists:get_value(cacertfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
-
%% Configure session tickets
ClientOpts = [{session_tickets, auto}, {log_level, debug},
{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
- Exe = "openssl",
- Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-cert", CertFile,"-key", KeyFile, "-CAfile", CACertFile, "-msg", "-debug"],
-
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+ Server = ssl_test_lib:start_server(openssl, [],
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
%% Store ticket from first connection
Client0 = ssl_test_lib:start_client([{node, ClientNode},
@@ -156,17 +167,16 @@ openssl_server_basic(Config) when is_list(Config) ->
[true, no_reply]}},
{from, self()},
{options, ClientOpts}]),
- process_flag(trap_exit, false),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
+ ssl_test_lib:close(Server),
ssl_test_lib:close(Client1).
openssl_client_basic() ->
[{doc,"Test session resumption with session tickets (openssl client - erlang server)"}].
openssl_client_basic(Config) when is_list(Config) ->
ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ClientOpts = proplists:get_value(client_rsa_opts, Config),
+
+ {_, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
TicketFile0 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket0"]),
TicketFile1 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket1"]),
ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
@@ -185,75 +195,52 @@ openssl_client_basic(Config) when is_list(Config) ->
[false]}},
{options, ServerOpts}]),
- Version = 'tlsv1.3',
Port0 = ssl_test_lib:inet_port(Server0),
- Exe = "openssl",
- Args0 = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
- ++ ":" ++ integer_to_list(Port0),
- ssl_test_lib:version_flag(Version),
- "-sess_out", TicketFile0],
-
- OpenSslPort0 = ssl_test_lib:portable_open_port(Exe, Args0),
+ Client0 = ssl_test_lib:start_client(openssl, [{port, Port0},
+ {options, ClientOpts},
+ {session_args, ["-sess_out", TicketFile0]}], Config),
- true = port_command(OpenSslPort0, Data),
+ ssl_test_lib:send(Client0, Data),
ssl_test_lib:check_result(Server0, ok),
Server0 ! {listen, {mfa, {ssl_test_lib,
- verify_active_session_resumption,
+ verify_active_session_resumption,
[true]}}},
-
- %% Wait for session ticket
+ ssl_test_lib:close(Client0),
+ %% %% Wait for session ticket
ct:sleep(100),
-
- Args1 = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
- ++ ":" ++ integer_to_list(Port0),
- ssl_test_lib:version_flag(Version),
- "-sess_in", TicketFile0,
- "-sess_out", TicketFile1],
-
- OpenSslPort1 = ssl_test_lib:portable_open_port(Exe, Args1),
-
- true = port_command(OpenSslPort1, Data),
-
+
+ Client1 = ssl_test_lib:start_client(openssl, [{port, Port0},
+ {options, ClientOpts},
+ {session_args, ["-sess_in", TicketFile0,
+ "-sess_out", TicketFile1]}], Config),
+
+
+ ssl_test_lib:send(Client1, Data),
ssl_test_lib:check_result(Server0, ok),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close(Server0),
- ssl_test_lib:close_port(OpenSslPort0),
- ssl_test_lib:close_port(OpenSslPort1).
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
openssl_server_hrr() ->
[{doc,"Test session resumption with session tickets and hello_retry_request (erlang client - openssl server)"}].
openssl_server_hrr(Config) when is_list(Config) ->
- process_flag(trap_exit, true),
ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
- Version = 'tlsv1.3',
- Port = ssl_test_lib:inet_port(node()),
- CertFile = proplists:get_value(certfile, ServerOpts),
- CACertFile = proplists:get_value(cacertfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
-
%% Configure session tickets
ClientOpts = [{session_tickets, auto}, {log_level, debug},
{versions, ['tlsv1.2','tlsv1.3']},
{supported_groups,[secp256r1, x25519]}|ClientOpts0],
- Exe = "openssl",
- Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-cert", CertFile,
- "-key", KeyFile,
- "-CAfile", CACertFile,
- "-groups", "X448:X25519",
- "-msg", "-debug"],
-
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+
+ Server = ssl_test_lib:start_server(openssl, [{groups, "X448:X25519"}],
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
%% Store ticket from first connection
Client0 = ssl_test_lib:start_client([{node, ClientNode},
@@ -276,17 +263,15 @@ openssl_server_hrr(Config) when is_list(Config) ->
[true, no_reply]}},
{from, self()},
{options, ClientOpts}]),
- process_flag(trap_exit, false),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
+ ssl_test_lib:close(Server),
ssl_test_lib:close(Client1).
openssl_client_hrr() ->
[{doc,"Test session resumption with session tickets and hello_retry_request (openssl client - erlang server)"}].
openssl_client_hrr(Config) when is_list(Config) ->
ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ClientOpts = proplists:get_value(client_rsa_opts, Config),
+ {_, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
TicketFile0 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket0"]),
TicketFile1 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket1"]),
ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
@@ -306,19 +291,15 @@ openssl_client_hrr(Config) when is_list(Config) ->
[false]}},
{options, ServerOpts}]),
- Version = 'tlsv1.3',
Port0 = ssl_test_lib:inet_port(Server0),
+
- Exe = "openssl",
- Args0 = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
- ++ ":" ++ integer_to_list(Port0),
- ssl_test_lib:version_flag(Version),
- "-groups", "P-256:X25519",
- "-sess_out", TicketFile0],
-
- OpenSslPort0 = ssl_test_lib:portable_open_port(Exe, Args0),
+ Client0 = ssl_test_lib:start_client(openssl, [{port, Port0},
+ {options, ClientOpts},
+ {groups, "P-256:X25519"},
+ {session_args, ["-sess_out", TicketFile0]}], Config),
- true = port_command(OpenSslPort0, Data),
+ ssl_test_lib:send(Client0, Data),
ssl_test_lib:check_result(Server0, ok),
@@ -327,56 +308,40 @@ openssl_client_hrr(Config) when is_list(Config) ->
[true]}}},
%% Wait for session ticket
+ ssl_test_lib:close(Client0),
ct:sleep(100),
- Args1 = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
- ++ ":" ++ integer_to_list(Port0),
- ssl_test_lib:version_flag(Version),
- "-groups", "P-256:X25519",
- "-sess_in", TicketFile0,
- "-sess_out", TicketFile1],
-
- OpenSslPort1 = ssl_test_lib:portable_open_port(Exe, Args1),
-
- true = port_command(OpenSslPort1, Data),
+ Client1 = ssl_test_lib:start_client(openssl, [{port, Port0},
+ {options, ClientOpts},
+ {groups, "P-256:X25519"},
+ {session_args, ["-sess_in", TicketFile0,
+ "-sess_out", TicketFile1]}], Config),
+ ssl_test_lib:send(Client1, Data),
ssl_test_lib:check_result(Server0, ok),
- %% Clean close down! Server needs to be closed first !!
ssl_test_lib:close(Server0),
- ssl_test_lib:close_port(OpenSslPort0),
- ssl_test_lib:close_port(OpenSslPort1).
+ ssl_test_lib:close(Client1).
openssl_server_hrr_multiple_tickets() ->
- [{doc,"Test session resumption with multiple session tickets and hello_retry_request (erlang client - openssl server)"}].
+ [{doc,"Test session resumption with multiple session tickets and hello_retry_request "
+ "(erlang client - openssl server)"}].
openssl_server_hrr_multiple_tickets(Config) when is_list(Config) ->
process_flag(trap_exit, true),
ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
- Version = 'tlsv1.3',
- Port = ssl_test_lib:inet_port(node()),
- CertFile = proplists:get_value(certfile, ServerOpts),
- CACertFile = proplists:get_value(cacertfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
-
%% Configure session tickets
ClientOpts = [{session_tickets, manual}, {log_level, debug},
{versions, ['tlsv1.2','tlsv1.3']},
{supported_groups,[secp256r1, x25519]}|ClientOpts0],
- Exe = "openssl",
- Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-cert", CertFile,
- "-key", KeyFile,
- "-CAfile", CACertFile,
- "-groups", "X448:X25519",
- "-msg", "-debug"],
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+ Server = ssl_test_lib:start_server(openssl, [{groups, "X448:X25519"}],
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
%% Store ticket from first connection
Client0 = ssl_test_lib:start_client([{node, ClientNode},
@@ -404,6 +369,350 @@ openssl_server_hrr_multiple_tickets(Config) when is_list(Config) ->
process_flag(trap_exit, false),
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Server).
+
+openssl_server_early_data_basic() ->
+ [{doc,"Test early data (erlang client - openssl server)"}].
+openssl_server_early_data_basic(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ClientOpts2 = [{early_data, <<"SampleData">>}|ClientOpts1],
+
+ Server = ssl_test_lib:start_server(openssl, [{early_data, 16384}],
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply]}},
+ {from, self()}, {options, ClientOpts1}]),
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ %% Close previous connection as s_server can only handle one at a time
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true, no_reply, no_tickets,
+ {verify_early_data, accepted}]}},
+ {from, self()},
+ {options, ClientOpts2}]),
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Server).
+
+openssl_server_early_data_big() ->
+ [{doc,"Send more early data than the max_early_data_size (erlang client - openssl server)"}].
+openssl_server_early_data_big(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ClientOpts2 = [{early_data, <<"SampleData">>}|ClientOpts1],
+
+ Server = ssl_test_lib:start_server(openssl, [{early_data, 5}],
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply]}},
+ {from, self()}, {options, ClientOpts1}]),
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ %% Close previous connection as s_server can only handle one at a time
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ %% The tickets received cannot be used for sending more early data than the
+ %% max_early_data_size. They are filtered by the automatic ticket handling
+ %% mechanism and there will be no session resumption.
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply, no_tickets]}},
+ {from, self()},
+ {options, ClientOpts2}]),
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Server).
+
+openssl_server_early_data_manual() ->
+ [{doc,"Test sending early data - manual ticket handling (erlang client - openssl server)"}].
+openssl_server_early_data_manual(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, manual}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ClientOpts2 = [{early_data, <<"SampleData">>}|ClientOpts1],
+
+ Server = ssl_test_lib:start_server(openssl, [{early_data, 16384}],
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 1}]}},
+ {from, self()}, {options, ClientOpts1}]),
+
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+
+ ct:pal("Received tickets: ~p~n", [Tickets0]),
+
+ %% Close previous connection as s_server can only handle one at a time
+ ssl_test_lib:close(Client0),
+
+ %% Use tickets
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true, no_reply, no_tickets,
+ {verify_early_data, accepted}]}},
+ {from, self()},
+ {options, [{use_ticket, Tickets0}|ClientOpts2]}]),
+
+ process_flag(trap_exit, false),
+
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Server).
+
+openssl_server_early_data_manual_big() ->
+ [{doc,"Test sending more early data than the max_early_data_size - manual ticket handling "
+ "(erlang client - openssl server)"}].
+openssl_server_early_data_manual_big(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, manual}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ClientOpts2 = [{early_data, <<"SampleData">>}|ClientOpts1],
+
+ Server = ssl_test_lib:start_server(openssl, [{early_data, 5}],
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 1}]}},
+ {from, self()}, {options, ClientOpts1}]),
+
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+
+ ct:pal("Received tickets: ~p~n", [Tickets0]),
+
+ %% Close previous connection as s_server can only handle one at a time
+ ssl_test_lib:close(Client0),
+
+ %% Use tickets
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true, no_reply, no_tickets]}},
+ {from, self()},
+ {options, [{use_ticket, Tickets0}|ClientOpts2]}]),
+ ssl_test_lib:check_client_alert(Client1, illegal_parameter),
+ process_flag(trap_exit, false),
+
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Server).
+
+openssl_server_early_data_manual_2_tickets() ->
+ [{doc,"Test sending early data - manual ticket handling, 2 tickets (erlang client - openssl server)"}].
+openssl_server_early_data_manual_2_tickets(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, manual}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ClientOpts2 = [{early_data, <<"SampleData">>}|ClientOpts1],
+
+ Server = ssl_test_lib:start_server(openssl, [{early_data, 16384}],
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 2}]}},
+ {from, self()}, {options, ClientOpts1}]),
+
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+
+ ct:pal("Received tickets: ~p~n", [Tickets0]),
+
+ %% Close previous connection as s_server can only handle one at a time
+ ssl_test_lib:close(Client0),
+
+ %% Use tickets
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true, no_reply, no_tickets,
+ {verify_early_data, accepted}]}},
+ {from, self()},
+ {options, [{use_ticket, Tickets0}|ClientOpts2]}]),
+ process_flag(trap_exit, false),
+
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Server).
+
+openssl_server_early_data_manual_2_chacha_tickets() ->
+ [{doc,"Test sending early data - manual ticket handling, 2 tickets - chacha (erlang client - openssl server)"}].
+openssl_server_early_data_manual_2_chacha_tickets(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, manual}, {log_level, debug},
+ {ciphers, ["TLS_CHACHA20_POLY1305_SHA256"]},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ClientOpts2 = [{early_data, <<"SampleData">>}|ClientOpts1],
+
+ %% openssl s_server seems to select a cipher_suite that satisfies the requirements
+ %% for early_data.
+ Server = ssl_test_lib:start_server(openssl, [{early_data, 16384}],
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 2}]}},
+ {from, self()}, {options, ClientOpts1}]),
+
+ %% Receive 2 tickets that used Chacha20-Poly1305 and sha256
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+
+ ct:pal("Received tickets: ~p~n", [Tickets0]),
+
+ %% Close previous connection as s_server can only handle one at a time
+ ssl_test_lib:close(Client0),
+
+ %% Use tickets
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true, no_reply, no_tickets,
+ {verify_early_data, accepted}]}},
+ {from, self()},
+ {options, [{use_ticket, Tickets0}|ClientOpts2]}]),
+ process_flag(trap_exit, false),
+
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Server).
+
+openssl_client_early_data_basic() ->
+ [{doc,"Test early data (openssl client - erlang server)"}].
+openssl_client_early_data_basic(Config) when is_list(Config) ->
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_opts, Config),
+
+ {_, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+ TicketFile0 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket0"]),
+ TicketFile1 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket1"]),
+ RequestFile = filename:join([proplists:get_value(priv_dir, Config), "request"]),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+ %% Create request file to be used with early data
+ EarlyData = <<"HEAD / HTTP/1.1\nHost: \nConnection: close\n\n">>,
+ create_request(RequestFile, EarlyData),
+
+ %% Configure session tickets
+ ServerOpts = [{session_tickets, ServerTicketMode},
+ {early_data, enabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ Client0 = ssl_test_lib:start_client(openssl, [{port, Port0},
+ {options, ClientOpts},
+ {session_args, ["-sess_out", TicketFile0]}], Config),
+ ssl_test_lib:check_result(Server0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_server_early_data,
+ [wait_reply, EarlyData]}}},
+
+ %% %% Wait for session ticket
+ ct:sleep(100),
+ ssl_test_lib:close(Client0),
+
+ Client1 = ssl_test_lib:start_client(openssl, [{port, Port0},
+ {options, ClientOpts},
+ {session_args, ["-sess_in", TicketFile0,
+ "-sess_out", TicketFile1,
+ "-early_data", RequestFile]}],
+ Config),
+
+ ssl_test_lib:check_result(Server0, ok),
+ ssl_test_lib:close(Server0),
ssl_test_lib:close(Client1).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+
+create_request(File, EarlyData) ->
+ {ok, S} = file:open(File, [write]),
+ io:format(S, "~s", [binary_to_list(EarlyData)]),
+ file:close(S).
+
diff --git a/lib/ssl/test/openssl_sni_SUITE.erl b/lib/ssl/test/openssl_sni_SUITE.erl
index 446f62d950..2ab553d335 100644
--- a/lib/ssl/test/openssl_sni_SUITE.erl
+++ b/lib/ssl/test/openssl_sni_SUITE.erl
@@ -21,10 +21,29 @@
-module(openssl_sni_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([erlang_server_openssl_client_sni_match/1,
+ erlang_server_openssl_client_sni_match_fun/1,
+ erlang_server_openssl_client_sni_no_match/1,
+ erlang_server_openssl_client_sni_no_match_fun/1,
+ erlang_server_openssl_client_sni_no_header/1,
+ erlang_server_openssl_client_sni_no_header_fun/1
+ ]).
+
+%% Apply exports
+-export([send_and_hostname/1
+ ]).
-define(OPENSSL_QUIT, "Q\n").
-define(OPENSSL_RENEGOTIATE, "R\n").
@@ -118,30 +137,10 @@ end_per_suite(_Config) ->
ssl_test_lib:kill_openssl().
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:supports_ssl_tls_version(GroupName) of
- true ->
- case ssl_test_lib:check_sane_openssl_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, openssl_does_not_support_version}
- end;
- false ->
- {skip, openssl_does_not_support_version}
- end;
- _ ->
- Config
- end.
+ ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(_TestCase, Config) ->
ct:timetrap({seconds, 10}),
@@ -223,7 +222,9 @@ erlang_server_openssl_client_sni_test_sni_fun(Config, SNIHostname, ExpectedSNIHo
ssl_test_lib:check_result(Server, ExpectedSNIHostname),
ssl_test_lib:close_port(ClientPort),
ssl_test_lib:close(Server).
-
+%%--------------------------------------------------------------------
+%% Callback functions ------------------------------------------------
+%%--------------------------------------------------------------------
send_and_hostname(SSLSocket) ->
ssl:send(SSLSocket, "OK"),
case ssl:connection_information(SSLSocket, [sni_hostname]) of
@@ -232,13 +233,17 @@ send_and_hostname(SSLSocket) ->
{ok, [{sni_hostname, Hostname}]} ->
Hostname
end.
-
+%%--------------------------------------------------------------------
+%% Internal functions -----------------------------------------------
+%%--------------------------------------------------------------------
openssl_client_args(Version, Hostname, Port) ->
- ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)].
+ ssl_test_lib:maybe_force_ipv4(["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port),
+ ssl_test_lib:version_flag(Version)]).
openssl_client_args(Version, Hostname, Port, ServerName) ->
- ["s_client", "-connect", Hostname ++ ":" ++
- integer_to_list(Port), ssl_test_lib:version_flag(Version), "-servername", ServerName].
+ ssl_test_lib:maybe_force_ipv4(["s_client", "-connect", Hostname ++ ":" ++
+ integer_to_list(Port),
+ ssl_test_lib:version_flag(Version), "-servername", ServerName]).
check_openssl_sni_support(Config) ->
HelpText = os:cmd("openssl s_client --help"),
diff --git a/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl
index 443236f166..8c79d2e178 100644
--- a/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl
+++ b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl
@@ -22,12 +22,24 @@
-module(openssl_tls_1_3_version_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ end_per_suite/1,
+ end_per_group/2
+ ]).
+
+%% Test cases
+-export([tls12_client_tls13_server/1
+ ]).
+
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -74,19 +86,27 @@ end_per_suite(_Config) ->
ssl:stop(),
application:stop(crypto).
-init_per_group(openssl_client, Config0) ->
+init_per_group(GroupName, Config) ->
+ case ssl_test_lib:is_protocol_version(GroupName) of
+ true ->
+ ssl_test_lib:init_per_group_openssl(GroupName, Config);
+ false ->
+ do_init_per_group(GroupName, Config)
+ end.
+
+do_init_per_group(openssl_client, Config0) ->
Config = proplists:delete(server_type, proplists:delete(client_type, Config0)),
[{client_type, openssl}, {server_type, erlang} | Config];
-init_per_group(openssl_server, Config0) ->
+do_init_per_group(openssl_server, Config0) ->
Config = proplists:delete(server_type, proplists:delete(client_type, Config0)),
[{client_type, erlang}, {server_type, openssl} | Config];
-init_per_group(rsa, Config0) ->
+do_init_per_group(rsa, Config0) ->
Config = ssl_test_lib:make_rsa_cert(Config0),
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
[{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))];
-init_per_group(ecdsa, Config0) ->
+do_init_per_group(ecdsa, Config0) ->
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso
(lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of
@@ -98,30 +118,10 @@ init_per_group(ecdsa, Config0) ->
lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))];
false ->
{skip, "Missing EC crypto support"}
- end;
-init_per_group(GroupName, Config) ->
- ssl_test_lib:clean_tls_version(Config),
- case ssl_test_lib:is_tls_version(GroupName) andalso
- ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- _ ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl:start(),
- Config;
- false ->
- {skip, "Missing crypto support"}
- end
end.
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
diff --git a/lib/ssl/test/property_test/ssl_eqc_chain.erl b/lib/ssl/test/property_test/ssl_eqc_chain.erl
new file mode 100644
index 0000000000..e108591776
--- /dev/null
+++ b/lib/ssl/test/property_test/ssl_eqc_chain.erl
@@ -0,0 +1,443 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2020. 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(ssl_eqc_chain).
+
+%%-export([prop_tls_orded_path/1]).
+-compile(export_all).
+
+-proptest(eqc).
+-proptest([triq,proper]).
+
+-ifndef(EQC).
+-ifndef(PROPER).
+-ifndef(TRIQ).
+-define(EQC,true).
+-endif.
+-endif.
+-endif.
+
+-ifdef(EQC).
+-include_lib("eqc/include/eqc.hrl").
+-define(MOD_eqc,eqc).
+
+-else.
+-ifdef(PROPER).
+-include_lib("proper/include/proper.hrl").
+-define(MOD_eqc,proper).
+
+-else.
+-ifdef(TRIQ).
+-define(MOD_eqc,triq).
+-include_lib("triq/include/triq.hrl").
+
+-endif.
+-endif.
+-endif.
+
+-include_lib("public_key/include/public_key.hrl").
+%%--------------------------------------------------------------------
+%% Properties --------------------------------------------------------
+%%--------------------------------------------------------------------
+prop_tls_unordered_path(PrivDir) ->
+ ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_options(Version, PrivDir)),
+ try
+ [TLSVersion] = proplists:get_value(versions, ClientOptions),
+ ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
+ _ ->
+ true
+ catch
+ _:_ ->
+ false
+ end
+ ).
+
+prop_tls_extraneous_path(PrivDir) ->
+ ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extraneous_options(Version, PrivDir)),
+ try
+ [TLSVersion] = proplists:get_value(versions, ClientOptions),
+ ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
+ _ ->
+ true
+ catch
+ _:_ ->
+ false
+ end
+ ).
+
+prop_tls_extraneous_paths() ->
+ ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extra_extraneous_options(Version)),
+ try
+ [TLSVersion] = proplists:get_value(versions, ClientOptions),
+ ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
+ _ ->
+ true
+ catch
+ _:_ ->
+ false
+ end
+ ).
+
+prop_tls_extraneous_and_unordered_path() ->
+ ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_extraneous_options(Version)),
+ try
+ [TLSVersion] = proplists:get_value(versions, ClientOptions),
+ ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
+ _ ->
+ true
+ catch
+ _:_ ->
+ false
+ end
+ ).
+
+prop_client_cert_auth() ->
+ ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), client_cert_auth_opts(Version)),
+ try
+ [TLSVersion] = proplists:get_value(versions, ClientOptions),
+ ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
+ _ ->
+ true
+ catch
+ _:_ ->
+ false
+ end
+ ).
+
+%%--------------------------------------------------------------------
+%% Chain Generators -----------------------------------------------
+%%--------------------------------------------------------------------
+tls_version() ->
+ Versions = [Version || Version <- ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', 'tlsv1', 'dtlsv1.2', 'dtlsv1'],
+ ssl_test_lib:sufficient_crypto_support(Version)
+ ],
+ oneof(Versions).
+
+key_alg(Version) when Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
+ Version == 'dtlsv1.2'->
+ oneof([rsa, ecdsa]);
+key_alg(_) ->
+ oneof([rsa]).
+
+server_options('tlsv1.3') ->
+ [{verify, verify_peer},
+ {fail_if_no_peer_cert, true},
+ {reuseaddr, true}];
+server_options(_) ->
+ [{verify, verify_peer},
+ {fail_if_no_peer_cert, true},
+ {reuse_sessions, false},
+ {reuseaddr, true}].
+
+client_options(_) ->
+ [{verify, verify_peer}].
+
+unordered_options(Version, PrivDir) ->
+ oneof([der_unordered_options(Version), pem_unordered_options(Version, PrivDir)]).
+
+der_unordered_options(Version) ->
+ ?LET(Alg, key_alg(Version), unordered_der_cert_chain_opts(Version, Alg)).
+
+pem_unordered_options(Version, PrivDir) ->
+ ?LET(Alg, key_alg(Version), unordered_pem_cert_chain_opts(Version, Alg, PrivDir)).
+
+unordered_der_cert_chain_opts(Version, Alg) ->
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => #{root => root_key(Alg),
+ intermediates => intermediates(Alg, 4),
+ peer => peer_key(Alg)},
+ client_chain => #{root => root_key(Alg),
+ intermediates => intermediates(Alg, 4),
+ peer => peer_key(Alg)}}),
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ClientConf)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ServerConf)]}.
+
+unordered_pem_cert_chain_opts(Version, Alg, PrivDir) ->
+ Index = integer_to_list(erlang:unique_integer()),
+ DerConfig = public_key:pkix_test_data(#{server_chain => #{root => root_key(Alg),
+ intermediates => intermediates(Alg, 4),
+ peer => peer_key(Alg)},
+ client_chain => #{root => root_key(Alg),
+ intermediates => intermediates(Alg, 4),
+ peer => peer_key(Alg)}}),
+
+ ClientBase = filename:join(PrivDir, "client_prop_test" ++ Index),
+ SeverBase = filename:join(PrivDir, "server_prop_test" ++ Index),
+ PemConfig = x509_test:gen_pem_config_files(DerConfig, ClientBase, SeverBase),
+ ClientConf = proplists:get_value(client_config, PemConfig),
+ ServerConf = proplists:get_value(server_config, PemConfig),
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ClientConf)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ServerConf)]}.
+
+unordered_der_conf(Config) ->
+ Cert = proplists:get_value(cert, Config),
+ {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, proplists:get_value(cacerts, Config)}),
+ {ok, _, [Cert | Path]} = ssl_certificate:certificate_chain(Cert, ets:new(foo, []), ExtractedCAs, []),
+ [{cert, [Cert | lists:reverse(Path)]}| proplists:delete(cert, Config)].
+
+unordered_pem_conf(Config) ->
+ CertFile = proplists:get_value(certfile, Config),
+ CACertFile = proplists:get_value(cacertfile, Config),
+ [{_, Cert, _}| _] = ssl_test_lib:pem_to_der(CertFile),
+ PemCAs = ssl_test_lib:pem_to_der(CACertFile),
+ DerList = [DerCert || {'Certificate', DerCert, not_encrypted} <- PemCAs],
+ {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, DerList}),
+ {ok, _, [Cert | Path]} = ssl_certificate:certificate_chain(Cert, ets:new(foo, []), ExtractedCAs, []),
+ Unorded = lists:reverse(Path),
+ UnordedPemEntries = [{'Certificate', DerCert, not_encrypted} || DerCert <- Unorded],
+ PEM = public_key:pem_encode([{'Certificate', Cert, not_encrypted} |UnordedPemEntries]),
+ file:write_file(CertFile, PEM),
+ Config.
+
+extraneous_options(Version, PrivDir) ->
+ oneof([der_extraneous_options(Version),
+ pem_extraneous_options(Version, PrivDir)
+ ]).
+extra_extraneous_options(Version) ->
+ oneof([extra_der_extraneous_options(Version)]).
+
+der_extraneous_options(Version) ->
+ ?LET(Alg, key_alg(Version), extraneous_der_cert_chain_opts(Version, Alg)).
+
+pem_extraneous_options(Version, PrivDir) ->
+ ?LET(Alg, key_alg(Version), extraneous_pem_cert_chain_opts(Version, Alg, PrivDir)).
+
+extra_der_extraneous_options(Version) ->
+ ?LET(Alg, key_alg(Version), extra_extraneous_der_cert_chain_opts(Version, Alg)).
+
+unordered_extraneous_options(Version) ->
+ oneof([der_extraneous_and_unorder_options(Version)]).
+
+der_extraneous_and_unorder_options(Version) ->
+ ?LET(Alg, key_alg(Version), der_extraneous_and_unorder_chain(Version, Alg)).
+
+client_cert_auth_opts(Version) ->
+ ?LET({SAlg, CAlg}, {key_alg(Version), key_alg(Version)}, der_cert_chains(Version, CAlg,SAlg)).
+
+extraneous_der_cert_chain_opts(Version, Alg) ->
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+
+ #{server_config := ServerConf0,
+ client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => intermediates(Alg, 1),
+ peer => []},
+ client_chain => #{root => CRoot,
+ intermediates => intermediates(Alg, 1),
+ peer => []}}),
+
+ {ClientChain, ClientRoot} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 1),
+ {ServerChain, ServerRoot} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 1),
+
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot, [OrgSRoot], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot, [OrgCRoot], ServerConf0)]}.
+
+extraneous_pem_cert_chain_opts(Version, Alg, PrivDir) ->
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+
+ #{server_config := ServerConf0,
+ client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => intermediates(Alg, 1),
+ peer => []},
+ client_chain => #{root => CRoot,
+ intermediates => intermediates(Alg, 1),
+ peer => []}}),
+
+ {ClientChain, ClientRoot} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 1),
+ {ServerChain, ServerRoot} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 1),
+
+ %% Only use files in final step
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_pem_conf(ClientChain, ServerRoot, OrgSRoot, ClientConf0, PrivDir)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_pem_conf(ServerChain, ClientRoot, OrgCRoot, ServerConf0, PrivDir)]}.
+
+extra_extraneous_der_cert_chain_opts(Version, Alg) ->
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+
+ #{server_config := ServerConf0,
+ client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => intermediates(Alg, 3),
+ peer => []},
+ client_chain => #{root => CRoot,
+ intermediates => intermediates(Alg, 3),
+ peer => []}}),
+
+
+ {ClientChain0, ClientRoot0} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
+ {ServerChain0, ServerRoot0} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
+
+ {ClientChain1, ClientRoot1} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
+ {ServerChain1, ServerRoot1} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
+
+ {ClientChain, ServerChain} = create_extraneous_chains(ClientChain0, ClientChain1,
+ ServerChain0, ServerChain1),
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
+
+
+der_extraneous_and_unorder_chain(Version, Alg) ->
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+
+ #{server_config := ServerConf0,
+ client_config := ClientConf0} =
+ public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => intermediates(Alg, 3),
+ peer => []},
+ client_chain => #{root => CRoot,
+ intermediates => intermediates(Alg, 3),
+ peer => []}}),
+
+ {ClientChain0, ClientRoot0} = chain_and_root(ClientConf0),
+ {ServerChain0, ServerRoot0} = chain_and_root(ServerConf0),
+
+ {ClientChain1, ClientRoot1} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
+ {ServerChain1, ServerRoot1} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
+
+ {ClientChain, ServerChain} = create_extraneous_and_unorded(ClientChain0, ClientChain1,
+ ServerChain0, ServerChain1),
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
+
+der_cert_chains(Version, CAlg, SAlg) ->
+ SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(SAlg)),
+ CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(CAlg)),
+
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
+ intermediates => intermediates(SAlg, 1),
+ peer => peer_key(SAlg)},
+ client_chain => #{root => CRoot,
+ intermediates => intermediates(CAlg, 1),
+ peer => peer_key(CAlg)}}),
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} | ClientConf],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} | ServerConf]}.
+
+chain_and_root(Config) ->
+ OwnCert = proplists:get_value(cert, Config),
+ {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, proplists:get_value(cacerts, Config)}),
+ {ok, Root, Chain} = ssl_certificate:certificate_chain(OwnCert, ets:new(foo, []), ExtractedCAs, []),
+ {Chain, Root}.
+
+extraneous_chain_and_root(Config, Name, 1) ->
+ #{cert := NewRoot, key := Key} = public_key:pkix_test_root_cert(Name, []),
+ {[OwnCert, CA0, OldRoot], OldRoot} = chain_and_root(Config),
+ CA1 = new_intermediat(CA0, Key),
+ {[OwnCert, CA1, CA0], NewRoot};
+extraneous_chain_and_root(Config, Name, 2) ->
+ #{cert := NewRoot, key := Key} = public_key:pkix_test_root_cert(Name, []),
+ {[OwnCert, CA0, CA1, CA2, OldRoot], OldRoot} = chain_and_root(Config),
+ CA3 = new_intermediat(CA2, Key),
+ {[OwnCert, CA0, CA1, CA2, CA3], NewRoot}.
+
+extraneous_der_conf(Chain, NewRoot, OrgRoots,Config0) ->
+ CaCerts = proplists:get_value(cacerts, Config0),
+ Config1 = [{cert, Chain} | proplists:delete(cert, Config0)],
+ [{cacerts, [NewRoot | CaCerts -- OrgRoots]} | proplists:delete(cacerts, Config1)].
+
+extraneous_pem_conf(Chain, NewRoot, OldRoot, Config0, PrivDir) ->
+ Int = erlang:unique_integer(),
+ FileName = filename:join(PrivDir, "prop_test" ++ integer_to_list(Int)),
+ CaCerts = proplists:get_value(cacerts, Config0),
+ NewCas = [NewRoot | CaCerts -- [OldRoot]],
+ Entries = [{'Certificate', DerCert, not_encrypted} || DerCert <- Chain],
+ PemBin = public_key:pem_encode(Entries),
+ file:write_file(FileName, PemBin),
+ Config1 = [{cacerts, NewCas} | proplists:delete(cacerts, Config0)],
+ [{certfile, FileName} | proplists:delete(cert, Config1)].
+
+protocol('dtlsv1.2') ->
+ {protocol, dtls};
+protocol('dtlsv1') ->
+ {protocol, dtls};
+protocol(_) ->
+ {protocol,tls}.
+
+create_extraneous_chains([Client, _CCA0, _CCA1, CCA2, _CCA3], [Client, OCCA0, OCCA1, OCCA2, OCROOT],
+ [Server, _SCA0, _SCA1, SCA2, _SROOT], [Server, OSCA0, OSCA1, OSCA2, OSROOT]) ->
+ {[Client, OCCA0, OCCA1, CCA2, OCCA2, OCROOT], [Server, OSCA0, OSCA1, SCA2, OSCA2, OSROOT]}.
+create_extraneous_and_unorded([Client, _CCA0, _CCA1, CCA2, _CCA3], [Client, OCCA0, OCCA1, OCCA2, OCROOT],
+ [Server, _SCA0, _SCA1, SCA2, _SROOT], [Server, OSCA0, OSCA1, OSCA2, OSROOT]) ->
+ {[Client, OCCA0, CCA2, OCCA2, OCROOT, OCCA1], [Server, OSCA0, SCA2, OSCA2, OSROOT, OSCA1]}.
+
+root_key(ecdsa) ->
+ []; %% Just generate one
+root_key(rsa) ->
+ %% As rsa keygen is not guaranteed to be fast
+ [{key, ssl_test_lib:hardcode_rsa_key(6)}].
+
+peer_key(ecdsa) ->
+ []; %% Just generate one
+peer_key(rsa) ->
+ %% As rsa keygen is not guaranteed to be fast
+ [{key, ssl_test_lib:hardcode_rsa_key(6)}].
+
+intermediates(ecdsa, N) ->
+ lists:duplicate(N, []);
+intermediates(rsa, N) when N =< 4 ->
+ Default = lists:duplicate(N, []),
+ %% As rsa keygen is not guaranteed to be fast
+ hardcode_rsa_keys(Default, N, []).
+
+hardcode_rsa_keys([], 0, Acc) ->
+ Acc;
+hardcode_rsa_keys([Head | Tail], N, Acc) ->
+ hardcode_rsa_keys(Tail, N-1, [[{key, ssl_test_lib:hardcode_rsa_key(N)} | Head] | Acc]).
+
+new_intermediat(CA0, Key) ->
+ OTPCert = public_key:pkix_decode_cert(CA0, otp),
+ TBSCert = OTPCert#'OTPCertificate'.tbsCertificate,
+ Num = TBSCert#'OTPTBSCertificate'.serialNumber,
+ public_key:pkix_sign(TBSCert#'OTPTBSCertificate'{serialNumber = Num+1}, Key).
+
+
diff --git a/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl
index f225065ba6..cf6ed755f7 100644
--- a/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl
+++ b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl
@@ -55,14 +55,13 @@
-define('TLS_v1.2', 'tlsv1.2').
-define('TLS_v1.1', 'tlsv1.1').
-define('TLS_v1', 'tlsv1').
--define('SSL_v3', 'sslv3').
%%--------------------------------------------------------------------
%% Properties --------------------------------------------------------
%%--------------------------------------------------------------------
prop_tls_cipher_suite_rfc_name() ->
- ?FORALL({CipherSuite, TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}),
+ ?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}),
case ssl:str_to_suite(ssl:suite_to_str(CipherSuite)) of
CipherSuite ->
true;
@@ -72,8 +71,23 @@ prop_tls_cipher_suite_rfc_name() ->
).
prop_tls_cipher_suite_openssl_name() ->
- ?FORALL({CipherSuite, TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}),
+ ?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}),
case ssl:str_to_suite(ssl:suite_to_openssl_str(CipherSuite)) of
+ CipherSuite ->
+ case ssl:suite_to_openssl_str(CipherSuite) of
+ "TLS_" ++ _ ->
+ true;
+ OpensslName ->
+ lists:member(OpensslName, openssl_legacy_names())
+ end;
+ _ ->
+ false
+ end
+ ).
+
+prop_tls_anon_cipher_suite_rfc_name() ->
+ ?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, pre_tls_1_3_version(), {anon_cipher_suite(Version), Version}),
+ case ssl:str_to_suite(ssl:suite_to_str(CipherSuite)) of
CipherSuite ->
true;
_ ->
@@ -81,16 +95,180 @@ prop_tls_cipher_suite_openssl_name() ->
end
).
+prop_tls_anon_cipher_suite_openssl_name() ->
+ ?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, pre_tls_1_3_version(), {anon_cipher_suite(Version), Version}),
+ case ssl:str_to_suite(ssl:suite_to_openssl_str(CipherSuite)) of
+ CipherSuite ->
+ lists:member(ssl:suite_to_openssl_str(CipherSuite), openssl_legacy_names());
+ _ ->
+ false
+ end
+ ).
%%--------------------------------------------------------------------
%% Generators -----------------------------------------------
%%--------------------------------------------------------------------
tls_version() ->
- oneof([?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1', ?'SSL_v3']).
+ oneof([?'TLS_v1.3', ?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1']).
+
+pre_tls_1_3_version() ->
+ oneof([?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1']).
cipher_suite(Version) ->
oneof(cipher_suites(Version)).
cipher_suites(Version) ->
- ssl:cipher_suites(all, Version).
+ ssl:cipher_suites(default, Version).
+
+anon_cipher_suite(Version) ->
+ oneof(ssl:cipher_suites(anonymous, Version)).
+
+openssl_legacy_names() ->
+ %% Only include names that we support
+ [
+ %% Legacy with RSA keyexchange
+ "AES128-SHA",
+ "AES256-SHA",
+ "AES128-SHA256",
+ "AES256-SHA256",
+ "AES256-GCM-SHA256",
+ "AES256-GCM-SHA384",
+ "DES-CBC-SHA",
+ "DES-CBC3-SHA",
+ "RC4-MD5",
+ "RC4-SHA",
+
+ %% DH based
+ "DH-RSA-AES128-SHA",
+ "DH-RSA-AES256-SHA",
+ "DH-RSA-AES128-SHA256",
+ "DH-RSA-AES256-SHA256",
+ "DH-DSS-AES128-SHA",
+ "DH-DSS-AES256-SHA",
+ "DH-DSS-AES128-SHA256",
+ "DH-DSS-AES256-SHA256",
+ "EDH-RSA-DES-CBC-SHA",
+ "EDH-RSA-DES-CBC3-SHA",
+ "DHE-RSA-AES128-SHA",
+ "DHE-RSA-AES256-SHA",
+ "DHE-RSA-AES128-SHA256",
+ "DHE-RSA-AES256-SHA256",
+ "DHE-RSA-AES256-SHA384",
+ "DHE-RSA-AES128-GCM-SHA256",
+ "DHE-RSA-AES256-GCM-SHA384",
+ "DHE-RSA-AES128-CCM-SHA256",
+ "DHE-RSA-AES256-CCM-SHA384",
+ "DHE-RSA-AES128-CCM8-SHA256",
+ "DHE-RSA-AES256-CCM8-SHA384",
+ "DHE-RSA-CHACHA20-POLY1305",
+ "EDH-DSS-DES-CBC-SHA",
+ "EDH-DSS-DES-CBC3-SHA",
+ "DHE-DSS-AES128-SHA",
+ "DHE-DSS-AES256-SHA",
+ "DHE-DSS-AES128-SHA256",
+ "DHE-DSS-AES256-SHA256",
+ "DHE-DSS-AES256-SHA384",
+ "DHE-DSS-AES128-GCM-SHA256",
+ "DHE-DSS-AES256-GCM-SHA384",
+ "DHE-DSS-RC4-SHA",
+ "ADH-AES128-SHA256",
+ "ADH-AES256-SHA256",
+ "ADH-AES128-CBC-SHA256",
+ "ADH-AES128-GCM-SHA256",
+ "ADH-AES256-GCM-SHA384",
+ "ADH-RC4-MD5",
+ "ADH-DES-CBC-SHA",
+ "ADH-DES-CBC3-SHA",
+ "ADH-AES256-SHA",
+ "ADH-AES256-SHA256",
+
+ %% ECDH based
+ "ECDH-ECDSA-AES128-SHA",
+ "ECDH-ECDSA-AES256-SHA",
+ "ECDH-ECDSA-AES128-SHA256",
+ "ECDH-ECDSA-AES256-SHA384",
+ "ECDH-ECDSA-AES128-GCM-SHA256",
+ "ECDH-ECDSA-AES256-GCM-SHA384",
+ "ECDHE-ECDSA-AES128-CCM",
+ "ECDHE-ECDSA-AES128-CCM8",
+ "ECDHE-ECDSA-AES256-CCM",
+ "ECDHE-ECDSA-AES256-CCM8",
+ "ECDH-ECDSA-CHACHA20-POLY1305",
+ "ECDHE-ECDSA-AES128-SHA",
+ "ECDHE-ECDSA-AES256-SHA",
+ "ECDHE-ECDSA-AES128-SHA256",
+ "ECDHE-ECDSA-AES256-SHA384",
+ "ECDHE-ECDSA-AES128-GCM-SHA256",
+ "ECDHE-ECDSA-AES256-GCM-SHA384",
+ "ECDHE-ECDSA-CHACHA20-POLY1305",
+ "ECDH-RSA-AES128-SHA",
+ "ECDH-RSA-AES256-SHA",
+ "ECDH-RSA-AES128-SHA256",
+ "ECDH-RSA-AES256-SHA384",
+ "ECDH-RSA-AES128-GCM-SHA256",
+ "ECDH-RSA-AES256-GCM-SHA384",
+ "ECDHE-RSA-AES128-SHA",
+ "ECDHE-RSA-AES256-SHA",
+ "ECDHE-RSA-AES128-SHA256",
+ "ECDHE-RSA-AES256-SHA384",
+ "ECDHE-RSA-AES128-GCM-SHA256",
+ "ECDHE-RSA-AES128-GCM-SHA384",
+ "ECDHE-RSA-AES256-GCM-SHA256",
+ "ECDHE-RSA-AES256-GCM-SHA384",
+ "ECDHE-RSA-CHACHA20-POLY1305",
+ "ECDHE-PSK-RC4-SHA",
+ "ECDHE-PSK-3DES-EDE-CBC-SHA",
+ "ECDHE-PSK-AES128-CBC-SHA",
+ "ECDHE-PSK-AES128-CBC-SHA256",
+ "ECDHE-PSK-AES256-CBC-SHA384",
+ "ECDHE-PSK-AES128-GCM-SHA256",
+ "ECDHE-PSK-AES256-GCM-SHA384",
+ "ECDHE-PSK-AES128-CCM-SHA256",
+ "ECDHE-PSK-AES128-CCM8-SHA256",
+ "ECDHE-PSK-CHACHA20-POLY1305",
+ "AECDH-DES-CBC3-SHA",
+ "AECDH-AES128-SHA",
+ "AECDH-AES256-SHA",
+ %% PSK based
+ "DHE-PSK-NULL-SHA",
+ "DHE-PSK-RC4-SHA",
+ "DHE-PSK-3DES-EDE-CBC-SHA",
+ "DHE-PSK-AES128-CBC-SHA",
+ "DHE-PSK-AES256-CBC-SHA",
+ "DHE-PSK-AES128-CBC-SHA256",
+ "DHE-PSK-AES256-CBC-SHA384",
+ "DHE-PSK-AES128-GCM-SHA256",
+ "DHE-PSK-AES256-GCM-SHA384",
+ "DHE-PSK-AES128-CCM",
+ "DHE-PSK-AES128-CCM8",
+ "DHE-PSK-AES256-CCM",
+ "DHE-PSK-AES256-CCM8",
+ "DHE-PSK-AES128-CCM-SHA256",
+ "DHE-PSK-AES128-CCM8-SHA256",
+ "DHE-PSK-CHACHA20-POLY1305",
+ "PSK-NULL-SHA",
+ "PSK-RC4-SHA",
+ "PSK-3DES-EDE-CBC-SHA",
+ "PSK-AES128-CBC-SHA",
+ "PSK-AES256-CBC-SHA",
+ "PSK-AES128-CBC-SHA256",
+ "PSK-AES256-CBC-SHA256",
+ "PSK-AES128-CCM",
+ "PSK-AES128-CCM8",
+ "PSK-AES256-CCM",
+ "PSK-AES256-CCM8",
+ "PSK-AES128-GCM-SHA256",
+ "PSK-AES256-CBC-SHA384",
+ "PSK-AES256-GCM-SHA384",
+ "PSK-CHACHA20-POLY1305",
+ "RSA-PSK-NULL-SHA",
+ "RSA-PSK-CHACHA20-POLY1305",
+
+ %% SRP based
+ "SRP-3DES-EDE-CBC-SHA",
+ "SRP-RSA-3DES-EDE-CBC-SHA",
+ "SRP-DSS-3DES-EDE-CBC-SHA",
+ "SRP-AES-128-CBC-SHA",
+ "SRP-AES-256-CBC-SHA"
+ ].
diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
index 2cdac0326d..73d7688288 100644
--- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl
+++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
@@ -62,7 +62,6 @@
-define('TLS_v1.2', {3,3}).
-define('TLS_v1.1', {3,2}).
-define('TLS_v1', {3,1}).
--define('SSL_v3', {3,0}).
%%--------------------------------------------------------------------
%% Properties --------------------------------------------------------
@@ -132,14 +131,6 @@ client_hello(Version) ->
compression_methods = compressions(Version),
random = client_random(Version),
extensions = client_hello_extensions(Version)
- };
-client_hello(?'SSL_v3' = Version) ->
- #client_hello{session_id = session_id(),
- client_version = Version,
- cipher_suites = cipher_suites(Version),
- compression_methods = compressions(Version),
- random = client_random(Version),
- extensions = ssl_handshake:empty_extensions(Version, client_hello)
}.
server_hello(?'TLS_v1.3' = Version) ->
@@ -150,14 +141,6 @@ server_hello(?'TLS_v1.3' = Version) ->
compression_method = compression(Version),
extensions = server_hello_extensions(Version)
};
-server_hello(?'SSL_v3' = Version) ->
- #server_hello{server_version = Version,
- session_id = session_id(),
- random = server_random(Version),
- cipher_suite = cipher_suite(Version),
- compression_method = compression(Version),
- extensions = ssl_handshake:empty_extensions(Version, server_hello)
- };
server_hello(Version) ->
#server_hello{server_version = Version,
session_id = session_id(),
@@ -214,7 +197,7 @@ key_update() ->
%%--------------------------------------------------------------------
tls_version() ->
- oneof([?'TLS_v1.3', ?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1', ?'SSL_v3']).
+ oneof([?'TLS_v1.3', ?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1']).
cipher_suite(Version) ->
oneof(cipher_suites(Version)).
@@ -382,8 +365,6 @@ extensions(?'TLS_v1.3' = Version, MsgType = client_hello) ->
%% post_handshake_auth => PostHandshakeAuth,
signature_algs_cert => SignatureAlgorithmsCert
}));
-extensions(?'SSL_v3', client_hello) ->
- #{};
extensions(Version, client_hello) ->
?LET({
SNI,
diff --git a/lib/ssl/test/ssl_ECC.erl b/lib/ssl/test/ssl_ECC.erl
index 36d949f74b..73dafcd1fc 100644
--- a/lib/ssl/test/ssl_ECC.erl
+++ b/lib/ssl/test/ssl_ECC.erl
@@ -23,14 +23,27 @@
-module(ssl_ECC).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+-export([client_ecdh_rsa_server_ecdh_rsa/1,
+ client_ecdhe_rsa_server_ecdh_rsa/1,
+ client_ecdhe_ecdsa_server_ecdh_rsa/1,
+ client_ecdh_rsa_server_ecdhe_rsa/1,
+ client_ecdhe_rsa_server_ecdhe_rsa/1,
+ client_ecdhe_ecdsa_server_ecdhe_rsa/1,
+ client_ecdh_ecdsa_server_ecdh_ecdsa/1,
+ client_ecdhe_rsa_server_ecdh_ecdsa/1,
+ client_ecdhe_ecdsa_server_ecdh_ecdsa/1,
+ client_ecdh_rsa_server_ecdhe_ecdsa/1,
+ client_ecdh_ecdsa_server_ecdhe_ecdsa/1,
+ client_ecdhe_ecdsa_server_ecdhe_ecdsa/1
+ ]).
+
%% Test diffrent certificate chain types, note that it is the servers
-%% chain that affect what cipher suit that will be choosen
+%% chain that affect what cipher suite that will be choosen
%% ECDH_RSA
client_ecdh_rsa_server_ecdh_rsa(Config) when is_list(Config) ->
@@ -164,6 +177,10 @@ client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) ->
ssl_test_lib:ssl_options(SOpts, Config),
[{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]).
+%%--------------------------------------------------------------------
+%% Internal functions -----------------------------------------------
+%%--------------------------------------------------------------------
+
all_rsa_suites(Config) ->
Version = proplists:get_value(tls_version, Config),
All = ssl:cipher_suites(all, Version),
diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl
index 215a1ab9f1..c628f3b6d9 100644
--- a/lib/ssl/test/ssl_ECC_SUITE.erl
+++ b/lib/ssl/test/ssl_ECC_SUITE.erl
@@ -22,12 +22,42 @@
-module(ssl_ECC_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([ecc_default_order/1,
+ ecc_default_order_custom_curves/1,
+ ecc_client_order/1,
+ ecc_client_order_custom_curves/1,
+ ecc_unknown_curve/1,
+ client_ecdh_rsa_server_ecdhe_ecdsa_server_custom/1,
+ client_ecdh_rsa_server_ecdhe_rsa_server_custom/1,
+ client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom/1,
+ client_ecdhe_rsa_server_ecdhe_rsa_server_custom/1,
+ client_ecdhe_rsa_server_ecdh_rsa_server_custom/1,
+ client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom/1,
+ client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom/1,
+ client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom/1,
+ client_ecdhe_rsa_server_ecdhe_ecdsa_client_custom/1,
+ client_ecdsa_server_ecdsa_with_raw_key/1,
+ mix_sign/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -94,29 +124,24 @@ end_per_suite(_Config) ->
%%--------------------------------------------------------------------
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- [{tls_version, GroupName},
- {server_type, erlang},
- {client_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)];
- _ ->
+ case ssl_test_lib:is_protocol_version(GroupName) of
+ true ->
+ ct:log("Ciphers: ~p~n ", [ssl:cipher_suites(default, GroupName)]),
+ ssl_test_lib:init_per_group(GroupName,
+ [{client_type, erlang},
+ {server_type, erlang},
+ {version, GroupName} | Config]);
+ false ->
Config
end.
-end_per_group(GroupName, Config0) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- Config = ssl_test_lib:clean_tls_version(Config0),
- proplists:delete(tls_version, Config);
- false ->
- Config0
- end.
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
%%--------------------------------------------------------------------
init_per_testcase(TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
- ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]),
end_per_testcase(TestCase, Config),
ssl:start(),
ct:timetrap({seconds, 15}),
@@ -130,7 +155,7 @@ end_per_testcase(_TestCase, Config) ->
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
%% Test diffrent certificate chain types, note that it is the servers
-%% chain that affect what cipher suit that will be choosen
+%% chain that affect what cipher suite that will be choosen
client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
diff --git a/lib/ssl/test/ssl_alert_SUITE.erl b/lib/ssl/test/ssl_alert_SUITE.erl
index 6b02df759f..3b32d3ece1 100644
--- a/lib/ssl/test/ssl_alert_SUITE.erl
+++ b/lib/ssl/test/ssl_alert_SUITE.erl
@@ -20,14 +20,28 @@
-module(ssl_alert_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("ssl/src/ssl_alert.hrl").
+%% Common test
+-export([all/0,
+ init_per_testcase/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([alerts/0,
+ alerts/1,
+ alert_details/0,
+ alert_details/1,
+ alert_details_not_too_big/0,
+ alert_details_not_too_big/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/ssl_alpn_SUITE.erl b/lib/ssl/test/ssl_alpn_SUITE.erl
index 4e10c071fd..e3c10caa43 100644
--- a/lib/ssl/test/ssl_alpn_SUITE.erl
+++ b/lib/ssl/test/ssl_alpn_SUITE.erl
@@ -21,10 +21,48 @@
%%
-module(ssl_alpn_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([empty_protocols_are_not_allowed/1,
+ protocols_must_be_a_binary_list/1,
+ empty_client/1,
+ empty_server/1,
+ empty_client_empty_server/1,
+ no_matching_protocol/1,
+ client_alpn_and_server_alpn/1,
+ client_alpn_and_server_no_support/1,
+ client_no_support_and_server_alpn/1,
+ client_renegotiate/1,
+ session_reused/1,
+ client_alpn_npn_and_server_alpn_npn/1,
+ client_alpn_and_server_alpn_npn/1,
+ client_alpn_npn_and_server_alpn/1
+ ]).
+
+%% Apply export
+
+-export([assert_alpn/2,
+ assert_alpn_and_renegotiate_and_send_data/3,
+ ssl_send_and_assert_alpn/3,
+ ssl_receive_and_assert_alpn/3,
+ ssl_send/2,
+ ssl_receive/2,
+ connection_info_result/1
+ ]).
+
-define(SLEEP, 500).
%%--------------------------------------------------------------------
@@ -37,7 +75,6 @@ all() ->
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
].
@@ -48,7 +85,6 @@ groups() ->
{'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist()},
{'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist()},
{'tlsv1', [], alpn_tests() ++ alpn_npn_coexist()},
- {'sslv3', [], alpn_not_supported()},
{'dtlsv1.2', [], alpn_tests() ++ alpn_npn_coexist()},
{'dtlsv1', [], alpn_tests() ++ alpn_npn_coexist()}
].
@@ -63,7 +99,6 @@ alpn_tests() ->
client_alpn_and_server_alpn,
client_alpn_and_server_no_support,
client_no_support_and_server_alpn,
- client_alpn_npn_and_server_alpn,
client_renegotiate,
session_reused
].
@@ -71,13 +106,10 @@ alpn_tests() ->
alpn_npn_coexist() ->
[
client_alpn_npn_and_server_alpn_npn,
- client_alpn_and_server_alpn_npn
+ client_alpn_and_server_alpn_npn,
+ client_alpn_npn_and_server_alpn
].
-alpn_not_supported() ->
- [alpn_not_supported_client,
- alpn_not_supported_server
- ].
init_per_suite(Config0) ->
catch crypto:stop(),
@@ -96,26 +128,10 @@ end_per_suite(_Config) ->
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(_TestCase, Config) ->
@@ -274,76 +290,11 @@ session_reused(Config) when is_list(Config)->
ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0,
ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
-%--------------------------------------------------------------------------------
-
-alpn_not_supported_client(Config) when is_list(Config) ->
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- PrefProtocols = {client_preferred_next_protocols,
- {client, [<<"http/1.0">>], <<"http/1.1">>}},
- ClientOpts = [PrefProtocols] ++ ClientOpts0,
- {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Client = ssl_test_lib:start_client_error([{node, ClientNode},
- {port, 8888}, {host, Hostname},
- {from, self()}, {options, ClientOpts}]),
-
- ssl_test_lib:check_result(Client, {error,
- {options,
- {not_supported_in_sslv3, PrefProtocols}}}).
-
-%--------------------------------------------------------------------------------
-
-alpn_not_supported_server(Config) when is_list(Config)->
- ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]},
- ServerOpts = [AdvProtocols] ++ ServerOpts0,
-
- {error, {options, {not_supported_in_sslv3, AdvProtocols}}} = ssl:listen(0, ServerOpts).
%%--------------------------------------------------------------------
-%% Internal functions ------------------------------------------------
+%% callback functions ------------------------------------------------
%%--------------------------------------------------------------------
-run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedAlert) ->
- ClientOpts = ClientExtraOpts ++ ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ServerOpts = ServerExtraOpts ++ ssl_test_lib:ssl_options(server_rsa_opts, Config),
-
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE, placeholder, []}},
- {options, ServerOpts}]),
-
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {?MODULE, placeholder, []}},
- {options, ClientOpts}]),
- ssl_test_lib:check_client_alert(Server, Client, ExpectedAlert).
-
-run_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) ->
- Data = "hello world",
-
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- ClientOpts = ClientExtraOpts ++ ClientOpts0,
- ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ServerOpts = ServerExtraOpts ++ ServerOpts0,
-
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE, ssl_receive_and_assert_alpn, [ExpectedProtocol, Data]}},
- {options, ServerOpts}]),
-
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {?MODULE, ssl_send_and_assert_alpn, [ExpectedProtocol, Data]}},
- {options, ClientOpts}]),
-
- ssl_test_lib:check_result(Server, ok, Client, ok).
-
assert_alpn(Socket, Protocol) ->
ct:log("Negotiated Protocol ~p, Expecting: ~p ~n",
[ssl:negotiated_protocol(Socket), Protocol]),
@@ -375,7 +326,7 @@ ssl_receive(Socket, Data) ->
ssl_receive(Socket, Data, Buffer) ->
ct:log("Connection info: ~p~n",
- [ssl:connection_information(Socket)]),
+ [ssl:connection_information(Socket)]),
receive
{ssl, Socket, MoreData} ->
ct:log("Received ~p~n",[MoreData]),
@@ -395,3 +346,48 @@ ssl_receive(Socket, Data, Buffer) ->
connection_info_result(Socket) ->
ssl:connection_information(Socket).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+
+run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedAlert) ->
+ ClientOpts = ClientExtraOpts ++ ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ServerExtraOpts ++ ssl_test_lib:ssl_options(server_rsa_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, placeholder, []}},
+ {options, ServerOpts}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, placeholder, []}},
+ {options, ClientOpts}]),
+ ssl_test_lib:check_client_alert(Server, Client, ExpectedAlert).
+
+run_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) ->
+ Data = "hello world",
+
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ClientExtraOpts ++ ClientOpts0,
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = ServerExtraOpts ++ ServerOpts0,
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, ssl_receive_and_assert_alpn, [ExpectedProtocol, Data]}},
+ {options, ServerOpts}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, ssl_send_and_assert_alpn, [ExpectedProtocol, Data]}},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl
index b393ba2f9b..b3be655cdc 100644
--- a/lib/ssl/test/ssl_api_SUITE.erl
+++ b/lib/ssl/test/ssl_api_SUITE.erl
@@ -21,11 +21,184 @@
%%
-module(ssl_api_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/ssl_api.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([conf_signature_algs/0,
+ conf_signature_algs/1,
+ no_common_signature_algs/0,
+ no_common_signature_algs/1,
+ default_reject_anonymous/0,
+ default_reject_anonymous/1,
+ connection_information_with_srp/0,
+ connection_information_with_srp/1,
+ peercert/0,
+ peercert/1,
+ peercert_with_client_cert/0,
+ peercert_with_client_cert/1,
+ connection_information/0,
+ connection_information/1,
+ secret_connection_info/0,
+ secret_connection_info/1,
+ keylog_connection_info/0,
+ keylog_connection_info/1,
+ versions/0,
+ versions/1,
+ active_n/0,
+ active_n/1,
+ dh_params/0,
+ dh_params/1,
+ prf/0,
+ prf/1,
+ hibernate/0,
+ hibernate/1,
+ hibernate_right_away/0,
+ hibernate_right_away/1,
+ listen_socket/0,
+ listen_socket/1,
+ peername/0,
+ peername/1,
+ recv_active/0,
+ recv_active/1,
+ recv_active_once/0,
+ recv_active_once/1,
+ recv_active_n/0,
+ recv_active_n/1,
+ recv_no_active_msg/0,
+ recv_no_active_msg/1,
+ recv_timeout/0,
+ recv_timeout/1,
+ recv_close/0,
+ recv_close/1,
+ controlling_process/0,
+ controlling_process/1,
+ controller_dies/0,
+ controller_dies/1,
+ controlling_process_transport_accept_socket/0,
+ controlling_process_transport_accept_socket/1,
+ close_with_timeout/0,
+ close_with_timeout/1,
+ close_in_error_state/0,
+ close_in_error_state/1,
+ call_in_error_state/0,
+ call_in_error_state/1,
+ close_transport_accept/0,
+ close_transport_accept/1,
+ abuse_transport_accept_socket/0,
+ abuse_transport_accept_socket/1,
+ honor_server_cipher_order/0,
+ honor_server_cipher_order/1,
+ honor_client_cipher_order/0,
+ honor_client_cipher_order/1,
+ honor_client_cipher_order_tls13/0,
+ honor_client_cipher_order_tls13/1,
+ honor_server_cipher_order_tls13/0,
+ honor_server_cipher_order_tls13/1,
+ ipv6/0,
+ ipv6/1,
+ der_input/0,
+ der_input/1,
+ new_options_in_handshake/0,
+ new_options_in_handshake/1,
+ max_handshake_size/0,
+ max_handshake_size/1,
+ invalid_certfile/0,
+ invalid_certfile/1,
+ invalid_cacertfile/0,
+ invalid_cacertfile/1,
+ invalid_keyfile/0,
+ invalid_keyfile/1,
+ options_not_proplist/0,
+ options_not_proplist/1,
+ invalid_options/0,
+ invalid_options/1,
+ cb_info/0,
+ cb_info/1,
+ log_alert/0,
+ log_alert/1,
+ getstat/0,
+ getstat/1,
+ handshake_continue/0,
+ handshake_continue/1,
+ handshake_continue_timeout/0,
+ handshake_continue_timeout/1,
+ handshake_continue_change_verify/0,
+ handshake_continue_change_verify/1,
+ hello_client_cancel/0,
+ hello_client_cancel/1,
+ hello_server_cancel/0,
+ hello_server_cancel/1,
+ handshake_continue_tls13_client/0,
+ handshake_continue_tls13_client/1,
+ rizzo_disabled/0,
+ rizzo_disabled/1,
+ rizzo_zero_n/0,
+ rizzo_zero_n/1,
+ rizzo_one_n_minus_one/0,
+ rizzo_one_n_minus_one/1,
+ supported_groups/0,
+ supported_groups/1,
+ client_options_negative_version_gap/0,
+ client_options_negative_version_gap/1,
+ client_options_negative_dependency_version/0,
+ client_options_negative_dependency_version/1,
+ client_options_negative_dependency_stateless/0,
+ client_options_negative_dependency_stateless/1,
+ client_options_negative_dependency_role/0,
+ client_options_negative_dependency_role/1,
+ client_options_negative_early_data/0,
+ client_options_negative_early_data/1,
+ server_options_negative_early_data/0,
+ server_options_negative_early_data/1,
+ server_options_negative_version_gap/0,
+ server_options_negative_version_gap/1,
+ server_options_negative_dependency_role/0,
+ server_options_negative_dependency_role/1,
+ invalid_options_tls13/0,
+ invalid_options_tls13/1,
+ cookie/0,
+ cookie/1
+ ]).
+
+%% Apply export
+-export([connection_information_result/1,
+ connection_info_result/1,
+ secret_connection_info_result/1,
+ keylog_connection_info_result/2,
+ check_srp_in_connection_information/3,
+ check_connection_info/2,
+ prf_verify_value/4,
+ try_recv_active/1,
+ try_recv_active_once/1,
+ controlling_process_result/3,
+ controller_dies_result/3,
+ send_recv_result_timeout_client/1,
+ send_recv_result_timeout_server/1,
+ do_recv_close/1,
+ tls_close/1,
+ no_recv_no_active/1,
+ ssl_getstat/1,
+ %%TODO Keep?
+ run_error_server/1,
+ run_error_server_close/1,
+ run_client_error/1
+ ]).
+
+
-define(SLEEP, 500).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -37,21 +210,24 @@ all() ->
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
].
groups() ->
[
- {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) --
- [dh_params, honor_server_cipher_order, honor_client_cipher_order,
- new_options_in_handshake, handshake_continue_tls13_client])
+ {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++
+ handshake_paus_tests()) --
+ [dh_params,
+ honor_server_cipher_order,
+ honor_client_cipher_order,
+ new_options_in_handshake,
+ handshake_continue_tls13_client,
+ invalid_options])
++ (since_1_2() -- [conf_signature_algs])},
{'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()},
{'tlsv1.1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3()},
{'tlsv1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3() ++ beast_mitigation_test()},
- {'sslv3', [], (gen_api_tests() -- [new_options_in_handshake]) ++ beast_mitigation_test() ++ pre_1_3()},
{'dtlsv1.2', [], (gen_api_tests() --
[invalid_keyfile, invalid_certfile, invalid_cacertfile,
invalid_options, new_options_in_handshake]) ++
@@ -70,23 +246,28 @@ since_1_2() ->
pre_1_3() ->
[
- default_reject_anonymous
+ default_reject_anonymous,
+ connection_information_with_srp
].
+
gen_api_tests() ->
[
peercert,
peercert_with_client_cert,
connection_information,
secret_connection_info,
+ keylog_connection_info,
versions,
active_n,
dh_params,
hibernate,
hibernate_right_away,
listen_socket,
+ peername,
recv_active,
recv_active_once,
recv_active_n,
+ recv_no_active_msg,
recv_timeout,
recv_close,
controlling_process,
@@ -116,13 +297,14 @@ gen_api_tests() ->
handshake_paus_tests() ->
[
handshake_continue,
- handshake_continue_timeout,
+ handshake_continue_timeout,
+ handshake_continue_change_verify,
hello_client_cancel,
hello_server_cancel,
handshake_continue_tls13_client
].
-%% Only relevant for SSL 3.0 and TLS 1.1
+%% Only relevant for SSL 3.0 and TLS 1.0
beast_mitigation_test() ->
[%% Original option
rizzo_disabled,
@@ -141,11 +323,14 @@ tls13_group() ->
client_options_negative_dependency_version,
client_options_negative_dependency_stateless,
client_options_negative_dependency_role,
+ client_options_negative_early_data,
+ server_options_negative_early_data,
server_options_negative_version_gap,
- server_options_negative_dependency_role
+ server_options_negative_dependency_role,
+ invalid_options_tls13,
+ cookie
].
-
init_per_suite(Config0) ->
catch crypto:stop(),
try crypto:start() of
@@ -161,29 +346,20 @@ end_per_suite(_Config) ->
application:unload(ssl),
application:stop(crypto).
-
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- [{client_type, erlang},
- {server_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)];
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:start(),
- Config
+ case ssl_test_lib:is_protocol_version(GroupName) of
+ true ->
+ ssl_test_lib:init_per_group(GroupName,
+ [{client_type, erlang},
+ {server_type, erlang},
+ {version, GroupName}
+ | Config]);
+ false ->
+ Config
end.
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(prf, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
@@ -202,6 +378,32 @@ init_per_testcase(prf, Config) ->
{md5sha, <<63,136,3,217,205,123,200,177,251,211,17,229,132,4,173,80>>}],
TestPlan = prf_create_plan([Version], PRFS, ExpectedPrfResults),
[{prf_test_plan, TestPlan} | Config];
+init_per_testcase(handshake_continue_tls13_client, Config) ->
+ case ssl_test_lib:sufficient_crypto_support('tlsv1.3') of
+ true ->
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap({seconds, 10}),
+ Config;
+ false ->
+ {skip, "Missing crypto support: TLS 1.3 not supported"}
+ end;
+init_per_testcase(connection_information_with_srp, Config) ->
+ PKAlg = proplists:get_value(public_keys, crypto:supports()),
+ case lists:member(srp, PKAlg) of
+ true ->
+ Config;
+ false ->
+ {skip, "Missing SRP crypto support"}
+ end;
+init_per_testcase(conf_signature_algs, Config) ->
+ case ssl_test_lib:appropriate_sha(crypto:supports()) of
+ sha256 ->
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap({seconds, 10}),
+ Config;
+ sha ->
+ {skip, "Tests needs certs with sha256"}
+ end;
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 10}),
@@ -224,13 +426,13 @@ peercert(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, peercert_result, []}},
+ {mfa, {ssl, peercert, []}},
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, peercert_result, []}},
+ {mfa, {ssl, peercert, []}},
{options, ClientOpts}]),
CertFile = proplists:get_value(certfile, ServerOpts),
@@ -258,13 +460,13 @@ peercert_with_client_cert(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, peercert_result, []}},
+ {mfa, {ssl, peercert, []}},
{options, [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, peercert_result, []}},
+ {mfa, {ssl, peercert, []}},
{options, ClientOpts}]),
ServerCertFile = proplists:get_value(certfile, ServerOpts),
@@ -309,6 +511,57 @@ connection_information(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+connection_information_with_srp() ->
+ [{doc,"Test the result of API function ssl:connection_information/1"
+ "includes srp_username."}].
+connection_information_with_srp(Config) when is_list(Config) ->
+ run_conn_info_srp_test(srp_anon, 'aes_128_cbc', Config).
+
+run_conn_info_srp_test(Kex, Cipher, Config) ->
+ Version = ssl_test_lib:protocol_version(Config),
+ TestCiphers = ssl_test_lib:test_ciphers(Kex, Cipher, Version),
+
+ case TestCiphers of
+ [] ->
+ {skip, {not_sup, Kex, Cipher, Version}};
+ [TestCipher | _T] ->
+ do_run_conn_info_srp_test(TestCipher, Version, Config)
+ end.
+
+do_run_conn_info_srp_test(ErlangCipherSuite, Version, Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ SOpts = [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}],
+ COpts = [{srp_identity, {"Test-User", "secret"}}],
+
+ ServerOpts = ssl_test_lib:ssl_options(SOpts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(COpts, Config),
+
+ ct:log("Erlang Cipher Suite is: ~p~n", [ErlangCipherSuite]),
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, check_srp_in_connection_information, [<<"Test-User">>, server]}},
+ {options, [{versions, [Version]}, {ciphers, [ErlangCipherSuite]} |
+ ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, check_srp_in_connection_information, [<<"Test-User">>, client]}},
+ {options, [{versions, [Version]}, {ciphers, [ErlangCipherSuite]} |
+ ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
%%--------------------------------------------------------------------
secret_connection_info() ->
@@ -318,17 +571,19 @@ secret_connection_info(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE, secret_connection_info_result, []}},
- {options, [{verify, verify_peer} | ServerOpts]}]),
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, secret_connection_info_result, []}},
+ {options, [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {?MODULE, secret_connection_info_result, []}},
- {options, [{verify, verify_peer} |ClientOpts]}]),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, secret_connection_info_result, []}},
+ {options, [{verify, verify_peer} |ClientOpts]}]),
ct:log("Testcase ~p, Client ~p Server ~p ~n",
[self(), Client, Server]),
@@ -338,6 +593,57 @@ secret_connection_info(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
+keylog_connection_info() ->
+ [{doc,"Test the API function ssl:connection_information/2"}].
+keylog_connection_info(Config) when is_list(Config) ->
+ keylog_connection_info(Config, true),
+ keylog_connection_info(Config, false).
+keylog_connection_info(Config, KeepSecrets) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, keylog_connection_info_result, [KeepSecrets]}},
+ {options, [{verify, verify_peer}, {keep_secrets, KeepSecrets} | ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, keylog_connection_info_result, [KeepSecrets]}},
+ {options, [{verify, verify_peer}, {keep_secrets, KeepSecrets} |ClientOpts]}]),
+
+ ct:log("Testcase ~p, KeepSecrets ~p Client ~p Server ~p ~n",
+ [self(), KeepSecrets, Client, Server]),
+
+ ServerKeylog = receive
+ {Server, {ok, Keylog}} ->
+ Keylog;
+ {Server, ServerError} ->
+ ct:fail({server, ServerError})
+ after 5000 ->
+ ct:fail({server, timeout})
+ end,
+
+ receive
+ {Client, {ok, ServerKeylog}} ->
+ ok;
+ {Client, {ok, ClientKeylog}} ->
+ ct:fail({mismatch, {ServerKeylog, ClientKeylog}});
+ {Client, ClientError} ->
+ ct:fail({client, ClientError})
+ after 5000 ->
+ ct:fail({client, timeout})
+ end,
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
prf() ->
[{doc,"Test that ssl:prf/5 uses the negotiated PRF."}].
prf(Config) when is_list(Config) ->
@@ -397,14 +703,16 @@ conf_signature_algs(Config) when is_list(Config) ->
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result, []}},
- {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ServerOpts]}]),
+ {options, [{active, false}, {signature_algs, [{sha256, rsa}]},
+ {versions, ['tlsv1.2']} | 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, []}},
- {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ClientOpts]}]),
+ {options, [{active, false}, {signature_algs, [{sha256, rsa}]},
+ {versions, ['tlsv1.2']} | ClientOpts]}]),
ct:log("Testcase ~p, Client ~p Server ~p ~n",
[self(), Client, Server]),
@@ -447,29 +755,31 @@ handshake_continue(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, ssl_test_lib:ssl_options([{reuseaddr, true},
- {log_level, debug},
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ssl_test_lib:ssl_options([{reuseaddr, true},
+ {log_level, debug},
{verify, verify_peer},
- {handshake, hello} | ServerOpts
- ],
- Config)},
- {continue_options, proplists:delete(reuseaddr, ServerOpts)}
- ]),
+ {handshake, hello} | ServerOpts
+ ],
+ Config)},
+ {continue_options, proplists:delete(reuseaddr, 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, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ClientOpts
- ],
- Config)},
- {continue_options, proplists:delete(reuseaddr, ClientOpts)}]),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ssl_test_lib:ssl_options([{handshake, hello},
+ {verify, verify_peer} | ClientOpts
+ ],
+ Config)},
+ {continue_options, proplists:delete(reuseaddr, ClientOpts)}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
@@ -483,18 +793,24 @@ handshake_continue_tls13_client(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, ssl_test_lib:ssl_options([{reuseaddr, true},
- {log_level, debug},
- {verify, verify_peer},
- {handshake, hello} | ServerOpts
- ],
- Config)},
- {continue_options, proplists:delete(reuseaddr, ServerOpts)}
- ]),
+ SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ssl_test_lib:ssl_options([{reuseaddr, true},
+ {log_level, debug},
+ {verify, verify_peer},
+ {ciphers, SCiphers},
+ {handshake, hello} | ServerOpts
+ ],
+ Config)},
+ {continue_options, proplists:delete(reuseaddr, ServerOpts)}
+ ]),
Port = ssl_test_lib:inet_port(Server),
@@ -519,21 +835,24 @@ handshake_continue_tls13_client(Config) when is_list(Config) ->
%% Send dummy session ticket to trigger sending of pre_shared_key and
%% psk_key_exchange_modes extensions.
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, ssl_test_lib:ssl_options([{handshake, hello},
- {session_tickets, manual},
- {use_ticket, [DummyTicket]},
- {versions, ['tlsv1.3',
- 'tlsv1.2',
- 'tlsv1.1',
- 'tlsv1']},
- {verify, verify_peer} | ClientOpts
- ],
- Config)},
- {continue_options, proplists:delete(reuseaddr, ClientOpts)}]),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ssl_test_lib:ssl_options([{handshake, hello},
+ {session_tickets, manual},
+ {use_ticket, [DummyTicket]},
+ {versions, ['tlsv1.3',
+ 'tlsv1.2',
+ 'tlsv1.1',
+ 'tlsv1'
+ ]},
+ {ciphers, ssl:cipher_suites(all, 'tlsv1.3')},
+ {verify, verify_peer} | ClientOpts
+ ],
+ Config)},
+ {continue_options, proplists:delete(reuseaddr, ClientOpts)}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
@@ -568,6 +887,34 @@ handshake_continue_timeout(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server, {error,timeout}),
ssl_test_lib:close(Server).
+handshake_continue_change_verify() ->
+ [{doc, "Test API function ssl:handshake_continue with updated verify option. "
+ "Use a verification that will fail to make sure verification is run"}].
+handshake_continue_change_verify(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ssl_test_lib:ssl_options(
+ [{handshake, hello},
+ {verify, verify_peer} | ServerOpts], Config)},
+ {continue_options, proplists:delete(reuseaddr, ServerOpts)}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client_error(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, ssl_test_lib:ssl_options(
+ [{handshake, hello},
+ {server_name_indication, "foobar"}
+ | ClientOpts], Config)},
+ {continue_options, [{verify, verify_peer} | ClientOpts]}]),
+ ssl_test_lib:check_client_alert(Client, handshake_failure).
%%--------------------------------------------------------------------
hello_client_cancel() ->
@@ -577,21 +924,23 @@ hello_client_cancel(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ServerOpts], Config)},
- {continue_options, proplists:delete(reuseaddr, ServerOpts)}]),
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ssl_test_lib:ssl_options([{handshake, hello},
+ {verify, verify_peer} | ServerOpts], Config)},
+ {continue_options, proplists:delete(reuseaddr, ServerOpts)}]),
Port = ssl_test_lib:inet_port(Server),
%% That is ssl:handshake_cancel returns ok
- {connect_failed, ok} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ClientOpts], Config)},
- {continue_options, cancel}]),
+ {connect_failed, ok} =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, ssl_test_lib:ssl_options([{handshake, hello},
+ {verify, verify_peer} | ClientOpts], Config)},
+ {continue_options, cancel}]),
ssl_test_lib:check_server_alert(Server, user_canceled).
%%--------------------------------------------------------------------
hello_server_cancel() ->
@@ -601,21 +950,22 @@ hello_server_cancel(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ServerOpts
- ], Config)},
- {continue_options, cancel}]),
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ssl_test_lib:ssl_options([{handshake, hello},
+ {verify, verify_peer} | ServerOpts
+ ], Config)},
+ {continue_options, cancel}]),
Port = ssl_test_lib:inet_port(Server),
ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
- {options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ClientOpts
- ], Config)},
+ {options, ssl_test_lib:ssl_options([{handshake, hello},
+ {verify, verify_peer} | ClientOpts
+ ], Config)},
{continue_options, proplists:delete(reuseaddr, ClientOpts)}]),
ssl_test_lib:check_result(Server, ok).
@@ -824,6 +1174,47 @@ listen_socket(Config) ->
ok = ssl:close(ListenSocket).
%%--------------------------------------------------------------------
+peername() ->
+ [{doc,"Test API function peername/1"}].
+
+peername(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server(
+ [
+ {node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ {Client, CSocket} = ssl_test_lib:start_client(
+ [return_socket,
+ {node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, ClientOpts}]),
+
+ ct:log("Testcase ~p, Client ~p Server ~p ~n",
+ [self(), Client, Server]),
+
+ Server ! get_socket,
+ SSocket =
+ receive
+ {Server, {socket, Socket}} ->
+ Socket
+ end,
+
+ {ok, ServerPeer} = ssl:peername(SSocket),
+ ct:log("Server's peer: ~p~n", [ServerPeer]),
+ {ok, ClientPeer} = ssl:peername(CSocket),
+ ct:log("Client's peer: ~p~n", [ClientPeer]),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+
recv_active() ->
[{doc,"Test recv on active socket"}].
@@ -951,6 +1342,27 @@ recv_close(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server, ok).
+%%--------------------------------------------------------------------
+recv_no_active_msg() ->
+ [{doc,"If we have a passive socket and do not call recv and peer closes we should no get"
+ "receive an active message"}].
+recv_no_active_msg(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, no_recv_no_active, []}},
+ {options, [{active, false} | 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, no_result, []}},
+ {options, ClientOpts}]),
+ ssl_test_lib:close(Client),
+ ssl_test_lib:check_result(Server, ok).
%%--------------------------------------------------------------------
controlling_process() ->
@@ -1130,7 +1542,7 @@ close_in_error_state(Config) when is_list(Config) ->
ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)],
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
- _ = spawn_link(?MODULE, run_error_server_close, [[self() | ServerOpts]]),
+ _ = spawn(?MODULE, run_error_server_close, [[self() | ServerOpts]]),
receive
{_Pid, Port} ->
spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]])
@@ -1149,7 +1561,7 @@ call_in_error_state(Config) when is_list(Config) ->
ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)],
- Pid = spawn_link(?MODULE, run_error_server, [[self() | ServerOpts]]),
+ Pid = spawn(?MODULE, run_error_server, [[self() | ServerOpts]]),
receive
{Pid, Port} ->
spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]])
@@ -1228,8 +1640,9 @@ invalid_keyfile(Config) when is_list(Config) ->
{from, self()}, {options, ClientOpts}]),
File = proplists:get_value(keyfile,BadOpts),
- ssl_test_lib:check_result(Server, {error,{options, {keyfile, File, {error,enoent}}}}, Client,
- {error, closed}).
+ ssl_test_lib:check_result(Server,
+ {error,{options, {keyfile, File, {error,enoent}}}}, Client,
+ {error, closed}).
%%--------------------------------------------------------------------
honor_server_cipher_order() ->
@@ -1251,10 +1664,11 @@ honor_server_cipher_order(Config) when is_list(Config) ->
cipher => aes_128_cbc,
mac => sha,
prf => default_prf}],
- honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa,
- cipher => aes_256_cbc,
- mac => sha,
- prf => default_prf}).
+ honor_cipher_order(Config, true, ServerCiphers,
+ ClientCiphers, #{key_exchange => dhe_rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf}).
%%--------------------------------------------------------------------
honor_client_cipher_order() ->
@@ -1276,10 +1690,11 @@ honor_client_cipher_order(Config) when is_list(Config) ->
cipher => aes_128_cbc,
mac => sha,
prf => default_prf}],
- honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa,
- cipher => aes_128_cbc,
- mac => sha,
- prf => default_prf}).
+ honor_cipher_order(Config, false, ServerCiphers,
+ ClientCiphers, #{key_exchange => dhe_rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf}).
%%--------------------------------------------------------------------
ipv6() ->
@@ -1329,7 +1744,7 @@ der_input(Config) when is_list(Config) ->
{status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
- [CADb | _] = element(6, State),
+ [CADb | _] = element(5, State),
ct:sleep(?SLEEP*2), %%Make sure there is no outstanding clean cert db msg in manager
Size = ets:info(CADb, size),
ct:pal("Size ~p", [Size]),
@@ -1467,7 +1882,7 @@ new_options_in_handshake(Config) when is_list(Config) ->
(ecdh_rsa) ->
true;
(rsa) ->
- true;
+ false;
(_) ->
false
end
@@ -1558,7 +1973,6 @@ invalid_options(Config) when is_list(Config) ->
{verify, 4},
{verify_fun, function},
{fail_if_no_peer_cert, 0},
- {verify_client_once, 1},
{depth, four},
{certfile, 'cert.pem'},
{keyfile,'key.pem' },
@@ -1576,6 +1990,22 @@ invalid_options(Config) when is_list(Config) ->
{active, trice},
{key, 'key.pem' }],
+ TestOpts2 =
+ [{[{anti_replay, '10k'}],
+ %% anti_replay is a server only option but tested with client
+ %% for simplicity
+ {options,dependency,{anti_replay,{versions,['tlsv1.3']}}}},
+ {[{cookie, false}],
+ {options,dependency,{cookie,{versions,['tlsv1.3']}}}},
+ {[{supported_groups, []}],
+ {options,dependency,{supported_groups,{versions,['tlsv1.3']}}}},
+ {[{use_ticket, [<<1,2,3,4>>]}],
+ {options,dependency,{use_ticket,{versions,['tlsv1.3']}}}},
+ {[{verify, verify_none}, {fail_if_no_peer_cert, true}],
+ {options, incompatible,
+ {verify, verify_none},
+ {fail_if_no_peer_cert, true}}}],
+
[begin
Server =
ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
@@ -1589,7 +2019,13 @@ invalid_options(Config) when is_list(Config) ->
Check(Client, Server, TestOpt),
ok
end || TestOpt <- TestOpts],
+
+ [begin
+ start_client_negative(Config, TestOpt, ErrorMsg),
+ ok
+ end || {TestOpt, ErrorMsg} <- TestOpts2],
ok.
+
%%-------------------------------------------------------------------
default_reject_anonymous()->
@@ -1648,7 +2084,8 @@ cb_info(Config) when is_list(Config) ->
%%-------------------------------------------------------------------
log_alert() ->
- [{doc,"Test that we can set log_alert."}].
+ [{doc,"Test that we can set log_alert and that it translates to correct log_level"
+ " that has replaced this option"}].
log_alert(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
@@ -1658,15 +2095,17 @@ log_alert(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, [{log_alert, false} | ServerOpts]}]),
+ {options, [{log_alert, true} | 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, [{log_alert, false} | ClientOpts]}]),
-
+ {Client, CSock} = ssl_test_lib:start_client([return_socket,
+ {node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{log_alert, false} | ClientOpts]}]),
+
+ {ok, [{log_level, none}]} = ssl:connection_information(CSock, [log_level]),
+
ssl_test_lib:check_result(Server, ok, Client, ok).
@@ -1703,6 +2142,8 @@ rizzo_one_n_minus_one (Config) ->
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
+%%-------------------------------------------------------------------
+
supported_groups() ->
[{doc,"Test the supported_groups option in TLS 1.3."}].
@@ -1758,7 +2199,7 @@ client_options_negative_version_gap() ->
[{doc,"Test client options with faulty version gap."}].
client_options_negative_version_gap(Config) when is_list(Config) ->
start_client_negative(Config, [{versions, ['tlsv1', 'tlsv1.3']}],
- {options, missing_version,
+ {options, missing_version,
{'tlsv1.2', {versions,[tlsv1, 'tlsv1.3']}}}).
%%--------------------------------------------------------------------
@@ -1791,6 +2232,91 @@ client_options_negative_dependency_role(Config) when is_list(Config) ->
{session_tickets,{stateless,{client,[disabled,manual,auto]}}}}).
%%--------------------------------------------------------------------
+client_options_negative_early_data() ->
+ [{doc,"Test client option early_data."}].
+client_options_negative_early_data(Config) when is_list(Config) ->
+ start_client_negative(Config, [{versions, ['tlsv1.2']},
+ {early_data, "test"}],
+ {options,dependency,
+ {early_data,{versions,['tlsv1.3']}}}),
+ start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {early_data, "test"}],
+ {options,dependency,
+ {early_data,{session_tickets,[manual,auto]}}}),
+
+ start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, stateful},
+ {early_data, "test"}],
+ {options,role,
+ {session_tickets,
+ {stateful,{client,[disabled,manual,auto]}}}}),
+ start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, disabled},
+ {early_data, "test"}],
+ {options,dependency,
+ {early_data,{session_tickets,[manual,auto]}}}),
+
+ start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, manual},
+ {early_data, "test"}],
+ {options,dependency,
+ {early_data, use_ticket}}),
+ start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, manual},
+ {use_ticket, [<<"ticket">>]},
+ {early_data, "test"}],
+ {options, type,
+ {early_data, {"test", not_binary}}}),
+ %% All options are ok but there is no server
+ start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, manual},
+ {use_ticket, [<<"ticket">>]},
+ {early_data, <<"test">>}],
+ econnrefused),
+
+ start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, auto},
+ {early_data, "test"}],
+ {options, type,
+ {early_data, {"test", not_binary}}}),
+ %% All options are ok but there is no server
+ start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, auto},
+ {early_data, <<"test">>}],
+ econnrefused).
+
+%%--------------------------------------------------------------------
+server_options_negative_early_data() ->
+ [{doc,"Test server option early_data."}].
+server_options_negative_early_data(Config) when is_list(Config) ->
+ start_server_negative(Config, [{versions, ['tlsv1.2']},
+ {early_data, "test"}],
+ {options,dependency,
+ {early_data,{versions,['tlsv1.3']}}}),
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {early_data, "test"}],
+ {options,dependency,
+ {early_data,{session_tickets,[stateful,stateless]}}}),
+
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, manual},
+ {early_data, "test"}],
+ {options,role,
+ {session_tickets,
+ {manual,{server,[disabled,stateful,stateless]}}}}),
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, disabled},
+ {early_data, "test"}],
+ {options,dependency,
+ {early_data,{session_tickets,[stateful,stateless]}}}),
+
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, stateful},
+ {early_data, "test"}],
+ {options,role,
+ {early_data,{"test",{server,[disabled,enabled]}}}}).
+
+%%--------------------------------------------------------------------
server_options_negative_version_gap() ->
[{doc,"Test server options with faulty version gap."}].
server_options_negative_version_gap(Config) when is_list(Config) ->
@@ -1827,13 +2353,14 @@ honor_server_cipher_order_tls13(Config) when is_list(Config) ->
cipher => aes_256_gcm,
mac => aead,
prf => sha384}],
- honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => any,
- cipher => aes_128_gcm,
- mac => aead,
- prf => sha256}).
+ honor_cipher_order(Config, true, ServerCiphers,
+ ClientCiphers, #{key_exchange => any,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256}).
%%--------------------------------------------------------------------
getstat() ->
- [{doc, "Test that you use ssl:getstat on an TLS socket"}].
+ [{doc, "Test that you use ssl:getstat on a TLS socket"}].
getstat(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
@@ -1857,14 +2384,115 @@ getstat(Config) when is_list(Config) ->
{options, ClientOpts}]),
ssl_test_lib:check_result(Server, ok, Client, ok).
-
-%%--------------------------------------------------------------------
-%% Internal functions ------------------------------------------------
-%%--------------------------------------------------------------------
-
-peercert_result(Socket) ->
- ssl:peercert(Socket).
-
+invalid_options_tls13() ->
+ [{doc, "Test invalid options with TLS 1.3"}].
+invalid_options_tls13(Config) when is_list(Config) ->
+ TestOpts =
+ [{{beast_mitigation, one_n_minus_one},
+ {options, dependency,
+ {beast_mitigation,{versions,[tlsv1]}}},
+ common},
+
+ {{next_protocols_advertised, [<<"http/1.1">>]},
+ {options, dependency,
+ {next_protocols_advertised,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ server},
+
+ {{client_preferred_next_protocols,
+ {client, [<<"http/1.1">>]}},
+ {options, dependency,
+ {client_preferred_next_protocols,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ client},
+
+ {{client_renegotiation, false},
+ {options, dependency,
+ {client_renegotiation,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ server
+ },
+
+ {{cookie, false},
+ {option , server_only, cookie},
+ client
+ },
+
+ {{padding_check, false},
+ {options, dependency,
+ {padding_check,{versions,[tlsv1]}}},
+ common},
+
+ {{psk_identity, "Test-User"},
+ {options, dependency,
+ {psk_identity,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ common},
+
+ {{user_lookup_fun,
+ {fun ssl_test_lib:user_lookup/3, <<1,2,3>>}},
+ {options, dependency,
+ {user_lookup_fun,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ common},
+
+ {{reuse_session, fun(_,_,_,_) -> false end},
+ {options, dependency,
+ {reuse_session,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ server},
+
+ {{reuse_session, <<1,2,3,4>>},
+ {options, dependency,
+ {reuse_session,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ client},
+
+ {{reuse_sessions, true},
+ {options, dependency,
+ {reuse_sessions,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ common},
+
+ {{secure_renegotiate, false},
+ {options, dependency,
+ {secure_renegotiate,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ common},
+
+ {{srp_identity, false},
+ {options, dependency,
+ {srp_identity,
+ {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ client}
+ ],
+
+ Fun = fun(Option, ErrorMsg, Type) ->
+ case Type of
+ server ->
+ start_server_negative(Config,
+ [Option,{versions, ['tlsv1.3']}],
+ ErrorMsg);
+ client ->
+ start_client_negative(Config,
+ [Option,{versions, ['tlsv1.3']}],
+ ErrorMsg);
+ common ->
+ start_server_negative(Config,
+ [Option,{versions, ['tlsv1.3']}],
+ ErrorMsg),
+ start_client_negative(Config,
+ [Option,{versions, ['tlsv1.3']}],
+ ErrorMsg)
+ end
+ end,
+ [Fun(Option, ErrorMsg, Type) || {Option, ErrorMsg, Type} <- TestOpts].
+
+cookie() ->
+ [{doc, "Test cookie extension in TLS 1.3"}].
+cookie(Config) when is_list(Config) ->
+ cookie_extension(Config, true),
+ cookie_extension(Config, false).
+
+%%% Checker functions
connection_information_result(Socket) ->
{ok, Info = [_ | _]} = ssl:connection_information(Socket),
case length(Info) > 3 of
@@ -1875,11 +2503,39 @@ connection_information_result(Socket) ->
false ->
ct:fail(no_ssl_options_returned)
end.
+
secret_connection_info_result(Socket) ->
{ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]),
{ok, ConnInfo} = ssl:connection_information(Socket, [client_random, server_random, master_secret]),
check_connection_info(Protocol, ConnInfo).
-
+
+keylog_connection_info_result(Socket, KeepSecrets) ->
+ {ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]),
+ {ok, ConnInfo} = ssl:connection_information(Socket, [keylog]),
+ check_keylog_info(Protocol, ConnInfo, KeepSecrets).
+
+check_keylog_info('tlsv1.3', [{keylog, ["CLIENT_HANDSHAKE_TRAFFIC_SECRET"++_,_|_]=Keylog}], true) ->
+ {ok, Keylog};
+check_keylog_info('tlsv1.3', []=Keylog, false) ->
+ {ok, Keylog};
+check_keylog_info('tlsv1.2', [{keylog, ["CLIENT_RANDOM"++_]=Keylog}], _) ->
+ {ok, Keylog};
+check_keylog_info(NotSup, [], _) when NotSup == 'tlsv1.1'; NotSup == tlsv1; NotSup == 'dtlsv1.2'; NotSup == dtlsv1 ->
+ {ok, []};
+check_keylog_info(_, Unexpected, _) ->
+ {unexpected, Unexpected}.
+
+check_srp_in_connection_information(_Socket, _Username, client) ->
+ ok;
+check_srp_in_connection_information(Socket, Username, server) ->
+ {ok, Info} = ssl:connection_information(Socket),
+ ct:log("Info ~p~n", [Info]),
+ case proplists:get_value(srp_username, Info, not_found) of
+ Username ->
+ ok;
+ not_found ->
+ ct:fail(srp_username_not_found)
+ end.
%% In TLS 1.3 the master_secret field is used to store multiple secrets from the key schedule and it is a tuple.
%% client_random and server_random are not used in the TLS 1.3 key schedule.
@@ -1892,19 +2548,125 @@ check_connection_info(_, [{client_random, ClientRand}, {server_random, ServerRan
check_connection_info(_, _) ->
false.
+prf_verify_value(Socket, TlsVer, Expected, Algo) ->
+ Ret = ssl:prf(Socket, <<>>, <<>>, [<<>>], 16),
+ case TlsVer of
+ sslv3 ->
+ case Ret of
+ {error, undefined} -> ok;
+ _ ->
+ {error, {expected, {error, undefined},
+ got, Ret, tls_ver, TlsVer, prf_algorithm, Algo}}
+ end;
+ _ ->
+ case Ret of
+ {ok, Expected} -> ok;
+ {ok, Val} -> {error, {expected, Expected, got, Val, tls_ver, TlsVer,
+ prf_algorithm, Algo}}
+ end
+ end.
+try_recv_active(Socket) ->
+ ssl:send(Socket, "Hello world"),
+ {error, einval} = ssl:recv(Socket, 11),
+ ok.
+try_recv_active_once(Socket) ->
+ {error, einval} = ssl:recv(Socket, 11),
+ ok.
+
+controlling_process_result(Socket, Pid, Msg) ->
+ ok = ssl:controlling_process(Socket, Pid),
+ %% Make sure other side has evaluated controlling_process
+ %% before message is sent
+ ct:sleep(?SLEEP),
+ ssl:send(Socket, Msg),
+ no_result_msg.
+
+controller_dies_result(_Socket, _Pid, _Msg) ->
+ receive Result -> Result end.
+send_recv_result_timeout_client(Socket) ->
+ {error, timeout} = ssl:recv(Socket, 11, 500),
+ {error, timeout} = ssl:recv(Socket, 11, 0),
+ ssl:send(Socket, "Hello world"),
+ receive
+ Msg ->
+ io:format("Msg ~p~n",[Msg])
+ after 500 ->
+ ok
+ end,
+ {ok, "Hello world"} = ssl:recv(Socket, 11, 500),
+ ok.
+send_recv_result_timeout_server(Socket) ->
+ ssl:send(Socket, "Hello"),
+ {ok, "Hello world"} = ssl:recv(Socket, 11),
+ ssl:send(Socket, " world"),
+ ok.
+
+do_recv_close(Socket) ->
+ {error, closed} = ssl:recv(Socket, 11),
+ receive
+ {_,{error,closed}} ->
+ error_extra_close_sent_to_user_process
+ after 500 ->
+ ok
+ end.
+
+tls_close(Socket) ->
+ ok = ssl_test_lib:send_recv_result(Socket),
+ case ssl:close(Socket, 10000) of
+ ok ->
+ ok;
+ {error, closed} ->
+ ok;
+ Other ->
+ ct:fail(Other)
+ end.
+
+run_error_server_close([Pid | Opts]) ->
+ {ok, Listen} = ssl:listen(0, Opts),
+ {ok,{_, Port}} = ssl:sockname(Listen),
+ Pid ! {self(), Port},
+ {ok, Socket} = ssl:transport_accept(Listen),
+ Pid ! ssl:close(Socket).
+
+run_error_server([ Pid | Opts]) ->
+ {ok, Listen} = ssl:listen(0, Opts),
+ {ok,{_, Port}} = ssl:sockname(Listen),
+ Pid ! {self(), Port},
+ {ok, Socket} = ssl:transport_accept(Listen),
+ Pid ! ssl:controlling_process(Socket, self()).
+
+run_client_error([Port, Opts]) ->
+ ssl:connect("localhost", Port, Opts).
+
+no_recv_no_active(Socket) ->
+ receive
+ {ssl_closed, Socket} ->
+ ct:fail(received_active_msg)
+ after 5000 ->
+ ok
+ end.
+
+connection_info_result(Socket) ->
+ {ok, Info} = ssl:connection_information(Socket, [protocol, selected_cipher_suite]),
+ {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}}.
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
prf_create_plan(TlsVersions, PRFs, Results) ->
lists:foldl(fun(Ver, Acc) ->
A = prf_ciphers_and_expected(Ver, PRFs, Results),
[A|Acc]
end, [], TlsVersions).
+
+
prf_ciphers_and_expected(TlsVer, PRFs, Results) ->
case TlsVer of
TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1
orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' ->
- Ciphers = ssl:cipher_suites(),
- {_, Expected} = lists:keyfind(md5sha, 1, Results),
+ Ciphers = ssl:cipher_suites(default, TlsVer),
+ Expected = [Expect#{prf := md5sha} || Expect <- Results],
[[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]];
TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'->
lists:foldl(
@@ -1915,22 +2677,23 @@ prf_ciphers_and_expected(TlsVer, PRFs, Results) ->
ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]),
Acc;
Ciphers ->
- {_, Expected} = lists:keyfind(PRF, 1, Results),
+ Expected = [Expect#{prf := PRF} || Expect <- Results],
[[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected},
{prf, PRF}] | Acc]
end
end, [], PRFs)
end.
-prf_get_ciphers(_, PRF) ->
- lists:filter(
- fun(C) when tuple_size(C) == 4 andalso
- element(4, C) == PRF ->
- true;
- (_) ->
- false
- end,
- ssl:cipher_suites()).
+prf_get_ciphers(TlsVer, PRF) ->
+ PrfFilter = fun(Value) ->
+ case Value of
+ PRF ->
+ true;
+ _ ->
+ false
+ end
+ end,
+ ssl:filter_cipher_suites(ssl:cipher_suites(default, TlsVer), [{prf, PrfFilter}]).
prf_run_test(_, TlsVer, [], _, Prf) ->
ct:fail({error, cipher_list_empty, TlsVer, Prf});
@@ -1953,23 +2716,6 @@ prf_run_test(Config, TlsVer, Ciphers, Expected, Prf) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
-prf_verify_value(Socket, TlsVer, Expected, Algo) ->
- Ret = ssl:prf(Socket, <<>>, <<>>, [<<>>], 16),
- case TlsVer of
- sslv3 ->
- case Ret of
- {error, undefined} -> ok;
- _ ->
- {error, {expected, {error, undefined},
- got, Ret, tls_ver, TlsVer, prf_algorithm, Algo}}
- end;
- _ ->
- case Ret of
- {ok, Expected} -> ok;
- {ok, Val} -> {error, {expected, Expected, got, Val, tls_ver, TlsVer,
- prf_algorithm, Algo}}
- end
- end.
tls_or_dtls('dtlsv1') ->
dtls;
@@ -2039,24 +2785,6 @@ repeat(N, T, Fun) when is_integer(N), N > 0 ->
repeat(_, _, _) ->
ok.
-try_recv_active(Socket) ->
- ssl:send(Socket, "Hello world"),
- {error, einval} = ssl:recv(Socket, 11),
- ok.
-try_recv_active_once(Socket) ->
- {error, einval} = ssl:recv(Socket, 11),
- ok.
-
-controlling_process_result(Socket, Pid, Msg) ->
- ok = ssl:controlling_process(Socket, Pid),
- %% Make sure other side has evaluated controlling_process
- %% before message is sent
- ct:sleep(?SLEEP),
- ssl:send(Socket, Msg),
- no_result_msg.
-
-controller_dies_result(_Socket, _Pid, _Msg) ->
- receive Result -> Result end.
get_close(Pid, Where) ->
receive
{'EXIT', Pid, _Reason} ->
@@ -2087,60 +2815,7 @@ ssl_active_recv(N, Acc) ->
ssl_active_recv(N-length(Bytes), Acc ++ Bytes)
end.
-send_recv_result_timeout_client(Socket) ->
- {error, timeout} = ssl:recv(Socket, 11, 500),
- {error, timeout} = ssl:recv(Socket, 11, 0),
- ssl:send(Socket, "Hello world"),
- receive
- Msg ->
- io:format("Msg ~p~n",[Msg])
- after 500 ->
- ok
- end,
- {ok, "Hello world"} = ssl:recv(Socket, 11, 500),
- ok.
-send_recv_result_timeout_server(Socket) ->
- ssl:send(Socket, "Hello"),
- {ok, "Hello world"} = ssl:recv(Socket, 11),
- ssl:send(Socket, " world"),
- ok.
-
-do_recv_close(Socket) ->
- {error, closed} = ssl:recv(Socket, 11),
- receive
- {_,{error,closed}} ->
- error_extra_close_sent_to_user_process
- after 500 ->
- ok
- end.
-
-tls_close(Socket) ->
- ok = ssl_test_lib:send_recv_result(Socket),
- case ssl:close(Socket, 10000) of
- ok ->
- ok;
- {error, closed} ->
- ok;
- Other ->
- ct:fail(Other)
- end.
-
-run_error_server_close([Pid | Opts]) ->
- {ok, Listen} = ssl:listen(0, Opts),
- {ok,{_, Port}} = ssl:sockname(Listen),
- Pid ! {self(), Port},
- {ok, Socket} = ssl:transport_accept(Listen),
- Pid ! ssl:close(Socket).
-
-run_error_server([ Pid | Opts]) ->
- {ok, Listen} = ssl:listen(0, Opts),
- {ok,{_, Port}} = ssl:sockname(Listen),
- Pid ! {self(), Port},
- {ok, Socket} = ssl:transport_accept(Listen),
- Pid ! ssl:controlling_process(Socket, self()).
-run_client_error([Port, Opts]) ->
- ssl:connect("localhost", Port, Opts).
honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
@@ -2170,12 +2845,40 @@ honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+cookie_extension(Config, Cookie) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ Data = "123456789012345", %% 15 bytes
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Trigger a HelloRetryRequest
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, [{supported_groups, [x448,
+ secp256r1,
+ secp384r1]},
+ {cookie, Cookie}|ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, ClientOpts}]),
+
+ ok = ssl_test_lib:send(Client, Data),
+ Data = ssl_test_lib:check_active_receive(Server, Data),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
start_client_negative(Config, Options, Error) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, 0},
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Port = ssl_test_lib:inet_port(ServerNode),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
+ {return_error, econnrefused},
{mfa, {?MODULE, connection_info_result, []}},
{options, Options ++ ClientOpts}]),
ct:pal("Actual: ~p~nExpected: ~p", [Client, {connect_failed, Error}]),
@@ -2192,10 +2895,6 @@ start_server_negative(Config, Options, Error) ->
ct:pal("Actual: ~p~nExpected: ~p", [Server,Error]),
Error = Server.
-connection_info_result(Socket) ->
- {ok, Info} = ssl:connection_information(Socket, [protocol, selected_cipher_suite]),
- {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}}.
-
der_input_opts(Opts) ->
Certfile = proplists:get_value(certfile, Opts),
CaCertsfile = proplists:get_value(cacertfile, Opts),
diff --git a/lib/ssl/test/ssl_app_env_SUITE.erl b/lib/ssl/test/ssl_app_env_SUITE.erl
index 27fbcb8e47..98e96023b2 100644
--- a/lib/ssl/test/ssl_app_env_SUITE.erl
+++ b/lib/ssl/test/ssl_app_env_SUITE.erl
@@ -21,11 +21,32 @@
%%
-module(ssl_app_env_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/ssl_api.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([internal_active_1/0,
+ internal_active_1/1,
+ protocol_versions/0,
+ protocol_versions/1,
+ empty_protocol_versions/0,
+ empty_protocol_versions/1
+ ]).
+
+-define(TIMEOUT, {seconds, 5}).
-define(SLEEP, 500).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -37,7 +58,6 @@ all() ->
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
].
@@ -48,7 +68,6 @@ groups() ->
{'tlsv1.2', [], tests()},
{'tlsv1.1', [], tests()},
{'tlsv1', [], tests()},
- {'sslv3', [], tests()},
{'dtlsv1.2', [], tests()},
{'dtlsv1', [], tests()}
].
@@ -76,52 +95,36 @@ end_per_suite(_Config) ->
application:unload(ssl),
application:stop(crypto).
-
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
+ case ssl_test_lib:is_protocol_version(GroupName) of
true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- [{client_type, erlang},
- {server_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)];
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
-
-end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
+ ssl_test_lib:init_per_group(GroupName,
+ [{client_type, erlang},
+ {server_type, erlang},
+ {version, GroupName} | Config]);
false ->
Config
end.
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
+
init_per_testcase(internal_active_1, Config) ->
+ Version = ssl_test_lib:protocol_version(Config),
ssl:stop(),
application:load(ssl),
+ ssl_test_lib:clean_env(),
application:set_env(ssl, internal_active_n, 1),
+ ssl_test_lib:set_protocol_versions(Version),
ssl:start(),
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
Config;
init_per_testcase(protocol_versions, Config) ->
Version = ssl_test_lib:protocol_version(Config),
- case atom_to_list(Version) of
- "d" ++ _ ->
- ssl:stop(),
- application:load(ssl),
- application:set_env(ssl, dtls_protocol_version, [Version]),
- ssl:start();
- _ ->
- ssl:stop(),
- application:load(ssl),
- application:set_env(ssl, protocol_version, [Version]),
- ssl:start()
- end,
- ct:timetrap({seconds, 5}),
+ ssl_test_lib:set_protocol_versions(Version),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(empty_protocol_versions, Config) ->
ssl:stop(),
@@ -130,14 +133,15 @@ init_per_testcase(empty_protocol_versions, Config) ->
application:set_env(ssl, protocol_version, []),
application:set_env(ssl, dtls_protocol_version, []),
ssl:start(),
- ct:timetrap({seconds, 5}),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(_TestCase, Config) ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config.
-end_per_testcase(_, _Config) ->
- ssl_test_lib:clean_start().
+end_per_testcase(_, Config) ->
+ Config.
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
@@ -165,7 +169,16 @@ empty_protocol_versions() ->
[{doc,"Test to set an empty list of protocol versions in app environment."}].
empty_protocol_versions(Config) when is_list(Config) ->
+ Version = proplists:get_value(version, Config),
+ VersionsR = ssl:versions(),
+ Supported = proplists:get_value(supported, VersionsR) ++
+ proplists:get_value(supported_dtls, VersionsR),
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
-
+ case lists:member(Version, Supported) of
+ true ->
+ ssl_test_lib:basic_test([{versions, [Version]} | ClientOpts], ServerOpts, Config);
+ false ->
+ ssl_test_lib:basic_alert([{versions, [Version]} | ClientOpts],
+ ServerOpts, Config, protocol_version)
+ end.
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index fbe2d4f6b0..a5ece4fad8 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -22,12 +22,71 @@
-module(ssl_basic_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
-include_lib("ssl/src/ssl_api.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([app/0,
+ app/1,
+ appup/0,
+ appup/1,
+ version_option/0,
+ version_option/1,
+ connect_twice/0,
+ connect_twice/1,
+ connect_dist/0,
+ connect_dist/1,
+ defaults/1,
+ fallback/0,
+ fallback/1,
+ cipher_format/0,
+ cipher_format/1,
+ tls_versions_option/0,
+ tls_versions_option/1,
+ eccs/0,
+ eccs/1,
+ cipher_suites/0,
+ cipher_suites/1,
+ old_cipher_suites/0,
+ old_cipher_suites/1,
+ cipher_suites_mix/0,
+ cipher_suites_mix/1,
+ unordered_protocol_versions_server/0,
+ unordered_protocol_versions_server/1,
+ unordered_protocol_versions_client/0,
+ unordered_protocol_versions_client/1,
+ fake_root/0,
+ fake_root/1,
+ fake_root_legacy/0,
+ fake_root_legacy/1,
+ fake_root_no_intermediate/0,
+ fake_root_no_intermediate/1,
+ fake_root_no_intermediate_legacy/0,
+ fake_root_no_intermediate_legacy/1,
+ fake_intermediate_cert/0,
+ fake_intermediate_cert/1
+ ]).
+
+%% Apply export
+-export([tcp_send_recv_result/1,
+ result_ok/1,
+ protocol_info_result/1,
+ version_info_result/1,
+ connect_dist_s/1,
+ connect_dist_c/1,
+ dummy/1
+ ]).
-define(TIMEOUT, 20000).
-define(EXPIRE, 10).
@@ -63,7 +122,12 @@ basic_tests() ->
eccs,
cipher_suites,
old_cipher_suites,
- cipher_suites_mix
+ cipher_suites_mix,
+ fake_root,
+ fake_root_no_intermediate,
+ fake_root_legacy,
+ fake_root_no_intermediate_legacy,
+ fake_intermediate_cert
].
options_tests() ->
@@ -76,7 +140,7 @@ init_per_suite(Config0) ->
try crypto:start() of
ok ->
ssl_test_lib:clean_start(),
- %% make rsa certs using oppenssl
+ %% make rsa certs using openssl
{ok, _} = make_certs:all(proplists:get_value(data_dir, Config0),
proplists:get_value(priv_dir, Config0)),
Config1 = ssl_test_lib:make_dsa_cert(Config0),
@@ -173,7 +237,7 @@ connect_twice(Config) when is_list(Config) ->
ssl_test_lib:close(Client1).
defaults(Config) when is_list(Config)->
Versions = ssl:versions(),
- true = lists:member(sslv3, proplists:get_value(available, Versions)),
+ false = lists:member(sslv3, proplists:get_value(available, Versions)),
false = lists:member(sslv3, proplists:get_value(supported, Versions)),
true = lists:member('tlsv1', proplists:get_value(available, Versions)),
false = lists:member('tlsv1', proplists:get_value(supported, Versions)),
@@ -244,7 +308,7 @@ cipher_suites(Config) when is_list(Config) ->
cipher => 'aes_128_cbc',
mac => sha,
prf => default_prf},
- Version = ssl_test_lib:protocol_version(Config),
+ Version = tls_record:highest_protocol_version([]),
All = [_|_] = ssl:cipher_suites(all, Version),
Default = [_|_] = ssl:cipher_suites(default, Version),
Anonymous = [_|_] = ssl:cipher_suites(anonymous, Version),
@@ -321,7 +385,7 @@ cipher_suites_mix(Config) when is_list(Config) ->
{host, Hostname},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, [{ciphers, CipherSuites} | ClientOpts]}]),
+ {options, [{versions, ['tlsv1.2']},{ciphers, CipherSuites} | ClientOpts]}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
ssl_test_lib:close(Server),
@@ -406,7 +470,6 @@ eccs() ->
eccs(Config) when is_list(Config) ->
[_|_] = All = ssl:eccs(),
- [] = ssl:eccs(sslv3),
[_|_] = Tls = ssl:eccs(tlsv1),
[_|_] = Tls1 = ssl:eccs('tlsv1.1'),
[_|_] = Tls2 = ssl:eccs('tlsv1.2'),
@@ -450,14 +513,252 @@ tls_versions_option(Config) when is_list(Config) ->
end,
ssl_test_lib:check_client_alert(ErrClient, protocol_version).
+fake_root() ->
+ [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
+fake_root(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ FakeKey = ssl_test_lib:hardcode_rsa_key(1),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ FakeCert = public_key:pkix_sign(TBS, FakeKey),
+
+
+ AuthExt = #'AuthorityKeyIdentifier'{authorityCertIssuer = [{directoryName, TBS#'OTPTBSCertificate'.issuer}],
+ authorityCertSerialNumber = TBS#'OTPTBSCertificate'.serialNumber},
+ [AuthKeyExt] = x509_test:extensions([{?'id-ce-authorityKeyIdentifier',
+ AuthExt,
+ false}]),
+
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {extensions, [AuthKeyExt]}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {extensions, [AuthKeyExt]}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
+
+fake_root_no_intermediate() ->
+ [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
+
+fake_root_no_intermediate(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ FakeKey = ssl_test_lib:hardcode_rsa_key(1),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ FakeCert = public_key:pkix_sign(TBS, FakeKey),
+
+ AuthExt = #'AuthorityKeyIdentifier'{authorityCertIssuer = [{directoryName, TBS#'OTPTBSCertificate'.issuer}],
+ authorityCertSerialNumber = TBS#'OTPTBSCertificate'.serialNumber},
+ [AuthKeyExt] = x509_test:extensions([{?'id-ce-authorityKeyIdentifier',
+ AuthExt,
+ false}]),
+
+
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {extensions, [AuthKeyExt]}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {extensions, [AuthKeyExt]}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
+
+fake_root_legacy() ->
+ [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
+fake_root_legacy(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ FakeKey = ssl_test_lib:hardcode_rsa_key(1),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ FakeCert = public_key:pkix_sign(TBS, FakeKey),
+
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
+
+fake_root_no_intermediate_legacy() ->
+ [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
+fake_root_no_intermediate_legacy(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ FakeKey = ssl_test_lib:hardcode_rsa_key(1),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ FakeCert = public_key:pkix_sign(TBS, FakeKey),
+
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
+
+fake_intermediate_cert() ->
+ [{doc,"Test that we can not use a fake intermediat cert claiming to be signed by a trusted ROOT but is not."}].
+
+fake_intermediate_cert(Config) when is_list(Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ ROOT = #{cert := Cert,
+ key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+
+ OtherSROOT = #{cert := OtherSCert,
+ key := OtherSKey} = public_key:pkix_test_root_cert("OTHER SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {extensions, Ext}]),
+ OtherCROOT = #{cert := OtherCCert,
+ key := _OtherCKey} = public_key:pkix_test_root_cert("OTHER Client ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {extensions, Ext}]),
+
+ #{client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+
+ #{server_config := OtherServerConf} = public_key:pkix_test_data(#{server_chain =>
+ #{root => OtherSROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]},
+ client_chain =>
+ #{root => OtherCROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+ OTPCert = public_key:pkix_decode_cert(Cert, otp),
+ TBS = OTPCert#'OTPCertificate'.tbsCertificate,
+ TBSExt = TBS#'OTPTBSCertificate'.extensions,
+ AuthExt = #'AuthorityKeyIdentifier'{authorityCertIssuer = [{directoryName, TBS#'OTPTBSCertificate'.issuer}],
+ authorityCertSerialNumber = TBS#'OTPTBSCertificate'.serialNumber},
+ [AuthKeyExt] = x509_test:extensions([{?'id-ce-authorityKeyIdentifier',
+ AuthExt,
+ false}]),
+
+
+ CAs = proplists:get_value(cacerts, OtherServerConf),
+
+ [ICA] = CAs -- [OtherSCert, OtherCCert],
+
+ OTPICACert = public_key:pkix_decode_cert(ICA, otp),
+ ICATBS = OTPICACert#'OTPCertificate'.tbsCertificate,
+
+ FakeICA = public_key:pkix_sign(ICATBS#'OTPTBSCertificate'{extensions = [AuthKeyExt | TBSExt]}, OtherSKey),
+
+ ServerCert = proplists:get_value(cert, OtherServerConf),
+ FakeServer = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, [{cert, [ServerCert, FakeICA]} |
+ proplists:delete(cert, OtherServerConf)]
+ }]),
+ Port1 = ssl_test_lib:inet_port(FakeServer),
+
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port1},
+ {host, Hostname},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientConf]}]),
+
+ ssl_test_lib:check_client_alert(Client1, bad_certificate).
%%--------------------------------------------------------------------
-%% Internal functions ------------------------------------------------
+%% callback functions ------------------------------------------------
%%--------------------------------------------------------------------
-send_recv_result(Socket) ->
- ssl:send(Socket, "Hello world"),
- {ok,"Hello world"} = ssl:recv(Socket, 11),
- ok.
tcp_send_recv_result(Socket) ->
gen_tcp:send(Socket, "Hello world"),
{ok,"Hello world"} = gen_tcp:recv(Socket, 11),
@@ -489,7 +790,9 @@ dummy(_Socket) ->
%% Should not happen as the ssl connection will not be established
%% due to fatal handshake failiure
exit(kill).
-
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
version_option_test(Config, Version) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
@@ -526,3 +829,60 @@ remove_supported_versions(Available, Supported) ->
Versions0
end.
+
+test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, ResultRootIncluded, ResultRootExcluded) ->
+ RealServer = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerConf}]),
+ Port0 = ssl_test_lib:inet_port(RealServer),
+ Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port0},
+ {host, Hostname},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientConf]}]),
+
+ ssl_test_lib:check_result(RealServer, ok, Client0, ok),
+
+ ssl_test_lib:close(RealServer),
+ ssl_test_lib:close(Client0),
+
+ %% Fake server sends ROOT cert
+ FakeServer = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, FakeServerConf}]),
+ Port1 = ssl_test_lib:inet_port(FakeServer),
+
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port1},
+ {host, Hostname},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientConf]}]),
+
+ ssl_test_lib:check_client_alert(Client1, ResultRootIncluded),
+
+
+ %%Fake server does not send ROOT cert
+ CAS0 = proplists:get_value(cacerts, FakeServerConf),
+ CAS1 = CAS0 -- [FakeCert],
+
+ FakeServer1 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, [{cacerts, CAS1} | proplists:delete(cacerts, FakeServerConf)]}]),
+
+ Port2 = ssl_test_lib:inet_port(FakeServer1),
+
+ Client2 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port2},
+ {host, Hostname},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientConf]}]),
+
+ ssl_test_lib:check_client_alert(Client2, ResultRootExcluded),
+
+ ssl_test_lib:close(FakeServer1).
+
+
+
+
+
diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl
index 5d15b69f5e..f78ac9c9cc 100644
--- a/lib/ssl/test/ssl_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_bench_SUITE.erl
@@ -18,20 +18,59 @@
%% %CopyrightEnd%
%%
-module(ssl_bench_SUITE).
--compile(export_all).
+
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct_event.hrl").
+%% Callback functions
+-export([suite/0,
+ all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
-define(remote_host, "NETMARKS_REMOTE_HOST").
+%% Test cases
+-export([setup_sequential/1,
+ setup_sequential_noreuse/1,
+ setup_sequential_13/1,
+ setup_concurrent/1,
+ setup_concurrent_noreuse/1,
+ setup_concurrent_13/1,
+ payload/1,
+ payload_13/1
+ ]).
+
+%% spawn/apply export
+-export([server_init/6,
+ client_init/6,
+ setup_connection/3,
+ setup_server_init/5,
+ payload/3
+ ]).
+
+%% Manual test
+-export([ssl/0,
+ test/2
+ ]).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
-all() -> [{group, basic}, {group, setup}, {group, payload}, {group, pem_cache}].
+all() -> [{group, setup}, {group, payload}].
groups() ->
- [{basic, [], [basic_pem_cache]},
- {setup, [{repeat, 3}], [setup_sequential, setup_concurrent]},
- {payload, [{repeat, 3}], [payload_simple]},
- {pem_cache, [{repeat, 3}], [use_pem_cache, bypass_pem_cache]}
+ [{setup, [{repeat, 3}], [setup_sequential, setup_sequential_noreuse, setup_sequential_13,
+ setup_concurrent, setup_concurrent_noreuse, setup_concurrent_13]},
+ {payload, [{repeat, 3}], [payload, payload_13]}
].
init_per_group(_GroupName, Config) ->
@@ -53,28 +92,16 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ok.
-init_per_testcase(TC, Conf) when TC =:= use_pem_cache;
- TC =:= bypass_pem_cache;
- TC =:= basic_pem_cache ->
- case bypass_pem_cache_supported() of
- false -> {skipped, "PEM cache bypass support required"};
- true ->
- application:set_env(ssl, bypass_pem_cache, false),
- Conf
- end;
init_per_testcase(_Func, Conf) ->
Conf.
-end_per_testcase(TC, _Config) when TC =:= use_pem_cache;
- TC =:= bypass_pem_cache;
- TC =:= basic_pem_cache ->
- case bypass_pem_cache_supported() of
- false -> ok;
- true -> application:set_env(ssl, bypass_pem_cache, false)
- end;
end_per_testcase(_Func, _Conf) ->
ok.
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
-define(COUNT, 400).
-define(TC(Cmd), tc(fun() -> Cmd end, ?MODULE, ?LINE)).
@@ -89,73 +116,115 @@ end_per_testcase(_Func, _Conf) ->
setup_sequential(Config) ->
Server = proplists:get_value(server_node, Config),
Server =/= undefined orelse error(no_server),
- {ok, Result} = do_test(ssl, setup_connection, ?COUNT * 20, 1, Server),
+ Cfg = [{version, 'tlsv1.2'}],
+ {ok, Result} = do_test(ssl, {setup_connection,Cfg}, ?COUNT * 20, 1, Server),
ct_event:notify(#event{name = benchmark_data,
data=[{value, Result},
{suite, "ssl"}, {name, "Sequential setup"}]}),
ok.
+setup_sequential_noreuse(Config) ->
+ Server = proplists:get_value(server_node, Config),
+ Server =/= undefined orelse error(no_server),
+ Cfg = [{version, 'tlsv1.2'}, no_reuse],
+ {ok, Result} = do_test(ssl, {setup_connection,Cfg}, ?COUNT * 20, 1, Server),
+ ct_event:notify(#event{name = benchmark_data,
+ data=[{value, Result},
+ {suite, "ssl"}, {name, "Seq setup 1.2 no session"}]}),
+ ok.
+
+setup_sequential_13(Config) ->
+ Server = proplists:get_value(server_node, Config),
+ Server =/= undefined orelse error(no_server),
+ Cfg = [{version, 'tlsv1.3'}],
+ {ok, Result} = do_test(ssl, {setup_connection,Cfg}, ?COUNT * 20, 1, Server),
+ ct_event:notify(#event{name = benchmark_data,
+ data=[{value, Result},
+ {suite, "ssl"}, {name, "Seq setup 1.3"}]}),
+ ok.
+
setup_concurrent(Config) ->
Server = proplists:get_value(server_node, Config),
Server =/= undefined orelse error(no_server),
- {ok, Result} = do_test(ssl, setup_connection, ?COUNT, 100, Server),
+ Cfg = [{version, 'tlsv1.2'}],
+ {ok, Result} = do_test(ssl, {setup_connection,Cfg}, ?COUNT, 100, Server),
ct_event:notify(#event{name = benchmark_data,
data=[{value, Result},
{suite, "ssl"}, {name, "Concurrent setup"}]}),
ok.
-payload_simple(Config) ->
+setup_concurrent_noreuse(Config) ->
Server = proplists:get_value(server_node, Config),
Server =/= undefined orelse error(no_server),
- {ok, Result} = do_test(ssl, payload, ?COUNT*300, 10, Server),
+ Cfg = [{version, 'tlsv1.2'}, no_reuse],
+ {ok, Result} = do_test(ssl, {setup_connection,Cfg}, ?COUNT, 100, Server),
ct_event:notify(#event{name = benchmark_data,
data=[{value, Result},
- {suite, "ssl"}, {name, "Payload simple"}]}),
+ {suite, "ssl"}, {name, "Conc setup 1.2 no session"}]}),
ok.
-basic_pem_cache(_Config) ->
- do_test(ssl, pem_cache, 10, 5, node()).
-
-use_pem_cache(_Config) ->
- {ok, Result} = do_test(ssl, pem_cache, 100, 500, node()),
+setup_concurrent_13(Config) ->
+ Server = proplists:get_value(server_node, Config),
+ Server =/= undefined orelse error(no_server),
+ Cfg = [{version, 'tlsv1.3'}],
+ {ok, Result} = do_test(ssl, {setup_connection,Cfg}, ?COUNT, 100, Server),
ct_event:notify(#event{name = benchmark_data,
- data=[{value, Result},
- {suite, "ssl"}, {name, "Use PEM cache"}]}).
+ data=[{value, Result},
+ {suite, "ssl"}, {name, "Conc setup 1.3"}]}),
+ ok.
-bypass_pem_cache(_Config) ->
- {ok, Result} = do_test(ssl, pem_cache, 100, 500, node()),
+payload(Config) ->
+ Server = proplists:get_value(server_node, Config),
+ Server =/= undefined orelse error(no_server),
+ Cfg = [{version, 'tlsv1.2'}],
+ {ok, Result} = do_test(ssl, {payload, Cfg}, ?COUNT*300, 10, Server),
ct_event:notify(#event{name = benchmark_data,
- data=[{value, Result},
- {suite, "ssl"}, {name, "Bypass PEM cache"}]}).
+ data=[{value, Result},
+ {suite, "ssl"}, {name, "Payload simple"}]}),
+ ok.
+payload_13(Config) ->
+ Server = proplists:get_value(server_node, Config),
+ Server =/= undefined orelse error(no_server),
+ Cfg = [{version, 'tlsv1.3'}],
+ {ok, Result} = do_test(ssl, {payload, Cfg}, ?COUNT*300, 10, Server),
+ ct_event:notify(#event{name = benchmark_data,
+ data=[{value, Result},
+ {suite, "ssl"}, {name, "Payload 1.3"}]}),
+ ok.
+%%--------------------------------------------------------------------
+%% ------------------------------------------------
+%%--------------------------------------------------------------------
ssl() ->
test(ssl, ?COUNT).
test(Type, Count) ->
Server = ssl_bench_test_lib:setup(perf_server),
- (do_test(Type, setup_connection, Count * 20, 1, Server)),
- (do_test(Type, setup_connection, Count, 100, Server)),
- (do_test(Type, payload, Count*300, 10, Server)),
+ (do_test(Type, {setup_connection, [{version, 'tlsv1.2'}]}, Count * 20, 1, Server)),
+ (do_test(Type, {setup_connection, [{version, 'tlsv1.3'}]}, Count * 20, 1, Server)),
+ (do_test(Type, {setup_connection, [{version, 'tlsv1.2'}, no_reuse]}, Count * 20, 1, Server)),
+ (do_test(Type, {setup_connection, [{version, 'tlsv1.2'}]}, Count, 100, Server)),
+ (do_test(Type, {payload, [{version, 'tlsv1.2'}]}, Count*300, 10, Server)),
ok.
-do_test(Type, TC, Loop, ParallellConnections, Server) ->
+do_test(Type, {Func, _}=TC, Loop, ParallellConnections, Server) ->
_ = ssl:stop(),
{ok, _} = ensure_all_started(ssl, []),
-
+ Certs = cert_data(),
{ok, {SPid, Host, Port}} = rpc:call(Server, ?MODULE, setup_server_init,
- [Type, TC, Loop, ParallellConnections]),
+ [Type, TC, Loop, ParallellConnections, Certs]),
link(SPid),
Me = self(),
Test = fun(Id) ->
- CData = client_init(Me, Type, TC, Host, Port),
+ CData = client_init(Me, Type, TC, Host, Port, Certs),
receive
go ->
?FPROF_CLIENT andalso Id =:= 1 andalso
start_profile(fprof, [self(),new]),
?EPROF_CLIENT andalso Id =:= 1 andalso
start_profile(eprof, [ssl_connection_sup, ssl_manager]),
- ok = ?MODULE:TC(Loop, Type, CData),
+ ok = ?MODULE:Func(Loop, Type, CData),
?FPROF_CLIENT andalso Id =:= 1 andalso
stop_profile(fprof, "test_connection_client_res.fprof"),
?EPROF_CLIENT andalso Id =:= 1 andalso
@@ -177,7 +246,7 @@ do_test(Type, TC, Loop, ParallellConnections, Server) ->
TestPerSecond = case TimeInMicro of
0 ->
undefined;
- _ ->
+ _ ->
1000000 * TotalTests div TimeInMicro
end,
io:format("TC ~p ~p ~p ~p 1/s~n", [TC, Type, ParallellConnections, TestPerSecond]),
@@ -185,8 +254,8 @@ do_test(Type, TC, Loop, ParallellConnections, Server) ->
SPid ! quit,
{ok, TestPerSecond}.
-server_init(ssl, setup_connection, _, _, Server) ->
- {ok, LSocket} = ssl:listen(0, ssl_opts(listen)),
+server_init(ssl, {setup_connection, Opts}, _, _, Server, Certs) ->
+ {ok, LSocket} = ssl:listen(0, ssl_opts(listen, Opts, Certs)),
{ok, {_Host, Port}} = ssl:sockname(LSocket),
{ok, Host} = inet:gethostname(),
?FPROF_SERVER andalso start_profile(fprof, [whereis(ssl_manager), new]),
@@ -198,20 +267,8 @@ server_init(ssl, setup_connection, _, _, Server) ->
ssl:close(Socket)
end,
setup_server_connection(LSocket, Test);
-server_init(ssl, payload, Loop, _, Server) ->
- {ok, LSocket} = ssl:listen(0, ssl_opts(listen)),
- {ok, {_Host, Port}} = ssl:sockname(LSocket),
- {ok, Host} = inet:gethostname(),
- Server ! {self(), {init, Host, Port}},
- Test = fun(TSocket) ->
- {ok, Socket} = ssl:handshake(TSocket),
- Size = byte_size(msg()),
- server_echo(Socket, Size, Loop),
- ssl:close(Socket)
- end,
- setup_server_connection(LSocket, Test);
-server_init(ssl, pem_cache, Loop, _, Server) ->
- {ok, LSocket} = ssl:listen(0, ssl_opts(listen_der)),
+server_init(ssl, {payload, Opts}, Loop, _, Server, Certs) ->
+ {ok, LSocket} = ssl:listen(0, ssl_opts(listen, Opts, Certs)),
{ok, {_Host, Port}} = ssl:sockname(LSocket),
{ok, Host} = inet:gethostname(),
Server ! {self(), {init, Host, Port}},
@@ -223,24 +280,19 @@ server_init(ssl, pem_cache, Loop, _, Server) ->
end,
setup_server_connection(LSocket, Test);
-server_init(Type, Tc, _, _, Server) ->
+server_init(Type, Tc, _, _, Server, _) ->
io:format("No server init code for ~p ~p~n",[Type, Tc]),
Server ! {self(), no_init}.
-client_init(Master, ssl, setup_connection, Host, Port) ->
+client_init(Master, ssl, {setup_connection, Opts}, Host, Port, Certs) ->
Master ! {self(), init},
- {Host, Port, ssl_opts(connect)};
-client_init(Master, ssl, payload, Host, Port) ->
- {ok, Sock} = ssl:connect(Host, Port, ssl_opts(connect)),
- Master ! {self(), init},
- Size = byte_size(msg()),
- {Sock, Size};
-client_init(Master, ssl, pem_cache, Host, Port) ->
- {ok, Sock} = ssl:connect(Host, Port, ssl_opts(connect_der)),
+ {Host, Port, ssl_opts(connect, Opts, Certs)};
+client_init(Master, ssl, {payload, Opts}, Host, Port, Certs) ->
+ {ok, Sock} = ssl:connect(Host, Port, ssl_opts(connect, Opts, Certs)),
Master ! {self(), init},
Size = byte_size(msg()),
{Sock, Size};
-client_init(_Me, Type, Tc, Host, Port) ->
+client_init(_Me, Type, Tc, Host, Port, _) ->
io:format("No client init code for ~p ~p~n",[Type, Tc]),
{Host, Port}.
@@ -282,13 +334,6 @@ payload(Loop, ssl, D = {Socket, Size}) when Loop > 0 ->
payload(_, _, {Socket, _}) ->
ssl:close(Socket).
-pem_cache(N, ssl, Data = {Socket, Size}) when N > 0 ->
- ok = ssl:send(Socket, msg()),
- {ok, _} = ssl:recv(Socket, Size),
- pem_cache(N-1, ssl, Data);
-pem_cache(_, _, {Socket, _}) ->
- ssl:close(Socket).
-
msg() ->
<<"Hello",
0:(512*8),
@@ -308,11 +353,11 @@ ensure_all_started(App, Ack) ->
{ok, Ack}
end.
-setup_server_init(Type, Tc, Loop, PC) ->
+setup_server_init(Type, Tc, Loop, PC, Certs) ->
_ = ssl:stop(),
{ok, _} = ensure_all_started(ssl, []),
Me = self(),
- Pid = spawn_link(fun() -> server_init(Type, Tc, Loop, PC, Me) end),
+ Pid = spawn_link(fun() -> server_init(Type, Tc, Loop, PC, Me, Certs) end),
Res = receive
{Pid, {init, Host, Port}} -> {ok, {Pid, Host, Port}};
{Pid, Error} -> {error, Error}
@@ -355,52 +400,37 @@ stop_profile(fprof, File) ->
fprof:stop(),
ok.
-ssl_opts(listen) ->
- [{backlog, 500} | ssl_opts("server")];
-ssl_opts(connect) ->
- [{verify, verify_peer} | ssl_opts("client")];
-ssl_opts(listen_der) ->
- [{backlog, 500} | ssl_opts("server_der")];
-ssl_opts(connect_der) ->
- [{verify, verify_peer} | ssl_opts("client_der")];
-ssl_opts(Role) ->
- CertData = cert_data(Role),
- Opts = [{active, false},
- {depth, 2},
- {reuseaddr, true},
- {mode,binary},
- {nodelay, true},
- {ciphers, [{dhe_rsa,aes_256_cbc,sha}]}
- |CertData],
- case Role of
- "client" ++ _ ->
- [{server_name_indication, disable} | Opts];
- "server" ++ _ ->
- Opts
- end.
-
-cert_data(Der) when Der =:= "server_der"; Der =:= "client_der" ->
- [Role,_] = string:tokens(Der, "_"),
- Dir = filename:join([code:lib_dir(ssl), "examples", "certs", "etc"]),
- {ok, CaCert0} = file:read_file(filename:join([Dir, Role, "cacerts.pem"])),
- {ok, Cert0} = file:read_file(filename:join([Dir, Role, "cert.pem"])),
- {ok, Key0} = file:read_file(filename:join([Dir, Role, "key.pem"])),
- [{_, Cert, _}] = public_key:pem_decode(Cert0),
- CaCert1 = public_key:pem_decode(CaCert0),
- CaCert = [CCert || {_, CCert, _} <- CaCert1],
- [{KeyType, Key, _}] = public_key:pem_decode(Key0),
- [{cert, Cert},
- {cacerts, CaCert},
- {key, {KeyType, Key}}];
-cert_data(Role) ->
- Dir = filename:join([code:lib_dir(ssl), "examples", "certs", "etc"]),
- [{cacertfile, filename:join([Dir, Role, "cacerts.pem"])},
- {certfile, filename:join([Dir, Role, "cert.pem"])},
- {keyfile, filename:join([Dir, Role, "key.pem"])}].
-
-bypass_pem_cache_supported() ->
- %% This function is currently critical to support cache bypass
- %% and did not exist in prior versions.
- catch ssl_pkix_db:module_info(), % ensure module is loaded
- erlang:function_exported(ssl_pkix_db, extract_trusted_certs, 1).
+ssl_opts(listen, Opts, Certs) ->
+ [{backlog, 500} | ssl_opts(server_config, Opts, Certs)];
+ssl_opts(connect, Opts, Certs) ->
+ [{verify, verify_peer} | ssl_opts(client_config, Opts, Certs)];
+ssl_opts(Role, TCOpts, Certs) ->
+ CertData = maps:get(Role, Certs),
+ {Version, KeyEx} =
+ case proplists:get_value(version, TCOpts) of
+ 'tlsv1.2' = V -> {V, ecdhe_ecdsa};
+ 'tlsv1.3' = V -> {V, any}
+ end,
+ Opts0 = [{active, false},
+ {depth, 2},
+ {reuseaddr, true},
+ {mode,binary},
+ {nodelay, true},
+ {versions, [Version]},
+ {ciphers, [ #{key_exchange => KeyEx, cipher => aes_128_gcm,
+ mac => aead, prf => sha256}
+ ]}
+ | CertData ],
+ Opts1 = case Role of
+ client_config -> [{server_name_indication, disable} | Opts0];
+ server_config -> Opts0
+ end,
+ Opts = case proplists:get_value(no_reuse, TCOpts) of
+ true -> [{reuse_sessions, false}|Opts1];
+ _ -> Opts1
+ end,
+ Opts.
+
+cert_data() ->
+ ssl_test_lib:make_cert_chains_der(ecdhe_ecdsa, []).
diff --git a/lib/ssl/test/ssl_bench_test_lib.erl b/lib/ssl/test/ssl_bench_test_lib.erl
index 15243ce983..74ab142993 100644
--- a/lib/ssl/test/ssl_bench_test_lib.erl
+++ b/lib/ssl/test/ssl_bench_test_lib.erl
@@ -19,6 +19,8 @@
%%
-module(ssl_bench_test_lib).
+-behaviour(ct_suite).
+
%% API
-export([setup/1]).
@@ -41,13 +43,13 @@ setup(Name) ->
lists:append([" -pa " ++ P || [P] <- PaPaths]);
_ -> []
end,
- %% io:format("Slave args: ~p~n",[SlaveArgs]),
+ %% ct:pal("Slave args: ~p~n",[SlaveArgs]),
Prog =
case os:find_executable("erl") of
false -> "erl";
P -> P
end,
- io:format("Prog = ~p~n", [Prog]),
+ ct:pal("Prog = ~p~n", [Prog]),
case net_adm:ping(Node) of
pong -> ok;
@@ -58,13 +60,13 @@ setup(Name) ->
Path = code:get_path(),
true = rpc:call(Node, code, set_path, [Path]),
ok = rpc:call(Node, ?MODULE, setup_server, [node()]),
- io:format("Client (~p) using ~ts~n",[node(), code:which(ssl)]),
+ ct:pal("Client (~p) using ~ts~n",[node(), code:which(ssl)]),
(Node =:= node()) andalso restrict_schedulers(client),
Node.
setup_server(ClientNode) ->
(ClientNode =:= node()) andalso restrict_schedulers(server),
- io:format("Server (~p) using ~ts~n",[node(), code:which(ssl)]),
+ ct:pal("Server (~p) using ~ts~n",[node(), code:which(ssl)]),
ok.
restrict_schedulers(Type) ->
diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl
index d5ca9bcf02..a142115b55 100644
--- a/lib/ssl/test/ssl_cert_SUITE.erl
+++ b/lib/ssl/test/ssl_cert_SUITE.erl
@@ -21,11 +21,101 @@
%%
-module(ssl_cert_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([no_auth/0,
+ no_auth/1,
+ auth/0,
+ auth/1,
+ client_auth_empty_cert_accepted/0,
+ client_auth_empty_cert_accepted/1,
+ client_auth_empty_cert_rejected/0,
+ client_auth_empty_cert_rejected/1,
+ client_auth_partial_chain/0,
+ client_auth_partial_chain/1,
+ client_auth_allow_partial_chain/0,
+ client_auth_allow_partial_chain/1,
+ client_auth_do_not_allow_partial_chain/0,
+ client_auth_do_not_allow_partial_chain/1,
+ client_auth_partial_chain_fun_fail/0,
+ client_auth_partial_chain_fun_fail/1,
+ client_auth_sni/0,
+ client_auth_sni/1,
+ client_auth_seelfsigned_peer/0,
+ client_auth_seelfsigned_peer/1,
+ missing_root_cert_no_auth/0,
+ missing_root_cert_no_auth/1,
+ missing_root_cert_auth/0,
+ missing_root_cert_auth/1,
+ missing_root_cert_auth_user_verify_fun_accept/0,
+ missing_root_cert_auth_user_verify_fun_accept/1,
+ missing_root_cert_auth_user_verify_fun_reject/0,
+ missing_root_cert_auth_user_verify_fun_reject/1,
+ verify_fun_always_run_client/0,
+ verify_fun_always_run_client/1,
+ verify_fun_always_run_server/0,
+ verify_fun_always_run_server/1,
+ incomplete_chain_auth/0,
+ incomplete_chain_auth/1,
+ invalid_signature_client/0,
+ invalid_signature_client/1,
+ invalid_signature_server/0,
+ invalid_signature_server/1,
+ critical_extension_auth/0,
+ critical_extension_auth/1,
+ critical_extension_client_auth/0,
+ critical_extension_client_auth/1,
+ critical_extension_no_auth/0,
+ critical_extension_no_auth/1,
+ extended_key_usage_auth/0,
+ extended_key_usage_auth/1,
+ extended_key_usage_client_auth/0,
+ extended_key_usage_client_auth/1,
+ cert_expired/0,
+ cert_expired/1,
+ no_auth_key_identifier_ext/0,
+ no_auth_key_identifier_ext/1,
+ no_auth_key_identifier_ext_keyEncipherment/0,
+ no_auth_key_identifier_ext_keyEncipherment/1,
+ unsupported_sign_algo_client_auth/0,
+ unsupported_sign_algo_client_auth/1,
+ unsupported_sign_algo_cert_client_auth/0,
+ unsupported_sign_algo_cert_client_auth/1,
+ longer_chain/0,
+ longer_chain/1,
+ duplicate_chain/0,
+ duplicate_chain/1,
+ key_auth_ext_sign_only/0,
+ key_auth_ext_sign_only/1,
+ hello_retry_request/0,
+ hello_retry_request/1,
+ custom_groups/0,
+ custom_groups/1,
+ hello_retry_client_auth/0,
+ hello_retry_client_auth/1,
+ hello_retry_client_auth_empty_cert_accepted/0,
+ hello_retry_client_auth_empty_cert_accepted/1,
+ hello_retry_client_auth_empty_cert_rejected/0,
+ hello_retry_client_auth_empty_cert_rejected/1,
+ basic_rsa_1024/0,
+ basic_rsa_1024/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -36,7 +126,6 @@ all() ->
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
].
@@ -44,16 +133,20 @@ all() ->
groups() ->
[
{'tlsv1.3', [], tls_1_3_protocol_groups()},
- {'tlsv1.2', [], tls_1_2_protocol_groups()},
+ {'tlsv1.2', [], tls_1_2_protocol_groups() -- [{group,rsa_pss_pss}]},
{'tlsv1.1', [], ssl_protocol_groups()},
{'tlsv1', [], ssl_protocol_groups()},
- {'sslv3', [], ssl_protocol_groups()},
- {'dtlsv1.2', [], tls_1_2_protocol_groups()},
+ {'dtlsv1.2', [], tls_1_2_protocol_groups() -- [{group,rsa_pss_rsae}, {group,rsa_pss_pss}]},
{'dtlsv1', [], ssl_protocol_groups()},
{rsa, [], all_version_tests() ++ rsa_tests() ++ pre_tls_1_3_rsa_tests()},
{ecdsa, [], all_version_tests()},
{dsa, [], all_version_tests()},
- {rsa_1_3, [], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()},
+ {rsa_1_3, [], all_version_tests() ++ rsa_tests() ++
+ tls_1_3_tests() ++ tls_1_3_rsa_tests() ++ [basic_rsa_1024]},
+ {rsa_pss_rsae, [], all_version_tests() ++ rsa_tests()},
+ {rsa_pss_rsae_1_3, [], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()},
+ {rsa_pss_pss, [], all_version_tests() ++ rsa_tests()},
+ {rsa_pss_pss_1_3, [], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()},
{ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()}
].
@@ -64,11 +157,17 @@ ssl_protocol_groups() ->
tls_1_2_protocol_groups() ->
[{group, rsa},
{group, ecdsa},
- {group, dsa}].
+ {group, dsa},
+ {group, rsa_pss_rsae},
+ {group, rsa_pss_pss}
+ ].
tls_1_3_protocol_groups() ->
[{group, rsa_1_3},
- {group, ecdsa_1_3}].
+ {group, ecdsa_1_3},
+ {group, rsa_pss_rsae_1_3},
+ {group, rsa_pss_pss_1_3}
+ ].
tls_1_3_tests() ->
[
@@ -105,6 +204,8 @@ all_version_tests() ->
client_auth_allow_partial_chain,
client_auth_do_not_allow_partial_chain,
client_auth_partial_chain_fun_fail,
+ client_auth_sni,
+ client_auth_seelfsigned_peer,
missing_root_cert_no_auth,
missing_root_cert_auth,
missing_root_cert_auth_user_verify_fun_accept,
@@ -120,7 +221,6 @@ all_version_tests() ->
extended_key_usage_auth,
extended_key_usage_client_auth,
cert_expired,
- client_auth_once,
no_auth_key_identifier_ext,
no_auth_key_identifier_ext_keyEncipherment
].
@@ -140,9 +240,22 @@ end_per_suite(_Config) ->
application:unload(ssl),
application:stop(crypto).
-init_per_group(Group, Config0) when Group == rsa;
- Group == rsa_1_3 ->
- Config = ssl_test_lib:make_rsa_cert(Config0),
+
+init_per_group(GroupName, Config) ->
+ case ssl_test_lib:is_protocol_version(GroupName) of
+ true ->
+ ssl_test_lib:init_per_group(GroupName,
+ [{client_type, erlang},
+ {server_type, erlang},
+ {version, GroupName} | Config]);
+ false ->
+ do_init_per_group(GroupName, Config)
+ end.
+
+do_init_per_group(Group, Config0) when Group == rsa;
+ Group == rsa_1_3 ->
+ Config1 = ssl_test_lib:make_rsa_cert(Config0),
+ Config = ssl_test_lib:make_rsa_1024_cert(Config1),
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
[{cert_key_alg, rsa} |
@@ -151,7 +264,31 @@ init_per_group(Group, Config0) when Group == rsa;
{server_cert_opts, SOpts} |
lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
-init_per_group(Group, Config0) when Group == ecdsa;
+
+do_init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
+ Alg == rsa_pss_pss;
+ Alg == rsa_pss_rsae_1_3;
+ Alg == rsa_pss_pss_1_3 ->
+
+ Supports = crypto:supports(),
+ RSAOpts = proplists:get_value(rsa_opts, Supports),
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
+ andalso lists:member(rsa_mgf1_md, RSAOpts) of
+ true ->
+ #{client_config := COpts,
+ server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(rsa_alg(Alg), [], Config, ""),
+ [{cert_key_alg, rsa_alg(Alg)} |
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
+ lists:delete(client_cert_opts, Config))])];
+ false ->
+ {skip, "Missing EC crypto support"}
+ end;
+do_init_per_group(Group, Config0) when Group == ecdsa;
Group == ecdsa_1_3 ->
PKAlg = crypto:supports(public_keys),
@@ -171,7 +308,7 @@ init_per_group(Group, Config0) when Group == ecdsa;
{skip, "Missing EC crypto support"}
end;
-init_per_group(Group, Config0) when Group == dsa ->
+do_init_per_group(Group, Config0) when Group == dsa ->
PKAlg = crypto:supports(public_keys),
case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of
true ->
@@ -186,30 +323,12 @@ init_per_group(Group, Config0) when Group == dsa ->
lists:delete(client_cert_opts, Config))])];
false ->
{skip, "Missing DSS crypto support"}
- end;
-init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- [{client_type, erlang},
- {server_type, erlang}, {version, GroupName}
- | ssl_test_lib:init_tls_version(GroupName, Config)];
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ end;
+do_init_per_group(Group, Config) ->
+ Config.
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
@@ -266,6 +385,17 @@ client_auth_partial_chain_fun_fail(Config) when is_list(Config) ->
ssl_cert_tests:client_auth_partial_chain_fun_fail(Config).
%%--------------------------------------------------------------------
+client_auth_sni() ->
+ ssl_cert_tests:client_auth_sni().
+client_auth_sni(Config) when is_list(Config) ->
+ ssl_cert_tests:client_auth_sni(Config).
+%%--------------------------------------------------------------------
+client_auth_seelfsigned_peer() ->
+ ssl_cert_tests:client_auth_seelfsigned_peer().
+client_auth_seelfsigned_peer(Config) when is_list(Config) ->
+ ssl_cert_tests:client_auth_seelfsigned_peer(Config).
+
+%%--------------------------------------------------------------------
missing_root_cert_no_auth() ->
ssl_cert_tests:missing_root_cert_no_auth().
missing_root_cert_no_auth(Config) when is_list(Config) ->
@@ -277,10 +407,11 @@ missing_root_cert_auth() ->
missing_root_cert_auth(Config) when is_list(Config) ->
ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_cert_opts, Config)),
{ClientNode, ServerNode, _} = ssl_test_lib:run_where(Config),
+ Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
- {options, [{verify, verify_peer}
- | ServerOpts]}]),
+ {options, no_reuse(n_version(Version)) ++ [{verify, verify_peer}
+ | ServerOpts]}]),
ssl_test_lib:check_result(Server, {error, {options, {cacertfile, ""}}}),
@@ -384,11 +515,12 @@ verify_fun_always_run_client(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib,
no_result, []}},
- {options, ServerOpts}]),
+ {options, no_reuse(n_version(Version)) ++ ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
%% If user verify fun is called correctly we fail the connection.
@@ -438,14 +570,15 @@ verify_fun_always_run_server(Config) when is_list(Config) ->
{valid, UserState}
end, [0]},
+ Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib,
no_result, []}},
{options,
- [{verify, verify_peer},
- {verify_fun, FunAndState} |
- ServerOpts]}]),
+ no_reuse(n_version(Version)) ++ [{verify, verify_peer},
+ {verify_fun, FunAndState} |
+ ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
@@ -486,12 +619,12 @@ critical_extension_auth(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
+ Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server_error(
[{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
- {options, [{verify, verify_none} | ServerOpts]}]),
+ {options, no_reuse(n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error(
[{node, ClientNode}, {port, Port},
@@ -518,12 +651,12 @@ critical_extension_client_auth(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
+ Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server_error(
[{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
- {options, [{verify, verify_peer} | ServerOpts]}]),
+ {options, no_reuse(n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error(
[{node, ClientNode}, {port, Port},
@@ -572,11 +705,11 @@ extended_key_usage_auth(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
+ Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, [{verify, verify_none} | ServerOpts]}]),
+ {options, no_reuse(n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -607,11 +740,11 @@ extended_key_usage_client_auth(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
+ Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, [{verify, verify_peer} | ServerOpts]}]),
+ {options, no_reuse(n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -644,10 +777,10 @@ cert_expired(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
+ Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
- {options, ServerOpts}]),
+ {options, no_reuse(n_version(Version)) ++ ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -657,41 +790,6 @@ cert_expired(Config) when is_list(Config) ->
ssl_test_lib:check_client_alert(Server, Client, certificate_expired).
%%--------------------------------------------------------------------
-client_auth_once() ->
- [{doc,"Test server option verify_client_once"}].
-
-client_auth_once(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config),
-
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, [{verify, verify_peer},
- {verify_client_once, true}
- | ServerOpts]}]),
- Port = ssl_test_lib:inet_port(Server),
- Client0 = 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, Client0, ok),
- Server ! {listen, {mfa, {ssl_test_lib, send_recv_result_active, []}}},
- ssl_test_lib:close(Client0),
- Client1 = 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(Client1, ok, Server, ok),
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client1).
-
-%%--------------------------------------------------------------------
no_auth_key_identifier_ext() ->
[{doc, "Test cert that does not have authorityKeyIdentifier extension"}].
@@ -774,6 +872,62 @@ longer_chain(Config) when is_list(Config) ->
proplists:delete(cacerts, ClientOpts0)], Config),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
+duplicate_chain() ->
+ [{doc, "Manual test of chain with duplicate entries"}].
+duplicate_chain(Config)
+ when is_list(Config) ->
+ Key1 = ssl_test_lib:hardcode_rsa_key(1),
+ Key2 = ssl_test_lib:hardcode_rsa_key(2),
+ Key3 = ssl_test_lib:hardcode_rsa_key(3),
+ Key4 = ssl_test_lib:hardcode_rsa_key(4),
+ Key5 = ssl_test_lib:hardcode_rsa_key(5),
+
+ #{server_config := ServerOpts0, client_config := ClientOpts0} =
+ public_key:pkix_test_data(#{server_chain => #{root => [{key, Key1}],
+ peer => [{key, Key5}]},
+ client_chain => #{root => [{key, Key3}],
+ intermediates => [[{key, Key2}], [{key, Key3}]],
+ peer => [{key, Key1}]}}),
+
+ #{client_config := ClientOptsNew} =
+ public_key:pkix_test_data(#{server_chain => #{root => [{key, Key1}],
+ peer => [{key, Key5}]},
+ client_chain => #{root => [{key, Key4}],
+ intermediates => [[{key, Key2}], [{key, Key1}]],
+ peer => [{key, Key1}]}}),
+
+ ServerCas0 = proplists:get_value(cacerts, ServerOpts0),
+ ClientCas0 = proplists:get_value(cacerts, ClientOpts0),
+
+ {[Peer,CI1,CI2,CROld], CROld} = chain_and_root(ClientOpts0),
+ {[_Peer,CI1New,CI2New,CRNew], CRNew} = chain_and_root(ClientOptsNew),
+
+ ServerCas = [CRNew|ServerCas0 -- [CROld]],
+ ServerOpts = ssl_test_lib:ssl_options([{verify, verify_peer} |
+ lists:keyreplace(cacerts, 1, ServerOpts0, {cacerts, ServerCas})],
+ Config),
+ ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer} |
+ lists:keyreplace(cacerts, 1,
+ lists:keyreplace(cert, 1, ClientOpts0,
+ {cert, [Peer,CI1New,CI2New,CI1,CI2,CRNew,CROld]}),
+ {cacerts, ClientCas0})],
+ Config),
+ ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config),
+ ClientOpts2 = ssl_test_lib:ssl_options([{verify, verify_peer} |
+ lists:keyreplace(cacerts, 1,
+ lists:keyreplace(cert, 1, ClientOpts0,
+ {cert, [Peer,CI1,CI1New,CI2,CI2New,CROld,CRNew]}),
+ {cacerts, ClientCas0})],
+ Config),
+ ssl_test_lib:basic_test(ClientOpts2, ServerOpts, Config),
+ ok.
+
+chain_and_root(Config) ->
+ OwnCert = proplists:get_value(cert, Config),
+ {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, proplists:get_value(cacerts, Config)}),
+ {ok, Root, Chain} = ssl_certificate:certificate_chain(OwnCert, ets:new(foo, []), ExtractedCAs, []),
+ {Chain, Root}.
+
%%--------------------------------------------------------------------
%% TLS 1.3 Test cases -----------------------------------------------
%%--------------------------------------------------------------------
@@ -817,7 +971,7 @@ unsupported_sign_algo_cert_client_auth(Config) ->
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
{verify, verify_peer},
- {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]},
+ {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256, rsa_pss_pss_sha256]},
%% Skip rsa_pkcs1_sha256!
{signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]},
{fail_if_no_peer_cert, true}|ServerOpts0],
@@ -896,27 +1050,40 @@ hello_retry_client_auth_empty_cert_rejected(Config) ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required).
%%--------------------------------------------------------------------
+basic_rsa_1024() ->
+ [{doc, "TLS 1.3 (Basic): Test if connection can be established using 1024 bits RSA keys in certificates."}].
+
+basic_rsa_1024(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_1024_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_1024_opts, Config),
+ ServerOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+ ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ServerOpts = [{verify, verify_peer},
+ {fail_if_no_peer_cert, true} | ServerOpts1],
+ ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
+
+%%--------------------------------------------------------------------
%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
-two_digits_str(N) when N < 10 ->
- lists:flatten(io_lib:format("0~p", [N]));
-two_digits_str(N) ->
- lists:flatten(io_lib:format("~p", [N])).
-
-delete_authority_key_extension([], Acc) ->
- lists:reverse(Acc);
-delete_authority_key_extension([#'Extension'{extnID = ?'id-ce-authorityKeyIdentifier'} | Rest],
- Acc) ->
- delete_authority_key_extension(Rest, Acc);
-delete_authority_key_extension([Head | Rest], Acc) ->
- delete_authority_key_extension(Rest, [Head | Acc]).
-
-n_version(Version) when Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == 'tlsv1';
- Version == 'sslv3'
- ->
+n_version(Version) when
+ Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == 'tlsv1';
+ Version == 'sslv3' ->
tls_record:protocol_version(Version);
n_version(Version) when Version == 'dtlsv1.2';
Version == 'dtlsv1' ->
dtls_record:protocol_version(Version).
+
+rsa_alg(rsa_pss_rsae_1_3) ->
+ rsa_pss_rsae;
+rsa_alg(rsa_pss_pss_1_3) ->
+ rsa_pss_pss;
+rsa_alg(Atom) ->
+ Atom.
+
+no_reuse({3, N}) when N >= 4 ->
+ [];
+no_reuse(_) ->
+ [{reuse_sessions, false}].
diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl
index c88daa2185..d9d535106a 100644
--- a/lib/ssl/test/ssl_cert_tests.erl
+++ b/lib/ssl/test/ssl_cert_tests.erl
@@ -21,11 +21,55 @@
%%
-module(ssl_cert_tests).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("public_key/include/public_key.hrl").
+%% Test cases
+-export([no_auth/0,
+ no_auth/1,
+ auth/0,
+ auth/1,
+ client_auth_empty_cert_accepted/0,
+ client_auth_empty_cert_accepted/1,
+ client_auth_empty_cert_rejected/0,
+ client_auth_empty_cert_rejected/1,
+ client_auth_partial_chain/0,
+ client_auth_partial_chain/1,
+ client_auth_allow_partial_chain/0,
+ client_auth_allow_partial_chain/1,
+ client_auth_do_not_allow_partial_chain/0,
+ client_auth_do_not_allow_partial_chain/1,
+ client_auth_partial_chain_fun_fail/0,
+ client_auth_partial_chain_fun_fail/1,
+ client_auth_sni/0,
+ client_auth_sni/1,
+ client_auth_seelfsigned_peer/0,
+ client_auth_seelfsigned_peer/1,
+ missing_root_cert_no_auth/0,
+ missing_root_cert_no_auth/1,
+ invalid_signature_client/0,
+ invalid_signature_client/1,
+ invalid_signature_server/0,
+ invalid_signature_server/1,
+ unsupported_sign_algo_client_auth/0,
+ unsupported_sign_algo_client_auth/1,
+ unsupported_sign_algo_cert_client_auth/0,
+ unsupported_sign_algo_cert_client_auth/1,
+ hello_retry_request/0,
+ hello_retry_request/1,
+ custom_groups/0,
+ custom_groups/1,
+ hello_retry_client_auth/0,
+ hello_retry_client_auth/1,
+ hello_retry_client_auth_empty_cert_accepted/0,
+ hello_retry_client_auth_empty_cert_accepted/1,
+ hello_retry_client_auth_empty_cert_rejected/0,
+ hello_retry_client_auth_empty_cert_rejected/1
+ ]).
+
+-export([test_ciphers/2, openssl_ciphers/0]).
+
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
@@ -162,6 +206,51 @@ client_auth_partial_chain_fun_fail(Config) when is_list(Config) ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca).
%%--------------------------------------------------------------------
+client_auth_sni() ->
+ [{doc, "Check that sni check works with user verify_fun"}].
+client_auth_sni(Config) when is_list(Config) ->
+ ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
+
+ FunAndState = {fun(valid_peer, {bad_cert, unknown_ca}, UserState) ->
+ {valid_peer, UserState};
+ (_,{bad_cert, _} = Reason, _) ->
+ {fail, Reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end, []},
+
+ ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
+ ClientOpts = [{verify, verify_peer}, {verify_fun, FunAndState
+ }, {server_name_indication, "localhost"} | ClientOpts0],
+
+ {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)),
+ [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs),
+
+ ServerOpts = [{cacerts, [IntermidiateCA]} |
+ proplists:delete(cacertfile, ServerOpts0)],
+ %% Basic test if hostname check is not performed the connection will succeed
+ ssl_test_lib:basic_alert(ClientOpts, ServerOpts0, Config, handshake_failure),
+ %% Also test that user verify_fun is run.
+ %% If user verify fun is not used the ALERT will be unknown_ca
+ ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, handshake_failure).
+
+%%--------------------------------------------------------------------
+client_auth_seelfsigned_peer() ->
+ [{doc, "Check that selfsigned peer raises alert"}].
+client_auth_seelfsigned_peer(Config) when is_list(Config) ->
+ Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
+ #{cert := Cert,
+ key := Key} = public_key:pkix_test_root_cert("OTP test server ROOT", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {extensions, Ext}]),
+ DerKey = public_key:der_encode('RSAPrivateKey', Key),
+ ssl_test_lib:basic_alert(ssl_test_lib:ssl_options([{verify, verify_peer}, {cacerts , [Cert]}], Config),
+ ssl_test_lib:ssl_options([{cert, Cert},
+ {key, {'RSAPrivateKey', DerKey}}], Config), Config, bad_certificate).
+%%--------------------------------------------------------------------
missing_root_cert_no_auth() ->
[{doc,"Test that the client succeds if the ROOT CA is unknown in verify_none mode"}].
@@ -334,31 +423,6 @@ hello_retry_client_auth_empty_cert_rejected(Config) ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required).
-
-%%--------------------------------------------------------------------
-%% Internal functions -----------------------------------------------
-%%--------------------------------------------------------------------
-
-group_config_custom(Config, ServerOpts, ClientOpts) ->
- case proplists:get_value(client_type, Config) of
- erlang ->
- {[{groups,"X448:P-256:P-384"} | ServerOpts],
- [{supported_groups, [secp384r1, secp256r1, x25519]} | ClientOpts]};
- openssl ->
- {[{supported_groups, [x448, secp256r1, secp384r1]} | ServerOpts],
- [{groups,"P-384:P-256:X25519"} | ClientOpts]}
- end.
-
-group_config(Config, ServerOpts, ClientOpts) ->
- case proplists:get_value(client_type, Config) of
- erlang ->
- {[{groups,"X448:X25519"} | ServerOpts],
- [{supported_groups, [secp256r1, x25519]} | ClientOpts]};
- openssl ->
- {[{supported_groups, [x448, x25519]} | ServerOpts],
- [{groups,"P-256:X25519"} | ClientOpts]}
- end.
-
test_ciphers(_, 'tlsv1.3' = Version) ->
Ciphers = ssl:cipher_suites(default, Version),
ct:log("Version ~p Testing ~p~n", [Version, Ciphers]),
@@ -368,6 +432,17 @@ test_ciphers(_, 'tlsv1.3' = Version) ->
ct:log("Cipher ~p~n", [C]),
lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers)
end, Ciphers);
+test_ciphers(_, Version) when Version == 'dtlsv1';
+ Version == 'dtlsv1.2' ->
+ {_, Minor} = dtls_record:proplists(Version),
+ Ciphers = dtls_v1:suites(Minor),
+ ct:log("Version ~p Testing ~p~n", [Version, Ciphers]),
+ OpenSSLCiphers = openssl_ciphers(),
+ ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]),
+ lists:filter(fun(C) ->
+ ct:log("Cipher ~p~n", [C]),
+ lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers)
+ end, Ciphers);
test_ciphers(Kex, Version) ->
Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, Version),
[{key_exchange, Kex}]),
@@ -384,3 +459,27 @@ test_ciphers(Kex, Version) ->
openssl_ciphers() ->
Str = os:cmd("openssl ciphers"),
string:split(string:strip(Str, right, $\n), ":", all).
+
+%%--------------------------------------------------------------------
+%% Internal functions -----------------------------------------------
+%%--------------------------------------------------------------------
+
+group_config_custom(Config, ServerOpts, ClientOpts) ->
+ case proplists:get_value(client_type, Config) of
+ erlang ->
+ {[{groups,"X448:P-256:P-384"} | ServerOpts],
+ [{supported_groups, [secp384r1, secp256r1, x25519]} | ClientOpts]};
+ openssl ->
+ {[{supported_groups, [x448, secp256r1, secp384r1]} | ServerOpts],
+ [{groups,"P-384:P-256:X25519"} | ClientOpts]}
+ end.
+
+group_config(Config, ServerOpts, ClientOpts) ->
+ case proplists:get_value(client_type, Config) of
+ erlang ->
+ {[{groups,"X448:X25519"} | ServerOpts],
+ [{supported_groups, [secp256r1, x25519]} | ClientOpts]};
+ openssl ->
+ {[{supported_groups, [x448, x25519]} | ServerOpts],
+ [{groups,"P-256:X25519"} | ClientOpts]}
+ end.
diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl
index b8096c5d7a..31e60269f8 100644
--- a/lib/ssl/test/ssl_cipher_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_SUITE.erl
@@ -20,15 +20,30 @@
-module(ssl_cipher_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-
--include("ssl_internal.hrl").
-include("tls_record.hrl").
-include("ssl_cipher.hrl").
--include("ssl_alert.hrl").
+
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([aes_decipher_good/0,
+ aes_decipher_good/1,
+ aes_decipher_fail/0,
+ aes_decipher_fail/1,
+ padding_test/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
diff --git a/lib/ssl/test/ssl_cipher_suite_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
index cd919a2b2f..22dbc3663c 100644
--- a/lib/ssl/test/ssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
@@ -22,10 +22,116 @@
-module(ssl_cipher_suite_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([dhe_rsa_3des_ede_cbc/1,
+ dhe_rsa_aes_128_cbc/1,
+ dhe_rsa_aes_128_gcm/1,
+ dhe_rsa_aes_256_cbc/1,
+ dhe_rsa_aes_256_gcm/1,
+ dhe_rsa_chacha20_poly1305/1,
+ ecdhe_rsa_3des_ede_cbc/1,
+ ecdhe_rsa_rc4_128/1,
+ ecdhe_rsa_aes_128_cbc/1,
+ ecdhe_rsa_aes_128_gcm/1,
+ ecdhe_rsa_aes_256_cbc/1,
+ ecdhe_rsa_aes_256_gcm/1,
+ ecdhe_rsa_chacha20_poly1305/1,
+ ecdhe_ecdsa_rc4_128/1,
+ ecdhe_ecdsa_3des_ede_cbc/1,
+ ecdhe_ecdsa_aes_128_cbc/1,
+ ecdhe_ecdsa_aes_128_gcm/1,
+ ecdhe_ecdsa_aes_256_cbc/1,
+ ecdhe_ecdsa_aes_256_gcm/1,
+ ecdhe_ecdsa_chacha20_poly1305/1,
+ rsa_des_cbc/1,
+ rsa_3des_ede_cbc/1,
+ rsa_aes_128_cbc/1,
+ rsa_aes_256_cbc/1,
+ rsa_aes_128_gcm/1,
+ rsa_aes_256_gcm/1,
+ rsa_rc4_128/1,
+ dhe_dss_des_cbc/1,
+ dhe_dss_3des_ede_cbc/1,
+ dhe_dss_aes_128_cbc/1,
+ dhe_dss_aes_256_cbc/1,
+ dhe_dss_aes_128_gcm/1,
+ dhe_dss_aes_256_gcm/1,
+ srp_rsa_3des_ede_cbc/1,
+ srp_rsa_aes_128_cbc/1,
+ srp_rsa_aes_256_cbc/1,
+ srp_dss_3des_ede_cbc/1,
+ srp_dss_aes_128_cbc/1,
+ srp_dss_aes_256_cbc/1,
+ rsa_psk_3des_ede_cbc/1,
+ rsa_psk_rc4_128/1,
+ rsa_psk_aes_128_cbc/1,
+ rsa_psk_aes_256_cbc/1,
+ dhe_psk_des_cbc/1,
+ dhe_psk_3des_ede_cbc/1,
+ dhe_psk_rc4_128/1,
+ dhe_psk_aes_128_cbc/1,
+ dhe_psk_aes_128_gcm/1,
+ dhe_psk_aes_128_ccm/1,
+ dhe_psk_aes_128_ccm_8/1,
+ dhe_psk_aes_256_cbc/1,
+ dhe_psk_aes_256_gcm/1,
+ dhe_psk_aes_256_ccm/1,
+ dhe_psk_aes_256_ccm_8/1,
+ ecdhe_psk_3des_ede_cbc/1,
+ ecdhe_psk_rc4_128/1,
+ ecdhe_psk_aes_128_cbc/1,
+ ecdhe_psk_aes_128_gcm/1,
+ ecdhe_psk_aes_128_ccm/1,
+ ecdhe_psk_aes_128_ccm_8/1,
+ ecdhe_psk_aes_256_cbc/1,
+ ecdhe_psk_aes_256_gcm/1,
+ srp_anon_3des_ede_cbc/1,
+ srp_anon_aes_128_cbc/1,
+ srp_anon_aes_256_cbc/1,
+ psk_3des_ede_cbc/1,
+ psk_rc4_128/1,
+ psk_aes_128_cbc/1,
+ psk_aes_128_gcm/1,
+ psk_aes_128_ccm/1,
+ psk_aes_128_ccm_8/1,
+ psk_aes_256_cbc/1,
+ psk_aes_256_gcm/1,
+ psk_aes_256_ccm/1,
+ psk_aes_256_ccm_8/1,
+ dh_anon_rc4_128/1,
+ dh_anon_3des_ede_cbc/1,
+ dh_anon_aes_128_cbc/1,
+ dh_anon_aes_128_gcm/1,
+ dh_anon_aes_256_cbc/1,
+ dh_anon_aes_256_gcm/1,
+ ecdh_anon_3des_ede_cbc/1,
+ ecdh_anon_aes_128_cbc/1,
+ ecdh_anon_aes_256_cbc/1,
+ aes_256_gcm_sha384/1,
+ aes_128_gcm_sha256/1,
+ chacha20_poly1305_sha256/1,
+ aes_128_ccm_sha256/1,
+ aes_128_ccm_8_sha256/1,
+ ecdhe_ecdsa_with_aes_128_ccm/1,
+ ecdhe_ecdsa_with_aes_256_ccm/1,
+ ecdhe_ecdsa_with_aes_128_ccm_8/1,
+ ecdhe_ecdsa_with_aes_256_ccm_8/1
+ ]).
+
+-define(TIMEOUT, {seconds, 10}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -36,7 +142,6 @@ all() ->
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
].
@@ -47,12 +152,13 @@ groups() ->
{'tlsv1.2', [], kex()},
{'tlsv1.1', [], kex()},
{'tlsv1', [], kex()},
- {'sslv3', [], ssl3_kex()},
{'dtlsv1.2', [], kex()},
{'dtlsv1', [], kex()},
{dhe_rsa, [],[dhe_rsa_3des_ede_cbc,
dhe_rsa_aes_128_cbc,
+ dhe_rsa_aes_128_gcm,
dhe_rsa_aes_256_cbc,
+ dhe_rsa_aes_256_gcm,
dhe_rsa_chacha20_poly1305
]},
{ecdhe_rsa, [], [ecdhe_rsa_3des_ede_cbc,
@@ -69,7 +175,11 @@ groups() ->
ecdhe_ecdsa_aes_128_gcm,
ecdhe_ecdsa_aes_256_cbc,
ecdhe_ecdsa_aes_256_gcm,
- ecdhe_ecdsa_chacha20_poly1305
+ ecdhe_ecdsa_chacha20_poly1305,
+ ecdhe_ecdsa_with_aes_128_ccm,
+ ecdhe_ecdsa_with_aes_256_ccm,
+ ecdhe_ecdsa_with_aes_128_ccm_8,
+ ecdhe_ecdsa_with_aes_256_ccm_8
]},
{rsa, [], [rsa_3des_ede_cbc,
rsa_aes_128_cbc,
@@ -112,21 +222,26 @@ groups() ->
psk_aes_256_ccm,
psk_aes_256_ccm_8
]},
- {dhe_psk, [], [dhe_psk_3des_ede_cbc,
+ {dhe_psk, [], [dhe_psk_des_cbc,
+ dhe_psk_3des_ede_cbc,
dhe_psk_rc4_128,
dhe_psk_aes_128_cbc,
+ dhe_psk_aes_128_gcm,
dhe_psk_aes_128_ccm,
dhe_psk_aes_128_ccm_8,
dhe_psk_aes_256_cbc,
+ dhe_psk_aes_256_gcm,
dhe_psk_aes_256_ccm,
dhe_psk_aes_256_ccm_8
]},
{ecdhe_psk, [], [ecdhe_psk_3des_ede_cbc,
- ecdhe_psk_rc4_128,
- ecdhe_psk_aes_128_cbc,
- ecdhe_psk_aes_128_ccm,
- ecdhe_psk_aes_128_ccm_8,
- ecdhe_psk_aes_256_cbc
+ ecdhe_psk_rc4_128,
+ ecdhe_psk_aes_128_cbc,
+ ecdhe_psk_aes_128_gcm,
+ ecdhe_psk_aes_128_ccm,
+ ecdhe_psk_aes_128_ccm_8,
+ ecdhe_psk_aes_256_cbc,
+ ecdhe_psk_aes_256_gcm
]}
].
@@ -138,17 +253,13 @@ tls_1_3_cipher_suites() ->
[aes_256_gcm_sha384,
aes_128_gcm_sha256,
chacha20_poly1305_sha256,
- aes_128_ccm_sha256
+ aes_128_ccm_sha256,
+ aes_128_ccm_8_sha256
].
kex() ->
rsa() ++ ecdsa() ++ dss() ++ anonymous().
-
-ssl3_kex() ->
- ssl3_rsa() ++ ssl3_dss() ++ ssl3_anonymous().
-
-
rsa() ->
[{group, dhe_rsa},
{group, ecdhe_rsa},
@@ -157,11 +268,6 @@ rsa() ->
{group, rsa_psk}
].
-ssl3_rsa() ->
- [{group, dhe_rsa},
- {group, rsa}
- ].
-
ecdsa() ->
[{group, ecdhe_ecdsa}].
@@ -169,10 +275,6 @@ dss() ->
[{group, dhe_dss},
{group, srp_dss}].
-ssl3_dss() ->
- [{group, dhe_dss}
- ].
-
anonymous() ->
[{group, dh_anon},
{group, ecdh_anon},
@@ -182,10 +284,6 @@ anonymous() ->
{group, srp_anon}
].
-ssl3_anonymous() ->
- [{group, dh_anon}].
-
-
init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
@@ -199,15 +297,24 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ssl:stop(),
application:stop(crypto).
+init_per_group(GroupName, Config) ->
+ case ssl_test_lib:is_protocol_version(GroupName) of
+ true ->
+ ssl_test_lib:init_per_group(GroupName, [{client_type, erlang},
+ {server_type, erlang},
+ {version, GroupName} | Config]);
+ false ->
+ do_init_per_group(GroupName, Config)
+ end.
-init_per_group(GroupName, Config) when GroupName == ecdhe_1_3_rsa_cert ->
+do_init_per_group(GroupName, Config) when GroupName == ecdhe_1_3_rsa_cert ->
case proplists:get_bool(ecdh, proplists:get_value(public_keys, crypto:supports())) of
true ->
init_certs(GroupName, Config);
false ->
{skip, "Missing EC crypto support"}
end;
-init_per_group(GroupName, Config) when GroupName == ecdh_anon;
+do_init_per_group(GroupName, Config) when GroupName == ecdh_anon;
GroupName == ecdhe_rsa;
GroupName == ecdhe_psk ->
case proplists:get_bool(ecdh, proplists:get_value(public_keys, crypto:supports())) of
@@ -216,7 +323,7 @@ init_per_group(GroupName, Config) when GroupName == ecdh_anon;
false ->
{skip, "Missing EC crypto support"}
end;
-init_per_group(ecdhe_ecdsa = GroupName, Config) ->
+do_init_per_group(ecdhe_ecdsa = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
case lists:member(ecdh, PKAlg) andalso lists:member(ecdsa, PKAlg) of
true ->
@@ -224,7 +331,7 @@ init_per_group(ecdhe_ecdsa = GroupName, Config) ->
false ->
{skip, "Missing EC crypto support"}
end;
-init_per_group(dhe_dss = GroupName, Config) ->
+do_init_per_group(dhe_dss = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of
true ->
@@ -232,7 +339,7 @@ init_per_group(dhe_dss = GroupName, Config) ->
false ->
{skip, "Missing DSS crypto support"}
end;
-init_per_group(srp_dss = GroupName, Config) ->
+do_init_per_group(srp_dss = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
case lists:member(dss, PKAlg) andalso lists:member(srp, PKAlg) of
true ->
@@ -240,8 +347,8 @@ init_per_group(srp_dss = GroupName, Config) ->
false ->
{skip, "Missing DSS_SRP crypto support"}
end;
-init_per_group(GroupName, Config) when GroupName == srp_anon;
- GroupName == srp_rsa ->
+do_init_per_group(GroupName, Config) when GroupName == srp_anon;
+ GroupName == srp_rsa ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
case lists:member(srp, PKAlg) of
true ->
@@ -249,7 +356,7 @@ init_per_group(GroupName, Config) when GroupName == srp_anon;
false ->
{skip, "Missing SRP crypto support"}
end;
-init_per_group(dhe_psk = GroupName, Config) ->
+do_init_per_group(dhe_psk = GroupName, Config) ->
PKAlg = proplists:get_value(public_keys, crypto:supports()),
case lists:member(dh, PKAlg) of
true ->
@@ -257,21 +364,11 @@ init_per_group(dhe_psk = GroupName, Config) ->
false ->
{skip, "Missing SRP crypto support"}
end;
-init_per_group(GroupName, Config0) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, end_per_group(GroupName, Config0));
- false ->
- init_certs(GroupName, Config0)
- end.
+do_init_per_group(GroupName, Config) ->
+ init_certs(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(TestCase, Config) when TestCase == psk_3des_ede_cbc;
TestCase == srp_anon_3des_ede_cbc;
@@ -291,7 +388,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_3des_ede_cbc;
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(des_ede3, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing 3DES crypto support"}
@@ -307,33 +404,30 @@ init_per_testcase(TestCase, Config) when TestCase == psk_rc4_128;
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(rc4, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing RC4 crypto support"}
end;
-init_per_testcase(TestCase, Config) when TestCase == psk_aes_128_ccm_8;
- TestCase == rsa_psk_aes_128_ccm_8;
- TestCase == psk_aes_128_ccm_8;
- TestCase == dhe_psk_aes_128_ccm_8;
- TestCase == ecdhe_psk_aes_128_ccm_8 ->
+init_per_testcase(TestCase, Config) when TestCase == psk_aes_128_ccm_8;
+ TestCase == dhe_psk_aes_128_ccm_8;
+ TestCase == ecdhe_psk_aes_128_ccm_8 ->
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(aes_128_ccm, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_128_CCM crypto support"}
end;
init_per_testcase(TestCase, Config) when TestCase == psk_aes_256_ccm_8;
- TestCase == rsa_psk_aes_256_ccm_8;
TestCase == psk_aes_256_ccm_8;
TestCase == dhe_psk_aes_256_ccm_8;
TestCase == ecdhe_psk_aes_256_ccm_8 ->
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(aes_256_ccm, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_256_CCM crypto support"}
@@ -345,7 +439,7 @@ init_per_testcase(aes_256_gcm_sha384, Config) ->
(lists:member(sha384, SupHashs))
of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_256_GCM_SHA384 crypto support"}
@@ -353,11 +447,11 @@ init_per_testcase(aes_256_gcm_sha384, Config) ->
init_per_testcase(aes_128_gcm_sha256, Config) ->
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
SupHashs = proplists:get_value(hashs, crypto:supports()),
- case (lists:member(aes_256_gcm, SupCiphers)) andalso
+ case (lists:member(aes_128_gcm, SupCiphers)) andalso
(lists:member(sha256, SupHashs))
of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_128_GCM_SHA256 crypto support"}
@@ -369,7 +463,7 @@ init_per_testcase(chacha20_poly1305_sha256, Config) ->
(lists:member(sha256, SupHashs))
of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing chacha20_poly1305_sha256 crypto support"}
@@ -380,17 +474,48 @@ init_per_testcase(aes_128_ccm_sha256, Config) ->
case (lists:member(aes_128_ccm, SupCiphers)) andalso
(lists:member(sha256, SupHashs)) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, "Missing AES_128_CCM_SHA256 crypto support"}
end;
+init_per_testcase(aes_128_ccm_8_sha256, Config) ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ SupHashs = proplists:get_value(hashs, crypto:supports()),
+ case (lists:member(aes_128_ccm, SupCiphers)) andalso
+ (lists:member(sha256, SupHashs)) of
+ true ->
+ ct:timetrap(?TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_128_CCM_8_SHA256 crypto support"}
+ end;
+init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_128_ccm;
+ TestCase == ecdhe_ecdsa_with_aes_128_ccm_8->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ case lists:member(aes_128_ccm, SupCiphers) of
+ true ->
+ ct:timetrap(?TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_128_CCM crypto support"}
+ end;
+init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_256_ccm;
+ TestCase == ecdhe_ecdsa_with_aes_256_ccm_8 ->
+ SupCiphers = proplists:get_value(ciphers, crypto:supports()),
+ case lists:member(aes_256_ccm, SupCiphers) of
+ true ->
+ ct:timetrap(?TIMEOUT),
+ Config;
+ _ ->
+ {skip, "Missing AES_256_CCM crypto support"}
+ end;
init_per_testcase(TestCase, Config) ->
Cipher = ssl_test_lib:test_cipher(TestCase, Config),
SupCiphers = proplists:get_value(ciphers, crypto:supports()),
case lists:member(Cipher, SupCiphers) of
true ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config;
_ ->
{skip, {Cipher, SupCiphers}}
@@ -515,7 +640,11 @@ chacha20_poly1305_sha256(Config) when is_list(Config) ->
aes_128_ccm_sha256(Config) when is_list(Config) ->
Version = ssl_test_lib:protocol_version(Config),
- cipher_suite_test(ssl:str_to_suite("TLS_AES_128_CCM_SHA256"), Version, Config).
+ cipher_suite_test(ssl:str_to_suite("TLS_AES_128_CCM_SHA256"), Version, Config).
+
+aes_128_ccm_8_sha256(Config) when is_list(Config) ->
+ Version = ssl_test_lib:protocol_version(Config),
+ cipher_suite_test(ssl:str_to_suite("TLS_AES_128_CCM_8_SHA256"), Version, Config).
%%--------------------------------------------------------------------
%% SRP --------------------------------------------------------
@@ -547,20 +676,8 @@ rsa_psk_3des_ede_cbc(Config) when is_list(Config) ->
rsa_psk_aes_128_cbc(Config) when is_list(Config) ->
run_ciphers_test(rsa_psk, 'aes_128_cbc', Config).
-rsa_psk_aes_128_ccm(Config) when is_list(Config) ->
- run_ciphers_test(rsa_psk, 'aes_128_ccm', Config).
-
-rsa_psk_aes_128_ccm_8(Config) when is_list(Config) ->
- run_ciphers_test(rsa_psk, 'aes_128_ccm_8', Config).
-
rsa_psk_aes_256_cbc(Config) when is_list(Config) ->
run_ciphers_test(rsa_psk, 'aes_256_cbc', Config).
-
-rsa_psk_aes_256_ccm(Config) when is_list(Config) ->
- run_ciphers_test(rsa_psk, 'aes_256_ccm', Config).
-
-rsa_psk_aes_256_ccm_8(Config) when is_list(Config) ->
- run_ciphers_test(rsa_psk, 'aes_256_ccm_8', Config).
rsa_psk_rc4_128(Config) when is_list(Config) ->
run_ciphers_test(rsa_psk, 'rc4_128', Config).
@@ -655,6 +772,18 @@ ecdhe_ecdsa_aes_256_gcm(Config) when is_list(Config) ->
ecdhe_ecdsa_chacha20_poly1305(Config) when is_list(Config) ->
run_ciphers_test(ecdhe_ecdsa, 'chacha20_poly1305', Config).
+
+ecdhe_ecdsa_with_aes_128_ccm(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm', Config).
+
+ecdhe_ecdsa_with_aes_256_ccm(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm', Config).
+
+ecdhe_ecdsa_with_aes_128_ccm_8(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm_8', Config).
+
+ecdhe_ecdsa_with_aes_256_ccm_8(Config) when is_list(Config) ->
+ run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm_8', Config).
%%--------------------------------------------------------------------
%% DHE_DSS --------------------------------------------------------
%%--------------------------------------------------------------------
@@ -748,9 +877,6 @@ dhe_psk_aes_128_ccm_8(Config) when is_list(Config) ->
dhe_psk_aes_256_ccm_8(Config) when is_list(Config) ->
run_ciphers_test(dhe_psk, 'aes_256_ccm_8', Config).
-ecdhe_psk_des_cbc(Config) when is_list(Config) ->
- run_ciphers_test(ecdhe_psk, 'des_cbc', Config).
-
ecdhe_psk_rc4_128(Config) when is_list(Config) ->
run_ciphers_test(ecdhe_psk, 'rc4_128', Config).
@@ -775,9 +901,6 @@ ecdhe_psk_aes_128_ccm(Config) when is_list(Config) ->
ecdhe_psk_aes_128_ccm_8(Config) when is_list(Config) ->
run_ciphers_test(ecdhe_psk, 'aes_128_ccm_8', Config).
-psk_des_cbc(Config) when is_list(Config) ->
- run_ciphers_test(psk, 'des_cbc', Config).
-
psk_rc4_128(Config) when is_list(Config) ->
run_ciphers_test(psk, 'rc4_128', Config).
diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl
index 8678d43450..c7897d9404 100644
--- a/lib/ssl/test/ssl_crl_SUITE.erl
+++ b/lib/ssl/test/ssl_crl_SUITE.erl
@@ -21,12 +21,41 @@
-module(ssl_crl_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+
+%% Test cases
+-export([crl_verify_valid/0,
+ crl_verify_valid/1,
+ crl_verify_revoked/0,
+ crl_verify_revoked/1,
+ crl_verify_valid_derCAs/0,
+ crl_verify_valid_derCAs/1,
+ crl_verify_revoked_derCAs/0,
+ crl_verify_revoked_derCAs/1,
+ crl_verify_no_crl/0,
+ crl_verify_no_crl/1,
+ crl_hash_dir_collision/0,
+ crl_hash_dir_collision/1,
+ crl_hash_dir_expired/0,
+ crl_hash_dir_expired/1]).
+
+-define(TIMEOUT, {seconds, 30}).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -59,7 +88,11 @@ groups() ->
{crl_verify_crldp_crlissuer, [], [crl_verify_valid]}].
basic_tests() ->
- [crl_verify_valid, crl_verify_revoked, crl_verify_no_crl].
+ [crl_verify_valid,
+ crl_verify_revoked,
+ crl_verify_valid_derCAs,
+ crl_verify_revoked_derCAs,
+ crl_verify_no_crl].
crl_hash_dir_tests() ->
[crl_hash_dir_collision, crl_hash_dir_expired].
@@ -159,7 +192,7 @@ init_per_testcase(Case, Config0) ->
{CertOpts, Config} = init_certs(CertDir, idp_crl, Config),
case make_certs:all(DataDir, CertDir, CertOpts) of
{ok, _} ->
- ct:timetrap({seconds, 6}),
+ ct:timetrap(?TIMEOUT),
[{cert_dir, CertDir} | Config];
_ ->
end_per_testcase(Case, Config0),
@@ -195,13 +228,13 @@ crl_verify_valid(Config) when is_list(Config) ->
{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}],
ClientOpts = case proplists:get_value(idp_crl, Config) of
true ->
- [{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])},
+ [{cacertfile, filename:join([PrivDir, "client", "cacerts.pem"])},
{crl_check, Check},
{crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}},
{verify, verify_peer}];
false ->
proplists:get_value(crl_cache_opts, Config) ++
- [{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])},
+ [{cacertfile, filename:join([PrivDir, "client", "cacerts.pem"])},
{crl_check, Check},
{verify, verify_peer}]
end,
@@ -241,15 +274,79 @@ crl_verify_revoked(Config) when is_list(Config) ->
crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts,
certificate_revoked).
+crl_verify_valid_derCAs() ->
+ [{doc,"Verify a simple valid CRL chain"}].
+crl_verify_valid_derCAs(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(cert_dir, Config),
+ Check = proplists:get_value(crl_check, Config),
+
+ CaCerts = der_cas(filename:join([PrivDir, "client", "cacerts.pem"])),
+
+ ServerOpts = [{keyfile, filename:join([PrivDir, "server", "key.pem"])},
+ {certfile, filename:join([PrivDir, "server", "cert.pem"])},
+ {cacerts, der_cas(filename:join([PrivDir, "server", "cacerts.pem"]))}
+ ],
+ ClientOpts = case proplists:get_value(idp_crl, Config) of
+ true ->
+ [{cacerts, CaCerts},
+ {crl_check, Check},
+ {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}},
+ {verify, verify_peer}];
+ false ->
+ proplists:get_value(crl_cache_opts, Config) ++
+ [{cacerts, CaCerts},
+ {crl_check, Check},
+ {verify, verify_peer}]
+ end,
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ ssl_crl_cache:insert({file, filename:join([PrivDir, "erlangCA", "crl.pem"])}),
+ ssl_crl_cache:insert({file, filename:join([PrivDir, "otpCA", "crl.pem"])}),
+
+ crl_verify_valid(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts).
+
+crl_verify_revoked_derCAs() ->
+ [{doc,"Verify a simple CRL chain when peer cert is reveoked"}].
+crl_verify_revoked_derCAs(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(cert_dir, Config),
+ Check = proplists:get_value(crl_check, Config),
+
+ CaCerts = der_cas(filename:join([PrivDir, "revoked", "cacerts.pem"])),
+
+ ServerOpts = [{keyfile, filename:join([PrivDir, "revoked", "key.pem"])},
+ {certfile, filename:join([PrivDir, "revoked", "cert.pem"])},
+ {cacerts, der_cas(filename:join([PrivDir, "server", "cacerts.pem"]))}],
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ ssl_crl_cache:insert({file, filename:join([PrivDir, "erlangCA", "crl.pem"])}),
+ ssl_crl_cache:insert({file, filename:join([PrivDir, "otpCA", "crl.pem"])}),
+
+ ClientOpts = case proplists:get_value(idp_crl, Config) of
+ true ->
+ [{cacerts, CaCerts},
+ {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}},
+ {crl_check, Check},
+ {verify, verify_peer}];
+ false ->
+ proplists:get_value(crl_cache_opts, Config) ++
+ [{cacerts, CaCerts},
+ {crl_check, Check},
+ {verify, verify_peer}]
+ end,
+
+ crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts,
+ certificate_revoked).
crl_verify_no_crl() ->
[{doc,"Verify a simple CRL chain when the CRL is missing"}].
crl_verify_no_crl(Config) when is_list(Config) ->
PrivDir = proplists:get_value(cert_dir, Config),
Check = proplists:get_value(crl_check, Config),
+
ServerOpts = [{keyfile, filename:join([PrivDir, "server", "key.pem"])},
- {certfile, filename:join([PrivDir, "server", "cert.pem"])},
- {cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}],
+ {certfile, filename:join([PrivDir, "server", "cert.pem"])},
+ {cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}],
ClientOpts = case proplists:get_value(idp_crl, Config) of
true ->
[{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])},
@@ -527,3 +624,9 @@ new_ca(FileName, CA1, CA2) ->
Pem = public_key:pem_encode(E1 ++E2),
file:write_file(FileName, Pem),
FileName.
+
+
+der_cas(CAcertsFile) ->
+ {ok, Pem} = file:read_file(CAcertsFile),
+ Decoded = public_key:pem_decode(Pem),
+ [DER || {_, DER, _} <- Decoded].
diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl
index be3d7c2dfe..3b0c4d8c09 100644
--- a/lib/ssl/test/ssl_dist_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_SUITE.erl
@@ -20,21 +20,68 @@
-module(ssl_dist_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include("ssl_dist_test_lib.hrl").
-%% Note: This directive should only be used in test suites.
--compile([export_all, nowarn_export_all]).
+%% Common test
+-export([all/0,
+ init_per_suite/1,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([basic/0,
+ basic/1,
+ payload/0,
+ payload/1,
+ plain_options/0,
+ plain_options/1,
+ plain_verify_options/0,
+ plain_verify_options/1,
+ nodelay_option/0,
+ nodelay_option/1,
+ listen_port_options/0,
+ listen_port_options/1,
+ listen_options/0,
+ listen_options/1,
+ connect_options/0,
+ connect_options/1,
+ use_interface/0,
+ use_interface/1,
+ verify_fun_fail/0,
+ verify_fun_fail/1,
+ verify_fun_pass/0,
+ verify_fun_pass/1
+ ]).
+
+%% Apply export
+-export([basic_test/3,
+ payload_test/3,
+ plain_options_test/3,
+ plain_verify_options_test/3,
+ do_listen_options/2,
+ listen_options_test/3,
+ do_connect_options/2,
+ connect_options_test/3,
+ verify_fun_fail_test/3,
+ verify_fun_pass_test/3,
+ verify_pass_always/3,
+ verify_fail_always/3]).
--define(DEFAULT_TIMETRAP_SECS, 240).
+-define(DEFAULT_TIMETRAP_SECS, 240).
-define(AWAIT_SSL_NODE_UP_TIMEOUT, 30000).
-import(ssl_dist_test_lib,
[tstsrvr_format/2, send_to_tstcntrl/1,
apply_on_ssl_node/4, apply_on_ssl_node/2,
stop_ssl_node/1]).
+
start_ssl_node_name(Name, Args) ->
ssl_dist_test_lib:start_ssl_node(Name, Args).
@@ -42,21 +89,21 @@ start_ssl_node_name(Name, Args) ->
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
all() ->
- [basic, payload, plain_options, plain_verify_options, nodelay_option,
- listen_port_options, listen_options, connect_options, use_interface,
- verify_fun_fail, verify_fun_pass, crl_check_pass, crl_check_fail,
- crl_check_best_effort, crl_cache_check_pass, crl_cache_check_fail].
-
-groups() ->
- [].
-
-init_per_group(_GroupName, Config) ->
- Config.
-
-end_per_group(_GroupName, Config) ->
- Config.
+ [basic,
+ payload,
+ plain_options,
+ plain_verify_options,
+ nodelay_option,
+ listen_port_options,
+ listen_options,
+ connect_options,
+ use_interface,
+ verify_fun_fail,
+ verify_fun_pass
+ ].
init_per_suite(Config0) ->
+ _ = end_per_suite(Config0),
try crypto:start() of
ok ->
%% Currently no ct function avilable for is_cover!
@@ -72,18 +119,17 @@ init_per_suite(Config0) ->
{skip, "Crypto did not start"}
end.
-end_per_suite(Config) ->
- application:stop(crypto),
- Config.
+end_per_suite(_Config) ->
+ application:stop(crypto).
init_per_testcase(plain_verify_options = Case, Config) when is_list(Config) ->
- SslFlags = setup_dist_opts([{many_verify_opts, true} | Config]),
+ SslFlags = setup_tls_opts(Config),
Flags = case os:getenv("ERL_FLAGS") of
false ->
os:putenv("ERL_FLAGS", SslFlags),
"";
OldFlags ->
- os:putenv("ERL_FLAGS", OldFlags ++ "" ++ SslFlags),
+ os:putenv("ERL_FLAGS", OldFlags ++ " " ++ SslFlags),
OldFlags
end,
common_init(Case, [{old_flags, Flags} | Config]);
@@ -112,6 +158,209 @@ basic() ->
basic(Config) when is_list(Config) ->
gen_dist_test(basic_test, Config).
+%%--------------------------------------------------------------------
+payload() ->
+ [{doc,"Test that send a lot of data between the ssl distributed nodes"}].
+payload(Config) when is_list(Config) ->
+ gen_dist_test(payload_test, Config).
+
+%%--------------------------------------------------------------------
+plain_options() ->
+ [{doc,"Test specifying tls options not related to certificate verification"}].
+plain_options(Config) when is_list(Config) ->
+ TLSOpts = "-ssl_dist_opt server_secure_renegotiate true "
+ "client_secure_renegotiate true "
+ "server_hibernate_after 500 client_hibernate_after 500",
+ gen_dist_test(plain_options_test, [{tls_only_basic_opts, TLSOpts} | Config]).
+
+
+%%--------------------------------------------------------------------
+plain_verify_options() ->
+ [{doc,"Test specifying tls options including certificate verification options"}].
+plain_verify_options(Config) when is_list(Config) ->
+ TLSOpts = "-ssl_dist_opt server_secure_renegotiate true "
+ "client_secure_renegotiate true "
+ "server_hibernate_after 500 client_hibernate_after 500"
+ "server_reuse_sessions true client_reuse_sessions true "
+ "server_depth 1 client_depth 1 ",
+ gen_dist_test(plain_verify_options_test, [{tls_verify_opts, TLSOpts} | Config]).
+
+%%--------------------------------------------------------------------
+nodelay_option() ->
+ [{doc,"Test specifying dist_nodelay option"}].
+nodelay_option(Config) ->
+ try
+ %% The default is 'true', so try setting it to 'false'.
+ application:set_env(kernel, dist_nodelay, false),
+ basic(Config)
+ after
+ application:unset_env(kernel, dist_nodelay)
+ end.
+%%--------------------------------------------------------------------
+
+listen_port_options() ->
+ [{doc, "Test specifying listening ports"}].
+listen_port_options(Config) when is_list(Config) ->
+ %% Start a node, and get the port number it's listening on.
+ NH1 = start_ssl_node(Config),
+ Node1 = NH1#node_handle.nodename,
+ Name1 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)),
+ {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0),
+ {Name1, Port1} = lists:keyfind(Name1, 1, NodesPorts),
+
+ %% Now start a second node, configuring it to use the same port
+ %% number.
+ PortOpt1 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++
+ " inet_dist_listen_max " ++ integer_to_list(Port1),
+
+ try start_ssl_node([{tls_verify_opts, PortOpt1} | proplists:delete(tls_verify_opts, Config)]) of
+ #node_handle{} ->
+ %% If the node was able to start, it didn't take the port
+ %% option into account.
+ stop_ssl_node(NH1),
+ exit(unexpected_success)
+ catch
+ exit:{accept_failed, timeout} ->
+ %% The node failed to start, as expected.
+ ok
+ end,
+
+ %% Try again, now specifying a high max port.
+ PortOpt2 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++
+ " inet_dist_listen_max 65535",
+ NH2 = start_ssl_node([{tls_verify_opts, PortOpt2} | proplists:delete(tls_verify_opts, Config)]),
+
+ try
+ Node2 = NH2#node_handle.nodename,
+ Name2 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node2)),
+ {ok, NodesPorts2} = apply_on_ssl_node(NH2, fun net_adm:names/0),
+ {Name2, Port2} = lists:keyfind(Name2, 1, NodesPorts2),
+
+ %% The new port should be higher:
+ if Port2 > Port1 ->
+ ok;
+ true ->
+ error({port, Port2, not_higher_than, Port1})
+ end
+ catch
+ _:Reason ->
+ stop_ssl_node(NH2),
+ stop_ssl_node(NH1),
+ ct:fail(Reason)
+ end,
+ stop_ssl_node(NH2),
+ stop_ssl_node(NH1),
+ success(Config).
+
+%%--------------------------------------------------------------------
+listen_options() ->
+ [{doc, "Test inet_dist_listen_options"}].
+listen_options(Config) when is_list(Config) ->
+ try_setting_priority(fun do_listen_options/2, Config).
+
+%%--------------------------------------------------------------------
+connect_options() ->
+ [{doc, "Test inet_dist_connect_options"}].
+connect_options(Config) when is_list(Config) ->
+ try_setting_priority(fun do_connect_options/2, Config).
+
+
+%%--------------------------------------------------------------------
+use_interface() ->
+ [{doc, "Test inet_dist_use_interface"}].
+use_interface(Config) when is_list(Config) ->
+ %% Force the node to listen only on the loopback interface.
+ IpString = localhost_ipstr(inet_ver()),
+ Options = "-kernel inet_dist_use_interface " ++ IpString,
+
+ %% Start a node, and get the port number it's listening on.
+ NH1 = start_ssl_node([{tls_verify_opts, Options} | Config]),
+
+ try
+ Node1 = NH1#node_handle.nodename,
+ Name = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)),
+ {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0),
+ {Name, Port} = lists:keyfind(Name, 1, NodesPorts),
+
+ %% Now find the socket listening on that port, and check its sockname.
+ Sockets = apply_on_ssl_node(
+ NH1,
+ fun() ->
+ [inet:sockname(P) ||
+ P <- inet_ports(),
+ {ok, Port} =:= (catch inet:port(P))]
+ end),
+ %% And check that it's actually listening on localhost.
+ IP = localhost_ip(inet_ver()),
+ [{ok,{IP,Port}}] = Sockets
+ catch
+ _:Reason ->
+ stop_ssl_node(NH1),
+ ct:fail(Reason)
+ end,
+ stop_ssl_node(NH1),
+ success(Config).
+%%--------------------------------------------------------------------
+verify_fun_fail() ->
+ [{doc,"Test specifying verify_fun with a function that always fails"}].
+verify_fun_fail(Config) when is_list(Config) ->
+ AddTLSVerifyOpts = "-ssl_dist_opt "
+ "server_verify_fun "
+ "\"{ssl_dist_SUITE,verify_fail_always,{}}\" "
+ "client_verify_fun "
+ "\"{ssl_dist_SUITE,verify_fail_always,{}}\" ",
+ gen_dist_test(verify_fun_fail_test, [{tls_verify_opts, AddTLSVerifyOpts} | Config]).
+
+
+%%--------------------------------------------------------------------
+verify_fun_pass() ->
+ [{doc,"Test specifying verify_fun with a function that always succeeds"}].
+verify_fun_pass(Config) when is_list(Config) ->
+ AddTLSVerifyOpts = "-ssl_dist_opt "
+ "server_verify_fun "
+ "\"{ssl_dist_SUITE,verify_pass_always,{}}\" "
+ "client_verify_fun "
+ "\"{ssl_dist_SUITE,verify_pass_always,{}}\" ",
+ gen_dist_test(verify_fun_pass_test, [{tls_verify_opts, AddTLSVerifyOpts} | Config]).
+
+%%--------------------------------------------------------------------
+%%% Internal functions -----------------------------------------------
+%%--------------------------------------------------------------------
+gen_dist_test(Test, Config) ->
+ NH1 = start_ssl_node(Config),
+ NH2 = start_ssl_node(Config),
+ try
+ ?MODULE:Test(NH1, NH2, Config)
+ catch
+ _:Reason ->
+ stop_ssl_node(NH1),
+ stop_ssl_node(NH2),
+ ct:fail(Reason)
+ end,
+ stop_ssl_node(NH1),
+ stop_ssl_node(NH2),
+ success(Config).
+
+%% ssl_node side api
+%%
+
+try_setting_priority(TestFun, Config) ->
+ Prio = 1,
+ case gen_udp:open(0, [{priority,Prio}]) of
+ {ok,Socket} ->
+ case inet:getopts(Socket, [priority]) of
+ {ok,[{priority,Prio}]} ->
+ ok = gen_udp:close(Socket),
+ TestFun(Prio, Config);
+ _ ->
+ ok = gen_udp:close(Socket),
+ {skip,
+ "Can not set priority "++integer_to_list(Prio)++
+ " on socket"}
+ end;
+ {error,_} ->
+ {skip, "Can not set priority on socket"}
+ end.
basic_test(NH1, NH2, _) ->
Node1 = NH1#node_handle.nodename,
Node2 = NH2#node_handle.nodename,
@@ -167,12 +416,6 @@ basic_test(NH1, NH2, _) ->
end)
end.
-%%--------------------------------------------------------------------
-payload() ->
- [{doc,"Test that send a lot of data between the ssl distributed noes"}].
-payload(Config) when is_list(Config) ->
- gen_dist_test(payload_test, Config).
-
payload_test(NH1, NH2, _) ->
Node1 = NH1#node_handle.nodename,
Node2 = NH2#node_handle.nodename,
@@ -209,18 +452,6 @@ payload_test(NH1, NH2, _) ->
end)
end.
-%%--------------------------------------------------------------------
-plain_options() ->
- [{doc,"Test specifying additional options"}].
-plain_options(Config) when is_list(Config) ->
- DistOpts = "-ssl_dist_opt server_secure_renegotiate true "
- "client_secure_renegotiate true "
- "server_reuse_sessions true client_reuse_sessions true "
- "client_verify verify_none server_verify verify_none "
- "server_depth 1 client_depth 1 "
- "server_hibernate_after 500 client_hibernate_after 500",
- gen_dist_test(plain_options_test, [{additional_dist_opts, DistOpts} | Config]).
-
plain_options_test(NH1, NH2, _) ->
Node1 = NH1#node_handle.nodename,
Node2 = NH2#node_handle.nodename,
@@ -230,99 +461,15 @@ plain_options_test(NH1, NH2, _) ->
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end).
-%%--------------------------------------------------------------------
-plain_verify_options() ->
- [{doc,"Test specifying additional options"}].
-plain_verify_options(Config) when is_list(Config) ->
- DistOpts = "-ssl_dist_opt server_secure_renegotiate true "
- "client_secure_renegotiate true "
- "server_reuse_sessions true client_reuse_sessions true "
- "server_hibernate_after 500 client_hibernate_after 500",
- gen_dist_test(plain_verify_options_test, [{additional_dist_opts, DistOpts} | Config]).
-
plain_verify_options_test(NH1, NH2, _) ->
Node1 = NH1#node_handle.nodename,
Node2 = NH2#node_handle.nodename,
-
+
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
-
+
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end).
-
-%%--------------------------------------------------------------------
-nodelay_option() ->
- [{doc,"Test specifying dist_nodelay option"}].
-nodelay_option(Config) ->
- try
- %% The default is 'true', so try setting it to 'false'.
- application:set_env(kernel, dist_nodelay, false),
- basic(Config)
- after
- application:unset_env(kernel, dist_nodelay)
- end.
-%%--------------------------------------------------------------------
-
-listen_port_options() ->
- [{doc, "Test specifying listening ports"}].
-listen_port_options(Config) when is_list(Config) ->
- %% Start a node, and get the port number it's listening on.
- NH1 = start_ssl_node(Config),
- Node1 = NH1#node_handle.nodename,
- Name1 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)),
- {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0),
- {Name1, Port1} = lists:keyfind(Name1, 1, NodesPorts),
-
- %% Now start a second node, configuring it to use the same port
- %% number.
- PortOpt1 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++
- " inet_dist_listen_max " ++ integer_to_list(Port1),
-
- try start_ssl_node([{additional_dist_opts, PortOpt1} | Config]) of
- #node_handle{} ->
- %% If the node was able to start, it didn't take the port
- %% option into account.
- stop_ssl_node(NH1),
- exit(unexpected_success)
- catch
- exit:{accept_failed, timeout} ->
- %% The node failed to start, as expected.
- ok
- end,
-
- %% Try again, now specifying a high max port.
- PortOpt2 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++
- " inet_dist_listen_max 65535",
- NH2 = start_ssl_node([{additional_dist_opts, PortOpt2} | Config]),
-
- try
- Node2 = NH2#node_handle.nodename,
- Name2 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node2)),
- {ok, NodesPorts2} = apply_on_ssl_node(NH2, fun net_adm:names/0),
- {Name2, Port2} = lists:keyfind(Name2, 1, NodesPorts2),
-
- %% The new port should be higher:
- if Port2 > Port1 ->
- ok;
- true ->
- error({port, Port2, not_higher_than, Port1})
- end
- catch
- _:Reason ->
- stop_ssl_node(NH2),
- stop_ssl_node(NH1),
- ct:fail(Reason)
- end,
- stop_ssl_node(NH2),
- stop_ssl_node(NH1),
- success(Config).
-
-%%--------------------------------------------------------------------
-listen_options() ->
- [{doc, "Test inet_dist_listen_options"}].
-listen_options(Config) when is_list(Config) ->
- try_setting_priority(fun do_listen_options/2, Config).
-
do_listen_options(Prio, Config) ->
PriorityString0 = "[{priority,"++integer_to_list(Prio)++"}]",
PriorityString =
@@ -335,18 +482,18 @@ do_listen_options(Prio, Config) ->
end,
Options = "-kernel inet_dist_listen_options " ++ PriorityString,
- gen_dist_test(listen_options_test, [{prio, Prio}, {additional_dist_opts, Options} | Config]).
-
+ gen_dist_test(listen_options_test, [{prio, Prio}, {tls_only_basic_opts, Options} | Config]).
+
listen_options_test(NH1, NH2, Config) ->
Prio = proplists:get_value(prio, Config),
- Node2 = NH2#node_handle.nodename,
+ Node2 = NH2#node_handle.nodename,
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
PrioritiesNode1 =
apply_on_ssl_node(NH1, fun get_socket_priorities/0),
PrioritiesNode2 =
apply_on_ssl_node(NH2, fun get_socket_priorities/0),
-
+
Elevated1 = [P || P <- PrioritiesNode1, P =:= Prio],
ct:pal("Elevated1: ~p~n", [Elevated1]),
Elevated2 = [P || P <- PrioritiesNode2, P =:= Prio],
@@ -354,12 +501,6 @@ listen_options_test(NH1, NH2, Config) ->
[_|_] = Elevated1,
[_|_] = Elevated2.
-%%--------------------------------------------------------------------
-connect_options() ->
- [{doc, "Test inet_dist_connect_options"}].
-connect_options(Config) when is_list(Config) ->
- try_setting_priority(fun do_connect_options/2, Config).
-
do_connect_options(Prio, Config) ->
PriorityString0 = "[{priority,"++integer_to_list(Prio)++"}]",
PriorityString =
@@ -372,13 +513,13 @@ do_connect_options(Prio, Config) ->
end,
Options = "-kernel inet_dist_connect_options " ++ PriorityString,
- gen_dist_test(connect_options_test,
- [{prio, Prio}, {additional_dist_opts, Options} | Config]).
+ gen_dist_test(connect_options_test,
+ [{prio, Prio}, {tls_only_basic_opts, Options} | Config]).
connect_options_test(NH1, NH2, Config) ->
Prio = proplists:get_value(prio, Config),
Node2 = NH2#node_handle.nodename,
-
+
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
PrioritiesNode1 =
@@ -395,54 +536,10 @@ connect_options_test(NH1, NH2, Config) ->
%% Node 2 will not, since it only applies to outbound connections.
[] = Elevated2.
-%%--------------------------------------------------------------------
-use_interface() ->
- [{doc, "Test inet_dist_use_interface"}].
-use_interface(Config) when is_list(Config) ->
- %% Force the node to listen only on the loopback interface.
- IpString = "'{127,0,0,1}'",
- Options = "-kernel inet_dist_use_interface " ++ IpString,
-
- %% Start a node, and get the port number it's listening on.
- NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]),
-
- try
- Node1 = NH1#node_handle.nodename,
- Name = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)),
- {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0),
- {Name, Port} = lists:keyfind(Name, 1, NodesPorts),
-
- %% Now find the socket listening on that port, and check its sockname.
- Sockets = apply_on_ssl_node(
- NH1,
- fun() ->
- [inet:sockname(P) ||
- P <- inet_ports(),
- {ok, Port} =:= (catch inet:port(P))]
- end),
- %% And check that it's actually listening on localhost.
- [{ok,{{127,0,0,1},Port}}] = Sockets
- catch
- _:Reason ->
- stop_ssl_node(NH1),
- ct:fail(Reason)
- end,
- stop_ssl_node(NH1),
- success(Config).
-%%--------------------------------------------------------------------
-verify_fun_fail() ->
- [{doc,"Test specifying verify_fun with a function that always fails"}].
-verify_fun_fail(Config) when is_list(Config) ->
- DistOpts = "-ssl_dist_opt "
- "server_verify verify_peer server_verify_fun "
- "\"{ssl_dist_SUITE,verify_fail_always,{}}\" "
- "client_verify verify_peer client_verify_fun "
- "\"{ssl_dist_SUITE,verify_fail_always,{}}\" ",
- gen_dist_test(verify_fun_fail_test, [{additional_dist_opts, DistOpts} | Config]).
verify_fun_fail_test(NH1, NH2, _) ->
Node2 = NH2#node_handle.nodename,
-
+
pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
[] = apply_on_ssl_node(NH1, fun () -> nodes() end),
@@ -456,20 +553,6 @@ verify_fun_fail_test(NH1, NH2, _) ->
undefined =
apply_on_ssl_node(NH2, fun () -> ets:info(verify_fun_ran) end).
-
-
-%%--------------------------------------------------------------------
-verify_fun_pass() ->
- [{doc,"Test specifying verify_fun with a function that always succeeds"}].
-verify_fun_pass(Config) when is_list(Config) ->
- DistOpts = "-ssl_dist_opt "
- "server_verify verify_peer server_verify_fun "
- "\"{ssl_dist_SUITE,verify_pass_always,{}}\" "
- "server_fail_if_no_peer_cert true "
- "client_verify verify_peer client_verify_fun "
- "\"{ssl_dist_SUITE,verify_pass_always,{}}\" ",
- gen_dist_test(verify_fun_pass_test, [{additional_dist_opts, DistOpts} | Config]).
-
verify_fun_pass_test(NH1, NH2, _) ->
Node1 = NH1#node_handle.nodename,
Node2 = NH2#node_handle.nodename,
@@ -488,158 +571,6 @@ verify_fun_pass_test(NH1, NH2, _) ->
[{verify_pass_always_ran, true}] =
apply_on_ssl_node(NH2, fun () -> ets:tab2list(verify_fun_ran) end).
-%%--------------------------------------------------------------------
-crl_check_pass() ->
- [{doc,"Test crl_check with non-revoked certificate"}].
-crl_check_pass(Config) when is_list(Config) ->
- DistOpts = "-ssl_dist_opt client_crl_check true",
- NewConfig =
- [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config,
- gen_dist_test(crl_check_pass_test, NewConfig).
-
-crl_check_pass_test(NH1, NH2, Config) ->
- Node1 = NH1#node_handle.nodename,
- Node2 = NH2#node_handle.nodename,
-
- PrivDir = ?config(priv_dir, Config),
- cache_crls_on_ssl_nodes(PrivDir, ["erlangCA", "otpCA"], [NH1, NH2]),
-
- %% The server's certificate is not revoked, so connection succeeds.
- pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
-
- [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
- [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end).
-
-%%--------------------------------------------------------------------
-crl_check_fail() ->
- [{doc,"Test crl_check with revoked certificate"}].
-crl_check_fail(Config) when is_list(Config) ->
- DistOpts = "-ssl_dist_opt client_crl_check true",
- NewConfig =
- [{many_verify_opts, true},
- %% The server uses a revoked certificate.
- {server_cert_dir, "revoked"},
- {additional_dist_opts, DistOpts}] ++ Config,
- gen_dist_test(crl_check_fail_test, NewConfig).
-
-crl_check_fail_test(NH1, NH2, Config) ->
- Node2 = NH2#node_handle.nodename,
-
- PrivDir = ?config(priv_dir, Config),
- cache_crls_on_ssl_nodes(PrivDir, ["erlangCA", "otpCA"], [NH1, NH2]),
-
- %% The server's certificate is revoked, so connection fails.
- pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
-
- [] = apply_on_ssl_node(NH1, fun () -> nodes() end),
- [] = apply_on_ssl_node(NH2, fun () -> nodes() end).
-
-%%--------------------------------------------------------------------
-crl_check_best_effort() ->
- [{doc,"Test specifying crl_check as best_effort"}].
-crl_check_best_effort(Config) when is_list(Config) ->
- DistOpts = "-ssl_dist_opt "
- "server_verify verify_peer server_crl_check best_effort",
- NewConfig =
- [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config,
- gen_dist_test(crl_check_best_effort_test, NewConfig).
-
-crl_check_best_effort_test(NH1, NH2, _Config) ->
- %% We don't have the correct CRL at hand, but since crl_check is
- %% best_effort, we accept it anyway.
- Node1 = NH1#node_handle.nodename,
- Node2 = NH2#node_handle.nodename,
-
- pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
-
- [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
- [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end).
-
-%%--------------------------------------------------------------------
-crl_cache_check_pass() ->
- [{doc,"Test specifying crl_check with custom crl_cache module"}].
-crl_cache_check_pass(Config) when is_list(Config) ->
- PrivDir = ?config(priv_dir, Config),
- NodeDir = filename:join([PrivDir, "Certs"]),
- DistOpts = "-ssl_dist_opt "
- "client_crl_check true "
- "client_crl_cache "
- "\"{ssl_dist_SUITE,{\\\"" ++ NodeDir ++ "\\\",[]}}\"",
- NewConfig =
- [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config,
- gen_dist_test(crl_cache_check_pass_test, NewConfig).
-
-crl_cache_check_pass_test(NH1, NH2, _) ->
- Node1 = NH1#node_handle.nodename,
- Node2 = NH2#node_handle.nodename,
-
- pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
-
- [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
- [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end).
-
-%%--------------------------------------------------------------------
-crl_cache_check_fail() ->
- [{doc,"Test custom crl_cache module with revoked certificate"}].
-crl_cache_check_fail(Config) when is_list(Config) ->
- PrivDir = ?config(priv_dir, Config),
- NodeDir = filename:join([PrivDir, "Certs"]),
- DistOpts = "-ssl_dist_opt "
- "client_crl_check true "
- "client_crl_cache "
- "\"{ssl_dist_SUITE,{\\\"" ++ NodeDir ++ "\\\",[]}}\"",
- NewConfig =
- [{many_verify_opts, true},
- %% The server uses a revoked certificate.
- {server_cert_dir, "revoked"},
- {additional_dist_opts, DistOpts}] ++ Config,
-
- gen_dist_test(crl_cache_check_fail_test, NewConfig).
-
-crl_cache_check_fail_test(NH1, NH2, _) ->
- Node2 = NH2#node_handle.nodename,
- pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
-
- [] = apply_on_ssl_node(NH1, fun () -> nodes() end),
- [] = apply_on_ssl_node(NH2, fun () -> nodes() end).
-%%--------------------------------------------------------------------
-%%% Internal functions -----------------------------------------------
-%%--------------------------------------------------------------------
-gen_dist_test(Test, Config) ->
- NH1 = start_ssl_node(Config),
- NH2 = start_ssl_node(Config),
- try
- ?MODULE:Test(NH1, NH2, Config)
- catch
- _:Reason ->
- stop_ssl_node(NH1),
- stop_ssl_node(NH2),
- ct:fail(Reason)
- end,
- stop_ssl_node(NH1),
- stop_ssl_node(NH2),
- success(Config).
-
-%% ssl_node side api
-%%
-
-try_setting_priority(TestFun, Config) ->
- Prio = 1,
- case gen_udp:open(0, [{priority,Prio}]) of
- {ok,Socket} ->
- case inet:getopts(Socket, [priority]) of
- {ok,[{priority,Prio}]} ->
- ok = gen_udp:close(Socket),
- TestFun(Prio, Config);
- _ ->
- ok = gen_udp:close(Socket),
- {skip,
- "Can not set priority "++integer_to_list(Prio)++
- " on socket"}
- end;
- {error,_} ->
- {skip, "Can not set priority on socket"}
- end.
get_socket_priorities() ->
[Priority ||
@@ -650,36 +581,16 @@ inet_ports() ->
[Port || Port <- erlang:ports(),
element(2, erlang:port_info(Port, name)) =:= "tcp_inet"].
-%%
-%% test_server side api
-%%
-
start_ssl_node(Config) ->
start_ssl_node(Config, "").
start_ssl_node(Config, XArgs) ->
Name = mk_node_name(Config),
- SSL = proplists:get_value(ssl_opts, Config),
- SSLDistOpts = setup_dist_opts(Config),
+ App = proplists:get_value(app_opts, Config),
+ SSLOpts = setup_tls_opts(Config),
start_ssl_node_name(
- Name, SSL ++ " " ++ SSLDistOpts ++ XArgs).
-
-cache_crls_on_ssl_nodes(PrivDir, CANames, NHs) ->
- [begin
- File = filename:join([PrivDir, "Certs", CAName, "crl.pem"]),
- {ok, PemBin} = file:read_file(File),
- PemEntries = public_key:pem_decode(PemBin),
- CRLs = [ CRL || {'CertificateList', CRL, not_encrypted}
- <- PemEntries],
- ok = apply_on_ssl_node(NH, ssl_manager, insert_crls,
- ["no_distribution_point", CRLs, dist])
- end
- || NH <- NHs, CAName <- CANames],
- ok.
+ Name, App ++ " " ++ SSLOpts ++ XArgs).
-%%
-%% command line creation
-%%
mk_node_name(Config) ->
N = erlang:unique_integer([positive]),
@@ -690,107 +601,51 @@ mk_node_name(Config) ->
++ "_"
++ integer_to_list(N).
-%%
-%% Setup ssl dist info
-%%
-
-rand_bin(N) ->
- rand_bin(N, []).
-
-rand_bin(0, Acc) ->
- Acc;
-rand_bin(N, Acc) ->
- rand_bin(N-1, [rand:uniform(256)-1|Acc]).
-
-make_randfile(Dir) ->
- {ok, IoDev} = file:open(filename:join([Dir, "RAND"]), [write]),
- ok = file:write(IoDev, rand_bin(1024)),
- file:close(IoDev).
-
-append_files(FileNames, ResultFileName) ->
- {ok, ResultFile} = file:open(ResultFileName, [write]),
- do_append_files(FileNames, ResultFile).
-
-do_append_files([], RF) ->
- ok = file:close(RF);
-do_append_files([F|Fs], RF) ->
- {ok, Data} = file:read_file(F),
- ok = file:write(RF, Data),
- do_append_files(Fs, RF).
-
setup_certs(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
- NodeDir = filename:join([PrivDir, "Certs"]),
- RGenDir = filename:join([NodeDir, "rand_gen"]),
- ok = file:make_dir(NodeDir),
- ok = file:make_dir(RGenDir),
- make_randfile(RGenDir),
- [Hostname|_] = string:split(net_adm:localhost(), ".", all),
- {ok, _} = make_certs:all(RGenDir, NodeDir, [{hostname,Hostname}]),
- SDir = filename:join([NodeDir, "server"]),
- SC = filename:join([SDir, "cert.pem"]),
- SK = filename:join([SDir, "key.pem"]),
- SKC = filename:join([SDir, "keycert.pem"]),
- append_files([SK, SC], SKC),
- CDir = filename:join([NodeDir, "client"]),
- CC = filename:join([CDir, "cert.pem"]),
- CK = filename:join([CDir, "key.pem"]),
- CKC = filename:join([CDir, "keycert.pem"]),
- append_files([CK, CC], CKC).
-
-setup_dist_opts(Config) ->
+ DerConfig = public_key:pkix_test_data(#{server_chain => #{root => rsa_root_key(1),
+ intermediates => [rsa_intermediate(2)],
+ peer => rsa_peer_key(3)},
+ client_chain => #{root => rsa_root_key(1),
+ intermediates => [rsa_intermediate(5)],
+ peer => rsa_peer_key(6)}}),
+ ClientBase = filename:join([PrivDir, "rsa"]),
+ SeverBase = filename:join([PrivDir, "rsa"]),
+
+ _ = x509_test:gen_pem_config_files(DerConfig, ClientBase, SeverBase).
+
+setup_tls_opts(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
- DataDir = proplists:get_value(data_dir, Config),
- Dhfile = filename:join([DataDir, "dHParam.pem"]),
- NodeDir = filename:join([PrivDir, "Certs"]),
- SDir = filename:join([NodeDir, proplists:get_value(server_cert_dir, Config, "server")]),
- CDir = filename:join([NodeDir, proplists:get_value(client_cert_dir, Config, "client")]),
- SC = filename:join([SDir, "cert.pem"]),
- SK = filename:join([SDir, "key.pem"]),
- SKC = filename:join([SDir, "keycert.pem"]),
- SCA = filename:join([CDir, "cacerts.pem"]),
- CC = filename:join([CDir, "cert.pem"]),
- CK = filename:join([CDir, "key.pem"]),
- CKC = filename:join([CDir, "keycert.pem"]),
- CCA = filename:join([SDir, "cacerts.pem"]),
-
- DistOpts = case proplists:get_value(many_verify_opts, Config, false) of
- false ->
- "-proto_dist inet_tls "
- ++ "-ssl_dist_opt server_certfile " ++ SKC ++ " "
- ++ "-ssl_dist_opt client_certfile " ++ CKC ++ " ";
- true ->
- case os:type() of
- {win32, _} ->
- "-proto_dist inet_tls "
- ++ "-ssl_dist_opt server_certfile " ++ SKC ++ " "
- ++ "-ssl_dist_opt server_cacertfile " ++ SCA ++ " "
- ++ "-ssl_dist_opt server_verify verify_peer "
- ++ "-ssl_dist_opt server_fail_if_no_peer_cert true "
- ++ "-ssl_dist_opt server_ciphers DHE-RSA-AES256-SHA\:DHE-RSA-AES128-SHA "
- ++ "-ssl_dist_opt server_dhfile " ++ Dhfile ++ " "
- ++ "-ssl_dist_opt client_certfile " ++ CKC ++ " "
- ++ "-ssl_dist_opt client_cacertfile " ++ CCA ++ " "
- ++ "-ssl_dist_opt client_verify verify_peer "
- ++ "-ssl_dist_opt client_ciphers DHE-RSA-AES256-SHA\:DHE-RSA-AES128-SHA ";
- _ ->
- "-proto_dist inet_tls "
- ++ "-ssl_dist_opt server_certfile " ++ SC ++ " "
- ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " "
- ++ "-ssl_dist_opt server_cacertfile " ++ SCA ++ " "
- ++ "-ssl_dist_opt server_verify verify_peer "
- ++ "-ssl_dist_opt server_fail_if_no_peer_cert true "
- ++ "-ssl_dist_opt server_ciphers DHE-RSA-AES256-SHA\:DHE-RSA-AES128-SHA "
- ++ "-ssl_dist_opt server_dhfile " ++ Dhfile ++ " "
- ++ "-ssl_dist_opt client_certfile " ++ CC ++ " "
- ++ "-ssl_dist_opt client_keyfile " ++ CK ++ " "
- ++ "-ssl_dist_opt client_cacertfile " ++ CCA ++ " "
- ++ "-ssl_dist_opt client_verify verify_peer "
- ++ "-ssl_dist_opt client_ciphers DHE-RSA-AES256-SHA\:DHE-RSA-AES128-SHA "
- end
- end,
- MoreOpts = proplists:get_value(additional_dist_opts, Config, []),
- DistOpts ++ MoreOpts.
+ SC = filename:join([PrivDir, "rsa_server_cert.pem"]),
+ SK = filename:join([PrivDir, "rsa_server_key.pem"]),
+ SCA = filename:join([PrivDir, "rsa_server_cacerts.pem"]),
+ CC = filename:join([PrivDir, "rsa_client_cert.pem"]),
+ CK = filename:join([PrivDir, "rsa_client_key.pem"]),
+ CCA = filename:join([PrivDir, "rsa_client_cacerts.pem"]),
+
+ case proplists:get_value(tls_only_basic_opts, Config, []) of
+ [_|_] = BasicOpts -> %% No verify but server still need to have cert
+ "-proto_dist inet_tls " ++ "-ssl_dist_opt server_certfile " ++ SC ++ " "
+ ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " " ++ BasicOpts;
+ [] -> %% Verify
+ case proplists:get_value(tls_verify_opts, Config, []) of
+ [_|_] ->
+ BasicVerifyOpts = "-proto_dist inet_tls "
+ ++ "-ssl_dist_opt server_certfile " ++ SC ++ " "
+ ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " "
+ ++ "-ssl_dist_opt server_cacertfile " ++ SCA ++ " "
+ ++ "-ssl_dist_opt server_verify verify_peer "
+ ++ "-ssl_dist_opt server_fail_if_no_peer_cert true "
+ ++ "-ssl_dist_opt client_certfile " ++ CC ++ " "
+ ++ "-ssl_dist_opt client_keyfile " ++ CK ++ " "
+ ++ "-ssl_dist_opt client_cacertfile " ++ CCA ++ " "
+ ++ "-ssl_dist_opt client_verify verify_peer ",
+ BasicVerifyOpts ++ proplists:get_value(tls_verify_opts, Config, []);
+ _ -> %% No verify, no extra opts
+ "-proto_dist inet_tls " ++ "-ssl_dist_opt server_certfile " ++ SC ++ " "
+ ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " "
+ end
+ end.
%%
%% Start scripts etc...
@@ -841,20 +696,16 @@ add_ssl_opts_config(Config) ->
SSL_VSN]),
ok = file:close(RelFile),
ok = systools:make_script(Script, []),
- [{ssl_opts, "-boot " ++ Script} | Config]
+ [{app_opts, "-boot " ++ Script} | Config]
catch
_:_ ->
- [{ssl_opts, "-pa \"" ++ filename:dirname(code:which(ssl))++"\""}
+ [{app_opts, "-pa \"" ++ filename:dirname(code:which(ssl))++"\""}
| add_comment_config(
"Bootscript wasn't used since the test wasn't run on an "
"installed OTP system.",
Config)]
end.
-%%
-%% Add common comments to config
-%%
-
add_comment_config(Comment, []) ->
[{comment, Comment}];
add_comment_config(Comment, [{comment, OldComment} | Cs]) ->
@@ -862,9 +713,6 @@ add_comment_config(Comment, [{comment, OldComment} | Cs]) ->
add_comment_config(Comment, [C|Cs]) ->
[C|add_comment_config(Comment, Cs)].
-%%
-%% Call when test case success
-%%
success(Config) ->
case lists:keysearch(comment, 1, Config) of
@@ -892,6 +740,7 @@ verify_fail_always(_Certificate, _Event, _State) ->
Parent = self(),
spawn(
fun() ->
+ catch ets:delete(verify_fun_ran),
ets:new(verify_fun_ran, [public, named_table]),
ets:insert(verify_fun_ran, {verify_fail_always_ran, true}),
Parent ! go_ahead,
@@ -906,6 +755,7 @@ verify_pass_always(_Certificate, _Event, State) ->
Parent = self(),
spawn(
fun() ->
+ catch ets:delete(verify_fun_ran),
ets:new(verify_fun_ran, [public, named_table]),
ets:insert(verify_fun_ran, {verify_pass_always_ran, true}),
Parent ! go_ahead,
@@ -914,24 +764,33 @@ verify_pass_always(_Certificate, _Event, State) ->
receive go_ahead -> ok end,
{valid, State}.
-%% ssl_crl_cache_api callbacks
-lookup(_DistributionPoint, _DbHandle) ->
- not_available.
-
-select({rdnSequence, NameParts}, {NodeDir, _}) ->
- %% Extract the CN from the issuer name...
- [CN] = [CN ||
- [#'AttributeTypeAndValue'{
- type = ?'id-at-commonName',
- value = <<_, _, CN/binary>>}] <- NameParts],
- %% ...and use that as the directory name to find the CRL.
- error_logger:info_report([{found_cn, CN}]),
- CRLFile = filename:join([NodeDir, CN, "crl.pem"]),
- {ok, PemBin} = file:read_file(CRLFile),
- PemEntries = public_key:pem_decode(PemBin),
- CRLs = [ CRL || {'CertificateList', CRL, not_encrypted}
- <- PemEntries],
- CRLs.
-
-fresh_crl(_DistributionPoint, CRL) ->
- CRL.
+localhost_ip(InetVer) ->
+ {ok, Addr} = inet:getaddr(net_adm:localhost(), InetVer),
+ Addr.
+
+localhost_ipstr(InetVer) ->
+ {ok, Addr} = inet:getaddr(net_adm:localhost(), InetVer),
+ case InetVer of
+ inet ->
+ lists:flatten(io_lib:format("'{~p,~p,~p,~p}'",
+ erlang:tuple_to_list(Addr)));
+ inet6 ->
+ lists:flatten(io_lib:format("'{~p,~p,~p,~p,~p,~p,~p,~p}'",
+ erlang:tuple_to_list(Addr)))
+ end.
+
+inet_ver() ->
+ inet.
+
+rsa_root_key(N) ->
+ %% As rsa keygen is not guaranteed to be fast
+ [{key, ssl_test_lib:hardcode_rsa_key(N)}].
+
+rsa_peer_key(N) ->
+ %% As rsa keygen is not guaranteed to be fast
+ [{key, ssl_test_lib:hardcode_rsa_key(N)}].
+
+rsa_intermediate(N) ->
+ [{key, ssl_test_lib:hardcode_rsa_key(N)}].
+
+
diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl
index 67944c74d2..2216c6b04b 100644
--- a/lib/ssl/test/ssl_dist_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl
@@ -19,6 +19,8 @@
%%
-module(ssl_dist_bench_SUITE).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct_event.hrl").
-include_lib("public_key/include/public_key.hrl").
diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl
index fa1f4217fe..fc6646690d 100644
--- a/lib/ssl/test/ssl_dist_test_lib.erl
+++ b/lib/ssl/test/ssl_dist_test_lib.erl
@@ -20,6 +20,8 @@
-module(ssl_dist_test_lib).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include("ssl_dist_test_lib.hrl").
@@ -102,7 +104,7 @@ start_ssl_node(Name, Args) ->
case open_port({spawn, CmdLine}, []) of
Port when is_port(Port) ->
unlink(Port),
- erlang:port_close(Port),
+ catch erlang:port_close(Port),
case await_ssl_node_up(Name, LSock) of
#node_handle{} = NodeHandle ->
?t:format("Ssl node ~s started.~n", [Name]),
diff --git a/lib/ssl/test/ssl_engine_SUITE.erl b/lib/ssl/test/ssl_engine_SUITE.erl
index a39a62e550..c0e28120be 100644
--- a/lib/ssl/test/ssl_engine_SUITE.erl
+++ b/lib/ssl/test/ssl_engine_SUITE.erl
@@ -21,12 +21,22 @@
%%
-module(ssl_engine_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ init_per_suite/1,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_testcase/2
+ ]).
+%% Test cases
+-export([private_key/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -101,15 +111,18 @@ private_key(Config) when is_list(Config) ->
#{server_config := ServerConf,
client_config := ClientConf} = GenCertData =
public_key:pkix_test_data(#{server_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{extensions, Ext},
+ #{root => [{digest, sha256},
+ {key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{digest, sha256},
+ {key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{extensions, Ext}, {digest, sha256},
{key, ssl_test_lib:hardcode_rsa_key(3)}
]},
client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}, {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(6)},{digest, sha256}]}}),
[{server_config, FileServerConf},
{client_config, FileClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
diff --git a/lib/ssl/test/ssl_eqc_SUITE.erl b/lib/ssl/test/ssl_eqc_SUITE.erl
index f7290092c0..4bfff1585e 100644
--- a/lib/ssl/test/ssl_eqc_SUITE.erl
+++ b/lib/ssl/test/ssl_eqc_SUITE.erl
@@ -20,7 +20,29 @@
-module(ssl_eqc_SUITE).
--compile(export_all).
+-behaviour(ct_suite).
+
+%% Common test
+-export([all/0,
+ init_per_suite/1,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([tls_handshake_encoding/1,
+ tls_cipher_suite_names/1,
+ tls_cipher_openssl_suite_names/1,
+ tls_anon_cipher_suite_names/1,
+ tls_anon_cipher_openssl_suite_names/1,
+ tls_unorded_chains/1,
+ tls_extraneous_chain/1,
+ tls_extraneous_chains/1,
+ tls_extraneous_and_unorder_chains/1,
+ tls_client_cert_auth/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -29,7 +51,14 @@ all() ->
[
tls_handshake_encoding,
tls_cipher_suite_names,
- tls_cipher_openssl_suite_names
+ tls_cipher_openssl_suite_names,
+ tls_anon_cipher_suite_names,
+ tls_anon_cipher_openssl_suite_names,
+ tls_unorded_chains,
+ tls_extraneous_chain,
+ tls_extraneous_chains,
+ tls_extraneous_and_unorder_chains,
+ tls_client_cert_auth
].
%%--------------------------------------------------------------------
@@ -38,12 +67,6 @@ init_per_suite(Config) ->
end_per_suite(Config) ->
Config.
-init_per_group(_GroupName, Config) ->
- Config.
-
-end_per_group(_,Config) ->
- Config.
-
init_per_testcase(_, Config0) ->
Config0.
@@ -68,3 +91,43 @@ tls_cipher_openssl_suite_names(Config) when is_list(Config) ->
%% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_cipher_suite_openssl_name()).
true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_openssl_name(),
Config).
+tls_anon_cipher_suite_names(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_rfc_name()).
+ true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_anon_cipher_suite_rfc_name(),
+ Config).
+
+tls_anon_cipher_openssl_suite_names(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_cipher_suite_openssl_name()).
+ true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_anon_cipher_suite_openssl_name(),
+ Config).
+
+tls_unorded_chains(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path("/tmp")
+ ssl:start(),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_unordered_path(PrivDir),
+ Config).
+
+tls_extraneous_chain(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path("/tmp")
+ ssl:start(),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_extraneous_path(PrivDir),
+ Config).
+
+tls_extraneous_chains(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path()
+ ssl:start(),
+ true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_extraneous_paths(),
+ Config).
+tls_extraneous_and_unorder_chains(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path()
+ ssl:start(),
+ true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_extraneous_and_unordered_path(),
+ Config).
+
+tls_client_cert_auth(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_chain:prop_client_cert_auth()
+ ssl:start(),
+ true = ct_property_test:quickcheck(ssl_eqc_chain:prop_client_cert_auth(),
+ Config).
diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl
index 3e44035b6a..df8f867f24 100644
--- a/lib/ssl/test/ssl_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_handshake_SUITE.erl
@@ -22,7 +22,7 @@
-module(ssl_handshake_SUITE).
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include("ssl_alert.hrl").
@@ -32,6 +32,29 @@
-include("tls_handshake.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([decode_hello_handshake/1,
+ decode_single_hello_extension_correctly/1,
+ decode_supported_elliptic_curves_hello_extension_correctly/1,
+ decode_unknown_hello_extension_correctly/1,
+ encode_single_hello_sni_extension_correctly/1,
+ decode_single_hello_sni_extension_correctly/1,
+ decode_empty_server_sni_correctly/1,
+ select_proper_tls_1_2_rsa_default_hashsign/1,
+ ignore_hassign_extension_pre_tls_1_2/1,
+ signature_algorithms/1,
+ encode_decode_srp/1]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -44,7 +67,7 @@ all() -> [decode_hello_handshake,
decode_empty_server_sni_correctly,
select_proper_tls_1_2_rsa_default_hashsign,
ignore_hassign_extension_pre_tls_1_2,
- unorded_chain, signature_algorithms,
+ signature_algorithms,
encode_decode_srp].
%%--------------------------------------------------------------------
@@ -169,30 +192,12 @@ ignore_hassign_extension_pre_tls_1_2(Config) ->
Opts = proplists:get_value(server_opts, Config),
CertFile = proplists:get_value(certfile, Opts),
[{_, Cert, _}] = ssl_test_lib:pem_to_der(CertFile),
- HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}, {sha, rsa}]},
+ HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}, {sha256, rsa}]},
{sha512, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,3}), {3,3}),
%%% Ignore
{md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,2}), {3,2}),
{md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,0}), {3,0}).
-unorded_chain(Config) when is_list(Config) ->
- DefConf = ssl_test_lib:default_cert_chain_conf(),
- CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf),
- #{server_config := ServerConf,
- client_config := _ClientConf} = public_key:pkix_test_data(CertChainConf),
- PeerCert = proplists:get_value(cert, ServerConf),
- CaCerts = [_, C1, C2] = proplists:get_value(cacerts, ServerConf),
- {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, CaCerts}),
- UnordedChain = case public_key:pkix_is_self_signed(C1) of
- true ->
- [C1, C2];
- false ->
- [C2, C1]
- end,
- OrderedChain = [PeerCert | lists:reverse(UnordedChain)],
- {ok, _, OrderedChain} =
- ssl_certificate:certificate_chain(PeerCert, ets:new(foo, []), ExtractedCerts, UnordedChain).
-
encode_decode_srp(_Config) ->
Exts = #{srp => #srp{username = <<"foo">>},
sni => #sni{hostname = "bar"},
@@ -226,9 +231,9 @@ signature_algorithms(Config) ->
HashSigns0 = #hash_sign_algos{
hash_sign_algos = [{sha512, rsa},
{sha, dsa},
- {sha, rsa}]},
+ {sha256, rsa}]},
Schemes0 = #signature_algorithms_cert{
- signature_scheme_list = [rsa_pkcs1_sha1,
+ signature_scheme_list = [rsa_pkcs1_sha256,
ecdsa_sha1]},
{sha512, rsa} = ssl_handshake:select_hashsign(
{HashSigns0, Schemes0},
@@ -237,14 +242,14 @@ signature_algorithms(Config) ->
{3,3}),
HashSigns1 = #hash_sign_algos{
hash_sign_algos = [{sha, dsa},
- {sha, rsa}]},
- {sha, rsa} = ssl_handshake:select_hashsign(
+ {sha256, rsa}]},
+ {sha256, rsa} = ssl_handshake:select_hashsign(
{HashSigns1, Schemes0},
Cert, ecdhe_rsa,
tls_v1:default_signature_algs({3,3}),
- {3,3}),
+ {3,3}),
Schemes1 = #signature_algorithms_cert{
- signature_scheme_list = [rsa_pkcs1_sha256,
+ signature_scheme_list = [rsa_pkcs1_sha1,
ecdsa_sha1]},
%% Signature not supported
#alert{} = ssl_handshake:select_hashsign(
@@ -253,11 +258,11 @@ signature_algorithms(Config) ->
tls_v1:default_signature_algs({3,3}),
{3,3}),
%% No scheme, hashsign is used
- {sha, rsa} = ssl_handshake:select_hashsign(
- {HashSigns1, undefined},
- Cert, ecdhe_rsa,
- tls_v1:default_signature_algs({3,3}),
- {3,3}),
+ {sha256, rsa} = ssl_handshake:select_hashsign(
+ {HashSigns1, undefined},
+ Cert, ecdhe_rsa,
+ tls_v1:default_signature_algs({3,3}),
+ {3,3}),
HashSigns2 = #hash_sign_algos{
hash_sign_algos = [{sha, dsa}]},
%% Signature not supported
diff --git a/lib/ssl/test/ssl_key_update_SUITE.erl b/lib/ssl/test/ssl_key_update_SUITE.erl
index 2816f1a39e..aba6a02ddc 100644
--- a/lib/ssl/test/ssl_key_update_SUITE.erl
+++ b/lib/ssl/test/ssl_key_update_SUITE.erl
@@ -19,6 +19,8 @@
%%
-module(ssl_key_update_SUITE).
+-behaviour(ct_suite).
+
%% Callback functions
-export([all/0,
groups/0,
diff --git a/lib/ssl/test/ssl_mfl_SUITE.erl b/lib/ssl/test/ssl_mfl_SUITE.erl
new file mode 100644
index 0000000000..0f9aeb1c67
--- /dev/null
+++ b/lib/ssl/test/ssl_mfl_SUITE.erl
@@ -0,0 +1,251 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ssl_mfl_SUITE).
+
+-behaviour(ct_suite).
+
+-include_lib("common_test/include/ct.hrl").
+
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([client_option/1,
+ server_option/1,
+ reuse_session/1,
+ handshake_continue/1]).
+
+%% export for use in ssl_test_lib
+-export([assert_mfl_and_send_first/3,
+ assert_mfl_and_recv_first/3]).
+
+-define(SLEEP, 500).
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+all() ->
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'},
+ {group, 'dtlsv1.2'},
+ {group, 'dtlsv1'}].
+
+groups() ->
+ [{'tlsv1.3', [], common_tests()},
+ {'tlsv1.2', [], common_tests() ++ pre_tls_1_3()},
+ {'tlsv1.1', [], common_tests() ++ pre_tls_1_3()},
+ {'tlsv1', [], common_tests() ++ pre_tls_1_3()},
+ {'dtlsv1.2', [], common_tests() ++ pre_tls_1_3()},
+ {'dtlsv1', [], common_tests() ++ pre_tls_1_3()}
+ ].
+
+common_tests() ->
+ [client_option, server_option, handshake_continue].
+
+pre_tls_1_3() ->
+ [reuse_session].
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ ssl:clear_pem_cache(),
+ Config = ssl_test_lib:make_rsa_cert(Config0),
+ ssl_test_lib:cert_options(Config)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ ssl:stop(),
+ application:stop(crypto).
+
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:init_per_group(GroupName, Config).
+
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
+
+init_per_testcase(_TestCase, Config) ->
+ ct:timetrap({seconds, 10}),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+%--------------------------------------------------------------------------------
+%% check max_fragment_length option on the client is accepted
+%% and both sides can successfully send > MFL
+client_option(Config) when is_list(Config) ->
+ ok = run_mfl_handshake(Config, 512),
+ ok = run_mfl_handshake(Config, 1024),
+ ok = run_mfl_handshake(Config, 2048),
+ ok = run_mfl_handshake(Config, 4096),
+ ok = run_mfl_handshake(Config, undefined),
+ ok.
+
+%--------------------------------------------------------------------------------
+%% check max_fragment_length option on the server is ignored
+%% and both sides can successfully send > 512 bytes
+server_option(Config) when is_list(Config) ->
+ Data = "mfl_server_options " ++ lists:duplicate(512, $x),
+ run_mfl_handshake(Config, undefined, Data, [], [{max_fragment_length, 512}]).
+
+%--------------------------------------------------------------------------------
+%% check max_fragment_length option on the client is accepted and reused
+reuse_session(Config) when is_list(Config) ->
+ MFL = 512,
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config) ++
+ [{max_fragment_length, MFL}],
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
+
+%%--------------------------------------------------------------------
+handshake_continue(Config) when is_list(Config) ->
+ ok = run_mfl_handshake_continue(Config, 1024),
+ ok = run_mfl_handshake_continue(Config, undefined),
+ ok.
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+run_mfl_handshake(Config, MFL) when is_integer(MFL) ->
+ Data = "hello world" ++ lists:duplicate(MFL, $0),
+ ClientExtraOpts = [{max_fragment_length, MFL}],
+ run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, []);
+run_mfl_handshake(Config, MFL) ->
+ Data = "hello world" ++ lists:duplicate(512, $9),
+ ClientExtraOpts = [],
+ run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, []).
+
+run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts) ->
+ run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts,
+ [], [], fun(_,_) -> ok end).
+
+run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts,
+ ClientExtraStartOpts, ServerExtraStartOpts,
+ PostFun) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ClientOpts = ClientExtraOpts ++ ClientOpts0,
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = ServerExtraOpts ++ ServerOpts0,
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, assert_mfl_and_send_first, [MFL, Data]}},
+ {options, ServerOpts} | ServerExtraStartOpts]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, assert_mfl_and_recv_first, [MFL, Data]}},
+ {options, ClientOpts} | ClientExtraStartOpts]),
+
+ ok = PostFun(Server, Client),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
+
+run_mfl_handshake_continue(Config, MFL) ->
+ Data = if is_integer(MFL) ->
+ "hello world" ++ lists:duplicate(MFL, $u);
+ true ->
+ "hello world" ++ lists:duplicate(999, $x)
+ end,
+ ClientExtraOpts = [{handshake, hello}, {max_fragment_length, MFL}],
+ ServerExtraOpts = [{handshake, hello}],
+ ExtraStartOpts = [{continue_options, [{want_ext, self()}]}],
+ MflEnum = mfl_enum(MFL),
+ PostF = fun(Server, Client) ->
+ receive {Server, {ext, ServerExt}} ->
+ ct:log("Server handshake Ext ~p~n", [ServerExt]),
+ MflEnum = maps:get(max_frag_enum, ServerExt, undefined)
+ end,
+ receive {Client, {ext, ClientExt}} ->
+ ct:log("Client handshake Ext ~p~n", [ClientExt]),
+ case maps:get(server_hello_selected_version, ClientExt, undefined) of
+ {3,4} ->
+ %% For TLS 1.3 the ssl {handshake, hello} API is inconsistent:
+ %% the server gets all the extensions CH+EE, but the client only CH
+ ignore;
+ _ ->
+ MflEnum = maps:get(max_frag_enum, ClientExt, undefined)
+ end
+ end,
+ ok
+ end,
+
+ run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts,
+ ExtraStartOpts, ExtraStartOpts, PostF).
+
+assert_mfl_and_send_first(Socket, MFL, Data) ->
+ ssl_test_lib:assert_mfl(Socket, MFL),
+ ssl_send(Socket, Data),
+ ssl_receive(Socket, "Got it"++lists:reverse(Data)).
+
+assert_mfl_and_recv_first(Socket, MFL, Data) ->
+ ssl_test_lib:assert_mfl(Socket, MFL),
+ ssl_receive(Socket, Data),
+ ssl_send(Socket, lists:reverse(Data)).
+
+ssl_send(Socket, Data) ->
+ ssl:send(Socket, Data).
+
+ssl_receive(Socket, Data) ->
+ ssl_receive(Socket, Data, []).
+
+ssl_receive(Socket, Data, Buffer) ->
+ receive
+ {ssl, Socket, MoreData} ->
+ ct:log("Received ~p~n",[MoreData]),
+ NewBuffer = Buffer ++ MoreData,
+ case NewBuffer of
+ Data ->
+ ssl:send(Socket, "Got it"),
+ ok;
+ _ ->
+ ssl_receive(Socket, Data, NewBuffer)
+ end;
+ Other ->
+ ct:fail({unexpected_message, Other})
+ after 4000 ->
+ ct:fail({did_not_get, Data})
+ end.
+
+%% RFC 6066
+mfl_enum(512) -> 1;
+mfl_enum(1024) -> 2;
+mfl_enum(2048) -> 3;
+mfl_enum(4096) -> 4;
+mfl_enum(undefined) -> undefined.
diff --git a/lib/ssl/test/ssl_npn_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl
index 4fbb3a5ade..81c75ecff0 100644
--- a/lib/ssl/test/ssl_npn_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_SUITE.erl
@@ -21,10 +21,45 @@
%%
-module(ssl_npn_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([validate_empty_protocols_are_not_allowed/1,
+ validate_empty_advertisement_list_is_allowed/1,
+ validate_advertisement_must_be_a_binary_list/1,
+ validate_client_protocols_must_be_a_tuple/1,
+ normal_npn_handshake_server_preference/1,
+ normal_npn_handshake_client_preference/1,
+ fallback_npn_handshake/1,
+ fallback_npn_handshake_server_preference/1,
+ client_negotiate_server_does_not_support/1,
+ no_client_negotiate_but_server_supports_npn/1,
+ renegotiate_from_client_after_npn_handshake/1,
+ npn_handshake_session_reused/1
+ ]).
+
+-export([assert_npn/2,
+ assert_npn_and_renegotiate_and_send_data/3,
+ ssl_send_and_assert_npn/3,
+ ssl_send/2,
+ ssl_receive/2,
+ ssl_receive_and_assert_npn/3,
+ connection_info_result/1
+ ]).
+
+-define(TIMEOUT, {seconds, 5}).
-define(SLEEP, 500).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -33,15 +68,14 @@
all() ->
[{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
- {group, 'tlsv1'},
- {group, 'sslv3'}].
+ {group, 'tlsv1'}
+ ].
groups() ->
[
{'tlsv1.2', [], next_protocol_tests()},
{'tlsv1.1', [], next_protocol_tests()},
- {'tlsv1', [], next_protocol_tests()},
- {'sslv3', [], next_protocol_not_supported()}
+ {'tlsv1', [], next_protocol_tests()}
].
next_protocol_tests() ->
@@ -59,11 +93,6 @@ next_protocol_tests() ->
npn_handshake_session_reused
].
-next_protocol_not_supported() ->
- [npn_not_supported_client,
- npn_not_supported_server
- ].
-
init_per_suite(Config0) ->
catch crypto:stop(),
try crypto:start() of
@@ -80,33 +109,16 @@ end_per_suite(_Config) ->
ssl:stop(),
application:stop(crypto).
-
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]),
- ct:timetrap({seconds, 10}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_TestCase, Config) ->
@@ -220,29 +232,6 @@ renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server, ok, Client, ok).
%--------------------------------------------------------------------------------
-npn_not_supported_client(Config) when is_list(Config) ->
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
- PrefProtocols = {client_preferred_next_protocols,
- {client, [<<"http/1.0">>], <<"http/1.1">>}},
- ClientOpts = [PrefProtocols] ++ ClientOpts0,
- {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Client = ssl_test_lib:start_client_error([{node, ClientNode},
- {port, 8888}, {host, Hostname},
- {from, self()}, {options, ClientOpts}]),
-
- ssl_test_lib:check_result(Client, {error,
- {options,
- {not_supported_in_sslv3, PrefProtocols}}}).
-
-%--------------------------------------------------------------------------------
-npn_not_supported_server(Config) when is_list(Config)->
- ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]},
- ServerOpts = [AdvProtocols] ++ ServerOpts0,
-
- {error, {options, {not_supported_in_sslv3, AdvProtocols}}} = ssl:listen(0, ServerOpts).
-
-%--------------------------------------------------------------------------------
npn_handshake_session_reused(Config) when is_list(Config)->
ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = [{client_preferred_next_protocols,
@@ -253,32 +242,10 @@ npn_handshake_session_reused(Config) when is_list(Config)->
ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
+
%%--------------------------------------------------------------------
-%% Internal functions ------------------------------------------------
+%% callback functions ------------------------------------------------
%%--------------------------------------------------------------------
-run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) ->
- Data = "hello world",
-
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
- ClientOpts = ClientExtraOpts ++ ClientOpts0,
- ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ServerOpts = ServerExtraOpts ++ ServerOpts0,
-
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE, ssl_receive_and_assert_npn, [ExpectedProtocol, Data]}},
- {options, ServerOpts}]),
-
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {?MODULE, ssl_send_and_assert_npn, [ExpectedProtocol, Data]}},
- {options, ClientOpts}]),
-
- ssl_test_lib:check_result(Server, ok, Client, ok).
-
assert_npn(Socket, Protocol) ->
ct:log("Negotiated Protocol ~p, Expecting: ~p ~n",
@@ -332,3 +299,29 @@ ssl_receive(Socket, Data, Buffer) ->
connection_info_result(Socket) ->
ssl:connection_information(Socket).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) ->
+ Data = "hello world",
+
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ClientOpts = ClientExtraOpts ++ ClientOpts0,
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = ServerExtraOpts ++ ServerOpts0,
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, ssl_receive_and_assert_npn, [ExpectedProtocol, Data]}},
+ {options, ServerOpts}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, ssl_send_and_assert_npn, [ExpectedProtocol, Data]}},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl
index 9dfc0314fb..dae07aae63 100644
--- a/lib/ssl/test/ssl_npn_hello_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl
@@ -22,8 +22,7 @@
-module(ssl_npn_hello_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("ssl/src/tls_record.hrl").
-include_lib("ssl/src/tls_handshake.hrl").
@@ -31,6 +30,23 @@
-include_lib("ssl/src/ssl_internal.hrl").
-include_lib("common_test/include/ct.hrl").
+
+%% Callback functions
+-export([all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([encode_and_decode_npn_client_hello_test/1,
+ encode_and_decode_npn_server_hello_test/1,
+ encode_and_decode_client_hello_test/1,
+ encode_and_decode_server_hello_test/1,
+ create_server_hello_with_advertised_protocols_test/1,
+ create_server_hello_with_no_advertised_protocols_test/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl
index fcf223d332..a65173f172 100644
--- a/lib/ssl/test/ssl_packet_SUITE.erl
+++ b/lib/ssl/test/ssl_packet_SUITE.erl
@@ -21,11 +21,198 @@
%%
-module(ssl_packet_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([packet_raw_passive_many_small/0,
+ packet_raw_passive_many_small/1,
+ packet_0_passive_many_small/0,
+ packet_0_passive_many_small/1,
+ packet_1_passive_many_small/0,
+ packet_1_passive_many_small/1,
+ packet_2_passive_many_small/0,
+ packet_2_passive_many_small/1,
+ packet_4_passive_many_small/0,
+ packet_4_passive_many_small/1,
+ packet_raw_passive_some_big/0,
+ packet_raw_passive_some_big/1,
+ packet_0_passive_some_big/0,
+ packet_0_passive_some_big/1,
+ packet_1_passive_some_big/0,
+ packet_1_passive_some_big/1,
+ packet_2_passive_some_big/0,
+ packet_2_passive_some_big/1,
+ packet_4_passive_some_big/0,
+ packet_4_passive_some_big/1,
+ packet_wait_passive/0,
+ packet_wait_passive/1,
+ packet_size_passive/0,
+ packet_size_passive/1,
+ header_decode_one_byte_passive/0,
+ header_decode_one_byte_passive/1,
+ header_decode_two_bytes_passive/0,
+ header_decode_two_bytes_passive/1,
+ header_decode_two_bytes_two_sent_passive/0,
+ header_decode_two_bytes_two_sent_passive/1,
+ header_decode_two_bytes_one_sent_passive/0,
+ header_decode_two_bytes_one_sent_passive/1,
+ packet_httph_passive/0,
+ packet_httph_passive/1,
+ packet_httph_bin_passive/0,
+ packet_httph_bin_passive/1,
+ packet_http_error_passive/0,
+ packet_http_error_passive/1,
+ packet_baddata_passive/0,
+ packet_baddata_passive/1,
+ packet_raw_active_once_many_small/0,
+ packet_raw_active_once_many_small/1,
+ packet_0_active_once_many_small/0,
+ packet_0_active_once_many_small/1,
+ packet_1_active_once_many_small/0,
+ packet_1_active_once_many_small/1,
+ packet_2_active_once_many_small/0,
+ packet_2_active_once_many_small/1,
+ packet_4_active_once_many_small/0,
+ packet_4_active_once_many_small/1,
+ packet_raw_active_once_some_big/0,
+ packet_raw_active_once_some_big/1,
+ packet_0_active_once_some_big/0,
+ packet_0_active_once_some_big/1,
+ packet_1_active_once_some_big/0,
+ packet_1_active_once_some_big/1,
+ packet_2_active_once_some_big/0,
+ packet_2_active_once_some_big/1,
+ packet_4_active_once_some_big/0,
+ packet_4_active_once_some_big/1,
+ packet_httph_active_once/0,
+ packet_httph_active_once/1,
+ packet_httph_bin_active_once/0,
+ packet_httph_bin_active_once/1,
+ packet_raw_active_many_small/0,
+ packet_raw_active_many_small/1,
+ packet_0_active_many_small/0,
+ packet_0_active_many_small/1,
+ packet_1_active_many_small/0,
+ packet_1_active_many_small/1,
+ packet_2_active_many_small/0,
+ packet_2_active_many_small/1,
+ packet_4_active_many_small/0,
+ packet_4_active_many_small/1,
+ packet_raw_active_some_big/0,
+ packet_raw_active_some_big/1,
+ packet_0_active_some_big/0,
+ packet_0_active_some_big/1,
+ packet_1_active_some_big/0,
+ packet_1_active_some_big/1,
+ packet_2_active_some_big/0,
+ packet_2_active_some_big/1,
+ packet_4_active_some_big/0,
+ packet_4_active_some_big/1,
+ packet_wait_active/0,
+ packet_wait_active/1,
+ packet_size_active/0,
+ packet_size_active/1,
+ packet_switch/0,
+ packet_switch/1,
+ header_decode_one_byte_active/0,
+ header_decode_one_byte_active/1,
+ header_decode_two_bytes_active/0,
+ header_decode_two_bytes_active/1,
+ header_decode_two_bytes_two_sent_active/0,
+ header_decode_two_bytes_two_sent_active/1,
+ header_decode_two_bytes_one_sent_active/0,
+ header_decode_two_bytes_one_sent_active/1,
+ packet_httph_active/0,
+ packet_httph_active/1,
+ packet_httph_bin_active/0,
+ packet_httph_bin_active/1,
+ packet_baddata_active/0,
+ packet_baddata_active/1,
+ packet_cdr_decode/0,
+ packet_cdr_decode/1,
+ packet_cdr_decode_list/0,
+ packet_cdr_decode_list/1,
+ packet_http_decode/0,
+ packet_http_decode/1,
+ packet_http_decode_list/0,
+ packet_http_decode_list/1,
+ packet_http_bin_decode_multi/0,
+ packet_http_bin_decode_multi/1,
+ packet_line_decode/0,
+ packet_line_decode/1,
+ packet_line_decode_list/0,
+ packet_line_decode_list/1,
+ packet_asn1_decode/0,
+ packet_asn1_decode/1,
+ packet_asn1_decode_list/0,
+ packet_asn1_decode_list/1,
+ packet_sunrm_decode/0,
+ packet_sunrm_decode/1,
+ packet_sunrm_decode_list/0,
+ packet_sunrm_decode_list/1,
+ packet_send_to_large/0,
+ packet_send_to_large/1,
+ packet_tpkt_decode/0,
+ packet_tpkt_decode/1,
+ packet_tpkt_decode_list/0,
+ packet_tpkt_decode_list/1,
+ reject_packet_opt/0,
+ reject_packet_opt/1
+ ]).
+
+%% Apply export
+-export([send_raw/3,
+ passive_raw/3,
+ passive_recv_packet/3,
+ send/3,
+ send_incomplete/3,
+ active_once_raw/4,
+ active_once_packet/3,
+ active_raw/3,
+ active_once_raw/3,
+ active_packet/3,
+ assert_packet_opt/2,
+ server_packet_decode/2,
+ client_packet_decode/2,
+ server_header_decode_active/3,
+ client_header_decode_active/3,
+ server_header_decode_passive/3,
+ client_header_decode_passive/3,
+ server_line_packet_decode/2,
+ server_line_packet_decode/4,
+ client_line_packet_decode/2,
+ client_line_packet_decode/5,
+ server_http_decode/2,
+ client_http_decode/2,
+ server_http_bin_decode/3,
+ client_http_bin_decode/3,
+ client_http_decode_list/2,
+ server_http_decode_error/2,
+ server_send_trailer/2,
+ client_http_decode_trailer_active/1,
+ client_http_decode_trailer_bin_active/1,
+ client_http_decode_trailer_active_once/1,
+ client_http_decode_trailer_bin_active_once/1,
+ client_http_decode_trailer_passive/1,
+ client_http_decode_trailer_bin_passive/1,
+ add_tpkt_header/1,
+ client_reject_packet_opt/2,
+ send_switch_packet/3,
+ recv_switch_packet/3
+ ]).
+
-define(BYTE(X), X:8/unsigned-big-integer).
-define(UINT16(X), X:16/unsigned-big-integer).
-define(UINT24(X), X:24/unsigned-big-integer).
@@ -41,7 +228,7 @@
-define(MANY, 1000).
-define(SOME, 50).
--define(BASE_TIMEOUT_SECONDS, 5).
+-define(BASE_TIMEOUT_SECONDS, 20).
-define(SOME_SCALE, 2).
-define(MANY_SCALE, 3).
@@ -54,7 +241,6 @@ all() ->
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
].
@@ -64,7 +250,6 @@ groups() ->
{'tlsv1.2', [], socket_packet_tests() ++ protocol_packet_tests()},
{'tlsv1.1', [], socket_packet_tests() ++ protocol_packet_tests()},
{'tlsv1', [], socket_packet_tests() ++ protocol_packet_tests()},
- {'sslv3', [], socket_packet_tests() ++ protocol_packet_tests()},
%% We will not support any packet types if the transport is
%% not reliable. We might support it for DTLS over SCTP in the future
{'dtlsv1.2', [], [reject_packet_opt]},
@@ -175,28 +360,10 @@ end_per_suite(_Config) ->
application:stop(crypto).
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:stop(),
- ssl:start(),
- Config
- end.
-
+ ssl_test_lib:init_per_group(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(_TestCase, Config) ->
ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS}),
@@ -594,8 +761,8 @@ packet_baddata_active(Config) when is_list(Config) ->
{packet, cdr} |
ClientOpts]}]),
receive
- {Client, {other, {ssl_error, _Socket,
- {invalid_packet, _}},{error,closed},1}} -> ok;
+ {Client, {ssl_error, _, {invalid_packet, _}}} ->
+ ok;
Unexpected ->
ct:fail({unexpected, Unexpected})
end,
@@ -629,7 +796,8 @@ packet_baddata_passive(Config) when is_list(Config) ->
ClientOpts]}]),
receive
- {Client, {other, {error, {invalid_packet, _}},{error,closed}, 1}} -> ok;
+ {Client, {error, {invalid_packet, _}}} ->
+ ok;
Unexpected ->
ct:fail({unexpected, Unexpected})
end,
@@ -662,11 +830,11 @@ packet_size_active(Config) when is_list(Config) ->
{packet, 4}, {packet_size, 10} |
ClientOpts]}]),
receive
- {Client, {other, {ssl_error, _Socket,
- {invalid_packet, _}},{error,closed},1}} -> ok;
+ {Client, {ssl_error, _, {invalid_packet, _}}}->
+ ok;
Unexpected ->
ct:fail({unexpected, Unexpected})
- end,
+ end,
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
@@ -697,7 +865,8 @@ packet_size_passive(Config) when is_list(Config) ->
{packet, 4}, {packet_size, 30} |
ClientOpts]}]),
receive
- {Client, {other, {error, {invalid_packet, _}},{error,closed},1}} -> ok;
+ {Client, {error, {invalid_packet, _}}} ->
+ ok;
Unexpected ->
ct:fail({unexpected, Unexpected})
end,
@@ -1132,7 +1301,7 @@ server_send_trailer(Socket, Trailer)->
client_http_decode_trailer_active(Socket) ->
receive
{ssl, Socket,
- {http_header,36,'Content-Encoding',undefined,"gzip"}} ->
+ {http_header,36,'Content-Encoding',"Content-Encoding","gzip"}} ->
ok;
Other1 ->
exit({?LINE, Other1})
@@ -1182,7 +1351,7 @@ packet_httph_bin_active(Config) when is_list(Config) ->
client_http_decode_trailer_bin_active(Socket) ->
receive
{ssl, Socket,
- {http_header,36,'Content-Encoding',undefined, <<"gzip">>}} ->
+ {http_header,36,'Content-Encoding',<<"Content-Encoding">>, <<"gzip">>}} ->
ok;
Other1 ->
exit({?LINE, Other1})
@@ -1234,7 +1403,7 @@ client_http_decode_trailer_active_once(Socket) ->
ssl:setopts(Socket, [{active, once}]),
receive
{ssl, Socket,
- {http_header,36,'Content-Encoding',undefined,"gzip"}} ->
+ {http_header,36,'Content-Encoding',"Content-Encoding","gzip"}} ->
ok;
Other1 ->
exit({?LINE, Other1})
@@ -1286,7 +1455,7 @@ client_http_decode_trailer_bin_active_once(Socket) ->
ssl:setopts(Socket, [{active, once}]),
receive
{ssl, Socket,
- {http_header,36,'Content-Encoding',undefined, <<"gzip">>}} ->
+ {http_header,36,'Content-Encoding',<<"Content-Encoding">>, <<"gzip">>}} ->
ok;
Other1 ->
exit({?LINE, Other1})
@@ -1337,7 +1506,7 @@ packet_httph_passive(Config) when is_list(Config) ->
ssl_test_lib:close(Client).
client_http_decode_trailer_passive(Socket) ->
- {ok,{http_header,36,'Content-Encoding',undefined,"gzip"}} = ssl:recv(Socket, 0),
+ {ok,{http_header,36,'Content-Encoding',"Content-Encoding","gzip"}} = ssl:recv(Socket, 0),
{ok, http_eoh} = ssl:recv(Socket, 0),
ok.
@@ -1377,7 +1546,7 @@ packet_httph_bin_passive(Config) when is_list(Config) ->
ssl_test_lib:close(Client).
client_http_decode_trailer_bin_passive(Socket) ->
- {ok,{http_header,36,'Content-Encoding',undefined,<<"gzip">>}} = ssl:recv(Socket, 0),
+ {ok,{http_header,36,'Content-Encoding',<<"Content-Encoding">>,<<"gzip">>}} = ssl:recv(Socket, 0),
{ok, http_eoh} = ssl:recv(Socket, 0),
ok.
@@ -2055,8 +2224,8 @@ passive_recv_packet(Socket, Data, N) ->
case ssl:recv(Socket, 0, 10000) of
{ok, Data} ->
passive_recv_packet(Socket, Data, N-1);
- Other ->
- {other, Other, ssl:connection_information(Socket, [session_id, cipher_suite]), N}
+ {error, _} = Other ->
+ Other
end.
send(Socket,_, 0) ->
@@ -2137,9 +2306,9 @@ active_packet(Socket, _, 0) ->
active_packet(Socket, Data, N) ->
receive
{ssl, Socket, Data} ->
- active_packet(Socket, Data, N -1);
+ active_packet(Socket, Data, N-1);
Other ->
- {other, Other, ssl:connection_information(Socket, [session_id, cipher_suite]),N}
+ Other
end.
assert_packet_opt(Socket, Type) ->
@@ -2150,7 +2319,7 @@ server_packet_decode(Socket, Packet) ->
{ssl, Socket, Packet} -> ok;
Other1 -> exit({?LINE, Other1})
end,
- ok = ssl:send(Socket, Packet),
+ spawn(fun() -> ssl:send(Socket, Packet) end),
receive
{ssl, Socket, Packet} -> ok;
Other2 -> exit({?LINE, Other2})
diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl
index eaf36ea6ff..e594c745b6 100644
--- a/lib/ssl/test/ssl_payload_SUITE.erl
+++ b/lib/ssl/test/ssl_payload_SUITE.erl
@@ -20,13 +20,89 @@
-module(ssl_payload_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-
--define(TIMEOUT, 600000).
-
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([server_echos_passive_small/0,
+ server_echos_passive_small/1,
+ server_echos_passive_chunk_small/0,
+ server_echos_passive_chunk_small/1,
+ server_echos_active_once_small/0,
+ server_echos_active_once_small/1,
+ server_echos_active_small/0,
+ server_echos_active_small/1,
+ client_echos_passive_small/0,
+ client_echos_passive_small/1,
+ client_echos_passive_chunk_small/0,
+ client_echos_passive_chunk_small/1,
+ client_echos_active_once_small/0,
+ client_echos_active_once_small/1,
+ client_echos_active_small/0,
+ client_echos_active_small/1,
+ server_echos_passive_big/0,
+ server_echos_passive_big/1,
+ server_echos_passive_chunk_big/0,
+ server_echos_passive_chunk_big/1,
+ server_echos_active_once_big/0,
+ server_echos_active_once_big/1,
+ server_echos_active_big/0,
+ server_echos_active_big/1,
+ client_echos_passive_big/0,
+ client_echos_passive_big/1,
+ client_echos_passive_chunk_big/0,
+ client_echos_passive_chunk_big/1,
+ client_echos_active_once_big/0,
+ client_echos_active_once_big/1,
+ client_echos_active_big/0,
+ client_echos_active_big/1,
+ server_echos_passive_huge/0,
+ server_echos_passive_huge/1,
+ server_echos_passive_chunk_huge/0,
+ server_echos_passive_chunk_huge/1,
+ server_echos_active_once_huge/0,
+ server_echos_active_once_huge/1,
+ server_echos_active_huge/0,
+ server_echos_active_huge/1,
+ client_echos_passive_huge/0,
+ client_echos_passive_huge/1,
+ client_echos_passive_chunk_huge/0,
+ client_echos_passive_chunk_huge/1,
+ client_echos_active_once_huge/0,
+ client_echos_active_once_huge/1,
+ client_echos_active_huge/0,
+ client_echos_active_huge/1,
+ client_active_once_server_close/0,
+ client_active_once_server_close/1]).
+
+%% Apply export
+-export([send/4,
+ send_close/2,
+ sender/2,
+ sender_active_once/2,
+ sender_active/2,
+ echoer/2,
+ echoer_chunk/2,
+ echoer_active_once/2,
+ echoer_active/2,
+ echo_recv/2,
+ echo_recv_chunk/3,
+ echo_active_once/2,
+ echo_active/2]).
+
+-define(TIMEOUT, {seconds, 20}).
+-define(TIMEOUT_LONG, {seconds, 80}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -36,8 +112,7 @@ all() ->
{group, 'tlsv1.3'},
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
- {group, 'tlsv1'},
- {group, 'sslv3'}
+ {group, 'tlsv1'}
].
groups() ->
@@ -45,8 +120,7 @@ groups() ->
{'tlsv1.3', [], payload_tests()},
{'tlsv1.2', [], payload_tests()},
{'tlsv1.1', [], payload_tests()},
- {'tlsv1', [], payload_tests()},
- {'sslv3', [], payload_tests()}
+ {'tlsv1', [], payload_tests()}
].
payload_tests() ->
@@ -95,26 +169,11 @@ end_per_suite(_Config) ->
application:stop(crypto).
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
+
init_per_testcase(TestCase, Config)
when TestCase == server_echos_passive_huge;
@@ -129,7 +188,7 @@ init_per_testcase(TestCase, Config)
"sparc-sun-solaris2.10" ->
{skip,"Will take to long time on an old Sparc"};
_ ->
- ct:timetrap({seconds, 90}),
+ ct:timetrap(?TIMEOUT_LONG),
Config
end;
@@ -142,11 +201,11 @@ init_per_testcase(TestCase, Config)
TestCase == client_echos_passive_chunk_big;
TestCase == client_echos_active_once_big;
TestCase == client_echos_active_big ->
- ct:timetrap({seconds, 60}),
+ ct:timetrap(?TIMEOUT_LONG),
Config;
init_per_testcase(_TestCase, Config) ->
- ct:timetrap({seconds, 15}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_TestCase, Config) ->
@@ -229,7 +288,7 @@ client_echos_passive_small(Config) when is_list(Config) ->
Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
-client_echos_passive_chunk__small() ->
+client_echos_passive_chunk_small() ->
[{doc, "Server sends 1000 bytes in passive mode to client, that receives them in chunks, "
"sends them back, and closes."}].
@@ -728,7 +787,7 @@ client_active_once_server_close(
send(_Socket, _Data, 0, _) ->
ok;
send(Socket, Data, Count, RecvEcho) ->
- ok = ssl:send(Socket, Data),
+ spawn(fun() -> ssl:send(Socket, Data) end),
RecvEcho(),
send(Socket, Data, Count - 1, RecvEcho).
diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl
index 31de7f9ad8..50d8089054 100644
--- a/lib/ssl/test/ssl_pem_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl
@@ -22,12 +22,30 @@
-module(ssl_pem_cache_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([pem_cleanup/0,
+ pem_cleanup/1,
+ clear_pem_cache/0,
+ clear_pem_cache/1,
+ invalid_insert/0,
+ invalid_insert/1
+ ]).
+
-define(CLEANUP_INTERVAL, 5000).
%%--------------------------------------------------------------------
@@ -120,7 +138,7 @@ clear_pem_cache(Config) when is_list(Config) ->
{status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
- [_,{FilRefDb, _} |_] = element(6, State),
+ [_,{FilRefDb, _} |_] = element(5, State),
{Server, Client} = basic_verify_test_no_close(Config),
CountReferencedFiles = fun({_, -1}, Acc) ->
Acc;
@@ -175,7 +193,7 @@ get_pem_cache() ->
{status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
- case element(6, State) of
+ case element(5, State) of
[_CertDb, _FileRefDb, PemCache| _] ->
PemCache;
_ ->
@@ -186,7 +204,7 @@ get_fileref_db() ->
{status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
- case element(6, State) of
+ case element(5, State) of
[_CertDb, {FileRefDb,_} | _] ->
FileRefDb;
_ ->
diff --git a/lib/ssl/test/ssl_renegotiate_SUITE.erl b/lib/ssl/test/ssl_renegotiate_SUITE.erl
index e2af2c937d..4b46863415 100644
--- a/lib/ssl/test/ssl_renegotiate_SUITE.erl
+++ b/lib/ssl/test/ssl_renegotiate_SUITE.erl
@@ -22,12 +22,54 @@
-module(ssl_renegotiate_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([client_renegotiate/0,
+ client_renegotiate/1,
+ server_renegotiate/0,
+ server_renegotiate/1,
+ client_secure_renegotiate/0,
+ client_secure_renegotiate/1,
+ client_secure_renegotiate_fallback/0,
+ client_secure_renegotiate_fallback/1,
+ client_renegotiate_reused_session/0,
+ client_renegotiate_reused_session/1,
+ server_renegotiate_reused_session/0,
+ server_renegotiate_reused_session/1,
+ client_no_wrap_sequence_number/0,
+ client_no_wrap_sequence_number/1,
+ server_no_wrap_sequence_number/0,
+ server_no_wrap_sequence_number/1,
+ renegotiate_dos_mitigate_active/0,
+ renegotiate_dos_mitigate_active/1,
+ renegotiate_dos_mitigate_passive/0,
+ renegotiate_dos_mitigate_passive/1,
+ renegotiate_dos_mitigate_absolute/0,
+ renegotiate_dos_mitigate_absolute/1
+ ]).
+
+%% Apply export
+-export([renegotiate/2,
+ renegotiate_reuse_session/2,
+ renegotiate_immediately/1,
+ renegotiate_rejected/1,
+ erlang_ssl_receive/2]).
+
-define(SLEEP, 500).
-define(RENEGOTIATION_DISABLE_TIME, 12000).
@@ -39,7 +81,6 @@ all() ->
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
].
@@ -50,8 +91,7 @@ groups() ->
{'tlsv1.3', [], renegotiate_tests()},
{'tlsv1.2', [], renegotiate_tests()},
{'tlsv1.1', [], renegotiate_tests()},
- {'tlsv1', [], renegotiate_tests()},
- {'sslv3', [], ssl3_renegotiate_tests()}
+ {'tlsv1', [], renegotiate_tests()}
].
renegotiate_tests() ->
@@ -93,27 +133,10 @@ end_per_suite(_Config) ->
application:stop(crypto).
init_per_group(GroupName, Config) ->
- ssl_test_lib:clean_tls_version(Config),
- case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- _ ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl:start(),
- Config;
- false ->
- {skip, "Missing crypto support"}
- end
- end.
+ ssl_test_lib:init_per_group(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(TestCase, Config) when TestCase == renegotiate_dos_mitigate_active;
TestCase == renegotiate_dos_mitigate_passive;
diff --git a/lib/ssl/test/ssl_rfc_5869_SUITE.erl b/lib/ssl/test/ssl_rfc_5869_SUITE.erl
index 8b2d1c2082..497a05107b 100644
--- a/lib/ssl/test/ssl_rfc_5869_SUITE.erl
+++ b/lib/ssl/test/ssl_rfc_5869_SUITE.erl
@@ -21,11 +21,35 @@
%%
-module(ssl_rfc_5869_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
+%% Common test
+-export([all/0,
+ init_per_suite/1,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([sha_256_basic/0,
+ sha_256_basic/1,
+ sha_256_long/0,
+ sha_256_long/1,
+ sha_256_no_salt/0,
+ sha_256_no_salt/1,
+ sha_basic/0,
+ sha_basic/1,
+ sha_long/0,
+ sha_long/1,
+ sha_no_salt/0,
+ sha_no_salt/1,
+ sha_default_salt/0,
+ sha_default_salt/1
+ ]).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl
index aa32c91be9..552be7ddd6 100644
--- a/lib/ssl/test/ssl_session_SUITE.erl
+++ b/lib/ssl/test/ssl_session_SUITE.erl
@@ -21,8 +21,7 @@
%%
-module(ssl_session_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include("tls_handshake.hrl").
-include("ssl_record.hrl").
@@ -30,6 +29,34 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_group/2,
+ end_per_testcase/2
+ ]).
+
+%% Test cases
+-export([reuse_session/0,
+ reuse_session/1,
+ reuse_session_expired/0,
+ reuse_session_expired/1,
+ server_does_not_want_to_reuse_session/0,
+ server_does_not_want_to_reuse_session/1,
+ explicit_session_reuse/0,
+ explicit_session_reuse/1,
+ no_reuses_session_server_restart_new_cert/0,
+ no_reuses_session_server_restart_new_cert/1,
+ no_reuses_session_server_restart_new_cert_file/0,
+ no_reuses_session_server_restart_new_cert_file/1,
+ session_table_stable_size_on_tcp_close/0,
+ session_table_stable_size_on_tcp_close/1
+ ]).
+
-define(SLEEP, 500).
-define(EXPIRE, 10).
@@ -41,7 +68,6 @@ all() ->
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
- {group, 'sslv3'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
].
@@ -52,14 +78,14 @@ groups() ->
{'tlsv1.3', [], session_tests() ++ tls_session_tests()},
{'tlsv1.2', [], session_tests() ++ tls_session_tests()},
{'tlsv1.1', [], session_tests() ++ tls_session_tests()},
- {'tlsv1', [], session_tests() ++ tls_session_tests()},
- {'sslv3', [], session_tests() ++ tls_session_tests()}
+ {'tlsv1', [], session_tests() ++ tls_session_tests()}
].
session_tests() ->
[reuse_session,
reuse_session_expired,
server_does_not_want_to_reuse_session,
+ explicit_session_reuse,
no_reuses_session_server_restart_new_cert,
no_reuses_session_server_restart_new_cert_file].
@@ -72,7 +98,7 @@ init_per_suite(Config0) ->
ok ->
ssl_test_lib:clean_start(),
Config = ssl_test_lib:make_rsa_cert(Config0),
- ssl_test_lib:make_dsa_cert(Config)
+ ssl_test_lib:make_rsa_1024_cert(Config)
catch _:_ ->
{skip, "Crypto did not start"}
end.
@@ -82,55 +108,44 @@ end_per_suite(_Config) ->
application:stop(crypto).
init_per_group(GroupName, Config) ->
- ssl_test_lib:clean_tls_version(Config),
- case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- _ ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl:start(),
- Config;
- false ->
- {skip, "Missing crypto support"}
- end
- end.
+ ssl_test_lib:init_per_group(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(reuse_session_expired, Config) ->
+ Versions = ssl_test_lib:protocol_version(Config),
ssl:stop(),
- application:load(ssl),
+ application:load(ssl),
ssl_test_lib:clean_env(),
+ ssl_test_lib:set_protocol_versions(Versions),
application:set_env(ssl, session_lifetime, ?EXPIRE),
- application:set_env(ssl, session_delay_cleanup_time, 500),
ssl:start(),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 30}),
Config;
init_per_testcase(_, Config) ->
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 15}),
Config.
-end_per_testcase(_TestCase, Config) ->
+end_per_testcase(reuse_session_expired, Config) ->
+ application:unset_env(ssl, session_lifetime),
+ Config;
+end_per_testcase(_, Config) ->
Config.
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-
reuse_session() ->
[{doc,"Test reuse of sessions (short handshake)"}].
reuse_session(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
-
- ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
+ Version = ssl_test_lib:protocol_version(Config),
+ ssl_test_lib:reuse_session([{versions,[Version]} | ClientOpts],
+ [{versions,[Version]} | ServerOpts], Config).
%%--------------------------------------------------------------------
reuse_session_expired() ->
[{doc,"Test sessions is not reused when it has expired"}].
@@ -184,6 +199,7 @@ reuse_session_expired(Config) when is_list(Config) ->
{from, self()}, {options, ClientOpts}]),
receive
{Client2, SID} ->
+ end_per_testcase(?FUNCTION_NAME, Config),
ct:fail(session_reused_when_session_expired);
{Client2, _} ->
ok
@@ -200,7 +216,7 @@ make_sure_expired(Host, Port, Id) ->
State = ssl_test_lib:state(Prop),
ClientCache = element(2, State),
- case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of
+ case ssl_client_session_cache_db:lookup(ClientCache, {{Host, Port}, Id}) of
undefined ->
ok;
#session{is_resumable = false} ->
@@ -258,14 +274,54 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client1).
+%%--------------------------------------------------------------------
+explicit_session_reuse() ->
+ [{doc,"Test {reuse_session, {ID, Data}}} option for explicit reuse of sessions not"
+ " saved in the clients automated session reuse"}].
+explicit_session_reuse(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ {Client0, Client0Sock} =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, [{reuse_sessions, false} | ClientOpts]},
+ return_socket
+ ]),
+
+ {ok, [{session_id, ID}, {session_data, SessData}]} = ssl:connection_information(Client0Sock, [session_id, session_data]),
+
+ ssl_test_lib:close(Client0),
+
+ Server ! listen,
+
+ {_, Client1Sock} =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, [{reuse_session, {ID, SessData}} | ClientOpts]},
+ return_socket]),
+
+ {ok, [{session_id, ID}]} = ssl:connection_information(Client1Sock, [session_id]).
+
+
+%%--------------------------------------------------------------------
no_reuses_session_server_restart_new_cert() ->
[{doc,"Check that a session is not reused if the server is restarted with a new cert."}].
no_reuses_session_server_restart_new_cert(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config),
- DsaClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config),
+ RSA1024ServerOpts = ssl_test_lib:ssl_options(server_rsa_1024_opts, Config),
+ RSA1024ClientOpts = ssl_test_lib:ssl_options(client_rsa_1024_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -278,34 +334,27 @@ no_reuses_session_server_restart_new_cert(Config) when is_list(Config) ->
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
SessionInfo =
receive
{Server, Info} ->
Info
end,
- %% Make sure session is registered
- ct:sleep(?SLEEP),
- Monitor = erlang:monitor(process, Server),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client0),
- receive
- {'DOWN', Monitor, _, _, _} ->
- ok
- end,
-
+
Server1 =
ssl_test_lib:start_server([{node, ServerNode}, {port, Port},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
- {options, [{reuseaddr, true} | DsaServerOpts]}]),
+ {options, [{reuseaddr, true} | RSA1024ServerOpts]}]),
Client1 =
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, DsaClientOpts}]),
+ {from, self()}, {options, RSA1024ClientOpts}]),
receive
{Client1, SessionInfo} ->
ct:fail(session_reused_when_server_has_new_cert);
@@ -323,7 +372,7 @@ no_reuses_session_server_restart_new_cert_file() ->
no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config),
+ RSA1024ServerOpts = ssl_test_lib:ssl_options(server_rsa_1024_verify_opts, Config),
PrivDir = proplists:get_value(priv_dir, Config),
NewServerOpts0 = ssl_test_lib:new_config(PrivDir, ServerOpts),
@@ -354,7 +403,7 @@ no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) ->
ssl:clear_pem_cache(),
- NewServerOpts1 = ssl_test_lib:new_config(PrivDir, DsaServerOpts),
+ NewServerOpts1 = ssl_test_lib:new_config(PrivDir, RSA1024ServerOpts),
Server1 =
ssl_test_lib:start_server([{node, ServerNode}, {port, Port},
@@ -382,37 +431,44 @@ session_table_stable_size_on_tcp_close(Config) when is_list(Config)->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
- [_, _,_, _, Prop] = StatusInfo,
- State = ssl_test_lib:state(Prop),
- ServerCache = element(3, State),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, [{reuseaddr, true} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
- N = ets:info(ServerCache, size),
+ Sup = whereis(ssl_server_session_cache_sup),
+
+ %% Will only be one process, that is one server, in our test senario
+ [{_, SessionCachePid, worker,[ssl_server_session_cache]}] = supervisor:which_children(Sup),
+
+
+ {SessionCacheCb, SessionCacheDb} = session_cachce_info(SessionCachePid),
+
+ N = SessionCacheCb:size(SessionCacheDb),
- Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
- {from, self()},
- {options, [{reuseaddr, true} | ServerOpts]}]),
- Port = ssl_test_lib:inet_port(Server),
faulty_client(Hostname, Port),
- check_table_did_not_grow(ServerCache, N).
+ check_table_did_not_grow(SessionCachePid, N).
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
-check_table_did_not_grow(ServerCache, N) ->
- ct:sleep(500),
- check_table_did_not_grow(ServerCache, N, 10).
-
-check_table_did_not_grow(_, _, 0) ->
- ct:fail(table_grew);
-check_table_did_not_grow(ServerCache, N, Tries) ->
- case ets:info(ServerCache, size) of
+session_cachce_info(SessionCache) ->
+ State = sys:get_state(SessionCache),
+ ServerCacheDb = element(4, State),
+ ServerCacheCb = element(2, State),
+ {ServerCacheCb, ServerCacheDb}.
+
+
+check_table_did_not_grow(SessionCachePid, N) ->
+ {SessionCacheCb, SessionCacheDb} = session_cachce_info(SessionCachePid),
+ ct:pal("Run ~p ~p", [SessionCacheCb, SessionCacheDb]),
+ case catch SessionCacheCb:size(SessionCacheDb) of
N ->
ok;
- _ ->
- ct:sleep(500),
- check_table_did_not_grow(ServerCache, N, Tries -1)
+ Size ->
+ ct:fail({table_grew, [{expected, N}, {got, Size}]})
end.
faulty_client(Host, Port) ->
@@ -425,18 +481,6 @@ faulty_client(Host, Port) ->
gen_tcp:close(Sock).
-server(LOpts, Port) ->
- {ok, LSock} = ssl:listen(Port, LOpts),
- Pid = spawn_link(?MODULE, accept_loop, [LSock]),
- ssl:controlling_process(LSock, Pid),
- Pid.
-
-accept_loop(Sock) ->
- {ok, CSock} = ssl:transport_accept(Sock),
- _ = ssl:handshake(CSock),
- accept_loop(Sock).
-
-
encode_client_hello(CH, Random) ->
HSBin = tls_handshake:encode_handshake(CH, {3,3}),
CS = connection_states(Random),
@@ -548,4 +592,4 @@ connection_states(Random) ->
client_random = Random,
server_random = undefined,
exportable = undefined},
- sequence_number => 0,server_verify_data => undefined}}.
+ sequence_number => 0,server_verify_data => undefined,max_fragment_length => undefined}}.
diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl
index 7f847888cc..8f67908ad7 100644
--- a/lib/ssl/test/ssl_session_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_session_cache_SUITE.erl
@@ -22,28 +22,73 @@
-module(ssl_session_cache_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
--define(DELAY, 500).
--define(SLEEP, 1000).
--define(TIMEOUT, 60000).
--define(LONG_TIMEOUT, 600000).
--define(MAX_TABLE_SIZE, 5).
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([session_cleanup/0,
+ session_cleanup/1,
+ session_cache_process_list/0,
+ session_cache_process_list/1,
+ session_cache_process_mnesia/0,
+ session_cache_process_mnesia/1,
+ client_unique_session/0,
+ client_unique_session/1,
+ max_table_size/0,
+ max_table_size/1,
+ save_specific_session/0,
+ save_specific_session/1
+ ]).
+
+%% Apply export
+-export([connection_info_result/1]).
-behaviour(ssl_session_cache_api).
%% For the session cache tests
-export([init/1, terminate/1, lookup/2, update/3,
- delete/2, foldl/3, select_session/2]).
+ size/1, delete/2, foldl/3, select_session/2]).
+
+-define(SLEEP, 1000).
+-define(TIMEOUT, {seconds, 20}).
+-define(MAX_TABLE_SIZE, 5).
+-define(CLIENT_CB, ssl_client_session_cache_db).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
-all() ->
+
+all() ->
+ [
+ {group, 'tlsv1.2'},
+ {group, 'tlsv1.1'},
+ {group, 'tlsv1'},
+ {group, 'dtlsv1.2'},
+ {group, 'dtlsv1'}
+ ].
+
+groups() ->
+ [{'dtlsv1.2', [], session_tests()},
+ {'dtlsv1', [], session_tests()},
+ {'tlsv1.2', [], session_tests()},
+ {'tlsv1.1', [], session_tests()},
+ {'tlsv1', [], session_tests()}
+ ].
+
+
+session_tests() ->
[session_cleanup,
session_cache_process_list,
session_cache_process_mnesia,
@@ -52,9 +97,6 @@ all() ->
save_specific_session
].
-groups() ->
- [].
-
init_per_suite(Config0) ->
catch crypto:stop(),
try crypto:start() of
@@ -70,11 +112,11 @@ end_per_suite(_Config) ->
ssl:stop(),
application:stop(crypto).
-init_per_group(_GroupName, Config) ->
- Config.
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:init_per_group(GroupName, Config).
-end_per_group(_GroupName, Config) ->
- Config.
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(session_cache_process_list, Config) ->
init_customized_session_cache(list, Config);
@@ -84,42 +126,52 @@ init_per_testcase(session_cache_process_mnesia, Config) ->
init_customized_session_cache(mnesia, Config);
init_per_testcase(session_cleanup, Config) ->
+ Versions = ssl_test_lib:protocol_version(Config),
ssl:stop(),
application:load(ssl),
+ ssl_test_lib:set_protocol_versions(Versions),
application:set_env(ssl, session_lifetime, 5),
- application:set_env(ssl, session_delay_cleanup_time, ?DELAY),
ssl:start(),
- ct:timetrap({seconds, 20}),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(client_unique_session, Config) ->
- ct:timetrap({seconds, 40}),
+ ct:timetrap(?TIMEOUT),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
Config;
init_per_testcase(save_specific_session, Config) ->
+ Versions = ssl_test_lib:protocol_version(Config),
ssl_test_lib:clean_start(),
- ct:timetrap({seconds, 5}),
+ ssl_test_lib:set_protocol_versions(Versions),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(max_table_size, Config) ->
+ Versions = ssl_test_lib:protocol_version(Config),
ssl:stop(),
application:load(ssl),
+ ssl_test_lib:set_protocol_versions(Versions),
application:set_env(ssl, session_cache_server_max, ?MAX_TABLE_SIZE),
application:set_env(ssl, session_cache_client_max, ?MAX_TABLE_SIZE),
- application:set_env(ssl, session_delay_cleanup_time, ?DELAY),
- ssl:start(),
+ ssl:start(),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 40}),
Config.
init_customized_session_cache(Type, Config) ->
+ Versions = ssl_test_lib:protocol_version(Config),
ssl:stop(),
application:load(ssl),
+ ssl_test_lib:set_protocol_versions(Versions),
application:set_env(ssl, session_cb, ?MODULE),
application:set_env(ssl, session_cb_init_args, [{type, Type}]),
ssl:start(),
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
catch (end_per_testcase(list_to_atom("session_cache_process" ++ atom_to_list(Type)),
Config)),
ets:new(ssl_test, [named_table, public, set]),
ets:insert(ssl_test, {type, Type}),
- ct:timetrap({seconds, 20}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(session_cache_process_list, Config) ->
@@ -133,7 +185,6 @@ end_per_testcase(session_cache_process_mnesia, Config) ->
ssl:start(),
end_per_testcase(default_action, Config);
end_per_testcase(session_cleanup, Config) ->
- application:unset_env(ssl, session_delay_cleanup_time),
application:unset_env(ssl, session_lifetime),
end_per_testcase(default_action, Config);
end_per_testcase(max_table_size, Config) ->
@@ -155,8 +206,8 @@ client_unique_session() ->
"sets up many connections"}].
client_unique_session(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
- ServerOpts = proplists:get_value(server_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -175,7 +226,7 @@ client_unique_session(Config) when is_list(Config) ->
State = ssl_test_lib:state(Prop),
ClientCache = element(2, State),
- 1 = ssl_session_cache:size(ClientCache),
+ 1 = ?CLIENT_CB:size(ClientCache),
ssl_test_lib:close(Server, 500),
ssl_test_lib:close(LastClient).
@@ -199,37 +250,30 @@ session_cleanup(Config) when is_list(Config) ->
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
SessionInfo =
receive
{Server, Info} ->
Info
end,
- %% Make sure session is registered
- ct:sleep(?SLEEP*2),
-
{status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
ClientCache = element(2, State),
- ServerCache = element(3, State),
- SessionTimer = element(7, State),
+ SessionTimer = element(6, State),
Id = proplists:get_value(session_id, SessionInfo),
- CSession = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}),
- SSession = ssl_session_cache:lookup(ServerCache, {Port, Id}),
+ CSession = ?CLIENT_CB:lookup(ClientCache, {{Hostname, Port}, Id}),
true = CSession =/= undefined,
- true = SSession =/= undefined,
%% Make sure session has expired and been cleaned up
check_timer(SessionTimer),
ct:sleep(?SLEEP), %% Make sure clean has had time to run
- undefined = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}),
- undefined = ssl_session_cache:lookup(ServerCache, {Port, Id}),
+ undefined = ?CLIENT_CB:lookup(ClientCache, {{Hostname, Port}, Id}),
process_flag(trap_exit, false),
ssl_test_lib:close(Server),
@@ -253,8 +297,8 @@ save_specific_session() ->
}].
save_specific_session(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
- ServerOpts = proplists:get_value(server_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -292,7 +336,7 @@ save_specific_session(Config) when is_list(Config) ->
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
ClientCache = element(2, State),
- 2 = ssl_session_cache:size(ClientCache),
+ 2 = ?CLIENT_CB:size(ClientCache),
Server ! listen,
@@ -336,13 +380,10 @@ max_table_size(Config) when is_list(Config) ->
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
ClientCache = element(2, State),
- ServerCache = element(3, State),
- N = ssl_session_cache:size(ServerCache),
- M = ssl_session_cache:size(ClientCache),
- ct:pal("~p",[{N, M}]),
+ M = ?CLIENT_CB:size(ClientCache),
+ ct:pal("~p",[M]),
ssl_test_lib:close(Server, 500),
ssl_test_lib:close(LastClient),
- true = N =< ?MAX_TABLE_SIZE,
true = M =< ?MAX_TABLE_SIZE.
%%--------------------------------------------------------------------
@@ -356,7 +397,7 @@ init(Opts) ->
mnesia ->
mnesia:start(),
Name = atom_to_list(proplists:get_value(role, Opts)),
- TabName = list_to_atom(Name ++ "sess_cache"),
+ TabName = list_to_atom(Name ++ "sess_cache" ++ erlang:pid_to_list(self())),
{atomic,ok} = mnesia:create_table(TabName, []),
TabName
end.
@@ -374,6 +415,16 @@ terminate(Cache) ->
mnesia:delete_table(Cache)
end.
+size(Cache) ->
+ case session_cb() of
+ list ->
+ Cache ! {self(), size},
+ receive {Cache, Res} -> Res end;
+ mnesia ->
+ mnesia:table_info(Cache, size)
+ end.
+
+
lookup(Cache, Key) ->
case session_cb() of
list ->
@@ -410,7 +461,7 @@ delete(Cache, Key) ->
mnesia ->
{atomic, ok} =
mnesia:transaction(fun() ->
- mnesia:delete(Cache, Key)
+ mnesia:delete(Cache, Key, write)
end)
end.
@@ -423,22 +474,26 @@ foldl(Fun, Acc, Cache) ->
Foldl = fun() ->
mnesia:foldl(Fun, Acc, Cache)
end,
- {atomic, Res} = mnesia:transaction(Foldl),
- Res
+ case mnesia:transaction(Foldl) of
+ {atomic, {_,Key, Value}} ->
+ {Key, Value};
+ Error ->
+ Error
+ end
end.
select_session(Cache, PartialKey) ->
case session_cb() of
list ->
- Cache ! {self(),select_session, PartialKey},
+ Cache ! {self(),select_session, PartialKey},
receive
- {Cache, Res} ->
+ {_Cache, Res} ->
Res
end;
mnesia ->
Sel = fun() ->
mnesia:select(Cache,
- [{{Cache,{PartialKey,'_'}, '$1'},
+ [{{Cache, {PartialKey,'_'}, '$1'},
[],['$1']}])
end,
{atomic, Res} = mnesia:transaction(Sel),
@@ -449,6 +504,9 @@ session_loop(Sess) ->
receive
terminate ->
ok;
+ {Pid, size} ->
+ Pid ! {self(), length(Sess)},
+ session_loop(Sess);
{Pid, lookup, Key} ->
case lists:keysearch(Key,1,Sess) of
{value, {Key,Value}} ->
@@ -475,7 +533,13 @@ session_loop(Sess) ->
Sessions = lists:foldl(Sel, [], Sess),
Pid ! {self(), Sessions},
session_loop(Sess)
- end.
+ end.
+%%--------------------------------------------------------------------
+%%% callback functions
+%%--------------------------------------------------------------------
+
+connection_info_result(Socket) ->
+ ssl:connection_information(Socket, [protocol, cipher_suite]).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -504,14 +568,12 @@ clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N) ->
wait_for_server(),
clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N-1).
-connection_info_result(Socket) ->
- ssl:connection_information(Socket, [protocol, cipher_suite]).
check_timer(Timer) ->
case erlang:read_timer(Timer) of
false ->
{status, _, _, _} = sys:get_status(whereis(ssl_manager)),
- timer:sleep(?SLEEP),
+ ct:sleep(?SLEEP),
{status, _, _, _} = sys:get_status(whereis(ssl_manager)),
ok;
Int ->
diff --git a/lib/ssl/test/ssl_session_cache_api_SUITE.erl b/lib/ssl/test/ssl_session_cache_api_SUITE.erl
new file mode 100644
index 0000000000..03f88f755f
--- /dev/null
+++ b/lib/ssl/test/ssl_session_cache_api_SUITE.erl
@@ -0,0 +1,105 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssl_session_cache_api_SUITE).
+
+-behaviour(ct_suite).
+
+-include_lib("common_test/include/ct.hrl").
+-include("tls_handshake.hrl").
+
+%% Callback functions
+-export([all/0]).
+
+%% Testcases
+-export([server_cb/0,
+ server_cb/1,
+ client_cb/0,
+ client_cb/1
+ ]).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+all() ->
+ [server_cb,
+ client_cb].
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+server_cb() ->
+ [{doc, "Test ssl_session_cache_api server callback implementation"
+ "Note that default implementation is treated special to avoid"
+ "an extra process to handle the functional db structure used"
+ "The callback is provided for the main purpose of having a"
+ "session table that may survive node restart an assumes
+ a db reference that is not updated"
+ }].
+
+server_cb(Config) when is_list(Config) ->
+ Cb = ssl_server_session_cache_db,
+ Db0 = Cb:init([]),
+ Id0 = crypto:strong_rand_bytes(32),
+ DummySession0 = #session{session_id = Id0},
+ 0 = Cb:size(Db0),
+
+ Db1 = Cb:update(Db0, Id0, DummySession0),
+ 1 = Cb:size(Db1),
+
+ Id1 = crypto:strong_rand_bytes(32),
+ DummySession1 = #session{session_id = Id1},
+ Db2 = Cb:update(Db1, Id1, DummySession1),
+ 2 = Cb:size(Db2),
+
+ DummySession0 = Cb:lookup(Db2, Id0),
+ DummySession1 = Cb:lookup(Db2, Id1),
+
+ Db3 = Cb:delete(Db2, Id1),
+ 1 = Cb:size(Db3),
+ undefined = Cb:lookup(Db3, crypto:strong_rand_bytes(32)),
+ Cb:terminate(Db3).
+
+client_cb() ->
+ [{doc, "Test ssl_session_cache_api client callback implementation"}].
+
+client_cb(Config) when is_list(Config) ->
+ Cb = ssl_client_session_cache_db,
+ Db = Cb:init([]),
+ Id0 = crypto:strong_rand_bytes(32),
+ DummySession0 = #session{session_id = Id0},
+ 0 = Cb:size(Db),
+
+ Cb:update(Db, Id0, DummySession0),
+ 1 = Cb:size(Db),
+
+ Id1 = crypto:strong_rand_bytes(32),
+ DummySession1 = #session{session_id = Id1},
+ Cb:update(Db, Id1, DummySession1),
+ 2 = Cb:size(Db),
+
+ DummySession0 = Cb:lookup(Db, Id0),
+ DummySession1 = Cb:lookup(Db, Id1),
+ undefined = Cb:lookup(Db, crypto:strong_rand_bytes(32)),
+
+ Cb:delete(Db, Id1),
+ 1 = Cb:size(Db),
+ Cb:terminate(Db).
diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl
index 3d41b59223..d39afaa5b7 100644
--- a/lib/ssl/test/ssl_session_ticket_SUITE.erl
+++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl
@@ -20,6 +20,8 @@
-module(ssl_session_ticket_SUITE).
+-behaviour(ct_suite).
+
%% Callback functions
-export([all/0,
groups/0,
@@ -33,12 +35,42 @@
%% Testcases
-export([basic/0,
basic/1,
+ basic_anti_replay/0,
+ basic_anti_replay/1,
+ basic_stateful_stateless/0,
+ basic_stateful_stateless/1,
+ basic_stateless_stateful/0,
+ basic_stateless_stateful/1,
+ basic_stateful_stateless_anti_replay/0,
+ basic_stateful_stateless_anti_replay/1,
+ basic_stateless_stateful_anti_replay/0,
+ basic_stateless_stateful_anti_replay/1,
+ basic_stateful_stateless_faulty_ticket/0,
+ basic_stateful_stateless_faulty_ticket/1,
+ basic_stateless_stateful_faulty_ticket/0,
+ basic_stateless_stateful_faulty_ticket/1,
hello_retry_request/0,
hello_retry_request/1,
multiple_tickets/0,
multiple_tickets/1,
multiple_tickets_2hash/0,
- multiple_tickets_2hash/1]).
+ multiple_tickets_2hash/1,
+ early_data_client_too_much_data/0,
+ early_data_client_too_much_data/1,
+ early_data_trial_decryption/0,
+ early_data_trial_decryption/1,
+ early_data_trial_decryption_failure/0,
+ early_data_trial_decryption_failure/1,
+ early_data_decryption_failure/0,
+ early_data_decryption_failure/1,
+ early_data_disabled_small_limit/0,
+ early_data_disabled_small_limit/1,
+ early_data_enabled_small_limit/0,
+ early_data_enabled_small_limit/1,
+ early_data_basic/0,
+ early_data_basic/1,
+ early_data_basic_auth/0,
+ early_data_basic_auth/1]).
-include("tls_handshake.hrl").
@@ -56,15 +88,36 @@ all() ->
].
groups() ->
- [{'tlsv1.3', [], [{group, stateful}, {group, stateless}]},
+ [{'tlsv1.3', [], [{group, stateful},
+ {group, stateless},
+ {group, mixed}]},
{stateful, [], session_tests()},
- {stateless, [], session_tests()}].
+ {stateless, [], session_tests() ++ [basic_anti_replay]},
+ {mixed, [], mixed_tests()}].
session_tests() ->
[basic,
hello_retry_request,
multiple_tickets,
- multiple_tickets_2hash].
+ multiple_tickets_2hash,
+ early_data_client_too_much_data,
+ early_data_trial_decryption,
+ early_data_trial_decryption_failure,
+ early_data_decryption_failure,
+ early_data_disabled_small_limit,
+ early_data_enabled_small_limit,
+ early_data_basic,
+ early_data_basic_auth].
+
+mixed_tests() ->
+ [
+ basic_stateful_stateless,
+ basic_stateless_stateful,
+ basic_stateful_stateless_anti_replay,
+ basic_stateless_stateful_anti_replay,
+ basic_stateful_stateless_faulty_ticket,
+ basic_stateless_stateful_faulty_ticket
+ ].
init_per_suite(Config0) ->
catch crypto:stop(),
@@ -98,6 +151,7 @@ init_per_testcase(_, Config) ->
Config.
end_per_testcase(_TestCase, Config) ->
+ application:unset_env(ssl, server_session_ticket_max_early_data),
Config.
%%--------------------------------------------------------------------
@@ -158,6 +212,159 @@ basic(Config) when is_list(Config) ->
ssl_test_lib:close(Server0),
ssl_test_lib:close(Client1).
+basic_anti_replay() ->
+ [{doc,"Test session resumption with stateless session tickets and anti_replay (erlang client - erlang server)"}].
+basic_anti_replay(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ServerOpts = [{session_tickets, ServerTicketMode}, {log_level, debug},
+ {anti_replay, '10k'},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
+basic_stateful_stateless() ->
+ [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateful_stateless(Config) when is_list(Config) ->
+ do_test_mixed(Config,
+ [{session_tickets, auto},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateful},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateless},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateless_stateful() ->
+ [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateless_stateful(Config) when is_list(Config) ->
+ do_test_mixed(Config,
+ [{session_tickets, auto},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateless},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateful},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateful_stateless_anti_replay() ->
+ [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateful_stateless_anti_replay(Config) when is_list(Config) ->
+ do_test_mixed(Config,
+ [{session_tickets, auto},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateful},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateless},
+ {log_level, debug},
+ {anti_replay, '10k'},
+ {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateless_stateful_anti_replay() ->
+ [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateless_stateful_anti_replay(Config) when is_list(Config) ->
+ do_test_mixed(Config,
+ [{session_tickets, auto},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateless},
+ {log_level, debug},
+ {anti_replay, '10k'},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateful},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateful_stateless_faulty_ticket() ->
+ [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateful_stateless_faulty_ticket(Config) when is_list(Config) ->
+ do_test_mixed(Config,
+ [{session_tickets, auto},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, manual},
+ {use_ticket, [<<131,100,0,12,"faultyticket">>,
+ <<"faulty ticket">>]},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateless},
+ {log_level, debug},
+ {anti_replay, '10k'},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateful},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}]).
+
+basic_stateless_stateful_faulty_ticket() ->
+ [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+basic_stateless_stateful_faulty_ticket(Config) when is_list(Config) ->
+ do_test_mixed(Config,
+ [{session_tickets, auto},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, manual},
+ {use_ticket, [<<"faulty ticket">>,
+ <<131,100,0,12,"faultyticket">>]},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateless},
+ {log_level, debug},
+ {anti_replay, '10k'},
+ {versions, ['tlsv1.2','tlsv1.3']}],
+ [{session_tickets, stateful},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}]).
+
hello_retry_request() ->
[{doc,"Test session resumption with session tickets and hello_retry_request (erlang client - erlang server)"}].
hello_retry_request(Config) when is_list(Config) ->
@@ -401,7 +608,578 @@ multiple_tickets_2hash(Config) when is_list(Config) ->
process_flag(trap_exit, false),
ssl_test_lib:close(Server0).
+early_data_trial_decryption() ->
+ [{doc,"Test trial decryption when server rejects early data (erlang client - erlang server)"}].
+early_data_trial_decryption(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send maximum sized early data to verify calculation of plain text size
+ %% in the server.
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 16384)}|ClientOpts1],
+
+ %% Disabled early data triggers trial decryption upon receiving early data
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, disabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts1}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts2}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
+early_data_client_too_much_data() ->
+ [{doc,"Client sending too much early data (erlang client - erlang server)"}].
+early_data_client_too_much_data(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, manual}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send more early data than max_early_data_size to verify calculation
+ %% of plain text size in the server.
+ MaxEarlyDataSize = 10000,
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 16384)}|ClientOpts1],
+
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, disabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ application:set_env(ssl, server_session_ticket_max_early_data, MaxEarlyDataSize),
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ application:unset_env(ssl, server_session_ticket_max_early_data),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 1}]}},
+ {from, self()}, {options, ClientOpts1}]),
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+ ssl_test_lib:verify_session_ticket_extension(Tickets0, MaxEarlyDataSize),
+ %% ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [false, no_reply, no_tickets]}},
+ {from, self()}, {options, [{use_ticket, Tickets0}|ClientOpts2]}]),
+ ssl_test_lib:check_client_alert(Client1, illegal_parameter),
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0).
+
+early_data_trial_decryption_failure() ->
+ [{doc,"Emulate faulty client that sends too much early data (erlang client - erlang server)"}].
+early_data_trial_decryption_failure(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, manual}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send more early data than max_early_data_size to verify calculation
+ %% of plain text size in the server.
+ MaxEarlyDataSize = 10000,
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 16385)}|ClientOpts1],
+
+ %% Disabled early data triggers trial decryption upon receiving early data
+ %% up to the configured amount. If more data is received the server triggers
+ %% a bad_record_mac alert.
+ %% It is not possible to trigger this condition in normal use cases:
+ %% - The ssl client in auto mode has a built in protection against sending
+ %% too much early data. It will not send any early data.
+ %% - The ssl client in manual mode can only send the mount that is received
+ %% in the ticket used for the 0-RTT handshake. If more data is sent the
+ %% client will trigger an illegal_parameter alert (too_much_early_data).
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, disabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ application:set_env(ssl, server_session_ticket_max_early_data, MaxEarlyDataSize),
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 1}]}},
+ {from, self()}, {options, ClientOpts1}]),
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+ %% Simulate a faulty client by updating the max_early_data_size extension in
+ %% the received session ticket
+ Tickets1 = ssl_test_lib:update_session_ticket_extension(Tickets0, 16385),
+ %% ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [false, no_reply, no_tickets]}},
+ {from, self()}, {options, [{use_ticket, Tickets1}|ClientOpts2]}]),
+ ssl_test_lib:check_server_alert(Server0, bad_record_mac),
+ process_flag(trap_exit, false),
+ application:unset_env(ssl, server_session_ticket_max_early_data),
+ ssl_test_lib:close(Server0).
+
+early_data_decryption_failure() ->
+ [{doc,"Emulate faulty client that sends too much early data - server early_data enabled (erlang client - erlang server)"}].
+early_data_decryption_failure(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, manual}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send more early data than max_early_data_size to verify calculation
+ %% of plain text size in the server.
+ MaxEarlyDataSize = 10000,
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 16385)}|ClientOpts1],
+
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, enabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ application:set_env(ssl, server_session_ticket_max_early_data, MaxEarlyDataSize),
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 1}]}},
+ {from, self()}, {options, ClientOpts1}]),
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+ %% Simulate a faulty client by updating the max_early_data_size extension in
+ %% the received session ticket
+ Tickets1 = ssl_test_lib:update_session_ticket_extension(Tickets0, 16385),
+ %% ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client_error([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [false, no_reply, no_tickets]}},
+ {from, self()}, {options, [{use_ticket, Tickets1}|ClientOpts2]}]),
+ ssl_test_lib:check_server_alert(Server0, unexpected_message),
+ process_flag(trap_exit, false),
+ application:unset_env(ssl, server_session_ticket_max_early_data),
+ ssl_test_lib:close(Server0).
+
+early_data_disabled_small_limit() ->
+ [{doc,"Test trial decryption when server rejects early data (erlang client - erlang server)"}].
+early_data_disabled_small_limit(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send maximum sized early data to verify calculation of plain text size
+ %% in the server.
+ MaxEarlyDataSize = 5,
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 4)}|ClientOpts1],
+
+ %% Disabled early data triggers trial decryption upon receiving early data
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, disabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+ application:set_env(ssl, server_session_ticket_max_early_data, MaxEarlyDataSize),
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts1}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts2}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ application:unset_env(ssl, server_session_ticket_max_early_data),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
+early_data_enabled_small_limit() ->
+ [{doc,"Test decryption when server accepts early data (erlang client - erlang server)"}].
+early_data_enabled_small_limit(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send maximum sized early data to verify calculation of plain text size
+ %% in the server.
+ MaxEarlyDataSize = 5,
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 4)}|ClientOpts1],
+
+ %% Disabled early data triggers trial decryption upon receiving early data
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, enabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+ application:set_env(ssl, server_session_ticket_max_early_data, MaxEarlyDataSize),
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts1}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts2}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ application:unset_env(ssl, server_session_ticket_max_early_data),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
+early_data_basic() ->
+ [{doc,"Test early data when client is not authenticated (erlang client - erlang server)"}].
+early_data_basic(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send maximum sized early data to verify calculation of plain text size
+ %% in the server.
+ EarlyData = binary:copy(<<"F">>, 16384),
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 16384)}|ClientOpts1],
+
+ %% Disabled early data triggers trial decryption upon receiving early data
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, enabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts1}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_server_early_data,
+ [wait_reply, EarlyData]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts2}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
+early_data_basic_auth() ->
+ [{doc,"Test early data when client is authenticated (erlang client - erlang server)"}].
+early_data_basic_auth(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts1 = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ %% Send maximum sized early data to verify calculation of plain text size
+ %% in the server.
+ EarlyData = binary:copy(<<"F">>, 16384),
+ ClientOpts2 = [{early_data, binary:copy(<<"F">>, 16384)}|ClientOpts1],
+
+ %% Disabled early data triggers trial decryption upon receiving early data
+ ServerOpts = [{session_tickets, ServerTicketMode}, {early_data, enabled},
+ {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts1}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_server_early_data,
+ [wait_reply, EarlyData]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% TODO This test should fail!
+ %% State transition is not implemented from wait_eoed to wait_cert!
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()},
+ {options,
+ proplists:delete(keyfile,
+ proplists:delete(certfile, ClientOpts2))}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
+
+do_test_mixed(Config, COpts, SOpts1, SOpts2) when is_list(Config) ->
+ do_test_mixed(Config, COpts, COpts, SOpts1, SOpts2).
+%%
+do_test_mixed(Config, COpts1, COpts2, SOpts1, SOpts2) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Configure session tickets
+ ClientOpts1 = COpts1 ++ ClientOpts0,
+ ServerOpts1 = SOpts1 ++ ServerOpts0,
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts1}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts1}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+ ssl_test_lib:close(Server0),
+
+ ClientOpts2 = COpts2 ++ ClientOpts0,
+ ServerOpts2 = SOpts2 ++ ServerOpts0,
+
+ Server1 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts2}]),
+ Port1 = ssl_test_lib:inet_port(Server1),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts2}]),
+ ssl_test_lib:check_result(Server1, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server1),
+ ssl_test_lib:close(Client1).
diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl
index f9fa6bf735..9e2f9fddb1 100644
--- a/lib/ssl/test/ssl_sni_SUITE.erl
+++ b/lib/ssl/test/ssl_sni_SUITE.erl
@@ -21,19 +21,55 @@
-module(ssl_sni_SUITE).
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/inet.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([no_sni_header/1,
+ sni_match/1,
+ sni_no_match/1,
+ no_sni_header_fun/1,
+ sni_match_fun/1,
+ sni_no_match_fun/1,
+ dns_name/1,
+ ip_fallback/1,
+ no_ip_fallback/1,
+ dns_name_reuse/1,
+ customize_hostname_check/0,
+ customize_hostname_check/1,
+ sni_no_trailing_dot/0,
+ sni_no_trailing_dot/1,
+ hostname_trailing_dot/0,
+ hostname_trailing_dot/1
+ ]).
+
+%% Apply export
+-export([send_and_hostname/1,
+ recv_and_certificate/1
+ ]).
+
+-define(TIMEOUT, {seconds, 6}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
all() ->
- [{group, 'tlsv1.2'},
+ [{group, 'tlsv1.3'},
+ {group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
{group, 'dtlsv1.2'},
@@ -42,6 +78,7 @@ all() ->
groups() ->
[
+ {'tlsv1.3', [], sni_tests()},
{'tlsv1.2', [], sni_tests()},
{'tlsv1.1', [], sni_tests()},
{'tlsv1', [], sni_tests()},
@@ -84,6 +121,11 @@ init_per_suite(Config0) ->
catch _:_ ->
{skip, "Crypto did not start"}
end.
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:init_per_group(GroupName, Config).
+
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
end_per_suite(_) ->
ssl:stop(),
@@ -91,13 +133,13 @@ end_per_suite(_) ->
init_per_testcase(customize_hostname_check, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
- ssl_test_lib:clean_start(),
- ct:timetrap({seconds, 5}),
+ ssl_test_lib:clean_start(keep_version),
+ ct:timetrap(?TIMEOUT),
Config;
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]),
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_TestCase, Config) ->
@@ -127,41 +169,46 @@ sni_no_match_fun(Config) ->
dns_name(Config) ->
Hostname = "OTP.test.server",
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{extensions, [#'Extension'{extnID =
- ?'id-ce-subjectAltName',
- extnValue = [{dNSName, Hostname}],
- critical = false}]},
- {key, ssl_test_lib:hardcode_rsa_key(3)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{extensions, [#'Extension'{extnID =
+ ?'id-ce-subjectAltName',
+ extnValue = [{dNSName, Hostname}],
+ critical = false}]},
+ {key, ssl_test_lib:hardcode_rsa_key(3)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
- successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, Hostname} | ClientConf], undefined, Config),
- unsuccessfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, "foo"} | ClientConf], undefined, Config),
- successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, disable} | ClientConf], undefined, Config).
+ successfull_connect(ServerConf, [{verify, verify_peer},
+ {server_name_indication, Hostname} | ClientConf], undefined, Config),
+ unsuccessfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, "foo"} | ClientConf],
+ undefined, Config),
+ successfull_connect(ServerConf, [{verify, verify_peer}, {server_name_indication, disable} | ClientConf],
+ undefined, Config).
ip_fallback(Config) ->
Hostname = net_adm:localhost(),
{ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()),
IPStr = tuple_to_list(IP),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{extensions, [#'Extension'{extnID =
- ?'id-ce-subjectAltName',
- extnValue = [{dNSName, Hostname},
- {iPAddress, IPStr}],
- critical = false}]},
- {key, ssl_test_lib:hardcode_rsa_key(3)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{extensions, [#'Extension'{extnID =
+ ?'id-ce-subjectAltName',
+ extnValue = [{dNSName, Hostname},
+ {iPAddress, IPStr}],
+ critical = false}]},
+ {key, ssl_test_lib:hardcode_rsa_key(3)}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config).
@@ -169,42 +216,44 @@ no_ip_fallback(Config) ->
Hostname = net_adm:localhost(),
{ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{extensions, [#'Extension'{extnID =
- ?'id-ce-subjectAltName',
- extnValue = [{dNSName, Hostname}],
- critical = false}]},
- {key, ssl_test_lib:hardcode_rsa_key(3)}
- ]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{extensions, [#'Extension'{extnID =
+ ?'id-ce-subjectAltName',
+ extnValue = [{dNSName, Hostname}],
+ critical = false}]},
+ {key, ssl_test_lib:hardcode_rsa_key(3)}
+ ]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config).
dns_name_reuse(Config) ->
SNIHostname = "OTP.test.server",
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{extensions, [#'Extension'{extnID =
- ?'id-ce-subjectAltName',
- extnValue = [{dNSName, SNIHostname}],
- critical = false}
- ]},
- {key, ssl_test_lib:hardcode_rsa_key(3)}
- ]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{extensions, [#'Extension'{extnID =
+ ?'id-ce-subjectAltName',
+ extnValue = [{dNSName, SNIHostname}],
+ critical = false}
+ ]},
+ {key, ssl_test_lib:hardcode_rsa_key(3)}
+ ]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
+
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
Server =
@@ -343,27 +392,8 @@ hostname_trailing_dot(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-%% Internal Functions ------------------------------------------------
+%% Callback Functions ------------------------------------------------
%%--------------------------------------------------------------------
-ssl_recv(SSLSocket, Expect) ->
- ssl_recv(SSLSocket, "", Expect).
-
-ssl_recv(SSLSocket, CurrentData, ExpectedData) ->
- receive
- {ssl, SSLSocket, Data} ->
- NeweData = CurrentData ++ Data,
- case NeweData of
- ExpectedData ->
- ok;
- _ ->
- ssl_recv(SSLSocket, NeweData, ExpectedData)
- end;
- Other ->
- ct:fail({unexpected_message, Other})
- after 4000 ->
- ct:fail({timeout, CurrentData, ExpectedData})
- end.
-
send_and_hostname(SSLSocket) ->
ssl:send(SSLSocket, "OK"),
case ssl:connection_information(SSLSocket, [sni_hostname]) of
@@ -373,18 +403,6 @@ send_and_hostname(SSLSocket) ->
undefined
end.
-rdnPart([[#'AttributeTypeAndValue'{type=Type, value=Value} | _] | _], Type) ->
- Value;
-rdnPart([_ | Tail], Type) ->
- rdnPart(Tail, Type);
-rdnPart([], _) ->
- unknown.
-
-rdn_to_string({utf8String, Binary}) ->
- erlang:binary_to_list(Binary);
-rdn_to_string({printableString, String}) ->
- String.
-
recv_and_certificate(SSLSocket) ->
ssl_recv(SSLSocket, "OK"),
{ok, PeerCert} = ssl:peercert(SSLSocket),
@@ -393,6 +411,9 @@ recv_and_certificate(SSLSocket) ->
ct:log("Subject of certificate received from server: ~p", [Subject]),
rdn_to_string(rdnPart(Subject, ?'id-at-commonName')).
+%%--------------------------------------------------------------------
+%% Internal Functions ------------------------------------------------
+%%--------------------------------------------------------------------
run_sni_fun_handshake(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) ->
ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, "
"ExpectedSNIHostname: ~p, ExpectedCN: ~p",
@@ -494,3 +515,33 @@ trailing_dot_hostname(HostName) ->
{skip, "Trailing dot conf not possible"}
end.
+ssl_recv(SSLSocket, Expect) ->
+ ssl_recv(SSLSocket, "", Expect).
+
+ssl_recv(SSLSocket, CurrentData, ExpectedData) ->
+ receive
+ {ssl, SSLSocket, Data} ->
+ NeweData = CurrentData ++ Data,
+ case NeweData of
+ ExpectedData ->
+ ok;
+ _ ->
+ ssl_recv(SSLSocket, NeweData, ExpectedData)
+ end;
+ Other ->
+ ct:fail({unexpected_message, Other})
+ after 4000 ->
+ ct:fail({timeout, CurrentData, ExpectedData})
+ end.
+
+rdnPart([[#'AttributeTypeAndValue'{type=Type, value=Value} | _] | _], Type) ->
+ Value;
+rdnPart([_ | Tail], Type) ->
+ rdnPart(Tail, Type);
+rdnPart([], _) ->
+ unknown.
+
+rdn_to_string({utf8String, Binary}) ->
+ erlang:binary_to_list(Binary);
+rdn_to_string({printableString, String}) ->
+ String.
diff --git a/lib/ssl/test/ssl_socket_SUITE.erl b/lib/ssl/test/ssl_socket_SUITE.erl
index d648f2f9e1..e10ec5afaf 100644
--- a/lib/ssl/test/ssl_socket_SUITE.erl
+++ b/lib/ssl/test/ssl_socket_SUITE.erl
@@ -20,12 +20,53 @@
-module(ssl_socket_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([getstat/0,
+ getstat/1,
+ socket_options/0,
+ socket_options/1,
+ invalid_inet_get_option/0,
+ invalid_inet_get_option/1,
+ invalid_inet_get_option_not_list/0,
+ invalid_inet_get_option_not_list/1,
+ invalid_inet_get_option_improper_list/0,
+ invalid_inet_get_option_improper_list/1,
+ invalid_inet_set_option/0,
+ invalid_inet_set_option/1,
+ invalid_inet_set_option_not_list/0,
+ invalid_inet_set_option_not_list/1,
+ invalid_inet_set_option_improper_list/0,
+ invalid_inet_set_option_improper_list/1,
+ raw_inet_option/0,
+ raw_inet_option/1
+ ]).
+
+%% Apply export
+-export([socket_options_result/5,
+ get_invalid_inet_option/1,
+ get_invalid_inet_option_not_list/1,
+ get_invalid_inet_option_improper_list/1,
+ set_invalid_inet_option/1,
+ set_invalid_inet_option_not_list/1,
+ set_invalid_inet_option_improper_list/1
+ ]).
+
+-define(TIMEOUT, {seconds, 5}).
-define(SLEEP, 500).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -87,7 +128,7 @@ end_per_group(_GroupName, Config) ->
Config.
init_per_testcase(raw_inet_option, Config) ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
case os:type() of
{unix,linux} ->
Config;
@@ -95,7 +136,7 @@ init_per_testcase(raw_inet_option, Config) ->
{skip, "Raw options are platform-specific"}
end;
init_per_testcase(_TestCase, Config) ->
- ct:timetrap({seconds, 5}),
+ ct:timetrap(?TIMEOUT),
Config.
end_per_testcase(_TestCase, Config) ->
@@ -388,7 +429,7 @@ invalid_inet_set_option_improper_list(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
-%% Internal functions ------------------------------------------------
+%% Callback functions ------------------------------------------------
%%--------------------------------------------------------------------
socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) ->
%% Test get/set emulated opts
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 00dc8eb725..012eb9217e 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -21,16 +21,194 @@
%%
-module(ssl_test_lib).
+-behaviour(ct_suite).
+
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-
-%% Note: This directive should only be used in test suites.
--compile(export_all).
--compile(nowarn_export_all).
+-include_lib("ssl/src/tls_handshake_1_3.hrl").
+
+-export([clean_start/0,
+ clean_start/1,
+ clean_env/0,
+ init_per_group/2,
+ init_per_group_openssl/2,
+ end_per_group/2,
+ ct_log_supported_protocol_versions/1,
+ ssl_options/2,
+ run_where/1,
+ run_where/2,
+ inet_port/1,
+ default_tls_version/1,
+ check_sane_openssl_renegotiate/2,
+ check_openssl_npn_support/1,
+ start_server/1,
+ start_server/2,
+ start_client/1,
+ start_client/2,
+ start_server/3,
+ start_client/3,
+ start_client/4,
+ start_upgrade_server/1,
+ start_upgrade_server_error/1,
+ start_upgrade_client/1,
+ start_client_error/1,
+ start_server_error/1,
+ start_server_transport_abuse_socket/1,
+ start_server_transport_control/1,
+ start_server_with_raw_key/3,
+ start_openssl_client/2,
+ run_server/1,
+ run_server/3,
+ run_server_error/1,
+ ecc_test/6,
+ ecc_test_error/5,
+ transport_accept_abuse/1,
+ transport_switch_control/1,
+ init_openssl_server/3,
+ init_openssl_client/1,
+ run_client_init/1,
+ run_upgrade_server/1,
+ run_upgrade_client/1,
+ run_upgrade_server_error/1,
+ run_client_error/1,
+ send_recv_result_active/3,
+ wait_for_result/2,
+ wait_for_result/4,
+ wait_for_openssl_server/2,
+ send_recv_result/1,
+ send_recv_result_active/1,
+ send_recv_result_active/2,
+ send_recv_result_active_once/1,
+ active_recv/2,
+ active_recv_loop/3,
+ active_once_recv/2,
+ recv_disregard/2,
+ active_disregard/2,
+ active_once_disregard/2,
+ send/2,
+ close/1,
+ close/2,
+ check_active_receive/2,
+ check_client_alert/2,
+ check_client_alert/3,
+ check_server_alert/2,
+ check_server_alert/3,
+ check_tickets/1,
+ check_ecc/3,
+ check_key_exchange_send_active/2,
+ verify_active_session_resumption/2,
+ verify_active_session_resumption/3,
+ verify_active_session_resumption/4,
+ verify_active_session_resumption/5,
+ verify_server_early_data/3,
+ verify_session_ticket_extension/2,
+ update_session_ticket_extension/2,
+ check_sane_openssl_version/1,
+ check_ok/1,
+ check_result/4,
+ check_result/2,
+ gen_check_result/4,
+ basic_alert/4,
+ session_id/1,
+ update_keys/2,
+ sanity_check/2,
+ oscp_responder/6,
+ supported_eccs/1,
+ no_result/1,
+ receive_tickets/1,
+ set_protocol_versions/1,
+ user_lookup/3,
+ digest/0,
+ accepters/1,
+ client_msg/2,
+ server_msg/2,
+ hardcode_rsa_key/1,
+ bigger_buffers/0,
+ stop/2,
+ working_openssl_client/0
+ ]).
+
+-export([basic_test/3,
+ erlang_ssl_receive_and_assert_negotiated_protocol/3,
+ cipher_result/2,
+ assert_mfl/2,
+ trigger_renegotiate/4,
+ trigger_renegotiate/2,
+ session_info_result/1,
+ reuse_session/3,
+ test_ciphers/3,
+ test_cipher/2,
+ openssl_ciphers/0,
+ openssl_support_rsa_kex/0
+ ]).
+
+-export([tls_version/1,
+ is_protocol_version/1,
+ is_dtls_version/1,
+ protocol_version/1,
+ protocol_version/2,
+ protocol_options/2,
+ public_key/1,
+ state/1,
+ new_config/2,
+ node_to_hostip/2
+ ]).
+
+-export([make_rsa_cert/1,
+ make_dsa_cert/1,
+ make_ecdsa_cert/1,
+ make_ecdh_rsa_cert/1,
+ make_rsa_cert_chains/3,
+ make_dsa_cert_chains/3,
+ make_ecc_cert_chains/3,
+ make_cert_chains_der/2,
+ make_cert_chains_pem/4,
+ make_ec_cert_chains/4,
+ make_ec_cert_chains/5,
+ make_rsa_1024_cert/1,
+ make_rsa_pss_pem/4,
+ gen_conf/4,
+ make_mix_cert/1,
+ default_cert_chain_conf/0,
+ cert_options/1,
+ rsa_non_signed_suites/1,
+ ecdh_dh_anonymous_suites/1,
+ ecdsa_suites/1,
+ der_to_pem/2,
+ pem_to_der/1,
+ appropriate_sha/1,
+ format_certs/1,
+ format_cert/1
+ ]).
+
+-export([maybe_force_ipv4/1,
+ openssl_sane_dtls/0,
+ kill_openssl/0,
+ openssl_allows_server_renegotiate/1,
+ openssl_dtls_maxfraglen_support/0,
+ openssl_maxfraglen_support/0,
+ is_sane_oppenssl_pss/1,
+ consume_port_exit/1,
+ is_sane_oppenssl_client/0,
+ openssl_sane_dtls_session_reuse/0,
+ sufficient_crypto_support/1,
+ openssl_sane_dtls_alpn/0,
+ openssl_ecdsa_suites/0,
+ openssl_dsa_suites/0,
+ enough_openssl_crl_support/1,
+ openssl_ocsp_support/0,
+ openssl_allows_client_renegotiate/1,
+ version_flag/1,
+ portable_cmd/2,
+ portable_open_port/2,
+ close_port/1,
+ verify_early_data/1
+ ]).
-record(sslsocket, { fd = nil, pid = nil}).
-define(SLEEP, 1000).
-define(DEFAULT_CURVE, secp256r1).
+-define(PRINT_DEPTH, 100).
%%====================================================================
%% API
@@ -45,75 +223,146 @@ start_client(Type, _Args, _Config) ->
start_server(erlang, Options, Config) ->
start_server(Options, Config);
start_server(openssl, Options, Config) ->
- start_openssl_server(Options, Config);
+ start_openssl_server(openssl, Options, Config);
+start_server(openssl_ocsp, Options, Config) ->
+ start_openssl_server(openssl_ocsp, Options, Config);
+start_server(openssl_ocsp_revoked, Options, Config) ->
+ start_openssl_server(openssl_ocsp_revoked, Options, Config);
start_server(Type, _Args, _Config) ->
{error, unsupported_server_type, Type}.
+
%% Test
send_recv_result_active(Peer1, Peer2, Data) ->
- ok = ssl_test_lib:send(Peer1, Data),
- Data = ssl_test_lib:check_active_receive(Peer2, Data),
- ok = ssl_test_lib:send(Peer2, Data),
- Data = ssl_test_lib:check_active_receive(Peer1, Data).
-
-%% Certs
-init_ecdsa_certs(Config) ->
- DefConf = ssl_test_lib:default_cert_chain_conf(),
- CertChainConf = ssl_test_lib:gen_conf(ecdsa, ecdsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
- = public_key:pkix_test_data(CertChainConf),
- [{tls_config, #{server_config => ServerOpts,
- client_config => ClientOpts}} |
- proplists:delete(tls_config, Config)].
+ ok = send(Peer1, Data),
+ Data = check_active_receive(Peer2, Data),
+ ok = send(Peer2, Data),
+ Data = check_active_receive(Peer1, Data).
+
%% Options
get_server_opts(Config) ->
- SOpts = proplists:get_value(server_ecdsa_opts, Config),
- ssl_test_lib:ssl_options(SOpts, Config).
+ get_server_opts(openssl, Config).
+ %% DSOpts = proplists:get_value(server_ecdsa_opts, Config),
+ %% SOpts = proplists:get_value(server_opts, Config, DSOpts),
+ %% ssl_test_lib:ssl_options(SOpts, Config).
+%%
+get_server_opts(openssl, Config) ->
+ DSOpts = proplists:get_value(server_ecdsa_opts, Config),
+ SOpts = proplists:get_value(server_opts, Config, DSOpts),
+ ssl_options(SOpts, Config);
+get_server_opts(openssl_ocsp, Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Cert = filename:join(PrivDir, "a.server/cert.pem"),
+ Key = filename:join(PrivDir, "a.server/key.pem"),
+ CACerts = filename:join(PrivDir, "a.server/cacerts.pem"),
+ SOpts = [{reuseaddr, true},
+ {cacertfile, CACerts},
+ {certfile, Cert},
+ {keyfile, Key}],
+ ssl_options(SOpts, Config);
+get_server_opts(openssl_ocsp_revoked, Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Cert = filename:join(PrivDir, "revoked/cert.pem"),
+ Key = filename:join(PrivDir, "revoked/key.pem"),
+ CACerts = filename:join(PrivDir, "revoked/cacerts.pem"),
+ SOpts = [{reuseaddr, true},
+ {cacertfile, CACerts},
+ {certfile, Cert},
+ {keyfile, Key}],
+ ssl_options(SOpts, Config).
+
get_client_opts(Config) ->
- COpts = proplists:get_value(client_ecdsa_opts, Config),
- ssl_test_lib:ssl_options(COpts, Config).
+ DCOpts = proplists:get_value(client_ecdsa_opts, Config),
+ COpts = proplists:get_value(client_opts, Config, DCOpts),
+ ssl_options(COpts, Config).
%% Default callback functions
-init_per_group(GroupName, Config) ->
- clean_tls_version(Config),
- case is_tls_version(GroupName) andalso sufficient_crypto_support(GroupName) of
+init_per_group(GroupName, Config0) ->
+ case is_protocol_version(GroupName) andalso sufficient_crypto_support(GroupName) of
true ->
- init_tls_version(GroupName, Config);
+ Config = clean_protocol_version(Config0),
+ [{version, GroupName}|init_protocol_version(GroupName, Config)];
_ ->
case sufficient_crypto_support(GroupName) of
true ->
ssl:start(),
- Config;
+ Config0;
false ->
{skip, "Missing crypto support"}
end
end.
-init_per_group_openssl(GroupName, Config) ->
- case is_tls_version(GroupName) of
+working_openssl_client() ->
+ case portable_cmd("openssl", ["version"]) of
+ %% Theses versions of OpenSSL has a client that
+ %% can not handle hello extensions. And will
+ %% fail with bad packet length if they are present
+ %% in ServerHello
+ "OpenSSL 0.9.8h" ++ _ ->
+ false;
+ "OpenSSL 0.9.8k" ++ _ ->
+ false;
+ _ ->
+ true
+ end.
+
+init_per_group_openssl(GroupName, Config0) ->
+ case is_tls_version(GroupName) andalso sufficient_crypto_support(GroupName) of
true ->
- case check_sane_openssl_version(GroupName) of
+ Config = clean_protocol_version(Config0),
+ case openssl_tls_version_support(GroupName, Config)
+ of
true ->
- [{version, GroupName}|init_tls_version(GroupName, Config)];
+ [{version, GroupName}|init_protocol_version(GroupName, Config)];
false ->
{skip, "Missing openssl support"}
end;
_ ->
- ssl:start(),
- Config
+ case sufficient_crypto_support(GroupName) of
+ true ->
+ ssl:start(),
+ Config0;
+ false ->
+ {skip, "Missing crypto support"}
+ end
end.
end_per_group(GroupName, Config) ->
case is_tls_version(GroupName) of
true ->
- clean_tls_version(Config);
+ clean_protocol_version(Config);
false ->
Config
end.
+openssl_ocsp_support() ->
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 1.1.1" ++ _Rest ->
+ true;
+ _ ->
+ false
+ end.
+
+openssl_ciphers() ->
+ Str = portable_cmd("openssl", ["ciphers"]),
+ Ciphers = string:split(string:strip(Str, right, $\n), ":", all),
+ case portable_cmd("openssl", ["version"]) of
+ "LibreSSL 3." ++ _ ->
+ Ciphers -- ["DES-CBC3-SHA","AES128-SHA", "AES256-SHA", "RC4-SHA", "RC4-MD5"];
+ _ ->
+ Ciphers
+ end.
+
+openssl_support_rsa_kex() ->
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 1.1.1" ++ _Rest ->
+ false;
+ _ ->
+ true
+ end.
+
%%====================================================================
%% Internal functions
%%====================================================================
@@ -147,8 +396,8 @@ normalize_loopback(Address, _) ->
start_server(Args0, Config) ->
- {_, ServerNode, _} = ssl_test_lib:run_where(Config),
- ServerOpts = ssl_test_lib:get_server_opts(Config),
+ {_, ServerNode, _} = run_where(Config),
+ ServerOpts = get_server_opts(Config),
Node = proplists:get_value(node, Args0, ServerNode),
Port = proplists:get_value(port, Args0, 0),
Args = [{from, self()},
@@ -172,7 +421,7 @@ run_server(Opts) ->
Options = proplists:get_value(options, Opts),
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
- ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]),
+ ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, format_options(Options)]),
%% {ok, ListenSocket} = Transport:listen(Port, Options),
case Transport:listen(Port, Options) of
{ok, ListenSocket} ->
@@ -259,6 +508,9 @@ do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid) ->
Pid ! {self(), Reason}
end,
do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid);
+ get_socket ->
+ Pid ! {self(), {socket, AcceptSocket}},
+ do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid);
listen ->
run_server(ListenSocket, Opts);
{listen, MFA} ->
@@ -293,9 +545,9 @@ connect(ListenSocket, _Opts) ->
connect(_, _, 0, AcceptSocket, _, _, _) ->
AcceptSocket;
connect(ListenSocket, Node, _N, _, Timeout, SslOpts, cancel) ->
- ct:log("ssl:transport_accept(~p)~n", [ListenSocket]),
+ ct:log("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
- ct:log("~p:~p~nssl:handshake(~p,~p,~p)~n", [?MODULE,?LINE, AcceptSocket, SslOpts,Timeout]),
+ ct:log("~p:~p~nssl:handshake(~p,~p,~p)~n", [?MODULE,?LINE, AcceptSocket, format_options(SslOpts),Timeout]),
case ssl:handshake(AcceptSocket, SslOpts, Timeout) of
{ok, Socket0, Ext} ->
@@ -306,8 +558,8 @@ connect(ListenSocket, Node, _N, _, Timeout, SslOpts, cancel) ->
ct:log("~p:~p~nssl:handshake@~p ret ~p",[?MODULE,?LINE, Node,Result]),
Result
end;
-connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) ->
- ct:log("ssl:transport_accept(~p)~n", [ListenSocket]),
+connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts0) ->
+ ct:log("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
ct:log("~p:~p~nssl:handshake(~p,~p,~p)~n", [?MODULE,?LINE, AcceptSocket, SslOpts,Timeout]),
@@ -315,10 +567,21 @@ connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) ->
{ok, Socket0, Ext} ->
[_|_] = maps:get(sni, Ext),
ct:log("Ext ~p:~n", [Ext]),
- ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]),
+ ContOpts = case lists:keytake(want_ext, 1, ContOpts0) of
+ {value, {_, WantExt}, ContOpts1} ->
+ if is_pid(WantExt) ->
+ WantExt ! {self(), {ext, Ext}};
+ true ->
+ ignore
+ end,
+ ContOpts1;
+ _ ->
+ ContOpts0
+ end,
+ ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]),
case ssl:handshake_continue(Socket0, ContOpts, Timeout) of
{ok, Socket} ->
- connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts);
+ connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts0);
Error ->
ct:log("~p:~p~nssl:handshake_continue@~p ret ~p",[?MODULE,?LINE, Node,Error]),
Error
@@ -328,7 +591,7 @@ connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) ->
Result
end;
connect(ListenSocket, Node, N, _, Timeout, [], ContOpts) ->
- ct:log("ssl:transport_accept(~p)~n", [ListenSocket]),
+ ct:log("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
ct:log("~p:~p~nssl:ssl_accept(~p, ~p)~n", [?MODULE,?LINE, AcceptSocket, Timeout]),
@@ -340,7 +603,7 @@ connect(ListenSocket, Node, N, _, Timeout, [], ContOpts) ->
Result
end;
connect(ListenSocket, _Node, _, _, Timeout, Opts, _) ->
- ct:log("ssl:transport_accept(~p)~n", [ListenSocket]),
+ ct:log("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
ct:log("ssl:handshake(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]),
ssl:handshake(AcceptSocket, Opts, Timeout),
@@ -363,56 +626,6 @@ start_server_transport_control(Args) ->
Result
end.
-start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) ->
- process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
- ClientOpts = ErlangClientOpts ++ ClientOpts0,
-
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
-
- Data = "From openssl to erlang",
-
- Port = ssl_test_lib:inet_port(node()),
- CaCertFile = proplists:get_value(cacertfile, ServerOpts),
- CertFile = proplists:get_value(certfile, ServerOpts),
- KeyFile = proplists:get_value(keyfile, ServerOpts),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = case OpensslServerOpts of
- [] ->
- ["s_server", "-accept",
- integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-CAfile", CaCertFile,
- "-cert", CertFile,"-key", KeyFile];
- [Opt, Value] ->
- ["s_server", Opt, Value, "-accept",
- integer_to_list(Port), ssl_test_lib:version_flag(Version),
- "-CAfile", CaCertFile,
- "-cert", CertFile,"-key", KeyFile]
- end,
-
- OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
-
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {?MODULE,
- active_recv, [length(Data)]}},
- {options, ClientOpts}]),
-
- Callback(Client, OpensslPort),
-
- %% Clean close down! Server needs to be closed first !!
- ssl_test_lib:close_port(OpensslPort),
-
- ssl_test_lib:close(Client),
- process_flag(trap_exit, false).
-
-
transport_accept_abuse(Opts) ->
Port = proplists:get_value(port, Opts),
Options = proplists:get_value(options, Opts),
@@ -427,35 +640,6 @@ transport_accept_abuse(Opts) ->
_ = ssl:handshake(AcceptSocket, infinity),
Pid ! {self(), ok}.
-start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenSSLClientOpts, Data, Callback) ->
- process_flag(trap_exit, true),
- ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ServerOpts = ErlangServerOpts ++ ServerOpts0,
-
- {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
-
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE, active_recv, [length(Data)]}},
- {options, ServerOpts}]),
- Port = ssl_test_lib:inet_port(Server),
- Version = ssl_test_lib:protocol_version(Config),
-
- Exe = "openssl",
- Args = ["s_client"] ++ OpenSSLClientOpts ++ ["-msg", "-connect",
- hostname_format(Hostname) ++ ":" ++ integer_to_list(Port),
- ssl_test_lib:version_flag(Version)],
-
- OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
-
- Callback(Server, OpenSslPort),
-
- ssl_test_lib:close(Server),
-
- ssl_test_lib:close_port(OpenSslPort),
- process_flag(trap_exit, false).
-
transport_switch_control(Opts) ->
Port = proplists:get_value(port, Opts),
Options = proplists:get_value(options, Opts),
@@ -479,81 +663,125 @@ remove_close_msg(ReconnectTimes) ->
end.
-start_openssl_server(Args0, Config) ->
- {_, ServerNode, _} = ssl_test_lib:run_where(Config),
- ServerOpts = ssl_test_lib:get_server_opts(Config),
+start_openssl_server(Mode, Args0, Config) ->
+ {_, ServerNode, _} = run_where(Config),
+ ServerOpts = get_server_opts(Mode, Config),
Node = proplists:get_value(node, Args0, ServerNode),
Port = proplists:get_value(port, Args0, 0),
- Args = [{from, self()}, {port, Port}] ++ ServerOpts ++ Args0,
- Result = spawn_link(Node, ?MODULE, init_openssl_server, [lists:delete(return_socket, Args)]),
+ ResponderPort = proplists:get_value(responder_port, Config, 0),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Args = [{from, self()}, {port, Port}] ++ ServerOpts ++ Args0 ++ [{priv_dir, PrivDir}],
+ Result = spawn_link(Node, ?MODULE, init_openssl_server,
+ [Mode, ResponderPort,lists:delete(return_port, Args)]),
receive
- {started, Socket} ->
- case lists:member(return_socket, Args) of
- true -> {Result, Socket};
+ {started, OpenSSLPort} ->
+ case lists:member(return_port, Args) of
+ true -> {Result, OpenSSLPort};
false -> Result
end;
{start_failed, Reason} ->
{start_failed, Reason}
end.
-init_openssl_server(Options) ->
- {ok, Version} = application:get_env(ssl,protocol_version),
- %% Port = proplists:get_value(port, Options),
+init_openssl_server(openssl, _, Options) ->
+ DefaultVersions = default_tls_version(Options),
+ [Version | _] = proplists:get_value(versions, Options, DefaultVersions),
Port = inet_port(node()),
Pid = proplists:get_value(from, Options),
-
+
Exe = "openssl",
- Ciphers = proplists:get_value(ciphers, Options, ssl:cipher_suites(default,Version)),
+ Ciphers = proplists:get_value(ciphers, Options, default_ciphers(Version)),
Groups0 = proplists:get_value(groups, Options),
- CertArgs = openssl_cert_options(Options, server),
- Exe = "openssl",
+ EarlyData = proplists:get_value(early_data, Options, undefined),
+ PrivDir = proplists:get_value(priv_dir, Options),
+ CertArgs = openssl_cert_options(Options, server),
+ AlpnArgs = openssl_alpn_options(proplists:get_value(alpn, Options, undefined)),
+ NpnArgs = openssl_npn_options(proplists:get_value(np, Options, undefined)),
+ Debug = openssl_debug_options(PrivDir),
- Args = case Groups0 of
+ Args0 = case Groups0 of
undefined ->
["s_server", "-accept", integer_to_list(Port), cipher_flag(Version),
- ciphers(Ciphers, Version),
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"];
+ ciphers(Ciphers, Version),
+ version_flag(Version)] ++ AlpnArgs ++ NpnArgs ++ CertArgs ++ Debug;
Group ->
["s_server", "-accept", integer_to_list(Port), cipher_flag(Version),
ciphers(Ciphers, Version), "-groups", Group,
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]
+ version_flag(Version)] ++ AlpnArgs ++ NpnArgs ++ CertArgs ++ Debug
end,
- SslPort = ssl_test_lib:portable_open_port(Exe, Args),
+ Args1 = case EarlyData of
+ undefined ->
+ Args0;
+ MaxSize ->
+ Args0 ++ ["-early_data", "-no_anti_replay", "-max_early_data",
+ integer_to_list(MaxSize)]
+ end,
+ Args = maybe_force_ipv4(Args1),
+ SslPort = portable_open_port(Exe, Args),
+ wait_for_openssl_server(Port, proplists:get_value(protocol, Options, tls)),
+ Pid ! {started, SslPort},
+ Pid ! {self(), {port, Port}},
+ openssl_server_loop(Pid, SslPort, Args);
+
+init_openssl_server(Mode, ResponderPort, Options) when Mode == openssl_ocsp orelse
+ Mode == openssl_ocsp_revoked ->
+ DefaultVersions = default_tls_version(Options),
+ [Version | _] = proplists:get_value(versions, Options, DefaultVersions),
+ Port = inet_port(node()),
+ Pid = proplists:get_value(from, Options),
+ GroupName = proplists:get_value(group, Options),
+
+ Exe = "openssl",
+ Ciphers = proplists:get_value(ciphers, Options, ssl:cipher_suites(default,Version)),
+ CertArgs = openssl_cert_options(Options, server),
+ Exe = "openssl",
+
+ Args = ["s_server", "-accept", integer_to_list(Port), cipher_flag(Version),
+ ciphers(Ciphers, Version),
+ "-status_verbose",
+ "-status_url",
+ "http://127.0.0.1:" ++ erlang:integer_to_list(ResponderPort),
+ version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]
+ ++ openssl_dtls_opt(GroupName),
+
+ SslPort = portable_open_port(Exe, Args),
+ wait_for_openssl_server(Port, proplists:get_value(protocol, Options, tls)),
Pid ! {started, Port},
Pid ! {self(), {port, Port}},
- case openssl_server_started(SslPort) of
- true ->
- openssl_server_loop(Pid, SslPort, Args);
- false ->
- {error, openssl_server}
- end.
+ openssl_server_loop(Pid, SslPort, Args).
-openssl_server_started(_Port) ->
- receive
- {Port, {data, Data}} ->
- ct:log("~p:~p~n Openssl~n ~s~n",[?MODULE,?LINE, Data]),
- verify_openssl_server_started(Port, Data)
- after
- 5000 ->
- false
- end.
+oscp_responder(Port, Index, CACerts, Cert, Key, Starter) ->
+ Args = ["ocsp", "-index", Index, "-CA", CACerts, "-rsigner", Cert,
+ "-rkey", Key, "-port", erlang:integer_to_list(Port)],
+ Responder = portable_open_port("openssl", Args),
+ wait_for_openssl_server(Port, tls),
+
+ openssl_server_loop(Starter, Responder, []).
+
+
+openssl_dtls_opt('dtlsv1.2') ->
+ ["-dtls"];
+openssl_dtls_opt(_Other) ->
+ [].
openssl_server_loop(Pid, SslPort, Args) ->
receive
{data, Data} ->
case port_command(SslPort, Data, [nosuspend]) of
true ->
- ct:log("[openssl server] Send data: ~p~n", [Data]),
+ ct:log("(~p) [openssl server] Send data: ~p~n",
+ [self(), Data]),
Pid ! {self(), ok};
_Else ->
- ct:log("[openssl server] Send failed, data: ~p~n", [Data]),
+ ct:log("(~p) [openssl server] Send failed, data: ~p~n",
+ [self(), Data]),
Pid ! {self(), {error, port_command_failed}}
end,
openssl_server_loop(Pid, SslPort, Args);
{active_receive, Data} ->
case active_recv(SslPort, length(Data)) of
ReceivedData ->
- ct:log("[openssl server] Received: ~p~n", [Data]),
+ ct:log("(~p) [openssl server] Received: ~p~n", [self(), Data]),
Pid ! {self(), ReceivedData}
end,
openssl_server_loop(Pid, SslPort, Args);
@@ -571,25 +799,27 @@ openssl_server_loop(Pid, SslPort, Args) ->
openssl_server_loop(Pid, SslPort, Args);
close ->
ct:log("~p:~p~n[openssl server] Server closing~n", [?MODULE,?LINE]),
- port_close(SslPort);
+ catch port_close(SslPort);
{ssl_closed, _Socket} ->
%% TODO
ok
end.
start_openssl_client(Args0, Config) ->
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
- ClientOpts = ssl_test_lib:get_client_opts(Config),
+ {ClientNode, _, Hostname} = run_where(Config),
+ ClientOpts = get_client_opts(Config),
+ DefaultVersions = default_tls_version(ClientOpts),
+ [Version | _] = proplists:get_value(versions, ClientOpts, DefaultVersions),
Node = proplists:get_value(node, Args0, ClientNode),
Args = [{from, self()},
{host, Hostname},
{options, ClientOpts} | Args0],
- Result = spawn_link(Node, ?MODULE, init_openssl_client, [lists:delete(return_socket, Args)]),
+ Result = spawn_link(Node, ?MODULE, init_openssl_client, [[{version, Version} | lists:delete(return_port, Args)]]),
receive
- {connected, Socket} ->
- case lists:member(return_socket, Args) of
- true -> {Result, Socket};
+ {connected, OpenSSLPort} ->
+ case lists:member(return_port, Args) of
+ true -> {Result, OpenSSLPort};
false -> Result
end;
{connect_failed, Reason} ->
@@ -597,59 +827,12 @@ start_openssl_client(Args0, Config) ->
end.
init_openssl_client(Options) ->
- {ok, Version} = application:get_env(ssl,protocol_version),
+ Version = proplists:get_value(version, Options),
Port = proplists:get_value(port, Options),
Pid = proplists:get_value(from, Options),
+ SslPort = start_client(openssl, Port, Options, [{version, Version}]),
+ openssl_client_loop(Pid, SslPort, []).
- Exe = "openssl",
- Ciphers = proplists:get_value(ciphers, Options, ssl:cipher_suites(default,Version)),
- Groups0 = proplists:get_value(groups, Options),
- CertArgs = openssl_cert_options(Options, client),
- Exe = "openssl",
- Args0 = case Groups0 of
- undefined ->
- ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version),
- ciphers(Ciphers, Version),
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"];
- Group ->
- ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version),
- ciphers(Ciphers, Version), "-groups", Group,
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]
- end,
- Args = maybe_force_ipv4(Args0),
- SslPort = ssl_test_lib:portable_open_port(Exe, Args),
- case openssl_client_started(SslPort) of
- true ->
- openssl_client_loop(Pid, SslPort, Args);
- false ->
- {error, openssl_client}
- end.
-
-openssl_client_started(Port) ->
- receive
- {Port, {data, Data}} ->
- ct:log("~p:~p~n Openssl~n ~s~n",[?MODULE,?LINE, Data]),
- verify_openssl_client_started(Port, Data)
- after
- 5000 ->
- false
- end.
-
-verify_openssl_server_started(Port, Data) ->
- case re:run(Data, ".*CIPHER is.*") of
- nomatch ->
- openssl_server_started(Port);
- {match, _} ->
- true
- end.
-
-verify_openssl_client_started(Port, Data) ->
- case re:run(Data, ".*New, TLSv\\d[.]\\d, Cipher is.*") of
- nomatch ->
- openssl_client_started(Port);
- {match, _} ->
- true
- end.
openssl_client_loop(Pid, SslPort, Args) ->
Pid ! {connected, SslPort},
@@ -660,17 +843,20 @@ openssl_client_loop_core(Pid, SslPort, Args) ->
{data, Data} ->
case port_command(SslPort, Data, [nosuspend]) of
true ->
- ct:log("[openssl client] Send data: ~p~n", [Data]),
+ ct:log("(~p) [openssl client] Send data: ~p~n",
+ [self(), Data]),
Pid ! {self(), ok};
_Else ->
- ct:log("[openssl client] Send failed, data: ~p~n", [Data]),
+ ct:log("(~p) [openssl client] Send failed, data: ~p~n",
+ [self(), Data]),
Pid ! {self(), {error, port_command_failed}}
end,
openssl_client_loop_core(Pid, SslPort, Args);
{active_receive, Data} ->
case active_recv(SslPort, length(Data)) of
ReceivedData ->
- ct:log("[openssl client] Received: ~p~n", [Data]),
+ ct:log("(~p) [openssl client] Received: ~p~n (forward to PID=~p)~n",
+ [self(), Data, Pid]),
Pid ! {self(), ReceivedData}
end,
openssl_client_loop_core(Pid, SslPort, Args);
@@ -688,20 +874,22 @@ openssl_client_loop_core(Pid, SslPort, Args) ->
openssl_client_loop_core(Pid, SslPort, Args);
close ->
ct:log("~p:~p~nClient closing~n", [?MODULE,?LINE]),
- port_close(SslPort);
+ catch port_close(SslPort);
{ssl_closed, _Socket} ->
%% TODO
ok
end.
start_client(Args0, Config) ->
- {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- ServerOpts = ssl_test_lib:get_server_opts(Config),
+ {_, ServerNode, Hostname} = run_where(Config),
+ ClientOpts = get_client_opts(Config),
+ ClientOpts1 = proplists:get_value(options, Args0, []),
Node = proplists:get_value(node, Args0, ServerNode),
+ Args1 = proplists:delete(options, Args0),
Args = [{from, self()},
{host, Hostname},
{node, Node},
- {options, ServerOpts} | Args0],
+ {options, ClientOpts ++ ClientOpts1} | Args1],
start_client(Args).
%%
start_client(Args) ->
@@ -730,7 +918,7 @@ run_client(Opts) ->
Options = proplists:get_value(options, Opts),
ContOpts = proplists:get_value(continue_options, Opts, []),
ct:log("~p:~p~n~p:connect(~p, ~p)@~p~n", [?MODULE,?LINE, Transport, Host, Port, Node]),
- ct:log("SSLOpts: ~p", [Options]),
+ ct:log("SSLOpts: ~p", [format_options(Options)]),
case ContOpts of
[] ->
client_loop(Node, Host, Port, Pid, Transport, Options, Opts);
@@ -756,16 +944,21 @@ client_loop(_Node, Host, Port, Pid, Transport, Options, Opts) ->
end,
client_loop_core(Socket, Pid, Transport);
{error, econnrefused = Reason} ->
- case get(retries) of
- N when N < 5 ->
- ct:log("~p:~p~neconnrefused retries=~p sleep ~p",[?MODULE,?LINE, N,?SLEEP]),
- put(retries, N+1),
- ct:sleep(?SLEEP),
- run_client(Opts);
- _ ->
- ct:log("~p:~p~nClient faild several times: connection failed: ~p ~n", [?MODULE,?LINE, Reason]),
- Pid ! {self(), {error, Reason}}
- end;
+ case proplists:get_value(return_error, Opts, undefined) of
+ econnrefused ->
+ Pid ! {connect_failed, Reason};
+ _ ->
+ case get(retries) of
+ N when N < 5 ->
+ ct:log("~p:~p~neconnrefused retries=~p sleep ~p",[?MODULE,?LINE, N,?SLEEP]),
+ put(retries, N+1),
+ ct:sleep(?SLEEP),
+ run_client(Opts);
+ _ ->
+ ct:log("~p:~p~nClient faild several times: connection failed: ~p ~n", [?MODULE,?LINE, Reason]),
+ Pid ! {self(), {error, Reason}}
+ end
+ end;
{error, econnreset = Reason} ->
case get(retries) of
N when N < 5 ->
@@ -810,6 +1003,9 @@ client_loop_core(Socket, Pid, Transport) ->
Pid ! {self(), Reason}
end,
client_loop_core(Socket, Pid, Transport);
+ get_socket ->
+ Pid ! {self(), {socket, Socket}},
+ client_loop_core(Socket, Pid, Transport);
close ->
ct:log("~p:~p~nClient closing~n", [?MODULE,?LINE]),
Transport:close(Socket);
@@ -830,9 +1026,20 @@ client_cont_loop(_Node, Host, Port, Pid, Transport, Options, cancel, _Opts) ->
Pid ! {connect_failed, Reason}
end;
-client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts, Opts) ->
+client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts0, Opts) ->
case Transport:connect(Host, Port, Options) of
- {ok, Socket0, _} ->
+ {ok, Socket0, Ext} ->
+ ContOpts = case lists:keytake(want_ext, 1, ContOpts0) of
+ {value, {_, WantExt}, ContOpts1} ->
+ if is_pid(WantExt) ->
+ WantExt ! {self(), {ext, Ext}};
+ true ->
+ ignore
+ end,
+ ContOpts1;
+ _ ->
+ ContOpts0
+ end,
ct:log("~p:~p~nClient: handshake_continue(~p, ~p, infinity) ~n", [?MODULE, ?LINE, Socket0, ContOpts]),
case Transport:handshake_continue(Socket0, ContOpts) of
{ok, Socket} ->
@@ -934,11 +1141,14 @@ check_server_alert(Pid, Alert) ->
{Pid, {error, {tls_alert, {Alert, STxt}}}} ->
check_server_txt(STxt),
ok;
+ {Pid, {error, {tls_alert, {OtherAlert, STxt}}}} ->
+ ct:fail("Unexpected alert during negative test: ~p - ~p", [OtherAlert, STxt]);
{Pid, {error, closed}} ->
ok;
{Pid, {ok, _}} ->
ct:fail("Successful connection during negative test.")
end.
+
check_server_alert(Server, Client, Alert) ->
receive
{Server, {error, {tls_alert, {Alert, STxt}}}} ->
@@ -947,14 +1157,19 @@ check_server_alert(Server, Client, Alert) ->
{Server, {ok, _}} ->
ct:fail("Successful connection during negative test.")
end.
+
check_client_alert(Pid, Alert) ->
receive
{Pid, {error, {tls_alert, {Alert, CTxt}}}} ->
check_client_txt(CTxt),
ok;
+ {Pid, {error, {tls_alert, {OtherAlert, CTxt}}}} ->
+ ct:fail("Unexpected alert during negative test: ~p - ~p", [OtherAlert, CTxt]);
{Pid, {ssl_error, _, {tls_alert, {Alert, CTxt}}}} ->
check_client_txt(CTxt),
ok;
+ {Pid, {ssl_error, _, {tls_alert, {OtherAlert, CTxt}}}} ->
+ ct:fail("Unexpected alert during negative test: ~p - ~p", [OtherAlert, CTxt]);
{Pid, {error, closed}} ->
ok;
{Pid, {ok, _}} ->
@@ -1031,6 +1246,44 @@ wait_for_result(Pid, Msg) ->
%% Unexpected
end.
+format_options([{cacerts, Certs}|R]) ->
+ [{cacerts, format_certs(Certs)} | format_options(R)];
+format_options([{cert, Certs}|R]) ->
+ [{cert, format_certs(Certs)} | format_options(R)];
+format_options([{key, Key}|R]) ->
+ [{key, lists:flatten(io_lib:format("~W",[Key, ?PRINT_DEPTH]))} | format_options(R)];
+format_options([Opt|R]) ->
+ [Opt | format_options(R)];
+format_options([]) ->
+ [].
+
+format_certs(Certs) when is_list(Certs) ->
+ [lists:flatten(format_cert(C)) || C <- Certs];
+format_certs(Cert) when is_binary(Cert) ->
+ lists:flatten(format_cert(Cert)).
+
+format_cert(BinCert) when is_binary(BinCert) ->
+ OtpCert = #'OTPCertificate'{tbsCertificate = Cert} = public_key:pkix_decode_cert(BinCert, otp),
+ #'OTPTBSCertificate'{subject = Subject, serialNumber = Nr, issuer = Issuer} = Cert,
+ case public_key:pkix_is_self_signed(OtpCert) of
+ true ->
+ io_lib:format("~.3w: ~s -> selfsigned", [Nr, format_subject(Subject)]);
+ false ->
+ case public_key:pkix_issuer_id(OtpCert, other) of
+ {ok, {IsNr, Issuer0}} ->
+ io_lib:format("~.3w:~s -> ~.3w:~s", [Nr, format_subject(Subject), IsNr, format_subject(Issuer0)]);
+ {error, _} ->
+ io_lib:format("~.3w:~s -> :~s", [Nr, format_subject(Subject), format_subject(Issuer)])
+ end
+ end.
+
+format_subject({rdnSequence, Seq}) ->
+ format_subject(Seq);
+format_subject([[{'AttributeTypeAndValue', ?'id-at-commonName', {_, String}}]|_]) ->
+ String;
+format_subject([_|R]) ->
+ format_subject(R).
+
cert_options(Config) ->
ClientCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
"client", "cacerts.pem"]),
@@ -1093,8 +1346,7 @@ cert_options(Config) ->
{server_bad_key, [{ssl_imp, new},{cacertfile, ServerCaCertFile},
{certfile, ServerCertFile}, {keyfile, BadKeyFile}]}
| Config].
-
-
+
make_dsa_cert(Config) ->
CryptoSupport = crypto:supports(),
case proplists:get_bool(dss, proplists:get_value(public_keys, CryptoSupport)) of
@@ -1206,6 +1458,18 @@ default_cert_chain_conf() ->
%% Use only default options
[[],[],[]].
+make_rsa_pss_pem(Alg, _UserConf, Config, Suffix) ->
+ DefClientConf = chain_spec(client, Alg, []),
+ DefServerConf = chain_spec(server, Alg, []),
+ CertChainConf = new_format([{client_chain, DefClientConf}, {server_chain, DefServerConf}]),
+ ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]),
+ ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]),
+ GenCertData = public_key:pkix_test_data(CertChainConf),
+ Conf = x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
+ CConf = proplists:get_value(client_config, Conf),
+ SConf = proplists:get_value(server_config, Conf),
+ #{server_config => SConf,
+ client_config => CConf}.
gen_conf(ClientChainType, ServerChainType, UserClient, UserServer) ->
gen_conf(ClientChainType, ServerChainType, UserClient, UserServer, ?DEFAULT_CURVE).
@@ -1282,8 +1546,33 @@ chain_spec(_Role, ecdsa, Curve) ->
chain_spec(_Role, rsa, _) ->
Digest = {digest, appropriate_sha(crypto:supports())},
[[Digest, {key, hardcode_rsa_key(1)}],
- [Digest, {key, hardcode_rsa_key(2)}],
- [Digest, {key, hardcode_rsa_key(3)}]];
+ [Digest, {key, hardcode_rsa_key(2)}],
+ [Digest, {key, hardcode_rsa_key(3)}]];
+chain_spec(_Role, 'rsa-1024', _) ->
+ Digest = {digest, appropriate_sha(crypto:supports())},
+ [[Digest, {key, hardcode_rsa_1024_key(1)}],
+ [Digest, {key, hardcode_rsa_1024_key(2)}],
+ [Digest, {key, hardcode_rsa_1024_key(3)}]];
+chain_spec(client, rsa_pss_rsae, _) ->
+ Digest = {digest, sha256},
+ [[Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(1)}],
+ [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(2)}],
+ [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(3)}]];
+chain_spec(server, rsa_pss_rsae, _) ->
+ Digest = {digest, sha256},
+ [[Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(4)}],
+ [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(5)}],
+ [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(6)}]];
+chain_spec(client, rsa_pss_pss, _) ->
+ Digest = {digest, sha256},
+ [[Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(1), pss_params(sha256)}}],
+ [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(2), pss_params(sha256)}}],
+ [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(3), pss_params(sha256)}}]];
+chain_spec(server, rsa_pss_pss, _) ->
+ Digest = {digest, sha256},
+ [[Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(4), pss_params(sha256)}}],
+ [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(5), pss_params(sha256)}}],
+ [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(6), pss_params(sha256)}}]];
chain_spec(_Role, dsa, _) ->
Digest = {digest, appropriate_sha(crypto:supports())},
[[Digest, {key, hardcode_dsa_key(1)}],
@@ -1389,23 +1678,53 @@ make_rsa_cert(Config) ->
[{server_config, ServerConf},
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
- [{server_rsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
+ [{server_rsa_opts, [{reuseaddr, true} | ServerConf]},
- {server_rsa_verify_opts, [{ssl_imp, new}, {reuseaddr, true},
- {verify, verify_peer} | ServerConf]},
+ {server_rsa_verify_opts, [{reuseaddr, true},
+ {verify, verify_peer} | ServerConf]},
{client_rsa_opts, ClientConf},
{client_rsa_verify_opts, [{verify, verify_peer} |ClientConf]}
| Config];
false ->
Config
end.
+
+make_rsa_1024_cert(Config) ->
+ CryptoSupport = crypto:supports(),
+ case proplists:get_bool(rsa, proplists:get_value(public_keys, CryptoSupport)) of
+ true ->
+ ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa-1024"]),
+ ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa-1024"]),
+ ClientChain = proplists:get_value(client_chain, Config, default_cert_chain_conf()),
+ ServerChain = proplists:get_value(server_chain, Config, default_cert_chain_conf()),
+ CertChainConf = gen_conf('rsa-1024', 'rsa-1024', ClientChain, ServerChain),
+ GenCertData = public_key:pkix_test_data(CertChainConf),
+ [{server_config, ServerConf},
+ {client_config, ClientConf}] =
+ x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
+ [{server_rsa_1024_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
+
+ {server_rsa_1024_verify_opts, [{ssl_imp, new}, {reuseaddr, true},
+ {verify, verify_peer} | ServerConf]},
+ {client_rsa_1024_opts, ClientConf},
+ {client_rsa_1024_verify_opts, [{verify, verify_peer} |ClientConf]}
+ | Config];
+ false ->
+ Config
+ end.
+
appropriate_sha(CryptoSupport) ->
Hashes = proplists:get_value(hashs, CryptoSupport),
- case lists:member(sha256, Hashes) of
- true ->
- sha256;
- false ->
- sha1
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 0.9.8" ++ _ ->
+ sha;
+ _ ->
+ case lists:member(sha256, Hashes) of
+ true ->
+ sha256;
+ false ->
+ sha
+ end
end.
%% RFC 4492, Sect. 2.3. ECDH_RSA
@@ -1611,7 +1930,11 @@ run_client_error(Opts) ->
receive
{ssl_error, _, {tls_alert, _}} = SslError ->
Pid ! {self(), SslError}
- end
+ end;
+ {ok, Socket, _Ext} ->
+ ContOpts = proplists:get_value(continue_options, Opts, []),
+ Result = Transport:handshake_continue(Socket, ContOpts),
+ Pid ! {self(), Result}
end.
accepters(N) ->
@@ -1627,8 +1950,8 @@ accepters(Acc, N) ->
basic_test(COpts, SOpts, Config) ->
- SType = proplists:get_value(server_type, Config),
- CType = proplists:get_value(client_type, Config),
+ SType = proplists:get_value(server_type, Config, erlang),
+ CType = proplists:get_value(client_type, Config, erlang),
{Server, Port} = start_server(SType, COpts, SOpts, Config),
Client = start_client(CType, Port, COpts, Config),
gen_check_result(Server, SType, Client, CType),
@@ -1640,7 +1963,7 @@ basic_alert(ClientOpts, ServerOpts, Config, Alert) ->
run_basic_alert(SType, CType, ClientOpts, ServerOpts, Config, Alert).
run_basic_alert(erlang, erlang, ClientOpts, ServerOpts, Config, Alert) ->
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ {ClientNode, ServerNode, Hostname} = run_where(Config),
Server = start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
@@ -1650,16 +1973,16 @@ run_basic_alert(erlang, erlang, ClientOpts, ServerOpts, Config, Alert) ->
Port = inet_port(Server),
Client = start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {ssl_test_lib, no_result, []}},
- {options, ClientOpts}]),
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, ClientOpts}]),
check_server_alert(Server, Client, Alert);
run_basic_alert(openssl = SType, erlang, ClientOpts, ServerOpts, Config, Alert) ->
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+ {ClientNode, _, Hostname} = run_where(Config),
{_Server, Port} = start_server(SType, ClientOpts, ServerOpts, Config),
- ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+ wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
Client = start_client_error([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -1668,7 +1991,7 @@ run_basic_alert(openssl = SType, erlang, ClientOpts, ServerOpts, Config, Alert)
check_client_alert(Client, Alert);
run_basic_alert(erlang, openssl = CType, ClientOpts, ServerOpts, Config, Alert) ->
- {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ {_, ServerNode, Hostname} = run_where(Config),
Server = start_server_error([{node, ServerNode}, {port, 0},
{host, Hostname},
{from, self()},
@@ -1691,61 +2014,48 @@ ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) ->
Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config),
check_server_alert(Server, Client, insufficient_security).
-start_basic_client(openssl, Version, Port, ClientOpts) ->
- Cert = proplists:get_value(certfile, ClientOpts),
- Key = proplists:get_value(keyfile, ClientOpts),
- CA = proplists:get_value(cacertfile, ClientOpts),
- Groups0 = proplists:get_value(groups, ClientOpts),
- Exe = "openssl",
- Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port),
- ssl_test_lib:version_flag(Version),
- "-CAfile", CA, "-host", "localhost", "-msg", "-debug"],
- Args1 =
- case Groups0 of
- undefined ->
- Args0;
- G ->
- Args0 ++ ["-groups", G]
- end,
- Args =
- case {Cert, Key} of
- {C, K} when C =:= undefined orelse
- K =:= undefined ->
- Args1;
- {C, K} ->
- Args1 ++ ["-cert", C, "-key", K]
- end,
-
- OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
- true = port_command(OpenSslPort, "Hello world"),
- OpenSslPort.
-
start_client(openssl, Port, ClientOpts, Config) ->
- Version = ssl_test_lib:protocol_version(Config),
+ Version = protocol_version(Config),
Exe = "openssl",
Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)),
Groups0 = proplists:get_value(groups, ClientOpts),
CertArgs = openssl_cert_options(ClientOpts, client),
+ AlpnArgs = openssl_alpn_options(proplists:get_value(alpn, ClientOpts, undefined)),
+ NpnArgs = openssl_npn_options(proplists:get_value(np, ClientOpts, undefined)),
+ Reconnect = openssl_reconect_option(proplists:get_value(reconnect, ClientOpts, false)),
+ MaxFragLen = openssl_maxfag_option(proplists:get_value(maxfrag, ClientOpts, false)),
+ SessionArgs = proplists:get_value(session_args, ClientOpts, []),
+ HostName = proplists:get_value(hostname, ClientOpts, net_adm:localhost()),
+ Debug = openssl_debug_options(),
+
Exe = "openssl",
Args0 = case Groups0 of
undefined ->
- ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version),
- ciphers(Ciphers, Version),
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"];
- Group ->
- ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version),
- ciphers(Ciphers, Version), "-groups", Group,
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]
- end,
+ ["s_client",
+ "-verify", "2",
+ "-connect", hostname_format(HostName) ++ ":" ++ integer_to_list(Port), cipher_flag(Version),
+ ciphers(Ciphers, Version),
+ version_flag(Version)]
+ ++ CertArgs ++ AlpnArgs ++ NpnArgs ++ Reconnect ++ MaxFragLen ++ SessionArgs
+ ++ Debug;
+ Group ->
+ ["s_client",
+ "-verify", "2",
+ "-connect", hostname_format(HostName) ++ ":" ++ integer_to_list(Port), cipher_flag(Version),
+ ciphers(Ciphers, Version), "-groups", Group,
+ version_flag(Version)]
+ ++ CertArgs ++ AlpnArgs ++ NpnArgs ++ Reconnect ++ MaxFragLen ++ SessionArgs
+ ++ Debug
+ end,
Args = maybe_force_ipv4(Args0),
- OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
+ OpenSslPort = portable_open_port(Exe, Args),
true = port_command(OpenSslPort, "Hello world"),
OpenSslPort;
start_client(erlang, Port, ClientOpts, Config) ->
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+ {ClientNode, _, Hostname} = run_where(Config),
KeyEx = proplists:get_value(check_keyex, Config, false),
- ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
{mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}},
@@ -1765,7 +2075,7 @@ maybe_force_ipv4(Args0) ->
end.
start_client_ecc(erlang, Port, ClientOpts, Expect, ECCOpts, Config) ->
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+ {ClientNode, _, Hostname} = run_where(Config),
ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -1775,7 +2085,7 @@ start_client_ecc(erlang, Port, ClientOpts, Expect, ECCOpts, Config) ->
[{verify, verify_peer} | ClientOpts]}]).
start_client_ecc_error(erlang, Port, ClientOpts, ECCOpts, Config) ->
- {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+ {ClientNode, _, Hostname} = run_where(Config),
ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -1791,39 +2101,49 @@ start_server(openssl, ClientOpts, ServerOpts, Config) ->
CertArgs = openssl_cert_options(ServerOpts, server),
Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)),
Groups0 = proplists:get_value(groups, ServerOpts),
+ SigAlgs = proplists:get_value(openssl_sigalgs, Config, undefined),
+ SessionArgs = proplists:get_value(session_args, Config, []),
+ Debug = openssl_debug_options(),
+
Args = case Groups0 of
undefined ->
["s_server", "-accept", integer_to_list(Port), cipher_flag(Version),
ciphers(Ciphers, Version),
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"];
+ version_flag(Version)] ++ sig_algs(SigAlgs) ++ CertArgs ++ SessionArgs ++ Debug;
Group ->
["s_server", "-accept", integer_to_list(Port), cipher_flag(Version),
ciphers(Ciphers, Version), "-groups", Group,
- ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]
+ version_flag(Version)] ++ sig_algs(SigAlgs) ++ CertArgs ++ SessionArgs ++ Debug
end,
OpenSslPort = portable_open_port(Exe, Args),
true = port_command(OpenSslPort, "Hello world"),
{OpenSslPort, Port};
start_server(erlang, _, ServerOpts, Config) ->
- {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+ {_, ServerNode, _} = run_where(Config),
KeyEx = proplists:get_value(check_keyex, Config, false),
- Versions = protocol_versions(Config),
Server = start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib,
check_key_exchange_send_active,
[KeyEx]}},
- {options, [{verify, verify_peer}, {versions, Versions} | ServerOpts]}]),
+ {options, [{verify, verify_peer} | ServerOpts]}]),
{Server, inet_port(Server)}.
+sig_algs(undefined) ->
+ [];
+sig_algs(SigAlgs) ->
+ ["-sigalgs " ++ SigAlgs].
+
cipher_flag('tlsv1.3') ->
"-ciphersuites";
cipher_flag(_) ->
"-cipher".
-ciphers(Ciphers, Version) ->
+ciphers([#{}| _] = Ciphers, Version) ->
Strs = [ssl_cipher_format:suite_map_to_openssl_str(Cipher) || Cipher <- Ciphers],
- ciphers_concat(Version, Strs, "").
+ ciphers_concat(Version, Strs, "");
+ciphers(Ciphers, Version) ->
+ ciphers_concat(Version, Ciphers, "").
ciphers_concat(_, [], [":" | Acc]) ->
lists:append(lists:reverse(Acc));
@@ -1837,8 +2157,47 @@ ciphers_concat('tlsv1.3' = Version, [Head| Tail], Acc) ->
ciphers_concat(Version, [Head| Tail], Acc) ->
ciphers_concat(Version, Tail, [":", Head | Acc]).
+openssl_alpn_options(undefined) ->
+ [];
+openssl_alpn_options(Alpn) ->
+ ["-alpn", Alpn].
+
+openssl_npn_options(undefined) ->
+ [];
+openssl_npn_options(Npn) ->
+ ["-nextprotoneg", Npn].
+
+openssl_reconect_option(false) ->
+ [];
+openssl_reconect_option(true) ->
+ ["-reconnect"].
+openssl_maxfag_option(false) ->
+ [];
+openssl_maxfag_option(Int) ->
+ ["-maxfraglen", integer_to_list(Int)].
+
+openssl_debug_options() ->
+ ["-msg", "-debug"].
+%%
+openssl_debug_options(PrivDir) ->
+ case is_keylogfile_supported() of
+ true ->
+ ["-msg", "-debug","-keylogfile", PrivDir ++ "keylog"];
+ false ->
+ ["-msg", "-debug"]
+ end.
+
+is_keylogfile_supported() ->
+ [{_,_, Bin}] = crypto:info_lib(),
+ case binary_to_list(Bin) of
+ "OpenSSL 1.1.1" ++ _ ->
+ true;
+ _ ->
+ false
+ end.
+
start_server_with_raw_key(erlang, ServerOpts, Config) ->
- {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+ {_, ServerNode, _} = run_where(Config),
Server = start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib,
@@ -1903,7 +2262,8 @@ openssl_cert_options(Opts, Role) ->
Other
end;
_ ->
- cert_option("-cert", Cert) ++ cert_option("-CAfile", CA) ++
+ cert_option("-cert", Cert) ++ cert_option("-CAfile", CA)
+ ++ cert_option("-cert_chain", CA) ++
cert_option("-key", Key) ++ openssl_verify(Opts) ++ ["2"]
end.
@@ -1917,6 +2277,13 @@ openssl_verify(Opts) ->
cert_option(_, undefined) ->
[];
+cert_option("-cert_chain", Value) ->
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 1.1.1" ++ _ ->
+ ["-cert_chain", Value];
+ _ ->
+ ""
+ end;
cert_option(Opt, Value) ->
[Opt, Value].
@@ -1985,41 +2352,6 @@ send_selected_port(Pid, 0, Socket) ->
send_selected_port(_,_,_) ->
ok.
-rsa_suites(CounterPart) ->
- ECC = is_sane_ecc(CounterPart),
- FIPS = is_fips(CounterPart),
- CryptoSupport = crypto:supports(),
- Ciphers = proplists:get_value(ciphers, CryptoSupport),
- lists:filter(fun({rsa, des_cbc, sha}) when FIPS == true ->
- false;
- ({dhe_rsa, des_cbc, sha}) when FIPS == true ->
- false;
- ({rsa, Cipher, _}) ->
- lists:member(cipher_atom(Cipher), Ciphers);
- ({dhe_rsa, Cipher, _}) ->
- lists:member(cipher_atom(Cipher), Ciphers);
- ({ecdhe_rsa, Cipher, _}) when ECC == true ->
- lists:member(cipher_atom(Cipher), Ciphers);
- ({ecdhe_rsa, Cipher, _,_}) when ECC == true ->
- lists:member(cipher_atom(Cipher), Ciphers);
- ({rsa, Cipher, _, _}) ->
- lists:member(cipher_atom(Cipher), Ciphers);
- ({dhe_rsa, Cipher, _,_}) ->
- lists:member(cipher_atom(Cipher), Ciphers);
- (_) ->
- false
- end,
- common_ciphers(CounterPart)).
-
-common_ciphers(crypto) ->
- ssl:cipher_suites();
-common_ciphers(openssl) ->
- OpenSslSuites =
- string:tokens(string:strip(portable_cmd("openssl", ["ciphers"]), right, $\n), ":"),
- [ssl_cipher_format:suite_bin_to_map(S)
- || S <- ssl_cipher:suites(tls_record:highest_protocol_version([])),
- lists:member(ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(S)), OpenSslSuites)
- ].
available_suites(Version) ->
[ssl_cipher_format:suite_bin_to_map(Suite) ||
@@ -2034,34 +2366,13 @@ rsa_non_signed_suites(Version) ->
end,
available_suites(Version)).
-dsa_suites(Version) ->
- lists:filter(fun({dhe_dss, _, _}) ->
- true;
- (_) ->
- false
- end,
- available_suites(Version)).
-
ecdsa_suites(Version) ->
lists:filter(fun({ecdhe_ecdsa, _, _}) ->
- true;
- (_) ->
- false
- end,
- available_suites(Version)).
-
-ecdh_rsa_suites(Version) ->
- lists:filter(fun({ecdh_rsa, _, _}) ->
- true;
- (_) ->
- false
- end,
- available_suites(Version)).
-
-openssl_rsa_suites() ->
- Ciphers = ssl:cipher_suites(openssl),
- lists:filter(fun(Str) -> string_regex_filter(Str, "RSA")
- end, Ciphers) -- openssl_ecdh_rsa_suites().
+ true;
+ (_) ->
+ false
+ end,
+ available_suites(Version)).
openssl_dsa_suites() ->
Ciphers = ssl:cipher_suites(openssl),
@@ -2073,10 +2384,6 @@ openssl_ecdsa_suites() ->
lists:filter(fun(Str) -> string_regex_filter(Str, "ECDHE-ECDSA")
end, Ciphers).
-openssl_ecdh_rsa_suites() ->
- Ciphers = ssl:cipher_suites(openssl),
- lists:filter(fun(Str) -> string_regex_filter(Str, "ECDH-RSA")
- end, Ciphers).
openssl_filter(FilterStr) ->
Ciphers = string:tokens(portable_cmd("openssl", ["ciphers"]), ":"),
@@ -2104,79 +2411,6 @@ ecdh_dh_anonymous_suites(Version) ->
(_) ->
false
end}]).
-psk_suites({3,_} = Version) ->
- ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:psk_suites(Version)], []);
-psk_suites(Version) ->
- ssl:filter_cipher_suites(psk_suites(dtls_v1:corresponding_tls_version(Version)),
- [{cipher,
- fun(rc4_128) ->
- false;
- (_) ->
- true
- end}]).
-
-psk_anon_suites({3,_} = Version) ->
- ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:psk_suites_anon(Version)],
- [{key_exchange,
- fun(psk) ->
- true;
- (dhe_psk) ->
- true;
- (ecdhe_psk) ->
- true;
- (_) ->
- false
- end}]);
-
-psk_anon_suites(Version) ->
- ssl:filter_cipher_suites(psk_anon_suites(dtls_v1:corresponding_tls_version(Version)),
- [{cipher,
- fun(rc4_128) ->
- false;
- (_) ->
- true
- end}]).
-
-
-srp_suites() ->
- ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:srp_suites()],
- [{key_exchange,
- fun(srp_rsa) ->
- true;
- (_) ->
- false
- end}]).
-srp_anon_suites() ->
- ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:srp_suites_anon()],
- []).
-srp_dss_suites() ->
- ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:srp_suites()],
- [{key_exchange,
- fun(srp_dss) ->
- true;
- (_) ->
- false
- end}]).
-chacha_suites(Version) ->
- [ssl_cipher_format:suite_bin_to_map(S) || S <- ssl_cipher:filter_suites(ssl_cipher:chacha_suites(Version))].
-
-
-rc4_suites(Version) ->
- ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <-ssl_cipher:rc4_suites(Version)], []).
-
-des_suites(Version) ->
- ssl:filter_cipher_suites([ssl_cipher_format:suite_bin_to_map(S) || S <-ssl_cipher:des_suites(Version)], []).
-
-tuple_to_map({Kex, Cipher, Mac}) ->
- #{key_exchange => Kex,
- cipher => Cipher,
- mac => Mac,
- prf => default_prf};
-tuple_to_map({Kex, Cipher, Mac, Prf}) ->
- #{key_exchange => Kex,
- cipher => Cipher,
- mac => Mac,
- prf => Prf}.
pem_to_der(File) ->
{ok, PemBin} = file:read_file(File),
@@ -2216,18 +2450,6 @@ public_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key));
public_key(Key) ->
Key.
-receive_rizzo_duong_beast() ->
- receive
- {ssl, _, "ello\n"} ->
- receive
- {ssl, _, " "} ->
- receive
- {ssl, _, "world\n"} ->
- ok
- end
- end
- end.
-
state([{data,[{"State", {_StateName, StateData}}]} | _]) -> %% gen_statem
StateData;
@@ -2238,11 +2460,10 @@ state([{data,[{"StateData", State}]} | _]) -> %% gen_fsm
state([_ | Rest]) ->
state(Rest).
-%% TODO: DTLS considered tls version in this use maybe rename
-is_tls_version('dtlsv1.2') ->
- true;
-is_tls_version('dtlsv1') ->
- true;
+is_protocol_version(Ver) ->
+ is_tls_version(Ver) orelse
+ is_dtls_version(Ver).
+
is_tls_version('tlsv1.3') ->
true;
is_tls_version('tlsv1.2') ->
@@ -2251,8 +2472,6 @@ is_tls_version('tlsv1.1') ->
true;
is_tls_version('tlsv1') ->
true;
-is_tls_version('sslv3') ->
- true;
is_tls_version(_) ->
false.
@@ -2263,25 +2482,63 @@ is_dtls_version('dtlsv1') ->
is_dtls_version(_) ->
false.
-init_tls_version(Version, Config)
+openssl_tls_version_support(Version, Config0) ->
+ %% Check if version is supported
+ Config = make_rsa_cert(Config0),
+ ServerOpts = proplists:get_value(server_rsa_opts, Config),
+ Port = inet_port(node()),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+ Exe = "openssl",
+ Args0 = ["s_server", "-accept",
+ integer_to_list(Port), "-CAfile", CaCertFile,
+ "-cert", CertFile,"-key", KeyFile],
+ Args = maybe_force_ipv4(Args0),
+ OpensslPort = portable_open_port(Exe, Args),
+
+ try wait_for_openssl_server(Port, tls) of
+ ok ->
+ case ssl:connect("localhost", Port, [{versions, [Version]}]) of
+ {ok, Socket} ->
+ ssl:close(Socket),
+ close_port(OpensslPort),
+ true;
+ {error, {tls_alert, {protocol_version, _}}} ->
+ ct:pal("Openssl does not support ~p", [Version]),
+ close_port(OpensslPort),
+ false;
+ {error, {tls_alert, Alert}} ->
+ ct:pal("Openssl returned alert ~p", [Alert]),
+ close_port(OpensslPort),
+ false
+ end
+ catch
+ _:_ ->
+ ct:pal("Openssl does not support ~p", [Version]),
+ close_port(OpensslPort),
+ false
+ end.
+
+init_protocol_version(Version, Config)
when Version == 'dtlsv1.2'; Version == 'dtlsv1' ->
ssl:stop(),
application:load(ssl),
- application:set_env(ssl, dtls_protocol_version, Version),
+ application:set_env(ssl, dtls_protocol_version, [Version]),
ssl:start(),
NewConfig = proplists:delete(protocol_opts, proplists:delete(protocol, Config)),
[{protocol, dtls}, {protocol_opts, [{protocol, dtls}]} | NewConfig];
-init_tls_version(Version, Config) ->
+init_protocol_version(Version, Config) ->
ssl:stop(),
application:load(ssl),
- application:set_env(ssl, protocol_version, Version),
+ application:set_env(ssl, protocol_version, [Version]),
ssl:start(),
NewConfig = proplists:delete(protocol_opts, proplists:delete(protocol, Config)),
[{protocol, tls} | NewConfig].
-clean_tls_version(Config) ->
- proplists:delete(protocol_opts, proplists:delete(protocol, Config)).
+clean_protocol_version(Config) ->
+ proplists:delete(version, proplists:delete(protocol_opts, proplists:delete(protocol, Config))).
sufficient_crypto_support(Version)
when Version == 'tlsv1.3' ->
@@ -2361,10 +2618,17 @@ send(Pid, Data) ->
check_active_receive(Pid, Data) ->
Pid ! {active_receive, Data},
+ check_active_receive_loop(Pid, Data).
+
+check_active_receive_loop(Pid, Data) ->
receive
{Pid, Data} ->
- %% ct:log("Received: ~p~n", [Data]),
- Data
+ ct:log("(~p) Received: ~p~n (from ~p)~n", [self(), Data, Pid]),
+ Data;
+ {Pid, Data2} ->
+ ct:log("(~p) Received unexpected message: ~p~n (from ~p)~n",
+ [self(), Data2, Pid]),
+ check_active_receive_loop(Pid, Data)
end.
update_keys(Pid, Type) ->
@@ -2381,13 +2645,19 @@ send_recv_result_active_once(Socket) ->
ssl:send(Socket, Data),
active_once_recv_list(Socket, length(Data)).
+%% This function can verify the following functionalities in clients:
+%% - session resumption, sending/receiving application data, receiving session tickets
+%% - verifying if client early data is accepted/rejected
verify_active_session_resumption(Socket, SessionResumption) ->
- verify_active_session_resumption(Socket, SessionResumption, wait_reply, no_tickets).
+ verify_active_session_resumption(Socket, SessionResumption, wait_reply, no_tickets, no_early_data).
%%
verify_active_session_resumption(Socket, SessionResumption, WaitReply) ->
- verify_active_session_resumption(Socket, SessionResumption, WaitReply, no_tickets).
+ verify_active_session_resumption(Socket, SessionResumption, WaitReply, no_tickets, no_early_data).
+%%
+verify_active_session_resumption(Socket, SessionResumption, WaitReply, TicketOption) ->
+ verify_active_session_resumption(Socket, SessionResumption, WaitReply, TicketOption, no_early_data).
%%
-verify_active_session_resumption(Socket, SessionResumption, WaitForReply, TicketOption) ->
+verify_active_session_resumption(Socket, SessionResumption, WaitForReply, TicketOption, EarlyData) ->
case ssl:connection_information(Socket, [session_resumption]) of
{ok, [{session_resumption, SessionResumption}]} ->
Msg = boolean_to_log_msg(SessionResumption),
@@ -2413,15 +2683,93 @@ verify_active_session_resumption(Socket, SessionResumption, WaitForReply, Ticket
Else1 ->
ct:fail("~p:~p~nFaulty parameter: ~p", [?MODULE, ?LINE, Else1])
end,
- case TicketOption of
- {tickets, N} ->
- receive_tickets(N);
- no_tickets ->
- ok;
- Else2 ->
- ct:fail("~p:~p~nFaulty parameter: ~p", [?MODULE, ?LINE, Else2])
+ Tickets =
+ case TicketOption of
+ {tickets, N} ->
+ receive_tickets(N);
+ no_tickets ->
+ ok;
+ Else2 ->
+ ct:fail("~p:~p~nFaulty parameter: ~p", [?MODULE, ?LINE, Else2])
+ end,
+ case EarlyData of
+ {verify_early_data, Atom} ->
+ case verify_early_data(Atom) of
+ ok ->
+ Tickets;
+ Else ->
+ ct:fail("~p:~p~nFailed to verify early_data! (expected ~p, got ~p)",
+ [?MODULE, ?LINE, Atom, Else])
+ end;
+ no_early_data ->
+ Tickets;
+ Else3 ->
+ ct:fail("~p:~p~nFaulty parameter: ~p", [?MODULE, ?LINE, Else3])
end.
+verify_server_early_data(Socket, WaitForReply, EarlyData) ->
+ case ssl:connection_information(Socket, [session_resumption]) of
+ {ok, [{session_resumption, true}]} ->
+ Msg = boolean_to_log_msg(true),
+ ct:log("~p:~p~nSession resumption verified! (expected ~p, got ~p)!",
+ [?MODULE, ?LINE, Msg, Msg]);
+ {ok, [{session_resumption, Got0}]} ->
+ Expected = boolean_to_log_msg(true),
+ Got = boolean_to_log_msg(Got0),
+ ct:fail("~p:~p~nFailed to verify session resumption! (expected ~p, got ~p)",
+ [?MODULE, ?LINE, Expected, Got]);
+ {error, Reason} ->
+ ct:fail("~p:~p~nFailed to verify session resumption! Reason: ~p",
+ [?MODULE, ?LINE, Reason])
+ end,
+ Data = "Hello world",
+ ssl:send(Socket, Data),
+ Reply =
+ case EarlyData of
+ no_early_data ->
+ Data;
+ _ ->
+ binary_to_list(EarlyData) ++ Data
+ end,
+ ct:log("Expected Reply: ~p~n", [Reply]),
+ case WaitForReply of
+ wait_reply ->
+ Reply = active_recv(Socket, length(Reply));
+ no_reply ->
+ ok;
+ Else1 ->
+ ct:fail("~p:~p~nFaulty parameter: ~p", [?MODULE, ?LINE, Else1])
+ end,
+ ok.
+
+verify_session_ticket_extension([Ticket0|_], MaxEarlyDataSize) ->
+ #{ticket := #new_session_ticket{
+ extensions = #{early_data :=
+ #early_data_indication_nst{
+ indication = Size}}}} = Ticket0,
+ case Size of
+ MaxEarlyDataSize ->
+ ct:log("~p:~p~nmax_early_data_size verified! (expected ~p, got ~p)!",
+ [?MODULE, ?LINE, MaxEarlyDataSize, Size]);
+ Else ->
+ ct:log("~p:~p~nFailed to verify max_early_data_size! (expected ~p, got ~p)!",
+ [?MODULE, ?LINE, MaxEarlyDataSize, Else])
+ end.
+
+update_session_ticket_extension([Ticket|_], MaxEarlyDataSize) ->
+ #{ticket := #new_session_ticket{
+ extensions = #{early_data :=
+ #early_data_indication_nst{
+ indication = Size}}}} = Ticket,
+ ct:log("~p:~p~nOverwrite max_early_data_size (from ~p to ~p)!",
+ [?MODULE, ?LINE, Size, MaxEarlyDataSize]),
+ #{ticket := #new_session_ticket{
+ extensions = #{early_data := Extensions0}} = NST0} = Ticket,
+ Extensions = #{early_data => #early_data_indication_nst{
+ indication = MaxEarlyDataSize}},
+ NST = NST0#new_session_ticket{extensions = Extensions},
+ [Ticket#{ticket => NST}].
+
boolean_to_log_msg(true) ->
"OK";
boolean_to_log_msg(false) ->
@@ -2434,7 +2782,7 @@ receive_tickets(0, Acc) ->
Acc;
receive_tickets(N, Acc) ->
receive
- {ssl, session_ticket, {_, Ticket}} ->
+ {ssl, session_ticket, Ticket} ->
receive_tickets(N - 1, [Ticket|Acc])
end.
@@ -2447,6 +2795,17 @@ check_tickets(Client) ->
ct:fail("~p:~p~nNo tickets received!", [?MODULE, ?LINE])
end.
+active_recv_loop(Pid, SslPort, Data) ->
+ case active_recv(SslPort, length(Data)) of
+ Data ->
+ ct:log("(~p) [openssl server] Received: ~p~n (forward to PID=~p)~n",
+ [self(), Data, Pid]),
+ Pid ! {self(), Data};
+ Unexpected ->
+ ct:log("(~p) [openssl server] Received unexpected: ~p~n (dropping message)~n", [self(), Unexpected]),
+ active_recv_loop(Pid, SslPort, Data)
+ end.
+
active_recv(Socket, N) ->
active_recv(Socket, N, []).
@@ -2457,17 +2816,24 @@ active_recv(_Socket, N, Acc) when N < 0 ->
T;
active_recv(Socket, N, Acc) ->
receive
- {ssl, Socket, Bytes} ->
- active_recv(Socket, N-length(Bytes), Acc ++ Bytes);
+ %% Filter {ssl, Socket, {early_data, Atom}} messages
+ {ssl, Socket, Bytes} when not is_tuple(Bytes) ->
+ active_recv(Socket, N-data_length(Bytes), Acc ++ Bytes);
{Socket, {data, Bytes0}} ->
Bytes = filter_openssl_debug_data(Bytes0),
- active_recv(Socket, N-length(Bytes), Acc ++ Bytes)
+ active_recv(Socket, N-data_length(Bytes), Acc ++ Bytes)
end.
+
+data_length(Bytes) when is_list(Bytes) ->
+ length(Bytes);
+data_length(Bytes) when is_binary(Bytes)->
+ size(Bytes).
+
filter_openssl_debug_data(Bytes) ->
re:replace(Bytes,
"(read.*\n|write to.*\n|[\\dabcdefABCDEF]{4,4} -.*\n|>>> .*\n|<<< .*\n| \\d\\d.*\n|KEYUPDATE\n|.*Read BLOCK\n)*",
- "", [{return, list}]).
+ "", [global,{return, list}]).
active_once_recv(_Socket, 0) ->
ok;
@@ -2514,40 +2880,12 @@ is_ipv6_supported() ->
false;
"OpenSSL 1.0" ++ _ -> % Does not support IPv6
false;
+ "LibreSSL 3" ++ _->
+ false;
_ ->
true
end.
-is_sane_ecc(openssl) ->
- case portable_cmd("openssl", ["version"]) of
- "OpenSSL 1.0.0a" ++ _ -> % Known bug in openssl
- %% manifests as SSL_CHECK_SERVERHELLO_TLSEXT:tls invalid ecpointformat list
- false;
- "OpenSSL 1.0.0" ++ _ -> % Known bug in openssl
- %% manifests as SSL_CHECK_SERVERHELLO_TLSEXT:tls invalid ecpointformat list
- false;
- "OpenSSL 1.0.1l" ++ _ ->
- %% Breaks signature verification
- false;
- "OpenSSL 0.9.8" ++ _ -> % Does not support ECC
- false;
- "OpenSSL 0.9.7" ++ _ -> % Does not support ECC
- false;
- _ ->
- true
- end;
-is_sane_ecc(crypto) ->
- [{_,_, Bin}] = crypto:info_lib(),
- case binary_to_list(Bin) of
- "OpenSSL 0.9.8" ++ _ -> % Does not support ECC
- false;
- "OpenSSL 0.9.7" ++ _ -> % Does not support ECC
- false;
- _ ->
- true
- end;
-is_sane_ecc(_) ->
- sufficient_crypto_support(cipher_ec).
is_sane_oppenssl_client() ->
[{_,_, Bin}] = crypto:info_lib(),
@@ -2558,6 +2896,21 @@ is_sane_oppenssl_client() ->
true
end.
+is_sane_oppenssl_pss(rsa_pss_pss) ->
+ case portable_cmd("openssl",["version"]) of
+ "OpenSSL 1.1.1" ++ Rest ->
+ hd(Rest) >= $c;
+ _ ->
+ false
+ end;
+is_sane_oppenssl_pss(rsa_pss_rsae) ->
+ case portable_cmd("openssl",["version"]) of
+ "OpenSSL 1.1.1" ++ _ ->
+ true;
+ _ ->
+ false
+ end.
+
is_fips(openssl) ->
VersionStr = portable_cmd("openssl",["version"]),
case re:split(VersionStr, "fips") of
@@ -2577,49 +2930,6 @@ is_fips(crypto) ->
is_fips(_) ->
false.
-cipher_restriction(Config0) ->
- Version = protocol_version(Config0, tuple),
- case is_sane_ecc(openssl) of
- false ->
- Opts = proplists:get_value(server_opts, Config0),
- Config1 = proplists:delete(server_opts, Config0),
- VerOpts = proplists:get_value(server_verification_opts, Config1),
- Config = proplists:delete(server_verification_opts, Config1),
- Restricted0 = ssl:cipher_suites() -- ecdsa_suites(Version),
- Restricted = Restricted0 -- ecdh_rsa_suites(Version),
- [{server_opts, [{ciphers, Restricted} | Opts]}, {server_verification_opts, [{ciphers, Restricted} | VerOpts] } | Config];
- true ->
- Config0
- end.
-
-openssl_dsa_support() ->
- case portable_cmd("openssl", ["version"]) of
- "LibreSSL 2.6.1" ++ _ ->
- true;
- "LibreSSL 2.6.2" ++ _ ->
- true;
- "LibreSSL 2.6" ++ _ ->
- false;
- "LibreSSL 2.4" ++ _ ->
- true;
- "LibreSSL 2.3" ++ _ ->
- true;
- "LibreSSL 2.2" ++ _ ->
- true;
- "LibreSSL 2.1" ++ _ ->
- true;
- "LibreSSL 2.0" ++ _ ->
- true;
- "LibreSSL" ++ _ ->
- false;
- "OpenSSL 1.1" ++ _Rest ->
- false;
- "OpenSSL 1.0.1" ++ Rest ->
- hd(Rest) >= $s;
- _ ->
- true
- end.
-
%% Acctual support is tested elsewhere, this is to exclude some LibreSSL and OpenSSL versions
openssl_sane_dtls() ->
case portable_cmd("openssl", ["version"]) of
@@ -2640,25 +2950,6 @@ openssl_sane_dtls() ->
_ ->
false
end.
-openssl_sane_client_cert() ->
- case portable_cmd("openssl", ["version"]) of
- "LibreSSL 2.5.2" ++ _ ->
- true;
- "LibreSSL 2.4" ++ _ ->
- false;
- "LibreSSL 2.3" ++ _ ->
- false;
- "LibreSSL 2.1" ++ _ ->
- false;
- "LibreSSL 2.0" ++ _ ->
- false;
- "OpenSSL 1.0.1s-freebsd" ->
- false;
- "OpenSSL 1.0.0" ++ _ ->
- false;
- _ ->
- true
- end.
check_sane_openssl_version(Version) ->
case supports_ssl_tls_version(Version) of
@@ -2696,7 +2987,7 @@ check_sane_openssl_version(Version) ->
false ->
false
end.
-check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1';
+check_sane_openssl_renegotiate(Config, Version) when Version == 'tlsv1';
Version == 'tlsv1.1';
Version == 'tlsv1.2' ->
case portable_cmd("openssl", ["version"]) of
@@ -2709,22 +3000,17 @@ check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1';
"OpenSSL 1.0.1 " ++ _ ->
{skip, "Known renegotiation bug in OpenSSL"};
"LibreSSL 3.0.2" ++ _ ->
- {skip, "Known renegotiation bug in OpenSSL"};
+ {skip, "Known renegotiation bug in LibreSSL"};
+ "LibreSSL 3.1" ++ _ ->
+ {skip, "Known renegotiation bug in LibreSSL"};
_ ->
- check_sane_openssl_renegotaite(Config)
+ check_sane_openssl_renegotiate(Config)
end;
-check_sane_openssl_renegotaite(Config, 'sslv3') ->
- case os:cmd("openssl version") of
- "OpenSSL 1" ++ _ ->
- {skip, "Known renegotiation bug with sslv3 in OpenSSL"};
- _ ->
- check_sane_openssl_renegotaite(Config)
- end;
-check_sane_openssl_renegotaite(Config, _) ->
- check_sane_openssl_renegotaite(Config).
-
-check_sane_openssl_renegotaite(Config) ->
- case os:cmd("openssl version") of
+check_sane_openssl_renegotiate(Config, _) ->
+ check_sane_openssl_renegotiate(Config).
+
+check_sane_openssl_renegotiate(Config) ->
+ case portable_cmd("openssl", ["version"]) of
"OpenSSL 1.0.0" ++ _ ->
{skip, "Known renegotiation bug in OpenSSL"};
"OpenSSL 0.9.8" ++ _ ->
@@ -2733,13 +3019,14 @@ check_sane_openssl_renegotaite(Config) ->
{skip, "Known renegotiation bug in OpenSSL"};
"LibreSSL 2." ++ _ ->
{skip, "Known renegotiation bug in LibreSSL"};
-
+ "LibreSSL 3." ++ _ ->
+ {skip, "Known renegotiation bug in LibreSSL"};
_ ->
Config
end.
-openssl_allows_client_renegotaite(Config) ->
- case os:cmd("openssl version") of
+openssl_allows_client_renegotiate(Config) ->
+ case portable_cmd("openssl", ["version"]) of
"OpenSSL 1.1" ++ _ ->
{skip, "OpenSSL does not allow client renegotiation"};
"LibreSSL" ++ _ ->
@@ -2748,23 +3035,14 @@ openssl_allows_client_renegotaite(Config) ->
Config
end.
-workaround_openssl_s_clinent() ->
- %% http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=683159
- %% https://bugs.archlinux.org/task/33919
- %% Bug seems to manifests it self if TLS version is not
- %% explicitly specified
- case os:cmd("openssl version") of
- "OpenSSL 1.0.1c" ++ _ ->
- ["-no_tls1_2"];
- "OpenSSL 1.0.1d" ++ _ ->
- ["-no_tls1_2"];
- "OpenSSL 1.0.1e" ++ _ ->
- ["-no_tls1_2"];
- "OpenSSL 1.0.1f" ++ _ ->
- ["-no_tls1_2"];
- _ ->
- []
- end.
+openssl_allows_server_renegotiate(Config) ->
+ case portable_cmd("openssl", ["version"]) of
+ "LibreSSL 3.1" ++ _ ->
+ {skip, "LibreSSL 3.1 does not allow server renegotiation"};
+ _ ->
+ Config
+ end.
+
enough_openssl_crl_support("OpenSSL 0." ++ _) -> false;
enough_openssl_crl_support(_) -> true.
@@ -2780,7 +3058,8 @@ do_wait_for_openssl_tls_server(_, 0) ->
do_wait_for_openssl_tls_server(Port, N) ->
case gen_tcp:connect("localhost", Port, []) of
{ok, S} ->
- gen_tcp:close(S);
+ gen_tcp:close(S),
+ ok;
_ ->
ct:sleep(?SLEEP),
do_wait_for_openssl_tls_server(Port, N-1)
@@ -2803,25 +3082,6 @@ version_flag('dtlsv1.2') ->
version_flag('dtlsv1') ->
"-dtls1".
-filter_suites([Cipher | _] = Ciphers, AtomVersion) when is_list(Cipher)->
- filter_suites([ssl_cipher_format:suite_openssl_str_to_map(S) || S <- Ciphers],
- AtomVersion);
-filter_suites([Cipher | _] = Ciphers, AtomVersion) when is_binary(Cipher)->
- filter_suites([ssl_cipher_format:suite_bin_to_map(S) || S <- Ciphers],
- AtomVersion);
-filter_suites(Ciphers0, AtomVersion) ->
- Version = tls_version(AtomVersion),
- Supported0 = ssl_cipher:suites(Version)
- ++ ssl_cipher:anonymous_suites(Version)
- ++ ssl_cipher:psk_suites(Version)
- ++ ssl_cipher:psk_suites_anon(Version)
- ++ ssl_cipher:srp_suites(Version)
- ++ ssl_cipher:srp_suites_anon(Version)
- ++ ssl_cipher:rc4_suites(Version),
- Supported1 = ssl_cipher:filter_suites(Supported0),
- Supported2 = [ssl_cipher_format:suite_bin_to_map(S) || S <- Supported1],
- [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported2)].
-
-define(OPENSSL_QUIT, "Q\n").
close_port(Port) ->
catch port_command(Port, ?OPENSSL_QUIT),
@@ -2855,17 +3115,43 @@ close_loop(Port, Time, SentClose) ->
end
end.
+portable_open_port("openssl" = Exe, Args0) ->
+ IsWindows = case os:type() of
+ {win32, _} -> true;
+ _ -> false
+ end,
+ case IsWindows andalso os:getenv("WSLENV") of
+ false ->
+ AbsPath = os:find_executable(Exe),
+ ct:pal("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).",
+ [AbsPath, Args0]),
+ open_port({spawn_executable, AbsPath},
+ [{args, Args0}, stderr_to_stdout]);
+ _ ->
+ %% I can't get the new windows version of openssl.exe to be stable
+ %% certain server tests are failing for no reason.
+ %% This is using "linux" openssl via wslenv
+
+ Translate = fun([_Drive|":/" ++ _ ]= Path) ->
+ string:trim(os:cmd("wsl wslpath -u " ++ Path));
+ (Arg) ->
+ Arg
+ end,
+ Args1 = [Translate(Arg) || Arg <- Args0],
+ Args = ["/C","wsl","openssl"| Args1] ++ ["2>&1"],
+ Cmd = os:find_executable("cmd"),
+ ct:pal("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).", [Cmd,Args]),
+ open_port({spawn_executable, Cmd},
+ [{args, Args}, stderr_to_stdout, hide])
+ end;
portable_open_port(Exe, Args) ->
AbsPath = os:find_executable(Exe),
ct:pal("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).", [AbsPath, Args]),
- open_port({spawn_executable, AbsPath},
- [{args, Args}, stderr_to_stdout]).
+ open_port({spawn_executable, AbsPath},
+ [{args, Args}, stderr_to_stdout]).
portable_cmd(Exe, Args) ->
- AbsPath = os:find_executable(Exe),
- ct:pal("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).", [AbsPath, Args]),
- Port = open_port({spawn_executable, AbsPath},
- [{args, Args}, stderr_to_stdout]),
+ Port = portable_open_port(Exe, Args),
receive
{Port, {data, Data}} ->
catch erlang:port_close(Port),
@@ -2878,10 +3164,10 @@ supports_ssl_tls_version(Version) when Version == sslv2;
case ubuntu_legacy_support() of
true ->
case portable_cmd("openssl", ["version"]) of
- "OpenSSL 1.1" ++ _ ->
- false;
- "OpenSSL 1" ++ _ ->
+ "OpenSSL 1.0.1" ++ _ ->
Version =/= sslv2;
+ "OpenSSL 1" ++ _ ->
+ false;
%% Appears to be broken
"OpenSSL 0.9.8.o" ++ _ ->
false;
@@ -2891,7 +3177,7 @@ supports_ssl_tls_version(Version) when Version == sslv2;
Args = ["s_client", VersionFlag],
[{trap_exit, Trap}] = process_info(self(), [trap_exit]),
process_flag(trap_exit, true),
- Port = ssl_test_lib:portable_open_port(Exe, Args),
+ Port = portable_open_port(Exe, Args),
Bool = do_supports_ssl_tls_version(Port, ""),
consume_port_exit(Port),
process_flag(trap_exit, Trap),
@@ -2904,7 +3190,7 @@ supports_ssl_tls_version(Version) ->
VersionFlag = version_flag(Version),
Exe = "openssl",
Args = ["s_client", VersionFlag],
- Port = ssl_test_lib:portable_open_port(Exe, Args),
+ Port = portable_open_port(Exe, Args),
do_supports_ssl_tls_version(Port, "").
do_supports_ssl_tls_version(Port, Acc) ->
@@ -2948,8 +3234,12 @@ ssl_options(Options, Config) ->
Options ++ ProtocolOpts.
protocol_version(Config) ->
- protocol_version(Config, atom).
-
+ case proplists:get_value(version, Config, undefined) of
+ undefined ->
+ protocol_version(Config, atom);
+ Version ->
+ Version
+ end.
protocol_version(Config, tuple) ->
case proplists:get_value(protocol, Config) of
dtls ->
@@ -2966,14 +3256,6 @@ protocol_version(Config, atom) ->
tls_record:protocol_version(protocol_version(Config, tuple))
end.
-protocol_versions(Config) ->
- Version = protocol_version(Config),
- case Version of
- 'tlsv1.3' -> %% TLS-1.3 servers shall also support 1.2
- ['tlsv1.3', 'tlsv1.2'];
- _ ->
- [Version]
- end.
protocol_options(Config, Options) ->
Protocol = proplists:get_value(protocol, Config, tls),
{Protocol, Opts} = lists:keyfind(Protocol, 1, Options),
@@ -2999,36 +3281,31 @@ clean_env() ->
application:unset_env(ssl, bypass_pem_cache),
application:unset_env(ssl, alert_timeout),
application:unset_env(ssl, internal_active_n).
+%%
+clean_env(keep_version) ->
+ application:unset_env(ssl, session_lifetime),
+ application:unset_env(ssl, session_cb),
+ application:unset_env(ssl, session_cb_init_args),
+ application:unset_env(ssl, session_cache_client_max),
+ application:unset_env(ssl, session_cache_server_max),
+ application:unset_env(ssl, ssl_pem_cache_clean),
+ application:unset_env(ssl, bypass_pem_cache),
+ application:unset_env(ssl, alert_timeout),
+ application:unset_env(ssl, internal_active_n).
clean_start() ->
ssl:stop(),
application:load(ssl),
clean_env(),
ssl:start().
+%%
+clean_start(keep_version) ->
+ ssl:stop(),
+ application:load(ssl),
+ clean_env(keep_version),
+ ssl:start().
-is_psk_anon_suite({psk, _,_}) ->
- true;
-is_psk_anon_suite({dhe_psk,_,_}) ->
- true;
-is_psk_anon_suite({ecdhe_psk,_,_}) ->
- true;
-is_psk_anon_suite({psk, _,_,_}) ->
- true;
-is_psk_anon_suite({dhe_psk, _,_,_}) ->
- true;
-is_psk_anon_suite({ecdhe_psk, _,_,_}) ->
- true;
-is_psk_anon_suite(_) ->
- false.
-cipher_atom(aes_256_cbc) ->
- aes_cbc256;
-cipher_atom(aes_128_cbc) ->
- aes_cbc128;
-cipher_atom('3des_ede_cbc') ->
- des_ede3;
-cipher_atom(Atom) ->
- Atom.
tls_version('dtlsv1' = Atom) ->
dtls_v1:corresponding_tls_version(dtls_record:protocol_version(Atom));
tls_version('dtlsv1.2' = Atom) ->
@@ -3117,6 +3394,41 @@ hardcode_rsa_key(6) ->
coefficient = 81173034184183681160439870161505779100040258708276674532866007896310418779840630960490793104541748007902477778658270784073595697910785917474138815202903114440800310078464142273778315781957021015333260021813037604142367434117205299831740956310682461174553260184078272196958146289378701001596552915990080834227,
otherPrimeInfos = asn1_NOVALUE}.
+hardcode_rsa_1024_key(1) ->
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = 152618920709346576506952607098028299458615405194120516804067302859774798720862572082626851690572130284910454988859007980367926204341637028795420927026111160369130942718840998292351565050537705794496742217762844103737634290634532232714374862322877076125650783658974242305324207239909234718160759907957502819181,
+ publicExponent = 17,
+ privateExponent = 89775835711380339121736827704722529093303179525953245178863119329279293365213277695662853935630664873476738228740592929628191884906845311056129957074183020957315463095429495020547731127789657232144654051871515007759243605000909583210051114028049068215595185959728886310943042856399988846590947179831354428913,
+ prime1 = 13018105310773689694711101111767176661493882304979760063552973933059514785910240943852845097923711145970844208861778343060919395218474310542285865516544653,
+ prime2 = 11723589344682921162046319310366118627005968525349821205037572987102618200031016344115630095736447992996683226273798377973464634035204645607416378683745377,
+ exponent1 = 7657709006337464526300647712804221565584636649988094155031161137093832227006024084619320645837477144688731887565751966506423173657926065024874038539143913,
+ exponent2 = 11033966442054514034867124056815170472476205670917478781211833399625993600029191853285298913634303993408643036492986708680907890856663195865803650525878001,
+ coefficient = 7357357483264399363785138527396251818499941660605442417644885251395376792981387533016821796011101917057636813775613592220898054882923958484000235934554630,
+ otherPrimeInfos = asn1_NOVALUE};
+hardcode_rsa_1024_key(2) ->
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = 132417984874904377472239436563253515498607309816574784497785056464473431603604973287322746340055062696030016903830406088140534281534301418467490242269156926775506208514027213826501153438861284871625076651352798208559277520683414148048437439635357639033850360133068980157555507518934285770383924814915583919331,
+ publicExponent = 17,
+ privateExponent = 116839398419033274240211267555811925439947626308742456909810343939241263179651447018225952652989761202379426679850358313065177307236148310412491390237491385620149263549211570156731410125598364338974865883306073709062002620705336269289633237348474049621806833904576124689232282666798030505410189805859996211233,
+ prime1 = 12354286715326546399270830019697416039683060665495490476376955068446562229853736822465010796530936501225722243114286822522048306078247961653481711526701259,
+ prime2 = 10718383661165041035044708868932433765604392896488115438294667272770655136522450030638962957185192634722652306257889603065114923772949624056219896061512009,
+ exponent1 = 5087059235722695576170341772816583075163613215204025490272863851713290329939773985720886798571562088740003276576471044567902243679278572445551292981582871,
+ exponent2 = 6304931565391200608849828746430843391531995821463597316643921925159208903836735312140566445403054491013324886034052707685361719866440955327188174153830593,
+ coefficient = 6764088858264512915296172980190092445938774052616013205418164952211827027745702759906572599388571087295432259160097016323193144471211837074613329649320009,
+ otherPrimeInfos = asn1_NOVALUE};
+hardcode_rsa_1024_key(3) ->
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = 132603582566987335579015215397416921308461253540735107996254563101087328483405996961761145905021132317760270172654141110354018131670337351296871719192630978670273323069438897632586026697023844069174787494970866246368200405578784055149230641370998125414763230872277095376893138420738507940599560410343688278361,
+ publicExponent = 17,
+ privateExponent = 124803371827752786427308438021098278878551768038338925172945471153964544454970350081657549087078712769656724868380368103862605300395611624749996912181299722918452562565562892031863847812293655197586374503957768862684015202213024002730410420619423541154205461118764880018581745374583581669240937327152309672753,
+ prime1 = 12202483472094988277172439292742673588688995751099198683383744575043357099902468606144011463115716181768777309695574163698153032647393450605174909802187971,
+ prime2 = 10866934003248540047676291395653788246732743513485317053446021859209870346149779563425451397497222238159656279714782986335807210805023580459325334557063091,
+ exponent1 = 3588965727086761257991893909630198114320292867970352553936395463248046205853667237101179842092857700520228620498698283440633244896292191354463208765349403,
+ exponent2 = 4474619883690575313749061162916265748654659093788071727889538412615828966061673937881068222498856215712799644588440053197097086802068533130310431876437743,
+ coefficient = 6440880395775803235356940314241907933534073137546236980469653455119937607298142560546736915150573386382326185901566797818281064505978928392351326571984856,
+ otherPrimeInfos = asn1_NOVALUE}.
+
+
hardcode_dsa_key(1) ->
{'DSAPrivateKey',0,
99438313664986922963487511141216248076486724382260996073922424025828494981416579966171753999204426907349400798052572573634137057487829150578821328280864500098312146772602202702021153757550650696224643730869835650674962433068943942837519621267815961566259265204876799778977478160416743037274938277357237615491,
@@ -3141,17 +3453,7 @@ hardcode_dsa_key(3) ->
y = 48598545580251057979126570873881530215432219542526130654707948736559463436274835406081281466091739849794036308281564299754438126857606949027748889019480936572605967021944405048011118039171039273602705998112739400664375208228641666852589396502386172780433510070337359132965412405544709871654840859752776060358,
x = 1457508827177594730669011716588605181448418352823}.
-tcp_delivery_workaround(Server, ServerMsg, Client, ClientMsg) ->
- receive
- {Server, ServerMsg} ->
- client_msg(Client, ClientMsg);
- {Client, ClientMsg} ->
- server_msg(Server, ServerMsg);
- {Client, {error,closed}} ->
- server_msg(Server, ServerMsg);
- {Server, {error,closed}} ->
- client_msg(Client, ClientMsg)
- end.
+
client_msg(Client, ClientMsg) ->
receive
{Client, ClientMsg} ->
@@ -3184,23 +3486,23 @@ session_id(Socket) ->
ID.
reuse_session(ClientOpts, ServerOpts, Config) ->
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ {ClientNode, ServerNode, Hostname} = run_where(Config),
Server0 =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
{tcp_options, [{active, false}]},
{options, ServerOpts}]),
- Port0 = ssl_test_lib:inet_port(Server0),
+ Port0 = inet_port(Server0),
- Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ Client0 = start_client([{node, ClientNode},
{port, Port0}, {host, Hostname},
{mfa, {ssl_test_lib, session_id, []}},
{from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
Server0 ! listen,
- Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ Client1 = start_client([{node, ClientNode},
{port, Port0}, {host, Hostname},
{mfa, {ssl_test_lib, session_id, []}},
{from, self()}, {options, ClientOpts}]),
@@ -3220,7 +3522,7 @@ reuse_session(ClientOpts, ServerOpts, Config) ->
Server0 ! listen,
Client2 =
- ssl_test_lib:start_client([{node, ClientNode},
+ start_client([{node, ClientNode},
{port, Port0}, {host, Hostname},
{mfa, {ssl_test_lib, session_id, []}},
{from, self()}, {options, [{reuse_sessions, false}
@@ -3232,23 +3534,23 @@ reuse_session(ClientOpts, ServerOpts, Config) ->
ok
end,
- ssl_test_lib:close(Server0),
- ssl_test_lib:close(Client0),
- ssl_test_lib:close(Client1),
- ssl_test_lib:close(Client2),
+ close(Server0),
+ close(Client0),
+ close(Client1),
+ close(Client2),
Server1 =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
{tcp_options, [{active, false}]},
- {options, [{reuse_sessions, false} |ServerOpts]}]),
- Port1 = ssl_test_lib:inet_port(Server1),
+ {options, [{reuse_sessions, false} | ServerOpts]}]),
+ Port1 = inet_port(Server1),
- Client3 = ssl_test_lib:start_client([{node, ClientNode},
- {port, Port1}, {host, Hostname},
- {mfa, {ssl_test_lib, session_id, []}},
- {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
+ Client3 = start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
SID1 = receive
{Client3, Id3} ->
Id3
@@ -3257,11 +3559,11 @@ reuse_session(ClientOpts, ServerOpts, Config) ->
Server1 ! listen,
Client4 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port1}, {host, Hostname},
- {mfa, {ssl_test_lib, session_id, []}},
- {from, self()}, {options, ClientOpts}]),
-
+ start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, ClientOpts}]),
+
receive
{Client4, SID1} ->
ct:fail(session_reused_when_session_reuse_disabled_by_server);
@@ -3269,9 +3571,9 @@ reuse_session(ClientOpts, ServerOpts, Config) ->
ok
end,
- ssl_test_lib:close(Server1),
- ssl_test_lib:close(Client3),
- ssl_test_lib:close(Client4).
+ close(Server1),
+ close(Client3),
+ close(Client4).
user_lookup(psk, _Identity, UserState) ->
{ok, UserState};
@@ -3295,10 +3597,13 @@ digest() ->
kill_openssl() ->
case os:type() of
- {unix, _} ->
- os:cmd("pkill openssl");
{win32, _} ->
- os:cmd("cmd.exe /C \"taskkill /IM openssl.exe /F\"")
+ case os:getenv("WSLENV") of
+ false -> os:cmd("cmd.exe /C \"taskkill /IM openssl.exe /F\"");
+ _ -> os:cmd("wsl pkill openssl")
+ end;
+ _ ->
+ os:cmd("pkill openssl")
end.
hostname_format(Hostname) ->
@@ -3318,7 +3623,7 @@ erlang_ssl_receive_and_assert_negotiated_protocol(Socket, Protocol, Data) ->
end.
check_openssl_npn_support(Config) ->
- HelpText = os:cmd("openssl s_client --help"),
+ HelpText = portable_cmd("openssl", ["s_client --help"]),
case string:str(HelpText, "nextprotoneg") of
0 ->
{skip, "Openssl not compiled with nextprotoneg support"};
@@ -3348,7 +3653,7 @@ new_config(PrivDir, ServerOpts0) ->
openssl_sane_dtls_alpn() ->
- case os:cmd("openssl version") of
+ case portable_cmd("openssl", ["version"]) of
"OpenSSL 1.1.0g" ++ _ ->
false;
"OpenSSL 1.1.1 " ++ _ ->
@@ -3364,7 +3669,7 @@ openssl_sane_dtls_alpn() ->
end.
openssl_sane_dtls_session_reuse() ->
- case os:cmd("openssl version") of
+ case portable_cmd("openssl", ["version"]) of
"OpenSSL 1.1.1 " ++ _ ->
false;
"OpenSSL 1.1.1a" ++ _ ->
@@ -3373,14 +3678,128 @@ openssl_sane_dtls_session_reuse() ->
openssl_sane_dtls()
end.
+set_protocol_versions(Version) when Version == 'tlsv1';
+ Version == 'tlsv1.1';
+ Version == 'tlsv1.2';
+ Version == 'tlsv1.3'->
+ set_protocol_versions(protocol_version, [Version]);
+set_protocol_versions(Version) when Version == 'dtlsv1';
+ Version == 'dtlsv1.2' ->
+ set_protocol_versions(dtls_protocol_version, [Version]).
+
+set_protocol_versions(_, undefined) ->
+ ok;
+set_protocol_versions(AppVar, Value) ->
+ application:set_env(ssl, AppVar, Value).
+
+pss_params(sha256) ->
+ #'RSASSA-PSS-params'{
+ hashAlgorithm = #'HashAlgorithm'{algorithm = ?'id-sha256'},
+ maskGenAlgorithm = #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
+ parameters = #'HashAlgorithm'{algorithm = ?'id-sha256'}
+ },
+ saltLength = 32,
+ trailerField = 1}.
+
+test_ciphers(Kex, Cipher, Version) ->
+ ssl:filter_cipher_suites(
+ ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version),
+ [{key_exchange,
+ fun(K) when K == Kex -> true;
+ (_) -> false
+ end},
+ {cipher,
+ fun(C) when C == Cipher -> true;
+ (_) -> false
+ end}]).
+
+sanity_check(ErlangPeer, OpenSSLPort) ->
+ Data = "OpenSSL to Erlang",
+ port_command(OpenSSLPort, Data, [nosuspend]),
+ Data = check_active_receive(ErlangPeer, Data).
+
+default_tls_version(Config) ->
+ case proplists:get_value(protocol, Config, tls) of
+ tls ->
+ {ok, Versions} = application:get_env(ssl, protocol_version),
+ Versions;
+ dtls ->
+ {ok, Versions} = application:get_env(ssl, dtls_protocol_version),
+ Versions
+ end.
+
+openssl_maxfraglen_support() ->
+ case portable_cmd("openssl", ["version"]) of
+ %% Max fragmentation support introduced in OpenSSL 1.1.1
+ "OpenSSL 0" ++ _ ->
+ false;
+ "OpenSSL 1.0" ++ _ ->
+ false;
+ "OpenSSL 1.1.0" ++ _ ->
+ false;
+ "OpenSSL 1.1.1" ++ _ ->
+ true;
+ "OpenSSL" ++ _ ->
+ true;
+ _ ->
+ false
+ end.
+
+openssl_dtls_maxfraglen_support() ->
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 0" ++ _ ->
+ false;
+ "OpenSSL 1.0" ++ _ ->
+ false;
+ "OpenSSL 1.1.0" ++ _ ->
+ false;
+ "OpenSSL 1.1.1" ++ _ ->
+ false;
+ "OpenSSL 1.1" ++ _ ->
+ false;
+ "OpenSSL" ++ _ ->
+ true;
+ _ ->
+ false
+ end.
+
+assert_mfl(Socket, undefined) ->
+ InfoMFL = ssl:connection_information(Socket, [max_fragment_length]),
+ ct:log("Connection MFL ~p, Expecting: [] ~n", [InfoMFL]),
+ {ok, []} = InfoMFL;
+assert_mfl(Socket, MFL) ->
+ InfoMFL = ssl:connection_information(Socket, [max_fragment_length]),
+ ct:log("Connection MFL ~p, Expecting: ~p ~n", [InfoMFL, MFL]),
+ {ok, [{max_fragment_length, ConnMFL}]} = InfoMFL,
+ ConnMFL = MFL.
-define(BIG_BUF, 10000000).
%% Workaround data delivery issues on solaris | openbsd when kernel buffers are small
bigger_buffers() ->
case os:type() of
{unix,sunos} ->
- [{recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}];
+ [{buffer, ?BIG_BUF}, {recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}];
{unix,openbsd} ->
- [{recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}];
+ [{buffer, ?BIG_BUF}, {recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}];
_ ->
[]
end.
+
+default_ciphers(Version) ->
+ OpenSSLCiphers = openssl_ciphers(),
+ Ciphers =
+ case portable_cmd("openssl", ["version"]) of
+ "OpenSSL 0.9" ++ _ ->
+ ssl:cipher_suites(all,Version);
+ _ ->
+ ssl:cipher_suites(default, Version)
+ end,
+ [Cipher || Cipher <- Ciphers, lists:member(ssl:suite_to_openssl_str(Cipher), OpenSSLCiphers)].
+
+verify_early_data(Atom) ->
+ receive
+ {ssl, _Socket, {early_data, Atom}} ->
+ ok;
+ {ssl, _Socket, {early_data, Other}} ->
+ Other
+ end.
+
diff --git a/lib/ssl/test/ssl_upgrade_SUITE.erl b/lib/ssl/test/ssl_upgrade_SUITE.erl
index 0eb3cf519a..cc5db69788 100644
--- a/lib/ssl/test/ssl_upgrade_SUITE.erl
+++ b/lib/ssl/test/ssl_upgrade_SUITE.erl
@@ -19,11 +19,28 @@
%%
-module(ssl_upgrade_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
+-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
+%% Common test
+-export([all/0,
+ init_per_suite/1,
+ init_per_testcase/2,
+ end_per_suite/1,
+ end_per_testcase/2,
+ upgrade_init/2,
+ upgrade_upgraded/2,
+ upgrade_downgraded/2]).
+
+%% Test cases
+-export([minor_upgrade/1,
+ major_upgrade/1]).
+
+%% spawn/apply export
+-export([result_proxy_init/1, use_connection/1]).
+
+
-record(state, {
config,
server,
@@ -33,6 +50,10 @@
skip
}).
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
all() ->
[
minor_upgrade,
@@ -43,6 +64,7 @@ init_per_suite(Config0) ->
catch crypto:stop(),
try crypto:start() of
ok ->
+ ssl_test_lib:clean_start(),
case ct_release_test:init(Config0) of
{skip, Reason} ->
{skip, Reason};
@@ -65,34 +87,10 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
-major_upgrade(Config) when is_list(Config) ->
- ct_release_test:upgrade(ssl, major,{?MODULE, #state{config = Config}}, Config).
-
-minor_upgrade(Config) when is_list(Config) ->
- ct_release_test:upgrade(ssl, minor,{?MODULE, #state{config = Config}}, Config).
-
upgrade_init(CtData, State) ->
{ok,{FromVsn,ToVsn}} = ct_release_test:get_app_vsns(CtData, ssl),
upgrade_init(FromVsn, ToVsn, CtData, State).
-upgrade_init(_, "8.0.2", _, State) ->
- %% Requires stdlib upgrade so it will be a node upgrade!
- State#state{skip = true};
-upgrade_init(_, _, CtData, #state{config = Config} = State) ->
- {ok, {_, _, Up, _Down}} = ct_release_test:get_appup(CtData, ssl),
- ct:pal("Up: ~p", [Up]),
- Soft = is_soft(Up), %% It is symmetrical, if upgrade is soft so is downgrade
- Pid = spawn(?MODULE, result_proxy_init, [[]]),
- case Soft of
- true ->
- {Server, Client} = soft_start_connection(Config, Pid),
- State#state{server = Server, client = Client,
- soft = Soft,
- result_proxy = Pid};
- false ->
- State#state{soft = Soft, result_proxy = Pid}
- end.
-
upgrade_upgraded(_, #state{skip = true} = State) ->
State;
upgrade_upgraded(_, #state{soft = false, config = Config, result_proxy = Pid} = State) ->
@@ -138,6 +136,36 @@ upgrade_downgraded(_, #state{server = Server, client = Client, soft = true, resu
ok = Result,
State.
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+major_upgrade(Config) when is_list(Config) ->
+ ct_release_test:upgrade(ssl, major,{?MODULE, #state{config = Config}}, Config).
+
+minor_upgrade(Config) when is_list(Config) ->
+ ct_release_test:upgrade(ssl, minor,{?MODULE, #state{config = Config}}, Config).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+upgrade_init(_, "8.0.2", _, State) ->
+ %% Requires stdlib upgrade so it will be a node upgrade!
+ State#state{skip = true};
+upgrade_init(_, _, CtData, #state{config = Config} = State) ->
+ {ok, {_, _, Up, _Down}} = ct_release_test:get_appup(CtData, ssl),
+ ct:pal("Up: ~p", [Up]),
+ Soft = is_soft(Up), %% It is symmetrical, if upgrade is soft so is downgrade
+ Pid = spawn(?MODULE, result_proxy_init, [[]]),
+ case Soft of
+ true ->
+ {Server, Client} = soft_start_connection(Config, Pid),
+ State#state{server = Server, client = Client,
+ soft = Soft,
+ result_proxy = Pid};
+ false ->
+ State#state{soft = Soft, result_proxy = Pid}
+ end.
+
use_connection(Socket) ->
ssl_test_lib:send_recv_result_active(Socket),
receive
diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl
index fc7572cdfa..f5f57b534b 100644
--- a/lib/ssl/test/tls_1_3_record_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_record_SUITE.erl
@@ -20,9 +20,6 @@
-module(tls_1_3_record_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/tls_record.hrl").
-include_lib("ssl/src/tls_handshake.hrl").
@@ -30,6 +27,26 @@
-include_lib("ssl/src/ssl_cipher.hrl").
-include_lib("ssl/src/ssl_internal.hrl").
+%% Callback functions
+-export([all/0,
+ init_per_suite/1,
+ end_per_suite/1]).
+
+%% Testcases
+-export([encode_decode/0,
+ encode_decode/1,
+ finished_verify_data/0,
+ finished_verify_data/1,
+ '1_RTT_handshake'/0,
+ '1_RTT_handshake'/1,
+ '0_RTT_handshake'/0,
+ '0_RTT_handshake'/1
+ ]).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
all() ->
[encode_decode,
finished_verify_data,
@@ -73,20 +90,25 @@ encode_decode(_Config) ->
client_verify_data => undefined,compression_state => undefined,
mac_secret => undefined,secure_renegotiation => undefined,
security_parameters =>
- {security_parameters,
- <<19,2>>,
- 0,8,2,undefined,undefined,undefined,undefined,undefined,
- sha384,undefined,undefined,
- {handshake_secret,
- <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
- 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
- 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
- 157>>}, undefined, undefined,
- undefined,
- <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207,
- 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>,
- undefined},
- sequence_number => 0,server_verify_data => undefined},
+ #security_parameters{
+ cipher_suite = <<19,2>>,
+ connection_end = 0,
+ bulk_cipher_algorithm = 8,
+ cipher_type = 2,
+ prf_algorithm = sha384,
+ master_secret =
+ {handshake_secret,
+ <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
+ 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
+ 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
+ 157>>},
+ server_random =
+ <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207,
+ 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>},
+ sequence_number => 0,server_verify_data => undefined,
+ max_early_data_size => 0,
+ trial_decryption => false,
+ early_data_limit => false},
current_write =>
#{beast_mitigation => one_n_minus_one,
cipher_state =>
@@ -99,20 +121,22 @@ encode_decode(_Config) ->
client_verify_data => undefined,compression_state => undefined,
mac_secret => undefined,secure_renegotiation => undefined,
security_parameters =>
- {security_parameters,
- <<19,2>>,
- 0,8,2,undefined,undefined,undefined,undefined,undefined,
- sha384,undefined,undefined,
- {handshake_secret,
- <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
- 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
- 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
- 157>>}, undefined, undefined,
- undefined,
- <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207,
- 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>,
- undefined},
- sequence_number => 0,server_verify_data => undefined}},
+ #security_parameters{
+ cipher_suite = <<19,2>>,
+ connection_end = 0,
+ bulk_cipher_algorithm = 8,
+ cipher_type = 2,
+ prf_algorithm = sha384,
+ master_secret =
+ {handshake_secret,
+ <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
+ 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
+ 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
+ 157>>},
+ server_random =
+ <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207,
+ 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>},
+ sequence_number => 0,server_verify_data => undefined},max_fragment_length => undefined},
PlainText = [11,
<<0,2,175>>,
@@ -168,7 +192,8 @@ encode_decode(_Config) ->
#{current_write =>
#{security_parameters =>
#security_parameters{cipher_suite = ?TLS_NULL_WITH_NULL_NULL},
- sequence_number => 0
+ sequence_number => 0,
+ max_fragment_length => undefined
}
},
@@ -249,7 +274,7 @@ encode_decode(_Config) ->
%% e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a
HKDFAlgo = sha256,
Salt = binary:copy(<<?BYTE(0)>>, 32),
- IKM = binary:copy(<<?BYTE(0)>>, 32),
+ _IKM = binary:copy(<<?BYTE(0)>>, 32),
EarlySecret =
hexstr2bin("33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c
e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a"),
@@ -278,7 +303,7 @@ encode_decode(_Config) ->
hexstr2bin("b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56
52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e"),
- SPublicKey =
+ _SPublicKey =
hexstr2bin("c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6
72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f"),
@@ -526,7 +551,8 @@ encode_decode(_Config) ->
%% TODO: remove hardcoded IV size
WriteIVInfo = tls_v1:create_info(<<"iv">>, <<>>, 12),
- {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SHSTrafficSecret),
+ KeyLength = ssl_cipher:key_material(Cipher),
+ {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, SHSTrafficSecret),
%% {server} construct an EncryptedExtensions handshake message:
%%
@@ -806,7 +832,7 @@ encode_decode(_Config) ->
SWIV =
hexstr2bin("cf 78 2b 88 dd 83 54 9a ad f1 e9 84"),
- {SWKey, SWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SAPTrafficSecret),
+ {SWKey, SWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, SAPTrafficSecret),
%% {server} derive read traffic keys for handshake data:
%%
@@ -831,7 +857,7 @@ encode_decode(_Config) ->
SRIV =
hexstr2bin("5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f"),
- {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CHSTrafficSecret),
+ {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, CHSTrafficSecret),
%% {client} calculate finished "tls13 finished":
%%
@@ -908,7 +934,7 @@ encode_decode(_Config) ->
CWIV =
hexstr2bin("5b 78 92 3d ee 08 57 90 33 e5 23 d9"),
- {CWKey, CWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CAPTrafficSecret),
+ {CWKey, CWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, KeyLength, CAPTrafficSecret),
%% {client} derive secret "tls13 res master":
%%
diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl
index e0ac53e0f9..03fcb9afe5 100644
--- a/lib/ssl/test/tls_1_3_version_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_version_SUITE.erl
@@ -22,36 +22,64 @@
-module(tls_1_3_version_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ end_per_suite/1,
+ end_per_group/2
+ ]).
+
+%% Test cases
+-export([tls13_client_tls12_server/0,
+ tls13_client_tls12_server/1,
+ tls13_client_with_ext_tls12_server/0,
+ tls13_client_with_ext_tls12_server/1,
+ tls12_client_tls13_server/0,
+ tls12_client_tls13_server/1,
+ tls_client_tls10_server/0,
+ tls_client_tls10_server/1,
+ tls_client_tls11_server/0,
+ tls_client_tls11_server/1,
+ tls_client_tls12_server/0,
+ tls_client_tls12_server/1,
+ tls10_client_tls_server/0,
+ tls10_client_tls_server/1,
+ tls11_client_tls_server/0,
+ tls11_client_tls_server/1,
+ tls12_client_tls_server/0,
+ tls12_client_tls_server/1
+ ]).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
all() ->
[
- {group, 'tlsv1.3'}
+ cert_groups()
].
groups() ->
[
- {'tlsv1.3', [], cert_groups()},
- {rsa, [], tests()},
- {ecdsa, [], tests()}
+ {rsa, [], tls_1_3_1_2_tests() ++ legacy_tests()},
+ {ecdsa, [], tls_1_3_1_2_tests()}
].
cert_groups() ->
[{group, rsa},
{group, ecdsa}].
-tests() ->
+tls_1_3_1_2_tests() ->
[tls13_client_tls12_server,
tls13_client_with_ext_tls12_server,
tls12_client_tls13_server,
- tls_client_tls10_server,
+ tls_client_tls12_server,
+ tls12_client_tls_server].
+legacy_tests() ->
+ [tls_client_tls10_server,
tls_client_tls11_server,
tls_client_tls12_server,
tls10_client_tls_server,
@@ -62,9 +90,14 @@ init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
ok ->
- ssl_test_lib:clean_start(),
- [{client_type, erlang}, {server_type, erlang} |
- Config]
+ case ssl_test_lib:sufficient_crypto_support('tlsv1.3') of
+ true ->
+ ssl_test_lib:clean_start(),
+ [{client_type, erlang}, {server_type, erlang} |
+ Config];
+ false ->
+ {skip, "Insufficient crypto support for TLS-1.3"}
+ end
catch _:_ ->
{skip, "Crypto did not start"}
end.
@@ -77,7 +110,8 @@ init_per_group(rsa, Config0) ->
Config = ssl_test_lib:make_rsa_cert(Config0),
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
- [{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
+ [{client_type, erlang},
+ {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))];
init_per_group(ecdsa, Config0) ->
PKAlg = crypto:supports(public_keys),
@@ -87,35 +121,15 @@ init_per_group(ecdsa, Config0) ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
COpts = proplists:get_value(client_ecdsa_opts, Config),
SOpts = proplists:get_value(server_ecdsa_opts, Config),
- [{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
+ [{client_type, erlang},
+ {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} |
lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))];
false ->
{skip, "Missing EC crypto support"}
- end;
-init_per_group(GroupName, Config) ->
- ssl_test_lib:clean_tls_version(Config),
- case ssl_test_lib:is_tls_version(GroupName) andalso
- ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- _ ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl:start(),
- Config;
- false ->
- {skip, "Missing crypto support"}
- end
end.
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
-
+ ssl_test_lib:end_per_group(GroupName, Config).
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
@@ -160,21 +174,38 @@ tls12_client_tls13_server(Config) when is_list(Config) ->
tls_client_tls10_server() ->
[{doc,"Test that a TLS 1.0-1.3 client can connect to a TLS 1.0 server."}].
tls_client_tls10_server(Config) when is_list(Config) ->
+ CCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
ClientOpts = [{versions,
- ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
+ {ciphers, CCiphers}
+ |
ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,
- ['tlsv1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ['tlsv1']},
+ {ciphers, ssl:cipher_suites(all, 'tlsv1')}
+ | ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
tls_client_tls11_server() ->
[{doc,"Test that a TLS 1.0-1.3 client can connect to a TLS 1.1 server."}].
tls_client_tls11_server(Config) when is_list(Config) ->
+ CCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
ClientOpts = [{versions,
- ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
+ {ciphers, CCiphers} |
ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,
- ['tlsv1.1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ['tlsv1.1']},
+ {ciphers, ssl:cipher_suites(all, 'tlsv1.1')}
+ | ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
tls_client_tls12_server() ->
@@ -190,20 +221,36 @@ tls_client_tls12_server(Config) when is_list(Config) ->
tls10_client_tls_server() ->
[{doc,"Test that a TLS 1.0 client can connect to a TLS 1.0-1.3 server."}].
tls10_client_tls_server(Config) when is_list(Config) ->
+ SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
ClientOpts = [{versions,
- ['tlsv1']} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ['tlsv1']}, {ciphers, ssl:cipher_suites(all, 'tlsv1')} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,
- ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
+ {ciphers, SCiphers}
+ |
ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
tls11_client_tls_server() ->
[{doc,"Test that a TLS 1.1 client can connect to a TLS 1.0-1.3 server."}].
tls11_client_tls_server(Config) when is_list(Config) ->
+ SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
+ [{key_exchange, fun(srp_rsa) -> false;
+ (srp_anon) -> false;
+ (srp_dss) -> false;
+ (_) -> true end}]),
+
ClientOpts = [{versions,
- ['tlsv1.1']} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ['tlsv1.1']}, {ciphers, ssl:cipher_suites(all, 'tlsv1.1')} |
+ ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,
- ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']},
+ {ciphers, SCiphers}
+ |
ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl
index fc0e9cf3fc..4d57dddb8d 100644
--- a/lib/ssl/test/tls_api_SUITE.erl
+++ b/lib/ssl/test/tls_api_SUITE.erl
@@ -21,14 +21,80 @@
%%
-module(tls_api_SUITE).
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/ssl_record.hrl").
-include_lib("ssl/src/ssl_internal.hrl").
-include_lib("ssl/src/ssl_api.hrl").
-include_lib("ssl/src/tls_handshake.hrl").
+%% Common test
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ init_per_group/2,
+ end_per_suite/1,
+ end_per_group/2
+ ]).
+
+%% Test cases
+-export([tls_upgrade/0,
+ tls_upgrade/1,
+ tls_upgrade_new_opts/0,
+ tls_upgrade_new_opts/1,
+ tls_upgrade_with_timeout/0,
+ tls_upgrade_with_timeout/1,
+ tls_downgrade/0,
+ tls_downgrade/1,
+ tls_shutdown/0,
+ tls_shutdown/1,
+ tls_shutdown_write/0,
+ tls_shutdown_write/1,
+ tls_shutdown_both/0,
+ tls_shutdown_both/1,
+ tls_shutdown_error/0,
+ tls_shutdown_error/1,
+ tls_client_closes_socket/0,
+ tls_client_closes_socket/1,
+ tls_closed_in_active_once/0,
+ tls_closed_in_active_once/1,
+ tls_reset_in_active_once/0,
+ tls_reset_in_active_once/1,
+ tls_tcp_msg/0,
+ tls_tcp_msg/1,
+ tls_tcp_msg_big/0,
+ tls_tcp_msg_big/1,
+ tls_dont_crash_on_handshake_garbage/0,
+ tls_dont_crash_on_handshake_garbage/1,
+ tls_tcp_error_propagation_in_active_mode/0,
+ tls_tcp_error_propagation_in_active_mode/1,
+ peername/0,
+ peername/1,
+ sockname/0,
+ sockname/1,
+ tls_server_handshake_timeout/0,
+ tls_server_handshake_timeout/1,
+ transport_close/0,
+ transport_close/1,
+ emulated_options/0,
+ emulated_options/1,
+ accept_pool/0,
+ accept_pool/1,
+ reuseaddr/0,
+ reuseaddr/1
+ ]).
+
+%% Apply export
+-export([upgrade_result/1,
+ upgrade_result_new_opts/1,
+ tls_downgrade_result/2,
+ tls_shutdown_result/2,
+ tls_shutdown_write_result/2,
+ tls_shutdown_both_result/2,
+ tls_socket_options_result/5,
+ receive_msg/1
+ ]).
+
+-define(TIMEOUT, {seconds, 10}).
-define(SLEEP, 500).
%%--------------------------------------------------------------------
@@ -40,8 +106,7 @@ all() ->
{group, 'tlsv1.3'},
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
- {group, 'tlsv1'},
- {group, 'sslv3'}
+ {group, 'tlsv1'}
].
groups() ->
@@ -49,13 +114,13 @@ groups() ->
{'tlsv1.3', [], api_tests() -- [sockname]},
{'tlsv1.2', [], api_tests()},
{'tlsv1.1', [], api_tests()},
- {'tlsv1', [], api_tests()},
- {'sslv3', [], api_tests() ++ [ssl3_cipher_suite_limitation]}
+ {'tlsv1', [], api_tests()}
].
api_tests() ->
[
tls_upgrade,
+ tls_upgrade_new_opts,
tls_upgrade_with_timeout,
tls_downgrade,
tls_shutdown,
@@ -64,6 +129,7 @@ api_tests() ->
tls_shutdown_error,
tls_client_closes_socket,
tls_closed_in_active_once,
+ tls_reset_in_active_once,
tls_tcp_msg,
tls_tcp_msg_big,
tls_dont_crash_on_handshake_garbage,
@@ -92,33 +158,11 @@ end_per_suite(_Config) ->
application:unload(ssl),
application:stop(crypto).
-
init_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- case ssl_test_lib:sufficient_crypto_support(GroupName) of
- true ->
- ssl_test_lib:init_tls_version(GroupName, Config);
- false ->
- {skip, "Missing crypto support"}
- end;
- _ ->
- ssl:start(),
- Config
- end.
+ ssl_test_lib:init_per_group(GroupName, Config).
end_per_group(GroupName, Config) ->
- case ssl_test_lib:is_tls_version(GroupName) of
- true ->
- ssl_test_lib:clean_tls_version(Config);
- false ->
- Config
- end.
-
-init_per_testcase(_TestCase, Config) ->
- ssl_test_lib:ct_log_supported_protocol_versions(Config),
- ct:timetrap({seconds, 10}),
- Config.
+ ssl_test_lib:end_per_group(GroupName, Config).
end_per_testcase(_TestCase, Config) ->
Config.
@@ -159,6 +203,44 @@ tls_upgrade(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+tls_upgrade_new_opts() ->
+ [{doc,"Test that you can upgrade an tcp connection to an ssl connection and give new socket opts"}].
+
+tls_upgrade_new_opts(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ TcpOpts = [binary, {reuseaddr, true}],
+
+ Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE,
+ upgrade_result_new_opts, []}},
+ {tcp_options,
+ [{active, false} | TcpOpts]},
+ {ssl_options, [{verify, verify_peer},
+ {mode, list} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_upgrade_client([{node, ClientNode},
+ {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, upgrade_result_new_opts, []}},
+ {tcp_options, [binary]},
+ {ssl_options, [{verify, verify_peer},
+ {mode, list},
+ {server_name_indication, Hostname} | ClientOpts]}]),
+
+ ct:log("Testcase ~p, Client ~p Server ~p ~n",
+ [self(), Client, Server]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
%%--------------------------------------------------------------------
tls_upgrade_with_timeout() ->
[{doc,"Test ssl_accept/3"}].
@@ -330,10 +412,10 @@ tls_client_closes_socket(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server, {error,closed}).
%%--------------------------------------------------------------------
-tls_closed_in_active_once() ->
+tls_reset_in_active_once() ->
[{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}].
-tls_closed_in_active_once(Config) when is_list(Config) ->
+tls_reset_in_active_once(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -359,6 +441,40 @@ tls_closed_in_active_once(Config) when is_list(Config) ->
ok -> ok;
_ -> ct:fail(Result)
end.
+
+%%--------------------------------------------------------------------
+tls_closed_in_active_once() ->
+ [{doc, "Test that active once can be used to deliver not only all data"
+ " but even the close message, see ERL-1409, in normal operation."
+ " This is also test, with slighly diffrent circumstances in"
+ " the old tls_closed_in_active_once test"
+ " renamed tls_reset_in_active_once"}].
+
+tls_closed_in_active_once(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ TcpOpts = [binary, {reuseaddr, true}],
+ Port = ssl_test_lib:inet_port(node()),
+ Server = fun() ->
+ {ok, Listen} = gen_tcp:listen(Port, TcpOpts),
+ {ok, TcpServerSocket} = gen_tcp:accept(Listen),
+ {ok, ServerSocket} = ssl:handshake(TcpServerSocket, ServerOpts),
+ lists:foreach(
+ fun(_) ->
+ ssl:send(ServerSocket, "some random message\r\n")
+ end, lists:seq(1, 20)),
+ ssl:close(ServerSocket)
+ end,
+ spawn_link(Server),
+ {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]),
+ Result = tls_closed_in_active_once_loop(Socket),
+ ssl:close(Socket),
+ case Result of
+ ok -> ok;
+ _ -> ct:fail(Result)
+ end.
+
%%--------------------------------------------------------------------
tls_tcp_msg() ->
[{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}].
@@ -431,32 +547,35 @@ tls_dont_crash_on_handshake_garbage() ->
tls_dont_crash_on_handshake_garbage(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
-
+ Version = ssl_test_lib:protocol_version(Config),
{_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, ServerOpts}]),
- unlink(Server), monitor(process, Server),
+ {from, self()},
+ {mfa, ssl_test_lib, no_result},
+ {options, [{versions, [Version]} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
-
+
{ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]),
- % Send hello and garbage record
+ %% Send hello and garbage record
ok = gen_tcp:send(Socket,
[<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello
16#deadbeef:256, % 32 'random' bytes = 256 bits
0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values
-
<<22, 3,3, 5:16, 92,64,37,228,209>> % garbage
]),
- % Send unexpected change_cipher_spec
+ %% Send unexpected change_cipher_spec
ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>),
-
+ gen_tcp:close(Socket),
% Ensure we receive an alert, not sudden disconnect
- {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000).
-
+ case Version of
+ 'tlsv1.3' ->
+ ssl_test_lib:check_server_alert(Server, illegal_parameter);
+ _ ->
+ ssl_test_lib:check_server_alert(Server, handshake_failure)
+ end.
+
%%--------------------------------------------------------------------
tls_tcp_error_propagation_in_active_mode() ->
[{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}].
@@ -498,13 +617,13 @@ peername(Config) when is_list(Config) ->
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, peername_result, []}},
+ {mfa, {ssl, peername, []}},
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, peername_result, []}},
+ {mfa, {ssl, peername, []}},
{options, [{port, 0} | ClientOpts]}]),
ClientPort = ssl_test_lib:inet_port(Client),
@@ -530,13 +649,13 @@ sockname(Config) when is_list(Config) ->
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, sockname_result, []}},
+ {mfa, {ssl, sockname, []}},
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, sockname_result, []}},
+ {mfa, {ssl, sockname, []}},
{options, [{port, 0} | ClientOpts]}]),
ClientPort = ssl_test_lib:inet_port(Client),
@@ -605,39 +724,6 @@ transport_close(Config) when is_list(Config) ->
{error, _} = ssl:send(SslS, "Hello world").
%%--------------------------------------------------------------------
-ssl3_cipher_suite_limitation() ->
- [{doc,"Test a SSLv3 client cannot negotiate a TLSv* cipher suite."}].
-ssl3_cipher_suite_limitation(Config) when is_list(Config) ->
-
- {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
-
- Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
- {from, self()},
- {options, ServerOpts}]),
- Port = ssl_test_lib:inet_port(Server),
-
- {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]),
- ok = gen_tcp:send(Socket,
- <<22, 3,0, 49:16, % handshake, SSL 3.0, length
- 1, 45:24, % client_hello, length
- 3,0, % SSL 3.0
- 16#deadbeef:256, % 32 'random' bytes = 256 bits
- 0, % no session ID
- %% three cipher suites -- null, one with sha256 hash and one with sha hash
- 6:16, 0,255, 0,61, 0,57,
- 1, 0 % no compression
- >>),
- {ok, <<22, RecMajor:8, RecMinor:8, _RecLen:16, 2, HelloLen:24>>} = gen_tcp:recv(Socket, 9, 10000),
- {ok, <<HelloBin:HelloLen/binary>>} = gen_tcp:recv(Socket, HelloLen, 5000),
- ServerHello = tls_handshake:decode_handshake({RecMajor, RecMinor}, 2, HelloBin),
- case ServerHello of
- #server_hello{server_version = {3,0}, cipher_suite = <<0,57>>} ->
- ok;
- _ ->
- ct:fail({unexpected_server_hello, ServerHello})
- end.
-%%--------------------------------------------------------------------
emulated_options() ->
[{doc,"Test API function getopts/2 and setopts/2"}].
@@ -770,15 +856,17 @@ upgrade_result(Socket) ->
ok = ssl:send(Socket, "Hello world"),
%% Make sure binary is inherited from tcp socket and that we do
%% not get the list default!
- receive
- {ssl, _, <<"H">>} ->
- receive
- {ssl, _, <<"ello world">>} ->
- ok
- end;
- {ssl, _, <<"Hello world">>} ->
- ok
- end.
+ <<"Hello world">> = ssl_test_lib:active_recv(Socket, length("Hello world")),
+ ok.
+
+upgrade_result_new_opts(Socket) ->
+ ssl:setopts(Socket, [{active, true}]),
+ ok = ssl:send(Socket, "Hello world"),
+ %% Make sure list option set in ssl:connect/handskae overrides
+ %% previous gen_tcp socket option that was set to binary.
+ "Hello world" = ssl_test_lib:active_recv(Socket, length("Hello world")),
+ ok.
+
tls_downgrade_result(Socket, Pid) ->
ok = ssl_test_lib:send_recv_result(Socket),
Pid ! {self(), ready},
@@ -790,16 +878,8 @@ tls_downgrade_result(Socket, Pid) ->
{ok, TCPSocket} ->
inet:setopts(TCPSocket, [{active, true}]),
gen_tcp:send(TCPSocket, "Downgraded"),
- receive
- {tcp, TCPSocket, <<"Downgraded">>} ->
- ct:sleep(?SLEEP),
- ok;
- {tcp_closed, TCPSocket} ->
- ct:fail("Did not receive TCP data"),
- ok;
- Other ->
- {error, Other}
- end;
+ <<"Downgraded">> = active_tcp_recv(TCPSocket, length("Downgraded")),
+ ok;
{error, timeout} ->
ct:comment("Timed out, downgrade aborted"),
ok;
@@ -839,19 +919,9 @@ tls_closed_in_active_once_loop(Socket) ->
tls_closed_in_active_once_loop(Socket);
{ssl_closed, Socket} ->
ok
- after 5000 ->
- no_ssl_closed_received
end;
{error, closed} ->
- ok
- end.
-
-drop_handshakes(Socket, Timeout) ->
- {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout),
- {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout),
- case RecType of
- 22 -> drop_handshakes(Socket, Timeout);
- _ -> {ok, <<Header/binary, Frag/binary>>}
+ {error, ssl_setopt_failed}
end.
receive_msg(_) ->
@@ -860,12 +930,6 @@ receive_msg(_) ->
Msg
end.
-sockname_result(S) ->
- ssl:sockname(S).
-
-peername_result(S) ->
- ssl:peername(S).
-
tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) ->
%% Test get/set emulated opts
{ok, DefaultValues} = ssl:getopts(Socket, Options),
@@ -879,3 +943,14 @@ tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues)
ct:log("All opts ~p~n", [All]),
ok.
+active_tcp_recv(Socket, N) ->
+ active_tcp_recv(Socket, N, []).
+
+active_tcp_recv(_Socket, 0, Acc) ->
+ Acc;
+active_tcp_recv(Socket, N, Acc) ->
+ receive
+ {tcp, Socket, Bytes} ->
+ active_tcp_recv(Socket, N-size(Bytes), Acc ++ Bytes)
+ end.
+
diff --git a/lib/ssl/test/x509_test.erl b/lib/ssl/test/x509_test.erl
index dd774d4c7c..e99c35e249 100644
--- a/lib/ssl/test/x509_test.erl
+++ b/lib/ssl/test/x509_test.erl
@@ -64,6 +64,8 @@ do_gen_pem_config_files(Config, CertFile, KeyFile, CAFile) ->
cert_entry(Cert) ->
{'Certificate', Cert, not_encrypted}.
+key_entry({'PrivateKeyInfo', DERKeyInfo}) ->
+ {'PrivateKeyInfo', DERKeyInfo, not_encrypted};
key_entry({'RSAPrivateKey', DERKey}) ->
{'RSAPrivateKey', DERKey, not_encrypted};
key_entry({'DSAPrivateKey', DERKey}) ->
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index fc8d1134f6..ecb5111403 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 9.6.2
+SSL_VSN = 10.3
diff --git a/lib/stdlib/Makefile b/lib/stdlib/Makefile
index 3086d85445..cae3844126 100644
--- a/lib/stdlib/Makefile
+++ b/lib/stdlib/Makefile
@@ -35,3 +35,7 @@ SPECIAL_TARGETS =
# Default Subdir Targets
#
include $(ERL_TOP)/make/otp_subdir.mk
+
+DIA_PLT_APPS=compiler crypto
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/stdlib/doc/src/Makefile b/lib/stdlib/doc/src/Makefile
index 4541b4a463..4b22e35e3b 100644
--- a/lib/stdlib/doc/src/Makefile
+++ b/lib/stdlib/doc/src/Makefile
@@ -28,11 +28,6 @@ VSN=$(STDLIB_VSN)
APPLICATION=stdlib
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -89,6 +84,7 @@ XML_REF3_FILES = \
sets.xml \
shell.xml \
shell_default.xml \
+ shell_docs.xml \
slave.xml \
sofs.xml \
string.xml \
@@ -105,7 +101,7 @@ XML_REF6_FILES = stdlib_app.xml
XML_PART_FILES = part.xml
XML_CHAPTER_FILES = introduction.xml io_protocol.xml unicode_usage.xml \
- notes.xml assert_hrl.xml
+ uri_string_usage.xml notes.xml assert_hrl.xml
BOOK_FILES = book.xml
@@ -113,74 +109,12 @@ XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF6_FILES) $(XML_APPLICATION_FILES)
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
-
TOP_SPECS_FILE = specs.xml
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-SPECS_FLAGS = -I../../include -I../../../kernel/include
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-docs: man pdf html
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES) $(MAN6_FILES)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECDIR)/*
- rm -f errs core *~
-
$(SPECDIR)/specs_erl_id_trans.xml:
$(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) \
-o$(dir $@) -module erl_id_trans
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
- $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
+NO_CHUNKS = erl_id_trans.xml
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/stdlib/doc/src/array.xml b/lib/stdlib/doc/src/array.xml
index 3607c4f162..bb18489994 100644
--- a/lib/stdlib/doc/src/array.xml
+++ b/lib/stdlib/doc/src/array.xml
@@ -47,14 +47,14 @@
value is the atom <c>undefined</c>. There is no difference between an
unset entry and an entry that has been explicitly set to the same value
as the default one (compare
- <seealso marker="#reset-2"><c>reset/2</c></seealso>). If you need to
+ <seemfa marker="#reset/2"><c>reset/2</c></seemfa>). If you need to
differentiate between unset and set entries, ensure that the default value
cannot be confused with the values of set entries.</p>
<p>The array never shrinks automatically. If an index <c>I</c> has been used
to set an entry successfully, all indices in the range [0,<c>I</c>] stay
accessible unless the array size is explicitly changed by calling
- <seealso marker="#resize-2"><c>resize/2</c></seealso>.</p>
+ <seemfa marker="#resize/2"><c>resize/2</c></seemfa>.</p>
<p><em>Examples:</em></p>
@@ -139,20 +139,20 @@ A3 = array:fix(A2).</pre>
<func>
<name name="default" arity="1" since=""/>
<fsummary>Get the value used for uninitialized entries.</fsummary>
- <desc><marker id="default-1"/>
+ <desc>
<p>Gets the value used for uninitialized entries.</p>
- <p>See also <seealso marker="#new-2"><c>new/2</c></seealso>.</p>
+ <p>See also <seemfa marker="#new/2"><c>new/2</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="fix" arity="1" since=""/>
<fsummary>Fix the array size.</fsummary>
- <desc><marker id="fix-1"/>
+ <desc>
<p>Fixes the array size. This prevents it from growing automatically
upon insertion.</p>
- <p>See also <seealso marker="#set-3"><c>set/3</c></seealso> and
- <seealso marker="#relax-1"><c>relax/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#set/3"><c>set/3</c></seemfa> and
+ <seemfa marker="#relax/1"><c>relax/1</c></seemfa>.</p>
</desc>
</func>
@@ -160,14 +160,14 @@ A3 = array:fix(A2).</pre>
<name name="foldl" arity="3" since=""/>
<fsummary>Fold the array elements using the specified function and initial
accumulator value.</fsummary>
- <desc><marker id="foldl-3"/>
+ <desc>
<p>Folds the array elements using the specified function and initial
accumulator value. The elements are visited in order from the lowest
index to the highest. If <c><anno>Function</anno></c> is not a
function, the call fails with reason <c>badarg</c>.</p>
- <p>See also <seealso marker="#foldr-3"><c>foldr/3</c></seealso>,
- <seealso marker="#map-2"><c>map/2</c></seealso>,
- <seealso marker="#sparse_foldl-3"><c>sparse_foldl/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#foldr/3"><c>foldr/3</c></seemfa>,
+ <seemfa marker="#map/2"><c>map/2</c></seemfa>,
+ <seemfa marker="#sparse_foldl/3"><c>sparse_foldl/3</c></seemfa>.</p>
</desc>
</func>
@@ -175,35 +175,35 @@ A3 = array:fix(A2).</pre>
<name name="foldr" arity="3" since=""/>
<fsummary>Fold the array elements right-to-left using the specified
function and initial accumulator value.</fsummary>
- <desc><marker id="foldr-3"/>
+ <desc>
<p>Folds the array elements right-to-left using the specified function
and initial accumulator value. The elements are visited in order from
the highest index to the lowest. If <c><anno>Function</anno></c> is
not a function, the call fails with reason <c>badarg</c>.</p>
- <p>See also <seealso marker="#foldl-3"><c>foldl/3</c></seealso>,
- <seealso marker="#map-2"><c>map/2</c></seealso>.</p>
+ <p>See also <seemfa marker="#foldl/3"><c>foldl/3</c></seemfa>,
+ <seemfa marker="#map/2"><c>map/2</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="from_list" arity="1" since=""/>
<fsummary>Equivalent to <c>from_list(List, undefined)</c>.</fsummary>
- <desc><marker id="from_list-1"/>
+ <desc>
<p>Equivalent to
- <seealso marker="#from_list-2"><c>from_list(<anno>List</anno>, undefined)</c></seealso>.</p>
+ <seemfa marker="#from_list/2"><c>from_list(<anno>List</anno>, undefined)</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="from_list" arity="2" since=""/>
<fsummary>Convert a list to an extendible array.</fsummary>
- <desc><marker id="from_list-2"/>
+ <desc>
<p>Converts a list to an extendible array. <c><anno>Default</anno></c>
is used as the value for uninitialized entries of the array. If
<c><anno>List</anno></c> is not a proper list, the call fails with
reason <c>badarg</c>.</p>
- <p>See also <seealso marker="#new-2"><c>new/2</c></seealso>,
- <seealso marker="#to_list-1"><c>to_list/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#new/2"><c>new/2</c></seemfa>,
+ <seemfa marker="#to_list/1"><c>to_list/1</c></seemfa>.</p>
</desc>
</func>
@@ -211,9 +211,9 @@ A3 = array:fix(A2).</pre>
<name name="from_orddict" arity="1" since=""/>
<fsummary>Equivalent to <c>from_orddict(Orddict, undefined)</c>.
</fsummary>
- <desc><marker id="from_orddict-1"/>
+ <desc>
<p>Equivalent to
- <seealso marker="#from_orddict-2"><c>from_orddict(<anno>Orddict</anno>, undefined)</c></seealso>.</p>
+ <seemfa marker="#from_orddict/2"><c>from_orddict(<anno>Orddict</anno>, undefined)</c></seemfa>.</p>
</desc>
</func>
@@ -221,22 +221,22 @@ A3 = array:fix(A2).</pre>
<name name="from_orddict" arity="2" since=""/>
<fsummary>Convert an ordered list of pairs <c>{Index, Value}</c> to a
corresponding extendible array.</fsummary>
- <desc><marker id="from_orddict-2"/>
+ <desc>
<p>Converts an ordered list of pairs <c>{Index, <anno>Value</anno>}</c>
to a corresponding extendible array. <c><anno>Default</anno></c> is
used as the value for uninitialized entries of the array. If
<c><anno>Orddict</anno></c> is not a proper, ordered list of pairs
whose first elements are non-negative integers, the call fails with
reason <c>badarg</c>.</p>
- <p>See also <seealso marker="#new-2"><c>new/2</c></seealso>,
- <seealso marker="#to_orddict-1"><c>to_orddict/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#new/2"><c>new/2</c></seemfa>,
+ <seemfa marker="#to_orddict/1"><c>to_orddict/1</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="get" arity="2" since=""/>
<fsummary>Get the value of entry <c>I</c>.</fsummary>
- <desc><marker id="get-2"/>
+ <desc>
<p>Gets the value of entry <c><anno>I</anno></c>. If
<c><anno>I</anno></c> is not a non-negative integer, or if the array
has fixed size and <c><anno>I</anno></c> is larger than the maximum
@@ -244,7 +244,7 @@ A3 = array:fix(A2).</pre>
<p>If the array does not have fixed size, the default value for any
index <c><anno>I</anno></c> greater than
<c>size(<anno>Array</anno>)-1</c> is returned.</p>
- <p>See also <seealso marker="#set-3"><c>set/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#set/3"><c>set/3</c></seemfa>.</p>
</desc>
</func>
@@ -252,7 +252,7 @@ A3 = array:fix(A2).</pre>
<name name="is_array" arity="1" since=""/>
<fsummary>Returns <c>true</c> if <c>X</c> is an array, otherwise
<c>false</c>.</fsummary>
- <desc><marker id="is_array-1"/>
+ <desc>
<p>Returns <c>true</c> if <c><anno>X</anno></c> is an array, otherwise
<c>false</c>. Notice that the check is only shallow, as there is no
guarantee that <c><anno>X</anno></c> is a well-formed array
@@ -263,24 +263,24 @@ A3 = array:fix(A2).</pre>
<func>
<name name="is_fix" arity="1" since=""/>
<fsummary>Check if the array has fixed size.</fsummary>
- <desc><marker id="is_fix-1"/>
+ <desc>
<p>Checks if the array has fixed size. Returns <c>true</c> if the array
is fixed, otherwise <c>false</c>.</p>
- <p>See also <seealso marker="#fix-1"><c>fix/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#fix/1"><c>fix/1</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="map" arity="2" since=""/>
<fsummary>Map the specified function onto each array element.</fsummary>
- <desc><marker id="map-2"/>
+ <desc>
<p>Maps the specified function onto each array element. The elements are
visited in order from the lowest index to the highest. If
<c><anno>Function</anno></c> is not a function, the call fails with
reason <c>badarg</c>.</p>
- <p>See also <seealso marker="#foldl-3"><c>foldl/3</c></seealso>,
- <seealso marker="#foldr-3"><c>foldr/3</c></seealso>,
- <seealso marker="#sparse_map-2"><c>sparse_map/2</c></seealso>.</p>
+ <p>See also <seemfa marker="#foldl/3"><c>foldl/3</c></seemfa>,
+ <seemfa marker="#foldr/3"><c>foldr/3</c></seemfa>,
+ <seemfa marker="#sparse_map/2"><c>sparse_map/2</c></seemfa>.</p>
</desc>
</func>
@@ -288,10 +288,10 @@ A3 = array:fix(A2).</pre>
<name name="new" arity="0" since=""/>
<fsummary>Create a new, extendible array with initial size zero.
</fsummary>
- <desc><marker id="new-0"/>
+ <desc>
<p>Creates a new, extendible array with initial size zero.</p>
- <p>See also <seealso marker="#new-1"><c>new/1</c></seealso>,
- <seealso marker="#new-2"><c>new/2</c></seealso>.</p>
+ <p>See also <seemfa marker="#new/1"><c>new/1</c></seemfa>,
+ <seemfa marker="#new/2"><c>new/2</c></seemfa>.</p>
</desc>
</func>
@@ -299,7 +299,7 @@ A3 = array:fix(A2).</pre>
<name name="new" arity="1" since=""/>
<fsummary>Create a new array according to the specified options.
</fsummary>
- <desc><marker id="new-1"/>
+ <desc>
<p>Creates a new array according to the specified otions. By default,
the array is extendible and has initial size zero. Array indices
start at <c>0</c>.</p>
@@ -313,7 +313,7 @@ A3 = array:fix(A2).</pre>
call fails with reason <c>badarg</c>.</p></item>
<tag><c>fixed</c> or <c>{fixed, true}</c></tag>
<item><p>Creates a fixed-size array. See also
- <seealso marker="#fix-1"><c>fix/1</c></seealso>.</p></item>
+ <seemfa marker="#fix/1"><c>fix/1</c></seemfa>.</p></item>
<tag><c>{fixed, false}</c></tag>
<item><p>Creates an extendible (non-fixed-size) array.</p></item>
<tag><c>{default, Value}</c></tag>
@@ -336,12 +336,12 @@ array:new({default,0})</pre>
array:new([{size,10},{fixed,false},{default,-1}])</pre>
<p>creates an extendible array with initial size 10 whose default value
is <c>-1</c>.</p>
- <p>See also <seealso marker="#fix-1"><c>fix/1</c></seealso>,
- <seealso marker="#from_list-2"><c>from_list/2</c></seealso>,
- <seealso marker="#get-2"><c>get/2</c></seealso>,
- <seealso marker="#new-0"><c>new/0</c></seealso>,
- <seealso marker="#new-2"><c>new/2</c></seealso>,
- <seealso marker="#set-3"><c>set/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#fix/1"><c>fix/1</c></seemfa>,
+ <seemfa marker="#from_list/2"><c>from_list/2</c></seemfa>,
+ <seemfa marker="#get/2"><c>get/2</c></seemfa>,
+ <seemfa marker="#new/0"><c>new/0</c></seemfa>,
+ <seemfa marker="#new/2"><c>new/2</c></seemfa>,
+ <seemfa marker="#set/3"><c>set/3</c></seemfa>.</p>
</desc>
</func>
@@ -349,7 +349,7 @@ array:new([{size,10},{fixed,false},{default,-1}])</pre>
<name name="new" arity="2" since=""/>
<fsummary>Create a new array according to the specified size and options.
</fsummary>
- <desc><marker id="new-2"/>
+ <desc>
<p>Creates a new array according to the specified size and options. If
<c><anno>Size</anno></c> is not a non-negative integer, the call fails
with reason <c>badarg</c>. By default, the array has fixed size.
@@ -365,17 +365,17 @@ array:new([{size,10},{fixed,false},{default,-1}])</pre>
array:new(100, {default,0})</pre>
<p>creates a fixed-size array of size 100, whose default value is
<c>0</c>.</p>
- <p>See also <seealso marker="#new-1"><c>new/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#new/1"><c>new/1</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="relax" arity="1" since=""/>
<fsummary>Make the array resizable.</fsummary>
- <desc><marker id="relax-1"/>
+ <desc>
<p>Makes the array resizable. (Reverses the effects of
- <seealso marker="#fix-1"><c>fix/1</c></seealso>.)</p>
- <p>See also <seealso marker="#fix-1"><c>fix/1</c></seealso>.</p>
+ <seemfa marker="#fix/1"><c>fix/1</c></seemfa>.)</p>
+ <p>See also <seemfa marker="#fix/1"><c>fix/1</c></seemfa>.</p>
</desc>
</func>
@@ -383,18 +383,18 @@ array:new(100, {default,0})</pre>
<name name="reset" arity="2" since=""/>
<fsummary>Reset entry <c>I</c> to the default value for the array.
</fsummary>
- <desc><marker id="reset-2"/>
+ <desc>
<p>Resets entry <c><anno>I</anno></c> to the default value for the
array. If the value of entry <c><anno>I</anno></c> is the default
value, the array is returned unchanged. Reset never changes the array
size. Shrinking can be done explicitly by calling
- <seealso marker="#resize-2"><c>resize/2</c></seealso>.</p>
+ <seemfa marker="#resize/2"><c>resize/2</c></seemfa>.</p>
<p>If <c><anno>I</anno></c> is not a non-negative integer, or if the
array has fixed size and <c><anno>I</anno></c> is larger than the
maximum index, the call fails with reason <c>badarg</c>; compare
- <seealso marker="#set-3"><c>set/3</c></seealso></p>
- <p>See also <seealso marker="#new-2"><c>new/2</c></seealso>,
- <seealso marker="#set-3"><c>set/3</c></seealso>.</p>
+ <seemfa marker="#set/3"><c>set/3</c></seemfa></p>
+ <p>See also <seemfa marker="#new/2"><c>new/2</c></seemfa>,
+ <seemfa marker="#set/3"><c>set/3</c></seemfa>.</p>
</desc>
</func>
@@ -402,20 +402,20 @@ array:new(100, {default,0})</pre>
<name name="resize" arity="1" since=""/>
<fsummary>Change the array size to that reported by <c>sparse_size/1</c>.
</fsummary>
- <desc><marker id="resize-1"/>
+ <desc>
<p>Changes the array size to that reported by
- <seealso marker="#sparse_size-1"><c>sparse_size/1</c></seealso>. If
+ <seemfa marker="#sparse_size/1"><c>sparse_size/1</c></seemfa>. If
the specified array has fixed size, also the resulting array has fixed
size.</p>
- <p>See also <seealso marker="#resize-2"><c>resize/2</c></seealso>,
- <seealso marker="#sparse_size-1"><c>sparse_size/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#resize/2"><c>resize/2</c></seemfa>,
+ <seemfa marker="#sparse_size/1"><c>sparse_size/1</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="resize" arity="2" since=""/>
<fsummary>Change the array size.</fsummary>
- <desc><marker id="resize-2"/>
+ <desc>
<p>Change the array size. If <c><anno>Size</anno></c> is not a
non-negative integer, the call fails with reason <c>badarg</c>. If
the specified array has fixed size, also the resulting array has fixed
@@ -426,7 +426,7 @@ array:new(100, {default,0})</pre>
<func>
<name name="set" arity="3" since=""/>
<fsummary>Set entry <c>I</c> of the array to <c>Value</c>.</fsummary>
- <desc><marker id="set-3"/>
+ <desc>
<p>Sets entry <c><anno>I</anno></c> of the array to
<c><anno>Value</anno></c>. If <c><anno>I</anno></c> is not a
non-negative integer, or if the array has fixed size and
@@ -435,21 +435,21 @@ array:new(100, {default,0})</pre>
<p>If the array does not have fixed size, and <c><anno>I</anno></c> is
greater than <c>size(<anno>Array</anno>)-1</c>, the array grows to
size <c><anno>I</anno>+1</c>.</p>
- <p>See also <seealso marker="#get-2"><c>get/2</c></seealso>,
- <seealso marker="#reset-2"><c>reset/2</c></seealso>.</p>
+ <p>See also <seemfa marker="#get/2"><c>get/2</c></seemfa>,
+ <seemfa marker="#reset/2"><c>reset/2</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="size" arity="1" since=""/>
<fsummary>Get the number of entries in the array.</fsummary>
- <desc><marker id="size-1"/>
+ <desc>
<p>Gets the number of entries in the array. Entries are numbered from
<c>0</c> to <c>size(<anno>Array</anno>)-1</c>. Hence, this is also the
index of the first entry that is guaranteed to not have been
previously set.</p>
- <p>See also <seealso marker="#set-3"><c>set/3</c></seealso>,
- <seealso marker="#sparse_size-1"><c>sparse_size/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#set/3"><c>set/3</c></seemfa>,
+ <seemfa marker="#sparse_size/1"><c>sparse_size/1</c></seemfa>.</p>
</desc>
</func>
@@ -457,14 +457,14 @@ array:new(100, {default,0})</pre>
<name name="sparse_foldl" arity="3" since=""/>
<fsummary>Fold the array elements using the specified function and initial
accumulator value, skipping default-valued entries.</fsummary>
- <desc><marker id="sparse_foldl-3"/>
+ <desc>
<p>Folds the array elements using the specified function and initial
accumulator value, skipping default-valued entries. The elements are
visited in order from the lowest index to the highest. If
<c><anno>Function</anno></c> is not a function, the call fails with
reason <c>badarg</c>.</p>
- <p>See also <seealso marker="#foldl-3"><c>foldl/3</c></seealso>,
- <seealso marker="#sparse_foldr-3"><c>sparse_foldr/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#foldl/3"><c>foldl/3</c></seemfa>,
+ <seemfa marker="#sparse_foldr/3"><c>sparse_foldr/3</c></seemfa>.</p>
</desc>
</func>
@@ -473,14 +473,14 @@ array:new(100, {default,0})</pre>
<fsummary>Fold the array elements right-to-left using the specified
function and initial accumulator value, skipping default-valued
entries.</fsummary>
- <desc><marker id="sparse_foldr-3"/>
+ <desc>
<p>Folds the array elements right-to-left using the specified
function and initial accumulator value, skipping default-valued
entries. The elements are visited in order from the highest index to
the lowest. If <c><anno>Function</anno></c> is not a function, the
call fails with reason <c>badarg</c>.</p>
- <p>See also <seealso marker="#foldr-3"><c>foldr/3</c></seealso>,
- <seealso marker="#sparse_foldl-3"><c>sparse_foldl/3</c></seealso>.</p>
+ <p>See also <seemfa marker="#foldr/3"><c>foldr/3</c></seemfa>,
+ <seemfa marker="#sparse_foldl/3"><c>sparse_foldl/3</c></seemfa>.</p>
</desc>
</func>
@@ -488,12 +488,12 @@ array:new(100, {default,0})</pre>
<name name="sparse_map" arity="2" since=""/>
<fsummary>Map the specified function onto each array element, skipping
default-valued entries.</fsummary>
- <desc><marker id="sparse_map-2"/>
+ <desc>
<p>Maps the specified function onto each array element, skipping
default-valued entries. The elements are visited in order from the
lowest index to the highest. If <c><anno>Function</anno></c> is not a
function, the call fails with reason <c>badarg</c>.</p>
- <p>See also <seealso marker="#map-2"><c>map/2</c></seealso>.</p>
+ <p>See also <seemfa marker="#map/2"><c>map/2</c></seemfa>.</p>
</desc>
</func>
@@ -501,13 +501,13 @@ array:new(100, {default,0})</pre>
<name name="sparse_size" arity="1" since=""/>
<fsummary>Get the number of entries in the array up until the last
non-default-valued entry.</fsummary>
- <desc><marker id="sparse_size-1"/>
+ <desc>
<p>Gets the number of entries in the array up until the last
non-default-valued entry. That is, returns <c>I+1</c> if <c>I</c> is
the last non-default-valued entry in the array, or zero if no such
entry exists.</p>
- <p>See also <seealso marker="#resize-1"><c>resize/1</c></seealso>,
- <seealso marker="#size-1"><c>size/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#resize/1"><c>resize/1</c></seemfa>,
+ <seemfa marker="#size/1"><c>size/1</c></seemfa>.</p>
</desc>
</func>
@@ -515,9 +515,9 @@ array:new(100, {default,0})</pre>
<name name="sparse_to_list" arity="1" since=""/>
<fsummary>Convert the array to a list, skipping default-valued entries.
</fsummary>
- <desc><marker id="sparse_to_list-1"/>
+ <desc>
<p>Converts the array to a list, skipping default-valued entries.</p>
- <p>See also <seealso marker="#to_list-1"><c>to_list/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#to_list/1"><c>to_list/1</c></seemfa>.</p>
</desc>
</func>
@@ -525,21 +525,21 @@ array:new(100, {default,0})</pre>
<name name="sparse_to_orddict" arity="1" since=""/>
<fsummary>Convert the array to an ordered list of pairs <c>{Index,
Value}</c>, skipping default-valued entries.</fsummary>
- <desc><marker id="sparse_to_orddict-1"/>
+ <desc>
<p>Converts the array to an ordered list of pairs <c>{Index,
<anno>Value</anno>}</c>, skipping default-valued entries.</p>
<p>See also
- <seealso marker="#to_orddict-1"><c>to_orddict/1</c></seealso>.</p>
+ <seemfa marker="#to_orddict/1"><c>to_orddict/1</c></seemfa>.</p>
</desc>
</func>
<func>
<name name="to_list" arity="1" since=""/>
<fsummary>Convert the array to a list.</fsummary>
- <desc><marker id="to_list-1"/>
+ <desc>
<p>Converts the array to a list.</p>
- <p>See also <seealso marker="#from_list-2"><c>from_list/2</c></seealso>,
- <seealso marker="#sparse_to_list-1"><c>sparse_to_list/1</c></seealso>.
+ <p>See also <seemfa marker="#from_list/2"><c>from_list/2</c></seemfa>,
+ <seemfa marker="#sparse_to_list/1"><c>sparse_to_list/1</c></seemfa>.
</p>
</desc>
</func>
@@ -548,12 +548,12 @@ array:new(100, {default,0})</pre>
<name name="to_orddict" arity="1" since=""/>
<fsummary>Convert the array to an ordered list of pairs <c>{Index,
Value}</c>.</fsummary>
- <desc><marker id="to_orddict-1"/>
+ <desc>
<p>Converts the array to an ordered list of pairs <c>{Index,
<anno>Value</anno>}</c>.</p>
<p>See also
- <seealso marker="#from_orddict-2"><c>from_orddict/2</c></seealso>,
- <seealso marker="#sparse_to_orddict-1"><c>sparse_to_orddict/1</c></seealso>.
+ <seemfa marker="#from_orddict/2"><c>from_orddict/2</c></seemfa>,
+ <seemfa marker="#sparse_to_orddict/1"><c>sparse_to_orddict/1</c></seemfa>.
</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/assert_hrl.xml b/lib/stdlib/doc/src/assert_hrl.xml
index fb27954235..c08066d7fb 100644
--- a/lib/stdlib/doc/src/assert_hrl.xml
+++ b/lib/stdlib/doc/src/assert_hrl.xml
@@ -185,7 +185,7 @@ erlc -DNOASSERT=true *.erl</code>
<section>
<title>See Also</title>
- <p><seealso marker="compiler:compile"><c>compile(3)</c></seealso>,
- <seealso marker="erts:erlc"><c>erlc(3)</c></seealso></p>
+ <p><seeerl marker="compiler:compile"><c>compile(3)</c></seeerl>,
+ <seecom marker="erts:erlc"><c>erlc(3)</c></seecom></p>
</section>
</fileref>
diff --git a/lib/stdlib/doc/src/beam_lib.xml b/lib/stdlib/doc/src/beam_lib.xml
index 91e5a6d14a..a33c5b076c 100644
--- a/lib/stdlib/doc/src/beam_lib.xml
+++ b/lib/stdlib/doc/src/beam_lib.xml
@@ -59,9 +59,9 @@
<marker id="debug_info"></marker>
<title>Debug Information/Abstract Code</title>
<p>Option <c>debug_info</c> can be specified to the Compiler (see
- <seealso marker="compiler:compile#debug_info"><c>compile(3)</c></seealso>)
- to have debug information, such as <seealso marker="erts:absform">Erlang
- Abstract Format</seealso>, stored in the <c>debug_info</c> chunk.
+ <seeerl marker="compiler:compile#debug_info"><c>compile(3)</c></seeerl>)
+ to have debug information, such as <seeguide marker="erts:absform">Erlang
+ Abstract Format</seeguide>, stored in the <c>debug_info</c> chunk.
Tools such as Debugger and Xref require the debug information to
be included.</p>
@@ -71,9 +71,9 @@
</warning>
<p>The debug information can also be removed from BEAM files
- using <seealso marker="#strip/1"><c>strip/1</c></seealso>,
- <seealso marker="#strip_files/1"><c>strip_files/1</c></seealso>, and/or
- <seealso marker="#strip_release/1"><c>strip_release/1</c></seealso>.</p>
+ using <seemfa marker="#strip/1"><c>strip/1</c></seemfa>,
+ <seemfa marker="#strip_files/1"><c>strip_files/1</c></seemfa>, and/or
+ <seemfa marker="#strip_release/1"><c>strip_release/1</c></seemfa>.</p>
</section>
<section>
@@ -101,7 +101,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
<p>The default type (and currently the only type) of crypto
algorithm is <c>des3_cbc</c>, three rounds of DES. The key string
is scrambled using
- <seealso marker="erts:erlang#md5/1"><c>erlang:md5/1</c></seealso>
+ <seemfa marker="erts:erlang#md5/1"><c>erlang:md5/1</c></seemfa>
to generate the keys used for <c>des3_cbc</c>.</p>
<note>
@@ -117,9 +117,9 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
<list type="ordered">
<item>
<p>Use Compiler option <c>{debug_info_key,Key}</c>, see
- <seealso marker="compiler:compile#debug_info_key"><c>compile(3)</c></seealso>
+ <seeerl marker="compiler:compile#debug_info_key"><c>compile(3)</c></seeerl>
and function
- <seealso marker="#crypto_key_fun/1"><c>crypto_key_fun/1</c></seealso>
+ <seemfa marker="#crypto_key_fun/1"><c>crypto_key_fun/1</c></seemfa>
to register a fun that returns the key whenever
<c>beam_lib</c> must decrypt the debug information.</p>
<p>If no such fun is registered, <c>beam_lib</c> instead
@@ -129,7 +129,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
<p>Store the key in a text file named <c>.erlang.crypt</c>.</p>
<p>In this case, Compiler option <c>encrypt_debug_info</c>
can be used, see
- <seealso marker="compiler:compile#encrypt_debug_info"><c>compile(3)</c></seealso>.
+ <seeerl marker="compiler:compile#encrypt_debug_info"><c>compile(3)</c></seeerl>.
</p>
</item>
</list>
@@ -317,7 +317,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
<desc>
<p>Unregisters the crypto key fun and terminates the process
holding it, started by
- <seealso marker="#crypto_key_fun/1"><c>crypto_key_fun/1</c></seealso>.
+ <seemfa marker="#crypto_key_fun/1"><c>crypto_key_fun/1</c></seemfa>.
</p>
<p>Returns either <c>{ok, undefined}</c> if no crypto key fun is
registered, or <c>{ok, Term}</c>, where <c>Term</c> is
@@ -402,7 +402,7 @@ CryptoKeyFun(clear) -> term()</code>
<fsummary>Compare the BEAM files in two directories.</fsummary>
<desc>
<p>Compares the BEAM files in two directories as
- <seealso marker="#cmp_dirs/2"><c>cmp_dirs/2</c></seealso>, but the
+ <seemfa marker="#cmp_dirs/2"><c>cmp_dirs/2</c></seemfa>, but the
names of files that exist in only one directory or are different are
presented on standard output.</p>
</desc>
@@ -416,7 +416,7 @@ CryptoKeyFun(clear) -> term()</code>
<p>For a specified error returned by any function in this module,
this function returns a descriptive string
of the error in English. For file errors, function
- <seealso marker="kernel:file#format_error/1"><c>file:format_error(Posix)</c></seealso>
+ <seemfa marker="kernel:file#format_error/1"><c>file:format_error(Posix)</c></seemfa>
is to be called.</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/binary.xml b/lib/stdlib/doc/src/binary.xml
index 2dd6c6a436..412a0f5def 100644
--- a/lib/stdlib/doc/src/binary.xml
+++ b/lib/stdlib/doc/src/binary.xml
@@ -64,7 +64,7 @@
</datatype>
<datatype>
<name name="part"/>
- <desc><p>A representaion of a part (or range) in a binary. <c>Start</c> is
+ <desc><p>A representation of a part (or range) in a binary. <c>Start</c> is
a zero-based offset into a <c>binary()</c> and <c>Length</c> is the
length of that part. As input to functions in this module, a reverse
part specification is allowed, constructed with a negative
@@ -131,10 +131,10 @@
<desc>
<p>Builds an internal structure representing a compilation of a
search pattern, later to be used in functions
- <seealso marker="#match-3"><c>match/3</c></seealso>,
- <seealso marker="#matches-3"><c>matches/3</c></seealso>,
- <seealso marker="#split-3"><c>split/3</c></seealso>, or
- <seealso marker="#replace-4"><c>replace/4</c></seealso>.
+ <seemfa marker="#match/3"><c>match/3</c></seemfa>,
+ <seemfa marker="#matches/3"><c>matches/3</c></seemfa>,
+ <seemfa marker="#split/3"><c>split/3</c></seemfa>, or
+ <seemfa marker="#replace/4"><c>replace/4</c></seemfa>.
The <c>cp()</c> returned is guaranteed to be a
<c>tuple()</c> to allow programs to distinguish it from
non-precompiled search patterns.</p>
@@ -173,7 +173,7 @@
duplicated <c><anno>N</anno></c> times.</p>
<p>This function always creates a new binary, even if <c><anno>N</anno> =
- 1</c>. By using <seealso marker="#copy/1"><c>copy/1</c></seealso>
+ 1</c>. By using <seemfa marker="#copy/1"><c>copy/1</c></seemfa>
on a binary referencing a larger binary, one
can free up the larger binary for garbage collection.</p>
@@ -267,7 +267,7 @@
<fsummary>Convert a list of integers and binaries to a binary.</fsummary>
<desc>
<p>Works exactly as
- <seealso marker="erts:erlang#list_to_binary/1"><c>erlang:list_to_binary/1</c></seealso>,
+ <seemfa marker="erts:erlang#list_to_binary/1"><c>erlang:list_to_binary/1</c></seemfa>,
added for completeness.</p>
</desc>
</func>
@@ -361,7 +361,7 @@
atom <c>nomatch</c> is returned.</p>
<p>For a description of <c><anno>Pattern</anno></c>, see function
- <seealso marker="#compile_pattern-1"><c>compile_pattern/1</c></seealso>.
+ <seemfa marker="#compile_pattern/1"><c>compile_pattern/1</c></seemfa>.
</p>
<p>If <c>{scope, {Start,Length}}</c> is specified in the options such
@@ -385,7 +385,7 @@
<fsummary>Search for all matches of a pattern in a binary.</fsummary>
<type name="part"/>
<desc>
- <p>As <seealso marker="#match-2"><c>match/2</c></seealso>,
+ <p>As <seemfa marker="#match/2"><c>match/2</c></seemfa>,
but <c><anno>Subject</anno></c> is searched until
exhausted and a list of all non-overlapping parts matching
<c><anno>Pattern</anno></c> is returned (in order).</p>
@@ -411,9 +411,9 @@
returned.</p>
<p>For a description of <c><anno>Pattern</anno></c>, see
- <seealso marker="#compile_pattern-1"><c>compile_pattern/1</c></seealso>.
+ <seemfa marker="#compile_pattern/1"><c>compile_pattern/1</c></seemfa>.
For a description of available options, see
- <seealso marker="#match-3"><c>match/3</c></seealso>.</p>
+ <seemfa marker="#match/3"><c>match/3</c></seemfa>.</p>
<p>If <c>{scope, {<anno>Start</anno>,<anno>Length</anno>}}</c> is
specified in the options such that <c><anno>Start</anno></c> &gt; size
@@ -440,9 +440,9 @@
&lt;&lt;6,7,8,9,10&gt;&gt;</code>
<note>
- <p><seealso marker="#part-2">part/2</seealso> and
- <seealso marker="#part-3">part/3</seealso> are also available in the
- <seealso marker="erts:erlang"><c>erlang</c></seealso>
+ <p><seemfa marker="#part/2">part/2</seemfa> and
+ <seemfa marker="#part/3">part/3</seemfa> are also available in the
+ <seeerl marker="erts:erlang"><c>erlang</c></seeerl>
module under the names <c>binary_part/2</c> and
<c>binary_part/3</c>. Those BIFs are allowed in guard tests.</p>
</note>
@@ -469,7 +469,7 @@
<p>If a binary references a larger binary (often described as
being a subbinary), it can be useful to get the size of the
referenced binary. This function can be used in a program to trigger the
- use of <seealso marker="#copy/1"><c>copy/1</c></seealso>. By copying a
+ use of <seemfa marker="#copy/1"><c>copy/1</c></seemfa>. By copying a
binary, one can dereference the original, possibly large, binary that a
smaller binary is a reference to.</p>
@@ -566,11 +566,11 @@ store(Binary, GBSet) ->
of the replacement binary, a <c>badarg</c> exception is raised.</p>
<p>Options <c>global</c> and <c>{scope, part()}</c> work as for
- <seealso marker="#split-3"><c>split/3</c></seealso>.
+ <seemfa marker="#split/3"><c>split/3</c></seemfa>.
The return type is always a <c>binary()</c>.</p>
<p>For a description of <c><anno>Pattern</anno></c>, see
- <seealso marker="#compile_pattern-1"><c>compile_pattern/1</c></seealso>.
+ <seemfa marker="#compile_pattern/1"><c>compile_pattern/1</c></seemfa>.
</p>
</desc>
</func>
@@ -608,23 +608,23 @@ store(Binary, GBSet) ->
<taglist>
<tag>{scope, part()}</tag>
- <item><p>Works as in <seealso marker="#match-3"><c>match/3</c></seealso>
- and <seealso marker="#matches-3"><c>matches/3</c></seealso>. Notice that
+ <item><p>Works as in <seemfa marker="#match/3"><c>match/3</c></seemfa>
+ and <seemfa marker="#matches/3"><c>matches/3</c></seemfa>. Notice that
this only defines the scope of the search for matching strings,
it does not cut the binary before splitting. The bytes before and after
the scope are kept in the result. See the example below.</p></item>
<tag>trim</tag>
<item><p>Removes trailing empty parts of the result (as does <c>trim</c>
- in <seealso marker="re#split/3"><c>re:split/3</c></seealso>.</p></item>
+ in <seemfa marker="re#split/3"><c>re:split/3</c></seemfa>.</p></item>
<tag>trim_all</tag>
<item><p>Removes all empty parts of the result.</p></item>
<tag>global</tag>
<item><p>Repeats the split until <c><anno>Subject</anno></c> is
exhausted. Conceptually option <c>global</c> makes split work
on the positions returned by
- <seealso marker="#matches-3"><c>matches/3</c></seealso>, while it
+ <seemfa marker="#matches/3"><c>matches/3</c></seemfa>, while it
normally works on the position returned by
- <seealso marker="#match-3"><c>match/3</c></seealso>.</p></item>
+ <seemfa marker="#match/3"><c>match/3</c></seemfa>.</p></item>
</taglist>
<p>Example of the difference between a scope and taking the
@@ -643,7 +643,7 @@ store(Binary, GBSet) ->
of the split are no longer referenced.</p>
<p>For a description of <c><anno>Pattern</anno></c>, see
- <seealso marker="#compile_pattern-1"><c>compile_pattern/1</c></seealso>.
+ <seemfa marker="#compile_pattern/1"><c>compile_pattern/1</c></seemfa>.
</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/c.xml b/lib/stdlib/doc/src/c.xml
index 29edc373c7..b481596379 100644
--- a/lib/stdlib/doc/src/c.xml
+++ b/lib/stdlib/doc/src/c.xml
@@ -63,8 +63,8 @@
source file, then the code path is searched to locate the object
file for the module and extract its original compiler options and
source path. If the source file is not found in the original
- location, <seealso
- marker="filelib#find_source/1"><c>filelib:find_source/1</c></seealso>
+ location, <seemfa
+ marker="filelib#find_source/1"><c>filelib:find_source/1</c></seemfa>
is used to search for it relative to the directory of the object
file.</p>
<p>The source file is compiled with the the original
@@ -120,6 +120,64 @@
</func>
<func>
+ <name name="h" arity="1" since="OTP 23.0"/>
+ <fsummary>Module help information</fsummary>
+ <type name="h_return"/>
+ <desc>
+ <p>Print the documentation for <c>Module</c></p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="h" arity="2" since="OTP 23.0"/>
+ <fsummary>Function help information</fsummary>
+ <type name="h_return"/>
+ <type name="hf_return"/>
+ <desc>
+ <p>Print the documentation for all <c>Module:Function</c>s (regardless of arity).</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="h" arity="3" since="OTP 23.0"/>
+ <fsummary>Function help information</fsummary>
+ <type name="h_return"/>
+ <type name="hf_return"/>
+ <desc>
+ <p>Print the documentation for <c>Module:Function/Arity</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="ht" arity="1" since="OTP 23.0"/>
+ <fsummary>Type help information</fsummary>
+ <type name="h_return"/>
+ <desc>
+ <p>Print the type documentation for <c>Module</c></p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="ht" arity="2" since="OTP 23.0"/>
+ <fsummary>Type help information</fsummary>
+ <type name="h_return"/>
+ <type name="ht_return"/>
+ <desc>
+ <p>Print the type documentation for <c>Type</c> in <c>Module</c> regardless of arity.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="ht" arity="3" since="OTP 23.0"/>
+ <fsummary>Type help information</fsummary>
+ <type name="h_return"/>
+ <type name="ht_return"/>
+ <desc>
+ <p>Print the type documentation for <c>Type/Arity</c> in <c>Module</c>.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="i" arity="0" since=""/>
<name name="ni" arity="0" since=""/>
<fsummary>System information.</fsummary>
@@ -165,7 +223,7 @@
<c>compile:file(File, [report_errors, report_warnings])</c> for each
<c>File</c> in <c>Files</c>.</p>
<p>For information about <c>File</c>, see
- <seealso marker="file#type-filename"><c>file:filename()</c></seealso>.
+ <seetype marker="file#filename"><c>file:filename()</c></seetype>.
</p>
</desc>
</func>
@@ -218,7 +276,7 @@
<fsummary>Lists all modified modules.</fsummary>
<desc>
<p>Lists all modified modules. Shorthand for
- <seealso marker="kernel:code#modified_modules/0"><c>code:modified_modules/0</c></seealso>.</p>
+ <seemfa marker="kernel:code#modified_modules/0"><c>code:modified_modules/0</c></seemfa>.</p>
</desc>
</func>
@@ -227,7 +285,7 @@
<fsummary>Memory allocation information.</fsummary>
<desc>
<p>Memory allocation information. Equivalent to
- <seealso marker="erts:erlang#memory/0"><c>erlang:memory/0</c></seealso>.</p>
+ <seemfa marker="erts:erlang#memory/0"><c>erlang:memory/0</c></seemfa>.</p>
</desc>
</func>
@@ -237,7 +295,7 @@
<fsummary>Memory allocation information.</fsummary>
<desc>
<p>Memory allocation information. Equivalent to
- <seealso marker="erts:erlang#memory/1"><c>erlang:memory/1</c></seealso>.</p>
+ <seemfa marker="erts:erlang#memory/1"><c>erlang:memory/1</c></seemfa>.</p>
</desc>
</func>
@@ -336,9 +394,9 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
<code type="none">
yecc:file(File)</code>
<p>For information about <c>File = name()</c>, see
- <seealso marker="filename"><c>filename(3)</c></seealso>.
+ <seeerl marker="filename"><c>filename(3)</c></seeerl>.
For information about <c>YeccRet</c>, see
- <seealso marker="parsetools:yecc#file/1"><c>yecc:file/2</c></seealso>.
+ <seemfa marker="parsetools:yecc#file/1"><c>yecc:file/2</c></seemfa>.
</p>
</desc>
</func>
@@ -355,9 +413,9 @@ yecc:file(File)</code>
<code type="none">
yecc:file(File, Options)</code>
<p>For information about <c>File = name()</c>, see
- <seealso marker="filename"><c>filename(3)</c></seealso>.
+ <seeerl marker="filename"><c>filename(3)</c></seeerl>.
For information about <c>Options</c> and <c>YeccRet</c>, see
- <seealso marker="parsetools:yecc#file/1"><c>yecc:file/2</c></seealso>.
+ <seemfa marker="parsetools:yecc#file/1"><c>yecc:file/2</c></seemfa>.
</p>
</desc>
</func>
@@ -365,11 +423,11 @@ yecc:file(File, Options)</code>
<section>
<title>See Also</title>
- <p><seealso marker="filename"><c>filename(3)</c></seealso>,
- <seealso marker="compiler:compile"><c>compile(3)</c></seealso>,
- <seealso marker="erts:erlang"><c>erlang(3)</c></seealso>,
- <seealso marker="parsetools:yecc"><c>yecc(3)</c></seealso>,
- <seealso marker="tools:xref"><c>xref(3)</c></seealso></p>
+ <p><seeerl marker="filename"><c>filename(3)</c></seeerl>,
+ <seeerl marker="compiler:compile"><c>compile(3)</c></seeerl>,
+ <seeerl marker="erts:erlang"><c>erlang(3)</c></seeerl>,
+ <seeerl marker="parsetools:yecc"><c>yecc(3)</c></seeerl>,
+ <seeerl marker="tools:xref"><c>xref(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/calendar.xml b/lib/stdlib/doc/src/calendar.xml
index 6cc0befd45..213bca365d 100644
--- a/lib/stdlib/doc/src/calendar.xml
+++ b/lib/stdlib/doc/src/calendar.xml
@@ -41,7 +41,7 @@
Greenwich Mean Time (GMT).</p>
<p>The time functions <c>local_time/0</c> and
<c>universal_time/0</c> in this module both return date
- and time. The is because separate functions for date
+ and time. This is because separate functions for date
and time can result in a date/time combination that is displaced
by 24 hours. This occurs if one of the functions is called
before midnight, and the other after midnight. This problem also
@@ -201,7 +201,7 @@
<p>Returns tuple <c>{Year, WeekNum}</c> representing
the ISO week number for the actual date. To determine the
actual date, use function
- <seealso marker="#local_time/0"><c>local_time/0</c></seealso>.</p>
+ <seemfa marker="#local_time/0"><c>local_time/0</c></seemfa>.</p>
</desc>
</func>
@@ -241,8 +241,8 @@
date after Jan 1, 1970.</p>
<warning>
<p>This function is deprecated. Use
- <seealso marker="#local_time_to_universal_time_dst/1">
- <c>local_time_to_universal_time_dst/1</c></seealso>
+ <seemfa marker="#local_time_to_universal_time_dst/1">
+ <c>local_time_to_universal_time_dst/1</c></seemfa>
instead, as it gives a more correct and complete result.
Especially for
the period that does not exist, as it is skipped during
@@ -290,7 +290,7 @@
<desc>
<p>Returns Universal Coordinated Time (UTC)
converted from the return value from
- <seealso marker="erts:erlang#timestamp/0"><c>erlang:timestamp/0</c></seealso>.
+ <seemfa marker="erts:erlang#timestamp/0"><c>erlang:timestamp/0</c></seemfa>.
</p>
</desc>
</func>
@@ -300,7 +300,7 @@
<fsummary>Convert now to local date and time.</fsummary>
<desc>
<p>Returns local date and time converted from the return value from
- <seealso marker="erts:erlang#timestamp/0"><c>erlang:timestamp/0</c></seealso>.
+ <seemfa marker="erts:erlang#timestamp/0"><c>erlang:timestamp/0</c></seemfa>.
</p>
</desc>
</func>
@@ -311,7 +311,7 @@
<desc>
<p>Returns Universal Coordinated Time (UTC)
converted from the return value from
- <seealso marker="erts:erlang#timestamp/0"><c>erlang:timestamp/0</c></seealso>.
+ <seemfa marker="erts:erlang#timestamp/0"><c>erlang:timestamp/0</c></seemfa>.
</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/dets.xml b/lib/stdlib/doc/src/dets.xml
index ba319cf8ff..873167714c 100644
--- a/lib/stdlib/doc/src/dets.xml
+++ b/lib/stdlib/doc/src/dets.xml
@@ -84,7 +84,7 @@
is important to realize that a single look-up operation involves a
series of disk seek and read operations. The Dets functions
are therefore much slower than the corresponding
- <seealso marker="ets"><c>ets(3)</c></seealso> functions,
+ <seeerl marker="ets"><c>ets(3)</c></seeerl> functions,
although Dets exports a similar interface.</p>
<p>Dets organizes data as a linear hash list and the hash list
@@ -107,8 +107,8 @@
ordered disk-based term storage.</p>
<p>All Dets functions return <c>{error, Reason}</c> if an error
- occurs (<seealso marker="#first/1"><c>first/1</c></seealso> and
- <seealso marker="#next/2"><c>next/2</c></seealso> are exceptions, they
+ occurs (<seemfa marker="#first/1"><c>first/1</c></seemfa> and
+ <seemfa marker="#next/2"><c>next/2</c></seemfa> are exceptions, they
exit the process with the error tuple). If badly formed arguments are
specified, all functions exit the process with a <c>badarg</c>
message.</p>
@@ -124,16 +124,16 @@
<datatype>
<name name="bindings_cont"/>
<desc>
- <p>Opaque continuation used by <seealso marker="#match/1">
- <c>match/1</c></seealso> and <seealso marker="#match/3">
- <c>match/3</c></seealso>.</p>
+ <p>Opaque continuation used by <seemfa marker="#match/1">
+ <c>match/1</c></seemfa> and <seemfa marker="#match/3">
+ <c>match/3</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
<name name="cont"/>
<desc>
- <p>Opaque continuation used by <seealso marker="#bchunk/2">
- <c>bchunk/2</c></seealso>.</p>
+ <p>Opaque continuation used by <seemfa marker="#bchunk/2">
+ <c>bchunk/2</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -143,9 +143,9 @@
<name name="match_spec"/>
<desc>
<p>Match specifications, see section
- <seealso marker="erts:match_spec">
- Match Specification in Erlang</seealso> in ERTS User's Guide and the
- <seealso marker="ms_transform"><c>ms_transform(3)</c></seealso>
+ <seeguide marker="erts:match_spec">
+ Match Specification in Erlang</seeguide> in ERTS User's Guide and the
+ <seeerl marker="ms_transform"><c>ms_transform(3)</c></seeerl>
module.</p>
</desc>
</datatype>
@@ -158,24 +158,24 @@
<datatype>
<name name="object_cont"/>
<desc>
- <p>Opaque continuation used by <seealso marker="#match_object/1">
- <c>match_object/1</c></seealso> and
- <seealso marker="#match_object/3"><c>match_object/3</c></seealso>.</p>
+ <p>Opaque continuation used by <seemfa marker="#match_object/1">
+ <c>match_object/1</c></seemfa> and
+ <seemfa marker="#match_object/3"><c>match_object/3</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
<name name="pattern"/>
<desc>
<p>For a description of patterns, see
- <seealso marker="ets#match/2"><c>ets:match/2</c></seealso>.</p>
+ <seemfa marker="ets#match/2"><c>ets:match/2</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
<name name="select_cont"/>
<desc>
- <p>Opaque continuation used by <seealso marker="#select/1">
- <c>select/1</c></seealso> and <seealso marker="#select/3">
- <c>select/3</c></seealso>.</p>
+ <p>Opaque continuation used by <seemfa marker="#select/1">
+ <c>select/1</c></seemfa> and <seemfa marker="#select/3">
+ <c>select/3</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -205,7 +205,7 @@
representation of the returned objects is not public. The
lists of data can be used for initializing a table by specifying
value <c>bchunk</c> to option <c>format</c> of function
- <seealso marker="#init_table/3"><c>init_table/3</c></seealso>
+ <seemfa marker="#init_table/3"><c>init_table/3</c></seemfa>
The Mnesia application uses this
function for copying open tables.</p>
<p>Unless the table is protected using <c>safe_fixtable/2</c>,
@@ -277,7 +277,7 @@
according to the internal order of the table, or
<c>'$end_of_table'</c> if the table is empty.</p>
<p>Unless the table is protected using <c>safe_fixtable/2</c>,
- subsequent calls to <seealso marker="#next/2"><c>next/2</c></seealso>
+ subsequent calls to <seemfa marker="#next/2"><c>next/2</c></seemfa>
do possibly not work as expected if
concurrent updates are made to the table.</p>
<p>If an error occurs, the process is exited with an error
@@ -287,9 +287,9 @@
are not to be used: they are not efficient, and they
prevent the use of key <c>'$end_of_table'</c>, as this atom
is used to indicate the end of the table. If possible, use functions
- <seealso marker="#match/1"><c>match</c></seealso>,
- <seealso marker="#match_object/1"><c>match_object</c></seealso>, and
- <seealso marker="#select/1"><c>select</c></seealso>
+ <seemfa marker="#match/1"><c>match</c></seemfa>,
+ <seemfa marker="#match_object/1"><c>match_object</c></seemfa>, and
+ <seemfa marker="#select/1"><c>select</c></seemfa>
for traversing tables.</p>
</desc>
</func>
@@ -333,21 +333,21 @@
bytes.</p>
</item>
<item>
- <p><c>{filename, </c><seealso marker="file#type-name">
- <c>file:name()</c></seealso><c>}</c> - The name of the file
+ <p><c>{filename, </c><seetype marker="file#name">
+ <c>file:name()</c></seetype><c>}</c> - The name of the file
where objects are stored.</p>
</item>
<item>
- <p><c>{keypos, </c><seealso marker="#type-keypos">
- <c>keypos()</c></seealso><c>}</c> - The key position.</p>
+ <p><c>{keypos, </c><seetype marker="#keypos">
+ <c>keypos()</c></seetype><c>}</c> - The key position.</p>
</item>
<item>
<p><c>{size, integer() >= 0}</c> - The number of objects
stored in the table.</p>
</item>
<item>
- <p><c>{type, </c><seealso marker="#type-type">
- <c>type()</c></seealso><c>}</c> - The table type.</p>
+ <p><c>{type, </c><seetype marker="#type">
+ <c>type()</c></seetype><c>}</c> - The table type.</p>
</item>
</list>
</desc>
@@ -361,16 +361,16 @@
<p>Returns the information associated with <c><anno>Item</anno></c>
for table <c><anno>Name</anno></c>.
In addition to the <c>{<anno>Item</anno>, <anno>Value</anno>}</c>
- pairs defined for <seealso marker="#info/1"><c>info/1</c></seealso>,
+ pairs defined for <seemfa marker="#info/1"><c>info/1</c></seemfa>,
the following items are allowed:</p>
<list type="bulleted">
<item>
- <p><c>{access, </c><seealso marker="#type-access">
- <c>access()</c></seealso><c>}</c> - The access mode.</p>
+ <p><c>{access, </c><seetype marker="#access">
+ <c>access()</c></seetype><c>}</c> - The access mode.</p>
</item>
<item>
- <p><c>{auto_save, </c><seealso marker="#type-auto_save">
- <c>auto_save()</c></seealso><c>}</c> - The autosave interval.</p>
+ <p><c>{auto_save, </c><seetype marker="#auto_save">
+ <c>auto_save()</c></seetype><c>}</c> - The autosave interval.</p>
</item>
<item>
<p><c>{bchunk_format, binary()}</c> - An opaque binary
@@ -429,25 +429,25 @@
There can be any number of processes in the list. If the table
is not fixed, <c>SafeFixed</c> is the atom <c>false</c>.</p>
<p><c>FixedAtTime</c> corresponds to the result returned by
- <seealso marker="erts:erlang#monotonic_time/0">
- <c>erlang:monotonic_time/0</c></seealso> at the time of fixation.
+ <seemfa marker="erts:erlang#monotonic_time/0">
+ <c>erlang:monotonic_time/0</c></seemfa> at the time of fixation.
The use of <c>safe_fixed_monotonic_time</c> is
- <seealso marker="erts:time_correction#Time_Warp_Safe_Code">
- time warp safe</seealso>.</p>
+ <seeguide marker="erts:time_correction#Time_Warp_Safe_Code">
+ time warp safe</seeguide>.</p>
</item>
<item>
<p><c>{safe_fixed, SafeFixed}</c> - The same as
<c>{safe_fixed_monotonic_time, SafeFixed}</c> except
the format and value of <c>FixedAtTime</c>.</p>
<p><c>FixedAtTime</c> corresponds to the result returned by
- <seealso marker="erts:erlang#timestamp/0">
- <c>erlang:timestamp/0</c></seealso> at the time of fixation.
+ <seemfa marker="erts:erlang#timestamp/0">
+ <c>erlang:timestamp/0</c></seemfa> at the time of fixation.
Notice that when the system uses single or multi
- <seealso marker="erts:time_correction#Time_Warp_Modes">time warp
- modes</seealso>, this can produce strange results. This is
+ <seeguide marker="erts:time_correction#Time_Warp_Modes">time warp
+ modes</seeguide>, this can produce strange results. This is
because the use of <c>safe_fixed</c> is not
- <seealso marker="erts:time_correction#Time_Warp_Safe_Code">
- time warp safe</seealso>. Time warp safe code must use
+ <seeguide marker="erts:time_correction#Time_Warp_Safe_Code">
+ time warp safe</seeguide>. Time warp safe code must use
<c>safe_fixed_monotonic_time</c> instead.</p>
</item>
</list>
@@ -508,7 +508,7 @@
<c><anno>InitFun</anno></c> is assumed to return a list of tuples.
If <c>Format</c> is <c>bchunk</c>, <c><anno>InitFun</anno></c> is
assumed to return <c><anno>Data</anno></c> as returned by
- <seealso marker="#bchunk/2"><c>bchunk/2</c></seealso>.
+ <seemfa marker="#bchunk/2"><c>bchunk/2</c></seemfa>.
This option overrides option <c>min_no_slots</c>.</p>
</item>
</list>
@@ -544,9 +544,9 @@
<desc>
<p>Returns <c>true</c> if it would be possible to initialize
table <c><anno>Name</anno></c>, using
- <seealso marker="#init_table/3"><c>init_table/3</c></seealso> with
+ <seemfa marker="#init_table/3"><c>init_table/3</c></seemfa> with
option <c>{format,&nbsp;bchunk}</c>, with objects read with
- <seealso marker="#bchunk/2"><c>bchunk/2</c></seealso> from some
+ <seemfa marker="#bchunk/2"><c>bchunk/2</c></seemfa> from some
table <c>T</c>, such that calling
<c>info(T,&nbsp;bchunk_format)</c> returns
<c>BchunkFormat</c>.</p>
@@ -613,7 +613,7 @@ ok
<p>Returns for each object of table <c><anno>Name</anno></c> that
matches <c><anno>Pattern</anno></c> a list of bindings in some
unspecified order. For a description of patterns, see
- <seealso marker="ets#match/2"><c>ets:match/2</c></seealso>.
+ <seemfa marker="ets#match/2"><c>ets:match/2</c></seemfa>.
If the keypos'th element of
<c><anno>Pattern</anno></c> is unbound, all table objects are
matched. If the keypos'th element is bound, only the
@@ -630,12 +630,12 @@ ok
returns a non-empty list of the bindings that match
<c><anno>Pattern</anno></c> in some unspecified order.
For a description of patterns, see
- <seealso marker="ets#match/2"><c>ets:match/2</c></seealso>.</p>
+ <seemfa marker="ets#match/2"><c>ets:match/2</c></seemfa>.</p>
<p>A tuple of the bindings and a continuation is returned,
unless the table is empty, in which case
<c>'$end_of_table'</c> is returned. The continuation is to be
used when matching further objects by calling
- <seealso marker="#match/1"><c>match/1</c></seealso>.</p>
+ <seemfa marker="#match/1"><c>match/1</c></seemfa>.</p>
<p>If the keypos'th element of <c><anno>Pattern</anno></c> is bound,
all table objects are matched. If the keypos'th element is
unbound, all table objects are matched, <c><anno>N</anno></c>
@@ -647,7 +647,7 @@ ok
same key are always matched at the same time, which implies that
more than <anno>N</anno> objects can sometimes be matched.</p>
<p>The table is always to be protected using
- <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ <seemfa marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seemfa>
before calling <c>match/3</c>, otherwise
errors can occur when calling <c>match/1</c>.</p>
</desc>
@@ -660,7 +660,7 @@ ok
<desc>
<p>Deletes all objects that match <c><anno>Pattern</anno></c> from
table <c><anno>Name</anno></c>. For a description of patterns,
- see <seealso marker="ets#match/2"><c>ets:match/2</c></seealso>.</p>
+ see <seemfa marker="ets#match/2"><c>ets:match/2</c></seemfa>.</p>
<p>If the keypos'th element of <c>Pattern</c> is bound,
only the objects with the correct key are matched.</p>
</desc>
@@ -690,7 +690,7 @@ ok
<p>Returns a list of all objects of table <c><anno>Name</anno></c> that
match <c><anno>Pattern</anno></c> in some unspecified order.
For a description of patterns, see
- <seealso marker="ets#match/2"><c>ets:match/2</c></seealso>.</p>
+ <seemfa marker="ets#match/2"><c>ets:match/2</c></seemfa>.</p>
<p>If the keypos'th element of <c><anno>Pattern</anno></c> is
unbound, all table objects are matched. If the
keypos'th element of <c><anno>Pattern</anno></c> is bound, only the
@@ -710,12 +710,12 @@ ok
and returns a non-empty list of the objects that match
<c><anno>Pattern</anno></c> in some unspecified order.
For a description of patterns, see
- <seealso marker="ets#match/2"><c>ets:match/2</c></seealso>.</p>
+ <seemfa marker="ets#match/2"><c>ets:match/2</c></seemfa>.</p>
<p>A list of objects and a continuation is returned, unless
the table is empty, in which case <c>'$end_of_table'</c>
is returned. The continuation is to be used when matching
further objects by calling
- <seealso marker="#match_object/1"><c>match_object/1</c></seealso>.</p>
+ <seemfa marker="#match_object/1"><c>match_object/1</c></seemfa>.</p>
<p>If the keypos'th element of <c><anno>Pattern</anno></c> is bound,
all table objects are matched. If the keypos'th element is
unbound, all table objects are matched, <c><anno>N</anno></c>
@@ -728,7 +728,7 @@ ok
in the same reply, which implies
that more than <anno>N</anno> objects can sometimes be returned.</p>
<p>The table is always to be protected using
- <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ <seemfa marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seemfa>
before calling <c>match_object/3</c>, otherwise
errors can occur when calling <c>match_object/1</c>.</p>
</desc>
@@ -738,7 +738,7 @@ ok
<name name="member" arity="2" since=""/>
<fsummary>Test for occurrence of a key in a Dets table.</fsummary>
<desc>
- <p>Works like <seealso marker="#lookup/2"><c>lookup/2</c></seealso>,
+ <p>Works like <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>,
but does not return the objects. Returns <c>true</c> if one or more
table elements has key <c><anno>Key</anno></c>, otherwise
<c>false</c>.</p>
@@ -755,7 +755,7 @@ ok
<p>If an error occurs, the process is exited with an error
tuple <c>{error, Reason}</c>.</p>
<p>To find the first key in the table, use
- <seealso marker="#first/1"><c>first/1</c></seealso>.</p>
+ <seemfa marker="#first/1"><c>first/1</c></seemfa>.</p>
</desc>
</func>
@@ -787,16 +787,16 @@ ok
tuples, where the following values are allowed:</p>
<list type="bulleted">
<item>
- <p><c>{access, </c><seealso marker="#type-access">
- <c>access()</c></seealso><c>}</c> - Existing tables can be
+ <p><c>{access, </c><seetype marker="#access">
+ <c>access()</c></seetype><c>}</c> - Existing tables can be
opened in read-only mode. A table that is opened
in read-only mode is not subjected to the automatic file
reparation algorithm if it is later opened after a crash.
Defaults to <c>read_write</c>.</p>
</item>
<item>
- <p><c>{auto_save, </c><seealso marker="#type-auto_save">
- <c>auto_save()</c></seealso><c>}</c> - The autosave
+ <p><c>{auto_save, </c><seetype marker="#auto_save">
+ <c>auto_save()</c></seetype><c>}</c> - The autosave
interval. If the interval is an integer <c>Time</c>, the
table is flushed to disk whenever it is not accessed for
<c>Time</c> milliseconds. A table that has been flushed
@@ -806,18 +806,18 @@ ok
180000 (3 minutes).</p>
</item>
<item>
- <p><c>{estimated_no_objects, </c><seealso marker="#type-no_slots">
- <c>no_slots()</c></seealso><c>}</c> - Equivalent to option
+ <p><c>{estimated_no_objects, </c><seetype marker="#no_slots">
+ <c>no_slots()</c></seetype><c>}</c> - Equivalent to option
<c>min_no_slots</c>.</p>
</item>
<item>
- <p><c>{file, </c><seealso marker="file#type-name">
- <c>file:name()</c></seealso><c>}</c> - The name of the file to be
+ <p><c>{file, </c><seetype marker="file#name">
+ <c>file:name()</c></seetype><c>}</c> - The name of the file to be
opened. Defaults to the table name.</p>
</item>
<item>
- <p><c>{max_no_slots, </c><seealso marker="#type-no_slots">
- <c>no_slots()</c></seealso><c>}</c> - The maximum number
+ <p><c>{max_no_slots, </c><seetype marker="#no_slots">
+ <c>no_slots()</c></seetype><c>}</c> - The maximum number
of slots to be used. Defaults to 32 M, which is the
maximal value. Notice that a higher value can
increase the table fragmentation, and
@@ -825,16 +825,16 @@ ok
the expense of execution time.</p>
</item>
<item>
- <p><c>{min_no_slots, </c><seealso marker="#type-no_slots">
- <c>no_slots()</c></seealso><c>}</c> - Application
+ <p><c>{min_no_slots, </c><seetype marker="#no_slots">
+ <c>no_slots()</c></seetype><c>}</c> - Application
performance can be enhanced with this flag by specifying,
when the table is created, the estimated number of
different keys to be stored in the table. Defaults to 256,
which is the minimum value.</p>
</item>
<item>
- <p><c>{keypos, </c><seealso marker="#type-keypos">
- <c>keypos()</c></seealso><c>}</c> - The position of the
+ <p><c>{keypos, </c><seetype marker="#keypos">
+ <c>keypos()</c></seetype><c>}</c> - The position of the
element of each object to be used as key. Defaults to 1.
The ability to explicitly state the key
position is most convenient when we want to store Erlang
@@ -863,8 +863,8 @@ ok
<p>Option <c>repair</c> is ignored if the table is already open.</p>
</item>
<item>
- <p><c>{type, </c><seealso marker="#type-type">
- <c>type()</c></seealso><c>}</c> - The table type. Defaults to
+ <p><c>{type, </c><seetype marker="#type">
+ <c>type()</c></seetype><c>}</c> - The table type. Defaults to
<c>set</c>.</p>
</item>
</list>
@@ -889,8 +889,8 @@ ok
<desc>
<p>This function can be used to restore an opaque continuation
returned by
- <seealso marker="#select/3"><c>select/3</c></seealso> or
- <seealso marker="#select/1"><c>select/1</c></seealso> if the
+ <seemfa marker="#select/3"><c>select/3</c></seemfa> or
+ <seemfa marker="#select/1"><c>select/1</c></seemfa> if the
continuation has passed through external term format (been
sent between nodes or stored on disk).</p>
<p>The reason for this function is that continuation terms
@@ -901,7 +901,7 @@ ok
used in subsequent <c>select/1</c> calls even though it has
been stored on disk or on another node.</p>
<p>For more information and examples, see the
- <seealso marker="ets"><c>ets(3)</c></seealso> module.</p>
+ <seeerl marker="ets"><c>ets(3)</c></seeerl> module.</p>
<note>
<p>This function is rarely needed in application code. It is used by
application Mnesia to provide distributed <c>select/3</c>
@@ -933,7 +933,7 @@ ok
<c>next/2</c>, or select and match functions work as expected
even if the table is fixed; the limited support for
concurrency provided by the
- <seealso marker="ets"><c>ets(3)</c></seealso> module is not yet
+ <seeerl marker="ets"><c>ets(3)</c></seeerl> module is not yet
provided by Dets.
Fixing a table currently only disables resizing of
the hash list of the table.</p>
@@ -954,8 +954,8 @@ ok
table, the match specification, and the number of objects
that are matched are all defined by <c><anno>Continuation</anno></c>,
which is returned by a previous call to
- <seealso marker="#select/1"><c>select/1</c></seealso> or
- <seealso marker="#select/3"><c>select/3</c></seealso>.</p>
+ <seemfa marker="#select/1"><c>select/1</c></seemfa> or
+ <seemfa marker="#select/3"><c>select/3</c></seemfa>.</p>
<p>When all objects of the table have been matched,
<c>'$end_of_table'</c> is returned.</p>
</desc>
@@ -970,7 +970,7 @@ ok
<c><anno>MatchSpec</anno></c> to all or some objects stored in table
<c><anno>Name</anno></c>. The order of the objects is not specified.
For a description of match specifications, see the
- <seealso marker="erts:match_spec">ERTS User's Guide</seealso>.</p>
+ <seeguide marker="erts:match_spec">ERTS User's Guide</seeguide>.</p>
<p>If the keypos'th element of <c><anno>MatchSpec</anno></c> is
unbound, the match specification is applied to all objects of
the table. If the keypos'th element is bound, the match
@@ -992,12 +992,12 @@ ok
<c><anno>MatchSpec</anno></c> to some or all objects stored in table
<c><anno>Name</anno></c>. The order of the objects is not specified.
For a description of match specifications, see the
- <seealso marker="erts:match_spec">ERTS User's Guide</seealso>.</p>
+ <seeguide marker="erts:match_spec">ERTS User's Guide</seeguide>.</p>
<p>A tuple of the results of applying the match specification
and a continuation is returned, unless the table is empty,
in which case <c>'$end_of_table'</c> is returned. The
continuation is to be used when matching more objects by calling
- <seealso marker="#select/1"><c>select/1</c></seealso>.</p>
+ <seemfa marker="#select/1"><c>select/1</c></seemfa>.</p>
<p>If the keypos'th element of <c><anno>MatchSpec</anno></c> is bound,
the match specification is applied to all objects of the table
with the correct key(s). If the keypos'th element of
@@ -1012,7 +1012,7 @@ ok
match specification can be applied to more than <anno>N</anno>
objects.</p>
<p>The table is always to be protected using
- <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ <seemfa marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seemfa>
before calling <c>select/3</c>, otherwise
errors can occur when calling <c>select/1</c>.</p>
</desc>
@@ -1027,7 +1027,7 @@ ok
applying match specification <c><anno>MatchSpec</anno></c> to the
object returns value <c>true</c>.
For a description of match specifications, see the
- <seealso marker="erts:match_spec">ERTS User's Guide</seealso>.
+ <seeguide marker="erts:match_spec">ERTS User's Guide</seeguide>.
Returns the number of deleted objects.</p>
<p>If the keypos'th element of <c><anno>MatchSpec</anno></c> is
bound, the match specification is applied to the objects
@@ -1070,16 +1070,16 @@ ok
<desc>
<p>Returns a Query List
Comprehension (QLC) query handle. The
- <seealso marker="qlc"><c>qlc(3)</c></seealso> module
+ <seeerl marker="qlc"><c>qlc(3)</c></seeerl> module
provides a query language aimed mainly for Mnesia, but
ETS tables, Dets tables, and lists are also recognized
by <c>qlc</c> as sources of data. Calling
- <seealso marker="dets#table/1"><c>dets:table/1,2</c></seealso> is the
+ <seemfa marker="dets#table/1"><c>dets:table/1,2</c></seemfa> is the
means to make Dets table <c><anno>Name</anno></c> usable to
<c>qlc</c>.</p>
<p>When there are only simple restrictions on the key position,
<c>qlc</c> uses
- <seealso marker="dets#lookup/2"><c>dets:lookup/2</c></seealso>
+ <seemfa marker="dets#lookup/2"><c>dets:lookup/2</c></seemfa>
to look up the keys. When
that is not possible, the whole table is traversed.
Option <c>traverse</c> determines how this is done:</p>
@@ -1090,8 +1090,8 @@ ok
</item>
<item>
<p><c>select</c> - The table is traversed by calling
- <seealso marker="dets#select/3"><c>dets:select/3</c></seealso> and
- <seealso marker="dets#select/1"><c>dets:select/1</c></seealso>.
+ <seemfa marker="dets#select/3"><c>dets:select/3</c></seemfa> and
+ <seemfa marker="dets#select/1"><c>dets:select/1</c></seemfa>.
Option <c>n_objects</c> determines the number of objects
returned (the third argument of <c>select/3</c>). The
match specification (the second argument of
@@ -1109,8 +1109,8 @@ ok
</list>
</item>
<item>
- <p><c>{select, </c><seealso marker="#type-match_spec">
- match_spec()</seealso><c>}</c> - As for <c>select</c>,
+ <p><c>{select, </c><seetype marker="#match_spec">
+ match_spec()</seetype><c>}</c> - As for <c>select</c>,
the table is traversed by calling <c>dets:select/3</c>
and <c>dets:select/1</c>. The difference is that the
match specification is specified explicitly. This is how to
@@ -1213,9 +1213,9 @@ fun(X) -> {continue, X} end.</pre>
<section>
<title>See Also</title>
- <p><seealso marker="ets"><c>ets(3)</c></seealso>,
- <seealso marker="mnesia:mnesia"><c>mnesia(3)</c></seealso>,
- <seealso marker="qlc"><c>qlc(3)</c></seealso></p>
+ <p><seeerl marker="ets"><c>ets(3)</c></seeerl>,
+ <seeerl marker="mnesia:mnesia"><c>mnesia(3)</c></seeerl>,
+ <seeerl marker="qlc"><c>qlc(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/dict.xml b/lib/stdlib/doc/src/dict.xml
index 197269190e..aeb2584509 100644
--- a/lib/stdlib/doc/src/dict.xml
+++ b/lib/stdlib/doc/src/dict.xml
@@ -34,7 +34,7 @@
<p>This module provides a <c>Key</c>-<c>Value</c> dictionary.
The representation of a dictionary is not defined.</p>
<p>This module provides the same interface as the
- <seealso marker="orddict"><c>orddict(3)</c></seealso> module.
+ <seeerl marker="orddict"><c>orddict(3)</c></seeerl> module.
One difference is that while this module
considers two keys as different if they do not match (<c>=:=</c>),
<c>orddict</c> considers two keys as different if and only if
@@ -45,7 +45,7 @@
<datatype>
<name name="dict" n_vars="2"/>
<desc><p>Dictionary as returned by
- <seealso marker="#new/0"><c>new/0</c></seealso>.</p>
+ <seemfa marker="#new/0"><c>new/0</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -60,7 +60,7 @@
<desc>
<p>Appends a new <c><anno>Value</anno></c> to the current list
of values associated with <c><anno>Key</anno></c>.</p>
- <p>See also section <seealso marker="#notes">Notes</seealso>.</p>
+ <p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
</desc>
</func>
@@ -72,7 +72,7 @@
the current list of values associated with <c><anno>Key</anno></c>. An
exception is generated if the initial value associated with
<c><anno>Key</anno></c> is not a list of values.</p>
- <p>See also section <seealso marker="#notes">Notes</seealso>.</p>
+ <p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
</desc>
</func>
@@ -93,7 +93,7 @@
<c><anno>Key</anno></c> is present in dictionary <c>Dict</c>,
and an exception
is generated if <c><anno>Key</anno></c> is not in the dictionary.</p>
- <p>See also section <seealso marker="#notes">Notes</seealso>.</p>
+ <p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
</desc>
</func>
@@ -134,7 +134,7 @@
<c>{ok, <anno>Value</anno>}</c>, where <c><anno>Value</anno></c> is
the value associated with <c><anno>Key</anno></c>, or <c>error</c>
if the key is not present in the dictionary.</p>
- <p>See also section <seealso marker="#notes">Notes</seealso>.</p>
+ <p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
</desc>
</func>
@@ -307,8 +307,8 @@ update_counter(Key, Incr, D) ->
<section>
<title>See Also</title>
- <p><seealso marker="gb_trees"><c>gb_trees(3)</c></seealso>,
- <seealso marker="orddict"><c>orddict(3)</c></seealso></p>
+ <p><seeerl marker="gb_trees"><c>gb_trees(3)</c></seeerl>,
+ <seeerl marker="orddict"><c>orddict(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/digraph.xml b/lib/stdlib/doc/src/digraph.xml
index 51aabe4ef6..721429d819 100644
--- a/lib/stdlib/doc/src/digraph.xml
+++ b/lib/stdlib/doc/src/digraph.xml
@@ -39,13 +39,13 @@
directed graphs ("digraphs").</p>
<p>The digraphs managed by this module are stored in
- <seealso marker="ets">ETS tables</seealso>.
+ <seeerl marker="ets">ETS tables</seeerl>.
That implies the following:</p>
<list type="bulleted">
<item><p>Only the process that created the digraph is allowed to update it.</p></item>
<item><p>Digraphs will not be garbage collected. The ETS tables used for a
- digraph will only be deleted when <seealso marker="#delete/1"><c>delete/1</c></seealso>
+ digraph will only be deleted when <seemfa marker="#delete/1"><c>delete/1</c></seemfa>
is called or the process that created the digraph terminates.</p></item>
<item><p>A digraph is a mutable data structure.</p></item>
</list>
@@ -142,7 +142,7 @@
<datatype>
<name name="graph"/>
<desc><p>A digraph as returned by
- <seealso marker="#new/0"><c>new/0,1</c></seealso>.</p></desc>
+ <seemfa marker="#new/0"><c>new/0,1</c></seemfa>.</p></desc>
</datatype>
<datatype>
<name>edge()</name>
@@ -165,10 +165,10 @@
<desc>
<p><c>add_edge/5</c> creates (or modifies) edge <c><anno>E</anno></c>
of digraph <c><anno>G</anno></c>, using <c><anno>Label</anno></c> as
- the (new) <seealso marker="#label">label</seealso> of the edge. The
- edge is <seealso marker="#emanate">emanating</seealso> from
+ the (new) <seeerl marker="#label">label</seeerl> of the edge. The
+ edge is <seeerl marker="#emanate">emanating</seeerl> from
<c><anno>V1</anno></c> and
- <seealso marker="#incident">incident</seealso>
+ <seeerl marker="#incident">incident</seeerl>
on <c><anno>V2</anno></c>. Returns <c><anno>E</anno></c>.</p>
<p><c>add_edge(<anno>G</anno>,&nbsp;<anno>V1</anno>,&nbsp;<anno>V2</anno>,&nbsp;<anno>Label</anno>)</c>
is equivalent to
@@ -181,7 +181,7 @@
<c>add_edge(<anno>G</anno>,&nbsp;<anno>V1</anno>,&nbsp;<anno>V2</anno>,&nbsp;[])</c>.
</p>
<p>If the edge would create a cycle in
- an <seealso marker="#acyclic_digraph">acyclic digraph</seealso>,
+ an <seeerl marker="#acyclic_digraph">acyclic digraph</seeerl>,
<c>{error,&nbsp;{bad_edge,&nbsp;<anno>Path</anno>}}</c> is returned.
If <c><anno>G</anno></c> already has an edge with value
<c><anno>E</anno></c> connecting a different pair of vertices,
@@ -204,7 +204,7 @@
<p><c>add_vertex/3</c> creates (or modifies) vertex
<c><anno>V</anno></c> of digraph <c><anno>G</anno></c>, using
<c><anno>Label</anno></c> as the (new)
- <seealso marker="#label">label</seealso> of the
+ <seeerl marker="#label">label</seeerl> of the
vertex. Returns <c><anno>V</anno></c>.</p>
<p><c>add_vertex(<anno>G</anno>,&nbsp;<anno>V</anno>)</c> is equivalent
to <c>add_vertex(<anno>G</anno>,&nbsp;<anno>V</anno>,&nbsp;[])</c>.
@@ -239,20 +239,20 @@
<fsummary>Delete paths from a digraph.</fsummary>
<desc>
<p>Deletes edges from digraph <c><anno>G</anno></c> until there are no
- <seealso marker="#path">paths</seealso> from vertex
+ <seeerl marker="#path">paths</seeerl> from vertex
<c><anno>V1</anno></c> to vertex <c><anno>V2</anno></c>.</p>
<p>A sketch of the procedure employed:</p>
<list type="bulleted">
<item>
<p>Find an arbitrary
- <seealso marker="#simple_path">simple path</seealso>
+ <seeerl marker="#simple_path">simple path</seeerl>
v[1],&nbsp;v[2],&nbsp;...,&nbsp;v[k] from <c><anno>V1</anno></c>
to <c><anno>V2</anno></c> in <c><anno>G</anno></c>.</p>
</item>
<item>
<p>Remove all edges of <c><anno>G</anno></c>
- <seealso marker="#emanate">emanating</seealso> from v[i] and
- <seealso marker="#incident">incident</seealso> to v[i+1] for
+ <seeerl marker="#emanate">emanating</seeerl> from v[i] and
+ <seeerl marker="#incident">incident</seeerl> to v[i+1] for
1&nbsp;&lt;=&nbsp;i&nbsp;&lt;&nbsp;k (including multiple
edges).</p>
</item>
@@ -270,9 +270,9 @@
<desc>
<p>Deletes vertex <c><anno>V</anno></c> from digraph
<c><anno>G</anno></c>. Any edges
- <seealso marker="#emanate">emanating</seealso> from
+ <seeerl marker="#emanate">emanating</seeerl> from
<c><anno>V</anno></c> or
- <seealso marker="#incident">incident</seealso>
+ <seeerl marker="#incident">incident</seeerl>
on <c><anno>V</anno></c> are also deleted.</p>
</desc>
</func>
@@ -305,10 +305,10 @@
<p>Returns
<c>{<anno>E</anno>,&nbsp;<anno>V1</anno>,&nbsp;<anno>V2</anno>,&nbsp;<anno>Label</anno>}</c>,
where <c><anno>Label</anno></c> is the
- <seealso marker="#label">label</seealso> of edge
- <c><anno>E</anno></c> <seealso marker="#emanate">emanating</seealso>
+ <seeerl marker="#label">label</seeerl> of edge
+ <c><anno>E</anno></c> <seeerl marker="#emanate">emanating</seeerl>
from <c><anno>V1</anno></c> and
- <seealso marker="#incident">incident</seealso> on
+ <seeerl marker="#incident">incident</seeerl> on
<c><anno>V2</anno></c> of digraph <c><anno>G</anno></c>.
If no edge <c><anno>E</anno></c> of
digraph <c><anno>G</anno></c> exists, <c>false</c> is returned.</p>
@@ -330,8 +330,8 @@
a digraph.</fsummary>
<desc>
<p>Returns a list of all
- edges <seealso marker="#emanate">emanating</seealso> from or
- <seealso marker="#incident">incident</seealso> on<c><anno>V</anno></c>
+ edges <seeerl marker="#emanate">emanating</seeerl> from or
+ <seeerl marker="#incident">incident</seeerl> on<c><anno>V</anno></c>
of digraph <c><anno>G</anno></c>, in some unspecified order.</p>
</desc>
</func>
@@ -340,15 +340,15 @@
<name name="get_cycle" arity="2" since=""/>
<fsummary>Find one cycle in a digraph.</fsummary>
<desc>
- <p>If a <seealso marker="#simple_cycle">simple cycle</seealso> of
+ <p>If a <seeerl marker="#simple_cycle">simple cycle</seeerl> of
length two or more exists through vertex <c><anno>V</anno></c>, the
cycle is returned as a list
<c>[<anno>V</anno>,&nbsp;...,&nbsp;<anno>V</anno>]</c> of vertices.
- If a <seealso marker="#loop">loop</seealso> through
+ If a <seeerl marker="#loop">loop</seeerl> through
<c><anno>V</anno></c> exists, the loop is returned as a list
<c>[<anno>V</anno>]</c>. If no cycles through
<c><anno>V</anno></c> exist, <c>false</c> is returned.</p>
- <p><seealso marker="#get_path/3"><c>get_path/3</c></seealso> is used
+ <p><seemfa marker="#get_path/3"><c>get_path/3</c></seemfa> is used
for finding a simple cycle through <c><anno>V</anno></c>.</p>
</desc>
</func>
@@ -358,7 +358,7 @@
<fsummary>Find one path in a digraph.</fsummary>
<desc>
<p>Tries to find
- a <seealso marker="#simple_path">simple path</seealso> from vertex
+ a <seeerl marker="#simple_path">simple path</seeerl> from vertex
<c><anno>V1</anno></c> to vertex <c><anno>V2</anno></c> of digraph
<c><anno>G</anno></c>. Returns the path as a list
<c>[<anno>V1</anno>,&nbsp;...,&nbsp;<anno>V2</anno>]</c> of vertices,
@@ -374,15 +374,15 @@
<fsummary>Find one short cycle in a digraph.</fsummary>
<desc>
<p>Tries to find an as short as possible
- <seealso marker="#simple_cycle">simple cycle</seealso> through
+ <seeerl marker="#simple_cycle">simple cycle</seeerl> through
vertex <c><anno>V</anno></c> of digraph <c>G</c>. Returns the cycle
as a list <c>[<anno>V</anno>,&nbsp;...,&nbsp;<anno>V</anno>]</c>
of vertices, or
<c>false</c> if no simple cycle through <c><anno>V</anno></c> exists.
- Notice that a <seealso marker="#loop">loop</seealso> through
+ Notice that a <seeerl marker="#loop">loop</seeerl> through
<c><anno>V</anno></c> is returned as list
<c>[<anno>V</anno>,&nbsp;<anno>V</anno>]</c>.</p>
- <p><seealso marker="#get_short_path/3"><c>get_short_path/3</c></seealso>
+ <p><seemfa marker="#get_short_path/3"><c>get_short_path/3</c></seemfa>
is used for finding a simple cycle through <c><anno>V</anno></c>.</p>
</desc>
</func>
@@ -392,7 +392,7 @@
<fsummary>Find one short path in a digraph.</fsummary>
<desc>
<p>Tries to find an as short as possible
- <seealso marker="#simple_path">simple path</seealso> from vertex
+ <seeerl marker="#simple_path">simple path</seeerl> from vertex
<c><anno>V1</anno></c> to vertex <c><anno>V2</anno></c> of digraph
<c><anno>G</anno></c>. Returns the path as a list
<c>[<anno>V1</anno>,&nbsp;...,&nbsp;<anno>V2</anno>]</c> of
@@ -408,7 +408,7 @@
<name name="in_degree" arity="2" since=""/>
<fsummary>Return the in-degree of a vertex of a digraph.</fsummary>
<desc>
- <p>Returns the <seealso marker="#in_degree">in-degree</seealso> of
+ <p>Returns the <seeerl marker="#in_degree">in-degree</seeerl> of
vertex <c><anno>V</anno></c> of digraph <c><anno>G</anno></c>.</p>
</desc>
</func>
@@ -418,7 +418,7 @@
<fsummary>Return all edges incident on a vertex of a digraph.</fsummary>
<desc>
<p>Returns a list of all
- edges <seealso marker="#incident">incident</seealso> on
+ edges <seeerl marker="#incident">incident</seeerl> on
<c><anno>V</anno></c> of digraph <c><anno>G</anno></c>,
in some unspecified order.</p>
</desc>
@@ -429,7 +429,7 @@
<fsummary>Return all in-neighbors of a vertex of a digraph.</fsummary>
<desc>
<p>Returns a list of
- all <seealso marker="#in_neighbour">in-neighbors</seealso> of
+ all <seeerl marker="#in_neighbour">in-neighbors</seeerl> of
<c><anno>V</anno></c> of digraph <c><anno>G</anno></c>,
in some unspecified order.</p>
</desc>
@@ -483,15 +483,15 @@
<type name="d_protection"/>
<desc>
<p>Returns
- an <seealso marker="#empty_digraph">empty digraph</seealso> with
+ an <seeerl marker="#empty_digraph">empty digraph</seeerl> with
properties according to the options in <c><anno>Type</anno></c>:</p>
<taglist>
<tag><c>cyclic</c></tag>
- <item><p>Allows <seealso marker="#cycle">cycles</seealso> in the
+ <item><p>Allows <seeerl marker="#cycle">cycles</seeerl> in the
digraph (default).</p></item>
<tag><c>acyclic</c></tag>
<item><p>The digraph is to be kept
- <seealso marker="#acyclic_digraph">acyclic</seealso>.</p></item>
+ <seeerl marker="#acyclic_digraph">acyclic</seeerl>.</p></item>
<tag><c>protected</c></tag>
<item><p>Other processes can read the digraph (default).</p></item>
<tag><c>private</c></tag>
@@ -524,7 +524,7 @@
<name name="out_degree" arity="2" since=""/>
<fsummary>Return the out-degree of a vertex of a digraph.</fsummary>
<desc>
- <p>Returns the <seealso marker="#out_degree">out-degree</seealso> of
+ <p>Returns the <seeerl marker="#out_degree">out-degree</seeerl> of
vertex <c><anno>V</anno></c> of digraph <c><anno>G</anno></c>.</p>
</desc>
</func>
@@ -535,7 +535,7 @@
</fsummary>
<desc>
<p>Returns a list of all
- edges <seealso marker="#emanate">emanating</seealso> from
+ edges <seeerl marker="#emanate">emanating</seeerl> from
<c><anno>V</anno></c> of digraph <c><anno>G</anno></c>,
in some unspecified order.</p>
</desc>
@@ -546,7 +546,7 @@
<fsummary>Return all out-neighbors of a vertex of a digraph.</fsummary>
<desc>
<p>Returns a list of
- all <seealso marker="#out_neighbour">out-neighbors</seealso> of
+ all <seeerl marker="#out_neighbour">out-neighbors</seeerl> of
<c><anno>V</anno></c> of digraph <c><anno>G</anno></c>,
in some unspecified order.</p>
</desc>
@@ -558,7 +558,7 @@
<desc>
<p>Returns <c>{<anno>V</anno>,&nbsp;<anno>Label</anno>}</c>,
where <c><anno>Label</anno></c> is the
- <seealso marker="#label">label</seealso> of the vertex
+ <seeerl marker="#label">label</seeerl> of the vertex
<c><anno>V</anno></c> of digraph <c><anno>G</anno></c>,
or <c>false</c> if no vertex <c><anno>V</anno></c>
of digraph <c><anno>G</anno></c> exists.</p>
@@ -577,8 +577,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="digraph_utils"><c>digraph_utils(3)</c></seealso>,
- <seealso marker="ets"><c>ets(3)</c></seealso></p>
+ <p><seeerl marker="digraph_utils"><c>digraph_utils(3)</c></seeerl>,
+ <seeerl marker="ets"><c>ets(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/digraph_utils.xml b/lib/stdlib/doc/src/digraph_utils.xml
index 8739445852..94fcc26ee5 100644
--- a/lib/stdlib/doc/src/digraph_utils.xml
+++ b/lib/stdlib/doc/src/digraph_utils.xml
@@ -37,7 +37,7 @@
<description>
<p>This module provides algorithms based on depth-first traversal of
directed graphs. For basic functions on directed graphs, see the
- <seealso marker="digraph"><c>digraph(3)</c></seealso> module.</p>
+ <seeerl marker="digraph"><c>digraph(3)</c></seeerl> module.</p>
<list type="bulleted">
<item>
@@ -158,7 +158,7 @@
<fsummary>Check if a digraph is an arborescence.</fsummary>
<desc>
<p>Returns <c>{yes, <anno>Root</anno>}</c> if <c><anno>Root</anno></c>
- is the <seealso marker="#root">root</seealso> of the arborescence
+ is the <seeerl marker="#root">root</seeerl> of the arborescence
<c><anno>Digraph</anno></c>, otherwise <c>no</c>.</p>
</desc>
</func>
@@ -168,7 +168,7 @@
<fsummary>Return the components of a digraph.</fsummary>
<desc>
<p>Returns a list
- of <seealso marker="#components">connected components.</seealso>.
+ of <seeerl marker="#components">connected components.</seeerl>.
Each component is represented by its
vertices. The order of the vertices and the order of the
components are arbitrary. Each vertex of digraph
@@ -181,22 +181,22 @@
<fsummary>Return a condensed graph of a digraph.</fsummary>
<desc>
<p>Creates a digraph where the vertices are
- the <seealso marker="#strong_components">strongly connected
- components</seealso> of <c><anno>Digraph</anno></c> as returned by
- <seealso marker="#strong_components/1">
- <c>strong_components/1</c></seealso>.
+ the <seeerl marker="#strong_components">strongly connected
+ components</seeerl> of <c><anno>Digraph</anno></c> as returned by
+ <seemfa marker="#strong_components/1">
+ <c>strong_components/1</c></seemfa>.
If X and Y are two different strongly
connected components, and vertices x and y exist in X
and Y, respectively, such that there is an
- edge <seealso marker="#emanate">emanating</seealso> from x
- and <seealso marker="#incident">incident</seealso> on y, then
+ edge <seeerl marker="#emanate">emanating</seeerl> from x
+ and <seeerl marker="#incident">incident</seeerl> on y, then
an edge emanating from X and incident on Y is created.</p>
<p>The created digraph has the same type as <c><anno>Digraph</anno></c>.
All vertices and edges have the
- default <seealso marker="#label">label</seealso> <c>[]</c>.</p>
- <p>Each <seealso marker="#cycle">cycle</seealso> is
+ default <seeerl marker="#label">label</seeerl> <c>[]</c>.</p>
+ <p>Each <seeerl marker="#cycle">cycle</seeerl> is
included in some strongly connected component, which implies that
- a <seealso marker="#topsort">topological ordering</seealso> of the
+ a <seeerl marker="#topsort">topological ordering</seeerl> of the
created digraph always exists.</p>
</desc>
</func>
@@ -205,15 +205,15 @@
<name name="cyclic_strong_components" arity="1" since=""/>
<fsummary>Return the cyclic strong components of a digraph.</fsummary>
<desc>
- <p>Returns a list of <seealso marker="#strong_components">strongly
- connected components</seealso>. Each strongly component is represented
+ <p>Returns a list of <seeerl marker="#strong_components">strongly
+ connected components</seeerl>. Each strongly component is represented
by its vertices. The order of the vertices and the order of
the components are arbitrary. Only vertices that are
- included in some <seealso marker="#cycle">cycle</seealso> in
+ included in some <seeerl marker="#cycle">cycle</seeerl> in
<c><anno>Digraph</anno></c> are returned, otherwise the returned
list is equal to that returned by
- <seealso marker="#strong_components/1">
- <c>strong_components/1</c></seealso>.</p>
+ <seemfa marker="#strong_components/1">
+ <c>strong_components/1</c></seemfa>.</p>
</desc>
</func>
@@ -223,7 +223,7 @@
<desc>
<p>Returns <c>true</c> if and only if digraph
<c><anno>Digraph</anno></c> is
- <seealso marker="#acyclic_digraph">acyclic</seealso>.</p>
+ <seeerl marker="#acyclic_digraph">acyclic</seeerl>.</p>
</desc>
</func>
@@ -233,7 +233,7 @@
<desc>
<p>Returns <c>true</c> if and only if digraph
<c><anno>Digraph</anno></c> is
- an <seealso marker="#arborescence">arborescence</seealso>.</p>
+ an <seeerl marker="#arborescence">arborescence</seeerl>.</p>
</desc>
</func>
@@ -243,7 +243,7 @@
<desc>
<p>Returns <c>true</c> if and only if digraph
<c><anno>Digraph</anno></c> is
- a <seealso marker="#tree">tree</seealso>.</p>
+ a <seeerl marker="#tree">tree</seeerl>.</p>
</desc>
</func>
@@ -253,7 +253,7 @@
</fsummary>
<desc>
<p>Returns a list of all vertices of <c><anno>Digraph</anno></c> that
- are included in some <seealso marker="#loop">loop</seealso>.</p>
+ are included in some <seeerl marker="#loop">loop</seeerl>.</p>
</desc>
</func>
@@ -263,8 +263,8 @@
<desc>
<p>Returns all vertices of digraph <c><anno>Digraph</anno></c>.
The order is given by
- a <seealso marker="#depth_first_traversal">depth-first
- traversal</seealso> of the digraph, collecting visited
+ a <seeerl marker="#depth_first_traversal">depth-first
+ traversal</seeerl> of the digraph, collecting visited
vertices in postorder. More precisely, the vertices visited
while searching from an arbitrarily chosen vertex are
collected in postorder, and all those collected vertices are
@@ -278,8 +278,8 @@
<desc>
<p>Returns all vertices of digraph <c><anno>Digraph</anno></c>.
The order is given by
- a <seealso marker="#depth_first_traversal">depth-first
- traversal</seealso> of the digraph, collecting visited
+ a <seeerl marker="#depth_first_traversal">depth-first
+ traversal</seeerl> of the digraph, collecting visited
vertices in preorder.</p>
</desc>
</func>
@@ -291,7 +291,7 @@
<desc>
<p>Returns an unsorted list of digraph vertices such that for
each vertex in the list, there is a
- <seealso marker="#path">path</seealso> in <c><anno>Digraph</anno></c>
+ <seeerl marker="#path">path</seeerl> in <c><anno>Digraph</anno></c>
from some
vertex of <c><anno>Vertices</anno></c> to the vertex. In particular,
as paths can have length zero, the vertices of
@@ -306,12 +306,12 @@
<desc>
<p>Returns an unsorted list of digraph vertices such that for
each vertex in the list, there is a
- <seealso marker="#path">path</seealso> in <c><anno>Digraph</anno></c>
+ <seeerl marker="#path">path</seeerl> in <c><anno>Digraph</anno></c>
of length
one or more from some vertex of <c><anno>Vertices</anno></c> to the
vertex. As a consequence, only those vertices
of <c><anno>Vertices</anno></c> that are included in
- some <seealso marker="#cycle">cycle</seealso> are returned.</p>
+ some <seeerl marker="#cycle">cycle</seeerl> are returned.</p>
</desc>
</func>
@@ -322,7 +322,7 @@
<desc>
<p>Returns an unsorted list of digraph vertices such that for
each vertex in the list, there is
- a <seealso marker="#path">path</seealso> from the vertex to some
+ a <seeerl marker="#path">path</seeerl> from the vertex to some
vertex of <c><anno>Vertices</anno></c>. In particular, as paths
can have length zero, the vertices of <c><anno>Vertices</anno></c>
are included in the returned list.</p>
@@ -336,11 +336,11 @@
<desc>
<p>Returns an unsorted list of digraph vertices such that for
each vertex in the list, there is
- a <seealso marker="#path">path</seealso> of length one or more
+ a <seeerl marker="#path">path</seeerl> of length one or more
from the vertex to some vertex of <c><anno>Vertices</anno></c>.
Therefore only those vertices of <c><anno>Vertices</anno></c>
that are included
- in some <seealso marker="#cycle">cycle</seealso> are returned.</p>
+ in some <seeerl marker="#cycle">cycle</seeerl> are returned.</p>
</desc>
</func>
@@ -348,8 +348,8 @@
<name name="strong_components" arity="1" since=""/>
<fsummary>Return the strong components of a digraph.</fsummary>
<desc>
- <p>Returns a list of <seealso marker="#strong_components">strongly
- connected components</seealso>.
+ <p>Returns a list of <seeerl marker="#strong_components">strongly
+ connected components</seeerl>.
Each strongly component is represented
by its vertices. The order of the vertices and the order of
the components are arbitrary. Each vertex of digraph
@@ -363,7 +363,7 @@
<name name="subgraph" arity="3" since=""/>
<fsummary>Return a subgraph of a digraph.</fsummary>
<desc>
- <p>Creates a maximal <seealso marker="#subgraph">subgraph</seealso>
+ <p>Creates a maximal <seeerl marker="#subgraph">subgraph</seeerl>
of <c>Digraph</c> having
as vertices those vertices of <c><anno>Digraph</anno></c> that are
mentioned in <c><anno>Vertices</anno></c>.</p>
@@ -371,10 +371,10 @@
the default, the type of <c><anno>Digraph</anno></c> is used
for the subgraph as well. Otherwise the option value of <c>type</c>
is used as argument to
- <seealso marker="digraph#new/1"><c>digraph:new/1</c></seealso>.</p>
+ <seemfa marker="digraph#new/1"><c>digraph:new/1</c></seemfa>.</p>
<p>If the value of option <c>keep_labels</c> is <c>true</c>,
which is the default,
- the <seealso marker="#label">labels</seealso> of vertices and edges
+ the <seeerl marker="#label">labels</seeerl> of vertices and edges
of <c><anno>Digraph</anno></c> are used for the subgraph as well. If
the value is <c>false</c>, default label <c>[]</c> is used
for the vertices and edges of the subgroup.</p>
@@ -391,11 +391,11 @@
<fsummary>Return a topological sorting of the vertices of a digraph.
</fsummary>
<desc>
- <p>Returns a <seealso marker="#topsort">topological
- ordering</seealso> of the vertices of digraph
+ <p>Returns a <seeerl marker="#topsort">topological
+ ordering</seeerl> of the vertices of digraph
<c><anno>Digraph</anno></c> if such an ordering exists, otherwise
<c>false</c>. For each vertex in the returned list,
- no <seealso marker="#out_neighbour">out-neighbors</seealso>
+ no <seeerl marker="#out_neighbour">out-neighbors</seeerl>
occur earlier in the list.</p>
</desc>
</func>
@@ -403,7 +403,7 @@
<section>
<title>See Also</title>
- <p><seealso marker="digraph"><c>digraph(3)</c></seealso></p>
+ <p><seeerl marker="digraph"><c>digraph(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/epp.xml b/lib/stdlib/doc/src/epp.xml
index 2244b02908..29d3a488c8 100644
--- a/lib/stdlib/doc/src/epp.xml
+++ b/lib/stdlib/doc/src/epp.xml
@@ -36,7 +36,7 @@
<modulesummary>An Erlang code preprocessor.</modulesummary>
<description>
<p>The Erlang code preprocessor includes functions that are used by the
- <seealso marker="compiler:compile"><c>compile</c></seealso>
+ <seeerl marker="compiler:compile"><c>compile</c></seeerl>
module to preprocess macros and include files before
the parsing takes place.</p>
@@ -98,10 +98,10 @@
<desc>
<p>Returns a string representation of an encoding. The string
is recognized by
- <seealso marker="#read_encoding/1"><c>read_encoding/1,2</c></seealso>,
- <seealso marker="#read_encoding_from_binary/1">
- <c>read_encoding_from_binary/1,2</c></seealso>, and
- <seealso marker="#set_encoding/1"><c>set_encoding/1,2</c></seealso>
+ <seemfa marker="#read_encoding/1"><c>read_encoding/1,2</c></seemfa>,
+ <seemfa marker="#read_encoding_from_binary/1">
+ <c>read_encoding_from_binary/1,2</c></seemfa>, and
+ <seemfa marker="#set_encoding/1"><c>set_encoding/1,2</c></seemfa>
as a valid encoding.</p>
</desc>
</func>
@@ -115,7 +115,7 @@
describes the error or warning. This function is usually
called implicitly when processing an <c>ErrorInfo</c>
structure (see section
- <seealso marker="#errorinfo">Error Information</seealso>).</p>
+ <seeerl marker="#errorinfo">Error Information</seeerl>).</p>
</desc>
</func>
@@ -198,7 +198,7 @@
<name name="read_encoding" arity="2" since="OTP R16B"/>
<fsummary>Read the encoding from a file.</fsummary>
<desc>
- <p>Read the <seealso marker="#encoding">encoding</seealso> from
+ <p>Read the <seeerl marker="#encoding">encoding</seeerl> from
a file. Returns the read encoding, or <c>none</c> if no
valid encoding is found.</p>
<p>Option <c>in_comment_only</c> is <c>true</c> by
@@ -213,7 +213,7 @@
<name name="read_encoding_from_binary" arity="2" since="OTP R16B"/>
<fsummary>Read the encoding from a binary.</fsummary>
<desc>
- <p>Read the <seealso marker="#encoding">encoding</seealso> from
+ <p>Read the <seeerl marker="#encoding">encoding</seeerl> from
a binary. Returns the read encoding, or <c>none</c> if no
valid encoding is found.</p>
<p>Option <c>in_comment_only</c> is <c>true</c> by
@@ -227,7 +227,7 @@
<name name="set_encoding" arity="1" since="OTP R16B"/>
<fsummary>Read and set the encoding of an I/O device.</fsummary>
<desc>
- <p>Reads the <seealso marker="#encoding">encoding</seealso> from
+ <p>Reads the <seeerl marker="#encoding">encoding</seeerl> from
an I/O device and sets the encoding of the device
accordingly. The position of the I/O device referenced by
<c><anno>File</anno></c> is not affected. If no valid
@@ -242,13 +242,13 @@
<name name="set_encoding" arity="2" since="OTP 17.0"/>
<fsummary>Read and set the encoding of an I/O device.</fsummary>
<desc>
- <p>Reads the <seealso marker="#encoding">encoding</seealso> from
+ <p>Reads the <seeerl marker="#encoding">encoding</seeerl> from
an I/O device and sets the encoding of the device
accordingly. The position of the I/O device referenced by
<c><anno>File</anno></c> is not affected. If no valid
encoding can be read from the I/O device, the encoding of the
I/O device is set to the
- <seealso marker="#encoding">encoding</seealso> specified by
+ <seeerl marker="#encoding">encoding</seeerl> specified by
<c><anno>Default</anno></c>.</p>
<p>Returns the read encoding, or <c>none</c> if no valid
encoding is found.</p>
@@ -270,7 +270,7 @@ Module:format_error(ErrorDescriptor)</code>
<section>
<title>See Also</title>
- <p><seealso marker="erl_parse"><c>erl_parse(3)</c></seealso></p>
+ <p><seeerl marker="erl_parse"><c>erl_parse(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/erl_anno.xml b/lib/stdlib/doc/src/erl_anno.xml
index 8026aa827b..6c3f0eef4b 100644
--- a/lib/stdlib/doc/src/erl_anno.xml
+++ b/lib/stdlib/doc/src/erl_anno.xml
@@ -78,30 +78,30 @@
<tag><c>record</c></tag>
<item><p>A Boolean indicating if the origin of the abstract
code is a record. Used by
- <seealso marker="dialyzer:dialyzer">Dialyzer</seealso>
+ <seeerl marker="dialyzer:dialyzer">Dialyzer</seeerl>
to assign types to tuple elements.</p>
</item>
</taglist>
<p>The functions
- <seealso marker="erl_scan#column/1"><c>column()</c></seealso>,
- <seealso marker="erl_scan#end_location/1"><c>end_location()</c></seealso>,
- <seealso marker="erl_scan#line/1"><c>line()</c></seealso>,
- <seealso marker="erl_scan#location/1"><c>location()</c></seealso>, and
- <seealso marker="erl_scan#text/1"><c>text()</c></seealso>
+ <seemfa marker="erl_scan#column/1"><c>column()</c></seemfa>,
+ <seemfa marker="erl_scan#end_location/1"><c>end_location()</c></seemfa>,
+ <seemfa marker="erl_scan#line/1"><c>line()</c></seemfa>,
+ <seemfa marker="erl_scan#location/1"><c>location()</c></seemfa>, and
+ <seemfa marker="erl_scan#text/1"><c>text()</c></seemfa>
in the <c>erl_scan</c> module can be used for inspecting
annotations in tokens.</p>
<p>The functions
- <seealso marker="erl_parse#anno_from_term/1">
- <c>anno_from_term()</c></seealso>,
- <seealso marker="erl_parse#anno_to_term/1">
- <c>anno_to_term()</c></seealso>,
- <seealso marker="erl_parse#fold_anno/3"><c>fold_anno()</c></seealso>,
- <seealso marker="erl_parse#map_anno/2"><c>map_anno()</c></seealso>,
- <seealso marker="erl_parse#mapfold_anno/3">
- <c>mapfold_anno()</c></seealso>,
- and <seealso marker="erl_parse#new_anno/1"><c>new_anno()</c></seealso>,
+ <seemfa marker="erl_parse#anno_from_term/1">
+ <c>anno_from_term()</c></seemfa>,
+ <seemfa marker="erl_parse#anno_to_term/1">
+ <c>anno_to_term()</c></seemfa>,
+ <seemfa marker="erl_parse#fold_anno/3"><c>fold_anno()</c></seemfa>,
+ <seemfa marker="erl_parse#map_anno/2"><c>map_anno()</c></seemfa>,
+ <seemfa marker="erl_parse#mapfold_anno/3">
+ <c>mapfold_anno()</c></seemfa>,
+ and <seemfa marker="erl_parse#new_anno/1"><c>new_anno()</c></seemfa>,
in the <c>erl_parse</c> module can be
used for manipulating annotations in abstract code.</p>
</description>
@@ -169,7 +169,7 @@
<fsummary>Return annotations given a term.</fsummary>
<desc>
<p>Returns annotations with representation <anno>Term</anno>.</p>
- <p>See also <seealso marker="#to_term/1">to_term()</seealso>.</p>
+ <p>See also <seemfa marker="#to_term/1">to_term()</seemfa>.</p>
</desc>
</func>
@@ -291,14 +291,14 @@
</fsummary>
<desc>
<p>Returns the term representing the annotations <anno>Anno</anno>.</p>
- <p>See also <seealso marker="#from_term/1">from_term()</seealso>.</p>
+ <p>See also <seemfa marker="#from_term/1">from_term()</seemfa>.</p>
</desc>
</func>
</funcs>
<section>
<title>See Also</title>
- <p><seealso marker="erl_parse"><c>erl_parse(3)</c></seealso>,
- <seealso marker="erl_scan"><c>erl_scan(3)</c></seealso></p>
+ <p><seeerl marker="erl_parse"><c>erl_parse(3)</c></seeerl>,
+ <seeerl marker="erl_scan"><c>erl_scan(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/erl_eval.xml b/lib/stdlib/doc/src/erl_eval.xml
index a35b1b34dd..e54dcdbdc9 100644
--- a/lib/stdlib/doc/src/erl_eval.xml
+++ b/lib/stdlib/doc/src/erl_eval.xml
@@ -37,8 +37,8 @@
<description>
<p>This module provides an interpreter for Erlang expressions. The
expressions are in the abstract syntax as returned by
- <seealso marker="erl_parse"><c>erl_parse</c></seealso>,
- the Erlang parser, or <seealso marker="io"><c>io</c></seealso>.</p>
+ <seeerl marker="erl_parse"><c>erl_parse</c></seeerl>,
+ the Erlang parser, or <seeerl marker="io"><c>io</c></seeerl>.</p>
</description>
<datatypes>
@@ -54,10 +54,10 @@
</datatype>
<datatype>
<name name="expressions"/>
- <desc><p>As returned by <seealso marker="erl_parse#parse_exprs/1">
- <c>erl_parse:parse_exprs/1</c></seealso> or
- <seealso marker="io#parse_erl_exprs/2">
- <c>io:parse_erl_exprs/2</c></seealso>.</p></desc>
+ <desc><p>As returned by <seemfa marker="erl_parse#parse_exprs/1">
+ <c>erl_parse:parse_exprs/1</c></seemfa> or
+ <seemfa marker="io#parse_erl_exprs/2">
+ <c>io:parse_erl_exprs/2</c></seemfa>.</p></desc>
</datatype>
<datatype>
<name name="expression_list"/>
@@ -74,8 +74,8 @@
<datatype>
<name name="local_function_handler"/>
<desc><p>Further described in section
- <seealso marker="#local_function_handler">
- Local Function Handler</seealso> in this module</p></desc>
+ <seeerl marker="#local_function_handler">
+ Local Function Handler</seeerl> in this module</p></desc>
</datatype>
<datatype>
<name name="name"/>
@@ -86,8 +86,8 @@
<datatype>
<name name="non_local_function_handler"/>
<desc><p>Further described in section
- <seealso marker="#non_local_function_handler">
- Non-Local Function Handler</seealso> in this module.</p></desc>
+ <seeerl marker="#non_local_function_handler">
+ Non-Local Function Handler</seeerl> in this module.</p></desc>
</datatype>
<datatype>
<name name="value"/>
@@ -146,10 +146,10 @@
For an explanation of when and how to use arguments
<c><anno>LocalFunctionHandler</anno></c> and
<c><anno>NonLocalFunctionHandler</anno></c>, see sections
- <seealso marker="#local_function_handler">
- Local Function Handler</seealso> and
- <seealso marker="#non_local_function_handler">
- Non-Local Function Handler</seealso> in this module.</p>
+ <seeerl marker="#local_function_handler">
+ Local Function Handler</seeerl> and
+ <seeerl marker="#non_local_function_handler">
+ Non-Local Function Handler</seeerl> in this module.</p>
<p>Returns <c>{value, <anno>Value</anno>, <anno>NewBindings</anno>}</c>
by default. If <c><anno>ReturnFormat</anno></c> is <c>value</c>,
only <c><anno>Value</anno></c> is returned.</p>
@@ -166,8 +166,8 @@
initial bindings for each expression. Attempts are made to
merge the bindings returned from each evaluation. This
function is useful in <c>LocalFunctionHandler</c>, see section
- <seealso marker="#local_function_handler">
- Local Function Handler</seealso> in this module.</p>
+ <seeerl marker="#local_function_handler">
+ Local Function Handler</seeerl> in this module.</p>
<p>Returns <c>{<anno>ValueList</anno>, <anno>NewBindings</anno>}</c>.
</p>
</desc>
@@ -182,15 +182,15 @@
<p>Evaluates <c><anno>Expressions</anno></c> with the set of bindings
<c><anno>Bindings</anno></c>, where <c><anno>Expressions</anno></c>
is a sequence of expressions (in abstract syntax) of a type that can
- be returned by <seealso marker="io#parse_erl_exprs/2">
- <c>io:parse_erl_exprs/2</c></seealso>.
+ be returned by <seemfa marker="io#parse_erl_exprs/2">
+ <c>io:parse_erl_exprs/2</c></seemfa>.
For an explanation of when and how to use arguments
<c><anno>LocalFunctionHandler</anno></c> and
<c><anno>NonLocalFunctionHandler</anno></c>, see sections
- <seealso marker="#local_function_handler">
- Local Function Handler</seealso> and
- <seealso marker="#non_local_function_handler">
- Non-Local Function Handler</seealso> in this module.</p>
+ <seeerl marker="#local_function_handler">
+ Local Function Handler</seeerl> and
+ <seeerl marker="#non_local_function_handler">
+ Non-Local Function Handler</seeerl> in this module.</p>
<p>Returns <c>{value, <anno>Value</anno>, <anno>NewBindings</anno>}</c>
</p>
</desc>
diff --git a/lib/stdlib/doc/src/erl_expand_records.xml b/lib/stdlib/doc/src/erl_expand_records.xml
index 0980af94b1..a7f273158c 100644
--- a/lib/stdlib/doc/src/erl_expand_records.xml
+++ b/lib/stdlib/doc/src/erl_expand_records.xml
@@ -55,7 +55,7 @@
<section>
<title>See Also</title>
- <p>Section <seealso marker="erts:absform">The Abstract Format</seealso>
+ <p>Section <seeguide marker="erts:absform">The Abstract Format</seeguide>
in ERTS User's Guide.</p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/erl_id_trans.xml b/lib/stdlib/doc/src/erl_id_trans.xml
index 686f3148a4..337cb6215f 100644
--- a/lib/stdlib/doc/src/erl_id_trans.xml
+++ b/lib/stdlib/doc/src/erl_id_trans.xml
@@ -49,9 +49,9 @@
<name since="">parse_transform(Forms, Options) -> Forms</name>
<fsummary>Transform Erlang forms.</fsummary>
<type>
- <v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso>
- | <seealso marker="erl_parse#type-form_info">erl_parse:form_info()</seealso>]</v>
- <v>Options = [<seealso marker="compile#type-option">compile:option()</seealso>]</v>
+ <v>Forms = [<seetype marker="erl_parse#abstract_form">erl_parse:abstract_form()</seetype>
+ | <seetype marker="erl_parse#form_info">erl_parse:form_info()</seetype>]</v>
+ <v>Options = [<seetype marker="compile#option">compile:option()</seetype>]</v>
</type>
<desc>
<p>Performs an identity transformation on Erlang forms, as an example.
@@ -73,8 +73,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="erl_parse"><c>erl_parse(3)</c></seealso>,
- <seealso marker="compiler:compile"><c>compile(3)</c></seealso></p>
+ <p><seeerl marker="erl_parse"><c>erl_parse(3)</c></seeerl>,
+ <seeerl marker="compiler:compile"><c>compile(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/erl_lint.xml b/lib/stdlib/doc/src/erl_lint.xml
index 6c8102c37a..bd33a00a48 100644
--- a/lib/stdlib/doc/src/erl_lint.xml
+++ b/lib/stdlib/doc/src/erl_lint.xml
@@ -85,7 +85,7 @@
that describes the error or warning. This function is usually
called implicitly when processing an <c>ErrorInfo</c> structure
(see section
- <seealso marker="#errorinfo">Error Information</seealso>).</p>
+ <seeerl marker="#errorinfo">Error Information</seeerl>).</p>
</desc>
</func>
@@ -95,8 +95,8 @@
<desc>
<p>Tests if <c><anno>Expr</anno></c> is a legal guard test.
<c><anno>Expr</anno></c> is an Erlang term representing the abstract
- form for the expression. <seealso marker="erl_parse#parse_exprs/1">
- <c>erl_parse:parse_exprs(Tokens)</c></seealso>
+ form for the expression. <seemfa marker="erl_parse#parse_exprs/1">
+ <c>erl_parse:parse_exprs(Tokens)</c></seemfa>
can be used to generate a list of <c><anno>Expr</anno></c>.</p>
</desc>
</func>
@@ -122,14 +122,14 @@
compiler, and to avoid the same description in two places, the
elements of <c>Options</c> that control the warnings are
only described in the
- <seealso marker="compiler:compile#erl_lint_options">
- <c>compile(3)</c></seealso> module.</p>
+ <seeerl marker="compiler:compile#erl_lint_options">
+ <c>compile(3)</c></seeerl> module.</p>
<p><c><anno>AbsForms</anno></c> of a module, which comes from a file
that is read through <c>epp</c>, the Erlang preprocessor, can come
from many files. This means that any references to errors must
- include the filename, see the <seealso marker="epp">
- <c>epp(3)</c></seealso> module or parser (see the
- <seealso marker="erl_parse"><c>erl_parse(3)</c></seealso> module).
+ include the filename, see the <seeerl marker="epp">
+ <c>epp(3)</c></seeerl> module or parser (see the
+ <seeerl marker="erl_parse"><c>erl_parse(3)</c></seeerl> module).
The returned errors and warnings have the following format:</p>
<code type="none">
[{<anno>FileName2</anno>,[<anno>ErrorInfo</anno>]}]</code>
@@ -154,8 +154,8 @@ Module:format_error(ErrorDescriptor)</code>
<section>
<title>See Also</title>
- <p><seealso marker="epp"><c>epp(3)</c></seealso>,
- <seealso marker="erl_parse"><c>erl_parse(3)</c></seealso></p>
+ <p><seeerl marker="epp"><c>epp(3)</c></seeerl>,
+ <seeerl marker="erl_parse"><c>erl_parse(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/erl_parse.xml b/lib/stdlib/doc/src/erl_parse.xml
index 9bbf3adb49..25f5d4d905 100644
--- a/lib/stdlib/doc/src/erl_parse.xml
+++ b/lib/stdlib/doc/src/erl_parse.xml
@@ -38,10 +38,10 @@
<p>This module is the basic Erlang parser that converts tokens into
the abstract form of either forms (that is, top-level constructs),
expressions, or terms. The Abstract Format is described in the
- <seealso marker="erts:absform">ERTS User's Guide</seealso>.
+ <seeguide marker="erts:absform">ERTS User's Guide</seeguide>.
Notice that a token list must end with the <em>dot</em> token to be
- acceptable to the parse functions (see the <seealso marker="erl_scan">
- <c>erl_scan(3)</c></seealso>) module.</p>
+ acceptable to the parse functions (see the <seeerl marker="erl_scan">
+ <c>erl_scan(3)</c></seeerl>) module.</p>
</description>
<datatypes>
@@ -69,6 +69,31 @@
<name name="erl_parse_tree"></name>
</datatype>
<datatype>
+ <name>af_binelement(_)</name>
+ <desc>
+ <p>Abstract representation of an element of a bitstring.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name>af_field_decl()</name>
+ <desc>
+ <p>Abstract representation of a record field.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name>af_generator()</name>
+ <desc>
+ <p>Abstract representation of a generator
+ or a bitstring generator.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name>af_remote_function()></name>
+ <desc>
+ <p>Abstract representation of a remote function call.</p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="error_description"></name>
</datatype>
<datatype>
@@ -95,7 +120,7 @@
<p>Converts the Erlang data structure <c><anno>Data</anno></c> into an
abstract form of type <c><anno>AbsTerm</anno></c>.
This function is the inverse of
- <seealso marker="#normalise/1"><c>normalise/1</c></seealso>.</p>
+ <seemfa marker="#normalise/1"><c>normalise/1</c></seemfa>.</p>
<p><c>erl_parse:abstract(T)</c> is equivalent to
<c>erl_parse:abstract(T, 0)</c>.</p>
</desc>
@@ -113,8 +138,8 @@
<p>Option <c><anno>Encoding</anno></c> is used for
selecting which integer lists to be considered
as strings. The default is to use the encoding returned by
- function <seealso marker="epp#default_encoding/0">
- <c>epp:default_encoding/0</c></seealso>.
+ function <seemfa marker="epp#default_encoding/0">
+ <c>epp:default_encoding/0</c></seemfa>.
Value <c>none</c> means that no integer lists are
considered as strings. <c>encoding_func()</c> is
called with one integer of a list at a time; if it
@@ -132,8 +157,8 @@
say <c>T</c>, where a <c>erl_parse</c> tree has collections
of annotations. Returns a <c>erl_parse</c> tree where each
term <c>T</c> is replaced by the value returned by
- <seealso marker="erl_anno#from_term/1">
- <c>erl_anno:from_term(T)</c></seealso>. The term
+ <seemfa marker="erl_anno#from_term/1">
+ <c>erl_anno:from_term(T)</c></seemfa>. The term
<c><anno>Term</anno></c> is traversed in a depth-first,
left-to-right fashion.</p>
</desc>
@@ -146,8 +171,8 @@
<p>Returns a term where each collection of annotations
<c>Anno</c> of the nodes of the <c>erl_parse</c> tree
<c><anno>Abstr</anno></c> is replaced by the term
- returned by <seealso marker="erl_anno#to_term/1">
- <c>erl_anno:to_term(Anno)</c></seealso>. The
+ returned by <seemfa marker="erl_anno#to_term/1">
+ <c>erl_anno:to_term(Anno)</c></seemfa>. The
<c>erl_parse</c> tree is traversed in a depth-first,
left-to-right fashion.</p>
</desc>
@@ -174,16 +199,16 @@
<name since="">format_error(ErrorDescriptor) -> Chars</name>
<fsummary>Format an error descriptor.</fsummary>
<type>
- <v>ErrorDescriptor = <seealso
- marker="#type-error_info">error_description()</seealso></v>
+ <v>ErrorDescriptor = <seetype
+ marker="#error_info">error_description()</seetype></v>
<v>Chars = [char() | Chars]</v>
</type>
<desc>
<p>Uses an <c>ErrorDescriptor</c> and returns a string
that describes the error. This function is usually called
implicitly when an <c>ErrorInfo</c> structure is processed
- (see section <seealso marker="#errorinfo">
- Error Information</seealso>).</p>
+ (see section <seeerl marker="#errorinfo">
+ Error Information</seeerl>).</p>
</desc>
</func>
@@ -224,12 +249,12 @@
<fsummary>Create new annotations.</fsummary>
<desc>
<p>Assumes that <c><anno>Term</anno></c> is a term with the same
- structure as a <c>erl_parse</c> tree, but with <seealso
- marker="erl_anno#type-location">locations</seealso> where a
+ structure as a <c>erl_parse</c> tree, but with <seetype
+ marker="erl_anno#location">locations</seetype> where a
<c>erl_parse</c> tree has collections of annotations.
Returns a <c>erl_parse</c> tree where each location <c>L</c>
- is replaced by the value returned by <seealso
- marker="erl_anno#new/1"><c>erl_anno:new(L)</c></seealso>.
+ is replaced by the value returned by <seemfa
+ marker="erl_anno#new/1"><c>erl_anno:new(L)</c></seemfa>.
The term <c><anno>Term</anno></c> is traversed in a
depth-first, left-to-right fashion.</p>
</desc>
@@ -242,7 +267,7 @@
<p>Converts the abstract form <c><anno>AbsTerm</anno></c> of a
term into a conventional Erlang data structure (that is, the
term itself). This function is the inverse of
- <seealso marker="#abstract/1"><c>abstract/1</c></seealso>.</p>
+ <seemfa marker="#abstract/1"><c>abstract/1</c></seemfa>.</p>
</desc>
</func>
@@ -332,10 +357,10 @@ Module:format_error(ErrorDescriptor)</code>
<section>
<title>See Also</title>
- <p><seealso marker="erl_anno"><c>erl_anno(3)</c></seealso>,
- <seealso marker="erl_scan"><c>erl_scan(3)</c></seealso>,
- <seealso marker="io"><c>io(3)</c></seealso>,
- section <seealso marker="erts:absform">The Abstract Format</seealso>
+ <p><seeerl marker="erl_anno"><c>erl_anno(3)</c></seeerl>,
+ <seeerl marker="erl_scan"><c>erl_scan(3)</c></seeerl>,
+ <seeerl marker="io"><c>io(3)</c></seeerl>,
+ section <seeguide marker="erts:absform">The Abstract Format</seeguide>
in the ERTS User's Guide</p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/erl_pp.xml b/lib/stdlib/doc/src/erl_pp.xml
index 0a46139db6..0600881fbe 100644
--- a/lib/stdlib/doc/src/erl_pp.xml
+++ b/lib/stdlib/doc/src/erl_pp.xml
@@ -57,7 +57,7 @@
is to be a valid expression. If <c>HookFunction</c> is equal to
<c>none</c>, there is no hook function.</p>
<p>The called hook function is to return a (possibly deep) list of
- characters. Function <seealso marker="#expr/4"><c>expr/4</c></seealso>
+ characters. Function <seemfa marker="#expr/4"><c>expr/4</c></seemfa>
is useful in a hook.</p>
<p>If <c><anno>CurrentIndentation</anno></c> is negative, there are no
line breaks and only a space is used as a separator.</p>
@@ -68,6 +68,10 @@
<desc>
<p>The option <c>quote_singleton_atom_types</c>
is used to add quotes to all singleton atom types.</p>
+ <p>The option <c>linewidth</c> controls the maximum line
+ width for formatted lines (defaults to 72 characters).</p>
+ <p>The option <c>indent</c> controls the
+ indention for formatted lines (defaults to 4 spaces).</p>
</desc>
</datatype>
<datatype>
@@ -81,7 +85,7 @@
<name name="attribute" arity="2" since=""/>
<fsummary>Pretty print an attribute.</fsummary>
<desc>
- <p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
+ <p>Same as <seemfa marker="#form/1"><c>form/1,2</c></seemfa>,
but only for attribute <c><anno>Attribute</anno></c>.</p>
</desc>
</func>
@@ -95,7 +99,7 @@
<desc>
<p>Prints one expression. It is useful for implementing hooks (see
section
- <seealso marker="#knownlimitations">Known Limitations</seealso>).</p>
+ <seeerl marker="#knownlimitations">Known Limitations</seeerl>).</p>
</desc>
</func>
@@ -105,7 +109,7 @@
<name name="exprs" arity="3" since=""/>
<fsummary>Pretty print <c>Expressions</c>.</fsummary>
<desc>
- <p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
+ <p>Same as <seemfa marker="#form/1"><c>form/1,2</c></seemfa>,
but only for the sequence of
expressions in <c><anno>Expressions</anno></c>.</p>
</desc>
@@ -118,8 +122,8 @@
<desc>
<p>Pretty prints a
<c><anno>Form</anno></c>, which is an abstract form of a type that is
- returned by <seealso marker="erl_parse#parse_form/1">
- <c>erl_parse:parse_form/1</c></seealso>.</p>
+ returned by <seemfa marker="erl_parse#parse_form/1">
+ <c>erl_parse:parse_form/1</c></seemfa>.</p>
</desc>
</func>
@@ -128,7 +132,7 @@
<name name="function" arity="2" since=""/>
<fsummary>Pretty print a function.</fsummary>
<desc>
- <p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
+ <p>Same as <seemfa marker="#form/1"><c>form/1,2</c></seemfa>,
but only for function <c><anno>Function</anno></c>.</p>
</desc>
</func>
@@ -138,7 +142,7 @@
<name name="guard" arity="2" since=""/>
<fsummary>Pretty print a guard.</fsummary>
<desc>
- <p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
+ <p>Same as <seemfa marker="#form/1"><c>form/1,2</c></seemfa>,
but only for the guard test <c><anno>Guard</anno></c>.</p>
</desc>
</func>
@@ -153,9 +157,9 @@
<section>
<title>See Also</title>
- <p><seealso marker="erl_eval"><c>erl_eval(3)</c></seealso>,
- <seealso marker="erl_parse"><c>erl_parse(3)</c></seealso>,
- <seealso marker="io"><c>io(3)</c></seealso></p>
+ <p><seeerl marker="erl_eval"><c>erl_eval(3)</c></seeerl>,
+ <seeerl marker="erl_parse"><c>erl_parse(3)</c></seeerl>,
+ <seeerl marker="io"><c>io(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/erl_scan.xml b/lib/stdlib/doc/src/erl_scan.xml
index 92c4d5627c..4cfad284e7 100644
--- a/lib/stdlib/doc/src/erl_scan.xml
+++ b/lib/stdlib/doc/src/erl_scan.xml
@@ -108,7 +108,7 @@
that describes the error or warning. This function is usually
called implicitly when an <c>ErrorInfo</c> structure is
processed (see section
- <seealso marker="#errorinfo">Error Information</seealso>).</p>
+ <seeerl marker="#errorinfo">Error Information</seeerl>).</p>
</desc>
</func>
@@ -180,10 +180,10 @@
line where the token begins, as well as the text of the
token (if option <c>text</c> is specified), all of which can
be accessed by calling
- <seealso marker="#column/1"><c>column/1</c></seealso>,
- <seealso marker="#line/1"><c>line/1</c></seealso>,
- <seealso marker="#location/1"><c>location/1</c></seealso>, and
- <seealso marker="#text/1"><c>text/1</c></seealso>.</p>
+ <seemfa marker="#column/1"><c>column/1</c></seemfa>,
+ <seemfa marker="#line/1"><c>line/1</c></seemfa>,
+ <seemfa marker="#location/1"><c>location/1</c></seemfa>, and
+ <seemfa marker="#text/1"><c>text/1</c></seemfa>.</p>
<p>A <em>token</em> is a tuple containing information about
syntactic category, the token annotations, and the
terminal symbol. For punctuation characters (such as <c>;</c> and
@@ -296,7 +296,7 @@
<c>tokens(<anno>Continuation</anno>, <anno>CharSpec</anno>,
<anno>StartLocation</anno>, [])</c>.</p>
<p>For a description of the options, see
- <seealso marker="#string/3"><c>string/3</c></seealso>.</p>
+ <seemfa marker="#string/3"><c>string/3</c></seemfa>.</p>
</desc>
</func>
</funcs>
@@ -323,8 +323,8 @@ Module:format_error(ErrorDescriptor)</code>
<section>
<title>See Also</title>
- <p><seealso marker="erl_anno"><c>erl_anno(3)</c></seealso>,
- <seealso marker="erl_parse"><c>erl_parse(3)</c></seealso>,
- <seealso marker="io"><c>io(3)</c></seealso></p>
+ <p><seeerl marker="erl_anno"><c>erl_anno(3)</c></seeerl>,
+ <seeerl marker="erl_parse"><c>erl_parse(3)</c></seeerl>,
+ <seeerl marker="io"><c>io(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/erl_tar.xml b/lib/stdlib/doc/src/erl_tar.xml
index be55dc0777..ace73372c2 100644
--- a/lib/stdlib/doc/src/erl_tar.xml
+++ b/lib/stdlib/doc/src/erl_tar.xml
@@ -48,41 +48,41 @@
To abide to the convention, add "<c>.tar</c>" to the name.</p>
<p>Tar files can be created in one operation using function
- <seealso marker="#create/2"><c>create/2</c></seealso> or
- <seealso marker="#create/3"><c>create/3</c></seealso>.</p>
+ <seemfa marker="#create/2"><c>create/2</c></seemfa> or
+ <seemfa marker="#create/3"><c>create/3</c></seemfa>.</p>
<p>Alternatively, for more control, use functions
- <seealso marker="#open/2"><c>open/2</c></seealso>,
- <seealso marker="#add/3"><c>add/3,4</c></seealso>, and
- <seealso marker="#close/1"><c>close/1</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/2</c></seemfa>,
+ <seemfa marker="#add/3"><c>add/3,4</c></seemfa>, and
+ <seemfa marker="#close/1"><c>close/1</c></seemfa>.</p>
<p>To extract all files from a tar file, use function
- <seealso marker="#extract/1"><c>extract/1</c></seealso>.
+ <seemfa marker="#extract/1"><c>extract/1</c></seemfa>.
To extract only some files or to be able to specify some more options,
- use function <seealso marker="#extract/2"><c>extract/2</c></seealso>.</p>
+ use function <seemfa marker="#extract/2"><c>extract/2</c></seemfa>.</p>
<p>To return a list of the files in a tar file,
- use function <seealso marker="#table/1"><c>table/1</c></seealso> or
- <seealso marker="#table/2"><c>table/2</c></seealso>.
+ use function <seemfa marker="#table/1"><c>table/1</c></seemfa> or
+ <seemfa marker="#table/2"><c>table/2</c></seemfa>.
To print a list of files to the Erlang shell,
- use function <seealso marker="#t/1"><c>t/1</c></seealso> or
- <seealso marker="#tt/1"><c>tt/1</c></seealso>.</p>
+ use function <seemfa marker="#t/1"><c>t/1</c></seemfa> or
+ <seemfa marker="#tt/1"><c>tt/1</c></seemfa>.</p>
<p>To convert an error term returned from one of the functions
above to a readable message, use function
- <seealso marker="#format_error/1"><c>format_error/1</c></seealso>.</p>
+ <seemfa marker="#format_error/1"><c>format_error/1</c></seemfa>.</p>
</description>
<section>
<title>Unicode Support</title>
- <p>If <seealso marker="kernel:file#native_name_encoding/0">
- <c>file:native_name_encoding/0</c></seealso>
+ <p>If <seemfa marker="kernel:file#native_name_encoding/0">
+ <c>file:native_name_encoding/0</c></seemfa>
returns <c>utf8</c>, path names are encoded in UTF-8 when
creating tar files, and path names are assumed to be encoded in
UTF-8 when extracting tar files.</p>
- <p>If <seealso marker="kernel:file#native_name_encoding/0">
- <c>file:native_name_encoding/0</c></seealso>
+ <p>If <seemfa marker="kernel:file#native_name_encoding/0">
+ <c>file:native_name_encoding/0</c></seemfa>
returns <c>latin1</c>, no translation of path names is done.</p>
<p>Unicode metadata stored in PAX headers is preserved</p>
@@ -90,16 +90,16 @@
<section>
<title>Other Storage Media</title>
- <p>The <seealso marker="ftp:ftp"><c>ftp</c></seealso>
+ <p>The <seeerl marker="ftp:ftp"><c>ftp</c></seeerl>
module normally accesses the tar file on disk using
- the <seealso marker="kernel:file"><c>file</c></seealso> module.
+ the <seeerl marker="kernel:file"><c>file</c></seeerl> module.
When other needs arise, you can define your own low-level Erlang
functions to perform the writing and reading on the storage media;
- use function <seealso marker="#init/3"><c>init/3</c></seealso>.</p>
+ use function <seemfa marker="#init/3"><c>init/3</c></seemfa>.</p>
<p>An example of this is the SFTP support in
- <seealso marker="ssh:ssh_sftp#open_tar/3">
- <c>ssh_sftp:open_tar/3</c></seealso>. This function opens a tar file
+ <seemfa marker="ssh:ssh_sftp#open_tar/3">
+ <c>ssh_sftp:open_tar/3</c></seemfa>. This function opens a tar file
on a remote machine using an SFTP channel.</p>
</section>
@@ -146,7 +146,7 @@
<type name="add_opt"/>
<desc>
<p>Adds a file to a tar file that has been opened for writing by
- <seealso marker="#open/2"><c>open/1</c></seealso>.</p>
+ <seemfa marker="#open/2"><c>open/1</c></seemfa>.</p>
<p><c>NameInArchive</c> is the name under which the file becomes
stored in the tar file. The file gets this name when it is
extracted from the tar file.</p>
@@ -168,44 +168,44 @@
<p>Reads data in parts from the file. This is intended for
memory-limited machines that, for example, builds a tar file
on a remote machine over SFTP, see
- <seealso marker="ssh:ssh_sftp#open_tar/3">
- <c>ssh_sftp:open_tar/3</c></seealso>.</p>
+ <seemfa marker="ssh:ssh_sftp#open_tar/3">
+ <c>ssh_sftp:open_tar/3</c></seemfa>.</p>
</item>
<tag><c>{atime,non_neg_integer()}</c></tag>
<item>
<p>Sets the last time, as
- <seealso marker="erts:time_correction#POSIX_Time">
- POSIX time</seealso>, when the file was read. See also
- <seealso marker="kernel:file#read_file_info/1">
- <c>file:read_file_info/1</c></seealso>.</p>
+ <seeguide marker="erts:time_correction#POSIX_Time">
+ POSIX time</seeguide>, when the file was read. See also
+ <seemfa marker="kernel:file#read_file_info/1">
+ <c>file:read_file_info/1</c></seemfa>.</p>
</item>
<tag><c>{mtime,non_neg_integer()}</c></tag>
<item>
<p>Sets the last time, as
- <seealso marker="erts:time_correction#POSIX_Time">
- POSIX time</seealso>, when the file was written. See also
- <seealso marker="kernel:file#read_file_info/1">
- <c>file:read_file_info/1</c></seealso>.</p>
+ <seeguide marker="erts:time_correction#POSIX_Time">
+ POSIX time</seeguide>, when the file was written. See also
+ <seemfa marker="kernel:file#read_file_info/1">
+ <c>file:read_file_info/1</c></seemfa>.</p>
</item>
<tag><c>{ctime,non_neg_integer()}</c></tag>
<item>
<p>Sets the time, as
- <seealso marker="erts:time_correction#POSIX_Time">
- POSIX time</seealso>, when the file was created. See also
- <seealso marker="kernel:file#read_file_info/1">
- <c>file:read_file_info/1</c></seealso>.</p>
+ <seeguide marker="erts:time_correction#POSIX_Time">
+ POSIX time</seeguide>, when the file was created. See also
+ <seemfa marker="kernel:file#read_file_info/1">
+ <c>file:read_file_info/1</c></seemfa>.</p>
</item>
<tag><c>{uid,non_neg_integer()}</c></tag>
<item>
<p>Sets the file owner.
- <seealso marker="kernel:file#read_file_info/1">
- <c>file:read_file_info/1</c></seealso>.</p>
+ <seemfa marker="kernel:file#read_file_info/1">
+ <c>file:read_file_info/1</c></seemfa>.</p>
</item>
<tag><c>{gid,non_neg_integer()}</c></tag>
<item>
<p>Sets the group that the file owner belongs to.
- <seealso marker="kernel:file#read_file_info/1">
- <c>file:read_file_info/1</c></seealso>.</p>
+ <seemfa marker="kernel:file#read_file_info/1">
+ <c>file:read_file_info/1</c></seemfa>.</p>
</item>
</taglist>
</desc>
@@ -216,7 +216,7 @@
<fsummary>Close an open tar file.</fsummary>
<desc>
<p>Closes a tar file
- opened by <seealso marker="#open/2"><c>open/2</c></seealso>.</p>
+ opened by <seemfa marker="#open/2"><c>open/2</c></seemfa>.</p>
</desc>
</func>
@@ -288,6 +288,11 @@
writing the file. That is, absolute paths will be turned into
relative paths. There will be an info message written to the error
logger when paths are changed in this way.</p></note>
+ <warning>
+ <p>The <c>compressed</c> and <c>cooked</c> flags are invalid when
+ passing a file descriptor with <c>{file,Fd}</c>. The file is
+ assumed to have been opened with the appropriate flags.</p>
+ </warning>
</desc>
</func>
@@ -349,6 +354,11 @@
<p>Prints an informational message for each extracted file.</p>
</item>
</taglist>
+ <warning>
+ <p>The <c>compressed</c> and <c>cooked</c> flags are invalid when
+ passing a file descriptor with <c>{file,Fd}</c>. The file is
+ assumed to have been opened with the appropriate flags.</p>
+ </warning>
</desc>
</func>
@@ -401,14 +411,14 @@
<tag><c>(position,{UserData,Position})</c></tag>
<item>
<p>Sets the position of <c>UserData</c> as defined for files in
- <seealso marker="kernel:file#position-2">
- <c>file:position/2</c></seealso></p>
+ <seemfa marker="kernel:file#position/2">
+ <c>file:position/2</c></seemfa></p>
</item>
</taglist>
<p><em>Example:</em></p>
<p>The following is a complete <c>Fun</c> parameter for reading and
writing on files using the
- <seealso marker="kernel:file"><c>file</c></seealso> module:</p>
+ <seeerl marker="kernel:file"><c>file</c></seeerl> module:</p>
<code type="none">
ExampleFun =
fun(write, {Fd,Data}) -> file:write(Fd, Data);
@@ -431,7 +441,7 @@ erl_tar:close(TarDesc)</code>
<note>
<p>This example with the <c>file</c> module operations is
not necessary to use directly, as that is what function
- <seealso marker="#open/2"><c>open/2</c></seealso> in principle
+ <seemfa marker="#open/2"><c>open/2</c></seemfa> in principle
does.</p>
</note>
<warning>
@@ -470,14 +480,19 @@ erl_tar:close(TarDesc)</code>
</item>
</taglist>
<p>To add one file at the time into an opened tar file, use function
- <seealso marker="#add/3"><c>add/3,4</c></seealso>. When you are
- finished adding files, use function <seealso marker="#close/1">
- <c>close/1</c></seealso> to close the tar file.</p>
+ <seemfa marker="#add/3"><c>add/3,4</c></seemfa>. When you are
+ finished adding files, use function <seemfa marker="#close/1">
+ <c>close/1</c></seemfa> to close the tar file.</p>
+ <warning>
+ <p>The <c>compressed</c> and <c>cooked</c> flags are invalid when
+ passing a file descriptor with <c>{file,Fd}</c>. The file must
+ already be opened with the appropriate flags.</p>
+ </warning>
<warning>
<p>The <c>TarDescriptor</c> term is not a file descriptor. You are
advised not to rely on the specific contents of this term, as it
can change in future Erlang/OTP releases when more features are
- added to this module..</p>
+ added to this module.</p>
</warning>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index f7e18fbbf8..4f43ae28d0 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -57,10 +57,10 @@
The previous default limit was about 1400 tables and
could be increased by setting the environment variable
<c>ERL_MAX_ETS_TABLES</c> or the command line option
- <seealso marker="erts:erl#+e"><c>+e</c></seealso> before starting the
+ <seecom marker="erts:erl#+e"><c>+e</c></seecom> before starting the
Erlang runtime system. This hard limit has been removed, but it is currently
useful to set the <c>ERL_MAX_ETS_TABLES</c> anyway. It should be
- set to an approximate of the maximum amount of tables used. This since
+ set to an approximate of the maximum amount of tables used since
an internal table for named tables is sized using this value. If
large amounts of named tables are used and <c>ERL_MAX_ETS_TABLES</c>
hasn't been increased, the performance of named table lookup will
@@ -72,11 +72,11 @@
Even if there are no references to a table from any process, it
is not automatically destroyed unless the owner process
terminates. To destroy a table explicitly, use function
- <seealso marker="#delete/1"><c>delete/1</c></seealso>.
+ <seemfa marker="#delete/1"><c>delete/1</c></seemfa>.
The default owner is the process that created the
table. To transfer table ownership at process termination, use
- option <seealso marker="#heir"><c>heir</c></seealso> or call
- <seealso marker="#give_away/3"><c>give_away/3</c></seealso>.</p>
+ option <seeerl marker="#heir"><c>heir</c></seeerl> or call
+ <seemfa marker="#give_away/3"><c>give_away/3</c></seemfa>.</p>
<p>Some implementation details:</p>
@@ -85,8 +85,8 @@
look-up operation results in a copy of the object.</p></item>
<item><p><c>'$end_of_table'</c> is not to be used as a key, as
this atom is used to mark the end of the table when using functions
- <seealso marker="#first/1"><c>first/1</c></seealso> and
- <seealso marker="#next/2"><c>next/2</c></seealso>.</p></item>
+ <seemfa marker="#first/1"><c>first/1</c></seemfa> and
+ <seemfa marker="#next/2"><c>next/2</c></seemfa>.</p></item>
</list>
<p>Notice the subtle difference between
@@ -116,13 +116,41 @@
</list>
</description>
- <section>
- <title>Failure</title>
- <p>The functions in this module exits with reason
- <c>badarg</c> if any argument has the wrong format, if the
- table identifier is invalid, or if the operation is denied because of
- table access rights (<seealso marker="#protected">protected</seealso>
- or <seealso marker="#private">private</seealso>).</p>
+ <section><marker id="ets_failures"></marker>
+ <title>Failures</title>
+ <p>Functions in this module fail by raising an error exception
+ with error reason:</p>
+ <taglist>
+ <tag><c>badarg</c></tag>
+ <item><p>
+ If any argument has the wrong format.
+ </p></item>
+ <tag><c>badarg</c></tag>
+ <item><p>
+ If the table identifier is invalid.
+ </p></item>
+ <tag><c>badarg</c></tag>
+ <item><p>
+ If the operation is denied because of
+ table access rights (<seeerl marker="#protected">protected</seeerl>
+ or <seeerl marker="#private">private</seeerl>).
+ </p></item>
+ <tag><c>system_limit</c></tag>
+ <item><p>
+ Modification of a value causes it to not be representable
+ internally in the VM. For example, incrementation of a
+ counter past the largest integer representable.
+ </p></item>
+ <tag><c>system_limit</c></tag>
+ <item><p>
+ If a match specification passed as argument has excessive
+ nesting which causes scheduler stack exhaustion for the
+ scheduler that the calling process is executing on.
+ <seecom marker="erts:erl#sched_thread_stack_size">Scheduler
+ stack size</seecom> can be configured when starting the
+ runtime system.
+ </p></item>
+ </taglist>
</section>
<section><marker id="concurrency"></marker>
@@ -145,32 +173,42 @@
<p>There are different ways to traverse through the objects of a table.</p>
<list type="bulleted">
<item><p><em>Single-step</em> traversal one key at at time, using
- <seealso marker="#first/1"><c>first/1</c></seealso>,
- <seealso marker="#next/2"><c>next/2</c></seealso>,
- <seealso marker="#last/1"><c>last/1</c></seealso> and
- <seealso marker="#prev/2"><c>prev/2</c></seealso>.</p>
+ <seemfa marker="#first/1"><c>first/1</c></seemfa>,
+ <seemfa marker="#next/2"><c>next/2</c></seemfa>,
+ <seemfa marker="#last/1"><c>last/1</c></seemfa> and
+ <seemfa marker="#prev/2"><c>prev/2</c></seemfa>.</p>
</item>
<item><p>Search with simple <em>match patterns</em>, using
- <seealso marker="#match/1"><c>match/1/2/3</c></seealso>,
- <seealso marker="#match_delete/2"><c>match_delete/2</c></seealso> and
- <seealso marker="#match_object/1"><c>match_object/1/2/3</c></seealso>.</p>
+ <seemfa marker="#match/1"><c>match/1/2/3</c></seemfa>,
+ <seemfa marker="#match_delete/2"><c>match_delete/2</c></seemfa> and
+ <seemfa marker="#match_object/1"><c>match_object/1/2/3</c></seemfa>.</p>
</item>
<item><p>Search with more powerful <em>match specifications</em>, using
- <seealso marker="#select/1"><c>select/1/2/3</c></seealso>,
- <seealso marker="#select_count/2"><c>select_count/2</c></seealso>,
- <seealso marker="#select_delete/2"><c>select_delete/2</c></seealso>,
- <seealso marker="#select_replace/2"><c>select_replace/2</c></seealso> and
- <seealso marker="#select_reverse/1"><c>select_reverse/1/2/3</c></seealso>.</p>
+ <seemfa marker="#select/1"><c>select/1/2/3</c></seemfa>,
+ <seemfa marker="#select_count/2"><c>select_count/2</c></seemfa>,
+ <seemfa marker="#select_delete/2"><c>select_delete/2</c></seemfa>,
+ <seemfa marker="#select_replace/2"><c>select_replace/2</c></seemfa> and
+ <seemfa marker="#select_reverse/1"><c>select_reverse/1/2/3</c></seemfa>.</p>
</item>
<item><p><em>Table conversions</em>, using
- <seealso marker="#tab2file/2"><c>tab2file/2/3</c></seealso> and
- <seealso marker="#tab2list/1"><c>tab2list/1</c></seealso>.</p>
+ <seemfa marker="#tab2file/2"><c>tab2file/2/3</c></seemfa> and
+ <seemfa marker="#tab2list/1"><c>tab2list/1</c></seemfa>.</p>
</item>
</list>
- <p>None of these ways of table traversal will guarantee a consistent table snapshot
- if the table is also updated during the traversal. Moreover, traversals not
- done in a <em>safe</em> way, on tables where keys are inserted or deleted
- during the traversal, may yield the following undesired effects:</p>
+ <p>
+ No table traversal will guarantee a consistent snapshot of the entire
+ table if the table is also updated by concurrent processes during the
+ traversal. The result of each concurrently updated object may be seen (or
+ not) depending on if it has happened when the traversal visits that part
+ of the table. The only way to guarantee a full consistent table snapshot
+ (if you really need that) is to disallow concurrent updates during the
+ entire traversal.
+ </p>
+ <p>
+ Moreover, traversals not done in a <em>safe</em> way, on tables where
+ keys are inserted or deleted during the traversal, may yield the
+ following undesired effects:
+ </p>
<list type="bulleted">
<item><p>Any key may be missed.</p></item>
<item><p>Any key may be found more than once.</p></item>
@@ -184,13 +222,13 @@
<item><p>the entire table traversal is done within one ETS function
call.</p>
</item>
- <item><p>function <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ <item><p>function <seemfa marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seemfa>
is used to keep the table fixated during the entire traversal.</p>
</item>
</list>
<note>
<p>Even though the access of a single object is always guaranteed to be
- <seealso marker="#concurrency">atomic and isolated</seealso>, each traversal
+ <seeerl marker="#concurrency">atomic and isolated</seeerl>, each traversal
through a table to find the next key is not done with such guarantees. This is often
not a problem, but may cause rare subtle "unexpected" effects if a concurrent
process inserts objects during a traversal. For example, consider one
@@ -209,7 +247,7 @@ ets:insert(t, {3}),
ordered directly after <c>1</c>.</p>
<p>Effects like this are improbable but possible. The probability will
further be reduced (if not vanish) if table option
- <seealso marker="#new_2_write_concurrency"><c>write_concurrency</c></seealso>
+ <seeerl marker="#new_2_write_concurrency"><c>write_concurrency</c></seeerl>
is not enabled. This can also only be a potential concern for
<c>ordered_set</c> where the traversal order is defined.</p>
</note>
@@ -235,9 +273,12 @@ true
<title>Match Specifications</title>
<p>Some of the functions use a <em>match specification</em>,
<c>match_spec</c>. For a brief explanation, see
- <seealso marker="#select/2"><c>select/2</c></seealso>. For a detailed
- description, see section <seealso marker="erts:match_spec">
- Match Specifications in Erlang</seealso> in ERTS User's Guide.</p>
+ <seemfa marker="#select/2"><c>select/2</c></seemfa>. For a detailed
+ description, see section <seeguide marker="erts:match_spec">
+ Match Specifications in Erlang</seeguide> in ERTS User's Guide.</p>
+ <p>A match specifications with excessive nesting will cause a
+ <seeerl marker="#ets_failures"><c>system_limit</c></seeerl> error
+ exception to be raised.</p>
</section>
<datatypes>
@@ -247,11 +288,11 @@ true
<datatype>
<name>continuation()</name>
<desc>
- <p>Opaque continuation used by <seealso marker="#select/1">
- <c>select/1,3</c></seealso>, <seealso marker="#select_reverse/1">
- <c>select_reverse/1,3</c></seealso>, <seealso marker="#match/1">
- <c>match/1,3</c></seealso>, and <seealso marker="#match_object/1">
- <c>match_object/1,3</c></seealso>.</p>
+ <p>Opaque continuation used by <seemfa marker="#select/1">
+ <c>select/1,3</c></seemfa>, <seemfa marker="#select_reverse/1">
+ <c>select_reverse/1,3</c></seemfa>, <seemfa marker="#match/1">
+ <c>match/1,3</c></seemfa>, and <seemfa marker="#match_object/1">
+ <c>match_object/1,3</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -271,7 +312,7 @@ true
<datatype>
<name name="tid"/>
<desc><p>A table identifier, as returned by
- <seealso marker="#new/2"><c>new/2</c></seealso>.</p></desc>
+ <seemfa marker="#new/2"><c>new/2</c></seemfa>.</p></desc>
</datatype>
<datatype>
<name name="type"/>
@@ -318,7 +359,7 @@ true
<desc>
<p>Delete all objects in the ETS table <c><anno>Tab</anno></c>.
The operation is guaranteed to be
- <seealso marker="#concurrency">atomic and isolated</seealso>.</p>
+ <seeerl marker="#concurrency">atomic and isolated</seeerl>.</p>
</desc>
</func>
@@ -338,9 +379,9 @@ true
<name name="file2tab" arity="1" since=""/>
<fsummary>Read an ETS table from a file.</fsummary>
<desc>
- <p>Reads a file produced by <seealso marker="#tab2file/2">
- <c>tab2file/2</c></seealso> or
- <seealso marker="#tab2file/3"><c>tab2file/3</c></seealso> and
+ <p>Reads a file produced by <seemfa marker="#tab2file/2">
+ <c>tab2file/2</c></seemfa> or
+ <seemfa marker="#tab2file/3"><c>tab2file/3</c></seemfa> and
creates the corresponding table <c><anno>Tab</anno></c>.</p>
<p>Equivalent to <c>file2tab(<anno>Filename</anno>, [])</c>.</p>
</desc>
@@ -350,16 +391,16 @@ true
<name name="file2tab" arity="2" since=""/>
<fsummary>Read an ETS table from a file.</fsummary>
<desc>
- <p>Reads a file produced by <seealso marker="#tab2file/2">
- <c>tab2file/2</c></seealso> or <seealso marker="#tab2file/3">
- <c>tab2file/3</c></seealso> and creates the
+ <p>Reads a file produced by <seemfa marker="#tab2file/2">
+ <c>tab2file/2</c></seemfa> or <seemfa marker="#tab2file/3">
+ <c>tab2file/3</c></seemfa> and creates the
corresponding table <c><anno>Tab</anno></c>.</p>
<p>The only supported option is <c>{verify,boolean()}</c>.
If verification is turned on (by specifying <c>{verify,true}</c>),
the function uses whatever information is present in the file to
assert that the information is not damaged. How this is done depends
on which <c>extended_info</c> was written using
- <seealso marker="#tab2file/3"><c>tab2file/3</c></seealso>.</p>
+ <seemfa marker="#tab2file/3"><c>tab2file/3</c></seemfa>.</p>
<p>If no <c>extended_info</c> is present in the file and
<c>{verify,true}</c> is specified, the number of objects
written is compared to the size of the original table when the
@@ -368,7 +409,7 @@ true
table was dumped to file. To avoid this problem,
either do not verify files dumped while updated simultaneously
or use option <c>{extended_info, [object_count]}</c> to
- <seealso marker="#tab2file/3"><c>tab2file/3</c></seealso>, which
+ <seemfa marker="#tab2file/3"><c>tab2file/3</c></seemfa>, which
extends the information in the file with the number of objects
written.</p>
<p>If verification is turned on and the file was written with
@@ -389,7 +430,7 @@ true
order of the table is returned. If the table is empty,
<c>'$end_of_table'</c> is returned.</p>
<p>To find subsequent keys in the table, use
- <seealso marker="#next/2"><c>next/2</c></seealso>.</p>
+ <seemfa marker="#next/2"><c>next/2</c></seemfa>.</p>
</desc>
</func>
@@ -399,7 +440,7 @@ true
<desc>
<p><c><anno>Acc0</anno></c> is returned if the table is empty.
This function is similar to
- <seealso marker="lists#foldl/3"><c>lists:foldl/3</c></seealso>.
+ <seemfa marker="lists#foldl/3"><c>lists:foldl/3</c></seemfa>.
The table elements are traversed in an unspecified order, except for
<c>ordered_set</c> tables, where they are traversed first to last.</p>
<p>If <c><anno>Function</anno></c> inserts objects into the table,
@@ -415,7 +456,7 @@ true
<desc>
<p><c><anno>Acc0</anno></c> is returned if the table is empty.
This function is similar to
- <seealso marker="lists#foldr/3"><c>lists:foldr/3</c></seealso>.
+ <seemfa marker="lists#foldr/3"><c>lists:foldr/3</c></seemfa>.
The table elements are traversed in an unspecified order, except for
<c>ordered_set</c> tables, where they are traversed last to first.</p>
<p>If <c><anno>Function</anno></c> inserts objects into the table,
@@ -447,7 +488,7 @@ true
<p>Pseudo function that by a <c>parse_transform</c> translates
<c><anno>LiteralFun</anno></c> typed as parameter in the function
call to a
- <seealso marker="#match_spec">match specification</seealso>.
+ <seeerl marker="#match_spec">match specification</seeerl>.
With "literal" is meant that the fun must textually be written
as the parameter of the function, it cannot be held in a
variable that in turn is passed to the function.</p>
@@ -503,8 +544,8 @@ Error: fun containing local Erlang function calls
code never calls the function, but the function call is
replaced by a literal match specification.</p>
</warning>
- <p>For more information, see <seealso marker="ms_transform#top">
- <c>ms_transform(3)</c></seealso>.</p>
+ <p>For more information, see <seeerl marker="ms_transform#top">
+ <c>ms_transform(3)</c></seeerl>.</p>
</desc>
</func>
@@ -520,7 +561,7 @@ Error: fun containing local Erlang function calls
already the owner of the table.
The calling process must be the table owner.</p>
<p>Notice that this function does not affect option
- <seealso marker="#heir"><c>heir</c></seealso> of the table. A table
+ <seeerl marker="#heir"><c>heir</c></seeerl> of the table. A table
owner can, for example, set <c>heir</c> to itself, give the table
away, and then get it back if the receiver terminates.</p>
</desc>
@@ -557,13 +598,17 @@ Error: fun containing local Erlang function calls
<item>
<p>Indicates if the table is compressed.</p>
</item>
+ <tag><c>{decentralized_counters, boolean()}</c></tag>
+ <item>
+ <p>Indicates whether the table uses <c>decentralized_counters</c>.</p>
+ </item>
<tag><c>{heir, pid() | none}</c></tag>
<item>
<p>The pid of the heir of the table, or <c>none</c> if no heir
is set.</p>
</item>
- <tag><c>{id,</c><seealso marker="#type-tid">
- <c>tid()</c></seealso><c>}</c></tag>
+ <tag><c>{id,</c><seetype marker="#tid">
+ <c>tid()</c></seetype><c>}</c></tag>
<item>
<p>The table identifier.</p>
</item>
@@ -592,8 +637,8 @@ Error: fun containing local Erlang function calls
<item>
<p>The pid of the owner of the table.</p>
</item>
- <tag><c>{protection,</c> <seealso marker="#type-access">
- <c>access()</c></seealso><c>}</c></tag>
+ <tag><c>{protection,</c> <seetype marker="#access">
+ <c>access()</c></seetype><c>}</c></tag>
<item>
<p>The table access rights.</p>
</item>
@@ -601,8 +646,8 @@ Error: fun containing local Erlang function calls
<item>
<p>The number of objects inserted in the table.</p>
</item>
- <tag><c>{type,</c> <seealso marker="#type-type">
- <c>type()</c></seealso><c>}</c></tag>
+ <tag><c>{type,</c> <seetype marker="#type">
+ <c>type()</c></seetype><c>}</c></tag>
<item>
<p>The table type.</p>
</item>
@@ -616,6 +661,13 @@ Error: fun containing local Erlang function calls
<p>Indicates whether the table uses <c>write_concurrency</c>.</p>
</item>
</taglist>
+ <note><p>The execution time of this function is affected by
+ the <seeerl marker="#new_2_decentralized_counters">
+ <c>decentralized_counters</c></seeerl> table option.
+ The execution time is much longer when the <c>decentralized_counters</c>
+ option is set to <c>true</c> than when the <c>decentralized_counters</c>
+ option is set to <c>false</c>.</p>
+ </note>
</desc>
</func>
@@ -631,7 +683,7 @@ Error: fun containing local Erlang function calls
not of the correct type, or if <c><anno>Item</anno></c> is not
one of the allowed values, a <c>badarg</c> exception is raised.</p>
<p>In addition to the <c>{<anno>Item</anno>,<anno>Value</anno>}</c>
- pairs defined for <seealso marker="#info/1"><c>info/1</c></seealso>,
+ pairs defined for <seemfa marker="#info/1"><c>info/1</c></seemfa>,
the following items are allowed:</p>
<list type="bulleted">
<item>
@@ -651,8 +703,8 @@ Error: fun containing local Erlang function calls
<p><c>Item=safe_fixed|safe_fixed_monotonic_time,
Value={FixationTime,Info}|false</c></p>
<p>If the table is fixed using
- <seealso marker="#safe_fixtable/2">
- <c>safe_fixtable/2</c></seealso>,
+ <seemfa marker="#safe_fixtable/2">
+ <c>safe_fixtable/2</c></seemfa>,
the call returns a tuple where <c>FixationTime</c> is the
last time when the table changed from unfixed to fixed.</p>
<p>The format and value of <c>FixationTime</c> depends on
@@ -661,24 +713,24 @@ Error: fun containing local Erlang function calls
<tag><c>safe_fixed</c></tag>
<item>
<p><c>FixationTime</c> corresponds to the result returned by
- <seealso marker="erts:erlang#timestamp/0">
- <c>erlang:timestamp/0</c></seealso> at the time of fixation.
+ <seemfa marker="erts:erlang#timestamp/0">
+ <c>erlang:timestamp/0</c></seemfa> at the time of fixation.
Notice that when the system uses single or multi
- <seealso marker="erts:time_correction#Time_Warp_Modes">time
- warp modes</seealso> this can produce strange results, as
+ <seeguide marker="erts:time_correction#Time_Warp_Modes">time
+ warp modes</seeguide> this can produce strange results, as
the use of <c>safe_fixed</c> is not
- <seealso marker="erts:time_correction#Time_Warp_Safe_Code">
- time warp safe</seealso>. Time warp safe code must use
+ <seeguide marker="erts:time_correction#Time_Warp_Safe_Code">
+ time warp safe</seeguide>. Time warp safe code must use
<c>safe_fixed_monotonic_time</c> instead.</p>
</item>
<tag><c>safe_fixed_monotonic_time</c></tag>
<item>
<p><c>FixationTime</c> corresponds to the result returned by
- <seealso marker="erts:erlang#monotonic_time/0">
- <c>erlang:monotonic_time/0</c></seealso> at the time of
+ <seemfa marker="erts:erlang#monotonic_time/0">
+ <c>erlang:monotonic_time/0</c></seemfa> at the time of
fixation. The use of <c>safe_fixed_monotonic_time</c> is
- <seealso marker="erts:time_correction#Time_Warp_Safe_Code">
- time warp safe</seealso>.</p>
+ <seeguide marker="erts:time_correction#Time_Warp_Safe_Code">
+ time warp safe</seeguide>.</p>
</item>
</taglist>
<p><c>Info</c> is a possibly empty lists of tuples
@@ -686,10 +738,10 @@ Error: fun containing local Erlang function calls
table is fixed by now. <c>RefCount</c> is the value
of the reference counter and it keeps track of how many times
the table has been fixed by the process.</p>
- <p>Table fixations are not limited to <seealso marker="#safe_fixtable/2">
- <c>safe_fixtable/2</c></seealso>. Temporary fixations may also
- be done by for example <seealso marker="#traversal">traversing
- functions</seealso> like <c>select</c> and <c>match</c>. Such
+ <p>Table fixations are not limited to <seemfa marker="#safe_fixtable/2">
+ <c>safe_fixtable/2</c></seemfa>. Temporary fixations may also
+ be done by for example <seeerl marker="#traversal">traversing
+ functions</seeerl> like <c>select</c> and <c>match</c>. Such
table fixations are automatically released before the
corresponding functions returns, but they may be seen by a
concurrent call to
@@ -701,6 +753,15 @@ Error: fun containing local Erlang function calls
<p>Returns internal statistics about tables on an internal format
used by OTP test suites. Not for production use.</p></item>
</list>
+ <note>
+ <p>The execution time of this function is affected by
+ the <seeerl marker="#new_2_decentralized_counters">
+ <c>decentralized_counters</c></seeerl> table option when the second
+ argument of the function is <c>size</c> or <c>memory</c>.
+ The execution time is much longer when the <c>decentralized_counters</c>
+ option is set to <c>true</c> than when the <c>decentralized_counters</c>
+ option is set to <c>false</c>.</p>
+ </note>
</desc>
</func>
@@ -714,7 +775,7 @@ Error: fun containing local Erlang function calls
see below. This function is provided for compatibility with
the <c>dets</c> module, it is not more efficient than filling
a table by using
- <seealso marker="#insert/2"><c>insert/2</c></seealso>.</p>
+ <seemfa marker="#insert/2"><c>insert/2</c></seemfa>.</p>
<p>When called with argument <c>read</c>, the function
<c><anno>InitFun</anno></c> is assumed to return
<c>end_of_input</c> when
@@ -761,7 +822,7 @@ Error: fun containing local Erlang function calls
</item>
</list>
<p>The entire operation is guaranteed to be
- <seealso marker="#concurrency">atomic and isolated</seealso>,
+ <seeerl marker="#concurrency">atomic and isolated</seeerl>,
even when a list of objects is inserted.</p>
</desc>
</func>
@@ -771,7 +832,7 @@ Error: fun containing local Erlang function calls
<fsummary>Insert an object into an ETS table if the key is not
already present.</fsummary>
<desc>
- <p>Same as <seealso marker="#insert/2"><c>insert/2</c></seealso>
+ <p>Same as <seemfa marker="#insert/2"><c>insert/2</c></seemfa>
except that instead of overwriting objects with the same key
(for <c>set</c> or <c>ordered_set</c>) or adding more objects with
keys already existing in the table (for <c>bag</c> and
@@ -781,7 +842,7 @@ Error: fun containing local Erlang function calls
inserting anything. Nothing is inserted unless
<em>all</em> keys present in the list are absent from the
table. Like <c>insert/2</c>, the entire operation is guaranteed to be
- <seealso marker="#concurrency">atomic and isolated</seealso>.</p>
+ <seeerl marker="#concurrency">atomic and isolated</seeerl>.</p>
</desc>
</func>
@@ -791,10 +852,10 @@ Error: fun containing local Erlang function calls
<c>match_spec_compile</c>.</fsummary>
<desc>
<p>Checks if a term represent a valid compiled
- <seealso marker="#match_spec">match specification</seealso>.
+ <seeerl marker="#match_spec">match specification</seeerl>.
A compiled match specifications is only valid on the Erlang node where
- it was compiled by calling <seealso marker="#match_spec_compile/1">
- <c>match_spec_compile/1</c></seealso>.</p>
+ it was compiled by calling <seemfa marker="#match_spec_compile/1">
+ <c>match_spec_compile/1</c></seemfa>.</p>
<note>
<p>
Before STDLIB 3.4 (OTP 20.0) compiled match specifications did
@@ -821,10 +882,10 @@ Error: fun containing local Erlang function calls
<p>Returns the last key <c><anno>Key</anno></c> according to Erlang
term order in table <c>Tab</c> of type <c>ordered_set</c>. For
other table types, the function is synonymous to
- <seealso marker="#first/1"><c>first/1</c></seealso>.
+ <seemfa marker="#first/1"><c>first/1</c></seemfa>.
If the table is empty, <c>'$end_of_table'</c> is returned.</p>
<p>To find preceding keys in the table, use
- <seealso marker="#prev/2"><c>prev/2</c></seealso>.</p>
+ <seemfa marker="#prev/2"><c>prev/2</c></seemfa>.</p>
</desc>
</func>
@@ -896,7 +957,7 @@ Error: fun containing local Erlang function calls
<fsummary>Continues matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with
- <seealso marker="#match/3"><c>match/3</c></seealso>. The next
+ <seemfa marker="#match/3"><c>match/3</c></seemfa>. The next
chunk of the size specified in the initial <c>match/3</c>
call is returned together with a new <c><anno>Continuation</anno></c>,
which can be used in subsequent calls to this function.</p>
@@ -942,20 +1003,20 @@ Error: fun containing local Erlang function calls
<fsummary>Match the objects in an ETS table against a pattern
and return part of the answers.</fsummary>
<desc>
- <p>Works like <seealso marker="#match/2"><c>match/2</c></seealso>,
+ <p>Works like <seemfa marker="#match/2"><c>match/2</c></seemfa>,
but returns only a limited (<c><anno>Limit</anno></c>) number of
matching objects. Term <c><anno>Continuation</anno></c> can then
- be used in subsequent calls to <seealso marker="#match/1">
- <c>match/1</c></seealso> to get the next chunk of matching
+ be used in subsequent calls to <seemfa marker="#match/1">
+ <c>match/1</c></seemfa> to get the next chunk of matching
objects. This is a space-efficient way to work on objects in a
table, which is faster than traversing the table object
by object using
- <seealso marker="#first/1"><c>first/1</c></seealso> and
- <seealso marker="#next/2"><c>next/2</c></seealso>.</p>
+ <seemfa marker="#first/1"><c>first/1</c></seemfa> and
+ <seemfa marker="#next/2"><c>next/2</c></seemfa>.</p>
<p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
- <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
- to guarantee <seealso marker="#traversal">safe traversal</seealso>
- for subsequent calls to <seealso marker="#match/1"><c>match/1</c></seealso>.</p>
+ <p>Use <seemfa marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seemfa>
+ to guarantee <seeerl marker="#traversal">safe traversal</seeerl>
+ for subsequent calls to <seemfa marker="#match/1"><c>match/1</c></seemfa>.</p>
</desc>
</func>
@@ -966,7 +1027,7 @@ Error: fun containing local Erlang function calls
<desc>
<p>Deletes all objects that match pattern <c><anno>Pattern</anno></c>
from table <c><anno>Tab</anno></c>. For a description of patterns,
- see <seealso marker="#match/2"><c>match/2</c></seealso>.</p>
+ see <seemfa marker="#match/2"><c>match/2</c></seemfa>.</p>
</desc>
</func>
@@ -975,7 +1036,7 @@ Error: fun containing local Erlang function calls
<fsummary>Continues matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with
- <seealso marker="#match_object/3"><c>match_object/3</c></seealso>.
+ <seemfa marker="#match_object/3"><c>match_object/3</c></seemfa>.
The next chunk of the size specified in the initial
<c>match_object/3</c> call is returned together with a new
<c><anno>Continuation</anno></c>, which can be used in subsequent
@@ -992,7 +1053,7 @@ Error: fun containing local Erlang function calls
<desc>
<p>Matches the objects in table <c><anno>Tab</anno></c> against
pattern <c><anno>Pattern</anno></c>. For a description of patterns,
- see <seealso marker="#match/2"><c>match/2</c></seealso>.
+ see <seemfa marker="#match/2"><c>match/2</c></seemfa>.
The function returns a list of all objects that
match the pattern.</p>
<p>If the key is specified in the pattern, the match is very
@@ -1009,22 +1070,22 @@ Error: fun containing local Erlang function calls
<fsummary>Match the objects in an ETS table against a pattern and
return part of the answers.</fsummary>
<desc>
- <p>Works like <seealso marker="#match_object/2">
- <c>match_object/2</c></seealso>, but only returns a
+ <p>Works like <seemfa marker="#match_object/2">
+ <c>match_object/2</c></seemfa>, but only returns a
limited (<c><anno>Limit</anno></c>) number of matching objects. Term
<c><anno>Continuation</anno></c> can then be used in subsequent
- calls to <seealso marker="#match_object/1">
- <c>match_object/1</c></seealso> to get the next chunk of matching
+ calls to <seemfa marker="#match_object/1">
+ <c>match_object/1</c></seemfa> to get the next chunk of matching
objects. This is a space-efficient way to work on objects in a
table, which is faster than traversing the table object
by object using
- <seealso marker="#first/1"><c>first/1</c></seealso> and
- <seealso marker="#next/2"><c>next/2</c></seealso>.</p>
+ <seemfa marker="#first/1"><c>first/1</c></seemfa> and
+ <seemfa marker="#next/2"><c>next/2</c></seemfa>.</p>
<p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
- <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
- to guarantee <seealso marker="#traversal">safe traversal</seealso>
- for subsequent calls to <seealso marker="#match_object/1">
- <c>match_object/1</c></seealso>.</p>
+ <p>Use <seemfa marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seemfa>
+ to guarantee <seeerl marker="#traversal">safe traversal</seeerl>
+ for subsequent calls to <seemfa marker="#match_object/1">
+ <c>match_object/1</c></seemfa>.</p>
</desc>
</func>
@@ -1034,19 +1095,18 @@ Error: fun containing local Erlang function calls
</fsummary>
<desc>
<p>Transforms a
- <seealso marker="#match_spec">match specification</seealso> into an
+ <seeerl marker="#match_spec">match specification</seeerl> into an
internal representation that can be used in subsequent calls to
- <seealso marker="#match_spec_run/2"><c>match_spec_run/2</c></seealso>.
+ <seemfa marker="#match_spec_run/2"><c>match_spec_run/2</c></seemfa>.
The internal representation is opaque.
To check the validity of a compiled match specification, use
- <seealso marker="#is_compiled_ms/1"><c>is_compiled_ms/1</c></seealso>.
+ <seemfa marker="#is_compiled_ms/1"><c>is_compiled_ms/1</c></seemfa>.
</p>
- <p>If term <c><anno>MatchSpec</anno></c> cannot be compiled (does not
- represent a valid match specification), a <c>badarg</c> exception is
- raised.</p>
+ <p>If term <c><anno>MatchSpec</anno></c> does not represent a valid
+ match specification, a <c>badarg</c> exception is raised.</p>
<note>
<p>This function has limited use in normal code. It is used by the
- <seealso marker="dets"><c>dets</c></seealso> module
+ <seeerl marker="dets"><c>dets</c></seeerl> module
to perform the <c>dets:select()</c> operations.</p>
</note>
</desc>
@@ -1058,10 +1118,10 @@ Error: fun containing local Erlang function calls
list of terms.</fsummary>
<desc>
<p>Executes the matching specified in a compiled
- <seealso marker="#match_spec">match specification</seealso> on a list
+ <seeerl marker="#match_spec">match specification</seeerl> on a list
of terms. Term <c><anno>CompiledMatchSpec</anno></c> is to be
- the result of a call to <seealso marker="#match_spec_compile/1">
- <c>match_spec_compile/1</c></seealso> and is hence the internal
+ the result of a call to <seemfa marker="#match_spec_compile/1">
+ <c>match_spec_compile/1</c></seemfa> and is hence the internal
representation of the match specification one wants to use.</p>
<p>The matching is executed on each element in <c><anno>List</anno></c>
and the function returns a list containing all results. If an element
@@ -1082,7 +1142,7 @@ ets:match_spec_run(ets:tab2list(Table),
ets:select(Table, MatchSpec),</code>
<note>
<p>This function has limited use in normal code. It is used by the
- <seealso marker="dets"><c>dets</c></seealso> module
+ <seeerl marker="dets"><c>dets</c></seeerl> module
to perform the <c>dets:select()</c> operations and by
Mnesia during transactions.</p>
</note>
@@ -1093,7 +1153,7 @@ ets:select(Table, MatchSpec),</code>
<name name="member" arity="2" since=""/>
<fsummary>Tests for occurrence of a key in an ETS table.</fsummary>
<desc>
- <p>Works like <seealso marker="#lookup/2"><c>lookup/2</c></seealso>,
+ <p>Works like <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>,
but does not return the objects. Returns <c>true</c> if one or more
elements in the table has key <c><anno>Key</anno></c>, otherwise
<c>false</c>.</p>
@@ -1113,7 +1173,8 @@ ets:select(Table, MatchSpec),</code>
table is named. Default values are used for omitted options.
This means that not specifying any options (<c>[]</c>) is the same
as specifying <c>[set, protected, {keypos,1}, {heir,none},
- {write_concurrency,false}, {read_concurrency,false}]</c>.</p>
+ {write_concurrency,false}, {read_concurrency,false},
+ {decentralized_counters,false}]</c>.</p>
<taglist>
<tag><c>set</c></tag>
<item>
@@ -1172,7 +1233,7 @@ ets:select(Table, MatchSpec),</code>
<p>The function will also return the <c><anno>Name</anno></c>
instead of the table identifier. To get the table identifier of a
named table, use
- <seealso marker="#whereis/1"><c>whereis/1</c></seealso>.</p>
+ <seemfa marker="#whereis/1"><c>whereis/1</c></seemfa>.</p>
</item>
<tag><c>{keypos,<anno>Pos</anno>}</c></tag>
<item>
@@ -1208,17 +1269,27 @@ ets:select(Table, MatchSpec),</code>
(and read) by concurrent processes. This is achieved to some
degree at the expense of memory consumption and the performance
of sequential access and concurrent reading.</p>
- <p>Option <c>write_concurrency</c> can be combined with option
- <seealso marker="#new_2_read_concurrency">
- <c>read_concurrency</c></seealso>. You typically want to combine
- these when large concurrent read bursts and large concurrent
+ <p>The <c>write_concurrency</c> option can be combined with the options
+ <seeerl marker="#new_2_read_concurrency">
+ <c>read_concurrency</c></seeerl> and
+ <seeerl marker="#new_2_decentralized_counters">
+ <c>decentralized_counters</c></seeerl>. You typically want to combine
+ <c>write_concurrency</c> with <c>read_concurrency</c> when large
+ concurrent read bursts and large concurrent
write bursts are common; for more information, see option
- <seealso marker="#new_2_read_concurrency">
- <c>read_concurrency</c></seealso>.</p>
+ <seeerl marker="#new_2_read_concurrency">
+ <c>read_concurrency</c></seeerl>. The <c>decentralized_counters</c>
+ option is turned on by default for tables of type <c>ordered_set</c>
+ with the <c>write_concurrency</c> option enabled, and the
+ <c>decentralized_counters</c> option is turned off by default for
+ all other table types.
+ For more information, see the documentation for the
+ <seeerl marker="#new_2_decentralized_counters">
+ <c>decentralized_counters</c></seeerl> option.</p>
<p>Notice that this option does not change any guarantees about
- <seealso marker="#concurrency">atomicity and isolation</seealso>.
+ <seeerl marker="#concurrency">atomicity and isolation</seeerl>.
Functions that makes such promises over many objects (like
- <seealso marker="#insert/2"><c>insert/2</c></seealso>)
+ <seemfa marker="#insert/2"><c>insert/2</c></seemfa>)
gain less (or nothing) from this option.</p>
<p>The memory consumption inflicted by both <c>write_concurrency</c>
and <c>read_concurrency</c> is a constant overhead per table for
@@ -1252,11 +1323,43 @@ ets:select(Table, MatchSpec),</code>
operations repeatedly. In this case, you would get a performance
degradation by enabling this option.</p>
<p>Option <c>read_concurrency</c> can be combined with option
- <seealso marker="#new_2_write_concurrency">
- <c>write_concurrency</c></seealso>.
+ <seeerl marker="#new_2_write_concurrency">
+ <c>write_concurrency</c></seeerl>.
You typically want to combine these when large concurrent
read bursts and large concurrent write bursts are common.</p>
- <marker id="new_2_compressed"></marker>
+ <marker id="new_2_decentralized_counters"></marker>
+ </item>
+ <tag><c>{decentralized_counters,boolean()}</c></tag>
+ <item>
+ <p>
+ Performance tuning. Defaults to <c>true</c> for tables
+ of type <c>ordered_set</c> with the
+ <seeerl marker="#new_2_write_concurrency">
+ <c>write_concurrency</c></seeerl> option enabled, and defaults to
+ false for all other table types. This option has no effect if
+ the <c>write_concurrency</c> option is set to <c>false</c>.</p>
+ <p>
+ When this option is set to <c>true</c>, the table is optimized for
+ frequent concurrent calls to operations that modify the tables
+ size and/or its memory consumption (e.g., <seemfa
+ marker="#insert/2"><c>insert/2</c></seemfa> and <seemfa
+ marker="#delete/2"><c>delete/2</c></seemfa>).
+ The drawback is that calls to
+ <seemfa marker="#info/1"><c>info/1</c></seemfa> and
+ <seemfa marker="#info/2"><c>info/2</c></seemfa> with <c>size</c> or
+ <c>memory</c> as the second argument can get much slower when the
+ <c>decentralized_counters</c> option is turned on.</p>
+ <p>
+ When this option is enabled the counters for the
+ table size and memory consumption are distributed over
+ several cache lines and the scheduling threads are
+ mapped to one of those cache lines. The <c>erl</c>
+ option <seecom
+ marker="erts:erl#+dcg"><c>+dcg</c></seecom> can be used
+ to control the number of cache lines that the counters
+ are distributed over.
+ </p>
+ <marker id="new_2_compressed"></marker>
</item>
<tag><c>compressed</c></tag>
<item>
@@ -1281,10 +1384,10 @@ ets:select(Table, MatchSpec),</code>
according to the internal order of the table is returned. If no
next key exists, <c>'$end_of_table'</c> is returned.</p>
<p>To find the first key in the table, use
- <seealso marker="#first/1"><c>first/1</c></seealso>.</p>
+ <seemfa marker="#first/1"><c>first/1</c></seemfa>.</p>
<p>Unless a table of type <c>set</c>, <c>bag</c>, or
<c>duplicate_bag</c> is fixated using
- <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>,
+ <seemfa marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seemfa>,
a call to <c>next/2</c> will fail if <c><anno>Key1</anno></c> no longer
exists in the table. For table type <c>ordered_set</c>, the function
always returns the next key after <c><anno>Key1</anno></c> in term
@@ -1302,10 +1405,10 @@ ets:select(Table, MatchSpec),</code>
<c><anno>Key1</anno></c> according to Erlang term order in table
<c><anno>Tab</anno></c> of type <c>ordered_set</c>. For other
table types, the function is synonymous to
- <seealso marker="#next/2"><c>next/2</c></seealso>.
+ <seemfa marker="#next/2"><c>next/2</c></seemfa>.
If no previous key exists, <c>'$end_of_table'</c> is returned.</p>
<p>To find the last key in an <c>ordered_set</c> table, use
- <seealso marker="#last/1"><c>last/1</c></seealso>.</p>
+ <seemfa marker="#last/1"><c>last/1</c></seemfa>.</p>
</desc>
</func>
@@ -1325,8 +1428,8 @@ ets:select(Table, MatchSpec),</code>
that has passed through external representation.</fsummary>
<desc>
<p>Restores an opaque continuation returned by
- <seealso marker="#select/3"><c>select/3</c></seealso> or
- <seealso marker="#select/1"><c>select/1</c></seealso> if the
+ <seemfa marker="#select/3"><c>select/3</c></seemfa> or
+ <seemfa marker="#select/1"><c>select/1</c></seemfa> if the
continuation has passed through external term format (been
sent between nodes or stored on disk).</p>
<p>The reason for this function is that continuation terms
@@ -1364,7 +1467,7 @@ ets:select(ets:repair_continuation(MaybeBroken,MS)).</code>
<p>The actual behavior of compiled match specifications when recreated
from external format has changed and may change in future releases,
but this interface remains for backward compatibility.
- See <seealso marker="#is_compiled_ms/1"><c>is_compiled_ms/1</c></seealso>.</p>
+ See <seemfa marker="#is_compiled_ms/1"><c>is_compiled_ms/1</c></seemfa>.</p>
</note>
</desc>
</func>
@@ -1374,16 +1477,16 @@ ets:select(ets:repair_continuation(MaybeBroken,MS)).</code>
<fsummary>Fix an ETS table for safe traversal.</fsummary>
<desc>
<p>Fixes a table of type <c>set</c>, <c>bag</c>, or
- <c>duplicate_bag</c> for <seealso marker="#traversal">
- safe traversal</seealso> using
- <seealso marker="#first/1"><c>first/1</c></seealso> &amp;
- <seealso marker="#next/2"><c>next/2</c></seealso>,
- <seealso marker="#match/3"><c>match/3</c></seealso> &amp;
- <seealso marker="#match/1"><c>match/1</c></seealso>,
- <seealso marker="#match_object/3"><c>match_object/3</c></seealso> &amp;
- <seealso marker="#match_object/1"><c>match_object/1</c></seealso>, or
- <seealso marker="#select/3"><c>select/3</c></seealso> &amp;
- <seealso marker="#select/1"><c>select/1</c></seealso>.</p>
+ <c>duplicate_bag</c> for <seeerl marker="#traversal">
+ safe traversal</seeerl> using
+ <seemfa marker="#first/1"><c>first/1</c></seemfa> &amp;
+ <seemfa marker="#next/2"><c>next/2</c></seemfa>,
+ <seemfa marker="#match/3"><c>match/3</c></seemfa> &amp;
+ <seemfa marker="#match/1"><c>match/1</c></seemfa>,
+ <seemfa marker="#match_object/3"><c>match_object/3</c></seemfa> &amp;
+ <seemfa marker="#match_object/1"><c>match_object/1</c></seemfa>, or
+ <seemfa marker="#select/3"><c>select/3</c></seemfa> &amp;
+ <seemfa marker="#select/1"><c>select/1</c></seemfa>.</p>
<p>A process fixes a table by calling
<c>safe_fixtable(<anno>Tab</anno>, true)</c>. The table remains
fixed until the process releases it by calling
@@ -1394,8 +1497,8 @@ ets:select(ets:repair_continuation(MaybeBroken,MS)).</code>
A reference counter is kept on a per process basis, and N
consecutive fixes requires N releases to release the table.</p>
<p>When a table is fixed, a sequence of
- <seealso marker="#first/1"><c>first/1</c></seealso> and
- <seealso marker="#next/2"><c>next/2</c></seealso> calls are
+ <seemfa marker="#first/1"><c>first/1</c></seemfa> and
+ <seemfa marker="#next/2"><c>next/2</c></seemfa> calls are
guaranteed to succeed even if keys are removed during the
traversal. The keys for objects inserted or deleted during a
traversal may or may not be returned by <c>next/2</c> depending on
@@ -1424,13 +1527,13 @@ clean_all_with_value(Tab,X,Key) ->
objects is never freed. The performance of operations on
the table also degrades significantly.</p>
<p>To retrieve information about which processes have fixed which
- tables, use <seealso marker="#info_2_safe_fixed_monotonic_time">
- <c>info(Tab, safe_fixed_monotonic_time)</c></seealso>. A system with
+ tables, use <seeerl marker="#info_2_safe_fixed_monotonic_time">
+ <c>info(Tab, safe_fixed_monotonic_time)</c></seeerl>. A system with
many processes fixing tables can need a monitor that sends alarms
when tables have been fixed for too long.</p>
<p>Notice that <c>safe_fixtable/2</c> is not necessary for table type
<c>ordered_set</c> and for traversals done by a single ETS function call,
- like <seealso marker="#select/2"><c>select/2</c></seealso>.</p>
+ like <seemfa marker="#select/2"><c>select/2</c></seemfa>.</p>
</desc>
</func>
@@ -1439,7 +1542,7 @@ clean_all_with_value(Tab,X,Key) ->
<fsummary>Continue matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with
- <seealso marker="#select/3"><c>select/3</c></seealso>. The next
+ <seemfa marker="#select/3"><c>select/3</c></seemfa>. The next
chunk of the size specified in the initial <c>select/3</c>
call is returned together with a new <c><anno>Continuation</anno></c>,
which can be used in subsequent calls to this function.</p>
@@ -1454,10 +1557,10 @@ clean_all_with_value(Tab,X,Key) ->
match specification.</fsummary>
<desc>
<p>Matches the objects in table <c><anno>Tab</anno></c> using a
- <seealso marker="#match_spec">match specification</seealso>.
+ <seeerl marker="#match_spec">match specification</seeerl>.
This is a more general call than
- <seealso marker="#match/2"><c>match/2</c></seealso> and
- <seealso marker="#match_object/2"><c>match_object/2</c></seealso>
+ <seemfa marker="#match/2"><c>match/2</c></seemfa> and
+ <seemfa marker="#match_object/2"><c>match_object/2</c></seemfa>
calls. In its simplest form, the match specification is as
follows:</p>
<code type="none">
@@ -1469,7 +1572,7 @@ Result = "Term construct"</code>
<p>This means that the match specification is always a list of one or
more tuples (of arity 3). The first element of the tuple is to be
a pattern as described in
- <seealso marker="#match/2"><c>match/2</c></seealso>.
+ <seemfa marker="#match/2"><c>match/2</c></seemfa>.
The second element of the tuple is to
be a list of 0 or more guard tests (described below). The
third element of the tuple is to be a list containing a
@@ -1512,8 +1615,8 @@ ets:select(Tab,[{{'$1','$2','$1'},[],['$_']}])</code>
ets:select(Tab,[{{'$1','$2','$1'},[],[{{'$1','$2','$3'}}]}])</code>
<p>This syntax is equivalent to the syntax used in the trace
patterns (see the
- <seealso marker="runtime_tools:dbg">
- <c>dbg(3)</c></seealso>) module in Runtime_Tools.</p>
+ <seeerl marker="runtime_tools:dbg">
+ <c>dbg(3)</c></seeerl>) module in Runtime_Tools.</p>
<p>The <c>Guard</c>s are constructed as tuples, where the first
element is the test name and the remaining elements
are the test parameters. To check for a specific type
@@ -1548,20 +1651,20 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
<fsummary>Match the objects in an ETS table against a match
specification and return part of the answers.</fsummary>
<desc>
- <p>Works like <seealso marker="#select/2"><c>select/2</c></seealso>,
+ <p>Works like <seemfa marker="#select/2"><c>select/2</c></seemfa>,
but only returns a limited
(<c><anno>Limit</anno></c>) number of matching objects. Term
<c><anno>Continuation</anno></c> can then be used in subsequent
- calls to <seealso marker="#select/1"><c>select/1</c></seealso>
+ calls to <seemfa marker="#select/1"><c>select/1</c></seemfa>
to get the next chunk of matching
objects. This is a space-efficient way to work on objects in a
table, which is still faster than traversing the table object by
- object using <seealso marker="#first/1"><c>first/1</c></seealso>
- and <seealso marker="#next/2"><c>next/2</c></seealso>.</p>
+ object using <seemfa marker="#first/1"><c>first/1</c></seemfa>
+ and <seemfa marker="#next/2"><c>next/2</c></seemfa>.</p>
<p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
- <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
- to guarantee <seealso marker="#traversal">safe traversal</seealso>
- for subsequent calls to <seealso marker="#select/1"><c>select/1</c></seealso>.</p>
+ <p>Use <seemfa marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seemfa>
+ to guarantee <seeerl marker="#traversal">safe traversal</seeerl>
+ for subsequent calls to <seemfa marker="#select/1"><c>select/1</c></seemfa>.</p>
</desc>
</func>
@@ -1572,13 +1675,13 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
specification returned <c>true</c>.</fsummary>
<desc>
<p>Matches the objects in table <c><anno>Tab</anno></c> using a
- <seealso marker="#match_spec">match specification</seealso>. If the
+ <seeerl marker="#match_spec">match specification</seeerl>. If the
match specification returns <c>true</c> for an object, that object
considered a match and is counted. For any other result from
the match specification the object is not considered a match and is
therefore not counted.</p>
<p>This function can be described as a
- <seealso marker="#match_delete/2"><c>match_delete/2</c></seealso>
+ <seemfa marker="#match_delete/2"><c>match_delete/2</c></seemfa>
function that does not delete any elements, but only counts them.</p>
<p>The function returns the number of objects matched.</p>
</desc>
@@ -1591,12 +1694,12 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
returns <c>true</c>.</fsummary>
<desc>
<p>Matches the objects in table <c><anno>Tab</anno></c> using a
- <seealso marker="#match_spec">match specification</seealso>. If the
+ <seeerl marker="#match_spec">match specification</seeerl>. If the
match specification returns <c>true</c> for an object, that object is
removed from the table. For any other result from the match
specification the object is retained. This is a more general
- call than the <seealso marker="#match_delete/2">
- <c>match_delete/2</c></seealso> call.</p>
+ call than the <seemfa marker="#match_delete/2">
+ <c>match_delete/2</c></seemfa> call.</p>
<p>The function returns the number of objects
deleted from the table.</p>
<note>
@@ -1613,11 +1716,11 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
<fsummary>Match and replace objects atomically in an ETS table</fsummary>
<desc>
<p>Matches the objects in the table <c><anno>Tab</anno></c> using a
- <seealso marker="#match_spec">match specification</seealso>. For each
+ <seeerl marker="#match_spec">match specification</seeerl>. For each
matched object, the existing object is replaced with
the match specification result.</p>
<p>The match-and-replace operation for each individual object is guaranteed to be
- <seealso marker="#concurrency">atomic and isolated</seealso>. The
+ <seeerl marker="#concurrency">atomic and isolated</seeerl>. The
<c>select_replace</c> table traversal as a whole, like all other select functions,
does not give such guarantees.</p>
<p>The match specifiction must be guaranteed to <em>retain the key</em>
@@ -1651,13 +1754,13 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<name name="select_reverse" arity="1" since="OTP R14B"/>
<fsummary>Continue matching objects in an ETS table.</fsummary>
<desc>
- <p>Continues a match started with <seealso marker="#select_reverse/3">
- <c>select_reverse/3</c></seealso>. For tables of type
+ <p>Continues a match started with <seemfa marker="#select_reverse/3">
+ <c>select_reverse/3</c></seemfa>. For tables of type
<c>ordered_set</c>, the traversal of the table continues
to objects with keys earlier in the Erlang term order. The
returned list also contains objects with keys in reverse order.
For all other table types, the behavior is exactly that of
- <seealso marker="#select/1"><c>select/1</c></seealso>.</p>
+ <seemfa marker="#select/1"><c>select/1</c></seemfa>.</p>
<p><em>Example:</em></p>
<code>
1> T = ets:new(x,[ordered_set]).
@@ -1685,7 +1788,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<fsummary>Match the objects in an ETS table against a
match specification.</fsummary>
<desc>
- <p>Works like <seealso marker="#select/2"><c>select/2</c></seealso>,
+ <p>Works like <seemfa marker="#select/2"><c>select/2</c></seemfa>,
but returns the list in reverse order for table type <c>ordered_set</c>.
For all other table types, the return value is identical to that of
<c>select/2</c>.</p>
@@ -1697,7 +1800,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<fsummary>Match the objects in an ETS table against a
match specification and return part of the answers.</fsummary>
<desc>
- <p>Works like <seealso marker="#select/3"><c>select/3</c></seealso>,
+ <p>Works like <seemfa marker="#select/3"><c>select/3</c></seemfa>,
but for table type <c>ordered_set</c>
traversing is done starting at the last object in
Erlang term order and moves to the first. For all other table
@@ -1716,7 +1819,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<desc>
<p>Sets table options. The only allowed option to be set after the
table has been created is
- <seealso marker="#heir"><c>heir</c></seealso>.
+ <seeerl marker="#heir"><c>heir</c></seeerl>.
The calling process must be the table owner.</p>
</desc>
</func>
@@ -1738,7 +1841,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
the function fails with reason <c>badarg</c>.</p>
<p>Unless a table of type <c>set</c>, <c>bag</c>, or
<c>duplicate_bag</c> is protected using
- <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>,
+ <seemfa marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seemfa>,
a traversal can fail if
concurrent updates are made to the table. For table type
<c>ordered_set</c>, the function returns a list containing
@@ -1817,8 +1920,8 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<fsummary>Return a list of all objects in an ETS table.</fsummary>
<desc>
<p>Returns information about the table dumped to file by
- <seealso marker="#tab2file/2"><c>tab2file/2</c></seealso> or
- <seealso marker="#tab2file/3"><c>tab2file/3</c></seealso>.</p>
+ <seemfa marker="#tab2file/2"><c>tab2file/2</c></seemfa> or
+ <seemfa marker="#tab2file/3"><c>tab2file/3</c></seemfa>.</p>
<p>The following items are returned:</p>
<taglist>
<tag><c>name</c></tag>
@@ -1826,7 +1929,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<p>The name of the dumped table. If the table was a
named table, a table with the same name cannot exist when the
table is loaded from file with
- <seealso marker="#file2tab/2"><c>file2tab/2</c></seealso>.
+ <seemfa marker="#file2tab/2"><c>file2tab/2</c></seemfa>.
If the table is
not saved as a named table, this field has no significance
when loading the table from file.</p>
@@ -1867,8 +1970,8 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<item>
<p>The extended information written in the file footer to
allow stronger verification during table loading from file, as
- specified to <seealso marker="#tab2file/3">
- <c>tab2file/3</c></seealso>. Notice that this
+ specified to <seemfa marker="#tab2file/3">
+ <c>tab2file/3</c></seemfa>. Notice that this
function only tells <em>which</em> information is present, not
the values in the file footer. The value is a list containing one
or more of the atoms <c>object_count</c> and <c>md5sum</c>.</p>
@@ -1885,8 +1988,8 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</taglist>
<p>An error is returned if the file is inaccessible,
badly damaged, or not produced with
- <seealso marker="#tab2file/2"><c>tab2file/2</c></seealso> or
- <seealso marker="#tab2file/3"><c>tab2file/3</c></seealso>.</p>
+ <seemfa marker="#tab2file/2"><c>tab2file/2</c></seemfa> or
+ <seemfa marker="#tab2file/3"><c>tab2file/3</c></seemfa>.</p>
</desc>
</func>
@@ -1897,14 +2000,14 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<desc>
<p>Returns a Query List
Comprehension (QLC) query handle. The
- <seealso marker="qlc"><c>qlc</c></seealso> module provides
+ <seeerl marker="qlc"><c>qlc</c></seeerl> module provides
a query language aimed mainly at Mnesia, but ETS
tables, Dets tables,
and lists are also recognized by QLC as sources of
data. Calling <c>table/1,2</c> is the means to make the
ETS table <c>Tab</c> usable to QLC.</p>
<p>When there are only simple restrictions on the key position,
- QLC uses <seealso marker="#lookup/2"><c>lookup/2</c></seealso>
+ QLC uses <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>
to look up the keys. When
that is not possible, the whole table is traversed.
Option <c>traverse</c> determines how this is done:</p>
@@ -1912,24 +2015,24 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<tag><c>first_next</c></tag>
<item>
<p>The table is traversed one key at a time by calling
- <seealso marker="#first/1"><c>first/1</c></seealso> and
- <seealso marker="#next/2"><c>next/2</c></seealso>.</p>
+ <seemfa marker="#first/1"><c>first/1</c></seemfa> and
+ <seemfa marker="#next/2"><c>next/2</c></seemfa>.</p>
</item>
<tag><c>last_prev</c></tag>
<item>
<p>The table is traversed one key at a time by calling
- <seealso marker="#last/1"><c>last/1</c></seealso> and
- <seealso marker="#prev/2"><c>prev/2</c></seealso>.</p>
+ <seemfa marker="#last/1"><c>last/1</c></seemfa> and
+ <seemfa marker="#prev/2"><c>prev/2</c></seemfa>.</p>
</item>
<tag><c>select</c></tag>
<item>
<p>The table is traversed by calling
- <seealso marker="#select/3"><c>select/3</c></seealso> and
- <seealso marker="#select/1"><c>select/1</c></seealso>.
+ <seemfa marker="#select/3"><c>select/3</c></seemfa> and
+ <seemfa marker="#select/1"><c>select/1</c></seemfa>.
Option <c>n_objects</c> determines the number of objects
returned (the third argument of <c>select/3</c>); the
default is to return <c>100</c> objects at a time. The
- <seealso marker="#match_spec">match specification</seealso> (the
+ <seeerl marker="#match_spec">match specification</seeerl> (the
second argument of <c>select/3</c>) is assembled by QLC: simple
filters are translated into equivalent match specifications
while more complicated filters must be applied to all
@@ -1939,8 +2042,8 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
<tag><c>{select, <anno>MatchSpec</anno>}</c></tag>
<item>
<p>As for <c>select</c>, the table is traversed by calling
- <seealso marker="#select/3"><c>select/3</c></seealso> and
- <seealso marker="#select/1"><c>select/1</c></seealso>.
+ <seemfa marker="#select/3"><c>select/3</c></seemfa> and
+ <seemfa marker="#select/1"><c>select/1</c></seemfa>.
The difference is that the match specification is explicitly
specified. This is how to state match specifications that cannot
easily be expressed within the syntax provided by QLC.</p>
@@ -1978,8 +2081,8 @@ true</pre>
by either <em>comparing equal</em> the key of an object in an
<c>ordered_set</c> table, or <em>matching</em> in other types of
tables (for details on the difference, see
- <seealso marker="#lookup/2"><c>lookup/2</c></seealso> and
- <seealso marker="#new/2"><c>new/2</c></seealso>).</p>
+ <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa> and
+ <seemfa marker="#new/2"><c>new/2</c></seemfa>).</p>
</desc>
</func>
<func>
@@ -1988,8 +2091,8 @@ true</pre>
</fsummary>
<desc>
<p>This function is a utility to test a
- <seealso marker="#match_spec">match specification</seealso> used in
- calls to <seealso marker="#select/2"><c>select/2</c></seealso>.
+ <seeerl marker="#match_spec">match specification</seeerl> used in
+ calls to <seemfa marker="#select/2"><c>select/2</c></seemfa>.
The function both tests <c><anno>MatchSpec</anno></c> for "syntactic"
correctness and runs the match specification against object
<c><anno>Tuple</anno></c>.</p>
@@ -2004,8 +2107,8 @@ true</pre>
descriptions of what was wrong with the match specification.</p>
<p>This is a useful debugging and test tool, especially when
writing complicated <c>select/2</c> calls.</p>
- <p>See also: <seealso marker="erts:erlang#match_spec_test/3">
- erlang:match_spec_test/3</seealso>.</p>
+ <p>See also: <seemfa marker="erts:erlang#match_spec_test/3">
+ erlang:match_spec_test/3</seemfa>.</p>
</desc>
</func>
@@ -2040,7 +2143,7 @@ true</pre>
counters, without the trouble of having to look up an object, update
the object by incrementing an element, and insert the resulting
object into the table again. The operation is guaranteed to be
- <seealso marker="#concurrency">atomic and isolated</seealso>.</p>
+ <seeerl marker="#concurrency">atomic and isolated</seeerl>.</p>
<p>This function destructively update the object with key
<c><anno>Key</anno></c> in table <c><anno>Tab</anno></c> by adding
<c><anno>Incr</anno></c> to the element at position
@@ -2074,8 +2177,8 @@ true</pre>
by either <em>matching</em> the key of an object in a <c>set</c>
table, or <em>compare equal</em> to the key of an object in an
<c>ordered_set</c> table (for details on the difference, see
- <seealso marker="#lookup/2"><c>lookup/2</c></seealso> and
- <seealso marker="#new/2"><c>new/2</c></seealso>).</p>
+ <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa> and
+ <seemfa marker="#new/2"><c>new/2</c></seemfa>).</p>
<p>If a default object <c><anno>Default</anno></c> is specified,
it is used
as the object to be updated if the key is missing from the table. The
@@ -2133,8 +2236,8 @@ true</pre>
by either <em>matching</em> the key of an object in a <c>set</c>
table, or <em>compare equal</em> to the key of an object in an
<c>ordered_set</c> table (for details on the difference, see
- <seealso marker="#lookup/2"><c>lookup/2</c></seealso> and
- <seealso marker="#new/2"><c>new/2</c></seealso>).</p>
+ <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa> and
+ <seemfa marker="#new/2"><c>new/2</c></seemfa>).</p>
<p>The function fails with reason <c>badarg</c> in the following
situations:</p>
<list type="bulleted">
@@ -2151,7 +2254,7 @@ true</pre>
<fsummary>Retrieves the tid() of a named table.</fsummary>
<desc>
<p>This function returns the
- <seealso marker="#type-tid"><c>tid()</c></seealso> of the named table
+ <seetype marker="#tid"><c>tid()</c></seetype> of the named table
identified by <c><anno>TableName</anno></c>, or <c>undefined</c> if
no such table exists. The <c>tid()</c> can be used in place of the
table name in all operations, which is slightly faster since the name
diff --git a/lib/stdlib/doc/src/file_sorter.xml b/lib/stdlib/doc/src/file_sorter.xml
index 0bd2399bff..3a90ade7dd 100644
--- a/lib/stdlib/doc/src/file_sorter.xml
+++ b/lib/stdlib/doc/src/file_sorter.xml
@@ -242,7 +242,7 @@ output(L) ->
<item>
<p><c>{file_error, FileName, file:posix()}</c> - For an
explanation of <c>file:posix()</c>, see
- <seealso marker="kernel:file"><c>file(3)</c></seealso>.</p>
+ <seeerl marker="kernel:file"><c>file(3)</c></seeerl>.</p>
</item>
<item>
<p><c>{premature_eof, FileName}</c> - End-of-file was
diff --git a/lib/stdlib/doc/src/filelib.xml b/lib/stdlib/doc/src/filelib.xml
index 5df415834f..75040e03de 100644
--- a/lib/stdlib/doc/src/filelib.xml
+++ b/lib/stdlib/doc/src/filelib.xml
@@ -37,14 +37,14 @@
</modulesummary>
<description>
<p>This module contains utilities on a higher level than the
- <seealso marker="kernel:file"><c>file</c></seealso> module.</p>
+ <seeerl marker="kernel:file"><c>file</c></seeerl> module.</p>
<p>This module does not support "raw" filenames (that is, files whose
names do not comply with the expected encoding). Such files are ignored
by the functions in this module.</p>
<p>For more information about raw filenames, see the
- <seealso marker="kernel:file"><c>file</c></seealso> module.</p>
+ <seeerl marker="kernel:file"><c>file</c></seeerl> module.</p>
<note>
<p>
@@ -123,7 +123,7 @@
<c><anno>F</anno></c> in directory <c><anno>Dir</anno></c> that match
the regular expression <c><anno>RegExp</anno></c> (for a description
of the allowed regular expressions,
- see the <seealso marker="re"><c>re</c></seealso> module).
+ see the <seeerl marker="re"><c>re</c></seeerl> module).
If <c><anno>Recursive</anno></c> is <c>true</c>, all subdirectories
to <c>Dir</c>
are processed. The regular expression matching is only done on
@@ -137,7 +137,7 @@
not conform to the expected character encoding (that is, are not
encoded in valid UTF-8).</p>
<p>For more information about raw filenames, see the
- <seealso marker="kernel:file"><c>file</c></seealso> module.</p>
+ <seeerl marker="kernel:file"><c>file</c></seeerl> module.</p>
</desc>
</func>
@@ -256,7 +256,7 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
<fsummary>Match filenames using Unix-style wildcards starting at a
specified directory.</fsummary>
<desc>
- <p>Same as <seealso marker="#wildcard/1"><c>wildcard/1</c></seealso>,
+ <p>Same as <seemfa marker="#wildcard/1"><c>wildcard/1</c></seemfa>,
except that <c><anno>Cwd</anno></c> is used instead of the working
directory.</p>
</desc>
@@ -273,8 +273,8 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
corresponding path ending in <c>"src"</c> should be searched.</p>
<p>If <c><anno>Rules</anno></c> is left out or is an empty list, the
default system rules are used. See also the Kernel application
- parameter <seealso
- marker="kernel:kernel_app#source_search_rules"><c>source_search_rules</c></seealso>.</p>
+ parameter <seeapp
+ marker="kernel:kernel_app#source_search_rules"><c>source_search_rules</c></seeapp>.</p>
</desc>
</func>
<func>
@@ -297,14 +297,44 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
look for a file with a corresponding extension <c>.erl</c> by
replacing the suffix <c>"ebin"</c> of the object directory path with
<c>"src"</c> or <c>"src/*"</c>.
- The file search is done through <seealso
- marker="#find_file/3"><c>find_file/3</c></seealso>. The directory of
+ The file search is done through <seemfa
+ marker="#find_file/3"><c>find_file/3</c></seemfa>. The directory of
the object file is always tried before any other directory specified
by the rules.</p>
<p>If <c><anno>Rules</anno></c> is left out or is an empty list, the
default system rules are used. See also the Kernel application
- parameter <seealso
- marker="kernel:kernel_app#source_search_rules"><c>source_search_rules</c></seealso>.</p>
+ parameter <seeapp
+ marker="kernel:kernel_app#source_search_rules"><c>source_search_rules</c></seeapp>.</p>
+ </desc>
+ </func>
+ <func>
+ <name name="safe_relative_path" arity="2" since="OTP 23.0"/>
+ <fsummary>Sanitize a relative path to avoid directory traversal attacks.</fsummary>
+ <desc>
+ <p>Sanitizes the relative path by eliminating ".." and "."
+ components to protect against directory traversal attacks.
+ Either returns the sanitized path name, or the atom
+ <c>unsafe</c> if the path is unsafe.
+ The path is considered unsafe in the following circumstances:</p>
+ <list type="bulleted">
+ <item><p>The path is not relative.</p></item>
+ <item><p>A ".." component would climb up above the root of
+ the relative path.</p></item>
+ <item><p>A symbolic link in the path points above the root
+ of the relative path.</p></item>
+ </list>
+ <p><em>Examples:</em></p>
+ <pre>
+1> <input>{ok, Cwd} = file:get_cwd().</input>
+...
+2> <input>filelib:safe_relative_path("dir/sub_dir/..", Cwd).</input>
+"dir"
+3> <input>filelib:safe_relative_path("dir/..", Cwd).</input>
+[]
+4> <input>filelib:safe_relative_path("dir/../..", Cwd).</input>
+unsafe
+5> <input>filelib:safe_relative_path("/abs/path", Cwd).</input>
+unsafe</pre>
</desc>
</func>
</funcs>
diff --git a/lib/stdlib/doc/src/filename.xml b/lib/stdlib/doc/src/filename.xml
index 70a1e60499..ec2ff0f40c 100644
--- a/lib/stdlib/doc/src/filename.xml
+++ b/lib/stdlib/doc/src/filename.xml
@@ -44,21 +44,21 @@
<p>In Windows, all functions return filenames with forward slashes
only, even if the arguments contain backslashes. To normalize a
filename by removing redundant directory separators, use
- <seealso marker="#join/1"><c>join/1</c></seealso>.</p>
+ <seemfa marker="#join/1"><c>join/1</c></seemfa>.</p>
<p>
The module supports
- <seealso marker="unicode_usage#notes-about-raw-filenames">raw
- filenames</seealso> in the way that if a binary is
+ <seeguide marker="unicode_usage#notes-about-raw-filenames">raw
+ filenames</seeguide> in the way that if a binary is
present, or the filename cannot be interpreted according to the return
- value of <seealso marker="kernel:file#native_name_encoding/0">
- <c>file:native_name_encoding/0</c></seealso>, a raw filename is also
+ value of <seemfa marker="kernel:file#native_name_encoding/0">
+ <c>file:native_name_encoding/0</c></seemfa>, a raw filename is also
returned. For example, <c>join/1</c> provided with a path component
that is a binary (and cannot be interpreted under the current
native filename encoding) results in a raw filename that is returned
(the join operation is performed of course). For more information
about raw filenames, see the
- <seealso marker="kernel:file"><c>file</c></seealso> module.</p>
+ <seeerl marker="kernel:file"><c>file</c></seeerl> module.</p>
<note>
<p>
@@ -123,7 +123,7 @@
<fsummary>Convert a filename to an absolute name, relative a specified
directory.</fsummary>
<desc>
- <p>Same as <seealso marker="#absname/1"><c>absname/1</c></seealso>,
+ <p>Same as <seemfa marker="#absname/1"><c>absname/1</c></seemfa>,
except that the directory to which the filename is to be made
relative is specified in argument <c><anno>Dir</anno></c>.</p>
</desc>
@@ -134,7 +134,7 @@
<fsummary>Join an absolute directory with a relative filename.</fsummary>
<desc>
<p>Joins an absolute directory with a relative filename. Similar to
- <seealso marker="#join/2"><c>join/2</c></seealso>, but on platforms
+ <seemfa marker="#join/2"><c>join/2</c></seemfa>, but on platforms
with tight restrictions on raw filename length and no support for
symbolic links (read: VxWorks), leading parent directory components
in <c><anno>Filename</anno></c> are matched against trailing
@@ -157,10 +157,10 @@
<type variable="Application"/>
<desc>
<p>
- Equivalent to <seealso marker="#basedir_3_1">
- basedir(<anno>PathType</anno>, <anno>Application</anno>, #{})</seealso>
- or <seealso marker="#basedir_3_2">
-basedir(<anno>PathsType</anno>, <anno>Application</anno>, #{})</seealso>.
+ Equivalent to <seeerl marker="#basedir_3_1">
+ basedir(<anno>PathType</anno>, <anno>Application</anno>, #{})</seeerl>
+ or <seeerl marker="#basedir_3_2">
+basedir(<anno>PathsType</anno>, <anno>Application</anno>, #{})</seeerl>.
</p>
</desc>
</func>
@@ -395,12 +395,12 @@ true
<fsummary>Find the filename and compiler options for a module.</fsummary>
<desc>
<p>Finds the source filename and compiler options for a module.
- The result can be fed to <seealso marker="compiler:compile#file/2">
- <c>compile:file/2</c></seealso> to compile the file again.</p>
+ The result can be fed to <seemfa marker="compiler:compile#file/2">
+ <c>compile:file/2</c></seemfa> to compile the file again.</p>
<warning>
- <p>This function is deprecated. Use <seealso marker="filelib#find_source/1">
- <c>filelib:find_source/1</c></seealso> instead for finding source files.</p>
- <p>If possible, use the <seealso marker="beam_lib"><c>beam_lib(3)</c></seealso>
+ <p>This function is deprecated. Use <seemfa marker="filelib#find_source/1">
+ <c>filelib:find_source/1</c></seemfa> instead for finding source files.</p>
+ <p>If possible, use the <seeerl marker="beam_lib"><c>beam_lib(3)</c></seeerl>
module to extract the compiler options and the abstract code
format from the Beam file and compile that instead.</p></warning>
<p>Argument <c><anno>Beam</anno></c>, which can be a string or an atom,
@@ -415,8 +415,8 @@ true
object is located matches <c><anno>BinSuffix</anno></c>, then the
name created by replacing <c><anno>BinSuffix</anno></c> with
<c><anno>SourceSuffix</anno></c> is expanded by calling
- <seealso marker="filelib#wildcard/1">
- <c>filelib:wildcard/1</c></seealso>.
+ <seemfa marker="filelib#wildcard/1">
+ <c>filelib:wildcard/1</c></seemfa>.
If a regular file is found among the matches, the function
returns that location together with <c><anno>Options</anno></c>.
Otherwise the next rule is tried, and so on.</p>
@@ -494,7 +494,7 @@ true
shell and native applications on the current platform. On Windows,
forward slashes are converted to backward slashes. On all
platforms, the name is normalized as done by
- <seealso marker="#join/1"><c>join/1</c></seealso>.</p>
+ <seemfa marker="#join/1"><c>join/1</c></seemfa>.</p>
<p><em>Examples:</em></p>
<pre>
19> <input>filename:nativename("/usr/local/bin/").</input> % Unix
@@ -570,6 +570,10 @@ true
<item><p>A ".." component would climb up above the root of
the relative path.</p></item>
</list>
+ <warning>
+ <p>This function is deprecated. Use <seemfa marker="filelib#safe_relative_path/2">
+ <c>filelib:safe_relative_path/2</c></seemfa> instead for sanitizing paths.</p>
+ </warning>
<p><em>Examples:</em></p>
<pre>
1> <input>filename:safe_relative_path("dir/sub_dir/..").</input>
diff --git a/lib/stdlib/doc/src/gb_sets.xml b/lib/stdlib/doc/src/gb_sets.xml
index a9596c6e4d..ffdcf54c5a 100644
--- a/lib/stdlib/doc/src/gb_sets.xml
+++ b/lib/stdlib/doc/src/gb_sets.xml
@@ -62,44 +62,44 @@
<title>Compatibility</title>
<p>The following functions in this module also exist and provides
the same functionality in the
- <seealso marker="sets"><c>sets(3)</c></seealso> and
- <seealso marker="ordsets"><c>ordsets(3)</c></seealso>
+ <seeerl marker="sets"><c>sets(3)</c></seeerl> and
+ <seeerl marker="ordsets"><c>ordsets(3)</c></seeerl>
modules. That is, by only changing the module name for each call,
you can try out different set representations.</p>
<list type="bulleted">
- <item><seealso marker="#add_element/2"><c>add_element/2</c></seealso>
+ <item><seemfa marker="#add_element/2"><c>add_element/2</c></seemfa>
</item>
- <item><seealso marker="#del_element/2"><c>del_element/2</c></seealso>
+ <item><seemfa marker="#del_element/2"><c>del_element/2</c></seemfa>
</item>
- <item><seealso marker="#filter/2"><c>filter/2</c></seealso>
+ <item><seemfa marker="#filter/2"><c>filter/2</c></seemfa>
</item>
- <item><seealso marker="#fold/3"><c>fold/3</c></seealso>
+ <item><seemfa marker="#fold/3"><c>fold/3</c></seemfa>
</item>
- <item><seealso marker="#from_list/1"><c>from_list/1</c></seealso>
+ <item><seemfa marker="#from_list/1"><c>from_list/1</c></seemfa>
</item>
- <item><seealso marker="#intersection/1"><c>intersection/1</c></seealso>
+ <item><seemfa marker="#intersection/1"><c>intersection/1</c></seemfa>
</item>
- <item><seealso marker="#intersection/2"><c>intersection/2</c></seealso>
+ <item><seemfa marker="#intersection/2"><c>intersection/2</c></seemfa>
</item>
- <item><seealso marker="#is_element/2"><c>is_element/2</c></seealso>
+ <item><seemfa marker="#is_element/2"><c>is_element/2</c></seemfa>
</item>
- <item><seealso marker="#is_empty/1"><c>is_empty/1</c></seealso>
+ <item><seemfa marker="#is_empty/1"><c>is_empty/1</c></seemfa>
</item>
- <item><seealso marker="#is_set/1"><c>is_set/1</c></seealso>
+ <item><seemfa marker="#is_set/1"><c>is_set/1</c></seemfa>
</item>
- <item><seealso marker="#is_subset/2"><c>is_subset/2</c></seealso>
+ <item><seemfa marker="#is_subset/2"><c>is_subset/2</c></seemfa>
</item>
- <item><seealso marker="#new/0"><c>new/0</c></seealso>
+ <item><seemfa marker="#new/0"><c>new/0</c></seemfa>
</item>
- <item><seealso marker="#size/1"><c>size/1</c></seealso>
+ <item><seemfa marker="#size/1"><c>size/1</c></seemfa>
</item>
- <item><seealso marker="#subtract/2"><c>subtract/2</c></seealso>
+ <item><seemfa marker="#subtract/2"><c>subtract/2</c></seemfa>
</item>
- <item><seealso marker="#to_list/1"><c>to_list/1</c></seealso>
+ <item><seemfa marker="#to_list/1"><c>to_list/1</c></seemfa>
</item>
- <item><seealso marker="#union/1"><c>union/1</c></seealso>
+ <item><seemfa marker="#union/1"><c>union/1</c></seemfa>
</item>
- <item><seealso marker="#union/2"><c>union/2</c></seealso>
+ <item><seemfa marker="#union/2"><c>union/2</c></seemfa>
</item>
</list>
</section>
@@ -324,10 +324,10 @@
<desc>
<p>Returns an iterator that can be used for traversing the entries of
<c><anno>Set</anno></c>; see
- <seealso marker="#next/1"><c>next/1</c></seealso>. The implementation
+ <seemfa marker="#next/1"><c>next/1</c></seemfa>. The implementation
of this is very efficient; traversing the whole set using
<c>next/1</c> is only slightly slower than getting the list of all
- elements using <seealso marker="#to_list/1"><c>to_list/1</c></seealso>
+ elements using <seemfa marker="#to_list/1"><c>to_list/1</c></seemfa>
and traversing that.
The main advantage of the iterator approach is that it does
not require the complete list of all elements to be built in
@@ -342,9 +342,9 @@
<desc>
<p>Returns an iterator that can be used for traversing the
entries of <c><anno>Set</anno></c>; see
- <seealso marker="#next/1"><c>next/1</c></seealso>.
+ <seemfa marker="#next/1"><c>next/1</c></seemfa>.
The difference as compared to the iterator returned by
- <seealso marker="#iterator/1"><c>iterator/1</c></seealso>
+ <seemfa marker="#iterator/1"><c>iterator/1</c></seemfa>
is that the first element greater than
or equal to <c><anno>Element</anno></c> is returned.</p>
</desc>
@@ -467,9 +467,9 @@
<section>
<title>See Also</title>
- <p><seealso marker="gb_trees"><c>gb_trees(3)</c></seealso>,
- <seealso marker="ordsets"><c>ordsets(3)</c></seealso>,
- <seealso marker="sets"><c>sets(3)</c></seealso></p>
+ <p><seeerl marker="gb_trees"><c>gb_trees(3)</c></seeerl>,
+ <seeerl marker="ordsets"><c>ordsets(3)</c></seeerl>,
+ <seeerl marker="sets"><c>sets(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/gb_trees.xml b/lib/stdlib/doc/src/gb_trees.xml
index 9a9f61ae52..c8944347a5 100644
--- a/lib/stdlib/doc/src/gb_trees.xml
+++ b/lib/stdlib/doc/src/gb_trees.xml
@@ -202,11 +202,11 @@
<desc>
<p>Returns an iterator that can be used for traversing the
entries of <c><anno>Tree</anno></c>; see
- <seealso marker="#next/1"><c>next/1</c></seealso>. The implementation
+ <seemfa marker="#next/1"><c>next/1</c></seemfa>. The implementation
of this is very efficient; traversing the whole tree using
<c>next/1</c> is only slightly slower than getting the list
of all elements using
- <seealso marker="#to_list/1"><c>to_list/1</c></seealso>
+ <seemfa marker="#to_list/1"><c>to_list/1</c></seemfa>
and traversing that.
The main advantage of the iterator approach is that it does
not require the complete list of all elements to be built in
@@ -221,9 +221,9 @@
<desc>
<p>Returns an iterator that can be used for traversing the
entries of <c><anno>Tree</anno></c>; see
- <seealso marker="#next/1"><c>next/1</c></seealso>.
+ <seemfa marker="#next/1"><c>next/1</c></seemfa>.
The difference as compared to the iterator returned by
- <seealso marker="#iterator/1"><c>iterator/1</c></seealso>
+ <seemfa marker="#iterator/1"><c>iterator/1</c></seemfa>
is that the first key greater than
or equal to <c><anno>Key</anno></c> is returned.</p>
</desc>
@@ -360,8 +360,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="dict"><c>dict(3)</c></seealso>,
- <seealso marker="gb_sets"><c>gb_sets(3)</c></seealso></p>
+ <p><seeerl marker="dict"><c>dict(3)</c></seeerl>,
+ <seeerl marker="gb_sets"><c>gb_sets(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml
index 2915c4f507..2ddff240d2 100644
--- a/lib/stdlib/doc/src/gen_event.xml
+++ b/lib/stdlib/doc/src/gen_event.xml
@@ -39,7 +39,7 @@
set of interface functions and includes functionality for tracing and
error reporting. It also fits into an OTP supervision tree. For more
information, see
- <seealso marker="doc/design_principles:events">OTP Design Principles</seealso>.
+ <seeguide marker="system/design_principles:events">OTP Design Principles</seeguide>.
</p>
<p>Each event handler is implemented as a callback module exporting
@@ -50,6 +50,7 @@
gen_event module Callback module
---------------- ---------------
gen_event:start
+gen_event:start_monitor
gen_event:start_link -----> -
gen_event:add_handler
@@ -58,6 +59,7 @@ gen_event:add_sup_handler -----> Module:init/1
gen_event:notify
gen_event:sync_notify -----> Module:handle_event/2
+gen_event:send_request
gen_event:call -----> Module:handle_call/2
- -----> Module:handle_info/2
@@ -81,21 +83,21 @@ gen_event:stop -----> Module:terminate/2
an installed event handler fails with <c>Reason</c>, or returns a
bad value <c>Term</c>, the event manager does not fail. It deletes
the event handler by calling callback function
- <seealso marker="#Module:terminate/2"><c>Module:terminate/2</c></seealso>,
+ <seemfa marker="#Module:terminate/2"><c>Module:terminate/2</c></seemfa>,
giving as argument
<c>{error,{'EXIT',Reason}}</c> or <c>{error,Term}</c>, respectively.
No other event handler is affected.</p>
<p>A <c>gen_event</c> process handles system messages as described in
- <seealso marker="sys"><c>sys(3)</c></seealso>. The <c>sys</c> module
+ <seeerl marker="sys"><c>sys(3)</c></seeerl>. The <c>sys</c> module
can be used for debugging an event manager.</p>
<p>Notice that an event manager <em>does</em> trap exit signals
automatically.</p>
<p>The <c>gen_event</c> process can go into hibernation
- (see <seealso marker="erts:erlang#hibernate/3">
- <c>erlang:hibernate/3</c></seealso>) if a callback function in
+ (see <seemfa marker="erts:erlang#hibernate/3">
+ <c>erlang:hibernate/3</c></seemfa>) if a callback function in
a handler module specifies <c>hibernate</c> in its return value.
This can be useful if the server is expected to be idle for a long
time. However, use this feature with care, as hibernation
@@ -126,6 +128,15 @@ gen_event:stop -----> Module:terminate/2
<datatype>
<name name="del_handler_ret"/>
</datatype>
+ <datatype>
+ <name name="request_id"/>
+ <desc>
+ <p>
+ A request handle, see <seemfa marker="#send_request/3"> <c>send_request/3</c> </seemfa>
+ for details.
+ </p>
+ </desc>
+ </datatype>
</datatypes>
<funcs>
@@ -147,7 +158,7 @@ gen_event:stop -----> Module:terminate/2
<desc>
<p>Adds a new event handler to event manager <c>EventMgrRef</c>.
The event manager calls
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
to initiate the event handler and its internal state.</p>
<p><c>EventMgrRef</c> can be any of the following:</p>
<list type="bulleted">
@@ -195,14 +206,14 @@ gen_event:stop -----> Module:terminate/2
</type>
<desc>
<p>Adds a new event handler in the same way as
- <seealso marker="#add_handler/3"><c>add_handler/3</c></seealso>,
+ <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa>,
but also supervises the connection between the event handler
and the calling process.</p>
<list type="bulleted">
<item>If the calling process later terminates with <c>Reason</c>,
the event manager deletes the event handler by calling
- <seealso marker="#Module:terminate/2">
- <c>Module:terminate/2</c></seealso>
+ <seemfa marker="#Module:terminate/2">
+ <c>Module:terminate/2</c></seemfa>
with <c>{stop,Reason}</c> as argument.
</item>
<item>
@@ -224,10 +235,10 @@ gen_event:stop -----> Module:terminate/2
<p><c>{swapped,NewHandler,Pid}</c>, if the process <c>Pid</c>
has replaced the event handler with another event handler
<c>NewHandler</c> using a call to
- <seealso marker="#swap_handler/3">
- <c>swap_handler/3</c></seealso> or
- <seealso marker="#swap_sup_handler/3">
- <c>swap_sup_handler/3</c></seealso>.</p>
+ <seemfa marker="#swap_handler/3">
+ <c>swap_handler/3</c></seemfa> or
+ <seemfa marker="#swap_sup_handler/3">
+ <c>swap_sup_handler/3</c></seemfa>.</p>
</item>
<item>
<p>A term, if the event handler is removed because of an error.
@@ -236,7 +247,7 @@ gen_event:stop -----> Module:terminate/2
</item>
</list>
<p>For a description of the arguments and return values, see
- <seealso marker="#add_handler/3"><c>add_handler/3</c></seealso>.</p>
+ <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa>.</p>
</desc>
</func>
@@ -263,10 +274,10 @@ gen_event:stop -----> Module:terminate/2
<p>Makes a synchronous call to event handler <c>Handler</c>
installed in event manager <c>EventMgrRef</c> by sending a
request and waiting until a reply arrives or a time-out occurs.
- The event manager calls <seealso marker="#Module:handle_call/2">
- <c>Module:handle_call/2</c></seealso> to handle the request.</p>
+ The event manager calls <seemfa marker="#Module:handle_call/2">
+ <c>Module:handle_call/2</c></seemfa> to handle the request.</p>
<p>For a description of <c>EventMgrRef</c> and <c>Handler</c>, see
- <seealso marker="#add_handler/3"><c>add_handler/3</c></seealso>.</p>
+ <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa>.</p>
<p><c>Request</c> is any term that is passed as one of
the arguments to <c>Module:handle_call/2</c>.</p>
<p><c>Timeout</c> is an integer greater than zero that specifies
@@ -284,6 +295,40 @@ gen_event:stop -----> Module:terminate/2
</desc>
</func>
+
+ <func>
+ <name since="OTP-23">check_response(Msg, RequestId) -> Result</name>
+ <fsummary>Check if a message is a reply from a server.</fsummary>
+ <type>
+ <v>Msg = term()</v>
+ <v>RequestId = request_id()</v>
+ <v>Result = {reply, Reply} | no_reply | {error, Error}</v>
+ <v>Reply = Error = term()</v>
+ </type>
+ <desc>
+ <p>
+ This function is used to check if a previously received
+ message, for example by <c>receive</c> or
+ <c>handle_info/2</c>, is a result of a request made with
+ <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa>.
+ If <c>Msg</c> is a reply to the handle <c>RequestId</c>
+ the result of the request is returned in <c>Reply</c>.
+ Otherwise returns <c>no_reply</c> and no cleanup is done, and
+ thus the function shall be invoked repeatedly until a reply
+ is returned.
+ </p>
+ <p>
+ If the specified event handler is not
+ installed, the function returns <c>{error,bad_module}</c>. If
+ the callback function fails with <c>Reason</c> or returns an
+ unexpected value <c>Term</c>, this function returns
+ <c>{error,{'EXIT',Reason}}</c> or <c>{error,Term}</c>,
+ respectively. If the event manager dies before or during the
+ request this function returns <c>{error,{Reason, EventMgrRef}}</c>.
+ </p>
+ </desc>
+ </func>
+
<func>
<name since="">delete_handler(EventMgrRef, Handler, Args) -> Result</name>
<fsummary>Delete an event handler from a generic event manager.</fsummary>
@@ -302,11 +347,11 @@ gen_event:stop -----> Module:terminate/2
<desc>
<p>Deletes an event handler from event manager
<c>EventMgrRef</c>. The event manager calls
- <seealso marker="#Module:terminate/2">
- <c>Module:terminate/2</c></seealso> to terminate the event
+ <seemfa marker="#Module:terminate/2">
+ <c>Module:terminate/2</c></seemfa> to terminate the event
handler.</p>
<p>For a description of <c>EventMgrRef</c> and <c>Handler</c>, see
- <seealso marker="#add_handler/3"><c>add_handler/3</c></seealso>.</p>
+ <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa>.</p>
<p><c>Args</c> is any term that is passed as one of
the arguments to <c>Module:terminate/2</c>.</p>
<p>The return value is the return value of <c>Module:terminate/2</c>.
@@ -331,23 +376,66 @@ gen_event:stop -----> Module:terminate/2
<desc>
<p>Sends an event notification to event manager
<c>EventMgrRef</c>. The event manager calls
- <seealso marker="#Module:handle_event/2">
- <c>Module:handle_event/2</c></seealso>
+ <seemfa marker="#Module:handle_event/2">
+ <c>Module:handle_event/2</c></seemfa>
for each installed event handler to handle the event.</p>
<p><c>notify/2</c> is asynchronous and returns immediately after
the event notification has been sent. <c>sync_notify/2</c> is
synchronous in the sense that it returns <c>ok</c> after
the event has been handled by all event handlers.</p>
<p>For a description of <c>EventMgrRef</c>, see
- <seealso marker="#add_handler/3"><c>add_handler/3</c></seealso>.</p>
+ <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa>.</p>
<p><c>Event</c> is any term that is passed as one of
- the arguments to <seealso marker="#Module:handle_event/2">
- <c>Module:handle_event/2</c></seealso>.</p>
+ the arguments to <seemfa marker="#Module:handle_event/2">
+ <c>Module:handle_event/2</c></seemfa>.</p>
<p><c>notify/1</c> does not fail even if the specified event manager
does not exist, unless it is specified as <c>Name</c>.</p>
</desc>
</func>
+
+ <func>
+ <name since="OTP-23">send_request(EventMgrRef, Handler, Request) -> RequestId</name>
+ <fsummary>Send a request to a generic event manager.</fsummary>
+ <type>
+ <v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}</v>
+ <v>&nbsp;&nbsp;| {via,Module,ViaName} | pid()</v>
+ <v>&nbsp;Node = atom()</v>
+ <v>&nbsp;GlobalName = ViaName = term()</v>
+ <v>Handler = Module | {Module,Id}</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Id = term()</v>
+ <v>Request = term()</v>
+ <v>RequestId = request_id()</v>
+ </type>
+ <desc>
+ <p>
+ Sends a request to event handler <c>Handler</c> installed in
+ event manager <c>EventMgrRef</c> and returns a handle
+ <c>RequestId</c>. The return value <c>RequestId</c> shall
+ later be used with <seemfa marker="#wait_response/2">
+ <c>wait_response/2</c></seemfa> or <seemfa
+ marker="#check_response/2">
+ <c>check_response/2</c></seemfa> in the same process to
+ fetch the actual result of the request.
+ </p>
+ <p>
+ The call <c>gen_event:wait_response(gen_event:send_request(EventMgrRef,Handler,Request), Timeout)</c>
+ can be seen as equivalent to
+ <seemfa marker="#call/3"><c>gen_event:call(EventMgrRef,Handler,Request,Timeout)</c></seemfa>,
+ ignoring the error handling.
+ </p>
+ <p>
+ The event manager calls <seemfa marker="#Module:handle_call/2">
+ <c>Module:handle_call/2</c></seemfa> to handle the request.
+ </p>
+ <p>
+ <c>Request</c> is any term that is passed as one of
+ the arguments to <c>Module:handle_call/3</c>.
+ </p>
+ </desc>
+ </func>
+
<func>
<name since="">start() -> Result</name>
<name since="">start(EventMgrName | Options) -> Result</name>
@@ -370,7 +458,7 @@ gen_event:stop -----> Module:terminate/2
manager that is not part of a supervision tree and thus has
no supervisor.</p>
<p>For a description of the arguments and return values, see
- <seealso marker="#start_link/0"><c>start_link/0,1</c></seealso>.</p>
+ <seemfa marker="#start_link/0"><c>start_link/0,1</c></seemfa>.</p>
</desc>
</func>
@@ -405,8 +493,8 @@ gen_event:stop -----> Module:terminate/2
<item>
<p>If <c>EventMgrName={global,GlobalName}</c>, the event manager is
registered globally as <c>GlobalName</c> using
- <seealso marker="kernel:global#register_name/2">
- <c>global:register_name/2</c></seealso>.
+ <seemfa marker="kernel:global#register_name/2">
+ <c>global:register_name/2</c></seemfa>.
If no name is provided, the event manager is not registered.</p>
</item>
<item>
@@ -416,14 +504,14 @@ gen_event:stop -----> Module:terminate/2
<c>register_name/2</c>, <c>unregister_name/1</c>,
<c>whereis_name/1</c>, and <c>send/2</c>, which are to behave
as the corresponding functions in
- <seealso marker="kernel:global"><c>global</c></seealso>.
+ <seeerl marker="kernel:global"><c>global</c></seeerl>.
Thus, <c>{via,global,GlobalName}</c> is a valid reference.</p>
</item>
<item>
<p>If option <c>{hibernate_after,HibernateAfterTimeout}</c> is present, the <c>gen_event</c>
process awaits any message for <c>HibernateAfterTimeout</c> milliseconds and
if no message is received, the process goes into hibernation automatically
- (by calling <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>).</p>
+ (by calling <seemfa marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seemfa>).</p>
</item>
</list>
<p>If the event manager is successfully created, the function
@@ -436,6 +524,40 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
+ <name since="OTP 23.0">start_monitor() -> Result</name>
+ <name since="OTP 23.0">start_monitor(EventMgrName | Options) -> Result</name>
+ <name since="OTP 23.0">start_monitor(EventMgrName, Options) -> Result</name>
+ <fsummary>Create a stand-alone event manager process.</fsummary>
+ <type>
+ <v>EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>&nbsp;GlobalName = ViaName = term()</v>
+ <v>Options = [Option]</v>
+ <v>&nbsp;Option = {debug,Dbgs} | {timeout,Time} | {hibernate_after,HibernateAfterTimeout} | {spawn_opt,SOpts}</v>
+ <v>&nbsp;&nbsp;Dbgs = [Dbg]</v>
+ <v>&nbsp;&nbsp;&nbsp;Dbg = trace | log | statistics | {log_to_file,FileName} | {install,{Func,FuncState}}</v>
+ <v>&nbsp;&nbsp;SOpts = [term()]</v>
+ <v>Result = {ok,{Pid,Mon}} | {error,{already_started,Pid}}</v>
+ <v>&nbsp;Pid = pid()</v>
+ </type>
+ <desc>
+ <p>Creates a stand-alone event manager process, that is, an event
+ manager that is not part of a supervision tree (and thus has
+ no supervisor) and atomically sets up a monitor to
+ the newly created process.</p>
+ <p>For a description of the arguments and return values, see
+ <seemfa marker="#start_link/0"><c>start_link/0,1</c></seemfa>.
+ Note that the return value on successful start differs from
+ <c>start_link/3,4</c>. <c>start_monitor/3,4</c> will return
+ <c>{ok,{Pid,Mon}}</c> where <c>Pid</c> is the process identifier
+ of the process, and <c>Mon</c> is a reference to the monitor
+ set up to monitor the process. If the start is not successful,
+ the caller will be blocked until the <c>DOWN</c> message has
+ been received and removed from the message queue.</p>
+ </desc>
+ </func>
+
+ <func>
<name since="">stop(EventMgrRef) -> ok</name>
<name since="OTP 18.0">stop(EventMgrRef, Reason, Timeout) -> ok</name>
<fsummary>Terminate a generic event manager.</fsummary>
@@ -451,14 +573,14 @@ gen_event:stop -----> Module:terminate/2
<p>Orders event manager <c>EventMgrRef</c> to exit with
the specifies <c>Reason</c> and waits for it to
terminate. Before terminating, <c>gen_event</c> calls
- <seealso marker="#Module:terminate/2">
- <c>Module:terminate(stop,...)</c></seealso>
+ <seemfa marker="#Module:terminate/2">
+ <c>Module:terminate(stop,...)</c></seemfa>
for each installed event handler.</p>
<p>The function returns <c>ok</c> if the event manager terminates
with the expected reason. Any other reason than <c>normal</c>,
<c>shutdown</c>, or <c>{shutdown,Term}</c> causes an
error report to be issued using
- <seealso marker="kernel:logger"><c>logger(3)</c></seealso>.
+ <seeerl marker="kernel:logger"><c>logger(3)</c></seeerl>.
The default <c>Reason</c> is <c>normal</c>.</p>
<p><c>Timeout</c> is an integer greater than zero that
specifies how many milliseconds to wait for the event manager to
@@ -469,7 +591,7 @@ gen_event:stop -----> Module:terminate/2
<p>If the process does not exist, a <c>noproc</c> exception
is raised.</p>
<p>For a description of <c>EventMgrRef</c>, see
- <seealso marker="#add_handler/3"><c>add_handler/3</c></seealso>.</p>
+ <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa>.</p>
</desc>
</func>
@@ -493,7 +615,7 @@ gen_event:stop -----> Module:terminate/2
<p>Replaces an old event handler with a new event handler in
event manager <c>EventMgrRef</c>.</p>
<p>For a description of the arguments, see
- <seealso marker="#add_handler/3"><c>add_handler/3</c></seealso>.</p>
+ <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa>.</p>
<p>First the old event handler <c>Handler1</c> is deleted.
The event manager calls <c>Module1:terminate(Args1, ...)</c>,
where <c>Module1</c> is the callback module of <c>Handler1</c>,
@@ -541,7 +663,50 @@ gen_event:stop -----> Module:terminate/2
in the same way as <c>swap_handler/3</c>, but also supervises
the connection between <c>Handler2</c> and the calling process.</p>
<p>For a description of the arguments and return values, see
- <seealso marker="#swap_handler/3"><c>swap_handler/3</c></seealso>.</p>
+ <seemfa marker="#swap_handler/3"><c>swap_handler/3</c></seemfa>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP-23">wait_response(RequestId, Timeout) -> Result</name>
+ <fsummary>Wait for a reply from a server.</fsummary>
+ <type>
+ <v>RequestId = request_id()</v>
+ <v>Reply = term()</v>
+ <v>Timeout = timeout()</v>
+ <v>Result = {reply, Reply} | timeout | {error, Error}</v>
+ <v>Reply = Error = term()</v>
+ </type>
+ <desc>
+ <p>
+ This function is used to wait for a reply of a request made with
+ <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa>
+ from the event manager. This function must be called from the same
+ process from which <seemfa marker="#send_request/3"><c>send_request/3</c></seemfa>
+ was made.
+ </p>
+ <p>
+ <c>Timeout</c> is an integer greater then or equal to zero
+ that specifies how many milliseconds to wait for an reply, or
+ the atom <c>infinity</c> to wait indefinitely.
+ If no reply is received within the specified
+ time, the function returns <c>timeout</c> and no cleanup is
+ done, and thus the function must be invoked repeatedly until a
+ reply is returned.
+ </p>
+ <p>
+ The return value <c>Reply</c> is defined in the return value
+ of <c>Module:handle_call/3</c>.
+ </p>
+ <p>
+ If the specified event handler is not
+ installed, the function returns <c>{error,bad_module}</c>. If
+ the callback function fails with <c>Reason</c> or returns an
+ unexpected value <c>Term</c>, this function returns
+ <c>{error,{'EXIT',Reason}}</c> or <c>{error,Term}</c>,
+ respectively. If the event manager dies before or during the
+ request this function returns <c>{error,{Reason, EventMgrRef}}</c>.
+ </p>
</desc>
</func>
@@ -562,18 +727,19 @@ gen_event:stop -----> Module:terminate/2
<p>Returns a list of all event handlers installed in event
manager <c>EventMgrRef</c>.</p>
<p>For a description of <c>EventMgrRef</c> and <c>Handler</c>, see
- <seealso marker="#add_handler/3"><c>add_handler/3</c></seealso>.</p>
+ <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa>.</p>
</desc>
</func>
</funcs>
- <section>
- <title>Callback Functions</title>
- <p>The following functions are to be exported from a <c>gen_event</c>
- callback module.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>The following functions are to be exported from a <c>gen_event</c>
+ callback module.</p>
+ </fsdescription>
<func>
<name since="">Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
@@ -596,8 +762,8 @@ gen_event:stop -----> Module:terminate/2
upgrade/downgrade, that is, when the instruction
<c>{update,Module,Change,...}</c>, where
<c>Change={advanced,Extra}</c>, is specified in the <c>.appup</c>
- file. For more information, see <seealso
- marker="doc/design_principles:users_guide">OTP Design Principles</seealso>.</p>
+ file. For more information, see <seeguide
+ marker="system/design_principles:index">OTP Design Principles</seeguide>.</p>
<p>For an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and for a downgrade,
<c>OldVsn</c> is <c>{down,Vsn}</c>. <c>Vsn</c> is defined by the
<c>vsn</c> attribute(s) of the old version of the callback module
@@ -630,8 +796,8 @@ gen_event:stop -----> Module:terminate/2
<p>This function is called by a <c>gen_event</c> process in the
following situations:</p>
<list type="bulleted">
- <item>One of <seealso marker="sys#get_status/1">
- <c>sys:get_status/1,2</c></seealso>
+ <item>One of <seemfa marker="sys#get_status/1">
+ <c>sys:get_status/1,2</c></seemfa>
is invoked to get the <c>gen_event</c> status. <c>Opt</c> is set
to the atom <c>normal</c> for this case.</item>
<item>The event handler terminates abnormally and <c>gen_event</c>
@@ -684,14 +850,14 @@ gen_event:stop -----> Module:terminate/2
</type>
<desc>
<p>Whenever an event manager receives a request sent using
- <seealso marker="#call/3"><c>call/3,4</c></seealso>,
+ <seemfa marker="#call/3"><c>call/3,4</c></seemfa>,
this function is called for
the specified event handler to handle the request.</p>
<p><c>Request</c> is the <c>Request</c> argument of <c>call/3,4</c>.</p>
<p><c>State</c> is the internal state of the event handler.</p>
<p>The return values are the same as for
- <seealso marker="#Module:handle_event/2">
- <c>Module:handle_event/2</c></seealso>
+ <seemfa marker="#Module:handle_event/2">
+ <c>Module:handle_event/2</c></seemfa>
except that they also contain a term <c>Reply</c>, which is the reply
to the client as the return value of <c>call/3,4</c>.</p>
</desc>
@@ -714,8 +880,8 @@ gen_event:stop -----> Module:terminate/2
</type>
<desc>
<p>Whenever an event manager receives an event sent using
- <seealso marker="#notify/2"><c>notify/2</c></seealso> or
- <seealso marker="#sync_notify/2"><c>sync_notify/2</c></seealso>,
+ <seemfa marker="#notify/2"><c>notify/2</c></seemfa> or
+ <seemfa marker="#sync_notify/2"><c>sync_notify/2</c></seemfa>,
this function is called for each installed event handler to handle
the event.</p>
<p><c>Event</c> is the <c>Event</c> argument of
@@ -731,8 +897,8 @@ gen_event:stop -----> Module:terminate/2
<item>
<p>If <c>{ok,NewState,hibernate}</c> is returned, the event
manager also goes into hibernation (by calling
- <seealso marker="proc_lib#hibernate/3">
- <c>proc_lib:hibernate/3</c></seealso>), waiting for the next
+ <seemfa marker="proc_lib#hibernate/3">
+ <c>proc_lib:hibernate/3</c></seemfa>), waiting for the next
event to occur. It is sufficient that one of the
event handlers return <c>{ok,NewState,hibernate}</c> for the
whole event manager process to hibernate.</p>
@@ -743,7 +909,7 @@ gen_event:stop -----> Module:terminate/2
first calling <c>Module:terminate(Args1,NewState)</c> and then
<c>Module2:init({Args2,Term})</c>, where <c>Term</c> is the return
value of <c>Module:terminate/2</c>. For more information, see
- <seealso marker="#swap_handler/3"><c>swap_handler/3</c></seealso>.
+ <seemfa marker="#swap_handler/3"><c>swap_handler/3</c></seemfa>.
</p>
</item>
<item>
@@ -782,8 +948,8 @@ gen_event:stop -----> Module:terminate/2
a synchronous request (or a system message).</p>
<p><c>Info</c> is the received message.</p>
<p>For a description of <c>State</c> and possible return values, see
- <seealso marker="#Module:handle_event/2">
- <c>Module:handle_event/2</c></seealso>.</p>
+ <seemfa marker="#Module:handle_event/2">
+ <c>Module:handle_event/2</c></seemfa>.</p>
</desc>
</func>
@@ -800,26 +966,26 @@ gen_event:stop -----> Module:terminate/2
<p>Whenever a new event handler is added to an event manager,
this function is called to initialize the event handler.</p>
<p>If the event handler is added because of a call to
- <seealso marker="#add_handler/3"><c>add_handler/3</c></seealso> or
- <seealso marker="#add_sup_handler/3">
- <c>add_sup_handler/3</c></seealso>, <c>InitArgs</c> is
+ <seemfa marker="#add_handler/3"><c>add_handler/3</c></seemfa> or
+ <seemfa marker="#add_sup_handler/3">
+ <c>add_sup_handler/3</c></seemfa>, <c>InitArgs</c> is
the <c>Args</c> argument of these functions.</p>
<p>If the event handler replaces another event handler because of
a call to
- <seealso marker="#swap_handler/3"><c>swap_handler/3</c></seealso> or
- <seealso marker="#swap_sup_handler/3">
- <c>swap_sup_handler/3</c></seealso>, or because of a <c>swap</c>
+ <seemfa marker="#swap_handler/3"><c>swap_handler/3</c></seemfa> or
+ <seemfa marker="#swap_sup_handler/3">
+ <c>swap_sup_handler/3</c></seemfa>, or because of a <c>swap</c>
return tuple from one of the other callback functions,
<c>InitArgs</c> is a tuple <c>{Args,Term}</c>, where <c>Args</c> is
the argument provided in the function call/return tuple and
<c>Term</c> is the result of terminating the old event handler, see
- <seealso marker="#swap_handler/3"><c>swap_handler/3</c></seealso>.</p>
+ <seemfa marker="#swap_handler/3"><c>swap_handler/3</c></seemfa>.</p>
<p>If successful, the function returns <c>{ok,State}</c>
or <c>{ok,State,hibernate}</c>, where <c>State</c> is the
initial internal state of the event handler.</p>
<p>If <c>{ok,State,hibernate}</c> is returned, the event
- manager goes into hibernation (by calling <seealso
- marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>),
+ manager goes into hibernation (by calling <seemfa
+ marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seemfa>),
waiting for the next event to occur.</p>
</desc>
</func>
@@ -840,13 +1006,13 @@ gen_event:stop -----> Module:terminate/2
</note>
<p>Whenever an event handler is deleted from an event manager,
this function is called. It is to be the opposite of
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
and do any necessary cleaning up.</p>
<p>If the event handler is deleted because of a call to
- <seealso marker="#delete_handler/3"><c>delete_handler/3</c></seealso>,
- <seealso marker="#swap_handler/3"><c>swap_handler/3</c></seealso>, or
- <seealso marker="#swap_sup_handler/3">
- <c>swap_sup_handler/3</c></seealso>, <c>Arg</c> is
+ <seemfa marker="#delete_handler/3"><c>delete_handler/3</c></seemfa>,
+ <seemfa marker="#swap_handler/3"><c>swap_handler/3</c></seemfa>, or
+ <seemfa marker="#swap_sup_handler/3">
+ <c>swap_sup_handler/3</c></seemfa>, <c>Arg</c> is
the <c>Args</c> argument of this function call.</p>
<p><c>Arg={stop,Reason}</c> if the event handler has a supervised
connection to a process that has terminated with reason
@@ -879,7 +1045,7 @@ gen_event:stop -----> Module:terminate/2
<section>
<title>See Also</title>
- <p><seealso marker="supervisor"><c>supervisor(3)</c></seealso>,
- <seealso marker="sys"><c>sys(3)</c></seealso></p>
+ <p><seeerl marker="supervisor"><c>supervisor(3)</c></seeerl>,
+ <seeerl marker="sys"><c>sys(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/gen_fsm.xml b/lib/stdlib/doc/src/gen_fsm.xml
index 7187630b43..03fce5aae4 100644
--- a/lib/stdlib/doc/src/gen_fsm.xml
+++ b/lib/stdlib/doc/src/gen_fsm.xml
@@ -32,14 +32,14 @@
<modulesummary>Deprecated and replaced by gen_statem </modulesummary>
<description>
- <p> Deprecated and replaced by <seealso marker="gen_statem"><c>gen_statem</c></seealso> </p>
+ <p> Deprecated and replaced by <seeerl marker="gen_statem"><c>gen_statem</c></seeerl> </p>
</description>
<section>
<marker id="Migration to gen_statem"/>
<title>Migration to gen_statem</title>
<p>Here follows a simple example of turning a gen_fsm into
- a <seealso marker="gen_statem"><c>gen_statem</c></seealso>. The example comes
+ a <seeerl marker="gen_statem"><c>gen_statem</c></seeerl>. The example comes
from the previous Users Guide for <c>gen_fsm</c> </p>
<code type="erl">
diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml
index a4554d7657..4abb91439e 100644
--- a/lib/stdlib/doc/src/gen_server.xml
+++ b/lib/stdlib/doc/src/gen_server.xml
@@ -36,8 +36,8 @@
this module has a standard set of interface functions and
includes functionality for tracing and error reporting. It also
fits into an OTP supervision tree. For more information, see section
- <seealso marker="doc/design_principles:gen_server_concepts">
- gen_server Behaviour</seealso> in OTP Design Principles.</p>
+ <seeguide marker="system/design_principles:gen_server_concepts">
+ gen_server Behaviour</seeguide> in OTP Design Principles.</p>
<p>A <c>gen_server</c> process assumes all specific parts to be located in
a callback module exporting a predefined set of functions.
@@ -48,11 +48,13 @@
gen_server module Callback module
----------------- ---------------
gen_server:start
+gen_server:start_monitor
gen_server:start_link -----> Module:init/1
gen_server:stop -----> Module:terminate/2
gen_server:call
+gen_server:send_request
gen_server:multi_call -----> Module:handle_call/3
gen_server:cast
@@ -70,7 +72,7 @@ gen_server:abcast -----> Module:handle_cast/2
<c>gen_server</c> process terminates.</p>
<p>A <c>gen_server</c> process handles system messages as described in
- <seealso marker="sys"><c>sys(3)</c></seealso>. The <c>sys</c> module
+ <seeerl marker="sys"><c>sys(3)</c></seeerl>. The <c>sys</c> module
can be used for debugging a <c>gen_server</c> process.</p>
<p>Notice that a <c>gen_server</c> process does not trap exit signals
@@ -82,8 +84,8 @@ gen_server:abcast -----> Module:handle_cast/2
arguments are specified.</p>
<p>The <c>gen_server</c> process can go into hibernation
- (see <seealso marker="erts:erlang#hibernate/3">
- <c>erlang:hibernate/3</c></seealso>) if a callback
+ (see <seemfa marker="erts:erlang#hibernate/3">
+ <c>erlang:hibernate/3</c></seemfa>) if a callback
function specifies <c>'hibernate'</c> instead of a time-out value. This
can be useful if the server is expected to be idle for a long
time. However, use this feature with care, as hibernation
@@ -116,10 +118,10 @@ gen_server:abcast -----> Module:handle_cast/2
returns immediately and ignores nodes that do not exist, or
where the <c>gen_server</c> <c>Name</c> does not exist.
The <c>gen_server</c> processes call
- <seealso marker="#Module:handle_cast/2">
- <c>Module:handle_cast/2</c></seealso> to handle the request.</p>
+ <seemfa marker="#Module:handle_cast/2">
+ <c>Module:handle_cast/2</c></seemfa> to handle the request.</p>
<p>For a description of the arguments, see
- <seealso marker="#multi_call/2"><c>multi_call/2,3,4</c></seealso>.</p>
+ <seemfa marker="#multi_call/2"><c>multi_call/2,3,4</c></seemfa>.</p>
</desc>
</func>
@@ -141,8 +143,8 @@ gen_server:abcast -----> Module:handle_cast/2
<c>gen_server</c> process
by sending a request and waiting until a reply arrives or a
time-out occurs. The <c>gen_server</c> process calls
- <seealso marker="#Module:handle_call/3">
- <c>Module:handle_call/3</c></seealso> to handle the request.</p>
+ <seemfa marker="#Module:handle_call/3">
+ <c>Module:handle_call/3</c></seemfa> to handle the request.</p>
<p><c>ServerRef</c> can be any of the following:</p>
<list type="bulleted">
<item>The pid</item>
@@ -155,8 +157,8 @@ gen_server:abcast -----> Module:handle_cast/2
<item><c>{via,Module,ViaName}</c>, if the <c>gen_server</c> process is
registered through an alternative process registry</item>
</list>
- <p><c>Request</c> is any term that is passed as one of
- the arguments to <c>Module:handle_call/3</c>.</p>
+ <p><c>Request</c> is any term that is passed as the
+ first argument to <c>Module:handle_call/3</c>.</p>
<p><c>Timeout</c> is an integer greater than zero that
specifies how many milliseconds to wait for a reply, or
the atom <c>infinity</c> to wait indefinitely. Defaults to
@@ -190,16 +192,53 @@ gen_server:abcast -----> Module:handle_cast/2
and returns <c>ok</c> immediately, ignoring
if the destination node or <c>gen_server</c> process does not exist.
The <c>gen_server</c> process calls
- <seealso marker="#Module:handle_cast/2">
- <c>Module:handle_cast/2</c></seealso> to handle the request.</p>
+ <seemfa marker="#Module:handle_cast/2">
+ <c>Module:handle_cast/2</c></seemfa> to handle the request.</p>
<p>For a description of <c>ServerRef</c>, see
- <seealso marker="#call/2"><c>call/2,3</c></seealso>.</p>
+ <seemfa marker="#call/2"><c>call/2,3</c></seemfa>.</p>
<p><c>Request</c> is any term that is passed as one
of the arguments to <c>Module:handle_cast/2</c>.</p>
</desc>
</func>
<func>
+ <name since="OTP-23">check_response(Msg, RequestId) -> Result</name>
+ <fsummary>Check if a message is a reply from a server.</fsummary>
+ <type>
+ <v>RequestId = term()</v>
+ <v>Result = {reply, Reply} | no_reply | {error, {Reason, ServerRef}}</v>
+ <v>Msg = Reply = term()</v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = term()</v>
+ <v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v>
+ <v>&nbsp;&nbsp;| {via,Module,ViaName} | pid()</v>
+ <v>&nbsp;Node = atom()</v>
+ <v>&nbsp;GlobalName = ViaName = term()</v>
+ </type>
+ <desc>
+ <p>
+ This function is used to check if a previously received
+ message, for example by <c>receive</c> or
+ <c>handle_info/2</c>, is a result of a request made with
+ <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>.
+ If <c>Msg</c> is a reply to the handle <c>RequestId</c>
+ the result of the request is returned in <c>Reply</c>.
+ Otherwise returns <c>no_reply</c> and no cleanup is done, and
+ thus the function must be invoked repeatedly until a reply
+ is returned.
+ </p>
+ <p>
+ The return value <c>Reply</c> is defined in the return value
+ of <c>Module:handle_call/3</c>.
+ </p>
+ <p>
+ The function returns an error if the <c>gen_server</c>
+ dies before or during this request.
+ </p>
+ </desc>
+ </func>
+
+ <func>
<name since="">enter_loop(Module, Options, State)</name>
<name since="">enter_loop(Module, Options, State, ServerName)</name>
<name since="">enter_loop(Module, Options, State, Timeout)</name>
@@ -225,20 +264,20 @@ gen_server:abcast -----> Module:handle_cast/2
process receive
loop and becomes a <c>gen_server</c> process. The process
<em>must</em> have been started using one of the start functions in
- <seealso marker="proc_lib"><c>proc_lib(3)</c></seealso>. The user is
+ <seeerl marker="proc_lib"><c>proc_lib(3)</c></seeerl>. The user is
responsible for any initialization of the process, including
registering a name for it.</p>
<p>This function is useful when a more complex initialization procedure
is needed than the <c>gen_server</c> process behavior provides.</p>
<p><c>Module</c>, <c>Options</c>, and <c>ServerName</c> have
the same meanings as when calling
- <seealso marker="#start_link/3"><c>start[_link]/3,4</c></seealso>.
+ <seemfa marker="#start_link/3"><c>start[_link|_monitor]/3,4</c></seemfa>.
However, if <c>ServerName</c> is specified, the process must
have been registered accordingly <em>before</em> this function
is called.</p>
<p><c>State</c> and <c>Timeout</c> have the same meanings as in
the return value of
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>.
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>.
The callback module <c>Module</c> does not need to
export an <c>init/1</c> function.</p>
<p>The function fails if the calling process was not started by a
@@ -268,8 +307,8 @@ gen_server:abcast -----> Module:handle_cast/2
registered as <c>Name</c> at the specified nodes by first
sending a request to every node and then waits for
the replies. The <c>gen_server</c> process calls
- <seealso marker="#Module:handle_call/3">
- <c>Module:handle_call/3</c></seealso> to handle the request.</p>
+ <seemfa marker="#Module:handle_call/3">
+ <c>Module:handle_call/3</c></seemfa> to handle the request.</p>
<p>The function returns a tuple <c>{Replies,BadNodes}</c>, where
<c>Replies</c> is a list of <c>{Node,Reply}</c> and
<c>BadNodes</c> is a list of node that either did not exist,
@@ -280,8 +319,8 @@ gen_server:abcast -----> Module:handle_cast/2
<c>[node()|nodes()]</c>.</p>
<p><c>Name</c> is the locally registered name of each
<c>gen_server</c> process.</p>
- <p><c>Request</c> is any term that is passed as one of
- the arguments to <c>Module:handle_call/3</c>.</p>
+ <p><c>Request</c> is any term that is passed as the first
+ argument to <c>Module:handle_call/3</c>.</p>
<p><c>Timeout</c> is an integer greater than zero that
specifies how many milliseconds to wait for each reply, or
the atom <c>infinity</c> to wait indefinitely. Defaults
@@ -307,27 +346,73 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name since="">reply(Client, Reply) -> Result</name>
+ <name since="">reply(Client, Reply) -> ok</name>
<fsummary>Send a reply to a client.</fsummary>
<type>
<v>Client - see below</v>
<v>Reply = term()</v>
- <v>Result = term()</v>
</type>
<desc>
<p>This function can be used by a <c>gen_server</c> process to
explicitly send a reply to a client that called
- <seealso marker="#call/2"><c>call/2,3</c></seealso> or
- <seealso marker="#multi_call/2"><c>multi_call/2,3,4</c></seealso>,
+ <seemfa marker="#call/2"><c>call/2,3</c></seemfa> or
+ <seemfa marker="#multi_call/2"><c>multi_call/2,3,4</c></seemfa>,
when the reply cannot be defined in the return value of
- <seealso marker="#Module:handle_call/3">
- <c>Module:handle_call/3</c></seealso>.</p>
+ <seemfa marker="#Module:handle_call/3">
+ <c>Module:handle_call/3</c></seemfa>.</p>
<p><c>Client</c> must be the <c>From</c> argument provided to
the callback function. <c>Reply</c> is any term
given back to the client as the return value of
<c>call/2,3</c> or <c>multi_call/2,3,4</c>.</p>
- <p>The return value <c>Result</c> is not further defined, and
- is always to be ignored.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP-23">send_request(ServerRef, Request) -> RequestId</name>
+ <fsummary>Sends a request to a generic server.</fsummary>
+ <type>
+ <v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v>
+ <v>&nbsp;&nbsp;| {via,Module,ViaName} | pid()</v>
+ <v>&nbsp;Node = atom()</v>
+ <v>&nbsp;GlobalName = ViaName = term()</v>
+ <v>RequestId = term()</v>
+ <v>Timeout = int()>0 | infinity</v>
+ <v>Request = term()</v>
+ </type>
+ <desc>
+ <p>
+ Sends a request to the <c>ServerRef</c> of the
+ <c>gen_server</c> process and returns a handle <c>RequestId</c>.
+ The return value <c>RequestId</c> shall later be used with
+ <seemfa marker="#wait_response/2"> <c>wait_response/2</c></seemfa> or
+ <seemfa marker="#check_response/2"> <c>check_response/2</c></seemfa>
+ to fetch the actual result of the request.
+ </p>
+ <p>
+ The call <c>gen_server:wait_response(gen_server:send_request(ServerRef,Request), Timeout)</c>
+ can be seen as equivalent to
+ <seemfa marker="#call/3"><c>gen_server:call(Server,Request,Timeout)</c></seemfa>,
+ ignoring the error handling.
+ </p>
+ <p>
+ The <c>gen_server</c> process calls
+ <seemfa marker="#Module:handle_call/3"> <c>Module:handle_call/3</c></seemfa>
+ to handle the request.
+ </p>
+ <p><c>ServerRef</c> can be any of the following:</p>
+ <list type="bulleted">
+ <item>The pid</item>
+ <item><c>Name</c>, if the <c>gen_server</c> process is locally
+ registered</item>
+ <item><c>{Name,Node}</c>, if the <c>gen_server</c> process is locally
+ registered at another node</item>
+ <item><c>{global,GlobalName}</c>, if the <c>gen_server</c> process is
+ globally registered</item>
+ <item><c>{via,Module,ViaName}</c>, if the <c>gen_server</c> process is
+ registered through an alternative process registry</item>
+ </list>
+ <p><c>Request</c> is any term that is passed as the first
+ argument to <c>Module:handle_call/3</c>.</p>
</desc>
</func>
@@ -356,7 +441,7 @@ gen_server:abcast -----> Module:handle_cast/2
<c>gen_server</c> process that is not part of a supervision tree
and thus has no supervisor.</p>
<p>For a description of arguments and return values, see
- <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>.</p>
+ <seemfa marker="#start_link/3"><c>start_link/3,4</c></seemfa>.</p>
</desc>
</func>
@@ -387,7 +472,7 @@ gen_server:abcast -----> Module:handle_cast/2
the supervisor. For example, it ensures that
the <c>gen_server</c> process is linked to the supervisor.</p>
<p>The <c>gen_server</c> process calls
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> to
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa> to
initialize. To ensure a synchronized startup procedure,
<c>start_link/3,4</c> does not return until
<c>Module:init/1</c> has returned.</p>
@@ -399,8 +484,8 @@ gen_server:abcast -----> Module:handle_cast/2
<item>
<p>If <c>ServerName={global,GlobalName}</c>, the <c>gen_server</c>
process id registered globally as <c>GlobalName</c> using
- <seealso marker="kernel:global#register_name/2">
- <c>global:register_name/2</c></seealso> If no name is
+ <seemfa marker="kernel:global#register_name/2">
+ <c>global:register_name/2</c></seemfa> If no name is
provided, the <c>gen_server</c> process is not registered.</p>
</item>
<item>
@@ -410,14 +495,14 @@ gen_server:abcast -----> Module:handle_cast/2
<c>register_name/2</c>, <c>unregister_name/1</c>,
<c>whereis_name/1</c>, and <c>send/2</c>, which are to behave
like the corresponding functions in
- <seealso marker="kernel:global"><c>global</c></seealso>.
+ <seeerl marker="kernel:global"><c>global</c></seeerl>.
Thus, <c>{via,global,GlobalName}</c> is a valid reference.</p>
</item>
</list>
<p><c>Module</c> is the name of the callback module.</p>
<p><c>Args</c> is any term that is passed as
the argument to
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>.</p>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>.</p>
<list type="bulleted">
<item>
<p>If option <c>{timeout,Time}</c> is present, the <c>gen_server</c>
@@ -429,21 +514,21 @@ gen_server:abcast -----> Module:handle_cast/2
<p>If option <c>{hibernate_after,HibernateAfterTimeout}</c> is present, the <c>gen_server</c>
process awaits any message for <c>HibernateAfterTimeout</c> milliseconds and
if no message is received, the process goes into hibernation automatically
- (by calling <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>).</p>
+ (by calling <seemfa marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seemfa>).</p>
</item>
<item>
<p>If option <c>{debug,Dbgs}</c> is present,
the corresponding <c>sys</c> function is called for each
item in <c>Dbgs</c>; see
- <seealso marker="sys"><c>sys(3)</c></seealso>.</p>
+ <seeerl marker="sys"><c>sys(3)</c></seeerl>.</p>
</item>
<item>
<p>If option <c>{spawn_opt,SOpts}</c> is present,
<c>SOpts</c> is passed as option list to
the <c>spawn_opt</c> BIF, which is used to spawn
the <c>gen_server</c> process; see
- <seealso marker="erts:erlang#spawn_opt/2">
- <c>spawn_opt/2</c></seealso>.</p>
+ <seemfa marker="erts:erlang#spawn_opt/2">
+ <c>spawn_opt/2</c></seemfa>.</p>
</item>
</list>
<note>
@@ -466,6 +551,43 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
+ <name since="OTP 23.0">start_monitor(Module, Args, Options) -> Result</name>
+ <name since="OTP 23.0">start_monitor(ServerName, Module, Args, Options) -> Result</name>
+ <fsummary>Create a standalone <c>gen_server</c> process.</fsummary>
+ <type>
+ <v>ServerName = {local,Name} | {global,GlobalName}</v>
+ <v>&nbsp;&nbsp;| {via,Module,ViaName}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>&nbsp;GlobalName = ViaName = term()</v>
+ <v>Module = atom()</v>
+ <v>Args = term()</v>
+ <v>Options = [Option]</v>
+ <v>&nbsp;Option = {debug,Dbgs} | {timeout,Time} | {hibernate_after,HibernateAfterTimeout} | {spawn_opt,SOpts}</v>
+ <v>&nbsp;&nbsp;Dbgs = [Dbg]</v>
+ <v>&nbsp;&nbsp;&nbsp;Dbg = trace | log | statistics | {log_to_file,FileName} | {install,{Func,FuncState}}</v>
+ <v>&nbsp;&nbsp;SOpts = [term()]</v>
+ <v>Result = {ok,{Pid,Mon}} | ignore | {error,Error}</v>
+ <v>&nbsp;Pid = pid()</v>
+ <v>&nbsp;Error = {already_started,Pid} | term()</v>
+ </type>
+ <desc>
+ <p>Creates a standalone <c>gen_server</c> process, that is, a
+ <c>gen_server</c> process that is not part of a supervision tree
+ (and thus has no supervisor) and atomically sets up a monitor to
+ the newly created server.</p>
+ <p>For a description of arguments and return values, see
+ <seemfa marker="#start_link/3"><c>start_link/3,4</c></seemfa>.
+ Note that the return value on successful start differs from
+ <c>start_link/3,4</c>. <c>start_monitor/3,4</c> will return
+ <c>{ok,{Pid,Mon}}</c> where <c>Pid</c> is the process identifier
+ of the server, and <c>Mon</c> is a reference to the monitor
+ set up to monitor the server. If the start is not successful,
+ the caller will be blocked until the <c>DOWN</c> message has
+ been received and removed from the message queue.</p>
+ </desc>
+ </func>
+
+ <func>
<name since="OTP 18.0">stop(ServerRef) -> ok</name>
<name since="OTP 18.0">stop(ServerRef, Reason, Timeout) -> ok</name>
<fsummary>Synchronously stop a generic server.</fsummary>
@@ -480,13 +602,13 @@ gen_server:abcast -----> Module:handle_cast/2
<desc>
<p>Orders a generic server to exit with the specified <c>Reason</c>
and waits for it to terminate. The <c>gen_server</c> process calls
- <seealso marker="#Module:terminate/2">
- <c>Module:terminate/2</c></seealso> before exiting.</p>
+ <seemfa marker="#Module:terminate/2">
+ <c>Module:terminate/2</c></seemfa> before exiting.</p>
<p>The function returns <c>ok</c> if the server terminates
with the expected reason. Any other reason than <c>normal</c>,
<c>shutdown</c>, or <c>{shutdown,Term}</c> causes an
error report to be issued using
- <seealso marker="kernel:logger"><c>logger(3)</c></seealso>.
+ <seeerl marker="kernel:logger"><c>logger(3)</c></seeerl>.
The default <c>Reason</c> is <c>normal</c>.</p>
<p><c>Timeout</c> is an integer greater than zero that
specifies how many milliseconds to wait for the server to
@@ -498,15 +620,59 @@ gen_server:abcast -----> Module:handle_cast/2
is raised.</p>
</desc>
</func>
+
+ <func>
+ <name since="OTP-23">wait_response(RequestId, Timeout) -> Result</name>
+ <fsummary>Wait for a reply from a server.</fsummary>
+ <type>
+ <v>RequestId = term()</v>
+ <v>Result = {reply, Reply} | timeout | {error, {Reason, ServerRef}}</v>
+ <v>Reply = term()</v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = term()</v>
+ <v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v>
+ <v>&nbsp;&nbsp;| {via,Module,ViaName} | pid()</v>
+ <v>&nbsp;Node = atom()</v>
+ <v>&nbsp;GlobalName = ViaName = term()</v>
+ </type>
+ <desc>
+ <p>
+ This function is used to wait for a reply of a request made with
+ <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>
+ from the <c>gen_server</c> process. This function must be called
+ from the same process from which
+ <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>
+ was made.
+ </p>
+ <p>
+ <c>Timeout</c> is an integer greater then or equal to zero
+ that specifies how many milliseconds to wait for an reply, or
+ the atom <c>infinity</c> to wait indefinitely.
+ If no reply is received within the specified
+ time, the function returns <c>timeout</c> and no cleanup is
+ done, and thus the function can be invoked repeatedly until a
+ reply is returned.
+ </p>
+ <p>
+ The return value <c>Reply</c> is defined in the return value
+ of <c>Module:handle_call/3</c>.
+ </p>
+ <p>
+ The function returns an error if the <c>gen_server</c>
+ dies before or during this request.
+ </p>
+ </desc>
+ </func>
</funcs>
- <section>
- <title>Callback Functions</title>
- <p>The following functions
- are to be exported from a <c>gen_server</c> callback module.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>The following functions
+ are to be exported from a <c>gen_server</c> callback module.</p>
+ </fsdescription>
<func>
<name since="">Module:code_change(OldVsn, State, Extra) -> {ok, NewState} | {error, Reason}</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
@@ -530,8 +696,8 @@ gen_server:abcast -----> Module:handle_cast/2
that is, when the instruction <c>{update,Module,Change,...}</c>,
where <c>Change={advanced,Extra}</c>, is specifed in
the <c>appup</c> file. For more information, see section
- <seealso marker="doc/design_principles:release_handling#instr">
- Release Handling Instructions</seealso> in OTP Design Principles.</p>
+ <seeguide marker="system/design_principles:release_handling#instr">
+ Release Handling Instructions</seeguide> in OTP Design Principles.</p>
<p>For an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and
for a downgrade, <c>OldVsn</c> is
<c>{down,Vsn}</c>. <c>Vsn</c> is defined by the <c>vsn</c>
@@ -570,8 +736,8 @@ gen_server:abcast -----> Module:handle_cast/2
following situations:</p>
<list type="bulleted">
<item>
- <p>One of <seealso marker="sys#get_status/1">
- <c>sys:get_status/1,2</c></seealso>
+ <p>One of <seemfa marker="sys#get_status/1">
+ <c>sys:get_status/1,2</c></seemfa>
is invoked to get the <c>gen_server</c> status. <c>Opt</c> is set
to the atom <c>normal</c>.</p>
</item>
@@ -631,8 +797,8 @@ gen_server:abcast -----> Module:handle_cast/2
</type>
<desc>
<p>Whenever a <c>gen_server</c> process receives a request sent using
- <seealso marker="#call/2"><c>call/2,3</c></seealso> or
- <seealso marker="#multi_call/2"><c>multi_call/2,3,4</c></seealso>,
+ <seemfa marker="#call/2"><c>call/2,3</c></seemfa> or
+ <seemfa marker="#multi_call/2"><c>multi_call/2,3,4</c></seemfa>,
this function is called to handle the request.</p>
<p><c>Request</c> is the <c>Request</c> argument provided
to <c>call</c> or <c>multi_call</c>.</p>
@@ -651,7 +817,7 @@ gen_server:abcast -----> Module:handle_cast/2
continues executing with the possibly updated internal state
<c>NewState</c>.</p>
<p>For a description of <c>Timeout</c> and <c>hibernate</c>, see
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>.</p>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>.</p>
</item>
<item>
<p>If <c>{noreply,NewState}</c> is returned,
@@ -659,7 +825,7 @@ gen_server:abcast -----> Module:handle_cast/2
<c>{noreply,NewState,hibernate}</c>, the <c>gen_server</c>
process continues executing with <c>NewState</c>. Any reply to
<c>From</c> must be specified explicitly using
- <seealso marker="#reply/2"><c>reply/2</c></seealso>.</p>
+ <seemfa marker="#reply/2"><c>reply/2</c></seemfa>.</p>
</item>
<item>
<p>If <c>{stop,Reason,Reply,NewState}</c> is returned,
@@ -668,7 +834,7 @@ gen_server:abcast -----> Module:handle_cast/2
<item>
<p>If <c>{stop,Reason,NewState}</c> is returned, any reply
to <c>From</c> must be specified explicitly using
- <seealso marker="#reply/2"><c>reply/2</c></seealso>.
+ <seemfa marker="#reply/2"><c>reply/2</c></seemfa>.
The <c>gen_server</c> process then calls
<c>Module:terminate(Reason,NewState)</c> and terminates.</p>
</item>
@@ -693,12 +859,12 @@ gen_server:abcast -----> Module:handle_cast/2
</type>
<desc>
<p>Whenever a <c>gen_server</c> process receives a request sent using
- <seealso marker="#cast/2"><c>cast/2</c></seealso> or
- <seealso marker="#abcast/2"><c>abcast/2,3</c></seealso>,
+ <seemfa marker="#cast/2"><c>cast/2</c></seemfa> or
+ <seemfa marker="#abcast/2"><c>abcast/2,3</c></seemfa>,
this function is called to handle the request.</p>
<p>For a description of the arguments and possible return values, see
- <seealso marker="#Module:handle_call/3">
- <c>Module:handle_call/3</c></seealso>.</p>
+ <seemfa marker="#Module:handle_call/3">
+ <c>Module:handle_call/3</c></seemfa>.</p>
</desc>
</func>
@@ -732,8 +898,8 @@ gen_server:abcast -----> Module:handle_cast/2
initialization or for splitting the work in a callback in
multiple steps, updating the process state along the way.</p>
<p>For a description of the other arguments and possible return values,
- see <seealso marker="#Module:handle_call/3">
- <c>Module:handle_call/3</c></seealso>.</p>
+ see <seemfa marker="#Module:handle_call/3">
+ <c>Module:handle_call/3</c></seemfa>.</p>
</desc>
</func>
@@ -764,8 +930,8 @@ gen_server:abcast -----> Module:handle_cast/2
<p><c>Info</c> is either the atom <c>timeout</c>, if a time-out
has occurred, or the received message.</p>
<p>For a description of the other arguments and possible return values,
- see <seealso marker="#Module:handle_call/3">
- <c>Module:handle_call/3</c></seealso>.</p>
+ see <seemfa marker="#Module:handle_call/3">
+ <c>Module:handle_call/3</c></seemfa>.</p>
</desc>
</func>
@@ -782,8 +948,9 @@ gen_server:abcast -----> Module:handle_cast/2
</type>
<desc>
<p>Whenever a <c>gen_server</c> process is started using
- <seealso marker="#start/3"><c>start/3,4</c></seealso> or
- <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>,
+ <seemfa marker="#start/3"><c>start/3,4</c></seemfa>,
+ <seemfa marker="#start_monitor/3"><c>start_monitor/3,4</c></seemfa>,
+ or <seemfa marker="#start_link/3"><c>start_link/3,4</c></seemfa>,
this function is called by the new process to initialize.</p>
<p><c>Args</c> is the <c>Args</c> argument provided to the start
function.</p>
@@ -795,15 +962,15 @@ gen_server:abcast -----> Module:handle_cast/2
unless a request or a message is received within
<c>Timeout</c> milliseconds. A time-out is represented by
the atom <c>timeout</c>, which is to be handled by the
- <seealso marker="#Module:handle_info/2">
- <c>Module:handle_info/2</c></seealso> callback function. The atom
+ <seemfa marker="#Module:handle_info/2">
+ <c>Module:handle_info/2</c></seemfa> callback function. The atom
<c>infinity</c> can be used to wait indefinitely, this is
the default value.</p>
<p>If <c>hibernate</c> is specified instead of a time-out value,
the process goes into
hibernation when waiting for the next message to arrive (by calling
- <seealso marker="proc_lib#hibernate/3">
- <c>proc_lib:hibernate/3</c></seealso>).</p>
+ <seemfa marker="proc_lib#hibernate/3">
+ <c>proc_lib:hibernate/3</c></seemfa>).</p>
<p>If the initialization fails, the function is to return
<c>{stop,Reason}</c>, where <c>Reason</c> is any term, or
<c>ignore</c>.</p>
@@ -825,7 +992,7 @@ gen_server:abcast -----> Module:handle_cast/2
</note>
<p>This function is called by a <c>gen_server</c> process when it is
about to terminate. It is to be the opposite of
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
and do any necessary cleaning up. When it returns,
the <c>gen_server</c> process terminates with <c>Reason</c>.
The return value is ignored.</p>
@@ -860,18 +1027,18 @@ gen_server:abcast -----> Module:handle_cast/2
<c>shutdown</c>, or <c>{shutdown,Term}</c>, the <c>gen_server</c>
process is assumed to terminate because of an error and
an error report is issued using
- <seealso marker="kernel:logger"><c>logger(3)</c></seealso>.</p>
+ <seeerl marker="kernel:logger"><c>logger(3)</c></seeerl>.</p>
</desc>
</func>
</funcs>
<section>
<title>See Also</title>
- <p><seealso marker="gen_event"><c>gen_event(3)</c></seealso>,
- <seealso marker="gen_statem"><c>gen_statem(3)</c></seealso>,
- <seealso marker="proc_lib"><c>proc_lib(3)</c></seealso>,
- <seealso marker="supervisor"><c>supervisor(3)</c></seealso>,
- <seealso marker="sys"><c>sys(3)</c></seealso></p>
+ <p><seeerl marker="gen_event"><c>gen_event(3)</c></seeerl>,
+ <seeerl marker="gen_statem"><c>gen_statem(3)</c></seeerl>,
+ <seeerl marker="proc_lib"><c>proc_lib(3)</c></seeerl>,
+ <seeerl marker="supervisor"><c>supervisor(3)</c></seeerl>,
+ <seeerl marker="sys"><c>sys(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index c12ad2deef..fa3f20535d 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -34,7 +34,7 @@
<p>
<c>gen_statem</c> provides a generic state machine behaviour
that for new code replaces its predecessor
- <seealso marker="gen_fsm"><c>gen_fsm</c></seealso>
+ <seeerl marker="gen_fsm"><c>gen_fsm</c></seeerl>
since Erlang/OTP 20.0. The <c>gen_fsm</c> behaviour remains
in OTP "as is".
</p>
@@ -42,13 +42,13 @@
<p>
If you are new to <c>gen_statem</c> and want an overview
of concepts and operation the section
- <seealso marker="doc/design_principles:statem">
+ <seeguide marker="system/design_principles:statem">
<c>gen_statem</c>&nbsp;Behaviour
- </seealso>
+ </seeguide>
located in the User's Guide
- <seealso marker="doc/design_principles:users_guide">
+ <seeguide marker="system/design_principles:index">
OTP Design Principles
- </seealso>
+ </seeguide>
is recommended to read before this reference manual,
possibly after the Description section you are reading here.
</p>
@@ -59,13 +59,13 @@
However, the generated descriptions also reflect the type hierarchy,
which sometimes makes it hard to get a good overview.
If so, see the section
- <seealso marker="doc/design_principles:statem">
+ <seeguide marker="system/design_principles:statem">
<c>gen_statem</c>&nbsp;Behaviour
- </seealso>
+ </seeguide>
in the
- <seealso marker="doc/design_principles:users_guide">
+ <seeguide marker="system/design_principles:index">
OTP Design Principles
- </seealso>
+ </seeguide>
User's Guide.
</p>
<note>
@@ -74,44 +74,44 @@
<item>
In OTP 19.1 a backwards incompatible change of
the return tuple from
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
was made and the mandatory callback function
- <seealso marker="#Module:callback_mode/0">
+ <seemfa marker="#Module:callback_mode/0">
<c>Module:callback_mode/0</c>
- </seealso>
+ </seemfa>
was introduced.
</item>
<item>
In OTP 20.0
- <seealso marker="#type-generic_timeout">
+ <seetype marker="#generic_timeout">
generic time-outs
- </seealso>
+ </seetype>
were added.
</item>
<item>
In OTP 22.1 time-out content
- <seealso marker="#type-timeout_update_action">
+ <seetype marker="#timeout_update_action">
<c>update</c>
- </seealso>
+ </seetype>
and explicit time-out
- <seealso marker="#type-timeout_cancel_action">
+ <seetype marker="#timeout_cancel_action">
<c>cancel</c>
- </seealso>
+ </seetype>
were added.
</item>
<item>
In OTP 22.3 the possibility to change the callback module
with actions
- <seealso marker="#type-action"><c>change_callback_module</c></seealso>,
- <seealso marker="#type-action"><c>push_callback_module</c></seealso> and
- <seealso marker="#type-action"><c>pop_callback_module</c></seealso>,
+ <seetype marker="#action"><c>change_callback_module</c></seetype>,
+ <seetype marker="#action"><c>push_callback_module</c></seetype> and
+ <seetype marker="#action"><c>pop_callback_module</c></seetype>,
was added.
</item>
</list>
</note>
<p>
<c>gen_statem</c> has got the same features that
- <seealso marker="gen_fsm"><c>gen_fsm</c></seealso>
+ <seeerl marker="gen_fsm"><c>gen_fsm</c></seeerl>
had and adds some really useful:
</p>
<list type="bulleted">
@@ -133,14 +133,14 @@
<p>
Two
- <seealso marker="#type-callback_mode"><em>callback modes</em></seealso>
+ <seetype marker="#callback_mode"><em>callback modes</em></seetype>
are supported:
</p>
<list type="bulleted">
<item>
<p>
One for finite-state machines
- (<seealso marker="gen_fsm"><c>gen_fsm</c></seealso> like),
+ (<seeerl marker="gen_fsm"><c>gen_fsm</c></seeerl> like),
which requires the state to be an atom and uses that state as
the name of the current callback function.
</p>
@@ -154,18 +154,18 @@
</list>
<p>
The callback model(s) for <c>gen_statem</c> differs from
- the one for <seealso marker="gen_fsm"><c>gen_fsm</c></seealso>,
+ the one for <seeerl marker="gen_fsm"><c>gen_fsm</c></seeerl>,
but it is still fairly easy to
- <seealso marker="gen_fsm#Migration to gen_statem">
+ <seeerl marker="gen_fsm#Migration to gen_statem">
rewrite from
- </seealso> <c>gen_fsm</c> to <c>gen_statem</c>.
+ </seeerl> <c>gen_fsm</c> to <c>gen_statem</c>.
</p>
<p>
A generic state machine server process (<c>gen_statem</c>) implemented
using this module has a standard set of interface functions
and includes functionality for tracing and error reporting.
It also fits into an OTP supervision tree. For more information, see
- <seealso marker="doc/design_principles:statem">OTP Design Principles</seealso>.
+ <seeguide marker="system/design_principles:statem">OTP Design Principles</seeguide>.
</p>
<p>
A <c>gen_statem</c> assumes all specific parts to be located in a
@@ -176,6 +176,7 @@
gen_statem module Callback module
----------------- ---------------
gen_statem:start
+gen_statem:start_monitor
gen_statem:start_link -----> Module:init/1
Server start or code change
@@ -185,6 +186,7 @@ gen_statem:stop -----> Module:terminate/3
gen_statem:call
gen_statem:cast
+gen_statem:send_request
erlang:send
erlang:'!' -----> Module:StateName/3
Module:handle_event/4
@@ -194,7 +196,7 @@ erlang:'!' -----> Module:StateName/3
- -----> Module:code_change/4</pre>
<p>
Events are of different
- <seealso marker="#type-event_type">types</seealso>,
+ <seetype marker="#event_type">types</seetype>,
so the callback functions can know the origin of an event
and how to respond.
</p>
@@ -202,39 +204,39 @@ erlang:'!' -----> Module:StateName/3
If a callback function fails or returns a bad value,
the <c>gen_statem</c> terminates, unless otherwise stated.
However, an exception of class
- <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>
+ <seemfa marker="erts:erlang#throw/1"><c>throw</c></seemfa>
is not regarded as an error but as a valid return
from all callback functions.
</p>
<marker id="state callback"/>
<p>
The <em>state callback</em> for a specific
- <seealso marker="#type-state">state</seealso>
+ <seetype marker="#state">state</seetype>
in a <c>gen_statem</c> is the callback function that is called
for all events in this state. It is selected depending on which
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
that the callback module defines with the callback function
- <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>.
+ <seemfa marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seemfa>.
</p>
<p>
When the
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
is <c>state_functions</c>, the state must be an atom and
is used as the <em>state callback</em> name; see
- <seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>.
+ <seemfa marker="#Module:StateName/3"><c>Module:StateName/3</c></seemfa>.
This co-locates all code for a specific state
in one function as the <c>gen_statem</c> engine
branches depending on state name.
Note the fact that the callback function
- <seealso marker="#Module:terminate/3"><c>Module:terminate/3</c></seealso>
+ <seemfa marker="#Module:terminate/3"><c>Module:terminate/3</c></seemfa>
makes the state name <c>terminate</c> unusable in this mode.
</p>
<p>
When the
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
is <c>handle_event_function</c>, the state can be any term
and the <em>state callback</em> name is
- <seealso marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>.
+ <seemfa marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seemfa>.
This makes it easy to branch depending on state or event as you desire.
Be careful about which events you handle in which
states so that you do not accidentally postpone an event
@@ -243,10 +245,10 @@ erlang:'!' -----> Module:StateName/3
<p>
When <c>gen_statem</c> receives a process message it is
converted into an event and the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
is called with the event as two arguments: type and content.
When the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
has processed the event it returns to <c>gen_statem</c>
which does a <em>state transition</em>.
If this <em>state transition</em> is to a different state,
@@ -254,13 +256,13 @@ erlang:'!' -----> Module:StateName/3
</p>
<p>
The
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
may return
- <seealso marker="#type-action"><em>transition actions</em></seealso>
+ <seetype marker="#action"><em>transition actions</em></seetype>
for <c>gen_statem</c>
to execute during the <em>state transition</em>,
for example to reply to a
- <seealso marker="#call/2"><c>gen_statem:call/2,3</c></seealso>.
+ <seemfa marker="#call/2"><c>gen_statem:call/2,3</c></seemfa>.
</p>
<p>
One of the possible <em>transition actions</em>
@@ -281,17 +283,17 @@ erlang:'!' -----> Module:StateName/3
</p>
<p>
The
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
can insert events using the
- <seealso marker="#type-action"><em>transition actions</em></seealso>
+ <seetype marker="#action"><em>transition actions</em></seetype>
<c>next_event</c>
and such an event is inserted in the event queue
as the next to call the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
with.
That is, as if it is the oldest incoming event.
A dedicated
- <seealso marker="#type-event_type"><c>event_type()</c></seealso>
+ <seetype marker="#event_type"><c>event_type()</c></seetype>
<c>internal</c> can be used for such events making them impossible
to mistake for external events.
</p>
@@ -299,15 +301,15 @@ erlang:'!' -----> Module:StateName/3
Inserting an event replaces the trick of calling your own
state handling functions that you often would have to
resort to in, for example,
- <seealso marker="gen_fsm"><c>gen_fsm</c></seealso>
+ <seeerl marker="gen_fsm"><c>gen_fsm</c></seeerl>
to force processing an inserted event before others.
</p>
<p>
The <c>gen_statem</c> engine can automatically
make a specialized call to the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
whenever a new state is entered; see
- <seealso marker="#type-state_enter"><c>state_enter()</c></seealso>.
+ <seetype marker="#state_enter"><c>state_enter()</c></seetype>.
This is for writing code common to all state entries.
Another way to do it is to explicitly insert an event
at the <em>state transition</em>,
@@ -325,18 +327,18 @@ erlang:'!' -----> Module:StateName/3
</note>
<p>
For the details of a <em>state transition</em>, see type
- <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>.
+ <seetype marker="#transition_option"><c>transition_option()</c></seetype>.
</p>
<p>
A <c>gen_statem</c> handles system messages as described in
- <seealso marker="sys"><c>sys</c></seealso>.
+ <seeerl marker="sys"><c>sys</c></seeerl>.
The <c>sys</c> module can be used for debugging a <c>gen_statem</c>.
</p>
<p>
Notice that a <c>gen_statem</c> does not trap exit signals
automatically, this must be explicitly initiated in
the callback module (by calling
- <seealso marker="erts:erlang#process_flag/2"><c>process_flag(trap_exit, true)</c></seealso>.
+ <seemfa marker="erts:erlang#process_flag/2"><c>process_flag(trap_exit, true)</c></seemfa>.
</p>
<p>
Unless otherwise stated, all functions in this module fail if
@@ -345,29 +347,30 @@ erlang:'!' -----> Module:StateName/3
</p>
<p>
The <c>gen_statem</c> process can go into hibernation; see
- <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>.
+ <seemfa marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seemfa>.
It is done when a
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
or
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
specifies <c>hibernate</c> in the returned
- <seealso marker="#type-action"><c>Actions</c></seealso>
+ <seetype marker="#action"><c>Actions</c></seetype>
list. This feature can be useful to reclaim process heap memory
while the server is expected to be idle for a long time.
However, use this feature with care,
as hibernation can be too costly
to use after every event; see
- <seealso marker="erts:erlang#hibernate/3"><c>erlang:hibernate/3</c></seealso>.
+ <seemfa marker="erts:erlang#hibernate/3"><c>erlang:hibernate/3</c></seemfa>.
</p>
<p>
There is also a server start option
- <seealso marker="#type-enter_loop_opt">
+ <seetype marker="#enter_loop_opt">
<c>{hibernate_after, Timeout}</c>
- </seealso>
+ </seetype>
for
- <seealso marker="#start/3"><c>start/3,4</c></seealso>,
- <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso> or
- <seealso marker="#enter_loop/4"><c>enter_loop/4,5,6</c></seealso>,
+ <seemfa marker="#start/3"><c>start/3,4</c></seemfa>,
+ <seemfa marker="#start_monitor/3"><c>start_monitor/3,4</c></seemfa>,
+ <seemfa marker="#start_link/3"><c>start_link/3,4</c></seemfa> or
+ <seemfa marker="#enter_loop/4"><c>enter_loop/4,5,6</c></seemfa>,
that may be used to automatically hibernate the server.
</p>
</description>
@@ -377,7 +380,7 @@ erlang:'!' -----> Module:StateName/3
<p>
The following example shows a simple pushbutton model
for a toggling pushbutton implemented with
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
<c>state_functions</c>.
You can push the button and it replies if it went on or off,
and you can ask for a count of how many times it has been
@@ -463,7 +466,7 @@ ok
</pre>
<p>
To compare styles, here follows the same example using
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
<c>handle_event_function</c>, or rather the code to replace
after function <c>init/1</c> of the <c>pushbutton.erl</c>
example file above:
@@ -498,9 +501,9 @@ handle_event(_, _, State, Data) ->
<p>
Name specification to use when starting
a <c>gen_statem</c> server. See
- <seealso marker="#start_link/3"><c>start_link/3</c></seealso>
+ <seemfa marker="#start_link/3"><c>start_link/3</c></seemfa>
and
- <seealso marker="#type-server_ref"><c>server_ref()</c></seealso>
+ <seetype marker="#server_ref"><c>server_ref()</c></seetype>
below.
</p>
</desc>
@@ -511,8 +514,8 @@ handle_event(_, _, State, Data) ->
<p>
Server specification to use when addressing
a <c>gen_statem</c> server.
- See <seealso marker="#call/2"><c>call/2</c></seealso> and
- <seealso marker="#type-server_name"><c>server_name()</c></seealso>
+ See <seemfa marker="#call/2"><c>call/2</c></seemfa> and
+ <seetype marker="#server_name"><c>server_name()</c></seetype>
above.
</p>
<p>It can be:</p>
@@ -534,7 +537,7 @@ handle_event(_, _, State, Data) ->
<item>
<p>
The <c>gen_statem</c> is globally registered in
- <seealso marker="kernel:global"><c>global</c></seealso>.
+ <seeerl marker="kernel:global"><c>global</c></seeerl>.
</p>
</item>
<tag><c>{via,RegMod,ViaName}</c></tag>
@@ -547,7 +550,7 @@ handle_event(_, _, State, Data) ->
<c>register_name/2</c>, <c>unregister_name/1</c>,
<c>whereis_name/1</c>, and <c>send/2</c>,
which are to behave like the corresponding functions in
- <seealso marker="kernel:global"><c>global</c></seealso>.
+ <seeerl marker="kernel:global"><c>global</c></seeerl>.
Thus, <c>{via,global,GlobalName}</c> is the same as
<c>{global,GlobalName}</c>.
</p>
@@ -561,7 +564,7 @@ handle_event(_, _, State, Data) ->
<p>
Options that can be used when starting
a <c>gen_statem</c> server through, for example,
- <seealso marker="#start_link/3"><c>start_link/3</c></seealso>.
+ <seemfa marker="#start_link/3"><c>start_link/3</c></seemfa>.
</p>
</desc>
</datatype>
@@ -569,8 +572,18 @@ handle_event(_, _, State, Data) ->
<name name="start_ret"/>
<desc>
<p>
- Return value from the start functions, for example,
- <seealso marker="#start_link/3"><c>start_link/3</c></seealso>.
+ Return value from the <c>start()</c> and <c>start_link()</c> functions,
+ for example, <seemfa marker="#start_link/3"><c>start_link/3</c></seemfa>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="start_mon_ret"/>
+ <desc>
+ <p>
+ Return value from the
+ <seemfa marker="#start_monitor/3"><c>start_monitor()</c></seemfa>
+ functions.
</p>
</desc>
</datatype>
@@ -580,7 +593,7 @@ handle_event(_, _, State, Data) ->
<p>
Options that can be used when starting
a <c>gen_statem</c> server through,
- <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>.
+ <seemfa marker="#enter_loop/4"><c>enter_loop/4-6</c></seemfa>.
</p>
<taglist>
<tag><c>hibernate_after</c></tag>
@@ -591,7 +604,7 @@ handle_event(_, _, State, Data) ->
any message for <c>HibernateAfterTimeout</c> milliseconds and
if no message is received, the process goes into hibernation
automatically (by calling
- <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>).
+ <seemfa marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seemfa>).
</p>
</item>
<tag><c>debug</c></tag>
@@ -599,7 +612,7 @@ handle_event(_, _, State, Data) ->
<p>
For every entry in <c><anno>Dbgs</anno></c>,
the corresponding function in
- <seealso marker="sys"><c>sys</c></seealso> is called.
+ <seeerl marker="sys"><c>sys</c></seeerl> is called.
</p>
</item>
</taglist>
@@ -610,10 +623,10 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
Destination to use when replying through, for example, the
- <seealso marker="#type-action"><c>action()</c></seealso>
+ <seetype marker="#action"><c>action()</c></seetype>
<c>{reply,From,Reply}</c>
to a process that has called the <c>gen_statem</c> server using
- <seealso marker="#call/2"><c>call/2</c></seealso>.
+ <seemfa marker="#call/2"><c>call/2</c></seemfa>.
</p>
</desc>
</datatype>
@@ -622,7 +635,7 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
If the
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
is <c>handle_event_function</c>,
the state can be any term.
After a <em>state change</em> (<c>NextState =/= State</c>),
@@ -635,14 +648,14 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
If the
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
is <c>state_functions</c>,
the state must be an atom.
After a <em>state change</em> (<c>NextState =/= State</c>),
all postponed events are retried.
Note that the state <c>terminate</c> is not possible
to use since it would collide with the optional callback function
- <seealso marker="#Module:terminate/3"><c>Module:terminate/3</c></seealso>.
+ <seemfa marker="#Module:terminate/3"><c>Module:terminate/3</c></seemfa>.
</p>
</desc>
</datatype>
@@ -652,7 +665,7 @@ handle_event(_, _, State, Data) ->
<p>
A term in which the state machine implementation
is to store any server data it needs. The difference between
- this and the <seealso marker="#type-state"><c>state()</c></seealso>
+ this and the <seetype marker="#state"><c>state()</c></seetype>
itself is that a change in this data does not cause
postponed events to be retried. Hence, if a change
in this data would change the set of events that
@@ -666,14 +679,14 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
There are 3 categories of events:
- <seealso marker="#type-external_event_type">external</seealso>,
- <seealso marker="#type-timeout_event_type">timeout</seealso>,
+ <seetype marker="#external_event_type">external</seetype>,
+ <seetype marker="#timeout_event_type">timeout</seetype>,
and <c>internal</c>.
</p>
<p>
<c>internal</c> events can only be generated by the
state machine itself through the <em>transition action</em>
- <seealso marker="#type-action"><c>next_event</c></seealso>.
+ <seetype marker="#action"><c>next_event</c></seetype>.
</p>
</desc>
</datatype>
@@ -683,11 +696,12 @@ handle_event(_, _, State, Data) ->
<p>
External events are of 3 types:
<c>{call,<anno>From</anno>}</c>, <c>cast</c>, or <c>info</c>.
- <seealso marker="#call/2">Calls</seealso>
- (synchronous) and
- <seealso marker="#cast/2">casts</seealso>
- originate from the corresponding API functions.
+ Type <c>call</c> originates from the API functions
+ <seemfa marker="#call/2"><c>call/2</c></seemfa>
+ and <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>.
For calls, the event contains whom to reply to.
+ Type <c>cast</c> originates from the API function
+ <seemfa marker="#cast/2"><c>cast/2</c></seemfa>.
Type <c>info</c> originates from regular process messages sent
to the <c>gen_statem</c>.
</p>
@@ -699,7 +713,7 @@ handle_event(_, _, State, Data) ->
<p>
There are 3 types of time-out events that the state machine
can generate for itself with the corresponding
- <seealso marker="#type-timeout_action">timeout_action()</seealso>s.
+ <seetype marker="#timeout_action">timeout_action()</seetype>s.
</p>
</desc>
</datatype>
@@ -708,11 +722,11 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
This is the return type from
- <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
+ <seemfa marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seemfa>
and selects
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
and whether to do
- <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
+ <seetype marker="#state_enter"><em>state enter calls</em></seetype>,
or not.
</p>
</desc>
@@ -723,16 +737,16 @@ handle_event(_, _, State, Data) ->
<p>
The <em>callback mode</em> is selected
with the return value from
- <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>:
+ <seemfa marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seemfa>:
</p>
<taglist>
<tag><c>state_functions</c></tag>
<item>
<p>
The state must be of type
- <seealso marker="#type-state_name"><c>state_name()</c></seealso>
+ <seetype marker="#state_name"><c>state_name()</c></seetype>
and one callback function per state, that is,
- <seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>,
+ <seemfa marker="#Module:StateName/3"><c>Module:StateName/3</c></seemfa>,
is used.
</p>
</item>
@@ -740,22 +754,22 @@ handle_event(_, _, State, Data) ->
<item>
<p>
The state can be any term and the callback function
- <seealso marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>
+ <seemfa marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seemfa>
is used for all states.
</p>
</item>
</taglist>
<p>
The function
- <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
+ <seemfa marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seemfa>
is called when starting the <c>gen_statem</c>,
after code change
and after changing the callback module with any of the actions
- <seealso marker="#type-action"><c>change_callback_module</c></seealso>,
- <seealso marker="#type-action"><c>push_callback_module</c></seealso> or
- <seealso marker="#type-action"><c>pop_callback_module</c></seealso>.
+ <seetype marker="#action"><c>change_callback_module</c></seetype>,
+ <seetype marker="#action"><c>push_callback_module</c></seetype> or
+ <seetype marker="#action"><c>pop_callback_module</c></seetype>.
The result is cached for subsequent calls to
- <seealso marker="#state callback">state callbacks</seealso>.
+ <seeerl marker="#state callback">state callbacks</seeerl>.
</p>
</desc>
</datatype>
@@ -766,45 +780,45 @@ handle_event(_, _, State, Data) ->
Whether the state machine should use <em>state enter calls</em>
or not is selected when starting the <c>gen_statem</c>
and after code change using the return value from
- <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>.
+ <seemfa marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seemfa>.
</p>
<p>
If
- <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
+ <seemfa marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seemfa>
returns a list containing <c>state_enter</c>,
the <c>gen_statem</c> engine will, at every <em>state change</em>,
call the
- <seealso marker="#state callback">state callback</seealso>
+ <seeerl marker="#state callback">state callback</seeerl>
with arguments <c>(enter, OldState, Data)</c> or
<c>(enter, OldState, State, Data)</c>, depending on the
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>.
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>.
This may look like an event but is really a call
performed after the previous
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
returned and before any event is delivered to the new
- <seealso marker="#state callback"><em>state callback</em></seealso>.
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>.
See
- <seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>
+ <seemfa marker="#Module:StateName/3"><c>Module:StateName/3</c></seemfa>
and
- <seealso marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>.
+ <seemfa marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seemfa>.
Such a call can be repeated by returning a
- <seealso marker="#type-state_callback_result">
+ <seetype marker="#state_callback_result">
<c>repeat_state</c>
- </seealso>
+ </seetype>
or
- <seealso marker="#type-state_callback_result">
+ <seetype marker="#state_callback_result">
<c>repeat_state_and_data</c>
- </seealso>
+ </seetype>
tuple from the <em>state callback</em>.
</p>
<p>
If
- <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
+ <seemfa marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seemfa>
does not return such a list, no <em>state enter calls</em> are done.
</p>
<p>
If
- <seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>
+ <seemfa marker="#Module:code_change/4"><c>Module:code_change/4</c></seemfa>
should transform the state,
it is regarded as a state rename and not a <em>state change</em>,
which will not cause a <em>state enter call</em>.
@@ -824,10 +838,10 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
Transition options can be set by
- <seealso marker="#type-action">actions</seealso>
+ <seetype marker="#action">actions</seetype>
and modify the <em>state transition</em>.
The <em>state transition</em> takes place when the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
has processed an event and returns.
Here are the sequence of steps for a <em>state transition</em>:
</p>
@@ -835,10 +849,10 @@ handle_event(_, _, State, Data) ->
<item>
<p>
All returned
- <seealso marker="#type-action">actions</seealso>
+ <seetype marker="#action">actions</seetype>
are processed in order of appearance.
In this step all replies generated by any
- <seealso marker="#type-reply_action"><c>reply_action()</c></seealso>
+ <seetype marker="#reply_action"><c>reply_action()</c></seetype>
are sent. Other actions set <c>transition_option()</c>s
that come into play in subsequent steps.
</p>
@@ -846,45 +860,45 @@ handle_event(_, _, State, Data) ->
<item>
<p>
If
- <seealso marker="#type-state_enter">
+ <seetype marker="#state_enter">
<em>state enter calls</em>
- </seealso>
+ </seetype>
are used, and either
it is the initial state or one of the callback results
- <seealso marker="#type-state_callback_result">
+ <seetype marker="#state_callback_result">
<c>repeat_state_and_data</c>
- </seealso>
+ </seetype>
or
- <seealso marker="#type-state_callback_result">
+ <seetype marker="#state_callback_result">
<c>repeat_state_and_data</c>
- </seealso>
+ </seetype>
is used the <c>gen_statem</c> engine calls
the current state callback with arguments
- <seealso marker="#type-state_enter"><c>(enter, State, Data)</c></seealso>
+ <seetype marker="#state_enter"><c>(enter, State, Data)</c></seetype>
or
- <seealso marker="#type-state_enter"><c>(enter, State, State, Data)</c></seealso>
- (depending on <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>)
+ <seetype marker="#state_enter"><c>(enter, State, State, Data)</c></seetype>
+ (depending on <seetype marker="#callback_mode"><em>callback mode</em></seetype>)
and when it returns starts again from the top of this sequence.
</p>
<p>
If
- <seealso marker="#type-state_enter">
+ <seetype marker="#state_enter">
<em>state enter calls</em>
- </seealso>
+ </seetype>
are used, and the state changes
the <c>gen_statem</c> engine calls
the new state callback with arguments
- <seealso marker="#type-state_enter"><c>(enter, OldState, Data)</c></seealso>
+ <seetype marker="#state_enter"><c>(enter, OldState, Data)</c></seetype>
or
- <seealso marker="#type-state_enter"><c>(enter, OldState, State, Data)</c></seealso>
- (depending on <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>)
+ <seetype marker="#state_enter"><c>(enter, OldState, State, Data)</c></seetype>
+ (depending on <seetype marker="#callback_mode"><em>callback mode</em></seetype>)
and when it returns starts again from the top of this sequence.
</p>
</item>
<item>
<p>
If
- <seealso marker="#type-postpone"><c>postpone()</c></seealso>
+ <seetype marker="#postpone"><c>postpone()</c></seetype>
is <c>true</c>,
the current event is postponed.
</p>
@@ -899,7 +913,7 @@ handle_event(_, _, State, Data) ->
<item>
<p>
All events stored with
- <seealso marker="#type-action"><c>action()</c></seealso>
+ <seetype marker="#action"><c>action()</c></seetype>
<c>next_event</c>
are inserted to be processed before previously queued events.
</p>
@@ -907,10 +921,10 @@ handle_event(_, _, State, Data) ->
<item>
<p>
Time-out timers
- <seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>,
- <seealso marker="#type-generic_timeout"><c>generic_timeout()</c></seealso>
+ <seetype marker="#event_timeout"><c>event_timeout()</c></seetype>,
+ <seetype marker="#generic_timeout"><c>generic_timeout()</c></seetype>
and
- <seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
+ <seetype marker="#state_timeout"><c>state_timeout()</c></seetype>
are handled. Time-outs with zero time are guaranteed to be
delivered to the state machine before any external
not yet received event so if there is such a time-out requested,
@@ -921,23 +935,23 @@ handle_event(_, _, State, Data) ->
</p>
<p>
Any event cancels an
- <seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>
+ <seetype marker="#event_timeout"><c>event_timeout()</c></seetype>
so a zero time event time-out is only generated
if the event queue is empty.
</p>
<p>
A <em>state change</em> cancels a
- <seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
+ <seetype marker="#state_timeout"><c>state_timeout()</c></seetype>
and any new transition option of this type
belongs to the new state, that is; a
- <seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
+ <seetype marker="#state_timeout"><c>state_timeout()</c></seetype>
applies to the state the state machine enters.
</p>
</item>
<item>
<p>
If there are enqueued events the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
for the possibly new state
is called with the oldest enqueued event,
and we start again from the top of this sequence.
@@ -948,14 +962,14 @@ handle_event(_, _, State, Data) ->
Otherwise the <c>gen_statem</c> goes into <c>receive</c>
or hibernation
(if
- <seealso marker="#type-hibernate"><c>hibernate()</c></seealso>
+ <seetype marker="#hibernate"><c>hibernate()</c></seetype>
is <c>true</c>)
to wait for the next message. In hibernation the next
non-system event awakens the <c>gen_statem</c>, or rather
the next incoming message awakens the <c>gen_statem</c>,
but if it is a system event it goes right back into hibernation.
When a new message arrives the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
is called with the corresponding event,
and we start again from the top of this sequence.
</p>
@@ -979,7 +993,7 @@ handle_event(_, _, State, Data) ->
<p>
If <c>true</c>, hibernates the <c>gen_statem</c>
by calling
- <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>
+ <seemfa marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seemfa>
before going into <c>receive</c>
to wait for a new external event.
</p>
@@ -988,9 +1002,9 @@ handle_event(_, _, State, Data) ->
If there are enqueued events to process
when hibrnation is requested,
this is optimized by not hibernating but instead calling
- <seealso marker="erts:erlang#garbage_collect/0">
+ <seemfa marker="erts:erlang#garbage_collect/0">
<c>erlang:garbage_collect/0</c>
- </seealso>
+ </seemfa>
to simulate that the <c>gen_statem</c> entered hibernation
and immediately got awakened by an enqueued event.
</p>
@@ -1002,15 +1016,15 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
Starts a timer set by
- <seealso marker="#type-enter_action"><c>enter_action()</c></seealso>
+ <seetype marker="#enter_action"><c>enter_action()</c></seetype>
<c>timeout</c>.
When the timer expires an event of
- <seealso marker="#type-event_type"><c>event_type()</c></seealso>
+ <seetype marker="#event_type"><c>event_type()</c></seetype>
<c>timeout</c> will be generated.
See
- <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seealso>
+ <seemfa marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seemfa>
for how <c>Time</c> and
- <seealso marker="#type-timeout_option"><c>Options</c></seealso>
+ <seetype marker="#timeout_option"><c>Options</c></seetype>
are interpreted. Future <c>erlang:start_timer/4</c> <c>Options</c>
will not necessarily be supported.
</p>
@@ -1042,15 +1056,15 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
Starts a timer set by
- <seealso marker="#type-enter_action"><c>enter_action()</c></seealso>
+ <seetype marker="#enter_action"><c>enter_action()</c></seetype>
<c>{timeout,Name}</c>.
When the timer expires an event of
- <seealso marker="#type-event_type"><c>event_type()</c></seealso>
+ <seetype marker="#event_type"><c>event_type()</c></seetype>
<c>{timeout,Name}</c> will be generated.
See
- <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seealso>
+ <seemfa marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seemfa>
for how <c>Time</c> and
- <seealso marker="#type-timeout_option"><c>Options</c></seealso>
+ <seetype marker="#timeout_option"><c>Options</c></seetype>
are interpreted. Future <c>erlang:start_timer/4</c> <c>Options</c>
will not necessarily be supported.
</p>
@@ -1078,15 +1092,15 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
Starts a timer set by
- <seealso marker="#type-enter_action"><c>enter_action()</c></seealso>
+ <seetype marker="#enter_action"><c>enter_action()</c></seetype>
<c>state_timeout</c>.
When the timer expires an event of
- <seealso marker="#type-event_type"><c>event_type()</c></seealso>
+ <seetype marker="#event_type"><c>event_type()</c></seetype>
<c>state_timeout</c> will be generated.
See
- <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seealso>
+ <seemfa marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seemfa>
for how <c>Time</c> and
- <seealso marker="#type-timeout_option"><c>Options</c></seealso>
+ <seetype marker="#timeout_option"><c>Options</c></seetype>
are interpreted. Future <c>erlang:start_timer/4</c> <c>Options</c>
will not necessarily be supported.
</p>
@@ -1115,9 +1129,9 @@ handle_event(_, _, State, Data) ->
If <c>Abs</c> is <c>true</c> an absolute timer is started,
and if it is <c>false</c> a relative, which is the default.
See
- <seealso marker="erts:erlang#start_timer/4">
+ <seemfa marker="erts:erlang#start_timer/4">
<c>erlang:start_timer/4</c>
- </seealso>
+ </seemfa>
for details.
</p>
<p>
@@ -1130,26 +1144,26 @@ handle_event(_, _, State, Data) ->
<p>
These <em>transition actions</em> can be invoked by
returning them from the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
when it is called with an
- <seealso marker="#type-event_type">event</seealso>,
+ <seetype marker="#event_type">event</seetype>,
from
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
or by giving them to
- <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
+ <seemfa marker="#enter_loop/5"><c>enter_loop/5,6</c></seemfa>.
</p>
<p>
Actions are executed in the containing list order.
</p>
<p>
Actions that set
- <seealso marker="#type-transition_option">
+ <seetype marker="#transition_option">
transition options
- </seealso>
+ </seetype>
override any previous of the same type,
so the last in the containing list wins.
For example, the last
- <seealso marker="#type-postpone"><c>postpone()</c></seealso>
+ <seetype marker="#postpone"><c>postpone()</c></seetype>
overrides any previous <c>postpone()</c> in the list.
</p>
<taglist>
@@ -1157,15 +1171,15 @@ handle_event(_, _, State, Data) ->
<item>
<p>
Sets the
- <seealso marker="#type-transition_option">
+ <seetype marker="#transition_option">
<c>transition_option()</c>
- </seealso>
- <seealso marker="#type-postpone"><c>postpone()</c></seealso>
+ </seetype>
+ <seetype marker="#postpone"><c>postpone()</c></seetype>
for this <em>state transition</em>.
This action is ignored when returned from
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
or given to
- <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>,
+ <seemfa marker="#enter_loop/5"><c>enter_loop/5,6</c></seemfa>,
as there is no event to postpone in those cases.
</p>
</item>
@@ -1173,9 +1187,9 @@ handle_event(_, _, State, Data) ->
<item>
<p>
This action does not set any
- <seealso marker="#type-transition_option">
+ <seetype marker="#transition_option">
<c>transition_option()</c>
- </seealso>
+ </seetype>
but instead stores the specified <c><anno>EventType</anno></c>
and <c><anno>EventContent</anno></c> for insertion after all
actions have been executed.
@@ -1188,7 +1202,7 @@ handle_event(_, _, State, Data) ->
</p>
<p>
An event of type
- <seealso marker="#type-event_type"><c>internal</c></seealso>
+ <seetype marker="#event_type"><c>internal</c></seetype>
is to be used when you want to reliably distinguish
an event inserted this way from any external event.
</p>
@@ -1201,19 +1215,19 @@ handle_event(_, _, State, Data) ->
Changes the callback module to
<c><anno>NewModule</anno></c>
which will be used when calling all subsequent
- <seealso marker="#state callback">state callbacks</seealso>.
+ <seeerl marker="#state callback">state callbacks</seeerl>.
</p>
<p>
The <c>gen_statem</c> engine will find out the
- <seealso marker="#type-callback_mode">
+ <seetype marker="#callback_mode">
<em>callback mode</em>
- </seealso>
+ </seetype>
of <c><anno>NewModule</anno></c> by calling
- <seealso marker="#Module:callback_mode/0">
+ <seemfa marker="#Module:callback_mode/0">
<c>NewModule:callback_mode/0</c>
- </seealso>
+ </seemfa>
before the next
- <seealso marker="#state callback">state callback</seealso>.
+ <seeerl marker="#state callback">state callback</seeerl>.
</p>
<p>
Changing the callback module does not affect the
@@ -1222,21 +1236,21 @@ handle_event(_, _, State, Data) ->
Be aware that all relevant callback functions in
<c><anno>NewModule</anno></c>
such as the
- <seealso marker="#state callback">state callback</seealso>,
- <seealso marker="#Module:code_change/4"><c><anno>NewModule</anno>:code_change/4</c></seealso>,
- <seealso marker="#Module:format_status/2">
+ <seeerl marker="#state callback">state callback</seeerl>,
+ <seemfa marker="#Module:code_change/4"><c><anno>NewModule</anno>:code_change/4</c></seemfa>,
+ <seemfa marker="#Module:format_status/2">
<c><anno>NewModule</anno>:format_status/2</c>
- </seealso>
+ </seemfa>
and
- <seealso marker="#Module:terminate/3">
+ <seemfa marker="#Module:terminate/3">
<c><anno>NewModule</anno>:terminate/3</c>
- </seealso>
+ </seemfa>
must be able to handle the state and data
from the old module.
</p>
</item>
<tag>
- <c>push_callback_module</c><br />
+ <c>push_callback_module</c>
</tag>
<item>
<p>
@@ -1270,21 +1284,21 @@ handle_event(_, _, State, Data) ->
<p>
These <em>transition actions</em> can be invoked by
returning them from the
- <seealso marker="#state callback"><em>state callback</em></seealso>, from
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>, from
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
or by giving them to
- <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
+ <seemfa marker="#enter_loop/5"><c>enter_loop/5,6</c></seemfa>.
</p>
<p>
Actions are executed in the containing list order.
</p>
<p>
Actions that set
- <seealso marker="#type-transition_option">transition options</seealso>
+ <seetype marker="#transition_option">transition options</seetype>
override any previous of the same type,
so the last in the containing list wins.
For example, the last
- <seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>
+ <seetype marker="#event_timeout"><c>event_timeout()</c></seetype>
overrides any previous <c>event_timeout()</c> in the list.
</p>
<taglist>
@@ -1292,8 +1306,8 @@ handle_event(_, _, State, Data) ->
<item>
<p>
Sets the
- <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
- <seealso marker="#type-hibernate"><c>hibernate()</c></seealso>
+ <seetype marker="#transition_option"><c>transition_option()</c></seetype>
+ <seetype marker="#hibernate"><c>hibernate()</c></seetype>
for this <em>state transition</em>.
</p>
</item>
@@ -1306,14 +1320,14 @@ handle_event(_, _, State, Data) ->
<p>
These <em>transition actions</em> can be invoked by
returning them from the
- <seealso marker="#state callback"><em>state callback</em></seealso>, from
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>, from
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
or by giving them to
- <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
+ <seemfa marker="#enter_loop/5"><c>enter_loop/5,6</c></seemfa>.
</p>
<p>
These time-out actions sets time-out
- <seealso marker="#type-transition_option">transition options</seealso>.
+ <seetype marker="#transition_option">transition options</seetype>.
</p>
<taglist>
<tag><c>Time</c></tag>
@@ -1322,7 +1336,7 @@ handle_event(_, _, State, Data) ->
Short for <c>{timeout,Time,Time}</c>, that is,
the time-out message is the time-out time.
This form exists to make the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
return value <c>{next_state,NextState,NewData,Time}</c>
allowed like for <c>gen_fsm</c>.
</p>
@@ -1331,34 +1345,34 @@ handle_event(_, _, State, Data) ->
<item>
<p>
Sets the
- <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
- <seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>
+ <seetype marker="#transition_option"><c>transition_option()</c></seetype>
+ <seetype marker="#event_timeout"><c>event_timeout()</c></seetype>
to <c><anno>Time</anno></c> with <c><anno>EventContent</anno></c>
and time-out options
- <seealso marker="#type-timeout_option"><c><anno>Options</anno></c></seealso>.
+ <seetype marker="#timeout_option"><c><anno>Options</anno></c></seetype>.
</p>
</item>
<tag><c>{timeout,<anno>Name</anno>}</c></tag>
<item>
<p>
Sets the
- <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
- <seealso marker="#type-generic_timeout"><c>generic_timeout()</c></seealso>
+ <seetype marker="#transition_option"><c>transition_option()</c></seetype>
+ <seetype marker="#generic_timeout"><c>generic_timeout()</c></seetype>
to <c><anno>Time</anno></c> for <c><anno>Name</anno></c>
with <c><anno>EventContent</anno></c>
and time-out options
- <seealso marker="#type-timeout_option"><c><anno>Options</anno></c></seealso>.
+ <seetype marker="#timeout_option"><c><anno>Options</anno></c></seetype>.
</p>
</item>
<tag><c>state_timeout</c></tag>
<item>
<p>
Sets the
- <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
- <seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
+ <seetype marker="#transition_option"><c>transition_option()</c></seetype>
+ <seetype marker="#state_timeout"><c>state_timeout()</c></seetype>
to <c><anno>Time</anno></c> with <c><anno>EventContent</anno></c>
and time-out options
- <seealso marker="#type-timeout_option"><c><anno>Options</anno></c></seealso>.
+ <seetype marker="#timeout_option"><c><anno>Options</anno></c></seetype>.
</p>
</item>
</taglist>
@@ -1369,9 +1383,9 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
This is a shorter and clearer form of
- <seealso marker="#type-timeout_action">
+ <seetype marker="#timeout_action">
timeout_action()
- </seealso>
+ </seetype>
with <c>Time = infinity</c> which cancels a time-out.
</p>
</desc>
@@ -1382,9 +1396,9 @@ handle_event(_, _, State, Data) ->
<p>
Updates a time-out with a new <c>EventContent</c>.
See
- <seealso marker="#type-timeout_action">
+ <seetype marker="#timeout_action">
timeout_action()
- </seealso>
+ </seetype>
for how to start a time-out.
</p>
<p>
@@ -1400,31 +1414,31 @@ handle_event(_, _, State, Data) ->
<p>
This <em>transition action</em> can be invoked by
returning it from the
- <seealso marker="#state callback"><em>state callback</em></seealso>, from
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>, from
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
or by giving it to
- <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
+ <seemfa marker="#enter_loop/5"><c>enter_loop/5,6</c></seemfa>.
</p>
<p>
It does not set any
- <seealso marker="#type-transition_option">
+ <seetype marker="#transition_option">
<c>transition_option()</c>
- </seealso>
+ </seetype>
but instead replies to a caller waiting for a reply in
- <seealso marker="#call/2"><c>call/2</c></seealso>.
+ <seemfa marker="#call/2"><c>call/2</c></seemfa>.
<c><anno>From</anno></c> must be the term from argument
- <seealso marker="#type-event_type"><c>{call,<anno>From</anno>}</c></seealso>
+ <seetype marker="#event_type"><c>{call,<anno>From</anno>}</c></seetype>
in a call to a
- <seealso marker="#state callback"><em>state callback</em></seealso>.
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>.
</p>
<p>
Note that using this action from
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
or
- <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>
+ <seemfa marker="#enter_loop/5"><c>enter_loop/5,6</c></seemfa>
would be weird on the border of witchcraft
since there has been no earlier call to a
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
in this server.
</p>
</desc>
@@ -1435,16 +1449,16 @@ handle_event(_, _, State, Data) ->
<p>
For a succesful initialization,
<c><anno>State</anno></c> is the initial
- <seealso marker="#type-state"><c>state()</c></seealso>
+ <seetype marker="#state"><c>state()</c></seetype>
and <c><anno>Data</anno></c> the initial server
- <seealso marker="#type-data"><c>data()</c></seealso>
+ <seetype marker="#data"><c>data()</c></seetype>
of the <c>gen_statem</c>.
</p>
<p>
- The <seealso marker="#type-action"><c>Actions</c></seealso>
+ The <seetype marker="#action"><c>Actions</c></seetype>
are executed when entering the first
- <seealso marker="#type-state">state</seealso> just as for a
- <seealso marker="#state callback"><em>state callback</em></seealso>,
+ <seetype marker="#state">state</seetype> just as for a
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>,
except that the action <c>postpone</c> is forced to
<c>false</c> since there is no event to postpone.
</p>
@@ -1452,7 +1466,7 @@ handle_event(_, _, State, Data) ->
For an unsuccesful initialization,
<c>{stop,<anno>Reason</anno>}</c>
or <c>ignore</c> should be used; see
- <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>.
+ <seemfa marker="#start_link/3"><c>start_link/3,4</c></seemfa>.
</p>
</desc>
</datatype>
@@ -1463,7 +1477,7 @@ handle_event(_, _, State, Data) ->
<c><anno>State</anno></c> is the current state
and it cannot be changed since the state callback
was called with a
- <seealso marker="#type-state_enter"><em>state enter call</em></seealso>.
+ <seetype marker="#state_enter"><em>state enter call</em></seetype>.
</p>
<taglist>
<tag><c>next_state</c></tag>
@@ -1484,13 +1498,13 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
<c><anno>StateType</anno></c> is
- <seealso marker="#type-state_name"><c>state_name()</c></seealso>
+ <seetype marker="#state_name"><c>state_name()</c></seetype>
if
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
is <c>state_functions</c>, or
- <seealso marker="#type-state"><c>state()</c></seealso>
+ <seetype marker="#state"><c>state()</c></seetype>
if
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
is <c>handle_event_function</c>.
</p>
<taglist>
@@ -1514,11 +1528,11 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
<c><anno>ActionType</anno></c> is
- <seealso marker="#type-enter_action"><c>enter_action()</c></seealso>
+ <seetype marker="#enter_action"><c>enter_action()</c></seetype>
if the state callback was called with a
- <seealso marker="#type-state_enter"><em>state enter call</em></seealso>
+ <seetype marker="#state_enter"><em>state enter call</em></seetype>
and
- <seealso marker="#type-action"><c>action()</c></seealso>
+ <seetype marker="#action"><c>action()</c></seetype>
if the state callback was called with an event.
</p>
<taglist>
@@ -1540,9 +1554,9 @@ handle_event(_, _, State, Data) ->
<item>
<p>
If the <c>gen_statem</c> runs with
- <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
+ <seetype marker="#state_enter"><em>state enter calls</em></seetype>,
the <em>state enter call</em> is repeated, see type
- <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>,
+ <seetype marker="#transition_option"><c>transition_option()</c></seetype>,
other than that <c>repeat_state</c> is the same as
<c>keep_state</c>.
</p>
@@ -1558,7 +1572,7 @@ handle_event(_, _, State, Data) ->
<item>
<p>
Terminates the <c>gen_statem</c> by calling
- <seealso marker="#Module:terminate/3"><c>Module:terminate/3</c></seealso>
+ <seemfa marker="#Module:terminate/3"><c>Module:terminate/3</c></seemfa>
with <c>Reason</c> and
<c><anno>NewData</anno></c>, if specified.
</p>
@@ -1568,7 +1582,7 @@ handle_event(_, _, State, Data) ->
<p>
Sends all <c><anno>Replies</anno></c>,
then terminates the <c>gen_statem</c> by calling
- <seealso marker="#Module:terminate/3"><c>Module:terminate/3</c></seealso>
+ <seemfa marker="#Module:terminate/3"><c>Module:terminate/3</c></seemfa>
with <c>Reason</c> and
<c><anno>NewData</anno></c>, if specified.
</p>
@@ -1580,6 +1594,16 @@ handle_event(_, _, State, Data) ->
</p>
</desc>
</datatype>
+
+ <datatype>
+ <name name="request_id"/>
+ <desc>
+ <p>
+ A request handle, see <seemfa marker="#send_request/2"> <c>send_request/2</c> </seemfa>
+ for details.
+ </p>
+ </desc>
+ </datatype>
</datatypes>
<funcs>
@@ -1590,22 +1614,22 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
Makes a synchronous call to the <c>gen_statem</c>
- <seealso marker="#type-server_ref"><c><anno>ServerRef</anno></c></seealso>
+ <seetype marker="#server_ref"><c><anno>ServerRef</anno></c></seetype>
by sending a request
and waiting until its reply arrives.
The <c>gen_statem</c> calls the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
with
- <seealso marker="#type-event_type"><c>event_type()</c></seealso>
+ <seetype marker="#event_type"><c>event_type()</c></seetype>
<c>{call,From}</c> and event content
<c><anno>Request</anno></c>.
</p>
<p>
A <c><anno>Reply</anno></c> is generated when a
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
returns with
<c>{reply,From,<anno>Reply</anno>}</c> as one
- <seealso marker="#type-action"><c>action()</c></seealso>,
+ <seetype marker="#action"><c>action()</c></seetype>,
and that <c><anno>Reply</anno></c> becomes the return value
of this function.
</p>
@@ -1666,14 +1690,14 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
Sends an asynchronous event to the <c>gen_statem</c>
- <seealso marker="#type-server_ref"><c><anno>ServerRef</anno></c></seealso>
+ <seetype marker="#server_ref"><c><anno>ServerRef</anno></c></seetype>
and returns <c>ok</c> immediately,
ignoring if the destination node or <c>gen_statem</c>
does not exist.
The <c>gen_statem</c> calls the
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
with
- <seealso marker="#type-event_type"><c>event_type()</c></seealso>
+ <seetype marker="#event_type"><c>event_type()</c></seetype>
<c>cast</c> and event content
<c><anno>Msg</anno></c>.
</p>
@@ -1681,14 +1705,45 @@ handle_event(_, _, State, Data) ->
</func>
<func>
+ <name name="check_response" arity="2" since="OTP-23"/>
+ <fsummary>Check if a message is a reply from a server.</fsummary>
+ <desc>
+ <p>
+ This function is used to check if a previously received
+ message, for example by <c>receive</c> or
+ <c>handle_info/2</c>, is a result of a request made with
+ <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>.
+ If <c>Msg</c> is a reply to the handle <c>RequestId</c>
+ the result of the request is returned in <c>Reply</c>.
+ Otherwise returns <c>no_reply</c> and no cleanup is done, and
+ thus the function shall be invoked repeatedly until a reply
+ is returned.
+ </p>
+ <p>
+ The return value <c><anno>Reply</anno></c> is generated when a
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
+ returns with
+ <c>{reply,From,<anno>Reply</anno>}</c> as one
+ <seetype marker="#action"><c>action()</c></seetype>,
+ and that <c><anno>Reply</anno></c> becomes the return value
+ of this function.
+ </p>
+ <p>
+ The function returns an error if the <c>gen_statem</c>
+ dies before or during this request.
+ </p>
+ </desc>
+ </func>
+
+ <func>
<name name="enter_loop" arity="4" since="OTP 19.1"/>
<fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary>
<desc>
<p>
The same as
- <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso>
+ <seemfa marker="#enter_loop/6"><c>enter_loop/6</c></seemfa>
with <c>Actions = []</c> except that no
- <seealso marker="#type-server_name"><c>server_name()</c></seealso>
+ <seetype marker="#server_name"><c>server_name()</c></seetype>
must have been registered. This creates an anonymous server.
</p>
</desc>
@@ -1701,16 +1756,16 @@ handle_event(_, _, State, Data) ->
<p>
If <c><anno>Server_or_Actions</anno></c> is a <c>list()</c>,
the same as
- <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso>
+ <seemfa marker="#enter_loop/6"><c>enter_loop/6</c></seemfa>
except that no
- <seealso marker="#type-server_name"><c>server_name()</c></seealso>
+ <seetype marker="#server_name"><c>server_name()</c></seetype>
must have been registered and
<c>Actions = <anno>Server_or_Actions</anno></c>.
This creates an anonymous server.
</p>
<p>
Otherwise the same as
- <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso>
+ <seemfa marker="#enter_loop/6"><c>enter_loop/6</c></seemfa>
with
<c>Server = <anno>Server_or_Actions</anno></c> and
<c>Actions = []</c>.
@@ -1729,7 +1784,7 @@ handle_event(_, _, State, Data) ->
a <c>gen_statem</c> server.
The process <em>must</em> have been started
using one of the start functions in
- <seealso marker="proc_lib"><c>proc_lib</c></seealso>.
+ <seeerl marker="proc_lib"><c>proc_lib</c></seeerl>.
The user is responsible for any initialization of the process,
including registering a name for it.
</p>
@@ -1741,18 +1796,18 @@ handle_event(_, _, State, Data) ->
<p>
<c><anno>Module</anno></c>, <c><anno>Opts</anno></c>
have the same meaning as when calling
- <seealso marker="#start_link/3"><c>start[_link]/3,4</c></seealso>.
+ <seemfa marker="#start_link/3"><c>start[_link|_monitor]/3,4</c></seemfa>.
</p>
<p>
If <c><anno>Server</anno></c> is <c>self()</c> an anonymous
server is created just as when using
- <seealso marker="#start_link/3"><c>start[_link]/3</c></seealso>.
+ <seemfa marker="#start_link/3"><c>start[_link|_monitor]/3</c></seemfa>.
If <c><anno>Server</anno></c> is a
- <seealso marker="#type-server_name"><c>server_name()</c></seealso>
+ <seetype marker="#server_name"><c>server_name()</c></seetype>
a named server is created just as when using
- <seealso marker="#start_link/4"><c>start[_link]/4</c></seealso>.
+ <seemfa marker="#start_link/4"><c>start[_link|_monitor]/4</c></seemfa>.
However, the
- <seealso marker="#type-server_name"><c>server_name()</c></seealso>
+ <seetype marker="#server_name"><c>server_name()</c></seetype>
name must have been registered accordingly
<em>before</em> this function is called.
</p>
@@ -1760,17 +1815,17 @@ handle_event(_, _, State, Data) ->
<c><anno>State</anno></c>, <c><anno>Data</anno></c>,
and <c><anno>Actions</anno></c>
have the same meanings as in the return value of
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>.
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>.
Also, the callback module does not need to export a
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
function.
</p>
<p>
The function fails if the calling process was not started by a
- <seealso marker="proc_lib"><c>proc_lib</c></seealso>
+ <seeerl marker="proc_lib"><c>proc_lib</c></seeerl>
start function, or if it is not registered
according to
- <seealso marker="#type-server_name"><c>server_name()</c></seealso>.
+ <seetype marker="#server_name"><c>server_name()</c></seetype>.
</p>
</desc>
</func>
@@ -1783,32 +1838,74 @@ handle_event(_, _, State, Data) ->
<p>
This function can be used by a <c>gen_statem</c>
to explicitly send a reply to a process that waits in
- <seealso marker="#call/2"><c>call/2</c></seealso>
+ <seemfa marker="#call/2"><c>call/2</c></seemfa>
when the reply cannot be defined in
the return value of a
- <seealso marker="#state callback"><em>state callback</em></seealso>.
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>.
</p>
<p>
<c><anno>From</anno></c> must be the term from argument
- <seealso marker="#type-event_type"><c>{call,<anno>From</anno>}</c></seealso>
+ <seetype marker="#event_type"><c>{call,<anno>From</anno>}</c></seetype>
to the
- <seealso marker="#state callback"><em>state callback</em></seealso>.
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>.
A reply or multiple replies canalso be sent
using one or several
- <seealso marker="#type-reply_action"><c>reply_action()</c></seealso>s
+ <seetype marker="#reply_action"><c>reply_action()</c></seetype>s
from a
- <seealso marker="#state callback"><em>state callback</em></seealso>.
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>.
</p>
<note>
<p>
A reply sent with this function is not visible
- in <seealso marker="sys"><c>sys</c></seealso> debug output.
+ in <seeerl marker="sys"><c>sys</c></seeerl> debug output.
</p>
</note>
</desc>
</func>
<func>
+ <name name="send_request" arity="2" since="OTP-23"/>
+ <fsummary>Send a request to a <c>gen_statem</c>.</fsummary>
+ <desc>
+ <p>
+ Sends a request to the <c>gen_statem</c>
+ <seetype marker="#server_ref"><c><anno>ServerRef</anno></c></seetype>
+ and returns a handle <c><anno>RequestId</anno></c>.
+ </p>
+ <p>
+ The return value <c><anno>RequestId</anno></c> shall later be used with
+ <seemfa marker="#wait_response/2"> <c>wait_response/1,2</c></seemfa> or
+ <seemfa marker="#check_response/2"> <c>check_response/2</c></seemfa>
+ to fetch the actual result of the request.
+ </p>
+ <p>
+ The call <c>gen_statem:wait_response(gen_statem:send_request(ServerRef,Request), Timeout)</c>
+ can be seen as equivalent to
+ <seemfa marker="#call/3"><c>gen_statem:call(Server,Request,Timeout)</c></seemfa>,
+ ignoring the error handling.
+ </p>
+ <p>
+ The <c>gen_statem</c> calls the
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
+ with
+ <seetype marker="#event_type"><c>event_type()</c></seetype>
+ <c>{call,From}</c> and event content
+ <c><anno>Request</anno></c>.
+ </p>
+ <p>
+ A <c>Reply</c> is generated when a
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
+ returns with
+ <c>{reply,From,Reply}</c> as one
+ <seetype marker="#action"><c>action()</c></seetype>,
+ and that <c>Reply</c> becomes the return value
+ of <seemfa marker="#wait_response/2"> <c>wait_response/1,2</c></seemfa> or
+ <seemfa marker="#check_response/2"> <c>check_response/2</c></seemfa> function.
+ </p>
+ </desc>
+ </func>
+
+ <func>
<name name="start" arity="3" since="OTP 19.0"/>
<name name="start" arity="4" since="OTP 19.0"/>
<fsummary>Create a standalone <c>gen_statem</c> process.</fsummary>
@@ -1816,7 +1913,7 @@ handle_event(_, _, State, Data) ->
<p>
Creates a standalone <c>gen_statem</c> process according to
OTP design principles (using
- <seealso marker="proc_lib"><c>proc_lib</c></seealso>
+ <seeerl marker="proc_lib"><c>proc_lib</c></seeerl>
primitives).
As it does not get linked to the calling process,
this start function cannot be used by a supervisor
@@ -1824,7 +1921,7 @@ handle_event(_, _, State, Data) ->
</p>
<p>
For a description of arguments and return values, see
- <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>.
+ <seemfa marker="#start_link/3"><c>start_link/3,4</c></seemfa>.
</p>
</desc>
</func>
@@ -1838,7 +1935,7 @@ handle_event(_, _, State, Data) ->
Creates a <c>gen_statem</c> process according
to OTP design principles
(using
- <seealso marker="proc_lib"><c>proc_lib</c></seealso>
+ <seeerl marker="proc_lib"><c>proc_lib</c></seeerl>
primitives)
that is linked to the calling process.
This is essential when the <c>gen_statem</c> must be part of
@@ -1846,15 +1943,15 @@ handle_event(_, _, State, Data) ->
</p>
<p>
The <c>gen_statem</c> process calls
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
to initialize the server. To ensure a synchronized startup
procedure, <c>start_link/3,4</c> does not return until
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
has returned.
</p>
<p>
<c><anno>ServerName</anno></c> specifies the
- <seealso marker="#type-server_name"><c>server_name()</c></seealso>
+ <seetype marker="#server_name"><c>server_name()</c></seetype>
to register for the <c>gen_statem</c>.
If the <c>gen_statem</c> is started with <c>start_link/3</c>,
no <c><anno>ServerName</anno></c> is provided and
@@ -1864,53 +1961,53 @@ handle_event(_, _, State, Data) ->
<p>
<c><anno>Args</anno></c> is an arbitrary term that is passed as
the argument to
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>.
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>.
</p>
<list type="bulleted">
<item>
<p>
If option
- <seealso marker="#type-start_opt">
+ <seetype marker="#start_opt">
<c>{timeout,Time}</c>
- </seealso>
+ </seetype>
is present in
<c><anno>Opts</anno></c>, the <c>gen_statem</c>
is allowed to spend <c>Time</c> milliseconds initializing
or it terminates and the start function returns
- <seealso marker="#type-start_ret"><c>{error,timeout}</c></seealso>.
+ <seetype marker="#start_ret"><c>{error,timeout}</c></seetype>.
</p>
</item>
<item>
<p>If option
- <seealso marker="#type-enter_loop_opt">
+ <seetype marker="#enter_loop_opt">
<c>{hibernate_after,HibernateAfterTimeout}</c>
- </seealso>
+ </seetype>
is present, the <c>gen_statem</c>
process awaits any message for <c>HibernateAfterTimeout</c> milliseconds and
if no message is received, the process goes into hibernation automatically
- (by calling <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>).
+ (by calling <seemfa marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seemfa>).
</p>
</item>
<item>
<p>
If option
- <seealso marker="#type-enter_loop_opt">
+ <seetype marker="#enter_loop_opt">
<c>{debug,Dbgs}</c>
- </seealso>
+ </seetype>
is present in <c><anno>Opts</anno></c>, debugging through
- <seealso marker="sys"><c>sys</c></seealso> is activated.
+ <seeerl marker="sys"><c>sys</c></seeerl> is activated.
</p>
</item>
<item>
<p>
If option
- <seealso marker="#type-start_opt">
+ <seetype marker="#start_opt">
<c>{spawn_opt,SpawnOpts}</c>
- </seealso>
+ </seetype>
is present in
<c><anno>Opts</anno></c>, <c>SpawnOpts</c> is passed
as option list to
- <seealso marker="erts:erlang#spawn_opt/2"><c>erlang:spawn_opt/2</c></seealso>,
+ <seemfa marker="erts:erlang#spawn_opt/2"><c>erlang:spawn_opt/2</c></seemfa>,
which is used to spawn the <c>gen_statem</c> process.
</p>
</item>
@@ -1925,39 +2022,68 @@ handle_event(_, _, State, Data) ->
<p>
If the <c>gen_statem</c> is successfully created
and initialized, this function returns
- <seealso marker="#type-start_ret"><c>{ok,Pid}</c></seealso>,
+ <seetype marker="#start_ret"><c>{ok,Pid}</c></seetype>,
where <c>Pid</c> is the <c>pid()</c>
of the <c>gen_statem</c>.
If a process with the specified <c><anno>ServerName</anno></c>
exists already, this function returns
- <seealso marker="#type-start_ret"><c>{error,{already_started,Pid}}</c></seealso>,
+ <seetype marker="#start_ret"><c>{error,{already_started,Pid}}</c></seetype>,
where <c>Pid</c> is the <c>pid()</c> of that process.
</p>
<p>
If <c>Module:init/1</c> fails with <c>Reason</c>,
this function returns
- <seealso marker="#type-start_ret"><c>{error,Reason}</c></seealso>.
+ <seetype marker="#start_ret"><c>{error,Reason}</c></seetype>.
If <c>Module:init/1</c> returns
- <seealso marker="#type-start_ret"><c>{stop,Reason}</c></seealso>
+ <seetype marker="#start_ret"><c>{stop,Reason}</c></seetype>
or
- <seealso marker="#type-start_ret"><c>ignore</c></seealso>,
+ <seetype marker="#start_ret"><c>ignore</c></seetype>,
the process is terminated and this function
returns
- <seealso marker="#type-start_ret"><c>{error,Reason}</c></seealso>
+ <seetype marker="#start_ret"><c>{error,Reason}</c></seetype>
or
- <seealso marker="#type-start_ret"><c>ignore</c></seealso>,
+ <seetype marker="#start_ret"><c>ignore</c></seetype>,
respectively.
</p>
</desc>
</func>
<func>
+ <name name="start_monitor" arity="3" since="OTP 23.0"/>
+ <name name="start_monitor" arity="4" since="OTP 23.0"/>
+ <fsummary>Create a standalone <c>gen_statem</c> process.</fsummary>
+ <desc>
+ <p>
+ Creates a standalone <c>gen_statem</c> process according to
+ OTP design principles (using
+ <seeerl marker="proc_lib"><c>proc_lib</c></seeerl>
+ primitives) and atomically sets up a monitor to
+ the newly created process.
+ As it does not get linked to the calling process,
+ this start function cannot be used by a supervisor
+ to start a child.
+ </p>
+ <p>
+ For a description of arguments and return values, see
+ <seemfa marker="#start_link/3"><c>start_link/3,4</c></seemfa>.
+ Note that the return value on successful start differs from
+ <c>start_link/3,4</c>. <c>start_monitor/3,4</c> will return
+ <c>{ok,{Pid,Mon}}</c> where <c>Pid</c> is the process identifier
+ of the process, and <c>Mon</c> is a reference to the monitor
+ set up to monitor the process. If the start is not successful,
+ the caller will be blocked until the <c>DOWN</c> message has
+ been received and removed from the message queue.
+ </p>
+ </desc>
+ </func>
+
+ <func>
<name name="stop" arity="1" since="OTP 19.0"/>
<fsummary>Synchronously stop a generic server.</fsummary>
<desc>
<p>
The same as
- <seealso marker="#stop/3"><c>stop(<anno>ServerRef</anno>, normal, infinity)</c></seealso>.
+ <seemfa marker="#stop/3"><c>stop(<anno>ServerRef</anno>, normal, infinity)</c></seemfa>.
</p>
</desc>
</func>
@@ -1968,11 +2094,11 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
Orders the <c>gen_statem</c>
- <seealso marker="#type-server_ref"><c><anno>ServerRef</anno></c></seealso>
+ <seetype marker="#server_ref"><c><anno>ServerRef</anno></c></seetype>
to exit with the specified <c><anno>Reason</anno></c>
and waits for it to terminate.
The <c>gen_statem</c> calls
- <seealso marker="#Module:terminate/3"><c>Module:terminate/3</c></seealso>
+ <seemfa marker="#Module:terminate/3"><c>Module:terminate/3</c></seemfa>
before exiting.
</p>
<p>
@@ -1980,7 +2106,7 @@ handle_event(_, _, State, Data) ->
with the expected reason. Any other reason than <c>normal</c>,
<c>shutdown</c>, or <c>{shutdown,Term}</c> causes an
error report to be issued through
- <seealso marker="kernel:logger"><c>logger(3)</c></seealso>.
+ <seeerl marker="kernel:logger"><c>logger(3)</c></seeerl>.
The default <c><anno>Reason</anno></c> is <c>normal</c>.
</p>
<p>
@@ -1997,17 +2123,57 @@ handle_event(_, _, State, Data) ->
</p>
</desc>
</func>
+
+ <func>
+ <name name="wait_response" arity="1" since="OTP-23"/>
+ <name name="wait_response" arity="2" since="OTP-23"/>
+ <fsummary>Wait for a reply from a server.</fsummary>
+ <desc>
+ <p>
+ This function is used to wait for a reply of a request made with
+ <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>
+ from the <c>gen_statem</c> process. This function must be called
+ from the same process from which
+ <seemfa marker="#send_request/2"><c>send_request/2</c></seemfa>
+ was made.
+ </p>
+ <p>
+ <c>Timeout</c> is an integer greater then or equal to zero
+ that specifies how many milliseconds to wait for an reply, or
+ the atom <c>infinity</c> to wait indefinitely. Defaults to
+ <c>infinity</c>.
+ If no reply is received within the specified
+ time, the function returns <c>timeout</c> and no cleanup is
+ done, and thus the function can be invoked repeatedly until a
+ reply is returned.
+ </p>
+ <p>
+ The return value <c><anno>Reply</anno></c> is generated when a
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
+ returns with
+ <c>{reply,From,<anno>Reply</anno>}</c> as one
+ <seetype marker="#action"><c>action()</c></seetype>,
+ and that <c><anno>Reply</anno></c> becomes the return value
+ of this function.
+ </p>
+ <p>
+ The function returns an error if the <c>gen_statem</c>
+ dies before or during this function call.
+ </p>
+ </desc>
+ </func>
</funcs>
- <section>
- <title>Callback Functions</title>
- <p>
- The following functions are to be exported from a
- <c>gen_statem</c> callback module.
- </p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>
+ The following functions are to be exported from a
+ <c>gen_statem</c> callback module.
+ </p>
+ </fsdescription>
<func>
<name since="OTP 19.1">Module:callback_mode() -> CallbackMode</name>
<fsummary>
@@ -2016,49 +2182,49 @@ handle_event(_, _, State, Data) ->
<type>
<v>
CallbackMode =
- <seealso marker="#type-callback_mode">callback_mode()</seealso> |
- [ <seealso marker="#type-callback_mode">callback_mode()</seealso>
- | <seealso marker="#type-state_enter">state_enter()</seealso> ]
+ <seetype marker="#callback_mode">callback_mode()</seetype> |
+ [ <seetype marker="#callback_mode">callback_mode()</seetype>
+ | <seetype marker="#state_enter">state_enter()</seetype> ]
</v>
</type>
<desc>
<p>
This function is called by a <c>gen_statem</c>
when it needs to find out the
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
of the callback module. The value is cached by <c>gen_statem</c>
for efficiency reasons, so this function is only called
once after server start, after code change,
and after changing the callback module,
but before the first
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
in the current callback module's code version is called.
More occasions may be added in future versions
of <c>gen_statem</c>.
</p>
<p>
Server start happens either when
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
returns or when
- <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>
+ <seemfa marker="#enter_loop/4"><c>enter_loop/4-6</c></seemfa>
is called.
Code change happens when
- <seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>
+ <seemfa marker="#Module:code_change/4"><c>Module:code_change/4</c></seemfa>
returns.
A change of the callback module happens when a
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
returns any of the actions
- <seealso marker="#type-action"><c>change_callback_module</c></seealso>,
- <seealso marker="#type-action"><c>push_callback_module</c></seealso> or
- <seealso marker="#type-action"><c>pop_callback_module</c></seealso>.
+ <seetype marker="#action"><c>change_callback_module</c></seetype>,
+ <seetype marker="#action"><c>push_callback_module</c></seetype> or
+ <seetype marker="#action"><c>pop_callback_module</c></seetype>.
</p>
<p>
The <c>CallbackMode</c> is either just
- <seealso marker="#type-callback_mode"><c>callback_mode()</c></seealso>
+ <seetype marker="#callback_mode"><c>callback_mode()</c></seetype>
or a list containing
- <seealso marker="#type-callback_mode"><c>callback_mode()</c></seealso>
+ <seetype marker="#callback_mode"><c>callback_mode()</c></seetype>
and possibly the atom
- <seealso marker="#type-state_enter"><c>state_enter</c></seealso>.
+ <seetype marker="#state_enter"><c>state_enter</c></seetype>.
</p>
<note>
<p>
@@ -2084,11 +2250,11 @@ handle_event(_, _, State, Data) ->
<v>Result = {ok,NewState,NewData} | Reason</v>
<v>
OldState = NewState =
- <seealso marker="#type-state">state()</seealso>
+ <seetype marker="#state">state()</seetype>
</v>
<v>
OldData = NewData =
- <seealso marker="#type-data">data()</seealso>
+ <seetype marker="#data">data()</seetype>
</v>
<v>Reason = term()</v>
</type>
@@ -2108,9 +2274,9 @@ handle_event(_, _, State, Data) ->
update its internal state during a release upgrade/downgrade,
that is, when the instruction <c>{update,Module,Change,...}</c>,
where <c>Change = {advanced,Extra}</c>, is specified in the
- <seealso marker="sasl:appup"><c>appup</c></seealso>
+ <seefile marker="sasl:appup"><c>appup</c></seefile>
file. For more information, see
- <seealso marker="doc/design_principles:release_handling#instr">OTP Design Principles</seealso>.
+ <seeguide marker="system/design_principles:release_handling#instr">OTP Design Principles</seeguide>.
</p>
<p>
For an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and
@@ -2148,20 +2314,20 @@ handle_event(_, _, State, Data) ->
Also note when upgrading a <c>gen_statem</c>,
this function and hence
the <c>Change = {advanced,Extra}</c> parameter in the
- <seealso marker="sasl:appup"><c>appup</c></seealso> file
+ <seefile marker="sasl:appup"><c>appup</c></seefile> file
is not only needed to update the internal state
or to act on the <c>Extra</c> argument.
It is also needed if an upgrade or downgrade should change
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>,
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>,
or else the <em>callback mode</em> after the code change
will not be honoured,
most probably causing a server crash.
</p>
<p>
If the server changes callback module using any of the actions
- <seealso marker="#type-action"><c>change_callback_module</c></seealso>,
- <seealso marker="#type-action"><c>push_callback_module</c></seealso> or
- <seealso marker="#type-action"><c>pop_callback_module</c></seealso>,
+ <seetype marker="#action"><c>change_callback_module</c></seetype>,
+ <seetype marker="#action"><c>push_callback_module</c></seetype> or
+ <seetype marker="#action"><c>pop_callback_module</c></seetype>,
be aware that it is always the current callback module that
will get this callback call. That the current callback module
handles the current state and data update should be no surprise,
@@ -2171,7 +2337,7 @@ handle_event(_, _, State, Data) ->
</p>
<p>
In the supervisor
- <seealso marker="doc/design_principles:sup_princ#child-specification">child specification</seealso>
+ <seeguide marker="system/design_principles:sup_princ#child-specification">child specification</seeguide>
there is a list of modules which is recommended to contain
only the callback module.
For a <c>gen_statem</c> with multiple callback modules
@@ -2200,16 +2366,17 @@ handle_event(_, _, State, Data) ->
<v>Args = term()</v>
<v>
Result(StateType) =
- <seealso marker="#type-init_result">init_result(StateType)</seealso>
+ <seetype marker="#init_result">init_result(StateType)</seetype>
</v>
</type>
<desc>
<marker id="Module:init-1"/>
<p>
Whenever a <c>gen_statem</c> is started using
- <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>
+ <seemfa marker="#start_link/3"><c>start_link/3,4</c></seemfa>,
+ <seemfa marker="#start_monitor/3"><c>start_monitor/3,4</c></seemfa>,
or
- <seealso marker="#start/3"><c>start/3,4</c></seealso>,
+ <seemfa marker="#start/3"><c>start/3,4</c></seemfa>,
this function is called by the new process to initialize
the implementation state and server data.
</p>
@@ -2220,9 +2387,9 @@ handle_event(_, _, State, Data) ->
<note>
<p>
Note that if the <c>gen_statem</c> is started through
- <seealso marker="proc_lib"><c>proc_lib</c></seealso>
+ <seeerl marker="proc_lib"><c>proc_lib</c></seeerl>
and
- <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>,
+ <seemfa marker="#enter_loop/4"><c>enter_loop/4-6</c></seemfa>,
this callback will never be called.
Since this callback is not optional it can
in that case be implemented as:
@@ -2245,11 +2412,11 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<v>PDict = [{Key, Value}]</v>
<v>
State =
- <seealso marker="#type-state">state()</seealso>
+ <seetype marker="#state">state()</seetype>
</v>
<v>
Data =
- <seealso marker="#type-data">data()</seealso>
+ <seetype marker="#data">data()</seetype>
</v>
<v>Key = term()</v>
<v>Value = term()</v>
@@ -2276,7 +2443,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<list type="bulleted">
<item>
One of
- <seealso marker="sys#get_status/1"><c>sys:get_status/1,2</c></seealso>
+ <seemfa marker="sys#get_status/1"><c>sys:get_status/1,2</c></seemfa>
is invoked to get the <c>gen_statem</c> status. <c>Opt</c> is set
to the atom <c>normal</c> for this case.
</item>
@@ -2289,7 +2456,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
This function is useful for changing the form and
appearance of the <c>gen_statem</c> status for these cases. A
callback module wishing to change the
- <seealso marker="sys#get_status/1"><c>sys:get_status/1,2</c></seealso>
+ <seemfa marker="sys#get_status/1"><c>sys:get_status/1,2</c></seemfa>
return value and how
its status appears in termination error logs exports an
instance of <c>format_status/2</c>, which returns a term
@@ -2300,11 +2467,11 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
of the <c>gen_statem</c>.
</p>
<p>
- <seealso marker="#type-state"><c>State</c></seealso>
+ <seetype marker="#state"><c>State</c></seetype>
is the internal state of the <c>gen_statem</c>.
</p>
<p>
- <seealso marker="#type-data"><c>Data</c></seealso>
+ <seetype marker="#data"><c>Data</c></seetype>
is the internal server data of the <c>gen_statem</c>.
</p>
<p>
@@ -2313,7 +2480,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
of the current state and status of
the <c>gen_statem</c>. There are no restrictions on the
form <c>Status</c> can take, but for the
- <seealso marker="sys#get_status/1"><c>sys:get_status/1,2</c></seealso>
+ <seemfa marker="sys#get_status/1"><c>sys:get_status/1,2</c></seemfa>
case (when <c>Opt</c>
is <c>normal</c>), the recommended form for
the <c>Status</c> value is <c>[{data, [{"State",
@@ -2321,7 +2488,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
the <c>gen_statem</c> state. Following this recommendation is not
required, but it makes the callback module status
consistent with the rest of the
- <seealso marker="sys#get_status/1"><c>sys:get_status/1,2</c></seealso>
+ <seemfa marker="sys#get_status/1"><c>sys:get_status/1,2</c></seemfa>
return value.
</p>
<p>
@@ -2350,56 +2517,56 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<type>
<v>
EventType =
- <seealso marker="#type-event_type">event_type()</seealso>
+ <seetype marker="#event_type">event_type()</seetype>
</v>
<v>EventContent = term()</v>
<v>
State =
- <seealso marker="#type-state">state()</seealso>
+ <seetype marker="#state">state()</seetype>
</v>
<v>
Data = NewData =
- <seealso marker="#type-data">data()</seealso>
+ <seetype marker="#data">data()</seetype>
</v>
<v>
StateEnterResult(StateName) =
- <seealso marker="#type-state_enter_result">state_enter_result(StateName)</seealso>
+ <seetype marker="#state_enter_result">state_enter_result(StateName)</seetype>
</v>
<v>
StateFunctionResult =
- <seealso marker="#type-event_handler_result">event_handler_result</seealso>(<seealso marker="#type-state_name">state_name()</seealso>)
+ <seetype marker="#event_handler_result">event_handler_result</seetype>(<seetype marker="#state_name">state_name()</seetype>)
</v>
<v>
StateEnterResult(State) =
- <seealso marker="#type-state_enter_result">state_enter_result(State)</seealso>
+ <seetype marker="#state_enter_result">state_enter_result(State)</seetype>
</v>
<v>
HandleEventResult =
- <seealso marker="#type-event_handler_result">event_handler_result</seealso>(<seealso marker="#type-state">state()</seealso>)
+ <seetype marker="#event_handler_result">event_handler_result</seetype>(<seetype marker="#state">state()</seetype>)
</v>
</type>
<desc>
<p>
Whenever a <c>gen_statem</c> receives an event from
- <seealso marker="#call/2"><c>call/2</c></seealso>,
- <seealso marker="#cast/2"><c>cast/2</c></seealso>, or
+ <seemfa marker="#call/2"><c>call/2</c></seemfa>,
+ <seemfa marker="#cast/2"><c>cast/2</c></seemfa>, or
as a normal process message, one of these functions is called. If
- <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
+ <seetype marker="#callback_mode"><em>callback mode</em></seetype>
is <c>state_functions</c>, <c>Module:StateName/3</c> is called,
and if it is <c>handle_event_function</c>,
<c>Module:handle_event/4</c> is called.
</p>
<p>
If <c>EventType</c> is
- <seealso marker="#type-event_type"><c>{call,From}</c></seealso>,
+ <seetype marker="#event_type"><c>{call,From}</c></seetype>,
the caller waits for a reply. The reply can be sent
from this or from any other
- <seealso marker="#state callback"><em>state callback</em></seealso>
+ <seeerl marker="#state callback"><em>state callback</em></seeerl>
by returning with <c>{reply,From,Reply}</c> in
- <seealso marker="#type-action"><c>Actions</c></seealso>, in
- <seealso marker="#type-reply_action"><c>Replies</c></seealso>,
+ <seetype marker="#action"><c>Actions</c></seetype>, in
+ <seetype marker="#reply_action"><c>Replies</c></seetype>,
or by calling
- <seealso marker="#reply/2"><c>reply(From, Reply)</c></seealso>.
+ <seemfa marker="#reply/2"><c>reply(From, Reply)</c></seemfa>.
</p>
<p>
If this function returns with a next state that
@@ -2415,20 +2582,20 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<p>
For options that can be set and actions that can be done
by <c>gen_statem</c> after returning from this function,
- see <seealso marker="#type-action"><c>action()</c></seealso>.
+ see <seetype marker="#action"><c>action()</c></seetype>.
</p>
<p>
When the <c>gen_statem</c> runs with
- <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
+ <seetype marker="#state_enter"><em>state enter calls</em></seetype>,
these functions are also called with arguments
<c>(enter, OldState, ...)</c> during every <em>state change</em>.
In this case there are some restrictions on the
- <seealso marker="#type-enter_action">actions</seealso>
+ <seetype marker="#enter_action">actions</seetype>
that may be returned:
- <seealso marker="#type-postpone"><c>postpone()</c></seealso>
+ <seetype marker="#postpone"><c>postpone()</c></seetype>
is not allowed since a <em>state enter call</em> is not
an event so there is no event to postpone, and
- <seealso marker="#type-action"><c>{next_event,_,_}</c></seealso>
+ <seetype marker="#action"><c>{next_event,_,_}</c></seetype>
is not allowed since using <em>state enter calls</em>
should not affect how events are consumed and produced.
You may also not change states from this call.
@@ -2451,7 +2618,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</p>
<p>
Note the fact that you can use
- <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>
+ <seemfa marker="erts:erlang#throw/1"><c>throw</c></seemfa>
to return the result, which can be useful.
For example to bail out with <c>throw(keep_state_and_data)</c>
from deep within complex code that cannot
@@ -2466,8 +2633,8 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<fsummary>Clean up before termination.</fsummary>
<type>
<v>Reason = normal | shutdown | {shutdown,term()} | term()</v>
- <v>State = <seealso marker="#type-state">state()</seealso></v>
- <v>Data = <seealso marker="#type-data">data()</seealso></v>
+ <v>State = <seetype marker="#state">state()</seetype></v>
+ <v>Data = <seetype marker="#data">data()</seetype></v>
<v>Ignored = term()</v>
</type>
<desc>
@@ -2479,13 +2646,13 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<p>
This function is called by a <c>gen_statem</c>
when it is about to terminate. It is to be the opposite of
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ <seemfa marker="#Module:init/1"><c>Module:init/1</c></seemfa>
and do any necessary cleaning up. When it returns,
the <c>gen_statem</c> terminates with <c>Reason</c>. The return
value is ignored.</p>
<p>
<c>Reason</c> is a term denoting the stop reason and
- <seealso marker="#type-state"><c>State</c></seealso>
+ <seetype marker="#state"><c>State</c></seetype>
is the internal state of the <c>gen_statem</c>.
</p>
<p>
@@ -2493,7 +2660,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
is terminating.
If it is because another callback function has returned, a
stop tuple <c>{stop,Reason}</c> in
- <seealso marker="#type-action"><c>Actions</c></seealso>,
+ <seetype marker="#action"><c>Actions</c></seetype>,
<c>Reason</c> has the value specified in that tuple.
If it is because of a failure, <c>Reason</c> is the error reason.
</p>
@@ -2532,7 +2699,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<c>shutdown</c>, or <c>{shutdown,Term}</c>,
the <c>gen_statem</c> is assumed to terminate because of an error
and an error report is issued using
- <seealso marker="kernel:logger"><c>logger(3)</c></seealso>.
+ <seeerl marker="kernel:logger"><c>logger(3)</c></seeerl>.
</p>
</desc>
</func>
@@ -2541,12 +2708,12 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<section>
<title>See Also</title>
<p>
- <seealso marker="gen_event"><c>gen_event(3)</c></seealso>,
- <seealso marker="gen_fsm"><c>gen_fsm(3)</c></seealso>,
- <seealso marker="gen_server"><c>gen_server(3)</c></seealso>,
- <seealso marker="proc_lib"><c>proc_lib(3)</c></seealso>,
- <seealso marker="supervisor"><c>supervisor(3)</c></seealso>,
- <seealso marker="sys"><c>sys(3)</c></seealso>.
+ <seeerl marker="gen_event"><c>gen_event(3)</c></seeerl>,
+ <seeerl marker="gen_fsm"><c>gen_fsm(3)</c></seeerl>,
+ <seeerl marker="gen_server"><c>gen_server(3)</c></seeerl>,
+ <seeerl marker="proc_lib"><c>proc_lib(3)</c></seeerl>,
+ <seeerl marker="supervisor"><c>supervisor(3)</c></seeerl>,
+ <seeerl marker="sys"><c>sys(3)</c></seeerl>.
</p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml
index d69e808586..9fd87d9cf1 100644
--- a/lib/stdlib/doc/src/io.xml
+++ b/lib/stdlib/doc/src/io.xml
@@ -39,29 +39,29 @@
parameter <c>IoDevice</c>. If included, it must be the pid of a
process that handles the I/O protocols. Normally, it is the
<c>IoDevice</c> returned by
- <seealso marker="kernel:file#open/2"><c>file:open/2</c></seealso>.</p>
+ <seemfa marker="kernel:file#open/2"><c>file:open/2</c></seemfa>.</p>
<p>For a description of the I/O protocols, see section
- <seealso marker="io_protocol">The Erlang I/O Protocol</seealso>
+ <seeguide marker="io_protocol">The Erlang I/O Protocol</seeguide>
in the User's Guide.</p>
<warning>
<p>As from Erlang/OTP R13A, data supplied to function
- <seealso marker="#put_chars/2"><c>put_chars/2</c></seealso>
- is to be in the <seealso marker="unicode#type-chardata">
- <c>unicode:chardata()</c></seealso> format. This means that programs
+ <seemfa marker="#put_chars/2"><c>put_chars/2</c></seemfa>
+ is to be in the <seetype marker="unicode#chardata">
+ <c>unicode:chardata()</c></seetype> format. This means that programs
supplying binaries to this function must convert them to UTF-8
before trying to output the data on an I/O device.</p>
<p>If an I/O device is set in binary mode, functions
- <seealso marker="#get_chars/2"><c>get_chars/2,3</c></seealso> and
- <seealso marker="#get_line/1"><c>get_line/1,2</c></seealso>
+ <seemfa marker="#get_chars/2"><c>get_chars/2,3</c></seemfa> and
+ <seemfa marker="#get_line/1"><c>get_line/1,2</c></seemfa>
can return binaries instead of lists.
The binaries are, as from Erlang/OTP R13A,
encoded in UTF-8.</p>
<p>To work with binaries in ISO Latin-1 encoding, use the
- <seealso marker="kernel:file"><c>file</c></seealso> module instead.</p>
+ <seeerl marker="kernel:file"><c>file</c></seeerl> module instead.</p>
<p>For conversion functions between character encodings, see the
- <seealso marker="stdlib:unicode"><c>unicode</c></seealso> module.</p>
+ <seeerl marker="stdlib:unicode"><c>unicode</c></seeerl> module.</p>
</warning>
</description>
@@ -71,7 +71,7 @@
<desc>
<p>An I/O device, either <c>standard_io</c>, <c>standard_error</c>, a
registered name, or a pid handling I/O protocols (returned from
- <seealso marker="kernel:file#open/2"><c>file:open/2</c></seealso>).
+ <seemfa marker="kernel:file#open/2"><c>file:open/2</c></seemfa>).
</p>
</desc>
</datatype>
@@ -317,8 +317,8 @@ ok</pre>
It defaults to 80. The precision specifies the initial
indentation of the term. It defaults to the number of
characters printed on this line in the <em>same</em> call to
- <seealso marker="#write/1"><c>write/1</c></seealso> or
- <seealso marker="#format/1"><c>format/1,2,3</c></seealso>.
+ <seemfa marker="#write/1"><c>write/1</c></seemfa> or
+ <seemfa marker="#format/1"><c>format/1,2,3</c></seemfa>.
For example, using <c>T</c> above:</p>
<pre>
4> <input>io:fwrite("Here T = ~62p~n", [T]).</input>
@@ -368,9 +368,9 @@ ok
ok</pre>
<p>By default, Erlang only detects lists of characters
in the Latin-1 range as strings, but the <c>+pc unicode</c>
- flag can be used to change this (see <seealso
+ flag can be used to change this (see <seemfa
marker="#printable_range/0">
- <c>printable_range/0</c></seealso> for details). For example:</p>
+ <c>printable_range/0</c></seemfa> for details). For example:</p>
<pre>
10> <input>io:fwrite("~p~n",[[214]]).</input>
"Ö"
@@ -804,8 +804,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
<c><anno>Prompt</anno></c>. Starts reading at location
<c><anno>StartLocation</anno></c> (<c>1</c>). Argument
<c><anno>Options</anno></c> is passed on as argument
- <c>Options</c> of function <seealso marker="erl_scan#tokens/4">
- <c>erl_scan:tokens/4</c></seealso>. The data is tokenized and parsed
+ <c>Options</c> of function <seemfa marker="erl_scan#tokens/4">
+ <c>erl_scan:tokens/4</c></seemfa>. The data is tokenized and parsed
as if it was a sequence of Erlang expressions until a final dot
(<c>.</c>) is reached.</p>
<p>The function returns:</p>
@@ -856,8 +856,8 @@ enter><input>abc("hey".</input>
prompting it with <c><anno>Prompt</anno></c>. Starts reading at
location <c><anno>StartLocation</anno></c> (<c>1</c>). Argument
<c><anno>Options</anno></c> is passed on as argument
- <c>Options</c> of function <seealso marker="erl_scan#tokens/4">
- <c>erl_scan:tokens/4</c></seealso>. The data is tokenized and parsed
+ <c>Options</c> of function <seemfa marker="erl_scan#tokens/4">
+ <c>erl_scan:tokens/4</c></seemfa>. The data is tokenized and parsed
as if it was an Erlang form (one of the valid Erlang expressions
in an Erlang source file) until a final dot (<c>.</c>) is reached.</p>
<p>The function returns:</p>
@@ -905,15 +905,15 @@ enter><input>abc("hey".</input>
<p>By default, Erlang is started so that only the <c>latin1</c> range
of characters indicate that a list of integers is a string.</p>
<p>The simplest way to use the setting is to call
- <seealso marker="io_lib#printable_list/1">
- <c>io_lib:printable_list/1</c></seealso>, which uses the return
+ <seemfa marker="io_lib#printable_list/1">
+ <c>io_lib:printable_list/1</c></seemfa>, which uses the return
value of this function to decide if a list is a string of printable
characters.</p>
<note>
<p>In a future release, this function may return more values and
ranges. To avoid compatibility problems, it is recommended to use
- function <seealso marker="io_lib#printable_list/1">
- <c>io_lib:printable_list/1</c></seealso>.</p></note>
+ function <seemfa marker="io_lib#printable_list/1">
+ <c>io_lib:printable_list/1</c></seemfa>.</p></note>
</desc>
</func>
@@ -970,8 +970,8 @@ enter><input>abc("hey".</input>
with <c><anno>Prompt</anno></c>. Reading starts at location
<c><anno>StartLocation</anno></c>. Argument
<c><anno>Options</anno></c> is passed on as argument <c>Options</c>
- of function <seealso marker="erl_scan#tokens/4">
- <c>erl_scan:tokens/4</c></seealso>.</p>
+ of function <seemfa marker="erl_scan#tokens/4">
+ <c>erl_scan:tokens/4</c></seemfa>.</p>
<p>The function returns:</p>
<taglist>
<tag><c>{ok, Term, <anno>EndLocation</anno>}</c></tag>
@@ -1020,8 +1020,8 @@ enter><input>abc("hey".</input>
prompting it with <c>Prompt</c>. Reading starts at location
<c>StartLocation</c> (<c>1</c>). Argument <c><anno>Options</anno></c>
is passed on as argument <c>Options</c> of function
- <seealso marker="erl_scan#tokens/4">
- <c>erl_scan:tokens/4</c></seealso>. The data is tokenized as if it
+ <seemfa marker="erl_scan#tokens/4">
+ <c>erl_scan:tokens/4</c></seemfa>. The data is tokenized as if it
were a sequence of Erlang expressions until a final dot (<c>.</c>) is
reached. This token is also returned.</p>
<p>The function returns:</p>
@@ -1071,14 +1071,14 @@ enter><input>1.0er.</input>
prompting it with <c><anno>Prompt</anno></c>. Starts reading
at location <c><anno>StartLocation</anno></c> (<c>1</c>).
Argument <c><anno>Options</anno></c> is passed on as argument
- <c>Options</c> of function <seealso marker="erl_scan#tokens/4">
- <c>erl_scan:tokens/4</c></seealso>. The data is tokenized as if it
+ <c>Options</c> of function <seemfa marker="erl_scan#tokens/4">
+ <c>erl_scan:tokens/4</c></seemfa>. The data is tokenized as if it
was an Erlang form (one of the valid Erlang expressions in an
Erlang source file) until a final dot (<c>.</c>) is reached.
This last token is also returned.</p>
<p>The return values are the same as for
- <seealso marker="#scan_erl_exprs/1">
- <c>scan_erl_exprs/1,2,3,4</c></seealso>.</p>
+ <seemfa marker="#scan_erl_exprs/1">
+ <c>scan_erl_exprs/1,2,3,4</c></seemfa>.</p>
</desc>
</func>
@@ -1092,7 +1092,7 @@ enter><input>1.0er.</input>
<p>Possible options and values vary depending on the
I/O device. For a list of supported options and their current values
on a specific I/O device, use function
- <seealso marker="#getopts/1"><c>getopts/1</c></seealso>.</p>
+ <seemfa marker="#getopts/1"><c>getopts/1</c></seemfa>.</p>
<p>The options and values supported by the OTP I/O devices
are as follows:</p>
<taglist>
@@ -1102,10 +1102,10 @@ enter><input>1.0er.</input>
the I/O server sends binary data (encoded in UTF-8) as answers
to the <c>get_line</c>, <c>get_chars</c>, and, if possible,
<c>get_until</c> requests (for details, see section
- <seealso marker="io_protocol">The Erlang I/O Protocol</seealso>)
+ <seeguide marker="io_protocol">The Erlang I/O Protocol</seeguide>)
in the User's Guide). The immediate effect is that
- <seealso marker="#get_chars/2"><c>get_chars/2,3</c></seealso> and
- <seealso marker="#get_line/1"><c>get_line/1,2</c></seealso>
+ <seemfa marker="#get_chars/2"><c>get_chars/2,3</c></seemfa> and
+ <seemfa marker="#get_line/1"><c>get_line/1,2</c></seemfa>
return UTF-8 binaries instead of lists of characters
for the affected I/O device.</p>
<p>By default, all I/O devices in OTP are set in <c>list</c> mode.
@@ -1127,7 +1127,7 @@ enter><input>1.0er.</input>
like the Erlang shell. This function is called
when the user presses the <em>Tab</em> key. The expansion is
active when calling line-reading functions, such as
- <seealso marker="#get_line/1"><c>get_line/1,2</c></seealso>.</p>
+ <seemfa marker="#get_line/1"><c>get_line/1,2</c></seemfa>.</p>
<p>The function is called with the current line, up to
the cursor, as a reversed string. It is to return a
three-tuple: <c>{yes|no, string(), [string(), ...]}</c>. The
@@ -1190,8 +1190,8 @@ fun("") -> {yes, "quit", []};
<c>{encoding, unicode}</c> on files.</p>
<p>The extended encodings are only supported on disk files
(opened by function
- <seealso marker="kernel:file#open/2">
- <c>file:open/2</c></seealso>).</p>
+ <seemfa marker="kernel:file#open/2">
+ <c>file:open/2</c></seemfa>).</p>
</item>
</taglist>
</desc>
diff --git a/lib/stdlib/doc/src/io_lib.xml b/lib/stdlib/doc/src/io_lib.xml
index 0e743867ba..3b7aea529e 100644
--- a/lib/stdlib/doc/src/io_lib.xml
+++ b/lib/stdlib/doc/src/io_lib.xml
@@ -33,11 +33,11 @@
<description>
<p>This module contains functions for converting to and from
strings (lists of characters). They are used for implementing the
- functions in the <seealso marker="io"><c>io</c></seealso> module.
+ functions in the <seeerl marker="io"><c>io</c></seeerl> module.
There is no guarantee that the
character lists returned from some of the functions are flat,
they can be deep lists. Function
- <seealso marker="lists#flatten/1"><c>lists:flatten/1</c></seealso>
+ <seemfa marker="lists#flatten/1"><c>lists:flatten/1</c></seemfa>
can be used for flattening deep lists.</p>
</description>
@@ -48,7 +48,7 @@
<datatype>
<name name="continuation"/>
<desc><p>A continuation as returned by
- <seealso marker="#fread/3"><c>fread/3</c></seealso>.</p>
+ <seemfa marker="#fread/3"><c>fread/3</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -103,7 +103,7 @@
<fsummary>Build the output text for a preparsed format list.</fsummary>
<desc>
<p>For details, see
- <seealso marker="#scan_format/2"><c>scan_format/2</c></seealso>.</p>
+ <seemfa marker="#scan_format/2"><c>scan_format/2</c></seemfa>.</p>
</desc>
</func>
@@ -143,7 +143,7 @@
<p>Returns a character list that represents <c><anno>Data</anno></c>
formatted in accordance with <c><anno>Format</anno></c>.
For a detailed description of the available formatting options, see
- <seealso marker="io#fwrite/1"><c>io:fwrite/1,2,3</c></seealso>.
+ <seemfa marker="io#fwrite/1"><c>io:fwrite/1,2,3</c></seemfa>.
If the format string or argument list contains an error, a fault is
generated.</p>
<p>If and only if the Unicode translation modifier is used in the
@@ -163,8 +163,8 @@
<p>Returns a character list that represents <c><anno>Data</anno></c>
formatted in accordance with <c><anno>Format</anno></c> in
the same way as
- <seealso marker="#fwrite/2"><c>fwrite/2</c></seealso> and
- <seealso marker="#format/2"><c>format/2</c></seealso>,
+ <seemfa marker="#fwrite/2"><c>fwrite/2</c></seemfa> and
+ <seemfa marker="#format/2"><c>format/2</c></seemfa>,
but takes an extra argument, a list of options.</p>
<p>Valid option:</p>
<taglist>
@@ -187,7 +187,7 @@
<p>Tries to read <c><anno>String</anno></c> in accordance with the
control sequences in <c><anno>Format</anno></c>.
For a detailed description of the available formatting options, see
- <seealso marker="io#fread/3"><c>io:fread/3</c></seealso>. It is
+ <seemfa marker="io#fread/3"><c>io:fread/3</c></seemfa>. It is
assumed that <c><anno>String</anno></c> contains whole lines.</p>
<p>The function returns:</p>
<taglist>
@@ -331,9 +331,9 @@
printable characters, otherwise <c>false</c>.</p>
<p>What is a printable character in this case is determined by
startup flag <c>+pc</c> to the Erlang VM; see
- <seealso marker="io#printable_range/0">
- <c>io:printable_range/0</c></seealso> and
- <seealso marker="erts:erl"><c>erl(1)</c></seealso>.</p>
+ <seemfa marker="io#printable_range/0">
+ <c>io:printable_range/0</c></seemfa> and
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom>.</p>
</desc>
</func>
@@ -355,12 +355,12 @@
corresponding tuples. This list can be passed to:</p>
<list type="bulleted">
<item>
- <p><seealso marker="#build_text/1"><c>build_text/1</c></seealso>
+ <p><seemfa marker="#build_text/1"><c>build_text/1</c></seemfa>
to have the same effect as <c>format(Format, Args)</c></p>
</item>
<item>
- <p><seealso marker="#unscan_format/1">
- <c>unscan_format/1</c></seealso> to get the corresponding pair
+ <p><seemfa marker="#unscan_format/1">
+ <c>unscan_format/1</c></seemfa> to get the corresponding pair
of <c>Format</c> and <c>Args</c> (with every <c>*</c> and
corresponding argument expanded to numeric values)</p>
</item>
@@ -378,7 +378,7 @@
and a list of arguments.</fsummary>
<desc>
<p>For details, see
- <seealso marker="#scan_format/2"><c>scan_format/2</c></seealso>.</p>
+ <seemfa marker="#scan_format/2"><c>scan_format/2</c></seemfa>.</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/io_protocol.xml b/lib/stdlib/doc/src/io_protocol.xml
index 84b5f62c7f..c2292d7d49 100644
--- a/lib/stdlib/doc/src/io_protocol.xml
+++ b/lib/stdlib/doc/src/io_protocol.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>1999</year>
- <year>2016</year>
+ <year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -86,7 +86,7 @@
<item>
<p><c>ReplyAs</c> can be any datum and is returned in the
corresponding <c>io_reply</c>. The
- <seealso marker="stdlib:io"><c>io</c></seealso> module monitors the
+ <seeerl marker="stdlib:io"><c>io</c></seeerl> module monitors the
the I/O server and uses the monitor reference as the <c>ReplyAs</c>
datum. A more complicated client can have many outstanding I/O
requests to the same I/O server and can use different references (or
@@ -142,7 +142,7 @@
<item>
<p><c>Module</c>, <c>Function</c>, and <c>Args</c> denote a function
that is called to produce the data (like
- <seealso marker="stdlib:io_lib#format/2"><c>io_lib:format/2</c></seealso>).
+ <seemfa marker="stdlib:io_lib#format/2"><c>io_lib:format/2</c></seemfa>).
</p>
<p><c>Args</c> is a list of arguments to the function. The function is
to produce data in the specified <c>Encoding</c>. The I/O server is
@@ -164,20 +164,10 @@ ok
<list type="bulleted">
<item><c>Error</c> describes the error to the client, which can do
whatever it wants with it. The
- <seealso marker="stdlib:io"><c>io</c></seealso> module typically
+ <seeerl marker="stdlib:io"><c>io</c></seeerl> module typically
returns it "as is".</item>
</list>
- <p>For backward compatibility, the following <c>Request</c>s are also to be
- handled by an I/O server (they are not to be present after
- Erlang/OTP R15B):</p>
-
- <pre>
-{put_chars, Characters}
-{put_chars, Module, Function, Args}</pre>
-
- <p>These are to behave as <c>{put_chars, latin1, Characters}</c> and
- <c>{put_chars, latin1, Module, Function, Args}</c>, respectively.</p>
</section>
<section>
@@ -327,24 +317,11 @@ eof
<item>
<p><c>Error</c> describes the error to the client, which can do
whatever it wants with it. The
- <seealso marker="stdlib:io"><c>io</c></seealso> module typically
+ <seeerl marker="stdlib:io"><c>io</c></seeerl> module typically
returns it as is.</p>
</item>
</list>
- <p>For backward compatibility, the following <c>Request</c>s are also to be
- handled by an I/O server (they are not to be present after
- Erlang/OTP R15B):</p>
-
- <pre>
-{get_until, Prompt, Module, Function, ExtraArgs}
-{get_chars, Prompt, N}
-{get_line, Prompt}</pre>
-
- <p>These are to behave as
- <c>{get_until, latin1, Prompt, Module, Function, ExtraArgs}</c>,
- <c>{get_chars, latin1, Prompt, N}</c>, and
- <c>{get_line, latin1, Prompt}</c>, respectively.</p>
</section>
<section>
@@ -363,14 +340,14 @@ eof
<p>Notice that the <c>get_until</c> request allows for a function with the
data specified as always being a list. Also, the return value data from
such a function can be of any type (as is indeed the case when an
- <seealso marker="stdlib:io#fread/2"><c>io:fread/2,3</c></seealso>
+ <seemfa marker="stdlib:io#fread/2"><c>io:fread/2,3</c></seemfa>
request is sent to an I/O server).
The client must be prepared for data received as
answers to those requests to be in various forms. However, the I/O
server is to convert the results to binaries whenever possible (that is,
when the function supplied to <c>get_until</c> returns a list). This is
done in the example in section
- <seealso marker="#example_io_server">An Annotated and Working Example I/O Server</seealso>.
+ <seeguide marker="#example_io_server">An Annotated and Working Example I/O Server</seeguide>.
</p>
<p>An I/O server in binary mode affects the data sent to the client, so that
@@ -382,7 +359,7 @@ eof
<list type="bulleted">
<item><c>Opts</c> is a list of options in the format recognized by the
- <seealso marker="stdlib:proplists"><c>proplists</c></seealso> module
+ <seeerl marker="stdlib:proplists"><c>proplists</c></seeerl> module
(and by the I/O server).</item>
</list>
@@ -554,7 +531,7 @@ loop(State) -&gt;
end.</code>
<p>The main loop receives messages from the client (which can use the
- the <seealso marker="stdlib:io"><c>io</c></seealso> module to send
+ the <seeerl marker="stdlib:io"><c>io</c></seeerl> module to send
requests). For each request, the function <c>request/2</c> is called and a
reply is eventually sent using function <c>reply/3</c>.</p>
@@ -592,7 +569,7 @@ request({put_chars, Encoding, Module, Function, Args}, State) -&gt;
<p>The <c>Encoding</c> says how the characters in the request are
represented. We want to store the characters as lists in the ETS
table, so we convert them to lists using function
- <seealso marker="stdlib:unicode#characters_to_list/2"><c>unicode:characters_to_list/2</c></seealso>.
+ <seemfa marker="stdlib:unicode#characters_to_list/2"><c>unicode:characters_to_list/2</c></seemfa>.
The conversion function conveniently accepts the encoding types
<c>unicode</c> and <c>latin1</c>, so we can use <c>Encoding</c> directly.</p>
@@ -637,24 +614,6 @@ request({requests, Reqs}, State) -&gt;
function applying the requests in the list one after another, returning
the last result.</p>
- <p>We need to handle backward compatibility and the
- <seealso marker="kernel:file"><c>file</c></seealso> module (which
- uses the old requests until backward compatibility with pre-R13 nodes is
- no longer needed). Notice that the I/O server does not work with a simple
- <c>file:write/2</c> if these are not added:</p>
-
- <code>
-request({put_chars,Chars}, State) -&gt;
- request({put_chars,latin1,Chars}, State);
-request({put_chars,M,F,As}, State) -&gt;
- request({put_chars,latin1,M,F,As}, State);
-request({get_chars,Prompt,N}, State) -&gt;
- request({get_chars,latin1,Prompt,N}, State);
-request({get_line,Prompt}, State) -&gt;
- request({get_line,latin1,Prompt}, State);
-request({get_until, Prompt,M,F,As}, State) -&gt;
- request({get_until,latin1,Prompt,M,F,As}, State);</code>
-
<p><c>{error, request}</c> must be returned if the request is not
recognized:</p>
@@ -910,8 +869,8 @@ apply_update(Table, {Row, Col, List}) -&gt;
<p>This concludes the example. It is fully runnable and you can read or
write to the I/O server by using, for example, the
- <seealso marker="stdlib:io"><c>io</c></seealso> module or even the
- <seealso marker="kernel:file"><c>file</c></seealso> module. It is
+ <seeerl marker="stdlib:io"><c>io</c></seeerl> module or even the
+ <seeerl marker="kernel:file"><c>file</c></seeerl> module. It is
as simple as that to implement a fully fledged I/O server in Erlang.</p>
</section>
</chapter>
diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml
index 2755fb3dce..9261ade998 100644
--- a/lib/stdlib/doc/src/lists.xml
+++ b/lib/stdlib/doc/src/lists.xml
@@ -281,7 +281,7 @@ flatmap(Fun, List1) ->
<name name="foldr" arity="3" since=""/>
<fsummary>Fold a function over a list.</fsummary>
<desc>
- <p>Like <seealso marker="#foldl/3"><c>foldl/3</c></seealso>, but the
+ <p>Like <seemfa marker="#foldl/3"><c>foldl/3</c></seemfa>, but the
list is traversed from right to left.</p>
<p><em>Example:</em></p>
<pre>
@@ -418,7 +418,7 @@ flatmap(Fun, List1) ->
otherwise <c>false</c>.</p>
<note>
<p>This function is retained for backward compatibility. Function
- <seealso marker="#keyfind/3"><c>keyfind/3</c></seealso>
+ <seemfa marker="#keyfind/3"><c>keyfind/3</c></seemfa>
is usually more convenient.</p>
</note>
</desc>
@@ -492,8 +492,8 @@ flatmap(Fun, List1) ->
<fsummary>Map and fold in one pass.</fsummary>
<desc>
<p>Combines the operations of
- <seealso marker="#map/2"><c>map/2</c></seealso> and
- <seealso marker="#foldl/3"><c>foldl/3</c></seealso> into one pass.</p>
+ <seemfa marker="#map/2"><c>map/2</c></seemfa> and
+ <seemfa marker="#foldl/3"><c>foldl/3</c></seemfa> into one pass.</p>
<p><em>Example:</em></p>
<p>Summing the elements in a list and double them at the same time:</p>
<pre>
@@ -508,8 +508,8 @@ flatmap(Fun, List1) ->
<fsummary>Map and fold in one pass.</fsummary>
<desc>
<p>Combines the operations of
- <seealso marker="#map/2"><c>map/2</c></seealso> and
- <seealso marker="#foldr/3"><c>foldr/3</c></seealso> into one pass.</p>
+ <seemfa marker="#map/2"><c>map/2</c></seemfa> and
+ <seemfa marker="#foldr/3"><c>foldr/3</c></seemfa> into one pass.</p>
</desc>
</func>
@@ -564,8 +564,8 @@ flatmap(Fun, List1) ->
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
and <c><anno>List2</anno></c>. Both <c><anno>List1</anno></c> and
- <c><anno>List2</anno></c> must be sorted according to the <seealso
- marker="#ordering_function">ordering function</seealso>
+ <c><anno>List2</anno></c> must be sorted according to the <seeerl
+ marker="#ordering_function">ordering function</seeerl>
<c><anno>Fun</anno></c> before evaluating this function.
<c><anno>Fun</anno>(<anno>A</anno>, <anno>B</anno>)</c> is to return
<c>true</c> if <c><anno>A</anno></c> compares less
@@ -653,7 +653,7 @@ c</pre>
> <input>lists:partition(fun(A) -> is_atom(A) end, [a,b,1,c,d,2,3,4,e]).</input>
{[a,b,c,d,e],[1,2,3,4]}</pre>
<p>For a different way to partition a list, see
- <seealso marker="#splitwith/2"><c>splitwith/2</c></seealso>.</p>
+ <seemfa marker="#splitwith/2"><c>splitwith/2</c></seemfa>.</p>
</desc>
</func>
@@ -761,8 +761,8 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code>
<fsummary>Sort a list.</fsummary>
<desc>
<p>Returns a list containing the sorted elements of
- <c><anno>List1</anno></c>, according to the <seealso
- marker="#ordering_function">ordering function</seealso>
+ <c><anno>List1</anno></c>, according to the <seeerl
+ marker="#ordering_function">ordering function</seeerl>
<c><anno>Fun</anno></c>. <c><anno>Fun</anno>(<anno>A</anno>,
<anno>B</anno>)</c> is to return <c>true</c> if <c><anno>A</anno></c>
compares less than or equal to <c><anno>B</anno></c> in the
@@ -799,7 +799,7 @@ splitwith(Pred, List) ->
> <input>lists:splitwith(fun(A) -> is_atom(A) end, [a,b,1,c,d,2,3,4,e]).</input>
{[a,b],[1,c,d,2,3,4,e]}</pre>
<p>For a different way to partition a list, see
- <seealso marker="#partition/2"><c>partition/2</c></seealso>.</p>
+ <seemfa marker="#partition/2"><c>partition/2</c></seemfa>.</p>
</desc>
</func>
@@ -946,8 +946,8 @@ splitwith(Pred, List) ->
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
and <c><anno>List2</anno></c>. Both <c><anno>List1</anno></c> and
- <c><anno>List2</anno></c> must be sorted according to the <seealso
- marker="#ordering_function">ordering function</seealso>
+ <c><anno>List2</anno></c> must be sorted according to the <seeerl
+ marker="#ordering_function">ordering function</seeerl>
<c>Fun</c> and contain no duplicates before evaluating this function.
<c><anno>Fun</anno>(<anno>A</anno>, <anno>B</anno>)</c> is to return
<c>true</c> if <c><anno>A</anno></c> compares less than or equal to
@@ -1009,8 +1009,8 @@ splitwith(Pred, List) ->
<desc>
<p>Returns a list containing the sorted elements of
<c><anno>List1</anno></c> where all except the first element of the
- elements comparing equal according to the <seealso
- marker="#ordering_function">ordering function</seealso>
+ elements comparing equal according to the <seeerl
+ marker="#ordering_function">ordering function</seeerl>
<c><anno>Fun</anno></c> have been deleted.
<c><anno>Fun</anno>(A, B)</c> is to return
<c>true</c> if <c>A</c> compares less than or equal to
diff --git a/lib/stdlib/doc/src/log_mf_h.xml b/lib/stdlib/doc/src/log_mf_h.xml
index b7c19bbdec..61b871227c 100644
--- a/lib/stdlib/doc/src/log_mf_h.xml
+++ b/lib/stdlib/doc/src/log_mf_h.xml
@@ -41,7 +41,7 @@
in any <c>gen_event</c> process. It logs onto disk all events that are
sent to an event manager. Each event is written as a binary, which makes
the logging very fast. However, a tool such as the Report Browser
- (<seealso marker="sasl:rb"><c>rb(3)</c></seealso>) must be used to read
+ (<seeerl marker="sasl:rb"><c>rb(3)</c></seeerl>) must be used to read
the files. The events are written to multiple files. When all files have
been used, the first one is reused and overwritten. The directory
location, the number of files, and the size of each file are configurable.
@@ -52,8 +52,8 @@
<datatypes>
<datatype>
<name name="args"/>
- <desc><p>Term to be sent to <seealso marker="gen_event#add_handler/3">
- <c>gen_event:add_handler/3</c></seealso>.</p>
+ <desc><p>Term to be sent to <seemfa marker="gen_event#add_handler/3">
+ <c>gen_event:add_handler/3</c></seemfa>.</p>
</desc>
</datatype>
</datatypes>
@@ -80,8 +80,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="gen_event"><c>gen_event(3)</c></seealso>,
- <seealso marker="sasl:rb"><c>rb(3)</c></seealso></p>
+ <p><seeerl marker="gen_event"><c>gen_event(3)</c></seeerl>,
+ <seeerl marker="sasl:rb"><c>rb(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml
index 8e88882b56..c445149f3b 100644
--- a/lib/stdlib/doc/src/maps.xml
+++ b/lib/stdlib/doc/src/maps.xml
@@ -39,11 +39,11 @@
<desc>
<p>An iterator representing the associations in a map with keys of type
<c><anno>Key</anno></c> and values of type <c><anno>Value</anno></c>.</p>
- <p>Created using <seealso marker="#iterator-1"><c>maps:iterator/1</c></seealso>.</p>
- <p>Consumed by <seealso marker="#next-1"><c>maps:next/1</c></seealso>,
- <seealso marker="#filter-2"><c>maps:filter/2</c></seealso>,
- <seealso marker="#fold-3"><c>maps:fold/3</c></seealso> and
- <seealso marker="#map-2"><c>maps:map/2</c></seealso>.</p>
+ <p>Created using <seemfa marker="#iterator/1"><c>maps:iterator/1</c></seemfa>.</p>
+ <p>Consumed by <seemfa marker="#next/1"><c>maps:next/1</c></seemfa>,
+ <seemfa marker="#filter/2"><c>maps:filter/2</c></seemfa>,
+ <seemfa marker="#fold/3"><c>maps:fold/3</c></seemfa> and
+ <seemfa marker="#map/2"><c>maps:map/2</c></seemfa>.</p>
</desc>
</datatype>
@@ -197,7 +197,7 @@ false</code>
<fsummary>Create a map iterator.</fsummary>
<desc>
<p>Returns a map iterator <c><anno>Iterator</anno></c> that can
- be used by <seealso marker="#next-1"><c>maps:next/1</c></seealso>
+ be used by <seemfa marker="#next/1"><c>maps:next/1</c></seemfa>
to traverse the key-value associations in a map. When iterating
over a map, the memory usage is guaranteed to be bounded no matter
the size of the map.</p>
diff --git a/lib/stdlib/doc/src/math.xml b/lib/stdlib/doc/src/math.xml
index b2f4a4eb12..59a05a4e5a 100644
--- a/lib/stdlib/doc/src/math.xml
+++ b/lib/stdlib/doc/src/math.xml
@@ -42,8 +42,8 @@
<note>
<p>Not all functions are provided on all platforms. In particular,
- the <seealso marker="#erf/1"><c>erf/1</c></seealso> and
- <seealso marker="#erfc/1"><c>erfc/1</c></seealso> functions
+ the <seemfa marker="#erf/1"><c>erf/1</c></seemfa> and
+ <seemfa marker="#erfc/1"><c>erfc/1</c></seemfa> functions
are not provided on Windows.</p>
</note>
</description>
diff --git a/lib/stdlib/doc/src/ms_transform.xml b/lib/stdlib/doc/src/ms_transform.xml
index 7e156e2110..bde9223eb0 100644
--- a/lib/stdlib/doc/src/ms_transform.xml
+++ b/lib/stdlib/doc/src/ms_transform.xml
@@ -38,19 +38,19 @@
<description>
<marker id="top"></marker>
<p>This module provides the parse transformation that makes calls to
- <seealso marker="ets"><c>ets</c></seealso> and
- <seealso marker="runtime_tools:dbg#fun2ms/1"><c>dbg:fun2ms/1</c></seealso>
+ <seeerl marker="ets"><c>ets</c></seeerl> and
+ <seemfa marker="runtime_tools:dbg#fun2ms/1"><c>dbg:fun2ms/1</c></seemfa>
translate into literal match specifications. It also provides the back end
for the same functions when called from the Erlang shell.</p>
<p>The translation from funs to match specifications
is accessed through the two "pseudo functions"
- <seealso marker="ets#fun2ms/1"><c>ets:fun2ms/1</c></seealso> and
- <seealso marker="runtime_tools:dbg#fun2ms/1"><c>dbg:fun2ms/1</c></seealso>.</p>
+ <seemfa marker="ets#fun2ms/1"><c>ets:fun2ms/1</c></seemfa> and
+ <seemfa marker="runtime_tools:dbg#fun2ms/1"><c>dbg:fun2ms/1</c></seemfa>.</p>
<p>As everyone trying to use
- <seealso marker="ets#select/1"><c>ets:select/2</c></seealso> or
- <seealso marker="runtime_tools:dbg"><c>dbg</c></seealso> seems to end up
+ <seemfa marker="ets#select/1"><c>ets:select/2</c></seemfa> or
+ <seeerl marker="runtime_tools:dbg"><c>dbg</c></seeerl> seems to end up
reading this manual page, this description is an introduction to the
concept of match specifications.</p>
@@ -59,7 +59,7 @@
<p>Match specifications are used more or less as filters. They resemble
usual Erlang matching in a list comprehension or in a fun used with
- <seealso marker="lists#foldl/3"><c>lists:foldl/3</c></seealso>, and so on.
+ <seemfa marker="lists#foldl/3"><c>lists:foldl/3</c></seemfa>, and so on.
However, the syntax of pure match specifications is awkward, as
they are made up purely by Erlang terms, and the language has no
syntax to make the match specifications more readable.</p>
@@ -77,11 +77,11 @@
<section>
<title>Example 1</title>
- <p>Using <seealso marker="ets#select/2"><c>ets:select/2</c></seealso>
+ <p>Using <seemfa marker="ets#select/2"><c>ets:select/2</c></seemfa>
and a match specification, one can filter out rows of
a table and construct a list of tuples containing relevant parts
of the data in these rows.
- One can use <seealso marker="ets#foldl/3"><c>ets:foldl/3</c></seealso>
+ One can use <seemfa marker="ets#foldl/3"><c>ets:foldl/3</c></seemfa>
instead, but the <c>ets:select/2</c> call is far more efficient.
Without the translation provided by <c>ms_transform</c>,
one must struggle with writing match specifications terms
@@ -127,8 +127,8 @@ ets:new(emp_tab, [{keypos,#emp.empno},named_table,ordered_set]).</code>
but it is still unreadable, and one has little control over the
returned result. It is always a list of lists.</p>
- <p><seealso marker="ets#foldl/3"><c>ets:foldl/3</c></seealso> or
- <seealso marker="ets#foldr/3"><c>ets:foldr/3</c></seealso> can be used to avoid the nested lists:</p>
+ <p><seemfa marker="ets#foldl/3"><c>ets:foldl/3</c></seemfa> or
+ <seemfa marker="ets#foldr/3"><c>ets:foldr/3</c></seemfa> can be used to avoid the nested lists:</p>
<code type="none">
ets:foldr(fun(#emp{empno = E, dept = sales},Acc) -> [E | Acc];
@@ -262,8 +262,8 @@ ets:select(emp_tab, ets:fun2ms(
mention that variable A is simply translated into '$_'.
Alternatively, pseudo function <c>object/0</c>
also returns the whole matched object, see section
- <seealso marker="#warnings_and_restrictions">
- Warnings and Restrictions</seealso>.</p>
+ <seeerl marker="#warnings_and_restrictions">
+ Warnings and Restrictions</seeerl>.</p>
</section>
<section>
@@ -339,12 +339,12 @@ ets:select(emp_tab, ets:fun2ms(
<section>
<title>Useful BIFs</title>
<p>What more can you do? A simple answer is: see the documentation of
- <seealso marker="erts:match_spec">match specifications</seealso>
+ <seeguide marker="erts:match_spec">match specifications</seeguide>
in ERTS User's Guide.
However, the following is a brief overview of the most useful "built-in
functions" that you can use when the fun is to be translated into a match
specification by
- <seealso marker="ets#fun2ms/1"> <c>ets:fun2ms/1</c></seealso>. It is not
+ <seemfa marker="ets#fun2ms/1"> <c>ets:fun2ms/1</c></seemfa>. It is not
possible to call other functions than those allowed in match
specifications. No "usual" Erlang code can be executed by the fun that
is translated by <c>ets:fun2ms/1</c>. The fun is limited
@@ -423,8 +423,8 @@ ets:select(emp_tab, ets:fun2ms(
<section>
<title>Example with dbg</title>
<p>This section describes the slightly different match specifications
- translated by <seealso marker="runtime_tools:dbg#fun2ms/1">
- <c>dbg:fun2ms/1</c></seealso>.</p>
+ translated by <seemfa marker="runtime_tools:dbg#fun2ms/1">
+ <c>dbg:fun2ms/1</c></seemfa>.</p>
<p>The same reasons for using the parse transformation apply to
<c>dbg</c>, maybe even more, as filtering using Erlang code is
@@ -574,7 +574,7 @@ start(Args) ->
<p>This example illustrates the most used calls in match specifications for
<c>dbg</c>. The other, more esoteric, calls are listed and explained in
- <seealso marker="erts:match_spec">Match specifications in Erlang</seealso>
+ <seeguide marker="erts:match_spec">Match specifications in Erlang</seeguide>
in ERTS User's Guide, as they are beyond
the scope of this description.</p>
</section>
@@ -725,7 +725,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code>
time, so runtime performance is not affected by using these pseudo
functions.</p>
<p>For more information about match specifications, see the
- <seealso marker="erts:match_spec">Match specifications in Erlang</seealso>
+ <seeguide marker="erts:match_spec">Match specifications in Erlang</seeguide>
in ERTS User's Guide.</p>
</section>
@@ -752,12 +752,12 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code>
transformation if and when header file <c>ms_transform.hrl</c>
is included in the source code.</p>
<p>For information about how to use this parse transformation, see
- <seealso marker="ets"><c>ets</c></seealso> and
- <seealso marker="runtime_tools:dbg#fun2ms/1">
- <c>dbg:fun2ms/1</c></seealso>.</p>
+ <seeerl marker="ets"><c>ets</c></seeerl> and
+ <seemfa marker="runtime_tools:dbg#fun2ms/1">
+ <c>dbg:fun2ms/1</c></seemfa>.</p>
<p>For a description of match specifications, see section
- <seealso marker="erts:match_spec">
- Match Specification in Erlang</seealso> in ERTS User's Guide.</p>
+ <seeguide marker="erts:match_spec">
+ Match Specification in Erlang</seeguide> in ERTS User's Guide.</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index f958bb796f..9c60fb860f 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,14 +31,406 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 3.14.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Handle maps in <c>erl_parse:tokens()</c>.</p>
+ <p>
+ Own Id: OTP-16978</p>
+ </item>
+ <item>
+ <p>
+ The erlang shell function <c>rr</c> has been fixed to be
+ able to read records from files within a code archive.</p>
+ <p>
+ Own Id: OTP-17182 Aux Id: PR-3002 </p>
+ </item>
+ <item>
+ <p>If <c>beam_lib</c> is asked to return abstract code
+ for a BEAM file produced by Elixir and Elixir is not
+ installed on the computer, <c>beam_lib</c> will no longer
+ crash, but will return an error tuple. The
+ <c>cover:compile_beam()</c> and
+ <c>cover:compile_beam_directory()</c> functions have been
+ updated to also return an error tuple in that
+ situation.</p>
+ <p>
+ Own Id: OTP-17194 Aux Id: GH-4353 </p>
+ </item>
+ <item>
+ <p>
+ Correct example module <c>erl_id_trans</c> regarding the
+ <c>{char, C}</c> type.</p>
+ <p>
+ Own Id: OTP-17273</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 3.14</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ This change fixes the handling of deep lists in the path
+ component when using uri_string:recompose/1.</p>
+ <p>
+ Own Id: OTP-16941</p>
+ </item>
+ <item>
+ <p>
+ Fix <seeerl
+ marker="shell_docs"><c>shell_docs</c></seeerl> to clear
+ shell decorations (bold/underline) when paginating
+ output.</p>
+ <p>
+ Fix various small renderings issues when integrating
+ <seeerl marker="shell_docs"><c>shell_docs</c></seeerl>
+ with edoc.</p>
+ <p>
+ Own Id: OTP-17047</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Improved the API and documentation of the uri_string
+ module.</p>
+ <p>
+ Added a new chapter to the Users Guide about Uniform
+ Resource Identifiers and their handling with the new API.</p>
+ <p>
+ Added two new API functions:
+ uri_string:allowed_characters/0 and
+ uri_string:percent_decode/1.</p>
+ <p>
+ This change has been marked as potentially incompatible
+ as uri_string:normalize/2 used to decode percent-encoded
+ character triplets that corresponded to characters not in
+ the reserved set. After this change,
+ uri_string:normalize/2 will only decode those
+ percent-encoded triplets that correspond to characters in
+ the unreserved set (ALPHA / DIGIT / "-" / "." / "_" /
+ "~").</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-16460</p>
+ </item>
+ <item>
+ <p>
+ The <c>shell_docs</c> module has been expanded with the
+ possibility to configure unicode, ansi and column size
+ for the rendered text.</p>
+ <p>
+ Own Id: OTP-16990</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 3.13.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The functions <c>digraph:in_edges/2</c> and
+ <c>digraph:out_edges/2</c> would return false edges if
+ called for a vertex that had a '_' atom in its name
+ term.</p>
+ <p>
+ Own Id: OTP-16655</p>
+ </item>
+ <item>
+ <p><c>filelib:wildcard("not-a-directory/..")</c> should
+ return an empty list. On Windows it returned
+ <c>"not-a-directory/.."</c>.</p>
+ <p>
+ Own Id: OTP-16700</p>
+ </item>
+ <item>
+ <p>
+ Fix the typespec of shell_docs:render to use the correct
+ type for an MFA.</p>
+ <p>
+ Own Id: OTP-16739</p>
+ </item>
+ <item>
+ <p>
+ Fix uri_string:recompose/1 when host is present but input
+ path is not absolute.</p>
+ <p>
+ This change prevents the recompose operation to change
+ the top level domain of the host when the path does not
+ start with a slash.</p>
+ <p>
+ Own Id: OTP-16751 Aux Id: ERL-1283 </p>
+ </item>
+ <item>
+ <p>The <c>epp</c> module would return a badly formed
+ error term when an '<c>if</c>' preprocessor directive
+ referenced an undefined symbol. <c>epp:format_error/1</c>
+ would crash when called with the bad error term.</p>
+ <p>
+ Own Id: OTP-16816 Aux Id: ERL-1310 </p>
+ </item>
+ <item>
+ <p>
+ <c>lists:sublist(List, Start, Len)</c> failed with an
+ exception if <c>Start &gt; length(List) + 1</c> even
+ though it is explicitly documented that "It is not an
+ error for <c>Start+Len</c> to exceed the length of the
+ list".</p>
+ <p>
+ Own Id: OTP-16830 Aux Id: ERL-1334, PR-2718 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 3.13.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>When a temporary child of a <c>simple_one_for_one
+ supervisor</c> died, the internal state of the supervisor
+ would be corrupted in a way that would cause the
+ supervisor to retain the start arguments for subsequent
+ children started by the supervisor, causing unnecessary
+ growth of the supervisor's heap. There state corruption
+ could potentially cause other problems as well.</p>
+ <p>
+ Own Id: OTP-16804</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 3.13</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Compiling a match specification with excessive nesting
+ caused the runtime system to crash due to scheduler stack
+ exhaustion. Instead of crashing the runtime system,
+ effected functions will now raise a <c>system_limit</c>
+ error exception in this situation.</p>
+ <p>
+ Own Id: OTP-16431 Aux Id: ERL-592 </p>
+ </item>
+ <item>
+ <p> Initialization of record fields using <c>_</c> is no
+ longer allowed if the number of affected fields is zero.
+ </p>
+ <p>
+ Own Id: OTP-16516</p>
+ </item>
+ <item>
+ <p> Fix bugs in <c>eval_bits</c>. </p>
+ <p>
+ Own Id: OTP-16545</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Improved the printout of single line logger events for
+ most of the OTP behaviours in STDLIB and Kernel. This
+ includes <c>proc_lib</c>, <c>gen_server</c>,
+ <c>gen_event</c>, <c>gen_statem</c>, <c>gen_fsm</c>,
+ <c>supervisor</c>, <c>supervisor_bridge</c> and
+ <c>application</c>.</p>
+ <p>
+ Improved the <seeerl
+ marker="kernel:logger_formatter#chars_limit"><c>chars_limit</c></seeerl>
+ and <seeerl
+ marker="kernel:logger_formatter#depth"><c>depth</c></seeerl>
+ handling in <c>proc_lib</c> and when formatting of
+ exceptions.</p>
+ <p>
+ Own Id: OTP-15299</p>
+ </item>
+ <item>
+ <p>
+ Remove usage and documentation of old requests of the
+ I/O-protocol.</p>
+ <p>
+ Own Id: OTP-15695</p>
+ </item>
+ <item>
+ <p>Improved ETS scalability of concurrent calls that
+ change the size of a table, like <c>ets:insert/2</c> and
+ <c>ets:delete/2</c>.</p> <p>This performance feature was
+ implemented for <c>ordered_set</c> in OTP 22.0 and does
+ now apply for all ETS table types.</p> <p>The improved
+ scalability may come at the cost of longer latency of
+ <c>ets:info(T,size)</c> and <c>ets:info(T,memory)</c>. A
+ new table option <c>decentralized_counters</c> has
+ therefore been added. It is default <c>true</c> for
+ <c>ordered_set</c> with <c>write_concurrency</c> enabled
+ and default <c>false</c> for all other table types.</p>
+ <p>
+ Own Id: OTP-15744 Aux Id: OTP-15623, PR-2229 </p>
+ </item>
+ <item>
+ <p> Handle Unicode filenames in the <c>zip</c> module.
+ </p>
+ <p>
+ Own Id: OTP-16005 Aux Id: ERL-1003, ERL-1150 </p>
+ </item>
+ <item>
+ <p>
+ Unicode support was updated to the Unicode 12.1 standard.</p>
+ <p>
+ Own Id: OTP-16073 Aux Id: PR-2339 </p>
+ </item>
+ <item>
+ <p>
+ All of the modules <seemfa
+ marker="stdlib:proc_lib#start_monitor/3"><c>proc_lib</c></seemfa>,
+ <seemfa
+ marker="stdlib:gen_server#start_monitor/3"><c>gen_server</c></seemfa>,
+ <seemfa
+ marker="stdlib:gen_statem#start_monitor/3"><c>gen_statem</c></seemfa>,
+ and <seemfa
+ marker="stdlib:gen_event#start_monitor/0"><c>gen_event</c></seemfa>
+ have been extended with a <c>start_monitor()</c>
+ function. For more information, see the documentation of
+ <c>start_monitor()</c> for these modules.</p>
+ <p>
+ Own Id: OTP-16120 Aux Id: ERIERL-402, PR-2427 </p>
+ </item>
+ <item>
+ <p>
+ Updates for new <c>erlang:term_to_iovec()</c> BIF.</p>
+ <p>
+ Own Id: OTP-16128 Aux Id: OTP-15618 </p>
+ </item>
+ <item>
+ <p>Documented a quirk regarding extraction from file
+ descriptors in <c>erl_tar</c>.</p>
+ <p>
+ Own Id: OTP-16171 Aux Id: ERL-1057 </p>
+ </item>
+ <item>
+ <p>
+ Added <c>ok</c> as return value to
+ <c>gen_server:reply/2</c></p>
+ <p>
+ Own Id: OTP-16210 Aux Id: PR-2411 </p>
+ </item>
+ <item>
+ <p>New functions have been added to <seeerl
+ marker="c"><c>c(3)</c></seeerl> for printing embedded
+ documentation for Erlang modules. The functions are:</p>
+ <taglist> <tag>h/1,2,3</tag> <item>Print the
+ documentation for a Module:Function/Arity.</item>
+ <tag>ht/1,2,3</tag> <item>Print the type documentation
+ for a Module:Type/Arity.</item> </taglist> <p>The
+ embedded documentation is created when building the
+ Erlang/OTP documentation.</p>
+ <p>
+ Own Id: OTP-16222</p>
+ </item>
+ <item>
+ <p> Add <c>indent</c> and <c>linewidth</c> to the options
+ of the <c>erl_pp</c> module's functions. </p>
+ <p>
+ Own Id: OTP-16276 Aux Id: PR-2443 </p>
+ </item>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ <item>
+ <p>The compiler will now raise a warning when inlining is
+ used in modules that load NIFs.</p>
+ <p>
+ Own Id: OTP-16429 Aux Id: ERL-303 </p>
+ </item>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ <item>
+ <p> Extend <c>erl_parse:abstract/1,2</c> to handle
+ external fun expressions (<c>fun M:F/A</c>). </p>
+ <p>
+ Own Id: OTP-16480</p>
+ </item>
+ <item>
+ <p>Added <c>filelib:safe_relative_path/2</c> to replace
+ <c>filename:safe_relative_path/1</c>, which did not
+ safely handle symbolic links.</p>
+ <p><c>filename:safe_relative_path/1</c> has been
+ deprecated.</p>
+ <p>
+ Own Id: OTP-16483 Aux Id: PR-2542 </p>
+ </item>
+ <item>
+ <p>
+ The module <c>shell_docs</c> has been added. The module
+ contains functions for rendering, validating and
+ normalizing embedded documentation.</p>
+ <p>
+ Own Id: OTP-16500</p>
+ </item>
+ <item>
+ <p>
+ Module and function auto-completion in the shell now
+ looks at all available modules instead of only those
+ loaded. A module is considered available if it either is
+ loaded already or would be loaded if called.</p>
+ <p>
+ The auto-completion has also been expanded to work in the
+ new <c>h/1,2,3</c> function in <c>c(3)</c>.</p>
+ <p>
+ Own Id: OTP-16501 Aux Id: OTP-16494, OTP-16222,
+ OTP-16406, OTP-16499, OTP-16500, PR-2545, ERL-708 </p>
+ </item>
+ <item>
+ <p>Updated the internal <c>pcre</c> library to
+ <c>8.44</c>.</p>
+ <p>
+ Own Id: OTP-16557</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.12.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
<item>
<p>
- <seealso marker="stdlib:re#run/3">re:run(Subject, RE,
- [unicode])</seealso> returned <c>nomatch</c> instead of
+ <seemfa marker="stdlib:re#run/3">re:run(Subject, RE,
+ [unicode])</seemfa> returned <c>nomatch</c> instead of
failing with a <c>badarg</c> error exception when
<c>Subject</c> contained illegal utf8 and <c>RE</c> was
passed as a binary. This has been corrected along with
@@ -161,10 +553,10 @@
<list>
<item>
<p>
- The functions <seealso
- marker="stdlib:unicode#characters_to_list/2"><c>unicode:characters_to_list()</c></seealso>
- and <seealso
- marker="stdlib:unicode#characters_to_binary/3"><c>unicode:characters_to_binary()</c></seealso>
+ The functions <seemfa
+ marker="stdlib:unicode#characters_to_list/2"><c>unicode:characters_to_list()</c></seemfa>
+ and <seemfa
+ marker="stdlib:unicode#characters_to_binary/3"><c>unicode:characters_to_binary()</c></seemfa>
raised a <c>badarg</c> exception instead of returning an
error tuple when passed very large invalid code points as
input.</p>
@@ -186,8 +578,8 @@
</item>
<item>
<p>
- Fixed erroneous type spec for <seealso
- marker="stdlib:binary#list_to_bin/1"><c>binary:list_to_bin/1</c></seealso>.
+ Fixed erroneous type spec for <seemfa
+ marker="stdlib:binary#list_to_bin/1"><c>binary:list_to_bin/1</c></seemfa>.
Argument type was changed from <c>iodata()</c> to
<c>iolist()</c>.</p>
<p>
@@ -253,8 +645,8 @@
to version 8.43. See <url
href="http://pcre.org/original/changelog.txt">http://pcre.org/original/changelog.txt</url>
for information about changes made to PCRE. This library
- implements major parts of the <seealso
- marker="stdlib:re"><c>re</c></seealso> regular
+ implements major parts of the <seeerl
+ marker="stdlib:re"><c>re</c></seeerl> regular
expressions module.</p>
<p>
Own Id: OTP-15889</p>
@@ -653,6 +1045,27 @@
</section>
+<section><title>STDLIB 3.8.2.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ <seemfa marker="stdlib:re#run/3">re:run(Subject, RE,
+ [unicode])</seemfa> returned <c>nomatch</c> instead of
+ failing with a <c>badarg</c> error exception when
+ <c>Subject</c> contained illegal utf8 and <c>RE</c> was
+ passed as a binary. This has been corrected along with
+ corrections of reduction counting in <c>re:run()</c>
+ error cases.</p>
+ <p>
+ Own Id: OTP-16553</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.8.2.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -965,18 +1378,18 @@
Own Id: OTP-14019 Aux Id: ERL-550 </p>
</item>
<item>
- <p> File operations used to accept <seealso
- marker="kernel:file#type-name_all">filenames</seealso>
+ <p> File operations used to accept <seetype
+ marker="kernel:file#name_all">filenames</seetype>
containing null characters (integer value zero). This
caused the name to be truncated and in some cases
arguments to primitive operations to be mixed up.
Filenames containing null characters inside the filename
are now <em>rejected</em> and will cause primitive file
operations to fail. </p> <p> Also environment variable
- operations used to accept <seealso
- marker="kernel:os#type-env_var_name">names</seealso> and
- <seealso
- marker="kernel:os#type-env_var_value">values</seealso> of
+ operations used to accept <seetype
+ marker="kernel:os#env_var_name">names</seetype> and
+ <seetype
+ marker="kernel:os#env_var_value">values</seetype> of
environment variables containing null characters (integer
value zero). This caused operations to silently produce
erroneous results. Environment variable names and values
@@ -987,12 +1400,12 @@
character in environment variable names causing various
problems. <c>$=</c> characters in environment variable
names are now also <em>rejected</em>. </p> <p>Also
- <seealso
- marker="kernel:os#cmd/1"><c>os:cmd/1</c></seealso> now
- reject null characters inside its <seealso
- marker="kernel:os#type-os_command">command</seealso>.
- </p> <p><seealso
- marker="erts:erlang#open_port/2"><c>erlang:open_port/2</c></seealso>
+ <seemfa
+ marker="kernel:os#cmd/1"><c>os:cmd/1</c></seemfa> now
+ reject null characters inside its <seetype
+ marker="kernel:os#os_command">command</seetype>.
+ </p> <p><seemfa
+ marker="erts:erlang#open_port/2"><c>erlang:open_port/2</c></seemfa>
will also reject null characters inside the port name
from now on.</p>
<p>
@@ -1073,10 +1486,10 @@
</item>
<item>
<p>A new logging API is added to Erlang/OTP, see the
- <seealso
- marker="kernel:logger"><c>logger(3)</c></seealso> manual
- page, and section <seealso
- marker="kernel:logger_chapter">Logging</seealso> in the
+ <seeerl
+ marker="kernel:logger"><c>logger(3)</c></seeerl> manual
+ page, and section <seeguide
+ marker="kernel:logger_chapter">Logging</seeguide> in the
Kernel User's Guide.</p>
<p>Calls to <c>error_logger</c> are automatically
redirected to the new API, and legacy error logger event
@@ -1662,9 +2075,9 @@
Own Id: OTP-13830</p>
</item>
<item>
- <p>Replaced usage of deprecated symbolic <seealso
- marker="erts:erlang#type-time_unit"><c>time
- unit</c></seealso> representations.</p>
+ <p>Replaced usage of deprecated symbolic <seetype
+ marker="erts:erlang#time_unit"><c>time
+ unit</c></seetype> representations.</p>
<p>
Own Id: OTP-13831 Aux Id: OTP-13735 </p>
</item>
@@ -1909,7 +2322,7 @@
<p>
Upgraded the OTP internal PCRE library from version 8.33
to version 8.40. This library is used for implementation
- of the <seealso marker="stdlib:re"><c>re</c></seealso>
+ of the <seeerl marker="stdlib:re"><c>re</c></seeerl>
regular expressions module.</p>
<p>
Besides various bug fixes, the new version allows for
@@ -1917,10 +2330,10 @@
feature, the stack size of normal scheduler threads is
now by default set to 128 kilo words on all platforms.
The stack size of normal scheduler threads can be set
- upon system start by passing the <seealso
- marker="erts:erl#sched_thread_stack_size"><c>+sss</c></seealso>
- command line argument to the <seealso
- marker="erts:erl"><c>erl</c></seealso> command.</p>
+ upon system start by passing the <seecom
+ marker="erts:erl#sched_thread_stack_size"><c>+sss</c></seecom>
+ command line argument to the <seecom
+ marker="erts:erl"><c>erl</c></seecom> command.</p>
<p>
See <url
href="http://pcre.org/original/changelog.txt">http://pcre.org/original/changelog.txt</url>
@@ -2892,10 +3305,10 @@
Earlier, supervisor flags and child specs were given as
tuples. While this is kept for backwards compatibility,
it is now also allowed to give these parameters as maps,
- see <seealso
- marker="stdlib:supervisor#sup_flags">sup_flags</seealso>
- and <seealso
- marker="stdlib:supervisor#child_spec">child_spec</seealso>.</p>
+ see <seeerl
+ marker="stdlib:supervisor#sup_flags">sup_flags</seeerl>
+ and <seeerl
+ marker="stdlib:supervisor#child_spec">child_spec</seeerl>.</p>
<p>
Own Id: OTP-11043</p>
</item>
@@ -3648,10 +4061,10 @@
and yield without having to set up the heap in a state
that can be garbage collected.</p>
<p>
- The <seealso
- marker="erts:erlang#garbage_collect/2"><c>garbage_collect/2</c></seealso>,
- and <seealso
- marker="erts:erlang#check_process_code/3"><c>check_process_code/3</c></seealso>
+ The <seemfa
+ marker="erts:erlang#garbage_collect/2"><c>garbage_collect/2</c></seemfa>,
+ and <seemfa
+ marker="erts:erlang#check_process_code/3"><c>check_process_code/3</c></seemfa>
BIFs have been introduced. Both taking an option list as
last argument. Using these, one can issue asynchronous
requests.</p>
@@ -3706,8 +4119,8 @@
</taglist>
<p>
For information on how to use Maps please see Map Expressions in the
- <seealso marker="doc/reference_manual:expressions#map_expressions">
- Reference Manual</seealso>.</p>
+ <seeguide marker="system/reference_manual:expressions#map_expressions">
+ Reference Manual</seeguide>.</p>
<p>
The current implementation is without the following
features:</p>
@@ -5508,15 +5921,15 @@
lines. The reader optimized rwlock implementation is used
by miscellaneous rwlocks in the runtime system that are
known to be read-locked frequently, and can be enabled on
- ETS tables by passing the <seealso
+ ETS tables by passing the <seeerl
marker="stdlib:ets#new_2_read_concurrency">{read_concurrency,
- true}</seealso> option upon table creation. See the
- documentation of <seealso
- marker="stdlib:ets#new/2">ets:new/2</seealso> for more
+ true}</seeerl> option upon table creation. See the
+ documentation of <seemfa
+ marker="stdlib:ets#new/2">ets:new/2</seemfa> for more
information. The reader optimized rwlock implementation
can be fine tuned when starting the runtime system. For
- more information, see the documentation of the <seealso
- marker="erts:erl#+rg">+rg</seealso> command line argument
+ more information, see the documentation of the <seecom
+ marker="erts:erl#+rg">+rg</seecom> command line argument
of <c>erl</c>.</p>
<p>
There is also a new implementation of rwlocks that is not
@@ -5558,8 +5971,8 @@
utilize optimized native atomic operations on more
platforms than before. If <c>configure</c> warns about no
atomic implementation available, try using the
- <c>libatomic_ops</c> library. Use the <seealso
- marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--with-libatomic_ops=PATH</seealso>
+ <c>libatomic_ops</c> library. Use the <seeguide
+ marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--with-libatomic_ops=PATH</seeguide>
<c>configure</c> command line argument when specifying
where the <c>libatomic_ops</c> installation is located.
The <c>libatomic_ops</c> library can be downloaded from:
@@ -5576,8 +5989,8 @@
library will now use instructions that first appeared on
the pentium 4 processor. If you want the runtime system
to be compatible with older processors (back to 486) you
- need to pass the <seealso
- marker="doc/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--enable-ethread-pre-pentium4-compatibility</seealso>
+ need to pass the <seeguide
+ marker="system/installation_guide:INSTALL#Advanced-configuration-and-build-of-ErlangOTP">--enable-ethread-pre-pentium4-compatibility</seeguide>
<c>configure</c> command line argument when configuring
the system.</p>
<p>
@@ -6100,10 +6513,10 @@
regarding <c>gen_server</c>, <c>gen_fsm</c>, and
<c>gen_event</c> behavior when handling <c>'EXIT'</c>
messages from the parent process. For more information
- see the <seealso
- marker="gen_server">gen_server(3)</seealso>, <seealso
- marker="gen_fsm">gen_fsm(3)</seealso>, and <seealso
- marker="gen_event">gen_event(3)</seealso> documentation.</p>
+ see the <seeerl
+ marker="gen_server">gen_server(3)</seeerl>, <seeerl
+ marker="gen_fsm">gen_fsm(3)</seeerl>, and <seeerl
+ marker="gen_event">gen_event(3)</seeerl> documentation.</p>
<p>
Own Id: OTP-8255 Aux Id: seq11419 </p>
</item>
diff --git a/lib/stdlib/doc/src/orddict.xml b/lib/stdlib/doc/src/orddict.xml
index d5ceca9e0c..796cb42ede 100644
--- a/lib/stdlib/doc/src/orddict.xml
+++ b/lib/stdlib/doc/src/orddict.xml
@@ -41,7 +41,7 @@
ordered after the keys in the <em>Erlang term order</em>.</p>
<p>This module provides the same interface as the
- <seealso marker="dict"><c>dict(3)</c></seealso> module
+ <seeerl marker="dict"><c>dict(3)</c></seeerl> module
but with a defined representation. One difference is
that while <c>dict</c> considers two keys as different if they
do not match (<c>=:=</c>), this module considers two keys as
@@ -52,7 +52,7 @@
<datatype>
<name name="orddict" n_vars="2"/>
<desc><p>Dictionary as returned by
- <seealso marker="#new/0"><c>new/0</c></seealso>.</p></desc>
+ <seemfa marker="#new/0"><c>new/0</c></seemfa>.</p></desc>
</datatype>
<datatype>
<name name="orddict" n_vars="0"/>
@@ -68,7 +68,7 @@
of values associated with <c><anno>Key</anno></c>. An exception is
generated if the initial value associated with <c><anno>Key</anno></c>
is not a list of values.</p>
- <p>See also section <seealso marker="#notes">Notes</seealso>.</p>
+ <p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
</desc>
</func>
@@ -80,7 +80,7 @@
the current list of values associated with <c><anno>Key</anno></c>.
An exception is generated if the initial value associated with
<c><anno>Key</anno></c> is not a list of values.</p>
- <p>See also section <seealso marker="#notes">Notes</seealso>.</p>
+ <p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
</desc>
</func>
@@ -100,7 +100,7 @@
in dictionary <c><anno>Orddict</anno></c>. This function assumes that
the <c><anno>Key</anno></c> is present in the dictionary. An exception
is generated if <c><anno>Key</anno></c> is not in the dictionary.</p>
- <p>See also section <seealso marker="#notes">Notes</seealso>.</p>
+ <p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
</desc>
</func>
@@ -140,7 +140,7 @@
<c>{ok, <anno>Value</anno>}</c>, where <c><anno>Value</anno></c> is
the value associated with <c><anno>Key</anno></c>, or <c>error</c> if
the key is not present in the dictionary.</p>
- <p>See also section <seealso marker="#notes">Notes</seealso>.</p>
+ <p>See also section <seeerl marker="#notes">Notes</seeerl>.</p>
</desc>
</func>
@@ -312,8 +312,8 @@ update_counter(Key, Incr, D) ->
<section>
<title>See Also</title>
- <p><seealso marker="dict"><c>dict(3)</c></seealso>,
- <seealso marker="gb_trees"><c>gb_trees(3)</c></seealso></p>
+ <p><seeerl marker="dict"><c>dict(3)</c></seeerl>,
+ <seeerl marker="gb_trees"><c>gb_trees(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/ordsets.xml b/lib/stdlib/doc/src/ordsets.xml
index fbe334c009..1b16f45a7d 100644
--- a/lib/stdlib/doc/src/ordsets.xml
+++ b/lib/stdlib/doc/src/ordsets.xml
@@ -43,7 +43,7 @@
according to the <em>Erlang term order</em>.</p>
<p>This module provides the same interface as the
- <seealso marker="sets"><c>sets(3)</c></seealso> module
+ <seeerl marker="sets"><c>sets(3)</c></seeerl> module
but with a defined representation. One difference is
that while <c>sets</c> considers two elements as different if they
do not match (<c>=:=</c>), this module considers two elements as
@@ -54,7 +54,7 @@
<datatype>
<name name="ordset" n_vars="1"/>
<desc><p>As returned by
- <seealso marker="#new/0"><c>new/0</c></seealso>.</p></desc>
+ <seemfa marker="#new/0"><c>new/0</c></seemfa>.</p></desc>
</datatype>
</datatypes>
@@ -222,8 +222,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="gb_sets"><c>gb_sets(3)</c></seealso>,
- <seealso marker="sets"><c>sets(3)</c></seealso></p>
+ <p><seeerl marker="gb_sets"><c>gb_sets(3)</c></seeerl>,
+ <seeerl marker="sets"><c>sets(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/part.xml b/lib/stdlib/doc/src/part.xml
index 93c47405bf..b6a2f16b57 100644
--- a/lib/stdlib/doc/src/part.xml
+++ b/lib/stdlib/doc/src/part.xml
@@ -37,5 +37,6 @@
<xi:include href="introduction.xml"/>
<xi:include href="io_protocol.xml"/>
<xi:include href="unicode_usage.xml"/>
+ <xi:include href="uri_string_usage.xml"/>
</part>
diff --git a/lib/stdlib/doc/src/pool.xml b/lib/stdlib/doc/src/pool.xml
index e17aff1b5b..ac67df2a2f 100644
--- a/lib/stdlib/doc/src/pool.xml
+++ b/lib/stdlib/doc/src/pool.xml
@@ -47,7 +47,7 @@
processes in the Erlang runtime system.</p>
<p>The slave nodes are started with the
- <seealso marker="slave"><c>slave(3)</c></seealso> module. This
+ <seeerl marker="slave"><c>slave(3)</c></seeerl> module. This
effects terminal I/O, file I/O, and code loading.</p>
<p>If the master node fails, the entire pool exits.</p>
</description>
@@ -106,10 +106,10 @@
<desc>
<p>Starts a new pool. The file <c>.hosts.erlang</c> is read to
find host names where the pool nodes can be started; see
- section <seealso marker="#files">Files</seealso>. The
+ section <seeerl marker="#files">Files</seeerl>. The
startup procedure fails if the file is not found.</p>
<p>The slave nodes are started with
- <seealso marker="slave#start/2"><c>slave:start/2,3</c></seealso>,
+ <seemfa marker="slave#start/2"><c>slave:start/2,3</c></seemfa>,
passing along <c><anno>Name</anno></c> and, if provided,
<c><anno>Args</anno></c>. <c><anno>Name</anno></c> is used as the
first part of the node names, <c><anno>Args</anno></c> is used to
@@ -135,8 +135,8 @@
<title>Files</title>
<p><c>.hosts.erlang</c> is used to pick hosts where nodes can
be started. For information about format and location of this file, see
- <seealso marker="kernel:net_adm#host_file/0">
- <c>net_adm:host_file/0</c></seealso>.</p>
+ <seemfa marker="kernel:net_adm#host_file/0">
+ <c>net_adm:host_file/0</c></seemfa>.</p>
<p><c>$HOME/.erlang.slave.out.HOST</c> is used for all extra I/O
that can come from the slave nodes on standard I/O. If the startup
procedure does not work, this file can indicate the reason.</p>
diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml
index 2c83005a56..aa649a280a 100644
--- a/lib/stdlib/doc/src/proc_lib.xml
+++ b/lib/stdlib/doc/src/proc_lib.xml
@@ -33,15 +33,15 @@
adhering to the OTP design principles.</modulesummary>
<description>
<p>This module is used to start processes adhering to
- the <seealso marker="doc/design_principles:des_princ">
- OTP Design Principles</seealso>. Specifically, the functions in this
+ the <seeguide marker="system/design_principles:des_princ">
+ OTP Design Principles</seeguide>. Specifically, the functions in this
module are used by the OTP standard behaviors (for example,
<c>gen_server</c> and <c>gen_statem</c>)
when starting new processes. The functions
can also be used to start <em>special processes</em>, user-defined
processes that comply to the OTP design principles. For an example,
- see section <seealso marker="doc/design_principles:spec_proc">
- sys and proc_lib</seealso> in OTP Design Principles.</p>
+ see section <seeguide marker="system/design_principles:spec_proc">
+ sys and proc_lib</seeguide> in OTP Design Principles.</p>
<p>Some useful information is initialized when a process starts.
@@ -62,7 +62,7 @@
is generated, which is written to terminal by the default logger
handler setup by Kernel. For more information about how crash reports
were logged prior to Erlang/OTP 21.0, see
- <seealso marker="sasl:error_logging">SASL Error Logging</seealso>
+ <seeguide marker="sasl:error_logging">SASL Error Logging</seeguide>
in the SASL User's Guide.</p>
<p>Unlike in "plain Erlang", <c>proc_lib</c> processes will not generate
@@ -81,18 +81,16 @@
<datatype>
<name name="spawn_option"/>
<desc>
- <p>See <seealso marker="erts:erlang#spawn_opt/4">
- <c>erlang:spawn_opt/2,3,4,5</c></seealso>.</p>
+ <p>See <seemfa marker="erts:erlang#spawn_opt/4">
+ <c>erlang:spawn_opt/2,3,4,5</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
- <name name="priority_level"/>
- </datatype>
- <datatype>
- <name name="max_heap_size"/>
+ <name name="start_spawn_option"/>
<desc>
- <p>See <seealso marker="erts:erlang#process_flag_max_heap_size">
- erlang:process_flag(max_heap_size, MaxHeapSize)</seealso>.</p>
+ <p>A restricted set of <seetype marker="#spawn_option">spawn
+ options</seetype>. Most notably <c>monitor</c> is <em>not</em> part
+ of these options.</p>
</desc>
</datatype>
<datatype>
@@ -105,8 +103,8 @@
<name name="format" arity="1" since=""/>
<fsummary>Format a crash report.</fsummary>
<desc>
- <p>Equivalent to <seealso marker="#format/2">
- <c>format(<anno>CrashReport</anno>, latin1)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#format/2">
+ <c>format(<anno>CrashReport</anno>, latin1)</c></seemfa>.</p>
</desc>
</func>
@@ -118,8 +116,8 @@
<p>This function is deprecated in the sense that
the <c>error_logger</c> is no longer the preferred
interface for logging in Erlang/OTP. A
- new <seealso marker="kernel:logger_chapter">logging
- API</seealso> was added in Erlang/OTP 21.0, but
+ new <seeguide marker="kernel:logger_chapter">logging
+ API</seeguide> was added in Erlang/OTP 21.0, but
legacy <c>error_logger</c> handlers can still be used. New
Logger handlers do not need to use this function, since
the formatting callback (<c>report_cb</c>) is included as
@@ -128,8 +126,8 @@
<p>This function can be used by a user-defined legacy
<c>error_logger</c> event handler to
format a crash report. The crash report is sent using
- <seealso marker="kernel:logger">
- <c>logger(3)</c></seealso>, and the event to be handled is of the format
+ <seeerl marker="kernel:logger">
+ <c>logger(3)</c></seeerl>, and the event to be handled is of the format
<c>{error_report, GL, {Pid, crash_report,
<anno>CrashReport</anno>}}</c>,
where <c>GL</c> is the group leader pid of process
@@ -145,8 +143,8 @@
<p>This function is deprecated in the sense that
the <c>error_logger</c> is no longer the preferred
interface for logging in Erlang/OTP. A
- new <seealso marker="kernel:logger_chapter">logging
- API</seealso> was added in Erlang/OTP 21.0, but
+ new <seeguide marker="kernel:logger_chapter">logging
+ API</seeguide> was added in Erlang/OTP 21.0, but
legacy <c>error_logger</c> handlers can still be used. New
Logger handlers do not need to used this function, since
the formatting callback (<c>report_cb</c>) is included as
@@ -166,8 +164,8 @@
<fsummary>Hibernate a process until a message is sent to it.</fsummary>
<desc>
<p>This function does the same as (and does call) the
- <seealso marker="erts:erlang#hibernate/3">
- <c>hibernate/3</c></seealso> BIF,
+ <seemfa marker="erts:erlang#hibernate/3">
+ <c>hibernate/3</c></seemfa> BIF,
but ensures that exception handling and logging continues to
work as expected when the process wakes up.</p>
<p>Always use this function instead of the BIF for processes started
@@ -181,7 +179,7 @@
<fsummary>Used by a process when it has started.</fsummary>
<desc>
<p>This function must be used by a process that has been started by
- a <seealso marker="#start/3"><c>start[_link]/3,4,5</c></seealso>
+ a <seemfa marker="#start/3"><c>start[_link]/3,4,5</c></seemfa>
function. It tells <c><anno>Parent</anno></c> that the process has
initialized itself, has started, or has failed to initialize
itself.</p>
@@ -257,7 +255,7 @@ init(Parent) ->
<desc>
<p>Spawns a new process and initializes it as described in the
beginning of this manual page. The process is spawned using the
- <seealso marker="erts:erlang#spawn/1"><c>spawn</c></seealso> BIFs.</p>
+ <seemfa marker="erts:erlang#spawn/1"><c>spawn</c></seemfa> BIFs.</p>
</desc>
</func>
@@ -275,7 +273,7 @@ init(Parent) ->
<desc>
<p>Spawns a new process and initializes it as described in the
beginning of this manual page. The process is spawned using the
- <seealso marker="erts:erlang#spawn_link/1"><c>spawn_link</c></seealso>
+ <seemfa marker="erts:erlang#spawn_link/1"><c>spawn_link</c></seemfa>
BIFs.</p>
</desc>
</func>
@@ -295,8 +293,31 @@ init(Parent) ->
<desc>
<p>Spawns a new process and initializes it as described in the
beginning of this manual page. The process is spawned using the
- <seealso marker="erts:erlang#spawn_opt/2"><c>spawn_opt</c></seealso>
+ <seemfa marker="erts:erlang#spawn_opt/2"><c>erlang:spawn_opt</c></seemfa>
BIFs.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="start" arity="3" since=""/>
+ <name name="start" arity="4" since=""/>
+ <name name="start" arity="5" since=""/>
+ <fsummary>Start a new process synchronously.</fsummary>
+ <desc>
+ <p>Starts a new process synchronously. Spawns the process and
+ waits for it to start. When the process has started, it
+ <em>must</em> call
+ <seemfa marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seemfa>
+ or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>,
+ where <c>Parent</c> is the process that evaluates this
+ function. At this time, <c>Ret</c> is returned.</p>
+ <p>If <c><anno>Time</anno></c> is specified as an integer, this
+ function waits for <c><anno>Time</anno></c> milliseconds for the
+ new process to call <c>init_ack</c>, or <c>Ret = {error, timeout}</c>
+ will be returned, and the process is killed.</p>
+ <p>Argument <c><anno>SpawnOpts</anno></c>, if specified, is passed
+ as the last argument to the <seemfa marker="erts:erlang#spawn_opt/2">
+ <c>spawn_opt/2,3,4,5</c></seemfa> BIF.</p>
<note>
<p>Using spawn option <c>monitor</c> is not
allowed. It causes the function to fail with reason
@@ -306,32 +327,71 @@ init(Parent) ->
</func>
<func>
- <name name="start" arity="3" since=""/>
- <name name="start" arity="4" since=""/>
- <name name="start" arity="5" since=""/>
<name name="start_link" arity="3" since=""/>
<name name="start_link" arity="4" since=""/>
<name name="start_link" arity="5" since=""/>
<fsummary>Start a new process synchronously.</fsummary>
<desc>
- <p>Starts a new process synchronously. Spawns the process and
- waits for it to start. When the process has started, it
+ <p>
+ Starts a new process synchronously. Spawns the process and
+ waits for it to start. A link is atomically set on the
+ newly spawned process. When the process has started, it
+ <em>must</em> call
+ <seemfa marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seemfa>
+ or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>,
+ where <c>Parent</c> is the process that evaluates this
+ function. At this time, <c>Ret</c> is returned.</p>
+ <p>If <c><anno>Time</anno></c> is specified as an integer, this
+ function waits for <c><anno>Time</anno></c> milliseconds for the
+ new process to call <c>init_ack</c>, or <c>Ret = {error, timeout}</c>
+ will be returned, and the process is killed.</p>
+ <p>If the process crashes before it has called <c>init_ack/1,2</c>,
+ <c>Ret = {error, <anno>Reason</anno>}</c> will be returned if
+ the calling process traps exits.</p>
+ <p>Argument <c><anno>SpawnOpts</anno></c>, if specified, is passed
+ as the last argument to the <seemfa marker="erts:erlang#spawn_opt/2">
+ <c>spawn_opt/2,3,4,5</c></seemfa> BIF.</p>
+ <note>
+ <p>Using spawn option <c>monitor</c> is not
+ allowed. It causes the function to fail with reason
+ <c>badarg</c>.</p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="start_monitor" arity="3" since="OTP 23.0"/>
+ <name name="start_monitor" arity="4" since="OTP 23.0"/>
+ <name name="start_monitor" arity="5" since="OTP 23.0"/>
+ <fsummary>Start a new process synchronously.</fsummary>
+ <desc>
+ <p>
+ Starts a new process synchronously. Spawns the process and
+ waits for it to start. A monitor is atomically set on the
+ newly spawned process. When the process has started, it
<em>must</em> call
- <seealso marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seealso>
- or <seealso marker="#init_ack/1"><c>init_ack(Ret)</c></seealso>,
+ <seemfa marker="#init_ack/2"><c>init_ack(Parent, Ret)</c></seemfa>
+ or <seemfa marker="#init_ack/1"><c>init_ack(Ret)</c></seemfa>,
where <c>Parent</c> is the process that evaluates this
function. At this time, <c>Ret</c> is returned.</p>
- <p>If function <c>start_link/3,4,5</c> is used and
- the process crashes before it has called <c>init_ack/1,2</c>,
- <c>{error, <anno>Reason</anno>}</c> is returned if the calling
- process traps exits.</p>
<p>If <c><anno>Time</anno></c> is specified as an integer, this
function waits for <c><anno>Time</anno></c> milliseconds for the
- new process to call <c>init_ack</c>, or <c>{error, timeout}</c> is
- returned, and the process is killed.</p>
+ new process to call <c>init_ack</c>, or <c>Ret = {error, timeout}</c>
+ will be returned, and the process is killed.</p>
+ <p>
+ The return value is <c>{Ret, Mon}</c> where <c>Ret</c> corresponds
+ to the <c>Ret</c> argument in the call to <c>init_ack()</c>, and
+ <c>Mon</c> is the monitor reference of the monitor that has been
+ set up.
+ </p>
+ <p>
+ A <c>'DOWN'</c> message will be delivered to the caller if
+ this function returns, and the spawned process terminates. This is
+ true also in the case when the operation times out.
+ </p>
<p>Argument <c><anno>SpawnOpts</anno></c>, if specified, is passed
- as the last argument to the <seealso marker="erts:erlang#spawn_opt/2">
- <c>spawn_opt/2,3,4,5</c></seealso> BIF.</p>
+ as the last argument to the <seemfa marker="erts:erlang#spawn_opt/2">
+ <c>spawn_opt/2,3,4,5</c></seemfa> BIF.</p>
<note>
<p>Using spawn option <c>monitor</c> is not
allowed. It causes the function to fail with reason
@@ -345,8 +405,8 @@ init(Parent) ->
<fsummary>Terminate a process synchronously.</fsummary>
<type variable="Process"/>
<desc>
- <p>Equivalent to <seealso marker="#stop/3">
- <c>stop(Process, normal, infinity)</c></seealso>.</p>
+ <p>Equivalent to <seemfa marker="#stop/3">
+ <c>stop(Process, normal, infinity)</c></seemfa>.</p>
</desc>
</func>
@@ -368,9 +428,9 @@ init(Parent) ->
<c>terminate</c> system message, and requires that the
process handles system messages correctly.
For information about system messages, see
- <seealso marker="sys"><c>sys(3)</c></seealso> and section
- <seealso marker="doc/design_principles:spec_proc">
- sys and proc_lib</seealso> in OTP Design Principles.</p>
+ <seeerl marker="sys"><c>sys(3)</c></seeerl> and section
+ <seeguide marker="system/design_principles:spec_proc">
+ sys and proc_lib</seeguide> in OTP Design Principles.</p>
</desc>
</func>
@@ -380,8 +440,8 @@ init(Parent) ->
<c>proc_lib</c>spawned process.</fsummary>
<desc>
<p>This function is used by functions
- <seealso marker="c#i/0"><c>c:i/0</c></seealso> and
- <seealso marker="c#regs/0"><c>c:regs/0</c></seealso>
+ <seemfa marker="c#i/0"><c>c:i/0</c></seemfa> and
+ <seemfa marker="c#regs/0"><c>c:regs/0</c></seemfa>
to present process information.</p>
<p>This function extracts the initial call of a process that was
started using one of the spawn or start functions in this module,
@@ -414,10 +474,10 @@ init(Parent) ->
<section>
<title>See Also</title>
- <p><seealso marker="kernel:error_logger">
- <c>error_logger(3)</c></seealso></p>
- <p><seealso marker="kernel:logger">
- <c>logger(3)</c></seealso></p>
+ <p><seeerl marker="kernel:error_logger">
+ <c>error_logger(3)</c></seeerl></p>
+ <p><seeerl marker="kernel:logger">
+ <c>logger(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/proplists.xml b/lib/stdlib/doc/src/proplists.xml
index 6dedaf79b5..f59b6eda17 100644
--- a/lib/stdlib/doc/src/proplists.xml
+++ b/lib/stdlib/doc/src/proplists.xml
@@ -70,7 +70,7 @@
<fsummary></fsummary>
<desc>
<p>Similar to
- <seealso marker="#get_all_values/2"><c>get_all_values/2</c></seealso>,
+ <seemfa marker="#get_all_values/2"><c>get_all_values/2</c></seemfa>,
but each value is wrapped in a list unless it is already itself a
list. The resulting list of lists is concatenated. This is often
useful for "incremental" options.</p>
@@ -90,8 +90,8 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code>
<p>Minimizes the representation of all entries in the list. This is
equivalent to <c><![CDATA[[property(P) || P <- ListIn]]]></c>.</p>
<p>See also
- <seealso marker="#property/1"><c>property/1</c></seealso>,
- <seealso marker="#unfold/1"><c>unfold/1</c></seealso>.</p>
+ <seemfa marker="#property/1"><c>property/1</c></seemfa>,
+ <seemfa marker="#unfold/1"><c>unfold/1</c></seemfa>.</p>
</desc>
</func>
@@ -133,7 +133,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
<c><anno>Expansions</anno></c> contains more than one property with
the same key, only the first occurrence is used.</p>
<p>See also
- <seealso marker="#normalize/2"><c>normalize/2</c></seealso>.</p>
+ <seemfa marker="#normalize/2"><c>normalize/2</c></seemfa>.</p>
</desc>
</func>
@@ -142,7 +142,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
<fsummary></fsummary>
<desc>
<p>Similar to
- <seealso marker="#get_value/2"><c>get_value/2</c></seealso>,
+ <seemfa marker="#get_value/2"><c>get_value/2</c></seemfa>,
but returns the list of values for <em>all</em> entries
<c>{Key, Value}</c> in <c><anno>List</anno></c>. If no such entry
exists, the result is the empty list.</p>
@@ -158,8 +158,8 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
<c>{<anno>Key</anno>, true}</c>, this function returns <c>true</c>,
otherwise <c>false</c>.</p>
<p>See also
- <seealso marker="#get_value/2"><c>get_value/2</c></seealso>,
- <seealso marker="#lookup/2"><c>lookup/2</c></seealso>.</p>
+ <seemfa marker="#get_value/2"><c>get_value/2</c></seemfa>,
+ <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>.</p>
</desc>
</func>
@@ -191,10 +191,10 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
this function returns the corresponding <c>Value</c>, otherwise
<c><anno>Default</anno></c>.</p>
<p>See also
- <seealso marker="#get_all_values/2"><c>get_all_values/2</c></seealso>,
- <seealso marker="#get_bool/2"><c>get_bool/2</c></seealso>,
- <seealso marker="#get_value/2"><c>get_value/2</c></seealso>,
- <seealso marker="#lookup/2"><c>lookup/2</c></seealso>.</p>
+ <seemfa marker="#get_all_values/2"><c>get_all_values/2</c></seemfa>,
+ <seemfa marker="#get_bool/2"><c>get_bool/2</c></seemfa>,
+ <seemfa marker="#get_value/2"><c>get_value/2</c></seemfa>,
+ <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>.</p>
</desc>
</func>
@@ -217,9 +217,9 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
<c>none</c>. For an atom <c>A</c> in the list, the tuple
<c>{A, true}</c> is the entry associated with <c>A</c>.</p>
<p>See also
- <seealso marker="#get_bool/2"><c>get_bool/2</c></seealso>,
- <seealso marker="#get_value/2"><c>get_value/2</c></seealso>,
- <seealso marker="#lookup_all/2"><c>lookup_all/2</c></seealso>.</p>
+ <seemfa marker="#get_bool/2"><c>get_bool/2</c></seemfa>,
+ <seemfa marker="#get_value/2"><c>get_value/2</c></seemfa>,
+ <seemfa marker="#lookup_all/2"><c>lookup_all/2</c></seemfa>.</p>
</desc>
</func>
@@ -231,7 +231,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
<c><anno>Key</anno></c> in <c><anno>List</anno></c>. If no such entry
exists, the result is the empty list.</p>
<p>See also
- <seealso marker="#lookup/2"><c>lookup/2</c></seealso>.</p>
+ <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>.</p>
</desc>
</func>
@@ -241,8 +241,8 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
<desc>
<p>Passes <c><anno>ListIn</anno></c> through a sequence of
substitution/expansion stages. For an <c>aliases</c> operation,
- function <seealso marker="#substitute_aliases/2">
- <c>substitute_aliases/2</c></seealso> is applied using the
+ function <seemfa marker="#substitute_aliases/2">
+ <c>substitute_aliases/2</c></seemfa> is applied using the
specified list of aliases:</p>
<list type="bulleted">
<item>
@@ -251,19 +251,19 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</item>
<item>
<p>For an <c>expand</c> operation, function
- <seealso marker="#expand/2"><c>expand/2</c></seealso>
+ <seemfa marker="#expand/2"><c>expand/2</c></seemfa>
is applied using the specified list of expansions.</p>
</item>
</list>
<p>The final result is automatically compacted (compare
- <seealso marker="#compact/1"><c>compact/1</c></seealso>).</p>
+ <seemfa marker="#compact/1"><c>compact/1</c></seemfa>).</p>
<p>Typically you want to substitute negations first, then aliases,
then perform one or more expansions (sometimes you want to pre-expand
particular entries before doing the main expansion). You might want
to substitute negations and/or aliases repeatedly, to allow such
forms in the right-hand side of aliases and expansion lists.</p>
- <p>See also <seealso marker="#substitute_negations/2">
- <c>substitute_negations/2</c></seealso>.</p>
+ <p>See also <seemfa marker="#substitute_negations/2">
+ <c>substitute_negations/2</c></seemfa>.</p>
</desc>
</func>
@@ -276,7 +276,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
<c>Key</c> is an atom, <c>Key</c> is returned, otherwise
the whole term <c><anno>PropertyIn</anno></c> is returned.</p>
<p>See also
- <seealso marker="#property/2"><c>property/2</c></seealso>.</p>
+ <seemfa marker="#property/2"><c>property/2</c></seemfa>.</p>
</desc>
</func>
@@ -289,7 +289,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
is <c>true</c> and <c><anno>Key</anno></c> is an atom, otherwise a
tuple <c>{<anno>Key</anno>, <anno>Value</anno>}</c> is returned.</p>
<p>See also
- <seealso marker="#property/1"><c>property/1</c></seealso>.</p>
+ <seemfa marker="#property/1"><c>property/1</c></seemfa>.</p>
</desc>
</func>
@@ -330,9 +330,9 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code>
with <c>{colour, ...}</c>, and all atoms <c>color</c>
with <c>colour</c>.</p>
<p>See also
- <seealso marker="#normalize/2"><c>normalize/2</c></seealso>,
- <seealso marker="#substitute_negations/2">
- <c>substitute_negations/2</c></seealso>.</p>
+ <seemfa marker="#normalize/2"><c>normalize/2</c></seemfa>,
+ <seemfa marker="#substitute_negations/2">
+ <c>substitute_negations/2</c></seemfa>.</p>
</desc>
</func>
@@ -348,8 +348,8 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code>
<c>{K1, true}</c>, it is replaced with <c>{K2, false}</c>, otherwise
with <c>K2</c>, thus changing the name of the option and
simultaneously negating the value specified by
- <seealso marker="#get_bool/2">
- <c>get_bool(Key, <anno>ListIn</anno>)</c></seealso>.
+ <seemfa marker="#get_bool/2">
+ <c>get_bool(Key, <anno>ListIn</anno>)</c></seemfa>.
If the same <c>K1</c> occurs more than once in
<c><anno>Negations</anno></c>, only the first occurrence is used.</p>
<p>For example, <c>substitute_negations([{no_foo, foo}], L)</c>
@@ -357,10 +357,10 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code>
<c>{no_foo, true}</c> in <c>L</c> with <c>{foo, false}</c>,
and any other tuple <c>{no_foo, ...}</c> with <c>foo</c>.</p>
<p>See also
- <seealso marker="#get_bool/2"><c>get_bool/2</c></seealso>,
- <seealso marker="#normalize/2"><c>normalize/2</c></seealso>,
- <seealso marker="#substitute_aliases/2">
- <c>substitute_aliases/2</c></seealso>.</p>
+ <seemfa marker="#get_bool/2"><c>get_bool/2</c></seemfa>,
+ <seemfa marker="#normalize/2"><c>normalize/2</c></seemfa>,
+ <seemfa marker="#substitute_aliases/2">
+ <c>substitute_aliases/2</c></seemfa>.</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/qlc.xml b/lib/stdlib/doc/src/qlc.xml
index 34f7c5bab9..7c3c9e2e70 100644
--- a/lib/stdlib/doc/src/qlc.xml
+++ b/lib/stdlib/doc/src/qlc.xml
@@ -37,9 +37,9 @@
</modulesummary>
<description>
<p>This module provides a query interface to
- <seealso marker="mnesia:mnesia">Mnesia</seealso>,
- <seealso marker="ets">ETS</seealso>,
- <seealso marker="dets">Dets</seealso>,
+ <seeerl marker="mnesia:mnesia">Mnesia</seeerl>,
+ <seeerl marker="ets">ETS</seeerl>,
+ <seeerl marker="dets">Dets</seeerl>,
and other data structures that provide an iterator style
traversal of objects.</p>
</description>
@@ -49,41 +49,41 @@
<p>This module provides a query interface to <em>QLC
tables</em>. Typical QLC tables are Mnesia, ETS, and
Dets tables. Support is also provided for user-defined tables, see section
- <seealso marker="#implementing_a_qlc_table">
- Implementing a QLC Table</seealso>.
+ <seeerl marker="#implementing_a_qlc_table">
+ Implementing a QLC Table</seeerl>.
<marker id="query_list_comprehension"></marker>
A <em>query</em> is expressed using
<em>Query List Comprehensions</em> (QLCs). The answers to a
query are determined by data in QLC tables that fulfill the
constraints expressed by the QLCs of the query. QLCs are similar
to ordinary list comprehensions as described in
- <seealso marker="doc/reference_manual:expressions#lcs">
- Erlang Reference Manual</seealso> and
- <seealso marker="doc/programming_examples:list_comprehensions">
- Programming Examples</seealso>, except that variables
+ <seeguide marker="system/reference_manual:expressions#lcs">
+ Erlang Reference Manual</seeguide> and
+ <seeguide marker="system/programming_examples:list_comprehensions">
+ Programming Examples</seeguide>, except that variables
introduced in patterns cannot be used in list expressions.
In the absence of optimizations and options such as
<c>cache</c> and <c>unique</c> (see section
- <seealso marker="#common_options">Common Options</seealso>, every
+ <seeerl marker="#common_options">Common Options</seeerl>, every
QLC free of QLC tables evaluates to the same list of answers as the
identical ordinary list comprehension.</p>
<p>While ordinary list comprehensions evaluate to lists, calling
- <seealso marker="#q/1"><c>q/1,2</c></seealso> returns a
+ <seemfa marker="#q/1"><c>q/1,2</c></seemfa> returns a
<marker id="query_handle"></marker><em>query handle</em>.
- To obtain all the answers to a query, <seealso marker="#eval/1">
- <c>eval/1,2</c></seealso> is to be called with the
+ To obtain all the answers to a query, <seemfa marker="#eval/1">
+ <c>eval/1,2</c></seemfa> is to be called with the
query handle as first argument. Query handles are essentially
functional objects (funs) created in the module calling <c>q/1,2</c>.
As the funs refer to the module code, be careful not to keep query
handles too long if the module code is to be replaced.
Code replacement is described in section
- <seealso marker="doc/reference_manual:code_loading">
- Compilation and Code Loading</seealso> in the Erlang Reference Manual.
+ <seeguide marker="system/reference_manual:code_loading">
+ Compilation and Code Loading</seeguide> in the Erlang Reference Manual.
The list of answers can also be traversed in chunks by use of a
<marker id="query_cursor"></marker><em>query cursor</em>.
Query cursors are created by calling
- <seealso marker="#cursor/1"><c>cursor/1,2</c></seealso> with a query
+ <seemfa marker="#cursor/1"><c>cursor/1,2</c></seemfa> with a query
handle as first argument. Query cursors are essentially Erlang processes.
One answer at a time is sent from the query cursor process to
the process that created the cursor.</p>
@@ -104,13 +104,13 @@
<c><![CDATA[Pattern <- ListExpression]]></c>, where
<c>ListExpression</c> is an expression evaluating to a query
handle or a list. Query handles are returned from
- <seealso marker="#append/1"><c>append/1,2</c></seealso>,
- <seealso marker="#keysort/2"><c>keysort/2,3</c></seealso>,
- <seealso marker="#q/1"><c>q/1,2</c></seealso>,
- <seealso marker="#sort/1"><c>sort/1,2</c></seealso>,
- <seealso marker="#string_to_handle/1">
- <c>string_to_handle/1,2,3</c></seealso>, and
- <seealso marker="#table/2"><c>table/2</c></seealso>.</p>
+ <seemfa marker="#append/1"><c>append/1,2</c></seemfa>,
+ <seemfa marker="#keysort/2"><c>keysort/2,3</c></seemfa>,
+ <seemfa marker="#q/1"><c>q/1,2</c></seemfa>,
+ <seemfa marker="#sort/1"><c>sort/1,2</c></seemfa>,
+ <seemfa marker="#string_to_handle/1">
+ <c>string_to_handle/1,2,3</c></seemfa>, and
+ <seemfa marker="#table/2"><c>table/2</c></seemfa>.</p>
</section>
<section>
@@ -200,10 +200,10 @@
<marker id="common_options"></marker>
<title>Common Options</title>
<p>The following options are accepted by
- <seealso marker="#cursor/2"><c>cursor/2</c></seealso>,
- <seealso marker="#eval/2"><c>eval/2</c></seealso>,
- <seealso marker="#fold/4"><c>fold/4</c></seealso>, and
- <seealso marker="#info/2"><c>info/2</c></seealso>:</p>
+ <seemfa marker="#cursor/2"><c>cursor/2</c></seemfa>,
+ <seemfa marker="#eval/2"><c>eval/2</c></seemfa>,
+ <seemfa marker="#fold/4"><c>fold/4</c></seemfa>, and
+ <seemfa marker="#info/2"><c>info/2</c></seemfa>:</p>
<list type="bulleted">
<item><p><c>{cache_all, Cache}</c>, where <c>Cache</c> is
@@ -230,15 +230,15 @@
The values <c>info_msg</c>, <c>warning_msg</c>, and
<c>error_msg</c> mean that the function with the corresponding
name in module
- <seealso marker="kernel:error_logger"><c>error_logger</c></seealso>
+ <seeerl marker="kernel:error_logger"><c>error_logger</c></seeerl>
is called for printing some information (currently the stacktrace).</p>
</item>
<item><p><c>{tmpdir, TempDirectory}</c> sets the directory used by
merge join for temporary files and by option
<c>{cache,&nbsp;list}</c>. The option also overrides
option <c>tmpdir</c> of
- <seealso marker="#keysort/3"><c>keysort/3</c></seealso> and
- <seealso marker="#sort/2"><c>sort/2</c></seealso>.
+ <seemfa marker="#keysort/3"><c>keysort/3</c></seemfa> and
+ <seemfa marker="#sort/2"><c>sort/2</c></seemfa>.
Defaults to <c>""</c>, which means that
the directory returned by <c>file:get_cwd()</c> is used.</p>
</item>
@@ -257,11 +257,11 @@
<p>As mentioned earlier,
queries are expressed in the list comprehension syntax as described
in section
- <seealso marker="doc/reference_manual:expressions">Expressions</seealso>
+ <seeguide marker="system/reference_manual:expressions">Expressions</seeguide>
in Erlang Reference Manual. In the following, some familiarity
with list comprehensions is assumed. The examples in section
- <seealso marker="doc/programming_examples:list_comprehensions">
- List Comprehensions</seealso> in Programming Examples can get you
+ <seeguide marker="system/programming_examples:list_comprehensions">
+ List Comprehensions</seeguide> in Programming Examples can get you
started. Notice that list comprehensions do not add any computational
power to the language; anything that can be done with list
comprehensions can also be done without them. But they add
@@ -277,7 +277,7 @@
<c>Y</c> is introduced in the first generator and used in the second.
The ordinary list comprehension is normally to be preferred when
there is a choice as to which to use. One difference is that
- <seealso marker="#eval/1"><c>eval/1,2</c></seealso>
+ <seemfa marker="#eval/1"><c>eval/1,2</c></seemfa>
collects answers in a list that is finally
reversed, while list comprehensions collect answers on the stack
that is finally unwound.</p>
@@ -285,26 +285,26 @@
<p>What the <c>qlc</c> module primarily adds to list
comprehensions is that data can be read from QLC tables in small
chunks. A QLC table is created by calling
- <seealso marker="#table/2"><c>qlc:table/2</c></seealso>.
+ <seemfa marker="#table/2"><c>qlc:table/2</c></seemfa>.
Usually <c>qlc:table/2</c> is not called directly from the query
but through an interface function of some data structure.
Erlang/OTP includes a few examples of such functions:
- <seealso marker="mnesia:mnesia#table/1"><c>mnesia:table/1,2</c></seealso>,
- <seealso marker="ets#table/1"><c>ets:table/1,2</c></seealso>, and
- <seealso marker="dets#table/1"><c>dets:table/1,2</c></seealso>.
+ <seemfa marker="mnesia:mnesia#table/1"><c>mnesia:table/1,2</c></seemfa>,
+ <seemfa marker="ets#table/1"><c>ets:table/1,2</c></seemfa>, and
+ <seemfa marker="dets#table/1"><c>dets:table/1,2</c></seemfa>.
For a given data structure, many functions can create QLC tables, but
common for these functions is that they return a query handle created by
- <seealso marker="#table/2"><c>qlc:table/2</c></seealso>.
+ <seemfa marker="#table/2"><c>qlc:table/2</c></seemfa>.
Using the QLC tables provided by Erlang/OTP is usually
probably sufficient, but for the more advanced user section
- <seealso marker="#implementing_a_qlc_table">Implementing a QLC
- Table</seealso> describes the implementation of a function
+ <seeerl marker="#implementing_a_qlc_table">Implementing a QLC
+ Table</seeerl> describes the implementation of a function
calling <c>qlc:table/2</c>.</p>
<p>Besides <c>qlc:table/2</c>, other functions
return query handles. They are used more seldom than tables,
but are sometimes useful.
- <seealso marker="#append/1"><c>qlc:append/1,2</c></seealso> traverses
+ <seemfa marker="#append/1"><c>qlc:append/1,2</c></seemfa> traverses
objects from many tables or lists after each other. If, for
example, you want to traverse all answers to a query <c>QH</c> and
then finish off by a term <c>{finished}</c>, you can do that by
@@ -389,9 +389,9 @@ R.]]></code>
the filter is run M*N times.</p>
<p>If <c>QH2</c> is a call to the function for
- <seealso marker="gb_trees"><c>gb_trees</c></seealso>, as defined
- in section <seealso marker="#implementing_a_qlc_table">Implementing
- a QLC Table</seealso>, then <c>gb_table:table/1</c>, the
+ <seeerl marker="gb_trees"><c>gb_trees</c></seeerl>, as defined
+ in section <seeerl marker="#implementing_a_qlc_table">Implementing
+ a QLC Table</seeerl>, then <c>gb_table:table/1</c>, the
iterator for the gb-tree is initiated for each answer to
<c>QH1</c>. The objects of the gb-tree are then returned one
by one. This is probably the most efficient way of traversing
@@ -432,8 +432,8 @@ R.]]></code>
lists is that if the list size exceeds a limit, a
temporary file is used. Reading the answers from a file is
much slower than copying them from an ETS table. But if the
- available RAM memory is scarce, setting the <seealso
- marker="#max_list_size">limit</seealso> to some low value is an
+ available RAM memory is scarce, setting the <seeerl
+ marker="#max_list_size">limit</seeerl> to some low value is an
alternative.</p>
<p>Option <c>cache_all</c> can be set to
@@ -449,9 +449,9 @@ R.]]></code>
<marker id="implementing_a_qlc_table"></marker>
<title>Implementing a QLC Table</title>
<p>As an example of
- how to use function <seealso marker="#table/2"><c>table/2</c></seealso>,
- the implementation of a QLC table for the <seealso
- marker="gb_trees"><c>gb_trees</c></seealso> module is given:</p>
+ how to use function <seemfa marker="#table/2"><c>table/2</c></seemfa>,
+ the implementation of a QLC table for the <seeerl
+ marker="gb_trees"><c>gb_trees</c></seeerl> module is given:</p>
<code type="none">
<![CDATA[-module(gb_table).
@@ -529,7 +529,7 @@ gb_iter(I0, N, EFun) ->
<c>{Key,&nbsp;Value}</c> pairs, as the traversal function does.</p>
<p>The format function is also optional. It is called by
- <seealso marker="#info/1"><c>info/1,2</c></seealso>
+ <seemfa marker="#info/1"><c>info/1,2</c></seemfa>
to give feedback at runtime of how the query
is to be evaluated. Try to give as good feedback as
possible without showing too much details. In the example, at
@@ -593,8 +593,8 @@ ets:match_spec_run(
exactly as <c>=:=/2</c> would have been handled. However,
if it cannot be determined at compile time that some
constant is free of integers, and the table uses <c>=:=/2</c>
- when comparing keys for equality (see option <seealso
- marker="#key_equality">key_equality</seealso>), then the
+ when comparing keys for equality (see option <seeerl
+ marker="#key_equality">key_equality</seeerl>), then the
<c>qlc</c> module does not try to look up the constant. The
reason is that there is in the general case no upper limit on
the number of key values that can compare equal to such a
@@ -648,8 +648,8 @@ ets:match_spec_run(ets:lookup(#Ref&lt;0.3098908599.2283929601.256211>,
<datatypes>
<datatype>
<name name="abstract_expr"></name>
- <desc><p>Parse trees for Erlang expression, see section <seealso
- marker="erts:absform">The Abstract Format</seealso>
+ <desc><p>Parse trees for Erlang expression, see section <seeguide
+ marker="erts:absform">The Abstract Format</seeguide>
in the ERTS User's Guide.</p></desc>
</datatype>
<datatype>
@@ -663,10 +663,10 @@ ets:match_spec_run(ets:lookup(#Ref&lt;0.3098908599.2283929601.256211>,
</datatype>
<datatype>
<name name="match_expression"></name>
- <desc><p>Match&nbsp;specification, see section <seealso
- marker="erts:match_spec">Match Specifications in Erlang</seealso>
- in the ERTS User's Guide and <seealso
- marker="ms_transform"><c>ms_transform(3)</c></seealso>.</p></desc>
+ <desc><p>Match&nbsp;specification, see section <seeguide
+ marker="erts:match_spec">Match Specifications in Erlang</seeguide>
+ in the ERTS User's Guide and <seeerl
+ marker="ms_transform"><c>ms_transform(3)</c></seeerl>.</p></desc>
</datatype>
<datatype>
<name name="no_files"></name>
@@ -686,12 +686,12 @@ ets:match_spec_run(ets:lookup(#Ref&lt;0.3098908599.2283929601.256211>,
</datatype>
<datatype>
<name name="query_cursor"></name>
- <desc><p>A <seealso marker="#query_cursor">query cursor</seealso>.</p>
+ <desc><p>A <seeerl marker="#query_cursor">query cursor</seeerl>.</p>
</desc>
</datatype>
<datatype>
<name name="query_handle"></name>
- <desc><p>A <seealso marker="#query_handle">query handle</seealso>.</p>
+ <desc><p>A <seeerl marker="#query_handle">query handle</seeerl>.</p>
</desc>
</datatype>
<datatype>
@@ -700,8 +700,8 @@ ets:match_spec_run(ets:lookup(#Ref&lt;0.3098908599.2283929601.256211>,
<datatype>
<name name="query_list_comprehension"></name>
<desc><p>A literal
- <seealso marker="#query_list_comprehension">query
- list comprehension</seealso>.</p></desc>
+ <seeerl marker="#query_list_comprehension">query
+ list comprehension</seeerl>.</p></desc>
</datatype>
<datatype>
<name name="spawn_options"></name>
@@ -711,8 +711,8 @@ ets:match_spec_run(ets:lookup(#Ref&lt;0.3098908599.2283929601.256211>,
</datatype>
<datatype>
<name name="sort_option"></name>
- <desc><p>See <seealso
- marker="file_sorter"><c>file_sorter(3)</c></seealso>.</p></desc>
+ <desc><p>See <seeerl
+ marker="file_sorter"><c>file_sorter(3)</c></seeerl>.</p></desc>
</datatype>
<datatype>
<name name="tmp_directory"></name>
@@ -755,11 +755,11 @@ ets:match_spec_run(ets:lookup(#Ref&lt;0.3098908599.2283929601.256211>,
<p>Creates a query cursor and
makes the calling process the owner of the cursor. The
cursor is to be used as argument to
- <seealso marker="#next_answers/1">
- <c>next_answers/1,2</c></seealso> and (eventually)
- <seealso marker="#delete_cursor/1"><c>delete_cursor/1</c></seealso>.
- Calls <seealso marker="erts:erlang#spawn_opt/2">
- <c>erlang:spawn_opt/2</c></seealso> to spawn and link to
+ <seemfa marker="#next_answers/1">
+ <c>next_answers/1,2</c></seemfa> and (eventually)
+ <seemfa marker="#delete_cursor/1"><c>delete_cursor/1</c></seemfa>.
+ Calls <seemfa marker="erts:erlang#spawn_opt/2">
+ <c>erlang:spawn_opt/2</c></seemfa> to spawn and link to
a process that evaluates the query handle. The value of option
<c>spawn_options</c> is used as last argument when calling
<c>spawn_opt/2</c>. Defaults to <c>[link]</c>.</p>
@@ -958,8 +958,8 @@ end</pre>
<desc>
<p>Returns a query handle. When evaluating query handle
<c><anno>QH2</anno></c>, the answers to query handle
- <c><anno>QH1</anno></c> are sorted by <seealso
- marker="file_sorter#keysort/4"><c>file_sorter:keysort/4</c></seealso>
+ <c><anno>QH1</anno></c> are sorted by <seemfa
+ marker="file_sorter#keysort/4"><c>file_sorter:keysort/4</c></seemfa>
according to the options.</p>
<p>The sorter uses temporary files only if
<c><anno>QH1</anno></c> does not evaluate to a list and the
@@ -1004,7 +1004,7 @@ end</pre>
<p>When calling <c>qlc:q/1,2</c> from the Erlang shell, the
parse transform is automatically called. When this occurs, the fun
substituted for the QLC is not compiled but is evaluated by
- <seealso marker="erl_eval"><c>erl_eval(3)</c></seealso>. This
+ <seeerl marker="erl_eval"><c>erl_eval(3)</c></seeerl>. This
is also true when expressions are evaluated by
<c>file:eval/1,2</c> or in the debugger.</p>
<p>To be explicit, this does not work:</p>
@@ -1062,7 +1062,7 @@ QH = qlc:q(A),
table only. If option <c>unique</c> is combined with option
<c>{cache,&nbsp;list}</c>, the answers are sorted
twice using
- <seealso marker="#keysort/3"><c>keysort/3</c></seealso>;
+ <seemfa marker="#keysort/3"><c>keysort/3</c></seemfa>;
once to remove duplicates and once to restore the order.</p>
</item>
</list>
@@ -1110,14 +1110,14 @@ begin
X =:= Y
])
end</pre>
- <p><seealso marker="#sort/1"><c>sort/1,2</c></seealso> and
- <seealso marker="#keysort/2"><c>keysort/2,3</c></seealso>
+ <p><seemfa marker="#sort/1"><c>sort/1,2</c></seemfa> and
+ <seemfa marker="#keysort/2"><c>keysort/2,3</c></seemfa>
can also be used for
caching answers and for removing duplicates. When sorting
answers are cached in a list, possibly stored on a temporary
file, and no ETS tables are used.</p>
- <p>Sometimes (see <seealso
- marker="#table/2"><c>table/2</c></seealso>) traversal
+ <p>Sometimes (see <seemfa
+ marker="#table/2"><c>table/2</c></seemfa>) traversal
of tables can be done by looking up key values, which is
assumed to be fast. Under certain (rare) circumstances
there can be too many key values to look up.
@@ -1130,8 +1130,8 @@ end</pre>
number of keys to look up.</p>
<p><em>Example:</em></p>
<p>In the following example, using the <c>gb_table</c> module from
- section <seealso marker="#implementing_a_qlc_table">Implementing a
- QLC Table</seealso>, there are six keys to look up:
+ section <seeerl marker="#implementing_a_qlc_table">Implementing a
+ QLC Table</seeerl>, there are six keys to look up:
<c>{1,a}</c>, <c>{1,b}</c>, <c>{1,c}</c>, <c>{2,a}</c>,
<c>{2,b}</c>, and <c>{2,c}</c>. The reason is that the two
elements of key <c>{X,&nbsp;Y}</c> are compared separately.</p>
@@ -1207,8 +1207,8 @@ ets:match_spec_run(
<desc>
<p>Returns a query handle. When evaluating query handle
<c><anno>QH2</anno></c>, the answers to query handle
- <c><anno>QH1</anno></c> are sorted by <seealso
- marker="file_sorter#sort/3"><c>file_sorter:sort/3</c></seealso>
+ <c><anno>QH1</anno></c> are sorted by <seemfa
+ marker="file_sorter#sort/3"><c>file_sorter:sort/3</c></seemfa>
according to the options.</p>
<p>The sorter uses temporary files only if
<c><anno>QH1</anno></c> does not evaluate to a list and the
@@ -1226,10 +1226,10 @@ ets:match_spec_run(
<name name="string_to_handle" arity="3" since=""/>
<fsummary>Return a handle for a query list comprehension.</fsummary>
<desc>
- <p>A string version of <seealso marker="#q/1"><c>q/1,2</c></seealso>.
+ <p>A string version of <seemfa marker="#q/1"><c>q/1,2</c></seemfa>.
When the query handle is evaluated, the fun created by the parse
transform is interpreted by
- <seealso marker="erl_eval"><c>erl_eval(3)</c></seealso>.
+ <seeerl marker="erl_eval"><c>erl_eval(3)</c></seeerl>.
The query string is to be one single QLC terminated by a period.</p>
<p><em>Example:</em></p>
<pre>
@@ -1283,15 +1283,15 @@ ets:match_spec_run(
<p>Modules that can use match specifications for optimized
traversal of tables are to call <c>qlc:table/2</c> with an unary
<c><anno>TraverseFun</anno></c>. An example is
- <seealso marker="ets#table/2">
- <c>ets:table/2</c></seealso>.</p>
+ <seemfa marker="ets#table/2">
+ <c>ets:table/2</c></seemfa>.</p>
</item>
<item>
<p>Other modules can provide a nullary
<c><anno>TraverseFun</anno></c>. An example is
<c>gb_table:table/1</c> in section
- <seealso marker="#implementing_a_qlc_table">Implementing a
- QLC Table</seealso>.</p>
+ <seeerl marker="#implementing_a_qlc_table">Implementing a
+ QLC Table</seeerl>.</p>
</item>
</list>
</item>
@@ -1310,9 +1310,9 @@ ets:match_spec_run(
<c><anno>ParentFun</anno></c> is called once just before the
call of <c><anno>PreFun</anno></c> in the context of the
process calling
- <seealso marker="#eval/1"><c>eval/1,2</c></seealso>,
- <seealso marker="#fold/3"><c>fold/3,4</c></seealso>, or
- <seealso marker="#cursor/1"><c>cursor/1,2</c></seealso>.
+ <seemfa marker="#eval/1"><c>eval/1,2</c></seemfa>,
+ <seemfa marker="#fold/3"><c>fold/3,4</c></seemfa>, or
+ <seemfa marker="#cursor/1"><c>cursor/1,2</c></seemfa>.
</p>
</item>
<item>
@@ -1361,7 +1361,7 @@ ets:match_spec_run(
chosen. If there is a tie between two indexed positions, the
one occurring first in the list returned by
<c><anno>InfoFun</anno></c> is chosen. Positions requiring
- more than <seealso marker="#max_lookup">max_lookup</seealso>
+ more than <seeerl marker="#max_lookup">max_lookup</seeerl>
lookups are ignored.</p>
</item>
<item>
@@ -1391,7 +1391,7 @@ ets:match_spec_run(
</item>
<item>
<p>Unary callback function <c><anno>FormatFun</anno></c>
- is used by <seealso marker="#info/1"><c>info/1,2</c></seealso>
+ is used by <seemfa marker="#info/1"><c>info/1,2</c></seemfa>
for displaying the call that created the query handle of the
table. Defaults to <c>undefined</c>, which means that
<c>info/1,2</c> displays a call to <c>'$MOD':'$FUN'/0</c>.
@@ -1448,9 +1448,9 @@ ets:match_spec_run(
</list>
<p>For the various options recognized by <c>table/1,2</c>
in respective module, see
- <seealso marker="ets#table/1"><c>ets(3)</c></seealso>,
- <seealso marker="dets#table/1"><c>dets(3)</c></seealso>, and
- <seealso marker="mnesia:mnesia#table/1"><c>mnesia(3)</c></seealso>.
+ <seemfa marker="ets#table/1"><c>ets(3)</c></seemfa>,
+ <seemfa marker="dets#table/1"><c>dets(3)</c></seemfa>, and
+ <seemfa marker="mnesia:mnesia#table/1"><c>mnesia(3)</c></seemfa>.
</p>
</desc>
</func>
@@ -1458,18 +1458,18 @@ ets:match_spec_run(
<section>
<title>See Also</title>
- <p><seealso marker="dets"><c>dets(3)</c></seealso>,
- <seealso marker="erl_eval"><c>erl_eval(3)</c></seealso>,
- <seealso marker="erts:erlang"><c>erlang(3)</c></seealso>,
- <seealso marker="kernel:error_logger"><c>error_logger(3)</c></seealso>,
- <seealso marker="ets"><c>ets(3)</c></seealso>,
- <seealso marker="kernel:file"><c>file(3)</c></seealso>,
- <seealso marker="file_sorter"><c>file_sorter(3)</c></seealso>,
- <seealso marker="mnesia:mnesia"><c>mnesia(3)</c></seealso>,
- <seealso marker="shell"><c>shell(3)</c></seealso>,
- <seealso marker="doc/reference_manual:users_guide">
- Erlang Reference Manual</seealso>,
- <seealso marker="doc/programming_examples:users_guide">
- Programming Examples</seealso></p>
+ <p><seeerl marker="dets"><c>dets(3)</c></seeerl>,
+ <seeerl marker="erl_eval"><c>erl_eval(3)</c></seeerl>,
+ <seeerl marker="erts:erlang"><c>erlang(3)</c></seeerl>,
+ <seeerl marker="kernel:error_logger"><c>error_logger(3)</c></seeerl>,
+ <seeerl marker="ets"><c>ets(3)</c></seeerl>,
+ <seeerl marker="kernel:file"><c>file(3)</c></seeerl>,
+ <seeerl marker="file_sorter"><c>file_sorter(3)</c></seeerl>,
+ <seeerl marker="mnesia:mnesia"><c>mnesia(3)</c></seeerl>,
+ <seeerl marker="shell"><c>shell(3)</c></seeerl>,
+ <seeguide marker="system/reference_manual:index">
+ Erlang Reference Manual</seeguide>,
+ <seeguide marker="system/programming_examples:index">
+ Programming Examples</seeguide></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/queue.xml b/lib/stdlib/doc/src/queue.xml
index ef0ce5681f..69b66129fa 100644
--- a/lib/stdlib/doc/src/queue.xml
+++ b/lib/stdlib/doc/src/queue.xml
@@ -53,11 +53,11 @@
assuming knowledge of the format is running on thin ice.</p>
<p>All operations have an amortized O(1) running time, except
- <seealso marker="#filter/2"><c>filter/2</c></seealso>,
- <seealso marker="#join/2"><c>join/2</c></seealso>,
- <seealso marker="#len/1"><c>len/1</c></seealso>,
- <seealso marker="#member/2"><c>member/2</c></seealso>,
- <seealso marker="#split/2"><c>split/2</c></seealso> that have O(n).
+ <seemfa marker="#filter/2"><c>filter/2</c></seemfa>,
+ <seemfa marker="#join/2"><c>join/2</c></seemfa>,
+ <seemfa marker="#len/1"><c>len/1</c></seemfa>,
+ <seemfa marker="#member/2"><c>member/2</c></seemfa>,
+ <seemfa marker="#split/2"><c>split/2</c></seemfa> that have O(n).
To minimize the size of a queue minimizing
the amount of garbage built by queue operations, the queues
do not contain explicit length information, and that is
@@ -96,15 +96,13 @@
some with more readable but perhaps less understandable aliases.</p>
</description>
- <section>
- <title>Original API</title>
- </section>
+
<datatypes>
<datatype>
<name name="queue" n_vars="1"/>
<desc><p>As returned by
- <seealso marker="#new/0"><c>new/0</c></seealso>.</p></desc>
+ <seemfa marker="#new/0"><c>new/0</c></seemfa>.</p></desc>
</datatype>
<datatype>
<name name="queue" n_vars="0"/>
@@ -112,6 +110,9 @@
</datatypes>
<funcs>
+ <fsdescription>
+ <title>Original API</title>
+ </fsdescription>
<func>
<name name="filter" arity="2" since=""/>
<fsummary>Filter a queue.</fsummary>
@@ -270,11 +271,12 @@
</func>
</funcs>
- <section>
- <title>Extended API</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Extended API</title>
+ </fsdescription>
<func>
<name name="drop" arity="1" since=""/>
<fsummary>Remove the front item from a queue.</fsummary>
@@ -336,11 +338,12 @@
</func>
</funcs>
- <section>
- <title>Okasaki API</title>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Okasaki API</title>
+ </fsdescription>
<func>
<name name="cons" arity="2" since=""/>
<fsummary>Insert an item at the head of a queue.</fsummary>
diff --git a/lib/stdlib/doc/src/rand.xml b/lib/stdlib/doc/src/rand.xml
index 5ed67dcaca..ac99736761 100644
--- a/lib/stdlib/doc/src/rand.xml
+++ b/lib/stdlib/doc/src/rand.xml
@@ -131,8 +131,8 @@
<p>
The default algorithm is <c>exsss</c> (Xorshift116**).
If a specific algorithm is
- required, ensure to always use <seealso marker="#seed-1">
- <c>seed/1</c></seealso> to initialize the state.
+ required, ensure to always use <seemfa marker="#seed/1">
+ <c>seed/1</c></seemfa> to initialize the state.
</p>
<p>
@@ -179,10 +179,10 @@
variable <c>rand_seed</c> to remember the current state.</p>
<p>If a process calls
- <seealso marker="#uniform-0"><c>uniform/0</c></seealso>,
- <seealso marker="#uniform-1"><c>uniform/1</c></seealso> or
- <seealso marker="#uniform_real-0"><c>uniform_real/0</c></seealso> without
- setting a seed first, <seealso marker="#seed-1"><c>seed/1</c></seealso>
+ <seemfa marker="#uniform/0"><c>uniform/0</c></seemfa>,
+ <seemfa marker="#uniform/1"><c>uniform/1</c></seemfa> or
+ <seemfa marker="#uniform_real/0"><c>uniform_real/0</c></seemfa> without
+ setting a seed first, <seemfa marker="#seed/1"><c>seed/1</c></seemfa>
is called automatically with the default algorithm and creates a
non-constant seed.</p>
@@ -236,7 +236,7 @@ SND0 = math:sqrt(-2 * math:log(R5)) * math:cos(math:pi() * R6)</pre>
<p>The builtin random number generator algorithms are not
cryptographically strong. If a cryptographically strong
random number generator is needed, use something like
- <seealso marker="crypto:crypto#rand_seed-0"><c>crypto:rand_seed/0</c></seealso>.
+ <seemfa marker="crypto:crypto#rand_seed/0"><c>crypto:rand_seed/0</c></seemfa>.
</p>
</note>
@@ -353,7 +353,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
<fsummary>Export the random number generation state.</fsummary>
<desc><marker id="export_seed-0"/>
<p>Returns the random number state in an external format.
- To be used with <seealso marker="#seed-1"><c>seed/1</c></seealso>.</p>
+ To be used with <seemfa marker="#seed/1"><c>seed/1</c></seemfa>.</p>
</desc>
</func>
@@ -362,7 +362,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
<fsummary>Export the random number generation state.</fsummary>
<desc><marker id="export_seed_s-1"/>
<p>Returns the random number generator state in an external format.
- To be used with <seealso marker="#seed-1"><c>seed/1</c></seealso>.</p>
+ To be used with <seemfa marker="#seed/1"><c>seed/1</c></seemfa>.</p>
</desc>
</func>
@@ -443,7 +443,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</p>
<p>Otherwise recreates the exported seed in the process dictionary,
and returns the state. See also
- <seealso marker="#export_seed-0"><c>export_seed/0</c></seealso>.</p>
+ <seemfa marker="#export_seed/0"><c>export_seed/0</c></seemfa>.</p>
</desc>
</func>
@@ -466,8 +466,8 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
is an algorithm.
</p>
<p>Otherwise recreates the exported seed and returns the state.
- See also <seealso marker="#export_seed-0">
- <c>export_seed/0</c></seealso>.</p>
+ See also <seemfa marker="#export_seed/0">
+ <c>export_seed/0</c></seemfa>.</p>
</desc>
</func>
@@ -499,7 +499,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
fatal for certain applications. If that is undesired
you can use <c>(1.0 - rand:uniform())</c> to get the
interval <c>0.0 &lt; <anno>X</anno> =&lt; 1.0</c>, or instead use
- <seealso marker="#uniform_real-0"><c>uniform_real/0</c></seealso>.
+ <seemfa marker="#uniform_real/0"><c>uniform_real/0</c></seemfa>.
</p>
<p>
If neither endpoint is desired you can test and re-try
@@ -537,7 +537,7 @@ end.</pre>
<p>
The generated numbers from this function has got better
granularity for small numbers than the regular
- <seealso marker="#uniform-0"><c>uniform/0</c></seealso>
+ <seemfa marker="#uniform/0"><c>uniform/0</c></seemfa>
because all bits in the mantissa are random.
This property, in combination with the fact that exactly zero
is never returned is useful for algoritms doing for example
@@ -546,7 +546,7 @@ end.</pre>
</note>
<p>
See
- <seealso marker="#uniform_real_s-1"><c>uniform_real_s/1</c></seealso>
+ <seemfa marker="#uniform_real_s/1"><c>uniform_real_s/1</c></seemfa>
for more explanation.
</p>
</desc>
@@ -582,7 +582,7 @@ end.</pre>
fatal for certain applications. If that is undesired
you can use <c>(1.0 - rand:uniform(State))</c> to get the
interval <c>0.0 &lt; <anno>X</anno> =&lt; 1.0</c>, or instead use
- <seealso marker="#uniform_real_s-1"><c>uniform_real_s/1</c></seealso>.
+ <seemfa marker="#uniform_real_s/1"><c>uniform_real_s/1</c></seemfa>.
</p>
<p>
If neither endpoint is desired you can test and re-try
@@ -620,7 +620,7 @@ end.</pre>
<p>
The generated numbers from this function has got better
granularity for small numbers than the regular
- <seealso marker="#uniform_s-1"><c>uniform_s/1</c></seealso>
+ <seemfa marker="#uniform_s/1"><c>uniform_s/1</c></seemfa>
because all bits in the mantissa are random.
This property, in combination with the fact that exactly zero
is never returned is useful for algoritms doing for example
@@ -650,13 +650,13 @@ end.</pre>
<c>0 =&lt; integer(N) &lt; 2.0^53</c>
the probability is the same.
Compare that with the form of the numbers generated by
- <seealso marker="#uniform_s-1"><c>uniform_s/1</c></seealso>.
+ <seemfa marker="#uniform_s/1"><c>uniform_s/1</c></seemfa>.
</p>
<p>
Having to generate extra random bits for
small numbers costs a little performance.
This function is about 20% slower than the regular
- <seealso marker="#uniform_s-1"><c>uniform_s/1</c></seealso>
+ <seemfa marker="#uniform_s/1"><c>uniform_s/1</c></seemfa>
</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/random.xml b/lib/stdlib/doc/src/random.xml
index 7566be0aab..30197d31fc 100644
--- a/lib/stdlib/doc/src/random.xml
+++ b/lib/stdlib/doc/src/random.xml
@@ -53,13 +53,13 @@
<p>This random number generator is not cryptographically
strong. If a strong cryptographic random number generator is
needed, use one of functions in the
- <seealso marker="crypto:crypto"><c>crypto</c></seealso>
- module, for example, <seealso marker="crypto:crypto">
- <c>crypto:strong_rand_bytes/1</c></seealso>.</p>
+ <seeerl marker="crypto:crypto"><c>crypto</c></seeerl>
+ module, for example, <seeerl marker="crypto:crypto">
+ <c>crypto:strong_rand_bytes/1</c></seeerl>.</p>
</note>
<note>
- <p>The improved <seealso marker="rand"><c>rand</c></seealso>
+ <p>The improved <seeerl marker="rand"><c>rand</c></seeerl>
module is to be used instead of this module.</p>
</note>
</description>
@@ -104,14 +104,14 @@ random:seed(erlang:phash2([node()]),
erlang:monotonic_time(),
erlang:unique_integer())</code>
<p>For details, see
- <seealso marker="erts:erlang#phash2/1">
- <c>erlang:phash2/1</c></seealso>,
- <seealso marker="erts:erlang#node/0">
- <c>erlang:node/0</c></seealso>,
- <seealso marker="erts:erlang#monotonic_time/0">
- <c>erlang:monotonic_time/0</c></seealso>, and
- <seealso marker="erts:erlang#unique_integer/0">
- <c>erlang:unique_integer/0</c></seealso>.</p>
+ <seemfa marker="erts:erlang#phash2/1">
+ <c>erlang:phash2/1</c></seemfa>,
+ <seemfa marker="erts:erlang#node/0">
+ <c>erlang:node/0</c></seemfa>,
+ <seemfa marker="erts:erlang#monotonic_time/0">
+ <c>erlang:monotonic_time/0</c></seemfa>, and
+ <seemfa marker="erts:erlang#unique_integer/0">
+ <c>erlang:unique_integer/0</c></seemfa>.</p>
</desc>
</func>
@@ -169,10 +169,10 @@ random:seed(erlang:phash2([node()]),
<c>random_seed</c> to remember the current seed.</p>
<p>If a process calls
- <seealso marker="#uniform/0"><c>uniform/0</c></seealso> or
- <seealso marker="#uniform/1"><c>uniform/1</c></seealso>
+ <seemfa marker="#uniform/0"><c>uniform/0</c></seemfa> or
+ <seemfa marker="#uniform/1"><c>uniform/1</c></seemfa>
without setting a seed first,
- <seealso marker="#seed/0"><c>seed/0</c></seealso>
+ <seemfa marker="#seed/0"><c>seed/0</c></seemfa>
is called automatically.</p>
<p>The implementation changed in Erlang/OTP R15. Upgrading to R15 breaks
diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml
index 4fd7a6b3d7..11041e63b2 100644
--- a/lib/stdlib/doc/src/re.xml
+++ b/lib/stdlib/doc/src/re.xml
@@ -40,7 +40,7 @@
<p>This module contains regular expression matching functions for
strings and binaries.</p>
- <p>The <seealso marker="#regexp_syntax">regular expression</seealso>
+ <p>The <seeerl marker="#regexp_syntax">regular expression</seeerl>
syntax and semantics resemble that of Perl.</p>
<p>The matching algorithms of the library are based on the
@@ -101,8 +101,8 @@
<p>Compiles a regular expression, with the syntax
described below, into an internal format to be used later as a
parameter to
- <seealso marker="#run/2"><c>run/2</c></seealso> and
- <seealso marker="#run/3"><c>run/3</c></seealso>.</p>
+ <seemfa marker="#run/2"><c>run/2</c></seemfa> and
+ <seemfa marker="#run/3"><c>run/3</c></seemfa>.</p>
<p>Compiling the regular expression before matching is useful if
the same expression is to be used in matching against multiple
subjects during the lifetime of the program. Compiling once and
@@ -281,8 +281,8 @@
the subject up to "A" and never realize that the (*COMMIT)
instruction is to have made the matching fail. This option is only
relevant if you use "start-of-pattern items", as discussed in
- section <seealso marker="#regexp_syntax_details">PCRE Regular Expression
- Details</seealso>.</p>
+ section <seeerl marker="#regexp_syntax_details">PCRE Regular Expression
+ Details</seeerl>.</p>
</item>
<tag><c>ucp</c></tag>
<item>
@@ -330,7 +330,7 @@
regardless of where the names are positioned in the regular
expression. The order of the names is the same as the order of
captured subexpressions if <c>{capture, all_names}</c> is specified as
- an option to <seealso marker="#run/3"><c>run/3</c></seealso>.
+ an option to <seemfa marker="#run/3"><c>run/3</c></seemfa>.
You can therefore create a name-to-value mapping from the result of
<c>run/3</c> like this:</p>
<code>
@@ -365,7 +365,7 @@
<p>Replaces the matched part of the <c><anno>Subject</anno></c> string
with the contents of <c><anno>Replacement</anno></c>.</p>
<p>The permissible options are the same as for
- <seealso marker="#run/3"><c>run/3</c></seealso>, except that option<c>
+ <seemfa marker="#run/3"><c>run/3</c></seemfa>, except that option<c>
capture</c> is not allowed. Instead a <c>{return,
<anno>ReturnType</anno>}</c> is present. The default return type is
<c>iodata</c>, constructed in a way to minimize copying. The
@@ -402,7 +402,7 @@ re:replace("abcd","c","[\\&amp;]",[{return,list}]).</code>
<code>
"ab[&amp;]d"</code>
<p>As with <c>run/3</c>, compilation errors raise the <c>badarg</c>
- exception. <seealso marker="#compile/2"><c>compile/2</c></seealso>
+ exception. <seemfa marker="#compile/2"><c>compile/2</c></seemfa>
can be used to get more information about the error.</p>
</desc>
</func>
@@ -420,8 +420,8 @@ re:replace("abcd","c","[\\&amp;]",[{return,list}]).</code>
<name name="run" arity="3" since=""/>
<fsummary>Match a subject against regular expression and capture
subpatterns.</fsummary>
- <type_desc variable="CompileOpt">See <seealso marker="#compile_options">
- <c>compile/2</c></seealso>.</type_desc>
+ <type_desc variable="CompileOpt">See <seeerl marker="#compile_options">
+ <c>compile/2</c></seeerl>.</type_desc>
<desc>
<p>Executes a regular expression matching, and returns
<c>match/{match, <anno>Captured</anno>}</c> or <c>nomatch</c>. The
@@ -638,8 +638,8 @@ a?b?</code>
before that (each recursive call is also a call, but not
conversely). Both limits can however be changed, either by
setting limits directly in the regular expression string (see
- section <seealso marker="#regexp_syntax_details">PCRE Regular
- Eexpression Details</seealso>) or by specifying options to
+ section <seeerl marker="#regexp_syntax_details">PCRE Regular
+ Eexpression Details</seeerl>) or by specifying options to
<c>run/3</c>.</p>
</item>
</taglist>
@@ -819,8 +819,8 @@ re:run("ABCabcdABC",".*(abcd).*",[]).</code>
as if a <c>list()</c> of all the names <em>in
alphabetical order</em> was specified. The list of all
names can also be retrieved with
- <seealso marker="#inspect/2">
- <c>inspect/2</c></seealso>.</p>
+ <seemfa marker="#inspect/2">
+ <c>inspect/2</c></seemfa>.</p>
</item>
<tag><c>first</c></tag>
<item>
@@ -902,10 +902,10 @@ re:run("ABCabcdABC",".*(?&lt;FOO&gt;abcd).*",[{capture,['FOO']}]).</code>
<p>Returns captured substrings as pairs of byte indexes
into the subject string and length of the matching string
in the subject (as if the subject string was flattened
- with <seealso marker="erts:erlang#iolist_to_binary/1">
- <c>erlang:iolist_to_binary/1</c></seealso> or
- <seealso marker="unicode#characters_to_binary/2">
- <c>unicode:characters_to_binary/2</c></seealso> before
+ with <seemfa marker="erts:erlang#iolist_to_binary/1">
+ <c>erlang:iolist_to_binary/1</c></seemfa> or
+ <seemfa marker="unicode#characters_to_binary/2">
+ <c>unicode:characters_to_binary/2</c></seemfa> before
matching). Notice that option <c>unicode</c> results in
<em>byte-oriented</em> indexes in a (possibly virtual)
<em>UTF-8 encoded</em> binary. A byte index tuple
@@ -926,8 +926,8 @@ re:run("ABCabcdABC",".*(?&lt;FOO&gt;abcd).*",[{capture,['FOO']}]).</code>
regardless of character encoding). In that case the
<c>list</c> capturing can result in the same types of
tuples that
- <seealso marker="unicode#characters_to_list/2">
- <c>unicode:characters_to_list/2</c></seealso> can return,
+ <seemfa marker="unicode#characters_to_list/2">
+ <c>unicode:characters_to_list/2</c></seemfa> can return,
namely three-tuples with tag <c>incomplete</c> or
<c>error</c>, the successfully converted characters and
the invalid UTF-8 tail of the conversion as a binary. The
@@ -987,7 +987,7 @@ re:run("cacb","c(a|b)",[global,{capture,[1],list}]).</code>
</item>
</taglist>
<p>For a descriptions of options only affecting the compilation step,
- see <seealso marker="#compile/2"><c>compile/2</c></seealso>.</p>
+ see <seemfa marker="#compile/2"><c>compile/2</c></seemfa>.</p>
</desc>
</func>
@@ -1003,15 +1003,15 @@ re:run("cacb","c(a|b)",[global,{capture,[1],list}]).</code>
<func>
<name name="split" arity="3" since=""/>
<fsummary>Split a string by tokens specified as a regular expression</fsummary>
- <type_desc variable="CompileOpt">See <seealso marker="#compile_options">
- <c>compile/2</c></seealso>.</type_desc>
+ <type_desc variable="CompileOpt">See <seeerl marker="#compile_options">
+ <c>compile/2</c></seeerl>.</type_desc>
<desc>
<p>Splits the input into parts by finding tokens according to the
regular expression supplied. The splitting is basically done by
running a global regular expression match and dividing the initial
string wherever a match occurs. The matching part of the string is
removed from the output.</p>
- <p>As in <seealso marker="#run/3"><c>run/3</c></seealso>, an <c>mp()</c>
+ <p>As in <seemfa marker="#run/3"><c>run/3</c></seemfa>, an <c>mp()</c>
compiled with option <c>unicode</c> requires
<c><anno>Subject</anno></c> to be a Unicode <c>charlist()</c>. If
compilation is done implicitly and the <c>unicode</c> compilation
@@ -1187,46 +1187,46 @@ re:split("Erlang","[lg]",[{return,list},{parts,4}]).</code>
<p>The reference material is divided into the following sections:</p>
<list type="bulleted">
- <item><seealso marker="#sect1">Special Start-of-Pattern Items</seealso>
+ <item><seeerl marker="#sect1">Special Start-of-Pattern Items</seeerl>
</item>
- <item><seealso marker="#sect2">Characters and Metacharacters</seealso>
+ <item><seeerl marker="#sect2">Characters and Metacharacters</seeerl>
</item>
- <item><seealso marker="#sect3">Backslash</seealso></item>
- <item><seealso marker="#sect4">Circumflex and Dollar</seealso></item>
- <item><seealso marker="#sect5">Full Stop (Period, Dot) and \N</seealso>
+ <item><seeerl marker="#sect3">Backslash</seeerl></item>
+ <item><seeerl marker="#sect4">Circumflex and Dollar</seeerl></item>
+ <item><seeerl marker="#sect5">Full Stop (Period, Dot) and \N</seeerl>
</item>
- <item><seealso marker="#sect6">Matching a Single Data Unit</seealso>
+ <item><seeerl marker="#sect6">Matching a Single Data Unit</seeerl>
</item>
- <item><seealso marker="#sect7">Square Brackets and Character
- Classes</seealso></item>
- <item><seealso marker="#sect8">Posix Character Classes</seealso></item>
- <item><seealso marker="#sect9">Vertical Bar</seealso></item>
- <item><seealso marker="#sect10">Internal Option Setting</seealso></item>
- <item><seealso marker="#sect11">Subpatterns</seealso></item>
- <item><seealso marker="#sect12">Duplicate Subpattern Numbers</seealso>
+ <item><seeerl marker="#sect7">Square Brackets and Character
+ Classes</seeerl></item>
+ <item><seeerl marker="#sect8">Posix Character Classes</seeerl></item>
+ <item><seeerl marker="#sect9">Vertical Bar</seeerl></item>
+ <item><seeerl marker="#sect10">Internal Option Setting</seeerl></item>
+ <item><seeerl marker="#sect11">Subpatterns</seeerl></item>
+ <item><seeerl marker="#sect12">Duplicate Subpattern Numbers</seeerl>
</item>
- <item><seealso marker="#sect13">Named Subpatterns</seealso></item>
- <item><seealso marker="#sect14">Repetition</seealso></item>
- <item><seealso marker="#sect15">Atomic Grouping and Possessive
- Quantifiers</seealso></item>
- <item><seealso marker="#sect16">Back References</seealso></item>
- <item><seealso marker="#sect17">Assertions</seealso></item>
- <item><seealso marker="#sect18">Conditional Subpatterns</seealso></item>
- <item><seealso marker="#sect19">Comments</seealso></item>
- <item><seealso marker="#sect20">Recursive Patterns</seealso></item>
- <item><seealso marker="#sect21">Subpatterns as Subroutines</seealso>
+ <item><seeerl marker="#sect13">Named Subpatterns</seeerl></item>
+ <item><seeerl marker="#sect14">Repetition</seeerl></item>
+ <item><seeerl marker="#sect15">Atomic Grouping and Possessive
+ Quantifiers</seeerl></item>
+ <item><seeerl marker="#sect16">Back References</seeerl></item>
+ <item><seeerl marker="#sect17">Assertions</seeerl></item>
+ <item><seeerl marker="#sect18">Conditional Subpatterns</seeerl></item>
+ <item><seeerl marker="#sect19">Comments</seeerl></item>
+ <item><seeerl marker="#sect20">Recursive Patterns</seeerl></item>
+ <item><seeerl marker="#sect21">Subpatterns as Subroutines</seeerl>
</item>
- <item><seealso marker="#sect22">Oniguruma Subroutine Syntax</seealso>
+ <item><seeerl marker="#sect22">Oniguruma Subroutine Syntax</seeerl>
</item>
- <item><seealso marker="#sect23">Backtracking Control</seealso></item>
+ <item><seeerl marker="#sect23">Backtracking Control</seeerl></item>
</list>
</section>
<section>
<marker id="sect1"></marker>
<title>Special Start-of-Pattern Items</title>
- <p>Some options that can be passed to <seealso marker="#compile/2">
- <c>compile/2</c></seealso> can also be set by special items at the start
+ <p>Some options that can be passed to <seemfa marker="#compile/2">
+ <c>compile/2</c></seemfa> can also be set by special items at the start
of a pattern. These are not Perl-compatible, but are provided to make
these options accessible to pattern writers who are not able to change
the program that processes the pattern. Any number of these items can
@@ -1236,8 +1236,8 @@ re:split("Erlang","[lg]",[{return,list},{parts,4}]).</code>
<p><em>UTF Support</em></p>
<p>Unicode support is basically UTF-8 based. To use Unicode characters, you
- either call <seealso marker="#compile/2"><c>compile/2</c></seealso> or
- <seealso marker="#run/3"><c>run/3</c></seealso> with option
+ either call <seemfa marker="#compile/2"><c>compile/2</c></seemfa> or
+ <seemfa marker="#run/3"><c>run/3</c></seemfa> with option
<c>unicode</c>, or the pattern must start with one of these special
sequences:</p>
@@ -1250,7 +1250,7 @@ re:split("Erlang","[lg]",[{return,list},{parts,4}]).</code>
lists to UTF-8 is not performed by the <c>re</c> functions. Therefore,
using these sequences is not recommended.
Add option <c>unicode</c> when running
- <seealso marker="#compile/2"><c>compile/2</c></seealso> instead.</p>
+ <seemfa marker="#compile/2"><c>compile/2</c></seemfa> instead.</p>
<p>Some applications that allow their users to supply patterns can wish to
restrict them to non-UTF data for security reasons. If option
@@ -1297,7 +1297,7 @@ re:split("Erlang","[lg]",[{return,list},{parts,4}]).</code>
</taglist>
<p>These override the default and the options specified to
- <seealso marker="#compile/2"><c>compile/2</c></seealso>. For example, the
+ <seemfa marker="#compile/2"><c>compile/2</c></seemfa>. For example, the
following pattern changes the convention to CR:</p>
<code>
@@ -1312,13 +1312,13 @@ re:split("Erlang","[lg]",[{return,list},{parts,4}]).</code>
affect what the \R escape sequence matches. By default, this is any
Unicode newline sequence, for Perl compatibility. However, this can be
changed; see the description of \R in section
- <seealso marker="#newline_sequences">Newline Sequences</seealso>. A change
+ <seeerl marker="#newline_sequences">Newline Sequences</seeerl>. A change
of the \R setting can be combined with a change of the newline
convention.</p>
<p><em>Setting Match and Recursion Limits</em></p>
- <p>The caller of <seealso marker="#run/3"><c>run/3</c></seealso> can set a
+ <p>The caller of <seemfa marker="#run/3"><c>run/3</c></seemfa> can set a
limit on the number of times the internal match() function is called and
on the maximum depth of recursive calls. These facilities are provided to
catch runaway matches that are provoked by patterns with huge matching
@@ -2184,7 +2184,7 @@ foo\Kbar</code>
they are independent of multiline mode. These three assertions are not
affected by options <c>notbol</c> or <c>noteol</c>, which affect only the
behavior of the circumflex and dollar metacharacters. However, if argument
- <c>startoffset</c> of <seealso marker="#run/3"><c>run/3</c></seealso> is
+ <c>startoffset</c> of <seemfa marker="#run/3"><c>run/3</c></seemfa> is
non-zero, indicating that matching is to start at a point other than the
beginning of the subject, \A can never match. The difference between \Z
and \z is that \Z matches before a newline at the end of the string and
@@ -2218,7 +2218,7 @@ foo\Kbar</code>
<p>Outside a character class, in the default matching mode, the circumflex
character is an assertion that is true only if the current matching point
is at the start of the subject string. If argument <c>startoffset</c> of
- <seealso marker="#run/3"><c>run/3</c></seealso> is non-zero, circumflex
+ <seemfa marker="#run/3"><c>run/3</c></seemfa> is non-zero, circumflex
can never match if option <c>multiline</c> is unset. Inside a character
class, circumflex has an entirely different meaning (see below).</p>
@@ -2321,8 +2321,8 @@ foo\Kbar</code>
(?=[\x{10000}-\x{1fffff}])(\C)(\C)(\C)(\C))</code>
<p>A group that starts with (?| resets the capturing parentheses numbers in
- each alternative (see section <seealso marker="#sect12">Duplicate
- Subpattern Numbers</seealso>). The assertions at the start of each branch
+ each alternative (see section <seeerl marker="#sect12">Duplicate
+ Subpattern Numbers</seeerl>). The assertions at the start of each branch
check the next UTF-8 character for values whose encoding uses 1, 2, 3, or
4 bytes, respectively. The individual bytes of the character are then
captured by the appropriate number of groups.</p>
@@ -2416,8 +2416,8 @@ foo\Kbar</code>
digit. In UTF modes, option <c>ucp</c> affects the meanings of \d, \s, \w
and their uppercase partners, just as it does when they appear outside a
character class, as described in section
- <seealso marker="#generic_character_types">Generic Character
- Types</seealso> earlier. The escape sequence \b has a different meaning
+ <seeerl marker="#generic_character_types">Generic Character
+ Types</seeerl> earlier. The escape sequence \b has a different meaning
inside a character class; it matches the backspace character. The
sequences \B, \N, \R, and \X are not special inside a character class.
Like any other unrecognized escape sequences, they are treated as the
@@ -2572,7 +2572,7 @@ gilbert|sullivan</code>
permitted (matching the empty string). The matching process tries each
alternative in turn, from left to right, and the first that succeeds is
used. If the alternatives are within a subpattern (defined in section
- <seealso marker="#sect11">Subpatterns</seealso>), "succeeds" means
+ <seeerl marker="#sect11">Subpatterns</seeerl>), "succeeds" means
matching the remaining main pattern and the alternative in the
subpattern.</p>
</section>
@@ -2607,7 +2607,7 @@ gilbert|sullivan</code>
subpattern parentheses), the change applies to the remainder of the
pattern that follows.</p>
<p>An option change within a subpattern (see section
- <seealso marker="#sect11">Subpatterns</seealso>) affects only that part of
+ <seeerl marker="#sect11">Subpatterns</seeerl>) affects only that part of
the subpattern that follows it. So, the following matches abc and aBc and
no other strings (assuming <c>caseless</c> is not used):</p>
@@ -2632,8 +2632,8 @@ gilbert|sullivan</code>
compiling or matching functions are called. Sometimes the pattern can
contain special leading sequences, such as (*CRLF), to override what
the application has set or what has been defaulted. Details are provided
- in section <seealso marker="#newline_sequences">
- Newline Sequences</seealso> earlier.</p>
+ in section <seeerl marker="#newline_sequences">
+ Newline Sequences</seeerl> earlier.</p>
<p>The (*UTF8) and (*UCP) leading sequences can be used to set UTF and
Unicode property modes. They are equivalent to setting options
<c>unicode</c> and <c>ucp</c>, respectively. The (*UTF) sequence is a
@@ -2664,7 +2664,7 @@ cat(aract|erpillar|)</code>
<p>It sets up the subpattern as a capturing subpattern. That is, when
the complete pattern matches, that portion of the subject string that
matched the subpattern is passed back to the caller through the
- return value of <seealso marker="#run/3"><c>run/3</c></seealso>.</p>
+ return value of <seemfa marker="#run/3"><c>run/3</c></seemfa>.</p>
</item>
</taglist>
@@ -2776,8 +2776,8 @@ the ((?:red|white) (king|queen))</code>
<p>Names consist of up to 32 alphanumeric characters and underscores, but
must start with a non-digit. Named capturing parentheses are still allocated
numbers as well as names, exactly as if the names were not present.
- The <c>capture</c> specification to <seealso marker="#run/3">
- <c>run/3</c></seealso> can use named values if they are present in the
+ The <c>capture</c> specification to <seemfa marker="#run/3">
+ <c>run/3</c></seemfa> can use named values if they are present in the
regular expression.</p>
<p>By default, a name must be unique within a pattern, but this constraint
@@ -2803,7 +2803,7 @@ the ((?:red|white) (king|queen))</code>
<p>For capturing named subpatterns which names are not unique, the first
matching occurrence (counted from left to right in the subject) is
- returned from <seealso marker="#run/3"><c>run/3</c></seealso>, if the name
+ returned from <seemfa marker="#run/3"><c>run/3</c></seemfa>, if the name
is specified in the <c>values</c> part of the <c>capture</c> statement.
The <c>all_names</c> capturing value matches all the names in the same
way.</p>
@@ -2875,8 +2875,8 @@ z{2,4}</code>
<p>The quantifier {0} is permitted, causing the expression to behave as if
the previous item and the quantifier were not present. This can be useful
for subpatterns that are referenced as subroutines from elsewhere in the
- pattern (but see also section <seealso marker="#defining_subpatterns">
- Defining Subpatterns for Use by Reference Only</seealso>). Items other
+ pattern (but see also section <seeerl marker="#defining_subpatterns">
+ Defining Subpatterns for Use by Reference Only</seeerl>). Items other
than subpatterns that have a {0} quantifier are omitted from the compiled
pattern.</p>
@@ -3125,8 +3125,8 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</code>
subpattern whose number is 10 or more using this syntax, as a sequence
such as \50 is interpreted as a character defined in octal. For more
details of the handling of digits following a backslash, see section
- <seealso marker="#non_printing_characters">Non-Printing
- Characters</seealso> earlier. There is no such problem when named
+ <seeerl marker="#non_printing_characters">Non-Printing
+ Characters</seeerl> earlier. There is no such problem when named
parentheses are used. A back reference to any subpattern is possible
using named parentheses (see below).</p>
@@ -3156,8 +3156,8 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</code>
<p>A back reference matches whatever matched the capturing subpattern in the
current subject string, rather than anything matching the subpattern
- itself (section <seealso marker="#sect21">Subpattern as
- Subroutines</seealso> describes a way of doing that). So, the
+ itself (section <seeerl marker="#sect21">Subpattern as
+ Subroutines</seeerl> describes a way of doing that). So, the
following pattern matches "sense and sensibility" and "response and
responsibility", but not "sense and responsibility":</p>
@@ -3202,7 +3202,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</code>
number. If the pattern continues with a digit character, some delimiter
must be used to terminate the back reference. If option <c>extended</c> is
set, this can be whitespace. Otherwise an empty comment (see section
- <seealso marker="#sect19">Comments</seealso>) can be used.</p>
+ <seeerl marker="#sect19">Comments</seeerl>) can be used.</p>
<p><em>Recursive Back References</em></p>
@@ -3463,8 +3463,8 @@ abcd$</code>
<p>If the text between the parentheses consists of a sequence of digits,
the condition is true if a capturing subpattern of that number has
previously matched. If more than one capturing subpattern with the same
- number exists (see section <seealso marker="#sect12">
- Duplicate Subpattern Numbers</seealso> earlier), the condition is true if
+ number exists (see section <seeerl marker="#sect12">
+ Duplicate Subpattern Numbers</seeerl> earlier), the condition is true if
any of them have matched. An alternative notation is to precede the
digits with a plus or minus sign. In this case, the subpattern number is
relative rather than absolute. The most recently opened parentheses can be
@@ -3594,8 +3594,8 @@ abcd$</code>
character or character sequence in the pattern. Which characters are
interpreted as newlines is controlled by the options passed to a
compiling function or by a special sequence at the start of the pattern,
- as described in section <seealso marker="#newline_conventions">
- Newline Conventions</seealso> earlier.</p>
+ as described in section <seeerl marker="#newline_conventions">
+ Newline Conventions</seeerl> earlier.</p>
<p>Notice that the end of this type of comment is a literal newline sequence
in the pattern; escape sequences that happen to represent a newline do not
@@ -3929,8 +3929,8 @@ $re = qr{\( (?: (?&gt;[^()]+) | (?p{$re}) )* \)}x;</code>
running of a match, any included backtracking verbs are not processed.
processed. You can suppress the start-of-match optimizations by setting
option <c>no_start_optimize</c> when calling
- <seealso marker="#compile/2"><c>compile/2</c></seealso> or
- <seealso marker="#run/3"><c>run/3</c></seealso>, or by starting the
+ <seemfa marker="#compile/2"><c>compile/2</c></seemfa> or
+ <seemfa marker="#run/3"><c>run/3</c></seemfa>, or by starting the
pattern with (*NO_START_OPT).</p>
<p>Experiments with Perl suggest that it too has similar optimizations,
@@ -3979,7 +3979,7 @@ A((?:A|B(*ACCEPT)|C)D)</code>
<note>
<p>In Erlang, there is no interface to retrieve a mark with
- <seealso marker="#run/2"><c>run/2,3</c></seealso>, so only the secondary
+ <seemfa marker="#run/2"><c>run/2,3</c></seemfa>, so only the secondary
purpose is relevant to the Erlang programmer.</p>
<p>The rest of this section is therefore deliberately not adapted for
@@ -4061,7 +4061,7 @@ No match, mark = B</code>
(*COMMIT)</code>
<p>If (*COMMIT) is the only backtracking verb that is encountered, once it
- has been passed, <seealso marker="#run/2"><c>run/2,3</c></seealso> is
+ has been passed, <seemfa marker="#run/2"><c>run/2,3</c></seemfa> is
committed to find a match at the current starting point, or not at all,
for example:</p>
diff --git a/lib/stdlib/doc/src/ref_man.xml b/lib/stdlib/doc/src/ref_man.xml
index 8d61833d1f..d12f537b07 100644
--- a/lib/stdlib/doc/src/ref_man.xml
+++ b/lib/stdlib/doc/src/ref_man.xml
@@ -84,6 +84,7 @@
<xi:include href="sets.xml"/>
<xi:include href="shell.xml"/>
<xi:include href="shell_default.xml"/>
+ <xi:include href="shell_docs.xml"/>
<xi:include href="slave.xml"/>
<xi:include href="sofs.xml"/>
<xi:include href="string.xml"/>
diff --git a/lib/stdlib/doc/src/sets.xml b/lib/stdlib/doc/src/sets.xml
index 07ce41b7a7..291425c35b 100644
--- a/lib/stdlib/doc/src/sets.xml
+++ b/lib/stdlib/doc/src/sets.xml
@@ -39,7 +39,7 @@
The representation of a set is undefined.</p>
<p>This module provides the same interface as the
- <seealso marker="ordsets"><c>ordsets(3)</c></seealso> module
+ <seeerl marker="ordsets"><c>ordsets(3)</c></seeerl> module
but with an undefined representation. One difference is
that while this module considers two elements as different if they
do not match (<c>=:=</c>), <c>ordsets</c> considers two elements as
@@ -50,7 +50,7 @@
<datatype>
<name name="set" n_vars="1"/>
<desc><p>As returned by
- <seealso marker="#new/0"><c>new/0</c></seealso>.</p></desc>
+ <seemfa marker="#new/0"><c>new/0</c></seemfa>.</p></desc>
</datatype>
<datatype>
<name name="set" n_vars="0"/>
@@ -220,8 +220,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="gb_sets"><c>gb_sets(3)</c></seealso>,
- <seealso marker="ordsets"><c>ordsets(3)</c></seealso></p>
+ <p><seeerl marker="gb_sets"><c>gb_sets(3)</c></seeerl>,
+ <seeerl marker="ordsets"><c>ordsets(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml
index d0d796655c..62ce63ec33 100644
--- a/lib/stdlib/doc/src/shell.xml
+++ b/lib/stdlib/doc/src/shell.xml
@@ -44,8 +44,8 @@
values, which can then be incorporated in later commands.
How many commands and results to save can be determined by the user,
either interactively, by calling
- <seealso marker="#history/1"><c>history/1</c></seealso> and
- <seealso marker="#results/1"><c>results/1</c></seealso>,
+ <seemfa marker="#history/1"><c>history/1</c></seemfa> and
+ <seemfa marker="#results/1"><c>results/1</c></seemfa>,
or by setting the application configuration
parameters <c>shell_history_length</c> and
<c>shell_saved_results</c> for the STDLIB application.</p>
@@ -53,8 +53,8 @@
<p>The shell uses a helper process for evaluating commands
to protect the history mechanism from exceptions. By
default the evaluator process is killed when an exception
- occurs, but by calling <seealso marker="#catch_exception/1">
- <c>catch_exception/1</c></seealso> or by
+ occurs, but by calling <seemfa marker="#catch_exception/1">
+ <c>catch_exception/1</c></seemfa> or by
setting the application configuration parameter
<c>shell_catch_exception</c> for the STDLIB application
this behavior can be changed. See also the example below.</p>
@@ -119,6 +119,9 @@
<section>
<title>Shell Commands</title>
+ <p>The commands below are the built-in shell commands that are always
+ available. In most system the commands listed in the <seeerl marker="c">c(3)</seeerl>
+ module are also available in the shell.</p>
<taglist>
<tag><c>b()</c></tag>
<item>
@@ -232,7 +235,7 @@
<p>Reads record definitions from files. Existing
definitions of any of the record names read are replaced.
<c>Wildcard</c> is a wildcard string as defined in
- <seealso marker="filelib"><c>filelib(3)</c></seealso>,
+ <seeerl marker="filelib"><c>filelib(3)</c></seeerl>,
but not an atom.</p>
</item>
<tag><c>rr(WildcardOrModule, RecordNames)</c></tag>
@@ -280,8 +283,8 @@ Eshell V5.3 (abort with ^G)
{4,abcd}</pre>
<p>Command 3 builds the tuple <c>Descriptor</c>, evaluating the BIF
- <seealso marker="erts:erlang#list_to_atom/1"><c>list_to_atom/1</c>
- </seealso>.</p>
+ <seemfa marker="erts:erlang#list_to_atom/1"><c>list_to_atom/1</c>
+ </seemfa>.</p>
<pre>
4> <input>L.</input>
@@ -740,8 +743,8 @@ q - quit erlang
<p>If you want an Erlang node to have a remote job active from the start
(rather than the default local job), start Erlang with flag
- <c>-remsh</c>, for example,
- <c>erl -sname this_node -remsh other_node@other_host</c></p>
+ <seecom marker="erts:erl#remsh"><c>-remsh</c></seecom>, for example,
+ <c>erl -remsh other_node@other_host</c></p>
</section>
<section>
@@ -782,7 +785,7 @@ q - quit erlang
<p>These callback functions are called from local and
non-local evaluation function handlers, described in the
- <seealso marker="erl_eval"><c>erl_eval</c></seealso>
+ <seeerl marker="erl_eval"><c>erl_eval</c></seeerl>
manual page. (Arguments in <c>ArgList</c> are evaluated before the
callback functions are called.)</p>
@@ -805,8 +808,8 @@ q - quit erlang
</item>
<item>
<p>From a normal shell session, call function
- <seealso marker="#start_restricted/1">
- <c>start_restricted/1</c></seealso>. This exits the current evaluator
+ <seemfa marker="#start_restricted/1">
+ <c>start_restricted/1</c></seemfa>. This exits the current evaluator
and starts a new one in restricted mode.</p>
</item>
</list>
@@ -842,8 +845,8 @@ q - quit erlang
</item>
<item>
<p>If the restricted shell is activated using
- <seealso marker="#start_restricted/1">
- <c>start_restricted/1</c></seealso> and the callback module cannot
+ <seemfa marker="#start_restricted/1">
+ <c>start_restricted/1</c></seemfa> and the callback module cannot
be loaded, an error report is sent to the error logger and the call
returns <c>{error,Reason}</c>.</p>
</item>
@@ -855,8 +858,8 @@ q - quit erlang
<p>The default shell prompt function displays the name of the node
(if the node can be part of a distributed system) and the
current command number. The user can customize the prompt
- function by calling <seealso marker="#prompt_func/1">
- <c>prompt_func/1</c></seealso> or by setting application
+ function by calling <seemfa marker="#prompt_func/1">
+ <c>prompt_func/1</c></seemfa> or by setting application
configuration parameter <c>shell_prompt_func</c> for the
STDLIB application.</p>
diff --git a/lib/stdlib/doc/src/shell_default.xml b/lib/stdlib/doc/src/shell_default.xml
index 75bf89ba8d..18af8b7273 100644
--- a/lib/stdlib/doc/src/shell_default.xml
+++ b/lib/stdlib/doc/src/shell_default.xml
@@ -48,7 +48,7 @@
2> <input>c(foo).</input>
{ok, foo}</pre>
- <p>In command one, module <seealso marker="lists"><c>lists</c></seealso> is
+ <p>In command one, module <seeerl marker="lists"><c>lists</c></seeerl> is
called. In command two, no module name is specified. The shell searches
module <c>user_default</c> followed by module <c>shell_default</c> for
function <c>c/1</c>.</p>
diff --git a/lib/stdlib/doc/src/shell_docs.xml b/lib/stdlib/doc/src/shell_docs.xml
new file mode 100644
index 0000000000..bd3e3600f7
--- /dev/null
+++ b/lib/stdlib/doc/src/shell_docs.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2020</year><year>2020</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>shell_docs</title>
+ <prepared>Lukas Larsson</prepared>
+ <responsible></responsible>
+ <docno>1</docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2020-02-19</date>
+ <rev>A</rev>
+ <file>shell_docs.xml</file>
+ </header>
+ <module since="OTP 23.0">shell_docs</module>
+ <modulesummary>Functions used to render EEP-48 style documentation for a shell.</modulesummary>
+ <description>
+ <p>This module can be used to render function and type documentation
+ to be printed in a shell. It can only render EEP-48 documentation of the format
+ <c>application/erlang+html</c>. For more information about this format see
+ <seeguide marker="erl_docgen:doc_storage">Documentation Storage</seeguide>
+ in Erl_Docgen's User's Guide.
+ </p>
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="docs_v1"/>
+ <desc>
+ <p>
+ The record holding EEP-48 documentation for a module.
+ You can use <seemfa marker="kernel:code#get_doc/1">code:get_doc/1</seemfa>
+ to fetch this information from a module.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="config"/>
+ <desc>
+ <p>
+ The configuration of how the documentation should be rendered.
+ </p>
+ <taglist>
+ <tag>encoding</tag>
+ <item>
+ Configure the encoding that should be used by
+ the renderer for graphical details such as bullet-points.
+ By default <c>shell_docs</c> uses the value returned
+ by <seemfa marker="io#getopts/0"><c>io:getopts()</c></seemfa>.</item>
+ <tag>ansi</tag>
+ <item>
+ Configure whether <url href="https://en.wikipedia.org/wiki/ANSI_escape_code">
+ ansi escape codes</url> should be used to
+ render graphical details such as bold and underscore. By default
+ <c>shell_docs</c> will try to determine if the receiving shell
+ supports ansi escape codes. It is possible to override
+ the automated check by setting the kernel configuration parameter
+ <c>shell_docs_ansi</c> to a <c>boolean()</c> value.</item>
+ <tag>columns</tag>
+ <item>
+ Configure how wide the target documentation should be rendered.
+ By default <c>shell_docs</c> used the value returned by
+ <seemfa marker="io#columns/0"><c>io:columns()</c></seemfa>.
+ </item>
+ </taglist>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="chunk_element_block_type"/>
+ <name name="chunk_element_inline_type"/>
+ <name name="chunk_element_type"/>
+ <desc>
+ <p>
+ The HTML tags allowed in <c>application/erlang+html</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="chunk_element_attr"/>
+ <name name="chunk_element_attrs"/>
+ <name name="chunk_element"/>
+ <name name="chunk_elements"/>
+ <desc>
+ </desc>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+
+ <func>
+ <name name="render" arity="2" since="OTP 23.0"/>
+ <name name="render" arity="3" clause_i="1" since="OTP 23.2"/>
+ <name name="render" arity="3" clause_i="2" since="OTP 23.0"/>
+ <name name="render" arity="4" clause_i="1" since="OTP 23.2"/>
+ <name name="render" arity="4" clause_i="2" since="OTP 23.0"/>
+ <name name="render" arity="5" since="OTP 23.2"/>
+ <fsummary>Render the documentation for a module or function.</fsummary>
+ <desc>
+ <p>Render the documentation for a module or function.</p>
+ </desc>
+ </func>
+ <func>
+ <name name="render_type" arity="2" since="OTP 23.0"/>
+ <name name="render_type" arity="3" clause_i="1" since="OTP 23.2"/>
+ <name name="render_type" arity="3" clause_i="2" since="OTP 23.0"/>
+ <name name="render_type" arity="4" clause_i="1" since="OTP 23.2"/>
+ <name name="render_type" arity="4" clause_i="2" since="OTP 23.0"/>
+ <name name="render_type" arity="5" since="OTP 23.2"/>
+ <fsummary>Render the documentation of a type in a module.</fsummary>
+ <desc>
+ <p>Render the documentation of a type in a module.</p>
+ </desc>
+ </func>
+ <func>
+ <name name="render_callback" arity="2" since="OTP 23.0"/>
+ <name name="render_callback" arity="3" clause_i="1" since="OTP 23.2"/>
+ <name name="render_callback" arity="3" clause_i="2" since="OTP 23.0"/>
+ <name name="render_callback" arity="4" clause_i="1" since="OTP 23.2"/>
+ <name name="render_callback" arity="4" clause_i="2" since="OTP 23.0"/>
+ <name name="render_callback" arity="5" since="OTP 23.2"/>
+ <fsummary>Render the documentation of a callback in a module.</fsummary>
+ <desc>
+ <p>Render the documentation of a callback in a module.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="validate" arity="1" since="OTP 23.0"/>
+ <fsummary>Validate the documentation</fsummary>
+ <desc>
+ <p>This function can be used to do a basic validation of
+ the doc content of <c>application/erlang+html</c> format.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="normalize" arity="1" since="OTP 23.0"/>
+ <fsummary>Normalize the documentation</fsummary>
+ <desc>
+ <p>This function can be used to do whitespace normalization
+ of <c>application/erlang+html</c> documentation.</p>
+ </desc>
+ </func>
+
+ </funcs>
+</erlref>
diff --git a/lib/stdlib/doc/src/slave.xml b/lib/stdlib/doc/src/slave.xml
index f60e7e1d2f..92bd676691 100644
--- a/lib/stdlib/doc/src/slave.xml
+++ b/lib/stdlib/doc/src/slave.xml
@@ -51,7 +51,7 @@
<p>An alternative to the <c>ssh</c> program can be specified on
the command line to
- <seealso marker="erts:erl"><c>erl(1)</c></seealso> as follows:</p>
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom> as follows:</p>
<pre>
-rsh Program</pre>
@@ -140,7 +140,7 @@ rpc:call(N, slave, pseudo, [node(), [pxw_server]]).</code>
<p>Argument <c><anno>Args</anno></c> is used to set <c>erl</c>
command-line arguments. If provided, it is passed to the new
node and can be used for a variety of purposes; see
- <seealso marker="erts:erl"><c>erl(1)</c></seealso>.</p>
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom>.</p>
<p>As an example, suppose that you want to start a slave node at
host <c>H</c> with node name <c>Name@H</c> and
want the slave node to have the following properties:</p>
@@ -197,7 +197,7 @@ slave:start(H, Name, Arg).</code>
executing process. If that process terminates, the slave node
also terminates.</p>
<p>For a description of arguments and return values, see
- <seealso marker="#start/1"><c>start/1,2,3</c></seealso>.</p>
+ <seemfa marker="#start/1"><c>start/1,2,3</c></seemfa>.</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/sofs.xml b/lib/stdlib/doc/src/sofs.xml
index c2c6675258..9b96c4d39a 100644
--- a/lib/stdlib/doc/src/sofs.xml
+++ b/lib/stdlib/doc/src/sofs.xml
@@ -273,7 +273,7 @@
<p>If S is an element (T,&nbsp;X) of Sets, then T is a
<marker id="valid_type"></marker><em>valid type</em> of X, T is the
type of S, and X is the external set of S.
- <seealso marker="#from_term/2"><c>from_term/2</c></seealso> creates a
+ <seemfa marker="#from_term/2"><c>from_term/2</c></seemfa> creates a
set from a type and an Erlang term turned into an external set.</p>
<p>The sets represented by Sets are the elements of the range of
function Set from Sets to Erlang terms and sets of Erlang terms:</p>
@@ -288,7 +288,7 @@
</list>
<p>When there is no risk of confusion, elements of Sets are identified
with the sets they represent. For example, if U is the result of
- calling <seealso marker="#union/2"><c>union/2</c></seealso> with S1
+ calling <seemfa marker="#union/2"><c>union/2</c></seemfa> with S1
and S2 as arguments, then U is said to be the union of S1 and S2.
A more precise formulation is that Set(U) is the union of Set(S1)
and Set(S2).</p>
@@ -300,8 +300,8 @@
product of two sets R and S, and recall that the relative
product of R and S is defined if R is a binary relation to Y and
S is a binary relation from Y. The function that implements the
- relative product, <seealso marker="#relative_product/2">
- <c>relative_product/2</c></seealso>, checks
+ relative product, <seemfa marker="#relative_product/2">
+ <c>relative_product/2</c></seemfa>, checks
that the arguments represent binary relations by matching [{A,B}]
against the type of the first argument (Arg1 say), and [{C,D}]
against the type of the second argument (Arg2 say). The fact
@@ -316,13 +316,13 @@
set.</p>
<p>A few functions of this module
- (<seealso marker="#drestriction/3"><c>drestriction/3</c></seealso>,
- <seealso marker="#family_projection/2"><c>family_projection/2</c></seealso>,
- <seealso marker="#partition/2"><c>partition/2</c></seealso>,
- <seealso marker="#partition_family/2"><c>partition_family/2</c></seealso>,
- <seealso marker="#projection/2"><c>projection/2</c></seealso>,
- <seealso marker="#restriction/3"><c>restriction/3</c></seealso>,
- <seealso marker="#substitution/2"><c>substitution/2</c></seealso>)
+ (<seemfa marker="#drestriction/3"><c>drestriction/3</c></seemfa>,
+ <seemfa marker="#family_projection/2"><c>family_projection/2</c></seemfa>,
+ <seemfa marker="#partition/2"><c>partition/2</c></seemfa>,
+ <seemfa marker="#partition_family/2"><c>partition_family/2</c></seemfa>,
+ <seemfa marker="#projection/2"><c>projection/2</c></seemfa>,
+ <seemfa marker="#restriction/3"><c>restriction/3</c></seemfa>,
+ <seemfa marker="#substitution/2"><c>substitution/2</c></seemfa>)
accept an Erlang
function as a means to modify each element of a given unordered
set. <marker id="set_fun"></marker>Such a function, called
@@ -377,12 +377,12 @@ fun(S) -> sofs:partition(1, S) end
the execution time is in the worst case proportional to the sum
of the sizes of the input arguments and the returned value. A
few functions execute in constant time:
- <seealso marker="#from_external/2"><c>from_external/2</c></seealso>,
- <seealso marker="#is_empty_set/1"><c>is_empty_set/1</c></seealso>,
- <seealso marker="#is_set/1"><c>is_set/1</c></seealso>,
- <seealso marker="#is_sofs_set/1"><c>is_sofs_set/1</c></seealso>,
- <seealso marker="#to_external/1"><c>to_external/1</c></seealso>
- <seealso marker="#type/1"><c>type/1</c></seealso>.</p>
+ <seemfa marker="#from_external/2"><c>from_external/2</c></seemfa>,
+ <seemfa marker="#is_empty_set/1"><c>is_empty_set/1</c></seemfa>,
+ <seemfa marker="#is_set/1"><c>is_set/1</c></seemfa>,
+ <seemfa marker="#is_sofs_set/1"><c>is_sofs_set/1</c></seemfa>,
+ <seemfa marker="#to_external/1"><c>to_external/1</c></seemfa>
+ <seemfa marker="#type/1"><c>type/1</c></seemfa>.</p>
<p>The functions of this module exit the process with a
<c>badarg</c>, <c>bad_function</c>, or <c>type_mismatch</c>
@@ -399,53 +399,53 @@ fun(S) -> sofs:partition(1, S) end
</datatype>
<datatype>
<name name="binary_relation"></name>
- <desc><p>A <seealso marker="#binary_relation">binary
- relation</seealso>.</p></desc>
+ <desc><p>A <seeerl marker="#binary_relation">binary
+ relation</seeerl>.</p></desc>
</datatype>
<datatype>
<name name="external_set"></name>
- <desc><p>An <seealso marker="#external_set">external
- set</seealso>.</p></desc>
+ <desc><p>An <seeerl marker="#external_set">external
+ set</seeerl>.</p></desc>
</datatype>
<datatype>
<name name="family"></name>
- <desc><p>A <seealso marker="#family">family</seealso> (of subsets).</p>
+ <desc><p>A <seeerl marker="#family">family</seeerl> (of subsets).</p>
</desc>
</datatype>
<datatype>
<name name="a_function"></name>
- <desc><p>A <seealso marker="#function">function</seealso>.</p></desc>
+ <desc><p>A <seeerl marker="#function">function</seeerl>.</p></desc>
</datatype>
<datatype>
<name name="ordset"></name>
- <desc><p>An <seealso marker="#sets_definition">ordered
- set</seealso>.</p></desc>
+ <desc><p>An <seeerl marker="#sets_definition">ordered
+ set</seeerl>.</p></desc>
</datatype>
<datatype>
<name name="relation"></name>
- <desc><p>An <seealso marker="#n_ary_relation">n-ary relation</seealso>.
+ <desc><p>An <seeerl marker="#n_ary_relation">n-ary relation</seeerl>.
</p></desc>
</datatype>
<datatype>
<name name="a_set"></name>
- <desc><p>An <seealso marker="#sets_definition">unordered
- set</seealso>.</p></desc>
+ <desc><p>An <seeerl marker="#sets_definition">unordered
+ set</seeerl>.</p></desc>
</datatype>
<datatype>
<name name="set_of_sets"></name>
- <desc><p>An <seealso marker="#sets_definition">unordered
- set</seealso> of unordered sets.</p></desc>
+ <desc><p>An <seeerl marker="#sets_definition">unordered
+ set</seeerl> of unordered sets.</p></desc>
</datatype>
<datatype>
<name name="set_fun"></name>
- <desc><p>A <seealso marker="#set_fun">SetFun</seealso>.</p></desc>
+ <desc><p>A <seeerl marker="#set_fun">SetFun</seeerl>.</p></desc>
</datatype>
<datatype>
<name name="spec_fun"></name>
</datatype>
<datatype>
<name name="type"></name>
- <desc><p>A <seealso marker="#type">type</seealso>.</p></desc>
+ <desc><p>A <seeerl marker="#type">type</seeerl>.</p></desc>
</datatype>
<datatype>
<!-- Parameterized opaque types are NYI: -->
@@ -460,10 +460,10 @@ fun(S) -> sofs:partition(1, S) end
<name name="a_function" arity="2" since=""/>
<fsummary>Create a function.</fsummary>
<desc>
- <p>Creates a <seealso marker="#function">function</seealso>.
+ <p>Creates a <seeerl marker="#function">function</seeerl>.
<c>a_function(F,&nbsp;T)</c> is equivalent to
<c>from_term(F,&nbsp;T)</c> if the result is a function. If
- no <seealso marker="#type">type</seealso> is explicitly
+ no <seeerl marker="#type">type</seeerl> is explicitly
specified, <c>[{atom,&nbsp;atom}]</c> is used as the
function type.</p>
</desc>
@@ -476,10 +476,10 @@ fun(S) -> sofs:partition(1, S) end
<p>Returns the binary relation containing the elements
(E,&nbsp;Set) such that Set belongs to <c><anno>SetOfSets</anno></c>
and E belongs to Set. If <c>SetOfSets</c> is
- a <seealso marker="#partition">partition</seealso> of a set X and
+ a <seeerl marker="#partition">partition</seeerl> of a set X and
R is the equivalence relation in X induced by <c>SetOfSets</c>,
then the returned relation is
- the <seealso marker="#canonical_map">canonical map</seealso> from
+ the <seeerl marker="#canonical_map">canonical map</seeerl> from
X onto the equivalence classes with respect to R.</p>
<pre>
1> <input>Ss = sofs:from_term([[a,b],[b,c]]),</input>
@@ -493,7 +493,7 @@ fun(S) -> sofs:partition(1, S) end
<name name="composite" arity="2" since=""/>
<fsummary>Return the composite of two functions.</fsummary>
<desc>
- <p>Returns the <seealso marker="#composite">composite</seealso> of
+ <p>Returns the <seeerl marker="#composite">composite</seeerl> of
the functions <c><anno>Function1</anno></c> and
<c><anno>Function2</anno></c>.</p>
<pre>
@@ -510,7 +510,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Create the function that maps each element of a
set onto another set.</fsummary>
<desc>
- <p>Creates the <seealso marker="#function">function</seealso>
+ <p>Creates the <seeerl marker="#function">function</seeerl>
that maps each element of set <c>Set</c> onto <c>AnySet</c>.</p>
<pre>
1> <input>S = sofs:set([a,b]),</input>
@@ -525,7 +525,7 @@ fun(S) -> sofs:partition(1, S) end
<name name="converse" arity="1" since=""/>
<fsummary>Return the converse of a binary relation.</fsummary>
<desc>
- <p>Returns the <seealso marker="#converse">converse</seealso>
+ <p>Returns the <seeerl marker="#converse">converse</seeerl>
of the binary relation <c><anno>BinRel1</anno></c>.</p>
<pre>
1> <input>R1 = sofs:relation([{1,a},{2,b},{3,a}]),</input>
@@ -539,7 +539,7 @@ fun(S) -> sofs:partition(1, S) end
<name name="difference" arity="2" since=""/>
<fsummary>Return the difference of two sets.</fsummary>
<desc>
- <p>Returns the <seealso marker="#difference">difference</seealso> of
+ <p>Returns the <seeerl marker="#difference">difference</seeerl> of
the sets <c><anno>Set1</anno></c> and <c><anno>Set2</anno></c>.</p>
</desc>
</func>
@@ -549,14 +549,14 @@ fun(S) -> sofs:partition(1, S) end
<name name="digraph_to_family" arity="2" since=""/>
<fsummary>Create a family from a directed graph.</fsummary>
<desc>
- <p>Creates a <seealso marker="#family">family</seealso> from
+ <p>Creates a <seeerl marker="#family">family</seeerl> from
the directed graph <c><anno>Graph</anno></c>. Each vertex a of
<c><anno>Graph</anno></c> is
represented by a pair (a,&nbsp;{b[1],&nbsp;...,&nbsp;b[n]}),
where the b[i]:s are the out-neighbors of a. If no type is
explicitly specified, [{atom,&nbsp;[atom]}] is used as type of
the family. It is assumed that <c><anno>Type</anno></c> is
- a <seealso marker="#valid_type">valid type</seealso> of the
+ a <seeerl marker="#valid_type">valid type</seeerl> of the
external set of the family.</p>
<p>If G is a directed graph, it holds that the vertices and
edges of G are the same as the vertices and edges of
@@ -568,7 +568,7 @@ fun(S) -> sofs:partition(1, S) end
<name name="domain" arity="1" since=""/>
<fsummary>Return the domain of a binary relation.</fsummary>
<desc>
- <p>Returns the <seealso marker="#domain">domain</seealso> of
+ <p>Returns the <seeerl marker="#domain">domain</seeerl> of
the binary relation <c><anno>BinRel</anno></c>.</p>
<pre>
1> <input>R = sofs:relation([{1,a},{1,b},{2,b},{2,c}]),</input>
@@ -584,7 +584,7 @@ fun(S) -> sofs:partition(1, S) end
<desc>
<p>Returns the difference between the binary relation
<c><anno>BinRel1</anno></c>
- and the <seealso marker="#restriction">restriction</seealso>
+ and the <seeerl marker="#restriction">restriction</seeerl>
of <c><anno>BinRel1</anno></c> to <c><anno>Set</anno></c>.</p>
<pre>
1> <input>R1 = sofs:relation([{1,a},{2,b},{3,c}]),</input>
@@ -621,8 +621,8 @@ fun(S) -> sofs:partition(1, S) end
<name name="empty_set" arity="0" since=""/>
<fsummary>Return the untyped empty set.</fsummary>
<desc>
- <p>Returns the <seealso marker="#sets_definition">untyped empty
- set</seealso>. <c>empty_set()</c> is equivalent to
+ <p>Returns the <seeerl marker="#sets_definition">untyped empty
+ set</seeerl>. <c>empty_set()</c> is equivalent to
<c>from_term([],&nbsp;['_'])</c>.</p>
</desc>
</func>
@@ -631,10 +631,10 @@ fun(S) -> sofs:partition(1, S) end
<name name="extension" arity="3" since=""/>
<fsummary>Extend the domain of a binary relation.</fsummary>
<desc>
- <p>Returns the <seealso marker="#extension">extension</seealso> of
+ <p>Returns the <seeerl marker="#extension">extension</seeerl> of
<c><anno>BinRel1</anno></c> such that for
each element E in <c><anno>Set</anno></c> that does not belong to the
- <seealso marker="#domain">domain</seealso> of
+ <seeerl marker="#domain">domain</seeerl> of
<c><anno>BinRel1</anno></c>, <c><anno>BinRel2</anno></c> contains the
pair (E,&nbsp;<c>AnySet</c>).</p>
<pre>
@@ -652,10 +652,10 @@ fun(S) -> sofs:partition(1, S) end
<name name="family" arity="2" since=""/>
<fsummary>Create a family of subsets.</fsummary>
<desc>
- <p>Creates a <seealso marker="#family">family of subsets</seealso>.
+ <p>Creates a <seeerl marker="#family">family of subsets</seeerl>.
<c>family(F,&nbsp;T)</c> is equivalent to
<c>from_term(F,&nbsp;T)</c> if the result is a family. If
- no <seealso marker="#type">type</seealso> is explicitly
+ no <seeerl marker="#type">type</seeerl> is explicitly
specified, <c>[{atom,&nbsp;[atom]}]</c> is used as the
family type.</p>
</desc>
@@ -666,7 +666,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return the difference of two families.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c>
- are <seealso marker="#family">families</seealso>, then
+ are <seeerl marker="#family">families</seeerl>, then
<c><anno>Family3</anno></c> is the family
such that the index set is equal to the index set of
<c><anno>Family1</anno></c>, and <c><anno>Family3</anno></c>[i] is
@@ -687,13 +687,13 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return a family of domains.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
- a <seealso marker="#family">family</seealso>
+ a <seeerl marker="#family">family</seeerl>
and <c><anno>Family1</anno></c>[i] is a binary relation for every i
in the index set of <c><anno>Family1</anno></c>,
then <c><anno>Family2</anno></c> is the family with the same index
set as <c><anno>Family1</anno></c> such
that <c><anno>Family2</anno></c>[i] is
- the <seealso marker="#domain">domain</seealso> of
+ the <seeerl marker="#domain">domain</seeerl> of
<c><anno>Family1</anno>[i]</c>.</p>
<pre>
1> <input>FR = sofs:from_term([{a,[{1,a},{2,b},{3,c}]},{b,[]},{c,[{4,d},{5,e}]}]),</input>
@@ -708,13 +708,13 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return a family of fields.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
- a <seealso marker="#family">family</seealso>
+ a <seeerl marker="#family">family</seeerl>
and <c><anno>Family1</anno></c>[i] is a binary relation for every i
in the index set of <c><anno>Family1</anno></c>,
then <c><anno>Family2</anno></c> is the family with the same index
set as <c><anno>Family1</anno></c> such
that <c><anno>Family2</anno></c>[i] is
- the <seealso marker="#field">field</seealso> of
+ the <seeerl marker="#field">field</seeerl> of
<c><anno>Family1</anno></c>[i].</p>
<pre>
1> <input>FR = sofs:from_term([{a,[{1,a},{2,b},{3,c}]},{b,[]},{c,[{4,d},{5,e}]}]),</input>
@@ -733,13 +733,13 @@ fun(S) -> sofs:partition(1, S) end
of sets of sets.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
- a <seealso marker="#family">family</seealso>
+ a <seeerl marker="#family">family</seeerl>
and <c><anno>Family1</anno></c>[i] is a set of sets for every i in
the index set of <c><anno>Family1</anno></c>,
then <c><anno>Family2</anno></c> is the family with the same index
set as <c><anno>Family1</anno></c> such
that <c><anno>Family2</anno></c>[i] is
- the <seealso marker="#intersection_n">intersection</seealso>
+ the <seeerl marker="#intersection_n">intersection</seeerl>
of <c><anno>Family1</anno></c>[i].</p>
<p>If <c><anno>Family1</anno></c>[i] is an empty set for some i,
the process exits with a <c>badarg</c> message.</p>
@@ -756,7 +756,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return the intersection of two families.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c>
- are <seealso marker="#family">families</seealso>,
+ are <seeerl marker="#family">families</seeerl>,
then <c><anno>Family3</anno></c> is the family such that the index
set is the intersection of <c><anno>Family1</anno></c>:s and
<c><anno>Family2</anno></c>:s index sets,
@@ -776,7 +776,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return a family of modified subsets.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
- a <seealso marker="#family">family</seealso>,
+ a <seeerl marker="#family">family</seeerl>,
then <c><anno>Family2</anno></c> is the family with the same index
set as <c><anno>Family1</anno></c> such
that <c><anno>Family2</anno></c>[i] is the result of
@@ -795,13 +795,13 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return a family of ranges.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
- a <seealso marker="#family">family</seealso>
+ a <seeerl marker="#family">family</seeerl>
and <c><anno>Family1</anno></c>[i] is a binary relation for every i
in the index set of <c><anno>Family1</anno></c>,
then <c><anno>Family2</anno></c> is the family with the same index
set as <c><anno>Family1</anno></c> such
that <c><anno>Family2</anno></c>[i] is
- the <seealso marker="#range">range</seealso> of
+ the <seeerl marker="#range">range</seeerl> of
<c><anno>Family1</anno></c>[i].</p>
<pre>
1> <input>FR = sofs:from_term([{a,[{1,a},{2,b},{3,c}]},{b,[]},{c,[{4,d},{5,e}]}]),</input>
@@ -816,15 +816,15 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Select a subset of a family using a predicate.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
- a <seealso marker="#family">family</seealso>,
+ a <seeerl marker="#family">family</seeerl>,
then <c><anno>Family2</anno></c> is
- the <seealso marker="#restriction">restriction</seealso> of
+ the <seeerl marker="#restriction">restriction</seeerl> of
<c><anno>Family1</anno></c> to those elements i of the index set
for which <c><anno>Fun</anno></c> applied
to <c><anno>Family1</anno></c>[i] returns
<c>true</c>. If <c><anno>Fun</anno></c> is a
tuple <c>{external,&nbsp;Fun2}</c>, then <c>Fun2</c> is applied to
- the <seealso marker="#external_set">external set</seealso>
+ the <seeerl marker="#external_set">external set</seeerl>
of <c><anno>Family1</anno></c>[i], otherwise <c><anno>Fun</anno></c>
is applied to <c><anno>Family1</anno></c>[i].</p>
<pre>
@@ -842,17 +842,17 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Create a directed graph from a family.</fsummary>
<desc>
<p>Creates a directed graph from
- <seealso marker="#family">family</seealso> <c><anno>Family</anno></c>.
+ <seeerl marker="#family">family</seeerl> <c><anno>Family</anno></c>.
For each pair (a,&nbsp;{b[1],&nbsp;...,&nbsp;b[n]})
of <c><anno>Family</anno></c>, vertex
a and the edges (a,&nbsp;b[i]) for
1&nbsp;&lt;=&nbsp;i&nbsp;&lt;=&nbsp;n are added to a newly
created directed graph.</p>
- <p>If no graph type is specified, <seealso marker="digraph#new/0">
- <c>digraph:new/0</c></seealso> is used for
+ <p>If no graph type is specified, <seemfa marker="digraph#new/0">
+ <c>digraph:new/0</c></seemfa> is used for
creating the directed graph, otherwise argument
<c><anno>GraphType</anno></c> is passed on as second argument to
- <seealso marker="digraph#new/1"><c>digraph:new/1</c></seealso>.</p>
+ <seemfa marker="digraph#new/1"><c>digraph:new/1</c></seemfa>.</p>
<p>It F is a family, it holds that F is a subset of
<c>digraph_to_family(family_to_digraph(F),&nbsp;type(F))</c>.
Equality holds if <c>union_of_family(F)</c> is a subset of
@@ -867,7 +867,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Create a binary relation from a family.</fsummary>
<desc>
<p>If <c><anno>Family</anno></c> is
- a <seealso marker="#family">family</seealso>,
+ a <seeerl marker="#family">family</seeerl>,
then <c><anno>BinRel</anno></c> is the binary relation containing
all pairs (i,&nbsp;x) such that i belongs to the index set
of <c><anno>Family</anno></c> and x belongs
@@ -885,13 +885,13 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return the union of a family of sets of sets.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
- a <seealso marker="#family">family</seealso>
+ a <seeerl marker="#family">family</seeerl>
and <c><anno>Family1</anno></c>[i] is a set of sets for each i in
the index set of <c><anno>Family1</anno></c>,
then <c><anno>Family2</anno></c> is the family with the same index
set as <c><anno>Family1</anno></c> such
that <c><anno>Family2</anno></c>[i] is
- the <seealso marker="#union_n">union</seealso> of
+ the <seeerl marker="#union_n">union</seeerl> of
<c><anno>Family1</anno></c>[i].</p>
<pre>
1> <input>F1 = sofs:from_term([{a,[[1,2],[2,3]]},{b,[[]]}]),</input>
@@ -908,7 +908,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return the union of two families.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c>
- are <seealso marker="#family">families</seealso>,
+ are <seeerl marker="#family">families</seeerl>,
then <c><anno>Family3</anno></c> is the family such that the index
set is the union of <c><anno>Family1</anno></c>:s
and <c><anno>Family2</anno></c>:s index sets,
@@ -929,7 +929,7 @@ fun(S) -> sofs:partition(1, S) end
<name name="field" arity="1" since=""/>
<fsummary>Return the field of a binary relation.</fsummary>
<desc>
- <p>Returns the <seealso marker="#field">field</seealso> of the
+ <p>Returns the <seeerl marker="#field">field</seeerl> of the
binary relation <c><anno>BinRel</anno></c>.</p>
<pre>
1> <input>R = sofs:relation([{1,a},{1,b},{2,b},{2,c}]),</input>
@@ -945,12 +945,12 @@ fun(S) -> sofs:partition(1, S) end
<name name="from_external" arity="2" since=""/>
<fsummary>Create a set.</fsummary>
<desc>
- <p>Creates a set from the <seealso marker="#external_set">external
- set</seealso> <c><anno>ExternalSet</anno></c> and
- the <seealso marker="#type">type</seealso> <c><anno>Type</anno></c>.
+ <p>Creates a set from the <seeerl marker="#external_set">external
+ set</seeerl> <c><anno>ExternalSet</anno></c> and
+ the <seeerl marker="#type">type</seeerl> <c><anno>Type</anno></c>.
It is assumed that <c><anno>Type</anno></c> is
- a <seealso marker="#valid_type">valid
- type</seealso> of <c><anno>ExternalSet</anno></c>.</p>
+ a <seeerl marker="#valid_type">valid
+ type</seeerl> of <c><anno>ExternalSet</anno></c>.</p>
</desc>
</func>
@@ -958,8 +958,8 @@ fun(S) -> sofs:partition(1, S) end
<name name="from_sets" arity="1" clause_i="1" since=""/>
<fsummary>Create a set out of a list of sets.</fsummary>
<desc>
- <p>Returns the <seealso marker="#sets_definition">unordered
- set</seealso> containing the sets of list
+ <p>Returns the <seeerl marker="#sets_definition">unordered
+ set</seeerl> containing the sets of list
<c><anno>ListOfSets</anno></c>.</p>
<pre>
1> <input>S1 = sofs:relation([{a,1},{b,2}]),</input>
@@ -974,8 +974,8 @@ fun(S) -> sofs:partition(1, S) end
<name name="from_sets" arity="1" clause_i="2" since=""/>
<fsummary>Create an ordered set out of a tuple of sets.</fsummary>
<desc>
- <p>Returns the <seealso marker="#sets_definition">ordered
- set</seealso> containing the sets of the non-empty tuple
+ <p>Returns the <seeerl marker="#sets_definition">ordered
+ set</seeerl> containing the sets of the non-empty tuple
<c><anno>TupleOfSets</anno></c>.</p>
</desc>
</func>
@@ -986,12 +986,12 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Create a set.</fsummary>
<desc>
<p><marker id="from_term"></marker>Creates an element
- of <seealso marker="#sets_definition">Sets</seealso> by
+ of <seeerl marker="#sets_definition">Sets</seeerl> by
traversing term <c><anno>Term</anno></c>, sorting lists,
removing duplicates, and
- deriving or verifying a <seealso marker="#valid_type">valid
- type</seealso> for the so obtained external set. An
- explicitly specified <seealso marker="#type">type</seealso>
+ deriving or verifying a <seeerl marker="#valid_type">valid
+ type</seeerl> for the so obtained external set. An
+ explicitly specified <seeerl marker="#type">type</seeerl>
<c><anno>Type</anno></c>
can be used to limit the depth of the traversal; an atomic
type stops the traversal, as shown by the following example
@@ -1019,14 +1019,14 @@ fun(S) -> sofs:partition(1, S) end
<input>sofs:to_external(Ss).</input>
[{a,[1,2,3]},{b,[4,5,6]}]</pre>
<p>Other functions that create sets are
- <seealso marker="#from_external/2"><c>from_external/2</c></seealso>
- and <seealso marker="#from_sets/1"><c>from_sets/1</c></seealso>.
+ <seemfa marker="#from_external/2"><c>from_external/2</c></seemfa>
+ and <seemfa marker="#from_sets/1"><c>from_sets/1</c></seemfa>.
Special cases of <c>from_term/2</c> are
- <seealso marker="#a_function/1"><c>a_function/1,2</c></seealso>,
- <seealso marker="#empty_set/0"><c>empty_set/0</c></seealso>,
- <seealso marker="#family/1"><c>family/1,2</c></seealso>,
- <seealso marker="#relation/1"><c>relation/1,2</c></seealso>, and
- <seealso marker="#set/1"><c>set/1,2</c></seealso>.</p>
+ <seemfa marker="#a_function/1"><c>a_function/1,2</c></seemfa>,
+ <seemfa marker="#empty_set/0"><c>empty_set/0</c></seemfa>,
+ <seemfa marker="#family/1"><c>family/1,2</c></seemfa>,
+ <seemfa marker="#relation/1"><c>relation/1,2</c></seemfa>, and
+ <seemfa marker="#set/1"><c>set/1,2</c></seemfa>.</p>
</desc>
</func>
@@ -1034,7 +1034,7 @@ fun(S) -> sofs:partition(1, S) end
<name name="image" arity="2" since=""/>
<fsummary>Return the image of a set under a binary relation.</fsummary>
<desc>
- <p>Returns the <seealso marker="#image">image</seealso> of
+ <p>Returns the <seeerl marker="#image">image</seeerl> of
set <c><anno>Set1</anno></c> under the binary
relation <c><anno>BinRel</anno></c>.</p>
<pre>
@@ -1051,7 +1051,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return the intersection of a set of sets.</fsummary>
<desc>
<p>Returns
- the <seealso marker="#intersection_n">intersection</seealso> of
+ the <seeerl marker="#intersection_n">intersection</seeerl> of
the set of sets <c><anno>SetOfSets</anno></c>.</p>
<p>Intersecting an empty set of sets exits the process with a
<c>badarg</c> message.</p>
@@ -1063,7 +1063,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return the intersection of two sets.</fsummary>
<desc>
<p>Returns
- the <seealso marker="#intersection">intersection</seealso> of
+ the <seeerl marker="#intersection">intersection</seeerl> of
<c><anno>Set1</anno></c> and <c><anno>Set2</anno></c>.</p>
</desc>
</func>
@@ -1073,7 +1073,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return the intersection of a family.</fsummary>
<desc>
<p>Returns the intersection of
- <seealso marker="#family">family</seealso> <c><anno>Family</anno></c>.
+ <seeerl marker="#family">family</seeerl> <c><anno>Family</anno></c>.
</p>
<p>Intersecting an empty family exits the process with a
<c>badarg</c> message.</p>
@@ -1089,7 +1089,7 @@ fun(S) -> sofs:partition(1, S) end
<name name="inverse" arity="1" since=""/>
<fsummary>Return the inverse of a function.</fsummary>
<desc>
- <p>Returns the <seealso marker="#inverse">inverse</seealso>
+ <p>Returns the <seeerl marker="#inverse">inverse</seeerl>
of function <c><anno>Function1</anno></c>.</p>
<pre>
1> <input>R1 = sofs:relation([{1,a},{2,b},{3,c}]),</input>
@@ -1104,8 +1104,8 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Return the inverse image of a set under
a binary relation.</fsummary>
<desc>
- <p>Returns the <seealso marker="#inverse_image">inverse
- image</seealso> of <c><anno>Set1</anno></c> under the binary
+ <p>Returns the <seeerl marker="#inverse_image">inverse
+ image</seeerl> of <c><anno>Set1</anno></c> under the binary
relation <c><anno>BinRel</anno></c>.</p>
<pre>
1> <input>R = sofs:relation([{1,a},{2,b},{2,c},{3,d}]),</input>
@@ -1121,7 +1121,7 @@ fun(S) -> sofs:partition(1, S) end
<fsummary>Test for a function.</fsummary>
<desc>
<p>Returns <c>true</c> if the binary relation <c><anno>BinRel</anno></c>
- is a <seealso marker="#function">function</seealso> or the
+ is a <seeerl marker="#function">function</seeerl> or the
untyped empty set, otherwise <c>false</c>.</p>
</desc>
</func>
@@ -1132,7 +1132,7 @@ fun(S) -> sofs:partition(1, S) end
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c>
and <c><anno>Set2</anno></c>
- are <seealso marker="#disjoint">disjoint</seealso>, otherwise
+ are <seeerl marker="#disjoint">disjoint</seeerl>, otherwise
<c>false</c>.</p>
</desc>
</func>
@@ -1152,7 +1152,7 @@ fun(S) -> sofs:partition(1, S) end
<desc>
<p>Returns <c>true</c> if <c><anno>AnySet1</anno></c>
and <c><anno>AnySet2</anno></c>
- are <seealso marker="#equal">equal</seealso>, otherwise
+ are <seeerl marker="#equal">equal</seeerl>, otherwise
<c>false</c>. The following example shows that <c>==/2</c> is
used when comparing sets for equality:</p>
<pre>
@@ -1168,7 +1168,7 @@ true</pre>
<fsummary>Test for an unordered set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>AnySet</anno></c> is
- an <seealso marker="#sets_definition">unordered set</seealso>, and
+ an <seeerl marker="#sets_definition">unordered set</seeerl>, and
<c>false</c> if <c><anno>AnySet</anno></c> is an ordered set or an
atomic set.</p>
</desc>
@@ -1179,7 +1179,7 @@ true</pre>
<fsummary>Test for an unordered set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is
- an <seealso marker="#sets_definition">unordered set</seealso>, an
+ an <seeerl marker="#sets_definition">unordered set</seeerl>, an
ordered set, or an atomic set, otherwise <c>false</c>.</p>
</desc>
</func>
@@ -1189,7 +1189,7 @@ true</pre>
<fsummary>Test two sets for subset.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c> is
- a <seealso marker="#subset">subset</seealso>
+ a <seeerl marker="#subset">subset</seeerl>
of <c><anno>Set2</anno></c>, otherwise <c>false</c>.</p>
</desc>
</func>
@@ -1199,7 +1199,7 @@ true</pre>
<fsummary>Test for a type.</fsummary>
<desc>
<p>Returns <c>true</c> if term <c><anno>Term</anno></c> is
- a <seealso marker="#type">type</seealso>.</p>
+ a <seeerl marker="#type">type</seeerl>.</p>
</desc>
</func>
@@ -1207,8 +1207,8 @@ true</pre>
<name name="join" arity="4" since=""/>
<fsummary>Return the join of two relations.</fsummary>
<desc>
- <p>Returns the <seealso marker="#natural_join">natural
- join</seealso> of the relations <c><anno>Relation1</anno></c>
+ <p>Returns the <seeerl marker="#natural_join">natural
+ join</seeerl> of the relations <c><anno>Relation1</anno></c>
and <c><anno>Relation2</anno></c> on coordinates <c><anno>I</anno></c>
and <c><anno>J</anno></c>.</p>
<pre>
@@ -1229,8 +1229,8 @@ true</pre>
{R[1],&nbsp;...,&nbsp;R[n]} of binary relations
and <c><anno>BinRel1</anno></c> is a binary relation,
then <c><anno>BinRel2</anno></c> is
- the <seealso marker="#multiple_relative_product">multiple relative
- product</seealso> of the ordered set
+ the <seeerl marker="#multiple_relative_product">multiple relative
+ product</seeerl> of the ordered set
(R[i],&nbsp;...,&nbsp;R[n]) and <c><anno>BinRel1</anno></c>.</p>
<pre>
1> <input>Ri = sofs:relation([{a,1},{b,2},{c,3}]),</input>
@@ -1254,7 +1254,7 @@ true</pre>
<name name="partition" arity="1" since=""/>
<fsummary>Return the coarsest partition given a set of sets.</fsummary>
<desc>
- <p>Returns the <seealso marker="#partition">partition</seealso> of
+ <p>Returns the <seeerl marker="#partition">partition</seeerl> of
the union of the set of sets <c><anno>SetOfSets</anno></c> such that
two elements are considered equal if they belong to the same
elements of <c><anno>SetOfSets</anno></c>.</p>
@@ -1271,7 +1271,7 @@ true</pre>
<name name="partition" arity="2" since=""/>
<fsummary>Return a partition of a set.</fsummary>
<desc>
- <p>Returns the <seealso marker="#partition">partition</seealso> of
+ <p>Returns the <seeerl marker="#partition">partition</seeerl> of
<c><anno>Set</anno></c> such that two elements are considered equal
if the results of applying <c><anno>SetFun</anno></c> are equal.</p>
<pre>
@@ -1288,7 +1288,7 @@ true</pre>
<fsummary>Return a partition of a set.</fsummary>
<desc>
<p>Returns a pair of sets that, regarded as constituting a
- set, forms a <seealso marker="#partition">partition</seealso> of
+ set, forms a <seeerl marker="#partition">partition</seeerl> of
<c><anno>Set1</anno></c>. If the
result of applying <c><anno>SetFun</anno></c> to an element of
<c><anno>Set1</anno></c> gives an element in <c><anno>Set2</anno></c>,
@@ -1310,14 +1310,14 @@ true</pre>
<name name="partition_family" arity="2" since=""/>
<fsummary>Return a family indexing a partition.</fsummary>
<desc>
- <p>Returns <seealso marker="#family">family</seealso>
+ <p>Returns <seeerl marker="#family">family</seeerl>
<c><anno>Family</anno></c> where the indexed set is
- a <seealso marker="#partition">partition</seealso>
+ a <seeerl marker="#partition">partition</seeerl>
of <c><anno>Set</anno></c> such that two elements are considered
equal if the results of applying <c><anno>SetFun</anno></c> are the
same value i. This i is the index that <c><anno>Family</anno></c>
- maps onto the <seealso marker="#equivalence_class">equivalence
- class</seealso>.</p>
+ maps onto the <seeerl marker="#equivalence_class">equivalence
+ class</seeerl>.</p>
<pre>
1> <input>S = sofs:relation([{a,a,a,a},{a,a,b,b},{a,b,b,b}]),</input>
<input>SetFun = {external, fun({A,_,C,_}) -> {A,C} end},</input>
@@ -1331,8 +1331,8 @@ true</pre>
<name name="product" arity="1" since=""/>
<fsummary>Return the Cartesian product of a tuple of sets.</fsummary>
<desc>
- <p>Returns the <seealso marker="#Cartesian_product_tuple">Cartesian
- product</seealso> of the non-empty tuple of sets
+ <p>Returns the <seeerl marker="#Cartesian_product_tuple">Cartesian
+ product</seeerl> of the non-empty tuple of sets
<c><anno>TupleOfSets</anno></c>. If (x[1],&nbsp;...,&nbsp;x[n]) is
an element of the n-ary relation <c><anno>Relation</anno></c>, then
x[i] is drawn from element i of <c><anno>TupleOfSets</anno></c>.</p>
@@ -1350,8 +1350,8 @@ true</pre>
<name name="product" arity="2" since=""/>
<fsummary>Return the Cartesian product of two sets.</fsummary>
<desc>
- <p>Returns the <seealso marker="#Cartesian_product">Cartesian
- product</seealso> of <c><anno>Set1</anno></c>
+ <p>Returns the <seeerl marker="#Cartesian_product">Cartesian
+ product</seeerl> of <c><anno>Set1</anno></c>
and <c><anno>Set2</anno></c>.</p>
<pre>
1> <input>S1 = sofs:set([1,2]),</input>
@@ -1373,7 +1373,7 @@ true</pre>
applying <c><anno>SetFun</anno></c> to the element.</p>
<p>If <c><anno>SetFun</anno></c> is a number i&nbsp;&gt;=&nbsp;1 and
<c><anno>Set1</anno></c> is a relation, then the returned set is
- the <seealso marker="#projection">projection</seealso> of
+ the <seeerl marker="#projection">projection</seeerl> of
<c><anno>Set1</anno></c> onto coordinate i.</p>
<pre>
1> <input>S1 = sofs:from_term([{1,a},{2,b},{3,a}]),</input>
@@ -1387,7 +1387,7 @@ true</pre>
<name name="range" arity="1" since=""/>
<fsummary>Return the range of a binary relation.</fsummary>
<desc>
- <p>Returns the <seealso marker="#range">range</seealso> of the
+ <p>Returns the <seeerl marker="#range">range</seeerl> of the
binary relation <c><anno>BinRel</anno></c>.</p>
<pre>
1> <input>R = sofs:relation([{1,a},{1,b},{2,b},{2,c}]),</input>
@@ -1402,10 +1402,10 @@ true</pre>
<name name="relation" arity="2" since=""/>
<fsummary>Create a relation.</fsummary>
<desc>
- <p>Creates a <seealso marker="#relation">relation</seealso>.
+ <p>Creates a <seeerl marker="#relation">relation</seeerl>.
<c>relation(R,&nbsp;T)</c> is equivalent to
<c>from_term(R,&nbsp;T)</c>, if T is
- a <seealso marker="#type">type</seealso> and the result is a
+ a <seeerl marker="#type">type</seeerl> and the result is a
relation. If <c><anno>Type</anno></c> is an integer N, then
<c>[{atom,&nbsp;...,&nbsp;atom}])</c>, where the tuple size
is N, is used as type of the relation. If no type is
@@ -1420,11 +1420,11 @@ true</pre>
<name name="relation_to_family" arity="1" since=""/>
<fsummary>Create a family from a binary relation.</fsummary>
<desc>
- <p>Returns <seealso marker="#family">family</seealso>
+ <p>Returns <seeerl marker="#family">family</seeerl>
<c><anno>Family</anno></c> such that the index set is equal to
- the <seealso marker="#domain">domain</seealso> of the binary
+ the <seeerl marker="#domain">domain</seeerl> of the binary
relation <c><anno>BinRel</anno></c>, and <c><anno>Family</anno></c>[i]
- is the <seealso marker="#image">image</seealso> of the set of i
+ is the <seeerl marker="#image">image</seeerl> of the set of i
under <c><anno>BinRel</anno></c>.</p>
<pre>
1> <input>R = sofs:relation([{b,1},{c,2},{c,3}]),</input>
@@ -1444,13 +1444,13 @@ true</pre>
[R[1],&nbsp;...,&nbsp;R[n]] of binary relations and
<c><anno>BinRel1</anno></c>
is a binary relation, then <c><anno>BinRel2</anno></c> is the
- <seealso marker="#tuple_relative_product">relative product</seealso>
+ <seeerl marker="#tuple_relative_product">relative product</seeerl>
of the ordered set (R[i],&nbsp;...,&nbsp;R[n]) and
<c><anno>BinRel1</anno></c>.</p>
<p>If <c><anno>BinRel1</anno></c> is omitted, the relation of equality
between the elements of
- the <seealso marker="#Cartesian_product_tuple">Cartesian
- product</seealso> of the ranges of R[i],
+ the <seeerl marker="#Cartesian_product_tuple">Cartesian
+ product</seeerl> of the ranges of R[i],
range&nbsp;R[1]&nbsp;&times;&nbsp;...&nbsp;&times;&nbsp;range&nbsp;R[n],
is used instead (intuitively, nothing is "lost").</p>
<pre>
@@ -1470,8 +1470,8 @@ true</pre>
<fsummary>Return the relative product of
two binary relations.</fsummary>
<desc>
- <p>Returns the <seealso marker="#relative_product">relative
- product</seealso> of the binary relations <c><anno>BinRel1</anno></c>
+ <p>Returns the <seeerl marker="#relative_product">relative
+ product</seeerl> of the binary relations <c><anno>BinRel1</anno></c>
and <c><anno>BinRel2</anno></c>.</p>
</desc>
</func>
@@ -1481,9 +1481,9 @@ true</pre>
<fsummary>Return the relative_product of
two binary relations.</fsummary>
<desc>
- <p>Returns the <seealso marker="#relative_product">relative
- product</seealso> of
- the <seealso marker="#converse">converse</seealso> of the
+ <p>Returns the <seeerl marker="#relative_product">relative
+ product</seeerl> of
+ the <seeerl marker="#converse">converse</seeerl> of the
binary relation <c><anno>BinRel1</anno></c> and the binary
relation <c><anno>BinRel2</anno></c>.</p>
<pre>
@@ -1501,7 +1501,7 @@ true</pre>
<name name="restriction" arity="2" since=""/>
<fsummary>Return a restriction of a binary relation.</fsummary>
<desc>
- <p>Returns the <seealso marker="#restriction">restriction</seealso> of
+ <p>Returns the <seeerl marker="#restriction">restriction</seeerl> of
the binary relation <c><anno>BinRel1</anno></c>
to <c><anno>Set</anno></c>.</p>
<pre>
@@ -1534,10 +1534,10 @@ true</pre>
<name name="set" arity="2" since=""/>
<fsummary>Create a set of atoms or any type of sets.</fsummary>
<desc>
- <p>Creates an <seealso marker="#sets_definition">unordered
- set</seealso>. <c>set(L,&nbsp;T)</c> is equivalent to
+ <p>Creates an <seeerl marker="#sets_definition">unordered
+ set</seeerl>. <c>set(L,&nbsp;T)</c> is equivalent to
<c>from_term(L,&nbsp;T)</c>, if the result is an unordered
- set. If no <seealso marker="#type">type</seealso> is
+ set. If no <seeerl marker="#type">type</seeerl> is
explicitly specified, <c>[atom]</c> is used as the set type.</p>
</desc>
</func>
@@ -1550,7 +1550,7 @@ true</pre>
of <c><anno>Set1</anno></c> for which <c><anno>Fun</anno></c>
returns <c>true</c>. If <c><anno>Fun</anno></c> is a tuple
<c>{external,&nbsp;Fun2}</c>, <c>Fun2</c> is applied to the
- <seealso marker="#external_set">external set</seealso> of
+ <seeerl marker="#external_set">external set</seeerl> of
each element, otherwise <c><anno>Fun</anno></c> is applied to each
element.</p>
<pre>
@@ -1568,8 +1568,8 @@ true</pre>
<fsummary>Return the strict relation corresponding to
a given relation.</fsummary>
<desc>
- <p>Returns the <seealso marker="#strict_relation">strict
- relation</seealso> corresponding to the binary
+ <p>Returns the <seeerl marker="#strict_relation">strict
+ relation</seeerl> corresponding to the binary
relation <c><anno>BinRel1</anno></c>.</p>
<pre>
1> <input>R1 = sofs:relation([{1,1},{1,2},{2,1},{2,2}]),</input>
@@ -1604,7 +1604,7 @@ true</pre>
[{a,a},{b,b},{c,c}]</pre>
<p>Let <c>SetOfSets</c> be a set of sets and <c>BinRel</c> a binary
relation. The function that maps each element <c>Set</c> of
- <c>SetOfSets</c> onto the <seealso marker="#image">image</seealso>
+ <c>SetOfSets</c> onto the <seeerl marker="#image">image</seeerl>
of <c>Set</c> under <c>BinRel</c> is returned by the following
function:</p>
<pre>
@@ -1614,7 +1614,7 @@ images(SetOfSets, BinRel) ->
<p>External unordered sets are represented as sorted lists. So,
creating the image of a set under a relation R can traverse all
elements of R (to that comes the sorting of results, the
- image). In <seealso marker="#image/2"><c>image/2</c></seealso>,
+ image). In <seemfa marker="#image/2"><c>image/2</c></seemfa>,
<c>BinRel</c> is traversed once
for each element of <c>SetOfSets</c>, which can take too long. The
following efficient function can be used instead under the
@@ -1632,8 +1632,8 @@ images2(SetOfSets, BinRel) ->
<name name="symdiff" arity="2" since=""/>
<fsummary>Return the symmetric difference of two sets.</fsummary>
<desc>
- <p>Returns the <seealso marker="#symmetric_difference">symmetric
- difference</seealso> (or the Boolean sum)
+ <p>Returns the <seeerl marker="#symmetric_difference">symmetric
+ difference</seeerl> (or the Boolean sum)
of <c><anno>Set1</anno></c> and <c><anno>Set2</anno></c>.</p>
<pre>
1> <input>S1 = sofs:set([1,2,3]),</input>
@@ -1669,8 +1669,8 @@ images2(SetOfSets, BinRel) ->
<name name="to_external" arity="1" since=""/>
<fsummary>Return the elements of a set.</fsummary>
<desc>
- <p>Returns the <seealso marker="#external_set">external
- set</seealso> of an atomic, ordered, or unordered set.</p>
+ <p>Returns the <seeerl marker="#external_set">external
+ set</seeerl> of an atomic, ordered, or unordered set.</p>
</desc>
</func>
@@ -1689,7 +1689,7 @@ images2(SetOfSets, BinRel) ->
<name name="type" arity="1" since=""/>
<fsummary>Return the type of a set.</fsummary>
<desc>
- <p>Returns the <seealso marker="#type">type</seealso> of an
+ <p>Returns the <seeerl marker="#type">type</seeerl> of an
atomic, ordered, or unordered set.</p>
</desc>
</func>
@@ -1698,7 +1698,7 @@ images2(SetOfSets, BinRel) ->
<name name="union" arity="1" since=""/>
<fsummary>Return the union of a set of sets.</fsummary>
<desc>
- <p>Returns the <seealso marker="#union_n">union</seealso> of the
+ <p>Returns the <seeerl marker="#union_n">union</seeerl> of the
set of sets <c><anno>SetOfSets</anno></c>.</p>
</desc>
</func>
@@ -1707,7 +1707,7 @@ images2(SetOfSets, BinRel) ->
<name name="union" arity="2" since=""/>
<fsummary>Return the union of two sets.</fsummary>
<desc>
- <p>Returns the <seealso marker="#union">union</seealso> of
+ <p>Returns the <seeerl marker="#union">union</seeerl> of
<c><anno>Set1</anno></c> and <c><anno>Set2</anno></c>.</p>
</desc>
</func>
@@ -1716,7 +1716,7 @@ images2(SetOfSets, BinRel) ->
<name name="union_of_family" arity="1" since=""/>
<fsummary>Return the union of a family.</fsummary>
<desc>
- <p>Returns the union of <seealso marker="#family">family</seealso>
+ <p>Returns the union of <seeerl marker="#family">family</seeerl>
<c><anno>Family</anno></c>.</p>
<pre>
1> <input>F = sofs:family([{a,[0,2,4]},{b,[0,1,2]},{c,[2,3]}]),</input>
@@ -1731,10 +1731,10 @@ images2(SetOfSets, BinRel) ->
<fsummary>Return the weak relation corresponding to
a given relation.</fsummary>
<desc>
- <p>Returns a subset S of the <seealso marker="#weak_relation">weak
- relation</seealso> W
+ <p>Returns a subset S of the <seeerl marker="#weak_relation">weak
+ relation</seeerl> W
corresponding to the binary relation <c><anno>BinRel1</anno></c>.
- Let F be the <seealso marker="#field">field</seealso> of
+ Let F be the <seeerl marker="#field">field</seeerl> of
<c><anno>BinRel1</anno></c>. The
subset S is defined so that x S y if x W y for some x in F
and for some y in F.</p>
@@ -1749,11 +1749,11 @@ images2(SetOfSets, BinRel) ->
<section>
<title>See Also</title>
- <p><seealso marker="dict"><c>dict(3)</c></seealso>,
- <seealso marker="digraph"><c>digraph(3)</c></seealso>,
- <seealso marker="orddict"><c>orddict(3)</c></seealso>,
- <seealso marker="ordsets"><c>ordsets(3)</c></seealso>,
- <seealso marker="sets"><c>sets(3)</c></seealso></p>
+ <p><seeerl marker="dict"><c>dict(3)</c></seeerl>,
+ <seeerl marker="digraph"><c>digraph(3)</c></seeerl>,
+ <seeerl marker="orddict"><c>orddict(3)</c></seeerl>,
+ <seeerl marker="ordsets"><c>ordsets(3)</c></seeerl>,
+ <seeerl marker="sets"><c>sets(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/specs.xml b/lib/stdlib/doc/src/specs.xml
index fd2d625685..9b11a6941c 100644
--- a/lib/stdlib/doc/src/specs.xml
+++ b/lib/stdlib/doc/src/specs.xml
@@ -51,6 +51,7 @@
<xi:include href="../specs/specs_sets.xml"/>
<xi:include href="../specs/specs_shell.xml"/>
<xi:include href="../specs/specs_shell_default.xml"/>
+ <xi:include href="../specs/specs_shell_docs.xml"/>
<xi:include href="../specs/specs_slave.xml"/>
<xi:include href="../specs/specs_sofs.xml"/>
<xi:include href="../specs/specs_string.xml"/>
diff --git a/lib/stdlib/doc/src/stdlib_app.xml b/lib/stdlib/doc/src/stdlib_app.xml
index f857cc394b..51caf4d14d 100644
--- a/lib/stdlib/doc/src/stdlib_app.xml
+++ b/lib/stdlib/doc/src/stdlib_app.xml
@@ -40,7 +40,7 @@
<title>Configuration</title>
<p>The following configuration parameters are defined for the STDLIB
application. For more information about configuration parameters, see the
- <seealso marker="kernel:app"><c>app(4)</c></seealso> module in Kernel.</p>
+ <seefile marker="kernel:app"><c>app(4)</c></seefile> module in Kernel.</p>
<taglist>
<tag><c>shell_esc = icl | abort</c></tag>
@@ -86,9 +86,9 @@
<section>
<title>See Also</title>
- <p><seealso marker="kernel:app"><c>app(4)</c></seealso>,
- <seealso marker="kernel:application"><c>application(3)</c></seealso>,
- <seealso marker="shell">shell(3)</seealso></p>
+ <p><seefile marker="kernel:app"><c>app(4)</c></seefile>,
+ <seeerl marker="kernel:application"><c>application(3)</c></seeerl>,
+ <seeerl marker="shell">shell(3)</seeerl></p>
</section>
</appref>
diff --git a/lib/stdlib/doc/src/string.xml b/lib/stdlib/doc/src/string.xml
index d102191a57..4735f72943 100644
--- a/lib/stdlib/doc/src/string.xml
+++ b/lib/stdlib/doc/src/string.xml
@@ -36,8 +36,8 @@
<modulesummary>String processing functions.</modulesummary>
<description>
<p>This module provides functions for string processing.</p>
- <p>A string in this module is represented by <seealso marker="unicode#type-chardata">
- <c>unicode:chardata()</c></seealso>, that is, a list of codepoints,
+ <p>A string in this module is represented by <seetype marker="unicode#chardata">
+ <c>unicode:chardata()</c></seetype>, that is, a list of codepoints,
binaries with UTF-8-encoded codepoints
(<em>UTF-8 binaries</em>), or a mix of the two.</p>
<code>
@@ -66,11 +66,11 @@
Grapheme clusters for codepoints of class <c>prepend</c>
and non-modern (or decomposed) Hangul is not handled for performance
reasons in
- <seealso marker="#find/3"><c>find/3</c></seealso>,
- <seealso marker="#replace/3"><c>replace/3</c></seealso>,
- <seealso marker="#split/2"><c>split/2</c></seealso>,
- <seealso marker="#lexemes/2"><c>split/2</c></seealso> and
- <seealso marker="#trim/3"><c>trim/3</c></seealso>.
+ <seemfa marker="#find/3"><c>find/3</c></seemfa>,
+ <seemfa marker="#replace/3"><c>replace/3</c></seemfa>,
+ <seemfa marker="#split/2"><c>split/2</c></seemfa>,
+ <seemfa marker="#lexemes/2"><c>split/2</c></seemfa> and
+ <seemfa marker="#trim/3"><c>trim/3</c></seemfa>.
</p>
<p>
Splitting and appending strings is to be done on grapheme clusters
@@ -80,8 +80,8 @@
</p>
<p>
Most of the functions expect all input to be normalized to one form,
- see for example <seealso marker="unicode#characters_to_nfc_list/1">
- <c>unicode:characters_to_nfc_list/1</c></seealso>.
+ see for example <seemfa marker="unicode#characters_to_nfc_list/1">
+ <c>unicode:characters_to_nfc_list/1</c></seemfa>.
</p>
<p>
Language or locale specific handling of input is not considered
@@ -107,10 +107,10 @@
4> string:lexemes(&lt;&lt;"foo bar">>, " ").
[&lt;&lt;"foo">>,&lt;&lt;"bar">>]</code>
<p>This module has been reworked in Erlang/OTP 20 to
- handle <seealso marker="unicode#type-chardata">
- <c>unicode:chardata()</c></seealso> and operate on grapheme
- clusters. The <seealso marker="#oldapi"> <c>old
- functions</c></seealso> that only work on Latin-1 lists as input
+ handle <seetype marker="unicode#chardata">
+ <c>unicode:chardata()</c></seetype> and operate on grapheme
+ clusters. The <seeerl marker="#oldapi"> <c>old
+ functions</c></seeerl> that only work on Latin-1 lists as input
are still available but should not be used, they will be
deprecated in a future release.
</p>
@@ -137,7 +137,7 @@
Converts <c><anno>String</anno></c> to a case-agnostic
comparable string. Function <c>casefold/1</c> is preferred
over <c>lowercase/1</c> when two strings are to be compared
- for equality. See also <seealso marker="#equal/4"><c>equal/4</c></seealso>.
+ for equality. See also <seemfa marker="#equal/4"><c>equal/4</c></seemfa>.
</p>
<p><em>Example:</em></p>
<pre>
@@ -175,16 +175,16 @@
</p>
<p>
If <c><anno>IgnoreCase</anno></c> is <c>true</c>
- the function does <seealso marker="#casefold/1">
- <c>casefold</c>ing</seealso> on the fly before the equality test.
+ the function does <seemfa marker="#casefold/1">
+ <c>casefold</c>ing</seemfa> on the fly before the equality test.
</p>
<p>If <c><anno>Norm</anno></c> is not <c>none</c>
the function applies normalization on the fly before the equality test.
There are four available normalization forms:
- <seealso marker="unicode#characters_to_nfc_list/1"> <c>nfc</c></seealso>,
- <seealso marker="unicode#characters_to_nfd_list/1"> <c>nfd</c></seealso>,
- <seealso marker="unicode#characters_to_nfkc_list/1"> <c>nfkc</c></seealso>, and
- <seealso marker="unicode#characters_to_nfkd_list/1"> <c>nfkd</c></seealso>.
+ <seemfa marker="unicode#characters_to_nfc_list/1"> <c>nfc</c></seemfa>,
+ <seemfa marker="unicode#characters_to_nfd_list/1"> <c>nfd</c></seemfa>,
+ <seemfa marker="unicode#characters_to_nfkc_list/1"> <c>nfkc</c></seemfa>, and
+ <seemfa marker="unicode#characters_to_nfkd_list/1"> <c>nfkd</c></seemfa>.
</p>
<p>By default,
<c><anno>IgnoreCase</anno></c> is <c>false</c> and
@@ -273,7 +273,7 @@ true</pre>
adjacent separator graphemes clusters in <c><anno>String</anno></c>
are treated as one. That is, there are no empty
strings in the resulting list of lexemes.
- See also <seealso marker="#split/3"><c>split/3</c></seealso> which returns
+ See also <seemfa marker="#split/3"><c>split/3</c></seemfa> which returns
empty strings.
</p>
<p>Notice that <c>[$\r,$\n]</c> is one grapheme cluster.</p>
@@ -294,7 +294,7 @@ true</pre>
Converts <c><anno>String</anno></c> to lowercase.
</p>
<p>
- Notice that function <seealso marker="#casefold/1"><c>casefold/1</c></seealso>
+ Notice that function <seemfa marker="#casefold/1"><c>casefold/1</c></seemfa>
should be used when converting a string to
be tested for equality.
</p>
@@ -622,7 +622,7 @@ ÖÄÅ</pre>
<p>
Converts <c><anno>String</anno></c> to uppercase.
</p>
- <p>See also <seealso marker="#titlecase/1"><c>titlecase/1</c></seealso>.</p>
+ <p>See also <seemfa marker="#titlecase/1"><c>titlecase/1</c></seemfa>.</p>
<p><em>Example:</em></p>
<pre>
1> <input>string:uppercase("Michał").</input>
@@ -632,22 +632,23 @@ ÖÄÅ</pre>
</funcs>
- <section>
- <marker id="oldapi"/>
- <title>Obsolete API functions</title>
- <p>Here follows the function of the old API.
- These functions only work on a list of Latin-1 characters.
- </p>
- <note><p>
- The functions are kept for backward compatibility, but are
- not recommended.
- They will be deprecated in a future release.
- </p>
- <p>Any undocumented functions in <c>string</c> are not to be used.</p>
- </note>
- </section>
+
<funcs>
+ <fsdescription>
+ <marker id="oldapi"/>
+ <title>Obsolete API functions</title>
+ <p>Here follows the function of the old API.
+ These functions only work on a list of Latin-1 characters.
+ </p>
+ <note><p>
+ The functions are kept for backward compatibility, but are
+ not recommended.
+ They will be deprecated in a future release.
+ </p>
+ <p>Any undocumented functions in <c>string</c> are not to be used.</p>
+ </note>
+ </fsdescription>
<func>
<name name="centre" arity="2" since=""/>
<name name="centre" arity="3" since=""/>
@@ -656,9 +657,9 @@ ÖÄÅ</pre>
<p>Returns a string, where <c><anno>String</anno></c> is centered in the
string and surrounded by blanks or <c><anno>Character</anno></c>.
The resulting string has length <c><anno>Number</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#pad/3"><c>pad/3</c></seealso>.
+ <seemfa marker="#pad/3"><c>pad/3</c></seemfa>.
</p>
</desc>
</func>
@@ -671,9 +672,9 @@ ÖÄÅ</pre>
<p>Returns a string consisting of <c><anno>Number</anno></c> characters
<c><anno>Character</anno></c>. Optionally, the string can end with
string <c><anno>Tail</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="lists#duplicate/2"><c>lists:duplicate/2</c></seealso>.</p>
+ <seemfa marker="lists#duplicate/2"><c>lists:duplicate/2</c></seemfa>.</p>
</desc>
</func>
@@ -685,9 +686,9 @@ ÖÄÅ</pre>
<p>Returns the index of the first occurrence of
<c><anno>Character</anno></c> in <c><anno>String</anno></c>. Returns
<c>0</c> if <c><anno>Character</anno></c> does not occur.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#find/2"><c>find/2</c></seealso>.</p>
+ <seemfa marker="#find/2"><c>find/2</c></seemfa>.</p>
</desc>
</func>
@@ -699,13 +700,13 @@ ÖÄÅ</pre>
<c><anno>String2</anno></c> to form a new string
<c><anno>String3</anno></c>, which is returned.</p>
<p>
- This function is <seealso marker="#oldapi">obsolete</seealso>.
+ This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use <c>[<anno>String1</anno>, <anno>String2</anno>]</c> as
<c>Data</c> argument, and call
- <seealso marker="unicode#characters_to_list/2">
- <c>unicode:characters_to_list/2</c></seealso> or
- <seealso marker="unicode#characters_to_binary/2">
- <c>unicode:characters_to_binary/2</c></seealso>
+ <seemfa marker="unicode#characters_to_list/2">
+ <c>unicode:characters_to_list/2</c></seemfa> or
+ <seemfa marker="unicode#characters_to_binary/2">
+ <c>unicode:characters_to_binary/2</c></seemfa>
to flatten the output.
</p>
</desc>
@@ -717,9 +718,9 @@ ÖÄÅ</pre>
<desc>
<p>Returns a string containing <c><anno>String</anno></c> repeated
<c><anno>Number</anno></c> times.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="lists#duplicate/2"><c>lists:duplicate/2</c></seealso>.</p>
+ <seemfa marker="lists#duplicate/2"><c>lists:duplicate/2</c></seemfa>.</p>
</desc>
</func>
@@ -730,9 +731,9 @@ ÖÄÅ</pre>
<p>Returns the length of the maximum initial segment of
<c><anno>String</anno></c>, which consists entirely of characters
not from <c><anno>Chars</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#take/3"><c>take/3</c></seealso>.</p>
+ <seemfa marker="#take/3"><c>take/3</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> string:cspan("\t abcdef", " \t").
@@ -746,9 +747,9 @@ ÖÄÅ</pre>
<desc>
<p>Returns a string with the elements of <c><anno>StringList</anno></c>
separated by the string in <c><anno>Separator</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="lists#join/2"><c>lists:join/2</c></seealso>.</p>
+ <seemfa marker="lists#join/2"><c>lists:join/2</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> join(["one", "two", "three"], ", ").
@@ -766,10 +767,10 @@ ÖÄÅ</pre>
fixed. If <c>length(<anno>String</anno>)</c> &lt;
<c><anno>Number</anno></c>, then <c><anno>String</anno></c> is padded
with blanks or <c><anno>Character</anno></c>s.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#pad/2"><c>pad/2</c></seealso> or
- <seealso marker="#pad/3"><c>pad/3</c></seealso>.</p>
+ <seemfa marker="#pad/2"><c>pad/2</c></seemfa> or
+ <seemfa marker="#pad/3"><c>pad/3</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> string:left("Hello",10,$.).
@@ -782,9 +783,9 @@ ÖÄÅ</pre>
<fsummary>Return the length of a string.</fsummary>
<desc>
<p>Returns the number of characters in <c><anno>String</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#length/1"><c>length/1</c></seealso>.</p>
+ <seemfa marker="#length/1"><c>length/1</c></seemfa>.</p>
</desc>
</func>
@@ -796,9 +797,9 @@ ÖÄÅ</pre>
<p>Returns the index of the last occurrence of
<c><anno>Character</anno></c> in <c><anno>String</anno></c>. Returns
<c>0</c> if <c><anno>Character</anno></c> does not occur.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#find/3"><c>find/3</c></seealso>.</p>
+ <seemfa marker="#find/3"><c>find/3</c></seemfa>.</p>
</desc>
</func>
@@ -812,9 +813,9 @@ ÖÄÅ</pre>
fixed. If the length of <c>(<anno>String</anno>)</c> &lt;
<c><anno>Number</anno></c>, then <c><anno>String</anno></c> is padded
with blanks or <c><anno>Character</anno></c>s.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#pad/3"><c>pad/3</c></seealso>.</p>
+ <seemfa marker="#pad/3"><c>pad/3</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> string:right("Hello", 10, $.).
@@ -830,9 +831,9 @@ ÖÄÅ</pre>
<c><anno>SubString</anno></c> begins in <c><anno>String</anno></c>.
Returns <c>0</c> if <c><anno>SubString</anno></c>
does not exist in <c><anno>String</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#find/3"><c>find/3</c></seealso>.</p>
+ <seemfa marker="#find/3"><c>find/3</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> string:rstr(" Hello Hello World World ", "Hello World").
@@ -847,9 +848,9 @@ ÖÄÅ</pre>
<p>Returns the length of the maximum initial segment of
<c><anno>String</anno></c>, which consists entirely of characters
from <c><anno>Chars</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#take/2"><c>take/2</c></seealso>.</p>
+ <seemfa marker="#take/2"><c>take/2</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> string:span("\t abcdef", " \t").
@@ -865,9 +866,9 @@ ÖÄÅ</pre>
<c><anno>SubString</anno></c> begins in <c><anno>String</anno></c>.
Returns <c>0</c> if <c><anno>SubString</anno></c>
does not exist in <c><anno>String</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#find/2"><c>find/2</c></seealso>.</p>
+ <seemfa marker="#find/2"><c>find/2</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> string:str(" Hello Hello World World ", "Hello World").
@@ -887,9 +888,9 @@ ÖÄÅ</pre>
or <c>both</c>, indicates from which direction blanks are to be
removed. <c>strip/1</c> is equivalent to
<c>strip(String, both)</c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#trim/3"><c>trim/3</c></seealso>.</p>
+ <seemfa marker="#trim/3"><c>trim/3</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> string:strip("...Hello.....", both, $.).
@@ -905,9 +906,9 @@ ÖÄÅ</pre>
<p>Returns a substring of <c><anno>String</anno></c>, starting at
position <c><anno>Start</anno></c> to the end of the string, or to
and including position <c><anno>Stop</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#slice/3"><c>slice/3</c></seealso>.</p>
+ <seemfa marker="#slice/3"><c>slice/3</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
sub_string("Hello World", 4, 8).
@@ -923,9 +924,9 @@ sub_string("Hello World", 4, 8).
<p>Returns a substring of <c><anno>String</anno></c>, starting at
position <c><anno>Start</anno></c>, and ending at the end of the
string or at length <c><anno>Length</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#slice/3"><c>slice/3</c></seealso>.</p>
+ <seemfa marker="#slice/3"><c>slice/3</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> substr("Hello World", 4, 5).
@@ -941,9 +942,9 @@ sub_string("Hello World", 4, 8).
<p>Returns the word in position <c><anno>Number</anno></c> of
<c><anno>String</anno></c>. Words are separated by blanks or
<c><anno>Character</anno></c>s.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#nth_lexeme/3"><c>nth_lexeme/3</c></seealso>.</p>
+ <seemfa marker="#nth_lexeme/3"><c>nth_lexeme/3</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> string:sub_word(" Hello old boy !",3,$o).
@@ -965,11 +966,11 @@ sub_string("Hello World", 4, 8).
<p>The specified string or character is case-converted. Notice that
the supported character set is ISO/IEC 8859-1 (also called Latin 1);
all values outside this set are unchanged</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso> use
- <seealso marker="#lowercase/1"><c>lowercase/1</c></seealso>,
- <seealso marker="#uppercase/1"><c>uppercase/1</c></seealso>,
- <seealso marker="#titlecase/1"><c>titlecase/1</c></seealso> or
- <seealso marker="#casefold/1"><c>casefold/1</c></seealso>.</p>
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl> use
+ <seemfa marker="#lowercase/1"><c>lowercase/1</c></seemfa>,
+ <seemfa marker="#uppercase/1"><c>uppercase/1</c></seemfa>,
+ <seemfa marker="#titlecase/1"><c>titlecase/1</c></seemfa> or
+ <seemfa marker="#casefold/1"><c>casefold/1</c></seemfa>.</p>
</desc>
</func>
@@ -987,9 +988,9 @@ sub_string("Hello World", 4, 8).
adjacent separator characters in <c><anno>String</anno></c>
are treated as one. That is, there are no empty
strings in the resulting list of tokens.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#lexemes/2"><c>lexemes/2</c></seealso>.</p>
+ <seemfa marker="#lexemes/2"><c>lexemes/2</c></seemfa>.</p>
</desc>
</func>
@@ -1000,9 +1001,9 @@ sub_string("Hello World", 4, 8).
<desc>
<p>Returns the number of words in <c><anno>String</anno></c>, separated
by blanks or <c><anno>Character</anno></c>.</p>
- <p>This function is <seealso marker="#oldapi">obsolete</seealso>.
+ <p>This function is <seeerl marker="#oldapi">obsolete</seeerl>.
Use
- <seealso marker="#lexemes/2"><c>lexemes/2</c></seealso>.</p>
+ <seemfa marker="#lexemes/2"><c>lexemes/2</c></seemfa>.</p>
<p><em>Example:</em></p>
<code type="none">
> words(" Hello old boy!", $o).
diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml
index f15b1a2dd3..6a44219084 100644
--- a/lib/stdlib/doc/src/supervisor.xml
+++ b/lib/stdlib/doc/src/supervisor.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2018</year>
+ <year>1996</year><year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -35,16 +35,16 @@
supervises other processes called child processes. A child
process can either be another supervisor or a worker process.
Worker processes are normally implemented using one of the
- <seealso marker="gen_event"><c>gen_event</c></seealso>,
- <seealso marker="gen_server"><c>gen_server</c></seealso>, or
- <seealso marker="gen_statem"><c>gen_statem</c></seealso>
+ <seeerl marker="gen_event"><c>gen_event</c></seeerl>,
+ <seeerl marker="gen_server"><c>gen_server</c></seeerl>, or
+ <seeerl marker="gen_statem"><c>gen_statem</c></seeerl>
behaviors. A supervisor implemented using this module has
a standard set of interface functions and include functionality
for tracing and error reporting. Supervisors are used to build a
hierarchical process structure called a supervision tree, a
nice way to structure a fault-tolerant application. For more
- information, see <seealso marker="doc/design_principles:sup_princ">
- Supervisor Behaviour</seealso> in OTP Design Principles.</p>
+ information, see <seeguide marker="system/design_principles:sup_princ">
+ Supervisor Behaviour</seeguide> in OTP Design Principles.</p>
<p>A supervisor expects the definition of which child processes to
supervise to be specified in a callback module exporting a
@@ -105,13 +105,13 @@ sup_flags() = #{strategy => strategy(), % optional
instances of the same process type, that is, running the same
code.</p>
<p>Functions
- <seealso marker="#delete_child/2"><c>delete_child/2</c></seealso> and
- <seealso marker="#restart_child/2"><c>restart_child/2</c></seealso>
+ <seemfa marker="#delete_child/2"><c>delete_child/2</c></seemfa> and
+ <seemfa marker="#restart_child/2"><c>restart_child/2</c></seemfa>
are invalid for <c>simple_one_for_one</c> supervisors and return
<c>{error,simple_one_for_one}</c> if the specified supervisor
uses this restart strategy.</p>
- <p>Function <seealso marker="#terminate_child/2">
- <c>terminate_child/2</c></seealso> can be used for
+ <p>Function <seemfa marker="#terminate_child/2">
+ <c>terminate_child/2</c></seemfa> can be used for
children under <c>simple_one_for_one</c> supervisors by
specifying the child's <c>pid()</c> as the second argument. If
instead the child specification identifier is used,
@@ -149,7 +149,7 @@ child_spec() = #{id => child_id(), % mandatory
modules => modules()} % optional</pre>
<p>The old tuple format is kept for backwards compatibility,
- see <seealso marker="#type-child_spec">child_spec()</seealso>,
+ see <seetype marker="#child_spec">child_spec()</seetype>,
but the map is preferred.</p>
<list type="bulleted">
@@ -251,8 +251,8 @@ child_spec() = #{id => child_id(), % mandatory
process is an event manager (<c>gen_event</c>) with a
dynamic set of callback modules, value <c>dynamic</c>
must be used. For more information about release handling, see
- <seealso marker="doc/design_principles:release_handling">
- Release Handling</seealso>
+ <seeguide marker="system/design_principles:release_handling">
+ Release Handling</seeguide>
in OTP Design Principles.</p>
<p>The <c>modules</c> key is optional. If it is not specified, it
defaults to <c>[M]</c>, where <c>M</c> comes from the
@@ -278,7 +278,7 @@ child_spec() = #{id => child_id(), % mandatory
<name name="child_spec"/>
<desc><p>The tuple format is kept for backward compatibility
only. A map is preferred; see more details
- <seealso marker="#child_spec">above</seealso>.</p></desc>
+ <seeerl marker="#child_spec">above</seeerl>.</p></desc>
</datatype>
<datatype>
<name name="mfargs"/>
@@ -300,13 +300,25 @@ child_spec() = #{id => child_id(), % mandatory
<name name="shutdown"/>
</datatype>
<datatype>
+ <name name="startchild_err"/>
+ </datatype>
+ <datatype>
+ <name name="startchild_ret"/>
+ </datatype>
+ <datatype>
+ <name name="startlink_err"/>
+ </datatype>
+ <datatype>
+ <name name="startlink_ret"/>
+ </datatype>
+ <datatype>
<name name="strategy"/>
</datatype>
<datatype>
<name name="sup_flags"/>
<desc><p>The tuple format is kept for backward compatibility
only. A map is preferred; see more details
- <seealso marker="#sup_flags">above</seealso>.</p></desc>
+ <seeerl marker="#sup_flags">above</seeerl>.</p></desc>
</datatype>
<datatype>
<name name="sup_ref"/>
@@ -333,8 +345,8 @@ child_spec() = #{id => child_id(), % mandatory
<fsummary>Return counts for the number of child specifications,
active children, supervisors, and workers.</fsummary>
<desc>
- <p>Returns a property list (see <seealso marker="proplists">
- <c>proplists</c></seealso>) containing the
+ <p>Returns a property list (see <seeerl marker="proplists">
+ <c>proplists</c></seeerl>) containing the
counts for each of the following elements of the supervisor's
child specifications and managed processes:</p>
<list type="bulleted">
@@ -361,7 +373,7 @@ child_spec() = #{id => child_id(), % mandatory
</item>
</list>
<p>For a description of <c><anno>SupRef</anno></c>, see
- <seealso marker="#SupRef"><c>start_child/2</c></seealso>.</p>
+ <seeerl marker="#SupRef"><c>start_child/2</c></seeerl>.</p>
</desc>
</func>
@@ -372,10 +384,10 @@ child_spec() = #{id => child_id(), % mandatory
<p>Tells supervisor <c><anno>SupRef</anno></c> to delete the child
specification identified by <c><anno>Id</anno></c>. The corresponding
child process must not be running. Use
- <seealso marker="#terminate_child/2">
- <c>terminate_child/2</c></seealso> to terminate it.</p>
+ <seemfa marker="#terminate_child/2">
+ <c>terminate_child/2</c></seemfa> to terminate it.</p>
<p>For a description of <c><anno>SupRef</anno></c>, see
- <seealso marker="#SupRef"><c>start_child/2</c></seealso>.</p>
+ <seeerl marker="#SupRef"><c>start_child/2</c></seeerl>.</p>
<p>If successful, the function returns <c>ok</c>. If the child
specification identified by <c><anno>Id</anno></c> exists but the
corresponding child process is running or is about to be restarted,
@@ -395,7 +407,7 @@ child_spec() = #{id => child_id(), % mandatory
by <c>Id</c> under supervisor <c>SupRef</c>. The returned
map contains all keys, both mandatory and optional.</p>
<p>For a description of <c><anno>SupRef</anno></c>, see
- <seealso marker="#SupRef"><c>start_child/2</c></seealso>.</p>
+ <seeerl marker="#SupRef"><c>start_child/2</c></seeerl>.</p>
</desc>
</func>
@@ -413,7 +425,7 @@ child_spec() = #{id => child_id(), % mandatory
is automatically deleted when the child terminates; thus,
it is not possible to restart such children.</p>
<p>For a description of <c><anno>SupRef</anno></c>, see
- <seealso marker="#SupRef"><c>start_child/2</c></seealso>.</p>
+ <seeerl marker="#SupRef"><c>start_child/2</c></seeerl>.</p>
<p>If the child specification identified
by <c><anno>Id</anno></c> does not exist, the function
returns <c>{error,not_found}</c>. If the child specification
@@ -528,8 +540,8 @@ child_spec() = #{id => child_id(), % mandatory
<item>
<p>If <c><anno>SupName</anno>={global,Name}</c>, the supervisor is
registered globally as <c>Name</c> using
- <seealso marker="kernel:global#register_name/2">
- <c>global:register_name/2</c></seealso>.</p>
+ <seemfa marker="kernel:global#register_name/2">
+ <c>global:register_name/2</c></seemfa>.</p>
</item>
<item>
<p>If
@@ -539,7 +551,7 @@ child_spec() = #{id => child_id(), % mandatory
export the functions <c>register_name/2</c>,
<c>unregister_name/1</c>, and <c>send/2</c>, which must behave
like the corresponding functions in
- <seealso marker="kernel:global"><c>global</c></seealso>. Thus,
+ <seeerl marker="kernel:global"><c>global</c></seeerl>. Thus,
<c>{via,global,<anno>Name</anno>}</c> is a valid reference.</p>
</item>
</list>
@@ -596,9 +608,9 @@ child_spec() = #{id => child_id(), % mandatory
kept by the supervisor. The child process can later be
restarted by the supervisor. The child process can also be
restarted explicitly by calling
- <seealso marker="#restart_child/2"><c>restart_child/2</c></seealso>.
+ <seemfa marker="#restart_child/2"><c>restart_child/2</c></seemfa>.
Use
- <seealso marker="#delete_child/2"><c>delete_child/2</c></seealso>
+ <seemfa marker="#delete_child/2"><c>delete_child/2</c></seemfa>
to remove the child specification.</p>
<p>If the child is temporary, the child specification is deleted as
soon as the process terminates. This means
@@ -616,7 +628,7 @@ child_spec() = #{id => child_id(), % mandatory
no child specification with the specified <c><anno>Id</anno></c>, the
function returns <c>{error,not_found}</c>.</p>
<p>For a description of <c><anno>SupRef</anno></c>, see
- <seealso marker="#SupRef"><c>start_child/2</c></seealso>.</p>
+ <seeerl marker="#SupRef"><c>start_child/2</c></seeerl>.</p>
</desc>
</func>
@@ -629,10 +641,10 @@ child_spec() = #{id => child_id(), % mandatory
specifications and child processes belonging to
supervisor <c><anno>SupRef</anno></c>.</p>
<p>Notice that calling this function when supervising many
- childrens under low memory conditions can cause an
+ children under low memory conditions can cause an
out of memory exception.</p>
<p>For a description of <c><anno>SupRef</anno></c>, see
- <seealso marker="#SupRef"><c>start_child/2</c></seealso>.</p>
+ <seeerl marker="#SupRef"><c>start_child/2</c></seeerl>.</p>
<p>The following information is given for each child
specification/process:</p>
<list type="bulleted">
@@ -658,13 +670,14 @@ child_spec() = #{id => child_id(), % mandatory
</func>
</funcs>
- <section>
- <title>Callback Functions</title>
- <p>The following function must be exported from a
- <c>supervisor</c> callback module.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>The following function must be exported from a
+ <c>supervisor</c> callback module.</p>
+ </fsdescription>
<func>
<name since="">Module:init(Args) -> Result</name>
<fsummary>Return a supervisor specification.</fsummary>
@@ -672,13 +685,13 @@ child_spec() = #{id => child_id(), % mandatory
<v>Args = term()</v>
<v>Result = {ok,{SupFlags,[ChildSpec]}} | ignore</v>
<v>&nbsp;SupFlags =
- <seealso marker="#type-sup_flags"><c>sup_flags()</c></seealso></v>
+ <seetype marker="#sup_flags"><c>sup_flags()</c></seetype></v>
<v>&nbsp;ChildSpec =
- <seealso marker="#type-child_spec"><c>child_spec()</c></seealso></v>
+ <seetype marker="#child_spec"><c>child_spec()</c></seetype></v>
</type>
<desc>
<p>Whenever a supervisor is started using
- <seealso marker="#start_link/2"><c>start_link/2,3</c></seealso>,
+ <seemfa marker="#start_link/2"><c>start_link/2,3</c></seemfa>,
this function is called by
the new process to find out about restart strategy, maximum
restart intensity, and child specifications.</p>
@@ -689,8 +702,8 @@ child_spec() = #{id => child_id(), % mandatory
supervisor. <c>[ChildSpec]</c> is a list of valid child
specifications defining which child processes the supervisor
must start and monitor. See the discussion in section
- <seealso marker="#supervision_princ">
- <c>Supervision Principles</c></seealso> earlier.</p>
+ <seeerl marker="#supervision_princ">
+ <c>Supervision Principles</c></seeerl> earlier.</p>
<p>Notice that when the restart strategy is
<c>simple_one_for_one</c>, the list of child specifications
must be a list with one child specification only.
@@ -698,24 +711,24 @@ child_spec() = #{id => child_id(), % mandatory
No child process is then started
during the initialization phase, but all children are assumed
to be started dynamically using
- <seealso marker="#start_child/2"><c>start_child/2</c></seealso>.</p>
+ <seemfa marker="#start_child/2"><c>start_child/2</c></seemfa>.</p>
<p>The function can also return <c>ignore</c>.</p>
<p>Notice that this function can also be called as a part of a code
upgrade procedure. Therefore, the function is not to have any side
effects. For more information about code upgrade of supervisors, see
section
- <seealso marker="doc/design_principles:appup_cookbook#sup">Changing
- a Supervisor</seealso> in OTP Design Principles.</p>
+ <seeguide marker="system/design_principles:appup_cookbook#sup">Changing
+ a Supervisor</seeguide> in OTP Design Principles.</p>
</desc>
</func>
</funcs>
<section>
<title>See Also</title>
- <p><seealso marker="gen_event"><c>gen_event(3)</c></seealso>,
- <seealso marker="gen_statem"><c>gen_statem(3)</c></seealso>,
- <seealso marker="gen_server"><c>gen_server(3)</c></seealso>,
- <seealso marker="sys"><c>sys(3)</c></seealso></p>
+ <p><seeerl marker="gen_event"><c>gen_event(3)</c></seeerl>,
+ <seeerl marker="gen_statem"><c>gen_statem(3)</c></seeerl>,
+ <seeerl marker="gen_server"><c>gen_server(3)</c></seeerl>,
+ <seeerl marker="sys"><c>sys(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/supervisor_bridge.xml b/lib/stdlib/doc/src/supervisor_bridge.xml
index 3280bea610..97d0ae292a 100644
--- a/lib/stdlib/doc/src/supervisor_bridge.xml
+++ b/lib/stdlib/doc/src/supervisor_bridge.xml
@@ -39,15 +39,15 @@
a supervisor and the subsystem. It behaves like a real supervisor to
its own supervisor, but has a different interface than a real
supervisor to the subsystem. For more information, see
- <seealso marker="doc/design_principles:sup_princ">
- Supervisor Behaviour</seealso> in OTP Design Principles.
+ <seeguide marker="system/design_principles:sup_princ">
+ Supervisor Behaviour</seeguide> in OTP Design Principles.
</p>
<p>A supervisor bridge assumes the functions for starting and stopping
the subsystem to be located in a callback module exporting a
predefined set of functions.</p>
- <p>The <seealso marker="sys"><c>sys(3)</c></seealso> module can be used
+ <p>The <seeerl marker="sys"><c>sys(3)</c></seeerl> module can be used
for debugging a supervisor bridge.</p>
<p>Unless otherwise stated, all functions in this module fail if
@@ -75,8 +75,8 @@
<p>If <c><anno>SupBridgeName</anno>={global,<anno>Name</anno>}</c>,
the supervisor bridge is registered globally as
<c><anno>Name</anno></c> using
- <seealso marker="kernel:global#register_name/2">
- <c>global:register_name/2</c></seealso>.</p>
+ <seemfa marker="kernel:global#register_name/2">
+ <c>global:register_name/2</c></seemfa>.</p>
</item>
<item>
<p>If
@@ -86,7 +86,7 @@
<c>Module</c> callback is to export functions
<c>register_name/2</c>, <c>unregister_name/1</c>, and <c>send/2</c>,
which are to behave like the corresponding functions in
- <seealso marker="kernel:global"><c>global</c></seealso>.
+ <seeerl marker="kernel:global"><c>global</c></seeerl>.
Thus, <c>{via,global,GlobalName}</c> is a valid reference.</p>
</item>
</list>
@@ -125,13 +125,14 @@
</func>
</funcs>
- <section>
- <title>Callback Functions</title>
- <p>The following functions must be exported from a
- <c>supervisor_bridge</c> callback module.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Callback Functions</title>
+ <p>The following functions must be exported from a
+ <c>supervisor_bridge</c> callback module.</p>
+ </fsdescription>
<func>
<name since="">Module:init(Args) -> Result</name>
<fsummary>Initialize process and start subsystem.</fsummary>
@@ -144,7 +145,7 @@
</type>
<desc>
<p>Whenever a supervisor bridge is started using
- <seealso marker="#start_link/2"><c>start_link/2,3</c></seealso>,
+ <seemfa marker="#start_link/2"><c>start_link/2,3</c></seemfa>,
this function is called
by the new process to start the subsystem and initialize.</p>
<p><c>Args</c> is the <c>Args</c> argument provided to the start
@@ -188,8 +189,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="supervisor"><c>supervisor(3)</c></seealso>,
- <seealso marker="sys"><c>sys(3)</c></seealso></p>
+ <p><seeerl marker="supervisor"><c>supervisor(3)</c></seeerl>,
+ <seeerl marker="sys"><c>sys(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml
index ebea054fff..8227be6824 100644
--- a/lib/stdlib/doc/src/sys.xml
+++ b/lib/stdlib/doc/src/sys.xml
@@ -41,7 +41,7 @@
understand system messages, such as debug messages and code change. These
functions must be used to implement the use of system messages for a
process; either directly, or through standard behaviors, such as
- <seealso marker="gen_server"><c>gen_server</c></seealso>.</p>
+ <seeerl marker="gen_server"><c>gen_server</c></seeerl>.</p>
<p>The default time-out is 5000 ms, unless otherwise specified.
<c>timeout</c> defines the time to wait for the process to
respond to a request. If the process does not respond, the
@@ -50,8 +50,8 @@
<marker id="dbg_opt"/>
<p>The functions make references to a debug structure.
The debug structure is a list of <c>dbg_opt()</c>, which is an internal
- data type used by function <seealso marker="#handle_system_msg/6">
- <c>handle_system_msg/6</c></seealso>. No debugging is performed if it is
+ data type used by function <seemfa marker="#handle_system_msg/6">
+ <c>handle_system_msg/6</c></seemfa>. No debugging is performed if it is
an empty list.</p>
</description>
@@ -66,8 +66,8 @@
<c>{system, From, Msg}</c>. The content and meaning of
this message are not interpreted by the
receiving process module. When a system message is received, function
- <seealso marker="#handle_system_msg/6">
- <c>handle_system_msg/6</c></seealso>
+ <seemfa marker="#handle_system_msg/6">
+ <c>handle_system_msg/6</c></seemfa>
is called to handle the request.</p>
</item>
<item>
@@ -82,7 +82,7 @@
<item>
<p>If the modules used to implement the process change dynamically
during runtime, the process must understand one more message. An
- example is the <seealso marker="gen_event"><c>gen_event</c></seealso>
+ example is the <seeerl marker="gen_event"><c>gen_event</c></seeerl>
processes. The message is <c>{_Label, {From, Ref}, get_modules}</c>.
The reply to this message is <c>From ! {Ref, Modules}</c>, where
<c>Modules</c> is a list of the currently active modules in the
@@ -332,8 +332,8 @@
provided for convenience, allowing developers to avoid having to
create their own state extraction functions and also avoid having
to interactively extract the state from the return values of
- <seealso marker="#get_status-1"><c>get_status/1</c></seealso> or
- <seealso marker="#get_status-2"><c>get_status/2</c></seealso>
+ <seemfa marker="#get_status/1"><c>get_status/1</c></seemfa> or
+ <seemfa marker="#get_status/2"><c>get_status/2</c></seemfa>
while debugging.</p>
</note>
<p>The value of <c><anno>State</anno></c> varies for different types of
@@ -341,19 +341,19 @@
<list type="bulleted">
<item>
<p>For a
- <seealso marker="gen_server"><c>gen_server</c></seealso>
+ <seeerl marker="gen_server"><c>gen_server</c></seeerl>
process, the returned <c><anno>State</anno></c>
is the state of the callback module.</p>
</item>
<item>
<p>For a
- <seealso marker="gen_statem"><c>gen_statem</c></seealso>
+ <seeerl marker="gen_statem"><c>gen_statem</c></seeerl>
process, <c><anno>State</anno></c> is the tuple
<c>{CurrentState,CurrentData}</c>.</p>
</item>
<item>
<p>For a
- <seealso marker="gen_event"><c>gen_event</c></seealso>
+ <seeerl marker="gen_event"><c>gen_event</c></seeerl>
process, <c><anno>State</anno></c> is a list of tuples,
where each tuple corresponds to an event handler registered
in the process and contains <c>{Module, Id, HandlerState}</c>,
@@ -378,9 +378,9 @@
<p>If the callback module exports a function <c>system_get_state/1</c>,
it is called in the target process to get its state. Its argument is
the same as the <c>Misc</c> value returned by
- <seealso marker="#get_status-1"><c>get_status/1,2</c></seealso>, and
- function <seealso marker="#Module:system_get_state/1">
- <c>Module:system_get_state/1</c></seealso> is expected to extract the
+ <seemfa marker="#get_status/1"><c>get_status/1,2</c></seemfa>, and
+ function <seemfa marker="#Module:system_get_state/1">
+ <c>Module:system_get_state/1</c></seemfa> is expected to extract the
state of the callback module from it. Function
<c>system_get_state/1</c> must return <c>{ok, State}</c>, where
<c>State</c> is the state of the callback module.</p>
@@ -394,14 +394,14 @@
<c>Class</c> and <c>Reason</c> indicate details of the exception.</p>
<p>Function <c>system_get_state/1</c> is primarily useful for
user-defined behaviors and modules that implement OTP
- <seealso marker="#special_process">special processes</seealso>.
+ <seeerl marker="#special_process">special processes</seeerl>.
The <c>gen_server</c>,
<c>gen_statem</c>, and <c>gen_event</c> OTP
behavior modules export this function, so callback modules for those
behaviors need not to supply their own.</p>
<p>For more information about a process, including its state, see
- <seealso marker="#get_status-1"><c>get_status/1</c></seealso> and
- <seealso marker="#get_status-2"><c>get_status/2</c></seealso>.</p>
+ <seemfa marker="#get_status/1"><c>get_status/1</c></seemfa> and
+ <seemfa marker="#get_status/2"><c>get_status/2</c></seemfa>.</p>
</desc>
</func>
@@ -415,16 +415,16 @@
processes, for example:</p>
<list type="bulleted">
<item>
- <p>A <seealso marker="gen_server"><c>gen_server</c></seealso>
+ <p>A <seeerl marker="gen_server"><c>gen_server</c></seeerl>
process returns the state of the callback module.</p>
</item>
<item>
- <p>A <seealso marker="gen_statem"><c>gen_statem</c></seealso>
+ <p>A <seeerl marker="gen_statem"><c>gen_statem</c></seeerl>
process returns information, such as its current
state name and state data.</p>
</item>
<item>
- <p>A <seealso marker="gen_event"><c>gen_event</c></seealso>
+ <p>A <seeerl marker="gen_event"><c>gen_event</c></seeerl>
process returns information about each of its
registered handlers.</p>
</item>
@@ -434,12 +434,12 @@
can also change the value of <c><anno>Misc</anno></c>
by exporting a function <c>format_status/2</c>, which contributes
module-specific information. For details, see
- <seealso marker="gen_server#Module:format_status/2">
- <c>gen_server:format_status/2</c></seealso>,
- <seealso marker="gen_statem#Module:format_status/2">
- <c>gen_statem:format_status/2</c></seealso>, and
- <seealso marker="gen_event#Module:format_status/2">
- <c>gen_event:format_status/2</c></seealso>.</p>
+ <seemfa marker="gen_server#Module:format_status/2">
+ <c>gen_server:format_status/2</c></seemfa>,
+ <seemfa marker="gen_statem#Module:format_status/2">
+ <c>gen_statem:format_status/2</c></seemfa>, and
+ <seemfa marker="gen_event#Module:format_status/2">
+ <c>gen_event:format_status/2</c></seemfa>.</p>
</desc>
</func>
@@ -475,8 +475,8 @@
are printed to <c>standard_io</c>.</p>
<p>The events are formatted with a function that is defined by the
process that generated the event (with a call to
- <seealso marker="#handle_debug/4">
- <c>handle_debug/4</c>)</seealso>.</p>
+ <seemfa marker="#handle_debug/4">
+ <c>handle_debug/4</c>)</seemfa>.</p>
</desc>
</func>
@@ -488,7 +488,7 @@
<p>Enables or disables the logging of all system events in text
format to the file. The events are formatted with a function that is
defined by the process that generated the event (with a call to
- <seealso marker="#handle_debug/4"><c>handle_debug/4</c></seealso>).
+ <seemfa marker="#handle_debug/4"><c>handle_debug/4</c></seemfa>).
The file is opened with encoding UTF-8.</p>
</desc>
</func>
@@ -500,7 +500,7 @@
<desc>
<p>Turns off all debugging for the process. This includes
functions that are installed explicitly with function
- <seealso marker="#install/2"><c>install/2,3</c></seealso>,
+ <seemfa marker="#install/2"><c>install/2,3</c></seemfa>,
for example, triggers.</p>
</desc>
</func>
@@ -535,13 +535,13 @@
processes as follows:</p>
<list type="bulleted">
<item>
- <p>For a <seealso marker="gen_server"><c>gen_server</c></seealso>
+ <p>For a <seeerl marker="gen_server"><c>gen_server</c></seeerl>
process, <c><anno>State</anno></c> is the state of the callback
module and <c><anno>NewState</anno></c>
is a new instance of that state.</p>
</item>
<item>
- <p>For a <seealso marker="gen_statem"><c>gen_statem</c></seealso>
+ <p>For a <seeerl marker="gen_statem"><c>gen_statem</c></seeerl>
process, <c><anno>State</anno></c> is the
tuple <c>{CurrentState,CurrentData}</c>,
and <c><anno>NewState</anno></c> is a
@@ -549,7 +549,7 @@
a new current state, new state data, or both.</p>
</item>
<item>
- <p>For a <seealso marker="gen_event"><c>gen_event</c></seealso>
+ <p>For a <seeerl marker="gen_event"><c>gen_event</c></seeerl>
process, <c><anno>State</anno></c> is the
tuple <c>{Module, Id, HandlerState}</c> as follows:</p>
<taglist>
@@ -591,12 +591,12 @@
succeed in changing the states of other event
handlers registered in the same <c>gen_event</c> process.</p>
<p>If the callback module exports a
- <seealso marker="#Module:system_replace_state/2">
- <c>system_replace_state/2</c></seealso> function, it is called in the
+ <seemfa marker="#Module:system_replace_state/2">
+ <c>system_replace_state/2</c></seemfa> function, it is called in the
target process to replace its state using <c>StateFun</c>. Its two
arguments are <c>StateFun</c> and <c>Misc</c>, where
<c>Misc</c> is the same as the <c>Misc</c> value returned by
- <seealso marker="#get_status-1"><c>get_status/1,2</c></seealso>.
+ <seemfa marker="#get_status/1"><c>get_status/1,2</c></seemfa>.
A <c>system_replace_state/2</c> function is expected to return
<c>{ok, NewState, NewMisc}</c>, where <c>NewState</c> is the new state
of the callback module, obtained by calling <c>StateFun</c>, and
@@ -606,7 +606,7 @@
module within it).</p>
<p>If the callback module does not export a
<c>system_replace_state/2</c> function,
- <seealso marker="#replace_state/2"><c>replace_state/2,3</c></seealso>
+ <seemfa marker="#replace_state/2"><c>replace_state/2,3</c></seemfa>
assumes that <c>Misc</c> is the state of the callback module,
passes it to <c>StateFun</c> and uses the return value as
both the new state and as the new value of <c>Misc</c>.</p>
@@ -621,7 +621,7 @@
<c>{callback_failed, StateFun, {Class, Reason}}</c>.</p>
<p>Function <c>system_replace_state/2</c> is primarily useful for
user-defined behaviors and modules that implement OTP
- <seealso marker="#special_process">special processes</seealso>. The
+ <seeerl marker="#special_process">special processes</seeerl>. The
OTP behavior modules <c>gen_server</c>,
<c>gen_statem</c>, and <c>gen_event</c>
export this function, so callback modules for those
@@ -680,22 +680,23 @@
<p>Prints all system events on <c>standard_io</c>. The events are
formatted with a function that is defined by the process that
generated the event (with a call to
- <seealso marker="#handle_debug/4"><c>handle_debug/4</c></seealso>).
+ <seemfa marker="#handle_debug/4"><c>handle_debug/4</c></seemfa>).
</p>
</desc>
</func>
</funcs>
- <section>
- <title>Process Implementation Functions</title>
- <marker id="special_process"/>
- <p>The following functions are used when implementing a
- special process. This is an ordinary process, which does not use a
- standard behavior, but a process that understands the standard system
- messages.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Process Implementation Functions</title>
+ <marker id="special_process"/>
+ <p>The following functions are used when implementing a
+ special process. This is an ordinary process, which does not use a
+ standard behavior, but a process that understands the standard system
+ messages.</p>
+ </fsdescription>
<func>
<name name="debug_options" arity="1" since=""/>
<fsummary>Convert a list of options to a debug structure.</fsummary>
@@ -785,7 +786,7 @@
<p>Prints the logged system events in the debug structure,
using <c>FormFunc</c> as defined when the event was
generated by a call to
- <seealso marker="#handle_debug/4"><c>handle_debug/4</c></seealso>.</p>
+ <seemfa marker="#handle_debug/4"><c>handle_debug/4</c></seemfa>.</p>
</desc>
</func>
@@ -796,7 +797,7 @@
<p>
Returns the logged system events in the debug structure,
that is the last argument to
- <seealso marker="#handle_debug/4"><c>handle_debug/4</c></seealso>.
+ <seemfa marker="#handle_debug/4"><c>handle_debug/4</c></seemfa>.
</p>
</desc>
</func>
@@ -813,8 +814,8 @@
<v>NMisc = term()</v>
</type>
<desc>
- <p>Called from <seealso marker="#handle_system_msg/6">
- <c>handle_system_msg/6</c></seealso> when the process is to perform a
+ <p>Called from <seemfa marker="#handle_system_msg/6">
+ <c>handle_system_msg/6</c></seemfa> when the process is to perform a
code change. The code change is used when the
internal data structure has changed. This function
converts argument <c>Misc</c> to the new data
@@ -829,12 +830,12 @@
<fsummary>Called when the process is to continue its execution.</fsummary>
<type>
<v>Parent = pid()</v>
- <v>Debug = [<seealso marker="#type-dbg_opt">dbg_opt()</seealso>]</v>
+ <v>Debug = [<seetype marker="#dbg_opt">dbg_opt()</seetype>]</v>
<v>Misc = term()</v>
</type>
<desc>
- <p>Called from <seealso marker="#handle_system_msg/6">
- <c>handle_system_msg/6</c></seealso> when the process is to continue
+ <p>Called from <seemfa marker="#handle_system_msg/6">
+ <c>handle_system_msg/6</c></seemfa> when the process is to continue
its execution (for example, after it has been
suspended). This function never returns.</p>
</desc>
@@ -849,11 +850,11 @@
<v>State = term()</v>
</type>
<desc>
- <p>Called from <seealso marker="#handle_system_msg/6">
- <c>handle_system_msg/6</c></seealso>
+ <p>Called from <seemfa marker="#handle_system_msg/6">
+ <c>handle_system_msg/6</c></seemfa>
when the process is to return a term that reflects its current state.
<c>State</c> is the value returned by
- <seealso marker="#get_state/2"><c>get_state/2</c></seealso>.</p>
+ <seemfa marker="#get_state/2"><c>get_state/2</c></seemfa>.</p>
</desc>
</func>
@@ -869,10 +870,10 @@
<v>NMisc = term()</v>
</type>
<desc>
- <p>Called from <seealso marker="#handle_system_msg/6">
- <c>handle_system_msg/6</c></seealso> when the process is to replace
+ <p>Called from <seemfa marker="#handle_system_msg/6">
+ <c>handle_system_msg/6</c></seemfa> when the process is to replace
its current state. <c>NState</c> is the value returned by
- <seealso marker="#replace_state/3"><c>replace_state/3</c></seealso>.
+ <seemfa marker="#replace_state/3"><c>replace_state/3</c></seemfa>.
</p>
</desc>
</func>
@@ -883,12 +884,12 @@
<type>
<v>Reason = term()</v>
<v>Parent = pid()</v>
- <v>Debug = [<seealso marker="#type-dbg_opt">dbg_opt()</seealso>]</v>
+ <v>Debug = [<seetype marker="#dbg_opt">dbg_opt()</seetype>]</v>
<v>Misc = term()</v>
</type>
<desc>
- <p>Called from <seealso marker="#handle_system_msg/6">
- <c>handle_system_msg/6</c></seealso> when the process is to terminate.
+ <p>Called from <seemfa marker="#handle_system_msg/6">
+ <c>handle_system_msg/6</c></seemfa> when the process is to terminate.
For example, this function is called when
the process is suspended and its parent orders shutdown.
It gives the process a chance to do a cleanup. This function never
diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml
index 165eecfbb0..56342b94be 100644
--- a/lib/stdlib/doc/src/timer.xml
+++ b/lib/stdlib/doc/src/timer.xml
@@ -41,12 +41,18 @@
process.</p>
<p>Successful evaluations of the timer functions give return values
containing a timer reference, denoted <c>TRef</c>. By using
- <seealso marker="#cancel/1"><c>cancel/1</c></seealso>,
+ <seemfa marker="#cancel/1"><c>cancel/1</c></seemfa>,
the returned reference can be used to cancel any
requested action. A <c>TRef</c> is an Erlang term, which contents
must not be changed.</p>
<p>The time-outs are not exact, but are <em>at least</em> as long
as requested.</p>
+ <p>Creating timers using
+ <seemfa marker="erts:erlang#send_after/3">erlang:send_after/3</seemfa> and
+ <seemfa marker="erts:erlang#start_timer/3">erlang:start_timer/3</seemfa>
+ is much more efficient than using the timers provided by this module. See
+ <seeguide marker="system/efficiency_guide:commoncaveats#timer-module">the
+ Timer Module section in the Efficiency Guide</seeguide>.</p>
</description>
<datatypes>
@@ -165,10 +171,10 @@
<anno>T2</anno> - <anno>T1</anno></c> in <em>microseconds</em>,
where <c><anno>T1</anno></c> and <c><anno>T2</anno></c>
are time-stamp tuples on the same format as returned from
- <seealso marker="erts:erlang#timestamp/0">
- <c>erlang:timestamp/0</c></seealso> or
- <seealso marker="kernel:os#timestamp/0">
- <c>os:timestamp/0</c></seealso>.</p>
+ <seemfa marker="erts:erlang#timestamp/0">
+ <c>erlang:timestamp/0</c></seemfa> or
+ <seemfa marker="kernel:os#timestamp/0">
+ <c>os:timestamp/0</c></seemfa>.</p>
</desc>
</func>
@@ -195,6 +201,9 @@
can also be an atom of a registered name.)</p>
<p>Returns <c>{ok, <anno>TRef</anno>}</c> or
<c>{error, <anno>Reason</anno>}</c>.</p>
+ <p>See also
+ <seeguide marker="system/efficiency_guide:commoncaveats#timer-module">
+ the Timer Module section in the Efficiency Guide</seeguide>.</p>
</item>
<tag><c>send_after/2</c></tag>
<item>
@@ -253,7 +262,7 @@
is needed. This is useful during development, but in a
target system the server is to be started explicitly. Use
configuration parameters for
- <seealso marker="kernel:index">Kernel</seealso> for this.</p>
+ <seeapp marker="kernel:index">Kernel</seeapp> for this.</p>
</desc>
</func>
@@ -270,8 +279,8 @@
<item>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
<anno>Arguments</anno>)</c> and measures the elapsed real time as
- reported by <seealso marker="erts:erlang#monotonic_time/0">
- <c>erlang:monotonic_time/0</c></seealso>.</p>
+ reported by <seemfa marker="erts:erlang#monotonic_time/0">
+ <c>erlang:monotonic_time/0</c></seemfa>.</p>
<p>Returns <c>{<anno>Time</anno>, <anno>Value</anno>}</c>, where
<c><anno>Time</anno></c> is the elapsed real time in
<em>microseconds</em>, and <c><anno>Value</anno></c> is what is
@@ -318,27 +327,27 @@ timer:cancel(R),
<section>
<title>Notes</title>
<p>A timer can always be removed by calling
- <seealso marker="#cancel/1"><c>cancel/1</c></seealso>.</p>
+ <seemfa marker="#cancel/1"><c>cancel/1</c></seemfa>.</p>
<p>An interval timer, that is, a timer created by evaluating any of the
functions
- <seealso marker="#apply_interval/4"><c>apply_interval/4</c></seealso>,
- <seealso marker="#send_interval/3"><c>send_interval/3</c></seealso>, and
- <seealso marker="#send_interval/2"><c>send_interval/2</c></seealso>
+ <seemfa marker="#apply_interval/4"><c>apply_interval/4</c></seemfa>,
+ <seemfa marker="#send_interval/3"><c>send_interval/3</c></seemfa>, and
+ <seemfa marker="#send_interval/2"><c>send_interval/2</c></seemfa>
is linked to the process to which the timer performs its task.</p>
<p>A one-shot timer, that is, a timer created by evaluating any of the
functions
- <seealso marker="#apply_after/4"><c>apply_after/4</c></seealso>,
- <seealso marker="#send_after/3"><c>send_after/3</c></seealso>,
- <seealso marker="#send_after/2"><c>send_after/2</c></seealso>,
- <seealso marker="#exit_after/3"><c>exit_after/3</c></seealso>,
- <seealso marker="#exit_after/2"><c>exit_after/2</c></seealso>,
- <seealso marker="#kill_after/2"><c>kill_after/2</c></seealso>, and
- <seealso marker="#kill_after/1"><c>kill_after/1</c></seealso>
+ <seemfa marker="#apply_after/4"><c>apply_after/4</c></seemfa>,
+ <seemfa marker="#send_after/3"><c>send_after/3</c></seemfa>,
+ <seemfa marker="#send_after/2"><c>send_after/2</c></seemfa>,
+ <seemfa marker="#exit_after/3"><c>exit_after/3</c></seemfa>,
+ <seemfa marker="#exit_after/2"><c>exit_after/2</c></seemfa>,
+ <seemfa marker="#kill_after/2"><c>kill_after/2</c></seemfa>, and
+ <seemfa marker="#kill_after/1"><c>kill_after/1</c></seemfa>
is not linked to any process. Hence, such a timer is removed only
when it reaches its time-out, or if it is explicitly removed by a call to
- <seealso marker="#cancel/1"><c>cancel/1</c></seealso>.</p>
+ <seemfa marker="#cancel/1"><c>cancel/1</c></seemfa>.</p>
</section>
</erlref>
diff --git a/lib/stdlib/doc/src/unicode.xml b/lib/stdlib/doc/src/unicode.xml
index d626f7473d..1f0133de67 100644
--- a/lib/stdlib/doc/src/unicode.xml
+++ b/lib/stdlib/doc/src/unicode.xml
@@ -56,23 +56,23 @@
canonical-equivalent Unicode characters as equal. All characters
should thus be normalized to one form once on the system borders.
One of the following functions can convert characters to their
- normalized forms <seealso marker="#characters_to_nfc_list/1">
- <c>characters_to_nfc_list/1</c></seealso>,
- <seealso marker="#characters_to_nfc_binary/1">
- <c>characters_to_nfc_binary/1</c></seealso>,
- <seealso marker="#characters_to_nfd_list/1">
- <c>characters_to_nfd_list/1</c></seealso> or
- <seealso marker="#characters_to_nfd_binary/1">
- <c>characters_to_nfd_binary/1</c></seealso>.
+ normalized forms <seemfa marker="#characters_to_nfc_list/1">
+ <c>characters_to_nfc_list/1</c></seemfa>,
+ <seemfa marker="#characters_to_nfc_binary/1">
+ <c>characters_to_nfc_binary/1</c></seemfa>,
+ <seemfa marker="#characters_to_nfd_list/1">
+ <c>characters_to_nfd_list/1</c></seemfa> or
+ <seemfa marker="#characters_to_nfd_binary/1">
+ <c>characters_to_nfd_binary/1</c></seemfa>.
For general text
- <seealso marker="#characters_to_nfc_list/1">
- <c>characters_to_nfc_list/1</c></seealso> or
- <seealso marker="#characters_to_nfc_binary/1">
- <c>characters_to_nfc_binary/1</c></seealso> is preferred, and
+ <seemfa marker="#characters_to_nfc_list/1">
+ <c>characters_to_nfc_list/1</c></seemfa> or
+ <seemfa marker="#characters_to_nfc_binary/1">
+ <c>characters_to_nfc_binary/1</c></seemfa> is preferred, and
for identifiers one of the compatibility normalization
functions, such as
- <seealso marker="#characters_to_nfkc_list/1">
- <c>characters_to_nfkc_list/1</c></seealso>,
+ <seemfa marker="#characters_to_nfkc_list/1">
+ <c>characters_to_nfkc_list/1</c></seemfa>,
is preferred for security reasons.
The normalization functions where introduced in OTP 20.
Additional information on normalization can be found in the
@@ -177,8 +177,8 @@
<name name="characters_to_binary" arity="3" since=""/>
<fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary>
<desc>
- <p>Behaves as <seealso marker="#characters_to_list/2">
- <c>characters_to_list/2</c></seealso>, but produces a binary
+ <p>Behaves as <seemfa marker="#characters_to_list/2">
+ <c>characters_to_list/2</c></seemfa>, but produces a binary
instead of a Unicode list.</p>
<p><c><anno>InEncoding</anno></c> defines how input is to be interpreted
if binaries are present in <c>Data</c></p>
@@ -203,8 +203,8 @@
<p>The atoms <c>big</c> and <c>little</c> denote big- or little-endian
encoding.</p>
<p>Errors and exceptions occur as in
- <seealso marker="#characters_to_list/2">
- <c>characters_to_list/2</c></seealso>, but the second element
+ <seemfa marker="#characters_to_list/2">
+ <c>characters_to_list/2</c></seemfa>, but the second element
in tuple <c>error</c> or <c>incomplete</c> is a <c>binary()</c>
and not a <c>list()</c>.</p>
</desc>
@@ -256,8 +256,8 @@
combinations of Unicode characters into a pure Unicode
string in list representation for further processing. For
writing the data to an external entity, the reverse function
- <seealso marker="#characters_to_binary/3">
- <c>characters_to_binary/3</c></seealso>
+ <seemfa marker="#characters_to_binary/3">
+ <c>characters_to_binary/3</c></seemfa>
comes in handy.</p>
<p>Option <c>unicode</c> is an alias for <c>utf8</c>, as this is the
preferred encoding for Unicode characters in
diff --git a/lib/stdlib/doc/src/unicode_usage.xml b/lib/stdlib/doc/src/unicode_usage.xml
index 789e063c12..cc90f0dafb 100644
--- a/lib/stdlib/doc/src/unicode_usage.xml
+++ b/lib/stdlib/doc/src/unicode_usage.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>1999</year>
- <year>2017</year>
+ <year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -55,8 +55,8 @@
source code, with enhancements to many of the applications to
support both Unicode encoded filenames and support for UTF-8
encoded files in many circumstances. Most notable is the
- support for UTF-8 in files read by <seealso
- marker="kernel:file#consult/1"><c>file:consult/1</c></seealso>,
+ support for UTF-8 in files read by <seemfa
+ marker="kernel:file#consult/1"><c>file:consult/1</c></seemfa>,
release handler support for UTF-8, and more support for
Unicode character sets in the I/O system.</p></item>
@@ -221,7 +221,7 @@
issues occur, which is why UTF-16 exists in both a big-endian
and a little-endian variant.</p>
<p>In Erlang, the full UTF-16 range is supported when applicable, like
- in the <seealso marker="stdlib:unicode"><c>unicode</c></seealso>
+ in the <seeerl marker="stdlib:unicode"><c>unicode</c></seeerl>
module and in the bit syntax.</p>
</item>
<tag>UTF-32</tag>
@@ -278,13 +278,13 @@
<p>The Unicode characters need to be processed by the Erlang
program, which is why library functions must be able to handle
them. In some cases functionality has been added to already
- existing interfaces (as the <seealso
- marker="stdlib:string"><c>string</c></seealso> module now can
+ existing interfaces (as the <seeerl
+ marker="stdlib:string"><c>string</c></seeerl> module now can
handle strings with any code points). In some cases new
- functionality or options have been added (as in the <seealso
- marker="stdlib:io"><c>io</c></seealso> module, the file
- handling, the <seealso
- marker="stdlib:unicode"><c>unicode</c></seealso> module, and
+ functionality or options have been added (as in the <seeerl
+ marker="stdlib:io"><c>io</c></seeerl> module, the file
+ handling, the <seeerl
+ marker="stdlib:unicode"><c>unicode</c></seeerl> module, and
the bit syntax). Today most modules in Kernel and
STDLIB, as well as the VM are Unicode-aware.</p>
</item>
@@ -343,7 +343,7 @@
%% -*- coding: utf-8 -*-</code>
<p>This of course requires your editor to support UTF-8 as well. The
same comment is also interpreted by functions like
- <seealso marker="kernel:file#consult/1"><c>file:consult/1</c></seealso>,
+ <seemfa marker="kernel:file#consult/1"><c>file:consult/1</c></seemfa>,
the release handler, and so on, so that you can have all text files
in your source directories in UTF-8 encoding.</p>
</item>
@@ -382,11 +382,11 @@
<p>Only if a string contains code points &lt; 256, can it be directly
converted to a binary by using, for example,
- <seealso marker="erts:erlang#iolist_to_binary/1"><c>erlang:iolist_to_binary/1</c></seealso>
+ <seemfa marker="erts:erlang#iolist_to_binary/1"><c>erlang:iolist_to_binary/1</c></seemfa>
or can be sent directly to a port. If the string contains Unicode
characters &gt; 255, an encoding must be decided upon and the string is to
be converted to a binary in the preferred encoding using
- <seealso marker="stdlib:unicode#characters_to_binary/1"><c>unicode:characters_to_binary/1,2,3</c></seealso>.
+ <seemfa marker="stdlib:unicode#characters_to_binary/1"><c>unicode:characters_to_binary/1,2,3</c></seemfa>.
Strings are not generally lists of bytes, as they were before
Erlang/OTP R13, they are lists of characters. Characters are not
generally bytes, they are Unicode code points.</p>
@@ -395,7 +395,7 @@
store textual data in binaries instead of lists, mainly because they are
more compact (one byte per character instead of two words per character,
as is the case with lists). Using
- <seealso marker="erts:erlang#list_to_binary/1"><c>erlang:list_to_binary/1</c></seealso>,
+ <seemfa marker="erts:erlang#list_to_binary/1"><c>erlang:list_to_binary/1</c></seemfa>,
an ISO Latin-1 Erlang string can be converted into a binary, effectively
using bytewise encoding: one byte per character. This was convenient for
those limited Erlang strings, but cannot be done for arbitrary Unicode
@@ -428,7 +428,7 @@ chardata() = charlist() | unicode_binary()
charlist() = maybe_improper_list(char() | unicode_binary() | charlist(),
unicode_binary() | nil())</code>
- <p>The module <seealso marker="stdlib:unicode"><c>unicode</c></seealso>
+ <p>The module <seeerl marker="stdlib:unicode"><c>unicode</c></seeerl>
even supports similar mixes with binaries containing other encodings than
UTF-8, but that is a special case to allow for conversions to and from
external data:</p>
@@ -448,8 +448,8 @@ external_charlist() = maybe_improper_list(char() | external_unicode_binary() |
<p><marker id="unicode_in_erlang"/>As from Erlang/OTP R16, Erlang
source files can be written in UTF-8 or bytewise (<c>latin1</c>)
encoding. For information about how to state the encoding of an
- Erlang source file, see the <seealso
- marker="stdlib:epp#encoding"><c>epp(3)</c></seealso> module. As
+ Erlang source file, see the <seeerl
+ marker="stdlib:epp#encoding"><c>epp(3)</c></seeerl> module. As
from Erlang/OTP R16, strings and comments can be written using
Unicode. As from Erlang/OTP 20, also atoms and functions can be
written using Unicode. Modules, applications, and nodes must still be
@@ -578,8 +578,8 @@ Eshell V5.10.1 (abort with ^G)
bytewise encoded) as string data.</p>
<p>These heuristics are also used by
- <seealso marker="stdlib:io#format/2"><c>io:format/2</c></seealso>,
- <seealso marker="stdlib:io_lib#format/2"><c>io_lib:format/2</c></seealso>,
+ <seemfa marker="stdlib:io#format/2"><c>io:format/2</c></seemfa>,
+ <seemfa marker="stdlib:io_lib#format/2"><c>io_lib:format/2</c></seemfa>,
and friends when modifier <c>t</c> is used with <c>~p</c> or
<c>~P</c>:</p>
@@ -644,7 +644,7 @@ en_US.UTF-8</pre>
language and character type settings.</p>
<p>To investigate what Erlang thinks about the terminal, the call
- <seealso marker="stdlib:io#getopts/1"><c>io:getopts()</c></seealso>
+ <seemfa marker="stdlib:io#getopts/1"><c>io:getopts()</c></seemfa>
can be used when the shell is started:</p>
<pre>
@@ -728,9 +728,9 @@ Eshell V5.10.1 (abort with ^G)
<taglist>
<tag>Mandatory Unicode file naming</tag>
<item>
- <p>Windows and, for most common uses, MacOS X enforce Unicode support
- for filenames. All files created in the file system have names that
- can consistently be interpreted. In MacOS X, all filenames are
+ <p>Windows, Android and, for most cases, MacOS X enforce Unicode support
+ for filenames. All files created in the file system have names that can
+ consistently be interpreted. In MacOS X and Android, all filenames are
retrieved in UTF-8 encoding. In Windows, each system call handling
filenames has a special Unicode-aware variant, giving much the same
effect. There are no filenames on these systems that are not Unicode
@@ -740,7 +740,7 @@ Eshell V5.10.1 (abort with ^G)
translated to the proper name encoding for the underlying operating
system and file system.</p>
<p>Doing, for example, a
- <seealso marker="kernel:file#list_dir/1"><c>file:list_dir/1</c></seealso>
+ <seemfa marker="kernel:file#list_dir/1"><c>file:list_dir/1</c></seemfa>
on one of these systems can return Unicode lists with code points
&gt; 255, depending on the content of the file system.</p>
</item>
@@ -759,7 +759,7 @@ Eshell V5.10.1 (abort with ^G)
<p>In <c>latin1</c> mode, filenames are bytewise encoded. This allows
for list representation of all filenames in the system. However, a
a file named "Östersund.txt", appears in
- <seealso marker="kernel:file#list_dir/1"><c>file:list_dir/1</c></seealso>
+ <seemfa marker="kernel:file#list_dir/1"><c>file:list_dir/1</c></seemfa>
either as "Östersund.txt" (if the filename was encoded in bytewise
ISO Latin-1 by the program creating the file) or more probably as
<c>[195,150,115,116,101,114,115,117,110,100]</c>, which is a list
@@ -767,7 +767,7 @@ Eshell V5.10.1 (abort with ^G)
filename translation on such a system, non-UTF-8 filenames are
ignored by functions like <c>file:list_dir/1</c>. They can be
retrieved with function
- <seealso marker="kernel:file#list_dir_all/1"><c>file:list_dir_all/1</c></seealso>,
+ <seemfa marker="kernel:file#list_dir_all/1"><c>file:list_dir_all/1</c></seemfa>,
but wrongly encoded filenames appear as &quot;raw filenames&quot;.
</p>
</item>
@@ -802,14 +802,14 @@ Eshell V5.10.1 (abort with ^G)
<p>Unicode filename translation is turned on with switch <c>+fnu</c>. On
Linux, a VM started without explicitly stating the filename translation
mode defaults to <c>latin1</c> as the native filename encoding. On
- Windows and MacOS X, the default behavior is that of Unicode filename
- translation. Therefore
- <seealso marker="kernel:file#native_name_encoding/0"><c>file:native_name_encoding/0</c></seealso>
+ Windows, MacOS X and Android, the default behavior is that of Unicode
+ filename translation. Therefore
+ <seemfa marker="kernel:file#native_name_encoding/0"><c>file:native_name_encoding/0</c></seemfa>
by default returns <c>utf8</c> on those systems (Windows does not use
UTF-8 on the file system level, but this can safely be ignored by the
Erlang programmer). The default behavior can, as stated earlier, be
changed using option <c>+fnu</c> or <c>+fnl</c> to the VM, see the
- <seealso marker="erts:erl"><c>erl</c></seealso> program. If the VM is
+ <seecom marker="erts:erl"><c>erl</c></seecom> program. If the VM is
started in Unicode filename translation mode,
<c>file:native_name_encoding/0</c> returns atom <c>utf8</c>. Switch
<c>+fnu</c> can be followed by <c>w</c>, <c>i</c>, or <c>e</c> to control
@@ -832,7 +832,7 @@ Eshell V5.10.1 (abort with ^G)
</list>
<p>Notice that
- <seealso marker="kernel:file#read_link/1"><c>file:read_link/1</c></seealso>
+ <seemfa marker="kernel:file#read_link/1"><c>file:read_link/1</c></seemfa>
always returns an error if the link points to an invalid filename.</p>
<p>In Unicode filename mode, filenames given to BIF <c>open_port/2</c> with
@@ -840,7 +840,7 @@ Eshell V5.10.1 (abort with ^G)
is the parameter list specified in option <c>args</c> available when
using <c>spawn_executable</c>. The UTF-8 translation of arguments can be
avoided using binaries, see section
- <seealso marker="#notes-about-raw-filenames">Notes About Raw Filenames</seealso>.
+ <seeguide marker="#notes-about-raw-filenames">Notes About Raw Filenames</seeguide>.
</p>
<p>Notice that the file encoding options specified when opening a file has
@@ -876,7 +876,7 @@ Eshell V5.10.1 (abort with ^G)
therefore expects UTF-8 file naming). The ISO Latin-1 name is not valid
UTF-8 and one can be tempted to think that automatic conversion in, for
example,
- <seealso marker="kernel:file#list_dir/1"><c>file:list_dir/1</c></seealso>
+ <seemfa marker="kernel:file#list_dir/1"><c>file:list_dir/1</c></seemfa>
is a good idea. But what would happen if we later tried to open the file
and have the name as a Unicode list (magically converted from the ISO
Latin-1 filename)? The VM converts the filename to UTF-8, as this is
@@ -892,7 +892,7 @@ Eshell V5.10.1 (abort with ^G)
are invalid under the encoding. By the common function
<c>file:list_dir/1</c>, the wrongly encoded filenames are ignored in
Unicode filename translation mode, but by function
- <seealso marker="kernel:file#list_dir_all/1"><c>file:list_dir_all/1</c></seealso>
+ <seemfa marker="kernel:file#list_dir_all/1"><c>file:list_dir_all/1</c></seemfa>
the filenames with invalid encoding are returned as &quot;raw&quot;
filenames, that is, as binaries.</p>
@@ -964,9 +964,9 @@ Eshell V5.10.1 (abort with ^G)
Unicode.</p>
<p>If Unicode filenames are enabled, the calls to
- <seealso marker="kernel:os#getenv/0"><c>os:getenv/0,1</c></seealso>,
- <seealso marker="kernel:os#putenv/2"><c>os:putenv/2</c></seealso>, and
- <seealso marker="kernel:os#unsetenv/1"><c>os:unsetenv/1</c></seealso>
+ <seemfa marker="kernel:os#getenv/0"><c>os:getenv/0,1</c></seemfa>,
+ <seemfa marker="kernel:os#putenv/2"><c>os:putenv/2</c></seemfa>, and
+ <seemfa marker="kernel:os#unsetenv/1"><c>os:unsetenv/1</c></seemfa>
handle Unicode strings. On Unix-like platforms, the built-in functions
translate environment variables in UTF-8 to/from Unicode strings, possibly
with code points &gt; 255. On Windows, the Unicode versions of the
@@ -982,8 +982,8 @@ Eshell V5.10.1 (abort with ^G)
non-textual or byte-oriented data (such as <c>gen_tcp</c>).</p>
<p>Modules handling textual data (such as
- <seealso marker="stdlib:io_lib"><c>io_lib</c></seealso> and
- <seealso marker="stdlib:string"><c>string</c></seealso> are sometimes
+ <seeerl marker="stdlib:io_lib"><c>io_lib</c></seeerl> and
+ <seeerl marker="stdlib:string"><c>string</c></seeerl> are sometimes
subject to conversion or extension to be able to handle Unicode
characters.</p>
@@ -997,7 +997,7 @@ Eshell V5.10.1 (abort with ^G)
<taglist>
<tag><c>unicode</c></tag>
<item>
- <p>The <seealso marker="stdlib:unicode"><c>unicode</c></seealso>
+ <p>The <seeerl marker="stdlib:unicode"><c>unicode</c></seeerl>
module is clearly Unicode-aware. It contains functions for conversion
between different Unicode formats and some utilities for identifying
byte order marks. Few programs handling Unicode data survive without
@@ -1005,7 +1005,7 @@ Eshell V5.10.1 (abort with ^G)
</item>
<tag><c>io</c></tag>
<item>
- <p>The <seealso marker="stdlib:io"><c>io</c></seealso> module has been
+ <p>The <seeerl marker="stdlib:io"><c>io</c></seeerl> module has been
extended along with the actual I/O protocol to handle Unicode data.
This means that many functions require binaries to be in UTF-8, and
there are modifiers to format control sequences to allow for output
@@ -1016,36 +1016,36 @@ Eshell V5.10.1 (abort with ^G)
<p>I/O-servers throughout the system can handle Unicode data and have
options for converting data upon output or input to/from the device.
As shown earlier, the
- <seealso marker="stdlib:shell"><c>shell</c></seealso> module has
+ <seeerl marker="stdlib:shell"><c>shell</c></seeerl> module has
support for Unicode terminals and the
- <seealso marker="kernel:file"><c>file</c></seealso> module
+ <seeerl marker="kernel:file"><c>file</c></seeerl> module
allows for translation to and from various Unicode formats on
disk.</p>
<p>Reading and writing of files with Unicode data is, however, not best
done with the <c>file</c> module, as its interface is
byte-oriented. A file opened with a Unicode encoding (like UTF-8) is
best read or written using the
- <seealso marker="stdlib:io"><c>io</c></seealso> module.</p>
+ <seeerl marker="stdlib:io"><c>io</c></seeerl> module.</p>
</item>
<tag><c>re</c></tag>
<item>
- <p>The <seealso marker="stdlib:re"><c>re</c></seealso> module allows
+ <p>The <seeerl marker="stdlib:re"><c>re</c></seeerl> module allows
for matching Unicode strings as a special option. As the library is
centered on matching in binaries, the Unicode support is
UTF-8-centered.</p>
</item>
<tag><c>wx</c></tag>
<item>
- <p>The graphical library <seealso marker="wx:wx"><c>wx</c></seealso>
+ <p>The graphical library <seeerl marker="wx:wx"><c>wx</c></seeerl>
has extensive support for Unicode text.</p></item>
</taglist>
- <p>The <seealso marker="stdlib:string"><c>string</c></seealso>
+ <p>The <seeerl marker="stdlib:string"><c>string</c></seeerl>
module works perfectly for Unicode strings and ISO Latin-1
- strings, except the language-dependent functions <seealso
- marker="stdlib:string#uppercase/1"><c>string:uppercase/1</c></seealso>
- and <seealso
- marker="stdlib:string#lowercase/1"><c>string:lowercase/1</c></seealso>.
+ strings, except the language-dependent functions <seemfa
+ marker="stdlib:string#uppercase/1"><c>string:uppercase/1</c></seemfa>
+ and <seemfa
+ marker="stdlib:string#lowercase/1"><c>string:lowercase/1</c></seemfa>.
These two functions can never function correctly for Unicode
characters in their current form, as there are language and locale
issues to consider when converting text between cases. Converting
@@ -1074,9 +1074,9 @@ Eshell V5.10.1 (abort with ^G)
terminal or pipe.</p>
<p>A file can have an encoding option that makes it generally usable by the
- <seealso marker="stdlib:io"><c>io</c></seealso> module (for example
+ <seeerl marker="stdlib:io"><c>io</c></seeerl> module (for example
<c>{encoding,utf8}</c>), but is by default opened as a byte-oriented file.
- The <seealso marker="kernel:file"><c>file</c></seealso> module is
+ The <seeerl marker="kernel:file"><c>file</c></seeerl> module is
byte-oriented, so only ISO Latin-1 characters can be written using that
module. Use the <c>io</c> module if Unicode data is to be output to a
file with other <c>encoding</c> than <c>latin1</c> (bytewise encoding).
@@ -1088,7 +1088,7 @@ Eshell V5.10.1 (abort with ^G)
as that is the way to access files other than text files, byte by byte.
As with ports, you can write encoded data into a file by "manually"
converting the data to the encoding of choice (using the
- <seealso marker="stdlib:unicode"><c>unicode</c></seealso> module or the
+ <seeerl marker="stdlib:unicode"><c>unicode</c></seeerl> module or the
bit syntax) and then output it on a bytewise (<c>latin1</c>) encoded
file.</p>
@@ -1096,10 +1096,10 @@ Eshell V5.10.1 (abort with ^G)
<list type="bulleted">
<item><p>Use the
- <seealso marker="kernel:file"><c>file</c></seealso> module for
+ <seeerl marker="kernel:file"><c>file</c></seeerl> module for
files opened for bytewise access (<c>{encoding,latin1}</c>).</p>
</item>
- <item><p>Use the <seealso marker="stdlib:io"><c>io</c></seealso> module
+ <item><p>Use the <seeerl marker="stdlib:io"><c>io</c></seeerl> module
when accessing files with any other encoding (for example
<c>{encoding,uf8}</c>).</p>
</item>
@@ -1147,31 +1147,31 @@ ok
is started with flag <c>+fna</c> (which is default from
Erlang/OTP 17.0).</p>
<p>You can check the setting of this by calling
- <seealso marker="stdlib:io#getopts/1"><c>io:getopts()</c></seealso>,
+ <seemfa marker="stdlib:io#getopts/1"><c>io:getopts()</c></seemfa>,
which gives you an option list containing <c>{encoding,unicode}</c>
or <c>{encoding,latin1}</c>.</p>
</item>
<tag>The <c>+pc</c> {<c>unicode</c>|<c>latin1</c>} flag to
- <seealso marker="erts:erl"><c>erl(1)</c></seealso></tag>
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom></tag>
<item>
<p>This flag affects what is interpreted as string data when doing
heuristic string detection in the shell and in
- <seealso marker="stdlib:io"><c>io</c></seealso>/
- <seealso marker="stdlib:io_lib#format/2"><c>io_lib:format</c></seealso>
+ <seeerl marker="stdlib:io"><c>io</c></seeerl>/
+ <seemfa marker="stdlib:io_lib#format/2"><c>io_lib:format</c></seemfa>
with the <c>"~tp"</c> and <c>~tP</c> formatting instructions, as
described earlier.</p>
<p>You can check this option by calling
- <seealso marker="stdlib:io#printable_range/0"><c>io:printable_range/0</c></seealso>,
+ <seemfa marker="stdlib:io#printable_range/0"><c>io:printable_range/0</c></seemfa>,
which returns <c>unicode</c> or <c>latin1</c>. To be compatible with
future (expected) extensions to the settings, rather use
- <seealso marker="stdlib:io_lib#printable_list/1"><c>io_lib:printable_list/1</c></seealso>
+ <seemfa marker="stdlib:io_lib#printable_list/1"><c>io_lib:printable_list/1</c></seemfa>
to check if a list is printable according to the setting. That
function takes into account new possible settings returned from
<c>io:printable_range/0</c>.</p>
</item>
<tag>The <c>+fn</c>{<c>l</c>|<c>u</c>|<c>a</c>}
[{<c>w</c>|<c>i</c>|<c>e</c>}] flag to
- <seealso marker="erts:erl"><c>erl(1)</c></seealso></tag>
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom></tag>
<item>
<p>This flag affects how the filenames are to be interpreted. On
operating systems with transparent file naming, this must be
@@ -1199,10 +1199,10 @@ ok
</item>
</list>
<p>The filename translation mode can be read with function
- <seealso marker="kernel:file#native_name_encoding/0"><c>file:native_name_encoding/0</c></seealso>,
+ <seemfa marker="kernel:file#native_name_encoding/0"><c>file:native_name_encoding/0</c></seemfa>,
which returns <c>latin1</c> (bytewise encoding) or <c>utf8</c>.</p>
</item>
- <tag><seealso marker="stdlib:epp#default_encoding/0"><c>epp:default_encoding/0</c></seealso></tag>
+ <tag><seemfa marker="stdlib:epp#default_encoding/0"><c>epp:default_encoding/0</c></seemfa></tag>
<item>
<p>This function returns the default encoding for Erlang source files
(if no encoding comment is present) in the currently running release.
@@ -1210,10 +1210,10 @@ ok
As from Erlang/OTP 17.0, <c>utf8</c> is returned.</p>
<p>The encoding of each file can be specified using comments as
described in the
- <seealso marker="stdlib:epp#encoding"><c>epp(3)</c></seealso> module.
+ <seeerl marker="stdlib:epp#encoding"><c>epp(3)</c></seeerl> module.
</p>
</item>
- <tag><seealso marker="stdlib:io#setopts/1"><c>io:setopts/1,2</c></seealso>
+ <tag><seemfa marker="stdlib:io#setopts/1"><c>io:setopts/1,2</c></seemfa>
and flags <c>-oldshell</c>/<c>-noshell</c></tag>
<item>
<p>When Erlang is started with <c>-oldshell</c> or <c>-noshell</c>, the
@@ -1221,7 +1221,7 @@ ok
encoding, while an interactive shell defaults to what the
environment variables says.</p>
<p>You can set the encoding of a file or other I/O server with function
- <seealso marker="stdlib:io#setopts/1"><c>io:setopts/2</c></seealso>.
+ <seemfa marker="stdlib:io#setopts/1"><c>io:setopts/2</c></seemfa>.
This can also be set when opening a file. Setting the terminal (or
other <c>standard_io</c> server) unconditionally to option
<c>{encoding,utf8}</c> implies that UTF-8 encoded characters are
@@ -1231,7 +1231,7 @@ ok
writing or reading text files in a known encoding.</p>
<p>You can retrieve the <c>encoding</c> setting for an I/O server with
function
- <seealso marker="stdlib:io#getopts/1"><c>io:getopts()</c></seealso>.
+ <seemfa marker="stdlib:io#getopts/1"><c>io:getopts()</c></seemfa>.
</p>
</item>
</taglist>
@@ -1251,7 +1251,7 @@ ok
text. This code outlines how to open a file that is believed to
have a BOM, and sets the files encoding and position for further
sequential reading (preferably using the
- <seealso marker="stdlib:io"><c>io</c></seealso> module).</p>
+ <seeerl marker="stdlib:io"><c>io</c></seeerl> module).</p>
<p>Notice that error handling is omitted from the code:</p>
@@ -1265,12 +1265,12 @@ open_bom_file_for_reading(File) -&gt;
{ok,F}.</code>
<p>Function
- <seealso marker="stdlib:unicode#bom_to_encoding/1"><c>unicode:bom_to_encoding/1</c></seealso>
+ <seemfa marker="stdlib:unicode#bom_to_encoding/1"><c>unicode:bom_to_encoding/1</c></seemfa>
identifies the encoding from a binary of at least four bytes. It
returns, along with a term suitable for setting the encoding of the
file, the byte length of the BOM, so that the file position can be set
accordingly. Notice that function
- <seealso marker="kernel:file#position/2"><c>file:position/2</c></seealso>
+ <seemfa marker="kernel:file#position/2"><c>file:position/2</c></seemfa>
always works on byte-offsets, so that the byte length of the BOM is
needed.</p>
@@ -1284,7 +1284,7 @@ open_bom_file_for_writing(File,Encoding) -&gt;
{ok,F}.</code>
<p>The file is in both these cases then best processed using the
- <seealso marker="stdlib:io"><c>io</c></seealso> module, as the functions
+ <seeerl marker="stdlib:io"><c>io</c></seeerl> module, as the functions
in that module can handle code points beyond the ISO Latin-1 range.</p>
</section>
@@ -1293,8 +1293,8 @@ open_bom_file_for_writing(File,Encoding) -&gt;
<p>When reading and writing to Unicode-aware entities, like a
file opened for Unicode translation, you probably want to format text
strings using the functions in the
- <seealso marker="stdlib:io"><c>io</c></seealso> module or the
- <seealso marker="stdlib:io_lib"><c>io_lib</c></seealso> module. For
+ <seeerl marker="stdlib:io"><c>io</c></seeerl> module or the
+ <seeerl marker="stdlib:io_lib"><c>io_lib</c></seeerl> module. For
backward compatibility reasons, these functions do not accept any list
as a string, but require a special <em>translation modifier</em> when
working with Unicode texts. The modifier is <c>t</c>. When applied to
@@ -1324,11 +1324,11 @@ ok</pre>
bytewise-encoded characters and not UTF-8.</p>
<p>Function
- <seealso marker="stdlib:io_lib#format/2"><c>io_lib:format/2</c></seealso>
+ <seemfa marker="stdlib:io_lib#format/2"><c>io_lib:format/2</c></seemfa>
behaves similarly. It is defined to return a deep list of characters
and the output can easily be converted to binary data for outputting on
any device by a simple
- <seealso marker="erts:erlang#list_to_binary/1"><c>erlang:list_to_binary/1</c></seealso>.
+ <seemfa marker="erts:erlang#list_to_binary/1"><c>erlang:list_to_binary/1</c></seemfa>.
When the translation modifier is used, the list can, however, contain
characters that cannot be stored in one byte. The call to
<c>erlang:list_to_binary/1</c> then fails. However, if the I/O server
@@ -1350,7 +1350,7 @@ ok</pre>
as such, as the Erlang shell uses the Unicode encoding (and is started
with all Unicode characters considered printable). The Unicode list is
valid input to function
- <seealso marker="stdlib:io#put_chars/2"><c>io:put_chars/2</c></seealso>,
+ <seemfa marker="stdlib:io#put_chars/2"><c>io:put_chars/2</c></seemfa>,
so data can be output on any Unicode-capable device. If the device is a
terminal, characters are output in format <c>\x{</c>H...<c>}</c> if
encoding is <c>latin1</c>. Otherwise in UTF-8 (for the non-interactive
@@ -1373,7 +1373,7 @@ ok</pre>
the 7-bit ASCII range are seldom considered valid when decoded as UTF-8.
Therefore one can usually use heuristics to determine if a file is in
UTF-8 or if it is encoded in ISO Latin-1 (one byte per character).
- The <seealso marker="stdlib:unicode"><c>unicode</c></seealso>
+ The <seeerl marker="stdlib:unicode"><c>unicode</c></seeerl>
module can be used to determine if data can be interpreted as UTF-8:</p>
<code>
@@ -1388,7 +1388,7 @@ heuristic_encoding_bin(Bin) when is_binary(Bin) -&gt;
<p>If you do not have a complete binary of the file content, you can
instead chunk through the file and check part by part. The return-tuple
<c>{incomplete,Decoded,Rest}</c> from function
- <seealso marker="stdlib:unicode#characters_to_binary/1"><c>unicode:characters_to_binary/1,2,3</c></seealso>
+ <seemfa marker="stdlib:unicode#characters_to_binary/1"><c>unicode:characters_to_binary/1,2,3</c></seemfa>
comes in handy. The incomplete rest from one chunk of data read from the
file is prepended to the next chunk and we therefore avoid the problem
of character boundaries when reading chunks of bytes in UTF-8
@@ -1415,7 +1415,7 @@ loop_through_file(F,Acc,{ok,Bin}) when is_binary(Bin) -&gt;
<p>Another option is to try to read the whole file in UTF-8 encoding and
see if it fails. Here we need to read the file using function
- <seealso marker="stdlib:io#get_chars/3"><c>io:get_chars/3</c></seealso>,
+ <seemfa marker="stdlib:io#get_chars/3"><c>io:get_chars/3</c></seemfa>,
as we have to read characters with a code point &gt; 255:</p>
<code>
@@ -1454,7 +1454,7 @@ utf8_list_to_string(StrangeList) ->
second time is encoded as UTF-8. A common situation is where you read a
file, byte by byte, but the content is already UTF-8. If you then
convert the bytes to UTF-8, using, for example, the
- <seealso marker="stdlib:unicode"><c>unicode</c></seealso> module, or by
+ <seeerl marker="stdlib:unicode"><c>unicode</c></seeerl> module, or by
writing to a file opened with option <c>{encoding,utf8}</c>, you have
each <em>byte</em> in the input file encoded as UTF-8, not each
character of the original text (one character can have been encoded in
diff --git a/lib/stdlib/doc/src/uri_string.xml b/lib/stdlib/doc/src/uri_string.xml
index 148accf107..dea8e60979 100644
--- a/lib/stdlib/doc/src/uri_string.xml
+++ b/lib/stdlib/doc/src/uri_string.xml
@@ -65,24 +65,27 @@
<p>The functions implemented by this module cover the following use cases:</p>
<list type="bulleted">
<item>Parsing URIs into its components and returing a map<br></br>
- <seealso marker="#parse/1"><c>parse/1</c></seealso>
+ <seemfa marker="#parse/1"><c>parse/1</c></seemfa>
</item>
<item>Recomposing a map of URI components into a URI string<br></br>
- <seealso marker="#recompose/1"><c>recompose/1</c></seealso>
+ <seemfa marker="#recompose/1"><c>recompose/1</c></seemfa>
</item>
<item>Changing inbound binary and percent-encoding of URIs<br></br>
- <seealso marker="#transcode/2"><c>transcode/2</c></seealso>
+ <seemfa marker="#transcode/2"><c>transcode/2</c></seemfa>
</item>
<item>Transforming URIs into a normalized form<br></br>
- <seealso marker="#normalize/1"><c>normalize/1</c></seealso><br></br>
- <seealso marker="#normalize/2"><c>normalize/2</c></seealso>
+ <seemfa marker="#normalize/1"><c>normalize/1</c></seemfa><br></br>
+ <seemfa marker="#normalize/2"><c>normalize/2</c></seemfa>
</item>
<item>Composing form-urlencoded query strings from a list of key-value pairs<br></br>
- <seealso marker="#compose_query/1"><c>compose_query/1</c></seealso><br></br>
- <seealso marker="#compose_query/2"><c>compose_query/2</c></seealso>
+ <seemfa marker="#compose_query/1"><c>compose_query/1</c></seemfa><br></br>
+ <seemfa marker="#compose_query/2"><c>compose_query/2</c></seemfa>
</item>
<item>Dissecting form-urlencoded query strings into a list of key-value pairs<br></br>
- <seealso marker="#dissect_query/1"><c>dissect_query/1</c></seealso>
+ <seemfa marker="#dissect_query/1"><c>dissect_query/1</c></seemfa>
+ </item>
+ <item>Decoding percent-encoded triplets<br></br>
+ <seemfa marker="#percent_decode/1"><c>percent_decode/1</c></seemfa>
</item>
</list>
<p>There are four different encodings present during the handling of URIs:</p>
@@ -150,6 +153,21 @@
<funcs>
<func>
+ <name name="allowed_characters" arity="0" since="OTP 23.2"/>
+ <fsummary>Print allowed characters in URI components.</fsummary>
+ <desc>
+ <p>This is a utility function meant to be used in the shell for printing
+ the allowed characters in each
+ major URI component, and also in the most important characters sets.
+ Please note that this function does not replace the ABNF rules defined by
+ the standards, these character sets are derived directly from those
+ aformentioned rules. For more information see the
+ <seeguide marker="uri_string_usage#percent_encoding">Uniform Resource
+ Identifiers</seeguide> chapter in stdlib's Users Guide.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="compose_query" arity="1" since="OTP 21.0"/>
<fsummary>Compose urlencoded query string.</fsummary>
<desc>
@@ -161,8 +179,8 @@
<url href="https://www.w3.org/TR/html50/">HTML 5.0</url> specification for
non-UTF-8 encodings.
</p>
- <p>See also the opposite operation <seealso marker="#dissect_query/1">
- <c>dissect_query/1</c></seealso>.
+ <p>See also the opposite operation <seemfa marker="#dissect_query/1">
+ <c>dissect_query/1</c></seemfa>.
</p>
<p><em>Example:</em></p>
<pre>
@@ -194,8 +212,8 @@
0x61 to 0x7A, are percent-encoded (U+0025 PERCENT SIGN character (%) followed by
uppercase ASCII hex digits representing the hexadecimal value of the byte).
</p>
- <p>See also the opposite operation <seealso marker="#dissect_query/1">
- <c>dissect_query/1</c></seealso>.
+ <p>See also the opposite operation <seemfa marker="#dissect_query/1">
+ <c>dissect_query/1</c></seemfa>.
</p>
<p><em>Example:</em></p>
<pre>
@@ -221,8 +239,8 @@
<url href="https://www.w3.org/TR/html50/">HTML 5.0</url> specification for
non-UTF-8 encodings.
</p>
- <p>See also the opposite operation <seealso marker="#compose_query/1">
- <c>compose_query/1</c></seealso>.
+ <p>See also the opposite operation <seemfa marker="#compose_query/1">
+ <c>compose_query/1</c></seemfa>.
</p>
<p><em>Example:</em></p>
<pre>
@@ -292,8 +310,8 @@
compliant <c>uri_string()</c> into a <c>uri_map()</c>, that holds the parsed
components of the <c>URI</c>.
If parsing fails, an error tuple is returned.</p>
- <p>See also the opposite operation <seealso marker="#recompose/1">
- <c>recompose/1</c></seealso>.</p>
+ <p>See also the opposite operation <seemfa marker="#recompose/1">
+ <c>recompose/1</c></seemfa>.</p>
<p><em>Example:</em></p>
<pre>
1> <input>uri_string:parse("foo://user@example.com:8042/over/there?name=ferret#nose").</input>
@@ -309,6 +327,37 @@
</func>
<func>
+ <name name="percent_decode" arity="1" since="OTP 23.2"/>
+ <fsummary>Decode percent-decode triplets in the input.</fsummary>
+ <desc>
+ <p>Decodes all percent-encoded triplets in the input that can be both a
+ <c>uri_string()</c> and a <c>uri_map()</c>. Note, that this function performs
+ raw decoding and it shall be used on already parsed URI components. Applying
+ this function directly on a standard URI can effectively change it.</p>
+ <p>If the input encoding is not UTF-8, an error tuple is returned.</p>
+ <p><em>Example:</em></p>
+ <pre>
+1> <input>uri_string:percent_decode(#{host => "localhost-%C3%B6rebro",path => [],</input>
+1> <input>scheme => "http"}).</input>
+#{host => "localhost-örebro",path => [],scheme => "http"}
+2> <![CDATA[uri_string:percent_decode(<<"%C3%B6rebro">>).]]>
+<![CDATA[<<"örebro"/utf8>>]]>
+ </pre>
+ <warning><p>
+ Using <c>uri_string:percent_decode/1</c> directly on a URI is not safe. This
+ example shows, that after each consecutive application of the function
+ the resulting URI will be changed. None of these URIs refer to the same
+ resource.</p>
+ <pre>
+<![CDATA[3> uri_string:percent_decode(<<"http://local%252Fhost/path">>).
+<<"http://local%2Fhost/path">>
+4> uri_string:percent_decode(<<"http://local%2Fhost/path">>).
+<<"http://local/host/path">>]]>
+ </pre></warning>
+ </desc>
+ </func>
+
+ <func>
<name name="recompose" arity="1" since="OTP 21.0"/>
<fsummary>Recompose URI.</fsummary>
<desc>
@@ -316,15 +365,15 @@
<c><anno>URIString</anno></c> (percent-encoded), based on the components of
<c><anno>URIMap</anno></c>.
If the <c><anno>URIMap</anno></c> is invalid, an error tuple is returned.</p>
- <p>See also the opposite operation <seealso marker="#parse/1">
- <c>parse/1</c></seealso>.</p>
+ <p>See also the opposite operation <seemfa marker="#parse/1">
+ <c>parse/1</c></seemfa>.</p>
<p><em>Example:</em></p>
<pre>
1> <input>URIMap = #{fragment => "nose", host => "example.com", path => "/over/there",</input>
1> port => 8042, query => "name=ferret", scheme => "foo", userinfo => "user"}.
-#{fragment => "top",host => "example.com",
- path => "/over/there",port => 8042,query => "?name=ferret",
- scheme => foo,userinfo => "user"}
+#{fragment => "nose",host => "example.com",
+ path => "/over/there",port => 8042,query => "name=ferret",
+ scheme => "foo",userinfo => "user"}
2> <input>uri_string:recompose(URIMap).</input>
"foo://example.com:8042/over/there?name=ferret#nose"</pre>
diff --git a/lib/stdlib/doc/src/uri_string_usage.xml b/lib/stdlib/doc/src/uri_string_usage.xml
new file mode 100644
index 0000000000..72851096b7
--- /dev/null
+++ b/lib/stdlib/doc/src/uri_string_usage.xml
@@ -0,0 +1,370 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2020</year>
+ <year>2020</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>Uniform Resource Identifiers</title>
+ <prepared>Péter Dimitrov</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2020-09-30</date>
+ <rev>PA1</rev>
+ <file>uri_string_usage.xml</file>
+ </header>
+ <section>
+ <title>Basics</title>
+ <p>At the time of writing this document, in October 2020, there are
+ two major standards concerning Universal Resource Identifiers and
+ Universal Resource Locators:</p>
+ <list type="bulleted">
+ <item><p>
+ <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986 - Uniform Resource
+ Identifier (URI): Generic Syntax</url></p></item>
+ <item><p>
+ <url href="https://url.spec.whatwg.org/">WHAT WG URL - Living standard</url>
+ </p></item>
+ </list>
+ <p>
+ The former is a classical standard with a proper formal syntax, using the so
+ called <url href="https://www.ietf.org/rfc/rfc2234.txt">Augmented Backus-Naur Form
+ (ABNF)</url> for describing
+ the grammar, while the latter is a living document describing the current pratice,
+ that is, how a majority of Web browsers work with URIs. WHAT WG URL is Web focused
+ and it has no formal grammar but a plain english description of the algorithms
+ that should be followed.</p>
+ <p>What is the difference between them, if any? They provide an overlapping
+ definition for resource identifiers and they are not compatible.
+ The <seeerl marker="stdlib:uri_string"><c>uri_string</c></seeerl> module implements
+ <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url> and the term URI will
+ be used throughout this document. A URI is an identifier, a string of characters
+ that identifies a particular resource.</p>
+ <p>
+ For a more complete problem
+ statement regarding the URIs check the
+ <url href="https://tools.ietf.org/html/draft-ruby-url-problem-01">URL Problem
+ Statement and Directions</url>.</p>
+ </section>
+
+ <section>
+ <title>What is a URI?</title>
+ <p>Let's start with what it is not. It is not the text that you type in the address
+ bar in your Web browser. Web browsers do all possible heuristics to convert the
+ input into a valid URI that could be sent over the network.</p>
+ <p>A URI is an identifier consisting of a sequence of characters matching the syntax
+ rule named <c>URI</c> in
+ <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>.
+ </p>
+ <p>It is crucial to clarify that a <i>character</i> is a symbol that is displayed on
+ a terminal or written to paper and should not be confused with its internal
+ representation.</p>
+ <p>A URI more specifically, is a sequence of characters from a
+ subset of the US ASCII character set. The generic URI syntax consists of a
+ hierarchical sequence of components referred to as the scheme, authority,
+ path, query, and fragment. There is a formal description for
+ each of these components in
+ <url href="https://www.ietf.org/rfc/rfc2234.txt">ABNF</url> notation in
+ <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>:</p>
+ <pre>
+ URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+ hier-part = "//" authority path-abempty
+ / path-absolute
+ / path-rootless
+ / path-empty
+ scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ authority = [ userinfo "@" ] host [ ":" port ]
+ userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
+
+ reserved = gen-delims / sub-delims
+ gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+ sub-delims = "!" / "$" / "&amp;" / "'" / "(" / ")"
+ / "*" / "+" / "," / ";" / "="
+
+ unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ </pre>
+ </section>
+
+ <section>
+ <title>The uri_string module</title>
+ <p>As producing and consuming standard URIs can get quite complex, Erlang/OTP
+ provides
+ a module, <seeerl marker="stdlib:uri_string"><c>uri_string</c></seeerl>, to handle all the most difficult operations such as parsing,
+ recomposing, normalizing and resolving URIs against a base URI.
+ </p>
+ <p>The API functions in <seeerl marker="stdlib:uri_string"><c>uri_string</c></seeerl>
+ work on two basic data types
+ <seetype marker="uri_string#uri_string"><c>uri_string()</c></seetype> and
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>.
+ <seetype marker="uri_string#uri_string"><c>uri_string()</c></seetype> represents a
+ standard URI, while
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype> is a wider datatype,
+ that can represent URI components using
+ <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide> characters.
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>
+ is a convenient choice for enabling
+ operations such as producing standard compliant URIs out of components that have
+ special or <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide>
+ characters. It is easier to explain this by an example.
+ </p>
+ <p>Let's say that we would like to create the following URI and send it over the
+ network: <c>http://cities/örebro?foo bar</c>. This is not a valid URI as it contains
+ characters that are not allowed in a URI such as "ö" and the space. We can verify
+ this by parsing the URI:
+ </p>
+ <pre>
+ 1> uri_string:parse("http://cities/örebro?foo bar").
+ {error,invalid_uri,":"}
+ </pre>
+ <p>The URI parser tries all possible combinations to interpret the input and fails
+ at the last attempt when it encounters the colon character <c>":"</c>. Note, that
+ the inital fault occurs when the parser attempts to interpret the character
+ <c>"ö"</c> and after a failure back-tracks to the point where it has another
+ possible parsing alternative.</p>
+ <p>The proper way to solve this problem is to use
+ <seemfa marker="uri_string#recompose/1"><c>uri_string:recompose/1</c></seemfa>
+ with a <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype> as input:</p>
+ <pre>
+ 2> uri_string:recompose(#{scheme => "http", host => "cities", path => "/örebro",
+ query => "foo bar"}).
+ "http://cities/%C3%B6rebro?foo%20bar"
+ </pre>
+ <p>The result is a valid URI where all the special characters are encoded as defined
+ by the standard. Applying
+ <seemfa marker="uri_string#parse/1"><c>uri_string:parse/1</c></seemfa> and
+ <seemfa marker="uri_string#percent_decode/1"><c>uri_string:percent_decode/1</c></seemfa>
+ on the URI returns the original input:
+ </p>
+ <pre>
+ 3> uri_string:percent_decode(uri_string:parse("http://cities/%C3%B6rebro?foo%20bar")).
+ #{host => "cities",path => "/örebro",query => "foo bar",
+ scheme => "http"}
+ </pre>
+ <p>This symmetric property is heavily used in our property test suite.
+ </p>
+ </section>
+
+ <section>
+ <title>Percent-encoding</title>
+ <p>As you have seen in the previous chapter, a standard URI can only contain a strict
+ subset of the US ASCII character set, moreover the allowed set of characters is not
+ the same in the different URI components. Percent-encoding is a mechanism to
+ represent a data octet in a component when that octet's corresponding character
+ is outside of
+ the allowed set or is being used as a delimiter. This is what you see when <c>"ö"</c>
+ is encoded as <c>%C3%B6</c> and <c>space</c> as <c>%20</c>.
+ Most of the API functions are
+ expecting UTF-8 encoding when handling percent-encoded triplets. The UTF-8 encoding
+ of the <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide>
+ character <c>"ö"</c> is two octets: <c>OxC3 0xB6</c>.
+ The character <c>space</c> is in the first 128 characters of
+ <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide> and it is encoded
+ using a single octet <c>0x20</c>.</p>
+ <note><p><seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide>
+ is backward compatible with ASCII, the encoding of the first 128
+ characters is the same binary value as in ASCII.
+ </p></note>
+ <p><marker id="percent_encoding"></marker>
+ It is a major source of confusion exactly which characters will be
+ percent-encoded. In order to make it easier to answer this question the library
+ provides a utility function,
+ <seemfa marker="uri_string#allowed_characters/0"><c>uri_string:allowed_characters/0
+ </c></seemfa>,
+ that lists the allowed set of characters in each major
+ URI component, and also in the most important standard character sets.
+ </p>
+ <pre>
+ 1> uri_string:allowed_characters().
+ <![CDATA[{scheme,
+ "+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"},
+ {userinfo,
+ "!$%&'()*+,-.0123456789:;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {host,
+ "!$&'()*+,-.0123456789:;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {ipv4,".0123456789"},
+ {ipv6,".0123456789:ABCDEFabcdef"},
+ {regname,
+ "!$%&'()*+,-.0123456789;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {path,
+ "!$%&'()*+,-./0123456789:;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {query,
+ "!$%&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {fragment,
+ "!$%&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"},
+ {reserved,"!#$&'()*+,/:;=?@[]"},
+ {unreserved,
+ "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"}] ]]>
+ </pre>
+ <p>If a URI component has a character that is not allowed, it will be
+ percent-encoded when the URI is produced:
+ </p>
+ <pre>
+ 2> uri_string:recompose(#{scheme => "https", host => "local#host", path => ""}).
+ "https://local%23host"
+ </pre>
+ <p>Consuming a URI containing percent-encoded triplets can take many steps. The
+ following example shows how to handle an input URI that is not normalized and
+ contains multiple percent-encoded triplets.
+ First, the input <seetype marker="uri_string#uri_string"><c>uri_string()</c></seetype>
+ is to be parsed into a <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>.
+ The parsing only splits the URI into its components without doing any decoding:
+ </p>
+ <pre>
+ 3> uri_string:parse("http://%6C%6Fcal%23host/%F6re%26bro%20").
+ #{host => "%6C%6Fcal%23host",path => "/%F6re%26bro%20",
+ scheme => "http"}}
+ </pre>
+ <p>The input is a valid URI but how can you decode those
+ percent-encoded octets? You can try to normalize the input with
+ <seemfa marker="uri_string#normalize/1"><c>uri_string:normalize/1</c></seemfa>. The
+ normalize operation decodes those
+ percent-encoded triplets that correspond to a character in the unreserved set.
+ Normalization is a safe, idempotent operation that converts a URI into its
+ canonical form:</p>
+ <pre>
+ 4> uri_string:normalize("http://%6C%6Fcal%23host/%F6re%26bro%20").
+ "http://local%23host/%F6re%26bro%20"
+ 5> uri_string:normalize("http://%6C%6Fcal%23host/%F6re%26bro%20", [return_map]).
+ #{host => "local%23host",path => "/%F6re%26bro%20",
+ scheme => "http"}
+ </pre>
+ <p>There are still a few percent-encoded triplets left in the output. At this point,
+ when the URI is already parsed, it is safe to apply application specific decoding on
+ the remaining character triplets. Erlang/OTP provides a function,
+ <seemfa marker="uri_string#percent_decode/1"><c>uri_string:percent_decode/1</c></seemfa>
+ for raw percent decoding
+ that you can use on the host and path components, or on the whole map:
+ </p>
+ <pre>
+ 6> uri_string:percent_decode("local%23host").
+ "local#host"
+ 7> uri_string:percent_decode("/%F6re%26bro%20").
+ <![CDATA[{error,invalid_utf8,<<"/öre&bro ">>}]]>
+ 8> uri_string:percent_decode(#{host => "local%23host",path => "/%F6re%26bro%20",
+ scheme => "http"}).
+ <![CDATA[{error,{invalid,{path,{invalid_utf8,<<"/öre&bro ">>}}}}]]>
+ </pre>
+ <p>The <c>host</c> was successfully decoded but the path contains at least one
+ character with
+ non-UTF-8 encoding. In order to be able to decode this, you have to make assumptions
+ about the encoding used in these triplets. The most obvious choice is
+ <i>latin-1</i>, so you can try
+ <seemfa marker="uri_string#transcode/2"><c>uri_string:transcode/2</c></seemfa>, to
+ transcode the path to UTF-8 and run the percent-decode operation on the
+ transcoded string:
+ </p>
+ <pre>
+ 9> uri_string:transcode("/%F6re%26bro%20", [{in_encoding, latin1}]).
+ "/%C3%B6re%26bro%20"
+ 10> uri_string:percent_decode("/%C3%B6re%26bro%20").
+ <![CDATA["/öre&bro "]]>
+ </pre>
+ <p>It is important to emphasize that it is not safe to apply
+ <seemfa marker="uri_string#percent_decode/1"><c>uri_string:percent_decode/1</c></seemfa>
+ directly on an input URI:
+ </p>
+ <pre>
+ 11> uri_string:percent_decode("http://%6C%6Fcal%23host/%C3%B6re%26bro%20").
+ <![CDATA["http://local#host/öre&bro "
+ 12> uri_string:parse("http://local#host/öre&bro ").]]>
+ {error,invalid_uri,":"}
+ </pre>
+ <note><p>Percent-encoding is implemented in
+ <seemfa marker="uri_string#recompose/1"><c>uri_string:recompose/1</c></seemfa>
+ and it happens when converting a
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>
+ into a <seetype marker="uri_string#uri_string"><c>uri_string()</c></seetype>.
+ There is no equivalent to a raw percent-encoding function as percent-encoding
+ shall be applied on the component level using different sets of allowed characters.
+ Applying percent-encoding directly on an input URI would not be safe just as in
+ the case of
+ <seemfa marker="uri_string#percent_decode/1"><c>uri_string:percent_decode/1</c></seemfa>,
+ the output could be an invalid URI.
+ </p>
+ </note>
+ </section>
+
+ <section>
+ <title>Normalization</title>
+ <p>Normalization is the operation of converting the input URI into a <i>canonical</i>
+ form and keeping the reference to the same underlying resource. The most common
+ application of normalization is determining whether two URIs are equivalent
+ without accessing their referenced resources.</p>
+ <p>Normalization has 6 distinct steps. First the input URI is parsed into an
+ intermediate form that can handle
+ <seeguide marker="unicode_usage#what-unicode-is">Unicode</seeguide> characters.
+ This datatype is the
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>, that can hold the
+ components of the URI in map elements of type
+ <seetype marker="unicode#chardata"><c>unicode:chardata()</c></seetype>.
+ After having the intermediate form, a sequence of
+ normalization algorithms are applied to the individual URI components:</p>
+ <taglist>
+ <tag>Case normalization</tag>
+ <item>
+ <p>Converts the <c>scheme</c> and <c>host</c> components
+ to lower case as they are not case sensitive.</p>
+ </item>
+ <tag>Percent-encoding normalization</tag>
+ <item>
+ <p>Decodes percent-encoded triplets that
+ correspond to characters in the unreserved set.</p>
+ </item>
+ <tag>Scheme-based normalization</tag>
+ <item>
+ <p>Applying rules for the schemes http, https,
+ ftp, ssh, sftp and tftp.</p>
+ </item>
+ <tag>Path segment normalization</tag>
+ <item>
+ <p>Converts the path into a canonical form.</p>
+ </item>
+ </taglist>
+ <p>After these steps, the intermediate data structure, an
+ <seetype marker="uri_string#uri_map"><c>uri_map()</c></seetype>,
+ is fully normalized. The last step is applying
+ <seemfa marker="uri_string#recompose/1"><c>uri_string:recompose/1</c></seemfa>
+ that converts the intermediate structure into a valid canonical URI string.</p>
+ <p>Notice the order, the
+ <seemfa marker="uri_string#normalize/2"><c>uri_string:normalize(URIMap, [return_map])</c></seemfa> that we
+ used many times in this user guide is a shortcut in the normalization process
+ returning the intermediate datastructure, and allowing us to inspect and apply
+ further decoding on the remaining percent-encoded triplets.</p>
+ <pre>
+ 13> uri_string:normalize("hTTp://LocalHost:80/%c3%B6rebro/a/../b").
+ "http://localhost/%C3%B6rebro/b"
+ 14> uri_string:normalize("hTTp://LocalHost:80/%c3%B6rebro/a/../b", [return_map]).
+ #{host => "localhost",path => "/%C3%B6rebro/b",
+ scheme => "http"}
+ </pre>
+ </section>
+
+ <section>
+ <title>Special considerations</title>
+ <p>The current URI implementation provides support for producing and consuming
+ standard URIs. The API is not meant to be directly exposed in a Web
+ browser's address bar where users can basically enter free text. Application
+ designers shall implement proper heuristics to map the input into a parsable URI.</p>
+ </section>
+
+</chapter>
diff --git a/lib/stdlib/doc/src/win32reg.xml b/lib/stdlib/doc/src/win32reg.xml
index 76d6bfdea3..3c34f66e38 100644
--- a/lib/stdlib/doc/src/win32reg.xml
+++ b/lib/stdlib/doc/src/win32reg.xml
@@ -87,7 +87,7 @@ hkdd HKEY_DYN_DATA</pre>
<p>Some registry values are stored as strings with references to environment
variables, for example, <c>%SystemRoot%Windows</c>. <c>SystemRoot</c> is
an environment variable, and is to be replaced with its value. Function
- <seealso marker="#expand/1"><c>expand/1</c></seealso> is provided so that
+ <seemfa marker="#expand/1"><c>expand/1</c></seemfa> is provided so that
environment variables surrounded by <c>%</c> can be expanded to their
values.</p>
<p>For more information on the Windows registry, see consult the Win32
@@ -99,7 +99,7 @@ hkdd HKEY_DYN_DATA</pre>
<name name="reg_handle"/>
<desc>
<p>As returned by
- <seealso marker="#open/1"><c>open/1</c></seealso>.</p>
+ <seemfa marker="#open/1"><c>open/1</c></seemfa>.</p>
</desc>
</datatype>
<datatype>
@@ -159,8 +159,8 @@ hkdd HKEY_DYN_DATA</pre>
<p>Deletes the current key, if it is valid. Calls the Win32 API
function <c>RegDeleteKey()</c>. Notice that this call does not change
the current key (unlike
- <seealso marker="#change_key_create/2">
- <c>change_key_create/2</c></seealso>).
+ <seemfa marker="#change_key_create/2">
+ <c>change_key_create/2</c></seemfa>).
This means that after the call, the current key is invalid.</p>
</desc>
</func>
@@ -204,9 +204,9 @@ hkdd HKEY_DYN_DATA</pre>
<p>Opens the registry for reading or writing. The current key is the
root (<c>HKEY_CLASSES_ROOT</c>). Flag <c>read</c> in the mode list
can be omitted.</p>
- <p>Use <seealso marker="#change_key/2"><c>change_key/2</c></seealso>
+ <p>Use <seemfa marker="#change_key/2"><c>change_key/2</c></seemfa>
with an absolute path after
- <seealso marker="#open/1"><c>open</c></seealso>.</p>
+ <seemfa marker="#open/1"><c>open</c></seemfa>.</p>
</desc>
</func>
@@ -256,7 +256,7 @@ hkdd HKEY_DYN_DATA</pre>
<desc>
<p>Retrieves a list of all values on the current key. The values
have types corresponding to the registry types, see
- <seealso marker="#value/2"><c>value/2</c></seealso>.
+ <seemfa marker="#value/2"><c>value/2</c></seemfa>.
Calls the Win32 API function <c>EnumRegValuesEx()</c>.</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/zip.xml b/lib/stdlib/doc/src/zip.xml
index 306ad27997..a056621ca1 100644
--- a/lib/stdlib/doc/src/zip.xml
+++ b/lib/stdlib/doc/src/zip.xml
@@ -47,42 +47,42 @@
<list type="bulleted">
<item>
<p>To create zip archives, use function
- <seealso marker="#zip/2"><c>zip/2</c></seealso> or
- <seealso marker="#zip/2"><c>zip/3</c></seealso>. They are
+ <seemfa marker="#zip/2"><c>zip/2</c></seemfa> or
+ <seemfa marker="#zip/2"><c>zip/3</c></seemfa>. They are
also available as <c>create/2,3</c>, to resemble the
- <seealso marker="erl_tar"><c>erl_tar</c></seealso> module.</p>
+ <seeerl marker="erl_tar"><c>erl_tar</c></seeerl> module.</p>
</item>
<item>
<p>To extract files from a zip archive, use function
- <seealso marker="#unzip/1"><c>unzip/1</c></seealso> or
- <seealso marker="#unzip/2"><c>unzip/2</c></seealso>. They are
+ <seemfa marker="#unzip/1"><c>unzip/1</c></seemfa> or
+ <seemfa marker="#unzip/2"><c>unzip/2</c></seemfa>. They are
also available as <c>extract/1,2</c>, to resemble the
- <seealso marker="erl_tar"><c>erl_tar</c></seealso> module.</p>
+ <seeerl marker="erl_tar"><c>erl_tar</c></seeerl> module.</p>
</item>
<item>
<p>To fold a function over all files in a zip archive, use function
- <seealso marker="#foldl/3"><c>foldl/3</c></seealso>.</p>
+ <seemfa marker="#foldl/3"><c>foldl/3</c></seemfa>.</p>
</item>
<item>
<p>To return a list of the files in a zip archive, use function
- <seealso marker="#list_dir/1"><c>list_dir/1</c></seealso> or
- <seealso marker="#list_dir/2"><c>list_dir/2</c></seealso>. They are
+ <seemfa marker="#list_dir/1"><c>list_dir/1</c></seemfa> or
+ <seemfa marker="#list_dir/2"><c>list_dir/2</c></seemfa>. They are
also available as <c>table/1,2</c>, to resemble the
- <seealso marker="erl_tar"><c>erl_tar</c></seealso> module.</p>
+ <seeerl marker="erl_tar"><c>erl_tar</c></seeerl> module.</p>
</item>
<item>
<p>To print a list of files to the Erlang shell, use function
- <seealso marker="#t/1"><c>t/1</c></seealso> or
- <seealso marker="#tt/1"><c>tt/1</c></seealso>.</p>
+ <seemfa marker="#t/1"><c>t/1</c></seemfa> or
+ <seemfa marker="#tt/1"><c>tt/1</c></seemfa>.</p>
</item>
<item>
<p>Sometimes it is desirable to open a zip archive, and to
unzip files from it file by file, without having to reopen the
archive. This can be done by functions
- <seealso marker="#zip_open/1"><c>zip_open/1,2</c></seealso>,
- <seealso marker="#zip_get/1"><c>zip_get/1,2</c></seealso>,
- <seealso marker="#zip_list_dir/1"><c>zip_list_dir/1</c></seealso>, and
- <seealso marker="#zip_close/1"><c>zip_close/1</c></seealso>.</p>
+ <seemfa marker="#zip_open/1"><c>zip_open/1,2</c></seemfa>,
+ <seemfa marker="#zip_get/1"><c>zip_get/1,2</c></seemfa>,
+ <seemfa marker="#zip_list_dir/1"><c>zip_list_dir/1</c></seemfa>, and
+ <seemfa marker="#zip_close/1"><c>zip_close/1</c></seemfa>.</p>
</item>
</list>
</description>
@@ -136,8 +136,8 @@
<tag><c>info</c></tag>
<item>
<p>File information as in
- <seealso marker="kernel:file#read_file_info/1">
- <c>file:read_file_info/1</c></seealso>
+ <seemfa marker="kernel:file#read_file_info/1">
+ <c>file:read_file_info/1</c></seemfa>
in Kernel</p>
</item>
<tag><c>comment</c></tag>
@@ -165,15 +165,15 @@
<datatype>
<name name="create_option"/>
<desc>
- <p>These options are described in <seealso marker="#zip_options">
- <c>create/3</c></seealso>.</p>
+ <p>These options are described in <seeerl marker="#zip_options">
+ <c>create/3</c></seeerl>.</p>
</desc>
</datatype>
<datatype>
<name name="handle"/>
<desc>
<p>As returned by
- <seealso marker="#zip_open/2"><c>zip_open/2</c></seealso>.</p>
+ <seemfa marker="#zip_open/2"><c>zip_open/2</c></seemfa>.</p>
</desc>
</datatype>
</datatypes>
@@ -245,7 +245,7 @@
<p><c>list_dir/2</c> provides options.</p>
<p><c>table/1</c> and <c>table/2</c> are provided as synonyms
to resemble the
- <seealso marker="erl_tar"><c>erl_tar</c></seealso> module.</p>
+ <seeerl marker="erl_tar"><c>erl_tar</c></seeerl> module.</p>
<p>The result value is the tuple <c>{ok, List}</c>, where <c>List</c>
contains the zip archive comment as the first element.</p>
<p>One option is available:</p>
@@ -293,7 +293,7 @@
<p><c>unzip/2</c> provides options to extract some files, and more.</p>
<p><c>extract/1</c> and <c>extract/2</c> are provided as synonyms
to resemble module
- <seealso marker="erl_tar"><c>erl_tar</c></seealso>.</p>
+ <seeerl marker="erl_tar"><c>erl_tar</c></seeerl>.</p>
<p>If argument <c><anno>Archive</anno></c> is specified as a binary,
the contents of the binary is assumed to be a zip archive,
otherwise a filename.</p>
@@ -344,8 +344,8 @@
<p>Uses the specified directory as current directory. It is
prepended to filenames when extracting them from the
zip archive. (Acting like
- <seealso marker="kernel:file#set_cwd/1">
- <c>file:set_cwd/1</c></seealso> in Kernel,
+ <seemfa marker="kernel:file#set_cwd/1">
+ <c>file:set_cwd/1</c></seemfa> in Kernel,
but without changing the global <c>cwd</c> property.)</p>
</item>
</taglist>
@@ -358,12 +358,14 @@
<name name="create" arity="2" since=""/>
<name name="create" arity="3" since=""/>
<fsummary>Create a zip archive with options.</fsummary>
+ <type name="create_option"/>
+ <type name="extension_spec"/>
<desc>
<p>Creates a zip archive containing the files specified in
<c><anno>FileList</anno></c>.</p>
<p><c>create/2</c> and <c>create/3</c> are provided as synonyms
to resemble module
- <seealso marker="erl_tar"><c>erl_tar</c></seealso>.</p>
+ <seeerl marker="erl_tar"><c>erl_tar</c></seeerl>.</p>
<p><c><anno>FileList</anno></c> is a list of files, with paths relative
to the current directory, which are stored with this path in the
archive. Files can also be specified with data in binaries
@@ -408,7 +410,7 @@
<p>The output is not to a file, but instead as a tuple
<c>{<anno>FileName</anno>, binary()}</c>. The binary is a full zip
archive with header and can be extracted with, for example,
- <seealso marker="#unzip/2"><c>unzip/2</c></seealso>.</p>
+ <seemfa marker="#unzip/2"><c>unzip/2</c></seemfa>.</p>
</item>
<tag><c>{comment, <anno>Comment</anno>}</c></tag>
<item>
@@ -419,8 +421,8 @@
<p>Uses the specified directory as current work directory
(<c>cwd</c>). This is prepended to filenames when adding them,
although not in the zip archive (acting like
- <seealso marker="kernel:file#set_cwd/1">
- <c>file:set_cwd/1</c></seealso> in Kernel, but without
+ <seemfa marker="kernel:file#set_cwd/1">
+ <c>file:set_cwd/1</c></seemfa> in Kernel, but without
changing the global <c>cwd</c> property.).</p>
</item>
<tag><c>{compress, <anno>What</anno>}</c></tag>
@@ -485,7 +487,7 @@
<fsummary>Close an open archive.</fsummary>
<desc>
<p>Closes a zip archive, previously opened with
- <seealso marker="#zip_open/1"><c>zip_open/1,2</c></seealso>.
+ <seemfa marker="#zip_open/1"><c>zip_open/1,2</c></seemfa>.
All resources are closed, and the handle is not to be used after
closing.</p>
</desc>
@@ -499,7 +501,7 @@
<p>Extracts one or all files from an open archive.</p>
<p>The files are unzipped to memory or to file, depending on
the options specified to function
- <seealso marker="#zip_open/1"><c>zip_open/1,2</c></seealso>
+ <seemfa marker="#zip_open/1"><c>zip_open/1,2</c></seemfa>
when opening the archive.</p>
</desc>
</func>
@@ -521,9 +523,9 @@
<p>Opens a zip archive, and reads and saves its directory. This
means that later reading files from the archive is
faster than unzipping files one at a time with
- <seealso marker="#unzip/1"><c>unzip/1,2</c></seealso>.</p>
+ <seemfa marker="#unzip/1"><c>unzip/1,2</c></seemfa>.</p>
<p>The archive must be closed with
- <seealso marker="#zip_close/1"><c>zip_close/1</c></seealso>.</p>
+ <seemfa marker="#zip_close/1"><c>zip_close/1</c></seemfa>.</p>
<p>The <c><anno>ZipHandle</anno></c> is closed if the
process that originally opened the archive dies.</p>
</desc>
diff --git a/lib/stdlib/examples/erl_id_trans.erl b/lib/stdlib/examples/erl_id_trans.erl
index eab2ec4164..b1562e0207 100644
--- a/lib/stdlib/examples/erl_id_trans.erl
+++ b/lib/stdlib/examples/erl_id_trans.erl
@@ -193,9 +193,6 @@ pattern({map_field_exact,Line,K,V}) ->
Ke = expr(K),
Ve = pattern(V),
{map_field_exact,Line,Ke,Ve};
-%%pattern({struct,Line,Tag,Ps0}) ->
-%% Ps1 = pattern_list(Ps0),
-%% {struct,Line,Tag,Ps1};
pattern({record,Line,Name,Pfs0}) ->
Pfs1 = pattern_fields(Pfs0),
{record,Line,Name,Pfs1};
@@ -433,9 +430,6 @@ expr({map_field_exact,Line,K,V}) ->
Ke = expr(K),
Ve = expr(V),
{map_field_exact,Line,Ke,Ve};
-%%expr({struct,Line,Tag,Es0}) ->
-%% Es1 = pattern_list(Es0),
-%% {struct,Line,Tag,Es1};
expr({record_index,Line,Name,Field0}) ->
Field1 = expr(Field0),
{record_index,Line,Name,Field1};
@@ -619,6 +613,8 @@ type({atom,Line,A}) ->
{atom,Line,A};
type({integer,Line,I}) ->
{integer,Line,I};
+type({char,Line,C}) ->
+ {char,Line,C};
type({op,Line,Op,T}) ->
T1 = type(T),
{op,Line,Op,T1};
diff --git a/lib/stdlib/include/erl_bits.hrl b/lib/stdlib/include/erl_bits.hrl
index 2a54587a17..7810527832 100644
--- a/lib/stdlib/include/erl_bits.hrl
+++ b/lib/stdlib/include/erl_bits.hrl
@@ -31,19 +31,3 @@
sign :: bt_sign() | 'undefined',
endian :: bt_endian() | 'undefined'
}).
-
--record(bitdefault, {
- integer, %% default type for integer
- float, %% default type for float
- binary %% default type for binary
- }).
-
-%%% (From config.hrl in the bitsyntax branch.)
--define(SYS_ENDIAN, big).
--define(SIZEOF_CHAR, 1).
--define(SIZEOF_DOUBLE, 8).
--define(SIZEOF_FLOAT, 4).
--define(SIZEOF_INT, 4).
--define(SIZEOF_LONG, 4).
--define(SIZEOF_LONG_LONG, 8).
--define(SIZEOF_SHORT, 2).
diff --git a/lib/stdlib/scripts/update_deprecations b/lib/stdlib/scripts/update_deprecations
new file mode 100755
index 0000000000..47723ca4bc
--- /dev/null
+++ b/lib/stdlib/scripts/update_deprecations
@@ -0,0 +1,359 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-mode(compile).
+-compile(warnings_as_errors).
+
+-import(lists, [foldl/3,sort/1]).
+
+-record(st,
+ {functions = [],
+ types = [],
+ deprecations = #{}}).
+
+main(["update",Top]) ->
+ St0 = summarize(Top),
+ St = check_deprecations(Top, St0),
+ emit(Top, St),
+ halt(0);
+main(["make_xml",Type,Top,Outfile]) ->
+ St = summarize(Top),
+ make_xml(Top, Type, Outfile, St#st.functions),
+ halt(0).
+
+ebin_directories(Top) ->
+ [filename:join(Top, "erts/preloaded/ebin")] ++
+ filelib:wildcard(filename:join(Top, "lib/*/ebin")).
+
+summarize(Top) ->
+ Directories = ebin_directories(Top),
+ foldl(fun summarize_directory/2, #st{}, Directories).
+
+summarize_directory(Dir, Acc) ->
+ Files = [filename:join(Dir, F) || F <- filelib:wildcard("*.beam", Dir)],
+ foldl(fun summarize_file/2, Acc, Files).
+
+summarize_file(File, Acc) ->
+ {ok, {Module, [Chunk]}} = beam_lib:chunks(File, [attributes]),
+ {attributes, Attributes} = Chunk,
+ summarize_attributes(Attributes, Module, Acc).
+
+summarize_attributes([{deprecated, Ds} | As], Module, Acc0) ->
+ Fs = sa_1(Ds, deprecated, Module, Acc0#st.functions),
+ Acc = Acc0#st{ functions = Fs },
+ summarize_attributes(As, Module, Acc);
+summarize_attributes([{removed, Rs} | As], Module, Acc0) ->
+ Fs = sa_1(Rs, removed, Module, Acc0#st.functions),
+ Acc = Acc0#st{ functions = Fs },
+ summarize_attributes(As, Module, Acc);
+summarize_attributes([{deprecated_type, Ds} | As], Module, Acc0) ->
+ Ts = sa_1(Ds, deprecated, Module, Acc0#st.types),
+ Acc = Acc0#st{ types = Ts },
+ summarize_attributes(As, Module, Acc);
+summarize_attributes([{removed_type, Rs} | As], Module, Acc0) ->
+ Ts = sa_1(Rs, removed, Module, Acc0#st.types),
+ Acc = Acc0#st{ types = Ts },
+ summarize_attributes(As, Module, Acc);
+summarize_attributes([_ | As], Module, Acc) ->
+ summarize_attributes(As, Module, Acc);
+summarize_attributes([], _Module, Acc) ->
+ Acc.
+
+sa_1([{F, A, Info} | As], Tag, Module, Acc0) ->
+ sa_1(As, Tag, Module, [{Tag, Module, F, A, Info} | Acc0]);
+sa_1([{F, A} | As], Tag, Module, Acc0) ->
+ sa_1(As, Tag, Module, [{Tag, Module, F, A, undefined} | Acc0]);
+sa_1([module | As], Tag, Module, Acc0) ->
+ sa_1(As, Tag, Module, [{Tag, Module, '_', '_', undefined} | Acc0]);
+sa_1([], _Tag, _Module, Acc) ->
+ Acc.
+
+%%
+
+emit(Top, #st{ functions = Fs0, types = Ts, deprecations = Depr }) ->
+ Fs = insert_removals(Fs0, Depr),
+ Name = filename:join(Top, "lib/stdlib/src/otp_internal.erl"),
+ Contents = ["%%\n"
+ "%% WARNING: DO NOT EDIT THIS FILE.\n"
+ "%%\n"
+ "%% This file was auto-generated from attributes in the source\n"
+ "%% code.\n"
+ "%%\n"
+ "%% To add a description to a deprecation or removal attribute,\n"
+ "%% write a string after the arity:\n"
+ "%%\n"
+ "%% -deprecated([{foo,1,\"use bar/1 instead\"}]).\n"
+ "%% -deprecated_type([{gadget,1,\"use widget/1 instead\"}]).\n"
+ "%% -removed([{hello,2,\"use there/2 instead\"}]).\n"
+ "%% -removed_type([{frobnitz,1,\"use grunka/1 instead\"}]).\n"
+ "%%\n"
+ "%% Descriptions cannot be given with the `f/1` shorthand, and\n"
+ "%% it will fall back to a generic description referring the\n"
+ "%% user to the documentation.\n"
+ "%%\n"
+ "%% Use `./otp_build update_deprecations` to update this file\n"
+ "%% after adding an attribute.\n"
+ "%%\n"
+ "-module(otp_internal).\n"
+ "-include(\"otp_internal.hrl\").\n"
+ "%%\n",
+ emit_function("obsolete", Fs),
+ emit_function("obsolete_type", Ts)],
+ ok = file:write_file(Name, Contents),
+ ok.
+
+emit_function(FuncName, Entries) ->
+ [io_lib:format("-dialyzer({no_match, ~ts/3}).\n", [FuncName]),
+ [emit_clause(FuncName, E) || E <- sort_clauses(Entries)],
+ io_lib:format("~ts(_,_,_) -> no.\n\n", [FuncName])].
+
+sort_clauses(Entries) ->
+ Tagged = [{clause_order(E), E} || E <- Entries],
+ [E || {_, E} <- sort(Tagged)].
+
+clause_order({_Tag, _Module, F, A, _Info}=Entry) ->
+ {clause_order(F, A), Entry};
+clause_order({_Tag, _Module, F, A, _Info, _Rel}) ->
+ {clause_order(F, A), {_Tag, _Module, F, A, _Info}}.
+
+%% Wildcard matches must be emitted *after* specific matches to avoid
+%% losing descriptions.
+clause_order(F, A) when F =/= '_', A =/= '_' -> 0;
+clause_order(F, '_') when F =/= '_' -> 1;
+clause_order('_', A) when A =/= '_' -> 2;
+clause_order('_', '_') -> 3.
+
+emit_clause(FuncName, {Tag, M, F, A, Info}) ->
+ io_lib:format("~ts(~ts, ~ts, ~ts) ->\n"
+ " {~p, ~p};\n",
+ [FuncName, match_string(M), match_string(F), match_string(A),
+ Tag, info_string(Info)]);
+emit_clause(FuncName, {Tag, M, F, A, Info, Rel}) ->
+ io_lib:format("~ts(~ts, ~ts, ~ts) ->\n"
+ " {~p, ~p, ~p};\n",
+ [FuncName, match_string(M), match_string(F), match_string(A),
+ Tag, info_string(Info), Rel]).
+
+%%
+
+info_string(undefined) ->
+ "see the documentation for details";
+info_string(next_version) ->
+ "will be removed in the next version. "
+ "See the documentation for details";
+info_string(next_major_release) ->
+ "will be removed in the next major release. "
+ "See the documentation for details";
+info_string(eventually) ->
+ "will be removed in a future release. "
+ "See the documentation for details";
+info_string(String) when is_list(String) ->
+ String.
+
+match_string('_') -> "_";
+match_string(Term) -> io_lib:format("~p", [Term]).
+
+%%
+
+insert_removals([{deprecated,M,F,A,Info}=Entry|T], Depr) ->
+ Key = {M,F,A},
+ case Depr of
+ #{Key := Ps} ->
+ case lists:keyfind(remove, 1, Ps) of
+ false ->
+ [Entry|insert_removals(T, Depr)];
+ {remove,Rel0} ->
+ Rel = lists:concat(["OTP ",Rel0]),
+ [{deprecated,M,F,A,Info,Rel}|insert_removals(T, Depr)]
+ end;
+ #{} ->
+ [Entry|insert_removals(T, Depr)]
+ end;
+insert_removals([H|T], Depr) ->
+ [H|insert_removals(T, Depr)];
+insert_removals([], _Depr) ->
+ [].
+
+%%%
+%%% Create XML files.
+%%%
+
+make_xml(Top, Type, OutFile, InfoText0) ->
+ DeprecationFile = deprecation_file(Top),
+ OutDir = filename:dirname(DeprecationFile),
+ InfoTextMap = maps:from_list(make_xml_info(InfoText0)),
+ Depr0 = read_deprecations(DeprecationFile),
+ Depr = maps:to_list(Depr0),
+ {Key,Prefix} = case Type of
+ "deprecations" ->
+ {since,"deprecations"};
+ "removals" ->
+ {remove,"scheduled_for_removal"}
+ end,
+ Collected = make_xml_collect(Depr, Key, InfoTextMap, []),
+ All = make_xml_gen(lists:reverse(Collected), Type, Prefix, OutDir),
+ file:write_file(OutFile, All),
+ ok.
+
+make_xml_info([{deprecated,M,F,A,Text}|T]) ->
+ [{{M,F,A},Text}|make_xml_info(T)];
+make_xml_info([{removed,_,_,_,_}|T]) ->
+ make_xml_info(T);
+make_xml_info([]) ->
+ [].
+
+make_xml_collect([{MFA,Ps}|T], Key, InfoTextMap, Acc) ->
+ case lists:keyfind(Key, 1, Ps) of
+ {Key,Rel} ->
+ InfoText = case InfoTextMap of
+ #{MFA := Text} -> Text;
+ #{} -> []
+ end,
+ make_xml_collect(T, Key, InfoTextMap, [{Rel,{MFA,InfoText}}|Acc]);
+ false ->
+ make_xml_collect(T, Key, InfoTextMap, Acc)
+ end;
+make_xml_collect([], _Key, _InfoTextMap, Acc) ->
+ rel2fam(Acc).
+
+make_xml_gen(Collected, Type, Prefix, Dir) ->
+ Head = get_xml_template(Dir, Prefix, head),
+ Contents = make_xml_gen_list(Collected, Type, Prefix, Dir),
+ Footer = "</chapter>\n",
+ [Head,Contents,Footer].
+
+make_xml_gen_list([{Rel,MFAs}|T], Type, Prefix, Dir) ->
+ RelStr = lists:concat(["OTP ",Rel]),
+ RelMarker = lists:concat(["otp-",Rel]),
+ Head = ["<section>\n",
+ "<marker id=\"",RelMarker,"\"/>\n",
+ "<title>",RelStr,"</title>\n"],
+ Footer = "</section>\n",
+ SubTitle = case Type of
+ "deprecations" ->
+ ["Functions Deprecated in ",RelStr];
+ "removals" ->
+ ["Functions Scheduled for Removal in ",RelStr]
+ end,
+ SubHead = ["<section>\n",
+ "<title>",SubTitle,"</title>\n"],
+ SubFooter = "</section>\n",
+ [Head, get_xml_template(Dir, Prefix, Rel),
+ SubHead, make_xml_gen_mfas(MFAs), SubFooter,
+ Footer | make_xml_gen_list(T, Type, Prefix, Dir)];
+make_xml_gen_list([], _, _, _) ->
+ [].
+
+make_xml_gen_mfas(MFAs) ->
+ ["<list type=\"bulleted\">\n",
+ [make_xml_item(MFA) || MFA <- MFAs],
+ "</list>\n"].
+
+make_xml_item({{M,F,A},Text}) ->
+ ["<item><c>",lists:concat([M,":",F,"/",A]),"</c>",
+ " (",Text,")</item>\n"].
+
+get_xml_template(Dir, Prefix, Key) ->
+ Name = lists:concat([Prefix,"_",Key,".inc"]),
+ File = filename:join(Dir, Name),
+ case file:read_file(File) of
+ {ok,Contents} ->
+ Contents;
+ {error,enoent} ->
+ []
+ end.
+
+%%%
+%%% Cross-checks deprecations against DEPRECATIONS file.
+%%%
+
+check_deprecations(Top, #st{functions = Fs} = St) ->
+ DeprFile = deprecation_file(Top),
+ Depr = read_deprecations(DeprFile),
+ Bad0 = [F || F <- Fs, not in_deprecations(F, Depr)],
+ case Bad0 of
+ [] ->
+ St#st{deprecations = Depr};
+ [_|_] ->
+ Msg = "The following function(s) have -deprecated() attributes, "
+ "but are not present in the DEPRECATIONS file:\n\n",
+ Bad = [io_lib:format(" ~w:~w/~w\n", [M,F,A]) ||
+ {deprecated,M,F,A,_} <- Bad0],
+ Loc = ["\n","Please update ",DeprFile,".\n"],
+ io:put_chars(standard_error, [Msg,Bad,Loc]),
+ halt(1)
+ end.
+
+read_deprecations(File) ->
+ {ok,Bin} = file:read_file(File),
+ Lines = binary:split(Bin, <<"\n">>, [global,trim_all]),
+ maps:from_list(parse_deprecations(Lines)).
+
+deprecation_file(Root) ->
+ filename:join(Root, "system/doc/general_info/DEPRECATIONS").
+
+in_deprecations({deprecated,M,F,A,_}, Depr) ->
+ is_map_key({M,F,A}, Depr);
+in_deprecations({removed,_,_,_,_}, _Depr) ->
+ true.
+
+parse_deprecations([<<"#",_/binary>>|Lines]) ->
+ parse_deprecations(Lines);
+parse_deprecations([Line|Lines]) ->
+ [parse_line(Line)|parse_deprecations(Lines)];
+parse_deprecations([]) ->
+ [].
+
+parse_line(Line) ->
+ [MFA0|Parts0] = binary:split(Line, <<" ">>, [global,trim_all]),
+ MFA = parse_mfa(MFA0),
+ Parts1 = [binary:split(Part, <<"=">>) || Part <- Parts0],
+ Parts = lists:sort([parse_part(Part) || Part <- Parts1]),
+ {MFA,Parts}.
+
+parse_part([<<"mfa">>,MFA]) ->
+ {mfa,parse_mfa(MFA)};
+parse_part([<<"since">>,Since]) ->
+ {since,parse_release(Since)};
+parse_part([<<"remove">>,Remove]) ->
+ {remove,parse_release(Remove)}.
+
+parse_release(Rel) ->
+ binary_to_integer(Rel).
+
+parse_mfa(MFA) ->
+ {match,[M0,F0,A0]} = re:run(MFA, <<"^(\\w+):(\\w+)/([\\d_]+)$">>,
+ [{capture,all_but_first,binary}]),
+ A = case A0 of
+ <<"_">> -> '_';
+ _ -> binary_to_integer(A0)
+ end,
+ {bin_to_atom(M0),bin_to_atom(F0),A}.
+
+bin_to_atom(Bin) ->
+ list_to_atom(binary_to_list(Bin)).
+
+rel2fam(S0) ->
+ S1 = sofs:relation(S0),
+ S = sofs:rel2fam(S1),
+ sofs:to_external(S).
diff --git a/lib/stdlib/src/Makefile b/lib/stdlib/src/Makefile
index 9d8a5f98f9..e3e0c9c03d 100644
--- a/lib/stdlib/src/Makefile
+++ b/lib/stdlib/src/Makefile
@@ -112,6 +112,7 @@ MODULES= \
sets \
shell \
shell_default \
+ shell_docs \
slave \
sofs \
string \
@@ -133,7 +134,7 @@ HRL_FILES= \
../include/qlc.hrl \
../include/zip.hrl
-INTERNAL_HRL_FILES= dets.hrl erl_tar.hrl
+INTERNAL_HRL_FILES= dets.hrl erl_tar.hrl otp_internal.hrl
ERL_FILES= $(MODULES:%=%.erl)
@@ -149,6 +150,12 @@ APPUP_FILE= stdlib.appup
APPUP_SRC= $(APPUP_FILE).src
APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
+ifeq ($(TARGET),win32)
+ EXE_SUFFIX=.exe
+else
+ EXE_SUFFIX=
+endif
+
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
@@ -207,7 +214,7 @@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
$(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@
unicode_util.erl: ../uc_spec/*
- escript ../uc_spec/gen_unicode_mod.escript
+ escript$(EXE_SUFFIX) ../uc_spec/gen_unicode_mod.escript
# ----------------------------------------------------
# Release Target
diff --git a/lib/stdlib/src/beam_lib.erl b/lib/stdlib/src/beam_lib.erl
index 48d18ec5b9..23413f43b1 100644
--- a/lib/stdlib/src/beam_lib.erl
+++ b/lib/stdlib/src/beam_lib.erl
@@ -107,6 +107,7 @@
-type chnk_rsn() :: {'unknown_chunk', file:filename(), atom()}
| {'key_missing_or_invalid', file:filename(),
'abstract_code' | 'debug_info'}
+ | {'missing_backend', file:filename(), module()}
| info_rsn().
-type cmp_rsn() :: {'modules_different', module(), module()}
| {'chunks_different', chunkid()}
@@ -310,6 +311,9 @@ format_error(badfun) ->
"not a fun or the fun has the wrong arity";
format_error(exists) ->
"a fun has already been installed";
+format_error({missing_backend, File, Backend}) ->
+ io_lib:format("~tp: Cannot retrieve abstract code because the backend ~p is missing",
+ [File, Backend]);
format_error(E) ->
io_lib:format("~tp~n", [E]).
@@ -682,10 +686,13 @@ chunks_to_data([{abst_chunk, Name} | CNs], Chunks, File, Cs, Module, Atoms, L) -
{NewAtoms, Ret} =
case catch chunk_to_data(debug_info, DbgiChunk, File, Cs, Atoms, Module) of
{DbgiAtoms, {debug_info, {debug_info_v1, Backend, Metadata}}} ->
- case Backend:debug_info(erlang_v1, Module, Metadata, []) of
+ try Backend:debug_info(erlang_v1, Module, Metadata, []) of
{ok, Code} -> {DbgiAtoms, {abstract_code, {raw_abstract_v1, Code}}};
{error, _} -> {DbgiAtoms, {abstract_code, no_abstract_code}}
- end;
+ catch
+ error:undef ->
+ error({missing_backend,File,Backend})
+ end;
{error,beam_lib,{key_missing_or_invalid,Path,debug_info}} ->
error({key_missing_or_invalid,Path,abstract_code});
_ ->
@@ -973,7 +980,7 @@ decrypt_chunk(Type, Module, File, Id, Bin) ->
KeyString = get_crypto_key({debug_info, Type, Module, File}),
{Type,Key,IVec,_BlockSize} = make_crypto_key(Type, KeyString),
ok = start_crypto(),
- NewBin = crypto:block_decrypt(Type, Key, IVec, Bin),
+ NewBin = crypto:crypto_one_time(des_ede3_cbc, Key, IVec, Bin, false),
binary_to_term(NewBin)
catch
_:_ ->
diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl
index 0362b72536..fa7bf8bfbc 100644
--- a/lib/stdlib/src/c.erl
+++ b/lib/stdlib/src/c.erl
@@ -19,6 +19,8 @@
%%
-module(c).
+-include_lib("kernel/include/eep48.hrl").
+
%% Utilities to use from shell.
%% Avoid warning for local function error/2 clashing with autoimported BIF.
@@ -28,6 +30,7 @@
lc_batch/0, lc_batch/1,
i/3,pid/3,m/0,m/1,mm/0,lm/0,
bt/1, q/0,
+ h/1,h/2,h/3,ht/1,ht/2,ht/3,hcb/1,hcb/2,hcb/3,
erlangrc/0,erlangrc/1,bi/1, flush/0, regs/0, uptime/0,
nregs/0,pwd/0,ls/0,ls/1,cd/1,memory/1,memory/0, xm/1]).
@@ -48,6 +51,9 @@ help() ->
"cd(Dir) -- change working directory\n"
"flush() -- flush any messages sent to the shell\n"
"help() -- help info\n"
+ "h(M) -- module documentation\n"
+ "h(M,F) -- module function documentation\n"
+ "h(M,F,A) -- module function arity documentation\n"
"i() -- information about the system\n"
"ni() -- information about the networked system\n"
"i(X,Y,Z) -- information about pid <X,Y,Z>\n"
@@ -147,6 +153,122 @@ c(SrcFile, NewOpts, Filter, BeamFile, Info) ->
format("Recompiling ~ts\n", [SrcFile]),
safe_recompile(SrcFile, Options, BeamFile).
+-type h_return() :: ok | {error, missing | {unknown_format, unicode:chardata()}}.
+-type hf_return() :: h_return() | {error, function_missing}.
+-type ht_return() :: h_return() | {error, type_missing}.
+-type hcb_return() :: h_return() | {error, callback_missing}.
+
+-spec h(module()) -> h_return().
+h(Module) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render(Module, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec h(module(),function()) -> hf_return().
+h(Module,Function) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render(Module, Function, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec h(module(),function(),arity()) -> hf_return().
+h(Module,Function,Arity) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render(Module, Function, Arity, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec ht(module()) -> h_return().
+ht(Module) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render_type(Module, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec ht(module(),Type :: atom()) -> ht_return().
+ht(Module,Type) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render_type(Module, Type, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec ht(module(),Type :: atom(),arity()) ->
+ ht_return().
+ht(Module,Type,Arity) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render_type(Module, Type, Arity, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec hcb(module()) -> h_return().
+hcb(Module) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render_callback(Module, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec hcb(module(),Callback :: atom()) -> hcb_return().
+hcb(Module,Callback) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render_callback(Module, Callback, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+-spec hcb(module(),Callback :: atom(),arity()) ->
+ hcb_return().
+hcb(Module,Callback,Arity) ->
+ case code:get_doc(Module) of
+ {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} ->
+ format_docs(shell_docs:render_callback(Module, Callback, Arity, Docs));
+ {ok, #docs_v1{ format = Enc }} ->
+ {error, {unknown_format, Enc}};
+ Error ->
+ Error
+ end.
+
+format_docs({error,_} = E) ->
+ E;
+format_docs(Docs) ->
+ {match, Lines} = re:run(Docs,"(.+\n|\n)",[unicode,global,{capture,all_but_first,binary}]),
+ _ = paged_output(fun(Line,_) ->
+ format("~ts",Line),
+ {1,undefined}
+ end, undefined, Lines),
+ ok.
+
old_options(Info) ->
case lists:keyfind(options, 1, Info) of
{options, Opts} -> Opts;
@@ -475,58 +597,54 @@ ni() -> i(all_procs()).
-spec i([pid()]) -> 'ok'.
i(Ps) ->
- i(Ps, length(Ps)).
-
--spec i([pid()], non_neg_integer()) -> 'ok'.
-
-i(Ps, N) when N =< 100 ->
- iformat("Pid", "Initial Call", "Heap", "Reds",
- "Msgs"),
- iformat("Registered", "Current Function", "Stack", "",
- ""),
- {R,M,H,S} = foldl(fun(Pid, {R0,M0,H0,S0}) ->
- {A,B,C,D} = display_info(Pid),
- {R0+A,M0+B,H0+C,S0+D}
- end, {0,0,0,0}, Ps),
- iformat("Total", "", w(H), w(R), w(M)),
- iformat("", "", w(S), "", "");
-i(Ps, N) ->
- iformat("Pid", "Initial Call", "Heap", "Reds",
- "Msgs"),
- iformat("Registered", "Current Function", "Stack", "",
- ""),
- paged_i(Ps, {0,0,0,0}, N, 50).
-
-paged_i([], {R,M,H,S}, _, _) ->
- iformat("Total", "", w(H), w(R), w(M)),
- iformat("", "", w(S), "", "");
-paged_i(Ps, Acc, N, Page) ->
- {Pids, Rest, N1} =
- if N > Page ->
- {L1,L2} = lists:split(Page, Ps),
- {L1,L2,N-Page};
- true ->
- {Ps, [], 0}
- end,
- NewAcc = foldl(fun(Pid, {R,M,H,S}) ->
- {A,B,C,D} = display_info(Pid),
- {R+A,M+B,H+C,S+D}
- end, Acc, Pids),
- case Rest of
- [_|_] ->
- choice(fun() -> paged_i(Rest, NewAcc, N1, Page) end);
- [] ->
- paged_i([], NewAcc, 0, Page)
+ iformat("Pid", "Initial Call", "Heap", "Reds", "Msgs"),
+ iformat("Registered", "Current Function", "Stack", "", ""),
+ case paged_output(fun(Pid, {R,M,H,S}) ->
+ {A,B,C,D} = display_info(Pid),
+ {2,{R+A,M+B,H+C,S+D}}
+ end, 2, {0,0,0,0}, Ps) of
+ {R,M,H,S} ->
+ iformat("Total", "", w(H), w(R), w(M)),
+ iformat("", "", w(S), "", "");
+ less ->
+ ok
end.
-choice(F) ->
- case get_line('(c)ontinue (q)uit -->', "c\n") of
+paged_output(Fun, Acc, Items) ->
+ paged_output(Fun, 0, Acc, Items).
+paged_output(Fun, CurrLine, Acc, Items) ->
+ Limit =
+ case io:rows() of
+ {ok, Rows} -> Rows-2;
+ _ -> 100
+ end,
+ paged_output(Fun, CurrLine, Limit, Acc, Items).
+
+paged_output(PrintFun, CurrLine, Limit, Acc, Items) when CurrLine >= Limit ->
+ case more() of
+ more ->
+ paged_output(PrintFun, 0, Limit, Acc, Items);
+ less ->
+ less
+ end;
+paged_output(PrintFun, CurrLine, Limit, Acc, [H|T]) ->
+ {Lines, NewAcc} = PrintFun(H, Acc),
+ paged_output(PrintFun, CurrLine+Lines, Limit, NewAcc, T);
+paged_output(_, _, _, Acc, []) ->
+ Acc.
+
+more() ->
+ case get_line('more (y/n)? (y) ', "y\n") of
"c\n" ->
- F();
+ more;
+ "y\n" ->
+ more;
"q\n" ->
- quit;
+ less;
+ "n\n" ->
+ less;
_ ->
- choice(F)
+ more()
end.
get_line(P, Default) ->
diff --git a/lib/stdlib/src/calendar.erl b/lib/stdlib/src/calendar.erl
index ef6d1882e6..2f95f54312 100644
--- a/lib/stdlib/src/calendar.erl
+++ b/lib/stdlib/src/calendar.erl
@@ -54,7 +54,8 @@
valid_date/1,
valid_date/3]).
--deprecated([{local_time_to_universal_time,1}]).
+-deprecated([{local_time_to_universal_time,1,
+ "use calendar:local_time_to_universal_time_dst/1 instead"}]).
-define(SECONDS_PER_MINUTE, 60).
-define(SECONDS_PER_HOUR, 3600).
diff --git a/lib/stdlib/src/digraph.erl b/lib/stdlib/src/digraph.erl
index 8a4df95027..58d493cf54 100644
--- a/lib/stdlib/src/digraph.erl
+++ b/lib/stdlib/src/digraph.erl
@@ -230,7 +230,7 @@ in_neighbours(G, V) ->
Edges :: [edge()].
in_edges(G, V) ->
- ets:select(G#digraph.ntab, [{{{in, V}, '$1'}, [], ['$1']}]).
+ [E || {{in, _}, E} <- ets:lookup(G#digraph.ntab, {in, V})].
-spec out_degree(G, V) -> non_neg_integer() when
G :: graph(),
@@ -255,7 +255,7 @@ out_neighbours(G, V) ->
Edges :: [edge()].
out_edges(G, V) ->
- ets:select(G#digraph.ntab, [{{{out, V}, '$1'}, [], ['$1']}]).
+ [E || {{out, _}, E} <- ets:lookup(G#digraph.ntab, {out, V})].
-spec add_edge(G, V1, V2) -> edge() | {'error', add_edge_err_rsn()} when
G :: graph(),
diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl
index f027d05f55..6078c5e67b 100644
--- a/lib/stdlib/src/edlin.erl
+++ b/lib/stdlib/src/edlin.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -352,9 +352,6 @@ do_op({blink,C,M}, Bef=[$$,$$|_], Aft, Rs) ->
%% don't blink after a $
do_op({blink,C,_}, Bef=[$$|_], Aft, Rs) ->
do_op({insert,C}, Bef, Aft, Rs);
-%do_op({blink,C,M}, Bef, [], Rs) ->
-% N = over_paren(Bef, C, M),
-% {blink,N+1,{[C|Bef],[]},[{move_rel,-(N+1)},{put_chars,[C]}|Rs]};
do_op({blink,C,M}, Bef, Aft, Rs) ->
case over_paren(Bef, C, M) of
beep ->
diff --git a/lib/stdlib/src/edlin_expand.erl b/lib/stdlib/src/edlin_expand.erl
index bdcefda6e5..bb6ad26d8f 100644
--- a/lib/stdlib/src/edlin_expand.erl
+++ b/lib/stdlib/src/edlin_expand.erl
@@ -32,35 +32,71 @@
%% function name must be on the same line. CurrentBefore is reversed
%% and over_word/3 reverses the characters it finds. In certain cases
%% possible expansions are printed.
+%%
+%% The function also handles expansion with "h(" for module and functions.
expand(Bef0) ->
{Bef1,Word,_} = edlin:over_word(Bef0, [], 0),
case over_white(Bef1, [], 0) of
- {[$:|Bef2],_White,_Nwh} ->
+ {[$,|Bef2],_White,_Nwh} ->
+ {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
+ {Bef4,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
+ case expand_function(Bef4) of
+ help ->
+ expand_function_name(Mod, Word, ",");
+ _ ->
+ expand_module_name(Word, ",")
+ end;
+ {[$:|Bef2],_White,_Nwh} ->
{Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
{_,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
- expand_function_name(Mod, Word);
+ expand_function_name(Mod, Word, "(");
{_,_,_} ->
- expand_module_name(Word)
+ CompleteChar
+ = case expand_function(Bef1) of
+ help -> ",";
+ _ -> ":"
+ end,
+ expand_module_name(Word, CompleteChar)
end.
-expand_module_name(Prefix) ->
- match(Prefix, code:all_loaded(), ":").
+expand_function("("++Str) ->
+ case edlin:over_word(Str, [], 0) of
+ {_,"h",_} ->
+ help;
+ {_,"ht",_} ->
+ help_type;
+ _ ->
+ module
+ end;
+expand_function(_) ->
+ module.
+
+expand_module_name("",_) ->
+ {no, [], []};
+expand_module_name(Prefix,CompleteChar) ->
+ match(Prefix, [{list_to_atom(M),P} || {M,P,_} <- code:all_available()], CompleteChar).
-expand_function_name(ModStr, FuncPrefix) ->
+expand_function_name(ModStr, FuncPrefix, CompleteChar) ->
case to_atom(ModStr) of
{ok, Mod} ->
- case erlang:module_loaded(Mod) of
- true ->
- L = Mod:module_info(),
- case lists:keyfind(exports, 1, L) of
- {_, Exports} ->
- match(FuncPrefix, Exports, "(");
- _ ->
- {no, [], []}
- end;
- false ->
- {no, [], []}
- end;
+ Exports =
+ case erlang:module_loaded(Mod) of
+ true ->
+ Mod:module_info(exports);
+ false ->
+ case beam_lib:chunks(code:which(Mod), [exports]) of
+ {ok, {Mod, [{exports,E}]}} ->
+ E;
+ _ ->
+ {no, [], []}
+ end
+ end,
+ case Exports of
+ {no, [], []} ->
+ {no, [], []};
+ Exports ->
+ match(FuncPrefix, Exports, CompleteChar)
+ end;
error ->
{no, [], []}
end.
@@ -99,8 +135,10 @@ match(Prefix, Alts, Extra0) ->
{no, [], []}
end.
-flat_write(T) ->
- lists:flatten(io_lib:fwrite("~tw",[T])).
+flat_write(T) when is_atom(T) ->
+ lists:flatten(io_lib:fwrite("~tw",[T]));
+flat_write(S) ->
+ S.
%% Return the list of names L in multiple columns.
format_matches(L) ->
diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
index 181a524db6..92b43a89ce 100644
--- a/lib/stdlib/src/epp.erl
+++ b/lib/stdlib/src/epp.erl
@@ -1106,6 +1106,8 @@ scan_if([{'(',_}|_]=Toks, If, From, St) ->
Error = case Error0 of
{_,erl_parse,_} ->
{error,Error0};
+ {error,ErrL,What} ->
+ {error,{ErrL,epp,What}};
_ ->
{error,{loc(If),epp,Error0}}
end,
diff --git a/lib/stdlib/src/erl_error.erl b/lib/stdlib/src/erl_error.erl
index fdcb9e824c..5fbf5a6282 100644
--- a/lib/stdlib/src/erl_error.erl
+++ b/lib/stdlib/src/erl_error.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
%%
-module(erl_error).
--export([format_exception/6, format_exception/7,
+-export([format_exception/6, format_exception/7, format_exception/8,
format_stacktrace/4, format_stacktrace/5,
format_call/4, format_call/5, format_fun/1, format_fun/2]).
@@ -38,20 +38,34 @@ format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun) ->
%% -> iolist() | unicode:charlist() (no \n at end)
%% FormatFun = fun(Term, I) -> iolist() | unicode:charlist().
-format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun, Encoding)
+format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun, Encoding) ->
+ FF = wrap_format_fun_2(FormatFun),
+ format_exception(I, Class, Reason, StackTrace, StackFun, FF, Encoding, -1).
+
+format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun, Encoding,
+ CharsLimit)
when is_integer(I), I >= 1, is_function(StackFun, 3),
- is_function(FormatFun, 2) ->
+ is_function(FormatFun, 3), is_integer(CharsLimit) ->
S = n_spaces(I-1),
{Term,Trace1,Trace} = analyze_exception(Class, Reason, StackTrace),
- Expl0 = explain_reason(Term, Class, Trace1, FormatFun, S, Encoding),
+ StLimit = if
+ CharsLimit < 0 ->
+ CharsLimit;
+ true ->
+ %% Reserve one third for the stacktrace.
+ CharsLimit div 3
+ end,
+ St = format_stacktrace1(S, Trace, FormatFun, StackFun, Encoding, StLimit),
+ Lim = sub(sub(CharsLimit, exited(Class), latin1), St, Encoding),
+ Expl0 = explain_reason(Term, Class, Trace1, FormatFun, S, Encoding, Lim),
FormatString = case Encoding of
latin1 -> "~s~s";
_ -> "~s~ts"
end,
Expl = io_lib:fwrite(FormatString, [exited(Class), Expl0]),
- case format_stacktrace1(S, Trace, FormatFun, StackFun, Encoding) of
+ case St of
[] -> Expl;
- Stack -> [Expl, $\n, Stack]
+ _ -> [Expl, $\n, St]
end.
%% -> iolist() (no \n at end)
@@ -63,7 +77,8 @@ format_stacktrace(I, StackTrace, StackFun, FormatFun, Encoding)
when is_integer(I), I >= 1, is_function(StackFun, 3),
is_function(FormatFun, 2) ->
S = n_spaces(I-1),
- format_stacktrace1(S, StackTrace, FormatFun, StackFun, Encoding).
+ FF = wrap_format_fun_2(FormatFun),
+ format_stacktrace1(S, StackTrace, FF, StackFun, Encoding, -1).
%% -> iolist() (no \n at end)
format_call(I, ForMForFun, As, FormatFun) ->
@@ -72,7 +87,8 @@ format_call(I, ForMForFun, As, FormatFun) ->
%% -> iolist() | unicode:charlist() (no \n at end)
format_call(I, ForMForFun, As, FormatFun, Enc)
when is_integer(I), I >= 1, is_list(As), is_function(FormatFun, 2) ->
- format_call("", n_spaces(I-1), ForMForFun, As, FormatFun, Enc).
+ FF = wrap_format_fun_2(FormatFun),
+ format_call("", n_spaces(I-1), ForMForFun, As, FF, Enc).
%% -> iolist() (no \n at end)
format_fun(Fun) ->
@@ -94,6 +110,9 @@ format_fun(Fun, Enc) when is_function(Fun) ->
mfa_to_string(M, F, A, Enc)
end.
+wrap_format_fun_2(FormatFun) ->
+ fun(T, I1, CL) -> {FormatFun(T, I1), CL} end.
+
analyze_exception(error, Term, Stack) ->
case {is_stacktrace(Stack), Stack, Term} of
{true, [{_,_,As,_}=MFAL|MFAs], function_clause} when is_list(As) ->
@@ -127,82 +146,83 @@ is_stacktrace(_) ->
false.
%% ERTS exit codes (some of them are also returned by erl_eval):
-explain_reason(badarg, error, [], _PF, _S, _Enc) ->
+explain_reason(badarg, error, [], _PF, _S, _Enc, _CL) ->
<<"bad argument">>;
-explain_reason({badarg,V}, error=Cl, [], PF, S, _Enc) -> % orelse, andalso
- format_value(V, <<"bad argument: ">>, Cl, PF, S);
-explain_reason(badarith, error, [], _PF, _S, _Enc) ->
+explain_reason({badarg,V}, error=Cl, [], PF, S, _Enc, CL) -> % orelse, andalso
+ format_value(V, <<"bad argument: ">>, Cl, PF, S, CL);
+explain_reason(badarith, error, [], _PF, _S, _Enc, _CL) ->
<<"an error occurred when evaluating an arithmetic expression">>;
-explain_reason({badarity,{Fun,As}}, error, [], _PF, _S, Enc)
+explain_reason({badarity,{Fun,As}}, error, [], _PF, _S, Enc, _CL)
when is_function(Fun) ->
%% Only the arity is displayed, not the arguments As.
io_lib:fwrite(<<"~ts called with ~s">>,
[format_fun(Fun, Enc), argss(length(As))]);
-explain_reason({badfun,Term}, error=Cl, [], PF, S, _Enc) ->
- format_value(Term, <<"bad function ">>, Cl, PF, S);
-explain_reason({badmatch,Term}, error=Cl, [], PF, S, _Enc) ->
+explain_reason({badfun,Term}, error=Cl, [], PF, S, _Enc, CL) ->
+ format_value(Term, <<"bad function ">>, Cl, PF, S, CL);
+explain_reason({badmatch,Term}, error=Cl, [], PF, S, _Enc, CL) ->
Str = <<"no match of right hand side value ">>,
- format_value(Term, Str, Cl, PF, S);
-explain_reason({case_clause,V}, error=Cl, [], PF, S, _Enc) ->
+ format_value(Term, Str, Cl, PF, S, CL);
+explain_reason({case_clause,V}, error=Cl, [], PF, S, _Enc, CL) ->
%% "there is no case clause with a true guard sequence and a
%% pattern matching..."
- format_value(V, <<"no case clause matching ">>, Cl, PF, S);
-explain_reason(function_clause, error, [{F,A}], _PF, _S, _Enc) ->
+ format_value(V, <<"no case clause matching ">>, Cl, PF, S, CL);
+explain_reason(function_clause, error, [{F,A}], _PF, _S, _Enc, _CL) ->
%% Shell commands
FAs = io_lib:fwrite(<<"~w/~w">>, [F, A]),
[<<"no function clause matching call to ">> | FAs];
-explain_reason(function_clause, error=Cl, [{M,F,As,Loc}], PF, S, Enc) ->
+explain_reason(function_clause, error=Cl, [{M,F,As,Loc}], PF, S, Enc, CL) ->
Str = <<"no function clause matching ">>,
- [format_errstr_call(Str, Cl, {M,F}, As, PF, S, Enc),$\s|location(Loc)];
-explain_reason(if_clause, error, [], _PF, _S, _Enc) ->
+ [format_errstr_call(Str, Cl, {M,F}, As, PF, S, Enc, CL),$\s|location(Loc)];
+explain_reason(if_clause, error, [], _PF, _S, _Enc, _CL) ->
<<"no true branch found when evaluating an if expression">>;
-explain_reason(noproc, error, [], _PF, _S, _Enc) ->
+explain_reason(noproc, error, [], _PF, _S, _Enc, _CL) ->
<<"no such process or port">>;
-explain_reason(notalive, error, [], _PF, _S, _Enc) ->
+explain_reason(notalive, error, [], _PF, _S, _Enc, _CL) ->
<<"the node cannot be part of a distributed system">>;
-explain_reason(system_limit, error, [], _PF, _S, _Enc) ->
+explain_reason(system_limit, error, [], _PF, _S, _Enc, _CL) ->
<<"a system limit has been reached">>;
-explain_reason(timeout_value, error, [], _PF, _S, _Enc) ->
+explain_reason(timeout_value, error, [], _PF, _S, _Enc, _CL) ->
<<"bad receive timeout value">>;
-explain_reason({try_clause,V}, error=Cl, [], PF, S, _Enc) ->
+explain_reason({try_clause,V}, error=Cl, [], PF, S, _Enc, CL) ->
%% "there is no try clause with a true guard sequence and a
%% pattern matching..."
- format_value(V, <<"no try clause matching ">>, Cl, PF, S);
-explain_reason(undef, error, [{M,F,A,_}], _PF, _S, Enc) ->
+ format_value(V, <<"no try clause matching ">>, Cl, PF, S, CL);
+explain_reason(undef, error, [{M,F,A,_}], _PF, _S, Enc, _CL) ->
%% Only the arity is displayed, not the arguments, if there are any.
io_lib:fwrite(<<"undefined function ~ts">>,
[mfa_to_string(M, F, n_args(A), Enc)]);
-explain_reason({shell_undef,F,A,_}, error, [], _PF, _S, Enc) ->
+explain_reason({shell_undef,F,A,_}, error, [], _PF, _S, Enc, _CL) ->
%% Give nicer reports for undefined shell functions
%% (but not when the user actively calls shell_default:F(...)).
FS = to_string(F, Enc),
io_lib:fwrite(<<"undefined shell command ~ts/~w">>, [FS, n_args(A)]);
%% Exit codes returned by erl_eval only:
-explain_reason({argument_limit,_Fun}, error, [], _PF, _S, _Enc) ->
+explain_reason({argument_limit,_Fun}, error, [], _PF, _S, _Enc, _CL) ->
io_lib:fwrite(<<"limit of number of arguments to interpreted function"
" exceeded">>, []);
-explain_reason({bad_filter,V}, error=Cl, [], PF, S, _Enc) ->
- format_value(V, <<"bad filter ">>, Cl, PF, S);
-explain_reason({bad_generator,V}, error=Cl, [], PF, S, _Enc) ->
- format_value(V, <<"bad generator ">>, Cl, PF, S);
-explain_reason({unbound,V}, error, [], _PF, _S, _Enc) ->
+explain_reason({bad_filter,V}, error=Cl, [], PF, S, _Enc, CL) ->
+ format_value(V, <<"bad filter ">>, Cl, PF, S, CL);
+explain_reason({bad_generator,V}, error=Cl, [], PF, S, _Enc, CL) ->
+ format_value(V, <<"bad generator ">>, Cl, PF, S, CL);
+explain_reason({unbound,V}, error, [], _PF, _S, _Enc, _CL) ->
io_lib:fwrite(<<"variable ~w is unbound">>, [V]);
%% Exit codes local to the shell module (restricted shell):
-explain_reason({restricted_shell_bad_return, V}, exit=Cl, [], PF, S, _Enc) ->
+explain_reason({restricted_shell_bad_return, V}, exit=Cl, [], PF, S, _Enc, CL) ->
Str = <<"restricted shell module returned bad value ">>,
- format_value(V, Str, Cl, PF, S);
+ format_value(V, Str, Cl, PF, S, CL);
explain_reason({restricted_shell_disallowed,{ForMF,As}},
- exit=Cl, [], PF, S, Enc) ->
+ exit=Cl, [], PF, S, Enc, CL) ->
%% ForMF can be a fun, but not a shell fun.
Str = <<"restricted shell does not allow ">>,
- format_errstr_call(Str, Cl, ForMF, As, PF, S, Enc);
-explain_reason(restricted_shell_started, exit, [], _PF, _S, _Enc) ->
+ format_errstr_call(Str, Cl, ForMF, As, PF, S, Enc, CL);
+explain_reason(restricted_shell_started, exit, [], _PF, _S, _Enc, _CL) ->
<<"restricted shell starts now">>;
-explain_reason(restricted_shell_stopped, exit, [], _PF, _S, _Enc) ->
+explain_reason(restricted_shell_stopped, exit, [], _PF, _S, _Enc, _CL) ->
<<"restricted shell stopped">>;
%% Other exit code:
-explain_reason(Reason, Class, [], PF, S, _Enc) ->
- PF(Reason, (iolist_size(S)+1) + exited_size(Class)).
+explain_reason(Reason, Class, [], PF, S, _Enc, CL) ->
+ {L, _} = PF(Reason, (iolist_size(S)+1) + exited_size(Class), CL),
+ L.
n_args(A) when is_integer(A) ->
A;
@@ -218,29 +238,33 @@ argss(2) ->
argss(I) ->
io_lib:fwrite(<<"~w arguments">>, [I]).
-format_stacktrace1(S0, Stack0, PF, SF, Enc) ->
+format_stacktrace1(S0, Stack0, PF, SF, Enc, CL) ->
Stack1 = lists:dropwhile(fun({M,F,A,_}) -> SF(M, F, A)
end, lists:reverse(Stack0)),
S = [" " | S0],
Stack = lists:reverse(Stack1),
- format_stacktrace2(S, Stack, 1, PF, Enc).
-
-format_stacktrace2(S, [{M,F,A,L}|Fs], N, PF, Enc) when is_integer(A) ->
- [io_lib:fwrite(<<"~s~s ~ts ~ts">>,
- [sep(N, S), origin(N, M, F, A),
- mfa_to_string(M, F, A, Enc),
- location(L)])
- | format_stacktrace2(S, Fs, N + 1, PF, Enc)];
-format_stacktrace2(S, [{M,F,As,_}|Fs], N, PF, Enc) when is_list(As) ->
+ format_stacktrace2(S, Stack, 1, PF, Enc, CL).
+
+format_stacktrace2(_S, _Stack, _N, _PF, _Enc, _CL=0) ->
+ [];
+format_stacktrace2(S, [{M,F,A,L}|Fs], N, PF, Enc, CL) when is_integer(A) ->
+ Cs = io_lib:fwrite(<<"~s~s ~ts ~ts">>,
+ [sep(N, S), origin(N, M, F, A),
+ mfa_to_string(M, F, A, Enc),
+ location(L)]),
+ CL1 = sub(CL, Cs, Enc),
+ [Cs | format_stacktrace2(S, Fs, N + 1, PF, Enc, CL1)];
+format_stacktrace2(S, [{M,F,As,_}|Fs], N, PF, Enc, CL) when is_list(As) ->
A = length(As),
CalledAs = [S,<<" called as ">>],
- C = format_call("", CalledAs, {M,F}, As, PF, Enc),
- [io_lib:fwrite(<<"~s~s ~ts\n~s~ts">>,
- [sep(N, S), origin(N, M, F, A),
- mfa_to_string(M, F, A, Enc),
- CalledAs, C])
- | format_stacktrace2(S, Fs, N + 1, PF, Enc)];
-format_stacktrace2(_S, [], _N, _PF, _Enc) ->
+ C = format_call("", CalledAs, {M,F}, As, PF, Enc, CL),
+ Cs = io_lib:fwrite(<<"~s~s ~ts\n~s~ts">>,
+ [sep(N, S), origin(N, M, F, A),
+ mfa_to_string(M, F, A, Enc),
+ CalledAs, C]),
+ CL1 = sub(CL, Enc, Cs),
+ [Cs | format_stacktrace2(S, Fs, N + 1, PF, Enc, CL1)];
+format_stacktrace2(_S, [], _N, _PF, _Enc, _CL) ->
"".
location(L) ->
@@ -264,22 +288,26 @@ origin(1, M, F, A) ->
origin(_N, _M, _F, _A) ->
<<"in call from">>.
-format_errstr_call(ErrStr, Class, ForMForFun, As, PF, Pre0, Enc) ->
+format_errstr_call(ErrStr, Class, ForMForFun, As, PF, Pre0, Enc, CL) ->
Pre1 = [Pre0 | n_spaces(exited_size(Class))],
- format_call(ErrStr, Pre1, ForMForFun, As, PF, Enc).
+ format_call(ErrStr, Pre1, ForMForFun, As, PF, Enc, CL).
format_call(ErrStr, Pre1, ForMForFun, As, PF, Enc) ->
+ format_call(ErrStr, Pre1, ForMForFun, As, PF, Enc, -1).
+
+format_call(ErrStr, Pre1, ForMForFun, As, PF, Enc, CL) ->
Arity = length(As),
[ErrStr |
case is_op(ForMForFun, Arity) of
{yes,Op} ->
- format_op(ErrStr, Pre1, Op, As, PF, Enc);
+ format_op(ErrStr, Pre1, Op, As, PF, Enc, CL);
no ->
MFs = mf_to_string(ForMForFun, Arity, Enc),
I1 = string:length([Pre1,ErrStr|MFs]),
- S1 = pp_arguments(PF, As, I1, Enc),
- S2 = pp_arguments(PF, As, string:length([Pre1|MFs]), Enc),
- Long = count_nl(pp_arguments(PF, [a2345,b2345], I1, Enc)) > 0,
+ S1 = pp_arguments(PF, As, I1, Enc, CL),
+ S2 = pp_arguments(PF, As, string:length([Pre1|MFs]), Enc, CL),
+ S3 = pp_arguments(PF, [a2345,b2345], I1, Enc, CL),
+ Long = count_nl(S3) > 0,
case Long or (count_nl(S2) < count_nl(S1)) of
true ->
[$\n, Pre1, MFs, S2];
@@ -288,14 +316,15 @@ format_call(ErrStr, Pre1, ForMForFun, As, PF, Enc) ->
end
end].
-format_op(ErrStr, Pre, Op, [A1], PF, _Enc) ->
+format_op(ErrStr, Pre, Op, [A1], PF, _Enc, CL) ->
OpS = io_lib:fwrite(<<"~s ">>, [Op]),
I1 = iolist_size([ErrStr,Pre,OpS]),
- [OpS | PF(A1, I1+1)];
-format_op(ErrStr, Pre, Op, [A1, A2], PF, Enc) ->
+ {S, _} = PF(A1, I1+1, CL),
+ [OpS | S];
+format_op(ErrStr, Pre, Op, [A1, A2], PF, Enc, CL) ->
I1 = iolist_size([ErrStr,Pre]),
- S1 = PF(A1, I1+1),
- S2 = PF(A2, I1+1),
+ {S1, CL1} = PF(A1, I1+1, CL),
+ {S2, _} = PF(A2, I1+1, CL1),
OpS = atom_to_list(Op),
Pre1 = [$\n | n_spaces(I1)],
case count_nl(S1) > 0 of
@@ -304,26 +333,28 @@ format_op(ErrStr, Pre, Op, [A1, A2], PF, Enc) ->
false ->
OpS2 = io_lib:fwrite(<<" ~s ">>, [Op]),
Size1 = iolist_size([ErrStr,Pre|OpS2]),
- {Size2,S1_2} = size(Enc, S1),
- S2_2 = PF(A2, Size1+Size2+1),
+ Size2 = size(Enc, S1),
+ {S2_2, _} = PF(A2, Size1+Size2+1, CL1),
case count_nl(S2) < count_nl(S2_2) of
true ->
- [S1_2,Pre1,OpS,Pre1|S2];
+ [S1,Pre1,OpS,Pre1|S2];
false ->
- [S1_2,OpS2|S2_2]
+ [S1,OpS2|S2_2]
end
end.
-pp_arguments(PF, As, I, Enc) ->
+pp_arguments(PF, As, I, Enc, CL) ->
case {As, printable_list(Enc, As)} of
{[Int | T], true} ->
L = integer_to_list(Int),
Ll = length(L),
A = list_to_atom(lists:duplicate(Ll, $a)),
- S0 = unicode:characters_to_list(PF([A | T], I+1), Enc),
- brackets_to_parens([$[,L,string:slice(S0, 1+Ll)], Enc);
+ {S0, _} = PF([A | T], I+1, CL),
+ S = unicode:characters_to_list(S0, Enc),
+ brackets_to_parens([$[,L,string:slice(S, 1+Ll)], Enc);
_ ->
- brackets_to_parens(PF(As, I+1), Enc)
+ {S, _CL1} = PF(As, I+1, CL),
+ brackets_to_parens(S, Enc)
end.
brackets_to_parens(S, Enc) ->
@@ -361,12 +392,12 @@ mf_to_string(F, _A, Enc) ->
FS = to_string(F, Enc),
io_lib:fwrite(<<"~ts">>, [FS]).
-format_value(V, ErrStr, Class, PF, S) ->
+format_value(V, ErrStr, Class, PF, S, CL) ->
Pre1Sz = exited_size(Class),
- S1 = PF(V, Pre1Sz + iolist_size([S, ErrStr])+1),
+ {S1, _} = PF(V, Pre1Sz + iolist_size([S, ErrStr]) + 1, CL),
[ErrStr | case count_nl(S1) of
N1 when N1 > 1 ->
- S2 = PF(V, iolist_size(S) + 1 + Pre1Sz),
+ {S2, _} = PF(V, iolist_size(S) + 1 + Pre1Sz, CL),
case count_nl(S2) < N1 of
true ->
[$\n, S, n_spaces(Pre1Sz) | S2];
@@ -413,9 +444,17 @@ to_string(A, latin1) ->
to_string(A, _) ->
io_lib:write_atom(A).
+%% Make sure T does change sign.
+sub(T, _, _Enc) when T < 0 -> T;
+sub(T, S, Enc) ->
+ sub(T, size(Enc, S)).
+
+sub(T, Sz) when T >= Sz ->
+ T - Sz;
+sub(_T, _Sz) ->
+ 0.
+
size(latin1, S) ->
- {iolist_size(S),S};
-size(_, S0) ->
- S = unicode:characters_to_list(S0, unicode),
- true = is_list(S),
- {string:length(S),S}.
+ iolist_size(S);
+size(_, S) ->
+ string:length(S).
diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl
index 2066b2f60f..aa809ab05c 100644
--- a/lib/stdlib/src/erl_eval.erl
+++ b/lib/stdlib/src/erl_eval.erl
@@ -501,13 +501,13 @@ find_maxline(LC) ->
hide_calls(LC, MaxLine) ->
LineId0 = MaxLine + 1,
- {NLC, _, D} = hide(LC, LineId0, dict:new()),
+ {NLC, _, D} = hide(LC, LineId0, maps:new()),
{NLC, D}.
%% v/1 and local calls are hidden.
hide({value,L,V}, Id, D) ->
A = erl_anno:new(Id),
- {{atom,A,ok}, Id+1, dict:store(Id, {value,L,V}, D)};
+ {{atom,A,ok}, Id+1, maps:put(Id, {value,L,V}, D)};
hide({call,L,{atom,_,N}=Atom,Args}, Id0, D0) ->
{NArgs, Id, D} = hide(Args, Id0, D0),
C = case erl_internal:bif(N, length(Args)) of
@@ -517,7 +517,7 @@ hide({call,L,{atom,_,N}=Atom,Args}, Id0, D0) ->
A = erl_anno:new(Id),
{call,A,{remote,L,{atom,L,m},{atom,L,f}},NArgs}
end,
- {C, Id+1, dict:store(Id, {call,Atom}, D)};
+ {C, Id+1, maps:put(Id, {call,Atom}, D)};
hide(T0, Id0, D0) when is_tuple(T0) ->
{L, Id, D} = hide(tuple_to_list(T0), Id0, D0),
{list_to_tuple(L), Id, D};
@@ -532,7 +532,7 @@ unhide_calls({atom,A,ok}=E, MaxLine, D) ->
L = erl_anno:line(A),
if
L > MaxLine ->
- dict:fetch(L, D);
+ map_get(L, D);
true ->
E
end;
@@ -540,7 +540,7 @@ unhide_calls({call,A,{remote,L,{atom,L,m},{atom,L,f}}=F,Args}, MaxLine, D) ->
Line = erl_anno:line(A),
if
Line > MaxLine ->
- {call,Atom} = dict:fetch(Line, D),
+ {call,Atom} = map_get(Line, D),
{call,L,Atom,unhide_calls(Args, MaxLine, D)};
true ->
{call,A,F,unhide_calls(Args, MaxLine, D)}
@@ -1163,9 +1163,19 @@ match1({map,_,Fs}, #{}=Map, Bs, BBs) ->
match1({map,_,_}, _, _Bs, _BBs) ->
throw(nomatch);
match1({bin, _, Fs}, <<_/bitstring>>=B, Bs0, BBs) ->
- eval_bits:match_bits(Fs, B, Bs0, BBs,
- match_fun(BBs),
- fun(E, Bs) -> expr(E, Bs, none, none, none) end);
+ EvalFun = fun(E, Bs) ->
+ case erl_lint:is_guard_expr(E) of
+ true -> ok;
+ false -> throw(invalid)
+ end,
+ try
+ expr(E, Bs, none, none, none)
+ catch
+ error:{unbound, _} ->
+ throw(invalid)
+ end
+ end,
+ eval_bits:match_bits(Fs, B, Bs0, BBs, match_fun(BBs), EvalFun);
match1({bin,_,_}, _, _Bs, _BBs) ->
throw(nomatch);
match1({op,_,'++',{nil,_},R}, Term, Bs, BBs) ->
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
index d7bd15d9db..6200978d4d 100644
--- a/lib/stdlib/src/erl_expand_records.erl
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -20,10 +20,6 @@
%% Purpose: Expand records into tuples. Also add explicit module
%% names to calls to imported functions and BIFs.
-%% N.B. Although structs (tagged tuples) are not yet allowed in the
-%% language there is code included in pattern/2 and expr/3 (commented out)
-%% that handles them.
-
-module(erl_expand_records).
-export([module/2]).
@@ -125,9 +121,6 @@ pattern({map_field_exact,Line,K0,V0}, St0) ->
{K,St1} = expr(K0, St0),
{V,St2} = pattern(V0, St1),
{{map_field_exact,Line,K,V},St2};
-%%pattern({struct,Line,Tag,Ps}, St0) ->
-%% {TPs,TPsvs,St1} = pattern_list(Ps, St0),
-%% {{struct,Line,Tag,TPs},TPsvs,St1};
pattern({record_index,Line,Name,Field}, St) ->
{index_expr(Line, Field, Name, record_fields(Name, St)),St};
pattern({record,Line0,Name,Pfs}, St0) ->
@@ -310,9 +303,6 @@ expr({map_field_exact,Line,K0,V0}, St0) ->
{K,St1} = expr(K0, St0),
{V,St2} = expr(V0, St1),
{{map_field_exact,Line,K,V},St2};
-%%expr({struct,Line,Tag,Es0}, Vs, St0) ->
-%% {Es1,Esvs,Esus,St1} = expr_list(Es0, Vs, St0),
-%% {{struct,Line,Tag,Es1},Esvs,Esus,St1};
expr({record_index,Line,Name,F}, St) ->
I = index_expr(Line, F, Name, record_fields(Name, St)),
expr(I, St);
@@ -797,9 +787,13 @@ is_simple_val(Val) ->
pattern_bin(Es0, St) ->
foldr(fun (E, Acc) -> pattern_element(E, Acc) end, {[],St}, Es0).
-pattern_element({bin_element,Line,Expr0,Size,Type}, {Es,St0}) ->
+pattern_element({bin_element,Line,Expr0,Size0,Type}, {Es,St0}) ->
{Expr,St1} = pattern(Expr0, St0),
- {[{bin_element,Line,Expr,Size,Type} | Es],St1}.
+ {Size,St2} = case Size0 of
+ default -> {Size0,St1};
+ _ -> expr(Size0, St1)
+ end,
+ {[{bin_element,Line,Expr,Size,Type} | Es],St2}.
%% expr_bin([Element], State) -> {[Element],State}.
diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl
index 939abaff00..6ff5e23ee3 100644
--- a/lib/stdlib/src/erl_internal.erl
+++ b/lib/stdlib/src/erl_internal.erl
@@ -245,11 +245,14 @@ bif(M, F, A) when is_atom(M), is_atom(F), is_integer(A) -> false.
bif(abs, 1) -> true;
bif(apply, 2) -> true;
bif(apply, 3) -> true;
+bif(atom_to_binary, 1) -> true;
bif(atom_to_binary, 2) -> true;
bif(atom_to_list, 1) -> true;
bif(binary_part, 2) -> true;
bif(binary_part, 3) -> true;
+bif(binary_to_atom, 1) -> true;
bif(binary_to_atom, 2) -> true;
+bif(binary_to_existing_atom, 1) -> true;
bif(binary_to_existing_atom, 2) -> true;
bif(binary_to_integer, 1) -> true;
bif(binary_to_integer, 2) -> true;
@@ -308,7 +311,6 @@ bif(is_process_alive, 1) -> true;
bif(is_atom, 1) -> true;
bif(is_boolean, 1) -> true;
bif(is_binary, 1) -> true;
-bif(is_bitstr, 1) -> true;
bif(is_bitstring, 1) -> true;
bif(is_float, 1) -> true;
bif(is_function, 1) -> true;
@@ -345,7 +347,6 @@ bif(max,2) -> true;
bif(min,2) -> true;
bif(module_loaded, 1) -> true;
bif(monitor, 2) -> true;
-bif(monitor, 3) -> true;
bif(monitor_node, 2) -> true;
bif(node, 0) -> true;
bif(node, 1) -> true;
@@ -383,8 +384,16 @@ bif(spawn_link, 1) -> true;
bif(spawn_link, 2) -> true;
bif(spawn_link, 3) -> true;
bif(spawn_link, 4) -> true;
+bif(spawn_request, 1) -> true;
+bif(spawn_request, 2) -> true;
+bif(spawn_request, 3) -> true;
+bif(spawn_request, 4) -> true;
+bif(spawn_request, 5) -> true;
+bif(spawn_request_abandon, 1) -> true;
bif(spawn_monitor, 1) -> true;
+bif(spawn_monitor, 2) -> true;
bif(spawn_monitor, 3) -> true;
+bif(spawn_monitor, 4) -> true;
bif(spawn_opt, 2) -> true;
bif(spawn_opt, 3) -> true;
bif(spawn_opt, 4) -> true;
@@ -393,6 +402,8 @@ bif(split_binary, 2) -> true;
bif(statistics, 1) -> true;
bif(term_to_binary, 1) -> true;
bif(term_to_binary, 2) -> true;
+bif(term_to_iovec, 1) -> true;
+bif(term_to_iovec, 2) -> true;
bif(throw, 1) -> true;
bif(time, 0) -> true;
bif(tl, 1) -> true;
@@ -452,7 +463,6 @@ old_bif(is_process_alive, 1) -> true;
old_bif(is_atom, 1) -> true;
old_bif(is_boolean, 1) -> true;
old_bif(is_binary, 1) -> true;
-old_bif(is_bitstr, 1) -> true;
old_bif(is_bitstring, 1) -> true;
old_bif(is_float, 1) -> true;
old_bif(is_function, 1) -> true;
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 54b0fbd999..7c717e47d1 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -1,8 +1,8 @@
-%% -*- erlang-indent-level: 4 -*-
+%%% -*- erlang-indent-level: 4 -*-
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,9 +20,6 @@
%%
%% Do necessary checking of Erlang code.
-%% N.B. All the code necessary for checking structs (tagged tuples) is
-%% here. Just comment out the lines in pattern/2, gexpr/3 and expr/3.
-
-module(erl_lint).
-export([module/1,module/2,module/3,format_error/1]).
@@ -31,7 +28,14 @@
-export([is_guard_expr/1]).
-export([bool_option/4,value_option/3,value_option/7]).
--import(lists, [member/2,map/2,foldl/3,foldr/3,mapfoldl/3,all/2,reverse/1]).
+-import(lists, [all/2,any/2,
+ foldl/3,foldr/3,
+ map/2,mapfoldl/3,member/2,
+ reverse/1]).
+
+%% Removed functions
+
+-removed([{modify_line,2,"use erl_parse:map_anno/2 instead"}]).
%% bool_option(OnOpt, OffOpt, Default, Options) -> boolean().
%% value_option(Flag, Default, Options) -> Value.
@@ -81,17 +85,19 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
-type module_or_mfa() :: module() | mfa().
+-type gexpr_context() :: 'guard' | 'bin_seg_size' | 'map_key'.
+
-record(typeinfo, {attr, line}).
%% Usage of records, functions, and imports. The variable table, which
%% is passed on as an argument, holds the usage of variables.
-record(usage, {
- calls = dict:new(), %Who calls who
+ calls = maps:new(), %Who calls who
imported = [], %Actually imported functions
- used_records = sets:new() %Used record definitions
- :: sets:set(atom()),
- used_types = dict:new() %Used type definitions
- :: dict:dict(ta(), line())
+ used_records = gb_sets:new() %Used record definitions
+ :: gb_sets:set(atom()),
+ used_types = maps:new() %Used type definitions
+ :: #{ta() := line()}
}).
@@ -104,8 +110,8 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
exports=gb_sets:empty() :: gb_sets:set(fa()),%Exports
imports=[] :: orddict:orddict(fa(), module()),%Imports
compile=[], %Compile flags
- records=dict:new() %Record definitions
- :: dict:dict(atom(), {line(),Fields :: term()}),
+ records=maps:new() %Record definitions
+ :: #{atom() => {line(),Fields :: term()}},
locals=gb_sets:empty() %All defined functions (prescanned)
:: gb_sets:set(fa()),
no_auto=gb_sets:empty() %Functions explicitly not autoimported
@@ -131,17 +137,20 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
xqlc= false :: boolean(), %true if qlc.hrl included
called= [] :: [{fa(),line()}], %Called functions
usage = #usage{} :: #usage{},
- specs = dict:new() %Type specifications
- :: dict:dict(mfa(), line()),
- callbacks = dict:new() %Callback types
- :: dict:dict(mfa(), line()),
- optional_callbacks = dict:new() %Optional callbacks
- :: dict:dict(mfa(), line()),
- types = dict:new() %Type definitions
- :: dict:dict(ta(), #typeinfo{}),
+ specs = maps:new() %Type specifications
+ :: #{mfa() => line()},
+ callbacks = maps:new() %Callback types
+ :: #{mfa() => line()},
+ optional_callbacks = maps:new() %Optional callbacks
+ :: #{mfa() => line()},
+ types = maps:new() %Type definitions
+ :: #{ta() => #typeinfo{}},
exp_types=gb_sets:empty() %Exported types
:: gb_sets:set(ta()),
- in_try_head=false :: boolean() %In a try head.
+ in_try_head=false :: boolean(), %In a try head.
+ bvt = none :: 'none' | [any()], %Variables in binary pattern
+ gexpr_context = guard %Context of guard expression
+ :: gexpr_context()
}).
-type lint_state() :: #lint{}.
@@ -183,6 +192,14 @@ format_error({invalid_deprecated,D}) ->
format_error({bad_deprecated,{F,A}}) ->
io_lib:format("deprecated function ~tw/~w undefined or not exported",
[F,A]);
+format_error({invalid_removed,D}) ->
+ io_lib:format("badly formed removed attribute ~tw", [D]);
+format_error({bad_removed,{F,A}}) when F =:= '_'; A =:= '_' ->
+ io_lib:format("at least one function matching ~tw/~w is still exported",
+ [F,A]);
+format_error({bad_removed,{F,A}}) ->
+ io_lib:format("removed function ~tw/~w is still exported",
+ [F,A]);
format_error({bad_nowarn_unused_function,{F,A}}) ->
io_lib:format("function ~tw/~w undefined", [F,A]);
format_error({bad_nowarn_bif_clash,{F,A}}) ->
@@ -198,6 +215,9 @@ format_error({bad_on_load_arity,{F,A}}) ->
io_lib:format("function ~tw/~w has wrong arity (must be 0)", [F,A]);
format_error({undefined_on_load,{F,A}}) ->
io_lib:format("function ~tw/~w undefined", [F,A]);
+format_error(nif_inline) ->
+ "inlining is enabled - local calls to NIFs may call their Erlang "
+ "implementation instead";
format_error(export_all) ->
"export_all flag enabled - all functions will be exported";
@@ -228,21 +248,21 @@ format_error({redefine_old_bif_import,{F,A}}) ->
format_error({redefine_bif_import,{F,A}}) ->
io_lib:format("import directive overrides auto-imported BIF ~w/~w~n"
" - use \"-compile({no_auto_import,[~w/~w]}).\" to resolve name clash", [F,A,F,A]);
-format_error({deprecated, MFA, ReplacementMFA, Rel}) ->
+format_error({deprecated, MFA, String, Rel}) ->
io_lib:format("~s is deprecated and will be removed in ~s; use ~s",
- [format_mfa(MFA), Rel, format_mfa(ReplacementMFA)]);
-format_error({deprecated, {M1, F1, A1}, String}) when is_list(String) ->
- io_lib:format("~p:~p/~p: ~s", [M1, F1, A1, String]);
+ [format_mfa(MFA), Rel, String]);
+format_error({deprecated, MFA, String}) when is_list(String) ->
+ io_lib:format("~s is deprecated; ~s", [format_mfa(MFA), String]);
format_error({deprecated_type, {M1, F1, A1}, String}) when is_list(String) ->
- io_lib:format("~p:~p~s: ~s", [M1, F1, gen_type_paren(A1), String]);
+ io_lib:format("the type ~p:~p~s is deprecated; ~s",
+ [M1, F1, gen_type_paren(A1), String]);
format_error({removed, MFA, ReplacementMFA, Rel}) ->
io_lib:format("call to ~s will fail, since it was removed in ~s; "
"use ~s", [format_mfa(MFA), Rel, format_mfa(ReplacementMFA)]);
format_error({removed, MFA, String}) when is_list(String) ->
- io_lib:format("~s: ~s", [format_mfa(MFA), String]);
-format_error({removed_type, MNA, ReplacementMNA, Rel}) ->
- io_lib:format("the type ~s was removed in ~s; use ~s instead",
- [format_mna(MNA), Rel, format_mna(ReplacementMNA)]);
+ io_lib:format("~s is removed; ~s", [format_mfa(MFA), String]);
+format_error({removed_type, MNA, String}) ->
+ io_lib:format("the type ~s is removed; ~s", [format_mna(MNA), String]);
format_error({obsolete_guard, {F, A}}) ->
io_lib:format("~p/~p obsolete (use is_~p/~p)", [F, A, F, A]);
format_error({obsolete_guard_overridden,Test}) ->
@@ -272,6 +292,8 @@ format_error({redefine_record,T}) ->
io_lib:format("record ~tw already defined", [T]);
format_error({redefine_field,T,F}) ->
io_lib:format("field ~tw already defined in record ~tw", [F,T]);
+format_error(bad_multi_field_init) ->
+ io_lib:format("'_' initializes no omitted fields", []);
format_error({undefined_field,T,F}) ->
io_lib:format("field ~tw undefined in record ~tw", [F,T]);
format_error(illegal_record_info) ->
@@ -313,6 +335,13 @@ format_error(bittype_unit) ->
"a bit unit size must not be specified unless a size is specified too";
format_error(illegal_bitsize) ->
"illegal bit size";
+format_error({illegal_bitsize_local_call, {F,A}}) ->
+ io_lib:format("call to local/imported function ~tw/~w is illegal in a size "
+ "expression for a binary segment",
+ [F,A]);
+format_error(non_integer_bitsize) ->
+ "a size expression in a pattern evaluates to a non-integer value; "
+ "this pattern cannot possibly match";
format_error(unsized_binary_not_at_end) ->
"a binary field without size is only allowed at the end of a binary pattern";
format_error(typed_literal_string) ->
@@ -585,13 +614,16 @@ start(File, Opts) ->
false, Opts)},
{removed,
bool_option(warn_removed, nowarn_removed,
+ true, Opts)},
+ {nif_inline,
+ bool_option(warn_nif_inline, nowarn_nif_inline,
true, Opts)}
],
Enabled1 = [Category || {Category,true} <- Enabled0],
Enabled = ordsets:from_list(Enabled1),
Calls = case ordsets:is_element(unused_function, Enabled) of
true ->
- dict:from_list([{{module_info,1},pseudolocals()}]);
+ maps:from_list([{{module_info,1},pseudolocals()}]);
false ->
undefined
end,
@@ -647,7 +679,14 @@ pack_warnings(Ws) ->
add_error(E, St) -> add_lint_error(E, St#lint.file, St).
-add_error(Anno, E, St) ->
+add_error(Anno, E0, #lint{gexpr_context=Context}=St) ->
+ E = case {E0,Context} of
+ {illegal_guard_expr,bin_seg_size} ->
+ illegal_bitsize;
+ {{illegal_guard_local_call,FA},bin_seg_size} ->
+ {illegal_bitsize_local_call,FA};
+ {_,_} -> E0
+ end,
{File,Location} = loc(Anno, St),
add_lint_error({Location,erl_lint,E}, File, St).
@@ -918,7 +957,8 @@ post_traversal_check(Forms, St0) ->
StE = check_unused_records(Forms, StD),
StF = check_local_opaque_types(StE),
StG = check_dialyzer_attribute(Forms, StF),
- check_callback_information(StG).
+ StH = check_callback_information(StG),
+ check_removed(Forms, StH).
%% check_behaviour(State0) -> State
%% Check that the behaviour attribute is valid.
@@ -1030,7 +1070,7 @@ check_deprecated(Forms, St0) ->
true -> St0#lint.defined;
false -> St0#lint.exports
end,
- X = gb_sets:to_list(Exports),
+ X = ignore_predefined_funcs(gb_sets:to_list(Exports)),
#lint{module = Mod} = St0,
Bad = [{E,L} || {attribute, L, deprecated, Depr} <- Forms,
D <- lists:flatten([Depr]),
@@ -1074,7 +1114,80 @@ depr_fa(F, A, _X, _Mod) ->
deprecated_flag(next_version) -> true;
deprecated_flag(next_major_release) -> true;
deprecated_flag(eventually) -> true;
-deprecated_flag(_) -> false.
+deprecated_flag(String) -> deprecated_desc(String).
+
+deprecated_desc([Char | Str]) when is_integer(Char) -> deprecated_desc(Str);
+deprecated_desc([]) -> true;
+deprecated_desc(_) -> false.
+
+%% check_removed(Forms, State0) -> State
+
+check_removed(Forms, St0) ->
+ %% Get the correct list of exported functions.
+ Exports = case member(export_all, St0#lint.compile) of
+ true -> St0#lint.defined;
+ false -> St0#lint.exports
+ end,
+ X = ignore_predefined_funcs(gb_sets:to_list(Exports)),
+ #lint{module = Mod} = St0,
+ Bad = [{E,L} || {attribute, L, removed, Removed} <- Forms,
+ R <- lists:flatten([Removed]),
+ E <- removed_cat(R, X, Mod)],
+ foldl(fun ({E,L}, St1) ->
+ add_error(L, E, St1)
+ end, St0, Bad).
+
+removed_cat({F, A, Desc}=R, X, Mod) ->
+ case removed_desc(Desc) of
+ false -> [{invalid_removed,R}];
+ true -> removed_fa(F, A, X, Mod)
+ end;
+removed_cat({F, A}, X, Mod) ->
+ removed_fa(F, A, X, Mod);
+removed_cat(module, X, Mod) ->
+ removed_fa('_', '_', X, Mod);
+removed_cat(R, _X, _Mod) ->
+ [{invalid_removed,R}].
+
+removed_fa('_', '_', X, _Mod) ->
+ case X of
+ [_|_] -> [{bad_removed,{'_','_'}}];
+ [] -> []
+ end;
+removed_fa(F, '_', X, _Mod) when is_atom(F) ->
+ %% Don't use this syntax for built-in functions.
+ case lists:filter(fun({F1,_}) -> F1 =:= F end, X) of
+ [_|_] -> [{bad_removed,{F,'_'}}];
+ _ -> []
+ end;
+removed_fa(F, A, X, Mod) when is_atom(F), is_integer(A), A >= 0 ->
+ case lists:member({F,A}, X) of
+ true ->
+ [{bad_removed,{F,A}}];
+ false ->
+ case erlang:is_builtin(Mod, F, A) of
+ true -> [{bad_removed,{F,A}}];
+ false -> []
+ end
+ end;
+removed_fa(F, A, _X, _Mod) ->
+ [{invalid_removed,{F,A}}].
+
+removed_desc([Char | Str]) when is_integer(Char) -> removed_desc(Str);
+removed_desc([]) -> true;
+removed_desc(_) -> false.
+
+%% Ignores functions added by erl_internal:add_predefined_functions/1
+ignore_predefined_funcs([{behaviour_info,1} | Fs]) ->
+ ignore_predefined_funcs(Fs);
+ignore_predefined_funcs([{module_info,0} | Fs]) ->
+ ignore_predefined_funcs(Fs);
+ignore_predefined_funcs([{module_info,1} | Fs]) ->
+ ignore_predefined_funcs(Fs);
+ignore_predefined_funcs([Other | Fs]) ->
+ [Other | ignore_predefined_funcs(Fs)];
+ignore_predefined_funcs([]) ->
+ [].
%% check_imports(Forms, State0) -> State
@@ -1134,7 +1247,7 @@ reached_functions([R|Rs], More0, Ref, Reached0) ->
true -> reached_functions(Rs, More0, Ref, Reached0);
false ->
Reached = gb_sets:add_element(R, Reached0), %It IS reached
- case dict:find(R, Ref) of
+ case maps:find(R, Ref) of
{ok,More} -> reached_functions(Rs, [More|More0], Ref, Reached);
error -> reached_functions(Rs, More0, Ref, Reached)
end
@@ -1157,10 +1270,10 @@ check_undefined_functions(#lint{called=Called0,defined=Def0}=St0) ->
check_undefined_types(#lint{usage=Usage,types=Def}=St0) ->
Used = Usage#usage.used_types,
- UTAs = dict:fetch_keys(Used),
- Undef = [{TA,dict:fetch(TA, Used)} ||
+ UTAs = maps:keys(Used),
+ Undef = [{TA,map_get(TA, Used)} ||
TA <- UTAs,
- not dict:is_key(TA, Def),
+ not is_map_key(TA, Def),
not is_default_type(TA)],
foldl(fun ({TA,L}, St) ->
add_error(L, {undefined_type,TA}, St)
@@ -1199,7 +1312,7 @@ check_untyped_records(Forms, St0) ->
case is_warn_enabled(untyped_record, St0) of
true ->
%% Use the names of all records *defined* in the module (not used)
- RecNames = dict:fetch_keys(St0#lint.records),
+ RecNames = maps:keys(St0#lint.records),
%% these are the records with field(s) containing type info
TRecNames = [Name ||
{attribute,_,record,{Name,Fields}} <- Forms,
@@ -1207,7 +1320,7 @@ check_untyped_records(Forms, St0) ->
(_) -> false
end, Fields)],
foldl(fun (N, St) ->
- {L, Fields} = dict:fetch(N, St0#lint.records),
+ {L, Fields} = map_get(N, St0#lint.records),
case Fields of
[] -> St; % exclude records with no fields
[_|_] -> add_warning(L, {untyped_record, N}, St)
@@ -1225,12 +1338,12 @@ check_unused_records(Forms, St0) ->
%% The check is a bit imprecise in that uses from unused
%% functions count.
Usage = St0#lint.usage,
- UsedRecords = sets:to_list(Usage#usage.used_records),
- URecs = foldl(fun (Used, Recs) ->
- dict:erase(Used, Recs)
- end, St0#lint.records, UsedRecords),
+ UsedRecords = Usage#usage.used_records,
+ URecs = gb_sets:fold(fun (Used, Recs) ->
+ maps:remove(Used, Recs)
+ end, St0#lint.records, UsedRecords),
Unused = [{Name,FileLine} ||
- {Name,{FileLine,_Fields}} <- dict:to_list(URecs),
+ {Name,{FileLine,_Fields}} <- maps:to_list(URecs),
element(1, loc(FileLine, St0)) =:= FirstFile],
foldl(fun ({N,L}, St) ->
add_warning(L, {unused_record, N}, St)
@@ -1242,27 +1355,26 @@ check_unused_records(Forms, St0) ->
check_callback_information(#lint{callbacks = Callbacks,
optional_callbacks = OptionalCbs,
defined = Defined} = St0) ->
- OptFun = fun({MFA, Line}, St) ->
- case dict:is_key(MFA, Callbacks) of
+ OptFun = fun(MFA, Line, St) ->
+ case is_map_key(MFA, Callbacks) of
true ->
St;
false ->
add_error(Line, {undefined_callback, MFA}, St)
end
end,
- St1 = lists:foldl(OptFun, St0, dict:to_list(OptionalCbs)),
+ St1 = maps:fold(OptFun, St0, OptionalCbs),
case gb_sets:is_member({behaviour_info, 1}, Defined) of
false -> St1;
true ->
- case dict:size(Callbacks) of
+ case map_size(Callbacks) of
0 -> St1;
_ ->
- CallbacksList = dict:to_list(Callbacks),
- FoldL =
- fun({Fa, Line}, St) ->
+ FoldFun =
+ fun(Fa, Line, St) ->
add_error(Line, {behaviour_info, Fa}, St)
end,
- lists:foldl(FoldL, St1, CallbacksList)
+ maps:fold(FoldFun, St1, Callbacks)
end
end.
@@ -1300,7 +1412,7 @@ export_type(Line, ETs, #lint{usage = Usage, exp_types = ETs0} = St0) ->
false ->
St2
end,
- {gb_sets:add_element(TA, E), dict:store(TA, Line, U), St}
+ {gb_sets:add_element(TA, E), maps:put(TA, Line, U), St}
end,
{ETs0,UTs0,St0}, ETs) of
{ETs1,UTs1,St1} ->
@@ -1430,7 +1542,7 @@ call_function(Line, F, A, #lint{usage=Usage0,called=Cd,func=Func,file=File}=St)
NA = {F,A},
Usage = case Cs of
undefined -> Usage0;
- _ -> Usage0#usage{calls=dict:append(Func, NA, Cs)}
+ _ -> Usage0#usage{calls=maps_prepend(Func, NA, Cs)}
end,
Anno = erl_anno:set_file(File, Line),
St#lint{called=[{NA,Anno}|Cd], usage=Usage}.
@@ -1531,8 +1643,6 @@ pattern({tuple,_Line,Ps}, Vt, Old, Bvt, St) ->
pattern_list(Ps, Vt, Old, Bvt, St);
pattern({map,_Line,Ps}, Vt, Old, Bvt, St) ->
pattern_map(Ps, Vt, Old, Bvt, St);
-%%pattern({struct,_Line,_Tag,Ps}, Vt, Old, Bvt, St) ->
-%% pattern_list(Ps, Vt, Old, Bvt, St);
pattern({record_index,Line,Name,Field}, _Vt, _Old, _Bvt, St) ->
{Vt1,St1} =
check_record(Line, Name, St,
@@ -1541,10 +1651,11 @@ pattern({record_index,Line,Name,Field}, _Vt, _Old, _Bvt, St) ->
end),
{Vt1,[],St1};
pattern({record,Line,Name,Pfs}, Vt, Old, Bvt, St) ->
- case dict:find(Name, St#lint.records) of
+ case maps:find(Name, St#lint.records) of
{ok,{_Line,Fields}} ->
St1 = used_record(Name, St),
- pattern_fields(Pfs, Name, Fields, Vt, Old, Bvt, St1);
+ St2 = check_multi_field_init(Pfs, Line, Fields, St1),
+ pattern_fields(Pfs, Name, Fields, Vt, Old, Bvt, St2);
error -> {[],[],add_error(Line, {undefined_record,Name}, St)}
end;
pattern({bin,_,Fs}, Vt, Old, Bvt, St) ->
@@ -1575,7 +1686,14 @@ pattern_list(Ps, Vt, Old, Bvt0, St) ->
{vtmerge_pat(Pvt, Psvt),vtmerge_pat(Bvt,Bvt1),St1}
end, {[],[],St}, Ps).
-
+%% Check for '_' initializing no fields.
+check_multi_field_init(Fs, Line, Fields, St) ->
+ case
+ has_wildcard_field(Fs) andalso init_fields(Fs, Line, Fields) =:= []
+ of
+ true -> add_error(Line, bad_multi_field_init, St);
+ false -> St
+ end.
%% reject_invalid_alias(Pat, Expr, Vt, St) -> St'
%% Reject aliases for binary patterns at the top level.
@@ -1625,10 +1743,10 @@ reject_invalid_alias({tuple,_,Es1}, {tuple,_,Es2}, Vt, St) ->
reject_invalid_alias_list(Es1, Es2, Vt, St);
reject_invalid_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2}, Vt,
#lint{records=Recs}=St) ->
- case {dict:find(Name1, Recs),dict:find(Name2, Recs)} of
- {{ok,{_Line1,Fields1}},{ok,{_Line2,Fields2}}} ->
+ case Recs of
+ #{Name1 := {_Line1,Fields1}, Name2 := {_Line2,Fields2}} ->
reject_invalid_alias_rec(Pfs1, Pfs2, Fields1, Fields2, Vt, St);
- {_,_} ->
+ #{} ->
%% One or more non-existing records. (An error messages has
%% already been generated, so we are done here.)
St
@@ -1706,19 +1824,16 @@ is_pattern_expr_1({op,_Line,Op,A1,A2}) ->
is_pattern_expr_1(_Other) -> false.
pattern_map(Ps, Vt, Old, Bvt, St) ->
- foldl(fun
- ({map_field_assoc,L,_,_}, {Psvt,Bvt0,St0}) ->
- {Psvt,Bvt0,add_error(L, illegal_pattern, St0)};
- ({map_field_exact,L,K,V}, {Psvt,Bvt0,St0}) ->
- case is_valid_map_key(K) of
- true ->
- {Kvt,St1} = expr(K, Vt, St0),
- {Vvt,Bvt2,St2} = pattern(V, Vt, Old, Bvt, St1),
- {vtmerge_pat(vtmerge_pat(Kvt, Vvt), Psvt), vtmerge_pat(Bvt0, Bvt2), St2};
- false ->
- {Psvt,Bvt0,add_error(L, illegal_map_key, St0)}
- end
- end, {[],[],St}, Ps).
+ foldl(fun({map_field_assoc,L,_,_}, {Psvt,Bvt0,St0}) ->
+ {Psvt,Bvt0,add_error(L, illegal_pattern, St0)};
+ ({map_field_exact,_L,K,V}, {Psvt,Bvt0,St0}) ->
+ St1 = St0#lint{gexpr_context=map_key},
+ {Kvt,St2} = gexpr(K, Vt, St1),
+ {Vvt,Bvt2,St3} = pattern(V, Vt, Old, Bvt, St2),
+ {vtmerge_pat(vtmerge_pat(Kvt, Vvt), Psvt),
+ vtmerge_pat(Bvt0, Bvt2),
+ St3}
+ end, {[],[],St}, Ps).
%% pattern_bin([Element], VarTable, Old, BinVarTable, State) ->
%% {UpdVarTable,UpdBinVarTable,State}.
@@ -1787,21 +1902,41 @@ pat_bit_expr(P, _Old, _Bvt, St) ->
%% Check pattern size expression, only allow really valid sizes!
pat_bit_size(default, _Vt, _Bvt, St) -> {default,[],[],St};
-pat_bit_size({atom,_Line,all}, _Vt, _Bvt, St) -> {all,[],[],St};
pat_bit_size({var,Lv,V}, Vt0, Bvt0, St0) ->
{Vt,Bvt,St1} = pat_binsize_var(V, Lv, Vt0, Bvt0, St0),
{unknown,Vt,Bvt,St1};
-pat_bit_size(Size, _Vt, _Bvt, St) ->
+pat_bit_size(Size, Vt0, Bvt0, St0) ->
Line = element(2, Size),
- case is_pattern_expr(Size) of
- true ->
- case erl_eval:partial_eval(Size) of
- {integer,Line,I} -> {I,[],[],St};
- _Other -> {unknown,[],[],add_error(Line, illegal_bitsize, St)}
- end;
- false -> {unknown,[],[],add_error(Line, illegal_bitsize, St)}
+ case erl_eval:partial_eval(Size) of
+ {integer,Line,I} -> {I,[],[],St0};
+ Expr ->
+ %% The size is an expression using operators
+ %% and/or guard BIFs calls. If the expression
+ %% happens to evaluate to a non-integer value, the
+ %% pattern will fail to match.
+ St1 = St0#lint{bvt=Bvt0,gexpr_context=bin_seg_size},
+ {Vt,#lint{bvt=Bvt}=St2} = gexpr(Size, Vt0, St1),
+ St3 = St2#lint{bvt=none,gexpr_context=St0#lint.gexpr_context},
+ St = case is_bit_size_illegal(Expr) of
+ true ->
+ %% The size is a non-integer literal or a simple
+ %% expression that does not evaluate to an
+ %% integer value. Issue a warning.
+ add_warning(Line, non_integer_bitsize, St3);
+ false -> St3
+ end,
+ {unknown,Vt,Bvt,St}
end.
+is_bit_size_illegal({atom,_,_}) -> true;
+is_bit_size_illegal({bin,_,_}) -> true;
+is_bit_size_illegal({cons,_,_,_}) -> true;
+is_bit_size_illegal({float,_,_}) -> true;
+is_bit_size_illegal({map,_,_}) -> true;
+is_bit_size_illegal({nil,_}) -> true;
+is_bit_size_illegal({tuple,_,_}) -> true;
+is_bit_size_illegal(_) -> false.
+
%% expr_bin(Line, [Element], VarTable, State, CheckFun) -> {UpdVarTable,State}.
%% Check an expression group.
@@ -2067,7 +2202,7 @@ gexpr_list(Es, Vt, St) ->
Expr :: erl_parse:abstract_expr().
is_guard_test(E) ->
- is_guard_test2(E, {dict:new(),fun(_) -> false end}).
+ is_guard_test2(E, {maps:new(),fun(_) -> false end}).
%% is_guard_test(Expression, Forms) -> boolean().
is_guard_test(Expression, Forms) ->
@@ -2115,7 +2250,7 @@ is_guard_test2(G, Info) ->
%% is_guard_expr(Expression) -> boolean().
%% Test if an expression is a guard expression.
-is_guard_expr(E) -> is_gexpr(E, []).
+is_guard_expr(E) -> is_gexpr(E, {[],fun({_,_}) -> false end}).
is_gexpr({var,_L,_V}, _Info) -> true;
is_gexpr({char,_L,_C}, _Info) -> true;
@@ -2126,8 +2261,6 @@ is_gexpr({string,_L,_S}, _Info) -> true;
is_gexpr({nil,_L}, _Info) -> true;
is_gexpr({cons,_L,H,T}, Info) -> is_gexpr_list([H,T], Info);
is_gexpr({tuple,_L,Es}, Info) -> is_gexpr_list(Es, Info);
-%%is_gexpr({struct,_L,_Tag,Es}, Info) ->
-%% is_gexpr_list(Es, Info);
is_gexpr({map,_L,Es}, Info) ->
is_map_fields(Es, Info);
is_gexpr({map,_L,Src,Es}, Info) ->
@@ -2183,7 +2316,7 @@ is_map_fields([], _Info) -> true;
is_map_fields(_T, _Info) -> false.
is_gexpr_fields(Fs, L, Name, {RDs,_}=Info) ->
- IFs = case dict:find(Name, RDs) of
+ IFs = case maps:find(Name, RDs) of
{ok,{_Line,Fields}} -> Fs ++ init_fields(Fs, L, Fields);
error -> Fs
end,
@@ -2510,73 +2643,16 @@ is_valid_call(Call) ->
_ -> true
end.
-%% is_valid_map_key(K) -> true | false
-%% variables are allowed for patterns only at the top of the tree
-
-is_valid_map_key({var,_,_}) -> true;
-is_valid_map_key(K) -> is_valid_map_key_value(K).
-is_valid_map_key_value(K) ->
- case K of
- {var,_,_} -> false;
- {char,_,_} -> true;
- {integer,_,_} -> true;
- {float,_,_} -> true;
- {string,_,_} -> true;
- {nil,_} -> true;
- {atom,_,_} -> true;
- {cons,_,H,T} ->
- is_valid_map_key_value(H) andalso
- is_valid_map_key_value(T);
- {tuple,_,Es} ->
- foldl(fun(E,B) ->
- B andalso is_valid_map_key_value(E)
- end,true,Es);
- {map,_,Arg,Ps} ->
- % only check for value expressions to be valid
- % invalid map expressions are later checked in
- % core and kernel
- is_valid_map_key_value(Arg) andalso foldl(fun
- ({Tag,_,Ke,Ve},B) when Tag =:= map_field_assoc;
- Tag =:= map_field_exact ->
- B andalso is_valid_map_key_value(Ke)
- andalso is_valid_map_key_value(Ve);
- (_,_) -> false
- end,true,Ps);
- {map,_,Ps} ->
- foldl(fun
- ({Tag,_,Ke,Ve},B) when Tag =:= map_field_assoc;
- Tag =:= map_field_exact ->
- B andalso is_valid_map_key_value(Ke)
- andalso is_valid_map_key_value(Ve);
- (_,_) -> false
- end, true, Ps);
- {record,_,_,Fs} ->
- foldl(fun
- ({record_field,_,Ke,Ve},B) ->
- B andalso is_valid_map_key_value(Ke)
- andalso is_valid_map_key_value(Ve)
- end,true,Fs);
- {bin,_,Es} ->
- % only check for value expressions to be valid
- % invalid binary expressions are later checked in
- % core and kernel
- foldl(fun
- ({bin_element,_,E,_,_},B) ->
- B andalso is_valid_map_key_value(E)
- end,true,Es);
- Val -> is_pattern_expr(Val)
- end.
-
%% record_def(Line, RecordName, [RecField], State) -> State.
%% Add a record definition if it does not already exist. Normalise
%% so that all fields have explicit initial value.
record_def(Line, Name, Fs0, St0) ->
- case dict:is_key(Name, St0#lint.records) of
+ case is_map_key(Name, St0#lint.records) of
true -> add_error(Line, {redefine_record,Name}, St0);
false ->
{Fs1,St1} = def_fields(normalise_fields(Fs0), Name, St0),
- St2 = St1#lint{records=dict:store(Name, {Line,Fs1},
+ St2 = St1#lint{records=maps:put(Name, {Line,Fs1},
St1#lint.records)},
Types = [T || {typed_record_field, _, T} <- Fs0],
check_type({type, nowarn(), product, Types}, St2)
@@ -2627,7 +2703,7 @@ normalise_fields(Fs) ->
%% Check if a record exists. Set State.
exist_record(Line, Name, St) ->
- case dict:is_key(Name, St#lint.records) of
+ case is_map_key(Name, St#lint.records) of
true -> used_record(Name, St);
false -> add_error(Line, {undefined_record,Name}, St)
end.
@@ -2644,13 +2720,13 @@ exist_record(Line, Name, St) ->
%% {UpdatedVarTable,State}
check_record(Line, Name, St, CheckFun) ->
- case dict:find(Name, St#lint.records) of
+ case maps:find(Name, St#lint.records) of
{ok,{_Line,Fields}} -> CheckFun(Fields, used_record(Name, St));
error -> {[],add_error(Line, {undefined_record,Name}, St)}
end.
used_record(Name, #lint{usage=Usage}=St) ->
- UsedRecs = sets:add_element(Name, Usage#usage.used_records),
+ UsedRecs = gb_sets:add_element(Name, Usage#usage.used_records),
St#lint{usage = Usage#usage{used_records=UsedRecs}}.
%%% Record check functions.
@@ -2678,9 +2754,12 @@ check_field({record_field,Lf,{atom,La,F},Val}, Name, Fields,
error -> {[],add_error(La, {undefined_field,Name,F}, St)}
end}
end;
-check_field({record_field,_Lf,{var,_La,'_'},Val}, _Name, _Fields,
+check_field({record_field,_Lf,{var,La,'_'=F},Val}, _Name, _Fields,
Vt, St, Sfs, CheckFun) ->
- {Sfs,CheckFun(Val, Vt, St)};
+ case member(F, Sfs) of
+ true -> {Sfs,{[],add_error(La, bad_multi_field_init, St)}};
+ false -> {[F|Sfs],CheckFun(Val, Vt, St)}
+ end;
check_field({record_field,_Lf,{var,La,V},_Val}, Name, _Fields,
Vt, St, Sfs, _CheckFun) ->
{Sfs,{Vt,add_error(La, {field_name_is_variable,Name,V}, St)}}.
@@ -2791,7 +2870,7 @@ type_def(Attr, Line, TypeName, ProtoType, Args, St0) ->
Info = #typeinfo{attr = Attr, line = Line},
StoreType =
fun(St) ->
- NewDefs = dict:store(TypePair, Info, TypeDefs),
+ NewDefs = maps:put(TypePair, Info, TypeDefs),
CheckType = {type, nowarn(), product, [ProtoType|Args]},
check_type(CheckType, St#lint{types=NewDefs})
end,
@@ -2811,7 +2890,7 @@ type_def(Attr, Line, TypeName, ProtoType, Args, St0) ->
end
end;
false ->
- case dict:is_key(TypePair, TypeDefs) of
+ case is_map_key(TypePair, TypeDefs) of
true ->
add_error(Line, {redefine_type, TypePair}, St0);
false ->
@@ -2833,8 +2912,8 @@ is_underspecified({type,_,any,[]}, 0) -> true;
is_underspecified(_ProtType, _Arity) -> false.
check_type(Types, St) ->
- {SeenVars, St1} = check_type(Types, dict:new(), St),
- dict:fold(fun(Var, {seen_once, Line}, AccSt) ->
+ {SeenVars, St1} = check_type(Types, maps:new(), St),
+ maps:fold(fun(Var, {seen_once, Line}, AccSt) ->
case atom_to_list(Var) of
"_"++_ -> AccSt;
_ -> add_error(Line, {singleton_typevar, Var}, AccSt)
@@ -2862,10 +2941,10 @@ check_type({atom, _L, _}, SeenVars, St) -> {SeenVars, St};
check_type({var, _L, '_'}, SeenVars, St) -> {SeenVars, St};
check_type({var, L, Name}, SeenVars, St) ->
NewSeenVars =
- case dict:find(Name, SeenVars) of
- {ok, {seen_once, _}} -> dict:store(Name, seen_multiple, SeenVars);
+ case maps:find(Name, SeenVars) of
+ {ok, {seen_once, _}} -> maps:put(Name, seen_multiple, SeenVars);
{ok, seen_multiple} -> SeenVars;
- error -> dict:store(Name, {seen_once, L}, SeenVars)
+ error -> maps:put(Name, {seen_once, L}, SeenVars)
end,
{NewSeenVars, St};
check_type({type, L, bool, []}, SeenVars, St) ->
@@ -2924,7 +3003,7 @@ check_type({type, La, TypeName, Args}, SeenVars, St) ->
andalso obsolete_builtin_type(TypePair)),
St1 = case Obsolete of
{deprecated, Repl, _} when element(1, Repl) =/= Module ->
- case dict:find(TypePair, Types) of
+ case maps:find(TypePair, Types) of
{ok, _} ->
used_type(TypePair, La, St);
error ->
@@ -2953,7 +3032,7 @@ check_type(I, SeenVars, St) ->
end.
check_record_types(Line, Name, Fields, SeenVars, St) ->
- case dict:find(Name, St#lint.records) of
+ case maps:find(Name, St#lint.records) of
{ok,{_L,DefFields}} ->
case lists:all(fun({type, _, field_type, _}) -> true;
(_) -> false
@@ -2988,7 +3067,7 @@ check_record_types([], _Name, _DefFields, SeenVars, St, _SeenFields) ->
used_type(TypePair, L, #lint{usage = Usage, file = File} = St) ->
OldUsed = Usage#usage.used_types,
- UsedTypes = dict:store(TypePair, erl_anno:set_file(File, L), OldUsed),
+ UsedTypes = maps:put(TypePair, erl_anno:set_file(File, L), OldUsed),
St#lint{usage=Usage#usage{used_types=UsedTypes}}.
is_default_type({Name, NumberOfTypeVariables}) ->
@@ -3012,8 +3091,8 @@ spec_decl(Line, MFA0, TypeSpecs, St00 = #lint{specs = Specs, module = Mod}) ->
{_M, _F, Arity} -> MFA0
end,
St0 = check_module_name(element(1, MFA), Line, St00),
- St1 = St0#lint{specs = dict:store(MFA, Line, Specs)},
- case dict:is_key(MFA, Specs) of
+ St1 = St0#lint{specs = maps:put(MFA, Line, Specs)},
+ case is_map_key(MFA, Specs) of
true -> add_error(Line, {redefine_spec, MFA0}, St1);
false ->
case MFA of
@@ -3034,8 +3113,8 @@ callback_decl(Line, MFA0, TypeSpecs,
add_error(Line, {bad_callback, MFA0}, St1);
{F, Arity} ->
MFA = {Mod, F, Arity},
- St1 = St0#lint{callbacks = dict:store(MFA, Line, Callbacks)},
- case dict:is_key(MFA, Callbacks) of
+ St1 = St0#lint{callbacks = maps:put(MFA, Line, Callbacks)},
+ case is_map_key(MFA, Callbacks) of
true -> add_error(Line, {redefine_callback, MFA0}, St1);
false -> check_specs(TypeSpecs, callback_wrong_arity,
Arity, St1)
@@ -3058,8 +3137,8 @@ optional_cbs(_Line, [], St) ->
optional_cbs(Line, [{F,A}|FAs], St0) ->
#lint{optional_callbacks = OptionalCbs, module = Mod} = St0,
MFA = {Mod, F, A},
- St1 = St0#lint{optional_callbacks = dict:store(MFA, Line, OptionalCbs)},
- St2 = case dict:is_key(MFA, OptionalCbs) of
+ St1 = St0#lint{optional_callbacks = maps:put(MFA, Line, OptionalCbs)},
+ St2 = case is_map_key(MFA, OptionalCbs) of
true ->
add_error(Line, {redefine_optional_callback, {F,A}}, St1);
false ->
@@ -3119,7 +3198,7 @@ check_specs_without_function(#lint{module=Mod,defined=Funcs,specs=Specs}=St) ->
end;
({_M, _F, _A}, _Line, AccSt) -> AccSt
end,
- dict:fold(Fun, St, Specs).
+ maps:fold(Fun, St, Specs).
%% This generates warnings for functions without specs; if the user has
%% specified both options, we do not generate the same warnings twice.
@@ -3137,7 +3216,7 @@ check_functions_without_spec(Forms, St0) ->
end.
add_missing_spec_warnings(Forms, St0, Type) ->
- Specs = [{F,A} || {_M,F,A} <- dict:fetch_keys(St0#lint.specs)],
+ Specs = [{F,A} || {_M,F,A} <- maps:keys(St0#lint.specs)],
Warns = %% functions + line numbers for which we should warn
case Type of
all ->
@@ -3163,7 +3242,7 @@ check_unused_types_1(Forms, #lint{usage=Usage, types=Ts, exp_types=ExpTs}=St) ->
case [File || {attribute,_L,file,{File,_Line}} <- Forms] of
[FirstFile|_] ->
D = Usage#usage.used_types,
- L = gb_sets:to_list(ExpTs) ++ dict:fetch_keys(D),
+ L = gb_sets:to_list(ExpTs) ++ maps:keys(D),
UsedTypes = gb_sets:from_list(L),
FoldFun =
fun({{record, _}=_Type, 0}, _, AccSt) ->
@@ -3182,7 +3261,7 @@ check_unused_types_1(Forms, #lint{usage=Usage, types=Ts, exp_types=ExpTs}=St) ->
AccSt
end
end,
- dict:fold(FoldFun, St, Ts);
+ maps:fold(FoldFun, St, Ts);
[] ->
St
end.
@@ -3200,7 +3279,7 @@ check_local_opaque_types(St) ->
add_warning(FileLine, Warn, AccSt)
end
end,
- dict:fold(FoldFun, St, Ts).
+ maps:fold(FoldFun, St, Ts).
check_dialyzer_attribute(Forms, St0) ->
Vals = [{L,V} ||
@@ -3444,7 +3523,7 @@ handle_generator(P,E,Vt,Uvt,St0) ->
handle_bitstring_gen_pat({bin,_,Segments=[_|_]},St) ->
case lists:last(Segments) of
- {bin_element,Line,{var,_,_},default,Flags} when is_list(Flags) ->
+ {bin_element,Line,_,default,Flags} when is_list(Flags) ->
case member(binary, Flags) orelse member(bytes, Flags)
orelse member(bits, Flags) orelse member(bitstring, Flags) of
true ->
@@ -3590,7 +3669,13 @@ pat_binsize_var(V, Line, Vt, Bvt, St) ->
%% exported vars are probably safe, warn only if warn_export_vars is
%% set.
-expr_var(V, Line, Vt, St) ->
+expr_var(V, Line, Vt, #lint{bvt=none}=St) ->
+ do_expr_var(V, Line, Vt, St);
+expr_var(V, Line, Vt0, #lint{bvt=Bvt0}=St0) when is_list(Bvt0) ->
+ {Vt,Bvt,St} = pat_binsize_var(V, Line, Vt0, Bvt0, St0),
+ {Vt,St#lint{bvt=vtmerge(Bvt0, Bvt)}}.
+
+do_expr_var(V, Line, Vt, St) ->
case orddict:find(V, Vt) of
{ok,{bound,_Usage,Ls}} ->
{[{V,{bound,used,Ls}}],St};
@@ -3775,7 +3860,29 @@ has_wildcard_field([]) -> false.
check_remote_function(Line, M, F, As, St0) ->
St1 = deprecated_function(Line, M, F, As, St0),
St2 = check_qlc_hrl(Line, M, F, As, St1),
- format_function(Line, M, F, As, St2).
+ St3 = check_load_nif(Line, M, F, As, St2),
+ format_function(Line, M, F, As, St3).
+
+%% check_load_nif(Line, ModName, FuncName, [Arg], State) -> State
+%% Add warning if erlang:load_nif/2 is called when any kind of inlining has
+%% been enabled.
+check_load_nif(Line, erlang, load_nif, [_, _], St) ->
+ case is_warn_enabled(nif_inline, St) of
+ true -> check_nif_inline(Line, St);
+ false -> St
+ end;
+check_load_nif(_Line, _ModName, _FuncName, _Args, St) ->
+ St.
+
+check_nif_inline(Line, St) ->
+ case any(fun is_inline_opt/1, St#lint.compile) of
+ true -> add_warning(Line, nif_inline, St);
+ false -> St
+ end.
+
+is_inline_opt({inline, [_|_]=_FAs}) -> true;
+is_inline_opt(inline) -> true;
+is_inline_opt(_) -> false.
%% check_qlc_hrl(Line, ModName, FuncName, [Arg], State) -> State
%% Add warning if qlc:q/1,2 has been called but qlc.hrl has not
@@ -3846,8 +3953,8 @@ deprecated_type(L, M, N, As, St) ->
false ->
St
end;
- {removed, Replacement, Rel} ->
- add_warning(L, {removed_type, {M,N,NAs}, Replacement, Rel}, St);
+ {removed, String} ->
+ add_warning(L, {removed_type, {M,N,NAs}, String}, St);
no ->
St
end.
@@ -4122,3 +4229,13 @@ no_guard_bif_clash(St,{F,A}) ->
is_imported_from_erlang(St#lint.imports,{F,A})
)
).
+
+%% maps_prepend(Key, Value, Map) -> Map.
+
+maps_prepend(Key, Value, Map) ->
+ case maps:find(Key, Map) of
+ {ok, Values} ->
+ maps:put(Key, [Value|Values], Map);
+ error ->
+ maps:put(Key, [Value], Map)
+ end.
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index 739f786321..dd7a2c2cc1 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -608,6 +608,15 @@ Erlang code.
-export_type([abstract_clause/0, abstract_expr/0, abstract_form/0,
abstract_type/0, form_info/0, error_info/0]).
+%% The following types are exported because they are used by syntax_tools
+-export_type([af_binelement/1, af_generator/0, af_remote_function/0]).
+%% The following type is used by PropEr
+-export_type([af_field_decl/0]).
+
+%% Removed functions
+-removed([{set_line,2,"use erl_anno:set_line/2"},
+ {get_attributes,1,"erl_anno:{column,line,location,text}/1 instead"},
+ {get_attribute,2,"erl_anno:{column,line,location,text}/1 instead"}]).
%% Start of Abstract Format
@@ -637,7 +646,7 @@ Erlang code.
-type af_export() :: {'attribute', anno(), 'export', af_fa_list()}.
--type af_import() :: {'attribute', anno(), 'import', af_fa_list()}.
+-type af_import() :: {'attribute', anno(), 'import', {module(), af_fa_list()}}.
-type af_fa_list() :: [{function_name(), arity()}].
@@ -1455,7 +1464,19 @@ abstract(List, A, E) when is_list(List) ->
abstract(Tuple, A, E) when is_tuple(Tuple) ->
{tuple,A,abstract_tuple_list(tuple_to_list(Tuple), A, E)};
abstract(Map, A, E) when is_map(Map) ->
- {map,A,abstract_map_fields(maps:to_list(Map),A,E)}.
+ {map,A,abstract_map_fields(maps:to_list(Map),A,E)};
+abstract(Fun, A, E) when is_function(Fun) ->
+ case erlang:fun_info(Fun, type) of
+ {type, external} ->
+ Info = erlang:fun_info(Fun),
+ {module, M} = lists:keyfind(module, 1, Info),
+ {name, F} = lists:keyfind(name, 1, Info),
+ {arity, Arity} = lists:keyfind(arity, 1, Info),
+ {'fun', A, {function,
+ abstract(M, A, E),
+ abstract(F, A, E),
+ abstract(Arity, A, E)}}
+ end.
abstract_list([H|T], String, A, E) ->
case is_integer(H) andalso H >= 0 andalso E(H) of
@@ -1514,7 +1535,13 @@ tokens({cons,A,Head,Tail}, More) ->
tokens({tuple,A,[]}, More) ->
[{'{',A},{'}',A}|More];
tokens({tuple,A,[E|Es]}, More) ->
- [{'{',A}|tokens(E, tokens_tuple(Es, ?anno(E), More))].
+ [{'{',A}|tokens(E, tokens_tuple(Es, ?anno(E), More))];
+tokens({map,A,[]}, More) ->
+ [{'#',A},{'{',A},{'}',A}|More];
+tokens({map,A,[P|Ps]}, More) ->
+ [{'#',A},{'{',A}|tokens(P, tokens_tuple(Ps, ?anno(P), More))];
+tokens({map_field_assoc,A,K,V}, More) ->
+ tokens(K, [{'=>',A}|tokens(V, More)]).
tokens_tail({cons,A,Head,Tail}, More) ->
[{',',A}|tokens(Head, tokens_tail(Tail, More))];
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index daa172af50..651c601bb0 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -31,7 +31,8 @@
-import(erl_parse, [inop_prec/1,preop_prec/1,func_prec/0,max_prec/0,
type_inop_prec/1, type_preop_prec/1]).
--define(MAXLINE, 72).
+-define(DEFAULT_LINEWIDTH, 72).
+-define(DEFAULT_INDENT, 4).
-type(hook_function() :: none
| fun((Expr :: erl_parse:abstract_expr(),
@@ -42,10 +43,13 @@
-type(option() :: {hook, hook_function()}
| {encoding, latin1 | unicode | utf8}
- | {quote_singleton_atom_types, boolean()}).
+ | {quote_singleton_atom_types, boolean()}
+ | {linewidth, pos_integer()}
+ | {indent, pos_integer()}).
-type(options() :: hook_function() | [option()]).
--record(pp, {value_fun, singleton_atom_type_fun, string_fun, char_fun}).
+-record(pp, {value_fun, singleton_atom_type_fun, string_fun, char_fun,
+ linewidth=?DEFAULT_LINEWIDTH, indent=?DEFAULT_INDENT}).
-record(options, {hook, encoding, opts}).
@@ -208,10 +212,14 @@ options(Hook) ->
state(Options) when is_list(Options) ->
Quote = proplists:get_bool(quote_singleton_atom_types, Options),
- case encoding(Options) of
- latin1 -> latin1_state(Quote);
- unicode -> unicode_state(Quote)
- end;
+ State =
+ case encoding(Options) of
+ latin1 -> latin1_state(Quote);
+ unicode -> unicode_state(Quote)
+ end,
+ Indent = proplists:get_value(indent, Options, ?DEFAULT_INDENT),
+ LineWidth = proplists:get_value(linewidth, Options, ?DEFAULT_LINEWIDTH),
+ State#pp{indent=Indent, linewidth=LineWidth};
state(_Hook) ->
latin1_state(false).
@@ -552,8 +560,6 @@ lexpr({bc,_,E,Qs}, _Prec, Opts) ->
%% {list,[{step,'<<',Lcl},'>>']};
lexpr({tuple,_,Elts}, _, Opts) ->
tuple(Elts, Opts);
-%%lexpr({struct,_,Tag,Elts}, _, Opts) ->
-%% {first,format("~w", [Tag]),tuple(Elts, Opts)};
lexpr({record_index, _, Name, F}, Prec, Opts) ->
{P,R} = preop_prec('#'),
Nl = record_name(Name),
@@ -644,8 +650,10 @@ lexpr({named_fun,_,Name,Cs,Extra}, _Prec, Opts) ->
{reserved,'end'}]}};
lexpr({call,_,{remote,_,{atom,_,M},{atom,_,F}=N}=Name,Args}, Prec, Opts) ->
case erl_internal:bif(M, F, length(Args)) of
- true ->
+ true when F =/= float ->
call(N, Args, Prec, Opts);
+ true ->
+ call(Name, Args, Prec, Opts);
false ->
call(Name, Args, Prec, Opts)
end;
@@ -1020,7 +1028,7 @@ f({seq,Before,After,Sep,LItems}, I0, ST, WT, PP) ->
true ->
0
end,
- case same_line(I0, Sizes, NSepChars) of
+ case same_line(I0, Sizes, NSepChars, PP) of
{yes,Size} ->
Chars = if
NSepChars > 0 -> insert_sep(CharsL, $\s);
@@ -1028,9 +1036,9 @@ f({seq,Before,After,Sep,LItems}, I0, ST, WT, PP) ->
end,
{BCharsL++Chars,Size};
no ->
- CharsList = handle_step(CharsSizeL, I, ST),
+ CharsList = handle_step(CharsSizeL, I, ST, PP),
{LChars, LSize} =
- maybe_newlines(CharsList, LItems, I, NSepChars, ST),
+ maybe_newlines(CharsList, LItems, I, NSepChars, ST, PP),
{[BCharsL,LChars],nsz(LSize, I0)}
end;
f({force_nl,_ExtraInfoItem,Item}, I, ST, WT, PP) when I < 0 ->
@@ -1047,7 +1055,7 @@ f({prefer_nl,Sep,LItems}, I0, ST, WT, PP) ->
Sizes =:= [] ->
{[], 0};
true ->
- {insert_newlines(CharsSize2L, I0, ST),
+ {insert_newlines(CharsSize2L, I0, ST, PP),
nsz(lists:last(Sizes), I0)}
end;
f({value,V}, I, ST, WT, PP) ->
@@ -1071,8 +1079,6 @@ f({ehook,HookExpr,Precedence,{Mod,Func,Eas}=ModFuncEas}, I, _ST, _WT, _PP) ->
f(WordName, _I, _ST, WT, _PP) when is_atom(WordName) ->
word(WordName, WT).
--define(IND, 4).
-
%% fl(ListItems, I0, ST, WT) -> [[CharsSize1,CharsSize2]]
%% ListItems = [{Item,Items}|Item]
fl([], _Sep, I0, After, ST, WT, PP) ->
@@ -1080,15 +1086,15 @@ fl([], _Sep, I0, After, ST, WT, PP) ->
fl(CItems, Sep0, I0, After, ST, WT, PP) ->
F = fun({step,Item1,Item2}, S) ->
[f(Item1, I0, ST, WT, PP),
- f([Item2,S], incr(I0, ?IND), ST, WT, PP)];
+ f([Item2,S], incr(I0, PP#pp.indent), ST, WT, PP)];
({cstep,Item1,Item2}, S) ->
{_,Sz1} = CharSize1 = f(Item1, I0, ST, WT, PP),
if
- is_integer(Sz1), Sz1 < ?IND ->
+ is_integer(Sz1), Sz1 < PP#pp.indent ->
Item2p = [leaf("\s"),Item2,S],
[consecutive(Item2p, CharSize1, I0, ST, WT, PP),{[],0}];
true ->
- [CharSize1,f([Item2,S], incr(I0, ?IND), ST, WT, PP)]
+ [CharSize1,f([Item2,S], incr(I0, PP#pp.indent), ST, WT, PP)]
end;
({reserved,Word}, S) ->
[f([Word,S], I0, ST, WT, PP),{[],0}];
@@ -1127,58 +1133,58 @@ unz1(CharSizes) ->
nonzero(CharSizes) ->
lists:filter(fun({_,Sz}) -> Sz =/= 0 end, CharSizes).
-maybe_newlines([{Chars,Size}], [], _I, _NSepChars, _ST) ->
+maybe_newlines([{Chars,Size}], [], _I, _NSepChars, _ST, _PP) ->
{Chars,Size};
-maybe_newlines(CharsSizeList, Items, I, NSepChars, ST) when I >= 0 ->
- maybe_sep(CharsSizeList, Items, I, NSepChars, nl_indent(I, ST)).
+maybe_newlines(CharsSizeList, Items, I, NSepChars, ST, PP) when I >= 0 ->
+ maybe_sep(CharsSizeList, Items, I, NSepChars, nl_indent(I, ST), PP).
-maybe_sep([{Chars1,Size1}|CharsSizeL], [Item|Items], I0, NSepChars, Sep) ->
+maybe_sep([{Chars1,Size1}|CharsSizeL], [Item|Items], I0, NSepChars, Sep, PP) ->
I1 = case classify_item(Item) of
atomic ->
I0 + Size1;
_ ->
- ?MAXLINE+1
+ PP#pp.linewidth+1
end,
- maybe_sep1(CharsSizeL, Items, I0, I1, Sep, NSepChars, Size1, [Chars1]).
+ maybe_sep1(CharsSizeL, Items, I0, I1, Sep, NSepChars, Size1, [Chars1], PP).
maybe_sep1([{Chars,Size}|CharsSizeL], [Item|Items],
- I0, I, Sep, NSepChars, Sz0, A) ->
+ I0, I, Sep, NSepChars, Sz0, A, PP) ->
case classify_item(Item) of
atomic when is_integer(Size) ->
Size1 = Size + 1,
I1 = I + Size1,
if
- I1 =< ?MAXLINE ->
+ I1 =< PP#pp.linewidth ->
A1 = if
NSepChars > 0 -> [Chars,$\s|A];
true -> [Chars|A]
end,
maybe_sep1(CharsSizeL, Items, I0, I1, Sep, NSepChars,
- Sz0 + Size1, A1);
+ Sz0 + Size1, A1, PP);
true ->
A1 = [Chars,Sep|A],
maybe_sep1(CharsSizeL, Items, I0, I0 + Size, Sep,
- NSepChars, Size1, A1)
+ NSepChars, Size1, A1, PP)
end;
_ ->
A1 = [Chars,Sep|A],
- maybe_sep1(CharsSizeL, Items, I0, ?MAXLINE+1, Sep, NSepChars,
- 0, A1)
+ maybe_sep1(CharsSizeL, Items, I0, PP#pp.linewidth+1, Sep, NSepChars,
+ 0, A1, PP)
end;
-maybe_sep1(_CharsSizeL, _Items, _Io, _I, _Sep, _NSepChars, Sz, A) ->
+maybe_sep1(_CharsSizeL, _Items, _Io, _I, _Sep, _NSepChars, Sz, A, _PP) ->
{lists:reverse(A), Sz}.
-insert_newlines(CharsSizesL, I, ST) when I >= 0 ->
- {CharsL, _} = unz1(handle_step(CharsSizesL, I, ST)),
+insert_newlines(CharsSizesL, I, ST, PP) when I >= 0 ->
+ {CharsL, _} = unz1(handle_step(CharsSizesL, I, ST, PP)),
insert_nl(CharsL, I, ST).
-handle_step(CharsSizesL, I, ST) ->
+handle_step(CharsSizesL, I, ST, PP) ->
map(fun([{_C1,0},{_C2,0}]) ->
{[], 0};
([{C1,Sz1},{_C2,0}]) ->
{C1, Sz1};
([{C1,Sz1},{C2,Sz2}]) when Sz2 > 0 ->
- {insert_nl([C1,C2], I+?IND, ST),line_size([Sz1,Sz2])}
+ {insert_nl([C1,C2], I+PP#pp.indent, ST),line_size([Sz1,Sz2])}
end, CharsSizesL).
insert_nl(CharsL, I, ST) ->
@@ -1198,10 +1204,10 @@ classify_item(Atom) when is_atom(Atom) -> atomic;
classify_item({leaf, _, _}) -> atomic;
classify_item(_) -> complex.
-same_line(I0, SizeL, NSepChars) ->
+same_line(I0, SizeL, NSepChars, PP) ->
try
Size = lists:sum(SizeL) + NSepChars,
- true = incr(I0, Size) =< ?MAXLINE,
+ true = incr(I0, Size) =< PP#pp.linewidth,
{yes,Size}
catch _:_ ->
no
@@ -1269,7 +1275,7 @@ write_a_char(C, PP) ->
write_a_string(S, I, PP) when I < 0; S =:= [] ->
flat_leaf(write_string(S, PP));
write_a_string(S, I, PP) ->
- Len = erlang:max(?MAXLINE-I, ?MIN_SUBSTRING),
+ Len = erlang:max(PP#pp.linewidth-I, ?MIN_SUBSTRING),
{list,write_a_string(S, Len, Len, PP)}.
write_a_string([], _N, _Len, _PP) ->
diff --git a/lib/stdlib/src/erl_scan.erl b/lib/stdlib/src/erl_scan.erl
index 4774c4bf19..0854e15177 100644
--- a/lib/stdlib/src/erl_scan.erl
+++ b/lib/stdlib/src/erl_scan.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -66,6 +66,17 @@
token/0,
tokens_result/0]).
+%% Removed functions and types
+-removed([{set_attribute,3,"use erl_anno:set_line/2 instead"},
+ {attributes_info,'_',
+ "erl_anno:{column,line,location,text}/1 instead"},
+ {token_info,'_',
+ "erl_scan:{category,column,line,location,symbol,text}/1 instead"}]).
+
+-removed_type([{column,0,"use erl_anno:column() instead"},
+ {line,0,"use erl_anno:line() instead"},
+ {location,0,"use erl_anno:location() instead"}]).
+
%%%
%%% Defines and type definitions
%%%
@@ -249,7 +260,7 @@ string_thing(_) -> "string".
-define(WHITE_SPACE(C),
is_integer(C) andalso
(C >= $\000 andalso C =< $\s orelse C >= $\200 andalso C =< $\240)).
--define(DIGIT(C), C >= $0, C =< $9).
+-define(DIGIT(C), C >= $0 andalso C =< $9).
-define(CHAR(C), is_integer(C), C >= 0).
-define(UNICODE(C),
is_integer(C) andalso
@@ -379,7 +390,7 @@ scan1([$\%|Cs], St, Line, Col, Toks) when not St#erl_scan.comment ->
scan1([$\%=C|Cs], St, Line, Col, Toks) ->
scan_comment(Cs, St, Line, Col, Toks, [C]);
scan1([C|Cs], St, Line, Col, Toks) when ?DIGIT(C) ->
- scan_number(Cs, St, Line, Col, Toks, [C]);
+ scan_number(Cs, St, Line, Col, Toks, [C], no_underscore);
scan1("..."++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "...", '...', 3);
scan1(".."=Cs, _St, Line, Col, Toks) ->
@@ -938,27 +949,35 @@ escape_char($s) -> $\s; % \s = SPC
escape_char($d) -> $\d; % \d = DEL
escape_char(C) -> C.
-scan_number([C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
- scan_number(Cs, St, Line, Col, Toks, [C|Ncs]);
-scan_number([$.,C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
- scan_fraction(Cs, St, Line, Col, Toks, [C,$.|Ncs]);
-scan_number([$.]=Cs, _St, Line, Col, Toks, Ncs) ->
- {more,{Cs,Col,Toks,Line,Ncs,fun scan_number/6}};
-scan_number([$#|Cs]=Cs0, St, Line, Col, Toks, Ncs0) ->
+scan_number(Cs, St, Line, Col, Toks, {Ncs, Us}) ->
+ scan_number(Cs, St, Line, Col, Toks, Ncs, Us).
+
+scan_number([C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
+ scan_number(Cs, St, Line, Col, Toks, [C|Ncs], Us);
+scan_number([$_,Next|Cs], St, Line, Col, Toks, [Prev|_]=Ncs, _Us) when
+ ?DIGIT(Next) andalso ?DIGIT(Prev) ->
+ scan_number(Cs, St, Line, Col, Toks, [Next,$_|Ncs], with_underscore);
+scan_number([$_]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_number/6}};
+scan_number([$.,C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
+ scan_fraction(Cs, St, Line, Col, Toks, [C,$.|Ncs], Us);
+scan_number([$.]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_number/6}};
+scan_number([$#|Cs]=Cs0, St, Line, Col, Toks, Ncs0, Us) ->
Ncs = lists:reverse(Ncs0),
- case catch list_to_integer(Ncs) of
+ case catch list_to_integer(remove_digit_separators(Ncs, Us)) of
B when B >= 2, B =< 1+$Z-$A+10 ->
Bcs = Ncs++[$#],
- scan_based_int(Cs, St, Line, Col, Toks, {B,[],Bcs});
+ scan_based_int(Cs, St, Line, Col, Toks, B, [], Bcs, no_underscore);
B ->
Len = length(Ncs),
scan_error({base,B}, Line, Col, Line, incr_column(Col, Len), Cs0)
end;
-scan_number([]=Cs, _St, Line, Col, Toks, Ncs) ->
- {more,{Cs,Col,Toks,Line,Ncs,fun scan_number/6}};
-scan_number(Cs, St, Line, Col, Toks, Ncs0) ->
+scan_number([]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_number/6}};
+scan_number(Cs, St, Line, Col, Toks, Ncs0, Us) ->
Ncs = lists:reverse(Ncs0),
- case catch list_to_integer(Ncs) of
+ case catch list_to_integer(remove_digit_separators(Ncs, Us)) of
N when is_integer(N) ->
tok3(Cs, St, Line, Col, Toks, integer, Ncs, N);
_ ->
@@ -966,20 +985,33 @@ scan_number(Cs, St, Line, Col, Toks, Ncs0) ->
scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs)
end.
-scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
- when ?DIGIT(C), C < $0+B ->
- scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
-scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
- when C >= $A, B > 10, C < $A+B-10 ->
- scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
-scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
- when C >= $a, B > 10, C < $a+B-10 ->
- scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
-scan_based_int([]=Cs, _St, Line, Col, Toks, State) ->
- {more,{Cs,Col,Toks,Line,State,fun scan_based_int/6}};
-scan_based_int(Cs, St, Line, Col, Toks, {B,Ncs0,Bcs}) ->
+remove_digit_separators(Number, no_underscore) ->
+ Number;
+remove_digit_separators(Number, with_underscore) ->
+ [C || C <- Number, C =/= $_].
+
+-define(BASED_DIGIT(C, B),
+ ((?DIGIT(C) andalso C < $0 + B)
+ orelse (C >= $A andalso B > 10 andalso C < $A + B - 10)
+ orelse (C >= $a andalso B > 10 andalso C < $a + B - 10))).
+
+scan_based_int(Cs, St, Line, Col, Toks, {B,NCs,BCs,Us}) ->
+ scan_based_int(Cs, St, Line, Col, Toks, B, NCs, BCs, Us).
+
+scan_based_int([C|Cs], St, Line, Col, Toks, B, Ncs, Bcs, Us) when
+ ?BASED_DIGIT(C, B) ->
+ scan_based_int(Cs, St, Line, Col, Toks, B, [C|Ncs], Bcs, Us);
+scan_based_int([$_,Next|Cs], St, Line, Col, Toks, B, [Prev|_]=Ncs, Bcs, _Us)
+ when ?BASED_DIGIT(Next, B) andalso ?BASED_DIGIT(Prev, B) ->
+ scan_based_int(Cs, St, Line, Col, Toks, B, [Next,$_|Ncs], Bcs,
+ with_underscore);
+scan_based_int([$_]=Cs, _St, Line, Col, Toks, B, NCs, BCs, Us) ->
+ {more,{Cs,Col,Toks,Line,{B,NCs,BCs,Us},fun scan_based_int/6}};
+scan_based_int([]=Cs, _St, Line, Col, Toks, B, NCs, BCs, Us) ->
+ {more,{Cs,Col,Toks,Line,{B,NCs,BCs,Us},fun scan_based_int/6}};
+scan_based_int(Cs, St, Line, Col, Toks, B, Ncs0, Bcs, Us) ->
Ncs = lists:reverse(Ncs0),
- case catch erlang:list_to_integer(Ncs, B) of
+ case catch erlang:list_to_integer(remove_digit_separators(Ncs, Us), B) of
N when is_integer(N) ->
tok3(Cs, St, Line, Col, Toks, integer, Bcs++Ncs, N);
_ ->
@@ -988,32 +1020,52 @@ scan_based_int(Cs, St, Line, Col, Toks, {B,Ncs0,Bcs}) ->
scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs)
end.
-scan_fraction([C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
- scan_fraction(Cs, St, Line, Col, Toks, [C|Ncs]);
-scan_fraction([E|Cs], St, Line, Col, Toks, Ncs) when E =:= $e; E =:= $E ->
- scan_exponent_sign(Cs, St, Line, Col, Toks, [E|Ncs]);
-scan_fraction([]=Cs, _St, Line, Col, Toks, Ncs) ->
- {more,{Cs,Col,Toks,Line,Ncs,fun scan_fraction/6}};
-scan_fraction(Cs, St, Line, Col, Toks, Ncs) ->
- float_end(Cs, St, Line, Col, Toks, Ncs).
-
-scan_exponent_sign([C|Cs], St, Line, Col, Toks, Ncs) when C =:= $+; C =:= $- ->
- scan_exponent(Cs, St, Line, Col, Toks, [C|Ncs]);
-scan_exponent_sign([]=Cs, _St, Line, Col, Toks, Ncs) ->
- {more,{Cs,Col,Toks,Line,Ncs,fun scan_exponent_sign/6}};
-scan_exponent_sign(Cs, St, Line, Col, Toks, Ncs) ->
- scan_exponent(Cs, St, Line, Col, Toks, Ncs).
-
-scan_exponent([C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
- scan_exponent(Cs, St, Line, Col, Toks, [C|Ncs]);
-scan_exponent([]=Cs, _St, Line, Col, Toks, Ncs) ->
- {more,{Cs,Col,Toks,Line,Ncs,fun scan_exponent/6}};
-scan_exponent(Cs, St, Line, Col, Toks, Ncs) ->
- float_end(Cs, St, Line, Col, Toks, Ncs).
-
-float_end(Cs, St, Line, Col, Toks, Ncs0) ->
+scan_fraction(Cs, St, Line, Col, Toks, {Ncs,Us}) ->
+ scan_fraction(Cs, St, Line, Col, Toks, Ncs, Us).
+
+scan_fraction([C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
+ scan_fraction(Cs, St, Line, Col, Toks, [C|Ncs], Us);
+scan_fraction([$_,Next|Cs], St, Line, Col, Toks, [Prev|_]=Ncs, _Us) when
+ ?DIGIT(Next) andalso ?DIGIT(Prev) ->
+ scan_fraction(Cs, St, Line, Col, Toks, [Next,$_|Ncs], with_underscore);
+scan_fraction([$_]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_fraction/6}};
+scan_fraction([E|Cs], St, Line, Col, Toks, Ncs, Us) when E =:= $e; E =:= $E ->
+ scan_exponent_sign(Cs, St, Line, Col, Toks, [E|Ncs], Us);
+scan_fraction([]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_fraction/6}};
+scan_fraction(Cs, St, Line, Col, Toks, Ncs, Us) ->
+ float_end(Cs, St, Line, Col, Toks, Ncs, Us).
+
+scan_exponent_sign(Cs, St, Line, Col, Toks, {Ncs, Us}) ->
+ scan_exponent_sign(Cs, St, Line, Col, Toks, Ncs, Us).
+
+scan_exponent_sign([C|Cs], St, Line, Col, Toks, Ncs, Us) when
+ C =:= $+; C =:= $- ->
+ scan_exponent(Cs, St, Line, Col, Toks, [C|Ncs], Us);
+scan_exponent_sign([]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_exponent_sign/6}};
+scan_exponent_sign(Cs, St, Line, Col, Toks, Ncs, Us) ->
+ scan_exponent(Cs, St, Line, Col, Toks, Ncs, Us).
+
+scan_exponent(Cs, St, Line, Col, Toks, {Ncs, Us}) ->
+ scan_exponent(Cs, St, Line, Col, Toks, Ncs, Us).
+
+scan_exponent([C|Cs], St, Line, Col, Toks, Ncs, Us) when ?DIGIT(C) ->
+ scan_exponent(Cs, St, Line, Col, Toks, [C|Ncs], Us);
+scan_exponent([$_,Next|Cs], St, Line, Col, Toks, [Prev|_]=Ncs, _) when
+ ?DIGIT(Next) andalso ?DIGIT(Prev) ->
+ scan_exponent(Cs, St, Line, Col, Toks, [Next,$_|Ncs], with_underscore);
+scan_exponent([$_]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_exponent/6}};
+scan_exponent([]=Cs, _St, Line, Col, Toks, Ncs, Us) ->
+ {more,{Cs,Col,Toks,Line,{Ncs,Us},fun scan_exponent/6}};
+scan_exponent(Cs, St, Line, Col, Toks, Ncs, Us) ->
+ float_end(Cs, St, Line, Col, Toks, Ncs, Us).
+
+float_end(Cs, St, Line, Col, Toks, Ncs0, Us) ->
Ncs = lists:reverse(Ncs0),
- case catch list_to_float(Ncs) of
+ case catch list_to_float(remove_digit_separators(Ncs, Us)) of
F when is_float(F) ->
tok3(Cs, St, Line, Col, Toks, float, Ncs, F);
_ ->
diff --git a/lib/stdlib/src/erl_tar.erl b/lib/stdlib/src/erl_tar.erl
index c2c498cf0a..009e72b957 100644
--- a/lib/stdlib/src/erl_tar.erl
+++ b/lib/stdlib/src/erl_tar.erl
@@ -321,22 +321,29 @@ do_open(Name, Mode) when is_list(Mode) ->
{error, {Name, Reason}}
end.
-open1({binary,Bin}, read, _Raw, Opts) when is_binary(Bin) ->
+open1({binary,Bin}=Handle, read, _Raw, Opts) when is_binary(Bin) ->
case file:open(Bin, [ram,binary,read]) of
{ok,File} ->
_ = [ram_file:uncompress(File) || lists:member(compressed, Opts)],
{ok, #reader{handle=File,access=read,func=fun file_op/2}};
- Error ->
- Error
+ {error, Reason} ->
+ {error, {Handle, Reason}}
end;
-open1({file, Fd}, read, _Raw, _Opts) ->
- Reader = #reader{handle=Fd,access=read,func=fun file_op/2},
- case do_position(Reader, {cur, 0}) of
- {ok, Pos, Reader2} ->
- {ok, Reader2#reader{pos=Pos}};
- {error, _} = Err ->
- Err
+open1({file, Fd}=Handle, read, [raw], Opts) ->
+ case not lists:member(compressed, Opts) of
+ true ->
+ Reader = #reader{handle=Fd,access=read,func=fun file_op/2},
+ case do_position(Reader, {cur, 0}) of
+ {ok, Pos, Reader2} ->
+ {ok, Reader2#reader{pos=Pos}};
+ {error, Reason} ->
+ {error, {Handle, Reason}}
+ end;
+ false ->
+ {error, {Handle, {incompatible_option, compressed}}}
end;
+open1({file, _Fd}=Handle, read, [], _Opts) ->
+ {error, {Handle, {incompatible_option, cooked}}};
open1(Name, Access, Raw, Opts) when is_list(Name) or is_binary(Name) ->
case file:open(Name, Raw ++ [binary, Access|Opts]) of
{ok, File} ->
@@ -1637,60 +1644,18 @@ write_extracted_element(#tar_header{name=Name0}=Header, Bin, Opts) ->
make_safe_path([$/|Path], Opts) ->
make_safe_path(Path, Opts);
-make_safe_path(Path, #read_opts{cwd=Cwd}) ->
- case filename:safe_relative_path(Path) of
- unsafe ->
- throw({error,{Path,unsafe_path}});
- SafePath ->
- filename:absname(SafePath, Cwd)
+make_safe_path(Path0, #read_opts{cwd=Cwd}) ->
+ case filelib:safe_relative_path(Path0, Cwd) of
+ unsafe -> throw({error,{Path0,unsafe_path}});
+ Path -> filename:absname(Path, Cwd)
end.
-safe_link_name(#tar_header{linkname=Path}, #read_opts{cwd=Cwd}) ->
- case safe_relative_path_links(Path, Cwd) of
- unsafe ->
- throw({error,{Path,unsafe_symlink}});
- SafePath ->
- SafePath
+safe_link_name(#tar_header{linkname=Path0},#read_opts{cwd=Cwd} ) ->
+ case filelib:safe_relative_path(Path0, Cwd) of
+ unsafe -> throw({error,{Path0,unsafe_symlink}});
+ Path -> Path
end.
-safe_relative_path_links(Path, Cwd) ->
- case filename:pathtype(Path) of
- relative -> safe_relative_path_links(filename:split(Path), Cwd, [], "");
- _ -> unsafe
- end.
-
-safe_relative_path_links([Segment|Segments], Cwd, PrevSegments, Acc) ->
- AccSegment = join(Acc, Segment),
- case lists:member(AccSegment, PrevSegments) of
- true ->
- unsafe;
- false ->
- case file:read_link(join(Cwd, AccSegment)) of
- {ok, LinkPath} ->
- case filename:pathtype(LinkPath) of
- relative ->
- safe_relative_path_links(filename:split(LinkPath) ++ Segments,
- Cwd, [AccSegment|PrevSegments], Acc);
- _ ->
- unsafe
- end;
-
- {error, _} ->
- case filename:safe_relative_path(join(Acc, Segment)) of
- unsafe ->
- unsafe;
- NewAcc ->
- safe_relative_path_links(Segments, Cwd,
- [AccSegment|PrevSegments], NewAcc)
- end
- end
- end;
-safe_relative_path_links([], _Cwd, _PrevSegments, Acc) ->
- Acc.
-
-join([], Path) -> Path;
-join(Left, Right) -> filename:join(Left, Right).
-
create_regular(Name, NameInArchive, Bin, Opts) ->
case write_extracted_file(Name, Bin, Opts) of
not_written ->
diff --git a/lib/stdlib/src/escript.erl b/lib/stdlib/src/escript.erl
index 4e9ba1cc16..0e120174fe 100644
--- a/lib/stdlib/src/escript.erl
+++ b/lib/stdlib/src/escript.erl
@@ -780,7 +780,7 @@ interpret(Forms, HasRecs, File, Args) ->
false -> Forms;
true -> erl_expand_records:module(Forms, [])
end,
- Dict = parse_to_dict(Forms2),
+ Dict = parse_to_map(Forms2),
ArgsA = erl_parse:abstract(Args, 0),
Anno = a0(),
Call = {call,Anno,{atom,Anno,main},[ArgsA]},
@@ -824,29 +824,29 @@ format_message(F, [{Mod,E}|Es]) ->
[M|format_message(F, Es)];
format_message(_, []) -> [].
-parse_to_dict(L) -> parse_to_dict(L, dict:new()).
-
-parse_to_dict([{function,_,Name,Arity,Clauses}|T], Dict0) ->
- Dict = dict:store({local, Name,Arity}, Clauses, Dict0),
- parse_to_dict(T, Dict);
-parse_to_dict([{attribute,_,import,{Mod,Funcs}}|T], Dict0) ->
- Dict = lists:foldl(fun(I, D) ->
- dict:store({remote,I}, Mod, D)
- end, Dict0, Funcs),
- parse_to_dict(T, Dict);
-parse_to_dict([_|T], Dict) ->
- parse_to_dict(T, Dict);
-parse_to_dict([], Dict) ->
- Dict.
+parse_to_map(L) -> parse_to_map(L, maps:new()).
+
+parse_to_map([{function,_,Name,Arity,Clauses}|T], Map0) ->
+ Map = maps:put({local, Name,Arity}, Clauses, Map0),
+ parse_to_map(T, Map);
+parse_to_map([{attribute,_,import,{Mod,Funcs}}|T], Map0) ->
+ Map = lists:foldl(fun(I, D) ->
+ maps:put({remote,I}, Mod, D)
+ end, Map0, Funcs),
+ parse_to_map(T, Map);
+parse_to_map([_|T], Map) ->
+ parse_to_map(T, Map);
+parse_to_map([], Map) ->
+ Map.
code_handler(local, [file], _, File) ->
File;
-code_handler(Name, Args, Dict, File) ->
+code_handler(Name, Args, Map, File) ->
%%io:format("code handler=~p~n",[{Name, Args}]),
Arity = length(Args),
- case dict:find({local,Name,Arity}, Dict) of
+ case maps:find({local,Name,Arity}, Map) of
{ok, Cs} ->
- LF = {value,fun(I, J) -> code_handler(I, J, Dict, File) end},
+ LF = {value,fun(I, J) -> code_handler(I, J, Map, File) end},
case erl_eval:match_clause(Cs, Args,erl_eval:new_bindings(),LF) of
{Body, Bs} ->
eval_exprs(Body, Bs, LF, none, none);
@@ -854,7 +854,7 @@ code_handler(Name, Args, Dict, File) ->
erlang:error({function_clause,[{local,Name,Args}]})
end;
error ->
- case dict:find({remote,{Name,Arity}}, Dict) of
+ case maps:find({remote,{Name,Arity}}, Map) of
{ok, Mod} ->
%% io:format("Calling:~p~n",[{Mod,Name,Args}]),
apply(Mod, Name, Args);
diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl
index 385b736b29..951a63892e 100644
--- a/lib/stdlib/src/ets.erl
+++ b/lib/stdlib/src/ets.erl
@@ -155,6 +155,7 @@ give_away(_, _, _) ->
Tab :: tab(),
InfoList :: [InfoTuple],
InfoTuple :: {compressed, boolean()}
+ | {decentralized_counters, boolean()}
| {heir, pid() | none}
| {id, tid()}
| {keypos, pos_integer()}
@@ -174,7 +175,7 @@ info(_) ->
-spec info(Tab, Item) -> Value | undefined when
Tab :: tab(),
- Item :: binary | compressed | fixed | heir | id | keypos | memory
+ Item :: binary | compressed | decentralized_counters | fixed | heir | id | keypos | memory
| name | named_table | node | owner | protection
| safe_fixed | safe_fixed_monotonic_time | size | stats | type
| write_concurrency | read_concurrency,
@@ -311,6 +312,7 @@ member(_, _) ->
Access :: access(),
Tweaks :: {write_concurrency, boolean()}
| {read_concurrency, boolean()}
+ | {decentralized_counters, boolean()}
| compressed,
Pos :: pos_integer(),
HeirData :: term().
diff --git a/lib/stdlib/src/eval_bits.erl b/lib/stdlib/src/eval_bits.erl
index bb86a65c72..5c75320de0 100644
--- a/lib/stdlib/src/eval_bits.erl
+++ b/lib/stdlib/src/eval_bits.erl
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -81,8 +81,15 @@ eval_field({bin_element, Line, {string, _, S}, Size0, Options0}, Bs0, Fun) ->
make_bit_type(Line, Size0, Options0),
{value,Size,Bs1} = Fun(Size1, Bs0),
Res = << <<(eval_exp_field1(C, Size, Unit,
- Type, Endian, Sign))/binary>> ||
+ Type, Endian, Sign))/bitstring>> ||
C <- S >>,
+ case S of
+ "" -> % find errors also when the string is empty
+ _ = eval_exp_field1(0, Size, Unit, Type, Endian, Sign),
+ ok;
+ _ ->
+ ok
+ end,
{Res,Bs1};
eval_field({bin_element,Line,E,Size0,Options0}, Bs0, Fun) ->
{value,V,Bs1} = Fun(E, Bs0),
@@ -119,10 +126,14 @@ eval_exp_field(Val, _Size, _Unit, utf16, big, _) ->
<<Val/big-utf16>>;
eval_exp_field(Val, _Size, _Unit, utf16, little, _) ->
<<Val/little-utf16>>;
+eval_exp_field(Val, _Size, _Unit, utf16, native, _) ->
+ <<Val/native-utf16>>;
eval_exp_field(Val, _Size, _Unit, utf32, big, _) ->
<<Val/big-utf32>>;
eval_exp_field(Val, _Size, _Unit, utf32, little, _) ->
<<Val/little-utf32>>;
+eval_exp_field(Val, _Size, _Unit, utf32, native, _) ->
+ <<Val/native-utf32>>;
eval_exp_field(Val, Size, Unit, float, little, _) ->
<<Val:(Size*Unit)/float-little>>;
eval_exp_field(Val, Size, Unit, float, native, _) ->
@@ -187,7 +198,6 @@ bin_gen_field({bin_element,Line,{string,SLine,S},Size0,Options0},
Bin0, Bs0, BBs0, Mfun, Efun) ->
{Size1, [Type,{unit,Unit},Sign,Endian]} =
make_bit_type(Line, Size0, Options0),
- match_check_size(Mfun, Size1, BBs0),
{value, Size, _BBs} = Efun(Size1, BBs0),
F = fun(C, Bin, Bs, BBs) ->
bin_gen_field1(Bin, Type, Size, Unit, Sign, Endian,
@@ -200,7 +210,6 @@ bin_gen_field({bin_element,Line,VE,Size0,Options0},
make_bit_type(Line, Size0, Options0),
V = erl_eval:partial_eval(VE),
NewV = coerce_to_float(V, Type),
- match_check_size(Mfun, Size1, BBs0, false),
{value, Size, _BBs} = Efun(Size1, BBs0),
bin_gen_field1(Bin, Type, Size, Unit, Sign, Endian, NewV, Bs0, BBs0, Mfun).
@@ -269,7 +278,6 @@ match_field_1({bin_element,Line,{string,SLine,S},Size0,Options0},
{Size1, [Type,{unit,Unit},Sign,Endian]} =
make_bit_type(Line, Size0, Options0),
Size2 = erl_eval:partial_eval(Size1),
- match_check_size(Mfun, Size2, BBs0),
{value, Size, _BBs} = Efun(Size2, BBs0),
F = fun(C, Bin, Bs, BBs) ->
match_field(Bin, Type, Size, Unit, Sign, Endian,
@@ -283,7 +291,6 @@ match_field_1({bin_element,Line,VE,Size0,Options0},
V = erl_eval:partial_eval(VE),
NewV = coerce_to_float(V, Type),
Size2 = erl_eval:partial_eval(Size1),
- match_check_size(Mfun, Size2, BBs0),
{value, Size, _BBs} = Efun(Size2, BBs0),
match_field(Bin, Type, Size, Unit, Sign, Endian, NewV, Bs0, BBs0, Mfun).
@@ -331,12 +338,18 @@ get_value(Bin, utf16, undefined, _Unit, _Sign, big) ->
get_value(Bin, utf16, undefined, _Unit, _Sign, little) ->
<<I/little-utf16,Rest/bits>> = Bin,
{I,Rest};
+get_value(Bin, utf16, undefined, _Unit, _Sign, native) ->
+ <<I/native-utf16,Rest/bits>> = Bin,
+ {I,Rest};
get_value(Bin, utf32, undefined, _Unit, _Sign, big) ->
<<Val/big-utf32,Rest/bits>> = Bin,
{Val,Rest};
get_value(Bin, utf32, undefined, _Unit, _Sign, little) ->
<<Val/little-utf32,Rest/bits>> = Bin,
{Val,Rest};
+get_value(Bin, utf32, undefined, _Unit, _Sign, native) ->
+ <<Val/native-utf32,Rest/bits>> = Bin,
+ {Val,Rest};
get_value(Bin, binary, all, Unit, _Sign, _Endian) ->
0 = (bit_size(Bin) rem Unit),
{Bin,<<>>};
@@ -387,24 +400,3 @@ make_bit_type(_Line, Size, Type0) -> %Size evaluates to an integer or 'all'
{ok,Size,Bt} -> {Size,erl_bits:as_list(Bt)};
{error,Reason} -> erlang:raise(error, Reason, ?STACKTRACE)
end.
-
-match_check_size(Mfun, Size, Bs) ->
- match_check_size(Mfun, Size, Bs, true).
-
-match_check_size(Mfun, {var,_,V}, Bs, _AllowAll) ->
- case Mfun(binding, {V,Bs}) of
- {value,_} -> ok;
- unbound -> throw(invalid) % or, rather, error({unbound,V})
- end;
-match_check_size(_, {atom,_,all}, _Bs, true) ->
- ok;
-match_check_size(_, {atom,_,all}, _Bs, false) ->
- throw(invalid);
-match_check_size(_, {atom,_,undefined}, _Bs, _AllowAll) ->
- ok;
-match_check_size(_, {integer,_,_}, _Bs, _AllowAll) ->
- ok;
-match_check_size(_, {value,_,_}, _Bs, _AllowAll) ->
- ok; %From the debugger.
-match_check_size(_, _, _Bs, _AllowAll) ->
- throw(invalid).
diff --git a/lib/stdlib/src/filelib.erl b/lib/stdlib/src/filelib.erl
index f72d54d13d..7f8c282a79 100644
--- a/lib/stdlib/src/filelib.erl
+++ b/lib/stdlib/src/filelib.erl
@@ -25,6 +25,7 @@
-export([wildcard/3, is_dir/2, is_file/2, is_regular/2]).
-export([fold_files/6, last_modified/2, file_size/2]).
-export([find_file/2, find_file/3, find_source/1, find_source/2, find_source/3]).
+-export([safe_relative_path/2]).
%% For debugging/testing.
-export([compile_wildcard/1]).
@@ -267,9 +268,9 @@ do_wildcard(Pattern, Cwd, Mod) ->
lists:sort(Files).
do_wildcard_1({exists,File}, Mod) ->
- case eval_read_link_info(File, Mod) of
- {ok,_} -> [File];
- _ -> []
+ case exists(File, Mod) of
+ true -> [File];
+ false -> []
end;
do_wildcard_1([Base|Rest], Mod) ->
do_wildcard_2([Base], Rest, [], Mod).
@@ -333,6 +334,7 @@ match_part([_|_], []) ->
false.
will_always_match([accept]) -> true;
+will_always_match([double_star]) -> true;
will_always_match(_) -> false.
prepare_base(Base0) ->
@@ -340,22 +342,33 @@ prepare_base(Base0) ->
"x"++Base2 = lists:reverse(Base1),
lists:reverse(Base2).
-do_double_star(Base, [H|T], Rest, Result, Mod, Root) ->
+do_double_star(Base, [H|T], Patterns, Result0, Mod, Root) ->
Full = case Root of
- false -> filename:join(Base, H);
- true -> H
- end,
+ false -> filename:join(Base, H);
+ true -> H
+ end,
Result1 = case do_list_dir(Full, Mod) of
- {ok, Files} ->
- do_double_star(Full, Files, Rest, Result, Mod, false);
- _ -> Result
- end,
- Result2 = case Root andalso Rest == [] of
- true -> Result1;
- false -> do_wildcard_3(Full, Rest, Result1, Mod)
- end,
- do_double_star(Base, T, Rest, Result2, Mod, Root);
-do_double_star(_Base, [], _Rest, Result, _Mod, _Root) ->
+ {ok, Files} ->
+ do_double_star(Full, Files, Patterns, Result0, Mod, false);
+ _ -> Result0
+ end,
+ Result2 = case Patterns of
+ %% The root is never included in the result.
+ _ when Root -> Result1;
+
+ %% An empty pattern includes all results (except the root).
+ [] -> [Full | Result1];
+
+ %% Otherwise we check if the current entry matches
+ %% and continue recursively.
+ [Pattern | Rest] ->
+ case match_part(Pattern, H) of
+ true -> do_wildcard_2([Full], Rest, Result1, Mod);
+ false -> Result1
+ end
+ end,
+ do_double_star(Base, T, Patterns, Result2, Mod, Root);
+do_double_star(_Base, [], _Patterns, Result, _Mod, _Root) ->
Result.
do_star(Pattern, [_|Rest]=File) ->
@@ -549,6 +562,36 @@ wrap_escapes([]) ->
badpattern(Reason) ->
error({badpattern,Reason}).
+exists(File, Mod) ->
+ case eval_read_link_info(File, Mod) of
+ {error, _} ->
+ false;
+ {ok, _Info} ->
+ case os:type() of
+ {win32,_} ->
+ do_exists(filename:split(File), Mod, []);
+ _ ->
+ true
+ end
+ end.
+
+do_exists([P,".."|Ps], Mod, Acc) ->
+ %% On Windows, "pathname/.." will seem to exist even if pathname
+ %% does not refer to a directory.
+ Path = case Acc of
+ [] -> P;
+ _ -> filename:join(lists:reverse(Acc, [P]))
+ end,
+ case eval_read_link_info(Path, Mod) of
+ {ok, #file_info{type=directory}} ->
+ do_exists(Ps, Mod, Acc);
+ _ ->
+ false
+ end;
+do_exists([P|Ps], Mod, Acc) ->
+ do_exists(Ps, Mod, [P|Acc]);
+do_exists([], _, _) -> true.
+
eval_read_file_info(File, file) ->
file:read_file_info(File);
eval_read_file_info(File, erl_prim_loader) ->
@@ -706,3 +749,71 @@ find_regular_file([File|Files]) ->
true -> {ok, File};
false -> find_regular_file(Files)
end.
+
+-spec safe_relative_path(Filename, Cwd) -> unsafe | SafeFilename when
+ Filename :: filename_all(),
+ Cwd :: filename_all(),
+ SafeFilename :: filename_all().
+
+safe_relative_path(Path, Cwd) ->
+ case filename:pathtype(Path) of
+ relative -> safe_relative_path(filename:split(Path), Cwd, [], "");
+ _ -> unsafe
+ end.
+
+safe_relative_path([], _Cwd, _PrevLinks, Acc) ->
+ Acc;
+
+safe_relative_path([Segment | Segments], Cwd, PrevLinks, Acc) ->
+ AccSegment = join(Acc, Segment),
+ case safe_relative_path(AccSegment) of
+ unsafe ->
+ unsafe;
+ SafeAccSegment ->
+ case file:read_link(join(Cwd, SafeAccSegment)) of
+ {ok, LinkPath} ->
+ case lists:member(LinkPath, PrevLinks) of
+ true ->
+ unsafe;
+ false ->
+ case safe_relative_path(filename:split(LinkPath), Cwd, [LinkPath | PrevLinks], Acc) of
+ unsafe -> unsafe;
+ NewAcc -> safe_relative_path(Segments, Cwd, [], NewAcc)
+ end
+ end;
+ {error, _} ->
+ safe_relative_path(Segments, Cwd, PrevLinks, SafeAccSegment)
+ end
+ end.
+
+join([], Path) -> Path;
+join(Left, Right) -> filename:join(Left, Right).
+
+safe_relative_path(Path) ->
+ case filename:pathtype(Path) of
+ relative ->
+ Cs0 = filename:split(Path),
+ safe_relative_path_1(Cs0, []);
+ _ ->
+ unsafe
+ end.
+
+safe_relative_path_1(["."|T], Acc) ->
+ safe_relative_path_1(T, Acc);
+safe_relative_path_1([<<".">>|T], Acc) ->
+ safe_relative_path_1(T, Acc);
+safe_relative_path_1([".."|T], Acc) ->
+ climb(T, Acc);
+safe_relative_path_1([<<"..">>|T], Acc) ->
+ climb(T, Acc);
+safe_relative_path_1([H|T], Acc) ->
+ safe_relative_path_1(T, [H|Acc]);
+safe_relative_path_1([], []) ->
+ [];
+safe_relative_path_1([], Acc) ->
+ filename:join(lists:reverse(Acc)).
+
+climb(_, []) ->
+ unsafe;
+climb(T, [_|Acc]) ->
+ safe_relative_path_1(T, Acc).
diff --git a/lib/stdlib/src/filename.erl b/lib/stdlib/src/filename.erl
index b7b7b562ab..b6df99621f 100644
--- a/lib/stdlib/src/filename.erl
+++ b/lib/stdlib/src/filename.erl
@@ -19,8 +19,8 @@
%%
-module(filename).
--deprecated({find_src,1,next_major_release}).
--deprecated({find_src,2,next_major_release}).
+-deprecated([{find_src,'_',"use filelib:find_source/1,3 instead"}]).
+-deprecated([{safe_relative_path,1,"use filelib:safe_relative_path/2 instead"}]).
%% Purpose: Provides generic manipulation of filenames.
%%
diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl
index a7f743bd4c..be14665d80 100644
--- a/lib/stdlib/src/gen.erl
+++ b/lib/stdlib/src/gen.erl
@@ -28,7 +28,9 @@
%%%-----------------------------------------------------------------
-export([start/5, start/6, debug_options/2, hibernate_after/1,
name/1, unregister_name/1, get_proc_name/1, get_parent/0,
- call/3, call/4, reply/2, stop/1, stop/3]).
+ call/3, call/4, reply/2,
+ send_request/3, wait_response/2, check_response/2,
+ stop/1, stop/3]).
-export([init_it/6, init_it/7]).
@@ -38,7 +40,7 @@
%%-----------------------------------------------------------------
--type linkage() :: 'link' | 'nolink'.
+-type linkage() :: 'monitor' | 'link' | 'nolink'.
-type emgr_name() :: {'local', atom()}
| {'global', term()}
| {'via', Module :: module(), Name :: term()}.
@@ -53,6 +55,11 @@
| {'spawn_opt', [proc_lib:spawn_option()]}.
-type options() :: [option()].
+-type server_ref() :: pid() | atom() | {atom(), node()}
+ | {global, term()} | {via, module(), term()}.
+
+-type request_id() :: term().
+
%%-----------------------------------------------------------------
%% Starts a generic process.
%% start(GenMod, LinkP, Mod, Args, Options)
@@ -95,6 +102,13 @@ do_spawn(GenMod, link, Mod, Args, Options) ->
[GenMod, self(), self(), Mod, Args, Options],
Time,
spawn_opts(Options));
+do_spawn(GenMod, monitor, Mod, Args, Options) ->
+ Time = timeout(Options),
+ Ret = proc_lib:start_monitor(?MODULE, init_it,
+ [GenMod, self(), self(), Mod, Args, Options],
+ Time,
+ spawn_opts(Options)),
+ monitor_return(Ret);
do_spawn(GenMod, _, Mod, Args, Options) ->
Time = timeout(Options),
proc_lib:start(?MODULE, init_it,
@@ -108,6 +122,13 @@ do_spawn(GenMod, link, Name, Mod, Args, Options) ->
[GenMod, self(), self(), Name, Mod, Args, Options],
Time,
spawn_opts(Options));
+do_spawn(GenMod, monitor, Name, Mod, Args, Options) ->
+ Time = timeout(Options),
+ Ret = proc_lib:start_monitor(?MODULE, init_it,
+ [GenMod, self(), self(), Name, Mod, Args, Options],
+ Time,
+ spawn_opts(Options)),
+ monitor_return(Ret);
do_spawn(GenMod, _, Name, Mod, Args, Options) ->
Time = timeout(Options),
proc_lib:start(?MODULE, init_it,
@@ -115,6 +136,26 @@ do_spawn(GenMod, _, Name, Mod, Args, Options) ->
Time,
spawn_opts(Options)).
+
+%%
+%% Adjust monitor returns for OTP gen behaviours...
+%%
+%% If an OTP behaviour is introduced that 'init_ack's
+%% other results, this has code has to be moved out
+%% into all behaviours as well as adjusted...
+%%
+monitor_return({{ok, Pid}, Mon}) when is_pid(Pid), is_reference(Mon) ->
+ %% Successful start_monitor()...
+ {ok, {Pid, Mon}};
+monitor_return({Error, Mon}) when is_reference(Mon) ->
+ %% Failure; wait for spawned process to terminate
+ %% and release resources, then return the error...
+ receive
+ {'DOWN', Mon, process, _Pid, _Reason} ->
+ ok
+ end,
+ Error.
+
%%-----------------------------------------------------------------
%% Initiate the new process.
%% Register the name using the Rfunc function
@@ -139,7 +180,7 @@ init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
%%-----------------------------------------------------------------
%% Makes a synchronous call to a generic process.
%% Request is sent to the Pid, and the response must be
-%% {Tag, _, Reply}.
+%% {Tag, Reply}.
%%-----------------------------------------------------------------
%%% New call function which uses the new monitor BIF
@@ -192,6 +233,56 @@ get_node(Process) ->
node(Process)
end.
+-spec send_request(Name::server_ref(), Label::term(), Request::term()) -> request_id().
+send_request(Process, Label, Request) when is_pid(Process) ->
+ do_send_request(Process, Label, Request);
+send_request(Process, Label, Request) ->
+ Fun = fun(Pid) -> do_send_request(Pid, Label, Request) end,
+ try do_for_proc(Process, Fun)
+ catch exit:Reason ->
+ %% Make send_request async and fake a down message
+ Mref = erlang:make_ref(),
+ self() ! {'DOWN', Mref, process, Process, Reason},
+ Mref
+ end.
+
+do_send_request(Process, Label, Request) ->
+ Mref = erlang:monitor(process, Process),
+ erlang:send(Process, {Label, {self(), {'$gen_request_id', Mref}}, Request}, [noconnect]),
+ Mref.
+
+%%
+%% Wait for a reply to the client.
+%% Note: if timeout is returned monitors are kept.
+
+-spec wait_response(RequestId::request_id(), timeout()) ->
+ {reply, Reply::term()} | 'timeout' | {error, {term(), server_ref()}}.
+wait_response(Mref, Timeout)
+ when is_reference(Mref) ->
+ receive
+ {{'$gen_request_id', Mref}, Reply} ->
+ erlang:demonitor(Mref, [flush]),
+ {reply, Reply};
+ {'DOWN', Mref, _, Object, Reason} ->
+ {error, {Reason, Object}}
+ after Timeout ->
+ timeout
+ end.
+
+-spec check_response(RequestId::term(), Key::request_id()) ->
+ {reply, Reply::term()} | 'no_reply' | {error, {term(), server_ref()}}.
+check_response(Msg, Mref)
+ when is_reference(Mref) ->
+ case Msg of
+ {{'$gen_request_id', Mref}, Reply} ->
+ erlang:demonitor(Mref, [flush]),
+ {reply, Reply};
+ {'DOWN', Mref, _, Object, Reason} ->
+ {error, {Reason, Object}};
+ _ ->
+ no_reply
+ end.
+
%%
%% Send a reply to the client.
%%
diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl
index 8213282867..8024221cab 100644
--- a/lib/stdlib/src/gen_event.erl
+++ b/lib/stdlib/src/gen_event.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -31,13 +31,20 @@
%%% Re-written by Joe with new functional interface !
%%% Modified by Martin - uses proc_lib, sys and gen!
+%%%
+%%% NOTE: If init_ack() return values are modified, see comment
+%%% above monitor_return() in gen.erl!
+%%%
-export([start/0, start/1, start/2,
start_link/0, start_link/1, start_link/2,
+ start_monitor/0, start_monitor/1, start_monitor/2,
stop/1, stop/3,
notify/2, sync_notify/2,
add_handler/3, add_sup_handler/3, delete_handler/3, swap_handler/3,
- swap_sup_handler/3, which_handlers/1, call/3, call/4, wake_hib/5]).
+ swap_sup_handler/3, which_handlers/1, call/3, call/4,
+ send_request/3, wait_response/2, check_response/2,
+ wake_hib/5]).
-export([init_it/6,
system_continue/3,
@@ -48,7 +55,7 @@
format_status/2]).
%% logger callback
--export([format_log/1]).
+-export([format_log/1, format_log/2]).
-export_type([handler/0, handler_args/0, add_handler_ret/0,
del_handler_ret/0]).
@@ -128,11 +135,13 @@
| {'logfile', string()}.
-type option() :: {'timeout', timeout()}
| {'debug', [debug_flag()]}
- | {'spawn_opt', [proc_lib:spawn_option()]}
+ | {'spawn_opt', [proc_lib:start_spawn_option()]}
| {'hibernate_after', timeout()}.
-type emgr_ref() :: atom() | {atom(), atom()} | {'global', term()}
| {'via', atom(), term()} | pid().
-type start_ret() :: {'ok', pid()} | {'error', term()}.
+-type start_mon_ret() :: {'ok', {pid(),reference()}} | {'error', term()}.
+-type request_id() :: term().
%%---------------------------------------------------------------------------
@@ -183,6 +192,20 @@ start_link(Options) when is_list(Options) ->
start_link(Name, Options) ->
gen:start(?MODULE, link, Name, ?NO_CALLBACK, [], Options).
+-spec start_monitor() -> start_mon_ret().
+start_monitor() ->
+ gen:start(?MODULE, monitor, ?NO_CALLBACK, [], []).
+
+-spec start_monitor(emgr_name() | [option()]) -> start_mon_ret().
+start_monitor(Name) when is_tuple(Name) ->
+ gen:start(?MODULE, monitor, Name, ?NO_CALLBACK, [], []);
+start_monitor(Options) when is_list(Options) ->
+ gen:start(?MODULE, monitor, ?NO_CALLBACK, [], Options).
+
+-spec start_monitor(emgr_name(), [option()]) -> start_mon_ret().
+start_monitor(Name, Options) ->
+ gen:start(?MODULE, monitor, Name, ?NO_CALLBACK, [], Options).
+
%% -spec init_it(pid(), 'self' | pid(), emgr_name(), module(), [term()], [_]) ->
init_it(Starter, self, Name, Mod, Args, Options) ->
init_it(Starter, self(), Name, Mod, Args, Options);
@@ -213,6 +236,26 @@ call(M, Handler, Query) -> call1(M, Handler, Query).
-spec call(emgr_ref(), handler(), term(), timeout()) -> term().
call(M, Handler, Query, Timeout) -> call1(M, Handler, Query, Timeout).
+-spec send_request(emgr_ref(), handler(), term()) -> request_id().
+send_request(M, Handler, Query) ->
+ gen:send_request(M, self(), {call, Handler, Query}).
+
+-spec wait_response(RequestId::request_id(), timeout()) ->
+ {reply, Reply::term()} | 'timeout' | {error, {Reason::term(), emgr_ref()}}.
+wait_response(RequestId, Timeout) ->
+ case gen:wait_response(RequestId, Timeout) of
+ {reply, {error, _} = Err} -> Err;
+ Return -> Return
+ end.
+
+-spec check_response(Msg::term(), RequestId::request_id()) ->
+ {reply, Reply::term()} | 'no_reply' | {error, {Reason::term(), emgr_ref()}}.
+check_response(Msg, RequestId) ->
+ case gen:check_response(Msg, RequestId) of
+ {reply, {error, _} = Err} -> Err;
+ Return -> Return
+ end.
+
-spec delete_handler(emgr_ref(), handler(), term()) -> term().
delete_handler(M, Handler, Args) -> rpc(M, {delete_handler, Handler, Args}).
@@ -590,8 +633,10 @@ server_update(Handler1, Func, Event, SName) ->
module=>Mod1,
message=>Event},
#{domain=>[otp],
- report_cb=>fun gen_event:format_log/1,
- error_logger=>#{tag=>warning_msg}}), % warningmap??
+ report_cb=>fun gen_event:format_log/2,
+ error_logger=>
+ #{tag=>warning_msg, % warningmap??
+ report_cb=>fun gen_event:format_log/1}}),
{ok, Handler1};
Other ->
do_terminate(Mod1, Handler1, {error, Other}, State,
@@ -752,46 +797,165 @@ report_error(Handler, Reason, State, LastIn, SName) ->
get(),State),
reason=>Reason},
#{domain=>[otp],
- report_cb=>fun gen_event:format_log/1,
- error_logger=>#{tag=>error}}).
-
-format_log(#{label:={gen_event,terminate},
- handler:=Handler,
- name:=SName,
- last_message:=LastIn,
- state:=State,
- reason:=Reason}) ->
- Reason1 =
- case Reason of
- {'EXIT',{undef,[{M,F,A,L}|MFAs]}} ->
- case code:is_loaded(M) of
- false ->
- {'module could not be loaded',[{M,F,A,L}|MFAs]};
- _ ->
- case erlang:function_exported(M, F, length(A)) of
- true ->
- {undef,[{M,F,A,L}|MFAs]};
- false ->
- {'function not exported',[{M,F,A,L}|MFAs]}
- end
- end;
- {'EXIT',Why} ->
- Why;
- _ ->
- Reason
- end,
- {"** gen_event handler ~p crashed.~n"
- "** Was installed in ~tp~n"
- "** Last event was: ~tp~n"
- "** When handler state == ~tp~n"
- "** Reason == ~tp~n",
- [Handler,SName,LastIn,State,Reason1]};
-format_log(#{label:={gen_event,no_handle_info},
- module:=Mod,
- message:=Msg}) ->
- {"** Undefined handle_info in ~tp~n"
- "** Unhandled message: ~tp~n",
- [Mod, Msg]}.
+ report_cb=>fun gen_event:format_log/2,
+ error_logger=>#{tag=>error,
+ report_cb=>fun gen_event:format_log/1}}).
+
+%% format_log/1 is the report callback used by Logger handler
+%% error_logger only. It is kept for backwards compatibility with
+%% legacy error_logger event handlers. This function must always
+%% return {Format,Args} compatible with the arguments in this module's
+%% calls to error_logger prior to OTP-21.0.
+format_log(Report) ->
+ Depth = error_logger:get_format_depth(),
+ FormatOpts = #{chars_limit => unlimited,
+ depth => Depth,
+ single_line => false,
+ encoding => utf8},
+ format_log_multi(limit_report(Report, Depth), FormatOpts).
+
+limit_report(Report, unlimited) ->
+ Report;
+limit_report(#{label:={gen_event,terminate},
+ last_message:=LastIn,
+ state:=State,
+ reason:=Reason}=Report,
+ Depth) ->
+ Report#{last_message => io_lib:limit_term(LastIn, Depth),
+ state => io_lib:limit_term(State, Depth),
+ reason => io_lib:limit_term(Reason, Depth)};
+limit_report(#{label:={gen_event,no_handle_info},
+ message:=Msg}=Report,
+ Depth) ->
+ Report#{message => io_lib:limit_term(Msg, Depth)}.
+
+%% format_log/2 is the report callback for any Logger handler, except
+%% error_logger.
+format_log(Report, FormatOpts0) ->
+ Default = #{chars_limit => unlimited,
+ depth => unlimited,
+ single_line => false,
+ encoding => utf8},
+ FormatOpts = maps:merge(Default, FormatOpts0),
+ IoOpts =
+ case FormatOpts of
+ #{chars_limit:=unlimited} ->
+ [];
+ #{chars_limit:=Limit} ->
+ [{chars_limit,Limit}]
+ end,
+ {Format,Args} = format_log_single(Report, FormatOpts),
+ io_lib:format(Format, Args, IoOpts).
+
+format_log_single(#{label:={gen_event,terminate},
+ handler:=Handler,
+ name:=SName,
+ last_message:=LastIn,
+ state:=State,
+ reason:=Reason},
+ #{single_line:=true, depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Reason1 = fix_reason(Reason),
+ Format1 = lists:append(["Generic event handler ",P," crashed. "
+ "Installed: ",P,". Last event: ",P,
+ ". State: ",P,". Reason: ",P,"."]),
+ Args1 =
+ case Depth of
+ unlimited ->
+ [Handler,SName,Reason1,LastIn,State];
+ _ ->
+ [Handler,Depth,SName,Depth,Reason1,Depth,
+ LastIn,Depth,State,Depth]
+ end,
+ {Format1, Args1};
+format_log_single(#{label:={gen_event,no_handle_info},
+ module:=Mod,
+ message:=Msg},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format = lists:append(["Undefined handle_info in ",P,
+ ". Unhandled message: ",P,"."]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Mod,Msg];
+ _ ->
+ [Mod,Depth,Msg,Depth]
+ end,
+ {Format,Args};
+format_log_single(Report,FormatOpts) ->
+ format_log_multi(Report,FormatOpts).
+
+format_log_multi(#{label:={gen_event,terminate},
+ handler:=Handler,
+ name:=SName,
+ last_message:=LastIn,
+ state:=State,
+ reason:=Reason},
+ #{depth:=Depth}=FormatOpts) ->
+ Reason1 = fix_reason(Reason),
+ P = p(FormatOpts),
+ Format =
+ lists:append(["** gen_event handler ",P," crashed.\n",
+ "** Was installed in ",P,"\n",
+ "** Last event was: ",P,"\n",
+ "** When handler state == ",P,"\n",
+ "** Reason == ",P,"\n"]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Handler,SName,LastIn,State,Reason1];
+ _ ->
+ [Handler,Depth,SName,Depth,LastIn,Depth,State,Depth,
+ Reason1,Depth]
+ end,
+ {Format,Args};
+format_log_multi(#{label:={gen_event,no_handle_info},
+ module:=Mod,
+ message:=Msg},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ "** Undefined handle_info in ~p\n"
+ "** Unhandled message: "++P++"\n",
+ Args =
+ case Depth of
+ unlimited ->
+ [Mod,Msg];
+ _ ->
+ [Mod,Msg,Depth]
+ end,
+ {Format,Args}.
+
+fix_reason({'EXIT',{undef,[{M,F,A,_L}|_]=MFAs}=Reason}) ->
+ case code:is_loaded(M) of
+ false ->
+ {'module could not be loaded',MFAs};
+ _ ->
+ case erlang:function_exported(M, F, length(A)) of
+ true ->
+ Reason;
+ false ->
+ {'function not exported',MFAs}
+ end
+ end;
+fix_reason({'EXIT',Reason}) ->
+ Reason;
+fix_reason(Reason) ->
+ Reason.
+
+p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
+ "~"++single(Single)++mod(Enc)++p(Depth);
+p(unlimited) ->
+ "p";
+p(_Depth) ->
+ "P".
+
+single(true) -> "0";
+single(false) -> "".
+
+mod(latin1) -> "";
+mod(_) -> "t".
handler(Handler) when not Handler#handler.id ->
Handler#handler.module;
diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl
index 1e18710738..f4752c37d4 100644
--- a/lib/stdlib/src/gen_fsm.erl
+++ b/lib/stdlib/src/gen_fsm.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -127,27 +127,9 @@
format_status/2]).
%% logger callback
--export([format_log/1]).
-
--deprecated({start, 3, eventually}).
--deprecated({start, 4, eventually}).
--deprecated({start_link, 3, eventually}).
--deprecated({start_link, 4, eventually}).
--deprecated({stop, 1, eventually}).
--deprecated({stop, 3, eventually}).
--deprecated({send_event, 2, eventually}).
--deprecated({sync_send_event, 2, eventually}).
--deprecated({sync_send_event, 3, eventually}).
--deprecated({send_all_state_event, 2, eventually}).
--deprecated({sync_send_all_state_event, 2, eventually}).
--deprecated({sync_send_all_state_event, 3, eventually}).
--deprecated({reply, 2, eventually}).
--deprecated({start_timer, 2, eventually}).
--deprecated({send_event_after, 2, eventually}).
--deprecated({cancel_timer, 1, eventually}).
--deprecated({enter_loop, 4, eventually}).
--deprecated({enter_loop, 5, eventually}).
--deprecated({enter_loop, 6, eventually}).
+-export([format_log/1, format_log/2]).
+
+-deprecated({'_','_', "use the 'gen_statem' module instead"}).
%%% ---------------------------------------------------
%%% Interface functions.
@@ -517,8 +499,10 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi
module=>Mod,
message=>Msg},
#{domain=>[otp],
- report_cb=>fun gen_fsm:format_log/1,
- error_logger=>#{tag=>warning_msg}}),
+ report_cb=>fun gen_fsm:format_log/2,
+ error_logger=>
+ #{tag=>warning_msg,
+ report_cb=>fun gen_fsm:format_log/1}}),
loop(Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, []);
{'EXIT', What} ->
terminate(What, Name, From, Msg, Mod, StateName, StateData, []);
@@ -634,8 +618,9 @@ error_info(Reason, Name, From, Msg, StateName, StateData, Debug) ->
reason=>Reason,
client_info=>client_stacktrace(From)},
#{domain=>[otp],
- report_cb=>fun gen_fsm:format_log/1,
- error_logger=>#{tag=>error}}),
+ report_cb=>fun gen_fsm:format_log/2,
+ error_logger=>#{tag=>error,
+ report_cb=>fun gen_fsm:format_log/1}}),
ok.
client_stacktrace(undefined) ->
@@ -655,70 +640,197 @@ client_stacktrace(Pid) when is_pid(Pid) ->
{Pid,remote}.
-format_log(#{label:={gen_fsm,terminate},
- name:=Name,
- last_message:=Msg,
- state_name:=StateName,
- state_data:=StateData,
- log:=Log,
- reason:=Reason,
- client_info:=ClientInfo}) ->
- Reason1 =
- case Reason of
- {undef,[{M,F,A,L}|MFAs]} ->
- case code:is_loaded(M) of
- false ->
- {'module could not be loaded',[{M,F,A,L}|MFAs]};
- _ ->
- case erlang:function_exported(M, F, length(A)) of
- true ->
- Reason;
- false ->
- {'function not exported',[{M,F,A,L}|MFAs]}
- end
- end;
- _ ->
- Reason
- end,
- {ClientFmt,ClientArgs} = format_client_log(ClientInfo),
- {"** State machine ~tp terminating \n" ++
- get_msg_str(Msg) ++
- "** When State == ~tp~n"
- "** Data == ~tp~n"
- "** Reason for termination ==~n** ~tp~n" ++
+%% format_log/1 is the report callback used by Logger handler
+%% error_logger only. It is kept for backwards compatibility with
+%% legacy error_logger event handlers. This function must always
+%% return {Format,Args} compatible with the arguments in this module's
+%% calls to error_logger prior to OTP-21.0.
+format_log(Report) ->
+ Depth = error_logger:get_format_depth(),
+ FormatOpts = #{chars_limit => unlimited,
+ depth => Depth,
+ single_line => false,
+ encoding => utf8},
+ format_log_multi(limit_report(Report, Depth), FormatOpts).
+
+limit_report(Report, unlimited) ->
+ Report;
+limit_report(#{label:={gen_fsm,terminate},
+ last_message:=Msg,
+ state_data:=StateData,
+ log:=Log,
+ reason:=Reason,
+ client_info:=ClientInfo}=Report,
+ Depth) ->
+ Report#{last_message=>io_lib:limit_term(Msg, Depth),
+ state_data=>io_lib:limit_term(StateData, Depth),
+ log=>[io_lib:limit_term(L, Depth) || L <- Log],
+ reason=>io_lib:limit_term(Reason, Depth),
+ client_info=>limit_client_report(ClientInfo, Depth)};
+limit_report(#{label:={gen_fsm,no_handle_info},
+ message:=Msg}=Report, Depth) ->
+ Report#{message=>io_lib:limit_term(Msg, Depth)}.
+
+limit_client_report({From,{Name,Stacktrace}}, Depth) ->
+ {From,{Name,io_lib:limit_term(Stacktrace, Depth)}};
+limit_client_report(Client, _) ->
+ Client.
+
+%% format_log/2 is the report callback for any Logger handler, except
+%% error_logger.
+format_log(Report, FormatOpts0) ->
+ Default = #{chars_limit => unlimited,
+ depth => unlimited,
+ single_line => false,
+ encoding => utf8},
+ FormatOpts = maps:merge(Default, FormatOpts0),
+ IoOpts =
+ case FormatOpts of
+ #{chars_limit:=unlimited} ->
+ [];
+ #{chars_limit:=Limit} ->
+ [{chars_limit,Limit}]
+ end,
+ {Format,Args} = format_log_single(Report, FormatOpts),
+ io_lib:format(Format, Args, IoOpts).
+
+format_log_single(#{label:={gen_fsm,terminate},
+ name:=Name,
+ last_message:=Msg,
+ state_name:=StateName,
+ state_data:=StateData,
+ log:=Log,
+ reason:=Reason,
+ client_info:=ClientInfo},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ FixedReason = fix_reason(Reason),
+ {ClientFmt,ClientArgs} = format_client_log_single(ClientInfo, P, Depth),
+ Format =
+ lists:append(
+ ["State machine ",P," terminating. Reason: ",P,
+ ". Last event: ",P,
+ ". State: ",P,
+ ". Data: ",P,
+ case Log of
+ [] -> "";
+ _ -> ". Log: "++P
+ end,
+ "."]),
+ Args0 =
+ [Name,FixedReason,get_msg(Msg),StateName,StateData] ++
+ case Log of
+ [] -> [];
+ _ -> [Log]
+ end,
+ Args = case Depth of
+ unlimited ->
+ Args0;
+ _ ->
+ lists:flatmap(fun(A) -> [A, Depth] end, Args0)
+ end,
+ {Format++ClientFmt, Args++ClientArgs};
+format_log_single(#{label:={gen_fsm,no_handle_info},
+ module:=Mod,
+ message:=Msg},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format = lists:append(["Undefined handle_info in ",P,
+ ". Unhandled message: ",P,"."]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Mod,Msg];
+ _ ->
+ [Mod,Depth,Msg,Depth]
+ end,
+ {Format,Args};
+format_log_single(Report, FormatOpts) ->
+ format_log_multi(Report, FormatOpts).
+
+format_log_multi(#{label:={gen_fsm,terminate},
+ name:=Name,
+ last_message:=Msg,
+ state_name:=StateName,
+ state_data:=StateData,
+ log:=Log,
+ reason:=Reason,
+ client_info:=ClientInfo},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ FixedReason = fix_reason(Reason),
+ {ClientFmt,ClientArgs} = format_client_log(ClientInfo, P, Depth),
+ Format =
+ lists:append(
+ ["** State machine ",P," terminating \n"++
+ get_msg_str(Msg, P)++
+ "** When State == ",P,"~n",
+ "** Data == ",P,"~n",
+ "** Reason for termination ==~n** ",P,"~n",
+ case Log of
+ [] -> [];
+ _ -> "** Log ==~n**"++P++"~n"
+ end]),
+ Args0 =
+ [Name|get_msg(Msg)] ++
+ [StateName,StateData,FixedReason |
case Log of
[] -> [];
- _ -> "** Log ==~n** ~tp~n"
- end ++ ClientFmt,
- [Name|error_logger:limit_term(get_msg(Msg))] ++
- [StateName,
- error_logger:limit_term(StateData),
- error_logger:limit_term(Reason1) |
- case Log of
- [] -> [];
- _ -> [[error_logger:limit_term(D) || D <- Log]]
- end] ++ ClientArgs};
-format_log(#{label:={gen_fsm,no_handle_info},
- module:=Mod,
- message:=Msg}) ->
- {"** Undefined handle_info in ~p~n"
- "** Unhandled message: ~tp~n",
- [Mod, error_logger:limit_term(Msg)]}.
-
-get_msg_str({'$gen_event', _Event}) ->
- "** Last event in was ~tp~n";
-get_msg_str({'$gen_sync_event', _From, _Event}) ->
- "** Last sync event in was ~tp from ~tw~n";
-get_msg_str({'$gen_all_state_event', _Event}) ->
- "** Last event in was ~tp (for all states)~n";
-get_msg_str({'$gen_sync_all_state_event', _From, _Event}) ->
- "** Last sync event in was ~tp (for all states) from ~tw~n";
-get_msg_str({timeout, _Ref, {'$gen_timer', _Msg}}) ->
- "** Last timer event in was ~tp~n";
-get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) ->
- "** Last timer event in was ~tp~n";
-get_msg_str(_Msg) ->
- "** Last message in was ~tp~n".
+ _ -> [Log]
+ end],
+ Args = case Depth of
+ unlimited ->
+ Args0;
+ _ ->
+ lists:flatmap(fun(A) -> [A, Depth] end, Args0)
+ end,
+ {Format++ClientFmt,Args++ClientArgs};
+format_log_multi(#{label:={gen_fsm,no_handle_info},
+ module:=Mod,
+ message:=Msg},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ "** Undefined handle_info in ~p~n"
+ "** Unhandled message: "++P++"~n",
+ Args =
+ case Depth of
+ unlimited ->
+ [Mod,Msg];
+ _ ->
+ [Mod,Msg,Depth]
+ end,
+ {Format,Args}.
+
+fix_reason({undef,[{M,F,A,L}|MFAs]}=Reason) ->
+ case code:is_loaded(M) of
+ false ->
+ {'module could not be loaded',[{M,F,A,L}|MFAs]};
+ _ ->
+ case erlang:function_exported(M, F, length(A)) of
+ true ->
+ Reason;
+ false ->
+ {'function not exported',[{M,F,A,L}|MFAs]}
+ end
+ end;
+fix_reason(Reason) ->
+ Reason.
+
+get_msg_str({'$gen_event', _Event}, P) ->
+ "** Last event in was "++P++"~n";
+get_msg_str({'$gen_sync_event', _From, _Event}, P) ->
+ "** Last sync event in was "++P++" from ~tw~n";
+get_msg_str({'$gen_all_state_event', _Event}, P) ->
+ "** Last event in was "++P++" (for all states)~n";
+get_msg_str({'$gen_sync_all_state_event', _From, _Event}, P) ->
+ "** Last sync event in was "++P++" (for all states) from "++P++"~n";
+get_msg_str({timeout, _Ref, {'$gen_timer', _Msg}}, P) ->
+ "** Last timer event in was "++P++"~n";
+get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}, P) ->
+ "** Last timer event in was "++P++"~n";
+get_msg_str(_Msg, P) ->
+ "** Last message in was "++P++"~n".
get_msg({'$gen_event', Event}) -> [Event];
get_msg({'$gen_sync_event', {From,_Tag}, Event}) -> [Event,From];
@@ -728,16 +840,53 @@ get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> [{timeout, Ref, Msg}];
get_msg({timeout, _Ref, {'$gen_event', Event}}) -> [Event];
get_msg(Msg) -> [Msg].
-format_client_log(undefined) ->
+format_client_log_single(undefined, _, _) ->
+ {"", []};
+format_client_log_single({Pid,dead}, _, _) ->
+ {" Client ~0p is dead.", [Pid]};
+format_client_log_single({Pid,remote}, _, _) ->
+ {" Client ~0p is remote on node ~0p.", [Pid,node(Pid)]};
+format_client_log_single({_Pid,{Name,Stacktrace0}}, P, Depth) ->
+ %% Minimize the stacktrace a bit for single line reports. This is
+ %% hopefully enough to point out the position.
+ Stacktrace = lists:sublist(Stacktrace0, 4),
+ Format = lists:append([" Client ",P," stacktrace: ",P,"."]),
+ Args = case Depth of
+ unlimited ->
+ [Name, Stacktrace];
+ _ ->
+ [Name, Depth, Stacktrace, Depth]
+ end,
+ {Format, Args}.
+
+format_client_log(undefined, _, _) ->
{"", []};
-format_client_log({From,dead}) ->
- {"** Client ~p is dead~n", [From]};
-format_client_log({From,remote}) ->
- {"** Client ~p is remote on node ~p~n", [From, node(From)]};
-format_client_log({_From,{Name,Stacktrace}}) ->
- {"** Client ~tp stacktrace~n"
- "** ~tp~n",
- [Name, error_logger:limit_term(Stacktrace)]}.
+format_client_log({Pid,dead}, _, _) ->
+ {"** Client ~p is dead~n", [Pid]};
+format_client_log({Pid,remote}, _, _) ->
+ {"** Client ~p is remote on node ~p~n", [Pid,node(Pid)]};
+format_client_log({_Pid,{Name,Stacktrace}}, P, Depth) ->
+ Format = lists:append(["** Client ",P," stacktrace~n** ",P,"~n"]),
+ Args = case Depth of
+ unlimited ->
+ [Name, Stacktrace];
+ _ ->
+ [Name, Depth, Stacktrace, Depth]
+ end,
+ {Format,Args}.
+
+p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
+ "~"++single(Single)++mod(Enc)++p(Depth);
+p(unlimited) ->
+ "p";
+p(_Depth) ->
+ "P".
+
+single(true) -> "0";
+single(false) -> "".
+
+mod(latin1) -> "";
+mod(_) -> "t".
%%-----------------------------------------------------------------
%% Status information
diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl
index c7b6406f54..3638027d4f 100644
--- a/lib/stdlib/src/gen_server.erl
+++ b/lib/stdlib/src/gen_server.erl
@@ -19,6 +19,11 @@
%%
-module(gen_server).
+%%%
+%%% NOTE: If init_ack() return values are modified, see comment
+%%% above monitor_return() in gen.erl!
+%%%
+
%%% ---------------------------------------------------
%%%
%%% The idea behind THIS server is that the user module
@@ -89,8 +94,10 @@
%% API
-export([start/3, start/4,
start_link/3, start_link/4,
+ start_monitor/3, start_monitor/4,
stop/1, stop/3,
call/2, call/3,
+ send_request/2, wait_response/2, check_response/2,
cast/2, reply/2,
abcast/2, abcast/3,
multi_call/2, multi_call/3, multi_call/4,
@@ -105,7 +112,7 @@
format_status/2]).
%% logger callback
--export([format_log/1]).
+-export([format_log/1, format_log/2]).
%% Internal exports
-export([init_it/6]).
@@ -116,6 +123,16 @@
STACKTRACE(),
element(2, erlang:process_info(self(), current_stacktrace))).
+
+-type server_ref() ::
+ pid()
+ | (LocalName :: atom())
+ | {Name :: atom(), Node :: atom()}
+ | {'global', GlobalName :: term()}
+ | {'via', RegMod :: module(), ViaName :: term()}.
+
+-type request_id() :: term().
+
%%%=========================================================================
%%% API
%%%=========================================================================
@@ -188,6 +205,12 @@ start_link(Mod, Args, Options) ->
start_link(Name, Mod, Args, Options) ->
gen:start(?MODULE, link, Name, Mod, Args, Options).
+start_monitor(Mod, Args, Options) ->
+ gen:start(?MODULE, monitor, Mod, Args, Options).
+
+start_monitor(Name, Mod, Args, Options) ->
+ gen:start(?MODULE, monitor, Name, Mod, Args, Options).
+
%% -----------------------------------------------------------------
%% Stop a generic server and wait for it to terminate.
@@ -224,6 +247,25 @@ call(Name, Request, Timeout) ->
end.
%% -----------------------------------------------------------------
+%% Send a request to a generic server and return a Key which should be
+%% used with wait_response/2 or check_response/2 to fetch the
+%% result of the request.
+
+-spec send_request(Name::server_ref(), Request::term()) -> request_id().
+send_request(Name, Request) ->
+ gen:send_request(Name, '$gen_call', Request).
+
+-spec wait_response(RequestId::request_id(), timeout()) ->
+ {reply, Reply::term()} | 'timeout' | {error, {Reason::term(), server_ref()}}.
+wait_response(RequestId, Timeout) ->
+ gen:wait_response(RequestId, Timeout).
+
+-spec check_response(Msg::term(), RequestId::request_id()) ->
+ {reply, Reply::term()} | 'no_reply' | {error, {Reason::term(), server_ref()}}.
+check_response(Msg, RequestId) ->
+ gen:check_response(Msg, RequestId).
+
+%% -----------------------------------------------------------------
%% Make a cast to a generic server.
%% -----------------------------------------------------------------
cast({global,Name}, Request) ->
@@ -249,7 +291,8 @@ cast_msg(Request) -> {'$gen_cast',Request}.
%% Send a reply to the client.
%% -----------------------------------------------------------------
reply({To, Tag}, Reply) ->
- catch To ! {Tag, Reply}.
+ catch To ! {Tag, Reply},
+ ok.
%% -----------------------------------------------------------------
%% Asynchronous broadcast, returns nothing, it's just send 'n' pray
@@ -448,6 +491,15 @@ do_send(Dest, Msg) ->
end,
ok.
+do_multi_call([Node], Name, Req, infinity) when Node =:= node() ->
+ % Special case when multi_call is used with local node only.
+ % In that case we can leverage the benefit of recv_mark optimisation
+ % existing in simple gen:call.
+ try gen:call(Name, '$gen_call', Req, infinity) of
+ {ok, Res} -> {[{Node, Res}],[]}
+ catch exit:_ ->
+ {[], [Node]}
+ end;
do_multi_call(Nodes, Name, Req, infinity) ->
Tag = make_ref(),
Monitors = send_nodes(Nodes, Name, Tag, Req),
@@ -646,8 +698,10 @@ try_dispatch(Mod, Func, Msg, State) ->
module=>Mod,
message=>Msg},
#{domain=>[otp],
- report_cb=>fun gen_server:format_log/1,
- error_logger=>#{tag=>warning_msg}}),
+ report_cb=>fun gen_server:format_log/2,
+ error_logger=>
+ #{tag=>warning_msg,
+ report_cb=>fun gen_server:format_log/1}}),
{ok, {noreply, State}};
true ->
{'EXIT', error, R, Stacktrace}
@@ -894,8 +948,9 @@ error_info(Reason, Name, From, Msg, Mod, State, Debug) ->
reason=>Reason,
client_info=>client_stacktrace(From)},
#{domain=>[otp],
- report_cb=>fun gen_server:format_log/1,
- error_logger=>#{tag=>error}}),
+ report_cb=>fun gen_server:format_log/2,
+ error_logger=>#{tag=>error,
+ report_cb=>fun gen_server:format_log/1}}),
ok.
client_stacktrace(undefined) ->
@@ -914,63 +969,236 @@ client_stacktrace(From) when is_pid(From), node(From) =:= node() ->
client_stacktrace(From) when is_pid(From) ->
{From,remote}.
-format_log(#{label:={gen_server,terminate},
- name:=Name,
- last_message:=Msg,
- state:=State,
- log:=Log,
- reason:=Reason,
- client_info:=Client}) ->
- Reason1 =
- case Reason of
- {undef,[{M,F,A,L}|MFAs]} ->
- case code:is_loaded(M) of
- false ->
- {'module could not be loaded',[{M,F,A,L}|MFAs]};
- _ ->
- case erlang:function_exported(M, F, length(A)) of
- true ->
- Reason;
- false ->
- {'function not exported',[{M,F,A,L}|MFAs]}
- end
- end;
- _ ->
- Reason
- end,
- {ClientFmt,ClientArgs} = format_client_log(Client),
- [LimitedMsg,LimitedState,LimitedReason|LimitedLog] =
- [error_logger:limit_term(D) || D <- [Msg,State,Reason1|Log]],
- {"** Generic server ~tp terminating \n"
- "** Last message in was ~tp~n"
- "** When Server state == ~tp~n"
- "** Reason for termination ==~n** ~tp~n" ++
- case LimitedLog of
- [] -> [];
- _ -> "** Log ==~n** ~tp~n"
- end ++ ClientFmt,
- [Name, LimitedMsg, LimitedState, LimitedReason] ++
- case LimitedLog of
- [] -> [];
- _ -> [LimitedLog]
- end ++ ClientArgs};
-format_log(#{label:={gen_server,no_handle_info},
- module:=Mod,
- message:=Msg}) ->
- {"** Undefined handle_info in ~p~n"
- "** Unhandled message: ~tp~n",
- [Mod, error_logger:limit_term(Msg)]}.
-
-format_client_log(undefined) ->
+
+%% format_log/1 is the report callback used by Logger handler
+%% error_logger only. It is kept for backwards compatibility with
+%% legacy error_logger event handlers. This function must always
+%% return {Format,Args} compatible with the arguments in this module's
+%% calls to error_logger prior to OTP-21.0.
+format_log(Report) ->
+ Depth = error_logger:get_format_depth(),
+ FormatOpts = #{chars_limit => unlimited,
+ depth => Depth,
+ single_line => false,
+ encoding => utf8},
+ format_log_multi(limit_report(Report,Depth),FormatOpts).
+
+limit_report(Report,unlimited) ->
+ Report;
+limit_report(#{label:={gen_server,terminate},
+ last_message:=Msg,
+ state:=State,
+ log:=Log,
+ reason:=Reason,
+ client_info:=Client}=Report,
+ Depth) ->
+ Report#{last_message=>io_lib:limit_term(Msg,Depth),
+ state=>io_lib:limit_term(State,Depth),
+ log=>[io_lib:limit_term(L,Depth)||L<-Log],
+ reason=>io_lib:limit_term(Reason,Depth),
+ client_info=>limit_client_report(Client,Depth)};
+limit_report(#{label:={gen_server,no_handle_info},
+ message:=Msg}=Report,Depth) ->
+ Report#{message=>io_lib:limit_term(Msg,Depth)}.
+
+limit_client_report({From,{Name,Stacktrace}},Depth) ->
+ {From,{Name,io_lib:limit_term(Stacktrace,Depth)}};
+limit_client_report(Client,_) ->
+ Client.
+
+%% format_log/2 is the report callback for any Logger handler, except
+%% error_logger.
+format_log(Report, FormatOpts0) ->
+ Default = #{chars_limit => unlimited,
+ depth => unlimited,
+ single_line => false,
+ encoding => utf8},
+ FormatOpts = maps:merge(Default,FormatOpts0),
+ IoOpts =
+ case FormatOpts of
+ #{chars_limit:=unlimited} ->
+ [];
+ #{chars_limit:=Limit} ->
+ [{chars_limit,Limit}]
+ end,
+ {Format,Args} = format_log_single(Report, FormatOpts),
+ io_lib:format(Format, Args, IoOpts).
+
+format_log_single(#{label:={gen_server,terminate},
+ name:=Name,
+ last_message:=Msg,
+ state:=State,
+ log:=Log,
+ reason:=Reason,
+ client_info:=Client},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format1 = lists:append(["Generic server ",P," terminating. Reason: ",P,
+ ". Last message: ", P, ". State: ",P,"."]),
+ {ServerLogFormat,ServerLogArgs} = format_server_log_single(Log,FormatOpts),
+ {ClientLogFormat,ClientLogArgs} = format_client_log_single(Client,FormatOpts),
+
+ Args1 =
+ case Depth of
+ unlimited ->
+ [Name,fix_reason(Reason),Msg,State];
+ _ ->
+ [Name,Depth,fix_reason(Reason),Depth,Msg,Depth,State,Depth]
+ end,
+ {Format1++ServerLogFormat++ClientLogFormat,
+ Args1++ServerLogArgs++ClientLogArgs};
+format_log_single(#{label:={gen_server,no_handle_info},
+ module:=Mod,
+ message:=Msg},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format = lists:append(["Undefined handle_info in ",P,
+ ". Unhandled message: ",P,"."]),
+ Args =
+ case Depth of
+ unlimited ->
+ [Mod,Msg];
+ _ ->
+ [Mod,Depth,Msg,Depth]
+ end,
+ {Format,Args};
+format_log_single(Report,FormatOpts) ->
+ format_log_multi(Report,FormatOpts).
+
+format_log_multi(#{label:={gen_server,terminate},
+ name:=Name,
+ last_message:=Msg,
+ state:=State,
+ log:=Log,
+ reason:=Reason,
+ client_info:=Client},
+ #{depth:=Depth}=FormatOpts) ->
+ Reason1 = fix_reason(Reason),
+ {ClientFmt,ClientArgs} = format_client_log(Client,FormatOpts),
+ P = p(FormatOpts),
+ Format =
+ lists:append(
+ ["** Generic server ",P," terminating \n"
+ "** Last message in was ",P,"~n"
+ "** When Server state == ",P,"~n"
+ "** Reason for termination ==~n** ",P,"~n"] ++
+ case Log of
+ [] -> [];
+ _ -> ["** Log ==~n** ["|
+ lists:join(",~n ",lists:duplicate(length(Log),P))]++
+ ["]~n"]
+ end) ++ ClientFmt,
+ Args =
+ case Depth of
+ unlimited ->
+ [Name, Msg, State, Reason1] ++
+ case Log of
+ [] -> [];
+ _ -> Log
+ end ++ ClientArgs;
+ _ ->
+ [Name, Depth, Msg, Depth, State, Depth, Reason1, Depth] ++
+ case Log of
+ [] -> [];
+ _ -> lists:flatmap(fun(L) -> [L, Depth] end, Log)
+ end ++ ClientArgs
+ end,
+ {Format,Args};
+format_log_multi(#{label:={gen_server,no_handle_info},
+ module:=Mod,
+ message:=Msg},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ "** Undefined handle_info in ~p~n"
+ "** Unhandled message: "++P++"~n",
+ Args =
+ case Depth of
+ unlimited ->
+ [Mod,Msg];
+ _ ->
+ [Mod,Msg,Depth]
+ end,
+ {Format,Args}.
+
+fix_reason({undef,[{M,F,A,L}|MFAs]}=Reason) ->
+ case code:is_loaded(M) of
+ false ->
+ {'module could not be loaded',[{M,F,A,L}|MFAs]};
+ _ ->
+ case erlang:function_exported(M, F, length(A)) of
+ true ->
+ Reason;
+ false ->
+ {'function not exported',[{M,F,A,L}|MFAs]}
+ end
+ end;
+fix_reason(Reason) ->
+ Reason.
+
+format_server_log_single([],_) ->
+ {"",[]};
+format_server_log_single(Log,FormatOpts) ->
+ Args =
+ case maps:get(depth,FormatOpts) of
+ unlimited ->
+ [Log];
+ Depth ->
+ [Log, Depth]
+ end,
+ {" Log: "++p(FormatOpts),Args}.
+
+format_client_log_single(undefined,_) ->
+ {"",[]};
+format_client_log_single({From,dead},_) ->
+ {" Client ~0p is dead.",[From]};
+format_client_log_single({From,remote},_) ->
+ {" Client ~0p is remote on node ~0p.", [From, node(From)]};
+format_client_log_single({_From,{Name,Stacktrace0}},FormatOpts) ->
+ P = p(FormatOpts),
+ %% Minimize the stacktrace a bit for single line reports. This is
+ %% hopefully enough to point out the position.
+ Stacktrace = lists:sublist(Stacktrace0,4),
+ Args =
+ case maps:get(depth,FormatOpts) of
+ unlimited ->
+ [Name, Stacktrace];
+ Depth ->
+ [Name, Depth, Stacktrace, Depth]
+ end,
+ {" Client "++P++" stacktrace: "++P++".", Args}.
+
+format_client_log(undefined,_) ->
{"", []};
-format_client_log({From,dead}) ->
+format_client_log({From,dead},_) ->
{"** Client ~p is dead~n", [From]};
-format_client_log({From,remote}) ->
+format_client_log({From,remote},_) ->
{"** Client ~p is remote on node ~p~n", [From, node(From)]};
-format_client_log({_From,{Name,Stacktrace}}) ->
- {"** Client ~tp stacktrace~n"
- "** ~tp~n",
- [Name, error_logger:limit_term(Stacktrace)]}.
+format_client_log({_From,{Name,Stacktrace}},FormatOpts) ->
+ P = p(FormatOpts),
+ Format = lists:append(["** Client ",P," stacktrace~n",
+ "** ",P,"~n"]),
+ Args =
+ case maps:get(depth,FormatOpts) of
+ unlimited ->
+ [Name, Stacktrace];
+ Depth ->
+ [Name, Depth, Stacktrace, Depth]
+ end,
+ {Format,Args}.
+
+p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
+ "~"++single(Single)++mod(Enc)++p(Depth);
+p(unlimited) ->
+ "p";
+p(_Depth) ->
+ "P".
+
+single(true) -> "0";
+single(false) -> "".
+
+mod(latin1) -> "";
+mod(_) -> "t".
%%-----------------------------------------------------------------
%% Status information
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 2cf30e10ec..adf1a82a08 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -21,11 +21,18 @@
-include("logger.hrl").
+%%%
+%%% NOTE: If init_ack() return values are modified, see comment
+%%% above monitor_return() in gen.erl!
+%%%
+
%% API
-export(
[start/3,start/4,start_link/3,start_link/4,
+ start_monitor/3,start_monitor/4,
stop/1,stop/3,
cast/2,call/2,call/3,
+ send_request/2,wait_response/1,wait_response/2,check_response/2,
enter_loop/4,enter_loop/5,enter_loop/6,
reply/1,reply/2]).
@@ -47,18 +54,20 @@
[wakeup_from_hibernate/3]).
%% logger callback
--export([format_log/1]).
+-export([format_log/1, format_log/2]).
%% Type exports for templates and callback modules
-export_type(
[event_type/0,
+ from/0,
callback_mode_result/0,
init_result/1,
state_enter_result/1,
event_handler_result/1,
reply_action/0,
enter_action/0,
- action/0]).
+ action/0
+ ]).
%% Old types, not advertised
-export_type(
[state_function_result/0,
@@ -259,6 +268,7 @@
Replies :: [reply_action()] | reply_action(),
NewData :: data()}.
+-type request_id() :: term().
%% The state machine init function. It is called only once and
%% the server is not running until this function has returned
@@ -455,12 +465,16 @@ timeout_event_type(Type) ->
| {'via', RegMod :: module(), ViaName :: term()}.
-type start_opt() ::
{'timeout', Time :: timeout()}
- | {'spawn_opt', [proc_lib:spawn_option()]}
+ | {'spawn_opt', [proc_lib:start_spawn_option()]}
| enter_loop_opt().
-type start_ret() ::
{'ok', pid()}
| 'ignore'
| {'error', term()}.
+-type start_mon_ret() ::
+ {'ok', {pid(),reference()}}
+ | 'ignore'
+ | {'error', term()}.
-type enter_loop_opt() ::
{'hibernate_after', HibernateAfterTimeout :: timeout()}
| {'debug', Dbgs :: [sys:debug_option()]}.
@@ -495,6 +509,20 @@ start_link(Module, Args, Opts) ->
start_link(ServerName, Module, Args, Opts) ->
gen:start(?MODULE, link, ServerName, Module, Args, Opts).
+%% Start and monitor a state machine
+-spec start_monitor(
+ Module :: module(), Args :: term(), Opts :: [start_opt()]) ->
+ start_mon_ret().
+start_monitor(Module, Args, Opts) ->
+ gen:start(?MODULE, monitor, Module, Args, Opts).
+%%
+-spec start_monitor(
+ ServerName :: server_name(),
+ Module :: module(), Args :: term(), Opts :: [start_opt()]) ->
+ start_mon_ret().
+start_monitor(ServerName, Module, Args, Opts) ->
+ gen:start(?MODULE, monitor, ServerName, Module, Args, Opts).
+
%% Stop a state machine
-spec stop(ServerRef :: server_ref()) -> ok.
stop(ServerRef) ->
@@ -553,6 +581,26 @@ call(ServerRef, Request, {_, _} = Timeout) ->
call(ServerRef, Request, Timeout) ->
call_clean(ServerRef, Request, Timeout, Timeout).
+-spec send_request(ServerRef::server_ref(), Request::term()) ->
+ RequestId::request_id().
+send_request(Name, Request) ->
+ gen:send_request(Name, '$gen_call', Request).
+
+-spec wait_response(RequestId::request_id()) ->
+ {reply, Reply::term()} | {error, {term(), server_ref()}}.
+wait_response(RequestId) ->
+ gen:wait_response(RequestId, infinity).
+
+-spec wait_response(RequestId::request_id(), timeout()) ->
+ {reply, Reply::term()} | 'timeout' | {error, {term(), server_ref()}}.
+wait_response(RequestId, Timeout) ->
+ gen:wait_response(RequestId, Timeout).
+
+-spec check_response(Msg::term(), RequestId::request_id()) ->
+ {reply, Reply::term()} | 'no_reply' | {error, {term(), server_ref()}}.
+check_response(Msg, RequestId) ->
+ gen:check_response(Msg, RequestId).
+
%% Reply from a state machine callback to whom awaits in call/2
-spec reply([reply_action()] | reply_action()) -> ok.
reply({reply,From,Reply}) ->
@@ -2365,8 +2413,10 @@ error_info(
reason=>{Class,Reason,Stacktrace},
client_info=>client_stacktrace(Q)},
#{domain=>[otp],
- report_cb=>fun gen_statem:format_log/1,
- error_logger=>#{tag=>error}}).
+ report_cb=>fun gen_statem:format_log/2,
+ error_logger=>
+ #{tag=>error,
+ report_cb=>fun gen_statem:format_log/1}}).
client_stacktrace([]) ->
undefined;
@@ -2392,43 +2442,158 @@ client_stacktrace([_|_]) ->
undefined.
-format_log(#{label:={gen_statem,terminate},
- name:=Name,
- queue:=Q,
- postponed:=Postponed,
- modules:=Modules,
- callback_mode:=CallbackMode,
- state_enter:=StateEnter,
- state:=FmtData,
- timeouts:=Timeouts,
- log:=Log,
- reason:={Class,Reason,Stacktrace},
- client_info:=ClientInfo}) ->
- {FixedReason,FixedStacktrace} =
- case Stacktrace of
- [{M,F,Args,_}|ST]
- when Class =:= error, Reason =:= undef ->
- case code:is_loaded(M) of
- false ->
- {{'module could not be loaded',M},ST};
- _ ->
- Arity =
- if
- is_list(Args) ->
- length(Args);
- is_integer(Args) ->
- Args
- end,
- case erlang:function_exported(M, F, Arity) of
- true ->
- {Reason,Stacktrace};
- false ->
- {{'function not exported',{M,F,Arity}},ST}
- end
- end;
- _ -> {Reason,Stacktrace}
- end,
- {ClientFmt,ClientArgs} = format_client_log(ClientInfo),
+%% format_log/1 is the report callback used by Logger handler
+%% error_logger only. It is kept for backwards compatibility with
+%% legacy error_logger event handlers. This function must always
+%% return {Format,Args} compatible with the arguments in this module's
+%% calls to error_logger prior to OTP-21.0.
+format_log(Report) ->
+ Depth = error_logger:get_format_depth(),
+ FormatOpts = #{chars_limit => unlimited,
+ depth => Depth,
+ single_line => false,
+ encoding => utf8},
+ format_log_multi(limit_report(Report, Depth), FormatOpts).
+
+limit_report(Report, unlimited) ->
+ Report;
+limit_report(#{label:={gen_statem,terminate},
+ queue:=Q,
+ postponed:=Postponed,
+ modules:=Modules,
+ state:=FmtData,
+ timeouts:=Timeouts,
+ log:=Log,
+ reason:={Class,Reason,Stacktrace},
+ client_info:=ClientInfo}=Report,
+ Depth) ->
+ Report#{queue =>
+ case Q of
+ [Event|Events] ->
+ [io_lib:limit_term(Event, Depth)
+ |io_lib:limit_term(Events, Depth)];
+ _ -> []
+ end,
+ postponed =>
+ case Postponed of
+ [] -> [];
+ _ -> io_lib:limit_term(Postponed, Depth)
+ end,
+ modules => io_lib:limit_term(Modules, Depth),
+ state => io_lib:limit_term(FmtData, Depth),
+ timeouts =>
+ case Timeouts of
+ {0,_} -> Timeouts;
+ _ -> io_lib:limit_term(Timeouts, Depth)
+ end,
+ log =>
+ case Log of
+ [] -> [];
+ _ -> [io_lib:limit_term(T, Depth) || T <- Log]
+ end,
+ reason =>
+ {Class,
+ io_lib:limit_term(Reason, Depth),
+ io_lib:limit_term(Stacktrace, Depth)},
+ client_info => limit_client_info(ClientInfo, Depth)}.
+
+
+limit_client_info({Pid,{Name,Stacktrace}}, Depth) ->
+ {Pid,{Name,io_lib:limit_term(Stacktrace, Depth)}};
+limit_client_info(Client, _Depth) ->
+ Client.
+
+%% format_log/2 is the report callback for any Logger handler, except
+%% error_logger.
+format_log(Report, FormatOpts0) ->
+ Default = #{chars_limit => unlimited,
+ depth => unlimited,
+ single_line => false,
+ encoding => utf8},
+ FormatOpts = maps:merge(Default,FormatOpts0),
+ IoOpts =
+ case FormatOpts of
+ #{chars_limit:=unlimited} ->
+ [];
+ #{chars_limit:=Limit} ->
+ [{chars_limit,Limit}]
+ end,
+ {Format,Args} = format_log_single(Report, FormatOpts),
+ io_lib:format(Format, Args, IoOpts).
+
+format_log_single(#{label:={gen_statem,terminate},
+ name:=Name,
+ queue:=Q,
+ %% postponed
+ %% callback_mode
+ %% state_enter
+ state:=FmtData,
+ %% timeouts
+ log:=Log,
+ reason:={Class,Reason,Stacktrace},
+ client_info:=ClientInfo},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ {FixedReason,FixedStacktrace} = fix_reason(Class, Reason, Stacktrace),
+ {ClientFmt,ClientArgs} = format_client_log_single(ClientInfo, P, Depth),
+ Format =
+ lists:append(
+ ["State machine ",P," terminating. Reason: ",P,
+ case FixedStacktrace of
+ [] -> "";
+ _ -> ". Stack: "++P
+ end,
+ case Q of
+ [] -> "";
+ _ -> ". Last event: "++P
+ end,
+ ". State: ",P,
+ case Log of
+ [] -> "";
+ _ -> ". Log: "++P
+ end,
+ "."]),
+ Args0 =
+ [Name,FixedReason] ++
+ case FixedStacktrace of
+ [] -> [];
+ _ -> [FixedStacktrace]
+ end ++
+ case Q of
+ [] -> [];
+ [Event|_] -> [Event]
+ end ++
+ [FmtData] ++
+ case Log of
+ [] -> [];
+ _ -> [Log]
+ end,
+ Args = case Depth of
+ unlimited ->
+ Args0;
+ _ ->
+ lists:flatmap(fun(A) -> [A, Depth] end, Args0)
+ end,
+ {Format++ClientFmt, Args++ClientArgs};
+format_log_single(Report, FormatOpts) ->
+ format_log_multi(Report, FormatOpts).
+
+format_log_multi(#{label:={gen_statem,terminate},
+ name:=Name,
+ queue:=Q,
+ postponed:=Postponed,
+ modules:=Modules,
+ callback_mode:=CallbackMode,
+ state_enter:=StateEnter,
+ state:=FmtData,
+ timeouts:=Timeouts,
+ log:=Log,
+ reason:={Class,Reason,Stacktrace},
+ client_info:=ClientInfo},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ {FixedReason,FixedStacktrace} = fix_reason(Class, Reason, Stacktrace),
+ {ClientFmt,ClientArgs} = format_client_log(ClientInfo, P, Depth),
CBMode =
case StateEnter of
true ->
@@ -2436,76 +2601,147 @@ format_log(#{label:={gen_statem,terminate},
false ->
CallbackMode
end,
- {"** State machine ~tp terminating~n" ++
- case Q of
- [] -> "";
- _ -> "** Last event = ~tp~n"
- end ++
- "** When server state = ~tp~n" ++
- "** Reason for termination = ~w:~tp~n" ++
- "** Callback modules = ~p~n" ++
- "** Callback mode = ~p~n" ++
+ Format =
+ lists:append(
+ ["** State machine ",P," terminating~n",
+ case Q of
+ [] -> "";
+ _ -> "** Last event = "++P++"~n"
+ end,
+ "** When server state = ",P,"~n",
+ "** Reason for termination = ",P,":",P,"~n",
+ "** Callback modules = ",P,"~n",
+ "** Callback mode = ",P,"~n",
+ case Q of
+ [_,_|_] -> "** Queued = "++P++"~n";
+ _ -> ""
+ end,
+ case Postponed of
+ [] -> "";
+ _ -> "** Postponed = "++P++"~n"
+ end,
+ case FixedStacktrace of
+ [] -> "";
+ _ -> "** Stacktrace =~n** "++P++"~n"
+ end,
+ case Timeouts of
+ {0,_} -> "";
+ _ -> "** Time-outs: "++P++"~n"
+ end,
+ case Log of
+ [] -> "";
+ _ -> "** Log =~n** "++P++"~n"
+ end]),
+ Args0 =
+ [Name |
case Q of
- [_,_|_] -> "** Queued = ~tp~n";
- _ -> ""
- end ++
- case Postponed of
- [] -> "";
- _ -> "** Postponed = ~tp~n"
- end ++
- case FixedStacktrace of
- [] -> "";
- _ -> "** Stacktrace =~n** ~tp~n"
- end ++
- case Timeouts of
- {0,_} -> "";
- _ -> "** Time-outs: ~p~n"
- end ++
- case Log of
- [] -> "";
- _ -> "** Log =~n** ~tp~n"
- end ++ ClientFmt,
- [Name |
- case Q of
- [] -> [];
- [Event|_] -> [error_logger:limit_term(Event)]
- end] ++
- [error_logger:limit_term(FmtData),
- Class,error_logger:limit_term(FixedReason),
- error_logger:limit_term(Modules),
- CBMode] ++
- case Q of
- [_|[_|_] = Events] -> [error_logger:limit_term(Events)];
- _ -> []
- end ++
- case Postponed of
- [] -> [];
- _ -> [error_logger:limit_term(Postponed)]
- end ++
- case FixedStacktrace of
[] -> [];
- _ -> [error_logger:limit_term(FixedStacktrace)]
- end ++
- case Timeouts of
- {0,_} -> [];
- _ -> [error_logger:limit_term(Timeouts)]
- end ++
- case Log of
- [] -> [];
- _ -> [[error_logger:limit_term(T) || T <- Log]]
- end ++ ClientArgs}.
+ [Event|_] -> [Event]
+ end] ++
+ [FmtData,
+ Class,FixedReason,
+ Modules,
+ CBMode] ++
+ case Q of
+ [_|[_|_] = Events] -> [Events];
+ _ -> []
+ end ++
+ case Postponed of
+ [] -> [];
+ _ -> [Postponed]
+ end ++
+ case FixedStacktrace of
+ [] -> [];
+ _ -> [FixedStacktrace]
+ end ++
+ case Timeouts of
+ {0,_} -> [];
+ _ -> [Timeouts]
+ end ++
+ case Log of
+ [] -> [];
+ _ -> [Log]
+ end,
+ Args = case Depth of
+ unlimited ->
+ Args0;
+ _ ->
+ lists:flatmap(fun(A) -> [A, Depth] end, Args0)
+ end,
+ {Format++ClientFmt,Args++ClientArgs}.
+
+fix_reason(Class, Reason, Stacktrace) ->
+ case Stacktrace of
+ [{M,F,Args,_}|ST]
+ when Class =:= error, Reason =:= undef ->
+ case code:is_loaded(M) of
+ false ->
+ {{'module could not be loaded',M},ST};
+ _ ->
+ Arity =
+ if
+ is_list(Args) ->
+ length(Args);
+ is_integer(Args) ->
+ Args
+ end,
+ case erlang:function_exported(M, F, Arity) of
+ true ->
+ {Reason,Stacktrace};
+ false ->
+ {{'function not exported',{M,F,Arity}},ST}
+ end
+ end;
+ _ -> {Reason,Stacktrace}
+ end.
-format_client_log(undefined) ->
+format_client_log_single(undefined, _, _) ->
{"", []};
-format_client_log({Pid,dead}) ->
+format_client_log_single({Pid,dead}, _, _) ->
+ {" Client ~0p is dead.", [Pid]};
+format_client_log_single({Pid,remote}, _, _) ->
+ {" Client ~0p is remote on node ~0p.", [Pid,node(Pid)]};
+format_client_log_single({_Pid,{Name,Stacktrace0}}, P, Depth) ->
+ %% Minimize the stacktrace a bit for single line reports. This is
+ %% hopefully enough to point out the position.
+ Stacktrace = lists:sublist(Stacktrace0, 4),
+ Format = lists:append([" Client ",P," stacktrace: ",P,"."]),
+ Args = case Depth of
+ unlimited ->
+ [Name, Stacktrace];
+ _ ->
+ [Name, Depth, Stacktrace, Depth]
+ end,
+ {Format, Args}.
+
+format_client_log(undefined, _, _) ->
+ {"", []};
+format_client_log({Pid,dead}, _, _) ->
{"** Client ~p is dead~n", [Pid]};
-format_client_log({Pid,remote}) ->
- {"** Client ~p is remote on node ~p~n", [Pid, node(Pid)]};
-format_client_log({_Pid,{Name,Stacktrace}}) ->
- {"** Client ~tp stacktrace~n"
- "** ~tp~n",
- [Name, error_logger:limit_term(Stacktrace)]}.
-
+format_client_log({Pid,remote}, _, _) ->
+ {"** Client ~p is remote on node ~p~n", [Pid,node(Pid)]};
+format_client_log({_Pid,{Name,Stacktrace}}, P, Depth) ->
+ Format = lists:append(["** Client ",P," stacktrace~n** ",P,"~n"]),
+ Args = case Depth of
+ unlimited ->
+ [Name, Stacktrace];
+ _ ->
+ [Name, Depth, Stacktrace, Depth]
+ end,
+ {Format,Args}.
+
+p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
+ "~"++single(Single)++mod(Enc)++p(Depth);
+p(unlimited) ->
+ "p";
+p(_Depth) ->
+ "P".
+
+single(true) -> "0";
+single(false) -> "".
+
+mod(latin1) -> "";
+mod(_) -> "t".
%% Call Module:format_status/2 or return a default value
format_status(
diff --git a/lib/stdlib/src/io.erl b/lib/stdlib/src/io.erl
index 63c9a6bddf..1848aa3628 100644
--- a/lib/stdlib/src/io.erl
+++ b/lib/stdlib/src/io.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -106,7 +106,6 @@ nl() ->
IoDevice :: device().
nl(Io) ->
-% o_request(Io, {put_chars,io_lib:nl()}).
o_request(Io, nl, nl).
-spec columns() -> {'ok', pos_integer()} | {'error', 'enotsup'}.
@@ -255,8 +254,6 @@ read(Io, Prompt) ->
case request(Io, {get_until,unicode,Prompt,erl_scan,tokens,[1]}) of
{ok,Toks,_EndLine} ->
erl_parse:parse_term(Toks);
-% {error, Reason} when atom(Reason) ->
-% erlang:error(conv_reason(read, Reason), [Io, Prompt]);
{error,E,_EndLine} ->
{error,E};
{eof,_EndLine} ->
@@ -352,12 +349,7 @@ fread(Prompt, Format) ->
| server_no_data().
fread(Io, Prompt, Format) ->
- case request(Io, {fread,Prompt,Format}) of
-% {error, Reason} when atom(Reason) ->
-% erlang:error(conv_reason(fread, Reason), [Io, Prompt, Format]);
- Other ->
- Other
- end.
+ request(Io, {fread,Prompt,Format}).
-spec format(Format) -> 'ok' when
Format :: format().
diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
index 21d66c5529..e2823b70f2 100644
--- a/lib/stdlib/src/io_lib.erl
+++ b/lib/stdlib/src/io_lib.erl
@@ -78,7 +78,7 @@
%% Utilities for collecting characters.
-export([collect_chars/3, collect_chars/4,
- collect_line/2, collect_line/3, collect_line/4,
+ collect_line/3, collect_line/4,
get_until/3, get_until/4]).
%% The following functions were used by Yecc's include-file.
@@ -851,6 +851,7 @@ collect_chars({binary,Stack,N}, Data,latin1, _) ->
end;
collect_chars({list,Stack,N}, Data, _,_) ->
collect_chars_list(Stack, N, Data);
+
%% collect_chars(Continuation, MoreChars, Count)
%% Returns:
%% {done,Result,RestChars}
@@ -881,32 +882,6 @@ collect_chars_list(Stack, N, []) ->
collect_chars_list(Stack,N, [H|T]) ->
collect_chars_list([H|Stack], N-1, T).
-%% collect_line(Continuation, MoreChars)
-%% Returns:
-%% {done,Result,RestChars}
-%% {more,Continuation}
-%%
-%% XXX Can be removed when compatibility with pre-R12B-5 nodes
-%% is no longer required.
-%%
-collect_line([], Chars) ->
- collect_line1(Chars, []);
-collect_line({SoFar}, More) ->
- collect_line1(More, SoFar).
-
-collect_line1([$\r, $\n|Rest], Stack) ->
- collect_line1([$\n|Rest], Stack);
-collect_line1([$\n|Rest], Stack) ->
- {done,lists:reverse([$\n|Stack], []),Rest};
-collect_line1([C|Rest], Stack) ->
- collect_line1(Rest, [C|Stack]);
-collect_line1(eof, []) ->
- {done,eof,[]};
-collect_line1(eof, Stack) ->
- {done,lists:reverse(Stack, []),[]};
-collect_line1([], Stack) ->
- {more,{Stack}}.
-
%% collect_line(State, Data, _). New in R9C.
%% Returns:
%% {stop,Result,RestData}
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index 77f02eafe0..838d412d0c 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -895,9 +895,6 @@ write_string(S, _Uni) ->
io_lib:write_string(S, $"). %"
expand({_, _, _Dots=0, no_more} = If, _T, _Dd) -> If;
-%% expand({{list,L}, _Len, _, no_more}, T, Dd) ->
-%% {NL, NLen, NDots} = expand_list(L, T, Dd, 2),
-%% {{list,NL}, NLen, NDots, no_more};
expand({{tuple,IsTagged,L}, _Len, _, no_more}, T, Dd) ->
{NL, NLen, NDots} = expand_list(L, T, Dd, 2),
{{tuple,IsTagged,NL}, NLen, NDots, no_more};
diff --git a/lib/stdlib/src/lists.erl b/lib/stdlib/src/lists.erl
index 06c90c0280..f44ed726ca 100644
--- a/lib/stdlib/src/lists.erl
+++ b/lib/stdlib/src/lists.erl
@@ -341,8 +341,12 @@ max([], Max) -> Max.
Len :: non_neg_integer(),
T :: term().
-sublist(List, S, L) when is_integer(L), L >= 0 ->
- sublist(nthtail(S-1, List), L).
+sublist(List, 1, L) when is_list(List), is_integer(L), L >= 0 ->
+ sublist(List, L);
+sublist([], S, _L) when is_integer(S), S >= 2 ->
+ [];
+sublist([_H|T], S, L) when is_integer(S), S >= 2 ->
+ sublist(T, S-1, L).
-spec sublist(List1, Len) -> List2 when
List1 :: [T],
diff --git a/lib/stdlib/src/maps.erl b/lib/stdlib/src/maps.erl
index 51965ddb57..49d6a12eb2 100644
--- a/lib/stdlib/src/maps.erl
+++ b/lib/stdlib/src/maps.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -100,6 +100,7 @@ merge(_,_) -> erlang:nif_error(undef).
put(_,_,_) -> erlang:nif_error(undef).
+%% Shadowed by erl_bif_types: maps:remove/2
-spec remove(Key,Map1) -> Map2 when
Key :: term(),
Map1 :: map(),
diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl
index 2024be53bd..1cc11d9093 100644
--- a/lib/stdlib/src/otp_internal.erl
+++ b/lib/stdlib/src/otp_internal.erl
@@ -1,574 +1,520 @@
%%
-%% %CopyrightBegin%
+%% WARNING: DO NOT EDIT THIS FILE.
%%
-%% Copyright Ericsson AB 1999-2020. All Rights Reserved.
+%% This file was auto-generated from attributes in the source
+%% code.
%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
+%% To add a description to a deprecation or removal attribute,
+%% write a string after the arity:
%%
-%% http://www.apache.org/licenses/LICENSE-2.0
+%% -deprecated([{foo,1,"use bar/1 instead"}]).
+%% -deprecated_type([{gadget,1,"use widget/1 instead"}]).
+%% -removed([{hello,2,"use there/2 instead"}]).
+%% -removed_type([{frobnitz,1,"use grunka/1 instead"}]).
%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
+%% Descriptions cannot be given with the `f/1` shorthand, and
+%% it will fall back to a generic description referring the
+%% user to the documentation.
%%
-%% %CopyrightEnd%
+%% Use `./otp_build update_deprecations` to update this file
+%% after adding an attribute.
%%
-module(otp_internal).
-
--export([obsolete/3, obsolete_type/3]).
-
-%%----------------------------------------------------------------------
-
+-include("otp_internal.hrl").
+%%
-dialyzer({no_match, obsolete/3}).
-
--type tag() :: 'deprecated' | 'removed'. %% | 'experimental'.
--type mfas() :: mfa() | {atom(), atom(), [byte()]}.
--type release() :: string().
-
--spec obsolete(module(), atom(), arity()) ->
- 'no' | {tag(), string()} | {tag(), mfas(), release()}.
-
-obsolete(Module, Name, Arity) ->
- case obsolete_1(Module, Name, Arity) of
- {deprecated=Tag,{_,_,_}=Replacement} ->
- {Tag,Replacement,"a future release"};
- {_,String}=Ret when is_list(String) ->
- Ret;
- {_,_,_}=Ret ->
- Ret;
- no ->
- no
- end.
-
-obsolete_1(net, call, 4) ->
- {deprecated, {rpc, call, 4}};
-obsolete_1(net, cast, 4) ->
- {deprecated, {rpc, cast, 4}};
-obsolete_1(net, broadcast, 3) ->
- {deprecated, {rpc, eval_everywhere, 3}};
-obsolete_1(net, ping, 1) ->
- {deprecated, {net_adm, ping, 1}};
-obsolete_1(net, sleep, 1) ->
- {deprecated, "Use 'receive after T -> ok end' instead"};
-obsolete_1(net, relay, 1) ->
- {deprecated, {slave, relay, 1}};
-
-
-obsolete_1(erlang, now, 0) ->
- {deprecated,
- "Deprecated BIF. See the \"Time and Time Correction in Erlang\" "
- "chapter of the ERTS User's Guide for more information."};
-
-obsolete_1(calendar, local_time_to_universal_time, 1) ->
- {deprecated, {calendar, local_time_to_universal_time_dst, 1}};
-
-%% *** STDLIB added in OTP 22 ***
-
-obsolete_1(sys, get_debug, 3) ->
- {deprecated,
- "Deprecated function. "
- "Incorrectly documented and in fact only for internal use. "
- "Can often be replaced with sys:get_log/1."};
-
-%% *** STDLIB added in OTP 20 ***
-
-obsolete_1(gen_fsm, start, 3) ->
- {deprecated, {gen_statem, start, 3}};
-obsolete_1(gen_fsm, start, 4) ->
- {deprecated, {gen_statem, start, 4}};
-
-obsolete_1(gen_fsm, start_link, 3) ->
- {deprecated, {gen_statem, start_link, 3}};
-obsolete_1(gen_fsm, start_link, 4) ->
- {deprecated, {gen_statem, start_link, 4}};
-
-obsolete_1(gen_fsm, stop, 1) ->
- {deprecated, {gen_statem, stop, 1}};
-obsolete_1(gen_fsm, stop, 3) ->
- {deprecated, {gen_statem, stop, 3}};
-
-obsolete_1(gen_fsm, enter_loop, 4) ->
- {deprecated, {gen_statem, enter_loop, 4}};
-obsolete_1(gen_fsm, enter_loop, 5) ->
- {deprecated, {gen_statem, enter_loop, 5}};
-obsolete_1(gen_fsm, enter_loop, 6) ->
- {deprecated, {gen_statem, enter_loop, 6}};
-
-obsolete_1(gen_fsm, reply, 2) ->
- {deprecated, {gen_statem, reply, 2}};
-
-obsolete_1(gen_fsm, send_event, 2) ->
- {deprecated, {gen_statem, cast, 2}};
-obsolete_1(gen_fsm, send_all_state_event, 2) ->
- {deprecated, {gen_statem, cast, 2}};
-
-obsolete_1(gen_fsm, sync_send_event, 2) ->
- {deprecated, {gen_statem, call, 2}};
-obsolete_1(gen_fsm, sync_send_event, 3) ->
- {deprecated, {gen_statem, call, 3}};
-
-obsolete_1(gen_fsm, sync_send_all_state_event, 2) ->
- {deprecated, {gen_statem, call, 2}};
-obsolete_1(gen_fsm, sync_send_all_state_event, 3) ->
- {deprecated, {gen_statem, call, 3}};
-
-obsolete_1(gen_fsm, start_timer, 2) ->
- {deprecated, {erlang, start_timer, 3}};
-obsolete_1(gen_fsm, cancel_timer, 1) ->
- {deprecated, {erlang, cancel_timer, 1}};
-obsolete_1(gen_fsm, send_event_after, 2) ->
- {deprecated, {erlang, send_after, 3}};
-
-%% *** CRYPTO added in OTP 20 ***
-
-obsolete_1(crypto, rand_uniform, 2) ->
- {deprecated, {rand, uniform, 1}};
-
-%% *** CRYPTO added in OTP 19 ***
-
-obsolete_1(crypto, rand_bytes, 1) ->
- {removed, {crypto, strong_rand_bytes, 1}, "20.0"};
-
-%% *** CRYPTO added in R16B01 ***
-
-obsolete_1(crypto, md4, 1) ->
- {removed, {crypto, hash, 2}, "20.0"};
-obsolete_1(crypto, md5, 1) ->
- {removed, {crypto, hash, 2}, "20.0"};
-obsolete_1(crypto, sha, 1) ->
- {removed, {crypto, hash, 2}, "20.0"};
-
-obsolete_1(crypto, md4_init, 0) ->
- {removed, {crypto, hash_init, 1}, "20.0"};
-obsolete_1(crypto, md5_init, 0) ->
- {removed, {crypto, hash_init, 1}, "20.0"};
-obsolete_1(crypto, sha_init, 0) ->
- {removed, {crypto, hash_init, 1}, "20.0"};
-
-obsolete_1(crypto, md4_update, 2) ->
- {removed, {crypto, hash_update, 2}, "20.0"};
-obsolete_1(crypto, md5_update, 2) ->
- {removed, {crypto, hash_update, 2}, "20.0"};
-obsolete_1(crypto, sha_update, 2) ->
- {removed, {crypto, hash_update, 2}, "20.0"};
-
-obsolete_1(crypto, md4_final, 1) ->
- {removed, {crypto, hash_final, 1}, "20.0"};
-obsolete_1(crypto, md5_final, 1) ->
- {removed, {crypto, hash_final, 1}, "20.0"};
-obsolete_1(crypto, sha_final, 1) ->
- {removed, {crypto, hash_final, 1}, "20.0"};
-
-obsolete_1(crypto, md5_mac, 2) ->
- {removed, {crypto, hmac, 3}, "20.0"};
-obsolete_1(crypto, sha_mac, 2) ->
- {removed, {crypto, hmac, 3}, "20.0"};
-obsolete_1(crypto, sha_mac, 3) ->
- {removed, {crypto, hmac, 4}, "20.0"};
-
-obsolete_1(crypto, sha_mac_96, 2) ->
- {removed, {crypto, hmac, 4}, "20.0"};
-obsolete_1(crypto, md5_mac_96, 2) ->
- {removed, {crypto, hmac, 4}, "20.0"};
-
-obsolete_1(crypto, rsa_sign, 2) ->
- {removed, {crypto, sign, 4}, "20.0"};
-obsolete_1(crypto, rsa_sign, 3) ->
- {removed, {crypto, sign, 4}, "20.0"};
-obsolete_1(crypto, rsa_verify, 3) ->
- {removed, {crypto, verify, 5}, "20.0"};
-obsolete_1(crypto, rsa_verify, 4) ->
- {removed, {crypto, verify, 5}, "20.0"};
-
-obsolete_1(crypto, dss_sign, 2) ->
- {removed, {crypto, sign, 4}, "20.0"};
-obsolete_1(crypto, dss_sign, 3) ->
- {removed, {crypto, sign, 4}, "20.0"};
-
-obsolete_1(crypto, dss_verify, 3) ->
- {removed, {crypto, verify, 5}, "20.0"};
-obsolete_1(crypto, dss_verify, 4) ->
- {removed, {crypto, verify, 5}, "20.0"};
-
-obsolete_1(crypto, mod_exp, 3) ->
- {removed, {crypto, mod_pow, 3}, "20.0"};
-
-obsolete_1(crypto, dh_compute_key, 3) ->
- {removed, {crypto, compute_key, 4}, "20.0"};
-obsolete_1(crypto, dh_generate_key, 1) ->
- {removed, {crypto, generate_key, 2}, "20.0"};
-obsolete_1(crypto, dh_generate_key, 2) ->
- {removed, {crypto, generate_key, 3}, "20.0"};
-
-obsolete_1(crypto, des_cbc_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, des3_cbc_encrypt, 5) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, des_ecb_encrypt, 2) ->
- {removed, {crypto, block_encrypt, 3}, "20.0"};
-obsolete_1(crypto, des_ede3_cbc_encrypt, 5) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, des_cfb_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, des3_cfb_encrypt, 5) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, blowfish_ecb_encrypt, 2) ->
- {removed, {crypto, block_encrypt, 3}, "20.0"};
-obsolete_1(crypto, blowfish_cbc_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, blowfish_cfb64_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, blowfish_ofb64_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, aes_cfb_128_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, aes_cbc_128_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto, aes_cbc_256_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto,rc2_cbc_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-obsolete_1(crypto,rc2_40_cbc_encrypt, 3) ->
- {removed, {crypto, block_encrypt, 4}, "20.0"};
-
-obsolete_1(crypto, des_cbc_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, des3_cbc_decrypt, 5) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, des_ecb_decrypt, 2) ->
- {removed, {crypto, block_decrypt, 3}, "20.0"};
-obsolete_1(crypto, des_ede3_cbc_decrypt, 5) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, des_cfb_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, des3_cfb_decrypt, 5) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, blowfish_ecb_decrypt, 2) ->
- {removed, {crypto, block_decrypt, 3}, "20.0"};
-obsolete_1(crypto, blowfish_cbc_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, blowfish_cfb64_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, blowfish_ofb64_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, aes_cfb_128_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, aes_cbc_128_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto, aes_cbc_256_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto,rc2_cbc_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-obsolete_1(crypto,rc2_40_cbc_decrypt, 3) ->
- {removed, {crypto, block_decrypt, 4}, "20.0"};
-
-obsolete_1(crypto, aes_ctr_stream_decrypt, 2) ->
- {removed, {crypto, stream_decrypt, 2}, "20.0"};
-obsolete_1(crypto, aes_ctr_stream_encrypt, 2) ->
- {removed, {crypto, stream_encrypt, 2}, "20.0"};
-obsolete_1(crypto, aes_ctr_decrypt, 3) ->
- {removed, {crypto, stream_decrypt, 2}, "20.0"};
-obsolete_1(crypto, aes_ctr_encrypt, 3) ->
- {removed, {crypto, stream_encrypt, 2}, "20.0"};
-obsolete_1(crypto, rc4_encrypt, 2) ->
- {removed, {crypto, stream_encrypt, 2}, "20.0"};
-obsolete_1(crypto, rc4_encrypt_with_state, 2) ->
- {removed, {crypto, stream_encrypt, 2}, "20.0"};
-obsolete_1(crypto, aes_ctr_stream_init, 2) ->
- {removed, {crypto, stream_init, 3}, "20.0"};
-obsolete_1(crypto, rc4_set_key, 1) ->
- {removed, {crypto, stream_init, 2}, "20.0"};
-
-obsolete_1(crypto, rsa_private_decrypt, 3) ->
- {removed, {crypto, private_decrypt, 4}, "20.0"};
-obsolete_1(crypto, rsa_public_decrypt, 3) ->
- {removed, {crypto, public_decrypt, 4}, "20.0"};
-obsolete_1(crypto, rsa_private_encrypt, 3) ->
- {removed, {crypto, private_encrypt, 4}, "20.0"};
-obsolete_1(crypto, rsa_public_encrypt, 3) ->
- {removed, {crypto, public_encrypt, 4}, "20.0"};
-
-obsolete_1(crypto, des_cfb_ivec, 2) ->
- {removed, {crypto, next_iv, 3}, "20.0"};
-obsolete_1(crypto,des_cbc_ivec, 1) ->
- {removed, {crypto, next_iv, 2}, "20.0"};
-obsolete_1(crypto, aes_cbc_ivec, 1) ->
- {removed, {crypto, next_iv, 2}, "20.0"};
-
-obsolete_1(crypto,info, 0) ->
- {removed, {crypto, module_info, 0}, "20.0"};
-
-obsolete_1(crypto, strong_rand_mpint, 3) ->
- {removed, "removed in 20.0; only needed by removed functions"};
-obsolete_1(crypto, erlint, 1) ->
- {removed, "removed in 20.0; only needed by removed functions"};
-obsolete_1(crypto, mpint, 1) ->
- {removed, "removed in 20.0; only needed by removed functions"};
-
-
-%% *** SNMP ***
-
-obsolete_1(snmp, N, A) ->
- case is_snmp_agent_function(N, A) of
- false ->
- no;
- true ->
- {deprecated, "Deprecated (will be removed in OTP 18); use snmpa:"++atom_to_list(N)++"/"++
- integer_to_list(A)++" instead"}
- end;
-
-obsolete_1(snmpa, old_info_format, 1) ->
- {deprecated, "Deprecated; (will be removed in OTP 18); use \"new\" format instead"};
-
-
-%% *** MEGACO ***
-
-obsolete_1(megaco, format_versions, 1) ->
- {deprecated, "Deprecated; use megaco:print_version_info/0,1 instead"};
-
-
-%% *** OS-MON-MIB ***
-
-%% FIXME: Remove this warning in OTP 24.
-obsolete_1(os_mon_mib, _, _) ->
- {removed, "was removed in 22.0"};
-
-obsolete_1(auth, is_auth, 1) ->
- {deprecated, {net_adm, ping, 1}};
-obsolete_1(auth, cookie, 0) ->
- {deprecated, {erlang, get_cookie, 0}};
-obsolete_1(auth, cookie, 1) ->
- {deprecated, {erlang, set_cookie, 2}};
-obsolete_1(auth, node_cookie, 1) ->
- {deprecated, "Deprecated; use erlang:set_cookie/2 and net_adm:ping/1 instead"};
-obsolete_1(auth, node_cookie, 2) ->
- {deprecated, "Deprecated; use erlang:set_cookie/2 and net_adm:ping/1 instead"};
-
-%% Added in R16
-obsolete_1(wxCalendarCtrl, enableYearChange, _) -> %% wx bug documented?
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxDC, computeScaleAndOrigin, 1) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxClientDC, new, 0) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxPaintDC, new, 0) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxWindowDC, new, 0) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxGraphicsRenderer, createLinearGradientBrush, 7) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxGraphicsRenderer, createRadialGradientBrush, 8) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxGridCellEditor, endEdit, 4) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxGridCellEditor, paintBackground, 3) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxIdleEvent, canSend, 1) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxMDIClientWindow, new, 1) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxMDIClientWindow, new, 2) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxPostScriptDC, getResolution, 0) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxPostScriptDC, setResolution, 1) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxCursor, new, 3) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-obsolete_1(wxCursor, new, 4) ->
- {deprecated,"deprecated function not available in wxWidgets-2.9 and later"};
-
-%% Added in OTP 17.
-obsolete_1(asn1ct, decode,3) ->
- {removed,"removed; use Mod:decode/2 instead"};
-obsolete_1(asn1ct, encode, 2) ->
- {removed,"removed; use Mod:encode/2 instead"};
-obsolete_1(asn1ct, encode, 3) ->
- {removed,"removed; use Mod:encode/2 instead"};
-obsolete_1(asn1rt, decode,3) ->
- {removed,"removed; use Mod:decode/2 instead"};
-obsolete_1(asn1rt, encode, 2) ->
- {removed,"removed; use Mod:encode/2 instead"};
-obsolete_1(asn1rt, encode, 3) ->
- {removed,"removed; use Mod:encode/2 instead"};
-obsolete_1(asn1rt, info, 1) ->
- {removed,"removed; use Mod:info/0 instead"};
-obsolete_1(asn1rt, utf8_binary_to_list, 1) ->
- {removed,{unicode,characters_to_list,1},"OTP 20"};
-obsolete_1(asn1rt, utf8_list_to_binary, 1) ->
- {removed,{unicode,characters_to_binary,1},"OTP 20"};
-
-%% Added in OTP 18.
-obsolete_1(core_lib, get_anno, 1) ->
- {removed,{cerl,get_ann,1},"19"};
-obsolete_1(core_lib, set_anno, 2) ->
- {removed,{cerl,set_ann,2},"19"};
-obsolete_1(core_lib, is_literal, 1) ->
- {removed,{cerl,is_literal,1},"19"};
-obsolete_1(core_lib, is_literal_list, 1) ->
- {removed,"removed; use lists:all(fun cerl:is_literal/1, L)"
- " instead"};
-obsolete_1(core_lib, literal_value, 1) ->
- {removed,{core_lib,concrete,1},"19"};
-obsolete_1(erl_scan, set_attribute, 3) ->
- {removed,{erl_anno,set_line,2},"19.0"};
-obsolete_1(erl_scan, attributes_info, 1) ->
- {removed,"removed in 19.0; use "
- "erl_anno:{column,line,location,text}/1 instead"};
-obsolete_1(erl_scan, attributes_info, 2) ->
- {removed,"removed in 19.0; use "
- "erl_anno:{column,line,location,text}/1 instead"};
-obsolete_1(erl_scan, token_info, 1) ->
- {removed,"removed in 19.0; use "
- "erl_scan:{category,column,line,location,symbol,text}/1 instead"};
-obsolete_1(erl_scan, token_info, 2) ->
- {removed,"removed in 19.0; use "
- "erl_scan:{category,column,line,location,symbol,text}/1 instead"};
-obsolete_1(erl_parse, set_line, 2) ->
- {removed,{erl_anno,set_line,2},"19.0"};
-obsolete_1(erl_parse, get_attributes, 1) ->
- {removed,"removed in 19.0; use "
- "erl_anno:{column,line,location,text}/1 instead"};
-obsolete_1(erl_parse, get_attribute, 2) ->
- {removed,"removed in 19.0; use "
- "erl_anno:{column,line,location,text}/1 instead"};
-obsolete_1(erl_lint, modify_line, 2) ->
- {removed,{erl_parse,map_anno,2},"19.0"};
-obsolete_1(ssl, negotiated_next_protocol, 1) ->
- {removed,"removed in 20.0; use ssl:negotiated_protocol/1 instead"};
-obsolete_1(ssl, connection_info, 1) ->
- {removed, "removed in 20.0; use ssl:connection_information/[1,2] instead"};
-
-obsolete_1(httpd_conf, check_enum, 2) ->
- {deprecated, "deprecated; use lists:member/2 instead"};
-obsolete_1(httpd_conf, clean, 1) ->
- {deprecated, "deprecated; use sting:strip/1 instead or possible the re module"};
-obsolete_1(httpd_conf, custom_clean, 3) ->
- {deprecated, "deprecated; use sting:strip/3 instead or possible the re module"};
-obsolete_1(httpd_conf, is_directory, 1) ->
- {deprecated, "deprecated; use filelib:is_dir/1 instead"};
-obsolete_1(httpd_conf, is_file, 1) ->
- {deprecated, "deprecated; use filelib:is_file/1 instead"};
-obsolete_1(httpd_conf, make_integer, 1) ->
- {deprecated, "deprecated; use erlang:list_to_integer/1 instead"};
-
-%% Added in OTP 19.
-
-obsolete_1(random, _, _) ->
- {deprecated, "the 'random' module is deprecated; "
- "use the 'rand' module instead"};
-obsolete_1(code, rehash, 0) ->
- {deprecated, "deprecated because the code path cache feature has been removed"};
-obsolete_1(queue, lait, 1) ->
- {deprecated, {queue,liat,1}};
-
-%% Removed in OTP 19.
-
-obsolete_1(rpc, safe_multi_server_call, A) when A =:= 2; A =:= 3 ->
- {removed, {rpc, multi_server_call, A}, "19.0"};
-
-%% Added in OTP 20.
-
-obsolete_1(filename, find_src, 1) ->
- {deprecated, "deprecated; use filelib:find_source/1 instead"};
-obsolete_1(filename, find_src, 2) ->
- {deprecated, "deprecated; use filelib:find_source/3 instead"};
-
-obsolete_1(erlang, get_stacktrace, 0) ->
- {deprecated, "deprecated; use the new try/catch syntax for retrieving the stack backtrace"};
-
-%% Removed in OTP 20.
-
-obsolete_1(erlang, hash, 2) ->
- {removed, {erlang, phash2, 2}, "20.0"};
-
-%% Add in OTP 21.
-
-obsolete_1(ssl, ssl_accept, 1) ->
- {deprecated, "deprecated; use ssl:handshake/1 instead"};
-obsolete_1(ssl, ssl_accept, 2) ->
- {deprecated, "deprecated; use ssl:handshake/2 instead"};
-obsolete_1(ssl, ssl_accept, 3) ->
- {deprecated, "deprecated; use ssl:handshake/3 instead"};
-obsolete_1(otp_mib, F, _) when F =:= load; F =:= unload ->
- {deprecated, "deprecated; functionality will be removed in a future release"};
-
-%% not obsolete
-
-obsolete_1(_, _, _) ->
- no.
-
--spec is_snmp_agent_function(atom(), byte()) -> boolean().
-
-is_snmp_agent_function(c, 1) -> true;
-is_snmp_agent_function(c, 2) -> true;
-is_snmp_agent_function(compile, 3) -> true;
-is_snmp_agent_function(is_consistent, 1) -> true;
-is_snmp_agent_function(mib_to_hrl, 1) -> true;
-is_snmp_agent_function(change_log_size, 1) -> true;
-is_snmp_agent_function(log_to_txt, 2) -> true;
-is_snmp_agent_function(log_to_txt, 3) -> true;
-is_snmp_agent_function(log_to_txt, 4) -> true;
-is_snmp_agent_function(current_request_id, 0) -> true;
-is_snmp_agent_function(current_community, 0) -> true;
-is_snmp_agent_function(current_address, 0) -> true;
-is_snmp_agent_function(current_context, 0) -> true;
-is_snmp_agent_function(current_net_if_data, 0) -> true;
-is_snmp_agent_function(get_symbolic_store_db, 0) -> true;
-is_snmp_agent_function(name_to_oid, 1) -> true;
-is_snmp_agent_function(name_to_oid, 2) -> true;
-is_snmp_agent_function(oid_to_name, 1) -> true;
-is_snmp_agent_function(oid_to_name, 2) -> true;
-is_snmp_agent_function(int_to_enum, 2) -> true;
-is_snmp_agent_function(int_to_enum, 3) -> true;
-is_snmp_agent_function(enum_to_int, 2) -> true;
-is_snmp_agent_function(enum_to_int, 3) -> true;
-is_snmp_agent_function(get, 2) -> true;
-is_snmp_agent_function(info, 1) -> true;
-is_snmp_agent_function(load_mibs, 2) -> true;
-is_snmp_agent_function(unload_mibs, 2) -> true;
-is_snmp_agent_function(dump_mibs, 0) -> true;
-is_snmp_agent_function(dump_mibs, 1) -> true;
-is_snmp_agent_function(register_subagent, 3) -> true;
-is_snmp_agent_function(unregister_subagent, 2) -> true;
-is_snmp_agent_function(send_notification, 3) -> true;
-is_snmp_agent_function(send_notification, 4) -> true;
-is_snmp_agent_function(send_notification, 5) -> true;
-is_snmp_agent_function(send_notification, 6) -> true;
-is_snmp_agent_function(send_trap, 3) -> true;
-is_snmp_agent_function(send_trap, 4) -> true;
-is_snmp_agent_function(add_agent_caps, 2) -> true;
-is_snmp_agent_function(del_agent_caps, 1) -> true;
-is_snmp_agent_function(get_agent_caps, 0) -> true;
-is_snmp_agent_function(_, _) -> false.
-
--dialyzer({no_match, obsolete_type/3}).
-
--spec obsolete_type(module(), atom(), arity()) ->
- 'no' | {tag(), string()} | {tag(), mfas(), release()}.
+obsolete(auth, cookie, 0) ->
+ {deprecated, "use erlang:get_cookie/0 instead"};
+obsolete(auth, cookie, 1) ->
+ {deprecated, "use erlang:set_cookie/2 instead"};
+obsolete(auth, is_auth, 1) ->
+ {deprecated, "use net_adm:ping/1 instead"};
+obsolete(calendar, local_time_to_universal_time, 1) ->
+ {deprecated, "use calendar:local_time_to_universal_time_dst/1 instead"};
+obsolete(code, rehash, 0) ->
+ {deprecated, "the code path cache feature has been removed"};
+obsolete(crypto, block_decrypt, 3) ->
+ {deprecated, "use crypto:crypto_one_time/4 or crypto:crypto_init/3 + crypto:crypto_update/2 + crypto:crypto_final/1 instead", "OTP 24"};
+obsolete(crypto, block_decrypt, 4) ->
+ {deprecated, "use crypto:crypto_one_time/5, crypto:crypto_one_time_aead/6,7 or crypto:crypto_(dyn_iv)?_init + crypto:crypto_(dyn_iv)?_update + crypto:crypto_final instead", "OTP 24"};
+obsolete(crypto, block_encrypt, 3) ->
+ {deprecated, "use crypto:crypto_one_time/4 or crypto:crypto_init/3 + crypto:crypto_update/2 + crypto:crypto_final/1 instead", "OTP 24"};
+obsolete(crypto, block_encrypt, 4) ->
+ {deprecated, "use crypto:crypto_one_time/5, crypto:crypto_one_time_aead/6,7 or crypto:crypto_(dyn_iv)?_init + crypto:crypto_(dyn_iv)?_update + crypto:crypto_final instead", "OTP 24"};
+obsolete(crypto, cmac, 3) ->
+ {deprecated, "use crypto:mac/4 instead", "OTP 24"};
+obsolete(crypto, cmac, 4) ->
+ {deprecated, "use crypto:macN/5 instead", "OTP 24"};
+obsolete(crypto, hmac, 3) ->
+ {deprecated, "use crypto:mac/4 instead", "OTP 24"};
+obsolete(crypto, hmac, 4) ->
+ {deprecated, "use crypto:macN/5 instead", "OTP 24"};
+obsolete(crypto, hmac_final, 1) ->
+ {deprecated, "use crypto:mac_final/1 instead", "OTP 24"};
+obsolete(crypto, hmac_final_n, 2) ->
+ {deprecated, "use crypto:mac_finalN/2 instead", "OTP 24"};
+obsolete(crypto, hmac_init, 2) ->
+ {deprecated, "use crypto:mac_init/3 instead", "OTP 24"};
+obsolete(crypto, hmac_update, 2) ->
+ {deprecated, "use crypto:mac_update/2 instead", "OTP 24"};
+obsolete(crypto, poly1305, 2) ->
+ {deprecated, "use crypto:mac/3 instead", "OTP 24"};
+obsolete(crypto, rand_uniform, 2) ->
+ {deprecated, "use rand:uniform/1 instead"};
+obsolete(crypto, stream_decrypt, 2) ->
+ {deprecated, "use crypto:crypto_update/2 instead", "OTP 24"};
+obsolete(crypto, stream_encrypt, 2) ->
+ {deprecated, "use crypto:crypto_update/2 instead", "OTP 24"};
+obsolete(erlang, get_stacktrace, 0) ->
+ {deprecated, "use the new try/catch syntax for retrieving the stack backtrace", "OTP 24"};
+obsolete(erlang, now, 0) ->
+ {deprecated, "see the \"Time and Time Correction in Erlang\" chapter of the ERTS User's Guide for more information"};
+obsolete(filename, safe_relative_path, 1) ->
+ {deprecated, "use filelib:safe_relative_path/2 instead", "OTP 25"};
+obsolete(http_uri, decode, 1) ->
+ {deprecated, "use uri_string functions instead", "OTP 25"};
+obsolete(http_uri, encode, 1) ->
+ {deprecated, "use uri_string functions instead", "OTP 25"};
+obsolete(http_uri, parse, 1) ->
+ {deprecated, "use uri_string functions instead", "OTP 25"};
+obsolete(http_uri, parse, 2) ->
+ {deprecated, "use uri_string functions instead", "OTP 25"};
+obsolete(http_uri, scheme_defaults, 0) ->
+ {deprecated, "use uri_string functions instead", "OTP 25"};
+obsolete(httpd, parse_query, 1) ->
+ {deprecated, "use uri_string:dissect_query/1 instead"};
+obsolete(megaco, format_versions, 1) ->
+ {deprecated, "use megaco:print_version_info/0,1 instead.", "OTP 24"};
+obsolete(net, broadcast, 3) ->
+ {deprecated, "use rpc:eval_everywhere/3 instead"};
+obsolete(net, call, 4) ->
+ {deprecated, "use rpc:call/4 instead"};
+obsolete(net, cast, 4) ->
+ {deprecated, "use rpc:cast/4 instead"};
+obsolete(net, ping, 1) ->
+ {deprecated, "use net_adm:ping/1 instead"};
+obsolete(net, relay, 1) ->
+ {deprecated, "use slave:relay/1 instead"};
+obsolete(net, sleep, 1) ->
+ {deprecated, "use 'receive after T -> ok end' instead"};
+obsolete(queue, lait, 1) ->
+ {deprecated, "use queue:liat/1 instead"};
+obsolete(snmp, add_agent_caps, 2) ->
+ {deprecated, "use snmpa:add_agent_caps/2 instead.", "OTP 24"};
+obsolete(snmp, c, 1) ->
+ {deprecated, "use snmpc:compile/1 instead.", "OTP 24"};
+obsolete(snmp, c, 2) ->
+ {deprecated, "use snmpc:compile/2 instead.", "OTP 24"};
+obsolete(snmp, change_log_size, 1) ->
+ {deprecated, "use snmpa:change_log_size/1 instead.", "OTP 24"};
+obsolete(snmp, compile, 3) ->
+ {deprecated, "use snmpc:compile/3 instead.", "OTP 24"};
+obsolete(snmp, current_address, 0) ->
+ {deprecated, "use snmpa:current_address/0 instead.", "OTP 24"};
+obsolete(snmp, current_community, 0) ->
+ {deprecated, "use snmpa:current_community/0 instead.", "OTP 24"};
+obsolete(snmp, current_context, 0) ->
+ {deprecated, "use snmpa:current_context/0 instead.", "OTP 24"};
+obsolete(snmp, current_net_if_data, 0) ->
+ {deprecated, "use snmpa:current_net_if_data/0 instead.", "OTP 24"};
+obsolete(snmp, current_request_id, 0) ->
+ {deprecated, "use snmpa:current_request_id/0 instead.", "OTP 24"};
+obsolete(snmp, del_agent_caps, 1) ->
+ {deprecated, "use snmpa:del_agent_caps/1 instead.", "OTP 24"};
+obsolete(snmp, dump_mibs, 0) ->
+ {deprecated, "use snmpa:dump_mibs/0 instead.", "OTP 24"};
+obsolete(snmp, dump_mibs, 1) ->
+ {deprecated, "use snmpa:dump_mibs/1 instead.", "OTP 24"};
+obsolete(snmp, enum_to_int, 2) ->
+ {deprecated, "use snmpa:enum_to_int/2 instead.", "OTP 24"};
+obsolete(snmp, enum_to_int, 3) ->
+ {deprecated, "use snmpa:enum_to_int/3 instead.", "OTP 24"};
+obsolete(snmp, get, 2) ->
+ {deprecated, "use snmpa:get/2 instead.", "OTP 24"};
+obsolete(snmp, get_agent_caps, 0) ->
+ {deprecated, "use snmpa:get_agent_caps/0 instead.", "OTP 24"};
+obsolete(snmp, get_symbolic_store_db, 0) ->
+ {deprecated, "use snmpa:get_symbolic_store_db/0 instead.", "OTP 24"};
+obsolete(snmp, info, 1) ->
+ {deprecated, "use snmpa:info/1 instead.", "OTP 24"};
+obsolete(snmp, int_to_enum, 2) ->
+ {deprecated, "use snmpa:int_to_enum/2 instead.", "OTP 24"};
+obsolete(snmp, int_to_enum, 3) ->
+ {deprecated, "use snmpa:int_to_enum/3 instead.", "OTP 24"};
+obsolete(snmp, is_consistent, 1) ->
+ {deprecated, "use snmpc:is_consistent/1 instead.", "OTP 24"};
+obsolete(snmp, load_mibs, 2) ->
+ {deprecated, "use snmpa:load_mibs/2 instead.", "OTP 24"};
+obsolete(snmp, log_to_txt, 2) ->
+ {deprecated, "use snmpa:log_to_txt/2 instead.", "OTP 24"};
+obsolete(snmp, log_to_txt, 3) ->
+ {deprecated, "use snmpa:log_to_txt/3 instead.", "OTP 24"};
+obsolete(snmp, log_to_txt, 4) ->
+ {deprecated, "use snmpa:log_to_txt/4 instead.", "OTP 24"};
+obsolete(snmp, mib_to_hrl, 1) ->
+ {deprecated, "use snmpc:mib_to_hrl/1 instead.", "OTP 24"};
+obsolete(snmp, name_to_oid, 1) ->
+ {deprecated, "use snmpa:name_to_oid/1 instead.", "OTP 24"};
+obsolete(snmp, name_to_oid, 2) ->
+ {deprecated, "use snmpa:name_to_oid/2 instead.", "OTP 24"};
+obsolete(snmp, oid_to_name, 1) ->
+ {deprecated, "use snmpa:oid_to_name/1 instead.", "OTP 24"};
+obsolete(snmp, oid_to_name, 2) ->
+ {deprecated, "use snmpa:oid_to_name/2 instead.", "OTP 24"};
+obsolete(snmp, register_subagent, 3) ->
+ {deprecated, "use snmpa:register_subagent/3 instead.", "OTP 24"};
+obsolete(snmp, send_notification, 3) ->
+ {deprecated, "use snmpa:send_notification/3 instead.", "OTP 24"};
+obsolete(snmp, send_notification, 4) ->
+ {deprecated, "use snmpa:send_notification/4 instead.", "OTP 24"};
+obsolete(snmp, send_notification, 5) ->
+ {deprecated, "use snmpa:send_notification/5 instead.", "OTP 24"};
+obsolete(snmp, send_notification, 6) ->
+ {deprecated, "use snmpa:send_notification/6 instead.", "OTP 24"};
+obsolete(snmp, send_trap, 3) ->
+ {deprecated, "use snmpa:send_trap/3 instead.", "OTP 24"};
+obsolete(snmp, send_trap, 4) ->
+ {deprecated, "use snmpa:send_trap/4 instead.", "OTP 24"};
+obsolete(snmp, unload_mibs, 2) ->
+ {deprecated, "use snmpa:unload_mibs/2 instead.", "OTP 24"};
+obsolete(snmp, unregister_subagent, 2) ->
+ {deprecated, "use snmpa:unregister_subagent/2 instead.", "OTP 24"};
+obsolete(snmpa, old_info_format, 1) ->
+ {deprecated, "use \"new\" format instead", "OTP 24"};
+obsolete(snmpm, async_get, 3) ->
+ {deprecated, "use snmpm:async_get2/3 instead.", "OTP 25"};
+obsolete(snmpm, async_get, 4) ->
+ {deprecated, "use snmpm:async_get2/4 instead.", "OTP 25"};
+obsolete(snmpm, async_get, 5) ->
+ {deprecated, "use snmpm:async_get2/4 instead.", "OTP 25"};
+obsolete(snmpm, async_get, 6) ->
+ {deprecated, "use snmpm:async_get2/4 instead.", "OTP 25"};
+obsolete(snmpm, async_get_bulk, 5) ->
+ {deprecated, "use snmpm:async_get_bulk2/5 instead.", "OTP 25"};
+obsolete(snmpm, async_get_bulk, 6) ->
+ {deprecated, "use snmpm:async_get_bulk2/6 instead.", "OTP 25"};
+obsolete(snmpm, async_get_bulk, 7) ->
+ {deprecated, "use snmpm:async_get_bulk2/6 instead.", "OTP 25"};
+obsolete(snmpm, async_get_bulk, 8) ->
+ {deprecated, "use snmpm:async_get_bulk2/6 instead.", "OTP 25"};
+obsolete(snmpm, async_get_next, 3) ->
+ {deprecated, "use snmpm:async_get_next2/3 instead.", "OTP 25"};
+obsolete(snmpm, async_get_next, 4) ->
+ {deprecated, "use snmpm:async_get_next2/4 instead.", "OTP 25"};
+obsolete(snmpm, async_get_next, 5) ->
+ {deprecated, "use snmpm:async_get_next2/4 instead.", "OTP 25"};
+obsolete(snmpm, async_get_next, 6) ->
+ {deprecated, "use snmpm:async_get_next2/4 instead.", "OTP 25"};
+obsolete(snmpm, async_set, 3) ->
+ {deprecated, "use snmpm:async_set2/3 instead.", "OTP 25"};
+obsolete(snmpm, async_set, 4) ->
+ {deprecated, "use snmpm:async_set2/4 instead.", "OTP 25"};
+obsolete(snmpm, async_set, 5) ->
+ {deprecated, "use snmpm:async_set2/4 instead.", "OTP 25"};
+obsolete(snmpm, async_set, 6) ->
+ {deprecated, "use snmpm:async_set2/4 instead.", "OTP 25"};
+obsolete(snmpm, sync_get, 3) ->
+ {deprecated, "use snmpm:sync_get2/3 instead.", "OTP 25"};
+obsolete(snmpm, sync_get, 4) ->
+ {deprecated, "use snmpm:sync_get2/4 instead.", "OTP 25"};
+obsolete(snmpm, sync_get, 5) ->
+ {deprecated, "use snmpm:sync_get2/4 instead.", "OTP 25"};
+obsolete(snmpm, sync_get, 6) ->
+ {deprecated, "use snmpm:sync_get2/4 instead.", "OTP 25"};
+obsolete(snmpm, sync_get_bulk, 5) ->
+ {deprecated, "use snmpm:sync_get_bulk2/5 instead.", "OTP 25"};
+obsolete(snmpm, sync_get_bulk, 6) ->
+ {deprecated, "use snmpm:sync_get_bulk2/6 instead.", "OTP 25"};
+obsolete(snmpm, sync_get_bulk, 7) ->
+ {deprecated, "use snmpm:sync_get_bulk2/6 instead.", "OTP 25"};
+obsolete(snmpm, sync_get_bulk, 8) ->
+ {deprecated, "use snmpm:sync_get_bulk2/6 instead.", "OTP 25"};
+obsolete(snmpm, sync_get_next, 3) ->
+ {deprecated, "use snmpm:sync_get_next2/3 instead.", "OTP 25"};
+obsolete(snmpm, sync_get_next, 4) ->
+ {deprecated, "use snmpm:sync_get_next2/4 instead.", "OTP 25"};
+obsolete(snmpm, sync_get_next, 5) ->
+ {deprecated, "use snmpm:sync_get_next2/4 instead.", "OTP 25"};
+obsolete(snmpm, sync_get_next, 6) ->
+ {deprecated, "use snmpm:sync_get_next2/4 instead.", "OTP 25"};
+obsolete(snmpm, sync_set, 3) ->
+ {deprecated, "use snmpm:sync_set2/3 instead.", "OTP 25"};
+obsolete(snmpm, sync_set, 4) ->
+ {deprecated, "use snmpm:sync_set2/4 instead.", "OTP 25"};
+obsolete(snmpm, sync_set, 5) ->
+ {deprecated, "use snmpm:sync_set2/4 instead.", "OTP 25"};
+obsolete(snmpm, sync_set, 6) ->
+ {deprecated, "use snmpm:sync_set2/4 instead.", "OTP 25"};
+obsolete(ssl, cipher_suites, 0) ->
+ {deprecated, "use cipher_suites/2,3 instead", "OTP 24"};
+obsolete(ssl, cipher_suites, 1) ->
+ {deprecated, "use cipher_suites/2,3 instead", "OTP 24"};
+obsolete(sys, get_debug, 3) ->
+ {deprecated, "incorrectly documented and only for internal use. Can often be replaced with sys:get_log/1"};
+obsolete(wxCalendarCtrl, enableYearChange, 1) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxCalendarCtrl, enableYearChange, 2) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxClientDC, new, 0) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxCursor, new, 3) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxCursor, new, 4) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxDC, computeScaleAndOrigin, 1) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxGraphicsRenderer, createLinearGradientBrush, 7) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxGraphicsRenderer, createRadialGradientBrush, 8) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxGridCellEditor, endEdit, 4) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxGridCellEditor, paintBackground, 3) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxIdleEvent, canSend, 1) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxMDIClientWindow, new, 1) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxMDIClientWindow, new, 2) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxPaintDC, new, 0) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxPostScriptDC, getResolution, 0) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxPostScriptDC, setResolution, 1) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(wxWindowDC, new, 0) ->
+ {deprecated, "not available in wxWidgets-2.9 and later"};
+obsolete(core_lib, get_anno, 1) ->
+ {removed, "use cerl:get_ann/1 instead"};
+obsolete(core_lib, is_literal, 1) ->
+ {removed, "use cerl:is_literal/1 instead"};
+obsolete(core_lib, is_literal_list, 1) ->
+ {removed, "use cerl:is_literal_list/1 instead"};
+obsolete(core_lib, literal_value, 1) ->
+ {removed, "use cerl:concrete/1 instead"};
+obsolete(core_lib, set_anno, 2) ->
+ {removed, "use cerl:set_ann/2 instead"};
+obsolete(crypto, aes_cbc_128_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, aes_cbc_128_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, aes_cbc_256_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, aes_cbc_256_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, aes_cbc_ivec, 2) ->
+ {removed, "use crypto:next_iv/2 instead"};
+obsolete(crypto, aes_cfb_128_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, aes_cfb_128_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, aes_ctr_decrypt, 3) ->
+ {removed, "use crypto:stream_decrypt/2 instead"};
+obsolete(crypto, aes_ctr_encrypt, 3) ->
+ {removed, "use crypto:stream_encrypt/2 instead"};
+obsolete(crypto, aes_ctr_stream_decrypt, 2) ->
+ {removed, "use crypto:stream_decrypt/2 instead"};
+obsolete(crypto, aes_ctr_stream_encrypt, 2) ->
+ {removed, "use crypto:stream_encrypt/2 instead"};
+obsolete(crypto, aes_ctr_stream_init, 2) ->
+ {removed, "use crypto:stream_init/3 instead"};
+obsolete(crypto, blowfish_cbc_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, blowfish_cbc_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, blowfish_cfb64_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, blowfish_cfb64_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, blowfish_ecb_decrypt, 2) ->
+ {removed, "use crypto:block_decrypt/3 instead"};
+obsolete(crypto, blowfish_ecb_encrypt, 2) ->
+ {removed, "use crypto:block_encrypt/3 instead"};
+obsolete(crypto, blowfish_ofb64_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, blowfish_ofb64_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, des3_cbc_decrypt, 5) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, des3_cbc_encrypt, 5) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, des3_cfb_decrypt, 5) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, des3_cfb_encrypt, 5) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, des3_ede3_cbc_decrypt, 5) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, des_cbc_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, des_cbc_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, des_cbc_ivec, 2) ->
+ {removed, "use crypto:next_iv/2 instead"};
+obsolete(crypto, des_cfb_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, des_cfb_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, des_cfb_ivec, 2) ->
+ {removed, "use crypto:next_iv/3 instead"};
+obsolete(crypto, des_ecb_decrypt, 2) ->
+ {removed, "use crypto:block_decrypt/3 instead"};
+obsolete(crypto, des_ecb_encrypt, 2) ->
+ {removed, "use crypto:block_encrypt/3 instead"};
+obsolete(crypto, des_ede3_cbc_encrypt, 5) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, dh_compute_key, 3) ->
+ {removed, "use crypto:compute_key/4 instead"};
+obsolete(crypto, dh_generate_key, 1) ->
+ {removed, "use crypto:generate_key/2 instead"};
+obsolete(crypto, dh_generate_key, 2) ->
+ {removed, "use crypto:generate_key/3 instead"};
+obsolete(crypto, erlint, 1) ->
+ {removed, "only needed by other removed functions"};
+obsolete(crypto, info, 0) ->
+ {removed, "use crypto:module_info/0 instead"};
+obsolete(crypto, md4, 1) ->
+ {removed, "use crypto:hash/2 instead"};
+obsolete(crypto, md4_final, 1) ->
+ {removed, "use crypto:hash_final/1 instead"};
+obsolete(crypto, md4_init, 0) ->
+ {removed, "use crypto:hash_init/1 instead"};
+obsolete(crypto, md4_update, 2) ->
+ {removed, "use crypto:hash_update/2 instead"};
+obsolete(crypto, md5, 1) ->
+ {removed, "use crypto:hash/2 instead"};
+obsolete(crypto, md5_final, 1) ->
+ {removed, "use crypto:hash_final/1 instead"};
+obsolete(crypto, md5_init, 0) ->
+ {removed, "use crypto:hash_init/1 instead"};
+obsolete(crypto, md5_mac, 2) ->
+ {removed, "use crypto:hmac/3 instead"};
+obsolete(crypto, md5_mac_96, 2) ->
+ {removed, "use crypto:hmac/4 instead"};
+obsolete(crypto, md5_update, 2) ->
+ {removed, "use crypto:hash_update/2 instead"};
+obsolete(crypto, mod_exp, 3) ->
+ {removed, "use crypto:mod_pow/3 instead"};
+obsolete(crypto, mpint, 1) ->
+ {removed, "only needed by other removed functions"};
+obsolete(crypto, rand_bytes, 1) ->
+ {removed, "use crypto:strong_rand_bytes/1 instead"};
+obsolete(crypto, rc2_40_cbc_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, rc2_40_cbc_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, rc2_cbc_decrypt, 3) ->
+ {removed, "use crypto:block_decrypt/4 instead"};
+obsolete(crypto, rc2_cbc_encrypt, 3) ->
+ {removed, "use crypto:block_encrypt/4 instead"};
+obsolete(crypto, rc4_encrypt, 2) ->
+ {removed, "use crypto:stream_encrypt/2 instead"};
+obsolete(crypto, rc4_encrypt_with_state, 2) ->
+ {removed, "use crypto:stream_encrypt/2 instead"};
+obsolete(crypto, rc4_set_key, 2) ->
+ {removed, "use crypto:stream_init/2 instead"};
+obsolete(crypto, sha, 1) ->
+ {removed, "use crypto:hash/2 instead"};
+obsolete(crypto, sha_final, 1) ->
+ {removed, "use crypto:hash_final/1 instead"};
+obsolete(crypto, sha_init, 0) ->
+ {removed, "use crypto:hash_init/1 instead"};
+obsolete(crypto, sha_mac, 2) ->
+ {removed, "use crypto:hmac/3 instead"};
+obsolete(crypto, sha_mac, 3) ->
+ {removed, "use crypto:hmac/4 instead"};
+obsolete(crypto, sha_mac_96, 2) ->
+ {removed, "use crypto:hmac/4 instead"};
+obsolete(crypto, sha_update, 2) ->
+ {removed, "use crypto:hash_update/2 instead"};
+obsolete(crypto, strong_rand_mpint, 3) ->
+ {removed, "only needed by other removed functions"};
+obsolete(erl_lint, modify_line, 2) ->
+ {removed, "use erl_parse:map_anno/2 instead"};
+obsolete(erl_parse, get_attribute, 2) ->
+ {removed, "erl_anno:{column,line,location,text}/1 instead"};
+obsolete(erl_parse, get_attributes, 1) ->
+ {removed, "erl_anno:{column,line,location,text}/1 instead"};
+obsolete(erl_parse, set_line, 2) ->
+ {removed, "use erl_anno:set_line/2"};
+obsolete(erl_scan, set_attribute, 3) ->
+ {removed, "use erl_anno:set_line/2 instead"};
+obsolete(erlang, hash, 2) ->
+ {removed, "use erlang:phash2/2 instead"};
+obsolete(httpd_conf, check_enum, 2) ->
+ {removed, "use lists:member/2 instead"};
+obsolete(httpd_conf, clean, 1) ->
+ {removed, "use sting:strip/1 instead or possibly the re module"};
+obsolete(httpd_conf, custom_clean, 3) ->
+ {removed, "use sting:strip/1 instead or possibly the re module"};
+obsolete(httpd_conf, is_directory, 1) ->
+ {removed, "use filelib:is_dir/1 instead"};
+obsolete(httpd_conf, is_file, 1) ->
+ {removed, "use filelib:is_file/1 instead"};
+obsolete(httpd_conf, make_integer, 1) ->
+ {removed, "use erlang:list_to_integer/1 instead"};
+obsolete(rpc, safe_multi_server_call, 2) ->
+ {removed, "use rpc:multi_server_call/2 instead"};
+obsolete(rpc, safe_multi_server_call, 3) ->
+ {removed, "use rpc:multi_server_call/3 instead"};
+obsolete(ssl, connection_info, 1) ->
+ {removed, "use ssl:connection_information/[1,2] instead"};
+obsolete(ssl, negotiated_next_protocol, 1) ->
+ {removed, "use ssl:negotiated_protocol/1 instead"};
+obsolete(auth, node_cookie, _) ->
+ {deprecated, "use erlang:set_cookie/2 and net_adm:ping/1 instead"};
+obsolete(crypto, next_iv, _) ->
+ {deprecated, "see the 'New and Old API' chapter of the CRYPTO User's guide", "OTP 24"};
+obsolete(crypto, stream_init, _) ->
+ {deprecated, "use crypto:crypto_init/3 + crypto:crypto_update/2 + crypto:crypto_final/1 or crypto:crypto_one_time/4 instead", "OTP 24"};
+obsolete(filename, find_src, _) ->
+ {deprecated, "use filelib:find_source/1,3 instead", "OTP 24"};
+obsolete(ssl, ssl_accept, _) ->
+ {deprecated, "use ssl_handshake/1,2,3 instead", "OTP 24"};
+obsolete(asn1ct, decode, _) ->
+ {removed, "use Mod:decode/2 instead"};
+obsolete(asn1ct, encode, _) ->
+ {removed, "use Mod:encode/2 instead"};
+obsolete(crypto, dss_sign, _) ->
+ {removed, "use crypto:sign/4 instead"};
+obsolete(crypto, dss_verify, _) ->
+ {removed, "use crypto:verify/5 instead"};
+obsolete(crypto, rsa_sign, _) ->
+ {removed, "use crypto:sign/4 instead"};
+obsolete(crypto, rsa_verify, _) ->
+ {removed, "use crypto:verify/5 instead"};
+obsolete(erl_scan, attributes_info, _) ->
+ {removed, "erl_anno:{column,line,location,text}/1 instead"};
+obsolete(erl_scan, token_info, _) ->
+ {removed, "erl_scan:{category,column,line,location,symbol,text}/1 instead"};
+obsolete(erl_tidy, _, _) ->
+ {deprecated, "use https://github.com/richcarl/erl_tidy", "OTP 24"};
+obsolete(gen_fsm, _, _) ->
+ {deprecated, "use the 'gen_statem' module instead"};
+obsolete(igor, _, _) ->
+ {deprecated, "use https://github.com/richcarl/igor", "OTP 24"};
+obsolete(pg2, _, _) ->
+ {deprecated, "use 'pg' instead", "OTP 24"};
+obsolete(random, _, _) ->
+ {deprecated, "use the 'rand' module instead"};
+obsolete(os_mon_mib, _, _) ->
+ {removed, "this module was removed in OTP 22.0"};
+obsolete(_,_,_) -> no.
-dialyzer({no_match, obsolete_type/3}).
-obsolete_type(Module, Name, NumberOfVariables) ->
- case obsolete_type_1(Module, Name, NumberOfVariables) of
- {deprecated=Tag,{_,_,_}=Replacement} ->
- {Tag,Replacement,"in a future release"};
- {_,String}=Ret when is_list(String) ->
- Ret;
- {_,_,_}=Ret ->
- Ret;
- no ->
- no
- end.
+obsolete_type(crypto, retired_cbc_cipher_aliases, 0) ->
+ {deprecated, "Use aes_*_cbc or des_ede3_cbc"};
+obsolete_type(crypto, retired_cfb_cipher_aliases, 0) ->
+ {deprecated, "Use aes_*_cfb8, aes_*_cfb128 or des_ede3_cfb"};
+obsolete_type(crypto, retired_ctr_cipher_aliases, 0) ->
+ {deprecated, "Use aes_*_ctr"};
+obsolete_type(crypto, retired_ecb_cipher_aliases, 0) ->
+ {deprecated, "Use aes_*_ecb"};
+obsolete_type(erl_scan, column, 0) ->
+ {removed, "use erl_anno:column() instead"};
+obsolete_type(erl_scan, line, 0) ->
+ {removed, "use erl_anno:line() instead"};
+obsolete_type(erl_scan, location, 0) ->
+ {removed, "use erl_anno:location() instead"};
+obsolete_type(_,_,_) -> no.
-obsolete_type_1(erl_scan,column,0) ->
- {removed,{erl_anno,column,0},"19.0"};
-obsolete_type_1(erl_scan,line,0) ->
- {removed,{erl_anno,line,0},"19.0"};
-obsolete_type_1(erl_scan,location,0) ->
- {removed,{erl_anno,location,0},"19.0"};
-obsolete_type_1(_,_,_) ->
- no.
diff --git a/lib/stdlib/src/otp_internal.hrl b/lib/stdlib/src/otp_internal.hrl
new file mode 100644
index 0000000000..17e15da68f
--- /dev/null
+++ b/lib/stdlib/src/otp_internal.hrl
@@ -0,0 +1,36 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% This file is included by the file "otp_internal.erl", which is
+%% auto-generated by stdlib/scripts/update_deprecations
+%%
+
+-export([obsolete/3, obsolete_type/3]).
+
+-type tag() :: 'deprecated' | 'removed'. %% | 'experimental'.
+-type mfas() :: mfa() | {atom(), atom(), [byte()]} | string().
+-type release() :: string().
+
+-spec obsolete(module(), atom(), arity()) ->
+ 'no' | {tag(), string()} | {tag(), mfas(), release()}.
+
+-spec obsolete_type(module(), atom(), arity()) ->
+ 'no' | {tag(), string()} | {tag(), mfas(), release()}.
diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl
index cfbaf8b242..58e6faf950 100644
--- a/lib/stdlib/src/proc_lib.erl
+++ b/lib/stdlib/src/proc_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
spawn/3, spawn_link/3, spawn/4, spawn_link/4,
spawn_opt/2, spawn_opt/3, spawn_opt/4, spawn_opt/5,
start/3, start/4, start/5, start_link/3, start_link/4, start_link/5,
+ start_monitor/3, start_monitor/4, start_monitor/5,
hibernate/3,
init_ack/1, init_ack/2,
init_p/3,init_p/5,format/1,format/2,format/3,report_cb/2,
@@ -39,25 +40,21 @@
-export([wake_up/3]).
-export_type([spawn_option/0]).
+-export_type([start_spawn_option/0]).
-include("logger.hrl").
%%-----------------------------------------------------------------------------
--type priority_level() :: 'high' | 'low' | 'max' | 'normal'.
--type max_heap_size() :: non_neg_integer() |
- #{ size => non_neg_integer(),
- kill => true,
- error_logger => true}.
--type spawn_option() :: 'link'
- | 'monitor'
- | {'priority', priority_level()}
- | {'max_heap_size', max_heap_size()}
- | {'min_heap_size', non_neg_integer()}
- | {'min_bin_vheap_size', non_neg_integer()}
- | {'fullsweep_after', non_neg_integer()}
- | {'message_queue_data',
- 'off_heap' | 'on_heap' | 'mixed' }.
+-type start_spawn_option() :: 'link'
+ | {'priority', erlang:priority_level()}
+ | {'max_heap_size', erlang:max_heap_size()}
+ | {'min_heap_size', non_neg_integer()}
+ | {'min_bin_vheap_size', non_neg_integer()}
+ | {'fullsweep_after', non_neg_integer()}
+ | {'message_queue_data', erlang:message_queue_data() }.
+
+-type spawn_option() :: erlang:spawn_opt_option().
-type dict_or_pid() :: pid()
| (ProcInfo :: [_])
@@ -65,6 +62,14 @@
%%-----------------------------------------------------------------------------
+-define(VERIFY_NO_MONITOR_OPT(M, F, A, T, Opts),
+ case lists:member(monitor, Opts) of
+ true -> erlang:error(badarg, [M,F,A,T,Opts]);
+ false -> ok
+ end).
+
+%%-----------------------------------------------------------------------------
+
-spec spawn(Fun) -> pid() when
Fun :: function().
@@ -141,17 +146,16 @@ spawn_link(Node, M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
Ancestors = get_ancestors(),
erlang:spawn_link(Node, ?MODULE, init_p, [Parent,Ancestors,M,F,A]).
--spec spawn_opt(Fun, SpawnOpts) -> pid() when
+-spec spawn_opt(Fun, SpawnOpts) -> pid() | {pid(), reference()} when
Fun :: function(),
SpawnOpts :: [spawn_option()].
spawn_opt(F, Opts) when is_function(F) ->
Parent = get_my_name(),
Ancestors = get_ancestors(),
- check_for_monitor(Opts),
erlang:spawn_opt(?MODULE, init_p, [Parent,Ancestors,F],Opts).
--spec spawn_opt(Node, Function, SpawnOpts) -> pid() when
+-spec spawn_opt(Node, Function, SpawnOpts) -> pid() | {pid(), reference()} when
Node :: node(),
Function :: function(),
SpawnOpts :: [spawn_option()].
@@ -159,10 +163,9 @@ spawn_opt(F, Opts) when is_function(F) ->
spawn_opt(Node, F, Opts) when is_function(F) ->
Parent = get_my_name(),
Ancestors = get_ancestors(),
- check_for_monitor(Opts),
erlang:spawn_opt(Node, ?MODULE, init_p, [Parent,Ancestors,F], Opts).
--spec spawn_opt(Module, Function, Args, SpawnOpts) -> pid() when
+-spec spawn_opt(Module, Function, Args, SpawnOpts) -> pid() | {pid(), reference()} when
Module :: module(),
Function :: atom(),
Args :: [term()],
@@ -171,10 +174,9 @@ spawn_opt(Node, F, Opts) when is_function(F) ->
spawn_opt(M, F, A, Opts) when is_atom(M), is_atom(F), is_list(A) ->
Parent = get_my_name(),
Ancestors = get_ancestors(),
- check_for_monitor(Opts),
erlang:spawn_opt(?MODULE, init_p, [Parent,Ancestors,M,F,A], Opts).
--spec spawn_opt(Node, Module, Function, Args, SpawnOpts) -> pid() when
+-spec spawn_opt(Node, Module, Function, Args, SpawnOpts) -> pid() | {pid(), reference()} when
Node :: node(),
Module :: module(),
Function :: atom(),
@@ -184,30 +186,13 @@ spawn_opt(M, F, A, Opts) when is_atom(M), is_atom(F), is_list(A) ->
spawn_opt(Node, M, F, A, Opts) when is_atom(M), is_atom(F), is_list(A) ->
Parent = get_my_name(),
Ancestors = get_ancestors(),
- check_for_monitor(Opts),
erlang:spawn_opt(Node, ?MODULE, init_p, [Parent,Ancestors,M,F,A], Opts).
-%% OTP-6345
-%% monitor spawn_opt option is currently not possible to use
-check_for_monitor(SpawnOpts) ->
- case lists:member(monitor, SpawnOpts) of
- true ->
- erlang:error(badarg);
- false ->
- false
- end.
-
spawn_mon(M,F,A) ->
Parent = get_my_name(),
Ancestors = get_ancestors(),
erlang:spawn_monitor(?MODULE, init_p, [Parent,Ancestors,M,F,A]).
-spawn_opt_mon(M, F, A, Opts) when is_atom(M), is_atom(F), is_list(A) ->
- Parent = get_my_name(),
- Ancestors = get_ancestors(),
- check_for_monitor(Opts),
- erlang:spawn_opt(?MODULE, init_p, [Parent,Ancestors,M,F,A], [monitor|Opts]).
-
-spec hibernate(Module, Function, Args) -> no_return() when
Module :: module(),
Function :: atom(),
@@ -216,14 +201,6 @@ spawn_opt_mon(M, F, A, Opts) when is_atom(M), is_atom(F), is_list(A) ->
hibernate(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
erlang:hibernate(?MODULE, wake_up, [M, F, A]).
-ensure_link(SpawnOpts) ->
- case lists:member(link, SpawnOpts) of
- true ->
- SpawnOpts;
- false ->
- [link|SpawnOpts]
- end.
-
-spec init_p(pid(), [pid()], function()) -> term().
init_p(Parent, Ancestors, Fun) when is_function(Fun) ->
@@ -299,20 +276,32 @@ start(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
Ret :: term() | {error, Reason :: term()}.
start(M, F, A, Timeout) when is_atom(M), is_atom(F), is_list(A) ->
- PidRef = spawn_mon(M, F, A),
- sync_wait_mon(PidRef, Timeout).
+ sync_start(spawn_mon(M, F, A), Timeout).
-spec start(Module, Function, Args, Time, SpawnOpts) -> Ret when
Module :: module(),
Function :: atom(),
Args :: [term()],
Time :: timeout(),
- SpawnOpts :: [spawn_option()],
+ SpawnOpts :: [start_spawn_option()],
Ret :: term() | {error, Reason :: term()}.
start(M, F, A, Timeout, SpawnOpts) when is_atom(M), is_atom(F), is_list(A) ->
- PidRef = spawn_opt_mon(M, F, A, SpawnOpts),
- sync_wait_mon(PidRef, Timeout).
+ ?VERIFY_NO_MONITOR_OPT(M, F, A, Timeout, SpawnOpts),
+ sync_start(?MODULE:spawn_opt(M, F, A, [monitor|SpawnOpts]), Timeout).
+
+sync_start({Pid, Ref}, Timeout) ->
+ receive
+ {ack, Pid, Return} ->
+ erlang:demonitor(Ref, [flush]),
+ Return;
+ {'DOWN', Ref, process, Pid, Reason} ->
+ {error, Reason}
+ after Timeout ->
+ erlang:demonitor(Ref, [flush]),
+ kill_flush(Pid),
+ {error, timeout}
+ end.
-spec start_link(Module, Function, Args) -> Ret when
Module :: module(),
@@ -331,60 +320,88 @@ start_link(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
Ret :: term() | {error, Reason :: term()}.
start_link(M, F, A, Timeout) when is_atom(M), is_atom(F), is_list(A) ->
- Pid = ?MODULE:spawn_link(M, F, A),
- sync_wait(Pid, Timeout).
+ sync_start_link(?MODULE:spawn_link(M, F, A), Timeout).
-spec start_link(Module, Function, Args, Time, SpawnOpts) -> Ret when
Module :: module(),
Function :: atom(),
Args :: [term()],
Time :: timeout(),
- SpawnOpts :: [spawn_option()],
+ SpawnOpts :: [start_spawn_option()],
Ret :: term() | {error, Reason :: term()}.
start_link(M,F,A,Timeout,SpawnOpts) when is_atom(M), is_atom(F), is_list(A) ->
- Pid = ?MODULE:spawn_opt(M, F, A, ensure_link(SpawnOpts)),
- sync_wait(Pid, Timeout).
+ ?VERIFY_NO_MONITOR_OPT(M, F, A, Timeout, SpawnOpts),
+ sync_start_link(?MODULE:spawn_opt(M, F, A, [link|SpawnOpts]), Timeout).
-sync_wait(Pid, Timeout) ->
+sync_start_link(Pid, Timeout) ->
receive
{ack, Pid, Return} ->
- Return;
+ Return;
{'EXIT', Pid, Reason} ->
- {error, Reason}
+ {error, Reason}
after Timeout ->
- unlink(Pid),
- exit(Pid, kill),
- flush(Pid),
- {error, timeout}
+ kill_flush(Pid),
+ {error, timeout}
end.
-sync_wait_mon({Pid, Ref}, Timeout) ->
+-spec start_monitor(Module, Function, Args) -> {Ret, Mon} when
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Mon :: reference(),
+ Ret :: term() | {error, Reason :: term()}.
+
+start_monitor(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
+ start_monitor(M, F, A, infinity).
+
+-spec start_monitor(Module, Function, Args, Time) -> {Ret, Mon} when
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Time :: timeout(),
+ Mon :: reference(),
+ Ret :: term() | {error, Reason :: term()}.
+
+start_monitor(M, F, A, Timeout) when is_atom(M), is_atom(F), is_list(A) ->
+ sync_start_monitor(spawn_mon(M, F, A), Timeout).
+
+-spec start_monitor(Module, Function, Args, Time, SpawnOpts) -> {Ret, Mon} when
+ Module :: module(),
+ Function :: atom(),
+ Args :: [term()],
+ Time :: timeout(),
+ SpawnOpts :: [start_spawn_option()],
+ Mon :: reference(),
+ Ret :: term() | {error, Reason :: term()}.
+
+start_monitor(M,F,A,Timeout,SpawnOpts) when is_atom(M),
+ is_atom(F),
+ is_list(A) ->
+ ?VERIFY_NO_MONITOR_OPT(M, F, A, Timeout, SpawnOpts),
+ sync_start_monitor(?MODULE:spawn_opt(M, F, A, [monitor|SpawnOpts]),
+ Timeout).
+
+sync_start_monitor({Pid, Ref}, Timeout) ->
receive
{ack, Pid, Return} ->
- erlang:demonitor(Ref, [flush]),
- Return;
- {'DOWN', Ref, _Type, Pid, Reason} ->
- {error, Reason};
- {'EXIT', Pid, Reason} -> %% link as spawn_opt?
- erlang:demonitor(Ref, [flush]),
- {error, Reason}
+ {Return, Ref};
+ {'DOWN', Ref, process, Pid, Reason} = Down ->
+ self() ! Down,
+ {{error, Reason}, Ref}
after Timeout ->
- erlang:demonitor(Ref, [flush]),
- exit(Pid, kill),
- flush(Pid),
- {error, timeout}
+ kill_flush(Pid),
+ {{error, timeout}, Ref}
end.
--spec flush(pid()) -> 'true'.
+-spec kill_flush(Pid) -> 'ok' when
+ Pid :: pid().
-flush(Pid) ->
- receive
- {'EXIT', Pid, _} ->
- true
- after 0 ->
- true
- end.
+kill_flush(Pid) ->
+ unlink(Pid),
+ exit(Pid, kill),
+ receive {'EXIT', Pid, _} -> ok after 0 -> ok end,
+ ok.
-spec init_ack(Parent, Ret) -> 'ok' when
Parent :: pid(),
@@ -784,20 +801,114 @@ format(CrashReport, Encoding, Depth) ->
encoding => Encoding,
single_line => false}).
-do_format([OwnReport,LinkReport], #{single_line:=Single}=Extra) ->
+do_format([OwnReport,LinkReport], Extra) ->
+ #{encoding:=Enc, single_line:=Single, chars_limit:=Limit0} = Extra,
Indent = if Single -> "";
true -> " "
end,
- MyIndent = Indent ++ Indent,
- Sep = nl(Single,"; "),
- OwnFormat = format_report(OwnReport, MyIndent, Extra),
- LinkFormat = lists:join(Sep,format_link_report(LinkReport, MyIndent, Extra)),
Nl = nl(Single," "),
- Str = io_lib:format("~scrasher:"++Nl++"~ts"++Sep++"~sneighbours:"++Nl++"~ts",
- [Indent,OwnFormat,Indent,LinkFormat]),
- lists:flatten(Str).
+ Sep = nl(Single, report_separator()),
+ {PartLimit, Limit} =
+ case Limit0 of
+ unlimited ->
+ {Limit0, Limit0};
+ _ when is_integer(Limit0) ->
+ %% HardcodedSize is the length of the hardcoded heading +
+ %% separators in the final format string below,
+ %% including neighbours. Just make sure the limit
+ %% does not become negative.
+ Num = length(OwnReport),
+ HardcodedSize = (length(Indent) + length("crasher")
+ + length(Nl) + length(Sep)
+ + (length(Sep) * Num)),
+ Limit1 = max(Limit0-HardcodedSize, 1),
+
+ %% Divide the available characters over all report
+ %% parts. Spend one third of the characters on the
+ %% crash reason, and let the rest of the elements
+ %% (including the neighbours) share the other two
+ %% thirds. This is to make sure we see a good part of
+ %% the crash reason. Most of the other elements in the
+ %% crasher's report are quite small, so we don't loose
+ %% a lot of info from these anyway.
+ EL = Limit1 div 3,
+ PL = (Limit1-EL) div (Num),
+ {PL, Limit1}
+ end,
+ LinkFormat = format_link_reports(LinkReport, Indent, Extra, PartLimit),
+ LinkFormatSize = size(Enc, LinkFormat),
+
+ OwnFormat = format_own_report(OwnReport, Indent, Extra,
+ LinkFormatSize, PartLimit, Limit),
+ io_lib:format("~scrasher:"++Nl++"~ts"++Sep++"~ts",
+ [Indent,OwnFormat,LinkFormat]).
+
+format_own_report(OwnReport, Indent, Extra, LinkFormatSize, PartLimit, Limit0) ->
+ MyIndent = Indent ++ Indent,
+ case separate_error_info(OwnReport) of
+ {First,{Class,Reason,StackTrace},Rest} ->
+ F = format_report(First, MyIndent, Extra, PartLimit),
+ R = format_report(Rest, MyIndent, Extra, PartLimit),
+ #{encoding:=Enc, single_line:=Single} = Extra,
+ Sep = nl(Single, part_separator()),
+ Limit = case Limit0 of
+ unlimited ->
+ Limit0;
+ _ when is_integer(Limit0) ->
+ %% Some of the report parts are quite small,
+ %% and we can use the leftover chars to show
+ %% more of the error_info part.
+ SizeOfOther = (size(Enc, F)
+ +size(Enc, R)
+ -length(Sep)*(length(F)+length(R))
+ +LinkFormatSize),
+ max(Limit0-SizeOfOther, 1)
+ end,
+ EI = format_exception(Class, Reason, StackTrace, Extra, Limit),
+ lists:join(Sep, [F, EI, R]);
+ no ->
+ Limit = case Limit0 of
+ unlimited ->
+ Limit0;
+ _ when is_integer(Limit0) ->
+ max(Limit0-LinkFormatSize, 1)
+ end,
+ format_report(OwnReport, MyIndent, Extra, Limit)
+ end.
-format_link_report([Link|Reps], Indent0, #{single_line:=Single}=Extra) ->
+separate_error_info(Report) ->
+ try
+ lists:splitwith(fun(A) -> element(1, A) =/= error_info end, Report)
+ of
+ {First, [{error_info,ErrorInfo}|Rest]} ->
+ {First,ErrorInfo,Rest};
+ _ -> no
+ catch _:_ -> no
+ end.
+
+%% If the size of the total report is limited by chars_limit, then
+%% print only the pids.
+format_link_reports(LinkReports, Indent, Extra, PartLimit)
+ when is_integer(PartLimit) ->
+ #{encoding:=Enc, depth:=Depth, single_line:=Single} = Extra,
+ Pids = [P || {neighbour,[{pid,P}|_]} <- LinkReports],
+ {P,Tl} = p(Enc,Depth),
+ Width = if Single -> "0";
+ true -> ""
+ end,
+ io_lib:format(Indent++"neighbours: ~"++Width++P,
+ [Pids|Tl],
+ [{chars_limit,PartLimit}]);
+format_link_reports(LinkReports, Indent, Extra, PartLimit) ->
+ #{single_line:=Single} = Extra,
+ MyIndent = Indent ++ Indent,
+ LinkFormat =
+ lists:join(nl(Single, report_separator()),
+ format_link_report(LinkReports, MyIndent, Extra, PartLimit)),
+ [Indent,"neighbours:",nl(Single," "),LinkFormat].
+
+format_link_report([Link|Reps], Indent0, Extra, PartLimit) ->
+ #{single_line:=Single} = Extra,
Rep = case Link of
{neighbour,Rep0} -> Rep0;
_ -> Link
@@ -806,63 +917,70 @@ format_link_report([Link|Reps], Indent0, #{single_line:=Single}=Extra) ->
true -> Indent0
end,
LinkIndent = [" ",Indent],
- [[Indent,"neighbour:",nl(Single," "),format_report(Rep, LinkIndent, Extra)]|
- format_link_report(Reps, Indent, Extra)];
-format_link_report(Rep, Indent, Extra) ->
- format_report(Rep, Indent, Extra).
-
-format_report(Rep, Indent, #{single_line:=Single}=Extra) when is_list(Rep) ->
- lists:join(nl(Single,", "),format_rep(Rep, Indent, Extra));
-format_report(Rep, Indent0, #{encoding:=Enc,depth:=Depth,
- chars_limit:=Limit,single_line:=Single}) ->
+ [[Indent,"neighbour:",nl(Single," "),
+ format_report(Rep, LinkIndent, Extra, PartLimit)]|
+ format_link_report(Reps, Indent, Extra, PartLimit)];
+format_link_report(Rep, Indent, Extra, PartLimit) ->
+ format_report(Rep, Indent, Extra, PartLimit).
+
+format_report(Rep, Indent, Extra, Limit) when is_list(Rep) ->
+ #{single_line:=Single} = Extra,
+ lists:join(nl(Single, part_separator()),
+ format_rep(Rep, Indent, Extra, Limit));
+format_report(Rep, Indent0, Extra, Limit) ->
+ #{encoding:=Enc, depth:=Depth, single_line:=Single} = Extra,
{P,Tl} = p(Enc,Depth),
{Indent,Width} = if Single -> {"","0"};
true -> {Indent0,""}
end,
- Opts = if is_integer(Limit) -> [{chars_limit,Limit}];
- true -> []
- end,
+ Opts = chars_limit_opt(Limit),
io_lib:format("~s~"++Width++P, [Indent, Rep | Tl], Opts).
-format_rep([{initial_call,InitialCall}|Rep], Indent, Extra) ->
- [format_mfa(Indent, InitialCall, Extra)|format_rep(Rep, Indent, Extra)];
-format_rep([{error_info,{Class,Reason,StackTrace}}|Rep], Indent, Extra) ->
- [format_exception(Class, Reason, StackTrace, Extra)|
- format_rep(Rep, Indent, Extra)];
-format_rep([{Tag,Data}|Rep], Indent, Extra) ->
- [format_tag(Indent, Tag, Data, Extra)|format_rep(Rep, Indent, Extra)];
-format_rep(_, _, _Extra) ->
+format_rep([{initial_call,InitialCall}|Rep], Indent, Extra, Limit) ->
+ [format_mfa(Indent, InitialCall, Extra, Limit)|
+ format_rep(Rep, Indent, Extra, Limit)];
+format_rep([{Tag,Data}|Rep], Indent, Extra, Limit) ->
+ [format_tag(Indent, Tag, Data, Extra, Limit)|
+ format_rep(Rep, Indent, Extra, Limit)];
+format_rep(_, _, _Extra, _Limit) ->
[].
-format_exception(Class, Reason, StackTrace,
- #{encoding:=Enc,depth:=Depth,chars_limit:=Limit,
- single_line:=Single}=Extra) ->
- PF = pp_fun(Extra),
+format_exception(Class, Reason, StackTrace, Extra, Limit) ->
+ #{encoding:=Enc,depth:=Depth, single_line:=Single} = Extra,
StackFun = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end,
if Single ->
{P,Tl} = p(Enc,Depth),
- Opts = if is_integer(Limit) -> [{chars_limit,Limit}];
- true -> []
- end,
+ Opts = chars_limit_opt(Limit),
[atom_to_list(Class), ": ",
io_lib:format("~0"++P,[{Reason,StackTrace}|Tl],Opts)];
true ->
+ %% Notice that each call to PF uses chars_limit, which
+ %% means that the total size of the formatted exception
+ %% can exceed the limit a lot.
+ PF = pp_fun(Extra, Enc),
EI = " ",
- [EI, erl_error:format_exception(1+length(EI), Class, Reason,
- StackTrace, StackFun, PF, Enc)]
+ Lim = case Limit of
+ unlimited -> -1;
+ _ -> Limit
+ end,
+ FE = erl_error:format_exception(1+length(EI), Class, Reason,
+ StackTrace, StackFun, PF, Enc,
+ Lim),
+ [EI, FE]
end.
-format_mfa(Indent0, {M,F,Args}=StartF, #{encoding:=Enc,single_line:=Single}=Extra) ->
+format_mfa(Indent0, {M,F,Args}=StartF, Extra, Limit) ->
+ #{encoding:=Enc,single_line:=Single} = Extra,
Indent = if Single -> "";
true -> Indent0
end,
try
A = length(Args),
- [Indent,"initial call: ",atom_to_list(M),$:,to_string(F, Enc),$/,
+ [Indent,"initial call: ",to_string(M, Enc),$:,to_string(F, Enc),$/,
integer_to_list(A)]
catch
error:_ ->
- format_tag(Indent, initial_call, StartF, Extra)
+ format_tag(Indent, initial_call, StartF, Extra, Limit)
end.
to_string(A, latin1) ->
@@ -870,27 +988,25 @@ to_string(A, latin1) ->
to_string(A, _) ->
io_lib:write_atom(A).
-pp_fun(#{encoding:=Enc,depth:=Depth,chars_limit:=Limit,single_line:=Single}) ->
+pp_fun(Extra, Enc) ->
+ #{encoding:=Enc,depth:=Depth, single_line:=Single} = Extra,
{P,Tl} = p(Enc, Depth),
Width = if Single -> "0";
true -> ""
end,
- Opts = if is_integer(Limit) -> [{chars_limit,Limit}];
- true -> []
- end,
- fun(Term, I) ->
- io_lib:format("~" ++ Width ++ "." ++ integer_to_list(I) ++ P,
- [Term|Tl], Opts)
+ fun(Term, I, Limit) ->
+ S = io_lib:format("~" ++ Width ++ "." ++ integer_to_list(I) ++ P,
+ [Term|Tl], [{chars_limit, Limit}]),
+ {S, sub(Limit, S, Enc)}
end.
-format_tag(Indent0, Tag, Data, #{encoding:=Enc,depth:=Depth,chars_limit:=Limit,single_line:=Single}) ->
+format_tag(Indent0, Tag, Data, Extra, Limit) ->
+ #{encoding:=Enc,depth:=Depth,single_line:=Single} = Extra,
{P,Tl} = p(Enc, Depth),
{Indent,Width} = if Single -> {"","0"};
true -> {Indent0,""}
end,
- Opts = if is_integer(Limit) -> [{chars_limit,Limit}];
- true -> []
- end,
+ Opts = chars_limit_opt(Limit),
io_lib:format("~s~" ++ Width ++ "p: ~" ++ Width ++ ".18" ++ P,
[Indent, Tag, Data|Tl], Opts).
@@ -902,12 +1018,35 @@ p(Encoding, Depth) ->
P = modifier(Encoding) ++ Letter,
{P, Tl}.
+report_separator() -> "; ".
+
+part_separator() -> ", ".
+
+chars_limit_opt(CharsLimit) ->
+ [{chars_limit, CharsLimit} || is_integer(CharsLimit)].
+
modifier(latin1) -> "";
modifier(_) -> "t".
nl(true,Else) -> Else;
nl(false,_) -> "\n".
+%% Make sure T does change sign.
+sub(T, _, _Enc) when T < 0 -> T;
+sub(T, E, Enc) ->
+ Sz = size(Enc, E),
+ if
+ T >= Sz ->
+ T - Sz;
+ true ->
+ 0
+ end.
+
+size(latin1, S) ->
+ iolist_size(S);
+size(_, S) ->
+ string:length(S).
+
%%% -----------------------------------------------------------
%%% Stop a process and wait for it to terminate
%%% -----------------------------------------------------------
diff --git a/lib/stdlib/src/proplists.erl b/lib/stdlib/src/proplists.erl
index 3ce68887ae..9216c3bdb3 100644
--- a/lib/stdlib/src/proplists.erl
+++ b/lib/stdlib/src/proplists.erl
@@ -220,7 +220,7 @@ get_value(Key, [P | Ps], Default) ->
{_, Value} ->
Value;
_ ->
- %% Don</code>t continue the search!
+ %% Don't continue the search!
Default
end;
true ->
@@ -419,7 +419,7 @@ substitute_aliases_1([], P) ->
%% <p>Example: <code>substitute_negations([{no_foo, foo}], L)</code>
%% will replace any atom <code>no_foo</code> or tuple <code>{no_foo,
%% true}</code> in <code>L</code> with <code>{foo, false}</code>, and
-%% any other tuple <code>{no_foo, ...}</code> with <code>foo</code.</p>
+%% any other tuple <code>{no_foo, ...}</code> with <code>foo</code>.</p>
%%
%% @see get_bool/2
%% @see substitute_aliases/2
@@ -639,24 +639,24 @@ normalize(L, []) ->
Rest :: [term()].
split(List, Keys) ->
- {Store, Rest} = split(List, dict:from_list([{K, []} || K <- Keys]), []),
- {[lists:reverse(dict:fetch(K, Store)) || K <- Keys],
+ {Store, Rest} = split(List, maps:from_list([{K, []} || K <- Keys]), []),
+ {[lists:reverse(map_get(K, Store)) || K <- Keys],
lists:reverse(Rest)}.
split([P | Ps], Store, Rest) ->
if is_atom(P) ->
- case dict:is_key(P, Store) of
+ case is_map_key(P, Store) of
true ->
- split(Ps, dict_prepend(P, P, Store), Rest);
+ split(Ps, maps_prepend(P, P, Store), Rest);
false ->
split(Ps, Store, [P | Rest])
end;
tuple_size(P) >= 1 ->
%% Note that Key does not have to be an atom in this case.
Key = element(1, P),
- case dict:is_key(Key, Store) of
+ case is_map_key(Key, Store) of
true ->
- split(Ps, dict_prepend(Key, P, Store), Rest);
+ split(Ps, maps_prepend(Key, P, Store), Rest);
false ->
split(Ps, Store, [P | Rest])
end;
@@ -666,5 +666,5 @@ split([P | Ps], Store, Rest) ->
split([], Store, Rest) ->
{Store, Rest}.
-dict_prepend(Key, Val, Dict) ->
- dict:store(Key, [Val | dict:fetch(Key, Dict)], Dict).
+maps_prepend(Key, Val, Dict) ->
+ Dict#{Key := [Val | map_get(Key, Dict)]}.
diff --git a/lib/stdlib/src/qlc.erl b/lib/stdlib/src/qlc.erl
index a1c1117e31..713ed1f896 100644
--- a/lib/stdlib/src/qlc.erl
+++ b/lib/stdlib/src/qlc.erl
@@ -785,7 +785,7 @@ merge_binding_structs(Bs1, Bs2) ->
aux_name1(Name, N, AllNames) ->
SN = name_suffix(Name, N),
- case sets:is_element(SN, AllNames) of
+ case gb_sets:is_member(SN, AllNames) of
true -> aux_name1(Name, N + 1, AllNames);
false -> {SN, N}
end.
@@ -1357,7 +1357,7 @@ flatten_abstr(E, VN, _Vars, Body) ->
{VN, Body, E}.
abstract_vars(Abstract) ->
- sets:from_list(ordsets:to_list(vars(Abstract))).
+ gb_sets:from_list(ordsets:to_list(vars(Abstract))).
collect([]=L) ->
L;
diff --git a/lib/stdlib/src/qlc_pt.erl b/lib/stdlib/src/qlc_pt.erl
index 4a39f8ae9d..7cf631d85d 100644
--- a/lib/stdlib/src/qlc_pt.erl
+++ b/lib/stdlib/src/qlc_pt.erl
@@ -511,7 +511,7 @@ used_genvar_check(FormsNoShadows, State) ->
Acc0 = {State#state.intro_vars, [{atom, anno0(), true}]},
{_, {[], Exprs}} = qual_fold(F, Acc0, [], FormsNoShadows, State),
FunctionNames = [Name || {function, _, Name, _, _} <- FormsNoShadows],
- UniqueFName = qlc:aux_name(used_genvar, 1, sets:from_list(FunctionNames)),
+ UniqueFName = qlc:aux_name(used_genvar, 1, gb_sets:from_list(FunctionNames)),
A = anno0(),
{function,A,UniqueFName,0,[{clause,A,[],[],lists:reverse(Exprs)}]}.
@@ -613,8 +613,8 @@ q_intro_vars(QId, [{QId, IVs} | QsIVs], IVsSoFar) -> {QsIVs, IVs ++ IVsSoFar}.
transform(FormsNoShadows, State) ->
_ = erlang:system_flag(backtrace_depth, 500),
IntroVars = State#state.intro_vars,
- AllVars = sets:from_list(ordsets:to_list(qlc:vars(FormsNoShadows))),
- ?DEBUG("AllVars = ~p~n", [sets:to_list(AllVars)]),
+ AllVars = gb_sets:from_list(ordsets:to_list(qlc:vars(FormsNoShadows))),
+ ?DEBUG("AllVars = ~p~n", [gb_sets:to_list(AllVars)]),
F1 = fun(QId, {generate,_,P,LE}, Foo, {GoI,SI}) ->
{{QId,GoI,SI,{gen,P,LE}},Foo,{GoI + 3, SI + 2}};
(QId, F, Foo, {GoI,SI}) ->
@@ -632,10 +632,10 @@ transform(FormsNoShadows, State) ->
{_,Source0} = qual_fold(fun(_QId, {generate,_,_P,_E}=Q, Dict, Foo) ->
{Q,Dict,Foo};
(QId, F, Dict, Foo) ->
- {F,dict:store(QId, F, Dict),Foo}
- end, dict:new(), [], FormsNoShadows, State),
+ {F,maps:put(QId, F, Dict),Foo}
+ end, maps:new(), [], FormsNoShadows, State),
{_,Source} = qlc_mapfold(fun(Id, {lc,_L,E,_Qs}=LC, Dict) ->
- {LC,dict:store(Id, E, Dict)}
+ {LC,maps:put(Id, E, Dict)}
end, Source0, FormsNoShadows, State),
@@ -685,7 +685,7 @@ transform(FormsNoShadows, State) ->
FunW = {'fun',L,{clauses,[{clause,L,AsW,[],
[{match,L,{var,L,Fun},FunC},
{call,L,{var,L,Fun},As0}]}]}},
- {ok, OrigE0} = dict:find(Id, Source),
+ OrigE0 = map_get(Id, Source),
OrigE = undo_no_shadows(OrigE0, State),
QCode = qcode(OrigE, XQCs, Source, L, State),
Qdata = qdata(XQCs, L),
@@ -2361,7 +2361,7 @@ qcode(E, QCs, Source, L, State) ->
qcode([{_QId, {_QIvs, {{gen,P,_LE,_GV}, GoI, _SI}}} | QCs], Source, State) ->
[{GoI,undo_no_shadows(P, State)} | qcode(QCs, Source, State)];
qcode([{QId, {_QIVs, {{fil,_F}, GoI, _SI}}} | QCs], Source, State) ->
- {ok,OrigF} = dict:find(QId, Source),
+ OrigF = map_get(QId, Source),
[{GoI,undo_no_shadows(OrigF, State)} | qcode(QCs, Source, State)];
qcode([], _Source, _State) ->
[].
@@ -2666,12 +2666,12 @@ no_shadows(Forms0, State) ->
%%
%% The original names of variables are kept in a table in State.
%% undo_no_shadows/2 re-creates the original code.
- AllVars = sets:from_list(ordsets:to_list(qlc:vars(Forms0))),
- ?DEBUG("nos AllVars = ~p~n", [sets:to_list(AllVars)]),
+ AllVars = gb_sets:from_list(ordsets:to_list(qlc:vars(Forms0))),
+ ?DEBUG("nos AllVars = ~p~n", [gb_sets:to_list(AllVars)]),
VFun = fun(_Id, LC, Vs) -> nos(LC, Vs) end,
LI = ets:new(?APIMOD,[]),
UV = ets:new(?APIMOD,[]),
- D0 = dict:new(),
+ D0 = maps:new(),
S1 = {LI, D0, UV, AllVars, [], State},
_ = qlc_mapfold(VFun, S1, Forms0, State),
?DEBUG("UsedIntroVars = ~p~n", [ets:match_object(UV, '_')]),
@@ -2781,7 +2781,7 @@ nos_var(Anno, Name, State) ->
end.
used_var(V, Vs, UV) ->
- case dict:find(V, Vs) of
+ case maps:find(V, Vs) of
{ok,Value} ->
VN = qlc:name_suffix(V, Value),
_ = ets:update_counter(UV, VN, 1),
@@ -2796,10 +2796,10 @@ next_var(V, Vs, AllVars, LI, UV) ->
end,
true = ets:insert(LI, {V, NValue}),
VN = qlc:name_suffix(V, NValue),
- case sets:is_element(VN, AllVars) of
+ case gb_sets:is_member(VN, AllVars) of
true -> next_var(V, Vs, AllVars, LI, UV);
false -> true = ets:insert(UV, {VN, 0}),
- NVs = dict:store(V, NValue, Vs),
+ NVs = maps:put(V, NValue, Vs),
{VN, NVs}
end.
diff --git a/lib/stdlib/src/queue.erl b/lib/stdlib/src/queue.erl
index 11c0aa8d2b..9fe3782f92 100644
--- a/lib/stdlib/src/queue.erl
+++ b/lib/stdlib/src/queue.erl
@@ -37,7 +37,7 @@
%% Mis-spelled, deprecated.
-export([lait/1]).
--deprecated([lait/1]).
+-deprecated([{lait,1,"use queue:liat/1 instead"}]).
%%--------------------------------------------------------------------------
%% Efficient implementation of double ended fifo queues
diff --git a/lib/stdlib/src/random.erl b/lib/stdlib/src/random.erl
index 46dabb4323..8d6a35f031 100644
--- a/lib/stdlib/src/random.erl
+++ b/lib/stdlib/src/random.erl
@@ -18,7 +18,7 @@
%% %CopyrightEnd%
%%
-module(random).
--deprecated(module).
+-deprecated({'_','_',"use the 'rand' module instead"}).
%% Reasonable random number generator.
%% The method is attributed to B. A. Wichmann and I. D. Hill
diff --git a/lib/stdlib/src/shell.erl b/lib/stdlib/src/shell.erl
index 041a89f909..b397b2fc36 100644
--- a/lib/stdlib/src/shell.erl
+++ b/lib/stdlib/src/shell.erl
@@ -1186,6 +1186,8 @@ record_bindings(Recs0, Bs0) ->
read_records(FileOrModule, Opts0) ->
Opts = lists:delete(report_warnings, Opts0),
case find_file(FileOrModule) of
+ {beam, Beam, File} ->
+ read_records_from_beam(Beam, File);
{files,[File]} ->
read_file_records(File, Opts);
{files,Files} ->
@@ -1204,10 +1206,22 @@ read_records(FileOrModule, Opts0) ->
find_file(Mod) when is_atom(Mod) ->
case code:which(Mod) of
File when is_list(File) ->
- {files,[File]};
- preloaded ->
- {_M,_Bin,File} = code:get_object_code(Mod),
- {files,[File]};
+ %% Special cases:
+ %% - Modules not in the code path (loaded with code:load_abs/1):
+ %% code:get_object_code/1 only searches in the code path
+ %% but code:which/1 finds all loaded modules
+ %% - File can also be a file in an archive,
+ %% beam_lib:chunks/2 cannot handle such paths but
+ %% erl_prim_loader:get_file/1 can
+ case erl_prim_loader:get_file(File) of
+ {ok, Beam, _} ->
+ {beam, Beam, File};
+ error ->
+ {error, nofile}
+ end;
+ preloaded ->
+ {_M, Beam, File} = code:get_object_code(Mod),
+ {beam, Beam, File};
_Else -> % non_existing, interpreted, cover_compiled
{error,nofile}
end;
@@ -1222,28 +1236,31 @@ find_file(File) ->
read_file_records(File, Opts) ->
case filename:extension(File) of
".beam" ->
- case beam_lib:chunks(File, [abstract_code,"CInf"]) of
- {ok,{_Mod,[{abstract_code,{Version,Forms}},{"CInf",CB}]}} ->
- case record_attrs(Forms) of
- [] when Version =:= raw_abstract_v1 ->
- [];
- [] ->
- %% If the version is raw_X, then this test
- %% is unnecessary.
- try_source(File, CB);
- Records ->
- Records
- end;
- {ok,{_Mod,[{abstract_code,no_abstract_code},{"CInf",CB}]}} ->
- try_source(File, CB);
- Error ->
- %% Could be that the "Abst" chunk is missing (pre R6).
- Error
- end;
+ read_records_from_beam(File, File);
_ ->
parse_file(File, Opts)
end.
+read_records_from_beam(Beam, File) ->
+ case beam_lib:chunks(Beam, [abstract_code,"CInf"]) of
+ {ok,{_Mod,[{abstract_code,{Version,Forms}},{"CInf",CB}]}} ->
+ case record_attrs(Forms) of
+ [] when Version =:= raw_abstract_v1 ->
+ [];
+ [] ->
+ %% If the version is raw_X, then this test
+ %% is unnecessary.
+ try_source(File, CB);
+ Records ->
+ Records
+ end;
+ {ok,{_Mod,[{abstract_code,no_abstract_code},{"CInf",CB}]}} ->
+ try_source(File, CB);
+ Error ->
+ %% Could be that the "Abst" chunk is missing (pre R6).
+ Error
+ end.
+
%% This is how the debugger searches for source files. See int.erl.
try_source(Beam, RawCB) ->
EbinDir = filename:dirname(Beam),
diff --git a/lib/stdlib/src/shell_default.erl b/lib/stdlib/src/shell_default.erl
index a0c1d98513..4819beb62b 100644
--- a/lib/stdlib/src/shell_default.erl
+++ b/lib/stdlib/src/shell_default.erl
@@ -28,6 +28,7 @@
erlangrc/1,bi/1, regs/0, flush/0,pwd/0,ls/0,ls/1,cd/1,
y/1, y/2,
xm/1, bt/1, q/0,
+ h/1, h/2, h/3, ht/1, ht/2, ht/3, hcb/1, hcb/2, hcb/3,
ni/0, nregs/0]).
-export([ih/0,iv/0,im/0,ii/1,ii/2,iq/1,ini/1,ini/2,inq/1,ib/2,ib/3,
@@ -43,7 +44,16 @@ help() ->
format("e(N) -- repeat the expression in query <N>\n"),
format("f() -- forget all variable bindings\n"),
format("f(X) -- forget the binding of variable X\n"),
- format("h() -- history\n"),
+ format("h() -- history\n"),
+ format("h(Mod) -- help about module\n"),
+ format("h(Mod,Func)-- help about function in module\n"),
+ format("h(Mod,Func,Arity) -- help about function with arity in module\n"),
+ format("ht(Mod) -- help about a module's types\n"),
+ format("ht(Mod,Type) -- help about type in module\n"),
+ format("ht(Mod,Type,Arity) -- help about type with arity in module\n"),
+ format("hcb(Mod) -- help about a module's callbacks\n"),
+ format("hcb(Mod,CB) -- help about callback in module\n"),
+ format("hcb(Mod,CB,Arity) -- help about callback with arity in module\n"),
format("history(N) -- set how many previous commands to keep\n"),
format("results(N) -- set how many previous command results to keep\n"),
format("catch_exception(B) -- how exceptions are handled\n"),
@@ -76,6 +86,15 @@ c(File, Opt, Filter) -> c:c(File, Opt, Filter).
cd(D) -> c:cd(D).
erlangrc(X) -> c:erlangrc(X).
flush() -> c:flush().
+h(M) -> c:h(M).
+h(M,F) -> c:h(M,F).
+h(M,F,A) -> c:h(M,F,A).
+ht(M) -> c:ht(M).
+ht(M,F) -> c:ht(M,F).
+ht(M,F,A) -> c:ht(M,F,A).
+hcb(M) -> c:hcb(M).
+hcb(M,F) -> c:hcb(M,F).
+hcb(M,F,A) -> c:hcb(M,F,A).
i() -> c:i().
i(X,Y,Z) -> c:i(X,Y,Z).
l(Mod) -> c:l(Mod).
diff --git a/lib/stdlib/src/shell_docs.erl b/lib/stdlib/src/shell_docs.erl
new file mode 100644
index 0000000000..5abb3a7f0c
--- /dev/null
+++ b/lib/stdlib/src/shell_docs.erl
@@ -0,0 +1,1018 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(shell_docs).
+
+-include_lib("kernel/include/eep48.hrl").
+
+-export([render/2, render/3, render/4, render/5]).
+-export([render_type/2, render_type/3, render_type/4, render_type/5]).
+-export([render_callback/2, render_callback/3, render_callback/4, render_callback/5]).
+
+%% Used by chunks.escript in erl_docgen
+-export([validate/1, normalize/1]).
+
+%% Convinience functions
+-export([get_doc/1, get_doc/3, get_type_doc/3, get_callback_doc/3]).
+
+-record(config, { docs,
+ encoding,
+ ansi,
+ io_opts = io:getopts(),
+ columns
+ }).
+
+-define(ALL_ELEMENTS,[a,p,'div',br,h1,h2,h3,i,em,pre,code,ul,ol,li,dl,dt,dd]).
+%% inline elements are:
+-define(INLINE,[i,em,code,a]).
+-define(IS_INLINE(ELEM),(((ELEM) =:= a) orelse ((ELEM) =:= code)
+ orelse ((ELEM) =:= i) orelse ((ELEM) =:= em))).
+%% non-inline elements are:
+-define(BLOCK,[p,'div',pre,br,ul,ol,li,dl,dt,dd,h1,h2,h3]).
+-define(IS_BLOCK(ELEM),not ?IS_INLINE(ELEM)).
+-define(IS_PRE(ELEM),(((ELEM) =:= pre))).
+
+%% If you update the below types, make sure to update the documentation in
+%% erl_docgen/doc/src/doc_storage.xml as well!!!
+-type docs_v1() :: #docs_v1{}.
+-type config() :: #{ encoding => unicode | latin1,
+ columns => pos_integer(),
+ ansi => boolean() }.
+-type chunk_elements() :: [chunk_element()].
+-type chunk_element() :: {chunk_element_type(),chunk_element_attrs(),
+ chunk_elements()} | binary().
+-type chunk_element_attrs() :: [chunk_element_attr()].
+-type chunk_element_attr() :: {atom(),unicode:chardata()}.
+-type chunk_element_type() :: chunk_element_inline_type() | chunk_element_block_type().
+-type chunk_element_inline_type() :: a | code | em | i.
+-type chunk_element_block_type() :: p | 'div' | br | pre | ul |
+ ol | li | dl | dt | dd | h1 | h2 | h3.
+
+-spec validate(Module) -> ok when
+ Module :: module() | docs_v1().
+%% Simple validation of erlang doc chunk. Check that all tags are supported and
+%% that the signature is correct.
+validate(Module) when is_atom(Module) ->
+ {ok, Doc} = code:get_doc(Module),
+ validate(Doc);
+validate(#docs_v1{ module_doc = MDocs, docs = AllDocs }) ->
+
+ %% Check some macro in-variants
+ AE = lists:sort(?ALL_ELEMENTS),
+ AE = lists:sort(?INLINE ++ ?BLOCK),
+ true = lists:all(fun(Elem) -> ?IS_INLINE(Elem) end, ?INLINE),
+ true = lists:all(fun(Elem) -> ?IS_BLOCK(Elem) end, ?BLOCK),
+
+ _ = validate_docs(MDocs),
+ lists:foreach(fun({_,_Anno, Sig, Docs, _Meta}) ->
+ case lists:all(fun erlang:is_binary/1, Sig) of
+ false -> throw({invalid_signature,Sig});
+ true -> ok
+ end,
+ validate_docs(Docs)
+ end, AllDocs),
+ ok.
+
+validate_docs(hidden) ->
+ ok;
+validate_docs(none) ->
+ ok;
+validate_docs(#{} = MDocs) ->
+ _ = maps:map(fun(_Key,MDoc) -> validate_docs(MDoc,[]) end, MDocs),
+ ok.
+validate_docs([H|T],Path) when is_tuple(H) ->
+ _ = validate_docs(H,Path),
+ validate_docs(T,Path);
+validate_docs({br,Attr,Content} = Br,Path) ->
+ if Attr =:= [], Content =:= [] ->
+ ok;
+ true ->
+ throw({content_to_allowed_in_br,Br,Path})
+ end;
+validate_docs({Tag,Attr,Content},Path) ->
+
+ %% Test that we only have li's within ul and ol
+ case (Tag =/= li) andalso (length(Path) > 0) andalso ((hd(Path) =:= ul) orelse (hd(Path) =:= ol)) of
+ true ->
+ throw({only_li_allowed_within_ul_or_ol,Tag,Path});
+ _ ->
+ ok
+ end,
+
+ %% Test that we only have dd's and dt's within dl
+ case (Tag =/= dd) andalso (Tag =/= dt) andalso (length(Path) > 0) andalso (hd(Path) =:= dl) of
+ true ->
+ throw({only_dd_or_dt_allowed_within_dl,Tag,Path});
+ _ ->
+ ok
+ end,
+
+ %% Test that we do not have p's within p's
+ case Tag =:= p andalso lists:member(p, Path) of
+ true ->
+ throw({nested_p_not_allowed,Tag,Path});
+ false ->
+ ok
+ end,
+ %% Test that there are no block tags within a pre, h1, h2 or h3
+ case lists:member(pre,Path) or lists:member(h1,Path) or
+ lists:member(h2,Path) or lists:member(h3,Path) of
+ true when ?IS_BLOCK(Tag) ->
+ throw({cannot_put_block_tag_within_pre,Tag,Path});
+ _ ->
+ ok
+ end,
+ %% Test that a block tag is not within an inline tag
+ case lists:member(Tag,?BLOCK) of
+ true ->
+ case lists:any(fun(P) -> ?IS_INLINE(P) end, Path) of
+ true ->
+ throw({cannot_put_inline_tag_outside_block, Tag, Path});
+ false ->
+ ok
+ end;
+ false ->
+ ok
+ end,
+ case lists:member(Tag,?ALL_ELEMENTS) of
+ false ->
+ throw({invalid_tag,Tag,Path});
+ true ->
+ ok
+ end,
+ case lists:all(fun({Key,Val}) -> is_atom(Key) andalso is_binary(Val) end,Attr) of
+ true -> ok;
+ false -> throw({invalid_attribute,{Tag,Attr}})
+ end,
+ validate_docs(Content,[Tag | Path]);
+validate_docs([Chars | T], Path) when is_binary(Chars) ->
+ validate_docs(T, Path);
+validate_docs([],_) ->
+ ok.
+
+%% Follows algorithm described here:
+%% * https://medium.com/@patrickbrosset/when-does-white-space-matter-in-html-b90e8a7cdd33
+%% which in turn follows this:
+%% * https://www.w3.org/TR/css-text-3/#white-space-processing
+-spec normalize(Docs) -> NormalizedDocs when
+ Docs :: chunk_elements(),
+ NormalizedDocs :: chunk_elements().
+normalize(Docs) ->
+ Trimmed = normalize_trim(Docs,true),
+ normalize_space(Trimmed).
+
+normalize_trim(Bin,true) when is_binary(Bin) ->
+ %% Remove any whitespace (except \n) before or after a newline
+ NoSpace = re:replace(Bin,"[^\\S\n]*\n+[^\\S\n]*","\n",[unicode,global]),
+ %% Replace any tabs with space
+ NoTab = re:replace(NoSpace,"\t"," ",[unicode,global]),
+ %% Replace any newlines with space
+ NoNewLine = re:replace(NoTab,"\\v"," ",[unicode,global]),
+ %% Replace any sequences of \s with a single " "
+ re:replace(NoNewLine,"\\s+"," ",[unicode,global,{return,binary}]);
+normalize_trim(Bin,false) when is_binary(Bin) ->
+ Bin;
+normalize_trim([{pre,Attr,Content}|T],Trim) ->
+ [{pre,Attr,normalize_trim(Content,false)} | normalize_trim(T,Trim)];
+normalize_trim([{Tag,Attr,Content}|T],Trim) ->
+ [{Tag,Attr,normalize_trim(Content,Trim)} | normalize_trim(T,Trim)];
+normalize_trim([<<>>|T],Trim) ->
+ normalize_trim(T,Trim);
+normalize_trim([B1,B2|T],Trim) when is_binary(B1),is_binary(B2) ->
+ normalize_trim([<<B1/binary,B2/binary>> | T],Trim);
+normalize_trim([H|T],Trim) ->
+ [normalize_trim(H,Trim) | normalize_trim(T,Trim)];
+normalize_trim([],_Trim) ->
+ [].
+
+%% We want to remove any duplicate spaces, even if they
+%% cross into other inline elements.
+%% For non-inline elements we just need to make sure that any
+%% leading or trailing spaces are stripped.
+normalize_space([{Pre,Attr,Content}|T]) when ?IS_PRE(Pre) ->
+ [{Pre,Attr,trim_first_and_last(Content,$\n)} | normalize_space(T)];
+normalize_space([{Block,Attr,Content}|T]) when ?IS_BLOCK(Block) ->
+ [{Block,Attr,normalize_space(Content)} | normalize_space(T)];
+normalize_space([]) ->
+ [];
+normalize_space(Elems) ->
+ {InlineElems, T} =
+ lists:splitwith(fun(E) ->
+ is_binary(E) orelse (is_tuple(E) andalso ?IS_INLINE(element(1,E)))
+ end, Elems),
+ trim_inline(InlineElems) ++ normalize_space(T).
+
+trim_inline(Content) ->
+ {NewContent,_} = trim_inline(Content,false),
+ trim_first_and_last(NewContent,$ ).
+trim_inline([Bin|T],false) when is_binary(Bin) ->
+ LastElem = binary:at(Bin,byte_size(Bin)-1),
+ case trim_inline(T,LastElem =:= $ ) of
+ {[B2 | NewT],NewState} when is_binary(B2) ->
+ {[<<Bin/binary,B2/binary>>|NewT],NewState};
+ {NewT, NewState} ->
+ {[Bin|NewT],NewState}
+ end;
+trim_inline([<<" ">>|T],true) ->
+ trim_inline(T,true);
+trim_inline([<<" ",Bin/binary>>|T],true) when is_binary(Bin) ->
+ trim_inline([Bin | T],true);
+trim_inline([Bin|T],true) when is_binary(Bin) ->
+ trim_inline([Bin|T],false);
+trim_inline([{Elem,Attr,Content}|T],TrimSpace) ->
+ {NewContent,ContentTrimSpace} = trim_inline(Content,TrimSpace),
+ {NewT,TTrimSpace} = trim_inline(T,ContentTrimSpace),
+ IsAnchor = (Elem =:= a) andalso proplists:is_defined(id,Attr),
+ if NewContent == [] andalso (not IsAnchor) ->
+ %% Remove if all content has been trimmed and this is not an anchor
+ {NewT, TTrimSpace};
+ true ->
+ {[{Elem,Attr,NewContent} | NewT], TTrimSpace}
+ end;
+trim_inline([],TrimSpace) ->
+ {[],TrimSpace}.
+
+
+%% This function removes the first and last What from the content.
+%% This is complicated by the fact that the first or last element
+%% may not have any binary, or have the binary deeply nested within.
+trim_first_and_last(Content, What) when What < 256 ->
+ {FirstTrimmed, _} = trim_first(Content,What),
+ {LastTrimmed, _} = trim_last(FirstTrimmed,What),
+ LastTrimmed.
+
+trim_first([Bin|T],What) when is_binary(Bin) ->
+ case Bin of
+ <<What>> ->
+ {T,true};
+ <<What,NewBin/binary>> ->
+ {[NewBin|T],true};
+ Bin ->
+ {[Bin|T],true}
+ end;
+trim_first([{Elem,Attr,Content} = Tag|T],What) ->
+ case trim_first(Content,What) of
+ {[],true} ->
+ {T,true};
+ {NewContent,true} ->
+ {[{Elem,Attr,NewContent}|T],true};
+ {Content,false} ->
+ {NewT,NewState} = trim_first(T,What),
+ {[Tag | NewT],NewState}
+ end;
+trim_first([],_What) ->
+ {[],false}.
+
+trim_last([Bin | T],What) when is_binary(Bin) ->
+ case trim_last(T,What) of
+ {NewT,true} ->
+ {[Bin | NewT],true};
+ {T,false} ->
+ PreSz = byte_size(Bin)-1,
+ case Bin of
+ <<What>> -> {T,true};
+ <<NewBin:PreSz/binary,What>> ->
+ {[NewBin|T],true};
+ Bin ->
+ {[Bin|T],true}
+ end
+ end;
+trim_last([{Elem,Attr,Content} = Tag|T],What) ->
+ case trim_last(T,What) of
+ {NewT,true} ->
+ {[Tag | NewT],true};
+ {T,false} ->
+ case trim_last(Content,What) of
+ {[],true} ->
+ %% If the content became empty and we processed some text
+ %% we remove the element.
+ {[],true};
+ {NewContent,NewState} ->
+ {[{Elem,Attr,NewContent}|T],NewState}
+ end
+ end;
+trim_last([],_What) ->
+ {[],false}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% API function for dealing with the function documentation
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec get_doc(Module :: module()) -> chunk_elements().
+get_doc(Module) ->
+ {ok, #docs_v1{ module_doc = ModuleDoc } } = code:get_doc(Module),
+ get_local_doc(Module, ModuleDoc).
+
+-spec get_doc(Module :: module(), Function, Arity) ->
+ [{{Function,Arity}, Anno, Signature, chunk_elements(), Metadata}] when
+ Function :: atom(),
+ Arity :: arity(),
+ Anno :: erl_anno:anno(),
+ Signature :: [binary()],
+ Metadata :: #{}.
+get_doc(Module, Function, Arity) ->
+ {ok, #docs_v1{ docs = Docs } } = code:get_doc(Module),
+ FnFunctions =
+ lists:filter(fun({{function, F, A},_Anno,_Sig,_Doc,_Meta}) ->
+ F =:= Function andalso A =:= Arity;
+ (_) ->
+ false
+ end, Docs),
+
+ [{F,A,S,get_local_doc({F,A},D),M} || {F,A,S,D,M} <- FnFunctions].
+
+-spec render(Module, Docs) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1().
+render(Module, #docs_v1{ } = D) when is_atom(Module) ->
+ render(Module, D, #{}).
+
+-spec render(Module, Docs, Config) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1(),
+ Config :: config();
+
+ (Module, Function, Docs) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error,function_missing}.
+render(Module, #docs_v1{ module_doc = ModuleDoc } = D, Config)
+ when is_atom(Module), is_map(Config) ->
+ render_headers_and_docs([[{h2,[],[<<"\t",(atom_to_binary(Module))/binary>>]}]],
+ get_local_doc(Module, ModuleDoc), D, Config);
+render(_Module, Function, #docs_v1{ } = D) ->
+ render(_Module, Function, D, #{}).
+
+-spec render(Module, Function, Docs, Config) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error,function_missing};
+
+ (Module, Function, Arity, Docs) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error,function_missing}.
+render(Module, Function, #docs_v1{ docs = Docs } = D, Config)
+ when is_atom(Module), is_atom(Function), is_map(Config) ->
+ render_function(
+ lists:filter(fun({{function, F, _},_Anno,_Sig,_Doc,_Meta}) ->
+ F =:= Function;
+ (_) ->
+ false
+ end, Docs), D, Config);
+render(_Module, Function, Arity, #docs_v1{ } = D) ->
+ render(_Module, Function, Arity, D, #{}).
+
+-spec render(Module, Function, Arity, Docs, Config) -> Res when
+ Module :: module(),
+ Function :: atom(),
+ Arity :: arity(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error,function_missing}.
+render(Module, Function, Arity, #docs_v1{ docs = Docs } = D, Config)
+ when is_atom(Module), is_atom(Function), is_integer(Arity), is_map(Config) ->
+ render_function(
+ lists:filter(fun({{function, F, A},_Anno,_Sig,_Doc,_Meta}) ->
+ F =:= Function andalso A =:= Arity;
+ (_) ->
+ false
+ end, Docs), D, Config).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% API function for dealing with the type documentation
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec get_type_doc(Module :: module(), Type :: atom(), Arity :: arity()) ->
+ [{{Type,Arity}, Anno, Signature, chunk_elements(), Metadata}] when
+ Type :: atom(),
+ Arity :: arity(),
+ Anno :: erl_anno:anno(),
+ Signature :: [binary()],
+ Metadata :: #{}.
+get_type_doc(Module, Type, Arity) ->
+ {ok, #docs_v1{ docs = Docs } } = code:get_doc(Module),
+ FnFunctions =
+ lists:filter(fun({{type, T, A},_Anno,_Sig,_Doc,_Meta}) ->
+ T =:= Type andalso A =:= Arity;
+ (_) ->
+ false
+ end, Docs),
+ [{F,A,S,get_local_doc(F, D),M} || {F,A,S,D,M} <- FnFunctions].
+
+-spec render_type(Module, Docs) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1().
+render_type(Module, D) ->
+ render_type(Module, D, #{}).
+
+-spec render_type(Module, Docs, Config) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1(),
+ Config :: config();
+ (Module, Type, Docs) -> Res when
+ Module :: module(), Type :: atom(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, type_missing}.
+render_type(Module, D = #docs_v1{}, Config) ->
+ render_signature_listing(Module, type, D, Config);
+render_type(Module, Type, D = #docs_v1{}) ->
+ render_type(Module, Type, D, #{}).
+
+-spec render_type(Module, Type, Docs, Config) -> Res when
+ Module :: module(), Type :: atom(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, type_missing};
+ (Module, Type, Arity, Docs) -> Res when
+ Module :: module(), Type :: atom(), Arity :: arity(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, type_missing}.
+render_type(_Module, Type, #docs_v1{ docs = Docs } = D, Config) ->
+ render_typecb_docs(
+ lists:filter(fun({{type, T, _},_Anno,_Sig,_Doc,_Meta}) ->
+ T =:= Type;
+ (_) ->
+ false
+ end, Docs), D, Config);
+render_type(_Module, Type, Arity, #docs_v1{ } = D) ->
+ render_type(_Module, Type, Arity, D, #{}).
+
+-spec render_type(Module, Type, Arity, Docs, Config) -> Res when
+ Module :: module(), Type :: atom(), Arity :: arity(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, type_missing}.
+render_type(_Module, Type, Arity, #docs_v1{ docs = Docs } = D, Config) ->
+ render_typecb_docs(
+ lists:filter(fun({{type, T, A},_Anno,_Sig,_Doc,_Meta}) ->
+ T =:= Type andalso A =:= Arity;
+ (_) ->
+ false
+ end, Docs), D, Config).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% API function for dealing with the callback documentation
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec get_callback_doc(Module :: module(), Callback :: atom(), Arity :: arity()) ->
+ [{{Callback,Arity}, Anno, Signature, chunk_elements(), Metadata}] when
+ Callback :: atom(),
+ Arity :: arity(),
+ Anno :: erl_anno:anno(),
+ Signature :: [binary()],
+ Metadata :: #{}.
+get_callback_doc(Module, Callback, Arity) ->
+ {ok, #docs_v1{ docs = Docs } } = code:get_doc(Module),
+ FnFunctions =
+ lists:filter(fun({{callback, T, A},_Anno,_Sig,_Doc,_Meta}) ->
+ T =:= Callback andalso A =:= Arity;
+ (_) ->
+ false
+ end, Docs),
+ [{F,A,S,get_local_doc(F, D),M} || {F,A,S,D,M} <- FnFunctions].
+
+-spec render_callback(Module, Docs) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1().
+render_callback(Module, D) ->
+ render_callback(Module, D, #{}).
+
+-spec render_callback(Module, Docs, Config) -> unicode:chardata() when
+ Module :: module(),
+ Docs :: docs_v1(),
+ Config :: config();
+ (Module, Callback, Docs) -> Res when
+ Module :: module(), Callback :: atom(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, callback_missing}.
+render_callback(_Module, Callback, #docs_v1{ } = D) ->
+ render_callback(_Module, Callback, D, #{});
+render_callback(Module, D, Config) ->
+ render_signature_listing(Module, callback, D, Config).
+
+-spec render_callback(Module, Callback, Docs, Config) -> Res when
+ Module :: module(), Callback :: atom(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, callback_missing};
+ (Module, Callback, Arity, Docs) -> Res when
+ Module :: module(), Callback :: atom(), Arity :: arity(),
+ Docs :: docs_v1(),
+ Res :: unicode:chardata() | {error, callback_missing}.
+render_callback(_Module, Callback, Arity, #docs_v1{ } = D) ->
+ render_callback(_Module, Callback, Arity, D, #{});
+render_callback(_Module, Callback, #docs_v1{ docs = Docs } = D, Config) ->
+ render_typecb_docs(
+ lists:filter(fun({{callback, T, _},_Anno,_Sig,_Doc,_Meta}) ->
+ T =:= Callback;
+ (_) ->
+ false
+ end, Docs), D, Config).
+
+-spec render_callback(Module, Callback, Arity, Docs, Config) -> Res when
+ Module :: module(), Callback :: atom(), Arity :: arity(),
+ Docs :: docs_v1(),
+ Config :: config(),
+ Res :: unicode:chardata() | {error, callback_missing}.
+render_callback(_Module, Callback, Arity, #docs_v1{ docs = Docs } = D, Config) ->
+ render_typecb_docs(
+ lists:filter(fun({{callback, T, A},_Anno,_Sig,_Doc,_Meta}) ->
+ T =:= Callback andalso A =:= Arity;
+ (_) ->
+ false
+ end, Docs), D, Config).
+
+%% Get the docs in the correct locale if it exists.
+get_local_doc(MissingMod, Docs) when is_atom(MissingMod) ->
+ get_local_doc(atom_to_binary(MissingMod), Docs);
+get_local_doc({F,A}, Docs) ->
+ get_local_doc(unicode:characters_to_binary(io_lib:format("~tp/~p",[F,A])), Docs);
+get_local_doc(_Missing, #{ <<"en">> := Docs }) ->
+ %% English if it exists
+ normalize(Docs);
+get_local_doc(_Missing, ModuleDoc) when map_size(ModuleDoc) > 0 ->
+ %% Otherwise take first alternative found
+ normalize(maps:get(hd(maps:keys(ModuleDoc)), ModuleDoc));
+get_local_doc(Missing, hidden) ->
+ [{p,[],[<<"The documentation for ">>,Missing,
+ <<" is hidden. This probably means that it is internal "
+ "and not to be used by other applications.">>]}];
+get_local_doc(Missing, None) when None =:= none; None =:= #{} ->
+ [{p,[],[<<"There is no documentation for ">>,Missing]}].
+
+%%% Functions for rendering reference documentation
+render_function([], _D, _Config) ->
+ {error,function_missing};
+render_function(FDocs, #docs_v1{ docs = Docs } = D, Config) ->
+ Grouping =
+ lists:foldl(
+ fun({_Group,_Anno,_Sig,_Doc,#{ equiv := Group }} = Func,Acc) ->
+ Members = maps:get(Group, Acc, []),
+ Acc#{ Group => [Func|Members] };
+ ({Group, _Anno, _Sig, _Doc, _Meta} = Func, Acc) ->
+ Members = maps:get(Group, Acc, []),
+ Acc#{ Group => [Func|Members] }
+ end, #{}, lists:sort(FDocs)),
+ lists:map(
+ fun({{_,F,A} = Group,Members}) ->
+ Signatures = lists:flatmap(fun render_signature/1,lists:reverse(Members)),
+ case lists:search(fun({_,_,_,Doc,_}) ->
+ Doc =/= #{}
+ end, Members) of
+ {value, {_,_,_,Doc,_Meta}} ->
+ render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D, Config);
+ false ->
+ case lists:keyfind(Group, 1, Docs) of
+ false ->
+ render_headers_and_docs(Signatures, get_local_doc({F,A},none), D, Config);
+ {_,_,_,Doc,_} ->
+ render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D, Config)
+ end
+ end
+ end, maps:to_list(Grouping)).
+
+%% Render the signature of either function, type, or anything else really.
+render_signature({{_Type,_F,_A},_Anno,_Sigs,_Docs,#{ signature := Specs } = Meta}) ->
+ lists:flatmap(
+ fun(ASTSpec) ->
+ PPSpec = erl_pp:attribute(ASTSpec,[{encoding,utf8}]),
+ Spec =
+ case ASTSpec of
+ {_Attribute, _Line, opaque, _} ->
+ %% We do not want show the internals of the opaque type
+ hd(string:split(PPSpec,"::"));
+ _ ->
+ PPSpec
+ end,
+ BinSpec =
+ unicode:characters_to_binary(
+ string:trim(Spec, trailing, "\n")),
+ [{pre,[],[{em,[],BinSpec}]}|render_meta(Meta)]
+ end, Specs);
+render_signature({{_Type,_F,_A},_Anno,Sigs,_Docs,Meta}) ->
+ lists:flatmap(
+ fun(Sig) ->
+ [{h2,[],[<<"  "/utf8,Sig/binary>>]}|render_meta(Meta)]
+ end, Sigs).
+
+render_meta(M) ->
+ case render_meta_(M) of
+ [] -> [];
+ Meta ->
+ [[{dl,[],Meta}]]
+ end.
+render_meta_(#{ since := Vsn } = M) ->
+ [{dt,[],<<"Since">>},{dd,[],[Vsn]}
+ | render_meta_(maps:remove(since, M))];
+render_meta_(#{ deprecated := Depr } = M) ->
+ [{dt,[],<<"Deprecated">>},{dd,[],[Depr]}
+ | render_meta_(maps:remove(deprecated, M))];
+render_meta_(_) ->
+ [].
+
+render_headers_and_docs(Headers, DocContents, D, Config) ->
+ render_headers_and_docs(Headers, DocContents, init_config(D, Config)).
+render_headers_and_docs(Headers, DocContents, #config{} = Config) ->
+ ["\n",render_docs(
+ lists:flatmap(
+ fun(Header) ->
+ [{br,[],[]},Header]
+ end,Headers), Config),
+ "\n",
+ render_docs(DocContents, 2, Config)].
+
+%%% Functions for rendering type/callback documentation
+render_signature_listing(Module, Type, #docs_v1{ docs = Docs } = D, Config) ->
+ Slogan = [{h2,[],[<<"\t",(atom_to_binary(Module))/binary>>]},{br,[],[]}],
+ case lists:filter(fun({{T, _, _},_Anno,_Sig,_Doc,_Meta}) ->
+ Type =:= T
+ end, Docs) of
+ [] ->
+ render_docs(
+ Slogan ++ [<<"There are no ",(atom_to_binary(Type))/binary,"s "
+ "in this module">>], D, Config);
+ Headers ->
+ Hdr = lists:flatmap(
+ fun(Header) ->
+ [{br,[],[]},render_signature(Header)]
+ end,Headers),
+ render_docs(
+ Slogan ++
+ [{p,[],[<<"These ",(atom_to_binary(Type))/binary,"s "
+ "are documented in this module:">>]},
+ {br,[],[]}, Hdr], D, Config)
+ end.
+
+render_typecb_docs([], _D) ->
+ {error,type_missing};
+render_typecb_docs(TypeCBs, #config{} = D) when is_list(TypeCBs) ->
+ [render_typecb_docs(TypeCB, D) || TypeCB <- TypeCBs];
+render_typecb_docs({{_,F,A},_,_Sig,Docs,_Meta} = TypeCB, #config{} = D) ->
+ render_headers_and_docs(render_signature(TypeCB), get_local_doc({F,A},Docs), D).
+render_typecb_docs(Docs, D, Config) ->
+ render_typecb_docs(Docs, init_config(D, Config)).
+
+%%% General rendering functions
+render_docs(DocContents, #config{} = Config) ->
+ render_docs(DocContents, 0, Config).
+render_docs(DocContents, D, Config) when is_map(Config) ->
+ render_docs(DocContents, 0, init_config(D, Config));
+render_docs(DocContents, Ind, D = #config{}) when is_integer(Ind) ->
+ init_ansi(D),
+ try
+ {Doc,_} = trimnl(render_docs(DocContents, [], 0, Ind, D)),
+ Doc
+ after
+ clean_ansi()
+ end.
+
+init_config(D, Config) ->
+ DefaultOpts = io:getopts(),
+ DefaultEncoding = proplists:get_value(encoding, DefaultOpts, latin1),
+ Columns =
+ case maps:find(columns, Config) of
+ error ->
+ case io:columns() of
+ {ok, C} ->
+ C;
+ _ ->
+ 80
+ end;
+ {ok, C} ->
+ C
+ end,
+ #config{ docs = D,
+ encoding = maps:get(encoding, Config, DefaultEncoding),
+ ansi = maps:get(ansi, Config, undefined),
+ columns = Columns
+ }.
+
+render_docs(Elems,State,Pos,Ind,D) when is_list(Elems) ->
+ lists:mapfoldl(fun(Elem,P) ->
+%%% io:format("Elem: ~p (~p) (~p,~p)~n",[Elem,State,P,Ind]),
+ render_docs(Elem,State,P,Ind,D)
+ end,Pos,Elems);
+render_docs(Elem,State,Pos,Ind,D) ->
+ render_element(Elem,State,Pos,Ind,D).
+
+
+%%% The function is the main element rendering function
+%%%
+%%% Elem: The current element to process
+%%% Stack: A stack of element names to see where we are in the dom
+%%% Pos: The current print position on the current line
+%%% Ind: How much the text should be indented after a newline
+%%% Config: The renderer's configuration
+%%%
+%%% Each element is responsible for putting new lines AFTER itself
+%%% The indents are done either by render_words when a newline happens
+%%% or when a new element is to be rendered and Pos < Ind.
+%%%
+%%% Any block elements (i.e. p, ul, li etc) are responsible for trimming
+%%% extra new lines. eg. <ul><li><p>content</p></li></ul> should only
+%%% have two newlines at the end.
+-spec render_element(Elem :: chunk_element(),
+ Stack :: [chunk_element_type()],
+ Pos :: non_neg_integer(),
+ Indent :: non_neg_integer(),
+ Config :: #config{}) ->
+ {unicode:chardata(), Pos :: non_neg_integer()}.
+
+render_element({IgnoreMe,_,Content}, State, Pos, Ind,D)
+ when IgnoreMe =:= a ->
+ render_docs(Content, State, Pos, Ind,D);
+
+%% Catch h1, h2 and h3 before the padding is done as they reset padding
+render_element({h1,_,Content},State,0 = Pos,_Ind,D) ->
+ trimnlnl(render_element({code,[],[{em,[],Content}]}, State, Pos, 0, D));
+render_element({h2,_,Content},State,0 = Pos,_Ind,D) ->
+ trimnlnl(render_element({em,[],Content}, State, Pos, 0, D));
+render_element({h3,_,Content},State,Pos,_Ind,D) when Pos =< 2 ->
+ trimnlnl(render_element({code,[],Content}, State, Pos, 2, D));
+
+render_element({pre,_Attr,_Content} = E,State,Pos,Ind,D) when Pos > Ind ->
+ %% We pad `pre` with two newlines if the previous section did not indent the region.
+ {Docs,NewPos} = render_element(E,State,0,Ind,D),
+ {["\n\n",Docs],NewPos};
+render_element({Elem,_Attr,_Content} = E,State,Pos,Ind,D) when Pos > Ind, ?IS_BLOCK(Elem) ->
+ {Docs,NewPos} = render_element(E,State,0,Ind,D),
+ {["\n",Docs],NewPos};
+render_element({'div',[{class,What}],Content},State,Pos,Ind,D) ->
+ {Docs,_} = render_docs(Content, ['div'|State], 0, Ind+2, D),
+ trimnlnl([pad(Ind - Pos),string:titlecase(What),":\n",Docs]);
+render_element({Tag,_,Content},State,Pos,Ind,D) when Tag =:= p; Tag =:= 'div' ->
+ trimnlnl(render_docs(Content, [Tag|State], Pos, Ind, D));
+
+render_element(Elem,State,Pos,Ind,D) when Pos < Ind ->
+ {Docs,NewPos} = render_element(Elem,State,Ind,Ind,D),
+ {[pad(Ind - Pos), Docs],NewPos};
+
+render_element({code,_,Content},[pre|_] = State,Pos,Ind,D) ->
+ %% When code is within a pre we don't emit any underline
+ render_docs(Content, [code|State], Pos, Ind,D);
+render_element({code,_,Content},State,Pos,Ind,D) ->
+ Underline = sansi(underline),
+ {Docs, NewPos} = render_docs(Content, [code|State], Pos, Ind,D),
+ {[Underline,Docs,ransi(underline)], NewPos};
+
+render_element({i,_,Content},State,Pos,Ind,D) ->
+ %% Just ignore i as ansi does not have cursive style
+ render_docs(Content, State, Pos, Ind,D);
+
+render_element({br,[],[]},_State,Pos,_Ind,_D) ->
+ {"",Pos};
+
+render_element({em,_,Content},State,Pos,Ind,D) ->
+ Bold = sansi(bold),
+ {Docs, NewPos} = render_docs(Content, State, Pos, Ind,D),
+ {[Bold,Docs,ransi(bold)], NewPos};
+
+render_element({pre,_,Content},State,Pos,Ind,D) ->
+ %% For pre we make sure to respect the newlines in pre
+ trimnlnl(render_docs(Content, [pre|State], Pos, Ind+2, D));
+
+render_element({ul,[{class,<<"types">>}],Content},State,_Pos,Ind,D) ->
+ {Docs, _} = render_docs(Content, [types|State], 0, Ind+2, D),
+ trimnlnl(["Types:\n", Docs]);
+render_element({li,Attr,Content},[types|_] = State,Pos,Ind,C) ->
+ Doc =
+ case {proplists:get_value(name, Attr),proplists:get_value(class, Attr)} of
+ {undefined,Class} when Class =:= undefined; Class =:= <<"type">> ->
+ %% Inline html for types
+ render_docs(Content,[type|State],Pos,Ind,C);
+ {_,<<"description">>} ->
+ %% Inline html for type descriptions
+ render_docs(Content,[type|State],Pos,Ind+2,C);
+ {Name,_} ->
+ %% Try to render from type metadata
+ case render_type_signature(binary_to_atom(Name),C) of
+ undefined when Content =:= [] ->
+ %% Failed and no content, emit place-holder
+ {["-type ",Name,"() :: term()."],0};
+ undefined ->
+ %% Failed with metadata, render the content
+ render_docs(Content,[type|State],Pos,Ind,C);
+ Type ->
+ %% Emit the erl_pp typespec
+ {Type,0}
+ end
+ end,
+ trimnl(Doc);
+render_element({ul,[],Content},State,Pos,Ind,D) ->
+ render_docs(Content, [l|State], Pos, Ind,D);
+render_element({ol,[],Content},State,Pos,Ind,D) ->
+ %% For now ul and ol does the same thing
+ render_docs(Content, [l|State], Pos, Ind,D);
+render_element({li,[],Content},[l | _] = State, Pos, Ind,D) ->
+ Bullet = get_bullet(State, D#config.encoding),
+ BulletLen = string:length(Bullet),
+ {Docs, _NewPos} = render_docs(Content, [li | State], Pos + BulletLen,Ind + BulletLen, D),
+ trimnlnl([Bullet,Docs]);
+
+render_element({dl,_,Content},State,Pos,Ind,D) ->
+ render_docs(Content, [dl|State], Pos, Ind,D);
+render_element({dt,_,Content},[dl | _] = State,Pos,Ind,D) ->
+ Underline = sansi(underline),
+ {Docs, _NewPos} = render_docs(Content, [li | State], Pos, Ind, D),
+ {[Underline,Docs,ransi(underline),":","\n"], 0};
+render_element({dd,_,Content},[dl | _] = State,Pos,Ind,D) ->
+ trimnlnl(render_docs(Content, [li | State], Pos, Ind + 2, D));
+
+render_element(B, State, Pos, Ind,#config{ columns = Cols }) when is_binary(B) ->
+ case lists:member(pre,State) of
+ true ->
+ Pre = string:replace(B,"\n",[nlpad(Ind)],all),
+ {Pre, Pos + lastline(Pre)};
+ _ ->
+ render_words(split_to_words(B),State,Pos,Ind,[[]],Cols)
+ end;
+
+render_element({Tag,Attr,Content}, State, Pos, Ind,D) ->
+ case lists:member(Tag,?ALL_ELEMENTS) of
+ true ->
+ throw({unhandled_element,Tag,Attr,Content});
+ false ->
+ %% We ignore tags that we do not care about
+ ok
+ end,
+ render_docs(Content, State, Pos, Ind,D).
+
+render_words(Words,[_,types|State],Pos,Ind,Acc,Cols) ->
+ %% When we render words and are in the types->type state we indent
+ %% the extra lines two additional spaces to make it look nice
+ render_words(Words,State,Pos,Ind+2,Acc,Cols);
+render_words([Word|T],State,Pos,Ind,Acc,Cols) when is_binary(Word) ->
+ WordLength = string:length(Word),
+ NewPos = WordLength + Pos,
+ %% We do not want to add a newline if this word is only a punctuation
+ IsPunct = is_tuple(re:run(Word,"^\\W$",[unicode])),
+ if
+ NewPos > (Cols - 10 - Ind), Word =/= <<>>, not IsPunct ->
+ %% Word does not fit, time to add a newline and also pad to Indent level
+ render_words(T,State,WordLength+Ind+1,Ind,[[[nlpad(Ind), Word]]|Acc],Cols);
+ true ->
+ %% Word does fit on line
+ [Line | LineAcc] = Acc,
+ %% Add + 1 to length for space
+ NewPosSpc = NewPos+1,
+ render_words(T,State,NewPosSpc,Ind,[[Word|Line]|LineAcc],Cols)
+ end;
+render_words([],_State,Pos,_Ind,Acc,_Cols) ->
+ Lines = lists:map(fun(RevLine) ->
+ Line = lists:reverse(RevLine),
+ lists:join($ ,Line)
+ end,lists:reverse(Acc)),
+ {iolist_to_binary(Lines), Pos}.
+
+render_type_signature(Name, #config{ docs = #docs_v1{ metadata = #{ types := AllTypes }}}) ->
+ case [Type || Type = {TName,_} <- maps:keys(AllTypes), TName =:= Name] of
+ [] ->
+ undefined;
+ Types ->
+ [erl_pp:attribute(maps:get(Type, AllTypes)) || Type <- Types]
+ end.
+
+%% Pad N spaces (and possibly pre-prend newline), disabling any ansi formatting while doing so.
+pad(N) ->
+ pad(N,"").
+nlpad(N) ->
+ %% It is important that we disable the ansi code before the new-line as otherwise the
+ %% ansi decoration may be enabled when c:paged_output tries to ask if more content
+ %% should be displayed.
+ pad(N,"\n").
+pad(N, Extra) ->
+ Pad = lists:duplicate(N," "),
+ case ansi() of
+ undefined ->
+ [Extra, Pad];
+ Ansi ->
+ ["\033[0m",Extra,Pad,Ansi]
+ end.
+
+get_bullet(_State,latin1) ->
+ <<" * ">>;
+get_bullet(State,unicode) ->
+ %% Fancy bullet point logic!
+ case length([l || l <- State]) of
+ Level when Level > 4 ->
+ get_bullet(State, latin1);
+ Level ->
+ lists:nth(Level,
+ [<<" • "/utf8>>,<<" ○ "/utf8>>,
+ <<" ◼ "/utf8>>,<<" ◻ "/utf8>>])
+ end.
+
+%% Look for the length of the last line of a string
+lastline(Str) ->
+ LastStr = case string:find(Str,"\n",trailing) of
+ nomatch ->
+ Str;
+ Match ->
+ tl(string:next_codepoint(Match))
+ end,
+ string:length(LastStr).
+
+split_to_words(B) ->
+ binary:split(B,[<<" ">>],[global]).
+
+%% These functions make sure that we trim extra newlines added
+%% by the renderer. For example if we do <li><p></p></li>
+%% that would add 4 \n at after the last </li>. This is trimmed
+%% here to only be 2 \n
+trimnlnl({Chars, _Pos}) ->
+ nl(nl(string:trim(Chars, trailing, "\n")));
+trimnlnl(Chars) ->
+ nl(nl(string:trim(Chars, trailing, "\n"))).
+trimnl({Chars, _Pos}) ->
+ nl(string:trim(Chars, trailing, "\n")).
+nl({Chars, _Pos}) ->
+ nl(Chars);
+nl(Chars) ->
+ {[Chars,"\n"],0}.
+
+%% We keep the current ansi state in the pdict so that we know
+%% what to disable and enable when doing padding
+init_ansi(#config{ ansi = undefined, io_opts = Opts }) ->
+ %% We use this as our heuristic to see if we should print ansi or not
+ case {application:get_env(kernel, shell_docs_ansi),
+ proplists:is_defined(echo, Opts) andalso
+ proplists:is_defined(expand_fun, Opts),
+ os:type()} of
+ {{ok,false}, _, _} ->
+ put(ansi, noansi);
+ {{ok,true}, _, _} ->
+ put(ansi, []);
+ {_, _, {win32,_}} ->
+ put(ansi, noansi);
+ {_, true,_} ->
+ put(ansi, []);
+ {_, false,_} ->
+ put(ansi, noansi)
+ end;
+init_ansi(#config{ ansi = true }) ->
+ put(ansi, []);
+init_ansi(#config{ ansi = false }) ->
+ put(ansi, noansi).
+
+
+
+clean_ansi() ->
+ case get(ansi) of
+ [] -> erase(ansi);
+ noansi -> erase(ansi)
+ end,
+ ok.
+
+%% Set ansi
+sansi(Type) -> sansi(Type, get(ansi)).
+sansi(_Type, noansi) ->
+ [];
+sansi(Type, Curr) ->
+ put(ansi,[Type | Curr]),
+ ansi(get(ansi)).
+
+%% Clear ansi
+ransi(Type) -> ransi(Type, get(ansi)).
+ransi(_Type, noansi) ->
+ [];
+ransi(Type, Curr) ->
+ put(ansi,proplists:delete(Type,Curr)),
+ case ansi(get(ansi)) of
+ undefined ->
+ "\033[0m";
+ Ansi ->
+ Ansi
+ end.
+
+ansi() -> ansi(get(ansi)).
+ansi(noansi) -> undefined;
+ansi(Curr) ->
+ case lists:usort(Curr) of
+ [] ->
+ undefined;
+ [bold] ->
+ "\033[;1m";
+ [underline] ->
+ "\033[;;4m";
+ [bold,underline] ->
+ "\033[;1;4m"
+ end.
diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src
index f1a1afaf72..b59e3b28c0 100644
--- a/lib/stdlib/src/stdlib.app.src
+++ b/lib/stdlib/src/stdlib.app.src
@@ -92,6 +92,7 @@
sets,
shell,
shell_default,
+ shell_docs,
slave,
sofs,
string,
@@ -108,6 +109,6 @@
dets]},
{applications, [kernel]},
{env, []},
- {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-10.7.1","crypto-3.3",
+ {runtime_dependencies, ["sasl-3.0","kernel-7.0","erts-11.0","crypto-3.3",
"compiler-5.0"]}
]}.
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 6d6ee14d29..bba0d1ceee 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -21,6 +21,7 @@
%% versions from the following OTP releases:
%% - OTP 21
%% - OTP 22
+%% - OTP 23
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
@@ -35,6 +36,13 @@
{<<"^3\\.11\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.12$">>,[restart_new_emulator]},
{<<"^3\\.12\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.12\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.13$">>,[restart_new_emulator]},
+ {<<"^3\\.13\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.13\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.13\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.14$">>,[restart_new_emulator]},
+ {<<"^3\\.14\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5$">>,[restart_new_emulator]},
{<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -59,6 +67,13 @@
{<<"^3\\.11\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.12$">>,[restart_new_emulator]},
{<<"^3\\.12\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.12\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.13$">>,[restart_new_emulator]},
+ {<<"^3\\.13\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.13\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.13\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.14$">>,[restart_new_emulator]},
+ {<<"^3\\.14\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5$">>,[restart_new_emulator]},
{<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl
index 1ac7334830..7295a2e08e 100644
--- a/lib/stdlib/src/supervisor.erl
+++ b/lib/stdlib/src/supervisor.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -32,6 +32,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, format_status/2]).
+%% logger callback
+-export([format_log/1, format_log/2]).
+
%% For release_handler only
-export([get_callback_module/1]).
@@ -44,14 +47,17 @@
{reason,Reason},
{offender,extract_child(Child)}]},
#{domain=>[otp,sasl],
- report_cb=>fun logger:format_otp_report/1,
+ report_cb=>fun supervisor:format_log/2,
logger_formatter=>#{title=>"SUPERVISOR REPORT"},
error_logger=>#{tag=>error_report,
- type=>supervisor_report}})).
+ type=>supervisor_report,
+ report_cb=>fun supervisor:format_log/1}})).
%%--------------------------------------------------------------------------
--export_type([sup_flags/0, child_spec/0, startchild_ret/0, strategy/0]).
+-export_type([sup_flags/0, child_spec/0, strategy/0,
+ startchild_ret/0, startchild_err/0,
+ startlink_ret/0, startlink_err/0]).
%%--------------------------------------------------------------------------
@@ -122,7 +128,7 @@
strategy :: strategy() | 'undefined',
children = {[],#{}} :: children(), % Ids in start order
dynamics :: {'maps', #{pid() => list()}}
- | {'sets', sets:set(pid())}
+ | {'mapsets', #{pid() => []}}
| 'undefined',
intensity :: non_neg_integer() | 'undefined',
period :: pos_integer() | 'undefined',
@@ -924,21 +930,21 @@ monitor_child(Pid) ->
terminate_dynamic_children(State) ->
Child = get_dynamic_child(State),
{Pids, EStack0} = monitor_dynamic_children(Child,State),
- Sz = sets:size(Pids),
+ Sz = maps:size(Pids),
EStack = case Child#child.shutdown of
brutal_kill ->
- sets:fold(fun(P, _) -> exit(P, kill) end, ok, Pids),
+ maps:fold(fun(P, _, _) -> exit(P, kill) end, ok, Pids),
wait_dynamic_children(Child, Pids, Sz, undefined, EStack0);
infinity ->
- sets:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids),
+ maps:fold(fun(P, _, _) -> exit(P, shutdown) end, ok, Pids),
wait_dynamic_children(Child, Pids, Sz, undefined, EStack0);
Time ->
- sets:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids),
+ maps:fold(fun(P, _, _) -> exit(P, shutdown) end, ok, Pids),
TRef = erlang:start_timer(Time, self(), kill),
wait_dynamic_children(Child, Pids, Sz, TRef, EStack0)
end,
%% Unroll stacked errors and report them
- dict:fold(fun(Reason, Ls, _) ->
+ maps:fold(fun(Reason, Ls, _) ->
?report_error(shutdown_error, Reason,
Child#child{pid=Ls}, State#state.name)
end, ok, EStack).
@@ -947,15 +953,15 @@ monitor_dynamic_children(Child,State) ->
dyn_fold(fun(P,{Pids, EStack}) when is_pid(P) ->
case monitor_child(P) of
ok ->
- {sets:add_element(P, Pids), EStack};
+ {maps:put(P, P, Pids), EStack};
{error, normal} when not (?is_permanent(Child)) ->
{Pids, EStack};
{error, Reason} ->
- {Pids, dict:append(Reason, P, EStack)}
+ {Pids, maps_prepend(Reason, P, EStack)}
end;
(?restarting(_), {Pids, EStack}) ->
{Pids, EStack}
- end, {sets:new(), dict:new()}, State).
+ end, {maps:new(), maps:new()}, State).
wait_dynamic_children(_Child, _Pids, 0, undefined, EStack) ->
EStack;
@@ -973,36 +979,44 @@ wait_dynamic_children(#child{shutdown=brutal_kill} = Child, Pids, Sz,
TRef, EStack) ->
receive
{'DOWN', _MRef, process, Pid, killed} ->
- wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
+ wait_dynamic_children(Child, maps:remove(Pid, Pids), Sz-1,
TRef, EStack);
{'DOWN', _MRef, process, Pid, Reason} ->
- wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
- TRef, dict:append(Reason, Pid, EStack))
+ wait_dynamic_children(Child, maps:remove(Pid, Pids), Sz-1,
+ TRef, maps_prepend(Reason, Pid, EStack))
end;
wait_dynamic_children(Child, Pids, Sz, TRef, EStack) ->
receive
{'DOWN', _MRef, process, Pid, shutdown} ->
- wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
+ wait_dynamic_children(Child, maps:remove(Pid, Pids), Sz-1,
TRef, EStack);
{'DOWN', _MRef, process, Pid, {shutdown, _}} ->
- wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
+ wait_dynamic_children(Child, maps:remove(Pid, Pids), Sz-1,
TRef, EStack);
{'DOWN', _MRef, process, Pid, normal} when not (?is_permanent(Child)) ->
- wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
+ wait_dynamic_children(Child, maps:remove(Pid, Pids), Sz-1,
TRef, EStack);
{'DOWN', _MRef, process, Pid, Reason} ->
- wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
- TRef, dict:append(Reason, Pid, EStack));
+ wait_dynamic_children(Child, maps:remove(Pid, Pids), Sz-1,
+ TRef, maps_prepend(Reason, Pid, EStack));
{timeout, TRef, kill} ->
- sets:fold(fun(P, _) -> exit(P, kill) end, ok, Pids),
+ maps:fold(fun(P, _, _) -> exit(P, kill) end, ok, Pids),
wait_dynamic_children(Child, Pids, Sz, undefined, EStack)
end.
+maps_prepend(Key, Value, Map) ->
+ case maps:find(Key, Map) of
+ {ok, Values} ->
+ maps:put(Key, [Value|Values], Map);
+ error ->
+ maps:put(Key, [Value], Map)
+ end.
+
%%-----------------------------------------------------------------
%% Access #state.children
%%-----------------------------------------------------------------
@@ -1420,9 +1434,159 @@ report_progress(Child, SupName) ->
report=>[{supervisor,SupName},
{started,extract_child(Child)}]},
#{domain=>[otp,sasl],
- report_cb=>fun logger:format_otp_report/1,
+ report_cb=>fun supervisor:format_log/2,
logger_formatter=>#{title=>"PROGRESS REPORT"},
- error_logger=>#{tag=>info_report,type=>progress}}).
+ error_logger=>#{tag=>info_report,
+ type=>progress,
+ report_cb=>fun supervisor:format_log/1}}).
+
+%% format_log/1 is the report callback used by Logger handler
+%% error_logger only. It is kept for backwards compatibility with
+%% legacy error_logger event handlers. This function must always
+%% return {Format,Args} compatible with the arguments in this module's
+%% calls to error_logger prior to OTP-21.0.
+format_log(LogReport) ->
+ Depth = error_logger:get_format_depth(),
+ FormatOpts = #{chars_limit => unlimited,
+ depth => Depth,
+ single_line => false,
+ encoding => utf8},
+ format_log_multi(limit_report(LogReport, Depth), FormatOpts).
+
+limit_report(LogReport, unlimited) ->
+ LogReport;
+limit_report(#{label:={supervisor,progress},
+ report:=[{supervisor,_}=Supervisor,{started,Child}]}=LogReport,
+ Depth) ->
+ LogReport#{report=>[Supervisor,
+ {started,limit_child_report(Child, Depth)}]};
+limit_report(#{label:={supervisor,_Error},
+ report:=[{supervisor,_}=Supervisor,{errorContext,Ctxt},
+ {reason,Reason},{offender,Child}]}=LogReport,
+ Depth) ->
+ LogReport#{report=>[Supervisor,
+ {errorContext,io_lib:limit_term(Ctxt, Depth)},
+ {reason,io_lib:limit_term(Reason, Depth)},
+ {offender,limit_child_report(Child, Depth)}]}.
+
+limit_child_report(Report, Depth) ->
+ io_lib:limit_term(Report, Depth).
+
+%% format_log/2 is the report callback for any Logger handler, except
+%% error_logger.
+format_log(Report, FormatOpts0) ->
+ Default = #{chars_limit => unlimited,
+ depth => unlimited,
+ single_line => false,
+ encoding => utf8},
+ FormatOpts = maps:merge(Default, FormatOpts0),
+ IoOpts =
+ case FormatOpts of
+ #{chars_limit:=unlimited} ->
+ [];
+ #{chars_limit:=Limit} ->
+ [{chars_limit,Limit}]
+ end,
+ {Format,Args} = format_log_single(Report, FormatOpts),
+ io_lib:format(Format, Args, IoOpts).
+
+format_log_single(#{label:={supervisor,progress},
+ report:=[{supervisor,SupName},{started,Child}]},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ {ChildFormat,ChildArgs} = format_child_log_single(Child, "Started:"),
+ Format = "Supervisor: "++P++".",
+ Args =
+ case Depth of
+ unlimited ->
+ [SupName];
+ _ ->
+ [SupName,Depth]
+ end,
+ {Format++ChildFormat,Args++ChildArgs};
+format_log_single(#{label:={supervisor,_Error},
+ report:=[{supervisor,SupName},
+ {errorContext,Ctxt},
+ {reason,Reason},
+ {offender,Child}]},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format = lists:append(["Supervisor: ",P,". Context: ",P,
+ ". Reason: ",P,"."]),
+ {ChildFormat,ChildArgs} = format_child_log_single(Child, "Offender:"),
+ Args =
+ case Depth of
+ unlimited ->
+ [SupName,Ctxt,Reason];
+ _ ->
+ [SupName,Depth,Ctxt,Depth,Reason,Depth]
+ end,
+ {Format++ChildFormat,Args++ChildArgs};
+format_log_single(Report,FormatOpts) ->
+ format_log_multi(Report,FormatOpts).
+
+format_log_multi(#{label:={supervisor,progress},
+ report:=[{supervisor,SupName},
+ {started,Child}]},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ lists:append(
+ [" supervisor: ",P,"~n",
+ " started: ",P,"~n"]),
+ Args =
+ case Depth of
+ unlimited ->
+ [SupName,Child];
+ _ ->
+ [SupName,Depth,Child,Depth]
+ end,
+ {Format,Args};
+format_log_multi(#{label:={supervisor,_Error},
+ report:=[{supervisor,SupName},
+ {errorContext,Ctxt},
+ {reason,Reason},
+ {offender,Child}]},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ lists:append(
+ [" supervisor: ",P,"~n",
+ " errorContext: ",P,"~n",
+ " reason: ",P,"~n",
+ " offender: ",P,"~n"]),
+ Args =
+ case Depth of
+ unlimited ->
+ [SupName,Ctxt,Reason,Child];
+ _ ->
+ [SupName,Depth,Ctxt,Depth,Reason,Depth,Child,Depth]
+ end,
+ {Format,Args}.
+
+format_child_log_single(Child, Tag) ->
+ {id,Id} = lists:keyfind(id, 1, Child),
+ case lists:keyfind(pid, 1, Child) of
+ false ->
+ {nb_children,NumCh} = lists:keyfind(nb_children, 1, Child),
+ {" ~s id=~w,nb_children=~w.", [Tag,Id,NumCh]};
+ T when is_tuple(T) ->
+ {pid,Pid} = lists:keyfind(pid, 1, Child),
+ {" ~s id=~w,pid=~w.", [Tag,Id,Pid]}
+ end.
+
+p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
+ "~"++single(Single)++mod(Enc)++p(Depth);
+p(unlimited) ->
+ "p";
+p(_Depth) ->
+ "P".
+
+single(true) -> "0";
+single(false) -> "".
+
+mod(latin1) -> "";
+mod(_) -> "t".
format_status(terminate, [_PDict, State]) ->
State;
@@ -1431,36 +1595,41 @@ format_status(_, [_PDict, State]) ->
{supervisor, [{"Callback", State#state.module}]}].
%%%-----------------------------------------------------------------
-%%% Dynamics database access
-dyn_size(#state{dynamics = {Mod,Db}}) ->
- Mod:size(Db).
-
-dyn_erase(Pid,#state{dynamics={sets,Db}}=State) ->
- State#state{dynamics={sets,sets:del_element(Pid,Db)}};
-dyn_erase(Pid,#state{dynamics={maps,Db}}=State) ->
- State#state{dynamics={maps,maps:remove(Pid,Db)}}.
-
-dyn_store(Pid,_,#state{dynamics={sets,Db}}=State) ->
- State#state{dynamics={sets,sets:add_element(Pid,Db)}};
-dyn_store(Pid,Args,#state{dynamics={maps,Db}}=State) ->
- State#state{dynamics={maps,Db#{Pid => Args}}}.
-
-dyn_fold(Fun,Init,#state{dynamics={sets,Db}}) ->
- sets:fold(Fun,Init,Db);
-dyn_fold(Fun,Init,#state{dynamics={maps,Db}}) ->
+%%% Dynamics database access.
+%%%
+%%% Store all dynamic children in a map with the pid as the key. If
+%%% the children are permanent, store the start arguments as the value,
+%%% otherwise store [] as the value.
+%%%
+
+dyn_size(#state{dynamics = {_Kind,Db}}) ->
+ map_size(Db).
+
+dyn_erase(Pid,#state{dynamics={Kind,Db}}=State) ->
+ State#state{dynamics={Kind,maps:remove(Pid,Db)}}.
+
+dyn_store(Pid,Args,#state{dynamics={Kind,Db}}=State) ->
+ case Kind of
+ mapsets ->
+ %% Children are temporary. The start arguments
+ %% will not be needed again. Store [].
+ State#state{dynamics={mapsets,Db#{Pid => []}}};
+ maps ->
+ %% Children are permanent and may be restarted.
+ %% Store the start arguments.
+ State#state{dynamics={maps,Db#{Pid => Args}}}
+ end.
+
+dyn_fold(Fun,Init,#state{dynamics={_Kind,Db}}) ->
maps:fold(fun(Pid,_,Acc) -> Fun(Pid,Acc) end, Init, Db).
-dyn_map(Fun, #state{dynamics={sets,Db}}) ->
- lists:map(Fun, sets:to_list(Db));
-dyn_map(Fun, #state{dynamics={maps,Db}}) ->
+dyn_map(Fun, #state{dynamics={_Kind,Db}}) ->
lists:map(Fun, maps:keys(Db)).
-dyn_exists(Pid, #state{dynamics={sets, Db}}) ->
- sets:is_element(Pid, Db);
-dyn_exists(Pid, #state{dynamics={maps, Db}}) ->
- maps:is_key(Pid, Db).
+dyn_exists(Pid, #state{dynamics={_Kind, Db}}) ->
+ is_map_key(Pid, Db).
-dyn_args(_Pid, #state{dynamics={sets, _Db}}) ->
+dyn_args(_Pid, #state{dynamics={mapsets, _Db}}) ->
{ok,undefined};
dyn_args(Pid, #state{dynamics={maps, Db}}) ->
maps:find(Pid, Db).
@@ -1469,6 +1638,6 @@ dyn_init(State) ->
dyn_init(get_dynamic_child(State),State).
dyn_init(Child,State) when ?is_temporary(Child) ->
- State#state{dynamics={sets,sets:new()}};
+ State#state{dynamics={mapsets,maps:new()}};
dyn_init(_Child,State) ->
State#state{dynamics={maps,maps:new()}}.
diff --git a/lib/stdlib/src/supervisor_bridge.erl b/lib/stdlib/src/supervisor_bridge.erl
index 21ba6f53af..abbfb404a5 100644
--- a/lib/stdlib/src/supervisor_bridge.erl
+++ b/lib/stdlib/src/supervisor_bridge.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
-export([code_change/3]).
+%% logger callback
+-export([format_log/1, format_log/2]).
-callback init(Args :: term()) ->
{ok, Pid :: pid(), State :: term()} | ignore | {error, Error :: term()}.
@@ -136,9 +138,12 @@ report_progress(Pid, Mod, StartArgs, SupName) ->
{started, [{pid, Pid},
{mfa, {Mod, init, [StartArgs]}}]}]},
#{domain=>[otp,sasl],
- report_cb=>fun logger:format_otp_report/1,
+ report_cb=>fun supervisor_bridge:format_log/2,
logger_formatter=>#{title=>"PROGRESS REPORT"},
- error_logger=>#{tag=>info_report,type=>progress}}).
+ error_logger=>#{tag=>info_report,
+ type=>progress,
+ report_cb=>
+ fun supervisor_bridge:format_log/1}}).
report_error(Error, Reason, #state{name = Name, pid = Pid, mod = Mod}) ->
?LOG_ERROR(#{label=>{supervisor,error},
@@ -147,6 +152,167 @@ report_error(Error, Reason, #state{name = Name, pid = Pid, mod = Mod}) ->
{reason, Reason},
{offender, [{pid, Pid}, {mod, Mod}]}]},
#{domain=>[otp,sasl],
- report_cb=>fun logger:format_otp_report/1,
+ report_cb=>fun supervisor_bridge:format_log/2,
logger_formatter=>#{title=>"SUPERVISOR REPORT"},
- error_logger=>#{tag=>error_report,type=>supervisor_report}}).
+ error_logger=>#{tag=>error_report,
+ type=>supervisor_report,
+ report_cb=>
+ fun supervisor_bridge:format_log/1}}).
+
+%% format_log/1 is the report callback used by Logger handler
+%% error_logger only. It is kept for backwards compatibility with
+%% legacy error_logger event handlers. This function must always
+%% return {Format,Args} compatible with the arguments in this module's
+%% calls to error_logger prior to OTP-21.0.
+format_log(LogReport) ->
+ Depth = error_logger:get_format_depth(),
+ FormatOpts = #{chars_limit => unlimited,
+ depth => Depth,
+ single_line => false,
+ encoding => utf8},
+ format_log_multi(limit_report(LogReport, Depth), FormatOpts).
+
+limit_report(LogReport, unlimited) ->
+ LogReport;
+limit_report(#{label:={supervisor,progress},
+ report:=[{supervisor,_}=Supervisor,{started,Child}]}=LogReport,
+ Depth) ->
+ LogReport#{report=>[Supervisor,
+ {started,limit_child_report(Child, Depth)}]};
+limit_report(#{label:={supervisor,error},
+ report:=[{supervisor,_}=Supervisor,{errorContext,Ctxt},
+ {reason,Reason},{offender,Child}]}=LogReport,
+ Depth) ->
+ LogReport#{report=>[Supervisor,
+ {errorContext,io_lib:limit_term(Ctxt, Depth)},
+ {reason,io_lib:limit_term(Reason, Depth)},
+ {offender,io_lib:limit_term(Child, Depth)}]}.
+
+limit_child_report(ChildReport, Depth) ->
+ {mfa,{M,F,[As]}} = lists:keyfind(mfa, 1, ChildReport),
+ NewMFAs = {M,F,[io_lib:limit_term(As, Depth)]},
+ lists:keyreplace(mfa, 1, ChildReport, {mfa,NewMFAs}).
+
+%% format_log/2 is the report callback for any Logger handler, except
+%% error_logger.
+format_log(Report, FormatOpts0) ->
+ Default = #{chars_limit => unlimited,
+ depth => unlimited,
+ single_line => false,
+ encoding => utf8},
+ FormatOpts = maps:merge(Default, FormatOpts0),
+ IoOpts =
+ case FormatOpts of
+ #{chars_limit:=unlimited} ->
+ [];
+ #{chars_limit:=Limit} ->
+ [{chars_limit,Limit}]
+ end,
+ {Format,Args} = format_log_single(Report, FormatOpts),
+ io_lib:format(Format, Args, IoOpts).
+
+format_log_single(#{label:={supervisor,progress},
+ report:=[{supervisor,SupName},{started,Child}]},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ {ChildFormat,ChildArgs} =
+ format_child_log_progress_single(Child, "Started:", FormatOpts),
+ Format = "Supervisor: "++P++".",
+ Args =
+ case Depth of
+ unlimited ->
+ [SupName];
+ _ ->
+ [SupName,Depth]
+ end,
+ {Format++ChildFormat,Args++ChildArgs};
+format_log_single(#{label:={supervisor,_Error},
+ report:=[{supervisor,SupName},
+ {errorContext,Ctxt},
+ {reason,Reason},
+ {offender,Child}]},
+ #{single_line:=true,depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format = lists:append(["Supervisor: ",P,". Context: ",P,
+ ". Reason: ",P,"."]),
+ {ChildFormat,ChildArgs} =
+ format_child_log_error_single(Child, "Offender:"),
+ Args =
+ case Depth of
+ unlimited ->
+ [SupName,Ctxt,Reason];
+ _ ->
+ [SupName,Depth,Ctxt,Depth,Reason,Depth]
+ end,
+ {Format++ChildFormat,Args++ChildArgs};
+format_log_single(Report, FormatOpts) ->
+ format_log_multi(Report, FormatOpts).
+
+format_log_multi(#{label:={supervisor,progress},
+ report:=[{supervisor,SupName},
+ {started,Child}]},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ lists:append(
+ [" supervisor: ",P,"~n",
+ " started: ",P,"~n"]),
+ Args =
+ case Depth of
+ unlimited ->
+ [SupName,Child];
+ _ ->
+ [SupName,Depth,Child,Depth]
+ end,
+ {Format,Args};
+format_log_multi(#{label:={supervisor,_Error},
+ report:=[{supervisor,SupName},
+ {errorContext,Ctxt},
+ {reason,Reason},
+ {offender,Child}]},
+ #{depth:=Depth}=FormatOpts) ->
+ P = p(FormatOpts),
+ Format =
+ lists:append(
+ [" supervisor: ",P,"~n",
+ " errorContext: ",P,"~n",
+ " reason: ",P,"~n",
+ " offender: ",P,"~n"]),
+ Args =
+ case Depth of
+ unlimited ->
+ [SupName,Ctxt,Reason,Child];
+ _ ->
+ [SupName,Depth,Ctxt,Depth,Reason,Depth,Child,Depth]
+ end,
+ {Format,Args}.
+
+format_child_log_progress_single(Child, Tag, FormatOpts) ->
+ {pid,Pid} = lists:keyfind(pid, 1, Child),
+ {mfa,MFAs} = lists:keyfind(mfa, 1, Child),
+ Args =
+ case maps:get(depth, FormatOpts) of
+ unlimited ->
+ [MFAs];
+ Depth ->
+ [MFAs, Depth]
+ end,
+ {" ~s pid=~w,mfa="++p(FormatOpts)++".",[Tag,Pid]++Args}.
+
+format_child_log_error_single(Child, Tag) ->
+ {pid,Pid} = lists:keyfind(pid, 1, Child),
+ {mod,Mod} = lists:keyfind(mod, 1, Child),
+ {" ~s pid=~w,mod=~w.",[Tag,Pid,Mod]}.
+
+p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
+ "~"++single(Single)++mod(Enc)++p(Depth);
+p(unlimited) ->
+ "p";
+p(_Depth) ->
+ "P".
+
+single(true) -> "0";
+single(false) -> "".
+
+mod(latin1) -> "";
+mod(_) -> "t".
diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl
index 93bf4743d2..e803b749f7 100644
--- a/lib/stdlib/src/sys.erl
+++ b/lib/stdlib/src/sys.erl
@@ -31,7 +31,10 @@
install/2, install/3, remove/2, remove/3]).
-export([handle_system_msg/6, handle_system_msg/7, handle_debug/4,
print_log/1, get_log/1, get_debug/3, debug_options/1, suspend_loop_hib/6]).
--deprecated([{get_debug,3,eventually}]).
+
+-deprecated([{get_debug,3,
+ "incorrectly documented and only for internal use. Can often "
+ "be replaced with sys:get_log/1"}]).
%%-----------------------------------------------------------------
%% Types
diff --git a/lib/stdlib/src/uri_string.erl b/lib/stdlib/src/uri_string.erl
index 2ee0ba53b9..3060f2bfaa 100644
--- a/lib/stdlib/src/uri_string.erl
+++ b/lib/stdlib/src/uri_string.erl
@@ -226,10 +226,21 @@
%%-------------------------------------------------------------------------
%% External API
%%-------------------------------------------------------------------------
--export([compose_query/1, compose_query/2,
- dissect_query/1, normalize/1, normalize/2, parse/1,
- recompose/1, resolve/2, resolve/3, transcode/2]).
--export_type([error/0, uri_map/0, uri_string/0]).
+-export([allowed_characters/0,
+ compose_query/1,
+ compose_query/2,
+ dissect_query/1,
+ normalize/1,
+ normalize/2,
+ percent_decode/1,
+ parse/1,
+ recompose/1,
+ resolve/2,
+ resolve/3,
+ transcode/2]).
+-export_type([error/0,
+ uri_map/0,
+ uri_string/0]).
%%-------------------------------------------------------------------------
@@ -286,7 +297,7 @@
port => non_neg_integer() | undefined,
query => unicode:chardata(),
scheme => unicode:chardata(),
- userinfo => unicode:chardata()} | #{}.
+ userinfo => unicode:chardata()}.
%%-------------------------------------------------------------------------
@@ -453,6 +464,61 @@ transcode(URIString, Options) when is_list(URIString) ->
%%-------------------------------------------------------------------------
+%% Misc
+%%-------------------------------------------------------------------------
+-spec allowed_characters() -> [{atom(), list()}].
+allowed_characters() ->
+ Input = lists:seq(0,127),
+ Scheme = lists:filter(fun is_scheme/1, Input),
+ UserInfo = lists:filter(fun is_userinfo/1, Input),
+ Host = lists:filter(fun is_host/1, Input),
+ IPv4 = lists:filter(fun is_ipv4/1, Input),
+ IPv6 = lists:filter(fun is_ipv6/1, Input),
+ RegName = lists:filter(fun is_reg_name/1, Input),
+ Path = lists:filter(fun is_path/1, Input),
+ Query = lists:filter(fun is_query/1, Input),
+ Fragment = lists:filter(fun is_fragment/1, Input),
+ Reserved = lists:filter(fun is_reserved/1, Input),
+ Unreserved = lists:filter(fun is_unreserved/1, Input),
+ [{scheme, Scheme},
+ {userinfo, UserInfo},
+ {host, Host},
+ {ipv4, IPv4},
+ {ipv6, IPv6},
+ {regname,RegName},
+ {path,Path},
+ {query, Query},
+ {fragment,Fragment},
+ {reserved, Reserved},
+ {unreserved, Unreserved}].
+
+-spec percent_decode(URI) -> Result when
+ URI :: uri_string() | uri_map(),
+ Result :: uri_string() |
+ uri_map() |
+ {error, {invalid, {atom(), {term(), term()}}}}.
+percent_decode(URIMap) when is_map(URIMap)->
+ Fun = fun (K,V) when K =:= userinfo; K =:= host; K =:= path;
+ K =:= query; K =:= fragment ->
+ case raw_decode(V) of
+ {error, Reason, Input} ->
+ throw({error, {invalid, {K, {Reason, Input}}}});
+ Else ->
+ Else
+ end;
+ %% Handle port and scheme
+ (_,V) ->
+ V
+ end,
+ try maps:map(Fun, URIMap)
+ catch throw:Return ->
+ Return
+ end;
+percent_decode(URI) when is_list(URI) orelse
+ is_binary(URI) ->
+ raw_decode(URI).
+
+%%-------------------------------------------------------------------------
%% Functions for working with the query part of a URI as a list
%% of key/value pairs.
%% HTML 5.2 - 4.10.21.6 URL-encoded form data - WHATWG URL (10 Jan 2018) - UTF-8
@@ -1421,8 +1487,15 @@ decode(<<$%,C0,C1,Cs/binary>>, Acc) ->
case is_hex_digit(C0) andalso is_hex_digit(C1) of
true ->
B = ?HEX2DEC(C0)*16+?HEX2DEC(C1),
- case is_reserved(B) of
- true ->
+ %% [2.4] When a URI is dereferenced, the components and subcomponents
+ %% significant to the scheme-specific dereferencing process (if any)
+ %% must be parsed and separated before the percent-encoded octets within
+ %% those components can be safely decoded, as otherwise the data may be
+ %% mistaken for component delimiters. The only exception is for
+ %% percent-encoded octets corresponding to characters in the unreserved
+ %% set, which can be decoded at any time.
+ case is_unreserved(B) of
+ false ->
%% [2.2] Characters in the reserved set are protected from
%% normalization.
%% [2.1] For consistency, URI producers and normalizers should
@@ -1431,7 +1504,7 @@ decode(<<$%,C0,C1,Cs/binary>>, Acc) ->
H0 = hex_to_upper(C0),
H1 = hex_to_upper(C1),
decode(Cs, <<Acc/binary,$%,H0,H1>>);
- false ->
+ true ->
decode(Cs, <<Acc/binary, B>>)
end;
false -> throw({error,invalid_percent_encoding,<<$%,C0,C1>>})
@@ -1441,6 +1514,32 @@ decode(<<C,Cs/binary>>, Acc) ->
decode(<<>>, Acc) ->
check_utf8(Acc).
+-spec raw_decode(list()|binary()) -> list() | binary() | error().
+raw_decode(Cs) ->
+ raw_decode(Cs, <<>>).
+%%
+raw_decode(L, Acc) when is_list(L) ->
+ try
+ B0 = unicode:characters_to_binary(L),
+ B1 = raw_decode(B0, Acc),
+ unicode:characters_to_list(B1)
+ catch
+ throw:{error, Atom, RestData} ->
+ {error, Atom, RestData}
+ end;
+raw_decode(<<$%,C0,C1,Cs/binary>>, Acc) ->
+ case is_hex_digit(C0) andalso is_hex_digit(C1) of
+ true ->
+ B = ?HEX2DEC(C0)*16+?HEX2DEC(C1),
+ raw_decode(Cs, <<Acc/binary, B>>);
+ false ->
+ throw({error,invalid_percent_encoding,<<$%,C0,C1>>})
+ end;
+raw_decode(<<C,Cs/binary>>, Acc) ->
+ raw_decode(Cs, <<Acc/binary, C>>);
+raw_decode(<<>>, Acc) ->
+ check_utf8(Acc).
+
%% Returns Cs if it is utf8 encoded.
check_utf8(Cs) ->
case unicode:characters_to_list(Cs) of
@@ -1679,6 +1778,11 @@ update_port(#{}, URI) ->
update_path(#{path := Path}, empty) ->
encode_path(Path);
+update_path(#{host := _, path := Path0}, URI) ->
+ %% When host is present in a URI the path must begin with "/" or be empty.
+ Path1 = maybe_flatten_list(Path0),
+ Path = make_path_absolute(Path1),
+ concat(URI,encode_path(Path));
update_path(#{path := Path}, URI) ->
concat(URI,encode_path(Path));
update_path(#{}, empty) ->
@@ -1756,6 +1860,35 @@ maybe_to_list(Comp) -> Comp.
encode_port(Port) ->
integer_to_binary(Port).
+%% URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+%%
+%% hier-part = "//" authority path-abempty
+%% / path-absolute
+%% / path-rootless
+%% / path-empty
+%%
+%% path = path-abempty ; begins with "/" or is empty
+%% / path-absolute ; begins with "/" but not "//"
+%% / path-noscheme ; begins with a non-colon segment
+%% / path-rootless ; begins with a segment
+%% / path-empty ; zero characters
+make_path_absolute(<<>>) ->
+ <<>>;
+make_path_absolute("") ->
+ "";
+make_path_absolute(<<"/",_/binary>> = Path) ->
+ Path;
+make_path_absolute([$/|_] = Path) ->
+ Path;
+make_path_absolute(Path) when is_binary(Path) ->
+ concat(<<$/>>, Path);
+make_path_absolute(Path) when is_list(Path) ->
+ concat("/", Path).
+
+maybe_flatten_list(Path) when is_binary(Path) ->
+ Path;
+maybe_flatten_list(Path) ->
+ unicode:characters_to_list(Path).
%%-------------------------------------------------------------------------
%% Helper functions for resolve
@@ -1869,7 +2002,7 @@ transcode_pct([], Acc, B, InEncoding, OutEncoding) ->
OutBinary = convert_to_binary(B, InEncoding, OutEncoding),
PctEncUtf8 = percent_encode_segment(OutBinary),
Out = convert_to_list(PctEncUtf8, utf8),
- lists:reverse(Acc) ++ Out.
+ lists:reverse(Acc, Out).
%% Convert to binary
@@ -1904,7 +2037,7 @@ flatten_list(L, InEnc) ->
%%
flatten_list([H|T], InEnc, Acc) when is_binary(H) ->
L = convert_to_list(H, InEnc),
- flatten_list(T, InEnc, lists:reverse(L) ++ Acc);
+ flatten_list(T, InEnc, lists:reverse(L, Acc));
flatten_list([H|T], InEnc, Acc) when is_list(H) ->
flatten_list(H ++ T, InEnc, Acc);
flatten_list([H|T], InEnc, Acc) ->
@@ -1924,7 +2057,7 @@ percent_encode_segment(Segment) ->
%%-------------------------------------------------------------------------
%% Returns separator to be used between key-value pairs
-get_separator(L) when length(L) =:= 0 ->
+get_separator([]) ->
<<>>;
get_separator(_L) ->
<<"&">>.
diff --git a/lib/stdlib/src/zip.erl b/lib/stdlib/src/zip.erl
index a922bf3fbe..8f703d9d1a 100644
--- a/lib/stdlib/src/zip.erl
+++ b/lib/stdlib/src/zip.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -53,6 +53,10 @@
%% for debugging, to turn off catch
-define(CATCH, catch).
+%% Debug.
+-define(SHOW_GP_BIT_11(B, F), ok).
+%%-define(SHOW_GP_BIT_11(B, F), io:format("F = ~.16#, B = ~lp\n", [F, B])).
+
%% option sets
-record(unzip_opts, {
output, % output object (fun)
@@ -138,6 +142,10 @@
-define(PKWARE_RESERVED, 11).
-define(BZIP2_COMPRESSED, 12).
+%% Version 2.0, attribute compatibility type 3 (Unix)
+-define(VERSION_MADE_BY, 20 bor (3 bsl 8)).
+-define(GP_BIT_11, 16#800). % Filename and file comment UTF-8 encoded.
+
%% zip-file records
-define(LOCAL_FILE_MAGIC,16#04034b50).
-define(LOCAL_FILE_HEADER_SZ,(4+2+2+2+2+2+4+4+4+2+2)).
@@ -160,6 +168,7 @@
-define(CENTRAL_DIR_DIGITAL_SIG_MAGIC, 16#05054b50).
-define(CENTRAL_DIR_DIGITAL_SIG_SZ, (4+2)).
+-define(CENTRAL_FILE_EXT_ATTRIBUTES, 8#644 bsl 16).
-define(CENTRAL_FILE_MAGIC, 16#02014b50).
-record(cd_file_header, {version_made_by,
@@ -191,12 +200,16 @@
zip_comment_length}).
--type create_option() :: memory | cooked | verbose | {comment, string()}
- | {cwd, file:filename()}
- | {compress, extension_spec()}
- | {uncompress, extension_spec()}.
+-type create_option() :: memory | cooked | verbose
+ | {comment, Comment ::string()}
+ | {cwd, CWD :: file:filename()}
+ | {compress, What :: extension_spec()}
+ | {uncompress, What :: extension_spec()}.
-type extension() :: string().
--type extension_spec() :: all | [extension()] | {add, [extension()]} | {del, [extension()]}.
+-type extension_spec() :: all
+ | [Extension :: extension()]
+ | {add, [Extension :: extension()]}
+ | {del, [Extension :: extension()]}.
-type filename() :: file:filename().
-type zip_comment() :: #zip_comment{}.
@@ -277,8 +290,11 @@ do_openzip_get(_, _) ->
throw(einval).
file_name_search(Name,Files) ->
- case lists:dropwhile(fun({ZipFile,_}) -> ZipFile#zip_file.name =/= Name end,
- Files) of
+ Fun = fun({ZipFile,_}) ->
+ not string:equal(ZipFile#zip_file.name, Name,
+ _IgnoreCase = false, _Norm = nfc)
+ end,
+ case lists:dropwhile(Fun, Files) of
[ZFile|_] -> ZFile;
[] -> false
end.
@@ -429,12 +445,7 @@ zip(F, Files) -> zip(F, Files, []).
FileSpec :: file:name() | {file:name(), binary()}
| {file:name(), binary(), file:file_info()},
Options :: [Option],
- Option :: memory | cooked | verbose | {comment, Comment}
- | {cwd, CWD} | {compress, What} | {uncompress, What},
- What :: all | [Extension] | {add, [Extension]} | {del, [Extension]},
- Extension :: string(),
- Comment :: string(),
- CWD :: file:filename(),
+ Option :: create_option(),
RetValue :: {ok, FileName :: file:name()}
| {ok, {FileName :: file:name(), binary()}}
| {error, Reason :: term()}).
@@ -622,9 +633,11 @@ get_zip_opt([Unknown | _Rest], _Opts) ->
%% feedback funs
silent(_) -> ok.
-verbose_unzip(FN) -> io:format("extracting: ~tp\n", [FN]).
+verbose_unzip(FN) ->
+ io:format("extracting: ~ts\n", [io_lib:write_string(FN)]).
-verbose_zip(FN) -> io:format("adding: ~tp\n", [FN]).
+verbose_zip(FN) ->
+ io:format("adding: ~ts\n", [io_lib:write_string(FN)]).
%% file filter funs
all(_) -> true.
@@ -655,7 +668,10 @@ get_zip_options(Files, Options) ->
compress = all,
uncompress = Suffixes
},
- get_zip_opt(Options, Opts).
+ Opts1 = #zip_opts{comment = Comment} = get_zip_opt(Options, Opts),
+ %% UTF-8 encode characters in the interval from 127 to 255.
+ {Comment1, _} = encode_string(Comment),
+ Opts1#zip_opts{comment = Comment1}.
get_unzip_options(F, Options) ->
Opts = #unzip_opts{file_filter = fun all/1,
@@ -850,16 +866,18 @@ put_z_files([F | Rest], Z, Out0, Pos0,
regular -> FileInfo#file_info.size;
directory -> 0
end,
- FileName = get_filename(F, Type),
+ FileName0 = get_filename(F, Type),
+ %% UTF-8 encode characters in the interval from 127 to 255.
+ {FileName, GPFlag} = encode_string(FileName0),
CompMethod = get_comp_method(FileName, UncompSize, Opts, Type),
- LH = local_file_header_from_info_method_name(FileInfo, UncompSize, CompMethod, FileName),
+ LH = local_file_header_from_info_method_name(FileInfo, UncompSize, CompMethod, FileName, GPFlag),
BLH = local_file_header_to_bin(LH),
B = [<<?LOCAL_FILE_MAGIC:32/little>>, BLH],
Out1 = Output({write, B}, Out0),
Out2 = Output({write, FileName}, Out1),
{Out3, CompSize, CRC} = put_z_file(CompMethod, UncompSize, Out2, F1,
0, Input, Output, OpO, Z, Type),
- FB(FileName),
+ FB(FileName0),
Patch = <<CRC:32/little, CompSize:32/little>>,
Out4 = Output({pwrite, Pos0 + ?LOCAL_FILE_HEADER_CRC32_OFFSET, Patch}, Out3),
Out5 = Output({seek, eof, 0}, Out4),
@@ -1012,7 +1030,7 @@ cd_file_header_from_lh_and_pos(LH, Pos) ->
uncomp_size = UncompSize,
file_name_length = FileNameLength,
extra_field_length = ExtraFieldLength} = LH,
- #cd_file_header{version_made_by = 20,
+ #cd_file_header{version_made_by = ?VERSION_MADE_BY,
version_needed = VersionNeeded,
gp_flag = GPFlag,
comp_method = CompMethod,
@@ -1026,7 +1044,7 @@ cd_file_header_from_lh_and_pos(LH, Pos) ->
file_comment_length = 0, % FileCommentLength,
disk_num_start = 0, % DiskNumStart,
internal_attr = 0, % InternalAttr,
- external_attr = 0, % ExternalAttr,
+ external_attr = ?CENTRAL_FILE_EXT_ATTRIBUTES, % ExternalAttr,
local_header_offset = Pos}.
cd_file_header_to_bin(
@@ -1103,10 +1121,10 @@ eocd_to_bin(#eocd{disk_num = DiskNum,
%% put together a local file header
local_file_header_from_info_method_name(#file_info{mtime = MTime},
UncompSize,
- CompMethod, Name) ->
+ CompMethod, Name, GPFlag) ->
{ModDate, ModTime} = dos_date_time_from_datetime(MTime),
#local_file_header{version_needed = 20,
- gp_flag = 0,
+ gp_flag = GPFlag,
comp_method = CompMethod,
last_mod_time = ModTime,
last_mod_date = ModDate,
@@ -1270,7 +1288,9 @@ get_central_dir(In0, RawIterator, Input) ->
In2 = Input({seek, bof, EOCD#eocd.offset}, In1),
N = EOCD#eocd.entries,
Acc0 = [],
- Out0 = RawIterator(EOCD, "", binary_to_list(BComment), <<>>, Acc0),
+ %% There is no encoding flag for the archive comment.
+ Comment = heuristic_to_string(BComment),
+ Out0 = RawIterator(EOCD, "", Comment, <<>>, Acc0),
get_cd_loop(N, In2, RawIterator, Input, Out0).
get_cd_loop(0, In, _RawIterator, _Input, Acc) ->
@@ -1286,20 +1306,32 @@ get_cd_loop(N, In0, RawIterator, Input, Acc0) ->
ExtraLen = CD#cd_file_header.extra_field_length,
CommentLen = CD#cd_file_header.file_comment_length,
ToRead = FileNameLen + ExtraLen + CommentLen,
+ GPFlag = CD#cd_file_header.gp_flag,
{B2, In2} = Input({read, ToRead}, In1),
{FileName, Comment, BExtra} =
- get_name_extra_comment(B2, FileNameLen, ExtraLen, CommentLen),
+ get_name_extra_comment(B2, FileNameLen, ExtraLen, CommentLen, GPFlag),
Acc1 = RawIterator(CD, FileName, Comment, BExtra, Acc0),
get_cd_loop(N-1, In2, RawIterator, Input, Acc1).
-get_name_extra_comment(B, FileNameLen, ExtraLen, CommentLen) ->
- case B of
- <<BFileName:FileNameLen/binary,
- BExtra:ExtraLen/binary,
- BComment:CommentLen/binary>> ->
- {binary_to_list(BFileName), binary_to_list(BComment), BExtra};
- _ ->
- throw(bad_central_directory)
+get_name_extra_comment(B, FileNameLen, ExtraLen, CommentLen, GPFlag) ->
+ try
+ <<BFileName:FileNameLen/binary,
+ BExtra:ExtraLen/binary,
+ BComment:CommentLen/binary>> = B,
+ {binary_to_chars(BFileName, GPFlag),
+ %% Appendix D says: "If general purpose bit 11 is unset, the
+ %% file name and comment should conform to the original ZIP
+ %% character encoding." However, it seems that at least Linux
+ %% zip(1) encodes the comment without setting bit 11 if the
+ %% filename is 7-bit ASCII. If bit 11 is set,
+ %% binary_to_chars/1 could (should?) be called (it can fail),
+ %% but the choice is to employ heuristics in this case too
+ %% (it does not fail).
+ heuristic_to_string(BComment),
+ BExtra}
+ catch
+ _:_ ->
+ throw(bad_central_directory)
end.
%% get end record, containing the offset to the central directory
@@ -1428,7 +1460,8 @@ get_z_file(In0, Z, Input, Output, OpO, FB,
LH#local_file_header.crc32}
end,
{BFileN, In3} = Input({read, FileNameLen + ExtraLen}, In1),
- {FileName, _} = get_file_name_extra(FileNameLen, ExtraLen, BFileN),
+ {FileName, _} =
+ get_file_name_extra(FileNameLen, ExtraLen, BFileN, GPFlag),
ReadAndWrite =
case check_valid_location(CWD, FileName) of
{true,FileName1} ->
@@ -1488,12 +1521,13 @@ check_dir_level([".." | Parts], Level) ->
check_dir_level([_Dir | Parts], Level) ->
check_dir_level(Parts, Level+1).
-get_file_name_extra(FileNameLen, ExtraLen, B) ->
- case B of
- <<BFileName:FileNameLen/binary, BExtra:ExtraLen/binary>> ->
- {binary_to_list(BFileName), BExtra};
- _ ->
- throw(bad_file_header)
+get_file_name_extra(FileNameLen, ExtraLen, B, GPFlag) ->
+ try
+ <<BFileName:FileNameLen/binary, BExtra:ExtraLen/binary>> = B,
+ {binary_to_chars(BFileName, GPFlag), BExtra}
+ catch
+ _:_ ->
+ throw(bad_file_header)
end.
%% get compressed or stored data
@@ -1597,6 +1631,38 @@ skip_bin(B, Pos) when is_binary(B) ->
_ -> <<>>
end.
+binary_to_chars(B, GPFlag) ->
+ ?SHOW_GP_BIT_11(B, GPFlag band ?GP_BIT_11),
+ case GPFlag band ?GP_BIT_11 of
+ 0 ->
+ binary_to_list(B);
+ ?GP_BIT_11 ->
+ case unicode:characters_to_list(B) of
+ List when is_list(List) ->
+ List
+ end
+ end.
+
+heuristic_to_string(B) when is_binary(B) ->
+ case unicode:characters_to_binary(B) of
+ B ->
+ unicode:characters_to_list(B);
+ _ ->
+ binary_to_list(B)
+ end.
+
+encode_string(String) ->
+ case lists:any(fun(C) -> C > 127 end, String) of
+ true ->
+ case unicode:characters_to_binary(String) of
+ B when is_binary(B) ->
+ {binary_to_list(B), ?GP_BIT_11};
+ _ ->
+ throw({bad_unicode, String})
+ end;
+ false ->
+ {String, 0}
+ end.
%% ZIP header manipulations
eocd_and_comment_from_bin(<<DiskNum:16/little,
diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile
index 7f7a0834ba..4b923c5fe2 100644
--- a/lib/stdlib/test/Makefile
+++ b/lib/stdlib/test/Makefile
@@ -34,11 +34,6 @@ MODULES= \
escript_SUITE \
ets_SUITE \
ets_tough_SUITE \
- expand_test \
- expand_test1 \
- unicode_expand \
- ExpandTestCaps \
- ExpandTestCaps1 \
filelib_SUITE \
file_sorter_SUITE \
filename_SUITE \
@@ -79,6 +74,7 @@ MODULES= \
supervisor_deadlock \
naughty_child \
shell_SUITE \
+ shell_docs_SUITE \
supervisor_SUITE \
supervisor_bridge_SUITE \
sys_SUITE \
diff --git a/lib/stdlib/test/beam_lib_SUITE.erl b/lib/stdlib/test/beam_lib_SUITE.erl
index 9ef85a15e0..e28301ec9e 100644
--- a/lib/stdlib/test/beam_lib_SUITE.erl
+++ b/lib/stdlib/test/beam_lib_SUITE.erl
@@ -36,7 +36,8 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
normal/1, error/1, cmp/1, cmp_literals/1, strip/1, strip_add_chunks/1, otp_6711/1,
- building/1, md5/1, encrypted_abstr/1, encrypted_abstr_file/1]).
+ building/1, md5/1, encrypted_abstr/1, encrypted_abstr_file/1,
+ missing_debug_info_backend/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
@@ -46,7 +47,7 @@ suite() ->
all() ->
[error, normal, cmp, cmp_literals, strip, strip_add_chunks, otp_6711,
- building, md5, encrypted_abstr, encrypted_abstr_file].
+ building, md5, encrypted_abstr, encrypted_abstr_file, missing_debug_info_backend].
groups() ->
[].
@@ -775,6 +776,29 @@ write_crypt_file(Contents0) ->
io:format("~s\n", [binary_to_list(Contents)]),
ok = file:write_file(".erlang.crypt", Contents).
+%% GH-4353: Don't crash when the backend for generating the abstract code
+%% is missing.
+missing_debug_info_backend(Conf) ->
+ PrivDir = ?privdir,
+ Simple = filename:join(PrivDir, "simple"),
+ Source = Simple ++ ".erl",
+ BeamFile = Simple ++ ".beam",
+ simple_file(Source),
+
+ %% Create a debug_info chunk with a non-existing backend.
+ {ok,simple} = compile:file(Source, [{outdir,PrivDir}]),
+ {ok,simple,All0} = beam_lib:all_chunks(BeamFile),
+ FakeBackend = definitely__not__an__existing__backend,
+ FakeDebugInfo = {debug_info_v1, FakeBackend, nothing_here},
+ All = lists:keyreplace("Dbgi", 1, All0, {"Dbgi", term_to_binary(FakeDebugInfo)}),
+ {ok,NewBeam} = beam_lib:build_module(All),
+ ok = file:write_file(BeamFile, NewBeam),
+
+ %% beam_lib should not crash, but return an error.
+ verify(missing_backend, beam_lib:chunks(BeamFile, [abstract_code])),
+
+ ok.
+
compare_chunks(File1, File2, ChunkIds) ->
{ok, {_, Chunks1}} = beam_lib:chunks(File1, ChunkIds),
{ok, {_, Chunks2}} = beam_lib:chunks(File2, ChunkIds),
diff --git a/lib/stdlib/test/dict_test_lib.erl b/lib/stdlib/test/dict_test_lib.erl
index 8f999f7cad..a85fd86ab9 100644
--- a/lib/stdlib/test/dict_test_lib.erl
+++ b/lib/stdlib/test/dict_test_lib.erl
@@ -39,6 +39,9 @@ new(Mod, Eq) ->
end.
empty(Mod) ->
+ %% The dict module might not be loaded since it not used by
+ %% anything in the core parts of Erlang/OTP.
+ {module,Mod} = code:ensure_loaded(Mod),
case erlang:function_exported(Mod, new, 0) of
false -> Mod:empty();
true -> Mod:new()
@@ -48,6 +51,7 @@ to_list(Mod, D) ->
Mod:to_list(D).
from_list(Mod, L) ->
+ {module,Mod} = code:ensure_loaded(Mod),
case erlang:function_exported(Mod, from_orddict, 1) of
false ->
Mod:from_list(L);
@@ -63,6 +67,7 @@ from_list(Mod, L) ->
%% Store new value into dictionary or update previous value in dictionary.
enter(Mod, Key, Val, Dict) ->
+ {module,Mod} = code:ensure_loaded(Mod),
case erlang:function_exported(Mod, store, 3) of
false ->
Mod:enter(Key, Val, Dict);
diff --git a/lib/stdlib/test/digraph_SUITE.erl b/lib/stdlib/test/digraph_SUITE.erl
index b5d3452616..ce0bc90f1c 100644
--- a/lib/stdlib/test/digraph_SUITE.erl
+++ b/lib/stdlib/test/digraph_SUITE.erl
@@ -31,7 +31,7 @@
init_per_group/2,end_per_group/2]).
-export([opts/1, degree/1, path/1, cycle/1, vertices/1,
- edges/1, data/1, otp_3522/1, otp_3630/1, otp_8066/1]).
+ edges/1, data/1, otp_3522/1, otp_3630/1, otp_8066/1, vertex_names/1]).
-export([spawn_graph/2]).
@@ -41,10 +41,10 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[opts, degree, path, cycle, {group, misc},
- {group, tickets}].
+ {group, tickets}, vertex_names].
groups() ->
- [{misc, [], [vertices, edges, data]},
+ [{misc, [], [vertices, edges, data, vertex_names]},
{tickets, [], [otp_3522, otp_3630, otp_8066]}].
init_per_suite(Config) ->
@@ -337,6 +337,51 @@ otp_8066(Config) when is_list(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+vertex_names(Config) when is_list(Config) ->
+ %% Check that a node named '_' does not lead to wildcard matches
+ %% in ets.
+
+ G = digraph:new([acyclic]),
+ A = digraph:add_vertex(G, 'A'),
+ B = digraph:add_vertex(G, '_'),
+ AB = digraph:add_edge(G, A, B),
+
+ %% Link A -> B
+ 1 = digraph:out_degree(G, A),
+ 1 = digraph:in_degree(G, B),
+ 0 = digraph:out_degree(G, B),
+ 0 = digraph:in_degree(G, A),
+ [B] = digraph:out_neighbours(G, A),
+ [A] = digraph:in_neighbours(G, B),
+ [] = digraph:out_neighbours(G, B),
+ [] = digraph:in_neighbours(G, A),
+ [AB] = digraph:out_edges(G, A),
+ [AB] = digraph:in_edges(G, B),
+ [] = digraph:out_edges(G, B),
+ [] = digraph:in_edges(G, A),
+
+ %% Reverse the edge
+ digraph:del_edge(G, AB),
+ BA = digraph:add_edge(G, B, A),
+
+ 1 = digraph:out_degree(G, B),
+ 1 = digraph:in_degree(G, A),
+ 0 = digraph:out_degree(G, A),
+ 0 = digraph:in_degree(G, B),
+ [A] = digraph:out_neighbours(G, B),
+ [B] = digraph:in_neighbours(G, A),
+ [] = digraph:out_neighbours(G, A),
+ [] = digraph:in_neighbours(G, B),
+ [BA] = digraph:out_edges(G, B),
+ [BA] = digraph:in_edges(G, A),
+ [] = digraph:out_edges(G, A),
+ [] = digraph:in_edges(G, B),
+
+ digraph:delete(G),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
sane(G) ->
sane1(G),
erase(sane) =:= undefined.
diff --git a/lib/stdlib/test/edlin_expand_SUITE.erl b/lib/stdlib/test/edlin_expand_SUITE.erl
index 5c2b1965ba..dd6b25a531 100644
--- a/lib/stdlib/test/edlin_expand_SUITE.erl
+++ b/lib/stdlib/test/edlin_expand_SUITE.erl
@@ -27,6 +27,7 @@
-include_lib("common_test/include/ct.hrl").
init_per_testcase(_Case, Config) ->
+ cleanup(),
Config.
end_per_testcase(_Case, _Config) ->
@@ -44,10 +45,6 @@ groups() ->
[].
init_per_suite(Config) ->
- (catch code:delete(expand_test)),
- (catch code:delete(expand_test1)),
- (catch code:delete('ExpandTestCaps')),
- (catch code:delete('ExpandTestCaps1')),
Config.
end_per_suite(_Config) ->
@@ -59,9 +56,15 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
+cleanup() ->
+ [try
+ code:purge(M),
+ code:delete(M)
+ catch _:_ -> ok end || M <- [expand_test, expand_test1,
+ 'ExpandTestCaps', 'ExpandTestCaps2']].
normal(Config) when is_list(Config) ->
- {module,expand_test} = c:l(expand_test),
+ {module,expand_test} = compile_and_load(Config,expand_test),
%% These tests might fail if another module with the prefix
%% "expand_" happens to also be loaded.
{yes, "test:", []} = do_expand("expand_"),
@@ -80,8 +83,8 @@ normal(Config) when is_list(Config) ->
%% Normal module name, some function names using quoted atoms.
quoted_fun(Config) when is_list(Config) ->
- {module,expand_test} = c:l(expand_test),
- {module,expand_test1} = c:l(expand_test1),
+ {module,expand_test} = compile_and_load(Config,expand_test),
+ {module,expand_test1} = compile_and_load(Config,expand_test1),
%% should be no colon after test this time
{yes, "test", []} = do_expand("expand_"),
{no, [], []} = do_expand("expandXX_"),
@@ -112,7 +115,7 @@ quoted_fun(Config) when is_list(Config) ->
ok.
quoted_module(Config) when is_list(Config) ->
- {module,'ExpandTestCaps'} = c:l('ExpandTestCaps'),
+ {module,'ExpandTestCaps'} = compile_and_load(Config,'ExpandTestCaps'),
{yes, "Caps':", []} = do_expand("'ExpandTest"),
{no,[],
[{"a_fun_name",1},
@@ -125,8 +128,8 @@ quoted_module(Config) when is_list(Config) ->
ok.
quoted_both(Config) when is_list(Config) ->
- {module,'ExpandTestCaps'} = c:l('ExpandTestCaps'),
- {module,'ExpandTestCaps1'} = c:l('ExpandTestCaps1'),
+ {module,'ExpandTestCaps'} = compile_and_load(Config,'ExpandTestCaps'),
+ {module,'ExpandTestCaps1'} = compile_and_load(Config,'ExpandTestCaps1'),
%% should be no colon (or quote) after test this time
{yes, "Caps", []} = do_expand("'ExpandTest"),
{no,[],[{"'#weird-fun-name'",0},
@@ -229,7 +232,7 @@ check_trailing([I|Str], ArityStr, Suffix, Dots) ->
end.
unicode(Config) when is_list(Config) ->
- {module,unicode_expand} = c:l('unicode_expand'),
+ {module,unicode_expand} = compile_and_load(Config,'unicode_expand'),
{no,[],[{"'кlирилли́ческий атом'",0},
{"'кlирилли́ческий атом'",1},
{"'кlирилли́ческий атомB'",1},
@@ -253,3 +256,10 @@ do_expand(String) ->
do_format(StringList) ->
lists:flatten(edlin_expand:format_matches(StringList)).
+
+compile_and_load(Config,Module) ->
+ Filename = filename:join(
+ proplists:get_value(data_dir,Config),
+ atom_to_list(Module)),
+ {ok,Module,Bin} = compile:file(Filename, [binary]),
+ code:load_binary(Module, Filename, Bin).
diff --git a/lib/stdlib/test/ExpandTestCaps.erl b/lib/stdlib/test/edlin_expand_SUITE_data/ExpandTestCaps.erl
index 7fb1107ae0..7fb1107ae0 100644
--- a/lib/stdlib/test/ExpandTestCaps.erl
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/ExpandTestCaps.erl
diff --git a/lib/stdlib/test/ExpandTestCaps1.erl b/lib/stdlib/test/edlin_expand_SUITE_data/ExpandTestCaps1.erl
index 400a17b137..400a17b137 100644
--- a/lib/stdlib/test/ExpandTestCaps1.erl
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/ExpandTestCaps1.erl
diff --git a/lib/stdlib/test/expand_test.erl b/lib/stdlib/test/edlin_expand_SUITE_data/expand_test.erl
index 5544923a12..5544923a12 100644
--- a/lib/stdlib/test/expand_test.erl
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/expand_test.erl
diff --git a/lib/stdlib/test/expand_test1.erl b/lib/stdlib/test/edlin_expand_SUITE_data/expand_test1.erl
index abefaefcfd..abefaefcfd 100644
--- a/lib/stdlib/test/expand_test1.erl
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/expand_test1.erl
diff --git a/lib/stdlib/test/unicode_expand.erl b/lib/stdlib/test/edlin_expand_SUITE_data/unicode_expand.erl
index 41f741fa84..41f741fa84 100644
--- a/lib/stdlib/test/unicode_expand.erl
+++ b/lib/stdlib/test/edlin_expand_SUITE_data/unicode_expand.erl
diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl
index a90beed4f3..a607598136 100644
--- a/lib/stdlib/test/epp_SUITE.erl
+++ b/lib/stdlib/test/epp_SUITE.erl
@@ -29,7 +29,7 @@
otp_8562/1, otp_8665/1, otp_8911/1, otp_10302/1, otp_10820/1,
otp_11728/1, encoding/1, extends/1, function_macro/1,
test_error/1, test_warning/1, otp_14285/1,
- test_if/1,source_name/1]).
+ test_if/1,source_name/1,otp_16978/1]).
-export([epp_parse_erl_form/2]).
@@ -70,7 +70,7 @@ all() ->
overload_mac, otp_8388, otp_8470, otp_8562,
otp_8665, otp_8911, otp_10302, otp_10820, otp_11728,
encoding, extends, function_macro, test_error, test_warning,
- otp_14285, test_if, source_name].
+ otp_14285, test_if, source_name, otp_16978].
groups() ->
[{upcase_mac, [], [upcase_mac_1, upcase_mac_2]},
@@ -1154,7 +1154,12 @@ test_if(Config) ->
{if_7c,
<<"-if(begin true end).\n" %Not a guard expression.
"-endif.\n">>,
- {errors,[{1,epp,{bad,'if'}}],[]}}
+ {errors,[{1,epp,{bad,'if'}}],[]}},
+
+ {if_8c,
+ <<"-if(?foo).\n" %Undefined symbol.
+ "-endif.\n">>,
+ {errors,[{1,epp,{undefined,foo,none}}],[]}}
],
[] = compile(Config, Cs),
@@ -1715,21 +1720,48 @@ source_name_1(File, Expected) ->
Res = epp:parse_file(File, [{source_name, Expected}]),
{ok, [{attribute,_,file,{Expected,_}} | _Forms]} = Res.
+otp_16978(Config) when is_list(Config) ->
+ %% A test of erl_parse:tokens().
+ P = <<"t() -> ?a.">>,
+ Vs = [#{},
+ #{k => 1,[[a],[{}]] => "str"},
+ #{#{} => [{#{x=>#{3=>$3}}},{3.14,#{}}]}],
+ Ts = [{erl_parse_tokens,
+ P,
+ [{d,{a,V}}],
+ V} || V <- Vs],
+ [] = run(Config, Ts),
+
+ ok.
+
check(Config, Tests) ->
- eval_tests(Config, fun check_test/2, Tests).
+ eval_tests(Config, fun check_test/3, Tests).
compile(Config, Tests) ->
- eval_tests(Config, fun compile_test/2, Tests).
+ eval_tests(Config, fun compile_test/3, Tests).
run(Config, Tests) ->
- eval_tests(Config, fun run_test/2, Tests).
+ eval_tests(Config, fun run_test/3, Tests).
eval_tests(Config, Fun, Tests) ->
- F = fun({N,P,E}, BadL) ->
+ TestsWithOpts =
+ [case Test of
+ {N,P,E} ->
+ {N,P,[],E};
+ {_,_,_,_} ->
+ Test
+ end || Test <- Tests],
+ F = fun({N,P,Opts,E}, BadL) ->
%% io:format("Testing ~p~n", [P]),
- Return = Fun(Config, P),
+ Return = Fun(Config, P, Opts),
case message_compare(E, Return) of
true ->
+ case E of
+ {errors, Errors} ->
+ call_format_error(Errors);
+ _ ->
+ ok
+ end,
BadL;
false ->
io:format("~nTest ~p failed. Expected~n ~p~n"
@@ -1737,15 +1769,14 @@ eval_tests(Config, Fun, Tests) ->
fail()
end
end,
- lists:foldl(F, [], Tests).
-
+ lists:foldl(F, [], TestsWithOpts).
-check_test(Config, Test) ->
+check_test(Config, Test, Opts) ->
Filename = "epp_test.erl",
PrivDir = proplists:get_value(priv_dir, Config),
File = filename:join(PrivDir, Filename),
ok = file:write_file(File, Test),
- case epp:parse_file(File, [PrivDir], []) of
+ case epp:parse_file(File, [PrivDir], Opts) of
{ok,Forms} ->
Errors = [E || E={error,_} <- Forms],
call_format_error([E || {error,E} <- Errors]),
@@ -1754,15 +1785,18 @@ check_test(Config, Test) ->
Error
end.
-compile_test(Config, Test0) ->
+compile_test(Config, Test0, Opts0) ->
Test = [<<"-module(epp_test). ">>, Test0],
Filename = "epp_test.erl",
PrivDir = proplists:get_value(priv_dir, Config),
File = filename:join(PrivDir, Filename),
ok = file:write_file(File, Test),
- Opts = [export_all,nowarn_export_all,return,nowarn_unused_record,{outdir,PrivDir}],
+ Opts = [export_all,nowarn_export_all,return,nowarn_unused_record,{outdir,PrivDir}] ++ Opts0,
case compile_file(File, Opts) of
{ok, Ws} -> warnings(File, Ws);
+ {errors, Errors}=Else ->
+ call_format_error(Errors),
+ Else;
Else -> Else
end.
@@ -1808,13 +1842,13 @@ epp_parse_file(File, Opts) ->
unopaque_forms(Forms) ->
[erl_parse:anno_to_term(Form) || Form <- Forms].
-run_test(Config, Test0) ->
+run_test(Config, Test0, Opts0) ->
Test = [<<"-module(epp_test). -export([t/0]). ">>, Test0],
Filename = "epp_test.erl",
PrivDir = proplists:get_value(priv_dir, Config),
File = filename:join(PrivDir, Filename),
ok = file:write_file(File, Test),
- Opts = [return, {i,PrivDir},{outdir,PrivDir}],
+ Opts = [return, {i,PrivDir},{outdir,PrivDir}] ++ Opts0,
{ok, epp_test, []} = compile:file(File, Opts),
AbsFile = filename:rootname(File, ".erl"),
{module, epp_test} = code:load_abs(AbsFile, epp_test),
diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl
index c7556f6f7e..a29b60cd1d 100644
--- a/lib/stdlib/test/erl_eval_SUITE.erl
+++ b/lib/stdlib/test/erl_eval_SUITE.erl
@@ -49,7 +49,9 @@
eep37/1,
eep43/1,
otp_15035/1,
- otp_16439/1]).
+ otp_16439/1,
+ otp_14708/1,
+ otp_16545/1]).
%%
%% Define to run outside of test server
@@ -89,7 +91,7 @@ all() ->
otp_6539, otp_6543, otp_6787, otp_6977, otp_7550,
otp_8133, otp_10622, otp_13228, otp_14826,
funs, try_catch, eval_expr_5, zero_width,
- eep37, eep43, otp_15035, otp_16439].
+ eep37, eep43, otp_15035, otp_16439, otp_14708, otp_16545].
groups() ->
[].
@@ -1674,10 +1676,75 @@ otp_16439(Config) when is_list(Config) ->
"case 7 of - - 7 -> seven end.", seven),
{ok,Ts,_} = erl_scan:string("- #{}. "),
- {ok,[{op,1,'-',{map,1,[]}}]} = erl_parse:parse_exprs(Ts),
+ A = erl_anno:new(1),
+ {ok,[{op,A,'-',{map,A,[]}}]} = erl_parse:parse_exprs(Ts),
ok.
+%% Test guard expressions in keys for maps and in sizes in binary matching.
+
+otp_14708(Config) when is_list(Config) ->
+ check(fun() -> X = 42, #{{tag,X} := V} = #{{tag,X} => a}, V end,
+ "begin X = 42, #{{tag,X} := V} = #{{tag,X} => a}, V end.",
+ a),
+ check(fun() ->
+ T = {x,y,z},
+ Map = #{x => 99, y => 100},
+ #{element(1, T) := V1, element(2, T) := V2} = Map,
+ {V1, V2}
+ end,
+ "begin
+ T = {x,y,z},
+ Map = #{x => 99, y => 100},
+ #{element(1, T) := V1, element(2, T) := V2} = Map,
+ {V1, V2}
+ end.",
+ {99, 100}),
+ error_check("#{term_to_binary(42) := _} = #{}.", illegal_guard_expr),
+
+ check(fun() ->
+ <<Sz:16,Body:(Sz-1)/binary>> = <<4:16,1,2,3>>,
+ Body
+ end,
+ "begin
+ <<Sz:16,Body:(Sz-1)/binary>> = <<4:16,1,2,3>>,
+ Body
+ end.",
+ <<1,2,3>>),
+ check(fun() ->
+ Sizes = #{0 => 3, 1 => 7},
+ <<SzTag:1,Body:(map_get(SzTag, Sizes))/binary>> =
+ <<1:1,1,2,3,4,5,6,7>>,
+ Body
+ end,
+ "begin
+ Sizes = #{0 => 3, 1 => 7},
+ <<SzTag:1,Body:(map_get(SzTag, Sizes))/binary>> =
+ <<1:1,1,2,3,4,5,6,7>>,
+ Body
+ end.",
+ <<1,2,3,4,5,6,7>>),
+ error_check("<<X:(process_info(self()))>> = <<>>.", illegal_bitsize),
+
+ ok.
+
+otp_16545(Config) when is_list(Config) ->
+ case eval_string("<<$W/utf16-native>> = <<$W/utf16-native>>.") of
+ <<$W/utf16-native>> -> ok
+ end,
+ case eval_string("<<$W/utf32-native>> = <<$W/utf32-native>>.") of
+ <<$W/utf32-native>> -> ok
+ end,
+ check(fun() -> <<10/unsigned,"fgbz":86>> end,
+ "<<10/unsigned,\"fgbz\":86>>.",
+ <<10,0,0,0,0,0,0,0,0,0,1,152,0,0,0,0,0,0,0,0,0,6,112,0,0,
+ 0,0,0,0,0,0,0,24,128,0,0,0,0,0,0,0,0,0,122>>),
+ check(fun() -> <<"":16/signed>> end,
+ "<<\"\":16/signed>>.",
+ <<>>),
+ error_check("<<\"\":problem/signed>>.", badarg),
+ ok.
+
%% Check the string in different contexts: as is; in fun; from compiled code.
check(F, String, Result) ->
check1(F, String, Result),
diff --git a/lib/stdlib/test/erl_expand_records_SUITE.erl b/lib/stdlib/test/erl_expand_records_SUITE.erl
index 3885f6648d..257546d6a4 100644
--- a/lib/stdlib/test/erl_expand_records_SUITE.erl
+++ b/lib/stdlib/test/erl_expand_records_SUITE.erl
@@ -702,9 +702,13 @@ otp_7078(Config) when is_list(Config) ->
-record(otp_7101, {a,b,c=[],d=[],e=[]}).
+id(I) -> I.
+
%% OTP-7101. Record update: more than one call to setelement/3.
otp_7101(Config) when is_list(Config) ->
- Rec = #otp_7101{},
+ %% Ensure the compiler won't do any funny constant propagation tricks.
+ id(#otp_7101{a=a,b=b,c=c,d=d,e=e}),
+ Rec = id(#otp_7101{}),
%% Spawn a tracer process to count the number of setelement/3 calls.
%% The tracer will forward all trace messages to us.
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index 38d07249fd..aa941e61b6 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2019. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -69,7 +69,8 @@
stacktrace_syntax/1,
otp_14285/1, otp_14378/1,
external_funs/1,otp_15456/1,otp_15563/1,
- unused_type/1]).
+ unused_type/1,removed/1, otp_16516/1,
+ inline_nifs/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -91,7 +92,8 @@ all() ->
otp_11851, otp_11879, otp_13230,
record_errors, otp_11879_cont, non_latin1_module, otp_14323,
stacktrace_syntax, otp_14285, otp_14378, external_funs,
- otp_15456, otp_15563, unused_type].
+ otp_15456, otp_15563, unused_type, removed, otp_16516,
+ inline_nifs].
groups() ->
[{unused_vars_warn, [],
@@ -469,7 +471,7 @@ unused_vars_warn_lc(Config) when is_list(Config) ->
">>,
[warn_unused_vars],
{warnings,[{6,erl_lint,{unused_var,'C1'}},
- {7,sys_core_fold,no_clause_match},
+ {7,sys_core_fold,no_clause_match},
{9,erl_lint,{unused_var,'C3'}}]}},
{lc21,
@@ -1298,6 +1300,10 @@ unsized_binary_in_bin_gen_pattern(Config) when is_list(Config) ->
<< <<X,Tail/bits>> || <<X,Tail/bits>> <= Bin >>;
t({bc,bitstring,Bin}) ->
<< <<X,Tail/bits>> || <<X,Tail/bitstring>> <= Bin >>;
+ t({bc,binary_lit,Bin}) ->
+ << <<X>> || <<X,1/binary>> <= Bin >>;
+ t({bc,bytes_lit,Bin}) ->
+ << <<X>> || <<X,a/bytes>> <= Bin >>;
t({lc,binary,Bin}) ->
[ {X,Tail} || <<X,Tail/binary>> <= Bin ];
t({lc,bytes,Bin}) ->
@@ -1305,17 +1311,25 @@ unsized_binary_in_bin_gen_pattern(Config) when is_list(Config) ->
t({lc,bits,Bin}) ->
[ {X,Tail} || <<X,Tail/bits>> <= Bin ];
t({lc,bitstring,Bin}) ->
- [ {X,Tail} || <<X,Tail/bitstring>> <= Bin ].">>,
+ [ {X,Tail} || <<X,Tail/bitstring>> <= Bin ];
+ t({lc,bits_lit,Bin}) ->
+ [ X || <<X,42/bits>> <= Bin ];
+ t({lc,bitstring_lit,Bin}) ->
+ [ X || <<X,b/bitstring>> <= Bin ].">>,
[],
{errors,
[{2,erl_lint,unsized_binary_in_bin_gen_pattern},
{4,erl_lint,unsized_binary_in_bin_gen_pattern},
{6,erl_lint,unsized_binary_in_bin_gen_pattern},
{8,erl_lint,unsized_binary_in_bin_gen_pattern},
- {10,erl_lint,unsized_binary_in_bin_gen_pattern},
- {12,erl_lint,unsized_binary_in_bin_gen_pattern},
- {14,erl_lint,unsized_binary_in_bin_gen_pattern},
- {16,erl_lint,unsized_binary_in_bin_gen_pattern}],
+ {10,erl_lint,unsized_binary_in_bin_gen_pattern},
+ {12,erl_lint,unsized_binary_in_bin_gen_pattern},
+ {14,erl_lint,unsized_binary_in_bin_gen_pattern},
+ {16,erl_lint,unsized_binary_in_bin_gen_pattern},
+ {18,erl_lint,unsized_binary_in_bin_gen_pattern},
+ {20,erl_lint,unsized_binary_in_bin_gen_pattern},
+ {22,erl_lint,unsized_binary_in_bin_gen_pattern},
+ {24,erl_lint,unsized_binary_in_bin_gen_pattern}],
[]}}],
[] = run(Config, Ts),
ok.
@@ -2061,8 +2075,9 @@ otp_5362(Config) when is_list(Config) ->
{error,
[{5,erl_lint,{call_to_redefined_old_bif,{spawn,1}}}],
[{4,erl_lint,{deprecated,{erlang,now,0},
- "Deprecated BIF. See the \"Time and Time Correction in Erlang\" "
- "chapter of the ERTS User's Guide for more information."}}]}},
+ "see the \"Time and Time Correction in Erlang\" "
+ "chapter of the ERTS User's Guide for more "
+ "information"}}]}},
{otp_5362_5,
<<"-compile(nowarn_deprecated_function).
-compile(nowarn_bif_clash).
@@ -2119,8 +2134,9 @@ otp_5362(Config) when is_list(Config) ->
{nowarn_bif_clash,{spawn,1}}]}, % has no effect
{warnings,
[{5,erl_lint,{deprecated,{erlang,now,0},
- "Deprecated BIF. See the \"Time and Time Correction in Erlang\" "
- "chapter of the ERTS User's Guide for more information."}}]}},
+ "see the \"Time and Time Correction in Erlang\" "
+ "chapter of the ERTS User's Guide for more "
+ "information"}}]}},
{otp_5362_9,
<<"-include_lib(\"stdlib/include/qlc.hrl\").
@@ -2150,13 +2166,15 @@ otp_5362(Config) when is_list(Config) ->
[],
{warnings,
[{1,erl_lint,{deprecated,{calendar,local_time_to_universal_time,1},
- {calendar,local_time_to_universal_time_dst,1}, "a future release"}}]}},
+ "use calendar:local_time_to_universal_time_dst/1 "
+ "instead"}}]}},
{call_removed_function,
<<"t(X) -> erlang:hash(X, 10000).">>,
[],
{warnings,
- [{1,erl_lint,{removed,{erlang,hash,2},{erlang,phash2,2},"20.0"}}]}},
+ [{1,erl_lint,{removed,{erlang,hash,2},
+ "use erlang:phash2/2 instead"}}]}},
{nowarn_call_removed_function_1,
<<"t(X) -> erlang:hash(X, 10000).">>,
@@ -2173,7 +2191,7 @@ otp_5362(Config) when is_list(Config) ->
[],
{warnings,[{1,erl_lint,
{removed,{os_mon_mib,any_function_really,1},
- "was removed in 22.0"}}]}},
+ "this module was removed in OTP 22.0"}}]}},
{nowarn_call_removed_module,
<<"t(X) -> os_mon_mib:any_function_really(X).">>,
@@ -2212,7 +2230,6 @@ otp_15456(Config) when is_list(Config) ->
warn_deprecated_function]},
{warnings,[{5,erl_lint,
{deprecated,{random,seed,3},
- "the 'random' module is deprecated; "
"use the 'rand' module instead"}}]}},
%% {nowarn_unused_function,[{M,F,A}]} can be given
@@ -3645,6 +3662,7 @@ bin_syntax_errors(Config) ->
Ts = [{bin_syntax_errors,
<<"t(<<X:bad_size>>) -> X;
t(<<_:(x ! y)/integer>>) -> ok;
+ t(<<_:(l())/integer>>) -> ok;
t(<<X:all/integer>>) -> X;
t(<<X/bad_type>>) -> X;
t(<<X/unit:8>>) -> X;
@@ -3653,22 +3671,30 @@ bin_syntax_errors(Config) ->
t(<<(x ! y):8/integer>>) -> ok;
t(X) ->
{<<X/binary-integer>>,<<X/signed-unsigned-integer>>,
- <<X/little-big>>,<<X/unit:4-unit:8>>}.
+ <<X/little-big>>,<<X/unit:4-unit:8>>};
+ t(<<_:{A,B}>>) -> ok.
+
+ l() ->
+ foo.
">>,
[],
- {error,[{1,erl_lint,illegal_bitsize},
- {2,erl_lint,illegal_bitsize},
- {3,erl_lint,illegal_bitsize},
- {4,erl_lint,{undefined_bittype,bad_type}},
- {5,erl_lint,bittype_unit},
- {7,erl_lint,illegal_pattern},
+ {error,[{2,erl_lint,illegal_bitsize},
+ {3,erl_lint,{illegal_bitsize_local_call,{l,0}}},
+ {5,erl_lint,{undefined_bittype,bad_type}},
+ {6,erl_lint,bittype_unit},
{8,erl_lint,illegal_pattern},
- {10,erl_lint,{bittype_mismatch,integer,binary,"type"}},
- {10,erl_lint,{bittype_mismatch,unsigned,signed,"sign"}},
- {11,erl_lint,{bittype_mismatch,8,4,"unit"}},
- {11,erl_lint,{bittype_mismatch,big,little,"endianness"}}
+ {9,erl_lint,illegal_pattern},
+ {11,erl_lint,{bittype_mismatch,integer,binary,"type"}},
+ {11,erl_lint,{bittype_mismatch,unsigned,signed,"sign"}},
+ {12,erl_lint,{bittype_mismatch,8,4,"unit"}},
+ {12,erl_lint,{bittype_mismatch,big,little,"endianness"}},
+ {13,erl_lint,{unbound_var,'A'}},
+ {13,erl_lint,{unbound_var,'B'}}
],
- [{6,erl_lint,{bad_bitsize,"float"}}]}}
+ [{1,erl_lint,non_integer_bitsize},
+ {4,erl_lint,non_integer_bitsize},
+ {7,erl_lint,{bad_bitsize,"float"}},
+ {13,erl_lint,non_integer_bitsize}]}}
],
[] = run(Config, Ts),
ok.
@@ -3779,7 +3805,7 @@ maps(Config) ->
">>,
[],
{errors,[{4,erl_lint,illegal_map_construction},
- {6,erl_lint,illegal_map_key}],[]}},
+ {6,erl_lint,{unbound_var,'V'}}],[]}},
{unused_vars_with_empty_maps,
<<"t(Foo, Bar, Baz) -> {#{},#{}}.">>,
[warn_unused_variables],
@@ -4128,9 +4154,9 @@ otp_14378(Config) ->
[],
{warnings,[{4,erl_lint,
{deprecated,{erlang,now,0},
- "Deprecated BIF. See the \"Time and Time Correction"
- " in Erlang\" chapter of the ERTS User's Guide"
- " for more information."}}]}}],
+ "see the \"Time and Time Correction in Erlang\" "
+ "chapter of the ERTS User's Guide for more "
+ "information"}}]}}],
[] = run(Config, Ts),
ok.
@@ -4291,6 +4317,140 @@ otp_15563(Config) when is_list(Config) ->
[] = run(Config, Ts),
ok.
+removed(Config) when is_list(Config) ->
+ Ts = [{removed,
+ <<"-removed([{nonexistent,1,\"hi\"}]). %% okay since it doesn't exist
+ -removed([frutt/0]). %% okay since frutt/0 is not exported
+ -removed([t/0]). %% not okay since t/0 is exported
+ -removed([{t,'_'}]). %% not okay since t/0 is exported
+ -removed([{'_','_'}]). %% not okay since t/0 is exported
+ -removed([{{badly,formed},1}]).
+ -removed('badly formed').
+ -export([t/0]).
+ frutt() -> ok.
+ t() -> ok.
+ ">>,
+ {[]},
+ {error,[{3,erl_lint,{bad_removed,{t,0}}},
+ {4,erl_lint,{bad_removed,{t,'_'}}},
+ {5,erl_lint,{bad_removed,{'_','_'}}},
+ {6,erl_lint,{invalid_removed,{{badly,formed},1}}},
+ {7,erl_lint,{invalid_removed,'badly formed'}}],
+ [{9,erl_lint,{unused_function,{frutt,0}}}]}}
+ ],
+ [] = run(Config, Ts),
+ ok.
+
+otp_16516(Config) when is_list(Config) ->
+ one_multi_init(Config),
+ several_multi_inits(Config).
+
+one_multi_init(Config) ->
+ "'_' initializes no omitted fields" =
+ format_error(bad_multi_field_init),
+ Ts = [{otp_16516_1,
+ <<"-record(r, {f}).
+ t(#r{f = 17, _ = V}) ->
+ V.
+ u(#r{_ = V, f = 17}) ->
+ V.
+ -record(r1, {f, g = 17}).
+ g(#r1{f = 3, _ = 42}) ->
+ g.
+ ">>,
+ [],
+ {errors,[{2,erl_lint,bad_multi_field_init},
+ {4,erl_lint,bad_multi_field_init}],[]}},
+ {otp_16516_2,
+ %% No error since "_ = '_'" is actually used as a catch-all
+ %% initialization. V is unused (as compilation with the 'E'
+ %% option shows), but no warning about V being unused is
+ %% output.
+ <<"-record(r, {f}).
+ t(V) ->
+ #r{f = 3, _ = V}.
+ u(V) ->
+ #r{_ = V, f = 3}.
+ ">>,
+ [],
+ []},
+ {otp_16516_3,
+ %% No error in this case either. And no unused variable warning.
+ <<"-record(r, {f}).
+ t(V) when #r{f = 3, _ = V} =:= #r{f = 3} ->
+ a.
+ u(V) when #r{_ = V, f = 3} =:= #r{f = 3} ->
+ a.
+ ">>,
+ [],
+ []}],
+ [] = run(Config, Ts).
+
+several_multi_inits(Config) ->
+ Ts = [{otp_16516_4,
+ <<"-record(r, {f, g}).
+ t(#r{f = 17, _ = V1,
+ _ = V2}) ->
+ {V1, V2}.
+ u(#r{_ = V1, f = 17,
+ _ = V2}) ->
+ {V1, V2}.
+ v(#r{_ = V1, f = 17}) ->
+ V1.
+ ">>,
+ [],
+ {errors,[{3,erl_lint,bad_multi_field_init},
+ {4,erl_lint,{unbound_var,'V1'}},
+ {4,erl_lint,{unbound_var,'V2'}},
+ {6,erl_lint,bad_multi_field_init},
+ {7,erl_lint,{unbound_var,'V1'}},
+ {7,erl_lint,{unbound_var,'V2'}}],[]}},
+ {otp_16516_5,
+ <<"-record(r, {f, g}).
+ t(V1, V2) ->
+ #r{_ = V1, f = 3,
+ _ = V2}.
+ u(V1, V2) ->
+ #r{_ = V1,
+ _ = V2, f = 3}.
+ ">>,
+ [],
+ {error,[{4,erl_lint,bad_multi_field_init},
+ {7,erl_lint,bad_multi_field_init}],
+ [{2,erl_lint,{unused_var,'V2'}},{5,erl_lint,{unused_var,'V2'}}]}},
+ {otp_16516_6,
+ <<"-record(r, {f, g}).
+ t(V1, V2) when #r{_ = V1, f = 3,
+ _ = V2} =:= #r{} ->
+ a.
+ u(V1, V2) when #r{_ = V1, f = 3,
+ _ = V2} =:= #r{} ->
+ a.
+ ">>,
+ [],
+ {error,[{3,erl_lint,bad_multi_field_init},
+ {6,erl_lint,bad_multi_field_init}],
+ [{2,erl_lint,{unused_var,'V2'}},
+ {5,erl_lint,{unused_var,'V2'}}]}}],
+ [] = run(Config, Ts).
+
+inline_nifs(Config) ->
+ Ts = [{implicit_inline,
+ <<"-compile(inline).
+ t() -> erlang:load_nif([], []).
+ gurka() -> ok.
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,nif_inline}]}},
+ {explicit_inline,
+ <<"-compile({inline, [gurka/0]}).
+ t() -> erlang:load_nif([], []).
+ gurka() -> ok.
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,nif_inline}]}}],
+ [] = run(Config, Ts).
+
format_error(E) ->
lists:flatten(erl_lint:format_error(E)).
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index 48152243f8..4edecdb8c3 100644
--- a/lib/stdlib/test/erl_pp_SUITE.erl
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -47,6 +47,7 @@
hook/1,
neg_indent/1,
maps_syntax/1,
+ format_options/1,
quoted_atom_types/1,
otp_6321/1, otp_6911/1, otp_6914/1, otp_8150/1, otp_8238/1,
@@ -76,7 +77,8 @@ groups() ->
[{expr, [],
[func, call, recs, try_catch, if_then, receive_after,
bits, head_tail, cond1, block, case1, ops,
- messages, maps_syntax, quoted_atom_types
+ messages, maps_syntax, quoted_atom_types,
+ format_options
]},
{attributes, [], [misc_attrs, import_export, dialyzer_attrs]},
{tickets, [],
@@ -545,6 +547,36 @@ import_export(Config) when is_list(Config) ->
compile(Config, Ts),
ok.
+format_options(Config) when is_list(Config) ->
+ "case 1 of\n"
+ " 2 ->\n"
+ " 3;\n"
+ " 4 ->\n"
+ " 5\n"
+ "end" = flat_parse_and_pp_expr("case 1 of 2 -> 3; 4 -> 5 end", 0, [{indent, 2}]),
+
+ "-spec foo(bar(),\n"
+ " qux()) ->\n"
+ " T |\n"
+ " baz(T)\n"
+ " when\n"
+ " T ::\n"
+ " tuple().\n" =
+ lists:flatten(
+ parse_and_pp_forms(
+ "-spec foo(bar(), qux()) -> T | baz(T) when T :: tuple().",
+ [{indent, 2}, {linewidth, 20}]
+ )
+ ),
+
+ "-spec foo(bar(), qux()) -> T | baz(T) when T :: tuple().\n" =
+ lists:flatten(
+ parse_and_pp_forms(
+ "-spec foo(bar(), qux()) -> T | baz(T) when T :: tuple().",
+ [{indent, 2}, {linewidth, 1000}]
+ )
+ ).
+
misc_attrs(Config) when is_list(Config) ->
ok = pp_forms(<<"-module(m). ">>),
ok = pp_forms(<<"-module(m, [Aafjlksfjdlsjflsdfjlsdjflkdsfjlk,"
@@ -1273,7 +1305,7 @@ otp_16435(_Config) ->
CheckF("f() ->\n << \n (catch <<1:4>>) ||\n"
" A <- []\n >>.\n"),
CheckF("f() ->\n [ \n (catch foo) ||\n A <- []\n ].\n"),
-
+ CheckF("f() when erlang:float(3.0) ->\n true.\n"),
Check = fun(S) -> S = flat_parse_and_pp_expr(S, 0, []) end,
Check("5 #r4.f1"),
diff --git a/lib/stdlib/test/erl_scan_SUITE.erl b/lib/stdlib/test/erl_scan_SUITE.erl
index aca5b1e54f..162e2a0a3d 100644
--- a/lib/stdlib/test/erl_scan_SUITE.erl
+++ b/lib/stdlib/test/erl_scan_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
init_per_group/2,end_per_group/2]).
-export([error_1/1, error_2/1, iso88591/1, otp_7810/1, otp_10302/1,
- otp_10990/1, otp_10992/1, otp_11807/1]).
+ otp_10990/1, otp_10992/1, otp_11807/1, otp_16480/1]).
-import(lists, [nth/2,flatten/1]).
-import(io_lib, [print/1]).
@@ -58,7 +58,7 @@ suite() ->
all() ->
[{group, error}, iso88591, otp_7810, otp_10302, otp_10990, otp_10992,
- otp_11807].
+ otp_11807, otp_16480].
groups() ->
[{error, [], [error_1, error_2]}].
@@ -300,6 +300,32 @@ integers() ->
Ts = [{integer,{1,1},I}],
test_string(S, Ts)
end || S <- [[N] || N <- lists:seq($0, $9)] ++ ["2323","000"] ],
+ UnderscoreSamples =
+ [{"123_456", 123456},
+ {"123_456_789", 123456789},
+ {"1_2", 12}],
+ lists:foreach(
+ fun({S, I}) ->
+ test_string(S, [{integer, {1, 1}, I}])
+ end, UnderscoreSamples),
+ UnderscoreErrors =
+ ["123_",
+ "123__",
+ "123_456_",
+ "123__456",
+ "_123",
+ "__123"],
+ lists:foreach(
+ fun(S) ->
+ case erl_scan:string(S) of
+ {ok, [{integer, _, _}], _} ->
+ error({unexpected_integer, S});
+ _ ->
+ ok
+ end
+ end, UnderscoreErrors),
+ test_string("_123", [{var,{1,1},'_123'}]),
+ test_string("123_", [{integer,{1,1},123},{var,{1,4},'_'}]),
ok.
base_integers() ->
@@ -315,13 +341,19 @@ base_integers() ->
{error,{{1,1},erl_scan,{base,1}},{1,2}} =
erl_scan:string("1#000", {1,1}, []),
+ {error,{1,erl_scan,{base,1}},1} = erl_scan:string("1#000"),
+ {error,{{1,1},erl_scan,{base,1000}},{1,6}} =
+ erl_scan:string("1_000#000", {1,1}, []),
+
test_string("12#bc", [{integer,{1,1},11},{atom,{1,5},c}]),
[begin
Str = BS ++ "#" ++ S,
- {error,{1,erl_scan,{illegal,integer}},1} =
- erl_scan:string(Str)
- end || {BS,S} <- [{"3","3"},{"15","f"}, {"12","c"}] ],
+ E = 2 + length(BS),
+ {error,{{1,1},erl_scan,{illegal,integer}},{1,E}} =
+ erl_scan:string(Str, {1,1}, [])
+ end || {BS,S} <- [{"3","3"},{"15","f"},{"12","c"},
+ {"1_5","f"},{"1_2","c"}] ],
{ok,[{integer,1,239},{'@',1}],1} = erl_scan_string("16#ef@"),
{ok,[{integer,{1,1},239},{'@',{1,6}}],{1,7}} =
@@ -329,6 +361,36 @@ base_integers() ->
{ok,[{integer,{1,1},14},{atom,{1,5},g@}],{1,7}} =
erl_scan_string("16#eg@", {1,1}, []),
+ UnderscoreSamples =
+ [{"16#1234_ABCD_EF56", 16#1234abcdef56},
+ {"2#0011_0101_0011", 2#001101010011},
+ {"1_6#123ABC", 16#123abc},
+ {"1_6#123_ABC", 16#123abc},
+ {"16#abcdef", 16#ABCDEF}],
+ lists:foreach(
+ fun({S, I}) ->
+ test_string(S, [{integer, {1, 1}, I}])
+ end, UnderscoreSamples),
+ UnderscoreErrors =
+ ["16_#123ABC",
+ "16#123_",
+ "16#_123",
+ "16#ABC_",
+ "16#_ABC",
+ "2#_0101",
+ "1__6#ABC",
+ "16#AB__CD"],
+ lists:foreach(
+ fun(S) ->
+ case erl_scan:string(S) of
+ {ok, [{integer, _, _}], _} ->
+ error({unexpected_integer, S});
+ _ ->
+ ok
+ end
+ end, UnderscoreErrors),
+ test_string("16#123_", [{integer,{1,1},291},{var,{1,7},'_'}]),
+ test_string("_16#ABC", [{var,{1,1},'_16'},{'#',{1,4}},{var,{1,5},'ABC'}]),
ok.
floats() ->
@@ -344,12 +406,44 @@ floats() ->
erl_scan:string("1.0e400"),
{error,{{1,1},erl_scan,{illegal,float}},{1,8}} =
erl_scan:string("1.0e400", {1,1}, []),
+ {error,{{1,1},erl_scan,{illegal,float}},{1,9}} =
+ erl_scan:string("1.0e4_00", {1,1}, []),
[begin
{error,{1,erl_scan,{illegal,float}},1} = erl_scan:string(S),
{error,{{1,1},erl_scan,{illegal,float}},{1,_}} =
erl_scan:string(S, {1,1}, [])
end || S <- ["1.14Ea"]],
+ UnderscoreSamples =
+ [{"123_456.789", 123456.789},
+ {"123.456_789", 123.456789},
+ {"1.2_345e10", 1.2345e10},
+ {"1.234e1_06", 1.234e106},
+ {"12_34.56_78e1_6", 1234.5678e16},
+ {"12_34.56_78e-1_8", 1234.5678e-18}],
+ lists:foreach(
+ fun({S, I}) ->
+ test_string(S, [{float, {1, 1}, I}])
+ end, UnderscoreSamples),
+ UnderscoreErrors =
+ ["123_.456",
+ "123._456",
+ "123.456_",
+ "123._",
+ "1._23e10",
+ "1.23e_10",
+ "1.23e10_"],
+ lists:foreach(
+ fun(S) ->
+ case erl_scan:string(S) of
+ {ok, [{float, _, _}], _} ->
+ error({unexpected_float, S});
+ _ ->
+ ok
+ end
+ end, UnderscoreErrors),
+ test_string("123._", [{integer,{1,1},123},{'.',{1,4}},{var,{1,5},'_'}]),
+ test_string("1.23_e10", [{float,{1,1},1.23},{var,{1,5},'_e10'}]),
ok.
dots() ->
@@ -1103,6 +1197,11 @@ otp_11807(Config) when is_list(Config) ->
(catch erl_parse:abstract("string", [{encoding,bad}])),
ok.
+otp_16480(Config) when is_list(Config) ->
+ F = fun mod:func/19,
+ F = erl_parse:normalise(erl_parse_abstract(F)),
+ ok.
+
test_string(String, ExpectedWithCol) ->
{ok, ExpectedWithCol, _EndWithCol} = erl_scan_string(String, {1, 1}, []),
Expected = [ begin
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index 092cf68bed..73cfada4c0 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -40,16 +40,21 @@
-export([lookup_element_mult/1]).
-export([foldl_ordered/1, foldr_ordered/1, foldl/1, foldr/1, fold_empty/1]).
-export([t_delete_object/1, t_init_table/1, t_whitebox/1,
- select_bound_chunk/1,
- t_delete_all_objects/1, t_insert_list/1, t_test_ms/1,
+ select_bound_chunk/1, t_delete_all_objects/1, t_test_ms/1,
t_select_delete/1,t_select_replace/1,t_select_replace_next_bug/1,
t_select_pam_stack_overflow_bug/1,
t_ets_dets/1]).
+-export([t_insert_list/1, t_insert_list_bag/1, t_insert_list_duplicate_bag/1,
+ t_insert_list_set/1, t_insert_list_delete_set/1,
+ t_insert_list_parallel/1, t_insert_list_delete_parallel/1,
+ t_insert_list_kill_process/1]).
-export([test_table_size_concurrency/1,test_table_memory_concurrency/1,
- test_delete_table_while_size_snapshot/1, test_delete_table_while_size_snapshot_helper/0]).
+ test_delete_table_while_size_snapshot/1, test_delete_table_while_size_snapshot_helper/1,
+ test_decentralized_counters_setting/1]).
-export([ordered/1, ordered_match/1, interface_equality/1,
- fixtable_next/1, fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1,
+ fixtable_next/1, fixtable_iter_bag/1,
+ fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1,
update_element/1, update_counter/1, evil_update_counter/1, partly_bound/1, match_heavy/1]).
-export([update_counter_with_default/1]).
-export([update_counter_with_default_bad_pos/1]).
@@ -94,6 +99,7 @@
-export([massive_ets_all/1]).
-export([take/1]).
-export([whereis_table/1]).
+-export([ms_excessive_nesting/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
%% Convenience for manual testing
@@ -130,8 +136,8 @@ all() ->
{group, delete}, firstnext, firstnext_concurrent, slot,
{group, match}, t_match_spec_run,
{group, lookup_element}, {group, misc}, {group, files},
- {group, heavy}, ordered, ordered_match,
- interface_equality, fixtable_next, fixtable_insert,
+ {group, heavy}, {group, insert_list}, ordered, ordered_match,
+ interface_equality, fixtable_next, fixtable_iter_bag, fixtable_insert,
rename, rename_unnamed, evil_rename, update_element,
update_counter, evil_update_counter,
update_counter_with_default,
@@ -141,7 +147,7 @@ all() ->
match_heavy, {group, fold}, member, t_delete_object,
select_bound_chunk,
t_init_table, t_whitebox, t_delete_all_objects,
- t_insert_list, t_test_ms, t_select_delete, t_select_replace,
+ t_test_ms, t_select_delete, t_select_replace,
t_select_replace_next_bug,
t_select_pam_stack_overflow_bug,
t_ets_dets, memory, t_select_reverse, t_bucket_disappears,
@@ -165,11 +171,13 @@ all() ->
take,
whereis_table,
delete_unfix_race,
- test_throughput_benchmark,
- {group, benchmark},
+ %test_throughput_benchmark,
+ %{group, benchmark},
test_table_size_concurrency,
test_table_memory_concurrency,
- test_delete_table_while_size_snapshot].
+ test_delete_table_while_size_snapshot,
+ test_decentralized_counters_setting,
+ ms_excessive_nesting].
groups() ->
@@ -200,7 +208,12 @@ groups() ->
meta_lookup_named_read, meta_lookup_named_write,
meta_newdel_unnamed, meta_newdel_named]},
{benchmark, [],
- [long_throughput_benchmark]}].
+ [long_throughput_benchmark]},
+ {insert_list, [],
+ [t_insert_list, t_insert_list_set, t_insert_list_bag,
+ t_insert_list_duplicate_bag, t_insert_list_delete_set,
+ t_insert_list_parallel, t_insert_list_delete_parallel,
+ t_insert_list_kill_process]}].
init_per_suite(Config) ->
erts_debug:set_internal_state(available_internal_state, true),
@@ -1170,7 +1183,7 @@ t_insert_new(Config) when is_list(Config) ->
L),
verify_etsmem(EtsMem).
-%% Test ets:insert/2 with list of objects.
+%% Test ets:insert/2 with list of objects into duplicate bag table.
t_insert_list(Config) when is_list(Config) ->
EtsMem = etsmem(),
repeat_for_opts(fun t_insert_list_do/1),
@@ -1182,6 +1195,256 @@ t_insert_list_do(Opts) ->
del_one_by_one_dbag_2(T,4000,0),
ets:delete(T).
+% Insert a long list twice in a bag
+t_insert_list_bag(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+ repeat_for_opts(fun t_insert_list_bag_do/1,
+ [write_concurrency, compressed]),
+ verify_etsmem(EtsMem).
+
+t_insert_list_bag_do(Opts) ->
+ T = ets:new(t, [bag | Opts]),
+ ListSize = 25000,
+ List = [ {N} || N <- lists:seq(1, ListSize)],
+ ets:insert(T, List),
+ ets:insert(T, List),
+ ListSize = ets:info(T, size),
+
+ %% Insert different sized objects to better test (compressed) object comparison
+ List2 = [begin Bits=(N rem 71), {N div 7, <<N:Bits>>} end || {N} <- List],
+ ets:insert(T, List2),
+ List2Sz = ListSize * 2,
+ List2Sz = ets:info(T, size),
+ ets:delete(T),
+ ok.
+
+% Insert a long list twice in a duplicate_bag
+t_insert_list_duplicate_bag(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+ T = ets:new(t, [duplicate_bag]),
+ ListSize = 25000,
+ List = [ {N} || N <- lists:seq(1, ListSize)],
+ ets:insert(T, List),
+ ets:insert(T, List),
+ DoubleListSize = ListSize * 2,
+ DoubleListSize = ets:info(T, size),
+ ets:delete(T),
+ verify_etsmem(EtsMem).
+
+%% Test ets:insert/2 with list of objects into set tables.
+t_insert_list_set(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+ repeat_for_opts(fun t_insert_list_set_do/1, [set_types]),
+ verify_etsmem(EtsMem).
+
+t_insert_list_set_do(Opts) ->
+ Nr = 2,
+ t_insert_list_set_do(Opts, fun ets_insert_with_check/2, Nr, 1, Nr+1),
+ t_insert_list_set_do(Opts, fun ets_insert_with_check/2, Nr*2, 2, Nr*2),
+ InsertNewWithCheck =
+ fun(T,E) ->
+ Res = ets:insert_new(T,E),
+ Seq = element(1, lists:nth(1, E)),
+ case Seq rem 2 =:= 0 of
+ true -> Res = false;
+ false -> Res = true
+ end
+ end,
+ t_insert_list_set_do(Opts, InsertNewWithCheck, Nr, 1, Nr),
+ t_insert_list_set_do(Opts, fun ets:insert_new/2, Nr*2, 2, Nr*2),
+ ok.
+
+t_insert_list_set_do(Opts, InsertFun, Nr, Step, ExpectedSize) ->
+ T = ets_new(x,Opts),
+ [InsertFun(T,[{X,X}, {X+1,X}]) || X <- lists:seq(1,Nr,Step)],
+ ExpectedSize = ets:info(T,size),
+ ets:delete(T).
+
+%% Test ets:insert/2 with list of objects into set tables in parallel.
+t_insert_list_parallel(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+ repeat_for_opts(fun t_insert_list_parallel_do/1, [[public], set_types]),
+ verify_etsmem(EtsMem).
+
+ets_insert_with_check(Table, ToInsert) ->
+ true = ets:insert(Table, ToInsert),
+ true.
+
+ets_insert_new_with_check(Table, ToInsert) ->
+ ExpectedRes =
+ case put(is_first_insert_for_list, true) of
+ undefined -> true;
+ true -> false
+ end,
+ ExpectedRes = ets:insert_new(Table, ToInsert),
+ ExpectedRes.
+
+t_insert_list_parallel_do(Opts) ->
+ [(fun(I) ->
+ t_insert_list_parallel_do(Opts, I, 2, 100, 500),
+ t_insert_list_parallel_do(Opts, I, 10, 100, 100),
+ t_insert_list_parallel_do(Opts, I, 1000, 100, 10),
+ t_insert_list_parallel_do(Opts, I, 50000, 3, 1)
+ end)(InsertFun) || InsertFun <- [fun ets_insert_with_check/2,
+ fun ets_insert_new_with_check/2]].
+
+t_insert_list_parallel_do(Opts, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess) ->
+ T = ets_new(x,Opts),
+ t_insert_list_parallel_do_helper(self(), T, 0, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess),
+ receive done -> ok end,
+ ExpectedSize = ListLength * NrOfProcesses,
+ ExpectedSize = length(ets:match_object(T, {'$0', '$1'})),
+ ExpectedSize = ets:info(T, size),
+ ets:delete(T),
+ ok.
+
+t_insert_list_delete_parallel(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+ repeat_for_opts(fun t_insert_list_delete_parallel_do/1, [[public], set_types]),
+ verify_etsmem(EtsMem).
+
+t_insert_list_delete_parallel_do(Opts) ->
+ [(fun(I) ->
+ t_insert_list_delete_parallel_do(Opts, I, 30, 32, 1000000),
+ t_insert_list_delete_parallel_do(Opts, I, 300, 8, 1000000),
+ t_insert_list_delete_parallel_do(Opts, I, 3000, 4, 1000000),
+ t_insert_list_delete_parallel_do(Opts, I, 9000, 4, 1000000)
+ end)(InsertFun) || InsertFun <- [fun ets_insert_with_check/2,
+ fun ets_insert_new_with_check/2]],
+ ok.
+
+t_insert_list_delete_parallel_do(Opts, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess) ->
+ T = ets_new(x,Opts),
+ CompletedInsertsCtr = counters:new(1,[]),
+ NewInsertFun =
+ fun(Table, ToInsert) ->
+ try
+ InsertFun(Table, ToInsert),
+ counters:add(CompletedInsertsCtr, 1, 1)
+ catch
+ error:badarg -> put(stop,yes)
+ end
+ end,
+ Self = self(),
+ spawn(fun()->
+ t_insert_list_parallel_do_helper(self(), T, 0, NewInsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess),
+ receive done -> Self ! done_parallel_insert end
+ end),
+ receive after 3 -> ok end,
+ spawn(fun()->
+ spawn(fun()->
+ receive after 7 -> ok end,
+ ets:delete(T),
+ Self ! done_delete
+ end)
+ end),
+ receive done_delete -> ok end,
+ receive done_parallel_insert -> ok end,
+ io:format("~p/~p completed",
+ [counters:get(CompletedInsertsCtr, 1),
+ NrOfProcesses * NrOfInsertsPerProcess]).
+
+
+t_insert_list_parallel_do_helper(Parent, T, StartKey, InsertFun, ListLength, 1, NrOfInsertsPerProcess) ->
+ try
+ repeat(fun()->
+ case get(stop) of
+ yes -> throw(end_repeat);
+ _ -> ok
+ end,
+ InsertFun(T,[{X,X} || X <- lists:seq(StartKey,StartKey+ListLength-1,1)])
+ end, NrOfInsertsPerProcess)
+ catch
+ throw:end_repeat -> ok
+ end,
+ Parent ! done;
+t_insert_list_parallel_do_helper(Parent, T, StartKey, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess) ->
+ Self = self(),
+ spawn(fun() ->
+ t_insert_list_parallel_do_helper(Self,
+ T,
+ StartKey,
+ InsertFun,
+ ListLength,
+ NrOfProcesses div 2,
+ NrOfInsertsPerProcess) end),
+ spawn(fun() ->
+ t_insert_list_parallel_do_helper(Self,
+ T,
+ StartKey + ListLength*(NrOfProcesses div 2),
+ InsertFun,
+ ListLength,
+ (NrOfProcesses div 2) + (NrOfProcesses rem 2),
+ NrOfInsertsPerProcess)
+ end),
+ receive done -> ok end,
+ receive done -> ok end,
+ Parent ! done.
+
+t_insert_list_delete_set(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+ repeat_for_opts(fun t_insert_list_delete_set_do/1, [[public],set_types]),
+ verify_etsmem(EtsMem).
+
+t_insert_list_delete_set_do(Opts) ->
+ [(fun(I) ->
+ t_insert_list_delete_set_do(Opts, I, 1000000, 1, 1),
+ t_insert_list_delete_set_do(Opts, I, 100000, 10, 5),
+ t_insert_list_delete_set_do(Opts, I, 10000, 100, 50),
+ t_insert_list_delete_set_do(Opts, I, 1000, 1000, 500)
+ end)(InsertFun) || InsertFun <- [fun ets_insert_with_check/2,
+ fun ets_insert_new_with_check/2]],
+ ok.
+
+
+t_insert_list_delete_set_do(Opts, InsertFun, ListLength, NrOfTables, NrOfInserts) ->
+ CompletedInsertsCtr = counters:new(1,[]),
+ Parent = self(),
+ [(fun() ->
+ T = ets_new(x,Opts),
+ spawn(
+ fun() ->
+ try
+ repeat(
+ fun() ->
+ InsertFun(T,[{Z,Z} ||
+ Z <- lists:seq(1,ListLength)]),
+ counters:add(CompletedInsertsCtr, 1, 1)%,
+ end, NrOfInserts)
+ catch
+ error:badarg -> ok
+ end,
+ Parent ! done
+ end),
+ receive after 1 -> ok end,
+ ets:delete(T)
+ end)() || _ <- lists:seq(1,NrOfTables)],
+ [receive done -> ok end || _ <- lists:seq(1,NrOfTables)],
+ io:format("~p/~p completed",
+ [counters:get(CompletedInsertsCtr, 1),
+ NrOfTables * NrOfInserts]).
+
+
+t_insert_list_kill_process(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+ repeat_for_opts(fun t_insert_list_kill_process_do/1, [[public], set_types]),
+ verify_etsmem(EtsMem).
+
+
+t_insert_list_kill_process_do(Opts) ->
+ [(fun(I) ->
+ [(fun(Time) ->
+ T = ets_new(x,Opts),
+ List = lists:seq(1,600000),
+ TupleList = [{E,E} || E <- List],
+ Pid = spawn(fun() -> I(T, TupleList) end),
+ receive after Time -> ok end,
+ exit(Pid, kill),
+ ets:delete(T)
+ end)(TheTime) || TheTime <- [1,3,5] ++ lists:seq(7,29,7)]
+ end)(InsertFun) || InsertFun <- [fun ets:insert/2,
+ fun ets:insert_new/2]],
+ ok.
%% Test interface of ets:test_ms/2.
t_test_ms(Config) when is_list(Config) ->
@@ -1393,7 +1656,11 @@ t_select_delete(Config) when is_list(Config) ->
%% Tests the ets:select_replace/2 BIF
t_select_replace(Config) when is_list(Config) ->
EtsMem = etsmem(),
- Tables = fill_sets_int(10000) ++ fill_sets_int(10000, [{write_concurrency,true}]),
+ repeat_for_opts(fun do_select_replace/1),
+ verify_etsmem(EtsMem).
+
+do_select_replace(Opts) ->
+ Tables = fill_sets_intup(10000, Opts),
TestFun = fun (Table, TableType) when TableType =:= bag ->
% Operation not supported; bag implementation
@@ -1402,80 +1669,80 @@ t_select_replace(Config) when is_list(Config) ->
(Table, TableType) ->
% Invalid replacement doesn't keep the key
- MatchSpec1 = [{{'$1', '$2'},
+ MatchSpec1 = [{{{'$1','$3'}, '$2'},
[{'=:=', {'band', '$1', 2#11}, 2#11},
{'=/=', {'hd', '$2'}, $x}],
- [{{'$2', '$1'}}]}],
+ [{{{{'$2','$3'}}, '$1'}}]}],
{'EXIT',{badarg,_}} = (catch ets:select_replace(Table, MatchSpec1)),
% Invalid replacement doesn't keep the key (even though it would be the same value)
- MatchSpec2 = [{{'$1', '$2'},
+ MatchSpec2 = [{{{'$1','$3'}, '$2'},
[{'=:=', {'band', '$1', 2#11}, 2#11}],
- [{{{'+', '$1', 0}, '$2'}}]},
- {{'$1', '$2'},
+ [{{{{{'+', '$1', 0},'$3'}}, '$2'}}]},
+ {{{'$1','$3'}, '$2'},
[{'=/=', {'band', '$1', 2#11}, 2#11}],
- [{{{'-', '$1', 0}, '$2'}}]}],
+ [{{{{{'-', '$1', 0},'$3'}}, '$2'}}]}],
{'EXIT',{badarg,_}} = (catch ets:select_replace(Table, MatchSpec2)),
% Invalid replacement changes key to float equivalent
- MatchSpec3 = [{{'$1', '$2'},
+ MatchSpec3 = [{{{'$1','$3'}, '$2'},
[{'=:=', {'band', '$1', 2#11}, 2#11},
{'=/=', {'hd', '$2'}, $x}],
- [{{{'*', '$1', 1.0}, '$2'}}]}],
+ [{{{{{'*', '$1', 1.0},'$3'}}, '$2'}}]}],
{'EXIT',{badarg,_}} = (catch ets:select_replace(Table, MatchSpec3)),
% Replacements are differently-sized tuples
- MatchSpec4_A = [{{'$1','$2'},
+ MatchSpec4_A = [{{{'$1','$3'},'$2'},
[{'<', {'rem', '$1', 5}, 2}],
- [{{'$1', [$x | '$2'], stuff}}]}],
- MatchSpec4_B = [{{'$1','$2','_'},
+ [{{{{'$1','$3'}}, [$x | '$2'], stuff}}]}],
+ MatchSpec4_B = [{{{'$1','$3'},'$2','_'},
[],
- [{{'$1','$2'}}]}],
+ [{{{{'$1','$3'}},'$2'}}]}],
4000 = ets:select_replace(Table, MatchSpec4_A),
4000 = ets:select_replace(Table, MatchSpec4_B),
% Replacement is the same tuple
- MatchSpec5 = [{{'$1', '$2'},
+ MatchSpec5 = [{{{'$1','$3'}, '$2'},
[{'>', {'rem', '$1', 5}, 3}],
['$_']}],
2000 = ets:select_replace(Table, MatchSpec5),
% Replacement reconstructs an equal tuple
- MatchSpec6 = [{{'$1', '$2'},
+ MatchSpec6 = [{{{'$1','$3'}, '$2'},
[{'>', {'rem', '$1', 5}, 3}],
- [{{'$1', '$2'}}]}],
+ [{{{{'$1','$3'}}, '$2'}}]}],
2000 = ets:select_replace(Table, MatchSpec6),
% Replacement uses {element,KeyPos,T} for key
2000 = ets:select_replace(Table,
- [{{'$1', '$2'},
+ [{{{'$1','$3'}, '$2'},
[{'>', {'rem', '$1', 5}, 3}],
[{{{element, 1, '$_'}, '$2'}}]}]),
% Replacement uses wrong {element,KeyPos,T} for key
{'EXIT',{badarg,_}} = (catch ets:select_replace(Table,
- [{{'$1', '$2'},
+ [{{{'$1','$3'}, '$2'},
[],
[{{{element, 2, '$_'}, '$2'}}]}])),
check(Table,
- fun ({N, [$x, C | _]}) when ((N rem 5) < 2) -> (C >= $0) andalso (C =< $9);
- ({N, [C | _]}) when is_float(N) -> (C >= $0) andalso (C =< $9);
- ({N, [C | _]}) when ((N rem 5) > 3) -> (C >= $0) andalso (C =< $9);
+ fun ({{N,_}, [$x, C | _]}) when ((N rem 5) < 2) -> (C >= $0) andalso (C =< $9);
+ ({{N,_}, [C | _]}) when is_float(N) -> (C >= $0) andalso (C =< $9);
+ ({{N,_}, [C | _]}) when ((N rem 5) > 3) -> (C >= $0) andalso (C =< $9);
({_, [C | _]}) -> (C >= $0) andalso (C =< $9)
end,
10000),
% Replace unbound range (>)
- MatchSpec7 = [{{'$1', '$2'},
+ MatchSpec7 = [{{{'$1','$3'}, '$2'},
[{'>', '$1', 7000}],
- [{{'$1', {{gt_range, '$2'}}}}]}],
+ [{{{{'$1','$3'}}, {{gt_range, '$2'}}}}]}],
3000 = ets:select_replace(Table, MatchSpec7),
% Replace unbound range (<)
- MatchSpec8 = [{{'$1', '$2'},
+ MatchSpec8 = [{{{'$1','$3'}, '$2'},
[{'<', '$1', 3000}],
- [{{'$1', {{le_range, '$2'}}}}]}],
+ [{{{{'$1','$3'}}, {{le_range, '$2'}}}}]}],
case TableType of
ordered_set -> 2999 = ets:select_replace(Table, MatchSpec8);
set -> 2999 = ets:select_replace(Table, MatchSpec8);
@@ -1483,10 +1750,10 @@ t_select_replace(Config) when is_list(Config) ->
end,
% Replace bound range
- MatchSpec9 = [{{'$1', '$2'},
+ MatchSpec9 = [{{{'$1','$3'}, '$2'},
[{'>=', '$1', 3001},
{'<', '$1', 7000}],
- [{{'$1', {{range, '$2'}}}}]}],
+ [{{{{'$1','$3'}}, {{range, '$2'}}}}]}],
case TableType of
ordered_set -> 3999 = ets:select_replace(Table, MatchSpec9);
set -> 3999 = ets:select_replace(Table, MatchSpec9);
@@ -1494,12 +1761,12 @@ t_select_replace(Config) when is_list(Config) ->
end,
% Replace particular keys
- MatchSpec10 = [{{'$1', '$2'},
+ MatchSpec10 = [{{{'$1','$3'}, '$2'},
[{'==', '$1', 3000}],
- [{{'$1', {{specific1, '$2'}}}}]},
- {{'$1', '$2'},
+ [{{{{'$1','$3'}}, {{specific1, '$2'}}}}]},
+ {{{'$1','$3'}, '$2'},
[{'==', '$1', 7000}],
- [{{'$1', {{specific2, '$2'}}}}]}],
+ [{{{{'$1','$3'}}, {{specific2, '$2'}}}}]}],
case TableType of
ordered_set -> 2 = ets:select_replace(Table, MatchSpec10);
set -> 2 = ets:select_replace(Table, MatchSpec10);
@@ -1507,11 +1774,11 @@ t_select_replace(Config) when is_list(Config) ->
end,
check(Table,
- fun ({N, {gt_range, _}}) -> N > 7000;
- ({N, {le_range, _}}) -> N < 3000;
- ({N, {range, _}}) -> (N >= 3001) andalso (N < 7000);
- ({N, {specific1, _}}) -> N == 3000;
- ({N, {specific2, _}}) -> N == 7000
+ fun ({{N,_}, {gt_range, _}}) -> N > 7000;
+ ({{N,_}, {le_range, _}}) -> N < 3000;
+ ({{N,_}, {range, _}}) -> (N >= 3001) andalso (N < 7000);
+ ({{N,_}, {specific1, _}}) -> N == 3000;
+ ({{N,_}, {specific2, _}}) -> N == 7000
end,
10000),
@@ -1551,7 +1818,7 @@ t_select_replace(Config) when is_list(Config) ->
]
end,
- T2 = ets:new(x, []),
+ T2 = ets:new(x, Opts),
[lists:foreach(fun({A, B}) ->
%% just check that matchspec is accepted
0 = ets:select_replace(T2, [{{A, '$2', '$3'}, [], [{{B, '$3', '$2'}}]}])
@@ -1612,8 +1879,7 @@ t_select_replace(Config) when is_list(Config) ->
ets:delete(T2),
-
- verify_etsmem(EtsMem).
+ ok.
%% OTP-15346: Bug caused select_replace of bound key to corrupt static stack
%% used by ets:next and ets:prev.
@@ -2506,6 +2772,135 @@ do_fixtable_next(Tab) ->
false = ets:info(Tab, fixed),
ets:delete(Tab).
+%% Check that iteration of bags find all live objects and nothing else.
+fixtable_iter_bag(Config) when is_list(Config) ->
+ repeat_for_opts(fun fixtable_iter_do/1,
+ [write_concurrency,[bag,duplicate_bag]]).
+
+fixtable_iter_do(Opts) ->
+ EtsMem = etsmem(),
+ do_fixtable_iter_bag(ets_new(fixtable_iter_bag,Opts)),
+ verify_etsmem(EtsMem).
+
+do_fixtable_iter_bag(T) ->
+ MaxValues = 4,
+ %% Create 1 to MaxValues objects for each key
+ %% and then delete every possible combination of those objects
+ %% in every possible order.
+ %% Then test iteration returns all live objects and nothing else.
+
+ CrDelOps = [begin
+ Values = lists:seq(1,N),
+ %% All ways of deleting any number of the Values in any order
+ Combos = combs(Values),
+ DeleteOps = concat_lists([perms(C) || C <- Combos]),
+ {N, DeleteOps}
+ end
+ || N <- lists:seq(1,MaxValues)],
+
+ %%io:format("~p\n", [CrDelOps]),
+
+ NKeys = lists:foldl(fun({_, DeleteOps}, Cnt) ->
+ Cnt + length(DeleteOps)
+ end,
+ 0,
+ CrDelOps),
+
+ io:format("Create ~p keys\n", [NKeys]),
+
+ %% Fixate even before inserts just to maintain small table size
+ %% and increase likelyhood of different keys in same bucket.
+ ets:safe_fixtable(T,true),
+ InsRes = [begin
+ [begin
+ Key = {NValues,ValueList},
+ [begin
+ Tpl = {Key, V},
+ %%io:format("Insert object ~p", [Tpl]),
+ ets:insert(T, Tpl),
+ Tpl
+ end
+ || V <- lists:seq(1,NValues)]
+ end
+ || ValueList <- DeleteOps]
+ end
+ || {NValues, DeleteOps} <- CrDelOps],
+
+ Inserted = lists:flatten(InsRes),
+ InSorted = lists:sort(Inserted),
+ InSorted = lists:usort(Inserted), %% No duplicates
+ NObjs = length(Inserted),
+
+ DelRes = [begin
+ [begin
+ Key = {NValues,ValueList},
+ [begin
+ Tpl = {Key, V},
+ %%io:format("Delete object ~p", [Tpl]),
+ ets:delete_object(T, Tpl),
+ Tpl
+ end
+ || V <- ValueList]
+ end
+ || ValueList <- DeleteOps]
+ end
+ || {NValues, DeleteOps} <- CrDelOps],
+
+ Deleted = lists:flatten(DelRes),
+ DelSorted = lists:sort(Deleted),
+ DelSorted = lists:usort(Deleted), %% No duplicates
+ NDels = length(Deleted),
+
+ %% Nr of keys where all values were deleted.
+ NDeletedKeys = lists:sum([factorial(N) || N <- lists:seq(1,MaxValues)]),
+
+ CountKeysFun = fun Me(K1, Cnt) ->
+ case ets:next(T, K1) of
+ '$end_of_table' ->
+ Cnt;
+ K2 ->
+ Objs = ets:lookup(T, K2),
+ [{{NValues, ValueList}, _V} | _] = Objs,
+ ExpectedLive = NValues - length(ValueList),
+ ExpectedLive = length(Objs),
+ Me(K2, Cnt+1)
+ end
+ end,
+
+ ExpectedKeys = NKeys - NDeletedKeys,
+ io:format("Expected keys: ~p\n", [ExpectedKeys]),
+ FoundKeys = CountKeysFun(ets:first(T), 1),
+ io:format("Found keys: ~p\n", [FoundKeys]),
+ ExpectedKeys = FoundKeys,
+
+ ExpectedObjs = NObjs - NDels,
+ io:format("Expected objects: ~p\n", [ExpectedObjs]),
+ FoundObjs = ets:select_count(T, [{{'_','_'}, [], [true]}]),
+ io:format("Found objects: ~p\n", [FoundObjs]),
+ ExpectedObjs = FoundObjs,
+
+ ets:delete(T).
+
+%% All permutations of list
+perms([]) -> [[]];
+perms(L) -> [[H|T] || H <- L, T <- perms(L--[H])].
+
+%% All combinations of picking the element (or not) from list
+combs([]) -> [[]];
+combs([H|T]) ->
+ Tcombs = combs(T),
+ Tcombs ++ [[H | C] || C <- Tcombs].
+
+factorial(0) -> 1;
+factorial(N) when N > 0 ->
+ N * factorial(N - 1).
+
+concat_lists([]) ->
+ [];
+concat_lists([H|T]) ->
+ H ++ concat_lists(T).
+
+
%% Check inserts of deleted keys in fixed bags.
fixtable_insert(Config) when is_list(Config) ->
Combos = [[Type,{write_concurrency,WC}] || Type<- [bag,duplicate_bag],
@@ -2570,15 +2965,17 @@ write_concurrency(Config) when is_list(Config) ->
Yes6 = ets_new(foo,[duplicate_bag,protected,{write_concurrency,true}]),
No3 = ets_new(foo,[duplicate_bag,private,{write_concurrency,true}]),
- Yes7 = ets_new(foo,[ordered_set,public,{write_concurrency,true}]),
- Yes8 = ets_new(foo,[ordered_set,protected,{write_concurrency,true}]),
- Yes9 = ets_new(foo,[ordered_set,{write_concurrency,true}]),
- Yes10 = ets_new(foo,[{write_concurrency,true},ordered_set,public]),
- Yes11 = ets_new(foo,[{write_concurrency,true},ordered_set,protected]),
+ NoCentCtrs = {decentralized_counters,false},
+ Yes7 = ets_new(foo,[ordered_set,public,{write_concurrency,true},NoCentCtrs]),
+ Yes8 = ets_new(foo,[ordered_set,protected,{write_concurrency,true},NoCentCtrs]),
+ Yes9 = ets_new(foo,[ordered_set,{write_concurrency,true},NoCentCtrs]),
+ Yes10 = ets_new(foo,[{write_concurrency,true},ordered_set,public,NoCentCtrs]),
+ Yes11 = ets_new(foo,[{write_concurrency,true},ordered_set,protected,NoCentCtrs]),
Yes12 = ets_new(foo,[set,{write_concurrency,false},
- {write_concurrency,true},ordered_set,public]),
+ {write_concurrency,true},ordered_set,public,NoCentCtrs]),
Yes13 = ets_new(foo,[private,public,set,{write_concurrency,false},
- {write_concurrency,true},ordered_set]),
+ {write_concurrency,true},ordered_set,NoCentCtrs]),
+ Yes14 = ets_new(foo,[ordered_set,public,{write_concurrency,true}]),
No4 = ets_new(foo,[ordered_set,private,{write_concurrency,true}]),
No5 = ets_new(foo,[ordered_set,public,{write_concurrency,false}]),
No6 = ets_new(foo,[ordered_set,protected,{write_concurrency,false}]),
@@ -2590,6 +2987,7 @@ write_concurrency(Config) when is_list(Config) ->
YesMem = ets:info(Yes1,memory),
NoHashMem = ets:info(No1,memory),
YesTreeMem = ets:info(Yes7,memory),
+ YesYesTreeMem = ets:info(Yes14,memory),
NoTreeMem = ets:info(No4,memory),
io:format("YesMem=~p NoHashMem=~p NoTreeMem=~p YesTreeMem=~p\n",[YesMem,NoHashMem,
NoTreeMem,YesTreeMem]),
@@ -2615,10 +3013,17 @@ write_concurrency(Config) when is_list(Config) ->
NoHashMem = ets:info(No8,memory),
NoHashMem = ets:info(No9,memory),
- true = YesMem > NoHashMem orelse erlang:system_info(schedulers) == 1,
- true = YesMem > NoTreeMem orelse erlang:system_info(schedulers) == 1,
true = YesMem > YesTreeMem,
- true = YesTreeMem < NoTreeMem orelse erlang:system_info(schedulers) == 1,
+
+ case erlang:system_info(schedulers) > 1 of
+ true ->
+ true = YesMem > NoHashMem,
+ true = YesMem > NoTreeMem,
+ true = YesTreeMem < NoTreeMem,
+ true = YesYesTreeMem > YesTreeMem;
+ _ ->
+ one_scheduler_only
+ end,
{'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency,foo}])),
{'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency}])),
@@ -2626,7 +3031,7 @@ write_concurrency(Config) when is_list(Config) ->
{'EXIT',{badarg,_}} = (catch ets_new(foo,[public,write_concurrency])),
lists:foreach(fun(T) -> ets:delete(T) end,
- [Yes1,Yes2,Yes3,Yes4,Yes5,Yes6,Yes7,Yes8,Yes9,Yes10,Yes11,Yes12,Yes13,
+ [Yes1,Yes2,Yes3,Yes4,Yes5,Yes6,Yes7,Yes8,Yes9,Yes10,Yes11,Yes12,Yes13,Yes14,
No1,No2,No3,No4,No5,No6,No7,No8,No9]),
verify_etsmem(EtsMem),
ok.
@@ -4509,6 +4914,8 @@ info_do(Opts) ->
{value, {protection, Protection}} =
lists:keysearch(protection, 1, Res),
{value, {id, Tab}} = lists:keysearch(id, 1, Res),
+ {value, {decentralized_counters, _DecentralizedCtrs}} =
+ lists:keysearch(decentralized_counters, 1, Res),
%% Test 'binary'
[] = ?ets_info(Tab, binary, SlavePid),
@@ -4622,7 +5029,10 @@ size_loop(_T, 0, _, _) ->
size_loop(T, I, PrevSize, WhatToTest) ->
Size = ets:info(T, WhatToTest),
case Size < PrevSize of
- true -> ct:fail("Bad ets:info/2");
+ true ->
+ io:format("Bad ets:info/2 (got ~p expected >=~p)",
+ [Size, PrevSize]),
+ ct:fail("Bad ets:info/2)");
_ -> ok
end,
size_loop(T, I -1, Size, WhatToTest).
@@ -4634,13 +5044,17 @@ add_loop(T, I) ->
add_loop(T, I -1).
-test_table_counter_concurrency(WhatToTest) ->
+test_table_counter_concurrency(WhatToTest, TableOptions) ->
IntStatePrevOn =
erts_debug:set_internal_state(available_internal_state, true),
ItemsToAdd = 1000000,
SizeLoopSize = 1000,
- T = ets:new(k, [public, ordered_set, {write_concurrency, true}]),
- erts_debug:set_internal_state(ets_debug_random_split_join, {T, false}),
+ T = ets:new(k, TableOptions),
+ case lists:member(ordered_set, TableOptions) of
+ true ->
+ erts_debug:set_internal_state(ets_debug_random_split_join, {T, false});
+ false -> ok
+ end,
0 = ets:info(T, size),
P = self(),
SpawnedSizeProcs =
@@ -4671,14 +5085,18 @@ test_table_size_concurrency(Config) when is_list(Config) ->
case erlang:system_info(schedulers) of
1 -> {skip,"Only valid on smp > 1 systems"};
_ ->
- test_table_counter_concurrency(size)
+ BaseOptions = [public, {write_concurrency, true}],
+ test_table_counter_concurrency(size, [set | BaseOptions]),
+ test_table_counter_concurrency(size, [ordered_set | BaseOptions])
end.
test_table_memory_concurrency(Config) when is_list(Config) ->
case erlang:system_info(schedulers) of
1 -> {skip,"Only valid on smp > 1 systems"};
_ ->
- test_table_counter_concurrency(memory)
+ BaseOptions = [public, {write_concurrency, true}],
+ test_table_counter_concurrency(memory, [set | BaseOptions]),
+ test_table_counter_concurrency(memory, [ordered_set | BaseOptions])
end.
%% Tests that calling the ets:delete operation on a table T with
@@ -4689,15 +5107,20 @@ test_delete_table_while_size_snapshot(Config) when is_list(Config) ->
%% depend on that pids are ordered in creation order which is no
%% longer the case when many processes have been started before
Node = start_slave(),
- ok = rpc:call(Node, ?MODULE, test_delete_table_while_size_snapshot_helper, []),
+ [ok = rpc:call(Node,
+ ?MODULE,
+ test_delete_table_while_size_snapshot_helper,
+ [TableType])
+ || TableType <- [set, ordered_set]],
test_server:stop_node(Node),
ok.
-test_delete_table_while_size_snapshot_helper()->
+test_delete_table_while_size_snapshot_helper(TableType) ->
TopParent = self(),
repeat_par(
fun() ->
- Table = ets:new(t, [public, ordered_set,
+ Table = ets:new(t, [public, TableType,
+ {decentralized_counters, true},
{write_concurrency, true}]),
Parent = self(),
NrOfSizeProcs = 100,
@@ -4705,12 +5128,12 @@ test_delete_table_while_size_snapshot_helper()->
|| _ <- lists:seq(1, NrOfSizeProcs)],
timer:sleep(1),
ets:delete(Table),
- [receive
+ [receive
table_gone -> ok;
Problem -> TopParent ! Problem
end || _ <- Pids]
end,
- 15000),
+ 100*erlang:system_info(schedulers_online)),
receive
Problem -> throw(Problem)
after 0 -> ok
@@ -4750,6 +5173,64 @@ repeat_par_help(FunToRepeat, NrOfTimes, OrgNrOfTimes) ->
end),
repeat_par_help(FunToRepeat, NrOfTimes-1, OrgNrOfTimes).
+test_decentralized_counters_setting(Config) when is_list(Config) ->
+ case erlang:system_info(schedulers) of
+ 1 -> {skip,"Only relevant when the number of shedulers > 1"};
+ _ -> EtsMem = etsmem(),
+ do_test_decentralized_counters_setting(set),
+ do_test_decentralized_counters_setting(ordered_set),
+ do_test_decentralized_counters_default_setting(),
+ verify_etsmem(EtsMem)
+ end.
+
+do_test_decentralized_counters_setting(TableType) ->
+ wait_for_memory_deallocations(),
+ FlxCtrMemUsage = erts_debug:get_internal_state(flxctr_memory_usage),
+ lists:foreach(
+ fun(OptList) ->
+ T1 = ets:new(t1, [public, TableType] ++ OptList ++ [TableType]),
+ check_decentralized_counters(T1, false, FlxCtrMemUsage),
+ ets:delete(T1)
+ end,
+ [[{write_concurrency, false}],
+ [{write_concurrency, true}, {decentralized_counters, false}]]),
+ lists:foreach(
+ fun(OptList) ->
+ T1 = ets:new(t1, [public,
+ TableType,
+ {write_concurrency, true}] ++ OptList ++ [TableType]),
+ check_decentralized_counters(T1, true, FlxCtrMemUsage),
+ ets:delete(T1),
+ wait_for_memory_deallocations(),
+ FlxCtrMemUsage = erts_debug:get_internal_state(flxctr_memory_usage)
+ end,
+ [[{decentralized_counters, true}]]),
+ ok.
+
+do_test_decentralized_counters_default_setting() ->
+ wait_for_memory_deallocations(),
+ FlxCtrMemUsage = erts_debug:get_internal_state(flxctr_memory_usage),
+ Set = ets:new(t1, [public, {write_concurrency, true}]),
+ check_decentralized_counters(Set, false, FlxCtrMemUsage),
+ ets:delete(Set),
+ Set2 = ets:new(t1, [public, set, {write_concurrency, true}]),
+ check_decentralized_counters(Set2, false, FlxCtrMemUsage),
+ ets:delete(Set2),
+ OrdSet = ets:new(t1, [public, ordered_set, {write_concurrency, true}]),
+ check_decentralized_counters(OrdSet, true, FlxCtrMemUsage),
+ ets:delete(OrdSet),
+ ok.
+
+check_decentralized_counters(T, ExpectedState, InitMemUsage) ->
+ case {ExpectedState, erts_debug:get_internal_state(flxctr_memory_usage)} of
+ {false, notsup} -> ok;
+ {false, X} -> InitMemUsage = X;
+ {true, notsup} -> ok;
+ {true, X} when X > InitMemUsage -> ok;
+ {true, _} -> ct:fail("Decentralized counter not used.")
+ end,
+ ExpectedState = ets:info(T, decentralized_counters).
+
%% Test various duplicate_bags stuff.
dups(Config) when is_list(Config) ->
repeat_for_opts(fun dups_do/1).
@@ -5035,7 +5516,7 @@ tabfile_ext4(Config) when is_list(Config) ->
{error,Y} = ets:file2tab(FName,[{verify,true}]),
ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
{X,Y}
- end || N <- lists:seq(500,600)],
+ end || N <- lists:seq(700,800)],
io:format("~p~n",[Res]),
file:delete(FName)
end),
@@ -5436,6 +5917,7 @@ make_table(Name, Options, Elements) ->
T = ets_new(Name, Options),
lists:foreach(fun(E) -> ets:insert(T, E) end, Elements),
T.
+
filltabint(Tab,0) ->
Tab;
filltabint(Tab,N) ->
@@ -5463,6 +5945,22 @@ xfilltabint(Tab,N) ->
filltabint(Tab,N)
end.
+filltabintup(Tab,0) ->
+ Tab;
+filltabintup(Tab,N) ->
+ ets:insert(Tab,{{N,integer_to_list(N)},integer_to_list(N)}),
+ filltabintup(Tab,N-1).
+
+filltabintup2(Tab,0) ->
+ Tab;
+filltabintup2(Tab,N) ->
+ ets:insert(Tab,{{N + N rem 2,integer_to_list(N)},integer_to_list(N)}),
+ filltabintup2(Tab,N-1).
+filltabintup3(Tab,0) ->
+ Tab;
+filltabintup3(Tab,N) ->
+ ets:insert(Tab,{{N + N rem 2,integer_to_list(N + N rem 2)},integer_to_list(N + N rem 2)}),
+ filltabintup3(Tab,N-1).
filltabstr(Tab,N) ->
filltabstr(Tab,0,N).
@@ -5508,6 +6006,19 @@ fill_sets_int(N,Opts) ->
filltabint3(Tab4,N),
[Tab1,Tab2,Tab3,Tab4].
+fill_sets_intup(N) ->
+ fill_sets_int(N,[]).
+fill_sets_intup(N,Opts) ->
+ Tab1 = ets_new(xxx, [ordered_set|Opts]),
+ filltabintup(Tab1,N),
+ Tab2 = ets_new(xxx, [set|Opts]),
+ filltabintup(Tab2,N),
+ Tab3 = ets_new(xxx, [bag|Opts]),
+ filltabintup2(Tab3,N),
+ Tab4 = ets_new(xxx, [duplicate_bag|Opts]),
+ filltabintup3(Tab4,N),
+ [Tab1,Tab2,Tab3,Tab4].
+
check_fun(_Tab,_Fun,'$end_of_table') ->
ok;
check_fun(Tab,Fun,Item) ->
@@ -5816,13 +6327,13 @@ otp_7665_act(Tab,Min,Max,DelNr) ->
true = ets:insert(Tab, List1),
true = ets:safe_fixtable(Tab, true),
true = ets:delete_object(Tab, {key,DelNr}),
- List2 = lists:delete({key,DelNr}, List1),
+ List2 = lists:sort(lists:delete({key,DelNr}, List1)),
%% Now verify that we find all remaining objects
- List2 = ets:lookup(Tab,key),
- EList2 = lists:map(fun({key,N})-> N end,
- List2),
- EList2 = ets:lookup_element(Tab,key,2),
+ List2 = lists:sort(ets:lookup(Tab,key)),
+ EList2 = lists:sort(lists:map(fun({key,N})-> N end,
+ List2)),
+ EList2 = lists:sort(ets:lookup_element(Tab,key,2)),
true = ets:delete(Tab, key),
[] = ets:lookup(Tab, key),
true = ets:safe_fixtable(Tab, false),
@@ -6387,7 +6898,7 @@ verify_table_load(T) ->
false;
true ->
- io:format("Stats = ~p\n",[Stats]),
+ io:format("Stats = ~p\n~p\n",[Stats, ets:info(T)]),
ok
end
end.
@@ -6475,7 +6986,8 @@ smp_select_delete_do(Opts) ->
smp_select_replace(Config) when is_list(Config) ->
repeat_for_opts(fun smp_select_replace_do/1,
- [[set,ordered_set,stim_cat_ord_set,duplicate_bag]]).
+ [[set,ordered_set,stim_cat_ord_set,duplicate_bag],
+ compressed]).
smp_select_replace_do(Opts) ->
KeyRange = 20,
@@ -6847,7 +7359,8 @@ take(Config) when is_list(Config) ->
%% Same with bag.
T3 = ets_new(c, [bag]),
ets:insert(T3, [{1,1},{1,2},{3,3}]),
- [{1,1},{1,2}] = ets:take(T3, 1),
+ R = lists:sort([{1,1},{1,2}]),
+ R = lists:sort(ets:take(T3, 1)),
[{3,3}] = ets:take(T3, 3),
[] = ets:tab2list(T3),
ets:delete(T1),
@@ -6885,6 +7398,50 @@ whereis_table(Config) when is_list(Config) ->
ok.
+ms_excessive_nesting(Config) when is_list(Config) ->
+ MkMSCond = fun (_Fun, N) when N < 0 -> true;
+ (Fun, N) -> {'orelse', {'==', N, '$1'}, Fun(Fun, N-1)}
+ end,
+ %% Ensure it compiles with substantial but reasonable
+ %% (hmm...) nesting
+ MS = [{{'$1', '$2'}, [MkMSCond(MkMSCond, 100)], [{{'$1', blipp}}]}],
+ io:format("~p~n", [erlang:match_spec_test({1, blupp}, MS, table)]),
+ _ = ets:match_spec_compile(MS),
+ %% Now test match_spec_compile() and select_replace()
+ %% with tree and hash using excessive nesting. These
+ %% used to seg-fault the emulator due to recursion
+ %% beyond the end of the C-stack.
+ %%
+ %% We expect to get a system_limit error, but don't
+ %% fail if it compiles (someone must have rewritten
+ %% compilation of match specs to use an explicit
+ %% stack instead of using recursion).
+ ENMS = [{{'$1', '$2'}, [MkMSCond(MkMSCond, 1000000)], [{{'$1', blipp}}]}],
+ io:format("~p~n", [erlang:match_spec_test({1, blupp}, ENMS, table)]),
+ ENMSC = try
+ ets:match_spec_compile(ENMS),
+ "compiled"
+ catch
+ error:system_limit ->
+ "got system_limit"
+ end,
+ Tree = ets:new(tree, [ordered_set]),
+ SRT = try
+ ets:select_replace(Tree, ENMS),
+ "compiled"
+ catch
+ error:system_limit ->
+ "got system_limit"
+ end,
+ Hash = ets:new(hash, [set]),
+ SRH = try
+ ets:select_replace(Hash, ENMS),
+ "compiled"
+ catch
+ error:system_limit ->
+ "got system_limit"
+ end,
+ {comment, "match_spec_compile() "++ENMSC++"; select_replace(_,[ordered_set]) "++SRT++"; select_replace(_,[set]) "++SRH}.
%% The following help functions are used by
%% throughput_benchmark. They are declared on the top level beacuse
@@ -7610,32 +8167,73 @@ my_tab_to_list(Ts,Key, Acc) ->
wait_for_memory_deallocations() ->
try
+ erts_debug:set_internal_state(wait, thread_progress),
erts_debug:set_internal_state(wait, deallocations)
catch
error:undef ->
erts_debug:set_internal_state(available_internal_state, true),
- wait_for_memory_deallocations()
+ wait_for_memory_deallocations();
+ error:badarg ->
+ %% The emulator we run on does not have the wait internal state
+ %% so we just sleep some time instead...
+ timer:sleep(100)
end.
etsmem() ->
- % The following is done twice to avoid an inconsistent memory
- % "snapshot" (see verify_etsmem/2).
- lists:foldl(
- fun(_,_) ->
- wait_for_memory_deallocations(),
-
- AllTabs = lists:map(fun(T) -> {T,ets:info(T,name),ets:info(T,size),
- ets:info(T,memory),ets:info(T,type)}
- end, ets:all()),
-
- EtsAllocSize = erts_debug:alloc_blocks_size(ets_alloc),
- ErlangMemoryEts = try erlang:memory(ets) catch error:notsup -> notsup end,
-
- Mem = {ErlangMemoryEts, EtsAllocSize},
- {Mem, AllTabs}
- end,
- not_used,
- lists:seq(1,2)).
+ etsmem(get_etsmem(), 1).
+
+etsmem(PrevEtsMem, Try) when Try < 10 ->
+ case get_etsmem() of
+ PrevEtsMem ->
+ PrevEtsMem;
+ EtsMem ->
+ io:format("etsmem(): Change in attempt ~p~n~nbefore:~n~p~n~nafter:~n~p~n~n",
+ [Try, PrevEtsMem, EtsMem]),
+ etsmem(EtsMem, Try+1)
+ end;
+etsmem(_, _) ->
+ ct:fail("Failed to get a stable/consistent memory snapshot").
+
+get_etsmem() ->
+ AllTabsExceptions = [logger, code],
+ %% The logger table is excluded from the AllTabs list
+ %% below because it uses decentralized counters to keep
+ %% track of the size and the memory counters. This cause
+ %% ets:info(T,size) and ets:info(T,memory) to trigger
+ %% allocations and frees that may change the amount of
+ %% memory that is allocated for ETS.
+ %%
+ %% The code table is excluded from the list below
+ %% because the amount of memory allocated for it may
+ %% change if the tested code loads a new module.
+ AllTabs =
+ lists:sort(
+ [begin
+ try ets:info(T, decentralized_counters) of
+ true ->
+ ct:fail("Background ETS table (~p) that "
+ "uses decentralized counters (Add exception?)",
+ [ets:info(T,name)]);
+ _ -> ok
+ catch _:_ ->
+ ok
+ end,
+ {T,
+ ets:info(T,name),
+ ets:info(T,size),
+ ets:info(T,memory),
+ ets:info(T,type)}
+ end
+ || T <- ets:all(),
+ not lists:member(ets:info(T, name), AllTabsExceptions)]),
+ wait_for_memory_deallocations(),
+ EtsAllocSize = erts_debug:alloc_blocks_size(ets_alloc),
+ ErlangMemoryEts = try erlang:memory(ets)
+ catch error:notsup -> notsup end,
+ FlxCtrMemUsage = try erts_debug:get_internal_state(flxctr_memory_usage)
+ catch error:badarg -> notsup end,
+ Mem = {ErlangMemoryEts, EtsAllocSize, FlxCtrMemUsage},
+ {Mem, AllTabs}.
verify_etsmem(MI) ->
wait_for_test_procs(),
diff --git a/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html b/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html
index 6ba00b019a..922a3790ea 100644
--- a/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html
+++ b/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html
@@ -44,6 +44,12 @@
<br>
<textarea id="dataField" rows="4" cols="50">#bench_data_placeholder</textarea>
<br>
+ <input type="checkbox" id="throughputPlot" checked> Include Throughput Plot
+ <br>
+ <input type="checkbox" id="betterThanWorstPlot"> Include % More Throughput Than Worst Plot
+ <br>
+ <input type="checkbox" id="worseThanBestPlot"> Include % Less Throughput Than Best Plot
+ <br>
<input type="checkbox" id="barPlot"> Bar Plot
<br>
<input type="checkbox" id="sameSpacing" checked> Same X Spacing Between Points
@@ -148,10 +154,52 @@
}
return data;
}
+ function toCompareData(dataParam, compareWithWorst) {
+ var data = $.extend(true, [], dataParam);
+ var worstSoFarMap = {};
+ var defaultSoFarValue = compareWithWorst ? Number.MAX_VALUE : Number.MIN_VALUE;
+ function getWorstBestSoFar(x){
+ return worstSoFarMap[x] === undefined ? defaultSoFarValue : worstSoFarMap[x];
+ }
+ function setWorstBestSoFar(x, y){
+ return worstSoFarMap[x] = y;
+ }
+ function lessOrGreaterThan(n1, n2){
+ return compareWithWorst ? n1 < n2 : n1 > n2;
+ }
+ $.each(data, function(i, allResConfig) {
+ $.each(allResConfig.y, function(index, res) {
+ var xName = allResConfig.x[index];
+ if(lessOrGreaterThan(res, getWorstBestSoFar(xName))){
+ setWorstBestSoFar(xName, res);
+ }
+ });
+ });
+ $.each(data, function(i, allResConfig) {
+ $.each(allResConfig.y, function(index, res) {
+ var xName = allResConfig.x[index];
+ if(compareWithWorst){
+ allResConfig.y[index] = ((res / getWorstBestSoFar(xName))-1.0) * 100;
+ }else{
+ allResConfig.y[index] = (1.0 -(res / getWorstBestSoFar(xName))) * 100;
+ }
+ });
+ });
+ return data;
+ }
+ function toBetterThanWorstData(data){
+ return toCompareData(data, true);
+ }
+ function toWorseThanBestData(data){
+ return toCompareData(data, false);
+ }
function plotGraphs(){
var insertPlaceholder = $("#insertPlaceholder");
var sameSpacing = $('#sameSpacing').is(":checked");
var barPlot = $('#barPlot').is(":checked");
+ var throughputPlot = $('#throughputPlot').is(":checked");
+ var betterThanWorstPlot = $('#betterThanWorstPlot').is(":checked");
+ var worseThanBestPlot = $('#worseThanBestPlot').is(":checked");
var lines = $("#dataField").val();
$('.showCheck').each(function() {
var item = $(this);
@@ -188,42 +236,59 @@
plotGraph(lines, sameSpacing, barPlot, prefix));
}
}
+ var nrOfGraphs = 0;
+ function plotScenario(name, plotType) {
+ var data = scenarioDataMap[name];
+ var yAxisTitle = undefined;
+ nrOfGraphs = nrOfGraphs + 1;
+ $("<div class='added' id='graph" + nrOfGraphs + "'>")
+ .insertBefore(insertPlaceholder);
+ $("<button type='button' class='added' id='fullscreenButton" + nrOfGraphs + "'>Fill screen</button>")
+ .insertBefore(insertPlaceholder);
+ $("<span class='added'><br><hr><br></span>")
+ .insertBefore(insertPlaceholder);
+ if (plotType === 'throughput') {
+ yAxisTitle = 'Operations/Second';
+ } else if (plotType === 'better_than_worst') {
+ yAxisTitle = '% More Throughput Than Worst';
+ data = toBetterThanWorstData(data);
+ } else {
+ yAxisTitle = '% Less Throughput Than Best';
+ data = toWorseThanBestData(data);
+ }
+ var layout = {
+ title: name,
+ xaxis: {
+ title: '# of Processes'
+ },
+ yaxis: {
+ title: yAxisTitle
+ }
+ };
+ $("#fullscreenButton" + nrOfGraphs).click(
+ function () {
+ $('#graph' + nrOfGraphs).replaceWith(
+ $("<div class='added' id='graph" + nrOfGraphs + "'>"));
+ layout = $.extend({}, layout, {
+ width: $(window).width() - 40,
+ height: $(window).height() - 40
+ });
+ Plotly.newPlot('graph' + nrOfGraphs, data, layout);
+ });
+ Plotly.newPlot('graph' + nrOfGraphs, data, layout);
+ }
$.each(scenarioList,
- function( index, name ) {
- var nrOfGraphs = index + 1;
- var data = scenarioDataMap[name];
- $( "<div class='added' id='graph"+nrOfGraphs+"'>")
- .insertBefore( insertPlaceholder );
- $( "<button type='button' class='added' id='fullscreenButton"+nrOfGraphs+"'>Fill screen</button>")
- .insertBefore( insertPlaceholder );
- $( "<span class='added'><br><hr><br></span>")
- .insertBefore( insertPlaceholder );
- var layout = {
- title:name,
- xaxis: {
- title: '# of Processes'
- },
- yaxis: {
- title: 'Operations/Second'
- }
-
- };
-
- $("#fullscreenButton"+nrOfGraphs).click(
- function(){
- $('#graph'+nrOfGraphs).replaceWith(
- $("<div class='added' id='graph"+nrOfGraphs+"'>"));
- layout = $.extend({}, layout, {
- width:$(window).width()-40,
- height:$(window).height()-40
- });
- Plotly.newPlot('graph'+nrOfGraphs, data, layout);
- });
- Plotly.newPlot('graph'+nrOfGraphs, data, layout);
-
- });
-
-
+ function (index, name) {
+ if (throughputPlot) {
+ plotScenario(name, 'throughput');
+ }
+ if (betterThanWorstPlot) {
+ plotScenario(name, 'better_than_worst');
+ }
+ if (worseThanBestPlot) {
+ plotScenario(name, 'worse_than_best');
+ }
+ });
}
$(document).ready(function(){
$('#renderButton').click(
diff --git a/lib/stdlib/test/filelib_SUITE.erl b/lib/stdlib/test/filelib_SUITE.erl
index ddb3a3d767..203ba2c31e 100644
--- a/lib/stdlib/test/filelib_SUITE.erl
+++ b/lib/stdlib/test/filelib_SUITE.erl
@@ -26,7 +26,8 @@
wildcard_one/1,wildcard_two/1,wildcard_errors/1,
fold_files/1,otp_5960/1,ensure_dir_eexist/1,ensure_dir_symlink/1,
wildcard_symlink/1, is_file_symlink/1, file_props_symlink/1,
- find_source/1, find_source_subdir/1]).
+ find_source/1, find_source_subdir/1, safe_relative_path/1,
+ safe_relative_path_links/1]).
-import(lists, [foreach/2]).
@@ -49,7 +50,8 @@ all() ->
[wildcard_one, wildcard_two, wildcard_errors,
fold_files, otp_5960, ensure_dir_eexist, ensure_dir_symlink,
wildcard_symlink, is_file_symlink, file_props_symlink,
- find_source, find_source_subdir].
+ find_source, find_source_subdir, safe_relative_path,
+ safe_relative_path_links].
groups() ->
[].
@@ -121,6 +123,8 @@ wcc(Wc, Error) ->
{'EXIT',{{badpattern,Error},
[{filelib,wildcard,2,_}|_]}} = (catch filelib:wildcard(Wc, ".")).
+disable_prefix_opt([_,$:|_]=Wc) ->
+ Wc;
disable_prefix_opt([C|Wc]) when $a =< C, C =< $z; C =:= $@ ->
%% There is an optimization for patterns that have a literal prefix
%% (such as "lib/compiler/ebin/*"). Test that we'll get the same result
@@ -647,3 +651,167 @@ find_source_subdir(Config) when is_list(Config) ->
{ok, SrcFile} = filelib:find_file(SrcName, BeamDir),
ok.
+
+safe_relative_path(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Root = filename:join(PrivDir, "filelib_SUITE_safe_relative_path"),
+ ok = file:make_dir(Root),
+ ok = file:set_cwd(Root),
+
+ ok = file:make_dir("a"),
+ ok = file:set_cwd("a"),
+ ok = file:make_dir("b"),
+ ok = file:set_cwd("b"),
+ ok = file:make_dir("c"),
+
+ ok = file:set_cwd(Root),
+
+ "a" = test_srp("a"),
+ "a/b" = test_srp("a/b"),
+ "a/b" = test_srp("a/./b"),
+ "a/b" = test_srp("a/./b/."),
+
+ "" = test_srp("a/.."),
+ "" = test_srp("a/./.."),
+ "" = test_srp("a/../."),
+ "a" = test_srp("a/b/.."),
+ "a" = test_srp("a/../a"),
+ "a" = test_srp("a/../a/../a"),
+ "a/b/c" = test_srp("a/../a/b/c"),
+
+ unsafe = test_srp("a/../.."),
+ unsafe = test_srp("a/../../.."),
+ unsafe = test_srp("a/./../.."),
+ unsafe = test_srp("a/././../../.."),
+ unsafe = test_srp("a/b/././../../.."),
+
+ unsafe = test_srp(PrivDir), %Absolute path.
+
+ ok.
+
+test_srp(RelPath) ->
+ Res = do_test_srp(RelPath),
+ Res = case do_test_srp(list_to_binary(RelPath)) of
+ Bin when is_binary(Bin) ->
+ binary_to_list(Bin);
+ Other ->
+ Other
+ end.
+
+do_test_srp(RelPath) ->
+ {ok,Root} = file:get_cwd(),
+ ok = file:set_cwd(RelPath),
+ {ok,Cwd} = file:get_cwd(),
+ ok = file:set_cwd(Root),
+ case filelib:safe_relative_path(RelPath, Cwd) of
+ unsafe ->
+ true = length(Cwd) < length(Root),
+ unsafe;
+ "" ->
+ "";
+ SafeRelPath ->
+ ok = file:set_cwd(SafeRelPath),
+ {ok,Cwd} = file:get_cwd(),
+ true = length(Cwd) >= length(Root),
+ ok = file:set_cwd(Root),
+ SafeRelPath
+ end.
+
+safe_relative_path_links(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ BaseDir = filename:join(PrivDir, "filelib_SUITE_safe_relative_path_links"),
+ ok = file:make_dir(BaseDir),
+ try
+ case check_symlink_support(BaseDir) of
+ true ->
+ simple_test(BaseDir),
+ inside_directory_test(BaseDir),
+ nested_links_test(BaseDir),
+ loop_test(BaseDir),
+ loop_with_parent_test(BaseDir),
+ revist_links_test(BaseDir);
+ false ->
+ {skipped, "This platform/user can't create symlinks."}
+ end
+ after
+ %% This test leaves some rather nasty links that may screw with
+ %% z_SUITE's core file search, so we must make sure everything's
+ %% removed regardless of what happens.
+ rm_rf(BaseDir)
+ end.
+
+check_symlink_support(BaseDir) ->
+ Canary = filename:join(BaseDir, "symlink_canary"),
+ Link = filename:join(BaseDir, "symlink_canary_link"),
+ ok = file:write_file(Canary, <<"chirp">>),
+ ok =:= file:make_symlink(Canary, Link).
+
+simple_test(BaseDir) ->
+ file:make_dir(filename:join(BaseDir, "simple_test")),
+ file:make_symlink("..", filename:join(BaseDir, "simple_test/link")),
+
+ unsafe = filelib:safe_relative_path("link/file", filename:join(BaseDir, "simple_test")),
+ "file" = filelib:safe_relative_path("file", filename:join(BaseDir, "simple_test/link")).
+
+inside_directory_test(BaseDir) ->
+ file:make_dir(filename:join(BaseDir, "inside_directory_test")),
+ file:make_symlink("..", filename:join(BaseDir, "inside_directory_test/link")),
+
+ unsafe = filelib:safe_relative_path("link/file", filename:join(BaseDir, "inside_directory_test")),
+ "file" = filelib:safe_relative_path("file", filename:join(BaseDir, "inside_directory_test/link")).
+
+nested_links_test(BaseDir) ->
+ file:make_dir(filename:join(BaseDir, "nested_links_test")),
+ file:make_dir(filename:join(BaseDir, "nested_links_test/a")),
+ file:make_symlink("a/b/c", filename:join(BaseDir, "nested_links_test/link")),
+ file:make_symlink("..", filename:join(BaseDir, "nested_links_test/a/b")),
+
+ "c/file" = filelib:safe_relative_path("link/file", filename:join(BaseDir, "nested_links_test")),
+
+ file:delete(filename:join(BaseDir, "nested_links_test/a/b")),
+ file:make_symlink("../..", filename:join(BaseDir, "nested_links_test/a/b")),
+ unsafe = filelib:safe_relative_path("link/file", filename:join(BaseDir, "nested_links_test")).
+
+loop_test(BaseDir) ->
+ file:make_dir(filename:join(BaseDir, "loop_test")),
+
+ file:make_symlink("b", filename:join(BaseDir, "loop_test/c")),
+ file:make_symlink("c", filename:join(BaseDir, "loop_test/b")),
+
+ unsafe = filelib:safe_relative_path("c", filename:join(BaseDir, "loop_test")).
+
+loop_with_parent_test(BaseDir) ->
+ file:make_dir(filename:join(BaseDir, "loop_with_parent_test")),
+ file:make_dir(filename:join(BaseDir, "loop_with_parent_test/bar")),
+
+ file:make_symlink("../bar/foo", filename:join(BaseDir, "loop_with_parent_test/bar/foo")),
+
+ unsafe = filelib:safe_relative_path("bar/foo", filename:join(BaseDir, "loop_with_parent_test")).
+
+revist_links_test(BaseDir) ->
+ file:make_dir(filename:join(BaseDir, "revist_links_test")),
+
+ file:make_symlink(".", filename:join(BaseDir, "revist_links_test/x")),
+ file:make_symlink("x", filename:join(BaseDir, "revist_links_test/y")),
+ file:make_symlink("y", filename:join(BaseDir, "revist_links_test/z")),
+
+ "file" = filelib:safe_relative_path("x/file", filename:join(BaseDir, "revist_links_test")),
+ "file" = filelib:safe_relative_path("y/x/file", filename:join(BaseDir, "revist_links_test")),
+ "file" = filelib:safe_relative_path("x/x/file", filename:join(BaseDir, "revist_links_test")),
+ "file" = filelib:safe_relative_path("x/y/x/y/file", filename:join(BaseDir, "revist_links_test")),
+ "file" = filelib:safe_relative_path("x/y/z/x/y/z/file", filename:join(BaseDir, "revist_links_test")),
+ "file" = filelib:safe_relative_path("x/x/y/y/file", filename:join(BaseDir, "revist_links_test")),
+ "file" = filelib:safe_relative_path("x/z/y/x/./z/foo/../x/./y/file", filename:join(BaseDir, "revist_links_test")).
+
+rm_rf(Dir) ->
+ case file:read_link_info(Dir) of
+ {ok, #file_info{type = directory}} ->
+ {ok, Content} = file:list_dir_all(Dir),
+ [ rm_rf(filename:join(Dir,C)) || C <- Content ],
+ file:del_dir(Dir),
+ ok;
+ {ok, #file_info{}} ->
+ file:delete(Dir);
+ _ ->
+ ok
+ end.
diff --git a/lib/stdlib/test/filename_SUITE.erl b/lib/stdlib/test/filename_SUITE.erl
index f284eb1ed6..846394d366 100644
--- a/lib/stdlib/test/filename_SUITE.erl
+++ b/lib/stdlib/test/filename_SUITE.erl
@@ -886,7 +886,7 @@ t_nativename_bin(Config) when is_list(Config) ->
safe_relative_path(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
- Root = filename:join(PrivDir, ?FUNCTION_NAME),
+ Root = filename:join(PrivDir, "filename_SUITE_safe_relative_path"),
ok = file:make_dir(Root),
ok = file:set_cwd(Root),
@@ -1081,7 +1081,10 @@ check_basedir_xdg([Type|Types]) ->
Opt = #{os=>linux},
Key = basedir_xdg_env(Type),
io:format("type: ~p~n", [Type]),
- Home = os:getenv("HOME"),
+ Home = case os:getenv("WSLENV") of
+ false -> os:getenv("HOME");
+ _ -> os:getenv("USERPROFILE")
+ end,
NDir = "/some/absolute/path",
DefPath = basedir_xdg_def(Type,Home,Name),
EnvPath = case Type of
diff --git a/lib/stdlib/test/gen_event_SUITE.erl b/lib/stdlib/test/gen_event_SUITE.erl
index 880b10117c..cb292cf01f 100644
--- a/lib/stdlib/test/gen_event_SUITE.erl
+++ b/lib/stdlib/test/gen_event_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@
start_opt/1,
undef_init/1, undef_handle_call/1, undef_handle_event/1,
undef_handle_info/1, undef_code_change/1, undef_terminate/1,
- undef_in_terminate/1]).
+ undef_in_terminate/1, format_log_1/1, format_log_2/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -40,7 +40,8 @@ all() ->
[start, {group, test_all}, hibernate, auto_hibernate,
call_format_status, call_format_status_anon, error_format_status,
get_state, replace_state,
- start_opt, {group, undef_callbacks}, undef_in_terminate].
+ start_opt, {group, undef_callbacks}, undef_in_terminate,
+ format_log_1, format_log_2].
groups() ->
[{test_all, [],
@@ -112,6 +113,11 @@ start(Config) when is_list(Config) ->
[] = gen_event:which_handlers(Pid1),
ok = gen_event:stop(Pid1),
+ {ok, {Pid1b,Mon1b}} = gen_event:start_monitor(), %anonymous
+ [] = gen_event:which_handlers(Pid1b),
+ ok = gen_event:stop(Pid1b),
+ receive {'DOWN',Mon1b,process,Pid1b,_} -> ok end,
+
{ok, Pid2} = gen_event:start(?LMGR),
[] = gen_event:which_handlers(my_dummy_name),
[] = gen_event:which_handlers(Pid2),
@@ -122,21 +128,45 @@ start(Config) when is_list(Config) ->
[] = gen_event:which_handlers(Pid3),
ok = gen_event:stop(my_dummy_name),
+ {ok, {Pid3b,Mon3b}} = gen_event:start_monitor(?LMGR),
+ [] = gen_event:which_handlers(my_dummy_name),
+ [] = gen_event:which_handlers(Pid3b),
+ ok = gen_event:stop(my_dummy_name),
+ receive {'DOWN',Mon3b,process,Pid3b,_} -> ok end,
+
{ok, Pid4} = gen_event:start_link(?GMGR),
[] = gen_event:which_handlers(?GMGR),
[] = gen_event:which_handlers(Pid4),
ok = gen_event:stop(?GMGR),
+ {ok, {Pid4b,Mon4b}} = gen_event:start_monitor(?GMGR),
+ [] = gen_event:which_handlers(?GMGR),
+ [] = gen_event:which_handlers(Pid4b),
+ ok = gen_event:stop(?GMGR),
+ receive {'DOWN',Mon4b,process,Pid4b,_} -> ok end,
+
{ok, Pid5} = gen_event:start_link({via, dummy_via, my_dummy_name}),
[] = gen_event:which_handlers({via, dummy_via, my_dummy_name}),
[] = gen_event:which_handlers(Pid5),
ok = gen_event:stop({via, dummy_via, my_dummy_name}),
+ {ok, {Pid5b,Mon5b}} = gen_event:start_monitor({via, dummy_via, my_dummy_name}),
+ [] = gen_event:which_handlers({via, dummy_via, my_dummy_name}),
+ [] = gen_event:which_handlers(Pid5b),
+ ok = gen_event:stop({via, dummy_via, my_dummy_name}),
+ receive {'DOWN',Mon5b,process,Pid5b,_} -> ok end,
+
{ok, _} = gen_event:start_link(?LMGR),
{error, {already_started, _}} = gen_event:start_link(?LMGR),
{error, {already_started, _}} = gen_event:start(?LMGR),
ok = gen_event:stop(my_dummy_name),
+ {ok, {Pid5c,Mon5c}} = gen_event:start_monitor(?LMGR),
+ {error, {already_started, Pid5c}} = gen_event:start_monitor(?LMGR),
+ {error, {already_started, Pid5c}} = gen_event:start(?LMGR),
+ ok = gen_event:stop(my_dummy_name),
+ receive {'DOWN',Mon5c,process,Pid5c,_} -> ok end,
+
{ok, Pid6} = gen_event:start_link(?GMGR),
{error, {already_started, _}} = gen_event:start_link(?GMGR),
{error, {already_started, _}} = gen_event:start(?GMGR),
@@ -148,6 +178,17 @@ start(Config) when is_list(Config) ->
ct:fail(exit_gen_event)
end,
+ {ok, {Pid6b,Mon6b}} = gen_event:start_monitor(?GMGR),
+ {error, {already_started, _}} = gen_event:start_monitor(?GMGR),
+ {error, {already_started, _}} = gen_event:start(?GMGR),
+
+ ok = gen_event:stop(?GMGR, shutdown, 10000),
+ receive
+ {'DOWN', Mon6b, process, Pid6b, shutdown} -> ok
+ after 10000 ->
+ ct:fail(exit_gen_event)
+ end,
+
{ok, Pid7} = gen_event:start_link({via, dummy_via, my_dummy_name}),
{error, {already_started, _}} = gen_event:start_link({via, dummy_via, my_dummy_name}),
{error, {already_started, _}} = gen_event:start({via, dummy_via, my_dummy_name}),
@@ -159,6 +200,17 @@ start(Config) when is_list(Config) ->
ct:fail(exit_gen_event)
end,
+ {ok, {Pid7b,Mon7b}} = gen_event:start_monitor({via, dummy_via, my_dummy_name}),
+ {error, {already_started, _}} = gen_event:start_monitor({via, dummy_via, my_dummy_name}),
+ {error, {already_started, _}} = gen_event:start({via, dummy_via, my_dummy_name}),
+
+ exit(Pid7b, shutdown),
+ receive
+ {'DOWN', Mon7b, process, Pid7b, shutdown} -> ok
+ after 10000 ->
+ ct:fail(exit_gen_event)
+ end,
+
process_flag(trap_exit, OldFl),
ok.
@@ -763,27 +815,49 @@ sync_notify(Config) when is_list(Config) ->
ok.
call(Config) when is_list(Config) ->
+ Async = fun(Mgr,H,Req) ->
+ try
+ Promise = gen_event:send_request(Mgr,H,Req),
+ gen_event:wait_response(Promise, infinity)
+ catch _:Reason ->
+ {'did_exit', Reason}
+ end
+ end,
{ok,_} = gen_event:start({local, my_dummy_handler}),
ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
ok = gen_event:add_handler(my_dummy_handler, {dummy_h, 1}, [self()]),
[{dummy_h, 1}, dummy_h] = gen_event:which_handlers(my_dummy_handler),
{'EXIT',_} = (catch gen_event:call(non_exist, dummy_h, hejsan)),
- {error, bad_module} =
- gen_event:call(my_dummy_handler, bad_h, hejsan),
+ {error, _} = Async(non_exist, dummy_h, hejsan),
+ {error, bad_module} = gen_event:call(my_dummy_handler, bad_h, hejsan),
+ {error, bad_module} = Async(my_dummy_handler, bad_h, hejsan),
+
{ok, hejhopp} = gen_event:call(my_dummy_handler, dummy_h, hejsan),
- {ok, hejhopp} = gen_event:call(my_dummy_handler, {dummy_h, 1},
- hejsan),
- {ok, hejhopp} = gen_event:call(my_dummy_handler, dummy_h, hejsan,
- 10000),
+ {reply, {ok, hejhopp}} = Async(my_dummy_handler, dummy_h, hejsan),
+ {ok, hejhopp} = gen_event:call(my_dummy_handler, {dummy_h, 1}, hejsan),
+ {reply, {ok, hejhopp}} = Async(my_dummy_handler, {dummy_h, 1}, hejsan),
+ {ok, hejhopp} = gen_event:call(my_dummy_handler, {dummy_h, 1}, hejsan),
+ {reply, {ok, hejhopp}} = Async(my_dummy_handler, {dummy_h, 1}, hejsan),
+ {ok, hejhopp} = gen_event:call(my_dummy_handler, dummy_h, hejsan, 10000),
{'EXIT', {timeout, _}} =
(catch gen_event:call(my_dummy_handler, dummy_h, hejsan, 0)),
flush(),
+ P1 = gen_event:send_request(my_dummy_handler, dummy_h, hejsan),
+ timeout = gen_event:wait_response(P1, 0),
+ {reply, {ok, hejhopp}} = gen_event:wait_response(P1, infinity),
+
+ flush(),
+ P2 = gen_event:send_request(my_dummy_handler, dummy_h, hejsan),
+ no_reply = gen_event:check_response({other,msg}, P2),
+ {reply, {ok, hejhopp}} = receive Msg -> gen_event:check_response(Msg, P2)
+ after 1000 -> exit(tmo) end,
+
ok = gen_event:delete_handler(my_dummy_handler, {dummy_h, 1}, []),
{ok, swapped} = gen_event:call(my_dummy_handler, dummy_h,
{swap_call,dummy1_h,swap}),
[dummy1_h] = gen_event:which_handlers(my_dummy_handler),
- {error, bad_module} =
- gen_event:call(my_dummy_handler, dummy_h, hejsan),
+ {error, bad_module} = gen_event:call(my_dummy_handler, dummy_h, hejsan),
+ {error, bad_module} = Async(my_dummy_handler, dummy_h, hejsan),
ok = gen_event:call(my_dummy_handler, dummy1_h, delete_call),
receive
{dummy1_h, removed} ->
@@ -1221,3 +1295,209 @@ fake_upgrade(Pid, Mod) ->
Ret = sys:change_code(Pid, Mod, old_vsn, []),
ok = sys:resume(Pid),
Ret.
+
+%% Test report callback for Logger handler error_logger
+format_log_1(_Config) ->
+ FD = application:get_env(kernel, error_logger_format_depth),
+ application:unset_env(kernel, error_logger_format_depth),
+ Term = lists:seq(1, 15),
+ Handler = my_handler,
+ Name = self(),
+ Report = #{label=>{gen_event,terminate},
+ handler=>Handler,
+ name=>Name,
+ last_message=>Term,
+ state=>Term,
+ reason=>Term},
+ {F1, A1} = gen_event:format_log(Report),
+ FExpected1 = "** gen_event handler ~tp crashed.\n"
+ "** Was installed in ~tp\n"
+ "** Last event was: ~tp\n"
+ "** When handler state == ~tp\n"
+ "** Reason == ~tp\n",
+ ct:log("F1: ~ts~nA1: ~tp", [F1,A1]),
+ FExpected1 = F1,
+ [Handler,Name,Term,Term,Term] = A1,
+
+ Warning = #{label=>{gen_event,no_handle_info},
+ module=>?MODULE,
+ message=>Term},
+ {WF1,WA1} = gen_event:format_log(Warning),
+ WFExpected1 = "** Undefined handle_info in ~p\n"
+ "** Unhandled message: ~tp\n",
+ ct:log("WF1: ~ts~nWA1: ~tp", [WF1,WA1]),
+ WFExpected1 = WF1,
+ [?MODULE,Term] = WA1,
+
+ Depth = 10,
+ ok = application:set_env(kernel, error_logger_format_depth, Depth),
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ {F2,A2} = gen_event:format_log(#{label=>{gen_event,terminate},
+ handler=>Handler,
+ name=>Name,
+ last_message=>Term,
+ state=>Term,
+ reason=>Term}),
+ FExpected2 = "** gen_event handler ~tP crashed.\n"
+ "** Was installed in ~tP\n"
+ "** Last event was: ~tP\n"
+ "** When handler state == ~tP\n"
+ "** Reason == ~tP\n",
+ ct:log("F2: ~ts~nA2: ~tp", [F2,A2]),
+ FExpected2 = F2,
+ [Handler,Depth,Name,Depth,Limited,Depth,Limited,Depth,Limited,Depth] = A2,
+
+ {WF2,WA2} = gen_event:format_log(Warning),
+ WFExpected2 = "** Undefined handle_info in ~p\n"
+ "** Unhandled message: ~tP\n",
+ ct:log("WF2: ~ts~nWA2: ~tp", [WF2,WA2]),
+ WFExpected2 = WF2,
+ [?MODULE,Limited,Depth] = WA2,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel, error_logger_format_depth);
+ _ ->
+ application:set_env(kernel, error_logger_format_depth, FD)
+ end,
+ ok.
+
+%% Test report callback for any Logger handler
+format_log_2(_Config) ->
+ Term = lists:seq(1, 15),
+ Handler = my_handler,
+ Name = self(),
+ NameStr = pid_to_list(Name),
+ Report = #{label=>{gen_event,terminate},
+ handler=>Handler,
+ name=>Name,
+ last_message=>Term,
+ state=>Term,
+ reason=>Term},
+ FormatOpts1 = #{},
+ Str1 = flatten_format_log(Report, FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = "** gen_event handler my_handler crashed.\n"
+ "** Was installed in "++NameStr++"\n"
+ "** Last event was: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ "** When handler state == [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ "** Reason == [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("Str1: ~ts", [Str1]),
+ ct:log("length(Str1): ~p", [L1]),
+ true = Expected1 =:= Str1,
+
+ Warning = #{label=>{gen_event,no_handle_info},
+ module=>?MODULE,
+ message=>Term},
+ WStr1 = flatten_format_log(Warning, FormatOpts1),
+ WL1 = length(WStr1),
+ WExpected1 = "** Undefined handle_info in gen_event_SUITE\n"
+ "** Unhandled message: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("WStr1: ~ts", [WStr1]),
+ ct:log("length(WStr1): ~p", [WL1]),
+ true = WExpected1 =:= WStr1,
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_format_log(Report, FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = "** gen_event handler my_handler crashed.\n"
+ "** Was installed in " ++ NameStr ++ "\n"
+ "** Last event was: [1,2,3,4,5,6,7,8,9|...]\n"
+ "** When handler state == [1,2,3,4,5,6,7,8,9|...]\n"
+ "** Reason == [1,2,3,4,5,6,7,8,9|...]\n",
+ ct:log("Str2: ~ts", [Str2]),
+ ct:log("length(Str2): ~p", [L2]),
+ true = Expected2 =:= Str2,
+
+ WStr2 = flatten_format_log(Warning, FormatOpts2),
+ WL2 = length(WStr2),
+ WExpected2 = "** Undefined handle_info in gen_event_SUITE\n"
+ "** Unhandled message: [1,2,3,4,5,6,7,8,9|...]\n",
+ ct:log("WStr2: ~ts", [WStr2]),
+ ct:log("length(WStr2): ~p", [WL2]),
+ true = WExpected2 =:= WStr2,
+
+ FormatOpts3 = #{chars_limit=>200},
+ Str3 = flatten_format_log(Report, FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = "** gen_event handler my_handler crashed.\n"
+ "** Was installed",
+ ct:log("Str3: ~ts", [Str3]),
+ ct:log("length(Str3): ~p", [L3]),
+ true = lists:prefix(Expected3, Str3),
+ true = L3 < L1,
+
+ WFormatOpts3 = #{chars_limit=>80},
+ WStr3 = flatten_format_log(Warning, WFormatOpts3),
+ WL3 = length(WStr3),
+ WExpected3 = "** Undefined handle_info in gen_event_SUITE\n"
+ "** Unhandled message: ",
+ ct:log("WStr3: ~ts", [WStr3]),
+ ct:log("length(WStr3): ~p", [WL3]),
+ true = lists:prefix(WExpected3, WStr3),
+ true = WL3 < WL1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_format_log(Report, FormatOpts4),
+ L4 = length(Str4),
+
+ Expected4 = "Generic event handler my_handler crashed. "
+ "Installed: "++NameStr++". "
+ "Last event: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "State: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "Reason: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].",
+ ct:log("Str4: ~ts", [Str4]),
+ ct:log("length(Str4): ~p", [L4]),
+ true = Expected4 =:= Str4,
+
+ WStr4 = flatten_format_log(Warning, FormatOpts4),
+ WL4 = length(WStr4),
+ WExpected4 = "Undefined handle_info in gen_event_SUITE. "
+ "Unhandled message: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].",
+ ct:log("WStr4: ~ts", [WStr4]),
+ ct:log("length(WStr4): ~p", [WL4]),
+ true = WExpected4 =:= WStr4,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_format_log(Report, FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "Generic event handler my_handler crashed. "
+ "Installed: "++NameStr++". "
+ "Last event: [1,2,3,4,5,6,7,8,9|...]. "
+ "State: [1,2,3,4,5,6,7,8,9|...]. "
+ "Reason: [1,2,3,4,5,6,7,8,9|...].",
+ ct:log("Str5: ~ts", [Str5]),
+ ct:log("length(Str5): ~p", [L5]),
+ true = Expected5 =:= Str5,
+
+ WStr5 = flatten_format_log(Warning, FormatOpts5),
+ WL5 = length(WStr5),
+ WExpected5 = "Undefined handle_info in gen_event_SUITE. "
+ "Unhandled message: [1,2,3,4,5,6,7,8,9|...].",
+ ct:log("WStr5: ~ts", [WStr5]),
+ ct:log("length(WStr5): ~p", [WL5]),
+ true = WExpected5 =:= WStr5,
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>200},
+ Str6 = flatten_format_log(Report, FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "Generic event handler my_handler crashed. Installed: ",
+ ct:log("Str6: ~ts", [Str6]),
+ ct:log("length(Str6): ~p", [L6]),
+ true = lists:prefix(Expected6, Str6),
+ true = L6 < L4,
+
+ WFormatOpts6 = #{single_line=>true, chars_limit=>80},
+ WStr6 = flatten_format_log(Warning, WFormatOpts6),
+ WL6 = length(WStr6),
+ WExpected6 = "Undefined handle_info in gen_event_SUITE. ",
+ ct:log("WStr6: ~ts", [WStr6]),
+ ct:log("length(WStr6): ~p", [WL6]),
+ true = lists:prefix(WExpected6, WStr6),
+ true = WL6 < WL4,
+
+ ok.
+
+flatten_format_log(Report, Format) ->
+ lists:flatten(gen_event:format_log(Report, Format)).
diff --git a/lib/stdlib/test/gen_fsm_SUITE.erl b/lib/stdlib/test/gen_fsm_SUITE.erl
index 62d9d0e0ae..6840184c74 100644
--- a/lib/stdlib/test/gen_fsm_SUITE.erl
+++ b/lib/stdlib/test/gen_fsm_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -46,6 +46,8 @@
-export([hibernate/1,auto_hibernate/1,hiber_idle/3,hiber_wakeup/3,hiber_idle/2,hiber_wakeup/2]).
+-export([format_log_1/1, format_log_2/1]).
+
-export([enter_loop/1]).
%% Exports for apply
@@ -69,7 +71,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[{group, start}, {group, abnormal}, shutdown,
{group, sys}, hibernate, auto_hibernate, enter_loop, {group, undef_callbacks},
- undef_in_handle_info, undef_in_terminate].
+ undef_in_handle_info, undef_in_terminate,{group,format_log}].
groups() ->
[{start, [],
@@ -83,7 +85,8 @@ groups() ->
get_state, replace_state]},
{undef_callbacks, [],
[undef_handle_event, undef_handle_sync_event, undef_handle_info,
- undef_init, undef_code_change, undef_terminate1, undef_terminate2]}].
+ undef_init, undef_code_change, undef_terminate1, undef_terminate2]},
+ {format_log, [], [format_log_1, format_log_2]}].
init_per_suite(Config) ->
Config.
@@ -1018,6 +1021,236 @@ undef_in_terminate(Config) when is_list(Config) ->
ok
end.
+%% Test report callback for Logger handler error_logger
+format_log_1(_Config) ->
+ FD = application:get_env(kernel, error_logger_format_depth),
+ application:unset_env(kernel, error_logger_format_depth),
+ Term = lists:seq(1, 15),
+ Name = self(),
+ Report = #{label=>{gen_fsm,terminate},
+ name=>Name,
+ last_message=>Term,
+ state_name=>Name,
+ state_data=>Term,
+ log=>[Term],
+ reason=>Term,
+ client_info=>{self(),{clientname,[]}}},
+ {F1,A1} = gen_fsm:format_log(Report),
+ FExpected1 = "** State machine ~tp terminating \n"
+ "** Last message in was ~tp~n"
+ "** When State == ~tp~n"
+ "** Data == ~tp~n"
+ "** Reason for termination ==~n** ~tp~n"
+ "** Log ==~n**~tp~n"
+ "** Client ~tp stacktrace~n** ~tp~n",
+ ct:log("F1: ~ts~nA1: ~tp", [F1,A1]),
+ FExpected1=F1,
+
+ [Name,Term,Name,Term,Term,[Term],clientname,[]] = A1,
+
+ Warning = #{label=>{gen_fsm,no_handle_info},
+ module=>?MODULE,
+ message=>Term},
+ {WF1,WA1} = gen_fsm:format_log(Warning),
+ WFExpected1 = "** Undefined handle_info in ~p~n"
+ "** Unhandled message: ~tp~n",
+ ct:log("WF1: ~ts~nWA1: ~tp", [WF1,WA1]),
+ WFExpected1=WF1,
+ [?MODULE,Term] = WA1,
+
+ Depth = 10,
+ ok = application:set_env(kernel, error_logger_format_depth, Depth),
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ {F2,A2} = gen_fsm:format_log(#{label=>{gen_fsm,terminate},
+ name=>Name,
+ last_message=>Term,
+ state_name=>Name,
+ state_data=>Term,
+ log=>[Term],
+ reason=>Term,
+ client_info=>{self(),{clientname,[]}}}),
+ FExpected2 = "** State machine ~tP terminating \n"
+ "** Last message in was ~tP~n"
+ "** When State == ~tP~n"
+ "** Data == ~tP~n"
+ "** Reason for termination ==~n** ~tP~n"
+ "** Log ==~n**~tP~n"
+ "** Client ~tP stacktrace~n** ~tP~n",
+ ct:log("F2: ~ts~nA2: ~tp", [F2,A2]),
+ FExpected2=F2,
+
+ [Name,Depth,Limited,Depth,Name,Depth,Limited,Depth,Limited,
+ Depth,[Limited],Depth,clientname,Depth,[],Depth] = A2,
+
+ {WF2,WA2} = gen_fsm:format_log(Warning),
+ WFExpected2 = "** Undefined handle_info in ~p~n"
+ "** Unhandled message: ~tP~n",
+ ct:log("WF2: ~ts~nWA2: ~tp", [WF2,WA2]),
+ WFExpected2=WF2,
+ [?MODULE,Limited,Depth] = WA2,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel, error_logger_format_depth);
+ _ ->
+ application:set_env(kernel, error_logger_format_depth, FD)
+ end,
+ ok.
+
+%% Test report callback for any Logger handler
+format_log_2(_Config) ->
+ Term = lists:seq(1, 15),
+ Name = self(),
+ NameStr = pid_to_list(Name),
+ Report = #{label=>{gen_fsm,terminate},
+ name=>Name,
+ last_message=>Term,
+ state_name=>Name,
+ state_data=>Term,
+ log=>[Term],
+ reason=>Term,
+ client_info=>{self(),{clientname,[]}}},
+ FormatOpts1 = #{},
+ Str1 = flatten_format_log(Report, FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = "** State machine "++NameStr++" terminating \n"
+ "** Last message in was [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ "** When State == "++NameStr++"\n"
+ "** Data == [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ "** Reason for termination ==\n"
+ "** [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ "** Log ==\n"
+ "**[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]]\n"
+ "** Client clientname stacktrace\n"
+ "** []\n",
+ ct:log("Str1: ~ts", [Str1]),
+ ct:log("length(Str1): ~p", [L1]),
+ true = Expected1 =:= Str1,
+
+ Warning = #{label=>{gen_fsm,no_handle_info},
+ module=>?MODULE,
+ message=>Term},
+ WStr1 = flatten_format_log(Warning, FormatOpts1),
+ WL1 = length(WStr1),
+ WExpected1 = "** Undefined handle_info in gen_fsm_SUITE\n"
+ "** Unhandled message: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("WStr1: ~ts", [WStr1]),
+ ct:log("length(WStr1): ~p", [WL1]),
+ true = WExpected1 =:= WStr1,
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_format_log(Report, FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = "** State machine "++NameStr++" terminating \n"
+ "** Last message in was [1,2,3,4,5,6,7,8,9|...]\n"
+ "** When State == "++NameStr++"\n"
+ "** Data == [1,2,3,4,5,6,7,8,9|...]\n"
+ "** Reason for termination ==\n"
+ "** [1,2,3,4,5,6,7,8,9|...]\n"
+ "** Log ==\n"
+ "**[[1,2,3,4,5,6,7,8|...]]\n"
+ "** Client clientname stacktrace\n"
+ "** []\n",
+ ct:log("Str2: ~ts", [Str2]),
+ ct:log("length(Str2): ~p", [L2]),
+ true = Expected2 =:= Str2,
+
+ WStr2 = flatten_format_log(Warning, FormatOpts2),
+ WL2 = length(WStr2),
+ WExpected2 = "** Undefined handle_info in gen_fsm_SUITE\n"
+ "** Unhandled message: [1,2,3,4,5,6,7,8,9|...]\n",
+ ct:log("WStr2: ~ts", [WStr2]),
+ ct:log("length(WStr2): ~p", [WL2]),
+ true = WExpected2 =:= WStr2,
+
+ FormatOpts3 = #{chars_limit=>200},
+ Str3 = flatten_format_log(Report, FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = "** State machine "++NameStr++" terminating \n"
+ "** Last ",
+ ct:log("Str3: ~ts", [Str3]),
+ ct:log("length(Str3): ~p", [L3]),
+ true = lists:prefix(Expected3, Str3),
+ true = L3 < L1,
+
+ WFormatOpts3 = #{chars_limit=>80},
+ WStr3 = flatten_format_log(Warning, WFormatOpts3),
+ WL3 = length(WStr3),
+ WExpected3 = "** Undefined handle_info in gen_fsm_SUITE",
+ ct:log("WStr3: ~ts", [WStr3]),
+ ct:log("length(WStr3): ~p", [WL3]),
+ true = lists:prefix(WExpected3, WStr3),
+ true = WL3 < WL1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_format_log(Report, FormatOpts4),
+ L4 = length(Str4),
+ Expected4 = "State machine "++NameStr++" terminating. "
+ "Reason: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "Last event: [[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]]. "
+ "State: "++NameStr++". "
+ "Data: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "Log: [[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]]. "
+ "Client clientname stacktrace: [].",
+ ct:log("Str4: ~ts", [Str4]),
+ ct:log("length(Str4): ~p", [L4]),
+ true = Expected4 =:= Str4,
+
+ WStr4 = flatten_format_log(Warning, FormatOpts4),
+ WL4 = length(WStr4),
+ WExpected4 = "Undefined handle_info in gen_fsm_SUITE. "
+ "Unhandled message: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].",
+ ct:log("WStr4: ~ts", [WStr4]),
+ ct:log("length(WStr4): ~p", [WL4]),
+ true = WExpected4 =:= WStr4,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_format_log(Report, FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "State machine "++NameStr++" terminating. "
+ "Reason: [1,2,3,4,5,6,7,8,9|...]. "
+ "Last event: [[1,2,3,4,5,6,7,8|...]]. "
+ "State: "++NameStr++". "
+ "Data: [1,2,3,4,5,6,7,8,9|...]. "
+ "Log: [[1,2,3,4,5,6,7,8|...]]. "
+ "Client clientname stacktrace: [].",
+ ct:log("Str5: ~ts", [Str5]),
+ ct:log("length(Str5): ~p", [L5]),
+ true = Expected5 =:= Str5,
+
+ WStr5 = flatten_format_log(Warning, FormatOpts5),
+ WL5 = length(WStr5),
+ WExpected5 = "Undefined handle_info in gen_fsm_SUITE. "
+ "Unhandled message: [1,2,3,4,5,6,7,8,9|...].",
+ ct:log("WStr5: ~ts", [WStr5]),
+ ct:log("length(WStr5): ~p", [WL5]),
+ true = WExpected5 =:= WStr5,
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>200},
+ Str6 = flatten_format_log(Report, FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "State machine "++NameStr++" terminating. Reason: ",
+ ct:log("Str6: ~ts", [Str6]),
+ ct:log("length(Str6): ~p", [L6]),
+ true = lists:prefix(Expected6, Str6),
+ true = L6 < L4,
+
+ WFormatOpts6 = #{single_line=>true, chars_limit=>80},
+ WStr6 = flatten_format_log(Warning, WFormatOpts6),
+ WL6 = length(WStr6),
+ WExpected6 = "Undefined handle_info in gen_fsm_SUITE. "
+ "Unhandled message: ",
+ ct:log("WStr6: ~ts", [WStr6]),
+ ct:log("length(WStr6): ~p", [WL6]),
+ true = lists:prefix(WExpected6, WStr6),
+ true = WL6 < WL4,
+
+ ok.
+
+flatten_format_log(Report, Format) ->
+ lists:flatten(gen_fsm:format_log(Report, Format)).
+
%%
%% Functionality check
%%
diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl
index e29195e895..8015126b0d 100644
--- a/lib/stdlib/test/gen_server_SUITE.erl
+++ b/lib/stdlib/test/gen_server_SUITE.erl
@@ -26,7 +26,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
--export([start/1, crash/1, call/1, cast/1, cast_fast/1,
+-export([start/1, crash/1, call/1, send_request/1, cast/1, cast_fast/1,
continue/1, info/1, abcast/1, multicall/1, multicall_down/1,
call_remote1/1, call_remote2/1, call_remote3/1,
call_remote_n1/1, call_remote_n2/1, call_remote_n3/1, spec_init/1,
@@ -38,7 +38,9 @@
undef_handle_call/1, undef_handle_cast/1, undef_handle_info/1,
undef_init/1, undef_code_change/1, undef_terminate1/1,
undef_terminate2/1, undef_in_terminate/1, undef_in_handle_info/1,
- undef_handle_continue/1
+ undef_handle_continue/1,
+
+ format_log_1/1, format_log_2/1
]).
-export([stop1/1, stop2/1, stop3/1, stop4/1, stop5/1, stop6/1, stop7/1,
@@ -61,7 +63,7 @@ suite() ->
{timetrap,{minutes,1}}].
all() ->
- [start, {group,stop}, crash, call, cast, cast_fast, info, abcast,
+ [start, {group,stop}, crash, call, send_request, cast, cast_fast, info, abcast,
continue, multicall, multicall_down, call_remote1, call_remote2,
call_remote3, call_remote_n1, call_remote_n2,
call_remote_n3, spec_init,
@@ -71,7 +73,8 @@ all() ->
call_format_status, error_format_status, terminate_crash_format,
get_state, replace_state,
call_with_huge_message_queue, {group, undef_callbacks},
- undef_in_terminate, undef_in_handle_info].
+ undef_in_terminate, undef_in_handle_info,
+ format_log_1, format_log_2].
groups() ->
[{stop, [],
@@ -104,7 +107,8 @@ init_per_testcase(Case, Config) when Case == call_remote1;
Case == call_remote3;
Case == call_remote_n1;
Case == call_remote_n2;
- Case == call_remote_n3 ->
+ Case == call_remote_n3;
+ Case == send_request ->
{ok,N} = start_node(hubba),
[{node,N} | Config];
@@ -161,6 +165,18 @@ start(Config) when is_list(Config) ->
ct:fail(not_stopped)
end,
+ %% anonymous monitored
+ {ok, {Pid1b, Mon1b}} =
+ gen_server:start_monitor(gen_server_SUITE, [], []),
+ ok = gen_server:call(Pid1b, started_p),
+ ok = gen_server:call(Pid1b, stop),
+ receive
+ {'DOWN', Mon1b, process, Pid1b, stopped} ->
+ ok
+ after 5000 ->
+ ct:fail(not_stopped)
+ end,
+
%% local register
{ok, Pid2} =
gen_server:start({local, my_test_name},
@@ -191,6 +207,22 @@ start(Config) when is_list(Config) ->
ct:fail(not_stopped)
end,
+ %% local register monitored
+ {ok, {Pid3b, Mon3b}} =
+ gen_server:start_monitor({local, my_test_name},
+ gen_server_SUITE, [], []),
+ ok = gen_server:call(my_test_name, started_p),
+ {error, {already_started, Pid3b}} =
+ gen_server:start_monitor({local, my_test_name},
+ gen_server_SUITE, [], []),
+ ok = gen_server:call(my_test_name, stop),
+ receive
+ {'DOWN', Mon3b, process, Pid3b, stopped} ->
+ ok
+ after 5000 ->
+ ct:fail(not_stopped)
+ end,
+
%% global register
{ok, Pid4} =
gen_server:start({global, my_test_name},
@@ -219,6 +251,22 @@ start(Config) when is_list(Config) ->
ct:fail(not_stopped)
end,
+ %% global register monitored
+ {ok, {Pid5b, Mon5b}} =
+ gen_server:start_monitor({global, my_test_name},
+ gen_server_SUITE, [], []),
+ ok = gen_server:call({global, my_test_name}, started_p),
+ {error, {already_started, Pid5b}} =
+ gen_server:start_monitor({global, my_test_name},
+ gen_server_SUITE, [], []),
+ ok = gen_server:call({global, my_test_name}, stop),
+ receive
+ {'DOWN', Mon5b, process, Pid5b, stopped} ->
+ ok
+ after 5000 ->
+ ct:fail(not_stopped)
+ end,
+
%% via register
dummy_via:reset(),
{ok, Pid6} =
@@ -459,6 +507,90 @@ call(Config) when is_list(Config) ->
ok.
%% --------------------------------------
+%% Test gen_server:send_request.
+%% --------------------------------------
+
+send_request(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ {ok, Pid} = gen_server:start_link({local, my_test_name},
+ gen_server_SUITE, [], []),
+
+ Async = fun(Process, Req) ->
+ try
+ Promise = gen_server:send_request(Process, Req),
+ gen_server:wait_response(Promise, infinity)
+ catch _:Reason:ST ->
+ {'did_exit', Reason, ST}
+ end
+ end,
+ {reply,ok} = Async(my_test_name, started_p),
+
+ {reply,delayed} = Async(Pid, {delayed_answer,1}),
+
+ %% two requests within a specified time.
+ Promise1 = gen_server:send_request(my_test_name, {call_within, 1000}),
+ Promise2 = gen_server:send_request(my_test_name, next_call),
+ {reply, ok} = gen_server:wait_response(Promise1, infinity),
+ {reply, ok} = gen_server:wait_response(Promise2, infinity),
+
+ Promise3 = gen_server:send_request(my_test_name, {call_within, 1000}),
+ no_reply = gen_server:check_response({foo, bar}, Promise3),
+ receive {{'$gen_request_id',Ref},_} = Msg when is_reference(Ref) ->
+ {reply, ok} = gen_server:check_response(Msg, Promise3)
+ after 1000 ->
+ %% Format not yet doumented so it might be ok
+ %% This test is just to make you aware that you have changed it
+ exit(message_format_changed)
+ end,
+ timer:sleep(1500),
+
+ {reply, false} = Async(my_test_name, next_call),
+
+ %% timeout
+ Promise5 = gen_server:send_request(my_test_name, {delayed_answer,50}),
+ timeout = gen_server:wait_response(Promise5, 0),
+ {reply, delayed} = gen_server:wait_response(Promise5, infinity),
+
+ %% bad return value in the gen_server loop from handle_call.
+ {error,{{bad_return_value, badreturn},_}} = Async(my_test_name, badreturn),
+
+ %% Test other error cases
+ {error, {noproc,_}} = Async(Pid, started_p),
+ {error, {noproc,_}} = Async(my_test_name, started_p),
+ {error, {noconnection, _}} = Async({my_test_name, foo@node}, started_p),
+
+ {error, {noproc,_}} = Async({global, non_existing}, started_p),
+ catch exit(whereis(dummy_via), foo),
+ {'EXIT', {badarg,_}} =
+ (catch gen_server:send_request({via, dummy_via, non_existing}, started_p)),
+
+ %% Remote nodes
+ Via = dummy_via:reset(),
+ Remote = proplists:get_value(node,Config),
+ {ok, RPid} = rpc:call(Remote, gen_server, start, [{global, remote}, ?MODULE, [], []]),
+ dummy_via:register_name(remote, RPid),
+ {reply, ok} = Async(RPid, started_p),
+ {reply, ok} = Async({global, remote}, started_p),
+ {reply, ok} = Async({via, dummy_via, remote}, started_p),
+ {error, {shutdown, _}} = Async({global, remote}, stop_shutdown),
+ {error, {noproc, _}} = Async({global, remote}, started_p),
+ {error, {noproc, _}} = Async({via, dummy_via, remote}, started_p),
+ {error, {noproc, _}} = Async({via, dummy_via, non_existing}, started_p),
+
+ {ok, _} = rpc:call(Remote, gen_server, start, [{local, remote}, ?MODULE, [], []]),
+ {reply, ok} = Async({remote, Remote}, started_p),
+ {error, {shutdown, _}} = Async({remote, Remote}, stop_shutdown),
+ {error, {noproc, _}} = Async({remote, Remote}, started_p),
+
+ %% Cleanup
+ catch exit(Via, foo2),
+ receive {'EXIT', Via, foo2} -> ok end,
+ process_flag(trap_exit, OldFl),
+ ok.
+
+
+%% --------------------------------------
%% Test handle_continue.
%% --------------------------------------
@@ -1528,6 +1660,208 @@ wait_until_processed(Pid, Message, N) ->
ok
end.
+%% Test report callback for Logger handler error_logger
+format_log_1(_Config) ->
+ FD = application:get_env(kernel,error_logger_format_depth),
+ application:unset_env(kernel,error_logger_format_depth),
+ Term = lists:seq(1,15),
+ Name = self(),
+ Report = #{label=>{gen_server,terminate},
+ name=>Name,
+ last_message=>Term,
+ state=>Term,
+ log=>[],
+ reason=>Term,
+ client_info=>{self(),{clientname,[]}}},
+ {F1,A1} = gen_server:format_log(Report),
+ FExpected1 = "** Generic server ~tp terminating \n"
+ "** Last message in was ~tp~n"
+ "** When Server state == ~tp~n"
+ "** Reason for termination ==~n** ~tp~n"
+ "** Client ~tp stacktrace~n"
+ "** ~tp~n",
+ ct:log("F1: ~ts~nA1: ~tp",[F1,A1]),
+ FExpected1=F1,
+ [Name,Term,Term,Term,clientname,[]] = A1,
+
+ Warning = #{label=>{gen_server,no_handle_info},
+ module=>?MODULE,
+ message=>Term},
+ {WF1,WA1} = gen_server:format_log(Warning),
+ WFExpected1 = "** Undefined handle_info in ~p~n"
+ "** Unhandled message: ~tp~n",
+ ct:log("WF1: ~ts~nWA1: ~tp",[WF1,WA1]),
+ WFExpected1=WF1,
+ [?MODULE,Term] = WA1,
+
+ Depth = 10,
+ ok = application:set_env(kernel,error_logger_format_depth,Depth),
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ {F2,A2} = gen_server:format_log(#{label=>{gen_server,terminate},
+ name=>Name,
+ last_message=>Term,
+ state=>Term,
+ log=>[],
+ reason=>Term,
+ client_info=>{self(),{clientname,[]}}}),
+ FExpected2 = "** Generic server ~tP terminating \n"
+ "** Last message in was ~tP~n"
+ "** When Server state == ~tP~n"
+ "** Reason for termination ==~n** ~tP~n"
+ "** Client ~tP stacktrace~n"
+ "** ~tP~n",
+ ct:log("F2: ~ts~nA2: ~tp",[F2,A2]),
+ FExpected2=F2,
+ [Name,Depth,Limited,Depth,Limited,Depth,Limited,Depth,
+ clientname,Depth,[],Depth] = A2,
+
+ {WF2,WA2} = gen_server:format_log(Warning),
+ WFExpected2 = "** Undefined handle_info in ~p~n"
+ "** Unhandled message: ~tP~n",
+ ct:log("WF2: ~ts~nWA2: ~tp",[WF2,WA2]),
+ WFExpected2=WF2,
+ [?MODULE,Limited,Depth] = WA2,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel,error_logger_format_depth);
+ _ ->
+ application:set_env(kernel,error_logger_format_depth,FD)
+ end,
+ ok.
+
+%% Test report callback for any Logger handler
+format_log_2(_Config) ->
+ Term = lists:seq(1,15),
+ Name = self(),
+ NameStr = pid_to_list(Name),
+ Report = #{label=>{gen_server,terminate},
+ name=>Name,
+ last_message=>Term,
+ state=>Term,
+ log=>[],
+ reason=>Term,
+ client_info=>{self(),{clientname,[]}}},
+ FormatOpts1 = #{},
+ Str1 = flatten_format_log(Report,FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = "** Generic server "++NameStr++" terminating \n"
+ "** Last message in was ",
+ ct:log("Str1: ~ts",[Str1]),
+ ct:log("length(Str1): ~p",[L1]),
+ true = lists:prefix(Expected1,Str1),
+
+ Warning = #{label=>{gen_server,no_handle_info},
+ module=>?MODULE,
+ message=>Term},
+ WStr1 = flatten_format_log(Warning,FormatOpts1),
+ WL1 = length(WStr1),
+ WExpected1 = "** Undefined handle_info in gen_server_SUITE\n"
+ "** Unhandled message: ",
+ ct:log("WStr1: ~ts",[WStr1]),
+ ct:log("length(WStr1): ~p",[WL1]),
+ true = lists:prefix(WExpected1,WStr1),
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_format_log(Report,FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = "** Generic server "++NameStr++" terminating \n"
+ "** Last message in was ",
+ ct:log("Str2: ~ts",[Str2]),
+ ct:log("length(Str2): ~p",[L2]),
+ true = lists:prefix(Expected2,Str2),
+ true = L2<L1,
+
+ WStr2 = flatten_format_log(Warning,FormatOpts2),
+ WL2 = length(WStr2),
+ WExpected2 = "** Undefined handle_info in gen_server_SUITE\n"
+ "** Unhandled message: ",
+ ct:log("WStr2: ~ts",[WStr2]),
+ ct:log("length(WStr2): ~p",[WL2]),
+ true = lists:prefix(WExpected2,WStr2),
+ true = WL2<WL1,
+
+ FormatOpts3 = #{chars_limit=>200},
+ Str3 = flatten_format_log(Report,FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = "** Generic server "++NameStr++" terminating \n"
+ "** Last message in was ",
+ ct:log("Str3: ~ts",[Str3]),
+ ct:log("length(Str3): ~p",[L3]),
+ true = lists:prefix(Expected3,Str3),
+ true = L3<L1,
+
+ WFormatOpts3 = #{chars_limit=>80},
+ WStr3 = flatten_format_log(Warning,WFormatOpts3),
+ WL3 = length(WStr3),
+ WExpected3 = "** Undefined handle_info in gen_server_SUITE\n"
+ "** Unhandled message: ",
+ ct:log("WStr3: ~ts",[WStr3]),
+ ct:log("length(WStr3): ~p",[WL3]),
+ true = lists:prefix(WExpected3,WStr3),
+ true = WL3<WL1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_format_log(Report,FormatOpts4),
+ L4 = length(Str4),
+ Expected4 = "Generic server "++NameStr++" terminating. Reason: ",
+ ct:log("Str4: ~ts",[Str4]),
+ ct:log("length(Str4): ~p",[L4]),
+ true = lists:prefix(Expected4,Str4),
+ true = L4<L1,
+
+ WStr4 = flatten_format_log(Warning,FormatOpts4),
+ WL4 = length(WStr4),
+ WExpected4 = "Undefined handle_info in gen_server_SUITE. "
+ "Unhandled message: ",
+ ct:log("WStr4: ~ts",[WStr4]),
+ ct:log("length(WStr4): ~p",[WL4]),
+ true = lists:prefix(WExpected4,WStr4),
+ true = WL4<WL1,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_format_log(Report,FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "Generic server "++NameStr++" terminating. Reason: ",
+ ct:log("Str5: ~ts",[Str5]),
+ ct:log("length(Str5): ~p",[L5]),
+ true = lists:prefix(Expected5,Str5),
+ true = L5<L4,
+
+ WStr5 = flatten_format_log(Warning,FormatOpts5),
+ WL5 = length(WStr5),
+ WExpected5 = "Undefined handle_info in gen_server_SUITE. "
+ "Unhandled message: ",
+ ct:log("WStr5: ~ts",[WStr5]),
+ ct:log("length(WStr5): ~p",[WL5]),
+ true = lists:prefix(WExpected5,WStr5),
+ true = WL5<WL4,
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>200},
+ Str6 = flatten_format_log(Report,FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "Generic server "++NameStr++" terminating. Reason: ",
+ ct:log("Str6: ~ts",[Str6]),
+ ct:log("length(Str6): ~p",[L6]),
+ true = lists:prefix(Expected6,Str6),
+ true = L6<L4,
+
+ WFormatOpts6 = #{single_line=>true, chars_limit=>80},
+ WStr6 = flatten_format_log(Warning,WFormatOpts6),
+ WL6 = length(WStr6),
+ WExpected6 = "Undefined handle_info in gen_server_SUITE. "
+ "Unhandled message: ",
+ ct:log("WStr6: ~ts",[WStr6]),
+ ct:log("length(WStr6): ~p",[WL6]),
+ true = lists:prefix(WExpected6,WStr6),
+ true = WL6<WL4,
+
+ ok.
+
+flatten_format_log(Report, Format) ->
+ lists:flatten(gen_server:format_log(Report, Format)).
+
%%--------------------------------------------------------------
%% Help functions to spec_init_*
start_link(Init, Options) ->
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 296973370c..76dee868e9 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -42,7 +42,7 @@ all() ->
event_types, generic_timers, code_change,
{group, sys},
hibernate, auto_hibernate, enter_loop, {group, undef_callbacks},
- undef_in_terminate].
+ undef_in_terminate, {group, format_log}].
groups() ->
[{start, [], tcs(start)},
@@ -53,7 +53,8 @@ groups() ->
{abnormal_handle_event, [], tcs(abnormal)},
{sys, [], tcs(sys)},
{sys_handle_event, [], tcs(sys)},
- {undef_callbacks, [], tcs(undef_callbacks)}].
+ {undef_callbacks, [], tcs(undef_callbacks)},
+ {format_log, [], tcs(format_log)}].
tcs(start) ->
[start1, start2, start3, start4, start5, start6, start7,
@@ -69,7 +70,9 @@ tcs(sys) ->
get_state, replace_state];
tcs(undef_callbacks) ->
[undef_code_change, undef_terminate1, undef_terminate2,
- pop_too_many].
+ pop_too_many];
+tcs(format_log) ->
+ [format_log_1, format_log_2].
init_per_suite(Config) ->
Config.
@@ -140,8 +143,18 @@ start1(Config) ->
%% ?EXPECT_FAILURE(gen_statem:call(Pid0, hej), Reason),
%%process_flag(trap_exit, OldFl),
- ok = verify_empty_msgq().
+ ok = verify_empty_msgq(),
+ {ok,{Pid1,Mon1}} = gen_statem:start_monitor(?MODULE, start_arg(Config, []), []),
+ ok = do_func_test(Pid1),
+ ok = do_sync_func_test(Pid1),
+ stop_it(Pid1),
+ receive
+ {'DOWN', Mon1, process, Pid1, _Reason} ->
+ ok
+ end,
+ ok = verify_empty_msgq().
+
%% anonymous w. shutdown
start2(Config) ->
%% Dont link when shutdown
@@ -197,7 +210,7 @@ start6(Config) ->
ok = verify_empty_msgq().
-%% global register linked
+%% global register linked & monitored
start7(Config) ->
STM = {global,my_stm},
@@ -207,6 +220,8 @@ start7(Config) ->
gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []),
{error,{already_started,Pid}} =
gen_statem:start(STM, ?MODULE, start_arg(Config, []), []),
+ {error,{already_started,Pid}} =
+ gen_statem:start_monitor(STM, ?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid),
ok = do_sync_func_test(Pid),
@@ -214,6 +229,28 @@ start7(Config) ->
ok = do_sync_func_test(STM),
stop_it(STM),
+ ok = verify_empty_msgq(),
+
+ {ok,{Pid1,Mon1}} =
+ gen_statem:start_monitor(STM, ?MODULE, start_arg(Config, []), []),
+ {error,{already_started,Pid1}} =
+ gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []),
+ {error,{already_started,Pid1}} =
+ gen_statem:start(STM, ?MODULE, start_arg(Config, []), []),
+ {error,{already_started,Pid1}} =
+ gen_statem:start_monitor(STM, ?MODULE, start_arg(Config, []), []),
+
+ ok = do_func_test(Pid1),
+ ok = do_sync_func_test(Pid1),
+ ok = do_func_test(STM),
+ ok = do_sync_func_test(STM),
+ stop_it(STM),
+
+ receive
+ {'DOWN', Mon1, process, Pid1, _Reason} ->
+ ok
+ end,
+
ok = verify_empty_msgq().
@@ -237,7 +274,7 @@ start8(Config) ->
%%process_flag(trap_exit, OldFl),
ok = verify_empty_msgq().
-%% local register linked
+%% local register linked & monitored
start9(Config) ->
%%OldFl = process_flag(trap_exit, true),
Name = my_stm,
@@ -255,6 +292,24 @@ start9(Config) ->
stop_it(Pid),
%%process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq(),
+
+ {ok,{Pid1,Mon1}} =
+ gen_statem:start_monitor(STM, ?MODULE, start_arg(Config, []), []),
+ {error,{already_started,Pid1}} =
+ gen_statem:start_monitor(STM, ?MODULE, start_arg(Config, []), []),
+
+ ok = do_func_test(Pid1),
+ ok = do_sync_func_test(Pid1),
+ ok = do_func_test(Name),
+ ok = do_sync_func_test(Name),
+ stop_it(Pid1),
+
+ receive
+ {'DOWN', Mon1, process, Pid1, _Reason} ->
+ ok
+ end,
+
ok = verify_empty_msgq().
%% global register
@@ -398,19 +453,24 @@ stop7(Config) ->
stop8(Config) ->
Node = gen_statem_stop8,
{ok,NodeName} = ct_slave:start(Node),
- Dir = filename:dirname(code:which(?MODULE)),
- rpc:call(NodeName, code, add_path, [Dir]),
- {ok,Pid} =
- rpc:call(
- NodeName, gen_statem,start,
- [?MODULE,start_arg(Config, []),[]]),
- ok = gen_statem:stop(Pid),
- false = rpc:call(NodeName, erlang, is_process_alive, [Pid]),
- noproc =
- ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1),
- {ok,NodeName} = ct_slave:stop(Node),
+ Statem =
+ try
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:block_call(NodeName, code, add_path, [Dir]),
+ {ok,Pid} =
+ rpc:block_call(
+ NodeName, gen_statem,start,
+ [?MODULE,start_arg(Config, []),[]]),
+ ok = gen_statem:stop(Pid),
+ false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1),
+ Pid
+ after
+ {ok,NodeName} = ct_slave:stop(Node)
+ end,
{{nodedown,NodeName},{sys,terminate,_}} =
- ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2),
+ ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2),
ok.
%% Registered name on remote node
@@ -419,21 +479,26 @@ stop9(Config) ->
LocalSTM = {local,Name},
Node = gen_statem__stop9,
{ok,NodeName} = ct_slave:start(Node),
- STM = {Name,NodeName},
- Dir = filename:dirname(code:which(?MODULE)),
- rpc:call(NodeName, code, add_path, [Dir]),
- {ok,Pid} =
- rpc:call(
- NodeName, gen_statem, start,
- [LocalSTM,?MODULE,start_arg(Config, []),[]]),
- ok = gen_statem:stop(STM),
- undefined = rpc:call(NodeName,erlang,whereis,[Name]),
- false = rpc:call(NodeName,erlang,is_process_alive,[Pid]),
- noproc =
- ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
- {ok,NodeName} = ct_slave:stop(Node),
+ Statem =
+ try
+ STM = {Name,NodeName},
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:block_call(NodeName, code, add_path, [Dir]),
+ {ok,Pid} =
+ rpc:block_call(
+ NodeName, gen_statem, start,
+ [LocalSTM,?MODULE,start_arg(Config, []),[]]),
+ ok = gen_statem:stop(STM),
+ undefined = rpc:block_call(NodeName,erlang,whereis,[Name]),
+ false = rpc:block_call(NodeName,erlang,is_process_alive,[Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
+ STM
+ after
+ {ok,NodeName} = ct_slave:stop(Node)
+ end,
{{nodedown,NodeName},{sys,terminate,_}} =
- ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2),
+ ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2),
ok.
%% Globally registered name on remote node
@@ -441,18 +506,21 @@ stop10(Config) ->
Node = gen_statem_stop10,
STM = {global,to_stop},
{ok,NodeName} = ct_slave:start(Node),
- Dir = filename:dirname(code:which(?MODULE)),
- rpc:call(NodeName,code,add_path,[Dir]),
- {ok,Pid} =
- rpc:call(
- NodeName, gen_statem, start,
- [STM,?MODULE,start_arg(Config, []),[]]),
- global:sync(),
- ok = gen_statem:stop(STM),
- false = rpc:call(NodeName, erlang, is_process_alive, [Pid]),
- noproc =
- ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
- {ok,NodeName} = ct_slave:stop(Node),
+ try
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:block_call(NodeName,code,add_path,[Dir]),
+ {ok,Pid} =
+ rpc:block_call(
+ NodeName, gen_statem, start,
+ [STM,?MODULE,start_arg(Config, []),[]]),
+ global:sync(),
+ ok = gen_statem:stop(STM),
+ false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1)
+ after
+ {ok,NodeName} = ct_slave:stop(Node)
+ end,
noproc =
?EXPECT_FAILURE(gen_statem:stop(STM), Reason2),
ok.
@@ -466,10 +534,10 @@ abnormal1(Config) ->
gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []),
%% timeout call.
- delayed = gen_statem:call(Name, {delayed_answer,1}, 100),
+ delayed = gen_statem:call(Name, {delayed_answer,100}, 2000),
{timeout,_} =
?EXPECT_FAILURE(
- gen_statem:call(Name, {delayed_answer,1000}, 10),
+ gen_statem:call(Name, {delayed_answer,2000}, 100),
Reason),
ok = gen_statem:stop(Name),
?t:sleep(1100),
@@ -1369,7 +1437,7 @@ hibernate(Config) ->
{ok,Pid0} =
gen_statem:start_link(
?MODULE, start_arg(Config, hiber_now), []),
- is_in_erlang_hibernate(Pid0),
+ wait_erlang_hibernate(Pid0),
stop_it(Pid0),
receive
{'EXIT',Pid0,normal} -> ok
@@ -1382,23 +1450,23 @@ hibernate(Config) ->
true = ({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid,current_function)),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
please_just_five_more = gen_statem:call(Pid, snooze_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, hibernate_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, wakeup_async),
is_not_in_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, hibernate_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, snooze_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, wakeup_async),
is_not_in_erlang_hibernate(Pid),
@@ -1406,14 +1474,14 @@ hibernate(Config) ->
true =
({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid, current_function)),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
'alive!' = gen_statem:call(Pid, 'alive?'),
true =
({current_function,{erlang,hibernate,3}} =/=
erlang:process_info(Pid, current_function)),
Pid ! hibernate_now,
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
'alive!' = gen_statem:call(Pid, 'alive?'),
true =
@@ -1421,34 +1489,34 @@ hibernate(Config) ->
erlang:process_info(Pid, current_function)),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
please_just_five_more = gen_statem:call(Pid, snooze_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, hibernate_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, wakeup_async),
is_not_in_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, hibernate_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, snooze_async),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
ok = gen_statem:cast(Pid, wakeup_async),
is_not_in_erlang_hibernate(Pid),
hibernating = gen_statem:call(Pid, hibernate_sync),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
sys:suspend(Pid),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
sys:resume(Pid),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
receive after 1000 -> ok end,
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
good_morning = gen_statem:call(Pid, wakeup_sync),
is_not_in_erlang_hibernate(Pid),
@@ -1464,15 +1532,16 @@ hibernate(Config) ->
%% Auto-hibernation timeout
auto_hibernate(Config) ->
OldFl = process_flag(trap_exit, true),
- HibernateAfterTimeout = 100,
+ HibernateAfterTimeout = 1000,
{ok,Pid} =
gen_statem:start_link(
- ?MODULE, start_arg(Config, []), [{hibernate_after, HibernateAfterTimeout}]),
+ ?MODULE, start_arg(Config, []),
+ [{hibernate_after, HibernateAfterTimeout}]),
%% After init test
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% After info test
Pid ! {hping, self()},
receive
@@ -1483,7 +1552,7 @@ auto_hibernate(Config) ->
end,
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% After cast test
ok = gen_statem:cast(Pid, {hping, self()}),
receive
@@ -1494,42 +1563,42 @@ auto_hibernate(Config) ->
end,
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% After call test
hpong = gen_statem:call(Pid, hping),
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% Timer test 1
- TimerTimeout1 = 50,
- ok = gen_statem:call(Pid, {arm_htimer, self(), TimerTimeout1}),
+ TimerTimeout1 = HibernateAfterTimeout div 2,
+ ok = gen_statem:call(Pid, {start_htimer, self(), TimerTimeout1}),
is_not_in_erlang_hibernate(Pid),
timer:sleep(TimerTimeout1),
is_not_in_erlang_hibernate(Pid),
receive
- {Pid, htimer_armed} ->
+ {Pid, htimer_timeout} ->
ok
after 1000 ->
ct:fail(timer1)
end,
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
%% Timer test 2
- TimerTimeout2 = 150,
- ok = gen_statem:call(Pid, {arm_htimer, self(), TimerTimeout2}),
+ TimerTimeout2 = HibernateAfterTimeout * 2,
+ ok = gen_statem:call(Pid, {start_htimer, self(), TimerTimeout2}),
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
receive
- {Pid, htimer_armed} ->
+ {Pid, htimer_timeout} ->
ok
- after 1000 ->
+ after TimerTimeout2 ->
ct:fail(timer2)
end,
is_not_in_erlang_hibernate(Pid),
timer:sleep(HibernateAfterTimeout),
- is_in_erlang_hibernate(Pid),
+ wait_erlang_hibernate(Pid),
stop_it(Pid),
process_flag(trap_exit, OldFl),
receive
@@ -1539,38 +1608,38 @@ auto_hibernate(Config) ->
end,
ok = verify_empty_msgq().
-is_in_erlang_hibernate(Pid) ->
+
+wait_erlang_hibernate(Pid) ->
receive after 1 -> ok end,
- is_in_erlang_hibernate_1(200, Pid).
+ wait_erlang_hibernate_1(200, Pid).
-is_in_erlang_hibernate_1(0, Pid) ->
+wait_erlang_hibernate_1(0, Pid) ->
ct:log("~p\n", [erlang:process_info(Pid, current_function)]),
- ct:fail(not_in_erlang_hibernate_3);
-is_in_erlang_hibernate_1(N, Pid) ->
+ ct:fail(should_be_in_erlang_hibernate_3);
+wait_erlang_hibernate_1(N, Pid) ->
{current_function,MFA} = erlang:process_info(Pid, current_function),
case MFA of
{erlang,hibernate,3} ->
ok;
_ ->
receive after 10 -> ok end,
- is_in_erlang_hibernate_1(N-1, Pid)
+ wait_erlang_hibernate_1(N-1, Pid)
end.
is_not_in_erlang_hibernate(Pid) ->
receive after 1 -> ok end,
is_not_in_erlang_hibernate_1(200, Pid).
-is_not_in_erlang_hibernate_1(0, Pid) ->
- ct:log("~p\n", [erlang:process_info(Pid, current_function)]),
- ct:fail(not_in_erlang_hibernate_3);
+is_not_in_erlang_hibernate_1(0, _Pid) ->
+ ct:fail(should_not_be_in_erlang_hibernate_3);
is_not_in_erlang_hibernate_1(N, Pid) ->
{current_function,MFA} = erlang:process_info(Pid, current_function),
case MFA of
- {erlang,hibernate,3} ->
+ {erlang,hibernate,3} ->
receive after 10 -> ok end,
is_not_in_erlang_hibernate_1(N-1, Pid);
- _ ->
- ok
+ _ ->
+ ok
end.
@@ -1836,6 +1905,315 @@ next_events(Config) ->
?EXPECT_FAILURE(gen_statem:stop(Pid), Reason).
+%% Test report callback for Logger handler error_logger
+format_log_1(_Config) ->
+ FD = application:get_env(kernel, error_logger_format_depth),
+ application:unset_env(kernel, error_logger_format_depth),
+ Term = lists:seq(1,15),
+ Name = self(),
+ Reason = {bad_reply_action_from_state_function,[]},
+ Report1 = simple_report(Name, Term, Reason),
+ Report2 = elaborate_report(Name, Term, Reason),
+
+ {F1,A1} = gen_statem:format_log(Report1),
+ ct:log("F1: ~ts~nA1: ~tp",[F1,A1]),
+ FExpected1 = "** State machine ~tp terminating~n"
+ "** When server state = ~tp~n"
+ "** Reason for termination = ~tp:~tp~n"
+ "** Callback modules = ~tp~n"
+ "** Callback mode = ~tp~n",
+ FExpected1 = F1,
+ [Name,Term,error,Reason,[?MODULE],state_functions] = A1,
+
+ {F3,A3} = gen_statem:format_log(Report2),
+ ct:log("F3: ~ts~nA3: ~tp",[F3,A3]),
+ FExpected3 = "** State machine ~tp terminating~n"
+ "** Last event = ~tp~n"
+ "** When server state = ~tp~n"
+ "** Reason for termination = ~tp:~tp~n"
+ "** Callback modules = ~tp~n"
+ "** Callback mode = ~tp~n"
+ "** Queued = ~tp~n"
+ "** Postponed = ~tp~n"
+ "** Stacktrace =~n** ~tp~n"
+ "** Time-outs: ~tp~n"
+ "** Log =~n** ~tp~n"
+ "** Client ~tp stacktrace~n"
+ "** ~tp~n",
+ FExpected3 = F3,
+ Stacktrace = stacktrace(),
+ [Name,Term,Term,error,Reason,[?MODULE],[state_functions,state_enter],[Term],
+ [{internal,Term}],Stacktrace,{1,[{timeout,message}]},[Term],Name,[]] = A3,
+
+ Depth = 10,
+ ok = application:set_env(kernel, error_logger_format_depth, Depth),
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ {F2,A2} = gen_statem:format_log(Report1),
+ ct:log("F2: ~ts~nA2: ~tp",[F2,A2]),
+ FExpected2 = "** State machine ~tP terminating~n"
+ "** When server state = ~tP~n"
+ "** Reason for termination = ~tP:~tP~n"
+ "** Callback modules = ~tP~n"
+ "** Callback mode = ~tP~n",
+ FExpected2 = F2,
+ [Name,Depth,Limited,Depth,error,Depth,Reason,Depth,
+ [?MODULE],Depth,state_functions,Depth] = A2,
+
+ {F4,A4} = gen_statem:format_log(Report2),
+ ct:log("F4: ~ts~nA4: ~tp",[F4,A4]),
+ FExpected4 = "** State machine ~tP terminating~n"
+ "** Last event = ~tP~n"
+ "** When server state = ~tP~n"
+ "** Reason for termination = ~tP:~tP~n"
+ "** Callback modules = ~tP~n"
+ "** Callback mode = ~tP~n"
+ "** Queued = ~tP~n"
+ "** Postponed = ~tP~n"
+ "** Stacktrace =~n** ~tP~n"
+ "** Time-outs: ~tP~n"
+ "** Log =~n** ~tP~n"
+ "** Client ~tP stacktrace~n"
+ "** ~tP~n",
+ FExpected4 = F4,
+ LimitedPostponed = [{internal,[1,2,3,4,5,6,'...']}],
+ LimitedStacktrace = io_lib:limit_term(Stacktrace, Depth),
+ LimitedQueue = io_lib:limit_term([Term], Depth),
+ [Name,Depth,Limited,Depth,Limited,Depth,error,Depth,Reason,Depth,
+ [?MODULE],Depth,[state_functions,state_enter],Depth,LimitedQueue,Depth,
+ LimitedPostponed,Depth,LimitedStacktrace,Depth,{1,[{timeout,message}]},
+ Depth,[Limited],Depth,Name,Depth,[],Depth] = A4,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel, error_logger_format_depth);
+ _ ->
+ application:set_env(kernel, error_logger_format_depth, FD)
+ end,
+ ok.
+
+%% Test report callback for any Logger handler
+format_log_2(_Config) ->
+ format_log_2_simple(),
+ format_log_2_elaborate(),
+ ok.
+
+format_log_2_simple() ->
+ FD = application:get_env(kernel, error_logger_format_depth),
+ application:unset_env(kernel, error_logger_format_depth),
+
+ Term = lists:seq(1,15),
+ Name = self(),
+ NameStr = pid_to_list(Name),
+ Reason = {bad_reply_action_from_state_function,[]},
+ Report = simple_report(Name, Term, Reason),
+
+ FormatOpts1 = #{},
+ Str1 = flatten_format_log(Report, FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = "** State machine " ++ NameStr ++ " terminating\n"
+ "** When server state = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ "** Reason for termination = "
+ "error:{bad_reply_action_from_state_function,[]}\n"
+ "** Callback modules = ["?MODULE_STRING"]\n"
+ "** Callback mode = state_functions\n",
+ ct:log("Str1: ~ts", [Str1]),
+ ct:log("length(Str1): ~p", [L1]),
+ Expected1 = Str1,
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_format_log(Report, FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = "** State machine " ++ NameStr ++ " terminating\n"
+ "** When server state = [1,2,3,4,5,6,7,8,9|...]\n"
+ "** Reason for termination = "
+ "error:{bad_reply_action_from_state_function,[]}\n"
+ "** Callback modules = ["?MODULE_STRING"]\n"
+ "** Callback mode = state_functions\n",
+ ct:log("Str2: ~ts", [Str2]),
+ ct:log("length(Str2): ~p", [L2]),
+ true = Expected2 =:= Str2,
+
+ FormatOpts3 = #{chars_limit=>200},
+ Str3 = flatten_format_log(Report, FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = "** State machine " ++ NameStr ++ " terminating\n"
+ "** When server state = [",
+ ct:log("Str3: ~ts", [Str3]),
+ ct:log("length(Str3): ~p", [L3]),
+ true = lists:prefix(Expected3, Str3),
+ true = L3 < L1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_format_log(Report, FormatOpts4),
+ L4 = length(Str4),
+ Expected4 = "State machine " ++ NameStr ++ " terminating. "
+ "Reason: {bad_reply_action_from_state_function,[]}. "
+ "State: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].",
+ ct:log("Str4: ~ts", [Str4]),
+ ct:log("length(Str4): ~p", [L4]),
+ Expected4 = Str4,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_format_log(Report, FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "State machine " ++ NameStr ++ " terminating. "
+ "Reason: {bad_reply_action_from_state_function,[]}. "
+ "State: [1,2,3,4,5,6,7,8,9|...].",
+ ct:log("Str5: ~ts", [Str5]),
+ ct:log("length(Str5): ~p", [L5]),
+ Expected5 = Str5,
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>100},
+ Str6 = flatten_format_log(Report, FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "State machine " ++ NameStr ++ " terminating. "
+ "Reason: ",
+ ct:log("Str6: ~ts", [Str6]),
+ ct:log("length(Str6): ~p", [L6]),
+ true = lists:prefix(Expected6, Str6),
+ true = L6 < L4,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel, error_logger_format_depth);
+ _ ->
+ application:set_env(kernel, error_logger_format_depth, FD)
+ end,
+ ok.
+
+format_log_2_elaborate() ->
+ FD = application:get_env(kernel, error_logger_format_depth),
+ application:unset_env(kernel, error_logger_format_depth),
+
+ Term = lists:seq(1,15),
+ Name = self(),
+ NameStr = pid_to_list(Name),
+ Reason = {bad_reply_action_from_state_function,[]},
+ Report = elaborate_report(Name, Term, Reason),
+ FormatOpts1 = #{},
+ Str1 = flatten_format_log(Report, FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = "** State machine " ++ NameStr ++ " terminating\n"
+ "** Last event = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n",
+ ct:log("Str1: ~ts", [Str1]),
+ ct:log("length(Str1): ~p", [L1]),
+ true = lists:prefix(Expected1, Str1),
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_format_log(Report, FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = "** State machine " ++ NameStr ++ " terminating\n"
+ "** Last event = [1,2,3,4,5,6,7,8,9|...]\n"
+ "** When server state = [1,2,3,4,5,6,7,8,9|...]\n"
+ "** Reason for termination = "
+ "error:{bad_reply_action_from_state_function,[]}\n"
+ "** Callback modules = ["?MODULE_STRING"]\n"
+ "** Callback mode = [state_functions,state_enter]\n"
+ "** Queued = [[1,2,3,4,5,6,7,8|...]]\n"
+ "** Postponed = [{internal,[1,2,3,4,5,6|...]}]\n"
+ "** Stacktrace =\n"
+ "** [{m,f,1,[1,2,3,4|...]}]\n"
+ "** Time-outs: {1,[{timeout,message}]}\n"
+ "** Log =\n"
+ "** [[1,2,3,4,5,6,7,8|...]]\n"
+ "** Client "++NameStr ++ " stacktrace\n"
+ "** []\n",
+ ct:log("Str2: ~ts", [Str2]),
+ ct:log("length(Str2): ~p", [L2]),
+ Expected2 = Str2,
+
+ FormatOpts3 = #{chars_limit=>300},
+ Str3 = flatten_format_log(Report, FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = "** State machine " ++ NameStr ++ " terminating\n"
+ "** Last event = ",
+ ct:log("Str3: ~ts", [Str3]),
+ ct:log("length(Str3): ~p", [L3]),
+ true = lists:prefix(Expected3, Str3),
+ true = L3 < L1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_format_log(Report, FormatOpts4),
+ L4 = length(Str4),
+ Expected4 = "State machine " ++ NameStr ++ " terminating. "
+ "Reason: {bad_reply_action_from_state_function,[]}. "
+ "Stack: [{m,f,1,[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]}]. "
+ "Last event: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "State: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "Log: [[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]]. "
+ "Client " ++ NameStr ++ " stacktrace: [].",
+ ct:log("Str4: ~ts", [Str4]),
+ ct:log("length(Str4): ~p", [L4]),
+ Expected4 = Str4,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_format_log(Report, FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "State machine " ++ NameStr ++ " terminating. "
+ "Reason: {bad_reply_action_from_state_function,[]}. "
+ "Stack: [{m,f,1,[1,2,3,4|...]}]. "
+ "Last event: [1,2,3,4,5,6,7,8,9|...]. "
+ "State: [1,2,3,4,5,6,7,8,9|...]. "
+ "Log: [[1,2,3,4,5,6,7,8|...]]. "
+ "Client " ++ NameStr ++ " stacktrace: [].",
+ ct:log("Str5: ~ts", [Str5]),
+ ct:log("length(Str5): ~p", [L5]),
+ Expected5 = Str5,
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>300},
+ Str6 = flatten_format_log(Report, FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "State machine " ++ NameStr ++ " terminating. "
+ "Reason:",
+ ct:log("Str6: ~ts", [Str6]),
+ ct:log("length(Str6): ~p", [L6]),
+ true = lists:prefix(Expected6, Str6),
+ true = L6 < L4,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel, error_logger_format_depth);
+ _ ->
+ application:set_env(kernel, error_logger_format_depth, FD)
+ end,
+ ok.
+
+simple_report(Name, Term, Reason) ->
+ #{label=>{gen_statem,terminate},
+ name=>Name,
+ queue=>[],
+ postponed=>[],
+ modules=>[?MODULE],
+ callback_mode=>state_functions,
+ state_enter=>false,
+ state=>Term,
+ timeouts=>{0,[]},
+ log=>[],
+ reason=>{error,Reason,[]},
+ client_info=>undefined}.
+
+elaborate_report(Name, Term, Reason) ->
+ #{label=>{gen_statem,terminate},
+ name=>Name,
+ queue=>[Term,Term],
+ postponed=>[{internal,Term}],
+ modules=>[?MODULE],
+ callback_mode=>state_functions,
+ state_enter=>true,
+ state=>Term,
+ timeouts=>{1,[{timeout,message}]},
+ log=>[Term],
+ reason=>{error,Reason,stacktrace()},
+ client_info=>{self(),{self(),[]}}}.
+
+stacktrace() ->
+ [{m,f,1,lists:seq(1, 15)}].
+
+flatten_format_log(Report, Format) ->
+ lists:flatten(gen_statem:format_log(Report, Format)).
+
%%
%% Functionality check
%%
@@ -1871,7 +2249,17 @@ do_func_test(STM) ->
wfor(yes),
ok = do_disconnect(STM),
ok = gen_statem:cast(STM, {'alive?',self()}),
+ P0 = gen_statem:send_request(STM, 'alive?'),
+ timeout = gen_statem:wait_response(P0, 0),
wfor(yes),
+ {reply, yes} = gen_statem:wait_response(P0, infinity),
+ _ = flush(),
+ P1 = gen_statem:send_request(STM, 'alive?'),
+ receive Msg ->
+ no_reply = gen_statem:check_response(Msg, P0),
+ {reply, yes} = gen_statem:check_response(Msg, P1)
+ after 1000 -> exit(timeout)
+ end,
ok.
@@ -1966,13 +2354,13 @@ init(stop_shutdown) ->
{stop,shutdown};
init(sleep) ->
?t:sleep(1000),
- {ok,idle,data};
+ init_sup({ok,idle,data});
init(hiber) ->
- {ok,hiber_idle,[]};
+ init_sup({ok,hiber_idle,[]});
init(hiber_now) ->
- {ok,hiber_idle,[],[hibernate]};
+ init_sup({ok,hiber_idle,[],[hibernate]});
init({data, Data}) ->
- {ok,idle,Data};
+ init_sup({ok,idle,Data});
init({callback_mode,CallbackMode,Arg}) ->
ets:new(?MODULE, [named_table,private]),
ets:insert(?MODULE, {callback_mode,CallbackMode}),
@@ -1982,14 +2370,35 @@ init({map_statem,#{init := Init}=Machine,Modes}) ->
ets:insert(?MODULE, {callback_mode,[handle_event_function|Modes]}),
case Init() of
{ok,State,Data,Ops} ->
- {ok,State,[Data|Machine],Ops};
+ init_sup({ok,State,[Data|Machine],Ops});
{ok,State,Data} ->
- {ok,State,[Data|Machine]};
+ init_sup({ok,State,[Data|Machine]});
Other ->
- Other
+ init_sup(Other)
end;
init([]) ->
- {ok,idle,data}.
+ init_sup({ok,idle,data}).
+
+%% Supervise state machine parent i.e the test case, and if it dies
+%% (fails due to some reason), kill the state machine,
+%% just to not leak resources (process, name, ETS table, etc...)
+%%
+init_sup(Result) ->
+ Parent = gen:get_parent(),
+ Statem = self(),
+ _Supervisor =
+ spawn(
+ fun () ->
+ StatemRef = monitor(process, Statem),
+ ParentRef = monitor(process, Parent),
+ receive
+ {'DOWN', StatemRef, _, _, Reason} ->
+ exit(Reason);
+ {'DOWN', ParentRef, _, _, _} ->
+ exit(Statem, kill)
+ end
+ end),
+ Result.
callback_mode() ->
try ets:lookup(?MODULE, callback_mode) of
@@ -2022,10 +2431,10 @@ idle(cast, {hping,Pid}, Data) ->
{keep_state, Data};
idle({call, From}, hping, _Data) ->
{keep_state_and_data, [{reply, From, hpong}]};
-idle({call, From}, {arm_htimer, Pid, Timeout}, _Data) ->
- {keep_state_and_data, [{reply, From, ok}, {timeout, Timeout, {arm_htimer, Pid}}]};
-idle(timeout, {arm_htimer, Pid}, _Data) ->
- Pid ! {self(), htimer_armed},
+idle({call, From}, {start_htimer, Pid, Timeout}, _Data) ->
+ {keep_state_and_data, [{reply, From, ok}, {timeout, Timeout, {htimer, Pid}}]};
+idle(timeout, {htimer, Pid}, _Data) ->
+ Pid ! {self(), htimer_timeout},
keep_state_and_data;
idle(cast, {connect,Pid}, Data) ->
Pid ! accept,
diff --git a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
index 1bcd08867f..1de7de527b 100644
--- a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
+++ b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
@@ -31,6 +31,24 @@ start(Opts) ->
gen_statem:start({local, ?MODULE}, ?MODULE, [], Opts).
init([]) ->
+ %% Supervise state machine parent i.e the test case, and if it dies
+ %% (fails due to some reason), kill the state machine,
+ %% just to not leak resources (process, name, ETS table, etc...)
+ %%
+ Parent = gen:get_parent(),
+ Statem = self(),
+ _Supervisor =
+ spawn(
+ fun () ->
+ StatemRef = monitor(process, Statem),
+ ParentRef = monitor(process, Parent),
+ receive
+ {'DOWN', StatemRef, _, _, Reason} ->
+ exit(Reason);
+ {'DOWN', ParentRef, _, _, _} ->
+ exit(Statem, kill)
+ end
+ end),
{ok, start, #{}}.
callback_mode() ->
diff --git a/lib/stdlib/test/io_proto_SUITE.erl b/lib/stdlib/test/io_proto_SUITE.erl
index e497b2fb5d..df6958cfa9 100644
--- a/lib/stdlib/test/io_proto_SUITE.erl
+++ b/lib/stdlib/test/io_proto_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1568,10 +1568,6 @@ request({put_chars, Encoding, Chars}, State) ->
request({put_chars, Encoding, Module, Function, Args}, State) ->
{ok, ok, State#state{q=[{put_chars, Encoding, Module, Function, Args} |
State#state.q ]}};
-request({put_chars,Chars}, State) ->
- {ok, ok, State#state{q=[{put_chars, Chars} | State#state.q ]}};
-request({put_chars,M,F,As}, State) ->
- {ok, ok, State#state{q=[{put_chars, M,F,As} | State#state.q ]}};
request({get_until, Encoding, Prompt, M, F, As}, State) ->
{ok, convert(State#state.nxt, Encoding, State#state.mode), State#state{nxt = eof, q = [{get_until, Encoding, Prompt, M, F, As} | State#state.q]}};
request({get_chars, Encoding, Prompt, N}, State) ->
@@ -1583,20 +1579,6 @@ request({get_line, Encoding, Prompt}, State) ->
State#state{nxt = eof,
q = [{get_line, Encoding, Prompt} |
State#state.q]}};
-request({get_until, Prompt, M, F, As}, State) ->
- {ok, convert(State#state.nxt, latin1, State#state.mode),
- State#state{nxt = eof,
- q = [{get_until, Prompt, M, F, As} | State#state.q]}};
-request({get_chars, Prompt, N}, State) ->
- {ok, convert(State#state.nxt, latin1, State#state.mode),
- State#state{nxt = eof,
- q = [{get_chars, Prompt, N} |
- State#state.q]}};
-request({get_line, Prompt}, State) ->
- {ok, convert(State#state.nxt, latin1, State#state.mode),
- State#state{nxt = eof,
- q = [{get_line, Prompt} |
- State#state.q]}};
request({get_geomentry,_}, State) ->
{error, {error,enotsup}, State};
request({setopts, Opts}, State) when Opts =:= [{binary, false}]; Opts =:= [list] ->
diff --git a/lib/stdlib/test/lists_SUITE.erl b/lib/stdlib/test/lists_SUITE.erl
index 19dd755a7c..7a0ffee9e1 100644
--- a/lib/stdlib/test/lists_SUITE.erl
+++ b/lib/stdlib/test/lists_SUITE.erl
@@ -2229,6 +2229,7 @@ sublist_2_e(Config) when is_list(Config) ->
sublist_3(Config) when is_list(Config) ->
[] = lists:sublist([], 1, 0),
[] = lists:sublist([], 1, 1),
+ [] = lists:sublist([], 2, 0),
[] = lists:sublist([a], 1, 0),
[a] = lists:sublist([a], 1, 1),
[a] = lists:sublist([a], 1, 2),
@@ -2242,6 +2243,7 @@ sublist_3(Config) when is_list(Config) ->
[] = lists:sublist([a], 2, 1),
[] = lists:sublist([a], 2, 2),
[] = lists:sublist([a], 2, 79),
+ [] = lists:sublist([a], 3, 1),
[] = lists:sublist([a,b|c], 1, 0),
[] = lists:sublist([a,b|c], 2, 0),
[a] = lists:sublist([a,b|c], 1, 1),
diff --git a/lib/stdlib/test/maps_SUITE.erl b/lib/stdlib/test/maps_SUITE.erl
index 6f3cd8bf1b..0ad6989cbb 100644
--- a/lib/stdlib/test/maps_SUITE.erl
+++ b/lib/stdlib/test/maps_SUITE.erl
@@ -30,7 +30,7 @@
-export([t_update_with_3/1, t_update_with_4/1,
t_get_3/1, t_filter_2/1,
t_fold_3/1,t_map_2/1,t_size_1/1,
- t_iterator_1/1,
+ t_iterator_1/1, t_put_opt/1, t_merge_opt/1,
t_with_2/1,t_without_2/1]).
%%-define(badmap(V,F,Args), {'EXIT', {{badmap,V}, [{maps,F,Args,_}|_]}}).
@@ -48,7 +48,7 @@ all() ->
[t_update_with_3,t_update_with_4,
t_get_3,t_filter_2,
t_fold_3,t_map_2,t_size_1,
- t_iterator_1,
+ t_iterator_1,t_put_opt,t_merge_opt,
t_with_2,t_without_2].
t_update_with_3(Config) when is_list(Config) ->
@@ -204,6 +204,28 @@ iter_kv(I) ->
[{K,V} | iter_kv(NI)]
end.
+t_put_opt(Config) when is_list(Config) ->
+ Value = id(#{complex => map}),
+ Map = id(#{a => Value}),
+ true = erts_debug:same(maps:put(a, Value, Map), Map),
+ ok.
+
+t_merge_opt(Config) when is_list(Config) ->
+ Small = id(#{a => 1}),
+ true = erts_debug:same(maps:merge(#{}, Small), Small),
+ true = erts_debug:same(maps:merge(Small, #{}), Small),
+ true = erts_debug:same(maps:merge(Small, Small), Small),
+
+ Large = maps:from_list([{I,I}||I<-lists:seq(1,200)]),
+ true = erts_debug:same(maps:merge(#{}, Large), Large),
+ true = erts_debug:same(maps:merge(Large, #{}), Large),
+ true = erts_debug:same(maps:merge(Large, Large), Large),
+
+ List = id([a|b]),
+ ?badmap([a|b],merge,[[a|b],[a|b]]) = (catch maps:merge(List, List)),
+
+ ok.
+
t_size_1(Config) when is_list(Config) ->
0 = maps:size(#{}),
10 = maps:size(maps:from_list([{{"k",I},I}||I<-lists:seq(1,10)])),
@@ -215,8 +237,11 @@ t_size_1(Config) when is_list(Config) ->
600 = maps:size(maps:from_list([{{"k",I},I}||I<-lists:seq(1,600)])),
%% error case
- ?badmap(a,size,[a]) = (catch maps:size(id(a))),
- ?badmap(<<>>,size,[<<>>]) = (catch maps:size(id(<<>>))),
+ %%
+ %% Note that the stack trace is ignored because the compiler may have
+ %% rewritten maps:size/2 to map_size.
+ {'EXIT', {{badmap,a}, _}} = (catch maps:size(id(a))),
+ {'EXIT', {{badmap,<<>>}, _}} = (catch maps:size(id(<<>>))),
ok.
id(I) -> I.
diff --git a/lib/stdlib/test/proc_lib_SUITE.erl b/lib/stdlib/test/proc_lib_SUITE.erl
index 127b1317e4..b3673efb5a 100644
--- a/lib/stdlib/test/proc_lib_SUITE.erl
+++ b/lib/stdlib/test/proc_lib_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,8 +27,12 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
crash/1, stacktrace/1, sync_start_nolink/1, sync_start_link/1,
- spawn_opt/1, sp1/0, sp2/0, sp3/1, sp4/2, sp5/1, '\x{447}'/0,
- hibernate/1, stop/1, t_format/1, t_format_arbitrary/1]).
+ sync_start_monitor/1, sync_start_monitor_link/1,
+ sync_start_timeout/1, sync_start_link_timeout/1,
+ sync_start_monitor_link_timeout/1,
+ spawn_opt/1, sp1/0, sp2/0, sp3/1, sp4/2, sp5/1, sp6/1, sp7/1,
+ sp8/1, sp9/1, sp10/1,
+ '\x{447}'/0, hibernate/1, stop/1, t_format/1, t_format_arbitrary/1]).
-export([ otp_6345/1, init_dont_hang/1]).
-export([hib_loop/1, awaken/1]).
@@ -39,6 +43,8 @@
-export([otp_6345_init/1, init_dont_hang_init/1]).
+-export([report_cb/1, report_cb_chars_limit/1, log/2, rcb_tester/0]).
+
-export([system_terminate/4]).
-ifdef(STANDALONE).
@@ -51,11 +57,14 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[crash, stacktrace, {group, sync_start}, spawn_opt, hibernate,
- {group, tickets}, stop, t_format, t_format_arbitrary].
+ {group, tickets}, stop, t_format, t_format_arbitrary, report_cb].
groups() ->
[{tickets, [], [otp_6345, init_dont_hang]},
- {sync_start, [], [sync_start_nolink, sync_start_link]}].
+ {sync_start, [], [sync_start_nolink, sync_start_link,
+ sync_start_monitor, sync_start_monitor_link,
+ sync_start_timeout, sync_start_link_timeout,
+ sync_start_monitor_link_timeout]}].
init_per_suite(Config) ->
Config.
@@ -275,6 +284,84 @@ sync_start_link(Config) when is_list(Config) ->
end,
ok.
+sync_start_monitor(Config) when is_list(Config) ->
+ _Pid = spawn_link(?MODULE, sp6, [self()]),
+ receive
+ {sync_started, _} -> ct:fail(async_start)
+ after 1000 -> ok
+ end,
+ receive
+ {Pid2, init} ->
+ Pid2 ! go_on
+ end,
+ receive
+ {sync_started, _} -> ok
+ after 1000 -> ct:fail(no_sync_start)
+ end,
+ receive received_down -> ok
+ after 2000 -> ct:fail(no_down)
+ end,
+ ok.
+
+sync_start_monitor_link(Config) when is_list(Config) ->
+ _Pid = spawn_link(?MODULE, sp7, [self()]),
+ receive
+ {sync_started, _} -> ct:fail(async_start)
+ after 1000 -> ok
+ end,
+ receive
+ {Pid2, init} ->
+ Pid2 ! go_on
+ end,
+ receive
+ {sync_started, _} -> ok
+ after 1000 -> ct:fail(no_sync_start)
+ end,
+ receive received_down -> ok
+ after 1000 -> ct:fail(no_down)
+ end,
+ receive received_exit -> ok
+ after 1000 -> ct:fail(no_exit)
+ end,
+ ok.
+
+sync_start_timeout(Config) when is_list(Config) ->
+ _Pid = spawn_link(?MODULE, sp8, [self()]),
+ receive done -> ok end,
+ receive {received_exit, _} = M1 -> ct:fail(M1)
+ after 0 -> ok
+ end,
+ receive {received_down, _} = M2 -> ct:fail(M2)
+ after 0 -> ok
+ end,
+ ok.
+
+sync_start_link_timeout(Config) when is_list(Config) ->
+ _Pid = spawn_link(?MODULE, sp9, [self()]),
+ receive done -> ok end,
+ receive {received_exit, _} = M1 -> ct:fail(M1)
+ after 0 -> ok
+ end,
+ receive {received_down, _} = M2 -> ct:fail(M2)
+ after 0 -> ok
+ end,
+ ok.
+
+sync_start_monitor_link_timeout(Config) when is_list(Config) ->
+ _Pid = spawn_link(?MODULE, sp10, [self()]),
+ receive done -> ok end,
+ receive {received_exit, _} = M1 -> ct:fail(M1)
+ after 0 -> ok
+ end,
+ receive
+ {received_down, R} ->
+ killed = R,
+ ok
+ after 0 -> ct:fail(no_down)
+ end,
+ ok.
+
+
spawn_opt(Config) when is_list(Config) ->
F = fun sp1/0,
{name,Fname} = erlang:fun_info(F, name),
@@ -313,6 +400,89 @@ sp5(Tester) ->
Pid = proc_lib:start(?MODULE, sp4, [self(), Tester]),
Tester ! {sync_started, Pid}.
+sp6(Tester) ->
+ process_flag(trap_exit, true),
+ {Pid, Mon} = proc_lib:start_monitor(?MODULE, sp4, [self(), Tester]),
+ Tester ! {sync_started, Pid},
+ receive
+ {'EXIT', Pid, normal} ->
+ exit(received_exit)
+ after 1000 ->
+ ok
+ end,
+ receive
+ {'DOWN', Mon, process, Pid, normal} ->
+ Tester ! received_down
+ end.
+
+sp7(Tester) ->
+ process_flag(trap_exit, true),
+ {Pid, Mon} = proc_lib:start_monitor(?MODULE, sp4, [self(), Tester], infinity, [link]),
+ Tester ! {sync_started, Pid},
+ receive
+ {'EXIT', Pid, normal} ->
+ Tester ! received_exit
+ end,
+ receive
+ {'DOWN', Mon, process, Pid, normal} ->
+ Tester ! received_down
+ end.
+
+sp8(Tester) ->
+ process_flag(trap_exit, true),
+ {error,timeout} = proc_lib:start(?MODULE, sp4, [self(), Tester], 500, [link]),
+ receive after 500 -> ok end,
+ receive
+ {'EXIT', _Pid1, Reason1} ->
+ Tester ! {received_exit, Reason1}
+ after 0 ->
+ ok
+ end,
+ receive
+ {'DOWN', _Mon2, process, _Pid2, Reason2} ->
+ Tester ! {received_down, Reason2}
+ after 0 ->
+ ok
+ end,
+ Tester ! done.
+
+sp9(Tester) ->
+ process_flag(trap_exit, true),
+ {error,timeout} = proc_lib:start_link(?MODULE, sp4, [self(), Tester], 500),
+ receive after 500 -> ok end,
+ receive
+ {'EXIT', _Pid1, Reason1} ->
+ Tester ! {received_exit, Reason1}
+ after 0 ->
+ ok
+ end,
+ receive
+ {'DOWN', _Mon, process, _Pid2, Reason2} ->
+ Tester ! {received_down, Reason2}
+ after 0 ->
+ ok
+ end,
+ Tester ! done.
+
+
+sp10(Tester) ->
+ process_flag(trap_exit, true),
+ {{error,timeout}, Mon} = proc_lib:start_monitor(?MODULE, sp4, [self(), Tester], 500, [link]),
+ receive after 500 -> ok end,
+ receive
+ {'EXIT', _Pid1, Reason1} ->
+ Tester ! {received_exit, Reason1}
+ after 0 ->
+ ok
+ end,
+ receive
+ {'DOWN', Mon, process, _Pid2, Reason2} ->
+ Tester ! {received_down, Reason2}
+ after 0 ->
+ ok
+ end,
+ Tester ! done.
+
sp4(Parent, Tester) ->
Tester ! {self(), init},
receive
@@ -421,10 +591,12 @@ hib_receive_messages(N) ->
%% 'monitor' spawn_opt option.
otp_6345(Config) when is_list(Config) ->
Opts = [link,monitor],
- {'EXIT', {badarg,[{proc_lib,check_for_monitor,_,_}|_Stack]}} =
- (catch proc_lib:start(?MODULE, otp_6345_init, [self()],
- 1000, Opts)),
- ok.
+ try
+ blupp = proc_lib:start(?MODULE, otp_6345_init, [self()],
+ 1000, Opts)
+ catch
+ error:badarg -> ok
+ end.
otp_6345_init(Parent) ->
proc_lib:init_ack(Parent, {ok, self()}),
@@ -628,6 +800,180 @@ do_test_format(Report, Encoding, Depth) ->
'\x{aaa}t_format_looper'()
end.
+%% Test report callback for any Logger handler
+report_cb(_Config) ->
+ ok = logger:add_handler(?MODULE,?MODULE,#{config=>self()}),
+ Pid = proc_lib:spawn(?MODULE, sp2, []),
+ ct:sleep(100),
+ {links,[NPid]} = process_info(Pid,links),
+ NPidStr = pid_to_list(NPid),
+ Pid ! die,
+ Report =
+ receive
+ {report,R} ->
+ R
+ after 5000 ->
+ ct:fail(no_report_received)
+ end,
+
+ Str1 = flatten_report_cb(Report,#{}),
+ L1 = length(Str1),
+ Expected1 = " crasher:\n initial call: proc_lib_SUITE:sp2/0\n",
+ ct:log("Str1: ~p",[Str1]),
+ ct:log("length(Str1): ~p",[L1]),
+ true = lists:prefix(Expected1,Str1),
+
+ FormatOpts1 = #{},
+ Str1 = flatten_report_cb(Report,FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = " crasher:\n initial call: proc_lib_SUITE:sp2/0\n",
+ ct:log("Str1: ~p",[Str1]),
+ ct:log("length(Str1): ~p",[L1]),
+ true = lists:prefix(Expected1,Str1),
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_report_cb(Report,FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = " crasher:\n initial call: proc_lib_SUITE:sp2/0\n",
+ ct:log("Str2: ~p",[Str2]),
+ ct:log("length(Str2): ~p",[L2]),
+ true = lists:prefix(Expected2,Str2),
+ true = L2<L1,
+
+ FormatOpts3 = #{chars_limit=>500},
+ Str3 = flatten_report_cb(Report,FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = " crasher:\n initial call: proc_lib_SUITE:sp2/0\n",
+ ct:log("Str3: ~p",[Str3]),
+ ct:log("length(Str3): ~p",[L3]),
+ true = lists:prefix(Expected3,Str3),
+ true = L3<L1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_report_cb(Report,FormatOpts4),
+ L4 = length(Str4),
+ Expected4 = "crasher: initial call: proc_lib_SUITE:sp2/0,",
+ ct:log("Str4: ~p",[Str4]),
+ ct:log("length(Str4): ~p",[L4]),
+ true = lists:prefix(Expected4,Str4),
+ true = L4<L1,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_report_cb(Report,FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "crasher: initial call: proc_lib_SUITE:sp2/0,",
+ ct:log("Str5: ~p",[Str5]),
+ ct:log("length(Str5): ~p",[L5]),
+ true = lists:prefix(Expected5,Str5),
+ true = L5<L4,
+ %% Check that neighbour information is printed
+ SplitFun = fun($;) -> false; (_) -> true end,
+ ExpectedNeighbours5 = "; neighbours: neighbour: pid: "++NPidStr++
+ ", registered_name: []",
+ true = lists:prefix(ExpectedNeighbours5,lists:dropwhile(SplitFun, Str5)),
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>500},
+ Str6 = flatten_report_cb(Report,FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "crasher: initial call: proc_lib_SUITE:sp2/0,",
+ ct:log("Str6: ~p",[Str6]),
+ ct:log("length(Str6): ~p",[L6]),
+ true = lists:prefix(Expected6,Str6),
+ true = L6<L4,
+ %% Check that only pid is printed for neighbour, due to chars_limit
+ ExpectedNeighbours6 = "; neighbours: ["++NPidStr++"]",
+ ExpectedNeighbours6 = lists:dropwhile(SplitFun, Str6),
+
+ ok = logger:remove_handler(?MODULE),
+ ok.
+
+report_cb_chars_limit(_Config) ->
+ %% This test does not really test anything, it just formats the
+ %% crash reports with different settings and prints the result. It
+ %% could be used as an example if report_cb was to be modified
+ %% for better utilization of the available number of characters
+ %% according to the chars_limit setting.
+ %%
+ %% Currently, multi-line formatting with chars_limit=1024 gives
+ %% a final report of 1696 character. The excess is due to the fact
+ %% that io_lib_pretty counts non-white characters--the indentation
+ %% of the formatted exception is not counted.
+ %%
+ %% Single-line formatting with chars_limit=1024 gives a final
+ %% report of 1104 characters.
+ %%
+ %% Single-line formatting a fake report with chars_limit=1024 gives
+ %% a final report of 1024 characters.
+
+ ok = logger:add_handler(?MODULE,?MODULE,#{config=>self()}),
+ Pid = proc_lib:spawn(?MODULE, rcb_tester, []),
+ ct:sleep(500),
+ Pid ! die,
+ Report =
+ receive
+ {report,R} ->
+ R
+ after 5000 ->
+ ct:fail(no_report_received)
+ end,
+
+ ct:sleep(500), % To separate debug calls to erlang:display(), if any.
+ Str1 = flatten_report_cb(Report,#{}),
+ L1 = length(Str1),
+ ct:log("Multi-line, no size limit:~n~s",[Str1]),
+ ct:log("Length, multi-line, no size limit: ~p",[L1]),
+
+ ct:sleep(500),
+ FormatOpts2 = #{chars_limit=>1024},
+ Str2 = flatten_report_cb(Report,FormatOpts2),
+ L2 = length(Str2),
+ ct:log("Multi-line, chars_limit=1024:~n~s",[Str2]),
+ ct:log("Length, multi-line, chars_limit=1024: ~p",[L2]),
+
+ ct:sleep(500),
+ FormatOpts3 = #{single_line=>true, chars_limit=>1024},
+ Str3 = flatten_report_cb(Report,FormatOpts3),
+ L3 = length(Str3),
+ ct:log("Single-line, chars_limit=1024:~n~s",[Str3]),
+ ct:log("Length, single-line, chars_limit=1024: ~p",[L3]),
+
+ ct:sleep(500),
+ Seq = lists:seq(1, 1000),
+ FakeReport = [[{fake_tag,Seq}],Seq],
+ FReport = #{label=>{proc_lib,crash}, report=>FakeReport},
+ Str4 = flatten_report_cb(FReport,FormatOpts3),
+ L4 = length(Str4),
+ ct:log("Fake: Single-line, chars_limit=1024:~n~s",[Str4]),
+ ct:log("Fake: Length, single-line, chars_limit=1024: ~p",[L4]),
+
+ ok = logger:remove_handler(?MODULE),
+ ok.
+
+rcb_tester() ->
+ L = lists:seq(1,255),
+ Term = [{some_data,#{pids=>processes(),
+ info=>process_info(self())}},
+ {tabs,lists:sort(ets:all())},
+ {bin,list_to_binary(L)},
+ {list,L}],
+
+ %% Put something in process dictionary
+ [put(K,V) ||{K,V} <- Term],
+
+ %% Add some messages
+ [self() ! {some_message,T} || T <- Term],
+
+ %% Create some neighbours
+ [_ = proc_lib:spawn_link(?MODULE,sp1,[]) || _ <- lists:seq(1,5)],
+
+ receive
+ die -> error({badmatch,Term})
+ end.
+
+flatten_report_cb(Report, Format) ->
+ lists:flatten(proc_lib:report_cb(Report, Format)).
+
%%-----------------------------------------------------------------
%% The error_logger handler used.
%%-----------------------------------------------------------------
@@ -648,3 +994,11 @@ handle_call(_Query, State) -> {ok, {error, bad_query}, State}.
terminate(_Reason, State) ->
State.
+
+%%-----------------------------------------------------------------
+%% The Logger handler used.
+%%-----------------------------------------------------------------
+log(#{msg:={report,Report}},#{config:=Pid}) ->
+ Pid ! {report,Report};
+log(_,_) ->
+ ok.
diff --git a/lib/stdlib/test/property_test/shell_docs_prop.erl b/lib/stdlib/test/property_test/shell_docs_prop.erl
new file mode 100644
index 0000000000..83b62e8837
--- /dev/null
+++ b/lib/stdlib/test/property_test/shell_docs_prop.erl
@@ -0,0 +1,135 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(shell_docs_prop).
+-export([prop_render/0]).
+
+%%% This will include the .hrl file for the installed testing tool:
+-include_lib("common_test/include/ct_property_test.hrl").
+-include_lib("kernel/include/eep48.hrl").
+-compile([export_all]).
+
+prop_render() ->
+ numtests(10000,
+ ?FORALL(Doc,
+ ?LET(Blocks,blocks(),
+ #docs_v1{ module_doc = #{ <<"en">> => Blocks }, docs = [] }
+ ),
+ begin
+ try
+ shell_docs:render(test, Doc),
+ shell_docs:validate(Doc),
+ N1 = shell_docs:normalize(maps:get(<<"en">>,Doc#docs_v1.module_doc)),
+ N1 = shell_docs:normalize(N1),
+ true
+ catch C:E:ST ->
+ ct:pal("~p ~p:~p ~p~n",[Doc,C,E,ST]),
+ false
+ end
+ end)).
+
+-define(LIMIT,5).
+
+blocks() ->
+ blocks([]).
+
+blocks([l|_] = S) ->
+ ?LAZY(
+ frequency(
+ [{2,[]},
+ {2,[{li,[],blocks([li | S])}]}])
+ );
+blocks([dl|_] = S) ->
+ ?LAZY(
+ frequency(
+ [{2,[]},
+ {2,[{dt,[],blocks([dt | S])}]},
+ {2,[{dd,[],blocks([dd | S])}]}])
+ );
+blocks(S) ->
+ ?LAZY(
+ frequency(
+ [{?LIMIT div 2,[]},
+ {max(1,?LIMIT - length(S)),
+ ?LET(Lst,[block(S)|blocks(S)],lists:flatten(Lst))},
+ {max(1,?LIMIT - length(S)),
+ ?LET(Lst,[inlines()|blocks(S)],lists:flatten(Lst))}
+ ]
+ )).
+
+inlines() ->
+ inlines([]).
+inlines(S) ->
+ ?LAZY(
+ frequency(
+ [{?LIMIT,[]},
+ {max(1,?LIMIT - length(S)),[inline(S)|inlines(S)]}
+ ]
+ )).
+
+block(S) ->
+ frequency(
+ fmax('div',3,S,{'div',oneof([[],[{class,<<"Warning">>}]]), blocks(['div'|S])}) ++
+ fmax(p,1,S,{p,[],blocks([p|S])}) ++
+ fmax(l,3,S,{ul,[],blocks([l|S])}) ++
+ fmax(l,3,S,{ol,[],blocks([l|S])}) ++
+ fmax(dl,3,S,{dl,[],blocks([dl|S])}) ++
+ fmax(['div',l,dl],0,S,{h1,[],inlines(['div'|S])}) ++
+ fmax(['div',l,dl],0,S,{h2,[],inlines(['div'|S])}) ++
+ fmax(['div',l,dl],1,S,{h3,[],inlines(['div'|S])}) ++
+ [{5,{br,[],[]}}]
+ ).
+
+inline(S) ->
+ frequency(
+ fmax(i,1,S,{i,[],?LAZY(inlines([i|S]))}) ++
+ fmax(code,1,S,{code,[],?LAZY(inlines([code|S]))}) ++
+ fmax(a,1,S,{a,[],?LAZY(inlines([a|S]))}) ++
+ fmax(em,1,S,{em,[],?LAZY(inlines([em|S]))}) ++
+ [{10,characters()}]).
+
+characters() ->
+ ?LET(Str,list(frequency([{10,printable_character()},{1,char()}])),
+ unicode:characters_to_binary(Str)).
+
+printable_character() ->
+ oneof([integer($\040,$\176),
+ integer(16#A0, 16#D800-1),
+ integer(16#DFFF+1,16#FFFE-1),
+ integer(16#FFFF+1,16#10FFFF),
+ $\n,$\r,$\t,$\v,$\b,$\f,$\e]).
+
+fmax(What,Depth,S,E) when not is_list(What) ->
+ fmax([What],Depth,S,E);
+fmax(What,Depth,S,E) ->
+ Cnt =
+ lists:foldl(
+ fun(E,Cnt) ->
+ case lists:member(E,What) of
+ true ->
+ Cnt+1;
+ false ->
+ Cnt
+ end
+ end, 0, S),
+ if Depth-Cnt =< 0 ->
+ [];
+ true ->
+ [{10 - (Depth-Cnt),E}]
+ end.
diff --git a/lib/stdlib/test/property_test/uri_string_recompose.erl b/lib/stdlib/test/property_test/uri_string_recompose.erl
index 39fadf23c2..3c0dae0f8b 100644
--- a/lib/stdlib/test/property_test/uri_string_recompose.erl
+++ b/lib/stdlib/test/property_test/uri_string_recompose.erl
@@ -85,9 +85,12 @@ prop_recompose() ->
prop_normalize() ->
?FORALL(Map, map(),
- uri_string:normalize(Map, [return_map]) =:=
- uri_string:normalize(uri_string:parse(uri_string:recompose(Map)),
- [return_map])).
+ uri_string:percent_decode(
+ uri_string:normalize(Map, [return_map])) =:=
+ uri_string:percent_decode(
+ uri_string:normalize(
+ uri_string:parse(uri_string:recompose(Map)),
+ [return_map]))).
%% Stats
prop_map_key_length_collect() ->
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput2 b/lib/stdlib/test/re_SUITE_data/testoutput2
index 4ccda27201..f5d32d6ad0 100644
--- a/lib/stdlib/test/re_SUITE_data/testoutput2
+++ b/lib/stdlib/test/re_SUITE_data/testoutput2
@@ -5614,9 +5614,8 @@ No match
123456\P
No match
-//KF>testsavedregex
+//S-KF>testsavedregex
Compiled pattern written to testsavedregex
-Study data written to testsavedregex
/abc/IS>testsavedregex
Capturing subpattern count = 0
diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl
index cdb6031b07..4df0a2238a 100644
--- a/lib/stdlib/test/shell_SUITE.erl
+++ b/lib/stdlib/test/shell_SUITE.erl
@@ -64,8 +64,9 @@ end_per_testcase(_Case, Config) ->
OrigPath = proplists:get_value(orig_path,Config),
code:set_path(OrigPath),
application:unset_env(stdlib, restricted_shell),
- (catch code:purge(user_default)),
- (catch code:delete(user_default)),
+ purge_and_delete(user_default),
+ %% used by `records' test case
+ purge_and_delete(test),
ok.
-endif.
@@ -298,8 +299,7 @@ restricted_local(Config) when is_list(Config) ->
comm_err(<<"begin shell:stop_restricted() end.">>),
undefined =
application:get_env(stdlib, restricted_shell),
- (catch code:purge(user_default)),
- true = (catch code:delete(user_default)),
+ true = purge_and_delete(user_default),
ok.
@@ -428,6 +428,30 @@ records(Config) when is_list(Config) ->
[{error,invalid_filename}] = scan(<<"rr({foo}).">>),
[[]] = scan(<<"rr(\"not_a_file\").">>),
+ %% load record from archive
+ true = purge_and_delete(test),
+
+ PrivDir = proplists:get_value(priv_dir, Config),
+ AppDir = filename:join(PrivDir, "test_app"),
+ ok = file:make_dir(AppDir),
+ AppEbinDir = filename:join(AppDir, "ebin"),
+ ok = file:make_dir(AppEbinDir),
+
+ ok = file:write_file(Test, Contents),
+ {ok, test} = compile:file(Test, [{outdir, AppEbinDir}]),
+
+ Ext = init:archive_extension(),
+ Archive = filename:join(PrivDir, "test_app" ++ Ext),
+ {ok, _} = zip:create(Archive, ["test_app"], [{compress, []}, {cwd, PrivDir}]),
+
+ ArchiveEbinDir = filename:join(Archive, "test_app/ebin"),
+ true = code:add_path(ArchiveEbinDir),
+ {module, test} = code:load_file(test),
+ BeamInArchive = filename:join(ArchiveEbinDir, "test.beam"),
+ BeamInArchive = code:which(test),
+
+ [[state]] = scan(<<"rr(test).">>),
+
%% using records
[2] = scan(<<"rd(foo,{bar}), record_info(size, foo).">>),
[true] = scan(<<"rd(foo,{bar}), is_record(#foo{}, foo).">>),
@@ -601,7 +625,7 @@ otp_5327(Config) when is_list(Config) ->
comm_err(<<"<<103133:64/binary>> = <<103133:64/float>>.">>),
"exception error: interpreted function with arity 1 called with two arguments" =
comm_err(<<"(fun(X) -> X end)(a,b).">>),
- {'EXIT', {{illegal_pattern,_}, _}} =
+ {'EXIT', {{badmatch,<<17:32>>}, _}} =
(catch evaluate("<<A:a>> = <<17:32>>.", [])),
C = <<"
<<A:4,B:4,C:4,D:4,E:4,F:4>> = <<\"hej\">>,
@@ -614,6 +638,9 @@ otp_5327(Config) when is_list(Config) ->
%% unbound_var would be nicer...
{'EXIT',{{illegal_pattern,_},_}} =
(catch evaluate(<<"<<A:B>> = <<17:32>>.">>, [])),
+ %% A badarith exception is turned into badmatch.
+ {'EXIT', {{badmatch,<<1777:32>>}, _}} =
+ (catch evaluate(<<"<<A:(1/0)>> = <<1777:32>>.">>, [])),
%% undefined_bittype is turned into badmatch:
{'EXIT',{{badmatch,<<17:32>>},_}} =
(catch evaluate(<<"<<A/apa>> = <<17:32>>.">>, [])),
@@ -2941,7 +2968,7 @@ otp_14296(Config) when is_list(Config) ->
end(),
fun() ->
- Port = open_port({spawn, "ls"}, [{line,1}]),
+ Port = open_port({spawn, "erl -s erlang halt"}, [{line,1}]),
KnownPort = erlang:port_to_list(Port),
S = KnownPort ++ ".",
R = KnownPort ++ ".\n",
@@ -3141,25 +3168,16 @@ io_request({get_geometry,columns}, S) ->
{ok,80,S};
io_request({get_geometry,rows}, S) ->
{ok,24,S};
-io_request({put_chars,Chars}, S) ->
- {ok,ok,S#state{reply = [S#state.reply | Chars]}};
io_request({put_chars,latin1,Chars}, S) ->
{ok,ok,S#state{reply = [S#state.reply | Chars]}};
io_request({put_chars,unicode,Chars0}, S) ->
Chars = unicode:characters_to_list(Chars0),
{ok,ok,S#state{reply = [S#state.reply | Chars]}};
-io_request({put_chars,Mod,Func,Args}, S) ->
- case catch apply(Mod, Func, Args) of
- Chars when is_list(Chars) ->
- io_request({put_chars,Chars}, S)
- end;
io_request({put_chars,Enc,Mod,Func,Args}, S) ->
case catch apply(Mod, Func, Args) of
Chars when is_list(Chars) ->
io_request({put_chars,Enc,Chars}, S)
end;
-io_request({get_until,_Prompt,Mod,Func,ExtraArgs}, S) ->
- get_until(Mod, Func, ExtraArgs, S, latin1);
io_request({get_until,Enc,_Prompt,Mod,Func,ExtraArgs}, S) ->
get_until(Mod, Func, ExtraArgs, S, Enc).
@@ -3224,3 +3242,6 @@ start_node(Name, Xargs) ->
global:sync(),
N.
+purge_and_delete(Module) ->
+ (catch code:purge(Module)),
+ (catch code:delete(Module)).
diff --git a/lib/stdlib/test/shell_docs_SUITE.erl b/lib/stdlib/test/shell_docs_SUITE.erl
new file mode 100644
index 0000000000..222aec5e0b
--- /dev/null
+++ b/lib/stdlib/test/shell_docs_SUITE.erl
@@ -0,0 +1,237 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(shell_docs_SUITE).
+-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2]).
+
+-export([render/1, links/1, normalize/1, render_prop/1]).
+
+-export([render_all/1]).
+
+-include_lib("kernel/include/eep48.hrl").
+-include_lib("stdlib/include/assert.hrl").
+
+suite() ->
+ [{timetrap,{minutes,10}}].
+
+all() ->
+ [render, links, normalize, {group, prop}].
+
+groups() ->
+ [{prop,[],[render_prop]}].
+
+%% Include a spec here in order to test that specs of undocumented functions
+%% is rendered correctly.
+-spec init_per_suite(Config1) -> Config2 when
+ Config1 :: list({atom(),term()}),
+ Config2 :: list({atom(),term()}).
+init_per_suite(Config) ->
+ {ok, ?MODULE} = c:c(?MODULE,[debug_info]),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(prop, Config) ->
+ ct_property_test:init_per_suite(Config);
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+render(_Config) ->
+ docsmap(
+ fun(Mod, #docs_v1{ docs = Docs } = D) ->
+ lists:foreach(
+ fun(Config) ->
+ try
+ shell_docs:render(Mod, D, Config),
+ shell_docs:render_type(Mod, D, Config),
+ shell_docs:render_callback(Mod, D, Config),
+ [try
+ shell_docs:render(Mod, F, A, D, Config)
+ catch _E:R:ST ->
+ io:format("Failed to render ~p:~p/~p~n~p:~p~n~p~n",
+ [Mod,F,A,R,ST,shell_docs:get_doc(Mod,F,A)]),
+ erlang:raise(error,R,ST)
+ end || {F,A} <- Mod:module_info(exports)],
+ [try
+ shell_docs:render_type(Mod, T, A, D, Config)
+ catch _E:R:ST ->
+ io:format("Failed to render type ~p:~p/~p~n~p:~p~n~p~n",
+ [Mod,T,A,R,ST,shell_docs:get_type_doc(Mod,T,A)]),
+ erlang:raise(error,R,ST)
+ end || {{type,T,A},_,_,_,_} <- Docs],
+ [try
+ shell_docs:render_callback(Mod, T, A, D, Config)
+ catch _E:R:ST ->
+ io:format("Failed to render callback ~p:~p/~p~n~p:~p~n~p~n",
+ [Mod,T,A,R,ST,shell_docs:get_callback_doc(Mod,T,A)]),
+ erlang:raise(error,R,ST)
+ end || {{callback,T,A},_,_,_,_} <- Docs]
+ catch throw:R:ST ->
+ io:format("Failed to render ~p~n~p:~p~n",[Mod,R,ST]),
+ exit(R)
+ end
+ end, [#{},
+ #{ ansi => false },
+ #{ ansi => true },
+ #{ columns => 5 },
+ #{ columns => 150 },
+ #{ encoding => unicode},
+ #{ encoding => latin1}])
+ end),
+ ok.
+
+render_prop(Config) ->
+% dbg:tracer(),dbg:p(all,c),dbg:tpl(shell_docs_prop,[]),
+ ct_property_test:quickcheck(
+ shell_docs_prop:prop_render(),Config).
+
+links(_Config) ->
+ docsmap(
+ fun(Mod, #docs_v1{ module_doc = MDoc, docs = Docs }) ->
+ try
+ [check_links(Mod, maps:get(<<"en">>,MDoc)) || MDoc =/= none, MDoc =/= hidden]
+ catch _E1:R1:ST1 ->
+ io:format("Failed to render ~p~n~p:~p~n~p~n",
+ [Mod,R1,ST1,MDoc]),
+ erlang:raise(error,R1,ST1)
+ end,
+ [try
+ check_links(Mod, D)
+ catch _E:R:ST ->
+ io:format("Failed to render ~p:~p~n~p:~p~n~p~n",
+ [Mod,Kind,R,ST,D]),
+ erlang:raise(error,R,ST)
+ end || {Kind,_Anno,_Sig,#{ <<"en">> := D },_MD} <- Docs]
+ end).
+
+check_links(Mod, [{a,Attr,C}|T]) ->
+ case proplists:get_value(rel,Attr) of
+ <<"https://erlang.org/doc/link/seemfa">> ->
+ case string:lexemes(proplists:get_value(href, Attr),":#/") of
+ [_App,TargetMod,Func,Arity] ->
+ case code:get_doc(b2a(TargetMod)) of
+ {ok, _ModDoc} ->
+ case shell_docs:get_doc(b2a(TargetMod),
+ b2a(Func),
+ binary_to_integer(Arity)) of
+ [] -> throw({could_not_find,b2a(TargetMod),
+ b2a(Func),
+ binary_to_integer(Arity)});
+ _Else -> ok
+ end;
+ _Else ->
+ throw({could_not_find, TargetMod, Attr})
+ end;
+ _Callback ->
+ ok
+ end;
+ _OtherlinkType ->
+ ok
+ end,
+ check_links(Mod, C),
+ check_links(Mod, T);
+check_links(Mod, [{_,_,C}|T]) ->
+ check_links(Mod, C),
+ check_links(Mod, T);
+check_links(Mod, [C|T]) when is_binary(C) ->
+ check_links(Mod, T);
+check_links(_, []) ->
+ ok.
+
+normalize(_Config) ->
+ ?assertMatch(
+ [{p,[],[{em,[],[<<"a ">>,{code,[],[<<"b ">>]},<<"c">>]}]}],
+ shell_docs:normalize([{p,[],[{em,[],[<<" a ">>,{code,[],[<<" b ">>]},<<" c">>]}]}])
+ ),
+ ?assertMatch(
+ [{'div',[],[<<"!">>]}],
+ shell_docs:normalize([{'div',[],[{code,[],[<<" ">>,{i,[],[<<" ">>]}]},<<" !">>]}])
+ ),
+ ok.
+
+%% Special binary_to_atom that deals with <<"'and'">>
+b2a(Bin) ->
+ case erl_scan:string(binary_to_list(Bin)) of
+ {ok,[{atom,_,A}],_} -> A;
+ {ok,[{A,_}],_} -> A
+ end.
+
+%% Testing functions
+render_all(Dir) ->
+ file:make_dir(Dir),
+ docsmap(
+ fun(Mod, #docs_v1{ docs = Docs } = D) ->
+ SMod = atom_to_list(Mod),
+ file:write_file(filename:join(Dir,SMod ++ ".txt"),
+ unicode:characters_to_binary(shell_docs:render(Mod, D))),
+ file:write_file(filename:join(Dir,SMod ++ "_type.txt"),
+ unicode:characters_to_binary(shell_docs:render_type(Mod, D))),
+ file:write_file(filename:join(Dir,SMod ++ "_cb.txt"),
+ unicode:characters_to_binary(shell_docs:render_callback(Mod, D))),
+ lists:foreach(
+ fun({{function,Name,Arity},_Anno,_Sig,_Doc,_Meta}) ->
+ FName = SMod ++ "_"++atom_to_list(Name)++"_"++integer_to_list(Arity)++"_func.txt",
+ ok = file:write_file(filename:join(Dir,re:replace(FName,"[/:]","_",
+ [global,{return,list}])),
+ unicode:characters_to_binary(shell_docs:render(Mod, Name, Arity, D)));
+ ({{type,Name,Arity},_Anno,_Sig,_Doc,_Meta}) ->
+ FName = SMod ++ "_"++atom_to_list(Name)++"_"++integer_to_list(Arity)++"_type.txt",
+ ok = file:write_file(filename:join(Dir,re:replace(FName,"[/:]","_",
+ [global,{return,list}])),
+ unicode:characters_to_binary(shell_docs:render_type(Mod, Name, Arity, D)));
+ ({{callback,Name,Arity},_Anno,_Sig,_Doc,_Meta}) ->
+ FName = SMod ++ "_"++atom_to_list(Name)++"_"++integer_to_list(Arity)++"_cb.txt",
+ file:write_file(filename:join(Dir,re:replace(FName,"[/:]","_",
+ [global,{return,list}])),
+ unicode:characters_to_binary(shell_docs:render_callback(Mod, Name, Arity, D)))
+ end, Docs)
+ end).
+
+docsmap(Fun) ->
+ lists:map(
+ fun F({Mod,_,_}) ->
+ F(Mod);
+ F(Mod) when is_list(Mod) ->
+ F(list_to_atom(Mod));
+ F(Mod) ->
+ case code:get_doc(Mod) of
+ {error, missing} ->
+ ok;
+ {error, cover_compiled} ->
+ ok;
+ {error, E} when E =:= eperm; E =:= eacces ->
+ %% This can happen in BSD's for some reason...
+ ok;
+ {error, eisdir} ->
+ %% Uhm?
+ ok;
+ {ok, Docs} ->
+ try
+ Fun(Mod, Docs)
+ catch E:R:ST ->
+ io:format("Failed to render ~p~n~p:~p:~p~n",[Mod,E,R,ST]),
+ erlang:raise(E,R,ST)
+ end
+ end
+ end, code:all_available()).
diff --git a/lib/stdlib/test/stdlib_SUITE.erl b/lib/stdlib/test/stdlib_SUITE.erl
index 029587a216..7e05723036 100644
--- a/lib/stdlib/test/stdlib_SUITE.erl
+++ b/lib/stdlib/test/stdlib_SUITE.erl
@@ -27,6 +27,8 @@
init_per_testcase/2, end_per_testcase/2,
app_test/1, appup_test/1, assert_test/1]).
+-compile(r21).
+
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
@@ -95,7 +97,7 @@ appup_tests(App,{OkVsns0,NokVsns}) ->
create_test_vsns(App) ->
ThisMajor = erlang:system_info(otp_release),
FirstMajor = previous_major(ThisMajor),
- SecondMajor = previous_major(FirstMajor),
+ SecondMajor = previous_major(previous_major(FirstMajor)),
Ok = app_vsn(App,[ThisMajor,FirstMajor]),
Nok0 = app_vsn(App,[SecondMajor]),
Nok = case Ok of
diff --git a/lib/stdlib/test/string_SUITE.erl b/lib/stdlib/test/string_SUITE.erl
index c9aadd7f10..4475d7c06e 100644
--- a/lib/stdlib/test/string_SUITE.erl
+++ b/lib/stdlib/test/string_SUITE.erl
@@ -1111,12 +1111,22 @@ needs_check(_) -> true.
%%%% Timer stuff
time_func(Fun, Mode, Bin, Repeat) ->
- timer:sleep(100), %% Let emulator catch up and clean things before test runs
+ %% Let emulator catch up and clean things before test runs
+ timer:sleep(100),
+
+ %% We're spawning with a minimum heap size of 1k because certain benchmarks
+ %% (e.g. string_lexemes_binary) keep very little heap data alive, making
+ %% them very sensitive to changes in GC behavior.
+ %%
+ %% If we don't do this, something as benign as shrinking a stack frame can
+ %% make things run slower by making it stick to a smaller heap size,
+ %% causing it to GC more often.
Self = self(),
- Pid = spawn_link(fun() ->
- Str = mode(Mode, Bin),
- Self ! {self(),time_func(0,0,0, Fun, Str, undefined, Repeat)}
- end),
+ Pid = spawn_opt(fun() ->
+ Str = mode(Mode, Bin),
+ Res = time_func(0,0,0, Fun, Str, undefined, Repeat),
+ Self ! {self(), Res}
+ end, [link, {min_heap_size, 1 bsl 10}]),
receive {Pid,Msg} -> Msg end.
time_func(N,Sum,SumSq, Fun, Str, _, Repeat) when N < Repeat ->
diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl
index 47df11923c..9d7ed2829a 100644
--- a/lib/stdlib/test/supervisor_SUITE.erl
+++ b/lib/stdlib/test/supervisor_SUITE.erl
@@ -63,6 +63,7 @@
one_for_one_escalation/1, one_for_all/1,
one_for_all_escalation/1, one_for_all_other_child_fails_restart/1,
simple_one_for_one/1, simple_one_for_one_escalation/1,
+ simple_one_for_one_corruption/1,
rest_for_one/1, rest_for_one_escalation/1,
rest_for_one_other_child_fails_restart/1,
simple_one_for_one_extra/1, simple_one_for_one_shutdown/1]).
@@ -77,7 +78,8 @@
hanging_restart_loop_rest_for_one/1,
hanging_restart_loop_simple/1, code_change/1, code_change_map/1,
code_change_simple/1, code_change_simple_map/1,
- order_of_children/1, scale_start_stop_many_children/1]).
+ order_of_children/1, scale_start_stop_many_children/1,
+ format_log_1/1, format_log_2/1]).
%%-------------------------------------------------------------------------
@@ -104,7 +106,8 @@ all() ->
simple_global_supervisor, hanging_restart_loop,
hanging_restart_loop_rest_for_one, hanging_restart_loop_simple,
code_change, code_change_map, code_change_simple, code_change_simple_map,
- order_of_children, scale_start_stop_many_children].
+ order_of_children, scale_start_stop_many_children,
+ format_log_1, format_log_2].
groups() ->
[{sup_start, [],
@@ -137,7 +140,8 @@ groups() ->
one_for_all_other_child_fails_restart]},
{restart_simple_one_for_one, [],
[simple_one_for_one, simple_one_for_one_shutdown,
- simple_one_for_one_extra, simple_one_for_one_escalation]},
+ simple_one_for_one_extra, simple_one_for_one_escalation,
+ simple_one_for_one_corruption]},
{restart_rest_for_one, [],
[rest_for_one, rest_for_one_escalation,
rest_for_one_other_child_fails_restart]}].
@@ -1276,6 +1280,62 @@ simple_one_for_one_extra(Config) when is_list(Config) ->
check_exit([SupPid]).
%%-------------------------------------------------------------------------
+%% Test for subtle corruption of internal state for a
+%% simple_one_for_one supervisor. Thanks to Zeyu Zhang (@zzydxm) and
+%% Maxim Fedorov for noticing this bug. (OTP-16804)
+simple_one_for_one_corruption(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+
+ logger:add_handler(?MODULE, ?MODULE, #{test_case_pid => self()}),
+
+ try
+ Child = #{id => child, start => {supervisor_1, start_child, []},
+ restart => temporary, shutdown => 1000,
+ type => worker, modules => []},
+ {ok, SupPid} = start_link({ok, {{simple_one_for_one, 2, 3600}, [Child]}}),
+ {ok, CPid1} = supervisor:start_child(sup_test, []),
+
+ terminate(SupPid, CPid1, child1, abnormal),
+
+ %% The first time a child of simple_one_for_one supervisor
+ %% with restart strategy `temporary` dies, the internal state
+ %% for the supervisor will become inconsistent (the `dynamics`
+ %% field would change from `{mapsets,Map}` to
+ %% `{maps,Map}`). That inconsistency will make the supervisor
+ %% retain the start arguments even for temporary processes.
+ %%
+ %% To test that the bug is fixed, start a new child process
+ %% with a large term in its argument list.
+
+ N = 50000,
+ BigData = binary_to_list(<<0:N/unit:8>>),
+ {ok, CPid2, BigData} = supervisor:start_child(sup_test, [BigData]),
+
+ %% Since the child is temporary, the supervisor should not keep
+ %% the argument list for the child and the supervisor's heap
+ %% size should shrink after a GC.
+
+ true = erlang:garbage_collect(SupPid),
+ {total_heap_size, HeapSize} = process_info(SupPid, total_heap_size),
+ if
+ HeapSize > 2*N ->
+ %% The start arguments for the child have been kept.
+ ct:fail({excessive_heap_size,HeapSize});
+ true ->
+ ok
+ end,
+
+ terminate(SupPid, CPid2, child2, abnormal),
+
+ exit(SupPid, kill)
+ after
+ logger:remove_handler(?MODULE)
+ end,
+
+ ok.
+
+
+%%-------------------------------------------------------------------------
%% Test restart escalation on a simple_one_for_one supervisor.
simple_one_for_one_escalation(Config) when is_list(Config) ->
process_flag(trap_exit, true),
@@ -2379,11 +2439,21 @@ scale_start_stop_many_children() ->
ct:log("~w children, start time: ~w ms, stop time: ~w ms",
[N2, StartT2 div 1000, StopT2 div 1000]),
+ TimerAdjustment =
+ case os:type() of
+ {win32,_} ->
+ %% Windows timer unit is really bad...
+ 16000;
+ _ ->
+ %% To avoid div by zero
+ 1
+ end,
+
%% Scaling should be more or less linear, but allowing a bit more
%% to avoid false alarms (add 1 to avoid div zero)
ScaleLimit = (N2 div N1) * 10,
- StartScale = StartT2 div (StartT1+1),
- StopScale = StopT2 div (StopT1+1),
+ StartScale = StartT2 div (StartT1+TimerAdjustment),
+ StopScale = StopT2 div (StopT1+TimerAdjustment),
ct:log("Scale limit: ~w~nStart scale: ~w~nStop scale: ~w",
[ScaleLimit, StartScale, StopScale]),
@@ -2398,6 +2468,249 @@ scale_start_stop_many_children() ->
ok.
+%% Test report callback for Logger handler error_logger
+format_log_1(_Config) ->
+ FD = application:get_env(kernel, error_logger_format_depth),
+ application:unset_env(kernel, error_logger_format_depth),
+ Term = lists:seq(1, 15),
+ Supervisor = my_supervisor,
+ Name = self(),
+ Error = shutdown_error,
+ Child = [{pid,Name},{id,any_id},
+ {mfargs,{mod,func,[any,Term]}},
+ {restart_type,temporary},
+ {shutdown,brutal_kill},
+ {child_type,worker}],
+ Report = #{label=>{supervisor,Error},
+ report=>[{supervisor,Supervisor},
+ {errorContext,Error},
+ {reason,Term},
+ {offender,Child}]},
+ {F1, A1} = supervisor:format_log(Report),
+ FExpected1 = " supervisor: ~tp~n"
+ " errorContext: ~tp~n"
+ " reason: ~tp~n"
+ " offender: ~tp~n",
+ ct:log("F1: ~ts~nA1: ~tp", [F1,A1]),
+ FExpected1 = F1,
+ [Supervisor,Error,Term,Child] = A1,
+
+ Progress = #{label=>{supervisor,progress},
+ report=>[{supervisor,Supervisor},{started,Child}]},
+ {PF1,PA1} = supervisor:format_log(Progress),
+ PFExpected1 = " supervisor: ~tp~n started: ~tp~n",
+ ct:log("PF1: ~ts~nPA1: ~tp", [PF1,PA1]),
+ PFExpected1 = PF1,
+ [Supervisor,Child] = PA1,
+
+ Depth = 10,
+ ok = application:set_env(kernel, error_logger_format_depth, Depth),
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ {F2,A2} = supervisor:format_log(Report),
+ FExpected2 = " supervisor: ~tP~n"
+ " errorContext: ~tP~n"
+ " reason: ~tP~n"
+ " offender: ~tP~n",
+ ct:log("F2: ~ts~nA2: ~tp", [F2,A2]),
+ FExpected2 = F2,
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ LimitedChild = [{pid,Name},{id,any_id},
+ {mfargs,{mod,func,[any,'...']}},
+ {restart_type,temporary},
+ {shutdown,brutal_kill},
+ {child_type,worker}],
+
+ [Supervisor,Depth,Error,Depth,Limited,Depth,LimitedChild,Depth] = A2,
+
+ {PF2,PA2} = supervisor:format_log(Progress),
+ PFExpected2 = " supervisor: ~tP~n started: ~tP~n",
+ ct:log("PF2: ~ts~nPA2: ~tp", [PF2,PA2]),
+ PFExpected2 = PF2,
+ [Supervisor,Depth,LimitedChild,Depth] = PA2,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel, error_logger_format_depth);
+ _ ->
+ application:set_env(kernel, error_logger_format_depth, FD)
+ end,
+ ok.
+
+%% Test report callback for any Logger handler
+format_log_2(_Config) ->
+ Term = lists:seq(1, 15),
+ Supervisor = my_supervisor,
+ Name = self(),
+ NameStr = pid_to_list(Name),
+ Error = shutdown_error,
+ Child = [{pid,Name},{id,any_id},
+ {mfargs,{mod,func,[Term]}},
+ {restart_type,temporary},
+ {shutdown,brutal_kill},
+ {child_type,worker}],
+ Report = #{label=>{supervisor,Error},
+ report=>[{supervisor,Supervisor},
+ {errorContext,Error},
+ {reason,Term},
+ {offender,Child}]},
+ FormatOpts1 = #{},
+ Str1 = flatten_format_log(Report, FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = " supervisor: my_supervisor\n"
+ " errorContext: shutdown_error\n"
+ " reason: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ " offender: [{pid,"++NameStr++"},\n"
+ " {id,any_id},\n"
+ " {mfargs,{mod,func,"
+ "[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]]}},\n"
+ " {restart_type,temporary},\n"
+ " {shutdown,brutal_kill},\n"
+ " {child_type,worker}]\n",
+ ct:log("Str1: ~ts", [Str1]),
+ ct:log("length(Str1): ~p", [L1]),
+ true = Expected1 =:= Str1,
+
+ Progress = #{label=>{supervisor,progress},
+ report=>[{supervisor,Supervisor},{started,Child}]},
+ PStr1 = flatten_format_log(Progress, FormatOpts1),
+ PL1 = length(PStr1),
+ PExpected1 = " supervisor: my_supervisor\n"
+ " started: [{pid,"++NameStr++"},\n"
+ " {id,any_id},\n"
+ " {mfargs,{mod,func,"
+ "[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]]}},\n"
+ " {restart_type,temporary},\n"
+ " {shutdown,brutal_kill},\n"
+ " {child_type,worker}]\n",
+ ct:log("PStr1: ~ts", [PStr1]),
+ ct:log("length(PStr1): ~p", [PL1]),
+ true = PExpected1 =:= PStr1,
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_format_log(Report, FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = " supervisor: my_supervisor\n"
+ " errorContext: shutdown_error\n"
+ " reason: [1,2,3,4,5,6,7,8,9|...]\n"
+ " offender: [{pid,"++NameStr++"},\n"
+ " {id,any_id},\n"
+ " {mfargs,{mod,func,[[...]]}},\n"
+ " {restart_type,temporary},\n"
+ " {shutdown,brutal_kill},\n"
+ " {child_type,worker}]\n",
+ ct:log("Str2: ~ts", [Str2]),
+ ct:log("length(Str2): ~p", [L2]),
+ true = Expected2 =:= Str2,
+
+ PStr2 = flatten_format_log(Progress, FormatOpts2),
+ PL2 = length(PStr2),
+ PExpected2 = " supervisor: my_supervisor\n"
+ " started: [{pid,"++NameStr++"},\n"
+ " {id,any_id},\n"
+ " {mfargs,{mod,func,[[...]]}},\n"
+ " {restart_type,temporary},\n"
+ " {shutdown,brutal_kill},\n"
+ " {child_type,worker}]\n",
+ ct:log("PStr2: ~ts", [PStr2]),
+ ct:log("length(PStr2): ~p", [PL2]),
+ true = PExpected2 =:= PStr2,
+
+ FormatOpts3 = #{chars_limit=>200},
+ Str3 = flatten_format_log(Report, FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = " supervisor: my_supervisor\n"
+ " errorContext: ",
+ ct:log("Str3: ~ts", [Str3]),
+ ct:log("length(Str3): ~p", [L3]),
+ true = lists:prefix(Expected3, Str3),
+ true = L3 < L1,
+
+ PFormatOpts3 = #{chars_limit=>80},
+ PStr3 = flatten_format_log(Progress, PFormatOpts3),
+ PL3 = length(PStr3),
+ PExpected3 = " supervisor: my_supervisor\n started:",
+ ct:log("PStr3: ~ts", [PStr3]),
+ ct:log("length(PStr3): ~p", [PL3]),
+ true = lists:prefix(PExpected3, PStr3),
+ true = PL3 < PL1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_format_log(Report, FormatOpts4),
+ L4 = length(Str4),
+
+ Expected4 = "Supervisor: my_supervisor. Context: shutdown_error. "
+ "Reason: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "Offender: id=any_id,pid="++NameStr++".",
+ ct:log("Str4: ~ts", [Str4]),
+ ct:log("length(Str4): ~p", [L4]),
+ true = Expected4 =:= Str4,
+
+ PStr4 = flatten_format_log(Progress, FormatOpts4),
+ PL4 = length(PStr4),
+ PExpected4 = "Supervisor: my_supervisor. "
+ "Started: id=any_id,pid="++NameStr++".",
+ ct:log("PStr4: ~ts", [PStr4]),
+ ct:log("length(PStr4): ~p", [PL4]),
+ true = PExpected4 =:= PStr4,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_format_log(Report, FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "Supervisor: my_supervisor. Context: shutdown_error. "
+ "Reason: [1,2,3,4,5,6,7,8,9|...]. "
+ "Offender: id=any_id,pid="++NameStr++".",
+ ct:log("Str5: ~ts", [Str5]),
+ ct:log("length(Str5): ~p", [L5]),
+ true = Expected5 =:= Str5,
+
+ PStr5 = flatten_format_log(Progress, FormatOpts5),
+ PL5 = length(PStr5),
+ PExpected5 = "Supervisor: my_supervisor. "
+ "Started: id=any_id,pid="++NameStr++".",
+ ct:log("PStr5: ~ts", [PStr5]),
+ ct:log("length(PStr5): ~p", [PL5]),
+ true = PExpected5 =:= PStr5,
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>200},
+ Str6 = flatten_format_log(Report, FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "Supervisor: my_supervisor. Context:",
+ ct:log("Str6: ~ts", [Str6]),
+ ct:log("length(Str6): ~p", [L6]),
+ true = lists:prefix(Expected6, Str6),
+ true = L6 < L4,
+
+ PFormatOpts6 = #{single_line=>true, chars_limit=>60},
+ PStr6 = flatten_format_log(Progress, PFormatOpts6),
+ PL6 = length(PStr6),
+ PExpected6 = "Supervisor: my_supervisor.",
+ ct:log("PStr6: ~ts", [PStr6]),
+ ct:log("length(PStr6): ~p", [PL6]),
+ true = lists:prefix(PExpected6, PStr6),
+ true = PL6 < PL4,
+
+ Child2 = [{nb_children,7},{id,any_id},
+ {mfargs,{mod,func,[Term]}},
+ {restart_type,temporary},
+ {shutdown,brutal_kill},
+ {child_type,worker}],
+ Report2 = #{label=>{supervisor,Error},
+ report=>[{supervisor,Supervisor},
+ {errorContext,Error},
+ {reason,Term},
+ {offender,Child2}]},
+ Str7 = flatten_format_log(Report2, FormatOpts6),
+ L7 = length(Str7),
+ ct:log("Str7: ~ts", [Str7]),
+ ct:log("length(Str7): ~p", [L7]),
+ true = string:find(Str7, "Offender: id=any_id,nb_children=7.") =/= nomatch,
+
+ ok.
+
+flatten_format_log(Report, Format) ->
+ lists:flatten(supervisor:format_log(Report, Format)).
+
%%-------------------------------------------------------------------------
terminate(Pid, Reason) when Reason =/= supervisor ->
terminate(dummy, Pid, dummy, Reason).
diff --git a/lib/stdlib/test/supervisor_bridge_SUITE.erl b/lib/stdlib/test/supervisor_bridge_SUITE.erl
index 279ae91cdc..60af37dd9a 100644
--- a/lib/stdlib/test/supervisor_bridge_SUITE.erl
+++ b/lib/stdlib/test/supervisor_bridge_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,starting/1,
mini_terminate/1,mini_die/1,badstart/1,
- simple_global_supervisor/1]).
+ simple_global_supervisor/1, format_log_1/1, format_log_2/1]).
-export([client/1,init/1,internal_loop_init/1,terminate/2,server9212/0]).
-include_lib("common_test/include/ct.hrl").
@@ -35,7 +35,8 @@ suite() ->
{timetrap,{minutes,1}}].
all() ->
- [starting, mini_terminate, mini_die, badstart, simple_global_supervisor].
+ [starting, mini_terminate, mini_die, badstart, simple_global_supervisor,
+ format_log_1, format_log_2].
groups() ->
[].
@@ -227,3 +228,202 @@ simple_global_supervisor(Config) when is_list(Config) ->
server9212() ->
supervisor_bridge:start_link({global,?bridge_name}, ?MODULE, 3).
+
+%% Test report callback for Logger handler error_logger
+format_log_1(_Config) ->
+ FD = application:get_env(kernel, error_logger_format_depth),
+ application:unset_env(kernel, error_logger_format_depth),
+ Term = lists:seq(1, 15),
+ Supervisor = my_supervisor,
+ Name = self(),
+ Error = error,
+ Offender = [{pid,Name},{mod,a_module}],
+ Report = #{label=>{supervisor,Error},
+ report=>[{supervisor,Supervisor},
+ {errorContext,Error},
+ {reason,Term},
+ {offender,Offender}]},
+ {F1, A1} = supervisor_bridge:format_log(Report),
+ FExpected1 = " supervisor: ~tp~n"
+ " errorContext: ~tp~n"
+ " reason: ~tp~n"
+ " offender: ~tp~n",
+ ct:log("F1: ~ts~nA1: ~tp", [F1,A1]),
+ FExpected1 = F1,
+ [Supervisor,Error,Term,Offender] = A1,
+
+ Started = [{pid,Name},{mfa,{a_module,init,[Term]}}],
+ Progress = #{label=>{supervisor,progress},
+ report=>[{supervisor,Supervisor},{started,Started}]},
+ {PF1,PA1} = supervisor_bridge:format_log(Progress),
+ PFExpected1 = " supervisor: ~tp~n started: ~tp~n",
+ ct:log("PF1: ~ts~nPA1: ~tp", [PF1,PA1]),
+ PFExpected1 = PF1,
+ [Supervisor,Started] = PA1,
+
+ Depth = 10,
+ ok = application:set_env(kernel, error_logger_format_depth, Depth),
+ Limited = [1,2,3,4,5,6,7,8,9,'...'],
+ {F2,A2} = supervisor_bridge:format_log(Report),
+ FExpected2 = " supervisor: ~tP~n"
+ " errorContext: ~tP~n"
+ " reason: ~tP~n"
+ " offender: ~tP~n",
+ ct:log("F2: ~ts~nA2: ~tp", [F2,A2]),
+ FExpected2 = F2,
+ LimitedOffender = Offender,
+ [Supervisor,Depth,Error,Depth,Limited,Depth,LimitedOffender,Depth] = A2,
+
+ LimitedStarted =
+ [{pid,Name},{mfa,{a_module,init,[[1,2,3,4,5,6,7,8,9,'...']]}}],
+ {PF2,PA2} = supervisor_bridge:format_log(Progress),
+ PFExpected2 = " supervisor: ~tP~n started: ~tP~n",
+ ct:log("PF2: ~ts~nPA2: ~tp", [PF2,PA2]),
+ PFExpected2 = PF2,
+ [Supervisor,Depth,LimitedStarted,Depth] = PA2,
+
+ case FD of
+ undefined ->
+ application:unset_env(kernel, error_logger_format_depth);
+ _ ->
+ application:set_env(kernel, error_logger_format_depth, FD)
+ end,
+ ok.
+
+%% Test report callback for any Logger handler
+format_log_2(_Config) ->
+ Term = lists:seq(1, 15),
+ Supervisor = my_supervisor,
+ Name = self(),
+ NameStr = pid_to_list(Name),
+ Error = shutdown_error,
+ Offender = [{pid,Name},{mod,a_module}],
+ Report = #{label=>{supervisor,Error},
+ report=>[{supervisor,Supervisor},
+ {errorContext,Error},
+ {reason,Term},
+ {offender,Offender}]},
+ FormatOpts1 = #{},
+ Str1 = flatten_format_log(Report, FormatOpts1),
+ L1 = length(Str1),
+ Expected1 = " supervisor: my_supervisor\n"
+ " errorContext: shutdown_error\n"
+ " reason: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ " offender: [{pid,"++NameStr++"},{mod,a_module}]\n",
+ ct:log("Str1: ~ts", [Str1]),
+ ct:log("length(Str1): ~p", [L1]),
+ true = Expected1 =:= Str1,
+
+ Started = [{pid,Name},{mfa,{a_module,init,[Term]}}],
+ Progress = #{label=>{supervisor,progress},
+ report=>[{supervisor,Supervisor},{started,Started}]},
+ PStr1 = flatten_format_log(Progress, FormatOpts1),
+ PL1 = length(PStr1),
+ PExpected1 = " supervisor: my_supervisor\n"
+ " started: [{pid,"++NameStr++"},\n"
+ " {mfa,{a_module,init,"
+ "[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]]}}]\n",
+ ct:log("PStr1: ~ts", [PStr1]),
+ ct:log("length(PStr1): ~p", [PL1]),
+ true = PExpected1 =:= PStr1,
+
+ Depth = 10,
+ FormatOpts2 = #{depth=>Depth},
+ Str2 = flatten_format_log(Report, FormatOpts2),
+ L2 = length(Str2),
+ Expected2 = " supervisor: my_supervisor\n"
+ " errorContext: shutdown_error\n"
+ " reason: [1,2,3,4,5,6,7,8,9|...]\n"
+ " offender: [{pid,"++NameStr++"},{mod,a_module}]\n",
+ ct:log("Str2: ~ts", [Str2]),
+ ct:log("length(Str2): ~p", [L2]),
+ true = Expected2 =:= Str2,
+
+ PStr2 = flatten_format_log(Progress, FormatOpts2),
+ PL2 = length(PStr2),
+ PExpected2 = " supervisor: my_supervisor\n"
+ " started: [{pid,"++NameStr++"},"
+ "{mfa,{a_module,init,[[1|...]]}}]\n",
+ ct:log("PStr2: ~ts", [PStr2]),
+ ct:log("length(PStr2): ~p", [PL2]),
+ true = PExpected2 =:= PStr2,
+
+ FormatOpts3 = #{chars_limit=>200},
+ Str3 = flatten_format_log(Report, FormatOpts3),
+ L3 = length(Str3),
+ Expected3 = " supervisor: my_supervisor\n"
+ " errorContext: shutdown_error\n"
+ " reason: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\n"
+ " offender: [{pid,"++NameStr++"},{mod,a_module}]\n",
+ ct:log("Str3: ~ts", [Str3]),
+ ct:log("length(Str3): ~p", [L3]),
+ true = Expected3 =:= Str3,
+
+ PFormatOpts3 = #{chars_limit=>80},
+ PStr3 = flatten_format_log(Progress, PFormatOpts3),
+ PL3 = length(PStr3),
+ PExpected3 = " supervisor: my_supervisor\n started:",
+ ct:log("PStr3: ~ts", [PStr3]),
+ ct:log("length(PStr3): ~p", [PL3]),
+ true = lists:prefix(PExpected3, PStr3),
+ true = PL3 < PL1,
+
+ FormatOpts4 = #{single_line=>true},
+ Str4 = flatten_format_log(Report, FormatOpts4),
+ L4 = length(Str4),
+ Expected4 = "Supervisor: my_supervisor. Context: shutdown_error. "
+ "Reason: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]. "
+ "Offender: pid="++NameStr++",mod=a_module.",
+ ct:log("Str4: ~ts", [Str4]),
+ ct:log("length(Str4): ~p", [L4]),
+ true = Expected4 =:= Str4,
+
+ PStr4 = flatten_format_log(Progress, FormatOpts4),
+ PL4 = length(PStr4),
+ PExpected4 = "Supervisor: my_supervisor. "
+ "Started: pid="++NameStr++",mfa={a_module,init,"
+ "[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]]}.",
+ ct:log("PStr4: ~ts", [PStr4]),
+ ct:log("length(PStr4): ~p", [PL4]),
+ true = PExpected4 =:= PStr4,
+
+ FormatOpts5 = #{single_line=>true, depth=>Depth},
+ Str5 = flatten_format_log(Report, FormatOpts5),
+ L5 = length(Str5),
+ Expected5 = "Supervisor: my_supervisor. Context: shutdown_error. "
+ "Reason: [1,2,3,4,5,6,7,8,9|...]. "
+ "Offender: pid="++NameStr++",mod=a_module.",
+ ct:log("Str5: ~ts", [Str5]),
+ ct:log("length(Str5): ~p", [L5]),
+ true = Expected5 =:= Str5,
+
+ PStr5 = flatten_format_log(Progress, FormatOpts5),
+ PL5 = length(PStr5),
+ PExpected5 = "Supervisor: my_supervisor. "
+ "Started: pid="++NameStr++",mfa={a_module,init,[[1,2,3,4,5|...]]}.",
+ ct:log("PStr5: ~ts", [PStr5]),
+ ct:log("length(PStr5): ~p", [PL5]),
+ true = PExpected5 =:= PStr5,
+
+ FormatOpts6 = #{single_line=>true, chars_limit=>100},
+ Str6 = flatten_format_log(Report, FormatOpts6),
+ L6 = length(Str6),
+ Expected6 = "Supervisor: my_supervisor. Context:",
+ ct:log("Str6: ~ts", [Str6]),
+ ct:log("length(Str6): ~p", [L6]),
+ true = lists:prefix(Expected6, Str6),
+ true = L6 < L4,
+
+ PFormatOpts6 = #{single_line=>true, chars_limit=>60},
+ PStr6 = flatten_format_log(Progress, PFormatOpts6),
+ PL6 = length(PStr6),
+ PExpected6 = "Supervisor: my_supervisor.",
+ ct:log("PStr6: ~ts", [PStr6]),
+ ct:log("length(PStr6): ~p", [PL6]),
+ true = lists:prefix(PExpected6, PStr6),
+ true = PL6 < PL4,
+
+ ok.
+
+flatten_format_log(Report, Format) ->
+ lists:flatten(supervisor_bridge:format_log(Report, Format)).
diff --git a/lib/stdlib/test/tar_SUITE.erl b/lib/stdlib/test/tar_SUITE.erl
index 55af3b16b8..585894af86 100644
--- a/lib/stdlib/test/tar_SUITE.erl
+++ b/lib/stdlib/test/tar_SUITE.erl
@@ -28,7 +28,8 @@
extract_from_open_file/1, symlinks/1, open_add_close/1, cooked_compressed/1,
memory/1,unicode/1,read_other_implementations/1,
sparse/1, init/1, leading_slash/1, dotdot/1,
- roundtrip_metadata/1, apply_file_info_opts/1]).
+ roundtrip_metadata/1, apply_file_info_opts/1,
+ incompatible_options/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
@@ -43,7 +44,7 @@ all() ->
symlinks, open_add_close, cooked_compressed, memory, unicode,
read_other_implementations,
sparse,init,leading_slash,dotdot,roundtrip_metadata,
- apply_file_info_opts].
+ apply_file_info_opts,incompatible_options].
groups() ->
[].
@@ -574,6 +575,29 @@ extract_from_open_file(Config) when is_list(Config) ->
verify_ports(Config).
+%% Make sure incompatible options are rejected when opening archives with file
+%% descriptors.
+incompatible_options(Config) when is_list(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ Long = filename:join(DataDir, "no_fancy_stuff.tar"),
+
+ {ok, File} = file:open(Long, [read]),
+ Handle = {file, File},
+
+ {error, {Handle, {incompatible_option, compressed}}}
+ = erl_tar:open(Handle, [read, compressed]),
+ {error, {Handle, {incompatible_option, cooked}}}
+ = erl_tar:open(Handle, [read, cooked]),
+
+ {error, {Handle, {incompatible_option, compressed}}}
+ = erl_tar:extract(Handle, [compressed]),
+ {error, {Handle, {incompatible_option, cooked}}}
+ = erl_tar:extract(Handle, [cooked]),
+
+ ok = file:close(File),
+
+ verify_ports(Config).
+
%% Test that archives containing symlinks can be created and extracted.
symlinks(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt b/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt
index 6847953c23..fb4fec9fff 100644
--- a/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt
+++ b/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt
@@ -1,6 +1,6 @@
-# GraphemeBreakTest-11.0.0.txt
-# Date: 2018-03-18, 13:30:33 GMT
-# © 2018 Unicode®, Inc.
+# GraphemeBreakTest-12.1.0.txt
+# Date: 2019-03-10, 10:53:12 GMT
+# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
@@ -56,8 +56,6 @@
÷ 0020 × 0308 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0020 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0020 × 0308 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0020 ÷ D800 ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0020 × 0308 ÷ D800 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 000D ÷ 0020 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] SPACE (Other) ÷ [0.3]
÷ 000D ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 000D ÷ 000D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -92,8 +90,6 @@
÷ 000D ÷ 0308 × 200D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 000D ÷ 0378 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
÷ 000D ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 000D ÷ D800 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 000A ÷ 0020 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] SPACE (Other) ÷ [0.3]
÷ 000A ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 000A ÷ 000D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -128,8 +124,6 @@
÷ 000A ÷ 0308 × 200D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 000A ÷ 0378 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
÷ 000A ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 000A ÷ D800 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 0001 ÷ 0020 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] SPACE (Other) ÷ [0.3]
÷ 0001 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0001 ÷ 000D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -164,8 +158,6 @@
÷ 0001 ÷ 0308 × 200D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0001 ÷ 0378 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0001 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0001 ÷ D800 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 034F ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 034F × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 034F ÷ 000D ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -200,8 +192,6 @@
÷ 034F × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 034F ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 034F × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 034F ÷ D800 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 034F × 0308 ÷ D800 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 1F1E6 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 1F1E6 × 0308 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 1F1E6 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -236,8 +226,6 @@
÷ 1F1E6 × 0308 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 1F1E6 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 1F1E6 × 0308 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1F1E6 ÷ D800 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ D800 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 0600 × 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] SPACE (Other) ÷ [0.3]
÷ 0600 × 0308 ÷ 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0600 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -272,8 +260,6 @@
÷ 0600 × 0308 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0600 × 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] <reserved-0378> (Other) ÷ [0.3]
÷ 0600 × 0308 ÷ 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0600 ÷ D800 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0600 × 0308 ÷ D800 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 0903 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0903 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0903 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -308,8 +294,6 @@
÷ 0903 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0903 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0903 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0903 ÷ D800 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0903 × 0308 ÷ D800 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 1100 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 1100 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 1100 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -344,8 +328,6 @@
÷ 1100 × 0308 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 1100 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 1100 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1100 ÷ D800 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1100 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 1160 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 1160 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 1160 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -380,8 +362,6 @@
÷ 1160 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 1160 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 1160 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1160 ÷ D800 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1160 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 11A8 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 11A8 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 11A8 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -416,8 +396,6 @@
÷ 11A8 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 11A8 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 11A8 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 11A8 ÷ D800 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 11A8 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ AC00 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ AC00 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ AC00 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -452,8 +430,6 @@
÷ AC00 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ AC00 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ AC00 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ AC00 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ AC00 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ AC01 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ AC01 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ AC01 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -488,8 +464,6 @@
÷ AC01 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ AC01 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ AC01 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ AC01 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ AC01 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 231A ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 231A × 0308 ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 231A ÷ 000D ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -524,8 +498,6 @@
÷ 231A × 0308 × 200D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 231A ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 231A × 0308 ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 231A ÷ D800 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 231A × 0308 ÷ D800 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 0300 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0300 × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0300 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -560,8 +532,6 @@
÷ 0300 × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0300 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0300 × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0300 ÷ D800 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0300 × 0308 ÷ D800 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 200D ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 200D × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 200D ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -596,8 +566,6 @@
÷ 200D × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 200D ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 200D × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 200D ÷ D800 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 200D × 0308 ÷ D800 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 0378 ÷ 0020 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0378 × 0308 ÷ 0020 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0378 ÷ 000D ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
@@ -632,44 +600,6 @@
÷ 0378 × 0308 × 200D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0378 ÷ 0378 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0378 × 0308 ÷ 0378 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0378 ÷ D800 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0378 × 0308 ÷ D800 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ D800 ÷ 0020 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] SPACE (Other) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ D800 ÷ 000D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ D800 ÷ 000A ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ D800 ÷ 0001 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ D800 ÷ 034F ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
-÷ D800 ÷ 0308 × 034F ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
-÷ D800 ÷ 1F1E6 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ D800 ÷ 0600 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ D800 ÷ 0903 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ D800 ÷ 0308 × 0903 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ D800 ÷ 1100 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ D800 ÷ 1160 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ D800 ÷ 11A8 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ D800 ÷ AC00 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ D800 ÷ AC01 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ D800 ÷ 231A ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 231A ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
-÷ D800 ÷ 0300 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
-÷ D800 ÷ 0308 × 0300 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
-÷ D800 ÷ 200D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
-÷ D800 ÷ 0308 × 200D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
-÷ D800 ÷ 0378 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ D800 ÷ D800 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 000D × 000A ÷ 0061 ÷ 000A ÷ 0308 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) × [3.0] <LINE FEED (LF)> (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3]
÷ 0061 × 0308 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3]
÷ 0020 × 200D ÷ 0646 ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC LETTER NOON (Other) ÷ [0.3]
@@ -695,6 +625,6 @@
÷ 2701 × 200D × 2701 ÷ # ÷ [0.2] UPPER BLADE SCISSORS (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] UPPER BLADE SCISSORS (Other) ÷ [0.3]
÷ 0061 × 200D ÷ 2701 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] UPPER BLADE SCISSORS (Other) ÷ [0.3]
#
-# Lines: 672
+# Lines: 602
#
# EOF
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt b/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt
index 0e9e678a85..eb056990a0 100644
--- a/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt
+++ b/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt
@@ -1,6 +1,6 @@
-# LineBreakTest-11.0.0.txt
-# Date: 2018-05-20, 09:03:09 GMT
-# © 2018 Unicode®, Inc.
+# LineBreakTest-12.1.0.txt
+# Date: 2019-03-10, 10:53:14 GMT
+# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt b/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt
index 72a31bcdf1..54f43bdf4d 100644
--- a/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt
+++ b/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt
@@ -1,6 +1,6 @@
-# NormalizationTest-11.0.0.txt
-# Date: 2018-02-19, 18:33:08 GMT
-# © 2018 Unicode®, Inc.
+# NormalizationTest-12.1.0.txt
+# Date: 2019-04-01, 09:10:28 GMT
+# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
@@ -2149,6 +2149,7 @@
32FC;32FC;32FC;30F0;30F0; # (㋼; ㋼; ㋼; ヰ; ヰ; ) CIRCLED KATAKANA WI
32FD;32FD;32FD;30F1;30F1; # (㋽; ㋽; ㋽; ヱ; ヱ; ) CIRCLED KATAKANA WE
32FE;32FE;32FE;30F2;30F2; # (㋾; ㋾; ㋾; ヲ; ヲ; ) CIRCLED KATAKANA WO
+32FF;32FF;32FF;4EE4 548C;4EE4 548C; # (㋿; ㋿; ㋿; 令和; 令和; ) SQUARE ERA NAME REIWA
3300;3300;3300;30A2 30D1 30FC 30C8;30A2 30CF 309A 30FC 30C8; # (㌀; ㌀; ㌀; アパート; アハ◌゚ート; ) SQUARE APAATO
3301;3301;3301;30A2 30EB 30D5 30A1;30A2 30EB 30D5 30A1; # (㌁; ㌁; ㌁; アルファ; アルファ; ) SQUARE ARUHUA
3302;3302;3302;30A2 30F3 30DA 30A2;30A2 30F3 30D8 309A 30A2; # (㌂; ㌂; ㌂; アンペア; アンヘ◌゚ア; ) SQUARE ANPEA
@@ -16363,6 +16364,7 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
1F14F;1F14F;1F14F;0057 0043;0057 0043; # (🅏; 🅏; 🅏; WC; WC; ) SQUARED WC
1F16A;1F16A;1F16A;004D 0043;004D 0043; # (🅪; 🅪; 🅪; MC; MC; ) RAISED MC SIGN
1F16B;1F16B;1F16B;004D 0044;004D 0044; # (🅫; 🅫; 🅫; MD; MD; ) RAISED MD SIGN
+1F16C;1F16C;1F16C;004D 0052;004D 0052; # (🅬; 🅬; 🅬; MR; MR; ) RAISED MR SIGN
1F190;1F190;1F190;0044 004A;0044 004A; # (🆐; 🆐; 🆐; DJ; DJ; ) SQUARE DJ
1F200;1F200;1F200;307B 304B;307B 304B; # (🈀; 🈀; 🈀; ほか; ほか; ) SQUARE HIRAGANA HOKA
1F201;1F201;1F201;30B3 30B3;30B3 30B3; # (🈁; 🈁; 🈁; ココ; ココ; ) SQUARED KATAKANA KOKO
@@ -17685,6 +17687,8 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
0061 0EB8 0EC8 0EB8 0E48 0062;0061 0E48 0EB8 0EB8 0EC8 0062;0061 0E48 0EB8 0EB8 0EC8 0062;0061 0E48 0EB8 0EB8 0EC8 0062;0061 0E48 0EB8 0EB8 0EC8 0062; # (a◌ຸ◌່◌ຸ◌่b; a◌่◌ຸ◌ຸ◌່b; a◌่◌ຸ◌ຸ◌່b; a◌่◌ຸ◌ຸ◌່b; a◌่◌ຸ◌ຸ◌່b; ) LATIN SMALL LETTER A, LAO VOWEL SIGN U, LAO TONE MAI EK, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, LATIN SMALL LETTER B
0061 0EC8 0EB8 0E48 0EB9 0062;0061 0E48 0EB8 0EB9 0EC8 0062;0061 0E48 0EB8 0EB9 0EC8 0062;0061 0E48 0EB8 0EB9 0EC8 0062;0061 0E48 0EB8 0EB9 0EC8 0062; # (a◌່◌ຸ◌่◌ູb; a◌่◌ຸ◌ູ◌່b; a◌่◌ຸ◌ູ◌່b; a◌่◌ຸ◌ູ◌່b; a◌่◌ຸ◌ູ◌່b; ) LATIN SMALL LETTER A, LAO TONE MAI EK, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, LAO VOWEL SIGN UU, LATIN SMALL LETTER B
0061 0EB9 0EC8 0EB8 0E48 0062;0061 0E48 0EB9 0EB8 0EC8 0062;0061 0E48 0EB9 0EB8 0EC8 0062;0061 0E48 0EB9 0EB8 0EC8 0062;0061 0E48 0EB9 0EB8 0EC8 0062; # (a◌ູ◌່◌ຸ◌่b; a◌่◌ູ◌ຸ◌່b; a◌่◌ູ◌ຸ◌່b; a◌่◌ູ◌ຸ◌່b; a◌่◌ູ◌ຸ◌່b; ) LATIN SMALL LETTER A, LAO VOWEL SIGN UU, LAO TONE MAI EK, LAO VOWEL SIGN U, THAI CHARACTER MAI EK, LATIN SMALL LETTER B
+0061 05B0 094D 3099 0EBA 0062;0061 3099 094D 0EBA 05B0 0062;0061 3099 094D 0EBA 05B0 0062;0061 3099 094D 0EBA 05B0 0062;0061 3099 094D 0EBA 05B0 0062; # (a◌ְ◌्◌゙◌຺b; a◌゙◌्◌຺◌ְb; a◌゙◌्◌຺◌ְb; a◌゙◌्◌຺◌ְb; a◌゙◌्◌຺◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LAO SIGN PALI VIRAMA, LATIN SMALL LETTER B
+0061 0EBA 05B0 094D 3099 0062;0061 3099 0EBA 094D 05B0 0062;0061 3099 0EBA 094D 05B0 0062;0061 3099 0EBA 094D 05B0 0062;0061 3099 0EBA 094D 05B0 0062; # (a◌຺◌ְ◌्◌゙b; a◌゙◌຺◌्◌ְb; a◌゙◌຺◌्◌ְb; a◌゙◌຺◌्◌ְb; a◌゙◌຺◌्◌ְb; ) LATIN SMALL LETTER A, LAO SIGN PALI VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
0061 0F71 0EC8 0EB8 0EC8 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062; # (a◌ཱ◌່◌ຸ◌່b; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LAO TONE MAI EK, LATIN SMALL LETTER B
0061 0EC8 0F71 0EC8 0EB8 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062;0061 0EB8 0EC8 0EC8 0F71 0062; # (a◌່◌ཱ◌່◌ຸb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; a◌ຸ◌່◌່◌ཱb; ) LATIN SMALL LETTER A, LAO TONE MAI EK, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LATIN SMALL LETTER B
0061 0F71 0EC8 0EB8 0EC9 0062;0061 0EB8 0EC8 0EC9 0F71 0062;0061 0EB8 0EC8 0EC9 0F71 0062;0061 0EB8 0EC8 0EC9 0F71 0062;0061 0EB8 0EC8 0EC9 0F71 0062; # (a◌ཱ◌່◌ຸ◌້b; a◌ຸ◌່◌້◌ཱb; a◌ຸ◌່◌້◌ཱb; a◌ຸ◌່◌້◌ཱb; a◌ຸ◌່◌້◌ཱb; ) LATIN SMALL LETTER A, TIBETAN VOWEL SIGN AA, LAO TONE MAI EK, LAO VOWEL SIGN U, LAO TONE MAI THO, LATIN SMALL LETTER B
@@ -18453,6 +18457,8 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
0061 11839 05B0 094D 3099 0062;0061 3099 11839 094D 05B0 0062;0061 3099 11839 094D 05B0 0062;0061 3099 11839 094D 05B0 0062;0061 3099 11839 094D 05B0 0062; # (a◌𑠹◌ְ◌्◌゙b; a◌゙◌𑠹◌्◌ְb; a◌゙◌𑠹◌्◌ְb; a◌゙◌𑠹◌्◌ְb; a◌゙◌𑠹◌्◌ְb; ) LATIN SMALL LETTER A, DOGRA SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
0061 3099 093C 0334 1183A 0062;0061 0334 093C 1183A 3099 0062;0061 0334 093C 1183A 3099 0062;0061 0334 093C 1183A 3099 0062;0061 0334 093C 1183A 3099 0062; # (a◌゙◌़◌̴◌𑠺b; a◌̴◌़◌𑠺◌゙b; a◌̴◌़◌𑠺◌゙b; a◌̴◌़◌𑠺◌゙b; a◌̴◌़◌𑠺◌゙b; ) LATIN SMALL LETTER A, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, COMBINING TILDE OVERLAY, DOGRA SIGN NUKTA, LATIN SMALL LETTER B
0061 1183A 3099 093C 0334 0062;0061 0334 1183A 093C 3099 0062;0061 0334 1183A 093C 3099 0062;0061 0334 1183A 093C 3099 0062;0061 0334 1183A 093C 3099 0062; # (a◌𑠺◌゙◌़◌̴b; a◌̴◌𑠺◌़◌゙b; a◌̴◌𑠺◌़◌゙b; a◌̴◌𑠺◌़◌゙b; a◌̴◌𑠺◌़◌゙b; ) LATIN SMALL LETTER A, DOGRA SIGN NUKTA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, DEVANAGARI SIGN NUKTA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
+0061 05B0 094D 3099 119E0 0062;0061 3099 094D 119E0 05B0 0062;0061 3099 094D 119E0 05B0 0062;0061 3099 094D 119E0 05B0 0062;0061 3099 094D 119E0 05B0 0062; # (a◌ְ◌्◌゙◌𑧠b; a◌゙◌्◌𑧠◌ְb; a◌゙◌्◌𑧠◌ְb; a◌゙◌्◌𑧠◌ְb; a◌゙◌्◌𑧠◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, NANDINAGARI SIGN VIRAMA, LATIN SMALL LETTER B
+0061 119E0 05B0 094D 3099 0062;0061 3099 119E0 094D 05B0 0062;0061 3099 119E0 094D 05B0 0062;0061 3099 119E0 094D 05B0 0062;0061 3099 119E0 094D 05B0 0062; # (a◌𑧠◌ְ◌्◌゙b; a◌゙◌𑧠◌्◌ְb; a◌゙◌𑧠◌्◌ְb; a◌゙◌𑧠◌्◌ְb; a◌゙◌𑧠◌्◌ְb; ) LATIN SMALL LETTER A, NANDINAGARI SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
0061 05B0 094D 3099 11A34 0062;0061 3099 094D 11A34 05B0 0062;0061 3099 094D 11A34 05B0 0062;0061 3099 094D 11A34 05B0 0062;0061 3099 094D 11A34 05B0 0062; # (a◌ְ◌्◌゙◌𑨴b; a◌゙◌्◌𑨴◌ְb; a◌゙◌्◌𑨴◌ְb; a◌゙◌्◌𑨴◌ְb; a◌゙◌्◌𑨴◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, ZANABAZAR SQUARE SIGN VIRAMA, LATIN SMALL LETTER B
0061 11A34 05B0 094D 3099 0062;0061 3099 11A34 094D 05B0 0062;0061 3099 11A34 094D 05B0 0062;0061 3099 11A34 094D 05B0 0062;0061 3099 11A34 094D 05B0 0062; # (a◌𑨴◌ְ◌्◌゙b; a◌゙◌𑨴◌्◌ְb; a◌゙◌𑨴◌्◌ְb; a◌゙◌𑨴◌्◌ְb; a◌゙◌𑨴◌्◌ְb; ) LATIN SMALL LETTER A, ZANABAZAR SQUARE SIGN VIRAMA, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, LATIN SMALL LETTER B
0061 05B0 094D 3099 11A47 0062;0061 3099 094D 11A47 05B0 0062;0061 3099 094D 11A47 05B0 0062;0061 3099 094D 11A47 05B0 0062;0061 3099 094D 11A47 05B0 0062; # (a◌ְ◌्◌゙◌𑩇b; a◌゙◌्◌𑩇◌ְb; a◌゙◌्◌𑩇◌ְb; a◌゙◌्◌𑩇◌ְb; a◌゙◌्◌𑩇◌ְb; ) LATIN SMALL LETTER A, HEBREW POINT SHEVA, DEVANAGARI SIGN VIRAMA, COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK, ZANABAZAR SQUARE SUBJOINER, LATIN SMALL LETTER B
@@ -18637,6 +18643,28 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
0061 1E029 0315 0300 05AE 0062;0061 05AE 1E029 0300 0315 0062;0061 05AE 1E029 0300 0315 0062;0061 05AE 1E029 0300 0315 0062;0061 05AE 1E029 0300 0315 0062; # (a◌𞀩◌̕◌̀◌֮b; a◌֮◌𞀩◌̀◌̕b; a◌֮◌𞀩◌̀◌̕b; a◌֮◌𞀩◌̀◌̕b; a◌֮◌𞀩◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER IOTATED BIG YUS, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
0061 0315 0300 05AE 1E02A 0062;00E0 05AE 1E02A 0315 0062;0061 05AE 0300 1E02A 0315 0062;00E0 05AE 1E02A 0315 0062;0061 05AE 0300 1E02A 0315 0062; # (a◌̕◌̀◌֮◌𞀪b; à◌֮◌𞀪◌̕b; a◌֮◌̀◌𞀪◌̕b; à◌֮◌𞀪◌̕b; a◌֮◌̀◌𞀪◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, COMBINING GLAGOLITIC LETTER FITA, LATIN SMALL LETTER B
0061 1E02A 0315 0300 05AE 0062;0061 05AE 1E02A 0300 0315 0062;0061 05AE 1E02A 0300 0315 0062;0061 05AE 1E02A 0300 0315 0062;0061 05AE 1E02A 0300 0315 0062; # (a◌𞀪◌̕◌̀◌֮b; a◌֮◌𞀪◌̀◌̕b; a◌֮◌𞀪◌̀◌̕b; a◌֮◌𞀪◌̀◌̕b; a◌֮◌𞀪◌̀◌̕b; ) LATIN SMALL LETTER A, COMBINING GLAGOLITIC LETTER FITA, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E130 0062;00E0 05AE 1E130 0315 0062;0061 05AE 0300 1E130 0315 0062;00E0 05AE 1E130 0315 0062;0061 05AE 0300 1E130 0315 0062; # (a◌̕◌̀◌֮◌𞄰b; à◌֮◌𞄰◌̕b; a◌֮◌̀◌𞄰◌̕b; à◌֮◌𞄰◌̕b; a◌֮◌̀◌𞄰◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-B, LATIN SMALL LETTER B
+0061 1E130 0315 0300 05AE 0062;0061 05AE 1E130 0300 0315 0062;0061 05AE 1E130 0300 0315 0062;0061 05AE 1E130 0300 0315 0062;0061 05AE 1E130 0300 0315 0062; # (a◌𞄰◌̕◌̀◌֮b; a◌֮◌𞄰◌̀◌̕b; a◌֮◌𞄰◌̀◌̕b; a◌֮◌𞄰◌̀◌̕b; a◌֮◌𞄰◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-B, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E131 0062;00E0 05AE 1E131 0315 0062;0061 05AE 0300 1E131 0315 0062;00E0 05AE 1E131 0315 0062;0061 05AE 0300 1E131 0315 0062; # (a◌̕◌̀◌֮◌𞄱b; à◌֮◌𞄱◌̕b; a◌֮◌̀◌𞄱◌̕b; à◌֮◌𞄱◌̕b; a◌֮◌̀◌𞄱◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-M, LATIN SMALL LETTER B
+0061 1E131 0315 0300 05AE 0062;0061 05AE 1E131 0300 0315 0062;0061 05AE 1E131 0300 0315 0062;0061 05AE 1E131 0300 0315 0062;0061 05AE 1E131 0300 0315 0062; # (a◌𞄱◌̕◌̀◌֮b; a◌֮◌𞄱◌̀◌̕b; a◌֮◌𞄱◌̀◌̕b; a◌֮◌𞄱◌̀◌̕b; a◌֮◌𞄱◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-M, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E132 0062;00E0 05AE 1E132 0315 0062;0061 05AE 0300 1E132 0315 0062;00E0 05AE 1E132 0315 0062;0061 05AE 0300 1E132 0315 0062; # (a◌̕◌̀◌֮◌𞄲b; à◌֮◌𞄲◌̕b; a◌֮◌̀◌𞄲◌̕b; à◌֮◌𞄲◌̕b; a◌֮◌̀◌𞄲◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-J, LATIN SMALL LETTER B
+0061 1E132 0315 0300 05AE 0062;0061 05AE 1E132 0300 0315 0062;0061 05AE 1E132 0300 0315 0062;0061 05AE 1E132 0300 0315 0062;0061 05AE 1E132 0300 0315 0062; # (a◌𞄲◌̕◌̀◌֮b; a◌֮◌𞄲◌̀◌̕b; a◌֮◌𞄲◌̀◌̕b; a◌֮◌𞄲◌̀◌̕b; a◌֮◌𞄲◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-J, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E133 0062;00E0 05AE 1E133 0315 0062;0061 05AE 0300 1E133 0315 0062;00E0 05AE 1E133 0315 0062;0061 05AE 0300 1E133 0315 0062; # (a◌̕◌̀◌֮◌𞄳b; à◌֮◌𞄳◌̕b; a◌֮◌̀◌𞄳◌̕b; à◌֮◌𞄳◌̕b; a◌֮◌̀◌𞄳◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-V, LATIN SMALL LETTER B
+0061 1E133 0315 0300 05AE 0062;0061 05AE 1E133 0300 0315 0062;0061 05AE 1E133 0300 0315 0062;0061 05AE 1E133 0300 0315 0062;0061 05AE 1E133 0300 0315 0062; # (a◌𞄳◌̕◌̀◌֮b; a◌֮◌𞄳◌̀◌̕b; a◌֮◌𞄳◌̀◌̕b; a◌֮◌𞄳◌̀◌̕b; a◌֮◌𞄳◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-V, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E134 0062;00E0 05AE 1E134 0315 0062;0061 05AE 0300 1E134 0315 0062;00E0 05AE 1E134 0315 0062;0061 05AE 0300 1E134 0315 0062; # (a◌̕◌̀◌֮◌𞄴b; à◌֮◌𞄴◌̕b; a◌֮◌̀◌𞄴◌̕b; à◌֮◌𞄴◌̕b; a◌֮◌̀◌𞄴◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-S, LATIN SMALL LETTER B
+0061 1E134 0315 0300 05AE 0062;0061 05AE 1E134 0300 0315 0062;0061 05AE 1E134 0300 0315 0062;0061 05AE 1E134 0300 0315 0062;0061 05AE 1E134 0300 0315 0062; # (a◌𞄴◌̕◌̀◌֮b; a◌֮◌𞄴◌̀◌̕b; a◌֮◌𞄴◌̀◌̕b; a◌֮◌𞄴◌̀◌̕b; a◌֮◌𞄴◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-S, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E135 0062;00E0 05AE 1E135 0315 0062;0061 05AE 0300 1E135 0315 0062;00E0 05AE 1E135 0315 0062;0061 05AE 0300 1E135 0315 0062; # (a◌̕◌̀◌֮◌𞄵b; à◌֮◌𞄵◌̕b; a◌֮◌̀◌𞄵◌̕b; à◌֮◌𞄵◌̕b; a◌֮◌̀◌𞄵◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-G, LATIN SMALL LETTER B
+0061 1E135 0315 0300 05AE 0062;0061 05AE 1E135 0300 0315 0062;0061 05AE 1E135 0300 0315 0062;0061 05AE 1E135 0300 0315 0062;0061 05AE 1E135 0300 0315 0062; # (a◌𞄵◌̕◌̀◌֮b; a◌֮◌𞄵◌̀◌̕b; a◌֮◌𞄵◌̀◌̕b; a◌֮◌𞄵◌̀◌̕b; a◌֮◌𞄵◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-G, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E136 0062;00E0 05AE 1E136 0315 0062;0061 05AE 0300 1E136 0315 0062;00E0 05AE 1E136 0315 0062;0061 05AE 0300 1E136 0315 0062; # (a◌̕◌̀◌֮◌𞄶b; à◌֮◌𞄶◌̕b; a◌֮◌̀◌𞄶◌̕b; à◌֮◌𞄶◌̕b; a◌֮◌̀◌𞄶◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, NYIAKENG PUACHUE HMONG TONE-D, LATIN SMALL LETTER B
+0061 1E136 0315 0300 05AE 0062;0061 05AE 1E136 0300 0315 0062;0061 05AE 1E136 0300 0315 0062;0061 05AE 1E136 0300 0315 0062;0061 05AE 1E136 0300 0315 0062; # (a◌𞄶◌̕◌̀◌֮b; a◌֮◌𞄶◌̀◌̕b; a◌֮◌𞄶◌̀◌̕b; a◌֮◌𞄶◌̀◌̕b; a◌֮◌𞄶◌̀◌̕b; ) LATIN SMALL LETTER A, NYIAKENG PUACHUE HMONG TONE-D, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E2EC 0062;00E0 05AE 1E2EC 0315 0062;0061 05AE 0300 1E2EC 0315 0062;00E0 05AE 1E2EC 0315 0062;0061 05AE 0300 1E2EC 0315 0062; # (a◌̕◌̀◌֮◌𞋬b; à◌֮◌𞋬◌̕b; a◌֮◌̀◌𞋬◌̕b; à◌֮◌𞋬◌̕b; a◌֮◌̀◌𞋬◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, WANCHO TONE TUP, LATIN SMALL LETTER B
+0061 1E2EC 0315 0300 05AE 0062;0061 05AE 1E2EC 0300 0315 0062;0061 05AE 1E2EC 0300 0315 0062;0061 05AE 1E2EC 0300 0315 0062;0061 05AE 1E2EC 0300 0315 0062; # (a◌𞋬◌̕◌̀◌֮b; a◌֮◌𞋬◌̀◌̕b; a◌֮◌𞋬◌̀◌̕b; a◌֮◌𞋬◌̀◌̕b; a◌֮◌𞋬◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE TUP, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E2ED 0062;00E0 05AE 1E2ED 0315 0062;0061 05AE 0300 1E2ED 0315 0062;00E0 05AE 1E2ED 0315 0062;0061 05AE 0300 1E2ED 0315 0062; # (a◌̕◌̀◌֮◌𞋭b; à◌֮◌𞋭◌̕b; a◌֮◌̀◌𞋭◌̕b; à◌֮◌𞋭◌̕b; a◌֮◌̀◌𞋭◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, WANCHO TONE TUPNI, LATIN SMALL LETTER B
+0061 1E2ED 0315 0300 05AE 0062;0061 05AE 1E2ED 0300 0315 0062;0061 05AE 1E2ED 0300 0315 0062;0061 05AE 1E2ED 0300 0315 0062;0061 05AE 1E2ED 0300 0315 0062; # (a◌𞋭◌̕◌̀◌֮b; a◌֮◌𞋭◌̀◌̕b; a◌֮◌𞋭◌̀◌̕b; a◌֮◌𞋭◌̀◌̕b; a◌֮◌𞋭◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE TUPNI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E2EE 0062;00E0 05AE 1E2EE 0315 0062;0061 05AE 0300 1E2EE 0315 0062;00E0 05AE 1E2EE 0315 0062;0061 05AE 0300 1E2EE 0315 0062; # (a◌̕◌̀◌֮◌𞋮b; à◌֮◌𞋮◌̕b; a◌֮◌̀◌𞋮◌̕b; à◌֮◌𞋮◌̕b; a◌֮◌̀◌𞋮◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, WANCHO TONE KOI, LATIN SMALL LETTER B
+0061 1E2EE 0315 0300 05AE 0062;0061 05AE 1E2EE 0300 0315 0062;0061 05AE 1E2EE 0300 0315 0062;0061 05AE 1E2EE 0300 0315 0062;0061 05AE 1E2EE 0300 0315 0062; # (a◌𞋮◌̕◌̀◌֮b; a◌֮◌𞋮◌̀◌̕b; a◌֮◌𞋮◌̀◌̕b; a◌֮◌𞋮◌̀◌̕b; a◌֮◌𞋮◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE KOI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
+0061 0315 0300 05AE 1E2EF 0062;00E0 05AE 1E2EF 0315 0062;0061 05AE 0300 1E2EF 0315 0062;00E0 05AE 1E2EF 0315 0062;0061 05AE 0300 1E2EF 0315 0062; # (a◌̕◌̀◌֮◌𞋯b; à◌֮◌𞋯◌̕b; a◌֮◌̀◌𞋯◌̕b; à◌֮◌𞋯◌̕b; a◌֮◌̀◌𞋯◌̕b; ) LATIN SMALL LETTER A, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, WANCHO TONE KOINI, LATIN SMALL LETTER B
+0061 1E2EF 0315 0300 05AE 0062;0061 05AE 1E2EF 0300 0315 0062;0061 05AE 1E2EF 0300 0315 0062;0061 05AE 1E2EF 0300 0315 0062;0061 05AE 1E2EF 0300 0315 0062; # (a◌𞋯◌̕◌̀◌֮b; a◌֮◌𞋯◌̀◌̕b; a◌֮◌𞋯◌̀◌̕b; a◌֮◌𞋯◌̀◌̕b; a◌֮◌𞋯◌̀◌̕b; ) LATIN SMALL LETTER A, WANCHO TONE KOINI, COMBINING COMMA ABOVE RIGHT, COMBINING GRAVE ACCENT, HEBREW ACCENT ZINOR, LATIN SMALL LETTER B
0061 059A 0316 302A 1E8D0 0062;0061 302A 0316 1E8D0 059A 0062;0061 302A 0316 1E8D0 059A 0062;0061 302A 0316 1E8D0 059A 0062;0061 302A 0316 1E8D0 059A 0062; # (a◌֚◌̖◌〪◌𞣐b; a◌〪◌̖◌𞣐◌֚b; a◌〪◌̖◌𞣐◌֚b; a◌〪◌̖◌𞣐◌֚b; a◌〪◌̖◌𞣐◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, IDEOGRAPHIC LEVEL TONE MARK, MENDE KIKAKUI COMBINING NUMBER TEENS, LATIN SMALL LETTER B
0061 1E8D0 059A 0316 302A 0062;0061 302A 1E8D0 0316 059A 0062;0061 302A 1E8D0 0316 059A 0062;0061 302A 1E8D0 0316 059A 0062;0061 302A 1E8D0 0316 059A 0062; # (a◌𞣐◌֚◌̖◌〪b; a◌〪◌𞣐◌̖◌֚b; a◌〪◌𞣐◌̖◌֚b; a◌〪◌𞣐◌̖◌֚b; a◌〪◌𞣐◌̖◌֚b; ) LATIN SMALL LETTER A, MENDE KIKAKUI COMBINING NUMBER TEENS, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, IDEOGRAPHIC LEVEL TONE MARK, LATIN SMALL LETTER B
0061 059A 0316 302A 1E8D1 0062;0061 302A 0316 1E8D1 059A 0062;0061 302A 0316 1E8D1 059A 0062;0061 302A 0316 1E8D1 059A 0062;0061 302A 0316 1E8D1 059A 0062; # (a◌֚◌̖◌〪◌𞣑b; a◌〪◌̖◌𞣑◌֚b; a◌〪◌̖◌𞣑◌֚b; a◌〪◌̖◌𞣑◌֚b; a◌〪◌̖◌𞣑◌֚b; ) LATIN SMALL LETTER A, HEBREW ACCENT YETIV, COMBINING GRAVE ACCENT BELOW, IDEOGRAPHIC LEVEL TONE MARK, MENDE KIKAKUI COMBINING NUMBER TENS, LATIN SMALL LETTER B
diff --git a/lib/stdlib/test/uri_string_SUITE.erl b/lib/stdlib/test/uri_string_SUITE.erl
index eb59ea9b0c..96e4a9d41b 100644
--- a/lib/stdlib/test/uri_string_SUITE.erl
+++ b/lib/stdlib/test/uri_string_SUITE.erl
@@ -51,7 +51,9 @@
compose_query/1, compose_query_latin1/1, compose_query_negative/1,
dissect_query/1, dissect_query_negative/1,
interop_query_latin1/1, interop_query_utf8/1,
- regression_parse/1, regression_recompose/1, regression_normalize/1
+ regression_parse/1, regression_recompose/1, regression_normalize/1,
+ recompose_host_relative_path/1,
+ recompose_host_absolute_path/1
]).
@@ -144,7 +146,9 @@ all() ->
interop_query_utf8,
regression_parse,
regression_recompose,
- regression_normalize
+ regression_normalize,
+ recompose_host_relative_path,
+ recompose_host_absolute_path
].
groups() ->
@@ -1047,14 +1051,20 @@ normalize_map(_Config) ->
normalize_return_map(_Config) ->
#{scheme := "http",path := "/a/g",host := "localhost-örebro"} =
- uri_string:normalize("http://localhos%74-%c3%b6rebro:80/a/b/c/./../../g",
- [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "http://localhos%74-%c3%b6rebro:80/a/b/c/./../../g",
+ [return_map])),
#{scheme := <<"http">>,path := <<"/a/g">>, host := <<"localhost-örebro"/utf8>>} =
- uri_string:normalize(<<"http://localhos%74-%c3%b6rebro:80/a/b/c/./../../g">>,
- [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"http://localhos%74-%c3%b6rebro:80/a/b/c/./../../g">>,
+ [return_map])),
#{scheme := <<"https">>,path := <<"/">>, host := <<"localhost">>} =
- uri_string:normalize(#{scheme => <<"https">>,port => 443,path => <<>>,
- host => <<"localhost">>}, [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ #{scheme => <<"https">>,port => 443,path => <<>>,
+ host => <<"localhost">>}, [return_map])).
normalize_negative(_Config) ->
{error,invalid_uri,":"} =
@@ -1065,64 +1075,103 @@ normalize_negative(_Config) ->
uri_string:normalize("http://[192.168.0.1]", [return_map]),
{error,invalid_uri,":"} =
uri_string:normalize(<<"http://[192.168.0.1]">>, [return_map]),
- {error,invalid_utf8,<<0,0,0,246>>} = uri_string:normalize("//%00%00%00%F6").
+ {error,invalid_utf8,<<47,47,0,0,0,246>>} =
+ uri_string:percent_decode(uri_string:normalize("//%00%00%00%F6")).
normalize_binary_pct_encoded_userinfo(_Config) ->
#{scheme := <<"user">>, path := <<"合@気道"/utf8>>} =
- uri_string:normalize(<<"user:%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"user:%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map])),
#{path := <<"合気道@"/utf8>>} =
- uri_string:normalize(<<"%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map])),
#{path := <<"/合気道@"/utf8>>} =
- uri_string:normalize(<<"/%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"/%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map])),
#{path := <<"合@気道"/utf8>>} =
- uri_string:normalize(<<"%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map])),
#{userinfo := <<"合"/utf8>>, host := <<"気道"/utf8>>} =
- uri_string:normalize(<<"//%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"//%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map])),
#{userinfo := <<"合:気"/utf8>>, host := <<"道"/utf8>>} =
- uri_string:normalize(<<"//%E5%90%88:%E6%B0%97@%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"//%E5%90%88:%E6%B0%97@%E9%81%93">>, [return_map])),
#{scheme := <<"foo">>, path := <<"/合気道@"/utf8>>} =
- uri_string:normalize(<<"foo:/%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo:/%E5%90%88%E6%B0%97%E9%81%93@">>, [return_map])),
#{scheme := <<"foo">>, userinfo := <<"合"/utf8>>, host := <<"気道"/utf8>>} =
- uri_string:normalize(<<"foo://%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo://%E5%90%88@%E6%B0%97%E9%81%93">>, [return_map])),
#{scheme := <<"foo">>, userinfo := <<"合:気"/utf8>>, host := <<"道"/utf8>>} =
- uri_string:normalize(<<"foo://%E5%90%88:%E6%B0%97@%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo://%E5%90%88:%E6%B0%97@%E9%81%93">>, [return_map])),
{error,invalid_uri,"@"} =
- uri_string:normalize(<<"//%E5%90%88@%E6%B0%97%E9%81%93@">>, [return_map]),
+ uri_string:normalize(
+ <<"//%E5%90%88@%E6%B0%97%E9%81%93@">>, [return_map]),
{error,invalid_uri,":"} =
- uri_string:normalize(<<"foo://%E5%90%88@%E6%B0%97%E9%81%93@">>, [return_map]).
+ uri_string:normalize(
+ <<"foo://%E5%90%88@%E6%B0%97%E9%81%93@">>, [return_map]).
normalize_binary_pct_encoded_query(_Config) ->
#{scheme := <<"foo">>, host := <<"example.com">>, path := <<"/">>,
query := <<"name=合気道"/utf8>>} =
- uri_string:normalize(<<"foo://example.com/?name=%E5%90%88%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo://example.com/?name=%E5%90%88%E6%B0%97%E9%81%93">>,
+ [return_map])),
#{host := <<"example.com">>, path := <<"/">>, query := <<"name=合気道"/utf8>>} =
- uri_string:normalize(<<"//example.com/?name=%E5%90%88%E6%B0%97%E9%81%93">>, [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"//example.com/?name=%E5%90%88%E6%B0%97%E9%81%93">>, [return_map])).
normalize_binary_pct_encoded_fragment(_Config) ->
#{scheme := <<"foo">>, host := <<"example.com">>, fragment := <<"合気道"/utf8>>} =
- uri_string:normalize(<<"foo://example.com#%E5%90%88%E6%B0%97%E9%81%93">>, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"foo://example.com#%E5%90%88%E6%B0%97%E9%81%93">>, [return_map])),
#{host := <<"example.com">>, path := <<"/">>, fragment := <<"合気道"/utf8>>} =
- uri_string:normalize(<<"//example.com/#%E5%90%88%E6%B0%97%E9%81%93">>, [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ <<"//example.com/#%E5%90%88%E6%B0%97%E9%81%93">>, [return_map])).
normalize_pct_encoded_userinfo(_Config) ->
#{scheme := "user", path := "合@気道"} =
- uri_string:normalize("user:%E5%90%88@%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("user:%E5%90%88@%E6%B0%97%E9%81%93", [return_map])),
#{path := "合気道@"} =
- uri_string:normalize("%E5%90%88%E6%B0%97%E9%81%93@", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("%E5%90%88%E6%B0%97%E9%81%93@", [return_map])),
#{path := "/合気道@"} =
- uri_string:normalize("/%E5%90%88%E6%B0%97%E9%81%93@", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("/%E5%90%88%E6%B0%97%E9%81%93@", [return_map])),
#{path := "合@気道"} =
- uri_string:normalize("%E5%90%88@%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("%E5%90%88@%E6%B0%97%E9%81%93", [return_map])),
#{userinfo := "合", host := "気道"} =
- uri_string:normalize("//%E5%90%88@%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("//%E5%90%88@%E6%B0%97%E9%81%93", [return_map])),
#{userinfo := "合:気", host := "道"} =
- uri_string:normalize("//%E5%90%88:%E6%B0%97@%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("//%E5%90%88:%E6%B0%97@%E9%81%93", [return_map])),
#{scheme := "foo", path := "/合気道@"} =
- uri_string:normalize("foo:/%E5%90%88%E6%B0%97%E9%81%93@", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("foo:/%E5%90%88%E6%B0%97%E9%81%93@", [return_map])),
#{scheme := "foo", userinfo := "合", host := "気道"} =
- uri_string:normalize("foo://%E5%90%88@%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("foo://%E5%90%88@%E6%B0%97%E9%81%93", [return_map])),
#{scheme := "foo", userinfo := "合:気", host := "道"} =
- uri_string:normalize("foo://%E5%90%88:%E6%B0%97@%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize("foo://%E5%90%88:%E6%B0%97@%E9%81%93", [return_map])),
{error,invalid_uri,"@"} =
uri_string:normalize("//%E5%90%88@%E6%B0%97%E9%81%93@", [return_map]),
{error,invalid_uri,":"} =
@@ -1131,25 +1180,37 @@ normalize_pct_encoded_userinfo(_Config) ->
normalize_pct_encoded_query(_Config) ->
#{scheme := "foo", host := "example.com", path := "/",
query := "name=合気道"} =
- uri_string:normalize("foo://example.com/?name=%E5%90%88%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "foo://example.com/?name=%E5%90%88%E6%B0%97%E9%81%93", [return_map])),
#{host := "example.com", path := "/", query := "name=合気道"} =
- uri_string:normalize("//example.com/?name=%E5%90%88%E6%B0%97%E9%81%93", [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "//example.com/?name=%E5%90%88%E6%B0%97%E9%81%93", [return_map])).
normalize_pct_encoded_fragment(_Config) ->
#{scheme := "foo", host := "example.com", fragment := "合気道"} =
- uri_string:normalize("foo://example.com#%E5%90%88%E6%B0%97%E9%81%93", [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "foo://example.com#%E5%90%88%E6%B0%97%E9%81%93", [return_map])),
#{host := "example.com", path := "/", fragment := "合気道"} =
- uri_string:normalize("//example.com/#%E5%90%88%E6%B0%97%E9%81%93", [return_map]).
+ uri_string:percent_decode(
+ uri_string:normalize(
+ "//example.com/#%E5%90%88%E6%B0%97%E9%81%93", [return_map])).
normalize_pct_encoded_negative(_Config) ->
- {error,invalid_utf8,<<0,0,0,246>>} =
- uri_string:normalize(#{host => "%00%00%00%F6",path => []}, [return_map]),
- {error,invalid_utf8,<<0,0,0,246>>} =
- uri_string:normalize(#{host => "%00%00%00%F6",path => []}, []),
- {error,invalid_utf8,<<0,0,0,246>>} =
- uri_string:normalize("//%00%00%00%F6", [return_map]),
- {error,invalid_utf8,<<0,0,0,246>>} =
- uri_string:normalize("//%00%00%00%F6", []).
+ {error,{invalid,{host,{invalid_utf8,<<0,0,0,246>>}}}} =
+ uri_string:percent_decode(
+ uri_string:normalize(#{host => "%00%00%00%F6",path => []}, [return_map])),
+ {error,invalid_utf8,<<47,47,0,0,0,246>>} =
+ uri_string:percent_decode(
+ uri_string:normalize(#{host => "%00%00%00%F6",path => []}, [])),
+ {error,{invalid,{host,{invalid_utf8,<<0,0,0,246>>}}}} =
+ uri_string:percent_decode(
+ uri_string:normalize("//%00%00%00%F6", [return_map])),
+ {error,invalid_utf8,<<47,47,0,0,0,246>>} =
+ uri_string:percent_decode(
+ uri_string:normalize("//%00%00%00%F6", [])).
interop_query_utf8(_Config) ->
Q = uri_string:compose_query([{"foo bar","1"}, {"合", "2"}]),
@@ -1214,8 +1275,8 @@ regression_normalize(_Config) ->
"foo://%C3%B6" =
uri_string:normalize("FOo://%C3%B6"),
#{host := "ö",path := [],scheme := "foo"} =
- uri_string:normalize("FOo://%C3%B6", [return_map]),
-
+ uri_string:percent_decode(
+ uri_string:normalize("FOo://%C3%B6", [return_map])),
"foo://bar" =
uri_string:normalize(#{host => "Bar",path => [],scheme => "FOo"}),
@@ -1240,9 +1301,34 @@ regression_normalize(_Config) ->
"foo://%C3%B6" =
uri_string:normalize(#{host => "%C3%B6",path => [],scheme => "FOo"}),
#{host := "ö",path := [],scheme := "foo"} =
- uri_string:normalize(#{host => "%C3%B6",path => [],scheme => "FOo"}, [return_map]),
+ uri_string:percent_decode(
+ uri_string:normalize(#{host => "%C3%B6",path => [],scheme => "FOo"},
+ [return_map])),
"foo://%C3%B6" =
uri_string:normalize(#{host => "ö",path => [],scheme => "FOo"}),
#{host := "ö",path := [],scheme := "foo"} =
uri_string:normalize(#{host => "ö",path => [],scheme => "FOo"}, [return_map]).
+
+recompose_host_relative_path(_Config) ->
+ "//example.com/.foo" =
+ uri_string:recompose(#{host => "example.com", path => ".foo"}),
+ <<"//example.com/foo">> =
+ uri_string:recompose(#{host => <<"example.com">>, path => <<"foo">>}),
+ ok.
+
+recompose_host_absolute_path(_Config) ->
+ "//example.com/foo" =
+ uri_string:recompose(#{host => "example.com",
+ path => ["/", "foo"]}),
+ "//example.com/foo" =
+ uri_string:recompose(#{host => <<"example.com">>,
+ path => [<<"/">>,<<"foo">>]}),
+ "//example.com/foo" =
+ uri_string:recompose(#{host => "example.com",
+ path => ["/f", "oo"]}),
+ "//example.com/foo" =
+ uri_string:recompose(#{host => <<"example.com">>,
+ path => [<<"/f">>,<<"oo">>]}),
+ ok.
+
diff --git a/lib/stdlib/test/win32reg_SUITE.erl b/lib/stdlib/test/win32reg_SUITE.erl
index 5e44e16ddc..f7d3d8da97 100644
--- a/lib/stdlib/test/win32reg_SUITE.erl
+++ b/lib/stdlib/test/win32reg_SUITE.erl
@@ -59,23 +59,27 @@ long(Config) when is_list(Config) ->
{ok,Read} = win32reg:open([read]),
ok = win32reg:change_key(Read, "\\hklm"),
- ok = win32reg:change_key(Read, LongKey),
- {ok,ErlangKey} = win32reg:current_key(Read),
- io:format("Erlang key: ~s~n", [ErlangKey]),
- ok = win32reg:close(Read),
-
- {ok,Reg} = win32reg:open([read, write]),
- %% Write a long value and read it back.
- TestKey = "test_key",
- LongValue = lists:concat(["This is a long value generated by the test case ",?MODULE,":long/1. "|lists:duplicate(128, "a")]),
- ok = win32reg:set_value(Reg, TestKey, LongValue),
- {ok,LongValue} = win32reg:value(Reg, TestKey),
-
- io:format("Where ~p Key ~s Value ~s ~n", [win32reg:current_key(Reg), TestKey, LongValue]),
- %% Done.
-
- ok = win32reg:close(Reg),
- ok.
+ case os:getenv("WSLENV") of
+ false ->
+ ok = win32reg:change_key(Read, LongKey),
+ {ok,ErlangKey} = win32reg:current_key(Read),
+ io:format("Erlang key: ~s~n", [ErlangKey]),
+ ok = win32reg:close(Read),
+
+ {ok,Reg} = win32reg:open([read, write]),
+ %% Write a long value and read it back.
+ TestKey = "test_key",
+ LongValue = lists:concat(["This is a long value generated by the test case ",?MODULE,":long/1. "|lists:duplicate(128, "a")]),
+ ok = win32reg:set_value(Reg, TestKey, LongValue),
+ {ok,LongValue} = win32reg:value(Reg, TestKey),
+
+ io:format("Where ~p Key ~s Value ~s ~n", [win32reg:current_key(Reg), TestKey, LongValue]),
+ %% Done.
+ ok = win32reg:close(Reg);
+ _ ->
+ %% We have installed erlang when testing on win10 and newer
+ ok
+ end.
evil_write(Config) when is_list(Config) ->
Key = "Software\\Ericsson\\Erlang",
diff --git a/lib/stdlib/test/zip_SUITE.erl b/lib/stdlib/test/zip_SUITE.erl
index 081bffa7cb..c1be2786da 100644
--- a/lib/stdlib/test/zip_SUITE.erl
+++ b/lib/stdlib/test/zip_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@
openzip_api/1, zip_api/1, open_leak/1, unzip_jar/1,
unzip_traversal_exploit/1,
compress_control/1,
- foldl/1,fd_leak/1]).
+ foldl/1,fd_leak/1,unicode/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
@@ -40,7 +40,7 @@ all() ->
unzip_to_binary, zip_to_binary, unzip_options,
zip_options, list_dir_options, aliases, openzip_api,
zip_api, open_leak, unzip_jar, compress_control, foldl,
- unzip_traversal_exploit,fd_leak].
+ unzip_traversal_exploit,fd_leak,unicode].
groups() ->
[].
@@ -105,49 +105,32 @@ borderline_test(Size, TempDir) ->
%% Verify that Unix zip can read it. (if we have a unix zip that is!)
- unzip_list(Archive, Name),
+ zipinfo_match(Archive, Name),
ok.
-unzip_list(Archive, Name) ->
- case unix_unzip_exists() of
- true ->
- unzip_list1(Archive, Name);
+zipinfo_match(Archive, Name) ->
+ case check_zipinfo_exists() of
+ true ->
+ Encoding = file:native_name_encoding(),
+ Expect = unicode:characters_to_binary(Name ++ "\n",
+ Encoding, Encoding),
+ cmd_expect("zipinfo -1 " ++ Archive, Expect);
_ ->
ok
end.
-%% Used to do os:find_executable() to check if unzip exists, but on
-%% some hosts that would give an unzip program which did not take the
-%% "-Z" option.
-%% Here we check that "unzip -Z" (which should display usage) and
-%% check that it exists with status 0.
-unix_unzip_exists() ->
- case os:type() of
- {unix,_} ->
- Port = open_port({spawn,"unzip -Z > /dev/null"}, [exit_status]),
- receive
- {Port,{exit_status,0}} ->
- true;
- {Port,{exit_status,_Fail}} ->
- false
- end;
- _ ->
- false
- end.
-
-unzip_list1(Archive, Name) ->
- Expect = Name ++ "\n",
- cmd_expect("unzip -Z -1 " ++ Archive, Expect).
+check_zipinfo_exists() ->
+ is_list(os:find_executable("zipinfo")).
cmd_expect(Cmd, Expect) ->
- Port = open_port({spawn, make_cmd(Cmd)}, [stream, in, eof]),
- get_data(Port, Expect).
+ Port = open_port({spawn, make_cmd(Cmd)}, [stream, in, binary, eof]),
+ get_data(Port, Expect, <<>>).
-get_data(Port, Expect) ->
+get_data(Port, Expect, Acc) ->
receive
{Port, {data, Bytes}} ->
- get_data(Port, match_output(Bytes, Expect, Port));
+ get_data(Port, Expect, <<Acc/binary, Bytes/binary>>);
{Port, eof} ->
Port ! {self(), close},
receive
@@ -160,21 +143,17 @@ get_data(Port, Expect) ->
after 1 -> % force context switch
ok
end,
- match_output(eof, Expect, Port)
+ match_output(Acc, Expect, Port)
end.
-match_output([C|Output], [C|Expect], Port) ->
+match_output(<<C, Output/bits>>, <<C,Expect/bits>>, Port) ->
match_output(Output, Expect, Port);
-match_output([_|_], [_|_], Port) ->
+match_output(<<_, _/bits>>, <<_, _/bits>>, Port) ->
kill_port_and_fail(Port, badmatch);
-match_output([X|Output], [], Port) ->
- kill_port_and_fail(Port, {too_much_data, [X|Output]});
-match_output([], Expect, _Port) ->
- Expect;
-match_output(eof, [], _Port) ->
- [];
-match_output(eof, Expect, Port) ->
- kill_port_and_fail(Port, {unexpected_end_of_input, Expect}).
+match_output(<<_, _/bits>>=Rest, <<>>, Port) ->
+ kill_port_and_fail(Port, {too_much_data, Rest});
+match_output(<<>>, <<>>, _Port) ->
+ ok.
kill_port_and_fail(Port, Reason) ->
unlink(Port),
@@ -913,3 +892,137 @@ do_fd_leak(Bad, N) ->
io:format("Bad error after ~p attempts\n", [N]),
erlang:raise(C, R, Stk)
end.
+
+unicode(Config) ->
+ case file:native_name_encoding() of
+ latin1 ->
+ {comment, "Native name encoding is Latin-1; skipping all tests"};
+ utf8 ->
+ DataDir = proplists:get_value(data_dir, Config),
+ ok = file:set_cwd(proplists:get_value(priv_dir, Config)),
+ test_file_comment(DataDir),
+ test_archive_comment(DataDir),
+ test_bad_comment(DataDir),
+ test_latin1_archive(DataDir),
+ case has_zip() of
+ false ->
+ {comment, "No zip program found; skipping some tests"};
+ true ->
+ case zip_is_unicode_aware() of
+ true ->
+ test_filename_compatibility(),
+ ok;
+ false ->
+ {comment, "Old zip program; skipping some tests"}
+ end
+ end
+ end.
+
+test_filename_compatibility() ->
+ FancyName = "üñíĉòdë한",
+ Archive = "test.zip",
+
+ {ok, Archive} = zip:zip(Archive, [{FancyName, <<"test">>}]),
+ zipinfo_match(Archive, FancyName).
+
+test_file_comment(DataDir) ->
+ Archive = filename:join(DataDir, "zip_file_comment.zip"),
+ Comments = ["a", [246], [1024]],
+ FileNames = [[C] ++ ".txt" || C <- [$a, 246, 1024]],
+ [begin
+ test_zip_file(FileName, Comment, Archive),
+ test_file_comment(FileName, Comment, Archive)
+ end ||
+ Comment <- Comments, FileName <- FileNames],
+ ok.
+
+test_zip_file(FileName, Comment, Archive) ->
+ _ = file:delete(Archive),
+ io:format("*** zip:zip(). Testing FileName ~ts, Comment ~ts\n",
+ [FileName, Comment]),
+ ok = file:write_file(FileName, ["anything"]),
+ {ok, Archive} =
+ zip:zip(Archive, [FileName], [verbose, {comment, Comment}]),
+ zip_check(Archive, Comment, FileName, "").
+
+test_file_comment(FileName, Comment, Archive) ->
+ case test_zip1() of
+ false ->
+ ok;
+ true ->
+ _ = file:delete(Archive),
+ io:format("*** zip(1). Testing FileName ~ts, Comment ~ts\n",
+ [FileName, Comment]),
+ ok = file:write_file(FileName, ["anything"]),
+ R = os:cmd("echo " ++ Comment ++ "| zip -c " ++
+ Archive ++ " " ++ FileName),
+ io:format("os:cmd/1 returns ~lp\n", [R]),
+ zip_check(Archive, "", FileName, Comment)
+ end.
+
+test_archive_comment(DataDir) ->
+ Archive = filename:join(DataDir, "zip_archive_comment.zip"),
+ Chars = [$a, 246, 1024],
+ [test_archive_comment(Char, Archive) || Char <- Chars],
+ ok.
+
+test_archive_comment(Char, Archive) ->
+ case test_zip1() of
+ false ->
+ ok;
+ true ->
+ _ = file:delete(Archive),
+ FileName = "a.txt",
+ Comment = [Char],
+ io:format("*** Testing archive Comment ~ts\n", [Comment]),
+ ok = file:write_file(FileName, ["anything"]),
+
+ {ok, _} =
+ zip:zip(Archive, [FileName], [verbose, {comment, Comment}]),
+ Res = os:cmd("zip -z " ++ Archive),
+ io:format("os:cmd/1 returns ~lp\n", [Res]),
+ true = lists:member(Char, Res),
+
+ os:cmd("echo " ++ Comment ++ "| zip -z "++
+ Archive ++ " " ++ FileName),
+ zip_check(Archive, Comment, FileName, "")
+ end.
+
+test_zip1() ->
+ has_zip() andalso zip_is_unicode_aware().
+
+has_zip() ->
+ os:find_executable("zip") =/= false.
+
+zip_is_unicode_aware() ->
+ S = os:cmd("zip -v | grep 'UNICODE_SUPPORT'"),
+ string:find(S, "UNICODE_SUPPORT") =/= nomatch.
+
+zip_check(Archive, ArchiveComment, FileName, FileNameComment) ->
+ {ok, CommentAndFiles} = zip:table(Archive),
+ io:format("zip:table/1 returns\n ~lp\n", [CommentAndFiles]),
+ io:format("checking archive comment ~lp\n", [ArchiveComment]),
+ [_] = [C || #zip_comment{comment = C} <- CommentAndFiles,
+ C =:= ArchiveComment],
+ io:format("checking filename ~lp\n", [FileName]),
+ io:format("and filename comment ~lp\n", [FileNameComment]),
+ [_] = [F || #zip_file{name = F, comment = C} <- CommentAndFiles,
+ F =:= FileName, C =:= FileNameComment],
+ {ok, FileList} = zip:unzip(Archive, [verbose]),
+ io:format("zip:unzip/2 returns\n ~lp\n", [FileList]),
+ true = lists:member(FileName, FileList),
+ ok.
+
+test_bad_comment(DataDir) ->
+ Archive = filename:join(DataDir, "zip_bad_comment.zip"),
+ FileName = "a.txt",
+ file:write_file(FileName, ["something"]),
+ Comment = [9999999],
+ {error,{bad_unicode,Comment}} =
+ zip:zip(Archive, [FileName], [verbose, {comment, Comment}]).
+
+test_latin1_archive(DataDir) ->
+ Archive = filename:join(DataDir, "zip-latin1.zip"),
+ FileName = [246] ++ ".txt",
+ ArchiveComment = [246],
+ zip_check(Archive, ArchiveComment, FileName, "").
diff --git a/lib/stdlib/test/zip_SUITE_data/zip-latin1.zip b/lib/stdlib/test/zip_SUITE_data/zip-latin1.zip
new file mode 100644
index 0000000000..d54c783653
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/zip-latin1.zip
Binary files differ
diff --git a/lib/stdlib/uc_spec/CaseFolding.txt b/lib/stdlib/uc_spec/CaseFolding.txt
index cce350f49c..7eeb915abf 100644
--- a/lib/stdlib/uc_spec/CaseFolding.txt
+++ b/lib/stdlib/uc_spec/CaseFolding.txt
@@ -1,6 +1,6 @@
-# CaseFolding-11.0.0.txt
-# Date: 2018-01-31, 08:20:09 GMT
-# © 2018 Unicode®, Inc.
+# CaseFolding-12.1.0.txt
+# Date: 2019-03-10, 10:53:00 GMT
+# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
@@ -1227,6 +1227,13 @@ A7B3; C; AB53; # LATIN CAPITAL LETTER CHI
A7B4; C; A7B5; # LATIN CAPITAL LETTER BETA
A7B6; C; A7B7; # LATIN CAPITAL LETTER OMEGA
A7B8; C; A7B9; # LATIN CAPITAL LETTER U WITH STROKE
+A7BA; C; A7BB; # LATIN CAPITAL LETTER GLOTTAL A
+A7BC; C; A7BD; # LATIN CAPITAL LETTER GLOTTAL I
+A7BE; C; A7BF; # LATIN CAPITAL LETTER GLOTTAL U
+A7C2; C; A7C3; # LATIN CAPITAL LETTER ANGLICANA W
+A7C4; C; A794; # LATIN CAPITAL LETTER C WITH PALATAL HOOK
+A7C5; C; 0282; # LATIN CAPITAL LETTER S WITH HOOK
+A7C6; C; 1D8E; # LATIN CAPITAL LETTER Z WITH PALATAL HOOK
AB70; C; 13A0; # CHEROKEE SMALL LETTER A
AB71; C; 13A1; # CHEROKEE SMALL LETTER E
AB72; C; 13A2; # CHEROKEE SMALL LETTER I
diff --git a/lib/stdlib/uc_spec/CompositionExclusions.txt b/lib/stdlib/uc_spec/CompositionExclusions.txt
index ea63595bd3..aa654974be 100644
--- a/lib/stdlib/uc_spec/CompositionExclusions.txt
+++ b/lib/stdlib/uc_spec/CompositionExclusions.txt
@@ -1,6 +1,6 @@
-# CompositionExclusions-11.0.0.txt
-# Date: 2017-12-06, 00:00:00 GMT [KW, LI]
-# © 2017 Unicode®, Inc.
+# CompositionExclusions-12.1.0.txt
+# Date: 2019-03-08, 23:59:00 GMT [KW, LI]
+# © 2019 Unicode®, Inc.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
# Unicode Character Database
diff --git a/lib/stdlib/uc_spec/GraphemeBreakProperty.txt b/lib/stdlib/uc_spec/GraphemeBreakProperty.txt
index 52052e6e33..b75b201f97 100644
--- a/lib/stdlib/uc_spec/GraphemeBreakProperty.txt
+++ b/lib/stdlib/uc_spec/GraphemeBreakProperty.txt
@@ -1,6 +1,6 @@
-# GraphemeBreakProperty-11.0.0.txt
-# Date: 2018-03-16, 20:34:02 GMT
-# © 2018 Unicode®, Inc.
+# GraphemeBreakProperty-12.1.0.txt
+# Date: 2019-03-10, 10:53:12 GMT
+# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
@@ -27,10 +27,10 @@
110CD ; Prepend # Cf KAITHI NUMBER SIGN ABOVE
111C2..111C3 ; Prepend # Lo [2] SHARADA SIGN JIHVAMULIYA..SHARADA SIGN UPADHMANIYA
11A3A ; Prepend # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
-11A86..11A89 ; Prepend # Lo [4] SOYOMBO CLUSTER-INITIAL LETTER RA..SOYOMBO CLUSTER-INITIAL LETTER SA
+11A84..11A89 ; Prepend # Lo [6] SOYOMBO SIGN JIHVAMULIYA..SOYOMBO CLUSTER-INITIAL LETTER SA
11D46 ; Prepend # Lo MASARAM GONDI REPHA
-# Total code points: 20
+# Total code points: 22
# ================================================
@@ -61,10 +61,10 @@
2060..2064 ; Control # Cf [5] WORD JOINER..INVISIBLE PLUS
2065 ; Control # Cn <reserved-2065>
2066..206F ; Control # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
-D800..DFFF ; Control # Cs [2048] <surrogate-D800>..<surrogate-DFFF>
FEFF ; Control # Cf ZERO WIDTH NO-BREAK SPACE
FFF0..FFF8 ; Control # Cn [9] <reserved-FFF0>..<reserved-FFF8>
FFF9..FFFB ; Control # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR
+13430..13438 ; Control # Cf [9] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END SEGMENT
1BCA0..1BCA3 ; Control # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
1D173..1D17A ; Control # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
E0000 ; Control # Cn <reserved-E0000>
@@ -73,7 +73,7 @@ E0002..E001F ; Control # Cn [30] <reserved-E0002>..<reserved-E001F>
E0080..E00FF ; Control # Cn [128] <reserved-E0080>..<reserved-E00FF>
E01F0..E0FFF ; Control # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
-# Total code points: 5925
+# Total code points: 3886
# ================================================
@@ -178,8 +178,7 @@ E01F0..E0FFF ; Control # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
0E34..0E3A ; Extend # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
0E47..0E4E ; Extend # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
0EB1 ; Extend # Mn LAO VOWEL SIGN MAI KAN
-0EB4..0EB9 ; Extend # Mn [6] LAO VOWEL SIGN I..LAO VOWEL SIGN UU
-0EBB..0EBC ; Extend # Mn [2] LAO VOWEL SIGN MAI KON..LAO SEMIVOWEL SIGN LO
+0EB4..0EBC ; Extend # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
0EC8..0ECD ; Extend # Mn [6] LAO TONE MAI EK..LAO NIGGAHITA
0F18..0F19 ; Extend # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
0F35 ; Extend # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
@@ -232,6 +231,7 @@ E01F0..E0FFF ; Control # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
1ABE ; Extend # Me COMBINING PARENTHESES OVERLAY
1B00..1B03 ; Extend # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
1B34 ; Extend # Mn BALINESE SIGN REREKAN
+1B35 ; Extend # Mc BALINESE VOWEL SIGN TEDUNG
1B36..1B3A ; Extend # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
1B3C ; Extend # Mn BALINESE VOWEL SIGN LA LENGA
1B42 ; Extend # Mn BALINESE VOWEL SIGN PEPET
@@ -283,7 +283,7 @@ A947..A951 ; Extend # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
A980..A982 ; Extend # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
A9B3 ; Extend # Mn JAVANESE SIGN CECAK TELU
A9B6..A9B9 ; Extend # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
-A9BC ; Extend # Mn JAVANESE VOWEL SIGN PEPET
+A9BC..A9BD ; Extend # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
A9E5 ; Extend # Mn MYANMAR SIGN SHAN SAW
AA29..AA2E ; Extend # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
AA31..AA32 ; Extend # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
@@ -368,6 +368,9 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
11727..1172B ; Extend # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
1182F..11837 ; Extend # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
11839..1183A ; Extend # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+119D4..119D7 ; Extend # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; Extend # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119E0 ; Extend # Mn NANDINAGARI SIGN VIRAMA
11A01..11A0A ; Extend # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
11A33..11A38 ; Extend # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
11A3B..11A3E ; Extend # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
@@ -394,6 +397,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
11EF3..11EF4 ; Extend # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
16AF0..16AF4 ; Extend # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
16B30..16B36 ; Extend # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+16F4F ; Extend # Mn MIAO SIGN CONSONANT MODIFIER BAR
16F8F..16F92 ; Extend # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
1BC9D..1BC9E ; Extend # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
1D165 ; Extend # Mc MUSICAL SYMBOL COMBINING STEM
@@ -414,13 +418,15 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
1E01B..1E021 ; Extend # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
1E023..1E024 ; Extend # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
1E026..1E02A ; Extend # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+1E130..1E136 ; Extend # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E2EC..1E2EF ; Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
1E8D0..1E8D6 ; Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
1E944..1E94A ; Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
1F3FB..1F3FF ; Extend # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6
E0020..E007F ; Extend # Cf [96] TAG SPACE..CANCEL TAG
E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
-# Total code points: 1948
+# Total code points: 1970
# ================================================
@@ -489,7 +495,6 @@ E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
1A57 ; SpacingMark # Mc TAI THAM CONSONANT SIGN LA TANG LAI
1A6D..1A72 ; SpacingMark # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI
1B04 ; SpacingMark # Mc BALINESE SIGN BISAH
-1B35 ; SpacingMark # Mc BALINESE VOWEL SIGN TEDUNG
1B3B ; SpacingMark # Mc BALINESE VOWEL SIGN RA REPA TEDUNG
1B3D..1B41 ; SpacingMark # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG
1B43..1B44 ; SpacingMark # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG
@@ -504,7 +509,6 @@ E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
1C24..1C2B ; SpacingMark # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU
1C34..1C35 ; SpacingMark # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG
1CE1 ; SpacingMark # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA
-1CF2..1CF3 ; SpacingMark # Mc [2] VEDIC SIGN ARDHAVISARGA..VEDIC SIGN ROTATED ARDHAVISARGA
1CF7 ; SpacingMark # Mc VEDIC SIGN ATIKRAMA
A823..A824 ; SpacingMark # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I
A827 ; SpacingMark # Mc SYLOTI NAGRI VOWEL SIGN OO
@@ -514,7 +518,7 @@ A952..A953 ; SpacingMark # Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA
A983 ; SpacingMark # Mc JAVANESE SIGN WIGNYAN
A9B4..A9B5 ; SpacingMark # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG
A9BA..A9BB ; SpacingMark # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE
-A9BD..A9C0 ; SpacingMark # Mc [4] JAVANESE CONSONANT SIGN KERET..JAVANESE PANGKON
+A9BE..A9C0 ; SpacingMark # Mc [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON
AA2F..AA30 ; SpacingMark # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI
AA33..AA34 ; SpacingMark # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA
AA4D ; SpacingMark # Mc CHAM CONSONANT SIGN FINAL H
@@ -566,6 +570,9 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK
11726 ; SpacingMark # Mc AHOM VOWEL SIGN E
1182C..1182E ; SpacingMark # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
11838 ; SpacingMark # Mc DOGRA SIGN VISARGA
+119D1..119D3 ; SpacingMark # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II
+119DC..119DF ; SpacingMark # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA
+119E4 ; SpacingMark # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E
11A39 ; SpacingMark # Mc ZANABAZAR SQUARE SIGN VISARGA
11A57..11A58 ; SpacingMark # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU
11A97 ; SpacingMark # Mc SOYOMBO SIGN VISARGA
@@ -578,11 +585,11 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK
11D93..11D94 ; SpacingMark # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
11D96 ; SpacingMark # Mc GUNJALA GONDI SIGN VISARGA
11EF5..11EF6 ; SpacingMark # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
-16F51..16F7E ; SpacingMark # Mc [46] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN NG
+16F51..16F87 ; SpacingMark # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
1D166 ; SpacingMark # Mc MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
1D16D ; SpacingMark # Mc MUSICAL SYMBOL COMBINING AUGMENTATION DOT
-# Total code points: 362
+# Total code points: 375
# ================================================
diff --git a/lib/stdlib/uc_spec/PropList.txt b/lib/stdlib/uc_spec/PropList.txt
index ef86795abe..4394602fea 100644
--- a/lib/stdlib/uc_spec/PropList.txt
+++ b/lib/stdlib/uc_spec/PropList.txt
@@ -1,6 +1,6 @@
-# PropList-11.0.0.txt
-# Date: 2018-03-15, 04:28:35 GMT
-# © 2018 Unicode®, Inc.
+# PropList-12.1.0.txt
+# Date: 2019-03-10, 10:53:16 GMT
+# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
@@ -138,7 +138,7 @@ FF63 ; Quotation_Mark # Pe HALFWIDTH RIGHT CORNER BRACKET
0F0D..0F12 ; Terminal_Punctuation # Po [6] TIBETAN MARK SHAD..TIBETAN MARK RGYA GRAM SHAD
104A..104B ; Terminal_Punctuation # Po [2] MYANMAR SIGN LITTLE SECTION..MYANMAR SIGN SECTION
1361..1368 ; Terminal_Punctuation # Po [8] ETHIOPIC WORDSPACE..ETHIOPIC PARAGRAPH SEPARATOR
-166D..166E ; Terminal_Punctuation # Po [2] CANADIAN SYLLABICS CHI SIGN..CANADIAN SYLLABICS FULL STOP
+166E ; Terminal_Punctuation # Po CANADIAN SYLLABICS FULL STOP
16EB..16ED ; Terminal_Punctuation # Po [3] RUNIC SINGLE PUNCTUATION..RUNIC CROSS PUNCTUATION
1735..1736 ; Terminal_Punctuation # Po [2] PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION
17D4..17D6 ; Terminal_Punctuation # Po [3] KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH
@@ -157,7 +157,7 @@ FF63 ; Quotation_Mark # Pe HALFWIDTH RIGHT CORNER BRACKET
2E3C ; Terminal_Punctuation # Po STENOGRAPHIC FULL STOP
2E41 ; Terminal_Punctuation # Po REVERSED COMMA
2E4C ; Terminal_Punctuation # Po MEDIEVAL COMMA
-2E4E ; Terminal_Punctuation # Po PUNCTUS ELEVATUS MARK
+2E4E..2E4F ; Terminal_Punctuation # Po [2] PUNCTUS ELEVATUS MARK..CORNISH VERSE DIVIDER
3001..3002 ; Terminal_Punctuation # Po [2] IDEOGRAPHIC COMMA..IDEOGRAPHIC FULL STOP
A4FE..A4FF ; Terminal_Punctuation # Po [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP
A60D..A60F ; Terminal_Punctuation # Po [3] VAI COMMA..VAI QUESTION MARK
@@ -553,15 +553,17 @@ FF41..FF46 ; Hex_Digit # L& [6] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH L
1056..1057 ; Other_Alphabetic # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR
1058..1059 ; Other_Alphabetic # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
105E..1060 ; Other_Alphabetic # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
-1062 ; Other_Alphabetic # Mc MYANMAR VOWEL SIGN SGAW KAREN EU
-1067..1068 ; Other_Alphabetic # Mc [2] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR VOWEL SIGN WESTERN PWO KAREN UE
+1062..1064 ; Other_Alphabetic # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO
+1067..106D ; Other_Alphabetic # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5
1071..1074 ; Other_Alphabetic # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
1082 ; Other_Alphabetic # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
1083..1084 ; Other_Alphabetic # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E
1085..1086 ; Other_Alphabetic # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
-109C ; Other_Alphabetic # Mc MYANMAR VOWEL SIGN AITON A
+1087..108C ; Other_Alphabetic # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3
+108D ; Other_Alphabetic # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+108F ; Other_Alphabetic # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5
+109A..109C ; Other_Alphabetic # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A
109D ; Other_Alphabetic # Mn MYANMAR VOWEL SIGN AITON AI
-135F ; Other_Alphabetic # Mn ETHIOPIC COMBINING GEMINATION MARK
1712..1713 ; Other_Alphabetic # Mn [2] TAGALOG VOWEL SIGN I..TAGALOG VOWEL SIGN U
1732..1733 ; Other_Alphabetic # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U
1752..1753 ; Other_Alphabetic # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
@@ -618,18 +620,21 @@ FF41..FF46 ; Hex_Digit # L& [6] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH L
1C24..1C2B ; Other_Alphabetic # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU
1C2C..1C33 ; Other_Alphabetic # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
1C34..1C35 ; Other_Alphabetic # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG
-1CF2..1CF3 ; Other_Alphabetic # Mc [2] VEDIC SIGN ARDHAVISARGA..VEDIC SIGN ROTATED ARDHAVISARGA
+1C36 ; Other_Alphabetic # Mn LEPCHA SIGN RAN
1DE7..1DF4 ; Other_Alphabetic # Mn [14] COMBINING LATIN SMALL LETTER ALPHA..COMBINING LATIN SMALL LETTER U WITH DIAERESIS
24B6..24E9 ; Other_Alphabetic # So [52] CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN SMALL LETTER Z
2DE0..2DFF ; Other_Alphabetic # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
A674..A67B ; Other_Alphabetic # Mn [8] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC LETTER OMEGA
A69E..A69F ; Other_Alphabetic # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+A802 ; Other_Alphabetic # Mn SYLOTI NAGRI SIGN DVISVARA
+A80B ; Other_Alphabetic # Mn SYLOTI NAGRI SIGN ANUSVARA
A823..A824 ; Other_Alphabetic # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I
A825..A826 ; Other_Alphabetic # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
A827 ; Other_Alphabetic # Mc SYLOTI NAGRI VOWEL SIGN OO
A880..A881 ; Other_Alphabetic # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA
A8B4..A8C3 ; Other_Alphabetic # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU
A8C5 ; Other_Alphabetic # Mn SAURASHTRA SIGN CANDRABINDU
+A8FF ; Other_Alphabetic # Mn DEVANAGARI VOWEL SIGN AY
A926..A92A ; Other_Alphabetic # Mn [5] KAYAH LI VOWEL UE..KAYAH LI VOWEL O
A947..A951 ; Other_Alphabetic # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
A952 ; Other_Alphabetic # Mc REJANG CONSONANT SIGN H
@@ -638,8 +643,9 @@ A983 ; Other_Alphabetic # Mc JAVANESE SIGN WIGNYAN
A9B4..A9B5 ; Other_Alphabetic # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG
A9B6..A9B9 ; Other_Alphabetic # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
A9BA..A9BB ; Other_Alphabetic # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE
-A9BC ; Other_Alphabetic # Mn JAVANESE VOWEL SIGN PEPET
-A9BD..A9BF ; Other_Alphabetic # Mc [3] JAVANESE CONSONANT SIGN KERET..JAVANESE CONSONANT SIGN CAKRA
+A9BC..A9BD ; Other_Alphabetic # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+A9BE..A9BF ; Other_Alphabetic # Mc [2] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE CONSONANT SIGN CAKRA
+A9E5 ; Other_Alphabetic # Mn MYANMAR SIGN SHAN SAW
AA29..AA2E ; Other_Alphabetic # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
AA2F..AA30 ; Other_Alphabetic # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI
AA31..AA32 ; Other_Alphabetic # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
@@ -648,6 +654,9 @@ AA35..AA36 ; Other_Alphabetic # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONA
AA43 ; Other_Alphabetic # Mn CHAM CONSONANT SIGN FINAL NG
AA4C ; Other_Alphabetic # Mn CHAM CONSONANT SIGN FINAL M
AA4D ; Other_Alphabetic # Mc CHAM CONSONANT SIGN FINAL H
+AA7B ; Other_Alphabetic # Mc MYANMAR SIGN PAO KAREN TONE
+AA7C ; Other_Alphabetic # Mn MYANMAR SIGN TAI LAING TONE-2
+AA7D ; Other_Alphabetic # Mc MYANMAR SIGN TAI LAING TONE-5
AAB0 ; Other_Alphabetic # Mn TAI VIET MAI KANG
AAB2..AAB4 ; Other_Alphabetic # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
AAB7..AAB8 ; Other_Alphabetic # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
@@ -740,6 +749,11 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
1182C..1182E ; Other_Alphabetic # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
1182F..11837 ; Other_Alphabetic # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
11838 ; Other_Alphabetic # Mc DOGRA SIGN VISARGA
+119D1..119D3 ; Other_Alphabetic # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II
+119D4..119D7 ; Other_Alphabetic # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+119DA..119DB ; Other_Alphabetic # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+119DC..119DF ; Other_Alphabetic # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA
+119E4 ; Other_Alphabetic # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E
11A01..11A0A ; Other_Alphabetic # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
11A35..11A38 ; Other_Alphabetic # Mn [4] ZANABAZAR SQUARE SIGN CANDRABINDU..ZANABAZAR SQUARE SIGN ANUSVARA
11A39 ; Other_Alphabetic # Mc ZANABAZAR SQUARE SIGN VISARGA
@@ -773,8 +787,9 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
11D96 ; Other_Alphabetic # Mc GUNJALA GONDI SIGN VISARGA
11EF3..11EF4 ; Other_Alphabetic # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
11EF5..11EF6 ; Other_Alphabetic # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
-16B30..16B36 ; Other_Alphabetic # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
-16F51..16F7E ; Other_Alphabetic # Mc [46] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN NG
+16F4F ; Other_Alphabetic # Mn MIAO SIGN CONSONANT MODIFIER BAR
+16F51..16F87 ; Other_Alphabetic # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
+16F8F..16F92 ; Other_Alphabetic # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
1BC9E ; Other_Alphabetic # Mn DUPLOYAN DOUBLE MARK
1E000..1E006 ; Other_Alphabetic # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
1E008..1E018 ; Other_Alphabetic # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
@@ -786,7 +801,7 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
1F150..1F169 ; Other_Alphabetic # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z
1F170..1F189 ; Other_Alphabetic # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z
-# Total code points: 1334
+# Total code points: 1377
# ================================================
@@ -798,7 +813,7 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
4E00..9FEF ; Ideographic # Lo [20976] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEF
F900..FA6D ; Ideographic # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D
FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9
-17000..187F1 ; Ideographic # Lo [6130] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F1
+17000..187F7 ; Ideographic # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7
18800..18AF2 ; Ideographic # Lo [755] TANGUT COMPONENT-001..TANGUT COMPONENT-755
1B170..1B2FB ; Ideographic # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
20000..2A6D6 ; Ideographic # Lo [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D6
@@ -808,7 +823,7 @@ FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COM
2CEB0..2EBE0 ; Ideographic # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0
2F800..2FA1D ; Ideographic # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D
-# Total code points: 96184
+# Total code points: 96190
# ================================================
@@ -876,6 +891,7 @@ FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COM
0DCA ; Diacritic # Mn SINHALA SIGN AL-LAKUNA
0E47..0E4C ; Diacritic # Mn [6] THAI CHARACTER MAITAIKHU..THAI CHARACTER THANTHAKHAT
0E4E ; Diacritic # Mn THAI CHARACTER YAMAKKAN
+0EBA ; Diacritic # Mn LAO SIGN PALI VIRAMA
0EC8..0ECC ; Diacritic # Mn [5] LAO TONE MAI EK..LAO CANCELLATION MARK
0F18..0F19 ; Diacritic # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
0F35 ; Diacritic # Mn TIBETAN MARK NGAS BZUNG NYI ZLA
@@ -887,10 +903,13 @@ FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COM
0FC6 ; Diacritic # Mn TIBETAN SYMBOL PADMA GDAN
1037 ; Diacritic # Mn MYANMAR SIGN DOT BELOW
1039..103A ; Diacritic # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+1063..1064 ; Diacritic # Mc [2] MYANMAR TONE MARK SGAW KAREN HATHI..MYANMAR TONE MARK SGAW KAREN KE PHO
+1069..106D ; Diacritic # Mc [5] MYANMAR SIGN WESTERN PWO KAREN TONE-1..MYANMAR SIGN WESTERN PWO KAREN TONE-5
1087..108C ; Diacritic # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3
108D ; Diacritic # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
108F ; Diacritic # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5
109A..109B ; Diacritic # Mc [2] MYANMAR SIGN KHAMTI TONE-1..MYANMAR SIGN KHAMTI TONE-3
+135D..135F ; Diacritic # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
17C9..17D3 ; Diacritic # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
17DD ; Diacritic # Mn KHMER SIGN ATTHACAN
1939..193B ; Diacritic # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
@@ -935,9 +954,11 @@ A67C..A67D ; Diacritic # Mn [2] COMBINING CYRILLIC KAVYKA..COMBINING CYRILL
A67F ; Diacritic # Lm CYRILLIC PAYEROK
A69C..A69D ; Diacritic # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN
A6F0..A6F1 ; Diacritic # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+A700..A716 ; Diacritic # Sk [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR
A717..A71F ; Diacritic # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK
A720..A721 ; Diacritic # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE
A788 ; Diacritic # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT
+A789..A78A ; Diacritic # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN
A7F8..A7F9 ; Diacritic # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE
A8C4 ; Diacritic # Mn SAURASHTRA SIGN VIRAMA
A8E0..A8F1 ; Diacritic # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
@@ -992,6 +1013,7 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON
116B7 ; Diacritic # Mn TAKRI SIGN NUKTA
1172B ; Diacritic # Mn AHOM SIGN KILLER
11839..1183A ; Diacritic # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+119E0 ; Diacritic # Mn NANDINAGARI SIGN VIRAMA
11A34 ; Diacritic # Mn ZANABAZAR SQUARE SIGN VIRAMA
11A47 ; Diacritic # Mn ZANABAZAR SQUARE SUBJOINER
11A99 ; Diacritic # Mn SOYOMBO SUBJOINER
@@ -1000,6 +1022,7 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON
11D44..11D45 ; Diacritic # Mn [2] MASARAM GONDI SIGN HALANTA..MASARAM GONDI VIRAMA
11D97 ; Diacritic # Mn GUNJALA GONDI VIRAMA
16AF0..16AF4 ; Diacritic # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+16B30..16B36 ; Diacritic # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
16F8F..16F92 ; Diacritic # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
16F93..16F9F ; Diacritic # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
1D167..1D169 ; Diacritic # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
@@ -1007,11 +1030,13 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON
1D17B..1D182 ; Diacritic # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
1D185..1D18B ; Diacritic # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
1D1AA..1D1AD ; Diacritic # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+1E130..1E136 ; Diacritic # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+1E2EC..1E2EF ; Diacritic # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
1E8D0..1E8D6 ; Diacritic # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
1E944..1E946 ; Diacritic # Mn [3] ADLAM ALIF LENGTHENER..ADLAM GEMINATION MARK
1E948..1E94A ; Diacritic # Mn [3] ADLAM CONSONANT MODIFIER..ADLAM NUKTA
-# Total code points: 818
+# Total code points: 873
# ================================================
@@ -1043,9 +1068,11 @@ FF70 ; Extender # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND
11A98 ; Extender # Mn SOYOMBO GEMINATION MARK
16B42..16B43 ; Extender # Lm [2] PAHAWH HMONG SIGN VOS NRUA..PAHAWH HMONG SIGN IB YAM
16FE0..16FE1 ; Extender # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK
+16FE3 ; Extender # Lm OLD CHINESE ITERATION MARK
+1E13C..1E13D ; Extender # Lm [2] NYIAKENG PUACHUE HMONG SIGN XW XW..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER
1E944..1E946 ; Extender # Mn [3] ADLAM ALIF LENGTHENER..ADLAM GEMINATION MARK
-# Total code points: 44
+# Total code points: 47
# ================================================
@@ -1119,6 +1146,7 @@ FFFFE..FFFFF ; Noncharacter_Code_Point # Cn [2] <noncharacter-FFFFE>..<noncha
0D57 ; Other_Grapheme_Extend # Mc MALAYALAM AU LENGTH MARK
0DCF ; Other_Grapheme_Extend # Mc SINHALA VOWEL SIGN AELA-PILLA
0DDF ; Other_Grapheme_Extend # Mc SINHALA VOWEL SIGN GAYANUKITTA
+1B35 ; Other_Grapheme_Extend # Mc BALINESE VOWEL SIGN TEDUNG
200C ; Other_Grapheme_Extend # Cf ZERO WIDTH NON-JOINER
302E..302F ; Other_Grapheme_Extend # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK
FF9E..FF9F ; Other_Grapheme_Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
@@ -1131,7 +1159,7 @@ FF9E..FF9F ; Other_Grapheme_Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND
1D16E..1D172 ; Other_Grapheme_Extend # Mc [5] MUSICAL SYMBOL COMBINING FLAG-1..MUSICAL SYMBOL COMBINING FLAG-5
E0020..E007F ; Other_Grapheme_Extend # Cf [96] TAG SPACE..CANCEL TAG
-# Total code points: 125
+# Total code points: 126
# ================================================
@@ -1547,10 +1575,7 @@ E0100..E01EF ; Variation_Selector # Mn [240] VARIATION SELECTOR-17..VARIATION S
2B74..2B75 ; Pattern_Syntax # Cn [2] <reserved-2B74>..<reserved-2B75>
2B76..2B95 ; Pattern_Syntax # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW
2B96..2B97 ; Pattern_Syntax # Cn [2] <reserved-2B96>..<reserved-2B97>
-2B98..2BC8 ; Pattern_Syntax # So [49] THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD..BLACK MEDIUM RIGHT-POINTING TRIANGLE CENTRED
-2BC9 ; Pattern_Syntax # Cn <reserved-2BC9>
-2BCA..2BFE ; Pattern_Syntax # So [53] TOP HALF BLACK CIRCLE..REVERSED RIGHT ANGLE
-2BFF ; Pattern_Syntax # Cn <reserved-2BFF>
+2B98..2BFF ; Pattern_Syntax # So [104] THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD..HELLSCHREIBER PAUSE SYMBOL
2E00..2E01 ; Pattern_Syntax # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER
2E02 ; Pattern_Syntax # Pi LEFT SUBSTITUTION BRACKET
2E03 ; Pattern_Syntax # Pf RIGHT SUBSTITUTION BRACKET
@@ -1588,8 +1613,8 @@ E0100..E01EF ; Variation_Selector # Mn [240] VARIATION SELECTOR-17..VARIATION S
2E40 ; Pattern_Syntax # Pd DOUBLE HYPHEN
2E41 ; Pattern_Syntax # Po REVERSED COMMA
2E42 ; Pattern_Syntax # Ps DOUBLE LOW-REVERSED-9 QUOTATION MARK
-2E43..2E4E ; Pattern_Syntax # Po [12] DASH WITH LEFT UPTURN..PUNCTUS ELEVATUS MARK
-2E4F..2E7F ; Pattern_Syntax # Cn [49] <reserved-2E4F>..<reserved-2E7F>
+2E43..2E4F ; Pattern_Syntax # Po [13] DASH WITH LEFT UPTURN..CORNISH VERSE DIVIDER
+2E50..2E7F ; Pattern_Syntax # Cn [48] <reserved-2E50>..<reserved-2E7F>
3001..3003 ; Pattern_Syntax # Po [3] IDEOGRAPHIC COMMA..DITTO MARK
3008 ; Pattern_Syntax # Ps LEFT ANGLE BRACKET
3009 ; Pattern_Syntax # Pe RIGHT ANGLE BRACKET
diff --git a/lib/stdlib/uc_spec/README-UPDATE.txt b/lib/stdlib/uc_spec/README-UPDATE.txt
index e1f5c8fcd0..73274e512a 100644
--- a/lib/stdlib/uc_spec/README-UPDATE.txt
+++ b/lib/stdlib/uc_spec/README-UPDATE.txt
@@ -2,12 +2,10 @@ When updating the unicode version copy the necessary files to this
directory.
And update the test files in stdlib/test/unicode_util_SUITE_data/*
-Unicode 11 was updated from:
-https://www.unicode.org/Public/11.0.0/ucd/
-https://www.unicode.org/Public/11.0.0/ucd/auxiliary/
-https://www.unicode.org/Public/emoji/11.0/
+Unicode 12.1 was updated from:
+https://www.unicode.org/Public/12.1.0/ucd/
+https://www.unicode.org/Public/12.1.0/ucd/auxiliary/
+https://www.unicode.org/Public/emoji/12.0/
Update the spec_version(..) function in the generator,
gen_unicode_mod.escript
-
-
diff --git a/lib/stdlib/uc_spec/SpecialCasing.txt b/lib/stdlib/uc_spec/SpecialCasing.txt
index c90d09acb3..1c04aacf97 100644
--- a/lib/stdlib/uc_spec/SpecialCasing.txt
+++ b/lib/stdlib/uc_spec/SpecialCasing.txt
@@ -1,6 +1,6 @@
-# SpecialCasing-11.0.0.txt
-# Date: 2018-02-22, 06:16:47 GMT
-# © 2018 Unicode®, Inc.
+# SpecialCasing-12.1.0.txt
+# Date: 2019-03-10, 10:53:28 GMT
+# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
diff --git a/lib/stdlib/uc_spec/UnicodeData.txt b/lib/stdlib/uc_spec/UnicodeData.txt
index ec32fafbce..e65aec52f7 100644
--- a/lib/stdlib/uc_spec/UnicodeData.txt
+++ b/lib/stdlib/uc_spec/UnicodeData.txt
@@ -640,7 +640,7 @@
027F;LATIN SMALL LETTER REVERSED R WITH FISHHOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER REVERSED FISHHOOK R;;;;
0280;LATIN LETTER SMALL CAPITAL R;Ll;0;L;;;;;N;;;01A6;;01A6
0281;LATIN LETTER SMALL CAPITAL INVERTED R;Ll;0;L;;;;;N;;;;;
-0282;LATIN SMALL LETTER S WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER S HOOK;;;;
+0282;LATIN SMALL LETTER S WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER S HOOK;;A7C5;;A7C5
0283;LATIN SMALL LETTER ESH;Ll;0;L;;;;;N;;;01A9;;01A9
0284;LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER DOTLESS J BAR HOOK;;;;
0285;LATIN SMALL LETTER SQUAT REVERSED ESH;Ll;0;L;;;;;N;;;;;
@@ -2809,6 +2809,7 @@
0C6D;TELUGU DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;;
0C6E;TELUGU DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;;
0C6F;TELUGU DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
+0C77;TELUGU SIGN SIDDHAM;Po;0;L;;;;;N;;;;;
0C78;TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR;No;0;ON;;;;0;N;;;;;
0C79;TELUGU FRACTION DIGIT ONE FOR ODD POWERS OF FOUR;No;0;ON;;;;1;N;;;;;
0C7A;TELUGU FRACTION DIGIT TWO FOR ODD POWERS OF FOUR;No;0;ON;;;;2;N;;;;;
@@ -3203,14 +3204,24 @@
0E81;LAO LETTER KO;Lo;0;L;;;;;N;;;;;
0E82;LAO LETTER KHO SUNG;Lo;0;L;;;;;N;;;;;
0E84;LAO LETTER KHO TAM;Lo;0;L;;;;;N;;;;;
+0E86;LAO LETTER PALI GHA;Lo;0;L;;;;;N;;;;;
0E87;LAO LETTER NGO;Lo;0;L;;;;;N;;;;;
0E88;LAO LETTER CO;Lo;0;L;;;;;N;;;;;
+0E89;LAO LETTER PALI CHA;Lo;0;L;;;;;N;;;;;
0E8A;LAO LETTER SO TAM;Lo;0;L;;;;;N;;;;;
+0E8C;LAO LETTER PALI JHA;Lo;0;L;;;;;N;;;;;
0E8D;LAO LETTER NYO;Lo;0;L;;;;;N;;;;;
+0E8E;LAO LETTER PALI NYA;Lo;0;L;;;;;N;;;;;
+0E8F;LAO LETTER PALI TTA;Lo;0;L;;;;;N;;;;;
+0E90;LAO LETTER PALI TTHA;Lo;0;L;;;;;N;;;;;
+0E91;LAO LETTER PALI DDA;Lo;0;L;;;;;N;;;;;
+0E92;LAO LETTER PALI DDHA;Lo;0;L;;;;;N;;;;;
+0E93;LAO LETTER PALI NNA;Lo;0;L;;;;;N;;;;;
0E94;LAO LETTER DO;Lo;0;L;;;;;N;;;;;
0E95;LAO LETTER TO;Lo;0;L;;;;;N;;;;;
0E96;LAO LETTER THO SUNG;Lo;0;L;;;;;N;;;;;
0E97;LAO LETTER THO TAM;Lo;0;L;;;;;N;;;;;
+0E98;LAO LETTER PALI DHA;Lo;0;L;;;;;N;;;;;
0E99;LAO LETTER NO;Lo;0;L;;;;;N;;;;;
0E9A;LAO LETTER BO;Lo;0;L;;;;;N;;;;;
0E9B;LAO LETTER PO;Lo;0;L;;;;;N;;;;;
@@ -3218,13 +3229,17 @@
0E9D;LAO LETTER FO TAM;Lo;0;L;;;;;N;;;;;
0E9E;LAO LETTER PHO TAM;Lo;0;L;;;;;N;;;;;
0E9F;LAO LETTER FO SUNG;Lo;0;L;;;;;N;;;;;
+0EA0;LAO LETTER PALI BHA;Lo;0;L;;;;;N;;;;;
0EA1;LAO LETTER MO;Lo;0;L;;;;;N;;;;;
0EA2;LAO LETTER YO;Lo;0;L;;;;;N;;;;;
0EA3;LAO LETTER LO LING;Lo;0;L;;;;;N;;;;;
0EA5;LAO LETTER LO LOOT;Lo;0;L;;;;;N;;;;;
0EA7;LAO LETTER WO;Lo;0;L;;;;;N;;;;;
+0EA8;LAO LETTER SANSKRIT SHA;Lo;0;L;;;;;N;;;;;
+0EA9;LAO LETTER SANSKRIT SSA;Lo;0;L;;;;;N;;;;;
0EAA;LAO LETTER SO SUNG;Lo;0;L;;;;;N;;;;;
0EAB;LAO LETTER HO SUNG;Lo;0;L;;;;;N;;;;;
+0EAC;LAO LETTER PALI LLA;Lo;0;L;;;;;N;;;;;
0EAD;LAO LETTER O;Lo;0;L;;;;;N;;;;;
0EAE;LAO LETTER HO TAM;Lo;0;L;;;;;N;;;;;
0EAF;LAO ELLIPSIS;Lo;0;L;;;;;N;;;;;
@@ -3238,6 +3253,7 @@
0EB7;LAO VOWEL SIGN YY;Mn;0;NSM;;;;;N;;;;;
0EB8;LAO VOWEL SIGN U;Mn;118;NSM;;;;;N;;;;;
0EB9;LAO VOWEL SIGN UU;Mn;118;NSM;;;;;N;;;;;
+0EBA;LAO SIGN PALI VIRAMA;Mn;9;NSM;;;;;N;;;;;
0EBB;LAO VOWEL SIGN MAI KON;Mn;0;NSM;;;;;N;;;;;
0EBC;LAO SEMIVOWEL SIGN LO;Mn;0;NSM;;;;;N;;;;;
0EBD;LAO SEMIVOWEL SIGN NYO;Lo;0;L;;;;;N;;;;;
@@ -5079,7 +5095,7 @@
166A;CANADIAN SYLLABICS CARRIER TTSEE;Lo;0;L;;;;;N;;;;;
166B;CANADIAN SYLLABICS CARRIER TTSI;Lo;0;L;;;;;N;;;;;
166C;CANADIAN SYLLABICS CARRIER TTSA;Lo;0;L;;;;;N;;;;;
-166D;CANADIAN SYLLABICS CHI SIGN;Po;0;L;;;;;N;;;;;
+166D;CANADIAN SYLLABICS CHI SIGN;So;0;L;;;;;N;;;;;
166E;CANADIAN SYLLABICS FULL STOP;Po;0;L;;;;;N;;;;;
166F;CANADIAN SYLLABICS QAI;Lo;0;L;;;;;N;;;;;
1670;CANADIAN SYLLABICS NGAI;Lo;0;L;;;;;N;;;;;
@@ -6488,14 +6504,15 @@
1CEF;VEDIC SIGN LONG ANUSVARA;Lo;0;L;;;;;N;;;;;
1CF0;VEDIC SIGN RTHANG LONG ANUSVARA;Lo;0;L;;;;;N;;;;;
1CF1;VEDIC SIGN ANUSVARA UBHAYATO MUKHA;Lo;0;L;;;;;N;;;;;
-1CF2;VEDIC SIGN ARDHAVISARGA;Mc;0;L;;;;;N;;;;;
-1CF3;VEDIC SIGN ROTATED ARDHAVISARGA;Mc;0;L;;;;;N;;;;;
+1CF2;VEDIC SIGN ARDHAVISARGA;Lo;0;L;;;;;N;;;;;
+1CF3;VEDIC SIGN ROTATED ARDHAVISARGA;Lo;0;L;;;;;N;;;;;
1CF4;VEDIC TONE CANDRA ABOVE;Mn;230;NSM;;;;;N;;;;;
1CF5;VEDIC SIGN JIHVAMULIYA;Lo;0;L;;;;;N;;;;;
1CF6;VEDIC SIGN UPADHMANIYA;Lo;0;L;;;;;N;;;;;
1CF7;VEDIC SIGN ATIKRAMA;Mc;0;L;;;;;N;;;;;
1CF8;VEDIC TONE RING ABOVE;Mn;230;NSM;;;;;N;;;;;
1CF9;VEDIC TONE DOUBLE RING ABOVE;Mn;230;NSM;;;;;N;;;;;
+1CFA;VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA;Lo;0;L;;;;;N;;;;;
1D00;LATIN LETTER SMALL CAPITAL A;Ll;0;L;;;;;N;;;;;
1D01;LATIN LETTER SMALL CAPITAL AE;Ll;0;L;;;;;N;;;;;
1D02;LATIN SMALL LETTER TURNED AE;Ll;0;L;;;;;N;;;;;
@@ -6638,7 +6655,7 @@
1D8B;LATIN SMALL LETTER ESH WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;;
1D8C;LATIN SMALL LETTER V WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;;
1D8D;LATIN SMALL LETTER X WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;;
-1D8E;LATIN SMALL LETTER Z WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;;
+1D8E;LATIN SMALL LETTER Z WITH PALATAL HOOK;Ll;0;L;;;;;N;;;A7C6;;A7C6
1D8F;LATIN SMALL LETTER A WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;;
1D90;LATIN SMALL LETTER ALPHA WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;;
1D91;LATIN SMALL LETTER D WITH HOOK AND TAIL;Ll;0;L;;;;;N;;;;;
@@ -10165,6 +10182,7 @@
2BC6;BLACK MEDIUM DOWN-POINTING TRIANGLE CENTRED;So;0;ON;;;;;N;;;;;
2BC7;BLACK MEDIUM LEFT-POINTING TRIANGLE CENTRED;So;0;ON;;;;;N;;;;;
2BC8;BLACK MEDIUM RIGHT-POINTING TRIANGLE CENTRED;So;0;ON;;;;;N;;;;;
+2BC9;NEPTUNE FORM TWO;So;0;ON;;;;;N;;;;;
2BCA;TOP HALF BLACK CIRCLE;So;0;ON;;;;;N;;;;;
2BCB;BOTTOM HALF BLACK CIRCLE;So;0;ON;;;;;N;;;;;
2BCC;LIGHT FOUR POINTED BLACK CUSP;So;0;ON;;;;;N;;;;;
@@ -10218,6 +10236,7 @@
2BFC;DOUBLED SYMBOL;So;0;ON;;;;;N;;;;;
2BFD;PASSED SYMBOL;So;0;ON;;;;;N;;;;;
2BFE;REVERSED RIGHT ANGLE;So;0;ON;;;;;Y;;;;;
+2BFF;HELLSCHREIBER PAUSE SYMBOL;So;0;ON;;;;;N;;;;;
2C00;GLAGOLITIC CAPITAL LETTER AZU;Lu;0;L;;;;;N;;;;2C30;
2C01;GLAGOLITIC CAPITAL LETTER BUKY;Lu;0;L;;;;;N;;;;2C31;
2C02;GLAGOLITIC CAPITAL LETTER VEDE;Lu;0;L;;;;;N;;;;2C32;
@@ -10756,6 +10775,7 @@
2E4C;MEDIEVAL COMMA;Po;0;ON;;;;;N;;;;;
2E4D;PARAGRAPHUS MARK;Po;0;ON;;;;;N;;;;;
2E4E;PUNCTUS ELEVATUS MARK;Po;0;ON;;;;;N;;;;;
+2E4F;CORNISH VERSE DIVIDER;Po;0;ON;;;;;N;;;;;
2E80;CJK RADICAL REPEAT;So;0;ON;;;;;N;;;;;
2E81;CJK RADICAL CLIFF;So;0;ON;;;;;N;;;;;
2E82;CJK RADICAL SECOND ONE;So;0;ON;;;;;N;;;;;
@@ -11836,6 +11856,7 @@
32FC;CIRCLED KATAKANA WI;So;0;L;<circle> 30F0;;;;N;;;;;
32FD;CIRCLED KATAKANA WE;So;0;L;<circle> 30F1;;;;N;;;;;
32FE;CIRCLED KATAKANA WO;So;0;L;<circle> 30F2;;;;N;;;;;
+32FF;SQUARE ERA NAME REIWA;So;0;L;<square> 4EE4 548C;;;;N;;;;;
3300;SQUARE APAATO;So;0;L;<square> 30A2 30D1 30FC 30C8;;;;N;SQUARED APAATO;;;;
3301;SQUARE ARUHUA;So;0;L;<square> 30A2 30EB 30D5 30A1;;;;N;SQUARED ARUHUA;;;;
3302;SQUARE ANPEA;So;0;L;<square> 30A2 30F3 30DA 30A2;;;;N;SQUARED ANPEA;;;;
@@ -14060,7 +14081,7 @@ A790;LATIN CAPITAL LETTER N WITH DESCENDER;Lu;0;L;;;;;N;;;;A791;
A791;LATIN SMALL LETTER N WITH DESCENDER;Ll;0;L;;;;;N;;;A790;;A790
A792;LATIN CAPITAL LETTER C WITH BAR;Lu;0;L;;;;;N;;;;A793;
A793;LATIN SMALL LETTER C WITH BAR;Ll;0;L;;;;;N;;;A792;;A792
-A794;LATIN SMALL LETTER C WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;;
+A794;LATIN SMALL LETTER C WITH PALATAL HOOK;Ll;0;L;;;;;N;;;A7C4;;A7C4
A795;LATIN SMALL LETTER H WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;;
A796;LATIN CAPITAL LETTER B WITH FLOURISH;Lu;0;L;;;;;N;;;;A797;
A797;LATIN SMALL LETTER B WITH FLOURISH;Ll;0;L;;;;;N;;;A796;;A796
@@ -14098,6 +14119,17 @@ A7B6;LATIN CAPITAL LETTER OMEGA;Lu;0;L;;;;;N;;;;A7B7;
A7B7;LATIN SMALL LETTER OMEGA;Ll;0;L;;;;;N;;;A7B6;;A7B6
A7B8;LATIN CAPITAL LETTER U WITH STROKE;Lu;0;L;;;;;N;;;;A7B9;
A7B9;LATIN SMALL LETTER U WITH STROKE;Ll;0;L;;;;;N;;;A7B8;;A7B8
+A7BA;LATIN CAPITAL LETTER GLOTTAL A;Lu;0;L;;;;;N;;;;A7BB;
+A7BB;LATIN SMALL LETTER GLOTTAL A;Ll;0;L;;;;;N;;;A7BA;;A7BA
+A7BC;LATIN CAPITAL LETTER GLOTTAL I;Lu;0;L;;;;;N;;;;A7BD;
+A7BD;LATIN SMALL LETTER GLOTTAL I;Ll;0;L;;;;;N;;;A7BC;;A7BC
+A7BE;LATIN CAPITAL LETTER GLOTTAL U;Lu;0;L;;;;;N;;;;A7BF;
+A7BF;LATIN SMALL LETTER GLOTTAL U;Ll;0;L;;;;;N;;;A7BE;;A7BE
+A7C2;LATIN CAPITAL LETTER ANGLICANA W;Lu;0;L;;;;;N;;;;A7C3;
+A7C3;LATIN SMALL LETTER ANGLICANA W;Ll;0;L;;;;;N;;;A7C2;;A7C2
+A7C4;LATIN CAPITAL LETTER C WITH PALATAL HOOK;Lu;0;L;;;;;N;;;;A794;
+A7C5;LATIN CAPITAL LETTER S WITH HOOK;Lu;0;L;;;;;N;;;;0282;
+A7C6;LATIN CAPITAL LETTER Z WITH PALATAL HOOK;Lu;0;L;;;;;N;;;;1D8E;
A7F7;LATIN EPIGRAPHIC LETTER SIDEWAYS I;Lo;0;L;;;;;N;;;;;
A7F8;MODIFIER LETTER CAPITAL H WITH STROKE;Lm;0;L;<super> 0126;;;;N;;;;;
A7F9;MODIFIER LETTER SMALL LIGATURE OE;Lm;0;L;<super> 0153;;;;N;;;;;
@@ -14506,7 +14538,7 @@ A9B9;JAVANESE VOWEL SIGN SUKU MENDUT;Mn;0;NSM;;;;;N;;;;;
A9BA;JAVANESE VOWEL SIGN TALING;Mc;0;L;;;;;N;;;;;
A9BB;JAVANESE VOWEL SIGN DIRGA MURE;Mc;0;L;;;;;N;;;;;
A9BC;JAVANESE VOWEL SIGN PEPET;Mn;0;NSM;;;;;N;;;;;
-A9BD;JAVANESE CONSONANT SIGN KERET;Mc;0;L;;;;;N;;;;;
+A9BD;JAVANESE CONSONANT SIGN KERET;Mn;0;NSM;;;;;N;;;;;
A9BE;JAVANESE CONSONANT SIGN PENGKAL;Mc;0;L;;;;;N;;;;;
A9BF;JAVANESE CONSONANT SIGN CAKRA;Mc;0;L;;;;;N;;;;;
A9C0;JAVANESE PANGKON;Mc;9;L;;;;;N;;;;;
@@ -14863,6 +14895,8 @@ AB62;LATIN SMALL LETTER OPEN OE;Ll;0;L;;;;;N;;;;;
AB63;LATIN SMALL LETTER UO;Ll;0;L;;;;;N;;;;;
AB64;LATIN SMALL LETTER INVERTED ALPHA;Ll;0;L;;;;;N;;;;;
AB65;GREEK LETTER SMALL CAPITAL OMEGA;Ll;0;L;;;;;N;;;;;
+AB66;LATIN SMALL LETTER DZ DIGRAPH WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;;
+AB67;LATIN SMALL LETTER TS DIGRAPH WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;;
AB70;CHEROKEE SMALL LETTER A;Ll;0;L;;;;;N;;;13A0;;13A0
AB71;CHEROKEE SMALL LETTER E;Ll;0;L;;;;;N;;;13A1;;13A1
AB72;CHEROKEE SMALL LETTER I;Ll;0;L;;;;;N;;;13A2;;13A2
@@ -19105,6 +19139,29 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
10F57;SOGDIAN PUNCTUATION CIRCLE WITH DOT;Po;0;AL;;;;;N;;;;;
10F58;SOGDIAN PUNCTUATION TWO CIRCLES WITH DOTS;Po;0;AL;;;;;N;;;;;
10F59;SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT;Po;0;AL;;;;;N;;;;;
+10FE0;ELYMAIC LETTER ALEPH;Lo;0;R;;;;;N;;;;;
+10FE1;ELYMAIC LETTER BETH;Lo;0;R;;;;;N;;;;;
+10FE2;ELYMAIC LETTER GIMEL;Lo;0;R;;;;;N;;;;;
+10FE3;ELYMAIC LETTER DALETH;Lo;0;R;;;;;N;;;;;
+10FE4;ELYMAIC LETTER HE;Lo;0;R;;;;;N;;;;;
+10FE5;ELYMAIC LETTER WAW;Lo;0;R;;;;;N;;;;;
+10FE6;ELYMAIC LETTER ZAYIN;Lo;0;R;;;;;N;;;;;
+10FE7;ELYMAIC LETTER HETH;Lo;0;R;;;;;N;;;;;
+10FE8;ELYMAIC LETTER TETH;Lo;0;R;;;;;N;;;;;
+10FE9;ELYMAIC LETTER YODH;Lo;0;R;;;;;N;;;;;
+10FEA;ELYMAIC LETTER KAPH;Lo;0;R;;;;;N;;;;;
+10FEB;ELYMAIC LETTER LAMEDH;Lo;0;R;;;;;N;;;;;
+10FEC;ELYMAIC LETTER MEM;Lo;0;R;;;;;N;;;;;
+10FED;ELYMAIC LETTER NUN;Lo;0;R;;;;;N;;;;;
+10FEE;ELYMAIC LETTER SAMEKH;Lo;0;R;;;;;N;;;;;
+10FEF;ELYMAIC LETTER AYIN;Lo;0;R;;;;;N;;;;;
+10FF0;ELYMAIC LETTER PE;Lo;0;R;;;;;N;;;;;
+10FF1;ELYMAIC LETTER SADHE;Lo;0;R;;;;;N;;;;;
+10FF2;ELYMAIC LETTER QOPH;Lo;0;R;;;;;N;;;;;
+10FF3;ELYMAIC LETTER RESH;Lo;0;R;;;;;N;;;;;
+10FF4;ELYMAIC LETTER SHIN;Lo;0;R;;;;;N;;;;;
+10FF5;ELYMAIC LETTER TAW;Lo;0;R;;;;;N;;;;;
+10FF6;ELYMAIC LIGATURE ZAYIN-YODH;Lo;0;R;;;;;N;;;;;
11000;BRAHMI SIGN CANDRABINDU;Mc;0;L;;;;;N;;;;;
11001;BRAHMI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;;
11002;BRAHMI SIGN VISARGA;Mc;0;L;;;;;N;;;;;
@@ -19887,6 +19944,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1145B;NEWA PLACEHOLDER MARK;Po;0;L;;;;;N;;;;;
1145D;NEWA INSERTION SIGN;Po;0;L;;;;;N;;;;;
1145E;NEWA SANDHI MARK;Mn;230;NSM;;;;;N;;;;;
+1145F;NEWA LETTER VEDIC ANUSVARA;Lo;0;L;;;;;N;;;;;
11480;TIRHUTA ANJI;Lo;0;L;;;;;N;;;;;
11481;TIRHUTA LETTER A;Lo;0;L;;;;;N;;;;;
11482;TIRHUTA LETTER AA;Lo;0;L;;;;;N;;;;;
@@ -20209,6 +20267,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
116B5;TAKRI VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;;
116B6;TAKRI SIGN VIRAMA;Mc;9;L;;;;;N;;;;;
116B7;TAKRI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;;
+116B8;TAKRI LETTER ARCHAIC KHA;Lo;0;L;;;;;N;;;;;
116C0;TAKRI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
116C1;TAKRI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
116C2;TAKRI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
@@ -20421,6 +20480,71 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
118F1;WARANG CITI NUMBER EIGHTY;No;0;L;;;;80;N;;;;;
118F2;WARANG CITI NUMBER NINETY;No;0;L;;;;90;N;;;;;
118FF;WARANG CITI OM;Lo;0;L;;;;;N;;;;;
+119A0;NANDINAGARI LETTER A;Lo;0;L;;;;;N;;;;;
+119A1;NANDINAGARI LETTER AA;Lo;0;L;;;;;N;;;;;
+119A2;NANDINAGARI LETTER I;Lo;0;L;;;;;N;;;;;
+119A3;NANDINAGARI LETTER II;Lo;0;L;;;;;N;;;;;
+119A4;NANDINAGARI LETTER U;Lo;0;L;;;;;N;;;;;
+119A5;NANDINAGARI LETTER UU;Lo;0;L;;;;;N;;;;;
+119A6;NANDINAGARI LETTER VOCALIC R;Lo;0;L;;;;;N;;;;;
+119A7;NANDINAGARI LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;;
+119AA;NANDINAGARI LETTER E;Lo;0;L;;;;;N;;;;;
+119AB;NANDINAGARI LETTER AI;Lo;0;L;;;;;N;;;;;
+119AC;NANDINAGARI LETTER O;Lo;0;L;;;;;N;;;;;
+119AD;NANDINAGARI LETTER AU;Lo;0;L;;;;;N;;;;;
+119AE;NANDINAGARI LETTER KA;Lo;0;L;;;;;N;;;;;
+119AF;NANDINAGARI LETTER KHA;Lo;0;L;;;;;N;;;;;
+119B0;NANDINAGARI LETTER GA;Lo;0;L;;;;;N;;;;;
+119B1;NANDINAGARI LETTER GHA;Lo;0;L;;;;;N;;;;;
+119B2;NANDINAGARI LETTER NGA;Lo;0;L;;;;;N;;;;;
+119B3;NANDINAGARI LETTER CA;Lo;0;L;;;;;N;;;;;
+119B4;NANDINAGARI LETTER CHA;Lo;0;L;;;;;N;;;;;
+119B5;NANDINAGARI LETTER JA;Lo;0;L;;;;;N;;;;;
+119B6;NANDINAGARI LETTER JHA;Lo;0;L;;;;;N;;;;;
+119B7;NANDINAGARI LETTER NYA;Lo;0;L;;;;;N;;;;;
+119B8;NANDINAGARI LETTER TTA;Lo;0;L;;;;;N;;;;;
+119B9;NANDINAGARI LETTER TTHA;Lo;0;L;;;;;N;;;;;
+119BA;NANDINAGARI LETTER DDA;Lo;0;L;;;;;N;;;;;
+119BB;NANDINAGARI LETTER DDHA;Lo;0;L;;;;;N;;;;;
+119BC;NANDINAGARI LETTER NNA;Lo;0;L;;;;;N;;;;;
+119BD;NANDINAGARI LETTER TA;Lo;0;L;;;;;N;;;;;
+119BE;NANDINAGARI LETTER THA;Lo;0;L;;;;;N;;;;;
+119BF;NANDINAGARI LETTER DA;Lo;0;L;;;;;N;;;;;
+119C0;NANDINAGARI LETTER DHA;Lo;0;L;;;;;N;;;;;
+119C1;NANDINAGARI LETTER NA;Lo;0;L;;;;;N;;;;;
+119C2;NANDINAGARI LETTER PA;Lo;0;L;;;;;N;;;;;
+119C3;NANDINAGARI LETTER PHA;Lo;0;L;;;;;N;;;;;
+119C4;NANDINAGARI LETTER BA;Lo;0;L;;;;;N;;;;;
+119C5;NANDINAGARI LETTER BHA;Lo;0;L;;;;;N;;;;;
+119C6;NANDINAGARI LETTER MA;Lo;0;L;;;;;N;;;;;
+119C7;NANDINAGARI LETTER YA;Lo;0;L;;;;;N;;;;;
+119C8;NANDINAGARI LETTER RA;Lo;0;L;;;;;N;;;;;
+119C9;NANDINAGARI LETTER LA;Lo;0;L;;;;;N;;;;;
+119CA;NANDINAGARI LETTER VA;Lo;0;L;;;;;N;;;;;
+119CB;NANDINAGARI LETTER SHA;Lo;0;L;;;;;N;;;;;
+119CC;NANDINAGARI LETTER SSA;Lo;0;L;;;;;N;;;;;
+119CD;NANDINAGARI LETTER SA;Lo;0;L;;;;;N;;;;;
+119CE;NANDINAGARI LETTER HA;Lo;0;L;;;;;N;;;;;
+119CF;NANDINAGARI LETTER LLA;Lo;0;L;;;;;N;;;;;
+119D0;NANDINAGARI LETTER RRA;Lo;0;L;;;;;N;;;;;
+119D1;NANDINAGARI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;;
+119D2;NANDINAGARI VOWEL SIGN I;Mc;0;L;;;;;N;;;;;
+119D3;NANDINAGARI VOWEL SIGN II;Mc;0;L;;;;;N;;;;;
+119D4;NANDINAGARI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;;
+119D5;NANDINAGARI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;;
+119D6;NANDINAGARI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;;
+119D7;NANDINAGARI VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;;
+119DA;NANDINAGARI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;;
+119DB;NANDINAGARI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;;
+119DC;NANDINAGARI VOWEL SIGN O;Mc;0;L;;;;;N;;;;;
+119DD;NANDINAGARI VOWEL SIGN AU;Mc;0;L;;;;;N;;;;;
+119DE;NANDINAGARI SIGN ANUSVARA;Mc;0;L;;;;;N;;;;;
+119DF;NANDINAGARI SIGN VISARGA;Mc;0;L;;;;;N;;;;;
+119E0;NANDINAGARI SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;;
+119E1;NANDINAGARI SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;;
+119E2;NANDINAGARI SIGN SIDDHAM;Po;0;L;;;;;N;;;;;
+119E3;NANDINAGARI HEADSTROKE;Lo;0;L;;;;;N;;;;;
+119E4;NANDINAGARI VOWEL SIGN PRISHTHAMATRA E;Mc;0;L;;;;;N;;;;;
11A00;ZANABAZAR SQUARE LETTER A;Lo;0;L;;;;;N;;;;;
11A01;ZANABAZAR SQUARE VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;;
11A02;ZANABAZAR SQUARE VOWEL SIGN UE;Mn;0;NSM;;;;;N;;;;;
@@ -20545,6 +20669,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11A81;SOYOMBO LETTER SA;Lo;0;L;;;;;N;;;;;
11A82;SOYOMBO LETTER HA;Lo;0;L;;;;;N;;;;;
11A83;SOYOMBO LETTER KSSA;Lo;0;L;;;;;N;;;;;
+11A84;SOYOMBO SIGN JIHVAMULIYA;Lo;0;L;;;;;N;;;;;
+11A85;SOYOMBO SIGN UPADHMANIYA;Lo;0;L;;;;;N;;;;;
11A86;SOYOMBO CLUSTER-INITIAL LETTER RA;Lo;0;L;;;;;N;;;;;
11A87;SOYOMBO CLUSTER-INITIAL LETTER LA;Lo;0;L;;;;;N;;;;;
11A88;SOYOMBO CLUSTER-INITIAL LETTER SHA;Lo;0;L;;;;;N;;;;;
@@ -20959,6 +21085,57 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11EF6;MAKASAR VOWEL SIGN O;Mc;0;L;;;;;N;;;;;
11EF7;MAKASAR PASSIMBANG;Po;0;L;;;;;N;;;;;
11EF8;MAKASAR END OF SECTION;Po;0;L;;;;;N;;;;;
+11FC0;TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH;No;0;L;;;;1/320;N;;;;;
+11FC1;TAMIL FRACTION ONE ONE-HUNDRED-AND-SIXTIETH;No;0;L;;;;1/160;N;;;;;
+11FC2;TAMIL FRACTION ONE EIGHTIETH;No;0;L;;;;1/80;N;;;;;
+11FC3;TAMIL FRACTION ONE SIXTY-FOURTH;No;0;L;;;;1/64;N;;;;;
+11FC4;TAMIL FRACTION ONE FORTIETH;No;0;L;;;;1/40;N;;;;;
+11FC5;TAMIL FRACTION ONE THIRTY-SECOND;No;0;L;;;;1/32;N;;;;;
+11FC6;TAMIL FRACTION THREE EIGHTIETHS;No;0;L;;;;3/80;N;;;;;
+11FC7;TAMIL FRACTION THREE SIXTY-FOURTHS;No;0;L;;;;3/64;N;;;;;
+11FC8;TAMIL FRACTION ONE TWENTIETH;No;0;L;;;;1/20;N;;;;;
+11FC9;TAMIL FRACTION ONE SIXTEENTH-1;No;0;L;;;;1/16;N;;;;;
+11FCA;TAMIL FRACTION ONE SIXTEENTH-2;No;0;L;;;;1/16;N;;;;;
+11FCB;TAMIL FRACTION ONE TENTH;No;0;L;;;;1/10;N;;;;;
+11FCC;TAMIL FRACTION ONE EIGHTH;No;0;L;;;;1/8;N;;;;;
+11FCD;TAMIL FRACTION THREE TWENTIETHS;No;0;L;;;;3/20;N;;;;;
+11FCE;TAMIL FRACTION THREE SIXTEENTHS;No;0;L;;;;3/16;N;;;;;
+11FCF;TAMIL FRACTION ONE FIFTH;No;0;L;;;;1/5;N;;;;;
+11FD0;TAMIL FRACTION ONE QUARTER;No;0;L;;;;1/4;N;;;;;
+11FD1;TAMIL FRACTION ONE HALF-1;No;0;L;;;;1/2;N;;;;;
+11FD2;TAMIL FRACTION ONE HALF-2;No;0;L;;;;1/2;N;;;;;
+11FD3;TAMIL FRACTION THREE QUARTERS;No;0;L;;;;3/4;N;;;;;
+11FD4;TAMIL FRACTION DOWNSCALING FACTOR KIIZH;No;0;L;;;;1/320;N;;;;;
+11FD5;TAMIL SIGN NEL;So;0;ON;;;;;N;;;;;
+11FD6;TAMIL SIGN CEVITU;So;0;ON;;;;;N;;;;;
+11FD7;TAMIL SIGN AAZHAAKKU;So;0;ON;;;;;N;;;;;
+11FD8;TAMIL SIGN UZHAKKU;So;0;ON;;;;;N;;;;;
+11FD9;TAMIL SIGN MUUVUZHAKKU;So;0;ON;;;;;N;;;;;
+11FDA;TAMIL SIGN KURUNI;So;0;ON;;;;;N;;;;;
+11FDB;TAMIL SIGN PATHAKKU;So;0;ON;;;;;N;;;;;
+11FDC;TAMIL SIGN MUKKURUNI;So;0;ON;;;;;N;;;;;
+11FDD;TAMIL SIGN KAACU;Sc;0;ET;;;;;N;;;;;
+11FDE;TAMIL SIGN PANAM;Sc;0;ET;;;;;N;;;;;
+11FDF;TAMIL SIGN PON;Sc;0;ET;;;;;N;;;;;
+11FE0;TAMIL SIGN VARAAKAN;Sc;0;ET;;;;;N;;;;;
+11FE1;TAMIL SIGN PAARAM;So;0;ON;;;;;N;;;;;
+11FE2;TAMIL SIGN KUZHI;So;0;ON;;;;;N;;;;;
+11FE3;TAMIL SIGN VELI;So;0;ON;;;;;N;;;;;
+11FE4;TAMIL WET CULTIVATION SIGN;So;0;ON;;;;;N;;;;;
+11FE5;TAMIL DRY CULTIVATION SIGN;So;0;ON;;;;;N;;;;;
+11FE6;TAMIL LAND SIGN;So;0;ON;;;;;N;;;;;
+11FE7;TAMIL SALT PAN SIGN;So;0;ON;;;;;N;;;;;
+11FE8;TAMIL TRADITIONAL CREDIT SIGN;So;0;ON;;;;;N;;;;;
+11FE9;TAMIL TRADITIONAL NUMBER SIGN;So;0;ON;;;;;N;;;;;
+11FEA;TAMIL CURRENT SIGN;So;0;ON;;;;;N;;;;;
+11FEB;TAMIL AND ODD SIGN;So;0;ON;;;;;N;;;;;
+11FEC;TAMIL SPENT SIGN;So;0;ON;;;;;N;;;;;
+11FED;TAMIL TOTAL SIGN;So;0;ON;;;;;N;;;;;
+11FEE;TAMIL IN POSSESSION SIGN;So;0;ON;;;;;N;;;;;
+11FEF;TAMIL STARTING FROM SIGN;So;0;ON;;;;;N;;;;;
+11FF0;TAMIL SIGN MUTHALIYA;So;0;ON;;;;;N;;;;;
+11FF1;TAMIL SIGN VAKAIYARAA;So;0;ON;;;;;N;;;;;
+11FFF;TAMIL PUNCTUATION END OF TEXT;Po;0;L;;;;;N;;;;;
12000;CUNEIFORM SIGN A;Lo;0;L;;;;;N;;;;;
12001;CUNEIFORM SIGN A TIMES A;Lo;0;L;;;;;N;;;;;
12002;CUNEIFORM SIGN A TIMES BAD;Lo;0;L;;;;;N;;;;;
@@ -23264,6 +23441,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1342C;EGYPTIAN HIEROGLYPH AA030;Lo;0;L;;;;;N;;;;;
1342D;EGYPTIAN HIEROGLYPH AA031;Lo;0;L;;;;;N;;;;;
1342E;EGYPTIAN HIEROGLYPH AA032;Lo;0;L;;;;;N;;;;;
+13430;EGYPTIAN HIEROGLYPH VERTICAL JOINER;Cf;0;L;;;;;N;;;;;
+13431;EGYPTIAN HIEROGLYPH HORIZONTAL JOINER;Cf;0;L;;;;;N;;;;;
+13432;EGYPTIAN HIEROGLYPH INSERT AT TOP START;Cf;0;L;;;;;N;;;;;
+13433;EGYPTIAN HIEROGLYPH INSERT AT BOTTOM START;Cf;0;L;;;;;N;;;;;
+13434;EGYPTIAN HIEROGLYPH INSERT AT TOP END;Cf;0;L;;;;;N;;;;;
+13435;EGYPTIAN HIEROGLYPH INSERT AT BOTTOM END;Cf;0;L;;;;;N;;;;;
+13436;EGYPTIAN HIEROGLYPH OVERLAY MIDDLE;Cf;0;L;;;;;N;;;;;
+13437;EGYPTIAN HIEROGLYPH BEGIN SEGMENT;Cf;0;L;;;;;N;;;;;
+13438;EGYPTIAN HIEROGLYPH END SEGMENT;Cf;0;L;;;;;N;;;;;
14400;ANATOLIAN HIEROGLYPH A001;Lo;0;L;;;;;N;;;;;
14401;ANATOLIAN HIEROGLYPH A002;Lo;0;L;;;;;N;;;;;
14402;ANATOLIAN HIEROGLYPH A003;Lo;0;L;;;;;N;;;;;
@@ -24782,6 +24968,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
16F42;MIAO LETTER WA;Lo;0;L;;;;;N;;;;;
16F43;MIAO LETTER AH;Lo;0;L;;;;;N;;;;;
16F44;MIAO LETTER HHA;Lo;0;L;;;;;N;;;;;
+16F45;MIAO LETTER BRI;Lo;0;L;;;;;N;;;;;
+16F46;MIAO LETTER SYI;Lo;0;L;;;;;N;;;;;
+16F47;MIAO LETTER DZYI;Lo;0;L;;;;;N;;;;;
+16F48;MIAO LETTER TE;Lo;0;L;;;;;N;;;;;
+16F49;MIAO LETTER TSE;Lo;0;L;;;;;N;;;;;
+16F4A;MIAO LETTER RTE;Lo;0;L;;;;;N;;;;;
+16F4F;MIAO SIGN CONSONANT MODIFIER BAR;Mn;0;NSM;;;;;N;;;;;
16F50;MIAO LETTER NASALIZATION;Lo;0;L;;;;;N;;;;;
16F51;MIAO SIGN ASPIRATION;Mc;0;L;;;;;N;;;;;
16F52;MIAO SIGN REFORMED VOICING;Mc;0;L;;;;;N;;;;;
@@ -24829,6 +25022,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
16F7C;MIAO VOWEL SIGN OU;Mc;0;L;;;;;N;;;;;
16F7D;MIAO VOWEL SIGN N;Mc;0;L;;;;;N;;;;;
16F7E;MIAO VOWEL SIGN NG;Mc;0;L;;;;;N;;;;;
+16F7F;MIAO VOWEL SIGN UOG;Mc;0;L;;;;;N;;;;;
+16F80;MIAO VOWEL SIGN YUI;Mc;0;L;;;;;N;;;;;
+16F81;MIAO VOWEL SIGN OG;Mc;0;L;;;;;N;;;;;
+16F82;MIAO VOWEL SIGN OER;Mc;0;L;;;;;N;;;;;
+16F83;MIAO VOWEL SIGN VW;Mc;0;L;;;;;N;;;;;
+16F84;MIAO VOWEL SIGN IG;Mc;0;L;;;;;N;;;;;
+16F85;MIAO VOWEL SIGN EA;Mc;0;L;;;;;N;;;;;
+16F86;MIAO VOWEL SIGN IONG;Mc;0;L;;;;;N;;;;;
+16F87;MIAO VOWEL SIGN UI;Mc;0;L;;;;;N;;;;;
16F8F;MIAO TONE RIGHT;Mn;0;NSM;;;;;N;;;;;
16F90;MIAO TONE TOP RIGHT;Mn;0;NSM;;;;;N;;;;;
16F91;MIAO TONE ABOVE;Mn;0;NSM;;;;;N;;;;;
@@ -24848,8 +25050,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
16F9F;MIAO LETTER REFORMED TONE-8;Lm;0;L;;;;;N;;;;;
16FE0;TANGUT ITERATION MARK;Lm;0;L;;;;;N;;;;;
16FE1;NUSHU ITERATION MARK;Lm;0;L;;;;;N;;;;;
+16FE2;OLD CHINESE HOOK MARK;Po;0;ON;;;;;N;;;;;
+16FE3;OLD CHINESE ITERATION MARK;Lm;0;L;;;;;N;;;;;
17000;<Tangut Ideograph, First>;Lo;0;L;;;;;N;;;;;
-187F1;<Tangut Ideograph, Last>;Lo;0;L;;;;;N;;;;;
+187F7;<Tangut Ideograph, Last>;Lo;0;L;;;;;N;;;;;
18800;TANGUT COMPONENT-001;Lo;0;L;;;;;N;;;;;
18801;TANGUT COMPONENT-002;Lo;0;L;;;;;N;;;;;
18802;TANGUT COMPONENT-003;Lo;0;L;;;;;N;;;;;
@@ -25892,6 +26096,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1B11C;HENTAIGANA LETTER WO-7;Lo;0;L;;;;;N;;;;;
1B11D;HENTAIGANA LETTER N-MU-MO-1;Lo;0;L;;;;;N;;;;;
1B11E;HENTAIGANA LETTER N-MU-MO-2;Lo;0;L;;;;;N;;;;;
+1B150;HIRAGANA LETTER SMALL WI;Lo;0;L;;;;;N;;;;;
+1B151;HIRAGANA LETTER SMALL WE;Lo;0;L;;;;;N;;;;;
+1B152;HIRAGANA LETTER SMALL WO;Lo;0;L;;;;;N;;;;;
+1B164;KATAKANA LETTER SMALL WI;Lo;0;L;;;;;N;;;;;
+1B165;KATAKANA LETTER SMALL WE;Lo;0;L;;;;;N;;;;;
+1B166;KATAKANA LETTER SMALL WO;Lo;0;L;;;;;N;;;;;
+1B167;KATAKANA LETTER SMALL N;Lo;0;L;;;;;N;;;;;
1B170;NUSHU CHARACTER-1B170;Lo;0;L;;;;;N;;;;;
1B171;NUSHU CHARACTER-1B171;Lo;0;L;;;;;N;;;;;
1B172;NUSHU CHARACTER-1B172;Lo;0;L;;;;;N;;;;;
@@ -28820,6 +29031,136 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1E028;COMBINING GLAGOLITIC LETTER BIG YUS;Mn;230;NSM;;;;;N;;;;;
1E029;COMBINING GLAGOLITIC LETTER IOTATED BIG YUS;Mn;230;NSM;;;;;N;;;;;
1E02A;COMBINING GLAGOLITIC LETTER FITA;Mn;230;NSM;;;;;N;;;;;
+1E100;NYIAKENG PUACHUE HMONG LETTER MA;Lo;0;L;;;;;N;;;;;
+1E101;NYIAKENG PUACHUE HMONG LETTER TSA;Lo;0;L;;;;;N;;;;;
+1E102;NYIAKENG PUACHUE HMONG LETTER NTA;Lo;0;L;;;;;N;;;;;
+1E103;NYIAKENG PUACHUE HMONG LETTER TA;Lo;0;L;;;;;N;;;;;
+1E104;NYIAKENG PUACHUE HMONG LETTER HA;Lo;0;L;;;;;N;;;;;
+1E105;NYIAKENG PUACHUE HMONG LETTER NA;Lo;0;L;;;;;N;;;;;
+1E106;NYIAKENG PUACHUE HMONG LETTER XA;Lo;0;L;;;;;N;;;;;
+1E107;NYIAKENG PUACHUE HMONG LETTER NKA;Lo;0;L;;;;;N;;;;;
+1E108;NYIAKENG PUACHUE HMONG LETTER CA;Lo;0;L;;;;;N;;;;;
+1E109;NYIAKENG PUACHUE HMONG LETTER LA;Lo;0;L;;;;;N;;;;;
+1E10A;NYIAKENG PUACHUE HMONG LETTER SA;Lo;0;L;;;;;N;;;;;
+1E10B;NYIAKENG PUACHUE HMONG LETTER ZA;Lo;0;L;;;;;N;;;;;
+1E10C;NYIAKENG PUACHUE HMONG LETTER NCA;Lo;0;L;;;;;N;;;;;
+1E10D;NYIAKENG PUACHUE HMONG LETTER NTSA;Lo;0;L;;;;;N;;;;;
+1E10E;NYIAKENG PUACHUE HMONG LETTER KA;Lo;0;L;;;;;N;;;;;
+1E10F;NYIAKENG PUACHUE HMONG LETTER DA;Lo;0;L;;;;;N;;;;;
+1E110;NYIAKENG PUACHUE HMONG LETTER NYA;Lo;0;L;;;;;N;;;;;
+1E111;NYIAKENG PUACHUE HMONG LETTER NRA;Lo;0;L;;;;;N;;;;;
+1E112;NYIAKENG PUACHUE HMONG LETTER VA;Lo;0;L;;;;;N;;;;;
+1E113;NYIAKENG PUACHUE HMONG LETTER NTXA;Lo;0;L;;;;;N;;;;;
+1E114;NYIAKENG PUACHUE HMONG LETTER TXA;Lo;0;L;;;;;N;;;;;
+1E115;NYIAKENG PUACHUE HMONG LETTER FA;Lo;0;L;;;;;N;;;;;
+1E116;NYIAKENG PUACHUE HMONG LETTER RA;Lo;0;L;;;;;N;;;;;
+1E117;NYIAKENG PUACHUE HMONG LETTER QA;Lo;0;L;;;;;N;;;;;
+1E118;NYIAKENG PUACHUE HMONG LETTER YA;Lo;0;L;;;;;N;;;;;
+1E119;NYIAKENG PUACHUE HMONG LETTER NQA;Lo;0;L;;;;;N;;;;;
+1E11A;NYIAKENG PUACHUE HMONG LETTER PA;Lo;0;L;;;;;N;;;;;
+1E11B;NYIAKENG PUACHUE HMONG LETTER XYA;Lo;0;L;;;;;N;;;;;
+1E11C;NYIAKENG PUACHUE HMONG LETTER NPA;Lo;0;L;;;;;N;;;;;
+1E11D;NYIAKENG PUACHUE HMONG LETTER DLA;Lo;0;L;;;;;N;;;;;
+1E11E;NYIAKENG PUACHUE HMONG LETTER NPLA;Lo;0;L;;;;;N;;;;;
+1E11F;NYIAKENG PUACHUE HMONG LETTER HAH;Lo;0;L;;;;;N;;;;;
+1E120;NYIAKENG PUACHUE HMONG LETTER MLA;Lo;0;L;;;;;N;;;;;
+1E121;NYIAKENG PUACHUE HMONG LETTER PLA;Lo;0;L;;;;;N;;;;;
+1E122;NYIAKENG PUACHUE HMONG LETTER GA;Lo;0;L;;;;;N;;;;;
+1E123;NYIAKENG PUACHUE HMONG LETTER RRA;Lo;0;L;;;;;N;;;;;
+1E124;NYIAKENG PUACHUE HMONG LETTER A;Lo;0;L;;;;;N;;;;;
+1E125;NYIAKENG PUACHUE HMONG LETTER AA;Lo;0;L;;;;;N;;;;;
+1E126;NYIAKENG PUACHUE HMONG LETTER I;Lo;0;L;;;;;N;;;;;
+1E127;NYIAKENG PUACHUE HMONG LETTER U;Lo;0;L;;;;;N;;;;;
+1E128;NYIAKENG PUACHUE HMONG LETTER O;Lo;0;L;;;;;N;;;;;
+1E129;NYIAKENG PUACHUE HMONG LETTER OO;Lo;0;L;;;;;N;;;;;
+1E12A;NYIAKENG PUACHUE HMONG LETTER E;Lo;0;L;;;;;N;;;;;
+1E12B;NYIAKENG PUACHUE HMONG LETTER EE;Lo;0;L;;;;;N;;;;;
+1E12C;NYIAKENG PUACHUE HMONG LETTER W;Lo;0;L;;;;;N;;;;;
+1E130;NYIAKENG PUACHUE HMONG TONE-B;Mn;230;NSM;;;;;N;;;;;
+1E131;NYIAKENG PUACHUE HMONG TONE-M;Mn;230;NSM;;;;;N;;;;;
+1E132;NYIAKENG PUACHUE HMONG TONE-J;Mn;230;NSM;;;;;N;;;;;
+1E133;NYIAKENG PUACHUE HMONG TONE-V;Mn;230;NSM;;;;;N;;;;;
+1E134;NYIAKENG PUACHUE HMONG TONE-S;Mn;230;NSM;;;;;N;;;;;
+1E135;NYIAKENG PUACHUE HMONG TONE-G;Mn;230;NSM;;;;;N;;;;;
+1E136;NYIAKENG PUACHUE HMONG TONE-D;Mn;230;NSM;;;;;N;;;;;
+1E137;NYIAKENG PUACHUE HMONG SIGN FOR PERSON;Lm;0;L;;;;;N;;;;;
+1E138;NYIAKENG PUACHUE HMONG SIGN FOR THING;Lm;0;L;;;;;N;;;;;
+1E139;NYIAKENG PUACHUE HMONG SIGN FOR LOCATION;Lm;0;L;;;;;N;;;;;
+1E13A;NYIAKENG PUACHUE HMONG SIGN FOR ANIMAL;Lm;0;L;;;;;N;;;;;
+1E13B;NYIAKENG PUACHUE HMONG SIGN FOR INVERTEBRATE;Lm;0;L;;;;;N;;;;;
+1E13C;NYIAKENG PUACHUE HMONG SIGN XW XW;Lm;0;L;;;;;N;;;;;
+1E13D;NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER;Lm;0;L;;;;;N;;;;;
+1E140;NYIAKENG PUACHUE HMONG DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
+1E141;NYIAKENG PUACHUE HMONG DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
+1E142;NYIAKENG PUACHUE HMONG DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
+1E143;NYIAKENG PUACHUE HMONG DIGIT THREE;Nd;0;L;;3;3;3;N;;;;;
+1E144;NYIAKENG PUACHUE HMONG DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;;
+1E145;NYIAKENG PUACHUE HMONG DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;;
+1E146;NYIAKENG PUACHUE HMONG DIGIT SIX;Nd;0;L;;6;6;6;N;;;;;
+1E147;NYIAKENG PUACHUE HMONG DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;;
+1E148;NYIAKENG PUACHUE HMONG DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;;
+1E149;NYIAKENG PUACHUE HMONG DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
+1E14E;NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ;Lo;0;L;;;;;N;;;;;
+1E14F;NYIAKENG PUACHUE HMONG CIRCLED CA;So;0;L;;;;;N;;;;;
+1E2C0;WANCHO LETTER AA;Lo;0;L;;;;;N;;;;;
+1E2C1;WANCHO LETTER A;Lo;0;L;;;;;N;;;;;
+1E2C2;WANCHO LETTER BA;Lo;0;L;;;;;N;;;;;
+1E2C3;WANCHO LETTER CA;Lo;0;L;;;;;N;;;;;
+1E2C4;WANCHO LETTER DA;Lo;0;L;;;;;N;;;;;
+1E2C5;WANCHO LETTER GA;Lo;0;L;;;;;N;;;;;
+1E2C6;WANCHO LETTER YA;Lo;0;L;;;;;N;;;;;
+1E2C7;WANCHO LETTER PHA;Lo;0;L;;;;;N;;;;;
+1E2C8;WANCHO LETTER LA;Lo;0;L;;;;;N;;;;;
+1E2C9;WANCHO LETTER NA;Lo;0;L;;;;;N;;;;;
+1E2CA;WANCHO LETTER PA;Lo;0;L;;;;;N;;;;;
+1E2CB;WANCHO LETTER TA;Lo;0;L;;;;;N;;;;;
+1E2CC;WANCHO LETTER THA;Lo;0;L;;;;;N;;;;;
+1E2CD;WANCHO LETTER FA;Lo;0;L;;;;;N;;;;;
+1E2CE;WANCHO LETTER SA;Lo;0;L;;;;;N;;;;;
+1E2CF;WANCHO LETTER SHA;Lo;0;L;;;;;N;;;;;
+1E2D0;WANCHO LETTER JA;Lo;0;L;;;;;N;;;;;
+1E2D1;WANCHO LETTER ZA;Lo;0;L;;;;;N;;;;;
+1E2D2;WANCHO LETTER WA;Lo;0;L;;;;;N;;;;;
+1E2D3;WANCHO LETTER VA;Lo;0;L;;;;;N;;;;;
+1E2D4;WANCHO LETTER KA;Lo;0;L;;;;;N;;;;;
+1E2D5;WANCHO LETTER O;Lo;0;L;;;;;N;;;;;
+1E2D6;WANCHO LETTER AU;Lo;0;L;;;;;N;;;;;
+1E2D7;WANCHO LETTER RA;Lo;0;L;;;;;N;;;;;
+1E2D8;WANCHO LETTER MA;Lo;0;L;;;;;N;;;;;
+1E2D9;WANCHO LETTER KHA;Lo;0;L;;;;;N;;;;;
+1E2DA;WANCHO LETTER HA;Lo;0;L;;;;;N;;;;;
+1E2DB;WANCHO LETTER E;Lo;0;L;;;;;N;;;;;
+1E2DC;WANCHO LETTER I;Lo;0;L;;;;;N;;;;;
+1E2DD;WANCHO LETTER NGA;Lo;0;L;;;;;N;;;;;
+1E2DE;WANCHO LETTER U;Lo;0;L;;;;;N;;;;;
+1E2DF;WANCHO LETTER LLHA;Lo;0;L;;;;;N;;;;;
+1E2E0;WANCHO LETTER TSA;Lo;0;L;;;;;N;;;;;
+1E2E1;WANCHO LETTER TRA;Lo;0;L;;;;;N;;;;;
+1E2E2;WANCHO LETTER ONG;Lo;0;L;;;;;N;;;;;
+1E2E3;WANCHO LETTER AANG;Lo;0;L;;;;;N;;;;;
+1E2E4;WANCHO LETTER ANG;Lo;0;L;;;;;N;;;;;
+1E2E5;WANCHO LETTER ING;Lo;0;L;;;;;N;;;;;
+1E2E6;WANCHO LETTER ON;Lo;0;L;;;;;N;;;;;
+1E2E7;WANCHO LETTER EN;Lo;0;L;;;;;N;;;;;
+1E2E8;WANCHO LETTER AAN;Lo;0;L;;;;;N;;;;;
+1E2E9;WANCHO LETTER NYA;Lo;0;L;;;;;N;;;;;
+1E2EA;WANCHO LETTER UEN;Lo;0;L;;;;;N;;;;;
+1E2EB;WANCHO LETTER YIH;Lo;0;L;;;;;N;;;;;
+1E2EC;WANCHO TONE TUP;Mn;230;NSM;;;;;N;;;;;
+1E2ED;WANCHO TONE TUPNI;Mn;230;NSM;;;;;N;;;;;
+1E2EE;WANCHO TONE KOI;Mn;230;NSM;;;;;N;;;;;
+1E2EF;WANCHO TONE KOINI;Mn;230;NSM;;;;;N;;;;;
+1E2F0;WANCHO DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
+1E2F1;WANCHO DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
+1E2F2;WANCHO DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
+1E2F3;WANCHO DIGIT THREE;Nd;0;L;;3;3;3;N;;;;;
+1E2F4;WANCHO DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;;
+1E2F5;WANCHO DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;;
+1E2F6;WANCHO DIGIT SIX;Nd;0;L;;6;6;6;N;;;;;
+1E2F7;WANCHO DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;;
+1E2F8;WANCHO DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;;
+1E2F9;WANCHO DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
+1E2FF;WANCHO NGUN SIGN;Sc;0;ET;;;;;N;;;;;
1E800;MENDE KIKAKUI SYLLABLE M001 KI;Lo;0;R;;;;;N;;;;;
1E801;MENDE KIKAKUI SYLLABLE M002 KA;Lo;0;R;;;;;N;;;;;
1E802;MENDE KIKAKUI SYLLABLE M003 KU;Lo;0;R;;;;;N;;;;;
@@ -29108,6 +29449,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1E948;ADLAM CONSONANT MODIFIER;Mn;230;NSM;;;;;N;;;;;
1E949;ADLAM GEMINATE CONSONANT MODIFIER;Mn;230;NSM;;;;;N;;;;;
1E94A;ADLAM NUKTA;Mn;7;NSM;;;;;N;;;;;
+1E94B;ADLAM NASALIZATION MARK;Lm;0;R;;;;;N;;;;;
1E950;ADLAM DIGIT ZERO;Nd;0;R;;0;0;0;N;;;;;
1E951;ADLAM DIGIT ONE;Nd;0;R;;1;1;1;N;;;;;
1E952;ADLAM DIGIT TWO;Nd;0;R;;2;2;2;N;;;;;
@@ -29188,6 +29530,67 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1ECB2;INDIC SIYAQ NUMBER ALTERNATE TWO;No;0;AL;;;;2;N;;;;;
1ECB3;INDIC SIYAQ NUMBER ALTERNATE TEN THOUSAND;No;0;AL;;;;10000;N;;;;;
1ECB4;INDIC SIYAQ ALTERNATE LAKH MARK;No;0;AL;;;;100000;N;;;;;
+1ED01;OTTOMAN SIYAQ NUMBER ONE;No;0;AL;;;;1;N;;;;;
+1ED02;OTTOMAN SIYAQ NUMBER TWO;No;0;AL;;;;2;N;;;;;
+1ED03;OTTOMAN SIYAQ NUMBER THREE;No;0;AL;;;;3;N;;;;;
+1ED04;OTTOMAN SIYAQ NUMBER FOUR;No;0;AL;;;;4;N;;;;;
+1ED05;OTTOMAN SIYAQ NUMBER FIVE;No;0;AL;;;;5;N;;;;;
+1ED06;OTTOMAN SIYAQ NUMBER SIX;No;0;AL;;;;6;N;;;;;
+1ED07;OTTOMAN SIYAQ NUMBER SEVEN;No;0;AL;;;;7;N;;;;;
+1ED08;OTTOMAN SIYAQ NUMBER EIGHT;No;0;AL;;;;8;N;;;;;
+1ED09;OTTOMAN SIYAQ NUMBER NINE;No;0;AL;;;;9;N;;;;;
+1ED0A;OTTOMAN SIYAQ NUMBER TEN;No;0;AL;;;;10;N;;;;;
+1ED0B;OTTOMAN SIYAQ NUMBER TWENTY;No;0;AL;;;;20;N;;;;;
+1ED0C;OTTOMAN SIYAQ NUMBER THIRTY;No;0;AL;;;;30;N;;;;;
+1ED0D;OTTOMAN SIYAQ NUMBER FORTY;No;0;AL;;;;40;N;;;;;
+1ED0E;OTTOMAN SIYAQ NUMBER FIFTY;No;0;AL;;;;50;N;;;;;
+1ED0F;OTTOMAN SIYAQ NUMBER SIXTY;No;0;AL;;;;60;N;;;;;
+1ED10;OTTOMAN SIYAQ NUMBER SEVENTY;No;0;AL;;;;70;N;;;;;
+1ED11;OTTOMAN SIYAQ NUMBER EIGHTY;No;0;AL;;;;80;N;;;;;
+1ED12;OTTOMAN SIYAQ NUMBER NINETY;No;0;AL;;;;90;N;;;;;
+1ED13;OTTOMAN SIYAQ NUMBER ONE HUNDRED;No;0;AL;;;;100;N;;;;;
+1ED14;OTTOMAN SIYAQ NUMBER TWO HUNDRED;No;0;AL;;;;200;N;;;;;
+1ED15;OTTOMAN SIYAQ NUMBER THREE HUNDRED;No;0;AL;;;;300;N;;;;;
+1ED16;OTTOMAN SIYAQ NUMBER FOUR HUNDRED;No;0;AL;;;;400;N;;;;;
+1ED17;OTTOMAN SIYAQ NUMBER FIVE HUNDRED;No;0;AL;;;;500;N;;;;;
+1ED18;OTTOMAN SIYAQ NUMBER SIX HUNDRED;No;0;AL;;;;600;N;;;;;
+1ED19;OTTOMAN SIYAQ NUMBER SEVEN HUNDRED;No;0;AL;;;;700;N;;;;;
+1ED1A;OTTOMAN SIYAQ NUMBER EIGHT HUNDRED;No;0;AL;;;;800;N;;;;;
+1ED1B;OTTOMAN SIYAQ NUMBER NINE HUNDRED;No;0;AL;;;;900;N;;;;;
+1ED1C;OTTOMAN SIYAQ NUMBER ONE THOUSAND;No;0;AL;;;;1000;N;;;;;
+1ED1D;OTTOMAN SIYAQ NUMBER TWO THOUSAND;No;0;AL;;;;2000;N;;;;;
+1ED1E;OTTOMAN SIYAQ NUMBER THREE THOUSAND;No;0;AL;;;;3000;N;;;;;
+1ED1F;OTTOMAN SIYAQ NUMBER FOUR THOUSAND;No;0;AL;;;;4000;N;;;;;
+1ED20;OTTOMAN SIYAQ NUMBER FIVE THOUSAND;No;0;AL;;;;5000;N;;;;;
+1ED21;OTTOMAN SIYAQ NUMBER SIX THOUSAND;No;0;AL;;;;6000;N;;;;;
+1ED22;OTTOMAN SIYAQ NUMBER SEVEN THOUSAND;No;0;AL;;;;7000;N;;;;;
+1ED23;OTTOMAN SIYAQ NUMBER EIGHT THOUSAND;No;0;AL;;;;8000;N;;;;;
+1ED24;OTTOMAN SIYAQ NUMBER NINE THOUSAND;No;0;AL;;;;9000;N;;;;;
+1ED25;OTTOMAN SIYAQ NUMBER TEN THOUSAND;No;0;AL;;;;10000;N;;;;;
+1ED26;OTTOMAN SIYAQ NUMBER TWENTY THOUSAND;No;0;AL;;;;20000;N;;;;;
+1ED27;OTTOMAN SIYAQ NUMBER THIRTY THOUSAND;No;0;AL;;;;30000;N;;;;;
+1ED28;OTTOMAN SIYAQ NUMBER FORTY THOUSAND;No;0;AL;;;;40000;N;;;;;
+1ED29;OTTOMAN SIYAQ NUMBER FIFTY THOUSAND;No;0;AL;;;;50000;N;;;;;
+1ED2A;OTTOMAN SIYAQ NUMBER SIXTY THOUSAND;No;0;AL;;;;60000;N;;;;;
+1ED2B;OTTOMAN SIYAQ NUMBER SEVENTY THOUSAND;No;0;AL;;;;70000;N;;;;;
+1ED2C;OTTOMAN SIYAQ NUMBER EIGHTY THOUSAND;No;0;AL;;;;80000;N;;;;;
+1ED2D;OTTOMAN SIYAQ NUMBER NINETY THOUSAND;No;0;AL;;;;90000;N;;;;;
+1ED2E;OTTOMAN SIYAQ MARRATAN;So;0;AL;;;;;N;;;;;
+1ED2F;OTTOMAN SIYAQ ALTERNATE NUMBER TWO;No;0;AL;;;;2;N;;;;;
+1ED30;OTTOMAN SIYAQ ALTERNATE NUMBER THREE;No;0;AL;;;;3;N;;;;;
+1ED31;OTTOMAN SIYAQ ALTERNATE NUMBER FOUR;No;0;AL;;;;4;N;;;;;
+1ED32;OTTOMAN SIYAQ ALTERNATE NUMBER FIVE;No;0;AL;;;;5;N;;;;;
+1ED33;OTTOMAN SIYAQ ALTERNATE NUMBER SIX;No;0;AL;;;;6;N;;;;;
+1ED34;OTTOMAN SIYAQ ALTERNATE NUMBER SEVEN;No;0;AL;;;;7;N;;;;;
+1ED35;OTTOMAN SIYAQ ALTERNATE NUMBER EIGHT;No;0;AL;;;;8;N;;;;;
+1ED36;OTTOMAN SIYAQ ALTERNATE NUMBER NINE;No;0;AL;;;;9;N;;;;;
+1ED37;OTTOMAN SIYAQ ALTERNATE NUMBER TEN;No;0;AL;;;;10;N;;;;;
+1ED38;OTTOMAN SIYAQ ALTERNATE NUMBER FOUR HUNDRED;No;0;AL;;;;400;N;;;;;
+1ED39;OTTOMAN SIYAQ ALTERNATE NUMBER SIX HUNDRED;No;0;AL;;;;600;N;;;;;
+1ED3A;OTTOMAN SIYAQ ALTERNATE NUMBER TWO THOUSAND;No;0;AL;;;;2000;N;;;;;
+1ED3B;OTTOMAN SIYAQ ALTERNATE NUMBER TEN THOUSAND;No;0;AL;;;;10000;N;;;;;
+1ED3C;OTTOMAN SIYAQ FRACTION ONE HALF;No;0;AL;;;;1/2;N;;;;;
+1ED3D;OTTOMAN SIYAQ FRACTION ONE SIXTH;No;0;AL;;;;1/6;N;;;;;
1EE00;ARABIC MATHEMATICAL ALEF;Lo;0;AL;<font> 0627;;;;N;;;;;
1EE01;ARABIC MATHEMATICAL BEH;Lo;0;AL;<font> 0628;;;;N;;;;;
1EE02;ARABIC MATHEMATICAL JEEM;Lo;0;AL;<font> 062C;;;;N;;;;;
@@ -29662,6 +30065,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F169;NEGATIVE CIRCLED LATIN CAPITAL LETTER Z;So;0;L;;;;;N;;;;;
1F16A;RAISED MC SIGN;So;0;ON;<super> 004D 0043;;;;N;;;;;
1F16B;RAISED MD SIGN;So;0;ON;<super> 004D 0044;;;;N;;;;;
+1F16C;RAISED MR SIGN;So;0;ON;<super> 004D 0052;;;;N;;;;;
1F170;NEGATIVE SQUARED LATIN CAPITAL LETTER A;So;0;L;;;;;N;;;;;
1F171;NEGATIVE SQUARED LATIN CAPITAL LETTER B;So;0;L;;;;;N;;;;;
1F172;NEGATIVE SQUARED LATIN CAPITAL LETTER C;So;0;L;;;;;N;;;;;
@@ -30794,6 +31198,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F6D2;SHOPPING TROLLEY;So;0;ON;;;;;N;;;;;
1F6D3;STUPA;So;0;ON;;;;;N;;;;;
1F6D4;PAGODA;So;0;ON;;;;;N;;;;;
+1F6D5;HINDU TEMPLE;So;0;ON;;;;;N;;;;;
1F6E0;HAMMER AND WRENCH;So;0;ON;;;;;N;;;;;
1F6E1;SHIELD;So;0;ON;;;;;N;;;;;
1F6E2;OIL DRUM;So;0;ON;;;;;N;;;;;
@@ -30817,6 +31222,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F6F7;SLED;So;0;ON;;;;;N;;;;;
1F6F8;FLYING SAUCER;So;0;ON;;;;;N;;;;;
1F6F9;SKATEBOARD;So;0;ON;;;;;N;;;;;
+1F6FA;AUTO RICKSHAW;So;0;ON;;;;;N;;;;;
1F700;ALCHEMICAL SYMBOL FOR QUINTESSENCE;So;0;ON;;;;;N;;;;;
1F701;ALCHEMICAL SYMBOL FOR AIR;So;0;ON;;;;;N;;;;;
1F702;ALCHEMICAL SYMBOL FOR FIRE;So;0;ON;;;;;N;;;;;
@@ -31022,6 +31428,18 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F7D6;NEGATIVE CIRCLED TRIANGLE;So;0;ON;;;;;N;;;;;
1F7D7;CIRCLED SQUARE;So;0;ON;;;;;N;;;;;
1F7D8;NEGATIVE CIRCLED SQUARE;So;0;ON;;;;;N;;;;;
+1F7E0;LARGE ORANGE CIRCLE;So;0;ON;;;;;N;;;;;
+1F7E1;LARGE YELLOW CIRCLE;So;0;ON;;;;;N;;;;;
+1F7E2;LARGE GREEN CIRCLE;So;0;ON;;;;;N;;;;;
+1F7E3;LARGE PURPLE CIRCLE;So;0;ON;;;;;N;;;;;
+1F7E4;LARGE BROWN CIRCLE;So;0;ON;;;;;N;;;;;
+1F7E5;LARGE RED SQUARE;So;0;ON;;;;;N;;;;;
+1F7E6;LARGE BLUE SQUARE;So;0;ON;;;;;N;;;;;
+1F7E7;LARGE ORANGE SQUARE;So;0;ON;;;;;N;;;;;
+1F7E8;LARGE YELLOW SQUARE;So;0;ON;;;;;N;;;;;
+1F7E9;LARGE GREEN SQUARE;So;0;ON;;;;;N;;;;;
+1F7EA;LARGE PURPLE SQUARE;So;0;ON;;;;;N;;;;;
+1F7EB;LARGE BROWN SQUARE;So;0;ON;;;;;N;;;;;
1F800;LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;;
1F801;UPWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;;
1F802;RIGHTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;;
@@ -31182,6 +31600,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F909;DOWNWARD FACING NOTCHED HOOK;So;0;ON;;;;;N;;;;;
1F90A;DOWNWARD FACING HOOK WITH DOT;So;0;ON;;;;;N;;;;;
1F90B;DOWNWARD FACING NOTCHED HOOK WITH DOT;So;0;ON;;;;;N;;;;;
+1F90D;WHITE HEART;So;0;ON;;;;;N;;;;;
+1F90E;BROWN HEART;So;0;ON;;;;;N;;;;;
+1F90F;PINCHING HAND;So;0;ON;;;;;N;;;;;
1F910;ZIPPER-MOUTH FACE;So;0;ON;;;;;N;;;;;
1F911;MONEY-MOUTH FACE;So;0;ON;;;;;N;;;;;
1F912;FACE WITH THERMOMETER;So;0;ON;;;;;N;;;;;
@@ -31229,6 +31650,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F93C;WRESTLERS;So;0;ON;;;;;N;;;;;
1F93D;WATER POLO;So;0;ON;;;;;N;;;;;
1F93E;HANDBALL;So;0;ON;;;;;N;;;;;
+1F93F;DIVING MASK;So;0;ON;;;;;N;;;;;
1F940;WILTED FLOWER;So;0;ON;;;;;N;;;;;
1F941;DRUM WITH DRUMSTICKS;So;0;ON;;;;;N;;;;;
1F942;CLINKING GLASSES;So;0;ON;;;;;N;;;;;
@@ -31278,11 +31700,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F96E;MOON CAKE;So;0;ON;;;;;N;;;;;
1F96F;BAGEL;So;0;ON;;;;;N;;;;;
1F970;SMILING FACE WITH SMILING EYES AND THREE HEARTS;So;0;ON;;;;;N;;;;;
+1F971;YAWNING FACE;So;0;ON;;;;;N;;;;;
1F973;FACE WITH PARTY HORN AND PARTY HAT;So;0;ON;;;;;N;;;;;
1F974;FACE WITH UNEVEN EYES AND WAVY MOUTH;So;0;ON;;;;;N;;;;;
1F975;OVERHEATED FACE;So;0;ON;;;;;N;;;;;
1F976;FREEZING FACE;So;0;ON;;;;;N;;;;;
1F97A;FACE WITH PLEADING EYES;So;0;ON;;;;;N;;;;;
+1F97B;SARI;So;0;ON;;;;;N;;;;;
1F97C;LAB COAT;So;0;ON;;;;;N;;;;;
1F97D;GOGGLES;So;0;ON;;;;;N;;;;;
1F97E;HIKING BOOT;So;0;ON;;;;;N;;;;;
@@ -31322,6 +31746,14 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F9A0;MICROBE;So;0;ON;;;;;N;;;;;
1F9A1;BADGER;So;0;ON;;;;;N;;;;;
1F9A2;SWAN;So;0;ON;;;;;N;;;;;
+1F9A5;SLOTH;So;0;ON;;;;;N;;;;;
+1F9A6;OTTER;So;0;ON;;;;;N;;;;;
+1F9A7;ORANGUTAN;So;0;ON;;;;;N;;;;;
+1F9A8;SKUNK;So;0;ON;;;;;N;;;;;
+1F9A9;FLAMINGO;So;0;ON;;;;;N;;;;;
+1F9AA;OYSTER;So;0;ON;;;;;N;;;;;
+1F9AE;GUIDE DOG;So;0;ON;;;;;N;;;;;
+1F9AF;PROBING CANE;So;0;ON;;;;;N;;;;;
1F9B0;EMOJI COMPONENT RED HAIR;So;0;ON;;;;;N;;;;;
1F9B1;EMOJI COMPONENT CURLY HAIR;So;0;ON;;;;;N;;;;;
1F9B2;EMOJI COMPONENT BALD;So;0;ON;;;;;N;;;;;
@@ -31332,9 +31764,26 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F9B7;TOOTH;So;0;ON;;;;;N;;;;;
1F9B8;SUPERHERO;So;0;ON;;;;;N;;;;;
1F9B9;SUPERVILLAIN;So;0;ON;;;;;N;;;;;
+1F9BA;SAFETY VEST;So;0;ON;;;;;N;;;;;
+1F9BB;EAR WITH HEARING AID;So;0;ON;;;;;N;;;;;
+1F9BC;MOTORIZED WHEELCHAIR;So;0;ON;;;;;N;;;;;
+1F9BD;MANUAL WHEELCHAIR;So;0;ON;;;;;N;;;;;
+1F9BE;MECHANICAL ARM;So;0;ON;;;;;N;;;;;
+1F9BF;MECHANICAL LEG;So;0;ON;;;;;N;;;;;
1F9C0;CHEESE WEDGE;So;0;ON;;;;;N;;;;;
1F9C1;CUPCAKE;So;0;ON;;;;;N;;;;;
1F9C2;SALT SHAKER;So;0;ON;;;;;N;;;;;
+1F9C3;BEVERAGE BOX;So;0;ON;;;;;N;;;;;
+1F9C4;GARLIC;So;0;ON;;;;;N;;;;;
+1F9C5;ONION;So;0;ON;;;;;N;;;;;
+1F9C6;FALAFEL;So;0;ON;;;;;N;;;;;
+1F9C7;WAFFLE;So;0;ON;;;;;N;;;;;
+1F9C8;BUTTER;So;0;ON;;;;;N;;;;;
+1F9C9;MATE DRINK;So;0;ON;;;;;N;;;;;
+1F9CA;ICE CUBE;So;0;ON;;;;;N;;;;;
+1F9CD;STANDING PERSON;So;0;ON;;;;;N;;;;;
+1F9CE;KNEELING PERSON;So;0;ON;;;;;N;;;;;
+1F9CF;DEAF PERSON;So;0;ON;;;;;N;;;;;
1F9D0;FACE WITH MONOCLE;So;0;ON;;;;;N;;;;;
1F9D1;ADULT;So;0;ON;;;;;N;;;;;
1F9D2;CHILD;So;0;ON;;;;;N;;;;;
@@ -31383,6 +31832,90 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F9FD;SPONGE;So;0;ON;;;;;N;;;;;
1F9FE;RECEIPT;So;0;ON;;;;;N;;;;;
1F9FF;NAZAR AMULET;So;0;ON;;;;;N;;;;;
+1FA00;NEUTRAL CHESS KING;So;0;ON;;;;;N;;;;;
+1FA01;NEUTRAL CHESS QUEEN;So;0;ON;;;;;N;;;;;
+1FA02;NEUTRAL CHESS ROOK;So;0;ON;;;;;N;;;;;
+1FA03;NEUTRAL CHESS BISHOP;So;0;ON;;;;;N;;;;;
+1FA04;NEUTRAL CHESS KNIGHT;So;0;ON;;;;;N;;;;;
+1FA05;NEUTRAL CHESS PAWN;So;0;ON;;;;;N;;;;;
+1FA06;WHITE CHESS KNIGHT ROTATED FORTY-FIVE DEGREES;So;0;ON;;;;;N;;;;;
+1FA07;BLACK CHESS KNIGHT ROTATED FORTY-FIVE DEGREES;So;0;ON;;;;;N;;;;;
+1FA08;NEUTRAL CHESS KNIGHT ROTATED FORTY-FIVE DEGREES;So;0;ON;;;;;N;;;;;
+1FA09;WHITE CHESS KING ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA0A;WHITE CHESS QUEEN ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA0B;WHITE CHESS ROOK ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA0C;WHITE CHESS BISHOP ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA0D;WHITE CHESS KNIGHT ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA0E;WHITE CHESS PAWN ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA0F;BLACK CHESS KING ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA10;BLACK CHESS QUEEN ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA11;BLACK CHESS ROOK ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA12;BLACK CHESS BISHOP ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA13;BLACK CHESS KNIGHT ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA14;BLACK CHESS PAWN ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA15;NEUTRAL CHESS KING ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA16;NEUTRAL CHESS QUEEN ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA17;NEUTRAL CHESS ROOK ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA18;NEUTRAL CHESS BISHOP ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA19;NEUTRAL CHESS KNIGHT ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA1A;NEUTRAL CHESS PAWN ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA1B;WHITE CHESS KNIGHT ROTATED ONE HUNDRED THIRTY-FIVE DEGREES;So;0;ON;;;;;N;;;;;
+1FA1C;BLACK CHESS KNIGHT ROTATED ONE HUNDRED THIRTY-FIVE DEGREES;So;0;ON;;;;;N;;;;;
+1FA1D;NEUTRAL CHESS KNIGHT ROTATED ONE HUNDRED THIRTY-FIVE DEGREES;So;0;ON;;;;;N;;;;;
+1FA1E;WHITE CHESS TURNED KING;So;0;ON;;;;;N;;;;;
+1FA1F;WHITE CHESS TURNED QUEEN;So;0;ON;;;;;N;;;;;
+1FA20;WHITE CHESS TURNED ROOK;So;0;ON;;;;;N;;;;;
+1FA21;WHITE CHESS TURNED BISHOP;So;0;ON;;;;;N;;;;;
+1FA22;WHITE CHESS TURNED KNIGHT;So;0;ON;;;;;N;;;;;
+1FA23;WHITE CHESS TURNED PAWN;So;0;ON;;;;;N;;;;;
+1FA24;BLACK CHESS TURNED KING;So;0;ON;;;;;N;;;;;
+1FA25;BLACK CHESS TURNED QUEEN;So;0;ON;;;;;N;;;;;
+1FA26;BLACK CHESS TURNED ROOK;So;0;ON;;;;;N;;;;;
+1FA27;BLACK CHESS TURNED BISHOP;So;0;ON;;;;;N;;;;;
+1FA28;BLACK CHESS TURNED KNIGHT;So;0;ON;;;;;N;;;;;
+1FA29;BLACK CHESS TURNED PAWN;So;0;ON;;;;;N;;;;;
+1FA2A;NEUTRAL CHESS TURNED KING;So;0;ON;;;;;N;;;;;
+1FA2B;NEUTRAL CHESS TURNED QUEEN;So;0;ON;;;;;N;;;;;
+1FA2C;NEUTRAL CHESS TURNED ROOK;So;0;ON;;;;;N;;;;;
+1FA2D;NEUTRAL CHESS TURNED BISHOP;So;0;ON;;;;;N;;;;;
+1FA2E;NEUTRAL CHESS TURNED KNIGHT;So;0;ON;;;;;N;;;;;
+1FA2F;NEUTRAL CHESS TURNED PAWN;So;0;ON;;;;;N;;;;;
+1FA30;WHITE CHESS KNIGHT ROTATED TWO HUNDRED TWENTY-FIVE DEGREES;So;0;ON;;;;;N;;;;;
+1FA31;BLACK CHESS KNIGHT ROTATED TWO HUNDRED TWENTY-FIVE DEGREES;So;0;ON;;;;;N;;;;;
+1FA32;NEUTRAL CHESS KNIGHT ROTATED TWO HUNDRED TWENTY-FIVE DEGREES;So;0;ON;;;;;N;;;;;
+1FA33;WHITE CHESS KING ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA34;WHITE CHESS QUEEN ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA35;WHITE CHESS ROOK ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA36;WHITE CHESS BISHOP ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA37;WHITE CHESS KNIGHT ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA38;WHITE CHESS PAWN ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA39;BLACK CHESS KING ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA3A;BLACK CHESS QUEEN ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA3B;BLACK CHESS ROOK ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA3C;BLACK CHESS BISHOP ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA3D;BLACK CHESS KNIGHT ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA3E;BLACK CHESS PAWN ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA3F;NEUTRAL CHESS KING ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA40;NEUTRAL CHESS QUEEN ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA41;NEUTRAL CHESS ROOK ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA42;NEUTRAL CHESS BISHOP ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA43;NEUTRAL CHESS KNIGHT ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA44;NEUTRAL CHESS PAWN ROTATED TWO HUNDRED SEVENTY DEGREES;So;0;ON;;;;;N;;;;;
+1FA45;WHITE CHESS KNIGHT ROTATED THREE HUNDRED FIFTEEN DEGREES;So;0;ON;;;;;N;;;;;
+1FA46;BLACK CHESS KNIGHT ROTATED THREE HUNDRED FIFTEEN DEGREES;So;0;ON;;;;;N;;;;;
+1FA47;NEUTRAL CHESS KNIGHT ROTATED THREE HUNDRED FIFTEEN DEGREES;So;0;ON;;;;;N;;;;;
+1FA48;WHITE CHESS EQUIHOPPER;So;0;ON;;;;;N;;;;;
+1FA49;BLACK CHESS EQUIHOPPER;So;0;ON;;;;;N;;;;;
+1FA4A;NEUTRAL CHESS EQUIHOPPER;So;0;ON;;;;;N;;;;;
+1FA4B;WHITE CHESS EQUIHOPPER ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA4C;BLACK CHESS EQUIHOPPER ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA4D;NEUTRAL CHESS EQUIHOPPER ROTATED NINETY DEGREES;So;0;ON;;;;;N;;;;;
+1FA4E;WHITE CHESS KNIGHT-QUEEN;So;0;ON;;;;;N;;;;;
+1FA4F;WHITE CHESS KNIGHT-ROOK;So;0;ON;;;;;N;;;;;
+1FA50;WHITE CHESS KNIGHT-BISHOP;So;0;ON;;;;;N;;;;;
+1FA51;BLACK CHESS KNIGHT-QUEEN;So;0;ON;;;;;N;;;;;
+1FA52;BLACK CHESS KNIGHT-ROOK;So;0;ON;;;;;N;;;;;
+1FA53;BLACK CHESS KNIGHT-BISHOP;So;0;ON;;;;;N;;;;;
1FA60;XIANGQI RED GENERAL;So;0;ON;;;;;N;;;;;
1FA61;XIANGQI RED MANDARIN;So;0;ON;;;;;N;;;;;
1FA62;XIANGQI RED ELEPHANT;So;0;ON;;;;;N;;;;;
@@ -31397,6 +31930,22 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1FA6B;XIANGQI BLACK CHARIOT;So;0;ON;;;;;N;;;;;
1FA6C;XIANGQI BLACK CANNON;So;0;ON;;;;;N;;;;;
1FA6D;XIANGQI BLACK SOLDIER;So;0;ON;;;;;N;;;;;
+1FA70;BALLET SHOES;So;0;ON;;;;;N;;;;;
+1FA71;ONE-PIECE SWIMSUIT;So;0;ON;;;;;N;;;;;
+1FA72;BRIEFS;So;0;ON;;;;;N;;;;;
+1FA73;SHORTS;So;0;ON;;;;;N;;;;;
+1FA78;DROP OF BLOOD;So;0;ON;;;;;N;;;;;
+1FA79;ADHESIVE BANDAGE;So;0;ON;;;;;N;;;;;
+1FA7A;STETHOSCOPE;So;0;ON;;;;;N;;;;;
+1FA80;YO-YO;So;0;ON;;;;;N;;;;;
+1FA81;KITE;So;0;ON;;;;;N;;;;;
+1FA82;PARACHUTE;So;0;ON;;;;;N;;;;;
+1FA90;RINGED PLANET;So;0;ON;;;;;N;;;;;
+1FA91;CHAIR;So;0;ON;;;;;N;;;;;
+1FA92;RAZOR;So;0;ON;;;;;N;;;;;
+1FA93;AXE;So;0;ON;;;;;N;;;;;
+1FA94;DIYA LAMP;So;0;ON;;;;;N;;;;;
+1FA95;BANJO;So;0;ON;;;;;N;;;;;
20000;<CJK Ideograph Extension B, First>;Lo;0;L;;;;;N;;;;;
2A6D6;<CJK Ideograph Extension B, Last>;Lo;0;L;;;;;N;;;;;
2A700;<CJK Ideograph Extension C, First>;Lo;0;L;;;;;N;;;;;
diff --git a/lib/stdlib/uc_spec/emoji-data.txt b/lib/stdlib/uc_spec/emoji-data.txt
index 6e66455252..2fb5c3ff68 100644
--- a/lib/stdlib/uc_spec/emoji-data.txt
+++ b/lib/stdlib/uc_spec/emoji-data.txt
@@ -1,11 +1,11 @@
# emoji-data.txt
-# Date: 2018-02-07, 07:55:18 GMT
-# © 2018 Unicode®, Inc.
+# Date: 2019-01-15, 12:10:05 GMT
+# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
# Emoji Data for UTS #51
-# Version: 11.0
+# Version: 12.0
#
# For documentation and usage, see http://www.unicode.org/reports/tr51
#
@@ -45,7 +45,7 @@
25FB..25FE ; Emoji # 3.2 [4] (◻️..◾) white medium square..black medium-small square
2600..2604 ; Emoji # 1.1 [5] (☀️..☄️) sun..comet
260E ; Emoji # 1.1 [1] (☎️) telephone
-2611 ; Emoji # 1.1 [1] (☑️) ballot box with check
+2611 ; Emoji # 1.1 [1] (☑️) check box with check
2614..2615 ; Emoji # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
2618 ; Emoji # 4.1 [1] (☘️) shamrock
261D ; Emoji # 1.1 [1] (☝️) index pointing up
@@ -82,14 +82,14 @@
26F7..26FA ; Emoji # 5.2 [4] (⛷️..⛺) skier..tent
26FD ; Emoji # 5.2 [1] (⛽) fuel pump
2702 ; Emoji # 1.1 [1] (✂️) scissors
-2705 ; Emoji # 6.0 [1] (✅) white heavy check mark
+2705 ; Emoji # 6.0 [1] (✅) check mark button
2708..2709 ; Emoji # 1.1 [2] (✈️..✉️) airplane..envelope
270A..270B ; Emoji # 6.0 [2] (✊..✋) raised fist..raised hand
270C..270D ; Emoji # 1.1 [2] (✌️..✍️) victory hand..writing hand
270F ; Emoji # 1.1 [1] (✏️) pencil
2712 ; Emoji # 1.1 [1] (✒️) black nib
-2714 ; Emoji # 1.1 [1] (✔️) heavy check mark
-2716 ; Emoji # 1.1 [1] (✖️) heavy multiplication x
+2714 ; Emoji # 1.1 [1] (✔️) check mark
+2716 ; Emoji # 1.1 [1] (✖️) multiplication sign
271D ; Emoji # 1.1 [1] (✝️) latin cross
2721 ; Emoji # 1.1 [1] (✡️) star of David
2728 ; Emoji # 6.0 [1] (✨) sparkles
@@ -100,8 +100,8 @@
274E ; Emoji # 6.0 [1] (❎) cross mark button
2753..2755 ; Emoji # 6.0 [3] (❓..❕) question mark..white exclamation mark
2757 ; Emoji # 5.2 [1] (❗) exclamation mark
-2763..2764 ; Emoji # 1.1 [2] (❣️..❤️) heavy heart exclamation..red heart
-2795..2797 ; Emoji # 6.0 [3] (➕..➗) heavy plus sign..heavy division sign
+2763..2764 ; Emoji # 1.1 [2] (❣️..❤️) heart exclamation..red heart
+2795..2797 ; Emoji # 6.0 [3] (➕..➗) plus sign..division sign
27A1 ; Emoji # 1.1 [1] (➡️) right arrow
27B0 ; Emoji # 6.0 [1] (➰) curly loop
27BF ; Emoji # 6.0 [1] (➿) double curly loop
@@ -109,7 +109,7 @@
2B05..2B07 ; Emoji # 4.0 [3] (⬅️..⬇️) left arrow..down arrow
2B1B..2B1C ; Emoji # 5.1 [2] (⬛..⬜) black large square..white large square
2B50 ; Emoji # 5.1 [1] (⭐) star
-2B55 ; Emoji # 5.2 [1] (⭕) heavy large circle
+2B55 ; Emoji # 5.2 [1] (⭕) hollow red circle
3030 ; Emoji # 1.1 [1] (〰️) wavy dash
303D ; Emoji # 3.2 [1] (〽️) part alternation mark
3297 ; Emoji # 1.1 [1] (㊗️) Japanese “congratulations” button
@@ -206,7 +206,7 @@
1F62E..1F62F ; Emoji # 6.1 [2] (😮..😯) face with open mouth..hushed face
1F630..1F633 ; Emoji # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
1F634 ; Emoji # 6.1 [1] (😴) sleeping face
-1F635..1F640 ; Emoji # 6.0 [12] (😵..🙀) dizzy face..weary cat face
+1F635..1F640 ; Emoji # 6.0 [12] (😵..🙀) dizzy face..weary cat
1F641..1F642 ; Emoji # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
1F643..1F644 ; Emoji # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
1F645..1F64F ; Emoji # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
@@ -214,6 +214,7 @@
1F6CB..1F6CF ; Emoji # 7.0 [5] (🛋️..🛏️) couch and lamp..bed
1F6D0 ; Emoji # 8.0 [1] (🛐) place of worship
1F6D1..1F6D2 ; Emoji # 9.0 [2] (🛑..🛒) stop sign..shopping cart
+1F6D5 ; Emoji # 12.0 [1] (🛕) hindu temple
1F6E0..1F6E5 ; Emoji # 7.0 [6] (🛠️..🛥️) hammer and wrench..motor boat
1F6E9 ; Emoji # 7.0 [1] (🛩️) small airplane
1F6EB..1F6EC ; Emoji # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
@@ -222,6 +223,9 @@
1F6F4..1F6F6 ; Emoji # 9.0 [3] (🛴..🛶) kick scooter..canoe
1F6F7..1F6F8 ; Emoji # 10.0 [2] (🛷..🛸) sled..flying saucer
1F6F9 ; Emoji # 11.0 [1] (🛹) skateboard
+1F6FA ; Emoji # 12.0 [1] (🛺) auto rickshaw
+1F7E0..1F7EB ; Emoji # 12.0 [12] (🟠..🟫) orange circle..brown square
+1F90D..1F90F ; Emoji # 12.0 [3] (🤍..🤏) white heart..pinching hand
1F910..1F918 ; Emoji # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
1F919..1F91E ; Emoji # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
1F91F ; Emoji # 10.0 [1] (🤟) love-you gesture
@@ -231,27 +235,39 @@
1F931..1F932 ; Emoji # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
1F933..1F93A ; Emoji # 9.0 [8] (🤳..🤺) selfie..person fencing
1F93C..1F93E ; Emoji # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
+1F93F ; Emoji # 12.0 [1] (🤿) diving mask
1F940..1F945 ; Emoji # 9.0 [6] (🥀..🥅) wilted flower..goal net
1F947..1F94B ; Emoji # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
1F94C ; Emoji # 10.0 [1] (🥌) curling stone
1F94D..1F94F ; Emoji # 11.0 [3] (🥍..🥏) lacrosse..flying disc
1F950..1F95E ; Emoji # 9.0 [15] (🥐..🥞) croissant..pancakes
1F95F..1F96B ; Emoji # 10.0 [13] (🥟..🥫) dumpling..canned food
-1F96C..1F970 ; Emoji # 11.0 [5] (🥬..🥰) leafy green..smiling face with 3 hearts
+1F96C..1F970 ; Emoji # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
+1F971 ; Emoji # 12.0 [1] (🥱) yawning face
1F973..1F976 ; Emoji # 11.0 [4] (🥳..🥶) partying face..cold face
1F97A ; Emoji # 11.0 [1] (🥺) pleading face
-1F97C..1F97F ; Emoji # 11.0 [4] (🥼..🥿) lab coat..woman’s flat shoe
-1F980..1F984 ; Emoji # 8.0 [5] (🦀..🦄) crab..unicorn face
+1F97B ; Emoji # 12.0 [1] (🥻) sari
+1F97C..1F97F ; Emoji # 11.0 [4] (🥼..🥿) lab coat..flat shoe
+1F980..1F984 ; Emoji # 8.0 [5] (🦀..🦄) crab..unicorn
1F985..1F991 ; Emoji # 9.0 [13] (🦅..🦑) eagle..squid
1F992..1F997 ; Emoji # 10.0 [6] (🦒..🦗) giraffe..cricket
1F998..1F9A2 ; Emoji # 11.0 [11] (🦘..🦢) kangaroo..swan
-1F9B0..1F9B9 ; Emoji # 11.0 [10] (🦰..🦹) red-haired..supervillain
+1F9A5..1F9AA ; Emoji # 12.0 [6] (🦥..🦪) sloth..oyster
+1F9AE..1F9AF ; Emoji # 12.0 [2] (🦮..🦯) guide dog..probing cane
+1F9B0..1F9B9 ; Emoji # 11.0 [10] (🦰..🦹) red hair..supervillain
+1F9BA..1F9BF ; Emoji # 12.0 [6] (🦺..🦿) safety vest..mechanical leg
1F9C0 ; Emoji # 8.0 [1] (🧀) cheese wedge
1F9C1..1F9C2 ; Emoji # 11.0 [2] (🧁..🧂) cupcake..salt
+1F9C3..1F9CA ; Emoji # 12.0 [8] (🧃..🧊) beverage box..ice cube
+1F9CD..1F9CF ; Emoji # 12.0 [3] (🧍..🧏) person standing..deaf person
1F9D0..1F9E6 ; Emoji # 10.0 [23] (🧐..🧦) face with monocle..socks
1F9E7..1F9FF ; Emoji # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
+1FA70..1FA73 ; Emoji # 12.0 [4] (🩰..🩳) ballet shoes..shorts
+1FA78..1FA7A ; Emoji # 12.0 [3] (🩸..🩺) drop of blood..stethoscope
+1FA80..1FA82 ; Emoji # 12.0 [3] (🪀..🪂) yo-yo..parachute
+1FA90..1FA95 ; Emoji # 12.0 [6] (🪐..🪕) ringed planet..banjo
-# Total elements: 1250
+# Total elements: 1311
# ================================================
@@ -278,19 +294,19 @@
26F5 ; Emoji_Presentation # 5.2 [1] (⛵) sailboat
26FA ; Emoji_Presentation # 5.2 [1] (⛺) tent
26FD ; Emoji_Presentation # 5.2 [1] (⛽) fuel pump
-2705 ; Emoji_Presentation # 6.0 [1] (✅) white heavy check mark
+2705 ; Emoji_Presentation # 6.0 [1] (✅) check mark button
270A..270B ; Emoji_Presentation # 6.0 [2] (✊..✋) raised fist..raised hand
2728 ; Emoji_Presentation # 6.0 [1] (✨) sparkles
274C ; Emoji_Presentation # 6.0 [1] (❌) cross mark
274E ; Emoji_Presentation # 6.0 [1] (❎) cross mark button
2753..2755 ; Emoji_Presentation # 6.0 [3] (❓..❕) question mark..white exclamation mark
2757 ; Emoji_Presentation # 5.2 [1] (❗) exclamation mark
-2795..2797 ; Emoji_Presentation # 6.0 [3] (➕..➗) heavy plus sign..heavy division sign
+2795..2797 ; Emoji_Presentation # 6.0 [3] (➕..➗) plus sign..division sign
27B0 ; Emoji_Presentation # 6.0 [1] (➰) curly loop
27BF ; Emoji_Presentation # 6.0 [1] (➿) double curly loop
2B1B..2B1C ; Emoji_Presentation # 5.1 [2] (⬛..⬜) black large square..white large square
2B50 ; Emoji_Presentation # 5.1 [1] (⭐) star
-2B55 ; Emoji_Presentation # 5.2 [1] (⭕) heavy large circle
+2B55 ; Emoji_Presentation # 5.2 [1] (⭕) hollow red circle
1F004 ; Emoji_Presentation # 5.1 [1] (🀄) mahjong red dragon
1F0CF ; Emoji_Presentation # 6.0 [1] (🃏) joker
1F18E ; Emoji_Presentation # 6.0 [1] (🆎) AB button (blood type)
@@ -349,7 +365,7 @@
1F62E..1F62F ; Emoji_Presentation # 6.1 [2] (😮..😯) face with open mouth..hushed face
1F630..1F633 ; Emoji_Presentation # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
1F634 ; Emoji_Presentation # 6.1 [1] (😴) sleeping face
-1F635..1F640 ; Emoji_Presentation # 6.0 [12] (😵..🙀) dizzy face..weary cat face
+1F635..1F640 ; Emoji_Presentation # 6.0 [12] (😵..🙀) dizzy face..weary cat
1F641..1F642 ; Emoji_Presentation # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
1F643..1F644 ; Emoji_Presentation # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
1F645..1F64F ; Emoji_Presentation # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
@@ -357,10 +373,14 @@
1F6CC ; Emoji_Presentation # 7.0 [1] (🛌) person in bed
1F6D0 ; Emoji_Presentation # 8.0 [1] (🛐) place of worship
1F6D1..1F6D2 ; Emoji_Presentation # 9.0 [2] (🛑..🛒) stop sign..shopping cart
+1F6D5 ; Emoji_Presentation # 12.0 [1] (🛕) hindu temple
1F6EB..1F6EC ; Emoji_Presentation # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
1F6F4..1F6F6 ; Emoji_Presentation # 9.0 [3] (🛴..🛶) kick scooter..canoe
1F6F7..1F6F8 ; Emoji_Presentation # 10.0 [2] (🛷..🛸) sled..flying saucer
1F6F9 ; Emoji_Presentation # 11.0 [1] (🛹) skateboard
+1F6FA ; Emoji_Presentation # 12.0 [1] (🛺) auto rickshaw
+1F7E0..1F7EB ; Emoji_Presentation # 12.0 [12] (🟠..🟫) orange circle..brown square
+1F90D..1F90F ; Emoji_Presentation # 12.0 [3] (🤍..🤏) white heart..pinching hand
1F910..1F918 ; Emoji_Presentation # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
1F919..1F91E ; Emoji_Presentation # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
1F91F ; Emoji_Presentation # 10.0 [1] (🤟) love-you gesture
@@ -370,27 +390,39 @@
1F931..1F932 ; Emoji_Presentation # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
1F933..1F93A ; Emoji_Presentation # 9.0 [8] (🤳..🤺) selfie..person fencing
1F93C..1F93E ; Emoji_Presentation # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
+1F93F ; Emoji_Presentation # 12.0 [1] (🤿) diving mask
1F940..1F945 ; Emoji_Presentation # 9.0 [6] (🥀..🥅) wilted flower..goal net
1F947..1F94B ; Emoji_Presentation # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
1F94C ; Emoji_Presentation # 10.0 [1] (🥌) curling stone
1F94D..1F94F ; Emoji_Presentation # 11.0 [3] (🥍..🥏) lacrosse..flying disc
1F950..1F95E ; Emoji_Presentation # 9.0 [15] (🥐..🥞) croissant..pancakes
1F95F..1F96B ; Emoji_Presentation # 10.0 [13] (🥟..🥫) dumpling..canned food
-1F96C..1F970 ; Emoji_Presentation # 11.0 [5] (🥬..🥰) leafy green..smiling face with 3 hearts
+1F96C..1F970 ; Emoji_Presentation # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
+1F971 ; Emoji_Presentation # 12.0 [1] (🥱) yawning face
1F973..1F976 ; Emoji_Presentation # 11.0 [4] (🥳..🥶) partying face..cold face
1F97A ; Emoji_Presentation # 11.0 [1] (🥺) pleading face
-1F97C..1F97F ; Emoji_Presentation # 11.0 [4] (🥼..🥿) lab coat..woman’s flat shoe
-1F980..1F984 ; Emoji_Presentation # 8.0 [5] (🦀..🦄) crab..unicorn face
+1F97B ; Emoji_Presentation # 12.0 [1] (🥻) sari
+1F97C..1F97F ; Emoji_Presentation # 11.0 [4] (🥼..🥿) lab coat..flat shoe
+1F980..1F984 ; Emoji_Presentation # 8.0 [5] (🦀..🦄) crab..unicorn
1F985..1F991 ; Emoji_Presentation # 9.0 [13] (🦅..🦑) eagle..squid
1F992..1F997 ; Emoji_Presentation # 10.0 [6] (🦒..🦗) giraffe..cricket
1F998..1F9A2 ; Emoji_Presentation # 11.0 [11] (🦘..🦢) kangaroo..swan
-1F9B0..1F9B9 ; Emoji_Presentation # 11.0 [10] (🦰..🦹) red-haired..supervillain
+1F9A5..1F9AA ; Emoji_Presentation # 12.0 [6] (🦥..🦪) sloth..oyster
+1F9AE..1F9AF ; Emoji_Presentation # 12.0 [2] (🦮..🦯) guide dog..probing cane
+1F9B0..1F9B9 ; Emoji_Presentation # 11.0 [10] (🦰..🦹) red hair..supervillain
+1F9BA..1F9BF ; Emoji_Presentation # 12.0 [6] (🦺..🦿) safety vest..mechanical leg
1F9C0 ; Emoji_Presentation # 8.0 [1] (🧀) cheese wedge
1F9C1..1F9C2 ; Emoji_Presentation # 11.0 [2] (🧁..🧂) cupcake..salt
+1F9C3..1F9CA ; Emoji_Presentation # 12.0 [8] (🧃..🧊) beverage box..ice cube
+1F9CD..1F9CF ; Emoji_Presentation # 12.0 [3] (🧍..🧏) person standing..deaf person
1F9D0..1F9E6 ; Emoji_Presentation # 10.0 [23] (🧐..🧦) face with monocle..socks
1F9E7..1F9FF ; Emoji_Presentation # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
+1FA70..1FA73 ; Emoji_Presentation # 12.0 [4] (🩰..🩳) ballet shoes..shorts
+1FA78..1FA7A ; Emoji_Presentation # 12.0 [3] (🩸..🩺) drop of blood..stethoscope
+1FA80..1FA82 ; Emoji_Presentation # 12.0 [3] (🪀..🪂) yo-yo..parachute
+1FA90..1FA95 ; Emoji_Presentation # 12.0 [6] (🪐..🪕) ringed planet..banjo
-# Total elements: 1032
+# Total elements: 1093
# ================================================
@@ -417,12 +449,12 @@
1F3CB..1F3CC ; Emoji_Modifier_Base # 7.0 [2] (🏋️..🏌️) person lifting weights..person golfing
1F442..1F443 ; Emoji_Modifier_Base # 6.0 [2] (👂..👃) ear..nose
1F446..1F450 ; Emoji_Modifier_Base # 6.0 [11] (👆..👐) backhand index pointing up..open hands
-1F466..1F469 ; Emoji_Modifier_Base # 6.0 [4] (👦..👩) boy..woman
-1F46E ; Emoji_Modifier_Base # 6.0 [1] (👮) police officer
-1F470..1F478 ; Emoji_Modifier_Base # 6.0 [9] (👰..👸) bride with veil..princess
+1F466..1F478 ; Emoji_Modifier_Base # 6.0 [19] (👦..👸) boy..princess
1F47C ; Emoji_Modifier_Base # 6.0 [1] (👼) baby angel
1F481..1F483 ; Emoji_Modifier_Base # 6.0 [3] (💁..💃) person tipping hand..woman dancing
1F485..1F487 ; Emoji_Modifier_Base # 6.0 [3] (💅..💇) nail polish..person getting haircut
+1F48F ; Emoji_Modifier_Base # 6.0 [1] (💏) kiss
+1F491 ; Emoji_Modifier_Base # 6.0 [1] (💑) couple with heart
1F4AA ; Emoji_Modifier_Base # 6.0 [1] (💪) flexed biceps
1F574..1F575 ; Emoji_Modifier_Base # 7.0 [2] (🕴️..🕵️) man in suit levitating..detective
1F57A ; Emoji_Modifier_Base # 9.0 [1] (🕺) man dancing
@@ -434,20 +466,22 @@
1F6B4..1F6B6 ; Emoji_Modifier_Base # 6.0 [3] (🚴..🚶) person biking..person walking
1F6C0 ; Emoji_Modifier_Base # 6.0 [1] (🛀) person taking bath
1F6CC ; Emoji_Modifier_Base # 7.0 [1] (🛌) person in bed
+1F90F ; Emoji_Modifier_Base # 12.0 [1] (🤏) pinching hand
1F918 ; Emoji_Modifier_Base # 8.0 [1] (🤘) sign of the horns
-1F919..1F91C ; Emoji_Modifier_Base # 9.0 [4] (🤙..🤜) call me hand..right-facing fist
-1F91E ; Emoji_Modifier_Base # 9.0 [1] (🤞) crossed fingers
+1F919..1F91E ; Emoji_Modifier_Base # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
1F91F ; Emoji_Modifier_Base # 10.0 [1] (🤟) love-you gesture
1F926 ; Emoji_Modifier_Base # 9.0 [1] (🤦) person facepalming
1F930 ; Emoji_Modifier_Base # 9.0 [1] (🤰) pregnant woman
1F931..1F932 ; Emoji_Modifier_Base # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
1F933..1F939 ; Emoji_Modifier_Base # 9.0 [7] (🤳..🤹) selfie..person juggling
-1F93D..1F93E ; Emoji_Modifier_Base # 9.0 [2] (🤽..🤾) person playing water polo..person playing handball
+1F93C..1F93E ; Emoji_Modifier_Base # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
1F9B5..1F9B6 ; Emoji_Modifier_Base # 11.0 [2] (🦵..🦶) leg..foot
1F9B8..1F9B9 ; Emoji_Modifier_Base # 11.0 [2] (🦸..🦹) superhero..supervillain
-1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (🧑..🧝) adult..elf
+1F9BB ; Emoji_Modifier_Base # 12.0 [1] (🦻) ear with hearing aid
+1F9CD..1F9CF ; Emoji_Modifier_Base # 12.0 [3] (🧍..🧏) person standing..deaf person
+1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (🧑..🧝) person..elf
-# Total elements: 106
+# Total elements: 120
# ================================================
@@ -462,7 +496,7 @@
FE0F ; Emoji_Component # 3.2 [1] () VARIATION SELECTOR-16
1F1E6..1F1FF ; Emoji_Component # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
1F3FB..1F3FF ; Emoji_Component # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
-1F9B0..1F9B3 ; Emoji_Component # 11.0 [4] (🦰..🦳) red-haired..white-haired
+1F9B0..1F9B3 ; Emoji_Component # 11.0 [4] (🦰..🦳) red hair..white hair
E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..cancel tag
# Total elements: 146
@@ -482,7 +516,7 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..ca
21A9..21AA ; Extended_Pictographic# 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
231A..231B ; Extended_Pictographic# 1.1 [2] (⌚..⌛) watch..hourglass done
2328 ; Extended_Pictographic# 1.1 [1] (⌨️) keyboard
-2388 ; Extended_Pictographic# 3.0 [1] (⎈️) HELM SYMBOL
+2388 ; Extended_Pictographic# 3.0 [1] (⎈) HELM SYMBOL
23CF ; Extended_Pictographic# 4.0 [1] (⏏️) eject button
23E9..23F3 ; Extended_Pictographic# 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
23F8..23FA ; Extended_Pictographic# 7.0 [3] (⏸️..⏺️) pause button..record button
@@ -491,42 +525,42 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..ca
25B6 ; Extended_Pictographic# 1.1 [1] (▶️) play button
25C0 ; Extended_Pictographic# 1.1 [1] (◀️) reverse button
25FB..25FE ; Extended_Pictographic# 3.2 [4] (◻️..◾) white medium square..black medium-small square
-2600..2605 ; Extended_Pictographic# 1.1 [6] (☀️..★️) sun..BLACK STAR
-2607..2612 ; Extended_Pictographic# 1.1 [12] (☇️..☒️) LIGHTNING..BALLOT BOX WITH X
+2600..2605 ; Extended_Pictographic# 1.1 [6] (☀️..★) sun..BLACK STAR
+2607..2612 ; Extended_Pictographic# 1.1 [12] (☇..☒) LIGHTNING..BALLOT BOX WITH X
2614..2615 ; Extended_Pictographic# 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
-2616..2617 ; Extended_Pictographic# 3.2 [2] (☖️..☗️) WHITE SHOGI PIECE..BLACK SHOGI PIECE
+2616..2617 ; Extended_Pictographic# 3.2 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE
2618 ; Extended_Pictographic# 4.1 [1] (☘️) shamrock
-2619 ; Extended_Pictographic# 3.0 [1] (☙️) REVERSED ROTATED FLORAL HEART BULLET
-261A..266F ; Extended_Pictographic# 1.1 [86] (☚️..♯️) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN
-2670..2671 ; Extended_Pictographic# 3.0 [2] (♰️..♱️) WEST SYRIAC CROSS..EAST SYRIAC CROSS
-2672..267D ; Extended_Pictographic# 3.2 [12] (♲️..♽️) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL
+2619 ; Extended_Pictographic# 3.0 [1] (☙) REVERSED ROTATED FLORAL HEART BULLET
+261A..266F ; Extended_Pictographic# 1.1 [86] (☚..♯) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN
+2670..2671 ; Extended_Pictographic# 3.0 [2] (♰..♱) WEST SYRIAC CROSS..EAST SYRIAC CROSS
+2672..267D ; Extended_Pictographic# 3.2 [12] (♲..♽) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL
267E..267F ; Extended_Pictographic# 4.1 [2] (♾️..♿) infinity..wheelchair symbol
-2680..2685 ; Extended_Pictographic# 3.2 [6] (⚀️..⚅️) DIE FACE-1..DIE FACE-6
-2690..2691 ; Extended_Pictographic# 4.0 [2] (⚐️..⚑️) WHITE FLAG..BLACK FLAG
+2680..2685 ; Extended_Pictographic# 3.2 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6
+2690..2691 ; Extended_Pictographic# 4.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG
2692..269C ; Extended_Pictographic# 4.1 [11] (⚒️..⚜️) hammer and pick..fleur-de-lis
-269D ; Extended_Pictographic# 5.1 [1] (⚝️) OUTLINED WHITE STAR
-269E..269F ; Extended_Pictographic# 5.2 [2] (⚞️..⚟️) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT
+269D ; Extended_Pictographic# 5.1 [1] (⚝) OUTLINED WHITE STAR
+269E..269F ; Extended_Pictographic# 5.2 [2] (⚞..⚟) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT
26A0..26A1 ; Extended_Pictographic# 4.0 [2] (⚠️..⚡) warning..high voltage
-26A2..26B1 ; Extended_Pictographic# 4.1 [16] (⚢️..⚱️) DOUBLED FEMALE SIGN..funeral urn
-26B2 ; Extended_Pictographic# 5.0 [1] (⚲️) NEUTER
-26B3..26BC ; Extended_Pictographic# 5.1 [10] (⚳️..⚼️) CERES..SESQUIQUADRATE
-26BD..26BF ; Extended_Pictographic# 5.2 [3] (⚽..⚿️) soccer ball..SQUARED KEY
-26C0..26C3 ; Extended_Pictographic# 5.1 [4] (⛀️..⛃️) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING
-26C4..26CD ; Extended_Pictographic# 5.2 [10] (⛄..⛍️) snowman without snow..DISABLED CAR
+26A2..26B1 ; Extended_Pictographic# 4.1 [16] (⚢..⚱️) DOUBLED FEMALE SIGN..funeral urn
+26B2 ; Extended_Pictographic# 5.0 [1] (⚲) NEUTER
+26B3..26BC ; Extended_Pictographic# 5.1 [10] (⚳..⚼) CERES..SESQUIQUADRATE
+26BD..26BF ; Extended_Pictographic# 5.2 [3] (⚽..⚿) soccer ball..SQUARED KEY
+26C0..26C3 ; Extended_Pictographic# 5.1 [4] (⛀..⛃) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING
+26C4..26CD ; Extended_Pictographic# 5.2 [10] (⛄..⛍) snowman without snow..DISABLED CAR
26CE ; Extended_Pictographic# 6.0 [1] (⛎) Ophiuchus
-26CF..26E1 ; Extended_Pictographic# 5.2 [19] (⛏️..⛡️) pick..RESTRICTED LEFT ENTRY-2
-26E2 ; Extended_Pictographic# 6.0 [1] (⛢️) ASTRONOMICAL SYMBOL FOR URANUS
-26E3 ; Extended_Pictographic# 5.2 [1] (⛣️) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
-26E4..26E7 ; Extended_Pictographic# 6.0 [4] (⛤️..⛧️) PENTAGRAM..INVERTED PENTAGRAM
-26E8..26FF ; Extended_Pictographic# 5.2 [24] (⛨️..⛿️) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE
-2700 ; Extended_Pictographic# 7.0 [1] (✀️) BLACK SAFETY SCISSORS
-2701..2704 ; Extended_Pictographic# 1.1 [4] (✁️..✄️) UPPER BLADE SCISSORS..WHITE SCISSORS
-2705 ; Extended_Pictographic# 6.0 [1] (✅) white heavy check mark
+26CF..26E1 ; Extended_Pictographic# 5.2 [19] (⛏️..⛡) pick..RESTRICTED LEFT ENTRY-2
+26E2 ; Extended_Pictographic# 6.0 [1] (⛢) ASTRONOMICAL SYMBOL FOR URANUS
+26E3 ; Extended_Pictographic# 5.2 [1] (⛣) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
+26E4..26E7 ; Extended_Pictographic# 6.0 [4] (⛤..⛧) PENTAGRAM..INVERTED PENTAGRAM
+26E8..26FF ; Extended_Pictographic# 5.2 [24] (⛨..⛿) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE
+2700 ; Extended_Pictographic# 7.0 [1] (✀) BLACK SAFETY SCISSORS
+2701..2704 ; Extended_Pictographic# 1.1 [4] (✁..✄) UPPER BLADE SCISSORS..WHITE SCISSORS
+2705 ; Extended_Pictographic# 6.0 [1] (✅) check mark button
2708..2709 ; Extended_Pictographic# 1.1 [2] (✈️..✉️) airplane..envelope
270A..270B ; Extended_Pictographic# 6.0 [2] (✊..✋) raised fist..raised hand
270C..2712 ; Extended_Pictographic# 1.1 [7] (✌️..✒️) victory hand..black nib
-2714 ; Extended_Pictographic# 1.1 [1] (✔️) heavy check mark
-2716 ; Extended_Pictographic# 1.1 [1] (✖️) heavy multiplication x
+2714 ; Extended_Pictographic# 1.1 [1] (✔️) check mark
+2716 ; Extended_Pictographic# 1.1 [1] (✖️) multiplication sign
271D ; Extended_Pictographic# 1.1 [1] (✝️) latin cross
2721 ; Extended_Pictographic# 1.1 [1] (✡️) star of David
2728 ; Extended_Pictographic# 6.0 [1] (✨) sparkles
@@ -537,8 +571,8 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..ca
274E ; Extended_Pictographic# 6.0 [1] (❎) cross mark button
2753..2755 ; Extended_Pictographic# 6.0 [3] (❓..❕) question mark..white exclamation mark
2757 ; Extended_Pictographic# 5.2 [1] (❗) exclamation mark
-2763..2767 ; Extended_Pictographic# 1.1 [5] (❣️..❧️) heavy heart exclamation..ROTATED FLORAL HEART BULLET
-2795..2797 ; Extended_Pictographic# 6.0 [3] (➕..➗) heavy plus sign..heavy division sign
+2763..2767 ; Extended_Pictographic# 1.1 [5] (❣️..❧) heart exclamation..ROTATED FLORAL HEART BULLET
+2795..2797 ; Extended_Pictographic# 6.0 [3] (➕..➗) plus sign..division sign
27A1 ; Extended_Pictographic# 1.1 [1] (➡️) right arrow
27B0 ; Extended_Pictographic# 6.0 [1] (➰) curly loop
27BF ; Extended_Pictographic# 6.0 [1] (➿) double curly loop
@@ -546,45 +580,46 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..ca
2B05..2B07 ; Extended_Pictographic# 4.0 [3] (⬅️..⬇️) left arrow..down arrow
2B1B..2B1C ; Extended_Pictographic# 5.1 [2] (⬛..⬜) black large square..white large square
2B50 ; Extended_Pictographic# 5.1 [1] (⭐) star
-2B55 ; Extended_Pictographic# 5.2 [1] (⭕) heavy large circle
+2B55 ; Extended_Pictographic# 5.2 [1] (⭕) hollow red circle
3030 ; Extended_Pictographic# 1.1 [1] (〰️) wavy dash
303D ; Extended_Pictographic# 3.2 [1] (〽️) part alternation mark
3297 ; Extended_Pictographic# 1.1 [1] (㊗️) Japanese “congratulations” button
3299 ; Extended_Pictographic# 1.1 [1] (㊙️) Japanese “secret” button
-1F000..1F02B ; Extended_Pictographic# 5.1 [44] (🀀️..🀫️) MAHJONG TILE EAST WIND..MAHJONG TILE BACK
-1F02C..1F02F ; Extended_Pictographic# NA [4] (🀬️..🀯️) <reserved-1F02C>..<reserved-1F02F>
-1F030..1F093 ; Extended_Pictographic# 5.1[100] (🀰️..🂓️) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
-1F094..1F09F ; Extended_Pictographic# NA [12] (🂔️..🂟️) <reserved-1F094>..<reserved-1F09F>
-1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (🂠️..🂮️) PLAYING CARD BACK..PLAYING CARD KING OF SPADES
-1F0AF..1F0B0 ; Extended_Pictographic# NA [2] (🂯️..🂰️) <reserved-1F0AF>..<reserved-1F0B0>
-1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (🂱️..🂾️) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS
-1F0BF ; Extended_Pictographic# 7.0 [1] (🂿️) PLAYING CARD RED JOKER
-1F0C0 ; Extended_Pictographic# NA [1] (🃀️) <reserved-1F0C0>
-1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (🃁️..🃏) PLAYING CARD ACE OF DIAMONDS..joker
-1F0D0 ; Extended_Pictographic# NA [1] (🃐️) <reserved-1F0D0>
-1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (🃑️..🃟️) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER
-1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (🃠️..🃵️) PLAYING CARD FOOL..PLAYING CARD TRUMP-21
-1F0F6..1F0FF ; Extended_Pictographic# NA [10] (🃶️..🃿️) <reserved-1F0F6>..<reserved-1F0FF>
-1F10D..1F10F ; Extended_Pictographic# NA [3] (🄍️..🄏️) <reserved-1F10D>..<reserved-1F10F>
-1F12F ; Extended_Pictographic# 11.0 [1] (🄯️) COPYLEFT SYMBOL
-1F16C..1F16F ; Extended_Pictographic# NA [4] (🅬️..🅯️) <reserved-1F16C>..<reserved-1F16F>
+1F000..1F02B ; Extended_Pictographic# 5.1 [44] (🀀..🀫) MAHJONG TILE EAST WIND..MAHJONG TILE BACK
+1F02C..1F02F ; Extended_Pictographic# NA [4] (🀬..🀯) <reserved-1F02C>..<reserved-1F02F>
+1F030..1F093 ; Extended_Pictographic# 5.1[100] (🀰..🂓) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
+1F094..1F09F ; Extended_Pictographic# NA [12] (🂔..🂟) <reserved-1F094>..<reserved-1F09F>
+1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (🂠..🂮) PLAYING CARD BACK..PLAYING CARD KING OF SPADES
+1F0AF..1F0B0 ; Extended_Pictographic# NA [2] (🂯..🂰) <reserved-1F0AF>..<reserved-1F0B0>
+1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (🂱..🂾) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS
+1F0BF ; Extended_Pictographic# 7.0 [1] (🂿) PLAYING CARD RED JOKER
+1F0C0 ; Extended_Pictographic# NA [1] (🃀) <reserved-1F0C0>
+1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (🃁..🃏) PLAYING CARD ACE OF DIAMONDS..joker
+1F0D0 ; Extended_Pictographic# NA [1] (🃐) <reserved-1F0D0>
+1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (🃑..🃟) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER
+1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (🃠..🃵) PLAYING CARD FOOL..PLAYING CARD TRUMP-21
+1F0F6..1F0FF ; Extended_Pictographic# NA [10] (🃶..🃿) <reserved-1F0F6>..<reserved-1F0FF>
+1F10D..1F10F ; Extended_Pictographic# NA [3] (🄍..🄏) <reserved-1F10D>..<reserved-1F10F>
+1F12F ; Extended_Pictographic# 11.0 [1] (🄯) COPYLEFT SYMBOL
+1F16C ; Extended_Pictographic# 12.0 [1] (🅬) RAISED MR SIGN
+1F16D..1F16F ; Extended_Pictographic# NA [3] (🅭..🅯) <reserved-1F16D>..<reserved-1F16F>
1F170..1F171 ; Extended_Pictographic# 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
1F17E ; Extended_Pictographic# 6.0 [1] (🅾️) O button (blood type)
1F17F ; Extended_Pictographic# 5.2 [1] (🅿️) P button
1F18E ; Extended_Pictographic# 6.0 [1] (🆎) AB button (blood type)
1F191..1F19A ; Extended_Pictographic# 6.0 [10] (🆑..🆚) CL button..VS button
-1F1AD..1F1E5 ; Extended_Pictographic# NA [57] (🆭️..🇥️) <reserved-1F1AD>..<reserved-1F1E5>
+1F1AD..1F1E5 ; Extended_Pictographic# NA [57] (🆭..🇥) <reserved-1F1AD>..<reserved-1F1E5>
1F201..1F202 ; Extended_Pictographic# 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
-1F203..1F20F ; Extended_Pictographic# NA [13] (🈃️..🈏️) <reserved-1F203>..<reserved-1F20F>
+1F203..1F20F ; Extended_Pictographic# NA [13] (🈃..🈏) <reserved-1F203>..<reserved-1F20F>
1F21A ; Extended_Pictographic# 5.2 [1] (🈚) Japanese “free of charge” button
1F22F ; Extended_Pictographic# 5.2 [1] (🈯) Japanese “reserved” button
1F232..1F23A ; Extended_Pictographic# 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
-1F23C..1F23F ; Extended_Pictographic# NA [4] (🈼️..🈿️) <reserved-1F23C>..<reserved-1F23F>
-1F249..1F24F ; Extended_Pictographic# NA [7] (🉉️..🉏️) <reserved-1F249>..<reserved-1F24F>
+1F23C..1F23F ; Extended_Pictographic# NA [4] (🈼..🈿) <reserved-1F23C>..<reserved-1F23F>
+1F249..1F24F ; Extended_Pictographic# NA [7] (🉉..🉏) <reserved-1F249>..<reserved-1F24F>
1F250..1F251 ; Extended_Pictographic# 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
-1F252..1F25F ; Extended_Pictographic# NA [14] (🉒️..🉟️) <reserved-1F252>..<reserved-1F25F>
-1F260..1F265 ; Extended_Pictographic# 10.0 [6] (🉠️..🉥️) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
-1F266..1F2FF ; Extended_Pictographic# NA[154] (🉦️..🋿️) <reserved-1F266>..<reserved-1F2FF>
+1F252..1F25F ; Extended_Pictographic# NA [14] (🉒..🉟) <reserved-1F252>..<reserved-1F25F>
+1F260..1F265 ; Extended_Pictographic# 10.0 [6] (🉠..🉥) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
+1F266..1F2FF ; Extended_Pictographic# NA[154] (🉦..🋿) <reserved-1F266>..<reserved-1F2FF>
1F300..1F320 ; Extended_Pictographic# 6.0 [33] (🌀..🌠) cyclone..shooting star
1F321..1F32C ; Extended_Pictographic# 7.0 [12] (🌡️..🌬️) thermometer..wind face
1F32D..1F32F ; Extended_Pictographic# 8.0 [3] (🌭..🌯) hot dog..burrito
@@ -594,7 +629,7 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..ca
1F37D ; Extended_Pictographic# 7.0 [1] (🍽️) fork and knife with plate
1F37E..1F37F ; Extended_Pictographic# 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
1F380..1F393 ; Extended_Pictographic# 6.0 [20] (🎀..🎓) ribbon..graduation cap
-1F394..1F39F ; Extended_Pictographic# 7.0 [12] (🎔️..🎟️) HEART WITH TIP ON THE LEFT..admission tickets
+1F394..1F39F ; Extended_Pictographic# 7.0 [12] (🎔..🎟️) HEART WITH TIP ON THE LEFT..admission tickets
1F3A0..1F3C4 ; Extended_Pictographic# 6.0 [37] (🎠..🏄) carousel horse..person surfing
1F3C5 ; Extended_Pictographic# 7.0 [1] (🏅) sports medal
1F3C6..1F3CA ; Extended_Pictographic# 6.0 [5] (🏆..🏊) trophy..person swimming
@@ -602,7 +637,7 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..ca
1F3CF..1F3D3 ; Extended_Pictographic# 8.0 [5] (🏏..🏓) cricket game..ping pong
1F3D4..1F3DF ; Extended_Pictographic# 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
1F3E0..1F3F0 ; Extended_Pictographic# 6.0 [17] (🏠..🏰) house..castle
-1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (🏱️..🏷️) WHITE PENNANT..label
+1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (🏱..🏷️) WHITE PENNANT..label
1F3F8..1F3FA ; Extended_Pictographic# 8.0 [3] (🏸..🏺) badminton..amphora
1F400..1F43E ; Extended_Pictographic# 6.0 [63] (🐀..🐾) rat..paw prints
1F43F ; Extended_Pictographic# 7.0 [1] (🐿️) chipmunk
@@ -611,15 +646,15 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..ca
1F442..1F4F7 ; Extended_Pictographic# 6.0[182] (👂..📷) ear..camera
1F4F8 ; Extended_Pictographic# 7.0 [1] (📸) camera with flash
1F4F9..1F4FC ; Extended_Pictographic# 6.0 [4] (📹..📼) video camera..videocassette
-1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (📽️..📾️) film projector..PORTABLE STEREO
+1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (📽️..📾) film projector..PORTABLE STEREO
1F4FF ; Extended_Pictographic# 8.0 [1] (📿) prayer beads
1F500..1F53D ; Extended_Pictographic# 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
-1F546..1F54A ; Extended_Pictographic# 7.0 [5] (🕆️..🕊️) WHITE LATIN CROSS..dove
-1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (🕋..🕏️) kaaba..BOWL OF HYGIEIA
+1F546..1F54A ; Extended_Pictographic# 7.0 [5] (🕆..🕊️) WHITE LATIN CROSS..dove
+1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (🕋..🕏) kaaba..BOWL OF HYGIEIA
1F550..1F567 ; Extended_Pictographic# 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
-1F568..1F579 ; Extended_Pictographic# 7.0 [18] (🕨️..🕹️) RIGHT SPEAKER..joystick
+1F568..1F579 ; Extended_Pictographic# 7.0 [18] (🕨..🕹️) RIGHT SPEAKER..joystick
1F57A ; Extended_Pictographic# 9.0 [1] (🕺) man dancing
-1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (🕻️..🖣️) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX
+1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (🕻..🖣) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX
1F5A4 ; Extended_Pictographic# 9.0 [1] (🖤) black heart
1F5A5..1F5FA ; Extended_Pictographic# 7.0 [86] (🖥️..🗺️) desktop computer..world map
1F5FB..1F5FF ; Extended_Pictographic# 6.0 [5] (🗻..🗿) mount fuji..moai
@@ -644,32 +679,37 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..ca
1F62E..1F62F ; Extended_Pictographic# 6.1 [2] (😮..😯) face with open mouth..hushed face
1F630..1F633 ; Extended_Pictographic# 6.0 [4] (😰..😳) anxious face with sweat..flushed face
1F634 ; Extended_Pictographic# 6.1 [1] (😴) sleeping face
-1F635..1F640 ; Extended_Pictographic# 6.0 [12] (😵..🙀) dizzy face..weary cat face
+1F635..1F640 ; Extended_Pictographic# 6.0 [12] (😵..🙀) dizzy face..weary cat
1F641..1F642 ; Extended_Pictographic# 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
1F643..1F644 ; Extended_Pictographic# 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
1F645..1F64F ; Extended_Pictographic# 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
1F680..1F6C5 ; Extended_Pictographic# 6.0 [70] (🚀..🛅) rocket..left luggage
-1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (🛆️..🛏️) TRIANGLE WITH ROUNDED CORNERS..bed
+1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (🛆..🛏️) TRIANGLE WITH ROUNDED CORNERS..bed
1F6D0 ; Extended_Pictographic# 8.0 [1] (🛐) place of worship
1F6D1..1F6D2 ; Extended_Pictographic# 9.0 [2] (🛑..🛒) stop sign..shopping cart
-1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (🛓️..🛔️) STUPA..PAGODA
-1F6D5..1F6DF ; Extended_Pictographic# NA [11] (🛕️..🛟️) <reserved-1F6D5>..<reserved-1F6DF>
+1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (🛓..🛔) STUPA..PAGODA
+1F6D5 ; Extended_Pictographic# 12.0 [1] (🛕) hindu temple
+1F6D6..1F6DF ; Extended_Pictographic# NA [10] (🛖..🛟) <reserved-1F6D6>..<reserved-1F6DF>
1F6E0..1F6EC ; Extended_Pictographic# 7.0 [13] (🛠️..🛬) hammer and wrench..airplane arrival
-1F6ED..1F6EF ; Extended_Pictographic# NA [3] (🛭️..🛯️) <reserved-1F6ED>..<reserved-1F6EF>
+1F6ED..1F6EF ; Extended_Pictographic# NA [3] (🛭..🛯) <reserved-1F6ED>..<reserved-1F6EF>
1F6F0..1F6F3 ; Extended_Pictographic# 7.0 [4] (🛰️..🛳️) satellite..passenger ship
1F6F4..1F6F6 ; Extended_Pictographic# 9.0 [3] (🛴..🛶) kick scooter..canoe
1F6F7..1F6F8 ; Extended_Pictographic# 10.0 [2] (🛷..🛸) sled..flying saucer
1F6F9 ; Extended_Pictographic# 11.0 [1] (🛹) skateboard
-1F6FA..1F6FF ; Extended_Pictographic# NA [6] (🛺️..🛿️) <reserved-1F6FA>..<reserved-1F6FF>
-1F774..1F77F ; Extended_Pictographic# NA [12] (🝴️..🝿️) <reserved-1F774>..<reserved-1F77F>
-1F7D5..1F7D8 ; Extended_Pictographic# 11.0 [4] (🟕️..🟘️) CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE
-1F7D9..1F7FF ; Extended_Pictographic# NA [39] (🟙️..🟿️) <reserved-1F7D9>..<reserved-1F7FF>
-1F80C..1F80F ; Extended_Pictographic# NA [4] (🠌️..🠏️) <reserved-1F80C>..<reserved-1F80F>
-1F848..1F84F ; Extended_Pictographic# NA [8] (🡈️..🡏️) <reserved-1F848>..<reserved-1F84F>
-1F85A..1F85F ; Extended_Pictographic# NA [6] (🡚️..🡟️) <reserved-1F85A>..<reserved-1F85F>
-1F888..1F88F ; Extended_Pictographic# NA [8] (🢈️..🢏️) <reserved-1F888>..<reserved-1F88F>
-1F8AE..1F8FF ; Extended_Pictographic# NA [82] (🢮️..🣿️) <reserved-1F8AE>..<reserved-1F8FF>
-1F90C..1F90F ; Extended_Pictographic# NA [4] (🤌️..🤏️) <reserved-1F90C>..<reserved-1F90F>
+1F6FA ; Extended_Pictographic# 12.0 [1] (🛺) auto rickshaw
+1F6FB..1F6FF ; Extended_Pictographic# NA [5] (🛻..🛿) <reserved-1F6FB>..<reserved-1F6FF>
+1F774..1F77F ; Extended_Pictographic# NA [12] (🝴..🝿) <reserved-1F774>..<reserved-1F77F>
+1F7D5..1F7D8 ; Extended_Pictographic# 11.0 [4] (🟕..🟘) CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE
+1F7D9..1F7DF ; Extended_Pictographic# NA [7] (🟙..🟟) <reserved-1F7D9>..<reserved-1F7DF>
+1F7E0..1F7EB ; Extended_Pictographic# 12.0 [12] (🟠..🟫) orange circle..brown square
+1F7EC..1F7FF ; Extended_Pictographic# NA [20] (🟬..🟿) <reserved-1F7EC>..<reserved-1F7FF>
+1F80C..1F80F ; Extended_Pictographic# NA [4] (🠌..🠏) <reserved-1F80C>..<reserved-1F80F>
+1F848..1F84F ; Extended_Pictographic# NA [8] (🡈..🡏) <reserved-1F848>..<reserved-1F84F>
+1F85A..1F85F ; Extended_Pictographic# NA [6] (🡚..🡟) <reserved-1F85A>..<reserved-1F85F>
+1F888..1F88F ; Extended_Pictographic# NA [8] (🢈..🢏) <reserved-1F888>..<reserved-1F88F>
+1F8AE..1F8FF ; Extended_Pictographic# NA [82] (🢮..🣿) <reserved-1F8AE>..<reserved-1F8FF>
+1F90C ; Extended_Pictographic# NA [1] (🤌) <reserved-1F90C>
+1F90D..1F90F ; Extended_Pictographic# 12.0 [3] (🤍..🤏) white heart..pinching hand
1F910..1F918 ; Extended_Pictographic# 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
1F919..1F91E ; Extended_Pictographic# 9.0 [6] (🤙..🤞) call me hand..crossed fingers
1F91F ; Extended_Pictographic# 10.0 [1] (🤟) love-you gesture
@@ -679,35 +719,50 @@ E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..ca
1F931..1F932 ; Extended_Pictographic# 10.0 [2] (🤱..🤲) breast-feeding..palms up together
1F933..1F93A ; Extended_Pictographic# 9.0 [8] (🤳..🤺) selfie..person fencing
1F93C..1F93E ; Extended_Pictographic# 9.0 [3] (🤼..🤾) people wrestling..person playing handball
-1F93F ; Extended_Pictographic# NA [1] (🤿️) <reserved-1F93F>
+1F93F ; Extended_Pictographic# 12.0 [1] (🤿) diving mask
1F940..1F945 ; Extended_Pictographic# 9.0 [6] (🥀..🥅) wilted flower..goal net
1F947..1F94B ; Extended_Pictographic# 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
1F94C ; Extended_Pictographic# 10.0 [1] (🥌) curling stone
1F94D..1F94F ; Extended_Pictographic# 11.0 [3] (🥍..🥏) lacrosse..flying disc
1F950..1F95E ; Extended_Pictographic# 9.0 [15] (🥐..🥞) croissant..pancakes
1F95F..1F96B ; Extended_Pictographic# 10.0 [13] (🥟..🥫) dumpling..canned food
-1F96C..1F970 ; Extended_Pictographic# 11.0 [5] (🥬..🥰) leafy green..smiling face with 3 hearts
-1F971..1F972 ; Extended_Pictographic# NA [2] (🥱️..🥲️) <reserved-1F971>..<reserved-1F972>
+1F96C..1F970 ; Extended_Pictographic# 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
+1F971 ; Extended_Pictographic# 12.0 [1] (🥱) yawning face
+1F972 ; Extended_Pictographic# NA [1] (🥲) <reserved-1F972>
1F973..1F976 ; Extended_Pictographic# 11.0 [4] (🥳..🥶) partying face..cold face
-1F977..1F979 ; Extended_Pictographic# NA [3] (🥷️..🥹️) <reserved-1F977>..<reserved-1F979>
+1F977..1F979 ; Extended_Pictographic# NA [3] (🥷..🥹) <reserved-1F977>..<reserved-1F979>
1F97A ; Extended_Pictographic# 11.0 [1] (🥺) pleading face
-1F97B ; Extended_Pictographic# NA [1] (🥻️) <reserved-1F97B>
-1F97C..1F97F ; Extended_Pictographic# 11.0 [4] (🥼..🥿) lab coat..woman’s flat shoe
-1F980..1F984 ; Extended_Pictographic# 8.0 [5] (🦀..🦄) crab..unicorn face
+1F97B ; Extended_Pictographic# 12.0 [1] (🥻) sari
+1F97C..1F97F ; Extended_Pictographic# 11.0 [4] (🥼..🥿) lab coat..flat shoe
+1F980..1F984 ; Extended_Pictographic# 8.0 [5] (🦀..🦄) crab..unicorn
1F985..1F991 ; Extended_Pictographic# 9.0 [13] (🦅..🦑) eagle..squid
1F992..1F997 ; Extended_Pictographic# 10.0 [6] (🦒..🦗) giraffe..cricket
1F998..1F9A2 ; Extended_Pictographic# 11.0 [11] (🦘..🦢) kangaroo..swan
-1F9A3..1F9AF ; Extended_Pictographic# NA [13] (🦣️..🦯️) <reserved-1F9A3>..<reserved-1F9AF>
-1F9B0..1F9B9 ; Extended_Pictographic# 11.0 [10] (🦰..🦹) red-haired..supervillain
-1F9BA..1F9BF ; Extended_Pictographic# NA [6] (🦺️..🦿️) <reserved-1F9BA>..<reserved-1F9BF>
+1F9A3..1F9A4 ; Extended_Pictographic# NA [2] (🦣..🦤) <reserved-1F9A3>..<reserved-1F9A4>
+1F9A5..1F9AA ; Extended_Pictographic# 12.0 [6] (🦥..🦪) sloth..oyster
+1F9AB..1F9AD ; Extended_Pictographic# NA [3] (🦫..🦭) <reserved-1F9AB>..<reserved-1F9AD>
+1F9AE..1F9AF ; Extended_Pictographic# 12.0 [2] (🦮..🦯) guide dog..probing cane
+1F9B0..1F9B9 ; Extended_Pictographic# 11.0 [10] (🦰..🦹) red hair..supervillain
+1F9BA..1F9BF ; Extended_Pictographic# 12.0 [6] (🦺..🦿) safety vest..mechanical leg
1F9C0 ; Extended_Pictographic# 8.0 [1] (🧀) cheese wedge
1F9C1..1F9C2 ; Extended_Pictographic# 11.0 [2] (🧁..🧂) cupcake..salt
-1F9C3..1F9CF ; Extended_Pictographic# NA [13] (🧃️..🧏️) <reserved-1F9C3>..<reserved-1F9CF>
+1F9C3..1F9CA ; Extended_Pictographic# 12.0 [8] (🧃..🧊) beverage box..ice cube
+1F9CB..1F9CC ; Extended_Pictographic# NA [2] (🧋..🧌) <reserved-1F9CB>..<reserved-1F9CC>
+1F9CD..1F9CF ; Extended_Pictographic# 12.0 [3] (🧍..🧏) person standing..deaf person
1F9D0..1F9E6 ; Extended_Pictographic# 10.0 [23] (🧐..🧦) face with monocle..socks
1F9E7..1F9FF ; Extended_Pictographic# 11.0 [25] (🧧..🧿) red envelope..nazar amulet
-1FA00..1FA5F ; Extended_Pictographic# NA [96] (🨀️..🩟️) <reserved-1FA00>..<reserved-1FA5F>
-1FA60..1FA6D ; Extended_Pictographic# 11.0 [14] (🩠️..🩭️) XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER
-1FA6E..1FFFD ; Extended_Pictographic# NA[1424] (🩮️..🿽️) <reserved-1FA6E>..<reserved-1FFFD>
+1FA00..1FA53 ; Extended_Pictographic# 12.0 [84] (🨀..🩓) NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP
+1FA54..1FA5F ; Extended_Pictographic# NA [12] (🩔..🩟) <reserved-1FA54>..<reserved-1FA5F>
+1FA60..1FA6D ; Extended_Pictographic# 11.0 [14] (🩠..🩭) XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER
+1FA6E..1FA6F ; Extended_Pictographic# NA [2] (🩮..🩯) <reserved-1FA6E>..<reserved-1FA6F>
+1FA70..1FA73 ; Extended_Pictographic# 12.0 [4] (🩰..🩳) ballet shoes..shorts
+1FA74..1FA77 ; Extended_Pictographic# NA [4] (🩴..🩷) <reserved-1FA74>..<reserved-1FA77>
+1FA78..1FA7A ; Extended_Pictographic# 12.0 [3] (🩸..🩺) drop of blood..stethoscope
+1FA7B..1FA7F ; Extended_Pictographic# NA [5] (🩻..🩿) <reserved-1FA7B>..<reserved-1FA7F>
+1FA80..1FA82 ; Extended_Pictographic# 12.0 [3] (🪀..🪂) yo-yo..parachute
+1FA83..1FA8F ; Extended_Pictographic# NA [13] (🪃..🪏) <reserved-1FA83>..<reserved-1FA8F>
+1FA90..1FA95 ; Extended_Pictographic# 12.0 [6] (🪐..🪕) ringed planet..banjo
+1FA96..1FFFD ; Extended_Pictographic# NA[1384] (🪖..🿽) <reserved-1FA96>..<reserved-1FFFD>
# Total elements: 3793
diff --git a/lib/stdlib/uc_spec/gen_unicode_mod.escript b/lib/stdlib/uc_spec/gen_unicode_mod.escript
index de67b18afc..d820b9ed8e 100644
--- a/lib/stdlib/uc_spec/gen_unicode_mod.escript
+++ b/lib/stdlib/uc_spec/gen_unicode_mod.escript
@@ -191,7 +191,7 @@ gen_static(Fd) ->
" {U,L} -> #{upper=>U,lower=>L,title=>U,fold=>L};\n"
" {U,L,T,F} -> #{upper=>U,lower=>L,title=>T,fold=>F}\n"
" end.\n\n"),
- io:put_chars(Fd, "spec_version() -> {11,0}.\n\n\n"),
+ io:put_chars(Fd, "spec_version() -> {12,1}.\n\n\n"),
io:put_chars(Fd, "class(Codepoint) -> {CCC,_,_} = unicode_table(Codepoint),\n CCC.\n\n"),
io:put_chars(Fd, "-spec uppercase(unicode:chardata()) -> "
"maybe_improper_list(gc(),unicode:chardata()).\n"),
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index 02eee400bf..e234b0cd58 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.12.1
+STDLIB_VSN = 3.14.1
diff --git a/lib/syntax_tools/Makefile b/lib/syntax_tools/Makefile
index 14ae6d4f97..d3e2aa9b2c 100644
--- a/lib/syntax_tools/Makefile
+++ b/lib/syntax_tools/Makefile
@@ -91,3 +91,5 @@ tar: $(APP_TAR_FILE)
$(APP_TAR_FILE): $(APP_DIR)
(cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME))
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/syntax_tools/doc/src/Makefile b/lib/syntax_tools/doc/src/Makefile
index b799c76177..3ff5b92cd7 100644
--- a/lib/syntax_tools/doc/src/Makefile
+++ b/lib/syntax_tools/doc/src/Makefile
@@ -28,21 +28,10 @@ VSN=$(SYNTAX_TOOLS_VSN)
APPLICATION=syntax_tools
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
-# Man page source directory (with .erl files)
-# ----------------------------------------------------
-SRC_DIR = $(ERL_TOP)/lib/syntax_tools/src
-INC_DIR = $(ERL_TOP)/lib/syntax_tools/include
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
-XML_REF3_FILES = \
+EDOC_REF3_FILES = \
epp_dodger.xml \
erl_comment_scan.xml \
erl_prettypr.xml \
@@ -56,100 +45,20 @@ XML_REF3_FILES = \
prettypr.xml
XML_PART_FILES = part.xml
-XML_CHAPTER_FILES = chapter.xml
+EDOC_CHAPTER_FILE = chapter.xml
XML_NOTES_FILES = notes.xml
BOOK_FILES = book.xml
-
XML_FILES=\
$(BOOK_FILES) $(XML_PART_FILES) $(XML_APPLICATION_FILES) \
$(XML_NOTES_FILES)
-XML_GEN_FILES = $(XML_REF3_FILES:%=$(XMLDIR)/%) $(XML_CHAPTER_FILES:%=$(XMLDIR)/%)
-
-# ----------------------------------------------------
-INFO_FILE = ../../info
-
-HTML_FILES = \
- $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+HTML_EXTRA_FILES = ../../examples/demo.erl
-EXAMPLES_DIR = ../../examples
-EXAMPLES = $(EXAMPLES_DIR)/demo.erl
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+XML_GEN_FILES = $(XML_CHAPTER_FILES:%=$(XMLDIR)/%)
TOP_SPECS_FILE = specs.xml
# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-SPECS_FLAGS = -I../../include
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: man pdf html
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES)
-
-$(XML_REF3_FILES:%=$(XMLDIR)/%):
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript \
- -dir $(XMLDIR) $(SRC_DIR)/$(@:$(XMLDIR)/%.xml=%.erl)
-
-$(XML_CHAPTER_FILES:%=$(XMLDIR)/%):
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -def vsn $(VSN) \
- -chapter -dir $(XMLDIR) ../overview.edoc
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-xml: $(XML_REF3_FILES) $(XML_CHAPTER_FILES)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(XML_REF3_FILES) $(XML_CHAPTER_FILES) *.html
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECDIR)/*
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DIR) "$(RELSYSDIR)/examples"
- $(INSTALL_DATA) $(EXAMPLES) "$(RELSYSDIR)/doc/html"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml
index 9963ac41ae..a1f280e594 100644
--- a/lib/syntax_tools/doc/src/notes.xml
+++ b/lib/syntax_tools/doc/src/notes.xml
@@ -32,6 +32,89 @@
<p>This document describes the changes made to the Syntax_Tools
application.</p>
+<section><title>Syntax_Tools 2.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p><c>epp_dodger</c> was unable to handle a parameterized
+ macro in a function head.</p>
+ <p>
+ Own Id: OTP-17064 Aux Id: GH-4445, PR-2964 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>All functions in <c>erl_tidy</c> in syntax_tools have
+ now been deprecated and are scheduled for removal in OTP
+ 24. Users who still need it can find it at <url
+ href="https://github.com/richcarl/erl_tidy">https://github.com/richcarl/erl_tidy</url>.</p>
+ <p>
+ Own Id: OTP-17167 Aux Id: OTP-17046 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Syntax_Tools 2.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ In the <c>syntax_tools</c> application, the <c>igor</c>
+ module and all functions in <c>erl_tidy</c> except
+ <c>file/2</c> have been deprecated.</p>
+ <p>
+ Own Id: OTP-17046</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Syntax_Tools 2.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Minor documentation fix of <c>erl_syntax:operator/1</c>.</p>
+ <p>
+ Own Id: OTP-16732 Aux Id: PR-2659 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Syntax_Tools 2.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Remove incomplete support for <c>cond</c>
+ expressions. </p>
+ <p>
+ Own Id: OTP-15925 Aux Id: PR-2304 </p>
+ </item>
+ <item>
+ <p>
+ Improved indentation for code generated with
+ <c>erl_prettypr</c> and <c>tidier</c>.</p>
+ <p>
+ Own Id: OTP-16386 Aux Id: PR-2451 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Syntax_Tools 2.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/syntax_tools/src/epp_dodger.erl b/lib/syntax_tools/src/epp_dodger.erl
index 7e741cc649..8ee6368868 100644
--- a/lib/syntax_tools/src/epp_dodger.erl
+++ b/lib/syntax_tools/src/epp_dodger.erl
@@ -544,6 +544,7 @@ quickscan_macros([{'?',_}, {Type, _, _}=N | [{'(',_}|_]=Ts],
Ts1 = case skip_macro_args(Ts) of
{_, [{'->',_} | _] = Ts2} -> Ts2;
{_, [{'when',_} | _] = Ts2} -> Ts2;
+ {_, [{':',_} | _] = Ts2} -> Ts2;
_ -> Ts %% assume macro without arguments
end,
quickscan_macros_1(N, Ts1, As);
@@ -598,8 +599,6 @@ skip_macro_args([{'receive',_}=T | Ts], Es, As) ->
skip_macro_args(Ts, ['end' | Es], [T | As]);
skip_macro_args([{'try',_}=T | Ts], Es, As) ->
skip_macro_args(Ts, ['end' | Es], [T | As]);
-skip_macro_args([{'cond',_}=T | Ts], Es, As) ->
- skip_macro_args(Ts, ['end' | Es], [T | As]);
skip_macro_args([{E,_}=T | Ts], [E], As) -> %final close
{lists:reverse([T | As]), Ts};
skip_macro_args([{E,_}=T | Ts], [E | Es], As) -> %matching close
@@ -707,6 +706,8 @@ scan_macros([{'?', L}, {Type, _, _}=N | [{'(',_}|_]=Ts],
macro_call(Args, L, N, Rest, As, Opt);
[{'when',_} | _] ->
macro_call(Args, L, N, Rest, As, Opt);
+ [{':',_} | _] ->
+ macro_call(Args, L, N, Rest, As, Opt);
_ ->
macro(L, N, Ts, As, Opt)
end;
@@ -724,7 +725,7 @@ scan_macros([T | Ts], As, Opt) ->
scan_macros([], As, _Opt) ->
lists:reverse(As).
-%% Rewriting to a call which will be recognized by the post-parse pass
+%% Rewriting to a tuple which will be recognized by the post-parse pass
%% (we insert parentheses to preserve the precedences when parsing).
macro(L, {Type, _, A}, Rest, As, Opt) ->
@@ -733,17 +734,28 @@ macro(L, {Type, _, A}, Rest, As, Opt) ->
macro_call([{'(',_}, {')',_}], L, {_, Ln, _}=N, Rest, As, Opt) ->
{Open, Close} = parentheses(As),
scan_macros_1([], Rest,
- lists:reverse(Open ++ [{atom,L,?macro_call},
- {'(',L}, N, {')',Ln}] ++ Close,
- As), Opt);
+ %% {'?macro_call', N }
+ lists:reverse(Open ++ [{'{', L},
+ {atom, L, ?macro_call},
+ {',', L},
+ N,
+ {'}', Ln}] ++ Close,
+ As), Opt);
macro_call([{'(',_} | Args], L, {_, Ln, _}=N, Rest, As, Opt) ->
{Open, Close} = parentheses(As),
+ %% drop closing parenthesis
+ {')', _} = lists:last(Args), %% assert
+ Args1 = lists:droplast(Args),
%% note that we must scan the argument list; it may not be skipped
- scan_macros_1(Args ++ Close,
- Rest,
- lists:reverse(Open ++ [{atom,L,?macro_call},
- {'(',L}, N, {',',Ln}],
- As), Opt).
+ scan_macros_1(Args1 ++ [{'}', Ln} | Close],
+ Rest,
+ %% {'?macro_call', N, Arg1, ... }
+ lists:reverse(Open ++ [{'{', L},
+ {atom, L, ?macro_call},
+ {',', L},
+ N,
+ {',', Ln}],
+ As), Opt).
macro_atom(atom, A) ->
list_to_atom(?atom_prefix ++ atom_to_list(A));
@@ -800,21 +812,24 @@ rewrite(Node) ->
_ ->
Node
end;
- application ->
- F = erl_syntax:application_operator(Node),
- case erl_syntax:type(F) of
- atom ->
- case erl_syntax:atom_value(F) of
- ?macro_call ->
- [A | As] = erl_syntax:application_arguments(Node),
- M = erl_syntax:macro(A, rewrite_list(As)),
- erl_syntax:copy_pos(Node, M);
- _ ->
- rewrite_1(Node)
- end;
- _ ->
- rewrite_1(Node)
- end;
+ tuple ->
+ case erl_syntax:tuple_elements(Node) of
+ [MagicWord, A | As] ->
+ case erl_syntax:type(MagicWord) of
+ atom ->
+ case erl_syntax:atom_value(MagicWord) of
+ ?macro_call ->
+ M = erl_syntax:macro(A, rewrite_list(As)),
+ erl_syntax:copy_pos(Node, M);
+ _ ->
+ rewrite_1(Node)
+ end;
+ _ ->
+ rewrite_1(Node)
+ end;
+ _ ->
+ rewrite_1(Node)
+ end;
_ ->
rewrite_1(Node)
end.
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index 6ad9bec2e6..75a5cd56bf 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -53,7 +53,7 @@
-type hook() :: 'none'
| fun((erl_syntax:syntaxTree(), _, _) -> prettypr:document()).
--type clause_t() :: 'case_expr' | 'cond_expr' | 'fun_expr'
+-type clause_t() :: 'case_expr' | 'fun_expr'
| 'if_expr' | 'receive_expr' | 'try_expr'
| {'function', prettypr:document()}
| 'spec'.
@@ -66,7 +66,8 @@
paper = ?PAPER :: integer(),
ribbon = ?RIBBON :: integer(),
user = ?NOUSER :: term(),
- encoding = epp:default_encoding() :: epp:source_encoding()}).
+ encoding = epp:default_encoding() :: epp:source_encoding(),
+ empty_lines = sets:new() :: sets:set(integer())}).
-type context() :: #ctxt{}.
@@ -358,7 +359,8 @@ layout(Node, Options) ->
ribbon = proplists:get_value(ribbon, Options, ?RIBBON),
user = proplists:get_value(user, Options),
encoding = proplists:get_value(encoding, Options,
- epp:default_encoding())}).
+ epp:default_encoding()),
+ empty_lines = proplists:get_value(empty_lines, Options, sets:new())}).
lay(Node, Ctxt) ->
case erl_syntax:get_ann(Node) of
@@ -474,13 +476,13 @@ lay_2(Node, Ctxt) ->
floating(text(",")), reset_prec(Ctxt),
fun lay/2),
beside(floating(text("{")),
- beside(par(Es),
+ beside(sep(Es),
floating(text("}"))));
list ->
Ctxt1 = reset_prec(Ctxt),
Node1 = erl_syntax:compact_list(Node),
- D1 = par(seq(erl_syntax:list_prefix(Node1),
+ D1 = sep(seq(erl_syntax:list_prefix(Node1),
floating(text(",")), Ctxt1,
fun lay/2)),
D = case erl_syntax:list_suffix(Node1) of
@@ -513,7 +515,7 @@ lay_2(Node, Ctxt) ->
D2 = lay(Operator, reset_prec(Ctxt)),
D3 = lay(erl_syntax:infix_expr_right(Node),
set_prec(Ctxt, PrecR)),
- D4 = par([D1, D2, D3], Ctxt#ctxt.sub_indent),
+ D4 = par([D1, D2, D3], Ctxt#ctxt.break_indent),
maybe_parentheses(D4, Prec, Ctxt);
prefix_expr ->
@@ -535,7 +537,7 @@ lay_2(Node, Ctxt) ->
'-' ->
beside(D1, D2);
_ ->
- par([D1, D2], Ctxt#ctxt.sub_indent)
+ par([D1, D2], Ctxt#ctxt.break_indent)
end,
maybe_parentheses(D3, Prec, Ctxt);
@@ -547,7 +549,7 @@ lay_2(Node, Ctxt) ->
floating(text(",")), reset_prec(Ctxt),
fun lay/2),
D1 = beside(D, beside(text("("),
- beside(par(As),
+ beside(sep(As),
floating(text(")"))))),
maybe_parentheses(D1, Prec, Ctxt);
@@ -576,9 +578,7 @@ lay_2(Node, Ctxt) ->
G ->
lay(G, Ctxt1)
end,
- D3 = sep(seq(erl_syntax:clause_body(Node),
- floating(text(",")), Ctxt1,
- fun lay/2)),
+ D3 = lay_clause_expressions(erl_syntax:clause_body(Node), Ctxt1),
case Ctxt#ctxt.clause of
fun_expr ->
make_fun_clause(D1, D2, D3, Ctxt);
@@ -586,8 +586,6 @@ lay_2(Node, Ctxt) ->
make_fun_clause(N, D1, D2, D3, Ctxt);
if_expr ->
make_if_clause(D1, D2, D3, Ctxt);
- cond_expr ->
- make_if_clause(D1, D2, D3, Ctxt);
case_expr ->
make_case_clause(D1, D2, D3, Ctxt);
receive_expr ->
@@ -614,32 +612,24 @@ lay_2(Node, Ctxt) ->
D1 = lay(erl_syntax:case_expr_argument(Node), Ctxt1),
D2 = lay_clauses(erl_syntax:case_expr_clauses(Node),
case_expr, Ctxt1),
- sep([par([follow(text("case"), D1, Ctxt1#ctxt.sub_indent),
+ sep([par([follow(text("case"), D1, Ctxt1#ctxt.break_indent),
text("of")],
Ctxt1#ctxt.break_indent),
- nest(Ctxt1#ctxt.sub_indent, D2),
+ nest(Ctxt1#ctxt.break_indent, D2),
text("end")]);
if_expr ->
Ctxt1 = reset_prec(Ctxt),
D = lay_clauses(erl_syntax:if_expr_clauses(Node),
if_expr, Ctxt1),
- sep([follow(text("if"), D, Ctxt1#ctxt.sub_indent),
- text("end")]);
-
- cond_expr ->
- Ctxt1 = reset_prec(Ctxt),
- D = lay_clauses(erl_syntax:cond_expr_clauses(Node),
- cond_expr, Ctxt1),
- sep([text("cond"),
- nest(Ctxt1#ctxt.sub_indent, D),
+ sep([follow(text("if"), D, Ctxt1#ctxt.break_indent),
text("end")]);
fun_expr ->
Ctxt1 = reset_prec(Ctxt),
D = lay_clauses(erl_syntax:fun_expr_clauses(Node),
fun_expr, Ctxt1),
- sep([follow(text("fun"), D, Ctxt1#ctxt.sub_indent),
+ sep([follow(text("fun"), D, Ctxt1#ctxt.break_indent),
text("end")]);
named_fun_expr ->
@@ -647,7 +637,7 @@ lay_2(Node, Ctxt) ->
D1 = lay(erl_syntax:named_fun_expr_name(Node), Ctxt1),
D = lay_clauses(erl_syntax:named_fun_expr_clauses(Node),
{function,D1}, Ctxt1),
- sep([follow(text("fun"), D, Ctxt1#ctxt.sub_indent),
+ sep([follow(text("fun"), D, Ctxt1#ctxt.break_indent),
text("end")]);
module_qualifier ->
@@ -734,7 +724,7 @@ lay_2(Node, Ctxt) ->
_ when Args =:= none ->
lay(N, Ctxt1);
_ ->
- D1 = par(seq(Args, floating(text(",")), Ctxt1,
+ D1 = sep(seq(Args, text(","), Ctxt1,
fun lay/2)),
beside(lay(N, Ctxt1),
beside(text("("),
@@ -766,14 +756,14 @@ lay_2(Node, Ctxt) ->
Es = seq(erl_syntax:block_expr_body(Node),
floating(text(",")), Ctxt1, fun lay/2),
sep([text("begin"),
- nest(Ctxt1#ctxt.sub_indent, sep(Es)),
+ nest(Ctxt1#ctxt.break_indent, sep(Es)),
text("end")]);
catch_expr ->
{Prec, PrecR} = preop_prec('catch'),
D = lay(erl_syntax:catch_expr_body(Node),
set_prec(Ctxt, PrecR)),
- D1 = follow(text("catch"), D, Ctxt#ctxt.sub_indent),
+ D1 = follow(text("catch"), D, Ctxt#ctxt.break_indent),
maybe_parentheses(D1, Prec, Ctxt);
class_qualifier ->
@@ -903,10 +893,10 @@ lay_2(Node, Ctxt) ->
follow(floating(text("after")),
append_clause_body(D4, D3,
Ctxt1),
- Ctxt1#ctxt.sub_indent)])
+ Ctxt1#ctxt.break_indent)])
end,
sep([text("receive"),
- nest(Ctxt1#ctxt.sub_indent, D2),
+ nest(Ctxt1#ctxt.break_indent, D2),
text("end")]);
record_access ->
@@ -1003,7 +993,7 @@ lay_2(Node, Ctxt) ->
D1 = lay(erl_syntax:typed_record_field_body(Node), Ctxt1),
D2 = lay(erl_syntax:typed_record_field_type(Node),
set_prec(Ctxt, Prec)),
- D3 = par([D1, floating(text(" ::")), D2],
+ D3 = par([D1, floating(text("::")), D2],
Ctxt1#ctxt.break_indent),
maybe_parentheses(D3, Prec, Ctxt);
@@ -1018,7 +1008,7 @@ lay_2(Node, Ctxt) ->
D2 = sep(seq(As, floating(text(",")), Ctxt1,
fun lay/2)),
[text("after"),
- nest(Ctxt1#ctxt.sub_indent, D2)
+ nest(Ctxt1#ctxt.break_indent, D2)
| Es0]
end,
Es2 = case erl_syntax:try_expr_handlers(Node) of
@@ -1026,7 +1016,7 @@ lay_2(Node, Ctxt) ->
Hs ->
D3 = lay_clauses(Hs, try_expr, Ctxt1),
[text("catch"),
- nest(Ctxt1#ctxt.sub_indent, D3)
+ nest(Ctxt1#ctxt.break_indent, D3)
| Es1]
end,
Es3 = case erl_syntax:try_expr_clauses(Node) of
@@ -1034,10 +1024,10 @@ lay_2(Node, Ctxt) ->
Cs ->
D4 = lay_clauses(Cs, try_expr, Ctxt1),
[text("of"),
- nest(Ctxt1#ctxt.sub_indent, D4)
+ nest(Ctxt1#ctxt.break_indent, D4)
| Es2]
end,
- sep([par([follow(text("try"), D1, Ctxt1#ctxt.sub_indent),
+ sep([par([follow(text("try"), D1, Ctxt1#ctxt.break_indent),
hd(Es3)])
| tl(Es3)]);
@@ -1213,12 +1203,12 @@ lay_2(Node, Ctxt) ->
floating(text(",")), reset_prec(Ctxt),
fun lay/2),
beside(floating(text("{")),
- beside(par(Es), floating(text("}"))))
+ beside(sep(Es), floating(text("}"))))
end;
type_union ->
{_, Prec, PrecR} = type_inop_prec('|'),
- Es = par(seq(erl_syntax:type_union_types(Node),
+ Es = sep(seq(erl_syntax:type_union_types(Node),
floating(text(" |")), set_prec(Ctxt, PrecR),
fun lay/2)),
maybe_parentheses(Es, Prec, Ctxt);
@@ -1504,5 +1494,27 @@ tidy_float_2([$e | Cs]) -> tidy_float_2([$e, $+ | Cs]);
tidy_float_2([_C | Cs]) -> tidy_float_2(Cs);
tidy_float_2([]) -> [].
+lay_clause_expressions([H], Ctxt) ->
+ lay(H, Ctxt);
+lay_clause_expressions([H | T], Ctxt) ->
+ Clause = beside(lay(H, Ctxt), floating(text(","))),
+ Next = lay_clause_expressions(T, Ctxt),
+ case is_last_and_before_empty_line(H, T, Ctxt) of
+ true ->
+ above(above(Clause, text("")), Next);
+ false ->
+ above(Clause, Next)
+ end;
+lay_clause_expressions([], _) ->
+ empty().
+
+is_last_and_before_empty_line(H, [], #ctxt{empty_lines = EmptyLines}) ->
+ try sets:is_element(erl_syntax:get_pos(H) + 1, EmptyLines)
+ catch error:badarith -> false
+ end;
+is_last_and_before_empty_line(H, [H2 | _], #ctxt{empty_lines = EmptyLines}) ->
+ try ((erl_syntax:get_pos(H2) - erl_syntax:get_pos(H)) >= 2) and sets:is_element(erl_syntax:get_pos(H) + 1, EmptyLines)
+ catch error:badarith -> false
+ end.
%% =====================================================================
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index 09ef0bf7a5..8d83aefdad 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -183,8 +183,6 @@
comment/2,
comment_padding/1,
comment_text/1,
- cond_expr/1,
- cond_expr_clauses/1,
conjunction/1,
conjunction_body/1,
constrained_function_type/2,
@@ -431,6 +429,7 @@
-record(tree, {type :: atom(),
attr = #attr{} :: #attr{},
data :: term()}).
+-type tree() :: #tree{}.
%% `wrapper' records are used for attaching new-form node information to
%% `erl_parse' trees.
@@ -446,18 +445,20 @@
-record(wrapper, {type :: atom(),
attr = #attr{} :: #attr{},
tree :: erl_parse()}).
+-type wrapper() :: #wrapper{}.
%% =====================================================================
--type syntaxTree() :: #tree{} | #wrapper{} | erl_parse().
+-type syntaxTree() :: tree() | wrapper() | erl_parse().
-type erl_parse() :: erl_parse:abstract_clause()
| erl_parse:abstract_expr()
| erl_parse:abstract_form()
| erl_parse:abstract_type()
| erl_parse:form_info()
- %% To shut up Dialyzer:
- | {bin_element, _, _, _, _}.
+ | erl_parse:af_binelement(term())
+ | erl_parse:af_generator()
+ | erl_parse:af_remote_function().
%% The representation built by the Erlang standard library parser
%% `erl_parse'. This is a subset of the {@link syntaxTree()} type.
@@ -494,39 +495,38 @@
%% <td>class_qualifier</td>
%% <td>clause</td>
%% <td>comment</td>
-%% <td>cond_expr</td>
-%% </tr><tr>
%% <td>conjunction</td>
+%% </tr><tr>
%% <td>constrained_function_type</td>
%% <td>constraint</td>
%% <td>disjunction</td>
-%% </tr><tr>
%% <td>eof_marker</td>
+%% </tr><tr>
%% <td>error_marker</td>
%% <td>float</td>
%% <td>form_list</td>
-%% </tr><tr>
%% <td>fun_expr</td>
+%% </tr><tr>
%% <td>fun_type</td>
%% <td>function</td>
%% <td>function_type</td>
-%% </tr><tr>
%% <td>generator</td>
+%% </tr><tr>
%% <td>if_expr</td>
%% <td>implicit_fun</td>
%% <td>infix_expr</td>
-%% </tr><tr>
%% <td>integer</td>
+%% </tr><tr>
%% <td>integer_range_type</td>
%% <td>list</td>
%% <td>list_comp</td>
-%% </tr><tr>
%% <td>macro</td>
+%% </tr><tr>
%% <td>map_expr</td>
%% <td>map_field_assoc</td>
%% <td>map_field_exact</td>
-%% </tr><tr>
%% <td>map_type</td>
+%% </tr><tr>
%% <td>map_type_assoc</td>
%% <td>map_type_exact</td>
%% <td>match_expr</td>
@@ -556,6 +556,7 @@
%% <td>tuple_type</td>
%% <td>typed_record_field</td>
%% <td>type_application</td>
+%% </tr><tr>
%% <td>type_union</td>
%% <td>underscore</td>
%% <td>user_type_application</td>
@@ -587,7 +588,6 @@
%% @see class_qualifier/2
%% @see clause/3
%% @see comment/2
-%% @see cond_expr/1
%% @see conjunction/1
%% @see constrained_function_type/2
%% @see constraint/2
@@ -673,7 +673,6 @@ type(Node) ->
%% Composite types
{'case', _, _, _} -> case_expr;
{'catch', _, _} -> catch_expr;
- {'cond', _, _} -> cond_expr;
{'fun', _, {clauses, _}} -> fun_expr;
{named_fun, _, _, _} -> named_fun_expr;
{'fun', _, {function, _, _}} -> implicit_fun;
@@ -4132,8 +4131,8 @@ match_expr_body(Node) ->
%% character sequence represented by `Name'. This is
%% analogous to the print name of an atom, but an operator is never
%% written within single-quotes; e.g., the result of
-%% `operator('++')' represents "`++'" rather
-%% than "`'++''".
+%% <code>operator('++')</code> represents "<code>++</code>" rather
+%% than "<code>'++'</code>".
%%
%% @see operator_name/1
%% @see operator_literal/1
@@ -6290,7 +6289,6 @@ if_expr_clauses(Node) ->
%% @see case_expr_argument/1
%% @see clause/3
%% @see if_expr/1
-%% @see cond_expr/1
-record(case_expr, {argument :: syntaxTree(), clauses :: [syntaxTree()]}).
@@ -6357,60 +6355,6 @@ case_expr_clauses(Node) ->
%% =====================================================================
-%% @doc Creates an abstract cond-expression. If `Clauses' is
-%% `[C1, ..., Cn]', the result represents "<code>cond
-%% <em>C1</em>; ...; <em>Cn</em> end</code>". More exactly, if each
-%% `Ci' represents "<code>() <em>Ei</em> ->
-%% <em>Bi</em></code>", then the result represents "<code>cond
-%% <em>E1</em> -> <em>B1</em>; ...; <em>En</em> -> <em>Bn</em>
-%% end</code>".
-%%
-%% @see cond_expr_clauses/1
-%% @see clause/3
-%% @see case_expr/2
-
-%% type(Node) = cond_expr
-%% data(Node) = Clauses
-%%
-%% Clauses = [syntaxTree()]
-%%
-%% `erl_parse' representation:
-%%
-%% {'cond', Pos, Clauses}
-%%
-%% Clauses = [Clause] \ []
-%% Clause = {clause, ...}
-%%
-%% See `clause' for documentation on `erl_parse' clauses.
-
--spec cond_expr([syntaxTree()]) -> syntaxTree().
-
-cond_expr(Clauses) ->
- tree(cond_expr, Clauses).
-
-revert_cond_expr(Node) ->
- Pos = get_pos(Node),
- Clauses = [revert_clause(C) || C <- cond_expr_clauses(Node)],
- {'cond', Pos, Clauses}.
-
-
-%% =====================================================================
-%% @doc Returns the list of clause subtrees of a `cond_expr' node.
-%%
-%% @see cond_expr/1
-
--spec cond_expr_clauses(syntaxTree()) -> [syntaxTree()].
-
-cond_expr_clauses(Node) ->
- case unwrap(Node) of
- {'cond', _, Clauses} ->
- Clauses;
- Node1 ->
- data(Node1)
- end.
-
-
-%% =====================================================================
%% @equiv receive_expr(Clauses, none, [])
-spec receive_expr([syntaxTree()]) -> syntaxTree().
@@ -7534,8 +7478,6 @@ revert_root(Node) ->
revert_char(Node);
clause ->
revert_clause(Node);
- cond_expr ->
- revert_cond_expr(Node);
constrained_function_type ->
revert_constrained_function_type(Node);
constraint ->
@@ -7802,8 +7744,6 @@ subtrees(T) ->
[clause_patterns(T), [G],
clause_body(T)]
end;
- cond_expr ->
- [cond_expr_clauses(T)];
conjunction ->
[conjunction_body(T)];
constrained_function_type ->
@@ -8017,7 +7957,6 @@ make_tree(class_qualifier, [[A], [B]]) -> class_qualifier(A, B);
make_tree(class_qualifier, [[A], [B], [C]]) -> class_qualifier(A, B, C);
make_tree(clause, [P, B]) -> clause(P, none, B);
make_tree(clause, [P, [G], B]) -> clause(P, G, B);
-make_tree(cond_expr, [C]) -> cond_expr(C);
make_tree(conjunction, [E]) -> conjunction(E);
make_tree(constrained_function_type, [[F],C]) ->
constrained_function_type(F, C);
@@ -8239,7 +8178,7 @@ meta_call(F, As) ->
%% =====================================================================
%% @equiv tree(Type, [])
--spec tree(atom()) -> #tree{}.
+-spec tree(atom()) -> tree().
tree(Type) ->
tree(Type, []).
@@ -8274,7 +8213,7 @@ tree(Type) ->
%% @see data/1
%% @see type/1
--spec tree(atom(), term()) -> #tree{}.
+-spec tree(atom(), term()) -> tree().
tree(Type, Data) ->
#tree{type = Type, data = Data}.
@@ -8330,7 +8269,7 @@ data(T) -> erlang:error({badarg, T}).
%% trees. <em>Attaching a wrapper onto another wrapper structure is an
%% error</em>.
--spec wrap(erl_parse()) -> #wrapper{}.
+-spec wrap(erl_parse()) -> wrapper().
wrap(Node) ->
%% We assume that Node is an old-school `erl_parse' tree.
@@ -8344,7 +8283,7 @@ wrap(Node) ->
%% `erl_parse' tree; otherwise it returns `Node'
%% itself.
--spec unwrap(syntaxTree()) -> #tree{} | erl_parse().
+-spec unwrap(syntaxTree()) -> tree() | erl_parse().
unwrap(#wrapper{tree = Node}) -> Node;
unwrap(Node) -> Node. % This could also be a new-form node.
diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl
index 352165893f..6185007235 100644
--- a/lib/syntax_tools/src/erl_syntax_lib.erl
+++ b/lib/syntax_tools/src/erl_syntax_lib.erl
@@ -528,8 +528,6 @@ vann(Tree, Env) ->
vann_case_expr(Tree, Env);
if_expr ->
vann_if_expr(Tree, Env);
- cond_expr ->
- vann_cond_expr(Tree, Env);
receive_expr ->
vann_receive_expr(Tree, Env);
catch_expr ->
@@ -613,9 +611,6 @@ vann_if_expr(Tree, Env) ->
Tree1 = rewrite(Tree, erl_syntax:if_expr(Cs1)),
{ann_bindings(Tree1, Env, Bound, Free), Bound, Free}.
-vann_cond_expr(_Tree, _Env) ->
- erlang:error({not_implemented,cond_expr}).
-
vann_catch_expr(Tree, Env) ->
E = erl_syntax:catch_expr_body(Tree),
{E1, _, Free} = vann(E, Env),
diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl
index 5623aa6af3..f6ae2cb960 100644
--- a/lib/syntax_tools/src/erl_tidy.erl
+++ b/lib/syntax_tools/src/erl_tidy.erl
@@ -48,6 +48,7 @@
%% @type filename() = file:filename().
-module(erl_tidy).
+-deprecated([{'_','_',"use https://github.com/richcarl/erl_tidy"}]).
-export([dir/0, dir/1, dir/2, file/1, file/2, module/1, module/2]).
@@ -319,7 +320,8 @@ file_1(Parent, Name, Opts) ->
file_2(Name, Opts) ->
Opts1 = Opts ++ file__defaults(),
- Forms = read_module(Name, Opts1),
+ {Forms, EmptyLines} = read_module(Name, Opts1),
+ Opts2 = [{empty_lines, EmptyLines} | Opts1],
Comments = erl_comment_scan:file(Name),
Forms1 = erl_recomment:recomment_forms(Forms, Comments),
Tree = module(Forms1, [{file, Name} | Opts1]),
@@ -329,10 +331,10 @@ file_2(Name, Opts) ->
false ->
case proplists:get_bool(stdout, Opts1) of
true ->
- print_module(Tree, Opts1),
+ print_module(Tree, Opts2),
ok;
false ->
- write_module(Tree, Name, Opts1),
+ write_module(Tree, Name, Opts2),
ok
end
end.
@@ -341,31 +343,25 @@ read_module(Name, Opts) ->
verbose("reading module `~ts'.", [filename(Name)], Opts),
case epp_dodger:parse_file(Name, [no_fail]) of
{ok, Forms} ->
- check_forms(Forms, Name),
- Forms;
+ {Forms, empty_lines(Name)};
{error, R} ->
error_read_file(Name),
exit({error, R})
end.
-check_forms(Fs, Name) ->
- Fun = fun (F) ->
- case erl_syntax:type(F) of
- error_marker ->
- S = case erl_syntax:error_marker_info(F) of
- {_, M, D} ->
- M:format_error(D);
- _ ->
- "unknown error"
- end,
- report_error({Name, erl_syntax:get_pos(F),
- "\n ~ts"}, [S]),
- exit(error);
- _ ->
- ok
- end
- end,
- lists:foreach(Fun, Fs).
+empty_lines(Name) ->
+ {ok, Data} = file:read_file(Name),
+ List = binary:split(Data, [<<"\n">>], [global]),
+ {ok, NonEmptyLineRe} = re:compile("\\S"),
+ {Res, _} = lists:foldl(
+ fun(Line, {Set, N}) ->
+ case re:run(Line, NonEmptyLineRe) of
+ {match, _} -> {Set, N + 1};
+ nomatch -> {sets:add_element(N, Set), N + 1}
+ end
+ end,
+ {sets:new(), 1}, List),
+ Res.
%% Create the target directory and make a backup file if necessary,
%% then open the file, output the text and close the file
@@ -1551,18 +1547,6 @@ visit_match_body(Ps, P, B, Tree, Env, St0) ->
false ->
visit_match_expr_final(P, B, Tree, Env, St0)
end;
- cond_expr ->
- Cs = erl_syntax:cond_expr_clauses(B),
- case multival_clauses(Cs, length(Ps), Ps) of
- {true, Cs1} ->
- report_export_vars(Env#env.file,
- erl_syntax:get_pos(B),
- "cond", Env#env.verbosity),
- Tree1 = erl_syntax:cond_expr(Cs1),
- {rewrite(Tree, Tree1), St0};
- false ->
- visit_match_expr_final(P, B, Tree, Env, St0)
- end;
receive_expr ->
%% Handle the timeout case as an extra clause.
As = erl_syntax:receive_expr_action(B),
diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl
index b712b77e9f..ad48ddd50d 100644
--- a/lib/syntax_tools/src/igor.erl
+++ b/lib/syntax_tools/src/igor.erl
@@ -89,6 +89,8 @@
%% TODO: optionally rename all functions from specified (or all) modules.
-module(igor).
+-deprecated([{'_','_',"use https://github.com/richcarl/igor"}]).
+-compile({nowarn_deprecated_function,[{erl_tidy,module,2}]}).
-export([create_stubs/2, merge/2, merge/3, merge_files/3, merge_files/4,
merge_sources/3, parse_transform/2, rename/2, rename/3]).
diff --git a/lib/syntax_tools/src/prettypr.erl b/lib/syntax_tools/src/prettypr.erl
index 61a8993b84..1f2dfffbdb 100644
--- a/lib/syntax_tools/src/prettypr.erl
+++ b/lib/syntax_tools/src/prettypr.erl
@@ -569,6 +569,9 @@ format(D, W, R) ->
layout(L) ->
lists:reverse(layout(0, L, [])).
+layout(N, #above{d1 = #text{s = [_ | ""]}, d2 = L}, Cs) ->
+ %% Text for this line is empty. Print newline but no indentation.
+ layout(N, L, [$\n | Cs]);
layout(N, #above{d1 = #text{s = S}, d2 = L}, Cs) ->
layout(N, L, [$\n | flatrev(string_chars(S), indent(N, Cs))]);
layout(N, #nest{n = N1, d = L}, Cs) ->
@@ -578,8 +581,6 @@ layout(N, #text{s = S}, Cs) ->
layout(_N, null, Cs) ->
Cs.
-indent(N, Cs) when N >= 8 ->
- indent(N - 8, [$\t | Cs]);
indent(N, Cs) when N > 0 ->
indent(N - 1, [$\s | Cs]);
indent(_N, Cs) ->
diff --git a/lib/syntax_tools/test/merl_SUITE.erl b/lib/syntax_tools/test/merl_SUITE.erl
index 6389ad7738..8f60f6371c 100644
--- a/lib/syntax_tools/test/merl_SUITE.erl
+++ b/lib/syntax_tools/test/merl_SUITE.erl
@@ -104,11 +104,12 @@ transform_parse_error_test(_Config) ->
otp_15291(_Config) ->
C0 = merl:quote("() -> ok"),
- {clause,1,[],[],[{atom,1,ok}]} = C0,
+ A1 = erl_anno:new(1),
+ {clause,A1,[],[],[{atom,A1,ok}]} = C0,
C2 = merl:quote("(_,_) -> ok"),
- {clause,1,[{var,1,'_'},{var,1,'_'}],[],[{atom,1,ok}]} = C2,
+ {clause,A1,[{var,A1,'_'},{var,A1,'_'}],[],[{atom,A1,ok}]} = C2,
C1 = merl:quote("(_) -> ok"),
- {clause,1,[{var,1,'_'}],[],[{atom,1,ok}]} = C1,
+ {clause,A1,[{var,A1,'_'}],[],[{atom,A1,ok}]} = C1,
ok.
%% utilities
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl
index 9baf36ce11..14a7912642 100644
--- a/lib/syntax_tools/test/syntax_tools_SUITE.erl
+++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl
@@ -25,7 +25,8 @@
%% Test cases
-export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1,
revert_map_type/1,wrapped_subtrees/1,
- t_abstract_type/1,t_erl_parse_type/1,t_type/1, t_epp_dodger/1,
+ t_abstract_type/1,t_erl_parse_type/1,t_type/1,
+ t_epp_dodger/1,t_epp_dodger_clever/1,
t_comment_scan/1,t_igor/1,t_erl_tidy/1,t_prettypr/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -33,7 +34,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[app_test,appup_test,smoke_test,revert,revert_map,revert_map_type,
wrapped_subtrees,
- t_abstract_type,t_erl_parse_type,t_type,t_epp_dodger,
+ t_abstract_type,t_erl_parse_type,t_type,
+ t_epp_dodger,t_epp_dodger_clever,
t_comment_scan,t_igor,t_erl_tidy,t_prettypr].
groups() ->
@@ -330,6 +332,13 @@ t_epp_dodger(Config) when is_list(Config) ->
ok = test_epp_dodger(Filenames,DataDir,PrivDir),
ok.
+t_epp_dodger_clever(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Filenames = ["epp_dodger_clever.erl"],
+ ok = test_epp_dodger_clever(Filenames,DataDir,PrivDir),
+ ok.
+
t_comment_scan(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
Filenames = test_files(),
@@ -447,9 +456,30 @@ test_epp_dodger([Filename|Files],DataDir,PrivDir) ->
ok = pretty_print_parse_forms(FsForms,PrivDir,Filename),
test_epp_dodger(Files,DataDir,PrivDir).
+test_epp_dodger_clever([], _, _) -> ok;
+test_epp_dodger_clever([Filename|Files],DataDir,PrivDir) ->
+ io:format("Parsing ~p~n", [Filename]),
+ InFile = filename:join(DataDir, Filename),
+ Parsers = [{fun(File) ->
+ epp_dodger:parse_file(File, [clever])
+ end, parse_file},
+ {fun(File) ->
+ epp_dodger:quick_parse_file(File, [clever])
+ end, quick_parse_file}],
+ FsForms = parse_with(Parsers, InFile),
+ ok = pretty_print_parse_forms(FsForms,PrivDir,Filename),
+ test_epp_dodger_clever(Files,DataDir,PrivDir).
+
parse_with([],_) -> [];
parse_with([{Fun,ParserType}|Funs],File) ->
{ok, Fs} = Fun(File),
+ ErrorMarkers = [begin
+ print_error_markers(F, File),
+ F
+ end
+ || F <- Fs,
+ erl_syntax:type(F) =:= error_marker],
+ [] = ErrorMarkers,
[{Fs,ParserType}|parse_with(Funs,File)].
pretty_print_parse_forms([],_,_) -> ok;
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/epp_dodger_clever.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/epp_dodger_clever.erl
new file mode 100644
index 0000000000..4a3be06d98
--- /dev/null
+++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/epp_dodger_clever.erl
@@ -0,0 +1,12 @@
+-module(epp_dodger_clever).
+
+-export([foo1/0]).
+
+-define(macro_string, "hello world").
+
+foo1() ->
+ % string combining ?
+ [?macro_string
+ "hello world ",
+ "more hello"
+ ?macro_string].
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
index dd3f88d7a8..b8a21ef0ab 100644
--- 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
@@ -6,7 +6,7 @@
-module(syntax_tools_test).
--export([foo1/0,foo2/2,foo3/0,foo4/3,foo5/1]).
+-export([foo1/0,foo2/2,foo3/0,foo4/3,foo5/1,foo6/2]).
-include_lib("kernel/include/file.hrl").
-record(state, { a, b, c, d}).
@@ -24,6 +24,7 @@
-define(macro_string, "hello world").
-define(macro_argument1(X), (X + 3)).
-define(macro_argument2(X,Y), (X + 3 * Y)).
+-define(macro_argument3(X), {error, X}).
-define(macro_block(X), begin X end).
-define(macro_if(X1,X2), if X1 -> X2; true -> none end).
@@ -48,8 +49,7 @@ foo1() ->
%% macro test
foo2(A,B) ->
% string combining ?
- [?macro_string, ?macro_string
- ?macro_string,
+ [?macro_string, ?macro_string ++
"hello world "
"more hello",
[?macro_simple1,
@@ -113,3 +113,16 @@ foo5(A) ->
error:?macro_simple5 ->
nope
end.
+
+%% macros in patterns
+foo6(?MACRO_SIMPLE2, ?macro_argument3(A)) ->
+ try foo2(A,A) of
+ R -> R
+ catch
+ ?macro_argument3(B) ->
+ B;
+ error:?macro_argument3(B) ->
+ B;
+ error:?macro_argument3(B):_ ->
+ B
+ end.
diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk
index 9e6967d45d..7a93e81b94 100644
--- a/lib/syntax_tools/vsn.mk
+++ b/lib/syntax_tools/vsn.mk
@@ -1 +1 @@
-SYNTAX_TOOLS_VSN = 2.2.1
+SYNTAX_TOOLS_VSN = 2.5
diff --git a/lib/tftp/Makefile b/lib/tftp/Makefile
index a4559fbc2e..d0397beaa0 100644
--- a/lib/tftp/Makefile
+++ b/lib/tftp/Makefile
@@ -43,36 +43,6 @@ include $(ERL_TOP)/make/otp_subdir.mk
.PHONY: info gclean dialyzer dialyzer_plt dclean
-info:
- @echo "OS: $(OS)"
- @echo "DOCB: $(DOCB)"
- @echo ""
- @echo "TFTP_VSN: $(TFTP_VSN)"
- @echo "APP_VSN: $(APP_VSN)"
- @echo ""
- @echo "DIA_PLT: $(DIA_PLT)"
- @echo "DIA_ANALYSIS: $(DIA_ANALYSIS)"
- @echo ""
+DIA_PLT_APPS=
-gclean:
- git clean -fXd
-
-dclean:
- rm -f $(DIA_PLT)
- rm -f $(DIA_ANALYSIS)
-
-dialyzer_plt: $(DIA_PLT)
-
-$(DIA_PLT):
- @echo "Building $(APPLICATION) plt file"
- @dialyzer --build_plt \
- --output_plt $@ \
- -r ../$(APPLICATION)/ebin \
- --output $(DIA_ANALYSIS) \
- --verbose
-
-dialyzer: $(DIA_PLT)
- @echo "Running dialyzer on $(APPLICATION)"
- @dialyzer --plt $< \
- ../$(APPLICATION)/ebin \
- --verbose
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/tftp/doc/src/Makefile b/lib/tftp/doc/src/Makefile
index 5d76799e41..e5ff8010fe 100644
--- a/lib/tftp/doc/src/Makefile
+++ b/lib/tftp/doc/src/Makefile
@@ -26,12 +26,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
include ../../vsn.mk
VSN=$(TFTP_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
+APPLICATION=tftp
# ----------------------------------------------------
# Target Specs
@@ -59,97 +54,6 @@ XML_FILES = \
$(XML_REF3_FILES) \
$(XML_APPLICATION_FILES)
-# GIF_FILES = tftp.gif
-
-
-# ----------------------------------------------------
-
-HTML_FILES = \
- $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-EXTRA_FILES = \
- $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-ldocs: local_docs
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-clean clean_docs: clean_html clean_man clean_pdf
- rm -rf $(XMLDIR)
- rm -f errs core *~
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean_pdf:
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
-
-clean_html:
- rm -rf $(TOP_HTML_FILES) $(HTMLDIR)/*
-
-clean_man:
- rm -f $(MAN3_FILES)
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
-info:
- @echo "GIF_FILES:\n$(GIF_FILES)"
- @echo ""
- @echo "EXTRA_FILES:\n$(EXTRA_FILES)"
- @echo ""
- @echo "HTML_FILES:\n$(HTML_FILES)"
- @echo ""
- @echo "TOP_HTML_FILES:\n$(TOP_HTML_FILES)"
- @echo ""
- @echo "XML_REF3_FILES:\n$(XML_REF3_FILES)"
- @echo ""
- @echo "XML_REF6_FILES:\n$(XML_REF6_FILES)"
- @echo ""
- @echo "XML_CHAPTER_FILES:\n$(XML_CHAPTER_FILES)"
- @echo ""
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/tftp/doc/src/getting_started.xml b/lib/tftp/doc/src/getting_started.xml
index 9bce52dbe0..9ddf2e67ae 100644
--- a/lib/tftp/doc/src/getting_started.xml
+++ b/lib/tftp/doc/src/getting_started.xml
@@ -34,25 +34,25 @@
<section>
<title>General Information</title>
- <p>The <seealso marker="tftp#start/1">start/1</seealso> function starts
+ <p>The <seemfa marker="tftp#start/1">start/1</seemfa> function starts
a daemon process listening for UDP packets on a port. When it
receives a request for read or write, it spawns a temporary server
process handling the transfer.</p>
<p>On the client side,
- function <seealso marker="tftp#read_file/3">read_file/3</seealso>
- and <seealso marker="tftp#write_file/3">write_file/3</seealso>
+ function <seemfa marker="tftp#read_file/3">read_file/3</seemfa>
+ and <seemfa marker="tftp#write_file/3">write_file/3</seemfa>
spawn a temporary client process establishing
contact with a TFTP daemon and perform the file transfer.</p>
<p><c>tftp</c> uses a callback module to handle the file
transfer. Two such callback modules are provided,
<c>tftp_binary</c> and <c>tftp_file</c>. See
- <seealso marker="tftp#read_file/3">read_file/3</seealso> and
- <seealso marker="tftp#write_file/3">write_file/3</seealso> for details.
+ <seemfa marker="tftp#read_file/3">read_file/3</seemfa> and
+ <seemfa marker="tftp#write_file/3">write_file/3</seemfa> for details.
You can also implement your own callback modules, see
- <seealso marker="tftp#tftp_callback">CALLBACK FUNCTIONS</seealso>.
+ <seeerl marker="tftp#tftp_callback">CALLBACK FUNCTIONS</seeerl>.
A callback module provided by
the user is registered using option <c>callback</c>, see
- <seealso marker="tftp#options">DATA TYPES</seealso>.</p>
+ <seeerl marker="tftp#options">DATA TYPES</seeerl>.</p>
</section>
<section>
diff --git a/lib/tftp/doc/src/tftp.xml b/lib/tftp/doc/src/tftp.xml
index 57d64b7379..4a2bb85200 100644
--- a/lib/tftp/doc/src/tftp.xml
+++ b/lib/tftp/doc/src/tftp.xml
@@ -52,7 +52,7 @@
<tag><c>{host, Host}</c></tag>
<item>
<p><c>Host = hostname()</c>, see
- <seealso marker="kernel:inet">inet(3)</seealso>.</p>
+ <seeerl marker="kernel:inet">inet(3)</seeerl>.</p>
<p>The name or IP address of the host where the TFTP daemon
resides. This option is only used by the client.</p>
</item>
@@ -91,7 +91,7 @@
<tag><c>{udp, Options}</c></tag>
<item>
<p><c>Options = [Opt]</c>, see
- <seealso marker="kernel:gen_udp#open/1">gen_udp:open/2</seealso>.</p>
+ <seemfa marker="kernel:gen_udp#open/1">gen_udp:open/2</seemfa>.</p>
</item>
<tag><c>{use_tsize, Bool}</c></tag>
<item>
@@ -141,11 +141,11 @@
transferred, its local filename is matched to the regular
expressions of the registered callbacks. The first matching
callback is used during the transfer. See
- <seealso marker="#read_file/3">read_file/3</seealso> and
- <seealso marker="#write_file/3">write_file/3</seealso>.
+ <seemfa marker="#read_file/3">read_file/3</seemfa> and
+ <seemfa marker="#write_file/3">write_file/3</seemfa>.
</p>
<p>The callback module must implement the <c>tftp</c> behavior, see
- <seealso marker="#tftp_callback">CALLBACK FUNCTIONS</seealso>.</p>
+ <seeerl marker="#tftp_callback">CALLBACK FUNCTIONS</seeerl>.</p>
</item>
<tag><c>{logger, Module}</c></tag>
@@ -155,7 +155,7 @@
<p>Callback module for customized logging of errors, warnings, and
info messages. The callback module must implement the
<c>tftp_logger</c> behavior, see
- <seealso marker="#tftp_logger">LOGGER FUNCTIONS</seealso>.
+ <seeerl marker="#tftp_logger">LOGGER FUNCTIONS</seeerl>.
The default module is <c>tftp_logger</c>.</p>
</item>
@@ -537,17 +537,18 @@
</func>
</funcs>
- <section>
- <marker id="tftp_logger"></marker>
- <title>LOGGER FUNCTIONS</title>
-
- <p>A <c>tftp_logger</c> callback module is to be implemented as a
- <c>tftp_logger</c> behavior and export the following functions:</p>
-
- <marker id="error_msg"></marker>
- </section>
+
<funcs>
+ <fsdescription>
+ <marker id="tftp_logger"></marker>
+ <title>LOGGER FUNCTIONS</title>
+
+ <p>A <c>tftp_logger</c> callback module is to be implemented as a
+ <c>tftp_logger</c> behavior and export the following functions:</p>
+
+ <marker id="error_msg"></marker>
+ </fsdescription>
<func>
<name since="OTP 18.1">Logger:error_msg(Format, Data) -> ok | exit(Reason)</name>
<fsummary>Logs an error message.</fsummary>
diff --git a/lib/tftp/test/Makefile b/lib/tftp/test/Makefile
index 99f36256b0..a68bc4df6f 100644
--- a/lib/tftp/test/Makefile
+++ b/lib/tftp/test/Makefile
@@ -114,7 +114,7 @@ SOURCE = $(ERL_FILES) $(HRL_FILES)
TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-TFTP_SPECS = tftp.spec tftp_bench.spec
+TFTP_SPECS = tftp.spec
COVER_FILE = tftp.cover
TFTP_FILES = tftp.config $(TFTP_SPECS)
diff --git a/lib/tftp/test/tftp_bench.spec b/lib/tftp/test/tftp_bench.spec
deleted file mode 100644
index 43fa385c85..0000000000
--- a/lib/tftp/test/tftp_bench.spec
+++ /dev/null
@@ -1 +0,0 @@
-{suites,"../tftp_test",[]}.
diff --git a/lib/tools/Makefile b/lib/tools/Makefile
index e17e9cfd1e..d849989a2d 100644
--- a/lib/tools/Makefile
+++ b/lib/tools/Makefile
@@ -36,3 +36,6 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+DIA_PLT_APPS=compiler runtime_tools
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/tools/c_src/Makefile.in b/lib/tools/c_src/Makefile.in
index 0cd760b137..8e13571786 100644
--- a/lib/tools/c_src/Makefile.in
+++ b/lib/tools/c_src/Makefile.in
@@ -22,7 +22,6 @@ include $(ERL_TOP)/make/output.mk
include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/erts/include/internal/$(TARGET)/ethread.mk
-USING_MINGW=@MIXED_CYGWIN_MINGW@
USING_VC=@MIXED_VC@
CC=@CC@
diff --git a/lib/tools/doc/src/Makefile b/lib/tools/doc/src/Makefile
index 5ff4fe3113..f9bb2314a8 100644
--- a/lib/tools/doc/src/Makefile
+++ b/lib/tools/doc/src/Makefile
@@ -28,11 +28,6 @@ VSN=$(TOOLS_VSN)
APPLICATION=tools
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
@@ -60,89 +55,25 @@ XML_CHAPTER_FILES = \
xref_chapter.xml \
notes.xml
-
BOOK_FILES = book.xml
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES)
-GIF_FILES = \
+IMAGE_FILES = \
venn1.gif \
venn2.gif
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
-
TOP_SPECS_FILE = specs.xml
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-
-TOOLS_SRC=$(ERL_TOP)/lib/tools/src
-TOOLS_INCLUDE=$(ERL_TOP)/lib/tools/include
-
-SPECS_FLAGS = -I$(TOOLS_SRC) -I$(TOOLS_INCLUDE)
+NO_CHUNKS = erlang_mode.xml
# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECDIR)/*
- rm -f errs core *~
# erlang_mode doesn't have erlang source so we generate a dummy file for it.
$(SPECDIR)/specs_erlang_mode.xml:
- echo '<module name="erlang_mode"/>' > $(SPECDIR)/specs_erlang_mode.xml
+ $(gen_verbose)echo '<module name="erlang_mode"/>' > $(SPECDIR)/specs_erlang_mode.xml
# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/tools/doc/src/cover.xml b/lib/tools/doc/src/cover.xml
index 1605b12e25..0889a16f65 100644
--- a/lib/tools/doc/src/cover.xml
+++ b/lib/tools/doc/src/cover.xml
@@ -217,9 +217,10 @@
<v>ModFiles = ModFile | [ModFile]</v>
<v>ModFile = Module | BeamFile</v>
<v>&nbsp;Module = atom()</v>
+ <v>&nbsp;BackendModule = atom()</v>
<v>&nbsp;BeamFile = string()</v>
<v>Result = {ok,Module} | {error,BeamFile} | {error,Reason}</v>
- <v>&nbsp;Reason = non_existing | {no_abstract_code,BeamFile} | {encrypted_abstract_code,BeamFile} | {already_cover_compiled,no_beam_found,Module} | not_main_node</v>
+ <v>&nbsp;Reason = non_existing | {no_abstract_code,BeamFile} | {{missing_backend,BackendModule},BeamFile} | {encrypted_abstract_code,BeamFile} | {already_cover_compiled,no_beam_found,Module} | not_main_node</v>
</type>
<desc>
<p>Does the same as <c>compile/1,2</c>, but uses an existing
@@ -387,11 +388,11 @@
</type>
<desc>
<p>This function works exactly the same way as
- <seealso marker="#analyse_to_file-1">analyse_to_file</seealso> except
+ <seemfa marker="#analyse_to_file/1">analyse_to_file</seemfa> except
that it is asynchronous instead of synchronous. The spawned process
will link with the caller when created. If an <c>Error</c> occurs
while doing the cover analysis the process will crash with the same
- error reason as <seealso marker="#analyse_to_file-1">analyse_to_file</seealso>
+ error reason as <seemfa marker="#analyse_to_file/1">analyse_to_file</seemfa>
would return.</p>
</desc>
</func>
diff --git a/lib/tools/doc/src/cover_chapter.xml b/lib/tools/doc/src/cover_chapter.xml
index 3847adbe59..fe7b17c1fa 100644
--- a/lib/tools/doc/src/cover_chapter.xml
+++ b/lib/tools/doc/src/cover_chapter.xml
@@ -34,7 +34,7 @@
<title>Introduction</title>
<p>The module <c>cover</c> provides a set of functions for coverage
analysis of Erlang programs, counting how many times each
- <seealso marker="#lines">executable line</seealso> is executed.</p>
+ <seeguide marker="#lines">executable line</seeguide> is executed.</p>
<p>Coverage analysis can be used to verify test cases, making sure all
relevant code is covered, and may be helpful when looking for
bottlenecks in the code.</p>
@@ -127,7 +127,7 @@ s() ->
<p>Before any analysis can take place, the involved modules must be
<em>Cover compiled</em>. This means that some extra information is
added to the module before it is compiled into a binary which then
- is <seealso marker="#loading">loaded</seealso>. The source file of
+ is <seeguide marker="#loading">loaded</seeguide>. The source file of
the module is not affected and no <c>.beam</c> file is created.</p>
<pre>
2> <input>cover:compile_module(channel).</input>
@@ -381,7 +381,7 @@ File generated from channel.erl by COVER 2001-05-21 at 11:16:38
Incidentally, when the test case is corrected a bug in <c>channel</c>
should indeed be discovered.</p>
<p>When the Cover analysis is ready, Cover is stopped and all Cover
- compiled modules are <seealso marker="#loading">unloaded</seealso>.
+ compiled modules are <seeguide marker="#loading">unloaded</seeguide>.
The code for <c>channel</c> is now loaded as usual from a
<c>.beam</c> file in the current path.</p>
<pre>
@@ -405,10 +405,10 @@ ok
Cover compiled module, performance decreases proportionally to
the size and number of the Cover compiled modules.</p>
<p>To improve performance when analysing cover results it is possible
- to do multiple calls to <seealso marker="cover#analyse-1">analyse</seealso>
- and <seealso marker="cover#analyse_to_file-1">analyse_to_file</seealso>
+ to do multiple calls to <seemfa marker="cover#analyse/1">analyse</seemfa>
+ and <seemfa marker="cover#analyse_to_file/1">analyse_to_file</seemfa>
at once. You can also use the
- <seealso marker="cover#async_analyse_to_file-1">async_analyse_to_file</seealso>
+ <seemfa marker="cover#async_analyse_to_file/1">async_analyse_to_file</seemfa>
convenience function.
</p>
</section>
diff --git a/lib/tools/doc/src/cprof.xml b/lib/tools/doc/src/cprof.xml
index 5b5f8dcdc7..03675b3e49 100644
--- a/lib/tools/doc/src/cprof.xml
+++ b/lib/tools/doc/src/cprof.xml
@@ -44,7 +44,7 @@
</p>
<p>Since breakpoints are used there is no need for special
compilation of any module to be profiled. For now these
- breakpoints can only be set on BEAM code so <term id="BIF"></term>s
+ breakpoints can only be set on BEAM code so BIFs
cannot be call count traced.
</p>
<p>The size of the call counters is the host machine word
@@ -131,7 +131,7 @@
<c>(pause({'_','_','_'})+stop({on_load}))</c>.
</p>
<p>See also
- <seealso marker="#pause">pause/1..3</seealso> below.
+ <seeerl marker="#pause">pause/1..3</seeerl> below.
<marker id="pause"></marker>
</p>
</desc>
@@ -206,7 +206,7 @@
<c>(start({'_','_','_'})+start({on_load}))</c>.
</p>
<p>See also
- <seealso marker="#start">start/1..3</seealso> below.
+ <seeerl marker="#start">start/1..3</seeerl> below.
<marker id="start"></marker>
</p>
</desc>
@@ -249,7 +249,7 @@
<c>(stop({'_','_','_'})+stop({on_load}))</c>.
</p>
<p>See also
- <seealso marker="#stop">stop/1..3</seealso> below.
+ <seeerl marker="#stop">stop/1..3</seeerl> below.
<marker id="stop"></marker>
</p>
</desc>
@@ -286,10 +286,10 @@
<section>
<title>See Also</title>
- <p><seealso marker="eprof">eprof</seealso>(3),
- <seealso marker="fprof">fprof</seealso>(3),
+ <p><seeerl marker="eprof">eprof</seeerl>(3),
+ <seeerl marker="fprof">fprof</seeerl>(3),
erlang(3),
- <seealso marker="cprof_chapter">User's Guide</seealso></p>
+ <seeguide marker="cprof_chapter">User's Guide</seeguide></p>
</section>
</erlref>
diff --git a/lib/tools/doc/src/cprof_chapter.xml b/lib/tools/doc/src/cprof_chapter.xml
index ba1e7432fd..2453f67ec5 100644
--- a/lib/tools/doc/src/cprof_chapter.xml
+++ b/lib/tools/doc/src/cprof_chapter.xml
@@ -88,7 +88,7 @@
</p>
<p>The following sections show some examples of profiling with
<c>cprof</c>. See also
- <seealso marker="cprof">cprof(3)</seealso>.
+ <seeerl marker="cprof">cprof(3)</seeerl>.
</p>
<section>
diff --git a/lib/tools/doc/src/erlang_mode.xml b/lib/tools/doc/src/erlang_mode.xml
index 7fef74813b..e22d4399a3 100644
--- a/lib/tools/doc/src/erlang_mode.xml
+++ b/lib/tools/doc/src/erlang_mode.xml
@@ -200,7 +200,7 @@
<section>
<title>Tags</title>
<p>For the tag commands to work it requires that you have
- generated a tag file. See <seealso marker="erlang_mode_chapter#tags">Erlang mode users guide</seealso></p>
+ generated a tag file. See <seeguide marker="erlang_mode_chapter#tags">Erlang mode users guide</seeguide></p>
<p></p>
<list type="bulleted">
<item><em><c>M-. </c></em> (<c>find-tag</c>) -
diff --git a/lib/tools/doc/src/erlang_mode_chapter.xml b/lib/tools/doc/src/erlang_mode_chapter.xml
index b4e30d883b..2a399cb453 100644
--- a/lib/tools/doc/src/erlang_mode_chapter.xml
+++ b/lib/tools/doc/src/erlang_mode_chapter.xml
@@ -34,7 +34,7 @@
<title>Purpose</title>
<p>The purpose of this user guide is to introduce you to the
Erlang mode for Emacs and gives some relevant background
- information of the functions and features. See also <seealso marker="erlang.el">Erlang mode reference manual</seealso> The
+ information of the functions and features. See also <seeerl marker="erlang.el">Erlang mode reference manual</seeerl> The
purpose of the Erlang mode itself is to facilitate the developing
process for the Erlang programmer.</p>
</section>
@@ -104,7 +104,7 @@
</quote>
<p>The Erlang mode does, of course, provide this feature. The layout
used is based on the common use of the language.</p>
- <p>It is strongly recommend to use this feature and avoid to indent lines
+ <p>It is strongly recommended to use this feature and avoid to indent lines
in a nonstandard way. Some motivations are:</p>
<list type="bulleted">
<item>Code using the same layout is easy to read and maintain. </item>
diff --git a/lib/tools/doc/src/fprof.xml b/lib/tools/doc/src/fprof.xml
index 4bb8862016..b3ba4a200c 100644
--- a/lib/tools/doc/src/fprof.xml
+++ b/lib/tools/doc/src/fprof.xml
@@ -558,7 +558,7 @@
<item>Specifies if the analysis should be sorted according
to the ACC column, which is the default, or the OWN
column. See
- <seealso marker="#analysis">Analysis Format</seealso> below.</item>
+ <seeerl marker="#analysis">Analysis Format</seeerl> below.</item>
<tag><c>totals</c>| <c>{totals, true}</c></tag>
<item>Includes a section containing call statistics
for all calls regardless of process, in the analysis.</item>
@@ -580,7 +580,7 @@
<marker id="analysis"></marker>
<title>Analysis format</title>
<p>This section describes the output format of the analyse
- command. See <seealso marker="#analyse">analyse/0</seealso>.
+ command. See <seeerl marker="#analyse">analyse/0</seeerl>.
</p>
<p>The format is parsable with the standard Erlang parsing tools
<c>erl_scan</c> and <c>erl_parse</c>, <c>file:consult/1</c> or
@@ -598,7 +598,7 @@
-module(foo).
-export([create_file_slow/2]).
-create_file_slow(Name, N) when integer(N), N >= 0 ->
+create_file_slow(Name, N) when is_integer(N), N >= 0 ->
{ok, FD} =
file:open(Name, [raw, write, delayed_write, binary]),
if N > 256 ->
@@ -910,9 +910,9 @@ create_file_slow(FD, M, N) ->
<section>
<title>See Also</title>
- <p>dbg(3), <seealso marker="eprof">eprof</seealso>(3), erlang(3),
+ <p>dbg(3), <seeerl marker="eprof">eprof</seeerl>(3), erlang(3),
io(3),
- <seealso marker="fprof_chapter">Tools User's Guide</seealso></p>
+ <seeguide marker="fprof_chapter">Tools User's Guide</seeguide></p>
</section>
</erlref>
diff --git a/lib/tools/doc/src/fprof_chapter.xml b/lib/tools/doc/src/fprof_chapter.xml
index 5a2a5ad47c..5d161ed2e6 100644
--- a/lib/tools/doc/src/fprof_chapter.xml
+++ b/lib/tools/doc/src/fprof_chapter.xml
@@ -72,7 +72,7 @@
</p>
<p>The following sections show some examples of how to profile with
Fprof. See also the reference manual
- <seealso marker="fprof">fprof(3)</seealso>.
+ <seeerl marker="fprof">fprof(3)</seeerl>.
</p>
<section>
@@ -95,12 +95,12 @@
<c>fprof:analyse([{dest, "my_fprof.analysis"}, {cols, 120}])</c>
for a wider listing on non-default filename.
</p>
- <p>See the <seealso marker="fprof">fprof(3)</seealso> manual page
+ <p>See the <seeerl marker="fprof">fprof(3)</seeerl> manual page
for more options and arguments to the functions
- <seealso marker="fprof#trace">trace</seealso>,
- <seealso marker="fprof#profile">profile</seealso>
+ <seeerl marker="fprof#trace">trace</seeerl>,
+ <seeerl marker="fprof#profile">profile</seeerl>
and
- <seealso marker="fprof#analyse">analyse</seealso>.
+ <seeerl marker="fprof#analyse">analyse</seeerl>.
</p>
</section>
diff --git a/lib/tools/doc/src/instrument.xml b/lib/tools/doc/src/instrument.xml
index 7e9cbaebb0..a6402b0643 100644
--- a/lib/tools/doc/src/instrument.xml
+++ b/lib/tools/doc/src/instrument.xml
@@ -73,14 +73,12 @@
<desc>
<p><c><anno>AllocatorType</anno></c> is the type of the allocator that
employs this carrier.</p>
- <p><c><anno>TotalSize</anno></c> is the total size of the carrier,
- including its header.</p>
- <p><c><anno>AllocatedSize</anno></c> is the combined size of the
- carrier's allocated blocks, including their headers.</p>
- <p><c><anno>AllocatedCount</anno></c> is the number of allocated
- blocks in the carrier.</p>
<p><c><anno>InPool</anno></c> is whether the carrier is in the
migration pool.</p>
+ <p><c><anno>TotalSize</anno></c> is the total size of the carrier,
+ including its header.</p>
+ <p><c><anno>Allocations</anno></c> is a summary of the allocated blocks
+ in the carrier.</p>
<p><c><anno>FreeBlocks</anno></c> is a histogram of the free block
sizes in the carrier.</p>
<p>If the carrier could not be scanned in full without harming the
@@ -96,7 +94,7 @@
<fsummary>Return a summary of all allocations in the system.</fsummary>
<desc>
<p>Shorthand for
- <seealso marker="#allocations/1"><c>allocations(#{})</c>.</seealso></p>
+ <seemfa marker="#allocations/1"><c>allocations(#{})</c>.</seemfa></p>
</desc>
</func>
@@ -109,8 +107,8 @@
optionally filtered by allocator type and scheduler id.</p>
<p>Only binaries and allocations made by NIFs and drivers are tagged by
default, but this can be configured an a per-allocator basis with the
- <seealso marker="erts:erts_alloc#M_atags"><c>+M&lt;S&gt;atags</c>
- </seealso> emulator option.</p>
+ <seecref marker="erts:erts_alloc#M_atags"><c>+M&lt;S&gt;atags</c>
+ </seecref> emulator option.</p>
<p>If the specified allocator types are not enabled, the call will fail
with <c>{error, not_enabled}</c>.</p>
<p>The following options can be used:</p>
@@ -177,7 +175,7 @@
<fsummary>Return a list of all carriers in the system.</fsummary>
<desc>
<p>Shorthand for
- <seealso marker="#carriers/1"><c>carriers(#{})</c>.</seealso></p>
+ <seemfa marker="#carriers/1"><c>carriers(#{})</c>.</seemfa></p>
</desc>
</func>
@@ -232,8 +230,8 @@
<section>
<title>See Also</title>
- <p><seealso marker="erts:erts_alloc">erts_alloc(3)</seealso>,
- <seealso marker="erts:erl">erl(1)</seealso></p>
+ <p><seecref marker="erts:erts_alloc">erts_alloc(3)</seecref>,
+ <seecom marker="erts:erl">erl(1)</seecom></p>
</section>
</erlref>
diff --git a/lib/tools/doc/src/lcnt.xml b/lib/tools/doc/src/lcnt.xml
index 1d434decfc..94a56c239a 100644
--- a/lib/tools/doc/src/lcnt.xml
+++ b/lib/tools/doc/src/lcnt.xml
@@ -149,7 +149,7 @@
</type>
<desc>
<p>Prints a list of internal locks and its statistics.</p>
- <p>For option description, see <seealso marker="#inspect/2">lcnt:inspect/2</seealso>.</p>
+ <p>For option description, see <seemfa marker="#inspect/2">lcnt:inspect/2</seemfa>.</p>
</desc>
</func>
@@ -172,7 +172,7 @@
</type>
<desc>
<p>Prints a list of internal lock counters by source code locations.</p>
- <p>For option description, see <seealso marker="#inspect/2">lcnt:inspect/2</seealso>.</p>
+ <p>For option description, see <seemfa marker="#inspect/2">lcnt:inspect/2</seemfa>.</p>
</desc>
</func>
@@ -196,7 +196,7 @@
</type>
<desc>
<p>Prints a list of internal lock counters for a specific lock.</p>
- <p>Lock <c>Name</c> and <c>Id</c> for ports and processes are interchangeable with the use of <c>lcnt:swap_pid_keys/0</c> and is the reason why <c>pid()</c> and <c>port()</c> options can be used in both <c>Name</c> and <c>Id</c> space. Both pids and ports are special identifiers with stripped creation and can be recreated with <seealso marker="#pid/3">lcnt:pid/2,3</seealso> and <seealso marker="#port/2">lcnt:port/1,2</seealso>. </p>
+ <p>Lock <c>Name</c> and <c>Id</c> for ports and processes are interchangeable with the use of <c>lcnt:swap_pid_keys/0</c> and is the reason why <c>pid()</c> and <c>port()</c> options can be used in both <c>Name</c> and <c>Id</c> space. Both pids and ports are special identifiers with stripped creation and can be recreated with <seemfa marker="#pid/3">lcnt:pid/2,3</seemfa> and <seemfa marker="#port/2">lcnt:port/1,2</seemfa>. </p>
<p>Option description:</p>
<taglist>
<tag><c>{combine, bool()}</c></tag>
@@ -306,11 +306,12 @@
</func>
</funcs>
- <section>
- <title>Convenience functions</title>
- <p>The following functions are used for convenience.</p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Convenience functions</title>
+ <p>The following functions are used for convenience.</p>
+ </fsdescription>
<func>
<name since="OTP R13B04">apply(Fun) -> term()</name>
<fsummary>Same as <c>apply(Fun, [])</c>.</fsummary>
@@ -392,12 +393,13 @@
</funcs>
- <section>
- <title>Internal runtime lock counter controllers</title>
- <p> The following functions control the behavior of the internal counters. </p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>Internal runtime lock counter controllers</title>
+ <p> The following functions control the behavior of the internal counters. </p>
+ </fsdescription>
<func>
<name since="OTP R13B04">rt_collect() -> [lock_counter_data()]</name>
<fsummary>Same as <c>rt_collect(node())</c>.</fsummary>
@@ -529,6 +531,6 @@
<section>
<title>See Also</title>
- <p> <seealso marker="lcnt_chapter">LCNT User's Guide</seealso></p>
+ <p> <seeguide marker="lcnt_chapter">LCNT User's Guide</seeguide></p>
</section>
</erlref>
diff --git a/lib/tools/doc/src/lcnt_chapter.xml b/lib/tools/doc/src/lcnt_chapter.xml
index 24b58136aa..d0197fdc4b 100644
--- a/lib/tools/doc/src/lcnt_chapter.xml
+++ b/lib/tools/doc/src/lcnt_chapter.xml
@@ -70,7 +70,7 @@ Erlang/OTP 20 [erts-9.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe
<p>To retrieve lock statistics information, use <c>lcnt:collect/0,1</c>. The collect operation will start a <c>lcnt</c> server if it not already started. All collected data will be built into an Erlang term and uploaded to the server and a duration time will also be uploaded. This duration is the time between <c>lcnt:clear/0,1</c> and <c>lcnt:collect/0,1</c>.</p>
<p>Once the data is collected to the server it can be filtered, sorted and printed in many different ways.</p>
- <p>See the <seealso marker="lcnt">reference manual</seealso> for a description of each function.</p>
+ <p>See the <seeerl marker="lcnt">reference manual</seeerl> for a description of each function.</p>
</section>
<section>
<title> Example of usage </title>
@@ -290,6 +290,6 @@ ok
<section>
<title>See Also</title>
- <p> <seealso marker="lcnt">LCNT Reference Manual</seealso></p>
+ <p> <seeerl marker="lcnt">LCNT Reference Manual</seeerl></p>
</section>
</chapter>
diff --git a/lib/tools/doc/src/make.xml b/lib/tools/doc/src/make.xml
index 7f19938c45..4c66fecaf1 100644
--- a/lib/tools/doc/src/make.xml
+++ b/lib/tools/doc/src/make.xml
@@ -148,7 +148,7 @@ Modules.
<section>
<title>See Also</title>
- <p><seealso marker="compiler:compile"><c>compile(3)</c></seealso></p>
+ <p><seeerl marker="compiler:compile"><c>compile(3)</c></seeerl></p>
</section>
</erlref>
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index 01314cf953..47373ca1fe 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -31,6 +31,116 @@
</header>
<p>This document describes the changes made to the Tools application.</p>
+<section><title>Tools 3.4.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p><c>cover</c> would crash when compiling a module
+ having an exported function named <c>clauses</c>.</p>
+ <p>
+ Own Id: OTP-17162 Aux Id: GH-4549, PR-2997, PR-4555,
+ elixir-lang/elixir#10666 </p>
+ </item>
+ <item>
+ <p>If <c>beam_lib</c> is asked to return abstract code
+ for a BEAM file produced by Elixir and Elixir is not
+ installed on the computer, <c>beam_lib</c> will no longer
+ crash, but will return an error tuple. The
+ <c>cover:compile_beam()</c> and
+ <c>cover:compile_beam_directory()</c> functions have been
+ updated to also return an error tuple in that
+ situation.</p>
+ <p>
+ Own Id: OTP-17194 Aux Id: GH-4353 </p>
+ </item>
+ <item>
+ <p>
+ Make emacs mode work on emacs-27.</p>
+ <p>
+ Own Id: OTP-17225 Aux Id: PR-4542, GH-4451 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Tools 3.4.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct the Xref analysis <c>undefined_functions</c> to
+ not report internally generated behaviour_info/1.</p>
+ <p>
+ Own Id: OTP-17191 Aux Id: OTP-16922, ERL-1476, GH-4192 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Tools 3.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct the Xref analysis <c>exports_not_used</c> to not
+ report internally generated <c>behaviour_info/1</c>.</p>
+ <p>
+ Own Id: OTP-16922 Aux Id: PR-2752 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Tools 3.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct the Xref analysis <c>locals_not_used</c> to find
+ functions called exclusively from <c>on_load</c>
+ functions.</p>
+ <p>
+ Own Id: OTP-16854 Aux Id: PR-2750 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Tools 3.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Updates for new <c>erlang:term_to_iovec()</c> BIF.</p>
+ <p>
+ Own Id: OTP-16128 Aux Id: OTP-15618 </p>
+ </item>
+ <item>
+ <p>Improved the presentation of allocations and carriers
+ in the <c>instrument</c> module.</p>
+ <p>
+ Own Id: OTP-16327</p>
+ </item>
+ <item>
+ <p>
+ Minor updates due to the new spawn improvements made.</p>
+ <p>
+ Own Id: OTP-16368 Aux Id: OTP-15251 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 3.3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -56,9 +166,9 @@
</item>
<item>
<p>
- Fixed generated <seealso
+ Fixed generated <seeerl
marker="tools:fprof#analysis">fprof analysis
- format</seealso> to also handle data in maps.</p>
+ format</seeerl> to also handle data in maps.</p>
<p>
Own Id: OTP-16498 Aux Id: ERL-814 </p>
</item>
@@ -720,10 +830,10 @@
Earlier, supervisor flags and child specs were given as
tuples. While this is kept for backwards compatibility,
it is now also allowed to give these parameters as maps,
- see <seealso
- marker="stdlib:supervisor#sup_flags">sup_flags</seealso>
- and <seealso
- marker="stdlib:supervisor#child_spec">child_spec</seealso>.</p>
+ see <seeerl
+ marker="stdlib:supervisor#sup_flags">sup_flags</seeerl>
+ and <seeerl
+ marker="stdlib:supervisor#child_spec">child_spec</seeerl>.</p>
<p>
Own Id: OTP-11043</p>
</item>
diff --git a/lib/tools/doc/src/xref.xml b/lib/tools/doc/src/xref.xml
index 3748655316..72cc5c6c39 100644
--- a/lib/tools/doc/src/xref.xml
+++ b/lib/tools/doc/src/xref.xml
@@ -46,7 +46,7 @@
<em>Module data</em>,
which are extracted from BEAM files, include local functions,
exported functions, local calls and external calls. By default,
- calls to built-in functions (<term id="BIF"></term>) are ignored, but
+ calls to built-in functions (BIF) are ignored, but
if the option <c>builtins</c>, accepted by some of this
module's functions, is set to <c>true</c>, calls to BIFs
are included as well. It is the analyzing OTP version that
@@ -125,7 +125,7 @@ debug information, which is an abstract
is not exported by any analyzed module or library module. With
this notion, a local function can be an undefined function, namely
if it is externally used from some module. All unknown functions
- are also undefined functions; there is a <seealso marker="xref_chapter#venn2">figure</seealso> in the
+ are also undefined functions; there is a <seeguide marker="xref_chapter#venn2">figure</seeguide> in the
User's Guide that illustrates this relationship.
</p>
<p>Starting with R9C, the module attribute tag <c>deprecated</c>
@@ -138,6 +138,9 @@ debug information, which is an abstract
<tag>-deprecated({f,1}).</tag>
<item>The exported function <c>f/1</c> is deprecated. Nothing is
said whether <c>f/1</c> will be removed or not.</item>
+ <tag>-deprecated({f,1,"Use g/1 instead"}).</tag>
+ <item>As above but with a descriptive string. The string is currently
+ unused by <c>xref</c> but other tools can make use of it.</item>
<tag>-deprecated({f,'_'}).</tag>
<item>All exported functions <c>f/0</c>, <c>f/1</c> and so on are
deprecated.</item>
@@ -740,8 +743,8 @@ xref() = atom() | pid() </pre>
<v>Xref = xref()</v>
</type>
<desc>
- <p>Adds an application, the modules of the application and <seealso marker="#module_data">module data</seealso> of the
- modules to an <seealso marker="#xref_server">Xref server</seealso>.
+ <p>Adds an application, the modules of the application and <seeerl marker="#module_data">module data</seeerl> of the
+ modules to an <seeerl marker="#xref_server">Xref server</seeerl>.
The modules will be members of the application.
The default is to use the base name of the
directory with the version removed as application name, but
@@ -753,9 +756,9 @@ xref() = atom() | pid() </pre>
directory, otherwise modules are searched for in the given
directory.
</p>
- <p>If the <seealso marker="#mode">mode</seealso> of the Xref
+ <p>If the <seeerl marker="#mode">mode</seeerl> of the Xref
server is <c>functions</c>, BEAM files that contain no
- <seealso marker="#debug_info">debug information</seealso> are
+ <seeerl marker="#debug_info">debug information</seeerl> are
ignored.
</p>
</desc>
@@ -773,8 +776,8 @@ xref() = atom() | pid() </pre>
<v>Xref = xref()</v>
</type>
<desc>
- <p>Adds the modules found in the given directory and the <seealso marker="#module_data">modules' data</seealso>
- to an <seealso marker="#xref_server">Xref server</seealso>.
+ <p>Adds the modules found in the given directory and the <seeerl marker="#module_data">modules' data</seeerl>
+ to an <seeerl marker="#xref_server">Xref server</seeerl>.
The default is not to examine subdirectories, but if the option
<c>recurse</c> has the value <c>true</c>, modules are searched
for in subdirectories on all levels as well as in the given
@@ -783,9 +786,9 @@ xref() = atom() | pid() </pre>
</p>
<p>The modules added will not be members of any applications.
</p>
- <p>If the <seealso marker="#mode">mode</seealso> of the Xref
+ <p>If the <seeerl marker="#mode">mode</seeerl> of the Xref
server is <c>functions</c>, BEAM files that contain no
- <seealso marker="#debug_info">debug information</seealso> are
+ <seeerl marker="#debug_info">debug information</seeerl> are
ignored.
</p>
</desc>
@@ -802,13 +805,13 @@ xref() = atom() | pid() </pre>
<v>Xref = xref()</v>
</type>
<desc>
- <p>Adds a module and its <seealso marker="#module_data">module data</seealso> to an <seealso marker="#xref_server">Xref server</seealso>.
+ <p>Adds a module and its <seeerl marker="#module_data">module data</seeerl> to an <seeerl marker="#xref_server">Xref server</seeerl>.
The module will not be member of any application.
Returns the name of the module.
</p>
- <p>If the <seealso marker="#mode">mode</seealso> of the Xref
+ <p>If the <seeerl marker="#mode">mode</seeerl> of the Xref
server is <c>functions</c>, and the BEAM file contains no
- <seealso marker="#debug_info">debug information</seealso>,
+ <seeerl marker="#debug_info">debug information</seeerl>,
the error message <c>no_debug_info</c> is returned.
</p>
</desc>
@@ -826,8 +829,8 @@ xref() = atom() | pid() </pre>
</type>
<desc>
<p>Adds a release, the applications of the release, the
- modules of the applications, and <seealso marker="#module_data">module data</seealso> of the
- modules to an <seealso marker="#xref_server">Xref server</seealso>.
+ modules of the applications, and <seeerl marker="#module_data">module data</seeerl> of the
+ modules to an <seeerl marker="#xref_server">Xref server</seeerl>.
The applications will be members of the release,
and the modules will be members of the applications.
The default is to use the base name of the
@@ -841,9 +844,9 @@ xref() = atom() | pid() </pre>
If there are several versions of some application, the one
with the highest version is chosen.
</p>
- <p>If the <seealso marker="#mode">mode</seealso> of the Xref
+ <p>If the <seeerl marker="#mode">mode</seeerl> of the Xref
server is <c>functions</c>, BEAM files that contain no
- <seealso marker="#debug_info">debug information</seealso> are
+ <seeerl marker="#debug_info">debug information</seeerl> are
ignored.
</p>
</desc>
@@ -870,21 +873,23 @@ xref() = atom() | pid() </pre>
Evaluates a predefined analysis.
Returns a sorted list without duplicates of <c>call()</c> or
<c>constant()</c>, depending on the chosen analysis. The
- predefined analyses, which operate on all <seealso marker="#analyzed_module">analyzed modules</seealso>, are
- (analyses marked with (*) are available in <c>functions</c><seealso marker="#mode">mode</seealso> only):</p>
+ predefined analyses, which operate on all <seeerl marker="#analyzed_module">analyzed modules</seeerl>, are
+ (analyses marked with (*) are available in <c>functions</c><seeerl marker="#mode">mode</seeerl> only):</p>
<taglist>
<tag><c>undefined_function_calls</c>(*)</tag>
- <item>Returns a list of calls to <seealso marker="#undefined_function">undefined functions</seealso>.</item>
+ <item>Returns a list of calls to <seeerl marker="#undefined_function">undefined functions</seeerl>.</item>
<tag><c>undefined_functions</c></tag>
- <item>Returns a list of <seealso marker="#undefined_function">undefined functions</seealso>. </item>
+ <item>Returns a list of <seeerl marker="#undefined_function">undefined functions</seeerl>. </item>
<tag><c>locals_not_used</c>(*)</tag>
<item>Returns a list of local functions that have not been
locally used.</item>
<tag><c>exports_not_used</c></tag>
<item>Returns a list of exported functions that have not been
- externally used.</item>
+ externally used. Note that in <c>modules</c> mode,
+ <c>M:behaviour_info/1</c> is never reported as unused.
+ </item>
<tag><c>deprecated_function_calls</c>(*)</tag>
- <item>Returns a list of external calls to <seealso marker="#deprecated_function">deprecated functions</seealso>.</item>
+ <item>Returns a list of external calls to <seeerl marker="#deprecated_function">deprecated functions</seeerl>.</item>
<tag><c>{deprecated_function_calls, DeprFlag}</c>(*)</tag>
<item>Returns a list of external calls to deprecated
functions. If <c>DeprFlag</c> is equal to
@@ -950,11 +955,11 @@ Evaluates a predefined analysis.
</type>
<desc>
<p>The modules found in the given directory are checked for
- calls to <seealso marker="#deprecated_function">deprecated functions</seealso>, calls to <seealso marker="#undefined_function">undefined functions</seealso>,
+ calls to <seeerl marker="#deprecated_function">deprecated functions</seeerl>, calls to <seeerl marker="#undefined_function">undefined functions</seeerl>,
and for unused local functions. The code path is used as
- <seealso marker="#library_path">library path</seealso>.
+ <seeerl marker="#library_path">library path</seeerl>.
</p>
- <p>If some of the found BEAM files contain <seealso marker="#debug_info">debug information</seealso>, then those
+ <p>If some of the found BEAM files contain <seeerl marker="#debug_info">debug information</seeerl>, then those
modules are checked and a list of tuples is returned. The
first element of each tuple is one of:
</p>
@@ -990,7 +995,7 @@ Evaluates a predefined analysis.
</type>
<desc>
<p><c>forget/1</c> and <c>forget/2</c> remove all or some of
- the <seealso marker="#user_variable">user variables</seealso> of an <seealso marker="#xref_server">xref server</seealso>.</p>
+ the <seeerl marker="#user_variable">user variables</seeerl> of an <seeerl marker="#xref_server">xref server</seeerl>.</p>
</desc>
</func>
<func>
@@ -1030,7 +1035,7 @@ Evaluates a predefined analysis.
<v>Xref = xref()</v>
</type>
<desc>
- <p>Returns the <seealso marker="#library_path">library path</seealso>.</p>
+ <p>Returns the <seeerl marker="#library_path">library path</seeerl>.</p>
</desc>
</func>
<func>
@@ -1052,27 +1057,27 @@ Evaluates a predefined analysis.
<desc>
<p>The <c>info</c> functions return information as a list of
pairs {Tag,&nbsp;term()} in some order about the state and the
- <seealso marker="#module_data">module data</seealso> of an <seealso marker="#xref_server">Xref server</seealso>.
+ <seeerl marker="#module_data">module data</seeerl> of an <seeerl marker="#xref_server">Xref server</seeerl>.
</p>
<p><c>info/1</c> returns information with the following tags
(tags marked with (*) are available in <c>functions</c>
mode only):</p>
<list type="bulleted">
- <item><c>library_path</c>, the <seealso marker="#library_path">library path</seealso>;</item>
- <item><c>mode</c>, the <seealso marker="#mode">mode</seealso>;</item>
+ <item><c>library_path</c>, the <seeerl marker="#library_path">library path</seeerl>;</item>
+ <item><c>mode</c>, the <seeerl marker="#mode">mode</seeerl>;</item>
<item><c>no_releases</c>, number of releases;</item>
<item><c>no_applications</c>, total number of applications
(of all releases);</item>
- <item><c>no_analyzed_modules</c>, total number of <seealso marker="#analyzed_module">analyzed modules</seealso>;</item>
+ <item><c>no_analyzed_modules</c>, total number of <seeerl marker="#analyzed_module">analyzed modules</seeerl>;</item>
<item><c>no_calls</c> (*), total number of calls (in all
modules), regarding instances of one function call in
different lines as separate calls;</item>
- <item><c>no_function_calls</c> (*), total number of <seealso marker="#local_call">local calls</seealso>, resolved <seealso marker="#external_call">external calls</seealso> and
- <seealso marker="#unresolved_call">unresolved calls</seealso>;</item>
+ <item><c>no_function_calls</c> (*), total number of <seeerl marker="#local_call">local calls</seeerl>, resolved <seeerl marker="#external_call">external calls</seeerl> and
+ <seeerl marker="#unresolved_call">unresolved calls</seeerl>;</item>
<item><c>no_functions</c> (*), total number of local and exported
functions;</item>
<item><c>no_inter_function_calls</c> (*), total number of
- calls of the <seealso marker="#inter_call_graph">Inter Call Graph</seealso>.</item>
+ calls of the <seeerl marker="#inter_call_graph">Inter Call Graph</seeerl>.</item>
</list>
<p><c>info/2</c> and <c>info/3</c> return information about
all or some of the analyzed modules, applications, releases
@@ -1141,7 +1146,7 @@ Evaluates a predefined analysis.
</list>
<p>The following information is returned for every library module:</p>
<list type="bulleted">
- <item><c>directory</c>, the directory where the <seealso marker="#library_module">library module's</seealso> BEAM file is located.</item>
+ <item><c>directory</c>, the directory where the <seeerl marker="#library_module">library module's</seeerl> BEAM file is located.</item>
</list>
<p>For every number of calls, functions etc. returned by the
<c>no_</c> tags, there is a query returning the same number.
@@ -1234,11 +1239,11 @@ Evaluates a predefined analysis.
<desc>
<p>The given BEAM file (with or without the <c>.beam</c>
extension) or the file found by calling
- <c>code:which(Module)</c> is checked for calls to <seealso marker="#deprecated_function">deprecated functions</seealso>, calls to <seealso marker="#undefined_function">undefined functions</seealso>,
+ <c>code:which(Module)</c> is checked for calls to <seeerl marker="#deprecated_function">deprecated functions</seeerl>, calls to <seeerl marker="#undefined_function">undefined functions</seeerl>,
and for unused local functions. The code path is used as
- <seealso marker="#library_path">library path</seealso>.
+ <seeerl marker="#library_path">library path</seeerl>.
</p>
- <p>If the BEAM file contains <seealso marker="#debug_info">debug information</seealso>, then a
+ <p>If the BEAM file contains <seeerl marker="#debug_info">debug information</seeerl>, then a
list of tuples is returned. The first element of each tuple
is one of:
</p>
@@ -1283,8 +1288,8 @@ Evaluates a predefined analysis.
<v>Xref = xref()</v>
</type>
<desc>
- <p>Evaluates a <seealso marker="#query">query</seealso> in the
- context of an <seealso marker="#xref_server">Xref server</seealso>, and returns the value of the last
+ <p>Evaluates a <seeerl marker="#query">query</seeerl> in the
+ context of an <seeerl marker="#xref_server">Xref server</seeerl>, and returns the value of the last
statement. The syntax of the value depends on the
expression:
</p>
@@ -1331,7 +1336,7 @@ Evaluates a predefined analysis.
<v>Xref = xref()</v>
</type>
<desc>
- <p>Removes applications and their modules and <seealso marker="#module_data">module data</seealso> from an <seealso marker="#xref_server">Xref server</seealso>.</p>
+ <p>Removes applications and their modules and <seeerl marker="#module_data">module data</seeerl> from an <seeerl marker="#xref_server">Xref server</seeerl>.</p>
</desc>
</func>
<func>
@@ -1344,7 +1349,7 @@ Evaluates a predefined analysis.
<v>Xref = xref()</v>
</type>
<desc>
- <p>Removes <seealso marker="#analyzed_module">analyzed modules</seealso> and <seealso marker="#module_data">module data</seealso> from an <seealso marker="#xref_server">Xref server</seealso>.</p>
+ <p>Removes <seeerl marker="#analyzed_module">analyzed modules</seeerl> and <seeerl marker="#module_data">module data</seeerl> from an <seeerl marker="#xref_server">Xref server</seeerl>.</p>
</desc>
</func>
<func>
@@ -1358,8 +1363,8 @@ Evaluates a predefined analysis.
</type>
<desc>
<p>Removes releases and their applications, modules and
- <seealso marker="#module_data">module data</seealso> from an
- <seealso marker="#xref_server">Xref server</seealso>.</p>
+ <seeerl marker="#module_data">module data</seeerl> from an
+ <seeerl marker="#xref_server">Xref server</seeerl>.</p>
</desc>
</func>
<func>
@@ -1397,7 +1402,7 @@ Evaluates a predefined analysis.
<v>Xref = xref()</v>
</type>
<desc>
- <p>Replaces <seealso marker="#module_data">module data</seealso> of an <seealso marker="#analyzed_module">analyzed module</seealso> with
+ <p>Replaces <seeerl marker="#module_data">module data</seeerl> of an <seeerl marker="#analyzed_module">analyzed module</seeerl> with
data read from a BEAM file. Application membership of the
module is retained, and so is the value of the
<c>builtins</c> option of the module. An error is returned
@@ -1430,7 +1435,7 @@ Evaluates a predefined analysis.
<item><c>verbose</c>, with initial default value <c>false</c>;</item>
<item><c>warnings</c>, with initial default value <c>true</c>.</item>
</list>
- <p>The initial default values are set when creating an <seealso marker="#xref_server">Xref server</seealso>.
+ <p>The initial default values are set when creating an <seeerl marker="#xref_server">Xref server</seeerl>.
</p>
</desc>
</func>
@@ -1446,8 +1451,8 @@ Evaluates a predefined analysis.
<v>Xref = xref()</v>
</type>
<desc>
- <p>Sets the <seealso marker="#library_path">library path</seealso>. If the given path is a list of
- directories, the set of <seealso marker="#library_module">library modules</seealso> is
+ <p>Sets the <seeerl marker="#library_path">library path</seeerl>. If the given path is a list of
+ directories, the set of <seeerl marker="#library_module">library modules</seeerl> is
determined by choosing the first module
encountered while traversing the directories in
the given order, for those modules that occur in more than
@@ -1458,7 +1463,7 @@ Evaluates a predefined analysis.
used by the functions
<c>m/1</c> and <c>d/1</c>, but can also be set explicitly.
Note however that the code path will be traversed once for
- each used <seealso marker="#library_module">library module</seealso> while setting up module data.
+ each used <seeerl marker="#library_module">library module</seeerl> while setting up module data.
On the other hand, if there are only a few modules that are
used but not analyzed, using <c>code_path</c> may be faster
than setting the library path to <c>code:get_path()</c>.
@@ -1479,9 +1484,9 @@ Evaluates a predefined analysis.
<v>Return = {ok, pid()} | {error, {already_started, pid()}}</v>
</type>
<desc>
- <p>Creates an <seealso marker="#xref_server">Xref server</seealso>.
+ <p>Creates an <seeerl marker="#xref_server">Xref server</seeerl>.
The process may optionally be given a name.
- The default <seealso marker="#mode">mode</seealso> is <c>functions</c>.
+ The default <seeerl marker="#mode">mode</seeerl> is <c>functions</c>.
Options that are not recognized by Xref
are passed on to <c>gen_server:start/4</c>.</p>
</desc>
@@ -1496,9 +1501,9 @@ Evaluates a predefined analysis.
<v>Return = {ok, pid()} | {error, {already_started, pid()}}</v>
</type>
<desc>
- <p>Creates an <seealso marker="#xref_server">Xref server</seealso>
+ <p>Creates an <seeerl marker="#xref_server">Xref server</seeerl>
with a given name.
- The default <seealso marker="#mode">mode</seealso> is <c>functions</c>.
+ The default <seeerl marker="#mode">mode</seeerl> is <c>functions</c>.
Options that are not recognized by Xref
are passed on to <c>gen_server:start/4</c>.</p>
</desc>
@@ -1510,7 +1515,7 @@ Evaluates a predefined analysis.
<v>Xref = xref()</v>
</type>
<desc>
- <p>Stops an <seealso marker="#xref_server">Xref server</seealso>.</p>
+ <p>Stops an <seeerl marker="#xref_server">Xref server</seeerl>.</p>
</desc>
</func>
<func>
@@ -1525,7 +1530,7 @@ Evaluates a predefined analysis.
<v>Xref = xref()</v>
</type>
<desc>
- <p>Replaces the <seealso marker="#module_data">module data</seealso> of all <seealso marker="#analyzed_module">analyzed modules</seealso> the BEAM
+ <p>Replaces the <seeerl marker="#module_data">module data</seeerl> of all <seeerl marker="#analyzed_module">analyzed modules</seeerl> the BEAM
files of which have been modified since last read by an
<c>add</c> function or <c>update</c>. Application membership
of the modules is retained, and so is the value of the
@@ -1545,19 +1550,19 @@ Evaluates a predefined analysis.
</type>
<desc>
<p>Returns a sorted lists of the names of the variables of an
- <seealso marker="#xref_server">Xref server</seealso>.
- The default is to return the <seealso marker="#user_variable">user variables</seealso> only.</p>
+ <seeerl marker="#xref_server">Xref server</seeerl>.
+ The default is to return the <seeerl marker="#user_variable">user variables</seeerl> only.</p>
</desc>
</func>
</funcs>
<section>
<title>See Also</title><p>
- <seealso marker="stdlib:beam_lib">beam_lib(3)</seealso>,
- <seealso marker="stdlib:digraph">digraph(3)</seealso>,
- <seealso marker="stdlib:digraph_utils">digraph_utils(3)</seealso>,
- <seealso marker="stdlib:re">re(3)</seealso>,
- <seealso marker="xref_chapter">TOOLS User's Guide</seealso></p>
+ <seeerl marker="stdlib:beam_lib">beam_lib(3)</seeerl>,
+ <seeerl marker="stdlib:digraph">digraph(3)</seeerl>,
+ <seeerl marker="stdlib:digraph_utils">digraph_utils(3)</seeerl>,
+ <seeerl marker="stdlib:re">re(3)</seeerl>,
+ <seeguide marker="xref_chapter">TOOLS User's Guide</seeguide></p>
</section>
</erlref>
diff --git a/lib/tools/doc/src/xref_chapter.xml b/lib/tools/doc/src/xref_chapter.xml
index 872793bdcb..d6d97150d9 100644
--- a/lib/tools/doc/src/xref_chapter.xml
+++ b/lib/tools/doc/src/xref_chapter.xml
@@ -49,7 +49,7 @@
with a module check and a predefined analysis. Then follow
examples that can be skipped on the first reading; not all of
the concepts used are explained, and it is assumed that the
- <seealso marker="xref">reference manual</seealso> has been at
+ <seeerl marker="xref">reference manual</seeerl> has been at
least skimmed.
</p>
@@ -78,7 +78,7 @@
contains debug information, which makes it possible to find
unused local functions.
</p>
- <p>The module can now be checked for calls to <seealso marker="xref#deprecated_function">deprecated functions</seealso>, calls to <seealso marker="xref#undefined_function">undefined functions</seealso>,
+ <p>The module can now be checked for calls to <seeerl marker="xref#deprecated_function">deprecated functions</seeerl>, calls to <seeerl marker="xref#undefined_function">undefined functions</seeerl>,
and for unused local functions:
</p>
<pre>
@@ -92,7 +92,7 @@
either case, the code path of the code server (see the module
<c>code</c>) is used for finding modules that export externally
called functions not exported by the checked module itself, so
- called <seealso marker="xref#library_module">library modules</seealso>.
+ called <seeerl marker="xref#library_module">library modules</seeerl>.
</p>
</section>
@@ -100,7 +100,7 @@
<title>Predefined Analysis</title>
<p>In the last example the module to analyze was given as an
argument to <c>m/1</c>, and the code path was (implicitly)
- used as <seealso marker="xref#library_path">library path</seealso>. In this example an <seealso marker="xref#xref_server">xref server</seealso> will be used,
+ used as <seeerl marker="xref#library_path">library path</seeerl>. In this example an <seeerl marker="xref#xref_server">xref server</seeerl> will be used,
which makes it possible to analyze applications and releases,
and also to select the library path explicitly.
</p>
@@ -115,7 +115,7 @@
Otherwise, when analyzing a system that uses OTP, the OTP
modules are typically made library modules by
setting the library path to the default OTP code path (or to
- <c>code_path</c>, see the <seealso marker="xref#code_path">reference manual</seealso>). By
+ <c>code_path</c>, see the <seeerl marker="xref#code_path">reference manual</seeerl>). By
default, the names of read BEAM files and warnings are output
when adding analyzed modules, but these messages can be avoided
by setting default values of some options:
@@ -145,12 +145,12 @@
predefined analysis, probably the most useful one. Other
examples are the analyses that find unused local
functions, or functions that call some given functions. See
- the <seealso marker="xref#analyze">analyze/2,3</seealso>
+ the <seeerl marker="xref#analyze">analyze/2,3</seeerl>
functions for a complete list of predefined analyses.
</p>
- <p>Each predefined analysis is a shorthand for a <seealso marker="xref#query">query</seealso>, a sentence of a tiny
+ <p>Each predefined analysis is a shorthand for a <seeerl marker="xref#query">query</seeerl>, a sentence of a tiny
language providing cross reference data as
- values of <seealso marker="xref#predefined_variable">predefined variables</seealso>.
+ values of <seeerl marker="xref#predefined_variable">predefined variables</seeerl>.
The check for calls to undefined functions can thus be stated as
a query:
</p>
@@ -173,7 +173,7 @@
<c>X&nbsp;+&nbsp;L&nbsp;+&nbsp;B&nbsp;+&nbsp;U</c>, and one
that focuses on how they are used:
<c>UU&nbsp;+&nbsp;LU&nbsp;+&nbsp;XU</c>.
- The reference also mentions some <seealso marker="xref#simple_facts">facts</seealso> about the
+ The reference also mentions some <seeerl marker="xref#simple_facts">facts</seeerl> about the
variables:
</p>
<list type="bulleted">
@@ -296,7 +296,7 @@
<section>
<title>Graph Analysis</title>
- <p>The list <seealso marker="xref#representation">representation of graphs</seealso> is used analyzing direct calls,
+ <p>The list <seeerl marker="xref#representation">representation of graphs</seeerl> is used analyzing direct calls,
while the <c>digraph</c> representation is suited for analyzing
indirect calls. The restriction operators (<c>|</c>, <c>||</c>
and <c>|||</c>) are the only operators that accept both
@@ -308,12 +308,12 @@
<p>As an example of analyzing indirect calls, the following Erlang
function tries to answer the question:
if we want to know which modules are used indirectly by some
- module(s), is it worth while using the <seealso marker="xref#call_graph">function graph</seealso> rather
+ module(s), is it worth while using the <seeerl marker="xref#call_graph">function graph</seeerl> rather
than the module graph? Recall that a module M1 is said to call
a module M2 if there is some function in M1 that calls some
function in M2. It would be nice if we could use the much
smaller module graph, since it is available also in the light
- weight <c>modules</c><seealso marker="xref#mode">mode</seealso> of Xref servers.
+ weight <c>modules</c><seeerl marker="xref#mode">mode</seeerl> of Xref servers.
</p>
<code type="erl">
t(S) ->
diff --git a/lib/tools/emacs/Makefile b/lib/tools/emacs/Makefile
index b7775d1c8c..c19ad3668c 100644
--- a/lib/tools/emacs/Makefile
+++ b/lib/tools/emacs/Makefile
@@ -65,7 +65,9 @@ clean:
rm -f $(TARGET_FILES) $(ELC_FILES)
rm -f errs core *~
-docs:
+DOC_TARGETS?=man
+
+docs: $(DOC_TARGETS)
# ----------------------------------------------------
# Release Target
@@ -77,14 +79,8 @@ release_spec: opt
$(INSTALL_DATA) $(EL_FILES) $(README_FILES) \
"$(RELSYSDIR)/emacs"
-ifeq ($(DOCTYPE),pdf)
-release_docs_spec:
-else
-ifeq ($(DOCTYPE),ps)
-release_docs_spec:
-else
-release_docs_spec: docs
+release_man_spec:
$(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
$(INSTALL_DATA) $(MAN_FILES) "$(RELEASE_PATH)/man/man3"
-endif
-endif
+
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
diff --git a/lib/tools/emacs/erlang-eunit.el b/lib/tools/emacs/erlang-eunit.el
index 73c25246df..6212553611 100644
--- a/lib/tools/emacs/erlang-eunit.el
+++ b/lib/tools/emacs/erlang-eunit.el
@@ -216,7 +216,7 @@ buffer and vice versa"
With prefix arg, compiles for debug and runs tests with the verbose flag set."
(interactive)
- (case (erlang-eunit-recent 'mode)
+ (cl-case (erlang-eunit-recent 'mode)
('test-mode
(erlang-eunit-compile-and-test
'erlang-eunit-run-test (list (erlang-eunit-recent 'module)
diff --git a/lib/tools/emacs/erlang-test.el b/lib/tools/emacs/erlang-test.el
index c1c0cd7222..3dab5e5525 100644
--- a/lib/tools/emacs/erlang-test.el
+++ b/lib/tools/emacs/erlang-test.el
@@ -62,8 +62,6 @@
;;; Code:
-(eval-when-compile
- (require 'cl))
(require 'ert)
(require 'erlang)
@@ -127,8 +125,8 @@ concatenated to form an erlang file to test on.")
(defun erlang-test-create-erlang-file (erlang-file)
(with-temp-file erlang-file
- (loop for (_ . code) in erlang-test-code
- do (insert code "\n"))))
+ (cl-loop for (_ . code) in erlang-test-code
+ do (insert code "\n"))))
(defun erlang-test-compile-tags (erlang-file tags-file)
(should (zerop (call-process "etags" nil nil nil
@@ -143,20 +141,20 @@ concatenated to form an erlang file to test on.")
(sort (erlang-expected-completion-table) #'string-lessp))))
(defun erlang-expected-completion-table ()
- (append (loop for (symbol . _) in erlang-test-code
- when (stringp symbol)
- append (list symbol (concat "erlang_test:" symbol)))
+ (append (cl-loop for (symbol . _) in erlang-test-code
+ when (stringp symbol)
+ append (list symbol (concat "erlang_test:" symbol)))
(list "erlang_test:" "erlang_test:module_info")))
(defun erlang-test-xref-find-definitions (erlang-file erlang-buffer)
- (loop for (tagname . code) in erlang-test-code
- for line = 1 then (1+ line)
- do (when tagname
- (switch-to-buffer erlang-buffer)
- (erlang-test-xref-jump tagname erlang-file line)
- (when (string-equal tagname "function")
- (erlang-test-xref-jump (concat "erlang_test:" tagname)
- erlang-file line))))
+ (cl-loop for (tagname . code) in erlang-test-code
+ for line = 1 then (1+ line)
+ do (when tagname
+ (switch-to-buffer erlang-buffer)
+ (erlang-test-xref-jump tagname erlang-file line)
+ (when (string-equal tagname "function")
+ (erlang-test-xref-jump (concat "erlang_test:" tagname)
+ erlang-file line))))
(erlang-test-xref-jump "erlang_test:" erlang-file 1))
(defun erlang-test-xref-jump (id expected-file expected-line)
@@ -225,27 +223,27 @@ concatenated to form an erlang file to test on.")
(ert-deftest erlang-test-parse-id ()
- (loop for id-string in '("fun/10"
- "qualified-function module:fun/10"
- "record reko"
- "macro _SYMBOL"
- "macro MACRO/10"
- "module modula"
- "macro"
- nil)
- for id-list in '((nil nil "fun" 10)
- (qualified-function "module" "fun" 10)
- (record nil "reko" nil)
- (macro nil "_SYMBOL" nil)
- (macro nil "MACRO" 10)
- (module nil "modula" nil)
- (nil nil "macro" nil)
- nil)
- for id-list2 = (erlang-id-to-list id-string)
- do (should (equal id-list id-list2))
- for id-string2 = (erlang-id-to-string id-list)
- do (should (equal id-string id-string2))
- collect id-list2))
+ (cl-loop for id-string in '("fun/10"
+ "qualified-function module:fun/10"
+ "record reko"
+ "macro _SYMBOL"
+ "macro MACRO/10"
+ "module modula"
+ "macro"
+ nil)
+ for id-list in '((nil nil "fun" 10)
+ (qualified-function "module" "fun" 10)
+ (record nil "reko" nil)
+ (macro nil "_SYMBOL" nil)
+ (macro nil "MACRO" 10)
+ (module nil "modula" nil)
+ (nil nil "macro" nil)
+ nil)
+ for id-list2 = (erlang-id-to-list id-string)
+ do (should (equal id-list id-list2))
+ for id-string2 = (erlang-id-to-string id-list)
+ do (should (equal id-string id-string2))
+ collect id-list2))
(provide 'erlang-test)
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index d02b4b9fe1..de5dd4955f 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -76,11 +76,15 @@
;; M-x toggle-debug-on-error RET
;;; Code:
-(eval-when-compile (require 'cl))
(require 'align)
(require 'comint)
(require 'tempo)
+;;; `caddr' is builtin since Emacs 26.
+(eval-and-compile
+ (or (fboundp 'caddr)
+ (defun caddr (x) (car (cdr (cdr x))))))
+
;; Variables:
(defgroup erlang nil
@@ -883,9 +887,12 @@ resulting regexp is surrounded by \\_< and \\_>."
"spawn_link"
"spawn_monitor"
"spawn_opt"
+ "spawn_request"
+ "spawn_request_abandon"
"split_binary"
"statistics"
"term_to_binary"
+ "term_to_iovec"
"time"
"throw"
"tl"
@@ -912,7 +919,6 @@ resulting regexp is surrounded by \\_< and \\_>."
"bump_reductions"
"call_on_load_function"
"cancel_timer"
- "crasher"
"crc32"
"crc32_combine"
"decode_packet"
@@ -994,7 +1000,6 @@ resulting regexp is surrounded by \\_< and \\_>."
"set_cookie"
"set_cpu_topology"
"setnode"
- "spawn_opt"
"start_timer"
"subtract"
"suspend_process"
@@ -1050,7 +1055,7 @@ behaviour.")
(define-key map "<" 'erlang-electric-lt)
(define-key map ">" 'erlang-electric-gt)
(define-key map "\C-m" 'erlang-electric-newline)
- (define-key map [(backspace)] 'backward-delete-char-untabify)
+ (define-key map (kbd "DEL") 'backward-delete-char-untabify)
(define-key map "\M-q" 'erlang-fill-paragraph)
(define-key map "\M-\t" 'erlang-complete-tag)
(define-key map "\C-c\M-\t" 'tempo-complete-tag)
@@ -1237,8 +1242,8 @@ This must be placed in front of `erlang-font-lock-keywords-vars'.")
1 'font-lock-type-face)
;; Don't highlight numerical constants.
(list (if erlang-regexp-modern-p
- "\\_<[0-9]+#\\([0-9a-zA-Z]+\\)"
- "\\<[0-9]+#\\([0-9a-zA-Z]+\\)")
+ "\\_<\\([0-9]+\\(_[0-9]+\\)*#[0-9a-zA-Z]+\\(_[0-9a-zA-Z]+\\)*\\)"
+ "\\<\\([0-9]+\\(_[0-9]+\\)*#[0-9a-zA-Z]+\\(_[0-9a-zA-Z]+\\)*\\)")
1 nil t)
(list (concat "^-record\\s-*(\\s-*" erlang-atom-regexp)
1 'font-lock-type-face))
@@ -1419,7 +1424,7 @@ Other commands:
(erlang-electric-init)
(erlang-menu-init)
(erlang-mode-variables)
- (erlang-check-module-name-init)
+ (add-hook 'before-save-hook 'erlang-check-module-name nil t)
(erlang-man-init)
(erlang-tags-init)
(erlang-font-lock-init)
@@ -1430,7 +1435,6 @@ Other commands:
(setq-local eldoc-documentation-function #'ignore))
(add-function :before-until (local 'eldoc-documentation-function)
#'erldoc-eldoc-function))
- (run-hooks 'erlang-mode-hook)
;; Align maps.
(add-to-list 'align-rules-list
@@ -1946,7 +1950,6 @@ The format is described in the documentation of `erlang-man-dirs'."
'(("Man Pages"
(("Error! Why?" erlang-man-describe-error)))))))
-
(defcustom erlang-man-download-url "http://erlang.org/download/otp_doc_man_22.1.tar.gz"
"The URL from which the erlang-man-download function will
download Erlang man pages "
@@ -2674,8 +2677,6 @@ This is automagically called by the user level function `indent-region'."
(defmacro erlang-push (x stack) (list 'setq stack (list 'cons x stack)))
(defmacro erlang-pop (stack) (list 'setq stack (list 'cdr stack)))
-;; Would much prefer to make caddr a macro but this clashes.
-(defun erlang-caddr (x) (car (cdr (cdr x))))
(defun erlang-calculate-indent (&optional parse-start)
@@ -3076,8 +3077,8 @@ Return nil if inside string, t if in a comment."
(if (eq (car stack-top) '->)
(erlang-pop stack))
(cond ((and stack (looking-at ";"))
- (+ (erlang-caddr (car stack)) (- erlang-indent-level 2)))
- (stack (erlang-caddr (car stack)))
+ (+ (caddr (car stack)) (- erlang-indent-level 2)))
+ (stack (caddr (car stack)))
(t off)))
((looking-at "catch\\b\\($\\|[^_a-zA-Z0-9]\\)")
;; Are we in a try
@@ -3091,12 +3092,12 @@ Return nil if inside string, t if in a comment."
(if (eq (car stack-top) '->)
(erlang-pop stack))
(if stack
- (erlang-caddr (car stack))
+ (caddr (car stack))
0)))
(t (erlang-indent-standard indent-point token base 'nil))))) ;; old catch
;; Indent result types
((eq (car (car (cdr stack))) 'spec_arg)
- (setq base (+ (erlang-caddr (car (last stack))) erlang-indent-level))
+ (setq base (+ (caddr (car (last stack))) erlang-indent-level))
(erlang-indent-standard indent-point token base 'nil))
(t
(erlang-indent-standard indent-point token base 'nil)
@@ -3224,7 +3225,7 @@ Return nil if inside string, t if in a comment."
;; Take parent identation + offset,
;; else just erlang-indent-level if no parent
(if stack
- (+ (erlang-caddr (car stack))
+ (+ (caddr (car stack))
offset)
erlang-indent-level))
(erlang-skip-blank indent-point)
@@ -4084,11 +4085,11 @@ of arguments could be found, otherwise nil."
(defun erlang-match-next-exported-function (max)
"Returns non-nil if there is an exported function in the current
buffer between point and MAX."
- (block nil
- (while (and (not erlang-inhibit-exported-function-name-face)
- (erlang-match-next-function max))
- (when (erlang-last-match-exported-p)
- (return (match-data))))))
+ (catch 'return
+ (while (and (not erlang-inhibit-exported-function-name-face)
+ (erlang-match-next-function max))
+ (when (erlang-last-match-exported-p)
+ (throw 'return (match-data))))))
(defun erlang-match-next-function (max)
"Searches forward in current buffer for the next erlang function,
@@ -4116,28 +4117,6 @@ exported function."
;;; Check module name
-;; The function `write-file', bound to C-x C-w, calls
-;; `set-visited-file-name' which clears the hook. :-(
-;; To make sure that the hook always is present, we advise
-;; `set-visited-file-name'.
-(defun erlang-check-module-name-init ()
- "Initialize the functionality to compare file and module names.
-
-Unless we have `before-save-hook', we advice the function
-`set-visited-file-name' since it clears the variable
-`local-write-file-hooks'."
- (if (boundp 'before-save-hook)
- (add-hook 'before-save-hook 'erlang-check-module-name nil t)
- (require 'advice)
- (when (fboundp 'ad-advised-definition-p)
- (unless (ad-advised-definition-p 'set-visited-file-name)
- (defadvice set-visited-file-name (after erlang-set-visited-file-name
- activate)
- (if (eq major-mode 'erlang-mode)
- (add-hook 'local-write-file-hooks 'erlang-check-module-name))))
- (add-hook 'local-write-file-hooks 'erlang-check-module-name))))
-
-
(defun erlang-check-module-name ()
"If the module name doesn't match file name, ask for permission to change.
@@ -4146,7 +4125,7 @@ function. It it is nil, this function does nothing. If it is t, the
source is silently changed. If it is set to the atom `ask', the user
is prompted.
-This function is normally placed in the hook `local-write-file-hooks'."
+This function is normally placed in the hook `before-save-hook'."
(if erlang-check-module-name
(let ((mn (erlang-add-quotes-if-needed
(erlang-get-module)))
@@ -5358,7 +5337,7 @@ is non-nil then TAG is a regexp."
(cl-loop for xref in xrefs
for loc = (xref-item-location xref)
for file = (xref-location-group loc)
- do (pushnew file files :test 'string-equal))
+ do (cl-pushnew file files :test 'string-equal))
(or (cl-loop for file in files
append (erlang-xrefs-in-file file kind tag is-regexp))
;; Failed for some reason. Pretend like it is raining and
diff --git a/lib/tools/emacs/erldoc.el b/lib/tools/emacs/erldoc.el
index a8ac81ecb3..51fc67c513 100644
--- a/lib/tools/emacs/erldoc.el
+++ b/lib/tools/emacs/erldoc.el
@@ -60,9 +60,10 @@
;;; Code:
-(eval-when-compile (require 'url-parse))
(require 'cl-lib)
+(require 'json)
(require 'erlang)
+(eval-when-compile (require 'url-parse))
(eval-and-compile ;for emacs < 24.3
(or (fboundp 'user-error) (defalias 'user-error 'error)))
@@ -268,7 +269,6 @@ up the indexing."
(with-temp-buffer
(if (not json)
(pp table (current-buffer))
- (eval-and-compile (require 'json))
(let ((json-encoding-pretty-print t))
(insert (json-encode table))))
(unless (file-directory-p (file-name-directory output))
diff --git a/lib/tools/emacs/internal_doc/emacs.sgml b/lib/tools/emacs/internal_doc/emacs.sgml
index eb6c3b7bb4..802f70fbea 100644
--- a/lib/tools/emacs/internal_doc/emacs.sgml
+++ b/lib/tools/emacs/internal_doc/emacs.sgml
@@ -349,7 +349,7 @@ used is based on the common use of the language.
</P>
<P>
-It is strongly recommend to use this feature and avoid to indent lines
+It is strongly recommended to use this feature and avoid to indent lines
in a nonstandard way. Some motivations are:
</P>
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl
index 9f86a68942..28cbedddce 100644
--- a/lib/tools/src/cover.erl
+++ b/lib/tools/src/cover.erl
@@ -1552,14 +1552,12 @@ do_compile2(File, UserOptions, LocalOnly) ->
do_compile_beam1(Module,Beam,UserOptions,LocalOnly) ->
%% Clear database
do_clear(Module),
-
+
%% Extract the abstract format.
case get_abstract_code(Module, Beam) of
- no_abstract_code=E ->
- {error,E};
- encrypted_abstract_code=E ->
- {error,E};
- {raw_abstract_v1,Code} ->
+ {error,_}=Error ->
+ Error;
+ {ok,{raw_abstract_v1,Code}} ->
Forms0 = epp:interpret_file_attribute(Code),
case find_main_filename(Forms0) of
{ok,MainFile} ->
@@ -1568,7 +1566,7 @@ do_compile_beam1(Module,Beam,UserOptions,LocalOnly) ->
Error ->
Error
end;
- {_VSN,_Code} ->
+ {ok,{_VSN,_Code}} ->
%% Wrong version of abstract code. Just report that there
%% is no abstract code.
{error,no_abstract_code}
@@ -1577,10 +1575,14 @@ do_compile_beam1(Module,Beam,UserOptions,LocalOnly) ->
get_abstract_code(Module, Beam) ->
case beam_lib:chunks(Beam, [abstract_code]) of
{ok, {Module, [{abstract_code, AbstractCode}]}} ->
- AbstractCode;
+ case AbstractCode of
+ no_abstract_code=E -> {error, E};
+ _ -> {ok,AbstractCode}
+ end;
{error,beam_lib,{key_missing_or_invalid,_,_}} ->
- encrypted_abstract_code;
- Error -> Error
+ {error,encrypted_abstract_code};
+ {error,beam_lib,{missing_backend,_,Backend}} ->
+ {error,{missing_backend,Backend}}
end.
do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile,LocalOnly) ->
@@ -2179,6 +2181,8 @@ patch_code1({'BUMP',_Line,Index}, {local_only,AbstrCref}) ->
[AbstrCref,{integer,A,Index},{integer,A,1}]};
patch_code1({clauses,Cs}, Key) ->
{clauses,[patch_code1(El, Key) || El <- Cs]};
+patch_code1({attribute, _, _, _} = Attribute, _Key) ->
+ Attribute;
patch_code1([_|_]=List, Key) ->
[patch_code1(El, Key) || El <- List];
patch_code1(Tuple, Key) when tuple_size(Tuple) >= 3 ->
diff --git a/lib/tools/src/eprof.erl b/lib/tools/src/eprof.erl
index 86e3d3a8b8..77bea2b114 100644
--- a/lib/tools/src/eprof.erl
+++ b/lib/tools/src/eprof.erl
@@ -28,7 +28,7 @@
stop/0,
dump/0, dump_data/0,
start_profiling/1, start_profiling/2, start_profiling/3,
- profile/1, profile/2, profile/3, profile/4, profile/5,
+ profile/1, profile/2, profile/3, profile/4, profile/5, profile/6,
stop_profiling/0,
analyze/0, analyze/1, analyze/2, analyze/4,
log/1]).
@@ -398,7 +398,7 @@ collect_bpd() ->
collect_bpd([M || M <- [element(1, Mi) || Mi <- code:all_loaded()], M =/= ?MODULE]).
collect_bpd(Ms) when is_list(Ms) ->
- collect_bpdf(collect_mfas(Ms) ++ erlang:system_info(snifs)).
+ collect_bpdf(collect_mfas(Ms)).
collect_mfas(Ms) ->
lists:foldl(fun
diff --git a/lib/tools/src/instrument.erl b/lib/tools/src/instrument.erl
index 0203fefe13..1d1edcbbea 100644
--- a/lib/tools/src/instrument.erl
+++ b/lib/tools/src/instrument.erl
@@ -94,11 +94,12 @@ alloc_hist_merge_hist(Index, A, B) ->
-type carrier_info_list() ::
{HistogramStart :: non_neg_integer(),
Carriers :: [{AllocatorType :: atom(),
+ InPool :: boolean(),
TotalSize :: non_neg_integer(),
UnscannedSize :: non_neg_integer(),
- AllocatedSize :: non_neg_integer(),
- AllocatedCount :: non_neg_integer(),
- InPool :: boolean(),
+ Allocations :: {Type :: atom(),
+ Count :: non_neg_integer(),
+ Size :: non_neg_integer()},
FreeBlocks :: block_histogram()}]}.
-spec carriers() -> {ok, Result} | {error, Reason} when
diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src
index 6e086c38ea..f0c7ec1ead 100644
--- a/lib/tools/src/tools.app.src
+++ b/lib/tools/src/tools.app.src
@@ -43,6 +43,6 @@
]
},
{runtime_dependencies, ["stdlib-3.4","runtime_tools-1.8.14",
- "kernel-5.4","erts-9.1","compiler-5.0"]}
+ "kernel-5.4","erts-9.1","compiler-5.0", "erts-11.0"]}
]
}.
diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl
index a28c6ee283..1ffad1abe4 100644
--- a/lib/tools/src/xref_base.erl
+++ b/lib/tools/src/xref_base.erl
@@ -401,8 +401,8 @@ analysis(locals_not_used, functions) ->
%% But then we only get locals that make some calls, so we add
%% locals that are not used at all: "L * (UU + XU - LU)".
%% We also need to exclude functions with the -on_load() attribute:
- %% (L - OL) is used rather than just L.
- "(L - OL) * ((UU + XU - LU) + domain EE + range EE)";
+ %% (L - (OL + range (closure LC | OL))) is used rather than just L.
+ "(L - (OL + range (closure LC | OL))) * ((UU + XU - LU) + domain EE + range EE)";
analysis(exports_not_used, _) ->
%% Local calls are not considered here. "X * UU" would do otherwise.
"X - XU";
@@ -897,15 +897,20 @@ depr_fa(_F, _A, _X, _M, _I) ->
undefined.
%% deprecated_flag(Flag) -> integer() | undefined
-%% Maps symbolic flags for deprecated functions to integers.
+%% Maps symbolic flags for deprecated functions to their category indexes
+%% in the deprecation tuple.
+%%
+%% {DF_1, DF_2, DF_3, DF}
-%deprecated_flag(1) -> 1;
-%deprecated_flag(2) -> 2;
-%deprecated_flag(3) -> 3;
deprecated_flag(next_version) -> 1;
deprecated_flag(next_major_release) -> 2;
deprecated_flag(eventually) -> 3;
-deprecated_flag(_) -> undefined.
+deprecated_flag(String) -> depr_desc(String).
+
+%% Strings fall into the general category, index 4.
+depr_desc([Char | Str]) when is_integer(Char) -> depr_desc(Str);
+depr_desc([]) -> 4;
+depr_desc(_) -> undefined.
%% -> {ok, Module, Bad, State} | throw(Error)
%% Assumes:
diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl
index d28bdb78db..9857e8c150 100644
--- a/lib/tools/src/xref_reader.erl
+++ b/lib/tools/src/xref_reader.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -53,7 +53,8 @@
%% R8: abstract_v2
%% R9C: raw_abstract_v1
-%% -> {ok, Module, {DefAt, CallAt, LC, XC, X, Attrs}, Unresolved}} | EXIT
+%% -> {ok, Module, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr, OL},
+%% Unresolved}} | EXIT
%% Attrs = {ALC, AXC, Bad}
%% ALC, AXC and Bad are extracted from the attribute 'xref'. An experiment.
module(Module, Forms, CollectBuiltins, X, DF) ->
@@ -96,13 +97,18 @@ form({function, _, module_info, 0, _Clauses}, S) ->
form({function, _, module_info, 1, _Clauses}, S) ->
S;
form({function, Anno, Name, Arity, Clauses}, S) ->
- MFA0 = {S#xrefr.module, Name, Arity},
- MFA = adjust_arity(S, MFA0),
- S1 = S#xrefr{function = MFA},
- Line = erl_anno:line(Anno),
- S2 = S1#xrefr{def_at = [{MFA,Line} | S#xrefr.def_at]},
- S3 = clauses(Clauses, S2),
- S3#xrefr{function = []};
+ case {Name, Arity, erl_anno:location(Anno)} of
+ {behaviour_info, 1, 0} ->
+ S; % generated
+ _ ->
+ MFA0 = {S#xrefr.module, Name, Arity},
+ MFA = adjust_arity(S, MFA0),
+ S1 = S#xrefr{function = MFA},
+ Line = erl_anno:line(Anno),
+ S2 = S1#xrefr{def_at = [{MFA,Line} | S#xrefr.def_at]},
+ S3 = clauses(Clauses, S2),
+ S3#xrefr{function = []}
+ end;
form(_, S) ->
%% OTP 20. Other uninteresting forms such as {eof, _} and {warning, _}.
%% Exposed because sys_pre_expand is no longer run.
diff --git a/lib/tools/src/xref_utils.erl b/lib/tools/src/xref_utils.erl
index eca751337b..c516f272cb 100644
--- a/lib/tools/src/xref_utils.erl
+++ b/lib/tools/src/xref_utils.erl
@@ -327,7 +327,7 @@ list_dirs([], _I, _Exts, C, E) ->
%% Returns functions that are present in all modules.
predefined_functions() ->
- [{module_info,0}, {module_info,1}].
+ [{module_info,0}, {module_info,1}, {behaviour_info,1}].
%% Returns true if an MFA takes functional arguments.
is_funfun(erlang, apply, 2) -> true;
diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl
index f4c3a0e3b6..771f527e43 100644
--- a/lib/tools/test/cover_SUITE.erl
+++ b/lib/tools/test/cover_SUITE.erl
@@ -31,13 +31,14 @@ all() ->
NoStartStop = [eif,otp_5305,otp_5418,otp_7095,otp_8273,
otp_8340,otp_8188,compile_beam_opts,eep37,
analyse_no_beam, line_0, compile_beam_no_file,
+ compile_beam_missing_backend,
otp_13277, otp_13289],
StartStop = [start, compile, analyse, misc, stop,
distribution, reconnect, die_and_reconnect,
dont_reconnect_after_stop, stop_node_after_disconnect,
export_import, otp_5031, otp_6115,
otp_8270, otp_10979_hanging_node, otp_14817,
- local_only, startup_race, otp_16476],
+ local_only, startup_race, otp_16476, cover_clauses],
case whereis(cover_server) of
undefined ->
[coverage,StartStop ++ NoStartStop];
@@ -1600,6 +1601,15 @@ otp_14817(Config) when is_list(Config) ->
ok = file:delete(CovOut),
ok.
+%% Tests a bug where cover failed for an export named clauses
+cover_clauses(Config) when is_list(Config) ->
+ Test = <<"-module(cover_clauses).
+ -export([clauses/0]).
+ clauses() -> ok.
+ ">>,
+ File = cc_mod(cover_clauses, Test, Config),
+ ok.
+
%% Take compiler options from beam in cover:compile_beam
compile_beam_opts(Config) when is_list(Config) ->
{ok, Cwd} = file:get_cwd(),
@@ -1706,12 +1716,42 @@ compile_beam_no_file(Config) ->
[{error,{no_file_attribute,BeamFile}}] = cover:compile_beam_directory(Dir),
ok.
+%% GH-4353: Don't crash when the backend for generating the abstract code
+%% is missing.
+compile_beam_missing_backend(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Dir = filename:join(PrivDir, ?FUNCTION_NAME),
+ ok = filelib:ensure_dir(filename:join(Dir, "*")),
+ code:add_patha(Dir),
+ Str = lists:append(
+ ["-module(no_backend).\n"
+ "-compile(export_all).\n"
+ "foo() -> ok.\n"]),
+ TT = do_scan(Str),
+ Forms = [ begin {ok,Y} = erl_parse:parse_form(X),Y end || X <- TT ],
+ {ok,_,Bin} = compile:forms(Forms, [debug_info]),
+
+ %% Create a debug_info chunk with a non-existing backend.
+ {ok,no_backend,All0} = beam_lib:all_chunks(Bin),
+ FakeBackend = definitely__not__an__existing__backend,
+ FakeDebugInfo = {debug_info_v1,FakeBackend,nothing_here},
+ All = lists:keyreplace("Dbgi", 1, All0, {"Dbgi", term_to_binary(FakeDebugInfo)}),
+ {ok,NewBeam} = beam_lib:build_module(All),
+ BeamFile = filename:join(Dir, "no_backend.beam"),
+ ok = file:write_file(BeamFile, NewBeam),
+
+ {error,{{missing_backend,FakeBackend},BeamFile}} = cover:compile_beam(no_backend),
+ [{error,{{missing_backend,FakeBackend},BeamFile}}] = cover:compile_beam_directory(Dir),
+
+ ok.
+
do_scan([]) ->
[];
do_scan(Str) ->
{done,{ok,T,_},C} = erl_scan:tokens([],Str,0),
[ T | do_scan(C) ].
+
%% PR 856. Fix a bc bug.
otp_13277(Config) ->
Test = <<"-module(t).
diff --git a/lib/tools/test/cprof_SUITE.erl b/lib/tools/test/cprof_SUITE.erl
index 9cbc27fb17..39239a66a9 100644
--- a/lib/tools/test/cprof_SUITE.erl
+++ b/lib/tools/test/cprof_SUITE.erl
@@ -211,16 +211,12 @@ on_load_test(Config) ->
Lr = seq_r(1, M, fun succ/1),
N2 = cprof:pause(),
{Module,0,[]} = cprof:analyse(Module),
- M_1 = M - 1,
M4__4 = M*4 - 4,
M10_7 = M*10 - 7,
{?MODULE,M10_7,[{{?MODULE,succ,1},M4__4},
+ {{?MODULE,'-fun.succ/1-',1},M4__4},
{{?MODULE,seq_r,4},M},
{{?MODULE,seq,3},M},
- {{?MODULE,'-on_load_test/1-fun-5-',1},M_1},
- {{?MODULE,'-on_load_test/1-fun-4-',1},M_1},
- {{?MODULE,'-on_load_test/1-fun-3-',1},M_1},
- {{?MODULE,'-on_load_test/1-fun-2-',1},M_1},
{{?MODULE,seq_r,3},1}]}
= cprof:analyse(?MODULE),
N2 = cprof:stop(),
@@ -246,18 +242,14 @@ modules_test(Config) ->
Lr = seq_r(1, M, fun succ/1),
N = cprof:pause(),
Lr = lists:reverse(L),
- M_1 = M - 1,
M4_4 = M*4 - 4,
M10_7 = M*10 - 7,
M2__1 = M*2 + 1,
{Tot,ModList} = cprof:analyse(),
{value,{?MODULE,M10_7,[{{?MODULE,succ,1},M4_4},
+ {{?MODULE,'-fun.succ/1-',1},M4_4},
{{?MODULE,seq_r,4},M},
{{?MODULE,seq,3},M},
- {{?MODULE,'-modules_test/1-fun-3-',1},M_1},
- {{?MODULE,'-modules_test/1-fun-2-',1},M_1},
- {{?MODULE,'-modules_test/1-fun-1-',1},M_1},
- {{?MODULE,'-modules_test/1-fun-0-',1},M_1},
{{?MODULE,seq_r,3},1}]}} =
lists:keysearch(?MODULE, 1, ModList),
{value,{Module,M2__1,[{{Module,seq_r,4},M},
diff --git a/lib/tools/test/emacs_SUITE.erl b/lib/tools/test/emacs_SUITE.erl
index a65a131817..f74a159cd2 100644
--- a/lib/tools/test/emacs_SUITE.erl
+++ b/lib/tools/test/emacs_SUITE.erl
@@ -107,7 +107,7 @@ compile_and_load(_Config) ->
%% Workaround byte-compile-error-on-warn which seem broken in
%% Emacs 25.
"\"(advice-add #'display-warning :after "
- "(lambda (_ f _ _) (error \"%s\" f)))\"";
+ "(lambda (_ f &optional _ _) (error \\\"%s\\\" f)))\"";
_ ->
"\"(setq byte-compile-error-on-warn t)\""
end,
@@ -193,7 +193,7 @@ diff(Orig, File) ->
end.
emacs_version_ok(AcceptVer) ->
- VersionLine = os:cmd("emacs --version | head -1"),
+ VersionLine = os:cmd("emacs --version | head -n 1"),
io:format("~s~n", [VersionLine]),
case VersionLine of
"GNU Emacs " ++ Ver ->
diff --git a/lib/tools/test/eprof_SUITE.erl b/lib/tools/test/eprof_SUITE.erl
index e908413315..4578b15cdb 100644
--- a/lib/tools/test/eprof_SUITE.erl
+++ b/lib/tools/test/eprof_SUITE.erl
@@ -131,6 +131,7 @@ basic_option_1(Config) ->
{ok, _} = eprof:profile(fun() -> eprof_test:do(10) end, [{set_on_spawn, true}]),
Mfas1 = lists:foldl(fun({_,Mfas},Out) -> Mfas ++ Out end, [], eprof:dump()),
+ assert_unique_mfas(Mfas1),
{value, {_, {11, _}}} = lists:keysearch({eprof_test,dec,1}, 1, Mfas1),
{value, {_, { 1, _}}} = lists:keysearch({eprof_test, go,1}, 1, Mfas1),
@@ -140,6 +141,8 @@ basic_option_1(Config) ->
{ok, _} = eprof:profile(fun() -> eprof_test:do(10) end, [set_on_spawn]),
Mfas2 = lists:foldl(fun({_,Mfas},Out) -> Mfas ++ Out end, [], eprof:dump()),
+ assert_unique_mfas(Mfas2),
+
{value, {_, {11, _}}} = lists:keysearch({eprof_test,dec,1}, 1, Mfas2),
{value, {_, { 1, _}}} = lists:keysearch({eprof_test, go,1}, 1, Mfas2),
{value, {_, { 9, _}}} = lists:keysearch({lists, split_2,5}, 1, Mfas2),
@@ -148,6 +151,9 @@ basic_option_1(Config) ->
% disable trace set_on_spawn
{ok, _} = eprof:profile(fun() -> eprof_test:do(10) end, []),
[{_, Mfas3}] = eprof:dump(),
+
+ assert_unique_mfas(Mfas3),
+
{value, {_, {11, _}}} = lists:keysearch({eprof_test,dec,1}, 1, Mfas3),
{value, {_, { 1, _}}} = lists:keysearch({eprof_test, go,1}, 1, Mfas3),
false = lists:keysearch({lists, split_2,5}, 1, Mfas3),
@@ -158,6 +164,13 @@ basic_option_1(Config) ->
stopped = eprof:stop(),
ok.
+assert_unique_mfas(MFAs) ->
+ Duplicates = lists:sort(MFAs) -- lists:usort(MFAs),
+ case Duplicates of
+ [{{erlang,apply,2},_}] -> ok;
+ [] -> ok
+ end.
+
tiny(Config) when is_list(Config) ->
ensure_eprof_stopped(),
{ok, OldCurDir} = file:get_cwd(),
diff --git a/lib/tools/test/fprof_SUITE.erl b/lib/tools/test/fprof_SUITE.erl
index 898d20f560..f5d68c20f0 100644
--- a/lib/tools/test/fprof_SUITE.erl
+++ b/lib/tools/test/fprof_SUITE.erl
@@ -501,7 +501,11 @@ cpu_create_file_slow(Config) when is_list(Config) ->
%%
{ok, [T, P]} = parse(AnalysisFile),
io:format("~p~n~n~p~n", [P, ets:tab2list(T)]),
- ok = verify(T, P),
+ try
+ ok = verify(T, P)
+ catch throw:{negative,_Weird} ->
+ io:format("Aborted as counter is negative because of bad cpu_time")
+ end,
Proc = pid_to_list(self()),
case P of
[{analysis_options, _},
@@ -520,13 +524,7 @@ cpu_create_file_slow(Config) when is_list(Config) ->
io:format("cpu_ts:~w, fprof:~w~n", [Acc, Acc1]),
{comment, io_lib:format("~p% cpu utilization", [100*divide(Acc,Acc1)])};
{'EXIT', not_supported} ->
- case {os:type(), os:version()} of
- {{unix, sunos}, {Major, Minor, _}}
- when Major >= 5, Minor >= 7 ->
- ct:fail(Result);
- _ ->
- {skipped, "not_supported"}
- end;
+ {skipped, "not_supported"};
_ ->
ct:fail(Result)
end,
@@ -633,6 +631,8 @@ verify(Tab, [{analysis_options, _},
{Proc, Cnt_P, Acc_P, Own_P} = Clocks
when Acc_P >= Own_P ->
Clocks;
+ {_, _, Acc_p, _} = Weird when Acc_p < 0 ->
+ throw({negative, Weird});
Weird ->
throw({error, [?MODULE, ?LINE, Weird]})
end
diff --git a/lib/tools/test/instrument_SUITE.erl b/lib/tools/test/instrument_SUITE.erl
index ec52d78318..41b7b11cd2 100644
--- a/lib/tools/test/instrument_SUITE.erl
+++ b/lib/tools/test/instrument_SUITE.erl
@@ -19,7 +19,7 @@
%%
-module(instrument_SUITE).
--export([all/0, suite/0]).
+-export([all/0, suite/0, init_per_suite/1, end_per_suite/1]).
-export([allocations_enabled/1, allocations_disabled/1, allocations_ramv/1,
carriers_enabled/1, carriers_disabled/1]).
@@ -37,6 +37,19 @@ all() ->
[allocations_enabled, allocations_disabled, allocations_ramv,
carriers_enabled, carriers_disabled].
+init_per_suite(Config) ->
+ case test_server:is_asan() of
+ true ->
+ %% No point testing own allocators under address sanitizer.
+ {skip, "Address sanitizer"};
+ false ->
+ Config
+ end.
+
+end_per_suite(_Config) ->
+ ok.
+
+
-define(GENERATED_SBC_BLOCK_COUNT, 1000).
-define(GENERATED_MBC_BLOCK_COUNT, ?GENERATED_SBC_BLOCK_COUNT).
@@ -194,23 +207,19 @@ verify_carriers_output(#{ histogram_start := HistStart,
%% Do the carriers look alright?
CarrierSet = ordsets:from_list(AllCarriers),
Verified = [C || {AllocType,
+ InPool,
TotalSize,
UnscannedSize,
- AllocatedSize,
- AllocatedCount,
- InPool,
+ Allocations,
FreeBlockHist} = C <- CarrierSet,
is_atom(AllocType),
+ is_boolean(InPool),
is_integer(TotalSize), TotalSize >= 1,
is_integer(UnscannedSize), UnscannedSize < TotalSize,
UnscannedSize >= 0,
- is_integer(AllocatedSize), AllocatedSize < TotalSize,
- AllocatedSize >= 0,
- is_integer(AllocatedCount), AllocatedCount =< AllocatedSize,
- AllocatedCount >= 0,
- is_boolean(InPool),
+ is_list(Allocations),
tuple_size(FreeBlockHist) =:= HistWidth,
- carrier_block_check(AllocatedCount, FreeBlockHist)],
+ carrier_block_check(Allocations, FreeBlockHist)],
[] = ordsets:subtract(CarrierSet, Verified),
%% Do we have at least as many carriers as we've generated?
@@ -229,8 +238,12 @@ verify_carriers_output(#{ histogram_start := HistStart,
verify_carriers_output(#{}, {error, not_enabled}) ->
ok.
-carrier_block_check(AllocCount, FreeHist) ->
- %% A carrier must contain at least one block, and th. number of free blocks
+carrier_block_check(Allocations, FreeHist) ->
+ AllocCount = lists:foldl(fun({_Type, Count, _Size}, Acc) ->
+ Count + Acc
+ end, 0, Allocations),
+
+ %% A carrier must contain at least one block, and the number of free blocks
%% must not exceed the number of allocated blocks + 1.
FreeCount = hist_sum(FreeHist),
diff --git a/lib/tools/test/prof_bench_SUITE.erl b/lib/tools/test/prof_bench_SUITE.erl
index e8c4642d37..c0b9a7913c 100644
--- a/lib/tools/test/prof_bench_SUITE.erl
+++ b/lib/tools/test/prof_bench_SUITE.erl
@@ -32,7 +32,7 @@
suite() ->
- [{timetrap,{minutes,10}}].
+ [{timetrap,{minutes,15}}].
all() ->
[overhead].
diff --git a/lib/tools/test/xref_SUITE.erl b/lib/tools/test/xref_SUITE.erl
index da4f56c09b..86dcb3c94d 100644
--- a/lib/tools/test/xref_SUITE.erl
+++ b/lib/tools/test/xref_SUITE.erl
@@ -48,7 +48,8 @@
fun_mfa/1, fun_mfa_r14/1,
fun_mfa_vars/1, qlc/1]).
--export([analyze/1, basic/1, md/1, q/1, variables/1, unused_locals/1]).
+-export([analyze/1, basic/1, md/1, q/1, variables/1, unused_locals/1,
+ behaviour/1]).
-export([format_error/1, otp_7423/1, otp_7831/1, otp_10192/1, otp_13708/1,
otp_14464/1, otp_14344/1]).
@@ -83,7 +84,7 @@ groups() ->
fun_mfa_r14, fun_mfa_vars, qlc]},
{analyses, [],
- [analyze, basic, md, q, variables, unused_locals]},
+ [analyze, basic, md, q, variables, unused_locals, behaviour]},
{misc, [], [format_error, otp_7423, otp_7831, otp_10192, otp_13708,
otp_14464, otp_14344]}].
@@ -1098,10 +1099,8 @@ read_expected(Version) ->
{POS4+5,{FF,{spm,spf,2}}},
{POS4+6,{FF,{dist,func,2}}},
{POS4+6,{FF,{erlang,spawn_link,4}}},
- {POS4+7,{FF,{erlang,spawn_opt,4}}},
{POS4+7,{FF,{read,bi,0}}},
{POS4+7,{FF,{spm,spf,2}}},
- {POS4+8,{FF,{erlang,spawn_opt,4}}},
{POS4+8,{FF,{read,bi,0}}},
{POS5+1,{FF,{erlang,spawn,1}}},
{POS5+2,{FF,{erlang,spawn,1}}},
@@ -1109,7 +1108,6 @@ read_expected(Version) ->
{POS6+1,{FF,{erlang,spawn,2}}},
{POS6+2,{FF,{erlang,spawn_link,2}}},
{POS7+1,{FF,{erlang,spawn,4}}},
- {POS7+2,{FF,{erlang,spawn_opt,4}}},
{POS8+1,{FF,{hej,san,1}}},
{POS8+4,{FF,{a,b,1}}},
{POS8+6,{FF,{m,f,1}}},
@@ -1150,7 +1148,10 @@ read_expected(Version) ->
{POS3+2, {FF,{erlang,spawn,3}}},
{POS3+3, {FF,{erlang,spawn_link,3}}},
{POS3+4, {FF,{erlang,spawn_link,3}}},
+ {POS4+7, {FF,{erlang,spawn_opt,4}}},
+ {POS4+8, {FF,{erlang,spawn_opt,4}}},
{POS6+4, {FF,{erlang,spawn,3}}},
+ {POS7+2, {FF,{erlang,spawn_opt,4}}},
{POS8+4,{FF,{erlang,apply,2}}},
{POS8+5,{FF,{erlang,apply,2}}},
{POS8+6,{FF,{erlang,apply,3}}},
@@ -1479,12 +1480,13 @@ deprecated(Conf) when is_list(Conf) ->
Test2= <<"-module(depr).
- -export([t/0,f/1,bar/2,f/2,g/3]).
+ -export([t/0,f/1,bar/2,f/2,g/3,string/0]).
-deprecated([{'_','_',eventually}]). % DF_3
-deprecated([{f,'_',next_major_release}]). % DF_2
-deprecated([{g,'_',next_version}]). % DF_1
-deprecated([{bar,2}]). % DF
+ -deprecated([{string,0,\"hello\"}]). % DF
t() ->
g(1,2, 3),
@@ -1499,6 +1501,9 @@ deprecated(Conf) when is_list(Conf) ->
g(F, G, H) ->
?MODULE:bar(F, {G,H}).
+ string() ->
+ ?MODULE:string().
+
bar(_, _) ->
?MODULE:t().
">>,
@@ -1512,7 +1517,8 @@ deprecated(Conf) when is_list(Conf) ->
M = depr,
DFa_1 = usort([{{M,f,2},{M,g,3}}]),
DFa_2 = usort(DFa_1++[{{M,f,1},{M,f,2}},{{M,t,0},{M,f,1}}]),
- DFa_3 = usort(DFa_2++[{{M,bar,2},{M,t,0}},{{M,g,3},{M,bar,2}}]),
+ DFa_3 = usort(DFa_2++[{{M,bar,2},{M,t,0}},{{M,g,3},{M,bar,2}},
+ {{M,string,0},{M,string,0}}]),
DFa = DFa_3,
{ok,DFa} = xref:analyze(s, deprecated_function_calls),
@@ -2449,8 +2455,9 @@ otp_14344(Conf) when is_list(Conf) ->
MFile1 = fname(Dir, "a"),
Beam1 = fname(Dir, "a.beam"),
Test1 = <<"-module(a).
- -on_load(doit/0).
- doit() -> ok.
+ -on_load(init/0).
+ init() -> do_init().
+ do_init() -> ok.
">>,
ok = file:write_file(File1, Test1),
{ok, a} = compile:file(File1, [debug_info,{outdir,Dir}]),
@@ -2458,13 +2465,91 @@ otp_14344(Conf) when is_list(Conf) ->
{ok, _} = xref:start(s),
{ok, a} = xref:add_module(s, MFile1),
- {ok, [{a,doit,0}]} = xref:q(s, "OL"),
+ {ok, [{a,init,0}]} = xref:q(s, "OL"),
{ok, []} = xref:analyze(s, locals_not_used),
xref:stop(s),
ok = file:delete(File1),
ok = file:delete(Beam1).
+%% PR-2752, ERL-1353, ERL-1476, GH-4192.
+behaviour(Config) ->
+ ModMode = [{xref_mode, modules}],
+ FunMode = [],
+
+ Test1 = [{a, <<"-module(a).
+ -callback a() -> ok.
+ ">>}],
+ {Undef1, UnusedExports1} = behaviour_test(Test1, Config, FunMode),
+ [] = Undef1,
+ [] = UnusedExports1,
+ {Undef1m, UnusedExports1m} = behaviour_test(Test1, Config, ModMode),
+ [] = Undef1m,
+ [] = UnusedExports1m,
+
+ Test2 = [{a, <<"-module(a).
+ -export([behaviour_info/1]).
+ behaviour_info(_) ->
+ ok.
+ ">>}],
+ {Undef2, UnusedExports2} = behaviour_test(Test2, Config, FunMode),
+ [] = Undef2,
+ [{a,behaviour_info,1}] = UnusedExports2,
+ {Undef2m, UnusedExports2m} = behaviour_test(Test2, Config, ModMode),
+ [] = Undef2m,
+ %% Without abstract code it is not possible to determine if
+ %% M:behaviour_info/1 is generated or not. The best we can do is
+ %% to assume it is generated since it would otherwise always be
+ %% returned as unused.
+ [] = UnusedExports2m,
+
+ Test3 = [{a, <<"-module(a).
+ -export([behaviour_info/1]).
+ behaviour_info(_) ->
+ ok.
+ ">>},
+ {b, <<"-module(b).
+ -export([bar/0]).
+ bar() -> a:behaviour_info(callbacks).
+ ">>}],
+ {Undef3, UnusedExports3} = behaviour_test(Test3, Config, FunMode),
+ [] = Undef3,
+ [{b,bar,0}] = UnusedExports3,
+ {Undef3m, UnusedExports3m} = behaviour_test(Test3, Config, ModMode),
+ [] = Undef3m,
+ [{b,bar,0}] = UnusedExports3m,
+ ok.
+
+behaviour_test(Tests, Conf, Opts) ->
+ {ok, _} = xref:start(s, Opts),
+ add_modules(Tests, Conf),
+ case lists:keyfind(xref_mode, 1, Opts) of
+ {xref_mode, modules} ->
+ UndefinedFunctionCalls = [];
+ _ ->
+ {ok, UndefinedFunctionCalls} =
+ xref:analyze(s, undefined_function_calls)
+ end,
+ {ok, ExportsNotUsed} = xref:analyze(s, exports_not_used),
+ xref:stop(s),
+ {UndefinedFunctionCalls, ExportsNotUsed}.
+
+add_modules([], _Conf) ->
+ ok;
+add_modules([{Mod, Test} |Tests], Conf) ->
+ Dir = ?copydir,
+ Name = atom_to_list(Mod),
+ File = fname(Dir, Name ++ ".erl"),
+ MFile = fname(Dir, Name),
+ Beam = fname(Dir, Name ++ ".beam"),
+ ok = file:write_file(File, Test),
+ {ok, Mod} = compile:file(File, [debug_info,{outdir,Dir}]),
+ {ok, Mod} = xref:add_module(s, MFile),
+ check_state(s),
+ ok = file:delete(File),
+ ok = file:delete(Beam),
+ add_modules(Tests, Conf).
+
%%%
%%% Utilities
%%%
diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk
index 66f2c03149..33ff4b4a7c 100644
--- a/lib/tools/vsn.mk
+++ b/lib/tools/vsn.mk
@@ -1 +1 @@
-TOOLS_VSN = 3.3.1
+TOOLS_VSN = 3.4.4
diff --git a/lib/wx/Makefile b/lib/wx/Makefile
index 2397950925..002887d9da 100644
--- a/lib/wx/Makefile
+++ b/lib/wx/Makefile
@@ -40,3 +40,5 @@ CLEANDIRS = $(SUBDIRS) api_gen
SUB_DIRECTORIES=$(SUBDIRS)
include $(ERL_TOP)/make/otp_subdir.mk
+
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/wx/api_gen/wx_extra/wxTaskBarIcon.c_src b/lib/wx/api_gen/wx_extra/wxTaskBarIcon.c_src
new file mode 100644
index 0000000000..1b1f37486c
--- /dev/null
+++ b/lib/wx/api_gen/wx_extra/wxTaskBarIcon.c_src
@@ -0,0 +1,46 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+<<wxTaskBarIcon_class
+class EwxTaskBarIcon : public wxTaskBarIcon {
+ public: ~EwxTaskBarIcon() {((WxeApp *)wxTheApp)->clearPtr(this);};
+ EwxTaskBarIcon() : wxTaskBarIcon() {};
+
+ int createPopupMenu;
+ ErlDrvTermData port;
+
+ private:
+ virtual wxMenu* CreatePopupMenu();
+};
+wxTaskBarIcon_class>>
+
+<<wxTaskBarIcon_new
+case ~s: { // wxTaskBarIcon::wxTaskBarIcon
+ EwxTaskBarIcon * Result = new EwxTaskBarIcon();
+ Result->port = Ecmd.port;
+ if ( * (int*) bp) {
+ Result->createPopupMenu = *(int *) bp;
+ }
+
+ newPtr((void *) Result, 1, memenv);
+ rt.addRef(getRef((void *)Result,memenv), "wxTaskBarIcon");
+ break;
+}
+wxTaskBarIcon_new>>
diff --git a/lib/wx/api_gen/wx_extra/wxTaskBarIcon.erl b/lib/wx/api_gen/wx_extra/wxTaskBarIcon.erl
new file mode 100644
index 0000000000..cde9c5ead6
--- /dev/null
+++ b/lib/wx/api_gen/wx_extra/wxTaskBarIcon.erl
@@ -0,0 +1,45 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+<<EXPORT:wxTaskBarIcon new/0, new/1 wxTaskBarIcon:EXPORT>>
+
+<<wxTaskBarIcon_new
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconwxtaskbaricon">external documentation</a>.
+-spec new() -> wxTaskBarIcon().
+new() ->
+ wxe_util:construct(~s, <<0:32>>).
+
+
+%% @doc Creates a TaskBarIcon with a callback function for CreatePopupMenu:
+%% <pre>Callback() -> term()</pre>
+%%
+%% See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconwxtaskbaricon">external documentation</a>.
+-spec new([Option]) -> wxTaskBarIcon() when
+ Option :: {'createPopupMenu', fun(() -> wxMenu:wxMenu())}.
+new([]) ->
+ new();
+new([{createPopupMenu, F}]) when is_function(F) ->
+ Fun = fun([_]) ->
+ #wx_ref{type=wxMenu,ref=ThisRef} = F(),
+ <<ThisRef:32/?UI>>
+ end,
+ BinFun = <<(wxe_util:get_cbId(Fun)):32/?UI, 0:32>>,
+ wxe_util:construct(?wxTaskBarIcon_new, BinFun).
+wxTaskBarIcon_new>>
diff --git a/lib/wx/api_gen/wx_gen.erl b/lib/wx/api_gen/wx_gen.erl
index d9dd624ca8..16b85be97d 100644
--- a/lib/wx/api_gen/wx_gen.erl
+++ b/lib/wx/api_gen/wx_gen.erl
@@ -1434,7 +1434,7 @@ extract_def([#xmlElement{name=name,content=[#xmlText{value=Name}]}|R], _N, Skip)
extract_def([#xmlElement{name=param}|_],Name,_) ->
throw(Name);
extract_def([#xmlElement{name=initializer,content=Cs}|_R],N,Skip) ->
- Val0 = extract_def2(Cs),
+ Val0 = string:strip(strip_comment(extract_def2(Cs))),
case Val0 of
"0x" ++ Val1 -> {N, list_to_integer(Val1, 16)};
_ ->
@@ -1458,7 +1458,7 @@ extract_def(_,N,_) ->
throw(N).
extract_def2([#xmlText{value=Val}|R]) ->
- string:strip(strip_comment(Val)) ++ extract_def2(R);
+ string:strip(Val) ++ extract_def2(R);
extract_def2([#xmlElement{content=Cs}|R]) ->
extract_def2(Cs) ++ extract_def2(R);
extract_def2([]) -> [].
diff --git a/lib/wx/api_gen/wx_gen_erl.erl b/lib/wx/api_gen/wx_gen_erl.erl
index 797533309b..e8287c4b1f 100644
--- a/lib/wx/api_gen/wx_gen_erl.erl
+++ b/lib/wx/api_gen/wx_gen_erl.erl
@@ -49,8 +49,10 @@ gen(Defs) ->
Replace = fun(C=#class{name=Name}, Dfs) ->
lists:keyreplace(Name, #class.name, Dfs, C)
end,
- [gen_class(Class) || Class <- lists:foldl(Replace, Defs, Static)],
- gen_funcnames().
+ All = lists:foldl(Replace, Defs, Static),
+ [gen_class(Class) || Class <- All],
+ gen_funcnames(),
+ gen_api_footprint(All).
gen_class(Class) ->
try
@@ -159,7 +161,13 @@ gen_class1(C=#class{name=Name,parent=Parent,methods=Ms,options=Opts}) ->
w("-export_type([~s/0]).~n", [Name]),
case lists:filter(fun({_F,Depr}) -> Depr end, ExportList) of
[] -> ok;
- Depr -> w("-deprecated([~s]).~n~n", [args(fun({EF,_}) -> EF end, ",", Depr, 60)])
+ Depr ->
+ DepStr = "not available in wxWidgets-2.9 and later",
+ w("-deprecated([~s]).~n~n",
+ [args(fun({EF,_}) ->
+ [DFun,DArgs] = string:split(EF, "/"),
+ io_lib:format("{~s,~s,\"~s\"}", [DFun,DArgs,DepStr])
+ end, ",\n ", Depr, 60)])
end,
case lists:filter(fun({_,_,Depr}) -> Depr end, InExported) of
[] -> ok;
@@ -1318,6 +1326,57 @@ gen_funcnames() ->
[w("-define(~s_~s, ~p).~n", [Class,Name,Id]) || {Class,Name,_,Id} <- Ns],
close().
+gen_api_footprint(All) ->
+ %% To be able to diff content between old and new generators.
+ open_write("wx_28_api.dump"),
+ TF = fun TF (Type) ->
+ try wx_gen:type_foot_print(Type) of
+ {class, C} -> list_to_atom(C);
+ {merged, MTs} -> [TF(MT) || {_, MT} <- MTs];
+ T -> T
+ catch _:R:ST ->
+ case Type of
+ void -> ok;
+ {merged, _,T1,_, _,T2,_} ->
+ lists:sort([TF(T1),TF(T2)]);
+ _ ->
+ io:format("ERROR: ~p~n ~p~n",[R,ST]),
+ exit(R)
+ end
+ end
+ end,
+ Out = fun(T, []) -> TF(T);
+ (void, [#param{type=T}]) -> TF(T);
+ (void, Ps) -> list_to_tuple([TF(T) || #param{type=T} <- Ps]);
+ (T, Ps) -> list_to_tuple([TF(T)|[TF(Tp) || #param{type=Tp} <- Ps]])
+ end,
+ Methods = fun(CName, Parent, #method{name=N, alias=Alias, method_type=MType,where=W,type=Type0,params=Ps}) ->
+ {Args0,Opts0} = split_optional(Ps),
+ Type = Out(Type0, [P || P=#param{in=In} <- Ps,In =/= true]),
+ Args = [TF(PT) || #param{type=PT} <- Args0],
+ case W of
+ merged_c ->
+ ignore;
+ erl_no_opt ->
+ {CName, list_to_atom(erl_func_name(N,Alias)), Type, Args, [], W, Parent};
+ _ ->
+ Opts = lists:sort([{list_to_atom(erl_option_name(ON)), TF(OT)} ||
+ #param{name=ON,type=OT} <- Opts0]),
+ {CName, list_to_atom(erl_func_name(N,Alias)), Type, Args, Opts, W, Parent}
+ end
+ end,
+ Class = fun(#class{name=CName0,parent=Parent,methods=Ms0,options=Opts}) ->
+ CName = case Parent of
+ "static" -> "wx_misc";
+ _ -> CName0
+ end,
+ [Methods(list_to_atom(CName), list_to_atom(Parent), M) || Ms <- Ms0, M <-Ms]
+ end,
+ Sorted = lists:sort(lists:append([Class(C) || C <- All])),
+ [w("~0p.~n", [E]) || E <- Sorted, E /= ignore],
+ close().
+
+
get_unique_name(ID) when is_integer(ID) ->
Tree = get(unique_names),
{Class,Name, _,_} = gb_trees:get(ID, Tree),
diff --git a/lib/wx/api_gen/wxapi.conf b/lib/wx/api_gen/wxapi.conf
index cd53a25c40..d2f8cac4c1 100644
--- a/lib/wx/api_gen/wxapi.conf
+++ b/lib/wx/api_gen/wxapi.conf
@@ -2005,7 +2005,7 @@
[wxLogNull, '~wxLogNull']}.
{class, wxTaskBarIcon, wxEvtHandler, [],
- [wxTaskBarIcon,'~wxTaskBarIcon',
+ [{wxTaskBarIcon, [{where, taylormade}]},'~wxTaskBarIcon',
%%'CreatePopupMenu', virtual overrided is a callback
%% 'IsIconInstalled', 'IsOk', not available on mac
'PopupMenu','RemoveIcon','SetIcon']}.
diff --git a/lib/wx/c_src/egl_impl.cpp b/lib/wx/c_src/egl_impl.cpp
index 61e05ee6f1..06c1de9cb6 100644
--- a/lib/wx/c_src/egl_impl.cpp
+++ b/lib/wx/c_src/egl_impl.cpp
@@ -48,6 +48,7 @@ int egl_initiated = 0;
#define OPENGLU_LIB L"glu32.dll"
typedef HMODULE DL_LIB_P;
typedef WCHAR DL_CHAR;
+#define DL_STR_FMT "%S"
void * dlsym(HMODULE Lib, const char *func) {
void * funcp;
if((funcp = (void *) GetProcAddress(Lib, func)))
@@ -67,6 +68,7 @@ void dlclose(HMODULE Lib) {
#else
typedef void * DL_LIB_P;
typedef char DL_CHAR;
+# define DL_STR_FMT "%s"
# ifdef _MACOSX
# define OPENGL_LIB "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib"
# define OPENGLU_LIB "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib"
@@ -125,7 +127,7 @@ int load_gl_functions() {
// dlclose(LIBhandle);
// fprintf(stderr, "OPENGL library is loaded\r\n");
} else {
- fprintf(stderr, "Could NOT load OpenGL library: %s\r\n", DLName);
+ fprintf(stderr, "Could NOT load OpenGL library: " DL_STR_FMT "\r\n", DLName);
};
DLName = (DL_CHAR *) OPENGLU_LIB;
@@ -154,7 +156,7 @@ int load_gl_functions() {
// dlclose(LIBhandle);
// fprintf(stderr, "GLU library is loaded\r\n");
} else {
- fprintf(stderr, "Could NOT load OpenGL GLU library: %s\r\n", DLName);
+ fprintf(stderr, "Could NOT load OpenGL GLU library: " DL_STR_FMT "\r\n", DLName);
};
return 1;
diff --git a/lib/wx/c_src/gen/wxe_derived_dest.h b/lib/wx/c_src/gen/wxe_derived_dest.h
index a7114eb188..52a1460a79 100644
--- a/lib/wx/c_src/gen/wxe_derived_dest.h
+++ b/lib/wx/c_src/gen/wxe_derived_dest.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2018. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2020. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -766,9 +766,16 @@ class EwxHtmlWindow : public wxHtmlWindow {
EwxHtmlWindow() : wxHtmlWindow() {};
};
+
class EwxTaskBarIcon : public wxTaskBarIcon {
public: ~EwxTaskBarIcon() {((WxeApp *)wxTheApp)->clearPtr(this);};
EwxTaskBarIcon() : wxTaskBarIcon() {};
+
+ int createPopupMenu;
+ ErlDrvTermData port;
+
+ private:
+ virtual wxMenu* CreatePopupMenu();
};
class EwxLocale : public wxLocale {
diff --git a/lib/wx/c_src/gen/wxe_funcs.cpp b/lib/wx/c_src/gen/wxe_funcs.cpp
index 747201fe78..4304cdda91 100644
--- a/lib/wx/c_src/gen/wxe_funcs.cpp
+++ b/lib/wx/c_src/gen/wxe_funcs.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2019. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2020. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31773,8 +31773,14 @@ case wxLogNull_destroy: { // wxLogNull::destroy
delete This;}
break;
}
+
case wxTaskBarIcon_new: { // wxTaskBarIcon::wxTaskBarIcon
- wxTaskBarIcon * Result = new EwxTaskBarIcon();
+ EwxTaskBarIcon * Result = new EwxTaskBarIcon();
+ Result->port = Ecmd.port;
+ if ( * (int*) bp) {
+ Result->createPopupMenu = *(int *) bp;
+ }
+
newPtr((void *) Result, 1, memenv);
rt.addRef(getRef((void *)Result,memenv), "wxTaskBarIcon");
break;
diff --git a/lib/wx/c_src/wxe_callback_impl.cpp b/lib/wx/c_src/wxe_callback_impl.cpp
index 77359e9256..09c9f2630a 100644
--- a/lib/wx/c_src/wxe_callback_impl.cpp
+++ b/lib/wx/c_src/wxe_callback_impl.cpp
@@ -309,6 +309,26 @@ int wxCALLBACK wxEListCtrlCompare(wxeIntPtr item1, wxeIntPtr item2, wxeIntPtr ca
}
+/* *****************************************************************/
+// TaskBarIcon with callbacks for VIRTUAL_TABLES
+
+wxMenu* EwxTaskBarIcon::CreatePopupMenu() {
+ if(createPopupMenu) {
+ INVOKE_CALLBACK(port, createPopupMenu, "wxTaskBarIcon");
+ char * bp = ((WxeApp *) wxTheApp)->cb_buff;
+ wxeMemEnv * memenv = ((WxeApp *) wxTheApp)->getMemEnv(port);
+ if(bp) {
+ wxMenu * result = (wxMenu *)((WxeApp *) wxTheApp)->getPtr(bp, memenv);
+ driver_free(((WxeApp *) wxTheApp)->cb_buff);
+ ((WxeApp *) wxTheApp)->cb_buff = NULL;
+ return result;
+ }
+ }
+ return NULL;
+}
+
+
+
// tools
void clear_cb(ErlDrvTermData port, int callback)
diff --git a/lib/wx/c_src/wxe_main.cpp b/lib/wx/c_src/wxe_main.cpp
index 5b65d8a59b..fdee1fb1c0 100644
--- a/lib/wx/c_src/wxe_main.cpp
+++ b/lib/wx/c_src/wxe_main.cpp
@@ -118,7 +118,20 @@ void *wxe_main_loop(void *vpdl)
{
int result;
int argc = 1;
- const wxChar temp[10] = L"Erlang";
+ wxChar temp[128] = L"Erlang";
+
+ size_t app_len = 127;
+ char app_title_buf[128];
+ int res = erl_drv_getenv("WX_APP_TITLE", app_title_buf, &app_len);
+ if (res == 0) {
+ wxString title = wxString::FromUTF8(app_title_buf);
+ int size = title.Length() < 127 ? title.Length() : 126;
+ for(int i = 0; i < size; i++) {
+ temp[i] = title[i];
+ }
+ temp[size] = 0;
+ }
+
wxChar * argv[] = {(wxChar *)temp, NULL};
ErlDrvPDL pdl = (ErlDrvPDL) vpdl;
diff --git a/lib/wx/c_src/wxe_ps_init.c b/lib/wx/c_src/wxe_ps_init.c
index 62c7c51c13..5d19502fbe 100644
--- a/lib/wx/c_src/wxe_ps_init.c
+++ b/lib/wx/c_src/wxe_ps_init.c
@@ -29,19 +29,8 @@
extern OSErr CPSSetProcessName (ProcessSerialNumber *psn, char *processname);
-void * wxe_ps_init()
+void * wxe_ps_init()
{
- ProcessSerialNumber psn;
- // Enable GUI
- if(!GetCurrentProcess(&psn)) {
- TransformProcessType(&psn, kProcessTransformToForegroundApplication);
-#ifdef MAC_OS_X_VERSION_10_6
- [[NSRunningApplication currentApplication] activateWithOptions:
- (NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
-#else
- SetFrontProcess(&psn);
-#endif
- }
return (void *) 0;
}
@@ -66,7 +55,6 @@ void * wxe_ps_init2() {
char * app_title;
size_t app_icon_len = 1023;
char app_icon_buf[1024];
- char * app_icon;
// Setup and enable gui
pool = [[NSAutoreleasePool alloc] init];
@@ -74,7 +62,7 @@ void * wxe_ps_init2() {
if( !is_packaged_app() ) {
// Undocumented function (but no documented way of doing this exists)
int res = erl_drv_getenv("WX_APP_TITLE", app_title_buf, &app_len);
- if (res >= 0) {
+ if (res == 0) {
app_title = app_title_buf;
} else {
app_title = NULL;
diff --git a/lib/wx/configure.in b/lib/wx/configure.in
index f35e6cdbd0..5a69738544 100644
--- a/lib/wx/configure.in
+++ b/lib/wx/configure.in
@@ -82,7 +82,7 @@ LM_WINDOWS_ENVIRONMENT
USER_CFLAGS=$CFLAGS
-if test X"$MIXED_CYGWIN_VC" = X"yes" -o X"$MIXED_MSYS_VC" = X"yes"; then
+if test X"$MIXED_VC" = X"yes" ; then
CFLAGS="-Owx"
fi
@@ -319,7 +319,7 @@ dnl
if test "$cross_compiling" = "yes"; then
echo "Cross compilation of the wx driver is not supported yet, wx will NOT be usable" >> ./CONF_INFO
WXERL_CAN_BUILD_DRIVER=false
-elif test X"$MIXED_CYGWIN_VC" = X"no" -a X"$MIXED_MSYS_VC" = X"no"; then
+elif test X"$MIXED_VC" = X"no"; then
WX_VERSION=`wx-config --version`
case $WX_VERSION in
2.8.*)
@@ -392,7 +392,7 @@ define(wx_warn_text,[
fi
else
AC_MSG_CHECKING(for wxWidgets in standard locations)
-
+ echo
# Check whether --with-wxdir was given.
AC_MSG_NOTICE(OptionCheck: [$with_wxdir $with_wx_prefix])
@@ -403,45 +403,40 @@ else
if test "${with_wx_prefix+set}" = set; then :
withval=$with_wx_prefix; CWXWIN0=$withval
else
- echo Setting it empty
CWXWIN0=""
fi
fi
- if test "x$MIXED_MSYS" = "xyes"; then
- CWXWIN_CONFIG=`win2msys_path.sh $wx_config_name 2>/dev/null`
- else
- CWXWIN_CONFIG=`cygpath $wx_config_name 2>/dev/null`
- fi
+ CWXWIN_CONFIG=`win32_path.sh -u $wx_config_name 2>/dev/null`
CWXWIN1=`dirname $CWXWIN_CONFIG 2>/dev/null`
CWXWIN2=`dirname $CWXWIN1 2>/dev/null`
if test -z "$PROGRAMFILES" ; then
- PROGRAMFILES=c:/Program Files
+ PROGRAMFILES="c:/Program Files"
fi
-
- if test "x$MIXED_MSYS" = "xyes"; then
- CWXWIN_PROG=`win2msys_path.sh "$PROGRAMFILES" 2>/dev/null`
- else
- CWXWIN_PROG=`cygpath -d "$PROGRAMFILES" | cygpath -f - 2>/dev/null`
- fi
+ CWXWIN_PROG=`win32_path.sh -u "$PROGRAMFILES" 2>/dev/null`
CWXWIN3="$CWXWIN_PROG/wxWidgets-3.*.* $CWXWIN_PROG/wxWidgets-2.*.*"
CWXWIN4="$CWXWIN_PROG/wxMSW-3.*.* $CWXWIN_PROG/wxMSW-2.*.*"
- DOC_OPT=/opt/local/pgm
- CWX_DOCUMENTED="$DOC_OPT/wxWidgets-2.*.* $DOC_OPT/wxMSW-2.*.*"
- CWX_DOCUMENTED="$DOC_OPT/wxWidgets-3.*.* $DOC_OPT/wxMSW-3.*.* $CWX_DOCUMENTED"
+ DOC_OPT1=/opt/local/pgm
+ DOC_OPT2=/mnt/c/opt/local/pgm
+ CWX_DOCUMENTED="$DOC_OPT1/wxWidgets-3.*.* $DOC_OPT1/wxMSW-3.*.*"
+ CWX_DOCUMENTED="$DOC_OPT2/wxWidgets-3.*.* $DOC_OPT2/wxMSW-3.*.* $CWX_DOCUMENTED"
case $ac_cv_sizeof_void_p in
8)
- DOC_OPT64=/opt/local64/pgm
- CWX_DOCUMENTED="$DOC_OPT64/wxWidgets-2.*.* $DOC_OPT64/wxMSW-2.*.* $CWX_DOCUMENTED"
- CWX_DOCUMENTED="$DOC_OPT64/wxWidgets-3.*.* $DOC_OPT64/wxMSW-3.*.* $CWX_DOCUMENTED"
+ DOC_OPT64_1=/opt/local64/pgm
+ DOC_OPT64_2=/mnt/c/opt/local64/pgm
+ CWX_DOCUMENTED="$DOC_OPT64_1/wxWidgets-3.*.* $DOC_OPT64_1/wxMSW-3.*.* $CWX_DOCUMENTED"
+ CWX_DOCUMENTED="$DOC_OPT64_2/wxWidgets-3.*.* $DOC_OPT64_2/wxMSW-3.*.* $CWX_DOCUMENTED"
;;
*)
- true
+ DOC_OPT3=/opt/local32/pgm
+ DOC_OPT4=/mnt/c/opt/local32/pgm
+ CWX_DOCUMENTED="$DOC_OPT3/wxWidgets-3.*.* $DOC_OPT3/wxMSW-3.*.* $CWX_DOCUMENTED"
+ CWX_DOCUMENTED="$DOC_OPT4/wxWidgets-3.*.* $DOC_OPT4/wxMSW-3.*.* $CWX_DOCUMENTED"
;;
esac
@@ -460,7 +455,7 @@ else
WX_RESCOMP="rc.sh -I$WXINCLUDE_PLAIN -D __WIN32__"
RC_FILE_TYPE=res
for lib in $WX_LIBDIR $WX_LIBDIR64; do
- maybe=`ls $lib/wxbase*.lib | egrep 'wxbase[[0-9]]*u\.lib'`
+ maybe=`ls $lib/wxbase*.lib 2> /dev/null | egrep 'wxbase[[0-9]]*u\.lib'`
if test '!' -z "$maybe"; then
corelib_number=`echo $maybe | sed 's,.*\([[0-9]].\)u\.lib,\1,'`
WX_LIBDIR=$lib
@@ -502,7 +497,36 @@ if test "$WXERL_CAN_BUILD_DRIVER" != "false"; then
AC_SUBST(WX_HAVE_STATIC_LIBS)
AC_SUBST(RC_FILE_TYPE)
-AC_MSG_CHECKING(if wxwidgets have opengl support)
+AC_MSG_CHECKING(for wxwidgets 2.8 compatibility )
+AC_LANG_PUSH(C++)
+saved_CXXFLAGS=$CXXFLAGS
+CXXFLAGS="$CXXFLAGS $WX_CXXFLAGS"
+
+AC_COMPILE_IFELSE(
+ [AC_LANG_PROGRAM([[
+ #ifdef WIN32
+ # include <windows.h>
+ #endif
+ #include "wx/wx.h"
+ ]], [[
+ #if wxMAJOR_VERSION > 2 && WXWIN_COMPATIBILITY_2_8 == 1
+ ;
+ #else
+ #error barf
+ #endif
+ ]])],
+ HAVE_COMPAT28_SUPPORT=yes, HAVE_COMPAT28_SUPPORT=no)
+
+CXXFLAGS=$saved_CXXFLAGS
+AC_LANG_POP(C++)
+AC_MSG_RESULT($HAVE_COMPAT28_SUPPORT)
+
+if test X"$HAVE_COMPAT28_SUPPORT" != X"yes" ; then
+ echo "wxWidgets was not compiled with --enable-compat28, wx will NOT be useable" >> ./CONF_INFO
+ WXERL_CAN_BUILD_DRIVER=false
+fi
+
+AC_MSG_CHECKING(for wxwidgets opengl support)
AC_LANG_PUSH(C++)
saved_CXXFLAGS=$CXXFLAGS
CXXFLAGS="$CXXFLAGS $WX_CXXFLAGS"
diff --git a/lib/wx/doc/src/Makefile b/lib/wx/doc/src/Makefile
index f66d63f63b..1d2b9824f4 100644
--- a/lib/wx/doc/src/Makefile
+++ b/lib/wx/doc/src/Makefile
@@ -37,10 +37,11 @@ ModsNoExt = $(ErlMods:%.erl=%) $(GenMods:%.erl=%)
# Release directory specification
XML_APPLICATION_FILES = ref_man.xml
-XML_REF3_FILES = $(ErlMods:%.erl=%.xml) $(GenMods:%.erl=%.xml)
+EDOC_REF3_FILES = $(ErlMods:%.erl=%.xml)
+XML_REF3_FILES = $(GenMods:%.erl=%.xml)
XML_PART_FILES = part.xml
-XML_CHAPTER_FILES = chapter.xml
+EDOC_CHAPTER_FILE = chapter.xml
XML_NOTES_FILES = notes.xml
BOOK_FILES = book.xml
@@ -50,101 +51,27 @@ XML_FILES = \
$(XML_PART_FILES) $(XML_NOTES_FILES)
XML_GEN_FILES = \
- $(XML_CHAPTER_FILES:%=$(XMLDIR)/%) \
- $(XML_REF3_FILES:%=$(XMLDIR)/%) \
- $(XML_APPLICATION_FILES:%=$(XMLDIR)/%)
+ $(XML_APPLICATION_FILES:%=$(XMLDIR)/%) \
+ $(XML_REF3_FILES:%=$(XMLDIR)/%)
-# ----------------------------------------------------
-INFO_FILE = ../../info
-
-HTML_FILES = \
- $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+EDOC_FLAGS=-i ../../src -preprocess true -sort_functions false
TOP_SPECS_FILE = specs.xml
# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-SPECS_FLAGS = -I../../include -I../../src
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
+include $(ERL_TOP)/make/doc.mk
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-xml: $(XML_REF3_FILES) $(XML_CHAPTER_FILES)
+$(XMLDIR)/%.xml: $(APP_SRC_DIR)/gen/%.erl
+ $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript \
+ -def vsn $(VSN) $(EDOC_FLAGS) -dir $(XMLDIR) $<
-ref_man.xml: ref_man.xml.src
+ref_man.xml: ref_man.xml.src Makefile
@echo Preparing $@
@cat ref_man.xml.src > $@
@for d in $(ModsNoExt); do \
echo " <xi:include href=\"$$d.xml\"/>" >> $@ ; \
done
@echo "</application>" >> $@
-
-$(ErlMods:%.erl=$(XMLDIR)/%.xml):
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript \
- -def vsn $(VSN) -preprocess true -sort_functions false -dir $(XMLDIR) \
- ../../src/$(@:$(XMLDIR)/%.xml=%.erl)
-
-$(GenMods:%.erl=$(XMLDIR)/%.xml):
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript \
- -def vsn $(VSN) -i ../../src -preprocess true -sort_functions false -dir $(XMLDIR) \
- ../../src/gen/$(@:$(XMLDIR)/%.xml=%.erl)
-
-$(XML_CHAPTER_FILES:%=$(XMLDIR)/%): ../overview.edoc
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript \
- -def vsn $(VSN) -chapter -dir $(XMLDIR) $<
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f $(SPECDIR)/*
- rm -f errs core *~ ../html/edoc-info
- rm -f $(XML_GEN_FILES) *.html
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3_FILES) "$(RELEASE_PATH)/man/man3"
-
-release_spec:
-
-release_tests_spec:
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index 7dcfbb1588..62f333f0c1 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -32,6 +32,66 @@
<p>This document describes the changes made to the wxErlang
application.</p>
+<section><title>Wx 1.9.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed wx initialization on mac, top level menus did not
+ always work on newer MacOS versions. The menues will not
+ work until wxWidgets-3.1.5 is released and used on these
+ MacOS versions.</p>
+ <p>
+ Own Id: OTP-17187</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Wx 1.9.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Add popup menu callback to <c>wxTaskBarIcon:new/1</c>.</p>
+ <p>
+ Own Id: OTP-16983 Aux Id: PR-2743 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Wx 1.9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix various compiler warnings on 64-bit Windows.</p>
+ <p>
+ Own Id: OTP-15800</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Refactored the internal handling of deprecated and
+ removed functions.</p>
+ <p>
+ Own Id: OTP-16469</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Wx 1.9</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/wx/examples/simple/menu.erl b/lib/wx/examples/simple/menu.erl
index 7c0400bd1e..a81a1a6efa 100644
--- a/lib/wx/examples/simple/menu.erl
+++ b/lib/wx/examples/simple/menu.erl
@@ -90,7 +90,10 @@
start() ->
Wx = wx:new(),
Frame = wx:batch(fun() -> create_frame(Wx) end),
+ Taskbar = wxTaskBarIcon:new([{createPopupMenu, fun() -> create_dummy_menu() end}]),
wxWindow:show(Frame),
+ Path = filename:dirname(code:which(?MODULE)),
+ wxTaskBarIcon:setIcon(Taskbar, wxIcon:new(filename:join(Path,"sample.xpm"), [{type, ?wxBITMAP_TYPE_XPM}])),
State = #state{},
diff --git a/lib/wx/src/gen/wxCalendarCtrl.erl b/lib/wx/src/gen/wxCalendarCtrl.erl
index 51116f601f..221de7bf69 100644
--- a/lib/wx/src/gen/wxCalendarCtrl.erl
+++ b/lib/wx/src/gen/wxCalendarCtrl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2019. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -78,7 +78,8 @@
update/1,updateWindowUI/1,updateWindowUI/2,validate/1,warpPointer/3]).
-export_type([wxCalendarCtrl/0]).
--deprecated([enableYearChange/1,enableYearChange/2]).
+-deprecated([{enableYearChange,1,"not available in wxWidgets-2.9 and later"},
+ {enableYearChange,2,"not available in wxWidgets-2.9 and later"}]).
%% @hidden
parent_class(wxControl) -> true;
diff --git a/lib/wx/src/gen/wxClientDC.erl b/lib/wx/src/gen/wxClientDC.erl
index 4965536690..a29ab8cab8 100644
--- a/lib/wx/src/gen/wxClientDC.erl
+++ b/lib/wx/src/gen/wxClientDC.erl
@@ -55,7 +55,7 @@
startPage/1]).
-export_type([wxClientDC/0]).
--deprecated([new/0]).
+-deprecated([{new,0,"not available in wxWidgets-2.9 and later"}]).
-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]).
diff --git a/lib/wx/src/gen/wxCursor.erl b/lib/wx/src/gen/wxCursor.erl
index 689935576a..7a9033dbee 100644
--- a/lib/wx/src/gen/wxCursor.erl
+++ b/lib/wx/src/gen/wxCursor.erl
@@ -36,7 +36,8 @@
saveFile/4,setDepth/2,setHeight/2,setMask/2,setPalette/2,setWidth/2]).
-export_type([wxCursor/0]).
--deprecated([new/3,new/4]).
+-deprecated([{new,3,"not available in wxWidgets-2.9 and later"},
+ {new,4,"not available in wxWidgets-2.9 and later"}]).
%% @hidden
parent_class(wxBitmap) -> true;
diff --git a/lib/wx/src/gen/wxDC.erl b/lib/wx/src/gen/wxDC.erl
index 16bfcc3463..322f80e4f0 100644
--- a/lib/wx/src/gen/wxDC.erl
+++ b/lib/wx/src/gen/wxDC.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@
-export([parent_class/1]).
-export_type([wxDC/0]).
--deprecated([computeScaleAndOrigin/1]).
+-deprecated([{computeScaleAndOrigin,1,"not available in wxWidgets-2.9 and later"}]).
%% @hidden
parent_class(_Class) -> erlang:error({badtype, ?MODULE}).
diff --git a/lib/wx/src/gen/wxGraphicsRenderer.erl b/lib/wx/src/gen/wxGraphicsRenderer.erl
index a1b67476e3..2e3e21bf50 100644
--- a/lib/wx/src/gen/wxGraphicsRenderer.erl
+++ b/lib/wx/src/gen/wxGraphicsRenderer.erl
@@ -33,7 +33,8 @@
-export([parent_class/1]).
-export_type([wxGraphicsRenderer/0]).
--deprecated([createLinearGradientBrush/7,createRadialGradientBrush/8]).
+-deprecated([{createLinearGradientBrush,7,"not available in wxWidgets-2.9 and later"},
+ {createRadialGradientBrush,8,"not available in wxWidgets-2.9 and later"}]).
%% @hidden
parent_class(_Class) -> erlang:error({badtype, ?MODULE}).
diff --git a/lib/wx/src/gen/wxGridCellEditor.erl b/lib/wx/src/gen/wxGridCellEditor.erl
index d14b1c994c..c4a730ed8b 100644
--- a/lib/wx/src/gen/wxGridCellEditor.erl
+++ b/lib/wx/src/gen/wxGridCellEditor.erl
@@ -32,7 +32,8 @@
-export([parent_class/1]).
-export_type([wxGridCellEditor/0]).
--deprecated([endEdit/4,paintBackground/3]).
+-deprecated([{endEdit,4,"not available in wxWidgets-2.9 and later"},
+ {paintBackground,3,"not available in wxWidgets-2.9 and later"}]).
%% @hidden
parent_class(_Class) -> erlang:error({badtype, ?MODULE}).
diff --git a/lib/wx/src/gen/wxIdleEvent.erl b/lib/wx/src/gen/wxIdleEvent.erl
index 1cb3e34ef6..d23943805e 100644
--- a/lib/wx/src/gen/wxIdleEvent.erl
+++ b/lib/wx/src/gen/wxIdleEvent.erl
@@ -39,7 +39,7 @@
resumePropagation/2,shouldPropagate/1,skip/1,skip/2,stopPropagation/1]).
-export_type([wxIdleEvent/0]).
--deprecated([canSend/1]).
+-deprecated([{canSend,1,"not available in wxWidgets-2.9 and later"}]).
%% @hidden
parent_class(wxEvent) -> true;
diff --git a/lib/wx/src/gen/wxMDIClientWindow.erl b/lib/wx/src/gen/wxMDIClientWindow.erl
index 7eabc7d99a..cd603aa08b 100644
--- a/lib/wx/src/gen/wxMDIClientWindow.erl
+++ b/lib/wx/src/gen/wxMDIClientWindow.erl
@@ -72,7 +72,8 @@
update/1,updateWindowUI/1,updateWindowUI/2,validate/1,warpPointer/3]).
-export_type([wxMDIClientWindow/0]).
--deprecated([new/1,new/2]).
+-deprecated([{new,1,"not available in wxWidgets-2.9 and later"},
+ {new,2,"not available in wxWidgets-2.9 and later"}]).
%% @hidden
parent_class(wxWindow) -> true;
diff --git a/lib/wx/src/gen/wxPaintDC.erl b/lib/wx/src/gen/wxPaintDC.erl
index b571219020..bb61511302 100644
--- a/lib/wx/src/gen/wxPaintDC.erl
+++ b/lib/wx/src/gen/wxPaintDC.erl
@@ -55,7 +55,7 @@
startPage/1]).
-export_type([wxPaintDC/0]).
--deprecated([new/0]).
+-deprecated([{new,0,"not available in wxWidgets-2.9 and later"}]).
-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]).
diff --git a/lib/wx/src/gen/wxPostScriptDC.erl b/lib/wx/src/gen/wxPostScriptDC.erl
index 07764bca74..eb315341f1 100644
--- a/lib/wx/src/gen/wxPostScriptDC.erl
+++ b/lib/wx/src/gen/wxPostScriptDC.erl
@@ -54,7 +54,8 @@
startPage/1]).
-export_type([wxPostScriptDC/0]).
--deprecated([getResolution/0,setResolution/1]).
+-deprecated([{getResolution,0,"not available in wxWidgets-2.9 and later"},
+ {setResolution,1,"not available in wxWidgets-2.9 and later"}]).
-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]).
diff --git a/lib/wx/src/gen/wxTaskBarIcon.erl b/lib/wx/src/gen/wxTaskBarIcon.erl
index af4859fb88..2f06d906ea 100644
--- a/lib/wx/src/gen/wxTaskBarIcon.erl
+++ b/lib/wx/src/gen/wxTaskBarIcon.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
-module(wxTaskBarIcon).
-include("wxe.hrl").
--export([destroy/1,new/0,popupMenu/2,removeIcon/1,setIcon/2,setIcon/3]).
+-export([ new/0, new/1 ,destroy/1,popupMenu/2,removeIcon/1,setIcon/2,setIcon/3]).
%% inherited exports
-export([connect/2,connect/3,disconnect/1,disconnect/2,disconnect/3,parent_class/1]).
@@ -39,12 +39,28 @@ parent_class(wxEvtHandler) -> true;
parent_class(_Class) -> erlang:error({badtype, ?MODULE}).
-type wxTaskBarIcon() :: wx:wx_object().
+
%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconwxtaskbaricon">external documentation</a>.
-spec new() -> wxTaskBarIcon().
new() ->
- wxe_util:construct(?wxTaskBarIcon_new,
- <<>>).
+ wxe_util:construct(?wxTaskBarIcon_new, <<0:32>>).
+
+%% @doc Creates a TaskBarIcon with a callback function for CreatePopupMenu:
+%% <pre>Callback() -> term()</pre>
+%%
+%% See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconwxtaskbaricon">external documentation</a>.
+-spec new([Option]) -> wxTaskBarIcon() when
+ Option :: {'createPopupMenu', fun(() -> wxMenu:wxMenu())}.
+new([]) ->
+ new();
+new([{createPopupMenu, F}]) when is_function(F) ->
+ Fun = fun([_]) ->
+ #wx_ref{type=wxMenu,ref=ThisRef} = F(),
+ <<ThisRef:32/?UI>>
+ end,
+ BinFun = <<(wxe_util:get_cbId(Fun)):32/?UI, 0:32>>,
+ wxe_util:construct(?wxTaskBarIcon_new, BinFun).
%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxtaskbaricon.html#wxtaskbariconpopupmenu">external documentation</a>.
-spec popupMenu(This, Menu) -> boolean() when
This::wxTaskBarIcon(), Menu::wxMenu:wxMenu().
diff --git a/lib/wx/src/gen/wxWindowDC.erl b/lib/wx/src/gen/wxWindowDC.erl
index 9b1045b84d..04856786a8 100644
--- a/lib/wx/src/gen/wxWindowDC.erl
+++ b/lib/wx/src/gen/wxWindowDC.erl
@@ -54,7 +54,7 @@
startPage/1]).
-export_type([wxWindowDC/0]).
--deprecated([new/0]).
+-deprecated([{new,0,"not available in wxWidgets-2.9 and later"}]).
-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]).
diff --git a/lib/wx/src/wx_object.erl b/lib/wx/src/wx_object.erl
index 181206c762..81d188b26a 100644
--- a/lib/wx/src/wx_object.erl
+++ b/lib/wx/src/wx_object.erl
@@ -117,12 +117,16 @@
start_link/3, start_link/4,
stop/1, stop/3,
call/2, call/3,
+ send_request/2, wait_response/1, wait_response/2, check_response/2,
cast/2,
reply/2,
get_pid/1,
set_pid/2
]).
+-type request_id() :: term().
+-type server_ref() :: Obj::wx:wx_object()|atom()|pid().
+
%% -export([behaviour_info/1]).
-callback init(Args :: term()) ->
{#wx_ref{}, State :: term()} | {#wx_ref{}, State :: term(), timeout() | 'hibernate'} |
@@ -317,6 +321,34 @@ call(Name, Request, Timeout) when is_atom(Name) orelse is_pid(Name) ->
erlang:error({Reason, {?MODULE, call, [Name, Request, Timeout]}})
end.
+%% @doc Make an send_request to a generic server.
+%% and return a RequestId which can/should be used with wait_response/[1|2].
+%% Invokes handle_call(Request, From, State) in server.
+-spec send_request(Obj, Request::term()) -> request_id() when
+ Obj::wx:wx_object()|atom()|pid().
+send_request(#wx_ref{state=Pid}, Request) ->
+ gen:send_request(Pid, '$gen_call', Request);
+send_request(Pid, Request) when is_atom(Pid) orelse is_pid(Pid) ->
+ gen:send_request(Pid, '$gen_call', Request).
+
+%% @doc Wait infinitely for a reply from a generic server.
+-spec wait_response(RequestId::request_id()) ->
+ {reply, Reply::term()} | {error, {term(), server_ref()}}.
+wait_response(RequestId) ->
+ gen:wait_response(RequestId, infinity).
+
+%% @doc Wait 'timeout' for a reply from a generic server.
+-spec wait_response(Key::request_id(), timeout()) ->
+ {reply, Reply::term()} | 'timeout' | {error, {term(), server_ref()}}.
+wait_response(RequestId, Timeout) ->
+ gen:wait_response(RequestId, Timeout).
+
+%% @doc Check if a received message was a reply to a RequestId
+-spec check_response(Msg::term(), Key::request_id()) ->
+ {reply, Reply::term()} | 'false' | {error, {term(), server_ref()}}.
+check_response(Msg, RequestId) ->
+ gen:check_response(Msg, RequestId).
+
%% @doc Make a cast to a wx_object server.
%% Invokes handle_cast(Request, State) in the server
-spec cast(Obj, Request) -> ok when
diff --git a/lib/wx/test/wx_basic_SUITE.erl b/lib/wx/test/wx_basic_SUITE.erl
index ad03a378de..433b705673 100644
--- a/lib/wx/test/wx_basic_SUITE.erl
+++ b/lib/wx/test/wx_basic_SUITE.erl
@@ -394,10 +394,19 @@ wx_object(Config) ->
{call, {Frame,Panel}, _} = wx_object:call(Frame, fun(US) -> US end),
?m(false, wxWindow:getParent(Panel) =:= Frame),
?m(true, wx:equal(wxWindow:getParent(Panel),Frame)),
+ flush(),
+ ReqId = wx_object:send_request(Frame, fun(_US) -> timer:sleep(10), yes end),
+ timeout = wx_object:wait_response(ReqId, 0),
+ {reply, {call, yes, {Me,{_,ReqId}}}} = wx_object:wait_response(ReqId, 1000),
+ ReqId2 = wx_object:send_request(Frame, yes),
+ [Msg] = flush(),
+ no_reply = wx_object:check_response(Msg, ReqId),
+ {reply, {call, yes, {Me,{_,ReqId2}}}} = wx_object:check_response(Msg, ReqId2),
+
FramePid = wx_object:get_pid(Frame),
io:format("wx_object pid ~p~n",[FramePid]),
FramePid ! foo3,
- ?m([{info, foo3}|_], flush()),
+ ?m([{info, foo3}], flush()),
?m(ok, wx_object:cast(Frame, fun(_) -> hehe end)),
?m([{cast, hehe}|_], flush()),
diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk
index b498d21f3f..42776cc17a 100644
--- a/lib/wx/vsn.mk
+++ b/lib/wx/vsn.mk
@@ -1 +1 @@
-WX_VSN = 1.9
+WX_VSN = 1.9.3
diff --git a/lib/xmerl/Makefile b/lib/xmerl/Makefile
index a584aacbac..65a0af9e4a 100644
--- a/lib/xmerl/Makefile
+++ b/lib/xmerl/Makefile
@@ -50,12 +50,7 @@ SPECIAL_TARGETS =
include $(ERL_TOP)/make/otp_subdir.mk
-.PHONY: info version
-
-info:
- @echo "APP_RELEASE_DIR: $(APP_RELEASE_DIR)"
- @echo "APP_DIR: $(APP_DIR)"
- @echo "APP_TAR_FILE: $(APP_TAR_FILE)"
+.PHONY: version
version:
@echo "$(VSN)"
@@ -97,3 +92,4 @@ tar: $(APP_TAR_FILE)
$(APP_TAR_FILE): $(APP_DIR)
(cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME))
+include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/xmerl/doc/src/Makefile b/lib/xmerl/doc/src/Makefile
index 0def492246..5effc831e2 100644
--- a/lib/xmerl/doc/src/Makefile
+++ b/lib/xmerl/doc/src/Makefile
@@ -28,33 +28,19 @@ VSN=$(XMERL_VSN)
APPLICATION=xmerl
# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-# ----------------------------------------------------
-# Help application directory specification
-# ----------------------------------------------------
-
-EDOC_DIR = $(ERL_TOP)/lib/edoc
-SYNTAX_TOOLS_DIR = $(ERL_TOP)/lib/syntax_tools
-
-# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
XMERL_DIR = $(ERL_TOP)/lib/$(APPLICATION)/src
-XMERL_MODULES = \
- xmerl_scan \
- xmerl \
- xmerl_xs \
- xmerl_eventp \
- xmerl_xpath \
- xmerl_xsd
-
+EDOC_REF3_FILES = \
+ xmerl_scan.xml \
+ xmerl.xml \
+ xmerl_xs.xml \
+ xmerl_eventp.xml \
+ xmerl_xpath.xml \
+ xmerl_xsd.xml
XML_APPLICATION_FILES = ref_man.xml
-XMERL_XML_FILES = $(XMERL_MODULES:%=$(XMLDIR)/%.xml)
XML_REF3_FILES = xmerl_sax_parser.xml
@@ -85,97 +71,15 @@ EXAMPLE_FILES = people2.txt people.txt motorcycles.txt motorcycles_dtd.txt \
new_motorcycles.txt new_motorcycles2.txt result_export.html \
motorcycles2.txt result_xs.html motorcycles2html.erl
+HTML_EXTRA_FILES = $(EXAMPLE_FILES) $(HTML_EXAMPLE_FILES) $(HTML_STYLESHEET_FILES)
+
XML_FILES= \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES)
-XML_GEN_FILES = $(XMERL_XML_FILES) $(XML_CHAPTER_GEN_FILES)
-
-# ----------------------------------------------------
-INFO_FILE = ../../info
-
-HTML_FILES = $(XML_REF_MAN:%.xml=$(HTMLDIR)/%.html) \
- $(XML_HTML_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) $(XMERL_MODULES:%=$(MAN3DIR)/%.3)
-MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-$(XMERL_XML_FILES):
- $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -dir $(XMLDIR) $(XMERL_DIR)/$(@:$(XMLDIR)/%.xml=%.erl)
-
-man: $(MAN3_FILES) $(MAN6_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-xml: $(XMERL_XML_FILES)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(MAN6DIR)/*
- rm -f $(XMERL_XML_FILES)
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-
-info:
- @echo "XML_PART_FILES: $(XML_PART_FILES)"
- @echo "XML_APPLICATION_FILES: $(XML_APPLICATION_FILES)"
- @echo "XMERL_XML_FILES: $(XMERL_XML_FILES)"
- @echo "XMERL_MODULES: $(XMERL_MODULES)"
- @echo "HTML_FILES: $(HTML_FILES)"
- @echo "HTMLDIR: $(HTMLDIR)"
- @echo "DEFAULT_GIF_FILES: $(DEFAULT_GIF_FILES)"
- @echo "DEFAULT_HTML_FILES: $(DEFAULT_HTML_FILES)"
+XML_GEN_FILES = $(XML_CHAPTER_GEN_FILES)
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(EXAMPLE_FILES) $(HTML_EXAMPLE_FILES) $(HTML_STYLESHEET_FILES) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
-
-
-
-release_tests_spec:
+include $(ERL_TOP)/make/doc.mk
diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml
index 997af9d037..143111e3d1 100644
--- a/lib/xmerl/doc/src/notes.xml
+++ b/lib/xmerl/doc/src/notes.xml
@@ -32,6 +32,45 @@
<p>This document describes the changes made to the Xmerl application.</p>
+<section><title>Xmerl 1.3.26</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Corrected namespace and expanded name in the old dom
+ backend example module.</p>
+ <p>
+ Own Id: OTP-17060</p>
+ </item>
+ <item>
+ <p>
+ Corrected a bug that in some cases didn't allow
+ unresolved references when skip_external_dtd option used.</p>
+ <p>
+ Own Id: OTP-17061</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Xmerl 1.3.25</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug that the function name didn't get
+ normalized in some case which left white spaces in links.
+ </p>
+ <p>
+ Own Id: OTP-16617</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Xmerl 1.3.24</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/xmerl/doc/src/xmerl_sax_parser.xml b/lib/xmerl/doc/src/xmerl_sax_parser.xml
index 2390779028..6fbc2199c6 100644
--- a/lib/xmerl/doc/src/xmerl_sax_parser.xml
+++ b/lib/xmerl/doc/src/xmerl_sax_parser.xml
@@ -56,7 +56,7 @@
<taglist>
<tag><c>{continuation_fun, ContinuationFun}</c></tag>
<item>
- <seealso marker="#ContinuationFun/1">ContinuationFun</seealso> is a call back function to decide what to do if
+ <seemfa marker="#Module:ContinuationFun/1">ContinuationFun</seemfa> is a call back function to decide what to do if
the parser runs into EOF before the document is complete.
</item>
<tag><c>{continuation_state, term()}</c></tag>
@@ -65,7 +65,7 @@
</item>
<tag><c>{event_fun, EventFun}</c></tag>
<item>
- <seealso marker="#EventFun/3">EventFun</seealso> is the call back function for parser events.
+ <seemfa marker="#Module:EventFun/3">EventFun</seemfa> is the call back function for parser events.
</item>
<tag><c>{event_state, term()}</c></tag>
<item>
@@ -370,18 +370,19 @@
</funcs>
- <section>
- <title>CALLBACK FUNCTIONS</title>
- <p>
- The callback interface is based on that the user sends a fun with the
- correct signature to the parser.
- </p>
- </section>
+
<funcs>
+ <fsdescription>
+ <title>CALLBACK FUNCTIONS</title>
+ <p>
+ The callback interface is based on that the user sends a fun with the
+ correct signature to the parser.
+ </p>
+ </fsdescription>
<func>
- <name since="">ContinuationFun(State) -> {NewBytes, NewState}</name>
+ <name since="">Module:ContinuationFun(State) -> {NewBytes, NewState}</name>
<fsummary>Continuation call back function.</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -402,7 +403,7 @@
</func>
<func>
- <name since="">EventFun(Event, Location, State) -> NewState</name>
+ <name since="">Module:EventFun(Event, Location, State) -> NewState</name>
<fsummary>Event call back function.</fsummary>
<type>
<v>Event = event()</v>
diff --git a/lib/xmerl/doc/src/xmerl_ug.xmlsrc b/lib/xmerl/doc/src/xmerl_ug.xmlsrc
index e4aeaddc78..dee8166d90 100644
--- a/lib/xmerl/doc/src/xmerl_ug.xmlsrc
+++ b/lib/xmerl/doc/src/xmerl_ug.xmlsrc
@@ -210,7 +210,7 @@ Grand Danois\
<p>If you instead receives the XML doc as a string you can
parse it by <c>xmerl_scan:string/1</c>. Both file/2 and string/2
exists where the second argument is a list of options to the
- parser, see the <seealso marker="xmerl_scan">reference manual</seealso>.</p>
+ parser, see the <seeerl marker="xmerl_scan">reference manual</seeerl>.</p>
</section>
<section>
@@ -280,7 +280,7 @@ Grand Danois\
<item>Value = IOString | atom() | integer()</item>
</list>
<p>See also reference manual for
- <seealso marker="xmerl#export_simple-3">xmerl</seealso></p>
+ <seeerl marker="xmerl#export_simple-3">xmerl</seeerl></p>
<p>If you want to add the information about a black Harley
Davidsson 1200 cc Sportster motorcycle from 2003 that is in
shape as new in the motorcycles.xml document you can put the
@@ -363,7 +363,7 @@ Data =
<section>
<title>Example: Transforming XML To HTML</title>
- <p>Assume that you want to transform the <seealso marker="#motorcyclesxml">motorcycles.xml</seealso> document to
+ <p>Assume that you want to transform the <seeguide marker="#motorcyclesxml">motorcycles.xml</seeguide> document to
HTML. If you want the same structure and tags of the resulting
HTML document as of the XML document then you can use the
<c>xmerl:export/2</c> function. The following:</p>
@@ -408,7 +408,7 @@ Data =
formats.</p>
<p><c>xmerl_xs</c> does not implement the entire XSLT
specification but the basic functionality. For all details see
- the <seealso marker="xmerl_xs">reference manual</seealso></p>
+ the <seeerl marker="xmerl_xs">reference manual</seeerl></p>
<p>First, some words about the xmerl_xs functionality:</p>
<p>You need to write template functions to be able to control
what kind of output you want. Thus if you want to encapsulate a
diff --git a/lib/xmerl/src/xmerl_sax_old_dom.erl b/lib/xmerl/src/xmerl_sax_old_dom.erl
index 6d0d836487..b3b2727082 100644
--- a/lib/xmerl/src/xmerl_sax_old_dom.erl
+++ b/lib/xmerl/src/xmerl_sax_old_dom.erl
@@ -1,9 +1,9 @@
%%-*-erlang-*-
%%--------------------------------------------------------------------
%% %CopyrightBegin%
-%%
+%%
%% Copyright Ericsson AB 2009-2017. All Rights Reserved.
-%%
+%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
@@ -15,13 +15,13 @@
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-%%
+%%
%% %CopyrightEnd%
%%----------------------------------------------------------------------
%% File : xmerl_sax_old_dom.erl
-%% Description :
+%% Description :
%%
-%% Created : 02 Oct 2008
+%% Created : 02 Oct 2008
%%----------------------------------------------------------------------
-module(xmerl_sax_old_dom).
@@ -52,7 +52,7 @@
%%----------------------------------------------------------------------
%% Error handling
%%----------------------------------------------------------------------
--define(error(Reason),
+-define(error(Reason),
throw({xmerl_sax_old_dom_error, Reason})).
%%======================================================================
@@ -66,7 +66,7 @@
tags=[], %% Tag stack
cno=[], %% Current node number
namespaces = [], %% NameSpace stack
- dom=[] %% DOM structure
+ dom=[] %% DOM structure
}).
%%======================================================================
@@ -74,8 +74,8 @@
%%======================================================================
%%----------------------------------------------------------------------
%% Function: initial_state() -> Result
-%% Parameters:
-%% Result:
+%% Parameters:
+%% Result:
%% Description:
%%----------------------------------------------------------------------
initial_state() ->
@@ -83,8 +83,8 @@ initial_state() ->
%%----------------------------------------------------------------------
%% Function: get_dom(State) -> Result
-%% Parameters:
-%% Result:
+%% Parameters:
+%% Result:
%% Description:
%%----------------------------------------------------------------------
get_dom(#xmerl_sax_old_dom_state{dom=Dom}) ->
@@ -92,8 +92,8 @@ get_dom(#xmerl_sax_old_dom_state{dom=Dom}) ->
%%----------------------------------------------------------------------
%% Function: event(Event, LineNo, State) -> Result
-%% Parameters:
-%% Result:
+%% Parameters:
+%% Result:
%% Description:
%%----------------------------------------------------------------------
event(Event, _LineNo, State) ->
@@ -109,14 +109,14 @@ event(Event, _LineNo, State) ->
%% Parameters: Event = term()
%% State = #xmerl_sax_old_dom_state{}
%% Result : #xmerl_sax_old_dom_state{} |
-%% Description:
+%% Description:
%%----------------------------------------------------------------------
-%% Document
%%----------------------------------------------------------------------
+%% Document
build_dom(startDocument, State) ->
State#xmerl_sax_old_dom_state{dom=[startDocument]};
-build_dom(endDocument,
+build_dom(endDocument,
#xmerl_sax_old_dom_state{dom=[#xmlElement{content=C} = Current |D]} = State) ->
case D of
[startDocument] ->
@@ -127,18 +127,17 @@ build_dom(endDocument,
State#xmerl_sax_old_dom_state{dom=[Decl, Current#xmlElement{
content=lists:reverse(C)
}]};
- _ ->
- %% endDocument is also sent by the parser when a fault occur to tell
+ _ ->
+ %% endDocument is also sent by the parser when a fault occur to tell
%% the event receiver that no more input will be sent
State
end;
-%% Element
%%----------------------------------------------------------------------
-build_dom({startElement, Uri, LocalName, QName, Attributes},
- #xmerl_sax_old_dom_state{tags=T, cno=CN, namespaces=NS, dom=D} = State) ->
+%% Element
+build_dom({startElement, Uri, LocalName, QName, Attributes},
+ #xmerl_sax_old_dom_state{tags=T, cno=CN, namespaces=NS0, dom=D} = State) ->
- A = parse_attributes(LocalName, Attributes),
{Num, NewCN} =
case CN of
[] ->
@@ -147,32 +146,43 @@ build_dom({startElement, Uri, LocalName, QName, Attributes},
{N, [1, N+1 |CNs]}
end,
- NsInfo =
+ NsInfo =
case QName of
{[], _} -> [];
QN -> QN
end,
- NameAsAtom = convert_qname_to_atom(QName),
+ NameAsAtom = convert_qname_to_atom(QName),
+ ExpandedName = convert_to_expanded_name(Uri, LocalName),
+ DefaultNS =
+ case lists:keyfind([], 1, NS0) of
+ false ->
+ [];
+ {_, Default} -> Default
+ end,
+ NS1 = lists:filter(fun({[], _}) -> false; ({_,_}) -> true end, NS0),
+ NameSpace = #xmlNamespace{default=DefaultNS, nodes=NS1},
+ NewTagsList = [{NameAsAtom, Num} |T],
- State#xmerl_sax_old_dom_state{tags=[{NameAsAtom, Num} |T],
+ A = parse_attributes(Attributes, LocalName, NameSpace, NewTagsList),
+
+ State#xmerl_sax_old_dom_state{tags=NewTagsList,
cno=NewCN,
- dom=[#xmlElement{name=NameAsAtom,
- expanded_name=NameAsAtom,
+ dom=[#xmlElement{name=NameAsAtom,
+ expanded_name=ExpandedName,
nsinfo=NsInfo,
- namespace=#xmlNamespace{default=list_to_atom(Uri),
- nodes=NS},
+ namespace=NameSpace,
pos=Num,
parents=T,
attributes=lists:reverse(A),
xmlbase="."
} | D]};
-build_dom({endElement, _Uri, LocalName, QName},
+build_dom({endElement, _Uri, LocalName, QName},
#xmerl_sax_old_dom_state{tags=[_ |T],
cno=[_ |CN],
- dom=[#xmlElement{name=CName, content=C} = Current,
+ dom=[#xmlElement{name=CName, content=C} = Current,
#xmlElement{content=PC} = Parent | D]} = State) ->
case convert_qname_to_atom(QName) of
- CName ->
+ CName ->
State#xmerl_sax_old_dom_state{tags=T,
cno=CN,
dom=[Parent#xmlElement{
@@ -182,51 +192,49 @@ build_dom({endElement, _Uri, LocalName, QName},
|PC]
} | D]};
_ ->
- ?error("Got end of element: " ++ LocalName ++ " but expected: " ++
+ ?error("Got end of element: " ++ LocalName ++ " but expected: " ++
Current#xmlElement.name)
end;
-%% Text
%%----------------------------------------------------------------------
+%% Text
build_dom({characters, String},
- #xmerl_sax_old_dom_state{tags=T,
+ #xmerl_sax_old_dom_state{tags=T,
cno=[Num |CN],
dom=[#xmlElement{content=C} = Current| D]} = State) ->
- State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
+ State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
dom=[Current#xmlElement{content=[#xmlText{value=String, parents=T, pos=Num, type=text}
|C]} | D]};
build_dom({ignorableWhitespace, String},
- #xmerl_sax_old_dom_state{tags=T,
+ #xmerl_sax_old_dom_state{tags=T,
cno=[Num |CN],
dom=[#xmlElement{content=C} = Current| D]} = State) ->
State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
- dom=[Current#xmlElement{content=[#xmlText{value=String,
- parents=T, pos=Num,
+ dom=[Current#xmlElement{content=[#xmlText{value=String,
+ parents=T, pos=Num,
type=text}
|C]} | D]};
-%% Comments
%%----------------------------------------------------------------------
+%% Comments
build_dom({comment, String},
- #xmerl_sax_old_dom_state{tags=T,
+ #xmerl_sax_old_dom_state{tags=T,
cno=[Num |CN],
dom=[#xmlElement{content=C} = Current| D]} = State) ->
State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
dom=[Current#xmlElement{content=[#xmlComment{parents=T, pos=Num, value=String}|C]} | D]};
-%% NameSpaces
%%----------------------------------------------------------------------
-build_dom({startPrefixMapping, [], _Uri}, State) ->
- State;
+%% NameSpaces
build_dom({startPrefixMapping, Prefix, Uri},
- #xmerl_sax_old_dom_state{namespaces=NS} = State) ->
+ #xmerl_sax_old_dom_state{namespaces=NS} = State) ->
State#xmerl_sax_old_dom_state{namespaces=[{Prefix, list_to_atom(Uri)} |NS]};
build_dom({endPrefixMapping, Prefix},
- #xmerl_sax_old_dom_state{namespaces=[{Prefix, _} |NS]} = State) ->
- State#xmerl_sax_old_dom_state{namespaces=NS};
+ #xmerl_sax_old_dom_state{namespaces=NS} = State) ->
+ State#xmerl_sax_old_dom_state{namespaces=lists:keydelete(Prefix, 1, NS)};
-%% Processing instructions
%%----------------------------------------------------------------------
+%% Processing instructions
build_dom({processingInstruction,"xml", PiData},
#xmerl_sax_old_dom_state{dom=D} = State) ->
{Vsn, PiData1} = find_and_remove_attribute("version", PiData, []),
@@ -236,44 +244,42 @@ build_dom({processingInstruction,"xml", PiData},
build_dom({processingInstruction, PiTarget, PiData},
#xmerl_sax_old_dom_state{cno=[Num |CN],
dom=[#xmlElement{content=C} = Current| D]} = State) ->
- State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
+ State#xmerl_sax_old_dom_state{cno=[Num+1 |CN],
dom=[Current#xmlElement{content=[#xmlPI{name=PiTarget,pos=Num, value=PiData}
|C]} | D]};
-%% Default
%%----------------------------------------------------------------------
+%% Default
build_dom(_E, State) ->
- State.
-
+ State.
%%----------------------------------------------------------------------
-%% Function : parse_attributes(ElName, Attributes) -> Result
-%% Parameters:
-%% Result :
-%% Description:
+%% Function : parse_attributes/2
%%----------------------------------------------------------------------
-parse_attributes(ElName, Attributes) ->
- parse_attributes(ElName, Attributes, 1, []).
+parse_attributes(Attributes, ElName, NameSpace, T) ->
+ parse_attributes(Attributes, ElName, NameSpace, T, 1 , []).
-parse_attributes(_, [], _, Acc) ->
+parse_attributes([], _, _, _, _, Acc) ->
Acc;
-parse_attributes(ElName, [{_Uri, Prefix, LocalName, AttrValue} |As], N, Acc) ->
+parse_attributes([{Uri, Prefix, LocalName, AttrValue} |As], ElName, NameSpace, T, N, Acc) ->
Name = convert_qname_to_atom({Prefix,LocalName}),
- NsInfo =
+ NsInfo =
case Prefix of
[] -> [];
P -> {P,LocalName}
end,
- parse_attributes(ElName, As, N+1, [#xmlAttribute{name=Name,
- pos=N,
- nsinfo=NsInfo,
- value=AttrValue,
- normalized=false} |Acc]).
+ ExpandedName = convert_to_expanded_name(Uri, LocalName),
+ parse_attributes(As, ElName, NameSpace, T, N+1,
+ [#xmlAttribute{name=Name,
+ expanded_name=ExpandedName,
+ nsinfo=NsInfo,
+ namespace=NameSpace,
+ parents=T,
+ pos=N,
+ value=AttrValue,
+ normalized=false} |Acc]).
%%----------------------------------------------------------------------
%% Function : convert_qname_to_atom(QName) -> Result
-%% Parameters:
-%% Result :
-%% Description:
%%----------------------------------------------------------------------
convert_qname_to_atom({[], N}) ->
list_to_atom(N);
@@ -281,10 +287,15 @@ convert_qname_to_atom({P,N}) ->
list_to_atom(P ++ ":" ++ N).
%%----------------------------------------------------------------------
-%% Function : find_and_remove_attribute(Key, Data, Default) -> Result
-%% Parameters:
-%% Result :
-%% Description:
+%% Function : convert_to_expanded_name/2
+%%----------------------------------------------------------------------
+convert_to_expanded_name([], LocalName) ->
+ list_to_atom(LocalName);
+convert_to_expanded_name(Uri, LocalName) ->
+ {list_to_atom(Uri), list_to_atom(LocalName)}.
+
+%%----------------------------------------------------------------------
+%% Function : find_and_remove_attribute/3
%%----------------------------------------------------------------------
find_and_remove_attribute(Key, Data, Default) ->
case lists:keysearch(Key, 1, Data) of
diff --git a/lib/xmerl/src/xmerl_sax_parser_base.erlsrc b/lib/xmerl/src/xmerl_sax_parser_base.erlsrc
index 297ef484fd..3dfdec440a 100644
--- a/lib/xmerl/src/xmerl_sax_parser_base.erlsrc
+++ b/lib/xmerl/src/xmerl_sax_parser_base.erlsrc
@@ -1209,7 +1209,12 @@ parse_att_value(?STRING_REST("&", Rest), State, Stop, Acc) ->
{external_general, Name, _} ->
?fatal_error(State1, "External parsed entity reference in attribute value: " ++ Name);
{not_found, Name} when State#xmerl_sax_parser_state.file_type =:= normal ->
- ?fatal_error(State1, "Undeclared reference: " ++ Name);
+ case State1#xmerl_sax_parser_state.skip_external_dtd of
+ false ->
+ ?fatal_error(State1, "Undeclared reference: " ++ Name);
+ true ->
+ parse_att_value(Rest1, State1, Stop, ";" ++ lists:reverse(Name) ++ "&" ++ Acc)
+ end;
{not_found, Name} ->
parse_att_value(Rest1, State1, Stop, ";" ++ lists:reverse(Name) ++ "&" ++ Acc);
{unparsed, Name, _} ->
diff --git a/lib/xmerl/test/xmerl_SUITE.erl b/lib/xmerl/test/xmerl_SUITE.erl
index 66f04f6c25..bc133558d6 100644
--- a/lib/xmerl/test/xmerl_SUITE.erl
+++ b/lib/xmerl/test/xmerl_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -241,7 +241,7 @@ default_attrs_bug(Config) ->
ok.
-xml_ns(Config) ->
+xml_ns(_Config) ->
Doc = "<?xml version='1.0'?>\n"
"<doc xml:attr1=\"implicit xml ns\"/>",
{#xmlElement{namespace=#xmlNamespace{default = [], nodes = []},
@@ -326,8 +326,9 @@ sax_parse_export_xml_big(Config) ->
{ok,IO} = file:open(OutFile,[write,append]),
ok = file:write(IO,Bin),
ok = io:format(IO,"~s~n~n",[lists:flatten(Ex)]),
- Cmd = lists:flatten(io_lib:format("cmp ~s ~s",[OutFile,CMOMxml])),
- [] = os:cmd(Cmd),
+ ok = file:close(IO),
+ {ok,CmpBin1} = file:read_file(OutFile),
+ {ok,CmpBin1} = file:read_file(CMOMxml),
ok.
sax_parse_export_xml_small(Config) ->
@@ -342,11 +343,11 @@ sax_parse_export_xml_small(Config) ->
{ok,IO} = file:open(OutFile,[write,append]),
ok = file:write(IO,Bin),
ok = io:format(IO,"~s~n",[lists:flatten(Ex)]),
- Cmd = lists:flatten(io_lib:format("cmp ~s ~s",[OutFile,Wurfl_xml])),
- [] = os:cmd(Cmd),
+ ok = file:close(IO),
+ {ok,CmpBin1} = file:read_file(OutFile),
+ {ok,CmpBin1} = file:read_file(Wurfl_xml),
ok.
-
simple() ->
[{document,
[{title, "Doc Title"}, {author, "Ulf Wiger"}],
@@ -450,24 +451,26 @@ ticket_5998(Config) ->
DataDir = datadir(Config),
%% First fix is tested by case syntax_bug2.
- case catch xmerl_scan:file(filename:join([DataDir,misc,"ticket_5998_2.xml"])) of
- {'EXIT',{fatal,Reason1}} ->
- case Reason1 of
- {{endtag_does_not_match,
- {was,obj,should_have_been,int}},
- _,_,_} -> ok;
- _ -> {comment,"parsing changed behaviour"}
- end
- end,
-
- case catch xmerl_scan:file(filename:join([DataDir,misc,"ticket_5998_3.xml"])) of
- {'EXIT',{fatal,Reason2}} ->
- case Reason2 of
- {"expected one of: ?>, standalone, encoding",
- _,_,_} -> ok;
- _ -> {comment,"parsing changed behaviour"}
- end
- end.
+ ok =
+ case catch xmerl_scan:file(filename:join([DataDir,misc,"ticket_5998_2.xml"])) of
+ {'EXIT',{fatal,Reason1}} ->
+ case Reason1 of
+ {{endtag_does_not_match,
+ {was,obj,should_have_been,int}},
+ _,_,_} -> ok;
+ _ -> {comment,"parsing changed behaviour"}
+ end
+ end,
+
+ ok =
+ case catch xmerl_scan:file(filename:join([DataDir,misc,"ticket_5998_3.xml"])) of
+ {'EXIT',{fatal,Reason2}} ->
+ case Reason2 of
+ {"expected one of: ?>, standalone, encoding",
+ _,_,_} -> ok;
+ _ -> {comment,"parsing changed behaviour"}
+ end
+ end.
%%
@@ -528,8 +531,8 @@ ticket_7214(Config) ->
%% Problem with contents of numeric character references followed by
%% UTF-8 characters..
%%
-ticket_7430(Config) ->
- DataDir = datadir(Config),
+ticket_7430(_Config) ->
+ %%DataDir = datadir(Config),
{E,[]} = xmerl_scan:string("<a>\303\251&#xD;\303\251</a>",[{encoding,'utf-8'}]),
@@ -553,7 +556,7 @@ ticket_7496(Config) ->
file:set_cwd(filename:join(datadir(Config),xpath)),
ok = xpath_abbrev:ticket_7496().
-ticket_8156(Config) ->
+ticket_8156(_Config) ->
{ftp,{[],[]},"testmachine1",21,"/w.erl"} = xmerl_uri:parse("ftp://testmachine1/w.erl"),
{ftp,{"user",[]},"testmachine1",21,"/w.erl"} = xmerl_uri:parse("ftp://user@testmachine1/w.erl"),
{ftp,{"user","hello"},"testmachine1",21,"/w.erl"} = xmerl_uri:parse("ftp://user:hello@testmachine1/w.erl"),
@@ -562,7 +565,7 @@ ticket_8156(Config) ->
ok.
%% Test that xmerl_scan can decode unicode entities properly
-ticket_8697(Config) ->
+ticket_8697(_Config) ->
{UTF8Output, []} = xmerl_scan:string("<?xml version=\"1\" ?>\n<text>" ++ [229, 145, 156] ++ "</text>"),
#xmlElement{content = [#xmlText{value = UTF8Text}]} = UTF8Output,
[16#545C] = UTF8Text,
@@ -585,9 +588,9 @@ ticket_9411(Config) ->
{E, _} = xmerl_xsd:validate(E, Schema).
%% Test that xmerl_scan handles continuation correct when current input runs out at the end of an attribute value
-ticket_9457(Config) ->
+ticket_9457(_Config) ->
Opts = [{continuation_fun, fun ticket_9457_cont/3, start}, {space, normalize}],
- {E, _} = xmerl_scan:string([], Opts).
+ {_E, _} = xmerl_scan:string([], Opts).
ticket_9457_cont(Continue, Exception, GlobalState) ->
case xmerl_scan:cont_state(GlobalState) of
diff --git a/lib/xmerl/test/xmerl_sax_std_SUITE.erl b/lib/xmerl/test/xmerl_sax_std_SUITE.erl
index 94c02b2218..fd392c1321 100644
--- a/lib/xmerl/test/xmerl_sax_std_SUITE.erl
+++ b/lib/xmerl/test/xmerl_sax_std_SUITE.erl
@@ -2817,7 +2817,7 @@ end_per_testcase(_Func,_Config) ->
%% Description:
%% Tests the VC by referring to an undefined parameter entity within an
%% external entity.
-'not-wf-not-sa-005'(Config) -> {skip, "unknown parameter reference in external (VC test not WFC)"}.
+'not-wf-not-sa-005'(_Config) -> {skip, "unknown parameter reference in external (VC test not WFC)"}.
%% file:set_cwd(datadir(Config)),
%% Path = filename:join([datadir(Config),"xmltest","not-wf/not-sa/005.xml"]),
%% R = xmerl_sax_parser:file(Path, [{event_fun, fun sax_canon/3}]),
@@ -2895,7 +2895,7 @@ end_per_testcase(_Func,_Config) ->
%% Entities: both
%% Description:
%% Tests the WFC by having an external general entity be self-recursive.
-'not-wf-ext-sa-001'(Config) -> {skip, "recursive external reference"}.
+'not-wf-ext-sa-001'(_Config) -> {skip, "recursive external reference"}.
%% file:set_cwd(datadir(Config)),
%% Path = filename:join([datadir(Config),"xmltest","not-wf/ext-sa/001.xml"]),
%% R = xmerl_sax_parser:file(Path, [{event_fun, fun sax_canon/3}]),
diff --git a/lib/xmerl/test/xmerl_sax_stream_SUITE.erl b/lib/xmerl/test/xmerl_sax_stream_SUITE.erl
index 7315f67374..9114a74e4a 100644
--- a/lib/xmerl/test/xmerl_sax_stream_SUITE.erl
+++ b/lib/xmerl/test/xmerl_sax_stream_SUITE.erl
@@ -207,7 +207,7 @@ one_document_and_junk(Config) ->
%%----------------------------------------------------------------------
%% Test of continuation when end of stream
-end_of_stream(Config) ->
+end_of_stream(_Config) ->
Stream = <<"<?xml version=\"1.0\" encoding=\"utf-8\"?><a>hej</a>">>,
{ok, undefined, <<>>} = xmerl_sax_parser:stream(Stream, []),
ok.
diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk
index 79be4c8a95..5715f32e4f 100644
--- a/lib/xmerl/vsn.mk
+++ b/lib/xmerl/vsn.mk
@@ -1 +1 @@
-XMERL_VSN = 1.3.24
+XMERL_VSN = 1.3.26
diff --git a/make/app_targets.mk b/make/app_targets.mk
new file mode 100644
index 0000000000..265fb43519
--- /dev/null
+++ b/make/app_targets.mk
@@ -0,0 +1,66 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-2019. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+
+APPLICATION ?= $(basename $(notdir $(PWD)))
+
+.PHONY: test info gclean dialyzer dialyzer_plt dclean
+
+test:
+ $(ERL_TOP)/make/test_target_script.sh $(ERL_TOP)
+
+info:
+ @echo "$(APPLICATION)_VSN: $(VSN)"
+ @echo "APP_VSN: $(APP_VSN)"
+ @echo ""
+ @echo "DIA_PLT: $(DIA_PLT)"
+ @echo "DIA_ANALYSIS: $(DIA_ANALYSIS)"
+ @echo ""
+
+gclean:
+ git clean -fXd
+
+
+DIA_DEFAULT_PLT_APPS = erts kernel stdlib $(APPLICATION)
+DIA_PLT_DIR = ./priv/plt
+DIA_PLT = $(DIA_PLT_DIR)/$(APPLICATION).plt
+DIA_ANALYSIS = $(basename $(DIA_PLT)).dialyzer_analysis
+
+dialyzer_plt: $(DIA_PLT)
+
+$(DIA_PLT_DIR):
+ @mkdir -p $@
+
+$(DIA_PLT): $(DIA_PLT_DIR)
+ @echo "Building $(APPLICATION) plt file"
+ @dialyzer --build_plt \
+ --output_plt $@ \
+ --apps $(sort $(DIA_PLT_APPS) $(DIA_DEFAULT_PLT_APPS)) \
+ --output $(DIA_ANALYSIS) \
+ --verbose
+
+dialyzer: $(DIA_PLT)
+ @echo "Running dialyzer on $(APPLICATION)"
+ @dialyzer --plt $< \
+ ../$(APPLICATION)/ebin \
+ --verbose
+
+dclean:
+ rm -f $(DIA_PLT)
+ rm -f $(DIA_ANALYSIS)
diff --git a/make/configure.in b/make/configure.in
index c4b89c4f45..d603c9d202 100644
--- a/make/configure.in
+++ b/make/configure.in
@@ -76,13 +76,13 @@ esac
#
# Now srcdir is absolute and also the top of Erlang distribution, ERL_TOP.
#
-test "X$ERL_TOP" != "X" || ERL_TOP="$srcdir"
+test "X$ERL_TOP" != "X" || AC_MSG_ERROR([ERL_TOP not set])
AC_SUBST(ERL_TOP)
dnl
dnl Aux programs are found in erts/autoconf
dnl
-AC_CONFIG_AUX_DIR(${srcdir}/erts/autoconf)
+AC_CONFIG_AUX_DIR(${ERL_TOP}/erts/autoconf)
dnl
dnl Figure out what we are running on. And in violation of autoconf
@@ -339,6 +339,10 @@ AS_HELP_STRING([--enable-m32-build],
esac
],enable_m32_build=no)
+AC_ARG_ENABLE(pie,
+AS_HELP_STRING([--enable-pie], [build position independent executables])
+AS_HELP_STRING([--disable-pie], [do no build position independent executables]))
+
AC_ARG_WITH(libatomic_ops,
AS_HELP_STRING([--with-libatomic_ops=PATH],
[specify and prefer usage of libatomic_ops in the ethread library]))
@@ -383,50 +387,6 @@ if test X${enable_native_libs} = Xyes -a X${enable_hipe} != Xno; then
fi
AC_SUBST(NATIVE_LIBS_ENABLED)
-if test $CROSS_COMPILING = no; then
- case $host_os in
- darwin*)
- macosx_version=`sw_vers -productVersion`
- test $? -eq 0 || {
- AC_MSG_ERROR([Failed to execute 'sw_vers'; please provide it in PATH])
- }
- [case "$macosx_version" in
- [1-9][0-9].[0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)|\1\2|'`;;
- [1-9][0-9].[0-9].[0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)\.\([^\.]*\)|\1\2\3|'`;;
- [1-9][0-9].[1-9][0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)|\1\200|'`;;
- [1-9][0-9].[1-9][0-9].[0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)\.\([^\.]*\)|\1\20\3|'`;;
- [1-9][0-9].[1-9][0-9].[1-9][0-9])
- int_macosx_version=`echo $macosx_version | sed 's|\([^\.]*\)\.\([^\.]*\)\.\([^\.]*\)|\1\2\3|'`;;
- *)
- int_macosx_version=unexpected;;
- esac]
- test $int_macosx_version != unexpected || {
- AC_MSG_ERROR([Unexpected MacOSX version ($macosx_version) returned by 'sw_vers -productVersion'; this configure script probably needs to be updated])
- }
- AC_TRY_COMPILE([
-#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > $int_macosx_version
-#error Compiling for a newer MacOSX version...
-#endif
- ], [;],
- [],
- [AC_MSG_ERROR([
-
- You are natively building Erlang/OTP for a later version of MacOSX
- than current version ($macosx_version). You either need to
- cross-build Erlang/OTP, or set the environment variable
- MACOSX_DEPLOYMENT_TARGET to $macosx_version (or a lower version).
-
-])])
- ;;
- *)
- ;;
- esac
-fi
-
ERL_DED
AC_CONFIG_FILES([../Makefile output.mk ../make/$host/otp_ded.mk:../make/otp_ded.mk.in])
diff --git a/make/doc.mk b/make/doc.mk
new file mode 100644
index 0000000000..4acc33913c
--- /dev/null
+++ b/make/doc.mk
@@ -0,0 +1,215 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-2019. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+ifeq ($(APPLICATION),erts)
+RELSYSDIR = $(RELEASE_PATH)/$(APPLICATION)-$(VSN)
+else
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+endif
+RELCHUNKSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+
+APP_DIR = $(ERL_TOP)/lib/$(APPLICATION)
+APP_SRC_DIR = $(APP_DIR)/src
+APP_EBIN_DIR = $(APP_DIR)/src
+
+# ----------------------------------------------------
+HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
+ $(XML_HTML_FILES:%.xml=$(HTMLDIR)/%.html) \
+ $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
+
+XML_ALL_REF3_FILES = $(XML_REF3_FILES) $(EDOC_REF3_FILES)
+XML_CHAPTER_FILES += $(EDOC_CHAPTER_FILE)
+XML_GEN_FILES += $(EDOC_REF3_FILES:%=$(XMLDIR)/%) $(EDOC_CHAPTER_FILE:%=$(XMLDIR)/%)
+
+INFO_FILE = ../../info
+
+MAN1_FILES = $(XML_REF1_FILES:%_cmd.xml=$(MAN1DIR)/%.1)
+MAN2_FILES = $(XML_REF2_FILES:%.xml=$(MAN1DIR)/%.2)
+MAN3_FILES = $(XML_ALL_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
+MAN4_FILES = $(XML_REF4_FILES:%.xml=$(MAN4DIR)/%.4)
+MAN5_FILES = $(XML_REF5_FILES:%.xml=$(MAN4DIR)/%.5)
+MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
+MAN7_FILES = $(MIB_REF7_FILES:$(MIBSDIR)/%.mib=$(MAN7DIR)/%.7)
+
+HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
+
+TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+
+ifneq ($(TOP_SPECS_FILE),)
+SPECS_FILES = $(XML_ALL_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+endif
+
+ifneq ($(strip $(CHUNKSDIR)),)
+_create_chunksdir_dirs := $(shell mkdir -p $(CHUNKSDIR))
+endif
+CHUNK_REF3_FILES = $(filter-out $(NO_CHUNKS), $(XML_ALL_REF3_FILES))
+CHUNK_FILES = $(CHUNK_REF3_FILES:%.xml=$(CHUNKSDIR)/%.chunk)
+
+ERL_CHUNK_FILES = $(patsubst $(APP_EBIN_DIR)/%.BEAM,$(CHUNKSDIR)/%.chunk,$(wildcard $(APP_EBIN_DIR)/*.beam))
+EMPTY_CHUNK_FILES = $(filter-out $(NO_CHUNKS:%.xml=$(CHUNKSDIR)/%.chunk) $(CHUNK_FILES), $(ERL_CHUNK_FILES))
+
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+
+SPECS_FLAGS = -I$(ERL_TOP)/lib -I$(ERL_TOP)/lib/*/include -I$(ERL_TOP)/lib/*/src
+
+
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+$(HTMLDIR)/%.gif: %.gif
+ $(INSTALL_DATA) $< $@
+$(HTMLDIR)/%.png: %.png
+ $(INSTALL_DATA) $< $@
+$(HTMLDIR)/%.jpg: %.jpg
+ $(INSTALL_DATA) $< $@
+
+DOC_TARGETS?=man pdf html chunks
+
+docs: $(INFO_FILE) $(DOC_TARGETS)
+
+$(TOP_PDF_FILE): $(XML_FILES)
+
+pdf: $(TOP_PDF_FILE)
+
+html: images $(HTML_REF_MAN_FILE) $(HTMLDIR)/$(APPLICATION).eix
+
+man: $(MAN1_FILES) $(MAN2_FILES) $(MAN3_FILES) $(MAN4_FILES) $(MAN5_FILES) $(MAN6_FILES) $(MAN7_FILES)
+
+chunks: $(CHUNK_FILES) $(EMPTY_CHUNK_FILES)
+
+images: $(IMAGE_FILES:%=$(HTMLDIR)/%)
+
+$(EDOC_REF3_FILES:%=$(XMLDIR)/%): $(APP_SRC_DIR)/$(@:$(XMLDIR)/%.xml=%.erl)
+ $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript \
+ -def vsn $(VSN) $(EDOC_FLAGS) -dir $(XMLDIR) $(APP_SRC_DIR)/$(@:$(XMLDIR)/%.xml=%.erl)
+$(XMLDIR)/$(EDOC_CHAPTER_FILE): ../overview.edoc
+ $(gen_verbose)escript $(DOCGEN)/priv/bin/xml_from_edoc.escript -def vsn $(VSN) \
+ -chapter -dir $(XMLDIR) $<
+
+info:
+ @echo "XML_APPLICATION_FILES: $(XML_APPLICATION_FILES)"
+ @echo "XML_REF1_FILES: $(XML_REF1_FILES)"
+ @echo "XML_REF2_FILES: $(XML_REF2_FILES)"
+ @echo "XML_REF3_FILES: $(XML_ALL_REF3_FILES)"
+ @echo "XML_REF4_FILES: $(XML_REF4_FILES)"
+ @echo "XML_REF5_FILES: $(XML_REF5_FILES)"
+ @echo "XML_REF6_FILES: $(XML_REF6_FILES)"
+ @echo "XML_REF7_FILES: $(XML_REF7_FILES)"
+ @echo "XML_PART_FILES: $(XML_PART_FILES)"
+ @echo "XML_CHAPTER_FILES: $(XML_CHAPTER_FILES)"
+ @echo "BOOK_FILES: $(BOOK_FILES)"
+
+debug opt lcnt:
+
+clean clean_docs: clean_xml clean_pdf clean_html clean_man clean_chunks
+ rm -rf $(EXTRA_FILES)
+ rm -f errs core *~ *.eps
+
+clean_pdf:
+ rm -f $(PDFDIR)/*
+
+clean_man:
+ rm -f $(MAN1DIR)/* $(MAN3DIR)/* $(MAN4DIR)/* $(MAN6DIR)/*
+
+clean_xml:
+ rm -f $(SPECDIR)/*
+ rm -rf $(XMLDIR)
+
+clean_html:
+ rm -rf $(HTMLDIR)/*
+
+clean_chunks:
+ rm -f $(CHUNKSDIR)/*
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+$(RELSYSDIR) $(RELSYSDIR)/doc:
+ $(INSTALL_DIR) "$@"
+
+release_pdf_spec: pdf
+ $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
+ $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
+
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
+ $(INSTALL_DIR_DATA) $(HTMLDIR) "$(RELSYSDIR)/doc/html"
+ifneq ($(HTML_EXTRA_FILES),)
+ $(INSTALL_DATA) $(HTML_EXTRA_FILES) "$(RELSYSDIR)/doc/html"
+endif
+
+release_chunks_spec: chunks
+ifneq ($(CHUNK_FILES),)
+ $(INSTALL_DIR) "$(RELCHUNKSDIR)/doc/chunks"
+ $(INSTALL_DATA) $(CHUNKSDIR)/* "$(RELCHUNKSDIR)/doc/chunks"
+endif
+
+release_man_spec: man
+ifneq ($(MAN1_FILES),)
+ $(INSTALL_DIR) "$(RELEASE_PATH)/man/man1"
+ $(INSTALL_DATA) $(MAN1DIR)/* "$(RELEASE_PATH)/man/man1"
+endif
+ifneq ($(MAN2_FILES),)
+ $(INSTALL_DIR) "$(RELEASE_PATH)/man/man2"
+ $(INSTALL_DATA) $(MAN2DIR)/* "$(RELEASE_PATH)/man/man2"
+endif
+ifneq ($(MAN3_FILES),)
+ $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
+ $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
+endif
+ifneq ($(MAN4_FILES),)
+ $(INSTALL_DIR) "$(RELEASE_PATH)/man/man4"
+ $(INSTALL_DATA) $(MAN4_FILES) "$(RELEASE_PATH)/man/man4"
+endif
+ifneq ($(MAN5_FILES),)
+ $(INSTALL_DIR) "$(RELEASE_PATH)/man/man5"
+ $(INSTALL_DATA) $(MAN5_FILES) "$(RELEASE_PATH)/man/man5"
+endif
+ifneq ($(MAN6_FILES),)
+ $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
+ $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
+endif
+ifneq ($(MAN7_FILES),)
+ $(INSTALL_DIR) "$(RELEASE_PATH)/man/man7"
+ $(INSTALL_DATA) $(MAN7_FILES) "$(RELEASE_PATH)/man/man7"
+endif
+
+release_docs_spec: $(RELSYSDIR)/doc $(INFO_FILE) $(DOC_TARGETS:%=release_%_spec)
+ $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR)
+ifneq ($(STANDARDS),)
+ $(INSTALL_DIR) "$(RELEASE_PATH)/doc/standard"
+ $(INSTALL_DATA) $(STANDARDS) "$(RELEASE_PATH)/doc/standard"
+endif
+
+release_spec:
+
+.PHONY: clean clean_xml clean_html clean_man clean_pdf \
+ debug opt info \
+ docs images html man pdf chunks \
+ release_docs_spec release_spec
diff --git a/make/emd2exml.in b/make/emd2exml.in
index ec3de0a7f8..d179c05b34 100755
--- a/make/emd2exml.in
+++ b/make/emd2exml.in
@@ -472,10 +472,8 @@ link_or_image(Str, Type) ->
Cont2};
{seealso, SeeAlso, _Title, Cont2} ->
case internal_seealso(SeeAlso) of
- no ->
- {["<seealso marker=\"", text(SeeAlso), "\">",
- text(Text), "</seealso>"],
- Cont2};
+ {no,Link} ->
+ {make_seealso(Link, Text),Cont2};
{yes, SeeAlsoKey} ->
{delayed, link, SeeAlsoKey, text(Text), Cont2}
end;
@@ -492,8 +490,16 @@ link_or_image(Str, Type) ->
internal_seealso("#" ++ Marker) ->
{yes, {internal, Marker}};
-internal_seealso(_) ->
- no.
+internal_seealso(Link) ->
+ {no, Link}.
+
+make_seealso("see" ++ _ = Link, Text) ->
+ [Tag, Target] = string:split(Link,"/"),
+ ["<",Tag," marker=\"", text(Target), "\">",
+ text(Text), "</",Tag,">"];
+make_seealso(Link, Text) ->
+ ["<seeguide marker=\"", text(Link), "\">",
+ text(Text), "</seeguide>"].
link_or_image_text(Cs0) ->
{Prefix, Cs2} = case Cs0 of
@@ -575,7 +581,7 @@ mk_image(Title, Url) ->
mk_link(Text, Url) ->
case chk_proto(Url) of
true -> ["<url href=\"", text(Url), "\">", text(Text), "</url>"];
- false -> ["<seealso marker=\"", text(Url), "\">", text(Text), "</seealso>"]
+ false -> make_seealso(Url, Text)
end.
chk_proto("://" ++ _) ->
@@ -653,8 +659,8 @@ put_title(#state{mlist = MList0,
S0#state{toc = [TOC,
lists:duplicate(H," "),
" ",
- "<seealso marker=\"#",Marker,"\">",
- TitleStr,"</seealso>",nl()],
+ "<seeguide marker=\"#",Marker,"\">",
+ TitleStr,"</seeguide>",nl()],
h = H,
mlist = MList1
}),
diff --git a/make/otp.mk.in b/make/otp.mk.in
index 63748c2f2b..0d1130b003 100644
--- a/make/otp.mk.in
+++ b/make/otp.mk.in
@@ -90,12 +90,8 @@ AR = @AR@
PERL = @PERL@
LLVM_PROFDATA = @LLVM_PROFDATA@
-MIXED_CYGWIN_VC = @MIXED_CYGWIN_VC@
-MIXED_MSYS_VC = @MIXED_MSYS_VC@
MIXED_VC = @MIXED_VC@
-MIXED_CYGWIN_MINGW = @MIXED_CYGWIN_MINGW@
-MIXED_CYGWIN = @MIXED_CYGWIN@
-MIXED_MSYS = @MIXED_MSYS@
+MIXED_MINGW = @MIXED_MINGW@
BITS64 = @BITS64@
@@ -105,6 +101,9 @@ OTP_RELEASE = @OTP_RELEASE@
# Erlang language section
# ----------------------------------------------------
EMULATOR = beam
+ifeq ($(ERL_COMPILE_WARNINGS_AS_ERRORS),yes)
+ ERL_COMPILE_FLAGS += -Werror
+endif
ifdef BOOTSTRAP
ERL_COMPILE_FLAGS += +slim
else
@@ -230,13 +229,16 @@ MAN1DIR = $(DOCDIR)/man1
MAN2DIR = $(DOCDIR)/man2
MAN3DIR = $(DOCDIR)/man3
MAN4DIR = $(DOCDIR)/man4
+MAN5DIR = $(DOCDIR)/man5
MAN6DIR = $(DOCDIR)/man6
+MAN7DIR = $(DOCDIR)/man7
MAN9DIR = $(DOCDIR)/man9
TEXDIR = .
SPECDIR = $(DOCDIR)/specs
XMLDIR = $(DOCDIR)/xml
+CHUNKSDIR = $(DOCDIR)/chunks
ifeq ($(CSS_FILE),)
CSS_FILE = otp_doc.css
@@ -272,7 +274,7 @@ DEFAULT_HTML_FILES = \
$(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.kwc) \
$(HTMLDIR)/index.html
-DEFAULT_GIF_FILES = $(HTMLDIR)/min_head.gif
+DEFAULT_GIF_FILES =
#
# Flags & Commands
@@ -283,7 +285,7 @@ XMLLINT = @XMLLINT@
CP = @CP@
MKDIR = @MKDIR@
-DOCGEN=$(ERL_TOP)/lib/erl_docgen
+DOCGEN?=$(ERL_TOP)/lib/erl_docgen
FOP_CONFIG = $(DOCGEN)/priv/fop.xconf
ifneq (,$(findstring $(origin SPECS_ESRC),$(DUBIOUS_ORIGINS)))
@@ -291,14 +293,18 @@ SPECS_ESRC = ../../src
endif
SPECS_EXTRACTOR=$(DOCGEN)/priv/bin/specs_gen.escript
# Extract specifications and types from Erlang source files (-spec, -type)
-$(SPECDIR)/specs_%.xml: $(SPECS_ESRC)/%.erl
+$(SPECDIR)/specs_%.xml: $(SPECS_ESRC)/%.erl $(TOP_SPECS_FILE)
$(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) -o$(dir $@) $<
-$(SPECDIR)/specs_%.xml: $(SPECS_ESRC)/gen/%.erl
+$(SPECDIR)/specs_%.xml: $(SPECS_ESRC)/gen/%.erl $(TOP_SPECS_FILE)
$(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) -o$(dir $@) $<
MANXSLTARGS=--stringparam company "Ericsson AB" --stringparam docgen "$(DOCGEN)" --stringparam gendate "$$date" --stringparam appname "$(APPLICATION)" --stringparam appver "$(VSN)" --xinclude -path $(DOCGEN)/priv/dtd -path $(DOCGEN)/priv/dtd_man_entities -path .
-$(MAN1DIR)/%.1 $(MAN2DIR)/%.2 $(MAN4DIR)/%.4 $(MAN4DIR)/%.5 $(MAN9DIR)/%.9: $(XMLDIR)/%.xml
+$(MAN1DIR)/%.1: $(XMLDIR)/%_cmd.xml
+ $(gen_verbose)date=`date +"%B %e, %Y"`; \
+ xsltproc --output "$@" $(MANXSLTARGS) $(DOCGEN)/priv/xsl/db_man.xsl $<
+
+$(MAN2DIR)/%.2 $(MAN4DIR)/%.4 $(MAN4DIR)/%.5 $(MAN9DIR)/%.9: $(XMLDIR)/%.xml
$(gen_verbose)date=`date +"%B %e, %Y"`; \
xsltproc --output "$@" $(MANXSLTARGS) $(DOCGEN)/priv/xsl/db_man.xsl $<
@@ -323,3 +329,12 @@ $(XMLDIR)/%.xml: $(XMLDIR)/%.xmlsrc
$(PDFDIR)/%.pdf: %.fo
$(FOP) -c $(FOP_CONFIG) -cache $(ERL_TOP)/make/$(TARGET)/fop-fonts.cache -fo $< -pdf $@
+
+$(CHUNKSDIR)/%.chunk: $(XMLDIR)/%.xml ../../ebin/%.beam $(DOCGEN)/ebin/docgen_xml_to_chunk.beam
+ $(gen_verbose)escript $(DOCGEN)/priv/bin/chunk.escript $(APPLICATION) $^ $@
+
+$(CHUNKSDIR)/%.chunk: $(XMLDIR)/%.xml ../../preloaded/ebin/%.beam $(DOCGEN)/ebin/docgen_xml_to_chunk.beam
+ $(gen_verbose)escript $(DOCGEN)/priv/bin/chunk.escript $(APPLICATION) $^ $@
+
+$(CHUNKSDIR)/%.chunk: ../../ebin/%.beam $(DOCGEN)/ebin/docgen_xml_to_chunk.beam
+ $(gen_verbose)escript $(DOCGEN)/priv/bin/chunk.escript $(APPLICATION) $^ $@
diff --git a/make/otp_patch_solve_forward_merge_version b/make/otp_patch_solve_forward_merge_version
index b1bd38b62a..b6a7d89c68 100644
--- a/make/otp_patch_solve_forward_merge_version
+++ b/make/otp_patch_solve_forward_merge_version
@@ -1 +1 @@
-13
+16
diff --git a/make/otp_release_targets.mk b/make/otp_release_targets.mk
index c629a7d143..a2ba51c9cf 100644
--- a/make/otp_release_targets.mk
+++ b/make/otp_release_targets.mk
@@ -60,7 +60,7 @@ endif
ifeq ($(TOPDOC),)
-$(HTMLDIR)/index.html: $(XML_GEN_FILES) $(SPECS_FILES)
+$(HTMLDIR)/index.html: $(XML_GEN_FILES) $(SPECS_FILES) $(TOP_SPECS_FILE)
$(gen_verbose)date=`date +"%B %e, %Y"`; \
$(XSLTPROC) --noout \
--stringparam outdir $(HTMLDIR) \
@@ -83,7 +83,7 @@ $(HTMLDIR)/index.html: $(XML_GEN_FILES) $(SPECS_FILES)
endif
-$(HTMLDIR)/users_guide.html: $(XML_GEN_FILES)
+$(HTMLDIR)/users_guide.html: $(XML_GEN_FILES) $(TOP_SPECS_FILE)
$(gen_verbose)date=`date +"%B %e, %Y"`; \
$(XSLTPROC) --noout \
--stringparam outdir $(HTMLDIR) \
@@ -104,7 +104,7 @@ $(HTMLDIR)/users_guide.html: $(XML_GEN_FILES)
-path $(DOCGEN)/priv/dtd_html_entities \
$(DOCGEN)/priv/xsl/db_html.xsl $(XMLDIR)/book.xml
-%.fo: $(XML_GEN_FILES) $(SPECS_FILES)
+%.fo: $(XML_GEN_FILES) $(SPECS_FILES) $(TOP_SPECS_FILE)
$(gen_verbose)date=`date +"%B %e, %Y"`; \
$(XSLTPROC) \
--stringparam docgen "$(DOCGEN)" \
@@ -142,8 +142,6 @@ $(HTMLDIR)/$(APPLICATION).eix: $(XML_GEN_FILES) $(SPECS_FILES)
-path $(DOCGEN)/priv/dtd_html_entities \
$(DOCGEN)/priv/xsl/db_eix.xsl $(XMLDIR)/book.xml > $@
-docs: $(HTMLDIR)/$(APPLICATION).eix
-
## Here awk is used to find all xi:include files in $(BOOK_FILES)
## Then we look into all those files check for xi:includes
BOOK_XI_INC_FILES:=$(foreach file,$(BOOK_FILES),$(shell awk -F\" '/xi:include/ {print $$2}' $(file))) $(BOOK_FILES)
@@ -207,15 +205,26 @@ endif
# Standard release target
# ----------------------------------------------------
+ifneq ($(XML_ALL_REF3_FILES),)
+man chunks: $(XML_GEN_FILES) $(SPECS_FILES) $(TOP_SPECS_FILE)
+else
+man chunks:
+endif
+pdf html: $(XML_GEN_FILES) $(SPECS_FILES) $(TOP_SPECS_FILE)
+release_man_spec: man
+release_pdf_spec: pdf
+release_chunks_spec: chunks
+release_html_spec: html
+
ifeq ($(TESTROOT),)
-release release_docs release_tests release_html:
+release release_docs release_tests:
$(MAKE) $(MFLAGS) RELEASE_PATH=$(OTP_DEFAULT_RELEASE_PATH) \
$(TARGET_MAKEFILE) $@_spec
else
-release release_docs release_tests release_html:
+release release_docs release_tests:
$(MAKE) $(MFLAGS) RELEASE_PATH="$(TESTROOT)" $(TARGET_MAKEFILE) $@_spec
endif
diff --git a/make/otp_subdir.mk b/make/otp_subdir.mk
index 19c744955c..f9b993e048 100644
--- a/make/otp_subdir.mk
+++ b/make/otp_subdir.mk
@@ -20,12 +20,12 @@
# Make include file for otp
.PHONY: debug opt lcnt release docs release_docs tests release_tests \
- clean depend valgrind static_lib
+ clean depend valgrind asan static_lib
#
# Targets that don't affect documentation directories
#
-opt debug lcnt release docs release_docs tests release_tests clean depend valgrind static_lib xmllint:
+opt debug lcnt release docs release_docs tests release_tests clean depend valgrind asan static_lib xmllint:
@set -e ; \
app_pwd=`pwd` ; \
if test -f vsn.mk; then \
diff --git a/make/otp_version_tickets b/make/otp_version_tickets
index 650fc4bad2..c9e062ec5c 100644
--- a/make/otp_version_tickets
+++ b/make/otp_version_tickets
@@ -1,5 +1,3 @@
OTP-16607
-OTP-16636
-OTP-16640
-OTP-16641
-OTP-16642
+OTP-17227
+OTP-17279
diff --git a/make/otp_version_tickets_in_merge b/make/otp_version_tickets_in_merge
index e69de29bb2..f66f16de77 100644
--- a/make/otp_version_tickets_in_merge
+++ b/make/otp_version_tickets_in_merge
@@ -0,0 +1,4 @@
+OTP-12960
+OTP-16607
+OTP-17227
+OTP-17228
diff --git a/make/prebuild.skip b/make/prebuild.skip
new file mode 100644
index 0000000000..9c558e357c
--- /dev/null
+++ b/make/prebuild.skip
@@ -0,0 +1 @@
+.
diff --git a/make/run_make.mk b/make/run_make.mk
index bcbbf53f7d..087129866d 100644
--- a/make/run_make.mk
+++ b/make/run_make.mk
@@ -29,9 +29,9 @@
include $(ERL_TOP)/make/output.mk
include $(ERL_TOP)/make/target.mk
-.PHONY: valgrind
+.PHONY: valgrind asan
-opt debug purify quantify purecov valgrind gcov gprof lcnt frmptr icount:
+opt debug purify quantify purecov valgrind asan gcov gprof lcnt frmptr icount:
$(make_verbose)$(MAKE) -f $(TARGET)/Makefile TYPE=$@
plain smp frag smp_frag:
diff --git a/make/target.mk b/make/target.mk
index 8917e1ae44..3bcb6e91d3 100644
--- a/make/target.mk
+++ b/make/target.mk
@@ -55,14 +55,11 @@ endif
ifneq ($(TARGET),)
ifneq ($(TARGET),win32)
-ifneq ($(findstring vxworks,$(TARGET)),vxworks)
override TARGET := $(shell $(ERL_TOP)/erts/autoconf/config.sub $(TARGET))
else
endif
else
endif
-else
-endif
ifeq ($(TARGET),)
$(error Neither TARGET nor OVERRIDE_TARGET can be determined!)
diff --git a/make/test_target_script.sh b/make/test_target_script.sh
new file mode 100755
index 0000000000..f605efa120
--- /dev/null
+++ b/make/test_target_script.sh
@@ -0,0 +1,254 @@
+#!/bin/sh
+
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-2019. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+LIGHT_CYAN='\033[1;36m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+
+print_highlighted_msg_with_printer () {
+ COLOR=$1
+ MSG_PRINTER=$2
+ printf "\n${COLOR}======================================================================${NC}\n"
+ echo
+ $MSG_PRINTER
+ echo
+ printf "${COLOR}======================================================================${NC}\n"
+}
+
+print_highlighted_msg () {
+ COLOR=$1
+ MSG=$2
+ print_msg () {
+ echo "$MSG"
+ }
+ print_highlighted_msg_with_printer $COLOR print_msg
+}
+
+print_all_tests_takes_long_time_warning () {
+ print_msg () {
+ cat << EOM
+
+WARNING
+
+All tests will require several hours to run. You may want to check the
+following text file that describes how to run tests for a specific
+application.
+
+EOM
+ echo $ERL_TOP/HOWTO/TESTING.md
+ }
+ print_highlighted_msg_with_printer $YELLOW print_msg
+}
+
+print_all_tests_for_application_notes () {
+ print_msg () {
+ cat << EOM
+
+NOTE 1
+
+ct_run will now attempt to execute tests in the test directory, which
+may take a long time to do. One can pass arguments to ct_run by
+setting the ARGS variable when invoking "make test".
+
+Example:
+
+make ARGS="-suite asn1_SUITE -case ticket_7407" test
+
+NOTE 2
+
+You may want to look at the more established way of running tests that
+is described in the following text file if you encounter strange
+errors:
+
+EOM
+ echo "$ERL_TOP/HOWTO/TESTING.md"
+ }
+ print_highlighted_msg_with_printer $LIGHT_CYAN print_msg
+}
+
+print_c_files_warning () {
+ print_msg () {
+ cat << EOM
+
+WARNING
+
+The test directory contains .c files which means that some test cases
+will probably not work correctly when run through "make test". The
+text file at the following location describes how one can compile and
+run all test cases:
+
+
+EOM
+ echo $ERL_TOP/HOWTO/TESTING.md
+ }
+ print_highlighted_msg_with_printer $YELLOW print_msg
+}
+
+
+print_on_error_note () {
+ print_msg () {
+ cat << EOM
+NOTE:
+
+Some test cases do not work correctly when run through "make test" as
+they are designed to be run through the method that is described in
+the "$ERL_TOP/HOWTO/TESTING.md" text file. You may want to check this
+text file if you encounter strange errors. Note also that you can
+rerun a specific test case by passing parameters to ct_run as in the
+example below:
+
+make ARGS="-suite asn1_SUITE -case ticket_7407" test
+
+EOM
+ }
+ print_highlighted_msg_with_printer $NC print_msg
+}
+
+# Check ERL_TOP
+
+if [ -d "$1" ]
+then
+ ERL_TOP="$1"
+ shift
+fi
+
+if [ -z $ERL_TOP ]
+then
+ ERL_TOP=`git rev-parse --show-toplevel`
+ if [ $? = 0 ]
+ then
+ print_highlighted_msg $LIGHT_CYAN "The environment variable ERL_TOP has been set to the git root"
+ else
+ echo "The ERL_TOP environment variable need to be set before this script is executed."
+ exit 1
+ fi
+fi
+
+export ERL_TOP=$ERL_TOP
+
+
+if [ -z "${ARGS}" ]
+then
+ ARGS="$@"
+fi
+
+# make test in root
+DIR=`pwd`
+if [ "$DIR" -ef "$ERL_TOP" ]
+then
+ TARGET_SYS=`$ERL_TOP/erts/autoconf/config.guess`
+ REL_DIR="$ERL_TOP/release/$TARGET_SYS"
+ cd "$REL_DIR"
+ ./Install -minimal "`pwd`"
+ export PATH="$REL_DIR/bin:$PATH"
+ cd "$ERL_TOP/release/tests/test_server"
+ print_all_tests_takes_long_time_warning
+ echo "The tests will start in a few seconds..."
+ sleep 45
+ cd "$ERL_TOP/release/tests/test_server"
+ erl -eval "ts:install(),erlang:halt()"
+ erl -noinput -eval "ts:run([all_tests,batch]),erlang:halt()"
+ exit $?
+fi
+
+# check that there is a test directory
+if [ ! -d test ]
+then
+ print_highlighted_msg $RED "This target only works in directories containing a test directory or\nin the root directory."
+ exit 1
+fi
+
+
+APPLICATION="`basename $DIR`"
+CT_RUN="$ERL_TOP/bin/ct_run"
+MAKE_TEST_DIR="`pwd`/make_test_dir"
+MAKE_TEST_REL_DIR="$MAKE_TEST_DIR/${APPLICATION}_test"
+MAKE_TEST_CT_LOGS="$MAKE_TEST_DIR/ct_logs"
+RELEASE_TEST_SPEC_LOG="$MAKE_TEST_CT_LOGS/release_tests_spec_log"
+
+cd test
+echo "The tests in test directory for $APPLICATION will be executed with ct_run"
+if [ -z "${ARGS}" ]
+then
+ if [ ! -d "$MAKE_TEST_DIR" ]
+ then
+ print_all_tests_for_application_notes
+ fi
+ if find . -type f -name '*.c' | grep -q "."
+ then
+ print_c_files_warning
+ fi
+fi
+
+mkdir -p "$MAKE_TEST_DIR"
+mkdir -p "$MAKE_TEST_REL_DIR"
+mkdir -p "$MAKE_TEST_CT_LOGS"
+make RELSYSDIR=$MAKE_TEST_REL_DIR release_tests_spec > $RELEASE_TEST_SPEC_LOG 2>&1
+
+if [ $? != 0 ]
+then
+ cat $RELEASE_TEST_SPEC_LOG
+ print_highlighted_msg $RED "\"make RELSYSDIR="$MAKE_TEST_REL_DIR" release_tests_spec\" failed."
+ exit 1
+fi
+SPEC_FLAG=""
+SPEC_FILE=""
+if [ -z "${ARGS}" ]
+then
+ SPEC_FLAG="-spec"
+ SPEC_FILE="$MAKE_TEST_REL_DIR/$APPLICATION.spec"
+ ARGS="$SPEC_FLAG $SPEC_FILE"
+fi
+# Compile test server
+(cd "$ERL_TOP/lib/common_test/test_server" && make)
+# Run ct_run
+cd $MAKE_TEST_REL_DIR
+$CT_RUN -logdir $MAKE_TEST_CT_LOGS\
+ -pa "$ERL_TOP/lib/common_test/test_server"\
+ ${ARGS}\
+ -erl_args\
+ -env "$PATH"\
+ -env ERL_CRASH_DUMP "$MAKE_TEST_DIR/${APPLICATION}_erl_crash.dump"\
+ -boot start_sasl\
+ -sasl errlog_type error\
+ -pz "$ERL_TOP/lib/common_test/test_server"\
+ -pz "."\
+ -ct_test_vars "{net_dir,\"\"}"\
+ -noshell\
+ -sname test_server\
+ -rsh ssh\
+ ${ERL_ARGS}
+CT_RUN_STATUS=$?
+if [ $CT_RUN_STATUS = "0" ]
+then
+ print_highlighted_msg $GREEN "The test(s) ran successfully (ct_run returned a success code)\nTest logs: file://$MAKE_TEST_CT_LOGS/index.html"
+ exit 0
+else
+ print_on_error_note
+ print_highlighted_msg $RED "ct_run returned the error code $CT_RUN_STATUS\nTest logs: file://$MAKE_TEST_CT_LOGS/index.html"
+ exit $CT_RUN_STATUS
+fi
diff --git a/otp_build b/otp_build
index 244f0c3e25..841cddf7f7 100755
--- a/otp_build
+++ b/otp_build
@@ -65,33 +65,14 @@ usage ()
echo ""
echo "Before trying to build on windows, consider the following option"
echo " env_win32 [<arch>] - echo environment settings for win32 with visual C++, use with eval"
- echo " The optional <arch> can be x64 for 64bit Windows 7"
- echo " or x86 for 32bit Windows XP+"
+ echo " The optional <arch> can be x64 for 64bit Windows"
+ echo " or x86 for 32bit Windows"
echo " env_win64 - echo environment settings for win32 with visual C++, use with eval"
- echo " Note that env_win32 x64 gives the same result, Windows 7 64bit"
- echo " env_mingw32 - echo environment settings for win32 with MinGW, use with eval"
- echo " - experimental!"
- echo " env_msys64 - echo environment settings for win32 with visual C++ running "
- echo " msys and mingw, use with eval"
- echo " - experimental!"
+ echo " Note that env_win32 x64 gives the same result, Windows 64bit"
echo ""
- echo "Before trying to build for vxworks, consider the following option"
- echo " env_vxworks <cpu> - echo environment settings for vxworks, use with eval"
- echo ""
- case $version_controller in
- none)
- ;;
- git)
- echo "update_primary [--no-commit] - build and maybe commit a new primary bootstrap"
- echo "update_preloaded [--no-commit] - build and maybe commit the preloaded modules"
- ;;
- esac
-}
-
-git_required ()
-{
- echo "This operation must be run in a git repository."
- exit 1
+ echo "update_primary [--no-commit] - build and maybe commit a new primary bootstrap"
+ echo "update_preloaded [--no-commit] - build and maybe commit the preloaded modules"
+ echo "update_deprecations [--no-commit] - build and maybe commit deprecations"
}
hide_vars ()
@@ -231,19 +212,6 @@ distribute_config_helpers ()
do_autoconf ()
{
distribute_config_helpers
-
- if [ ! -z "$OVERRIDE_CONFIGURE" ]; then
- echo "Autoconf disabled on target $TARGET, but is performed on host" >&2
- # We still use erts configure for erl_interface and VxWorks
- case "$TARGET" in
- *vxworks*)
- AUTOCONF_SUBDIRS=`echo $AUTOCONF_SUBDIRS | \
- sed -e 's,lib/erl_interface,,' \
- -e 's,lib/gs,,' \
- -e 's,lib/megaco,,'`
- ;;
- esac
- fi
hide_vars OVERRIDE_TARGET TARGET
TARGET=$BUILDSYS
@@ -452,33 +420,12 @@ do_configure ()
# Get `erl_build_tool_vars'
. "$ERL_TOP/erl-build-tool-vars.sh" || exit 1
- if [ ! -z "$OVERRIDE_CONFIGURE" ]; then
- case $TARGET in
- vxworks_*)
- ( cd erts/autoconf && \
- "$ERL_TOP/erts/autoconf/configure.vxworks" $TARGET )
- echo "Configuring for build system too..." >&2
- hide_vars OVERRIDE_TARGET TARGET
- TARGET=$BUILDSYS
- export TARGET
- set_config_flags "$@"
- run_configure "$@"
- restore_vars OVERRIDE_TARGET TARGET;;
- *)
- echo "Unexpected target when ordinary configure is" \
- "overridden" >&2
- echo 'check if $OVERRIDE_CONFIGURE and $OVERRIDE_TAGET' \
- 'environments are correct.' >&2
- exit 1;;
- esac
- else
- maybe_copy_static_cache
- try_cross_configure "$@"
- if [ $cross_configure = no ]; then
- CONFIG_FLAGS=
- set_config_flags "$@"
- run_configure "$@"
- fi
+ maybe_copy_static_cache
+ try_cross_configure "$@"
+ if [ $cross_configure = no ]; then
+ CONFIG_FLAGS=
+ set_config_flags "$@"
+ run_configure "$@"
fi
}
@@ -537,74 +484,9 @@ echo_envinfo ()
fi
}
-echo_env_vxworks ()
-{
- if [ -z "$1" ]; then
- echo "env_vxworks requires CPU architecture as parameter (ppc603, ppc860 etc)." >&2
- exit 1
- fi
- echo_env_erltop
- echo_setenv OVERRIDE_CONFIGURE true ';'
- echo_setenv OVERRIDE_TARGET vxworks_$1
- echo_envinfo
-}
-
-echo_env_win32 ()
-{
- #echo_envinfo
- if [ X"$SHELL" = X"" ]; then
- echo "You need to export the shell variable first," \
- "for bourne-like shells, type:" >&2
- echo 'export SHELL' >&2
- echo "and for csh-like shells, type:" >&2
- echo 'setenv SHELL $SHELL' >&2
- echo " - then try again." >&2
- exit 1
- fi
- echo_env_erltop
- # Try to cope with paths containing unexpected things like stray
- # mixed paths (c:/something/bin) and quotes. Only C and D drive
- # handled.
- CCYGPATH=`cygpath c:\\`
- DCYGPATH=`cygpath d:\\`
- P2=`echo :$PATH | \
- sed "s,\",,g;s,:[cC]:,:$CCYGPATH,g;s,:[dD]:,:$DCYGPATH,g;s,^:,,"`
- P3=""
- save_ifs=$IFS
- IFS=:
- for p in $P2; do
- if [ -d "$p" ]; then
- C1="`(cygpath -d $p 2>/dev/null || cygpath -w $p)`" 2> /dev/null
- C2=`cygpath "$C1" 2> /dev/null` 2> /dev/null
- else
- C2=""
- fi
- if [ ! -z "$C2" ]; then
- if [ -z "$P3" ];then
- P3="$C2"
- else
- P3="$P3:$C2"
- fi
- fi
- done
- IFS=$save_ifs
- WIN32_WRAPPER_PATH="$ERL_TOP/erts/etc/win32/cygwin_tools/vc:$ERL_TOP/erts/etc/win32/cygwin_tools"
-
-
- echo_setenv OVERRIDE_TARGET win32 ';'
- echo_setenv CC cc.sh ';'
- echo_setenv CXX cc.sh ';'
- echo_setenv AR ar.sh ';'
- echo_setenv RANLIB true ';'
- if [ -f "$ERL_TOP/erts/autoconf/win32.config.cache.static" ]; then
- echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win32.config.cache.static" ';'
- fi
- echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win32.config.cache" ';'
- echo_setenv WIN32_WRAPPER_PATH "$WIN32_WRAPPER_PATH" ';'
- echo_setenv PATH "$WIN32_WRAPPER_PATH:$P3" ';'
- echo_envinfo
-}
-
+#
+# Cygwin build without microsoft visual C++ (dead?)
+#
echo_env_mingw32 ()
{
#echo_envinfo
@@ -681,8 +563,9 @@ echo_env_mingw32 ()
# N.B. In Erlang, and the build system, win32 means windows, so we keep
# everything as terget win32, but add the CONFIG_SUBTYPE win64, which can
# be handled by configure, setting WINDOWS_64BIT in headers and such
-echo_env_win64 ()
+echo_env_cygwin ()
{
+ X64=$1
#echo_envinfo
if [ X"$SHELL" = X"" ]; then
echo "You need to export the shell variable first," \
@@ -722,24 +605,33 @@ echo_env_win64 ()
IFS=$save_ifs
WIN32_WRAPPER_PATH="$ERL_TOP/erts/etc/win32/cygwin_tools/vc:$ERL_TOP/erts/etc/win32/cygwin_tools"
-
echo_setenv OVERRIDE_TARGET win32 ';'
- echo_setenv CONFIG_SUBTYPE win64 ';'
+ if [ X"$X64" = X"true" ]; then
+ echo_setenv CONFIG_SUBTYPE win64 ';'
+ fi
echo_setenv CC cc.sh ';'
echo_setenv CXX cc.sh ';'
echo_setenv AR ar.sh ';'
echo_setenv RANLIB true ';'
- if [ -f "$ERL_TOP/erts/autoconf/win64.config.cache.static" ]; then
- echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win64.config.cache.static" ';'
+ if [ X"$X64" = X"true" ]; then
+ if [ -f "$ERL_TOP/erts/autoconf/win64.config.cache.static" ]; then
+ echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win64.config.cache.static" ';'
+ fi
+ echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win64.config.cache" ';'
+ else
+ if [ -f "$ERL_TOP/erts/autoconf/win32.config.cache.static" ]; then
+ echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win32.config.cache.static" ';'
+ fi
+ echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win32.config.cache" ';'
fi
- echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win64.config.cache" ';'
echo_setenv WIN32_WRAPPER_PATH "$WIN32_WRAPPER_PATH" ';'
echo_setenv PATH "$WIN32_WRAPPER_PATH:$P3" ';'
echo_envinfo
}
-echo_env_msys32 ()
+echo_env_msys ()
{
+ X64=$1
#echo_envinfo
if [ X"$SHELL" = X"" ]; then
echo "You need to export the shell variable first," \
@@ -779,23 +671,32 @@ echo_env_msys32 ()
WIN32_WRAPPER_PATH="$ERL_TOP/erts/etc/win32/msys_tools/vc:$ERL_TOP/erts/etc/win32/msys_tools"
echo_setenv OVERRIDE_TARGET win32 ';'
+ if [ X"$X64" = X"true" ]; then
+ echo_setenv CONFIG_SUBTYPE win64 ';'
+ fi
echo_setenv CC cc.sh ';'
echo_setenv CXX cc.sh ';'
echo_setenv AR ar.sh ';'
echo_setenv RANLIB true ';'
- if [ -f "$ERL_TOP/erts/autoconf/win32.config.cache.static" ]; then
- echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win32.config.cache.static" ';'
+ if [ X"$X64" = X"true" ]; then
+ if [ -f "$ERL_TOP/erts/autoconf/win64.config.cache.static" ]; then
+ echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win64.config.cache.static" ';'
+ fi
+ echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win64.config.cache" ';'
+ else
+ if [ -f "$ERL_TOP/erts/autoconf/win32.config.cache.static" ]; then
+ echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win32.config.cache.static" ';'
+ fi
+ echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win32.config.cache" ';'
fi
-
- echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win32.config.cache" ';'
echo_setenv WIN32_WRAPPER_PATH "$WIN32_WRAPPER_PATH" ';'
echo_setenv PATH "$WIN32_WRAPPER_PATH:$P3" ';'
echo_envinfo
}
-
-echo_env_msys64 ()
+echo_env_wsl ()
{
+ X64=$1
#echo_envinfo
if [ X"$SHELL" = X"" ]; then
echo "You need to export the shell variable first," \
@@ -807,50 +708,69 @@ echo_env_msys64 ()
exit 1
fi
echo_env_erltop
- # Try to cope with paths containing unexpected things like stray
- # mixed paths (c:/something/bin) and quotes. Only C and D drive
- # handled.
- P2=`echo :$PATH | \
- sed "s,\",,g;s,:\([a-zA-Z]\):,:/\L\1,g;s,^:,,"`
- P3=""
- save_pwd=`pwd`
- save_ifs=$IFS
- IFS=:
- for p in $P2; do
- if [ -d "$p" ]; then
- C1=`(cd "$p" && cmd //C "for %i in (".") do @echo %~fsi")`
- C2=`echo "$C1" | sed 's,^\([a-zA-Z]\):\\\\,/\L\1/,;s,\\\\,/,g'`
- else
- C2=""
- fi
- if [ ! -z "$C2" ]; then
- if [ -z "$P3" ];then
- P3="$C2"
- else
- P3="$P3:$C2"
- fi
- fi
- done
- IFS=$save_ifs
- WIN32_WRAPPER_PATH="$ERL_TOP/erts/etc/win32/msys_tools/vc:$ERL_TOP/erts/etc/win32/msys_tools"
+ WIN32_WRAPPER_PATH="$ERL_TOP/erts/etc/win32/wsl_tools/vc:$ERL_TOP/erts/etc/win32/wsl_tools"
+
+ if [ ! -n "`lookup_prog_in_path cl`" ]; then
+ if [ X"$X64" = X"true" ]; then
+ setup_win32_cl_env "x64" `wslpath -a -m erts/etc/win32/wsl_tools/SetupWSLcross.bat`
+ else
+ setup_win32_cl_env "x86" `wslpath -a -m erts/etc/win32/wsl_tools/SetupWSLcross.bat`
+ fi
+ fi
echo_setenv OVERRIDE_TARGET win32 ';'
- echo_setenv CONFIG_SUBTYPE win64 ';'
+ if [ X"$X64" = X"true" ]; then
+ echo_setenv CONFIG_SUBTYPE win64 ';'
+ fi
+ echo_setenv WSLcross true ';'
echo_setenv CC cc.sh ';'
echo_setenv CXX cc.sh ';'
echo_setenv AR ar.sh ';'
echo_setenv RANLIB true ';'
- if [ -f "$ERL_TOP/erts/autoconf/win64.config.cache.static" ]; then
- echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win64.config.cache.static" ';'
+ if [ X"$X64" = X"true" ]; then
+ if [ -f "$ERL_TOP/erts/autoconf/win64.config.cache.static" ]; then
+ echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win64.config.cache.static" ';'
+ fi
+ echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win64.config.cache" ';'
+ else
+ if [ -f "$ERL_TOP/erts/autoconf/win32.config.cache.static" ]; then
+ echo_setenv OVERRIDE_CONFIG_CACHE_STATIC "$ERL_TOP/erts/autoconf/win32.config.cache.static" ';'
+ fi
+ echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win32.config.cache" ';'
fi
-
- echo_setenv OVERRIDE_CONFIG_CACHE "$ERL_TOP/erts/autoconf/win64.config.cache" ';'
echo_setenv WIN32_WRAPPER_PATH "$WIN32_WRAPPER_PATH" ';'
- echo_setenv PATH "$WIN32_WRAPPER_PATH:$P3" ';'
+ echo_setenv PATH "$WIN32_WRAPPER_PATH:$PATH" ';'
echo_envinfo
}
+setup_win32_cl_env ()
+{
+ eval `cmd.exe /c $2 $1`
+ echo_setenv INCLUDE "$INCLUDE" ';'
+ echo_setenv LIB "$LIB" ';'
+ echo_setenv LIBPATH "$LIBPATH" ';'
+ echo_setenv VCToolsRedistDir "$VCToolsRedistDir" ';'
+ echo_setenv WSLENV "$WSLENV:WSLPATH/l:CLASSPATH/l" ';'
+
+ save_ifs=$IFS
+ IFS=:
+ WSLPATH=""
+ for p in $PATH; do
+ if [ -d "$p" ]; then
+ case "$p" in
+ /mnt/c/*)
+ WSLPATH=$WSLPATH:$p
+ ;;
+ *)
+ ;;
+ esac
+ fi
+ done
+ IFS=$save_ifs
+ echo_setenv WSLPATH "$WSLPATH" ';'
+}
+
lookup_prog_in_path ()
{
PROG=$1
@@ -892,13 +812,47 @@ setup_make ()
get_do_commit ()
{
- if [ "x$1" = "x" ]; then
- do_commit=true
- elif [ "$1" = "--no-commit" ]; then
- do_commit=false
+ case $version_controller in
+ git)
+ if [ "x$1" = "x" ]; then
+ do_commit=true
+ elif [ "$1" = "--no-commit" ]; then
+ do_commit=false
+ else
+ echo "Unknown option '$1'" 1>&2
+ exit 1
+ fi;;
+ none)
+ do_commit=false;;
+ esac
+}
+
+do_deprecations_git ()
+{
+ get_do_commit $1
+ setup_make
+
+ $MAKE MAKE="$MAKE" BOOTSTRAP_ROOT=$BOOTSTRAP_ROOT TARGET=$TARGET \
+ deprecations || exit 1;
+
+ $MAKE MAKE="$MAKE" BOOTSTRAP_ROOT=$BOOTSTRAP_ROOT TARGET=$TARGET \
+ primary_bootstrap || exit 1;
+
+ if [ $do_commit = true ]; then
+ git add -A bootstrap/lib/stdlib/ebin/otp_internal.beam
+ git add -A lib/stdlib/src/otp_internal.erl
+ git commit --no-verify -m 'Update deprecations'
+ echo ""
+ echo "Deprecations updated and committed."
+ echo ""
else
- echo "Unknown option '$1'" 1>&2
- exit 1
+ echo ""
+ echo "Deprecations updated. Use the following commands to stage "
+ echo "changed files:"
+ echo ""
+ echo "$ git add bootstrap/lib/stdlib/ebin/otp_internal.beam"
+ echo "$ git add lib/stdlib/src/otp_internal.erl"
+ echo ""
fi
}
@@ -923,7 +877,10 @@ do_primary_git ()
echo "Primary bootstrap updated and commited."
else
echo ""
- echo "Primary bootstrap rebuilt. Use \"git add bootstrap/...\" to stage changed files."
+ echo "Primary bootstrap rebuilt."
+ if [ $version_controller = git ]; then
+ echo "Use \"git add bootstrap/...\" to stage changed files."
+ fi
echo ""
fi
}
@@ -941,7 +898,10 @@ do_update_prel_git ()
echo "Preloaded updated and commited."
else
echo ""
- echo "Preloaded rebuilt. Use \"git add erts/preloaded/ebin/...\" to stage changed beam files."
+ echo "Preloaded rebuilt."
+ if [ $version_controller = git ]; then
+ echo "Use \"git add erts/preloaded/ebin/...\" to stage changed beam files."
+ fi
echo ""
fi
}
@@ -1141,8 +1101,13 @@ case $TARGET in
'the command'
exit 1
fi;;
- *)
- ;;
+ *)
+ if [ -x /bin/wslpath -a X"$OVERRIDE_TARGET" = X"" \
+ -a X"$1" != X"env_win32" -a X"$1" != X"env_msys32" -a X"$1" != X"env_msys64" ]; then
+ echo "Building linux binary; if you intended to cross build for win32 use" >&2
+ echo ' eval `./otp_build env_win32`\n' >&2
+ fi
+ ;;
esac
if [ ! -z "$OVERRIDE_TARGET" ]; then
@@ -1211,16 +1176,12 @@ case "$1" in
fi;
FLAVOR=$1
do_boot;;
+ update_deprecations)
+ do_deprecations_git "$2";;
update_primary)
- case $version_controller in
- git) do_primary_git "$2";;
- none) git_required ;;
- esac ;;
+ do_primary_git "$2";;
update_preloaded)
- case $version_controller in
- git) do_update_prel_git "$2";;
- none) git_required ;;
- esac ;;
+ do_update_prel_git "$2";;
primary)
echo "Primary bootstrap is under version control since R13";
echo "Use update_primary if you really are";
@@ -1257,26 +1218,29 @@ case "$1" in
do_debuginfo_win32 "$2";;
env_win32)
if [ x"$2" = x"x64" -o x"$2" = x"amd64" ]; then
- if [ -x /usr/bin/msys-?.0.dll ]; then
- echo_env_msys64
- else
- echo_env_win64
- fi
+ ISX64=true
+ fi
+ if [ -x /bin/wslpath ]; then
+ echo_env_wsl $ISX64
+ elif [ -x /usr/bin/msys-?.0.dll ]; then
+ echo_env_msys $ISX64
else
- if [ -x /usr/bin/msys-?.0.dll ]; then
- echo_env_msys32
- else
- echo_env_win32
- fi
+ echo_env_cygwin $ISX64
fi;;
env_mingw32)
echo_env_mingw32;;
env_win64)
- echo_env_win64;;
+ if [ -x /bin/wslpath ]; then
+ echo_env_wsl true
+ elif [ -x /usr/bin/msys-?.0.dll ]; then
+ echo_env_msys true
+ else
+ echo_env_cygwin true
+ fi;;
+ env_msys32)
+ echo_env_msys;;
env_msys64)
- echo_env_msys64;;
- env_vxworks)
- echo_env_vxworks "$2";;
+ echo_env_msys true;;
env_cross)
echo_env_cross "$2";;
env_bootstrap)
diff --git a/otp_versions.table b/otp_versions.table
index a87d5dad9e..e325d7d8bd 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,3 +1,40 @@
+OTP-23.3.1 : ssh-4.11.1 # asn1-5.0.14 common_test-1.20 compiler-7.6.7 crypto-4.9 debugger-5.0 dialyzer-4.3.1 diameter-2.2.3 edoc-0.12 eldap-1.2.9 erl_docgen-1.0.2 erl_interface-4.0.2 erts-11.2 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.2 jinterface-1.11.1 kernel-7.3 megaco-3.19.5 mnesia-4.19 observer-2.9.5 odbc-2.13.3 os_mon-2.6.1 parsetools-2.2 public_key-1.10 reltool-0.8 runtime_tools-1.16 sasl-4.0.2 snmp-5.8 ssl-10.3 stdlib-3.14.1 syntax_tools-2.5 tftp-1.0.2 tools-3.4.4 wx-1.9.3 xmerl-1.3.26 :
+OTP-23.3 : common_test-1.20 compiler-7.6.7 crypto-4.9 dialyzer-4.3.1 eldap-1.2.9 erts-11.2 jinterface-1.11.1 kernel-7.3 mnesia-4.19 odbc-2.13.3 public_key-1.10 runtime_tools-1.16 sasl-4.0.2 snmp-5.8 ssh-4.11 ssl-10.3 stdlib-3.14.1 syntax_tools-2.5 tools-3.4.4 wx-1.9.3 # asn1-5.0.14 debugger-5.0 diameter-2.2.3 edoc-0.12 erl_docgen-1.0.2 erl_interface-4.0.2 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.2 megaco-3.19.5 observer-2.9.5 os_mon-2.6.1 parsetools-2.2 reltool-0.8 tftp-1.0.2 xmerl-1.3.26 :
+OTP-23.2.7 : kernel-7.2.1 ssl-10.2.4 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 crypto-4.8.3 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.2 erts-11.1.8 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.2 jinterface-1.11 megaco-3.19.5 mnesia-4.18.1 observer-2.9.5 odbc-2.13.2 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.7.3 ssh-4.10.8 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.3 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.6 : inets-7.3.2 ssh-4.10.8 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 crypto-4.8.3 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.2 erts-11.1.8 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 jinterface-1.11 kernel-7.2 megaco-3.19.5 mnesia-4.18.1 observer-2.9.5 odbc-2.13.2 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.7.3 ssl-10.2.3 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.3 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.5 : erts-11.1.8 ssl-10.2.3 tools-3.4.3 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 crypto-4.8.3 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.2 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 megaco-3.19.5 mnesia-4.18.1 observer-2.9.5 odbc-2.13.2 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.7.3 ssh-4.10.7 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.4 : snmp-5.7.3 ssl-10.2.2 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 crypto-4.8.3 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.2 erts-11.1.7 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 megaco-3.19.5 mnesia-4.18.1 observer-2.9.5 odbc-2.13.2 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 ssh-4.10.7 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.3 : crypto-4.8.3 erts-11.1.7 snmp-5.7.2 ssh-4.10.7 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.2 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 megaco-3.19.5 mnesia-4.18.1 observer-2.9.5 odbc-2.13.2 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 ssl-10.2.1 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.2 : crypto-4.8.2 erl_interface-4.0.2 erts-11.1.6 megaco-3.19.5 odbc-2.13.2 snmp-5.7.1 ssl-10.2.1 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 mnesia-4.18.1 observer-2.9.5 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 ssh-4.10.6 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2.1 : erts-11.1.5 # asn1-5.0.14 common_test-1.19.1 compiler-7.6.6 crypto-4.8.1 debugger-5.0 dialyzer-4.3 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.2 erl_interface-4.0.1 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3.1 jinterface-1.11 kernel-7.2 megaco-3.19.4 mnesia-4.18.1 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 public_key-1.9.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.7 ssh-4.10.6 ssl-10.2 stdlib-3.14 syntax_tools-2.4 tftp-1.0.2 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 :
+OTP-23.2 : common_test-1.19.1 compiler-7.6.6 crypto-4.8.1 dialyzer-4.3 erl_docgen-1.0.2 erts-11.1.4 inets-7.3.1 kernel-7.2 megaco-3.19.4 mnesia-4.18.1 public_key-1.9.2 snmp-5.7 ssh-4.10.6 ssl-10.2 stdlib-3.14 syntax_tools-2.4 tools-3.4.2 wx-1.9.2 xmerl-1.3.26 # asn1-5.0.14 debugger-5.0 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_interface-4.0.1 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 jinterface-1.11 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 tftp-1.0.2 :
+OTP-23.1.5 : ssh-4.10.5 # asn1-5.0.14 common_test-1.19 compiler-7.6.5 crypto-4.8 debugger-5.0 dialyzer-4.2.1 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.1 erl_interface-4.0.1 erts-11.1.3 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3 jinterface-1.11 kernel-7.1 megaco-3.19.3 mnesia-4.18 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 public_key-1.9.1 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.6.1 ssl-10.1 stdlib-3.13.2 syntax_tools-2.3.1 tftp-1.0.2 tools-3.4.1 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.1.4 : ssh-4.10.4 # asn1-5.0.14 common_test-1.19 compiler-7.6.5 crypto-4.8 debugger-5.0 dialyzer-4.2.1 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.1 erl_interface-4.0.1 erts-11.1.3 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3 jinterface-1.11 kernel-7.1 megaco-3.19.3 mnesia-4.18 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 public_key-1.9.1 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.6.1 ssl-10.1 stdlib-3.13.2 syntax_tools-2.3.1 tftp-1.0.2 tools-3.4.1 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.1.3 : erts-11.1.3 ssh-4.10.3 # asn1-5.0.14 common_test-1.19 compiler-7.6.5 crypto-4.8 debugger-5.0 dialyzer-4.2.1 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.1 erl_interface-4.0.1 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3 jinterface-1.11 kernel-7.1 megaco-3.19.3 mnesia-4.18 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 public_key-1.9.1 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.6.1 ssl-10.1 stdlib-3.13.2 syntax_tools-2.3.1 tftp-1.0.2 tools-3.4.1 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.1.2 : compiler-7.6.5 erts-11.1.2 # asn1-5.0.14 common_test-1.19 crypto-4.8 debugger-5.0 dialyzer-4.2.1 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.1 erl_interface-4.0.1 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3 jinterface-1.11 kernel-7.1 megaco-3.19.3 mnesia-4.18 observer-2.9.5 odbc-2.13.1 os_mon-2.6.1 parsetools-2.2 public_key-1.9.1 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.6.1 ssh-4.10.2 ssl-10.1 stdlib-3.13.2 syntax_tools-2.3.1 tftp-1.0.2 tools-3.4.1 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.1.1 : compiler-7.6.4 erts-11.1.1 os_mon-2.6.1 public_key-1.9.1 ssh-4.10.2 # asn1-5.0.14 common_test-1.19 crypto-4.8 debugger-5.0 dialyzer-4.2.1 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0.1 erl_interface-4.0.1 et-1.6.4 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3 jinterface-1.11 kernel-7.1 megaco-3.19.3 mnesia-4.18 observer-2.9.5 odbc-2.13.1 parsetools-2.2 reltool-0.8 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.6.1 ssl-10.1 stdlib-3.13.2 syntax_tools-2.3.1 tftp-1.0.2 tools-3.4.1 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.1 : asn1-5.0.14 compiler-7.6.3 crypto-4.8 dialyzer-4.2.1 erl_docgen-1.0.1 erl_interface-4.0.1 erts-11.1 eunit-2.6 ftp-1.0.5 hipe-4.0.1 inets-7.3 kernel-7.1 megaco-3.19.3 mnesia-4.18 observer-2.9.5 odbc-2.13.1 os_mon-2.6 public_key-1.9 runtime_tools-1.15.1 sasl-4.0.1 snmp-5.6.1 ssh-4.10.1 ssl-10.1 stdlib-3.13.2 syntax_tools-2.3.1 tools-3.4.1 # common_test-1.19 debugger-5.0 diameter-2.2.3 edoc-0.12 eldap-1.2.8 et-1.6.4 jinterface-1.11 parsetools-2.2 reltool-0.8 tftp-1.0.2 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.0.4 : erts-11.0.4 megaco-3.19.2 stdlib-3.13.1 # asn1-5.0.13 common_test-1.19 compiler-7.6.2 crypto-4.7 debugger-5.0 dialyzer-4.2 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0 erl_interface-4.0 et-1.6.4 eunit-2.5 ftp-1.0.4 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 reltool-0.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 syntax_tools-2.3 tftp-1.0.2 tools-3.4 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.0.3 : compiler-7.6.2 erts-11.0.3 # asn1-5.0.13 common_test-1.19 crypto-4.7 debugger-5.0 dialyzer-4.2 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0 erl_interface-4.0 et-1.6.4 eunit-2.5 ftp-1.0.4 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 megaco-3.19.1 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 reltool-0.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tftp-1.0.2 tools-3.4 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.0.2 : erts-11.0.2 megaco-3.19.1 # asn1-5.0.13 common_test-1.19 compiler-7.6.1 crypto-4.7 debugger-5.0 dialyzer-4.2 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0 erl_interface-4.0 et-1.6.4 eunit-2.5 ftp-1.0.4 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 reltool-0.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tftp-1.0.2 tools-3.4 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.0.1 : compiler-7.6.1 erts-11.0.1 # asn1-5.0.13 common_test-1.19 crypto-4.7 debugger-5.0 dialyzer-4.2 diameter-2.2.3 edoc-0.12 eldap-1.2.8 erl_docgen-1.0 erl_interface-4.0 et-1.6.4 eunit-2.5 ftp-1.0.4 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 megaco-3.19 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 reltool-0.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tftp-1.0.2 tools-3.4 wx-1.9.1 xmerl-1.3.25 :
+OTP-23.0 : asn1-5.0.13 common_test-1.19 compiler-7.6 crypto-4.7 debugger-5.0 dialyzer-4.2 edoc-0.12 erl_docgen-1.0 erl_interface-4.0 erts-11.0 eunit-2.5 hipe-4.0 inets-7.2 jinterface-1.11 kernel-7.0 megaco-3.19 mnesia-4.17 observer-2.9.4 odbc-2.13 os_mon-2.5.2 parsetools-2.2 public_key-1.8 runtime_tools-1.15 sasl-4.0 snmp-5.6 ssh-4.10 ssl-10.0 stdlib-3.13 syntax_tools-2.3 tools-3.4 wx-1.9.1 xmerl-1.3.25 # diameter-2.2.3 eldap-1.2.8 et-1.6.4 ftp-1.0.4 reltool-0.8 tftp-1.0.2 :
+OTP-22.3.4.16 : erts-10.7.2.8 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.3 crypto-4.6.5.2 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.3 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.4 ssh-4.9.1.2 ssl-9.6.2.3 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.15 : crypto-4.6.5.2 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.3 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.7 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.3 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.4 ssh-4.9.1.2 ssl-9.6.2.3 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.14 : compiler-7.5.4.3 erts-10.7.2.7 # asn1-5.0.12 common_test-1.18.2 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.3 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.4 ssh-4.9.1.2 ssl-9.6.2.3 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.13 : compiler-7.5.4.2 erts-10.7.2.6 megaco-3.18.8.3 snmp-5.5.0.4 # asn1-5.0.12 common_test-1.18.2 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 ssh-4.9.1.2 ssl-9.6.2.3 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.12 : erts-10.7.2.5 ssl-9.6.2.3 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.2 mnesia-4.16.3.1 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.3 ssh-4.9.1.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.11 : erts-10.7.2.4 mnesia-4.16.3.1 os_mon-2.5.1.1 ssh-4.9.1.2 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.2 observer-2.9.3 odbc-2.12.4 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.3 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.10 : megaco-3.18.8.2 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.3 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.3 ssh-4.9.1.1 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.9 : ssh-4.9.1.1 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.3 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.3 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.8 : snmp-5.5.0.3 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.3 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 ssh-4.9.1 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.7 : snmp-5.5.0.2 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.3 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 ssh-4.9.1 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.6 : erts-10.7.2.3 inets-7.1.3.3 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.1 ssh-4.9.1 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.5 : inets-7.1.3.2 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 crypto-4.6.5.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.1 ssh-4.9.1 ssl-9.6.2.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.4 : crypto-4.6.5.1 erts-10.7.2.2 ssl-9.6.2.2 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4.1 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.1 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5.0.1 ssh-4.9.1 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.3 : compiler-7.5.4.1 snmp-5.5.0.1 # asn1-5.0.12 common_test-1.18.2 crypto-4.6.5 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.1 et-1.6.4 eunit-2.4.1 ftp-1.0.4.1 hipe-3.19.3 inets-7.1.3.1 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 ssh-4.9.1 ssl-9.6.2.1 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.2 : ftp-1.0.4.1 inets-7.1.3.1 ssl-9.6.2.1 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4 crypto-4.6.5 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.2.1 et-1.6.4 eunit-2.4.1 hipe-3.19.3 jinterface-1.10.1 kernel-6.5.2.1 megaco-3.18.8.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5 ssh-4.9.1 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
+OTP-22.3.4.1 : erts-10.7.2.1 kernel-6.5.2.1 megaco-3.18.8.1 # asn1-5.0.12 common_test-1.18.2 compiler-7.5.4 crypto-4.6.5 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4 hipe-3.19.3 inets-7.1.3 jinterface-1.10.1 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5 ssh-4.9.1 ssl-9.6.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
OTP-22.3.4 : asn1-5.0.12 erts-10.7.2 # common_test-1.18.2 compiler-7.5.4 crypto-4.6.5 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 et-1.6.4 eunit-2.4.1 ftp-1.0.4 hipe-3.19.3 inets-7.1.3 jinterface-1.10.1 kernel-6.5.2 megaco-3.18.8 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5 ssh-4.9.1 ssl-9.6.2 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
OTP-22.3.3 : ssh-4.9.1 ssl-9.6.2 # asn1-5.0.11 common_test-1.18.2 compiler-7.5.4 crypto-4.6.5 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.1 et-1.6.4 eunit-2.4.1 ftp-1.0.4 hipe-3.19.3 inets-7.1.3 jinterface-1.10.1 kernel-6.5.2 megaco-3.18.8 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
OTP-22.3.2 : asn1-5.0.11 # common_test-1.18.2 compiler-7.5.4 crypto-4.6.5 debugger-4.2.8 dialyzer-4.1.1 diameter-2.2.3 edoc-0.11 eldap-1.2.8 erl_docgen-0.11 erl_interface-3.13.2 erts-10.7.1 et-1.6.4 eunit-2.4.1 ftp-1.0.4 hipe-3.19.3 inets-7.1.3 jinterface-1.10.1 kernel-6.5.2 megaco-3.18.8 mnesia-4.16.3 observer-2.9.3 odbc-2.12.4 os_mon-2.5.1 parsetools-2.1.8 public_key-1.7.2 reltool-0.8 runtime_tools-1.14 sasl-3.4.2 snmp-5.5 ssh-4.9 ssl-9.6.1 stdlib-3.12.1 syntax_tools-2.2.1 tftp-1.0.2 tools-3.3.1 wx-1.9 xmerl-1.3.24 :
@@ -30,6 +67,13 @@ OTP-22.0.3 : compiler-7.4.2 dialyzer-4.0.1 erts-10.4.2 ssl-9.3.2 stdlib-3.9.2 #
OTP-22.0.2 : compiler-7.4.1 crypto-4.5.1 erts-10.4.1 stdlib-3.9.1 # asn1-5.0.9 common_test-1.17.3 debugger-4.2.7 dialyzer-4.0 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 ssl-9.3.1 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
OTP-22.0.1 : ssl-9.3.1 # asn1-5.0.9 common_test-1.17.3 compiler-7.4 crypto-4.5 debugger-4.2.7 dialyzer-4.0 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 erts-10.4 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 stdlib-3.9 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
OTP-22.0 : asn1-5.0.9 common_test-1.17.3 compiler-7.4 crypto-4.5 debugger-4.2.7 dialyzer-4.0 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 erts-10.4 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 ssl-9.3 stdlib-3.9 syntax_tools-2.2 tools-3.2 wx-1.8.8 xmerl-1.3.21 # diameter-2.2.1 et-1.6.4 eunit-2.3.7 ftp-1.0.2 parsetools-2.1.8 tftp-1.0.1 :
+OTP-21.3.8.21 : erts-10.3.5.16 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.3 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3.1 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6.5 ssl-9.2.3.7 stdlib-3.8.2.4 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
+OTP-21.3.8.20 : erl_interface-3.11.3.1 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.3 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erts-10.3.5.15 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6.5 ssl-9.2.3.7 stdlib-3.8.2.4 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
+OTP-21.3.8.19 : crypto-4.4.2.3 erts-10.3.5.15 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6.5 ssl-9.2.3.7 stdlib-3.8.2.4 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
+OTP-21.3.8.18 : erts-10.3.5.14 ssh-4.7.6.5 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssl-9.2.3.7 stdlib-3.8.2.4 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
+OTP-21.3.8.17 : erts-10.3.5.13 ssl-9.2.3.7 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6.4 stdlib-3.8.2.4 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
+OTP-21.3.8.16 : erts-10.3.5.12 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6.4 ssl-9.2.3.6 stdlib-3.8.2.4 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
+OTP-21.3.8.15 : erts-10.3.5.11 ssh-4.7.6.4 ssl-9.2.3.6 stdlib-3.8.2.4 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
OTP-21.3.8.14 : erts-10.3.5.10 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6.3 ssl-9.2.3.5 stdlib-3.8.2.3 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
OTP-21.3.8.13 : erts-10.3.5.9 stdlib-3.8.2.3 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 crypto-4.4.2.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6.3 ssl-9.2.3.5 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
OTP-21.3.8.12 : crypto-4.4.2.2 erts-10.3.5.8 ssh-4.7.6.3 # asn1-5.0.8 common_test-1.17.2.1 compiler-7.3.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.7 erl_docgen-0.9 erl_interface-3.11.3 et-1.6.4 eunit-2.3.7 ftp-1.0.2.2 hipe-3.18.3 inets-7.0.7.2 jinterface-1.9.1 kernel-6.3.1.3 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.6.1 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssl-9.2.3.5 stdlib-3.8.2.2 syntax_tools-2.1.7.1 tftp-1.0.1 tools-3.1.0.1 wx-1.8.7 xmerl-1.3.20.1 :
diff --git a/prebuild.delete b/prebuild.delete
index b1b18adbcb..17efc89229 100644
--- a/prebuild.delete
+++ b/prebuild.delete
@@ -1,2 +1,4 @@
bootstrap/lib
bootstrap/target
+.git
+scripts
diff --git a/scripts/build-otp b/scripts/build-otp
index afab1f9136..f3f7f8ee86 100755
--- a/scripts/build-otp
+++ b/scripts/build-otp
@@ -58,6 +58,9 @@ if [ "$1" = "docs" ]; then
DOC_TARGET=${TRAVIS_TAG:-$DOC_TARGET}
TESTROOT=$PWD/$DOC_TARGET do_and_log "Building documentation" make release_docs
do_and_log "Linting documentation" make xmllint
+ do_and_log "Checking html links" scripts/otp_html_check $PWD/$DOC_TARGET doc/index.html
+ do_and_log "Test chunks" erlc lib/stdlib/test/shell_docs_SUITE.erl &&
+ ct_run -no_auto_compile -suite shell_docs_SUITE -case render
# The code below prepares this build to be used as a deploy to
# github pages for documentation.
if [ "$TRAVIS_PULL_REQUEST" = "false" -a "$TRAVIS_TAG" = "" -a "$TRAVIS_REPO_SLUG" = "erlang/otp" ]; then
diff --git a/scripts/build-otp-tar b/scripts/build-otp-tar
new file mode 100755
index 0000000000..fa7ad2c19d
--- /dev/null
+++ b/scripts/build-otp-tar
@@ -0,0 +1,701 @@
+#!/bin/bash
+
+#
+# Description:
+# Create one gzipped-tar file containing pre-built platform independent
+# OTP code and one gzipped-tar file containing clean source code.
+# Author: Rickard Green
+#
+
+Revision=X
+revision="$Revision: 1.8 $Revision"
+version=`echo $revision | sed "s|[^0-9]*\([0-9.]*\).*|\1|g"`
+
+# 'global_restore' contains files and/or directories that always
+# should be restored to source state (@TARGET@ will be replaced
+# by actual target name).
+global_restore="@TARGET@ config.status config.log core core.*"
+
+prebuilt_filename=prebuilt.files
+#configure_args="--disable-smp-support --disable-hybrid-heap"
+configure_args="--disable-hybrid-heap"
+pbskip_name=prebuild.skip
+pbdel_name=prebuild.delete
+skip_name=SKIP
+script_name=`basename $0`
+verbose=true
+work_dir_used=false
+remove_work_dir=false
+failing=false
+got_warning=false
+rm=/bin/rm
+gtar=tar
+tmp_dir=/tmp
+build_log=
+work_dir=
+build_dir=
+cln_tgz=SRC_CLN.tar.gz
+bld_tgz=SRC_PREBLD.tar.gz
+src_tgz=
+newfiles_log=
+work_dir=
+tmp_work_dir=
+
+print_usage () {
+ echo "Usage:
+$script_name
+[-b|--build-dir <directory>]
+[-d|--deleted-files-log <filename>]
+[-g|--gtar <gtar>]
+[-h|--help]
+[-l|--build-log <filename>]
+[-n|--new-files-log <filename>]
+[-o|--output-filenames <clean source filename> <pre-build filename>]
+[-r|--remove-working-dir]
+[-s|--silent]
+[-v|--version]
+[-w|--working-dir <existing directory>]
+<source filename>"
+}
+
+print_help () {
+ echo "--- otp_prebuild version $version -------------------------------------------"
+ echo `print_usage`
+ echo ""
+ echo " Mandatory parameters:"
+ echo " <source filename> --- Filename of gzipped tar"
+ echo " file containing the OTP"
+ echo " source (produced by the"
+ echo " otp_pack script)"
+ echo ""
+ echo " Optional parameters:"
+ echo " -b|--build-dir <directory> --- Directory containing"
+ echo " a build from exactly the"
+ echo " same source as specified"
+ echo " by the mandatory"
+ echo " parameter. If this"
+ echo " parameter isn't given,"
+ echo " OTP will be built."
+ echo " -d|--deleted-files-log <filename> --- Filename of file to log"
+ echo " deleted files to"
+ echo " (deleted files in the"
+ echo " pre-build source result"
+ echo " compared to the clean"
+ echo " source result)."
+ echo " Defaults to /dev/null."
+ echo " -g|--gtar <gtar> --- GNU tar to use. Defaults"
+ echo " to 'tar' in path."
+ echo " -h|--help --- Display (this) help"
+ echo " text and exit."
+ echo " -l|--build-log <filename> --- Filename of file to log"
+ echo " OTP build results to."
+ echo " Defaults to /dev/null."
+ echo " -n|--new-files-log <filename> --- Filename of file to log"
+ echo " new files to (new files"
+ echo " in the pre-build source"
+ echo " result compared to the"
+ echo " clean source result)."
+ echo " Defaults to /dev/null."
+ echo " -o|--output-filenames <clean source filename> <pre-build filename>"
+ echo " --- Filename of clean source"
+ echo " result and filename of"
+ echo " pre-build source result."
+ echo " Both as gzipped tar "
+ echo " files. Defaults to "
+ echo " 'SRC_CLN.tar.gz', resp."
+ echo " 'SRC_PREBLD.tar.gz'."
+ echo " -r|--remove-working-dir --- Remove content of"
+ echo " used, existing working"
+ echo " directory (passed with"
+ echo " the -w parameter) when"
+ echo " done."
+ echo " -s|--silent --- Silent mode."
+ echo " -v|--version --- Print version and exit."
+ echo " -w|--working-dir <existing directory> --- An existing working"
+ echo " directory to use."
+ echo " If not passed, a"
+ echo " temporary working"
+ echo " directory will be"
+ echo " created, used, and then"
+ echo " removed."
+ echo ""
+ echo "--- otp_prebuild version $version -------------------------------------------"
+}
+
+progress_start () {
+ if [ $verbose = true ]; then
+ printf "${script_name}: $@..."
+ fi
+}
+
+progress () {
+ if [ $verbose = true ]; then
+ if [ $got_warning = true ]; then
+ got_warning=false
+ else
+ if [ $failing = false ]; then
+ printf " ok\n"
+ fi
+ fi
+ printf "${script_name}: $@..."
+ fi
+}
+
+progress_end () {
+ if [ $verbose = true ]; then
+ if [ $got_warning = true ]; then
+ got_warning=false
+ else
+ if [ $failing = false ]; then
+ printf " ok\n"
+ fi
+ fi
+ printf "${script_name}: done\n"
+ fi
+}
+
+remove () {
+ #
+ # As an extra safety precaution, 'remove' requires the path of
+ # the file or directory to be removed to begin with $work_dir.
+ #
+ if [ "X${work_dir}" = "X" ]; then
+ error "remove() called before working dir has been initialized"
+ fi
+ while [ $# -gt 0 ]; do
+ case "X$1" in
+ X${work_dir}*)
+ ;;
+ *)
+ error "Refusing to remove $1 since its path doesn't begin with working directory (${work_dir})";;
+ esac
+ $rm -rf $1
+ if [ $? -ne 0 ]; then
+ error "Failed to remove $1"
+ fi
+ shift
+ done
+}
+
+cleanup_work_dir () {
+ if [ "x$tmp_work_dir" != "x" ]; then
+ #
+ # Temporary working directories should always be removed
+ #
+ if [ $work_dir_used = true ]; then
+ progress "Removing temporary working directory"
+ if [ "x$work_dir" != "x" ]; then
+ $rm -rf $work_dir
+ fi
+ fi
+ else
+ if [ $remove_work_dir != false -a $work_dir_used != false ]; then
+ progress "Removing content of working directory"
+ if [ "x$work_dir" != "x" ]; then
+ $rm -rf $work_dir/*
+ fi
+ fi
+ fi
+}
+
+error () {
+ failing=true
+ echo "" 1>&2
+ echo "ERROR: $@" 1>&2
+ echo `cleanup_work_dir` 1>&2
+ echo "" 1>&2
+ exit 1
+}
+
+warning () {
+ got_warning=true
+ echo " WARNING: $@"
+}
+
+usage_error () {
+ failing=true
+ echo "" 1>&2
+ echo "ERROR: $@" 1>&2
+ echo `print_usage` 1>&2
+ echo `cleanup_work_dir` 1>&2
+ echo "" 1>&2
+ exit 1
+}
+
+missing_param_value () {
+ failing=true
+ echo "" 1>&2
+ echo "Missing value(s) to parameter $1" 1>&2
+ echo `print_usage` 1>&2
+ echo `cleanup_work_dir` 1>&2
+ echo "" 1>&2
+ exit 1
+}
+
+valid_values () {
+ while [ $# -gt 0 ]; do
+ case $1 in
+ -*) return 1;;
+ *) ;;
+ esac
+ shift
+ done
+ return 0
+}
+
+copy () {
+ if [ $# -ne 2 ]; then
+ error "copy: bad number of arguments: $#"
+ fi
+ local from_dir=`dirname $1`
+ local from_obj=`basename $1`
+ local to_dir=$2
+ gtar_err=`(( $gtar -c -C $from_dir -f - $from_obj || echo ERROR 1>&2 ) | ( $gtar -x -B -p -C $to_dir -f - || echo ERROR 1>&2 )) 2>&1`
+ if [ "x$gtar_err" != "x" ]; then
+ echo "$gtar_err"
+ error "Failed to copy $1 to $2"
+ fi
+}
+
+restore () {
+ if [ $# -ne 1 ]; then
+ error "restore: bad number of arguments: $#";
+ fi
+
+ local obj=$1
+ local src_exist=false
+ local obj_type=
+
+ if [ -d $prebld_root/$obj ]; then
+ if [ -d $src_root/$obj ]; then
+ src_exist=true;
+ fi
+ obj_type="directory"
+ else
+ if [ -f $src_root/$obj ]; then
+ src_exist=true;
+ fi
+ obj_type="file"
+ fi
+
+ progress "Removing $obj_type $obj from pre-build-directory"
+ remove $prebld_root/$obj
+
+ if [ $src_exist = true ]; then
+ progress "Copying $obj_type $obj from source-directory to pre-build-directory"
+ copy $src_root/$obj $prebld_root/`dirname $obj`
+ fi
+}
+
+check_filename () { # <file containing file name> <dir> <filename>
+ if [ $# -ne 3 ]; then
+ error "check_filename: bad number of arguments: $#"
+ fi
+ case $3 in
+ /*|../*|*/../*|*/..|*/./*|*/.)
+ error "File path not allowed ($3) in: $1";;
+ .)
+ echo "$2";;
+ *)
+ echo "$2/$3";;
+ esac
+}
+
+start_dir=`pwd`
+if [ ! -d $tmp_dir ]; then
+ $tmp_dir=$start_dir
+fi
+
+while [ $# -gt 0 ]; do
+ case $1 in
+ -b|--build-dir)
+ (test $# -gt 1 && valid_values $2) || missing_param_value $1
+ shift
+ build_dir=$1;;
+ -d|--deleted-files-log)
+ (test $# -gt 1 && valid_values $2) || missing_param_value $1
+ shift
+ deletedfiles_log=$1;;
+ -g|--gtar)
+ (test $# -gt 1 && valid_values $2) || missing_param_value $1
+ shift
+ gtar=$1;;
+ -h|--help)
+ print_help
+ exit 0;;
+ -l|--build-log)
+ (test $# -gt 1 && valid_values $2) || missing_param_value $1
+ shift
+ build_log=$1;;
+ -n|--new-files-log)
+ (test $# -gt 1 && valid_values $2) || missing_param_value $1
+ shift
+ newfiles_log=$1;;
+ -o|--output-filenames)
+ (test $# -gt 2 && valid_values $2 $3) || missing_param_value $1
+ shift
+ cln_tgz=$1
+ shift
+ bld_tgz=$1;;
+ -r|--remove-working-dir)
+ remove_work_dir=true;;
+ -s|--silent)
+ verbose=false;;
+ -v|--version)
+ echo "otp_prebuild version $version"
+ exit 0;;
+ -w|--working-dir)
+ (test $# -gt 1 && valid_values $2) || missing_param_value $1
+ shift
+ work_dir=$1;;
+ -*)
+ usage_error "Unknown argument: $1";;
+ *)
+ if [ "x$src_tgz" != "x" ]; then
+ usage_error "Multiple source filnames: $src_tgz; $1"
+ fi
+ src_tgz=$1;;
+ esac
+ shift
+done
+
+
+progress_start "Verifying arguments"
+
+case "x$bld_tgz" in
+ x)
+ usage_error "Argument -o|--output-filenames missing";;
+ x/*)
+ ;;
+ *)
+ bld_tgz=$start_dir/$bld_tgz;;
+esac
+
+case "x$cln_tgz" in
+ x)
+ usage_error "Argument -o|--output-filenames missing";;
+ x/*)
+ ;;
+ *)
+ cln_tgz=$start_dir/$cln_tgz;;
+esac
+
+case "x$build_log" in
+ x) ;;
+ x/*)
+ ;;
+ *)
+ build_log=$start_dir/$build_log;;
+esac
+
+case "x$src_tgz" in
+ x)
+ usage_error "Mandatory argument <source filename> missing";;
+ x/*)
+ ;;
+ *)
+ src_tgz=$start_dir/$src_tgz;;
+esac
+
+
+if [ "x$work_dir" != "x" ]; then
+ case $work_dir in
+ /*) ;;
+ *) work_dir=$start_dir/$work_dir;;
+ esac
+ progress "Using existing working directory: $work_dir"
+ if [ ! -d $work_dir ]; then
+ error "Not a directory: $work_dir"
+ fi
+else
+ tmp_work_dir=$tmp_dir/otp_prebuild.$$
+ progress "Creating working directory: $tmp_work_dir"
+
+ mkdir $tmp_work_dir
+ if [ $? -ne 0 ]; then
+ error "Failed to create working directory: $tmp_work_dir"
+ fi
+ work_dir=$tmp_work_dir
+ work_dir_used=true
+fi
+
+if [ "x$build_dir" != "x" ]; then
+
+ progress "Using already built OTP distibution in: $build_dir"
+
+else
+
+ build_root=$work_dir/build
+
+ progress "Creating build-directory: $build_root"
+
+ mkdir $build_root
+ if [ $? -ne 0 ]; then
+ error "Failed to build-directory"
+ fi
+
+ work_dir_used=true
+
+ progress "Unpacking OTP source code into build-directory"
+
+ $gtar -z -x -C $build_root -f $src_tgz
+ if [ $? -ne 0 ]; then
+ error "Failed to unpack source"
+ fi
+ cd $build_root/*
+ if [ $? -ne 0 ]; then
+ error "Failed to change directory into unpacked source"
+ fi
+ build_dir=`pwd`
+ if [ ! -f ./otp_build ]; then
+ usage_error "Bad build-directory"
+ fi
+
+ export ERL_TOP=$build_dir
+
+ if [ "x$build_log" = "x" ]; then
+ build_log=/dev/null
+ progress "Using $build_log as build log"
+ else
+ progress "Creating build log: $build_log"
+ touch $build_log >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ error "Failed to create build log"
+ fi
+ fi
+
+ progress "Writing environment to build log"
+ echo " === Environment ==================================== " >> $build_log
+ env >> $build_log
+
+ progress "Running autoconf in OTP"
+ echo " " >> $build_log
+ echo " === Running autoconf in OTP ================================ " >> $build_log
+ echo " " >> $build_log
+ echo "./otp_build autoconf" >> $build_log
+ ./otp_build autoconf >> $build_log 2>&1
+ if [ $? -ne 0 ]; then
+ error "Failed to run autoconf in OTP"
+ fi
+
+ progress "Configuring OTP"
+ echo " " >> $build_log
+ echo " === Configuring OTP ================================ " >> $build_log
+ echo " " >> $build_log
+ echo "./otp_build configure $configure_args" >> $build_log
+ ./otp_build configure $configure_args >> $build_log 2>&1
+ if [ $? -ne 0 ]; then
+ error "Failed to configure OTP"
+ fi
+
+ progress "Building OTP"
+ echo " " >> $build_log
+ echo " === Building OTP =================================== " >> $build_log
+ echo " " >> $build_log
+ echo "./otp_build boot -a" >> $build_log
+ ./otp_build boot -a >> $build_log 2>&1
+ if [ $? -ne 0 ]; then
+ error "Failed to build OTP"
+ fi
+ echo " " >> $build_log
+ echo " ==================================================== " >> $build_log
+
+ cd $start_dir
+fi
+
+if [ ! -d $build_dir -o ! -f $build_dir/otp_build ]; then
+ usage_error "Bad build-directory"
+fi
+
+build_dir_name=`basename $build_dir`
+
+prebld_root=$work_dir/prebuild
+progress "Creating pre-build-directory: $prebld_root"
+mkdir $prebld_root
+if [ $? -ne 0 ]; then
+ error "Failed to create temporary pack dir"
+fi
+
+work_dir_used=true
+
+progress "Copying OTP build into pre-build-directory: $prebld_root"
+copy $build_dir $prebld_root
+
+prebld_dir=$prebld_root/$build_dir_name
+
+src_root=$work_dir/src
+progress "Creating source-directory: $src_root"
+mkdir $src_root
+if [ $? -ne 0 ]; then
+ error "Failed to source-directory"
+fi
+
+progress "Unpacking OTP source code into source-directory"
+$gtar -z -x -C $src_root -f $src_tgz
+if [ $? -ne 0 ]; then
+ error "Failed to unpack source"
+fi
+
+src_dir=$src_root/$build_dir_name
+if [ ! -d $src_dir -o ! -f $src_dir/otp_build ]; then
+ usage_error "Source and build mismatch"
+fi
+
+progress "Checking target directory name"
+target_dirname=`$prebld_dir/erts/autoconf/config.guess`
+if [ $? -ne 0 ]; then
+ error "Failed to check target directory name"
+fi
+if [ "x$target_dirname" = "x" ]; then
+ error "No target directory name found"
+fi
+
+global_restore=`echo $global_restore | sed "s|@TARGET@|$target_dirname|g"`
+if [ $? -ne 0 ]; then
+ error "Failed to replace @TARGET@ with $target_dirname in global_restore"
+fi
+
+cd $prebld_root
+for restore_name in $global_restore; do
+ progress "Searching for $restore_name files/directories in pre-build-directory"
+ for restore_obj in `find . -name $restore_name`; do
+ restore $restore_obj
+ done
+done
+
+progress "Searching for $skip_name files"
+cd $prebld_root
+skip_files=`find . -name $skip_name`
+
+for skip_file in $skip_files; do
+ # Normally these files should be removed, but if a skip file is part of
+ # the source it shouldn't be removed.
+ restore $skip_file
+done
+
+progress "Searching for $pbskip_name files in source-directory"
+cd $src_root
+skip_files=`find . -name $pbskip_name`
+
+for skip_file in $skip_files; do
+ progress "Removing $skip_file from source-directory"
+ remove $src_root/$skip_file
+done
+
+progress "Searching for $pbdel_name files in source-directory"
+cd $src_root
+delete_files=`find . -name $pbdel_name`
+
+for delete_file in $delete_files; do
+ progress "Removing $delete_file from source-directory"
+ remove $src_root/$delete_file
+done
+
+progress "Searching for $pbskip_name files in pre-build-directory"
+cd $prebld_root
+skip_files=`find . -name $pbskip_name`
+
+for skip_file in $skip_files; do
+ dir=`dirname $skip_file`
+ restore_objs=`cat $skip_file`
+ for rf in $restore_objs; do
+ restore `check_filename $skip_file $dir $rf`
+ done
+
+ progress "Removing $skip_file from pre-build-directory"
+ remove $prebld_root/$skip_file
+done
+
+progress "Searching for $pbdel_name files in pre-build-directory"
+cd $prebld_root
+delete_files=`find . -name $pbdel_name`
+
+for delete_file in $delete_files; do
+ dir=`dirname $delete_file`
+ delete_objs=`cat $delete_file`
+ for delete_obj in $delete_objs; do
+ dobj=`check_filename $delete_file $dir $delete_obj`
+ progress "Removing $dobj from pre-build-directory"
+ remove $prebld_root/$dobj
+ done
+
+ progress "Removing $delete_file from pre-build-directory"
+ remove $prebld_root/$delete_file
+done
+
+cd $prebld_dir
+prebuilt_files=$prebld_dir/$prebuilt_filename
+progress "Creating $build_dir_name/$prebuilt_filename in pre-build-directory"
+touch $prebuilt_files >/dev/null 2>&1
+if [ $? -ne 0 ]; then
+ warning "Failed to create $build_dir_name/$prebuilt_filename in pre-build-directory"
+fi
+progress "Writing prebuilt files to $build_dir_name/$prebuilt_filename in pre-build-directory"
+prebld_files=`find . -type f | sort`
+for prebld_file in $prebld_files; do
+ if [ ! -f $src_dir/$prebld_file ]; then
+ echo "$prebld_file" >> $prebuilt_files
+ fi
+done
+
+if [ "x$deletedfiles_log" != "x" ]; then
+ case $deletedfiles_log in
+ /*) ;;
+ *) deletedfiles_log=$start_dir/$deletedfiles_log;;
+ esac
+
+ $rm -f $deletedfiles_log
+ progress "Creating deleted files log: $deletedfiles_log"
+ touch $deletedfiles_log >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ warning "Failed to create deleted files log"
+ else
+ progress "Writing deleted files log"
+ cd $src_root
+ src_files=`find . -type f | sort`
+ for src_file in $src_files; do
+ if [ ! -f $prebld_root/$src_file ]; then
+ echo "$src_file" >> $deletedfiles_log
+ fi
+ done
+ fi
+fi
+
+if [ "x$newfiles_log" != "x" ]; then
+ case $newfiles_log in
+ /*) ;;
+ *) newfiles_log=$start_dir/$newfiles_log;;
+ esac
+
+ $rm -f $newfiles_log
+ progress "Creating new files log: $newfiles_log"
+ touch $newfiles_log >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ warning "Failed to create new files log"
+ else
+ progress "Writing new files log"
+ cat $prebuilt_files | sed "s|^./|./$build_dir_name/|g" > $newfiles_log
+ fi
+fi
+
+progress "Packing source-directory into output file: $cln_tgz"
+cd $start_root
+$rm -f $cln_tgz
+$gtar -C $src_root -z -c -f $cln_tgz $build_dir_name
+if [ $? -ne 0 ]; then
+ error "Failed to create tar file: $cln_tgz"
+fi
+
+progress "Packing pre-build-directory into output file: $bld_tgz"
+cd $start_root
+$rm -f $bld_tgz
+$gtar -C $prebld_root -z -c -f $bld_tgz $build_dir_name
+if [ $? -ne 0 ]; then
+ error "Failed to create tar file: $bld_tgz"
+fi
+
+cleanup_work_dir
+
+progress_end
+
+exit 0
diff --git a/scripts/bundle-otp b/scripts/bundle-otp
index df82ff4fc5..ce5bc47572 100755
--- a/scripts/bundle-otp
+++ b/scripts/bundle-otp
@@ -2,14 +2,17 @@
set -e
-if [ "$TRAVIS_PULL_REQUEST" = "false" -a "$TRAVIS_REPO_SLUG" != "erlang/otp" ]; then
- exit 0
-fi
+GIT_TAG="$1"
+ERL_TOP=${ERL_TOP:-$PWD}
+
+OTP_META_FILE=$ERL_TOP/artifacts/${GIT_TAG}.0-bundle.txt
+OTP_FILE=$ERL_TOP/artifacts/${GIT_TAG}.0-bundle.tar.gz
-OTP_META_FILE=$ERL_TOP/${TRAVIS_TAG}.0-bundle.txt
-OTP_FILE=$ERL_TOP/${TRAVIS_TAG}.0-bundle.tar.gz
+REPOSITORIES="otp,$GIT_TAG corba,.*"
-REPOSITORIES="otp,$TRAVIS_TAG corba,.*"
+if [ ! -d $ERL_TOP/artifacts ]; then
+ mkdir $ERL_TOP/artifacts
+fi
mkdir bundle
diff --git a/scripts/diffable b/scripts/diffable
index d2208d9d89..671538a00d 100755
--- a/scripts/diffable
+++ b/scripts/diffable
@@ -2,8 +2,12 @@
%% -*- erlang -*-
-mode(compile).
+%% We print out a warning for files that take longer than
+%% ?LONG_COMPILE_THRESHOLD milliseconds to compile.
+-define(LONG_COMPILE_THRESHOLD, 10000).
+
main(Args0) ->
- DefOpts = #{format=>asm,no_compile=>false,legacy=>false},
+ DefOpts = #{format=>asm,no_compile=>false,legacy=>false,deterministic=>[]},
{Args,Opts} = opts(Args0, DefOpts),
case Args of
[OutDir] ->
@@ -15,10 +19,12 @@ main(Args0) ->
usage() ->
S = ["usage: otp-diffable-asm [OPTION] DIRECTORY\n\n"
"Options:\n"
- " --asm Output to .S files (default)\n"
- " --legacy-asm Output to legacy .S files\n"
- " --dis Output to .dis files\n"
- " --no-compile Disassemble from BEAM files (use with --dis)\n"
+ " --asm Output to .S files (default)\n"
+ " --legacy-asm Output to legacy .S files\n"
+ " --dis Output to .dis files\n"
+ " --no-compile Disassemble from BEAM files (use with --dis)\n"
+ " --deterministic Compile with +deterministic (useful when comparing"
+ " output from different build trees using --asm)"
"\n"
"DESCRIPTION\n"
"\n"
@@ -78,6 +84,8 @@ opt("legacy-asm", Opts) ->
Opts#{format:=asm,legacy:=true};
opt("no-compile", Opts) ->
Opts#{format:=dis,no_compile:=true};
+opt("deterministic", Opts) ->
+ Opts#{deterministic:=[deterministic]};
opt(Opt, _Opts) ->
io:format("Uknown option: --~ts\n\n", [Opt]),
usage().
@@ -103,11 +111,11 @@ do_compile(OutDir, Opts0) ->
"dialyzer",
"ssl",
"wx"],
- {Files,Opts} = get_files(Apps, Opts1),
+ {Specs,Opts} = get_specs(Apps, Opts1),
CF = choose_format(Opts),
- p_run(fun(File) ->
- compile_file(CF, File)
- end, Files).
+ p_run(fun(Spec) ->
+ compile_spec(CF, Spec)
+ end, Specs).
choose_format(#{format:=Format}=Opts) ->
case Format of
@@ -117,43 +125,50 @@ choose_format(#{format:=Format}=Opts) ->
compile_to_dis_fun(Opts)
end.
-compile_file(CF, File) ->
+compile_spec(CF, Spec) ->
try
- CF(File)
+ case timer:tc(CF, [Spec]) of
+ {Time0, ok} ->
+ Time = erlang:convert_time_unit(Time0, microsecond, millisecond),
+ {Spec, Time};
+ _ ->
+ error
+ end
catch
- Class:Error:Stk ->
+ Class:Error:Stk ->
if
- is_list(File) ->
+ is_list(Spec) ->
io:format("~s: ~p ~p\n~p\n\n",
- [File,Class,Error,Stk]);
+ [Spec,Class,Error,Stk]);
true ->
io:format("~p: ~p ~p\n~p\n\n",
- [File,Class,Error,Stk])
+ [Spec,Class,Error,Stk])
end,
- error
+ error
end.
elixir_root() ->
filename:join(filename:dirname(code:root_dir()), "elixir").
%%%
-%%% Get names of files (either .erl files or BEAM files).
+%%% Get names of files (either .erl files or BEAM files) together with their
+%%% compile options.
%%%
-get_files(Apps, #{format:=dis,no_compile:=true}=Opts) ->
+get_specs(Apps, #{format:=dis,no_compile:=true}=Opts) ->
Files = get_elixir_beams() ++ get_beams(Apps),
{Files,Opts};
-get_files(Apps, #{}=Opts) ->
+get_specs(Apps, #{}=Opts) ->
Inc = make_includes(),
CompilerOpts = [{d,epmd_dist_high,42},
{d,epmd_dist_low,37},
{d,'VSN',1},
{d,'COMPILER_VSN',1},
{d,erlang_daemon_port,1337}|Inc],
- Files0 = get_src(Apps),
- Files1 = add_opts(Files0, CompilerOpts),
- Files = [{Beam,elixir} || Beam <- get_elixir_beams()] ++ Files1,
- {Files,Opts}.
+ Files = get_src(Apps),
+ Specs1 = add_opts(Files, CompilerOpts),
+ Specs = [{Beam,elixir} || Beam <- get_elixir_beams()] ++ Specs1,
+ {Specs,Opts}.
get_elixir_beams() ->
ElixirEbin = filename:join(elixir_root(), "lib/elixir/ebin"),
@@ -247,12 +262,13 @@ get_beams([]) -> [].
%%%
compile_to_asm_fun(#{outdir:=OutDir}=Opts) ->
- fun(File) ->
+ fun(Spec) ->
Legacy = map_get(legacy, Opts),
- compile_to_asm(File, OutDir, Legacy)
+ Deterministic = map_get(deterministic, Opts),
+ compile_to_asm(Spec, OutDir, Legacy, Deterministic)
end.
-compile_to_asm({Beam,elixir}, OutDir, _Legacy) ->
+compile_to_asm({Beam,elixir}, OutDir, _Legacy, _Deterministic) ->
Abst = get_abstract_from_beam(Beam),
Source = filename:rootname(Beam, ".beam"),
Opts = [diffable,{outdir,OutDir},report_errors,{source,Source}],
@@ -262,8 +278,9 @@ compile_to_asm({Beam,elixir}, OutDir, _Legacy) ->
error ->
error
end;
-compile_to_asm({File,Opts}, OutDir, Legacy) ->
- case compile:file(File, [diffable,{outdir,OutDir},report_errors|Opts]) of
+compile_to_asm({File,Opts0}, OutDir, Legacy, Deterministic) ->
+ Opts = Deterministic ++ [diffable,{outdir,OutDir},report_errors|Opts0],
+ case compile:file(File, Opts) of
{ok,_Mod} ->
case Legacy of
true ->
@@ -294,12 +311,12 @@ get_abstract_from_beam(Beam) ->
%%%
compile_to_dis_fun(#{outdir:=OutDir,no_compile:=false}) ->
- fun(File) ->
- compile_to_dis(File, OutDir)
+ fun(Spec) ->
+ compile_to_dis(Spec, OutDir)
end;
compile_to_dis_fun(#{outdir:=OutDir,no_compile:=true}) ->
- fun(File) ->
- dis_only(File, OutDir)
+ fun(Spec) ->
+ dis_only(Spec, OutDir)
end.
compile_to_dis({File,elixir}, OutDir) ->
@@ -593,14 +610,26 @@ p_run_loop(_, [], _, [], Errors) ->
p_run_loop(Test, [H|T], N, Refs, Errors) when length(Refs) < N ->
{_,Ref} = erlang:spawn_monitor(fun() -> exit(Test(H)) end),
p_run_loop(Test, T, N, [Ref|Refs], Errors);
-p_run_loop(Test, List, N, Refs0, Errors0) ->
- io:format("\r~p ", [length(List)+length(Refs0)]),
+p_run_loop(Test, List, N, Refs0, Errors) ->
receive
- {'DOWN',Ref,process,_,Res} ->
- Errors = case Res of
- ok -> Errors0;
- error -> Errors0 + 1
- end,
- Refs = Refs0 -- [Ref],
- p_run_loop(Test, List, N, Refs, Errors)
+ {'DOWN',Ref,process,_,error} ->
+ Refs = Refs0 -- [Ref],
+ p_run_loop(Test, List, N, Refs, Errors + 1);
+ {'DOWN',Ref,process,_,{Spec,Time}} ->
+ if
+ Time >= ?LONG_COMPILE_THRESHOLD ->
+ Name = format_spec(Spec),
+ io:format("~s took ~pms to compile~n", [Name, Time]);
+ Time < ?LONG_COMPILE_THRESHOLD ->
+ io:format("\r~p ", [length(List) + length(Refs0)])
+ end,
+ Refs = Refs0 -- [Ref],
+ p_run_loop(Test, List, N, Refs, Errors)
end.
+
+format_spec(File) when is_list(File) ->
+ File;
+format_spec({File, _Options}) when is_list(File) ->
+ File;
+format_spec(Spec) ->
+ io_lib:format("~p", [Spec]).
diff --git a/scripts/otp_html_check b/scripts/otp_html_check
new file mode 100755
index 0000000000..d9c49ef30a
--- /dev/null
+++ b/scripts/otp_html_check
@@ -0,0 +1,540 @@
+#!/usr/bin/perl -w
+
+###########################################################################
+#
+# Find broken links and files not referenced.
+#
+# Author: Kent Boortz <kent@erix.ericsson.se>
+#
+###########################################################################
+
+use File::Find;
+use strict;
+
+undef $/; # No record separator reading files
+
+###########################################################################
+#
+# When we talk about "a page" we mean the actual page/file
+# When we talk about "a link" we mean a referense to a page/file.
+# All links/URL's start with an slash except the top link that is
+# the empty string.
+#
+# So basically we have a set of links and a set of URL's to pages and
+# check if this is a valid combination.
+#
+###########################################################################
+
+my $debug = 1;
+my $expand_url = 0; # If we are to expand an URL with default
+ # names like "index.html"
+my @indexes = # The order to try URL expansion
+ (
+ "index.shtml",
+ "index.html",
+ "index.htm",
+ );
+
+my $html_ext = 'shtml|html|htm'; # HTML pages ends in these
+
+my @links; # Set of [page,link] we want to check
+my @exclude; # Pages/dir/prefix to exclude
+my %pages; # Set of all files found in the file system
+ # limited by the script arguments.
+ # After the spider is done all members in the
+ # set thas has the value 1 was visited.
+
+my %missing; # Pages not found "$page$;$link"
+my %invalid; # After expansion it is invalid
+my %access; # Can't access but exists
+
+my %anchor_refs; # Absolute links including anchor part
+my %anchor_defs; # <a name="..."> in the form "$page#$anchor"
+
+###########################################################################
+#
+# Argument processing, see usage() function below
+#
+###########################################################################
+
+@ARGV or usage("No base directory given");
+my $base = shift @ARGV;
+-d $base or usage("Not a directory: $base");
+$base =~ m&^/& or usage("Has to be absolute path: $base");
+$base =~ s&/+$&&; # Remove ending slash if any
+
+my $link;
+while ($link = shift @ARGV) {
+ last if $link eq '--';
+ $link =~ s&/+$&&; # Remove ending slash if any
+ $link =~ s&$base&&; # Make absolute URL
+ $link =~ m&^/& and usage("Invalid start point of HTML tree \"$_\"");
+ $link = "/$link";
+ push(@links,["",$link]);
+}
+
+while ($link = shift @ARGV) {
+ $link =~ s&/+$&&; # Remove ending slash if any
+ $link =~ s&$base&&; # Make absolute URL
+ $link =~ m&^/& and usage("Invalid exclude URL \"$_\"");
+ $link = "/$link";
+ push(@exclude,$link);
+}
+
+# OTP specific
+
+push(@links,["","/doc/index.html"]) unless @links;
+
+###########################################################################
+#
+# Traverse all files and directories and put all possible URL's into
+# the set %pages. When we later find a referense to a page that URL
+# is removed from the set. When we have followed all links the set
+# contains the pages never visited.
+#
+# We skip files and directories in @exclude.
+#
+###########################################################################
+
+find(\&wanted,$base);
+
+sub wanted {
+ return unless -f;
+ return if /^\.info\./;
+ return if /~$/;
+
+ my $url = $File::Find::name;
+ $url =~ s&$base&&;
+ $pages{$url} = 0 unless map {$url =~ m&^$_&} @exclude;
+}
+
+
+###########################################################################
+#
+# Spider that follow all links adding links to the @links set.
+#
+# @links is expanded, normalized links
+#
+# We check if there is an valid URL for this link.
+# @links may contain links that look bad, this is cleaned up here
+# before checking it.
+#
+###########################################################################
+
+while (@links) {
+ my $page_and_link = shift @links;
+ my ($page,$link) = @$page_and_link;
+
+ # We skip some links directly
+
+ next if $link =~ /^\w{3,10}:/i;
+ next if $link =~ /cgi-bin|cgiwrap|user-cgi/;
+ next if $link =~ /^and|or$/;
+# next if $link eq "";
+
+# print STDERR "1 link: $link\n";
+
+ $link = expand_link($link,\%pages) if $expand_url;
+
+ unless (exists $pages{$link}) {
+ # No page for link, mark as invalid
+ $missing{"$page$;$link"} = 1;
+ next;
+ }
+
+# print STDERR "2 link: $link\n";
+
+ next if $pages{$link}; # If == 1 it is visited
+ $pages{$link} = 1; # Mark as visited
+
+# print STDERR "3 link: $link\n";
+
+# next unless $link =~ /\.(shtml|html|htm)$/oi;
+ next unless $link =~ /\.($html_ext)$/oi;
+
+ push(@links,get_page_links($base,$link));
+}
+
+
+###########################################################################
+#
+# Read the page and get all the links. We know that the URL for the page
+# is absolute and that a page/file exists.
+#
+###########################################################################
+
+sub get_page_links {
+ my $base = shift;
+ my $page = shift; # Absolute URL
+
+# print STDERR "open: $page\n";
+
+ my $path = "$base$page";
+
+ open(HTML,$path)
+ or print STDERR "INTERNAL ERROR: Can't open page $page: $!\n";
+
+ my $html = <HTML>;
+ close HTML;
+
+# my $url_base = $page;
+# $url_base =~ s&/[^/]+$&&;
+
+ # Remove comments
+ $html =~ s/\<\!\-\-\s*(.*?)\s*\-\-\>//gs;
+
+# # Remove comments and expand SSI
+# $html =~ s/\<\!\-\-\s*(.*?)\s*\-\-\>/
+# expand_ssi($url_base,$page,$1)/gsie;
+
+ my @links; # Links in this document
+# push(@links,$html =~ /\/\*URL\*\/\s*\'([^\']+\.[^\']+)\'/gsi);
+# push(@links,$html =~ /=\s*\'([^\']+\.(?:gif|jpg|jpeg))\'/gsi);
+# push(@links,$html =~ /option value=\s*\"(\/[^\"]+)\"/gsi);
+# push(@links,$html =~ /option value=\s*\"([^\"]+\.[^\"]+)\"/gsi);
+# FIXME: This is not working....
+# push(@links,$html =~ /url\s*=\s*([\w-\.\/]+)/gsi);
+# push(@links,$html =~ /\"([^\"]+\.html)\"/gsi);
+
+ # Find real HTML links
+ push(@links,$html =~ /\<\s*\w[^\>]*\sHREF=\s*\"([^\"]*)\"[^\>]*\>/gsi);
+ push(@links,$html =~ /\<\s*\w[^\>]*\sSRC=\s*\"([^\"]*)\"[^\>]*\>/gsi);
+ push(@links,$html =~ /\<\s*\w[^\>]*\sLOWSRC=\s*\"([^\"]*)\"[^\>]*\>/gsi);
+ push(@links,$html =~ /\<\s*\w[^\>]*\sBACKGROUND=\s*\"([^\"]*)\"[^\>]*\>/gsi);
+
+ # FIXME: Now we have the raw links, if we want to complain about
+ # spaces etc this is the time.
+
+ # Remove referenses to the same page FIXME??? Was removed , why...
+# @links = grep {$_ and $_ !~ /^\#/} @links;
+
+ # Find the URL to the current directory
+ my $rpath = $page;
+ $rpath =~ s&/[^/]+$&&; # Remove name
+
+ # Links pointing to the same page
+ # should look the same
+ map {$_ = normalize_link($page,$rpath,$_)} @links;
+
+# print "XXX $page\n" if grep {m&lib/asn1-1.3.2/doc/index\.html&} @links;
+
+ map {$_ = [$page,$_]} @links; # Add what page was referensing it
+
+ # Find the anchors
+
+ my @anchors =
+ ($html =~ m/
+ <
+ \s*
+ A
+ [^>]*
+ \s (?: NAME|ID) \s* = \s*
+ (?: \"([^\"]*)\" | \'([^\']*)\' | ([^>\s]+) )
+ [^>]*
+ >
+ /gsix);
+
+ foreach my $anchor (@anchors) {
+ # FIXME if already there, duplicate
+ next unless defined $anchor;
+ $anchor =~ s/%([\da-fA-F]{2})/chr(hex($1))/eg; # Translate hex to char
+ $anchor =~ s/&lt;/</g; #
+ $anchor =~ s/&gt;/>/g; #
+ $anchor_defs{"$page#$anchor"} = 1;
+ }
+
+ return @links;
+}
+
+
+# -------------------------------------------------------------------------
+# -------------------------------------------------------------------------
+
+sub normalize_link {
+ my $page = shift; # Page where we found this link
+ my $rpath = shift; # URL to directory where we found this link
+ my $link = shift; # The link to normalize
+
+# print STDERR "\n";
+# print STDERR "1 normalize_link: $link\n";
+
+ # Handle javascript:erlhref() specially to be able to check those links.
+ if ($link =~ /^javascript:erlhref\(([^\)]*)\);$/) {
+ my($up,$part,$mod) = split(/,\s*/, $1);
+ $up =~ tr/\'//d;
+ $part =~ tr/\'//d;
+ $mod =~ tr/\'//d;
+ my $dir;
+ if ($part =~ m&^[a-z]+/&) {
+ $dir = "$base$rpath/${up}/$part";
+ } else {
+ my $path = "$base$rpath/${up}lib/$part/doc/html";
+ ($dir) = <$path-*>;
+ return $link unless defined $dir;
+ }
+ $dir =~ s&^$base&&o;
+ $link = "$dir/$mod";
+ }
+
+ return $link if $link =~ /^\w{3,10}:/i; # mailto: http: .....
+
+ $link =~ s/%([\da-fA-F]{2})/chr(hex($1))/eg; # Translate hex to char
+
+ if ($link eq "") {
+ # The empty link is a reference to URL directory
+ return $rpath;
+ } elsif ($link =~ /^#(.*)$/s) {
+ # Lokal reference to anchor
+ my $anchor = $1;
+ $anchor =~ s/%([\da-fA-F]{2})/chr(hex($1))/eg; # Translate hex to char
+ $anchor =~ s/&lt;/</g; #
+ $anchor =~ s/&gt;/>/g; #
+ $anchor =~ s&^\s+&&; # Remove leading any whitespaces
+ $anchor =~ s&\s+$&&; # Remove trailing any whitespaces
+ push(@{$anchor_refs{"$page#$anchor"}}, $page);
+ return $page;
+ }
+
+ my $anchor = "";
+
+ if ($link =~ s&#(.*)$&&s) {
+ # Removed page ref (anchor)
+ $anchor = $1;
+ $anchor =~ s/%([\da-fA-F]{2})/chr(hex($1))/eg; # Translate hex to char
+ $anchor =~ s/&lt;/</g; #
+ $anchor =~ s/&gt;/>/g; #
+ $anchor =~ s&^\s+&&; # Remove leading any whitespaces
+ $anchor =~ s&\s+$&&; # Remove trailing any whitespaces
+ }
+
+ $link = "" if $link eq "/";
+
+ # Make the link absolute
+ # FIXME: maybe move down.....
+
+ if ($link !~ m&^/&) {
+ if ($link) {
+ $link = "$rpath/$link";
+ } else {
+ $link = $rpath;
+ }
+ }
+
+ my $xlink = $link;
+
+ $link =~ s&//+&/&g; # Replace multiple slashes with one slash
+# $link =~ s&^(\./)+&&g; # Remove starting dot slash "./" (can't be if absolute)
+ $link =~ s&(/\.)+$&&; # Remove ending slash dot "/."
+ $link =~ s&(/\.)+/&/&g; # Remove all slash dot slash "/./"
+ $link =~ s&/+$&&; # Remove ending slashes
+ $link =~ s&\?.*$&&; # Remove any query parameters
+
+ # Remove a real directory part followed by ".."
+
+ while ($link =~ s&/[^/]+/\.\.&&) {}
+
+# print STDERR "4 normalize_link: $link\n";
+
+ $link = "" if $link eq "/"; # We do this again
+
+ # print STDERR "5 normalize_link: $link\n";
+
+ push(@{$anchor_refs{"$link#$anchor"}}, $page) if $anchor;
+
+ return $link;
+}
+
+
+# -------------------------------------------------------------------------
+# We know the link is normalized
+# -------------------------------------------------------------------------
+
+sub expand_link {
+ my $link = shift;
+ my $pages = shift;
+
+ return $link if exists $pages{$link};
+
+ my $newlink;
+
+ foreach my $index (@indexes) {
+ $newlink = "$link/$index";
+ return $newlink if exists $pages{$newlink};
+ }
+
+ return $link;
+}
+
+###########################################################################
+#
+# Report the result
+#
+###########################################################################
+
+if (keys %missing) {
+ print "\n\n\n**** Broken links\n\n";
+ foreach (sort keys %missing) {
+ my ($page,$link) = split($;);
+ print qq(Broken Link: $page -> "$link"\n);
+ }
+}
+
+
+# Entrys in %pages that has the value 0 is not visited
+if (keys %pages) {
+ print "\n\n\n**** Files not used (that I can see)\n\n";
+ foreach my $page (sort keys %pages) {
+ next if $pages{$page}; # If == 1 it is visited
+
+ # OTP specific
+
+ next if $page =~ m&^/(man|pdf|logs|COPYRIGHT|PR.template|README)&;
+ next if $page =~ m&^/.*\.tar.gz$&;
+ next if $page =~ m&(/info|\.kwc)$&;
+
+ print qq("$page"\n);
+ }
+}
+
+
+# Remove all references that has a matching NAME=....
+map {delete $anchor_refs{$_}} keys %anchor_defs;
+
+if (keys %anchor_refs) {
+ print "\n\n\n**** References to missing anchors\n\n";
+ foreach my $ref (sort keys %anchor_refs) {
+ foreach my $anchor (sort @{$anchor_refs{$ref}}) {
+ print qq(Missing Anchor: "$ref" from ${anchor}\n);
+ }
+ }
+}
+
+if (keys %missing || keys %anchor_refs) {
+ exit 1;
+}
+
+
+###########################################################################
+
+sub usage {
+ print STDERR "ERROR: ",join("\n",@_),"\n" if @_;
+ print <<HERE;
+Usage: $0 BaseDirectory URL [ URLs... ] [ -- ExcludeURLs... ]
+
+This script try to find out what files are used and not of your
+HTML documents, graphic files etc. It doesn't use HTTP, i.e. you
+work off-line, so this script may fail to find a link. Javascripts
+and other extensions also makes it very hard. But for many sites
+it work very well.
+
+The base directory has to given has to start with a slash.
+
+For URLs and ExcludeURLs absolute paths or relative the base
+directory can be used.
+
+ExcludeURLs is used as prefixes of directories or files that
+should be excluded from the search.
+
+You call it something like
+
+ % $0 /test/r7a /test/r7a/doc/index.html /test/r7a/lib/*/doc/index.html
+
+or using relative start points
+
+ % $0 /test/r7a doc/index.html
+
+HERE
+ exit 1;
+}
+
+
+__END__
+
+# FIXME: The order below is important
+
+if (%access) {
+ print "\n**** Link exists but can't open\n\n";
+
+ my $file;
+
+ foreach $file (sort keys %access) {
+ print "$file\n";
+ }
+}
+
+
+if (%invalid) {
+ print "\n**** Invalid links (goes up above top directory)\n\n";
+
+ foreach (sort keys %invalid) {
+ my ($page,$link) = split($;,$_);
+ delete $done{$link}; # FIXME: xxxx
+ print "$page\n\t-> $link\n";
+ }
+}
+
+if (%done) {
+ print "\n**** Internal error, should be no files here\n\n";
+
+ foreach (sort keys %done) {
+ print "$_\n";
+ }
+}
+
+
+__END__
+###########################################################################
+
+
+sub expand_ssi {
+ my $url_base = shift;
+ my $page = shift;
+ my $comment = shift; # Text between <!-- and -->
+
+ return "" unless $comment =~ s/^\#//;
+
+ # This is an SSI
+ unless ($comment =~ /([\w-]+)=\"([^\"]+)\"/) {
+# print STDERR "WARNING: Unknown SSI $comment\n\ton $page\n";
+ return "";
+ }
+
+ my $op = lc($1); # Operator
+ my $inc = $2; # Absolute or relative URL anding in anything
+
+ if ($debug) {
+ print STDERR "X: url_base = $url_base\n";
+ print STDERR "X: page = $page\n";
+ print STDERR "X: op = $op\n";
+ print STDERR "X: inc = $inc\n";
+ print STDERR "X: base = $base\n";
+ }
+
+ unless ($op eq 'virtual') {
+# print STDERR "WARNING: Unknown SSI $comment\n\ton $page\n";
+ return "";
+ }
+
+ $inc = make_url_absolute($url_base,$page,$inc);
+
+ my $path = "$base$inc";
+
+ if ($debug) {
+ print STDERR "X: inc = $inc\n";
+ print STDERR "X: path = $path\n\n";
+ }
+
+ unless (open(HTML,$path)) {
+# print STDERR "ERROR: Can't open page $inc: $!\n";
+ $access{$inc} = 1;
+ return "";
+ }
+
+ my $html = <HTML>;
+ close HTML;
+
+ $done{$inc} = 1; # Mark done
+
+ return $html;
+}
+
diff --git a/scripts/pre-push b/scripts/pre-push
index 71e9fd1e75..978f8cceef 100755
--- a/scripts/pre-push
+++ b/scripts/pre-push
@@ -22,12 +22,15 @@
# <local ref> <local sha1> <remote ref> <remote sha1>
#
-NEW_RELEASES="21 20 19 18 17"
+# Bump this version to give users an update notification.
+PRE_PUSH_SCRIPT_VERSION=2
+
+NEW_RELEASES="23 22 21 20 19 18 17"
OLD_RELEASES="r16 r15 r14 r13"
RELEASES="$NEW_RELEASES $OLD_RELEASES"
# First commit on master, not allowed in other branches
-MASTER_ONLY=aea2a053e28a11497796879715be29ab0c3cd1a0
+MASTER_ONLY=740b29ecc21c73a4bf4ebfc494490865d3c31978
# Number of commits and files allowed in one push by this script
NCOMMITS_MAX=100
@@ -54,13 +57,23 @@ null=0000000000000000000000000000000000000000
#echo "pre-push hook: remote=$remote"
#echo "pre-push hook: url=$url"
-if [ "$url" = 'https://github.com/erlang/otp.git' -o "$url" = 'git@github.com:erlang/otp.git' ]
+red_on() {
+ printf '%b' "\033[31m"
+}
+
+red_off() {
+ printf '%b' "\033[0m"
+}
+
+if [ "$url" = 'https://github.com/erlang/otp.git' -o "$url" = 'git@github.com:erlang/otp.git' -o "$url" = 'git@github.com:erlang/otp' ]
then
if [ $remote = "$url" ]; then
+ red_on
echo "$0 says:"
echo "***"
echo "*** Push to $url without using a named remote is NOT ALLOWED!!!!"
echo "***"
+ red_off
exit 1
fi
IFS=' '
@@ -73,18 +86,22 @@ then
if [ "$local_sha" = $null ]
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** DELETE push to '$remote' NOT ALLOWED!!!!!"
echo "***"
+ red_off
exit 1
fi
if [ "$local_ref" != "$remote_ref" ]
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** RENAME push: $local_ref pushed as $remote_ref to '$remote' NOT ALLOWED!!!!"
echo "***"
+ red_off
exit 1
fi
case "$remote_ref" in
@@ -92,46 +109,74 @@ then
branch=${remote_ref#refs/heads/}
if [ "$remote_sha" = $null ]
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** UNKNOWN BRANCH: '$branch' does not exist at '$remote'!!!!"
echo "***"
+ red_off
exit 1
fi
if ! git log -1 --oneline $remote_sha > /dev/null 2>&1
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** The top of '$branch' at '$remote' ($remote_sha)"
echo "*** does not exist locally!!!"
echo "*** You probably need to refresh local '$branch' and redo merge."
echo "***"
+ red_off
exit 1
fi
if ! git merge-base --is-ancestor $remote_sha $local_sha
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** FORCE push branch to '$remote' NOT ALLOWED!!!"
echo "***"
+ red_off
exit 1
fi
if [ $remote_ref != refs/heads/master -a "$MASTER_ONLY" ] && git merge-base --is-ancestor $MASTER_ONLY $local_sha
then
- echo "$0 says:"
- echo "***"
- echo "*** INVALID MERGE: Commit $MASTER_ONLY should not be reachable from '$branch'!!!!"
- echo "*** You have probably merged master into '$branch' by mistake"
- echo "***"
- exit 1
+ THIS_SCRIPT=`git rev-parse --git-path hooks/pre-push`
+ THIS_SCRIPT=`realpath $THIS_SCRIPT`
+ if git show refs/remotes/$remote/master:scripts/pre-push | diff -q --context=0 $THIS_SCRIPT - > /dev/null 2>&1
+ then
+ red_on
+ echo "$0 says:"
+ echo "***"
+ echo "*** INVALID MERGE: Commit $MASTER_ONLY should not be reachable from '$branch'!!!!"
+ echo "*** You have probably merged master into '$branch' by mistake"
+ echo "***"
+ red_off
+ exit 1
+ else
+ red_on
+ echo "$0 says:"
+ echo "***"
+ echo "*** The pre-push hook of this OTP repo needs updating."
+ echo "*** Do it by executing the following command:"
+ echo "***"
+ echo "*** git show refs/remotes/$remote/master:scripts/pre-push > $THIS_SCRIPT"
+ echo "***"
+ echo "*** And then retry the push."
+ echo "***"
+ red_off
+ exit 1
+ fi
fi
if [ ${remote_ref#refs/heads/maint-} != $remote_ref ] && git merge-base --is-ancestor refs/remotes/$remote/maint $local_sha
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** INVALID MERGE: Branch maint should not be reachable from '$branch'!!!!"
echo "*** You have probably merged maint into '$branch' by mistake."
echo "***"
+ red_off
exit 1
fi
if [ $remote_ref = refs/heads/maint -o $remote_ref = refs/heads/master ]; then
@@ -147,29 +192,35 @@ then
fi
if [ $remote_ref = refs/heads/master ] && ! git merge-base --is-ancestor refs/remotes/$remote/maint $local_sha
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** INVALID PUSH: Branch '$remote/maint' is not reachable from master!!!!"
echo "*** Someone needs to merge maint forward to master and push."
echo "***"
+ red_off
exit 1
fi
NCOMMITS=`git rev-list --count $remote_sha..$local_sha`
if [ $NCOMMITS -gt $NCOMMITS_MAX ]
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** HUGE push: $NCOMMITS commits (> $NCOMMITS_MAX) to '$branch' at '$remote' NOT ALLOWED!!!!"
echo "***"
+ red_off
exit 1
fi
NFILES=`git diff --name-only $remote_sha $local_sha | wc --lines`
if [ $NFILES -gt $NFILES_MAX ]
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** HUGE push: $NFILES changed files (> $NFILES_MAX) to '$branch' at '$remote' NOT ALLOWED!!!!"
echo "***"
+ red_off
exit 1
fi
;;
@@ -185,49 +236,74 @@ then
done
if [ $REL = "UNKNOWN" ]
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** Unknown OTP release number in tag '$tag'"
echo "***"
+ red_off
exit 1
fi
if [ "$remote_sha" != $null ]
then
+ red_on
echo "$0 says:"
echo "***"
echo "*** FORCE push tag to '$remote' NOT ALLOWED!!!"
echo "*** Tag '$tag' already exists at '$remote'."
echo "***"
+ red_off
exit 1
fi
;;
refs/heads/*)
branch=${remote_ref#refs/heads/}
+ red_on
echo "$0 says:"
echo "***"
echo "*** UNKNOWN branch name: '$branch' pushed to '$remote' NOT ALLOWED!!!!"
echo "***"
+ red_off
exit 1
;;
refs/tags/*)
tag=${remote_ref#refs/tags/}
+ red_on
echo "$0 says:"
echo "***"
echo "*** UNKNOWN tag name: '$tag' pushed to '$remote' NOT ALLOWED!!!!"
echo "***"
+ red_off
exit 1
;;
*)
+ red_on
echo "$0 says:"
echo "***"
echo "*** STRANGE ref: '$remote_ref' pushed to '$remote' NOT ALLOWED!!!!"
echo "***"
+ red_off
exit 1
;;
esac
done
+
+ echo "$0: OK"
+
+ THIS_SCRIPT=`git rev-parse --git-path hooks/pre-push`
+ THIS_SCRIPT=`realpath $THIS_SCRIPT`
+ if git show refs/remotes/$remote/master:scripts/pre-push | diff --context=0 $THIS_SCRIPT - | grep -q PRE_PUSH_SCRIPT_VERSION > /dev/null 2>&1
+ then
+ echo ""
+ echo "NOTE: There is a newer version of the pre-push hook in this OTP repo."
+ echo " You can install it by executing the following command:"
+ echo
+ echo " git show refs/remotes/$remote/master:scripts/pre-push > $THIS_SCRIPT"
+ echo
+ fi
else
echo "$0: No checks done for remote '$remote' at $url."
fi
exit 0
+
diff --git a/scripts/run-dialyzer b/scripts/run-dialyzer
index 34724a8ca0..8d1e27b112 100755
--- a/scripts/run-dialyzer
+++ b/scripts/run-dialyzer
@@ -11,13 +11,19 @@ filter () {
done
}
-ALL_APPLICATIONS=$(ls -d -1 lib/*/ | sed 's:^lib\|/::g')
+if [ "X$ERL_TOP" != "X" -a -d $ERL_TOP/lib ]; then
+ LIB_DIR=$ERL_TOP/lib
+else
+ LIB_DIR=$(erl -noshell -eval 'io:format("~ts~n",[code:lib_dir()])' -s init stop)
+fi
+ALL_APPLICATIONS=$(ls -d -1 $LIB_DIR/*/ | sed "s:^$LIB_DIR/\\([^/\-]\+\\).*$:\1:g")
+ALL_APPLICATIONS="erts $ALL_APPLICATIONS"
echo "All applications: $ALL_APPLICATIONS" |tr '\n' ' ' && echo ""
BASE_PLT="compiler crypto erts hipe kernel stdlib syntax_tools"
APP_PLT="asn1 edoc et ftp inets mnesia observer public_key sasl runtime_tools snmp ssl tftp wx xmerl tools"
-NO_UNMATCHED="common_test debugger edoc eunit ftp inets mnesia observer reltool ssh ssl syntax_tools tftp wx xmerl"
-WARNINGS="diameter megaco snmp"
+NO_UNMATCHED="common_test debugger edoc eunit ftp inets megaco mnesia observer reltool ssh ssl syntax_tools tftp wx xmerl"
+WARNINGS="diameter snmp"
TRAVIS_SKIP=""
if [ "X$TRAVIS" = "Xtrue" ]; then
@@ -37,11 +43,19 @@ echo "UNMATCHED = $UNMATCHED"
echo "NO_UNMATCHED = $NO_UNMATCHED"
echo "WARNINGS = $WARNINGS"
+DIALYZER=dialyzer
+
+if [ -f $ERL_TOP/bin/dialyzer ]; then
+ DIALYZER=$ERL_TOP/bin/dialyzer
+fi
+
+PLT=$(mktemp --suffix=.plt)
+
set -x
-$ERL_TOP/bin/dialyzer --build_plt -Wunknown --apps $BASE_PLT $APP_PLT --statistics
-$ERL_TOP/bin/dialyzer -n -Wunknown -Wunmatched_returns --apps $UNMATCHED --statistics
-$ERL_TOP/bin/dialyzer -n -Wunknown --apps $NO_UNMATCHED --statistics
+$DIALYZER --build_plt --output_plt $PLT -Wunknown --apps $BASE_PLT $APP_PLT --statistics
+$DIALYZER -n --plt $PLT -Wunknown -Wunmatched_returns --apps $UNMATCHED --statistics
+$DIALYZER -n --plt $PLT -Wunknown --apps $NO_UNMATCHED --statistics
if [ "X$WARNINGS" != "X" ]; then
- $ERL_TOP/bin/dialyzer -n --apps $WARNINGS --statistics || true
+ $DIALYZER -n --plt $PLT --apps $WARNINGS --statistics || true
fi
diff --git a/scripts/run-smoke-tests b/scripts/run-smoke-tests
index b3d26f1fce..82231a2b81 100755
--- a/scripts/run-smoke-tests
+++ b/scripts/run-smoke-tests
@@ -1,19 +1,20 @@
#!/bin/bash
set -ev
-if [ -z "$ERL_TOP" ]; then
- ERL_TOP=$(pwd)
+if [ -d $ERL_TOP/release/tests/test_server ]; then
+ cd $ERL_TOP/release/tests/test_server
+elif [ -d test_server ]; then
+ cd test_server
+else
+ echo "Could not find tests"
+ exit 1;
fi
-function run_smoke_tests {
- cd $ERL_TOP/release/tests/test_server
- $ERL_TOP/bin/erl -s ts install -s ts smoke_test batch -s init stop
+erl -noshell -s ts install -s ts smoke_test batch -s init stop
- if grep -q '=failed *[1-9]' ct_run.test_server@*/*/run.*/suite.log; then
- echo "One or more tests failed."
- exit 1
- fi
- rm -rf ct_run.test_server@*
-}
+if grep -q '=failed *[1-9]' ct_run.test_server@*/*/run.*/suite.log; then
+ echo "One or more tests failed."
+ exit 1
+fi
-run_smoke_tests
+rm -rf ct_run.test_server@*
diff --git a/system/COPYRIGHT b/system/COPYRIGHT
index 3156886f7c..0368961f85 100644
--- a/system/COPYRIGHT
+++ b/system/COPYRIGHT
@@ -62,7 +62,7 @@ Email domain: cam.ac.uk
University of Cambridge Computing Service,
Cambridge, England.
-Copyright (c) 1997-2019 University of Cambridge
+Copyright (c) 1997-2020 University of Cambridge
All rights reserved.
@@ -73,7 +73,7 @@ Written by: Zoltan Herczeg
Email local part: hzmester
Email domain: freemail.hu
-Copyright(c) 2010-2019 Zoltan Herczeg
+Copyright(c) 2010-2020 Zoltan Herczeg
All rights reserved.
@@ -84,7 +84,7 @@ Written by: Zoltan Herczeg
Email local part: hzmester
Email domain: freemail.hu
-Copyright(c) 2009-2019 Zoltan Herczeg
+Copyright(c) 2009-2020 Zoltan Herczeg
All rights reserved.
diff --git a/system/doc/definitions/cite.defs b/system/doc/definitions/cite.defs
deleted file mode 100644
index 41fffc1460..0000000000
--- a/system/doc/definitions/cite.defs
+++ /dev/null
@@ -1,26 +0,0 @@
-[
-{"4711","CCITT","CCITT, Red Book Vol.-FASCILE VIII.7, Recomm. X400-X.430, Geneva, 1985","jocke"},
-{"X.680","ITU-T X.680","ITU-T Recommendation X.680 (1994) | ISO/IEC 8824-1: 1995, Abstract Syntax Notation One (ASN.1): Specification of Basic Notation"},
-{"X.682","ITU-T X.682","ITU-T Recommendation X.682 (1994) | ISO/IEC 8824-3: 1995, Abstract Syntax Notation One (ASN.1): Constraint Specification"},
-{"X.690","ITU-T X.690","ITU-T Recommendation X.690 (1994) | ISO/IEC 8825-1: 1995, ASN.1 Encoding Rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER)"},
-{"X.691","ITU-T X.691","ITU-T Recommendation X.691 (04/95) | ISO/IEC 8825-2: 1995, ASN.1 Encoding Rules: Specification of Packed Encoding Rules (PER)"},
-{"X.681","ITU-T X.681","ITU-T Recommendation X.681 (1994) | ISO/IEC 8824-2: 1995, Abstract Syntax Notation One (ASN.1): Information Object Specification"},
-{"X.683","ITU-T X.683","ITU-T Recommendation X.683 (1994) | ISO/IEC 8824-4: 1995, Abstract Syntax Notation One (ASN.1): Parameterization of ASN.1 Specifications"},
-{
- "DUBUISSON",
- "ASN.1 Communication between Heterogeneous Systems",
- "Oliver Dubuisson, ASN.1 Communication between Heterogeneous Systems, "
- "June 2000 ISBN 0-126333361-0", "nibe"
- },
- {
- "erlbook2",
- "Concurrent Programming in ERLANG",
- "J. Armstrong, R. Virding, C. Wikstrom, M. Williams, "
- "Concurrent Programming in ERLANG, Prentice Hall, 1996, ISBN 0-13-508301-X",
- "kent"
- },
- {"practsgml", "Practical SGML",
- "Eric van Herwijnen: Practical SGML, Kluwer Academic Publishers, 1990.",
- "peter"
-}
-].
diff --git a/system/doc/definitions/cite.defs.xml b/system/doc/definitions/cite.defs.xml
deleted file mode 100644
index 8fb91e2b44..0000000000
--- a/system/doc/definitions/cite.defs.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE cites SYSTEM "cites.dtd">
-
-<cites>
- <cite>
- <id>4711</id>
- <shortdef>CCITT</shortdef>
- <def>
-CCITT, Red Book Vol.-FASCILE VIII.7, Recomm. X400-X.430, Geneva, 1985. </def>
- <resp>jocke</resp>
- </cite>
- <cite>
- <id>X.680</id>
- <shortdef>ITU-T X.680</shortdef>
- <def>
-ITU-T Recommendation X.680 (1994) | ISO/IEC 8824-1: 1995, Abstract Syntax Notation One (ASN.1): Specification of Basic Notation. </def>
- </cite>
- <cite>
- <id>X.682</id>
- <shortdef>ITU-T X.682</shortdef>
- <def>
-ITU-T Recommendation X.682 (1994) | ISO/IEC 8824-3: 1995, Abstract Syntax Notation One (ASN.1): Constraint Specification. </def>
- </cite>
- <cite>
- <id>X.690</id>
- <shortdef>ITU-T X.690</shortdef>
- <def>
-ITU-T Recommendation X.690 (1994) | ISO/IEC 8825-1: 1995, ASN.1 Encoding Rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER). </def>
- </cite>
- <cite>
- <id>X.691</id>
- <shortdef>ITU-T X.691</shortdef>
- <def>
-ITU-T Recommendation X.691 (04/95) | ISO/IEC 8825-2: 1995, ASN.1 Encoding Rules: Specification of Packed Encoding Rules (PER). </def>
- </cite>
- <cite>
- <id>X.681</id>
- <shortdef>ITU-T X.681</shortdef>
- <def>
-ITU-T Recommendation X.681 (1994) | ISO/IEC 8824-2: 1995, Abstract Syntax Notation One (ASN.1): Information Object Specification. </def>
- </cite>
- <cite>
- <id>X.683</id>
- <shortdef>ITU-T X.683</shortdef>
- <def>
-ITU-T Recommendation X.683 (1994) | ISO/IEC 8824-4: 1995, Abstract Syntax Notation One (ASN.1): Parameterization of ASN.1 Specifications. </def>
- </cite>
- <cite>
- <id>DUBUISSON</id>
- <shortdef>ASN.1 Communication between Heterogeneous Systems</shortdef>
- <def>
-Oliver Dubuisson, ASN.1 Communication between Heterogeneous Systems, June 2000 ISBN 0-126333361-0. </def>
- <resp>nibe</resp>
- </cite>
- <cite>
- <id>erlbook2</id>
- <shortdef>Concurrent Programming in ERLANG</shortdef>
- <def>
-J. Armstrong, R. Virding, C. Wikstrom, M. Williams, Concurrent Programming in ERLANG, Prentice Hall, 1996, ISBN 0-13-508301-X. </def>
- <resp>kent</resp>
- </cite>
- <cite>
- <id>practsgml</id>
- <shortdef>Practical SGML</shortdef>
- <def>
-Eric van Herwijnen: Practical SGML, Kluwer Academic Publishers, 1990. </def>
- <resp>peter</resp>
- </cite>
-</cites>
-
diff --git a/system/doc/definitions/term.defs b/system/doc/definitions/term.defs
deleted file mode 100644
index 921175a7f0..0000000000
--- a/system/doc/definitions/term.defs
+++ /dev/null
@@ -1,211 +0,0 @@
-[{"agent","agent","An entity that terminates a management protocol in the Network Element.","mbj"},
-{"API","API","Application Programming Interface. The interface towards an application. Usually this is a set of functions available, but can also be a set of messages sent to or from an application.","mbj"},
-{"application","application","A collection of resources which is required to offer a specific service.","mbj"},
-{"app callback","application callback module","A module which is called when the application is started, and when it has stopped. Every application has one application callback module.","mbj"},
-{"AC","application controller","A process which coordinates all operations on applications.","mbj"},
-{"app master","application master","The application master is a process that monitors the application. It is provided by the Erlang run-time system. Every application has an application master process.","mbj"},
-{".app file","application resource file","Specifies the resources required by the application and how the application should be started. Every application has one application resource file, called AppName.app.","mbj"},
-{"arity","arity","Denotes the number of arguments to a function.","jocke"},
-{"ASN.1","ASN.1","Abstract Syntax Notation One - an ITU-T and ISO standard notation for describing data formats used in communication protocols.","kenneth"},
-{"ASN.1 Compiler","ASN.1 Compiler","The Erlang/OTP ASN.1 Compiler translates an ASN.1 module into a corresponding Erlang module with encode and decode functions.","kenneth"},
-{"atom","atom","An atom is a constant. Atoms always starts with a lower case letter (a-z) and are terminated by a non-alphanumeric character - otherwise they must be quoted (enclosed in \' \'). An atom is a data type in Erlang, used to enhance the legibility of programs.","kenneth"},
-{"atomicity","atomicity","Atomicity refers to the \"all or nothing\" property. If a transaction succeeds (i.e. commits), then all its effects on the data is captured in the database. If the transaction does not succeed (i.e. aborts), then none of its effect on the data is captured in the database. In other words, the transaction processing algorithm guarantees that the database will not reflect a partitial effect of a transaction.","hakan"},
-{"attach","attach","The debugger may attach to a process. When attached, the debugger may show process details, such as message queues and variable bindings.","olin"},
-{"behaviour","behaviour","A \"pattern of design\" which can be used to build applications and processes in an applications.","mbj"},
-{"BIF","BIF","Built-In Functions which perform operations that are impossible or inefficient to program in Erlang itself. Are defined in
-the module Erlang in the application kernel","kenneth"},
-{"binary","binary","A data type in Erlang which is used to store an area of untyped memory. Binaries are used for efficiently handling large quantities of untyped data.","kenneth"},
-{"boolean","boolean","A common data type in programming and specification languages. The value can be either true or false.","kenneth"},
-{"boot file","boot file","A binary file with extension .boot which is read during start of an Erlang node. See SASL User's Guide for more info.","kenneth"},
-{"break point","break point","By setting a break point using the debugger, the user specifies a position in the source code of a module where execution is to be suspended and control transferred to the debugger.","olin"},
-{"CAshort","CA","See Certification Authority.","helen"},
-{"CA certificate","CA certificate","A certificate containing a CA's public key. Network entities use this public key to verify certificates signed with the CA's private key.","helen"},
-{"callback function","callback function","A callback function is a function exported from a callback module, that a generic behaviour calls to perform a specific task.", "mbj"},
-{"callback module","callback module","A callback module is a module that implements the specific parts of a generic behaviour. The generic behaviour specifies which callback functions must be exported from the module.","mbj"},
-{"certificate","certificate","A file used for authenticating network entities under the SSL protocol. A certificate contains information about its owner (called the subject) and issuer, plus the owner's public key and a signature made by a CA. Network entities verify these signatures using CA certificates.","helen"},
-{"CAlong","Certification Authority (CA)","A trusted third party whose purpose is to sign certificates for network entities it has authenticated using secure means. Other network entities can check the signature to verify that a CA has authenticated the bearer of a certificate.","helen"},
-{"CSRlong","Certificate Signing Request (CSR)","An unsigned certificate for submission to a Certification Authority, which signs it with its private key. Once the CSR is signed, it becomes a certificate.","helen"},
-{"child","child","A supervised process. See also permanent, transient, temporary child.","mbj"},
-{"cipher","cipher","A system of encryption.","helen"},
-{"ClearCase","ClearCase","A configuration management system from Rational Software Corporation.","lars"},
-{"client-server model","client-server model","A model where there is a server, which manages some resource, and a number of clients which send requests to the server to access the resource. The client-server model is one of the basic programming techniques for coordinating the activities of several parallel processes.","mbj"},
-{"cover","Coverage Analyser","Module name for the coverage analyser tool, located in the Tools application.","gunilla"},
-{"CORBAlong","Common Object Request Broker Architecture (CORBA)","A specification of an architecture for a distributed object system","lars"},{"CORBA","Common Object Request Broker Architecture (CORBA)","A specification of an architecture for a distributed object system","lars"},
-{"compiler","compiler","A compiler is a translator. A common type of compilers are those who takes source code for a programming language and translates it into code that is executable on a specific platform. E.g. the Erlang compiler translates Erlang source code to an intermediary code that is executable by the Erlang Run Time System.","kenneth"},
-{"consistency","consistency","Consistency refers to the requirement that, given a consistent initial database state, the state of the database after the successful execution of a transaction is also consistent; that is, a transaction transforms the database from a consistent state to another consistent state. Database consistency may be defined as a set of rules or constraints. If the execution of a transaction causes the consistency constraints to be violated, the transaction is not accepted (and thus aborted) by the system.","hakan"},
-{"cookie","cookie","A magic cookie is a secret atom assigned to each Erlang node. The Erlang nodes in a distributed system must know each others cookies in order to authorize each other for communication","kenneth"},
-{"CORBAshort","CORBA","See Common Object RequestBroker Architecture.","lars"},
-{"Coverage Analyser","Coverage Analyser","A tool which provides a set of functions for coverage analysis of Erlang programs, i.e observing how many times each line or function are executed. See also cover.","olin"},
-{"coverage analysis","coverage analysis","The task of determining which lines, or how many lines of code, has actually been executed. Useful for determining the completeness of test suites.","olin"},
-{"cross reference tool","cross reference tool","A tool that can be used for finding dependencies between functions, modules, applications and releases. The Erlang/OTP cross reference tool is called xref and is part of the Tools application.","gunilla"},
-{"CSRshort","CSR","See Certificate Signing Request.","helen"},
-{"data type","data type","The data types in Erlang are numbers, atoms, tuples, lists, pids, funs, records, ports, references and binaries. The values of Erlang data types can be stored in variables.","kenneth"},
-{"DBMSlong","Database Management System (DBMS)","A database is a collection of data and a DBMS is a system which manages the database. Applications accesses the database through the database management system.","hakan"},
-{"DBMSshort","DBMS","See Database Management System.","hakan"},
-{"Debugger","Debugger","An Erlang/OTP tool which provides mechanisms which makes it possible to see what happens during the execution of code in specified modules, or when processes crash.","olin"},
-{"dets","dets","A module within the stdlib application, which provides a term storage, and which is used as the underlying file storage mechanism by the Mnesia DBMS.","hakan"},
-{"dirty operations","dirty operations","Functions which manipulate data in a DBMS, without using transactions.","hakan"},
-{"distributed application","distributed application","An application which runs on one of several nodes. May be restarted on another node. (See local application.)","mbj"},
-{"DNSshort","DNS","See Domain Name System.","lars"},
-{"Docbuilder","Docbuilder","The documentation system used in Erlang/OTP.","jocke"},
-{"DNSlong","Domain Name System (DNS)","DNS is a service that map names to internet addresses","lars"},
-{"DTD","DTD","Document Type Definition as defined in SGML.","jocke"},
-{"durability","durability","If a transaction succeeds, then its effect on the data is persistently captured, and will survive subsequent system failures resulting in loss of data in volatile memory. Durability is usually enforced by first writing modified data to some non-volatile memory (usually disc), before a transaction is allowed to commit. If there is a system failure, the state of the non-volatile memory must be recovered to reflect the effect of all and only committed transactions.","hakan"},
-{"Emacs","Emacs"," A widely used text editor which allows customization of its behaviour. An Erlang mode for Emacs is included in the Erlang deliverables.","kenneth"},
-{"Emacs for Erlang","Emacs for Erlang","A tool which provides a major mode for editing Erlang source files in Emacs.","olin"},
-{"encaps","encapsulation","Data can be encapsulated into another data element.","kent"},
-{"eprof","eprof","A module in the tools application. See Profiler.","olin"},
-{"erl","erl","The command which starts an Erlang run-time system.","kenneth"},
-{"erl_interface","erl_interface library","A thread safe library with C-functions which makes it possible to write a C-program which appears as one of the nodes in a system of distributed Erlang nodes.","kenneth"},
-{"Erlang","Erlang","Erlang is a functional programming language intended for designing large industrial soft real time systems.","kenneth"},
-{"Erlang emulator","Erlang emulator","Another word for Erlang Virtual Machine.","kenneth"},
-{"ERTSlong","Erlang Run Time System","A fundamental part of Erlang/OTP which contains the Erlang Virtual Machine, the kernel and stdlib applications. The Erlang Run Time System is a mandatory part which all other Erlang applications are dependent upon.","kenneth"},
-{"Erlang VM","Erlang Virtual Machine","The virtual machine, which makes Erlang/OTP work together with a specific OS/HW platform. The Erlang Virtual Machine is available on several different platforms. The Erlang Virtual Machine is the glue which makes it possible to run an Erlang application on any platform without change.","kenneth"},
-{"ERTSshort","ERTS","See Erlang Run Time System.","kenneth"},
-{"ETS","ETS","Erlang Term Storage tables.","olin"},
-{"EVAshort","EVA","See Event and Alarm handling application","mbj"},
-{"EVAlong","Event and Alarm handling application (EVA)","An application that consists of Fault Management functionality, such as sending and logging of events and alarms.","mbj"},
-{"event handler","event handler","A module exporting functions which can process events sent to an event manager process. The event handler is a behaviour of type gen_event.","mbj"},
-{"event manager","event manager","A process to which events of a certain category is sent. gen_event handler can be installed in the event manager.","mbj"},
-{"exit signal","exit signal","A signal which is sent from a terminating process to the processes and ports it is linked to. An EXIT signal has the following format: {'EXIT', Exiting_Process_Id, Reason}.","kent"},
-{"foo","foo","Algebraic place holder.","kent"},
-{"FSM","FSM","Finite State Machine.","kenneth"},
-{"fun","fun","A data type, introduced in Erlang 4.4, which represent functional objects.","kenneth"},
-{"function","function","Erlang programs are written entirely in terms of modules with functions. A function can have arguments and does always return a result. A function can be exported which makes it available for calls from other modules. Non exported functions can only be called internally within the module.","kenneth"},
-{"Gateway","gateway","A server which acts as an intermediary for some other server. Unlike a proxy, a gateway receives requests as if it were the origin server for the requested resource; the requesting client may not be aware that it is communicating with a gateway.","jocke"},
-{"gen_event","gen_event","A behaviour used for programming event handling mechanisms, such as alarm handlers, error loggers, and plug-and-play handlers.","mbj"},
-{"gen_fsm","gen_fsm","A behaviour used for programming finite state machines.","mbj"},
-{"gen_server","gen_server","A behaviour used for programming client-server processes.","mbj"},
-{"gen_statem","gen_statem","A behaviour used for programming generic state machines.","raimo"},
-{"gterm","Global Glossary Database","A glossary database used to list common acronymns and defintions etc.","jocke"},
-{"xref","xref","A cross reference tool that can be used for finding dependencies between functions, modules, applications and releases. Part of the Tools application.","gunilla"},
-{"GSlong","Graphics System","A library module which provides a graphics interface for Erlang.","mbj"},
-{"grid","grid","A multi-column object which is used to display tables. (Graphics System.)","mbj"},
-{"GSshort","GS","See Graphics System.","olin"},
-{"GS Contributions","GS Contributions","Unsupported user supplied tools which are included with the Erlang/OTP software release.","olin"},
-{"GUI","GUI","Graphical User Interface","mbj"},
-{"Home Directory","Home Directory","The position of a user account in the file system. The Home Directory is automatically passed to the Erlang run-time system at startup. On Unix the contents of the environment variable \"HOME\" is passed. Om Win32 the concatenation of the environment variables \"HOMEDRIVE\" and \"HOMEPATH\" is passed, or if these variables are not set, the value returned by the Win32 API function \"GetWindowsDirectory\" is passed.","kenneth"},
-{"host name","host name","The name of a machine on a network, e.g. erlang.ericsson.se.","kent"},
-{"HTML","HTML","Hypertext Markup Language.","jocke"},
-{"HTTP","HTTP","Hypertext Transfer Protocol.","jocke"},
-{"HTTPS","HTTPS","The Hypertext Transport Protocol, Secure, the standard SSL communication mechanism of the World Wide Web.","helen"},
-{"IDLshort","IDL","See Interface Description Language.","lars"},
-{"IDLlong","Interface Description Language (IDL)","The interface specification language created by OMG.","lars"},
-{"indexing","indexing","Fast lookup using an (usually enumerated) key.","kent"},
-{"I1","INETS","The Internet Services application","jocke"},
-{"initial call","initial call","The first call to an interpreted function (when using the Interpreter).","kent"},
-{"instrumentation function","instrumentation function","A function used to implement a Managed Object, i.e. give access to the real resources behind an MO.","mbj"},
-{"IDLlong","Interface Description Language (IDL)","The interface specification language created by OMG.","lars"},
-{"interpreter","interpreter","An application which provides mechanisms which make it possible to see what happens during the execution of code in specified (interpreted) modules, or when processes crash.","kent"},
-{"isolation","isolation","A transaction executes as if no other concurrent transactions are executing, and thus its execution results are equivalent to those obtained by executing database transactions serially. A system which maintains transaction isolation is also said to be enforcing serializability.","hakan"},
-{"kernel","kernel","An application which contains file servers, code servers and other code necessary for the Erlang run-time system.","kenneth"},
-{"key","key","A file containing the value that must be fed into an algorithm in order to encrypt or decrypt a message.","helen"},
-{"key pair","key pair","A set of two keys used in public key cryptography. One is the public key used to encrypt data, and the other is the private key necessary to decrypt the same data.","helen"},
-{"list","list","Terms separated by commas and enclosed in square brackets [ ] are called lists. A list is a data type in Erlang, used for storing a variable number of terms. It is dynamically sized. The first element of the list is referred to as the head of the list, and the remainer of the list as the tail.","kenneth"},
-{"list box","list box ","A list of labels with optional scroll bars attached. (Graphics System.)","mbj"},
-{"lc","list comprehension","A language construct in Erlang which are analogous to set comprehensions in Zermelo-Frankel set theory. Analogous to the 'setof' and 'findall' predicates in Prolog.","kenneth"},
-{"local application","local application","An application which runs on one node and which are always started at the local node only. (See distributed application.)","mbj"},
-{"manager","manager","An entity that terminates a management protocol in the Network Management Station.","mbj"},
-{"Master Agent","Master Agent","The SNMP agent system consists of one Master Agent which terminates the SNMP protocol","mbj"}, {"MIB","Management Information Base (MIB)","An abstract definition of the management information available through a management interface in a system.","mbj"},
-{"matching","matching","See pattern matching.","kenneth"},
-{"message queue","message queue","The queue of not yet received messages that are in the mailbox of a process.","olin"},
-{"Mnemosyne","Mnemosyne","Mnemosyne was the query language of Mnesia up to the R11B release. Supersed by QLC.","hakan"},
-{"Mnesia","Mnesia","Mnesia is a distributed Database Management System, appropriate for telecommunications applications and other applications with need of continuous operation and soft real-time properties.","hakan"},
-{"MIBshort","MIB","See Management Information Base.","mbj"},
-{"MIME","MIME","Multi-purpose Internet Mail Extensions.","jocke"},
-{"MOlong","Managed Object (MO)","The abstract management information defined in a MIB.","mbj"}, {"MO", "MO","Managed Object; The abstract management information defined in a MIB.","nibe"},
-{"MOshort","MO","See Managed Object.","mbj"},
-{"module","module","Module is the unit for compilation and for loading in Erlang. A Module contains a module declaration, export declarations and code representing the functions in the module.","kenneth"},
-{"NCSA","NCSA","The National Center for Supercomputing Applications.","jocke"},
-{"NEshort","NE","See Network Element.","mbj"},
-{"NElong", "Network Element","In OTP, the Network Element is the entire distributed OTP system, meaning that the distributed OTP system is managed as one entity.","mbj"}, {"NE", "NE","Network Element; In OTP, the Network Element is the entire distributed OTP system, meaning that the distributed OTP system is managed as one entity.","mbj"},
-{"NMSlong","Network Management Station (NMS)","The place where the operator manages the network.","mbj"}, {"NMS","NMS","Network Management Station; The place where the operator manages the network.","nibe"},
-{"NMSshort","NMS","See Network Management Station.","mbj"},
-{"node","node","An executing Erlang run-time system which can communicate with other Erlang run-time systems.","kenneth"},
-{"node name","node name","A node name is an atom constructed as the concatenation of a name supplied by the user, an \"@\" character, and the name of the host where the node is executing.","kenneth"},
-{"notation","notation","How things are written.","kent"},
-{"notification","notification","Information of an event.","kent"},
-{"NROFF","NROFF","A text formatting language for line printer quality output devices that runs on the UNIX operating system.","jocke"},
-{"number","number","A data type in Erlang. Are subdivided into integers, for storing natural numbers, or floats, for storing real numbers.","kenneth"},
-{"OMGlong","Object Managment Group (OMG)","A standardisation group for all specifications in the area of CORBA.","lars"},
-{"OMGshort","OMG","Object Managment Group.","lars"},
-{"OTP","OTP","Open Telecom Platform","mike"},
-{"os_mon","os_mon","An application which monitors the behaviour of the underlying operating system","mbj"},
-{"parser generator","parser generator","A tool for making compilers which takes a grammar description as input and generates a complete program (a parser) which recognizes input which complies with the grammar. YECC is a parser generator included in the Erlang/OTP.","kenneth"},
-{"pass phrase","pass phrase","The word or phrase which authenticates the user who is authorized to use private key file. The pass phrase prevents unauthorized users from starting, restarting, or reconfiguring the server.","helen"},
-{"pattern matching","pattern matching","A basic mechanism in Erlang for assigning values to variables and for controlling the flow of a program.","kenneth"},
-{"permanent child","permanent child","A supervised process which always is restarted when it dies.","mbj"},
-{"Pid","Pid","Process Identifier. A data type in Erlang for storing process references. The process identity of the process displayed in the line.","kenneth"},
-{"point","point","A unit used to indicate the size of a typeface. Equal to 1/72 inches.","jocke"},
-{"pointer","pointer","A pointer tells where data is stored. Memory pointers are not used in Erlang.","kent"},
-{"port","port","A data type in Erlang. Ports provide the basic mechanism for communication with the external world.","peterl"},
-{"port controller","port controller","An Erlang process which controls a port program. A port has exactly one port controller.","peterl"},
-{"port program","port program","A program that runs as an external program in the operating system and which the Erlang run-time system can start and communicate with by means of the Erlang port mechanism.","kenneth"},
-{"PostScript","PostScript","A language describing a fully laid-out page in terms of fonts, lines, grey scales, and so on, in a way that is interpretable by a printer. The language was developed by Adobe Systems.","jocke"},
-{"pretty-printed","pretty-printed","Nicely formatted code or data, e.g. C or Erlang, with indents and tabs etc.","jocke"},
-{"primitive","primitive","The basic elements in a programming language.","kent"},
-{"private key","private key","The secret key in a pair, used to decrypt incoming messages and sign outgoing ones.","helen"},
-{"process","process","A process is a self-contained separate unit of execution which exists concurrently with other processes in the system. The BIF \"spawn/3\" creates and starts the execution of a new process.","kenneth"},
-{"process dictionary","process dictionary","Each process has an associated dictionary which provides the process with simple destructive storage capabilities.","kenneth"},
-{"Process Manager","Process Manager","Obsolete name for the Process Trace Tool.","olin"},
-{"Process Trace Tool","Process Trace Tool","A tool which gives an overview of the processes in the Erlang run-time system. See also Pman.","olin"},
-{"Profiler","Profiler","Another name for eprof, a tool used to profile a system in order to find out how much time is spent in various segments of a program.","olin"},
-{"program","program","Routines which can be executed by a computer.","kent"},
-{"Proxy","proxy","An intermediary program which acts as both a server and a client for the purpose of making requests on behalf of other clients.","jocke"},
-{"public key","public key","The publicly available key in a key pair, used to encrypt messages bound for its owner and to decrypt signatures made by its owner.","helen"},
-{"query","query","Queries are used for accessing the data in a Database Management System. The query specify a maybe complicated relation that should hold for all of the selected data. This could involve several tables as well as conditions like for instance less then and greater then.","hakan"},
-{"query language","query language","A language which is specially designed to express database queries. Examples of query languages are QLC and SQL.","hakan"},
-{"receive","receive","A primitive for message processing in Erlang, receives a message from a process.","kenneth"},
-{"record","record","A data structure intended for storing a fixed number of related Erlang terms, it is similar to a \"struct\" in C or a \"record\" in Pascal.","kenneth"},
-{"recursion","recursion","A function is recursive if it calls itself until the result desired is attained. Recursion is the heart of functional programming.","kenneth"},
-{"reference","reference","A data type in Erlang for storing system unique references.","kenneth"},
-{"release handler","release handler","A SASL process which handles software upgrade.","mbj"},
-{"relup","release upgrade script","A script with instructions to the release handler of how the release should be installed in the system.","mbj"},
-{"RPC","Remote Proceedure Call","A technique for evaluating a function transparently on a remote node.","kenneth"},
-{"resource","resource","The actual resource to be managed. A resource is represented by a Managed Object. Each resource is mapped to one or several resources.","mbj"},{"resources","resources","The actual resources to be managed. A resource is represented by a Managed Object. Each resource is mapped to one or several resources.","nibe"},
-{"RFC","RFC","A \"Request for Comments\" used as a proposed standard by IETF.","jocke"},
-{"SASLshort","SASL","See System Architecture Support Libraries.","mbj"},
-{"schema","schema","The schema contains the definitions and whereabouts for all tables. In Mnesia it is realized as a special table named \"schema\".","hakan"},
-{"schema functions","schema functions","The functions which are available for managing schemas.","hakan"},
-{"SDL","SDL","Specification and Description Language. A ITU-T standard specification language which is used to specify the behaviour of switching systems.","kenneth"},
-{"send","send","A primitive for message processing in Erlang, sends a message to a process.","kenneth"},
-{"shell","shell"," The shell is an interactive front-end to an Erlang node where Erlang expressions can be evaluated. ","kenneth"},
-{"shell prompt","shell prompt","The text or symbol shown on the screen when the shell is ready to receive commands.","kent"},
-{"SNMPEAlong","Simple Network Management Protocol Extensible Agent (SNMPEA).","An Erlang/OTP application that includes a bilingual extensible SNMP agent.","mbj"},
-{"single assignment","single assignment","Means that once a variable has been assigned a value, the value can never be changed. Erlang is a single assignment language.","kenneth"},
-{"single step","single step","Single stepping is a function provided by the debugger. By single stepping the developer may use the debugger to follow the execution of a process and see what actually happens at each function call.","olin"},
-{"slave","slave","Not in control, can never take over by himself.","kent"},
-{"SSLlong","Secure Sockets Layer (SSL)","A protocol created by Netscape Communications Corporation for authentication and encryption over TCP/IP networks, including Web.","helen"},
-{"signature","signature","An encrypted text block that validates a certificate or other file. A Certification Authority (CA) creates a signature by generating a hash of the public key embedded in a certificate, then encrypting the hash with its own private key. Only the CA's public key can decrypt the signature, verifying that the CA has authenticated the network entity that owns the certificate.","helen"},
-{"SNMPshort","SNMP","Simple Network Management Protocol.","mbj"},
-{"SNMPshort","SNMPEA","See Simple Network Management Protocol Extensible Agent.","mbj"},
-{"spawn","spawn","A primitive for multiprocessing in Erlang, that starts a parallel computation (called a process). The creation of a new process","kenneth"},
-{"SSLshort","SSL","See Secure Sockets Layer.","helen"},
-{"SSLeay","SSLeay","An SSL library developed by Eric Yong (eay@mincom.oz.au).","helen"},
-{"SSLTOP","SSLTOP","The path to your SSL directory, a subdirectory of ServerRoot.","helen"},
-{"start script","start script","A start script is a file with .script extension which is the source when a boot file is created. See SASL User's Guide for more info.","kenneth"},
-{"stdlib","stdlib","An application within Erlang/OTP which contains modules for manipulating lists, strings, files, etc.","kenneth"},
-{"sticky directory","sticky directory","A directory containing Erlang object code that is part of the runtime system.","kent"},
-{"sticky lock","sticky lock","A lock which lingers at a node after the transaction which first acquired the lock has terminated. Once a process has obtained a sticky lock on a node, subsequent locks acquired by processes on the same node, can be set without need of involving remote nodes.","hakan"},
-{"string","string","The ASCII or ISO-8859-1 representation of the list of characters occurring within quotation marks in Erlang code.","kent"},
-{"Subagent","Subagent","The SNMP agent system consists of one Master Agent (See Master Agent) and zero or more Subagents which can be used to distribute the SNMP agent system on several nodes.","mbj"},
-{"supervision tree","supervision tree","A hierarcial tree of processes used to program fault tolerant systems.","mbj"},
-{"supervisor","supervisor","A behaviour to stucture fault tolerant computations, and program supervision trees with.","mbj"},
-{"sup_bridge","supervisor bridge"," A behaviour used to connect a process, or subsystem, to a supervisor tree.","mbj"},
-{"SASLlong","System Architecture Support Libraries (SASL)","An Erlang/OTP application which contains services for error logging, release handling and report browsing.","mbj"}, {".config","system configuration file","A file which specifies configuration parameters for the applications in the system.","mbj"},
-{"table lock","table lock","Table locks are locks which are set on whole tables. They may either be read locks or write locks.","hakan"},
-{"temporary child","temporary child","A supervised process which is never restarted when it dies.","mbj"},
-{"term","term","The super type of all Erlang types.","kenneth"},
-{"tools","tools","An application within Erlang/OTP which contains the tools which are not applications themselves.","olin"},
-{"transaction","transaction","Transactions groups a set of database accesses into an atomic unit. All transactions has the ACID (atomicity, concistency, isolation and durability) properties.","hakan"},
-{"transient child","transient child","A supervised process which is restarted if it dies non-normally.","mbj"},
-{"trigger","trigger","The Interpreter. A break point that is reached by a process triggers if it is active, and the execution of the process is stopped.","olin"},
-{"tty","tty","tty is a simple command line interface program where keystrokes are collected and interpreted. Originally meant teletypewriter equipment. Now it usually means the user console/terminal/shell window.","kent"},
-{"tuple","tuple","A tuple is a data type in Erlang. Tuples are used as place holders for complex data structures. Tuples may contain anything of any size, and are written as sequences of terms separated by commas, and enclosed in curly brackets { }.","kenneth"},
-{"variable","variable","An alias for a memory position, in which a value can be put. Erlang variables always start with an upper case letter.","kenneth"},
-{"workers","workers","The lower nodes in a supervision tree. These are the processes that actually performs some real work, e.g. servers.","mbj"},
-{"YECC","YECC","A LALR-1 parser generator included in Erlang/OTP. It is written in Erlang and generates a parser as an Erlang module.","kenneth"}].
-
-
-
-
diff --git a/system/doc/definitions/term.defs.xml b/system/doc/definitions/term.defs.xml
deleted file mode 100644
index fdcda4eddb..0000000000
--- a/system/doc/definitions/term.defs.xml
+++ /dev/null
@@ -1,1525 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE terms SYSTEM "terms.dtd">
-
-<terms>
- <term>
- <id>agent</id>
- <shortdef>agent</shortdef>
- <def>
-An entity that terminates a management protocol in the Network Element. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>API</id>
- <shortdef>API</shortdef>
- <def>
-Application Programming Interface. The interface towards an application. Usually this is a set of functions available, but can also be a set of messages sent to or from an application. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>application</id>
- <shortdef>application</shortdef>
- <def>
-A collection of resources which is required to offer a specific service. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>appmon</id>
- <shortdef>Application Monitor</shortdef>
- <def>
-A graphical node and application process tree viewer. See also appmon. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>Appmon</id>
- <shortdef>Appmon</shortdef>
- <def>
-Application name for the Application Monitor within Erlang/OTP. A graphical node and process viewer. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>app callback</id>
- <shortdef>application callback module</shortdef>
- <def>
-A module which is called when the application is started, and when it has stopped. Every application has one application callback module. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>AC</id>
- <shortdef>application controller</shortdef>
- <def>
-A process which coordinates all operations on applications. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>app master</id>
- <shortdef>application master</shortdef>
- <def>
-The application master is a process that monitors the application. It is provided by the Erlang run-time system. Every application has an application master process. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>.app file</id>
- <shortdef>application resource file</shortdef>
- <def>
-Specifies the resources required by the application and how the application should be started. Every application has one application resource file, called AppName.app. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>arity</id>
- <shortdef>arity</shortdef>
- <def>
-Denotes the number of arguments to a function. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>ASN.1</id>
- <shortdef>ASN.1</shortdef>
- <def>
-Abstract Syntax Notation One - an ITU-T and ISO standard notation for describing data formats used in communication protocols. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>ASN.1 Compiler</id>
- <shortdef>ASN.1 Compiler</shortdef>
- <def>
-The Erlang/OTP ASN.1 Compiler translates an ASN.1 module into a corresponding Erlang module with encode and decode functions. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>atom</id>
- <shortdef>atom</shortdef>
- <def>
-An atom is a constant. Atoms always starts with a lower case letter (a-z) and are terminated by a non-alphanumeric character - otherwise they must be quoted (enclosed in ' '). An atom is a data type in Erlang, used to enhance the legibility of programs. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>atomicity</id>
- <shortdef>atomicity</shortdef>
- <def>
-Atomicity refers to the "all or nothing" property. If a transaction succeeds (i.e. commits), then all its effects on the data is captured in the database. If the transaction does not succeed (i.e. aborts), then none of its effect on the data is captured in the database. In other words, the transaction processing algorithm guarantees that the database will not reflect a partitial effect of a transaction. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>attach</id>
- <shortdef>attach</shortdef>
- <def>
-The debugger may attach to a process. When attached, the debugger may show process details, such as message queues and variable bindings. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>behaviour</id>
- <shortdef>behaviour</shortdef>
- <def>
-A "pattern of design" which can be used to build applications and processes in an applications. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>BIF</id>
- <shortdef>BIF</shortdef>
- <def>
-Built-In Functions which perform operations that are impossible or inefficient to program in Erlang itself. Are defined in the module Erlang in the application kernel </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>binary</id>
- <shortdef>binary</shortdef>
- <def>
-A data type in Erlang which is used to store an area of untyped memory. Binaries are used for efficiently handling large quantities of untyped data. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>boolean</id>
- <shortdef>boolean</shortdef>
- <def>
-A common data type in programming and specification languages. The value can be either true or false. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>boot file</id>
- <shortdef>boot file</shortdef>
- <def>
-A binary file with extension .boot which is read during start of an Erlang node. See SASL User's Guide for more info. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>break point</id>
- <shortdef>break point</shortdef>
- <def>
-By setting a break point using the debugger, the user specifies a position in the source code of a module where execution is to be suspended and control transferred to the debugger. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>CAshort</id>
- <shortdef>CA</shortdef>
- <def>
-See Certification Authority. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>CA certificate</id>
- <shortdef>CA certificate</shortdef>
- <def>
-A certificate containing a CA's public key. Network entities use this public key to verify certificates signed with the CA's private key. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>callback function</id>
- <shortdef>callback function</shortdef>
- <def>
-A callback function is a function exported from a callback module, that a generic behaviour calls to perform a specific task. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>callback module</id>
- <shortdef>callback module</shortdef>
- <def>
-A callback module is a module that implements the specific parts of a generic behaviour. The generic behaviour specifies which callback functions must be exported from the module. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>certificate</id>
- <shortdef>certificate</shortdef>
- <def>
-A file used for authenticating network entities under the SSL protocol. A certificate contains information about its owner (called the subject) and issuer, plus the owner's public key and a signature made by a CA. Network entities verify these signatures using CA certificates. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>CAlong</id>
- <shortdef>Certification Authority (CA)</shortdef>
- <def>
-A trusted third party whose purpose is to sign certificates for network entities it has authenticated using secure means. Other network entities can check the signature to verify that a CA has authenticated the bearer of a certificate. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>CSRlong</id>
- <shortdef>Certificate Signing Request (CSR)</shortdef>
- <def>
-An unsigned certificate for submission to a Certification Authority, which signs it with its private key. Once the CSR is signed, it becomes a certificate. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>child</id>
- <shortdef>child</shortdef>
- <def>
-A supervised process. See also permanent, transient, temporary child. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>cipher</id>
- <shortdef>cipher</shortdef>
- <def>
-A system of encryption. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>ClearCase</id>
- <shortdef>ClearCase</shortdef>
- <def>
-A configuration management system from Rational Software Corporation. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>client-server model</id>
- <shortdef>client-server model</shortdef>
- <def>
-A model where there is a server, which manages some resource, and a number of clients which send requests to the server to access the resource. The client-server model is one of the basic programming techniques for coordinating the activities of several parallel processes. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>cover</id>
- <shortdef>Coverage Analyser</shortdef>
- <def>
-Module name for the coverage analyser tool, located in the Tools application. </def>
- <resp>gunilla</resp>
- </term>
- <term>
- <id>CORBAlong</id>
- <shortdef>Common Object Request Broker Architecture (CORBA)</shortdef>
- <def>
-A specification of an architecture for a distributed object system </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>CORBA</id>
- <shortdef>Common Object Request Broker Architecture (CORBA)</shortdef>
- <def>
-A specification of an architecture for a distributed object system </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>compiler</id>
- <shortdef>compiler</shortdef>
- <def>
-A compiler is a translator. A common type of compilers are those who takes source code for a programming language and translates it into code that is executable on a specific platform. E.g. the Erlang compiler translates Erlang source code to an intermediary code that is executable by the Erlang Run Time System. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>consistency</id>
- <shortdef>consistency</shortdef>
- <def>
-Consistency refers to the requirement that, given a consistent initial database state, the state of the database after the successful execution of a transaction is also consistent; that is, a transaction transforms the database from a consistent state to another consistent state. Database consistency may be defined as a set of rules or constraints. If the execution of a transaction causes the consistency constraints to be violated, the transaction is not accepted (and thus aborted) by the system. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>cookie</id>
- <shortdef>cookie</shortdef>
- <def>
-A magic cookie is a secret atom assigned to each Erlang node. The Erlang nodes in a distributed system must know each others cookies in order to authorize each other for communication </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>CORBAshort</id>
- <shortdef>CORBA</shortdef>
- <def>
-See Common Object RequestBroker Architecture. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>Coverage Analyser</id>
- <shortdef>Coverage Analyser</shortdef>
- <def>
-A tool which provides a set of functions for coverage analysis of Erlang programs, i.e observing how many times each line or function are executed. See also cover. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>coverage analysis</id>
- <shortdef>coverage analysis</shortdef>
- <def>
-The task of determining which lines, or how many lines of code, has actually been executed. Useful for determining the completeness of test suites. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>cross reference tool</id>
- <shortdef>cross reference tool</shortdef>
- <def>
-A tool that can be used for finding dependencies between functions, modules, applications and releases. The Erlang/OTP cross reference tool is called xref and is part of the Tools application. </def>
- <resp>gunilla</resp>
- </term>
- <term>
- <id>CSRshort</id>
- <shortdef>CSR</shortdef>
- <def>
-See Certificate Signing Request. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>data type</id>
- <shortdef>data type</shortdef>
- <def>
-The data types in Erlang are numbers, atoms, tuples, lists, pids, funs, records, ports, references and binaries. The values of Erlang data types can be stored in variables. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>DBMSlong</id>
- <shortdef>Database Management System (DBMS)</shortdef>
- <def>
-A database is a collection of data and a DBMS is a system which manages the database. Applications accesses the database through the database management system. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>DBMSshort</id>
- <shortdef>DBMS</shortdef>
- <def>
-See Database Management System. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>Debugger</id>
- <shortdef>Debugger</shortdef>
- <def>
-An Erlang/OTP tool which provides mechanisms which makes it possible to see what happens during the execution of code in specified modules, or when processes crash. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>dets</id>
- <shortdef>dets</shortdef>
- <def>
-A module within the stdlib application, which provides a term storage, and which is used as the underlying file storage mechanism by the Mnesia DBMS. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>dirty operations</id>
- <shortdef>dirty operations</shortdef>
- <def>
-Functions which manipulate data in a DBMS, without using transactions. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>distributed application</id>
- <shortdef>distributed application</shortdef>
- <def>
-An application which runs on one of several nodes. May be restarted on another node. (See local application.) </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>DNSshort</id>
- <shortdef>DNS</shortdef>
- <def>
-See Domain Name System. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>Docbuilder</id>
- <shortdef>Docbuilder</shortdef>
- <def>
-The documentation system used in Erlang/OTP. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>DNSlong</id>
- <shortdef>Domain Name System (DNS)</shortdef>
- <def>
-DNS is a service that map names to internet addresses </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>DTD</id>
- <shortdef>DTD</shortdef>
- <def>
-Document Type Definition as defined in SGML. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>durability</id>
- <shortdef>durability</shortdef>
- <def>
-If a transaction succeeds, then its effect on the data is persistently captured, and will survive subsequent system failures resulting in loss of data in volatile memory. Durability is usually enforced by first writing modified data to some non-volatile memory (usually disc), before a transaction is allowed to commit. If there is a system failure, the state of the non-volatile memory must be recovered to reflect the effect of all and only committed transactions. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>Emacs</id>
- <shortdef>Emacs</shortdef>
- <def>
-A widely used text editor which allows customization of its behaviour. An Erlang mode for Emacs is included in the Erlang deliverables. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Emacs for Erlang</id>
- <shortdef>Emacs for Erlang</shortdef>
- <def>
-A tool which provides a major mode for editing Erlang source files in Emacs. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>encaps</id>
- <shortdef>encapsulation</shortdef>
- <def>
-Data can be encapsulated into another data element. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>eprof</id>
- <shortdef>eprof</shortdef>
- <def>
-A module in the tools application. See Profiler. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>erl</id>
- <shortdef>erl</shortdef>
- <def>
-The command which starts an Erlang run-time system. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>erl_interface</id>
- <shortdef>erl_interface library</shortdef>
- <def>
-A thread safe library with C-functions which makes it possible to write a C-program which appears as one of the nodes in a system of distributed Erlang nodes. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Erlang</id>
- <shortdef>Erlang</shortdef>
- <def>
-Erlang is a functional programming language intended for designing large industrial soft real time systems. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Erlang emulator</id>
- <shortdef>Erlang emulator</shortdef>
- <def>
-Another word for Erlang Virtual Machine. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>ERTSlong</id>
- <shortdef>Erlang Run Time System</shortdef>
- <def>
-A fundamental part of Erlang/OTP which contains the Erlang Virtual Machine, the kernel and stdlib applications. The Erlang Run Time System is a mandatory part which all other Erlang applications are dependent upon. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Erlang VM</id>
- <shortdef>Erlang Virtual Machine</shortdef>
- <def>
-The virtual machine, which makes Erlang/OTP work together with a specific OS/HW platform. The Erlang Virtual Machine is available on several different platforms. The Erlang Virtual Machine is the glue which makes it possible to run an Erlang application on any platform without change. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>ERTSshort</id>
- <shortdef>ERTS</shortdef>
- <def>
-See Erlang Run Time System. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>ETS</id>
- <shortdef>ETS</shortdef>
- <def>
-Erlang Term Storage tables. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>EVAshort</id>
- <shortdef>EVA</shortdef>
- <def>
-See Event and Alarm handling application </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>EVAlong</id>
- <shortdef>Event and Alarm handling application (EVA)</shortdef>
- <def>
-An application that consists of Fault Management functionality, such as sending and logging of events and alarms. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>event handler</id>
- <shortdef>event handler</shortdef>
- <def>
-A module exporting functions which can process events sent to an event manager process. The event handler is a behaviour of type gen_event. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>event manager</id>
- <shortdef>event manager</shortdef>
- <def>
-A process to which events of a certain category is sent. gen_event handler can be installed in the event manager. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>exit signal</id>
- <shortdef>exit signal</shortdef>
- <def>
-A signal which is sent from a terminating process to the processes and ports it is linked to. An EXIT signal has the following format: {'EXIT', Exiting_Process_Id, Reason}. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>foo</id>
- <shortdef>foo</shortdef>
- <def>
-Algebraic place holder. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>FSM</id>
- <shortdef>FSM</shortdef>
- <def>
-Finite State Machine. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>fun</id>
- <shortdef>fun</shortdef>
- <def>
-A data type, introduced in Erlang 4.4, which represent functional objects. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>function</id>
- <shortdef>function</shortdef>
- <def>
-Erlang programs are written entirely in terms of modules with functions. A function can have arguments and does always return a result. A function can be exported which makes it available for calls from other modules. Non exported functions can only be called internally within the module. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Gateway</id>
- <shortdef>gateway</shortdef>
- <def>
-A server which acts as an intermediary for some other server. Unlike a proxy, a gateway receives requests as if it were the origin server for the requested resource; the requesting client may not be aware that it is communicating with a gateway. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>gen_event</id>
- <shortdef>gen_event</shortdef>
- <def>
-A behaviour used for programming event handling mechanisms, such as alarm handlers, error loggers, and plug-and-play handlers. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>gen_fsm</id>
- <shortdef>gen_fsm</shortdef>
- <def>
-A behaviour used for programming finite state machines. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>gen_server</id>
- <shortdef>gen_server</shortdef>
- <def>
-A behaviour used for programming client-server processes. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>gterm</id>
- <shortdef>Global Glossary Database</shortdef>
- <def>
-A glossary database used to list common acronymns and defintions etc. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>xref</id>
- <shortdef>xref</shortdef>
- <def>
-A cross reference tool that can be used for finding dependencies between functions, modules, applications and releases. Part of the Tools application. </def>
- <resp>gunilla</resp>
- </term>
- <term>
- <id>GSlong</id>
- <shortdef>Graphics System</shortdef>
- <def>
-A library module which provides a graphics interface for Erlang. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>grid</id>
- <shortdef>grid</shortdef>
- <def>
-A multi-column object which is used to display tables. (Graphics System.) </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>GSshort</id>
- <shortdef>GS</shortdef>
- <def>
-See Graphics System. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>GS Contributions</id>
- <shortdef>GS Contributions</shortdef>
- <def>
-Unsupported user supplied tools which are included with the Erlang/OTP software release. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>GUI</id>
- <shortdef>GUI</shortdef>
- <def>
-Graphical User Interface </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>Home Directory</id>
- <shortdef>Home Directory</shortdef>
- <def>
-The position of a user account in the file system. The Home Directory is automatically passed to the Erlang run-time system at startup. On Unix the contents of the environment variable "HOME" is passed. Om Win32 the concatenation of the environment variables "HOMEDRIVE" and "HOMEPATH" is passed, or if these variables are not set, the value returned by the Win32 API function "GetWindowsDirectory" is passed. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>host name</id>
- <shortdef>host name</shortdef>
- <def>
-The name of a machine on a network, e.g. erlang.ericsson.se. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>HTML</id>
- <shortdef>HTML</shortdef>
- <def>
-Hypertext Markup Language. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>HTTP</id>
- <shortdef>HTTP</shortdef>
- <def>
-Hypertext Transfer Protocol. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>HTTPS</id>
- <shortdef>HTTPS</shortdef>
- <def>
-The Hypertext Transport Protocol, Secure, the standard SSL communication mechanism of the World Wide Web. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>IDLshort</id>
- <shortdef>IDL</shortdef>
- <def>
-See Interface Description Language. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>IDLlong</id>
- <shortdef>Interface Description Language (IDL)</shortdef>
- <def>
-The interface specification language created by OMG. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>indexing</id>
- <shortdef>indexing</shortdef>
- <def>
-Fast lookup using an (usually enumerated) key. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>I1</id>
- <shortdef>INETS</shortdef>
- <def>
-The Internet Services application </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>initial call</id>
- <shortdef>initial call</shortdef>
- <def>
-The first call to an interpreted function (when using the Interpreter). </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>instrumentation function</id>
- <shortdef>instrumentation function</shortdef>
- <def>
-A function used to implement a Managed Object, i.e. give access to the real resources behind an MO. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>IDLlong</id>
- <shortdef>Interface Description Language (IDL)</shortdef>
- <def>
-The interface specification language created by OMG. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>interpreter</id>
- <shortdef>interpreter</shortdef>
- <def>
-An application which provides mechanisms which make it possible to see what happens during the execution of code in specified (interpreted) modules, or when processes crash. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>isolation</id>
- <shortdef>isolation</shortdef>
- <def>
-A transaction executes as if no other concurrent transactions are executing, and thus its execution results are equivalent to those obtained by executing database transactions serially. A system which maintains transaction isolation is also said to be enforcing serializability. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>kernel</id>
- <shortdef>kernel</shortdef>
- <def>
-An application which contains file servers, code servers and other code necessary for the Erlang run-time system. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>key</id>
- <shortdef>key</shortdef>
- <def>
-A file containing the value that must be fed into an algorithm in order to encrypt or decrypt a message. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>key pair</id>
- <shortdef>key pair</shortdef>
- <def>
-A set of two keys used in public key cryptography. One is the public key used to encrypt data, and the other is the private key necessary to decrypt the same data. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>list</id>
- <shortdef>list</shortdef>
- <def>
-Terms separated by commas and enclosed in square brackets [ ] are called lists. A list is a data type in Erlang, used for storing a variable number of terms. It is dynamically sized. The first element of the list is referred to as the head of the list, and the remainer of the list as the tail. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>list box</id>
- <shortdef>list box </shortdef>
- <def>
-A list of labels with optional scroll bars attached. (Graphics System.) </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>lc</id>
- <shortdef>list comprehension</shortdef>
- <def>
-A language construct in Erlang which are analogous to set comprehensions in Zermelo-Frankel set theory. Analogous to the 'setof' and 'findall' predicates in Prolog. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>local application</id>
- <shortdef>local application</shortdef>
- <def>
-An application which runs on one node and which are always started at the local node only. (See distributed application.) </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>manager</id>
- <shortdef>manager</shortdef>
- <def>
-An entity that terminates a management protocol in the Network Management Station. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>Master Agent</id>
- <shortdef>Master Agent</shortdef>
- <def>
-The SNMP agent system consists of one Master Agent which terminates the SNMP protocol </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>MIB</id>
- <shortdef>Management Information Base (MIB)</shortdef>
- <def>
-An abstract definition of the management information available through a management interface in a system. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>matching</id>
- <shortdef>matching</shortdef>
- <def>
-See pattern matching. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>message queue</id>
- <shortdef>message queue</shortdef>
- <def>
-The queue of not yet received messages that are in the mailbox of a process. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>Mnemosyne</id>
- <shortdef>Mnemosyne</shortdef>
- <def>
-Mnemosyne was the query language of Mnesia up to the R11B release. Supersed by QLC.</def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>Mnesia</id>
- <shortdef>Mnesia</shortdef>
- <def>
-Mnesia is a distributed Database Management System, appropriate for telecommunications applications and other applications with need of continuous operation and soft real-time properties. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>MIBshort</id>
- <shortdef>MIB</shortdef>
- <def>
-See Management Information Base. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>MIME</id>
- <shortdef>MIME</shortdef>
- <def>
-Multi-purpose Internet Mail Extensions. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>MOlong</id>
- <shortdef>Managed Object (MO)</shortdef>
- <def>
-The abstract management information defined in a MIB. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>MO</id>
- <shortdef>MO</shortdef>
- <def>
-Managed Object; The abstract management information defined in a MIB. </def>
- <resp>nibe</resp>
- </term>
- <term>
- <id>MOshort</id>
- <shortdef>MO</shortdef>
- <def>
-See Managed Object. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>module</id>
- <shortdef>module</shortdef>
- <def>
-Module is the unit for compilation and for loading in Erlang. A Module contains a module declaration, export declarations and code representing the functions in the module. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>NCSA</id>
- <shortdef>NCSA</shortdef>
- <def>
-The National Center for Supercomputing Applications. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>NEshort</id>
- <shortdef>NE</shortdef>
- <def>
-See Network Element. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>NElong</id>
- <shortdef>Network Element</shortdef>
- <def>
-In OTP, the Network Element is the entire distributed OTP system, meaning that the distributed OTP system is managed as one entity. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>NE</id>
- <shortdef>NE</shortdef>
- <def>
-Network Element; In OTP, the Network Element is the entire distributed OTP system, meaning that the distributed OTP system is managed as one entity. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>NMSlong</id>
- <shortdef>Network Management Station (NMS)</shortdef>
- <def>
-The place where the operator manages the network. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>NMS</id>
- <shortdef>NMS</shortdef>
- <def>
-Network Management Station; The place where the operator manages the network. </def>
- <resp>nibe</resp>
- </term>
- <term>
- <id>NMSshort</id>
- <shortdef>NMS</shortdef>
- <def>
-See Network Management Station. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>node</id>
- <shortdef>node</shortdef>
- <def>
-An executing Erlang run-time system which can communicate with other Erlang run-time systems. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>node name</id>
- <shortdef>node name</shortdef>
- <def>
-A node name is an atom constructed as the concatenation of a name supplied by the user, an "@" character, and the name of the host where the node is executing. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>notation</id>
- <shortdef>notation</shortdef>
- <def>
-How things are written. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>notification</id>
- <shortdef>notification</shortdef>
- <def>
-Information of an event. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>NROFF</id>
- <shortdef>NROFF</shortdef>
- <def>
-A text formatting language for line printer quality output devices that runs on the UNIX operating system. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>number</id>
- <shortdef>number</shortdef>
- <def>
-A data type in Erlang. Are subdivided into integers, for storing natural numbers, or floats, for storing real numbers. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>OMGlong</id>
- <shortdef>Object Managment Group (OMG)</shortdef>
- <def>
-A standardisation group for all specifications in the area of CORBA. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>OMGshort</id>
- <shortdef>OMG</shortdef>
- <def>
-Object Managment Group. </def>
- <resp>lars</resp>
- </term>
- <term>
- <id>OTP</id>
- <shortdef>OTP</shortdef>
- <def>
-Open Telecom Platform </def>
- <resp>mike</resp>
- </term>
- <term>
- <id>os_mon</id>
- <shortdef>os_mon</shortdef>
- <def>
-An application which monitors the behaviour of the underlying operating system </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>parser generator</id>
- <shortdef>parser generator</shortdef>
- <def>
-A tool for making compilers which takes a grammar description as input and generates a complete program (a parser) which recognizes input which complies with the grammar. YECC is a parser generator included in the Erlang/OTP. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>pass phrase</id>
- <shortdef>pass phrase</shortdef>
- <def>
-The word or phrase which authenticates the user who is authorized to use private key file. The pass phrase prevents unauthorized users from starting, restarting, or reconfiguring the server. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>pattern matching</id>
- <shortdef>pattern matching</shortdef>
- <def>
-A basic mechanism in Erlang for assigning values to variables and for controlling the flow of a program. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>permanent child</id>
- <shortdef>permanent child</shortdef>
- <def>
-A supervised process which always is restarted when it dies. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>Pid</id>
- <shortdef>Pid</shortdef>
- <def>
-Process Identifier. A data type in Erlang for storing process references. The process identity of the process displayed in the line. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Pman</id>
- <shortdef>Pman</shortdef>
- <def>
-Module and application name for the Process Trace Tool. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>point</id>
- <shortdef>point</shortdef>
- <def>
-A unit used to indicate the size of a typeface. Equal to 1/72 inches. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>pointer</id>
- <shortdef>pointer</shortdef>
- <def>
-A pointer tells where data is stored. Memory pointers are not used in Erlang. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>port</id>
- <shortdef>port</shortdef>
- <def>
-A data type in Erlang. Ports provide the basic mechanism for communication with the external world. </def>
- <resp>peterl</resp>
- </term>
- <term>
- <id>port controller</id>
- <shortdef>port controller</shortdef>
- <def>
-An Erlang process which controls a port program. A port has exactly one port controller. </def>
- <resp>peterl</resp>
- </term>
- <term>
- <id>port program</id>
- <shortdef>port program</shortdef>
- <def>
-A program that runs as an external program in the operating system and which the Erlang run-time system can start and communicate with by means of the Erlang port mechanism. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>PostScript</id>
- <shortdef>PostScript</shortdef>
- <def>
-A language describing a fully laid-out page in terms of fonts, lines, grey scales, and so on, in a way that is interpretable by a printer. The language was developed by Adobe Systems. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>pretty-printed</id>
- <shortdef>pretty-printed</shortdef>
- <def>
-Nicely formatted code or data, e.g. C or Erlang, with indents and tabs etc. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>primitive</id>
- <shortdef>primitive</shortdef>
- <def>
-The basic elements in a programming language. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>private key</id>
- <shortdef>private key</shortdef>
- <def>
-The secret key in a pair, used to decrypt incoming messages and sign outgoing ones. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>process</id>
- <shortdef>process</shortdef>
- <def>
-A process is a self-contained separate unit of execution which exists concurrently with other processes in the system. The BIF "spawn/3" creates and starts the execution of a new process. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>process dictionary</id>
- <shortdef>process dictionary</shortdef>
- <def>
-Each process has an associated dictionary which provides the process with simple destructive storage capabilities. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Process Manager</id>
- <shortdef>Process Manager</shortdef>
- <def>
-Obsolete name for the Process Trace Tool. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>Process Trace Tool</id>
- <shortdef>Process Trace Tool</shortdef>
- <def>
-A tool which gives an overview of the processes in the Erlang run-time system. See also Pman. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>Profiler</id>
- <shortdef>Profiler</shortdef>
- <def>
-Another name for eprof, a tool used to profile a system in order to find out how much time is spent in various segments of a program. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>program</id>
- <shortdef>program</shortdef>
- <def>
-Routines which can be executed by a computer. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>Proxy</id>
- <shortdef>proxy</shortdef>
- <def>
-An intermediary program which acts as both a server and a client for the purpose of making requests on behalf of other clients. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>public key</id>
- <shortdef>public key</shortdef>
- <def>
-The publicly available key in a key pair, used to encrypt messages bound for its owner and to decrypt signatures made by its owner. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>query</id>
- <shortdef>query</shortdef>
- <def>
-Queries are used for accessing the data in a Database Management System. The query specify a maybe complicated relation that should hold for all of the selected data. This could involve several tables as well as conditions like for instance less then and greater then. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>query language</id>
- <shortdef>query language</shortdef>
- <def>
-A language which is specially designed to express database queries. Examples of query languages are QLC and SQL. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>receive</id>
- <shortdef>receive</shortdef>
- <def>
-A primitive for message processing in Erlang, receives a message from a process. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>record</id>
- <shortdef>record</shortdef>
- <def>
-A data structure intended for storing a fixed number of related Erlang terms, it is similar to a "struct" in C or a "record" in Pascal. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>recursion</id>
- <shortdef>recursion</shortdef>
- <def>
-A function is recursive if it calls itself until the result desired is attained. Recursion is the heart of functional programming. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>reference</id>
- <shortdef>reference</shortdef>
- <def>
-A data type in Erlang for storing system unique references. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>release handler</id>
- <shortdef>release handler</shortdef>
- <def>
-A SASL process which handles software upgrade. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>relup</id>
- <shortdef>release upgrade script</shortdef>
- <def>
-A script with instructions to the release handler of how the release should be installed in the system. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>RPC</id>
- <shortdef>Remote Proceedure Call</shortdef>
- <def>
-A technique for evaluating a function transparently on a remote node. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>resource</id>
- <shortdef>resource</shortdef>
- <def>
-The actual resource to be managed. A resource is represented by a Managed Object. Each resource is mapped to one or several resources. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>resources</id>
- <shortdef>resources</shortdef>
- <def>
-The actual resources to be managed. A resource is represented by a Managed Object. Each resource is mapped to one or several resources. </def>
- <resp>nibe</resp>
- </term>
- <term>
- <id>RFC</id>
- <shortdef>RFC</shortdef>
- <def>
-A "Request for Comments" used as a proposed standard by IETF. </def>
- <resp>jocke</resp>
- </term>
- <term>
- <id>SASLshort</id>
- <shortdef>SASL</shortdef>
- <def>
-See System Architecture Support Libraries. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>schema</id>
- <shortdef>schema</shortdef>
- <def>
-The schema contains the definitions and whereabouts for all tables. In Mnesia it is realized as a special table named "schema". </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>schema functions</id>
- <shortdef>schema functions</shortdef>
- <def>
-The functions which are available for managing schemas. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>SDL</id>
- <shortdef>SDL</shortdef>
- <def>
-Specification and Description Language. A ITU-T standard specification language which is used to specify the behaviour of switching systems. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>send</id>
- <shortdef>send</shortdef>
- <def>
-A primitive for message processing in Erlang, sends a message to a process. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>shell</id>
- <shortdef>shell</shortdef>
- <def>
-The shell is an interactive front-end to an Erlang node where Erlang expressions can be evaluated. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>shell prompt</id>
- <shortdef>shell prompt</shortdef>
- <def>
-The text or symbol shown on the screen when the shell is ready to receive commands. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>SNMPEAlong</id>
- <shortdef>Simple Network Management Protocol Extensible Agent (SNMPEA).</shortdef>
- <def>
-An Erlang/OTP application that includes a bilingual extensible SNMP agent. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>single assignment</id>
- <shortdef>single assignment</shortdef>
- <def>
-Means that once a variable has been assigned a value, the value can never be changed. Erlang is a single assignment language. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>single step</id>
- <shortdef>single step</shortdef>
- <def>
-Single stepping is a function provided by the debugger. By single stepping the developer may use the debugger to follow the execution of a process and see what actually happens at each function call. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>slave</id>
- <shortdef>slave</shortdef>
- <def>
-Not in control, can never take over by himself. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>SSLlong</id>
- <shortdef>Secure Sockets Layer (SSL)</shortdef>
- <def>
-A protocol created by Netscape Communications Corporation for authentication and encryption over TCP/IP networks, including Web. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>signature</id>
- <shortdef>signature</shortdef>
- <def>
-An encrypted text block that validates a certificate or other file. A Certification Authority (CA) creates a signature by generating a hash of the public key embedded in a certificate, then encrypting the hash with its own private key. Only the CA's public key can decrypt the signature, verifying that the CA has authenticated the network entity that owns the certificate. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>SNMPshort</id>
- <shortdef>SNMP</shortdef>
- <def>
-Simple Network Management Protocol. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>SNMPshort</id>
- <shortdef>SNMPEA</shortdef>
- <def>
-See Simple Network Management Protocol Extensible Agent. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>spawn</id>
- <shortdef>spawn</shortdef>
- <def>
-A primitive for multiprocessing in Erlang, that starts a parallel computation (called a process). The creation of a new process </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>SSLshort</id>
- <shortdef>SSL</shortdef>
- <def>
-See Secure Sockets Layer. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>SSLeay</id>
- <shortdef>SSLeay</shortdef>
- <def>
-An SSL library developed by Eric Yong (eay@mincom.oz.au). </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>SSLTOP</id>
- <shortdef>SSLTOP</shortdef>
- <def>
-The path to your SSL directory, a subdirectory of ServerRoot. </def>
- <resp>helen</resp>
- </term>
- <term>
- <id>start script</id>
- <shortdef>start script</shortdef>
- <def>
-A start script is a file with .script extension which is the source when a boot file is created. See SASL User's Guide for more info. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>stdlib</id>
- <shortdef>stdlib</shortdef>
- <def>
-An application within Erlang/OTP which contains modules for manipulating lists, strings, files, etc. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>sticky directory</id>
- <shortdef>sticky directory</shortdef>
- <def>
-A directory containing Erlang object code that is part of the runtime system. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>sticky lock</id>
- <shortdef>sticky lock</shortdef>
- <def>
-A lock which lingers at a node after the transaction which first acquired the lock has terminated. Once a process has obtained a sticky lock on a node, subsequent locks acquired by processes on the same node, can be set without need of involving remote nodes. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>string</id>
- <shortdef>string</shortdef>
- <def>
-The ASCII or ISO-8859-1 representation of the list of characters occurring within quotation marks in Erlang code. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>Subagent</id>
- <shortdef>Subagent</shortdef>
- <def>
-The SNMP agent system consists of one Master Agent (See Master Agent) and zero or more Subagents which can be used to distribute the SNMP agent system on several nodes. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>supervision tree</id>
- <shortdef>supervision tree</shortdef>
- <def>
-A hierarcial tree of processes used to program fault tolerant systems. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>supervisor</id>
- <shortdef>supervisor</shortdef>
- <def>
-A behaviour to stucture fault tolerant computations, and program supervision trees with. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>sup_bridge</id>
- <shortdef>supervisor bridge</shortdef>
- <def>
-A behaviour used to connect a process, or subsystem, to a supervisor tree. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>SASLlong</id>
- <shortdef>System Architecture Support Libraries (SASL)</shortdef>
- <def>
-An Erlang/OTP application which contains services for error logging, release handling and report browsing. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>.config</id>
- <shortdef>system configuration file</shortdef>
- <def>
-A file which specifies configuration parameters for the applications in the system. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>table lock</id>
- <shortdef>table lock</shortdef>
- <def>
-Table locks are locks which are set on whole tables. They may either be read locks or write locks. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>Table Visualizer</id>
- <shortdef>Table Visualizer</shortdef>
- <def>
-A tool which enables the user to examine ETS and Mnesia tables. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>temporary child</id>
- <shortdef>temporary child</shortdef>
- <def>
-A supervised process which is never restarted when it dies. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>term</id>
- <shortdef>term</shortdef>
- <def>
-The super type of all Erlang types. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>Toolbar</id>
- <shortdef>Toolbar</shortdef>
- <def>
-A tool that provides an simplistic interface to the other various Erlang/OTP tools </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>tools</id>
- <shortdef>tools</shortdef>
- <def>
-An application within Erlang/OTP which contains the tools which are not applications themselves. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>transaction</id>
- <shortdef>transaction</shortdef>
- <def>
-Transactions groups a set of database accesses into an atomic unit. All transactions has the ACID (atomicity, concistency, isolation and durability) properties. </def>
- <resp>hakan</resp>
- </term>
- <term>
- <id>transient child</id>
- <shortdef>transient child</shortdef>
- <def>
-A supervised process which is restarted if it dies non-normally. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>trigger</id>
- <shortdef>trigger</shortdef>
- <def>
-The Interpreter. A break point that is reached by a process triggers if it is active, and the execution of the process is stopped. </def>
- <resp>olin</resp>
- </term>
- <term>
- <id>tty</id>
- <shortdef>tty</shortdef>
- <def>
-tty is a simple command line interface program where keystrokes are collected and interpreted. Originally meant teletypewriter equipment. Now it usually means the user console/terminal/shell window. </def>
- <resp>kent</resp>
- </term>
- <term>
- <id>tuple</id>
- <shortdef>tuple</shortdef>
- <def>
-A tuple is a data type in Erlang. Tuples are used as place holders for complex data structures. Tuples may contain anything of any size, and are written as sequences of terms separated by commas, and enclosed in curly brackets { }. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>variable</id>
- <shortdef>variable</shortdef>
- <def>
-An alias for a memory position, in which a value can be put. Erlang variables always start with an upper case letter. </def>
- <resp>kenneth</resp>
- </term>
- <term>
- <id>workers</id>
- <shortdef>workers</shortdef>
- <def>
-The lower nodes in a supervision tree. These are the processes that actually performs some real work, e.g. servers. </def>
- <resp>mbj</resp>
- </term>
- <term>
- <id>YECC</id>
- <shortdef>YECC</shortdef>
- <def>
-A LALR-1 parser generator included in Erlang/OTP. It is written in Erlang and generates a parser as an Erlang module. </def>
- <resp>kenneth</resp>
- </term>
-</terms>
-
diff --git a/system/doc/design_principles/Makefile b/system/doc/design_principles/Makefile
index a73929e15a..833e66ca43 100644
--- a/system/doc/design_principles/Makefile
+++ b/system/doc/design_principles/Makefile
@@ -95,7 +95,9 @@ $(HTMLDIR)/%.gif: %.gif
$(HTMLDIR)/%.svg: %.svg
$(INSTALL_DATA) $< $@
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -116,12 +118,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
-# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
-# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(IMAGE_FILES) $(HTMLDIR)/*.html \
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(IMAGE_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
release_spec:
diff --git a/system/doc/design_principles/applications.xml b/system/doc/design_principles/applications.xml
index 8f1969ff29..32e4de237d 100644
--- a/system/doc/design_principles/applications.xml
+++ b/system/doc/design_principles/applications.xml
@@ -40,18 +40,18 @@
that is, a component that can be started and stopped as a unit,
and which can also be reused in other systems.</p>
<p>To do this, create an
- <seealso marker="#callback_module">application callback module</seealso>,
+ <seeguide marker="#callback_module">application callback module</seeguide>,
and describe how the application is to be started and stopped.</p>
<p>Then, an <em>application specification</em> is needed, which is
put in an
- <seealso marker="#appl_res_file">application resource file</seealso>.
+ <seeguide marker="#appl_res_file">application resource file</seeguide>.
Among other things, this file specifies which modules the application
consists of and the name of the callback module.</p>
<p>If you use <c>systools</c>, the Erlang/OTP tools for packaging code
- (see <seealso marker="release_structure">Releases</seealso>),
+ (see <seeguide marker="release_structure">Releases</seeguide>),
the code for each application is placed in a
separate directory following a pre-defined
- <seealso marker="#app_dir">directory structure</seealso>.</p>
+ <seeguide marker="#app_dir">directory structure</seeguide>.</p>
</section>
<section>
@@ -71,22 +71,22 @@ stop(State)
as is to <c>stop</c>.</item>
<item><c>StartType</c> is usually the atom <c>normal</c>. It has other
values only in the case of a takeover or failover, see
- <seealso marker="distributed_applications">Distributed Applications</seealso>.
+ <seeguide marker="distributed_applications">Distributed Applications</seeguide>.
</item>
<item><c>StartArgs</c> is defined by the key <c>mod</c> in the
- <seealso marker="#appl_res_file">application
- resource file</seealso>.</item>
+ <seeguide marker="#appl_res_file">application
+ resource file</seeguide>.</item>
<item><c>stop/1</c> is called <em>after</em> the application has been
stopped and is to do any necessary cleaning up. The actual stopping of
the application, that is, the shutdown of the supervision tree, is
handled automatically as described in
- <seealso marker="#stopping">Starting and Stopping Applications</seealso>.
+ <seeguide marker="#stopping">Starting and Stopping Applications</seeguide>.
</item>
</list>
<marker id="ch_app"></marker>
<p>Example of an application callback module for packaging
the supervision tree from
- <seealso marker="sup_princ#ex">Supervisor Behaviour</seealso>:</p>
+ <seeguide marker="sup_princ#ex">Supervisor Behaviour</seeguide>:</p>
<code type="none">
-module(ch_app).
-behaviour(application).
@@ -113,7 +113,7 @@ stop(_State) ->
<list>
<item><c>Application</c>, an atom, is the name of the application.
The file must be named <c>Application.app</c>.</item>
- <item>Each <c>Opt</c> is a tuple <c>{Key,Value}</c>, which define a
+ <item>Each <c>Opt</c> is a tuple <c>{Key,Value}</c>, which defines a
certain property of the application. All keys are optional.
Default values are used for any omitted keys.</item>
</list>
@@ -137,7 +137,7 @@ ch_app:start(normal, [])</code>
ch_app:stop([])</code>
<p>When using <c>systools</c>, the Erlang/OTP tools for packaging
code (see Section
- <seealso marker="release_structure">Releases</seealso>), the keys
+ <seeguide marker="release_structure">Releases</seeguide>), the keys
<c>description</c>, <c>vsn</c>, <c>modules</c>, <c>registered</c>,
and <c>applications</c> are also to be specified:</p>
<code type="none">
@@ -167,7 +167,7 @@ ch_app:stop([])</code>
and STDLIB.</item>
</list>
<note><p>For details about the syntax and contents of the application
- resource file, see the <seealso marker="kernel:app">app</seealso>
+ resource file, see the <seefile marker="kernel:app">app</seefile>
manual page in Kernel.</p></note>
</section>
@@ -180,14 +180,14 @@ ch_app:stop([])</code>
<p>This can be useful to know, even if <c>systools</c> is not used,
since Erlang/OTP is packaged according to the OTP principles
and thus comes with a specific directory structure. The code server
- (see the <seealso marker="kernel:code"><c>code(3)</c></seealso> manual
+ (see the <seeerl marker="kernel:code"><c>code(3)</c></seeerl> manual
page in Kernel) automatically uses code from
the directory with the highest version number, if more than one
version of an application is present.</p>
<section>
<title>Directory Structure Guidelines for a Development Environment</title>
<p>Any directory structure for development will suffice as long as the released directory structure
- adhere to the <seealso marker="#app_dir_released">description below</seealso>,
+ adheres to the <seeguide marker="#app_dir_released">description below</seeguide>,
but it is encouraged that the same directory structure
also be used in a development environment. The version number should be omitted from the
application directory name since this is an artifact of the release step.
@@ -236,7 +236,7 @@ ch_app:stop([])</code>
should target the <c>priv/lib</c> or <c>priv/bin</c> directories.</p>
<p>The <c>priv</c> directory holds assets that the application needs during runtime. Executables should
reside in <c>priv/bin</c> and dynamically-linked libraries should reside in <c>priv/lib</c>. Other assets
- are free to reside within the <c>priv</c> directory but it is recommended it does so in a structured manner.</p>
+ are free to reside within the <c>priv</c> directory but it is recommended they do so in a structured manner.</p>
<p>Source files from other languages that generate Erlang code, such as ASN.1 or Mibs, should be placed
in directories, at the top level or in <c>src</c>, with the same name as the source language, for example
<c>asn1</c> and <c>mibs</c>. Build artifacts should be placed in their respective language directory,
@@ -312,7 +312,7 @@ ch_app:stop([])</code>
processes is the <em>application controller</em> process,
registered as <c>application_controller</c>.</p>
<p>All operations on applications are coordinated by the application
- controller. It is interacted through the functions in
+ controller. It is interacted with through the functions in
the module <c>application</c>, see the <c>application(3)</c>
manual page in Kernel. In particular, applications can be
loaded, unloaded, started, and stopped.</p>
@@ -443,7 +443,7 @@ ok
2> <input>application:get_env(ch_app, file).</input>
{ok,"testlog"}</pre>
<p>If
- <seealso marker="release_handling#sys">release handling</seealso>
+ <seeguide marker="release_handling#sys">release handling</seeguide>
is used, exactly one system configuration file is to be used and
that file is to be called <c>sys.config</c>.</p>
<p>The values in the <c>.app</c> file and the values in a
diff --git a/system/doc/design_principles/appup_cookbook.xml b/system/doc/design_principles/appup_cookbook.xml
index 4f23c42c59..e3441d7483 100644
--- a/system/doc/design_principles/appup_cookbook.xml
+++ b/system/doc/design_principles/appup_cookbook.xml
@@ -55,7 +55,7 @@
belong to the STDLIB application and upgrading/downgrading
normally requires an emulator restart.</p>
<p>OTP thus provides no support for changing residence modules except
- in the case of <seealso marker="#spec">special processes</seealso>.</p>
+ in the case of <seeguide marker="#spec">special processes</seeguide>.</p>
</section>
<section>
@@ -64,7 +64,7 @@
extensions simple code replacement is sufficient.</p>
<p><em>Example:</em> When adding a function to <c>ch3</c>,
as described in the example in
- <seealso marker="release_handling#appup">Release Handling</seealso>,
+ <seeguide marker="release_handling#appup">Release Handling</seeguide>,
<c>ch_app.appup</c> looks as follows:</p>
<code type="none">
{"2",
@@ -72,7 +72,7 @@
[{"1", [{load_module, ch3}]}]
}.</code>
<p>OTP also supports changing the internal state of behaviour
- processes, see <seealso marker="#int_state">Changing Internal State</seealso>.</p>
+ processes, see <seeguide marker="#int_state">Changing Internal State</seeguide>.</p>
</section>
<section>
@@ -84,7 +84,7 @@
of the callback module. Thus, synchronized code replacement is
used.</p>
<p><em>Example:</em> Consider <c>gen_server</c> <c>ch3</c> from
- <seealso marker="gen_server_concepts#ex">gen_server Behaviour</seealso>.
+ <seeguide marker="gen_server_concepts#ex">gen_server Behaviour</seeguide>.
The internal state is a term <c>Chs</c>
representing the available channels. Assume you want to add a counter
<c>N</c>, which keeps track of the number of <c>alloc</c> requests
@@ -130,7 +130,7 @@ code_change(_Vsn, Chs, _Extra) ->
<title>Module Dependencies</title>
<p>Assume that a module is extended by adding an interface function,
as in the example in
- <seealso marker="release_handling#appup">Release Handling</seealso>,
+ <seeguide marker="release_handling#appup">Release Handling</seeguide>,
where a function <c>available/0</c> is added to <c>ch3</c>.</p>
<p>If a call is added to this function, say in module
<c>m1</c>, a runtime error could can occur during release upgrade if
@@ -197,7 +197,7 @@ ch_app.appup:
find the process.</p>
</note>
<p><em>Example:</em> Consider the example <c>ch4</c> in
- <seealso marker="spec_proc#ex">sys and proc_lib</seealso>.
+ <seeguide marker="spec_proc#ex">sys and proc_lib</seeguide>.
When started by a supervisor, the child specification can look
as follows:</p>
<code type="none">
@@ -236,13 +236,13 @@ system_code_change(Chs, _Module, _OldVsn, _Extra) ->
(<c>ch4</c>).</item>
<item>The third argument is <c>Vsn</c> or <c>{down,Vsn}</c>, as
described for <c>gen_server:code_change/3</c> in
- <seealso marker="#code_change">Changing Internal State</seealso>.</item>
+ <seeguide marker="#code_change">Changing Internal State</seeguide>.</item>
</list>
<p>In this case, all arguments but the first are ignored and
the function simply returns the internal state again. This is
enough if the code only has been extended. If instead the
internal state is changed (similar to the example in
- <seealso marker="#int_state">Changing Internal State</seealso>),
+ <seeguide marker="#int_state">Changing Internal State</seeguide>),
this is done in this function and <c>{ok,Chs2}</c> returned.</p>
</section>
@@ -271,7 +271,7 @@ system_code_change(Chs, _Module, _OldVsn, _Extra) ->
{update, Module, supervisor}</code>
<p><em>Example:</em> To change the restart strategy of
<c>ch_sup</c> (from
- <seealso marker="sup_princ#ex">Supervisor Behaviour</seealso>)
+ <seeguide marker="sup_princ#ex">Supervisor Behaviour</seeguide>)
from <c>one_for_one</c> to <c>one_for_all</c>, change the callback
function <c>init/1</c> in <c>ch_sup.erl</c>:</p>
<code type="none">
@@ -380,7 +380,7 @@ init(_Args) ->
<title>Starting or Terminating a Process</title>
<p>In a system structured according to the OTP design principles,
any process would be a child process belonging to a supervisor, see
- <seealso marker="#sup_add">Adding and Deleting Child Processes</seealso>
+ <seeguide marker="#sup_add">Adding and Deleting Child Processes</seeguide>
in Changing a Supervisor.</p>
</section>
@@ -398,7 +398,7 @@ init(_Args) ->
complicated to be made without restarting the processes, for
example, if the supervisor hierarchy has been restructured.</p>
<p><em>Example:</em> When adding a child <c>m1</c> to <c>ch_sup</c>, as in
- <seealso marker="#sup_add">Adding and Deleting Child Processes</seealso>
+ <seeguide marker="#sup_add">Adding and Deleting Child Processes</seeguide>
in Changing a Supervisor, an alternative to updating
the supervisor is to restart the entire application:</p>
<code type="none">
@@ -631,14 +631,14 @@ code_change(_OldVsn, State, port) ->
It is executed before all other upgrade instructions.
For more information about this instruction, see
restart_new_emulator (Low-Level) in
- <seealso marker="release_handling#restart_new_emulator_instr">Release Handling Instructions</seealso>.
+ <seeguide marker="release_handling#restart_new_emulator_instr">Release Handling Instructions</seeguide>.
</p></item>
<item><p><c>restart_emulator</c></p>
<p>Used when a restart of the emulator is required after all
other upgrade instructions are executed.
For more information about this instruction, see
restart_emulator (Low-Level) in
- <seealso marker="release_handling#restart_emulator_instr">Release Handling Instructions</seealso>.
+ <seeguide marker="release_handling#restart_emulator_instr">Release Handling Instructions</seeguide>.
</p></item>
</list>
<p>If an emulator restart is necessary and no upgrade instructions
diff --git a/system/doc/design_principles/des_princ.xml b/system/doc/design_principles/des_princ.xml
index 2bfb8eb3c7..f46532c3f2 100644
--- a/system/doc/design_principles/des_princ.xml
+++ b/system/doc/design_principles/des_princ.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>Overview</title>
@@ -193,7 +193,7 @@ handle_cast({free, Ch}, Chs) ->
<p>In <c>ch1.erl</c> and <c>ch2.erl</c> above, the implementation
of <c>channels/0</c>, <c>alloc/1</c>, and <c>free/2</c> has been
intentionally left out, as it is not relevant to the example.
- For completeness, one way to write these functions are given
+ For completeness, one way to write these functions is given
below. This is an example only, a realistic
implementation must be able to handle situations like running out
of channels to allocate, and so on.</p>
@@ -223,13 +223,13 @@ free(Ch, {Alloc, Free} = Channels) ->
to the Erlang/OTP behaviour <c>gen_server</c>.</p>
<p>The standard Erlang/OTP behaviours are:</p>
<list type="bulleted">
- <item><p><seealso marker="gen_server_concepts">gen_server</seealso></p>
+ <item><p><seeguide marker="gen_server_concepts">gen_server</seeguide></p>
<p>For implementing the server of a client-server relation</p></item>
- <item><p><seealso marker="statem">gen_statem</seealso></p>
+ <item><p><seeguide marker="statem">gen_statem</seeguide></p>
<p>For implementing state machines</p></item>
- <item><p><seealso marker="events">gen_event</seealso></p>
+ <item><p><seeguide marker="events">gen_event</seeguide></p>
<p>For implementing event handling functionality</p></item>
- <item><p><seealso marker="sup_princ">supervisor</seealso></p>
+ <item><p><seeguide marker="sup_princ">supervisor</seeguide></p>
<p>For implementing a supervisor in a supervision tree</p></item>
</list>
<p>The compiler understands the module attribute
@@ -267,7 +267,7 @@ free(Ch, {Alloc, Free} = Channels) ->
<p>An application with processes is easiest implemented as a
supervision tree using the standard behaviours.</p>
<p>How to program applications is described in
- <seealso marker="applications">Applications</seealso>.</p>
+ <seeguide marker="applications">Applications</seeguide>.</p>
</section>
<section>
@@ -275,7 +275,7 @@ free(Ch, {Alloc, Free} = Channels) ->
<p>A <em>release</em> is a complete system made out from a subset of
Erlang/OTP applications and a set of user-specific applications.</p>
<p>How to program releases is described in
- <seealso marker="release_structure">Releases</seealso>.</p>
+ <seeguide marker="release_structure">Releases</seeguide>.</p>
<p>How to install a release in a target environment is described
in the section about target systems in Section 2 System Principles.</p>
</section>
@@ -285,7 +285,6 @@ free(Ch, {Alloc, Free} = Channels) ->
<p><em>Release handling</em> is upgrading and downgrading between
different versions of a release, in a (possibly) running system.
How to do this is described in
- <seealso marker="release_handling">Release Handling</seealso>.</p>
+ <seeguide marker="release_handling">Release Handling</seeguide>.</p>
</section>
</chapter>
-
diff --git a/system/doc/design_principles/distributed_applications.xml b/system/doc/design_principles/distributed_applications.xml
index 6a0d65f7a0..99219cd5a5 100644
--- a/system/doc/design_principles/distributed_applications.xml
+++ b/system/doc/design_principles/distributed_applications.xml
@@ -45,7 +45,7 @@
addressing mechanism is required to ensure that it can be
addressed by other applications, regardless on which node it
currently executes. This issue is not addressed here, but the
- <c>global</c> or <c>pg2</c> modules in Kernel
+ <c>global</c> or <c>pg</c> modules in Kernel
can be used for this purpose.</p>
</section>
@@ -123,7 +123,7 @@
<c>application:start(Application)</c> at <em>all of these
nodes.</em></p>
<p>A boot script (see
- <seealso marker="release_structure">Releases</seealso>)
+ <seeguide marker="release_structure">Releases</seeguide>)
can be used that automatically starts the application.</p>
<p>The application is started at the first operational node that
is listed in the list of nodes in the <c>distributed</c>
@@ -164,7 +164,7 @@ Module:start(normal, StartArgs)</code>
Module:start(normal, StartArgs)</code>
<p>An exception is if the application has the <c>start_phases</c>
key defined
- (see <seealso marker="included_applications">Included Applications</seealso>).
+ (see <seeguide marker="included_applications">Included Applications</seeguide>).
The application is then instead started by calling:</p>
<code type="none">
Module:start({failover, Node}, StartArgs)</code>
diff --git a/system/doc/design_principles/events.xml b/system/doc/design_principles/events.xml
index e37b8b460d..c2196c6758 100644
--- a/system/doc/design_principles/events.xml
+++ b/system/doc/design_principles/events.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>gen_event Behaviour</title>
@@ -45,7 +45,7 @@
event handlers. For example, an event manager for handling errors
can by default have a handler installed, which writes error
messages to the terminal. If the error messages during a certain
- period is to be saved to a file as well, the user adds another
+ period are to be saved to a file as well, the user adds another
event handler that does this. When logging to the file is no
longer necessary, this event handler is deleted.</p>
<p>An event manager is implemented as a process and each event
@@ -208,7 +208,7 @@ terminate(_Args, Fd) ->
function is needed. The event manager is automatically
terminated by its supervisor. Exactly how this is done is
defined by a
- <seealso marker="sup_princ#shutdown">shutdown strategy</seealso>
+ <seeguide marker="sup_princ#shutdown">shutdown strategy</seeguide>
set in the supervisor.</p>
</section>
@@ -239,4 +239,3 @@ code_change(OldVsn, State, Extra) ->
{ok, NewState}</code>
</section>
</chapter>
-
diff --git a/system/doc/design_principles/gen_server_concepts.xml b/system/doc/design_principles/gen_server_concepts.xml
index 06413a3d93..3afe4df983 100644
--- a/system/doc/design_principles/gen_server_concepts.xml
+++ b/system/doc/design_principles/gen_server_concepts.xml
@@ -31,7 +31,7 @@
</header>
<marker id="gen_server"></marker>
<p>This section is to be read with the
- <seealso marker="stdlib:gen_server">gen_server(3)</seealso>
+ <seeerl marker="stdlib:gen_server">gen_server(3)</seeerl>
manual page in <c>stdlib</c>, where all interface functions and
callback functions are described in detail.</p>
@@ -52,7 +52,7 @@
<title>Example</title>
<p>An example of a simple server written in plain Erlang is
provided in
- <seealso marker="des_princ#ch1">Overview</seealso>.
+ <seeguide marker="des_princ#ch1">Overview</seeguide>.
The server can be reimplemented using <c>gen_server</c>,
resulting in this callback module:</p>
<marker id="ex"></marker>
@@ -208,7 +208,7 @@ handle_cast({free, Ch}, Chs) ->
function is needed. The <c>gen_server</c> is automatically
terminated by its supervisor. Exactly how this is done is
defined by a
- <seealso marker="sup_princ#shutdown">shutdown strategy</seealso>
+ <seeguide marker="sup_princ#shutdown">shutdown strategy</seeguide>
set in the supervisor.</p>
<p>If it is necessary to clean up before termination, the shutdown
strategy must be a time-out value and the <c>gen_server</c> must
diff --git a/system/doc/design_principles/release_handling.xml b/system/doc/design_principles/release_handling.xml
index 0bcd7dc1b2..d23e9732fc 100644
--- a/system/doc/design_principles/release_handling.xml
+++ b/system/doc/design_principles/release_handling.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>Release Handling</title>
@@ -55,23 +55,23 @@
<section>
<title>Release Handling Workflow</title>
<p><em>Step 1</em>) A release is created as described in
- <seealso marker="release_structure">Releases</seealso>.</p>
+ <seeguide marker="release_structure">Releases</seeguide>.</p>
<p><em>Step 2</em>) The release is transferred to and installed at
target environment. For information of how to install the first
target system, see
- <seealso marker="doc/system_principles:create_target">System Principles</seealso>.</p>
+ <seeguide marker="system/system_principles:create_target">System Principles</seeguide>.</p>
<p><em>Step 3</em>) Modifications, for example, error corrections,
are made to the code in the development environment.</p>
<p><em>Step 4</em>) At some point, it is time to make a new version
of release. The relevant <c>.app</c> files are updated and a new
<c>.rel</c> file is written.</p>
<p><em>Step 5</em>) For each modified application, an
- <seealso marker="#appup">application upgrade file</seealso>,
+ <seeguide marker="#appup">application upgrade file</seeguide>,
<c>.appup</c>, is created. In this file, it is described how to
upgrade and/or downgrade between the old and new version of the
application.</p>
<p><em>Step 6</em>) Based on the <c>.appup</c> files, a
- <seealso marker="#relup">release upgrade file</seealso> called
+ <seeguide marker="#relup">release upgrade file</seeguide> called
<c>relup</c>, is created. This file describes how to upgrade and/or
downgrade between the old and new version of the entire release.</p>
<p><em>Step 7</em>) A new release package is made and transferred to
@@ -95,7 +95,7 @@
<section>
<title>Release Handling Aspects</title>
- <p><seealso marker="appup_cookbook">Appup Cookbook</seealso>,
+ <p><seeguide marker="appup_cookbook">Appup Cookbook</seeguide>,
contains examples of <c>.appup</c> files
for typical cases of upgrades/downgrades that are normally easy to
handle in runtime. However, many aspects can make release handling
@@ -235,10 +235,10 @@
function <c>code_change</c>, passing the term <c>Extra</c> and
some other information as arguments. See the manual pages for
the respective behaviours and
- <seealso marker="appup_cookbook#int_state">Appup Cookbook</seealso>.</p>
+ <seeguide marker="appup_cookbook#int_state">Appup Cookbook</seeguide>.</p>
<p><c>update</c> with argument <c>supervisor</c> is used when
changing the start specification of a supervisor. See
- <seealso marker="appup_cookbook#sup">Appup Cookbook</seealso>.</p>
+ <seeguide marker="appup_cookbook#sup">Appup Cookbook</seeguide>.</p>
<p>When a module is to be updated, the release handler finds
which processes that are <em>using</em> the module by
traversing the supervision tree of each running application
@@ -418,11 +418,11 @@
</list>
<p>For information about the syntax and contents of the <c>.appup</c>
file, see the <c>appup(4)</c> manual page in SASL.</p>
- <p><seealso marker="appup_cookbook">Appup Cookbook</seealso>
+ <p><seeguide marker="appup_cookbook">Appup Cookbook</seeguide>
includes examples of <c>.appup</c> files for typical upgrade/downgrade
cases.</p>
<p><em>Example:</em> Consider the release <c>ch_rel-1</c> from
- <seealso marker="release_structure#ch_rel">Releases</seealso>.
+ <seeguide marker="release_structure#ch_rel">Releases</seeguide>.
Assume you want to add a function <c>available/0</c> to server
<c>ch3</c>, which returns the number of available channels (when
trying out the example, change in a copy of the original
@@ -493,13 +493,13 @@ handle_cast({free, Ch}, Chs) ->
<p>This file does not need to be created manually, it can be
generated by <c>systools:make_relup/3,4</c>. The relevant versions
of the <c>.rel</c> file, <c>.app</c> files, and <c>.appup</c> files
- are used as input. It is deducted which applications are to be
+ are used as input. It is deduced which applications are to be
added and deleted, and which applications that must be upgraded
and/or downgraded. The instructions for this are fetched from
the <c>.appup</c> files and transformed into a single list of
low-level instructions in the right order.</p>
<p>If the <c>relup</c> file is relatively simple, it can be created
- manually. It it only to contain low-level instructions.</p>
+ manually. It is only to contain low-level instructions.</p>
<p>For details about the syntax and contents of the release upgrade
file, see the <c>relup(4)</c> manual page in SASL.</p>
<p><em>Example, continued from the previous section:</em> You have a
@@ -601,7 +601,7 @@ release_handler:remove_release(Vsn) => ok</code>
<p><em>Step 1)</em> Create a target system as described in
System Principles of the first version <c>"A"</c>
of <c>ch_rel</c> from
- <seealso marker="release_structure#ch_rel">Releases</seealso>.
+ <seeguide marker="release_structure#ch_rel">Releases</seeguide>.
This time <c>sys.config</c> must be included in the release package.
If no configuration is needed, the file is to contain the empty
list:</p>
@@ -619,7 +619,7 @@ release_handler:remove_release(Vsn) => ok</code>
<p><em>Step 3)</em> In another Erlang shell, generate start scripts and
create a release package for the new version <c>"B"</c>. Remember to
include (a possible updated) <c>sys.config</c> and the <c>relup</c> file,
- see <seealso marker="#relup">Release Upgrade File</seealso>.</p>
+ see <seeguide marker="#relup">Release Upgrade File</seeguide>.</p>
<pre>
1> <input>systools:make_script("ch_rel-2").</input>
ok
@@ -628,13 +628,13 @@ ok</pre>
<p>The new release package now also contains version "2" of <c>ch_app</c>
and the <c>relup</c> file:</p>
<code type="none">
-% tar tf ch_rel-2.tar
+% tar tf ch_rel-2.tar
lib/kernel-2.9/ebin/kernel.app
lib/kernel-2.9/ebin/application.beam
...
lib/stdlib-1.12/ebin/stdlib.app
lib/stdlib-1.12/ebin/beam_lib.beam
-...
+...
lib/sasl-1.10/ebin/sasl.app
lib/sasl-1.10/ebin/sasl.beam
...
@@ -735,4 +735,3 @@ Module:config_change(Changed, New, Removed)</code>
application callback module.</p>
</section>
</chapter>
-
diff --git a/system/doc/design_principles/release_structure.xml b/system/doc/design_principles/release_structure.xml
index e8dfcad805..9e2e8df9c6 100644
--- a/system/doc/design_principles/release_structure.xml
+++ b/system/doc/design_principles/release_structure.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>Releases</title>
@@ -40,11 +40,11 @@
subset of the Erlang/OTP applications. This is called a
<em>release</em>.</p>
<p>To do this, create a
- <seealso marker="#res_file">release resource file</seealso> that
+ <seeguide marker="#res_file">release resource file</seeguide> that
defines which applications are included in the release.</p>
<p>The release resource file is used to generate
- <seealso marker="#boot">boot scripts</seealso> and
- <seealso marker="#pack">release packages</seealso>. A system
+ <seeguide marker="#boot">boot scripts</seeguide> and
+ <seeguide marker="#pack">release packages</seeguide>. A system
that is transferred to and installed at another site is called a
<em>target system</em>. How to use a release package to create a
target system is described in System Principles.</p>
@@ -75,7 +75,7 @@
the SASL application.</p>
<marker id="ch_rel"></marker>
<p><em>Example: </em> A release of <c>ch_app</c> from
- <seealso marker="applications#ch_app">Applications</seealso>
+ <seeguide marker="applications#ch_app">Applications</seeguide>
has the following <c>.app</c> file:</p>
<code type="none">
{application, ch_app,
@@ -105,7 +105,7 @@
<title>Generating Boot Scripts</title>
<p><c>systools</c> in the SASL application includes tools to
build and check releases. The functions read the <c>rel</c> and
- <c>.app</c> files and performs syntax and dependency checks.
+ <c>.app</c> files and perform syntax and dependency checks.
The <c>systools:make_script/1,2</c> function is used to generate
a boot script (see System Principles):</p>
<pre>
@@ -130,7 +130,7 @@ ok</pre>
Erlang (BEAM) emulator version 5.3
Eshell V5.3 (abort with ^G)
-1>
+1>
=PROGRESS REPORT==== 13-Jun-2003::12:01:15 ===
supervisor: {local,sasl_safe_sup}
started: [{pid,&lt;0.33.0>},
@@ -169,8 +169,8 @@ ok</pre>
<item>The <c>.rel</c> file</item>
<item>The object code for all applications, structured according
to the
- <seealso marker="applications#app_dir">application directory
- structure</seealso></item>
+ <seeguide marker="applications#app_dir">application directory
+ structure</seeguide></item>
<item>The binary boot script renamed to <c>start.boot</c></item>
</list>
<pre>
@@ -210,13 +210,13 @@ releases/ch_rel-1.rel</pre>
<p>If a <c>relup</c> file and/or a system configuration file called
<c>sys.config</c>, or a <c>sys.config.src</c>, is found, these files
are also included in the release package. See
- <seealso marker="release_handling#req">Release Handling</seealso>.</p>
+ <seeguide marker="release_handling#req">Release Handling</seeguide>.</p>
<p>Options can be set to make the release package include source
code and the ERTS binary as well.</p>
<p>For information on how to install the first target system, using
a release package, see System Principles. For information
on how to install a new release package in an existing system, see
- <seealso marker="release_handling">Release Handling</seealso>.</p>
+ <seeguide marker="release_handling">Release Handling</seeguide>.</p>
</section>
<section>
@@ -320,4 +320,3 @@ $ROOT/...
</section>
</section>
</chapter>
-
diff --git a/system/doc/design_principles/spec_proc.xml b/system/doc/design_principles/spec_proc.xml
index 65f5492bdd..5296b20377 100644
--- a/system/doc/design_principles/spec_proc.xml
+++ b/system/doc/design_principles/spec_proc.xml
@@ -45,7 +45,7 @@
<p>The <c>sys</c> module has functions for simple debugging of
processes implemented using behaviours. The <c>code_lock</c>
example from
- <seealso marker="statem#Example">gen_statem Behaviour</seealso>
+ <seeguide marker="statem#Example">gen_statem Behaviour</seeguide>
is used to illustrate this:</p>
<pre>
Erlang/OTP 20 [DEVELOPMENT] [erts-9.0] [source-5ace45e] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]
@@ -113,9 +113,9 @@ ok
<item>Be started in a way that makes the process fit into a
supervision tree</item>
<item>Support the <c>sys</c>
- <seealso marker="#debug">debug facilities</seealso></item>
+ <seeguide marker="#debug">debug facilities</seeguide></item>
<item>Take care of
- <seealso marker="#msg">system messages</seealso>.</item>
+ <seeguide marker="#msg">system messages</seeguide>.</item>
</list>
<p>System messages are messages with a special meaning, used in
the supervision tree. Typical system messages are requests for
@@ -126,7 +126,7 @@ ok
<section>
<title>Example</title>
<p>The simple server from
- <seealso marker="des_princ#ch1">Overview</seealso>,
+ <seeguide marker="des_princ#ch1">Overview</seeguide>,
implemented using <c>sys</c> and <c>proc_lib</c> so it fits into a
supervision tree:</p>
<marker id="ex"></marker>
@@ -445,8 +445,8 @@ loop(...) ->
-callback NameM(ArgM_1, ArgM_2, ..., ArgM_NM) -> ResM.</code>
<p><c>NameX</c> are the names of the expected callbacks.
<c>ArgX_Y</c> and <c>ResX</c> are types as they are described in
- <seealso marker="../reference_manual/typespec">Types and
- Function Specifications</seealso>. The whole syntax of the <c>-spec</c>
+ <seeguide marker="system/reference_manual:typespec">Types and
+ Function Specifications</seeguide>. The whole syntax of the <c>-spec</c>
attribute is supported by the <c>-callback</c> attribute.</p>
<p>Callback functions that are optional for the user of the
behaviour to implement are specified by use of the
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 3cfbd3ec22..2d6ea27635 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -32,7 +32,7 @@
<marker id="gen_statem Behaviour" />
<p>
This section is to be read with the
- <seealso marker="stdlib:gen_statem"><c>gen_statem(3)</c></seealso>
+ <seeerl marker="stdlib:gen_statem"><c>gen_statem(3)</c></seeerl>
manual page in STDLIB, where all interface functions and callback
functions are described in detail.
</p>
@@ -53,8 +53,8 @@
For an Event-Driven State Machine, the input is an event
that triggers a <em>state transition</em> and the output
is actions executed during the <em>state transition</em>.
- It can analogously to the mathematical model of a
- Finite State Machine be described as
+ Analogously to the mathematical model of a
+ Finite State Machine, it can be described as
a set of relations of the following form:
</p>
<pre>
@@ -62,8 +62,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<p>
These relations are interpreted as follows:
if we are in state <c>S</c> and event <c>E</c> occurs, we
- are to perform actions <c>A</c> and make a transition to
- state <c>S'</c>. Notice that <c>S'</c> can be equal to <c>S</c>
+ are to perform actions <c>A</c>, and make a transition to
+ state <c>S'</c>. Notice that <c>S'</c> can be equal to <c>S</c>,
and that <c>A</c> can be empty.
</p>
<p>
@@ -72,9 +72,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
in which the new state <c>S'</c> is different from
the current state <c>S</c>, where "different" means
Erlang's strict inequality: <c>=/=</c>
- also know as "does not match".
- During a <em>state changes</em>,
- <c>gen_statem</c> does more things
+ also known as "does not match".
+ <c>gen_statem</c> does more things during <em>state changes</em>
than during other <em>state transitions</em>.
</p>
<p>
@@ -108,48 +107,48 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
Co-located callback code for each state,
for all
- <seealso marker="#Event Types and Event Content">
+ <seeguide marker="#Event Types and Event Content">
<em>Event Types</em>
- </seealso>
+ </seeguide>
(such as <em>call</em>, <em>cast</em> and <em>info</em>)
</item>
<item>
- <seealso marker="#Postponing Events">
+ <seeguide marker="#Postponing Events">
Postponing Events
- </seealso>
+ </seeguide>
(a substitute for selective receive)
</item>
<item>
- <seealso marker="#Inserted Events">
+ <seeguide marker="#Inserted Events">
Inserted Events
- </seealso>
- that is: events from the state machine to itself
- (in particular purely internal events)
+ </seeguide>
+ (that is, events from the state machine to itself;
+ for purely internal events in particular)
</item>
<item>
- <seealso marker="#State Enter Calls">
+ <seeguide marker="#State Enter Calls">
<em>State Enter Calls</em>
- </seealso>
+ </seeguide>
(callback on state entry co-located with the rest
of each state's callback code)
</item>
<item>
Easy-to-use time-outs
- (<seealso marker="#State Time-Outs">State Time-Outs</seealso>,
- <seealso marker="#Event Time-Outs">Event Time-Outs</seealso>
+ (<seeguide marker="#State Time-Outs">State Time-Outs</seeguide>,
+ <seeguide marker="#Event Time-Outs">Event Time-Outs</seeguide>
and
- <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>
+ <seeguide marker="#Generic Time-Outs">Generic Time-Outs</seeguide>
(named time-outs))
</item>
</list>
<p>
If so, or if possibly needed in future versions,
then you should consider using <c>gen_statem</c> over
- <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso>.
+ <seeerl marker="stdlib:gen_server"><c>gen_server</c></seeerl>.
</p>
<p>
For simple state machines not needing these features
- <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso>
+ <seeerl marker="stdlib:gen_server"><c>gen_server</c></seeerl>
works just fine.
It also has got smaller call overhead,
but we are talking about something like 2 vs 3.3 microseconds
@@ -178,7 +177,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
<p>
The behaviour engine holds the state machine state,
- server data, timer references, a queue of posponed messages
+ server data, timer references, a queue of postponed messages
and other metadata. It receives all process messages,
handles the system messages, and calls the <em>callback module</em>
with machine specific events.
@@ -186,18 +185,18 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<p>
The <em>callback module</em> can be changed for a running server
using any of the
- <seealso marker="#Transition Actions">transition actions</seealso>
- <seealso marker="stdlib:gen_statem#type-action"><c>{change_callback_module, NewModule}</c></seealso>,
- <seealso marker="stdlib:gen_statem#type-action"><c>{push_callback_module, NewModule}</c></seealso> or
- <seealso marker="stdlib:gen_statem#type-action"><c>pop_callback_module</c></seealso>.
- Note that this is a pretty esotheric thing to do...
+ <seeguide marker="#Transition Actions">transition actions</seeguide>
+ <seetype marker="stdlib:gen_statem#action"><c>{change_callback_module, NewModule}</c></seetype>,
+ <seetype marker="stdlib:gen_statem#action"><c>{push_callback_module, NewModule}</c></seetype> or
+ <seetype marker="stdlib:gen_statem#action"><c>pop_callback_module</c></seetype>.
+ Note that this is a pretty esoteric thing to do...
The origin for this feature is a protocol that after
version negotiation branches off into quite different
state machines depending on the protocol version.
There <i>might</i> be other use cases.
<i>Beware</i> that the new callback module
completely replaces the previous behaviour module,
- so all relevant callback functions has to handle
+ so all relevant callback functions have to handle
the state and data from the previous callback module.
</p>
</section>
@@ -212,9 +211,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
<taglist>
<tag>
- <seealso marker="stdlib:gen_statem#type-callback_mode">
+ <seetype marker="stdlib:gen_statem#callback_mode">
<c>state_functions</c>
- </seealso>
+ </seetype>
</tag>
<item>
<p>
@@ -222,9 +221,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-callback_mode">
+ <seetype marker="stdlib:gen_statem#callback_mode">
<c>handle_event_function</c>
- </seealso>
+ </seetype>
</tag>
<item>
<p>
@@ -240,27 +239,27 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
<p>
See the section
- <seealso marker="#State Callback"><em>State Callback</em></seealso>
+ <seeguide marker="#State Callback"><em>State Callback</em></seeguide>
that describes the event handling callback function(s).
</p>
<p>
The <em>callback mode</em> is selected by implementing
a mandatory callback function
- <seealso marker="stdlib:gen_statem#Module:callback_mode/0">
+ <seemfa marker="stdlib:gen_statem#Module:callback_mode/0">
<c>Module:callback_mode()</c>
- </seealso>
+ </seemfa>
that returns one of the <em>callback modes</em>.
</p>
<p>
The
- <seealso marker="stdlib:gen_statem#Module:callback_mode/0">
+ <seemfa marker="stdlib:gen_statem#Module:callback_mode/0">
<c>Module:callback_mode()</c>
- </seealso>
+ </seemfa>
function may also return a list containing the <em>callback mode</em>
and the atom <c>state_enter</c> in which case
- <seealso marker="#State Enter Calls">
+ <seeguide marker="#State Enter Calls">
<em>state enter calls</em>
- </seealso>
+ </seeguide>
are activated for the <em>callback mode</em>.
</p>
@@ -276,7 +275,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
<p>
The two
- <seealso marker="#Callback Modes"><em>callback modes</em></seealso>
+ <seeguide marker="#Callback Modes"><em>callback modes</em></seeguide>
give different possibilities and restrictions,
with one common goal:
to handle all possible combinations of events and states.
@@ -311,9 +310,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
This mode works equally well when you want to focus on
one event at the time or on
one state at the time, but function
- <seealso marker="stdlib:gen_statem#Module:handle_event/4">
+ <seemfa marker="stdlib:gen_statem#Module:handle_event/4">
<c>Module:handle_event/4</c>
- </seealso>
+ </seemfa>
quickly grows too large to handle without branching to
helper functions.
</p>
@@ -321,7 +320,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
The mode enables the use of non-atom states, for example,
complex states or even hierarchical states.
See section
- <seealso marker="#Complex State">Complex State</seealso>.
+ <seeguide marker="#Complex State">Complex State</seeguide>.
If, for example, a state diagram is largely alike
for the client side and the server side of a protocol,
you can have a state <c>{StateName,server}</c> or
@@ -348,26 +347,26 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<tag><c>state_functions</c></tag>
<item>
The event is handled by:<br />
- <seealso marker="stdlib:gen_statem#Module:StateName/3">
+ <seemfa marker="stdlib:gen_statem#Module:StateName/3">
<c>Module:StateName(EventType, EventContent, Data)</c>
- </seealso>
+ </seemfa>
<p>
This form is the one mostly used in the
- <seealso marker="#Example">Example</seealso>
+ <seeguide marker="#Example">Example</seeguide>
section.
</p>
</item>
<tag><c>handle_event_function</c></tag>
<item>
The event is handled by:<br />
- <seealso marker="stdlib:gen_statem#Module:handle_event/4">
+ <seemfa marker="stdlib:gen_statem#Module:handle_event/4">
<c>Module:handle_event(EventType, EventContent, State, Data)</c>
- </seealso>
+ </seemfa>
<p>
See section
- <seealso marker="#One State Callback">
+ <seeguide marker="#One State Callback">
<em>One State Callback</em>
- </seealso>
+ </seeguide>
for an example.
</p>
</item>
@@ -377,20 +376,20 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
The other arguments are the <c>EventType</c>
and the event dependent <c>EventContent</c>,
both described in section
- <seealso marker="#Event Types and Event Content">Event Types and Event Content</seealso>,
+ <seeguide marker="#Event Types and Event Content">Event Types and Event Content</seeguide>,
and the current server <c>Data</c>.
</p>
<p>
<em>State enter calls</em> are also handled by the event handler
and have slightly different arguments. See section
- <seealso marker="#State Enter Calls">State Enter Calls</seealso>.
+ <seeguide marker="#State Enter Calls">State Enter Calls</seeguide>.
</p>
<p>
The <em>state callback</em> return values
are defined in the description of
- <seealso marker="stdlib:gen_statem#Module:StateName/3">
+ <seemfa marker="stdlib:gen_statem#Module:StateName/3">
<c>Module:StateName/3</c>
- </seealso>
+ </seemfa>
in the <c>gen_statem</c> manual page, but here is
a more readable list:
</p>
@@ -409,9 +408,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
<p>
See section
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>Transition Actions</em>
- </seealso>
+ </seeguide>
for a list of possible
<em>transition actions</em>.
</p>
@@ -419,11 +418,11 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
If <c>NextState =/= State</c> this is a <em>state change</em>
so the extra things <c>gen_statem</c> does are: the event queue
is restarted from the oldest
- <seealso marker="#Postponing Events">postponed event</seealso>,
+ <seeguide marker="#Postponing Events">postponed event</seeguide>,
any current
- <seealso marker="#State Time-Outs">state time-out</seealso>
+ <seeguide marker="#State Time-Outs">state time-out</seeguide>
is cancelled, and a
- <seealso marker="#State Enter Calls">state enter call</seealso>
+ <seeguide marker="#State Enter Calls">state enter call</seeguide>
is performed, if enabled.
</p>
</item>
@@ -457,9 +456,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<p>
Same as the <c>keep_state</c> or <c>keep_state_and_data</c> values,
and if
- <seealso marker="#State Enter Calls">
+ <seeguide marker="#State Enter Calls">
State Enter Calls
- </seealso>
+ </seeguide>
are enabled, repeat the <em>state enter call</em>
as if this state was entered again.
</p>
@@ -488,9 +487,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
<p>
Same as the <c>stop</c> values, but first execute the given
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>transition actions</em>
- </seealso>
+ </seeguide>
that may only be reply actions.
</p>
</item>
@@ -501,25 +500,25 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<title>The First State</title>
<p>
To decide the first state the
- <seealso marker="stdlib:gen_statem#Module:init/1">
+ <seemfa marker="stdlib:gen_statem#Module:init/1">
<c>Module:init(Args)</c>
- </seealso>
+ </seemfa>
callback function is called before any
- <seealso marker="#State Callback"><em>state callback</em></seealso>
- is called. This function behaves like an <em>state callback</em>
- function, but gets its only argument <c>Args</c> from
+ <seeguide marker="#State Callback"><em>state callback</em></seeguide>
+ is called. This function behaves like a <em>state callback</em>
+ function, but gets its only argument <c>Args</c> from
the <c>gen_statem</c>
- <seealso marker="stdlib:gen_statem#start/3">
+ <seemfa marker="stdlib:gen_statem#start/3">
<c>start/3,4</c>
- </seealso>
+ </seemfa>
or
- <seealso marker="stdlib:gen_statem#start_link/3">
+ <seemfa marker="stdlib:gen_statem#start_link/3">
<c>start_link/3,4</c>
- </seealso>
+ </seemfa>
function, and returns <c>{ok, State, Data}</c>
or <c>{ok, State, Data, Actions}</c>.
If you use the
- <seealso marker="#Postponing Events"><c>postpone</c></seealso>
+ <seeguide marker="#Postponing Events"><c>postpone</c></seeguide>
action from this function, that action is ignored,
since there is no event to postpone.
</p>
@@ -534,9 +533,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<title>Transition Actions</title>
<p>
In the first section
- <seealso marker="#Event-Driven State Machines">
- Event-Driven State Machines
- </seealso>
+ (<seeguide marker="#Event-Driven State Machines">Event-Driven State Machines</seeguide>),
actions were mentioned as a part of
the general state machine model. These general actions
are implemented with the code that <em>callback module</em>
@@ -549,152 +546,152 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
that a callback function can command the <c>gen_statem</c>
engine to do after the callback function return.
These are commanded by returning a list of
- <seealso marker="stdlib:gen_statem#type-action">actions</seealso>
+ <seetype marker="stdlib:gen_statem#action">actions</seetype>
in the
- <seealso marker="stdlib:gen_statem#type-state_callback_result">
+ <seetype marker="stdlib:gen_statem#state_callback_result">
return value
- </seealso>
+ </seetype>
from the
- <seealso marker="stdlib:gen_statem#Module:StateName/3">callback function</seealso>.
+ <seemfa marker="stdlib:gen_statem#Module:StateName/3">callback function</seemfa>.
These are the possible <em>transition actions</em>:
</p>
<taglist>
<tag>
- <seealso marker="stdlib:gen_statem#type-postpone">
+ <seetype marker="stdlib:gen_statem#postpone">
<c>postpone</c>
- </seealso>
+ </seetype>
<br />
<c>{postpone, Boolean}</c>
</tag>
<item>
If set postpone the current event, see section
- <seealso marker="#Postponing Events">Postponing Events</seealso>.
+ <seeguide marker="#Postponing Events">Postponing Events</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-hibernate">
+ <seetype marker="stdlib:gen_statem#hibernate">
<c>hibernate</c>
- </seealso>
+ </seetype>
<br />
<c>{hibernate, Boolean}</c>
</tag>
<item>
If set hibernate the <c>gen_statem</c>, treated in section
- <seealso marker="#Hibernation">Hibernation</seealso>.
+ <seeguide marker="#Hibernation">Hibernation</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-state_timeout">
+ <seetype marker="stdlib:gen_statem#state_timeout">
<c>{state_timeout, Time, EventContent}</c>
- </seealso>
+ </seetype>
<br />
<c>{state_timeout, Time, EventContent, Opts}</c><br />
- <seealso marker="stdlib:gen_statem#type-timeout_update_action">
+ <seetype marker="stdlib:gen_statem#timeout_update_action">
<c>{state_timeout, update, EventContent}</c>
- </seealso>
+ </seetype>
<br />
- <seealso marker="stdlib:gen_statem#type-timeout_cancel_action">
+ <seetype marker="stdlib:gen_statem#timeout_cancel_action">
<c>{state_timeout, cancel}</c>
- </seealso>
+ </seetype>
</tag>
<item>
Start, update or cancel a state time-out, read more in sections
- <seealso marker="#Time-Outs">Time-Outs</seealso> and
- <seealso marker="#State Time-Outs">State Time-Outs</seealso>.
+ <seeguide marker="#Time-Outs">Time-Outs</seeguide> and
+ <seeguide marker="#State Time-Outs">State Time-Outs</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-generic_timeout">
+ <seetype marker="stdlib:gen_statem#generic_timeout">
<c>{{timeout, Name}, Time, EventContent}</c>
- </seealso>
+ </seetype>
<br />
<c>{{timeout, Name}, Time, EventContent, Opts}</c><br />
- <seealso marker="stdlib:gen_statem#type-timeout_update_action">
+ <seetype marker="stdlib:gen_statem#timeout_update_action">
<c>{{timeout, Name}, update, EventContent}</c>
- </seealso>
+ </seetype>
<br />
- <seealso marker="stdlib:gen_statem#type-timeout_cancel_action">
+ <seetype marker="stdlib:gen_statem#timeout_cancel_action">
<c>{{timeout, Name}, cancel}</c>
- </seealso>
+ </seetype>
</tag>
<item>
Start, update or cancel a generic time-out, read more in sections
- <seealso marker="#Time-Outs">Time-Outs</seealso> and
- <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>.
+ <seeguide marker="#Time-Outs">Time-Outs</seeguide> and
+ <seeguide marker="#Generic Time-Outs">Generic Time-Outs</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-event_timeout">
+ <seetype marker="stdlib:gen_statem#event_timeout">
<c>{timeout, Time, EventContent}</c>
- </seealso>
+ </seetype>
<br />
<c>{timeout, Time, EventContent, Opts}</c><br />
<c>Time</c>
</tag>
<item>
Start an event time-out, see more in sections
- <seealso marker="#Time-Outs">Time-Outs</seealso> and
- <seealso marker="#Event Time-Outs">Event Time-Outs</seealso>.
+ <seeguide marker="#Time-Outs">Time-Outs</seeguide> and
+ <seeguide marker="#Event Time-Outs">Event Time-Outs</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-reply_action">
+ <seetype marker="stdlib:gen_statem#reply_action">
<c>{reply, From, Reply}</c>
- </seealso>
+ </seetype>
</tag>
<item>
Reply to a caller, mentioned at the end of section
- <seealso marker="#All State Events">All State Events</seealso>.
+ <seeguide marker="#All State Events">All State Events</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-action">
+ <seetype marker="stdlib:gen_statem#action">
<c>{next_event, EventType, EventContent}</c>
- </seealso>
+ </seetype>
</tag>
<item>
- Generate the next event to handle, see section
- <seealso marker="#Inserted Events">Inserted Events</seealso>.
+ Generate the next event to handle, see section
+ <seeguide marker="#Inserted Events">Inserted Events</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-action">
+ <seetype marker="stdlib:gen_statem#action">
<c>{change_callback_module, NewModule}</c>
- </seealso>
+ </seetype>
</tag>
<item>
Change the
- <seealso marker="#Callback Module">
+ <seeguide marker="#Callback Module">
<em>callback module</em>
- </seealso>
+ </seeguide>
for the running server.
This can be done during any <em>state transition</em>,
whether it is a <em>state change</em> or not,
but it can <i>not</i> be done from a
- <seealso marker="#State Enter Calls"><em>state enter call</em></seealso>.
+ <seeguide marker="#State Enter Calls"><em>state enter call</em></seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-action">
+ <seetype marker="stdlib:gen_statem#action">
<c>{push_callback_module, NewModule}</c>
- </seealso>
+ </seetype>
</tag>
<item>
Push the current <em>callback module</em>
to the top of an internal stack of callback modules
and set the new
- <seealso marker="#Callback Module">
+ <seeguide marker="#Callback Module">
<em>callback module</em>
- </seealso>
+ </seeguide>
for the running server.
Otherwise like
<c>{change_callback_module, NewModule}</c>
above.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-action">
+ <seetype marker="stdlib:gen_statem#action">
<c>pop_callback_module</c>
- </seealso>
+ </seetype>
</tag>
<item>
Pop the top module from
the internal stack of callback modules
and set it to be the new
- <seealso marker="#Callback Module">
+ <seeguide marker="#Callback Module">
<em>callback module</em>
- </seealso>
+ </seeguide>
for the running server.
If the stack is empty the server fails.
Otherwise like
@@ -705,7 +702,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<p>
For details, see the <c>gen_statem(3)</c>
manual page for type
- <seealso marker="stdlib:gen_statem#type-action"><c>action()</c></seealso>.
+ <seetype marker="stdlib:gen_statem#action"><c>action()</c></seetype>.
You can, for example, reply to many callers,
generate multiple next events,
and set a time-out to use absolute instead of relative time
@@ -715,22 +712,22 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
Among these <em>transition actions</em> only to reply to a caller
is an immediate action. The others are collected and handled
later during the <em>state transition</em>.
- <seealso marker="#Inserted Events">Inserted Events</seealso>
+ <seeguide marker="#Inserted Events">Inserted Events</seeguide>
are stored and inserted all together,
and the rest set transition options
where the last of a specific type override the previous.
See the description of a <em>state transition</em>
in the <c>gen_statem(3)</c> manual page for type
- <seealso marker="stdlib:gen_statem#type-transition_option"><c>transition_option()</c></seealso>.
+ <seetype marker="stdlib:gen_statem#transition_option"><c>transition_option()</c></seetype>.
</p>
<p>
The different
- <seealso marker="#Time-Outs">Time-Outs</seealso> and
- <seealso marker="#Inserted Events"><c>next_event</c></seealso>
+ <seeguide marker="#Time-Outs">Time-Outs</seeguide> and
+ <seeguide marker="#Inserted Events"><c>next_event</c></seeguide>
actions generate new events with corresponding
- <seealso marker="#Event Types and Event Content">
+ <seeguide marker="#Event Types and Event Content">
Event Types and Event Content
- </seealso>.
+ </seeguide>.
</p>
</section>
@@ -741,7 +738,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<title>Event Types and Event Content</title>
<p>
Events are categorized in different
- <seealso marker="stdlib:gen_statem#type-event_type"><em>event types</em></seealso>.
+ <seetype marker="stdlib:gen_statem#event_type"><em>event types</em></seetype>.
Events of all types are for a given state
handled in the same callback function, and that function gets
<c>EventType</c> and <c>EventContent</c> as arguments.
@@ -754,40 +751,40 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
<taglist>
<tag>
- <seealso marker="stdlib:gen_statem#type-external_event_type">
+ <seetype marker="stdlib:gen_statem#external_event_type">
<c>cast</c>
- </seealso>
+ </seetype>
</tag>
<item>
Generated by
- <seealso marker="stdlib:gen_statem#cast/2">
+ <seemfa marker="stdlib:gen_statem#cast/2">
<c>gen_statem:cast(ServerRef, Msg)</c>
- </seealso>
+ </seemfa>
where <c>Msg</c> becomes the <c>EventContent</c>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-external_event_type">
+ <seetype marker="stdlib:gen_statem#external_event_type">
<c>{call,From}</c>
- </seealso>
+ </seetype>
</tag>
<item>
Generated by
- <seealso marker="stdlib:gen_statem#call/2">
+ <seemfa marker="stdlib:gen_statem#call/2">
<c>gen_statem:call(ServerRef, Request)</c>
- </seealso>
+ </seemfa>
where <c>Request</c> becomes the <c>EventContent</c>.
<c>From</c> is the reply address to use
when replying either through the <em>transition action</em>
<c>{reply,From,Reply}</c> or by calling
- <seealso marker="stdlib:gen_statem#reply/1">
+ <seemfa marker="stdlib:gen_statem#reply/1">
<c>gen_statem:reply(From, Reply)</c>
- </seealso>
+ </seemfa>
from the <em>callback module</em>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-external_event_type">
+ <seetype marker="stdlib:gen_statem#external_event_type">
<c>info</c>
- </seealso>
+ </seetype>
</tag>
<item>
Generated by any regular process message sent to
@@ -795,56 +792,56 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
The process message becomes the <c>EventContent</c>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-timeout_event_type">
+ <seetype marker="stdlib:gen_statem#timeout_event_type">
<c>state_timeout</c>
- </seealso>
+ </seetype>
</tag>
<item>
Generated by <em>transition action</em>
- <seealso marker="stdlib:gen_statem#type-timeout_action">
+ <seetype marker="stdlib:gen_statem#timeout_action">
<c>{state_timeout,Time,EventContent}</c>
- </seealso>
+ </seetype>
state timer timing out. Read more in sections
- <seealso marker="#Time-Outs">Time-Outs</seealso> and
- <seealso marker="#State Time-Outs">State Time-Outs</seealso>.
+ <seeguide marker="#Time-Outs">Time-Outs</seeguide> and
+ <seeguide marker="#State Time-Outs">State Time-Outs</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-timeout_event_type">
+ <seetype marker="stdlib:gen_statem#timeout_event_type">
<c>{timeout,Name}</c>
- </seealso>
+ </seetype>
</tag>
<item>
Generated by <em>transition action</em>
- <seealso marker="stdlib:gen_statem#type-timeout_action">
+ <seetype marker="stdlib:gen_statem#timeout_action">
<c>{{timeout,Name},Time,EventContent}</c>
- </seealso>
+ </seetype>
generic timer timing out. Read more in sections
- <seealso marker="#Time-Outs">Time-Outs</seealso> and
- <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>.
+ <seeguide marker="#Time-Outs">Time-Outs</seeguide> and
+ <seeguide marker="#Generic Time-Outs">Generic Time-Outs</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-timeout_event_type">
+ <seetype marker="stdlib:gen_statem#timeout_event_type">
<c>timeout</c>
- </seealso>
+ </seetype>
</tag>
<item>
Generated by <em>transition action</em>
- <seealso marker="stdlib:gen_statem#type-timeout_action">
+ <seetype marker="stdlib:gen_statem#timeout_action">
<c>{timeout,Time,EventContent}</c>
- </seealso>
+ </seetype>
(or its short form <c>Time</c>)
event timer timing out. Read more in sections
- <seealso marker="#Time-Outs">Time-Outs</seealso> and
- <seealso marker="#Event Time-Outs">Event Time-Outs</seealso>.
+ <seeguide marker="#Time-Outs">Time-Outs</seeguide> and
+ <seeguide marker="#Event Time-Outs">Event Time-Outs</seeguide>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-event_type">
+ <seetype marker="stdlib:gen_statem#event_type">
<c>internal</c>
- </seealso>
+ </seetype>
</tag>
<item>
Generated by <em>transition action</em>
- <seealso marker="stdlib:gen_statem#type-action"><c>{next_event,internal,EventContent}</c></seealso>.
+ <seetype marker="stdlib:gen_statem#action"><c>{next_event,internal,EventContent}</c></seetype>.
All <em>event types</em> above can also be generated using
the <c>next_event</c> action:
<c>{next_event,EventType,EventContent}</c>.
@@ -861,9 +858,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
The <c>gen_statem</c> behaviour can if this is enabled,
regardless of <em>callback mode</em>,
automatically
- <seealso marker="stdlib:gen_statem#type-state_enter">
+ <seetype marker="stdlib:gen_statem#state_enter">
call the state callback
- </seealso>
+ </seetype>
with special arguments whenever the state changes
so you can write state enter actions
near the rest of the <em>state transition</em> rules.
@@ -879,13 +876,13 @@ StateName(EventType, EventContent, Data) ->
<p>
Since the <em>state enter call</em> is not an event there are restrictions
on the allowed return value and
- <seealso marker="#Transition Actions">State Transition Actions</seealso>.
+ <seeguide marker="#Transition Actions">State Transition Actions</seeguide>.
You may not change the state,
- <seealso marker="#Postponing Events">postpone</seealso>
+ <seeguide marker="#Postponing Events">postpone</seeguide>
this non-event,
- <seealso marker="#Inserted Events">insert any events</seealso>,
+ <seeguide marker="#Inserted Events">insert any events</seeguide>,
or change the
- <seealso marker="#Callback Module"><em>callback module</em></seealso>.
+ <seeguide marker="#Callback Module"><em>callback module</em></seeguide>.
</p>
<p>
The first state that is entered
@@ -895,8 +892,8 @@ StateName(EventType, EventContent, Data) ->
<p>
You may repeat the <em>state enter call</em>
using the <c>{repeat_state,...}</c>
- return value from the
- <seealso marker="#State Callback">state callback</seealso>.
+ return value from the
+ <seeguide marker="#State Callback">state callback</seeguide>.
In this case <c>OldState</c> will also be equal to the current state.
</p>
<p>
@@ -904,9 +901,9 @@ StateName(EventType, EventContent, Data) ->
this can be a very useful feature, but it forces you to handle
the <em>state enter calls</em> in all states.
See also the
- <seealso marker="#State Enter Actions">
+ <seeguide marker="#State Enter Actions">
State Enter Actions
- </seealso>
+ </seeguide>
section.
</p>
</section>
@@ -918,50 +915,50 @@ StateName(EventType, EventContent, Data) ->
<title>Time-Outs</title>
<p>
Time-outs in <c>gen_statem</c> are started from a
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>transition action</em>
- </seealso>
+ </seeguide>
during a state transition that is when exiting from the
- <seealso marker="#State Callback"><em>state callback</em></seealso>.
+ <seeguide marker="#State Callback"><em>state callback</em></seeguide>.
</p>
<p>
There are 3 types of time-outs in <c>gen_statem</c>:
</p>
<taglist>
<tag>
- <seealso marker="stdlib:gen_statem#type-state_timeout">
+ <seetype marker="stdlib:gen_statem#state_timeout">
<c>state_timeout</c>
- </seealso>
+ </seetype>
</tag>
<item>
There is one
- <seealso marker="#State Time-Outs">State Time-Out</seealso>
+ <seeguide marker="#State Time-Outs">State Time-Out</seeguide>
that is automatically cancelled by a <em>state change</em>.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-generic_timeout">
+ <seetype marker="stdlib:gen_statem#generic_timeout">
<c>{timeout, Name}</c>
- </seealso>
+ </seetype>
</tag>
<item>
There are any number of
- <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>
+ <seeguide marker="#Generic Time-Outs">Generic Time-Outs</seeguide>
differing by their <c>Name</c>.
They have no automatic cancelling.
</item>
<tag>
- <seealso marker="stdlib:gen_statem#type-event_timeout">
+ <seetype marker="stdlib:gen_statem#event_timeout">
<c>timeout</c>
- </seealso>
+ </seetype>
</tag>
<item>
There is one
- <seealso marker="#Event Time-Outs">Event Time-Out</seealso>
+ <seeguide marker="#Event Time-Outs">Event Time-Out</seeguide>
that is automatically cancelled by any event.
Note that
- <seealso marker="#Postponing Events">postponed </seealso>
+ <seeguide marker="#Postponing Events">postponed </seeguide>
and
- <seealso marker="#Inserted Events">inserted</seealso>
+ <seeguide marker="#Inserted Events">inserted</seeguide>
events cancel this time-out just as external events.
</item>
</taglist>
@@ -972,13 +969,13 @@ StateName(EventType, EventContent, Data) ->
</p>
<p>
All time-outs has got an <c>EventContent</c> that is part of the
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>transition action</em>
- </seealso>
+ </seeguide>
that starts the time-out.
Different <c>EventContent</c>s does not create different time-outs.
The <c>EventContent</c> is delivered to the
- <seealso marker="#State Callback"><em>state callback</em></seealso>
+ <seeguide marker="#State Callback"><em>state callback</em></seeguide>
when the time-out expires.
</p>
<section>
@@ -993,13 +990,13 @@ StateName(EventType, EventContent, Data) ->
</p>
<p>
A more explicit way to cancel a timer is to use a
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>transition action</em>
- </seealso>
+ </seeguide>
on the form
- <seealso marker="stdlib:gen_statem#type-timeout_cancel_action">
+ <seetype marker="stdlib:gen_statem#timeout_cancel_action">
<c>{TimeoutType, cancel}</c>
- </seealso>
+ </seetype>
which is a feature introduced in OTP 22.1.
</p>
</section>
@@ -1009,20 +1006,20 @@ StateName(EventType, EventContent, Data) ->
<p>
While a time-out is running, its <c>EventContent</c>
can be updated using a
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>transition action</em>
- </seealso>
+ </seeguide>
on the form
- <seealso marker="stdlib:gen_statem#type-timeout_update_action">
+ <seetype marker="stdlib:gen_statem#timeout_update_action">
<c>{TimeoutType, update, NewEventContent}</c>
- </seealso>
+ </seetype>
which is a feature introduced in OTP 22.1.
</p>
<p>
If this feature is used while no such <c>TimeoutType</c>
is running then a time-out event is immediately delivered
as when starting a
- <seealso marker="#Time-Out Zero">Time-Out Zero</seealso>.
+ <seeguide marker="#Time-Out Zero">Time-Out Zero</seeguide>.
</p>
</section>
<section>
@@ -1035,9 +1032,9 @@ StateName(EventType, EventContent, Data) ->
already enqueued, and before any not yet received external events.
Note that some time-outs are automatically cancelled
so if you for example combine
- <seealso marker="#Postponing Events">postponing</seealso>
+ <seeguide marker="#Postponing Events">postponing</seeguide>
an event in a <em>state change</em> with starting an
- <seealso marker="#Event Time-Outs">event time-out</seealso>
+ <seeguide marker="#Event Time-Outs">event time-out</seeguide>
with time <c>0</c> there will be no time-out event inserted
since the event time-out is cancelled by the postponed
event that is delivered due to the state change.
@@ -1148,7 +1145,7 @@ start_link(Code) ->
]]></code>
<p>
<c>start_link</c> calls function
- <seealso marker="stdlib:gen_statem#start_link/4"><c>gen_statem:start_link/4</c></seealso>,
+ <seemfa marker="stdlib:gen_statem#start_link/4"><c>gen_statem:start_link/4</c></seemfa>,
which spawns and links to a new process, a <c>gen_statem</c>.
</p>
<list type="bulleted">
@@ -1163,7 +1160,7 @@ start_link(Code) ->
Instead its pid must be used. The name can also be specified
as <c>{global,Name}</c>, then the <c>gen_statem</c> is
registered using
- <seealso marker="kernel:global#register_name/2"><c>global:register_name/2</c></seealso>
+ <seemfa marker="kernel:global#register_name/2"><c>global:register_name/2</c></seemfa>
in Kernel.
</p>
</item>
@@ -1193,7 +1190,7 @@ start_link(Code) ->
<p>
The fourth argument, <c>[]</c>, is a list of options.
For the available options, see
- <seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link/3</c></seealso>.
+ <seemfa marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link/3</c></seemfa>.
</p>
</item>
</list>
@@ -1204,7 +1201,7 @@ start_link(Code) ->
where <c>State</c> is the initial state of the <c>gen_statem</c>,
in this case <c>locked</c>; assuming that the door is locked to begin
with. <c>Data</c> is the internal server data of the <c>gen_statem</c>.
- Here the server data is a <seealso marker="stdlib:maps">map</seealso>
+ Here the server data is a <seeerl marker="stdlib:maps">map</seeerl>
with key <c>code</c> that stores the correct button sequence,
key <c>length</c> store its length,
and key <c>buttons</c> that stores the collected buttons
@@ -1218,28 +1215,28 @@ init(Code) ->
{ok, locked, Data}.
]]></code>
<p>Function
- <seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link</c></seealso>
+ <seemfa marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link</c></seemfa>
is synchronous. It does not return until the <c>gen_statem</c>
is initialized and is ready to receive events.
</p>
<p>
Function
- <seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link</c></seealso>
+ <seemfa marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link</c></seemfa>
must be used if the <c>gen_statem</c>
is part of a supervision tree, that is, started by a supervisor.
Another function,
- <seealso marker="stdlib:gen_statem#start/3"><c>gen_statem:start</c></seealso>
+ <seemfa marker="stdlib:gen_statem#start/3"><c>gen_statem:start</c></seemfa>
can be used to start a standalone <c>gen_statem</c>, that is,
a <c>gen_statem</c> that is not part of a supervision tree.
</p>
<p>
Function
- <seealso marker="stdlib:gen_statem#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
+ <seemfa marker="stdlib:gen_statem#Module:callback_mode/0"><c>Module:callback_mode/0</c></seemfa>
selects the
- <seealso marker="#Callback Modes"><c>CallbackMode</c></seealso>
+ <seeguide marker="#Callback Modes"><c>CallbackMode</c></seeguide>
for the <em>callback module</em>, in this case
- <seealso marker="stdlib:gen_statem#type-callback_mode"><c>state_functions</c></seealso>.
+ <seetype marker="stdlib:gen_statem#callback_mode"><c>state_functions</c></seetype>.
That is, each state has got its own handler function:
</p>
<code type="erl"><![CDATA[
@@ -1256,7 +1253,7 @@ callback_mode() ->
<title>Handling Events</title>
<p>The function notifying the code lock about a button event is
implemented using
- <seealso marker="stdlib:gen_statem#cast/2"><c>gen_statem:cast/2</c></seealso>:
+ <seemfa marker="stdlib:gen_statem#cast/2"><c>gen_statem:cast/2</c></seemfa>:
</p>
<code type="erl"><![CDATA[
button(Button) ->
@@ -1359,7 +1356,7 @@ open(state_timeout, lock, Data) ->
<p>
You can restart, cancel or update a state time-out.
See section
- <seealso marker="#Time-Outs">Time-Outs</seealso>
+ <seeguide marker="#Time-Outs">Time-Outs</seeguide>
for details.
</p>
</section>
@@ -1433,7 +1430,7 @@ open(...) -> ... ;
<p>
This example uses
- <seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call/2</c></seealso>,
+ <seemfa marker="stdlib:gen_statem#call/2"><c>gen_statem:call/2</c></seemfa>,
which waits for a reply from the server.
The reply is sent with a <c>{reply,From,Reply}</c> tuple
in an action list in the <c>{keep_state, ...}</c> tuple
@@ -1458,12 +1455,12 @@ open(...) -> ... ;
<title>One State Callback</title>
<p>
If
- <seealso marker="#Callback Modes">
+ <seeguide marker="#Callback Modes">
<em>callback mode</em>
- </seealso>
+ </seeguide>
<c>handle_event_function</c> is used,
all events are handled in
- <seealso marker="stdlib:gen_statem#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>
+ <seemfa marker="stdlib:gen_statem#Module:handle_event/4"><c>Module:handle_event/4</c></seemfa>
and we can (but do not have to) use an event-centered approach
where we first branch depending on event
and then depending on state:
@@ -1524,7 +1521,7 @@ handle_event(
no stop function is needed.
The <c>gen_statem</c> is automatically terminated by its supervisor.
Exactly how this is done is defined by a
- <seealso marker="sup_princ#shutdown">shutdown strategy</seealso>
+ <seeguide marker="sup_princ#shutdown">shutdown strategy</seeguide>
set in the supervisor.
</p>
<p>
@@ -1532,7 +1529,7 @@ handle_event(
strategy must be a time-out value and the <c>gen_statem</c> must
in function <c>init/1</c> set itself to trap exit signals
by calling
- <seealso marker="erts:erlang#process_flag/2"><c>process_flag(trap_exit, true)</c></seealso>:
+ <seemfa marker="erts:erlang#process_flag/2"><c>process_flag(trap_exit, true)</c></seemfa>:
</p>
<code type="erl"><![CDATA[
init(Args) ->
@@ -1562,7 +1559,7 @@ terminate(_Reason, State, _Data) ->
<p>
If the <c>gen_statem</c> is not part of a supervision tree,
it can be stopped using
- <seealso marker="stdlib:gen_statem#stop/1"><c>gen_statem:stop</c></seealso>,
+ <seemfa marker="stdlib:gen_statem#stop/1"><c>gen_statem:stop</c></seemfa>,
preferably through an API function:
</p>
<code type="erl"><![CDATA[
@@ -1588,16 +1585,16 @@ stop() ->
<title>Event Time-Outs</title>
<p>
A time-out feature inherited from <c>gen_statem</c>'s predecessor
- <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso>,
+ <seeerl marker="stdlib:gen_fsm"><c>gen_fsm</c></seeerl>,
is an event time-out, that is,
if an event arrives the timer is cancelled.
You get either an event or a time-out, but not both.
</p>
<p>
It is ordered by the
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>transition action</em>
- </seealso>
+ </seeguide>
<c>{timeout,Time,EventContent}</c>, or just an integer <c>Time</c>,
even without the enclosing actions list
(the latter is a form inherited from <c>gen_fsm</c>.
@@ -1637,7 +1634,7 @@ locked(
<p>
Note that an event time-out does not work well
when you have for example a status call as in section
- <seealso marker="#All State Events">All State Events</seealso>,
+ <seeguide marker="#All State Events">All State Events</seeguide>,
or handle unknown events, since all kinds of events
will cancel the event time-out.
</p>
@@ -1659,9 +1656,9 @@ locked(
to the time-out in another, maybe cancel the time-out
without changing states, or perhaps run multiple
time-outs in parallel. All this can be accomplished with
- <seealso marker="stdlib:gen_statem#type-generic_timeout">generic time-outs</seealso>.
+ <seetype marker="stdlib:gen_statem#generic_timeout">generic time-outs</seetype>.
They may look a little bit like
- <seealso marker="stdlib:gen_statem#type-event_timeout">event time-outs</seealso>
+ <seetype marker="stdlib:gen_statem#event_timeout">event time-outs</seetype>
but contain a name to allow for any number of them simultaneously
and they are not automatically cancelled.
</p>
@@ -1692,7 +1689,7 @@ open(cast, {button,_}, Data) ->
]]></code>
<p>
Specific generic time-outs can just as
- <seealso marker="#State Time-Outs">state time-outs</seealso>
+ <seeguide marker="#State Time-Outs">state time-outs</seeguide>
be restarted or cancelled
by setting it to a new time or <c>infinity</c>.
</p>
@@ -1709,7 +1706,7 @@ open(cast, {button,_}, Data) ->
<p>
You can restart, cancel or update a generic time-out.
See section
- <seealso marker="#Time-Outs">Time-Outs</seealso>
+ <seeguide marker="#Time-Outs">Time-Outs</seeguide>
for details.
</p>
</section>
@@ -1722,12 +1719,12 @@ open(cast, {button,_}, Data) ->
<p>
The most versatile way to handle time-outs is to use
Erlang Timers; see
- <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/3,4</c></seealso>.
+ <seemfa marker="erts:erlang#start_timer/4"><c>erlang:start_timer/3,4</c></seemfa>.
Most time-out tasks can be performed with the
time-out features in <c>gen_statem</c>,
but an example of one that cannot is if you should need
the return value from
- <seealso marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seealso>, that is; the remaining time of the timer.
+ <seemfa marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seemfa>, that is; the remaining time of the timer.
</p>
<p>
Here is how to accomplish the state time-out
@@ -1764,15 +1761,15 @@ open(cast, {button,_}, Data) ->
</p>
<p>
If you need to cancel a timer because of some other event, you can use
- <seealso marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seealso>.
+ <seemfa marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seemfa>.
Note that no time-out message will arrive after this (because the timer has been explicitly canceled),
unless you have already postponed one earlier (see the next section),
so ensure that you do not accidentally postpone such messages.
- Also note that a time-out message may arrive during a **state callback**
+ Also note that a time-out message may arrive during a <em>state callback</em>
that is cancelling the timer, so you may have to read out
such a message from the process mailbox, depending on
- the return value from
- <seealso marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seealso>.
+ the return value from
+ <seemfa marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seemfa>.
</p>
<p>
Another way to handle a late time-out can be to not cancel it,
@@ -1794,9 +1791,9 @@ open(cast, {button,_}, Data) ->
</p>
<p>
Postponing is ordered by the
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>transition action</em>
- </seealso>
+ </seeguide>
<c>postpone</c>.
</p>
<p>
@@ -1819,10 +1816,10 @@ open(cast, {button,_}, Data) ->
for example by having two more or less identical states
to keep a boolean value, or by using a complex state
(see section
- <seealso marker="#Complex State">Complex State</seealso>)
+ <seeguide marker="#Complex State">Complex State</seeguide>)
with
- <seealso marker="#Callback Modes"><em>callback mode</em></seealso>
- <seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso>.
+ <seeguide marker="#Callback Modes"><em>callback mode</em></seeguide>
+ <seetype marker="stdlib:gen_statem#callback_mode"><c>handle_event_function</c></seetype>.
If a change in the value changes the set of events that is handled,
then the value should be kept in the State.
Otherwise no postponed events will be retried
@@ -1832,7 +1829,7 @@ open(cast, {button,_}, Data) ->
This is not important if you do not postpone events.
But if you later decide to start postponing some events,
then the design flaw of not having separate states
- when they should be, might become a hard to find bug.
+ when they should be, might become a hard-to-find bug.
</p>
<section>
@@ -1919,16 +1916,16 @@ do_unlock() ->
behaviour (or from any <c>gen_*</c> behaviour),
as the receive statement is within the <c>gen_*</c> engine itself.
It must be there because all
- <seealso marker="stdlib:sys"><c>sys</c></seealso>
+ <seeerl marker="stdlib:sys"><c>sys</c></seeerl>
compatible behaviours must respond to system messages and therefore
do that in their engine receive loop,
passing non-system messages to the <em>callback module</em>.
</p>
<p>
The
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>transition action</em>
- </seealso>
+ </seeguide>
<c>postpone</c> is designed to model
selective receives. A selective receive implicitly postpones
any not received events, but the <c>postpone</c>
@@ -1954,13 +1951,13 @@ do_unlock() ->
(described in the next section), especially if just
one or a few states has got state enter actions,
this is a perfect use case for the built in
- <seealso marker="#State Enter Calls"><em>state enter calls</em></seealso>.
+ <seeguide marker="#State Enter Calls"><em>state enter calls</em></seeguide>.
</p>
<p>
You return a list containing <c>state_enter</c> from your
- <seealso marker="stdlib:gen_statem#Module:callback_mode/0">
+ <seemfa marker="stdlib:gen_statem#Module:callback_mode/0">
<c>callback_mode/0</c>
- </seealso>
+ </seemfa>
function and the <c>gen_statem</c> engine will call your
<em>state callback</em> once with an event
<c>(enter, OldState, ...)</c>
@@ -2003,9 +2000,9 @@ open(state_timeout, lock, Data) ->
or <c>repeat_state_and_data</c> that otherwise behaves
exactly like their <c>keep_state</c> siblings.
See the type
- <seealso marker="stdlib:gen_statem#type-state_callback_result">
+ <seetype marker="stdlib:gen_statem#state_callback_result">
<c>state_callback_result()</c>
- </seealso>
+ </seetype>
in the reference manual.
</p>
</section>
@@ -2019,14 +2016,14 @@ open(state_timeout, lock, Data) ->
It can sometimes be beneficial to be able to generate events
to your own state machine.
This can be done with the
- <seealso marker="#Transition Actions">
+ <seeguide marker="#Transition Actions">
<em>transition action</em>
- </seealso>
- <seealso marker="stdlib:gen_statem#type-action"><c>{next_event,EventType,EventContent}</c></seealso>.
+ </seeguide>
+ <seetype marker="stdlib:gen_statem#action"><c>{next_event,EventType,EventContent}</c></seetype>.
</p>
<p>
You can generate events of any existing
- <seealso marker="stdlib:gen_statem#type-action">type</seealso>,
+ <seetype marker="stdlib:gen_statem#action">type</seetype>,
but the <c>internal</c> type can only be generated through action
<c>next_event</c>. Hence, it cannot come from an external source,
so you can be certain that an <c>internal</c> event is an event
@@ -2050,9 +2047,9 @@ open(state_timeout, lock, Data) ->
</p>
<p>
A variant of this is to use a
- <seealso marker="#Complex State">complex state</seealso>
+ <seeguide marker="#Complex State">complex state</seeguide>
with
- <seealso marker="#One State Callback"><em>one state callback</em></seealso>.
+ <seeguide marker="#One State Callback"><em>one state callback</em></seeguide>.
The state is then modeled with for example a tuple
<c>{MainFSMState,SubFSMState}</c>.
</p>
@@ -2336,9 +2333,9 @@ handle_event({call,From}, code_length, _State, #{length := Length}) ->
<p>
To avoid this, you can format the internal state
that gets in the error log and gets returned from
- <seealso marker="stdlib:sys#get_status/1"><c>sys:get_status/1,2</c></seealso>
+ <seemfa marker="stdlib:sys#get_status/1"><c>sys:get_status/1,2</c></seemfa>
by implementing function
- <seealso marker="stdlib:gen_statem#Module:format_status/2"><c>Module:format_status/2</c></seealso>,
+ <seemfa marker="stdlib:gen_statem#Module:format_status/2"><c>Module:format_status/2</c></seemfa>,
for example like this:
</p>
<code type="erl"><![CDATA[
@@ -2363,7 +2360,7 @@ format_status(Opt, [_PDict,State,Data]) ->
]]></code>
<p>
It is not mandatory to implement a
- <seealso marker="stdlib:gen_statem#Module:format_status/2"><c>Module:format_status/2</c></seealso>
+ <seemfa marker="stdlib:gen_statem#Module:format_status/2"><c>Module:format_status/2</c></seemfa>
function. If you do not, a default implementation is used that
does the same as this example function without filtering
the <c>Data</c> term, that is, <c>StateData = {State,Data}</c>,
@@ -2378,15 +2375,15 @@ format_status(Opt, [_PDict,State,Data]) ->
<title>Complex State</title>
<p>
The <em>callback mode</em>
- <seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso>
+ <seetype marker="stdlib:gen_statem#callback_mode"><c>handle_event_function</c></seetype>
enables using a non-atom state as described in section
- <seealso marker="#Callback Modes">Callback Modes</seealso>,
+ <seeguide marker="#Callback Modes">Callback Modes</seeguide>,
for example, a complex state term like a tuple.
</p>
<p>
One reason to use this is when you have a state item
that when changed should cancel the
- <seealso marker="#State Time-Outs">state time-out</seealso>,
+ <seeguide marker="#State Time-Outs">state time-out</seeguide>,
or one that affects the event handling
in combination with postponing events.
We will go for the latter and complicate the previous example
@@ -2532,12 +2529,12 @@ terminate(_Reason, State, _Data) ->
and the amount of heap memory all these servers need
is a problem, then the memory footprint of a server
can be mimimized by hibernating it through
- <seealso marker="stdlib:proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>.
+ <seemfa marker="stdlib:proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seemfa>.
</p>
<note>
<p>
It is rather costly to hibernate a process; see
- <seealso marker="erts:erlang#hibernate/3"><c>erlang:hibernate/3</c></seealso>.
+ <seemfa marker="erts:erlang#hibernate/3"><c>erlang:hibernate/3</c></seemfa>.
It is not something you want to do after every event.
</p>
</note>
@@ -2560,7 +2557,7 @@ handle_event(enter, _OldState, {open,_}, _Data) ->
]]></code>
<p>
The atom
- <seealso marker="stdlib:gen_statem#type-hibernate"><c>hibernate</c></seealso>
+ <seetype marker="stdlib:gen_statem#hibernate"><c>hibernate</c></seetype>
in the action list on the last line
when entering the <c>{open,_}</c> state is the only change.
If any event arrives in the <c>{open,_},</c> state, we
@@ -2576,21 +2573,21 @@ handle_event(enter, _OldState, {open,_}, _Data) ->
</p>
<p>
Another not uncommon scenario is to use the
- <seealso marker="#Event Time-Outs">event time-out</seealso>
+ <seeguide marker="#Event Time-Outs">event time-out</seeguide>
to trigger hibernation after a certain time of inactivity.
There is also a server start option
- <seealso marker="stdlib:gen_statem#type-enter_loop_opt">
+ <seetype marker="stdlib:gen_statem#enter_loop_opt">
<c>{hibernate_after, Timeout}</c>
- </seealso>
+ </seetype>
for
- <seealso marker="stdlib:gen_statem#start/3"><c>start/3,4</c></seealso>,
- <seealso marker="stdlib:gen_statem#start_link/3">
+ <seemfa marker="stdlib:gen_statem#start/3"><c>start/3,4</c></seemfa>,
+ <seemfa marker="stdlib:gen_statem#start_link/3">
<c>start_link/3,4</c>
- </seealso>
+ </seemfa>
or
- <seealso marker="stdlib:gen_statem#enter_loop/4">
+ <seemfa marker="stdlib:gen_statem#enter_loop/4">
<c>enter_loop/4,5,6</c>
- </seealso>
+ </seemfa>
that may be used to automatically hibernate the server.
</p>
<p>
diff --git a/system/doc/design_principles/sup_princ.xml b/system/doc/design_principles/sup_princ.xml
index 321fa41e8d..60ec23466f 100644
--- a/system/doc/design_principles/sup_princ.xml
+++ b/system/doc/design_principles/sup_princ.xml
@@ -11,7 +11,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>Supervisor Behaviour</title>
@@ -30,7 +30,7 @@
<file>sup_princ.xml</file>
</header>
<p>This section should be read with the
- <seealso marker="stdlib:supervisor">supervisor(3)</seealso> manual page
+ <seeerl marker="stdlib:supervisor">supervisor(3)</seeerl> manual page
in STDLIB, where all details about the supervisor
behaviour is given.</p>
@@ -41,7 +41,7 @@
that it is to keep its child processes alive by restarting them
when necessary.</p>
<p>Which child processes to start and monitor is specified by a
- list of <seealso marker="#spec">child specifications</seealso>.
+ list of <seeguide marker="#spec">child specifications</seeguide>.
The child processes are started in the order specified by this
list, and terminated in the reversed order.</p>
</section>
@@ -49,7 +49,7 @@
<section>
<title>Example</title>
<p>The callback module for a supervisor starting the server from
- <seealso marker="gen_server_concepts#ex">gen_server Behaviour</seealso>
+ <seeguide marker="gen_server_concepts#ex">gen_server Behaviour</seeguide>
can look as follows:</p>
<marker id="ex"></marker>
<code type="none">
@@ -73,10 +73,10 @@ init(_Args) ->
{ok, {SupFlags, ChildSpecs}}.</code>
<p>The <c>SupFlags</c> variable in the return value
from <c>init/1</c> represents
- the <seealso marker="#flags">supervisor flags</seealso>.</p>
+ the <seeguide marker="#flags">supervisor flags</seeguide>.</p>
<p>The <c>ChildSpecs</c> variable in the return value
- from <c>init/1</c> is a list of <seealso marker="#spec">child
- specifications</seealso>.</p>
+ from <c>init/1</c> is a list of <seeguide marker="#spec">child
+ specifications</seeguide>.</p>
</section>
<section>
@@ -94,13 +94,13 @@ sup_flags() = #{strategy => strategy(), % optional
<list type="bulleted">
<item>
<p><c>strategy</c> specifies
- the <seealso marker="#strategy">restart
- strategy</seealso>.</p>
+ the <seeguide marker="#strategy">restart
+ strategy</seeguide>.</p>
</item>
<item>
<p><c>intensity</c> and <c>period</c> specify
- the <seealso marker="#max_intensity">maximum restart
- intensity</seealso>.</p>
+ the <seeguide marker="#max_intensity">maximum restart
+ intensity</seeguide>.</p>
</item>
</list>
</section>
@@ -146,8 +146,8 @@ SupFlags = #{strategy => Strategy, ...}</code>
<section>
<title>simple_one_for_one</title>
- <p>See <seealso marker="#simple">simple-one-for-one
- supervisors</seealso>.</p>
+ <p>See <seeguide marker="#simple">simple-one-for-one
+ supervisors</seeguide>.</p>
</section>
</section>
@@ -350,7 +350,7 @@ child_spec() = #{id => child_id(), % mandatory
the value shall be <c>dynamic</c>.</p>
<p>This information is used by the release handler during
upgrades and downgrades, see
- <seealso marker="release_handling">Release Handling</seealso>.</p>
+ <seeguide marker="release_handling">Release Handling</seeguide>.</p>
<p>The <c>modules</c> key is optional. If it is not given, it
defaults to <c>[M]</c>, where <c>M</c> comes from the
child's start <c>{M,F,A}</c>.</p>
@@ -372,7 +372,7 @@ child_spec() = #{id => child_id(), % mandatory
shutdown => brutal_kill}</code>
<p>Example: A child specification to start the event manager from
the chapter about
- <seealso marker="events#mgr">gen_event</seealso>:</p>
+ <seeguide marker="events#mgr">gen_event</seeguide>:</p>
<code type="none">
#{id => error_man,
start => {gen_event, start_link, [{local, error_man}]},
@@ -444,7 +444,7 @@ init(_Args) ->
supervisor:start_child(Sup, ChildSpec)</code>
<p><c>Sup</c> is the pid, or name, of the supervisor.
<c>ChildSpec</c> is a
- <seealso marker="#spec">child specification</seealso>.</p>
+ <seeguide marker="#spec">child specification</seeguide>.</p>
<p>Child processes added using <c>start_child/2</c> behave in
the same way as the other child processes, with the an important
exception: if a supervisor dies and is recreated, then
@@ -464,9 +464,9 @@ supervisor:terminate_child(Sup, Id)</code>
supervisor:delete_child(Sup, Id)</code>
<p><c>Sup</c> is the pid, or name, of the supervisor.
<c>Id</c> is the value associated with the <c>id</c> key in
- the <seealso marker="#spec">child specification</seealso>.</p>
+ the <seeguide marker="#spec">child specification</seeguide>.</p>
<p>As with dynamically added child processes, the effects of
- deleting a static child process is lost if the supervisor itself
+ deleting a static child process are lost if the supervisor itself
restarts.</p>
</section>
@@ -534,4 +534,3 @@ supervisor:terminate_child(Sup, Pid)</code>
then terminates itself.</p>
</section>
</chapter>
-
diff --git a/system/doc/efficiency_guide/Makefile b/system/doc/efficiency_guide/Makefile
index dccce05f70..50ed66019a 100644
--- a/system/doc/efficiency_guide/Makefile
+++ b/system/doc/efficiency_guide/Makefile
@@ -88,7 +88,9 @@ DVIPS_FLAGS +=
# Targets
# ----------------------------------------------------
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -107,15 +109,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
-# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
-# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
- $(INSTALL_DIR) $(RELSYSDIR)
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
release_spec:
-
-
-
diff --git a/system/doc/efficiency_guide/advanced.xml b/system/doc/efficiency_guide/advanced.xml
index fe77ce8ea4..acf909b0fc 100644
--- a/system/doc/efficiency_guide/advanced.xml
+++ b/system/doc/efficiency_guide/advanced.xml
@@ -153,9 +153,9 @@
<cell>The maximum number of simultaneously alive Erlang processes
is by default 262,144. This limit can be configured at startup.
For more information, see the
- <seealso marker="erts:erl#max_processes"><c>+P</c></seealso>
+ <seecom marker="erts:erl#max_processes"><c>+P</c></seecom>
command-line flag in the
- <seealso marker="erts:erl"><c>erl(1)</c></seealso>
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom>
manual page in ERTS.</cell>
</row>
<row>
@@ -164,7 +164,7 @@
any pids, ports, references, or funs (Erlang data types) from Y
on X, or if X and Y are connected. The maximum number of remote
nodes simultaneously/ever known to a node is limited by the
- <seealso marker="#atoms">maximum number of atoms</seealso>
+ <seeguide marker="#atoms">maximum number of atoms</seeguide>
available for node names. All data concerning remote nodes,
except for the node name atom, are garbage-collected.</cell>
</row>
@@ -173,9 +173,9 @@
<cell>The maximum number of simultaneously connected nodes is
limited by either the maximum number of simultaneously known
remote nodes,
- <seealso marker="#ports">the maximum number of (Erlang) ports</seealso>
+ <seeguide marker="#ports">the maximum number of (Erlang) ports</seeguide>
available, or
- <seealso marker="#files_sockets">the maximum number of sockets</seealso>
+ <seeguide marker="#files_sockets">the maximum number of sockets</seeguide>
available.</cell>
</row>
<row>
@@ -226,9 +226,9 @@
<cell>The maximum number of simultaneously open Erlang ports is
often by default 16,384. This limit can be configured at startup.
For more information, see the
- <seealso marker="erts:erl#max_ports"><c>+Q</c></seealso>
+ <seecom marker="erts:erl#max_ports"><c>+Q</c></seecom>
command-line flag in the
- <seealso marker="erts:erl"><c>erl(1)</c></seealso> manual page
+ <seecom marker="erts:erl"><c>erl(1)</c></seecom> manual page
in ERTS.</cell>
</row>
<row>
@@ -236,7 +236,7 @@
sockets</cell>
<cell>The maximum number of simultaneously open files and
sockets depends on
- <seealso marker="#ports">the maximum number of Erlang ports</seealso>
+ <seeguide marker="#ports">the maximum number of Erlang ports</seeguide>
available, as well as on operating system-specific settings
and limits.</cell>
</row>
@@ -260,7 +260,7 @@
<cell><marker id="unique_integers"/>Unique Integers on a Runtime System Instance</cell>
<cell>
There are two types of unique integers both created using the
- <seealso marker="erts:erlang#unique_integer/1">erlang:unique_integer()</seealso>
+ <seemfa marker="erts:erlang#unique_integer/1">erlang:unique_integer()</seemfa>
BIF:
<br/><br/>
<em>1.</em> Unique integers created <em>with</em> the
diff --git a/system/doc/efficiency_guide/binaryhandling.xml b/system/doc/efficiency_guide/binaryhandling.xml
index d92da17390..4d124095e8 100644
--- a/system/doc/efficiency_guide/binaryhandling.xml
+++ b/system/doc/efficiency_guide/binaryhandling.xml
@@ -165,12 +165,12 @@ Bin4 = <<Bin1/binary,17>>, %% 5 !!!
<list type="bulleted">
<item>Line 1 (marked with the <c>%% 1</c> comment), assigns
- a <seealso marker="#heap_binary">heap binary</seealso> to
+ a <seeguide marker="#heap_binary">heap binary</seeguide> to
the <c>Bin0</c> variable.</item>
<item>Line 2 is an append operation. As <c>Bin0</c>
has not been involved in an append operation,
- a new <seealso marker="#refc_binary">refc binary</seealso>
+ a new <seeguide marker="#refc_binary">refc binary</seeguide>
is created and the contents of <c>Bin0</c> is copied
into it. The <em>ProcBin</em> part of the refc binary has
its size set to the size of the data stored in the binary, while
@@ -248,7 +248,7 @@ Bin = <<Bin1,...>> %% Bin1 will be COPIED
<p>The same happens if you insert a binary into an Ets
table, send it to a port using <c>erlang:port_command/2</c>, or
pass it to
- <seealso marker="erts:erl_nif#enif_inspect_binary">enif_inspect_binary</seealso>
+ <seecref marker="erts:erl_nif#enif_inspect_binary">enif_inspect_binary</seecref>
in a NIF.</p>
<p>Matching a binary will also cause it to shrink and the next append
@@ -261,7 +261,7 @@ Bin = <<Bin1,...>> %% Bin1 will be COPIED
]]></code>
<p>The reason is that a
- <seealso marker="#match_context">match context</seealso>
+ <seeguide marker="#match_context">match context</seeguide>
contains a direct pointer to the binary data.</p>
<p>If a process simply keeps binaries (either in "loop data" or in the
@@ -285,13 +285,13 @@ my_binary_to_list(<<H,T/binary>>) ->
my_binary_to_list(<<>>) -> [].]]></code>
<p>The first time <c>my_binary_to_list/1</c> is called,
- a <seealso marker="#match_context">match context</seealso>
+ a <seeguide marker="#match_context">match context</seeguide>
is created. The match context points to the first
byte of the binary. 1 byte is matched out and the match context
is updated to point to the second byte in the binary.</p>
<p>At this point it would make sense to create a
- <seealso marker="#sub_binary">sub binary</seealso>,
+ <seeguide marker="#sub_binary">sub binary</seeguide>,
but in this particular example the compiler sees that
there will soon be a call to a function (in this case,
to <c>my_binary_to_list/1</c> itself) that immediately will
diff --git a/system/doc/efficiency_guide/commoncaveats.xml b/system/doc/efficiency_guide/commoncaveats.xml
index dd09e78e3f..35dd23aa9b 100644
--- a/system/doc/efficiency_guide/commoncaveats.xml
+++ b/system/doc/efficiency_guide/commoncaveats.xml
@@ -35,12 +35,12 @@
<section>
<title>Timer Module</title>
- <p>Creating timers using <seealso
- marker="erts:erlang#send_after/3">erlang:send_after/3</seealso>
+ <p>Creating timers using <seemfa
+ marker="erts:erlang#send_after/3">erlang:send_after/3</seemfa>
and
- <seealso marker="erts:erlang#start_timer/3">erlang:start_timer/3</seealso>,
+ <seemfa marker="erts:erlang#start_timer/3">erlang:start_timer/3</seemfa>,
is much more efficient than using the timers provided by the
- <seealso marker="stdlib:timer">timer</seealso> module in STDLIB.
+ <seeerl marker="stdlib:timer">timer</seeerl> module in STDLIB.
The <c>timer</c> module uses a separate process to manage the timers.
That process can easily become overloaded if many processes
create and cancel timers frequently (especially when using the
@@ -61,7 +61,7 @@
<p>Therefore, converting arbitrary input strings to atoms can be
dangerous in a system that runs continuously.
If only certain well-defined atoms are allowed as input,
- <seealso marker="erts:erlang#list_to_existing_atom/1">list_to_existing_atom/1</seealso>
+ <seemfa marker="erts:erlang#list_to_existing_atom/1">list_to_existing_atom/1</seemfa>
can be used
to guard against a denial-of-service attack. (All atoms that are allowed
must have been created earlier, for example, by simply using all of them
@@ -105,7 +105,7 @@ foo([_,_,_|_]=L) ->
<section>
<title>setelement/3</title>
- <p><seealso marker="erts:erlang#setelement/3">setelement/3</seealso>
+ <p><seemfa marker="erts:erlang#setelement/3">setelement/3</seemfa>
copies the tuple it modifies. Therefore, updating a tuple in a loop
using <c>setelement/3</c> creates a new copy of the tuple every time.</p>
diff --git a/system/doc/efficiency_guide/drivers.xml b/system/doc/efficiency_guide/drivers.xml
index c99701eeba..2b6e22b980 100644
--- a/system/doc/efficiency_guide/drivers.xml
+++ b/system/doc/efficiency_guide/drivers.xml
@@ -74,7 +74,7 @@ client_port() ->
<list type="bulleted">
<item><p>If the <c>Data</c> argument for
- <seealso marker="erts:erlang#port_control/3">port_control/3</seealso>
+ <seemfa marker="erts:erlang#port_control/3">port_control/3</seemfa>
is a binary, the driver will be passed a pointer to the contents of
the binary and the binary will not be copied. If the <c>Data</c>
argument is an iolist (list of binaries and lists), all binaries in
@@ -91,7 +91,7 @@ client_port() ->
<c>output</c> callback) in the driver. If a driver has an
<c>outputv</c> callback, refc binaries passed in an iolist
in the <c>Data</c> argument for
- <seealso marker="erts:erlang#port_command/2">port_command/2</seealso>
+ <seemfa marker="erts:erlang#port_command/2">port_command/2</seemfa>
will be passed as references to the driver.</p></item>
</list>
</section>
@@ -107,9 +107,9 @@ client_port() ->
<p>If you know that the binaries you return are always small, you
are advised to use driver API calls that do not require a pre-allocated
binary, for example,
- <seealso marker="erts:erl_driver#driver_output">driver_output()</seealso>
+ <seecref marker="erts:erl_driver#driver_output">driver_output()</seecref>
or
- <seealso marker="erts:erl_driver#erl_drv_output_term">erl_drv_output_term()</seealso>,
+ <seecref marker="erts:erl_driver#erl_drv_output_term">erl_drv_output_term()</seecref>,
using the <c>ERL_DRV_BUF2BINARY</c> format,
to allow the runtime to construct a heap binary.</p>
@@ -123,7 +123,7 @@ client_port() ->
binary and then send it to an Erlang process in some way.</p>
<p>Use
- <seealso marker="erts:erl_driver#driver_alloc_binary">driver_alloc_binary()</seealso>
+ <seecref marker="erts:erl_driver#driver_alloc_binary">driver_alloc_binary()</seecref>
to allocate a binary.</p>
<p>There are several ways to send a binary created with
@@ -131,16 +131,16 @@ client_port() ->
<list type="bulleted">
<item>From the <c>control</c> callback, a binary can be returned if
- <seealso marker="erts:erl_driver#set_port_control_flags">set_port_control_flags()</seealso>
+ <seecref marker="erts:erl_driver#set_port_control_flags">set_port_control_flags()</seecref>
has been called with the flag value <c>PORT_CONTROL_FLAG_BINARY</c>.</item>
<item>A single binary can be sent with
- <seealso marker="erts:erl_driver#driver_output_binary">driver_output_binary()</seealso>.</item>
+ <seecref marker="erts:erl_driver#driver_output_binary">driver_output_binary()</seecref>.</item>
<item>Using
- <seealso marker="erts:erl_driver#erl_drv_output_term">erl_drv_output_term()</seealso>
+ <seecref marker="erts:erl_driver#erl_drv_output_term">erl_drv_output_term()</seecref>
or
- <seealso marker="erts:erl_driver#erl_drv_send_term">erl_drv_send_term()</seealso>,
+ <seecref marker="erts:erl_driver#erl_drv_send_term">erl_drv_send_term()</seecref>,
a binary can be included in an Erlang term.</item>
</list>
diff --git a/system/doc/efficiency_guide/listhandling.xml b/system/doc/efficiency_guide/listhandling.xml
index 4f2497359d..9c498c9235 100644
--- a/system/doc/efficiency_guide/listhandling.xml
+++ b/system/doc/efficiency_guide/listhandling.xml
@@ -144,7 +144,7 @@ ok.]]></code>
<section>
<title>Deep and Flat Lists</title>
- <p><seealso marker="stdlib:lists#flatten/1">lists:flatten/1</seealso>
+ <p><seemfa marker="stdlib:lists#flatten/1">lists:flatten/1</seemfa>
builds an entirely new list. It is therefore expensive, and even
<em>more</em> expensive than the <c>++</c> operator (which copies its
left argument, but not its right argument).</p>
@@ -157,10 +157,10 @@ ok.]]></code>
so there is no reason to flatten the list before sending it to
the port.</item>
<item>When calling BIFs that accept deep lists, such as
- <seealso marker="erts:erlang#list_to_binary/1">list_to_binary/1</seealso> or
- <seealso marker="erts:erlang#iolist_to_binary/1">iolist_to_binary/1</seealso>.</item>
+ <seemfa marker="erts:erlang#list_to_binary/1">list_to_binary/1</seemfa> or
+ <seemfa marker="erts:erlang#iolist_to_binary/1">iolist_to_binary/1</seemfa>.</item>
<item>When you know that your list is only one level deep, you can use
- <seealso marker="stdlib:lists#append/1">lists:append/1</seealso>.</item>
+ <seemfa marker="stdlib:lists#append/1">lists:append/1</seemfa>.</item>
</list>
<section>
@@ -218,8 +218,8 @@ ok.]]></code>
<title>Recursive List Functions</title>
<p>In section about myths, the following myth was exposed:
- <seealso marker="myths#tail_recursive">Tail-Recursive Functions
- are Much Faster Than Recursive Functions</seealso>.</p>
+ <seeguide marker="myths#tail_recursive">Tail-Recursive Functions
+ are Much Faster Than Recursive Functions</seeguide>.</p>
<p>There is usually not much difference between
a body-recursive list function and tail-recursive function that reverses
diff --git a/system/doc/efficiency_guide/myths.xml b/system/doc/efficiency_guide/myths.xml
index 778cd06c09..0ae12bcfec 100644
--- a/system/doc/efficiency_guide/myths.xml
+++ b/system/doc/efficiency_guide/myths.xml
@@ -121,7 +121,7 @@ vanilla_reverse([], Acc) ->
In Erlang, you need to think a little more about how the strings
are used and choose an appropriate representation. If you
use regular expressions, use the
- <seealso marker="stdlib:re">re</seealso> module in STDLIB
+ <seeerl marker="stdlib:re">re</seeerl> module in STDLIB
instead of the obsolete <c>regexp</c> module.</p>
</section>
@@ -167,13 +167,13 @@ vanilla_reverse([], Acc) ->
but not guaranteed to speed up the program.</p>
<p>Doing too much work in each NIF call will
- <seealso marker="erts:erl_nif#WARNING">degrade responsiveness
- of the VM</seealso>. Doing too little work may mean that
+ <seecref marker="erts:erl_nif#WARNING">degrade responsiveness
+ of the VM</seecref>. Doing too little work may mean that
the gain of the faster processing in the NIF is eaten up by
the overhead of calling the NIF and checking the arguments.</p>
<p>Be sure to read about
- <seealso marker="erts:erl_nif#lengthy_work">Long-running NIFs</seealso>
+ <seecref marker="erts:erl_nif#lengthy_work">Long-running NIFs</seecref>
before writing a NIF.</p>
</section>
</chapter>
diff --git a/system/doc/efficiency_guide/processes.xml b/system/doc/efficiency_guide/processes.xml
index 3b64c863ff..db05e6a3a8 100644
--- a/system/doc/efficiency_guide/processes.xml
+++ b/system/doc/efficiency_guide/processes.xml
@@ -101,9 +101,9 @@ loop() ->
<p>In a system that use comparatively few processes, performance
<em>might</em> be improved by increasing the minimum heap size
using either the <c>+h</c> option for
- <seealso marker="erts:erl">erl</seealso> or on a process-per-process
+ <seecom marker="erts:erl">erl</seecom> or on a process-per-process
basis using the <c>min_heap_size</c> option for
- <seealso marker="erts:erlang#spawn_opt/4">spawn_opt/4</seealso>.</p>
+ <seemfa marker="erts:erlang#spawn_opt/4">spawn_opt/4</seemfa>.</p>
<p>The gain is twofold:</p>
<list type="bulleted">
@@ -135,7 +135,7 @@ loop() ->
<p>All data in messages between Erlang processes is copied,
except for
- <seealso marker="binaryhandling#refc_binary">refc binaries</seealso>
+ <seeguide marker="binaryhandling#refc_binary">refc binaries</seeguide>
on the same Erlang node.</p>
<p>When a message is sent to a process on another Erlang node,
diff --git a/system/doc/efficiency_guide/profiling.xml b/system/doc/efficiency_guide/profiling.xml
index 5ec1f1be6e..594d066df4 100644
--- a/system/doc/efficiency_guide/profiling.xml
+++ b/system/doc/efficiency_guide/profiling.xml
@@ -41,11 +41,11 @@
<p>Erlang/OTP contains several tools to help finding bottlenecks:</p>
<list type="bulleted">
- <item><p><seealso marker="tools:fprof"><c>fprof</c></seealso> provides
+ <item><p><seeerl marker="tools:fprof"><c>fprof</c></seeerl> provides
the most detailed information about where the program time is spent,
but it significantly slows down the program it profiles.</p></item>
- <item><p><seealso marker="tools:eprof"><c>eprof</c></seealso> provides
+ <item><p><seeerl marker="tools:eprof"><c>eprof</c></seeerl> provides
time information of each function used in the program. No call graph is
produced, but <c>eprof</c> has considerable less impact on the program it
profiles.</p>
@@ -53,16 +53,16 @@
<c>eprof</c>, <c>cprof</c> can be used to locate code parts that
are to be more thoroughly profiled using <c>fprof</c> or <c>eprof</c>.</p></item>
- <item><p><seealso marker="tools:cprof"><c>cprof</c></seealso> is the
+ <item><p><seeerl marker="tools:cprof"><c>cprof</c></seeerl> is the
most lightweight tool, but it only provides execution counts on a
function basis (for all processes, not per process).</p></item>
- <item><p><seealso marker="runtime_tools:dbg"><c>dbg</c></seealso> is the
+ <item><p><seeerl marker="runtime_tools:dbg"><c>dbg</c></seeerl> is the
generic erlang tracing frontend. By using the <c>timestamp</c> or
<c>cpu_timestamp</c> options it can be used to time how long function
calls in a live system take.</p></item>
- <item><p><seealso marker="tools:lcnt"><c>lcnt</c></seealso> is used
+ <item><p><seeerl marker="tools:lcnt"><c>lcnt</c></seeerl> is used
to find contention points in the Erlang Run-Time System's internal
locking mechanisms. It is useful when looking for bottlenecks in
interaction between process, port, ets tables and other entities
@@ -71,7 +71,7 @@
</list>
<p>The tools are further described in
- <seealso marker="#profiling_tools">Tools</seealso>.</p>
+ <seeguide marker="#profiling_tools">Tools</seeguide>.</p>
<p>There are also several open source tools outside of Erlang/OTP
that can be used to help profiling. Some of them are:</p>
@@ -95,29 +95,29 @@
For unknown reasons the Erlang Run-Time System failed to allocate memory to
use. When this happens a crash dump is generated that contains information
about the state of the system as it ran out of memory. Use the
- <seealso marker="observer:cdv"><c>crashdump_viewer</c></seealso> to get a
+ <seecom marker="observer:cdv"><c>crashdump_viewer</c></seecom> to get a
view of the memory is being used. Look for processes with large heaps or
many messages, large ets tables, etc.</p>
<p>When looking at memory usage in a running system the most basic function
- to get information from is <seealso marker="erts:erlang#memory/0"><c>
- erlang:memory()</c></seealso>. It returns the current memory usage
- of the system. <seealso marker="tools:instrument"><c>instrument(3)</c></seealso>
+ to get information from is <seemfa marker="erts:erlang#memory/0"><c>
+ erlang:memory()</c></seemfa>. It returns the current memory usage
+ of the system. <seeerl marker="tools:instrument"><c>instrument(3)</c></seeerl>
can be used to get a more detailed breakdown of where memory is used.</p>
<p>Processes, ports and ets tables can then be inspecting using their
respective info functions, i.e.
- <seealso marker="erts:erlang#process_info_memory"><c>erlang:process_info/2
- </c></seealso>,
- <seealso marker="erts:erlang#port_info_memory"><c>erlang:port_info/2
- </c></seealso> and
- <seealso marker="stdlib:ets#info/1"><c>ets:info/1</c></seealso>.
+ <seeerl marker="erts:erlang#process_info_memory"><c>erlang:process_info/2
+ </c></seeerl>,
+ <seeerl marker="erts:erlang#port_info_memory"><c>erlang:port_info/2
+ </c></seeerl> and
+ <seemfa marker="stdlib:ets#info/1"><c>ets:info/1</c></seemfa>.
</p>
<p>Sometimes the system can enter a state where the reported memory
from <c>erlang:memory(total)</c> is very different from the
memory reported by the OS. This can be because of internal
fragmentation within the Erlang Run-Time System. Data about
how memory is allocated can be retrieved using
- <seealso marker="erts:erlang#system_info_allocator">
- <c>erlang:system_info(allocator)</c></seealso>.
+ <seeerl marker="erts:erlang#system_info_allocator">
+ <c>erlang:system_info(allocator)</c></seeerl>.
The data you get from that function is very raw and not very plesant to read.
<url href="http://ferd.github.io/recon/recon_alloc.html">recon_alloc</url>
can be used to extract useful information from system_info
@@ -141,13 +141,13 @@
<p>There are also some tools that can be used to get a view of the
whole system with more or less overhead.</p>
<list type="bulleted">
- <item><seealso marker="observer:observer"><c>observer</c></seealso>
+ <item><seeerl marker="observer:observer"><c>observer</c></seeerl>
is a GUI tool that can connect to remote nodes and display a
variety of information about the running system.</item>
- <item><seealso marker="observer:etop"><c>etop</c></seealso>
+ <item><seeerl marker="observer:etop"><c>etop</c></seeerl>
is a command line tool that can connect to remote nodes and
display information similar to what the UNIX tool top shows.</item>
- <item><seealso marker="runtime_tools:msacc"><c>msacc</c></seealso>
+ <item><seeerl marker="runtime_tools:msacc"><c>msacc</c></seeerl>
allows the user to get a view of what the Erlang Run-Time system
is spending its time doing. Has a very low overhead, which makes it
useful to run in heavily loaded systems to get some idea of where
@@ -183,7 +183,7 @@
<p>These questions are not always trivial to answer. Some
benchmarks might be needed to back up your theory and to avoid
making things slower if your theory is wrong. For details, see
- <seealso marker="#benchmark">Benchmarking</seealso>.</p>
+ <seeguide marker="#benchmark">Benchmarking</seeguide>.</p>
</section>
<section>
@@ -200,7 +200,7 @@
<p><c>fprof</c> is based on trace to file to minimize runtime
performance impact. Using <c>fprof</c> is just a matter of
calling a few library functions, see the
- <seealso marker="tools:fprof">fprof</seealso> manual page in
+ <seeerl marker="tools:fprof">fprof</seeerl> manual page in
Tools.</p>
</section>
@@ -210,7 +210,7 @@
<c>eprof</c> shows how much time has been used by each process,
and in which function calls this time has been spent. Time is
shown as percentage of total time and absolute time. For more
- information, see the <seealso marker="tools:eprof">eprof</seealso>
+ information, see the <seeerl marker="tools:eprof">eprof</seeerl>
manual page in Tools.</p>
</section>
@@ -223,7 +223,7 @@
(compared with <c>fprof</c>) and does not need to recompile
any modules to profile (compared with <c>cover</c>).
For more information, see the
- <seealso marker="tools:cprof">cprof</seealso> manual page in
+ <seeerl marker="tools:cprof">cprof</seeerl> manual page in
Tools.</p>
</section>
@@ -284,7 +284,7 @@
system as it is possible to limit the scope of what is profiled
to be very small.
For more information, see the
- <seealso marker="runtime_tools:dbg">dbg</seealso> manual page in
+ <seeerl marker="runtime_tools:dbg">dbg</seeerl> manual page in
Runtime Tools.</p>
</section>
@@ -301,7 +301,7 @@
show more contention points (and thus be more useful) on systems
using many schedulers on many cores.</p>
<p>For more information, see the
- <seealso marker="tools:lcnt">lcnt</seealso> manual page in Tools.</p>
+ <seeerl marker="tools:lcnt">lcnt</seeerl> manual page in Tools.</p>
</section>
</section>
@@ -322,7 +322,7 @@
<p>Benchmarks can measure wall-clock time or CPU time.</p>
<list type="bulleted">
- <item><seealso marker="stdlib:timer#tc/3">timer:tc/3</seealso> measures
+ <item><seemfa marker="stdlib:timer#tc/3">timer:tc/3</seemfa> measures
wall-clock time. The advantage with wall-clock time is that I/O,
swapping, and other activities in the operating system kernel are
included in the measurements. The disadvantage is that the
@@ -331,7 +331,7 @@
be the minimum time that is possible to achieve under the best of
circumstances.</item>
- <item><seealso marker="erts:erlang#statistics/1">statistics/1</seealso>
+ <item><seemfa marker="erts:erlang#statistics/1">statistics/1</seemfa>
with argument <c>runtime</c> measures CPU time spent in the Erlang
virtual machine. The advantage with CPU time is that the results are more
consistent from run to run. The disadvantage is that the time
diff --git a/system/doc/embedded/Makefile b/system/doc/embedded/Makefile
index 0a2a83b2e4..29f3f74564 100644
--- a/system/doc/embedded/Makefile
+++ b/system/doc/embedded/Makefile
@@ -76,7 +76,9 @@ DVIPS_FLAGS +=
# Targets
# ----------------------------------------------------
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -95,13 +97,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
-# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
-# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
-release_spec:
-
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+release_spec:
diff --git a/system/doc/general_info/DEPRECATIONS b/system/doc/general_info/DEPRECATIONS
new file mode 100644
index 0000000000..358bc319af
--- /dev/null
+++ b/system/doc/general_info/DEPRECATIONS
@@ -0,0 +1,214 @@
+#
+# This file contains additional information about each
+# deprecated function, namely in what release it was
+# deprecated and whether it is scheduled for removal.
+#
+# Examples:
+#
+# foo:bar/1 since=23
+#
+# The function foo:bar/1 was deprecated in OTP 23.
+# Nothing has been decided yet about its eventual
+# removal.
+#
+# foo:bar/3 since=23 remove=25
+#
+# The function foo:bar/3 was deprecated in OTP 23 and
+# is scheduled to be removed in OTP 25.
+#
+
+#
+# Added in OTP 23.2.
+#
+igor:_/_ since=23 remove=24
+erl_tidy:_/_ since=23 remove=24
+
+#
+# Added in OTP 23.
+#
+ssl:cipher_suites/1 since=21 remove=24
+ssl:cipher_suites/0 since=21 remove=24
+
+http_uri:parse/1 since=23 remove=25
+http_uri:parse/2 since=23 remove=25
+http_uri:encode/1 since=23 remove=25
+http_uri:decode/1 since=23 remove=25
+http_uri:scheme_defaults/0 since=23 remove=25
+httpd:parse_query/1 since=23
+pg2:_/_ since=23 remove=24
+filename:safe_relative_path/1 since=23 remove=25
+
+snmpm:sync_get/3 since=23 remove=25
+snmpm:sync_get/4 since=23 remove=25
+snmpm:sync_get/5 since=23 remove=25
+snmpm:sync_get/6 since=23 remove=25
+snmpm:async_get/3 since=23 remove=25
+snmpm:async_get/4 since=23 remove=25
+snmpm:async_get/5 since=23 remove=25
+snmpm:async_get/6 since=23 remove=25
+snmpm:sync_get_next/3 since=23 remove=25
+snmpm:sync_get_next/4 since=23 remove=25
+snmpm:sync_get_next/5 since=23 remove=25
+snmpm:sync_get_next/6 since=23 remove=25
+snmpm:async_get_next/3 since=23 remove=25
+snmpm:async_get_next/4 since=23 remove=25
+snmpm:async_get_next/5 since=23 remove=25
+snmpm:async_get_next/6 since=23 remove=25
+snmpm:sync_set/3 since=23 remove=25
+snmpm:sync_set/4 since=23 remove=25
+snmpm:sync_set/5 since=23 remove=25
+snmpm:sync_set/6 since=23 remove=25
+snmpm:async_set/3 since=23 remove=25
+snmpm:async_set/4 since=23 remove=25
+snmpm:async_set/5 since=23 remove=25
+snmpm:async_set/6 since=23 remove=25
+snmpm:sync_get_bulk/5 since=23 remove=25
+snmpm:sync_get_bulk/6 since=23 remove=25
+snmpm:sync_get_bulk/7 since=23 remove=25
+snmpm:sync_get_bulk/8 since=23 remove=25
+snmpm:async_get_bulk/5 since=23 remove=25
+snmpm:async_get_bulk/6 since=23 remove=25
+snmpm:async_get_bulk/7 since=23 remove=25
+snmpm:async_get_bulk/8 since=23 remove=25
+
+#
+# Added in OTP 22.
+#
+
+crypto:block_decrypt/3 since=22 remove=24
+crypto:block_decrypt/4 since=22 remove=24
+crypto:block_encrypt/3 since=22 remove=24
+crypto:block_encrypt/4 since=22 remove=24
+crypto:cmac/3 since=22 remove=24
+crypto:cmac/4 since=22 remove=24
+crypto:hmac/3 since=22 remove=24
+crypto:hmac/4 since=22 remove=24
+crypto:hmac_final/1 since=22 remove=24
+crypto:hmac_final_n/2 since=22 remove=24
+crypto:hmac_init/2 since=22 remove=24
+crypto:hmac_update/2 since=22 remove=24
+crypto:next_iv/_ since=22 remove=24
+crypto:poly1305/2 since=22 remove=24
+crypto:stream_decrypt/2 since=22 remove=24
+crypto:stream_encrypt/2 since=22 remove=24
+crypto:stream_init/_ since=22 remove=24
+
+net:call/4 since=22
+net:cast/4 since=22
+net:broadcast/3 since=22
+net:ping/1 since=22
+net:sleep/1 since=22
+net:relay/1 since=22
+
+sys:get_debug/3 since=22
+
+#
+# Added in OTP 21.
+#
+
+ssl:ssl_accept/_ since=21 remove=24
+
+#
+# Added in OTP 20.
+#
+
+crypto:rand_uniform/2 since=20
+erlang:get_stacktrace/0 since=20 remove=24
+filename:find_src/_ since=20 remove=24
+gen_fsm:_/_ since=20
+
+#
+# Added in OTP 19.
+#
+
+code:rehash/0 since=19
+crypto:rand_bytes/1 since=19
+queue:lait/1 since=19
+random:_/_ since=19
+
+#
+# Added in OTP 18.
+#
+
+erlang:now/0 since=18
+
+#
+# Added in OTP 16.
+#
+
+snmpa:old_info_format/1 since=16 remove=24
+megaco:format_versions/1 since=16 remove=24
+
+wxCalendarCtrl:enableYearChange/1 since=16
+wxCalendarCtrl:enableYearChange/2 since=16
+wxDC:computeScaleAndOrigin/1 since=16
+wxClientDC:new/0 since=16
+wxPaintDC:new/0 since=16
+wxWindowDC:new/0 since=16
+wxGraphicsRenderer:createLinearGradientBrush/7 since=16
+wxGraphicsRenderer:createRadialGradientBrush/8 since=16
+wxGridCellEditor:endEdit/4 since=16
+wxGridCellEditor:paintBackground/3 since=16
+wxIdleEvent:canSend/1 since=16
+wxMDIClientWindow:new/1 since=16
+wxMDIClientWindow:new/2 since=16
+wxPostScriptDC:getResolution/0 since=16
+wxPostScriptDC:setResolution/1 since=16
+wxCursor:new/3 since=16
+wxCursor:new/4 since=16
+
+#
+# Added in OTP 12.
+#
+
+auth:is_auth/1 since=12
+auth:cookie/0 since=12
+auth:cookie/1 since=12
+auth:node_cookie/_ since=12
+
+calendar:local_time_to_universal_time/1 since=12
+
+#
+# Added in OTP 10.
+#
+
+snmp:c/1 since=10 remove=24
+snmp:c/2 since=10 remove=24
+snmp:compile/3 since=10 remove=24
+snmp:is_consistent/1 since=10 remove=24
+snmp:mib_to_hrl/1 since=10 remove=24
+snmp:change_log_size/1 since=10 remove=24
+snmp:log_to_txt/2 since=10 remove=24
+snmp:log_to_txt/3 since=10 remove=24
+snmp:log_to_txt/4 since=10 remove=24
+snmp:current_request_id/0 since=10 remove=24
+snmp:current_community/0 since=10 remove=24
+snmp:current_address/0 since=10 remove=24
+snmp:current_context/0 since=10 remove=24
+snmp:current_net_if_data/0 since=10 remove=24
+snmp:get_symbolic_store_db/0 since=10 remove=24
+snmp:name_to_oid/1 since=10 remove=24
+snmp:name_to_oid/2 since=10 remove=24
+snmp:oid_to_name/1 since=10 remove=24
+snmp:oid_to_name/2 since=10 remove=24
+snmp:int_to_enum/2 since=10 remove=24
+snmp:int_to_enum/3 since=10 remove=24
+snmp:enum_to_int/2 since=10 remove=24
+snmp:enum_to_int/3 since=10 remove=24
+snmp:get/2 since=10 remove=24
+snmp:info/1 since=10 remove=24
+snmp:load_mibs/2 since=10 remove=24
+snmp:unload_mibs/2 since=10 remove=24
+snmp:dump_mibs/0 since=10 remove=24
+snmp:dump_mibs/1 since=10 remove=24
+snmp:register_subagent/3 since=10 remove=24
+snmp:unregister_subagent/2 since=10 remove=24
+snmp:send_notification/3 since=10 remove=24
+snmp:send_notification/4 since=10 remove=24
+snmp:send_notification/5 since=10 remove=24
+snmp:send_notification/6 since=10 remove=24
+snmp:send_trap/3 since=10 remove=24
+snmp:send_trap/4 since=10 remove=24
+snmp:add_agent_caps/2 since=10 remove=24
+snmp:del_agent_caps/1 since=10 remove=24
+snmp:get_agent_caps/0 since=10 remove=24
diff --git a/system/doc/general_info/Makefile b/system/doc/general_info/Makefile
index 22406eb3b7..5f087e22be 100644
--- a/system/doc/general_info/Makefile
+++ b/system/doc/general_info/Makefile
@@ -43,6 +43,8 @@ include xmlfiles.mk
XML_CHAPTER_FILES=$(GENERAL_INFO_CHAPTER_FILES)
+# ----------------------------------------------------
+
TOPDOCDIR=..
BOOK_FILES = book.xml
@@ -51,7 +53,9 @@ GIF_FILES =
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
- $(XML_PART_FILES)
+ $(XML_PART_FILES)
+
+XML_GEN_FILES=$(GENERAL_INFO_CHAPTER_GEN_FILES:%=$(XMLDIR)/%)
# ----------------------------------------------------
@@ -69,7 +73,9 @@ DVIPS_FLAGS +=
# Targets
# ----------------------------------------------------
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -77,6 +83,15 @@ html: $(GIF_FILES) $(HTML_UG_FILE)
debug opt:
+DEPRECATIONS_SCRIPT = $(ERL_TOP)/lib/stdlib/scripts/update_deprecations
+
+$(XMLDIR)/deprecations.xml: DEPRECATIONS
+ $(gen_verbose)escript $(DEPRECATIONS_SCRIPT) make_xml deprecations $(ERL_TOP) $@
+
+$(XMLDIR)/scheduled_for_removal.xml: DEPRECATIONS
+ $(gen_verbose)escript $(DEPRECATIONS_SCRIPT) make_xml removals $(ERL_TOP) $@
+
+
clean clean_docs:
rm -f $(XMLDIR)/*.xml
rm -f $(HTMLDIR)/*.gif $(HTMLDIR)/*.html
@@ -88,12 +103,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
-# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
-# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
-release_spec:
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+release_spec:
diff --git a/system/doc/general_info/deprecations.xml b/system/doc/general_info/deprecations.xml
deleted file mode 100644
index c748e60059..0000000000
--- a/system/doc/general_info/deprecations.xml
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE chapter SYSTEM "chapter.dtd">
-
-<chapter>
- <header>
- <copyright>
- <year>2019</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- </legalnotice>
-
- <title>Deprecations</title>
- <prepared></prepared>
- <docno></docno>
- <date></date>
- <rev></rev>
- <file>deprecations.xml</file>
- </header>
- <section>
- <title>Introduction</title>
- <p>This document aim to list all deprecated functionality in Erlang/OTP. It
- was introduced as of OTP 22, and have not yet been updated with all old
- deprecations. Deprecations made in other parts of the documentation are of
- course still valid. For more information regarding the strategy regarding
- deprecations see the documentation of
- <seealso marker="../system_principles/misc#deprecation">Support, Compatibility,
- Deprecations, and Removal</seealso>.</p>
- </section>
- <section>
- <marker id="OTP-22"/>
- <title>OTP 22</title>
- <section>
- <title>VxWorks Support</title>
- <p>Some parts of OTP has had limited VxWorks support, such as for
- example <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>.
- This support is now deprecated and has also been
- <seealso marker="scheduled_for_removal#OTP-23">scheduled for removal</seealso>.</p>
- </section>
- <section>
- <title>Legacy parts of erl_interface</title>
- <p>The old legacy <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>
- library (functions with prefix <c>erl_</c>) is deprecated as of OTP 22. These
- parts of <c>erl_interface</c> has been informally deprecated
- for a very long time. You typically want to replace the usage of
- the <c>erl_interface</c> library with the use of the <c>ei</c> library
- which also is part of the <c>erl_interface</c> application. The old legacy
- <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>
- library has also been <seealso marker="scheduled_for_removal#OTP-23">scheduled
- for removal</seealso>.</p>
- </section>
- <section>
- <title>System Events</title>
- <p>
- The format of "System Events" as defined in the man page for
- <seealso marker="stdlib:sys">sys</seealso>
- has been clarified and cleaned up.
- Due to this, code that relied on the internal badly
- documented previous (before this change) format
- of OTP's "System Events", needs to be changed.
- </p>
- <p>
- In the wake of this the function
- <seealso marker="stdlib:sys#get_debug/3">sys:get_debug/3</seealso>
- that returns data with undocumented and internal format
- (and therefore is practically useless) has been deprecated,
- and a new function
- <seealso marker="stdlib:sys#get_log/1">sys:get_log/1</seealso>
- has been added,
- that hopefully does what the deprecated function was intended for.
- </p>
- </section>
- </section>
- <section>
- <marker id="OTP-18"/>
- <title>OTP 18</title>
- <section>
- <title>erlang:now()</title>
- <p>New time functionality and a new time API was introduced. For more information
- see the <seealso marker="erts:time_correction">Time and Time Correction</seealso>
- chapter in the ERTS User's guide and specifically the
- <seealso marker="erts:time_correction#Dos_and_Donts">Dos and Donts</seealso>
- section on how to replace usage of <c>erlang:now()</c>.</p>
- </section>
- </section>
-</chapter>
diff --git a/system/doc/general_info/deprecations_12.inc b/system/doc/general_info/deprecations_12.inc
new file mode 100644
index 0000000000..7bc6965507
--- /dev/null
+++ b/system/doc/general_info/deprecations_12.inc
@@ -0,0 +1,6 @@
+ <section>
+ <title>inets - httpd Apache config files</title>
+ <p>
+ A new config file format was introduced.
+ </p>
+ </section>
diff --git a/system/doc/general_info/deprecations_18.inc b/system/doc/general_info/deprecations_18.inc
new file mode 100644
index 0000000000..04eaf990ea
--- /dev/null
+++ b/system/doc/general_info/deprecations_18.inc
@@ -0,0 +1,17 @@
+ <section>
+ <title>erlang:now/0</title>
+ <p>New time functionality and a new time API was introduced. For more information
+ see the <seeguide marker="erts:time_correction">Time and Time Correction</seeguide>
+ chapter in the ERTS User's guide and specifically the
+ <seeguide marker="erts:time_correction#Dos_and_Donts">Dos and Donts</seeguide>
+ section on how to replace usage of <c>erlang:now/0</c>.</p>
+ </section>
+
+ <section>
+ <title>httpd_conf module</title>
+ <p>
+ API functions in the module <c>httpd_conf</c> was deprecated in favor
+ of standard modules such as <c>lists</c>, <c>string</c>,
+ <c>filelib</c>, and <c>erlang</c>.
+ </p>
+ </section>
diff --git a/system/doc/general_info/deprecations_19.inc b/system/doc/general_info/deprecations_19.inc
new file mode 100644
index 0000000000..162049b4ad
--- /dev/null
+++ b/system/doc/general_info/deprecations_19.inc
@@ -0,0 +1,4 @@
+ <section>
+ <title>SSL/TLS</title>
+ <p>For security reasons SSL-3.0 is no longer supported by default, but can be configured.</p>
+ </section>
diff --git a/system/doc/general_info/deprecations_22.inc b/system/doc/general_info/deprecations_22.inc
new file mode 100644
index 0000000000..53da6df594
--- /dev/null
+++ b/system/doc/general_info/deprecations_22.inc
@@ -0,0 +1,38 @@
+ <section>
+ <title>VxWorks Support</title>
+ <p>Some parts of OTP has had limited VxWorks support, such as for
+ example <seeapp marker="erl_interface:index"><c>erl_interface</c></seeapp>.
+ This support is now deprecated and has also been scheduled for removal in OTP 23.</p>
+ </section>
+ <section>
+ <title>Legacy parts of erl_interface</title>
+ <p>The old legacy <seeapp marker="erl_interface:index"><c>erl_interface</c></seeapp>
+ library (functions with prefix <c>erl_</c>) is deprecated as of OTP 22. These
+ parts of <c>erl_interface</c> has been informally deprecated
+ for a very long time. You typically want to replace the usage of
+ the <c>erl_interface</c> library with the use of the <c>ei</c> library
+ which also is part of the <c>erl_interface</c> application. The old legacy
+ <seeapp marker="erl_interface:index"><c>erl_interface</c></seeapp>
+ library has also been scheduled for removal in OTP 23.</p>
+ </section>
+ <section>
+ <title>System Events</title>
+ <p>
+ The format of "System Events" as defined in the man page for
+ <seeerl marker="stdlib:sys">sys</seeerl>
+ has been clarified and cleaned up.
+ Due to this, code that relied on the internal badly
+ documented previous (before this change) format
+ of OTP's "System Events", needs to be changed.
+ </p>
+ <p>
+ In the wake of this the function
+ <seemfa marker="stdlib:sys#get_debug/3">sys:get_debug/3</seemfa>
+ that returns data with undocumented and internal format
+ (and therefore is practically useless) has been deprecated,
+ and a new function
+ <seemfa marker="stdlib:sys#get_log/1">sys:get_log/1</seemfa>
+ has been added,
+ that hopefully does what the deprecated function was intended for.
+ </p>
+ </section>
diff --git a/system/doc/general_info/deprecations_23.inc b/system/doc/general_info/deprecations_23.inc
new file mode 100644
index 0000000000..f7c5dc0790
--- /dev/null
+++ b/system/doc/general_info/deprecations_23.inc
@@ -0,0 +1,70 @@
+ <section>
+ <title>Crypto Old API</title>
+ <p>The <seeguide marker="crypto:new_api#the-old-api">Old API</seeguide> is now deprecated
+ and has also been
+ <seeguide marker="scheduled_for_removal#otp-24">scheduled for removal</seeguide>.</p>
+ <p>For replacement functions see the
+ <seeguide marker="crypto:new_api#the-new-api">New API</seeguide>.</p>
+ </section>
+
+ <section>
+ <title>http_uri</title>
+ <p>Since OTP 21 the recommended module to handle URIs is
+ <seeerl marker="stdlib:uri_string">uri_string</seeerl>. The module
+ http_uri does not provide a implementation that satisfies the
+ RFC.
+ </p>
+ </section>
+
+ <section>
+ <title>ssh</title>
+ <p>The public key algorithm <c>'ssh-rsa</c> is regarded as insecure due
+ to its usage of SHA1, and is therfore deprecated.
+ It will not be available by default from OTP-24.
+ </p>
+
+ <p>The public key algorithm <c>'ssh-dss</c> is regarded as insecure due
+ to its usage of SHA1 and its short key length, and is therfore deprecated.
+ It is not available by default from OTP-23.
+ </p>
+ </section>
+
+ <section>
+ <title>pg2</title>
+ <p>
+ As of OTP 23, a new process group implementation
+ <seeerl marker="kernel:pg">pg</seeerl> is introduced.
+ <c>pg</c> is similar to <seeerl marker="kernel:pg2">pg2</seeerl>,
+ but with much better scalability properties. However, the API and behavior
+ are not compatible.
+ </p>
+ <p>
+ <c>pg2</c> is now deprecated and has also been
+ <seeguide marker="scheduled_for_removal#otp-24">scheduled for removal</seeguide>
+ in OTP 24.
+ </p>
+ </section>
+
+ <section>
+ <title>Distributed Disk Logs</title>
+ <p>
+ As of OTP 23, the distributed
+ <seeerl marker="kernel:disk_log"><c>disk_log</c></seeerl>
+ feature has been deprecated and it has also been
+ <seeguide marker="scheduled_for_removal#otp-24">scheduled for removal</seeguide>
+ in OTP 24.
+ </p>
+ </section>
+
+ <section>
+ <title>erl_interface registry</title>
+ <p>
+ As of OTP 23, the
+ <seecref marker="erl_interface:registry"><c>registry</c></seecref>
+ functionality part of
+ <seeapp marker="erl_interface:index"><c>erl_interface</c></seeapp>
+ has been deprecated and it has also been
+ <seeguide marker="scheduled_for_removal#otp-24">scheduled for removal</seeguide>
+ in OTP 24.
+ </p>
+ </section>
diff --git a/lib/erl_interface/doc/src/part_erl_interface.xml b/system/doc/general_info/deprecations_head.inc
index e256cfa193..7653dc44f9 100644
--- a/lib/erl_interface/doc/src/part_erl_interface.xml
+++ b/system/doc/general_info/deprecations_head.inc
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE part SYSTEM "part.dtd">
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
-<part xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter>
<header>
<copyright>
- <year>1996</year><year>2016</year>
+ <year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -22,12 +22,18 @@
</legalnotice>
- <title>Erl_Interface User's Guide</title>
- <prepared>Gordon Beaton</prepared>
+ <title>Deprecations</title>
+ <prepared></prepared>
<docno></docno>
- <date>1998-11-30</date>
- <rev>1.2</rev>
- <file>part_erl_interface.sgml</file>
+ <date></date>
+ <rev></rev>
+ <file>deprecations.xml</file>
</header>
- <xi:include href="erl_interface.xml"/>
-</part>
+ <section>
+ <title>Introduction</title>
+ <p>This document lists all deprecated functionality in
+ Erlang/OTP. For more information regarding the strategy regarding
+ deprecations see the documentation of <seeguide
+ marker="system/system_principles:misc#deprecation">Support,
+ Compatibility, Deprecations, and Removal</seeguide>.</p>
+ </section>
diff --git a/system/doc/general_info/scheduled_for_removal_23.inc b/system/doc/general_info/scheduled_for_removal_23.inc
new file mode 100644
index 0000000000..692d51d09c
--- /dev/null
+++ b/system/doc/general_info/scheduled_for_removal_23.inc
@@ -0,0 +1,39 @@
+ <section>
+ <title>VxWorks Support</title>
+ <p>Some parts of OTP has had limited VxWorks support, such as
+ <seeapp marker="erl_interface:index"><c>erl_interface</c></seeapp>.
+ This support will be removed as of OTP 23. This limited support
+ was formally deprecated as of OTP 22.</p>
+ </section>
+ <section>
+ <title>Legacy parts of erl_interface</title>
+ <p>The old legacy <seeapp marker="erl_interface:index"><c>erl_interface</c></seeapp>
+ library (functions with prefix <c>erl_</c>) will be removed as of
+ OTP 23. These parts of <c>erl_interface</c> has been informally deprecated
+ for a very long time, and was formally deprecated in OTP 22. You typically
+ want to replace the usage of the <c>erl_interface</c> library with the use
+ of the <c>ei</c> library which also is part of the <c>erl_interface</c>
+ application.</p>
+ </section>
+
+ <section>
+ <title>httpd_conf module</title>
+ <p>
+ API functions in the module called <c>httpd_conf</c> was deprecated in favor
+ of standard modules such as <c>lists</c>, <c>string</c>,
+ <c>filelib</c>, and <c>erlang</c>. Formally deprecated as of OTP 18.
+ </p>
+ </section>
+
+ <section>
+ <title>inets - httpd Apache config files</title>
+ <p>
+ Support for the Apache-compatible config files will be removed in
+ OTP 23. A new config file format was introduced in OTP 12.
+ </p>
+ </section>
+
+ <section>
+ <title>SSL/TLS</title>
+ <p>For security reasons SSL-3.0 is no longer supported at all.</p>
+ </section>
diff --git a/system/doc/general_info/scheduled_for_removal_24.inc b/system/doc/general_info/scheduled_for_removal_24.inc
new file mode 100644
index 0000000000..72aa9ba648
--- /dev/null
+++ b/system/doc/general_info/scheduled_for_removal_24.inc
@@ -0,0 +1,52 @@
+ <section>
+ <title>Old Crypto API</title>
+ <p>The <seeguide marker="crypto:new_api#the-old-api">Old API</seeguide> will be
+ removed as of OTP 24. The support was formally deprecated as of OTP 23.</p>
+ <p>For replacement functions see the
+ <seeguide marker="crypto:new_api#the-new-api">New API</seeguide>.</p>
+ </section>
+ <section>
+ <title>pg2</title>
+ <p>
+ <seeerl marker="kernel:pg2">pg2</seeerl> is as of
+ <seeguide marker="deprecations#otp-23">OTP 23 deprecated</seeguide>
+ and will be removed in OTP 24.
+ </p>
+ </section>
+ <section>
+ <title>Distributed Disk Logs</title>
+ <p>
+ The distributed
+ <seeerl marker="kernel:disk_log"><c>disk_log</c></seeerl>
+ feature is as of
+ <seeguide marker="deprecations#otp-23">OTP 23 deprecated</seeguide>
+ and will be removed in OTP 24.
+ </p>
+ </section>
+ <section>
+ <title>Megaco version 3 encoding config</title>
+ <p>As of OTP 24, the pre-release version 3 encoding configs;
+ <c>prev3a</c>, <c>prev3b</c> and <c>prev3c</c> will be removed.
+ Use the full version instead. </p>
+ <p>The (encoding) config option for the full version, <c>{version3, 3}</c>,
+ will still be supported, even though its no longer necessary to specify
+ it this way. </p>
+ </section>
+ <section>
+ <title>Compilation of Latin-1 Encoded Erlang Files</title>
+ <p>As of OTP 24, the Erlang compiler will refuse to compile source files
+ encoded in Latin-1 but without a <c>%% coding: latin-1</c> comment at the
+ beginning of the file.</p>
+ </section>
+ <section>
+ <title>erl_interface registry</title>
+ <p>
+ The
+ <seecref marker="erl_interface:registry"><c>registry</c></seecref>
+ functionality part of
+ <seeapp marker="erl_interface:index"><c>erl_interface</c></seeapp>
+ is as of
+ <seeguide marker="deprecations#otp-23">OTP 23 deprecated</seeguide>
+ and will be removed in OTP 24.
+ </p>
+ </section>
diff --git a/system/doc/general_info/scheduled_for_removal_25.inc b/system/doc/general_info/scheduled_for_removal_25.inc
new file mode 100644
index 0000000000..a3dd3a9571
--- /dev/null
+++ b/system/doc/general_info/scheduled_for_removal_25.inc
@@ -0,0 +1,7 @@
+ <section>
+ <title>http_uri</title>
+ <p>Since OTP 21 the recommended module to handle URIs is
+ <seeerl marker="stdlib:uri_string">uri_string</seeerl>. The module
+ <c>http_uri</c> does not provide a implementation that satisfies the
+ RFC. Formally deprecated since OTP-23.</p>
+ </section>
diff --git a/system/doc/general_info/scheduled_for_removal.xml b/system/doc/general_info/scheduled_for_removal_head.inc
index a9421df385..cc64df776b 100644
--- a/system/doc/general_info/scheduled_for_removal.xml
+++ b/system/doc/general_info/scheduled_for_removal_head.inc
@@ -4,14 +4,14 @@
<chapter>
<header>
<copyright>
- <year>2019</year>
+ <year>2020</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
@@ -19,7 +19,7 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
+
</legalnotice>
<title>Scheduled for Removal</title>
@@ -34,28 +34,6 @@
<p>This document list all functionality in Erlang/OTP that currently are
scheduled for removal. For more information regarding the strategy regarding
removal of functionality see the documentation of
- <seealso marker="../system_principles/misc#removal">Support, Compatibility,
- Deprecations, and Removal</seealso>.</p>
- </section>
- <section>
- <marker id="OTP-23"/>
- <title>OTP 23</title>
- <section>
- <title>VxWorks Support</title>
- <p>Some parts of OTP has had limited VxWorks support, such as for
- example <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>.
- This support will be removed as of OTP 23. This limited support
- was formally deprecated as of OTP 22</p>
- </section>
- <section>
- <title>Legacy parts of erl_interface</title>
- <p>The old legacy <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>
- library (functions with prefix <c>erl_</c>) will be removed as of
- OTP 23. These parts of <c>erl_interface</c> has been informally deprecated
- for a very long time, and was formally deprecated in OTP 22. You typically
- want to replace the usage of the <c>erl_interface</c> library with the use
- of the <c>ei</c> library which also is part of the <c>erl_interface</c>
- application.</p>
- </section>
+ <seeguide marker="system/system_principles:misc#removal">Support, Compatibility,
+ Deprecations, and Removal</seeguide>.</p>
</section>
-</chapter>
diff --git a/system/doc/general_info/xmlfiles.mk b/system/doc/general_info/xmlfiles.mk
index 85cdef8919..c52930c62d 100644
--- a/system/doc/general_info/xmlfiles.mk
+++ b/system/doc/general_info/xmlfiles.mk
@@ -17,6 +17,9 @@
#
# %CopyrightEnd%
#
-GENERAL_INFO_CHAPTER_FILES = \
+
+GENERAL_INFO_CHAPTER_FILES =
+
+GENERAL_INFO_CHAPTER_GEN_FILES = \
deprecations.xml \
scheduled_for_removal.xml
diff --git a/system/doc/getting_started/Makefile b/system/doc/getting_started/Makefile
index e337f63e35..6a286d6df6 100644
--- a/system/doc/getting_started/Makefile
+++ b/system/doc/getting_started/Makefile
@@ -75,7 +75,9 @@ DVIPS_FLAGS +=
# Targets
# ----------------------------------------------------
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -94,11 +96,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
-# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
-# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+
release_spec:
diff --git a/system/doc/getting_started/conc_prog.xml b/system/doc/getting_started/conc_prog.xml
index 4374a59e04..76b36c8bd5 100644
--- a/system/doc/getting_started/conc_prog.xml
+++ b/system/doc/getting_started/conc_prog.xml
@@ -555,8 +555,8 @@ ping finished</pre>
<p>This sort of problem can be solved easier by use of
the facilities in OTP, which also provide methods for
updating code on the fly and so on (see
- <seealso marker="doc/design_principles:des_princ#otp design principles">
- OTP Design Principles</seealso>).</p>
+ <seeguide marker="system/design_principles:des_princ#otp design principles">
+ OTP Design Principles</seeguide>).</p>
</item>
<item>
<p>The first program contains some inadequacies
diff --git a/system/doc/getting_started/intro.xml b/system/doc/getting_started/intro.xml
index afae4e8c18..b945fada8d 100644
--- a/system/doc/getting_started/intro.xml
+++ b/system/doc/getting_started/intro.xml
@@ -37,8 +37,8 @@
Also, parts that are greatly simplified are indicated with *manual*.
This means that a lot more information on the subject is to be found in
the Erlang book or in
- <seealso marker="doc/reference_manual:introduction#erlang ref manual">
- Erlang Reference Manual</seealso>.</p>
+ <seeguide marker="system/reference_manual:introduction#erlang ref manual">
+ Erlang Reference Manual</seeguide>.</p>
<section>
<title>Prerequisites</title>
@@ -64,8 +64,8 @@
<item>How to communicate with the outside world and software
written in other languages (ports);
this is described in
- <seealso marker="doc/tutorial:introduction#interoperability tutorial">
- Interoperability Tutorial</seealso>.</item>
+ <seeguide marker="system/tutorial:introduction#interoperability tutorial">
+ Interoperability Tutorial</seeguide>.</item>
<item>Erlang libraries (for example, file handling).</item>
<item>OTP and (in consequence) the Mnesia database.</item>
<item>Hash tables for Erlang terms (ETS).</item>
diff --git a/system/doc/getting_started/robustness.xml b/system/doc/getting_started/robustness.xml
index 6932f0ca0f..cccacf4a65 100644
--- a/system/doc/getting_started/robustness.xml
+++ b/system/doc/getting_started/robustness.xml
@@ -30,7 +30,7 @@
<file>robustness.xml</file>
</header>
<p>Several things are wrong with the messenger example in
- <seealso marker="conc_prog#ex">A Larger Example</seealso>.
+ <seeguide marker="conc_prog#ex">A Larger Example</seeguide>.
For example, if a node where a user is logged
on goes down without doing a logoff, the user remains in
the server's <c>User_List</c>, but the client disappears. This
@@ -141,12 +141,12 @@ after pong_timeout() -></code>
bad match, trying to call a function that does not exist and so on)
exits with an error, that is, has an <em>abnormal</em> exit. A
process which executes
- <seealso marker="erts:erlang#exit/1">exit(Reason)</seealso>
+ <seemfa marker="erts:erlang#exit/1">exit(Reason)</seemfa>
where <c>Reason</c> is any Erlang term except the atom
<c>normal</c>, also has an abnormal exit.</p>
<p>An Erlang process can set up links to other Erlang processes. If
a process calls
- <seealso marker="erts:erlang#link/1">link(Other_Pid)</seealso>
+ <seemfa marker="erts:erlang#link/1">link(Other_Pid)</seemfa>
it sets up a bidirectional link between itself and the process
called <c>Other_Pid</c>. When a process terminates, it sends
something called a <em>signal</em> to all the processes it has
@@ -168,7 +168,7 @@ after pong_timeout() -></code>
exits abnormally, all the processes in the transaction are
killed. As it is often wanted to create a process and link to it at
the same time, there is a special BIF,
- <seealso marker="erts:erlang#spawn_link/1">spawn_link</seealso>
+ <seemfa marker="erts:erlang#spawn_link/1">spawn_link</seemfa>
that does the same as <c>spawn</c>, but also creates a link to
the spawned process.</p>
<p>Now an example of the ping pong example using links to terminate
@@ -227,7 +227,7 @@ Ping received pong</pre>
<code type="none">
process_flag(trap_exit, true)</code>
<p>There are several other process flags, see
- <seealso marker="erts:erlang#process_flag/2">erlang(3)</seealso>.
+ <seemfa marker="erts:erlang#process_flag/2">erlang(3)</seemfa>.
Changing the default behaviour of a process in this way is
usually not done in standard user programs, but is left to
the supervisory programs in OTP.
diff --git a/system/doc/getting_started/seq_prog.xml b/system/doc/getting_started/seq_prog.xml
index 2b0750ff80..7a6b355b8c 100644
--- a/system/doc/getting_started/seq_prog.xml
+++ b/system/doc/getting_started/seq_prog.xml
@@ -37,7 +37,7 @@
and Linux have many, Windows has the command prompt. Erlang has
its own shell where bits of Erlang code can be written directly,
and be evaluated to see what happens
- (see the <seealso marker="stdlib:shell">shell(3)</seealso>
+ (see the <seeerl marker="stdlib:shell">shell(3)</seeerl>
manual page in STDLIB).
</p>
<p>Start
@@ -62,7 +62,7 @@ Eshell V5.9.1 (abort with ^G)
that 2 + 5 is 7. If you make writing mistakes in the shell,
you can delete with the backspace key, as in most shells.
There are many more editing commands in the shell
- (see <seealso marker="erts:tty">tty - A command line interface</seealso> in ERTS User's Guide).</p>
+ (see <seeguide marker="erts:tty">tty - A command line interface</seeguide> in ERTS User's Guide).</p>
<p>(Notice that many line numbers given by the shell in the
following examples are out of sequence. This is because this
tutorial was written and code-tested in separate sessions).</p>
@@ -72,7 +72,7 @@ Eshell V5.9.1 (abort with ^G)
2618.0</pre>
<p>Notice the use of brackets, the multiplication operator "*",
and the division operator "/", as in normal arithmetic (see
- <seealso marker="doc/reference_manual:expressions">Expressions</seealso>).</p>
+ <seeguide marker="system/reference_manual:expressions">Expressions</seeguide>).</p>
<p>Press Control-C to shut down the Erlang system and the Erlang
shell.</p>
<p>The following output is shown:</p>
@@ -98,9 +98,9 @@ BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
that it is in the same directory as the one where you started
<c>erl</c>). If you are lucky your editor has an Erlang mode
that makes it easier for you to enter and format your code
- nicely (see <seealso
+ nicely (see <seeguide
marker="tools:erlang_mode_chapter">The Erlang mode for
- Emacs</seealso> in Tools User's Guide), but you can manage
+ Emacs</seeguide> in Tools User's Guide), but you can manage
perfectly well without. Here is the code to enter:</p>
<code type="none">
-module(tut).
@@ -130,7 +130,7 @@ double(X) ->
written in files. Each file contains an Erlang
<em>module</em>. The first line of code in the module is
the module name (see
- <seealso marker="doc/reference_manual:modules">Modules</seealso>):</p>
+ <seeguide marker="system/reference_manual:modules">Modules</seeguide>):</p>
<code type="none">
-module(tut).</code>
<p>Thus, the module is called <em>tut</em>. Notice
@@ -217,7 +217,7 @@ mult(X, Y) ->
in the functions in the code <c>N</c>, <c>X</c>, and <c>Y</c> are
called variables. Variables must start with a capital letter
(see
- <seealso marker="doc/reference_manual:expressions">Variables</seealso>).
+ <seeguide marker="system/reference_manual:expressions">Variables</seeguide>).
Examples of variables are
<c>Number</c>, <c>ShoeSize</c>, and <c>Age</c>.</p>
</section>
@@ -226,7 +226,7 @@ mult(X, Y) ->
<title>Atoms</title>
<p>Atom is another data type in Erlang. Atoms start with a small
letter (see
- <seealso marker="doc/reference_manual:data_types">Atom</seealso>),
+ <seeguide marker="system/reference_manual:data_types">Atom</seeguide>),
for example, <c>charles</c>,
<c>centimeter</c>, and <c>inch</c>. Atoms are simply names, nothing
else. They are not like variables, which can have a value.</p>
@@ -504,8 +504,8 @@ blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) ->
<p>First a macro <c>is_channel</c> is defined to help with the
guard tests. This is only here for convenience and to reduce
syntax cluttering. For more information about macros, see
- <seealso marker="doc/reference_manual:macros">
- The Preprocessor</seealso>.
+ <seeguide marker="system/reference_manual:macros">
+ The Preprocessor</seeguide>.
</p>
<code type="none">
new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
@@ -683,7 +683,7 @@ ok</pre>
<p><c>print_temp</c> simply calls <c>io:format</c> in a similar way
to what has been described above. Notice that ~-15w says to print
the "term" with a field length (width) of 15 and left justify it.
- (see the <seealso marker="stdlib:io#fwrite/1">io(3)</seealso>) manual page in STDLIB.</p>
+ (see the <seemfa marker="stdlib:io#fwrite/1">io(3)</seemfa>) manual page in STDLIB.</p>
<p>Now <c>format_temps(Rest)</c> is called with the rest of the list
as an argument. This way of doing things is similar to the loop
constructs in other languages. (Yes, this is recursion, but do not
@@ -756,7 +756,7 @@ list_max([Head|Rest], Result_so_far) ->
<item>&gt;= greater or equal</item>
<item>=&lt; less or equal</item>
<item>/= not equal</item></list>
- <p>(see <seealso marker="doc/reference_manual:expressions">Guard Sequences</seealso>).</p>
+ <p>(see <seeguide marker="system/reference_manual:expressions">Guard Sequences</seeguide>).</p>
<p>To change the above program to one that works out the minimum
value of the element in a list, you only need to
write &lt; instead of &gt;. (But it would be wise to change
@@ -863,7 +863,7 @@ reverse([], [3,2,1]) =>
manipulating lists, for example, for reversing them. So before
writing a list-manipulating function it is a good idea to check
if one not already is written for you
- (see the <seealso marker="stdlib:lists">lists(3)</seealso>
+ (see the <seeerl marker="stdlib:lists">lists(3)</seeerl>
manual page in STDLIB).</p>
<p>Now let us get back to the cities and temperatures, but take a more
structured approach this time. First let us convert the whole list
@@ -1093,7 +1093,7 @@ a_equals_1_or_b_equals_7
<p>Notice that <c>tut9:test_if(33,33)</c> does not cause any
condition to succeed. This leads to the run time error
<c>if_clause</c>, here nicely formatted by the shell. See
- <seealso marker="doc/reference_manual:expressions">Guard Sequences</seealso>
+ <seeguide marker="system/reference_manual:expressions">Guard Sequences</seeguide>
for details of the many guard tests available.</p>
<p><c>case</c> is another construct in Erlang. Recall that the
<c>convert_length</c> function was written as:</p>
@@ -1222,7 +1222,7 @@ Year rem 400 == 0 ->
<c>trunc</c>. Only a few BIFs can be used in guards,
and you cannot use functions you have defined yourself in guards.
(see
- <seealso marker="doc/reference_manual:expressions">Guard Sequences</seealso>)
+ <seeguide marker="system/reference_manual:expressions">Guard Sequences</seeguide>)
(For advanced readers: This is to ensure that guards do not have side
effects.) Let us play with a few of these functions in the shell:</p>
<pre>
diff --git a/system/doc/installation_guide/Makefile b/system/doc/installation_guide/Makefile
index c5234c1c9a..c95a426653 100644
--- a/system/doc/installation_guide/Makefile
+++ b/system/doc/installation_guide/Makefile
@@ -100,7 +100,9 @@ $(REDIRECT_HTML_DIR)/%.html: Makefile
echo "This <a href=\"../"$(notdir $@)"\">link</a> will" >> $@
echo "take you there immediately.</p></body></html>" >> $@
-docs: $(XML_GEN_FILES) html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
local_docs: $(XML_GEN_FILES)
@@ -123,13 +125,12 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) $(RELSYSDIR)
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html $(RELSYSDIR)
$(INSTALL_DIR) $(REDIRECT_HTML_RELSYSDIR)
$(INSTALL_DATA) $(REDIRECT_HTML_FILES) $(REDIRECT_HTML_RELSYSDIR)
-release_spec:
-
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+release_spec:
diff --git a/system/doc/oam/Makefile b/system/doc/oam/Makefile
index 77037fa4f8..40b14485da 100644
--- a/system/doc/oam/Makefile
+++ b/system/doc/oam/Makefile
@@ -75,7 +75,9 @@ DVIPS_FLAGS +=
$(HTMLDIR)/%.gif: %.gif
$(CP) $< $@
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -96,11 +98,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
-release_spec:
-
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+release_spec:
diff --git a/system/doc/programming_examples/Makefile b/system/doc/programming_examples/Makefile
index 69289da4f8..7099d88ebc 100644
--- a/system/doc/programming_examples/Makefile
+++ b/system/doc/programming_examples/Makefile
@@ -75,7 +75,10 @@ DVIPS_FLAGS +=
# Targets
# ----------------------------------------------------
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
+
local_docs: PDFDIR=../../pdf
html: $(GIF_FILES) $(HTML_UG_FILE)
@@ -93,12 +96,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
-release_spec:
-
-
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+release_spec:
diff --git a/system/doc/programming_examples/bit_syntax.xml b/system/doc/programming_examples/bit_syntax.xml
index 648b4fb3aa..cc89f3a469 100644
--- a/system/doc/programming_examples/bit_syntax.xml
+++ b/system/doc/programming_examples/bit_syntax.xml
@@ -33,7 +33,7 @@
<section>
<title>Introduction</title>
<p>The complete specification for the bit syntax appears in the
- <seealso marker="doc/reference_manual:expressions#bit_syntax">Reference Manual</seealso>.</p>
+ <seeguide marker="system/reference_manual:expressions#bit_syntax">Reference Manual</seeguide>.</p>
<p>In Erlang, a Bin is used for constructing binaries and matching
binary patterns. A Bin is written with the following syntax:</p>
<code type="none"><![CDATA[
@@ -150,14 +150,14 @@ end.]]></code>
</list>
<p>Default values are used when specifications are missing.
The default values are described in
- <seealso marker="#Defaults">Defaults</seealso>.</p>
+ <seeguide marker="#Defaults">Defaults</seeguide>.</p>
<p>The <c>Value</c> part is any expression, when used in binary construction.
Used in binary matching, the <c>Value</c> part must
be a literal or a variable. For more information about
the <c>Value</c> part, see
- <seealso marker="#Constructing Binaries and Bitstrings">Constructing Binaries and Bitstrings</seealso>
+ <seeguide marker="#Constructing Binaries and Bitstrings">Constructing Binaries and Bitstrings</seeguide>
and
- <seealso marker="#Matching Binaries">Matching Binaries</seealso>.</p>
+ <seeguide marker="#Matching Binaries">Matching Binaries</seeguide>.</p>
<p>The <c>Size</c> part of the segment multiplied by the unit in
<c>TypeSpecifierList</c> (described later) gives the number
of bits for the segment. In construction, <c>Size</c> is any
@@ -168,7 +168,7 @@ end.]]></code>
<taglist>
<tag>Type</tag>
<item>The most commonly used types are <c>integer</c>, <c>float</c>, and <c>binary</c>.
- See <seealso marker="doc/reference_manual:expressions#bit_syntax">Bit Syntax Expressions in the Reference Manual</seealso> for a complete description.
+ See <seeguide marker="system/reference_manual:expressions#bit_syntax">Bit Syntax Expressions in the Reference Manual</seeguide> for a complete description.
</item>
<tag>Signedness</tag>
<item>The signedness specification can be either <c>signed</c>
@@ -292,8 +292,9 @@ X:4/little-signed-integer-unit:8</code>
<p>When matching <c>Value</c>, value must be either a variable or
an integer, or a floating point literal. Expressions are not
allowed.</p>
- <p><c>Size</c> must be an integer literal, or a previously bound
- variable. The following is not allowed:</p>
+ <p><c>Size</c> must be a
+ <seeguide marker="system/reference_manual:expressions#guard_expressions">guard expression</seeguide>, which can use literals and previously bound variables.
+ The following is not allowed:</p>
<code type="none"><![CDATA[
foo(N, <<X:N,T/binary>>) ->
{X,T}.]]></code>
@@ -304,6 +305,33 @@ foo(N, <<X:N,T/binary>>) ->
foo(N, Bin) ->
<<X:N,T/binary>> = Bin,
{X,T}.]]></code>
+ <note><p>Before OTP 23, <c>Size</c> was restricted to be an
+ integer or a variable bound to an integer.</p></note>
+
+ <section>
+ <title>Binding and Using a Size Variable</title>
+ <p>There is one exception to the rule that a variable that is
+ as size must be previously bound. It is possible to match and
+ bind a variable, and use it as a size within the the same
+ binary pattern. For example:</p>
+
+ <code type="none"><![CDATA[
+bar(<<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>) ->
+ {Payload,Rest}.]]></code>
+
+ <p>Here <c>Sz</c> is bound to the value in the first byte of
+ the binary. <c>Sz</c> is then used at the number of bytes
+ to match out as a binary.</p>
+
+ <p>Starting in OTP 23, the size can be a guard expression:</p>
+ <code type="none"><![CDATA[
+bar(<<Sz:8,Payload:((Sz-1)*8)/binary,Rest/binary>>) ->
+ {Payload,Rest}.]]></code>
+
+ <p>Here <c>Sz</c> is the combined size of the header and the
+ payload, so we will need to subtract one byte to get the size
+ of the payload.</p>
+ </section>
<section>
<title>Getting the Rest of the Binary or Bitstring</title>
diff --git a/system/doc/programming_examples/funs.xmlsrc b/system/doc/programming_examples/funs.xmlsrc
index bb519be612..b6dc2da249 100644
--- a/system/doc/programming_examples/funs.xmlsrc
+++ b/system/doc/programming_examples/funs.xmlsrc
@@ -112,9 +112,9 @@ foreach(fun(Pid) -> Pid ! M end, L)</code>
<section>
<title>Syntax of Funs</title>
- <p>Funs are written with the following syntax (see <seealso
- marker="doc/reference_manual:expressions#funs">Fun Expressions
- </seealso> for full description):</p>
+ <p>Funs are written with the following syntax (see <seeguide
+ marker="system/reference_manual:expressions#funs">Fun Expressions
+ </seeguide> for full description):</p>
<code type="none">
F = fun (Arg1, Arg2, ... ArgN) ->
...
diff --git a/system/doc/programming_examples/list_comprehensions.xml b/system/doc/programming_examples/list_comprehensions.xml
index 706cb337ad..f9ce57f478 100644
--- a/system/doc/programming_examples/list_comprehensions.xml
+++ b/system/doc/programming_examples/list_comprehensions.xml
@@ -40,10 +40,10 @@
<c>[1,2,a,...]</c> and X is greater than 3.</p>
<p>The notation <c><![CDATA[X <- [1,2,a,...]]]></c> is a generator and
the expression <c>X > 3</c> is a filter.</p>
- <p>An additional filter, <c>integer(X)</c>, can be added to restrict
+ <p>An additional filter, <c>is_integer(X)</c>, can be added to restrict
the result to integers:</p>
<pre>
-> <input>[X || X &lt;- [1,2,a,3,4,b,5,6], integer(X), X > 3].</input>
+> <input>[X || X &lt;- [1,2,a,3,4,b,5,6], is_integer(X), X > 3].</input>
[4,5,6]</pre>
<p>Generators can be combined. For example, the Cartesian product
of two lists can be written as follows:</p>
diff --git a/system/doc/programming_examples/records.xml b/system/doc/programming_examples/records.xml
index 074aa636b4..04c06816c5 100644
--- a/system/doc/programming_examples/records.xml
+++ b/system/doc/programming_examples/records.xml
@@ -92,7 +92,7 @@ person</pre>
<p>This is because record definitions are only available
at compile time, not at runtime. For details on records
in the shell, see the
- <seealso marker="stdlib:shell">shell(3)</seealso>
+ <seeerl marker="stdlib:shell">shell(3)</seeerl>
manual page in STDLIB.</p>
</section>
@@ -222,7 +222,7 @@ print(#person{name = Name, age = Age,
%% Demonstrates type testing, selector, updating.
-birthday(P) when record(P, person) ->
+birthday(P) when is_record(P, person) ->
P#person{age = P#person.age + 1}.
register_two_hackers() ->
diff --git a/system/doc/reference_manual/Makefile b/system/doc/reference_manual/Makefile
index da44272adb..2e0cb4fbc2 100644
--- a/system/doc/reference_manual/Makefile
+++ b/system/doc/reference_manual/Makefile
@@ -18,6 +18,7 @@
# %CopyrightEnd%
#
#
+
include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/make/$(TARGET)/otp.mk
@@ -25,10 +26,10 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# Application version
# ----------------------------------------------------
include $(ERL_TOP)/erts/vsn.mk
-#VSN=$(SYSTEM_VSN)
APPLICATION=otp-system-documentation
XMLDIR := $(XMLDIR)/reference_manual
+
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
@@ -40,6 +41,7 @@ RELSYSDIR = "$(RELEASE_PATH)/doc/reference_manual"
XML_PART_FILES = part.xml
include xmlfiles.mk
+
XML_CHAPTER_FILES=$(REF_MAN_CHAPTER_FILES)
TOPDOCDIR=..
@@ -48,7 +50,6 @@ BOOK_FILES = book.xml
GIF_FILES=
-
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES)
@@ -65,14 +66,6 @@ HTML_FILES = \
HTMLDIR = ../html/reference_manual
-EXTRA_FILES = $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
- $(C_FILES) \
- $(ERL_FILES) \
- $(HRL_FILES) \
- $(MISC_FILES) \
- $(XML_CHAPTER_FILES:%.xml=%.html)
-
HTML_UG_FILE = $(HTMLDIR)/users_guide.html
# ----------------------------------------------------
@@ -85,7 +78,9 @@ DVIPS_FLAGS +=
# Targets
# ----------------------------------------------------
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -104,14 +99,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
-# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
-# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
-release_spec:
-
-
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+release_spec:
diff --git a/system/doc/reference_manual/character_set.xml b/system/doc/reference_manual/character_set.xml
index ef14bf1372..4d1380afb9 100644
--- a/system/doc/reference_manual/character_set.xml
+++ b/system/doc/reference_manual/character_set.xml
@@ -105,9 +105,9 @@
handle Unicode. The support was limited to
string literals and comments.
More about the usage of Unicode in Erlang source files
- can be found in <seealso
+ can be found in <seeguide
marker="stdlib:unicode_usage#unicode_in_erlang">STDLIB's User's
- Guide</seealso>.</p>
+ Guide</seeguide>.</p>
<p>From Erlang/OTP 20, atoms and function names are also allowed
to contain Unicode characters outside the ISO-Latin-1 range.
Module names, application names, and node names are still
diff --git a/system/doc/reference_manual/code_loading.xml b/system/doc/reference_manual/code_loading.xml
index f5e5e74841..11b95be2b3 100644
--- a/system/doc/reference_manual/code_loading.xml
+++ b/system/doc/reference_manual/code_loading.xml
@@ -43,7 +43,7 @@
<c>.beam</c>. The compiler can also generate a binary which can
be loaded directly.</p>
<p>The compiler is located in the module <c>compile</c> (see the
- <seealso marker="compiler:compile">compile(3)</seealso> manual page in
+ <seeerl marker="compiler:compile">compile(3)</seeerl> manual page in
Compiler).</p>
<pre>
compile:file(Module)
@@ -52,16 +52,16 @@ compile:file(Module, Options)</pre>
both compiles and loads <c>Module</c>.</p>
<p>There is also a module <c>make</c>, which provides a set of
functions similar to the UNIX type Make functions, see the
- <seealso marker="tools:make">make(3)</seealso>
+ <seeerl marker="tools:make">make(3)</seeerl>
manual page in Tools.</p>
<p>The compiler can also be accessed from the OS prompt, see the
- <seealso marker="erts:erl">erl(1)</seealso> manual page in ERTS.</p>
+ <seecom marker="erts:erl">erl(1)</seecom> manual page in ERTS.</p>
<pre>
% erl -compile <input>Module1</input>...<input>ModuleN</input>
% erl -make</pre>
<p>The <c>erlc</c> program provides an even better way to compile
modules from the shell, see the
- <seealso marker="erts:erlc">erlc(1)</seealso> manual page in ERTS.
+ <seecom marker="erts:erlc">erlc(1)</seecom> manual page in ERTS.
It understands a
number of flags that can be used to define macros, add search
paths for include files, and more.</p>
@@ -74,7 +74,7 @@ compile:file(Module, Options)</pre>
<title>Code Loading</title>
<p>The object code must be <em>loaded</em> into the Erlang runtime
system. This is handled by the <em>code server</em>, see the
- <seealso marker="kernel:code">code(3)</seealso>
+ <seeerl marker="kernel:code">code(3)</seeerl>
manual page in Kernel.</p>
<p>The code server loads code according to a code loading strategy,
which is either <em>interactive</em> (default) or
@@ -82,8 +82,8 @@ compile:file(Module, Options)</pre>
a <em>code path</em> and loaded when first referenced. In
embedded mode, code is loaded at start-up according to a
<em>boot script</em>. This is described in
- <seealso marker="doc/system_principles:system_principles#code_loading">
- System Principles </seealso>.</p>
+ <seeguide marker="system/system_principles:system_principles#code_loading">
+ System Principles </seeguide>.</p>
</section>
<section>
diff --git a/system/doc/reference_manual/data_types.xml b/system/doc/reference_manual/data_types.xml
index 93c679357b..35e2ce1c7a 100644
--- a/system/doc/reference_manual/data_types.xml
+++ b/system/doc/reference_manual/data_types.xml
@@ -52,24 +52,33 @@
Integer with the base <em><c>base</c></em>, that must be an
integer in the range 2..36.</item>
</list>
+ <p>Leading zeroes are ignored. Single underscore <c>_</c> can be inserted
+ between digits as a visual separator.</p>
<p><em>Examples:</em></p>
<pre>
1> <input>42.</input>
42
-2> <input>$A.</input>
+2> <input>-1_234_567_890.</input>
+-1234567890
+3> <input>$A.</input>
65
-3> <input>$\n.</input>
+4> <input>$\n.</input>
10
-4> <input>2#101.</input>
+5> <input>2#101.</input>
5
-5> <input>16#1f.</input>
+6> <input>16#1f.</input>
31
-6> <input>2.3.</input>
+7> <input>16#4865_316F_774F_6C64.</input>
+5216630098191412324
+8> <input>2.3.</input>
2.3
-7> <input>2.3e3.</input>
+9> <input>2.3e3.</input>
2.3e3
-8> <input>2.3e-3.</input>
-0.0023</pre>
+10> <input>2.3e-3.</input>
+0.0023
+11> <input>1_234.333_333</input>
+1234.333333
+</pre>
</section>
<section>
@@ -90,7 +99,7 @@ phone_number
<title>Bit Strings and Binaries</title>
<p>A bit string is used to store an area of untyped memory.</p>
<p>Bit strings are expressed using the
- <seealso marker="expressions#bit_syntax">bit syntax</seealso>.</p>
+ <seeguide marker="expressions#bit_syntax">bit syntax</seeguide>.</p>
<p>Bit strings that consist of a number of bits that are evenly
divisible by eight, are called <em>binaries</em></p>
<p><em>Examples:</em></p>
@@ -102,8 +111,8 @@ phone_number
1> <input>&lt;&lt;1:1,0:1&gt;&gt;.</input>
&lt;&lt;2:2>></pre>
<p>For more examples,
- see <seealso marker="doc/programming_examples:bit_syntax">
- Programming Examples</seealso>.</p>
+ see <seeguide marker="system/programming_examples:bit_syntax">
+ Programming Examples</seeguide>.</p>
</section>
<section>
@@ -123,10 +132,10 @@ phone_number
#Fun&lt;erl_eval.6.39074546&gt;
2> <input>Fun1(2).</input>
3</pre>
- <p>Read more about funs in <seealso marker="expressions#funs">
- Fun Expressions</seealso>. For more examples, see
- <seealso marker="doc/programming_examples:funs">
- Programming Examples</seealso>.</p>
+ <p>Read more about funs in <seeguide marker="expressions#funs">
+ Fun Expressions</seeguide>. For more examples, see
+ <seeguide marker="system/programming_examples:funs">
+ Programming Examples</seeguide>.</p>
</section>
<section>
@@ -134,7 +143,7 @@ phone_number
<p>A port identifier identifies an Erlang port.</p>
<p><c>open_port/2</c>, which is used to create ports, returns
a value of this data type.</p>
- <p>Read more about ports in <seealso marker="ports">Ports and Port Drivers</seealso>.</p>
+ <p>Read more about ports in <seeguide marker="ports">Ports and Port Drivers</seeguide>.</p>
</section>
<section>
@@ -170,7 +179,7 @@ loop() ->
I am &lt;0.58.0>
who_are_you</pre>
<p>Read more about processes in
- <seealso marker="processes">Processes</seealso>.</p>
+ <seeguide marker="processes">Processes</seeguide>.</p>
</section>
<section>
@@ -224,10 +233,10 @@ adam
6> <input>map_size(#{}).</input>
0</pre>
<p>A collection of maps processing functions can be found in
- <seealso marker="stdlib:maps"><c>maps</c></seealso> manual page
+ <seeerl marker="stdlib:maps"><c>maps</c></seeerl> manual page
in STDLIB.</p>
- <p>Read more about maps in <seealso marker="expressions#map_expressions">
- Map Expressions</seealso>.</p>
+ <p>Read more about maps in <seeguide marker="expressions#map_expressions">
+ Map Expressions</seeguide>.</p>
<note>
<p>Maps are considered to be experimental during Erlang/OTP R17.</p>
</note>
@@ -274,7 +283,7 @@ a
7> <input>length([]).</input>
0</pre>
<p>A collection of list processing functions can be found in
- the <seealso marker="stdlib:lists">lists</seealso> manual
+ the <seeerl marker="stdlib:lists">lists</seeerl> manual
page in STDLIB.</p>
</section>
@@ -302,7 +311,7 @@ a
expressions are translated to tuple expressions during
compilation. Therefore, record expressions are not understood by
the shell unless special actions are taken. For details, see the
- <seealso marker="stdlib:shell">shell(3)</seealso> manual
+ <seeerl marker="stdlib:shell">shell(3)</seeerl> manual
page in STDLIB).</p>
<p><em>Examples:</em></p>
<pre>
@@ -317,9 +326,9 @@ new(Name, Age) ->
1> <input>person:new(ernie, 44).</input>
{person,ernie,44}</pre>
<p>Read more about records in
- <seealso marker="records">Records</seealso>. More examples can be
- found in <seealso marker="doc/programming_examples:records">
- Programming Examples</seealso>.</p>
+ <seeguide marker="records">Records</seeguide>. More examples can be
+ found in <seeguide marker="system/programming_examples:records">
+ Programming Examples</seeguide>.</p>
</section>
<section>
diff --git a/system/doc/reference_manual/distributed.xml b/system/doc/reference_manual/distributed.xml
index b519609717..38cb99dd0d 100644
--- a/system/doc/reference_manual/distributed.xml
+++ b/system/doc/reference_manual/distributed.xml
@@ -41,17 +41,17 @@
when sending messages, and so on, using registered names.</p>
<p>The distribution mechanism is implemented using TCP/IP sockets.
How to implement an alternative carrier is described in the
- <seealso marker="erts:alt_dist">ERTS User's Guide</seealso>.</p>
+ <seeguide marker="erts:alt_dist">ERTS User's Guide</seeguide>.</p>
<warning>
<p>
Starting a distributed node without also specifying
- <seealso marker="erts:erl#proto_dist"><c>-proto_dist inet_tls</c></seealso>
+ <seecom marker="erts:erl#proto_dist"><c>-proto_dist inet_tls</c></seecom>
will expose the node to attacks that may give the attacker
complete access to the node and in extension the cluster.
When using un-secure distributed nodes, make sure that the
network is configured to keep potential attackers out.
- See the <seealso marker="ssl:ssl_distribution">
- Using SSL for Erlang Distribution</seealso> User's Guide
+ See the <seeguide marker="ssl:ssl_distribution">
+ Using SSL for Erlang Distribution</seeguide> User's Guide
for details on how to setup a secure distributed node.
</p>
</warning>
@@ -92,7 +92,7 @@ dilbert@uab</pre>
node B, and node B has a connection to node C, then node A
also tries to connect to node C. This feature can be turned off by
using the command-line flag <c>-connect_all false</c>, see the
- <seealso marker="erts:erl">erl(1)</seealso> manual page in ERTS.</p>
+ <seecom marker="erts:erl">erl(1)</seecom> manual page in ERTS.</p>
<p>If a node goes down, all connections to that node are removed.
Calling <c>erlang:disconnect_node(Node)</c> forces disconnection
of a node.</p>
@@ -106,7 +106,7 @@ dilbert@uab</pre>
started at every host where an Erlang node is started. It is
responsible for mapping the symbolic node names to machine
addresses. See the
- <seealso marker="erts:epmd">epmd(1)</seealso> manual page in ERTS.</p>
+ <seecom marker="erts:epmd">epmd(1)</seecom> manual page in ERTS.</p>
</section>
<section>
@@ -132,10 +132,10 @@ dilbert@uab</pre>
<p>A <em>C node</em> is a C program written to act as a hidden node
in a distributed Erlang system. The library <em>Erl_Interface</em>
contains functions for this purpose. For more information about
- C nodes, see the <seealso marker="erl_interface:ei_users_guide">
- Erl_Interface</seealso> application and
- <seealso marker="doc/tutorial:introduction#interoperability tutorial">
- Interoperability Tutorial.</seealso>.</p>
+ C nodes, see the <seeguide marker="erl_interface:ei_users_guide">
+ Erl_Interface</seeguide> application and
+ <seeguide marker="system/tutorial:introduction#interoperability tutorial">
+ Interoperability Tutorial.</seeguide>.</p>
</section>
<section>
@@ -175,7 +175,7 @@ dilbert@uab</pre>
way, there is always a fully connected network. If there are
nodes with different cookies, this method can be inappropriate
and the command-line flag <c>-connect_all false</c> must be set,
- see the <seealso marker="erts:erl">erl(1)</seealso>
+ see the <seecom marker="erts:erl">erl(1)</seecom>
manual page in ERTS.</p>
<p>The magic cookie of the local node is retrieved by calling
<c>erlang:get_cookie()</c>.</p>
@@ -184,8 +184,8 @@ dilbert@uab</pre>
<section>
<title>Distribution BIFs</title>
<p>Some useful BIFs for distributed programming
- (for more information, see the <seealso marker="erts:erlang">
- erlang(3)</seealso> manual page in ERTS:</p>
+ (for more information, see the <seeerl marker="erts:erlang">
+ erlang(3)</seeerl> manual page in ERTS:</p>
<table>
<row>
<cell align="left" valign="middle"><em>BIF</em></cell>
@@ -249,8 +249,8 @@ dilbert@uab</pre>
<section>
<title>Distribution Command-Line Flags</title>
<p>Examples of command-line flags used for distributed programming
- (for more information, see the <seealso marker="erts:erl">erl(1)
- </seealso> manual page in ERTS:</p>
+ (for more information, see the <seecom marker="erts:erl">erl(1)
+ </seecom> manual page in ERTS:</p>
<table>
<row>
<cell align="left" valign="middle"><em>Command-Line Flag</em></cell>
diff --git a/system/doc/reference_manual/errors.xml b/system/doc/reference_manual/errors.xml
index 16d3e7590e..ab00c1a2ce 100644
--- a/system/doc/reference_manual/errors.xml
+++ b/system/doc/reference_manual/errors.xml
@@ -77,12 +77,12 @@
<title>Exceptions</title>
<p>Exceptions are run-time errors or generated errors and
are of three different classes, with different origins. The
- <seealso marker="expressions#try">try</seealso> expression
+ <seeguide marker="expressions#try">try</seeguide> expression
can distinguish between the different classes, whereas the
- <seealso marker="expressions#catch">catch</seealso>
+ <seeguide marker="expressions#catch">catch</seeguide>
expression cannot. They are described in
- <seealso marker="expressions">Expressions
- </seealso>.</p>
+ <seeguide marker="expressions">Expressions
+ </seeguide>.</p>
<table>
<row>
<cell align="left" valign="middle"><em>Class</em></cell>
@@ -105,16 +105,17 @@
<tcaption>Exception Classes.</tcaption>
</table>
<p>An exception consists of its class, an exit reason
- (see <seealso marker="#exit_reasons">Exit Reason</seealso>),
+ (see <seeguide marker="#exit_reasons">Exit Reason</seeguide>),
and a stack trace (which aids in finding the code location of
the exception).</p>
- <p>The stack trace can be be bound to a variable from within
+ <p>The stack trace can be bound to a variable from within
a <c>try</c> expression, and is returned for
exceptions of class <c>error</c> from a <c>catch</c> expression.</p>
<p>An exception of class <c>error</c> is also known as a run-time
error.</p>
<section>
+ <marker id="stacktrace"></marker>
<title>The call-stack back trace (stacktrace)</title>
<p>The stack back-trace (<em>stacktrace</em>) is a list of
<c>{Module,Function,Arity,Location}</c>
@@ -165,17 +166,17 @@
<p>It is possible to prevent run-time errors and other
exceptions from causing
the process to terminate by using <c>catch</c> or
- <c>try</c>, see <seealso marker="expressions">
- Expressions</seealso> about
- <seealso marker="expressions#catch">catch</seealso>
- and <seealso marker="expressions#try">try</seealso>.</p>
+ <c>try</c>, see <seeguide marker="expressions">
+ Expressions</seeguide> about
+ <seeguide marker="expressions#catch">catch</seeguide>
+ and <seeguide marker="expressions#try">try</seeguide>.</p>
</section>
<section>
<title>Error Handling Between Processes</title>
<p>Processes can monitor other processes and detect process
terminations, see
- <seealso marker="processes#errors">Processes</seealso>.</p>
+ <seeguide marker="processes#errors">Processes</seeguide>.</p>
</section>
</section>
@@ -237,7 +238,11 @@
</row>
<row>
<cell align="left" valign="middle"><c>noproc</c></cell>
- <cell align="left" valign="middle">Trying to link to a non-existing process.</cell>
+ <cell align="left" valign="middle">Trying to link or monitor to a non-existing process or port.</cell>
+ </row>
+ <row>
+ <cell align="left" valign="middle"><c>noconnection</c></cell>
+ <cell align="left" valign="middle">A link or monitor to a remote process was broken because a connection between the nodes could not be established or was severed.</cell>
</row>
<row>
<cell align="left" valign="middle"><c>{nocatch,V}</c></cell>
@@ -246,8 +251,8 @@
<row>
<cell align="left" valign="middle"><c>system_limit</c></cell>
<cell align="left" valign="middle">A system limit has been reached.
- See <seealso marker="doc/efficiency_guide:advanced">
- Efficiency Guide</seealso> for information about system limits.
+ See <seeguide marker="system/efficiency_guide:advanced">
+ Efficiency Guide</seeguide> for information about system limits.
</cell>
</row>
<tcaption>Exit Reasons</tcaption>
diff --git a/system/doc/reference_manual/expressions.xml b/system/doc/reference_manual/expressions.xml
index a24729ad45..9747cc9a04 100644
--- a/system/doc/reference_manual/expressions.xml
+++ b/system/doc/reference_manual/expressions.xml
@@ -37,8 +37,8 @@
separate sections:
</p>
<list type="bulleted">
- <item><p><seealso marker="macros">Preprocessor</seealso></p></item>
- <item><p><seealso marker="records">Records</seealso></p></item>
+ <item><p><seeguide marker="macros">Preprocessor</seeguide></p></item>
+ <item><p><seeguide marker="records">Records</seeguide></p></item>
</list>
<section>
@@ -82,7 +82,7 @@ Phone_number
_
_Height</pre>
<p>Variables are bound to values using
- <seealso marker="patterns">pattern matching</seealso>. Erlang
+ <seeguide marker="patterns">pattern matching</seeguide>. Erlang
uses <em>single assignment</em>, that is, a variable can only be bound
once.</p>
<p>The <em>anonymous variable</em> is denoted by underscore (_) and
@@ -254,9 +254,9 @@ spawn(m, init, [])</code>
<p>Notice that when calling a local function, there is a difference
between using the implicitly or fully qualified function name.
The latter always refers to the latest version of the module.
- See <seealso marker="code_loading">Compilation and Code Loading
- </seealso> and <seealso marker="functions#eval">
- Function Evaluation</seealso>.</p>
+ See <seeguide marker="code_loading">Compilation and Code Loading
+ </seeguide> and <seeguide marker="functions#eval">
+ Function Evaluation</seeguide>.</p>
<section>
<title>Local Function Names Clashing With Auto-Imported BIFs</title>
@@ -574,6 +574,8 @@ number &lt; atom &lt; reference &lt; fun &lt; port &lt; pid &lt; tuple &lt; map
<p>Lists are compared element by element. Tuples are ordered by
size, two tuples with the same size are compared element by
element.</p>
+ <p>Bit strings are compared bit by bit. If one bit string is a
+ prefix of the other, the shorter bit string is considered smaller.</p>
<p>Maps are ordered by size, two maps with the same size are compared by keys in
ascending term order and then by values in key order.
In maps key order integers types are considered less than floats types.
@@ -601,8 +603,13 @@ false
false
4> <input>#{c => 3} > #{a => 1, b => 2}.</input>
false
-4> <input>#{a => 1, b => 2} == #{a => 1.0, b => 2.0}.</input>
-true</pre>
+5> <input>#{a => 1, b => 2} == #{a => 1.0, b => 2.0}.</input>
+true
+6> <input>&lt;&lt;2:2>> &lt; &lt;&lt;128>>.</input>
+true
+7> <input>&lt;&lt;3:2>> &lt; &lt;&lt;128>>.</input>
+false
+</pre>
</section>
<section>
@@ -944,7 +951,7 @@ M4 = M3#{a := 2, b := 3}. % 'a' and 'b' was added in `M1` and `M2`.</code>
Here <c>M0</c> is any map. It follows that <c>M1 .. M4</c> are maps as well.
</p>
<p>
- More <em>Examples:</em>
+ <em>More examples:</em>
</p>
<pre>
1> <input>M = #{1 => a}.</input>
@@ -973,15 +980,20 @@ M4 = M3#{a := 2, b := 3}. % 'a' and 'b' was added in `M1` and `M2`.</code>
<code>#{ K := V } = M</code>
<p>
- Here <c>M</c> is any map. The key <c>K</c> must be an expression with bound
- variables or literals. <c>V</c> can be any pattern with either bound or
- unbound variables.
+ Here <c>M</c> is any map. The key <c>K</c> must be a
+ <seeguide marker="#guard_expressions">guard
+ expression</seeguide>, with all variables already bound.
+ <c>V</c> can be any pattern with either bound or unbound
+ variables.
</p>
<p>
If the variable <c>V</c> is unbound, it becomes bound to the value associated
with the key <c>K</c>, which must exist in the map <c>M</c>. If the variable
<c>V</c> is bound, it must match the value associated with <c>K</c> in <c>M</c>.
</p>
+ <note><p>Before OTP 23, the expression defining the key
+ <c>K</c> was restricted to be either a single variable or a
+ literal.</p></note>
<p><em>Example:</em></p>
<pre>
1> <input>M = #{"tuple" => {1,2}}.</input>
@@ -998,9 +1010,12 @@ M4 = M3#{a := 2, b := 3}. % 'a' and 'b' was added in `M1` and `M2`.</code>
</p>
<code>#{ K1 := V1, .., Kn := Vn } = M</code>
<p>
- Here keys <c>K1 .. Kn</c> are any expressions with literals or bound variables. If all
- keys exist in map <c>M</c>, all variables in <c>V1 .. Vn</c> is matched to the
- associated values of their respective keys.
+ Here keys <c>K1 .. Kn</c> are any expressions with
+ literals or bound variables. If all key expressions
+ evalute successfully and all keys exist in map
+ <c>M</c>, all variables in <c>V1 .. Vn</c> is
+ matched to the associated values of their respective
+ keys.
</p>
<p>
If the matching conditions are not met, the match fails, either with:
@@ -1034,6 +1049,9 @@ M4 = M3#{a := 2, b := 3}. % 'a' and 'b' was added in `M1` and `M2`.</code>
This expression matches if the expression <c>Expr</c> is of type map, otherwise
it fails with an exception <c>badmatch</c>.
</p>
+ <p>Here the key to be retrieved is constructed from an expression:</p>
+ <code>#{{tag,length(List)} := V} = Map</code>
+ <p><c>List</c> must be an already bound variable.</p>
<section>
<title>Matching Syntax</title>
<p>
@@ -1057,15 +1075,19 @@ handle_call(change, From, #{ state := start } = S) ->
Maps are allowed in guards as long as all subexpressions are valid guard expressions.
</p>
<p>
- Two guard BIFs handle maps:
+ The following guard BIFs handle maps:
</p>
<list>
<item>
- <seealso marker="erts:erlang#is_map/1">is_map/1</seealso>
+ <seemfa marker="erts:erlang#is_map/1">is_map/1</seemfa>
+ in the <c>erlang</c> module
+ </item>
+ <item>
+ <seemfa marker="erts:erlang#map_get/2">map_get/2</seemfa>
in the <c>erlang</c> module
</item>
<item>
- <seealso marker="erts:erlang#map_size/1">map_size/1</seealso>
+ <seemfa marker="erts:erlang#map_size/1">map_size/1</seemfa>
in the <c>erlang</c> module
</item>
</list>
@@ -1100,8 +1122,13 @@ Ei = Value |
<p>Used in a bit string construction, <c>Size</c> is an expression
that is to evaluate to an integer.</p>
- <p>Used in a bit string matching, <c>Size</c> must be an integer, or a
- variable bound to an integer.</p>
+ <p>Used in a bit string matching, <c>Size</c> must be a
+ <seeguide marker="#guard_expressions">guard expression</seeguide>
+ that evaluates to an integer. All variables in the guard expression
+ must be already bound.</p>
+
+ <note><p>Before OTP 23, <c>Size</c> was restricted to be an
+ integer or a variable bound to an integer.</p></note>
<p>The value of <c>Size</c> specifies the size of the segment in
units (see below). The default value depends on the type (see
@@ -1234,8 +1261,8 @@ Ei = Value |
"<c><![CDATA[B =<<1>>]]></c>" which is a syntax error. The correct way is
to write a space after '=': "<c><![CDATA[B= <<1>>]]></c>.</p>
<p>More examples are provided in
- <seealso marker="doc/programming_examples:bit_syntax">
- Programming Examples</seealso>.</p>
+ <seeguide marker="system/programming_examples:bit_syntax">
+ Programming Examples</seeguide>.</p>
</section>
<section>
@@ -1252,7 +1279,7 @@ end</pre>
<p>A fun expression begins with the keyword <c>fun</c> and ends
with the keyword <c>end</c>. Between them is to be a function
declaration, similar to a
- <seealso marker="functions#syntax">regular function declaration</seealso>,
+ <seeguide marker="functions#syntax">regular function declaration</seeguide>,
except that the function name is optional and is to be a variable, if
any.</p>
<p>Variables in a fun head shadow the function name and both shadow
@@ -1291,8 +1318,8 @@ fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end</pre>
the code for the module in which it is defined.
</p>
<p>More examples are provided in
- <seealso marker="doc/programming_examples:funs">
- Programming Examples</seealso>.</p>
+ <seeguide marker="system/programming_examples:funs">
+ Programming Examples</seeguide>.</p>
</section>
<section>
@@ -1314,7 +1341,7 @@ catch Expr</code>
<c>Term</c> is returned.</p>
<p><c>Reason</c> depends on the type of error that occurred, and
<c>Stack</c> is the stack of recent function calls, see
- <seealso marker="errors#exit_reasons">Exit Reasons</seealso>.</p>
+ <seeguide marker="errors#exit_reasons">Exit Reasons</seeguide>.</p>
<p><em>Examples:</em></p>
<pre>
1> <input>catch 1+2.</input>
@@ -1352,7 +1379,7 @@ catch
ExceptionBodyN
end</code>
<p>This is an enhancement of
- <seealso marker="#catch">catch</seealso>.
+ <seeguide marker="#catch">catch</seeguide>.
It gives the possibility to:</p>
<list type="bulleted">
<item>Distinguish between different exception classes.</item>
@@ -1413,7 +1440,7 @@ end</code>
<p>If the evaluation of <c>Exprs</c> succeeds without an exception,
the patterns <c>Pattern</c> are sequentially matched against
the result in the same way as for a
- <seealso marker="#case">case</seealso> expression, except that if
+ <seeguide marker="#case">case</seeguide> expression, except that if
the matching fails, a <c>try_clause</c> run-time error occurs instead of a
<c>case_clause</c>.</p>
<p>Only exceptions occurring during the evaluation of <c>Exprs</c> can be
@@ -1501,7 +1528,7 @@ end</code>
<pre>
(Expr)</pre>
<p>Parenthesized expressions are useful to override
- <seealso marker="#prec">operator precedences</seealso>,
+ <seeguide marker="#prec">operator precedences</seeguide>,
for example, in arithmetic expressions:</p>
<pre>
1> <input>1 + 2 * 3.</input>
@@ -1571,8 +1598,8 @@ end</pre>
2> <input>[x || is_integer(x)].</input>
[]</pre>
<p>More examples are provided in
- <seealso marker="doc/programming_examples:list_comprehensions">
- Programming Examples.</seealso></p>
+ <seeguide marker="system/programming_examples:list_comprehensions">
+ Programming Examples.</seeguide></p>
</section>
@@ -1615,8 +1642,8 @@ end</pre>
&lt;&lt;X&gt;&gt; &lt;= &lt;&lt; 1,2,3 &gt;&gt; &gt;&gt;.</input>
&lt;&lt;2,4,6&gt;&gt;</pre>
<p>More examples are provided in
- <seealso marker="doc/programming_examples:bit_syntax">
- Programming Examples.</seealso></p>
+ <seeguide marker="system/programming_examples:bit_syntax">
+ Programming Examples.</seeguide></p>
</section>
<section>
@@ -1632,16 +1659,26 @@ end</pre>
by comma (,). The guard is true if all guard expressions
evaluate to <c>true</c>.</p>
<p><c>GuardExpr1,...,GuardExprN</c></p>
- <p>The set of valid <em>guard expressions</em> (sometimes called
- guard tests) is a subset of the set of valid Erlang expressions.
- The reason for restricting the set of valid expressions is that
- evaluation of a guard expression must be guaranteed to be free
- of side effects. Valid guard expressions are the following:</p>
+ </section>
+
+ <section>
+ <marker id="guard_expressions"></marker>
+ <title>Guard Expressions</title>
+ <p>The set of valid <em>guard expressions</em> is a subset of the
+ set of valid Erlang expressions. The reason for restricting the
+ set of valid expressions is that evaluation of a guard expression
+ must be guaranteed to be free of side effects. Valid guard
+ expressions are the following:</p>
<list type="bulleted">
- <item>The atom <c>true</c></item>
- <item>Other constants (terms and bound variables), all regarded
- as false</item>
- <item>Calls to the BIFs specified in table <c>Type Test BIFs</c></item>
+ <item>Variables</item>
+ <item>Constants (atoms, integer, floats, lists, tuples, records,
+ binaries, and maps)</item>
+ <item>Expressions that construct atoms, integer, floats, lists,
+ tuples, records, binaries, and maps</item>
+ <item>Expressions that update a map</item>
+ <item>The record epxressions <c>Expr#Name.Field</c> and <c>#Name.Field</c></item>
+ <item>Calls to the BIFs specified in tables <em>Type Test BIFs</em> and
+ <em>Other BIFs Allowed in Guard Expressions</em></item>
<item>Term comparisons</item>
<item>Arithmetic expressions</item>
<item>Boolean expressions</item>
@@ -1729,6 +1766,9 @@ end</pre>
<cell align="left" valign="middle"><c>length(List)</c></cell>
</row>
<row>
+ <cell align="left" valign="middle"><c>map_get(Key, Map)</c></cell>
+ </row>
+ <row>
<cell align="left" valign="middle"><c>map_size(Map)</c></cell>
</row>
<row>
diff --git a/system/doc/reference_manual/functions.xml b/system/doc/reference_manual/functions.xml
index 863514ee78..948df872f7 100644
--- a/system/doc/reference_manual/functions.xml
+++ b/system/doc/reference_manual/functions.xml
@@ -61,7 +61,7 @@ Expr1,
...,
ExprN</pre>
<p>Valid Erlang expressions and guard sequences are described in
- <seealso marker="expressions">Expressions</seealso>.</p>
+ <seeguide marker="expressions">Expressions</seeguide>.</p>
<p><em>Example:</em></p>
<pre>
fact(N) when N>0 -> % first clause head
@@ -155,7 +155,7 @@ loop(N) ->
<p>The most commonly used BIFs belonging to <c>erlang(3)</c> are
<em>auto-imported</em>. They do not need to be prefixed with
the module name. Which BIFs that are auto-imported is specified in the
- <seealso marker="erts:erlang">erlang(3)</seealso> module in ERTS.
+ <seeerl marker="erts:erlang">erlang(3)</seeerl> module in ERTS.
For example, standard-type conversion BIFs like
<c>atom_to_list</c> and BIFs allowed in guards can be called
without specifying the module name.</p>
diff --git a/system/doc/reference_manual/introduction.xml b/system/doc/reference_manual/introduction.xml
index 69e52e0b37..6af58f59ed 100644
--- a/system/doc/reference_manual/introduction.xml
+++ b/system/doc/reference_manual/introduction.xml
@@ -44,21 +44,21 @@
<p>Information about implementation of Erlang can, for example, be found,
in the following:</p>
<list type="bulleted">
- <item><p><seealso marker="doc/system_principles:system_principles">
- System Principles</seealso></p>
+ <item><p><seeguide marker="system/system_principles:system_principles">
+ System Principles</seeguide></p>
<p>Starting and stopping, boot scripts, code loading,
- <seealso marker="doc/system_principles:error_logging">
- logging</seealso>,
- <seealso marker="doc/system_principles:create_target">
- creating target systems</seealso></p>
+ <seeguide marker="system/system_principles:error_logging">
+ logging</seeguide>,
+ <seeguide marker="system/system_principles:create_target">
+ creating target systems</seeguide></p>
</item>
- <item><p><seealso marker="doc/efficiency_guide:advanced">
- Efficiency Guide</seealso></p>
+ <item><p><seeguide marker="system/efficiency_guide:advanced">
+ Efficiency Guide</seeguide></p>
<p>Memory consumption, system limits</p>
</item>
<item><p>ERTS User's Guide</p>
- <p><seealso marker="erts:crash_dump">Crash dumps</seealso>,
- <seealso marker="erts:driver">drivers</seealso></p>
+ <p><seeguide marker="erts:crash_dump">Crash dumps</seeguide>,
+ <seeguide marker="erts:driver">drivers</seeguide></p>
</item>
</list>
</section>
@@ -87,7 +87,7 @@
<section>
<title>Complete List of BIFs</title>
<p>For a complete list of BIFs, their arguments and return values,
- see <seealso marker="erts:erlang">erlang(3)</seealso>
+ see <seeerl marker="erts:erlang">erlang(3)</seeerl>
manual page in ERTS.</p>
</section>
diff --git a/system/doc/reference_manual/macros.xml b/system/doc/reference_manual/macros.xml
index 8a8d5f3a4c..32efc635c3 100644
--- a/system/doc/reference_manual/macros.xml
+++ b/system/doc/reference_manual/macros.xml
@@ -58,9 +58,9 @@
<item>The directories given by the <c>include</c> option</item>
</list>
<p>For details, see the
- <seealso marker="erts:erlc">erlc(1)</seealso> manual page
+ <seecom marker="erts:erlc">erlc(1)</seecom> manual page
in ERTS and
- <seealso marker="compiler:compile">compile(3)</seealso>
+ <seeerl marker="compiler:compile">compile(3)</seeerl>
manual page in Compiler.</p>
<p><em>Examples:</em></p>
<pre>
@@ -153,7 +153,7 @@ bar(X) ->
<tag><c>?OTP_RELEASE</c></tag>
<item>The OTP release that the currently executing ERTS
application is part of, as an integer. For details, see
- <seealso marker="erts:erlang#system_info/1"><c>erlang:system_info(otp_release)</c></seealso>.
+ <seemfa marker="erts:erlang#system_info/1"><c>erlang:system_info(otp_release)</c></seemfa>.
This macro was introduced in OTP release 21.</item>
</taglist>
</section>
diff --git a/system/doc/reference_manual/modules.xml b/system/doc/reference_manual/modules.xml
index 6f93198ec1..b8f20894b9 100644
--- a/system/doc/reference_manual/modules.xml
+++ b/system/doc/reference_manual/modules.xml
@@ -45,7 +45,7 @@ fact(N) when N>0 -> % beginning of function declaration
fact(0) -> % |
1. % end of function declaration</pre>
<p>For a description of function declarations, see
- <seealso marker="functions">Function Declaration Syntax</seealso>.</p>
+ <seeguide marker="functions">Function Declaration Syntax</seeguide>.</p>
</section>
<section>
@@ -64,7 +64,7 @@ fact(0) -> % |
<p>Any module attribute can be specified. The attributes are stored
in the compiled code and can be retrieved by calling
<c>Module:module_info(attributes)</c>, or by using the module
- <seealso marker="stdlib:beam_lib#chunks/2">beam_lib(3)</seealso>
+ <seemfa marker="stdlib:beam_lib#chunks/2">beam_lib(3)</seemfa>
in STDLIB.</p>
<p>Several module attributes have predefined meanings.
@@ -81,7 +81,7 @@ fact(0) -> % |
<p>Module declaration, defining the name of the module.
The name <c>Module</c>, an atom, is to be same as
the file name minus the extension <c>.erl</c>. Otherwise
- <seealso marker="code_loading#loading">code loading</seealso> does
+ <seeguide marker="code_loading#loading">code loading</seeguide> does
not work as intended.</p>
<p>This attribute is to be specified first and is the only
mandatory attribute.</p>
@@ -109,14 +109,14 @@ fact(0) -> % |
<p>Compiler options. <c>Options</c> is a single option
or a list of options.
This attribute is added to the option list when
- compiling the module. See the <seealso marker="compiler:compile">
- compile(3)</seealso> manual page in Compiler.</p>
+ compiling the module. See the <seeerl marker="compiler:compile">
+ compile(3)</seeerl> manual page in Compiler.</p>
</item>
<tag><c>-vsn(Vsn).</c></tag>
<item>
<p>Module version. <c>Vsn</c> is any literal term and can be
retrieved using <c>beam_lib:version/1</c>, see the
- <seealso marker="stdlib:beam_lib#version/1">beam_lib(3)</seealso>
+ <seemfa marker="stdlib:beam_lib#version/1">beam_lib(3)</seemfa>
manual page in STDLIB.</p>
<p>If this attribute is not specified, the version defaults
to the MD5 checksum of the module.</p>
@@ -126,8 +126,8 @@ fact(0) -> % |
<p>This attribute names a function that is to be run
automatically when a
module is loaded. For more information, see
- <seealso marker="code_loading#on_load">
- Running a Function When a Module is Loaded</seealso>.</p>
+ <seeguide marker="code_loading#on_load">
+ Running a Function When a Module is Loaded</seeguide>.</p>
</item>
</taglist>
</section>
@@ -161,8 +161,8 @@ behaviour_info(callbacks) -> Callbacks.</pre>
extra type information can be used by tools to produce
documentation or find discrepancies.</p>
<p>Read more about behaviours and callback modules in
- <seealso marker="doc/design_principles:spec_proc#behaviours">
- OTP Design Principles</seealso>.</p>
+ <seeguide marker="system/design_principles:spec_proc#behaviours">
+ OTP Design Principles</seeguide>.</p>
</section>
<section>
@@ -173,7 +173,7 @@ behaviour_info(callbacks) -> Callbacks.</pre>
-record(Record,Fields).</pre>
<p>Record definitions are allowed anywhere in a module,
also among the function declarations.
- Read more in <seealso marker="records">Records</seealso>.</p>
+ Read more in <seeguide marker="records">Records</seeguide>.</p>
</section>
<section>
@@ -185,7 +185,7 @@ behaviour_info(callbacks) -> Callbacks.</pre>
-include("SomeFile.hrl").
-define(Macro,Replacement).</pre>
- <p>Read more in <seealso marker="macros">Preprocessor</seealso>.</p>
+ <p>Read more in <seeguide marker="macros">Preprocessor</seeguide>.</p>
</section>
<section>
@@ -209,7 +209,7 @@ behaviour_info(callbacks) -> Callbacks.</pre>
<pre>
-type my_type() :: atom() | integer().
-spec my_function(integer()) -> integer().</pre>
- <p>Read more in <seealso marker="typespec">Types and Function specifications</seealso>.
+ <p>Read more in <seeguide marker="typespec">Types and Function specifications</seeguide>.
</p>
<p>
The description is based on
@@ -275,7 +275,7 @@ behaviour_info(callbacks) -> Callbacks.</pre>
<p>The list of attributes becomes empty if
the module is stripped with the
- <seealso marker="stdlib:beam_lib#strip/1">beam_lib(3)</seealso>
+ <seemfa marker="stdlib:beam_lib#strip/1">beam_lib(3)</seemfa>
module (in STDLIB).</p>
</item>
@@ -284,7 +284,7 @@ behaviour_info(callbacks) -> Callbacks.</pre>
<p>Returns a list of tuples with information about
how the module was compiled. This list is empty if
the module has been stripped with the
- <seealso marker="stdlib:beam_lib#strip/1">beam_lib(3)</seealso>
+ <seemfa marker="stdlib:beam_lib#strip/1">beam_lib(3)</seemfa>
module (in STDLIB).</p>
</item>
diff --git a/system/doc/reference_manual/patterns.xml b/system/doc/reference_manual/patterns.xml
index 57b84b4dfc..e2363cfd0e 100644
--- a/system/doc/reference_manual/patterns.xml
+++ b/system/doc/reference_manual/patterns.xml
@@ -36,9 +36,9 @@
evaluating a function call, <c>case</c>- <c>receive</c>-
<c>try</c>- expressions and match operator (=) expressions.</p>
<p>In a pattern matching, a left-hand side
- <seealso marker="expressions#pattern">pattern</seealso> is matched
+ <seeguide marker="expressions#pattern">pattern</seeguide> is matched
against a right-hand side
- <seealso marker="expressions#term">term</seealso>. If
+ <seeguide marker="expressions#term">term</seeguide>. If
the matching succeeds, any unbound variables in the pattern
become bound. If the matching fails, a run-time error occurs.</p>
<p><em>Examples:</em></p>
diff --git a/system/doc/reference_manual/ports.xml b/system/doc/reference_manual/ports.xml
index e96eb428e2..623bfe34e2 100644
--- a/system/doc/reference_manual/ports.xml
+++ b/system/doc/reference_manual/ports.xml
@@ -30,10 +30,10 @@
<file>ports.xml</file>
</header>
<p>Examples of how to use ports and port drivers are provided in
- <seealso marker="doc/tutorial:introduction#interoperability tutorial">
- Interoperability Tutorial</seealso>.
+ <seeguide marker="system/tutorial:introduction#interoperability tutorial">
+ Interoperability Tutorial</seeguide>.
For information about the BIFs mentioned, see the
- <seealso marker="erts:erlang">erlang(3)</seealso> manual
+ <seeerl marker="erts:erlang">erlang(3)</seeerl> manual
page in ERTS.</p>
<section>
@@ -65,11 +65,11 @@
system to leak memory, hang or crash.</p>
</warning>
<p>For information about port drivers, see the
- <seealso marker="erts:erl_driver">erl_driver(4)</seealso>
+ <seecref marker="erts:erl_driver">erl_driver(4)</seecref>
manual page in ERTS,
- <seealso marker="erts:driver_entry">driver_entry(1)</seealso>
+ <seecref marker="erts:driver_entry">driver_entry(1)</seecref>
manual page in ERTS, and
- <seealso marker="kernel:erl_ddll">erl_ddll(3)</seealso>
+ <seeerl marker="kernel:erl_ddll">erl_ddll(3)</seeerl>
manual page in Kernel.</p>
</section>
diff --git a/system/doc/reference_manual/processes.xml b/system/doc/reference_manual/processes.xml
index f656d0318e..597258b9fd 100644
--- a/system/doc/reference_manual/processes.xml
+++ b/system/doc/reference_manual/processes.xml
@@ -93,7 +93,7 @@ spawn(Module, Name, Args) -> pid()
execute terminates normally.</p>
<p>A process terminates with an exit reason <c>{Reason,Stack}</c>
when a run-time error occurs. See
- <seealso marker="errors#exit_reasons">Exit Reasons</seealso>.</p>
+ <seeguide marker="errors#exit_reasons">Exit Reasons</seeguide>.</p>
<p>A process can terminate itself by calling one of the
following BIFs:</p>
<list type="bulleted">
@@ -105,16 +105,16 @@ spawn(Module, Name, Args) -> pid()
<c>exit/1</c> or <c>{Reason,Stack}</c> for the others.</p>
<p>A process can also be terminated if it receives an exit signal
with another exit reason than <c>normal</c>, see
- <seealso marker="#errors">Error Handling</seealso>.</p>
+ <seeguide marker="#errors">Error Handling</seeguide>.</p>
</section>
<section>
<title>Message Sending</title>
<p>Processes communicate by sending and receiving messages.
Messages are sent by using
- the <seealso marker="expressions#send">send operator !</seealso>
+ the <seeguide marker="expressions#send">send operator !</seeguide>
and received by calling
- <seealso marker="expressions#receive">receive</seealso>.</p>
+ <seeguide marker="expressions#receive">receive</seeguide>.</p>
<p>Message sending is asynchronous and safe, the message is
guaranteed to eventually reach the recipient, provided that
the recipient exists.</p>
@@ -122,16 +122,41 @@ spawn(Module, Name, Args) -> pid()
<section>
<title>Links</title>
- <p>Two processes can be <em>linked</em> to each other. A link
- between two processes <c>Pid1</c> and <c>Pid2</c> is created
- by <c>Pid1</c> calling the BIF <c>link(Pid2)</c> (or conversely).
- There also exist a number of <c>spawn_link</c> BIFs, which spawn
- and link to a process in one operation.</p>
- <p>Links are bidirectional and there can only be one link between
- two processes. Repeated calls to <c>link(Pid)</c> have no effect.</p>
- <p>A link can be removed by calling the BIF <c>unlink(Pid)</c>.</p>
+ <p>
+ Two processes can be <em>linked</em> to each other. Also a
+ process and a port that reside on the same node can be linked
+ to each other. A link beteen two processes can be created
+ if one of them calls the
+ <seemfa marker="erts:erlang#link/1"><c>link/1</c></seemfa> BIF
+ with the process identifier of the other process as argument.
+ Links can also be created using one the following spawn BIFs
+ <seemfa marker="erts:erlang#spawn_link/4"><c>spawn_link()</c></seemfa>,
+ <seemfa marker="erts:erlang#spawn_opt/5"><c>spawn_opt()</c></seemfa>, or
+ <seemfa marker="erts:erlang#spawn_request/5"><c>spawn_request()</c></seemfa>.
+ The spawn operation and the link operation will
+ be performed atomically, in these cases.
+ </p>
+ <p>
+ If one of the participants of a link terminates, it will
+ <seeguide marker="system/reference_manual:processes#sending_exit_signals">send
+ an exit signal</seeguide> to the other participant. The exit
+ signal will contain the
+ <seeguide marker="system/reference_manual:processes#link_exit_signal_reason">exit
+ reason</seeguide> of the terminated participant.
+ </p>
+ <p>
+ A link can be removed by calling the
+ <seemfa marker="erts:erlang#unlink/1"><c>unlink/1</c></seemfa>
+ BIF.
+ </p>
+ <p>
+ Links are bidirectional and there can only be one link between
+ two processes. Repeated calls to <c>link()</c> have no effect.
+ Either one of the involved processes may create or remove a
+ link.
+ </p>
<p>Links are used to monitor the behaviour of other processes, see
- <seealso marker="#errors">Error Handling</seealso>.</p>
+ <seeguide marker="#errors">Error Handling</seeguide>.</p>
</section>
<section>
@@ -144,41 +169,203 @@ spawn(Module, Name, Args) -> pid()
program structures where some processes are supervising other
processes, for example, restarting them if they terminate
abnormally.</p>
- <p>See <seealso marker=
- "doc/design_principles:des_princ#otp design principles">
- OTP Design Principles</seealso> for more information about
+ <p>See <seeguide marker="system/design_principles:des_princ#otp design principles">
+ OTP Design Principles</seeguide> for more information about
OTP supervision trees, which use this feature.</p>
<section>
- <title>Emitting Exit Signals</title>
- <p>When a process terminates, it terminates with an
- <em>exit reason</em> as explained in <seealso marker="#term">
- Process Termination</seealso>. This exit reason is emitted in
- an <em>exit signal</em> to all linked processes.</p>
- <p>A process can also call the function <c>exit(Pid,Reason)</c>.
- This results in an exit signal with exit reason
- <c>Reason</c> being emitted to <c>Pid</c>, but does not affect
- the calling process.</p>
+ <marker id="sending_exit_signals"/>
+ <title>Sending Exit Signals</title>
+ <p>
+ When a process or port
+ <seeguide marker="#term">terminates</seeguide> it will
+ send exit signals to all processes and ports that it
+ is <seeguide marker="#links">linked</seeguide> to.
+ The exit signal will contain the following information:
+ </p>
+ <taglist>
+ <tag>Sender identifier</tag>
+ <item><p>
+ The process or port identifier of the process or port
+ that terminated.
+ </p></item>
+ <tag>Receiver identifier</tag>
+ <item><p>
+ The process or port identifier of the process or port
+ which the exit signal is sent to.
+ </p></item>
+ <tag>The <c>link</c> flag</tag>
+ <item><p>
+ This flag will be set indicating that the exit signal
+ was sent due to a link.
+ </p></item>
+ <tag><marker id="link_exit_signal_reason"/>Exit reason</tag>
+ <item><p>
+ The exit reason of the process or port that
+ terminated or the atom:</p>
+ <list>
+ <item><p>
+ <c>noproc</c> in case no process or port was
+ found when setting up a link in a preceeding
+ call to the
+ <seemfa marker="erts:erlang#link/1"><c>link(PidOrPort)</c></seemfa>
+ BIF. The process or port identified as sender
+ of the exit signal will equal the <c>PidOrPort</c>
+ argument passed to <c>link/1</c>.
+ </p></item>
+ <item><p>
+ <c>noconnection</c> in case the linked
+ processes resides on different nodes and
+ the connection between the nodes was lost or
+ could not be established. The process or port
+ identified as sender of the exit signal might
+ in this case still be alive.
+ </p></item>
+ </list>
+ </item>
+ </taglist>
+
+ <p>
+ Exit signals can also be sent explicitly by calling the
+ <seemfa marker="erts:erlang#exit/2"><c>exit(PidOrPort,
+ Reason)</c></seemfa> BIF. The exit signal is sent to the
+ process or port identified by the <c>PidOrPort</c> argument.
+ The exit signal sent will contain the following information:
+ </p>
+ <taglist>
+ <tag>Sender identifier</tag>
+ <item><p>
+ The process identifier of the process that called
+ <c>exit/2</c>.
+ </p></item>
+ <tag>Receiver identifier</tag>
+ <item><p>
+ The process or port identifier of the process or port
+ which the exit signal is sent to.
+ </p></item>
+ <tag>The <c>link</c> flag</tag>
+ <item><p>
+ This flag will not be set, indicating that this exit
+ signal was not sent due to a link.
+ </p></item>
+ <tag>Exit reason</tag>
+ <item><p>
+ The term passed as <c>Reason</c> in the call to
+ <c>exit/2</c>. If <c>Reason</c> is the atom <c>kill</c>,
+ the receiver cannot
+ <seeerl marker="erts:erlang#process_flag_trap_exit">trap
+ the exit</seeerl> signal and will unconditionally
+ terminate when it receives the signal.
+ </p></item>
+ </taglist>
</section>
<section>
+ <marker id="receiving_exit_signals"/>
<title>Receiving Exit Signals</title>
- <p>The default behaviour when a process receives an exit signal
- with an exit reason other than <c>normal</c>, is to terminate
- and in turn emit exit signals with the same exit reason to its
- linked processes. An exit signal with reason <c>normal</c> is
- ignored.</p>
- <p>A process can be set to trap exit signals by calling:</p>
- <pre>
-process_flag(trap_exit, true)</pre>
- <p>When a process is trapping exits, it does not terminate when
- an exit signal is received. Instead, the signal is transformed
- into a message <c>{'EXIT',FromPid,Reason}</c>, which is put into
- the mailbox of the process, just like a regular message.</p>
- <p>An exception to the above is if the exit reason is <c>kill</c>,
- that is if <c>exit(Pid,kill)</c> has been called. This
- unconditionally terminates the process, regardless of if it is
- trapping exit signals.</p>
+
+ <p>What happens when a process receives an exit signal depends on:</p>
+ <list>
+ <item><p>
+ The <seeerl marker="erts:erlang#process_flag_trap_exit">trap exit</seeerl>
+ state of the receiver at the time when the exit signal is received.
+ </p></item>
+ <item><p>
+ The exit reason of the exit signal.
+ </p></item>
+ <item><p>
+ The sender of the exit signal.
+ </p></item>
+ <item><p>
+ The state of the <c>link</c> flag of the exit signal. If the
+ <c>link</c> flag is set, the exit signal was sent due to a
+ link; otherwise, the exit signal was sent by a call to the
+ <seemfa marker="erts:erlang#exit/2"><c>exit/2</c></seemfa> BIF.
+ </p></item>
+ <item><p>
+ If the <c>link</c> flag is set, what happens also depends on
+ whether the <seemfa marker="erts:erlang#unlink/1">link is still active
+ or not</seemfa> when the exit signal is received.
+ </p></item>
+ </list>
+
+ <p>
+ Based on the above states, the following will happen when an
+ exit signal is received by a process:
+ </p>
+ <list>
+ <item>
+ <p>The exit signal is silently dropped if:</p>
+ <list>
+ <item><p>
+ the <c>link</c> flag of the exit signal is set and
+ the corresponding link has been deactivated.
+ </p></item>
+ <item><p>
+ the exit reason of the exit signal is the atom <c>normal</c>,
+ the receiver is not trapping exits, and the receiver and
+ sender are not the same process.
+ </p></item>
+ </list>
+ </item>
+ <item>
+ <p>The receiving process is terminated if:</p>
+ <list>
+ <item><p>
+ the <c>link</c> flag of the exit signal
+ is not set, and the exit reason of the exit signal
+ is the atom <c>kill</c>. The receiving process will
+ terminate with the atom <c>killed</c> as exit reason.
+ </p></item>
+ <item><p>
+ the receiver is not trapping exits, and the exit
+ reason is something other than the atom <c>normal</c>.
+ Also, if the <c>link</c> flag of the exit signal
+ is set, the link also needs to be active otherwise the
+ exit signal will be dropped. The exit reason of the
+ receiving process will equal the exit reason of the
+ exit signal. Note that if the <c>link</c> flag
+ is set, an exit reason of <c>kill</c> will <em>not</em>
+ be converted to <c>killed</c>.
+ </p></item>
+ <item><p>
+ the exit reason of the exit signal is the atom
+ <c>normal</c> and the sender of the exit signal is
+ the same process as the receiver. The <c>link</c>
+ flag cannot be set in this case. The exit reason
+ of the receiving process will be the atom <c>normal</c>.
+ </p></item>
+ </list>
+ </item>
+ <item>
+ <p>
+ The exit signal is converted to a message signal and
+ moved into the message queue of the receiver, if the
+ receiver is trapping exits, the <c>link</c> flag
+ of the exit signal is:
+ </p>
+ <list>
+ <item><p>
+ not set, and the exit reason of the signal is not
+ the atom <c>kill</c>.
+ </p></item>
+ <item><p>
+ set, and the corresponding link is active.
+ Note that an exit reason of <c>kill</c> will
+ <em>not</em> terminate the process in this
+ case and it will not be converted to
+ <c>killed</c>.
+ </p></item>
+ </list>
+ <p>
+ The converted message will be on the form
+ <c>{'EXIT', SenderID, Reason}</c> where <c>Reason</c>
+ equals the exit reason of the exit signal and
+ <c>SenderID</c> is the identifier of the process
+ or port that sent the exit signal.
+ </p>
+ </item>
+ </list>
</section>
</section>
@@ -217,4 +404,3 @@ erase(Key)
erase()</pre>
</section>
</chapter>
-
diff --git a/system/doc/reference_manual/records.xml b/system/doc/reference_manual/records.xml
index 6b26e2c242..6b5ed82b99 100644
--- a/system/doc/reference_manual/records.xml
+++ b/system/doc/reference_manual/records.xml
@@ -34,11 +34,11 @@
Record expressions are translated to tuple expressions during
compilation. Therefore, record expressions are not understood by
the shell unless special actions are taken. For details, see the
- <seealso marker="stdlib:shell">shell(3)</seealso>
+ <seeerl marker="stdlib:shell">shell(3)</seeerl>
manual page in STDLIB.</p>
<p>More examples are provided in
- <seealso marker="doc/programming_examples:records">
- Programming Examples</seealso>.</p>
+ <seeguide marker="system/programming_examples:records">
+ Programming Examples</seeguide>.</p>
<section>
<title>Defining Records</title>
diff --git a/system/doc/reference_manual/typespec.xml b/system/doc/reference_manual/typespec.xml
index f517259a64..b46cf0e8e8 100644
--- a/system/doc/reference_manual/typespec.xml
+++ b/system/doc/reference_manual/typespec.xml
@@ -207,6 +207,7 @@
They can be thought as predefined aliases for the type unions also shown in
the table.
</p>
+ <marker id="builtin_types"/>
<table>
<row>
<cell><em>Built-in type</em></cell><cell><em>Defined as</em></cell>
@@ -335,8 +336,8 @@
| #Erlang_Atom{Fields}</pre>
<p>
Records are extended to possibly contain type information.
- This is described in <seealso marker="#typeinrecords">
- Type Information in Record Declarations</seealso>.
+ This is described in <seeguide marker="#typeinrecords">
+ Type Information in Record Declarations</seeguide>.
</p>
</section>
@@ -475,22 +476,20 @@
<c>-spec</c> attribute. The general format is as follows:
</p>
<pre>
- -spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.</pre>
- <p>
- The arity of the function must match the number of arguments,
- else a compilation error occurs.
- </p>
+ -spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.</pre>
<p>
- This form can also be used in header files (.hrl) to declare type
- information for exported functions.
- Then these header files can be included in files that (implicitly or
- explicitly) import these functions.
+ An implementation of the function with the same name
+ <c>Function</c> must exist in the current module, and the arity
+ of the function must match the number of arguments, else a
+ compilation error occurs.
</p>
<p>
- Within a given module, the following shorthand suffices in most cases:
+ The following longer format with module name is also valid as
+ long as <c>Module</c> is the name of the current module. This
+ can be useful for documentation purposes.
</p>
<pre>
- -spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.</pre>
+ -spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.</pre>
<p>
Also, for documentation purposes, argument names can be given:
</p>
diff --git a/system/doc/system_architecture_intro/Makefile b/system/doc/system_architecture_intro/Makefile
index 5a5ead1ecf..8657da0e2c 100644
--- a/system/doc/system_architecture_intro/Makefile
+++ b/system/doc/system_architecture_intro/Makefile
@@ -70,7 +70,9 @@ DVIPS_FLAGS +=
# Targets
# ----------------------------------------------------
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -89,13 +91,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
-# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
-# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
-release_spec:
-
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+release_spec:
diff --git a/system/doc/system_architecture_intro/sys_arch_intro.xml b/system/doc/system_architecture_intro/sys_arch_intro.xml
index e8ada6427b..f04e7a7879 100644
--- a/system/doc/system_architecture_intro/sys_arch_intro.xml
+++ b/system/doc/system_architecture_intro/sys_arch_intro.xml
@@ -93,7 +93,7 @@
<p>Database Management.</p>
<list type="bulleted">
<item><em>QLC</em> Query language support for Mnesia DBMS.</item>
- <item><em>Mnesia</em> A heavy duty real-time distributed database.</item>
+ <item><em>Mnesia</em> A heavy-duty real-time distributed database.</item>
<item><em>ODBC</em> ODBC database interface.</item>
</list>
</item>
diff --git a/system/doc/system_principles/Makefile b/system/doc/system_principles/Makefile
index db2001fad5..00b2203394 100644
--- a/system/doc/system_principles/Makefile
+++ b/system/doc/system_principles/Makefile
@@ -71,7 +71,9 @@ DVIPS_FLAGS +=
# Targets
# ----------------------------------------------------
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -90,12 +92,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
-# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
-# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
+ $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
-release_spec:
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+release_spec:
diff --git a/system/doc/system_principles/create_target.xmlsrc b/system/doc/system_principles/create_target.xmlsrc
index a69a02e40c..8ea4551630 100644
--- a/system/doc/system_principles/create_target.xmlsrc
+++ b/system/doc/system_principles/create_target.xmlsrc
@@ -67,8 +67,8 @@
module <c>target_system.erl</c>, which contains functions for
creating and installing a target system. This module is used in
the following examples. The source code of the module is listed
- in <seealso marker="#listing of target system">
- Listing of target_system.erl</seealso></p>
+ in <seeguide marker="#listing of target system">
+ Listing of target_system.erl</seeguide></p>
<section>
<marker id="create"/>
@@ -76,7 +76,7 @@
<p>It is assumed that you have a working Erlang/OTP system structured
according to the OTP design principles.</p>
<p><em>Step 1.</em> Create a <c>.rel</c> file (see the
- <seealso marker="sasl:rel">rel(4)</seealso> manual page in
+ <seefile marker="sasl:rel">rel(4)</seefile> manual page in
SASL), which specifies the ERTS version and lists
all applications that are to be included in the new basic target
system. An example is the following <c>mysystem.rel</c> file:</p>
@@ -298,7 +298,7 @@ os> <input>/usr/local/erl-target/bin/erl -boot /usr/local/erl-target/releases/FI
{sasl, "2.4"},
{pea, "2.0"}]}.</code>
<p><em>Step 2.</em> Create the application upgrade file (see the
- <seealso marker="sasl:appup">appup(4)</seealso> manual page in
+ <seefile marker="sasl:appup">appup(4)</seefile> manual page in
SASL) for Pea, for example:</p>
<code type="none">
%% pea.appup
@@ -311,7 +311,7 @@ os> <input>/usr/local/erl-target/bin/erl -boot /usr/local/erl-target/releases/FI
<pre>
os> <input>erl -pa /home/user/target_system/myapps/pea-2.0/ebin</input></pre>
<p><em>Step 4.</em> Create the release upgrade file (see the
- <seealso marker="sasl:relup">relup(4)</seealso> manual page in
+ <seefile marker="sasl:relup">relup(4)</seefile> manual page in
SASL):</p>
<pre>
1> <input>systools:make_relup("mysystem2",["mysystem"],["mysystem"],
@@ -337,8 +337,8 @@ os> <input>erl -pa /home/user/target_system/myapps/pea-2.0/ebin</input></pre>
<p>This part is done on the target node, and for this example we
want the node to be running as an embedded system with the
<c>-heart</c> option, allowing automatic restart of the node.
- For more information, see <seealso marker="#start">
- Starting a Target System</seealso>.</p>
+ For more information, see <seeguide marker="#start">
+ Starting a Target System</seeguide>.</p>
<p>We add <c>-heart</c> to <c>bin/start</c>:</p>
<code type="none">
#!/bin/sh
@@ -393,8 +393,8 @@ heart: Tue Apr 1 12:15:11 2014: Executed "/usr/local/erl-target/bin/start /usr/
<c>heart</c>. This is always done when the upgrade involves
a change of the applications ERTS, Kernel,
STDLIB, or SASL. For more information, see
- <seealso marker="upgrade">
- Upgrade when Erlang/OTP has Changed</seealso>.
+ <seeguide marker="upgrade">
+ Upgrade when Erlang/OTP has Changed</seeguide>.
</p>
<p>
The node is accessible through a new pipe:
diff --git a/system/doc/system_principles/error_logging.xml b/system/doc/system_principles/error_logging.xml
index 9cbf7a2e94..4844cf2002 100644
--- a/system/doc/system_principles/error_logging.xml
+++ b/system/doc/system_principles/error_logging.xml
@@ -43,13 +43,13 @@ Error in process <0.27.0> with exit value: {{badmatch,[1,2,3]},[{m,f,1},{shell,e
the Kernel application.</p>
<p>The exit reasons (such as <c>badarg</c>) used by
the runtime system are described in
- <seealso marker="doc/reference_manual:errors#exit_reasons">
- Errors and Error Handling</seealso>.</p>
+ <seeguide marker="system/reference_manual:errors#exit_reasons">
+ Errors and Error Handling</seeguide>.</p>
<p>For information about Logger and its user
interface, see the
- <seealso marker="kernel:logger">logger(3)</seealso>
+ <seeerl marker="kernel:logger">logger(3)</seeerl>
manual page and
- the <seealso marker="kernel:logger_chapter">Logging</seealso>
+ the <seeguide marker="kernel:logger_chapter">Logging</seeguide>
section in the Kernel User's Guide. The system can be configured so that
log events are
written to file or to tty, or both. In addition, user-defined
@@ -72,10 +72,10 @@ Error in process <0.27.0> with exit value: {{badmatch,[1,2,3]},[{m,f,1},{shell,e
reports were only logged when the SASL application was
running. This behaviour can, for backwards compatibility, be
enabled by setting the Kernel configuration
- parameter <seealso marker="kernel:kernel_app#logger_sasl_compatible">
- <c>logger_sasl_compatible</c></seealso>
+ parameter <seeapp marker="kernel:kernel_app#logger_sasl_compatible">
+ <c>logger_sasl_compatible</c></seeapp>
to <c>true</c>. For more information, see
- <seealso marker="sasl:error_logging">SASL Error Logging</seealso>
+ <seeguide marker="sasl:error_logging">SASL Error Logging</seeguide>
in the SASL User's Guide.</p>
<pre>
% <input>erl -kernel logger_level info</input>
diff --git a/system/doc/system_principles/misc.xml b/system/doc/system_principles/misc.xml
index 418f783399..0276da9c08 100644
--- a/system/doc/system_principles/misc.xml
+++ b/system/doc/system_principles/misc.xml
@@ -46,7 +46,7 @@
<title>Supported Releases</title>
<p>
In general, bugs are only fixed on the latest
- <seealso marker="versions#releases_and_patches">release</seealso>,
+ <seeguide marker="versions#releases_and_patches">release</seeguide>,
and new features are introduced in the upcoming release that is
under development. However, when we, due to internal reasons, fix
bugs on older releases, these will be available and announced as well.
@@ -56,7 +56,7 @@
<c>maint</c> and the <c>master</c> branches in our
<url href="https://github.com/erlang/otp">git repository</url>.
The <c>maint</c> branch contains changes planned for the next
- <seealso marker="versions#releases_and_patches">maintenance patch package</seealso>
+ <seeguide marker="versions#releases_and_patches">maintenance patch package</seeguide>
on the latest OTP release and the <c>master</c> branch contain
changes planned for the upcoming OTP release.
</p>
@@ -101,7 +101,7 @@
checksums in order to determine if it can load the code
or not. Note that HiPE has some limitations. For more
information see the documentation of the
- <seealso marker="hipe:HiPE_app">HiPE</seealso> application.
+ <seeapp marker="hipe:HiPE_app">HiPE</seeapp> application.
</p>
</item>
<tag>APIs</tag>
diff --git a/system/doc/system_principles/system_principles.xml b/system/doc/system_principles/system_principles.xml
index c66ae786c2..d0c6f59807 100644
--- a/system/doc/system_principles/system_principles.xml
+++ b/system/doc/system_principles/system_principles.xml
@@ -41,19 +41,19 @@ Erlang/OTP 17 [erts-6.0] [hipe] [smp:8:8]
Eshell V6.0 (abort with ^G)
1> </pre>
<p><c>erl</c> understands a number of command-line arguments, see
- the <seealso marker="erts:erl">erl(1)</seealso> manual page in
+ the <seecom marker="erts:erl">erl(1)</seecom> manual page in
ERTS. Some of them are also described in this chapter.</p>
<p>Application programs can access the values of the command-line
arguments by calling the function <c>init:get_argument(Key)</c>
or <c>init:get_arguments()</c>. See the
- <seealso marker="erts:init">init(3)</seealso> manual page in
+ <seeerl marker="erts:init">init(3)</seeerl> manual page in
ERTS.</p>
</section>
<section>
<title>Restarting and Stopping the System</title>
<p>The runtime system is halted by calling <c>halt/0,1</c>. For
- details, see the <seealso marker="erts:erlang">erlang(3)</seealso>
+ details, see the <seeerl marker="erts:erlang">erlang(3)</seeerl>
manual page in ERTS.</p>
<p>The module <c>init</c> contains functions for restarting,
rebooting, and stopping the runtime system:</p>
@@ -61,7 +61,7 @@ Eshell V6.0 (abort with ^G)
init:restart()
init:reboot()
init:stop()</pre>
- <p>For details, see the <seealso marker="erts:init">init(3)</seealso>
+ <p>For details, see the <seeerl marker="erts:init">init(3)</seeerl>
manual page in ERTS.</p>
<p>The runtime system terminates if the Erlang shell is
terminated.</p>
@@ -82,8 +82,8 @@ init:stop()</pre>
<pre>
% <input>erl -boot start_all</input></pre>
<p>If no boot script is specified, it defaults to
- <c>ROOT/bin/start</c>, see <seealso marker="#default_boot_scripts">
- Default Boot Scripts</seealso>.</p>
+ <c>ROOT/bin/start</c>, see <seeguide marker="#default_boot_scripts">
+ Default Boot Scripts</seeguide>.</p>
<p>The command-line flag <c>-init_debug</c> makes the <c>init</c>
process write some debug information while interpreting the boot
script:</p>
@@ -128,8 +128,8 @@ init:stop()</pre>
<title>User-Defined Boot Scripts</title>
<p>It is sometimes useful or necessary to create a user-defined
boot script. This is true especially when running Erlang in
- embedded mode, see <seealso marker="#code_loading">
- Code Loading Strategy</seealso>.</p>
+ embedded mode, see <seeguide marker="#code_loading">
+ Code Loading Strategy</seeguide>.</p>
<p>A boot script can be written manually. However, it is
recommended to create a boot script by generating it from a
release resource file <c>Name.rel</c>, using the function
@@ -138,9 +138,9 @@ init:stop()</pre>
principles. (The program does not have to be started in terms of
OTP applications, but can be plain Erlang).</p>
<p>For more information about <c>.rel</c> files, see
- <seealso marker="doc/design_principles:release_handling">
- OTP Design Principles</seealso> and the
- <seealso marker="sasl:rel">rel(4)</seealso> manual page in
+ <seeguide marker="system/design_principles:release_handling">
+ OTP Design Principles</seeguide> and the
+ <seefile marker="sasl:rel">rel(4)</seefile> manual page in
SASL.</p>
<p>The binary boot script file <c>Name.boot</c> is generated from
the boot script file <c>Name.script</c>, using the function
@@ -201,35 +201,35 @@ init:stop()</pre>
<cell align="left" valign="middle">Module</cell>
<cell align="left" valign="middle"><c>.erl</c></cell>
<cell align="left" valign="middle">
- <seealso marker="doc/reference_manual:modules">
- Erlang Reference Manual</seealso></cell>
+ <seeguide marker="system/reference_manual:modules">
+ Erlang Reference Manual</seeguide></cell>
</row>
<row>
<cell align="left" valign="middle">Include file</cell>
<cell align="left" valign="middle"><c>.hrl</c></cell>
<cell align="left" valign="middle">
- <seealso marker="doc/reference_manual:modules">
- Erlang Reference Manual</seealso></cell>
+ <seeguide marker="system/reference_manual:modules">
+ Erlang Reference Manual</seeguide></cell>
</row>
<row>
<cell align="left" valign="middle">Release resource file</cell>
<cell align="left" valign="middle"><c>.rel</c></cell>
<cell align="left" valign="middle">
- <seealso marker="sasl:rel">rel(4)</seealso>
+ <seefile marker="sasl:rel">rel(4)</seefile>
manual page in SASL</cell>
</row>
<row>
<cell align="left" valign="middle">Application resource file</cell>
<cell align="left" valign="middle"><c>.app</c></cell>
<cell align="left" valign="middle">
- <seealso marker="kernel:app">app(4)</seealso>
+ <seefile marker="kernel:app">app(4)</seefile>
manual page in Kernel</cell>
</row>
<row>
<cell align="left" valign="middle">Boot script</cell>
<cell align="left" valign="middle"><c>.script</c></cell>
<cell align="left" valign="middle">
- <seealso marker="sasl:script">script(4)</seealso>
+ <seefile marker="sasl:script">script(4)</seefile>
manual page in SASL</cell>
</row>
<row>
@@ -241,21 +241,21 @@ init:stop()</pre>
<cell align="left" valign="middle">Configuration file</cell>
<cell align="left" valign="middle"><c>.config</c></cell>
<cell align="left" valign="middle">
- <seealso marker="kernel:config">config(4)</seealso>
+ <seefile marker="kernel:config">config(4)</seefile>
manual page in Kernel</cell>
</row>
<row>
<cell align="left" valign="middle">Application upgrade file</cell>
<cell align="left" valign="middle"><c>.appup</c></cell>
<cell align="left" valign="middle">
- <seealso marker="sasl:appup">appup(4)</seealso>
+ <seefile marker="sasl:appup">appup(4)</seefile>
manual page in SASL</cell>
</row>
<row>
<cell align="left" valign="middle">Release upgrade file</cell>
<cell align="left" valign="middle"><c>relup</c></cell>
<cell align="left" valign="middle">
- <seealso marker="sasl:relup">relup(4)</seealso>
+ <seefile marker="sasl:relup">relup(4)</seefile>
manual page in SASL</cell>
</row>
<tcaption>File Types</tcaption>
diff --git a/system/doc/system_principles/upgrade.xml b/system/doc/system_principles/upgrade.xml
index c491bd855e..1a905befdf 100644
--- a/system/doc/system_principles/upgrade.xml
+++ b/system/doc/system_principles/upgrade.xml
@@ -83,10 +83,10 @@
<c>restart_emulator</c> has no effect as the
<c>release_handler</c> will not execute them.</p>
<p>For information about the release upgrade file, see the
- <seealso marker="sasl:relup">relup(4)</seealso> manual page
+ <seefile marker="sasl:relup">relup(4)</seefile> manual page
in SASL.
For more information about upgrade instructions, see the
- <seealso marker="sasl:appup">appup(4)</seealso> manual page
+ <seefile marker="sasl:appup">appup(4)</seefile> manual page
in SASL.</p>
</section>
diff --git a/system/doc/system_principles/versions.xml b/system/doc/system_principles/versions.xml
index fbdcc6b2b0..c5efc1a6c0 100644
--- a/system/doc/system_principles/versions.xml
+++ b/system/doc/system_principles/versions.xml
@@ -39,7 +39,7 @@
<p>As of OTP release 17, the OTP release number corresponds to
the major part of the OTP version. The OTP version as a concept was
introduced in OTP 17. The version scheme used is described in detail in
- <seealso marker="#version_scheme">Version Scheme</seealso>.</p>
+ <seeguide marker="#version_scheme">Version Scheme</seeguide>.</p>
<p>OTP of a specific version is a set of applications of specific
versions. The application versions identified by an OTP version
corresponds to application versions that have been tested together
@@ -57,15 +57,15 @@
<p>In an OTP source code tree, the OTP version can be read from
the text file <c>&lt;OTP source root&gt;/OTP_VERSION</c>. The
absolute path to the file can be constructed by calling
- <c>filename:join([</c><seealso marker="kernel:code#root_dir/0"><c>code:root_dir()</c></seealso><c>, "OTP_VERSION"])</c>.</p>
+ <c>filename:join([</c><seemfa marker="kernel:code#root_dir/0"><c>code:root_dir()</c></seemfa><c>, "OTP_VERSION"])</c>.</p>
<p>In an installed OTP development system, the OTP version can be read
from the text file <c>&lt;OTP installation root&gt;/releases/&lt;OTP release number&gt;/OTP_VERSION</c>.
The absolute path to the file can by constructed by calling
- <c>filename:join([</c><seealso marker="kernel:code#root_dir/0"><c>code:root_dir()</c></seealso><c>, "releases", </c><seealso marker="erts:erlang#system_info_otp_release"><c>erlang:system_info(otp_release)</c></seealso><c>, "OTP_VERSION"]).</c></p>
+ <c>filename:join([</c><seemfa marker="kernel:code#root_dir/0"><c>code:root_dir()</c></seemfa><c>, "releases", </c><seeerl marker="erts:erlang#system_info_otp_release"><c>erlang:system_info(otp_release)</c></seeerl><c>, "OTP_VERSION"]).</c></p>
<p>If the version read from the <c>OTP_VERSION</c> file in a
development system has a <c>**</c> suffix, the system has been
patched using the
- <seealso marker="../installation_guide/OTP-PATCH-APPLY"><c>otp_patch_apply</c></seealso>
+ <seeguide marker="system/installation_guide:OTP-PATCH-APPLY"><c>otp_patch_apply</c></seeguide>
tool. In this case, the system consists of application
versions from multiple OTP versions. The version preceding the <c>**</c>
suffix corresponds to the OTP version of the base system that
@@ -73,7 +73,7 @@
other means than <c>otp_patch_apply</c>, the file <c>OTP_VERSION</c>
can identify an incorrect OTP version.</p>
<p>No <c>OTP_VERSION</c> file is placed in a
- <seealso marker="create_target">target system</seealso> created
+ <seeguide marker="create_target">target system</seeguide> created
by OTP tools. This since one easily can create a target system
where it is hard to even determine the base OTP version. You can,
however, place such a file there if you know the OTP version.</p>
@@ -126,7 +126,7 @@
<section>
<title>Application Version</title>
<p>As of OTP 17.0 application versions use the same
- <seealso marker="#version_scheme">version scheme</seealso> as the
+ <seeguide marker="#version_scheme">version scheme</seeguide> as the
OTP version. Application versions part of a release candidate will
however not have an <c>-rc&lt;N&gt;</c> suffix as the OTP version.
Also note that a major increment in an application version does not
@@ -140,8 +140,8 @@
<title>Version Scheme</title>
<note><p>The version scheme was changed as of OTP 17.0. This implies
that application versions used prior to OTP 17.0 do not adhere to this
- version scheme. <seealso marker="#otp_17_0_app_versions">A list of
- application versions used in OTP 17.0</seealso> is included at the
+ version scheme. <seeguide marker="#otp_17_0_app_versions">A list of
+ application versions used in OTP 17.0</seeguide> is included at the
end of this section</p></note>
<p>In the normal case, a version is constructed as
<c>&lt;Major&gt;.&lt;Minor&gt;.&lt;Patch&gt;</c>,
@@ -234,7 +234,7 @@
OTP versions are based on the actual changes in the code
and not based on whether the patch was planned or not.
For more information see the
- <seealso marker="#version_scheme">Version Scheme</seealso>
+ <seeguide marker="#version_scheme">Version Scheme</seeguide>
section above.
</p>
</section>
@@ -251,9 +251,9 @@
more than the version numbers are needed in order to construct
the tree. The root of the tree is OTP version 17.0 which is when
we introduced the new
- <seealso marker="#version_scheme">version scheme</seealso>. The
+ <seeguide marker="#version_scheme">version scheme</seeguide>. The
green versions are normal versions released on the main track.
- Old <seealso marker="#releases_and_patches">OTP releases</seealso>
+ Old <seeguide marker="#releases_and_patches">OTP releases</seeguide>
will be maintained for a while on <c>maint</c> branches that have
branched off from the main track. Old <c>maint</c> branches always
branch off from the main track when the next OTP release is
diff --git a/system/doc/top/Makefile b/system/doc/top/Makefile
index 3c1c0a0fa2..3ebee96c08 100644
--- a/system/doc/top/Makefile
+++ b/system/doc/top/Makefile
@@ -58,6 +58,7 @@ XML_FILES = \
XML_GUIDE_FILES = \
$(GENERAL_INFO_CHAPTER_FILES:%=general_info/%) \
+ $(GENERAL_INFO_CHAPTER_GEN_FILES:%=general_info/%) \
$(INST_GUIDE_CHAPTER_FILES:%=installation_guide/%) \
$(INST_GUIDE_CHAPTER_GEN_FILES:%=installation_guide/%) \
$(SYSTEM_PRINCIPLES_CHAPTER_FILES:%=system_principles/%) \
@@ -249,8 +250,9 @@ DVIPS_FLAGS +=
# Targets
# ----------------------------------------------------
+DOC_TARGETS?=pdf html man
-docs: pdf html $(INFO_FILES)
+docs: $(DOC_TARGETS) $(INFO_FILES)
local_docs: PDFREFDIR=../pdf
@@ -258,10 +260,11 @@ $(TOP_PDF_FILE): $(XML_FILES)
pdf: $(TOP_PDF_FILE)
-html: $(INDEX_FILES) \
- $(MAN_INDEX) $(JAVASCRIPT)
+html: $(INDEX_FILES) $(JAVASCRIPT)
-debug opt:
+man: $(MAN_INDEX)
+
+debug opt:
clean:
$(RM) ../html/js/*.js
@@ -280,21 +283,28 @@ include $(ERL_TOP)/make/otp_release_targets.mk
$(RELSYSDIR)/temporary:
$(INSTALL_DIR) $(RELSYSDIR)/temporary
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELEASE_PATH)"
- $(INSTALL_DATA) $(INFO_FILES) "$(RELEASE_PATH)"
- $(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DIR) $(RELSYSDIR)/pdf
- $(INSTALL_DATA) \
- $(TOP_PDF_FILE) $(RELSYSDIR)/pdf
+$(RELSYSDIR)/docbuild:
+ $(INSTALL_DIR) $(RELSYSDIR)/docbuild
+
+release_man_spec: man $(RELSYSDIR)/docbuild
+ $(INSTALL_DATA) $(MAN_INDEX) $(RELSYSDIR)
+ $(INSTALL_DATA) $(MAN_INDEX_SRC) $(MAN_INDEX_SCRIPT) $(RELSYSDIR)/docbuild
+
+release_html_spec: html $(RELSYSDIR)/docbuild
$(INSTALL_DIR) $(RELSYSDIR)/js
$(INSTALL_DATA) $(JAVASCRIPT) $(RELSYSDIR)/js
- $(INSTALL_DATA) $(INDEX_FILES) $(MAN_INDEX) $(RELSYSDIR)
- $(INSTALL_DIR) $(RELSYSDIR)/docbuild
- $(INSTALL_DATA) $(INDEX_SCRIPT) $(MAN_INDEX_SCRIPT) $(JAVASCRIPT_BUILD_SCRIPT) \
- $(INDEX_SRC) $(MAN_INDEX_SRC) $(JAVASCRIPT_BUILD_SCRIPT_SRC) \
+ $(INSTALL_DATA) $(INDEX_FILES) $(RELSYSDIR)
+ $(INSTALL_DATA) $(INDEX_SCRIPT) $(JAVASCRIPT_BUILD_SCRIPT) \
+ $(INDEX_SRC) $(JAVASCRIPT_BUILD_SCRIPT_SRC) \
$(TEMPLATES) $(RELSYSDIR)/docbuild
- $(RM) -r $(RELSYSDIR)/temporary
+release_pdf_spec: pdf
+ $(INSTALL_DIR) $(RELSYSDIR)/pdf
+ $(INSTALL_DATA) \
+ $(TOP_PDF_FILE) $(RELSYSDIR)/pdf
+
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec) $(INFO_FILES)
+ $(INSTALL_DATA) $(INFO_FILES) "$(RELEASE_PATH)"
+ $(RM) -r $(RELSYSDIR)/temporary
release_spec:
diff --git a/system/doc/top/print.html b/system/doc/top/print.html
index b562d0e9bc..e65ac491a9 100644
--- a/system/doc/top/print.html
+++ b/system/doc/top/print.html
@@ -19,7 +19,7 @@
<center>
<a href="http://www.ericsson.com/technology/opensource/erlang">
- <img alt="Ericsson AB" BORDER=0 SRC="pics/min_head.gif">
+ <img alt="Ericsson AB" BORDER=0>
</a>
<br>
diff --git a/system/doc/top/templates/index.html.src b/system/doc/top/templates/index.html.src
index 5507d4dfe4..ab75ed0478 100644
--- a/system/doc/top/templates/index.html.src
+++ b/system/doc/top/templates/index.html.src
@@ -33,7 +33,7 @@ limitations under the License.
<div id="container">
<div id="leftnav">
-<div class="innertube">
+<div class="leftnav-tube">
<div class="erlang-logo-wrapper">
<img alt="Erlang logo" src="erlang-logo.png" class="erlang-logo"/ >
</div>
diff --git a/system/doc/tutorial/Makefile b/system/doc/tutorial/Makefile
index dbef39b91c..faf06b3878 100644
--- a/system/doc/tutorial/Makefile
+++ b/system/doc/tutorial/Makefile
@@ -97,7 +97,9 @@ DVIPS_FLAGS +=
$(HTMLDIR)/%.gif: %.gif
$(CP) $< $@
-docs: html
+DOC_TARGETS?=html
+
+docs: $(DOC_TARGETS)
local_docs: PDFDIR=../../pdf
@@ -118,11 +120,11 @@ clean clean_docs:
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_docs_spec: docs
-# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
-# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
- $(INSTALL_DIR) $(RELSYSDIR)
+release_html_spec: html
+ $(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
+release_docs_spec: $(DOC_TARGETS:%=release_%_spec)
+
release_spec:
diff --git a/system/doc/tutorial/c_port.xmlsrc b/system/doc/tutorial/c_port.xmlsrc
index ff0997fb54..e9a85b2f1d 100644
--- a/system/doc/tutorial/c_port.xmlsrc
+++ b/system/doc/tutorial/c_port.xmlsrc
@@ -30,7 +30,7 @@
<file>c_port.xml</file>
</header>
<p>This section outlines an example of how to solve the example
- problem in the <seealso marker="example">previous section</seealso>
+ problem in the <seeguide marker="example">previous section</seeguide>
by using a port.</p>
<p>The scenario is illustrated in the following figure:</p>
<image file="../tutorial/port.gif">
diff --git a/system/doc/tutorial/c_portdriver.xmlsrc b/system/doc/tutorial/c_portdriver.xmlsrc
index c4cf6daa3b..47bf34d3d5 100644
--- a/system/doc/tutorial/c_portdriver.xmlsrc
+++ b/system/doc/tutorial/c_portdriver.xmlsrc
@@ -30,7 +30,7 @@
<file>c_portdriver.xml</file>
</header>
<p>This section outlines an example of how to solve the example problem
- in <seealso marker="example">Problem Example</seealso>
+ in <seeguide marker="example">Problem Example</seeguide>
by using a linked-in port driver.</p>
<p>A port driver is a linked-in driver that is accessible as a port
from an Erlang program. It is a shared library (SO in UNIX, DLL in
diff --git a/system/doc/tutorial/cnode.xmlsrc b/system/doc/tutorial/cnode.xmlsrc
index f1f0e976ad..bbea3e129a 100644
--- a/system/doc/tutorial/cnode.xmlsrc
+++ b/system/doc/tutorial/cnode.xmlsrc
@@ -29,8 +29,25 @@
<rev></rev>
<file>cnode.xml</file>
</header>
+ <p>The reader is referred to
+ <seeguide marker="erl_interface:ei_users_guide">
+ the erl_interface users guide</seeguide> for information about how
+ to create C nodes.</p>
+ <!-- Commented out because it uses the removed erl_*
+ functions. Should be updated to use ei_* functions instead.
+
+
+
+ <note><p>This tutorial uses the <c>erl_*</c> functions which have
+ been removed as of OTP version 23. The <c>erl_*</c> functions are
+ replaced by the <c>ei_*</c> functions in the erl_interface
+ application. The reader is therefore referred to
+ <seeguide marker="erl_interface:ei_users_guide">
+ the erl_interface users guide</seeguide> for information about how
+ to create C nodes until this tutorial has got updated.</p></note>
+
<p>This section outlines an example of how to solve the example
- problem in <seealso marker="example">Problem Example</seealso>
+ problem in <seeguide marker="example">Problem Example</seeguide>
by using a C node. Notice that a C node is not typically
used for solving simple problems like this, a port is
sufficient.</p>
@@ -113,8 +130,8 @@ fd = erl_connect("e1@idril");</pre>
(call <c>bind()</c> and <c>listen()</c>) listening to a
certain port number <c>port</c>. It then publishes its name
and port number with <c>epmd</c>, the Erlang port mapper
- daemon. For details, see the <seealso
- marker="erts:epmd">epmd</seealso> manual page in ERTS:</p>
+ daemon. For details, see the <seecom
+ marker="erts:epmd">epmd</seecom> manual page in ERTS:</p>
<pre>
erl_publish(port);</pre>
<p>Now the C node server can accept connections from Erlang nodes:</p>
@@ -273,6 +290,7 @@ Eshell V4.9.1.2 (abort with ^G)
4
(e1@idril.du.uab.ericsson.se)2> <input>complex4:bar(5).</input>
10</pre>
- </section>
+</section>
+-->
</chapter>
diff --git a/system/doc/tutorial/distribution.xml b/system/doc/tutorial/distribution.xml
index b489410841..b337dc59b1 100644
--- a/system/doc/tutorial/distribution.xml
+++ b/system/doc/tutorial/distribution.xml
@@ -59,7 +59,7 @@
<item>global_group - Grouping nodes to global name registration groups.</item>
<item>net_adm - Various net administration routines.</item>
<item>net_kernel - Networking kernel.</item>
- <item>pg2 - Distributed named process groups.</item>
+ <item>pg - Distributed named process groups.</item>
<item>pool - Load distribution facility.</item>
<item>slave - Functions for starting and controlling slave nodes.</item>
</list>
diff --git a/system/doc/tutorial/ei.c b/system/doc/tutorial/ei.c
index c33e3fb78e..8db226b462 100644
--- a/system/doc/tutorial/ei.c
+++ b/system/doc/tutorial/ei.c
@@ -1,38 +1,57 @@
/* ei.c */
-#include "erl_interface.h"
#include "ei.h"
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
typedef unsigned char byte;
-int main() {
- ETERM *tuplep, *intp;
- ETERM *fnp, *argp;
- int res;
- byte buf[100];
- long allocated, freed;
-
- erl_init(NULL, 0);
+int read_cmd(byte *buf);
+int write_cmd(byte *buf, int len);
+int foo(int x);
+int bar(int y);
- while (read_cmd(buf) > 0) {
- tuplep = erl_decode(buf);
- fnp = erl_element(1, tuplep);
- argp = erl_element(2, tuplep);
-
- if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) {
- res = foo(ERL_INT_VALUE(argp));
- } else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) {
- res = bar(ERL_INT_VALUE(argp));
- }
+static void fail(int place) {
+ fprintf(stderr, "Something went wrong %d\n", place);
+ exit(1);
+}
- intp = erl_mk_int(res);
- erl_encode(intp, buf);
- write_cmd(buf, erl_term_len(intp));
+int main() {
+ byte buf[100];
+ int index = 0;
+ int version = 0;
+ int arity = 0;
+ char atom[128];
+ long in = 0;
+ int res = 0;
+ ei_x_buff res_buf;
+ ei_init();
+ while (read_cmd(buf) > 0) {
+ if (ei_decode_version(buf, &index, &version) != 0)
+ fail(1);
+ if (ei_decode_tuple_header(buf, &index, &arity) != 0)
+ fail(2);
+ if (arity != 2)
+ fail(3);
+ if (ei_decode_atom(buf, &index, atom) != 0)
+ fail(4);
+ if (ei_decode_long(buf, &index, &in) != 0)
+ fail(5);
+ if (strncmp(atom, "foo", 3) == 0) {
+ res = foo((int)in);
+ } else if (strncmp(atom, "bar", 3) == 0) {
+ res = bar((int)in);
+ }
+ if (ei_x_new_with_version(&res_buf) != 0)
+ fail(6);
+ if (ei_x_encode_long(&res_buf, res) != 0)
+ fail(7);
+ write_cmd(res_buf.buff, res_buf.index);
- erl_free_compound(tuplep);
- erl_free_term(fnp);
- erl_free_term(argp);
- erl_free_term(intp);
- }
+ if (ei_x_free(&res_buf) != 0)
+ fail(8);
+ index = 0;
+ }
}
-
+
diff --git a/system/doc/tutorial/erl_comm.c b/system/doc/tutorial/erl_comm.c
index 303c6dc170..c4f8cb3117 100644
--- a/system/doc/tutorial/erl_comm.c
+++ b/system/doc/tutorial/erl_comm.c
@@ -1,44 +1,25 @@
/* erl_comm.c */
-typedef unsigned char byte;
-
-read_cmd(byte *buf)
-{
- int len;
-
- if (read_exact(buf, 2) != 2)
- return(-1);
- len = (buf[0] << 8) | buf[1];
- return read_exact(buf, len);
-}
-
-write_cmd(byte *buf, int len)
-{
- byte li;
+#include <stdio.h>
+#include <unistd.h>
- li = (len >> 8) & 0xff;
- write_exact(&li, 1);
-
- li = len & 0xff;
- write_exact(&li, 1);
-
- return write_exact(buf, len);
-}
+typedef unsigned char byte;
-read_exact(byte *buf, int len)
+int read_exact(byte *buf, int len)
{
int i, got=0;
do {
- if ((i = read(0, buf+got, len-got)) <= 0)
- return(i);
+ if ((i = read(0, buf+got, len-got)) <= 0){
+ return(i);
+ }
got += i;
} while (got<len);
return(len);
}
-write_exact(byte *buf, int len)
+int write_exact(byte *buf, int len)
{
int i, wrote = 0;
@@ -50,3 +31,27 @@ write_exact(byte *buf, int len)
return (len);
}
+
+int read_cmd(byte *buf)
+{
+ int len;
+
+ if (read_exact(buf, 2) != 2)
+ return(-1);
+ len = (buf[0] << 8) | buf[1];
+ return read_exact(buf, len);
+}
+
+int write_cmd(byte *buf, int len)
+{
+ byte li;
+
+ li = (len >> 8) & 0xff;
+ write_exact(&li, 1);
+
+ li = len & 0xff;
+ write_exact(&li, 1);
+
+ return write_exact(buf, len);
+}
+
diff --git a/system/doc/tutorial/erl_interface.xmlsrc b/system/doc/tutorial/erl_interface.xmlsrc
index bf9bd36597..956e515ca7 100644
--- a/system/doc/tutorial/erl_interface.xmlsrc
+++ b/system/doc/tutorial/erl_interface.xmlsrc
@@ -31,9 +31,9 @@
</header>
<p>This section outlines an example of how to solve the example
- problem in <seealso marker="example">Problem Example</seealso> by
+ problem in <seeguide marker="example">Problem Example</seeguide> by
using a port and Erl_Interface. It is necessary to read the port
- example in <seealso marker="c_port">Ports</seealso> before reading
+ example in <seeguide marker="c_port">Ports</seeguide> before reading
this section.</p>
<section>
@@ -42,8 +42,8 @@
with a C program over a plain port with home made encoding:</p>
<codeinclude file="complex1.erl" type="erl"/>
<p>There are two differences when using Erl_Interface on the C
- side compared to the example in <seealso marker="c_port">
- Ports</seealso>, using only the plain port:</p>
+ side compared to the example in <seeguide marker="c_port">
+ Ports</seeguide>, using only the plain port:</p>
<list type="bulleted">
<item>As Erl_Interface operates on the Erlang external term format,
the port must be set to use binaries.</item>
@@ -83,88 +83,27 @@ end</pre>
<section>
<title>C Program</title>
<p>The following example shows a C program communicating with an
- Erlang program over a plain port with home made encoding:</p>
- <codeinclude file="port.c" type="c"/>
- <p>Compared to the C program in <seealso marker="c_port">
- Ports</seealso>, using only the plain port, the
- <c>while</c>-loop must be rewritten. Messages coming from the
- port is on the Erlang external term format. They must be
- converted into an <c>ETERM</c> struct, which is a C struct
- similar to an Erlang term. The result of calling <c>foo()</c> or
- <c>bar()</c> must be converted to the Erlang external term
- format before being sent back to the port. But before calling
- any other Erl_Interface function, the memory handling must be
- initiated:</p>
- <pre>
-erl_init(NULL, 0);</pre>
- <p>The following functions, <c>read_cmd()</c> and
+ Erlang program over a plain port with the Erlang external term format encoding:</p>
+ <codeinclude file="ei.c" type="c"/>
+ <p>The following functions, <c>read_cmd()</c> and
<c>write_cmd()</c>, from the <c>erl_comm.c</c> example in
- <seealso marker="c_port">Ports</seealso> can still be
+ <seeguide marker="c_port">Ports</seeguide> can still be
used for reading from and writing to the port:
</p>
<codeinclude file="erl_comm.c" type="c"/>
- <p>The function <c>erl_decode()</c> from <c>erl_marshal</c>
- converts the binary into an <c>ETERM</c> struct:</p>
- <pre>
-int main() {
- ETERM *tuplep;
- while (read_cmd(buf) > 0) {
- tuplep = erl_decode(buf);</pre>
- <p>Here, <c>tuplep</c> points to an <c>ETERM</c> struct
- representing a tuple with two elements; the function name (atom)
- and the argument (integer). Using the function
- <c>erl_element()</c> from <c>erl_eterm</c>, these elements can
- be extracted, but they must also be declared as pointers to an
- <c>ETERM</c> struct:</p>
- <pre>
- fnp = erl_element(1, tuplep);
- argp = erl_element(2, tuplep);</pre>
- <p>The macros <c>ERL_ATOM_PTR</c> and <c>ERL_INT_VALUE</c> from
- <c>erl_eterm</c> can be used to obtain the actual values of the
- atom and the integer. The atom value is represented as a string.
- By comparing this value with the strings "foo" and "bar", it can
- be decided which function to call:</p>
- <pre>
- if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) {
- res = foo(ERL_INT_VALUE(argp));
- } else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) {
- res = bar(ERL_INT_VALUE(argp));
- }</pre>
- <p>Now an <c>ETERM</c> struct that represents the integer result
- can be constructed using the function <c>erl_mk_int()</c> from
- <c>erl_eterm</c>. The function
- <c>erl_format()</c> from the module <c>erl_format</c> can also
- be used:</p>
- <pre>
- intp = erl_mk_int(res);</pre>
- <p>The resulting <c>ETERM</c> struct is converted into the Erlang
- external term format using the function <c>erl_encode()</c> from
- <c>erl_marshal</c> and sent to Erlang using
- <c>write_cmd()</c>:</p>
- <pre>
- erl_encode(intp, buf);
- write_cmd(buf, erl_eterm_len(intp));</pre>
- <p>Finally, the memory allocated by the <c>ETERM</c> creating
- functions must be freed:</p>
- <pre>
- erl_free_compound(tuplep);
- erl_free_term(fnp);
- erl_free_term(argp);
- erl_free_term(intp);</pre>
- <p>The resulting C program is as follows:</p>
- <codeinclude file="ei.c" type="c"/>
+
</section>
<section>
<title>Running the Example</title>
<p><em>Step 1.</em> Compile the C code. This provides the paths to
- the include files <c>erl_interface.h</c> and <c>ei.h</c>, and
- also to the libraries <c>erl_interface</c> and <c>ei</c>:</p>
+ the include file <c>ei.h</c>, and
+ also to the library <c>ei</c>:</p>
<pre>
-unix> <input>gcc -o extprg -I/usr/local/otp/lib/erl_interface-3.9.2/include \\ </input>
-<input> -L/usr/local/otp/lib/erl_interface-3.9.2/lib \\ </input>
-<input> complex.c erl_comm.c ei.c -lerl_interface -lei -lpthread</input></pre>
+unix> <input>gcc -o extprg -I/usr/local/otp/lib/erl_interface-3.9.2/include \ </input>
+<input> -L/usr/local/otp/lib/erl_interface-3.9.2/lib \ </input>
+<input> complex.c erl_comm.c ei.c -lei -lpthread</input></pre>
<p>In Erlang/OTP R5B and later versions of OTP, the <c>include</c>
and <c>lib</c> directories are situated under
<c>OTPROOT/lib/erl_interface-VSN</c>, where <c>OTPROOT</c> is
diff --git a/system/doc/tutorial/nif.xmlsrc b/system/doc/tutorial/nif.xmlsrc
index ccd49b228d..637b764c66 100644
--- a/system/doc/tutorial/nif.xmlsrc
+++ b/system/doc/tutorial/nif.xmlsrc
@@ -30,7 +30,7 @@
<file>nif.xml</file>
</header>
<p>This section outlines an example of how to solve the example
- problem in <seealso marker="example">Problem Example</seealso>
+ problem in <seeguide marker="example">Problem Example</seeguide>
by using Native Implemented Functions (NIFs).</p>
<p>NIFs were introduced in Erlang/OTP R13B03 as an experimental
feature. It is a simpler and more efficient way of calling C-code
diff --git a/system/doc/tutorial/overview.xml b/system/doc/tutorial/overview.xml
index 1312270e9d..bb08839c9c 100644
--- a/system/doc/tutorial/overview.xml
+++ b/system/doc/tutorial/overview.xml
@@ -52,22 +52,22 @@
for Erlang-Erlang communication. It can also be used for
communication between Erlang and C, if the C program is
implemented as a C node, see
- <seealso marker="#cnode">C and Java Libraries</seealso>.</p>
+ <seeguide marker="#cnode">C and Java Libraries</seeguide>.</p>
<p><em>Where to read more:</em> Distributed Erlang and some distributed
programming techniques are described in the Erlang book.</p>
- <p>For more information, see <seealso
- marker="doc/getting_started:conc_prog#Distributed Programming">
- Distributed Programming.</seealso></p>
+ <p>For more information, see <seeguide
+ marker="system/getting_started:conc_prog#Distributed Programming">
+ Distributed Programming.</seeguide></p>
<p>Relevant manual pages are the following:</p>
<list type="bulleted">
- <item><seealso marker="erts:erlang">erlang</seealso> manual page in ERTS
+ <item><seeerl marker="erts:erlang">erlang</seeerl> manual page in ERTS
(describes the BIFs)</item>
- <item><seealso marker="kernel:global">global</seealso> manual page in Kernel</item>
- <item><seealso marker="kernel:net_adm">net_adm</seealso> manual page in Kernel</item>
- <item><seealso marker="kernel:pg2">pg2</seealso> manual page in Kernel</item>
- <item><seealso marker="kernel:rpc">rpc</seealso> manual page in Kernel</item>
- <item><seealso marker="stdlib:pool">pool</seealso> manual page in STDLIB</item>
- <item><seealso marker="stdlib:slave">slave</seealso> manual page in STDLIB</item>
+ <item><seeerl marker="kernel:global">global</seeerl> manual page in Kernel</item>
+ <item><seeerl marker="kernel:net_adm">net_adm</seeerl> manual page in Kernel</item>
+ <item><seeerl marker="kernel:pg">pg</seeerl> manual page in Kernel</item>
+ <item><seeerl marker="kernel:rpc">rpc</seeerl> manual page in Kernel</item>
+ <item><seeerl marker="stdlib:pool">pool</seeerl> manual page in STDLIB</item>
+ <item><seeerl marker="stdlib:slave">slave</seeerl> manual page in STDLIB</item>
</list>
</section>
@@ -109,13 +109,13 @@
"Miscellaneous Items" of the Erlang book. Linked-in drivers
are described in Appendix E.</p>
<p>The BIF <c>open_port/2</c> is documented in the
- <seealso marker="erts:erlang">erlang</seealso> manual page in
+ <seeerl marker="erts:erlang">erlang</seeerl> manual page in
ERTS.</p>
<p>For linked-in drivers, the programmer needs to read the
- <seealso marker="kernel:erl_ddll">erl_ddll</seealso> manual
+ <seeerl marker="kernel:erl_ddll">erl_ddll</seeerl> manual
page in Kernel.</p>
- <p><em>Examples:</em> Port example in <seealso marker="c_port">
- Ports</seealso>.</p>
+ <p><em>Examples:</em> Port example in <seeguide marker="c_port">
+ Ports</seeguide>.</p>
</section>
</section>
@@ -126,25 +126,7 @@
<title>Erl_Interface</title>
<p>The program at the other side of a port is often a C program.
To help the C programmer, the Erl_Interface library
- has been developed, including the following five parts:</p>
- <list type="bulleted">
- <item>
- <c>erl_marshal</c>, <c>erl_eterm</c>, <c>erl_format</c>, and
- <c>erl_malloc</c>: Handling of the Erlang external term format</item>
- <item>
- <c>erl_connect</c>:
- Communication with distributed Erlang, see <seealso
- marker="#cnode">C nodes</seealso> below</item>
- <item>
- <c>erl_error</c>:
- Error print routines</item>
- <item>
- <c>erl_global</c>:
- Access globally registered names</item>
- <item>
- <c>Registry</c>:
- Store and backup of key-value pairs</item>
- </list>
+ has been developed</p>
<p>The Erlang external term format is a representation of an
Erlang term as a sequence of bytes, that is, a binary.
Conversion between the two representations is done using the
@@ -165,7 +147,7 @@ Term = binary_to_term(Binary)</pre>
R5B, and earlier versions, the information is part of the
Kernel application.</p> </section>
<p><em>Examples:</em> Erl_Interface example in
- <seealso marker="erl_interface">Erl_Interface</seealso>.</p>
+ <seeguide marker="erl_interface">Erl_Interface</seeguide>.</p>
<marker id="cnode"></marker>
<section>
@@ -181,14 +163,15 @@ Term = binary_to_term(Binary)</pre>
processors (as opposed to control processors) where C is a
better choice than Erlang due to memory limitations or
application characteristics, or both.</p>
- <p><em>Where to read more:</em> See the <c>erl_connect</c> part
- of the Erl_Interface documentation. The programmer also needs
- to be familiar with TCP/IP sockets, see Sockets in <seealso
- marker="#sockets">Standard
- Protocols</seealso> and Distributed Erlang in <seealso
- marker="#dist">Built-In Mechanisms</seealso>.</p>
- <p><em>Example:</em> C node example in <seealso marker="cnode">
- C Nodes</seealso>.</p>
+ <p><em>Where to read more:</em> See the <c>ei_connect</c> part
+ of the <seeguide marker="erl_interface">Erl_Interface</seeguide>
+ documentation. The programmer also needs to be familiar with
+ TCP/IP sockets, see Sockets in <seeguide
+ marker="#sockets">Standard Protocols</seeguide> and Distributed
+ Erlang in <seeguide marker="#dist">Built-In
+ Mechanisms</seeguide>.</p>
+ <p><em>Example:</em> C node example in <seeguide marker="cnode">
+ C Nodes</seeguide>.</p>
</section>
<section>
@@ -237,9 +220,9 @@ Term = binary_to_term(Binary)</pre>
detailed knowledge about the socket concept.</p>
<p><em>When to use:</em> For programs running on the same or on
another machine than the Erlang program.</p>
- <p><em>Where to read more:</em> See the <seealso
- marker="kernel:gen_tcp">gen_tcp</seealso> and the <seealso
- marker="kernel:gen_udp">gen_udp</seealso> manual pages in
+ <p><em>Where to read more:</em> See the <seeerl
+ marker="kernel:gen_tcp">gen_tcp</seeerl> and the <seeerl
+ marker="kernel:gen_udp">gen_udp</seeerl> manual pages in
Kernel.</p>
</section>
</section>
diff --git a/xcomp/erl-xcomp-arm-android.conf b/xcomp/erl-xcomp-arm-android.conf
index 44ff7537d3..6d25d48f0a 100644
--- a/xcomp/erl-xcomp-arm-android.conf
+++ b/xcomp/erl-xcomp-arm-android.conf
@@ -2,7 +2,7 @@
##
## %CopyrightBegin%
##
-## Copyright Ericsson AB 2009-2010. All Rights Reserved.
+## Copyright Ericsson AB 2009-2019. All Rights Reserved.
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@
##
## %CopyrightEnd%
##
-## File: erl-xcomp.conf.template
-## Author:
+## File: erl-xcomp-arm-android.conf
+## Author: Dmitry Kolesnikov
##
## -----------------------------------------------------------------------------
## When cross compiling Erlang/OTP using `otp_build', copy this file and set
@@ -60,14 +60,21 @@ erl_xcomp_host=arm-linux-androideabi
# * `erl_xcomp_configure_flags' - Extra configure flags to pass to the
# `configure' script.
-erl_xcomp_configure_flags="--disable-hipe --without-termcap"
+# Set --enable-builtin-zlib to avoid a inflateGetDictionary missing symbol
+erl_xcomp_configure_flags="--disable-hipe --without-termcap --without-wx \
+ --enable-builtin-zlib"
## -- Cross Compiler and Other Tools -------------------------------------------
##
##
-NDK_SYSROOT=$NDK_ROOT/platforms/$NDK_PLAT/arch-arm
+# Previously with older Android NDK versions before NDK r14, each Android
+# API level had a different set of headers, each in its own directory.
+#NDK_SYSROOT=$NDK_ROOT/platforms/$NDK_PLAT/arch-arm
+# Starting with Android NDK r14, a single set of headers (called Unified Headers)
+# is provided in one location for every Android API level.
+NDK_SYSROOT=$NDK_ROOT/sysroot
## If the cross compilation tools are prefixed by `<HOST>-' you probably do
## not need to set these variables (where `<HOST>' is what has been passed as
@@ -76,10 +83,15 @@ NDK_SYSROOT=$NDK_ROOT/platforms/$NDK_PLAT/arch-arm
## All variables in this section can also be used when native compiling.
# * `CC' - C compiler.
-CC="arm-linux-androideabi-gcc --sysroot=$NDK_SYSROOT"
+#
+# For older Android NDK versions still supporting GCC.
+#CC="arm-linux-androideabi-gcc --sysroot=$NDK_SYSROOT"
+# For more recent Android NDK versions only supporting Clang/LLVM.
+#NDK_ABI_PLAT=androideabi16 # when targeting Android 4.1 Jelly Bean
+CC="armv7a-linux-$NDK_ABI_PLAT-clang"
# * `CFLAGS' - C compiler flags.
-CFLAGS="-static -march=armv7-a -msoft-float -mthumb"
+CFLAGS="-g -O2 -march=armv7-a -mfloat-abi=softfp -mthumb"
# * `STATIC_CFLAGS' - Static C compiler flags.
#STATIC_CFLAGS=
@@ -90,22 +102,38 @@ CFLAGS="-static -march=armv7-a -msoft-float -mthumb"
#CFLAG_RUNTIME_LIBRARY_PATH=
# * `CPP' - C pre-processor.
-CPP="arm-linux-androideabi-cpp --sysroot=$NDK_SYSROOT"
+#
+# For older Android NDK versions still supporting GCC.
+#CPP="arm-linux-androideabi-cpp --sysroot=$NDK_SYSROOT"
+# Not needed when using Clang/LLVM.
+#CPP=
# * `CPPFLAGS' - C pre-processor flags.
-CPPFLAGS="-static -march=armv7-a -msoft-float -mthumb"
+#
+# For older Android NDK versions still supporting GCC.
+#CPPFLAGS="-static -march=armv7-a -msoft-float -mthumb"
+# Not needed when using Clang/LLVM.
+#CPPFLAGS=
# * `CXX' - C++ compiler.
-CXX="arm-linux-androideabi-c++ --sysroot=$NDK_SYSROOT"
+#
+# For older Android NDK versions still supporting GCC.
+#CXX="arm-linux-androideabi-c++ --sysroot=$NDK_SYSROOT
+# For more recent Android NDK versions only supporting Clang/LLVM.
+CXX="armv7a-linux-$NDK_ABI_PLAT-clang++"
# * `CXXFLAGS' - C++ compiler flags.
-CXXFLAGS="-static -march=armv7-a -msoft-float -mthumb"
+CXXFLAGS="-march=armv7-a -mfloat-abi=softfp -mthumb"
# * `LD' - Linker.
+#
+# Not needed when using GCC.
#LD=
+# For more recent Android NDK versions only supporting Clang/LLVM.
+LD="arm-linux-androideabi-ld"
# * `LDFLAGS' - Linker flags.
-LDFLAGS="-static -march=armv7-a -msoft-float -mthumb"
+LDFLAGS="-march=armv7-a -mfloat-abi=softfp -mthumb"
# * `LIBS' - Libraries.
#LIBS=
diff --git a/xcomp/erl-xcomp-vxworks_ppc32.conf b/xcomp/erl-xcomp-arm64-android.conf
index f1655caa4e..e423651fed 100644
--- a/xcomp/erl-xcomp-vxworks_ppc32.conf
+++ b/xcomp/erl-xcomp-arm64-android.conf
@@ -2,7 +2,7 @@
##
## %CopyrightBegin%
##
-## Copyright Ericsson AB 2009-2012. All Rights Reserved.
+## Copyright Ericsson AB 2019. All Rights Reserved.
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@
##
## %CopyrightEnd%
##
-## File: erl-xcomp-vxworks_ppc32.conf
-## Author: Rickard Green/Patrik Nyblom
+## File: erl-xcomp-arm64-android.conf
+## Author: Jérôme de Bretagne
##
## -----------------------------------------------------------------------------
## When cross compiling Erlang/OTP using `otp_build', copy this file and set
@@ -56,14 +56,26 @@ erl_xcomp_build=guess
# It does not have to be a full `CPU-VENDOR-OS' triplet, but can be. The
# full `CPU-VENDOR-OS' triplet will be created by
# `$ERL_TOP/erts/autoconf/config.sub $erl_xcomp_host'.
-erl_xcomp_host=powerpc-wrs-vxworks
+erl_xcomp_host=aarch64-linux-android
# * `erl_xcomp_configure_flags' - Extra configure flags to pass to the
# `configure' script.
-erl_xcomp_configure_flags="--disable-threads --without-termcap"
+# Set --enable-builtin-zlib to avoid a inflateGetDictionary missing symbol
+erl_xcomp_configure_flags="--disable-hipe --without-termcap --without-wx \
+ --enable-builtin-zlib"
+
## -- Cross Compiler and Other Tools -------------------------------------------
+##
+##
+# Previously with older Android NDK versions before NDK r14, each Android
+# API level had a different set of headers, each in its own directory.
+#NDK_SYSROOT=$NDK_ROOT/platforms/$NDK_PLAT/arch-arm
+# Starting with Android NDK r14, a single set of headers (called Unified Headers)
+# is provided in one location for every Android API level.
+NDK_SYSROOT=$NDK_ROOT/sysroot
+
## If the cross compilation tools are prefixed by `<HOST>-' you probably do
## not need to set these variables (where `<HOST>' is what has been passed as
## `--host=<HOST>' argument to `configure').
@@ -71,10 +83,13 @@ erl_xcomp_configure_flags="--disable-threads --without-termcap"
## All variables in this section can also be used when native compiling.
# * `CC' - C compiler.
-CC="$WIND_BASE/gnu/3.4.4-vxworks-6.3/$WIND_HOST_TYPE/bin/ccppc"
+#
+# For recent Android NDK versions only supporting Clang/LLVM.
+#NDK_ABI_PLAT=android21 # when targeting Android 5.0 Lollipop
+CC="aarch64-linux-$NDK_ABI_PLAT-clang"
# * `CFLAGS' - C compiler flags.
-CFLAGS="-DCPU=PPC32 -DTOOL_FAMILY=gnu -DTOOL=gnu -DWANT_NONBLOCKING -DHAVE_SENS -DHAVE_MEMMOVE -DVXWORKS -I$WIND_BASE/vxworks-6.3/target/h -I$WIND_BASE/gnu/3.4.4-vxworks-6.3/$WIND_HOST_TYPE/lib/gcc/powerpc-wrs-vxworks/3.4.4/include -I$WIND_BASE/vxworks-6.3/target/h/wrn/coreip -I$WIND_BASE/vxworks-6.3/target/h -mstrict-align -fvolatile -fno-builtin -mlongcall"
+CFLAGS="-g -O2"
# * `STATIC_CFLAGS' - Static C compiler flags.
#STATIC_CFLAGS=
@@ -85,22 +100,30 @@ CFLAGS="-DCPU=PPC32 -DTOOL_FAMILY=gnu -DTOOL=gnu -DWANT_NONBLOCKING -DHAVE_SENS
#CFLAG_RUNTIME_LIBRARY_PATH=
# * `CPP' - C pre-processor.
-CPP="$CC $CFLAGS -E"
+#
+# Not needed when using Clang/LLVM.
+#CPP=
# * `CPPFLAGS' - C pre-processor flags.
+#
+# Not needed when using Clang/LLVM.
#CPPFLAGS=
# * `CXX' - C++ compiler.
-#CXX=
+#
+# For recent Android NDK versions only supporting Clang/LLVM.
+CXX="aarch64-linux-$NDK_ABI_PLAT-clang++"
# * `CXXFLAGS' - C++ compiler flags.
#CXXFLAGS=
# * `LD' - Linker.
-LD="$WIND_BASE/gnu/3.4.4-vxworks-6.3/$WIND_HOST_TYPE/bin/ldppc"
+#
+# For recent Android NDK versions only supporting Clang/LLVM.
+LD="aarch64-linux-android-ld"
# * `LDFLAGS' - Linker flags.
-LDFLAGS="-mlongcall"
+#LDFLAGS=
# * `LIBS' - Libraries.
#LIBS=
@@ -135,10 +158,10 @@ LDFLAGS="-mlongcall"
## -- Other Tools --
# * `RANLIB' - `ranlib' archive index tool.
-RANLIB="$WIND_BASE/gnu/3.4.4-vxworks-6.3/$WIND_HOST_TYPE/bin/ranlibppc"
+#RANLIB=
# * `AR' - `ar' archiving tool.
-AR="$WIND_BASE/gnu/3.4.4-vxworks-6.3/$WIND_HOST_TYPE/bin/arppc"
+#AR=
# * `GETCONF' - `getconf' system configuration inspection tool. `getconf' is
# currently used for finding out large file support flags to use, and
@@ -154,7 +177,8 @@ AR="$WIND_BASE/gnu/3.4.4-vxworks-6.3/$WIND_HOST_TYPE/bin/arppc"
# skipped if the system root has not been set. The system root might be
# needed for other things too. If this is the case and the system root
# has not been set, `configure' will fail and request you to set it.
-#erl_xcomp_sysroot=
+erl_xcomp_sysroot="$NDK_SYSROOT"
+
# * `erl_xcomp_isysroot' - The absolute path to the system root for includes
# of the cross compilation environment. If not set, this value defaults
@@ -186,16 +210,7 @@ AR="$WIND_BASE/gnu/3.4.4-vxworks-6.3/$WIND_HOST_TYPE/bin/arppc"
# automatically detected, but not always. If not automatically detected,
# `configure' will fail unless this variable is set. Since no default
# value is used, `configure' will try to figure this out automatically.
-erl_xcomp_bigendian=yes
-
-# * `erl_xcomp_double_middle_endian` - `yes|no`. No default. If `yes`, the
-# target system must have doubles in "middle-endian" format. If
-# `no`, it has "regular" endianness. This can often be automatically
-# detected, but not always. If not automatically detected,
-# `configure` will fail unless this variable is set. Since no
-# default value is used, `configure` will try to figure this out
-# automatically.
-#erl_xcomp_double_middle_endian
+#erl_xcomp_bigendian=
# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes',
# the target system must have a working `clock_gettime()' implementation